From d225fc835fa13cb51c3e0655cb025eba24a8cdac Mon Sep 17 00:00:00 2001 From: johnalan Date: Mon, 4 Jan 2010 17:35:12 +0000 Subject: [PATCH] =?UTF-8?q?09.1.5=E6=B7=BB=E5=8A=A0=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- T3D/aiClient.cpp | 542 + T3D/aiClient.h | 97 + T3D/aiConnection.cpp | 193 + T3D/aiConnection.h | 39 + T3D/aiPlayer.cpp | 572 + T3D/aiPlayer.h | 79 + T3D/ambientAudioManager.cpp | 260 + T3D/ambientAudioManager.h | 60 + T3D/camera.cpp | 1724 ++ T3D/camera.h | 184 + T3D/cameraSpline.cpp | 370 + T3D/cameraSpline.h | 91 + T3D/containerQuery.cpp | 80 + T3D/containerQuery.h | 54 + T3D/debris.cpp | 823 + T3D/debris.h | 160 + T3D/decal/decalData.cpp | 362 + T3D/decal/decalData.h | 106 + T3D/decal/decalDataFile.cpp | 436 + T3D/decal/decalDataFile.h | 97 + T3D/decal/decalInstance.cpp | 60 + T3D/decal/decalInstance.h | 81 + T3D/decal/decalManager.cpp | 1386 ++ T3D/decal/decalManager.h | 240 + T3D/examples/renderMeshExample.cpp | 324 + T3D/examples/renderMeshExample.h | 115 + T3D/examples/renderObjectExample.cpp | 263 + T3D/examples/renderObjectExample.h | 117 + T3D/examples/renderShapeExample.cpp | 259 + T3D/examples/renderShapeExample.h | 103 + T3D/fps/guiClockHud.cpp | 125 + T3D/fps/guiCrossHairHud.cpp | 152 + T3D/fps/guiHealthBarHud.cpp | 146 + T3D/fps/guiShapeNameHud.cpp | 291 + T3D/fps/guiShapeNameHud.h | 65 + T3D/fx/cameraFXMgr.cpp | 160 + T3D/fx/cameraFXMgr.h | 90 + T3D/fx/explosion.cpp | 1092 ++ T3D/fx/explosion.h | 184 + T3D/fx/fxFoliageReplicator.cpp | 1823 +++ T3D/fx/fxFoliageReplicator.h | 374 + T3D/fx/fxShapeReplicator.cpp | 742 + T3D/fx/fxShapeReplicator.h | 183 + T3D/fx/groundCover.cpp | 1693 ++ T3D/fx/groundCover.h | 352 + T3D/fx/lightning.cpp | 1190 ++ T3D/fx/lightning.h | 221 + T3D/fx/particle.cpp | 503 + T3D/fx/particle.h | 109 + T3D/fx/particleEmitter.cpp | 1848 +++ T3D/fx/particleEmitter.h | 258 + T3D/fx/particleEmitterNode.cpp | 321 + T3D/fx/particleEmitterNode.h | 99 + T3D/fx/precipitation.cpp | 1691 ++ T3D/fx/precipitation.h | 271 + T3D/fx/splash.cpp | 672 + T3D/fx/splash.h | 183 + T3D/fx/windEmitter.cpp | 119 + T3D/fx/windEmitter.h | 79 + T3D/gameBase.cpp | 591 + T3D/gameBase.h | 401 + T3D/gameConnection.cpp | 2156 +++ T3D/gameConnection.h | 354 + T3D/gameConnectionEvents.cpp | 334 + T3D/gameConnectionEvents.h | 96 + T3D/gameFunctions.cpp | 431 + T3D/gameFunctions.h | 47 + T3D/gameProcess.cpp | 412 + T3D/gameProcess.h | 63 + T3D/gameTSCtrl.cpp | 162 + T3D/gameTSCtrl.h | 60 + T3D/groundPlane.cpp | 525 + T3D/groundPlane.h | 138 + T3D/guiMaterialPreview.cpp | 475 + T3D/guiMaterialPreview.h | 108 + T3D/guiNoMouseCtrl.cpp | 19 + T3D/guiObjectView.cpp | 382 + T3D/guiObjectView.h | 100 + T3D/hifi/gameProcess.cpp | 685 + T3D/hifi/gameProcess.h | 74 + T3D/hifi/moveList.cpp | 553 + T3D/hifi/moveList.h | 112 + T3D/item.cpp | 1079 ++ T3D/item.h | 155 + T3D/levelInfo.cpp | 202 + T3D/levelInfo.h | 72 + T3D/lightAnimData.cpp | 119 + T3D/lightAnimData.h | 70 + T3D/lightBase.cpp | 460 + T3D/lightBase.h | 122 + T3D/lightDescription.cpp | 201 + T3D/lightDescription.h | 101 + T3D/lightFlareData.cpp | 586 + T3D/lightFlareData.h | 106 + T3D/missionArea.cpp | 135 + T3D/missionArea.h | 56 + T3D/missionMarker.cpp | 452 + T3D/missionMarker.h | 210 + T3D/moveList.cpp | 320 + T3D/moveList.h | 88 + T3D/moveManager.cpp | 238 + T3D/moveManager.h | 83 + T3D/objectTypes.h | 82 + T3D/pathCamera.cpp | 512 + T3D/pathCamera.h | 110 + T3D/physicalZone.cpp | 404 + T3D/physicalZone.h | 86 + T3D/physics/physicsObject.cpp | 7 + T3D/physics/physicsObject.h | 32 + T3D/physics/physicsPlayer.cpp | 8 + T3D/physics/physicsPlayer.h | 56 + T3D/physics/physicsPlugin.cpp | 164 + T3D/physics/physicsPlugin.h | 107 + T3D/physics/physicsStatic.cpp | 10 + T3D/physics/physicsStatic.h | 45 + T3D/physics/physicsWorld.cpp | 7 + T3D/physics/physicsWorld.h | 42 + T3D/physics/physx/px.h | 55 + T3D/physics/physx/pxBoxPlayer.cpp | 164 + T3D/physics/physx/pxBoxPlayer.h | 46 + T3D/physics/physx/pxCapsulePlayer.cpp | 167 + T3D/physics/physx/pxCapsulePlayer.h | 43 + T3D/physics/physx/pxCasts.h | 118 + T3D/physics/physx/pxCloth.cpp | 890 + T3D/physics/physx/pxCloth.h | 167 + T3D/physics/physx/pxContactReporter.cpp | 92 + T3D/physics/physx/pxContactReporter.h | 37 + T3D/physics/physx/pxFluid.cpp | 291 + T3D/physics/physx/pxFluid.h | 88 + T3D/physics/physx/pxMaterial.cpp | 109 + T3D/physics/physx/pxMaterial.h | 54 + T3D/physics/physx/pxMeshRoad.cpp | 177 + T3D/physics/physx/pxMeshRoad.h | 64 + T3D/physics/physx/pxMultiActor.cpp | 2629 +++ T3D/physics/physx/pxMultiActor.h | 333 + T3D/physics/physx/pxPlane.cpp | 87 + T3D/physics/physx/pxPlane.h | 49 + T3D/physics/physx/pxPlayer.cpp | 328 + T3D/physics/physx/pxPlayer.h | 95 + T3D/physics/physx/pxPlugin.cpp | 217 + T3D/physics/physx/pxPlugin.h | 40 + T3D/physics/physx/pxSingleActor.cpp | 1137 ++ T3D/physics/physx/pxSingleActor.h | 210 + T3D/physics/physx/pxStream.cpp | 162 + T3D/physics/physx/pxStream.h | 63 + T3D/physics/physx/pxTSStatic.cpp | 390 + T3D/physics/physx/pxTSStatic.h | 76 + T3D/physics/physx/pxTerrain.cpp | 215 + T3D/physics/physx/pxTerrain.h | 77 + T3D/physics/physx/pxUserData.cpp | 82 + T3D/physics/physx/pxUserData.h | 133 + T3D/physics/physx/pxUtils.cpp | 92 + T3D/physics/physx/pxUtils.h | 21 + T3D/physics/physx/pxWorld.cpp | 858 + T3D/physics/physx/pxWorld.h | 167 + T3D/player.cpp | 5015 ++++++ T3D/player.h | 631 + T3D/pointLight.cpp | 108 + T3D/pointLight.h | 43 + T3D/portal.cpp | 416 + T3D/portal.h | 95 + T3D/projectile.cpp | 1278 ++ T3D/projectile.h | 245 + T3D/resource.h | 18 + T3D/rigid.cpp | 349 + T3D/rigid.h | 83 + T3D/rigidShape.cpp | 1588 ++ T3D/rigidShape.h | 293 + T3D/sceneComponent/T3DSceneClient.cpp | 90 + T3D/sceneComponent/T3DSceneClient.h | 76 + T3D/sceneComponent/T3DSceneComponent.cpp | 272 + T3D/sceneComponent/T3DSceneComponent.h | 161 + T3D/sceneComponent/T3DTransform.cpp | 369 + T3D/sceneComponent/T3DTransform.h | 201 + T3D/scopeAlwaysShape.cpp | 29 + T3D/sfxEmitter.cpp | 619 + T3D/sfxEmitter.h | 151 + T3D/shapeBase.cpp | 4298 +++++ T3D/shapeBase.h | 1692 ++ T3D/shapeCollision.cpp | 44 + T3D/shapeImage.cpp | 2028 +++ T3D/sphere.cpp | 233 + T3D/sphere.h | 71 + T3D/spotLight.cpp | 146 + T3D/spotLight.h | 47 + T3D/staticShape.cpp | 233 + T3D/staticShape.h | 76 + T3D/tickCache.cpp | 157 + T3D/tickCache.h | 52 + T3D/trigger.cpp | 746 + T3D/trigger.h | 136 + T3D/tsStatic.cpp | 961 ++ T3D/tsStatic.h | 180 + T3D/vehicles/flyingVehicle.cpp | 726 + T3D/vehicles/flyingVehicle.h | 183 + T3D/vehicles/guiSpeedometer.cpp | 144 + T3D/vehicles/hoverVehicle.cpp | 858 + T3D/vehicles/hoverVehicle.h | 196 + T3D/vehicles/vehicle.cpp | 1713 ++ T3D/vehicles/vehicle.h | 281 + T3D/vehicles/vehicleBlocker.cpp | 130 + T3D/vehicles/vehicleBlocker.h | 44 + T3D/vehicles/wheeledVehicle.cpp | 1517 ++ T3D/vehicles/wheeledVehicle.h | 233 + T3D/zone.cpp | 457 + T3D/zone.h | 96 + add/GUI/GuiCellArray.cpp | 462 + add/GUI/GuiCellArray.h | 81 + add/GUI/GuiRPGStatuBar.cpp | 152 + add/GUI/GuiRPGStatuBar.h | 0 add/GUI/GuiRPGStatuBox.cpp | 21 + add/GUI/GuiRPGStatuBox.h | 23 + add/GUI/GuiRPGStatuLabel.cpp | 14 + add/GUI/GuiRPGStatuLabel.h | 21 + add/GUI/GuiRPGTsCtrl.cpp | 181 + add/GUI/GuiRPGTsCtrl.h | 44 + add/GUI/GuiRadarMap.cpp | 144 + add/GUI/GuiRadarMap.h | 26 + add/GUI/GuiRoleList.cpp | 252 + add/GUI/GuiRoleList.h | 60 + add/Global/GlobalStatic.cpp | 247 + add/Global/GlobalStatic.h | 60 + add/Others/CSLock.h | 14 + add/Others/LockFreeChunker.cpp | 60 + add/Others/LockFreeChunker.h | 171 + add/Others/StringHash.h | 36 + add/RPGPack/Constraint/CST_Caster.cpp | 34 + add/RPGPack/Constraint/CST_Caster.h | 22 + add/RPGPack/Constraint/CST_GameBase.cpp | 36 + add/RPGPack/Constraint/CST_GameBase.h | 20 + add/RPGPack/Constraint/CST_Player.cpp | 44 + add/RPGPack/Constraint/CST_Player.h | 23 + add/RPGPack/Constraint/CST_Point.cpp | 86 + add/RPGPack/Constraint/CST_Point.h | 28 + add/RPGPack/Constraint/CST_SceneObject.cpp | 105 + add/RPGPack/Constraint/CST_SceneObject.h | 32 + add/RPGPack/Constraint/CST_ShapeBase.cpp | 36 + add/RPGPack/Constraint/CST_ShapeBase.h | 20 + add/RPGPack/Constraint/CST_Target.cpp | 0 add/RPGPack/Constraint/CST_Target.h | 0 .../Constraint/CST_TerrainClickedPoint.cpp | 30 + .../Constraint/CST_TerrainClickedPoint.h | 19 + .../Constraint/CST_TerrainRolloverPoint.cpp | 28 + .../Constraint/CST_TerrainRolloverPoint.h | 19 + add/RPGPack/Constraint/Constraint.cpp | 71 + add/RPGPack/Constraint/Constraint.h | 52 + .../EffectPhrase/EffectWrapperDataPhrase.cpp | 19 + .../EffectPhrase/EffectWrapperDataPhrase.h | 20 + .../EffectPhrase/EffectWrapperPhrase.cpp | 102 + .../EffectPhrase/EffectWrapperPhrase.h | 32 + add/RPGPack/EffectWrapper/EffectWrapper.cpp | 57 + add/RPGPack/EffectWrapper/EffectWrapper.h | 30 + .../EffectWrapper/EffectWrapperData.cpp | 76 + add/RPGPack/EffectWrapper/EffectWrapperData.h | 27 + .../EffectWrapper/EffectWrapperDesc.cpp | 40 + add/RPGPack/EffectWrapper/EffectWrapperDesc.h | 29 + add/RPGPack/Effects/EFX_AnimClip.cpp | 111 + add/RPGPack/Effects/EFX_AnimClip.h | 45 + add/RPGPack/Effects/EFX_Decal.cpp | 131 + add/RPGPack/Effects/EFX_Decal.h | 49 + add/RPGPack/Effects/EFX_Particle.cpp | 133 + add/RPGPack/Effects/EFX_Particle.h | 47 + add/RPGPack/RPGBase.cpp | 152 + add/RPGPack/RPGBase.h | 47 + add/RPGPack/RPGBaseData.cpp | 70 + add/RPGPack/RPGBaseData.h | 36 + add/RPGPack/RPGBook.cpp | 340 + add/RPGPack/RPGBook.h | 66 + add/RPGPack/RPGBookData.cpp | 105 + add/RPGPack/RPGBookData.h | 34 + add/RPGPack/RPGDefs.h | 52 + add/RPGPack/RPGEffectron/RPGEffectron.cpp | 126 + add/RPGPack/RPGEffectron/RPGEffectron.h | 45 + add/RPGPack/RPGEffectron/RPGEffectronData.cpp | 131 + add/RPGPack/RPGEffectron/RPGEffectronData.h | 42 + add/RPGPack/RPGSpell/RPGSpell.cpp | 304 + add/RPGPack/RPGSpell/RPGSpell.h | 70 + add/RPGPack/RPGSpell/RPGSpellData.cpp | 149 + add/RPGPack/RPGSpell/RPGSpellData.h | 46 + add/RPGPack/RPGUtils.cpp | 86 + add/RPGPack/RPGUtils.h | 39 + add/WLLib/wlmgr.cpp | 210 + add/WLLib/wlmgr.h | 41 + app/auth.h | 42 + app/badWordFilter.cpp | 217 + app/badWordFilter.h | 49 + app/banList.cpp | 186 + app/banList.h | 49 + app/game.cpp | 228 + app/game.h | 20 + app/mainLoop.cpp | 567 + app/mainLoop.h | 36 + app/net/httpObject.cpp | 309 + app/net/httpObject.h | 64 + app/net/net.cpp | 277 + app/net/netTest.cpp | 84 + app/net/serverQuery.cpp | 2073 +++ app/net/serverQuery.h | 109 + app/net/tcpObject.cpp | 334 + app/net/tcpObject.h | 71 + app/version.cpp | 111 + app/version.h | 28 + cinterface/cinterface.cpp | 415 + cinterface/cinterface.h | 8 + collision/abstractPolyList.cpp | 97 + collision/abstractPolyList.h | 242 + collision/boxConvex.cpp | 199 + collision/boxConvex.h | 46 + collision/clippedPolyList.cpp | 431 + collision/clippedPolyList.h | 121 + collision/collision.h | 154 + collision/concretePolyList.cpp | 154 + collision/concretePolyList.h | 72 + collision/convex.cpp | 673 + collision/convex.h | 263 + collision/depthSortList.cpp | 833 + collision/depthSortList.h | 101 + collision/earlyOutPolyList.cpp | 266 + collision/earlyOutPolyList.h | 80 + collision/extrudedPolyList.cpp | 493 + collision/extrudedPolyList.h | 106 + collision/gjk.cpp | 370 + collision/gjk.h | 65 + collision/optimizedPolyList.cpp | 326 + collision/optimizedPolyList.h | 139 + collision/planeExtractor.cpp | 112 + collision/planeExtractor.h | 59 + collision/polyhedron.cpp | 72 + collision/polyhedron.h | 43 + collision/polytope.cpp | 412 + collision/polytope.h | 95 + component/componentInterface.cpp | 65 + component/componentInterface.h | 217 + component/dynamicConsoleMethodComponent.cpp | 162 + component/dynamicConsoleMethodComponent.h | 72 + component/interfaces/IProcessInput.h | 27 + component/moreAdvancedComponent.cpp | 84 + component/moreAdvancedComponent.h | 38 + component/simComponent.cpp | 428 + component/simComponent.h | 232 + component/simpleComponent.cpp | 128 + component/simpleComponent.h | 142 + console/CMDgram.y | 569 + console/CMDscan.cpp | 2522 +++ console/CMDscan.l | 596 + console/ICallMethod.h | 16 + console/SimXMLDocument.cpp | 1012 ++ console/SimXMLDocument.h | 132 + console/arrayObject.cpp | 710 + console/arrayObject.h | 192 + console/ast.h | 543 + console/astAlloc.cpp | 368 + console/astNodes.cpp | 1798 ++ console/bison.bat | 6 + console/bison.simple | 686 + console/cmdgram.cpp | 2318 +++ console/cmdgram.h | 89 + console/codeBlock.cpp | 661 + console/codeBlock.h | 136 + console/compiledEval.cpp | 1798 ++ console/compiler.cpp | 272 + console/compiler.h | 244 + console/console.cpp | 1384 ++ console/console.h | 914 ++ console/consoleCallback.cpp | 35 + console/consoleCallback.h | 116 + console/consoleDoc.cpp | 612 + console/consoleDoc.h | 176 + console/consoleFunctions.cpp | 1641 ++ console/consoleInternal.cpp | 1266 ++ console/consoleInternal.h | 335 + console/consoleLogger.cpp | 217 + console/consoleLogger.h | 93 + console/consoleObject.cpp | 840 + console/consoleObject.h | 984 ++ console/consoleParser.cpp | 77 + console/consoleParser.h | 111 + console/consoleTypes.cpp | 822 + console/consoleTypes.h | 77 + console/debugOutputConsumer.cpp | 35 + console/debugOutputConsumer.h | 30 + console/dynamicTypes.cpp | 98 + console/dynamicTypes.h | 142 + console/fieldBrushObject.cpp | 564 + console/fieldBrushObject.h | 54 + console/fileSystemFunctions.cpp | 581 + console/generateCompiler.bat | 3 + console/persistenceManager.cpp | 2248 +++ console/persistenceManager.h | 295 + console/propertyParsing.cpp | 570 + console/propertyParsing.h | 149 + console/runtimeClassRep.cpp | 9 + console/runtimeClassRep.h | 125 + console/scriptFilename.cpp | 367 + console/scriptFilename.h | 18 + console/scriptObjects.cpp | 78 + console/scriptObjects.h | 29 + console/sim.cpp | 152 + console/sim.h | 195 + console/simBase.h | 20 + console/simDatablock.cpp | 120 + console/simDatablock.h | 169 + console/simDictionary.cpp | 306 + console/simDictionary.h | 95 + console/simEvents.cpp | 123 + console/simEvents.h | 116 + console/simFieldDictionary.cpp | 343 + console/simFieldDictionary.h | 91 + console/simManager.cpp | 684 + console/simObject.cpp | 1465 ++ console/simObject.h | 828 + console/simObjectList.cpp | 90 + console/simObjectList.h | 60 + console/simObjectMemento.cpp | 85 + console/simObjectMemento.h | 46 + console/simSerialize.cpp | 281 + console/simSet.cpp | 761 + console/simSet.h | 291 + console/stringStack.cpp | 24 + console/stringStack.h | 271 + console/telnetConsole.cpp | 292 + console/telnetConsole.h | 105 + console/telnetDebugger.cpp | 886 + console/telnetDebugger.h | 122 + console/typeValidators.cpp | 70 + console/typeValidators.h | 72 + core/bitMatrix.h | 165 + core/bitRender.cpp | 987 ++ core/bitRender.h | 42 + core/bitSet.h | 71 + core/bitVector.cpp | 65 + core/bitVector.h | 181 + core/bitVectorW.h | 91 + core/color.cpp | 23 + core/color.h | 590 + core/crc.cpp | 73 + core/crc.h | 32 + core/dataChunker.cpp | 80 + core/dataChunker.h | 277 + core/dnet.cpp | 267 + core/dnet.h | 66 + core/fileObject.cpp | 203 + core/fileObject.h | 41 + core/fileio.h | 121 + core/filterStream.cpp | 61 + core/filterStream.h | 39 + core/frameAllocator.cpp | 20 + core/frameAllocator.h | 291 + core/iTickable.cpp | 102 + core/iTickable.h | 152 + core/idGenerator.cpp | 19 + core/idGenerator.h | 76 + core/memVolume.cpp | 491 + core/memVolume.h | 115 + core/ogg/oggInputStream.cpp | 245 + core/ogg/oggInputStream.h | 156 + core/ogg/oggTheoraDecoder.cpp | 683 + core/ogg/oggTheoraDecoder.h | 250 + core/ogg/oggVorbisDecoder.cpp | 178 + core/ogg/oggVorbisDecoder.h | 74 + core/resizeStream.cpp | 142 + core/resizeStream.h | 52 + core/resource.cpp | 93 + core/resource.h | 306 + core/resourceManager.cpp | 199 + core/resourceManager.h | 72 + core/stream/bitStream.cpp | 1126 ++ core/stream/bitStream.h | 338 + core/stream/fileStream.cpp | 562 + core/stream/fileStream.h | 76 + core/stream/fileStreamObject.cpp | 94 + core/stream/fileStreamObject.h | 50 + core/stream/ioHelper.h | 98 + core/stream/memStream.cpp | 216 + core/stream/memStream.h | 87 + core/stream/stream.cpp | 361 + core/stream/stream.h | 225 + core/stream/streamObject.cpp | 189 + core/stream/streamObject.h | 121 + core/stream/tStream.h | 310 + core/stringBuffer.cpp | 454 + core/stringBuffer.h | 117 + core/stringTable.cpp | 213 + core/stringTable.h | 154 + core/strings/findMatch.cpp | 143 + core/strings/findMatch.h | 46 + core/strings/stringFunctions.cpp | 456 + core/strings/stringFunctions.h | 200 + core/strings/stringUnit.cpp | 193 + core/strings/stringUnit.h | 22 + core/strings/unicode.cpp | 645 + core/strings/unicode.h | 114 + core/tAlgorithm.h | 45 + core/tSimpleHashTable.h | 98 + core/tSparseArray.h | 135 + core/tagDictionary.cpp | 303 + core/tagDictionary.h | 66 + core/threadStatic.cpp | 96 + core/threadStatic.h | 199 + core/tokenizer.cpp | 596 + core/tokenizer.h | 92 + core/util/FastDelegate.h | 2110 +++ core/util/autoPtr.h | 114 + core/util/byteBuffer.cpp | 141 + core/util/byteBuffer.h | 68 + core/util/byteswap.h | 8 + core/util/commonSwizzles.cpp | 25 + core/util/delegate.h | 45 + core/util/dxt5nmSwizzle.h | 86 + core/util/endian.h | 124 + core/util/fourcc.h | 10 + core/util/hashFunction.cpp | 254 + core/util/hashFunction.h | 22 + core/util/journal/journal.cpp | 157 + core/util/journal/journal.h | 617 + core/util/journal/journaledSignal.h | 287 + core/util/journal/process.cpp | 78 + core/util/journal/process.h | 203 + core/util/journal/test/testJournal.cpp | 156 + core/util/journal/test/testProcess.cpp | 39 + core/util/namedSingleton.h | 128 + core/util/noncopyable.h | 20 + core/util/path.cpp | 404 + core/util/path.h | 124 + core/util/preprocessorHelpers.h | 21 + core/util/rawData.h | 140 + core/util/refBase.h | 444 + core/util/rgb2luv.cpp | 37 + core/util/rgb2luv.h | 15 + core/util/rgb2xyz.cpp | 46 + core/util/rgb2xyz.h | 12 + core/util/safeCast.h | 43 + core/util/safeDelete.h | 71 + core/util/safeRelease.h | 8 + core/util/str.cpp | 1432 ++ core/util/str.h | 345 + core/util/swizzle.h | 145 + core/util/swizzleSpec.h | 95 + core/util/tAlignedArray.h | 186 + core/util/tDictionary.cpp | 30 + core/util/tDictionary.h | 827 + core/util/tFixedSizeDeque.h | 168 + core/util/tList.h | 473 + core/util/tSignal.cpp | 50 + core/util/tSignal.h | 426 + core/util/tSingleton.h | 120 + core/util/tVector.cpp | 76 + core/util/tVector.h | 916 ++ core/util/tVectorSpecializations.h | 29 + core/util/test/testFixedSizeDeque.cpp | 37 + core/util/test/testPath.cpp | 27 + core/util/test/testString.cpp | 341 + core/util/timeClass.cpp | 181 + core/util/timeClass.h | 261 + core/util/timeSource.h | 133 + core/util/zip/centralDir.cpp | 303 + core/util/zip/centralDir.h | 95 + core/util/zip/compressor.cpp | 53 + core/util/zip/compressor.h | 86 + core/util/zip/compressors/deflate.cpp | 33 + core/util/zip/compressors/stored.cpp | 30 + core/util/zip/crctab.h | 67 + core/util/zip/extraField.cpp | 42 + core/util/zip/extraField.h | 67 + core/util/zip/fileHeader.cpp | 174 + core/util/zip/fileHeader.h | 95 + core/util/zip/unitTests/zipTest.h | 90 + core/util/zip/unitTests/zipTestMisc.cpp | 179 + core/util/zip/unitTests/zipTestRead.cpp | 235 + core/util/zip/unitTests/zipTestWrite.cpp | 227 + core/util/zip/zipArchive.cpp | 907 + core/util/zip/zipArchive.h | 573 + core/util/zip/zipCryptStream.cpp | 187 + core/util/zip/zipCryptStream.h | 51 + core/util/zip/zipObject.cpp | 289 + core/util/zip/zipObject.h | 68 + core/util/zip/zipStatFilter.h | 156 + core/util/zip/zipSubStream.cpp | 462 + core/util/zip/zipSubStream.h | 104 + core/util/zip/zipTempStream.cpp | 38 + core/util/zip/zipTempStream.h | 75 + core/util/zip/zipVolume.cpp | 441 + core/util/zip/zipVolume.h | 54 + core/virtualMountSystem.cpp | 286 + core/virtualMountSystem.h | 70 + core/volume.cpp | 1097 ++ core/volume.h | 560 + environment/basicClouds.cpp | 382 + environment/basicClouds.h | 96 + environment/cloudLayer.cpp | 434 + environment/cloudLayer.h | 112 + environment/decalRoad.cpp | 1409 ++ environment/decalRoad.h | 257 + environment/editors/guiMeshRoadEditorCtrl.cpp | 1273 ++ environment/editors/guiMeshRoadEditorCtrl.h | 156 + environment/editors/guiRiverEditorCtrl.cpp | 1466 ++ environment/editors/guiRiverEditorCtrl.h | 183 + environment/editors/guiRoadEditorCtrl.cpp | 1105 ++ environment/editors/guiRoadEditorCtrl.h | 147 + environment/meshRoad.cpp | 2145 +++ environment/meshRoad.h | 547 + environment/river.cpp | 2162 +++ environment/river.h | 512 + environment/scatterSky.cpp | 1207 ++ environment/scatterSky.h | 223 + environment/sky.cpp | 1526 ++ environment/sky.h | 235 + environment/skyBox.cpp | 592 + environment/skyBox.h | 107 + environment/sun.cpp | 489 + environment/sun.h | 123 + environment/timeOfDay.cpp | 522 + environment/timeOfDay.h | 187 + environment/waterBlock.cpp | 678 + environment/waterBlock.h | 135 + environment/waterObject.cpp | 926 ++ environment/waterObject.h | 253 + environment/waterPlane.cpp | 757 + environment/waterPlane.h | 132 + gfx/D3D/screenshotD3D.cpp | 119 + gfx/D3D/screenshotD3D.h | 24 + gfx/D3D9/d3dx9Functions.h | 131 + gfx/D3D9/gfxD3D9CardProfiler.cpp | 143 + gfx/D3D9/gfxD3D9CardProfiler.h | 37 + gfx/D3D9/gfxD3D9Cubemap.cpp | 165 + gfx/D3D9/gfxD3D9Cubemap.h | 55 + gfx/D3D9/gfxD3D9Device.cpp | 1054 ++ gfx/D3D9/gfxD3D9Device.h | 296 + gfx/D3D9/gfxD3D9Device.regen-states.cpp | 76 + gfx/D3D9/gfxD3D9EnumTranslate.h | 46 + gfx/D3D9/gfxD3D9OcclusionQuery.cpp | 105 + gfx/D3D9/gfxD3D9OcclusionQuery.h | 35 + gfx/D3D9/gfxD3D9PrimitiveBuffer.cpp | 53 + gfx/D3D9/gfxD3D9PrimitiveBuffer.h | 52 + gfx/D3D9/gfxD3D9QueryFence.cpp | 84 + gfx/D3D9/gfxD3D9QueryFence.h | 30 + gfx/D3D9/gfxD3D9Shader.cpp | 1300 ++ gfx/D3D9/gfxD3D9Shader.h | 253 + gfx/D3D9/gfxD3D9StateBlock.cpp | 179 + gfx/D3D9/gfxD3D9StateBlock.h | 57 + gfx/D3D9/gfxD3D9TextureManager.cpp | 572 + gfx/D3D9/gfxD3D9TextureManager.h | 56 + gfx/D3D9/gfxD3D9TextureObject.cpp | 260 + gfx/D3D9/gfxD3D9TextureObject.h | 63 + gfx/D3D9/gfxD3D9VertexBuffer.cpp | 178 + gfx/D3D9/gfxD3D9VertexBuffer.h | 106 + gfx/D3D9/pc/gfxD3D9Device.pc.cpp | 54 + gfx/D3D9/pc/gfxD3D9EnumTranslate.pc.cpp | 356 + gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp | 55 + gfx/D3D9/pc/gfxPCD3D9Device.cpp | 1034 ++ gfx/D3D9/pc/gfxPCD3D9Device.h | 50 + gfx/D3D9/pc/gfxPCD3D9Target.cpp | 468 + gfx/D3D9/pc/gfxPCD3D9Target.h | 98 + gfx/D3D9/platformD3D.h | 6 + gfx/Null/gfxNullDevice.cpp | 315 + gfx/Null/gfxNullDevice.h | 158 + gfx/bitmap/bitmapUtils.cpp | 267 + gfx/bitmap/bitmapUtils.h | 24 + gfx/bitmap/ddsFile.h | 153 + gfx/bitmap/ddsLoader.cpp | 779 + gfx/bitmap/ddsUtils.cpp | 94 + gfx/bitmap/ddsUtils.h | 12 + gfx/bitmap/gBitmap.cpp | 1126 ++ gfx/bitmap/gBitmap.h | 288 + gfx/bitmap/loaders/bitmapBmp.cpp | 228 + gfx/bitmap/loaders/bitmapGif.cpp | 212 + gfx/bitmap/loaders/bitmapJpeg.cpp | 232 + gfx/bitmap/loaders/bitmapMng.cpp | 404 + gfx/bitmap/loaders/bitmapPng.cpp | 486 + gfx/bitmap/loaders/bitmapTga.cpp | 123 + gfx/gFont.cpp | 1269 ++ gfx/gFont.h | 177 + gfx/genericConstBuffer.cpp | 310 + gfx/genericConstBuffer.h | 193 + gfx/gfxAdapter.h | 64 + gfx/gfxCardProfile.cpp | 180 + gfx/gfxCardProfile.h | 216 + gfx/gfxCubemap.cpp | 68 + gfx/gfxCubemap.h | 45 + gfx/gfxDebugEvent.h | 66 + gfx/gfxDevice.cpp | 1321 ++ gfx/gfxDevice.h | 968 ++ gfx/gfxDeviceStatistics.cpp | 57 + gfx/gfxDeviceStatistics.h | 45 + gfx/gfxDrawUtil.cpp | 1716 ++ gfx/gfxDrawUtil.h | 143 + gfx/gfxEnums.h | 582 + gfx/gfxFence.cpp | 125 + gfx/gfxFence.h | 88 + gfx/gfxFontRenderBatcher.cpp | 220 + gfx/gfxFontRenderBatcher.h | 45 + gfx/gfxFormatUtils.cpp | 72 + gfx/gfxFormatUtils.h | 126 + gfx/gfxInit.cpp | 460 + gfx/gfxInit.h | 79 + gfx/gfxOcclusionQuery.cpp | 26 + gfx/gfxOcclusionQuery.h | 68 + gfx/gfxPrimitiveBuffer.cpp | 65 + gfx/gfxPrimitiveBuffer.h | 146 + gfx/gfxResource.cpp | 38 + gfx/gfxResource.h | 89 + gfx/gfxShader.cpp | 142 + gfx/gfxShader.h | 290 + gfx/gfxStateBlock.cpp | 320 + gfx/gfxStateBlock.h | 203 + gfx/gfxStringEnumTranslate.cpp | 596 + gfx/gfxStringEnumTranslate.h | 75 + gfx/gfxStructs.cpp | 62 + gfx/gfxStructs.h | 177 + gfx/gfxTarget.cpp | 9 + gfx/gfxTarget.h | 190 + gfx/gfxTextureHandle.cpp | 144 + gfx/gfxTextureHandle.h | 114 + gfx/gfxTextureManager.cpp | 1174 ++ gfx/gfxTextureManager.h | 301 + gfx/gfxTextureObject.cpp | 231 + gfx/gfxTextureObject.h | 191 + gfx/gfxTextureProfile.cpp | 158 + gfx/gfxTextureProfile.h | 194 + gfx/gfxTransformSaver.h | 128 + gfx/gfxVertexBuffer.cpp | 43 + gfx/gfxVertexBuffer.h | 217 + gfx/gfxVertexColor.cpp | 3 + gfx/gfxVertexColor.h | 52 + gfx/gfxVertexFormat.cpp | 147 + gfx/gfxVertexFormat.h | 268 + gfx/gfxVertexTypes.cpp | 102 + gfx/gfxVertexTypes.h | 154 + gfx/gl/gfxGLAppleFence.cpp | 54 + gfx/gl/gfxGLAppleFence.h | 33 + gfx/gl/gfxGLCardProfiler.cpp | 124 + gfx/gl/gfxGLCardProfiler.h | 37 + gfx/gl/gfxGLCubemap.cpp | 165 + gfx/gl/gfxGLCubemap.h | 63 + gfx/gl/gfxGLDevice.cpp | 747 + gfx/gl/gfxGLDevice.h | 211 + gfx/gl/gfxGLDevice.mac.mm | 340 + gfx/gl/gfxGLDevice.win.cpp | 386 + gfx/gl/gfxGLEnumTranslate.cpp | 193 + gfx/gl/gfxGLEnumTranslate.h | 35 + gfx/gl/gfxGLOcclusionQuery.cpp | 72 + gfx/gl/gfxGLOcclusionQuery.h | 32 + gfx/gl/gfxGLPrimitiveBuffer.cpp | 99 + gfx/gl/gfxGLPrimitiveBuffer.h | 36 + gfx/gl/gfxGLShader.cpp | 908 + gfx/gl/gfxGLShader.h | 161 + gfx/gl/gfxGLStateBlock.cpp | 171 + gfx/gl/gfxGLStateBlock.h | 48 + gfx/gl/gfxGLTextureManager.cpp | 316 + gfx/gl/gfxGLTextureManager.h | 45 + gfx/gl/gfxGLTextureObject.cpp | 222 + gfx/gl/gfxGLTextureObject.h | 82 + gfx/gl/gfxGLTextureTarget.cpp | 421 + gfx/gl/gfxGLTextureTarget.h | 84 + gfx/gl/gfxGLUtils.h | 94 + gfx/gl/gfxGLVertexBuffer.cpp | 161 + gfx/gl/gfxGLVertexBuffer.h | 47 + gfx/gl/gfxGLWindowTarget.cpp | 62 + gfx/gl/gfxGLWindowTarget.h | 45 + gfx/gl/ggl/generated/glc.h | 649 + gfx/gl/ggl/generated/glcfn.h | 330 + gfx/gl/ggl/generated/gle.h | 3980 +++++ gfx/gl/ggl/generated/glefn.h | 2382 +++ gfx/gl/ggl/generated/glxe.h | 388 + gfx/gl/ggl/generated/glxefn.h | 271 + gfx/gl/ggl/generated/glxfn.h | 27 + gfx/gl/ggl/generated/wgle.h | 366 + gfx/gl/ggl/generated/wglefn.h | 228 + gfx/gl/ggl/generated/wglfn.h | 30 + gfx/gl/ggl/generator/parse.cpp | 606 + gfx/gl/ggl/ggl.cpp | 72 + gfx/gl/ggl/ggl.h | 100 + gfx/gl/ggl/gglConfig.h | 362 + gfx/gl/ggl/mac/agl.h | 93 + gfx/gl/ggl/mac/aglBind.cpp | 178 + gfx/gl/ggl/win32/wgl.h | 86 + gfx/gl/ggl/win32/wglBind.cpp | 156 + gfx/gl/ggl/x11/glx.cpp | 396 + gfx/gl/ggl/x11/glx.h | 124 + gfx/gl/ggl/x11/glxBind.cpp | 159 + gfx/primBuilder.cpp | 317 + gfx/primBuilder.h | 68 + gfx/screenshot.cpp | 51 + gfx/screenshot.h | 46 + gfx/sim/cubemapData.cpp | 249 + gfx/sim/cubemapData.h | 68 + gfx/sim/debugDraw.cpp | 313 + gfx/sim/debugDraw.h | 160 + gfx/sim/gfxStateBlockData.cpp | 140 + gfx/sim/gfxStateBlockData.h | 53 + gfx/test/stanfordBunny.cpp | 13623 ++++++++++++++++ gfx/test/testGfx.cpp | 1539 ++ gfx/util/distanceField.cpp | 189 + gfx/util/distanceField.h | 17 + gfx/util/gfxFrustumSaver.cpp | 20 + gfx/util/gfxFrustumSaver.h | 30 + gfx/util/screenspace.cpp | 30 + gfx/util/screenspace.h | 13 + gfx/util/triListOpt.cpp | 370 + gfx/util/triListOpt.h | 78 + gfx/video/theoraTexture.cpp | 672 + gfx/video/theoraTexture.h | 399 + ggEndOfLineFix.txt | 3 + gui/3d/guiTSControl.cpp | 369 + gui/3d/guiTSControl.h | 104 + gui/buttons/guiBitmapButtonCtrl.cpp | 277 + gui/buttons/guiBitmapButtonCtrl.h | 76 + gui/buttons/guiBorderButton.cpp | 65 + gui/buttons/guiButtonBaseCtrl.cpp | 368 + gui/buttons/guiButtonBaseCtrl.h | 77 + gui/buttons/guiButtonCtrl.cpp | 84 + gui/buttons/guiButtonCtrl.h | 25 + gui/buttons/guiCheckBoxCtrl.cpp | 241 + gui/buttons/guiCheckBoxCtrl.h | 40 + gui/buttons/guiIconButtonCtrl.cpp | 385 + gui/buttons/guiIconButtonCtrl.h | 100 + gui/buttons/guiRadioCtrl.cpp | 18 + gui/buttons/guiRadioCtrl.h | 31 + gui/buttons/guiSwatchButtonCtrl.cpp | 84 + gui/buttons/guiSwatchButtonCtrl.h | 39 + gui/buttons/guiToggleButtonCtrl.cpp | 79 + gui/buttons/guiToggleButtonCtrl.h | 24 + gui/buttons/guiToolboxButtonCtrl.cpp | 205 + gui/buttons/guiToolboxButtonCtrl.h | 53 + gui/containers/guiAutoScrollCtrl.cpp | 133 + gui/containers/guiAutoScrollCtrl.h | 51 + gui/containers/guiContainer.cpp | 384 + gui/containers/guiContainer.h | 121 + gui/containers/guiCtrlArrayCtrl.cpp | 142 + gui/containers/guiCtrlArrayCtrl.h | 51 + gui/containers/guiDragAndDropCtrl.cpp | 106 + gui/containers/guiDragAndDropCtrl.h | 51 + gui/containers/guiDynamicCtrlArrayCtrl.cpp | 205 + gui/containers/guiDynamicCtrlArrayCtrl.h | 63 + gui/containers/guiFormCtrl.cpp | 353 + gui/containers/guiFormCtrl.h | 94 + gui/containers/guiFrameCtrl.cpp | 1048 ++ gui/containers/guiFrameCtrl.h | 174 + gui/containers/guiPaneCtrl.cpp | 319 + gui/containers/guiPaneCtrl.h | 70 + gui/containers/guiPanel.cpp | 50 + gui/containers/guiPanel.h | 45 + gui/containers/guiRolloutCtrl.cpp | 491 + gui/containers/guiRolloutCtrl.h | 120 + gui/containers/guiScrollCtrl.cpp | 1258 ++ gui/containers/guiScrollCtrl.h | 240 + gui/containers/guiSplitContainer.cpp | 545 + gui/containers/guiSplitContainer.h | 88 + gui/containers/guiStackCtrl.cpp | 326 + gui/containers/guiStackCtrl.h | 83 + gui/containers/guiTabBookCtrl.cpp | 799 + gui/containers/guiTabBookCtrl.h | 228 + gui/containers/guiWindowCollapseCtrl.cpp | 1216 ++ gui/containers/guiWindowCollapseCtrl.h | 62 + gui/containers/guiWindowCtrl.cpp | 1062 ++ gui/containers/guiWindowCtrl.h | 184 + gui/controls/guiBackgroundCtrl.cpp | 27 + gui/controls/guiBackgroundCtrl.h | 31 + gui/controls/guiBitmapBorderCtrl.cpp | 131 + gui/controls/guiBitmapCtrl.cpp | 199 + gui/controls/guiBitmapCtrl.h | 53 + gui/controls/guiColorPicker.cpp | 523 + gui/controls/guiColorPicker.h | 132 + gui/controls/guiConsole.cpp | 107 + gui/controls/guiConsole.h | 34 + gui/controls/guiConsoleEditCtrl.cpp | 87 + gui/controls/guiConsoleEditCtrl.h | 39 + gui/controls/guiConsoleTextCtrl.cpp | 149 + gui/controls/guiConsoleTextCtrl.h | 65 + gui/controls/guiDecoyCtrl.cpp | 216 + gui/controls/guiDecoyCtrl.h | 52 + gui/controls/guiDirectoryFileListCtrl.cpp | 178 + gui/controls/guiDirectoryFileListCtrl.h | 62 + gui/controls/guiFileTreeCtrl.cpp | 407 + gui/controls/guiFileTreeCtrl.h | 60 + gui/controls/guiGameListMenuCtrl.cpp | 770 + gui/controls/guiGameListMenuCtrl.h | 331 + gui/controls/guiGameListOptionsCtrl.cpp | 464 + gui/controls/guiGameListOptionsCtrl.h | 168 + gui/controls/guiListBoxCtrl.cpp | 1260 ++ gui/controls/guiListBoxCtrl.h | 128 + gui/controls/guiMLTextCtrl.cpp | 2204 +++ gui/controls/guiMLTextCtrl.h | 282 + gui/controls/guiMLTextEditCtrl.cpp | 440 + gui/controls/guiMLTextEditCtrl.h | 47 + gui/controls/guiMaterialCtrl.cpp | 133 + gui/controls/guiMaterialCtrl.h | 50 + gui/controls/guiPopUpCtrl.cpp | 1519 ++ gui/controls/guiPopUpCtrl.h | 154 + gui/controls/guiPopUpCtrlEx.cpp | 1590 ++ gui/controls/guiPopUpCtrlEx.h | 175 + gui/controls/guiSliderCtrl.cpp | 419 + gui/controls/guiSliderCtrl.h | 75 + gui/controls/guiTabPageCtrl.cpp | 177 + gui/controls/guiTabPageCtrl.h | 54 + gui/controls/guiTextCtrl.cpp | 197 + gui/controls/guiTextCtrl.h | 75 + gui/controls/guiTextEditCtrl.cpp | 1647 ++ gui/controls/guiTextEditCtrl.h | 133 + gui/controls/guiTextEditSliderBitmapCtrl.cpp | 379 + gui/controls/guiTextEditSliderBitmapCtrl.h | 76 + gui/controls/guiTextEditSliderCtrl.cpp | 391 + gui/controls/guiTextEditSliderCtrl.h | 71 + gui/controls/guiTextListCtrl.cpp | 612 + gui/controls/guiTextListCtrl.h | 94 + gui/controls/guiTreeViewCtrl.cpp | 4318 +++++ gui/controls/guiTreeViewCtrl.h | 443 + gui/core/guiArrayCtrl.cpp | 435 + gui/core/guiArrayCtrl.h | 79 + gui/core/guiCanvas.cpp | 2210 +++ gui/core/guiCanvas.h | 431 + gui/core/guiControl.cpp | 2270 +++ gui/core/guiControl.h | 760 + gui/core/guiDefaultControlRender.cpp | 530 + gui/core/guiDefaultControlRender.h | 28 + gui/core/guiScriptNotifyControl.cpp | 215 + gui/core/guiScriptNotifyControl.h | 86 + gui/core/guiTypes.cpp | 633 + gui/core/guiTypes.h | 478 + gui/editor/guiControlListPopup.cpp | 45 + gui/editor/guiDebugger.cpp | 682 + gui/editor/guiDebugger.h | 84 + gui/editor/guiEditCtrl.cpp | 2572 +++ gui/editor/guiEditCtrl.h | 292 + gui/editor/guiFilterCtrl.cpp | 267 + gui/editor/guiFilterCtrl.h | 80 + gui/editor/guiGraphCtrl.cpp | 365 + gui/editor/guiGraphCtrl.h | 67 + gui/editor/guiImageList.cpp | 183 + gui/editor/guiImageList.h | 61 + gui/editor/guiInspector.cpp | 525 + gui/editor/guiInspector.h | 99 + gui/editor/guiInspectorTypes.cpp | 1095 ++ gui/editor/guiInspectorTypes.h | 393 + gui/editor/guiMenuBar.cpp | 1572 ++ gui/editor/guiMenuBar.h | 201 + gui/editor/guiParticleGraphCtrl.cc | 1429 ++ gui/editor/guiParticleGraphCtrl.h | 155 + gui/editor/guiSeparatorCtrl.cpp | 97 + gui/editor/guiSeparatorCtrl.h | 41 + gui/editor/guiShapeEdPreview.cpp | 793 + gui/editor/guiShapeEdPreview.h | 117 + gui/editor/inspector/customField.cpp | 109 + gui/editor/inspector/customField.h | 52 + gui/editor/inspector/datablockField.cpp | 100 + gui/editor/inspector/datablockField.h | 35 + gui/editor/inspector/dynamicField.cpp | 252 + gui/editor/inspector/dynamicField.h | 53 + gui/editor/inspector/dynamicGroup.cpp | 193 + gui/editor/inspector/dynamicGroup.h | 47 + gui/editor/inspector/field.cpp | 404 + gui/editor/inspector/field.h | 112 + gui/editor/inspector/group.cpp | 492 + gui/editor/inspector/group.h | 64 + gui/editor/inspector/variableField.cpp | 98 + gui/editor/inspector/variableField.h | 45 + gui/editor/inspector/variableGroup.cpp | 86 + gui/editor/inspector/variableGroup.h | 38 + gui/editor/inspector/variableInspector.cpp | 45 + gui/editor/inspector/variableInspector.h | 35 + gui/game/guiAviBitmapCtrl.cpp | 1373 ++ gui/game/guiAviBitmapCtrl.h | 186 + gui/game/guiChunkedBitmapCtrl.cpp | 151 + gui/game/guiFadeinBitmapCtrl.cpp | 103 + gui/game/guiIdleCamFadeBitmapCtrl.cpp | 164 + gui/game/guiMessageVectorCtrl.cpp | 820 + gui/game/guiMessageVectorCtrl.h | 138 + gui/game/guiProgressBitmapCtrl.cpp | 157 + gui/game/guiProgressBitmapCtrl.h | 49 + gui/game/guiProgressCtrl.cpp | 77 + gui/game/guiProgressCtrl.h | 38 + gui/shiny/guiTheoraCtrl.cpp | 223 + gui/shiny/guiTheoraCtrl.h | 96 + gui/shiny/guiTickCtrl.cpp | 17 + gui/shiny/guiTickCtrl.h | 49 + gui/utility/guiBubbleTextCtrl.cpp | 71 + gui/utility/guiBubbleTextCtrl.h | 37 + gui/utility/guiInputCtrl.cpp | 94 + gui/utility/guiInputCtrl.h | 32 + gui/utility/guiMouseEventCtrl.cpp | 96 + gui/utility/guiMouseEventCtrl.h | 48 + gui/utility/messageVector.cpp | 360 + gui/utility/messageVector.h | 110 + gui/worldEditor/creator.cpp | 453 + gui/worldEditor/creator.h | 101 + gui/worldEditor/editTSCtrl.cpp | 1208 ++ gui/worldEditor/editTSCtrl.h | 175 + gui/worldEditor/editor.cpp | 138 + gui/worldEditor/editor.h | 52 + gui/worldEditor/editorIconRegistry.cpp | 186 + gui/worldEditor/editorIconRegistry.h | 61 + gui/worldEditor/gizmo.cpp | 1743 ++ gui/worldEditor/gizmo.h | 305 + gui/worldEditor/guiDecalEditorCtrl.cpp | 958 ++ gui/worldEditor/guiDecalEditorCtrl.h | 175 + gui/worldEditor/guiTerrPreviewCtrl.cpp | 343 + gui/worldEditor/guiTerrPreviewCtrl.h | 71 + gui/worldEditor/missionAreaEditor.cpp | 1086 ++ gui/worldEditor/missionAreaEditor.h | 152 + gui/worldEditor/terrainActions.cpp | 759 + gui/worldEditor/terrainActions.h | 334 + gui/worldEditor/terrainEditor.cpp | 2509 +++ gui/worldEditor/terrainEditor.h | 429 + gui/worldEditor/undoActions.cpp | 225 + gui/worldEditor/undoActions.h | 116 + gui/worldEditor/worldEditor.cpp | 3547 ++++ gui/worldEditor/worldEditor.h | 423 + i18n/i18n.cpp | 61 + i18n/i18n.h | 22 + i18n/lang.cpp | 415 + i18n/lang.h | 103 + interior/forceField.cpp | 450 + interior/forceField.h | 178 + interior/interior.cpp | 2822 ++++ interior/interior.h | 1174 ++ interior/interiorCollision.cpp | 1784 ++ interior/interiorDebug.cpp | 699 + interior/interiorIO.cpp | 1652 ++ interior/interiorInstance.cpp | 1628 ++ interior/interiorInstance.h | 252 + interior/interiorLMManager.cpp | 367 + interior/interiorLMManager.h | 75 + interior/interiorRender.cpp | 352 + interior/interiorRes.cpp | 335 + interior/interiorRes.h | 148 + interior/interiorResObjects.cpp | 229 + interior/interiorResObjects.h | 131 + interior/interiorSimpleMesh.cpp | 623 + interior/interiorSimpleMesh.h | 180 + interior/interiorSubObject.cpp | 93 + interior/interiorSubObject.h | 65 + interior/mirrorSubObject.cpp | 266 + interior/mirrorSubObject.h | 89 + interior/pathedInterior.cpp | 587 + interior/pathedInterior.h | 134 + lighting/advanced/advancedLightBinManager.cpp | 816 + lighting/advanced/advancedLightBinManager.h | 221 + .../advancedLightBufferConditioner.cpp | 166 + .../advanced/advancedLightBufferConditioner.h | 48 + lighting/advanced/advancedLightManager.cpp | 644 + lighting/advanced/advancedLightManager.h | 129 + .../advanced/advancedLightingFeatures.cpp | 72 + lighting/advanced/advancedLightingFeatures.h | 25 + .../glsl/advancedLightingFeaturesGLSL.cpp | 708 + .../glsl/advancedLightingFeaturesGLSL.h | 141 + .../advanced/glsl/gBufferConditionerGLSL.cpp | 302 + .../advanced/glsl/gBufferConditionerGLSL.h | 57 + .../hlsl/advancedLightingFeaturesHLSL.cpp | 557 + .../hlsl/advancedLightingFeaturesHLSL.h | 153 + .../advanced/hlsl/gBufferConditionerHLSL.cpp | 364 + .../advanced/hlsl/gBufferConditionerHLSL.h | 64 + lighting/basic/basicLightManager.cpp | 383 + lighting/basic/basicLightManager.h | 109 + .../basic/basicSceneObjectLightingPlugin.cpp | 164 + .../basic/basicSceneObjectLightingPlugin.h | 58 + lighting/basic/blInteriorSystem.cpp | 1489 ++ lighting/basic/blTerrainSystem.cpp | 730 + lighting/common/blobShadow.cpp | 335 + lighting/common/blobShadow.h | 70 + lighting/common/lightMapParams.cpp | 44 + lighting/common/lightMapParams.h | 37 + lighting/common/projectedShadow.cpp | 536 + lighting/common/projectedShadow.h | 89 + lighting/common/sceneLighting.cpp | 1094 ++ lighting/common/sceneLighting.h | 237 + lighting/common/sceneLightingGlobals.h | 49 + lighting/common/scenePersist.cpp | 127 + lighting/common/scenePersist.h | 51 + lighting/common/shadowBase.h | 23 + lighting/common/shadowVolumeBSP.cpp | 714 + lighting/common/shadowVolumeBSP.h | 137 + lighting/lightInfo.cpp | 171 + lighting/lightInfo.h | 227 + lighting/lightManager.cpp | 609 + lighting/lightManager.h | 236 + lighting/lightReceiver.cpp | 22 + lighting/lightReceiver.h | 33 + lighting/lightingInterfaces.cpp | 34 + lighting/lightingInterfaces.h | 106 + lighting/shadowManager.cpp | 62 + lighting/shadowManager.h | 49 + lighting/shadowMap/blurTexture.cpp | 129 + lighting/shadowMap/blurTexture.h | 49 + lighting/shadowMap/cubeLightShadowMap.cpp | 178 + lighting/shadowMap/cubeLightShadowMap.h | 44 + .../dualParaboloidLightShadowMap.cpp | 180 + .../shadowMap/dualParaboloidLightShadowMap.h | 26 + lighting/shadowMap/lightShadowMap.cpp | 589 + lighting/shadowMap/lightShadowMap.h | 329 + .../shadowMap/paraboloidLightShadowMap.cpp | 125 + lighting/shadowMap/paraboloidLightShadowMap.h | 31 + lighting/shadowMap/pssmLightShadowMap.cpp | 433 + lighting/shadowMap/pssmLightShadowMap.h | 44 + lighting/shadowMap/shadowCommon.h | 36 + lighting/shadowMap/shadowMapManager.cpp | 105 + lighting/shadowMap/shadowMapManager.h | 78 + lighting/shadowMap/shadowMapPass.cpp | 242 + lighting/shadowMap/shadowMapPass.h | 103 + lighting/shadowMap/shadowMatHook.cpp | 174 + lighting/shadowMap/shadowMatHook.h | 64 + lighting/shadowMap/singleLightShadowMap.cpp | 111 + lighting/shadowMap/singleLightShadowMap.h | 29 + main/main.cpp | 292 + materials/baseMatInstance.cpp | 63 + materials/baseMatInstance.h | 198 + materials/baseMaterialDefinition.h | 26 + materials/customMaterialDefinition.cpp | 96 + materials/customMaterialDefinition.h | 59 + materials/matInstance.cpp | 495 + materials/matInstance.h | 142 + materials/matInstanceHook.cpp | 18 + materials/matInstanceHook.h | 70 + materials/matTextureTarget.cpp | 92 + materials/matTextureTarget.h | 80 + materials/materialDefinition.cpp | 460 + materials/materialDefinition.h | 380 + materials/materialFeatureData.cpp | 29 + materials/materialFeatureData.h | 56 + materials/materialFeatureTypes.cpp | 59 + materials/materialFeatureTypes.h | 156 + materials/materialList.cpp | 475 + materials/materialList.h | 107 + materials/materialManager.cpp | 401 + materials/materialManager.h | 135 + materials/materialParameters.h | 73 + materials/miscShdrDat.h | 39 + materials/processedCustomMaterial.cpp | 487 + materials/processedCustomMaterial.h | 57 + materials/processedFFMaterial.cpp | 381 + materials/processedFFMaterial.h | 110 + materials/processedMaterial.cpp | 421 + materials/processedMaterial.h | 263 + materials/processedShaderMaterial.cpp | 1077 ++ materials/processedShaderMaterial.h | 185 + materials/sceneData.h | 108 + materials/shaderData.cpp | 224 + materials/shaderData.h | 108 + materials/shaderMaterialParameters.cpp | 210 + materials/shaderMaterialParameters.h | 79 + math/mAngAxis.cpp | 78 + math/mAngAxis.h | 87 + math/mBox.cpp | 166 + math/mBox.h | 625 + math/mConsoleFunctions.cpp | 202 + math/mConstants.h | 28 + math/mMath.h | 32 + math/mMathAMD.cpp | 199 + math/mMathAMD_ASM.asm | 160 + math/mMathAltivec.cpp | 119 + math/mMathFn.h | 419 + math/mMathSSE.cpp | 365 + math/mMathSSE_ASM.asm | 111 + math/mMath_ASM.asm | 221 + math/mMath_C.cpp | 958 ++ math/mMatrix.cpp | 178 + math/mMatrix.h | 546 + math/mPlane.cpp | 27 + math/mPlane.h | 599 + math/mPlaneTransformer.cpp | 54 + math/mPlaneTransformer.h | 32 + math/mPoint.cpp | 60 + math/mPoint2.h | 837 + math/mPoint3.h | 954 ++ math/mPoint4.h | 211 + math/mQuadPatch.cpp | 59 + math/mQuadPatch.h | 45 + math/mQuat.cpp | 338 + math/mQuat.h | 162 + math/mRandom.cpp | 154 + math/mRandom.h | 123 + math/mRect.cpp | 11 + math/mRect.h | 473 + math/mSolver.cpp | 236 + math/mSphere.h | 145 + math/mSplinePatch.cpp | 94 + math/mSplinePatch.h | 110 + math/mTrig.h | 41 + math/mathIO.h | 231 + math/mathTypes.cpp | 580 + math/mathTypes.h | 35 + math/mathUtils.cpp | 979 ++ math/mathUtils.h | 204 + math/util/frustum.cpp | 713 + math/util/frustum.h | 391 + math/util/matrixSet.cpp | 28 + math/util/matrixSet.h | 149 + math/util/matrixSetDelegateMethods.h | 31 + math/util/quadTransforms.cpp | 168 + math/util/quadTransforms.h | 65 + math/util/sphereMesh.cpp | 233 + math/util/sphereMesh.h | 71 + math/util/tResponseCurve.cpp | 52 + math/util/tResponseCurve.h | 230 + platform/async/asyncBufferedStream.h | 400 + platform/async/asyncPacketQueue.h | 260 + platform/async/asyncPacketStream.h | 309 + platform/async/asyncUpdate.cpp | 66 + platform/async/asyncUpdate.h | 145 + platform/event.h | 370 + platform/menus/menuBar.cpp | 106 + platform/menus/menuBar.h | 54 + platform/menus/popupMenu.cpp | 213 + platform/menus/popupMenu.h | 158 + platform/nativeDialogs/fileDialog.h | 191 + platform/nativeDialogs/msgBox.cpp | 68 + platform/nativeDialogs/msgBox.h | 42 + platform/platform.cpp | 131 + platform/platform.h | 579 + platform/platformAssert.cpp | 155 + platform/platformAssert.h | 124 + platform/platformCPU.cpp | 241 + platform/platformCPUCount.cpp | 730 + platform/platformCPUCount.h | 58 + platform/platformCPUInfo.asm | 111 + platform/platformDlibrary.h | 55 + platform/platformFileIO.cpp | 491 + platform/platformFont.cpp | 37 + platform/platformFont.h | 80 + platform/platformInput.h | 117 + platform/platformIntrinsics.gcc.h | 67 + platform/platformIntrinsics.h | 40 + platform/platformIntrinsics.visualc.h | 50 + platform/platformMemory.cpp | 1666 ++ platform/platformMemory.h | 30 + platform/platformNet.cpp | 1018 ++ platform/platformNet.h | 133 + platform/platformNetAsync.cpp | 151 + platform/platformNetAsync.h | 46 + platform/platformRedBook.cpp | 254 + platform/platformRedBook.h | 70 + platform/platformTLS.h | 38 + platform/platformTimer.cpp | 162 + platform/platformTimer.h | 94 + platform/platformVFS.h | 17 + platform/platformVideoInfo.cpp | 77 + platform/platformVideoInfo.h | 77 + platform/platformVolume.cpp | 72 + platform/platformVolume.h | 38 + platform/profiler.cpp | 714 + platform/profiler.h | 177 + platform/test/testAlerts.cpp | 33 + platform/test/testAsyncPacketQueue.cpp | 134 + platform/test/testBasicTypes.cpp | 107 + platform/test/testFile.cpp | 133 + platform/test/testNet.cpp | 177 + platform/test/testThreadPool.cpp | 68 + platform/test/testThreadSafeDeque.cpp | 386 + platform/test/testThreadSafePriorityQueue.cpp | 228 + platform/test/testThreadSafeRefCount.cpp | 210 + platform/test/testThreading.cpp | 400 + platform/test/testTimeManager.cpp | 88 + platform/threads/mutex.h | 112 + platform/threads/semaphore.h | 38 + platform/threads/thread.h | 217 + platform/threads/threadPool.cpp | 451 + platform/threads/threadPool.h | 366 + platform/threads/threadPoolAsyncIO.h | 340 + platform/threads/threadSafeDeque.h | 457 + platform/threads/threadSafeFreeList.h | 175 + platform/threads/threadSafePriorityQueue.h | 714 + platform/threads/threadSafeRefCount.h | 363 + platform/tmm_off.h | 1 + platform/tmm_on.h | 3 + platform/types.codewarrior.h | 81 + platform/types.gcc.h | 146 + platform/types.h | 275 + platform/types.lint.h | 41 + platform/types.mac.h | 34 + platform/types.posix.h | 29 + platform/types.ppc.h | 33 + platform/types.visualc.h | 82 + platform/types.win32.h | 32 + platform/types.xenon.h | 32 + platform/typesLinux.h | 63 + platform/typesPPC.h | 82 + platform/typesWin32.h | 106 + platform/typesX86UNIX.h | 63 + platform/typetraits.h | 346 + platformMac/cursors/resizeNESW.png | Bin 0 -> 630 bytes platformMac/cursors/resizeNWSE.png | Bin 0 -> 707 bytes platformMac/cursors/resizeall.png | Bin 0 -> 307 bytes platformMac/macApplication.h | 21 + platformMac/macApplication.mm | 62 + platformMac/macCarbAsync.cpp | 47 + platformMac/macCarbCPUInfo.cpp | 265 + platformMac/macCarbFileio.mm | 914 ++ platformMac/macCarbFont.cpp | 432 + platformMac/macCarbFont.h | 57 + platformMac/macCarbInput.cpp | 483 + platformMac/macCarbMath.cpp | 106 + platformMac/macCarbMemory.cpp | 56 + platformMac/macCarbMutex.cpp | 80 + platformMac/macCarbProcessControl.cpp | 48 + platformMac/macCarbSemaphore.cpp | 51 + platformMac/macCarbStrings.cpp | 52 + platformMac/macCarbThread.cpp | 142 + platformMac/macCarbTime.cpp | 126 + platformMac/macCarbUtil.cpp | 48 + platformMac/macCarbVolume.cpp | 154 + platformMac/macCarbVolume.h | 69 + platformMac/macCocoaDialogs.mm | 643 + platformMac/macCocoaPlatform.mm | 222 + platformMac/macDLibrary.cpp | 61 + platformMac/macGLUtils.h | 61 + platformMac/macMain.mm | 177 + platformMac/macMsgBox.mm | 163 + platformMac/menus/mainMenu.nib/classes.nib | 30 + platformMac/menus/mainMenu.nib/info.nib | 20 + .../menus/mainMenu.nib/keyedobjects.nib | Bin 0 -> 17142 bytes platformMac/menus/menuBarMac.cpp | 279 + platformMac/menus/popupMenu.cpp | 422 + platformMac/platformMacCarb.h | 158 + platformMac/t2d.icns | Bin 0 -> 32047 bytes platformMac/torqueDemo.icns | Bin 0 -> 39457 bytes platformPOSIX/posixVolume.cpp | 572 + platformPOSIX/posixVolume.h | 112 + platformWin32/VFSRes.h | 21 + platformWin32/cardProfile.cpp | 62 + platformWin32/menus/menuBarWin32.cpp | 155 + platformWin32/menus/popupMenuWin32.cpp | 658 + platformWin32/nativeDialogs/fileDialog.cpp | 678 + platformWin32/nativeDialogs/win32MsgBox.cpp | 112 + platformWin32/platformWin32.h | 121 + platformWin32/threads/mutex.cpp | 73 + platformWin32/threads/thread.cpp | 191 + platformWin32/videoInfo/wmiVideoInfo.cpp | 566 + platformWin32/videoInfo/wmiVideoInfo.h | 41 + platformWin32/winAsmBlit.cpp | 190 + platformWin32/winAsync.cpp | 67 + platformWin32/winCPUInfo.cpp | 178 + platformWin32/winConsole.cpp | 324 + platformWin32/winConsole.h | 48 + platformWin32/winDInputDevice.cpp | 1584 ++ platformWin32/winDInputDevice.h | 145 + platformWin32/winDirectInput.cpp | 880 + platformWin32/winDirectInput.h | 115 + platformWin32/winDlibrary.cpp | 75 + platformWin32/winExec.cpp | 160 + platformWin32/winFileio.cpp | 1460 ++ platformWin32/winFont.cpp | 294 + platformWin32/winFont.h | 43 + platformWin32/winInput.cpp | 852 + platformWin32/winMath.cpp | 111 + platformWin32/winMath_ASM.cpp | 77 + platformWin32/winMemory.cpp | 57 + platformWin32/winProcessControl.cpp | 53 + platformWin32/winRedbook.cpp | 480 + platformWin32/winSemaphore.cpp | 58 + platformWin32/winTLS.cpp | 73 + platformWin32/winTime.cpp | 151 + platformWin32/winTimer.cpp | 71 + platformWin32/winUser.cpp | 208 + platformWin32/winVFS.cpp | 94 + platformWin32/winVolume.cpp | 737 + platformWin32/winVolume.h | 112 + platformWin32/winWindow.cpp | 592 + platformWin32/win_common_prefix.h | 37 + platformWin32/win_debug_prefix.h | 24 + platformWin32/win_release_prefix.h | 28 + platformX86UNIX/gl_types.h | 966 ++ platformX86UNIX/platformAL.h | 145 + platformX86UNIX/platformX86UNIX.h | 52 + platformX86UNIX/threads/mutex.cpp | 59 + platformX86UNIX/threads/semaphore.cpp | 61 + platformX86UNIX/threads/thread.cpp | 142 + platformX86UNIX/x86UNIXAsmBlit.cpp | 46 + platformX86UNIX/x86UNIXCPUInfo.cpp | 139 + platformX86UNIX/x86UNIXConsole.cpp | 387 + platformX86UNIX/x86UNIXFileio.cpp | 1247 ++ platformX86UNIX/x86UNIXFont.client.cpp | 331 + platformX86UNIX/x86UNIXFont.h | 49 + platformX86UNIX/x86UNIXGL.client.cpp | 374 + platformX86UNIX/x86UNIXIO.cpp | 34 + platformX86UNIX/x86UNIXInput.client.cpp | 587 + .../x86UNIXInputManager.client.cpp | 1818 +++ platformX86UNIX/x86UNIXInputManager.h | 173 + platformX86UNIX/x86UNIXMain.cpp | 124 + platformX86UNIX/x86UNIXMath.cpp | 129 + platformX86UNIX/x86UNIXMath_ASM.cpp | 38 + platformX86UNIX/x86UNIXMemory.cpp | 51 + platformX86UNIX/x86UNIXMessageBox.client.cpp | 478 + platformX86UNIX/x86UNIXMessageBox.h | 103 + platformX86UNIX/x86UNIXNet.cpp | 905 + platformX86UNIX/x86UNIXOGLVideo.client.cpp | 488 + platformX86UNIX/x86UNIXOGLVideo.h | 41 + platformX86UNIX/x86UNIXOpenAL.client.cpp | 267 + platformX86UNIX/x86UNIXProcessControl.cpp | 191 + platformX86UNIX/x86UNIXRedbook.cpp | 438 + platformX86UNIX/x86UNIXState.h | 257 + platformX86UNIX/x86UNIXStdConsole.h | 56 + platformX86UNIX/x86UNIXStrings.cpp | 53 + platformX86UNIX/x86UNIXStub.dedicated.cpp | 123 + platformX86UNIX/x86UNIXTime.cpp | 144 + platformX86UNIX/x86UNIXUtils.cpp | 267 + platformX86UNIX/x86UNIXUtils.h | 73 + platformX86UNIX/x86UNIXWindow.client.cpp | 842 + postFx/postEffect.cpp | 1334 ++ postFx/postEffect.h | 318 + postFx/postEffectCommon.h | 73 + postFx/postEffectManager.cpp | 280 + postFx/postEffectManager.h | 123 + postFx/postEffectVis.cpp | 359 + postFx/postEffectVis.h | 88 + renderInstance/forcedMaterialMeshMgr.cpp | 98 + renderInstance/forcedMaterialMeshMgr.h | 36 + renderInstance/renderBinManager.cpp | 135 + renderInstance/renderBinManager.h | 129 + renderInstance/renderFormatChanger.cpp | 292 + renderInstance/renderFormatChanger.h | 68 + renderInstance/renderGlowMgr.cpp | 179 + renderInstance/renderGlowMgr.h | 69 + renderInstance/renderImposterMgr.cpp | 400 + renderInstance/renderImposterMgr.h | 159 + renderInstance/renderMeshMgr.cpp | 240 + renderInstance/renderMeshMgr.h | 37 + renderInstance/renderObjectMgr.cpp | 50 + renderInstance/renderObjectMgr.h | 34 + renderInstance/renderOcclusionMgr.cpp | 218 + renderInstance/renderOcclusionMgr.h | 44 + renderInstance/renderParticleMgr.cpp | 781 + renderInstance/renderParticleMgr.h | 124 + renderInstance/renderPassManager.cpp | 427 + renderInstance/renderPassManager.h | 431 + renderInstance/renderPassStateToken.cpp | 111 + renderInstance/renderPassStateToken.h | 46 + renderInstance/renderPrePassMgr.cpp | 765 + renderInstance/renderPrePassMgr.h | 171 + renderInstance/renderTerrainMgr.cpp | 174 + renderInstance/renderTerrainMgr.h | 89 + renderInstance/renderTexTargetBinManager.cpp | 353 + renderInstance/renderTexTargetBinManager.h | 125 + renderInstance/renderTranslucentMgr.cpp | 256 + renderInstance/renderTranslucentMgr.h | 50 + sceneGraph/fogStructs.h | 35 + sceneGraph/pathManager.cpp | 404 + sceneGraph/pathManager.h | 115 + sceneGraph/reflectionManager.cpp | 241 + sceneGraph/reflectionManager.h | 120 + sceneGraph/reflector.cpp | 606 + sceneGraph/reflector.h | 200 + sceneGraph/sceneGraph.cpp | 865 + sceneGraph/sceneGraph.h | 344 + sceneGraph/sceneObject.cpp | 2319 +++ sceneGraph/sceneObject.h | 990 ++ sceneGraph/sceneRoot.cpp | 252 + sceneGraph/sceneRoot.h | 55 + sceneGraph/sceneState.cpp | 217 + sceneGraph/sceneState.h | 353 + sceneGraph/sceneTraversal.cpp | 409 + sceneGraph/sgUtil.cpp | 310 + sceneGraph/sgUtil.h | 58 + sceneGraph/simPath.cpp | 434 + sceneGraph/simPath.h | 127 + sceneGraph/windingClipper.cpp | 127 + sceneGraph/windingClipper.h | 15 + sfx/dsound/dsFunctions.h | 10 + sfx/dsound/sfxDSBuffer.cpp | 250 + sfx/dsound/sfxDSBuffer.h | 84 + sfx/dsound/sfxDSDevice.cpp | 214 + sfx/dsound/sfxDSDevice.h | 124 + sfx/dsound/sfxDSProvider.cpp | 163 + sfx/dsound/sfxDSVoice.cpp | 212 + sfx/dsound/sfxDSVoice.h | 75 + sfx/fmod/fmodFunctions.h | 63 + sfx/fmod/sfxFMODBuffer.cpp | 281 + sfx/fmod/sfxFMODBuffer.h | 46 + sfx/fmod/sfxFMODDevice.cpp | 211 + sfx/fmod/sfxFMODDevice.h | 195 + sfx/fmod/sfxFMODProvider.cpp | 194 + sfx/fmod/sfxFMODVoice.cpp | 264 + sfx/fmod/sfxFMODVoice.h | 96 + sfx/media/sfxVorbisStream.cpp | 266 + sfx/media/sfxVorbisStream.h | 97 + sfx/media/sfxWavStream.cpp | 282 + sfx/media/sfxWavStream.h | 64 + sfx/null/sfxNullBuffer.cpp | 26 + sfx/null/sfxNullBuffer.h | 33 + sfx/null/sfxNullDevice.cpp | 52 + sfx/null/sfxNullDevice.h | 45 + sfx/null/sfxNullProvider.cpp | 62 + sfx/null/sfxNullVoice.cpp | 104 + sfx/null/sfxNullVoice.h | 69 + sfx/openal/LoadOAL.h | 192 + sfx/openal/aldlist.cpp | 315 + sfx/openal/aldlist.h | 53 + sfx/openal/mac/LoadOAL.mac.cpp | 428 + sfx/openal/sfxALBuffer.cpp | 209 + sfx/openal/sfxALBuffer.h | 102 + sfx/openal/sfxALCaps.h | 23 + sfx/openal/sfxALDevice.cpp | 165 + sfx/openal/sfxALDevice.h | 73 + sfx/openal/sfxALProvider.cpp | 108 + sfx/openal/sfxALVoice.cpp | 218 + sfx/openal/sfxALVoice.h | 90 + sfx/openal/win32/LoadOAL.cpp | 454 + sfx/sfxBuffer.cpp | 211 + sfx/sfxBuffer.h | 206 + sfx/sfxCommon.h | 208 + sfx/sfxDescription.cpp | 184 + sfx/sfxDescription.h | 124 + sfx/sfxDevice.cpp | 192 + sfx/sfxDevice.h | 154 + sfx/sfxEffect.cpp | 154 + sfx/sfxEffect.h | 173 + sfx/sfxEnvironment.cpp | 139 + sfx/sfxEnvironment.h | 64 + sfx/sfxFileStream.cpp | 156 + sfx/sfxFileStream.h | 98 + sfx/sfxInternal.cpp | 173 + sfx/sfxInternal.h | 407 + sfx/sfxListener.cpp | 74 + sfx/sfxListener.h | 63 + sfx/sfxPacketStream.cpp | 100 + sfx/sfxPacketStream.h | 67 + sfx/sfxProfile.cpp | 350 + sfx/sfxProfile.h | 179 + sfx/sfxProvider.cpp | 78 + sfx/sfxProvider.h | 111 + sfx/sfxResource.cpp | 65 + sfx/sfxResource.h | 97 + sfx/sfxSource.cpp | 834 + sfx/sfxSource.h | 328 + sfx/sfxStream.h | 69 + sfx/sfxSystem.cpp | 970 ++ sfx/sfxSystem.h | 395 + sfx/sfxVoice.cpp | 243 + sfx/sfxVoice.h | 144 + sfx/xaudio/sfxXAudioBuffer.cpp | 96 + sfx/xaudio/sfxXAudioBuffer.h | 63 + sfx/xaudio/sfxXAudioDevice.cpp | 226 + sfx/xaudio/sfxXAudioDevice.h | 83 + sfx/xaudio/sfxXAudioProvider.cpp | 158 + sfx/xaudio/sfxXAudioVoice.cpp | 402 + sfx/xaudio/sfxXAudioVoice.h | 128 + shaderGen/GLSL/bumpGLSL.cpp | 284 + shaderGen/GLSL/bumpGLSL.h | 66 + shaderGen/GLSL/depthGLSL.cpp | 150 + shaderGen/GLSL/depthGLSL.h | 43 + shaderGen/GLSL/paraboloidGLSL.cpp | 144 + shaderGen/GLSL/paraboloidGLSL.h | 31 + shaderGen/GLSL/pixSpecularGLSL.cpp | 191 + shaderGen/GLSL/pixSpecularGLSL.h | 62 + shaderGen/GLSL/shaderCompGLSL.cpp | 246 + shaderGen/GLSL/shaderCompGLSL.h | 56 + shaderGen/GLSL/shaderFeatureGLSL.cpp | 1659 ++ shaderGen/GLSL/shaderFeatureGLSL.h | 447 + shaderGen/GLSL/shaderGenGLSL.cpp | 162 + shaderGen/GLSL/shaderGenGLSL.h | 42 + shaderGen/GLSL/shaderGenGLSLInit.cpp | 71 + shaderGen/HLSL/bumpHLSL.cpp | 295 + shaderGen/HLSL/bumpHLSL.h | 69 + shaderGen/HLSL/depthHLSL.cpp | 169 + shaderGen/HLSL/depthHLSL.h | 43 + shaderGen/HLSL/paraboloidHLSL.cpp | 149 + shaderGen/HLSL/paraboloidHLSL.h | 31 + shaderGen/HLSL/pixSpecularHLSL.cpp | 127 + shaderGen/HLSL/pixSpecularHLSL.h | 60 + shaderGen/HLSL/shaderCompHLSL.cpp | 319 + shaderGen/HLSL/shaderCompHLSL.h | 55 + shaderGen/HLSL/shaderFeatureHLSL.cpp | 1855 +++ shaderGen/HLSL/shaderFeatureHLSL.h | 543 + shaderGen/HLSL/shaderGenHLSL.cpp | 182 + shaderGen/HLSL/shaderGenHLSL.h | 43 + shaderGen/HLSL/shaderGenHLSLInit.cpp | 80 + shaderGen/conditionerFeature.cpp | 224 + shaderGen/conditionerFeature.h | 120 + shaderGen/featureMgr.cpp | 107 + shaderGen/featureMgr.h | 69 + shaderGen/featureSet.cpp | 211 + shaderGen/featureSet.h | 101 + shaderGen/featureType.cpp | 45 + shaderGen/featureType.h | 101 + shaderGen/langElement.cpp | 183 + shaderGen/langElement.h | 166 + shaderGen/shaderComp.cpp | 26 + shaderGen/shaderComp.h | 82 + shaderGen/shaderDependency.cpp | 30 + shaderGen/shaderDependency.h | 49 + shaderGen/shaderFeature.cpp | 56 + shaderGen/shaderFeature.h | 272 + shaderGen/shaderGen.cpp | 472 + shaderGen/shaderGen.h | 209 + shaderGen/shaderGenVars.cpp | 56 + shaderGen/shaderGenVars.h | 70 + shaderGen/shaderOp.cpp | 141 + shaderGen/shaderOp.h | 134 + sim/actionMap.cpp | 1942 +++ sim/actionMap.h | 165 + sim/connectionStringTable.cpp | 172 + sim/connectionStringTable.h | 92 + sim/netConnection.cpp | 1212 ++ sim/netConnection.h | 1115 ++ sim/netDownload.cpp | 244 + sim/netEvent.cpp | 437 + sim/netGhost.cpp | 1246 ++ sim/netInterface.cpp | 634 + sim/netInterface.h | 125 + sim/netObject.cpp | 312 + sim/netObject.h | 431 + sim/netStringTable.cpp | 262 + sim/netStringTable.h | 139 + sim/processList.cpp | 242 + sim/processList.h | 107 + terrain/glsl/terrFeatureGLSL.cpp | 752 + terrain/glsl/terrFeatureGLSL.h | 140 + terrain/hlsl/terrFeatureHLSL.cpp | 739 + terrain/hlsl/terrFeatureHLSL.h | 124 + terrain/terrCell.cpp | 714 + terrain/terrCell.h | 162 + terrain/terrCellMaterial.cpp | 578 + terrain/terrCellMaterial.h | 168 + terrain/terrCollision.cpp | 960 ++ terrain/terrCollision.h | 40 + terrain/terrData.cpp | 1084 ++ terrain/terrData.h | 394 + terrain/terrExport.cpp | 145 + terrain/terrFeatureTypes.cpp | 20 + terrain/terrFeatureTypes.h | 23 + terrain/terrFile.cpp | 857 + terrain/terrFile.h | 239 + terrain/terrImport.cpp | 295 + terrain/terrLighting.cpp | 75 + terrain/terrMaterial.cpp | 117 + terrain/terrMaterial.h | 94 + terrain/terrRender.cpp | 462 + terrain/terrRender.h | 42 + ts/arch/tsMeshIntrinsics.arch.h | 32 + ts/arch/tsMeshIntrinsics.sse.cpp | 166 + ts/arch/tsMeshIntrinsics.sse4.cpp | 86 + ts/collada/colladaAppMaterial.cpp | 219 + ts/collada/colladaAppMaterial.h | 69 + ts/collada/colladaAppMesh.cpp | 960 ++ ts/collada/colladaAppMesh.h | 211 + ts/collada/colladaAppNode.cpp | 194 + ts/collada/colladaAppNode.h | 93 + ts/collada/colladaAppSequence.cpp | 54 + ts/collada/colladaAppSequence.h | 44 + ts/collada/colladaExtensions.cpp | 43 + ts/collada/colladaExtensions.h | 282 + ts/collada/colladaImport.cpp | 225 + ts/collada/colladaLights.cpp | 196 + ts/collada/colladaShapeLoader.cpp | 632 + ts/collada/colladaShapeLoader.h | 41 + ts/collada/colladaUtils.cpp | 1459 ++ ts/collada/colladaUtils.h | 727 + ts/loader/appMaterial.h | 26 + ts/loader/appMesh.cpp | 74 + ts/loader/appMesh.h | 88 + ts/loader/appNode.cpp | 87 + ts/loader/appNode.h | 74 + ts/loader/appSequence.h | 44 + ts/loader/tsShapeLoader.cpp | 1228 ++ ts/loader/tsShapeLoader.h | 166 + ts/tsAnimate.cpp | 1083 ++ ts/tsCollision.cpp | 1135 ++ ts/tsDecal.cpp | 90 + ts/tsDecal.h | 49 + ts/tsDump.cpp | 215 + ts/tsIntegerSet.cpp | 289 + ts/tsIntegerSet.h | 102 + ts/tsLastDetail.cpp | 468 + ts/tsLastDetail.h | 187 + ts/tsMaterialList.cpp | 330 + ts/tsMesh.cpp | 2934 ++++ ts/tsMesh.h | 512 + ts/tsMeshIntrinsics.cpp | 106 + ts/tsMeshIntrinsics.h | 34 + ts/tsPartInstance.cpp | 353 + ts/tsPartInstance.h | 121 + ts/tsRenderState.cpp | 30 + ts/tsRenderState.h | 109 + ts/tsShape.cpp | 2300 +++ ts/tsShape.h | 692 + ts/tsShapeAlloc.cpp | 212 + ts/tsShapeAlloc.h | 139 + ts/tsShapeConstruct.cpp | 1841 +++ ts/tsShapeConstruct.h | 140 + ts/tsShapeEdit.cpp | 1638 ++ ts/tsShapeInstance.cpp | 778 + ts/tsShapeInstance.h | 752 + ts/tsShapeOldRead.cpp | 734 + ts/tsSortedMesh.cpp | 151 + ts/tsSortedMesh.h | 63 + ts/tsThread.cpp | 795 + ts/tsTransform.cpp | 139 + ts/tsTransform.h | 91 + unit/consoleTest.cpp | 21 + unit/memoryTester.cpp | 20 + unit/memoryTester.h | 20 + unit/test.cpp | 266 + unit/test.h | 148 + unit/tests/testComponents.cpp | 121 + unit/tests/testMatrixMul.cpp | 99 + unit/tests/testRuntimeClassRep.cpp | 85 + unit/tests/testSwizzle.cpp | 109 + unit/tests/testThreadStatic.cpp | 84 + unit/tests/testThreadStaticPerformance.cpp | 65 + unit/tests/testVector.cpp | 36 + unit/unitTestComponentInterface.cpp | 7 + unit/unitTestComponentInterface.h | 74 + util/catmullRom.cpp | 157 + util/catmullRom.h | 354 + util/fpsTracker.cpp | 76 + util/fpsTracker.h | 34 + util/imposterCapture.cpp | 472 + util/imposterCapture.h | 90 + util/messaging/dispatcher.cpp | 371 + util/messaging/dispatcher.h | 254 + util/messaging/eventManager.cpp | 456 + util/messaging/eventManager.h | 192 + util/messaging/message.cpp | 127 + util/messaging/message.h | 125 + util/messaging/messageForwarder.cpp | 51 + util/messaging/messageForwarder.h | 57 + util/messaging/scriptMsgListener.cpp | 68 + util/messaging/scriptMsgListener.h | 56 + util/noise2d.cpp | 501 + util/noise2d.h | 78 + util/quadTreeTracer.cpp | 225 + util/quadTreeTracer.h | 87 + util/rectClipper.cpp | 150 + util/rectClipper.h | 55 + util/returnType.h | 49 + util/sampler.cpp | 401 + util/sampler.h | 129 + util/settings.cpp | 552 + util/settings.h | 84 + util/tempAlloc.h | 42 + util/triBoxCheck.cpp | 175 + util/triBoxCheck.h | 36 + util/triRayCheck.cpp | 324 + util/triRayCheck.h | 27 + util/undo.cpp | 394 + util/undo.h | 171 + .../dedicated/dedicatedWindowStub.cpp | 12 + windowManager/dedicated/dedicatedWindowStub.h | 92 + windowManager/mac/macCursorController.h | 33 + windowManager/mac/macCursorController.mm | 143 + windowManager/mac/macView.h | 57 + windowManager/mac/macView.mm | 353 + windowManager/mac/macWindow.h | 174 + windowManager/mac/macWindow.mm | 572 + windowManager/mac/macWindowManager.h | 112 + windowManager/mac/macWindowManager.mm | 228 + windowManager/platformCursorController.cpp | 77 + windowManager/platformCursorController.h | 85 + windowManager/platformInterface.cpp | 126 + windowManager/platformWindow.cpp | 20 + windowManager/platformWindow.h | 416 + windowManager/platformWindowMgr.h | 116 + windowManager/test/testWinMgr.cpp | 521 + windowManager/win32/win32CursorController.cpp | 185 + windowManager/win32/win32CursorController.h | 35 + windowManager/win32/win32SplashScreen.cpp | 125 + windowManager/win32/win32Window.cpp | 1025 ++ windowManager/win32/win32Window.h | 208 + windowManager/win32/win32WindowMgr.cpp | 418 + windowManager/win32/win32WindowMgr.h | 72 + windowManager/win32/winDispatch.cpp | 573 + windowManager/win32/winDispatch.h | 57 + windowManager/windowInputGenerator.cpp | 335 + windowManager/windowInputGenerator.h | 43 + 1762 files changed, 526661 insertions(+) create mode 100644 T3D/aiClient.cpp create mode 100644 T3D/aiClient.h create mode 100644 T3D/aiConnection.cpp create mode 100644 T3D/aiConnection.h create mode 100644 T3D/aiPlayer.cpp create mode 100644 T3D/aiPlayer.h create mode 100644 T3D/ambientAudioManager.cpp create mode 100644 T3D/ambientAudioManager.h create mode 100644 T3D/camera.cpp create mode 100644 T3D/camera.h create mode 100644 T3D/cameraSpline.cpp create mode 100644 T3D/cameraSpline.h create mode 100644 T3D/containerQuery.cpp create mode 100644 T3D/containerQuery.h create mode 100644 T3D/debris.cpp create mode 100644 T3D/debris.h create mode 100644 T3D/decal/decalData.cpp create mode 100644 T3D/decal/decalData.h create mode 100644 T3D/decal/decalDataFile.cpp create mode 100644 T3D/decal/decalDataFile.h create mode 100644 T3D/decal/decalInstance.cpp create mode 100644 T3D/decal/decalInstance.h create mode 100644 T3D/decal/decalManager.cpp create mode 100644 T3D/decal/decalManager.h create mode 100644 T3D/examples/renderMeshExample.cpp create mode 100644 T3D/examples/renderMeshExample.h create mode 100644 T3D/examples/renderObjectExample.cpp create mode 100644 T3D/examples/renderObjectExample.h create mode 100644 T3D/examples/renderShapeExample.cpp create mode 100644 T3D/examples/renderShapeExample.h create mode 100644 T3D/fps/guiClockHud.cpp create mode 100644 T3D/fps/guiCrossHairHud.cpp create mode 100644 T3D/fps/guiHealthBarHud.cpp create mode 100644 T3D/fps/guiShapeNameHud.cpp create mode 100644 T3D/fps/guiShapeNameHud.h create mode 100644 T3D/fx/cameraFXMgr.cpp create mode 100644 T3D/fx/cameraFXMgr.h create mode 100644 T3D/fx/explosion.cpp create mode 100644 T3D/fx/explosion.h create mode 100644 T3D/fx/fxFoliageReplicator.cpp create mode 100644 T3D/fx/fxFoliageReplicator.h create mode 100644 T3D/fx/fxShapeReplicator.cpp create mode 100644 T3D/fx/fxShapeReplicator.h create mode 100644 T3D/fx/groundCover.cpp create mode 100644 T3D/fx/groundCover.h create mode 100644 T3D/fx/lightning.cpp create mode 100644 T3D/fx/lightning.h create mode 100644 T3D/fx/particle.cpp create mode 100644 T3D/fx/particle.h create mode 100644 T3D/fx/particleEmitter.cpp create mode 100644 T3D/fx/particleEmitter.h create mode 100644 T3D/fx/particleEmitterNode.cpp create mode 100644 T3D/fx/particleEmitterNode.h create mode 100644 T3D/fx/precipitation.cpp create mode 100644 T3D/fx/precipitation.h create mode 100644 T3D/fx/splash.cpp create mode 100644 T3D/fx/splash.h create mode 100644 T3D/fx/windEmitter.cpp create mode 100644 T3D/fx/windEmitter.h create mode 100644 T3D/gameBase.cpp create mode 100644 T3D/gameBase.h create mode 100644 T3D/gameConnection.cpp create mode 100644 T3D/gameConnection.h create mode 100644 T3D/gameConnectionEvents.cpp create mode 100644 T3D/gameConnectionEvents.h create mode 100644 T3D/gameFunctions.cpp create mode 100644 T3D/gameFunctions.h create mode 100644 T3D/gameProcess.cpp create mode 100644 T3D/gameProcess.h create mode 100644 T3D/gameTSCtrl.cpp create mode 100644 T3D/gameTSCtrl.h create mode 100644 T3D/groundPlane.cpp create mode 100644 T3D/groundPlane.h create mode 100644 T3D/guiMaterialPreview.cpp create mode 100644 T3D/guiMaterialPreview.h create mode 100644 T3D/guiNoMouseCtrl.cpp create mode 100644 T3D/guiObjectView.cpp create mode 100644 T3D/guiObjectView.h create mode 100644 T3D/hifi/gameProcess.cpp create mode 100644 T3D/hifi/gameProcess.h create mode 100644 T3D/hifi/moveList.cpp create mode 100644 T3D/hifi/moveList.h create mode 100644 T3D/item.cpp create mode 100644 T3D/item.h create mode 100644 T3D/levelInfo.cpp create mode 100644 T3D/levelInfo.h create mode 100644 T3D/lightAnimData.cpp create mode 100644 T3D/lightAnimData.h create mode 100644 T3D/lightBase.cpp create mode 100644 T3D/lightBase.h create mode 100644 T3D/lightDescription.cpp create mode 100644 T3D/lightDescription.h create mode 100644 T3D/lightFlareData.cpp create mode 100644 T3D/lightFlareData.h create mode 100644 T3D/missionArea.cpp create mode 100644 T3D/missionArea.h create mode 100644 T3D/missionMarker.cpp create mode 100644 T3D/missionMarker.h create mode 100644 T3D/moveList.cpp create mode 100644 T3D/moveList.h create mode 100644 T3D/moveManager.cpp create mode 100644 T3D/moveManager.h create mode 100644 T3D/objectTypes.h create mode 100644 T3D/pathCamera.cpp create mode 100644 T3D/pathCamera.h create mode 100644 T3D/physicalZone.cpp create mode 100644 T3D/physicalZone.h create mode 100644 T3D/physics/physicsObject.cpp create mode 100644 T3D/physics/physicsObject.h create mode 100644 T3D/physics/physicsPlayer.cpp create mode 100644 T3D/physics/physicsPlayer.h create mode 100644 T3D/physics/physicsPlugin.cpp create mode 100644 T3D/physics/physicsPlugin.h create mode 100644 T3D/physics/physicsStatic.cpp create mode 100644 T3D/physics/physicsStatic.h create mode 100644 T3D/physics/physicsWorld.cpp create mode 100644 T3D/physics/physicsWorld.h create mode 100644 T3D/physics/physx/px.h create mode 100644 T3D/physics/physx/pxBoxPlayer.cpp create mode 100644 T3D/physics/physx/pxBoxPlayer.h create mode 100644 T3D/physics/physx/pxCapsulePlayer.cpp create mode 100644 T3D/physics/physx/pxCapsulePlayer.h create mode 100644 T3D/physics/physx/pxCasts.h create mode 100644 T3D/physics/physx/pxCloth.cpp create mode 100644 T3D/physics/physx/pxCloth.h create mode 100644 T3D/physics/physx/pxContactReporter.cpp create mode 100644 T3D/physics/physx/pxContactReporter.h create mode 100644 T3D/physics/physx/pxFluid.cpp create mode 100644 T3D/physics/physx/pxFluid.h create mode 100644 T3D/physics/physx/pxMaterial.cpp create mode 100644 T3D/physics/physx/pxMaterial.h create mode 100644 T3D/physics/physx/pxMeshRoad.cpp create mode 100644 T3D/physics/physx/pxMeshRoad.h create mode 100644 T3D/physics/physx/pxMultiActor.cpp create mode 100644 T3D/physics/physx/pxMultiActor.h create mode 100644 T3D/physics/physx/pxPlane.cpp create mode 100644 T3D/physics/physx/pxPlane.h create mode 100644 T3D/physics/physx/pxPlayer.cpp create mode 100644 T3D/physics/physx/pxPlayer.h create mode 100644 T3D/physics/physx/pxPlugin.cpp create mode 100644 T3D/physics/physx/pxPlugin.h create mode 100644 T3D/physics/physx/pxSingleActor.cpp create mode 100644 T3D/physics/physx/pxSingleActor.h create mode 100644 T3D/physics/physx/pxStream.cpp create mode 100644 T3D/physics/physx/pxStream.h create mode 100644 T3D/physics/physx/pxTSStatic.cpp create mode 100644 T3D/physics/physx/pxTSStatic.h create mode 100644 T3D/physics/physx/pxTerrain.cpp create mode 100644 T3D/physics/physx/pxTerrain.h create mode 100644 T3D/physics/physx/pxUserData.cpp create mode 100644 T3D/physics/physx/pxUserData.h create mode 100644 T3D/physics/physx/pxUtils.cpp create mode 100644 T3D/physics/physx/pxUtils.h create mode 100644 T3D/physics/physx/pxWorld.cpp create mode 100644 T3D/physics/physx/pxWorld.h create mode 100644 T3D/player.cpp create mode 100644 T3D/player.h create mode 100644 T3D/pointLight.cpp create mode 100644 T3D/pointLight.h create mode 100644 T3D/portal.cpp create mode 100644 T3D/portal.h create mode 100644 T3D/projectile.cpp create mode 100644 T3D/projectile.h create mode 100644 T3D/resource.h create mode 100644 T3D/rigid.cpp create mode 100644 T3D/rigid.h create mode 100644 T3D/rigidShape.cpp create mode 100644 T3D/rigidShape.h create mode 100644 T3D/sceneComponent/T3DSceneClient.cpp create mode 100644 T3D/sceneComponent/T3DSceneClient.h create mode 100644 T3D/sceneComponent/T3DSceneComponent.cpp create mode 100644 T3D/sceneComponent/T3DSceneComponent.h create mode 100644 T3D/sceneComponent/T3DTransform.cpp create mode 100644 T3D/sceneComponent/T3DTransform.h create mode 100644 T3D/scopeAlwaysShape.cpp create mode 100644 T3D/sfxEmitter.cpp create mode 100644 T3D/sfxEmitter.h create mode 100644 T3D/shapeBase.cpp create mode 100644 T3D/shapeBase.h create mode 100644 T3D/shapeCollision.cpp create mode 100644 T3D/shapeImage.cpp create mode 100644 T3D/sphere.cpp create mode 100644 T3D/sphere.h create mode 100644 T3D/spotLight.cpp create mode 100644 T3D/spotLight.h create mode 100644 T3D/staticShape.cpp create mode 100644 T3D/staticShape.h create mode 100644 T3D/tickCache.cpp create mode 100644 T3D/tickCache.h create mode 100644 T3D/trigger.cpp create mode 100644 T3D/trigger.h create mode 100644 T3D/tsStatic.cpp create mode 100644 T3D/tsStatic.h create mode 100644 T3D/vehicles/flyingVehicle.cpp create mode 100644 T3D/vehicles/flyingVehicle.h create mode 100644 T3D/vehicles/guiSpeedometer.cpp create mode 100644 T3D/vehicles/hoverVehicle.cpp create mode 100644 T3D/vehicles/hoverVehicle.h create mode 100644 T3D/vehicles/vehicle.cpp create mode 100644 T3D/vehicles/vehicle.h create mode 100644 T3D/vehicles/vehicleBlocker.cpp create mode 100644 T3D/vehicles/vehicleBlocker.h create mode 100644 T3D/vehicles/wheeledVehicle.cpp create mode 100644 T3D/vehicles/wheeledVehicle.h create mode 100644 T3D/zone.cpp create mode 100644 T3D/zone.h create mode 100644 add/GUI/GuiCellArray.cpp create mode 100644 add/GUI/GuiCellArray.h create mode 100644 add/GUI/GuiRPGStatuBar.cpp create mode 100644 add/GUI/GuiRPGStatuBar.h create mode 100644 add/GUI/GuiRPGStatuBox.cpp create mode 100644 add/GUI/GuiRPGStatuBox.h create mode 100644 add/GUI/GuiRPGStatuLabel.cpp create mode 100644 add/GUI/GuiRPGStatuLabel.h create mode 100644 add/GUI/GuiRPGTsCtrl.cpp create mode 100644 add/GUI/GuiRPGTsCtrl.h create mode 100644 add/GUI/GuiRadarMap.cpp create mode 100644 add/GUI/GuiRadarMap.h create mode 100644 add/GUI/GuiRoleList.cpp create mode 100644 add/GUI/GuiRoleList.h create mode 100644 add/Global/GlobalStatic.cpp create mode 100644 add/Global/GlobalStatic.h create mode 100644 add/Others/CSLock.h create mode 100644 add/Others/LockFreeChunker.cpp create mode 100644 add/Others/LockFreeChunker.h create mode 100644 add/Others/StringHash.h create mode 100644 add/RPGPack/Constraint/CST_Caster.cpp create mode 100644 add/RPGPack/Constraint/CST_Caster.h create mode 100644 add/RPGPack/Constraint/CST_GameBase.cpp create mode 100644 add/RPGPack/Constraint/CST_GameBase.h create mode 100644 add/RPGPack/Constraint/CST_Player.cpp create mode 100644 add/RPGPack/Constraint/CST_Player.h create mode 100644 add/RPGPack/Constraint/CST_Point.cpp create mode 100644 add/RPGPack/Constraint/CST_Point.h create mode 100644 add/RPGPack/Constraint/CST_SceneObject.cpp create mode 100644 add/RPGPack/Constraint/CST_SceneObject.h create mode 100644 add/RPGPack/Constraint/CST_ShapeBase.cpp create mode 100644 add/RPGPack/Constraint/CST_ShapeBase.h create mode 100644 add/RPGPack/Constraint/CST_Target.cpp create mode 100644 add/RPGPack/Constraint/CST_Target.h create mode 100644 add/RPGPack/Constraint/CST_TerrainClickedPoint.cpp create mode 100644 add/RPGPack/Constraint/CST_TerrainClickedPoint.h create mode 100644 add/RPGPack/Constraint/CST_TerrainRolloverPoint.cpp create mode 100644 add/RPGPack/Constraint/CST_TerrainRolloverPoint.h create mode 100644 add/RPGPack/Constraint/Constraint.cpp create mode 100644 add/RPGPack/Constraint/Constraint.h create mode 100644 add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.cpp create mode 100644 add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.h create mode 100644 add/RPGPack/EffectPhrase/EffectWrapperPhrase.cpp create mode 100644 add/RPGPack/EffectPhrase/EffectWrapperPhrase.h create mode 100644 add/RPGPack/EffectWrapper/EffectWrapper.cpp create mode 100644 add/RPGPack/EffectWrapper/EffectWrapper.h create mode 100644 add/RPGPack/EffectWrapper/EffectWrapperData.cpp create mode 100644 add/RPGPack/EffectWrapper/EffectWrapperData.h create mode 100644 add/RPGPack/EffectWrapper/EffectWrapperDesc.cpp create mode 100644 add/RPGPack/EffectWrapper/EffectWrapperDesc.h create mode 100644 add/RPGPack/Effects/EFX_AnimClip.cpp create mode 100644 add/RPGPack/Effects/EFX_AnimClip.h create mode 100644 add/RPGPack/Effects/EFX_Decal.cpp create mode 100644 add/RPGPack/Effects/EFX_Decal.h create mode 100644 add/RPGPack/Effects/EFX_Particle.cpp create mode 100644 add/RPGPack/Effects/EFX_Particle.h create mode 100644 add/RPGPack/RPGBase.cpp create mode 100644 add/RPGPack/RPGBase.h create mode 100644 add/RPGPack/RPGBaseData.cpp create mode 100644 add/RPGPack/RPGBaseData.h create mode 100644 add/RPGPack/RPGBook.cpp create mode 100644 add/RPGPack/RPGBook.h create mode 100644 add/RPGPack/RPGBookData.cpp create mode 100644 add/RPGPack/RPGBookData.h create mode 100644 add/RPGPack/RPGDefs.h create mode 100644 add/RPGPack/RPGEffectron/RPGEffectron.cpp create mode 100644 add/RPGPack/RPGEffectron/RPGEffectron.h create mode 100644 add/RPGPack/RPGEffectron/RPGEffectronData.cpp create mode 100644 add/RPGPack/RPGEffectron/RPGEffectronData.h create mode 100644 add/RPGPack/RPGSpell/RPGSpell.cpp create mode 100644 add/RPGPack/RPGSpell/RPGSpell.h create mode 100644 add/RPGPack/RPGSpell/RPGSpellData.cpp create mode 100644 add/RPGPack/RPGSpell/RPGSpellData.h create mode 100644 add/RPGPack/RPGUtils.cpp create mode 100644 add/RPGPack/RPGUtils.h create mode 100644 add/WLLib/wlmgr.cpp create mode 100644 add/WLLib/wlmgr.h create mode 100644 app/auth.h create mode 100644 app/badWordFilter.cpp create mode 100644 app/badWordFilter.h create mode 100644 app/banList.cpp create mode 100644 app/banList.h create mode 100644 app/game.cpp create mode 100644 app/game.h create mode 100644 app/mainLoop.cpp create mode 100644 app/mainLoop.h create mode 100644 app/net/httpObject.cpp create mode 100644 app/net/httpObject.h create mode 100644 app/net/net.cpp create mode 100644 app/net/netTest.cpp create mode 100644 app/net/serverQuery.cpp create mode 100644 app/net/serverQuery.h create mode 100644 app/net/tcpObject.cpp create mode 100644 app/net/tcpObject.h create mode 100644 app/version.cpp create mode 100644 app/version.h create mode 100644 cinterface/cinterface.cpp create mode 100644 cinterface/cinterface.h create mode 100644 collision/abstractPolyList.cpp create mode 100644 collision/abstractPolyList.h create mode 100644 collision/boxConvex.cpp create mode 100644 collision/boxConvex.h create mode 100644 collision/clippedPolyList.cpp create mode 100644 collision/clippedPolyList.h create mode 100644 collision/collision.h create mode 100644 collision/concretePolyList.cpp create mode 100644 collision/concretePolyList.h create mode 100644 collision/convex.cpp create mode 100644 collision/convex.h create mode 100644 collision/depthSortList.cpp create mode 100644 collision/depthSortList.h create mode 100644 collision/earlyOutPolyList.cpp create mode 100644 collision/earlyOutPolyList.h create mode 100644 collision/extrudedPolyList.cpp create mode 100644 collision/extrudedPolyList.h create mode 100644 collision/gjk.cpp create mode 100644 collision/gjk.h create mode 100644 collision/optimizedPolyList.cpp create mode 100644 collision/optimizedPolyList.h create mode 100644 collision/planeExtractor.cpp create mode 100644 collision/planeExtractor.h create mode 100644 collision/polyhedron.cpp create mode 100644 collision/polyhedron.h create mode 100644 collision/polytope.cpp create mode 100644 collision/polytope.h create mode 100644 component/componentInterface.cpp create mode 100644 component/componentInterface.h create mode 100644 component/dynamicConsoleMethodComponent.cpp create mode 100644 component/dynamicConsoleMethodComponent.h create mode 100644 component/interfaces/IProcessInput.h create mode 100644 component/moreAdvancedComponent.cpp create mode 100644 component/moreAdvancedComponent.h create mode 100644 component/simComponent.cpp create mode 100644 component/simComponent.h create mode 100644 component/simpleComponent.cpp create mode 100644 component/simpleComponent.h create mode 100644 console/CMDgram.y create mode 100644 console/CMDscan.cpp create mode 100644 console/CMDscan.l create mode 100644 console/ICallMethod.h create mode 100644 console/SimXMLDocument.cpp create mode 100644 console/SimXMLDocument.h create mode 100644 console/arrayObject.cpp create mode 100644 console/arrayObject.h create mode 100644 console/ast.h create mode 100644 console/astAlloc.cpp create mode 100644 console/astNodes.cpp create mode 100644 console/bison.bat create mode 100644 console/bison.simple create mode 100644 console/cmdgram.cpp create mode 100644 console/cmdgram.h create mode 100644 console/codeBlock.cpp create mode 100644 console/codeBlock.h create mode 100644 console/compiledEval.cpp create mode 100644 console/compiler.cpp create mode 100644 console/compiler.h create mode 100644 console/console.cpp create mode 100644 console/console.h create mode 100644 console/consoleCallback.cpp create mode 100644 console/consoleCallback.h create mode 100644 console/consoleDoc.cpp create mode 100644 console/consoleDoc.h create mode 100644 console/consoleFunctions.cpp create mode 100644 console/consoleInternal.cpp create mode 100644 console/consoleInternal.h create mode 100644 console/consoleLogger.cpp create mode 100644 console/consoleLogger.h create mode 100644 console/consoleObject.cpp create mode 100644 console/consoleObject.h create mode 100644 console/consoleParser.cpp create mode 100644 console/consoleParser.h create mode 100644 console/consoleTypes.cpp create mode 100644 console/consoleTypes.h create mode 100644 console/debugOutputConsumer.cpp create mode 100644 console/debugOutputConsumer.h create mode 100644 console/dynamicTypes.cpp create mode 100644 console/dynamicTypes.h create mode 100644 console/fieldBrushObject.cpp create mode 100644 console/fieldBrushObject.h create mode 100644 console/fileSystemFunctions.cpp create mode 100644 console/generateCompiler.bat create mode 100644 console/persistenceManager.cpp create mode 100644 console/persistenceManager.h create mode 100644 console/propertyParsing.cpp create mode 100644 console/propertyParsing.h create mode 100644 console/runtimeClassRep.cpp create mode 100644 console/runtimeClassRep.h create mode 100644 console/scriptFilename.cpp create mode 100644 console/scriptFilename.h create mode 100644 console/scriptObjects.cpp create mode 100644 console/scriptObjects.h create mode 100644 console/sim.cpp create mode 100644 console/sim.h create mode 100644 console/simBase.h create mode 100644 console/simDatablock.cpp create mode 100644 console/simDatablock.h create mode 100644 console/simDictionary.cpp create mode 100644 console/simDictionary.h create mode 100644 console/simEvents.cpp create mode 100644 console/simEvents.h create mode 100644 console/simFieldDictionary.cpp create mode 100644 console/simFieldDictionary.h create mode 100644 console/simManager.cpp create mode 100644 console/simObject.cpp create mode 100644 console/simObject.h create mode 100644 console/simObjectList.cpp create mode 100644 console/simObjectList.h create mode 100644 console/simObjectMemento.cpp create mode 100644 console/simObjectMemento.h create mode 100644 console/simSerialize.cpp create mode 100644 console/simSet.cpp create mode 100644 console/simSet.h create mode 100644 console/stringStack.cpp create mode 100644 console/stringStack.h create mode 100644 console/telnetConsole.cpp create mode 100644 console/telnetConsole.h create mode 100644 console/telnetDebugger.cpp create mode 100644 console/telnetDebugger.h create mode 100644 console/typeValidators.cpp create mode 100644 console/typeValidators.h create mode 100644 core/bitMatrix.h create mode 100644 core/bitRender.cpp create mode 100644 core/bitRender.h create mode 100644 core/bitSet.h create mode 100644 core/bitVector.cpp create mode 100644 core/bitVector.h create mode 100644 core/bitVectorW.h create mode 100644 core/color.cpp create mode 100644 core/color.h create mode 100644 core/crc.cpp create mode 100644 core/crc.h create mode 100644 core/dataChunker.cpp create mode 100644 core/dataChunker.h create mode 100644 core/dnet.cpp create mode 100644 core/dnet.h create mode 100644 core/fileObject.cpp create mode 100644 core/fileObject.h create mode 100644 core/fileio.h create mode 100644 core/filterStream.cpp create mode 100644 core/filterStream.h create mode 100644 core/frameAllocator.cpp create mode 100644 core/frameAllocator.h create mode 100644 core/iTickable.cpp create mode 100644 core/iTickable.h create mode 100644 core/idGenerator.cpp create mode 100644 core/idGenerator.h create mode 100644 core/memVolume.cpp create mode 100644 core/memVolume.h create mode 100644 core/ogg/oggInputStream.cpp create mode 100644 core/ogg/oggInputStream.h create mode 100644 core/ogg/oggTheoraDecoder.cpp create mode 100644 core/ogg/oggTheoraDecoder.h create mode 100644 core/ogg/oggVorbisDecoder.cpp create mode 100644 core/ogg/oggVorbisDecoder.h create mode 100644 core/resizeStream.cpp create mode 100644 core/resizeStream.h create mode 100644 core/resource.cpp create mode 100644 core/resource.h create mode 100644 core/resourceManager.cpp create mode 100644 core/resourceManager.h create mode 100644 core/stream/bitStream.cpp create mode 100644 core/stream/bitStream.h create mode 100644 core/stream/fileStream.cpp create mode 100644 core/stream/fileStream.h create mode 100644 core/stream/fileStreamObject.cpp create mode 100644 core/stream/fileStreamObject.h create mode 100644 core/stream/ioHelper.h create mode 100644 core/stream/memStream.cpp create mode 100644 core/stream/memStream.h create mode 100644 core/stream/stream.cpp create mode 100644 core/stream/stream.h create mode 100644 core/stream/streamObject.cpp create mode 100644 core/stream/streamObject.h create mode 100644 core/stream/tStream.h create mode 100644 core/stringBuffer.cpp create mode 100644 core/stringBuffer.h create mode 100644 core/stringTable.cpp create mode 100644 core/stringTable.h create mode 100644 core/strings/findMatch.cpp create mode 100644 core/strings/findMatch.h create mode 100644 core/strings/stringFunctions.cpp create mode 100644 core/strings/stringFunctions.h create mode 100644 core/strings/stringUnit.cpp create mode 100644 core/strings/stringUnit.h create mode 100644 core/strings/unicode.cpp create mode 100644 core/strings/unicode.h create mode 100644 core/tAlgorithm.h create mode 100644 core/tSimpleHashTable.h create mode 100644 core/tSparseArray.h create mode 100644 core/tagDictionary.cpp create mode 100644 core/tagDictionary.h create mode 100644 core/threadStatic.cpp create mode 100644 core/threadStatic.h create mode 100644 core/tokenizer.cpp create mode 100644 core/tokenizer.h create mode 100644 core/util/FastDelegate.h create mode 100644 core/util/autoPtr.h create mode 100644 core/util/byteBuffer.cpp create mode 100644 core/util/byteBuffer.h create mode 100644 core/util/byteswap.h create mode 100644 core/util/commonSwizzles.cpp create mode 100644 core/util/delegate.h create mode 100644 core/util/dxt5nmSwizzle.h create mode 100644 core/util/endian.h create mode 100644 core/util/fourcc.h create mode 100644 core/util/hashFunction.cpp create mode 100644 core/util/hashFunction.h create mode 100644 core/util/journal/journal.cpp create mode 100644 core/util/journal/journal.h create mode 100644 core/util/journal/journaledSignal.h create mode 100644 core/util/journal/process.cpp create mode 100644 core/util/journal/process.h create mode 100644 core/util/journal/test/testJournal.cpp create mode 100644 core/util/journal/test/testProcess.cpp create mode 100644 core/util/namedSingleton.h create mode 100644 core/util/noncopyable.h create mode 100644 core/util/path.cpp create mode 100644 core/util/path.h create mode 100644 core/util/preprocessorHelpers.h create mode 100644 core/util/rawData.h create mode 100644 core/util/refBase.h create mode 100644 core/util/rgb2luv.cpp create mode 100644 core/util/rgb2luv.h create mode 100644 core/util/rgb2xyz.cpp create mode 100644 core/util/rgb2xyz.h create mode 100644 core/util/safeCast.h create mode 100644 core/util/safeDelete.h create mode 100644 core/util/safeRelease.h create mode 100644 core/util/str.cpp create mode 100644 core/util/str.h create mode 100644 core/util/swizzle.h create mode 100644 core/util/swizzleSpec.h create mode 100644 core/util/tAlignedArray.h create mode 100644 core/util/tDictionary.cpp create mode 100644 core/util/tDictionary.h create mode 100644 core/util/tFixedSizeDeque.h create mode 100644 core/util/tList.h create mode 100644 core/util/tSignal.cpp create mode 100644 core/util/tSignal.h create mode 100644 core/util/tSingleton.h create mode 100644 core/util/tVector.cpp create mode 100644 core/util/tVector.h create mode 100644 core/util/tVectorSpecializations.h create mode 100644 core/util/test/testFixedSizeDeque.cpp create mode 100644 core/util/test/testPath.cpp create mode 100644 core/util/test/testString.cpp create mode 100644 core/util/timeClass.cpp create mode 100644 core/util/timeClass.h create mode 100644 core/util/timeSource.h create mode 100644 core/util/zip/centralDir.cpp create mode 100644 core/util/zip/centralDir.h create mode 100644 core/util/zip/compressor.cpp create mode 100644 core/util/zip/compressor.h create mode 100644 core/util/zip/compressors/deflate.cpp create mode 100644 core/util/zip/compressors/stored.cpp create mode 100644 core/util/zip/crctab.h create mode 100644 core/util/zip/extraField.cpp create mode 100644 core/util/zip/extraField.h create mode 100644 core/util/zip/fileHeader.cpp create mode 100644 core/util/zip/fileHeader.h create mode 100644 core/util/zip/unitTests/zipTest.h create mode 100644 core/util/zip/unitTests/zipTestMisc.cpp create mode 100644 core/util/zip/unitTests/zipTestRead.cpp create mode 100644 core/util/zip/unitTests/zipTestWrite.cpp create mode 100644 core/util/zip/zipArchive.cpp create mode 100644 core/util/zip/zipArchive.h create mode 100644 core/util/zip/zipCryptStream.cpp create mode 100644 core/util/zip/zipCryptStream.h create mode 100644 core/util/zip/zipObject.cpp create mode 100644 core/util/zip/zipObject.h create mode 100644 core/util/zip/zipStatFilter.h create mode 100644 core/util/zip/zipSubStream.cpp create mode 100644 core/util/zip/zipSubStream.h create mode 100644 core/util/zip/zipTempStream.cpp create mode 100644 core/util/zip/zipTempStream.h create mode 100644 core/util/zip/zipVolume.cpp create mode 100644 core/util/zip/zipVolume.h create mode 100644 core/virtualMountSystem.cpp create mode 100644 core/virtualMountSystem.h create mode 100644 core/volume.cpp create mode 100644 core/volume.h create mode 100644 environment/basicClouds.cpp create mode 100644 environment/basicClouds.h create mode 100644 environment/cloudLayer.cpp create mode 100644 environment/cloudLayer.h create mode 100644 environment/decalRoad.cpp create mode 100644 environment/decalRoad.h create mode 100644 environment/editors/guiMeshRoadEditorCtrl.cpp create mode 100644 environment/editors/guiMeshRoadEditorCtrl.h create mode 100644 environment/editors/guiRiverEditorCtrl.cpp create mode 100644 environment/editors/guiRiverEditorCtrl.h create mode 100644 environment/editors/guiRoadEditorCtrl.cpp create mode 100644 environment/editors/guiRoadEditorCtrl.h create mode 100644 environment/meshRoad.cpp create mode 100644 environment/meshRoad.h create mode 100644 environment/river.cpp create mode 100644 environment/river.h create mode 100644 environment/scatterSky.cpp create mode 100644 environment/scatterSky.h create mode 100644 environment/sky.cpp create mode 100644 environment/sky.h create mode 100644 environment/skyBox.cpp create mode 100644 environment/skyBox.h create mode 100644 environment/sun.cpp create mode 100644 environment/sun.h create mode 100644 environment/timeOfDay.cpp create mode 100644 environment/timeOfDay.h create mode 100644 environment/waterBlock.cpp create mode 100644 environment/waterBlock.h create mode 100644 environment/waterObject.cpp create mode 100644 environment/waterObject.h create mode 100644 environment/waterPlane.cpp create mode 100644 environment/waterPlane.h create mode 100644 gfx/D3D/screenshotD3D.cpp create mode 100644 gfx/D3D/screenshotD3D.h create mode 100644 gfx/D3D9/d3dx9Functions.h create mode 100644 gfx/D3D9/gfxD3D9CardProfiler.cpp create mode 100644 gfx/D3D9/gfxD3D9CardProfiler.h create mode 100644 gfx/D3D9/gfxD3D9Cubemap.cpp create mode 100644 gfx/D3D9/gfxD3D9Cubemap.h create mode 100644 gfx/D3D9/gfxD3D9Device.cpp create mode 100644 gfx/D3D9/gfxD3D9Device.h create mode 100644 gfx/D3D9/gfxD3D9Device.regen-states.cpp create mode 100644 gfx/D3D9/gfxD3D9EnumTranslate.h create mode 100644 gfx/D3D9/gfxD3D9OcclusionQuery.cpp create mode 100644 gfx/D3D9/gfxD3D9OcclusionQuery.h create mode 100644 gfx/D3D9/gfxD3D9PrimitiveBuffer.cpp create mode 100644 gfx/D3D9/gfxD3D9PrimitiveBuffer.h create mode 100644 gfx/D3D9/gfxD3D9QueryFence.cpp create mode 100644 gfx/D3D9/gfxD3D9QueryFence.h create mode 100644 gfx/D3D9/gfxD3D9Shader.cpp create mode 100644 gfx/D3D9/gfxD3D9Shader.h create mode 100644 gfx/D3D9/gfxD3D9StateBlock.cpp create mode 100644 gfx/D3D9/gfxD3D9StateBlock.h create mode 100644 gfx/D3D9/gfxD3D9TextureManager.cpp create mode 100644 gfx/D3D9/gfxD3D9TextureManager.h create mode 100644 gfx/D3D9/gfxD3D9TextureObject.cpp create mode 100644 gfx/D3D9/gfxD3D9TextureObject.h create mode 100644 gfx/D3D9/gfxD3D9VertexBuffer.cpp create mode 100644 gfx/D3D9/gfxD3D9VertexBuffer.h create mode 100644 gfx/D3D9/pc/gfxD3D9Device.pc.cpp create mode 100644 gfx/D3D9/pc/gfxD3D9EnumTranslate.pc.cpp create mode 100644 gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp create mode 100644 gfx/D3D9/pc/gfxPCD3D9Device.cpp create mode 100644 gfx/D3D9/pc/gfxPCD3D9Device.h create mode 100644 gfx/D3D9/pc/gfxPCD3D9Target.cpp create mode 100644 gfx/D3D9/pc/gfxPCD3D9Target.h create mode 100644 gfx/D3D9/platformD3D.h create mode 100644 gfx/Null/gfxNullDevice.cpp create mode 100644 gfx/Null/gfxNullDevice.h create mode 100644 gfx/bitmap/bitmapUtils.cpp create mode 100644 gfx/bitmap/bitmapUtils.h create mode 100644 gfx/bitmap/ddsFile.h create mode 100644 gfx/bitmap/ddsLoader.cpp create mode 100644 gfx/bitmap/ddsUtils.cpp create mode 100644 gfx/bitmap/ddsUtils.h create mode 100644 gfx/bitmap/gBitmap.cpp create mode 100644 gfx/bitmap/gBitmap.h create mode 100644 gfx/bitmap/loaders/bitmapBmp.cpp create mode 100644 gfx/bitmap/loaders/bitmapGif.cpp create mode 100644 gfx/bitmap/loaders/bitmapJpeg.cpp create mode 100644 gfx/bitmap/loaders/bitmapMng.cpp create mode 100644 gfx/bitmap/loaders/bitmapPng.cpp create mode 100644 gfx/bitmap/loaders/bitmapTga.cpp create mode 100644 gfx/gFont.cpp create mode 100644 gfx/gFont.h create mode 100644 gfx/genericConstBuffer.cpp create mode 100644 gfx/genericConstBuffer.h create mode 100644 gfx/gfxAdapter.h create mode 100644 gfx/gfxCardProfile.cpp create mode 100644 gfx/gfxCardProfile.h create mode 100644 gfx/gfxCubemap.cpp create mode 100644 gfx/gfxCubemap.h create mode 100644 gfx/gfxDebugEvent.h create mode 100644 gfx/gfxDevice.cpp create mode 100644 gfx/gfxDevice.h create mode 100644 gfx/gfxDeviceStatistics.cpp create mode 100644 gfx/gfxDeviceStatistics.h create mode 100644 gfx/gfxDrawUtil.cpp create mode 100644 gfx/gfxDrawUtil.h create mode 100644 gfx/gfxEnums.h create mode 100644 gfx/gfxFence.cpp create mode 100644 gfx/gfxFence.h create mode 100644 gfx/gfxFontRenderBatcher.cpp create mode 100644 gfx/gfxFontRenderBatcher.h create mode 100644 gfx/gfxFormatUtils.cpp create mode 100644 gfx/gfxFormatUtils.h create mode 100644 gfx/gfxInit.cpp create mode 100644 gfx/gfxInit.h create mode 100644 gfx/gfxOcclusionQuery.cpp create mode 100644 gfx/gfxOcclusionQuery.h create mode 100644 gfx/gfxPrimitiveBuffer.cpp create mode 100644 gfx/gfxPrimitiveBuffer.h create mode 100644 gfx/gfxResource.cpp create mode 100644 gfx/gfxResource.h create mode 100644 gfx/gfxShader.cpp create mode 100644 gfx/gfxShader.h create mode 100644 gfx/gfxStateBlock.cpp create mode 100644 gfx/gfxStateBlock.h create mode 100644 gfx/gfxStringEnumTranslate.cpp create mode 100644 gfx/gfxStringEnumTranslate.h create mode 100644 gfx/gfxStructs.cpp create mode 100644 gfx/gfxStructs.h create mode 100644 gfx/gfxTarget.cpp create mode 100644 gfx/gfxTarget.h create mode 100644 gfx/gfxTextureHandle.cpp create mode 100644 gfx/gfxTextureHandle.h create mode 100644 gfx/gfxTextureManager.cpp create mode 100644 gfx/gfxTextureManager.h create mode 100644 gfx/gfxTextureObject.cpp create mode 100644 gfx/gfxTextureObject.h create mode 100644 gfx/gfxTextureProfile.cpp create mode 100644 gfx/gfxTextureProfile.h create mode 100644 gfx/gfxTransformSaver.h create mode 100644 gfx/gfxVertexBuffer.cpp create mode 100644 gfx/gfxVertexBuffer.h create mode 100644 gfx/gfxVertexColor.cpp create mode 100644 gfx/gfxVertexColor.h create mode 100644 gfx/gfxVertexFormat.cpp create mode 100644 gfx/gfxVertexFormat.h create mode 100644 gfx/gfxVertexTypes.cpp create mode 100644 gfx/gfxVertexTypes.h create mode 100644 gfx/gl/gfxGLAppleFence.cpp create mode 100644 gfx/gl/gfxGLAppleFence.h create mode 100644 gfx/gl/gfxGLCardProfiler.cpp create mode 100644 gfx/gl/gfxGLCardProfiler.h create mode 100644 gfx/gl/gfxGLCubemap.cpp create mode 100644 gfx/gl/gfxGLCubemap.h create mode 100644 gfx/gl/gfxGLDevice.cpp create mode 100644 gfx/gl/gfxGLDevice.h create mode 100644 gfx/gl/gfxGLDevice.mac.mm create mode 100644 gfx/gl/gfxGLDevice.win.cpp create mode 100644 gfx/gl/gfxGLEnumTranslate.cpp create mode 100644 gfx/gl/gfxGLEnumTranslate.h create mode 100644 gfx/gl/gfxGLOcclusionQuery.cpp create mode 100644 gfx/gl/gfxGLOcclusionQuery.h create mode 100644 gfx/gl/gfxGLPrimitiveBuffer.cpp create mode 100644 gfx/gl/gfxGLPrimitiveBuffer.h create mode 100644 gfx/gl/gfxGLShader.cpp create mode 100644 gfx/gl/gfxGLShader.h create mode 100644 gfx/gl/gfxGLStateBlock.cpp create mode 100644 gfx/gl/gfxGLStateBlock.h create mode 100644 gfx/gl/gfxGLTextureManager.cpp create mode 100644 gfx/gl/gfxGLTextureManager.h create mode 100644 gfx/gl/gfxGLTextureObject.cpp create mode 100644 gfx/gl/gfxGLTextureObject.h create mode 100644 gfx/gl/gfxGLTextureTarget.cpp create mode 100644 gfx/gl/gfxGLTextureTarget.h create mode 100644 gfx/gl/gfxGLUtils.h create mode 100644 gfx/gl/gfxGLVertexBuffer.cpp create mode 100644 gfx/gl/gfxGLVertexBuffer.h create mode 100644 gfx/gl/gfxGLWindowTarget.cpp create mode 100644 gfx/gl/gfxGLWindowTarget.h create mode 100644 gfx/gl/ggl/generated/glc.h create mode 100644 gfx/gl/ggl/generated/glcfn.h create mode 100644 gfx/gl/ggl/generated/gle.h create mode 100644 gfx/gl/ggl/generated/glefn.h create mode 100644 gfx/gl/ggl/generated/glxe.h create mode 100644 gfx/gl/ggl/generated/glxefn.h create mode 100644 gfx/gl/ggl/generated/glxfn.h create mode 100644 gfx/gl/ggl/generated/wgle.h create mode 100644 gfx/gl/ggl/generated/wglefn.h create mode 100644 gfx/gl/ggl/generated/wglfn.h create mode 100644 gfx/gl/ggl/generator/parse.cpp create mode 100644 gfx/gl/ggl/ggl.cpp create mode 100644 gfx/gl/ggl/ggl.h create mode 100644 gfx/gl/ggl/gglConfig.h create mode 100644 gfx/gl/ggl/mac/agl.h create mode 100644 gfx/gl/ggl/mac/aglBind.cpp create mode 100644 gfx/gl/ggl/win32/wgl.h create mode 100644 gfx/gl/ggl/win32/wglBind.cpp create mode 100644 gfx/gl/ggl/x11/glx.cpp create mode 100644 gfx/gl/ggl/x11/glx.h create mode 100644 gfx/gl/ggl/x11/glxBind.cpp create mode 100644 gfx/primBuilder.cpp create mode 100644 gfx/primBuilder.h create mode 100644 gfx/screenshot.cpp create mode 100644 gfx/screenshot.h create mode 100644 gfx/sim/cubemapData.cpp create mode 100644 gfx/sim/cubemapData.h create mode 100644 gfx/sim/debugDraw.cpp create mode 100644 gfx/sim/debugDraw.h create mode 100644 gfx/sim/gfxStateBlockData.cpp create mode 100644 gfx/sim/gfxStateBlockData.h create mode 100644 gfx/test/stanfordBunny.cpp create mode 100644 gfx/test/testGfx.cpp create mode 100644 gfx/util/distanceField.cpp create mode 100644 gfx/util/distanceField.h create mode 100644 gfx/util/gfxFrustumSaver.cpp create mode 100644 gfx/util/gfxFrustumSaver.h create mode 100644 gfx/util/screenspace.cpp create mode 100644 gfx/util/screenspace.h create mode 100644 gfx/util/triListOpt.cpp create mode 100644 gfx/util/triListOpt.h create mode 100644 gfx/video/theoraTexture.cpp create mode 100644 gfx/video/theoraTexture.h create mode 100644 ggEndOfLineFix.txt create mode 100644 gui/3d/guiTSControl.cpp create mode 100644 gui/3d/guiTSControl.h create mode 100644 gui/buttons/guiBitmapButtonCtrl.cpp create mode 100644 gui/buttons/guiBitmapButtonCtrl.h create mode 100644 gui/buttons/guiBorderButton.cpp create mode 100644 gui/buttons/guiButtonBaseCtrl.cpp create mode 100644 gui/buttons/guiButtonBaseCtrl.h create mode 100644 gui/buttons/guiButtonCtrl.cpp create mode 100644 gui/buttons/guiButtonCtrl.h create mode 100644 gui/buttons/guiCheckBoxCtrl.cpp create mode 100644 gui/buttons/guiCheckBoxCtrl.h create mode 100644 gui/buttons/guiIconButtonCtrl.cpp create mode 100644 gui/buttons/guiIconButtonCtrl.h create mode 100644 gui/buttons/guiRadioCtrl.cpp create mode 100644 gui/buttons/guiRadioCtrl.h create mode 100644 gui/buttons/guiSwatchButtonCtrl.cpp create mode 100644 gui/buttons/guiSwatchButtonCtrl.h create mode 100644 gui/buttons/guiToggleButtonCtrl.cpp create mode 100644 gui/buttons/guiToggleButtonCtrl.h create mode 100644 gui/buttons/guiToolboxButtonCtrl.cpp create mode 100644 gui/buttons/guiToolboxButtonCtrl.h create mode 100644 gui/containers/guiAutoScrollCtrl.cpp create mode 100644 gui/containers/guiAutoScrollCtrl.h create mode 100644 gui/containers/guiContainer.cpp create mode 100644 gui/containers/guiContainer.h create mode 100644 gui/containers/guiCtrlArrayCtrl.cpp create mode 100644 gui/containers/guiCtrlArrayCtrl.h create mode 100644 gui/containers/guiDragAndDropCtrl.cpp create mode 100644 gui/containers/guiDragAndDropCtrl.h create mode 100644 gui/containers/guiDynamicCtrlArrayCtrl.cpp create mode 100644 gui/containers/guiDynamicCtrlArrayCtrl.h create mode 100644 gui/containers/guiFormCtrl.cpp create mode 100644 gui/containers/guiFormCtrl.h create mode 100644 gui/containers/guiFrameCtrl.cpp create mode 100644 gui/containers/guiFrameCtrl.h create mode 100644 gui/containers/guiPaneCtrl.cpp create mode 100644 gui/containers/guiPaneCtrl.h create mode 100644 gui/containers/guiPanel.cpp create mode 100644 gui/containers/guiPanel.h create mode 100644 gui/containers/guiRolloutCtrl.cpp create mode 100644 gui/containers/guiRolloutCtrl.h create mode 100644 gui/containers/guiScrollCtrl.cpp create mode 100644 gui/containers/guiScrollCtrl.h create mode 100644 gui/containers/guiSplitContainer.cpp create mode 100644 gui/containers/guiSplitContainer.h create mode 100644 gui/containers/guiStackCtrl.cpp create mode 100644 gui/containers/guiStackCtrl.h create mode 100644 gui/containers/guiTabBookCtrl.cpp create mode 100644 gui/containers/guiTabBookCtrl.h create mode 100644 gui/containers/guiWindowCollapseCtrl.cpp create mode 100644 gui/containers/guiWindowCollapseCtrl.h create mode 100644 gui/containers/guiWindowCtrl.cpp create mode 100644 gui/containers/guiWindowCtrl.h create mode 100644 gui/controls/guiBackgroundCtrl.cpp create mode 100644 gui/controls/guiBackgroundCtrl.h create mode 100644 gui/controls/guiBitmapBorderCtrl.cpp create mode 100644 gui/controls/guiBitmapCtrl.cpp create mode 100644 gui/controls/guiBitmapCtrl.h create mode 100644 gui/controls/guiColorPicker.cpp create mode 100644 gui/controls/guiColorPicker.h create mode 100644 gui/controls/guiConsole.cpp create mode 100644 gui/controls/guiConsole.h create mode 100644 gui/controls/guiConsoleEditCtrl.cpp create mode 100644 gui/controls/guiConsoleEditCtrl.h create mode 100644 gui/controls/guiConsoleTextCtrl.cpp create mode 100644 gui/controls/guiConsoleTextCtrl.h create mode 100644 gui/controls/guiDecoyCtrl.cpp create mode 100644 gui/controls/guiDecoyCtrl.h create mode 100644 gui/controls/guiDirectoryFileListCtrl.cpp create mode 100644 gui/controls/guiDirectoryFileListCtrl.h create mode 100644 gui/controls/guiFileTreeCtrl.cpp create mode 100644 gui/controls/guiFileTreeCtrl.h create mode 100644 gui/controls/guiGameListMenuCtrl.cpp create mode 100644 gui/controls/guiGameListMenuCtrl.h create mode 100644 gui/controls/guiGameListOptionsCtrl.cpp create mode 100644 gui/controls/guiGameListOptionsCtrl.h create mode 100644 gui/controls/guiListBoxCtrl.cpp create mode 100644 gui/controls/guiListBoxCtrl.h create mode 100644 gui/controls/guiMLTextCtrl.cpp create mode 100644 gui/controls/guiMLTextCtrl.h create mode 100644 gui/controls/guiMLTextEditCtrl.cpp create mode 100644 gui/controls/guiMLTextEditCtrl.h create mode 100644 gui/controls/guiMaterialCtrl.cpp create mode 100644 gui/controls/guiMaterialCtrl.h create mode 100644 gui/controls/guiPopUpCtrl.cpp create mode 100644 gui/controls/guiPopUpCtrl.h create mode 100644 gui/controls/guiPopUpCtrlEx.cpp create mode 100644 gui/controls/guiPopUpCtrlEx.h create mode 100644 gui/controls/guiSliderCtrl.cpp create mode 100644 gui/controls/guiSliderCtrl.h create mode 100644 gui/controls/guiTabPageCtrl.cpp create mode 100644 gui/controls/guiTabPageCtrl.h create mode 100644 gui/controls/guiTextCtrl.cpp create mode 100644 gui/controls/guiTextCtrl.h create mode 100644 gui/controls/guiTextEditCtrl.cpp create mode 100644 gui/controls/guiTextEditCtrl.h create mode 100644 gui/controls/guiTextEditSliderBitmapCtrl.cpp create mode 100644 gui/controls/guiTextEditSliderBitmapCtrl.h create mode 100644 gui/controls/guiTextEditSliderCtrl.cpp create mode 100644 gui/controls/guiTextEditSliderCtrl.h create mode 100644 gui/controls/guiTextListCtrl.cpp create mode 100644 gui/controls/guiTextListCtrl.h create mode 100644 gui/controls/guiTreeViewCtrl.cpp create mode 100644 gui/controls/guiTreeViewCtrl.h create mode 100644 gui/core/guiArrayCtrl.cpp create mode 100644 gui/core/guiArrayCtrl.h create mode 100644 gui/core/guiCanvas.cpp create mode 100644 gui/core/guiCanvas.h create mode 100644 gui/core/guiControl.cpp create mode 100644 gui/core/guiControl.h create mode 100644 gui/core/guiDefaultControlRender.cpp create mode 100644 gui/core/guiDefaultControlRender.h create mode 100644 gui/core/guiScriptNotifyControl.cpp create mode 100644 gui/core/guiScriptNotifyControl.h create mode 100644 gui/core/guiTypes.cpp create mode 100644 gui/core/guiTypes.h create mode 100644 gui/editor/guiControlListPopup.cpp create mode 100644 gui/editor/guiDebugger.cpp create mode 100644 gui/editor/guiDebugger.h create mode 100644 gui/editor/guiEditCtrl.cpp create mode 100644 gui/editor/guiEditCtrl.h create mode 100644 gui/editor/guiFilterCtrl.cpp create mode 100644 gui/editor/guiFilterCtrl.h create mode 100644 gui/editor/guiGraphCtrl.cpp create mode 100644 gui/editor/guiGraphCtrl.h create mode 100644 gui/editor/guiImageList.cpp create mode 100644 gui/editor/guiImageList.h create mode 100644 gui/editor/guiInspector.cpp create mode 100644 gui/editor/guiInspector.h create mode 100644 gui/editor/guiInspectorTypes.cpp create mode 100644 gui/editor/guiInspectorTypes.h create mode 100644 gui/editor/guiMenuBar.cpp create mode 100644 gui/editor/guiMenuBar.h create mode 100644 gui/editor/guiParticleGraphCtrl.cc create mode 100644 gui/editor/guiParticleGraphCtrl.h create mode 100644 gui/editor/guiSeparatorCtrl.cpp create mode 100644 gui/editor/guiSeparatorCtrl.h create mode 100644 gui/editor/guiShapeEdPreview.cpp create mode 100644 gui/editor/guiShapeEdPreview.h create mode 100644 gui/editor/inspector/customField.cpp create mode 100644 gui/editor/inspector/customField.h create mode 100644 gui/editor/inspector/datablockField.cpp create mode 100644 gui/editor/inspector/datablockField.h create mode 100644 gui/editor/inspector/dynamicField.cpp create mode 100644 gui/editor/inspector/dynamicField.h create mode 100644 gui/editor/inspector/dynamicGroup.cpp create mode 100644 gui/editor/inspector/dynamicGroup.h create mode 100644 gui/editor/inspector/field.cpp create mode 100644 gui/editor/inspector/field.h create mode 100644 gui/editor/inspector/group.cpp create mode 100644 gui/editor/inspector/group.h create mode 100644 gui/editor/inspector/variableField.cpp create mode 100644 gui/editor/inspector/variableField.h create mode 100644 gui/editor/inspector/variableGroup.cpp create mode 100644 gui/editor/inspector/variableGroup.h create mode 100644 gui/editor/inspector/variableInspector.cpp create mode 100644 gui/editor/inspector/variableInspector.h create mode 100644 gui/game/guiAviBitmapCtrl.cpp create mode 100644 gui/game/guiAviBitmapCtrl.h create mode 100644 gui/game/guiChunkedBitmapCtrl.cpp create mode 100644 gui/game/guiFadeinBitmapCtrl.cpp create mode 100644 gui/game/guiIdleCamFadeBitmapCtrl.cpp create mode 100644 gui/game/guiMessageVectorCtrl.cpp create mode 100644 gui/game/guiMessageVectorCtrl.h create mode 100644 gui/game/guiProgressBitmapCtrl.cpp create mode 100644 gui/game/guiProgressBitmapCtrl.h create mode 100644 gui/game/guiProgressCtrl.cpp create mode 100644 gui/game/guiProgressCtrl.h create mode 100644 gui/shiny/guiTheoraCtrl.cpp create mode 100644 gui/shiny/guiTheoraCtrl.h create mode 100644 gui/shiny/guiTickCtrl.cpp create mode 100644 gui/shiny/guiTickCtrl.h create mode 100644 gui/utility/guiBubbleTextCtrl.cpp create mode 100644 gui/utility/guiBubbleTextCtrl.h create mode 100644 gui/utility/guiInputCtrl.cpp create mode 100644 gui/utility/guiInputCtrl.h create mode 100644 gui/utility/guiMouseEventCtrl.cpp create mode 100644 gui/utility/guiMouseEventCtrl.h create mode 100644 gui/utility/messageVector.cpp create mode 100644 gui/utility/messageVector.h create mode 100644 gui/worldEditor/creator.cpp create mode 100644 gui/worldEditor/creator.h create mode 100644 gui/worldEditor/editTSCtrl.cpp create mode 100644 gui/worldEditor/editTSCtrl.h create mode 100644 gui/worldEditor/editor.cpp create mode 100644 gui/worldEditor/editor.h create mode 100644 gui/worldEditor/editorIconRegistry.cpp create mode 100644 gui/worldEditor/editorIconRegistry.h create mode 100644 gui/worldEditor/gizmo.cpp create mode 100644 gui/worldEditor/gizmo.h create mode 100644 gui/worldEditor/guiDecalEditorCtrl.cpp create mode 100644 gui/worldEditor/guiDecalEditorCtrl.h create mode 100644 gui/worldEditor/guiTerrPreviewCtrl.cpp create mode 100644 gui/worldEditor/guiTerrPreviewCtrl.h create mode 100644 gui/worldEditor/missionAreaEditor.cpp create mode 100644 gui/worldEditor/missionAreaEditor.h create mode 100644 gui/worldEditor/terrainActions.cpp create mode 100644 gui/worldEditor/terrainActions.h create mode 100644 gui/worldEditor/terrainEditor.cpp create mode 100644 gui/worldEditor/terrainEditor.h create mode 100644 gui/worldEditor/undoActions.cpp create mode 100644 gui/worldEditor/undoActions.h create mode 100644 gui/worldEditor/worldEditor.cpp create mode 100644 gui/worldEditor/worldEditor.h create mode 100644 i18n/i18n.cpp create mode 100644 i18n/i18n.h create mode 100644 i18n/lang.cpp create mode 100644 i18n/lang.h create mode 100644 interior/forceField.cpp create mode 100644 interior/forceField.h create mode 100644 interior/interior.cpp create mode 100644 interior/interior.h create mode 100644 interior/interiorCollision.cpp create mode 100644 interior/interiorDebug.cpp create mode 100644 interior/interiorIO.cpp create mode 100644 interior/interiorInstance.cpp create mode 100644 interior/interiorInstance.h create mode 100644 interior/interiorLMManager.cpp create mode 100644 interior/interiorLMManager.h create mode 100644 interior/interiorRender.cpp create mode 100644 interior/interiorRes.cpp create mode 100644 interior/interiorRes.h create mode 100644 interior/interiorResObjects.cpp create mode 100644 interior/interiorResObjects.h create mode 100644 interior/interiorSimpleMesh.cpp create mode 100644 interior/interiorSimpleMesh.h create mode 100644 interior/interiorSubObject.cpp create mode 100644 interior/interiorSubObject.h create mode 100644 interior/mirrorSubObject.cpp create mode 100644 interior/mirrorSubObject.h create mode 100644 interior/pathedInterior.cpp create mode 100644 interior/pathedInterior.h create mode 100644 lighting/advanced/advancedLightBinManager.cpp create mode 100644 lighting/advanced/advancedLightBinManager.h create mode 100644 lighting/advanced/advancedLightBufferConditioner.cpp create mode 100644 lighting/advanced/advancedLightBufferConditioner.h create mode 100644 lighting/advanced/advancedLightManager.cpp create mode 100644 lighting/advanced/advancedLightManager.h create mode 100644 lighting/advanced/advancedLightingFeatures.cpp create mode 100644 lighting/advanced/advancedLightingFeatures.h create mode 100644 lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp create mode 100644 lighting/advanced/glsl/advancedLightingFeaturesGLSL.h create mode 100644 lighting/advanced/glsl/gBufferConditionerGLSL.cpp create mode 100644 lighting/advanced/glsl/gBufferConditionerGLSL.h create mode 100644 lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp create mode 100644 lighting/advanced/hlsl/advancedLightingFeaturesHLSL.h create mode 100644 lighting/advanced/hlsl/gBufferConditionerHLSL.cpp create mode 100644 lighting/advanced/hlsl/gBufferConditionerHLSL.h create mode 100644 lighting/basic/basicLightManager.cpp create mode 100644 lighting/basic/basicLightManager.h create mode 100644 lighting/basic/basicSceneObjectLightingPlugin.cpp create mode 100644 lighting/basic/basicSceneObjectLightingPlugin.h create mode 100644 lighting/basic/blInteriorSystem.cpp create mode 100644 lighting/basic/blTerrainSystem.cpp create mode 100644 lighting/common/blobShadow.cpp create mode 100644 lighting/common/blobShadow.h create mode 100644 lighting/common/lightMapParams.cpp create mode 100644 lighting/common/lightMapParams.h create mode 100644 lighting/common/projectedShadow.cpp create mode 100644 lighting/common/projectedShadow.h create mode 100644 lighting/common/sceneLighting.cpp create mode 100644 lighting/common/sceneLighting.h create mode 100644 lighting/common/sceneLightingGlobals.h create mode 100644 lighting/common/scenePersist.cpp create mode 100644 lighting/common/scenePersist.h create mode 100644 lighting/common/shadowBase.h create mode 100644 lighting/common/shadowVolumeBSP.cpp create mode 100644 lighting/common/shadowVolumeBSP.h create mode 100644 lighting/lightInfo.cpp create mode 100644 lighting/lightInfo.h create mode 100644 lighting/lightManager.cpp create mode 100644 lighting/lightManager.h create mode 100644 lighting/lightReceiver.cpp create mode 100644 lighting/lightReceiver.h create mode 100644 lighting/lightingInterfaces.cpp create mode 100644 lighting/lightingInterfaces.h create mode 100644 lighting/shadowManager.cpp create mode 100644 lighting/shadowManager.h create mode 100644 lighting/shadowMap/blurTexture.cpp create mode 100644 lighting/shadowMap/blurTexture.h create mode 100644 lighting/shadowMap/cubeLightShadowMap.cpp create mode 100644 lighting/shadowMap/cubeLightShadowMap.h create mode 100644 lighting/shadowMap/dualParaboloidLightShadowMap.cpp create mode 100644 lighting/shadowMap/dualParaboloidLightShadowMap.h create mode 100644 lighting/shadowMap/lightShadowMap.cpp create mode 100644 lighting/shadowMap/lightShadowMap.h create mode 100644 lighting/shadowMap/paraboloidLightShadowMap.cpp create mode 100644 lighting/shadowMap/paraboloidLightShadowMap.h create mode 100644 lighting/shadowMap/pssmLightShadowMap.cpp create mode 100644 lighting/shadowMap/pssmLightShadowMap.h create mode 100644 lighting/shadowMap/shadowCommon.h create mode 100644 lighting/shadowMap/shadowMapManager.cpp create mode 100644 lighting/shadowMap/shadowMapManager.h create mode 100644 lighting/shadowMap/shadowMapPass.cpp create mode 100644 lighting/shadowMap/shadowMapPass.h create mode 100644 lighting/shadowMap/shadowMatHook.cpp create mode 100644 lighting/shadowMap/shadowMatHook.h create mode 100644 lighting/shadowMap/singleLightShadowMap.cpp create mode 100644 lighting/shadowMap/singleLightShadowMap.h create mode 100644 main/main.cpp create mode 100644 materials/baseMatInstance.cpp create mode 100644 materials/baseMatInstance.h create mode 100644 materials/baseMaterialDefinition.h create mode 100644 materials/customMaterialDefinition.cpp create mode 100644 materials/customMaterialDefinition.h create mode 100644 materials/matInstance.cpp create mode 100644 materials/matInstance.h create mode 100644 materials/matInstanceHook.cpp create mode 100644 materials/matInstanceHook.h create mode 100644 materials/matTextureTarget.cpp create mode 100644 materials/matTextureTarget.h create mode 100644 materials/materialDefinition.cpp create mode 100644 materials/materialDefinition.h create mode 100644 materials/materialFeatureData.cpp create mode 100644 materials/materialFeatureData.h create mode 100644 materials/materialFeatureTypes.cpp create mode 100644 materials/materialFeatureTypes.h create mode 100644 materials/materialList.cpp create mode 100644 materials/materialList.h create mode 100644 materials/materialManager.cpp create mode 100644 materials/materialManager.h create mode 100644 materials/materialParameters.h create mode 100644 materials/miscShdrDat.h create mode 100644 materials/processedCustomMaterial.cpp create mode 100644 materials/processedCustomMaterial.h create mode 100644 materials/processedFFMaterial.cpp create mode 100644 materials/processedFFMaterial.h create mode 100644 materials/processedMaterial.cpp create mode 100644 materials/processedMaterial.h create mode 100644 materials/processedShaderMaterial.cpp create mode 100644 materials/processedShaderMaterial.h create mode 100644 materials/sceneData.h create mode 100644 materials/shaderData.cpp create mode 100644 materials/shaderData.h create mode 100644 materials/shaderMaterialParameters.cpp create mode 100644 materials/shaderMaterialParameters.h create mode 100644 math/mAngAxis.cpp create mode 100644 math/mAngAxis.h create mode 100644 math/mBox.cpp create mode 100644 math/mBox.h create mode 100644 math/mConsoleFunctions.cpp create mode 100644 math/mConstants.h create mode 100644 math/mMath.h create mode 100644 math/mMathAMD.cpp create mode 100644 math/mMathAMD_ASM.asm create mode 100644 math/mMathAltivec.cpp create mode 100644 math/mMathFn.h create mode 100644 math/mMathSSE.cpp create mode 100644 math/mMathSSE_ASM.asm create mode 100644 math/mMath_ASM.asm create mode 100644 math/mMath_C.cpp create mode 100644 math/mMatrix.cpp create mode 100644 math/mMatrix.h create mode 100644 math/mPlane.cpp create mode 100644 math/mPlane.h create mode 100644 math/mPlaneTransformer.cpp create mode 100644 math/mPlaneTransformer.h create mode 100644 math/mPoint.cpp create mode 100644 math/mPoint2.h create mode 100644 math/mPoint3.h create mode 100644 math/mPoint4.h create mode 100644 math/mQuadPatch.cpp create mode 100644 math/mQuadPatch.h create mode 100644 math/mQuat.cpp create mode 100644 math/mQuat.h create mode 100644 math/mRandom.cpp create mode 100644 math/mRandom.h create mode 100644 math/mRect.cpp create mode 100644 math/mRect.h create mode 100644 math/mSolver.cpp create mode 100644 math/mSphere.h create mode 100644 math/mSplinePatch.cpp create mode 100644 math/mSplinePatch.h create mode 100644 math/mTrig.h create mode 100644 math/mathIO.h create mode 100644 math/mathTypes.cpp create mode 100644 math/mathTypes.h create mode 100644 math/mathUtils.cpp create mode 100644 math/mathUtils.h create mode 100644 math/util/frustum.cpp create mode 100644 math/util/frustum.h create mode 100644 math/util/matrixSet.cpp create mode 100644 math/util/matrixSet.h create mode 100644 math/util/matrixSetDelegateMethods.h create mode 100644 math/util/quadTransforms.cpp create mode 100644 math/util/quadTransforms.h create mode 100644 math/util/sphereMesh.cpp create mode 100644 math/util/sphereMesh.h create mode 100644 math/util/tResponseCurve.cpp create mode 100644 math/util/tResponseCurve.h create mode 100644 platform/async/asyncBufferedStream.h create mode 100644 platform/async/asyncPacketQueue.h create mode 100644 platform/async/asyncPacketStream.h create mode 100644 platform/async/asyncUpdate.cpp create mode 100644 platform/async/asyncUpdate.h create mode 100644 platform/event.h create mode 100644 platform/menus/menuBar.cpp create mode 100644 platform/menus/menuBar.h create mode 100644 platform/menus/popupMenu.cpp create mode 100644 platform/menus/popupMenu.h create mode 100644 platform/nativeDialogs/fileDialog.h create mode 100644 platform/nativeDialogs/msgBox.cpp create mode 100644 platform/nativeDialogs/msgBox.h create mode 100644 platform/platform.cpp create mode 100644 platform/platform.h create mode 100644 platform/platformAssert.cpp create mode 100644 platform/platformAssert.h create mode 100644 platform/platformCPU.cpp create mode 100644 platform/platformCPUCount.cpp create mode 100644 platform/platformCPUCount.h create mode 100644 platform/platformCPUInfo.asm create mode 100644 platform/platformDlibrary.h create mode 100644 platform/platformFileIO.cpp create mode 100644 platform/platformFont.cpp create mode 100644 platform/platformFont.h create mode 100644 platform/platformInput.h create mode 100644 platform/platformIntrinsics.gcc.h create mode 100644 platform/platformIntrinsics.h create mode 100644 platform/platformIntrinsics.visualc.h create mode 100644 platform/platformMemory.cpp create mode 100644 platform/platformMemory.h create mode 100644 platform/platformNet.cpp create mode 100644 platform/platformNet.h create mode 100644 platform/platformNetAsync.cpp create mode 100644 platform/platformNetAsync.h create mode 100644 platform/platformRedBook.cpp create mode 100644 platform/platformRedBook.h create mode 100644 platform/platformTLS.h create mode 100644 platform/platformTimer.cpp create mode 100644 platform/platformTimer.h create mode 100644 platform/platformVFS.h create mode 100644 platform/platformVideoInfo.cpp create mode 100644 platform/platformVideoInfo.h create mode 100644 platform/platformVolume.cpp create mode 100644 platform/platformVolume.h create mode 100644 platform/profiler.cpp create mode 100644 platform/profiler.h create mode 100644 platform/test/testAlerts.cpp create mode 100644 platform/test/testAsyncPacketQueue.cpp create mode 100644 platform/test/testBasicTypes.cpp create mode 100644 platform/test/testFile.cpp create mode 100644 platform/test/testNet.cpp create mode 100644 platform/test/testThreadPool.cpp create mode 100644 platform/test/testThreadSafeDeque.cpp create mode 100644 platform/test/testThreadSafePriorityQueue.cpp create mode 100644 platform/test/testThreadSafeRefCount.cpp create mode 100644 platform/test/testThreading.cpp create mode 100644 platform/test/testTimeManager.cpp create mode 100644 platform/threads/mutex.h create mode 100644 platform/threads/semaphore.h create mode 100644 platform/threads/thread.h create mode 100644 platform/threads/threadPool.cpp create mode 100644 platform/threads/threadPool.h create mode 100644 platform/threads/threadPoolAsyncIO.h create mode 100644 platform/threads/threadSafeDeque.h create mode 100644 platform/threads/threadSafeFreeList.h create mode 100644 platform/threads/threadSafePriorityQueue.h create mode 100644 platform/threads/threadSafeRefCount.h create mode 100644 platform/tmm_off.h create mode 100644 platform/tmm_on.h create mode 100644 platform/types.codewarrior.h create mode 100644 platform/types.gcc.h create mode 100644 platform/types.h create mode 100644 platform/types.lint.h create mode 100644 platform/types.mac.h create mode 100644 platform/types.posix.h create mode 100644 platform/types.ppc.h create mode 100644 platform/types.visualc.h create mode 100644 platform/types.win32.h create mode 100644 platform/types.xenon.h create mode 100644 platform/typesLinux.h create mode 100644 platform/typesPPC.h create mode 100644 platform/typesWin32.h create mode 100644 platform/typesX86UNIX.h create mode 100644 platform/typetraits.h create mode 100644 platformMac/cursors/resizeNESW.png create mode 100644 platformMac/cursors/resizeNWSE.png create mode 100644 platformMac/cursors/resizeall.png create mode 100644 platformMac/macApplication.h create mode 100644 platformMac/macApplication.mm create mode 100644 platformMac/macCarbAsync.cpp create mode 100644 platformMac/macCarbCPUInfo.cpp create mode 100644 platformMac/macCarbFileio.mm create mode 100644 platformMac/macCarbFont.cpp create mode 100644 platformMac/macCarbFont.h create mode 100644 platformMac/macCarbInput.cpp create mode 100644 platformMac/macCarbMath.cpp create mode 100644 platformMac/macCarbMemory.cpp create mode 100644 platformMac/macCarbMutex.cpp create mode 100644 platformMac/macCarbProcessControl.cpp create mode 100644 platformMac/macCarbSemaphore.cpp create mode 100644 platformMac/macCarbStrings.cpp create mode 100644 platformMac/macCarbThread.cpp create mode 100644 platformMac/macCarbTime.cpp create mode 100644 platformMac/macCarbUtil.cpp create mode 100644 platformMac/macCarbVolume.cpp create mode 100644 platformMac/macCarbVolume.h create mode 100644 platformMac/macCocoaDialogs.mm create mode 100644 platformMac/macCocoaPlatform.mm create mode 100644 platformMac/macDLibrary.cpp create mode 100644 platformMac/macGLUtils.h create mode 100644 platformMac/macMain.mm create mode 100644 platformMac/macMsgBox.mm create mode 100644 platformMac/menus/mainMenu.nib/classes.nib create mode 100644 platformMac/menus/mainMenu.nib/info.nib create mode 100644 platformMac/menus/mainMenu.nib/keyedobjects.nib create mode 100644 platformMac/menus/menuBarMac.cpp create mode 100644 platformMac/menus/popupMenu.cpp create mode 100644 platformMac/platformMacCarb.h create mode 100644 platformMac/t2d.icns create mode 100644 platformMac/torqueDemo.icns create mode 100644 platformPOSIX/posixVolume.cpp create mode 100644 platformPOSIX/posixVolume.h create mode 100644 platformWin32/VFSRes.h create mode 100644 platformWin32/cardProfile.cpp create mode 100644 platformWin32/menus/menuBarWin32.cpp create mode 100644 platformWin32/menus/popupMenuWin32.cpp create mode 100644 platformWin32/nativeDialogs/fileDialog.cpp create mode 100644 platformWin32/nativeDialogs/win32MsgBox.cpp create mode 100644 platformWin32/platformWin32.h create mode 100644 platformWin32/threads/mutex.cpp create mode 100644 platformWin32/threads/thread.cpp create mode 100644 platformWin32/videoInfo/wmiVideoInfo.cpp create mode 100644 platformWin32/videoInfo/wmiVideoInfo.h create mode 100644 platformWin32/winAsmBlit.cpp create mode 100644 platformWin32/winAsync.cpp create mode 100644 platformWin32/winCPUInfo.cpp create mode 100644 platformWin32/winConsole.cpp create mode 100644 platformWin32/winConsole.h create mode 100644 platformWin32/winDInputDevice.cpp create mode 100644 platformWin32/winDInputDevice.h create mode 100644 platformWin32/winDirectInput.cpp create mode 100644 platformWin32/winDirectInput.h create mode 100644 platformWin32/winDlibrary.cpp create mode 100644 platformWin32/winExec.cpp create mode 100644 platformWin32/winFileio.cpp create mode 100644 platformWin32/winFont.cpp create mode 100644 platformWin32/winFont.h create mode 100644 platformWin32/winInput.cpp create mode 100644 platformWin32/winMath.cpp create mode 100644 platformWin32/winMath_ASM.cpp create mode 100644 platformWin32/winMemory.cpp create mode 100644 platformWin32/winProcessControl.cpp create mode 100644 platformWin32/winRedbook.cpp create mode 100644 platformWin32/winSemaphore.cpp create mode 100644 platformWin32/winTLS.cpp create mode 100644 platformWin32/winTime.cpp create mode 100644 platformWin32/winTimer.cpp create mode 100644 platformWin32/winUser.cpp create mode 100644 platformWin32/winVFS.cpp create mode 100644 platformWin32/winVolume.cpp create mode 100644 platformWin32/winVolume.h create mode 100644 platformWin32/winWindow.cpp create mode 100644 platformWin32/win_common_prefix.h create mode 100644 platformWin32/win_debug_prefix.h create mode 100644 platformWin32/win_release_prefix.h create mode 100644 platformX86UNIX/gl_types.h create mode 100644 platformX86UNIX/platformAL.h create mode 100644 platformX86UNIX/platformX86UNIX.h create mode 100644 platformX86UNIX/threads/mutex.cpp create mode 100644 platformX86UNIX/threads/semaphore.cpp create mode 100644 platformX86UNIX/threads/thread.cpp create mode 100644 platformX86UNIX/x86UNIXAsmBlit.cpp create mode 100644 platformX86UNIX/x86UNIXCPUInfo.cpp create mode 100644 platformX86UNIX/x86UNIXConsole.cpp create mode 100644 platformX86UNIX/x86UNIXFileio.cpp create mode 100644 platformX86UNIX/x86UNIXFont.client.cpp create mode 100644 platformX86UNIX/x86UNIXFont.h create mode 100644 platformX86UNIX/x86UNIXGL.client.cpp create mode 100644 platformX86UNIX/x86UNIXIO.cpp create mode 100644 platformX86UNIX/x86UNIXInput.client.cpp create mode 100644 platformX86UNIX/x86UNIXInputManager.client.cpp create mode 100644 platformX86UNIX/x86UNIXInputManager.h create mode 100644 platformX86UNIX/x86UNIXMain.cpp create mode 100644 platformX86UNIX/x86UNIXMath.cpp create mode 100644 platformX86UNIX/x86UNIXMath_ASM.cpp create mode 100644 platformX86UNIX/x86UNIXMemory.cpp create mode 100644 platformX86UNIX/x86UNIXMessageBox.client.cpp create mode 100644 platformX86UNIX/x86UNIXMessageBox.h create mode 100644 platformX86UNIX/x86UNIXNet.cpp create mode 100644 platformX86UNIX/x86UNIXOGLVideo.client.cpp create mode 100644 platformX86UNIX/x86UNIXOGLVideo.h create mode 100644 platformX86UNIX/x86UNIXOpenAL.client.cpp create mode 100644 platformX86UNIX/x86UNIXProcessControl.cpp create mode 100644 platformX86UNIX/x86UNIXRedbook.cpp create mode 100644 platformX86UNIX/x86UNIXState.h create mode 100644 platformX86UNIX/x86UNIXStdConsole.h create mode 100644 platformX86UNIX/x86UNIXStrings.cpp create mode 100644 platformX86UNIX/x86UNIXStub.dedicated.cpp create mode 100644 platformX86UNIX/x86UNIXTime.cpp create mode 100644 platformX86UNIX/x86UNIXUtils.cpp create mode 100644 platformX86UNIX/x86UNIXUtils.h create mode 100644 platformX86UNIX/x86UNIXWindow.client.cpp create mode 100644 postFx/postEffect.cpp create mode 100644 postFx/postEffect.h create mode 100644 postFx/postEffectCommon.h create mode 100644 postFx/postEffectManager.cpp create mode 100644 postFx/postEffectManager.h create mode 100644 postFx/postEffectVis.cpp create mode 100644 postFx/postEffectVis.h create mode 100644 renderInstance/forcedMaterialMeshMgr.cpp create mode 100644 renderInstance/forcedMaterialMeshMgr.h create mode 100644 renderInstance/renderBinManager.cpp create mode 100644 renderInstance/renderBinManager.h create mode 100644 renderInstance/renderFormatChanger.cpp create mode 100644 renderInstance/renderFormatChanger.h create mode 100644 renderInstance/renderGlowMgr.cpp create mode 100644 renderInstance/renderGlowMgr.h create mode 100644 renderInstance/renderImposterMgr.cpp create mode 100644 renderInstance/renderImposterMgr.h create mode 100644 renderInstance/renderMeshMgr.cpp create mode 100644 renderInstance/renderMeshMgr.h create mode 100644 renderInstance/renderObjectMgr.cpp create mode 100644 renderInstance/renderObjectMgr.h create mode 100644 renderInstance/renderOcclusionMgr.cpp create mode 100644 renderInstance/renderOcclusionMgr.h create mode 100644 renderInstance/renderParticleMgr.cpp create mode 100644 renderInstance/renderParticleMgr.h create mode 100644 renderInstance/renderPassManager.cpp create mode 100644 renderInstance/renderPassManager.h create mode 100644 renderInstance/renderPassStateToken.cpp create mode 100644 renderInstance/renderPassStateToken.h create mode 100644 renderInstance/renderPrePassMgr.cpp create mode 100644 renderInstance/renderPrePassMgr.h create mode 100644 renderInstance/renderTerrainMgr.cpp create mode 100644 renderInstance/renderTerrainMgr.h create mode 100644 renderInstance/renderTexTargetBinManager.cpp create mode 100644 renderInstance/renderTexTargetBinManager.h create mode 100644 renderInstance/renderTranslucentMgr.cpp create mode 100644 renderInstance/renderTranslucentMgr.h create mode 100644 sceneGraph/fogStructs.h create mode 100644 sceneGraph/pathManager.cpp create mode 100644 sceneGraph/pathManager.h create mode 100644 sceneGraph/reflectionManager.cpp create mode 100644 sceneGraph/reflectionManager.h create mode 100644 sceneGraph/reflector.cpp create mode 100644 sceneGraph/reflector.h create mode 100644 sceneGraph/sceneGraph.cpp create mode 100644 sceneGraph/sceneGraph.h create mode 100644 sceneGraph/sceneObject.cpp create mode 100644 sceneGraph/sceneObject.h create mode 100644 sceneGraph/sceneRoot.cpp create mode 100644 sceneGraph/sceneRoot.h create mode 100644 sceneGraph/sceneState.cpp create mode 100644 sceneGraph/sceneState.h create mode 100644 sceneGraph/sceneTraversal.cpp create mode 100644 sceneGraph/sgUtil.cpp create mode 100644 sceneGraph/sgUtil.h create mode 100644 sceneGraph/simPath.cpp create mode 100644 sceneGraph/simPath.h create mode 100644 sceneGraph/windingClipper.cpp create mode 100644 sceneGraph/windingClipper.h create mode 100644 sfx/dsound/dsFunctions.h create mode 100644 sfx/dsound/sfxDSBuffer.cpp create mode 100644 sfx/dsound/sfxDSBuffer.h create mode 100644 sfx/dsound/sfxDSDevice.cpp create mode 100644 sfx/dsound/sfxDSDevice.h create mode 100644 sfx/dsound/sfxDSProvider.cpp create mode 100644 sfx/dsound/sfxDSVoice.cpp create mode 100644 sfx/dsound/sfxDSVoice.h create mode 100644 sfx/fmod/fmodFunctions.h create mode 100644 sfx/fmod/sfxFMODBuffer.cpp create mode 100644 sfx/fmod/sfxFMODBuffer.h create mode 100644 sfx/fmod/sfxFMODDevice.cpp create mode 100644 sfx/fmod/sfxFMODDevice.h create mode 100644 sfx/fmod/sfxFMODProvider.cpp create mode 100644 sfx/fmod/sfxFMODVoice.cpp create mode 100644 sfx/fmod/sfxFMODVoice.h create mode 100644 sfx/media/sfxVorbisStream.cpp create mode 100644 sfx/media/sfxVorbisStream.h create mode 100644 sfx/media/sfxWavStream.cpp create mode 100644 sfx/media/sfxWavStream.h create mode 100644 sfx/null/sfxNullBuffer.cpp create mode 100644 sfx/null/sfxNullBuffer.h create mode 100644 sfx/null/sfxNullDevice.cpp create mode 100644 sfx/null/sfxNullDevice.h create mode 100644 sfx/null/sfxNullProvider.cpp create mode 100644 sfx/null/sfxNullVoice.cpp create mode 100644 sfx/null/sfxNullVoice.h create mode 100644 sfx/openal/LoadOAL.h create mode 100644 sfx/openal/aldlist.cpp create mode 100644 sfx/openal/aldlist.h create mode 100644 sfx/openal/mac/LoadOAL.mac.cpp create mode 100644 sfx/openal/sfxALBuffer.cpp create mode 100644 sfx/openal/sfxALBuffer.h create mode 100644 sfx/openal/sfxALCaps.h create mode 100644 sfx/openal/sfxALDevice.cpp create mode 100644 sfx/openal/sfxALDevice.h create mode 100644 sfx/openal/sfxALProvider.cpp create mode 100644 sfx/openal/sfxALVoice.cpp create mode 100644 sfx/openal/sfxALVoice.h create mode 100644 sfx/openal/win32/LoadOAL.cpp create mode 100644 sfx/sfxBuffer.cpp create mode 100644 sfx/sfxBuffer.h create mode 100644 sfx/sfxCommon.h create mode 100644 sfx/sfxDescription.cpp create mode 100644 sfx/sfxDescription.h create mode 100644 sfx/sfxDevice.cpp create mode 100644 sfx/sfxDevice.h create mode 100644 sfx/sfxEffect.cpp create mode 100644 sfx/sfxEffect.h create mode 100644 sfx/sfxEnvironment.cpp create mode 100644 sfx/sfxEnvironment.h create mode 100644 sfx/sfxFileStream.cpp create mode 100644 sfx/sfxFileStream.h create mode 100644 sfx/sfxInternal.cpp create mode 100644 sfx/sfxInternal.h create mode 100644 sfx/sfxListener.cpp create mode 100644 sfx/sfxListener.h create mode 100644 sfx/sfxPacketStream.cpp create mode 100644 sfx/sfxPacketStream.h create mode 100644 sfx/sfxProfile.cpp create mode 100644 sfx/sfxProfile.h create mode 100644 sfx/sfxProvider.cpp create mode 100644 sfx/sfxProvider.h create mode 100644 sfx/sfxResource.cpp create mode 100644 sfx/sfxResource.h create mode 100644 sfx/sfxSource.cpp create mode 100644 sfx/sfxSource.h create mode 100644 sfx/sfxStream.h create mode 100644 sfx/sfxSystem.cpp create mode 100644 sfx/sfxSystem.h create mode 100644 sfx/sfxVoice.cpp create mode 100644 sfx/sfxVoice.h create mode 100644 sfx/xaudio/sfxXAudioBuffer.cpp create mode 100644 sfx/xaudio/sfxXAudioBuffer.h create mode 100644 sfx/xaudio/sfxXAudioDevice.cpp create mode 100644 sfx/xaudio/sfxXAudioDevice.h create mode 100644 sfx/xaudio/sfxXAudioProvider.cpp create mode 100644 sfx/xaudio/sfxXAudioVoice.cpp create mode 100644 sfx/xaudio/sfxXAudioVoice.h create mode 100644 shaderGen/GLSL/bumpGLSL.cpp create mode 100644 shaderGen/GLSL/bumpGLSL.h create mode 100644 shaderGen/GLSL/depthGLSL.cpp create mode 100644 shaderGen/GLSL/depthGLSL.h create mode 100644 shaderGen/GLSL/paraboloidGLSL.cpp create mode 100644 shaderGen/GLSL/paraboloidGLSL.h create mode 100644 shaderGen/GLSL/pixSpecularGLSL.cpp create mode 100644 shaderGen/GLSL/pixSpecularGLSL.h create mode 100644 shaderGen/GLSL/shaderCompGLSL.cpp create mode 100644 shaderGen/GLSL/shaderCompGLSL.h create mode 100644 shaderGen/GLSL/shaderFeatureGLSL.cpp create mode 100644 shaderGen/GLSL/shaderFeatureGLSL.h create mode 100644 shaderGen/GLSL/shaderGenGLSL.cpp create mode 100644 shaderGen/GLSL/shaderGenGLSL.h create mode 100644 shaderGen/GLSL/shaderGenGLSLInit.cpp create mode 100644 shaderGen/HLSL/bumpHLSL.cpp create mode 100644 shaderGen/HLSL/bumpHLSL.h create mode 100644 shaderGen/HLSL/depthHLSL.cpp create mode 100644 shaderGen/HLSL/depthHLSL.h create mode 100644 shaderGen/HLSL/paraboloidHLSL.cpp create mode 100644 shaderGen/HLSL/paraboloidHLSL.h create mode 100644 shaderGen/HLSL/pixSpecularHLSL.cpp create mode 100644 shaderGen/HLSL/pixSpecularHLSL.h create mode 100644 shaderGen/HLSL/shaderCompHLSL.cpp create mode 100644 shaderGen/HLSL/shaderCompHLSL.h create mode 100644 shaderGen/HLSL/shaderFeatureHLSL.cpp create mode 100644 shaderGen/HLSL/shaderFeatureHLSL.h create mode 100644 shaderGen/HLSL/shaderGenHLSL.cpp create mode 100644 shaderGen/HLSL/shaderGenHLSL.h create mode 100644 shaderGen/HLSL/shaderGenHLSLInit.cpp create mode 100644 shaderGen/conditionerFeature.cpp create mode 100644 shaderGen/conditionerFeature.h create mode 100644 shaderGen/featureMgr.cpp create mode 100644 shaderGen/featureMgr.h create mode 100644 shaderGen/featureSet.cpp create mode 100644 shaderGen/featureSet.h create mode 100644 shaderGen/featureType.cpp create mode 100644 shaderGen/featureType.h create mode 100644 shaderGen/langElement.cpp create mode 100644 shaderGen/langElement.h create mode 100644 shaderGen/shaderComp.cpp create mode 100644 shaderGen/shaderComp.h create mode 100644 shaderGen/shaderDependency.cpp create mode 100644 shaderGen/shaderDependency.h create mode 100644 shaderGen/shaderFeature.cpp create mode 100644 shaderGen/shaderFeature.h create mode 100644 shaderGen/shaderGen.cpp create mode 100644 shaderGen/shaderGen.h create mode 100644 shaderGen/shaderGenVars.cpp create mode 100644 shaderGen/shaderGenVars.h create mode 100644 shaderGen/shaderOp.cpp create mode 100644 shaderGen/shaderOp.h create mode 100644 sim/actionMap.cpp create mode 100644 sim/actionMap.h create mode 100644 sim/connectionStringTable.cpp create mode 100644 sim/connectionStringTable.h create mode 100644 sim/netConnection.cpp create mode 100644 sim/netConnection.h create mode 100644 sim/netDownload.cpp create mode 100644 sim/netEvent.cpp create mode 100644 sim/netGhost.cpp create mode 100644 sim/netInterface.cpp create mode 100644 sim/netInterface.h create mode 100644 sim/netObject.cpp create mode 100644 sim/netObject.h create mode 100644 sim/netStringTable.cpp create mode 100644 sim/netStringTable.h create mode 100644 sim/processList.cpp create mode 100644 sim/processList.h create mode 100644 terrain/glsl/terrFeatureGLSL.cpp create mode 100644 terrain/glsl/terrFeatureGLSL.h create mode 100644 terrain/hlsl/terrFeatureHLSL.cpp create mode 100644 terrain/hlsl/terrFeatureHLSL.h create mode 100644 terrain/terrCell.cpp create mode 100644 terrain/terrCell.h create mode 100644 terrain/terrCellMaterial.cpp create mode 100644 terrain/terrCellMaterial.h create mode 100644 terrain/terrCollision.cpp create mode 100644 terrain/terrCollision.h create mode 100644 terrain/terrData.cpp create mode 100644 terrain/terrData.h create mode 100644 terrain/terrExport.cpp create mode 100644 terrain/terrFeatureTypes.cpp create mode 100644 terrain/terrFeatureTypes.h create mode 100644 terrain/terrFile.cpp create mode 100644 terrain/terrFile.h create mode 100644 terrain/terrImport.cpp create mode 100644 terrain/terrLighting.cpp create mode 100644 terrain/terrMaterial.cpp create mode 100644 terrain/terrMaterial.h create mode 100644 terrain/terrRender.cpp create mode 100644 terrain/terrRender.h create mode 100644 ts/arch/tsMeshIntrinsics.arch.h create mode 100644 ts/arch/tsMeshIntrinsics.sse.cpp create mode 100644 ts/arch/tsMeshIntrinsics.sse4.cpp create mode 100644 ts/collada/colladaAppMaterial.cpp create mode 100644 ts/collada/colladaAppMaterial.h create mode 100644 ts/collada/colladaAppMesh.cpp create mode 100644 ts/collada/colladaAppMesh.h create mode 100644 ts/collada/colladaAppNode.cpp create mode 100644 ts/collada/colladaAppNode.h create mode 100644 ts/collada/colladaAppSequence.cpp create mode 100644 ts/collada/colladaAppSequence.h create mode 100644 ts/collada/colladaExtensions.cpp create mode 100644 ts/collada/colladaExtensions.h create mode 100644 ts/collada/colladaImport.cpp create mode 100644 ts/collada/colladaLights.cpp create mode 100644 ts/collada/colladaShapeLoader.cpp create mode 100644 ts/collada/colladaShapeLoader.h create mode 100644 ts/collada/colladaUtils.cpp create mode 100644 ts/collada/colladaUtils.h create mode 100644 ts/loader/appMaterial.h create mode 100644 ts/loader/appMesh.cpp create mode 100644 ts/loader/appMesh.h create mode 100644 ts/loader/appNode.cpp create mode 100644 ts/loader/appNode.h create mode 100644 ts/loader/appSequence.h create mode 100644 ts/loader/tsShapeLoader.cpp create mode 100644 ts/loader/tsShapeLoader.h create mode 100644 ts/tsAnimate.cpp create mode 100644 ts/tsCollision.cpp create mode 100644 ts/tsDecal.cpp create mode 100644 ts/tsDecal.h create mode 100644 ts/tsDump.cpp create mode 100644 ts/tsIntegerSet.cpp create mode 100644 ts/tsIntegerSet.h create mode 100644 ts/tsLastDetail.cpp create mode 100644 ts/tsLastDetail.h create mode 100644 ts/tsMaterialList.cpp create mode 100644 ts/tsMesh.cpp create mode 100644 ts/tsMesh.h create mode 100644 ts/tsMeshIntrinsics.cpp create mode 100644 ts/tsMeshIntrinsics.h create mode 100644 ts/tsPartInstance.cpp create mode 100644 ts/tsPartInstance.h create mode 100644 ts/tsRenderState.cpp create mode 100644 ts/tsRenderState.h create mode 100644 ts/tsShape.cpp create mode 100644 ts/tsShape.h create mode 100644 ts/tsShapeAlloc.cpp create mode 100644 ts/tsShapeAlloc.h create mode 100644 ts/tsShapeConstruct.cpp create mode 100644 ts/tsShapeConstruct.h create mode 100644 ts/tsShapeEdit.cpp create mode 100644 ts/tsShapeInstance.cpp create mode 100644 ts/tsShapeInstance.h create mode 100644 ts/tsShapeOldRead.cpp create mode 100644 ts/tsSortedMesh.cpp create mode 100644 ts/tsSortedMesh.h create mode 100644 ts/tsThread.cpp create mode 100644 ts/tsTransform.cpp create mode 100644 ts/tsTransform.h create mode 100644 unit/consoleTest.cpp create mode 100644 unit/memoryTester.cpp create mode 100644 unit/memoryTester.h create mode 100644 unit/test.cpp create mode 100644 unit/test.h create mode 100644 unit/tests/testComponents.cpp create mode 100644 unit/tests/testMatrixMul.cpp create mode 100644 unit/tests/testRuntimeClassRep.cpp create mode 100644 unit/tests/testSwizzle.cpp create mode 100644 unit/tests/testThreadStatic.cpp create mode 100644 unit/tests/testThreadStaticPerformance.cpp create mode 100644 unit/tests/testVector.cpp create mode 100644 unit/unitTestComponentInterface.cpp create mode 100644 unit/unitTestComponentInterface.h create mode 100644 util/catmullRom.cpp create mode 100644 util/catmullRom.h create mode 100644 util/fpsTracker.cpp create mode 100644 util/fpsTracker.h create mode 100644 util/imposterCapture.cpp create mode 100644 util/imposterCapture.h create mode 100644 util/messaging/dispatcher.cpp create mode 100644 util/messaging/dispatcher.h create mode 100644 util/messaging/eventManager.cpp create mode 100644 util/messaging/eventManager.h create mode 100644 util/messaging/message.cpp create mode 100644 util/messaging/message.h create mode 100644 util/messaging/messageForwarder.cpp create mode 100644 util/messaging/messageForwarder.h create mode 100644 util/messaging/scriptMsgListener.cpp create mode 100644 util/messaging/scriptMsgListener.h create mode 100644 util/noise2d.cpp create mode 100644 util/noise2d.h create mode 100644 util/quadTreeTracer.cpp create mode 100644 util/quadTreeTracer.h create mode 100644 util/rectClipper.cpp create mode 100644 util/rectClipper.h create mode 100644 util/returnType.h create mode 100644 util/sampler.cpp create mode 100644 util/sampler.h create mode 100644 util/settings.cpp create mode 100644 util/settings.h create mode 100644 util/tempAlloc.h create mode 100644 util/triBoxCheck.cpp create mode 100644 util/triBoxCheck.h create mode 100644 util/triRayCheck.cpp create mode 100644 util/triRayCheck.h create mode 100644 util/undo.cpp create mode 100644 util/undo.h create mode 100644 windowManager/dedicated/dedicatedWindowStub.cpp create mode 100644 windowManager/dedicated/dedicatedWindowStub.h create mode 100644 windowManager/mac/macCursorController.h create mode 100644 windowManager/mac/macCursorController.mm create mode 100644 windowManager/mac/macView.h create mode 100644 windowManager/mac/macView.mm create mode 100644 windowManager/mac/macWindow.h create mode 100644 windowManager/mac/macWindow.mm create mode 100644 windowManager/mac/macWindowManager.h create mode 100644 windowManager/mac/macWindowManager.mm create mode 100644 windowManager/platformCursorController.cpp create mode 100644 windowManager/platformCursorController.h create mode 100644 windowManager/platformInterface.cpp create mode 100644 windowManager/platformWindow.cpp create mode 100644 windowManager/platformWindow.h create mode 100644 windowManager/platformWindowMgr.h create mode 100644 windowManager/test/testWinMgr.cpp create mode 100644 windowManager/win32/win32CursorController.cpp create mode 100644 windowManager/win32/win32CursorController.h create mode 100644 windowManager/win32/win32SplashScreen.cpp create mode 100644 windowManager/win32/win32Window.cpp create mode 100644 windowManager/win32/win32Window.h create mode 100644 windowManager/win32/win32WindowMgr.cpp create mode 100644 windowManager/win32/win32WindowMgr.h create mode 100644 windowManager/win32/winDispatch.cpp create mode 100644 windowManager/win32/winDispatch.h create mode 100644 windowManager/windowInputGenerator.cpp create mode 100644 windowManager/windowInputGenerator.h diff --git a/T3D/aiClient.cpp b/T3D/aiClient.cpp new file mode 100644 index 0000000..d17edf6 --- /dev/null +++ b/T3D/aiClient.cpp @@ -0,0 +1,542 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "T3D/aiClient.h" +#include "math/mMatrix.h" +#include "T3D/shapeBase.h" +#include "T3D/player.h" +#include "T3D/moveManager.h" + +#include "console/consoleInternal.h" + +IMPLEMENT_CONOBJECT( AIClient ); + +/** + * Constructor + */ +AIClient::AIClient() { + mMoveMode = ModeStop; + mMoveDestination.set( 0.0f, 0.0f, 0.0f ); + mAimLocation.set( 0.0f, 0.0f, 0.0f ); + mMoveSpeed = 1.0f; + mMoveTolerance = 0.25f; + + // Clear the triggers + for( int i = 0; i < MaxTriggerKeys; i++ ) + mTriggers[i] = false; + + mAimToDestination = true; + mTargetInLOS = false; + + mLocation.set( 0.0f, 0.0f, 0.0f ); + mPlayer = NULL; +} + +/** + * Destructor + */ +AIClient::~AIClient() { + // Blah +} + +/** + * Sets the object the bot is targeting + * + * @param targetObject The object to target + */ +void AIClient::setTargetObject( ShapeBase *targetObject ) { + if ( !targetObject || !bool( mTargetObject ) || targetObject->getId() != mTargetObject->getId() ) + mTargetInLOS = false; + + mTargetObject = targetObject; +} + +/** + * Returns the target object + * + * @return Object bot is targeting + */ +S32 AIClient::getTargetObject() const { + if( bool( mTargetObject ) ) + return mTargetObject->getId(); + else + return -1; +} + +/** + * Sets the speed at which this AI moves + * + * @param speed Speed to move, default player was 10 + */ +void AIClient::setMoveSpeed( F32 speed ) { + if( speed <= 0.0f ) + mMoveSpeed = 0.0f; + else + mMoveSpeed = getMin( 1.0f, speed ); +} + +/** + * Sets the movement mode for this AI + * + * @param mode Movement mode, see enum + */ +void AIClient::setMoveMode( S32 mode ) { + if( mode < 0 || mode >= ModeCount ) + mode = 0; + + if( mode != mMoveMode ) { + switch( mode ) { + case ModeStuck: + throwCallback( "onStuck" ); + break; + case ModeMove: + if( mMoveMode == ModeStuck ) + throwCallback( "onUnStuck" ); + else + throwCallback( "onMove" ); + break; + case ModeStop: + throwCallback( "onStop" ); + break; + } + } + + mMoveMode = mode; +} + +/** + * Sets how far away from the move location is considered + * "on target" + * + * @param tolerance Movement tolerance for error + */ +void AIClient::setMoveTolerance( const F32 tolerance ) { + mMoveTolerance = getMax( 0.1f, tolerance ); +} + +/** + * Sets the location for the bot to run to + * + * @param location Point to run to + */ +void AIClient::setMoveDestination( const Point3F &location ) { + // Ok, here's the story...we're going to aim where we are going UNLESS told otherwise + if( mAimToDestination ) { + mAimLocation = location; + mAimLocation.z = 0.0f; + } + + mMoveDestination = location; +} + +/** + * Sets the location for the bot to aim at + * + * @param location Point to aim at + */ +void AIClient::setAimLocation( const Point3F &location ) { + mAimLocation = location; + mAimToDestination = false; +} + +/** + * Clears the aim location and sets it to the bot's + * current destination so he looks where he's going + */ +void AIClient::clearAim() { + mAimLocation = Point3F( 0.0f, 0.0f, 0.0f ); + mAimToDestination = true; +} + +/** + * This method gets the move list for an object, in the case + * of the AI, it actually calculates the moves, and then + * sends them down the pipe. + * + * @param movePtr Pointer to move the move list into + * @param numMoves Number of moves in the move list + */ +U32 AIClient::getMoveList( Move **movePtr,U32 *numMoves ) { + //initialize the move structure and return pointers + mMove = NullMove; + *movePtr = &mMove; + *numMoves = 1; + + // Check if we got a player + mPlayer = NULL; + mPlayer = dynamic_cast( getControlObject() ); + + // We got a something controling us? + if( !mPlayer ) + return 1; + + + // What is The Matrix? + MatrixF moveMatrix; + moveMatrix.set( EulerF( 0, 0, 0 ) ); + moveMatrix.setColumn( 3, Point3F( 0, 0, 0 ) ); + moveMatrix.transpose(); + + // Position / rotation variables + F32 curYaw, curPitch; + F32 newYaw, newPitch; + F32 xDiff, yDiff; + + + F32 moveSpeed = mMoveSpeed; + + switch( mMoveMode ) { + + case ModeStop: + return 1; // Stop means no action + break; + + case ModeStuck: + // Fall through, so we still try to move + case ModeMove: + + // Get my location + MatrixF const& myTransform = mPlayer->getTransform(); + myTransform.getColumn( 3, &mLocation ); + + // Set rotation variables + Point3F rotation = mPlayer->getRotation(); + Point3F headRotation = mPlayer->getHeadRotation(); + curYaw = rotation.z; + curPitch = headRotation.x; + xDiff = mAimLocation.x - mLocation.x; + yDiff = mAimLocation.y - mLocation.y; + + // first do Yaw + if( !mIsZero( xDiff ) || !mIsZero( yDiff ) ) { + // use the cur yaw between -Pi and Pi + while( curYaw > M_2PI_F ) + curYaw -= M_2PI_F; + while( curYaw < -M_2PI_F ) + curYaw += M_2PI_F; + + // find the new yaw + newYaw = mAtan2( xDiff, yDiff ); + + // find the yaw diff + F32 yawDiff = newYaw - curYaw; + + // make it between 0 and 2PI + if( yawDiff < 0.0f ) + yawDiff += M_2PI_F; + else if( yawDiff >= M_2PI_F ) + yawDiff -= M_2PI_F; + + // now make sure we take the short way around the circle + if( yawDiff > M_2PI_F ) + yawDiff -= M_2PI_F; + else if( yawDiff < -M_2PI_F ) + yawDiff += M_2PI_F; + + mMove.yaw = yawDiff; + + // set up the movement matrix + moveMatrix.set( EulerF( 0.0f, 0.0f, newYaw ) ); + } + else + moveMatrix.set( EulerF( 0.0f, 0.0f, curYaw ) ); + + // next do pitch + F32 horzDist = Point2F( mAimLocation.x, mAimLocation.y ).len(); + + if( !mIsZero( horzDist ) ) { + //we shoot from the gun, not the eye... + F32 vertDist = mAimLocation.z; + + newPitch = mAtan2( horzDist, vertDist ) - ( M_2PI_F / 2.0f ); + + F32 pitchDiff = newPitch - curPitch; + mMove.pitch = pitchDiff; + } + + // finally, mMove towards mMoveDestination + xDiff = mMoveDestination.x - mLocation.x; + yDiff = mMoveDestination.y - mLocation.y; + + + // Check if we should mMove, or if we are 'close enough' + if( ( ( mFabs( xDiff ) > mMoveTolerance ) || + ( mFabs( yDiff ) > mMoveTolerance ) ) && ( !mIsZero( mMoveSpeed ) ) ) + { + if( mIsZero( xDiff ) ) + mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed ); + else if( mIsZero( yDiff ) ) + mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed ); + else if( mFabs( xDiff ) > mFabs( yDiff ) ) { + F32 value = mFabs( yDiff / xDiff ) * mMoveSpeed; + mMove.y = ( mLocation.y > mMoveDestination.y ? -value : value ); + mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed ); + } + else { + F32 value = mFabs( xDiff / yDiff ) * mMoveSpeed; + mMove.x = ( mLocation.x > mMoveDestination.x ? -value : value ); + mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed ); + } + + //now multiply the mMove vector by the transpose of the object rotation matrix + moveMatrix.transpose(); + Point3F newMove; + moveMatrix.mulP( Point3F( mMove.x, mMove.y, 0.0f ), &newMove ); + + //and sub the result back in the mMove structure + mMove.x = newMove.x; + mMove.y = newMove.y; + + // We should check to see if we are stuck... + if( mLocation.x == mLastLocation.x && + mLocation.y == mLastLocation.y && + mLocation.z == mLastLocation.z ) { + + // We're stuck...probably + setMoveMode( ModeStuck ); + } + else + setMoveMode( ModeMove ); + } + else { + // Ok, we are close enough, lets stop + + // setMoveMode( ModeStop ); // DON'T use this, it'll throw the wrong callback + mMoveMode = ModeStop; + throwCallback( "onReachDestination" ); // Callback + + } + break; + } + + // Test for target location in sight + RayInfo dummy; + Point3F targetLoc = mMoveDestination; // Change this + + if( mPlayer ) { + if( !mPlayer->getContainer()->castRay( mLocation, targetLoc, InteriorObjectType | + StaticShapeObjectType | StaticObjectType | + TerrainObjectType, &dummy ) ) { + if( !mTargetInLOS ) + throwCallback( "onTargetEnterLOS" ); + } + else { + if( mTargetInLOS ) + throwCallback( "onTargetExitLOS" ); + + } + } + + // Copy over the trigger status + for( int i = 0; i < MaxTriggerKeys; i++ ) { + mMove.trigger[i] = mTriggers[i]; + mTriggers[i] = false; + } + + return 1; +} + +/** + * This method is just called to stop the bots from running amuck + * while the mission cycles + */ +void AIClient::missionCycleCleanup() { + setMoveMode( ModeStop ); +} + + +/** + * Utility function to throw callbacks + */ +void AIClient::throwCallback( const char *name ) { + Con::executef( this, name ); +} + +/** + * What gets called when this gets created, different from constructor + */ +void AIClient::onAdd( const char *nameSpace ) { + + // This doesn't work... + // + if( dStrcmp( nameSpace, mNameSpace->mName ) ) { + Con::linkNamespaces( mNameSpace->mName, nameSpace ); + mNameSpace = Con::lookupNamespace( nameSpace ); + } + + throwCallback( "onAdd" ); +} + +// -------------------------------------------------------------------------------------------- +// Console Functions +// -------------------------------------------------------------------------------------------- + +/** + * Sets the move speed for an AI object + */ +ConsoleMethod( AIClient, setMoveSpeed, void, 3, 3, "ai.setMoveSpeed( float );" ) { + AIClient *ai = static_cast( object ); + ai->setMoveSpeed( dAtof( argv[2] ) ); +} + +/** + * Stops all AI movement, halt! + */ +ConsoleMethod( AIClient, stop, void, 2, 2, "ai.stop();" ) { + AIClient *ai = static_cast( object ); + ai->setMoveMode( AIClient::ModeStop ); +} + +/** + * Tells the AI to aim at the location provided + */ +ConsoleMethod( AIClient, setAimLocation, void, 3, 3, "ai.setAimLocation( x y z );" ) { + AIClient *ai = static_cast( object ); + Point3F v( 0.0f,0.0f,0.0f ); + dSscanf( argv[2], "%f %f %f", &v.x, &v.y, &v.z ); + + ai->setAimLocation( v ); +} + +/** + * Tells the AI to move to the location provided + */ +ConsoleMethod( AIClient, setMoveDestination, void, 3, 3, "ai.setMoveDestination( x y z );" ) { + AIClient *ai = static_cast( object ); + Point3F v( 0.0f, 0.0f, 0.0f ); + dSscanf( argv[2], "%f %f", &v.x, &v.y ); + + ai->setMoveDestination( v ); +} + +/** + * Returns the point the AI is aiming at + */ +ConsoleMethod( AIClient, getAimLocation, const char *, 2, 2, "ai.getAimLocation();" ) { + AIClient *ai = static_cast( object ); + Point3F aimPoint = ai->getAimLocation(); + + char *returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z ); + + return returnBuffer; +} + +/** + * Returns the point the AI is set to move to + */ +ConsoleMethod( AIClient, getMoveDestination, const char *, 2, 2, "ai.getMoveDestination();" ) { + AIClient *ai = static_cast( object ); + Point3F movePoint = ai->getMoveDestination(); + + char *returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%f %f %f", movePoint.x, movePoint.y, movePoint.z ); + + return returnBuffer; +} + +/** + * Sets the bots target object + */ +ConsoleMethod( AIClient, setTargetObject, void, 3, 3, "ai.setTargetObject( obj );" ) { + AIClient *ai = static_cast( object ); + + // Find the target + ShapeBase *targetObject; + if( Sim::findObject( argv[2], targetObject ) ) + ai->setTargetObject( targetObject ); + else + ai->setTargetObject( NULL ); +} + +/** + * Gets the object the AI is targeting + */ +ConsoleMethod( AIClient, getTargetObject, S32, 2, 2, "ai.getTargetObject();" ) { + AIClient *ai = static_cast( object ); + + return ai->getTargetObject(); +} + +/** + * Tells the bot the mission is cycling + */ +ConsoleMethod( AIClient, missionCycleCleanup, void, 2, 2, "ai.missionCycleCleanup();" ) { + AIClient *ai = static_cast( object ); + ai->missionCycleCleanup(); +} + +/** + * Sets the AI to run mode + */ +ConsoleMethod( AIClient, move, void, 2, 2, "ai.move();" ) { + AIClient *ai = static_cast( object ); + ai->setMoveMode( AIClient::ModeMove ); +} + +/** + * Gets the AI's location in the world + */ +ConsoleMethod( AIClient, getLocation, const char *, 2, 2, "ai.getLocation();" ) { + AIClient *ai = static_cast( object ); + Point3F locPoint = ai->getLocation(); + + char *returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%f %f %f", locPoint.x, locPoint.y, locPoint.z ); + + return returnBuffer; +} + +/** + * Adds an AI Player to the game + */ +ConsoleFunction( aiAddPlayer, S32 , 2, 3, "aiAddPlayer( 'playerName'[, 'AIClassType'] );" ) { + // Create the player + AIClient *aiPlayer = new AIClient(); + aiPlayer->registerObject(); + aiPlayer->setGhostFrom(false); + aiPlayer->setGhostTo(false); + aiPlayer->setSendingEvents(false); + aiPlayer->setTranslatesStrings(true); + aiPlayer->setEstablished(); + + // Add the connection to the client group + SimGroup *g = Sim::getClientGroup(); + g->addObject( aiPlayer ); + + char *name = new char[ dStrlen( argv[1] ) + 1]; + char *ns = new char[ dStrlen( argv[2] ) + 1]; + + dStrcpy( name, argv[1] ); + dStrcpy( ns, argv[2] ); + + // Execute the connect console function, this is the same + // onConnect function invoked for normal client connections + Con::executef( aiPlayer, "onConnect", name ); + + // Now execute the onAdd command and feed it the namespace + if( argc > 2 ) + aiPlayer->onAdd( ns ); + else + aiPlayer->onAdd( "AIClient" ); + + return aiPlayer->getId(); +} + + +/** + * Tells the AI to move forward 100 units...TEST FXN + */ +ConsoleMethod( AIClient, moveForward, void, 2, 2, "ai.moveForward();" ) { + + AIClient *ai = static_cast( object ); + ShapeBase *player = dynamic_cast(ai->getControlObject()); + Point3F location; + MatrixF const &myTransform = player->getTransform(); + myTransform.getColumn( 3, &location ); + + location.y += 100.0f; + + ai->setMoveDestination( location ); +} // *** /TEST FXN diff --git a/T3D/aiClient.h b/T3D/aiClient.h new file mode 100644 index 0000000..7a75449 --- /dev/null +++ b/T3D/aiClient.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AICLIENT_H_ +#define _AICLIENT_H_ + +#ifndef _AICONNECTION_H_ +#include "T3D/aiConnection.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class ShapeBase; +class Player; + +class AIClient : public AIConnection { + + typedef AIConnection Parent; + + private: + enum { + FireTrigger = 0, + JumpTrigger = 2, + JetTrigger = 3, + GrenadeTrigger = 4, + MineTrigger = 5 + }; + + F32 mMoveSpeed; + S32 mMoveMode; + F32 mMoveTolerance; // How close to the destination before we stop + + bool mTriggers[MaxTriggerKeys]; + + Player *mPlayer; + + Point3F mMoveDestination; + Point3F mLocation; + Point3F mLastLocation; // For stuck check + + bool mAimToDestination; + Point3F mAimLocation; + bool mTargetInLOS; + + SimObjectPtr mTargetObject; + + // Utility Methods + void throwCallback( const char *name ); + public: + + DECLARE_CONOBJECT( AIClient ); + + enum { + ModeStop = 0, + ModeMove, + ModeStuck, + ModeCount // This is in there as a max index value + }; + + AIClient(); + ~AIClient(); + + U32 getMoveList( Move **movePtr,U32 *numMoves ); + + // ---Targeting and aiming sets/gets + void setTargetObject( ShapeBase *targetObject ); + S32 getTargetObject() const; + + // ---Movement sets/gets + void setMoveSpeed( const F32 speed ); + F32 getMoveSpeed() const { return mMoveSpeed; } + + void setMoveMode( S32 mode ); + S32 getMoveMode() const { return mMoveMode; } + + void setMoveTolerance( const F32 tolerance ); + F32 getMoveTolerance() const { return mMoveTolerance; } + + void setMoveDestination( const Point3F &location ); + Point3F getMoveDestination() const { return mMoveDestination; } + + Point3F getLocation() const { return mLocation; } + + // ---Facing(Aiming) sets/gets + void setAimLocation( const Point3F &location ); + Point3F getAimLocation() const { return mAimLocation; } + void clearAim(); + + // ---Other + void missionCycleCleanup(); + void onAdd( const char *nameSpace ); +}; + +#endif diff --git a/T3D/aiConnection.cpp b/T3D/aiConnection.cpp new file mode 100644 index 0000000..fbb605a --- /dev/null +++ b/T3D/aiConnection.cpp @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/aiConnection.h" + +IMPLEMENT_CONOBJECT( AIConnection ); + + +//----------------------------------------------------------------------------- + +AIConnection::AIConnection() { + mAIControlled = true; + mMove = NullMove; +} + + +//----------------------------------------------------------------------------- + +void AIConnection::clearMoves( U32 ) +{ + // Clear the pending move list. This connection generates moves + // on the fly, so there are never any pending moves. +} + +void AIConnection::setMove(Move* m) +{ + mMove = *m; +} + +const Move& AIConnection::getMove() +{ + return mMove; +} + +/// Retrive the pending moves +/** + * The GameConnection base class queues moves for delivery to the + * controll object. This function is normally used to retrieve the + * queued moves recieved from the client. The AI connection does not + * have a connected client and simply generates moves on-the-fly + * base on it's current state. + */ +U32 AIConnection::getMoveList( Move **lngMove, U32 *numMoves ) +{ + *numMoves = 1; + *lngMove = &mMove; + return *numMoves; +} + + +//----------------------------------------------------------------------------- +// Console functions & methods +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +static inline F32 moveClamp(F32 v) +{ + // Support function to convert/clamp the input into a move rotation + // which only allows 0 -> M_2PI. + F32 a = mClampF(v, -M_2PI_F, M_2PI_F); + return (a < 0) ? a + M_2PI_F : a; +} + + +//----------------------------------------------------------------------------- +/// Construct and connect an AI connection object +ConsoleFunction(aiConnect, S32 , 2, 20, "(...)" + "Make a new AIConnection, and pass arguments to the onConnect script callback.") +{ + // Create the connection + AIConnection *aiConnection = new AIConnection(); + aiConnection->registerObject(); + + // Add the connection to the client group + SimGroup *g = Sim::getClientGroup(); + g->addObject( aiConnection ); + + // Prep the arguments for the console exec... + // Make sure and leav args[1] empty. + const char* args[21]; + args[0] = "onConnect"; + for (S32 i = 1; i < argc; i++) + args[i + 1] = argv[i]; + + // Execute the connect console function, this is the same + // onConnect function invoked for normal client connections + Con::execute(aiConnection, argc + 1, args); + return aiConnection->getId(); +} + + +//----------------------------------------------------------------------------- +ConsoleMethod(AIConnection,setMove,void,4, 4,"(string field, float value)" + "Set a field on the current move.\n\n" + "@param field One of {'x','y','z','yaw','pitch','roll'}\n" + "@param value Value to set field to.") +{ + Move move = object->getMove(); + + // Ok, a little slow for now, but this is just an example.. + if (!dStricmp(argv[2],"x")) + move.x = mClampF(dAtof(argv[3]),-1,1); + else + if (!dStricmp(argv[2],"y")) + move.y = mClampF(dAtof(argv[3]),-1,1); + else + if (!dStricmp(argv[2],"z")) + move.z = mClampF(dAtof(argv[3]),-1,1); + else + if (!dStricmp(argv[2],"yaw")) + move.yaw = moveClamp(dAtof(argv[3])); + else + if (!dStricmp(argv[2],"pitch")) + move.pitch = moveClamp(dAtof(argv[3])); + else + if (!dStricmp(argv[2],"roll")) + move.roll = moveClamp(dAtof(argv[3])); + + // + object->setMove(&move); +} + +ConsoleMethod(AIConnection,getMove,F32,3, 3,"(string field)" + "Get the given field of a move.\n\n" + "@param field One of {'x','y','z','yaw','pitch','roll'}\n" + "@returns The requested field on the current move.") +{ + const Move& move = object->getMove(); + if (!dStricmp(argv[2],"x")) + return move.x; + if (!dStricmp(argv[2],"y")) + return move.y; + if (!dStricmp(argv[2],"z")) + return move.z; + if (!dStricmp(argv[2],"yaw")) + return move.yaw; + if (!dStricmp(argv[2],"pitch")) + return move.pitch; + if (!dStricmp(argv[2],"roll")) + return move.roll; + return 0; +} + + +ConsoleMethod(AIConnection,setFreeLook,void,3, 3,"(bool isFreeLook)" + "Enable/disable freelook on the current move.") +{ + Move move = object->getMove(); + move.freeLook = dAtob(argv[2]); + object->setMove(&move); +} + +ConsoleMethod(AIConnection,getFreeLook,bool,2, 2,"getFreeLook()" + "Is freelook on for the current move?") +{ + return object->getMove().freeLook; +} + + +//----------------------------------------------------------------------------- + +ConsoleMethod(AIConnection,setTrigger,void,4, 4,"(int trigger, bool set)" + "Set a trigger.") +{ + S32 idx = dAtoi(argv[2]); + if (idx >= 0 && idx < MaxTriggerKeys) { + Move move = object->getMove(); + move.trigger[idx] = dAtob(argv[3]); + object->setMove(&move); + } +} + +ConsoleMethod(AIConnection,getTrigger,bool,4, 4,"(int trigger)" + "Is the given trigger set?") +{ + S32 idx = dAtoi(argv[2]); + if (idx >= 0 && idx < MaxTriggerKeys) + return object->getMove().trigger[idx]; + return false; +} + + +//----------------------------------------------------------------------------- + +ConsoleMethod(AIConnection,getAddress,const char*,2, 2,"") +{ + // Override the netConnection method to return to indicate + // this is an ai connection. + return "ai:local"; +} diff --git a/T3D/aiConnection.h b/T3D/aiConnection.h new file mode 100644 index 0000000..d2f1eeb --- /dev/null +++ b/T3D/aiConnection.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AICONNECTION_H_ +#define _AICONNECTION_H_ + +#ifndef _GAMECONNECTION_H_ +#include "T3D/gameConnection.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "T3D/moveManager.h" +#endif + +//----------------------------------------------------------------------------- + +class AIConnection : public GameConnection +{ + typedef GameConnection Parent; + +protected: + Move mMove; + +public: + AIConnection(); + DECLARE_CONOBJECT( AIConnection ); + + // Interface + const Move& getMove(); + void setMove(Move *m); + + // GameConnection overrides + void clearMoves(U32 n); + virtual U32 getMoveList(Move **,U32 *numMoves); +}; + + +#endif diff --git a/T3D/aiPlayer.cpp b/T3D/aiPlayer.cpp new file mode 100644 index 0000000..7b21159 --- /dev/null +++ b/T3D/aiPlayer.cpp @@ -0,0 +1,572 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "T3D/aiPlayer.h" +#include "console/consoleInternal.h" +#include "math/mMatrix.h" +#include "T3D/moveManager.h" + +IMPLEMENT_CO_NETOBJECT_V1(AIPlayer); + +/** + * Constructor + */ +AIPlayer::AIPlayer(): +m_fCamDistance(0.f), +m_fCamDistanceToReach(0.f), +m_ptCamRot(0.f,0.f,0.f), +m_bControlByKey(false) +{ + mMoveDestination.set( 0.0f, 0.0f, 0.0f ); + mMoveSpeed = 1.0f; + mMoveTolerance = 0.25f; + mMoveSlowdown = true; + mMoveState = ModeStop; + + mAimObject = 0; + mAimLocationSet = false; + mTargetInLOS = false; + mAimOffset = Point3F(0.0f, 0.0f, 0.0f); + + mTypeMask |= AIObjectType; +} + +/** + * Destructor + */ +AIPlayer::~AIPlayer() +{ +} + +/** + * Sets the speed at which this AI moves + * + * @param speed Speed to move, default player was 10 + */ +void AIPlayer::setMoveSpeed( F32 speed ) +{ + mMoveSpeed = getMax(0.0f, getMin( 1.0f, speed )); +} + +/** + * Stops movement for this AI + */ +void AIPlayer::stopMove() +{ + mMoveState = ModeStop; +} + +/** + * Sets how far away from the move location is considered + * "on target" + * + * @param tolerance Movement tolerance for error + */ +void AIPlayer::setMoveTolerance( const F32 tolerance ) +{ + mMoveTolerance = getMax( 0.1f, tolerance ); +} + +/** + * Sets the location for the bot to run to + * + * @param location Point to run to + */ +void AIPlayer::setMoveDestination( const Point3F &location, bool slowdown ) +{ + mMoveDestination = location; + mMoveState = ModeMove; + mMoveSlowdown = slowdown; +} + +/** + * Sets the object the bot is targeting + * + * @param targetObject The object to target + */ +void AIPlayer::setAimObject( GameBase *targetObject ) +{ + mAimObject = targetObject; + mTargetInLOS = false; + mAimOffset = Point3F(0.0f, 0.0f, 0.0f); +} + +/** + * Sets the object the bot is targeting and an offset to add to target location + * + * @param targetObject The object to target + * @param offset The offest from the target location to aim at + */ +void AIPlayer::setAimObject( GameBase *targetObject, Point3F offset ) +{ + mAimObject = targetObject; + mTargetInLOS = false; + mAimOffset = offset; +} + +/** + * Sets the location for the bot to aim at + * + * @param location Point to aim at + */ +void AIPlayer::setAimLocation( const Point3F &location ) +{ + mAimObject = 0; + mAimLocationSet = true; + mAimLocation = location; + mAimOffset = Point3F(0.0f, 0.0f, 0.0f); +} + +/** + * Clears the aim location and sets it to the bot's + * current destination so he looks where he's going + */ +void AIPlayer::clearAim() +{ + mAimObject = 0; + mAimLocationSet = false; + mAimOffset = Point3F(0.0f, 0.0f, 0.0f); +} + +/** + * This method calculates the moves for the AI player + * + * @param movePtr Pointer to move the move list into + */ +bool AIPlayer::getAIMove(Move *movePtr) +{ + if (mMoveState == ModeStop) + { + return true; + } + *movePtr = NullMove; + + // Use the eye as the current position. + MatrixF eye; + getEyeTransform(&eye); + Point3F location = eye.getPosition(); + Point3F rotation = getRotation(); + + // Orient towards the aim point, aim object, or towards + // our destination. + if (mAimObject || mAimLocationSet || mMoveState == ModeMove) { + + // Update the aim position if we're aiming for an object + if (mAimObject) + mAimLocation = mAimObject->getPosition() + mAimOffset; + else + if (!mAimLocationSet) + mAimLocation = mMoveDestination; + + F32 xDiff = mAimLocation.x - location.x; + F32 yDiff = mAimLocation.y - location.y; + if (!mIsZero(xDiff) || !mIsZero(yDiff)) { + + // First do Yaw + // use the cur yaw between -Pi and Pi + F32 curYaw = rotation.z; + while (curYaw > M_2PI_F) + curYaw -= M_2PI_F; + while (curYaw < -M_2PI_F) + curYaw += M_2PI_F; + + // find the yaw offset + F32 newYaw = mAtan2( xDiff, yDiff ); + F32 yawDiff = newYaw - curYaw; + + // make it between 0 and 2PI + if( yawDiff < 0.0f ) + yawDiff += M_2PI_F; + else if( yawDiff >= M_2PI_F ) + yawDiff -= M_2PI_F; + + // now make sure we take the short way around the circle + if( yawDiff > M_PI_F ) + yawDiff -= M_2PI_F; + else if( yawDiff < -M_PI_F ) + yawDiff += M_2PI_F; + + movePtr->yaw = yawDiff > 0 ? mClampF(yawDiff,0,M_PI_F / 20) : mClampF(yawDiff,-M_PI_F / 20 ,0); + + // Next do pitch. + if (!mAimObject && !mAimLocationSet) { + // Level out if were just looking at our next way point. + Point3F headRotation = getHeadRotation(); + movePtr->pitch = -headRotation.x; + } + else { + // This should be adjusted to run from the + // eye point to the object's center position. Though this + // works well enough for now. + F32 vertDist = mAimLocation.z - location.z; + F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff); + F32 newPitch = mAtan2( horzDist, vertDist ) - ( M_PI_F / 2.0f ); + if (mFabs(newPitch) > 0.01f) { + Point3F headRotation = getHeadRotation(); + movePtr->pitch = newPitch - headRotation.x; + } + } + } + } + else { + // Level out if we're not doing anything else + Point3F headRotation = getHeadRotation(); + movePtr->pitch = -headRotation.x; + } + + // Move towards the destination + if (mMoveState == ModeMove) { + F32 xDiff = mMoveDestination.x - location.x; + F32 yDiff = mMoveDestination.y - location.y; + + F32 zDiff = mMoveDestination.z - location.z; + + // Check if we should mMove, or if we are 'close enough' + if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance) { + mMoveState = ModeStop; + throwCallback("onReachDestination"); + } + else { + // Build move direction in world space + + + if (!mIsZero(zDiff)) + { + movePtr->z = zDiff > 0 ? 1 : -1; + } + + if (mIsZero(xDiff)) + movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f; + else + if (mIsZero(yDiff)) + movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f; + else + if (mFabs(xDiff) > mFabs(yDiff)) { + F32 value = mFabs(yDiff / xDiff); + movePtr->y = (location.y > mMoveDestination.y) ? -value : value; + movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f; + } + else { + F32 value = mFabs(xDiff / yDiff); + movePtr->x = (location.x > mMoveDestination.x) ? -value : value; + movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f; + } + + // Rotate the move into object space (this really only needs + // a 2D matrix) + Point3F newMove; + MatrixF moveMatrix; + moveMatrix.set(EulerF(0.0f, 0.0f, -(rotation.z + movePtr->yaw))); + moveMatrix.mulV( Point3F( movePtr->x, movePtr->y, 0.0f ), &newMove ); + movePtr->x = newMove.x; + movePtr->y = newMove.y; + + // Set movement speed. We'll slow down once we get close + // to try and stop on the spot... + if (mMoveSlowdown) { + F32 speed = mMoveSpeed; + F32 dist = mSqrt(xDiff*xDiff + yDiff*yDiff); + F32 maxDist = 5.0f; + if (dist < maxDist) + speed *= dist / maxDist; + movePtr->x *= speed; + movePtr->y *= speed; + } + else { + movePtr->x *= mMoveSpeed; + movePtr->y *= mMoveSpeed; + } + + // We should check to see if we are stuck... + if (location == mLastLocation) { + throwCallback("onMoveStuck"); + mMoveState = ModeStop; + } + } + } + + // Test for target location in sight if it's an object. The LOS is + // run from the eye position to the center of the object's bounding, + // which is not very accurate. + if (mAimObject) { + MatrixF eyeMat; + getEyeTransform(&eyeMat); + eyeMat.getColumn(3,&location); + Point3F targetLoc = mAimObject->getBoxCenter(); + + // This ray ignores non-static shapes. Cast Ray returns true + // if it hit something. + RayInfo dummy; + if (getContainer()->castRay( location, targetLoc, + InteriorObjectType | StaticShapeObjectType | StaticObjectType | + TerrainObjectType, &dummy)) { + if (mTargetInLOS) { + throwCallback( "onTargetExitLOS" ); + mTargetInLOS = false; + } + } + else + if (!mTargetInLOS) { + throwCallback( "onTargetEnterLOS" ); + mTargetInLOS = true; + } + } + + // Replicate the trigger state into the move so that + // triggers can be controlled from scripts. + for( int i = 0; i < MaxTriggerKeys; i++ ) + movePtr->trigger[i] = getImageTriggerState(i); + + return true; +} + +/** + * Utility function to throw callbacks. Callbacks always occure + * on the datablock class. + * + * @param name Name of script function to call + */ +void AIPlayer::throwCallback( const char *name ) +{ + Con::executef(getDataBlock(), name, scriptThis()); +} + +ConsoleMethod( AIPlayer, setControlByKey, void, 3, 3, "(%byKey)") +{ + object->setControlByKey(dAtob(argv[2])); +} +void AIPlayer::setControlByKey( bool val ) +{ + if (m_bControlByKey != val) + { + if (val) + { + backToCamera(); + } + m_bControlByKey = val; + } +} +void AIPlayer::backToCamera() +{ + faceToCamera(false); +} +void AIPlayer::faceToCamera(bool faceto) +{ + MatrixF mat; + F32 pos; + Point3F posCam,posPlayer; + getCameraTransform(&pos,&mat); + mat.getColumn(3,&posCam); + getTransform().getColumn(3,&posPlayer); + Point3F vec (posPlayer - posCam); + vec.z = 0; + vec.normalizeSafe(); + mat = MathUtils::createOrientFromDir(vec); + mat.setColumn(3,posPlayer); + setTransform(mat); +} + +void AIPlayer::preprocessMove(Move * mv) +{ + getAIMove(mv); + Parent::preprocessMove(mv); +} + +#define M_PI_DIV180 0.01745329251994329576922222f +void AIPlayer::getCameraTransform(F32* pos,MatrixF* mat) +{ + VectorF vecCam(-10.f,-10.f,20.f); + Point3F ptLookAt; + + m_ptCamRot.x += MoveManager::mPitchCam; + m_ptCamRot.x = mClampF(m_ptCamRot.x,-45*M_PI_DIV180,89*M_PI_DIV180); + + m_ptCamRot.z += MoveManager::mYawCam; + m_ptCamRot.z += MoveManager::mKeyYawCam; + m_ptCamRot.z = mFmod(m_ptCamRot.z,360*M_PI_DIV180); + + MatrixF matPlayer = getTransform(); + EulerF rotPlayerZ(0,0,0); + if (m_bControlByKey) + { + rotPlayerZ.z = -(MoveManager::mKeyYawCam + MoveManager::mYawCam); + } + else + { + rotPlayerZ.z = -MoveManager::mKeyYawCam; + } + MatrixF rotPlayer(rotPlayerZ); + matPlayer.mul(rotPlayer); + setTransform(matPlayer); + + PlayerData * pDataBlock = dynamic_cast ( getDataBlock() ); + m_fCamDistanceToReach = mClampF(MoveManager::mDistanceCam,pDataBlock->cameraMinDist, pDataBlock->cameraMaxDist); + MoveManager::mDistanceCam = m_fCamDistanceToReach; + if ( mFabs(m_fCamDistance) < 0.00001 ||\ + mFabs(m_fCamDistance - m_fCamDistanceToReach) < 0.03) + { + m_fCamDistance = m_fCamDistanceToReach; + } + else if (m_fCamDistance > m_fCamDistanceToReach) + { + m_fCamDistance -= 0.03; + } + else if(m_fCamDistance < m_fCamDistanceToReach) + { + m_fCamDistance += 0.03; + } + + MoveManager::mPitchCam = MoveManager::mYawCam = 0; + + F32 reflect = m_fCamDistance * mCos(m_ptCamRot.x); + vecCam.z = m_fCamDistance * mSin(m_ptCamRot.x); + vecCam.x = reflect * mCos(m_ptCamRot.z); + vecCam.y = reflect * mSin(m_ptCamRot.z); + + vecCam.neg(); + + + MatrixF eye; + getRenderEyeTransform(&eye); + // Use the eye transform to orient the camera + VectorF vp,vec; + vp.x = vp.z = 0; + vp.y = -7 * *pos; + eye.mulV(vp,&vec); + + Point3F sp; + eye.getColumn(3,&sp); + Point3F camPos = sp - vecCam; + + disableCollision(); + if (isMounted()) + getObjectMount()->disableCollision(); + RayInfo collision; + Point3F rayEnd = camPos - sp; + rayEnd.normalize(); + rayEnd *= 0.2; + rayEnd += camPos; + // if (mContainer->castRay(sp, rayEnd, + // (0xFFFFFFFF & ~(WaterObjectType | + // GameBaseObjectType | + // DefaultObjectType )), + // &collision) == true) + if (mContainer->castRay(sp, rayEnd, + WaterObjectType | TerrainObjectType | InteriorObjectType | StaticShapeObjectType, + &collision) == true) + { + F32 veclen = vec.len(); + F32 adj = (-mDot(vec, collision.normal) / veclen) * 0.1; + F32 newPos = getMax(0.0f, collision.t - adj); + if (newPos == 0.0f) + eye.getColumn(3,&camPos); + else + camPos = sp + (vec * newPos); + } + if (isMounted()) + getObjectMount()->enableCollision(); + enableCollision(); + + + VectorF x,y(vecCam),z(0,0,1); + y.normalize(); + mCross(y,z,&x); + x.normalize(); + mCross(x,y,&z); + z.normalize(); + + mat->identity(); + mat->setColumn(0,x); + mat->setColumn(1,y); + mat->setColumn(2,z); + + mat->setColumn(3,camPos); +} +// -------------------------------------------------------------------------------------------- +// Console Functions +// -------------------------------------------------------------------------------------------- + +ConsoleMethod( AIPlayer, stop, void, 2, 2, "()" + "Stop moving.") +{ + object->stopMove(); +} + +ConsoleMethod( AIPlayer, clearAim, void, 2, 2, "()" + "Stop aiming at anything.") +{ + object->clearAim(); +} + +ConsoleMethod( AIPlayer, setMoveSpeed, void, 3, 3, "( float speed )" + "Sets the move speed for an AI object.") +{ + object->setMoveSpeed( dAtof( argv[2] ) ); +} + +ConsoleMethod( AIPlayer, setMoveDestination, void, 3, 4, "(Point3F goal, bool slowDown=true)" + "Tells the AI to move to the location provided.") +{ + Point3F v( 0.0f, 0.0f, 0.0f ); + dSscanf( argv[2], "%g %g %g", &v.x, &v.y, &v.z ); + bool slowdown = (argc > 3)? dAtob(argv[3]): true; + object->setMoveDestination( v, slowdown); +} + +ConsoleMethod( AIPlayer, getMoveDestination, const char *, 2, 2, "()" + "Returns the point the AI is set to move to.") +{ + Point3F movePoint = object->getMoveDestination(); + + char *returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%g %g %g", movePoint.x, movePoint.y, movePoint.z ); + + return returnBuffer; +} + +ConsoleMethod( AIPlayer, setAimLocation, void, 3, 3, "( Point3F target )" + "Tells the AI to aim at the location provided.") +{ + Point3F v( 0.0f,0.0f,0.0f ); + dSscanf( argv[2], "%g %g %g", &v.x, &v.y, &v.z ); + + object->setAimLocation( v ); +} + +ConsoleMethod( AIPlayer, getAimLocation, const char *, 2, 2, "()" + "Returns the point the AI is aiming at.") +{ + Point3F aimPoint = object->getAimLocation(); + + char *returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%g %g %g", aimPoint.x, aimPoint.y, aimPoint.z ); + + return returnBuffer; +} + +ConsoleMethod( AIPlayer, setAimObject, void, 3, 4, "( GameBase obj, [Point3F offset] )" + "Sets the bot's target object. Optionally set an offset from target location.") +{ + Point3F off( 0.0f, 0.0f, 0.0f ); + + // Find the target + GameBase *targetObject; + if( Sim::findObject( argv[2], targetObject ) ) + { + if (argc == 4) + dSscanf( argv[3], "%g %g %g", &off.x, &off.y, &off.z ); + + object->setAimObject( targetObject, off ); + } + else + object->setAimObject( 0, off ); +} + +ConsoleMethod( AIPlayer, getAimObject, S32, 2, 2, "()" + "Gets the object the AI is targeting.") +{ + GameBase* obj = object->getAimObject(); + return obj? obj->getId(): -1; +} + diff --git a/T3D/aiPlayer.h b/T3D/aiPlayer.h new file mode 100644 index 0000000..2887b49 --- /dev/null +++ b/T3D/aiPlayer.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AIPLAYER_H_ +#define _AIPLAYER_H_ + +#ifndef _PLAYER_H_ +#include "T3D/player.h" +#endif + + +class AIPlayer : public Player { + + typedef Player Parent; + +public: + enum MoveState { + ModeStop, + ModeMove, + ModeStuck, + }; + +private: + MoveState mMoveState; + F32 mMoveSpeed; + F32 mMoveTolerance; // Distance from destination before we stop + Point3F mMoveDestination; // Destination for movement + Point3F mLastLocation; // For stuck check + bool mMoveSlowdown; // Slowdown as we near the destination + + SimObjectPtr mAimObject; // Object to point at, overrides location + bool mAimLocationSet; // Has an aim location been set? + Point3F mAimLocation; // Point to look at + bool mTargetInLOS; // Is target object visible? + + Point3F mAimOffset; + + // Utility Methods + void throwCallback( const char *name ); +public: + DECLARE_CONOBJECT( AIPlayer ); + + AIPlayer(); + ~AIPlayer(); + + virtual bool getAIMove( Move *move ); + + // Targeting and aiming sets/gets + void setAimObject( GameBase *targetObject ); + void setAimObject( GameBase *targetObject, Point3F offset ); + GameBase* getAimObject() const { return mAimObject; } + void setAimLocation( const Point3F &location ); + Point3F getAimLocation() const { return mAimLocation; } + void clearAim(); + + // Movement sets/gets + void setMoveSpeed( const F32 speed ); + F32 getMoveSpeed() const { return mMoveSpeed; } + void setMoveTolerance( const F32 tolerance ); + F32 getMoveTolerance() const { return mMoveTolerance; } + void setMoveDestination( const Point3F &location, bool slowdown ); + Point3F getMoveDestination() const { return mMoveDestination; } + void stopMove(); + + void preprocessMove(Move * mv); + void getCameraTransform(F32* pos,MatrixF* mat); + void setControlByKey(bool val); + void faceToCamera(bool faceto = true); + void backToCamera(); +protected: + Point3F m_ptCamRot; + F32 m_fCamDistance; + F32 m_fCamDistanceToReach; + bool m_bControlByKey; +}; + +#endif diff --git a/T3D/ambientAudioManager.cpp b/T3D/ambientAudioManager.cpp new file mode 100644 index 0000000..6f1747e --- /dev/null +++ b/T3D/ambientAudioManager.cpp @@ -0,0 +1,260 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#if 0 +#include "T3D/ambientAudioManager.h" +#include "interior/interiorInstance.h" +#include "T3D/gameConnection.h" +#include "interior/interior.h" +#include "environment/waterBlock.h" +#include "sceneGraph/sceneGraph.h" +#include "sfx/sfxProfile.h" + +AmbientAudioManager gAmbientAudioManager; + +//------------------------------------------------------------------------------ +AmbientAudioManager::AmbientAudioManager() +{ + mOutsideScale = 1.f; + mInteriorAudioHandle = NULL_AUDIOHANDLE; + mPowerAudioHandle = NULL_AUDIOHANDLE; + mLastAlarmState = false; +} + +//------------------------------------------------------------------------------ +ConsoleFunction(setPowerAudioProfiles, void, 3, 3, "( SFXProfile powerUp, SFXProfile powerDown)" + "Set the ambient audio manager's power up/down profiles.") +{ + gAmbientAudioManager.mPowerUpProfile = dynamic_cast(Sim::findObject(argv[1])); + gAmbientAudioManager.mPowerDownProfile = dynamic_cast(Sim::findObject(argv[2])); +} + +void AmbientAudioManager::addEmitter(AudioEmitter * emitter) +{ + mEmitters.push_back(emitter); + updateEmitter(emitter); +} + +void AmbientAudioManager::removeEmitter(AudioEmitter * emitter) +{ + for(U32 i = 0; i < mEmitters.size(); i++) + if(mEmitters[i] == emitter) + { + mEmitters.erase_fast(i); + return; + } +} + +bool AmbientAudioManager::getOutsideScale(F32 * pScale, InteriorInstance ** pInterior) +{ + GameConnection * gc = dynamic_cast(NetConnection::getConnectionToServer()); + if(!gc) + return(false); + + MatrixF camMat; + if(!gc->getControlCameraTransform(0.032, &camMat)) + return(false); + + Point3F pos; + camMat.getColumn(3, &pos); + + RayInfo collision; + if(!gClientContainer.castRay(pos, Point3F(pos.x, pos.y, pos.z - 2000.f), InteriorObjectType, &collision)) + { + *pScale = 1.f; + *pInterior = 0; + return(true); + } + + // could have hit a null face.. dont change anything + if(collision.face == -1) + { + *pScale = mOutsideScale; + *pInterior = static_cast(mInteriorInstance); + return(true); + } + + InteriorInstance * interior = dynamic_cast(collision.object); + if(!interior) + { + Con::errorf(ConsoleLogEntry::General, "AmbientAudioManager::getOutsideScale: invalid interior on collision"); + return(false); + } + + // 'inside'? + if(!interior->getDetailLevel(0)->isSurfaceOutsideVisible(collision.face)) + { + // how 'inside' are we? + F32 insideScale = 1.f; + interior->getPointInsideScale(pos, &insideScale); + + // desire 'outside' scale... + *pScale = 1.f - insideScale; + *pInterior = interior; + } + else + { + *pScale = 1.f; + *pInterior = 0; + } + + return(true); +} + +void AmbientAudioManager::stopInteriorAudio() +{ + if(mInteriorAudioHandle != NULL_AUDIOHANDLE) + alxStop(mInteriorAudioHandle); + if(mPowerAudioHandle != NULL_AUDIOHANDLE) + alxStop(mPowerAudioHandle); + + mInteriorInstance = 0; + mInteriorAudioHandle = mPowerAudioHandle = NULL_AUDIOHANDLE; +} + +//--------------------------------------------------------------------------- +void AmbientAudioManager::update() +{ + if(!bool(mInteriorInstance) && (mInteriorAudioHandle != NULL_AUDIOHANDLE)) + stopInteriorAudio(); + + F32 scale = mOutsideScale; + InteriorInstance * interior = 0; + + if(!getOutsideScale(&scale, &interior)) + stopInteriorAudio(); + + // handle the interior sound... + if(interior) + { + if(bool(mInteriorInstance) && (interior != static_cast(mInteriorInstance))) + stopInteriorAudio(); + + // update + if(bool(mInteriorInstance)) + { + SFXProfile * profile = interior->getAudioProfile(); + if(profile) + { + if(mLastAlarmState ^ mInteriorInstance->inAlarmState()) + { + if(mLastAlarmState) + { + mLastAlarmState = false; + + // play powerup + if(bool(mPowerUpProfile)) + mPowerAudioHandle = alxPlay(mPowerUpProfile, &mInteriorInstance->getTransform()); + } + else + { + alxStop(mInteriorAudioHandle); + mInteriorAudioHandle = NULL_AUDIOHANDLE; + mLastAlarmState = true; + + // play powerdown + if(bool(mPowerDownProfile)) + mPowerAudioHandle = alxPlay(mPowerDownProfile, &mInteriorInstance->getTransform()); + } + } + + // play the ambient noise when the powerup sound is done + if((mInteriorAudioHandle == NULL_AUDIOHANDLE) && (mPowerAudioHandle == NULL_AUDIOHANDLE) + && (mLastAlarmState == false)) + mInteriorAudioHandle = alxPlay(profile, &mInteriorInstance->getTransform()); + + if(mInteriorAudioHandle != NULL_AUDIOHANDLE) + alxSourcef(mInteriorAudioHandle, AL_GAIN_LINEAR, 1.f - scale); + + if(mPowerAudioHandle != NULL_AUDIOHANDLE) + { + if(alxIsPlaying(mPowerAudioHandle)) + alxSourcef(mPowerAudioHandle, AL_GAIN_LINEAR, 1.f - scale); + else + mPowerAudioHandle = NULL_AUDIOHANDLE; + } + } + } + else // start + { + mInteriorInstance = interior; + mLastAlarmState = mInteriorInstance->inAlarmState(); + + if(!mLastAlarmState) + { + SFXProfile * profile = interior->getAudioProfile(); + if(profile) + mInteriorAudioHandle = alxPlay(profile, &mInteriorInstance->getTransform()); + } + else + mInteriorAudioHandle = NULL_AUDIOHANDLE; + } + } + else + stopInteriorAudio(); + + updateEnvironment(); + if(scale == mOutsideScale) + return; + + mOutsideScale = scale; + + // do all the outside sounds + for(U32 i = 0; i < mEmitters.size(); i++) + updateEmitter(mEmitters[i]); + +} + +void AmbientAudioManager::updateEnvironment() +{ + F32 scale = 0.f; + + // inside? + if(bool(mInteriorInstance)) + { + mCurrentEnvironment = mInteriorInstance->getAudioEnvironment(); + scale = 1.f - mOutsideScale; + } + else + { + mCurrentEnvironment = 0; + + Point3F pos; + alxGetListenerPoint3F(AL_POSITION, &pos); + + // check if submerged + SimpleQueryList sql; + gClientSceneGraph->getWaterObjectList(sql); + + // grab the audio environment from the waterblock + for(U32 i = 0; i < sql.mList.size(); i++) + { + WaterBlock * wBlock = dynamic_cast(sql.mList[i]); + if(wBlock && wBlock->isPointSubmerged(pos)) + { + mCurrentEnvironment = wBlock->getAudioEnvironment(); + scale = 1.f; + break; + } + } + } + + if(mCurrentEnvironment != alxGetEnvironment()) + alxSetEnvironment(mCurrentEnvironment); + + if(mEnvironmentScale != scale) + { + mEnvironmentScale = scale; +#pragma message("todo") /* + alxEnvironmentf(AL_ENV_EFFECT_VOLUME_EXT, mEnvironmentScale); +*/ + } +} + +void AmbientAudioManager::updateEmitter(AudioEmitter * emitter) +{ + if(emitter->mOutsideAmbient && (emitter->mAudioHandle != NULL_AUDIOHANDLE)) + alxSourcef(emitter->mAudioHandle, AL_GAIN_LINEAR, emitter->mDescription.mVolume * mOutsideScale); +} +#endif \ No newline at end of file diff --git a/T3D/ambientAudioManager.h b/T3D/ambientAudioManager.h new file mode 100644 index 0000000..2bf1ad6 --- /dev/null +++ b/T3D/ambientAudioManager.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#if 0 +#ifndef _AMBIENTAUDIOMANAGER_H_ +#define _AMBIENTAUDIOMANAGER_H_ + +#ifndef _AUDIOEMITTER_H_ +#include "T3D/audioEmitter.h" +#endif + +class InteriorInstance; + +/// The AmbientAudioManager manages varying the properties of audio emitters +/// based on the player's position. +/// +/// It not only provides a notion of "outside"-ness and "inside"-ness to Torque's +/// sound library, but it also varies sounds based on the powered status of interiors, +/// and plays the PowerUp/PowerDown sounds as needed. +/// +/// AudioEmitters automatically add themselves to the AmbientAudioManager, see +/// AudioEmitter::onAdd() and AudioEmitter::onRemove(). update() is called in +/// clientProcess(), and gAmbientAudioManager stores the global reference to the +/// AmbientAudioManager. +class AmbientAudioManager +{ + + private: + F32 mOutsideScale; ///< 0:inside -> 1:outside + Vector mEmitters; + SimObjectPtr mInteriorInstance; + + SimObjectPtr mCurrentEnvironment; + F32 mEnvironmentScale; + + SFXSource *mInteriorAudioHandle; + SFXSource *mPowerAudioHandle; + bool mLastAlarmState; + + bool getOutsideScale(F32 *, InteriorInstance **); + void updateEnvironment(); + void updateEmitter(AudioEmitter *); + void stopInteriorAudio(); + + public: + SimObjectPtr mPowerUpProfile; + SimObjectPtr mPowerDownProfile; + + AmbientAudioManager(); + + void addEmitter(AudioEmitter*); + void removeEmitter(AudioEmitter*); + void update(); +}; + +extern AmbientAudioManager gAmbientAudioManager; + +#endif +#endif \ No newline at end of file diff --git a/T3D/camera.cpp b/T3D/camera.cpp new file mode 100644 index 0000000..e84b6c5 --- /dev/null +++ b/T3D/camera.cpp @@ -0,0 +1,1724 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "app/game.h" +#include "math/mMath.h" +#include "core/stream/bitStream.h" +#include "T3D/fx/cameraFXMgr.h" +#include "T3D/camera.h" +#include "T3D/gameConnection.h" +#include "math/mathIO.h" +#include "gui/worldEditor/editor.h" +#include "console/consoleTypes.h" +#include "math/mathUtils.h" + +#define MaxPitch 1.5706f +#define CameraRadius 0.05f; + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(CameraData); + +void CameraData::initPersistFields() +{ + Parent::initPersistFields(); +} + +void CameraData::packData(BitStream* stream) +{ + Parent::packData(stream); +} + +void CameraData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); +} + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(Camera); +F32 Camera::mMovementSpeed = 40.0f; + +Camera::Camera() +{ + mNetFlags.clear(Ghostable); + mTypeMask |= CameraObjectType; + delta.pos = Point3F(0.0f, 0.0f, 100.0f); + delta.rot = Point3F(0.0f, 0.0f, 0.0f); + delta.posVec = delta.rotVec = VectorF(0.0f, 0.0f, 0.0f); + mObjToWorld.setColumn(3, delta.pos); + mRot = delta.rot; + + mOffset.set(0.0f, 0.0f, 0.0f); + + mMinOrbitDist = 0.0f; + mMaxOrbitDist = 0.0f; + mCurOrbitDist = 0.0f; + mOrbitObject = NULL; + mPosition.set(0.0f, 0.0f, 0.0f); + mObservingClientObject = false; + mode = FlyMode; + + // For NewtonFlyMode + mNewtonRotation = false; + mAngularVelocity.set(0.0f, 0.0f, 0.0f); + mAngularForce = 100.0f; + mAngularDrag = 2.0f; + mVelocity.set(0.0f, 0.0f, 0.0f); + mNewtonMode = false; + mMass = 10.0f; + mDrag = 2.0; + mFlyForce = 500.0f; + mSpeedMultiplier = 2.0f; + mBrakeMultiplier = 2.0f; + + // For EditOrbitMode + mValidEditOrbitPoint = false; + mEditOrbitPoint.set(0.0f, 0.0f, 0.0f); + mCurrentEditOrbitDist = 2.0; + + mLocked = false; +} + +Camera::~Camera() +{ +} + + +//---------------------------------------------------------------------------- + +bool Camera::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.maxExtents = mObjScale; + mObjBox.minExtents = mObjScale; + mObjBox.minExtents.neg(); + resetWorldBox(); + + if(isClientObject()) + gClientContainer.addObject(this); + else + gServerContainer.addObject(this); + + // addToScene(); + return true; +} + +void Camera::onEditorEnable() +{ + mNetFlags.set(Ghostable); +} + +void Camera::onEditorDisable() +{ + mNetFlags.clear(Ghostable); +} + +void Camera::onRemove() +{ +// removeFromScene(); + if (getContainer()) + getContainer()->removeObject(this); + + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- +// check if the object needs to be observed through its own camera... +void Camera::getCameraTransform(F32* pos, MatrixF* mat) +{ + // The camera doesn't support a third person mode, + // so we want to override the default ShapeBase behavior. + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + obj->getCameraTransform(pos, mat); + else + getRenderEyeTransform(mat); + + // Apply Camera FX. + mat->mul( gCamFXMgr.getTrans() ); +} + +F32 Camera::getCameraFov() +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + return(obj->getCameraFov()); + else + return(Parent::getCameraFov()); +} + +F32 Camera::getDefaultCameraFov() +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + return(obj->getDefaultCameraFov()); + else + return(Parent::getDefaultCameraFov()); +} + +bool Camera::isValidCameraFov(F32 fov) +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + return(obj->isValidCameraFov(fov)); + else + return(Parent::isValidCameraFov(fov)); +} + +void Camera::setCameraFov(F32 fov) +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + obj->setCameraFov(fov); + else + Parent::setCameraFov(fov); +} + +//---------------------------------------------------------------------------- +void Camera::processTick(const Move* move) +{ + Parent::processTick(move); + + if ( isMounted() ) + { + // Fetch Mount Transform. + MatrixF mat; + mMount.object->getMountTransform( mMount.node, &mat ); + + if ( isClientObject() ) + { + delta.rotVec = mRot; + mObjToWorld.getColumn( 3, &delta.posVec ); + } + + // Apply. + setTransform( mat ); + + if ( isClientObject() ) + { + delta.pos = mat.getPosition(); + delta.rot = mRot; + delta.posVec = delta.posVec - delta.pos; + delta.rotVec = delta.rotVec - delta.rot; + } + + // Update Container. + updateContainer(); + return; + } + + Point3F vec,pos; + if (move) + { + bool strafeMode = move->trigger[2]; + + // If using editor then force camera into fly mode, unless using EditOrbitMode + if(gEditingMission && mode != FlyMode && mode != EditOrbitMode) + setFlyMode(); + + // Massage the mode if we're in EditOrbitMode + CameraMode virtualMode = mode; + if(mode == EditOrbitMode) + { + if(!mValidEditOrbitPoint) + { + virtualMode = FlyMode; + } + else + { + // Reset any Newton camera velocities for when we switch + // out of EditOrbitMode. + mNewtonRotation = false; + mVelocity.set(0.0f, 0.0f, 0.0f); + mAngularVelocity.set(0.0f, 0.0f, 0.0f); + } + } + + // Update orientation + delta.rotVec = mRot; + + VectorF rotVec(0, 0, 0); + + // process input/determine rotation vector + if(virtualMode != StationaryMode && + virtualMode != TrackObjectMode && + (!mLocked || virtualMode != OrbitObjectMode && virtualMode != OrbitPointMode)) + { + if(!strafeMode) + { + rotVec.x = move->pitch; + rotVec.z = move->yaw; + } + } + else if(virtualMode == TrackObjectMode && bool(mOrbitObject)) + { + // orient the camera to face the object + Point3F objPos; + // If this is a shapebase, use its render eye transform + // to avoid jittering. + ShapeBase *shape = dynamic_cast((GameBase*)mOrbitObject); + if( shape != NULL ) + { + MatrixF ret; + shape->getRenderEyeTransform( &ret ); + objPos = ret.getPosition(); + } + else + { + mOrbitObject->getWorldBox().getCenter(&objPos); + } + mObjToWorld.getColumn(3,&pos); + vec = objPos - pos; + vec.normalizeSafe(); + F32 pitch, yaw; + MathUtils::getAnglesFromVector(vec, yaw, pitch); + rotVec.x = -pitch - mRot.x; + rotVec.z = yaw - mRot.z; + if(rotVec.z > M_PI_F) + rotVec.z -= M_2PI_F; + else if(rotVec.z < -M_PI_F) + rotVec.z += M_2PI_F; + } + + // apply rotation vector according to physics rules + if(mNewtonRotation) + { + const F32 force = mAngularForce; + const F32 drag = mAngularDrag; + + VectorF acc(0.0f, 0.0f, 0.0f); + + rotVec.x *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script + rotVec.z *= 2.0f; // Assume that our -2PI to 2PI range was clamped to -PI to PI in script + + F32 rotVecL = rotVec.len(); + if(rotVecL > 0) + { + acc = (rotVec * force / mMass) * TickSec; + } + + // Accelerate + mAngularVelocity += acc; + + // Drag + mAngularVelocity -= mAngularVelocity * drag * TickSec; + + // Rotate + mRot += mAngularVelocity * TickSec; + if(mRot.x > MaxPitch) + mRot.x = MaxPitch; + else if(mRot.x < -MaxPitch) + mRot.x = -MaxPitch; + } + else + { + mRot.x += rotVec.x; + mRot.z += rotVec.z; + if(mRot.x > MaxPitch) + mRot.x = MaxPitch; + else if(mRot.x < -MaxPitch) + mRot.x = -MaxPitch; + } + + // Update position + VectorF posVec(0, 0, 0); + bool mustValidateEyePoint = false; + bool serverInterpolate = false; + + // process input/determine translation vector + if(virtualMode == OrbitObjectMode || virtualMode == OrbitPointMode) + { + pos = delta.pos; + if(virtualMode == OrbitObjectMode && bool(mOrbitObject)) + { + // If this is a shapebase, use its render eye transform + // to avoid jittering. + GameBase *castObj = mOrbitObject; + ShapeBase* shape = dynamic_cast(castObj); + if( shape != NULL ) + { + MatrixF ret; + shape->getRenderEyeTransform( &ret ); + mPosition = ret.getPosition(); + } + else + { + // Hopefully this is a static object that doesn't move, + // because the worldbox doesn't get updated between ticks. + mOrbitObject->getWorldBox().getCenter(&mPosition); + } + } + + posVec = (mPosition + mOffset) - pos; + mustValidateEyePoint = true; + serverInterpolate = mNewtonMode; + } + else if(virtualMode == EditOrbitMode && mValidEditOrbitPoint) + { + bool faster = move->trigger[0] || move->trigger[1]; + F32 scale = mMovementSpeed * (faster + 1); + mCurrentEditOrbitDist -= move->y * TickSec * scale; + mCurrentEditOrbitDist -= move->roll * TickSec * scale; // roll will be -Pi to Pi and we'll attempt to scale it here to be in line with the move->y calculation above + if(mCurrentEditOrbitDist < 0.0f) + mCurrentEditOrbitDist = 0.0f; + + mPosition = mEditOrbitPoint; + setPosition(mPosition, mRot); + calcEditOrbitPoint(&mObjToWorld, mRot); + pos = mPosition; + } + else if(virtualMode == FlyMode) + { + bool faster = move->trigger[0] || move->trigger[1]; + F32 scale = mMovementSpeed * (faster + 1); + + mObjToWorld.getColumn(3,&pos); + mObjToWorld.getColumn(0,&vec); + posVec = vec * move->x * TickSec * scale + vec * (strafeMode ? move->yaw * 2.0f * TickSec * scale : 0.0f); + mObjToWorld.getColumn(1,&vec); + posVec += vec * move->y * TickSec * scale + vec * move->roll * TickSec * scale; + mObjToWorld.getColumn(2,&vec); + posVec += vec * move->z * TickSec * scale - vec * (strafeMode ? move->pitch * 2.0f * TickSec * scale : 0.0f); + } + else if(virtualMode == OverheadMode) + { + bool faster = move->trigger[0] || move->trigger[1]; + F32 scale = mMovementSpeed * (faster + 1); + + mObjToWorld.getColumn(3,&pos); + mObjToWorld.getColumn(0,&vec); + vec = vec * move->x * TickSec * scale + (strafeMode ? vec * move->yaw * 2.0f * TickSec * scale : Point3F(0, 0, 0)); + vec.z = 0; + vec.normalizeSafe(); + posVec = vec; + mObjToWorld.getColumn(2,&vec); + vec = vec * move->y * TickSec * scale - (strafeMode ? vec * move->pitch * 2.0f * TickSec * scale : Point3F(0, 0, 0)); + vec.z = 0; + vec.normalizeSafe(); + posVec += vec; + posVec.z += move->z * TickSec * scale + move->roll * TickSec * scale; + } + else // ignore input + { + mObjToWorld.getColumn(3,&pos); + } + + // apply translation vector according to physics rules + delta.posVec = pos; + if(mNewtonMode) + { + bool faster = move->trigger[0]; + bool brake = move->trigger[1]; + + const F32 movementSpeedMultiplier = mMovementSpeed / 40.0f; // Using the starting value as the base + const F32 force = faster ? mFlyForce * movementSpeedMultiplier * mSpeedMultiplier : mFlyForce * movementSpeedMultiplier; + const F32 drag = brake ? mDrag * mBrakeMultiplier : mDrag; + + VectorF acc(0.0f, 0.0f, 0.0f); + + F32 posVecL = posVec.len(); + if(posVecL > 0) + { + acc = (posVec * force / mMass) * TickSec; + } + + // Accelerate + mVelocity += acc; + + // Drag + mVelocity -= mVelocity * drag * TickSec; + + // Move + pos += mVelocity * TickSec; + } + else + { + pos += posVec; + } + + setPosition(pos,mRot); + + // If on the client, calc delta for backstepping + if (serverInterpolate || isClientObject()) + { + delta.pos = pos; + delta.rot = mRot; + delta.posVec = delta.posVec - delta.pos; + delta.rotVec = delta.rotVec - delta.rot; + } + + if(mustValidateEyePoint) + validateEyePoint(1.0f, &mObjToWorld); + + setMaskBits(MoveMask); + } + + if(getControllingClient() && mContainer) + updateContainer(); +} + +void Camera::onDeleteNotify(SimObject *obj) +{ + Parent::onDeleteNotify(obj); + if (obj == (SimObject*)mOrbitObject) + { + mOrbitObject = NULL; + + if(mode == OrbitObjectMode) + mode = OrbitPointMode; + } +} + +void Camera::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); + + if ( isMounted() ) + { + // Fetch Mount Transform. + MatrixF mat; + mMount.object->getMountTransform( mMount.node, &mat ); + + // Apply. + setTransform( mat ); + + return; + } + + Point3F rot = delta.rot + delta.rotVec * dt; + + if((mode == OrbitObjectMode || mode == OrbitPointMode) && !mNewtonMode) + { + if(mode == OrbitObjectMode && bool(mOrbitObject)) + { + // If this is a shapebase, use its render eye transform + // to avoid jittering. + GameBase *castObj = mOrbitObject; + ShapeBase* shape = dynamic_cast(castObj); + if( shape != NULL ) + { + MatrixF ret; + shape->getRenderEyeTransform( &ret ); + mPosition = ret.getPosition(); + } + else + { + // Hopefully this is a static object that doesn't move, + // because the worldbox doesn't get updated between ticks. + mOrbitObject->getWorldBox().getCenter(&mPosition); + } + } + setRenderPosition(mPosition + mOffset, rot); + validateEyePoint(1.0f, &mRenderObjToWorld); + } + else if(mode == EditOrbitMode && mValidEditOrbitPoint) + { + mPosition = mEditOrbitPoint; + setRenderPosition(mPosition, rot); + calcEditOrbitPoint(&mRenderObjToWorld, rot); + } + else if(mode == TrackObjectMode && bool(mOrbitObject) && !mNewtonRotation) + { + // orient the camera to face the object + Point3F objPos; + // If this is a shapebase, use its render eye transform + // to avoid jittering. + ShapeBase *shape = dynamic_cast((GameBase*)mOrbitObject); + if( shape != NULL ) + { + MatrixF ret; + shape->getRenderEyeTransform( &ret ); + objPos = ret.getPosition(); + } + else + { + mOrbitObject->getWorldBox().getCenter(&objPos); + } + Point3F pos = delta.pos + delta.posVec * dt; + Point3F vec = objPos - pos; + vec.normalizeSafe(); + F32 pitch, yaw; + MathUtils::getAnglesFromVector(vec, yaw, pitch); + rot.x = -pitch; + rot.z = yaw; + setRenderPosition(pos, rot); + } + else + { + Point3F pos = delta.pos + delta.posVec * dt; + setRenderPosition(pos,rot); + if(mode == OrbitObjectMode || mode == OrbitPointMode) + validateEyePoint(1.0f, &mRenderObjToWorld); + } +} + +void Camera::setPosition(const Point3F& pos, const Point3F& rot) +{ + MatrixF xRot, zRot; + xRot.set(EulerF(rot.x, 0.0f, 0.0f)); + zRot.set(EulerF(0.0f, 0.0f, rot.z)); + + MatrixF temp; + temp.mul(zRot, xRot); + temp.setColumn(3, pos); + Parent::setTransform(temp); + mRot = rot; +} + +void Camera::setRenderPosition(const Point3F& pos,const Point3F& rot) +{ + MatrixF xRot, zRot; + xRot.set(EulerF(rot.x, 0, 0)); + zRot.set(EulerF(0, 0, rot.z)); + MatrixF temp; + temp.mul(zRot, xRot); + temp.setColumn(3, pos); + Parent::setRenderTransform(temp); +} + +//---------------------------------------------------------------------------- + +void Camera::writePacketData(GameConnection *connection, BitStream *bstream) +{ + // Update client regardless of status flags. + Parent::writePacketData(connection, bstream); + + Point3F pos; + mObjToWorld.getColumn(3,&pos); + bstream->setCompressionPoint(pos); + mathWrite(*bstream, pos); + bstream->write(mRot.x); + bstream->write(mRot.z); + + U32 writeMode = mode; + Point3F writePos = mPosition; + S32 gIndex = -1; + if(mode == OrbitObjectMode) + { + gIndex = bool(mOrbitObject) ? connection->getGhostIndex(mOrbitObject): -1; + if(gIndex == -1) + { + writeMode = OrbitPointMode; + if(bool(mOrbitObject)) + mOrbitObject->getWorldBox().getCenter(&writePos); + } + } + else if(mode == TrackObjectMode) + { + gIndex = bool(mOrbitObject) ? connection->getGhostIndex(mOrbitObject): -1; + if(gIndex == -1) + writeMode = StationaryMode; + } + bstream->writeRangedU32(writeMode, CameraFirstMode, CameraLastMode); + + if (writeMode == OrbitObjectMode || writeMode == OrbitPointMode) + { + bstream->write(mMinOrbitDist); + bstream->write(mMaxOrbitDist); + bstream->write(mCurOrbitDist); + if(writeMode == OrbitObjectMode) + { + bstream->writeFlag(mObservingClientObject); + bstream->writeInt(gIndex, NetConnection::GhostIdBitSize); + } + if (writeMode == OrbitPointMode) + bstream->writeCompressedPoint(writePos); + } + else if(writeMode == TrackObjectMode) + { + bstream->writeInt(gIndex, NetConnection::GhostIdBitSize); + } + + if(bstream->writeFlag(mNewtonMode)) + { + bstream->write(mVelocity.x); + bstream->write(mVelocity.y); + bstream->write(mVelocity.z); + } + if(bstream->writeFlag(mNewtonRotation)) + { + bstream->write(mAngularVelocity.x); + bstream->write(mAngularVelocity.y); + bstream->write(mAngularVelocity.z); + } + + bstream->writeFlag(mValidEditOrbitPoint); + if(writeMode == EditOrbitMode) + { + bstream->write(mEditOrbitPoint.x); + bstream->write(mEditOrbitPoint.y); + bstream->write(mEditOrbitPoint.z); + bstream->write(mCurrentEditOrbitDist); + } +} + +void Camera::readPacketData(GameConnection *connection, BitStream *bstream) +{ + Parent::readPacketData(connection, bstream); + Point3F pos,rot; + mathRead(*bstream, &pos); + bstream->setCompressionPoint(pos); + bstream->read(&rot.x); + bstream->read(&rot.z); + + GameBase* obj = 0; + mode = (CameraMode)bstream->readRangedU32(CameraFirstMode, CameraLastMode); + mObservingClientObject = false; + if (mode == OrbitObjectMode || mode == OrbitPointMode) + { + bstream->read(&mMinOrbitDist); + bstream->read(&mMaxOrbitDist); + bstream->read(&mCurOrbitDist); + + if(mode == OrbitObjectMode) + { + mObservingClientObject = bstream->readFlag(); + S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); + obj = static_cast(connection->resolveGhost(gIndex)); + } + if (mode == OrbitPointMode) + bstream->readCompressedPoint(&mPosition); + } + else if (mode == TrackObjectMode) + { + S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); + obj = static_cast(connection->resolveGhost(gIndex)); + } + + if (obj != (GameBase*)mOrbitObject) + { + if (mOrbitObject) + { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + + mOrbitObject = obj; + if (mOrbitObject) + { + processAfter(mOrbitObject); + deleteNotify(mOrbitObject); + } + } + + mNewtonMode = bstream->readFlag(); + if(mNewtonMode) + { + bstream->read(&mVelocity.x); + bstream->read(&mVelocity.y); + bstream->read(&mVelocity.z); + } + + mNewtonRotation = bstream->readFlag(); + if(mNewtonRotation) + { + bstream->read(&mAngularVelocity.x); + bstream->read(&mAngularVelocity.y); + bstream->read(&mAngularVelocity.z); + } + + mValidEditOrbitPoint = bstream->readFlag(); + if(mode == EditOrbitMode) + { + bstream->read(&mEditOrbitPoint.x); + bstream->read(&mEditOrbitPoint.y); + bstream->read(&mEditOrbitPoint.z); + bstream->read(&mCurrentEditOrbitDist); + } + + setPosition(pos,rot); + // Movement in OrbitObjectMode is not input-based - don't reset interpolation + if(mode != OrbitObjectMode) + { + delta.pos = pos; + delta.posVec.set(0.0f, 0.0f, 0.0f); + delta.rot = rot; + delta.rotVec.set(0.0f, 0.0f, 0.0f); + } +} + +U32 Camera::packUpdate(NetConnection *con, U32 mask, BitStream *bstream) +{ + Parent::packUpdate(con, mask, bstream); + + if (bstream->writeFlag(mask & UpdateMask)) + { + bstream->writeFlag(mLocked); + mathWrite(*bstream, mOffset); + } + + if(bstream->writeFlag(mask & NewtonCameraMask)) + { + bstream->write(mAngularForce); + bstream->write(mAngularDrag); + bstream->write(mMass); + bstream->write(mDrag); + bstream->write(mFlyForce); + bstream->write(mSpeedMultiplier); + bstream->write(mBrakeMultiplier); + } + + if(bstream->writeFlag(mask & EditOrbitMask)) + { + bstream->write(mEditOrbitPoint.x); + bstream->write(mEditOrbitPoint.y); + bstream->write(mEditOrbitPoint.z); + bstream->write(mCurrentEditOrbitDist); + } + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + if(bstream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) + return 0; + + if (bstream->writeFlag(mask & MoveMask)) + { + Point3F pos; + mObjToWorld.getColumn(3,&pos); + bstream->write(pos.x); + bstream->write(pos.y); + bstream->write(pos.z); + bstream->write(mRot.x); + bstream->write(mRot.z); + + // Only required if in NewtonFlyMode + F32 len = mVelocity.len(); + if(bstream->writeFlag(mNewtonMode && len > 0.02f)) + { + Point3F outVel = mVelocity; + outVel *= 1.0f/len; + bstream->writeNormalVector(outVel, 10); + len *= 32.0f; // 5 bits of fraction + if(len > 8191) + len = 8191; + bstream->writeInt((S32)len, 13); + } + + // Rotation + len = mAngularVelocity.len(); + if(bstream->writeFlag(mNewtonRotation && len > 0.02f)) + { + Point3F outVel = mAngularVelocity; + outVel *= 1.0f/len; + bstream->writeNormalVector(outVel, 10); + len *= 32.0f; // 5 bits of fraction + if(len > 8191) + len = 8191; + bstream->writeInt((S32)len, 13); + } + } + + return 0; +} + +void Camera::unpackUpdate(NetConnection *con, BitStream *bstream) +{ + Parent::unpackUpdate(con,bstream); + + if (bstream->readFlag()) + { + mLocked = bstream->readFlag(); + mathRead(*bstream, &mOffset); + } + + // NewtonCameraMask + if(bstream->readFlag()) + { + bstream->read(&mAngularForce); + bstream->read(&mAngularDrag); + bstream->read(&mMass); + bstream->read(&mDrag); + bstream->read(&mFlyForce); + bstream->read(&mSpeedMultiplier); + bstream->read(&mBrakeMultiplier); + } + + // EditOrbitMask + if(bstream->readFlag()) + { + bstream->read(&mEditOrbitPoint.x); + bstream->read(&mEditOrbitPoint.y); + bstream->read(&mEditOrbitPoint.z); + bstream->read(&mCurrentEditOrbitDist); + } + + // controlled by the client? + if(bstream->readFlag()) + return; + + // MoveMask + if (bstream->readFlag()) + { + Point3F pos,rot; + bstream->read(&pos.x); + bstream->read(&pos.y); + bstream->read(&pos.z); + bstream->read(&rot.x); + bstream->read(&rot.z); + setPosition(pos,rot); + + // NewtonMode + if(bstream->readFlag()) + { + bstream->readNormalVector(&mVelocity, 10); + mVelocity *= bstream->readInt(13) / 32.0f; + } + + // NewtonRotation + mNewtonRotation = bstream->readFlag(); + if(mNewtonRotation) + { + bstream->readNormalVector(&mAngularVelocity, 10); + mAngularVelocity *= bstream->readInt(13) / 32.0f; + } + + if(mode != OrbitObjectMode) + { + // New delta for client side interpolation + delta.pos = pos; + delta.rot = rot; + delta.posVec = delta.rotVec = VectorF(0.0f, 0.0f, 0.0f); + } + } +} + + +//---------------------------------------------------------------------------- + +static EnumTable::Enums cameraTypeEnum[] = +{ + { Camera::StationaryMode, "Stationary" }, + { Camera::FreeRotateMode, "FreeRotate" }, + { Camera::FlyMode, "Fly" }, + { Camera::OrbitObjectMode, "OrbitObject" }, + { Camera::OrbitPointMode, "OrbitPoint" }, + { Camera::TrackObjectMode, "TrackObject" }, + { Camera::OverheadMode, "Overhead" }, + { Camera::EditOrbitMode, "EditOrbit" }, +}; + +static EnumTable gCameraTypeTable(Camera::CameraLastMode, &cameraTypeEnum[0]); + +void Camera::initPersistFields() +{ + addProtectedField("controlMode", TypeEnum, Offset(mode, Camera), &setMode, &defaultProtectedGetFn, 1, &gCameraTypeTable, + "The current camera control mode."); + + addGroup("Newton Mode"); + addField("newtonMode", TypeBool, Offset(mNewtonMode, Camera), + "Apply smoothing (acceleration) to camera movements."); + addField("newtonRotation", TypeBool, Offset(mNewtonRotation, Camera), + "Apply smoothing (acceleration) to camera rotations."); + addProtectedField("mass", TypeF32, Offset(mMass, Camera), &setNewtonProperty, &defaultProtectedGetFn, + "Camera mass."); + addProtectedField("drag", TypeF32, Offset(mDrag, Camera), &setNewtonProperty, &defaultProtectedGetFn, + "Drag on camera when moving."); + addProtectedField("force", TypeF32, Offset(mFlyForce, Camera), &setNewtonProperty, &defaultProtectedGetFn, + "Force on camera when moving."); + addProtectedField("angularDrag", TypeF32, Offset(mAngularDrag, Camera), &setNewtonProperty, &defaultProtectedGetFn, + "Drag on camera when rotating."); + addProtectedField("angularForce", TypeF32, Offset(mAngularForce, Camera), &setNewtonProperty, &defaultProtectedGetFn, + "Force on camera when rotating."); + addProtectedField("speedMultiplier", TypeF32, Offset(mSpeedMultiplier, Camera), &setNewtonProperty, &defaultProtectedGetFn, + "Speed multiplier when triggering the accelerator."); + addProtectedField("brakeMultiplier", TypeF32, Offset(mBrakeMultiplier, Camera), &setNewtonProperty, &defaultProtectedGetFn, + "Speed multiplier when triggering the brake."); + endGroup("Newton Mode"); + + Parent::initPersistFields(); +} + +void Camera::consoleInit() +{ + Con::addVariable("Camera::movementSpeed", TypeF32, &mMovementSpeed); +} + +bool Camera::setNewtonProperty(void *obj, const char *data) +{ + static_cast(obj)->setMaskBits(NewtonCameraMask); + return true; // ok to set value +} + +bool Camera::setMode(void *obj, const char *data) +{ + Camera *cam = static_cast(obj); + + if( dStricmp(data, "Fly") == 0 ) + { + cam->setFlyMode(); + return false; // already changed mode + } + + else if( dStricmp(data, "EditOrbit" ) == 0 ) + { + cam->setEditOrbitMode(); + return false; // already changed mode + } + + else if( (dStricmp(data, "OrbitObject") == 0 && cam->mode != OrbitObjectMode) || + (dStricmp(data, "TrackObject") == 0 && cam->mode != TrackObjectMode) || + (dStricmp(data, "OrbitPoint") == 0 && cam->mode != OrbitPointMode) ) + { + Con::warnf("Couldn't change Camera mode to %s: required information missing. Use camera.set%s().", data, data); + return false; // don't change the mode - not valid + } + + else if( dStricmp(data, "OrbitObject") != 0 && + dStricmp(data, "TrackObject") != 0 && + bool(cam->mOrbitObject) ) + { + cam->clearProcessAfter(); + cam->clearNotify(cam->mOrbitObject); + cam->mOrbitObject = NULL; + } + + // make sure the requested mode is supported, and set it + for( S32 i = (S32)CameraFirstMode; i <= CameraLastMode; i++ ) + { + if( dStricmp(data, cameraTypeEnum[i].label) == 0 ) + { + cam->mode = (CameraMode)cameraTypeEnum[i].index; + return false; + } + } + + Con::warnf("Unsupported camera mode: %s", data); + return false; +} + +Camera::CameraMode Camera::getMode() +{ + return mode; +} + +Point3F Camera::getPosition() +{ + static Point3F position; + mObjToWorld.getColumn(3, &position); + return position; +} + +ConsoleMethod( Camera, getMode, const char*, 2, 2, "()" + " - Returns the current camera control mode.\n\n") +{ + return cameraTypeEnum[object->getMode()].label; +} + +ConsoleMethod( Camera, getPosition, const char *, 2, 2, "()" + " - Get the position of the camera.\n\n" + "@returns A string of form \"x y z\".") +{ + static char buffer[256]; + + Point3F pos = object->getPosition(); + dSprintf(buffer, sizeof(buffer),"%g %g %g",pos.x,pos.y,pos.z); + return buffer; +} + +ConsoleMethod( Camera, getRotation, const char *, 2, 2, "()" + " - Get the euler rotation of the camera.\n\n" + "@returns A string of form \"x y z\".") +{ + static char buffer[256]; + + Point3F rot = object->getRotation(); + dSprintf(buffer, sizeof(buffer),"%g %g %g",rot.x,rot.y,rot.z); + return buffer; +} + +ConsoleMethod( Camera, getOffset, const char *, 2, 2, "()" + " - Get the offset for the camera.\n\n" + "@returns A string of form \"x y z\".") +{ + static char buffer[256]; + + Point3F offset = object->getOffset(); + dSprintf(buffer, sizeof(buffer),"%g %g %g",offset.x,offset.y,offset.z); + return buffer; +} + +ConsoleMethod( Camera, setOffset, void, 3, 3, "(Point3F offset)" + " - Set the offset for the camera.") +{ + Point3F offset(0.0f, 0.0f, 0.0f); + + dSscanf(argv[2],"%g %g %g", + &offset.x,&offset.y,&offset.z); + + object->setOffset(offset); +} + +ConsoleMethod( Camera, setOrbitMode, void, 7, 10, "(GameBase orbitObject, transform mat, float minDistance," + " float maxDistance, float curDistance, [bool ownClientObject = false], [Point3F offset], [bool locked = false])\n" + "Set the camera to orbit around some given object. If the object passed\n" + "is 0 or NULL, orbit around the point specified by mat.\n\n" + "@param orbitObject Object we want to orbit.\n" + "@param mat A set of fields: posX posY posZ rotX rotY rotZ\n" + "@param minDistance Minimum distance to keep from object.\n" + "@param maxDistance Maximum distance to keep from object.\n" + "@param curDistance Distance to set initially from object.\n" + "@param ownClientObj Are we observing an object owned by us?\n" + "@param offset An offset to add to our position\n" + "@param locked Camera doesn't receive inputs from player") +{ + Point3F pos; + Point3F rot; + Point3F offset(0.0f, 0.0f, 0.0f); + F32 minDis, maxDis, curDis; + bool locked = false; + + GameBase *orbitObject = NULL; + + // See if we have an orbit object + if ((dStricmp(argv[2], "NULL") == 0) || (dStricmp(argv[2], "0") == 0)) + orbitObject = NULL; + else + { + if(Sim::findObject(argv[2],orbitObject) == false) + { + Con::warnf("Cannot orbit non-existing object."); + object->setFlyMode(); + return; + } + } + + dSscanf(argv[3],"%g %g %g %g %g %g", + &pos.x,&pos.y,&pos.z,&rot.x,&rot.y,&rot.z); + minDis = dAtof(argv[4]); + maxDis = dAtof(argv[5]); + curDis = dAtof(argv[6]); + + if (argc == 9) + dSscanf(argv[8],"%g %g %g", + &offset.x,&offset.y,&offset.z); + + if (argc == 10) + locked = dAtob(argv[9]); + + object->setOrbitMode(orbitObject, pos, rot, offset, minDis, maxDis, curDis, (argc == 8) ? dAtob(argv[7]) : false, locked); +} + +ConsoleMethod( Camera, setOrbitObject, bool, 6, 10, "(GameBase orbitObject, vector rotation, float minDistance," + " float maxDistance, [float curDistance], [bool ownClientObject = false], [Point3F offset], [bool locked = false])\n" + "Set the camera to orbit around some given object.\n\n" + "@param orbitObject Object we want to orbit.\n" + "@param rotation Initial camera angles in radians (x, y, z).\n" + "@param minDistance Minimum distance to keep from object.\n" + "@param maxDistance Maximum distance to keep from object.\n" + "@param curDistance Initial distance from object (default=maxDistance).\n" + "@param ownClientObj Are we observing an object owned by us?\n" + "@param offset An offset to add to our position\n" + "@param locked Camera doesn't receive inputs from player") +{ + F32 minDis, maxDis, curDis; + GameBase *orbitObject = NULL; + Point3F offset(0.0f, 0.0f, 0.0f); + Point3F rot; + bool own = false; + bool locked = false; + + if(Sim::findObject(argv[2],orbitObject) == false) + { + Con::warnf("Cannot orbit non-existing object."); + object->setFlyMode(); + return false; + } + + dSscanf(argv[3],"%g %g %g", + &rot.x,&rot.y,&rot.z); + + minDis = dAtof(argv[4]); + curDis = maxDis = dAtof(argv[5]); + + if (argc >= 7) + curDis = dAtof(argv[6]); + + if (argc >= 8) + own = dAtob(argv[7]); + + if (argc >= 9) + dSscanf(argv[8],"%g %g %g", + &offset.x,&offset.y,&offset.z); + + if (argc >= 10) + locked = dAtob(argv[9]); + + object->setOrbitMode(orbitObject, Point3F(0,0,0), rot, offset, minDis, maxDis, curDis, own, locked); + return true; +} + +ConsoleMethod( Camera, setOrbitPoint, void, 5, 8, "(transform xform, float minDistance," + " float maxDistance, [float curDistance], [Point3F offset], [bool locked = false])\n" + "Set the camera to orbit around some given point.\n\n" + "@param xform A set of fields: posX posY posZ rotX rotY rotZ\n" + "@param minDistance Minimum distance to keep from point.\n" + "@param maxDistance Maximum distance to keep from point.\n" + "@param curDistance Initial distance from point (default=maxDistance).\n" + "@param offset An offset to add to our position\n" + "@param locked Camera doesn't receive inputs from player") +{ + F32 minDis, maxDis, curDis; + Point3F pos; + Point3F rot; + Point3F offset(0.0f, 0.0f, 0.0f); + bool locked = false; + + dSscanf(argv[2],"%g %g %g %g %g %g", + &pos.x,&pos.y,&pos.z,&rot.x,&rot.y,&rot.z); + + minDis = dAtof(argv[3]); + curDis = maxDis = dAtof(argv[4]); + + if (argc >= 6) + curDis = dAtof(argv[5]); + + if (argc >= 7) + dSscanf(argv[6],"%g %g %g", + &offset.x,&offset.y,&offset.z); + + if (argc >= 8) + locked = dAtob(argv[7]); + + object->setOrbitMode(NULL, pos, rot, offset, minDis, maxDis, curDis, false, locked); +} + +ConsoleMethod( Camera, setTrackObject, bool, 3, 4, "(GameBase object, [Point3F offset])\n" + " - Set the camera to track some given object.\n\n" + "@param object Object we want to track\n" + "@param offset An offset to add to our object\n") +{ + Point3F offset(0.0f, 0.0f, 0.0f); + GameBase *trackObject = NULL; + + if(Sim::findObject(argv[2],trackObject) == false) + { + Con::warnf("Cannot track non-existing object."); + object->setFlyMode(); + return false; + } + + if (argc >= 4) + dSscanf(argv[3],"%g %g %g", + &offset.x,&offset.y,&offset.z); + + object->setTrackObject(trackObject, offset); + return true; +} + +ConsoleMethod( Camera, setEditOrbitMode, void, 2, 2, "()" + " - Set the editor camera to orbit around some point.\n\n") +{ + object->setEditOrbitMode(); +} + +ConsoleMethod( Camera, setFlyMode, void, 2, 2, "()" + " - Set the camera to be able to fly freely.") +{ + object->setFlyMode(); +} + +ConsoleMethod( Camera, setNewtonFlyMode, void, 2, 2, "()" + " - Set the camera to be able to fly freely, but with ease-in and ease-out.") +{ + object->setNewtonFlyMode(); +} + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +// NEW Observer Code +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +void Camera::setFlyMode() +{ + mode = FlyMode; + + if (bool(mOrbitObject)) + { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = NULL; +} + +void Camera::setNewtonFlyMode() +{ + mNewtonMode = true; + setFlyMode(); +} + +void Camera::setOrbitMode(GameBase *obj, const Point3F &pos, const Point3F &rot, const Point3F& offset, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject, bool locked) +{ + mObservingClientObject = ownClientObject; + + if (bool(mOrbitObject)) + { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + + mOrbitObject = obj; + if(bool(mOrbitObject)) + { + processAfter(mOrbitObject); + deleteNotify(mOrbitObject); + mOrbitObject->getWorldBox().getCenter(&mPosition); + mode = OrbitObjectMode; + } + else + { + mode = OrbitPointMode; + mPosition = pos; + } + + setPosition(mPosition, rot); + + mMinOrbitDist = minDist; + mMaxOrbitDist = maxDist; + mCurOrbitDist = curDist; + + if (locked != mLocked || mOffset != offset) + { + mLocked = locked; + mOffset = offset; + setMaskBits(UpdateMask); + } +} + +void Camera::setTrackObject(GameBase *obj, const Point3F &offset) +{ + if(bool(mOrbitObject)) + { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = obj; + if(bool(mOrbitObject)) + { + processAfter(mOrbitObject); + deleteNotify(mOrbitObject); + } + + if (mOffset != offset) + { + mOffset = offset; + setMaskBits(UpdateMask); + } + mode = TrackObjectMode; +} + +void Camera::validateEyePoint(F32 pos, MatrixF *mat) +{ + if (pos != 0) + { + // Use the eye transform to orient the camera + Point3F dir; + mat->getColumn(1, &dir); + if (mMaxOrbitDist - mMinOrbitDist > 0.0f) + pos *= mMaxOrbitDist - mMinOrbitDist; + + // Use the camera node's pos. + Point3F startPos = getRenderPosition(); + Point3F endPos; + + // Make sure we don't extend the camera into anything solid + if(mOrbitObject) + mOrbitObject->disableCollision(); + disableCollision(); + RayInfo collision; + U32 mask = TerrainObjectType | + InteriorObjectType | + WaterObjectType | + StaticShapeObjectType | + PlayerObjectType | + ItemObjectType | + VehicleObjectType; + + Container* pContainer = isServerObject() ? &gServerContainer : &gClientContainer; + if (!pContainer->castRay(startPos, startPos - dir * 2.5 * pos, mask, &collision)) + endPos = startPos - dir * pos; + else + { + float dot = mDot(dir, collision.normal); + if (dot > 0.01f) + { + float colDist = mDot(startPos - collision.point, dir) - (1 / dot) * CameraRadius; + if (colDist > pos) + colDist = pos; + if (colDist < 0.0f) + colDist = 0.0f; + endPos = startPos - dir * colDist; + } + else + endPos = startPos - dir * pos; + } + mat->setColumn(3, endPos); + enableCollision(); + if(mOrbitObject) + mOrbitObject->enableCollision(); + } +} + +void Camera::setPosition(const Point3F& pos, const Point3F& rot, MatrixF *mat) +{ + MatrixF xRot, zRot; + xRot.set(EulerF(rot.x, 0.0f, 0.0f)); + zRot.set(EulerF(0.0f, 0.0f, rot.z)); + mat->mul(zRot, xRot); + mat->setColumn(3,pos); + mRot = rot; +} + +void Camera::setTransform(const MatrixF& mat) +{ + // This method should never be called on the client. + + // This currently converts all rotation in the mat into + // rotations around the z and x axis. + Point3F pos,vec; + mat.getColumn(1, &vec); + mat.getColumn(3, &pos); + Point3F rot(-mAtan2(vec.z, mSqrt(vec.x * vec.x + vec.y * vec.y)), 0.0f, -mAtan2(-vec.x, vec.y)); + setPosition(pos,rot); +} + +void Camera::setRenderTransform(const MatrixF& mat) +{ + // This method should never be called on the client. + + // This currently converts all rotation in the mat into + // rotations around the z and x axis. + Point3F pos,vec; + mat.getColumn(1,&vec); + mat.getColumn(3,&pos); + Point3F rot(-mAtan2(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)),0,-mAtan2(-vec.x,vec.y)); + setRenderPosition(pos,rot); +} + +F32 Camera::getDamageFlash() const +{ + if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject)) + { + const GameBase *castObj = mOrbitObject; + const ShapeBase* psb = dynamic_cast(castObj); + if (psb) + return psb->getDamageFlash(); + } + + return mDamageFlash; +} + +F32 Camera::getWhiteOut() const +{ + if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject)) + { + const GameBase *castObj = mOrbitObject; + const ShapeBase* psb = dynamic_cast(castObj); + if (psb) + return psb->getWhiteOut(); + } + + return mWhiteOut; +} + +//---------------------------------------------------------------------------- + +VectorF Camera::getVelocity() const +{ + return mVelocity; +} + +void Camera::setVelocity(const VectorF& vel) +{ + mVelocity = vel; + setMaskBits(MoveMask); +} + +VectorF Camera::getAngularVelocity() const +{ + return mAngularVelocity; +} + +void Camera::setAngularVelocity(const VectorF& vel) +{ + mAngularVelocity = vel; + setMaskBits(MoveMask); +} + +ConsoleMethod( Camera, isRotationDamped, bool, 2, 2, "()" + " - Is this a Newton Fly Mode camera with damped rotation?") +{ + return object->isRotationDamped(); +} + +ConsoleMethod( Camera, getAngularVelocity, const char *, 2, 2, "()" + " - Get the angular velocity of the camera.\n\n" + "@returns A string of form \"x y z\".") +{ + static char buffer[256]; + + VectorF vel = object->getAngularVelocity(); + dSprintf(buffer, sizeof(buffer),"%g %g %g",vel.x,vel.y,vel.z); + return buffer; +} + +ConsoleMethod( Camera, setAngularVelocity, void, 3, 3, "(VectorF velocity)" + " - Set the angular velocity for the camera.\n\n" + "@param velocity Angular velocity in the form of: velX, velY, velZ") +{ + VectorF vel(0.0f, 0.0f, 0.0f); + + dSscanf(argv[2],"%g %g %g", + &vel.x,&vel.y,&vel.z); + + object->setAngularVelocity(vel); +} + +ConsoleMethod( Camera, setAngularForce, void, 3, 3, "(F32)" + " - Angular force for Newton camera") +{ + object->setAngularForce(dAtof(argv[2])); +} + +ConsoleMethod( Camera, setAngularDrag, void, 3, 3, "(F32)" + " - Angular drag for Newton camera") +{ + object->setAngularDrag(dAtof(argv[2])); +} + +ConsoleMethod( Camera, setMass, void, 3, 3, "(F32)" + " - Mass of Newton camera") +{ + object->setMass(dAtof(argv[2])); +} + +ConsoleMethod( Camera, getVelocity, const char *, 2, 2, "()" + " - Get the velocity of the camera.\n\n" + "@returns A string of form \"x y z\".") +{ + static char buffer[256]; + + VectorF vel = object->getVelocity(); + dSprintf(buffer, sizeof(buffer),"%g %g %g",vel.x,vel.y,vel.z); + return buffer; +} + +ConsoleMethod( Camera, setVelocity, void, 3, 3, "(VectorF velocity)" + " - Set the velocity for the camera.\n\n" + "@param velocity Velocity in the form of: 'x y z'") +{ + VectorF vel(0.0f, 0.0f, 0.0f); + + dSscanf(argv[2],"%g %g %g", + &vel.x,&vel.y,&vel.z); + + object->setVelocity(vel); +} + +ConsoleMethod( Camera, setDrag, void, 3, 3, "(F32)" + " - Drag of Newton camera") +{ + object->setDrag(dAtof(argv[2])); +} + +ConsoleMethod( Camera, setFlyForce, void, 3, 3, "(F32)" + " - Force of Newton camera") +{ + object->setFlyForce(dAtof(argv[2])); +} + +ConsoleMethod( Camera, setSpeedMultiplier, void, 3, 3, "(F32)" + " - Newton camera speed multiplier when trigger[0] is active") +{ + object->setSpeedMultiplier(dAtof(argv[2])); +} + +ConsoleMethod( Camera, setBrakeMultiplier, void, 3, 3, "(F32)" + " - Newton camera brake multiplier when trigger[1] is active") +{ + object->setBrakeMultiplier(dAtof(argv[2])); +} + +//---------------------------------------------------------------------------- + +void Camera::setEditOrbitMode() +{ + mode = EditOrbitMode; + + if (bool(mOrbitObject)) + { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = NULL; + + // If there is a valid orbit point, then point to it + // rather than move the camera. + if(mValidEditOrbitPoint) + { + Point3F currentPos; + mObjToWorld.getColumn(3,¤tPos); + + Point3F dir = mEditOrbitPoint - currentPos; + mCurrentEditOrbitDist = dir.len(); + dir.normalize(); + + F32 yaw, pitch; + MathUtils::getAnglesFromVector(dir, yaw, pitch); + mRot.x = -pitch; + mRot.z = yaw; + } +} + +void Camera::calcEditOrbitPoint(MatrixF *mat, const Point3F& rot) +{ + //Point3F dir; + //mat->getColumn(1, &dir); + + //Point3F startPos = getRenderPosition(); + //Point3F endPos = startPos - dir * mCurrentEditOrbitDist; + + Point3F pos; + pos.x = mCurrentEditOrbitDist * mSin(rot.x + mDegToRad(90.0f)) * mCos(-1.0f * (rot.z + mDegToRad(90.0f))) + mEditOrbitPoint.x; + pos.y = mCurrentEditOrbitDist * mSin(rot.x + mDegToRad(90.0f)) * mSin(-1.0f * (rot.z + mDegToRad(90.0f))) + mEditOrbitPoint.y; + pos.z = mCurrentEditOrbitDist * mSin(rot.x) + mEditOrbitPoint.z; + + mat->setColumn(3, pos); +} + +void Camera::setValidEditOrbitPoint(bool state) +{ + mValidEditOrbitPoint = state; + setMaskBits(EditOrbitMask); +} + +Point3F Camera::getEditOrbitPoint() const +{ + return mEditOrbitPoint; +} + +void Camera::setEditOrbitPoint(const Point3F& pnt) +{ + // Change the point that we orbit in EditOrbitMode. + // We'll also change the facing and distance of the + // camera so that it doesn't jump around. + Point3F currentPos; + mObjToWorld.getColumn(3,¤tPos); + + Point3F dir = pnt - currentPos; + mCurrentEditOrbitDist = dir.len(); + + if(mode == EditOrbitMode) + { + dir.normalize(); + + F32 yaw, pitch; + MathUtils::getAnglesFromVector(dir, yaw, pitch); + mRot.x = -pitch; + mRot.z = yaw; + } + + mEditOrbitPoint = pnt; + + setMaskBits(EditOrbitMask); +} + +ConsoleMethod( Camera, isEditOrbitMode, bool, 2, 2, "()" + " - Is the camera in edit orbit mode") +{ + return object->isEditOrbitMode(); +} + +ConsoleMethod( Camera, setValidEditOrbitPoint, void, 3, 3, "(bool)" + " - Indicate if there is a valid editor camera orbit point") +{ + object->setValidEditOrbitPoint(dAtob(argv[2])); +} + +ConsoleMethod( Camera, setEditOrbitPoint, void, 3, 3, "(Point3F point)" + " - Set the editor camera's orbit point.\n\n" + "@param point Orbit point in the form of: 'x y z'") +{ + Point3F pnt(0.0f, 0.0f, 0.0f); + + dSscanf(argv[2],"%g %g %g", + &pnt.x,&pnt.y,&pnt.z); + + object->setEditOrbitPoint(pnt); +} + +//---------------------------------------------------------------------------- + +void Camera::autoFitRadius(F32 radius) +{ + F32 fov = getCameraFov(); + F32 viewradius = (radius * 2.0f) / mTan(fov * 0.5f); + + // Be careful of infinite sized objects. Clip to 16km + if(viewradius > 16000.0f) + viewradius = 16000.0f; + + if(mode == EditOrbitMode && mValidEditOrbitPoint) + { + mCurrentEditOrbitDist = viewradius; + } + else if(mValidEditOrbitPoint) + { + mCurrentEditOrbitDist = viewradius; + + Point3F currentPos; + mObjToWorld.getColumn(3,¤tPos); + + Point3F dir = mEditOrbitPoint - currentPos; + dir.normalize(); + + F32 yaw, pitch; + MathUtils::getAnglesFromVector(dir, yaw, pitch); + mRot.x = -pitch; + mRot.z = yaw; + + mPosition = mEditOrbitPoint; + setPosition(mPosition, mRot); + calcEditOrbitPoint(&mObjToWorld, mRot); + } +} + +ConsoleMethod( Camera, autoFitRadius, void, 3, 3, "(F32 radius)" + " - Orient the camera to view the given radius.\n\n" + "@param radius Selection radius to view") +{ + object->autoFitRadius(dAtof(argv[2])); +} + +//---------------------------------------------------------------------------- + +void Camera::lookAt(const Point3F &pos) +{ + Point3F vec; + mObjToWorld.getColumn(3, &mPosition); + vec = pos - mPosition; + vec.normalizeSafe(); + F32 pitch, yaw; + MathUtils::getAnglesFromVector(vec, yaw, pitch); + mRot.x = -pitch; + mRot.z = yaw; + setPosition(mPosition, mRot); +} + +ConsoleMethod( Camera, lookAt, void, 3, 3, "(point p)" + " - Point the camera at the specified location. (does not work in Orbit or Track modes)") +{ + Point3F point; + dSscanf(argv[2], "%g %g %g", + &point.x, &point.y, &point.z); + + object->lookAt(point); +} \ No newline at end of file diff --git a/T3D/camera.h b/T3D/camera.h new file mode 100644 index 0000000..f32e63e --- /dev/null +++ b/T3D/camera.h @@ -0,0 +1,184 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CAMERA_H_ +#define _CAMERA_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif + +//---------------------------------------------------------------------------- +struct CameraData: public ShapeBaseData { + typedef ShapeBaseData Parent; + + // + DECLARE_CONOBJECT(CameraData); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- +/// Implements a basic camera object. +class Camera: public ShapeBase +{ + typedef ShapeBase Parent; + + enum MaskBits { + MoveMask = Parent::NextFreeMask, + UpdateMask = Parent::NextFreeMask << 1, + NewtonCameraMask = Parent::NextFreeMask << 2, + EditOrbitMask = Parent::NextFreeMask << 3, + NextFreeMask = Parent::NextFreeMask << 4 + }; + + struct StateDelta { + Point3F pos; + Point3F rot; + VectorF posVec; + VectorF rotVec; + }; + Point3F mRot; + StateDelta delta; + + Point3F mOffset; + + static F32 mMovementSpeed; + + void setPosition(const Point3F& pos,const Point3F& viewRot); + void setRenderPosition(const Point3F& pos,const Point3F& viewRot); + + SimObjectPtr mOrbitObject; + F32 mMinOrbitDist; + F32 mMaxOrbitDist; + F32 mCurOrbitDist; + Point3F mPosition; + bool mObservingClientObject; + + // Used by NewtonMode + VectorF mAngularVelocity; + F32 mAngularForce; + F32 mAngularDrag; + VectorF mVelocity; + bool mNewtonMode; + bool mNewtonRotation; + F32 mMass; + F32 mDrag; + F32 mFlyForce; + F32 mSpeedMultiplier; + F32 mBrakeMultiplier; + + // Used by EditOrbitMode + bool mValidEditOrbitPoint; + Point3F mEditOrbitPoint; + F32 mCurrentEditOrbitDist; + + bool mLocked; + +public: + enum CameraMode + { + StationaryMode = 0, + + FreeRotateMode, + FlyMode, + OrbitObjectMode, + OrbitPointMode, + TrackObjectMode, + OverheadMode, + EditOrbitMode, ///< Used by the World Editor + + CameraFirstMode = 0, + CameraLastMode = EditOrbitMode + }; + +private: + CameraMode mode; + + void setPosition(const Point3F& pos,const Point3F& viewRot, MatrixF *mat); + void setTransform(const MatrixF& mat); + void setRenderTransform(const MatrixF& mat); + F32 getCameraFov(); + F32 getDefaultCameraFov(); + bool isValidCameraFov(F32 fov); + void setCameraFov(F32 fov); + + F32 getDamageFlash() const; + F32 getWhiteOut() const; + +public: + DECLARE_CONOBJECT(Camera); + + Camera(); + ~Camera(); + static void initPersistFields(); + static void consoleInit(); + static bool setMode(void *obj, const char *data); + static bool setNewtonProperty(void *obj, const char *data); + + void onEditorEnable(); + void onEditorDisable(); + + bool onAdd(); + void onRemove(); + void processTick(const Move* move); + void interpolateTick(F32 delta); + void getCameraTransform(F32* pos,MatrixF* mat); + + void writePacketData(GameConnection *conn, BitStream *stream); + void readPacketData(GameConnection *conn, BitStream *stream); + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + CameraMode getMode(); + + Point3F getPosition(); + Point3F getRotation() { return mRot; }; + Point3F getOffset() { return mOffset; }; + void lookAt(const Point3F &pos); + void setOffset(const Point3F &offset) { mOffset = offset; }; + void setFlyMode(); + void setNewtonFlyMode(); + void setOrbitMode(GameBase *obj, const Point3F &pos, const Point3F &rot, const Point3F& offset, + F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject, bool locked = false); + void setTrackObject(GameBase *obj, const Point3F &offset); + void validateEyePoint(F32 pos, MatrixF *mat); + void onDeleteNotify(SimObject *obj); + + GameBase * getOrbitObject() { return(mOrbitObject); } + bool isObservingClientObject() { return(mObservingClientObject); } + + // Used by NewtonFlyMode + VectorF getVelocity() const; + void setVelocity(const VectorF& vel); + VectorF getAngularVelocity() const; + void setAngularVelocity(const VectorF& vel); + bool isRotationDamped() {return mNewtonRotation;} + void setAngularForce(F32 force) {mAngularForce = force; setMaskBits(NewtonCameraMask);} + void setAngularDrag(F32 drag) {mAngularDrag = drag; setMaskBits(NewtonCameraMask);} + void setMass(F32 mass) {mMass = mass; setMaskBits(NewtonCameraMask);} + void setDrag(F32 drag) {mDrag = drag; setMaskBits(NewtonCameraMask);} + void setFlyForce(F32 force) {mFlyForce = force; setMaskBits(NewtonCameraMask);} + void setSpeedMultiplier(F32 mul) {mSpeedMultiplier = mul; setMaskBits(NewtonCameraMask);} + void setBrakeMultiplier(F32 mul) {mBrakeMultiplier = mul; setMaskBits(NewtonCameraMask);} + + // Used by EditOrbitMode + void setEditOrbitMode(); + bool isEditOrbitMode() {return mode == EditOrbitMode;} + void calcEditOrbitPoint(MatrixF *mat, const Point3F& rot); + bool getValidEditOrbitPoint() { return mValidEditOrbitPoint; } + void setValidEditOrbitPoint(bool state); + Point3F getEditOrbitPoint() const; + void setEditOrbitPoint(const Point3F& pnt); + + // Orient the camera to view the given radius. Requires that an + // edit orbit point has been set. + void autoFitRadius(F32 radius); +}; + + +#endif diff --git a/T3D/cameraSpline.cpp b/T3D/cameraSpline.cpp new file mode 100644 index 0000000..39c4581 --- /dev/null +++ b/T3D/cameraSpline.cpp @@ -0,0 +1,370 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#include "T3D/cameraSpline.h" + +#include "console/console.h" +#include "gfx/gfxDevice.h" + + +//----------------------------------------------------------------------------- + +CameraSpline::Knot::Knot(const Knot &k) +{ + mPosition = k.mPosition; + mRotation = k.mRotation; + mSpeed = k.mSpeed; + mType = k.mType; + mPath = k.mPath; + prev = NULL; next = NULL; +} + +CameraSpline::Knot::Knot(const Point3F &p, const QuatF &r, F32 s, Knot::Type type, Knot::Path path) +{ + mPosition = p; + mRotation = r; + mSpeed = s; + mType = type; + mPath = path; + prev = NULL; next = NULL; +} + + +//----------------------------------------------------------------------------- + +CameraSpline::CameraSpline() +{ + mFront = NULL; + mSize = 0; + mIsMapDirty = true; + VECTOR_SET_ASSOCIATION(mTimeMap); +} + + +CameraSpline::~CameraSpline() +{ + removeAll(); +} + + +void CameraSpline::push_back(Knot *w) +{ + if (!mFront) + { + mFront = w; + w->next = w; + w->prev = w; + } + else + { + Knot *before = back(); + Knot *after = before->next; + + w->next = before->next; + w->prev = before; + after->prev = w; + before->next = w; + } + ++mSize; + mIsMapDirty = true; +} + +CameraSpline::Knot* CameraSpline::getKnot(S32 i) +{ + Knot *k = mFront; + while(i--) + k = k->next; + return k; +} + +CameraSpline::Knot* CameraSpline::remove(Knot *w) +{ + if (w->next == mFront && w->prev == mFront) + mFront = NULL; + else + { + w->prev->next = w->next; + w->next->prev = w->prev; + if (mFront == w) + mFront = w->next; + } + --mSize; + mIsMapDirty = true; + return w; +} + + +void CameraSpline::removeAll() +{ + while(front()) + delete remove(front()); + mSize = 0; +} + + +//----------------------------------------------------------------------------- + +static bool gBuilding = false; + +void CameraSpline::buildTimeMap() +{ + if (!mIsMapDirty) + return; + + gBuilding = true; + + mTimeMap.clear(); + mTimeMap.reserve(size()*3); // preallocate + + // Initial node and knot value.. + TimeMap map; + map.mTime = 0; + map.mDistance = 0; + mTimeMap.push_back(map); + + Knot ka,kj,ki; + value(0, &kj, true); + F32 length = 0.0f; + ka = kj; + + // Loop through the knots and add nodes. Nodes are added for every knot and + // whenever the spline length and segment length deviate by epsilon. + F32 epsilon = Con::getFloatVariable("CameraSpline::epsilon", 0.90f); + const F32 Step = 0.05f; + F32 lt = 0,time = 0; + do + { + if ((time += Step) > F32(mSize - 1)) + time = (F32)mSize - 1.0f; + + value(time, &ki, true); + length += (ki.mPosition - kj.mPosition).len(); + F32 segment = (ki.mPosition - ka.mPosition).len(); + + if ((segment / length) < epsilon || time == (mSize - 1) || mFloor(lt) != mFloor(time)) + { + map.mTime = time; + map.mDistance = length; + mTimeMap.push_back(map); + ka = ki; + } + kj = ki; + lt = time; + } + while (time < mSize - 1); + + mIsMapDirty = false; + + gBuilding = false; +} + + +//----------------------------------------------------------------------------- + +void CameraSpline::renderTimeMap() +{ + buildTimeMap(); + + gBuilding = true; + + // Build vertex buffer + GFXVertexBufferHandle vb; + vb.set(GFX, mTimeMap.size(), GFXBufferTypeVolatile); + vb.lock(); + + MRandomLCG random(1376312589 * (U32)this); + int index = 0; + for(Vector::iterator itr=mTimeMap.begin(); itr != mTimeMap.end(); itr++) + { + Knot a; + value(itr->mTime, &a, true); + + S32 cr = random.randI(0,255); + S32 cg = random.randI(0,255); + S32 cb = random.randI(0,255); + vb[index].color.set(cr, cg, cb); + vb[index].point.set(a.mPosition.x, a.mPosition.y, a.mPosition.z); + index++; + } + + gBuilding = false; + + vb.unlock(); + + // Render the buffer + GFX->pushWorldMatrix(); + GFX->disableShaders(); + GFX->setVertexBuffer(vb); + GFX->drawPrimitive(GFXLineStrip,0,index); + GFX->popWorldMatrix(); +} + + +//----------------------------------------------------------------------------- + +F32 CameraSpline::advanceTime(F32 t, S32 delta_ms) +{ + buildTimeMap(); + Knot k; + value(t, &k, false); + F32 dist = getDistance(t) + k.mSpeed * (F32(delta_ms) / 1000.0f); + return getTime(dist); +} + + +F32 CameraSpline::advanceDist(F32 t, F32 meters) +{ + buildTimeMap(); + F32 dist = getDistance(t) + meters; + return getTime(dist); +} + + +F32 CameraSpline::getDistance(F32 t) +{ + if (mSize <= 1) + return 0; + + // Find the nodes spanning the time + Vector::iterator end = mTimeMap.begin() + 1, start; + for (; end < (mTimeMap.end() - 1) && end->mTime < t; end++) { } + start = end - 1; + + // Interpolate between the two nodes + F32 i = (t - start->mTime) / (end->mTime - start->mTime); + return start->mDistance + (end->mDistance - start->mDistance) * i; +} + + +F32 CameraSpline::getTime(F32 d) +{ + if (mSize <= 1) + return 0; + + // Find nodes spanning the distance + Vector::iterator end = mTimeMap.begin() + 1, start; + for (; end < (mTimeMap.end() - 1) && end->mDistance < d; end++) { } + start = end - 1; + + // Check for duplicate points.. + F32 seg = end->mDistance - start->mDistance; + if (!seg) + return end->mTime; + + // Interpolate between the two nodes + F32 i = (d - start->mDistance) / (end->mDistance - start->mDistance); + return start->mTime + (end->mTime - start->mTime) * i; +} + + +//----------------------------------------------------------------------------- +void CameraSpline::value(F32 t, CameraSpline::Knot *result, bool skip_rotation) +{ + // Do some easing in and out for t. + if(!gBuilding) + { + F32 oldT = t; + if(oldT < 0.5f) + { + t = 0.5f - (mSin( (0.5 - oldT) * M_PI ) / 2.f); + } + + if((F32(size()) - 1.5f) > 0.f && oldT - (F32(size()) - 1.5f) > 0.f) + { + oldT -= (F32(size()) - 1.5f); + t = (F32(size()) - 1.5f) + (mCos( (0.5f - oldT) * F32(M_PI) ) / 2.f); + } + } + + // Verify that t is in range [0 >= t > size] +// AssertFatal(t >= 0.0f && t < (F32)size(), "t out of range"); + Knot *p1 = getKnot((S32)mFloor(t)); + Knot *p2 = next(p1); + + F32 i = t - mFloor(t); // adjust t to 0 to 1 on p1-p2 interval + + if (p1->mPath == Knot::SPLINE) + { + Knot *p0 = (p1->mType == Knot::KINK) ? p1 : prev(p1); + Knot *p3 = (p2->mType == Knot::KINK) ? p2 : next(p2); + result->mPosition.x = mCatmullrom(i, p0->mPosition.x, p1->mPosition.x, p2->mPosition.x, p3->mPosition.x); + result->mPosition.y = mCatmullrom(i, p0->mPosition.y, p1->mPosition.y, p2->mPosition.y, p3->mPosition.y); + result->mPosition.z = mCatmullrom(i, p0->mPosition.z, p1->mPosition.z, p2->mPosition.z, p3->mPosition.z); + } + else + { // Linear + result->mPosition.interpolate(p1->mPosition, p2->mPosition, i); + } + + if (skip_rotation) + return; + + buildTimeMap(); + + // find the two knots to interpolate rotation and velocity through since some + // knots are only positional + S32 start = (S32)mFloor(t); + S32 end = (p2 == p1) ? start : (start + 1); + while (p1->mType == Knot::POSITION_ONLY && p1 != front()) + { + p1 = prev(p1); + start--; + } + + while (p2->mType == Knot::POSITION_ONLY && p2 != back()) + { + p2 = next(p2); + end++; + } + + if (start == end) + { + result->mRotation = p1->mRotation; + result->mSpeed = p1->mSpeed; + } + else + { + + F32 c = getDistance(t); + F32 d1 = getDistance((F32)start); + F32 d2 = getDistance((F32)end); + + if (d1 == d2) + { + result->mRotation = p2->mRotation; + result->mSpeed = p2->mSpeed; + } + else + { + i = (c-d1)/(d2-d1); + + if(p1->mPath == Knot::SPLINE) + { + Knot *p0 = (p1->mType == Knot::KINK) ? p1 : prev(p1); + Knot *p3 = (p2->mType == Knot::KINK) ? p2 : next(p2); + + F32 q,w,e; + q = mCatmullrom(i, 0, 1, 1, 1); + w = mCatmullrom(i, 0, 0, 0, 1); + e = mCatmullrom(i, 0, 0, 1, 1); + + QuatF a; a.interpolate(p0->mRotation, p1->mRotation, q); + QuatF b; b.interpolate(p2->mRotation, p3->mRotation, w); + + result->mRotation.interpolate(a, b, e); + result->mSpeed = mCatmullrom(i, p0->mSpeed, p1->mSpeed, p2->mSpeed, p3->mSpeed); + } + else + { + result->mRotation.interpolate(p1->mRotation, p2->mRotation, i); + result->mSpeed = (p1->mSpeed * (1.0f-i)) + (p2->mSpeed * i); + } + } + } +} + + + diff --git a/T3D/cameraSpline.h b/T3D/cameraSpline.h new file mode 100644 index 0000000..ba62ebf --- /dev/null +++ b/T3D/cameraSpline.h @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#ifndef _CAMERASPLINE_H_ +#define _CAMERASPLINE_H_ + +#include "math/mMath.h" +#include "core/util/tVector.h" + +//---------------------------------------------------------------------------- +class CameraSpline +{ +public: + struct Knot + { + public: + Point3F mPosition; + QuatF mRotation; + F32 mSpeed; /// in meters per second + enum Type { + NORMAL, + POSITION_ONLY, + KINK, + NUM_TYPE_BITS = 2 + }mType; + + enum Path { + LINEAR, + SPLINE, + NUM_PATH_BITS = 1 + }mPath; + + F32 mDistance; + Knot *prev; + Knot *next; + + Knot() {}; + Knot(const Knot &k); + Knot(const Point3F &p, const QuatF &r, F32 s, Knot::Type type = NORMAL, Knot::Path path = SPLINE); + }; + + + CameraSpline(); + ~CameraSpline(); + + bool isEmpty() { return (mFront == NULL); } + S32 size() { return mSize; } + Knot* remove(Knot *w); + void removeAll(); + + Knot* front() { return mFront; } + Knot* back() { return (mFront == NULL) ? NULL : mFront->prev; } + + void push_back(Knot *w); + void push_front(Knot *w) { push_back(w); mFront = w; mIsMapDirty = true; } + + Knot* getKnot(S32 i); + Knot* next(Knot *k) { return (k->next == mFront) ? k : k->next; } + Knot* prev(Knot *k) { return (k == mFront) ? k : k->prev; } + + F32 advanceTime(F32 t, S32 delta_ms); + F32 advanceDist(F32 t, F32 meters); + void value(F32 t, Knot *result, bool skip_rotation=false); + + F32 getDistance(F32 t); + F32 getTime(F32 d); + + void renderTimeMap(); + + +private: + Knot *mFront; + S32 mSize; + bool mIsMapDirty; + + struct TimeMap { + F32 mTime; + F32 mDistance; + }; + + Vector mTimeMap; + void buildTimeMap(); +}; + + + + +#endif \ No newline at end of file diff --git a/T3D/containerQuery.cpp b/T3D/containerQuery.cpp new file mode 100644 index 0000000..5839765 --- /dev/null +++ b/T3D/containerQuery.cpp @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/containerQuery.h" + +#include "sceneGraph/sceneObject.h" +#include "environment/waterObject.h" +#include "T3D/physicalZone.h" + + +void findRouter( SceneObject *obj, void *key ) +{ + if (obj->getTypeMask() & WaterObjectType) + waterFind(obj, key); + else if (obj->getTypeMask() & PhysicalZoneObjectType) + physicalZoneFind(obj, key); + else { + AssertFatal(false, "Error, must be either water or physical zone here!"); + } +} + +void waterFind( SceneObject *obj, void *key ) +{ + PROFILE_SCOPE( waterFind ); + + // This is called for each WaterObject the ShapeBase object is overlapping. + + ContainerQueryInfo *info = static_cast(key); + WaterObject *water = dynamic_cast(obj); + AssertFatal( water != NULL, "containerQuery - waterFind(), passed object was not of class WaterObject!"); + + // Get point at the bottom/center of the box. + Point3F testPnt = info->box.getCenter(); + testPnt.z = info->box.minExtents.z; + + F32 coverage = water->getWaterCoverage(info->box); + + // Since a WaterObject can have global bounds we may get this call + // even though we have zero coverage. If so we want to early out and + // not save the water properties. + if ( coverage == 0.0f ) + return; + + // Add in flow force. Would be appropriate to try scaling it by coverage + // thought. Or perhaps have getFlow do that internally and take + // the box parameter. + info->appliedForce += water->getFlow( testPnt ); + + // Only save the following properties for the WaterObject with the + // greatest water coverage for this ShapeBase object. + if ( coverage < info->waterCoverage ) + return; + + info->waterCoverage = coverage; + info->liquidType = water->getLiquidType(); + info->waterViscosity = water->getViscosity(); + info->waterDensity = water->getDensity(); + info->waterHeight = water->getSurfaceHeight( Point2F(testPnt.x,testPnt.y) ); + info->waterObject = water; +} + +void physicalZoneFind(SceneObject* obj, void *key) +{ + PROFILE_SCOPE( physicalZoneFind ); + + ContainerQueryInfo *info = static_cast(key); + PhysicalZone* pz = dynamic_cast(obj); + AssertFatal(pz != NULL, "Error, not a physical zone!"); + if (pz == NULL || pz->testBox(info->box) == false) + return; + + if (pz->isActive()) { + info->gravityScale *= pz->getGravityMod(); + info->appliedForce += pz->getForce(); + } +} + diff --git a/T3D/containerQuery.h b/T3D/containerQuery.h new file mode 100644 index 0000000..812cacb --- /dev/null +++ b/T3D/containerQuery.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONTAINERQUERY_H_ +#define _CONTAINERQUERY_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif + +class SceneObject; +class WaterObject; + +struct ContainerQueryInfo +{ + ContainerQueryInfo() + : waterCoverage(0.0f), + waterHeight(0.0f), + waterDensity(0.0f), + waterViscosity(0.0f), + gravityScale(1.0f), + appliedForce(0,0,0), + box(-1,-1,-1,1,1,1), + mass(1.0f), + waterObject(NULL) + { + } + + //SceneObject *sceneObject; + Box3F box; + F32 mass; + F32 waterCoverage; + F32 waterHeight; + F32 waterDensity; + F32 waterViscosity; + String liquidType; + F32 gravityScale; + Point3F appliedForce; + WaterObject *waterObject; +}; + +extern void findRouter( SceneObject *obj, void *key ); +extern void waterFind( SceneObject *obj, void *key ); +extern void physicalZoneFind( SceneObject *obj, void *key ); + +#endif // _CONTAINERQUERY_H_ \ No newline at end of file diff --git a/T3D/debris.cpp b/T3D/debris.cpp new file mode 100644 index 0000000..c771ea3 --- /dev/null +++ b/T3D/debris.cpp @@ -0,0 +1,823 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/debris.h" + +#include "core/stream/bitStream.h" +#include "math/mathUtils.h" +#include "console/consoleTypes.h" +#include "console/consoleObject.h" +#include "sim/netConnection.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "ts/tsShapeInstance.h" +#include "ts/tsPartInstance.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/fx/explosion.h" +#include "T3D/gameProcess.h" +#include "core/resourceManager.h" +#include "gfx/gfxTransformSaver.h" + + +const U32 csmStaticCollisionMask = TerrainObjectType | + InteriorObjectType; + +const U32 csmDynamicCollisionMask = StaticShapeObjectType; + + +IMPLEMENT_CO_DATABLOCK_V1(DebrisData); + + +DebrisData::DebrisData() +{ + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); + + explosion = NULL; + explosionId = 0; + + velocity = 0.0f; + velocityVariance = 0.0; + elasticity = 0.3f; + friction = 0.2f; + numBounces = 0; + bounceVariance = 0; + minSpinSpeed = maxSpinSpeed = 0.0; + render2D = false; + staticOnMaxBounce = false; + explodeOnMaxBounce = false; + snapOnMaxBounce = false; + lifetime = 3.0f; + lifetimeVariance = 0.0f; + minSpinSpeed = 0.0f; + maxSpinSpeed = 0.0f; + textureName = NULL; + mTypeMask |= DebrisObjectType; + shapeName = NULL; + fade = true; + useRadiusMass = false; + baseRadius = 1.0f; + gravModifier = 1.0f; + terminalVelocity = 0.0f; + ignoreWater = true; +} + +bool DebrisData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + for( int i=0; i velocity ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: velocityVariance invalid", getName()); + velocityVariance = velocity; + } + if( friction < -10.0f || friction > 10.0f ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: friction invalid", getName()); + friction = 0.2f; + } + if( elasticity < -10.0f || elasticity > 10.0f ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: elasticity invalid", getName()); + elasticity = 0.2f; + } + if( lifetime < 0.0f || lifetime > 1000.0f ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: lifetime invalid", getName()); + lifetime = 3.0f; + } + if( lifetimeVariance < 0.0f || lifetimeVariance > lifetime ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: lifetimeVariance invalid", getName()); + lifetimeVariance = 0.0f; + } + if( numBounces < 0 || numBounces > 10000 ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: numBounces invalid", getName()); + numBounces = 3; + } + if( bounceVariance < 0 || bounceVariance > numBounces ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: bounceVariance invalid", getName()); + bounceVariance = 0; + } + if( minSpinSpeed < -10000.0f || minSpinSpeed > 10000.0f || minSpinSpeed > maxSpinSpeed ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: minSpinSpeed invalid", getName()); + minSpinSpeed = maxSpinSpeed - 1.0f; + } + if( maxSpinSpeed < -10000.0f || maxSpinSpeed > 10000.0f ) + { + Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: maxSpinSpeed invalid", getName()); + maxSpinSpeed = 0.0f; + } + + return true; +} + +bool DebrisData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + if( server ) return true; + + if( shapeName && shapeName[0] != '\0' && !bool(shape) ) + { + shape = ResourceManager::get().load(shapeName); + if( bool(shape) == false ) + { + errorStr = String::ToString("DebrisData::load: Couldn't load shape \"%s\"", shapeName); + return false; + } + else + { + TSShapeInstance* pDummy = new TSShapeInstance(shape, !server); + delete pDummy; + } + + } + + return true; +} + +IMPLEMENT_CONSOLETYPE(DebrisData) +IMPLEMENT_SETDATATYPE(DebrisData) +IMPLEMENT_GETDATATYPE(DebrisData) + +void DebrisData::initPersistFields() +{ + addGroup("Display"); + addField("texture", TypeString, Offset(textureName, DebrisData)); + addField("shapeFile", TypeFilename, Offset(shapeName, DebrisData)); + addField("render2D", TypeBool, Offset(render2D, DebrisData)); + endGroup("Display"); + + addGroup("Datablocks"); + addField("emitters", TypeParticleEmitterDataPtr, Offset(emitterList, DebrisData), DDC_NUM_EMITTERS); + addField("explosion", TypeExplosionDataPtr, Offset(explosion, DebrisData)); + endGroup("Datablocks"); + + addGroup("Physical Properties"); + addField("elasticity", TypeF32, Offset(elasticity, DebrisData)); + addField("friction", TypeF32, Offset(friction, DebrisData)); + addField("numBounces", TypeS32, Offset(numBounces, DebrisData)); + addField("bounceVariance", TypeS32, Offset(bounceVariance, DebrisData)); + addField("minSpinSpeed", TypeF32, Offset(minSpinSpeed, DebrisData)); + addField("maxSpinSpeed", TypeF32, Offset(maxSpinSpeed, DebrisData)); + addField("gravModifier", TypeF32, Offset(gravModifier, DebrisData)); + addField("terminalVelocity", TypeF32, Offset(terminalVelocity, DebrisData)); + addField("velocity", TypeF32, Offset(velocity, DebrisData)); + addField("velocityVariance", TypeF32, Offset(velocityVariance, DebrisData)); + addField("lifetime", TypeF32, Offset(lifetime, DebrisData)); + addField("lifetimeVariance", TypeF32, Offset(lifetimeVariance, DebrisData)); + addField("useRadiusMass", TypeBool, Offset(useRadiusMass, DebrisData)); + addField("baseRadius", TypeF32, Offset(baseRadius, DebrisData)); + endGroup("Physical Properties"); + + addGroup("Behavior"); + addField("explodeOnMaxBounce", TypeBool, Offset(explodeOnMaxBounce, DebrisData)); + addField("staticOnMaxBounce", TypeBool, Offset(staticOnMaxBounce, DebrisData)); + addField("snapOnMaxBounce", TypeBool, Offset(snapOnMaxBounce, DebrisData)); + addField("fade", TypeBool, Offset(fade, DebrisData)); + addField("ignoreWater", TypeBool, Offset(ignoreWater, DebrisData)); + endGroup("Behavior"); + + Parent::initPersistFields(); +} + +void DebrisData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(elasticity); + stream->write(friction); + stream->write(numBounces); + stream->write(bounceVariance); + stream->write(minSpinSpeed); + stream->write(maxSpinSpeed); + stream->write(render2D); + stream->write(explodeOnMaxBounce); + stream->write(staticOnMaxBounce); + stream->write(snapOnMaxBounce); + stream->write(lifetime); + stream->write(lifetimeVariance); + stream->write(velocity); + stream->write(velocityVariance); + stream->write(fade); + stream->write(useRadiusMass); + stream->write(baseRadius); + stream->write(gravModifier); + stream->write(terminalVelocity); + stream->write(ignoreWater); + + stream->writeString( textureName ); + stream->writeString( shapeName ); + + for( int i=0; iwriteFlag( emitterList[i] != NULL ) ) + { + stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + if( stream->writeFlag( explosion ) ) + { + stream->writeRangedU32(packed? SimObjectId(explosion): + explosion->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + +} + +void DebrisData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&elasticity); + stream->read(&friction); + stream->read(&numBounces); + stream->read(&bounceVariance); + stream->read(&minSpinSpeed); + stream->read(&maxSpinSpeed); + stream->read(&render2D); + stream->read(&explodeOnMaxBounce); + stream->read(&staticOnMaxBounce); + stream->read(&snapOnMaxBounce); + stream->read(&lifetime); + stream->read(&lifetimeVariance); + stream->read(&velocity); + stream->read(&velocityVariance); + stream->read(&fade); + stream->read(&useRadiusMass); + stream->read(&baseRadius); + stream->read(&gravModifier); + stream->read(&terminalVelocity); + stream->read(&ignoreWater); + + textureName = stream->readSTString(); + shapeName = stream->readSTString(); + + for( int i=0; ireadFlag() ) + { + emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + if(stream->readFlag()) + { + explosionId = (S32)stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + else + { + explosionId = 0; + } + +} + + +IMPLEMENT_CO_NETOBJECT_V1(Debris); + +ConsoleMethod( Debris, init, bool, 4, 4, "(Point3F position, Point3F velocity)" + "Set this piece of debris at the given position with the given velocity.") +{ + Point3F pos; + dSscanf( argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z ); + + Point3F vel; + dSscanf( argv[3], "%f %f %f", &vel.x, &vel.y, &vel.z ); + + object->init( pos, vel ); + + return true; +} + +Debris::Debris() +{ + mTypeMask |= DebrisObjectType; + + mVelocity = Point3F( 0.0f, 0.0f, 4.0f ); + mLifetime = gRandGen.randF( 1.0f, 10.0f ); + mLastPos = getPosition(); + mNumBounces = gRandGen.randI( 0, 1 ); + mSize = 2.0f; + mElapsedTime = 0.0f; + mShape = NULL; + mPart = NULL; + mXRotSpeed = 0.0f; + mZRotSpeed = 0.0f; + mInitialTrans.identity(); + mRadius = 0.2f; + mStatic = false; + + dMemset( mEmitterList, 0, sizeof( mEmitterList ) ); +} + +Debris::~Debris() +{ + if( mShape ) + { + delete mShape; + mShape = NULL; + } + + if( mPart ) + { + delete mPart; + mPart = NULL; + } +} + +void Debris::initPersistFields() +{ + addGroup("Misc"); + addField("lifetime", TypeF32, Offset(mLifetime, Debris)); + endGroup("Misc"); +} + +void Debris::init( const Point3F &position, const Point3F &velocity ) +{ + setPosition( position ); + setVelocity( velocity ); +} + +bool Debris::onNewDataBlock( GameBaseData* dptr ) +{ + mDataBlock = dynamic_cast< DebrisData* >( dptr ); + if( !mDataBlock || !Parent::onNewDataBlock( dptr ) ) + return false; + + scriptOnNewDataBlock(); + return true; + +} + +bool Debris::onAdd() +{ + if( !Parent::onAdd() ) + { + return false; + } + + // create emitters + for( int i=0; iemitterList[i] != NULL ) + { + ParticleEmitter * pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock( mDataBlock->emitterList[i] ); + if( !pEmitter->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() ); + delete pEmitter; + pEmitter = NULL; + } + mEmitterList[i] = pEmitter; + } + } + + // set particle sizes based on debris size + F32 sizeList[ParticleData::PDC_NUM_KEYS]; + + if( mEmitterList[0] ) + { + sizeList[0] = mSize * 0.5; + sizeList[1] = mSize; + sizeList[2] = mSize * 1.5; + + mEmitterList[0]->setSizes( sizeList ); + } + + if( mEmitterList[1] ) + { + sizeList[0] = 0.0; + sizeList[1] = mSize * 0.5; + sizeList[2] = mSize; + + mEmitterList[1]->setSizes( sizeList ); + } + + S32 bounceVar = (S32)mDataBlock->bounceVariance; + bounceVar = gRandGen.randI( -bounceVar, bounceVar ); + mNumBounces = mDataBlock->numBounces + bounceVar; + + F32 lifeVar = (mDataBlock->lifetimeVariance * 2.0f * gRandGen.randF(-1.0,1.0)) - mDataBlock->lifetimeVariance; + mLifetime = mDataBlock->lifetime + lifeVar; + + F32 xRotSpeed = gRandGen.randF( mDataBlock->minSpinSpeed, mDataBlock->maxSpinSpeed ); + F32 zRotSpeed = gRandGen.randF( mDataBlock->minSpinSpeed, mDataBlock->maxSpinSpeed ); + zRotSpeed *= gRandGen.randF( 0.1f, 0.5f ); + + mRotAngles.set( xRotSpeed, 0.0f, zRotSpeed ); + + mElasticity = mDataBlock->elasticity; + mFriction = mDataBlock->friction; + + // Setup our bounding box + if( mDataBlock->shape ) + { + mObjBox = mDataBlock->shape->bounds; + } + else + { + mObjBox = Box3F(Point3F(-1, -1, -1), Point3F(1, 1, 1)); + } + + if( mDataBlock->shape ) + { + mShape = new TSShapeInstance( mDataBlock->shape, true); + } + + if( mPart ) + { + // use half radius becuase we want debris to stick in ground + mRadius = mPart->getRadius() * 0.5; + mObjBox = mPart->getBounds(); + } + + resetWorldBox(); + + mInitialTrans = getTransform(); + + if( mDataBlock->velocity != 0.0 ) + { + F32 velocity = mDataBlock->velocity + gRandGen.randF( -mDataBlock->velocityVariance, mDataBlock->velocityVariance ); + + mVelocity.normalizeSafe(); + mVelocity *= velocity; + } + + // mass calculations + if( mDataBlock->useRadiusMass ) + { + if( mRadius < mDataBlock->baseRadius ) + { + mRadius = mDataBlock->baseRadius; + } + + // linear falloff + F32 multFactor = mDataBlock->baseRadius / mRadius; + + mElasticity *= multFactor; + mFriction *= multFactor; + mRotAngles *= multFactor; + } + + + // tell engine the debris exists + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + removeFromProcessList(); + gClientProcessList.addObject(this); + + NetConnection* pNC = NetConnection::getConnectionToServer(); + AssertFatal(pNC != NULL, "Error, must have a connection to the server!"); + pNC->addObject(this); + + return true; +} + +void Debris::onRemove() +{ + for( int i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + if( mPart ) + { + TSShapeInstance *ss = mPart->getSourceShapeInstance(); + if( ss ) + { + ss->decDebrisRefCount(); + if( ss->getDebrisRefCount() == 0 ) + { + delete ss; + } + } + } + + mSceneManager->removeObjectFromScene(this); + getContainer()->removeObject(this); + + Parent::onRemove(); +} + +void Debris::processTick(const Move*) +{ + if (mLifetime <= 0.0) + deleteObject(); +} + +void Debris::advanceTime( F32 dt ) +{ + mElapsedTime += dt; + + mLifetime -= dt; + if( mLifetime <= 0.0 ) + { + mLifetime = 0.0; + return; + } + + mLastPos = getPosition(); + + if( !mStatic ) + { + rotate( dt ); + + Point3F nextPos = getPosition(); + computeNewState( nextPos, mVelocity, dt ); + + if( bounce( nextPos, dt ) ) + { + --mNumBounces; + if( mNumBounces <= 0 ) + { + if( mDataBlock->explodeOnMaxBounce ) + { + explode(); + mLifetime = 0.0; + } + if( mDataBlock->snapOnMaxBounce ) + { + // orient debris so it's flat + MatrixF stat = getTransform(); + + Point3F dir; + stat.getColumn( 1, &dir ); + dir.z = 0.0; + + MatrixF newTrans = MathUtils::createOrientFromDir( dir ); + + // hack for shell casings to get them above ground. Need something better - bramage + newTrans.setPosition( getPosition() + Point3F( 0.0f, 0.0f, 0.10f ) ); + + setTransform( newTrans ); + } + if( mDataBlock->staticOnMaxBounce ) + { + mStatic = true; + } + } + } + else + { + setPosition( nextPos ); + } + } + + Point3F pos( getPosition( ) ); + updateEmitters( pos, mVelocity, (U32)(dt * 1000.0)); + +} + +void Debris::rotate( F32 dt ) +{ + MatrixF curTrans = getTransform(); + curTrans.setPosition( Point3F(0.0f, 0.0f, 0.0f) ); + + Point3F curAngles = mRotAngles * dt * M_PI_F/180.0f; + MatrixF rotMatrix( EulerF( curAngles.x, curAngles.y, curAngles.z ) ); + + curTrans.mul( rotMatrix ); + curTrans.setPosition( getPosition() ); + setTransform( curTrans ); +} + +bool Debris::bounce( const Point3F &nextPos, F32 dt ) +{ + Point3F curPos = getPosition(); + + Point3F dir = nextPos - curPos; + if( dir.magnitudeSafe() == 0.0f ) return false; + dir.normalizeSafe(); + Point3F extent = nextPos + dir * mRadius; + F32 totalDist = Point3F( extent - curPos ).magnitudeSafe(); + F32 moveDist = Point3F( nextPos - curPos ).magnitudeSafe(); + F32 movePercent = (moveDist / totalDist); + + RayInfo rayInfo; + U32 collisionMask = csmStaticCollisionMask; + if( !mDataBlock->ignoreWater ) + { + collisionMask |= WaterObjectType; + } + + if( getContainer()->castRay( curPos, extent, collisionMask, &rayInfo ) ) + { + + Point3F reflection = mVelocity - rayInfo.normal * (mDot( mVelocity, rayInfo.normal ) * 2.0f); + mVelocity = reflection; + + Point3F tangent = reflection - rayInfo.normal * mDot( reflection, rayInfo.normal ); + mVelocity -= tangent * mFriction; + + Point3F velDir = mVelocity; + velDir.normalizeSafe(); + + mVelocity *= mElasticity; + + Point3F bouncePos = curPos + dir * rayInfo.t * movePercent; + bouncePos += mVelocity * dt; + + setPosition( bouncePos ); + + mRotAngles *= mElasticity; + + return true; + + } + + return false; + +} + +void Debris::explode() +{ + + if( !mDataBlock->explosion ) return; + + Point3F explosionPos = getPosition(); + + Explosion* pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->explosion); + + MatrixF trans( true ); + trans.setPosition( getPosition() ); + + pExplosion->setTransform( trans ); + pExplosion->setInitialState( explosionPos, VectorF(0,0,1), 1); + if (!pExplosion->registerObject()) + delete pExplosion; +} + +void Debris::computeNewState( Point3F &newPos, Point3F &newVel, F32 dt ) +{ + // apply gravity + Point3F force = Point3F(0, 0, -9.81 * mDataBlock->gravModifier ); + + if( mDataBlock->terminalVelocity > 0.0001 ) + { + if( newVel.magnitudeSafe() > mDataBlock->terminalVelocity ) + { + newVel.normalizeSafe(); + newVel *= mDataBlock->terminalVelocity; + } + else + { + newVel += force * dt; + } + } + else + { + newVel += force * dt; + } + + newPos += newVel * dt; + +} + +void Debris::updateEmitters( Point3F &pos, Point3F &vel, U32 ms ) +{ + + Point3F axis = -vel; + + if( axis.magnitudeSafe() == 0.0 ) + { + axis = Point3F( 0.0, 0.0, 1.0 ); + } + axis.normalizeSafe(); + + + Point3F lastPos = mLastPos; + + for( int i=0; iemitParticles( lastPos, pos, axis, vel, ms ); + } + } + +} + +bool Debris::prepRenderImage(SceneState *state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if( state->isObjectRendered(this) && (mPart || mShape) ) + { + Point3F cameraOffset; + mObjToWorld.getColumn(3,&cameraOffset); + cameraOffset -= state->getDiffuseCameraPosition(); + F32 dist = cameraOffset.len(); + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + + if( mShape ) + { + mShape->setDetailFromDistance( state, dist * invScale ); + if( mShape->getCurrentDetail() < 0 ) + return false; + } + + if( mPart ) + mShape->setDetailFromDistance( state, dist * invScale ); + + prepBatchRender( state ); + } + + return false; +} + +void Debris::prepBatchRender( SceneState *state ) +{ + if ( !mShape && !mPart ) + return; + + GFXTransformSaver saver; + + F32 alpha = 1.0; + if( mDataBlock->fade ) + { + if( mLifetime < 1.0 ) alpha = mLifetime; + } + + Point3F cameraOffset; + mObjToWorld.getColumn(3,&cameraOffset); + cameraOffset -= state->getCameraPosition(); + + // Set up our TS render state. + TSRenderState rdata; + rdata.setSceneState( state ); + + LightManager *lm = gClientSceneGraph->getLightManager(); + if ( !state->isShadowPass() ) + lm->setupLights( this, getWorldSphere() ); + + if( mShape ) + { + MatrixF mat = getRenderTransform(); + GFX->setWorldMatrix( mat ); + + rdata.setFadeOverride( alpha ); + mShape->render( rdata ); + } + else + { + if (mPart->getCurrentObjectDetail() != -1) + { + MatrixF mat = getRenderTransform(); + GFX->setWorldMatrix( mat ); + + rdata.setFadeOverride( alpha ); + mPart->render( rdata ); + } + } + + render2D(); +} + +void Debris::render2D() +{ +} + +void Debris::setSize( F32 size ) +{ + mSize = size; +} diff --git a/T3D/debris.h b/T3D/debris.h new file mode 100644 index 0000000..d12a29b --- /dev/null +++ b/T3D/debris.h @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DEBRIS_H_ +#define _DEBRIS_H_ + +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif + +class ParticleEmitterData; +class ParticleEmitter; +class ExplosionData; +class TSPartInstance; +class TSShapeInstance; +class TSShape; + +//************************************************************************** +// Debris Data +//************************************************************************** +struct DebrisData : public GameBaseData +{ + typedef GameBaseData Parent; + + //----------------------------------------------------------------------- + // Data Decs + //----------------------------------------------------------------------- + enum DebrisDataConst + { + DDC_NUM_EMITTERS = 2, + }; + + + //----------------------------------------------------------------------- + // Debris datablock + //----------------------------------------------------------------------- + F32 velocity; + F32 velocityVariance; + F32 friction; + F32 elasticity; + F32 lifetime; + F32 lifetimeVariance; + U32 numBounces; + U32 bounceVariance; + F32 minSpinSpeed; + F32 maxSpinSpeed; + bool render2D; + bool explodeOnMaxBounce; // explodes after it has bounced max times + bool staticOnMaxBounce; // becomes static after bounced max times + bool snapOnMaxBounce; // snap into a "resting" position on last bounce + bool fade; + bool useRadiusMass; // use mass calculations based on radius + F32 baseRadius; // radius at which the standard elasticity and friction apply + F32 gravModifier; // how much gravity affects debris + F32 terminalVelocity; // max velocity magnitude + bool ignoreWater; + + const char* shapeName; + Resource shape; + + StringTableEntry textureName; +// TextureHandle texture; + + + S32 explosionId; + ExplosionData * explosion; + ParticleEmitterData* emitterList[DDC_NUM_EMITTERS]; + S32 emitterIDList[DDC_NUM_EMITTERS]; + + DebrisData(); + + bool onAdd(); + bool preload( bool server, String &errorStr ); + static void initPersistFields(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + DECLARE_CONOBJECT(DebrisData); + +}; +DECLARE_CONSOLETYPE(DebrisData) + +//************************************************************************** +// Debris +//************************************************************************** +class Debris : public GameBase +{ + typedef GameBase Parent; + +private: + S32 mNumBounces; + F32 mSize; + Point3F mLastPos; + Point3F mVelocity; + F32 mLifetime; + DebrisData * mDataBlock; + F32 mElapsedTime; + TSShapeInstance * mShape; + TSPartInstance * mPart; + MatrixF mInitialTrans; + F32 mXRotSpeed; + F32 mZRotSpeed; + Point3F mRotAngles; + F32 mRadius; + bool mStatic; + F32 mElasticity; + F32 mFriction; + + SimObjectPtr mEmitterList[ DebrisData::DDC_NUM_EMITTERS ]; + + /// Bounce the debris - returns true if debris bounces. + bool bounce( const Point3F &nextPos, F32 dt ); + + /// Compute state of debris as if it hasn't collided with anything. + void computeNewState( Point3F &newPos, Point3F &newVel, F32 dt ); + + void explode(); + void render2D(); + void rotate( F32 dt ); + +protected: + virtual void processTick(const Move* move); + virtual void advanceTime( F32 dt ); + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + void prepBatchRender(SceneState *state); + + + bool onAdd(); + void onRemove(); + void updateEmitters( Point3F &pos, Point3F &vel, U32 ms ); + +public: + + Debris(); + ~Debris(); + + static void initPersistFields(); + + bool onNewDataBlock( GameBaseData* dptr ); + + void init( const Point3F &position, const Point3F &velocity ); + void setLifetime( F32 lifetime ){ mLifetime = lifetime; } + void setPartInstance( TSPartInstance *part ){ mPart = part; } + void setSize( F32 size ); + void setVelocity( const Point3F &vel ){ mVelocity = vel; } + void setRotAngles( const Point3F &angles ){ mRotAngles = angles; } + + DECLARE_CONOBJECT(Debris); + +}; + + + + +#endif diff --git a/T3D/decal/decalData.cpp b/T3D/decal/decalData.cpp new file mode 100644 index 0000000..efd0747 --- /dev/null +++ b/T3D/decal/decalData.cpp @@ -0,0 +1,362 @@ + +#include "platform/platform.h" +#include "T3D/decal/decalData.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" +#include "materials/materialManager.h" +#include "materials/baseMatInstance.h" +#include "T3D/objectTypes.h" + +GFXImplementVertexFormat( DecalVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TANGENT, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +} + +IMPLEMENT_CO_DATABLOCK_V1( DecalData ); +IMPLEMENT_CONSOLETYPE( DecalData ) +IMPLEMENT_GETDATATYPE( DecalData ) +IMPLEMENT_SETDATATYPE( DecalData ) + +//------------------------------------------------------------------------- +// DecalData +//------------------------------------------------------------------------- + +DecalData::DecalData() +{ + size = 5; + materialName = ""; + + lifeSpan = 5000; + fadeTime = 1000; + + frame = 0; + randomize = false; + texRows = 1; + texCols = 1; + + startPixRadius = 2.0f; + endPixRadius = 1.0f; + + material = NULL; + matInst = NULL; + + renderPriority = 10; + clippingMasks = STATIC_COLLISION_MASK; + + texCoordCount = 1; + + for ( S32 i = 0; i < 16; i++ ) + { + texRect[i].point.set( 0.0f, 0.0f ); + texRect[i].extent.set( 1.0f, 1.0f ); + } +} + +DecalData::~DecalData() +{ + SAFE_DELETE( matInst ); +} + +bool DecalData::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + if (size < 0.0) { + Con::warnf("DecalData::onAdd: size < 0"); + size = 0; + } + + getSet()->addObject( this ); + + if( texRows > 1 || texCols > 1 ) + reloadRects(); + + return true; +} + +void DecalData::onRemove() +{ + Parent::onRemove(); +} + +void DecalData::initPersistFields() +{ + Parent::initPersistFields(); + + addGroup("AdvCoordManipulation"); + addField( "textureCoordCount", TypeS32, Offset( texCoordCount, DecalData ) ); + addField( "textureCoords", TypeRectF, Offset( texRect, DecalData ), MAX_TEXCOORD_COUNT, NULL, + "A RectF in uv space - eg ( topleft.x topleft.y extent.x extent.y )"); + endGroup("AdvCoordManipulation"); + + addField( "size", TypeF32, Offset( size, DecalData ) ); + addField( "material", TypeMaterialName, Offset( materialName, DecalData ) ); + addField( "lifeSpan", TypeS32, Offset( lifeSpan, DecalData ) ); + addField( "fadeTime", TypeS32, Offset( fadeTime, DecalData ) ); + + addField( "frame", TypeS32, Offset( frame, DecalData ) ); + addField( "randomize", TypeBool, Offset( randomize, DecalData ) ); + addField( "texRows", TypeS32, Offset( texRows, DecalData ) ); + addField( "texCols", TypeS32, Offset( texCols, DecalData ) ); + + addField( "screenStartRadius", TypeF32, Offset( startPixRadius, DecalData ) ); + addField( "screenEndRadius", TypeF32, Offset( endPixRadius, DecalData ) ); + + addField( "renderPriority", TypeS8, Offset( renderPriority, DecalData ), "Default renderPriority for decals of this type." ); +} + +void DecalData::onStaticModified( const char *slotName, const char *newValue ) +{ + Parent::onStaticModified( slotName, newValue ); + + if ( !isProperlyAdded() ) + return; + + // To allow changing materials live. + if ( dStricmp( slotName, "material" ) == 0 ) + { + materialName = newValue; + _updateMaterial(); + } + // To allow changing name live. + else if ( dStricmp( slotName, "name" ) == 0 ) + { + lookupName = getName(); + } + else if ( dStricmp( slotName, "renderPriority" ) == 0 ) + { + renderPriority = getMax( renderPriority, (U8)1 ); + } +} + +bool DecalData::preload( bool server, String &errorStr ) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + // Server assigns name to lookupName, + // client assigns lookupName in unpack. + if ( server ) + lookupName = getName(); + + return true; +} + +void DecalData::packData( BitStream *stream ) +{ + Parent::packData( stream ); + + stream->write( lookupName ); + stream->write( size ); + stream->write( materialName ); + stream->write( lifeSpan ); + stream->write( fadeTime ); + stream->write( texCoordCount ); + + for (S32 i = 0; i < texCoordCount; i++) + mathWrite( *stream, texRect[i] ); + + stream->write( startPixRadius ); + stream->write( endPixRadius ); + stream->write( renderPriority ); + stream->write( clippingMasks ); + + stream->write( texRows ); + stream->write( texCols ); + stream->write( frame ); + stream->write( randomize ); +} + +void DecalData::unpackData( BitStream *stream ) +{ + Parent::unpackData( stream ); + + stream->read( &lookupName ); + stream->read( &size ); + stream->read( &materialName ); + _updateMaterial(); + stream->read( &lifeSpan ); + stream->read( &fadeTime ); + stream->read( &texCoordCount ); + + for (S32 i = 0; i < texCoordCount; i++) + mathRead(*stream, &texRect[i]); + + stream->read( &startPixRadius ); + stream->read( &endPixRadius ); + stream->read( &renderPriority ); + stream->read( &clippingMasks ); + + stream->read( &texRows ); + stream->read( &texCols ); + stream->read( &frame ); + stream->read( &randomize ); +} + +void DecalData::_initMaterial() +{ + SAFE_DELETE( matInst ); + + if ( material ) + matInst = material->createMatInstance(); + else + matInst = MATMGR->createMatInstance( "WarningMaterial" ); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + //desc.zFunc = GFXCmpLess; + matInst->addStateBlockDesc( desc ); + + matInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); +} + +void DecalData::_updateMaterial() +{ + if ( materialName.isEmpty() ) + return; + + Material *pMat = NULL; + if ( !Sim::findObject( materialName, pMat ) ) + { + Con::printf( "DecalData::unpackUpdate, failed to find Material of name &s!", materialName.c_str() ); + return; + } + + material = pMat; + + // Only update material instance if we have one allocated. + if ( matInst ) + _initMaterial(); +} + +Material* DecalData::getMaterial() +{ + if ( !material ) + { + _updateMaterial(); + if ( !material ) + material = static_cast( Sim::findObject("WarningMaterial") ); + } + + return material; +} + +BaseMatInstance* DecalData::getMaterialInstance() +{ + if ( !material || !matInst || matInst->getMaterial() != material ) + _initMaterial(); + + return matInst; +} + +DecalData* DecalData::findDatablock( String searchName ) +{ + StringTableEntry className = DecalData::getStaticClassRep()->getClassName(); + DecalData *pData; + SimSet *set = getSet(); + SimSetIterator iter( set ); + + for ( ; *iter; ++iter ) + { + if ( (*iter)->getClassName() != className ) + { + Con::errorf( "DecalData::findDatablock - found a class %s object in DecalDataSet!", (*iter)->getClassName() ); + continue; + } + + pData = static_cast( *iter ); + if ( pData->lookupName.equal( searchName, String::NoCase ) ) + return pData; + } + + return NULL; +} + +void DecalData::inspectPostApply() +{ + reloadRects(); +} + +void DecalData::reloadRects() +{ + F32 rowsBase = 0; + F32 colsBase = 0; + bool canRenderRowsByFrame = false; + bool canRenderColsByFrame = false; + S32 id = 0; + + texRect[id].point.x = 0.f; + texRect[id].extent.x = 1.f; + texRect[id].point.y = 0.f; + texRect[id].extent.y = 1.f; + + texCoordCount = (texRows * texCols) - 1; + + if( texCoordCount > 16 ) + { + Con::warnf("Coordinate max must be lower than 16 to be a valid decal !"); + texRows = 1; + texCols = 1; + texCoordCount = 1; + } + + // use current datablock information in order to build a template to extract + // coordinates from. + if( texRows > 1 ) + { + rowsBase = ( 1.f / texRows ); + canRenderRowsByFrame = true; + } + if( texCols > 1 ) + { + colsBase = ( 1.f / texCols ); + canRenderColsByFrame = true; + } + + // if were able, lets enter the loop + if( frame >= 0 && (canRenderRowsByFrame || canRenderColsByFrame) ) + { + // columns first then rows + for ( S32 colId = 1; colId <= texCols; colId++ ) + { + for ( S32 rowId = 1; rowId <= texRows; rowId++, id++ ) + { + // if were over the coord count, lets go + if(id > texCoordCount) + return; + + // keep our dimensions correct + if(rowId > texRows) + rowId = 1; + + if(colId > texCols) + colId = 1; + + // start setting our rect values per frame + if( canRenderRowsByFrame ) + { + texRect[id].point.x = rowsBase * ( rowId - 1 ); + texRect[id].extent.x = rowsBase; + } + + if( canRenderColsByFrame ) + { + texRect[id].point.y = colsBase * ( colId - 1 ); + texRect[id].extent.y = colsBase; + } + } + } + } +} + +ConsoleMethod( DecalData, postApply, void, 2, 2, "") +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/T3D/decal/decalData.h b/T3D/decal/decalData.h new file mode 100644 index 0000000..da62e40 --- /dev/null +++ b/T3D/decal/decalData.h @@ -0,0 +1,106 @@ + +#ifndef _DECALDATA_H_ +#define _DECALDATA_H_ + +#ifndef _SIMDATABLOCK_H_ +#include "console/simDatablock.h" +#endif +#ifndef _MATERIALDEFINITION_H_ +#include "materials/materialDefinition.h" +#endif +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif +#ifndef _DYNAMIC_CONSOLETYPES_H_ +#include "console/dynamicTypes.h" +#endif + +GFXDeclareVertexFormat( DecalVertex ) +{ + // .xyz = coords + Point3F point; + Point3F normal; + Point3F tangent; + GFXVertexColor color; + Point2F texCoord; +}; + +/// DataBlock implementation for decals. +class DecalData : public SimDataBlock +{ + typedef SimDataBlock Parent; + +public: + + enum { MAX_TEXCOORD_COUNT = 16 }; + + F32 size; + U32 lifeSpan; + U32 fadeTime; + + S32 texCoordCount; + RectF texRect[MAX_TEXCOORD_COUNT]; + + S32 frame; + bool randomize; + S32 texRows; + S32 texCols; + + F32 startPixRadius; + F32 endPixRadius; + + String materialName; + SimObjectPtr material; + BaseMatInstance *matInst; + + String lookupName; + + U8 renderPriority; + S32 clippingMasks; + +public: + + DecalData(); + ~DecalData(); + + DECLARE_CONOBJECT(DecalData); + static void initPersistFields(); + virtual void onStaticModified( const char *slotName, const char *newValue = NULL ); + + virtual bool onAdd(); + virtual void onRemove(); + + virtual bool preload( bool server, String &errorStr ); + virtual void packData( BitStream* ); + virtual void unpackData( BitStream* ); + + Material* getMaterial(); + BaseMatInstance* getMaterialInstance(); + + static SimSet* getSet(); + static DecalData* findDatablock( String lookupName ); + + virtual void inspectPostApply(); + void reloadRects(); + +protected: + + void _initMaterial(); + void _updateMaterial(); +}; + +inline SimSet* DecalData::getSet() +{ + SimSet *set = NULL; + if ( !Sim::findObject( "DecalDataSet", set ) ) + { + set = new SimSet; + set->registerObject( "DecalDataSet" ); + Sim::getRootGroup()->addObject( set ); + } + return set; +} + +DECLARE_CONSOLETYPE( DecalData ); + +#endif // _DECALDATA_H_ \ No newline at end of file diff --git a/T3D/decal/decalDataFile.cpp b/T3D/decal/decalDataFile.cpp new file mode 100644 index 0000000..b0f083a --- /dev/null +++ b/T3D/decal/decalDataFile.cpp @@ -0,0 +1,436 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "decalDataFile.h" + +#include "math/mathIO.h" +#include "core/tAlgorithm.h" +#include "core/stream/fileStream.h" + +#include "T3D/decal/decalManager.h" +#include "T3D/decal/decalData.h" + + +void DecalSphere::updateBounds() +{ + // This will be the distance + // from the sphere center + // of the farthest item out. + F32 distFromCenter = 0; + + // Used to hold the + // computed item distance. + F32 itemDist = 0; + + F32 largestItemSize = 0; + + // Current item. + DecalInstance *inst = NULL; + + for ( U32 i = 0; i < mItems.size(); i++ ) + { + inst = mItems[i]; + + itemDist = (mWorldSphere.center - inst->mPosition).len(); + if ( itemDist > distFromCenter ) + distFromCenter = itemDist; + + if ( inst->mSize > largestItemSize ) + largestItemSize = inst->mSize; + } + + mWorldSphere.radius = distFromCenter + largestItemSize + largestItemSize + 0.5f; +} + +template<> +void* Resource::create( const Torque::Path &path ) +{ + FileStream stream; + stream.open( path.getFullPath(), Torque::FS::File::Read ); + if ( stream.getStatus() != Stream::Ok ) + return NULL; + + DecalDataFile *file = new DecalDataFile(); + if ( !file->read( stream ) ) + { + delete file; + return NULL; + } + + return file; +} + +template<> +ResourceBase::Signature Resource::signature() +{ + return MakeFourCC('d','e','c','f'); +} + +DecalDataFile::DecalDataFile() + : mIsDirty( false ) +{ + mSphereList.setSize( 0 ); +} + +DecalDataFile::~DecalDataFile() +{ + clear(); +} + +void DecalDataFile::clear() +{ + for ( U32 i=0; i < mSphereList.size(); i++ ) + delete mSphereList[i]; + + mSphereList.clear(); + mChunker.freeBlocks(); + + mIsDirty = true; +} + +bool DecalDataFile::write( const char *path ) +{ + // Open the stream. + FileStream stream; + if ( !stream.open( path, Torque::FS::File::Write ) ) + { + Con::errorf( "DecalDataFile::write() - Failed opening stream!" ); + return false; + } + + // Write our identifier... so we have a better + // idea if we're reading pure garbage. + // This identifier stands for "Torque Decal Data File". + stream.write( 4, "TDDF" ); + + // Now the version number. + stream.write( (U8)FILE_VERSION ); + + Vector allDecals; + + // Gather all DecalInstances that should be saved. + for ( U32 i = 0; i < mSphereList.size(); i++ ) + { + Vector::const_iterator item = mSphereList[i]->mItems.begin(); + for ( ; item != mSphereList[i]->mItems.end(); item++ ) + { + if ( (*item)->mFlags & SaveDecal ) + allDecals.push_back( (*item) ); + } + } + + // Gather all the DecalData datablocks used. + Vector allDatablocks; + for ( U32 i = 0; i < allDecals.size(); i++ ) + allDatablocks.push_back_unique( allDecals[i]->mDataBlock ); + + // Write out datablock lookupNames. + U32 count = allDatablocks.size(); + stream.write( count ); + for ( U32 i = 0; i < count; i++ ) + stream.write( allDatablocks[i]->lookupName ); + + Vector::iterator dataIter; + + // Write out the DecalInstance list. + count = allDecals.size(); + stream.write( count ); + for ( U32 i = 0; i < count; i++ ) + { + DecalInstance *inst = allDecals[i]; + + dataIter = find( allDatablocks.begin(), allDatablocks.end(), inst->mDataBlock ); + U8 dataIndex = dataIter - allDatablocks.begin(); + + stream.write( dataIndex ); + mathWrite( stream, inst->mPosition ); + mathWrite( stream, inst->mNormal ); + mathWrite( stream, inst->mTangent ); + stream.write( inst->mTextureRectIdx ); + stream.write( inst->mSize ); + stream.write( inst->mRenderPriority ); + } + + // Clear the dirty flag. + mIsDirty = false; + + return true; +} + +bool DecalDataFile::read( Stream &stream ) +{ + // NOTE: we are shortcutting by just saving out the DecalInst and + // using regular addDecal methods to add them, which will end up + // generating the DecalSphere(s) in the process. + // It would be more efficient however to just save out all the data + // and read it all in with no calculation required. + + // Read our identifier... so we know we're + // not reading in pure garbage. + char id[4] = { 0 }; + stream.read( 4, id ); + if ( dMemcmp( id, "TDDF", 4 ) != 0 ) + { + Con::errorf( "DecalDataFile::read() - This is not a Decal file!" ); + return false; + } + + // Empty ourselves before we really begin reading. + clear(); + + // Now the version number. + U8 version; + stream.read( &version ); + if ( version != (U8)FILE_VERSION ) + { + Con::errorf( "DecalDataFile::read() - file versions do not match!" ); + Con::errorf( "You must manually delete the old .decals file before continuing!" ); + return false; + } + + // Read in the lookupNames of the DecalData datablocks and recover the datablock. + Vector allDatablocks; + U32 count; + stream.read( &count ); + allDatablocks.setSize( count ); + for ( U32 i = 0; i < count; i++ ) + { + String lookupName; + stream.read( &lookupName ); + + DecalData *data = DecalData::findDatablock( lookupName ); + + if ( data ) + allDatablocks[ i ] = data; + else + { + allDatablocks[ i ] = NULL; + Con::errorf( "DecalDataFile::read() - DecalData %s does not exist!", lookupName.c_str() ); + } + } + + U8 dataIndex; + DecalData *data; + + // Now read all the DecalInstance(s). + stream.read( &count ); + for ( U32 i = 0; i < count; i++ ) + { + DecalInstance *inst = _allocateInstance(); + + stream.read( &dataIndex ); + mathRead( stream, &inst->mPosition ); + mathRead( stream, &inst->mNormal ); + mathRead( stream, &inst->mTangent ); + stream.read( &inst->mTextureRectIdx ); + stream.read( &inst->mSize ); + stream.read( &inst->mRenderPriority ); + + inst->mVisibility = 1.0f; + inst->mFlags = PermanentDecal | SaveDecal | ClipDecal; + inst->mCreateTime = Sim::getCurrentTime(); + inst->mVerts = NULL; + inst->mIndices = NULL; + inst->mVertCount = 0; + inst->mIndxCount = 0; + + data = allDatablocks[ dataIndex ]; + + if ( data ) + { + inst->mDataBlock = data; + + addDecal( inst ); + + // onload set instances should get added to the appropriate vec + inst->mId = gDecalManager->mDecalInstanceVec.size(); + gDecalManager->mDecalInstanceVec.push_back(inst); + } + else + { + _freeInstance( inst ); + Con::errorf( "DecalDataFile::read - cannot find DecalData for DecalInstance read from disk." ); + } + } + + // Clear the dirty flag. + mIsDirty = false; + + return true; +} + +DecalInstance* DecalDataFile::_allocateInstance() +{ + DecalInstance *decal = mChunker.alloc(); + decal->mRenderPriority = 0; + decal->mCustomTex = NULL; + decal->mId = -1; + + // TODO: Add a real constructor! + + return decal; +} + +void DecalDataFile::addDecal( DecalInstance *inst ) +{ + DecalSphere *closestSphere = NULL; + F32 closestDist = F32_MAX; + + // Might want to expose these. + const F32 distanceTol = 10.0f; + const F32 maxRadius = 10.0f; + + // Validate the decal instance. + //inst->mTextureRectIdx = getMin( inst->mTextureRectIdx, + // (U32)DecalData::MAX_TEXCOORD_COUNT ); + + // First find the closest existing sphere to this + // item that is within our tolerance. + for ( U32 i = 0; i < mSphereList.size(); i++ ) + { + DecalSphere *sphere = mSphereList[i]; + const SphereF &bounds = sphere->mWorldSphere; + + F32 dist = bounds.distanceTo( inst->mPosition ); + + if ( dist > distanceTol ) + continue; + else if ( dist < 0.0f ) + { + // This point is inside the + // sphere, so we can just add this item. + closestDist = distanceTol; + closestSphere = sphere; + break; + } + + // If the new radius is greater + // than the max radius, break, + // and add a new sphere. + F32 newRadius = dist + bounds.radius + 0.5f + inst->mSize; + SphereF newBounds( bounds.center, newRadius ); + + if ( newRadius > maxRadius ) + continue; + + if ( distanceTol < closestDist ) + { + closestDist = distanceTol; + closestSphere = sphere; + closestSphere->mWorldSphere = newBounds; + } + } + + // If we didn't find an existing cell... create one. + if ( !closestSphere ) + { + F32 itemSize = inst->mSize; + F32 radius = itemSize * itemSize + 0.5f; + const Point3F &itemPos = inst->mPosition; + Point3F offsetPos( itemPos ); + offsetPos.y += itemSize; + SphereF newSphere( offsetPos, radius ); + + radius += mFabs( newSphere.distanceTo( itemPos ) ); + + closestSphere = new DecalSphere( itemPos, radius ); + mSphereList.push_back( closestSphere ); + } + + // And add the DecalInst to the sphere we either just created + // or found earlier. + closestSphere->mItems.push_back( inst ); + closestSphere->updateBounds(); +} + +void DecalDataFile::removeDecal( DecalInstance *inst ) +{ + DecalSphere *pSphere = NULL; + + for ( U32 i = 0; i < mSphereList.size(); i++ ) + { + pSphere = mSphereList[i]; + + Vector &items = pSphere->mItems; + + //Vector::iterator found = find( items.begin(), items.last(), inst ); + bool found = items.remove( inst ); + + if ( found ) + { + if ( items.empty() ) + { + delete pSphere; + mSphereList.erase( i ); + } + else + pSphere->updateBounds(); + + _freeInstance( inst ); + + return; + } + } + + Con::errorf( "DecalDataFile did not contain a DecalInstance passed to removeData!" ); +} + +void DecalDataFile::notifyDecalModified( DecalInstance *inst ) +{ + // Delete buffers, need to be regenerated the next time + // this instance is rendered. + + // Currently the decal editor itself handles reclipping decals + // that are modified, since it needs the edge verts anyway, + // but might want to do that here in the future. + + /* + if ( inst->mVerts ) + { + delete inst->mVerts; + inst->mVerts = NULL; + + delete inst->mIndices; + inst->mIndices = NULL; + } + + inst->mFlags |= ClipDecal; + */ + + // Find the DecalSphere containing this DecalInst, + // Remove it from that sphere, + // Delete the sphere if that was the last DecalInst, + // Re-add the DecalInst to the DecalDataFile. + + DecalSphere *pSphere = NULL; + + for ( U32 i = 0; i < mSphereList.size(); i++ ) + { + pSphere = mSphereList[i]; + + Vector &items = pSphere->mItems; + + bool found = items.remove( inst ); + + if ( found ) + { + if ( items.empty() ) + { + delete pSphere; + mSphereList.erase( i ); + } + else + pSphere->updateBounds(); + + addDecal( inst ); + + return; + } + } + + Con::errorf( "DecalDataFile did not contain a DecalInstance passed to notifyDecalModified!" ); +} + diff --git a/T3D/decal/decalDataFile.h b/T3D/decal/decalDataFile.h new file mode 100644 index 0000000..1e90298 --- /dev/null +++ b/T3D/decal/decalDataFile.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DECALDATAFILE_H_ +#define _DECALDATAFILE_H_ + +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MSPHERE_H_ +#include "math/mSphere.h" +#endif + + +class DecalInstance; +class Stream; + +/// A bounding sphere in world space and a list of DecalInstance(s) +/// contained by it. DecalInstance(s) are organized/binned in this fashion +/// as a lookup and culling optimization. +class DecalSphere +{ +public: + + DecalSphere() {} + DecalSphere( const Point3F &position, F32 radius ) + { + mWorldSphere.center = position; + mWorldSphere.radius = radius; + } + + void updateBounds(); + + Vector mItems; + SphereF mWorldSphere; +}; + +/// This is the data file for decals. +/// Not intended to be used directly, do your work with decals +/// via the DecalManager. +class DecalDataFile +{ + friend class DecalManager; + +public: + + DecalDataFile(); + virtual ~DecalDataFile(); + + Vector& getGrid() { return mSphereList; } + const Vector& getGrid() const { return mSphereList; } + + /// Deletes all the data and resets the + /// file to an empty state. + void clear(); + + /// + bool write( const char *path ); + + /// + bool read( Stream &stream ); + + void addDecal( DecalInstance *inst ); + void removeDecal( DecalInstance *inst ); + void notifyDecalModified( DecalInstance *inst ); + +protected: + + DecalInstance* _allocateInstance(); + void _freeInstance( DecalInstance *decal ); + +protected: + + enum { FILE_VERSION = 5 }; + + /// Set to true if the file is dirty and + /// needs to be saved before being destroyed. + bool mIsDirty; + + /// List of bounding sphere shapes that contain and organize + /// DecalInstances for optimized culling and lookup. + Vector mSphereList; + + FreeListChunker mChunker; +}; + +inline void DecalDataFile::_freeInstance( DecalInstance *decal ) +{ + mChunker.free( decal ); +} + +#endif // _DECALDATAFILE_H_ diff --git a/T3D/decal/decalInstance.cpp b/T3D/decal/decalInstance.cpp new file mode 100644 index 0000000..d4f8319 --- /dev/null +++ b/T3D/decal/decalInstance.cpp @@ -0,0 +1,60 @@ + +#include "platform/platform.h" +#include "T3D/decal/decalInstance.h" +#include "sceneGraph/sceneState.h" + +void DecalInstance::getWorldMatrix( MatrixF *outMat, bool flip ) +{ + outMat->setPosition( mPosition ); + + Point3F fvec; + mCross( mNormal, mTangent, &fvec ); + + outMat->setColumn( 0, mTangent ); + outMat->setColumn( 1, fvec ); + outMat->setColumn( 2, mNormal ); +} + +F32 DecalInstance::calcPixelRadius( const SceneState *state ) const +{ + const Point2I &viewportExtent = state->getViewportExtent(); + const F32 pixelScale = viewportExtent.y / 300.0f; + F32 decalSize = getMax( mSize, 0.001f ); + + Point3F cameraOffset = state->getCameraPosition() - mPosition; + + F32 dist = getMax( cameraOffset.len(), 0.01f ); + + F32 invScale = 1.0f / decalSize; + F32 scaledDistance = dist * invScale; + + return state->projectRadius( scaledDistance, decalSize ) * pixelScale; +} + +F32 DecalInstance::calcEndPixRadius( const Point2I &viewportExtent ) const +{ + const F32 pixelScale = viewportExtent.y / 300.0f; + F32 decalSize = getMax( mSize, 0.001f ); + + return mDataBlock->endPixRadius * decalSize * pixelScale; +} + +void DecalInstance::setPosition( const Point3F & pos ) +{ + mPosition = pos; +} + +void DecalInstance::setTangent( const Point3F & tangent ) +{ + mTangent = tangent; +} + +const Point3F & DecalInstance::getTangent() +{ + return mTangent; +} + +const Point3F & DecalInstance::getPosition() +{ + return mPosition; +} \ No newline at end of file diff --git a/T3D/decal/decalInstance.h b/T3D/decal/decalInstance.h new file mode 100644 index 0000000..ebb5dab --- /dev/null +++ b/T3D/decal/decalInstance.h @@ -0,0 +1,81 @@ + +#ifndef _DECALINSTANCE_H_ +#define _DECALINSTANCE_H_ + +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + +#ifndef _DECALDATA_H_ +#include "T3D/decal/decalData.h" +#endif + +struct DecalVertex; +class SceneState; + +/// DecalInstance represents a rendering decal in the scene. +/// You should not allocate this yourself, add new decals to the scene +/// via the DecalManager. +/// All data is public, change it if you wish, but be sure to inform +/// the DecalManager. +class DecalInstance +{ +public: + + DecalData *mDataBlock; + + Point3F mPosition; + Point3F mNormal; + Point3F mTangent; + F32 mRotAroundNormal; + F32 mSize; + + U32 mCreateTime; + F32 mVisibility; + + U32 mTextureRectIdx; + + DecalVertex *mVerts; + U16 *mIndices; + + U32 mVertCount; + U32 mIndxCount; + + U8 mFlags; + + U8 mRenderPriority; + + S32 mId; + + GFXTexHandle *mCustomTex; + +public: + ///¿ÉÒÔÔÚÍâ½ç¿ØÖƵÄһЩ½Ó¿Ú + void setPosition(const Point3F & pos); + const Point3F & getPosition(); + void setTangent(const Point3F & tangent); + const Point3F & getTangent(); + + /// + void getWorldMatrix( MatrixF *outMat, bool flip = false ); + + /// + U8 getRenderPriority() const; + + /// Calculates the screen pixel radius of the decal, used for LOD. + F32 calcPixelRadius( const SceneState *state ) const; + + /// Calculates the "real" end pixel radius of the decal based on + /// its size and the setting for endPixRadius in the DecalData. + F32 calcEndPixRadius( const Point2I &viewportExtent ) const; + + /// The constructor ss + DecalInstance() : mId(-1) {} +}; + +inline U8 DecalInstance::getRenderPriority() const +{ + return mRenderPriority == 0 ? mDataBlock->renderPriority : mRenderPriority; +} + +#endif // _DECALINSTANCE_H_ diff --git a/T3D/decal/decalManager.cpp b/T3D/decal/decalManager.cpp new file mode 100644 index 0000000..4f2a48a --- /dev/null +++ b/T3D/decal/decalManager.cpp @@ -0,0 +1,1386 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/decal/decalManager.h" + +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "ts/tsShapeInstance.h" +#include "console/console.h" +#include "console/dynamicTypes.h" +#include "gfx/primBuilder.h" +#include "console/consoleTypes.h" +#include "platform/profiler.h" +#include "gfx/gfxTransformSaver.h" +#include "lighting/lightManager.h" +#include "lighting/lightInfo.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/sim/gfxStateBlockData.h" +#include "materials/shaderData.h" +#include "renderInstance/renderPassManager.h" +#include "core/resourceManager.h" +#include "gfx/gfxDebugEvent.h" +#include "math/util/quadTransforms.h" +#include "core/volume.h" + +#include "T3D/decal/decalData.h" + +/// A bias applied to the nearPlane for Decal and DecalRoad rendering. +/// Is set by by LevelInfo. +F32 gDecalBias = 0.0015f; + +bool DecalManager::smDecalsOn = true; +F32 DecalManager::smDecalLifeTimeScale = 1.0f; +const U32 DecalManager::smMaxVerts = 6000; +const U32 DecalManager::smMaxIndices = 10000; + +DecalManager *gDecalManager = NULL; + +IMPLEMENT_CONOBJECT(DecalManager); + +namespace { + +int QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2) +{ + const DecalInstance** pd1 = (const DecalInstance**)p1; + const DecalInstance** pd2 = (const DecalInstance**)p2; + + return int(((char *)(*pd1)->mDataBlock) - ((char *)(*pd2)->mDataBlock)); +} + +int QSORT_CALLBACK cmpPointsXY( const void *p1, const void *p2 ) +{ + const Point3F *pnt1 = (const Point3F*)p1; + const Point3F *pnt2 = (const Point3F*)p2; + + if ( pnt1->x < pnt2->x ) + return -1; + else if ( pnt1->x > pnt2->x ) + return 1; + else if ( pnt1->y < pnt2->y ) + return -1; + else if ( pnt1->y > pnt2->y ) + return 1; + else + return 0; +} + +int QSORT_CALLBACK cmpQuadPointTheta( const void *p1, const void *p2 ) +{ + const Point4F *pnt1 = (const Point4F*)p1; + const Point4F *pnt2 = (const Point4F*)p2; + + if ( mFabs( pnt1->w ) > mFabs( pnt2->w ) ) + return 1; + else if ( mFabs( pnt1->w ) < mFabs( pnt2->w ) ) + return -1; + else + return 0; +} + +static Point3F gSortPoint; + +int QSORT_CALLBACK cmpDecalDistance( const void *p1, const void *p2 ) +{ + const DecalInstance** pd1 = (const DecalInstance**)p1; + const DecalInstance** pd2 = (const DecalInstance**)p2; + + F32 dist1 = ( (*pd1)->mPosition - gSortPoint ).lenSquared(); + F32 dist2 = ( (*pd2)->mPosition - gSortPoint ).lenSquared(); + + return dist1 - dist2; +} + +int QSORT_CALLBACK cmpDecalRenderOrder( const void *p1, const void *p2 ) +{ + const DecalInstance** pd1 = (const DecalInstance**)p1; + const DecalInstance** pd2 = (const DecalInstance**)p2; + + if ( ( (*pd2)->mFlags & SaveDecal ) && !( (*pd1)->mFlags & SaveDecal ) ) + return -1; + else if ( !( (*pd2)->mFlags & SaveDecal ) && ( (*pd1)->mFlags & SaveDecal ) ) + return 1; + else + { + int priority = (*pd1)->getRenderPriority() - (*pd2)->getRenderPriority(); + + if ( priority != 0 ) + return priority; + + if ( (*pd2)->mFlags & SaveDecal ) + { + int id = ( (*pd1)->mDataBlock->getMaterial()->getId() - (*pd2)->mDataBlock->getMaterial()->getId() ); + if ( id != 0 ) + return id; + + return (*pd1)->mCreateTime - (*pd2)->mCreateTime; + } + else + return (*pd1)->mCreateTime - (*pd2)->mCreateTime; + } +} + +} // namespace {} + +// These numbers should be tweaked to get as many dynamically placed decals +// as possible to allocate buffer arrays with the FreeListChunker. +enum +{ + SIZE_CLASS_0 = 256, + SIZE_CLASS_1 = 512, + SIZE_CLASS_2 = 1024, + + NUM_SIZE_CLASSES = 3 +}; + +//------------------------------------------------------------------------- +// DecalManager +//------------------------------------------------------------------------- +DecalManager::DecalManager() +{ + mObjBox.minExtents.set(-1e7, -1e7, -1e7); + mObjBox.maxExtents.set( 1e7, 1e7, 1e7); + mWorldBox.minExtents.set(-1e7, -1e7, -1e7); + mWorldBox.maxExtents.set( 1e7, 1e7, 1e7); + + mDataFileName = NULL; + + mTypeMask |= EnvironmentObjectType; + + mDirty = false; + + mChunkers[0] = new FreeListChunkerUntyped( SIZE_CLASS_0 * sizeof( U8 ) ); + mChunkers[1] = new FreeListChunkerUntyped( SIZE_CLASS_1 * sizeof( U8 ) ); + mChunkers[2] = new FreeListChunkerUntyped( SIZE_CLASS_2 * sizeof( U8 ) ); +} + +DecalManager::~DecalManager() +{ + clearData(); + + for( U32 i = 0; i < NUM_SIZE_CLASSES; ++ i ) + delete mChunkers[ i ]; +} + +void DecalManager::consoleInit() +{ + Con::addVariable( "$pref::decalsOn", TypeBool, &smDecalsOn ); + Con::addVariable( "$pref::Decal::decalLifeTimeScale", TypeF32, &smDecalLifeTimeScale ); +} + +bool DecalManager::clipDecal( DecalInstance *decal, Vector *edgeVerts, const Point2F *clipDepth ) +{ + PROFILE_SCOPE( DecalManager_clipDecal ); + + // Free old verts and indices. + _freeBuffers( decal ); + + ClippedPolyList clipper; + + clipper.mNormal.set( Point3F( 0, 0, 0 ) ); + clipper.mPlaneList.setSize(6); + + F32 halfSize = decal->mSize * 0.5f; + + // Ugly hack for ProjectedShadow! + F32 halfSizeZ = clipDepth ? clipDepth->x : halfSize; + F32 negHalfSize = clipDepth ? clipDepth->y : halfSize; + Point3F decalHalfSize( halfSize, halfSize, halfSize ); + Point3F decalHalfSizeZ( halfSizeZ, halfSizeZ, halfSizeZ ); + + MatrixF projMat( true ); + decal->getWorldMatrix( &projMat ); + + const VectorF &crossVec = decal->mNormal; + const Point3F &decalPos = decal->mPosition; + + VectorF newFwd, newRight; + projMat.getColumn( 0, &newRight ); + projMat.getColumn( 1, &newFwd ); + + VectorF objRight( 1.0f, 0, 0 ); + VectorF objFwd( 0, 1.0f, 0 ); + VectorF objUp( 0, 0, 1.0f ); + + // See above re: decalHalfSizeZ hack. + clipper.mPlaneList[0].set( ( decalPos + ( -newRight * halfSize ) ), -newRight ); + clipper.mPlaneList[1].set( ( decalPos + ( -newFwd * halfSize ) ), -newFwd ); + clipper.mPlaneList[2].set( ( decalPos + ( -crossVec * decalHalfSizeZ ) ), -crossVec ); + clipper.mPlaneList[3].set( ( decalPos + ( newRight * halfSize ) ), newRight ); + clipper.mPlaneList[4].set( ( decalPos + ( newFwd * halfSize ) ), newFwd ); + clipper.mPlaneList[5].set( ( decalPos + ( crossVec * negHalfSize ) ), crossVec ); + + clipper.mNormal = -decal->mNormal; + + Box3F box( -decalHalfSizeZ, decalHalfSizeZ ); + + projMat.mul( box ); + + DecalData *decalData = decal->mDataBlock; + + PROFILE_START( DecalManager_clipDecal_buildPolyList ); + getContainer()->buildPolyList( box, decalData->clippingMasks, &clipper ); + PROFILE_END(); + + clipper.cullUnusedVerts(); + clipper.triangulate(); + clipper.generateNormals(); + + if ( clipper.mVertexList.empty() ) + return false; + +#ifdef DECALMANAGER_DEBUG + mDebugPlanes.clear(); + mDebugPlanes.merge( clipper.mPlaneList ); +#endif + + decal->mVertCount = clipper.mVertexList.size(); + decal->mIndxCount = clipper.mIndexList.size(); + + Vector tmpPoints; + + tmpPoints.push_back(( objFwd * decalHalfSize ) + ( objRight * decalHalfSize )); + tmpPoints.push_back(( objFwd * decalHalfSize ) + ( -objRight * decalHalfSize )); + tmpPoints.push_back(( -objFwd * decalHalfSize ) + ( -objRight * decalHalfSize )); + + Point3F lowerLeft(( -objFwd * decalHalfSize ) + ( objRight * decalHalfSize )); + + projMat.inverse(); + + _generateWindingOrder( lowerLeft, &tmpPoints ); + + BiQuadToSqr quadToSquare( Point2F( lowerLeft.x, lowerLeft.y ), + Point2F( tmpPoints[0].x, tmpPoints[0].y ), + Point2F( tmpPoints[1].x, tmpPoints[1].y ), + Point2F( tmpPoints[2].x, tmpPoints[2].y ) ); + + Point2F uv( 0, 0 ); + Point3F vecX(0.0f, 0.0f, 0.0f); + + // Allocate memory for vert and index arrays + _allocBuffers( decal ); + + Point3F vertPoint( 0, 0, 0 ); + + for ( U32 i = 0; i < clipper.mVertexList.size(); i++ ) + { + const ClippedPolyList::Vertex &vert = clipper.mVertexList[i]; + vertPoint = vert.point; + + // Transform this point to + // object space to look up the + // UV coordinate for this vertex. + projMat.mulP( vertPoint ); + + // Clamp the point to be within the quad. + vertPoint.x = mClampF( vertPoint.x, -decalHalfSize.x, decalHalfSize.x ); + vertPoint.y = mClampF( vertPoint.y, -decalHalfSize.y, decalHalfSize.y ); + + // Get our UV. + uv = quadToSquare.transform( Point2F( vertPoint.x, vertPoint.y ) ); + + const RectF &rect = decal->mDataBlock->texRect[decal->mTextureRectIdx]; + + uv *= rect.extent; + uv += rect.point; + + // Set the world space vertex position. + decal->mVerts[i].point = vert.point; + + decal->mVerts[i].texCoord.set( uv.x, uv.y ); + + decal->mVerts[i].normal = clipper.mNormalList[i]; + + decal->mVerts[i].normal.normalize(); + + if( mFabs( decal->mVerts[i].normal.z ) > 0.8f ) + mCross( decal->mVerts[i].normal, Point3F( 1.0f, 0.0f, 0.0f ), &vecX ); + else if ( mFabs( decal->mVerts[i].normal.x ) > 0.8f ) + mCross( decal->mVerts[i].normal, Point3F( 0.0f, 1.0f, 0.0f ), &vecX ); + else if ( mFabs( decal->mVerts[i].normal.y ) > 0.8f ) + mCross( decal->mVerts[i].normal, Point3F( 0.0f, 0.0f, 1.0f ), &vecX ); + + decal->mVerts[i].tangent = mCross( decal->mVerts[i].normal, vecX ); + } + + U32 curIdx = 0; + for ( U32 j = 0; j < clipper.mPolyList.size(); j++ ) + { + // Write indices for each Poly + ClippedPolyList::Poly *poly = &clipper.mPolyList[j]; + + AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" ); + + decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart]; + curIdx++; + decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart + 1]; + curIdx++; + decal->mIndices[curIdx] = clipper.mIndexList[poly->vertexStart + 2]; + curIdx++; + } + + if ( !edgeVerts ) + return true; + + Point3F tmpHullPt( 0, 0, 0 ); + Vector tmpHullPts; + + for ( U32 i = 0; i < clipper.mVertexList.size(); i++ ) + { + const ClippedPolyList::Vertex &vert = clipper.mVertexList[i]; + tmpHullPt = vert.point; + projMat.mulP( tmpHullPt ); + tmpHullPts.push_back( tmpHullPt ); + } + + edgeVerts->clear(); + U32 verts = _generateConvexHull( tmpHullPts, edgeVerts ); + edgeVerts->setSize( verts ); + + projMat.inverse(); + for ( U32 i = 0; i < edgeVerts->size(); i++ ) + projMat.mulP( (*edgeVerts)[i] ); + + return true; +} + +DecalInstance* DecalManager::addDecal( const Point3F &pos, + const Point3F &normal, + F32 rotAroundNormal, + DecalData *decalData, + F32 decalScale, + S32 decalTexIndex, + U8 flags ) +{ + MatrixF mat( true ); + MathUtils::getMatrixFromUpVector( normal, &mat ); + + AngAxisF rot( normal, rotAroundNormal ); + MatrixF rotmat; + rot.setMatrix( &rotmat ); + mat.mul( rotmat ); + + Point3F tangent; + mat.getColumn( 1, &tangent ); + + return addDecal( pos, normal, tangent, decalData, decalScale, decalTexIndex, flags ); +} + +DecalInstance* DecalManager::addDecal( const Point3F &pos, + const Point3F &normal, + const Point3F &tangent, + DecalData *decalData, + F32 decalScale, + S32 decalTexIndex, + U8 flags ) +{ + if ( !mData && !_createDataFile() ) + return NULL; + + // only dirty the manager if this decal should be saved + if ( flags & SaveDecal ) + mDirty = true; + + Point3F vecX, vecY, norm; + DecalInstance *newDecal = mData->_allocateInstance(); + + newDecal->mPosition = pos; + newDecal->mNormal = normal; + newDecal->mTangent = tangent; + + newDecal->mSize = decalData->size * decalScale; + + newDecal->mDataBlock = decalData; + + S32 frame = newDecal->mDataBlock->frame; + // randomize the frame if the flag is set. this number is used directly below us + // when calculating render coords + if ( decalData->randomize ) + frame = gRandGen.randI(); + + frame %= getMax( decalData->texCoordCount, 0 ) + 1; + + + newDecal->mTextureRectIdx = frame; + + newDecal->mVisibility = 1.0f; + + newDecal->mCreateTime = Sim::getCurrentTime(); + + newDecal->mVerts = NULL; + newDecal->mIndices = NULL; + newDecal->mVertCount = 0; + newDecal->mIndxCount = 0; + + newDecal->mFlags = flags; + newDecal->mFlags |= ClipDecal; + + mData->addDecal( newDecal ); + + return newDecal; +} + +void DecalManager::removeDecal( DecalInstance *inst ) +{ + // If this is a decal we save then we need + // to set the dirty flag. + if ( inst->mFlags & SaveDecal ) + mDirty = true; + + // Remove the decal from the instance vector. + + if( inst->mId != -1 && inst->mId < mDecalInstanceVec.size() ) + mDecalInstanceVec[ inst->mId ] = NULL; + + // Release its geometry (if it has any). + + _freeBuffers( inst ); + + // Remove it from the decal file. + + if ( mData ) + mData->removeDecal( inst ); +} + +DecalInstance* DecalManager::getDecal( S32 id ) +{ + if( id < 0 || id >= mDecalInstanceVec.size() ) + return NULL; + + return mDecalInstanceVec[id]; +} + +void DecalManager::notifyDecalModified( DecalInstance *inst ) +{ + // If this is a decal we save then we need + // to set the dirty flag. + if ( inst->mFlags & SaveDecal ) + mDirty = true; + + if ( mData ) + mData->notifyDecalModified( inst ); +} + +DecalInstance* DecalManager::getClosestDecal( const Point3F &pos ) +{ + if ( !mData ) + return NULL; + + const Vector &grid = mData->getGrid(); + + DecalInstance *inst = NULL; + SphereF worldPickSphere( pos, 0.5f ); + SphereF worldInstSphere( Point3F( 0, 0, 0 ), 1.0f ); + + Vector collectedInsts; + + for ( U32 i = 0; i < grid.size(); i++ ) + { + DecalSphere *decalSphere = grid[i]; + const SphereF &worldSphere = decalSphere->mWorldSphere; + if ( !worldSphere.isIntersecting( worldPickSphere ) && + !worldSphere.isContained( pos ) ) + continue; + + const Vector &items = decalSphere->mItems; + for ( U32 n = 0; n < items.size(); n++ ) + { + inst = items[n]; + if ( !inst ) + continue; + + worldInstSphere.center = inst->mPosition; + worldInstSphere.radius = inst->mSize; + + if ( !worldInstSphere.isContained( inst->mPosition ) ) + continue; + + collectedInsts.push_back( inst ); + } + } + + F32 closestDistance = F32_MAX; + F32 currentDist = 0; + U32 closestIndex = 0; + for ( U32 i = 0; i < collectedInsts.size(); i++ ) + { + inst = collectedInsts[i]; + currentDist = (inst->mPosition - pos).len(); + if ( currentDist < closestDistance ) + { + closestIndex = i; + closestDistance = currentDist; + worldInstSphere.center = inst->mPosition; + worldInstSphere.radius = inst->mSize; + } + } + + if ( !collectedInsts.empty() && + collectedInsts[closestIndex] && + closestDistance < 1.0f || + worldInstSphere.isContained( pos ) ) + return collectedInsts[closestIndex]; + else + return NULL; +} + +DecalInstance* DecalManager::raycast( const Point3F &start, const Point3F &end, bool savedDecalsOnly ) +{ + if ( !mData ) + return NULL; + + const Vector &grid = mData->getGrid(); + + DecalInstance *inst = NULL; + SphereF worldSphere( Point3F( 0, 0, 0 ), 1.0f ); + + Vector hitDecals; + + for ( U32 i = 0; i < grid.size(); i++ ) + { + DecalSphere *decalSphere = grid[i]; + if ( !decalSphere->mWorldSphere.intersectsRay( start, end ) ) + continue; + + const Vector &items = decalSphere->mItems; + for ( U32 n = 0; n < items.size(); n++ ) + { + inst = items[n]; + if ( !inst ) + continue; + + if ( savedDecalsOnly && !(inst->mFlags & SaveDecal) ) + continue; + + worldSphere.center = inst->mPosition; + worldSphere.radius = inst->mSize; + + if ( !worldSphere.intersectsRay( start, end ) ) + continue; + + RayInfo ri; + bool containsPoint = false; + if ( gServerContainer.castRayRendered( start, end, STATIC_COLLISION_MASK, &ri ) ) + { + Point2F poly[4]; + poly[0].set( inst->mPosition.x - (inst->mSize / 2), inst->mPosition.y + (inst->mSize / 2)); + poly[1].set( inst->mPosition.x - (inst->mSize / 2), inst->mPosition.y - (inst->mSize / 2)); + poly[2].set( inst->mPosition.x + (inst->mSize / 2), inst->mPosition.y - (inst->mSize / 2)); + poly[3].set( inst->mPosition.x + (inst->mSize / 2), inst->mPosition.y + (inst->mSize / 2)); + + if ( MathUtils::pointInPolygon( poly, 4, Point2F(ri.point.x, ri.point.y) ) ) + containsPoint = true; + } + + if( !containsPoint ) + continue; + + hitDecals.push_back( inst ); + } + } + + if ( hitDecals.empty() ) + return NULL; + + gSortPoint = start; + dQsort( hitDecals.address(), hitDecals.size(), sizeof(DecalInstance*), cmpDecalDistance ); + return hitDecals[0]; +} + +U32 DecalManager::_generateConvexHull( const Vector &points, Vector *outPoints ) +{ + // chainHull_2D(): Andrew's monotone chain 2D convex hull algorithm + // Input: P[] = an array of 2D points + // presorted by increasing x- and y-coordinates + // n = the number of points in P[] + // Output: H[] = an array of the convex hull vertices (max is n) + // Return: the number of points in H[] + //int + + if ( points.size() < 3 ) + { + outPoints->merge( points ); + return outPoints->size(); + } + + // Sort our input points. + dQsort( points.address(), points.size(), sizeof( Point3F ), cmpPointsXY ); + + U32 n = points.size(); + + Vector tmpPoints; + tmpPoints.setSize( n ); + + // the output array H[] will be used as the stack + S32 bot=0, top=(-1); // indices for bottom and top of the stack + S32 i; // array scan index + S32 toptmp = 0; + + // Get the indices of points with min x-coord and min|max y-coord + S32 minmin = 0, minmax; + F32 xmin = points[0].x; + for ( i = 1; i < n; i++ ) + if (points[i].x != xmin) + break; + + minmax = i - 1; + if ( minmax == n - 1 ) + { + // degenerate case: all x-coords == xmin + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[minmin]; + + if ( points[minmax].y != points[minmin].y ) // a nontrivial segment + { + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[minmax]; + } + + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[minmin]; // add polygon endpoint + + return top+1; + } + + // Get the indices of points with max x-coord and min|max y-coord + S32 maxmin, maxmax = n-1; + F32 xmax = points[n-1].x; + + for ( i = n - 2; i >= 0; i-- ) + if ( points[i].x != xmax ) + break; + + maxmin = i + 1; + + // Compute the lower hull on the stack H + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[minmin]; // push minmin point onto stack + + i = minmax; + while ( ++i <= maxmin ) + { + // the lower line joins P[minmin] with P[maxmin] + if ( isLeft( points[minmin], points[maxmin], points[i]) >= 0 && i < maxmin ) + continue; // ignore P[i] above or on the lower line + + while (top > 0) // there are at least 2 points on the stack + { + // test if P[i] is left of the line at the stack top + if ( isLeft( tmpPoints[top-1], tmpPoints[top], points[i]) > 0) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[i]; // push P[i] onto stack + } + + // Next, compute the upper hull on the stack H above the bottom hull + if (maxmax != maxmin) // if distinct xmax points + { + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[maxmax]; // push maxmax point onto stack + } + + bot = top; // the bottom point of the upper hull stack + i = maxmin; + while (--i >= minmax) + { + // the upper line joins P[maxmax] with P[minmax] + if ( isLeft( points[maxmax], points[minmax], points[i] ) >= 0 && i > minmax ) + continue; // ignore P[i] below or on the upper line + + while ( top > bot ) // at least 2 points on the upper stack + { + // test if P[i] is left of the line at the stack top + if ( isLeft( tmpPoints[top-1], tmpPoints[top], points[i] ) > 0 ) + break; // P[i] is a new hull vertex + else + top--; // pop top point off stack + } + + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[i]; // push P[i] onto stack + } + + if (minmax != minmin) + { + toptmp = top + 1; + if ( toptmp < n ) + tmpPoints[++top] = points[minmin]; // push joining endpoint onto stack + } + + outPoints->merge( tmpPoints ); + + return top + 1; +} + +void DecalManager::_generateWindingOrder( const Point3F &cornerPoint, Vector *sortPoints ) +{ + // This block of code is used to find + // the winding order for the points in our quad. + + // First, choose an arbitrary corner point. + // We'll use the "lowerRight" point. + Point3F relPoint( 0, 0, 0 ); + + // See comment below about radius. + //F32 radius = 0; + + F32 theta = 0; + + Vector tmpPoints; + + for ( U32 i = 0; i < (*sortPoints).size(); i++ ) + { + const Point3F &pnt = (*sortPoints)[i]; + relPoint = cornerPoint - pnt; + + // Get the radius (r^2 = x^2 + y^2). + + // This is commented because for a quad + // you typically can't have the same values + // for theta, which is the caveat which would + // require sorting by the radius. + //radius = mSqrt( (relPoint.x * relPoint.x) + (relPoint.y * relPoint.y) ); + + // Get the theta value for the + // interval -PI, PI. + + // This algorithm for determining the + // theta value is defined by + // | arctan( y / x ) if x > 0 + // | arctan( y / x ) if x < 0 and y >= 0 + // theta = | arctan( y / x ) if x < 0 and y < 0 + // | PI / 2 if x = 0 and y > 0 + // | -( PI / 2 ) if x = 0 and y < 0 + if ( relPoint.x > 0.0f ) + theta = mAtan2( relPoint.y, relPoint.x ); + else if ( relPoint.x < 0.0f ) + { + if ( relPoint.y >= 0.0f ) + theta = mAtan2( relPoint.y, relPoint.x ) + M_PI_F; + else if ( relPoint.y < 0.0f ) + theta = mAtan2( relPoint.y, relPoint.x ) - M_PI_F; + } + else if ( relPoint.x == 0.0f ) + { + if ( relPoint.y > 0.0f ) + theta = M_PI_F / 2.0f; + else if ( relPoint.y < 0.0f ) + theta = -(M_PI_F / 2.0f); + } + + tmpPoints.push_back( Point4F( pnt.x, pnt.y, pnt.z, theta ) ); + } + + dQsort( tmpPoints.address(), tmpPoints.size(), sizeof( Point4F ), cmpQuadPointTheta ); + + for ( U32 i = 0; i < tmpPoints.size(); i++ ) + { + const Point4F &tmpPoint = tmpPoints[i]; + (*sortPoints)[i].set( tmpPoint.x, tmpPoint.y, tmpPoint.z ); + } +} + +void DecalManager::_allocBuffers( DecalInstance *inst ) +{ + const S32 sizeClass = _getSizeClass( inst ); + + void* data; + if ( sizeClass == -1 ) + data = dMalloc( sizeof( DecalVertex ) * inst->mVertCount + sizeof( U16 ) * inst->mIndxCount ); + else + data = mChunkers[sizeClass]->alloc(); + + inst->mVerts = reinterpret_cast< DecalVertex* >( data ); + data = (U8*)data + sizeof( DecalVertex ) * inst->mVertCount; + inst->mIndices = reinterpret_cast< U16* >( data ); +} + +void DecalManager::_freeBuffers( DecalInstance *inst ) +{ + if ( inst->mVerts != NULL ) + { + const S32 sizeClass = _getSizeClass( inst ); + + if ( sizeClass == -1 ) + dFree( inst->mVerts ); + else + { + // Use FreeListChunker + mChunkers[sizeClass]->free( inst->mVerts ); + } + + inst->mVerts = NULL; + inst->mVertCount = 0; + inst->mIndices = NULL; + inst->mIndxCount = 0; + } +} + +S32 DecalManager::_getSizeClass( DecalInstance *inst ) const +{ + U32 bytes = inst->mVertCount * sizeof( DecalVertex ) + inst->mIndxCount * sizeof ( U16 ); + + if ( bytes <= SIZE_CLASS_0 ) + return 0; + if ( bytes <= SIZE_CLASS_1 ) + return 1; + if ( bytes <= SIZE_CLASS_2 ) + return 2; + + // Size is outside of the largest chunker. + return -1; +} + +bool DecalManager::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + PROFILE_SCOPE( DecalManager_RenderDecals ); + + if ( !smDecalsOn || !mData ) + return false; + + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if ( !state->isDiffusePass() && !state->isReflectPass() ) + return false; + + PROFILE_START( DecalManager_RenderDecals_SphereTreeCull ); + + // Grab this before anything here changes it. + mCuller = state->getFrustum(); + + // Populate vector of decal instances to be rendered with all + // decals from visible decal spheres. + + mDecalQueue.clear(); + + const Vector &grid = mData->getGrid(); + + for ( U32 i = 0; i < grid.size(); i++ ) + { + const DecalSphere *decalSphere = grid[i]; + const SphereF &worldSphere = decalSphere->mWorldSphere; + if ( !mCuller.sphereInFrustum( worldSphere.center, worldSphere.radius ) ) + continue; + + // TODO: If each sphere stored its largest decal instance we + // could do an LOD step on it here and skip adding any of the + // decals in the sphere. + + mDecalQueue.merge( decalSphere->mItems ); + } + + PROFILE_END(); + + PROFILE_START( DecalManager_RenderDecals_Update ); + + const U32 &curSimTime = Sim::getCurrentTime(); + const Point2I &viewportExtent = state->getViewportExtent(); + Point3F cameraOffset; + F32 decalSize, + pixelRadius; + U32 delta, diff; + DecalInstance *dinst; + + // Loop through DecalQueue once for preRendering work. + // 1. Update DecalInstance fade (over time) + // 2. Clip geometry if flagged to do so. + // 3. Calculate lod - if decal is far enough away it will not render. + for ( U32 i = 0; i < mDecalQueue.size(); i++ ) + { + dinst = mDecalQueue[i]; + + // LOD calculation. + // See TSShapeInstance::setDetailFromDistance for more + // information on these calculations. + decalSize = getMax( dinst->mSize, 0.001f ); + pixelRadius = dinst->calcPixelRadius( state ); + + // Need to clamp here. + if ( pixelRadius < dinst->calcEndPixRadius( viewportExtent ) ) + { + mDecalQueue.erase_fast( i ); + i--; + continue; + } + + // We're gonna try to render this decal... so do any + // final adjustments to it before rendering. + + // Update fade and delete expired. + if ( !( dinst->mFlags & PermanentDecal || dinst->mFlags & CustomDecal ) ) + { + delta = ( curSimTime - dinst->mCreateTime ); + if ( delta > dinst->mDataBlock->lifeSpan ) + { + diff = delta - dinst->mDataBlock->lifeSpan; + dinst->mVisibility = 1.0f - (F32)diff / (F32)dinst->mDataBlock->fadeTime; + + if ( dinst->mVisibility <= 0.0f ) + { + mDecalQueue.erase_fast( i ); + removeDecal( dinst ); + i--; + continue; + } + } + } + + // Build clipped geometry for this decal if needed. + if ( dinst->mFlags & ClipDecal/* && !( dinst->mFlags & CustomDecal ) */) + { + // Turn off the flag so we don't continually try to clip + // if it fails. + if(!clipDecal( dinst )) + { + dinst->mFlags = dinst->mFlags & ~ClipDecal; + if ( !(dinst->mFlags & CustomDecal) ) + { + // Clipping failed to get any geometry... + + // Remove it from the render queue. + mDecalQueue.erase_fast( i ); + i--; + + // If the decal is one placed at run-time (not the editor) + // then we should also permanently delete the decal instance. + if ( !(dinst->mFlags & SaveDecal) ) + { + removeDecal( dinst ); + } + } + // If this is a decal placed by the editor it will be + // flagged to attempt clipping again the next time it is + // modified. For now we just skip rendering it. + continue; + } + } + + // If we get here and the decal still does not have any geometry + // skip rendering it. It must be an editor placed decal that failed + // to clip any geometry but has not yet been flagged to try again. + if ( !dinst->mVerts || dinst->mVertCount == 0 || dinst->mIndxCount == 0 ) + { + mDecalQueue.erase_fast( i ); + i--; + continue; + } + + // + F32 alpha = pixelRadius / (dinst->mDataBlock->startPixRadius * decalSize) - 1.0f; + if ( dinst->mFlags & CustomDecal ) + { + alpha = mClampF( alpha, 0.0f, 1.0f ); + alpha *= dinst->mVisibility; + } + else + alpha = mClampF( alpha * dinst->mVisibility, 0.0f, 1.0f ); + + // + for ( U32 v = 0; v < dinst->mVertCount; v++ ) + dinst->mVerts[v].color.set( 255, 255, 255, alpha * 255.0f ); + } + + PROFILE_END(); + + if ( mDecalQueue.empty() ) + return false; + + // Sort queued decals... + // 1. Editor decals - in render priority order first, creation time second, and material third. + // 2. Dynamic decals - in render priority order first and creation time second. + // + // With the constraint that decals with different render priority cannot + // be rendered together in the same draw call. + + PROFILE_START( DecalManager_RenderDecals_Sort ); + dQsort( mDecalQueue.address(), mDecalQueue.size(), sizeof(DecalInstance*), cmpDecalRenderOrder ); + PROFILE_END(); + + PROFILE_SCOPE( DecalManager_RenderDecals_RenderBatch ); + + mPrimBuffs.clear(); + mVBs.clear(); + + RenderPassManager *renderPass = state->getRenderPass(); + + // Base render instance for convenience we use for convenience. + // Data shared by all instances we allocate below can be copied + // from the base instance at the same time. + MeshRenderInst baseRenderInst; + baseRenderInst.clear(); + + MatrixF *tempMat = renderPass->allocUniqueXform( MatrixF( true ) ); + MathUtils::getZBiasProjectionMatrix( gDecalBias, mCuller, tempMat ); + baseRenderInst.projection = tempMat; + + baseRenderInst.objectToWorld = &MatrixF::Identity; + baseRenderInst.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View); + + baseRenderInst.type = RenderPassManager::RIT_Decal; + + // Make it the sort distance the max distance so that + // it renders after all the other opaque geometry in + // the prepass bin. + baseRenderInst.sortDistSq = F32_MAX; + + // Get the best lights for the current camera position. + LightManager *lm = state->getLightManager(); + if ( lm ) + { + lm->setupLights( NULL, + mCuller.getPosition(), + mCuller.getTransform().getForwardVector(), + mCuller.getFarDist() ); + lm->getBestLights( baseRenderInst.lights, 4 ); + lm->resetLights(); + } + + Vector batches; + DecalBatch *currentBatch = NULL; + + // Loop through DecalQueue collecting them into render batches. + for ( U32 i = 0; i < mDecalQueue.size(); i++ ) + { + DecalInstance *decal = mDecalQueue[i]; + DecalData *data = decal->mDataBlock; + Material *mat = data->getMaterial(); + + if ( currentBatch == NULL ) + { + // Start a new batch, beginning with this decal. + + batches.increment(); + currentBatch = &batches.last(); + currentBatch->startDecal = i; + currentBatch->decalCount = 1; + currentBatch->iCount = decal->mIndxCount; + currentBatch->vCount = decal->mVertCount; + currentBatch->mat = mat; + currentBatch->matInst = decal->mDataBlock->getMaterialInstance(); + currentBatch->priority = decal->getRenderPriority(); + currentBatch->dynamic = !(decal->mFlags & SaveDecal); + + continue; + } + + if ( currentBatch->iCount + decal->mIndxCount >= smMaxIndices || + currentBatch->vCount + decal->mVertCount >= smMaxVerts || + currentBatch->mat != mat || + currentBatch->priority != decal->getRenderPriority() || + decal->mCustomTex ) + { + // End batch. + + currentBatch = NULL; + i--; + continue; + } + + // Add on to current batch. + currentBatch->decalCount++; + currentBatch->iCount += decal->mIndxCount; + currentBatch->vCount += decal->mVertCount; + } + + // Loop through batches allocating buffers and submitting render instances. + for ( U32 i = 0; i < batches.size(); i++ ) + { + DecalBatch ¤tBatch = batches[i]; + + // Allocate buffers... + + GFXVertexBufferHandle vb; + vb.set( GFX, currentBatch.vCount, GFXBufferTypeDynamic ); + DecalVertex *vpPtr = vb.lock(); + + GFXPrimitiveBufferHandle pb; + pb.set( GFX, currentBatch.iCount, 0, GFXBufferTypeDynamic ); + U16 *pbPtr; + pb.lock( &pbPtr ); + + // Copy data into the buffers from all decals in this batch... + + U32 lastDecal = currentBatch.startDecal + currentBatch.decalCount; + + U32 voffset = 0; + U32 ioffset = 0; + + // This is an ugly hack for ProjectedShadow! + GFXTextureObject *customTex = NULL; + + for ( U32 j = currentBatch.startDecal; j < lastDecal; j++ ) + { + DecalInstance *dinst = mDecalQueue[j]; + + for ( U32 k = 0; k < dinst->mIndxCount; k++ ) + { + *( pbPtr + ioffset + k ) = dinst->mIndices[k] + voffset; + } + + ioffset += dinst->mIndxCount; + + dMemcpy( vpPtr + voffset, dinst->mVerts, sizeof( DecalVertex ) * dinst->mVertCount ); + voffset += dinst->mVertCount; + + // Ugly hack for ProjectedShadow! + if ( (dinst->mFlags & CustomDecal) && dinst->mCustomTex != NULL ) + customTex = *dinst->mCustomTex; + } + + AssertFatal( ioffset == currentBatch.iCount, "bad" ); + AssertFatal( voffset == currentBatch.vCount, "bad" ); + + pb.unlock(); + vb.unlock(); + + // DecalManager must hold handles to these buffers so they remain valid, + // we don't actually use them elsewhere. + mPrimBuffs.push_back( pb ); + mVBs.push_back( vb ); + + // Submit render inst... + + MeshRenderInst *ri = renderPass->allocInst(); + + *ri = baseRenderInst; + + ri->primBuff = &mPrimBuffs.last(); + ri->vertBuff = &mVBs.last(); + + ri->matInst = currentBatch.matInst; + + ri->prim = renderPass->allocPrim(); + ri->prim->type = GFXTriangleList; + ri->prim->minIndex = 0; + ri->prim->startIndex = 0; + ri->prim->numPrimitives = currentBatch.iCount / 3; + ri->prim->startVertex = 0; + ri->prim->numVertices = currentBatch.vCount; + + // Ugly hack for ProjectedShadow! + if ( customTex ) + ri->miscTex = customTex; + + // The decal bin will contain render instances for both decals and decalRoad's. + // Dynamic decals render last, then editor decals and roads in priority order. + // DefaultKey is sorted in descending order. + ri->defaultKey = currentBatch.dynamic ? 0xFFFFFFFF : (U32)currentBatch.priority; + ri->defaultKey2 = 1;//(U32)lastDecal->mDataBlock; + + renderPass->addInst( ri ); + } + + return false; +} + +void DecalManager::renderDecalSpheres() +{ + if ( mData && Con::getBoolVariable( "$renderSpheres" ) ) + { + PROFILE_SCOPE( DecalManager_renderDecalSpheres ); + + const Vector &grid = mData->getGrid(); + + GFXDrawUtil *drawUtil = GFX->getDrawUtil(); + ColorI sphereLineColor( 0, 255, 0, 25 ); + ColorI sphereColor( 0, 0, 255, 30 ); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + for ( U32 i = 0; i < grid.size(); i++ ) + { + DecalSphere *decalSphere = grid[i]; + const SphereF &worldSphere = decalSphere->mWorldSphere; + drawUtil->drawSphere( desc, worldSphere.radius, worldSphere.center, sphereColor ); + } + } +} + +void DecalManager::advanceTime( F32 timeDelta ) +{ +} + +void DecalManager::interpolateTick( F32 delta ) +{ +} + +void DecalManager::processTick() +{ +} + +bool DecalManager::_createDataFile() +{ + AssertFatal( !mData, "DecalManager::tried to create duplicate data file?" ); + + // We need to construct a default file name + char fileName[1024]; + fileName[0] = 0; + + // See if we know our current mission name + char missionName[1024]; + dStrcpy( missionName, Con::getVariable( "$Client::MissionFile" ) ); + char *dot = dStrstr((const char*)missionName, ".mis"); + if(dot) + *dot = '\0'; + + dSprintf( fileName, sizeof(fileName), "%s.mis.decals", missionName ); + + mDataFileName = StringTable->insert( fileName ); + + if( !Torque::FS::IsFile( fileName ) ) + { + DecalDataFile *file = new DecalDataFile(); + file->write( mDataFileName ); + delete file; + } + + mData = ResourceManager::get().load( mDataFileName ); + return (bool)mData; +} + +void DecalManager::saveDecals( const UTF8 *fileName ) +{ + mDirty = false; + + if ( mData ) + mData->write( fileName ); +} + +bool DecalManager::loadDecals( const UTF8 *fileName ) +{ + if( mData ) + clearData(); + + mData = ResourceManager::get().load( fileName ); + + mDirty = false; + + return mData != NULL; +} + +void DecalManager::clearData() +{ + mClearDataSignal.trigger(); + + // Free all geometry buffers. + + if( mData ) + { + const Vector< DecalSphere* > grid = mData->getGrid(); + for( U32 i = 0; i < grid.size(); ++ i ) + { + DecalSphere* sphere = grid[ i ]; + for( U32 n = 0; n < sphere->mItems.size(); ++ n ) + _freeBuffers( sphere->mItems[ n ] ); + } + } + + mData = NULL; + mDecalInstanceVec.clear(); +} + +ConsoleFunction( decalManagerSave, void, 1, 2, "decalManagerSave( mission decal file )" ) +{ + if ( argc > 1 ) + gDecalManager->saveDecals( argv[1] ); + else + { + char missionName[1024]; + dStrcpy( missionName, Con::getVariable( "$Client::MissionFile" ) ); + char *dot = dStrstr((const char*)missionName, ".mis"); + + if(dot) + *dot = '\0'; + + char testName[1024]; + dSprintf( testName, sizeof(testName), "%s.mis.decals", missionName ); + + char fullName[1024]; + Platform::makeFullPathName(testName, fullName, sizeof(fullName)); + gDecalManager->saveDecals( fullName ); + } +} + +ConsoleFunction( decalManagerLoad, bool, 2, 2, "decalManagerLoad( mission decal file )" ) +{ + return gDecalManager->loadDecals( argv[1] ); +} + +ConsoleFunction( decalManagerDirty, bool, 1, 1, "" ) +{ + return gDecalManager->isDirty(); +} + +ConsoleFunction( decalManagerClear, void, 1, 1, "" ) +{ + gDecalManager->clearData(); +} + +ConsoleFunction( decalManagerAddDecal, S32, 6, 7, "decalManagerAddDecal( %position, %normal, %rotation, %scale, %decalData, [%immortal]) - Place a Decal. Immortal decals don't age and must be removed explicitly. Returns Decal ID" ) +{ + Point3F pos; + dSscanf(argv[1], "%g %g %g", &pos.x, &pos.y, &pos.z); + Point3F normal; + dSscanf(argv[2], "%g %g %g", &normal.x, &normal.y, &normal.z); + F32 rot = dAtof(argv[3]); + F32 scale = dAtof(argv[4]); + + DecalData *decalData = NULL; + Sim::findObject(argv[5], decalData); + + if(!decalData) + { + Con::warnf("Invalid Decal dataBlock: %s", argv[5]); + return -1; + } + + U8 flags = 0; + if( argc >= 7 && dAtob(argv[6]) ) + flags |= PermanentDecal; + + DecalInstance *inst = gDecalManager->addDecal( pos, normal, rot, decalData, scale, -1, flags ); + + if(!inst) + { + Con::warnf("Unable to create decal instance."); + return -1; + } + + // Add the decal to the instance vector. + + inst->mId = gDecalManager->mDecalInstanceVec.size(); + gDecalManager->mDecalInstanceVec.push_back( inst ); + + return inst->mId; +} + +ConsoleFunction( decalManagerRemoveDecal, bool, 2, 2, "decalManagerRemoveDecal( %decalId ) - Remove specified decal from the scene. Returns true if successful, false if decal not found.") +{ + S32 id = dAtoi(argv[1]); + DecalInstance *inst = gDecalManager->getDecal(id); + if(inst) + { + gDecalManager->removeDecal(inst); + return true; + } + return false; +} diff --git a/T3D/decal/decalManager.h b/T3D/decal/decalManager.h new file mode 100644 index 0000000..27d0add --- /dev/null +++ b/T3D/decal/decalManager.h @@ -0,0 +1,240 @@ + //----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DECALMANAGER_H_ +#define _DECALMANAGER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _ITICKABLE_H_ +#include "core/iTickable.h" +#endif +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif +#ifndef _DECALDATAFILE_H_ +#include "decalDataFile.h" +#endif +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _DECALINSTANCE_H_ +#include "decalInstance.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif + +//#define DECALMANAGER_DEBUG + +struct ObjectRenderInst; +class Material; + +enum DecalFlags +{ + PermanentDecal = 1 << 0, + SaveDecal = 1 << 1, + ClipDecal = 1 << 2, + CustomDecal = 1 << 3 // DecalManager will not attempt to clip or remove this decal + // it is managed by someone else. +}; + + +/// Manage decals in the scene. +class DecalManager : public SceneObject, public ITickable +{ + typedef SceneObject Parent; + +public: + + static bool smDecalsOn; + static F32 smDecalLifeTimeScale; + static const U32 smMaxVerts; + static const U32 smMaxIndices; + + Vector mDecalInstanceVec; + +#ifdef DECALMANAGER_DEBUG + Vector mDebugVectors; + Vector mDebugPoints; + Vector mDebugPlanes; + Point3F mDebugVecPos; +#endif + +public: + + DecalManager(); + ~DecalManager(); + + DECLARE_CONOBJECT( DecalManager ); + static void consoleInit(); + + // ITickable + virtual void interpolateTick( F32 delta ); + virtual void processTick(); + virtual void advanceTime( F32 timeDelta ); + + /// @name Decal Addition + /// @{ + + /// Adds a decal using a normal and a rotation. + /// + /// @param pos The 3d position for the decal center. + /// @param normal The decal up vector. + /// @param rotAroundNormal The decal rotation around the normal. + /// @param decalData The datablock which defines this decal. + /// @param decalScale A scalar for adjusting the default size of the decal. + /// @param decalTexIndex Selects the texture coord index within the decal + /// data to use. If it is less than zero then a random + /// coord is selected. + /// @param flags The decal flags. @see DecalFlags + /// + DecalInstance* addDecal( const Point3F &pos, + const Point3F &normal, + F32 rotAroundNormal, + DecalData *decalData, + F32 decalScale = 1.0f, + S32 decalTexIndex = 0, + U8 flags = 0x000 ); + + /// Adds a decal using a normal and a tangent. + /// + /// @param pos The 3d position for the decal center. + /// @param normal The decal up vector. + /// @param tanget The decal right vector. + /// @param decalData The datablock which defines this decal. + /// @param decalScale A scalar for adjusting the default size of the decal. + /// @param decalTexIndex Selects the texture coord index within the decal + /// data to use. If it is less than zero then a random + /// coord is selected. + /// @param flags The decal flags. @see DecalFlags + /// + DecalInstance* addDecal( const Point3F &pos, + const Point3F &normal, + const Point3F &tangent, + DecalData *decalData, + F32 decalScale = 1.0f, + S32 decalTexIndex = 0, + U8 flags = 0 ); + + /// @} + + /// @name Decal Removal + /// @{ + + void removeDecal( DecalInstance *inst ); + + /// @} + + DecalInstance* getDecal( S32 id ); + + DecalInstance* getClosestDecal( const Point3F &pos ); + + /// Return the closest DecalInstance hit by a ray. + DecalInstance* raycast( const Point3F &start, const Point3F &end, bool savedDecalsOnly = true ); + + //void dataDeleted( DecalData *data ); + + void saveDecals( const UTF8 *fileName ); + bool loadDecals( const UTF8 *fileName ); + void clearData(); + + const Frustum& getFrustum() const { return mCuller; } + + /// Returns true if changes have been made since the last load/save + bool isDirty() const { return mDirty; } + + bool clipDecal( DecalInstance *decal, Vector *edgeVerts = NULL, const Point2F *clipDepth = NULL ); + + void renderDecalSpheres(); + + void notifyDecalModified( DecalInstance *inst ); + + bool _createDataFile(); + + Signal< void() >& getClearDataSignal() { return mClearDataSignal; } + + Resource getDecalDataFile() { return mData; } + +protected: + + // Assume that a class is already given for the object: + // Point with coordinates {float x, y;} + //=================================================================== + + // isLeft(): tests if a point is Left|On|Right of an infinite line. + // Input: three points P0, P1, and P2 + // Return: >0 for P2 left of the line through P0 and P1 + // =0 for P2 on the line + // <0 for P2 right of the line + // See: the January 2001 Algorithm on Area of Triangles + inline F32 isLeft( const Point3F &P0, const Point3F &P1, const Point3F &P2 ) + { + return (P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y); + } + + U32 _generateConvexHull( const Vector &points, Vector *outPoints ); + + // Rendering + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + + void _generateWindingOrder( const Point3F &cornerPoint, Vector *sortPoints ); + + // Helpers for creating and deleting the vert and index arrays + // held by DecalInstance. + void _allocBuffers( DecalInstance *inst ); + void _freeBuffers( DecalInstance *inst ); + + /// Returns index used to index into the correct sized FreeListChunker for + /// allocating vertex and index arrays. + S32 _getSizeClass( DecalInstance *inst ) const; + +protected: + + Frustum mCuller; + + Vector mDecalQueue; + + StringTableEntry mDataFileName; + Resource mData; + + Signal< void() > mClearDataSignal; + + Vector< GFXVertexBufferHandle > mVBs; + Vector mPrimBuffs; + + FreeListChunkerUntyped *mChunkers[3]; + + bool mDirty; + + struct DecalBatch + { + U32 startDecal; + U32 decalCount; + U32 iCount; + U32 vCount; + U8 priority; + Material *mat; + BaseMatInstance *matInst; + bool dynamic; + }; +}; + +extern DecalManager* gDecalManager; + +#endif // _DECALMANAGER_H_ diff --git a/T3D/examples/renderMeshExample.cpp b/T3D/examples/renderMeshExample.cpp new file mode 100644 index 0000000..3168240 --- /dev/null +++ b/T3D/examples/renderMeshExample.cpp @@ -0,0 +1,324 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/examples/renderMeshExample.h" + +#include "math/mathIO.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "materials/materialManager.h" +#include "renderInstance/renderPassManager.h" + +IMPLEMENT_CO_NETOBJECT_V1(RenderMeshExample); + +//----------------------------------------------------------------------------- +// Object setup and teardown +//----------------------------------------------------------------------------- +RenderMeshExample::RenderMeshExample() +{ + // Flag this object so that it will always + // be sent across the network to clients + mNetFlags.set( Ghostable | ScopeAlways ); + + // Set it as a "static" object that casts shadows + mTypeMask |= StaticObjectType | ShadowCasterObjectType; + + // Make sure we the Material instance to NULL + // so we don't try to access it incorrectly + mMaterialInst = NULL; +} + +RenderMeshExample::~RenderMeshExample() +{ + if ( mMaterialInst ) + SAFE_DELETE( mMaterialInst ); +} + +//----------------------------------------------------------------------------- +// Object Editing +//----------------------------------------------------------------------------- +void RenderMeshExample::initPersistFields() +{ + addGroup( "Rendering" ); + addField( "material", TypeMaterialName, Offset( mMaterialName, RenderMeshExample ) ); + endGroup( "Rendering" ); + + // SceneObject already handles exposing the transform + Parent::initPersistFields(); +} + +void RenderMeshExample::inspectPostApply() +{ + Parent::inspectPostApply(); + + // Flag the network mask to send the updates + // to the client object + setMaskBits( UpdateMask ); +} + +bool RenderMeshExample::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Set up a 1x1x1 bounding box + mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ), + Point3F( 0.5f, 0.5f, 0.5f ) ); + + resetWorldBox(); + + // Add this object to the scene + addToScene(); + + return true; +} + +void RenderMeshExample::onRemove() +{ + // Remove this object from the scene + removeFromScene(); + + Parent::onRemove(); +} + +void RenderMeshExample::setTransform(const MatrixF & mat) +{ + // Let SceneObject handle all of the matrix manipulation + Parent::setTransform( mat ); + + // Dirty our network mask so that the new transform gets + // transmitted to the client object + setMaskBits( TransformMask ); +} + +U32 RenderMeshExample::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + // Allow the Parent to get a crack at writing its info + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + // Write our transform information + if ( stream->writeFlag( mask & TransformMask ) ) + { + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + } + + // Write out any of the updated editable properties + if ( stream->writeFlag( mask & UpdateMask ) ) + stream->write( mMaterialName ); + + return retMask; +} + +void RenderMeshExample::unpackUpdate(NetConnection *conn, BitStream *stream) +{ + // Let the Parent read any info it sent + Parent::unpackUpdate(conn, stream); + + if ( stream->readFlag() ) // TransformMask + { + mathRead(*stream, &mObjToWorld); + mathRead(*stream, &mObjScale); + + setTransform( mObjToWorld ); + } + + if ( stream->readFlag() ) // UpdateMask + { + stream->read( &mMaterialName ); + + if ( isProperlyAdded() ) + updateMaterial(); + } +} + +//----------------------------------------------------------------------------- +// Object Rendering +//----------------------------------------------------------------------------- +void RenderMeshExample::createGeometry() +{ + static const Point3F cubePoints[8] = + { + Point3F( 1, -1, -1), Point3F( 1, -1, 1), Point3F( 1, 1, -1), Point3F( 1, 1, 1), + Point3F(-1, -1, -1), Point3F(-1, 1, -1), Point3F(-1, -1, 1), Point3F(-1, 1, 1) + }; + + static const Point3F cubeNormals[6] = + { + Point3F( 1, 0, 0), Point3F(-1, 0, 0), Point3F( 0, 1, 0), + Point3F( 0, -1, 0), Point3F( 0, 0, 1), Point3F( 0, 0, -1) + }; + + static const Point2F cubeTexCoords[4] = + { + Point2F( 0, 0), Point2F( 0, -1), + Point2F( 1, 0), Point2F( 1, -1) + }; + + static const U32 cubeFaces[36][3] = + { + { 3, 0, 3 }, { 0, 0, 0 }, { 1, 0, 1 }, + { 2, 0, 2 }, { 0, 0, 0 }, { 3, 0, 3 }, + { 7, 1, 1 }, { 4, 1, 2 }, { 5, 1, 0 }, + { 6, 1, 3 }, { 4, 1, 2 }, { 7, 1, 1 }, + { 3, 2, 1 }, { 5, 2, 2 }, { 2, 2, 0 }, + { 7, 2, 3 }, { 5, 2, 2 }, { 3, 2, 1 }, + { 1, 3, 3 }, { 4, 3, 0 }, { 6, 3, 1 }, + { 0, 3, 2 }, { 4, 3, 0 }, { 1, 3, 3 }, + { 3, 4, 3 }, { 6, 4, 0 }, { 7, 4, 1 }, + { 1, 4, 2 }, { 6, 4, 0 }, { 3, 4, 3 }, + { 2, 5, 1 }, { 4, 5, 2 }, { 0, 5, 0 }, + { 5, 5, 3 }, { 4, 5, 2 }, { 2, 5, 1 } + }; + + // Fill the vertex buffer + VertexType *pVert = NULL; + + mVertexBuffer.set( GFX, 36, GFXBufferTypeStatic ); + pVert = mVertexBuffer.lock(); + + Point3F halfSize = getObjBox().getExtents() * 0.5f; + + for (U32 i = 0; i < 36; i++) + { + const U32& vdx = cubeFaces[i][0]; + const U32& ndx = cubeFaces[i][1]; + const U32& tdx = cubeFaces[i][2]; + + pVert[i].point = cubePoints[vdx] * halfSize; + pVert[i].normal = cubeNormals[ndx]; + pVert[i].texCoord = cubeTexCoords[tdx]; + } + + mVertexBuffer.unlock(); + + // Fill the primitive buffer + U16 *pIdx = NULL; + + mPrimitiveBuffer.set( GFX, 36, 12, GFXBufferTypeStatic ); + + mPrimitiveBuffer.lock(&pIdx); + + for (U16 i = 0; i < 36; i++) + pIdx[i] = i; + + mPrimitiveBuffer.unlock(); +} + +void RenderMeshExample::updateMaterial() +{ + if ( mMaterialName.isEmpty() ) + return; + + // If the material name matches then don't bother updating it. + if ( mMaterialInst && mMaterialName.equal( mMaterialInst->getMaterial()->getName(), String::NoCase ) ) + return; + + SAFE_DELETE( mMaterialInst ); + + mMaterialInst = MATMGR->createMatInstance( mMaterialName, getGFXVertexFormat< VertexType >() ); + if ( !mMaterialInst ) + Con::errorf( "RenderMeshExample::updateMaterial - no Material called '%s'", mMaterialName.c_str() ); +} + +bool RenderMeshExample::prepRenderImage( SceneState *state, const U32 stateKey, + const U32 startZone, const bool modifyBaseZoneState) +{ + // Do a little prep work if needed + if ( mVertexBuffer.isNull() ) + createGeometry(); + + // Make sure we haven't already been processed by this state + if ( isLastState( state, stateKey ) ) + return false; + + // Update our state + setLastState(state, stateKey); + + // If we are actually rendered then create and submit our RenderInst + if ( state->isObjectRendered( this ) ) + { + // Get a handy pointer to our RenderPassmanager + RenderPassManager *renderPass = state->getRenderPass(); + + // Allocate an MeshRenderInst so that we can submit it to the RenderPassManager + MeshRenderInst *ri = renderPass->allocInst(); + + // Set our RenderInst as a standard mesh render + ri->type = RenderPassManager::RIT_Mesh; + + // Calculate our sorting point + if ( state ) + { + // Calculate our sort point manually. + const Box3F& rBox = getRenderWorldBox(); + ri->sortDistSq = rBox.getSqDistanceToPoint( state->getCameraPosition() ); + } + else + ri->sortDistSq = 0.0f; + + // Set up our transforms + MatrixF objectToWorld = getRenderTransform(); + objectToWorld.scale( getScale() ); + + ri->objectToWorld = renderPass->allocUniqueXform( objectToWorld ); + ri->worldToCamera = renderPass->allocSharedXform(RenderPassManager::View); + ri->projection = renderPass->allocSharedXform(RenderPassManager::Projection); + + // Let the light manager fill the RIs light vector with the current best lights + LightManager* lm = NULL; + if ( state->getSceneManager() ) + { + lm = state->getSceneManager()->getLightManager(); + if ( lm && !state->isShadowPass() ) + { + lm->setupLights( this, getWorldSphere() ); + + lm->getBestLights( ri->lights, 8 ); + } + } + + // Make sure we have an up-to-date backbuffer in case + // our Material would like to make use of it + // NOTICE: SFXBB is removed and refraction is disabled! + //ri->backBuffTex = GFX->getSfxBackBuffer(); + + // Set our Material + if ( mMaterialInst ) + ri->matInst = mMaterialInst; + else + ri->matInst = MATMGR->getWarningMatInstance(); + + // Set up our vertex buffer and primitive buffer + ri->vertBuff = &mVertexBuffer; + ri->primBuff = &mPrimitiveBuffer; + + ri->prim = renderPass->allocPrim(); + ri->prim->type = GFXTriangleList; + ri->prim->minIndex = 0; + ri->prim->startIndex = 0; + ri->prim->numPrimitives = 12; + ri->prim->startVertex = 0; + ri->prim->numVertices = 36; + + // We sort by the vertex buffer + ri->defaultKey = (U32)ri->vertBuff; // Not 64bit safe! + + // Submit our RenderInst to the RenderPassManager + state->getRenderPass()->addInst( ri ); + + // Give the light manager a chance to reset the lights + if ( lm ) + lm->resetLights(); + } + + return false; +} + +ConsoleMethod( RenderMeshExample, postApply, void, 2, 2, "") +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/T3D/examples/renderMeshExample.h b/T3D/examples/renderMeshExample.h new file mode 100644 index 0000000..e708bfd --- /dev/null +++ b/T3D/examples/renderMeshExample.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RENDERMESHEXAMPLE_H_ +#define _RENDERMESHEXAMPLE_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + +//----------------------------------------------------------------------------- +// This class implements a basic SceneObject that can exist in the world at a +// 3D position and render itself. There are several valid ways to render an +// object in Torque. This class implements the preferred rendering method which +// is to submit a MeshRenderInst along with a Material, vertex buffer, +// primitive buffer, and transform and allow the RenderMeshMgr handle the +// actual setup and rendering for you. +//----------------------------------------------------------------------------- + +class RenderMeshExample : public SceneObject +{ + typedef SceneObject Parent; + + // Networking masks + // We need to implement a mask specifically to handle + // updating our transform from the server object to its + // client-side "ghost". We also need to implement a + // maks for handling editor updates to our properties + // (like material). + enum MaskBits + { + TransformMask = Parent::NextFreeMask << 0, + UpdateMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + //-------------------------------------------------------------------------- + // Rendering variables + //-------------------------------------------------------------------------- + // The name of the Material we will use for rendering + String mMaterialName; + // The actual Material instance + BaseMatInstance* mMaterialInst; + + // Define our vertex format here so we don't have to + // change it in multiple spots later + typedef GFXVertexPNT VertexType; + + // The GFX vertex and primitive buffers + GFXVertexBufferHandle< VertexType > mVertexBuffer; + GFXPrimitiveBufferHandle mPrimitiveBuffer; + +public: + RenderMeshExample(); + virtual ~RenderMeshExample(); + + // Declare this object as a ConsoleObject so that we can + // instantiate it into the world and network it + DECLARE_CONOBJECT(RenderMeshExample); + + //-------------------------------------------------------------------------- + // Object Editing + // Since there is always a server and a client object in Torque and we + // actually edit the server object we need to implement some basic + // networking functions + //-------------------------------------------------------------------------- + // Set up any fields that we want to be editable (like position) + static void initPersistFields(); + + // Allows the object to update its editable settings + // from the server object to the client + virtual void inspectPostApply(); + + // Handle when we are added to the scene and removed from the scene + bool onAdd(); + void onRemove(); + + // Override this so that we can dirty the network flag when it is called + void setTransform( const MatrixF &mat ); + + // This function handles sending the relevant data from the server + // object to the client object + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + // This function handles receiving relevant data from the server + // object and applying it to the client object + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + //-------------------------------------------------------------------------- + // Object Rendering + // Torque utilizes a "batch" rendering system. This means that it builds a + // list of objects that need to render (via RenderInst's) and then renders + // them all in one batch. This allows it to optimized on things like + // minimizing texture, state, and shader switching by grouping objects that + // use the same Materials. + //-------------------------------------------------------------------------- + // Create the geometry for rendering + void createGeometry(); + + // Get the Material instance + void updateMaterial(); + + // This is the function that allows this object to submit itself for rendering + bool prepRenderImage( SceneState *state, const U32 stateKey, + const U32 startZone, const bool modifyBaseZoneState = false); +}; + +#endif // _RENDERMESHEXAMPLE_H_ \ No newline at end of file diff --git a/T3D/examples/renderObjectExample.cpp b/T3D/examples/renderObjectExample.cpp new file mode 100644 index 0000000..4431974 --- /dev/null +++ b/T3D/examples/renderObjectExample.cpp @@ -0,0 +1,263 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/examples/renderObjectExample.h" + +#include "math/mathIO.h" +#include "sceneGraph/sceneState.h" +#include "core/stream/bitStream.h" +#include "materials/sceneData.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" + +IMPLEMENT_CO_NETOBJECT_V1(RenderObjectExample); + +//----------------------------------------------------------------------------- +// Object setup and teardown +//----------------------------------------------------------------------------- +RenderObjectExample::RenderObjectExample() +{ + // Flag this object so that it will always + // be sent across the network to clients + mNetFlags.set( Ghostable | ScopeAlways ); + + // Set it as a "static" object + mTypeMask |= StaticObjectType; +} + +RenderObjectExample::~RenderObjectExample() +{ +} + +//----------------------------------------------------------------------------- +// Object Editing +//----------------------------------------------------------------------------- +void RenderObjectExample::initPersistFields() +{ + // SceneObject already handles exposing the transform + Parent::initPersistFields(); +} + +bool RenderObjectExample::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Set up a 1x1x1 bounding box + mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ), + Point3F( 0.5f, 0.5f, 0.5f ) ); + + resetWorldBox(); + + // Add this object to the scene + addToScene(); + + return true; +} + +void RenderObjectExample::onRemove() +{ + // Remove this object from the scene + removeFromScene(); + + Parent::onRemove(); +} + +void RenderObjectExample::setTransform(const MatrixF & mat) +{ + // Let SceneObject handle all of the matrix manipulation + Parent::setTransform( mat ); + + // Dirty our network mask so that the new transform gets + // transmitted to the client object + setMaskBits( TransformMask ); +} + +U32 RenderObjectExample::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + // Allow the Parent to get a crack at writing its info + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + // Write our transform information + if ( stream->writeFlag( mask & TransformMask ) ) + { + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + } + + return retMask; +} + +void RenderObjectExample::unpackUpdate(NetConnection *conn, BitStream *stream) +{ + // Let the Parent read any info it sent + Parent::unpackUpdate(conn, stream); + + if ( stream->readFlag() ) // TransformMask + { + mathRead(*stream, &mObjToWorld); + mathRead(*stream, &mObjScale); + + setTransform( mObjToWorld ); + } +} + +//----------------------------------------------------------------------------- +// Object Rendering +//----------------------------------------------------------------------------- +void RenderObjectExample::createGeometry() +{ + static const Point3F cubePoints[8] = + { + Point3F( 1.0f, -1.0f, -1.0f), Point3F( 1.0f, -1.0f, 1.0f), + Point3F( 1.0f, 1.0f, -1.0f), Point3F( 1.0f, 1.0f, 1.0f), + Point3F(-1.0f, -1.0f, -1.0f), Point3F(-1.0f, 1.0f, -1.0f), + Point3F(-1.0f, -1.0f, 1.0f), Point3F(-1.0f, 1.0f, 1.0f) + }; + + static const Point3F cubeNormals[6] = + { + Point3F( 1.0f, 0.0f, 0.0f), Point3F(-1.0f, 0.0f, 0.0f), + Point3F( 0.0f, 1.0f, 0.0f), Point3F( 0.0f, -1.0f, 0.0f), + Point3F( 0.0f, 0.0f, 1.0f), Point3F( 0.0f, 0.0f, -1.0f) + }; + + static const ColorI cubeColors[3] = + { + ColorI( 255, 0, 0, 255 ), + ColorI( 0, 255, 0, 255 ), + ColorI( 0, 0, 255, 255 ) + }; + + static const U32 cubeFaces[36][3] = + { + { 3, 0, 0 }, { 0, 0, 0 }, { 1, 0, 0 }, + { 2, 0, 0 }, { 0, 0, 0 }, { 3, 0, 0 }, + { 7, 1, 0 }, { 4, 1, 0 }, { 5, 1, 0 }, + { 6, 1, 0 }, { 4, 1, 0 }, { 7, 1, 0 }, + { 3, 2, 1 }, { 5, 2, 1 }, { 2, 2, 1 }, + { 7, 2, 1 }, { 5, 2, 1 }, { 3, 2, 1 }, + { 1, 3, 1 }, { 4, 3, 1 }, { 6, 3, 1 }, + { 0, 3, 1 }, { 4, 3, 1 }, { 1, 3, 1 }, + { 3, 4, 2 }, { 6, 4, 2 }, { 7, 4, 2 }, + { 1, 4, 2 }, { 6, 4, 2 }, { 3, 4, 2 }, + { 2, 5, 2 }, { 4, 5, 2 }, { 0, 5, 2 }, + { 5, 5, 2 }, { 4, 5, 2 }, { 2, 5, 2 } + }; + + // Fill the vertex buffer + VertexType *pVert = NULL; + + mVertexBuffer.set( GFX, 36, GFXBufferTypeStatic ); + pVert = mVertexBuffer.lock(); + + Point3F halfSize = getObjBox().getExtents() * 0.5f; + + for (U32 i = 0; i < 36; i++) + { + const U32& vdx = cubeFaces[i][0]; + const U32& ndx = cubeFaces[i][1]; + const U32& cdx = cubeFaces[i][2]; + + pVert[i].point = cubePoints[vdx] * halfSize; + pVert[i].normal = cubeNormals[ndx]; + pVert[i].color = cubeColors[cdx]; + } + + mVertexBuffer.unlock(); + + // Set up our normal and reflection StateBlocks + GFXStateBlockDesc desc; + + // The normal StateBlock only needs a default StateBlock + mNormalSB = GFX->createStateBlock( desc ); + + // The reflection needs its culling reversed + desc.cullDefined = true; + desc.cullMode = GFXCullCW; + mReflectSB = GFX->createStateBlock( desc ); +} + +bool RenderObjectExample::prepRenderImage( SceneState *state, const U32 stateKey, + const U32 startZone, const bool modifyBaseZoneState) +{ + // Do a little prep work if needed + if ( mVertexBuffer.isNull() ) + createGeometry(); + + // Make sure we haven't already been processed by this state + if ( isLastState( state, stateKey ) ) + return false; + + // Update our state + setLastState(state, stateKey); + + // If we are actually rendered then create and submit our RenderInst + if ( state->isObjectRendered( this ) ) + { + // Allocate an ObjectRenderInst so that we can submit it to the RenderPassManager + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + + // Now bind our rendering function so that it will get called + ri->renderDelegate.bind( this, &RenderObjectExample::render ); + + // Set our RenderInst as a standard object render + ri->type = RenderPassManager::RIT_Object; + + // Set our sorting keys to a default value + ri->defaultKey = 0; + ri->defaultKey2 = 0; + + // Submit our RenderInst to the RenderPassManager + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void RenderObjectExample::render( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + if ( overrideMat ) + return; + + if ( mVertexBuffer.isNull() ) + return; + + PROFILE_SCOPE(RenderObjectExample_Render); + + // Set up a GFX debug event (this helps with debugging rendering events in external tools) + GFXDEBUGEVENT_SCOPE( RenderObjectExample_Render, ColorI::RED ); + + // GFXTransformSaver is a handy helper class that restores + // the current GFX matrices to their original values when + // it goes out of scope at the end of the function + GFXTransformSaver saver; + + // Calculate our object to world transform matrix + MatrixF objectToWorld = getRenderTransform(); + objectToWorld.scale( getScale() ); + + // Apply our object transform + GFX->multWorld( objectToWorld ); + + // Deal with reflect pass otherwise + // set the normal StateBlock + if ( state->isReflectPass() ) + GFX->setStateBlock( mReflectSB ); + else + GFX->setStateBlock( mNormalSB ); + + // Set up the "generic" shaders + // These handle rendering on GFX layers that don't support + // fixed function. Otherwise they disable shaders. + GFX->setupGenericShaders( GFXDevice::GSModColorTexture ); + + // Set the vertex buffer + GFX->setVertexBuffer( mVertexBuffer ); + + // Draw our triangles + GFX->drawPrimitive( GFXTriangleList, 0, 12 ); +} \ No newline at end of file diff --git a/T3D/examples/renderObjectExample.h b/T3D/examples/renderObjectExample.h new file mode 100644 index 0000000..c315519 --- /dev/null +++ b/T3D/examples/renderObjectExample.h @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RENDEROBJECTEXAMPLE_H_ +#define _RENDEROBJECTEXAMPLE_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + +//----------------------------------------------------------------------------- +// This class implements a basic SceneObject that can exist in the world at a +// 3D position and render itself. Note that RenderObjectExample handles its own +// rendering by submitting itself as an ObjectRenderInst (see +// renderInstance\renderPassmanager.h) along with a delegate for its render() +// function. However, the preffered rendering method in the engine is to submit +// a MeshRenderInst along with a Material, vertex buffer, primitive buffer, and +// transform and allow the RenderMeshMgr handle the actual rendering. You can +// see this implemented in RenderMeshExample. +//----------------------------------------------------------------------------- + +class RenderObjectExample : public SceneObject +{ + typedef SceneObject Parent; + + // Networking masks + // We need to implement at least one of these to allow + // the client version of the object to receive updates + // from the server version (like if it has been moved + // or edited) + enum MaskBits + { + TransformMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + //-------------------------------------------------------------------------- + // Rendering variables + //-------------------------------------------------------------------------- + // Define our vertex format here so we don't have to + // change it in multiple spots later + typedef GFXVertexPCN VertexType; + + // The handles for our StateBlocks + GFXStateBlockRef mNormalSB; + GFXStateBlockRef mReflectSB; + + // The GFX vertex and primitive buffers + GFXVertexBufferHandle< VertexType > mVertexBuffer; + +public: + RenderObjectExample(); + virtual ~RenderObjectExample(); + + // Declare this object as a ConsoleObject so that we can + // instantiate it into the world and network it + DECLARE_CONOBJECT(RenderObjectExample); + + //-------------------------------------------------------------------------- + // Object Editing + // Since there is always a server and a client object in Torque and we + // actually edit the server object we need to implement some basic + // networking functions + //-------------------------------------------------------------------------- + // Set up any fields that we want to be editable (like position) + static void initPersistFields(); + + // Handle when we are added to the scene and removed from the scene + bool onAdd(); + void onRemove(); + + // Override this so that we can dirty the network flag when it is called + void setTransform( const MatrixF &mat ); + + // This function handles sending the relevant data from the server + // object to the client object + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + // This function handles receiving relevant data from the server + // object and applying it to the client object + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + //-------------------------------------------------------------------------- + // Object Rendering + // Torque utilizes a "batch" rendering system. This means that it builds a + // list of objects that need to render (via RenderInst's) and then renders + // them all in one batch. This allows it to optimized on things like + // minimizing texture, state, and shader switching by grouping objects that + // use the same Materials. For this example, however, we are just going to + // get this object added to the list of objects that handle their own + // rendering. + //-------------------------------------------------------------------------- + // Create the geometry for rendering + void createGeometry(); + + // This is the function that allows this object to submit itself for rendering + bool prepRenderImage( SceneState *state, const U32 stateKey, + const U32 startZone, const bool modifyBaseZoneState = false); + + // This is the function that actually gets called to do the rendering + // Note that there is no longer a predefined name for this function. + // Instead, when we submit our ObjectRenderInst in prepRenderImage(), + // we bind this function as our rendering delegate function + void render( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); +}; + +#endif // _RENDEROBJECTEXAMPLE_H_ \ No newline at end of file diff --git a/T3D/examples/renderShapeExample.cpp b/T3D/examples/renderShapeExample.cpp new file mode 100644 index 0000000..84a520e --- /dev/null +++ b/T3D/examples/renderShapeExample.cpp @@ -0,0 +1,259 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/examples/renderShapeExample.h" + +#include "math/mathIO.h" +#include "sim/netConnection.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "core/resourceManager.h" +#include "core/stream/bitStream.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" + +IMPLEMENT_CO_NETOBJECT_V1(RenderShapeExample); + +//----------------------------------------------------------------------------- +// Object setup and teardown +//----------------------------------------------------------------------------- +RenderShapeExample::RenderShapeExample() +{ + // Flag this object so that it will always + // be sent across the network to clients + mNetFlags.set( Ghostable | ScopeAlways ); + + // Set it as a "static" object that casts shadows + mTypeMask |= StaticObjectType | ShadowCasterObjectType; + + // Make sure to initialize our TSShapeInstance to NULL + mShapeInstance = NULL; +} + +RenderShapeExample::~RenderShapeExample() +{ +} + +//----------------------------------------------------------------------------- +// Object Editing +//----------------------------------------------------------------------------- +void RenderShapeExample::initPersistFields() +{ + addGroup( "Rendering" ); + addField( "shapeFile", TypeStringFilename, Offset( mShapeFile, RenderShapeExample ) ); + endGroup( "Rendering" ); + + // SceneObject already handles exposing the transform + Parent::initPersistFields(); +} + +void RenderShapeExample::inspectPostApply() +{ + Parent::inspectPostApply(); + + // Flag the network mask to send the updates + // to the client object + setMaskBits( UpdateMask ); +} + +bool RenderShapeExample::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Set up a 1x1x1 bounding box + mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ), + Point3F( 0.5f, 0.5f, 0.5f ) ); + + resetWorldBox(); + + // Add this object to the scene + addToScene(); + + return true; +} + +void RenderShapeExample::onRemove() +{ + // Remove this object from the scene + removeFromScene(); + + // Remove our TSShapeInstance + if ( mShapeInstance ) + SAFE_DELETE( mShapeInstance ); + + Parent::onRemove(); +} + +void RenderShapeExample::setTransform(const MatrixF & mat) +{ + // Let SceneObject handle all of the matrix manipulation + Parent::setTransform( mat ); + + // Dirty our network mask so that the new transform gets + // transmitted to the client object + setMaskBits( TransformMask ); +} + +U32 RenderShapeExample::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + // Allow the Parent to get a crack at writing its info + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + // Write our transform information + if ( stream->writeFlag( mask & TransformMask ) ) + { + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + } + + // Write out any of the updated editable properties + if ( stream->writeFlag( mask & UpdateMask ) ) + { + stream->write( mShapeFile ); + + // Allow the server object a chance to handle a new shape + createShape(); + } + + return retMask; +} + +void RenderShapeExample::unpackUpdate(NetConnection *conn, BitStream *stream) +{ + // Let the Parent read any info it sent + Parent::unpackUpdate(conn, stream); + + if ( stream->readFlag() ) // TransformMask + { + mathRead(*stream, &mObjToWorld); + mathRead(*stream, &mObjScale); + + setTransform( mObjToWorld ); + } + + if ( stream->readFlag() ) // UpdateMask + { + stream->read( &mShapeFile ); + + if ( isProperlyAdded() ) + createShape(); + } +} + +//----------------------------------------------------------------------------- +// Object Rendering +//----------------------------------------------------------------------------- +void RenderShapeExample::createShape() +{ + if ( mShapeFile.isEmpty() ) + return; + + // If this is the same shape then no reason to update it + if ( mShapeInstance && mShapeFile.equal( mShape.getPath().getFullPath(), String::NoCase ) ) + return; + + // Clean up our previous shape + if ( mShapeInstance ) + SAFE_DELETE( mShapeInstance ); + mShape = NULL; + + // Attempt to get the resource from the ResourceManager + mShape = ResourceManager::get().load( mShapeFile ); + + if ( !mShape ) + { + Con::errorf( "RenderShapeExample::createShape() - Unable to load shape: %s", mShapeFile.c_str() ); + return; + } + + // Attempt to preload the Materials for this shape + if ( isClientObject() && + !mShape->preloadMaterialList( mShape.getPath() ) && + NetConnection::filesWereDownloaded() ) + { + mShape = NULL; + return; + } + + // Update the bounding box + mObjBox = mShape->bounds; + resetWorldBox(); + + // Create the TSShapeInstance + mShapeInstance = new TSShapeInstance( mShape, isClientObject() ); +} + +bool RenderShapeExample::prepRenderImage( SceneState *state, const U32 stateKey, + const U32 startZone, const bool modifyBaseZoneState) +{ + // Make sure we have a TSShapeInstance + if ( !mShapeInstance ) + return false; + + // Make sure we haven't already been processed by this state + if ( isLastState( state, stateKey ) ) + return false; + + // Update our state + setLastState(state, stateKey); + + // If we are actually rendered then create and submit our RenderInst + if ( state->isObjectRendered( this ) ) + { + // Calculate the distance of this object from the camera + Point3F cameraOffset; + getRenderTransform().getColumn( 3, &cameraOffset ); + cameraOffset -= state->getDiffuseCameraPosition(); + F32 dist = cameraOffset.len(); + if ( dist < 0.01f ) + dist = 0.01f; + + // Set up the LOD for the shape + F32 invScale = ( 1.0f / getMax( getMax( mObjScale.x, mObjScale.y ), mObjScale.z ) ); + + mShapeInstance->setDetailFromDistance( state, dist * invScale ); + + // Make sure we have a valid level of detail + if ( mShapeInstance->getCurrentDetail() < 0 ) + return false; + + // GFXTransformSaver is a handy helper class that restores + // the current GFX matrices to their original values when + // it goes out of scope at the end of the function + GFXTransformSaver saver; + + // Set up our TS render state + TSRenderState rdata; + rdata.setSceneState( state ); + rdata.setFadeOverride( 1.0f ); + + // Allow the light manager to set up any lights it needs + LightManager* lm = NULL; + if ( state->getSceneManager() ) + { + lm = state->getSceneManager()->getLightManager(); + if ( lm && !state->isShadowPass() ) + lm->setupLights( this, getWorldSphere() ); + } + + // Set the world matrix to the objects render transform + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + GFX->setWorldMatrix( mat ); + + // Animate the the shape + mShapeInstance->animate(); + + // Allow the shape to submit the RenderInst(s) for itself + mShapeInstance->render( rdata ); + + // Give the light manager a chance to reset the lights + if ( lm ) + lm->resetLights(); + } + + return false; +} \ No newline at end of file diff --git a/T3D/examples/renderShapeExample.h b/T3D/examples/renderShapeExample.h new file mode 100644 index 0000000..b63ce21 --- /dev/null +++ b/T3D/examples/renderShapeExample.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RENDERSHAPEEXAMPLE_H_ +#define _RENDERSHAPEEXAMPLE_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _TSSHAPEINSTANCE_H_ +#include "ts/tsShapeInstance.h" +#endif + +//----------------------------------------------------------------------------- +// This class implements a basic SceneObject that can exist in the world at a +// 3D position and render itself. There are several valid ways to render an +// object in Torque. This class makes use of the "TS" (three space) shape +// system. TS manages loading the various mesh formats supported by Torque as +// well was rendering those meshes (including LOD and animation...though this +// example doesn't include any animation over time). +//----------------------------------------------------------------------------- + +class RenderShapeExample : public SceneObject +{ + typedef SceneObject Parent; + + // Networking masks + // We need to implement a mask specifically to handle + // updating our transform from the server object to its + // client-side "ghost". We also need to implement a + // maks for handling editor updates to our properties + // (like material). + enum MaskBits + { + TransformMask = Parent::NextFreeMask << 0, + UpdateMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + //-------------------------------------------------------------------------- + // Rendering variables + //-------------------------------------------------------------------------- + // The name of the shape file we will use for rendering + String mShapeFile; + // The actual shape instance + TSShapeInstance* mShapeInstance; + // Store the resource so we can access the filename later + Resource mShape; + +public: + RenderShapeExample(); + virtual ~RenderShapeExample(); + + // Declare this object as a ConsoleObject so that we can + // instantiate it into the world and network it + DECLARE_CONOBJECT(RenderShapeExample); + + //-------------------------------------------------------------------------- + // Object Editing + // Since there is always a server and a client object in Torque and we + // actually edit the server object we need to implement some basic + // networking functions + //-------------------------------------------------------------------------- + // Set up any fields that we want to be editable (like position) + static void initPersistFields(); + + // Allows the object to update its editable settings + // from the server object to the client + virtual void inspectPostApply(); + + // Handle when we are added to the scene and removed from the scene + bool onAdd(); + void onRemove(); + + // Override this so that we can dirty the network flag when it is called + void setTransform( const MatrixF &mat ); + + // This function handles sending the relevant data from the server + // object to the client object + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + // This function handles receiving relevant data from the server + // object and applying it to the client object + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + //-------------------------------------------------------------------------- + // Object Rendering + // Torque utilizes a "batch" rendering system. This means that it builds a + // list of objects that need to render (via RenderInst's) and then renders + // them all in one batch. This allows it to optimized on things like + // minimizing texture, state, and shader switching by grouping objects that + // use the same Materials. + //-------------------------------------------------------------------------- + // Create the geometry for rendering + void createShape(); + + // This is the function that allows this object to submit itself for rendering + bool prepRenderImage( SceneState *state, const U32 stateKey, + const U32 startZone, const bool modifyBaseZoneState = false); +}; + +#endif // _RENDERSHAPEEXAMPLE_H_ \ No newline at end of file diff --git a/T3D/fps/guiClockHud.cpp b/T3D/fps/guiClockHud.cpp new file mode 100644 index 0000000..e66ce5b --- /dev/null +++ b/T3D/fps/guiClockHud.cpp @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "gui/core/guiControl.h" +#include "console/consoleTypes.h" +#include "T3D/shapeBase.h" +#include "gfx/gfxDrawUtil.h" + +//----------------------------------------------------------------------------- + +/// Vary basic HUD clock. +/// Displays the current simulation time offset from some base. The base time +/// is usually synchronized with the server as mission start time. This hud +/// currently only displays minutes:seconds. +class GuiClockHud : public GuiControl +{ + typedef GuiControl Parent; + + bool mShowFrame; + bool mShowFill; + + ColorF mFillColor; + ColorF mFrameColor; + ColorF mTextColor; + + S32 mTimeOffset; + +public: + GuiClockHud(); + + void setTime(F32 newTime); + F32 getTime(); + + void onRender( Point2I, const RectI &); + static void initPersistFields(); + DECLARE_CONOBJECT( GuiClockHud ); + DECLARE_CATEGORY( "Gui Game" ); +}; + + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT( GuiClockHud ); + +GuiClockHud::GuiClockHud() +{ + mShowFrame = mShowFill = true; + mFillColor.set(0, 0, 0, 0.5); + mFrameColor.set(0, 1, 0, 1); + mTextColor.set( 0, 1, 0, 1 ); + + mTimeOffset = 0; +} + +void GuiClockHud::initPersistFields() +{ + addGroup("Misc"); + addField( "showFill", TypeBool, Offset( mShowFill, GuiClockHud ) ); + addField( "showFrame", TypeBool, Offset( mShowFrame, GuiClockHud ) ); + addField( "fillColor", TypeColorF, Offset( mFillColor, GuiClockHud ) ); + addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiClockHud ) ); + addField( "textColor", TypeColorF, Offset( mTextColor, GuiClockHud ) ); + endGroup("Misc"); + + Parent::initPersistFields(); +} + + +//----------------------------------------------------------------------------- + +void GuiClockHud::onRender(Point2I offset, const RectI &updateRect) +{ + // Background first + if (mShowFill) + GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor); + + // Convert ms time into hours, minutes and seconds. + S32 time = S32(getTime()); + S32 secs = time % 60; + S32 mins = (time % 3600) / 60; + + // Currently only displays min/sec + char buf[256]; + dSprintf(buf,sizeof(buf), "%02d:%02d",mins,secs); + + // Center the text + offset.x += (getWidth() - mProfile->mFont->getStrWidth((const UTF8 *)buf)) / 2; + offset.y += (getHeight() - mProfile->mFont->getHeight()) / 2; + GFX->getDrawUtil()->setBitmapModulation(mTextColor); + GFX->getDrawUtil()->drawText(mProfile->mFont, offset, buf); + GFX->getDrawUtil()->clearBitmapModulation(); + + // Border last + if (mShowFrame) + GFX->getDrawUtil()->drawRect(updateRect, mFrameColor); +} + + +//----------------------------------------------------------------------------- + +void GuiClockHud::setTime(F32 time) +{ + // Set the current time in seconds. + mTimeOffset = S32(time * 1000) - Platform::getVirtualMilliseconds(); +} + +F32 GuiClockHud::getTime() +{ + // Return elapsed time in seconds. + return F32(mTimeOffset + Platform::getVirtualMilliseconds()) / 1000; +} + +ConsoleMethod(GuiClockHud,setTime,void,3, 3,"(time in sec)Sets the current base time for the clock") +{ + object->setTime(dAtof(argv[2])); +} + +ConsoleMethod(GuiClockHud,getTime, F32, 2, 2,"()Returns current time in secs.") +{ + return object->getTime(); +} diff --git a/T3D/fps/guiCrossHairHud.cpp b/T3D/fps/guiCrossHairHud.cpp new file mode 100644 index 0000000..e7d565b --- /dev/null +++ b/T3D/fps/guiCrossHairHud.cpp @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "gui/core/guiControl.h" +#include "gui/controls/guiBitmapCtrl.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" +#include "gfx/gfxDrawUtil.h" + + +//----------------------------------------------------------------------------- +/// Vary basic cross hair hud. +/// Uses the base bitmap control to render a bitmap, and decides whether +/// to draw or not depending on the current control object and it's state. +/// If there is ShapeBase object under the cross hair and it's named, +/// then a small health bar is displayed. +class GuiCrossHairHud : public GuiBitmapCtrl +{ + typedef GuiBitmapCtrl Parent; + + ColorF mDamageFillColor; + ColorF mDamageFrameColor; + Point2I mDamageRectSize; + Point2I mDamageOffset; + +protected: + void drawDamage(Point2I offset, F32 damage, F32 opacity); + +public: + GuiCrossHairHud(); + + void onRender( Point2I, const RectI &); + static void initPersistFields(); + DECLARE_CONOBJECT( GuiCrossHairHud ); + DECLARE_CATEGORY( "Gui Game" ); +}; + +/// Valid object types for which the cross hair will render, this +/// should really all be script controlled. +static const U32 ObjectMask = PlayerObjectType | VehicleObjectType; + + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT( GuiCrossHairHud ); + +GuiCrossHairHud::GuiCrossHairHud() +{ + mDamageFillColor.set( 0.0f, 1.0f, 0.0f, 1.0f ); + mDamageFrameColor.set( 1.0f, 0.6f, 0.0f, 1.0f ); + mDamageRectSize.set(50, 4); + mDamageOffset.set(0,32); +} + +void GuiCrossHairHud::initPersistFields() +{ + addGroup("Damage"); + addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiCrossHairHud ) ); + addField( "damageFrameColor", TypeColorF, Offset( mDamageFrameColor, GuiCrossHairHud ) ); + addField( "damageRect", TypePoint2I, Offset( mDamageRectSize, GuiCrossHairHud ) ); + addField( "damageOffset", TypePoint2I, Offset( mDamageOffset, GuiCrossHairHud ) ); + endGroup("Damage"); + Parent::initPersistFields(); +} + + +//----------------------------------------------------------------------------- + +void GuiCrossHairHud::onRender(Point2I offset, const RectI &updateRect) +{ + // Must have a connection and player control object + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) + return; + ShapeBase* control = dynamic_cast(conn->getControlObject()); + if (!control || !(control->getType() & ObjectMask) || !conn->isFirstPerson()) + return; + + // Parent render. + Parent::onRender(offset,updateRect); + + // Get control camera info + MatrixF cam; + Point3F camPos; + conn->getControlCameraTransform(0,&cam); + cam.getColumn(3, &camPos); + + // Extend the camera vector to create an endpoint for our ray + Point3F endPos; + cam.getColumn(1, &endPos); + endPos *= gClientSceneGraph->getVisibleDistance(); + endPos += camPos; + + // Collision info. We're going to be running LOS tests and we + // don't want to collide with the control object. + static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType; + control->disableCollision(); + + RayInfo info; + if (gClientContainer.castRay(camPos, endPos, losMask, &info)) { + // Hit something... but we'll only display health for named + // ShapeBase objects. Could mask against the object type here + // and do a static cast if it's a ShapeBaseObjectType, but this + // isn't a performance situation, so I'll just use dynamic_cast. + if (ShapeBase* obj = dynamic_cast(info.object)) + if (obj->getShapeName()) { + offset.x = updateRect.point.x + updateRect.extent.x / 2; + offset.y = updateRect.point.y + updateRect.extent.y / 2; + drawDamage(offset + mDamageOffset, obj->getDamageValue(), 1); + } + } + + // Restore control object collision + control->enableCollision(); +} + + +//----------------------------------------------------------------------------- +/** + Display a damage bar ubove the shape. + This is a support funtion, called by onRender. +*/ +void GuiCrossHairHud::drawDamage(Point2I offset, F32 damage, F32 opacity) +{ + mDamageFillColor.alpha = mDamageFrameColor.alpha = opacity; + + // Damage should be 0->1 (0 being no damage,or healthy), but + // we'll just make sure here as we flip it. + damage = mClampF(1 - damage, 0, 1); + + // Center the bar + RectI rect(offset, mDamageRectSize); + rect.point.x -= mDamageRectSize.x / 2; + + // Draw the border + GFX->getDrawUtil()->drawRect(rect, mDamageFrameColor); + + // Draw the damage % fill + rect.point += Point2I(1, 1); + rect.extent -= Point2I(1, 1); + rect.extent.x = (S32)(rect.extent.x * damage); + if (rect.extent.x == 1) + rect.extent.x = 2; + if (rect.extent.x > 0) + GFX->getDrawUtil()->drawRectFill(rect, mDamageFillColor); +} diff --git a/T3D/fps/guiHealthBarHud.cpp b/T3D/fps/guiHealthBarHud.cpp new file mode 100644 index 0000000..802f8da --- /dev/null +++ b/T3D/fps/guiHealthBarHud.cpp @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "gui/core/guiControl.h" +#include "console/consoleTypes.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" +#include "gfx/gfxDrawUtil.h" + + +//----------------------------------------------------------------------------- +/// A basic health bar control. +/// This gui displays the damage value of the current PlayerObjectType +/// control object. The gui can be set to pulse if the health value +/// drops below a set value. This control only works if a server +/// connection exists and it's control object is a PlayerObjectType. If +/// either of these requirements is false, the control is not rendered. +class GuiHealthBarHud : public GuiControl +{ + typedef GuiControl Parent; + + bool mShowFrame; + bool mShowFill; + bool mDisplayEnergy; + + ColorF mFillColor; + ColorF mFrameColor; + ColorF mDamageFillColor; + + S32 mPulseRate; + F32 mPulseThreshold; + + F32 mValue; + +public: + GuiHealthBarHud(); + + void onRender( Point2I, const RectI &); + static void initPersistFields(); + DECLARE_CONOBJECT( GuiHealthBarHud ); + DECLARE_CATEGORY( "Gui Game" ); +}; + + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT( GuiHealthBarHud ); + +GuiHealthBarHud::GuiHealthBarHud() +{ + mShowFrame = mShowFill = true; + mDisplayEnergy = false; + mFillColor.set(0, 0, 0, 0.5); + mFrameColor.set(0, 1, 0, 1); + mDamageFillColor.set(0, 1, 0, 1); + + mPulseRate = 0; + mPulseThreshold = 0.3f; + mValue = 0.2f; +} + +void GuiHealthBarHud::initPersistFields() +{ + addGroup("Colors"); + addField( "fillColor", TypeColorF, Offset( mFillColor, GuiHealthBarHud ) ); + addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiHealthBarHud ) ); + addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiHealthBarHud ) ); + endGroup("Colors"); + + addGroup("Pulse"); + addField( "pulseRate", TypeS32, Offset( mPulseRate, GuiHealthBarHud ) ); + addField( "pulseThreshold", TypeF32, Offset( mPulseThreshold, GuiHealthBarHud ) ); + endGroup("Pulse"); + + addGroup("Misc"); + addField( "showFill", TypeBool, Offset( mShowFill, GuiHealthBarHud ) ); + addField( "showFrame", TypeBool, Offset( mShowFrame, GuiHealthBarHud ) ); + addField( "displayEnergy", TypeBool, Offset( mDisplayEnergy, GuiHealthBarHud ) ); + endGroup("Misc"); + + Parent::initPersistFields(); +} + + +//----------------------------------------------------------------------------- +/** + Gui onRender method. + Renders a health bar with filled background and border. +*/ +void GuiHealthBarHud::onRender(Point2I offset, const RectI &updateRect) +{ + // Must have a connection and player control object + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) + return; + ShapeBase* control = dynamic_cast(conn->getControlObject()); + if (!control || !(control->getType() & PlayerObjectType)) + return; + + if(mDisplayEnergy) + { + mValue = control->getEnergyValue(); + } + else + { + // We'll just grab the damage right off the control object. + // Damage value 0 = no damage. + mValue = 1 - control->getDamageValue(); + } + + + // Background first + if (mShowFill) + GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor); + + // Pulse the damage fill if it's below the threshold + if (mPulseRate != 0) + if (mValue < mPulseThreshold) + { + U32 time = Platform::getVirtualMilliseconds(); + F32 alpha = 2.0f * F32(time % mPulseRate) / F32(mPulseRate); + mDamageFillColor.alpha = (alpha > 1.0f)? 2.0f - alpha: alpha; + } + else + mDamageFillColor.alpha = 1; + + // Render damage fill % + RectI rect(updateRect); + if(getWidth() > getHeight()) + rect.extent.x = (S32)(rect.extent.x * mValue); + else + { + S32 bottomY = rect.point.y + rect.extent.y; + rect.extent.y = (S32)(rect.extent.y * mValue); + rect.point.y = bottomY - rect.extent.y; + } + GFX->getDrawUtil()->drawRectFill(rect, mDamageFillColor); + + // Border last + if (mShowFrame) + GFX->getDrawUtil()->drawRect(updateRect, mFrameColor); +} diff --git a/T3D/fps/guiShapeNameHud.cpp b/T3D/fps/guiShapeNameHud.cpp new file mode 100644 index 0000000..231fe0b --- /dev/null +++ b/T3D/fps/guiShapeNameHud.cpp @@ -0,0 +1,291 @@ +#include "guiShapeNameHud.h" +#include "T3D/player.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CONOBJECT(GuiShapeNameHud); + +/// Default distance for object's information to be displayed. +static const F32 cDefaultVisibleDistance = 500.0f; +Vector GuiShapeNameHud::PlayersInScene; + +GuiShapeNameHud::GuiShapeNameHud() +{ + mFillColor.set( 0.25f, 0.25f, 0.25f, 0.25f ); + mFrameColor.set( 0, 1, 0, 1 ); + mTextColor.set( 0, 1, 0, 1 ); + mShowFrame = mShowFill = true; + mVerticalOffset = 0.5f; + mDistanceFade = 0.1f; + + _m_tex_MarkBmp2 = NULL; + _m_tex_MarkBmp1 = NULL; + _m_str_MarkBmp2 = NULL; + _m_str_MarkBmp1 = NULL; +} + +void GuiShapeNameHud::initPersistFields() +{ + Parent::initPersistFields(); + addGroup("Colors"); + addField( "fillColor", TypeColorF, Offset( mFillColor, GuiShapeNameHud ) ); + addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiShapeNameHud ) ); + addField( "textColor", TypeColorF, Offset( mTextColor, GuiShapeNameHud ) ); + endGroup("Colors"); + + addGroup("Misc"); + addField( "showFill", TypeBool, Offset( mShowFill, GuiShapeNameHud ) ); + addField( "showFrame", TypeBool, Offset( mShowFrame, GuiShapeNameHud ) ); + addField( "verticalOffset", TypeF32, Offset( mVerticalOffset, GuiShapeNameHud ) ); + addField( "distanceFade", TypeF32, Offset( mDistanceFade, GuiShapeNameHud ) ); + endGroup("Misc"); + + addProtectedField( "MarkMissionComplete", TypeFilename, Offset( _m_str_MarkBmp1, GuiShapeNameHud ), &setMarkBmp1Name, &defaultProtectedGetFn, "" ); + addProtectedField( "MarkMissionValide", TypeFilename, Offset( _m_str_MarkBmp2, GuiShapeNameHud ), &setMarkBmp2Name, &defaultProtectedGetFn, "" ); +} + + +static void findObjectsCallback(SceneObject* obj, void * val) +{ + Vector * list = (Vector*)val; + list->push_back(obj); +} + +//---------------------------------------------------------------------------- +/// Core rendering method for this control. +/// +/// This method scans through all the current client ShapeBase objects. +/// If one is named, it displays the name and damage information for it. +/// +/// Information is offset from the center of the object's bounding box, +/// unless the object is a PlayerObjectType, in which case the eye point +/// is used. +/// +/// @param updateRect Extents of control. +void GuiShapeNameHud::onRender( Point2I, const RectI &updateRect) +{ + // Background fill first + if (mShowFill) + GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor); + + // Must be in a TS Control + GuiTSCtrl *parent = dynamic_cast(getParent()); + if (!parent) return; + + // Must have a connection and control object + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) return; + GameBase * control = dynamic_cast(conn->getControlObject()); + if (!control) return; + + // Get control camera info + MatrixF cam; + Point3F camPos; + VectorF camDir; + conn->getControlCameraTransform(0,&cam); + cam.getColumn(3, &camPos); + cam.getColumn(1, &camDir); + + F32 camFov; + conn->getControlCameraFov(&camFov); + camFov = mDegToRad(camFov) / 2; + + // Visible distance info & name fading + F32 visDistance = 200;//gClientSceneGraph->getVisibleDistance(); + F32 visDistanceSqr = 40000;//visDistance * visDistance; + F32 fadeDistance = visDistance * mDistanceFade; + + // Collision info. We're going to be running LOS tests and we + // don't want to collide with the control object. + static U32 losMask = TerrainObjectType | InteriorObjectType | ShapeBaseObjectType; + control->disableCollision(); + + + U32 mask = PlayerObjectType | VehicleObjectType ; + PlayersInScene.clear(); + gClientContainer.findObjects(mask, findObjectsCallback, &PlayersInScene); + + // All ghosted objects are added to the server connection group, + // so we can find all the shape base objects by iterating through + // our current connection. +#if 1 + for (int n = 0 ; n < PlayersInScene.size() ; n++) + { + if (PlayersInScene[n]->getType() & ShapeBaseObjectType) + { + ShapeBase * shape = static_cast(PlayersInScene[n]); +#else + for (SimSetIterator itr(conn); *itr; ++itr) { + if ((*itr)->getType() & ShapeBaseObjectType) { + ShapeBase * shape = static_cast(*itr); +#endif + if (shape != control && shape->getShapeName()) + { + + // Target pos to test, if it's a player run the LOS to his eye + // point, otherwise we'll grab the generic box center. + Point3F shapePos; + if (shape->getType() & PlayerObjectType) + { + MatrixF eye; + + // Use the render eye transform, otherwise we'll see jittering + shape->getRenderEyeTransform(&eye); + eye.getColumn(3, &shapePos); + } + else + { + // Use the render transform instead of the box center + // otherwise it'll jitter. + MatrixF srtMat = shape->getRenderTransform(); + srtMat.getColumn(3, &shapePos); + } + VectorF shapeDir = shapePos - camPos; + + // Test to see if it's in range + F32 shapeDist = shapeDir.lenSquared(); + if (shapeDist == 0 || shapeDist > visDistanceSqr) + continue; + shapeDist = mSqrt(shapeDist); + + // Test to see if it's within our viewcone, this test doesn't + // actually match the viewport very well, should consider + // projection and box test. + shapeDir.normalize(); + F32 dot = mDot(shapeDir, camDir); + if (dot < camFov) + continue; + + // Test to see if it's behind something, and we want to + // ignore anything it's mounted on when we run the LOS. + RayInfo info; + shape->disableCollision(); + ShapeBase *mount = dynamic_cast(shape->getObjectMount()); + if (mount) + mount->disableCollision(); + bool los = !gClientContainer.castRay(camPos, shapePos,losMask, &info); + shape->enableCollision(); + if (mount) + mount->enableCollision(); + if (!los) + continue; + + // Project the shape pos into screen space and calculate + // the distance opacity used to fade the labels into the + // distance. + Point3F projPnt; + shapePos.z += mVerticalOffset; + if (!parent->project(shapePos, &projPnt)) + continue; + F32 opacity = (shapeDist < fadeDistance)? 1.0: + 1.0 - (shapeDist - fadeDistance) / (visDistance - fadeDistance); + + // Render the shape's name + drawName(Point2I((S32)projPnt.x, (S32)projPnt.y),shape->getShapeName(),opacity); + // Render the mark + /* + Player * pNPC = dynamic_cast(shape); + if (pNPC && pNPC->getMissionObjSimID()) + { + projPnt.y -= 30; + drawMark(Point2I((S32)projPnt.x, (S32)projPnt.y),MARK_CANRETURN,shapeDist); + } + */ + } + } + } + + // Restore control object collision + control->enableCollision(); + + // Border last + if (mShowFrame) + GFX->getDrawUtil()->drawRect(updateRect, mFrameColor); +} + + +//---------------------------------------------------------------------------- +/// Render object names. +/// +/// Helper function for GuiShapeNameHud::onRender +/// +/// @param offset Screen coordinates to render name label. (Text is centered +/// horizontally about this location, with bottom of text at +/// specified y position.) +/// @param name String name to display. +/// @param opacity Opacity of name (a fraction). +void GuiShapeNameHud::drawName(Point2I offset, const char *name, F32 opacity) +{ + // Center the name + offset.x -= mProfile->mFont->getStrWidth((const UTF8 *)name) / 2; + offset.y -= mProfile->mFont->getHeight(); + + // Deal with opacity and draw. + mTextColor.alpha = opacity; + GFX->getDrawUtil()->setBitmapModulation(mTextColor); + GFX->getDrawUtil()->drawText(mProfile->mFont, offset, name); + GFX->getDrawUtil()->clearBitmapModulation(); +} + +bool GuiShapeNameHud::setMarkBmp1Name( void *obj, const char *data ) +{ + static_cast( obj )->setMarkBmp1( data ); + return false; +} + +void GuiShapeNameHud::setMarkBmp1( const char * bmpName ) +{ + _m_str_MarkBmp1 = StringTable->insert(bmpName); + if (*_m_str_MarkBmp1) + { + _m_tex_MarkBmp1.set( _m_str_MarkBmp1, &GFXDefaultGUIProfile ,"mark 1"); + } + else + _m_tex_MarkBmp1 = NULL; + + setUpdate(); +} + +bool GuiShapeNameHud::setMarkBmp2Name( void *obj, const char *data ) +{ + static_cast( obj )->setMarkBmp2( data ); + return false; +} + +void GuiShapeNameHud::setMarkBmp2( const char * bmpName ) +{ + _m_str_MarkBmp2 = StringTable->insert(bmpName); + if (*_m_str_MarkBmp2) + { + _m_tex_MarkBmp2.set( _m_str_MarkBmp2, &GFXDefaultGUIProfile ,"mark 2"); + } + else + _m_tex_MarkBmp2 = NULL; + + setUpdate(); +} + +void GuiShapeNameHud::drawMark( Point2I offset , MARKS mark , F32 distance ) +{ + GFXTexHandle handle = NULL; + switch(mark) + { + case MARK_QUESTION: + handle = _m_tex_MarkBmp1; + case MARK_CANRETURN: + handle = _m_tex_MarkBmp2; + } + if(handle) + { + F32 ratio = ( 1 - distance / 20.f ); + if (ratio > 0) + { + U32 width = 70 * ratio; + U32 height = 200 * ratio; + offset.x -= width / 2; + offset.y -= height; + RectI rect(offset.x,offset.y,width,height); + GFX->getDrawUtil()->drawBitmapStretch(handle,rect); + } + handle = NULL; + } +} \ No newline at end of file diff --git a/T3D/fps/guiShapeNameHud.h b/T3D/fps/guiShapeNameHud.h new file mode 100644 index 0000000..3cfaa0e --- /dev/null +++ b/T3D/fps/guiShapeNameHud.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Element Engine + +//----------------------------------------------------------------------------- + +#include "gui/core/guiControl.h" +#include "gui/3d/guiTSControl.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" + +//---------------------------------------------------------------------------- +/// Displays name & damage above shape objects. +/// +/// This control displays the name and damage value of all named +/// ShapeBase objects on the client. The name and damage of objects +/// within the control's display area are overlayed above the object. +/// +/// This GUI control must be a child of a TSControl, and a server connection +/// and control object must be present. +/// +/// This is a stand-alone control and relies only on the standard base GuiControl. +class GuiShapeNameHud : public GuiControl { + typedef GuiControl Parent; + + // field data + ColorF mFillColor; + ColorF mFrameColor; + ColorF mTextColor; + + F32 mVerticalOffset; + F32 mDistanceFade; + bool mShowFrame; + bool mShowFill; + + StringTableEntry _m_str_MarkBmp1; // ÎʺŵÄͼƬÃû + StringTableEntry _m_str_MarkBmp2; //¸Ð̾ºÅµÄͼƬÃû + GFXTexHandle _m_tex_MarkBmp1;//ÎʺŵÄͼƬ¾ä±ú + GFXTexHandle _m_tex_MarkBmp2;//¸Ð̾ºÅµÄͼƬ¾ä±ú +public: + enum MARKS + { + MARK_QUESTION, + MARK_CANRETURN, + }; +protected: + void drawName( Point2I offset, const char *buf, F32 opacity); + void drawMark(Point2I offset , MARKS mark , F32 distance); +public: + + static Vector PlayersInScene; + + GuiShapeNameHud(); + + // GuiControl + virtual void onRender(Point2I offset, const RectI &updateRect); + static void initPersistFields(); + + static bool setMarkBmp1Name( void *obj, const char *data ); + void setMarkBmp1(const char * bmpName); + static bool setMarkBmp2Name( void *obj, const char *data ); + void setMarkBmp2(const char * bmpName); + DECLARE_CONOBJECT( GuiShapeNameHud ); +}; \ No newline at end of file diff --git a/T3D/fx/cameraFXMgr.cpp b/T3D/fx/cameraFXMgr.cpp new file mode 100644 index 0000000..655276b --- /dev/null +++ b/T3D/fx/cameraFXMgr.cpp @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/fx/cameraFXMgr.h" + +#include "math/mRandom.h" +#include "math/mMatrix.h" + +// global cam fx +CameraFXManager gCamFXMgr; + + +//************************************************************************** +// Camera effect +//************************************************************************** +CameraFX::CameraFX() +{ + mElapsedTime = 0.0; + mDuration = 1.0; +} + +//-------------------------------------------------------------------------- +// Update +//-------------------------------------------------------------------------- +void CameraFX::update( F32 dt ) +{ + mElapsedTime += dt; +} + + + + + +//************************************************************************** +// Camera shake effect +//************************************************************************** +CameraShake::CameraShake() +{ + mFreq.zero(); + mAmp.zero(); + mStartAmp.zero(); + mTimeOffset.zero(); + mCamFXTrans.identity(); + mFalloff = 10.0; +} + +//-------------------------------------------------------------------------- +// Update +//-------------------------------------------------------------------------- +void CameraShake::update( F32 dt ) +{ + Parent::update( dt ); + + fadeAmplitude(); + + VectorF camOffset; + camOffset.x = mAmp.x * sin( M_2PI * (mTimeOffset.x + mElapsedTime) * mFreq.x ); + camOffset.y = mAmp.y * sin( M_2PI * (mTimeOffset.y + mElapsedTime) * mFreq.y ); + camOffset.z = mAmp.z * sin( M_2PI * (mTimeOffset.z + mElapsedTime) * mFreq.z ); + + VectorF rotAngles; + rotAngles.x = camOffset.x * 10.0 * M_PI/180.0; + rotAngles.y = camOffset.y * 10.0 * M_PI/180.0; + rotAngles.z = camOffset.z * 10.0 * M_PI/180.0; + MatrixF rotMatrix( EulerF( rotAngles.x, rotAngles.y, rotAngles.z ) ); + + mCamFXTrans = rotMatrix; + mCamFXTrans.setPosition( camOffset ); +} + +//-------------------------------------------------------------------------- +// Fade out the amplitude over time +//-------------------------------------------------------------------------- +void CameraShake::fadeAmplitude() +{ + F32 percentDone = (mElapsedTime / mDuration); + if( percentDone > 1.0 ) percentDone = 1.0; + + F32 time = 1 + percentDone * mFalloff; + time = 1 / (time * time); + + mAmp = mStartAmp * time; +} + +//-------------------------------------------------------------------------- +// Initialize +//-------------------------------------------------------------------------- +void CameraShake::init() +{ + mTimeOffset.x = 0.0; + mTimeOffset.y = gRandGen.randF(); + mTimeOffset.z = gRandGen.randF(); +} + +//************************************************************************** +// CameraFXManager +//************************************************************************** +CameraFXManager::CameraFXManager() +{ + mCamFXTrans.identity(); +} + +//-------------------------------------------------------------------------- +// Destructor +//-------------------------------------------------------------------------- +CameraFXManager::~CameraFXManager() +{ + clear(); +} + +//-------------------------------------------------------------------------- +// Add new effect to currently running list +//-------------------------------------------------------------------------- +void CameraFXManager::addFX( CameraFX *newFX ) +{ + mFXList.pushFront( newFX ); +} + +//-------------------------------------------------------------------------- +// Clear all currently running camera effects +//-------------------------------------------------------------------------- +void CameraFXManager::clear() +{ + for(CamFXList::Iterator i = mFXList.begin(); i != mFXList.end(); ++i) + { + delete *i; + } + + mFXList.clear(); +} + +//-------------------------------------------------------------------------- +// Update camera effects +//-------------------------------------------------------------------------- +void CameraFXManager::update( F32 dt ) +{ + mCamFXTrans.identity(); + + CamFXList::Iterator cur; + for(CamFXList::Iterator i = mFXList.begin(); i != mFXList.end(); /*Trickiness*/) + { + // Store previous iterator and increment while iterator is still valid. + cur = i; + ++i; + CameraFX * curFX = *cur; + curFX->update( dt ); + MatrixF fxTrans = curFX->getTrans(); + + mCamFXTrans.mul( fxTrans ); + + if( curFX->isExpired() ) + { + delete curFX; + mFXList.erase( cur ); + } + } +} diff --git a/T3D/fx/cameraFXMgr.h b/T3D/fx/cameraFXMgr.h new file mode 100644 index 0000000..34ad085 --- /dev/null +++ b/T3D/fx/cameraFXMgr.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CAMERAFXMGR_H_ +#define _CAMERAFXMGR_H_ + +#ifndef _TORQUE_LIST_ +#include "core/util/tList.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif + +//************************************************************************** +// Abstract camera effect template +//************************************************************************** +class CameraFX +{ +protected: + MatrixF mCamFXTrans; + F32 mElapsedTime; + F32 mDuration; + +public: + CameraFX(); + + MatrixF & getTrans(){ return mCamFXTrans; } + bool isExpired(){ return mElapsedTime >= mDuration; } + void setDuration( F32 duration ){ mDuration = duration; } + + virtual void update( F32 dt ); +}; + +//-------------------------------------------------------------------------- +// Camera shake effect +//-------------------------------------------------------------------------- +class CameraShake : public CameraFX +{ + typedef CameraFX Parent; + + VectorF mFreq; // these are vectors to represent these values in 3D + VectorF mStartAmp; + VectorF mAmp; + VectorF mTimeOffset; + F32 mFalloff; + +public: + CameraShake(); + + void init(); + void fadeAmplitude(); + void setFalloff( F32 falloff ){ mFalloff = falloff; } + void setFrequency( VectorF &freq ){ mFreq = freq; } + void setAmplitude( VectorF & ){ mStartAmp = amp; } + + virtual void update( F32 dt ); +}; + + +//************************************************************************** +// CameraFXManager +//************************************************************************** +class CameraFXManager +{ + typedef CameraFX * CameraFXPtr; + + MatrixF mCamFXTrans; + typedef Torque::List CamFXList; + CamFXList mFXList; + +public: + void addFX( CameraFX *newFX ); + void clear(); + MatrixF & getTrans(){ return mCamFXTrans; } + void update( F32 dt ); + + CameraFXManager(); + ~CameraFXManager(); + +}; + +extern CameraFXManager gCamFXMgr; + + +#endif diff --git a/T3D/fx/explosion.cpp b/T3D/fx/explosion.cpp new file mode 100644 index 0000000..c4653b4 --- /dev/null +++ b/T3D/fx/explosion.cpp @@ -0,0 +1,1092 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/fx/explosion.h" + +#include "core/resourceManager.h" +#include "console/consoleTypes.h" +#include "console/typeValidators.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" +#include "core/stream/bitStream.h" +#include "sim/netConnection.h" +#include "ts/tsShape.h" +#include "ts/tsShapeInstance.h" +#include "math/mRandom.h" +#include "math/mathIO.h" +#include "math/mathUtils.h" +#include "T3D/debris.h" +#include "T3D/gameConnection.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/fx/cameraFXMgr.h" +#include "T3D/debris.h" +#include "T3D/shapeBase.h" +#include "T3D/gameProcess.h" +#include "renderInstance/renderPassManager.h" + +IMPLEMENT_CONOBJECT(Explosion); + +#define MaxLightRadius 20 + +MRandomLCG sgRandom(0xdeadbeef); + +ConsoleFunction( calcExplosionCoverage, F32, 4, 4, "(Point3F source, SceneObject originator, bitset coverageMask)") +{ + Point3F pos, center; + + dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z); + S32 id = dAtoi(argv[2]); + U32 covMask = (U32)dAtoi(argv[3]); + + SceneObject* sceneObject = NULL; + if (Sim::findObject(id, sceneObject) == false) { + Con::warnf(ConsoleLogEntry::General, "calcExplosionCoverage: couldn't find object: %s", argv[2]); + return 1.0f; + } + if (sceneObject->isClientObject() || sceneObject->getContainer() == NULL) { + Con::warnf(ConsoleLogEntry::General, "calcExplosionCoverage: object is on the client, or not in the container system"); + return 1.0f; + } + + sceneObject->getObjBox().getCenter(¢er); + center.convolve(sceneObject->getScale()); + sceneObject->getTransform().mulP(center); + + RayInfo rayInfo; + sceneObject->disableCollision(); + if (sceneObject->getContainer()->castRay(pos, center, covMask, &rayInfo) == true) { + // Try casting up and then out + if (sceneObject->getContainer()->castRay(pos, pos + Point3F(0.0f, 0.0f, 1.0f), covMask, &rayInfo) == false) + { + if (sceneObject->getContainer()->castRay(pos + Point3F(0.0f, 0.0f, 1.0f), center, covMask, &rayInfo) == false) + { + sceneObject->enableCollision(); + return 1.0f; + } + } + + sceneObject->enableCollision(); + return 0.0f; + } else { + sceneObject->enableCollision(); + return 1.0f; + } +} + +//---------------------------------------------------------------------------- +// +IMPLEMENT_CO_DATABLOCK_V1(ExplosionData); + +ExplosionData::ExplosionData() +{ + dtsFileName = NULL; + particleDensity = 10; + particleRadius = 1.0f; + + faceViewer = false; + + soundProfile = NULL; + particleEmitter = NULL; + soundProfileId = 0; + particleEmitterId = 0; + + explosionScale.set(1.0f, 1.0f, 1.0f); + playSpeed = 1.0f; + + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); + dMemset( debrisList, 0, sizeof( debrisList ) ); + dMemset( debrisIDList, 0, sizeof( debrisIDList ) ); + + debrisThetaMin = 0.0f; + debrisThetaMax = 90.0f; + debrisPhiMin = 0.0f; + debrisPhiMax = 360.0f; + debrisNum = 1; + debrisNumVariance = 0; + debrisVelocity = 2.0f; + debrisVelocityVariance = 0.0f; + + dMemset( explosionList, 0, sizeof( explosionList ) ); + dMemset( explosionIDList, 0, sizeof( explosionIDList ) ); + + delayMS = 0; + delayVariance = 0; + lifetimeMS = 1000; + lifetimeVariance = 0; + offset = 0.0f; + + shockwave = NULL; + shockwaveID = 0; + shockwaveOnTerrain = false; + + shakeCamera = false; + camShakeFreq.set( 10.0f, 10.0f, 10.0f ); + camShakeAmp.set( 1.0f, 1.0f, 1.0f ); + camShakeDuration = 1.5f; + camShakeRadius = 10.0f; + camShakeFalloff = 10.0f; + + for( U32 i=0; i= 0.01", getName()); + explosionScale.x = explosionScale.x < 0.01f ? 0.01f : explosionScale.x; + explosionScale.y = explosionScale.y < 0.01f ? 0.01f : explosionScale.y; + explosionScale.z = explosionScale.z < 0.01f ? 0.01f : explosionScale.z; + } + + if (debrisThetaMin < 0.0f) + { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisThetaMin < 0.0", getName()); + debrisThetaMin = 0.0f; + } + if (debrisThetaMax > 180.0f) + { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisThetaMax > 180.0", getName()); + debrisThetaMax = 180.0f; + } + if (debrisThetaMin > debrisThetaMax) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisThetaMin > debrisThetaMax", getName()); + debrisThetaMin = debrisThetaMax; + } + if (debrisPhiMin < 0.0f) + { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisPhiMin < 0.0", getName()); + debrisPhiMin = 0.0f; + } + if (debrisPhiMax > 360.0f) + { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisPhiMax > 360.0", getName()); + debrisPhiMax = 360.0f; + } + if (debrisPhiMin > debrisPhiMax) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisPhiMin > debrisPhiMax", getName()); + debrisPhiMin = debrisPhiMax; + } + if (debrisNum > 1000) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisNum > 1000", getName()); + debrisNum = 1000; + } + if (debrisNumVariance > 1000) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisNumVariance > 1000", getName()); + debrisNumVariance = 1000; + } + if (debrisVelocity < 0.1f) + { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocity < 0.1", getName()); + debrisVelocity = 0.1f; + } + if (debrisVelocityVariance > 1000) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) debrisVelocityVariance > 1000", getName()); + debrisVelocityVariance = 1000; + } + if (playSpeed < 0.05f) + { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) playSpeed < 0.05", getName()); + playSpeed = 0.05f; + } + if (lifetimeMS < 1) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) lifetimeMS < 1", getName()); + lifetimeMS = 1; + } + if (lifetimeVariance > lifetimeMS) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) lifetimeVariance > lifetimeMS", getName()); + lifetimeVariance = lifetimeMS; + } + if (delayMS < 0) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) delayMS < 0", getName()); + delayMS = 0; + } + if (delayVariance > delayMS) { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) delayVariance > delayMS", getName()); + delayVariance = delayMS; + } + if (offset < 0.0f) + { + Con::warnf(ConsoleLogEntry::General, "ExplosionData(%s) offset < 0.0", getName()); + offset = 0.0f; + } + + S32 i; + for( i=0; iwriteString(dtsFileName); + + if (stream->writeFlag(soundProfile != NULL)) + stream->writeRangedU32(soundProfile->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(particleEmitter)) + stream->writeRangedU32(particleEmitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + + stream->writeInt(particleDensity, 14); + stream->write(particleRadius); + stream->writeFlag(faceViewer); + if(stream->writeFlag(explosionScale.x != 1 || explosionScale.y != 1 || explosionScale.z != 1)) + { + stream->writeInt((S32)(explosionScale.x * 100), 16); + stream->writeInt((S32)(explosionScale.y * 100), 16); + stream->writeInt((S32)(explosionScale.z * 100), 16); + } + stream->writeInt((S32)(playSpeed * 20), 14); + stream->writeRangedU32((U32)debrisThetaMin, 0, 180); + stream->writeRangedU32((U32)debrisThetaMax, 0, 180); + stream->writeRangedU32((U32)debrisPhiMin, 0, 360); + stream->writeRangedU32((U32)debrisPhiMax, 0, 360); + stream->writeRangedU32((U32)debrisNum, 0, 1000); + stream->writeRangedU32(debrisNumVariance, 0, 1000); + stream->writeInt((S32)(debrisVelocity * 10), 14); + stream->writeRangedU32((U32)(debrisVelocityVariance * 10), 0, 10000); + stream->writeInt(delayMS >> 5, 16); + stream->writeInt(delayVariance >> 5, 16); + stream->writeInt(lifetimeMS >> 5, 16); + stream->writeInt(lifetimeVariance >> 5, 16); + stream->write(offset); + + stream->writeFlag( shakeCamera ); + stream->write(camShakeFreq.x); + stream->write(camShakeFreq.y); + stream->write(camShakeFreq.z); + stream->write(camShakeAmp.x); + stream->write(camShakeAmp.y); + stream->write(camShakeAmp.z); + stream->write(camShakeDuration); + stream->write(camShakeRadius); + stream->write(camShakeFalloff); + + for( S32 j=0; jwriteFlag( debrisList[j] ) ) + { + stream->writeRangedU32( debrisList[j]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + S32 i; + for( i=0; iwriteFlag( emitterList[i] != NULL ) ) + { + stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for( i=0; iwriteFlag( explosionList[i] != NULL ) ) + { + stream->writeRangedU32( explosionList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + U32 count; + for(count = 0; count < EC_NUM_TIME_KEYS; count++) + if(times[i] >= 1) + break; + count++; + if(count > EC_NUM_TIME_KEYS) + count = EC_NUM_TIME_KEYS; + + stream->writeRangedU32(count, 0, EC_NUM_TIME_KEYS); + + for( i=0; iwriteFloat( times[i], 8 ); + + for( i=0; iwriteRangedU32((U32)(sizes[i].x * 100), 0, 16000); + stream->writeRangedU32((U32)(sizes[i].y * 100), 0, 16000); + stream->writeRangedU32((U32)(sizes[i].z * 100), 0, 16000); + } + + // Dynamic light info + stream->writeFloat(lightStartRadius/MaxLightRadius, 8); + stream->writeFloat(lightEndRadius/MaxLightRadius, 8); + stream->writeFloat(lightStartColor.red,7); + stream->writeFloat(lightStartColor.green,7); + stream->writeFloat(lightStartColor.blue,7); + stream->writeFloat(lightEndColor.red,7); + stream->writeFloat(lightEndColor.green,7); + stream->writeFloat(lightEndColor.blue,7); + stream->writeFloat(lightStartBrightness/MaxLightRadius, 8); + stream->writeFloat(lightEndBrightness/MaxLightRadius, 8); + stream->write(lightNormalOffset); +} + +void ExplosionData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + dtsFileName = stream->readSTString(); + + if (stream->readFlag()) + soundProfileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + soundProfileId = 0; + + if (stream->readFlag()) + particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + particleEmitterId = 0; + + particleDensity = stream->readInt(14); + stream->read(&particleRadius); + faceViewer = stream->readFlag(); + if(stream->readFlag()) + { + explosionScale.x = stream->readInt(16) / 100.0f; + explosionScale.y = stream->readInt(16) / 100.0f; + explosionScale.z = stream->readInt(16) / 100.0f; + } + else + explosionScale.set(1,1,1); + playSpeed = stream->readInt(14) / 20.0f; + debrisThetaMin = stream->readRangedU32(0, 180); + debrisThetaMax = stream->readRangedU32(0, 180); + debrisPhiMin = stream->readRangedU32(0, 360); + debrisPhiMax = stream->readRangedU32(0, 360); + debrisNum = stream->readRangedU32(0, 1000); + debrisNumVariance = stream->readRangedU32(0, 1000); + + debrisVelocity = stream->readInt(14) / 10.0f; + debrisVelocityVariance = stream->readRangedU32(0, 10000) / 10.0f; + delayMS = stream->readInt(16) << 5; + delayVariance = stream->readInt(16) << 5; + lifetimeMS = stream->readInt(16) << 5; + lifetimeVariance = stream->readInt(16) << 5; + + stream->read(&offset); + + shakeCamera = stream->readFlag(); + stream->read(&camShakeFreq.x); + stream->read(&camShakeFreq.y); + stream->read(&camShakeFreq.z); + stream->read(&camShakeAmp.x); + stream->read(&camShakeAmp.y); + stream->read(&camShakeAmp.z); + stream->read(&camShakeDuration); + stream->read(&camShakeRadius); + stream->read(&camShakeFalloff); + + + for( S32 j=0; jreadFlag() ) + { + debrisIDList[j] = (S32) stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + U32 i; + for( i=0; ireadFlag() ) + { + emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for( S32 k=0; kreadFlag() ) + { + explosionIDList[k] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + U32 count = stream->readRangedU32(0, EC_NUM_TIME_KEYS); + + for( i=0; ireadFloat(8); + + for( i=0; ireadRangedU32(0, 16000) / 100.0f; + sizes[i].y = stream->readRangedU32(0, 16000) / 100.0f; + sizes[i].z = stream->readRangedU32(0, 16000) / 100.0f; + } + + // + lightStartRadius = stream->readFloat(8) * MaxLightRadius; + lightEndRadius = stream->readFloat(8) * MaxLightRadius; + lightStartColor.red = stream->readFloat(7); + lightStartColor.green = stream->readFloat(7); + lightStartColor.blue = stream->readFloat(7); + lightEndColor.red = stream->readFloat(7); + lightEndColor.green = stream->readFloat(7); + lightEndColor.blue = stream->readFloat(7); + lightStartBrightness = stream->readFloat(8) * MaxLightRadius; + lightEndBrightness = stream->readFloat(8) * MaxLightRadius; + stream->read( &lightNormalOffset ); +} + +bool ExplosionData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + if (dtsFileName && dtsFileName[0]) { + explosionShape = ResourceManager::get().load(dtsFileName); + if (!bool(explosionShape)) { + errorStr = String::ToString("ExplosionData: Couldn't load shape \"%s\"", dtsFileName); + return false; + } + + // Resolve animations + explosionAnimation = explosionShape->findSequence("ambient"); + + // Preload textures with a dummy instance... + TSShapeInstance* pDummy = new TSShapeInstance(explosionShape, !server); + delete pDummy; + + } else { + explosionShape = NULL; + explosionAnimation = -1; + } + + return true; +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +Explosion::Explosion() +{ + mTypeMask |= ExplosionObjectType | LightObjectType; + + mExplosionInstance = NULL; + mExplosionThread = NULL; + + dMemset( mEmitterList, 0, sizeof( mEmitterList ) ); + mMainEmitter = NULL; + + mFade = 1; + mDelayMS = 0; + mCurrMS = 0; + mEndingMS = 1000; + mActive = false; + mCollideType = 0; + + mInitialNormal.set( 0.0f, 0.0f, 1.0f ); + mRandAngle = sgRandom.randF( 0.0f, 1.0f ) * M_PI_F * 2.0f; + mLight = gClientSceneGraph->getLightManager()->createLightInfo(); +} + +Explosion::~Explosion() +{ + if( mExplosionInstance ) + { + delete mExplosionInstance; + mExplosionInstance = NULL; + mExplosionThread = NULL; + } + + SAFE_DELETE(mLight); +} + + +void Explosion::setInitialState(const Point3F& point, const Point3F& normal, const F32 fade) +{ + setPosition(point); + mInitialNormal = normal; + mFade = fade; +} + +//-------------------------------------------------------------------------- +void Explosion::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + +//-------------------------------------------------------------------------- +bool Explosion::onAdd() +{ + // first check if we have a server connection, if we dont then this is on the server + // and we should exit, then check if the parent fails to add the object + GameConnection* conn = GameConnection::getConnectionToServer(); + if(!conn || !Parent::onAdd()) + return false; + + mDelayMS = mDataBlock->delayMS + sgRandom.randI( -mDataBlock->delayVariance, mDataBlock->delayVariance ); + mEndingMS = mDataBlock->lifetimeMS + sgRandom.randI( -mDataBlock->lifetimeVariance, mDataBlock->lifetimeVariance ); + + if( mFabs( mDataBlock->offset ) > 0.001f ) + { + MatrixF axisOrient = MathUtils::createOrientFromDir( mInitialNormal ); + + MatrixF trans = getTransform(); + Point3F randVec; + randVec.x = sgRandom.randF( -1.0f, 1.0f ); + randVec.y = sgRandom.randF( 0.0f, 1.0f ); + randVec.z = sgRandom.randF( -1.0f, 1.0f ); + randVec.normalize(); + randVec *= mDataBlock->offset; + axisOrient.mulV( randVec ); + trans.setPosition( trans.getPosition() + randVec ); + setTransform( trans ); + } + + // shake camera + if( mDataBlock->shakeCamera ) + { + // first check if explosion is near player + GameConnection* connection = GameConnection::getConnectionToServer(); + ShapeBase *obj = dynamic_cast(connection->getControlObject()); + + bool applyShake = true; + + if( obj ) + { + ShapeBase* cObj = obj; + while((cObj = cObj->getControlObject()) != 0) + { + if(cObj->useObjsEyePoint()) + { + applyShake = false; + break; + } + } + } + + + if( applyShake && obj ) + { + VectorF diff = obj->getPosition() - getPosition(); + F32 dist = diff.len(); + if( dist < mDataBlock->camShakeRadius ) + { + CameraShake *camShake = new CameraShake; + camShake->setDuration( mDataBlock->camShakeDuration ); + camShake->setFrequency( mDataBlock->camShakeFreq ); + + F32 falloff = dist / mDataBlock->camShakeRadius; + falloff = 1.0f + falloff * 10.0f; + falloff = 1.0f / (falloff * falloff); + + VectorF shakeAmp = mDataBlock->camShakeAmp * falloff; + camShake->setAmplitude( shakeAmp ); + camShake->setFalloff( mDataBlock->camShakeFalloff ); + camShake->init(); + gCamFXMgr.addFX( camShake ); + } + } + } + + + if( mDelayMS == 0 ) + { + if( !explode() ) + { + return false; + } + } + + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + removeFromProcessList(); + gClientProcessList.addObject(this); + + mRandomVal = sgRandom.randF(); + + NetConnection* pNC = NetConnection::getConnectionToServer(); + AssertFatal(pNC != NULL, "Error, must have a connection to the server!"); + pNC->addObject(this); + + // Initialize the light structure and register as a dynamic light + if (mDataBlock->lightStartRadius != 0.0f || mDataBlock->lightEndRadius) + { + mLight->setType( LightInfo::Point ); + mLight->setRange( mDataBlock->lightStartRadius ); + mLight->setColor( mDataBlock->lightStartColor ); + } + + return true; +} + +void Explosion::onRemove() +{ + for( int i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + if( mMainEmitter ) + { + mMainEmitter->deleteWhenEmpty(); + mMainEmitter = NULL; + } + + if (mSceneManager != NULL) + mSceneManager->removeObjectFromScene(this); + if (getContainer() != NULL) + getContainer()->removeObject(this); + + Parent::onRemove(); +} + + +bool Explosion::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +bool Explosion::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if ( state->isObjectRendered( this ) ) + prepBatchRender( state ); + + return false; +} + +void Explosion::setCurrentScale() +{ + F32 t = F32(mCurrMS) / F32(mEndingMS); + + for( U32 i = 1; i < ExplosionData::EC_NUM_TIME_KEYS; i++ ) + { + if( mDataBlock->times[i] >= t ) + { + F32 firstPart = t - mDataBlock->times[i-1]; + F32 total = mDataBlock->times[i] - + mDataBlock->times[i-1]; + + firstPart /= total; + + mObjScale = (mDataBlock->sizes[i-1] * (1.0f - firstPart)) + + (mDataBlock->sizes[i] * firstPart); + + return; + } + } + +} + +//-------------------------------------------------------------------------- +// Make the explosion face the viewer (if desired) +//-------------------------------------------------------------------------- +void Explosion::prepModelView(SceneState* state) +{ + MatrixF rotMatrix( true ); + Point3F targetVector; + + if( mDataBlock->faceViewer ) + { + targetVector = getPosition() - state->getCameraPosition(); + targetVector.normalize(); + + // rotate explosion each time so it's a little different + rotMatrix.set( EulerF( 0.0f, mRandAngle, 0.0f ) ); + } + else + { + targetVector = mInitialNormal; + } + + MatrixF explOrient = MathUtils::createOrientFromDir( targetVector ); + explOrient.mul( rotMatrix ); + explOrient.setPosition( getPosition() ); + + setCurrentScale(); + explOrient.scale( mObjScale ); + GFX->setWorldMatrix( explOrient ); +} + +//-------------------------------------------------------------------------- +// Render object +//-------------------------------------------------------------------------- +void Explosion::prepBatchRender(SceneState* state) +{ + MatrixF proj = GFX->getProjectionMatrix(); + + RectI viewport = GFX->getViewport(); + + // Set up our TS render state here. + TSRenderState rdata; + rdata.setSceneState( state ); + + if( mExplosionInstance ) + { + // render mesh + GFX->pushWorldMatrix(); + + prepModelView( state ); + + mExplosionInstance->animate(); + mExplosionInstance->render( rdata ); + + GFX->popWorldMatrix(); + } + + + GFX->setProjectionMatrix( proj ); + GFX->setViewport( viewport ); + +} + +void Explosion::submitLights( LightManager *lm, bool staticLighting ) +{ + if ( staticLighting ) + return; + + // Update the light's info and add it to the scene, the light will + // only be visible for this current frame. + mLight->setPosition( getRenderTransform().getPosition() + mInitialNormal * mDataBlock->lightNormalOffset ); + F32 t = F32(mCurrMS) / F32(mEndingMS); + mLight->setRange( mDataBlock->lightStartRadius + + (mDataBlock->lightEndRadius - mDataBlock->lightStartRadius) * t ); + mLight->setColor( mDataBlock->lightStartColor + + (mDataBlock->lightEndColor - mDataBlock->lightStartColor) * t ); + mLight->setBrightness( mDataBlock->lightStartBrightness + + (mDataBlock->lightEndBrightness - mDataBlock->lightStartBrightness) * t ); + + lm->registerGlobalLight( mLight, this ); +} + + +//-------------------------------------------------------------------------- +void Explosion::processTick(const Move*) +{ + mCurrMS += TickMs; + + if( mCurrMS >= mEndingMS ) + { + deleteObject(); + return; + } + + if( (mCurrMS > mDelayMS) && !mActive ) + explode(); +} + +void Explosion::advanceTime(F32 dt) +{ + if (dt == 0.0f) + return; + + GameConnection* conn = GameConnection::getConnectionToServer(); + if(!conn) + return; + + updateEmitters( dt ); + + if( mExplosionInstance ) + mExplosionInstance->advanceTime(dt, mExplosionThread); +} + +//---------------------------------------------------------------------------- +// Update emitters +//---------------------------------------------------------------------------- +void Explosion::updateEmitters( F32 dt ) +{ + Point3F pos = getPosition(); + + for( int i=0; iemitParticles( pos, pos, mInitialNormal, Point3F( 0.0f, 0.0f, 0.0f ), (U32)(dt * 1000)); + } + } + +} + +//---------------------------------------------------------------------------- +// Launch Debris +//---------------------------------------------------------------------------- +void Explosion::launchDebris( Point3F &axis ) +{ + GameConnection* conn = GameConnection::getConnectionToServer(); + if(!conn) + return; + + bool hasDebris = false; + for( int j=0; jdebrisList[j] ) + { + hasDebris = true; + break; + } + } + if( !hasDebris ) + { + return; + } + + Point3F axisx; + if (mFabs(axis.z) < 0.999f) + mCross(axis, Point3F(0.0f, 0.0f, 1.0f), &axisx); + else + mCross(axis, Point3F(0.0f, 1.0f, 0.0f), &axisx); + axisx.normalize(); + + Point3F pos( 0.0f, 0.0f, 0.5f ); + pos += getPosition(); + + + U32 numDebris = mDataBlock->debrisNum + sgRandom.randI( -mDataBlock->debrisNumVariance, mDataBlock->debrisNumVariance ); + + for( int i=0; idebrisThetaMin, mDataBlock->debrisThetaMax, + mDataBlock->debrisPhiMin, mDataBlock->debrisPhiMax ); + + F32 debrisVel = mDataBlock->debrisVelocity + mDataBlock->debrisVelocityVariance * sgRandom.randF( -1.0f, 1.0f ); + + launchDir *= debrisVel; + + Debris *debris = new Debris; + debris->setDataBlock( mDataBlock->debrisList[0] ); + debris->setTransform( getTransform() ); + debris->init( pos, launchDir ); + + if( !debris->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register debris for class: %s", mDataBlock->getName() ); + delete debris; + debris = NULL; + } + } +} + +//---------------------------------------------------------------------------- +// Spawn sub explosions +//---------------------------------------------------------------------------- +void Explosion::spawnSubExplosions() +{ + GameConnection* conn = GameConnection::getConnectionToServer(); + if(!conn) + return; + + for( S32 i=0; iexplosionList[i] ) + { + MatrixF trans = getTransform(); + Explosion* pExplosion = new Explosion; + pExplosion->setDataBlock( mDataBlock->explosionList[i] ); + pExplosion->setTransform( trans ); + pExplosion->setInitialState( trans.getPosition(), mInitialNormal, 1); + if (!pExplosion->registerObject()) + delete pExplosion; + } + } +} + +//---------------------------------------------------------------------------- +// Explode +//---------------------------------------------------------------------------- +bool Explosion::explode() +{ + mActive = true; + + GameConnection* conn = GameConnection::getConnectionToServer(); + if(!conn) + return false; + + launchDebris( mInitialNormal ); + spawnSubExplosions(); + + if (bool(mDataBlock->explosionShape) && mDataBlock->explosionAnimation != -1) { + mExplosionInstance = new TSShapeInstance(mDataBlock->explosionShape, true); + + mExplosionThread = mExplosionInstance->addThread(); + mExplosionInstance->setSequence(mExplosionThread, mDataBlock->explosionAnimation, 0); + mExplosionInstance->setTimeScale(mExplosionThread, mDataBlock->playSpeed); + + mCurrMS = 0; + mEndingMS = U32(mExplosionInstance->getScaledDuration(mExplosionThread) * 1000.0f); + + mObjScale.convolve(mDataBlock->explosionScale); + mObjBox = mDataBlock->explosionShape->bounds; + resetWorldBox(); + } + + if (mDataBlock->soundProfile) + SFX->playOnce( mDataBlock->soundProfile, &getTransform() ); + + if (mDataBlock->particleEmitter) { + mMainEmitter = new ParticleEmitter; + mMainEmitter->setDataBlock(mDataBlock->particleEmitter); + mMainEmitter->registerObject(); + + mMainEmitter->emitParticles(getPosition(), mInitialNormal, mDataBlock->particleRadius, + Point3F(0.0f, 0.0f, 0.0f), U32(mDataBlock->particleDensity * mFade)); + } + + for( int i=0; iemitterList[i] != NULL ) + { + ParticleEmitter * pEmitter = new ParticleEmitter; + pEmitter->setDataBlock( mDataBlock->emitterList[i] ); + if( !pEmitter->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() ); + SAFE_DELETE(pEmitter); + } + mEmitterList[i] = pEmitter; + } + } + + return true; +} + diff --git a/T3D/fx/explosion.h b/T3D/fx/explosion.h new file mode 100644 index 0000000..16aa1af --- /dev/null +++ b/T3D/fx/explosion.h @@ -0,0 +1,184 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EXPLOSION_H_ +#define _EXPLOSION_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class TSThread; +class SFXProfile; +struct DebrisData; +class ShockwaveData; + +//-------------------------------------------------------------------------- +class ExplosionData : public GameBaseData { + public: + typedef GameBaseData Parent; + + enum ExplosionConsts + { + EC_NUM_DEBRIS_TYPES = 1, + EC_NUM_EMITTERS = 4, + EC_MAX_SUB_EXPLOSIONS = 5, + EC_NUM_TIME_KEYS = 4, + }; + + public: + StringTableEntry dtsFileName; + + bool faceViewer; + + S32 particleDensity; + F32 particleRadius; + + SFXProfile* soundProfile; + ParticleEmitterData* particleEmitter; + S32 soundProfileId; + S32 particleEmitterId; + + Point3F explosionScale; + F32 playSpeed; + + Resource explosionShape; + S32 explosionAnimation; + + ParticleEmitterData* emitterList[EC_NUM_EMITTERS]; + S32 emitterIDList[EC_NUM_EMITTERS]; + + ShockwaveData * shockwave; + S32 shockwaveID; + bool shockwaveOnTerrain; + + DebrisData * debrisList[EC_NUM_DEBRIS_TYPES]; + S32 debrisIDList[EC_NUM_DEBRIS_TYPES]; + + F32 debrisThetaMin; + F32 debrisThetaMax; + F32 debrisPhiMin; + F32 debrisPhiMax; + S32 debrisNum; + S32 debrisNumVariance; + F32 debrisVelocity; + F32 debrisVelocityVariance; + + // sub - explosions + ExplosionData* explosionList[EC_MAX_SUB_EXPLOSIONS]; + S32 explosionIDList[EC_MAX_SUB_EXPLOSIONS]; + + S32 delayMS; + S32 delayVariance; + S32 lifetimeMS; + S32 lifetimeVariance; + + F32 offset; + Point3F sizes[ EC_NUM_TIME_KEYS ]; + F32 times[ EC_NUM_TIME_KEYS ]; + + // camera shake data + bool shakeCamera; + VectorF camShakeFreq; + VectorF camShakeAmp; + F32 camShakeDuration; + F32 camShakeRadius; + F32 camShakeFalloff; + + // Dynamic Lighting. The light is smoothly + // interpolated from start to end time. + F32 lightStartRadius; + F32 lightEndRadius; + ColorF lightStartColor; + ColorF lightEndColor; + F32 lightStartBrightness; + F32 lightEndBrightness; + F32 lightNormalOffset; + + ExplosionData(); + DECLARE_CONOBJECT(ExplosionData); + bool onAdd(); + bool preload(bool server, String &errorStr); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; +DECLARE_CONSOLETYPE(ExplosionData) + + +//-------------------------------------------------------------------------- +class Explosion : public GameBase, public ISceneLight +{ + typedef GameBase Parent; + + private: + ExplosionData* mDataBlock; + + TSShapeInstance* mExplosionInstance; + TSThread* mExplosionThread; + + SimObjectPtr mEmitterList[ ExplosionData::EC_NUM_EMITTERS ]; + SimObjectPtr mMainEmitter; + + U32 mCurrMS; + U32 mEndingMS; + F32 mRandAngle; + LightInfo* mLight; + + protected: + Point3F mInitialNormal; + F32 mFade; + bool mActive; + S32 mDelayMS; + F32 mRandomVal; + U32 mCollideType; + + protected: + bool onAdd(); + void onRemove(); + bool explode(); + + void processTick(const Move *move); + void advanceTime(F32 dt); + void updateEmitters( F32 dt ); + void launchDebris( Point3F &axis ); + void spawnSubExplosions(); + void setCurrentScale(); + + // Rendering + protected: + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + void prepBatchRender(SceneState *state); + void prepModelView(SceneState*); + + public: + Explosion(); + ~Explosion(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + + // ISceneLight + virtual void submitLights( LightManager *lm, bool staticLighting ); + virtual LightInfo* getLight() { return mLight; } + + bool onNewDataBlock(GameBaseData* dptr); + void setCollideType( U32 cType ){ mCollideType = cType; } + + DECLARE_CONOBJECT(Explosion); + static void initPersistFields(); +}; + +#endif // _H_EXPLOSION + diff --git a/T3D/fx/fxFoliageReplicator.cpp b/T3D/fx/fxFoliageReplicator.cpp new file mode 100644 index 0000000..cfe238a --- /dev/null +++ b/T3D/fx/fxFoliageReplicator.cpp @@ -0,0 +1,1823 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Written by Melvyn May, Started on 4th August 2002. +// +// "My code is written for the Torque community, so do your worst with it, +// just don't rip-it-off and call it your own without even thanking me". +// +// - Melv. +// +// +// Conversion to TSE By Brian "bzztbomb" Richardson 9/2005 +// This was a neat piece of code! Thanks Melv! +// I've switched this to use one large indexed primitive buffer. All animation +// is then done in the vertex shader. This means we have a static vertex/primitive +// buffer that never changes! How spiff! Because of this, the culling code was +// changed to render out full quadtree nodes, we don't try to cull each individual +// node ourselves anymore. This means to get good performance, you probably need to do the +// following: +// 1. If it's a small area to cover, turn off culling completely. +// 2. You want to tune the parameters to make sure there are a lot of billboards within +// each quadrant. +// +// POTENTIAL TODO LIST: +// TODO: Clamp item alpha to fog alpha +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/fx/fxFoliageReplicator.h" + +#include "gfx/gfxDevice.h" +#include "gfx/primBuilder.h" // Used for debug / mission edit rendering +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "math/mRandom.h" +#include "math/mathIO.h" +#include "console/simBase.h" +#include "sceneGraph/sceneGraph.h" +#include "renderInstance/renderPassManager.h" +#include "sceneGraph/sceneState.h" +#include "sim/netConnection.h" +#include "materials/shaderData.h" + + +const U32 AlphaTexLen = 1024; + +GFXImplementVertexFormat( GFXVertexFoliage ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 1 ); +} + +//------------------------------------------------------------------------------ +// +// Put the function in /example/common/editor/ObjectBuilderGui.gui [around line 458] ... +// +// function ObjectBuilderGui::buildfxFoliageReplicator(%this) +// { +// %this.className = "fxFoliageReplicator"; +// %this.process(); +// } +// +//------------------------------------------------------------------------------ +// +// Put this in /example/common/editor/EditorGui.cs in [function Creator::init( %this )] +// +// %Environment_Item[8] = "fxFoliageReplicator"; <-- ADD THIS. +// +//------------------------------------------------------------------------------ +// +// Put this in /example/common/client/missionDownload.cs in [function clientCmdMissionStartPhase3(%seq,%missionName)] (line 65) +// after codeline 'onPhase2Complete();'. +// +// StartFoliageReplication(); +// +//------------------------------------------------------------------------------ +// +// Put this in /engine/console/simBase.h (around line 509) in +// +// namespace Sim +// { +// DeclareNamedSet(fxFoliageSet) <-- ADD THIS (Note no semi-colon). +// +//------------------------------------------------------------------------------ +// +// Put this in /engine/console/simBase.cc (around line 19) in +// +// ImplementNamedSet(fxFoliageSet) <-- ADD THIS (Note no semi-colon). +// +//------------------------------------------------------------------------------ +// +// Put this in /engine/console/simManager.cc [function void init()] (around line 269). +// +// namespace Sim +// { +// InstantiateNamedSet(fxFoliageSet); <-- ADD THIS (Including Semi-colon). +// +//------------------------------------------------------------------------------ +extern bool gEditingMission; + +//------------------------------------------------------------------------------ + +IMPLEMENT_CO_NETOBJECT_V1(fxFoliageReplicator); + + +//------------------------------------------------------------------------------ +// +// Trig Table Lookups. +// +//------------------------------------------------------------------------------ +const F32 PeriodLen = (F32) 2.0f * (F32) M_PI; +const F32 PeriodLenMinus = (F32) (2.0f * M_PI) - 0.01f; + +//------------------------------------------------------------------------------ +// +// Class: fxFoliageRenderList +// +//------------------------------------------------------------------------------ + +void fxFoliageRenderList::SetupClipPlanes(SceneState* state, const F32 FarClipPlane) +{ + const F32 nearPlane = state->getNearPlane(); + const F32 farPlane = FarClipPlane; + + const SceneState::ZoneState &zoneState = state->getBaseZoneState(); + + mFrustum.set( false,//zoneState.frustum.isOrtho(), + zoneState.frustum.getNearLeft(), + zoneState.frustum.getNearRight(), + zoneState.frustum.getNearTop(), + zoneState.frustum.getNearBottom(), + nearPlane, + farPlane, + state->getCameraTransform() ); + + + mBox = mFrustum.getBounds(); +} + +//------------------------------------------------------------------------------ + + +inline void fxFoliageRenderList::DrawQuadBox(const Box3F& QuadBox, const ColorF Colour) +{ + // Define our debug box. + static Point3F BoxPnts[] = { + Point3F(0,0,0), + Point3F(0,0,1), + Point3F(0,1,0), + Point3F(0,1,1), + Point3F(1,0,0), + Point3F(1,0,1), + Point3F(1,1,0), + Point3F(1,1,1) + }; + + static U32 BoxVerts[][4] = { + {0,2,3,1}, // -x + {7,6,4,5}, // +x + {0,1,5,4}, // -y + {3,2,6,7}, // +y + {0,4,6,2}, // -z + {3,7,5,1} // +z + }; + + // Project our Box Points. + Point3F ProjectionPoints[8]; + + for( U32 i=0; i<8; i++ ) + { + ProjectionPoints[i].set(BoxPnts[i].x ? QuadBox.maxExtents.x : QuadBox.minExtents.x, + BoxPnts[i].y ? QuadBox.maxExtents.y : QuadBox.minExtents.y, + BoxPnts[i].z ? (mHeightLerp * QuadBox.maxExtents.z) + (1-mHeightLerp) * QuadBox.minExtents.z : QuadBox.minExtents.z); + + } + + PrimBuild::color(Colour); + + // Draw the Box. + for(U32 x = 0; x < 6; x++) + { + // Draw a line-loop. + PrimBuild::begin(GFXLineStrip, 5); + + for(U32 y = 0; y < 4; y++) + { + PrimBuild::vertex3f(ProjectionPoints[BoxVerts[x][y]].x, + ProjectionPoints[BoxVerts[x][y]].y, + ProjectionPoints[BoxVerts[x][y]].z); + } + PrimBuild::vertex3f(ProjectionPoints[BoxVerts[x][0]].x, + ProjectionPoints[BoxVerts[x][0]].y, + ProjectionPoints[BoxVerts[x][0]].z); + PrimBuild::end(); + } +} + +//------------------------------------------------------------------------------ +bool fxFoliageRenderList::IsQuadrantVisible(const Box3F VisBox, const MatrixF& RenderTransform) +{ + // Can we trivially accept the visible box? + if ( mFrustum.intersects( VisBox ) ) + return true; + + // Not visible. + return false; +} + + + +//------------------------------------------------------------------------------ +// +// Class: fxFoliageCulledList +// +//------------------------------------------------------------------------------ +fxFoliageCulledList::fxFoliageCulledList(Box3F SearchBox, fxFoliageCulledList* InVec) +{ + // Find the Candidates. + FindCandidates(SearchBox, InVec); +} + +//------------------------------------------------------------------------------ + +void fxFoliageCulledList::FindCandidates(Box3F SearchBox, fxFoliageCulledList* InVec) +{ + // Search the Culled List. + for (U32 i = 0; i < InVec->GetListCount(); i++) + { + // Is this Box overlapping our search box? + if (SearchBox.isOverlapped(InVec->GetElement(i)->FoliageBox)) + { + // Yes, so add it to our culled list. + mCulledObjectSet.push_back(InVec->GetElement(i)); + } + } +} + + + +//------------------------------------------------------------------------------ +// +// Class: fxFoliageReplicator +// +//------------------------------------------------------------------------------ + +fxFoliageReplicator::fxFoliageReplicator() +{ + // Setup NetObject. + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + // Reset Client Replication Started. + mClientReplicationStarted = false; + + // Reset Foliage Count. + mCurrentFoliageCount = 0; + + // Reset Creation Area Angle Animation. + mCreationAreaAngle = 0; + + // Reset Last Render Time. + mLastRenderTime = 0; + + // Reset Foliage Nodes. + mPotentialFoliageNodes = 0; + // Reset Billboards Acquired. + mBillboardsAcquired = 0; + + // Reset Frame Serial ID. + mFrameSerialID = 0; + + mAlphaLookup = NULL; + + mDirty = true; + + mFoliageShaderProjectionSC = NULL; + mFoliageShaderWorldSC = NULL; + mFoliageShaderGlobalSwayPhaseSC = NULL; + mFoliageShaderSwayMagnitudeSideSC = NULL; + mFoliageShaderSwayMagnitudeFrontSC = NULL; + mFoliageShaderGlobalLightPhaseSC = NULL; + mFoliageShaderLuminanceMagnitudeSC = NULL; + mFoliageShaderLuminanceMidpointSC = NULL; + mFoliageShaderDistanceRangeSC = NULL; + mFoliageShaderCameraPosSC = NULL; + mFoliageShaderTrueBillboardSC = NULL; + mFoliageShaderGroundAlphaSC = NULL; + mFoliageShaderAmbientColorSC = NULL; + + mShaderData = NULL; +} + +//------------------------------------------------------------------------------ + +fxFoliageReplicator::~fxFoliageReplicator() +{ + if (mAlphaLookup) + delete mAlphaLookup; + + mPlacementSB = NULL; +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::initPersistFields() +{ + // Add out own persistent fields. + addGroup( "Debugging" ); // MM: Added Group Header. + addField( "UseDebugInfo", TypeBool, Offset( mFieldData.mUseDebugInfo, fxFoliageReplicator ), "Culling bins are drawn when set to true." ); + addField( "DebugBoxHeight", TypeF32, Offset( mFieldData.mDebugBoxHeight, fxFoliageReplicator ), "Height multiplier for drawn culling bins."); + addField( "HideFoliage", TypeBool, Offset( mFieldData.mHideFoliage, fxFoliageReplicator ), "Foliage is hidden when set to true." ); + addField( "ShowPlacementArea", TypeBool, Offset( mFieldData.mShowPlacementArea, fxFoliageReplicator ), "Draw placement rings when set to true." ); + addField( "PlacementAreaHeight", TypeS32, Offset( mFieldData.mPlacementBandHeight, fxFoliageReplicator ), "Height of the placement ring in world units." ); + addField( "PlacementColour", TypeColorF, Offset( mFieldData.mPlaceAreaColour, fxFoliageReplicator ), "Color of the placement ring." ); + endGroup( "Debugging" ); // MM: Added Group Footer. + + addGroup( "Media" ); // MM: Added Group Header. + addField( "Seed", TypeS32, Offset( mFieldData.mSeed, fxFoliageReplicator ), "Random seed for foliage placement." ); + addField( "FoliageFile", TypeFilename, Offset( mFieldData.mFoliageFile, fxFoliageReplicator ), "Image file for the foliage texture." ); + addField( "FoliageCount", TypeS32, Offset( mFieldData.mFoliageCount, fxFoliageReplicator ), "Maximum foliage instance count." ); + addField( "FoliageRetries", TypeS32, Offset( mFieldData.mFoliageRetries, fxFoliageReplicator ), "Number of times to try placing a foliage instance before giving up." ); + endGroup( "Media" ); // MM: Added Group Footer. + + addGroup( "Area" ); // MM: Added Group Header. + addField( "InnerRadiusX", TypeS32, Offset( mFieldData.mInnerRadiusX, fxFoliageReplicator ), "Placement area inner radius on the X axis" ); + addField( "InnerRadiusY", TypeS32, Offset( mFieldData.mInnerRadiusY, fxFoliageReplicator ), "Placement area inner radius on the Y axis" ); + addField( "OuterRadiusX", TypeS32, Offset( mFieldData.mOuterRadiusX, fxFoliageReplicator ), "Placement area outer radius on the X axis" ); + addField( "OuterRadiusY", TypeS32, Offset( mFieldData.mOuterRadiusY, fxFoliageReplicator ), "Placement area outer radius on the Y axis" ); + endGroup( "Area" ); // MM: Added Group Footer. + + addGroup( "Dimensions" ); // MM: Added Group Header. + addField( "MinWidth", TypeF32, Offset( mFieldData.mMinWidth, fxFoliageReplicator ), "Minimum width of foliage billboards" ); + addField( "MaxWidth", TypeF32, Offset( mFieldData.mMaxWidth, fxFoliageReplicator ), "Maximum width of foliage billboards" ); + addField( "MinHeight", TypeF32, Offset( mFieldData.mMinHeight, fxFoliageReplicator ), "Minimum height of foliage billboards" ); + addField( "MaxHeight", TypeF32, Offset( mFieldData.mMaxHeight, fxFoliageReplicator ), "Maximum height of foliage billboards" ); + addField( "FixAspectRatio", TypeBool, Offset( mFieldData.mFixAspectRatio, fxFoliageReplicator ), "Maintain aspect ratio of image if true. This option ignores MaxWidth." ); + addField( "FixSizeToMax", TypeBool, Offset( mFieldData.mFixSizeToMax, fxFoliageReplicator ), "Use only MaxWidth and MaxHeight for billboard size. Ignores MinWidth and MinHeight." ); + addField( "OffsetZ", TypeF32, Offset( mFieldData.mOffsetZ, fxFoliageReplicator ), "Offset billboards by this amount vertically." ); + addField( "RandomFlip", TypeBool, Offset( mFieldData.mRandomFlip, fxFoliageReplicator ), "Randomly flip billboards left-to-right." ); + addField( "UseTrueBillboards", TypeBool, Offset( mFieldData.mUseTrueBillboards, fxFoliageReplicator ), "Use camera facing billboards ( including the z axis )." ); + endGroup( "Dimensions" ); // MM: Added Group Footer. + + addGroup( "Culling" ); // MM: Added Group Header. + addField( "UseCulling", TypeBool, Offset( mFieldData.mUseCulling, fxFoliageReplicator ), "Use culling bins when enabled." ); + addField( "CullResolution", TypeS32, Offset( mFieldData.mCullResolution, fxFoliageReplicator ), "Minimum size of culling bins. Must be >= 8 and <= OuterRadius." ); + addField( "ViewDistance", TypeF32, Offset( mFieldData.mViewDistance, fxFoliageReplicator ), "Maximum distance from camera where foliage appears." ); + addField( "ViewClosest", TypeF32, Offset( mFieldData.mViewClosest, fxFoliageReplicator ), "Minimum distance from camera where foliage appears." ); + addField( "FadeInRegion", TypeF32, Offset( mFieldData.mFadeInRegion, fxFoliageReplicator ), "Region beyond ViewDistance where foliage fades in/out." ); + addField( "FadeOutRegion", TypeF32, Offset( mFieldData.mFadeOutRegion, fxFoliageReplicator ), "Region before ViewClosest where foliage fades in/out." ); + addField( "AlphaCutoff", TypeF32, Offset( mFieldData.mAlphaCutoff, fxFoliageReplicator ), "Minimum alpha value allowed on foliage instances." ); + addField( "GroundAlpha", TypeF32, Offset( mFieldData.mGroundAlpha, fxFoliageReplicator ), "Alpha of the foliage at ground level. 0 = transparent, 1 = opaque." ); + endGroup( "Culling" ); // MM: Added Group Footer. + + addGroup( "Animation" ); // MM: Added Group Header. + addField( "SwayOn", TypeBool, Offset( mFieldData.mSwayOn, fxFoliageReplicator ), "Foliage should sway randomly when true." ); + addField( "SwaySync", TypeBool, Offset( mFieldData.mSwaySync, fxFoliageReplicator ), "Foliage instances should sway together when true and SwayOn is enabled." ); + addField( "SwayMagSide", TypeF32, Offset( mFieldData.mSwayMagnitudeSide, fxFoliageReplicator ), "Left-to-right sway magnitude." ); + addField( "SwayMagFront", TypeF32, Offset( mFieldData.mSwayMagnitudeFront, fxFoliageReplicator ), "Front-to-back sway magnitude." ); + addField( "MinSwayTime", TypeF32, Offset( mFieldData.mMinSwayTime, fxFoliageReplicator ), "Minumum sway cycle time in seconds." ); + addField( "MaxSwayTime", TypeF32, Offset( mFieldData.mMaxSwayTime, fxFoliageReplicator ), "Maximum sway cycle time in seconds." ); + endGroup( "Animation" ); // MM: Added Group Footer. + + addGroup( "Lighting" ); // MM: Added Group Header. + addField( "LightOn", TypeBool, Offset( mFieldData.mLightOn, fxFoliageReplicator ), "Foliage should be illuminated with changing lights when true." ); + addField( "LightSync", TypeBool, Offset( mFieldData.mLightSync, fxFoliageReplicator ), "Foliage instances have the same lighting when set and LightOn is set." ); + addField( "MinLuminance", TypeF32, Offset( mFieldData.mMinLuminance, fxFoliageReplicator ), "Minimum luminance for foliage instances." ); + addField( "MaxLuminance", TypeF32, Offset( mFieldData.mMaxLuminance, fxFoliageReplicator ), "Maximum luminance for foliage instances." ); + addField( "LightTime", TypeF32, Offset( mFieldData.mLightTime, fxFoliageReplicator ), "Time before foliage illumination cycle repeats." ); + endGroup( "Lighting" ); // MM: Added Group Footer. + + addGroup( "Restrictions" ); // MM: Added Group Header. + addField( "AllowOnTerrain", TypeBool, Offset( mFieldData.mAllowOnTerrain, fxFoliageReplicator ), "Foliage will be placed on terrain when set." ); + addField( "AllowOnInteriors", TypeBool, Offset( mFieldData.mAllowOnInteriors, fxFoliageReplicator ), "Foliage will be placed on InteriorInstances when set." ); + addField( "AllowOnStatics", TypeBool, Offset( mFieldData.mAllowStatics, fxFoliageReplicator ), "Foliage will be placed on Static shapes when set." ); + addField( "AllowOnWater", TypeBool, Offset( mFieldData.mAllowOnWater, fxFoliageReplicator ), "Foliage will be placed on/under water when set." ); + addField( "AllowWaterSurface", TypeBool, Offset( mFieldData.mAllowWaterSurface, fxFoliageReplicator ), "Foliage will be placed on water when set. Requires AllowOnWater." ); + addField( "AllowedTerrainSlope", TypeS32, Offset( mFieldData.mAllowedTerrainSlope, fxFoliageReplicator ), "Maximum surface angle allowed for foliage instances." ); + endGroup( "Restrictions" ); // MM: Added Group Footer. + + // Initialise parents' persistent fields. + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::CreateFoliage(void) +{ + F32 HypX, HypY; + F32 Angle; + U32 RelocationRetry; + Point3F FoliagePosition; + Point3F FoliageStart; + Point3F FoliageEnd; + Point3F FoliageScale; + bool CollisionResult; + RayInfo RayEvent; + + // Let's get a minimum bounding volume. + Point3F MinPoint( -0.5, -0.5, -0.5 ); + Point3F MaxPoint( 0.5, 0.5, 0.5 ); + + // Check Host. + AssertFatal(isClientObject(), "Trying to create Foliage on Server, this is bad!") + + // Cannot continue without Foliage Texture! + if (dStrlen(mFieldData.mFoliageFile) == 0) + return; + + // Check that we can position somewhere! + if (!( mFieldData.mAllowOnTerrain || + mFieldData.mAllowOnInteriors || + mFieldData.mAllowStatics || + mFieldData.mAllowOnWater)) + { + // Problem ... + Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Could not place Foliage, All alloweds are off!"); + + // Return here. + return; + } + + // Destroy Foliage if we've already got some. + if (mCurrentFoliageCount != 0) DestroyFoliage(); + + // Inform the user if culling has been disabled! + if (!mFieldData.mUseCulling) + { + // Console Output. + Con::printf("fxFoliageReplicator - Culling has been disabled!"); + } + + // ---------------------------------------------------------------------------------------------------------------------- + // > Calculate the Potential Foliage Nodes Required to achieve the selected culling resolution. + // > Populate Quad-tree structure to depth determined by culling resolution. + // + // A little explanation is called for here ... + // + // The approach to this problem has been choosen to make it *much* easier for + // the user to control the quad-tree culling resolution. The user enters a single + // world-space value 'mCullResolution' which controls the highest resolution at + // which the replicator will check visibility culling. + // + // example: If 'mCullResolution' is 32 and the size of the replicated area is 128 radius + // (256 diameter) then this results in the replicator creating a quad-tree where + // there are 256/32 = 8x8 blocks. Each of these can be checked to see if they + // reside within the viewing frustum and if not then they get culled therefore + // removing the need to parse all the billboards that occcupy that region. + // Most of the time you will get better than this as the culling algorithm will + // check the culling pyramid from the top to bottom e.g. the follow 'blocks' + // will be checked:- + // + // 1 x 256 x 256 (All of replicated area) + // 4 x 128 x 128 (4 corners of above) + // 16 x 64 x 64 (16 x 4 corners of above) + // etc. + // + // + // 1. First-up, the replicator needs to create a fixed-list of quad-tree nodes to work with. + // + // To calculate this we take the largest outer-radius value set in the replicator and + // calculate how many quad-tree levels are required to achieve the selected 'mCullResolution'. + // One of the initial problems is that the replicator has seperate radii values for X & Y. + // This can lead to a culling resolution smaller in one axis than the other if there is a + // difference between the Outer-Radii. Unfortunately, we just live with this as there is + // not much we can do here if we still want to allow the user to have this kind of + // elliptical placement control. + // + // To calculate the number of nodes needed we using the following equation:- + // + // Note:- We are changing the Logarithmic bases from 10 -> 2 ... grrrr! + // + // Cr = mCullResolution + // Rs = Maximum Radii Diameter + // + // + // ( Log10( Rs / Cr ) ) + // int ( ---------------- + 0.5 ) + // ( Log10( 2 ) ) + // + // ---------| + // | + // | n + // / 4 + // / + // ---------| + // n = 0 + // + // + // So basically we calculate the number of blocks in 1D at the highest resolution, then + // calculate the inverse exponential (base 2 - 1D) to achieve that quantity of blocks. + // We round that upto the next highest integer = e. We then sum 4 to the power 0->e + // which gives us the correct number of nodes required. e is also stored as the starting + // level value for populating the quad-tree (see 3. below). + // + // 2. We then proceed to calculate the billboard positions as normal and calculate and assign + // each billboard a basic volume (rather than treat each as a point). We need to take into + // account possible front/back swaying as well as the basic plane dimensions here. + // When all the billboards have been choosen we then proceed to populate the quad-tree. + // + // 3. To populate the quad-tree we start with a box which completely encapsulates the volume + // occupied by all the billboards and enter into a recursive procedure to process that node. + // Processing this node involves splitting it into quadrants in X/Y untouched (for now). + // We then find candidate billboards with each of these quadrants searching using the + // current subset of shapes from the parent (this reduces the searching to a minimum and + // is very efficient). + // + // If a quadrant does not enclose any billboards then the node is dropped otherwise it + // is processed again using the same procedure. + // + // This happens until we have recursed through the maximum number of levels as calculated + // using the summation max (see equation above). When level 0 is reached, the current list + // of enclosed objects is stored within the node (for the rendering algorithm). + // + // 4. When this is complete we have finished here. The next stage is when rendering takes place. + // An algorithm steps through the quad-tree from the top and does visibility culling on + // each box (with respect to the viewing frustum) and culls as appropriate. If the box is + // visible then the next level is checked until we reach level 0 where the node contains + // a complete subset of billboards enclosed by the visible box. + // + // + // Using the above algorithm we can now generate *massive* quantities of billboards and (using the + // appropriate 'mCullResolution') only visible blocks of billboards will be processed. + // + // - Melv. + // + // ---------------------------------------------------------------------------------------------------------------------- + + + + // ---------------------------------------------------------------------------------------------------------------------- + // Step 1. + // ---------------------------------------------------------------------------------------------------------------------- + + // Calculate the maximum dimension. + F32 MaxDimension = 2.0f * ( (mFieldData.mOuterRadiusX > mFieldData.mOuterRadiusY) ? mFieldData.mOuterRadiusX : mFieldData.mOuterRadiusY ); + + // Let's check that our cull resolution is not greater than half our maximum dimension (and less than 1). + if (mFieldData.mCullResolution > (MaxDimension/2) || mFieldData.mCullResolution < 8) + { + // Problem ... + Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Could create Foliage, invalid Culling Resolution!"); + Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Culling Resolution *must* be >=8 or <= %0.2f!", (MaxDimension/2)); + + // Return here. + return; + } + + // Take first Timestamp. + F32 mStartCreationTime = (F32) Platform::getRealMilliseconds(); + + // Calculate the quad-tree levels needed for selected 'mCullResolution'. + mQuadTreeLevels = (U32)(mCeil(mLog( MaxDimension / mFieldData.mCullResolution ) / mLog( 2.0f ))); + + // Calculate the number of potential nodes required. + mPotentialFoliageNodes = 0; + for (U32 n = 0; n <= mQuadTreeLevels; n++) + mPotentialFoliageNodes += (U32)(mCeil(mPow(4.0f, (F32) n))); // Ceil to be safe! + + // ---------------------------------------------------------------------------------------------------------------------- + // Step 2. + // ---------------------------------------------------------------------------------------------------------------------- + + // Set Seed. + RandomGen.setSeed(mFieldData.mSeed); + + // Add Foliage. + for (U32 idx = 0; idx < mFieldData.mFoliageCount; idx++) + { + fxFoliageItem* pFoliageItem; + Point3F FoliageOffsetPos; + + // Reset Relocation Retry. + RelocationRetry = mFieldData.mFoliageRetries; + + // Find it a home ... + do + { + // Get the fxFoliageReplicator Position. + FoliagePosition = getPosition(); + + // Calculate a random offset + HypX = RandomGen.randF((F32) mFieldData.mInnerRadiusX < mFieldData.mOuterRadiusX ? mFieldData.mInnerRadiusX : mFieldData.mOuterRadiusX, (F32) mFieldData.mOuterRadiusX); + HypY = RandomGen.randF((F32) mFieldData.mInnerRadiusY < mFieldData.mOuterRadiusY ? mFieldData.mInnerRadiusY : mFieldData.mOuterRadiusY, (F32) mFieldData.mOuterRadiusY); + Angle = RandomGen.randF(0, (F32) M_2PI); + + // Calcualte the new position. + FoliagePosition.x += HypX * mCos(Angle); + FoliagePosition.y += HypY * mSin(Angle); + + // Initialise RayCast Search Start/End Positions. + FoliageStart = FoliageEnd = FoliagePosition; + FoliageStart.z = 2000.f; + FoliageEnd.z= -2000.f; + + // Perform Ray Cast Collision on Client. + CollisionResult = gClientContainer.castRay( FoliageStart, FoliageEnd, FXFOLIAGEREPLICATOR_COLLISION_MASK, &RayEvent); + + // Did we hit anything? + if (CollisionResult) + { + // For now, let's pretend we didn't get a collision. + CollisionResult = false; + + // Yes, so get it's type. + U32 CollisionType = RayEvent.object->getTypeMask(); + + // Check Illegal Placements, fail if we hit a disallowed type. + if (((CollisionType & TerrainObjectType) && !mFieldData.mAllowOnTerrain) || + ((CollisionType & InteriorObjectType) && !mFieldData.mAllowOnInteriors) || + ((CollisionType & ( StaticTSObjectType | StaticShapeObjectType )) && !mFieldData.mAllowStatics) || + ((CollisionType & WaterObjectType) && !mFieldData.mAllowOnWater) ) continue; + + // If we collided with water and are not allowing on the water surface then let's find the + // terrain underneath and pass this on as the original collision else fail. + if ((CollisionType & WaterObjectType) && !mFieldData.mAllowWaterSurface && + !gClientContainer.castRay( FoliageStart, FoliageEnd, FXFOLIAGEREPLICATOR_NOWATER_COLLISION_MASK, &RayEvent)) continue; + + // We passed with flying colour so carry on. + CollisionResult = true; + } + + // Invalidate if we are below Allowed Terrain Angle. + if (RayEvent.normal.z < mSin(mDegToRad(90.0f-mFieldData.mAllowedTerrainSlope))) CollisionResult = false; + + // Wait until we get a collision. + } while(!CollisionResult && --RelocationRetry); + + // Check for Relocation Problem. + if (RelocationRetry > 0) + { + // Adjust Impact point. + RayEvent.point.z += mFieldData.mOffsetZ; + + // Set New Position. + FoliagePosition = RayEvent.point; + } + else + { + // Warning. + Con::warnf(ConsoleLogEntry::General, "fxFoliageReplicator - Could not find satisfactory position for Foliage!"); + + // Skip to next. + continue; + } + + // Monitor the total volume. + FoliageOffsetPos = FoliagePosition - getPosition(); + MinPoint.setMin(FoliageOffsetPos); + MaxPoint.setMax(FoliageOffsetPos); + + // Create our Foliage Item. + pFoliageItem = new fxFoliageItem; + + // Reset Frame Serial. + pFoliageItem->LastFrameSerialID = 0; + + // Reset Transform. + pFoliageItem->Transform.identity(); + + // Set Position. + pFoliageItem->Transform.setColumn(3, FoliagePosition); + + // Are we fixing size @ max? + if (mFieldData.mFixSizeToMax) + { + // Yes, so set height maximum height. + pFoliageItem->Height = mFieldData.mMaxHeight; + // Is the Aspect Ratio Fixed? + if (mFieldData.mFixAspectRatio) + // Yes, so lock to height. + pFoliageItem->Width = pFoliageItem->Height; + else + // No, so set width to maximum width. + pFoliageItem->Width = mFieldData.mMaxWidth; + } + else + { + // No, so choose a new Scale. + pFoliageItem->Height = RandomGen.randF(mFieldData.mMinHeight, mFieldData.mMaxHeight); + // Is the Aspect Ratio Fixed? + if (mFieldData.mFixAspectRatio) + // Yes, so lock to height. + pFoliageItem->Width = pFoliageItem->Height; + else + // No, so choose a random width. + pFoliageItem->Width = RandomGen.randF(mFieldData.mMinWidth, mFieldData.mMaxWidth); + } + + // Are we randomly flipping horizontally? + if (mFieldData.mRandomFlip) + // Yes, so choose a random flip for this object. + pFoliageItem->Flipped = (RandomGen.randF(0, 1000) < 500.0f) ? false : true; + else + // No, so turn-off flipping. + pFoliageItem->Flipped = false; + + // Calculate Foliage Item World Box. + // NOTE:- We generate a psuedo-volume here. It's basically the volume to which the + // plane can move and this includes swaying! + // + // Is Sway On? + if (mFieldData.mSwayOn) + { + // Yes, so take swaying into account... + pFoliageItem->FoliageBox.minExtents = FoliagePosition + + Point3F(-pFoliageItem->Width / 2.0f - mFieldData.mSwayMagnitudeSide, + -0.5f - mFieldData.mSwayMagnitudeFront, + pFoliageItem->Height ); + + pFoliageItem->FoliageBox.maxExtents = FoliagePosition + + Point3F(+pFoliageItem->Width / 2.0f + mFieldData.mSwayMagnitudeSide, + +0.5f + mFieldData.mSwayMagnitudeFront, + pFoliageItem->Height ); + } + else + { + // No, so give it a minimum volume... + pFoliageItem->FoliageBox.minExtents = FoliagePosition + + Point3F(-pFoliageItem->Width / 2.0f, + -0.5f, + pFoliageItem->Height ); + + pFoliageItem->FoliageBox.maxExtents = FoliagePosition + + Point3F(+pFoliageItem->Width / 2.0f, + +0.5f, + pFoliageItem->Height ); + } + + // Store Shape in Replicated Shapes Vector. + mReplicatedFoliage.push_back(pFoliageItem); + + // Increase Foliage Count. + mCurrentFoliageCount++; + } + + // Is Lighting On? + if (mFieldData.mLightOn) + { + // Yes, so reset Global Light phase. + mGlobalLightPhase = 0.0f; + // Set Global Light Time Ratio. + mGlobalLightTimeRatio = PeriodLenMinus / mFieldData.mLightTime; + + // Yes, so step through Foliage. + for (U32 idx = 0; idx < mCurrentFoliageCount; idx++) + { + fxFoliageItem* pFoliageItem; + + // Fetch the Foliage Item. + pFoliageItem = mReplicatedFoliage[idx]; + + // Do we have an item? + if (pFoliageItem) + { + // Yes, so are lights syncronised? + if (mFieldData.mLightSync) + { + pFoliageItem->LightTimeRatio = 1.0f; + pFoliageItem->LightPhase = 0.0f; + } + else + { + // No, so choose a random Light phase. + pFoliageItem->LightPhase = RandomGen.randF(0, PeriodLenMinus); + // Set Light Time Ratio. + pFoliageItem->LightTimeRatio = PeriodLenMinus / mFieldData.mLightTime; + } + } + } + + } + + // Is Swaying Enabled? + if (mFieldData.mSwayOn) + { + // Yes, so reset Global Sway phase. + mGlobalSwayPhase = 0.0f; + // Always set Global Sway Time Ratio. + mGlobalSwayTimeRatio = PeriodLenMinus / RandomGen.randF(mFieldData.mMinSwayTime, mFieldData.mMaxSwayTime); + + // Yes, so step through Foliage. + for (U32 idx = 0; idx < mCurrentFoliageCount; idx++) + { + fxFoliageItem* pFoliageItem; + + // Fetch the Foliage Item. + pFoliageItem = mReplicatedFoliage[idx]; + // Do we have an item? + if (pFoliageItem) + { + // Are we using Sway Sync? + if (mFieldData.mSwaySync) + { + pFoliageItem->SwayPhase = 0; + pFoliageItem->SwayTimeRatio = mGlobalSwayTimeRatio; + } + else + { + // No, so choose a random Sway phase. + pFoliageItem->SwayPhase = RandomGen.randF(0, PeriodLenMinus); + // Set to random Sway Time. + pFoliageItem->SwayTimeRatio = PeriodLenMinus / RandomGen.randF(mFieldData.mMinSwayTime, mFieldData.mMaxSwayTime); + } + } + } + } + + // Update our Object Volume. + mObjBox.minExtents.set(MinPoint); + mObjBox.maxExtents.set(MaxPoint); + setTransform(mObjToWorld); + + // ---------------------------------------------------------------------------------------------------------------------- + // Step 3. + // ---------------------------------------------------------------------------------------------------------------------- + + // Reset Next Allocated Node to Stack base. + mNextAllocatedNodeIdx = 0; + + // Allocate a new Node. + fxFoliageQuadrantNode* pNewNode = new fxFoliageQuadrantNode; + + // Store it in the Quad-tree. + mFoliageQuadTree.push_back(pNewNode); + + // Populate Initial Node. + // + // Set Start Level. + pNewNode->Level = mQuadTreeLevels; + // Calculate Total Foliage Area. + pNewNode->QuadrantBox = getWorldBox(); + // Reset Quadrant child nodes. + pNewNode->QuadrantChildNode[0] = + pNewNode->QuadrantChildNode[1] = + pNewNode->QuadrantChildNode[2] = + pNewNode->QuadrantChildNode[3] = NULL; + + // Create our initial cull list with *all* billboards into. + fxFoliageCulledList CullList; + CullList.mCulledObjectSet = mReplicatedFoliage; + + // Move to next node Index. + mNextAllocatedNodeIdx++; + + // Let's start this thing going by recursing it's children. + ProcessNodeChildren(pNewNode, &CullList); + + // Calculate Elapsed Time and take new Timestamp. + F32 ElapsedTime = (Platform::getRealMilliseconds() - mStartCreationTime) * 0.001f; + + // Console Output. + Con::printf("fxFoliageReplicator - Lev: %d PotNodes: %d Used: %d Objs: %d Time: %0.4fs.", + mQuadTreeLevels, + mPotentialFoliageNodes, + mNextAllocatedNodeIdx-1, + mBillboardsAcquired, + ElapsedTime); + + // Dump (*very*) approximate allocated memory. + F32 MemoryAllocated = (F32) ((mNextAllocatedNodeIdx-1) * sizeof(fxFoliageQuadrantNode)); + MemoryAllocated += mCurrentFoliageCount * sizeof(fxFoliageItem); + MemoryAllocated += mCurrentFoliageCount * sizeof(fxFoliageItem*); + Con::printf("fxFoliageReplicator - Approx. %0.2fMb allocated.", MemoryAllocated / 1048576.0f); + + // ---------------------------------------------------------------------------------------------------------------------- + + SetupBuffers(); + + // Take first Timestamp. + mLastRenderTime = Platform::getVirtualMilliseconds(); +} + +void fxFoliageReplicator::SetupShader() +{ + if ( !mShaderData ) + { + if ( !Sim::findObject( "fxFoliageReplicatorShader", mShaderData ) ) + { + Con::errorf( "fxFoliageReplicator::SetupShader - could not find ShaderData named fxFoliageReplicatorShader" ); + return; + } + } + + Vector macros; + if ( mFieldData.mUseTrueBillboards ) + macros.push_back( GFXShaderMacro( "TRUE_BILLBOARD" ) ); + + mShader = mShaderData->getShader( macros ); + + if ( !mShader ) + return; + + + mFoliageShaderConsts = mShader->allocConstBuffer(); + mShaderData->mapSamplerNames( mFoliageShaderConsts ); + + mFoliageShaderProjectionSC = mShader->getShaderConstHandle( "$projection" ); + mFoliageShaderWorldSC = mShader->getShaderConstHandle( "$world" ); + mFoliageShaderGlobalSwayPhaseSC = mShader->getShaderConstHandle( "$GlobalSwayPhase" ); + mFoliageShaderSwayMagnitudeSideSC = mShader->getShaderConstHandle( "$SwayMagnitudeSide" ); + mFoliageShaderSwayMagnitudeFrontSC = mShader->getShaderConstHandle( "$SwayMagnitudeFront" ); + mFoliageShaderGlobalLightPhaseSC = mShader->getShaderConstHandle( "$GlobalLightPhase" ); + mFoliageShaderLuminanceMagnitudeSC = mShader->getShaderConstHandle( "$LuminanceMagnitude" ); + mFoliageShaderLuminanceMidpointSC = mShader->getShaderConstHandle( "$LuminanceMidpoint" ); + mFoliageShaderDistanceRangeSC = mShader->getShaderConstHandle( "$DistanceRange" ); + mFoliageShaderCameraPosSC = mShader->getShaderConstHandle( "$CameraPos" ); + mFoliageShaderTrueBillboardSC = mShader->getShaderConstHandle( "$TrueBillboard" ); + mFoliageShaderGroundAlphaSC = mShader->getShaderConstHandle( "$groundAlpha" ); + mFoliageShaderAmbientColorSC = mShader->getShaderConstHandle( "$ambient" ); +} + +// Ok, what we do is let the older code setup the FoliageItem list and the QuadTree. +// Then we build the Vertex and Primitive buffers here. It would probably be +// slightly more memory efficient to build the buffers directly, but we +// want to sort the items within the buffer by the quadtreenodes +void fxFoliageReplicator::SetupBuffers() +{ + // Following two arrays are used to build the vertex and primitive buffers. + Point3F basePoints[8]; + basePoints[0] = Point3F(-0.5f, 0.0f, 1.0f); + basePoints[1] = Point3F(-0.5f, 0.0f, 0.0f); + basePoints[2] = Point3F(0.5f, 0.0f, 0.0f); + basePoints[3] = Point3F(0.5f, 0.0f, 1.0f); + + Point2F texCoords[4]; + texCoords[0] = Point2F(0.0, 0.0); + texCoords[1] = Point2F(0.0, 1.0); + texCoords[2] = Point2F(1.0, 1.0); + texCoords[3] = Point2F(1.0, 0.0); + + // Init our Primitive Buffer + U32 indexSize = mFieldData.mFoliageCount * 6; + U16* indices = new U16[indexSize]; + // Two triangles per particle + for (U16 i = 0; i < mFieldData.mFoliageCount; i++) { + U16* idx = &indices[i*6]; // hey, no offset math below, neat + U16 vertOffset = i*4; + idx[0] = vertOffset + 0; + idx[1] = vertOffset + 1; + idx[2] = vertOffset + 2; + idx[3] = vertOffset + 2; + idx[4] = vertOffset + 3; + idx[5] = vertOffset + 0; + } + // Init the prim buffer and copy our indexes over + U16 *ibIndices; + mPrimBuffer.set(GFX, indexSize, 0, GFXBufferTypeStatic); + mPrimBuffer.lock(&ibIndices); + dMemcpy(ibIndices, indices, indexSize * sizeof(U16)); + mPrimBuffer.unlock(); + delete[] indices; + + // Now, let's init the vertex buffer + U32 currPrimitiveStartIndex = 0; + mVertexBuffer.set(GFX, mFieldData.mFoliageCount * 4, GFXBufferTypeStatic); + mVertexBuffer.lock(); + U32 idx = 0; + for (S32 qtIdx = 0; qtIdx < mFoliageQuadTree.size(); qtIdx++) { + fxFoliageQuadrantNode* quadNode = mFoliageQuadTree[qtIdx]; + if (quadNode->Level == 0) { + quadNode->startIndex = currPrimitiveStartIndex; + quadNode->primitiveCount = 0; + // Ok, there should be data in here! + for (S32 i = 0; i < quadNode->RenderList.size(); i++) { + fxFoliageItem* pFoliageItem = quadNode->RenderList[i]; + if (pFoliageItem->LastFrameSerialID == 0) { + pFoliageItem->LastFrameSerialID++; + // Dump it into the vertex buffer + for (U32 vertIndex = 0; vertIndex < 4; vertIndex++) { + GFXVertexFoliage *vert = &mVertexBuffer[(idx*4) + vertIndex]; + // This is the position of the billboard. + vert->point = pFoliageItem->Transform.getPosition(); + // Normal contains the point of the billboard (except for the y component, see below) + vert->normal = basePoints[vertIndex]; + + vert->normal.x *= pFoliageItem->Width; + vert->normal.z *= pFoliageItem->Height; + // Handle texture coordinates + vert->texCoord = texCoords[vertIndex]; + if (pFoliageItem->Flipped) + vert->texCoord.x = 1.0f - vert->texCoord.x; + // Handle sway. Sway is stored in a texture coord. The x coordinate is the sway phase multiplier, + // the y coordinate determines if this vertex actually sways or not. + if ((vertIndex == 0) || (vertIndex == 3)) { + vert->texCoord2.set(pFoliageItem->SwayTimeRatio / mGlobalSwayTimeRatio, 1.0f); + } else { + vert->texCoord2.set(0.0f, 0.0f); + } + // Handle lighting, lighting happens at the same time as global so this is just an offset. + vert->normal.y = pFoliageItem->LightPhase; + } + idx++; + quadNode->primitiveCount += 2; + currPrimitiveStartIndex += 6; + } + } + } + } + mVertexBuffer.unlock(); + + DestroyFoliageItems(); +} + +//------------------------------------------------------------------------------ + +Box3F fxFoliageReplicator::FetchQuadrant(Box3F Box, U32 Quadrant) +{ + Box3F QuadrantBox; + + // Select Quadrant. + switch(Quadrant) + { + // UL. + case 0: + QuadrantBox.minExtents = Box.minExtents + Point3F(0, Box.len_y()/2, 0); + QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z()); + break; + + // UR. + case 1: + QuadrantBox.minExtents = Box.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, 0); + QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z()); + break; + + // LL. + case 2: + QuadrantBox.minExtents = Box.minExtents; + QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z()); + break; + + // LR. + case 3: + QuadrantBox.minExtents = Box.minExtents + Point3F(Box.len_x()/2, 0, 0); + QuadrantBox.maxExtents = QuadrantBox.minExtents + Point3F(Box.len_x()/2, Box.len_y()/2, Box.len_z()); + break; + + default: + return Box; + } + + return QuadrantBox; +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::ProcessNodeChildren(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList) +{ + // --------------------------------------------------------------- + // Split Node into Quadrants and Process each. + // --------------------------------------------------------------- + + // Process All Quadrants (UL/UR/LL/LR). + for (U32 q = 0; q < 4; q++) + ProcessQuadrant(pParentNode, pCullList, q); +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::ProcessQuadrant(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList, U32 Quadrant) +{ + // Fetch Quadrant Box. + const Box3F QuadrantBox = FetchQuadrant(pParentNode->QuadrantBox, Quadrant); + + // Create our new Cull List. + fxFoliageCulledList CullList(QuadrantBox, pCullList); + + // Did we get any objects? + if (CullList.GetListCount() > 0) + { + // Yes, so allocate a new Node. + fxFoliageQuadrantNode* pNewNode = new fxFoliageQuadrantNode; + + // Store it in the Quad-tree. + mFoliageQuadTree.push_back(pNewNode); + + // Move to next node Index. + mNextAllocatedNodeIdx++; + + // Populate Quadrant Node. + // + // Next Sub-level. + pNewNode->Level = pParentNode->Level - 1; + // Calculate Quadrant Box. + pNewNode->QuadrantBox = QuadrantBox; + // Reset Child Nodes. + pNewNode->QuadrantChildNode[0] = + pNewNode->QuadrantChildNode[1] = + pNewNode->QuadrantChildNode[2] = + pNewNode->QuadrantChildNode[3] = NULL; + + // Put a reference in parent. + pParentNode->QuadrantChildNode[Quadrant] = pNewNode; + + // If we're not at sub-level 0 then process this nodes children. + if (pNewNode->Level != 0) ProcessNodeChildren(pNewNode, &CullList); + // If we've reached sub-level 0 then store Cull List (for rendering). + if (pNewNode->Level == 0) + { + // Store the render list from our culled object set. + pNewNode->RenderList = CullList.mCulledObjectSet; + // Keep track of the total billboard acquired. + mBillboardsAcquired += CullList.GetListCount(); + } + } +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::SyncFoliageReplicators(void) +{ + // Check Host. + AssertFatal(isServerObject(), "We *MUST* be on server when Synchronising Foliage!") + + // Find the Replicator Set. + SimSet *fxFoliageSet = dynamic_cast(Sim::findObject("fxFoliageSet")); + + // Return if Error. + if (!fxFoliageSet) + { + // Console Warning. + Con::warnf("fxFoliageReplicator - Cannot locate the 'fxFoliageSet', this is bad!"); + // Return here. + return; + } + + // Parse Replication Object(s). + for (SimSetIterator itr(fxFoliageSet); *itr; ++itr) + { + // Fetch the Replicator Object. + fxFoliageReplicator* Replicator = static_cast(*itr); + // Set Foliage Replication Mask. + if (Replicator->isServerObject()) + { + Con::printf("fxFoliageReplicator - Restarting fxFoliageReplicator Object..."); + Replicator->setMaskBits(FoliageReplicationMask); + } + } + + // Info ... + Con::printf("fxFoliageReplicator - Client Foliage Sync has completed."); +} + + +//------------------------------------------------------------------------------ +// Lets chill our memory requirements out a little +void fxFoliageReplicator::DestroyFoliageItems() +{ + // Remove shapes. + for (S32 idx = 0; idx < mReplicatedFoliage.size(); idx++) + { + fxFoliageItem* pFoliageItem; + + // Fetch the Foliage Item. + pFoliageItem = mReplicatedFoliage[idx]; + + // Delete Shape. + if (pFoliageItem) delete pFoliageItem; + } + // Clear the Replicated Foliage Vector. + mReplicatedFoliage.clear(); + + // Clear out old references also + for (S32 qtIdx = 0; qtIdx < mFoliageQuadTree.size(); qtIdx++) { + fxFoliageQuadrantNode* quadNode = mFoliageQuadTree[qtIdx]; + if (quadNode->Level == 0) { + quadNode->RenderList.clear(); + } + } +} + +void fxFoliageReplicator::DestroyFoliage(void) +{ + // Check Host. + AssertFatal(isClientObject(), "Trying to destroy Foliage on Server, this is bad!") + + // Destroy Quad-tree. + mPotentialFoliageNodes = 0; + // Reset Billboards Acquired. + mBillboardsAcquired = 0; + + // Finish if we didn't create any shapes. + if (mCurrentFoliageCount == 0) return; + + DestroyFoliageItems(); + + // Let's remove the Quad-Tree allocations. + for ( Vector::iterator QuadNodeItr = mFoliageQuadTree.begin(); + QuadNodeItr != mFoliageQuadTree.end(); + QuadNodeItr++ ) + { + // Remove the node. + delete *QuadNodeItr; + } + + // Clear the Foliage Quad-Tree Vector. + mFoliageQuadTree.clear(); + + // Clear the Frustum Render Set Vector. + mFrustumRenderSet.mVisObjectSet.clear(); + + // Reset Foliage Count. + mCurrentFoliageCount = 0; +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::StartUp(void) +{ + // Flag, Client Replication Started. + mClientReplicationStarted = true; + + // Create foliage on Client. + if (isClientObject()) CreateFoliage(); +} + +//------------------------------------------------------------------------------ + +bool fxFoliageReplicator::onAdd() +{ + if(!Parent::onAdd()) return(false); + + // Add the Replicator to the Replicator Set. + dynamic_cast(Sim::findObject("fxFoliageSet"))->addObject(this); + + // Set Default Object Box. + mObjBox.minExtents.set( -0.5, -0.5, -0.5 ); + mObjBox.maxExtents.set( 0.5, 0.5, 0.5 ); + resetWorldBox(); + setRenderTransform(mObjToWorld); + + // Add to Scene. + addToScene(); + + // Are we on the client? + if ( isClientObject() ) + { + // Yes, so load foliage texture. + if( mFieldData.mFoliageFile != NULL && dStrlen(mFieldData.mFoliageFile) > 0 ) + mFieldData.mFoliageTexture = GFXTexHandle( mFieldData.mFoliageFile, &GFXDefaultStaticDiffuseProfile, avar("%s() - mFieldData.mFoliageTexture (line %d)", __FUNCTION__, __LINE__) ); + + if ((GFXTextureObject*) mFieldData.mFoliageTexture == NULL) + Con::printf("fxFoliageReplicator: %s is an invalid or missing foliage texture file.", mFieldData.mFoliageFile); + + mAlphaLookup = new GBitmap(AlphaTexLen, 1); + computeAlphaTex(); + + // Register for notification when GhostAlways objects are done loading + NetConnection::smGhostAlwaysDone.notify( this, &fxFoliageReplicator::onGhostAlwaysDone ); + + SetupShader(); + } + + // Return OK. + return(true); +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::onRemove() +{ + // Remove the Replicator from the Replicator Set. + dynamic_cast(Sim::findObject("fxFoliageSet"))->removeObject(this); + + NetConnection::smGhostAlwaysDone.remove( this, &fxFoliageReplicator::onGhostAlwaysDone ); + + // Remove from Scene. + removeFromScene(); + + // Are we on the Client? + if (isClientObject()) + { + // Yes, so destroy Foliage. + DestroyFoliage(); + + // Remove Texture. + mFieldData.mFoliageTexture = NULL; + + mShader = NULL; + } + + // Do Parent. + Parent::onRemove(); +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::onGhostAlwaysDone() +{ + if ( isClientObject() ) + CreateFoliage(); +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::inspectPostApply() +{ + // Set Parent. + Parent::inspectPostApply(); + + // Set Foliage Replication Mask (this object only). + setMaskBits(FoliageReplicationMask); + + mDirty = true; +} + +//------------------------------------------------------------------------------ + +ConsoleFunction(StartFoliageReplication, void, 1, 1, "StartFoliageReplication()") +{ + TORQUE_UNUSED(argv); TORQUE_UNUSED(argc); + // Find the Replicator Set. + SimSet *fxFoliageSet = dynamic_cast(Sim::findObject("fxFoliageSet")); + + // Return if Error. + if (!fxFoliageSet) + { + // Console Warning. + Con::warnf("fxFoliageReplicator - Cannot locate the 'fxFoliageSet', this is bad!"); + // Return here. + return; + } + + // Parse Replication Object(s). + U32 startupCount = 0; + for (SimSetIterator itr(fxFoliageSet); *itr; ++itr) + { + // Fetch the Replicator Object. + fxFoliageReplicator* Replicator = static_cast(*itr); + + // Start Client Objects Only. + if (Replicator->isClientObject()) + { + Replicator->StartUp(); + startupCount++; + } + } + + // Info ... + Con::printf("fxFoliageReplicator - replicated client foliage for %d objects", startupCount); +} + +//------------------------------------------------------------------------------ + +bool fxFoliageReplicator::prepRenderImage(SceneState* state, const U32 stateKey, const U32 /*startZone*/, + const bool /*modifyBaseZoneState*/) +{ + // Return if last state. + if (isLastState(state, stateKey)) return false; + // Set Last State. + setLastState(state, stateKey); + + // Is Object Rendered? + if ( !state->isShadowPass() && state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &fxFoliageReplicator::renderObject); + ri->type = RenderPassManager::RIT_Foliage; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +// +// RENDERING +// +void fxFoliageReplicator::computeAlphaTex() +{ + // Distances used in alpha + const F32 ClippedViewDistance = mFieldData.mViewDistance; + const F32 MaximumViewDistance = ClippedViewDistance + mFieldData.mFadeInRegion; + + // This is used for the alpha computation in the shader. + for (U32 i = 0; i < AlphaTexLen; i++) { + F32 Distance = ((float) i / (float) AlphaTexLen) * MaximumViewDistance; + F32 ItemAlpha = 1.0f; + // Are we fading out? + if (Distance < mFieldData.mViewClosest) + { + // Yes, so set fade-out. + ItemAlpha = 1.0f - ((mFieldData.mViewClosest - Distance) * mFadeOutGradient); + } + // No, so are we fading in? + else if (Distance > ClippedViewDistance) + { + // Yes, so set fade-in + ItemAlpha = 1.0f - ((Distance - ClippedViewDistance) * mFadeInGradient); + } + + // Set texture info + ColorI c((U8) (255.0f * ItemAlpha), 0, 0); + mAlphaLookup->setColor(i, 0, c); + } + mAlphaTexture.set(mAlphaLookup, &GFXDefaultStaticDiffuseProfile, false, String("fxFoliage Replicator Alpha Texture") ); +} + +// Renders a triangle stripped oval +void fxFoliageReplicator::renderArc(const F32 fRadiusX, const F32 fRadiusY) +{ + PrimBuild::begin(GFXTriangleStrip, 720); + for (U32 Angle = mCreationAreaAngle; Angle < (mCreationAreaAngle+360); Angle++) + { + F32 XPos, YPos; + + // Calculate Position. + XPos = fRadiusX * mCos(mDegToRad(-(F32)Angle)); + YPos = fRadiusY * mSin(mDegToRad(-(F32)Angle)); + + // Set Colour. + PrimBuild::color4f(mFieldData.mPlaceAreaColour.red, + mFieldData.mPlaceAreaColour.green, + mFieldData.mPlaceAreaColour.blue, + AREA_ANIMATION_ARC * (Angle-mCreationAreaAngle)); + + PrimBuild::vertex3f(XPos, YPos, -(F32)mFieldData.mPlacementBandHeight/2.0f); + PrimBuild::vertex3f(XPos, YPos, +(F32)mFieldData.mPlacementBandHeight/2.0f); + } + PrimBuild::end(); +} + +// This currently uses the primbuilder, could convert out, but why allocate the buffer if we +// never edit the misison? +void fxFoliageReplicator::renderPlacementArea(const F32 ElapsedTime) +{ + if (gEditingMission && mFieldData.mShowPlacementArea) + { + GFX->pushWorldMatrix(); + GFX->multWorld(getTransform()); + + if (!mPlacementSB) + { + GFXStateBlockDesc transparent; + transparent.setCullMode(GFXCullNone); + transparent.alphaTestEnable = true; + transparent.setZReadWrite(true); + transparent.zWriteEnable = false; + transparent.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + mPlacementSB = GFX->createStateBlock( transparent ); + } + + GFX->setStateBlock(mPlacementSB); + + // Do we need to draw the Outer Radius? + if (mFieldData.mOuterRadiusX || mFieldData.mOuterRadiusY) + renderArc((F32) mFieldData.mOuterRadiusX, (F32) mFieldData.mOuterRadiusY); + // Inner radius? + if (mFieldData.mInnerRadiusX || mFieldData.mInnerRadiusY) + renderArc((F32) mFieldData.mInnerRadiusX, (F32) mFieldData.mInnerRadiusY); + + GFX->popWorldMatrix(); + mCreationAreaAngle = (U32)(mCreationAreaAngle + (1000 * ElapsedTime)); + mCreationAreaAngle = mCreationAreaAngle % 360; + } +} + +void fxFoliageReplicator::renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + if ( !mShader ) + return; + + // If we're rendering and we haven't placed any foliage yet - do it. + if(!mClientReplicationStarted) + { + Con::warnf("fxFoliageReplicator::renderObject - tried to render a non replicated fxFoliageReplicator; replicating it now..."); + + StartUp(); + } + + // Calculate Elapsed Time and take new Timestamp. + S32 Time = Platform::getVirtualMilliseconds(); + F32 ElapsedTime = (Time - mLastRenderTime) * 0.001f; + mLastRenderTime = Time; + + renderPlacementArea(ElapsedTime); + + if (mCurrentFoliageCount > 0) { + + if ( mRenderSB.isNull() || mDirty) + { + mDirty = false; + + GFXStateBlockDesc desc; + + // Debug SB + desc.samplersDefined = true; + desc.samplers[0].textureColorOp = GFXTOPDisable; + desc.samplers[1].textureColorOp = GFXTOPDisable; + + mDebugSB = GFX->createStateBlock(desc); + + // Render SB + desc.samplers[0].textureColorOp = GFXTOPModulate; + desc.samplers[1].textureColorOp = GFXTOPModulate; + desc.samplers[1].addressModeU = GFXAddressClamp; + desc.samplers[1].addressModeV = GFXAddressClamp; + + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setAlphaTest(true, GFXCmpGreater, (U8) (255.0f * mFieldData.mAlphaCutoff)); + desc.setCullMode(GFXCullNone); + + mRenderSB = GFX->createStateBlock(desc); + } + + if (!mFieldData.mHideFoliage) { + // Animate Global Sway Phase (Modulus). + mGlobalSwayPhase = mGlobalSwayPhase + (mGlobalSwayTimeRatio * ElapsedTime); + + // Animate Global Light Phase (Modulus). + mGlobalLightPhase = mGlobalLightPhase + (mGlobalLightTimeRatio * ElapsedTime); + + // Compute other light parameters + const F32 LuminanceMidPoint = (mFieldData.mMinLuminance + mFieldData.mMaxLuminance) / 2.0f; + const F32 LuminanceMagnitude = mFieldData.mMaxLuminance - LuminanceMidPoint; + + // Distances used in alpha + const F32 ClippedViewDistance = mFieldData.mViewDistance; + const F32 MaximumViewDistance = ClippedViewDistance + mFieldData.mFadeInRegion; + + if (mFoliageShaderConsts.isValid()) + { + if (mFoliageShaderGlobalSwayPhaseSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderGlobalSwayPhaseSC, mGlobalSwayPhase); + + if (mFoliageShaderSwayMagnitudeSideSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderSwayMagnitudeSideSC, mFieldData.mSwayMagnitudeSide); + + if (mFoliageShaderSwayMagnitudeFrontSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderSwayMagnitudeFrontSC, mFieldData.mSwayMagnitudeFront); + + if (mFoliageShaderGlobalLightPhaseSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderGlobalLightPhaseSC, mGlobalLightPhase); + + if (mFoliageShaderLuminanceMagnitudeSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderLuminanceMagnitudeSC, LuminanceMagnitude); + + if (mFoliageShaderLuminanceMidpointSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderLuminanceMidpointSC, LuminanceMidPoint); + + // Set up our shader constants + // Projection matrix + MatrixF proj = GFX->getProjectionMatrix(); + //proj.transpose(); + + if (mFoliageShaderProjectionSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderProjectionSC, proj); + + // World transform matrix + MatrixF world = GFX->getWorldMatrix(); + //world.transpose(); + + if (mFoliageShaderWorldSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderWorldSC, world); + + Point3F camPos = state->getCameraPosition(); + + if (mFoliageShaderDistanceRangeSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderDistanceRangeSC, MaximumViewDistance); + + if (mFoliageShaderCameraPosSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderCameraPosSC, camPos); + + if (mFoliageShaderTrueBillboardSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderTrueBillboardSC, mFieldData.mUseTrueBillboards ? 1.0f : 0.0f ); + + if (mFoliageShaderGroundAlphaSC->isValid()) + mFoliageShaderConsts->set(mFoliageShaderGroundAlphaSC, Point4F(mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha)); + + if (mFoliageShaderAmbientColorSC->isValid()) + { + const ColorF &ambient = state->getLightManager()->getSpecialLight( LightManager::slSunLightType )->getAmbient(); + mFoliageShaderConsts->set(mFoliageShaderAmbientColorSC, ambient); + } + + GFX->setShaderConstBuffer(mFoliageShaderConsts); + + } + + // Blend ops + // Set up our texture and color ops. + + GFX->setStateBlock(mRenderSB); + GFX->setShader( mShader ); + + GFX->setTexture(0, mFieldData.mFoliageTexture); + // computeAlphaTex(); // Uncomment if we figure out how to clamp to fogAndHaze + GFX->setTexture(1, mAlphaTexture); + + // Setup our buffers + GFX->setVertexBuffer(mVertexBuffer); + GFX->setPrimitiveBuffer(mPrimBuffer); + + // If we use culling, we're going to send chunks of our buffers to the card + if (mFieldData.mUseCulling) + { + // Setup the Clip-Planes. + F32 FarClipPlane = getMin((F32)state->getFarPlane(), + mFieldData.mViewDistance + mFieldData.mFadeInRegion); + mFrustumRenderSet.SetupClipPlanes(state, FarClipPlane); + + renderQuad(mFoliageQuadTree[0], getRenderTransform(), false); + + // Multipass, don't want to interrupt the vb state + if (mFieldData.mUseDebugInfo) + { + // hey man, we're done, so it doesn't matter if we kill it to render the next part + GFX->setStateBlock(mDebugSB); + renderQuad(mFoliageQuadTree[0], getRenderTransform(), true); + } + } + else + { + // Draw the whole shebang! + GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, mVertexBuffer->mNumVerts, + 0, mPrimBuffer->mIndexCount / 3); + } + } + } +} + +void fxFoliageReplicator::renderQuad(fxFoliageQuadrantNode* quadNode, const MatrixF& RenderTransform, const bool UseDebug) +{ + if (quadNode != NULL) { + if (mFrustumRenderSet.IsQuadrantVisible(quadNode->QuadrantBox, RenderTransform)) + { + // Draw the Quad Box (Debug Only). + if (UseDebug) + mFrustumRenderSet.DrawQuadBox(quadNode->QuadrantBox, ColorF(0.0f, 1.0f, 0.1f, 1.0f)); + if (quadNode->Level != 0) { + for (U32 i = 0; i < 4; i++) + renderQuad(quadNode->QuadrantChildNode[i], RenderTransform, UseDebug); + } else { + if (!UseDebug) + if(quadNode->primitiveCount) + GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, mVertexBuffer->mNumVerts, + quadNode->startIndex, quadNode->primitiveCount); + } + } else { + // Use a different color to say "I think I'm not visible!" + if (UseDebug) + mFrustumRenderSet.DrawQuadBox(quadNode->QuadrantBox, ColorF(1.0f, 0.8f, 0.1f, 1.0f)); + } + } +} + +//------------------------------------------------------------------------------ +// NETWORK +//------------------------------------------------------------------------------ + +U32 fxFoliageReplicator::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + // Pack Parent. + U32 retMask = Parent::packUpdate(con, mask, stream); + + // Write Foliage Replication Flag. + if (stream->writeFlag(mask & FoliageReplicationMask)) + { + stream->writeAffineTransform(mObjToWorld); // Foliage Master-Object Position. + + stream->writeFlag(mFieldData.mUseDebugInfo); // Foliage Debug Information Flag. + stream->write(mFieldData.mDebugBoxHeight); // Foliage Debug Height. + stream->write(mFieldData.mSeed); // Foliage Seed. + stream->write(mFieldData.mFoliageCount); // Foliage Count. + stream->write(mFieldData.mFoliageRetries); // Foliage Retries. + stream->writeString(mFieldData.mFoliageFile); // Foliage File. + + stream->write(mFieldData.mInnerRadiusX); // Foliage Inner Radius X. + stream->write(mFieldData.mInnerRadiusY); // Foliage Inner Radius Y. + stream->write(mFieldData.mOuterRadiusX); // Foliage Outer Radius X. + stream->write(mFieldData.mOuterRadiusY); // Foliage Outer Radius Y. + + stream->write(mFieldData.mMinWidth); // Foliage Minimum Width. + stream->write(mFieldData.mMaxWidth); // Foliage Maximum Width. + stream->write(mFieldData.mMinHeight); // Foliage Minimum Height. + stream->write(mFieldData.mMaxHeight); // Foliage Maximum Height. + stream->write(mFieldData.mFixAspectRatio); // Foliage Fix Aspect Ratio. + stream->write(mFieldData.mFixSizeToMax); // Foliage Fix Size to Max. + stream->write(mFieldData.mOffsetZ); // Foliage Offset Z. + stream->writeFlag(mFieldData.mRandomFlip); // Foliage Random Flip. + stream->writeFlag(mFieldData.mUseTrueBillboards); // Foliage faces the camera (including z axis) + + stream->write(mFieldData.mUseCulling); // Foliage Use Culling. + stream->write(mFieldData.mCullResolution); // Foliage Cull Resolution. + stream->write(mFieldData.mViewDistance); // Foliage View Distance. + stream->write(mFieldData.mViewClosest); // Foliage View Closest. + stream->write(mFieldData.mFadeInRegion); // Foliage Fade-In Region. + stream->write(mFieldData.mFadeOutRegion); // Foliage Fade-Out Region. + stream->write(mFieldData.mAlphaCutoff); // Foliage Alpha Cutoff. + stream->write(mFieldData.mGroundAlpha); // Foliage Ground Alpha. + + stream->writeFlag(mFieldData.mSwayOn); // Foliage Sway On Flag. + stream->writeFlag(mFieldData.mSwaySync); // Foliage Sway Sync Flag. + stream->write(mFieldData.mSwayMagnitudeSide); // Foliage Sway Magnitude Side2Side. + stream->write(mFieldData.mSwayMagnitudeFront); // Foliage Sway Magnitude Front2Back. + stream->write(mFieldData.mMinSwayTime); // Foliage Minimum Sway Time. + stream->write(mFieldData.mMaxSwayTime); // Foliage Maximum way Time. + + stream->writeFlag(mFieldData.mLightOn); // Foliage Light On Flag. + stream->writeFlag(mFieldData.mLightSync); // Foliage Light Sync + stream->write(mFieldData.mMinLuminance); // Foliage Minimum Luminance. + stream->write(mFieldData.mMaxLuminance); // Foliage Maximum Luminance. + stream->write(mFieldData.mLightTime); // Foliage Light Time. + + stream->writeFlag(mFieldData.mAllowOnTerrain); // Allow on Terrain. + stream->writeFlag(mFieldData.mAllowOnInteriors); // Allow on Interiors. + stream->writeFlag(mFieldData.mAllowStatics); // Allow on Statics. + stream->writeFlag(mFieldData.mAllowOnWater); // Allow on Water. + stream->writeFlag(mFieldData.mAllowWaterSurface); // Allow on Water Surface. + stream->write(mFieldData.mAllowedTerrainSlope); // Foliage Offset Z. + + stream->writeFlag(mFieldData.mHideFoliage); // Hide Foliage. + stream->writeFlag(mFieldData.mShowPlacementArea); // Show Placement Area Flag. + stream->write(mFieldData.mPlacementBandHeight); // Placement Area Height. + stream->write(mFieldData.mPlaceAreaColour); // Placement Area Colour. + } + + // Were done ... + return(retMask); +} + +//------------------------------------------------------------------------------ + +void fxFoliageReplicator::unpackUpdate(NetConnection * con, BitStream * stream) +{ + // Unpack Parent. + Parent::unpackUpdate(con, stream); + + // Read Replication Details. + if(stream->readFlag()) + { + MatrixF ReplicatorObjectMatrix; + + stream->readAffineTransform(&ReplicatorObjectMatrix); // Foliage Master Object Position. + + mFieldData.mUseDebugInfo = stream->readFlag(); // Foliage Debug Information Flag. + stream->read(&mFieldData.mDebugBoxHeight); // Foliage Debug Height. + stream->read(&mFieldData.mSeed); // Foliage Seed. + stream->read(&mFieldData.mFoliageCount); // Foliage Count. + stream->read(&mFieldData.mFoliageRetries); // Foliage Retries. + mFieldData.mFoliageFile = stream->readSTString(); // Foliage File. + + stream->read(&mFieldData.mInnerRadiusX); // Foliage Inner Radius X. + stream->read(&mFieldData.mInnerRadiusY); // Foliage Inner Radius Y. + stream->read(&mFieldData.mOuterRadiusX); // Foliage Outer Radius X. + stream->read(&mFieldData.mOuterRadiusY); // Foliage Outer Radius Y. + + stream->read(&mFieldData.mMinWidth); // Foliage Minimum Width. + stream->read(&mFieldData.mMaxWidth); // Foliage Maximum Width. + stream->read(&mFieldData.mMinHeight); // Foliage Minimum Height. + stream->read(&mFieldData.mMaxHeight); // Foliage Maximum Height. + stream->read(&mFieldData.mFixAspectRatio); // Foliage Fix Aspect Ratio. + stream->read(&mFieldData.mFixSizeToMax); // Foliage Fix Size to Max. + stream->read(&mFieldData.mOffsetZ); // Foliage Offset Z. + mFieldData.mRandomFlip = stream->readFlag(); // Foliage Random Flip. + + bool wasTrueBB = mFieldData.mUseTrueBillboards; + mFieldData.mUseTrueBillboards = stream->readFlag(); // Foliage is camera facing (including z axis). + + stream->read(&mFieldData.mUseCulling); // Foliage Use Culling. + stream->read(&mFieldData.mCullResolution); // Foliage Cull Resolution. + stream->read(&mFieldData.mViewDistance); // Foliage View Distance. + stream->read(&mFieldData.mViewClosest); // Foliage View Closest. + stream->read(&mFieldData.mFadeInRegion); // Foliage Fade-In Region. + stream->read(&mFieldData.mFadeOutRegion); // Foliage Fade-Out Region. + stream->read(&mFieldData.mAlphaCutoff); // Foliage Alpha Cutoff. + stream->read(&mFieldData.mGroundAlpha); // Foliage Ground Alpha. + + mFieldData.mSwayOn = stream->readFlag(); // Foliage Sway On Flag. + mFieldData.mSwaySync = stream->readFlag(); // Foliage Sway Sync Flag. + stream->read(&mFieldData.mSwayMagnitudeSide); // Foliage Sway Magnitude Side2Side. + stream->read(&mFieldData.mSwayMagnitudeFront); // Foliage Sway Magnitude Front2Back. + stream->read(&mFieldData.mMinSwayTime); // Foliage Minimum Sway Time. + stream->read(&mFieldData.mMaxSwayTime); // Foliage Maximum way Time. + + mFieldData.mLightOn = stream->readFlag(); // Foliage Light On Flag. + mFieldData.mLightSync = stream->readFlag(); // Foliage Light Sync + stream->read(&mFieldData.mMinLuminance); // Foliage Minimum Luminance. + stream->read(&mFieldData.mMaxLuminance); // Foliage Maximum Luminance. + stream->read(&mFieldData.mLightTime); // Foliage Light Time. + + mFieldData.mAllowOnTerrain = stream->readFlag(); // Allow on Terrain. + mFieldData.mAllowOnInteriors = stream->readFlag(); // Allow on Interiors. + mFieldData.mAllowStatics = stream->readFlag(); // Allow on Statics. + mFieldData.mAllowOnWater = stream->readFlag(); // Allow on Water. + mFieldData.mAllowWaterSurface = stream->readFlag(); // Allow on Water Surface. + stream->read(&mFieldData.mAllowedTerrainSlope); // Allowed Terrain Slope. + + mFieldData.mHideFoliage = stream->readFlag(); // Hide Foliage. + mFieldData.mShowPlacementArea = stream->readFlag(); // Show Placement Area Flag. + stream->read(&mFieldData.mPlacementBandHeight); // Placement Area Height. + stream->read(&mFieldData.mPlaceAreaColour); + + // Calculate Fade-In/Out Gradients. + mFadeInGradient = 1.0f / mFieldData.mFadeInRegion; + mFadeOutGradient = 1.0f / mFieldData.mFadeOutRegion; + + // Set Transform. + setTransform(ReplicatorObjectMatrix); + + // Load Foliage Texture on the client. + if( mFieldData.mFoliageFile != NULL && dStrlen(mFieldData.mFoliageFile) > 0 ) + mFieldData.mFoliageTexture = GFXTexHandle( mFieldData.mFoliageFile, &GFXDefaultStaticDiffuseProfile, avar("%s() - mFieldData.mFoliageTexture (line %d)", __FUNCTION__, __LINE__) ); + + if ((GFXTextureObject*) mFieldData.mFoliageTexture == NULL) + Con::printf("fxFoliageReplicator: %s is an invalid or missing foliage texture file.", mFieldData.mFoliageFile); + + // Set Quad-Tree Box Height Lerp. + mFrustumRenderSet.mHeightLerp = mFieldData.mDebugBoxHeight; + + // Create Foliage (if Replication has begun). + if (mClientReplicationStarted) + { + CreateFoliage(); + mDirty = true; + } + + if ( isProperlyAdded() && mFieldData.mUseTrueBillboards != wasTrueBB ) + SetupShader(); + } +} diff --git a/T3D/fx/fxFoliageReplicator.h b/T3D/fx/fxFoliageReplicator.h new file mode 100644 index 0000000..3443824 --- /dev/null +++ b/T3D/fx/fxFoliageReplicator.h @@ -0,0 +1,374 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Written by Melvyn May, 4th August 2002. +//----------------------------------------------------------------------------- + +#ifndef _FOLIAGEREPLICATOR_H_ +#define _FOLIAGEREPLICATOR_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _GBITMAP_H_ +#include "gfx/bitmap/gBitmap.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif + +#pragma warning( push, 4 ) + +#define AREA_ANIMATION_ARC (1.0f / 360.0f) + +#define FXFOLIAGEREPLICATOR_COLLISION_MASK ( TerrainObjectType | \ + InteriorObjectType | \ + StaticObjectType | \ + WaterObjectType ) + +#define FXFOLIAGEREPLICATOR_NOWATER_COLLISION_MASK ( TerrainObjectType | \ + InteriorObjectType | \ + StaticObjectType ) + + +#define FXFOLIAGE_ALPHA_EPSILON 1e-4 + + + +//------------------------------------------------------------------------------ +// Class: fxFoliageItem +//------------------------------------------------------------------------------ +class fxFoliageItem +{ +public: + MatrixF Transform; + F32 Width; + F32 Height; + Box3F FoliageBox; + bool Flipped; + F32 SwayPhase; + F32 SwayTimeRatio; + F32 LightPhase; + F32 LightTimeRatio; + U32 LastFrameSerialID; +}; + +//------------------------------------------------------------------------------ +// Class: fxFoliageCulledList +//------------------------------------------------------------------------------ +class fxFoliageCulledList +{ +public: + fxFoliageCulledList() {}; + fxFoliageCulledList(Box3F SearchBox, fxFoliageCulledList* InVec); + ~fxFoliageCulledList() {}; + + void FindCandidates(Box3F SearchBox, fxFoliageCulledList* InVec); + + U32 GetListCount(void) { return mCulledObjectSet.size(); }; + fxFoliageItem* GetElement(U32 index) { return mCulledObjectSet[index]; }; + + Vector mCulledObjectSet; // Culled Object Set. +}; + + +//------------------------------------------------------------------------------ +// Class: fxFoliageQuadNode +//------------------------------------------------------------------------------ +class fxFoliageQuadrantNode { +public: + U32 Level; + Box3F QuadrantBox; + fxFoliageQuadrantNode* QuadrantChildNode[4]; + Vector RenderList; + // Used in DrawIndexPrimitive call. + U32 startIndex; + U32 primitiveCount; +}; + + +//------------------------------------------------------------------------------ +// Class: fxFoliageRenderList +//------------------------------------------------------------------------------ +class fxFoliageRenderList +{ +public: + + Box3F mBox; // Clipping Box. + Frustum mFrustum; // View frustum. + + Vector mVisObjectSet; // Visible Object Set. + F32 mHeightLerp; // Height Lerp. + +public: + bool IsQuadrantVisible(const Box3F VisBox, const MatrixF& RenderTransform); + void SetupClipPlanes(SceneState* state, const F32 FarClipPlane); + void DrawQuadBox(const Box3F& QuadBox, const ColorF Colour); +}; + + +// Define a vertex +GFXDeclareVertexFormat( GFXVertexFoliage ) +{ + Point3F point; + Point3F normal; + Point2F texCoord; + Point2F texCoord2; +}; + + +//------------------------------------------------------------------------------ +// Class: fxFoliageReplicator +//------------------------------------------------------------------------------ +class fxFoliageReplicator : public SceneObject +{ +private: + typedef SceneObject Parent; + +protected: + + void CreateFoliage(void); + void DestroyFoliage(void); + void DestroyFoliageItems(); + + + void SyncFoliageReplicators(void); + + Box3F FetchQuadrant(Box3F Box, U32 Quadrant); + void ProcessQuadrant(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList, U32 Quadrant); + void ProcessNodeChildren(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList); + + enum { FoliageReplicationMask = (1 << 0) }; + + + U32 mCreationAreaAngle; + bool mClientReplicationStarted; + U32 mCurrentFoliageCount; + + Vector mFoliageQuadTree; + Vector mReplicatedFoliage; + fxFoliageRenderList mFrustumRenderSet; + + GFXVertexBufferHandle mVertexBuffer; + GFXPrimitiveBufferHandle mPrimBuffer; + GFXShaderRef mShader; + ShaderData* mShaderData; + GBitmap* mAlphaLookup; + + MRandomLCG RandomGen; + F32 mFadeInGradient; + F32 mFadeOutGradient; + S32 mLastRenderTime; + F32 mGlobalSwayPhase; + F32 mGlobalSwayTimeRatio; + F32 mGlobalLightPhase; + F32 mGlobalLightTimeRatio; + U32 mFrameSerialID; + + U32 mQuadTreeLevels; // Quad-Tree Levels. + U32 mPotentialFoliageNodes; // Potential Foliage Nodes. + U32 mNextAllocatedNodeIdx; // Next Allocated Node Index. + U32 mBillboardsAcquired; // Billboards Acquired. + + // Used for alpha lookup in the pixel shader + GFXTexHandle mAlphaTexture; + + GFXStateBlockRef mPlacementSB; + GFXStateBlockRef mRenderSB; + GFXStateBlockRef mDebugSB; + + GFXShaderConstBufferRef mFoliageShaderConsts; + + GFXShaderConstHandle* mFoliageShaderProjectionSC; + GFXShaderConstHandle* mFoliageShaderWorldSC; + GFXShaderConstHandle* mFoliageShaderGlobalSwayPhaseSC; + GFXShaderConstHandle* mFoliageShaderSwayMagnitudeSideSC; + GFXShaderConstHandle* mFoliageShaderSwayMagnitudeFrontSC; + GFXShaderConstHandle* mFoliageShaderGlobalLightPhaseSC; + GFXShaderConstHandle* mFoliageShaderLuminanceMagnitudeSC; + GFXShaderConstHandle* mFoliageShaderLuminanceMidpointSC; + GFXShaderConstHandle* mFoliageShaderDistanceRangeSC; + GFXShaderConstHandle* mFoliageShaderCameraPosSC; + GFXShaderConstHandle* mFoliageShaderTrueBillboardSC; + + //pixel shader + GFXShaderConstHandle* mFoliageShaderGroundAlphaSC; + GFXShaderConstHandle* mFoliageShaderAmbientColorSC; + + + + bool mDirty; + + void SetupShader(); + void SetupBuffers(); + void renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance*); + void renderBuffers(SceneState* state); + void renderArc(const F32 fRadiusX, const F32 fRadiusY); + void renderPlacementArea(const F32 ElapsedTime); + void renderQuad(fxFoliageQuadrantNode* quadNode, const MatrixF& RenderTransform, const bool UseDebug); + void computeAlphaTex(); +public: + fxFoliageReplicator(); + ~fxFoliageReplicator(); + + void StartUp(void); + void ShowReplication(void); + void HideReplication(void); + + // SceneObject + virtual bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, + const bool modifyBaseZoneState = false); + + // SimObject + bool onAdd(); + void onRemove(); + void inspectPostApply(); + + // NetObject + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + // Editor + void onGhostAlwaysDone(); + + // ConObject. + static void initPersistFields(); + + // Field Data. + class tagFieldData + { + public: + + bool mUseDebugInfo; + F32 mDebugBoxHeight; + U32 mSeed; + StringTableEntry mFoliageFile; + GFXTexHandle mFoliageTexture; + U32 mFoliageCount; + U32 mFoliageRetries; + + U32 mInnerRadiusX; + U32 mInnerRadiusY; + U32 mOuterRadiusX; + U32 mOuterRadiusY; + + F32 mMinWidth; + F32 mMaxWidth; + F32 mMinHeight; + F32 mMaxHeight; + bool mFixAspectRatio; + bool mFixSizeToMax; + F32 mOffsetZ; + bool mRandomFlip; + bool mUseTrueBillboards; + + bool mUseCulling; + U32 mCullResolution; + F32 mViewDistance; + F32 mViewClosest; + F32 mFadeInRegion; + F32 mFadeOutRegion; + F32 mAlphaCutoff; + F32 mGroundAlpha; + + bool mSwayOn; + bool mSwaySync; + F32 mSwayMagnitudeSide; + F32 mSwayMagnitudeFront; + F32 mMinSwayTime; + F32 mMaxSwayTime; + + bool mLightOn; + bool mLightSync; + F32 mMinLuminance; + F32 mMaxLuminance; + F32 mLightTime; + + bool mAllowOnTerrain; + bool mAllowOnInteriors; + bool mAllowStatics; + bool mAllowOnWater; + bool mAllowWaterSurface; + S32 mAllowedTerrainSlope; + + bool mHideFoliage; + bool mShowPlacementArea; + U32 mPlacementBandHeight; + ColorF mPlaceAreaColour; + + tagFieldData() + { + // Set Defaults. + mUseDebugInfo = false; + mDebugBoxHeight = 1.0f; + mSeed = 1376312589; + mFoliageFile = StringTable->insert(""); + mFoliageTexture = GFXTexHandle(); + mFoliageCount = 10; + mFoliageRetries = 100; + + mInnerRadiusX = 0; + mInnerRadiusY = 0; + mOuterRadiusX = 128; + mOuterRadiusY = 128; + + mMinWidth = 1; + mMaxWidth = 3; + mMinHeight = 1; + mMaxHeight = 5; + mFixAspectRatio = true; + mFixSizeToMax = false; + mOffsetZ = 0; + mRandomFlip = true; + mUseTrueBillboards = false; + + mUseCulling = true; + mCullResolution = 64; + mViewDistance = 50.0f; + mViewClosest = 1.0f; + mFadeInRegion = 10.0f; + mFadeOutRegion = 1.0f; + mAlphaCutoff = 0.2f; + mGroundAlpha = 1.0f; + + mSwayOn = false; + mSwaySync = false; + mSwayMagnitudeSide = 0.1f; + mSwayMagnitudeFront = 0.2f; + mMinSwayTime = 3.0f; + mMaxSwayTime = 10.0f; + + mLightOn = false; + mLightSync = false; + mMinLuminance = 0.7f; + mMaxLuminance = 1.0f; + mLightTime = 5.0f; + + mAllowOnTerrain = true; + mAllowOnInteriors = true; + mAllowStatics = true; + mAllowOnWater = false; + mAllowWaterSurface = false; + mAllowedTerrainSlope = 90; + + mHideFoliage = false; + mShowPlacementArea = true; + mPlacementBandHeight = 25; + mPlaceAreaColour .set(0.4f, 0, 0.8f); + } + + } mFieldData; + + // Declare Console Object. + DECLARE_CONOBJECT(fxFoliageReplicator); +}; +#pragma warning( pop ) +#endif // _FOLIAGEREPLICATOR_H_ diff --git a/T3D/fx/fxShapeReplicator.cpp b/T3D/fx/fxShapeReplicator.cpp new file mode 100644 index 0000000..7d1453e --- /dev/null +++ b/T3D/fx/fxShapeReplicator.cpp @@ -0,0 +1,742 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gfxDevice.h" +#include "gfx/primBuilder.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "math/mRandom.h" +#include "math/mathIO.h" +#include "T3D/gameConnection.h" +#include "console/simBase.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "fxShapeReplicator.h" +#include "renderInstance/renderPassManager.h" + +//------------------------------------------------------------------------------ +// +// Put this in /example/common/editor/editor.cs in function [Editor::create()] (around line 66). +// +// // Ignore Replicated fxStatic Instances. +// EWorldEditor.ignoreObjClass("fxShapeReplicatedStatic"); +// +//------------------------------------------------------------------------------ +// +// Put this in /example/common/editor/EditorGui.cs in [function Creator::init( %this )] +// +// %Environment_Item[8] = "fxShapeReplicator"; <-- ADD THIS. +// +//------------------------------------------------------------------------------ +// +// Put the function in /example/common/editor/ObjectBuilderGui.gui [around line 458] ... +// +// function ObjectBuilderGui::buildfxShapeReplicator(%this) +// { +// %this.className = "fxShapeReplicator"; +// %this.process(); +// } +// +//------------------------------------------------------------------------------ +// +// Put this in /example/common/client/missionDownload.cs in [function clientCmdMissionStartPhase3(%seq,%missionName)] (line 65) +// after codeline 'onPhase2Complete();'. +// +// StartClientReplication(); +// +//------------------------------------------------------------------------------ +// +// Put this in /engine/console/simBase.h (around line 509) in +// +// namespace Sim +// { +// DeclareNamedSet(fxReplicatorSet) <-- ADD THIS (Note no semi-colon). +// +//------------------------------------------------------------------------------ +// +// Put this in /engine/console/simBase.cc (around line 19) in +// +// ImplementNamedSet(fxReplicatorSet) <-- ADD THIS +// +//------------------------------------------------------------------------------ +// +// Put this in /engine/console/simManager.cc [function void init()] (around line 269). +// +// namespace Sim +// { +// InstantiateNamedSet(fxReplicatorSet); <-- ADD THIS +// +//------------------------------------------------------------------------------ + +extern bool gEditingMission; + +//------------------------------------------------------------------------------ + +IMPLEMENT_CO_NETOBJECT_V1(fxShapeReplicator); +IMPLEMENT_CO_NETOBJECT_V1(fxShapeReplicatedStatic); + + +//------------------------------------------------------------------------------ +// Class: fxShapeReplicator +//------------------------------------------------------------------------------ + +fxShapeReplicator::fxShapeReplicator() +{ + // Setup NetObject. + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + // Reset Shape Count. + mCurrentShapeCount = 0; + + // Reset Creation Area Angle Animation. + mCreationAreaAngle = 0; + + // Reset Last Render Time. + mLastRenderTime = 0; + + mPlacementSB = NULL; +} + +//------------------------------------------------------------------------------ + +fxShapeReplicator::~fxShapeReplicator() +{ + mPlacementSB = NULL; +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::initPersistFields() +{ + // Add out own persistent fields. + addGroup( "Debugging" ); // MM: Added Group Header. + addField( "HideReplications", TypeBool, Offset( mFieldData.mHideReplications, fxShapeReplicator ), "Replicated shapes are hidden when set to true." ); + addField( "ShowPlacementArea", TypeBool, Offset( mFieldData.mShowPlacementArea, fxShapeReplicator ), "Draw placement rings when set to true." ); + addField( "PlacementAreaHeight", TypeS32, Offset( mFieldData.mPlacementBandHeight, fxShapeReplicator ), "Height of the placement ring in world units." ); + addField( "PlacementColour", TypeColorF, Offset( mFieldData.mPlaceAreaColour, fxShapeReplicator ), "Color of the placement ring." ); + endGroup( "Debugging" ); // MM: Added Group Footer. + + addGroup( "Media" ); // MM: Added Group Header. + addField( "ShapeFile", TypeFilename, Offset( mFieldData.mShapeFile, fxShapeReplicator ), "Filename of shape to replicate." ); + endGroup( "Media" ); // MM: Added Group Footer. + + addGroup( "Replications" ); // MM: Added Group Header. + addField( "Seed", TypeS32, Offset( mFieldData.mSeed, fxShapeReplicator ), "Random seed for shape placement." ); + addField( "ShapeCount", TypeS32, Offset( mFieldData.mShapeCount, fxShapeReplicator ), "Maximum shape instance count." ); + addField( "ShapeRetries", TypeS32, Offset( mFieldData.mShapeRetries, fxShapeReplicator ), "Number of times to try placing a shape instance before giving up." ); + endGroup( "Replications" ); // MM: Added Group Footer. + + addGroup( "Placement Radius" ); // MM: Added Group Header. + addField( "InnerRadiusX", TypeS32, Offset( mFieldData.mInnerRadiusX, fxShapeReplicator ), "Placement area inner radius on the X axis" ); + addField( "InnerRadiusY", TypeS32, Offset( mFieldData.mInnerRadiusY, fxShapeReplicator ), "Placement area inner radius on the Y axis" ); + addField( "OuterRadiusX", TypeS32, Offset( mFieldData.mOuterRadiusX, fxShapeReplicator ), "Placement area outer radius on the X axis" ); + addField( "OuterRadiusY", TypeS32, Offset( mFieldData.mOuterRadiusY, fxShapeReplicator ), "Placement area outer radius on the Y axis" ); + endGroup( "Placement Radius" ); // MM: Added Group Footer. + + addGroup( "Restraints" ); // MM: Added Group Header. + addField( "AllowOnTerrain", TypeBool, Offset( mFieldData.mAllowOnTerrain, fxShapeReplicator ), "Shapes will be placed on terrain when set." ); + addField( "AllowOnInteriors", TypeBool, Offset( mFieldData.mAllowOnInteriors, fxShapeReplicator ), "Shapes will be placed on InteriorInstances when set." ); + addField( "AllowOnStatics", TypeBool, Offset( mFieldData.mAllowStatics, fxShapeReplicator ), "Shapes will be placed on Static shapes when set." ); + addField( "AllowOnWater", TypeBool, Offset( mFieldData.mAllowOnWater, fxShapeReplicator ), "Shapes will be placed on/under water when set." ); + addField( "AllowWaterSurface", TypeBool, Offset( mFieldData.mAllowWaterSurface, fxShapeReplicator ), "Shapes will be placed on water when set. Requires AllowOnWater." ); + addField( "AlignToTerrain", TypeBool, Offset( mFieldData.mAlignToTerrain, fxShapeReplicator ), "Align shapes to surface normal when set." ); + addField( "Interactions", TypeBool, Offset( mFieldData.mInteractions, fxShapeReplicator ), "Allow physics interactions with shapes." ); + addField( "AllowedTerrainSlope", TypeS32, Offset( mFieldData.mAllowedTerrainSlope, fxShapeReplicator ), "Maximum surface angle allowed for shape instances." ); + addField( "TerrainAlignment", TypePoint3F, Offset( mFieldData.mTerrainAlignment, fxShapeReplicator ), "Surface normals will be multiplied by these values when AlignToTerrain is enabled." ); + endGroup( "Restraints" ); // MM: Added Group Footer. + + addGroup( "Object Transforms" ); // MM: Added Group Header. + addField( "ShapeScaleMin", TypePoint3F, Offset( mFieldData.mShapeScaleMin, fxShapeReplicator ), "Minimum shape scale." ); + addField( "ShapeScaleMax", TypePoint3F, Offset( mFieldData.mShapeScaleMax, fxShapeReplicator ), "Maximum shape scale." ); + addField( "ShapeRotateMin", TypePoint3F, Offset( mFieldData.mShapeRotateMin, fxShapeReplicator ), "Minimum shape rotation angles."); + addField( "ShapeRotateMax", TypePoint3F, Offset( mFieldData.mShapeRotateMax, fxShapeReplicator ), "Maximum shape rotation angles." ); + addField( "OffsetZ", TypeS32, Offset( mFieldData.mOffsetZ, fxShapeReplicator ), "Offset shapes by this amount vertically." ); + endGroup( "Object Transforms" ); // MM: Added Group Footer. + + // Initialise parents' persistent fields. + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::CreateShapes(void) +{ + F32 HypX, HypY; + F32 Angle; + U32 RelocationRetry; + Point3F ShapePosition; + Point3F ShapeStart; + Point3F ShapeEnd; + Point3F ShapeScale; + EulerF ShapeRotation; + QuatF QRotation; + bool CollisionResult; + RayInfo RayEvent; + TSShape* pShape; + + + // Don't create shapes if we are hiding replications. + if (mFieldData.mHideReplications) return; + + // Cannot continue without shapes! + if (dStrcmp(mFieldData.mShapeFile, "") == 0) return; + + // Check that we can position somewhere! + if (!( mFieldData.mAllowOnTerrain || + mFieldData.mAllowOnInteriors || + mFieldData.mAllowStatics || + mFieldData.mAllowOnWater)) + { + // Problem ... + Con::warnf(ConsoleLogEntry::General, "[%s] - Could not place object, All alloweds are off!", getName()); + + // Return here. + return; + } + + // Check Shapes. + AssertFatal(mCurrentShapeCount==0,"Shapes already present, this should not be possible!") + + // Check that we have a shape... + if (!mFieldData.mShapeFile) return; + + // Set Seed. + RandomGen.setSeed(mFieldData.mSeed); + + // Set shape vector. + mReplicatedShapes.clear(); + + // Add shapes. + for (U32 idx = 0; idx < mFieldData.mShapeCount; idx++) + { + fxShapeReplicatedStatic* fxStatic; + + // Create our static shape. + fxStatic = new fxShapeReplicatedStatic(); + + // Set the 'shapeName' field. + fxStatic->setField("shapeName", mFieldData.mShapeFile); + + // Is this Replicator on the Server? + if (isServerObject()) + // Yes, so stop it from Ghosting. (Hack, Hack, Hack!) + fxStatic->touchNetFlags(Ghostable, false); + else + // No, so flag as ghost object. (Another damn Hack!) + fxStatic->touchNetFlags(IsGhost, true); + + // Register the Object. + if (!fxStatic->registerObject()) + { + // Problem ... + Con::warnf(ConsoleLogEntry::General, "[%s] - Could not load shape file '%s'!", getName(), mFieldData.mShapeFile); + + // Destroy Shape. + delete fxStatic; + + // Destroy existing hapes. + DestroyShapes(); + + // Quit. + return; + } + + // Get Allocated Shape. + pShape = fxStatic->getShape(); + + // Reset Relocation Retry. + RelocationRetry = mFieldData.mShapeRetries; + + // Find it a home ... + do + { + // Get the Replicator Position. + ShapePosition = getPosition(); + + // Calculate a random offset + HypX = RandomGen.randF(mFieldData.mInnerRadiusX, mFieldData.mOuterRadiusX); + HypY = RandomGen.randF(mFieldData.mInnerRadiusY, mFieldData.mOuterRadiusY); + Angle = RandomGen.randF(0, (F32)M_2PI); + + // Calcualte the new position. + ShapePosition.x += HypX * mCos(Angle); + ShapePosition.y += HypY * mSin(Angle); + + + // Initialise RayCast Search Start/End Positions. + ShapeStart = ShapeEnd = ShapePosition; + ShapeStart.z = 2000.f; + ShapeEnd.z= -2000.f; + + // Is this the Server? + if (isServerObject()) + // Perform Ray Cast Collision on Server Terrain. + CollisionResult = gServerContainer.castRay(ShapeStart, ShapeEnd, FXREPLICATOR_COLLISION_MASK, &RayEvent); + else + // Perform Ray Cast Collision on Client Terrain. + CollisionResult = gClientContainer.castRay( ShapeStart, ShapeEnd, FXREPLICATOR_COLLISION_MASK, &RayEvent); + + // Did we hit anything? + if (CollisionResult) + { + // For now, let's pretend we didn't get a collision. + CollisionResult = false; + + // Yes, so get it's type. + U32 CollisionType = RayEvent.object->getTypeMask(); + + // Check Illegal Placements. + if (((CollisionType & TerrainObjectType) && !mFieldData.mAllowOnTerrain) || + ((CollisionType & InteriorObjectType) && !mFieldData.mAllowOnInteriors) || + ((CollisionType & StaticTSObjectType) && !mFieldData.mAllowStatics) || + ((CollisionType & WaterObjectType) && !mFieldData.mAllowOnWater) ) continue; + + // If we collided with water and are not allowing on the water surface then let's find the + // terrain underneath and pass this on as the original collision else fail. + // + // NOTE:- We need to do this on the server/client as appropriate. + if ((CollisionType & WaterObjectType) && !mFieldData.mAllowWaterSurface) + { + // Is this the Server? + if (isServerObject()) + { + // Yes, so do it on the server container. + if (!gServerContainer.castRay( ShapeStart, ShapeEnd, FXREPLICATOR_NOWATER_COLLISION_MASK, &RayEvent)) continue; + } + else + { + // No, so do it on the client container. + if (!gClientContainer.castRay( ShapeStart, ShapeEnd, FXREPLICATOR_NOWATER_COLLISION_MASK, &RayEvent)) continue; + } + } + + // We passed with flying colours so carry on. + CollisionResult = true; + } + + // Invalidate if we are below Allowed Terrain Angle. + if (RayEvent.normal.z < mSin(mDegToRad(90.0f-mFieldData.mAllowedTerrainSlope))) CollisionResult = false; + + // Wait until we get a collision. + } while(!CollisionResult && --RelocationRetry); + + // Check for Relocation Problem. + if (RelocationRetry > 0) + { + // Adjust Impact point. + RayEvent.point.z += mFieldData.mOffsetZ; + + // Set New Position. + ShapePosition = RayEvent.point; + } + else + { + // Warning. + Con::warnf(ConsoleLogEntry::General, "[%s] - Could not find satisfactory position for shape '%s' on %s!", getName(), mFieldData.mShapeFile,isServerObject()?"Server":"Client"); + + // Unregister Object. + fxStatic->unregisterObject(); + + // Destroy Shape. + delete fxStatic; + + // Skip to next. + continue; + } + + // Get Shape Transform. + MatrixF XForm = fxStatic->getTransform(); + + // Are we aligning to Terrain? + if (mFieldData.mAlignToTerrain) + { + // Yes, so set rotation to Terrain Impact Normal. + ShapeRotation = RayEvent.normal * mFieldData.mTerrainAlignment; + } + else + { + // No, so choose a new Rotation (in Radians). + ShapeRotation.set( mDegToRad(RandomGen.randF(mFieldData.mShapeRotateMin.x, mFieldData.mShapeRotateMax.x)), + mDegToRad(RandomGen.randF(mFieldData.mShapeRotateMin.y, mFieldData.mShapeRotateMax.y)), + mDegToRad(RandomGen.randF(mFieldData.mShapeRotateMin.z, mFieldData.mShapeRotateMax.z))); + } + + // Set Quaternion Roation. + QRotation.set(ShapeRotation); + + // Set Transform Rotation. + QRotation.setMatrix(&XForm); + + // Set Position. + XForm.setColumn(3, ShapePosition); + + // Set Shape Position / Rotation. + fxStatic->setTransform(XForm); + + // Choose a new Scale. + ShapeScale.set( RandomGen.randF(mFieldData.mShapeScaleMin.x, mFieldData.mShapeScaleMax.x), + RandomGen.randF(mFieldData.mShapeScaleMin.y, mFieldData.mShapeScaleMax.y), + RandomGen.randF(mFieldData.mShapeScaleMin.z, mFieldData.mShapeScaleMax.z)); + + // Set Shape Scale. + fxStatic->setScale(ShapeScale); + + // Lock it. + fxStatic->setLocked(true); + + // Store Shape in Replicated Shapes Vector. + //mReplicatedShapes[mCurrentShapeCount++] = fxStatic; + mReplicatedShapes.push_back(fxStatic); + + } + + mCurrentShapeCount = mReplicatedShapes.size(); + + // Take first Timestamp. + mLastRenderTime = Platform::getVirtualMilliseconds(); +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::DestroyShapes(void) +{ + // Finish if we didn't create any shapes. + if (mCurrentShapeCount == 0) return; + + // Remove shapes. + for (U32 idx = 0; idx < mCurrentShapeCount; idx++) + { + fxShapeReplicatedStatic* fxStatic; + + // Fetch the Shape Object. + fxStatic = mReplicatedShapes[idx]; + + // Got a Shape? + if (fxStatic) + { + // Unlock it. + fxStatic->setLocked(false); + + // Unregister the object. + fxStatic->unregisterObject(); + + // Delete it. + delete fxStatic; + } + } + + // Empty the Replicated Shapes Vector. + mReplicatedShapes.clear(); + + // Reset Shape Count. + mCurrentShapeCount = 0; +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::RenewShapes(void) +{ + // Destroy any shapes. + DestroyShapes(); + + // Don't create shapes on the Server if we don't need interactions. + if (isServerObject() && !mFieldData.mInteractions) return; + + // Create Shapes. + CreateShapes(); +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::StartUp(void) +{ + RenewShapes(); +} + +//------------------------------------------------------------------------------ + +bool fxShapeReplicator::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // Add the Replicator to the Replicator Set. + dynamic_cast(Sim::findObject("fxReplicatorSet"))->addObject(this); + + // Set Default Object Box. + mObjBox.minExtents.set( -0.5, -0.5, -0.5 ); + mObjBox.maxExtents.set( 0.5, 0.5, 0.5 ); + resetWorldBox(); + + // Add to Scene. + setRenderTransform(mObjToWorld); + addToScene(); + + // Register for notification when GhostAlways objects are done loading + NetConnection::smGhostAlwaysDone.notify( this, &fxShapeReplicator::onGhostAlwaysDone ); + + return true; +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::onRemove() +{ + // Remove the Replicator from the Replicator Set. + dynamic_cast(Sim::findObject("fxReplicatorSet"))->removeObject(this); + + NetConnection::smGhostAlwaysDone.remove( this, &fxShapeReplicator::onGhostAlwaysDone ); + + removeFromScene(); + + // Destroy Shapes. + DestroyShapes(); + + // Do Parent. + Parent::onRemove(); +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::onGhostAlwaysDone() +{ + RenewShapes(); +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::inspectPostApply() +{ + // Set Parent. + Parent::inspectPostApply(); + + // Renew Shapes. + RenewShapes(); + + // Set Replication Mask. + setMaskBits(ReplicationMask); +} + +//------------------------------------------------------------------------------ + +ConsoleFunction(StartClientReplication, void, 1, 1, "StartClientReplication()") +{ + // Find the Replicator Set. + SimSet *fxReplicatorSet = dynamic_cast(Sim::findObject("fxReplicatorSet")); + + // Return if Error. + if (!fxReplicatorSet) return; + + // StartUp Replication Object. + for (SimSetIterator itr(fxReplicatorSet); *itr; ++itr) + { + // Fetch the Replicator Object. + fxShapeReplicator* Replicator = static_cast(*itr); + // Start Client Objects Only. + if (Replicator->isClientObject()) Replicator->StartUp(); + } + // Info ... + Con::printf("Client Replication Startup has Happened!"); +} + + +//------------------------------------------------------------------------------ + +bool fxShapeReplicator::prepRenderImage(SceneState* state, const U32 stateKey, const U32 startZone, + const bool modifyBaseZoneState) +{ + // Return if last state. + if (isLastState(state, stateKey)) return false; + // Set Last State. + setLastState(state, stateKey); + + // Is Object Rendered? + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &fxShapeReplicator::renderObject); + // The fxShapeReplicator isn't technically foliage but our debug + // effect seems to render best as a Foliage type (translucent, + // renders itself, no sorting) + ri->type = RenderPassManager::RIT_Foliage; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +//------------------------------------------------------------------------------ + +// Renders a triangle stripped oval +void fxShapeReplicator::renderArc(const F32 fRadiusX, const F32 fRadiusY) +{ + PrimBuild::begin(GFXTriangleStrip, 720); + for (U32 Angle = mCreationAreaAngle; Angle < (mCreationAreaAngle+360); Angle++) + { + F32 XPos, YPos; + + // Calculate Position. + XPos = fRadiusX * mCos(mDegToRad(-(F32)Angle)); + YPos = fRadiusY * mSin(mDegToRad(-(F32)Angle)); + + // Set Colour. + PrimBuild::color4f(mFieldData.mPlaceAreaColour.red, + mFieldData.mPlaceAreaColour.green, + mFieldData.mPlaceAreaColour.blue, + AREA_ANIMATION_ARC * (Angle-mCreationAreaAngle)); + + PrimBuild::vertex3f(XPos, YPos, -(F32)mFieldData.mPlacementBandHeight/2.0f); + PrimBuild::vertex3f(XPos, YPos, +(F32)mFieldData.mPlacementBandHeight/2.0f); + } + PrimBuild::end(); +} + +// This currently uses the primbuilder, could convert out, but why allocate the buffer if we +// never edit the misison? +void fxShapeReplicator::renderPlacementArea(const F32 ElapsedTime) +{ + if (gEditingMission && mFieldData.mShowPlacementArea) + { + GFX->pushWorldMatrix(); + GFX->multWorld(getTransform()); + + if (!mPlacementSB) + { + GFXStateBlockDesc transparent; + transparent.setCullMode(GFXCullNone); + transparent.alphaTestEnable = true; + transparent.setZReadWrite(true); + transparent.zWriteEnable = false; + transparent.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + mPlacementSB = GFX->createStateBlock( transparent ); + } + + GFX->setStateBlock(mPlacementSB); + + // Do we need to draw the Outer Radius? + if (mFieldData.mOuterRadiusX || mFieldData.mOuterRadiusY) + renderArc((F32) mFieldData.mOuterRadiusX, (F32) mFieldData.mOuterRadiusY); + // Inner radius? + if (mFieldData.mInnerRadiusX || mFieldData.mInnerRadiusY) + renderArc((F32) mFieldData.mInnerRadiusX, (F32) mFieldData.mInnerRadiusY); + + GFX->popWorldMatrix(); + mCreationAreaAngle = (U32)(mCreationAreaAngle + (1000 * ElapsedTime)); + mCreationAreaAngle = mCreationAreaAngle % 360; + } +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + // Return if placement area not needed. + if (!mFieldData.mShowPlacementArea) + return; + + // Calculate Elapsed Time and take new Timestamp. + S32 Time = Platform::getVirtualMilliseconds(); + F32 ElapsedTime = (Time - mLastRenderTime) * 0.001f; + mLastRenderTime = Time; + + renderPlacementArea(ElapsedTime); +} + +//------------------------------------------------------------------------------ + +U32 fxShapeReplicator::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + // Pack Parent. + U32 retMask = Parent::packUpdate(con, mask, stream); + + // Write Replication Flag. + if (stream->writeFlag(mask & ReplicationMask)) + { + stream->writeAffineTransform(mObjToWorld); // Replicator Position. + + stream->writeInt(mFieldData.mSeed, 32); // Replicator Seed. + stream->writeInt(mFieldData.mShapeCount, 32); // Shapes Count. + stream->writeInt(mFieldData.mShapeRetries, 32); // Shapes Retries. + stream->writeString(mFieldData.mShapeFile); + stream->writeInt(mFieldData.mInnerRadiusX, 32); // Shapes Inner Radius X. + stream->writeInt(mFieldData.mInnerRadiusY, 32); // Shapes Inner Radius Y. + stream->writeInt(mFieldData.mOuterRadiusX, 32); // Shapes Outer Radius X. + stream->writeInt(mFieldData.mOuterRadiusY, 32); // Shapes Outer Radius Y. + mathWrite(*stream, mFieldData.mShapeScaleMin); // Shapes Scale Min. + mathWrite(*stream, mFieldData.mShapeScaleMax); // Shapes Scale Max. + mathWrite(*stream, mFieldData.mShapeRotateMin); // Shapes Rotate Min. + mathWrite(*stream, mFieldData.mShapeRotateMax); // Shapes Rotate Max. + stream->writeSignedInt(mFieldData.mOffsetZ, 32); // Shapes Offset Z. + stream->writeFlag(mFieldData.mAllowOnTerrain); // Allow on Terrain. + stream->writeFlag(mFieldData.mAllowOnInteriors); // Allow on Interiors. + stream->writeFlag(mFieldData.mAllowStatics); // Allow on Statics. + stream->writeFlag(mFieldData.mAllowOnWater); // Allow on Water. + stream->writeFlag(mFieldData.mAllowWaterSurface); // Allow on Water Surface. + stream->writeSignedInt(mFieldData.mAllowedTerrainSlope, 32); // Shapes Offset Z. + stream->writeFlag(mFieldData.mAlignToTerrain); // Shapes AlignToTerrain. + mathWrite(*stream, mFieldData.mTerrainAlignment); // Write Terrain Alignment. + stream->writeFlag(mFieldData.mHideReplications); // Hide Replications. + stream->writeFlag(mFieldData.mInteractions); // Shape Interactions. + stream->writeFlag(mFieldData.mShowPlacementArea); // Show Placement Area Flag. + stream->writeInt(mFieldData.mPlacementBandHeight, 32); // Placement Area Height. + stream->write(mFieldData.mPlaceAreaColour); + } + + // Were done ... + return(retMask); +} + +//------------------------------------------------------------------------------ + +void fxShapeReplicator::unpackUpdate(NetConnection * con, BitStream * stream) +{ + // Unpack Parent. + Parent::unpackUpdate(con, stream); + + // Read Replication Details. + if(stream->readFlag()) + { + MatrixF ReplicatorObjectMatrix; + + stream->readAffineTransform(&ReplicatorObjectMatrix); // Replication Position. + + mFieldData.mSeed = stream->readInt(32); // Replicator Seed. + mFieldData.mShapeCount = stream->readInt(32); // Shapes Count. + mFieldData.mShapeRetries = stream->readInt(32); // Shapes Retries. + mFieldData.mShapeFile = stream->readSTString(); // Shape File. + mFieldData.mInnerRadiusX = stream->readInt(32); // Shapes Inner Radius X. + mFieldData.mInnerRadiusY = stream->readInt(32); // Shapes Inner Radius Y. + mFieldData.mOuterRadiusX = stream->readInt(32); // Shapes Outer Radius X. + mFieldData.mOuterRadiusY = stream->readInt(32); // Shapes Outer Radius Y. + mathRead(*stream, &mFieldData.mShapeScaleMin); // Shapes Scale Min. + mathRead(*stream, &mFieldData.mShapeScaleMax); // Shapes Scale Max. + mathRead(*stream, &mFieldData.mShapeRotateMin); // Shapes Rotate Min. + mathRead(*stream, &mFieldData.mShapeRotateMax); // Shapes Rotate Max. + mFieldData.mOffsetZ = stream->readSignedInt(32); // Shapes Offset Z. + mFieldData.mAllowOnTerrain = stream->readFlag(); // Allow on Terrain. + mFieldData.mAllowOnInteriors = stream->readFlag(); // Allow on Interiors. + mFieldData.mAllowStatics = stream->readFlag(); // Allow on Statics. + mFieldData.mAllowOnWater = stream->readFlag(); // Allow on Water. + mFieldData.mAllowWaterSurface = stream->readFlag(); // Allow on Water Surface. + mFieldData.mAllowedTerrainSlope = stream->readSignedInt(32); // Allowed Terrain Slope. + mFieldData.mAlignToTerrain = stream->readFlag(); // Read AlignToTerrain. + mathRead(*stream, &mFieldData.mTerrainAlignment); // Read Terrain Alignment. + mFieldData.mHideReplications = stream->readFlag(); // Hide Replications. + mFieldData.mInteractions = stream->readFlag(); // Read Interactions. + mFieldData.mShowPlacementArea = stream->readFlag(); // Show Placement Area Flag. + mFieldData.mPlacementBandHeight = stream->readInt(32); // Placement Area Height. + stream->read(&mFieldData.mPlaceAreaColour); + + // Set Transform. + setTransform(ReplicatorObjectMatrix); + + RenewShapes(); + } +} + diff --git a/T3D/fx/fxShapeReplicator.h b/T3D/fx/fxShapeReplicator.h new file mode 100644 index 0000000..239da63 --- /dev/null +++ b/T3D/fx/fxShapeReplicator.h @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHAPEREPLICATOR_H_ +#define _SHAPEREPLICATOR_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sim/sceneObject.h" +#endif +#ifndef _TSSTATIC_H_ +#include "T3D/tsStatic.h" +#endif +#ifndef _TSSHAPEINSTANCE_H_ +#include "ts/tsShapeInstance.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif + +#define AREA_ANIMATION_ARC (1.0f / 360.0f) + +#define FXREPLICATOR_COLLISION_MASK ( TerrainObjectType | \ + InteriorObjectType | \ + StaticObjectType | \ + WaterObjectType ) + +#define FXREPLICATOR_NOWATER_COLLISION_MASK ( TerrainObjectType | \ + InteriorObjectType | \ + StaticObjectType ) + + +//------------------------------------------------------------------------------ +// Class: fxShapeReplicatedStatic +//------------------------------------------------------------------------------ +class fxShapeReplicatedStatic : public TSStatic +{ +private: + typedef SceneObject Parent; + +public: + fxShapeReplicatedStatic() {}; + ~fxShapeReplicatedStatic() {}; + void touchNetFlags(const U32 m, bool setflag = true) { if (setflag) mNetFlags.set(m); else mNetFlags.clear(m); }; + TSShape* getShape(void) { return mShapeInstance->getShape(); }; + void setTransform(const MatrixF & mat) { Parent::setTransform(mat); setRenderTransform(mat); }; + + DECLARE_CONOBJECT(fxShapeReplicatedStatic); +}; + + +//------------------------------------------------------------------------------ +// Class: fxShapeReplicator +//------------------------------------------------------------------------------ +class fxShapeReplicator : public SceneObject +{ +private: + typedef SceneObject Parent; + +protected: + + void CreateShapes(void); + void DestroyShapes(void); + void RenewShapes(void); + + enum { ReplicationMask = (1 << 0) }; + + U32 mCreationAreaAngle; + U32 mCurrentShapeCount; + Vector mReplicatedShapes; + MRandomLCG RandomGen; + S32 mLastRenderTime; + + +public: + fxShapeReplicator(); + ~fxShapeReplicator(); + + + void StartUp(void); + void ShowReplication(void); + void HideReplication(void); + + GFXStateBlockRef mPlacementSB; + + void renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance*); + void renderArc(const F32 fRadiusX, const F32 fRadiusY); + void renderPlacementArea(const F32 ElapsedTime); + + // SceneObject + virtual bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, + const bool modifyBaseZoneState = false); + + // SimObject + bool onAdd(); + void onRemove(); + void inspectPostApply(); + + // NetObject + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + // Editor + void onGhostAlwaysDone(); + + // ConObject. + static void initPersistFields(); + + // Field Data. + class tagFieldData + { + public: + + U32 mSeed; + StringTableEntry mShapeFile; + U32 mShapeCount; + U32 mShapeRetries; + Point3F mShapeScaleMin; + Point3F mShapeScaleMax; + Point3F mShapeRotateMin; + Point3F mShapeRotateMax; + U32 mInnerRadiusX; + U32 mInnerRadiusY; + U32 mOuterRadiusX; + U32 mOuterRadiusY; + S32 mOffsetZ; + bool mAllowOnTerrain; + bool mAllowOnInteriors; + bool mAllowStatics; + bool mAllowOnWater; + S32 mAllowedTerrainSlope; + bool mAlignToTerrain; + bool mAllowWaterSurface; + Point3F mTerrainAlignment; + bool mInteractions; + bool mHideReplications; + bool mShowPlacementArea; + U32 mPlacementBandHeight; + ColorF mPlaceAreaColour; + + tagFieldData() + { + // Set Defaults. + mSeed = 1376312589; + mShapeFile = StringTable->insert(""); + mShapeCount = 10; + mShapeRetries = 100; + mInnerRadiusX = 0; + mInnerRadiusY = 0; + mOuterRadiusX = 100; + mOuterRadiusY = 100; + mOffsetZ = 0; + + mAllowOnTerrain = true; + mAllowOnInteriors = true; + mAllowStatics = true; + mAllowOnWater = false; + mAllowWaterSurface = false; + mAllowedTerrainSlope= 90; + mAlignToTerrain = false; + mInteractions = true; + + mHideReplications = false; + + mShowPlacementArea = true; + mPlacementBandHeight = 25; + mPlaceAreaColour .set(0.4f, 0, 0.8f); + + mShapeScaleMin .set(1, 1, 1); + mShapeScaleMax .set(1, 1, 1); + mShapeRotateMin .set(0, 0, 0); + mShapeRotateMax .set(0, 0, 0); + mTerrainAlignment .set(1, 1, 1); + } + + } mFieldData; + + // Declare Console Object. + DECLARE_CONOBJECT(fxShapeReplicator); +}; + +#endif // _SHAPEREPLICATOR_H_ diff --git a/T3D/fx/groundCover.cpp b/T3D/fx/groundCover.cpp new file mode 100644 index 0000000..9f3f19e --- /dev/null +++ b/T3D/fx/groundCover.cpp @@ -0,0 +1,1693 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/fx/groundCover.h" + +#include "core/resourceManager.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneState.h" +#include "terrain/terrData.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/primBuilder.h" +#include "T3D/gameConnection.h" +#include "gfx/gfxVertexBuffer.h" +#include "gfx/gfxStructs.h" +#include "ts/tsShapeInstance.h" +#include "lighting/lightManager.h" +#include "lighting/lightInfo.h" +#include "materials/shaderData.h" +#include "gfx/gfxTransformSaver.h" +#include "shaderGen/shaderGenVars.h" +#include "materials/matTextureTarget.h" +#include "gfx/util/screenspace.h" + +/// This is used for rendering ground cover billboards. +GFXImplementVertexFormat( GCVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float4, 0 ); +}; + +/// This defines one grid cell. +class GroundCoverCell +{ +protected: + + friend class GroundCover; + + struct Placement + { + Point3F point; + Point3F size; + F32 rotation; + U32 type; + F32 windAmplitude; + Box3F worldBox; + ColorF lmColor; + }; + + /// This is the x,y index for this cell. + Point2I mIndex; + + /// The worldspace bounding box this cell. + Box3F mBounds; + + /// The worldspace bounding box of the renderable + /// content within this cell. + Box3F mRenderBounds; + + /// The instances of billboard cover elements in this cell. + Vector mBillboards; + + /// The instances of shape cover elements in this cell. + Vector mShapes; + + typedef GFXVertexBufferHandle VBHandle; + typedef Vector< VBHandle > VBHandleVector; + + /// The vertex buffers that hold all the + /// prepared billboards for this cell. + VBHandleVector mVBs; + + /// Used to mark the cell dirty and in need + /// of a rebuild. + bool mDirty; + + /// Repacks the billboards into the vertex buffer. + void _rebuildVB(); + +public: + + GroundCoverCell() {} + + ~GroundCoverCell() + { + mVBs.clear(); + } + + const Point2I& shiftIndex( const Point2I& shift ) { return mIndex += shift; } + + /// The worldspace bounding box this cell. + const Box3F& getBounds() const { return mBounds; } + + /// The worldspace bounding box of the renderable + /// content within this cell. + const Box3F& getRenderBounds() const { return mRenderBounds; } + + Point3F getCenter() const { return mBounds.getCenter(); } + + VectorF getSize() const { return VectorF( mBounds.len_x() / 2.0f, + mBounds.len_y() / 2.0f, + mBounds.len_z() / 2.0f ); } + + /// Renders all the billboard batches returning the + /// total billboards rendered. + U32 renderBillboards( GFXPrimitiveBufferHandle& primBuffer ); + + U32 renderShapes( const TSRenderState &rdata, + Frustum *culler, + TSShapeInstance** shapes ); +}; + +void GroundCoverCell::_rebuildVB() +{ + if ( mBillboards.empty() ) + return; + + PROFILE_SCOPE(GroundCover_RebuildVB); + + // The maximum verts we can put in one vertex buffer batch. + const U32 MAX_BILLBOARDS = 0xFFFF / 4; + + // How many batches will we need in total? + const U32 batches = mCeil( (F32)mBillboards.size() / (F32)MAX_BILLBOARDS ); + + // So... how many billboards do we need in + // each batch? We're trying to evenly divide + // the amount across all the VBs. + const U32 batchBB = mBillboards.size() / batches; + + // Init the vertex buffer list to the right size. Any + // VBs already in there will remain unless we're truncating + // the list... those are freed. + mVBs.setSize( batches ); + + // Get the iter to the first billboard. + Vector::const_iterator iter = mBillboards.begin(); + + // Prepare each batch. + U32 bb, remaining = mBillboards.size(); + for ( U32 b = 0; b < batches; b++ ) + { + // Grab a reference to the vb. + VBHandle &vb = mVBs[b]; + + // How many billboards in this batch? + bb = getMin( batchBB, remaining ); + remaining -= bb; + + // Ok... now how many verts is that? + const U32 verts = bb * 4; + + // Create the VB hasn't been created or if its + // too small then resize it. + if ( vb.isNull() || vb->mNumVerts < verts ) + { + PROFILE_START(GroundCover_CreateVB); + vb.set( GFX, verts, GFXBufferTypeStatic ); + PROFILE_END(); + } + + // Fill this puppy! + GCVertex* vertPtr = vb.lock( 0, verts ); + + GFXVertexColor color; + + Vector::const_iterator last = iter + bb; + for ( ; iter != last; iter++ ) + { + const Point3F &position = (*iter).point; + const S32 &type = (*iter).type; + const Point3F &size = (*iter).size; + const F32 &windAmplitude = (*iter).windAmplitude; + GFXVertexColor color = (ColorI)(*iter).lmColor; + U8 *col = (U8 *)const_cast( (const U32 *)color ); + + vertPtr->point = position; + vertPtr->params.x = size.x; + vertPtr->params.y = size.y; + vertPtr->params.z = type; + vertPtr->params.w = 0; + col[3] = 0; + vertPtr->ambient = color; + ++vertPtr; + + vertPtr->point = position; + vertPtr->params.x = size.x; + vertPtr->params.y = size.y; + vertPtr->params.z = type; + vertPtr->params.w = 0; + col[3] = 1; + vertPtr->ambient = color; + ++vertPtr; + + vertPtr->point = position; + vertPtr->params.x = size.x; + vertPtr->params.y = size.y; + vertPtr->params.z = type; + vertPtr->params.w = windAmplitude; + col[3] = 2; + vertPtr->ambient = color; + ++vertPtr; + + vertPtr->point = position; + vertPtr->params.x = size.x; + vertPtr->params.y = size.y; + vertPtr->params.z = type; + vertPtr->params.w = windAmplitude; + col[3] = 3; + vertPtr->ambient = color; + ++vertPtr; + } + + vb.unlock(); + } +} + +U32 GroundCoverCell::renderShapes( const TSRenderState &rdata, + Frustum *culler, + TSShapeInstance** shapes ) +{ + MatrixF worldMat; + TSShapeInstance* shape; + Point3F camVector; + F32 dist; + F32 invScale; + + const SceneState *state = rdata.getSceneState(); + + U32 totalRendered = 0; + + Vector::const_iterator iter = mShapes.begin(); + for ( ; iter != mShapes.end(); iter++ ) + { + // Grab a reference here once. + const Placement& inst = (*iter); + + // If we were pass a culler then us it to test the shape world box. + if ( culler && !culler->intersects( inst.worldBox ) ) + continue; + + shape = shapes[ inst.type ]; + + camVector = inst.point - state->getDiffuseCameraPosition(); + dist = getMax( camVector.len(), 0.01f ); + + worldMat.set( EulerF(0, 0, inst.rotation), inst.point ); + + // TSShapeInstance::render() uses the + // world matrix for the RenderInst. + worldMat.scale( inst.size ); + GFX->setWorldMatrix( worldMat ); + + // Obey the normal screen space lod metrics. The shapes should + // be tuned to lod out quickly for ground cover. + // + // Note: The profile doesn't indicate that lod selection is + // very expensive... in fact its less than 1/10th of the cost + // of the render() call below. + PROFILE_START(GroundCover_RenderShapes_SelectDetail); + + invScale = (1.0f/getMax(getMax(inst.size.x,inst.size.y),inst.size.z)); + shape->setDetailFromDistance( state, dist * invScale ); + + PROFILE_END(); // GroundCover_RenderShapes_SelectDetail + + // Note: This is the most expensive call of this loop. We + // need to rework the render call completely to optimize it. + PROFILE_START(GroundCover_RenderShapes_Render); + + shape->render( rdata ); + + PROFILE_END(); // GroundCover_RenderShapes_Render + + totalRendered++; + } + + return totalRendered; +} + +U32 GroundCoverCell::renderBillboards( GFXPrimitiveBufferHandle& primBuffer ) +{ + if ( mDirty ) + { + _rebuildVB(); + mDirty = false; + } + + // Do we have anything to render? + if ( mBillboards.size() == 0 || mVBs.empty() ) + return 0; + + // TODO: Maybe add support for non-facing billboards + // with random rotations and optional crosses. We could + // stick them into the buffer after the normal billboards, + // then change shader consts. + // + + // Setup the primitive buffer once. + GFX->setPrimitiveBuffer( primBuffer ); + + // Draw each batch. + U32 remaining = mBillboards.size(); + const U32 batches = mVBs.size(); + const U32 batchBB = remaining / batches; + + for ( U32 b = 0; b < batches; b++ ) + { + // Grab a reference to the vb. + VBHandle &vb = mVBs[b]; + + // How many billboards in this batch? + U32 bb = getMin( batchBB, remaining ); + remaining -= bb; + + // Setup and render it! + GFX->setVertexBuffer( vb ); + GFX->drawIndexedPrimitive( GFXTriangleList, + 0, + 0, + bb * 4, + 0, + bb * 2 ); + } + + return mBillboards.size(); +} + + +U32 GroundCover::smStatRenderedCells = 0; +U32 GroundCover::smStatRenderedBillboards = 0; +U32 GroundCover::smStatRenderedBatches = 0; +U32 GroundCover::smStatRenderedShapes = 0; +U32 GroundCover::smLastState = 0; +F32 GroundCover::smQualityScale = 1.0f; + + +GroundCover::GroundCover() +{ + mTypeMask |= StaticObjectType; + mNetFlags.set( Ghostable | ScopeAlways ); + + mRadius = 200.0f; + mZOffset = 0.0f; + mFadeRadius = 50.0f; + mShapeCullRadius = 75.0f; + mReflectRadiusScale = 0.25f; + + mGridSize = 7; + + // By initializing this to a big value we + // ensure we warp on first render. + mGridIndex.set( S32_MAX, S32_MAX ); + + mMaxPlacement = 1000; + mLastPlacementCount = 0; + + mDebugRenderCells = false; + mDebugNoBillboards = false; + mDebugNoShapes = false; + mDebugLockFrustum = false; + + mRandomSeed = 1; + + mTextureName = NULL; + + mMaxBillboardTiltAngle = 90.0f; + + // TODO: This really doesn't belong here... we need a + // real wind system for Torque scenes. This data + // would be part of a global scene wind or area wind + // emitter. + // + // Tom Spilman - 10/16/2007 + + mWindGustLength = 20.0f; + mWindGustFrequency = 0.5f; + mWindGustStrength = 0.5f; + mWindDirection.set( 1.0f, 0.0f ); + mWindTurbulenceFrequency = 1.2f; + mWindTurbulenceStrength = 0.125f; + + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + { + mProbability[i] = 0.0f; + + mSizeMin[i] = 1.0f; + mSizeMax[i] = 1.0f; + mSizeExponent[i] = 1.0f; + + mWindScale[i] = 1.0f; + + mMaxSlope[i] = 0.0f; + + mMinElevation[i] = -99999.0f; + mMaxElevation[i] = 99999.0f; + + mLayer[i] = -1; + mInvertLayer[i] = false; + + mMinClumpCount[i] = 1; + mMaxClumpCount[i] = 1; + mClumpCountExponent[i] = 1.0f; + mClumpRadius[i] = 1.0f; + + mBillboardRects[i].point.set( 0.0f, 0.0f ); + mBillboardRects[i].extent.set( 1.0f, 1.0f ); + + mShapeFilenames[i] = NULL; + mShapeInstances[i] = NULL; + + mBillboardAspectScales[i] = 1.0f; + + mNormalizedProbability[i] = 0.0f; + } +} + +IMPLEMENT_CO_NETOBJECT_V1(GroundCover); + +void GroundCover::initPersistFields() +{ + addGroup( "GroundCover General" ); + + addField( "radius", TypeF32, Offset( mRadius, GroundCover ) ); + addField( "dissolveRadius", TypeF32, Offset( mFadeRadius, GroundCover ) ); + addField( "reflectScale", TypeF32, Offset( mReflectRadiusScale, GroundCover ) ); + + addField( "gridSize", TypeS32, Offset( mGridSize, GroundCover ) ); + addField( "zOffset", TypeF32, Offset( mZOffset, GroundCover ) ); + + addField( "seed", TypeS32, Offset( mRandomSeed, GroundCover ) ); + addField( "maxElements", TypeS32, Offset( mMaxPlacement, GroundCover ) ); + + addField( "billboardTexture", TypeFilename, Offset( mTextureName, GroundCover ) ); + addField( "maxBillboardTiltAngle", TypeF32, Offset( mMaxBillboardTiltAngle, GroundCover ) ); + addField( "shapeCullRadius", TypeF32, Offset( mShapeCullRadius, GroundCover ) ); + + addArray( "Types", MAX_COVERTYPES ); + + addField( "billboardUVs", TypeRectF, Offset( mBillboardRects, GroundCover ), MAX_COVERTYPES ); + + addField( "shapeFilename", TypeFilename, Offset( mShapeFilenames, GroundCover ), MAX_COVERTYPES ); + + addField( "layer", TypeS32, Offset( mLayer, GroundCover ), MAX_COVERTYPES ); + + addField( "invertLayer", TypeBool, Offset( mInvertLayer, GroundCover ), MAX_COVERTYPES ); + + addField( "probability", TypeF32, Offset( mProbability, GroundCover ), MAX_COVERTYPES ); + + addField( "sizeMin", TypeF32, Offset( mSizeMin, GroundCover ), MAX_COVERTYPES ); + + addField( "sizeMax", TypeF32, Offset( mSizeMax, GroundCover ), MAX_COVERTYPES ); + + addField( "sizeExponent", TypeF32, Offset( mSizeExponent, GroundCover ), MAX_COVERTYPES ); + + addField( "windScale", TypeF32, Offset( mWindScale, GroundCover ), MAX_COVERTYPES ); + + addField( "maxSlope", TypeF32, Offset( mMaxSlope, GroundCover ), MAX_COVERTYPES ); + + addField( "minElevation", TypeF32, Offset( mMinElevation, GroundCover ), MAX_COVERTYPES ); + + addField( "maxElevation", TypeF32, Offset( mMaxElevation, GroundCover ), MAX_COVERTYPES ); + + addField( "minClumpCount", TypeS32, Offset( mMinClumpCount, GroundCover ), MAX_COVERTYPES ); + + addField( "maxClumpCount", TypeS32, Offset( mMaxClumpCount, GroundCover ), MAX_COVERTYPES ); + + addField( "clumpExponent", TypeF32, Offset( mClumpCountExponent, GroundCover ), MAX_COVERTYPES ); + + addField( "clumpRadius", TypeF32, Offset( mClumpRadius, GroundCover ), MAX_COVERTYPES ); + + endArray( "Types" ); + + endGroup( "GroundCover General" ); + + addGroup( "GroundCover Wind" ); + + addField( "windDirection", TypePoint2F, Offset( mWindDirection, GroundCover ) ); + + addField( "windGustLength", TypeF32, Offset( mWindGustLength, GroundCover ) ); + addField( "windGustFrequency", TypeF32, Offset( mWindGustFrequency, GroundCover ) ); + addField( "windGustStrength", TypeF32, Offset( mWindGustStrength, GroundCover ) ); + + addField( "windTurbulenceFrequency", TypeF32, Offset( mWindTurbulenceFrequency, GroundCover ) ); + addField( "windTurbulenceStrength", TypeF32, Offset( mWindTurbulenceStrength, GroundCover ) ); + + endGroup( "GroundCover Wind" ); + + addGroup( "GroundCover Debug" ); + + addField( "lockFrustum", TypeBool, Offset( mDebugLockFrustum, GroundCover ) ); + addField( "renderCells", TypeBool, Offset( mDebugRenderCells, GroundCover ) ); + addField( "noBillboards", TypeBool, Offset( mDebugNoBillboards, GroundCover ) ); + addField( "noShapes", TypeBool, Offset( mDebugNoShapes, GroundCover ) ); + + endGroup( "GroundCover Debug" ); + + Parent::initPersistFields(); +} + +void GroundCover::consoleInit() +{ + Con::addVariable( "$GroundCover::renderedCells", TypeS32, &smStatRenderedCells ); + Con::addVariable( "$GroundCover::renderedBillboards", TypeS32, &smStatRenderedBillboards ); + Con::addVariable( "$GroundCover::renderedBatches", TypeS32, &smStatRenderedBatches ); + Con::addVariable( "$GroundCover::renderedShapes", TypeS32, &smStatRenderedShapes ); +} + +bool GroundCover::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // We don't use any bounds. + mObjBox.minExtents.set(-1e5, -1e5, -1e5); + mObjBox.maxExtents.set( 1e5, 1e5, 1e5); + resetWorldBox(); + + // Prepare some client side things. + if ( isClientObject() ) + { + if ( !_initShader() ) + { + Con::warnf( "GroundCover - failed to find and load billboard shader GroundCoverShaderData!" ); + return false; + } + + LightManager::smActivateSignal.notify( this, &GroundCover::_onLMActivate ); + + _initShapes(); + + // Hook ourselves up to get terrain change notifications. + TerrainBlock::smUpdateSignal.notify( this, &GroundCover::onTerrainUpdated ); + } + + addToScene(); + + return true; +} + +void GroundCover::onRemove() +{ + Parent::onRemove(); + + _deleteCells(); + _deleteShapes(); + + if ( isClientObject() ) + { + TerrainBlock::smUpdateSignal.remove( this, &GroundCover::onTerrainUpdated ); + LightManager::smActivateSignal.remove( this, &GroundCover::_onLMActivate ); + + mBBShader = NULL; + mStateBlock = NULL; + mConstBuffer = NULL; + mLightInfoTarget = NULL; + } + + removeFromScene(); +} + +void GroundCover::inspectPostApply() +{ + Parent::inspectPostApply(); + + // We flag all the parameters as changed because + // we're feeling lazy and there is not a good way + // to track what parameters changed. + // + // TODO: Add a mask bit option to addField() and/or + // addGroup() which is passed to inspectPostApply + // for detection of changed elements. + // + setMaskBits(U32(-1) ); +} + +U32 GroundCover::packUpdate( NetConnection *connection, U32 mask, BitStream *stream ) +{ + Parent::packUpdate( connection, mask, stream ); + + if (stream->writeFlag(mask & InitialUpdateMask)) + { + // TODO: We could probably optimize a few of these + // based on reasonable units at some point. + + stream->write( mRadius ); + stream->write( mZOffset ); + stream->write( mFadeRadius ); + stream->write( mShapeCullRadius ); + stream->write( mReflectRadiusScale ); + stream->write( mGridSize ); + stream->write( mRandomSeed ); + stream->write( mMaxPlacement ); + stream->write( mMaxBillboardTiltAngle ); + + stream->writeString( mTextureName ); + + stream->write( mWindDirection.x ); + stream->write( mWindDirection.y ); + stream->write( mWindGustLength ); + stream->write( mWindGustFrequency ); + stream->write( mWindGustStrength ); + stream->write( mWindTurbulenceFrequency ); + stream->write( mWindTurbulenceStrength ); + + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + { + stream->write( mProbability[i] ); + stream->write( mSizeMin[i] ); + stream->write( mSizeMax[i] ); + stream->write( mSizeExponent[i] ); + stream->write( mWindScale[i] ); + + stream->write( mMaxSlope[i] ); + + stream->write( mMinElevation[i] ); + stream->write( mMaxElevation[i] ); + + stream->write( mLayer[i] ); + stream->writeFlag( mInvertLayer[i] ); + + stream->write( mMinClumpCount[i] ); + stream->write( mMaxClumpCount[i] ); + stream->write( mClumpCountExponent[i] ); + stream->write( mClumpRadius[i] ); + + stream->write( mBillboardRects[i].point.x ); + stream->write( mBillboardRects[i].point.y ); + stream->write( mBillboardRects[i].extent.x ); + stream->write( mBillboardRects[i].extent.y ); + + stream->writeString( mShapeFilenames[i] ); + } + + stream->writeFlag( mDebugRenderCells ); + stream->writeFlag( mDebugNoBillboards ); + stream->writeFlag( mDebugNoShapes ); + stream->writeFlag( mDebugLockFrustum ); + } + + return 0; +} + +void GroundCover::unpackUpdate( NetConnection *connection, BitStream *stream ) +{ + Parent::unpackUpdate( connection, stream ); + + if (stream->readFlag()) + { + stream->read( &mRadius ); + stream->read( &mZOffset ); + stream->read( &mFadeRadius ); + stream->read( &mShapeCullRadius ); + stream->read( &mReflectRadiusScale ); + stream->read( &mGridSize ); + stream->read( &mRandomSeed ); + stream->read( &mMaxPlacement ); + stream->read( &mMaxBillboardTiltAngle ); + + mTextureName = stream->readSTString(); + + stream->read( &mWindDirection.x ); + stream->read( &mWindDirection.y ); + stream->read( &mWindGustLength ); + stream->read( &mWindGustFrequency ); + stream->read( &mWindGustStrength ); + stream->read( &mWindTurbulenceFrequency ); + stream->read( &mWindTurbulenceStrength ); + + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + { + stream->read( &mProbability[i] ); + stream->read( &mSizeMin[i] ); + stream->read( &mSizeMax[i] ); + stream->read( &mSizeExponent[i] ); + stream->read( &mWindScale[i] ); + + stream->read( &mMaxSlope[i] ); + + stream->read( &mMinElevation[i] ); + stream->read( &mMaxElevation[i] ); + + stream->read( &mLayer[i] ); + mInvertLayer[i] = stream->readFlag(); + + stream->read( &mMinClumpCount[i] ); + stream->read( &mMaxClumpCount[i] ); + stream->read( &mClumpCountExponent[i] ); + stream->read( &mClumpRadius[i] ); + + stream->read( &mBillboardRects[i].point.x ); + stream->read( &mBillboardRects[i].point.y ); + stream->read( &mBillboardRects[i].extent.x ); + stream->read( &mBillboardRects[i].extent.y ); + + mShapeFilenames[i] = stream->readSTString(); + } + + mDebugRenderCells = stream->readFlag(); + mDebugNoBillboards = stream->readFlag(); + mDebugNoShapes = stream->readFlag(); + mDebugLockFrustum = stream->readFlag(); + + // We have no way to easily know what changed, so by clearing + // the cells we force a reinit and regeneration of the cells. + // It's sloppy, but it works for now. + _freeCells(); + } +} + +bool GroundCover::_initShader() +{ + ShaderData *shaderData; + if ( !Sim::findObject( "GroundCoverShaderData", shaderData ) ) + return false; + + // Get the lightinfo conditioner macros. + Vector macros; + mLightInfoTarget = MatTextureTarget::findTargetByName( "lightinfo" ); + if ( mLightInfoTarget ) + mLightInfoTarget->getTargetShaderMacros( ¯os ); + + // Get the shader. + mBBShader = shaderData->getShader( macros ); + if ( !mBBShader ) + return false; + + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setZReadWrite( true ); + desc.zWriteEnable = true; + desc.setAlphaTest( true, GFXCmpGreater, 84 ); + desc.samplersDefined = true; + desc.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + desc.samplers[1] = GFXSamplerStateDesc::getClampLinear(); + desc.samplers[2] = GFXSamplerStateDesc::getWrapLinear(); + mStateBlock = GFX->createStateBlock( desc ); + + mConstBuffer = mBBShader->allocConstBuffer(); + shaderData->mapSamplerNames( mConstBuffer ); + + mModelViewProjectConst = mBBShader->getShaderConstHandle( "$modelViewProj" ); + mCamPosConst = mBBShader->getShaderConstHandle( "$camPos" ); + mCamRightConst = mBBShader->getShaderConstHandle( "$camRight" ); + mCamUpConst = mBBShader->getShaderConstHandle( "$camUp" ); + mFadeParamConst = mBBShader->getShaderConstHandle( "$fadeParams" ); + mWindDirConst = mBBShader->getShaderConstHandle( "$windDir" ); + mGustInfoConst = mBBShader->getShaderConstHandle( "$gustInfo" ); + mTurbInfoConst = mBBShader->getShaderConstHandle( "$turbInfo" ); + mTypeRectsConst = mBBShader->getShaderConstHandle( "$typeRects" ); + mLightRTConst = mBBShader->getShaderConstHandle( "$lightRT" ); + mTextureConst = mBBShader->getShaderConstHandle( "$diffuseMap" ); + + return true; +} + +void GroundCover::_initShapes() +{ + _deleteShapes(); + + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + { + if ( !mShapeFilenames[i] || !mShapeFilenames[i][0] ) + continue; + + // Load the shape. + Resource shape = ResourceManager::get().load(mShapeFilenames[i]); + if ( !(bool)shape ) + { + Con::warnf( "GroundCover::_initShapes() unable to load shape: %s", mShapeFilenames[i] ); + continue; + } + + if ( isClientObject() && !shape->preloadMaterialList(shape.getPath()) && NetConnection::filesWereDownloaded() ) + { + Con::warnf( "GroundCover::_initShapes() material preload failed for shape: %s", mShapeFilenames[i] ); + continue; + } + + // Create the shape instance. + mShapeInstances[i] = new TSShapeInstance( shape, isClientObject() ); + } +} + +void GroundCover::_deleteShapes() +{ + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + { + delete mShapeInstances[i]; + mShapeInstances[i] = NULL; + } +} + +void GroundCover::_deleteCells() +{ + // Delete the allocation list. + for ( S32 i=0; i < mAllocCellList.size(); i++ ) + delete mAllocCellList[i]; + mAllocCellList.clear(); + + // Zero out the rest of the stuff. + _freeCells(); +} + +void GroundCover::_freeCells() +{ + // Zero the grid and scratch space. + mCellGrid.clear(); + mScratchGrid.clear(); + + // Compact things... remove excess allocated cells. + const U32 maxCells = mGridSize * mGridSize; + if ( mAllocCellList.size() > maxCells ) + { + for ( S32 i=maxCells; i < mAllocCellList.size(); i++ ) + delete mAllocCellList[i]; + mAllocCellList.setSize( maxCells ); + } + + // Move all the alloced cells into the free list. + mFreeCellList.clear(); + mFreeCellList.merge( mAllocCellList ); + + // Release the primitive buffer. + mPrimBuffer = NULL; +} + +void GroundCover::_recycleCell( GroundCoverCell* cell ) +{ + mFreeCellList.push_back( cell ); +} + +void GroundCover::_initialize( U32 cellCount, U32 cellPlacementCount ) +{ + // Cleanup everything... we're starting over. + _freeCells(); + _deleteShapes(); + mTexture.free(); + + // Nothing to do without a count! + if ( cellPlacementCount == 0 ) + return; + + // Reset the grid sizes. + mCellGrid.setSize( cellCount ); + dMemset( mCellGrid.address(), 0, mCellGrid.memSize() ); + mScratchGrid.setSize( cellCount ); + + // Reload the texture. + if ( mTextureName && mTextureName[0] ) + mTexture.set( mTextureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - mTexture (line %d)", __FUNCTION__, __LINE__) );//&GFXMaterialStaticDXT5Profile ); + + // Grab the texture aspect ratio. + F32 texAspect = 1.0f; + if ( !mTexture.isNull() ) + texAspect = mTexture.getWidth() / (F32)mTexture.getHeight(); + + // Rebuild the texture aspect scales for each type. + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + { + if ( mBillboardRects[i].len_y() > 0.0f ) + mBillboardAspectScales[i] = texAspect * ( mBillboardRects[i].len_x() / mBillboardRects[i].len_y() ); + else + mBillboardAspectScales[i] = 0.0f; + } + + // Load the shapes again. + _initShapes(); + + // Set the primitive buffer up for the maximum placement in a cell. + mPrimBuffer.set( GFX, cellPlacementCount * 6, 0, GFXBufferTypeStatic ); + U16 *idxBuff; + mPrimBuffer.lock(&idxBuff); + for ( U32 i=0; i < cellPlacementCount; i++ ) + { + // + // The vertex pattern in the VB for each + // billboard is as follows... + // + // 0----1 + // |\ | + // | \ | + // | \ | + // | \| + // 3----2 + // + // We setup the index order below to ensure + // sequential, cache friendly, access. + // + U32 offset = i * 4; + idxBuff[i*6+0] = 0 + offset; + idxBuff[i*6+1] = 1 + offset; + idxBuff[i*6+2] = 2 + offset; + idxBuff[i*6+3] = 2 + offset; + idxBuff[i*6+4] = 3 + offset; + idxBuff[i*6+5] = 0 + offset; + } + mPrimBuffer.unlock(); + + // Generate the normalised probability. + F32 total = 0.0f; + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + { + // If the element isn't gonna render... then + // set the probability to zero. + if ( mShapeInstances[i] == NULL && mBillboardAspectScales[i] <= 0.0001f ) + { + mNormalizedProbability[i] = 0.0f; + } + else + { + mNormalizedProbability[i] = mProbability[i]; + + total += mProbability[i]; + } + } + if ( total > 0.0f ) + { + for ( S32 i=0; i < MAX_COVERTYPES; i++ ) + mNormalizedProbability[i] /= total; + } +} + +void GroundCover::_findTerrainCallback( SceneObject *obj, void *param ) +{ + Vector *terrains = reinterpret_cast*>( param ); + + TerrainBlock *terrain = dynamic_cast( obj ); + if ( terrain ) + terrains->push_back( terrain ); +} + +GroundCoverCell* GroundCover::_generateCell( const Point2I& index, + const Box3F& bounds, + U32 placementCount, + S32 randSeed ) +{ + PROFILE_SCOPE(GroundCover_GenerateCell); + + Vector terrainBlocks; + getContainer()->findObjects( bounds, TerrainObjectType, _findTerrainCallback, &terrainBlocks ); + if ( terrainBlocks.empty() ) + return NULL; + + // Grab a free cell or allocate a new one. + GroundCoverCell* cell; + if ( mFreeCellList.empty() ) + { + cell = new GroundCoverCell(); + mAllocCellList.push_back( cell ); + } + else + { + cell = mFreeCellList.last(); + mFreeCellList.pop_back(); + } + + cell->mDirty = true; + cell->mIndex = index; + cell->mBounds = bounds; + + Point3F pos( 0, 0, 0 ); + + Box3F renderBounds = bounds; + Point3F point; + Point3F normal; + F32 h; + Point2F cp, uv; + bool hit; + GroundCoverCell::Placement p; + F32 rotation; + F32 size; + F32 sizeExponent; + Point2I lpos; + //F32 value; + VectorF right; + U8 matIndex; + bool firstElem = true; + + TerrainBlock *terrainBlock = NULL; + + cell->mBillboards.clear(); + cell->mBillboards.reserve( placementCount ); + cell->mShapes.clear(); + cell->mShapes.reserve( placementCount ); + + F32 terrainSquareSize, + oneOverTerrainLength, + oneOverTerrainSquareSize; + const GBitmap* terrainLM = NULL; + + // The RNG that we'll use in generation. + MRandom rand( 0 ); + + // We process one type at a time. + for ( U32 type=0; type < MAX_COVERTYPES; type++ ) + { + // How many cover elements do we need to generate for this type? + const S32 typeCount = mNormalizedProbability[type] * (F32)placementCount; + if ( typeCount <= 0 ) + continue; + + // Grab the terrain layer for this type. + /* + const TerrainDataLayer* dataLayer = NULL; + const bool typeInvertLayer = mInvertLayer[type]; + if ( mLayer[type] > -1 ) + { + dataLayer = mTerrainBlock->getDataLayer( mLayer[type] ); + if ( dataLayer ) + { + // Do an initial check to see if we can place any place anything + // at all... if the layer area for this element is empty then + // there is nothing more to do. + + RectI area( (S32)mFloor( ( bounds.minExtents.x - pos.x ) * oneOverTerrainSquareSize ), + (S32)mFloor( ( bounds.minExtents.y - pos.y ) * oneOverTerrainSquareSize ), + (S32)mCeil( ( bounds.maxExtents.x - pos.x ) * oneOverTerrainSquareSize ), + (S32)mCeil( ( bounds.maxExtents.y - pos.y ) * oneOverTerrainSquareSize ) ); + area.extent -= area.point; + + if ( dataLayer->testFill( area, typeInvertLayer ? 255 : 0 ) ) + continue; + } + } + + // If the layer is not inverted and we have no data + // then we have nothing to draw. + if ( !typeInvertLayer && !dataLayer ) + continue; + */ + + // We set the seed we were passed which is based on this grids position + // in the world and add the type value. This keeps changes to one type + // from effecting the outcome of the others. + rand.setSeed( randSeed + type ); + + // Setup for doing clumps. + S32 clumps = 0; + Point2F clumpCenter(0.0f, 0.0f); + const S32 clumpMin = getMax( 1, (S32)mMinClumpCount[type] ); + F32 clumpExponent; + + // We mult this by -1 each billboard we make then use + // it to scale the billboard x axis to flip them. This + // essentially gives us twice the variation for free. + F32 flipBB = -1.0f; + + // Precompute a few other type specific values. + const F32 typeSizeRange = mSizeMax[type] - mSizeMin[type]; + const F32 typeMaxSlope = mMaxSlope[type]; + const F32 typeMaxElevation = mMaxElevation[type]; + const F32 typeMinElevation = mMinElevation[type]; + const bool typeIsShape = mShapeInstances[ type ] != NULL; + const Box3F typeShapeBounds = typeIsShape ? mShapeInstances[ type ]->getShape()->bounds : Box3F(); + const F32 typeWindScale = mWindScale[type]; + const S32 typeLayer = mLayer[type]; + + // We can set this once here... all the placements for this are the same. + p.type = type; + p.windAmplitude = typeWindScale; + p.lmColor.set(1.0f,1.0f,1.0f); + + // Generate all the cover elements for this type. + for ( S32 i=0; i < typeCount; i++ ) + { + // Do all the other random things here first as to not + // disturb the random sequence if the terrain geometry + // or cover layers change. + + // Get the random position. + cp.set( rand.randF(), rand.randF() ); + + // Prepare the clump info. + clumpExponent = mClampF( mPow( rand.randF(), mClumpCountExponent[type] ), 0.0f, 1.0f ); + if ( clumps <= 0 ) + { + // We're starting a new clump. + clumps = ( clumpMin + mFloor( ( mMaxClumpCount[type] - clumpMin ) * clumpExponent ) ) - 1; + cp.set( bounds.minExtents.x + cp.x * bounds.len_x(), + bounds.minExtents.y + cp.y * bounds.len_y() ); + clumpCenter = cp; + } + else + { + clumps--; + cp.set( clumpCenter.x - ( ( cp.x - 0.5f ) * mClumpRadius[type] ), + clumpCenter.y - ( ( cp.y - 0.5f ) * mClumpRadius[type] ) ); + } + + // Which terrain do I place on? + if ( terrainBlocks.size() == 1 ) + terrainBlock = terrainBlocks.first(); + else + { + for ( U32 i = 0; i < terrainBlocks.size(); i++ ) + { + TerrainBlock *terrain = terrainBlocks[i]; + const Box3F &terrBounds = terrain->getWorldBox(); + + if ( cp.x < terrBounds.minExtents.x || cp.x > terrBounds.maxExtents.x || + cp.y < terrBounds.minExtents.y || cp.y > terrBounds.maxExtents.y ) + continue; + + terrainBlock = terrain; + break; + } + } + + // This should only happen if the generation went off + // the edge of the terrain blocks. + if ( !terrainBlock ) + continue; + + terrainLM = terrainBlock->getLightMap(); + pos = terrainBlock->getPosition(); + + terrainSquareSize = (F32)terrainBlock->getSquareSize(); + oneOverTerrainLength = 1.0f / terrainBlock->getWorldBlockSize(); + oneOverTerrainSquareSize = 1.0f / terrainSquareSize; + + // The size is calculated using an exponent to control + // the frequency between min and max sizes. + sizeExponent = mClampF( mPow( rand.randF(), mSizeExponent[type] ), 0.0f, 1.0f ); + size = mSizeMin[type] + ( typeSizeRange * sizeExponent ); + + // Generate a random z rotation. + rotation = rand.randF() * M_2PI_F; + + // Flip the billboard now for the next generation. + flipBB *= -1.0f; + + PROFILE_START( GroundCover_TerrainRayCast ); + hit = terrainBlock->getNormalHeightMaterial( Point2F( cp.x - pos.x, cp.y - pos.y ), + &normal, &h, &matIndex ); + + // TODO: When did we loose the world space elevation when + // getting the terrain height? + h += pos.z; + + PROFILE_END(); // GroundCover_TerrainRayCast + if ( !hit || h > typeMaxElevation || h < typeMinElevation || + ( typeLayer != -1 && matIndex != typeLayer ) ) + continue; + + // Do we need to check slope? + if ( !mIsZero( typeMaxSlope ) ) + { + if (mAcos(normal.z) > mDegToRad(typeMaxSlope)) + continue; + } + + point.set( cp.x, cp.y, h ); + p.point = point; + p.rotation = rotation; + + // Grab the terrain lightmap color at this position. + // + // TODO: Can't we remove this test? The terrain + // lightmap should never be null... NEVER! + // + if ( terrainLM ) + { + // TODO: We could probably call terrainLM->getBits() + // once outside the loop then pre-calculate the scalar + // for converting a world position into a lexel... + // avoiding the extra protections inside of sampleTexel(). + + uv.x = (point.x + pos.x) * oneOverTerrainLength; + uv.y = (point.y + pos.y) * oneOverTerrainLength; + uv.x -= mFloor(uv.x); + uv.y -= mFloor(uv.y); + p.lmColor = terrainLM->sampleTexel(uv.x,uv.y); + } + + // Put it into the right list by type. + // + // TODO: Could we break up the generation into + // two separate loops for shapes and billboards + // and gain performance? + // + if ( typeIsShape ) + { + // TODO: Convert the size into a real size... not scale! + + // TODO: We could probably cache the shape bounds + // into a primitive array and avoid the double pointer + // dereference per placement. + + p.size.set( size, size, size ); + p.worldBox = typeShapeBounds; + p.worldBox.minExtents *= size; + p.worldBox.maxExtents *= size; + p.worldBox.minExtents += point; + p.worldBox.maxExtents += point; + + cell->mShapes.push_back( p ); + } + else + { + p.size.y = size; + p.size.x = size * flipBB * mBillboardAspectScales[type]; + p.worldBox.maxExtents = p.worldBox.minExtents = point; + + cell->mBillboards.push_back( p ); + } + + // Update the render bounds. + if ( firstElem ) + { + renderBounds = p.worldBox; + firstElem = false; + } + else + { + renderBounds.extend( p.worldBox.minExtents ); + renderBounds.extend( p.worldBox.maxExtents ); + } + + } // for ( S32 i=0; i < typeCount; i++ ) + + } // for ( U32 type=0; type < NumCoverTypes; type++ ) + + + cell->mRenderBounds = renderBounds; + cell->mBounds.minExtents.z = renderBounds.minExtents.z; + cell->mBounds.maxExtents.z = renderBounds.maxExtents.z; + + return cell; +} + +void GroundCover::onTerrainUpdated( U32 flags, TerrainBlock *tblock, const Point2I& min, const Point2I& max ) +{ + if ( isServerObject() ) + return; + + // Free all the cells if we've gotten a lightmap update. + if ( flags & TerrainBlock::LightmapUpdate ) + { + _freeCells(); + return; + } + + // TODO: EmptyUpdate doesn't work yet... fix editor/terrain. + + // If this is a height or opacity update only clear + // the cells that have changed. + if ( flags & TerrainBlock::HeightmapUpdate || + flags & TerrainBlock::LayersUpdate || + flags & TerrainBlock::EmptyUpdate ) + { + // Convert the min and max into world space. + const F32 size = tblock->getSquareSize(); + const Point3F pos = tblock->getPosition(); + + // TODO: I don't think this works right with tiling! + Box3F dirty( F32( min.x * size ) + pos.x, F32( min.y * size ) + pos.y, 0.0f, + F32( max.x * size ) + pos.x, F32( max.y * size ) + pos.y, 0.0f ); + + // Now free any cells that overlap it! + for ( S32 i = 0; i < mCellGrid.size(); i++ ) + { + GroundCoverCell* cell = mCellGrid[ i ]; + if ( !cell ) + continue; + + const Box3F& bounds = cell->getBounds(); + dirty.minExtents.z = bounds.minExtents.z; + dirty.maxExtents.z = bounds.maxExtents.z; + if ( bounds.isOverlapped( dirty ) ) + { + mCellGrid[ i ] = NULL; + _recycleCell( cell ); + } + } + } +} + +void GroundCover::_updateCoverGrid( const Frustum &culler ) +{ + PROFILE_SCOPE( GroundCover_UpdateCoverGrid ); + + mGridSize = getMax( mGridSize, (U32)2 ); + + // How many cells in the grid? + const U32 cells = mGridSize * mGridSize; + + // Whats the max placement count for each cell considering + // the grid size and quality scale LOD value. + const S32 placementCount = ( (F32)mMaxPlacement * smQualityScale ) / F32( mGridSize * mGridSize ); + + // If the cell grid isn't sized or the placement count + // changed (most likely because of quality lod) then we + // need to initialize the system again. + if ( mCellGrid.empty() || placementCount != mLastPlacementCount ) + { + _initialize( cells, placementCount ); + mLastPlacementCount = placementCount; + } + + // Without a count... we don't function at all. + if ( placementCount == 0 ) + return; + + // Clear the scratch grid. + dMemset( mScratchGrid.address(), 0, mScratchGrid.memSize() ); + + // Calculate the normal cell size here. + const F32 cellSize = ( mRadius * 2.0f ) / (F32)(mGridSize - 1); + + // Figure out the root index of the new grid based on the camera position. + Point2I index( (S32)mFloor( ( culler.getPosition().x - mRadius ) / cellSize ), + (S32)mFloor( ( culler.getPosition().y - mRadius ) / cellSize ) ); + + // Figure out the cell shift between the old and new grid positions. + Point2I shift = mGridIndex - index; + + // If we've shifted more than one in either axis then we've warped. + bool didWarp = shift.x > 1 || shift.x < -1 || + shift.y > 1 || shift.y < -1 ? true : false; + + // Go thru the grid shifting each cell we find and + // placing them in the scratch grid. + for ( S32 i = 0; i < mCellGrid.size(); i++ ) + { + GroundCoverCell* cell = mCellGrid[ i ]; + if ( !cell ) + continue; + + // Whats our new index? + Point2I newIndex = cell->shiftIndex( shift ); + + // Is this cell outside of the new grid? + if ( newIndex.x < 0 || newIndex.x >= mGridSize || + newIndex.y < 0 || newIndex.y >= mGridSize ) + { + _recycleCell( cell ); + continue; + } + + // Place the cell in the scratch grid. + mScratchGrid[ ( newIndex.y * mGridSize ) + newIndex.x ] = cell; + } + + // Get the terrain elevation range for setting the default cell bounds. + F32 terrainMinHeight = -5000.0f, + terrainMaxHeight = 5000.0f; + + // Go thru the scratch grid copying each cell back to the + // cell grid and creating new cells as needed. + // + // By limiting ourselves to only one new cell generation per + // update we're lowering the performance hiccup during movement + // without getting into the complexity of threading. The delay + // in generation is rarely noticeable in normal play. + // + // The only caveat is that we need to generate the entire visible + // grid when we warp. + U32 cellsGenerated = 0; + for ( S32 i = 0; i < mScratchGrid.size(); i++ ) + { + GroundCoverCell* cell = mScratchGrid[ i ]; + if ( !cell && ( cellsGenerated == 0 || didWarp ) ) + { + // Get the index point of this new cell. + S32 y = i / mGridSize; + S32 x = i - ( y * mGridSize ); + Point2I newIndex = index + Point2I( x, y ); + + // What will be the world placement bounds for this cell. + Box3F bounds; + bounds.minExtents.set( newIndex.x * cellSize, newIndex.y * cellSize, terrainMinHeight ); + bounds.maxExtents.set( bounds.minExtents.x + cellSize, bounds.minExtents.y + cellSize, terrainMaxHeight ); + + if ( !mCuller.intersects( bounds ) ) + { + mCellGrid[ i ] = NULL; + continue; + } + + // We need to allocate a new cell. + // + // TODO: This is the expensive call and where we should optimize. In + // particular the next best optimization would be to take advantage of + // multiple cores so that we can generate all the cells in one update. + // + // Instead of generating the cell here we would allocate a cell and stick + // it into a thread safe queue (maybe lockless) as well as the mCellGrid. + // Once all were allocated we would do something like this... + // + // TorqueParallelProcess( cellsToGenerateQueue, _generateCell ); + // + // Internally this function would pass the queue to some global pre-allocated + // worker threads which are locked to a particular core. While the main + // thread waits for the worker threads to finish it will process cells itself. + // + + cell = _generateCell( newIndex - index, + bounds, + placementCount, + mRandomSeed + mAbs( newIndex.x ) + mAbs( newIndex.y ) ); + + // Increment our generation count. + if ( cell ) + ++cellsGenerated; + } + + mCellGrid[ i ] = cell; + } + + // Store the new grid index. + mGridIndex = index; +} + +bool GroundCover::prepRenderImage( + SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseZoneState ) +{ + // TODO: Educate myself... WTF does this accomplish? + // Is it just a protection against double rendering? + if ( isLastState( state, stateKey ) ) + return false; + + RenderPassManager *defaultPass = NULL; + Sim::findObject( "DiffuseRenderPassManager", defaultPass ); + const bool isShadowPass = gClientSceneGraph->getRenderPass() != defaultPass; + if ( isShadowPass ) + return false; + + // Reset the rendering stats on a new scene state! + // + // TODO: This doesn't work with multiple ground cover + // elements in a scene... fix me! + // + if ( stateKey != smLastState ) + { + smStatRenderedCells = 0; + smStatRenderedBillboards = 0; + smStatRenderedBatches = 0; + smStatRenderedShapes = 0; + + smLastState = stateKey; + } + + setLastState( state, stateKey ); + + // Check portal visibility. + // + // TODO: Make sure that the ground cover stops rendering + // if you're inside a zoned interior. + // + if ( !state->isObjectRendered( this ) ) + return false; + + GFXTransformSaver saver; + + // Setup the frustum culler. + if ( mCuller.getPosition().isZero() || !mDebugLockFrustum ) + mCuller = state->getFrustum(); + + // Update the cells, but only during the diffuse pass... we don't + // want cell generation to thrash when the reflection camera + // position doesn't match the diffuse camera! + if ( state->isDiffusePass() ) + _updateCoverGrid( mCuller ); + + // Prepare for billboard rendering later. + if ( !state->isShadowPass() && mBBShader ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &GroundCover::_renderBillboards ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + + // Prepare to render the grid shapes. + PROFILE_SCOPE(GroundCover_RenderShapes); + + // Set up our TS render state. + TSRenderState rdata; + rdata.setSceneState( state ); + + // TODO: Add a special fade out for DTS? + mCuller.setFarDist( mShapeCullRadius ); + + for ( S32 i = 0; i < mCellGrid.size(); i++ ) + { + GroundCoverCell* cell = mCellGrid[ i ]; + if ( !cell || mDebugNoShapes ) + continue; + + U32 clipMask = mCuller.testPlanes( cell->getRenderBounds(), Frustum::PlaneMaskAll ); + if ( clipMask == -1 ) + continue; + + smStatRenderedCells++; + + // Render the shapes in this cell... only pass the culler if the + // cell wasn't fully within the frustum. + smStatRenderedShapes += cell->renderShapes( + rdata, + clipMask != 0 ? &mCuller : NULL, + mShapeInstances ); + } + + return true; +} + + +void GroundCover::_renderBillboards( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat ) +{ + // Skip special materials. + if ( overrideMat ) + return; + + PROFILE_SCOPE(GroundCover_RenderBillboards); + + // Prepare to render. + GFXTransformSaver saver; + MatrixF world = GFX->getWorldMatrix(); + + if(mZOffset != 0) + { + const F32 &zOffset = mZOffset; + + const F32 *b = world; + world[8] = b[8] + zOffset * b[12]; + world[9] = b[9] + zOffset * b[13]; + world[10]= b[10]+ zOffset * b[14]; + world[11]= b[11]+ zOffset * b[15]; + + GFX->setWorldMatrix( world ); + } + + // Set the projection and world transform info. + MatrixF proj = GFX->getProjectionMatrix(); + proj.mul( world ); + mConstBuffer->set( mModelViewProjectConst, proj ); + + F32 cullScale = 1.0f; + if ( state->isReflectPass() ) + cullScale = mReflectRadiusScale; + + // Prepare to render some batches. + GFX->setShader( mBBShader ); + GFX->setStateBlock( mStateBlock ); + GFX->setShaderConstBuffer( mConstBuffer ); + + // Get the data we need from the camera matrix. + const MatrixF &camMat = state->getCameraTransform(); + Point3F camRight, camUp, camDir, camPos; + camMat.getColumn( 0, &camRight ); + camMat.getColumn( 1, &camDir ); + camMat.getColumn( 2, &camUp ); + camMat.getColumn( 3, &camPos ); + + // Limit the camera up vector to keep the billboards + // from leaning too far down into the terrain. + VectorF lookDir( camDir.x, camDir.y, 0.0f ); + F32 angle; + if ( !lookDir.isZero() ) + { + lookDir.normalize(); + angle = mAcos( mDot( camUp, lookDir ) ); + } + else + { + angle = camDir.z < 0.0f ? 0.0f : ( M_PI_F / 2.0f ); + } + + const F32 maxBillboardTiltRads = mDegToRad( mMaxBillboardTiltAngle ); + if ( angle < (M_PI_F / 2.0f) - maxBillboardTiltRads ) + { + QuatF quat( AngAxisF( camRight, maxBillboardTiltRads ) ); + quat.mulP( VectorF( 0.0f, 0.0f, 1.0f ), &camUp ); + } + + // Setup the shader consts for the camera. + mConstBuffer->set( mCamPosConst, camPos ); + mConstBuffer->set( mCamRightConst, camRight ); + mConstBuffer->set( mCamUpConst, camUp ); + + // Setup the fade parameters. + Point2F fadeParams( mFadeRadius * cullScale, mRadius * cullScale ); + mConstBuffer->set( mFadeParamConst, fadeParams ); + + // Setup the texture. + //S32 idx = mTextureConst->getSamplerRegister(); + //if ( idx != -1 ) + GFX->setTexture( 0, mTexture ); + + // Pass the cover rects. + AlignedArray rectData( MAX_COVERTYPES, sizeof( Point4F ), (U8*)mBillboardRects, false ); + mConstBuffer->set( mTypeRectsConst, rectData ); + + const F32 simTime = Sim::getCurrentTime() * 0.001f; + + // Pass the wind parameters. + mConstBuffer->set( mWindDirConst, mWindDirection ); + mConstBuffer->set( mGustInfoConst, Point3F( mWindGustLength, + mWindGustFrequency * simTime, + mWindGustStrength ) ); + mConstBuffer->set( mTurbInfoConst, Point2F( mWindTurbulenceFrequency * simTime, + mWindTurbulenceStrength ) ); + + /// Are we in advanced lighting mode? + if ( mLightRTConst->isValid() && mLightInfoTarget ) + { + GFXTextureObject *texObject = mLightInfoTarget->getTargetTexture( 0 ); + GFX->setTexture( 1, texObject ); + + const Point3I &targetSz = texObject->getSize(); + const RectI &targetVp = mLightInfoTarget->getTargetViewport(); + Point4F rtParams; + + ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams); + + mConstBuffer->set( mLightRTConst, rtParams ); + } + + // Set the far distance for billboards. + mCuller.setFarDist( mRadius ); + + // We need to view direction for light and sphere selection! + for ( S32 i = 0; i < mCellGrid.size(); i++ ) + { + GroundCoverCell* cell = mCellGrid[ i ]; + if ( !cell || mDebugNoBillboards ) + continue; + + if ( !mCuller.intersects( cell->getRenderBounds() ) ) + continue; + + // Render! + smStatRenderedBillboards += cell->renderBillboards( mPrimBuffer ); + + // TODO: Eventually we may allow more than one billboard batch + // per cell and we need to account for that! + smStatRenderedBatches++; + } + + // We must make a separate pass rendering the debug stuff + // as the draw util will clobber the render state! + if ( mDebugRenderCells ) + { + // Used for debug drawing. + GFXDrawUtil* drawer = GFX->getDrawUtil(); + drawer->clearBitmapModulation(); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setBlend( true ); + desc.fillMode = GFXFillWireframe; + + for ( S32 i = 0; i < mCellGrid.size(); i++ ) + { + GroundCoverCell* cell = mCellGrid[ i ]; + if ( !cell || ( cell->mBillboards.size() + cell->mShapes.size() ) == 0 ) + continue; + + if ( !mCuller.intersects( cell->getRenderBounds() ) ) + continue; + + drawer->drawCube( desc, cell->getSize(), cell->getCenter(), ColorI( 0, 255, 0 ) ); + } + } +} + + + +ConsoleStaticMethod( GroundCover, setQualityScale, F32, 2, 2, + "GroundCover.setQualityScale( F32 scale )\n" + "Sets the global ground cover LOD scalar which controls " + "the percentage of the maximum designed cover to put down. " + "It scales both rendering cost and placement CPU performance. " + "Returns the actual value set." ) +{ + return GroundCover::setQualityScale( dAtof( argv[1] ) ); +} + +ConsoleStaticMethod( GroundCover, getQualityScale, F32, 1, 1, + "GroundCover.getQualityScale()\n" + "Returns the global quality scale. See GroundCover::setQualityScale()..." ) +{ + return GroundCover::getQualityScale(); +} diff --git a/T3D/fx/groundCover.h b/T3D/fx/groundCover.h new file mode 100644 index 0000000..756c65f --- /dev/null +++ b/T3D/fx/groundCover.h @@ -0,0 +1,352 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GROUNDCOVER_H_ +#define _GROUNDCOVER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFX_GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif + +class TerrainBlock; +class GroundCoverCell; +class TSShapeInstance; + +/// +#define MAX_COVERTYPES 8 + + +GFXDeclareVertexFormat( GCVertex ) +{ + Point3F point; + + // .rgb = ambient + // .a = corner index + GFXVertexColor ambient; + + // .x = size x + // .y = size y + // .z = type + // .w = wind amplitude + Point4F params; +}; + + +class GroundCover : public SceneObject +{ +public: + GroundCover(); + + DECLARE_CONOBJECT(GroundCover); + + static void consoleInit(); + static void initPersistFields(); + + bool onAdd(); + void onRemove(); + void inspectPostApply(); + + // Network + U32 packUpdate( NetConnection *, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *, BitStream *stream ); + + // Rendering + bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseState ); + + // Editor + void onTerrainUpdated( U32 flags, TerrainBlock *tblock, const Point2I& min, const Point2I& max ); + + /// Sets the global ground cover LOD scalar which controls + /// the percentage of the maximum designed cover to put down. + /// It scales both rendering cost and placement CPU performance. + /// Returns the actual value set. + static F32 setQualityScale( F32 scale ) { return smQualityScale = mClampF( scale, 0.0f, 1.0f ); } + + /// Returns the current quality scale... see above. + static F32 getQualityScale() { return smQualityScale; } + +protected: + friend class GroundCoverCell; + + typedef SceneObject Parent; + + enum MaskBits + { + InitialUpdateMask = Parent::NextFreeMask, + TerrainBlockMask = InitialUpdateMask << 1, + NextFreeMask = TerrainBlockMask << 1 + }; + + /// This RNG seed is saved and sent to clients + /// for generating the same cover. + S32 mRandomSeed; + + /// This is the outer generation radius from + /// the current camera position. + F32 mRadius; + + // Offset along the Z axis to render the ground cover. + F32 mZOffset; + + /// This is less than or equal to mRadius and + /// defines when fading of cover elements begins. + F32 mFadeRadius; + + /// This is the distance at which DTS elements are + /// completely culled out. + F32 mShapeCullRadius; + + /// This is used to scale the various culling radii + /// when rendering a reflection... typically for water. + F32 mReflectRadiusScale; + + /// This is the number of cells per axis in the grid. + U32 mGridSize; + + typedef Vector CellVector; + + /// This is the allocator for GridCell chunks. + CellVector mAllocCellList; + CellVector mFreeCellList; + + /// This is the grid of active cells. + CellVector mCellGrid; + + /// This is a scratch grid used while updating + /// the cell grid. + CellVector mScratchGrid; + + /// This is the index to the first grid cell. + Point2I mGridIndex; + + /// The maximum amount of cover elements to include in + /// the grid at any one time. The actual amount may be + /// less than this based on randomization. + S32 mMaxPlacement; + + /// Used to detect changes in cell placement count from + /// the global quality scale so we can regen the cells. + S32 mLastPlacementCount; + + /// Used for culling cells to update and render. + Frustum mCuller; + + /// The shader used to render the ground cover billboards. + GFXShaderRef mBBShader; + + /// + GFXStateBlockRef mStateBlock; + + /// + GFXShaderConstBufferRef mConstBuffer; + GFXShaderConstHandle *mModelViewProjectConst; + GFXShaderConstHandle *mCamRightConst; + GFXShaderConstHandle *mCamUpConst; + GFXShaderConstHandle *mCamPosConst; + GFXShaderConstHandle *mFadeParamConst; + GFXShaderConstHandle *mWindDirConst; + GFXShaderConstHandle *mGustInfoConst; + GFXShaderConstHandle *mTurbInfoConst; + GFXShaderConstHandle *mTypeRectsConst; + GFXShaderConstHandle *mLightRTConst; + GFXShaderConstHandle *mTextureConst; + + /// + MatTextureTargetRef mLightInfoTarget; + + /// Debug parameter for displaying the grid cells. + bool mDebugRenderCells; + + /// Debug parameter for turning off billboard rendering. + bool mDebugNoBillboards; + + /// Debug parameter for turning off shape rendering. + bool mDebugNoShapes; + + /// Debug parameter for locking the culling frustum which + /// will freeze the cover generation. + bool mDebugLockFrustum; + + /// Stat for number of rendered cells. + static U32 smStatRenderedCells; + + /// Stat for number of rendered billboards. + static U32 smStatRenderedBillboards; + + /// Stat for number of rendered billboard batches. + static U32 smStatRenderedBatches; + + /// Stat for number of rendered shapes. + static U32 smStatRenderedShapes; + + /// Used to detect when to reset the stats. + static U32 smLastState; + + /// The global ground cover LOD scalar which controls + /// the percentage of the maximum amount of cover to put + /// down. It scales both rendering cost and placement + /// CPU performance. + static F32 smQualityScale; + + /// The name of the texture atlas of the cover billboards. + StringTableEntry mTextureName; + + /// The texture atlas of the cover billboards. + GFXTexHandle mTexture; + + /// This is the maximum amout of degrees the billboard will + /// tilt down to match the camera. + F32 mMaxBillboardTiltAngle; + + /// The probability of one cover type verses another. + F32 mProbability[MAX_COVERTYPES]; + + /// The minimum random size for each cover type. + F32 mSizeMin[MAX_COVERTYPES]; + + /// The maximum random size of this cover type. + F32 mSizeMax[MAX_COVERTYPES]; + + /// An exponent used to bias between the minimum + /// and maximum random sizes. + F32 mSizeExponent[MAX_COVERTYPES]; + + /// The wind effect scale. + F32 mWindScale[MAX_COVERTYPES]; + + /// The maximum slope angle in degrees for placement. + F32 mMaxSlope[MAX_COVERTYPES]; + + /// The minimum world space elevation for placement. + F32 mMinElevation[MAX_COVERTYPES]; + + /// The maximum world space elevation for placement. + F32 mMaxElevation[MAX_COVERTYPES]; + + /// The integer terrain data layer or -1 to not + /// test placement against one. + S32 mLayer[MAX_COVERTYPES]; + + /// Inverts the data layer test making the + /// layer an exclusion mask. + bool mInvertLayer[MAX_COVERTYPES]; + + /// The minimum amount of elements in a clump. + S32 mMinClumpCount[MAX_COVERTYPES]; + + /// The maximum amount of elements in a clump. + S32 mMaxClumpCount[MAX_COVERTYPES]; + + /// An exponent used to bias between the minimum + /// and maximum clump counts for a particular clump. + F32 mClumpCountExponent[MAX_COVERTYPES]; + + /// The maximum clump radius. + F32 mClumpRadius[MAX_COVERTYPES]; + + /// The billboard atlas texture uvs. + RectF mBillboardRects[MAX_COVERTYPES]; + + /// This is a cached array of billboard aspect scales + /// used to avoid some calculations when generating cells. + F32 mBillboardAspectScales[MAX_COVERTYPES]; + + /// The cover shape filenames. + StringTableEntry mShapeFilenames[MAX_COVERTYPES]; + + /// The cover shape instances. + TSShapeInstance* mShapeInstances[MAX_COVERTYPES]; + + /// This is the same as mProbability, but normalized for use + /// during the cover placement process. + F32 mNormalizedProbability[MAX_COVERTYPES]; + + /// A shared primitive buffer setup for drawing the maximum amount + /// of billboards you could possibly have in a single cell. + GFXPrimitiveBufferHandle mPrimBuffer; + + /// The length in meters between peaks in the wind gust. + F32 mWindGustLength; + + /// Controls how often the wind gust peaks per second. + F32 mWindGustFrequency; + + /// The maximum distance in meters that the peak wind + /// gust will displace an element. + F32 mWindGustStrength; + + /// The direction of the wind. + Point2F mWindDirection; + + /// Controls the overall rapidity of the wind turbulence. + F32 mWindTurbulenceFrequency; + + /// The maximum distance in meters that the turbulence can + /// displace a ground cover element. + F32 mWindTurbulenceStrength; + + /// @see ShaderData::addReloadCallback + void _onLMActivate( const char*, bool activate ) + { + if ( activate && mBBShader ) + { + mBBShader = NULL; + _initShader(); + } + } + + bool _initShader(); + + void _initShapes(); + + void _deleteShapes(); + + /// Called when GroundCover parameters are changed and + /// things need to be reinitialized to continue. + void _initialize( U32 cellCount, U32 cellPlacementCount ); + + /// Updates the cover grid by removing cells that + /// have fallen outside of mRadius and adding new + /// ones that have come into view. + void _updateCoverGrid( const Frustum &culler ); + + /// Clears the cell grid, moves all the allocated cells to + /// the free list, and deletes excess free cells. + void _freeCells(); + + /// Clears the cell grid and deletes all the free cells. + void _deleteCells(); + + /// Returns a cell to the free list. + void _recycleCell( GroundCoverCell* cell ); + + /// Generates a new cell using the recycle list when possible. + GroundCoverCell* _generateCell( const Point2I& index, + const Box3F& bounds, + U32 placementCount, + S32 randSeed ); + + void _renderBillboards( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat ); + + /// Used to gather a list of TerrainBlocks from findObjects. + static void _findTerrainCallback( SceneObject *obj, void *param ); +}; + +#endif // _GROUNDCOVER_H_ diff --git a/T3D/fx/lightning.cpp b/T3D/fx/lightning.cpp new file mode 100644 index 0000000..be52a98 --- /dev/null +++ b/T3D/fx/lightning.cpp @@ -0,0 +1,1190 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/fx/lightning.h" + +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "math/mathIO.h" +#include "core/stream/bitStream.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" +#include "math/mRandom.h" +#include "math/mathUtils.h" +//#include "platform/platformAudio.h" +#include "terrain/terrData.h" +#include "sceneGraph/sceneGraph.h" +#include "T3D/player.h" +#include "T3D/camera.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "gfx/primBuilder.h" + + +IMPLEMENT_CO_DATABLOCK_V1(LightningData); +IMPLEMENT_CO_NETOBJECT_V1(Lightning); + +MRandomLCG sgLightningRand; + +ConsoleMethod( Lightning, warningFlashes, void, 2, 2, "") +{ + if (object->isServerObject()) object->warningFlashes(); +} + +ConsoleMethod( Lightning, strikeRandomPoint, void, 2, 2, "") +{ + if (object->isServerObject()) object->strikeRandomPoint(); +} + +ConsoleMethod( Lightning, strikeObject, void, 3, 3, "(ShapeBase id)") +{ + S32 id = dAtoi(argv[2]); + ShapeBase* pSB; + + if (object->isServerObject() && Sim::findObject(id, pSB)) + object->strikeObject(pSB); +} + +S32 QSORT_CALLBACK cmpSounds(const void* p1, const void* p2) +{ + U32 i1 = *((const S32*)p1); + U32 i2 = *((const S32*)p2); + + if (i1 < i2) { + return 1; + } else if (i1 > i2) { + return -1; + } else { + return 0; + } +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +class LightningStrikeEvent : public NetEvent +{ + typedef NetEvent Parent; + + public: + enum EventType { + WarningFlash = 0, + Strike = 1, + TargetedStrike = 2, + + TypeMin = WarningFlash, + TypeMax = TargetedStrike + }; + enum Constants { + PositionalBits = 10 + }; + + Point2F mStart; + SimObjectPtr mTarget; + + Lightning* mLightning; + + // Set by unpack... + public: + S32 mClientId; + + public: + LightningStrikeEvent(); + ~LightningStrikeEvent(); + + void pack(NetConnection*, BitStream*); + void write(NetConnection*, BitStream*){} + void unpack(NetConnection*, BitStream*); + void process(NetConnection*); + + DECLARE_CONOBJECT(LightningStrikeEvent); +}; +IMPLEMENT_CO_CLIENTEVENT_V1(LightningStrikeEvent); + +LightningStrikeEvent::LightningStrikeEvent() +{ + mLightning = NULL; + mTarget = NULL; +} + +LightningStrikeEvent::~LightningStrikeEvent() +{ + +} + +void LightningStrikeEvent::pack(NetConnection* con, BitStream* stream) +{ + if(!mLightning) + { + stream->writeFlag(false); + return; + } + S32 id = con->getGhostIndex(mLightning); + if(id == -1) + { + stream->writeFlag(false); + return; + } + stream->writeFlag(true); + stream->writeRangedU32(U32(id), 0, NetConnection::MaxGhostCount); + stream->writeFloat(mStart.x, PositionalBits); + stream->writeFloat(mStart.y, PositionalBits); + + if( mTarget ) + { + S32 ghostIndex = con->getGhostIndex(mTarget); + if (ghostIndex == -1) + stream->writeFlag(false); + else + { + stream->writeFlag(true); + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + } + } + else + stream->writeFlag( false ); +} + +void LightningStrikeEvent::unpack(NetConnection* con, BitStream* stream) +{ + if(!stream->readFlag()) + return; + S32 mClientId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mLightning = NULL; + NetObject* pObject = con->resolveGhost(mClientId); + if (pObject) + mLightning = dynamic_cast(pObject); + + mStart.x = stream->readFloat(PositionalBits); + mStart.y = stream->readFloat(PositionalBits); + + if( stream->readFlag() ) + { + // target id + S32 mTargetID = stream->readRangedU32(0, NetConnection::MaxGhostCount); + + NetObject* pObject = con->resolveGhost(mTargetID); + if( pObject != NULL ) + { + mTarget = dynamic_cast(pObject); + } + if( bool(mTarget) == false ) + { + Con::errorf(ConsoleLogEntry::General, "LightningStrikeEvent::unpack: could not resolve target ghost properly"); + } + + } + +} + +void LightningStrikeEvent::process(NetConnection*) +{ + if (mLightning) + mLightning->processEvent(this); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +LightningData::LightningData() +{ + strikeSound = NULL; + strikeSoundID = -1; + + dMemset( strikeTextureNames, 0, sizeof( strikeTextureNames ) ); + dMemset( strikeTextures, 0, sizeof( strikeTextures ) ); + + U32 i; + for (i = 0; i < MaxThunders; i++) { + thunderSounds[i] = NULL; + thunderSoundIds[i] = -1; + } +} + +LightningData::~LightningData() +{ + +} + + +//-------------------------------------------------------------------------- +void LightningData::initPersistFields() +{ + addField("strikeSound", TypeSFXProfilePtr, Offset(strikeSound, LightningData)); + addField("thunderSounds", TypeSFXProfilePtr, Offset(thunderSounds, LightningData), MaxThunders); + addField("strikeTextures", TypeString, Offset(strikeTextureNames, LightningData), MaxTextures); + + Parent::initPersistFields(); +} + + +//-------------------------------------------------------------------------- +bool LightningData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + for (U32 i = 0; i < MaxThunders; i++) { + if (!thunderSounds[i] && thunderSoundIds[i] != -1) { + if (Sim::findObject(thunderSoundIds[i], thunderSounds[i]) == false) + Con::errorf(ConsoleLogEntry::General, "LightningData::onAdd: Invalid packet, bad datablockId(sound: %d", thunderSounds[i]); + } + } + + if( !strikeSound && strikeSoundID != -1 ) + { + if( Sim::findObject( strikeSoundID, strikeSound ) == false) + Con::errorf(ConsoleLogEntry::General, "LightningData::onAdd: Invalid packet, bad datablockId(sound: %d", strikeSound); + } + + return true; +} + + +bool LightningData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + dQsort(thunderSounds, MaxThunders, sizeof(SFXProfile*), cmpSounds); + for (numThunders = 0; numThunders < MaxThunders && thunderSounds[numThunders] != NULL; numThunders++) { + // + } + + if (server == false) + { + for (U32 i = 0; i < MaxTextures; i++) + { + if (strikeTextureNames[i][0]) + strikeTextures[i] = GFXTexHandle(strikeTextureNames[i], &GFXDefaultStaticDiffuseProfile, avar("%s() - strikeTextures[%d] (line %d)", __FUNCTION__, i, __LINE__)); + } + } + + + return true; +} + + +//-------------------------------------------------------------------------- +void LightningData::packData(BitStream* stream) +{ + Parent::packData(stream); + + U32 i; + for (i = 0; i < MaxThunders; i++) { + if (stream->writeFlag(thunderSounds[i] != NULL)) { + stream->writeRangedU32(thunderSounds[i]->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + } + for (i = 0; i < MaxTextures; i++) { + stream->writeString(strikeTextureNames[i]); + } + + if( stream->writeFlag( strikeSound != NULL) ) + { + stream->writeRangedU32( strikeSound->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } +} + +void LightningData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + U32 i; + for (i = 0; i < MaxThunders; i++) { + if (stream->readFlag()) + thunderSoundIds[i] = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + thunderSoundIds[i] = -1; + } + for (i = 0; i < MaxTextures; i++) { + strikeTextureNames[i] = stream->readSTString(); + } + + if (stream->readFlag()) + strikeSoundID = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + strikeSoundID = -1; +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +Lightning::Lightning() +{ + mNetFlags.set(Ghostable|ScopeAlways); + mTypeMask |= StaticObjectType|EnvironmentObjectType; + + mLastThink = 0; + + mStrikeListHead = NULL; + mThunderListHead = NULL; + + strikesPerMinute = 12; + strikeWidth = 2.5; + chanceToHitTarget = 0.5f; + strikeRadius = 20.0f; + boltStartRadius = 20.0f; + color.set( 1.0f, 1.0f, 1.0f, 1.0f ); + fadeColor.set( 0.1f, 0.1f, 1.0f, 1.0f ); + useFog = true; + + setScale( VectorF( 512.0f, 512.0f, 300.0f ) ); +} + +Lightning::~Lightning() +{ + // +} + +//-------------------------------------------------------------------------- +void Lightning::initPersistFields() +{ + addGroup("Strikes"); // MM: Added Group Header. + addField("strikesPerMinute",TypeS32, Offset(strikesPerMinute, Lightning)); + addField("strikeWidth", TypeF32, Offset(strikeWidth, Lightning)); + addField("strikeRadius", TypeF32, Offset(strikeRadius, Lightning)); + endGroup("Strikes"); // MM: Added Group Footer. + + addGroup("Colors"); // MM: Added Group Header. + addField("color", TypeColorF, Offset(color, Lightning)); + addField("fadeColor", TypeColorF, Offset(fadeColor, Lightning)); + endGroup("Colors"); // MM: Added Group Footer. + + addGroup("Bolts"); // MM: Added Group Header. + addField("chanceToHitTarget", TypeF32, Offset(chanceToHitTarget, Lightning)); + addField("boltStartRadius", TypeF32, Offset(boltStartRadius, Lightning)); + addField("useFog", TypeBool, Offset(useFog, Lightning)); + endGroup("Bolts"); // MM: Added Group Footer. + + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +bool Lightning::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.minExtents.set( -0.5f, -0.5f, -0.5f ); + mObjBox.maxExtents.set( 0.5f, 0.5f, 0.5f ); + + resetWorldBox(); + addToScene(); + + return true; +} + + +void Lightning::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + + +bool Lightning::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +bool Lightning::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &Lightning::renderObject); + // The Lightning isn't technically foliage but our debug + // effect seems to render best as a Foliage type (translucent, + // renders itself, no sorting) + ri->type = RenderPassManager::RIT_Foliage; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + + +void Lightning::renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + if (mLightningSB.isNull()) + { + GFXStateBlockDesc desc; + desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendOne); + desc.setCullMode(GFXCullNone); + desc.zWriteEnable = false; + desc.samplersDefined = true; + desc.samplers[0].magFilter = GFXTextureFilterLinear; + desc.samplers[0].minFilter = GFXTextureFilterLinear; + desc.samplers[0].addressModeU = GFXAddressWrap; + desc.samplers[0].addressModeV = GFXAddressWrap; + + mLightningSB = GFX->createStateBlock(desc); + + } + + GFX->setStateBlock(mLightningSB); + + + Strike* walk = mStrikeListHead; + while (walk != NULL) + { + GFX->setTexture(0, mDataBlock->strikeTextures[0]); + + for( U32 i=0; i<3; i++ ) + { + if( walk->bolt[i].isFading ) + { + F32 alpha = 1.0f - walk->bolt[i].percentFade; + if( alpha < 0.0f ) alpha = 0.0f; + PrimBuild::color4f( fadeColor.red, fadeColor.green, fadeColor.blue, alpha ); + } + else + { + PrimBuild::color4f( color.red, color.green, color.blue, color.alpha ); + } + walk->bolt[i].render( state->getCameraPosition() ); + } + + walk = walk->next; + } + + //GFX->setZWriteEnable(true); + //GFX->setAlphaTestEnable(false); + //GFX->setAlphaBlendEnable(false); +} + +void Lightning::scheduleThunder(Strike* newStrike) +{ + AssertFatal(isClientObject(), "Lightning::scheduleThunder: server objects should not enter this version of the function"); + + // If no thunder sounds, don't schedule anything! + if (mDataBlock->numThunders == 0) + return; + + GameConnection* connection = GameConnection::getConnectionToServer(); + if (connection) { + MatrixF cameraMatrix; + + if (connection->getControlCameraTransform(0, &cameraMatrix)) { + Point3F worldPos; + cameraMatrix.getColumn(3, &worldPos); + + worldPos.x -= newStrike->xVal; + worldPos.y -= newStrike->yVal; + worldPos.z = 0.0f; + + F32 dist = worldPos.len(); + F32 t = dist / 330.0f; + + // Ok, we need to schedule a random strike sound t secs in the future... + // + if (t <= 0.03f) { + // If it's really close, just play it... + U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); + SFX->playOnce(mDataBlock->thunderSounds[thunder]); + } else { + Thunder* pThunder = new Thunder; + pThunder->tRemaining = t; + pThunder->next = mThunderListHead; + mThunderListHead = pThunder; + } + } + } +} + + +//-------------------------------------------------------------------------- +void Lightning::processTick(const Move* move) +{ + Parent::processTick(move); + + if (isServerObject()) { + S32 msBetweenStrikes = (S32)(60.0 / strikesPerMinute * 1000.0); + + mLastThink += TickMs; + if( mLastThink > msBetweenStrikes ) + { + strikeRandomPoint(); + mLastThink -= msBetweenStrikes; + } + } +} + +void Lightning::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); +} + +void Lightning::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + Strike** pWalker = &mStrikeListHead; + while (*pWalker != NULL) { + Strike* pStrike = *pWalker; + + for( U32 i=0; i<3; i++ ) + { + pStrike->bolt[i].update( dt ); + } + + pStrike->currentAge += dt; + if (pStrike->currentAge > pStrike->deathAge) { + *pWalker = pStrike->next; + delete pStrike; + } else { + pWalker = &((*pWalker)->next); + } + } + + Thunder** pThunderWalker = &mThunderListHead; + while (*pThunderWalker != NULL) { + Thunder* pThunder = *pThunderWalker; + + pThunder->tRemaining -= dt; + if (pThunder->tRemaining <= 0.0f) { + *pThunderWalker = pThunder->next; + delete pThunder; + + // Play the sound... + U32 thunder = sgLightningRand.randI(0, mDataBlock->numThunders - 1); + SFX->playOnce(mDataBlock->thunderSounds[thunder]); + } else { + pThunderWalker = &((*pThunderWalker)->next); + } + } +} + + +//-------------------------------------------------------------------------- +void Lightning::processEvent(LightningStrikeEvent* pEvent) +{ + AssertFatal(pEvent->mStart.x >= 0.0f && pEvent->mStart.x <= 1.0f, "Out of bounds coord!"); + + Strike* pStrike = new Strike; + + Point3F strikePoint; + strikePoint.zero(); + + if( pEvent->mTarget ) + { + Point3F objectCenter; + pEvent->mTarget->getObjBox().getCenter( &objectCenter ); + objectCenter.convolve( pEvent->mTarget->getScale() ); + pEvent->mTarget->getTransform().mulP( objectCenter ); + + strikePoint = objectCenter; + } + else + { + strikePoint.x = pEvent->mStart.x; + strikePoint.y = pEvent->mStart.y; + strikePoint *= mObjScale; + strikePoint += getPosition(); + strikePoint += Point3F( -mObjScale.x * 0.5f, -mObjScale.y * 0.5f, 0.0f ); + + RayInfo rayInfo; + Point3F start = strikePoint; + start.z = mObjScale.z * 0.5f + getPosition().z; + strikePoint.z += -mObjScale.z * 0.5f; + bool rayHit = gClientContainer.castRay( start, strikePoint, + (STATIC_COLLISION_MASK | WaterObjectType), + &rayInfo); + if( rayHit ) + { + strikePoint.z = rayInfo.point.z; + } + else + { + strikePoint.z = pStrike->bolt[0].findHeight( strikePoint, mSceneManager ); + } + } + + pStrike->xVal = strikePoint.x; + pStrike->yVal = strikePoint.y; + + pStrike->deathAge = 1.6f; + pStrike->currentAge = 0.0f; + pStrike->next = mStrikeListHead; + + for( U32 i=0; i<3; i++ ) + { + F32 randStart = boltStartRadius; + F32 height = mObjScale.z * 0.5f + getPosition().z; + pStrike->bolt[i].startPoint.set( pStrike->xVal + gRandGen.randF( -randStart, randStart ), pStrike->yVal + gRandGen.randF( -randStart, randStart ), height ); + pStrike->bolt[i].endPoint = strikePoint; + pStrike->bolt[i].width = strikeWidth; + pStrike->bolt[i].numMajorNodes = 10; + pStrike->bolt[i].maxMajorAngle = 30.0f; + pStrike->bolt[i].numMinorNodes = 4; + pStrike->bolt[i].maxMinorAngle = 15.0f; + pStrike->bolt[i].generate(); + pStrike->bolt[i].startSplits(); + pStrike->bolt[i].lifetime = 1.0f; + pStrike->bolt[i].fadeTime = 0.2f; + pStrike->bolt[i].renderTime = gRandGen.randF(0.0f, 0.25f); + } + + mStrikeListHead = pStrike; + + scheduleThunder(pStrike); + + MatrixF trans(true); + trans.setPosition( strikePoint ); + + if (mDataBlock->strikeSound) + { + SFX->playOnce(mDataBlock->strikeSound, &trans ); + } + +} + +void Lightning::warningFlashes() +{ + AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!"); + + + SimGroup* pClientGroup = Sim::getClientGroup(); + for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) { + NetConnection* nc = static_cast(*itr); + if (nc != NULL) + { + LightningStrikeEvent* pEvent = new LightningStrikeEvent; + pEvent->mLightning = this; + + nc->postNetEvent(pEvent); + } + } +} + +void Lightning::strikeRandomPoint() +{ + AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!"); + + + Point3F strikePoint( gRandGen.randF( 0.0f, 1.0f ), gRandGen.randF( 0.0f, 1.0f ), 0.0f ); + + // check if an object is within target range + + strikePoint *= mObjScale; + strikePoint += getPosition(); + strikePoint += Point3F( -mObjScale.x * 0.5f, -mObjScale.y * 0.5f, 0.0f ); + + Box3F queryBox; + F32 boxWidth = strikeRadius * 2.0f; + + queryBox.minExtents.set( -boxWidth * 0.5f, -boxWidth * 0.5f, -mObjScale.z * 0.5f ); + queryBox.maxExtents.set( boxWidth * 0.5f, boxWidth * 0.5f, mObjScale.z * 0.5f ); + queryBox.minExtents += strikePoint; + queryBox.maxExtents += strikePoint; + + SimpleQueryList sql; + getContainer()->findObjects(queryBox, DAMAGEABLE_MASK, + SimpleQueryList::insertionCallback, &sql); + + SceneObject *highestObj = NULL; + F32 highestPnt = 0.0f; + + for( U32 i = 0; i < sql.mList.size(); i++ ) + { + Point3F objectCenter; + sql.mList[i]->getObjBox().getCenter(&objectCenter); + objectCenter.convolve(sql.mList[i]->getScale()); + sql.mList[i]->getTransform().mulP(objectCenter); + + // check if object can be struck + + RayInfo rayInfo; + Point3F start = objectCenter; + start.z = mObjScale.z * 0.5f + getPosition().z; + Point3F end = objectCenter; + end.z = -mObjScale.z * 0.5f + getPosition().z; + bool rayHit = gServerContainer.castRay( start, end, + (0xFFFFFFFF), + &rayInfo); + + if( rayHit && rayInfo.object == sql.mList[i] ) + { + if( !highestObj ) + { + highestObj = sql.mList[i]; + highestPnt = objectCenter.z; + continue; + } + + if( objectCenter.z > highestPnt ) + { + highestObj = sql.mList[i]; + highestPnt = objectCenter.z; + } + } + + + } + + // hah haaaaa, we have a target! + SceneObject *targetObj = NULL; + if( highestObj ) + { + F32 chance = gRandGen.randF(); + if( chance <= chanceToHitTarget ) + { + Point3F objectCenter; + highestObj->getObjBox().getCenter(&objectCenter); + objectCenter.convolve(highestObj->getScale()); + highestObj->getTransform().mulP(objectCenter); + + bool playerInWarmup = false; + Player *playerObj = dynamic_cast< Player * >(highestObj); + if( playerObj ) + { + if( !playerObj->getControllingClient() ) + { + playerInWarmup = true; + } + } + + if( !playerInWarmup ) + { + applyDamage( objectCenter, VectorF( 0.0f, 0.0f, 1.0f ), highestObj ); + targetObj = highestObj; + } + } + } + + SimGroup* pClientGroup = Sim::getClientGroup(); + for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) + { + NetConnection* nc = static_cast(*itr); + + LightningStrikeEvent* pEvent = new LightningStrikeEvent; + pEvent->mLightning = this; + + pEvent->mStart.x = strikePoint.x; + pEvent->mStart.y = strikePoint.y; + pEvent->mTarget = targetObj; + + nc->postNetEvent(pEvent); + } + + +} + +//-------------------------------------------------------------------------- +void Lightning::strikeObject(ShapeBase*) +{ + AssertFatal(isServerObject(), "Error, client objects may not initiate lightning!"); + + AssertFatal(false, "Lightning::strikeObject is not implemented."); +} + + +//-------------------------------------------------------------------------- +U32 Lightning::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // Only write data if this is the initial packet or we've been inspected. + if (stream->writeFlag(mask & (InitialUpdateMask | ExtendedInfoMask))) + { + // Initial update + mathWrite(*stream, getPosition()); + mathWrite(*stream, mObjScale); + + stream->write(strikeWidth); + stream->write(chanceToHitTarget); + stream->write(strikeRadius); + stream->write(boltStartRadius); + stream->write(color.red); + stream->write(color.green); + stream->write(color.blue); + stream->write(fadeColor.red); + stream->write(fadeColor.green); + stream->write(fadeColor.blue); + stream->write(useFog); + stream->write(strikesPerMinute); + } + + return retMask; +} + +//-------------------------------------------------------------------------- +void Lightning::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + // Initial update + Point3F pos; + mathRead(*stream, &pos); + setPosition( pos ); + + mathRead(*stream, &mObjScale); + + stream->read(&strikeWidth); + stream->read(&chanceToHitTarget); + stream->read(&strikeRadius); + stream->read(&boltStartRadius); + stream->read(&color.red); + stream->read(&color.green); + stream->read(&color.blue); + stream->read(&fadeColor.red); + stream->read(&fadeColor.green); + stream->read(&fadeColor.blue); + stream->read(&useFog); + stream->read(&strikesPerMinute); + } +} + +//-------------------------------------------------------------------------- +void Lightning::applyDamage( const Point3F& hitPosition, + const Point3F& hitNormal, + SceneObject* hitObject) +{ + if (!isClientObject() && hitObject != NULL) + { + char *posArg = Con::getArgBuffer(64); + char *normalArg = Con::getArgBuffer(64); + + dSprintf(posArg, 64, "%f %f %f", hitPosition.x, hitPosition.y, hitPosition.z); + dSprintf(normalArg, 64, "%f %f %f", hitNormal.x, hitNormal.y, hitNormal.z); + + Con::executef(mDataBlock, "applyDamage", + Con::getIntArg(getId()), + Con::getIntArg(hitObject->getId()), + posArg, + normalArg); + } +} + +//************************************************************************** +// Lightning Bolt +//************************************************************************** +LightningBolt::LightningBolt() +{ + width = 0.1f; + startPoint.zero(); + endPoint.zero(); + chanceOfSplit = 0.0f; + isFading = false; + elapsedTime = 0.0f; + lifetime = 1.0f; + startRender = false; +} + +//-------------------------------------------------------------------------- +// Destructor +//-------------------------------------------------------------------------- +LightningBolt::~LightningBolt() +{ + splitList.clear(); +} + +//-------------------------------------------------------------------------- +// Generate nodes +//-------------------------------------------------------------------------- +void LightningBolt::NodeManager::generateNodes() +{ + F32 overallDist = VectorF( endPoint - startPoint ).magnitudeSafe(); + F32 minDistBetweenNodes = overallDist / (numNodes-1); + F32 maxDistBetweenNodes = minDistBetweenNodes / mCos( maxAngle * M_PI_F / 180.0f ); + + VectorF mainLineDir = endPoint - startPoint; + mainLineDir.normalizeSafe(); + + for( U32 i=0; iisFading = true; + } + i->render( camPos ); + } + +} + +//-------------------------------------------------------------------------- +// Render segment +//-------------------------------------------------------------------------- +void LightningBolt::renderSegment( NodeManager &segment, const Point3F &camPos, bool renderLastPoint ) +{ + + for (U32 i = 0; i < segment.numNodes; i++) + { + Point3F curPoint = segment.nodeList[i].point; + + Point3F nextPoint; + Point3F segDir; + + if( i == (segment.numNodes-1) ) + { + if( renderLastPoint ) + { + segDir = curPoint - segment.nodeList[i-1].point; + } + else + { + continue; + } + } + else + { + nextPoint = segment.nodeList[i+1].point; + segDir = nextPoint - curPoint; + } + segDir.normalizeSafe(); + + + Point3F dirFromCam = curPoint - camPos; + Point3F crossVec; + mCross(dirFromCam, segDir, &crossVec); + crossVec.normalize(); + crossVec *= width * 0.5f; + + F32 u = i % 2; + + PrimBuild::texCoord2f( u, 1.0 ); + PrimBuild::vertex3fv( curPoint - crossVec ); + + PrimBuild::texCoord2f( u, 0.0 ); + PrimBuild::vertex3fv( curPoint + crossVec ); + } + +} + +//---------------------------------------------------------------------------- +// Find height +//---------------------------------------------------------------------------- +F32 LightningBolt::findHeight( Point3F &point, SceneGraph *sceneManager ) +{ + TerrainBlock* pTerrain = sceneManager->getCurrentTerrain(); + if( !pTerrain ) + return 0.0f; + + Point3F terrPt = point; + pTerrain->getWorldTransform().mulP(terrPt); + F32 h; + if (pTerrain->getHeight(Point2F(terrPt.x, terrPt.y), &h)) + { + return h; + } + + + return 0.0f; +} + + +//---------------------------------------------------------------------------- +// Generate lightning bolt +//---------------------------------------------------------------------------- +void LightningBolt::generate() +{ + mMajorNodes.startPoint = startPoint; + mMajorNodes.endPoint = endPoint; + mMajorNodes.numNodes = numMajorNodes; + mMajorNodes.maxAngle = maxMajorAngle; + + mMajorNodes.generateNodes(); + + generateMinorNodes(); + +} + +//---------------------------------------------------------------------------- +// Generate Minor Nodes +//---------------------------------------------------------------------------- +void LightningBolt::generateMinorNodes() +{ + mMinorNodes.clear(); + + for( int i=0; i 0.70f ) + return; + + if( width < 0.75f ) + width = 0.75f; + + VectorF diff = endPoint - startPoint; + F32 length = diff.len(); + diff.normalizeSafe(); + + LightningBolt newBolt; + newBolt.startPoint = startPoint; + newBolt.endPoint = endPoint; + newBolt.width = width; + newBolt.numMajorNodes = 3; + newBolt.maxMajorAngle = 30.0f; + newBolt.numMinorNodes = 3; + newBolt.maxMinorAngle = 10.0f; + newBolt.startRender = true; + newBolt.generate(); + + splitList.pushBack( newBolt ); + + VectorF newDir1 = MathUtils::randomDir( diff, 10.0f, 45.0f ); + Point3F newEndPoint1 = endPoint + newDir1 * gRandGen.randF( 0.5f, 1.5f ) * length; + + VectorF newDir2 = MathUtils::randomDir( diff, 10.0f, 45.0f ); + Point3F newEndPoint2 = endPoint + newDir2 * gRandGen.randF( 0.5f, 1.5f ) * length; + + createSplit( endPoint, newEndPoint1, depth - 1, width * 0.30f ); + createSplit( endPoint, newEndPoint2, depth - 1, width * 0.30f ); + +} + +//---------------------------------------------------------------------------- +// Start split - kick off the recursive 'createSplit' procedure +//---------------------------------------------------------------------------- +void LightningBolt::startSplits() +{ + + for( U32 i=0; i 0.3f ) + continue; + + Node node = mMajorNodes.nodeList[i]; + Node node2 = mMajorNodes.nodeList[i+1]; + + VectorF segDir = node2.point - node.point; + F32 length = segDir.len(); + segDir.normalizeSafe(); + + VectorF newDir = MathUtils::randomDir( segDir, 20.0f, 40.0f ); + Point3F newEndPoint = node.point + newDir * gRandGen.randF( 0.5f, 1.5f ) * length; + + + createSplit( node.point, newEndPoint, 4, width * 0.30f ); + } + + +} + +//---------------------------------------------------------------------------- +// Update +//---------------------------------------------------------------------------- +void LightningBolt::update( F32 dt ) +{ + elapsedTime += dt; + + F32 percentDone = elapsedTime / lifetime; + + if( elapsedTime > fadeTime ) + { + isFading = true; + percentFade = percentDone + (fadeTime/lifetime); + } + + if( elapsedTime > renderTime && !startRender ) + { + startRender = true; + isFading = false; + elapsedTime = 0.0f; + } +} \ No newline at end of file diff --git a/T3D/fx/lightning.h b/T3D/fx/lightning.h new file mode 100644 index 0000000..27a0451 --- /dev/null +++ b/T3D/fx/lightning.h @@ -0,0 +1,221 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTNING_H_ +#define _LIGHTNING_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _TORQUE_LIST_ +#include "core/util/tList.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif + +#include "gfx/gfxTextureHandle.h" + +class ShapeBase; +class LightningStrikeEvent; +class SFXProfile; + + +// ------------------------------------------------------------------------- +class LightningData : public GameBaseData +{ + typedef GameBaseData Parent; + + public: + enum Constants { + MaxThunders = 8, + MaxTextures = 8 + }; + + //-------------------------------------- Console set variables + public: + SFXProfile* thunderSounds[MaxThunders]; + SFXProfile* strikeSound; + StringTableEntry strikeTextureNames[MaxTextures]; + + //-------------------------------------- load set variables + public: + S32 thunderSoundIds[MaxThunders]; + S32 strikeSoundID; + + GFXTexHandle strikeTextures[MaxTextures]; + U32 numThunders; + + protected: + bool onAdd(); + + + public: + LightningData(); + ~LightningData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, String &errorStr); + + DECLARE_CONOBJECT(LightningData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +struct LightningBolt +{ + + struct Node + { + Point3F point; + VectorF dirToMainLine; + }; + + struct NodeManager + { + Node nodeList[10]; + + Point3F startPoint; + Point3F endPoint; + U32 numNodes; + F32 maxAngle; + + void generateNodes(); + }; + + NodeManager mMajorNodes; + Vector< NodeManager > mMinorNodes; + + typedef Torque::List LightingBoltList; + LightingBoltList splitList; + + F32 lifetime; + F32 elapsedTime; + F32 fadeTime; + bool isFading; + F32 percentFade; + bool startRender; + F32 renderTime; + + F32 width; + F32 chanceOfSplit; + Point3F startPoint; + Point3F endPoint; + + U32 numMajorNodes; + F32 maxMajorAngle; + U32 numMinorNodes; + F32 maxMinorAngle; + + LightningBolt(); + ~LightningBolt(); + + void createSplit( const Point3F &startPoint, const Point3F &endPoint, U32 depth, F32 width ); + F32 findHeight( Point3F &point, SceneGraph* sceneManager ); + void render( const Point3F &camPos ); + void renderSegment( NodeManager &segment, const Point3F &camPos, bool renderLastPoint ); + void generate(); + void generateMinorNodes(); + void startSplits(); + void update( F32 dt ); + +}; + + +// ------------------------------------------------------------------------- +class Lightning : public GameBase +{ + typedef GameBase Parent; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData* dptr); + + struct Strike { + F32 xVal; // Position in cloud layer of strike + F32 yVal; // top + + bool targetedStrike; // Is this a targeted strike? + U32 targetGID; + + F32 deathAge; // Age at which this strike expires + F32 currentAge; // Current age of this strike (updated by advanceTime) + + LightningBolt bolt[3]; + + Strike* next; + }; + struct Thunder { + F32 tRemaining; + Thunder* next; + }; + + public: + + //-------------------------------------- Console set variables + public: + + U32 strikesPerMinute; + F32 strikeWidth; + F32 chanceToHitTarget; + F32 strikeRadius; + F32 boltStartRadius; + ColorF color; + ColorF fadeColor; + bool useFog; + + GFXStateBlockRef mLightningSB; + + protected: + + // Rendering + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + void renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ); + + // Time management + void processTick(const Move *move); + void interpolateTick(F32 delta); + void advanceTime(F32 dt); + + // Strike management + void scheduleThunder(Strike*); + + // Data members + private: + LightningData* mDataBlock; + + protected: + U32 mLastThink; // Valid only on server + + Strike* mStrikeListHead; // Valid on on the client + Thunder* mThunderListHead; + + static const U32 csmTargetMask; + + public: + Lightning(); + ~Lightning(); + + void applyDamage( const Point3F& hitPosition, const Point3F& hitNormal, SceneObject* hitObject ); + void warningFlashes(); + void strikeRandomPoint(); + void strikeObject(ShapeBase*); + void processEvent(LightningStrikeEvent*); + + DECLARE_CONOBJECT(Lightning); + static void initPersistFields(); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); +}; + +#endif // _H_LIGHTNING + diff --git a/T3D/fx/particle.cpp b/T3D/fx/particle.cpp new file mode 100644 index 0000000..4a7e1ca --- /dev/null +++ b/T3D/fx/particle.cpp @@ -0,0 +1,503 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "particle.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "math/mRandom.h" +#include "math/mathIO.h" + +static ParticleData gDefaultParticleData; + + +IMPLEMENT_CO_DATABLOCK_V1(ParticleData); + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +ParticleData::ParticleData() +{ + dragCoefficient = 0.0f; + windCoefficient = 1.0f; + gravityCoefficient = 0.0f; + inheritedVelFactor = 0.0f; + constantAcceleration = 0.0f; + lifetimeMS = 1000; + lifetimeVarianceMS = 0; + spinSpeed = 1.0; + spinRandomMin = 0.0; + spinRandomMax = 0.0; + useInvAlpha = false; + animateTexture = false; + + numFrames = 1; + framesPerSec = numFrames; + + S32 i; + for( i=0; iwriteFloat(dragCoefficient / 5, 10); + if(stream->writeFlag(windCoefficient != gDefaultParticleData.windCoefficient)) + stream->write(windCoefficient); + if (stream->writeFlag(gravityCoefficient != 0.0f)) + stream->writeSignedFloat(gravityCoefficient / 10, 12); + stream->writeFloat(inheritedVelFactor, 9); + if(stream->writeFlag(constantAcceleration != gDefaultParticleData.constantAcceleration)) + stream->write(constantAcceleration); + + stream->write( lifetimeMS ); + stream->write( lifetimeVarianceMS ); + + if(stream->writeFlag(spinSpeed != gDefaultParticleData.spinSpeed)) + stream->write(spinSpeed); + if(stream->writeFlag(spinRandomMin != gDefaultParticleData.spinRandomMin || spinRandomMax != gDefaultParticleData.spinRandomMax)) + { + stream->writeInt((S32)(spinRandomMin + 1000), 11); + stream->writeInt((S32)(spinRandomMax + 1000), 11); + } + stream->writeFlag(useInvAlpha); + + S32 i, count; + + // see how many frames there are: + for(count = 0; count < 3; count++) + if(times[count] >= 1) + break; + + count++; + + stream->writeInt(count-1, 2); + + for( i=0; iwriteFloat( colors[i].red, 7); + stream->writeFloat( colors[i].green, 7); + stream->writeFloat( colors[i].blue, 7); + stream->writeFloat( colors[i].alpha, 7); + stream->writeFloat( sizes[i]/MaxParticleSize, 14); + stream->writeFloat( times[i], 8); + } + + if (stream->writeFlag(textureName && textureName[0])) + stream->writeString(textureName); + for (i = 0; i < 4; i++) + mathWrite(*stream, texCoords[i]); + if (stream->writeFlag(animateTexture)) + { + if (stream->writeFlag(animTexFramesString && animTexFramesString[0])) + { + stream->writeString(animTexFramesString); + } + mathWrite(*stream, animTexTiling); + stream->writeInt(framesPerSec, 8); + } +} + +//----------------------------------------------------------------------------- +// Unpack data +//----------------------------------------------------------------------------- +void ParticleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + dragCoefficient = stream->readFloat(10) * 5; + if(stream->readFlag()) + stream->read(&windCoefficient); + else + windCoefficient = gDefaultParticleData.windCoefficient; + if (stream->readFlag()) + gravityCoefficient = stream->readSignedFloat(12)*10; + else + gravityCoefficient = 0.0f; + inheritedVelFactor = stream->readFloat(9); + if(stream->readFlag()) + stream->read(&constantAcceleration); + else + constantAcceleration = gDefaultParticleData.constantAcceleration; + + stream->read( &lifetimeMS ); + stream->read( &lifetimeVarianceMS ); + + if(stream->readFlag()) + stream->read(&spinSpeed); + else + spinSpeed = gDefaultParticleData.spinSpeed; + + if(stream->readFlag()) + { + spinRandomMin = (F32)(stream->readInt(11) - 1000); + spinRandomMax = (F32)(stream->readInt(11) - 1000); + } + else + { + spinRandomMin = gDefaultParticleData.spinRandomMin; + spinRandomMax = gDefaultParticleData.spinRandomMax; + } + + useInvAlpha = stream->readFlag(); + + S32 i; + S32 count = stream->readInt(2) + 1; + for(i = 0;i < count; i++) + { + colors[i].red = stream->readFloat(7); + colors[i].green = stream->readFloat(7); + colors[i].blue = stream->readFloat(7); + colors[i].alpha = stream->readFloat(7); + sizes[i] = stream->readFloat(14) * MaxParticleSize; + times[i] = stream->readFloat(8); + } + textureName = (stream->readFlag()) ? stream->readSTString() : 0; + for (i = 0; i < 4; i++) + mathRead(*stream, &texCoords[i]); + + animateTexture = stream->readFlag(); + if (animateTexture) + { + animTexFramesString = (stream->readFlag()) ? stream->readSTString() : 0; + mathRead(*stream, &animTexTiling); + framesPerSec = stream->readInt(8); + } +} + +//----------------------------------------------------------------------------- +// onAdd +//----------------------------------------------------------------------------- +bool ParticleData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (dragCoefficient < 0.0) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) drag coeff less than 0", getName()); + dragCoefficient = 0.0f; + } + if (lifetimeMS < 1) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetime < 1 ms", getName()); + lifetimeMS = 1; + } + if (lifetimeVarianceMS >= lifetimeMS) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetimeVariance >= lifetime", getName()); + lifetimeVarianceMS = lifetimeMS - 1; + } + if (spinSpeed > 10000.0 || spinSpeed < -10000.0) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinSpeed invalid", getName()); + return false; + } + if (spinRandomMin > 10000.0 || spinRandomMin < -10000.0) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin invalid", getName()); + spinRandomMin = -360.0; + return false; + } + if (spinRandomMin > spinRandomMax) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin greater than spinRandomMax", getName()); + spinRandomMin = spinRandomMax - (spinRandomMin - spinRandomMax ); + return false; + } + if (spinRandomMax > 10000.0 || spinRandomMax < -10000.0) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMax invalid", getName()); + spinRandomMax = 360.0; + return false; + } + if (framesPerSec > 255) + { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) framesPerSec > 255, too high", getName()); + framesPerSec = 255; + return false; + } + + times[0] = 0.0f; + for (U32 i = 1; i < 4; i++) { + if (times[i] < times[i-1]) { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) times[%d] < times[%d]", getName(), i, i-1); + times[i] = times[i-1]; + } + } + + // Here we validate parameters + if (animateTexture) + { + // Tiling dimensions must be positive and non-zero + if (animTexTiling.x <= 0 || animTexTiling.y <= 0) + { + Con::warnf(ConsoleLogEntry::General, + "ParticleData(%s) bad value(s) for animTexTiling [%d or %d <= 0], invalid datablock", + animTexTiling.x, animTexTiling.y, getName()); + return false; + } + + // Indices must fit into a byte so these are also bad + if (animTexTiling.x * animTexTiling.y > 256) + { + Con::warnf(ConsoleLogEntry::General, + "ParticleData(%s) bad values for animTexTiling [%d*%d > %d], invalid datablock", + animTexTiling.x, animTexTiling.y, 256, getName()); + return false; + } + + // A list of frames is required + if (!animTexFramesString || !animTexFramesString[0]) + { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) no animTexFrames, invalid datablock", getName()); + return false; + } + + // The frame list cannot be too long. + if (animTexFramesString && dStrlen(animTexFramesString) > 255) + { + Con::errorf(ConsoleLogEntry::General, "ParticleData(%s) animTexFrames string too long [> 255 chars]", getName()); + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// preload +//----------------------------------------------------------------------------- +bool ParticleData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + bool error = false; +#ifdef CLIENT_ONLY_CODE +#else + if(!server) +#endif + { + // Here we attempt to load the particle's texture if specified. An undefined + // texture is *not* an error since the emitter may provide one. + if (textureName && textureName[0]) + { + textureHandle = GFXTexHandle(textureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__)); + if (!textureHandle) + { + errorStr = String::ToString("Missing particle texture: %s", textureName); + error = true; + } + } + + if (animateTexture) + { + // Here we parse animTexFramesString into byte-size frame numbers in animTexFrames. + // Each frame token must be separated by whitespace. + // A frame token must be a positve integer frame number or a range of frame numbers + // separated with a '-'. + // The range separator, '-', cannot have any whitspace around it. + // Ranges can be specified to move through the frames in reverse as well as forward. + // Frame numbers exceeding the number of tiles will wrap. + // example: + // "0-16 20 19 18 17 31-21" + + S32 n_tiles = animTexTiling.x * animTexTiling.y; + AssertFatal(n_tiles > 0 && n_tiles <= 256, "Error, bad animTexTiling setting." ); + + animTexFrames.clear(); + + char* tokCopy = new char[dStrlen(animTexFramesString) + 1]; + dStrcpy(tokCopy, animTexFramesString); + + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + char* minus = dStrchr(currTok, '-'); + if (minus) + { + // add a range of frames + *minus = '\0'; + S32 range_a = dAtoi(currTok); + S32 range_b = dAtoi(minus+1); + if (range_b < range_a) + { + // reverse frame range + for (S32 i = range_a; i >= range_b; i--) + animTexFrames.push_back((U8)(i % n_tiles)); + } + else + { + // forward frame range + for (S32 i = range_a; i <= range_b; i++) + animTexFrames.push_back((U8)(i % n_tiles)); + } + } + else + { + // add one frame + animTexFrames.push_back((U8)(dAtoi(currTok) % n_tiles)); + } + currTok = dStrtok(NULL, " \t"); + } + + // Here we pre-calculate the UVs for each frame tile, which are + // tiled inside the UV region specified by texCoords. Since the + // UVs are calculated using bilinear interpolation, the texCoords + // region does *not* have to be an axis-aligned rectangle. + + if (animTexUVs) + delete [] animTexUVs; + + animTexUVs = new Point2F[(animTexTiling.x+1)*(animTexTiling.y+1)]; + + // interpolate points on the left and right edge of the uv quadrangle + Point2F lf_pt = texCoords[0]; + Point2F rt_pt = texCoords[3]; + + // per-row delta for left and right interpolated points + Point2F lf_d = (texCoords[1] - texCoords[0])/(F32)animTexTiling.y; + Point2F rt_d = (texCoords[2] - texCoords[3])/(F32)animTexTiling.y; + + S32 idx = 0; + for (S32 yy = 0; yy <= animTexTiling.y; yy++) + { + Point2F p = lf_pt; + Point2F dp = (rt_pt - lf_pt)/(F32)animTexTiling.x; + for (S32 xx = 0; xx <= animTexTiling.x; xx++) + { + animTexUVs[idx++] = p; + p += dp; + } + lf_pt += lf_d; + rt_pt += rt_d; + } + + // cleanup + delete [] tokCopy; + numFrames = animTexFrames.size(); + } + } + + return !error; +} + +//----------------------------------------------------------------------------- +// Initialize particle +//----------------------------------------------------------------------------- +void ParticleData::initializeParticle(Particle* init, const Point3F& inheritVelocity) +{ + init->dataBlock = this; + + // Calculate the constant accleration... + init->vel += inheritVelocity * inheritedVelFactor; + init->acc = init->vel * constantAcceleration; + + // Calculate this instance's lifetime... + init->totalLifetime = lifetimeMS; + if (lifetimeVarianceMS != 0) + init->totalLifetime += S32(gRandGen.randI() % (2 * lifetimeVarianceMS + 1)) - S32(lifetimeVarianceMS); + + // assign spin amount + init->spinSpeed = spinSpeed * gRandGen.randF( spinRandomMin, spinRandomMax ); +} + +bool ParticleData::reload(char errorBuffer[256]) +{ + bool error = false; + if (textureName && textureName[0]) + { + textureHandle = GFXTexHandle(textureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__)); + if (!textureHandle) + { + dSprintf(errorBuffer, 256, "Missing particle texture: %s", textureName); + error = true; + } + } + /* + numFrames = 0; + for( int i=0; ireload(errorBuffer); +} diff --git a/T3D/fx/particle.h b/T3D/fx/particle.h new file mode 100644 index 0000000..f7311d8 --- /dev/null +++ b/T3D/fx/particle.h @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PARTICLE_H_ +#define _PARTICLE_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif + +#define MaxParticleSize 50.0 + +struct Particle; + +//***************************************************************************** +// Particle Data +//***************************************************************************** +class ParticleData : public SimDataBlock +{ + typedef SimDataBlock Parent; + + public: + enum PDConst + { + PDC_NUM_KEYS = 4, + }; + + F32 dragCoefficient; + F32 windCoefficient; + F32 gravityCoefficient; + + F32 inheritedVelFactor; + F32 constantAcceleration; + + S32 lifetimeMS; + S32 lifetimeVarianceMS; + + F32 spinSpeed; // degrees per second + F32 spinRandomMin; + F32 spinRandomMax; + + bool useInvAlpha; + + bool animateTexture; + U32 numFrames; + U32 framesPerSec; + + ColorF colors[ PDC_NUM_KEYS ]; + F32 sizes[ PDC_NUM_KEYS ]; + F32 times[ PDC_NUM_KEYS ]; + + Point2F* animTexUVs; + Point2F texCoords[4]; // default: {{0.0,0.0}, {0.0,1.0}, {1.0,1.0}, {1.0,0.0}} + Point2I animTexTiling; + StringTableEntry animTexFramesString; + Vector animTexFrames; + StringTableEntry textureName; + GFXTexHandle textureHandle; + + public: + ParticleData(); + ~ParticleData(); + + // move this procedure to Particle + void initializeParticle(Particle*, const Point3F&); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + bool preload(bool server, String &errorStr); + DECLARE_CONOBJECT(ParticleData); + static void initPersistFields(); + + bool reload(char errorBuffer[256]); +}; + +//***************************************************************************** +// Particle +// +// This structure should be as small as possible. +//***************************************************************************** +struct Particle +{ + Point3F pos; // current instantaneous position + Point3F vel; // " " velocity + Point3F acc; // Constant acceleration + Point3F orientDir; // direction particle should go if using oriented particles + + U32 totalLifetime; // Total ms that this instance should be "live" + ParticleData* dataBlock; // datablock that contains global parameters for + // this instance + U32 currentAge; + + + // are these necessary to store here? - they are interpolated in real time + ColorF color; + F32 size; + + F32 spinSpeed; + Particle * next; +}; + + +#endif // _PARTICLE_H_ \ No newline at end of file diff --git a/T3D/fx/particleEmitter.cpp b/T3D/fx/particleEmitter.cpp new file mode 100644 index 0000000..b194d76 --- /dev/null +++ b/T3D/fx/particleEmitter.cpp @@ -0,0 +1,1848 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/fx/particleEmitter.h" + +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "math/mRandom.h" +#include "gfx/gfxDevice.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "renderInstance/renderPassManager.h" +#include "T3D/gameProcess.h" +#include "lighting/lightInfo.h" + +#if defined(TORQUE_OS_XENON) +# include "gfx/D3D9/360/gfx360MemVertexBuffer.h" +#endif + +static ParticleEmitterData gDefaultEmitterData; +Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 ); + +IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterData); + +//----------------------------------------------------------------------------- +// ParticleEmitterData +//----------------------------------------------------------------------------- +ParticleEmitterData::ParticleEmitterData() +{ + VECTOR_SET_ASSOCIATION(particleDataBlocks); + VECTOR_SET_ASSOCIATION(dataBlockIds); + + ejectionPeriodMS = 100; // 10 Particles Per second + periodVarianceMS = 0; // exactly + + ejectionVelocity = 2.0f; // From 1.0 - 3.0 meters per sec + velocityVariance = 1.0f; + ejectionOffset = 0.0f; // ejection from the emitter point + + thetaMin = 0.0f; // All heights + thetaMax = 90.0f; + + phiReferenceVel = 0.0f; // All directions + phiVariance = 360.0f; + + softnessDistance = 1.0f; + ambientFactor = 0.0f; + + lifetimeMS = 0; + lifetimeVarianceMS = 0; + + overrideAdvance = true; + orientParticles = false; + orientOnVelocity = true; + useEmitterSizes = false; + useEmitterColors = false; + particleString = NULL; + partListInitSize = 0; + + // These members added for support of user defined blend factors + // and optional particle sorting. + blendStyle = ParticleRenderInst::BlendUndefined; + sortParticles = false; + reverseOrder = false; + textureName = 0; + textureHandle = 0; + highResOnly = true; + + alignParticles = false; + alignDirection = Point3F(0.0f, 1.0f, 0.0f); +} + + +IMPLEMENT_CONSOLETYPE(ParticleEmitterData) +IMPLEMENT_GETDATATYPE(ParticleEmitterData) +IMPLEMENT_SETDATATYPE(ParticleEmitterData) + + +// Enum tables used for fields blendStyle, srcBlendFactor, dstBlendFactor. +// Note that the enums for srcBlendFactor and dstBlendFactor are consistent +// with the blending enums used in Torque Game Builder. + +static EnumTable::Enums blendStyleLookup[] = +{ + { ParticleRenderInst::BlendNormal, "NORMAL" }, + { ParticleRenderInst::BlendAdditive, "ADDITIVE" }, + { ParticleRenderInst::BlendSubtractive, "SUBTRACTIVE" }, + { ParticleRenderInst::BlendPremultAlpha, "PREMULTALPHA" }, +}; +EnumTable blendStyleTable(sizeof(blendStyleLookup) / sizeof(EnumTable::Enums), &blendStyleLookup[0]); + +//----------------------------------------------------------------------------- +// initPersistFields +//----------------------------------------------------------------------------- +void ParticleEmitterData::initPersistFields() +{ + addField("ejectionPeriodMS", TypeS32, Offset(ejectionPeriodMS, ParticleEmitterData)); + addField("periodVarianceMS", TypeS32, Offset(periodVarianceMS, ParticleEmitterData)); + addField("ejectionVelocity", TypeF32, Offset(ejectionVelocity, ParticleEmitterData)); + addField("velocityVariance", TypeF32, Offset(velocityVariance, ParticleEmitterData)); + addField("ejectionOffset", TypeF32, Offset(ejectionOffset, ParticleEmitterData)); + addField("thetaMin", TypeF32, Offset(thetaMin, ParticleEmitterData)); + addField("thetaMax", TypeF32, Offset(thetaMax, ParticleEmitterData)); + addField("phiReferenceVel", TypeF32, Offset(phiReferenceVel, ParticleEmitterData)); + addField("phiVariance", TypeF32, Offset(phiVariance, ParticleEmitterData)); + addField("softnessDistance", TypeF32, Offset(softnessDistance, ParticleEmitterData)); + addField("ambientFactor", TypeF32, Offset(ambientFactor, ParticleEmitterData)); + addField("overrideAdvance", TypeBool, Offset(overrideAdvance, ParticleEmitterData)); + addField("orientParticles", TypeBool, Offset(orientParticles, ParticleEmitterData)); + addField("orientOnVelocity", TypeBool, Offset(orientOnVelocity, ParticleEmitterData)); + addField("particles", TypeString, Offset(particleString, ParticleEmitterData)); + addField("lifetimeMS", TypeS32, Offset(lifetimeMS, ParticleEmitterData)); + addField("lifetimeVarianceMS", TypeS32, Offset(lifetimeVarianceMS, ParticleEmitterData)); + addField("useEmitterSizes", TypeBool, Offset(useEmitterSizes, ParticleEmitterData)); + addField("useEmitterColors", TypeBool, Offset(useEmitterColors, ParticleEmitterData)); + + // These fields added for support of user defined blend factors and optional particle sorting. + addField("blendStyle", TypeEnum, Offset(blendStyle, ParticleEmitterData), 1, &blendStyleTable); + addField("sortParticles", TypeBool, Offset(sortParticles, ParticleEmitterData)); + addField("reverseOrder", TypeBool, Offset(reverseOrder, ParticleEmitterData)); + addField("textureName", TypeFilename, Offset(textureName, ParticleEmitterData)); + + addField("alignParticles", TypeBool, Offset(alignParticles, ParticleEmitterData)); + addField("alignDirection", TypePoint3F, Offset(alignDirection, ParticleEmitterData)); + + addField("highResOnly", TypeBool, Offset(highResOnly, ParticleEmitterData), "This particle system should not use the mixed-resolution renderer. If your particle system has large amounts of overdraw, consider disabling this option."); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// packData +//----------------------------------------------------------------------------- +void ParticleEmitterData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeInt(ejectionPeriodMS, 10); + stream->writeInt(periodVarianceMS, 10); + stream->writeInt((S32)(ejectionVelocity * 100), 16); + stream->writeInt((S32)(velocityVariance * 100), 14); + if( stream->writeFlag(ejectionOffset != gDefaultEmitterData.ejectionOffset) ) + stream->writeInt((S32)(ejectionOffset * 100), 16); + stream->writeRangedU32((U32)thetaMin, 0, 180); + stream->writeRangedU32((U32)thetaMax, 0, 180); + if( stream->writeFlag(phiReferenceVel != gDefaultEmitterData.phiReferenceVel) ) + stream->writeRangedU32((U32)phiReferenceVel, 0, 360); + if( stream->writeFlag(phiVariance != gDefaultEmitterData.phiVariance) ) + stream->writeRangedU32((U32)phiVariance, 0, 360); + + stream->write( softnessDistance ); + stream->write( ambientFactor ); + + stream->writeFlag(overrideAdvance); + stream->writeFlag(orientParticles); + stream->writeFlag(orientOnVelocity); + stream->write( lifetimeMS ); + stream->write( lifetimeVarianceMS ); + stream->writeFlag(useEmitterSizes); + stream->writeFlag(useEmitterColors); + + stream->write(dataBlockIds.size()); + for (U32 i = 0; i < dataBlockIds.size(); i++) + stream->write(dataBlockIds[i]); + stream->writeFlag(sortParticles); + stream->writeFlag(reverseOrder); + if (stream->writeFlag(textureName != 0)) + stream->writeString(textureName); + + if (stream->writeFlag(alignParticles)) + { + stream->write(alignDirection.x); + stream->write(alignDirection.y); + stream->write(alignDirection.z); + } + stream->writeFlag(highResOnly); +} + +//----------------------------------------------------------------------------- +// unpackData +//----------------------------------------------------------------------------- +void ParticleEmitterData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + ejectionPeriodMS = stream->readInt(10); + periodVarianceMS = stream->readInt(10); + ejectionVelocity = stream->readInt(16) / 100.0f; + velocityVariance = stream->readInt(14) / 100.0f; + if( stream->readFlag() ) + ejectionOffset = stream->readInt(16) / 100.0f; + else + ejectionOffset = gDefaultEmitterData.ejectionOffset; + + thetaMin = (F32)stream->readRangedU32(0, 180); + thetaMax = (F32)stream->readRangedU32(0, 180); + if( stream->readFlag() ) + phiReferenceVel = (F32)stream->readRangedU32(0, 360); + else + phiReferenceVel = gDefaultEmitterData.phiReferenceVel; + + if( stream->readFlag() ) + phiVariance = (F32)stream->readRangedU32(0, 360); + else + phiVariance = gDefaultEmitterData.phiVariance; + + stream->read( &softnessDistance ); + stream->read( &ambientFactor ); + + overrideAdvance = stream->readFlag(); + orientParticles = stream->readFlag(); + orientOnVelocity = stream->readFlag(); + stream->read( &lifetimeMS ); + stream->read( &lifetimeVarianceMS ); + useEmitterSizes = stream->readFlag(); + useEmitterColors = stream->readFlag(); + + U32 size; stream->read(&size); + dataBlockIds.setSize(size); + for (U32 i = 0; i < dataBlockIds.size(); i++) + stream->read(&dataBlockIds[i]); + sortParticles = stream->readFlag(); + reverseOrder = stream->readFlag(); + textureName = (stream->readFlag()) ? stream->readSTString() : 0; + + alignParticles = stream->readFlag(); + if (alignParticles) + { + stream->read(&alignDirection.x); + stream->read(&alignDirection.y); + stream->read(&alignDirection.z); + } + highResOnly = stream->readFlag(); +} + +//----------------------------------------------------------------------------- +// onAdd +//----------------------------------------------------------------------------- +bool ParticleEmitterData::onAdd() +{ + if( Parent::onAdd() == false ) + return false; + +// if (overrideAdvance == true) { +// Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData: Not going to work. Fix it!"); +// return false; +// } + + // Validate the parameters... + // + if( ejectionPeriodMS < 1 ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) period < 1 ms", getName()); + ejectionPeriodMS = 1; + } + if( periodVarianceMS >= ejectionPeriodMS ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) periodVariance >= period", getName()); + periodVarianceMS = ejectionPeriodMS - 1; + } + if( ejectionVelocity < 0.0f ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionVelocity < 0.0f", getName()); + ejectionVelocity = 0.0f; + } + if( velocityVariance > ejectionVelocity ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) velocityVariance > ejectionVelocity", getName()); + velocityVariance = ejectionVelocity; + } + if( ejectionOffset < 0.0f ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) ejectionOffset < 0", getName()); + ejectionOffset = 0.0f; + } + if( thetaMin < 0.0f ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin < 0.0", getName()); + thetaMin = 0.0f; + } + if( thetaMax > 180.0f ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMax > 180.0", getName()); + thetaMax = 180.0f; + } + if( thetaMin > thetaMax ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) thetaMin > thetaMax", getName()); + thetaMin = thetaMax; + } + if( phiVariance < 0.0f || phiVariance > 360.0f ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid phiVariance", getName()); + phiVariance = phiVariance < 0.0f ? 0.0f : 360.0f; + } + + if ( softnessDistance < 0.0f ) + { + Con::warnf( ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid softnessDistance", getName() ); + softnessDistance = 0.0f; + } + + if ( ambientFactor < 0.0f ) + { + Con::warnf( ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid ambientFactor", getName() ); + ambientFactor = 0.0f; + } + + if (particleString == NULL && dataBlockIds.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); + return false; + } + if (particleString && particleString[0] == '\0') + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) no particleString, invalid datablock", getName()); + return false; + } + if (particleString && dStrlen(particleString) > 255) + { + Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particle string too long [> 255 chars]", getName()); + return false; + } + + if( lifetimeMS < 0 ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeMS < 0.0f", getName()); + lifetimeMS = 0; + } + if( lifetimeVarianceMS > lifetimeMS ) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) lifetimeVarianceMS >= lifetimeMS", getName()); + lifetimeVarianceMS = lifetimeMS; + } + + + // load the particle datablocks... + // + if( particleString != NULL ) + { + // particleString is once again a list of particle datablocks so it + // must be parsed to extract the particle references. + + // First we parse particleString into a list of particle name tokens + Vector dataBlocks(__FILE__, __LINE__); + char* tokCopy = new char[dStrlen(particleString) + 1]; + dStrcpy(tokCopy, particleString); + + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + dataBlocks.push_back(currTok); + currTok = dStrtok(NULL, " \t"); + } + if (dataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid particles string. No datablocks found", getName()); + delete [] tokCopy; + return false; + } + + // Now we convert the particle name tokens into particle datablocks and IDs + particleDataBlocks.clear(); + dataBlockIds.clear(); + + for (U32 i = 0; i < dataBlocks.size(); i++) + { + ParticleData* pData = NULL; + if (Sim::findObject(dataBlocks[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dataBlocks[i]); + } + else + { + particleDataBlocks.push_back(pData); + dataBlockIds.push_back(pData->getId()); + } + } + + // cleanup + delete [] tokCopy; + + // check that we actually found some particle datablocks + if (particleDataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName()); + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// preload +//----------------------------------------------------------------------------- +bool ParticleEmitterData::preload(bool server, String &errorStr) +{ + if( Parent::preload(server, errorStr) == false ) + return false; + + particleDataBlocks.clear(); + for (U32 i = 0; i < dataBlockIds.size(); i++) + { + ParticleData* pData = NULL; + if (Sim::findObject(dataBlockIds[i], pData) == false) + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %d", getName(), dataBlockIds[i]); + else + particleDataBlocks.push_back(pData); + } + + if (!server) + { + // load emitter texture if specified + if (textureName && textureName[0]) + { + textureHandle = GFXTexHandle(textureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__)); + if (!textureHandle) + { + errorStr = String::ToString("Missing particle emitter texture: %s", textureName); + return false; + } + } + // otherwise, check that all particles refer to the same texture + else if (particleDataBlocks.size() > 1) + { + StringTableEntry txr_name = particleDataBlocks[0]->textureName; + for (S32 i = 1; i < particleDataBlocks.size(); i++) + { + // warn if particle textures are inconsistent + if (particleDataBlocks[i]->textureName != txr_name) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles reference different textures.", getName()); + break; + } + } + } + } + + // if blend-style is undefined check legacy useInvAlpha settings + if (blendStyle == ParticleRenderInst::BlendUndefined && particleDataBlocks.size() > 0) + { + bool useInvAlpha = particleDataBlocks[0]->useInvAlpha; + for (S32 i = 1; i < particleDataBlocks.size(); i++) + { + // warn if blend-style legacy useInvAlpha settings are inconsistent + if (particleDataBlocks[i]->useInvAlpha != useInvAlpha) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) particles have inconsistent useInvAlpha settings.", getName()); + break; + } + } + blendStyle = (useInvAlpha) ? ParticleRenderInst::BlendNormal : ParticleRenderInst::BlendAdditive; + } + + if( !server ) + { + allocPrimBuffer(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// alloc PrimitiveBuffer +// The datablock allocates this static index buffer because it's the same +// for all of the emitters - each particle quad uses the same index ordering +//----------------------------------------------------------------------------- +void ParticleEmitterData::allocPrimBuffer( S32 overrideSize ) +{ + // TODO: Rewrite + + // calculate particle list size + AssertFatal(particleDataBlocks.size() > 0, "Error, no particles found." ); + U32 maxPartLife = particleDataBlocks[0]->lifetimeMS + particleDataBlocks[0]->lifetimeVarianceMS; + for (S32 i = 1; i < particleDataBlocks.size(); i++) + { + U32 mpl = particleDataBlocks[i]->lifetimeMS + particleDataBlocks[i]->lifetimeVarianceMS; + if (mpl > maxPartLife) + maxPartLife = mpl; + } + + partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS); + partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1 + + // if override size is specified, then the emitter overran its buffer and needs a larger allocation + if( overrideSize != -1 ) + { + partListInitSize = overrideSize; + } + + // create index buffer based on that size + U32 indexListSize = partListInitSize * 6; // 6 indices per particle + U16 *indices = new U16[ indexListSize ]; + + for( U32 i=0; i( Sim::findObject( "ClientMissionCleanup") ); + if( cleanup != NULL ) + { + cleanup->addObject( this ); + } + else + { + AssertFatal( false, "Error, could not find ClientMissionCleanup group" ); + return false; + } + + + removeFromProcessList(); + + mLifetimeMS = mDataBlock->lifetimeMS; + if( mDataBlock->lifetimeVarianceMS ) + { + mLifetimeMS += S32( gRandGen.randI() % (2 * mDataBlock->lifetimeVarianceMS + 1)) - S32(mDataBlock->lifetimeVarianceMS ); + } + + // Allocate particle structures and init the freelist. Member part_store + // is a Vector so that we can allocate more particles if partListInitSize + // turns out to be too small. + // + if (mDataBlock->partListInitSize > 0) + { + for( S32 i = 0; i < part_store.size(); i++ ) + { + delete [] part_store[i]; + } + part_store.clear(); + n_part_capacity = mDataBlock->partListInitSize; + Particle* store_block = new Particle[n_part_capacity]; + part_store.push_back(store_block); + part_freelist = store_block; + Particle* last_part = part_freelist; + Particle* part = last_part+1; + for( S32 i = 1; i < n_part_capacity; i++, part++, last_part++ ) + { + last_part->next = part; + } + store_block[n_part_capacity-1].next = NULL; + part_list_head.next = NULL; + n_parts = 0; + } + + F32 radius = 5.0; + mObjBox.minExtents = Point3F(-radius, -radius, -radius); + mObjBox.maxExtents = Point3F(radius, radius, radius); + resetWorldBox(); + + return true; +} + + +//----------------------------------------------------------------------------- +// onRemove +//----------------------------------------------------------------------------- +void ParticleEmitter::onRemove() +{ + removeFromScene(); + Parent::onRemove(); +} + + +//----------------------------------------------------------------------------- +// onNewDataBlock +//----------------------------------------------------------------------------- +bool ParticleEmitter::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if( !mDataBlock || !Parent::onNewDataBlock(dptr) ) + return false; + + scriptOnNewDataBlock(); + return true; +} + +//----------------------------------------------------------------------------- +// getCollectiveColor +//----------------------------------------------------------------------------- +ColorF ParticleEmitter::getCollectiveColor() +{ + U32 count = 0; + ColorF color = ColorF(0.0f, 0.0f, 0.0f); + + count = n_parts; + for( Particle* part = part_list_head.next; part != NULL; part = part->next ) + { + color += part->color; + } + + if(count > 0) + { + color /= F32(count); + } + + //if(color.red == 0.0f && color.green == 0.0f && color.blue == 0.0f) + // color = color; + + return color; +} + + +//----------------------------------------------------------------------------- +// prepRenderImage +//----------------------------------------------------------------------------- +bool ParticleEmitter::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if( isLastState(state, stateKey) ) + return false; + + PROFILE_SCOPE(ParticleEmitter_prepRenderImage); + + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if( state->isObjectRendered(this) ) + { + const LightInfo *sunlight = state->getLightManager()->getSpecialLight( LightManager::slSunLightType ); + prepBatchRender( state, sunlight->getAmbient() ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// prepBatchRender +//----------------------------------------------------------------------------- +void ParticleEmitter::prepBatchRender( SceneState *state, const ColorF &ambientColor ) +{ + if ( mDead || + n_parts == 0 || + part_list_head.next == NULL ) + return; + + RenderPassManager *renderManager = state->getRenderPass(); + const Point3F &camPos = state->getCameraPosition(); + + // Only update the particle vertex buffer once per frame + if(state->isDiffusePass()) + copyToVB( camPos, ambientColor ); + + ParticleRenderInst *ri = renderManager->allocInst(); + + ri->vertBuff = &mVertBuff; + ri->primBuff = &getDataBlock()->primBuff; + ri->translucentSort = true; + ri->type = RenderPassManager::RIT_Particle; + ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos ); + + // TODO: Pass primitive data to RPM the way TSMesh does so # of prims/primtype is hidden + + // Draw the system offscreen unless the highResOnly flag is set on the datablock + ri->systemState = ( getDataBlock()->highResOnly ? ParticleRenderInst::AwaitingHighResDraw : ParticleRenderInst::AwaitingOffscreenDraw ); + + // TODO: change this to use shared transforms + ri->modelViewProj = renderManager->allocUniqueXform( GFX->getProjectionMatrix() * + GFX->getViewMatrix() * + GFX->getWorldMatrix() ); + + Point3F boxExtents = getRenderWorldBox().getExtents(); + ri->systemSphere.radius = getMax(boxExtents.x, getMax(boxExtents.y, boxExtents.z)); + ri->systemSphere.center = getRenderWorldBox().getCenter(); + + // Update position on the matrix before multiplying it + mBBObjToWorld.setPosition(mLastPosition); + + ri->bbModelViewProj = renderManager->allocUniqueXform( *ri->modelViewProj * mBBObjToWorld ); + + ri->count = n_parts; + + ri->blendStyle = mDataBlock->blendStyle; + + // use first particle's texture unless there is an emitter texture to override it + if (mDataBlock->textureHandle) + ri->diffuseTex = &*(mDataBlock->textureHandle); + else + ri->diffuseTex = &*(part_list_head.next->dataBlock->textureHandle); + + ri->softnessDistance = mDataBlock->softnessDistance; + + // Sort by texture too. + ri->defaultKey = ri->diffuseTex ? (U32)ri->diffuseTex : (U32)ri->vertBuff; + + renderManager->addInst( ri ); + +} + +//----------------------------------------------------------------------------- +// setSizes +//----------------------------------------------------------------------------- +void ParticleEmitter::setSizes( F32 *sizeList ) +{ + for( int i=0; i 0 && mElapsedTimeMS > mLifetimeMS ) + { + return; + } + + Point3F realStart; + if( useLastPosition && mHasLastPosition ) + realStart = mLastPosition; + else + realStart = point; + + emitParticles(realStart, point, + axis, + velocity, + numMilliseconds); +} + +//----------------------------------------------------------------------------- +// emitParticles +//----------------------------------------------------------------------------- +void ParticleEmitter::emitParticles(const Point3F& start, + const Point3F& end, + const Point3F& axis, + const Point3F& velocity, + const U32 numMilliseconds) +{ + if( mDead ) return; + + // lifetime over - no more particles + if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) + { + return; + } + + U32 currTime = 0; + bool particlesAdded = false; + + Point3F axisx; + if( mFabs(axis.z) < 0.9f ) + mCross(axis, Point3F(0, 0, 1), &axisx); + else + mCross(axis, Point3F(0, 1, 0), &axisx); + axisx.normalize(); + + if( mNextParticleTime != 0 ) + { + // Need to handle next particle + // + if( mNextParticleTime > numMilliseconds ) + { + // Defer to next update + // (Note that this introduces a potential spatial irregularity if the owning + // object is accelerating, and updating at a low frequency) + // + mNextParticleTime -= numMilliseconds; + mInternalClock += numMilliseconds; + mLastPosition = end; + mHasLastPosition = true; + return; + } + else + { + currTime += mNextParticleTime; + mInternalClock += mNextParticleTime; + // Emit particle at curr time + + // Create particle at the correct position + Point3F pos; + pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); + addParticle(pos, axis, velocity, axisx); + particlesAdded = true; + mNextParticleTime = 0; + } + } + + while( currTime < numMilliseconds ) + { + S32 nextTime = mDataBlock->ejectionPeriodMS; + if( mDataBlock->periodVarianceMS != 0 ) + { + nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) - + S32(mDataBlock->periodVarianceMS); + } + AssertFatal(nextTime > 0, "Error, next particle ejection time must always be greater than 0"); + + if( currTime + nextTime > numMilliseconds ) + { + mNextParticleTime = (currTime + nextTime) - numMilliseconds; + mInternalClock += numMilliseconds - currTime; + AssertFatal(mNextParticleTime > 0, "Error, should not have deferred this particle!"); + break; + } + + currTime += nextTime; + mInternalClock += nextTime; + + // Create particle at the correct position + Point3F pos; + pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); + addParticle(pos, axis, velocity, axisx); + particlesAdded = true; + + // This override-advance code is restored in order to correctly adjust + // animated parameters of particles allocated within the same frame + // update. Note that ordering is important and this code correctly + // adds particles in the same newest-to-oldest ordering of the link-list. + // + // NOTE: We are assuming that the just added particle is at the head of our + // list. If that changes, so must this... + U32 advanceMS = numMilliseconds - currTime; + if (mDataBlock->overrideAdvance == false && advanceMS != 0) + { + Particle* last_part = part_list_head.next; + if (advanceMS > last_part->totalLifetime) + { + part_list_head.next = last_part->next; + n_parts--; + last_part->next = part_freelist; + part_freelist = last_part; + } + else + { + if (advanceMS != 0) + { + F32 t = F32(advanceMS) / 1000.0; + + Point3F a = last_part->acc; + a -= last_part->vel * last_part->dataBlock->dragCoefficient; + a -= mWindVelocity * last_part->dataBlock->windCoefficient; + a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient; + + last_part->vel += a * t; + last_part->pos += last_part->vel * t; + + updateKeyData( last_part ); + } + } + } + } + + // DMMFIX: Lame and slow... + if( particlesAdded == true ) + updateBBox(); + + + if( n_parts > 0 && mSceneManager == NULL ) + { + gClientSceneGraph->addObjectToScene(this); + gClientContainer.addObject(this); + gClientProcessList.addObject(this); + } + + mLastPosition = end; + mHasLastPosition = true; +} + +//----------------------------------------------------------------------------- +// emitParticles +//----------------------------------------------------------------------------- +void ParticleEmitter::emitParticles(const Point3F& rCenter, + const Point3F& rNormal, + const F32 radius, + const Point3F& velocity, + S32 count) +{ + if( mDead ) return; + + // lifetime over - no more particles + if( mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS ) + { + return; + } + + + Point3F axisx, axisy; + Point3F axisz = rNormal; + + if( axisz.isZero() ) + { + axisz.set( 0.0, 0.0, 1.0 ); + } + + if( mFabs(axisz.z) < 0.98 ) + { + mCross(axisz, Point3F(0, 0, 1), &axisy); + axisy.normalize(); + } + else + { + mCross(axisz, Point3F(0, 1, 0), &axisy); + axisy.normalize(); + } + mCross(axisz, axisy, &axisx); + axisx.normalize(); + + // Should think of a better way to distribute the + // particles within the hemisphere. + for( S32 i = 0; i < count; i++ ) + { + Point3F pos = axisx * (radius * (1 - (2 * gRandGen.randF()))); + pos += axisy * (radius * (1 - (2 * gRandGen.randF()))); + pos += axisz * (radius * gRandGen.randF()); + + Point3F axis = pos; + axis.normalize(); + pos += rCenter; + + addParticle(pos, axis, velocity, axisz); + } + + // Set world bounding box + mObjBox.minExtents = rCenter - Point3F(radius, radius, radius); + mObjBox.maxExtents = rCenter + Point3F(radius, radius, radius); + resetWorldBox(); + + // Make sure we're part of the world + if( n_parts > 0 && mSceneManager == NULL ) + { + gClientSceneGraph->addObjectToScene(this); + gClientContainer.addObject(this); + gClientProcessList.addObject(this); + } + + mHasLastPosition = false; +} + +//----------------------------------------------------------------------------- +// updateBBox - SLOW, bad news +//----------------------------------------------------------------------------- +void ParticleEmitter::updateBBox() +{ + Point3F minPt(1e10, 1e10, 1e10); + Point3F maxPt(-1e10, -1e10, -1e10); + + for (Particle* part = part_list_head.next; part != NULL; part = part->next) + { + Point3F particleSize(part->size * 0.5f, 0.0f, part->size * 0.5f); + minPt.setMin( part->pos - particleSize ); + maxPt.setMax( part->pos + particleSize ); + } + + mObjBox = Box3F(minPt, maxPt); + MatrixF temp = getTransform(); + setTransform(temp); + + mBBObjToWorld.identity(); + Point3F boxScale = mObjBox.getExtents(); + boxScale.x = getMax(boxScale.x, 1.0f); + boxScale.y = getMax(boxScale.y, 1.0f); + boxScale.z = getMax(boxScale.z, 1.0f); + mBBObjToWorld.scale(boxScale); +} + +//----------------------------------------------------------------------------- +// addParticle +//----------------------------------------------------------------------------- +void ParticleEmitter::addParticle(const Point3F& pos, + const Point3F& axis, + const Point3F& vel, + const Point3F& axisx) +{ + n_parts++; + if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize) + { + // In an emergency we allocate additional particles in blocks of 16. + // This should happen rarely. + Particle* store_block = new Particle[16]; + part_store.push_back(store_block); + n_part_capacity += 16; + for (S32 i = 0; i < 16; i++) + { + store_block[i].next = part_freelist; + part_freelist = &store_block[i]; + } + mDataBlock->allocPrimBuffer(n_part_capacity); // allocate larger primitive buffer or will crash + } + Particle* pNew = part_freelist; + part_freelist = pNew->next; + pNew->next = part_list_head.next; + part_list_head.next = pNew; + + Point3F ejectionAxis = axis; + F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() + + mDataBlock->thetaMin; + + F32 ref = (F32(mInternalClock) / 1000.0) * mDataBlock->phiReferenceVel; + F32 phi = ref + gRandGen.randF() * mDataBlock->phiVariance; + + // Both phi and theta are in degs. Create axis angles out of them, and create the + // appropriate rotation matrix... + AngAxisF thetaRot(axisx, theta * (M_PI / 180.0)); + AngAxisF phiRot(axis, phi * (M_PI / 180.0)); + + MatrixF temp(true); + thetaRot.setMatrix(&temp); + temp.mulP(ejectionAxis); + phiRot.setMatrix(&temp); + temp.mulP(ejectionAxis); + + F32 initialVel = mDataBlock->ejectionVelocity; + initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; + + pNew->pos = pos + (ejectionAxis * mDataBlock->ejectionOffset); + pNew->vel = ejectionAxis * initialVel; + pNew->orientDir = ejectionAxis; + pNew->acc.set(0, 0, 0); + pNew->currentAge = 0; + + // Choose a new particle datablack randomly from the list + U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size(); + mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel); + updateKeyData( pNew ); + +} + + +//----------------------------------------------------------------------------- +// processTick +//----------------------------------------------------------------------------- +void ParticleEmitter::processTick(const Move*) +{ + if( mDeleteOnTick == true ) + { + mDead = true; + deleteObject(); + } +} + + +//----------------------------------------------------------------------------- +// advanceTime +//----------------------------------------------------------------------------- +void ParticleEmitter::advanceTime(F32 dt) +{ + if( dt < 0.00001 ) return; + + Parent::advanceTime(dt); + + if( dt > 0.5 ) dt = 0.5; + + if( mDead ) return; + + mElapsedTimeMS += (S32)(dt * 1000.0f); + + U32 numMSToUpdate = (U32)(dt * 1000.0f); + if( numMSToUpdate == 0 ) return; + + // TODO: Prefetch + + // remove dead particles + Particle* last_part = &part_list_head; + for (Particle* part = part_list_head.next; part != NULL; part = part->next) + { + part->currentAge += numMSToUpdate; + if (part->currentAge > part->totalLifetime) + { + n_parts--; + last_part->next = part->next; + part->next = part_freelist; + part_freelist = part; + part = last_part; + } + else + { + last_part = part; + } + } + + AssertFatal( n_parts >= 0, "ParticleEmitter: negative part count!" ); + + if (n_parts < 1 && mDeleteWhenEmpty) + { + mDeleteOnTick = true; + return; + } + + if( numMSToUpdate != 0 && n_parts > 0 ) + { + update( numMSToUpdate ); + } +} + +//----------------------------------------------------------------------------- +// Update key related particle data +//----------------------------------------------------------------------------- +void ParticleEmitter::updateKeyData( Particle *part ) +{ + //Ensure that our lifetime is never below 0 + if( part->totalLifetime < 1 ) + part->totalLifetime = 1; + + F32 t = F32(part->currentAge) / F32(part->totalLifetime); + AssertFatal(t <= 1.0f, "Out out bounds filter function for particle."); + + for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ ) + { + if( part->dataBlock->times[i] >= t ) + { + F32 firstPart = t - part->dataBlock->times[i-1]; + F32 total = part->dataBlock->times[i] - + part->dataBlock->times[i-1]; + + firstPart /= total; + + if( mDataBlock->useEmitterColors ) + { + part->color.interpolate(colors[i-1], colors[i], firstPart); + } + else + { + part->color.interpolate(part->dataBlock->colors[i-1], + part->dataBlock->colors[i], + firstPart); + } + + if( mDataBlock->useEmitterSizes ) + { + part->size = (sizes[i-1] * (1.0 - firstPart)) + + (sizes[i] * firstPart); + } + else + { + part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) + + (part->dataBlock->sizes[i] * firstPart); + } + break; + + } + } +} + +//----------------------------------------------------------------------------- +// Update particles +//----------------------------------------------------------------------------- +void ParticleEmitter::update( U32 ms ) +{ + // TODO: Prefetch + + for (Particle* part = part_list_head.next; part != NULL; part = part->next) + { + F32 t = F32(ms) / 1000.0; + + Point3F a = part->acc; + a -= part->vel * part->dataBlock->dragCoefficient; + a -= mWindVelocity * part->dataBlock->windCoefficient; + a += Point3F(0.0f, 0.0f, -9.81f) * part->dataBlock->gravityCoefficient; + + part->vel += a * t; + part->pos += part->vel * t; + + updateKeyData( part ); + } +} + +//----------------------------------------------------------------------------- +// Copy particles to vertex buffer +//----------------------------------------------------------------------------- + +// structure used for particle sorting. +struct SortParticle +{ + Particle* p; + F32 k; +}; + +// qsort callback function for particle sorting +int QSORT_CALLBACK cmpSortParticles(const void* p1, const void* p2) +{ + const SortParticle* sp1 = (const SortParticle*)p1; + const SortParticle* sp2 = (const SortParticle*)p2; + + if (sp2->k > sp1->k) + return 1; + else if (sp2->k == sp1->k) + return 0; + else + return -1; +} + +void ParticleEmitter::copyToVB( const Point3F &camPos, const ColorF &ambientColor ) +{ + // TODO: Rewrite this whole thing... + + + static Vector orderedVector(__FILE__, __LINE__); + + PROFILE_START(ParticleEmitter_copyToVB); + + PROFILE_START(ParticleEmitter_copyToVB_Sort); + // build sorted list of particles (far to near) + if (mDataBlock->sortParticles) + { + orderedVector.clear(); + + MatrixF modelview = GFX->getWorldMatrix(); + Point3F viewvec; modelview.getRow(1, &viewvec); + + // add each particle and a distance based sort key to orderedVector + for (Particle* pp = part_list_head.next; pp != NULL; pp = pp->next) + { + orderedVector.increment(); + orderedVector.last().p = pp; + orderedVector.last().k = mDot(pp->pos, viewvec); + } + + // qsort the list into far to near ordering + dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticle), cmpSortParticles); + } + PROFILE_END(); + +#if defined(TORQUE_OS_XENON) + // Allocate writecombined since we don't read back from this buffer (yay!) + if(mVertBuff.isNull()) + mVertBuff = new GFX360MemVertexBuffer(GFX, 1, getGFXVertexFormat(), sizeof(ParticleVertexType), GFXBufferTypeDynamic, PAGE_WRITECOMBINE); + if( n_parts > mCurBuffSize ) + { + mCurBuffSize = n_parts; + mVertBuff.resize(n_parts * 4); + } + + ParticleVertexType *buffPtr = mVertBuff.lock(); +#else + static Vector tempBuff(2048); + tempBuff.reserve( n_parts*4 + 64); // make sure tempBuff is big enough + ParticleVertexType *buffPtr = tempBuff.address(); // use direct pointer (faster) +#endif + + if (mDataBlock->orientParticles) + { + PROFILE_START(ParticleEmitter_copyToVB_Orient); + + if (mDataBlock->reverseOrder) + { + buffPtr += 4*(n_parts-1); + // do sorted-oriented particles + if (mDataBlock->sortParticles) + { + SortParticle* partPtr = orderedVector.address(); + for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) + setupOriented(partPtr->p, camPos, ambientColor, buffPtr); + } + // do unsorted-oriented particles + else + { + for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) + setupOriented(partPtr, camPos, ambientColor, buffPtr); + } + } + else + { + // do sorted-oriented particles + if (mDataBlock->sortParticles) + { + SortParticle* partPtr = orderedVector.address(); + for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) + setupOriented(partPtr->p, camPos, ambientColor, buffPtr); + } + // do unsorted-oriented particles + else + { + for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) + setupOriented(partPtr, camPos, ambientColor, buffPtr); + } + } + PROFILE_END(); + } + else if (mDataBlock->alignParticles) + { + PROFILE_START(ParticleEmitter_copyToVB_Aligned); + + if (mDataBlock->reverseOrder) + { + buffPtr += 4*(n_parts-1); + + // do sorted-oriented particles + if (mDataBlock->sortParticles) + { + SortParticle* partPtr = orderedVector.address(); + for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr-=4 ) + setupAligned(partPtr->p, buffPtr); + } + // do unsorted-oriented particles + else + { + Particle *partPtr = part_list_head.next; + for (; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) + setupAligned(partPtr, buffPtr); + } + } + else + { + // do sorted-oriented particles + if (mDataBlock->sortParticles) + { + SortParticle* partPtr = orderedVector.address(); + for (U32 i = 0; i < n_parts; i++, partPtr++, buffPtr+=4 ) + setupAligned(partPtr->p, buffPtr); + } + // do unsorted-oriented particles + else + { + Particle *partPtr = part_list_head.next; + for (; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) + setupAligned(partPtr, buffPtr); + } + } + PROFILE_END(); + } + else + { + PROFILE_START(ParticleEmitter_copyToVB_NonOriented); + // somewhat odd ordering so that texture coordinates match the oriented + // particles + Point3F basePoints[4]; + basePoints[0] = Point3F(-1.0, 0.0, 1.0); + basePoints[1] = Point3F(-1.0, 0.0, -1.0); + basePoints[2] = Point3F( 1.0, 0.0, -1.0); + basePoints[3] = Point3F( 1.0, 0.0, 1.0); + + MatrixF camView = GFX->getWorldMatrix(); + camView.transpose(); // inverse - this gets the particles facing camera + + if (mDataBlock->reverseOrder) + { + buffPtr += 4*(n_parts-1); + // do sorted-billboard particles + if (mDataBlock->sortParticles) + { + SortParticle *partPtr = orderedVector.address(); + for( U32 i=0; ip, basePoints, camView, ambientColor, buffPtr ); + } + // do unsorted-billboard particles + else + { + for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr-=4) + setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); + } + } + else + { + // do sorted-billboard particles + if (mDataBlock->sortParticles) + { + SortParticle *partPtr = orderedVector.address(); + for( U32 i=0; ip, basePoints, camView, ambientColor, buffPtr ); + } + // do unsorted-billboard particles + else + { + for (Particle* partPtr = part_list_head.next; partPtr != NULL; partPtr = partPtr->next, buffPtr+=4) + setupBillboard( partPtr, basePoints, camView, ambientColor, buffPtr ); + } + } + + PROFILE_END(); + } + +#if defined(TORQUE_OS_XENON) + mVertBuff.unlock(); +#else + PROFILE_START(ParticleEmitter_copyToVB_LockCopy); + // create new VB if emitter size grows + if( !mVertBuff || n_parts > mCurBuffSize ) + { + mCurBuffSize = n_parts; + mVertBuff.set( GFX, n_parts * 4, GFXBufferTypeDynamic ); + } + // lock and copy tempBuff to video RAM + ParticleVertexType *verts = mVertBuff.lock(); + dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(ParticleVertexType) ); + mVertBuff.unlock(); + PROFILE_END(); +#endif + + PROFILE_END(); +} + +//----------------------------------------------------------------------------- +// Set up particle for billboard style render +//----------------------------------------------------------------------------- +void ParticleEmitter::setupBillboard( Particle *part, + Point3F *basePts, + const MatrixF &camView, + const ColorF &ambientColor, + ParticleVertexType *lVerts ) +{ + // TODO: rewrite + + const F32 spinFactor = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f; + + F32 width = part->size * 0.5f; + F32 spinAngle = part->spinSpeed * part->currentAge * spinFactor; + + F32 sy, cy; + mSinCos(spinAngle, sy, cy); + + ColorF ambColor = ambientColor * mDataBlock->ambientFactor; + if ( !ambColor.isValidColor() ) + { + ambColor.set( mClampF( ambientColor.red * mDataBlock->ambientFactor, 0, 1.0f ), + mClampF( ambientColor.green * mDataBlock->ambientFactor, 0, 1.0f ), + mClampF( ambientColor.blue * mDataBlock->ambientFactor, 0, 1.0f ), + mClampF( ambientColor.alpha * mDataBlock->ambientFactor, 0, 1.0f ) ); + } + + // fill four verts, use macro and unroll loop + #define fillVert(){ \ + lVerts->point.x = cy * basePts->x - sy * basePts->z; \ + lVerts->point.y = 0.0f; \ + lVerts->point.z = sy * basePts->x + cy * basePts->z; \ + camView.mulV( lVerts->point ); \ + lVerts->point *= width; \ + lVerts->point += part->pos; \ + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * ambColor : part->color; } \ + + // Here we deal with UVs for animated particle (billboard) + if (part->dataBlock->animateTexture) + { + S32 fm = (S32)(part->currentAge*(1.0/1000.0)*part->dataBlock->framesPerSec); + U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; + S32 uv[4]; + uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; + uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); + uv[2] = uv[1] + 1; + uv[3] = uv[0] + 1; + + fillVert(); + // Here and below, we copy UVs from particle datablock's current frame's UVs (billboard) + lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; + ++lVerts; + ++basePts; + + fillVert(); + lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; + ++lVerts; + ++basePts; + + fillVert(); + lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; + ++lVerts; + ++basePts; + + fillVert(); + lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; + ++lVerts; + ++basePts; + + return; + } + + fillVert(); + // Here and below, we copy UVs from particle datablock's texCoords (billboard) + lVerts->texCoord = part->dataBlock->texCoords[0]; + ++lVerts; + ++basePts; + + fillVert(); + lVerts->texCoord = part->dataBlock->texCoords[1]; + ++lVerts; + ++basePts; + + fillVert(); + lVerts->texCoord = part->dataBlock->texCoords[2]; + ++lVerts; + ++basePts; + + fillVert(); + lVerts->texCoord = part->dataBlock->texCoords[3]; + ++lVerts; + ++basePts; +} + +//----------------------------------------------------------------------------- +// Set up oriented particle +//----------------------------------------------------------------------------- +void ParticleEmitter::setupOriented( Particle *part, + const Point3F &camPos, + const ColorF &ambientColor, + ParticleVertexType *lVerts ) +{ + // TODO: Rewrite + + Point3F dir; + + if( mDataBlock->orientOnVelocity ) + { + // don't render oriented particle if it has no velocity + if( part->vel.magnitudeSafe() == 0.0 ) return; + dir = part->vel; + } + else + { + dir = part->orientDir; + } + + Point3F dirFromCam = part->pos - camPos; + Point3F crossDir; + mCross( dirFromCam, dir, &crossDir ); + crossDir.normalize(); + dir.normalize(); + + + F32 width = part->size * 0.5; + dir *= width; + crossDir *= width; + Point3F start = part->pos - dir; + Point3F end = part->pos + dir; + + // Here we deal with UVs for animated particle (oriented) + if (part->dataBlock->animateTexture) + { + // Let particle compute the UV indices for current frame + S32 fm = (S32)(part->currentAge*(1.0/1000.0)*part->dataBlock->framesPerSec); + U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; + S32 uv[4]; + uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; + uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); + uv[2] = uv[1] + 1; + uv[3] = uv[0] + 1; + + lVerts->point = start + crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + // Here and below, we copy UVs from particle datablock's current frame's UVs (oriented) + lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; + ++lVerts; + + lVerts->point = start - crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; + ++lVerts; + + lVerts->point = end - crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; + ++lVerts; + + lVerts->point = end + crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; + ++lVerts; + + return; + } + + lVerts->point = start + crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + // Here and below, we copy UVs from particle datablock's texCoords (oriented) + lVerts->texCoord = part->dataBlock->texCoords[0]; + ++lVerts; + + lVerts->point = start - crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + lVerts->texCoord = part->dataBlock->texCoords[1]; + ++lVerts; + + lVerts->point = end - crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + lVerts->texCoord = part->dataBlock->texCoords[2]; + ++lVerts; + + lVerts->point = end + crossDir; + lVerts->color = mDataBlock->ambientFactor > 0.0f ? part->color * (ambientColor * mDataBlock->ambientFactor) : part->color; + lVerts->texCoord = part->dataBlock->texCoords[3]; + ++lVerts; +} + +void ParticleEmitter::setupAligned( const Particle *part, ParticleVertexType *lVerts ) +{ + // TODO: Rewrite + + Point3F dir = mDataBlock->alignDirection; + + Point3F cross(0.0f, 1.0f, 0.0f); + if (mFabs(dir.y) > 0.9f) + cross.set(0.0f, 0.0f, 1.0f); + + // optimization - this is the same for every particle + Point3F crossDir; + mCross(cross, dir, &crossDir); + crossDir.normalize(); + dir.normalize(); + + F32 width = part->size * 0.5f; + dir *= width; + crossDir *= width; + Point3F start = part->pos - dir; + Point3F end = part->pos + dir; + + // Here we deal with UVs for animated particle (aligned) + if (part->dataBlock->animateTexture) + { + // Let particle compute the UV indices for current frame + S32 fm = (S32)(part->currentAge*(1.0f/1000.0f)*part->dataBlock->framesPerSec); + U8 fm_tile = part->dataBlock->animTexFrames[fm % part->dataBlock->numFrames]; + S32 uv[4]; + uv[0] = fm_tile + fm_tile/part->dataBlock->animTexTiling.x; + uv[1] = uv[0] + (part->dataBlock->animTexTiling.x + 1); + uv[2] = uv[1] + 1; + uv[3] = uv[0] + 1; + + lVerts->point = start + crossDir; + lVerts->color = part->color; + // Here and below, we copy UVs from particle datablock's current frame's UVs (aligned) + lVerts->texCoord = part->dataBlock->animTexUVs[uv[0]]; + ++lVerts; + + lVerts->point = start - crossDir; + lVerts->color = part->color; + lVerts->texCoord = part->dataBlock->animTexUVs[uv[1]]; + ++lVerts; + + lVerts->point = end - crossDir; + lVerts->color = part->color; + lVerts->texCoord = part->dataBlock->animTexUVs[uv[2]]; + ++lVerts; + + lVerts->point = end + crossDir; + lVerts->color = part->color; + lVerts->texCoord = part->dataBlock->animTexUVs[uv[3]]; + ++lVerts; + + return; + } + + lVerts->point = start + crossDir; + lVerts->color = part->color; + // Here and below, we copy UVs from particle datablock's texCoords (aligned) + lVerts->texCoord = part->dataBlock->texCoords[0]; + ++lVerts; + + lVerts->point = start - crossDir; + lVerts->color = part->color; + lVerts->texCoord = part->dataBlock->texCoords[1]; + ++lVerts; + + lVerts->point = end - crossDir; + lVerts->color = part->color; + lVerts->texCoord = part->dataBlock->texCoords[2]; + ++lVerts; + + lVerts->point = end + crossDir; + lVerts->color = part->color; + lVerts->texCoord = part->dataBlock->texCoords[3]; + ++lVerts; +} + +bool ParticleEmitterData::reload() +{ + particleDataBlocks.clear(); + // load the particle datablocks... + // + if( particleString != NULL ) + { + // particleString is once again a list of particle datablocks so it + // must be parsed to extract the particle references. + + // First we parse particleString into a list of particle name tokens + Vector dataBlocks(__FILE__, __LINE__); + char* tokCopy = new char[dStrlen(particleString) + 1]; + dStrcpy(tokCopy, particleString); + + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + dataBlocks.push_back(currTok); + currTok = dStrtok(NULL, " \t"); + } + if (dataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid particles string. No datablocks found", getName()); + delete [] tokCopy; + return false; + } + + // Now we convert the particle name tokens into particle datablocks and IDs + particleDataBlocks.clear(); + dataBlockIds.clear(); + + for (U32 i = 0; i < dataBlocks.size(); i++) + { + ParticleData* pData = NULL; + if (Sim::findObject(dataBlocks[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find particle datablock: %s", getName(), dataBlocks[i]); + } + else + { + particleDataBlocks.push_back(pData); + dataBlockIds.push_back(pData->getId()); + } + } + + // cleanup + delete [] tokCopy; + + // check that we actually found some particle datablocks + if (particleDataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any particle datablocks", getName()); + return false; + } + } + return true; +} + + +ConsoleMethod(ParticleEmitterData, reload, void, 2, 2, "(void)" + "Reloads this emitter") +{ + object->reload(); +} + diff --git a/T3D/fx/particleEmitter.h b/T3D/fx/particleEmitter.h new file mode 100644 index 0000000..3aa5198 --- /dev/null +++ b/T3D/fx/particleEmitter.h @@ -0,0 +1,258 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PARTICLE_EMITTER +#define _H_PARTICLE_EMITTER + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _PARTICLE_H_ +#include "T3D/fx/particle.h" +#endif + +#if defined(TORQUE_OS_XENON) +#include "gfx/D3D9/360/gfx360MemVertexBuffer.h" +#endif + +class RenderPassManager; +class ParticleData; + +//***************************************************************************** +// Particle Emitter Data +//***************************************************************************** +class ParticleEmitterData : public GameBaseData +{ + typedef GameBaseData Parent; + + public: + ParticleEmitterData(); + DECLARE_CONOBJECT(ParticleEmitterData); + static void initPersistFields(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool preload(bool server, String &errorStr); + bool onAdd(); + void allocPrimBuffer( S32 overrideSize = -1 ); + + public: + S32 ejectionPeriodMS; ///< Time, in Milliseconds, between particle ejection + S32 periodVarianceMS; ///< Varience in ejection peroid between 0 and n + + F32 ejectionVelocity; ///< Ejection velocity + F32 velocityVariance; ///< Variance for velocity between 0 and n + F32 ejectionOffset; ///< Z offset from emitter point to eject from + + F32 thetaMin; ///< Minimum angle, from the horizontal plane, to eject from + F32 thetaMax; ///< Maximum angle, from the horizontal plane, to eject from + + F32 phiReferenceVel; ///< Reference angle, from the verticle plane, to eject from + F32 phiVariance; ///< Varience from the reference angle, from 0 to n + + F32 softnessDistance; ///< For soft particles, the distance (in meters) where particles will be faded + ///< based on the difference in depth between the particle and the scene geometry. + + F32 ambientFactor; ///< A scalar value used to influence the effect of the ambient color on the particle. + + U32 lifetimeMS; ///< Lifetime of particles + U32 lifetimeVarianceMS; ///< Varience in lifetime from 0 to n + + bool overrideAdvance; ///< + bool orientParticles; ///< Particles always face the screen + bool orientOnVelocity; ///< Particles face the screen at the start + bool useEmitterSizes; ///< Use emitter specified sizes instead of datablock sizes + bool useEmitterColors; ///< Use emitter specified colors instead of datablock colors + bool alignParticles; ///< Particles always face along a particular axis + Point3F alignDirection; ///< The direction aligned particles should face + + StringTableEntry particleString; ///< Used to load particle data directly from a string + + Vector particleDataBlocks; ///< Particle Datablocks + Vector dataBlockIds; ///< Datablock IDs (parellel array to particleDataBlocks) + + U32 partListInitSize; /// initial size of particle list calc'd from datablock info + + GFXPrimitiveBufferHandle primBuff; + + S32 blendStyle; ///< Pre-define blend factor setting + bool sortParticles; ///< Particles are sorted back-to-front + bool reverseOrder; ///< reverses draw order + StringTableEntry textureName; ///< Emitter texture file to override particle textures + GFXTexHandle textureHandle; ///< Emitter texture handle from txrName + bool highResOnly; ///< This particle system should not use the mixed-resolution particle rendering + + bool reload(); +}; + +DECLARE_CONSOLETYPE(ParticleEmitterData) + +//***************************************************************************** +// Particle Emitter +//***************************************************************************** +class ParticleEmitter : public GameBase +{ + typedef GameBase Parent; + + public: + +#if defined(TORQUE_OS_XENON) + typedef GFXVertexPCTT ParticleVertexType; +#else + typedef GFXVertexPCT ParticleVertexType; +#endif + + ParticleEmitter(); + ~ParticleEmitter(); + + static Point3F mWindVelocity; + static void setWindVelocity( const Point3F &vel ){ mWindVelocity = vel; } + + ColorF getCollectiveColor(); + + /// Sets sizes of particles based on sizelist provided + /// @param sizeList List of sizes + void setSizes( F32 *sizeList ); + + /// Sets colors for particles based on color list provided + /// @param colorList List of colors + void setColors( ColorF *colorList ); + + ParticleEmitterData *getDataBlock(){ return mDataBlock; } + bool onNewDataBlock(GameBaseData* dptr); + + /// By default, a particle renderer will wait for it's owner to delete it. When this + /// is turned on, it will delete itself as soon as it's particle count drops to zero. + void deleteWhenEmpty(); + + /// @name Particle Emission + /// Main interface for creating particles. The emitter does _not_ track changes + /// in axis or velocity over the course of a single update, so this should be called + /// at a fairly fine grain. The emitter will potentially track the last particle + /// to be created into the next call to this function in order to create a uniformly + /// random time distribution of the particles. If the object to which the emitter is + /// attached is in motion, it should try to ensure that for call (n+1) to this + /// function, start is equal to the end from call (n). This will ensure a uniform + /// spatial distribution. + /// @{ + + void emitParticles(const Point3F& start, + const Point3F& end, + const Point3F& axis, + const Point3F& velocity, + const U32 numMilliseconds); + void emitParticles(const Point3F& point, + const bool useLastPosition, + const Point3F& axis, + const Point3F& velocity, + const U32 numMilliseconds); + void emitParticles(const Point3F& rCenter, + const Point3F& rNormal, + const F32 radius, + const Point3F& velocity, + S32 count); + /// @} + + bool mDead; + + protected: + /// @name Internal interface + /// @{ + + /// Adds a particle + /// @param pos Initial position of particle + /// @param axis + /// @param vel Initial velocity + /// @param axisx + void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx); + + + inline void setupBillboard( Particle *part, + Point3F *basePts, + const MatrixF &camView, + const ColorF &ambientColor, + ParticleVertexType *lVerts ); + + inline void setupOriented( Particle *part, + const Point3F &camPos, + const ColorF &ambientColor, + ParticleVertexType *lVerts ); + + inline void setupAligned( const Particle *part, ParticleVertexType *lVerts ); + + /// Updates the bounding box for the particle system + void updateBBox(); + + /// @} + protected: + bool onAdd(); + void onRemove(); + + void processTick(const Move *move); + void advanceTime(F32 dt); + + // Rendering + protected: + bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState ); + void prepBatchRender( SceneState *state, const ColorF &ambientColor ); + void copyToVB( const Point3F &camPos, const ColorF &ambientColor ); + + // PEngine interface + private: + + void update( U32 ms ); + inline void updateKeyData( Particle *part ); + + + private: + ParticleEmitterData* mDataBlock; + + U32 mInternalClock; + + U32 mNextParticleTime; + + Point3F mLastPosition; + bool mHasLastPosition; + MatrixF mBBObjToWorld; + + bool mDeleteWhenEmpty; + bool mDeleteOnTick; + + S32 mLifetimeMS; + S32 mElapsedTimeMS; + + F32 sizes[ ParticleData::PDC_NUM_KEYS ]; + ColorF colors[ ParticleData::PDC_NUM_KEYS ]; + +#if defined(TORQUE_OS_XENON) + GFX360MemVertexBufferHandle mVertBuff; +#else + GFXVertexBufferHandle mVertBuff; +#endif + + // These members are for implementing a link-list of the active emitter + // particles. Member part_store contains blocks of particles that can be + // chained in a link-list. Usually the first part_store block is large + // enough to contain all the particles but it can be expanded in emergency + // circumstances. + Vector part_store; + Particle* part_freelist; + Particle part_list_head; + S32 n_part_capacity; + S32 n_parts; + S32 mCurBuffSize; + +}; + +#endif // _H_PARTICLE_EMITTER + diff --git a/T3D/fx/particleEmitterNode.cpp b/T3D/fx/particleEmitterNode.cpp new file mode 100644 index 0000000..f88d092 --- /dev/null +++ b/T3D/fx/particleEmitterNode.cpp @@ -0,0 +1,321 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "particleEmitterNode.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "T3D/fx/particleEmitter.h" +#include "math/mathIO.h" + +IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterNodeData); +IMPLEMENT_CO_NETOBJECT_V1(ParticleEmitterNode); + +//----------------------------------------------------------------------------- +// ParticleEmitterNodeData +//----------------------------------------------------------------------------- +ParticleEmitterNodeData::ParticleEmitterNodeData() +{ + timeMultiple = 1.0; +} + +ParticleEmitterNodeData::~ParticleEmitterNodeData() +{ + +} + +//----------------------------------------------------------------------------- +// initPersistFields +//----------------------------------------------------------------------------- +void ParticleEmitterNodeData::initPersistFields() +{ + addField("timeMultiple", TypeF32, Offset(timeMultiple, ParticleEmitterNodeData)); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// onAdd +//----------------------------------------------------------------------------- +bool ParticleEmitterNodeData::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + if( timeMultiple < 0.01 || timeMultiple > 100 ) + { + Con::warnf("ParticleEmitterNodeData::onAdd(%s): timeMultiple must be between 0.01 and 100", getName()); + timeMultiple = timeMultiple < 0.01 ? 0.01 : 100; + } + + return true; +} + + +//----------------------------------------------------------------------------- +// preload +//----------------------------------------------------------------------------- +bool ParticleEmitterNodeData::preload(bool server, String &errorStr) +{ + if( Parent::preload(server, errorStr) == false ) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// packData +//----------------------------------------------------------------------------- +void ParticleEmitterNodeData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(timeMultiple); +} + +//----------------------------------------------------------------------------- +// unpackData +//----------------------------------------------------------------------------- +void ParticleEmitterNodeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&timeMultiple); +} + + +//----------------------------------------------------------------------------- +// ParticleEmitterNode +//----------------------------------------------------------------------------- +ParticleEmitterNode::ParticleEmitterNode() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + mTypeMask |= EnvironmentObjectType; + + mActive = true; + + mDataBlock = NULL; + mEmitterDatablock = NULL; + mEmitterDatablockId = 0; + mEmitter = NULL; + mVelocity = 1.0; +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +ParticleEmitterNode::~ParticleEmitterNode() +{ + // +} + +//----------------------------------------------------------------------------- +// initPersistFields +//----------------------------------------------------------------------------- +void ParticleEmitterNode::initPersistFields() +{ + addField("active", TypeBool, Offset(mActive, ParticleEmitterNode)); + addField("emitter", TypeParticleEmitterDataPtr, Offset(mEmitterDatablock, ParticleEmitterNode)); + addField("velocity", TypeF32, Offset(mVelocity, ParticleEmitterNode)); + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// onAdd +//----------------------------------------------------------------------------- +bool ParticleEmitterNode::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + if( !mEmitterDatablock && mEmitterDatablockId != 0 ) + { + if( Sim::findObject(mEmitterDatablockId, mEmitterDatablock) == false ) + Con::errorf(ConsoleLogEntry::General, "ParticleEmitterNode::onAdd: Invalid packet, bad datablockId(mEmitterDatablock): %d", mEmitterDatablockId); + } + + if( mEmitterDatablock == NULL ) + return false; + + if( isClientObject() ) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mEmitterDatablock); + if( pEmitter->registerObject() == false ) + { + Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mEmitterDatablock->getName()); + delete pEmitter; + return false; + } + mEmitter = pEmitter; + } + + mObjBox.minExtents.set(-0.5, -0.5, -0.5); + mObjBox.maxExtents.set( 0.5, 0.5, 0.5); + resetWorldBox(); + addToScene(); + + return true; +} + +//----------------------------------------------------------------------------- +// onRemove +//----------------------------------------------------------------------------- +void ParticleEmitterNode::onRemove() +{ + removeFromScene(); + if( isClientObject() ) + { + if( mEmitter ) + { + mEmitter->deleteWhenEmpty(); + mEmitter = NULL; + } + } + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- +// onNewDataBlock +//----------------------------------------------------------------------------- +bool ParticleEmitterNode::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if( !mDataBlock || !Parent::onNewDataBlock(dptr) ) + return false; + + // Todo: Uncomment if this is a "leaf" class + scriptOnNewDataBlock(); + return true; +} + +//----------------------------------------------------------------------------- +// advanceTime +//----------------------------------------------------------------------------- +void ParticleEmitterNode::processTick(const Move* move) +{ + Parent::processTick(move); + + if ( isMounted() ) + { + MatrixF mat; + mMount.object->getRenderMountTransform( mMount.node, &mat ); + setTransform( mat ); + } +} + +void ParticleEmitterNode::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if(!mActive || mEmitter.isNull() || !mDataBlock) + return; + + Point3F emitPoint, emitVelocity; + Point3F emitAxis(0, 0, 1); + getTransform().mulV(emitAxis); + getTransform().getColumn(3, &emitPoint); + emitVelocity = emitAxis * mVelocity; + + mEmitter->emitParticles(emitPoint, emitPoint, + emitAxis, + emitVelocity, (U32)(dt * mDataBlock->timeMultiple * 1000.0f)); +} + +//----------------------------------------------------------------------------- +// packUpdate +//----------------------------------------------------------------------------- +U32 ParticleEmitterNode::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if ( stream->writeFlag( mask & InitialUpdateMask ) ) + { + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + if( stream->writeFlag(mEmitterDatablock != NULL) ) + { + stream->writeRangedU32(mEmitterDatablock->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + } + + if ( stream->writeFlag( mask & StateMask ) ) + { + stream->writeFlag( mActive ); + } + + return retMask; +} + +//----------------------------------------------------------------------------- +// unpackUpdate +//----------------------------------------------------------------------------- +void ParticleEmitterNode::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if ( stream->readFlag() ) + { + MatrixF temp; + Point3F tempScale; + mathRead(*stream, &temp); + mathRead(*stream, &tempScale); + + if( stream->readFlag() ) + { + mEmitterDatablockId = stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + else + { + mEmitterDatablockId = 0; + } + + setScale(tempScale); + setTransform(temp); + } + + if ( stream->readFlag() ) + { + mActive = stream->readFlag(); + } +} + +void ParticleEmitterNode::setEmitterDataBlock(ParticleEmitterData* data) +{ + if (!data) + return; + + if (mEmitter) + { + mEmitterDatablock = data; + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mEmitterDatablock); + if (pEmitter->registerObject() == false) { + Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", mEmitterDatablock->getName()); + delete pEmitter; + pEmitter = NULL; + } + if (pEmitter) + { + mEmitter->deleteWhenEmpty(); + mEmitter = pEmitter; + } + } +} + +ConsoleMethod(ParticleEmitterNode, setEmitterDataBlock, void, 3, 3, "(data)") +{ + ParticleEmitterData* data = dynamic_cast(Sim::findObject(dAtoi(argv[2]))); + if (!data) + data = dynamic_cast(Sim::findObject(argv[2])); + + if (data) + object->setEmitterDataBlock(data); +} + diff --git a/T3D/fx/particleEmitterNode.h b/T3D/fx/particleEmitterNode.h new file mode 100644 index 0000000..c517a2c --- /dev/null +++ b/T3D/fx/particleEmitterNode.h @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PARTICLEEMITTERDUMMY_H_ +#define _PARTICLEEMITTERDUMMY_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif + +class ParticleEmitterData; +class ParticleEmitter; + +//***************************************************************************** +// ParticleEmitterNodeData +//***************************************************************************** +class ParticleEmitterNodeData : public GameBaseData +{ + typedef GameBaseData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + F32 timeMultiple; + + //-------------------------------------- load set variables + public: + + public: + ParticleEmitterNodeData(); + ~ParticleEmitterNodeData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, String &errorStr); + + DECLARE_CONOBJECT(ParticleEmitterNodeData); + static void initPersistFields(); +}; + + +//***************************************************************************** +// ParticleEmitterNode +//***************************************************************************** +class ParticleEmitterNode : public GameBase +{ + typedef GameBase Parent; + + enum MaskBits + { + StateMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1, + }; + + private: + ParticleEmitterNodeData* mDataBlock; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData *dptr); + + ParticleEmitterData* mEmitterDatablock; + S32 mEmitterDatablockId; + + bool mActive; + + SimObjectPtr mEmitter; + F32 mVelocity; + + public: + ParticleEmitterNode(); + ~ParticleEmitterNode(); + + ParticleEmitter *getParticleEmitter() {return mEmitter;} + + // Time/Move Management + public: + void processTick(const Move* move); + void advanceTime(F32 dt); + + DECLARE_CONOBJECT(ParticleEmitterNode); + static void initPersistFields(); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection *conn, BitStream* stream); + + inline bool getActive( void ) { return mActive; }; + inline void setActive( bool active ) { mActive = active; setMaskBits( StateMask ); }; + + void setEmitterDataBlock(ParticleEmitterData* data); +}; + +#endif // _H_PARTICLEEMISSIONDUMMY + diff --git a/T3D/fx/precipitation.cpp b/T3D/fx/precipitation.cpp new file mode 100644 index 0000000..6de13a6 --- /dev/null +++ b/T3D/fx/precipitation.cpp @@ -0,0 +1,1691 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/fx/precipitation.h" + +#include "math/mathIO.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" +#include "materials/shaderData.h" +#include "T3D/gameConnection.h" +#include "T3D/player.h" +#include "core/stream/bitStream.h" +#include "platform/profiler.h" +#include "renderInstance/renderPassManager.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" + + +static const U32 dropHitMask = +TerrainObjectType | +InteriorObjectType | +WaterObjectType | +StaticShapeObjectType | +StaticTSObjectType; + +IMPLEMENT_CO_NETOBJECT_V1(Precipitation); +IMPLEMENT_CO_DATABLOCK_V1(PrecipitationData); + +extern bool GameGetCameraTransform(MatrixF *mat, Point3F *velocity); +//---------------------------------------------------------- +// PrecipitationData +//---------------------------------------------------------- +PrecipitationData::PrecipitationData() +{ + soundProfile = NULL; + soundProfileId = 0; + + mDropName = StringTable->insert(""); + mDropShaderName = StringTable->insert(""); + mSplashName = StringTable->insert(""); + mSplashShaderName = StringTable->insert(""); + + mDropsPerSide = 4; + mSplashesPerSide = 2; +} + +IMPLEMENT_CONSOLETYPE(PrecipitationData) +IMPLEMENT_GETDATATYPE(PrecipitationData) +IMPLEMENT_SETDATATYPE(PrecipitationData) + +void PrecipitationData::initPersistFields() +{ + addField("soundProfile", TypeSFXProfilePtr, Offset(soundProfile, PrecipitationData)); + + addField("dropTexture", TypeFilename, Offset(mDropName, PrecipitationData)); + addField("dropShader", TypeString, Offset(mDropShaderName, PrecipitationData)); + addField("splashTexture", TypeFilename, Offset(mSplashName, PrecipitationData)); + addField("splashShader", TypeString, Offset(mSplashShaderName, PrecipitationData)); + addField("dropsPerSide", TypeS32, Offset(mDropsPerSide, PrecipitationData)); + addField("splashesPerSide", TypeS32, Offset(mSplashesPerSide, PrecipitationData)); + + Parent::initPersistFields(); +} + +bool PrecipitationData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (!soundProfile && soundProfileId != 0) + if (Sim::findObject(soundProfileId, soundProfile) == false) + Con::errorf(ConsoleLogEntry::General, "Error, unable to load sound profile for precipitation datablock"); + + return true; +} + +void PrecipitationData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (stream->writeFlag(soundProfile != NULL)) + stream->writeRangedU32(soundProfile->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + stream->writeString(mDropName); + stream->writeString(mDropShaderName); + stream->writeString(mSplashName); + stream->writeString(mSplashShaderName); + stream->write(mDropsPerSide); + stream->write(mSplashesPerSide); +} + +void PrecipitationData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + if (stream->readFlag()) + soundProfileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + else + soundProfileId = 0; + + mDropName = stream->readSTString(); + mDropShaderName = stream->readSTString(); + mSplashName = stream->readSTString(); + mSplashShaderName = stream->readSTString(); + stream->read(&mDropsPerSide); + stream->read(&mSplashesPerSide); +} + +//---------------------------------------------------------- +// Precipitation! +//---------------------------------------------------------- +Precipitation::Precipitation() +{ + mTypeMask |= ProjectileObjectType; + + mDataBlock = NULL; + + mTexCoords = NULL; + mSplashCoords = NULL; + + mDropShader = NULL; + mDropHandle = NULL; + + mSplashShader = NULL; + mSplashHandle = NULL; + + mDropHead = NULL; + mSplashHead = NULL; + mNumDrops = 1024; + mPercentage = 1.0; + + mMinSpeed = 1.5; + mMaxSpeed = 2.0; + + mFollowCam = true; + + mLastRenderFrame = 0; + + mDropHitMask = 0; + + mDropSize = 0.5; + mSplashSize = 0.5; + mUseTrueBillboards = false; + mSplashMS = 250; + + mAnimateSplashes = true; + mDropAnimateMS = 0; + + mUseLighting = false; + mGlowIntensity = ColorF( 0,0,0,0 ); + + mReflect = false; + + mUseWind = false; + + mBoxWidth = 200; + mBoxHeight = 100; + mFadeDistance = 0; + mFadeDistanceEnd = 0; + + mMinMass = 0.75f; + mMaxMass = 0.85f; + + mMaxTurbulence = 0.1f; + mTurbulenceSpeed = 0.2f; + mUseTurbulence = false; + + mRotateWithCamVel = true; + + mDoCollision = true; + mDropHitPlayers = false; + mDropHitVehicles = false; + + mStormData.valid = false; + mStormData.startPct = 0; + mStormData.endPct = 0; + mStormData.startTime = 0; + mStormData.totalTime = 0; + + mTurbulenceData.valid = false; + mTurbulenceData.startTime = 0; + mTurbulenceData.totalTime = 0; + mTurbulenceData.startMax = 0; + mTurbulenceData.startSpeed = 0; + mTurbulenceData.endMax = 0; + mTurbulenceData.endSpeed = 0; + + mAmbientSound = NULL; + + mDropShaderModelViewSC = NULL; + mDropShaderFadeStartEndSC = NULL; + mDropShaderCameraPosSC = NULL; + mDropShaderAmbientSC = NULL; + + mSplashShaderModelViewSC = NULL; + mSplashShaderFadeStartEndSC = NULL; + mSplashShaderCameraPosSC = NULL; + mSplashShaderAmbientSC = NULL; + +} + +Precipitation::~Precipitation() +{ + SAFE_DELETE_ARRAY(mTexCoords); + SAFE_DELETE_ARRAY(mSplashCoords); +} + +void Precipitation::inspectPostApply() +{ + if (mFollowCam) + { + setGlobalBounds(); + } + else + { + mObjBox.minExtents = -Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2); + mObjBox.maxExtents = Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2); + } + + resetWorldBox(); + setMaskBits(DataMask); +} + +void Precipitation::setTransform(const MatrixF & mat) +{ + Parent::setTransform(mat); + + setMaskBits(TransformMask); +} + +//-------------------------------------------------------------------------- +// Console stuff... +//-------------------------------------------------------------------------- +void Precipitation::initPersistFields() +{ + addGroup("Rendering"); + addField("dropSize", TypeF32, Offset(mDropSize, Precipitation), "Size of each drop of precipitation. This will scale the texture."); + addField("splashSize", TypeF32, Offset(mSplashSize, Precipitation), "Size of each splash animation for when a drop collides."); + addField("splashMS", TypeS32, Offset(mSplashMS, Precipitation), "Life of splashes in millisecons."); + addField("animateSplashes", TypeBool, Offset(mAnimateSplashes, Precipitation), "Check to enable splash animation on collision."); + addField("dropAnimateMS", TypeS32, Offset(mDropAnimateMS, Precipitation), "If greater than zero, will animate the drops from" + " the frames in the texture."); + addField("fadeDist", TypeF32, Offset(mFadeDistance, Precipitation), "The distance at which fading of the drops begins."); + addField("fadeDistEnd", TypeF32, Offset(mFadeDistanceEnd, Precipitation), "The distance at which fading of the particles ends."); + addField("useTrueBillboards", TypeBool, Offset(mUseTrueBillboards, Precipitation), "Check to make drops true (non axis-aligned) billboards."); + addField("useLighting", TypeBool, Offset(mUseLighting, Precipitation), "Check to enable shading of the drops and splashes " + "by the sun color."); + addField("glowIntensity", TypeColorF, Offset(mGlowIntensity, Precipitation), "Set to 0 to disable the glow or or use it to " + "control the intensity of each channel."); + addField("reflect", TypeBool, Offset(mReflect, Precipitation), "This enables the precipitation to be rendered during " + "reflection passes. This is expensive."); + addField("rotateWithCamVel", TypeBool, Offset(mRotateWithCamVel, Precipitation), "Enables drops to rotate to face camera."); + endGroup("Rendering"); + + addGroup("Collision"); + addField("doCollision", TypeBool, Offset(mDoCollision, Precipitation), "Allow collision with world objects."); + addField("hitPlayers", TypeBool, Offset(mDropHitPlayers, Precipitation), "Allow collision on player objects."); + addField("hitVehicles", TypeBool, Offset(mDropHitVehicles, Precipitation), "Allow collision on vechiles."); + endGroup("Collision"); + + addGroup("Movement"); + addField("followCam", TypeBool, Offset(mFollowCam, Precipitation), "Enables system to follow the camera or stay where it is placed."); + addField("useWind", TypeBool, Offset(mUseWind, Precipitation), "Check to have the Sky property windSpeed affect precipitation."); + addField("minSpeed", TypeF32, Offset(mMinSpeed, Precipitation), "Minimum speed that a drop will fall."); + addField("maxSpeed", TypeF32, Offset(mMaxSpeed, Precipitation), "Maximum speed that a drop will fall."); + addField("minMass", TypeF32, Offset(mMinMass, Precipitation), "Minimum mass of a drop."); + addField("maxMass", TypeF32, Offset(mMaxMass, Precipitation), "Maximum mass of a drop."); + endGroup("Movement"); + + addGroup("Turbulence"); + addField("useTurbulence", TypeBool, Offset(mUseTurbulence, Precipitation), "Check to enable turubulence. This " + "causes precipitation drops to spiral while falling."); + addField("maxTurbulence", TypeF32, Offset(mMaxTurbulence, Precipitation), "Radius at which precipitation drops " + "spiral when turbulence is enabled."); + addField("turbulenceSpeed", TypeF32, Offset(mTurbulenceSpeed, Precipitation), "Speed at which precipitation drops " + "spiral when turbulence is enabled."); + endGroup("Turbulence"); + + addField("numDrops", TypeS32, Offset(mNumDrops, Precipitation), "Number of drops allowed to exists " + "in the precipitation box at any one time."); + addField("boxWidth", TypeF32, Offset(mBoxWidth, Precipitation), "Width of precipitation box."); + addField("boxHeight", TypeF32, Offset(mBoxHeight, Precipitation), "Height of precipitation box."); + + Parent::initPersistFields(); +} + +//----------------------------------- +// Console methods... +ConsoleMethod(Precipitation, setPercentange, void, 3, 3, "precipitation.setPercentage(percentage <0.0 to 1.0>)") +{ + object->setPercentage(dAtof(argv[2])); +} + +ConsoleMethod(Precipitation, modifyStorm, void, 4, 4, "precipitation.modifyStorm(Percentage <0.0 to 1.0>, Time)") +{ + object->modifyStorm(dAtof(argv[2]), S32(dAtof(argv[3]) * 1000.0f)); +} + +ConsoleMethod(Precipitation, setTurbulence, void, 5, 5, "%precip.setTurbulence(max, speed, seconds)") +{ + object->setTurbulence( dAtof(argv[2]), dAtof(argv[3]), S32(dAtof(argv[4]) * 1000.0f)); +} + +//-------------------------------------------------------------------------- +// Backend +//-------------------------------------------------------------------------- +bool Precipitation::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (mFollowCam) + { + setGlobalBounds(); + } + else + { + mObjBox.minExtents = -Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2); + mObjBox.maxExtents = Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2); + } + resetWorldBox(); + + if (isClientObject()) + { + fillDropList(); + initRenderObjects(); + initMaterials(); + } + + addToScene(); + + return true; +} + +void Precipitation::onRemove() +{ + removeFromScene(); + Parent::onRemove(); + + SFX_DELETE( mAmbientSound ); + + if (isClientObject()) + killDropList(); +} + +bool Precipitation::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + if (isClientObject()) + { + SFX_DELETE( mAmbientSound ); + + if ( mDataBlock->soundProfile ) + { + mAmbientSound = SFX->createSource( mDataBlock->soundProfile, &getTransform() ); + if ( mAmbientSound ) + mAmbientSound->play(); + } + + initRenderObjects(); + initMaterials(); + } + + scriptOnNewDataBlock(); + return true; +} + +void Precipitation::initMaterials() +{ + AssertFatal(isClientObject(), "Precipitation is setting materials on the server - BAD!"); + + if(!mDataBlock) + return; + + PrecipitationData *pd = (PrecipitationData*)mDataBlock; + + mDropHandle = NULL; + mSplashHandle = NULL; + mDropShader = NULL; + mSplashShader = NULL; + + if( dStrlen(pd->mDropName) > 0 && !mDropHandle.set(pd->mDropName, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDropHandle (line %d)", __FUNCTION__, __LINE__)) ) + Con::warnf("Precipitation::initMaterials - failed to locate texture '%s'!", pd->mDropName); + + if ( dStrlen(pd->mDropShaderName) > 0 ) + { + ShaderData *shaderData; + if ( Sim::findObject( pd->mDropShaderName, shaderData ) ) + mDropShader = shaderData->getShader(); + + if( !mDropShader ) + Con::warnf( "Precipitation::initMaterials - could not find shader '%s'!", pd->mDropShaderName ); + else + { + mDropShaderConsts = mDropShader->allocConstBuffer(); + shaderData->mapSamplerNames(mDropShaderConsts); + mDropShaderModelViewSC = mDropShader->getShaderConstHandle("$modelView"); + mDropShaderFadeStartEndSC = mDropShader->getShaderConstHandle("$fadeStartEnd"); + mDropShaderCameraPosSC = mDropShader->getShaderConstHandle("$cameraPos"); + mDropShaderAmbientSC = mDropShader->getShaderConstHandle("$ambient"); + } + } + + if( dStrlen(pd->mSplashName) > 0 && !mSplashHandle.set(pd->mSplashName, &GFXDefaultStaticDiffuseProfile, avar("%s() - mSplashHandle (line %d)", __FUNCTION__, __LINE__)) ) + Con::warnf("Precipitation::initMaterials - failed to locate texture '%s'!", pd->mSplashName); + + if ( dStrlen(pd->mSplashShaderName) > 0 ) + { + ShaderData *shaderData; + if ( Sim::findObject( pd->mSplashShaderName, shaderData ) ) + mSplashShader = shaderData->getShader(); + + if( !mSplashShader ) + Con::warnf( "Precipitation::initMaterials - could not find shader '%s'!", pd->mSplashShaderName ); + else + { + mSplashShaderConsts = mSplashShader->allocConstBuffer(); + shaderData->mapSamplerNames(mSplashShaderConsts); + mSplashShaderModelViewSC = mSplashShader->getShaderConstHandle("$modelView"); + mSplashShaderFadeStartEndSC = mSplashShader->getShaderConstHandle("$fadeStartEnd"); + mSplashShaderCameraPosSC = mSplashShader->getShaderConstHandle("$cameraPos"); + mSplashShaderAmbientSC = mSplashShader->getShaderConstHandle("$ambient"); + } + } +} + +U32 Precipitation::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag( !mFollowCam && mask & TransformMask)) + stream->writeAffineTransform(mObjToWorld); + + if (stream->writeFlag(mask & DataMask)) + { + stream->write(mDropSize); + stream->write(mSplashSize); + stream->write(mSplashMS); + stream->write(mDropAnimateMS); + stream->write(mNumDrops); + stream->write(mMinSpeed); + stream->write(mMaxSpeed); + stream->write(mBoxWidth); + stream->write(mBoxHeight); + stream->write(mMinMass); + stream->write(mMaxMass); + stream->write(mMaxTurbulence); + stream->write(mTurbulenceSpeed); + stream->write(mFadeDistance); + stream->write(mFadeDistanceEnd); + stream->write(mGlowIntensity.red); + stream->write(mGlowIntensity.green); + stream->write(mGlowIntensity.blue); + stream->write(mGlowIntensity.alpha); + stream->writeFlag(mReflect); + stream->writeFlag(mRotateWithCamVel); + stream->writeFlag(mDoCollision); + stream->writeFlag(mDropHitPlayers); + stream->writeFlag(mDropHitVehicles); + stream->writeFlag(mUseTrueBillboards); + stream->writeFlag(mUseTurbulence); + stream->writeFlag(mUseLighting); + stream->writeFlag(mUseWind); + stream->writeFlag(mFollowCam); + stream->writeFlag(mAnimateSplashes); + } + + if (stream->writeFlag(!(mask & DataMask) && (mask & TurbulenceMask))) + { + stream->write(mTurbulenceData.endMax); + stream->write(mTurbulenceData.endSpeed); + stream->write(mTurbulenceData.totalTime); + } + + if (stream->writeFlag(mask & PercentageMask)) + { + stream->write(mPercentage); + } + + if (stream->writeFlag(!(mask & ~(DataMask | PercentageMask | StormMask)) && (mask & StormMask))) + { + stream->write(mStormData.endPct); + stream->write(mStormData.totalTime); + } + + return 0; +} + +void Precipitation::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + MatrixF mat; + stream->readAffineTransform(&mat); + Parent::setTransform(mat); + } + + U32 oldDrops = U32(mNumDrops * mPercentage); + if (stream->readFlag()) + { + stream->read(&mDropSize); + stream->read(&mSplashSize); + stream->read(&mSplashMS); + stream->read(&mDropAnimateMS); + stream->read(&mNumDrops); + stream->read(&mMinSpeed); + stream->read(&mMaxSpeed); + stream->read(&mBoxWidth); + stream->read(&mBoxHeight); + stream->read(&mMinMass); + stream->read(&mMaxMass); + stream->read(&mMaxTurbulence); + stream->read(&mTurbulenceSpeed); + stream->read(&mFadeDistance); + stream->read(&mFadeDistanceEnd); + stream->read(&mGlowIntensity.red); + stream->read(&mGlowIntensity.green); + stream->read(&mGlowIntensity.blue); + stream->read(&mGlowIntensity.alpha); + mReflect = stream->readFlag(); + mRotateWithCamVel = stream->readFlag(); + mDoCollision = stream->readFlag(); + mDropHitPlayers = stream->readFlag(); + mDropHitVehicles = stream->readFlag(); + mUseTrueBillboards = stream->readFlag(); + mUseTurbulence = stream->readFlag(); + mUseLighting = stream->readFlag(); + mUseWind = stream->readFlag(); + mFollowCam = stream->readFlag(); + mAnimateSplashes = stream->readFlag(); + + mDropHitMask = dropHitMask | + ( mDropHitPlayers ? PlayerObjectType : 0 ) | + ( mDropHitVehicles ? VehicleObjectType : 0 ); + + mTurbulenceData.valid = false; + } + + if (stream->readFlag()) + { + F32 max, speed; + U32 ms; + stream->read(&max); + stream->read(&speed); + stream->read(&ms); + setTurbulence( max, speed, ms ); + } + + if (stream->readFlag()) + { + F32 pct; + stream->read(&pct); + setPercentage(pct); + } + + if (stream->readFlag()) + { + F32 pct; + U32 time; + stream->read(&pct); + stream->read(&time); + modifyStorm(pct, time); + } + + AssertFatal(isClientObject(), "Precipitation::unpackUpdate() should only be called on the client!"); + + U32 newDrops = U32(mNumDrops * mPercentage); + if (oldDrops != newDrops) + { + fillDropList(); + initRenderObjects(); + } + + if (mFollowCam) + { + setGlobalBounds(); + } + else + { + mObjBox.minExtents = -Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2); + mObjBox.maxExtents = Point3F(mBoxWidth/2, mBoxWidth/2, mBoxHeight/2); + } + + resetWorldBox(); +} + +//-------------------------------------------------------------------------- +// Support functions +//-------------------------------------------------------------------------- +VectorF Precipitation::getWindVelocity() +{ + // TODO: Fix me... this shouldn't be in the sky! + //Sky* sky = gClientSceneGraph->getCurrentSky(); + //(sky && mUseWind) ? -sky->getWindVelocity() : VectorF(0,0,0); + return VectorF::Zero; +} + +void Precipitation::fillDropList() +{ + AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!"); + + F32 density = Con::getFloatVariable("$pref::precipitationDensity", 1.0f); + U32 newDropCount = (U32)(mNumDrops * mPercentage * density); + U32 dropCount = 0; + + if (newDropCount == 0) + killDropList(); + + if (mDropHead) + { + Raindrop* curr = mDropHead; + while (curr) + { + dropCount++; + curr = curr->next; + if (dropCount == newDropCount && curr) + { + //delete the remaining drops + Raindrop* next = curr->next; + curr->next = NULL; + while (next) + { + Raindrop* last = next; + next = next->next; + last->next = NULL; + destroySplash(last); + delete last; + } + break; + } + } + } + + if (dropCount < newDropCount) + { + //move to the end + Raindrop* curr = mDropHead; + if (curr) + { + while (curr->next) + curr = curr->next; + } + else + { + mDropHead = curr = new Raindrop; + spawnNewDrop(curr); + dropCount++; + } + + //and add onto it + while (dropCount < newDropCount) + { + curr->next = new Raindrop; + curr = curr->next; + spawnNewDrop(curr); + dropCount++; + } + } +} + +void Precipitation::initRenderObjects() +{ + AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!"); + + SAFE_DELETE_ARRAY(mTexCoords); + SAFE_DELETE_ARRAY(mSplashCoords); + + if (!mDataBlock) + return; + + mTexCoords = new Point2F[4*mDataBlock->mDropsPerSide*mDataBlock->mDropsPerSide]; + + // Setup the texcoords for the drop texture. + // The order of the coords when animating is... + // + // +---+---+---+ + // | 1 | 2 | 3 | + // |---|---|---+ + // | 4 | 5 | 6 | + // +---+---+---+ + // | 7 | etc... + // +---+ + // + U32 count = 0; + for (U32 v = 0; v < mDataBlock->mDropsPerSide; v++) + { + F32 y1 = (F32) v / mDataBlock->mDropsPerSide; + F32 y2 = (F32)(v+1) / mDataBlock->mDropsPerSide; + for (U32 u = 0; u < mDataBlock->mDropsPerSide; u++) + { + F32 x1 = (F32) u / mDataBlock->mDropsPerSide; + F32 x2 = (F32)(u+1) / mDataBlock->mDropsPerSide; + + mTexCoords[4*count+0].x = x1; + mTexCoords[4*count+0].y = y1; + + mTexCoords[4*count+1].x = x2; + mTexCoords[4*count+1].y = y1; + + mTexCoords[4*count+2].x = x2; + mTexCoords[4*count+2].y = y2; + + mTexCoords[4*count+3].x = x1; + mTexCoords[4*count+3].y = y2; + count++; + } + } + + count = 0; + mSplashCoords = new Point2F[4*mDataBlock->mSplashesPerSide*mDataBlock->mSplashesPerSide]; + for (U32 v = 0; v < mDataBlock->mSplashesPerSide; v++) + { + F32 y1 = (F32) v / mDataBlock->mSplashesPerSide; + F32 y2 = (F32)(v+1) / mDataBlock->mSplashesPerSide; + for (U32 u = 0; u < mDataBlock->mSplashesPerSide; u++) + { + F32 x1 = (F32) u / mDataBlock->mSplashesPerSide; + F32 x2 = (F32)(u+1) / mDataBlock->mSplashesPerSide; + + mSplashCoords[4*count+0].x = x1; + mSplashCoords[4*count+0].y = y1; + + mSplashCoords[4*count+1].x = x2; + mSplashCoords[4*count+1].y = y1; + + mSplashCoords[4*count+2].x = x2; + mSplashCoords[4*count+2].y = y2; + + mSplashCoords[4*count+3].x = x1; + mSplashCoords[4*count+3].y = y2; + count++; + } + } + + // Cap the number of precipitation drops so that we don't blow out the max verts + mMaxVBDrops = getMin( (U32)mNumDrops, ( GFX->getMaxDynamicVerts() / 4 ) - 1 ); + + // If we have no drops then skip allocating anything! + if ( mMaxVBDrops == 0 ) + return; + + // Create a volitile vertex buffer which + // we'll lock and fill every frame. + mRainVB.set(GFX, mMaxVBDrops * 4, GFXBufferTypeVolatile); + + // Init the index buffer for rendering the + // entire or a partially filled vb. + mRainIB.set(GFX, mMaxVBDrops * 6, 0, GFXBufferTypeStatic); + U16 *idxBuff; + mRainIB.lock(&idxBuff, NULL, NULL, NULL); + for( U32 i=0; i < mMaxVBDrops; i++ ) + { + // + // The vertex pattern in the VB for each + // particle is as follows... + // + // 0----1 + // |\ | + // | \ | + // | \ | + // | \| + // 3----2 + // + // We setup the index order below to ensure + // sequential, cache friendly, access. + // + U32 offset = i * 4; + idxBuff[i*6+0] = 0 + offset; + idxBuff[i*6+1] = 1 + offset; + idxBuff[i*6+2] = 2 + offset; + idxBuff[i*6+3] = 2 + offset; + idxBuff[i*6+4] = 3 + offset; + idxBuff[i*6+5] = 0 + offset; + } + mRainIB.unlock(); +} + +void Precipitation::killDropList() +{ + AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!"); + + Raindrop* curr = mDropHead; + while (curr) + { + Raindrop* next = curr->next; + delete curr; + curr = next; + } + mDropHead = NULL; + mSplashHead = NULL; +} + +void Precipitation::spawnDrop(Raindrop *drop) +{ + PROFILE_START(PrecipSpawnDrop); + AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!"); + + drop->velocity = Platform::getRandom() * (mMaxSpeed - mMinSpeed) + mMinSpeed; + + drop->position.x = Platform::getRandom() * mBoxWidth; + drop->position.y = Platform::getRandom() * mBoxWidth; + + // The start time should be randomized so that + // all the drops are not animating at the same time. + drop->animStartTime = (SimTime)(Platform::getVirtualMilliseconds() * Platform::getRandom()); + + if (mDropAnimateMS <= 0 && mDataBlock) + drop->texCoordIndex = (U32)(Platform::getRandom() * ((F32)mDataBlock->mDropsPerSide*mDataBlock->mDropsPerSide - 0.5)); + + drop->valid = true; + drop->time = Platform::getRandom() * M_2PI; + drop->mass = Platform::getRandom() * (mMaxMass - mMinMass) + mMinMass; + PROFILE_END(); +} + +void Precipitation::spawnNewDrop(Raindrop *drop) +{ + AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!"); + + spawnDrop(drop); + drop->position.z = Platform::getRandom() * mBoxHeight - (mBoxHeight / 2); +} + +void Precipitation::wrapDrop(Raindrop *drop, const Box3F &box, const U32 currTime, const VectorF &windVel) +{ + //could probably be slightly optimized to get rid of the while loops + if (drop->position.z < box.minExtents.z) + { + spawnDrop(drop); + drop->position.x += box.minExtents.x; + drop->position.y += box.minExtents.y; + while (drop->position.z < box.minExtents.z) + drop->position.z += mBoxHeight; + findDropCutoff(drop, box, windVel); + } + else if (drop->position.z > box.maxExtents.z) + { + while (drop->position.z > box.maxExtents.z) + drop->position.z -= mBoxHeight; + findDropCutoff(drop, box, windVel); + } + else if (drop->position.x < box.minExtents.x) + { + while (drop->position.x < box.minExtents.x) + drop->position.x += mBoxWidth; + findDropCutoff(drop, box, windVel); + } + else if (drop->position.x > box.maxExtents.x) + { + while (drop->position.x > box.maxExtents.x) + drop->position.x -= mBoxWidth; + findDropCutoff(drop, box, windVel); + } + else if (drop->position.y < box.minExtents.y) + { + while (drop->position.y < box.minExtents.y) + drop->position.y += mBoxWidth; + findDropCutoff(drop, box, windVel); + } + else if (drop->position.y > box.maxExtents.y) + { + while (drop->position.y > box.maxExtents.y) + drop->position.y -= mBoxWidth; + findDropCutoff(drop, box, windVel); + } +} + +void Precipitation::findDropCutoff(Raindrop *drop, const Box3F &box, const VectorF &windVel) +{ + PROFILE_START(PrecipFindDropCutoff); + AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!"); + + if (mDoCollision) + { + VectorF velocity = windVel / drop->mass - VectorF(0, 0, drop->velocity); + velocity.normalize(); + + Point3F end = drop->position + 100 * velocity; + Point3F start = drop->position - (mFollowCam ? 500.0f : 0.0f) * velocity; + + if (!mFollowCam) + { + mObjToWorld.mulP(start); + mObjToWorld.mulP(end); + } + + // Look for a collision... make sure we don't + // collide with backfaces. + RayInfo rInfo; + if (getContainer()->castRay(start, end, mDropHitMask, &rInfo)) + { + // TODO: Add check to filter out hits on backfaces. + + if (!mFollowCam) + mWorldToObj.mulP(rInfo.point); + + drop->hitPos = rInfo.point; + drop->hitType = rInfo.object->getTypeMask(); + } + else + drop->hitPos = Point3F(0,0,-1000); + + drop->valid = drop->position.z > drop->hitPos.z; + } + else + { + drop->hitPos = Point3F(0,0,-1000); + drop->valid = true; + } + PROFILE_END(); +} + +void Precipitation::createSplash(Raindrop *drop) +{ + if (!mDataBlock) + return; + + PROFILE_START(PrecipCreateSplash); + if (drop != mSplashHead && !(drop->nextSplashDrop || drop->prevSplashDrop)) + { + if (!mSplashHead) + { + mSplashHead = drop; + drop->prevSplashDrop = NULL; + drop->nextSplashDrop = NULL; + } + else + { + mSplashHead->prevSplashDrop = drop; + drop->nextSplashDrop = mSplashHead; + drop->prevSplashDrop = NULL; + mSplashHead = drop; + } + } + + drop->animStartTime = Platform::getVirtualMilliseconds(); + + if (!mAnimateSplashes) + drop->texCoordIndex = (U32)(Platform::getRandom() * ((F32)mDataBlock->mSplashesPerSide*mDataBlock->mSplashesPerSide - 0.5)); + + PROFILE_END(); +} + +void Precipitation::destroySplash(Raindrop *drop) +{ + PROFILE_START(PrecipDestroySplash); + if (drop == mSplashHead) + { + mSplashHead = NULL; + PROFILE_END(); + return; + } + + if (drop->nextSplashDrop) + drop->nextSplashDrop->prevSplashDrop = drop->prevSplashDrop; + if (drop->prevSplashDrop) + drop->prevSplashDrop->nextSplashDrop = drop->nextSplashDrop; + + drop->nextSplashDrop = NULL; + drop->prevSplashDrop = NULL; + + PROFILE_END(); +} + +//-------------------------------------------------------------------------- +// Processing +//-------------------------------------------------------------------------- +void Precipitation::setPercentage(F32 pct) +{ + mPercentage = mClampF(pct, 0, 1); + mStormData.valid = false; + + if (isServerObject()) + { + setMaskBits(PercentageMask); + } +} + +void Precipitation::modifyStorm(F32 pct, U32 ms) +{ + if ( ms == 0 ) + { + setPercentage( pct ); + return; + } + + pct = mClampF(pct, 0, 1); + mStormData.endPct = pct; + mStormData.totalTime = ms; + + if (isServerObject()) + { + setMaskBits(StormMask); + return; + } + + mStormData.startTime = Platform::getVirtualMilliseconds(); + mStormData.startPct = mPercentage; + mStormData.valid = true; +} + +void Precipitation::setTurbulence(F32 max, F32 speed, U32 ms) +{ + if ( ms == 0 && !isServerObject() ) + { + mUseTurbulence = max > 0; + mMaxTurbulence = max; + mTurbulenceSpeed = speed; + return; + } + + mTurbulenceData.endMax = max; + mTurbulenceData.endSpeed = speed; + mTurbulenceData.totalTime = ms; + + if (isServerObject()) + { + setMaskBits(TurbulenceMask); + return; + } + + mTurbulenceData.startTime = Platform::getVirtualMilliseconds(); + mTurbulenceData.startMax = mMaxTurbulence; + mTurbulenceData.startSpeed = mTurbulenceSpeed; + mTurbulenceData.valid = true; +} + +void Precipitation::interpolateTick(F32 delta) +{ + AssertFatal(isClientObject(), "Precipitation is doing stuff on the server - BAD!"); + + // If we're not being seen then the simulation + // is paused and we don't need any interpolation. + if (mLastRenderFrame != ShapeBase::sLastRenderFrame) + return; + + PROFILE_START(PrecipInterpolate); + + const F32 dt = 1-delta; + const VectorF windVel = dt * getWindVelocity(); + const F32 turbSpeed = dt * mTurbulenceSpeed; + + Raindrop* curr = mDropHead; + VectorF turbulence; + F32 renderTime; + + while (curr) + { + if (!curr->valid || !curr->toRender) + { + curr = curr->next; + continue; + } + + if (mUseTurbulence) + { + renderTime = curr->time + turbSpeed; + turbulence.x = windVel.x + ( mSin(renderTime) * mMaxTurbulence ); + turbulence.y = windVel.y + ( mCos(renderTime) * mMaxTurbulence ); + turbulence.z = windVel.z; + curr->renderPosition = curr->position + turbulence / curr->mass; + } + else + curr->renderPosition = curr->position + windVel / curr->mass; + + curr->renderPosition.z -= dt * curr->velocity; + + curr = curr->next; + } + PROFILE_END(); +} + +void Precipitation::processTick(const Move *) +{ + //nothing to do on the server + if (isServerObject() || mDataBlock == NULL) + return; + + const U32 currTime = Platform::getVirtualMilliseconds(); + + // Update the storm if necessary + if (mStormData.valid) + { + F32 t = (currTime - mStormData.startTime) / (F32)mStormData.totalTime; + if (t >= 1) + { + mPercentage = mStormData.endPct; + mStormData.valid = false; + } + else + mPercentage = mStormData.startPct * (1-t) + mStormData.endPct * t; + + fillDropList(); + } + + // Do we need to update the turbulence? + if ( mTurbulenceData.valid ) + { + F32 t = (currTime - mTurbulenceData.startTime) / (F32)mTurbulenceData.totalTime; + if (t >= 1) + { + mMaxTurbulence = mTurbulenceData.endMax; + mTurbulenceSpeed = mTurbulenceData.endSpeed; + mTurbulenceData.valid = false; + } + else + { + mMaxTurbulence = mTurbulenceData.startMax * (1-t) + mTurbulenceData.endMax * t; + mTurbulenceSpeed = mTurbulenceData.startSpeed * (1-t) + mTurbulenceData.endSpeed * t; + } + + mUseTurbulence = mMaxTurbulence > 0; + } + + // If we're not being seen then pause the + // simulation. Precip is generally noisy + // enough that no one should notice. + if (mLastRenderFrame != ShapeBase::sLastRenderFrame) + return; + + //we need to update positions and do some collision here + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) + return; //need connection to server + + ShapeBase* camObj = dynamic_cast(conn->getCameraObject()); + if (!camObj) + return; + + PROFILE_START(PrecipProcess); + + MatrixF camMat; + Point3F velocity; + GameGetCameraTransform(&camMat,&velocity); + //camObj->getEyeTransform(&camMat); + + const F32 camFov = camObj->getCameraFov(); + + Point3F camPos, camDir; + Box3F box; + + if (mFollowCam) + { + camMat.getColumn(3, &camPos); + + box = Box3F(camPos.x - mBoxWidth / 2, camPos.y - mBoxWidth / 2, camPos.z - mBoxHeight / 2, + camPos.x + mBoxWidth / 2, camPos.y + mBoxWidth / 2, camPos.z + mBoxHeight / 2); + + camMat.getColumn(1, &camDir); + camDir.normalize(); + } + else + { + box = mObjBox; + + camMat.getColumn(3, &camPos); + mWorldToObj.mulP(camPos); + + camMat.getColumn(1, &camDir); + camDir.normalize(); + mWorldToObj.mulV(camDir); + } + + const VectorF windVel = getWindVelocity(); + const F32 fovDot = camFov / 180; + + Raindrop* curr = mDropHead; + + //offset the renderbox in the direction of the camera direction + //in order to have more of the drops actually rendered + if (mFollowCam) + { + box.minExtents.x += camDir.x * mBoxWidth / 4; + box.maxExtents.x += camDir.x * mBoxWidth / 4; + box.minExtents.y += camDir.y * mBoxWidth / 4; + box.maxExtents.y += camDir.y * mBoxWidth / 4; + box.minExtents.z += camDir.z * mBoxHeight / 4; + box.maxExtents.z += camDir.z * mBoxHeight / 4; + } + + VectorF lookVec; + F32 pct; + const S32 dropCount = mDataBlock->mDropsPerSide*mDataBlock->mDropsPerSide; + while (curr) + { + // Update the position. This happens even if this + // is a splash so that the drop respawns when it wraps + // around to the top again. + if (mUseTurbulence) + curr->time += mTurbulenceSpeed; + curr->position += windVel / curr->mass; + curr->position.z -= curr->velocity; + + // Wrap the drop if it reaches an edge of the box. + wrapDrop(curr, box, currTime, windVel); + + // Did the drop pass below the hit position? + if (curr->valid && curr->position.z < curr->hitPos.z) + { + // If this drop was to hit a player or vehicle double + // check to see if the object has moved out of the way. + // This keeps us from leaving phantom trails of splashes + // behind a moving player/vehicle. + if (curr->hitType & (PlayerObjectType | VehicleObjectType)) + { + findDropCutoff(curr, box, windVel); + + if (curr->position.z > curr->hitPos.z) + goto NO_SPLASH; // Ugly, yet simple. + } + + // The drop is dead. + curr->valid = false; + + // Convert the drop into a splash or let it + // wrap around and respawn in wrapDrop(). + if (mSplashMS > 0) + createSplash(curr); + + // So ugly... yet simple. +NO_SPLASH:; + } + + // We do not do cull individual drops when we're not + // following as it is usually a tight box and all of + // the particles are in view. + if (!mFollowCam) + curr->toRender = true; + else + { + lookVec = curr->position - camPos; + curr->toRender = mDot(lookVec, camDir) > fovDot; + } + + // Do we need to animate the drop? + if (curr->valid && mDropAnimateMS > 0 && curr->toRender) + { + pct = (F32)(currTime - curr->animStartTime) / mDropAnimateMS; + pct = mFmod(pct, 1); + curr->texCoordIndex = (U32)(dropCount * pct); + } + + curr = curr->next; + } + + //update splashes + curr = mSplashHead; + Raindrop *next; + const S32 splashCount = mDataBlock->mSplashesPerSide * mDataBlock->mSplashesPerSide; + while (curr) + { + pct = (F32)(currTime - curr->animStartTime) / mSplashMS; + if (pct >= 1.0f) + { + next = curr->nextSplashDrop; + destroySplash(curr); + curr = next; + continue; + } + + if (mAnimateSplashes) + curr->texCoordIndex = (U32)(splashCount * pct); + + curr = curr->nextSplashDrop; + } + + PROFILE_END_NAMED(PrecipProcess); +} + +//-------------------------------------------------------------------------- +// Rendering +//-------------------------------------------------------------------------- +bool Precipitation::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + + PROFILE_START(Precipitation_prepRenderImage); + + setLastState(state, stateKey); + + // We we have no drops then skip rendering + // and don't bother with the sound. + if (mMaxVBDrops == 0) + { + PROFILE_END(); + return false; + } + + // We do nothing if we're not supposed to be reflected. + if ( state->isReflectPass() && !mReflect ) + { + PROFILE_END(); + return false; + } + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &Precipitation::renderObject); + ri->type = RenderPassManager::RIT_Foliage; + state->getRenderPass()->addInst( ri ); + } + + PROFILE_END(); + return false; +} + +void Precipitation::renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + +#ifdef TORQUE_OS_XENON + return; +#endif + + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) + return; //need connection to server + + ShapeBase* camObj = dynamic_cast(conn->getCameraObject()); + if (!camObj) + return; // need camera object + + PROFILE_START(PrecipRender); + + GFX->pushWorldMatrix(); + + MatrixF world = GFX->getWorldMatrix(); + MatrixF proj = GFX->getProjectionMatrix(); + if (!mFollowCam) + { + world.mul( getRenderTransform() ); + world.scale( getScale() ); + GFX->setWorldMatrix( world ); + } + proj.mul(world); + + //GFX2 doesn't require transpose? + //proj.transpose(); + + Point3F camPos = state->getCameraPosition(); + VectorF camVel = camObj->getVelocity(); + if (!mFollowCam) + { + getRenderWorldTransform().mulP(camPos); + getRenderWorldTransform().mulV(camVel); + } + const VectorF windVel = getWindVelocity(); + const bool useBillboards = mUseTrueBillboards; + const F32 dropSize = mDropSize; + + Point3F pos; + VectorF orthoDir, velocity, right, up, rightUp(0.0f, 0.0f, 0.0f), leftUp(0.0f, 0.0f, 0.0f); + F32 distance = 0; + GFXVertexPT* vertPtr = NULL; + const Point2F *tc; + + // Do this here and we won't have to in the loop! + if (useBillboards) + { + MatrixF camMat = state->getCameraTransform(); + camMat.inverse(); + camMat.getRow(0,&right); + camMat.getRow(2,&up); + if (!mFollowCam) + { + mWorldToObj.mulV(right); + mWorldToObj.mulV(up); + } + right.normalize(); + up.normalize(); + right *= mDropSize; + up *= mDropSize; + rightUp = right + up; + leftUp = -right + up; + } + + // We pass the sunlight as a constant to the + // shader. Once the lighting and shadow systems + // are added into TSE we can expand this to include + // the N nearest lights to the camera + the ambient. + ColorF ambient( 1, 1, 1 ); + if ( mUseLighting ) + { + const LightInfo *sunlight = gClientSceneGraph->getLightManager()->getSpecialLight(LightManager::slSunLightType); + ambient = sunlight->getColor(); + } + + if ( mGlowIntensity.red > 0 || + mGlowIntensity.green > 0 || + mGlowIntensity.blue > 0 ) + { + ambient *= mGlowIntensity; + } + + // Setup render state + + if (mDefaultSB.isNull()) + { + GFXStateBlockDesc desc; + + desc.zWriteEnable = false; + desc.setAlphaTest(true, GFXCmpGreaterEqual, 1); + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + + mDefaultSB = GFX->createStateBlock(desc); + + desc.samplersDefined = true; + desc.samplers[0].textureColorOp = GFXTOPModulate; + desc.samplers[0].colorArg1 = GFXTATexture; + desc.samplers[0].colorArg2 = GFXTADiffuse; + desc.samplers[0].alphaOp = GFXTOPSelectARG1; + desc.samplers[0].alphaArg1 = GFXTATexture; + + desc.samplers[1].textureColorOp = GFXTOPDisable; + desc.samplers[1].alphaOp = GFXTOPDisable; + + mDistantSB = GFX->createStateBlock(desc); + } + + GFX->setStateBlock(mDefaultSB); + + // Everything is rendered from these buffers. + GFX->setPrimitiveBuffer(mRainIB); + GFX->setVertexBuffer(mRainVB); + + // Set the constants used by the shaders. + if (mDropShader) + { + Point2F fadeStartEnd( mFadeDistance, mFadeDistanceEnd ); + + if (mDropShaderModelViewSC->isValid()) + mDropShaderConsts->set(mDropShaderModelViewSC, proj); + + if (mDropShaderFadeStartEndSC->isValid()) + mDropShaderConsts->set(mDropShaderFadeStartEndSC, fadeStartEnd); + + if (mDropShaderCameraPosSC->isValid()) + mDropShaderConsts->set(mDropShaderCameraPosSC, camPos); + + if (mDropShaderAmbientSC->isValid()) + mDropShaderConsts->set(mDropShaderAmbientSC, Point3F(ambient.red, ambient.green, ambient.blue)); + } + + + if (mSplashShader) + { + Point2F fadeStartEnd( mFadeDistance, mFadeDistanceEnd ); + + if (mSplashShaderModelViewSC) + mSplashShaderConsts->set(mSplashShaderModelViewSC, proj); + + if (mSplashShaderFadeStartEndSC) + mSplashShaderConsts->set(mSplashShaderFadeStartEndSC, fadeStartEnd); + + if (mSplashShaderCameraPosSC) + mSplashShaderConsts->set(mSplashShaderCameraPosSC, camPos); + + if (mSplashShaderAmbientSC) + mSplashShaderConsts->set(mSplashShaderAmbientSC, Point3F(ambient.red, ambient.green, ambient.blue)); + } + + // Time to render the drops... + const Raindrop *curr = mDropHead; + U32 vertCount = 0; + + GFX->setTexture(0, mDropHandle); + + // Use the shader or setup the pipeline + // for fixed function rendering. + if (mDropShader) + { + GFX->setShader( mDropShader ); + GFX->setShaderConstBuffer( mDropShaderConsts ); + } + else + { + GFX->disableShaders(); + + // We don't support distance fade or lighting without shaders. + GFX->setStateBlock(mDistantSB); + } + + while (curr) + { + // Skip ones that are not drops (hit something and + // may have been converted into a splash) or they + // are behind the camera. + if (!curr->valid || !curr->toRender) + { + curr = curr->next; + continue; + } + + pos = curr->renderPosition; + + // two forms of billboards - true billboards (which we set + // above outside this loop) or axis-aligned with velocity + // (this codeblock) the axis-aligned billboards are aligned + // with the velocity of the raindrop, and tilted slightly + // towards the camera + if (!useBillboards) + { + orthoDir = camPos - pos; + distance = orthoDir.len(); + + // Inline the normalize so we don't + // calculate the ortho len twice. + if (distance > 0.0) + orthoDir *= 1.0f / distance; + else + orthoDir.set( 0, 0, 1 ); + + velocity = windVel / curr->mass; + + // We do not optimize this for the "still" case + // because its not a typical scenario. + if (mRotateWithCamVel) + velocity -= camVel / (distance > 2.0f ? distance : 2.0f) * 0.3f; + + velocity.z -= curr->velocity; + velocity.normalize(); + + right = mCross(-velocity, orthoDir); + right.normalize(); + up = mCross(orthoDir, right) * 0.5 - velocity * 0.5; + up.normalize(); + right *= dropSize; + up *= dropSize; + rightUp = right + up; + leftUp = -right + up; + } + + // Do we need to relock the buffer? + if ( !vertPtr ) + vertPtr = mRainVB.lock(); + + // Set the proper texture coords... (it's fun!) + tc = &mTexCoords[4*curr->texCoordIndex]; + vertPtr->point = pos + leftUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + vertPtr->point = pos + rightUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + vertPtr->point = pos - leftUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + vertPtr->point = pos - rightUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + // Do we need to render to clear the buffer? + vertCount += 4; + if ( (vertCount + 4) >= mRainVB->mNumVerts ) { + + mRainVB.unlock(); + GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2); + vertPtr = NULL; + vertCount = 0; + } + + curr = curr->next; + } + + // Do we have stuff left to render? + if ( vertCount > 0 ) { + + mRainVB.unlock(); + GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2); + vertCount = 0; + vertPtr = NULL; + } + + // Setup the billboard for the splashes. + MatrixF camMat = state->getCameraTransform(); + camMat.inverse(); + camMat.getRow(0, &right); + camMat.getRow(2, &up); + if (!mFollowCam) + { + mWorldToObj.mulV(right); + mWorldToObj.mulV(up); + } + right.normalize(); + up.normalize(); + right *= mSplashSize; + up *= mSplashSize; + rightUp = right + up; + leftUp = -right + up; + + // Render the visible splashes. + curr = mSplashHead; + + GFX->setTexture(0, mSplashHandle); + + if (mSplashShader) + { + GFX->setShader( mSplashShader ); + GFX->setShaderConstBuffer(mSplashShaderConsts); + } + else + GFX->disableShaders(); + + while (curr) + { + if (!curr->toRender) + { + curr = curr->nextSplashDrop; + continue; + } + + pos = curr->hitPos; + + tc = &mSplashCoords[4*curr->texCoordIndex]; + + // Do we need to relock the buffer? + if ( !vertPtr ) + vertPtr = mRainVB.lock(); + + vertPtr->point = pos + leftUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + vertPtr->point = pos + rightUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + vertPtr->point = pos - leftUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + vertPtr->point = pos - rightUp; + vertPtr->texCoord = *tc; + tc++; + vertPtr++; + + // Do we need to flush the buffer by rendering? + vertCount += 4; + if ( (vertCount + 4) >= mRainVB->mNumVerts ) { + + mRainVB.unlock(); + GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2); + vertPtr = NULL; + vertCount = 0; + } + + curr = curr->nextSplashDrop; + } + + // Do we have stuff left to render? + if ( vertCount > 0 ) { + + mRainVB.unlock(); + GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2); + } + + mLastRenderFrame = ShapeBase::sLastRenderFrame; + + GFX->popWorldMatrix(); + + PROFILE_END(); +} diff --git a/T3D/fx/precipitation.h b/T3D/fx/precipitation.h new file mode 100644 index 0000000..ccf0912 --- /dev/null +++ b/T3D/fx/precipitation.h @@ -0,0 +1,271 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PRECIPITATION_H_ +#define _PRECIPITATION_H_ + +#include "gfx/gfxDevice.h" +#include "T3D/gameBase.h" + +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif + +class SFXProfile; +class SFXSource; + +//-------------------------------------------------------------------------- +/// Precipitation datablock. +class PrecipitationData : public GameBaseData +{ + typedef GameBaseData Parent; + + public: + SFXProfile* soundProfile; + S32 soundProfileId; ///< Ambient sound + + StringTableEntry mDropName; ///< Texture filename for drop particles + StringTableEntry mDropShaderName; ///< The name of the shader used for raindrops + StringTableEntry mSplashName; ///< Texture filename for splash particles + StringTableEntry mSplashShaderName; ///< The name of the shader used for raindrops + + S32 mDropsPerSide; ///< How many drops are on a side of the raindrop texture. + S32 mSplashesPerSide; ///< How many splash are on a side of the splash texture. + + PrecipitationData(); + DECLARE_CONOBJECT(PrecipitationData); + bool onAdd(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + +struct Raindrop +{ + F32 velocity; ///< How fast the drop is falling downwards + Point3F position; ///< Position of the drop + Point3F renderPosition; ///< Interpolated render-position of the drop + F32 time; ///< Time into the turbulence function + F32 mass; ///< Mass of drop used for how much turbulence/wind effects the drop + + U32 texCoordIndex; ///< Which piece of the material will be used + + bool toRender; ///< Don't want to render all drops, just the ones that pass a few tests + bool valid; ///< Drop becomes invalid after hitting something. Just keep updating + ///< the position of it, but don't render until it hits the bottom + ///< of the renderbox and respawns + + Point3F hitPos; ///< Point at which the drop will collide with something + U32 hitType; ///< What kind of object the drop will hit + + Raindrop *nextSplashDrop; ///< Linked list cruft for easily adding/removing stuff from the splash list + Raindrop *prevSplashDrop; ///< Same as next but previous! + + SimTime animStartTime; ///< Animation time tracker + + Raindrop* next; ///< linked list cruft + + Raindrop() + { + velocity = 0; + time = 0; + mass = 1; + texCoordIndex = 0; + next = NULL; + toRender = false; + valid = true; + nextSplashDrop = NULL; + prevSplashDrop = NULL; + animStartTime = 0; + hitType = 0; + hitPos = Point3F(0,0,0); + } +}; + +//-------------------------------------------------------------------------- +class Precipitation : public GameBase +{ + protected: + + typedef GameBase Parent; + PrecipitationData* mDataBlock; + + Raindrop *mDropHead; ///< Drop linked list head + Raindrop *mSplashHead; ///< Splash linked list head + + Point2F* mTexCoords; ///< texture coords for rain texture + Point2F* mSplashCoords; ///< texture coordinates for splash texture + + SFXSource* mAmbientSound; ///< Ambient sound + + GFXShaderRef mDropShader; ///< The shader used for raindrops + GFXTexHandle mDropHandle; ///< Texture handle for raindrop + GFXShaderRef mSplashShader; ///< The shader used for splashes + GFXTexHandle mSplashHandle; ///< Texture handle for splash + + U32 mLastRenderFrame; ///< Used to skip processTick when we haven't been visible. + + U32 mDropHitMask; ///< Stores the current drop hit mask. + + //console exposed variables + bool mFollowCam; ///< Does the system follow the camera or stay where it's placed. + + F32 mDropSize; ///< Droplet billboard size + F32 mSplashSize; ///< Splash billboard size + bool mUseTrueBillboards; ///< True to use true billboards, false for axis-aligned billboards + S32 mSplashMS; ///< How long in milliseconds a splash will last + bool mAnimateSplashes; ///< Animate the splashes using the frames in the texture. + + S32 mDropAnimateMS; ///< If greater than zero, will animate the drops from + ///< the frames in the texture + + S32 mNumDrops; ///< Number of drops in the scene + F32 mPercentage; ///< Server-side set var (NOT exposed to console) + ///< which controls how many drops are present [0,1] + + F32 mMinSpeed; ///< Minimum downward speed of drops + F32 mMaxSpeed; ///< Maximum downward speed of drops + + F32 mMinMass; ///< Minimum mass of drops + F32 mMaxMass; ///< Maximum mass of drops + + F32 mBoxWidth; ///< How far away in the x and y directions drops will render + F32 mBoxHeight; ///< How high drops will render + + F32 mMaxTurbulence; ///< Coefficient to sin/cos for adding turbulence + F32 mTurbulenceSpeed; ///< How fast the turbulence wraps in a circle + bool mUseTurbulence; ///< Whether to use turbulence or not (MAY EFFECT PERFORMANCE) + + bool mUseLighting; ///< This enables shading of the drops and splashes + ///< by the sun color. + + ColorF mGlowIntensity; ///< Set it to 0 to disable the glow or use it to control + ///< the intensity of each channel. + + bool mReflect; ///< This enables the precipitation to be rendered + ///< during reflection passes. This is expensive. + + bool mUseWind; ///< This enables the wind from the sky SceneObject + ///< to effect the velocitiy of the drops. + + bool mRotateWithCamVel; ///< Rotate the drops relative to the camera velocity + ///< This is useful for "streak" type drops + + bool mDoCollision; ///< Whether or not to do collision + bool mDropHitPlayers; ///< Should drops collide with players + bool mDropHitVehicles; ///< Should drops collide with vehicles + + F32 mFadeDistance; ///< The distance at which fading of the particles begins. + F32 mFadeDistanceEnd; ///< The distance at which fading of the particles ends. + + U32 mMaxVBDrops; ///< The maximum drops allowed in one render batch. + + GFXStateBlockRef mDefaultSB; + GFXStateBlockRef mDistantSB; + + GFXShaderConstBufferRef mDropShaderConsts; + + GFXShaderConstHandle* mDropShaderModelViewSC; + GFXShaderConstHandle* mDropShaderFadeStartEndSC; + GFXShaderConstHandle* mDropShaderCameraPosSC; + GFXShaderConstHandle* mDropShaderAmbientSC; + + GFXShaderConstBufferRef mSplashShaderConsts; + + GFXShaderConstHandle* mSplashShaderModelViewSC; + GFXShaderConstHandle* mSplashShaderFadeStartEndSC; + GFXShaderConstHandle* mSplashShaderCameraPosSC; + GFXShaderConstHandle* mSplashShaderAmbientSC; + + struct + { + bool valid; + U32 startTime; + U32 totalTime; + F32 startPct; + F32 endPct; + + } mStormData; + + struct + { + bool valid; + U32 startTime; + U32 totalTime; + F32 startMax; + F32 startSpeed; + F32 endMax; + F32 endSpeed; + + } mTurbulenceData; + + //other functions... + void processTick(const Move*); + void interpolateTick(F32 delta); + + VectorF getWindVelocity(); + void fillDropList(); ///< Adds/removes drops from the list to have the right # of drops + void killDropList(); ///< Deletes the entire drop list + void initRenderObjects(); ///< Re-inits the texture coord lookup tables + void initMaterials(); ///< Re-inits the textures and shaders + void spawnDrop(Raindrop *drop); ///< Fills drop info with random velocity, x/y positions, and mass + void spawnNewDrop(Raindrop *drop); ///< Same as spawnDrop except also does z position + + void findDropCutoff(Raindrop *drop, const Box3F &box, const VectorF &windVel); ///< Casts a ray to see if/when a drop will collide + void wrapDrop(Raindrop *drop, const Box3F &box, const U32 currTime, const VectorF &windVel); ///< Wraps a drop within the specified box + + void createSplash(Raindrop *drop); ///< Adds a drop to the splash list + void destroySplash(Raindrop *drop); ///< Removes a drop from the splash list + + GFXPrimitiveBufferHandle mRainIB; + GFXVertexBufferHandle mRainVB; + + bool onAdd(); + void onRemove(); + + // Rendering + bool prepRenderImage(SceneState*, const U32, const U32, const bool); + void renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ); + + void setTransform(const MatrixF &mat); + + public: + + Precipitation(); + ~Precipitation(); + void inspectPostApply(); + + enum + { + DataMask = Parent::NextFreeMask << 0, + PercentageMask = Parent::NextFreeMask << 1, + StormMask = Parent::NextFreeMask << 2, + TransformMask = Parent::NextFreeMask << 3, + TurbulenceMask = Parent::NextFreeMask << 4, + NextFreeMask = Parent::NextFreeMask << 5 + }; + + bool onNewDataBlock(GameBaseData* dptr); + DECLARE_CONOBJECT(Precipitation); + static void initPersistFields(); + + U32 packUpdate(NetConnection*, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection*, BitStream* stream); + + void setPercentage(F32 pct); + void modifyStorm(F32 pct, U32 ms); + + /// This is used to smoothly change the turbulence + /// over a desired time period. Setting ms to zero + /// will cause the change to be instantaneous. Setting + /// max zero will disable turbulence. + void setTurbulence(F32 max, F32 speed, U32 ms); +}; + +#endif // PRECIPITATION_H_ + diff --git a/T3D/fx/splash.cpp b/T3D/fx/splash.cpp new file mode 100644 index 0000000..7668154 --- /dev/null +++ b/T3D/fx/splash.cpp @@ -0,0 +1,672 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/fx/splash.h" + +#include "console/consoleTypes.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" +#include "T3D/fx/explosion.h" +#include "T3D/fx/particle.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/fx/particleEmitterNode.h" +#include "T3D/gameProcess.h" +#include "sim/netConnection.h" +#include "renderInstance/renderPassManager.h" + +namespace +{ + +MRandomLCG sgRandom(0xdeadbeef); + +} // namespace {} + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(SplashData); +IMPLEMENT_CO_NETOBJECT_V1(Splash); + +//-------------------------------------------------------------------------- +// Splash Data +//-------------------------------------------------------------------------- +SplashData::SplashData() +{ + soundProfile = NULL; + soundProfileId = 0; + + scale.set(1, 1, 1); + + dMemset( emitterList, 0, sizeof( emitterList ) ); + dMemset( emitterIDList, 0, sizeof( emitterIDList ) ); + + delayMS = 0; + delayVariance = 0; + lifetimeMS = 1000; + lifetimeVariance = 0; + width = 4.0; + numSegments = 10; + velocity = 5.0; + height = 0.0; + acceleration = 0.0; + texWrap = 1.0; + texFactor = 3.0; + ejectionFreq = 5; + ejectionAngle = 45.0; + ringLifetime = 1.0; + startRadius = 0.5; + explosion = NULL; + explosionId = 0; + + dMemset( textureName, 0, sizeof( textureName ) ); + + U32 i; + for( i=0; iwrite(delayMS); + stream->write(delayVariance); + stream->write(lifetimeMS); + stream->write(lifetimeVariance); + stream->write(width); + stream->write(numSegments); + stream->write(velocity); + stream->write(height); + stream->write(acceleration); + stream->write(texWrap); + stream->write(texFactor); + stream->write(ejectionFreq); + stream->write(ejectionAngle); + stream->write(ringLifetime); + stream->write(startRadius); + + if( stream->writeFlag( explosion ) ) + { + stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + S32 i; + for( i=0; iwriteFlag( emitterList[i] != NULL ) ) + { + stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for( i=0; iwrite( colors[i] ); + } + + for( i=0; iwrite( times[i] ); + } + + for( i=0; iwriteString(textureName[i]); + } +} + +//-------------------------------------------------------------------------- +// Unpack data +//-------------------------------------------------------------------------- +void SplashData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + mathRead(*stream, &scale); + stream->read(&delayMS); + stream->read(&delayVariance); + stream->read(&lifetimeMS); + stream->read(&lifetimeVariance); + stream->read(&width); + stream->read(&numSegments); + stream->read(&velocity); + stream->read(&height); + stream->read(&acceleration); + stream->read(&texWrap); + stream->read(&texFactor); + stream->read(&ejectionFreq); + stream->read(&ejectionAngle); + stream->read(&ringLifetime); + stream->read(&startRadius); + + if( stream->readFlag() ) + { + explosionId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + U32 i; + for( i=0; ireadFlag() ) + { + emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for( i=0; iread( &colors[i] ); + } + + for( i=0; iread( ×[i] ); + } + + for( i=0; ireadSTString(); + } +} + +//-------------------------------------------------------------------------- +// Preload data - load resources +//-------------------------------------------------------------------------- +bool SplashData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + if (!server) + { + S32 i; + for( i=0; idelayMS + sgRandom.randI( -mDataBlock->delayVariance, mDataBlock->delayVariance ); + mEndingMS = mDataBlock->lifetimeMS + sgRandom.randI( -mDataBlock->lifetimeVariance, mDataBlock->lifetimeVariance ); + + mVelocity = mDataBlock->velocity; + mHeight = mDataBlock->height; + mTimeSinceLastRing = 1.0 / mDataBlock->ejectionFreq; + + for( U32 i=0; iemitterList[i] != NULL ) + { + ParticleEmitter * pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock( mDataBlock->emitterList[i] ); + if( !pEmitter->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() ); + delete pEmitter; + pEmitter = NULL; + } + mEmitterList[i] = pEmitter; + } + } + + spawnExplosion(); + + mObjBox.minExtents = Point3F( -1, -1, -1 ); + mObjBox.maxExtents = Point3F( 1, 1, 1 ); + resetWorldBox(); + + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + + removeFromProcessList(); + gClientProcessList.addObject(this); + + conn->addObject(this); + + return true; +} + +//-------------------------------------------------------------------------- +// OnRemove +//-------------------------------------------------------------------------- +void Splash::onRemove() +{ + for( U32 i=0; ideleteWhenEmpty(); + mEmitterList[i] = NULL; + } + } + + ringList.clear(); + + mSceneManager->removeObjectFromScene(this); + getContainer()->removeObject(this); + + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +// On New Data Block +//-------------------------------------------------------------------------- +bool Splash::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + + +//-------------------------------------------------------------------------- +// Process tick +//-------------------------------------------------------------------------- +void Splash::processTick(const Move*) +{ + mCurrMS += TickMs; + + if( isServerObject() ) + { + if( mCurrMS >= mEndingMS ) + { + mDead = true; + if( mCurrMS >= (mEndingMS + mDataBlock->ringLifetime * 1000) ) + { + deleteObject(); + } + } + } + else + { + if( mCurrMS >= mEndingMS ) + { + mDead = true; + } + } +} + +//-------------------------------------------------------------------------- +// Advance time +//-------------------------------------------------------------------------- +void Splash::advanceTime(F32 dt) +{ + if (dt == 0.0) + return; + + mElapsedTime += dt; + + updateColor(); + updateWave( dt ); + updateEmitters( dt ); + updateRings( dt ); + + if( !mDead ) + { + emitRings( dt ); + } +} + +//---------------------------------------------------------------------------- +// Update emitters +//---------------------------------------------------------------------------- +void Splash::updateEmitters( F32 dt ) +{ + Point3F pos = getPosition(); + + for( U32 i=0; iemitParticles( pos, pos, mInitialNormal, Point3F( 0.0, 0.0, 0.0 ), (S32) (dt * 1000) ); + } + } + +} + +//---------------------------------------------------------------------------- +// Update wave +//---------------------------------------------------------------------------- +void Splash::updateWave( F32 dt ) +{ + mVelocity += mDataBlock->acceleration * dt; + mRadius += mVelocity * dt; + +} + +//---------------------------------------------------------------------------- +// Update color +//---------------------------------------------------------------------------- +void Splash::updateColor() +{ + for(SplashRingList::Iterator ring = ringList.begin(); ring != ringList.end(); ++ring) + { + F32 t = F32(ring->elapsedTime) / F32(ring->lifetime); + + for( U32 i = 1; i < SplashData::NUM_TIME_KEYS; i++ ) + { + if( mDataBlock->times[i] >= t ) + { + F32 firstPart = t - mDataBlock->times[i-1]; + F32 total = (mDataBlock->times[i] - + mDataBlock->times[i-1]); + + firstPart /= total; + + ring->color.interpolate( mDataBlock->colors[i-1], + mDataBlock->colors[i], + firstPart); + break; + } + } + } +} + +//---------------------------------------------------------------------------- +// Create ring +//---------------------------------------------------------------------------- +SplashRing Splash::createRing() +{ + SplashRing ring; + U32 numPoints = mDataBlock->numSegments + 1; + + Point3F ejectionAxis( 0.0, 0.0, 1.0 ); + + Point3F axisx; + if (mFabs(ejectionAxis.z) < 0.999f) + mCross(ejectionAxis, Point3F(0, 0, 1), &axisx); + else + mCross(ejectionAxis, Point3F(0, 1, 0), &axisx); + axisx.normalize(); + + for( U32 i=0; iejectionAngle * (M_PI / 180.0)); + AngAxisF phiRot( ejectionAxis, t * (M_PI * 2.0)); + + Point3F pointAxis = ejectionAxis; + + MatrixF temp; + thetaRot.setMatrix(&temp); + temp.mulP(pointAxis); + phiRot.setMatrix(&temp); + temp.mulP(pointAxis); + + Point3F startOffset = axisx; + temp.mulV( startOffset ); + startOffset *= mDataBlock->startRadius; + + SplashRingPoint point; + point.position = getPosition() + startOffset; + point.velocity = pointAxis * mDataBlock->velocity; + + ring.points.push_back( point ); + } + + ring.color = mDataBlock->colors[0]; + ring.lifetime = mDataBlock->ringLifetime; + ring.elapsedTime = 0.0; + ring.v = mDataBlock->texFactor * mFmod( mElapsedTime, 1.0 ); + + return ring; +} + +//---------------------------------------------------------------------------- +// Emit rings +//---------------------------------------------------------------------------- +void Splash::emitRings( F32 dt ) +{ + mTimeSinceLastRing += dt; + + S32 numNewRings = (S32) (mTimeSinceLastRing * F32(mDataBlock->ejectionFreq)); + + mTimeSinceLastRing -= numNewRings / mDataBlock->ejectionFreq; + + for( S32 i=numNewRings-1; i>=0; i-- ) + { + F32 t = F32(i) / F32(numNewRings); + t *= dt; + t += mTimeSinceLastRing; + + SplashRing ring = createRing(); + updateRing( ring, t ); + + ringList.pushBack( ring ); + } +} + +//---------------------------------------------------------------------------- +// Update rings +//---------------------------------------------------------------------------- +void Splash::updateRings( F32 dt ) +{ + SplashRingList::Iterator ring; + for(SplashRingList::Iterator i = ringList.begin(); i != ringList.end(); /*Trickiness*/) + { + ring = i++; + ring->elapsedTime += dt; + + if( !ring->isActive() ) + { + ringList.erase( ring ); + } + else + { + updateRing( *ring, dt ); + } + } +} + +//---------------------------------------------------------------------------- +// Update ring +//---------------------------------------------------------------------------- +void Splash::updateRing( SplashRing& ring, F32 dt ) +{ + for( U32 i=0; iacceleration; + ring.points[i].velocity += vel * dt; + } + + ring.points[i].velocity += Point3F( 0.0f, 0.0f, -9.8f ) * dt; + ring.points[i].position += ring.points[i].velocity * dt; + } +} + +//---------------------------------------------------------------------------- +// Explode +//---------------------------------------------------------------------------- +void Splash::spawnExplosion() +{ + if( !mDataBlock->explosion ) return; + + Explosion* pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->explosion); + + MatrixF trans = getTransform(); + trans.setPosition( getPosition() ); + + pExplosion->setTransform( trans ); + pExplosion->setInitialState( trans.getPosition(), VectorF(0,0,1), 1); + if (!pExplosion->registerObject()) + delete pExplosion; +} + +//-------------------------------------------------------------------------- +// packUpdate +//-------------------------------------------------------------------------- +U32 Splash::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if( stream->writeFlag(mask & GameBase::InitialUpdateMask) ) + { + mathWrite(*stream, mInitialPosition); + } + + return retMask; +} + +//-------------------------------------------------------------------------- +// unpackUpdate +//-------------------------------------------------------------------------- +void Splash::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if( stream->readFlag() ) + { + mathRead(*stream, &mInitialPosition); + setPosition( mInitialPosition ); + } +} diff --git a/T3D/fx/splash.h b/T3D/fx/splash.h new file mode 100644 index 0000000..3fa9472 --- /dev/null +++ b/T3D/fx/splash.h @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SPLASH_H_ +#define _SPLASH_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif + +#ifndef _TORQUE_LIST_ +#include "core/util/tList.h" +#endif + +#include "gfx/gfxTextureHandle.h" + +class ParticleEmitter; +class ParticleEmitterData; +class AudioProfile; +class ExplosionData; + + +//-------------------------------------------------------------------------- +// Ring Point +//-------------------------------------------------------------------------- +struct SplashRingPoint +{ + Point3F position; + Point3F velocity; +}; + +//-------------------------------------------------------------------------- +// Splash Ring +//-------------------------------------------------------------------------- +struct SplashRing +{ + Vector points; + ColorF color; + F32 lifetime; + F32 elapsedTime; + F32 v; + + SplashRing() + { + color.set( 0.0, 0.0, 0.0, 1.0 ); + lifetime = 0.0; + elapsedTime = 0.0; + v = 0.0; + } + + bool isActive() + { + return elapsedTime < lifetime; + } +}; + +//-------------------------------------------------------------------------- +// Splash Data +//-------------------------------------------------------------------------- +class SplashData : public GameBaseData +{ + public: + typedef GameBaseData Parent; + + enum Constants + { + NUM_EMITTERS = 3, + NUM_TIME_KEYS = 4, + NUM_TEX = 2, + }; + +public: + AudioProfile* soundProfile; + S32 soundProfileId; + + ParticleEmitterData* emitterList[NUM_EMITTERS]; + S32 emitterIDList[NUM_EMITTERS]; + + S32 delayMS; + S32 delayVariance; + S32 lifetimeMS; + S32 lifetimeVariance; + Point3F scale; + F32 width; + F32 height; + U32 numSegments; + F32 velocity; + F32 acceleration; + F32 texWrap; + F32 texFactor; + + F32 ejectionFreq; + F32 ejectionAngle; + F32 ringLifetime; + F32 startRadius; + + F32 times[ NUM_TIME_KEYS ]; + ColorF colors[ NUM_TIME_KEYS ]; + + StringTableEntry textureName[NUM_TEX]; + GFXTexHandle textureHandle[NUM_TEX]; + + ExplosionData* explosion; + S32 explosionId; + + SplashData(); + DECLARE_CONOBJECT(SplashData); + bool onAdd(); + bool preload(bool server, String &errorStr); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; +DECLARE_CONSOLETYPE(SplashData) + +//-------------------------------------------------------------------------- +// Splash +//-------------------------------------------------------------------------- +class Splash : public GameBase +{ + typedef GameBase Parent; + +private: + SplashData* mDataBlock; + + ParticleEmitter * mEmitterList[ SplashData::NUM_EMITTERS ]; + + typedef Torque::List SplashRingList; + SplashRingList ringList; + + U32 mCurrMS; + U32 mEndingMS; + F32 mRandAngle; + F32 mRadius; + F32 mVelocity; + F32 mHeight; + ColorF mColor; + F32 mTimeSinceLastRing; + bool mDead; + F32 mElapsedTime; + +protected: + Point3F mInitialPosition; + Point3F mInitialNormal; + F32 mFade; + F32 mFog; + bool mActive; + S32 mDelayMS; + +protected: + bool onAdd(); + void onRemove(); + void processTick(const Move *move); + void advanceTime(F32 dt); + void updateEmitters( F32 dt ); + void updateWave( F32 dt ); + void updateColor(); + SplashRing createRing(); + void updateRings( F32 dt ); + void updateRing( SplashRing& ring, F32 dt ); + void emitRings( F32 dt ); + void spawnExplosion(); + + // Rendering +protected: + bool prepRenderImage ( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false){return false;} + +public: + Splash(); + ~Splash(); + void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection *conn, BitStream* stream); + + bool onNewDataBlock(GameBaseData* dptr); + DECLARE_CONOBJECT(Splash); +}; + + +#endif // _H_SPLASH diff --git a/T3D/fx/windEmitter.cpp b/T3D/fx/windEmitter.cpp new file mode 100644 index 0000000..94e0f5a --- /dev/null +++ b/T3D/fx/windEmitter.cpp @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// Torque Ground Cover +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/fx/windEmitter.h" + +#include "math/mBox.h" +#include "core/tAlgorithm.h" +#include "platform/profiler.h" + + +Vector WindEmitter::smAllEmitters; + + +WindEmitter::WindEmitter() +{ + smAllEmitters.push_back( this ); + + mEnabled = true; + mScore = 0.0f; + mSphere.center.zero(); + mSphere.radius = 0.0f; + mStrength = 0.0f; + mTurbulenceFrequency = 0.0f; + mTurbulenceStrength = 0.0f; + mVelocity.zero(); +} + +WindEmitter::~WindEmitter() +{ + WindEmitterList::iterator iter = find( smAllEmitters.begin(), smAllEmitters.end(), this ); + smAllEmitters.erase( iter ); +} + +void WindEmitter::setPosition( const Point3F& pos ) +{ + mSphere.center = pos; +} + +void WindEmitter::update( const Point3F& pos, const VectorF& velocity ) +{ + mSphere.center = pos; + mVelocity = velocity; +} + +void WindEmitter::setRadius( F32 radius ) +{ + mSphere.radius = radius; +} + +void WindEmitter::setStrength( F32 strength ) +{ + mStrength = strength; +} + +void WindEmitter::setTurbulency( F32 frequency, F32 strength ) +{ + mTurbulenceFrequency = frequency; + mTurbulenceStrength = strength; +} + +S32 QSORT_CALLBACK WindEmitter::_sortByScore(const void* a, const void* b) +{ + return ((*(WindEmitter**)b)->mScore - (*(WindEmitter**)a)->mScore); +} + +bool WindEmitter::findBest( const Point3F& cameraPos, + const VectorF& cameraDir, + F32 viewDistance, + U32 maxResults, + WindEmitterList* results ) +{ + PROFILE_START(WindEmitter_findBest); + + // Build a sphere from the camera point. + SphereF cameraSphere; + cameraSphere.center = cameraPos; + cameraSphere.radius = viewDistance; + + // Collect the active spheres within the camera space and score them. + WindEmitterList best; + WindEmitterList::iterator iter = smAllEmitters.begin(); + for ( ; iter != smAllEmitters.end(); iter++ ) + { + const SphereF& sphere = *(*iter); + + // Skip any spheres outside of our camera range or that are disabled. + if ( !(*iter)->mEnabled || !cameraSphere.isIntersecting( sphere ) ) + continue; + + // Simple score calculation... + // + // score = ( radius / distance to camera ) * dot( cameraDir, vector from camera to sphere ) + // + Point3F vect = sphere.center - cameraSphere.center; + F32 dist = vect.len(); + (*iter)->mScore = dist * sphere.radius; + vect /= getMax( dist, 0.001f ); + (*iter)->mScore *= mDot( vect, cameraDir ); + + best.push_back( *iter ); + } + + // Sort the results by score! + dQsort( best.address(), best.size(), sizeof(WindEmitter*), &WindEmitter::_sortByScore ); + + // Clip the results to the max requested. + if ( best.size() > maxResults ) + best.setSize( maxResults ); + + // Merge the results and return. + results->merge( best ); + + PROFILE_END(); // WindEmitter_findBest + + return best.size() > 0; +} diff --git a/T3D/fx/windEmitter.h b/T3D/fx/windEmitter.h new file mode 100644 index 0000000..c80b347 --- /dev/null +++ b/T3D/fx/windEmitter.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Torque Ground Cover +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#ifndef _WINDEMITTER_H_ +#define _WINDEMITTER_H_ + +#ifndef _MPOINT3_H_ + #include "math/mPoint3.h" +#endif +#ifndef _MSPHERE_H_ + #include "math/mSphere.h" +#endif +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif + +class WindEmitter; + +/// A vector of WindEmitter pointers. +typedef Vector WindEmitterList; + + +class WindEmitter +{ +public: + WindEmitter(); + ~WindEmitter(); + + operator const SphereF&() const { return mSphere; } + + void update( const Point3F& pos, const VectorF& velocity ); + + void setPosition( const Point3F& pos ); + + void setRadius( F32 radius ); + + void setStrength( F32 strength ); + + void setTurbulency( F32 frequency, F32 strength ); + + const Point3F& getCenter() const { return mSphere.center; } + + F32 getRadius() const { return mSphere.radius; } + + F32 getStrength() const { return mStrength; } + + F32 getTurbulenceFrequency() const { return mTurbulenceFrequency; } + + F32 getTurbulenceStrength() const { return mTurbulenceStrength; } + + const VectorF& getVelocity() const { return mVelocity; } + + + static bool findBest( const Point3F& cameraPos, + const VectorF& cameraDir, + F32 viewDistance, + U32 maxResults, + WindEmitterList* results ); + +protected: + SphereF mSphere; + + VectorF mVelocity; + + F32 mStrength; + F32 mTurbulenceFrequency; + F32 mTurbulenceStrength; + F32 mScore; + + bool mEnabled; + + static WindEmitterList smAllEmitters; + + static S32 QSORT_CALLBACK _sortByScore( const void* a, const void* b ); +}; + +#endif // _WINDEMITTER_H_ diff --git a/T3D/gameBase.cpp b/T3D/gameBase.cpp new file mode 100644 index 0000000..d53342d --- /dev/null +++ b/T3D/gameBase.cpp @@ -0,0 +1,591 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/gameBase.h" +#include "console/consoleTypes.h" +#include "console/consoleInternal.h" +#include "core/stream/bitStream.h" +#include "sim/netConnection.h" +#include "T3D/gameConnection.h" +#include "math/mathIO.h" +#include "T3D/moveManager.h" +#include "T3D/gameProcess.h" + +#ifdef TORQUE_DEBUG_NET_MOVES +#include "T3D/aiConnection.h" +#endif + +#include "add/RPGPack/RPGBase.h" +//---------------------------------------------------------------------------- +// Ghost update relative priority values + +static F32 sUpFov = 1.0; +static F32 sUpDistance = 0.4f; +static F32 sUpVelocity = 0.4f; +static F32 sUpSkips = 0.2f; +static F32 sUpInterest = 0.2f; + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(GameBaseData); + +GameBaseData::GameBaseData() +{ + category = ""; + packed = false; + + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +bool GameBaseData::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +void GameBaseData::initPersistFields() +{ + addField("category", TypeCaseString, Offset(category, GameBaseData)); + Parent::initPersistFields(); +} + +bool GameBaseData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + packed = false; + return true; +} + +void GameBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + packed = true; +} + + +//---------------------------------------------------------------------------- +bool UNPACK_DB_ID(BitStream * stream, U32 & id) +{ + if (stream->readFlag()) + { + id = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast); + return true; + } + return false; +} + +bool PACK_DB_ID(BitStream * stream, U32 id) +{ + if (stream->writeFlag(id)) + { + stream->writeRangedU32(id,DataBlockObjectIdFirst,DataBlockObjectIdLast); + return true; + } + return false; +} + +bool PRELOAD_DB(U32 & id, SimDataBlock ** data, bool server, const char * clientMissing, const char * serverMissing) +{ + if (server) + { + if (*data) + id = (*data)->getId(); + else if (server && serverMissing) + { + Con::errorf(ConsoleLogEntry::General,serverMissing); + return false; + } + } + else + { + if (id && !Sim::findObject(id,*data) && clientMissing) + { + Con::errorf(ConsoleLogEntry::General,clientMissing); + return false; + } + } + return true; +} +//---------------------------------------------------------------------------- + +bool GameBase::gShowBoundingBox = false; + +//---------------------------------------------------------------------------- +IMPLEMENT_CO_NETOBJECT_V1(GameBase); + +GameBase::GameBase() +{ + mNetFlags.set(Ghostable); + mTypeMask |= GameBaseObjectType; + + mProcessTag = 0; + mDataBlock = 0; + mProcessTick = true; + mNameTag = ""; + mControllingClient = 0; + mCurrentWaterObject = NULL; + +#ifdef TORQUE_DEBUG_NET_MOVES + mLastMoveId = 0; + mTicksSinceLastMove = 0; + mIsAiControlled = false; +#endif + +} + +GameBase::~GameBase() +{ + plUnlink(); +} + + +//---------------------------------------------------------------------------- + +bool GameBase::onAdd() +{ + if (!Parent::onAdd())// || !mDataBlock) + return false; + + if (isClientObject()) { + // Client datablock are initialized by the initial update + gClientProcessList.addObject(this); + } + else { + // Datablock must be initialized on the server + if ( mDataBlock && !onNewDataBlock(mDataBlock)) + return false; + gServerProcessList.addObject(this); + } + return true; +} + +void GameBase::onRemove() +{ + plUnlink(); + Parent::onRemove(); +} + +bool GameBase::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dptr; + + if (!mDataBlock) + return false; + + setMaskBits(DataBlockMask); + return true; +} + +void GameBase::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits(ExtendedInfoMask); +} + +//---------------------------------------------------------------------------- + +void GameBase::processTick(const Move * move) +{ +#ifdef TORQUE_DEBUG_NET_MOVES + if (!move) + mTicksSinceLastMove++; + + const char * srv = isClientObject() ? "client" : "server"; + const char * who = ""; + if (isClientObject()) + { + if (this == (GameBase*)GameConnection::getConnectionToServer()->getControlObject()) + who = " player"; + else + who = " ghost"; + if (mIsAiControlled) + who = " ai"; + } + if (isServerObject()) + { + if (dynamic_cast(getControllingClient())) + { + who = " ai"; + mIsAiControlled = true; + } + else if (getControllingClient()) + { + who = " player"; + mIsAiControlled = false; + } + else + { + who = ""; + mIsAiControlled = false; + } + } + U32 moveid = mLastMoveId+mTicksSinceLastMove; + if (move) + moveid = move->id; + + if (getType() & GameBaseHiFiObjectType) + { + if (move) + Con::printf("Processing (%s%s id %i) move %i",srv,who,getId(), move->id); + else + Con::printf("Processing (%s%s id %i) move %i (%i)",srv,who,getId(),mLastMoveId+mTicksSinceLastMove,mTicksSinceLastMove); + } + + if (move) + { + mLastMoveId = move->id; + mTicksSinceLastMove=0; + } +#endif +} + +void GameBase::interpolateTick(F32 backDelta) +{ +} + +void GameBase::advanceTime(F32) +{ +} + +void GameBase::preprocessMove(Move *move) +{ +} + +//---------------------------------------------------------------------------- + +F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) +{ + TORQUE_UNUSED(updateMask); + + // Calculate a priority used to decide if this object + // will be updated on the client. All the weights + // are calculated 0 -> 1 Then weighted together at the + // end to produce a priority. + Point3F pos; + getWorldBox().getCenter(&pos); + pos -= camInfo->pos; + F32 dist = pos.len(); + if (dist == 0.0f) dist = 0.001f; + pos *= 1.0f / dist; + + // Weight based on linear distance, the basic stuff. + F32 wDistance = (dist < camInfo->visibleDistance)? + 1.0f - (dist / camInfo->visibleDistance): 0.0f; + + // Weight by field of view, objects directly in front + // will be weighted 1, objects behind will be 0 + F32 dot = mDot(pos,camInfo->orientation); + bool inFov = dot > camInfo->cosFov; + F32 wFov = inFov? 1.0f: 0; + + // Weight by linear velocity parallel to the viewing plane + // (if it's the field of view, 0 if it's not). + F32 wVelocity = 0.0f; + if (inFov) + { + Point3F vec; + mCross(camInfo->orientation,getVelocity(),&vec); + wVelocity = (vec.len() * camInfo->fov) / + (camInfo->fov * camInfo->visibleDistance); + if (wVelocity > 1.0f) + wVelocity = 1.0f; + } + + // Weight by interest. + F32 wInterest; + if (getType() & PlayerObjectType) + wInterest = 0.75f; + else if (getType() & ProjectileObjectType) + { + // Projectiles are more interesting if they + // are heading for us. + wInterest = 0.30f; + F32 dot = -mDot(pos,getVelocity()); + if (dot > 0.0f) + wInterest += 0.20 * dot; + } + else + { + if (getType() & ItemObjectType) + wInterest = 0.25f; + else + // Everything else is less interesting. + wInterest = 0.0f; + } + + // Weight by updateSkips + F32 wSkips = updateSkips * 0.5; + + // Calculate final priority, should total to about 1.0f + // + return + wFov * sUpFov + + wDistance * sUpDistance + + wVelocity * sUpVelocity + + wSkips * sUpSkips + + wInterest * sUpInterest; +} + +//---------------------------------------------------------------------------- +bool GameBase::setDataBlock(GameBaseData* dptr) +{ + if (isGhost() || isProperlyAdded()) { + if (mDataBlock != dptr) + return onNewDataBlock(dptr); + } + else + mDataBlock = dptr; + return true; +} + + +//-------------------------------------------------------------------------- +void GameBase::scriptOnAdd() +{ + // Script onAdd() must be called by the leaf class after + // everything is ready. + if (mDataBlock && !isGhost()) + Con::executef(mDataBlock, "onAdd",scriptThis()); +} + +void GameBase::scriptOnNewDataBlock() +{ + // Script onNewDataBlock() must be called by the leaf class + // after everything is loaded. + if (mDataBlock && !isGhost()) + Con::executef(mDataBlock, "onNewDataBlock",scriptThis()); +} + +void GameBase::scriptOnRemove() +{ + // Script onRemove() must be called by leaf class while + // the object state is still valid. + if (!isGhost() && mDataBlock) + Con::executef(mDataBlock, "onRemove",scriptThis()); +} + +//---------------------------------------------------------------------------- +void GameBase::processAfter(GameBase* obj) +{ + mAfterObject = obj; + if ((const GameBase*)obj->mAfterObject == this) + obj->mAfterObject = 0; + if (isGhost()) + gClientProcessList.markDirty(); + else + gServerProcessList.markDirty(); +} + +void GameBase::clearProcessAfter() +{ + mAfterObject = 0; +} + +//---------------------------------------------------------------------------- + +void GameBase::setControllingClient(GameConnection* client) +{ + if (isClientObject()) + { + if (mControllingClient) + Con::executef(this, "setControl", "0"); + if (client) + Con::executef(this, "setControl", "1"); + } + + mControllingClient = client; + setMaskBits(ControlMask); +} + +U32 GameBase::getPacketDataChecksum(GameConnection * connection) +{ + // just write the packet data into a buffer + // then we can CRC the buffer. This should always let us + // know when there is a checksum problem. + + static U8 buffer[1500] = { 0, }; + BitStream stream(buffer, sizeof(buffer)); + + writePacketData(connection, &stream); + U32 byteCount = stream.getPosition(); + U32 ret = CRC::calculateCRC(buffer, byteCount, 0xFFFFFFFF); + dMemset(buffer, 0, byteCount); + return ret; +} + +void GameBase::writePacketData(GameConnection*, BitStream*) +{ +} + +void GameBase::readPacketData(GameConnection*, BitStream*) +{ +} + +U32 GameBase::packUpdate(NetConnection *, U32 mask, BitStream *stream) +{ + // Check the mask for the ScaleMask; if it's true, pass that in. + if (stream->writeFlag( mask & ScaleMask ) ) + { + mathWrite( *stream, Parent::getScale() ); + } + + if (stream->writeFlag((mask & DataBlockMask) && mDataBlock != NULL)) + { + stream->writeRangedU32(mDataBlock->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->writeFlag(mNetFlags.test(NetOrdered))) + stream->writeInt(mOrderGUID,16); + } + +#ifdef TORQUE_DEBUG_NET_MOVES + stream->write(mLastMoveId); + stream->writeFlag(mIsAiControlled); +#endif + + return 0; +} + +void GameBase::unpackUpdate(NetConnection *con, BitStream *stream) +{ + if (stream->readFlag()) { + VectorF scale; + mathRead( *stream, &scale ); + setScale( scale ); + } + if (stream->readFlag()) + { + GameBaseData* dptr = 0; + SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + if (stream->readFlag()) + mOrderGUID = stream->readInt(16); + + if (!Sim::findObject(id,dptr) || !setDataBlock(dptr)) + con->setLastError("Invalid packet GameBase::unpackUpdate()"); + } + +#ifdef TORQUE_DEBUG_NET_MOVES + stream->read(&mLastMoveId); + mTicksSinceLastMove = 0; + mIsAiControlled = stream->readFlag(); +#endif +} + +bool GameBase::setDataBlockProperty(void* obj, const char* db) +{ + if(db == NULL) + { + Con::errorf("Attempted to set a NULL datablock"); + return true; + } + + GameBase* object = static_cast (obj); + GameBaseData* data; + if(Sim::findObject(db, data)) + { + return object->setDataBlock(data); + } + Con::errorf("Could not find data block \"%s\"", db); + return false; +} + +void GameBase::pushRPGBase( RPGBase * pBase ) +{ + _mRPGBases.push_back(pBase); +} + +void GameBase::removeRPGBase( RPGBase * pBase ) +{ + std::vector temp; + for (S32 i = 0 ; i < _mRPGBases.size() ; ++i) + { + if (_mRPGBases[i] != pBase) + temp.push_back(_mRPGBases[i]); + } + _mRPGBases.swap(temp); +} + +void GameBase::onInterrupt() +{ + for (S32 i = 0 ; i < _mRPGBases.size() ; ++i) + { + _mRPGBases[i]->onInterrupt(); + } +} + +void GameBase::onMoved( const Point3F & pos ) +{ + for (S32 i = 0 ; i < _mRPGBases.size() ; ++i) + { + _mRPGBases[i]->setPosition(pos); + } +} +//---------------------------------------------------------------------------- +ConsoleMethod( GameBase, getDataBlock, S32, 2, 2, "()" + "Return the datablock this GameBase is using.") +{ + return object->getDataBlock()? object->getDataBlock()->getId(): 0; +} + +//---------------------------------------------------------------------------- +ConsoleMethod(GameBase, setDataBlock, bool, 3, 3, "(DataBlock db)" + "Assign this GameBase to use the specified datablock.") +{ + GameBaseData* data; + if (Sim::findObject(argv[2],data)) { + return object->setDataBlock(data); + } + Con::errorf("Could not find data block \"%s\"",argv[2]); + return false; +} + +//---------------------------------------------------------------------------- +IMPLEMENT_CONSOLETYPE(GameBaseData) +IMPLEMENT_GETDATATYPE(GameBaseData) +IMPLEMENT_SETDATATYPE(GameBaseData) + +void GameBase::initPersistFields() +{ + addGroup("Misc"); + addField("nameTag", TypeCaseString, Offset(mNameTag, GameBase), "Name of the precipitation box."); + addProtectedField("dataBlock", TypeGameBaseDataPtr, Offset(mDataBlock, GameBase), &setDataBlockProperty, &defaultProtectedGetFn, + "Script datablock used for game objects."); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +void GameBase::consoleInit() +{ +#ifdef TORQUE_DEBUG + Con::addVariable("GameBase::boundingBox", TypeBool, &gShowBoundingBox); +#endif +} + +ConsoleMethod( GameBase, applyImpulse, bool, 4, 4, "(Point3F Pos, VectorF vel)") +{ + Point3F pos(0,0,0); + VectorF vel(0,0,0); + dSscanf(argv[2],"%g %g %g",&pos.x,&pos.y,&pos.z); + dSscanf(argv[3],"%g %g %g",&vel.x,&vel.y,&vel.z); + object->applyImpulse(pos,vel); + return true; +} + +ConsoleMethod( GameBase, applyRadialImpulse, void, 5, 5, "(Point3F origin, F32 radius, F32 magnitude)" ) +{ + Point3F origin( 0, 0, 0 ); + dSscanf(argv[2],"%g %g %g",&origin.x,&origin.y,&origin.z); + + F32 radius = dAtof( argv[3] ); + F32 magnitude = dAtof( argv[4] ); + + object->applyRadialImpulse( origin, radius, magnitude ); +} \ No newline at end of file diff --git a/T3D/gameBase.h b/T3D/gameBase.h new file mode 100644 index 0000000..506eb91 --- /dev/null +++ b/T3D/gameBase.h @@ -0,0 +1,401 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMEBASE_H_ +#define _GAMEBASE_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _PROCESSLIST_H_ +#include "sim/processList.h" +#endif +#ifndef _TICKCACHE_H_ +#include "T3D/tickCache.h" +#endif +#ifndef _DYNAMIC_CONSOLETYPES_H_ +#include "console/dynamicTypes.h" +#endif + +//=============rpg stuff============ +#include +class RPGBase; +//=============================== +class NetConnection; +class ProcessList; +class GameBase; +struct Move; + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +/// Scriptable, demo-able datablock. +/// +/// This variant of SimDataBlock performs these additional tasks: +/// - Linking datablock's namepsaces to the namespace of their C++ class, so +/// that datablocks can expose script functionality. +/// - Linking datablocks to a user defined scripting namespace, by setting the +/// 'class' field at datablock definition time. +/// - Adds a category field; this is used by the world creator in the editor to +/// classify creatable shapes. Creatable shapes are placed under the Shapes +/// node in the treeview for this; additional levels are created, named after +/// the category fields. +/// - Adds support for demo stream recording. This support takes the form +/// of the member variable packed. When a demo is being recorded by a client, +/// data is unpacked, then packed again to the data stream, then, in the case +/// of datablocks, preload() is called to process the data. It is occasionally +/// the case that certain references in the datablock stream cannot be resolved +/// until preload is called, in which case a raw ID field is stored in the variable +/// which will eventually be used to store a pointer to the object. However, if +/// packData() is called before we resolve this ID, trying to call getID() on the +/// objecct ID would be a fatal error. Therefore, in these cases, we test packed; +/// if it is true, then we know we have to write the raw data, instead of trying +/// to resolve an ID. +/// +/// @see SimDataBlock for further details about datablocks. +/// @see http://hosted.tribalwar.com/t2faq/datablocks.shtml for an excellent +/// explanation of the basics of datablocks from a scripting perspective. +/// @nosubgrouping +class GameBaseData : public SimDataBlock { + private: + typedef SimDataBlock Parent; + + public: + bool packed; + StringTableEntry category; + + bool onAdd(); + + // The derived class should provide the following: + DECLARE_CONOBJECT(GameBaseData); + GameBaseData(); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + void unpackData(BitStream* stream); +}; + +DECLARE_CONSOLETYPE(GameBaseData) + +//---------------------------------------------------------------------------- +// A few utility methods for sending datablocks over the net +//---------------------------------------------------------------------------- + +bool UNPACK_DB_ID(BitStream *, U32 & id); +bool PACK_DB_ID(BitStream *, U32 id); +bool PRELOAD_DB(U32 & id, SimDataBlock **, bool server, const char * clientMissing = NULL, const char * serverMissing = NULL); + +//---------------------------------------------------------------------------- +class GameConnection; +class WaterObject; + +// For truly it is written: "The wise man extends GameBase for his purposes, +// while the fool has the ability to eject shell casings from the belly of his +// dragon." -- KillerBunny + +/// Base class for game objects which use datablocks, networking, are editable, +/// and need to process ticks. +/// +/// @section GameBase_process GameBase and ProcessList +/// +/// GameBase adds two kinds of time-based updates. Torque works off of a concept +/// of ticks. Ticks are slices of time 32 milliseconds in length. There are three +/// methods which are used to update GameBase objects that are registered with +/// the ProcessLists: +/// - processTick(Move*) is called on each object once for every tick, regardless +/// of the "real" framerate. +/// - interpolateTick(float) is called on client objects when they need to interpolate +/// to match the next tick. +/// - advanceTime(float) is called on client objects so they can do time-based behaviour, +/// like updating animations. +/// +/// Torque maintains a server and a client processing list; in a local game, both +/// are populated, while in multiplayer situations, either one or the other is +/// populated. +/// +/// You can control whether an object is considered for ticking by means of the +/// setProcessTick() method. +/// +/// @section GameBase_datablock GameBase and Datablocks +/// +/// GameBase adds support for datablocks. Datablocks are secondary classes which store +/// static data for types of game elements. For instance, this means that all "light human +/// male armor" type Players share the same datablock. Datablocks typically store not only +/// raw data, but perform precalculations, like finding nodes in the game model, or +/// validating movement parameters. +/// +/// There are three parts to the datablock interface implemented in GameBase: +/// - getDataBlock(), which gets a pointer to the current datablock. This is +/// mostly for external use; for in-class use, it's better to directly access the +/// mDataBlock member. +/// - setDataBlock(), which sets mDataBlock to point to a new datablock; it +/// uses the next part of the interface to inform subclasses of this. +/// - onNewDataBlock() is called whenever a new datablock is assigned to a GameBase. +/// +/// Datablocks are also usable through the scripting language. +/// +/// @see SimDataBlock for more details. +/// +/// @section GameBase_networking GameBase and Networking +/// +/// writePacketData() and readPacketData() are called to transfer information needed for client +/// side prediction. They are usually used when updating a client of its control object state. +/// +/// Subclasses of GameBase usually transmit positional and basic status data in the packUpdate() +/// functions, while giving velocity, momentum, and similar state information in the writePacketData(). +/// +/// writePacketData()/readPacketData() are called in addition to packUpdate/unpackUpdate(). +/// +/// @nosubgrouping +class GameBase : public SceneObject, public ProcessObject +{ + private: + typedef SceneObject Parent; + friend class ClientProcessList; + friend class ServerProcessList; + + /// @name Datablock + /// @{ + private: + GameBaseData* mDataBlock; + StringTableEntry mNameTag; + + /// @} + TickCache mTickCache; + + // Control interface + GameConnection* mControllingClient; + + SimObjectPtr mAfterObject; + + public: + static bool gShowBoundingBox; ///< Should we render bounding boxes? + protected: + bool mProcessTick; + F32 mCameraFov; + + /// The WaterObject we are currently within. + WaterObject *mCurrentWaterObject; + + static bool setDataBlockProperty(void* obj, const char* db); + +#ifdef TORQUE_DEBUG_NET_MOVES + U32 mLastMoveId; + U32 mTicksSinceLastMove; + bool mIsAiControlled; +#endif + + public: + GameBase(); + ~GameBase(); + + enum GameBaseMasks { + InitialUpdateMask = Parent::NextFreeMask, + DataBlockMask = InitialUpdateMask << 1, + ExtendedInfoMask = DataBlockMask << 1, + ControlMask = ExtendedInfoMask << 1, + NextFreeMask = ControlMask << 1 + }; + + // net flags added by game base + enum + { + NetOrdered = BIT(Parent::MaxNetFlagBit+1), // if set, process in same order on client and server + NetNearbyAdded = BIT(Parent::MaxNetFlagBit+2), // work flag -- set during client catchup when neighbors have been checked + GhostUpdated = BIT(Parent::MaxNetFlagBit+3), // set whenever ghost updated (and reset) on client -- for hifi objects + TickLast = BIT(Parent::MaxNetFlagBit+4), // if set, tick this object after all others (except other tick last objects) + NewGhost = BIT(Parent::MaxNetFlagBit+5), // if set, this ghost was just added during the last update + HiFiPassive = BIT(Parent::MaxNetFlagBit+6), // hifi passive objects don't interact with other hifi passive objects + MaxNetFlagBit = Parent::MaxNetFlagBit+6 + }; + + /// @name Inherited Functionality. + /// @{ + + bool onAdd(); + void onRemove(); + void inspectPostApply(); + static void initPersistFields(); + static void consoleInit(); + /// @} + + ///@name Datablock + ///@{ + + /// Assigns this object a datablock and loads attributes with onNewDataBlock. + /// + /// @see onNewDataBlock + /// @param dptr Datablock + bool setDataBlock(GameBaseData* dptr); + + /// Returns the datablock for this object. + GameBaseData* getDataBlock() { return mDataBlock; } + + /// Called when a new datablock is set. This allows subclasses to + /// appropriately handle new datablocks. + /// + /// @see setDataBlock() + /// @param dptr New datablock + virtual bool onNewDataBlock(GameBaseData* dptr); + ///@} + + /// @name Script + /// The scriptOnXX methods are invoked by the leaf classes + /// @{ + + /// Executes the 'onAdd' script function for this object. + /// @note This must be called after everything is ready + void scriptOnAdd(); + + /// Executes the 'onNewDataBlock' script function for this object. + /// + /// @note This must be called after everything is loaded. + void scriptOnNewDataBlock(); + + /// Executes the 'onRemove' script function for this object. + /// @note This must be called while the object is still valid + void scriptOnRemove(); + /// @} + + /// @name Tick Processing + /// @{ + + /// Set the status of tick processing. + /// + /// If this is set to true, processTick will be called; if false, + /// then it will be skipped. + /// + /// @see processTick + /// @param t If true, tick processing is enabled. + void setProcessTick(bool t) { mProcessTick = t; } + + /// Force this object to process after some other object. + /// + /// For example, a player mounted to a vehicle would want to process after the vehicle, + /// to prevent a visible "lagging" from occurring when the vehicle motions, so the player + /// would be set to processAfter(theVehicle); + /// + /// @param obj Object to process after + void processAfter(GameBase *obj); + + /// Clears the effects of a call to processAfter() + void clearProcessAfter(); + + /// Returns the object that this processes after. + /// + /// @see processAfter + GameBase* getProcessAfter() { return mAfterObject; } + ProcessObject* getAfterObject() { return mAfterObject; } + + /// Removes this object from the tick-processing list + void removeFromProcessList() { plUnlink(); } + + /// Processes a move event and updates object state once every 32 milliseconds. + /// + /// This takes place both on the client and server, every 32 milliseconds (1 tick). + /// + /// @see ProcessList + /// @param move Move event corresponding to this tick, or NULL. + virtual void processTick(const Move *move); + + /// Interpolates between tick events. This takes place on the CLIENT ONLY. + /// + /// @param delta Time since last call to interpolate + virtual void interpolateTick(F32 delta); + + /// Advances simulation time for animations. This is called every frame. + /// + /// @param dt Time since last advance call + virtual void advanceTime(F32 dt); + + /// Allow object a chance to tweak move before it is sent to client and server. + virtual void preprocessMove(Move *move); + /// @} + + // tick cache methods for hifi networking... + TickCache & getTickCache() { return mTickCache; } + void setGhostUpdated(bool b) { if (b) mNetFlags.set(GhostUpdated); else mNetFlags.clear(GhostUpdated); } + bool isGhostUpdated() const { return mNetFlags.test(GhostUpdated); } + void setNewGhost(bool n) { if (n) mNetFlags.set(NewGhost); else mNetFlags.clear(NewGhost); } + bool isNewGhost() { return mNetFlags.test(NewGhost); } + virtual void computeNetSmooth(F32 backDelta) {} + + /// @name Network + /// @see NetObject, NetConnection + /// @{ + + F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + /// Write state information necessary to perform client side prediction of an object. + /// + /// This information is sent only to the controling object. For example, if you are a client + /// controlling a Player, the server uses writePacketData() instead of packUpdate() to + /// generate the data you receive. + /// + /// @param conn Connection for which we're generating this data. + /// @param stream Bitstream for output. + virtual void writePacketData(GameConnection *conn, BitStream *stream); + + /// Read data written with writePacketData() and update the object state. + /// + /// @param conn Connection for which we're generating this data. + /// @param stream Bitstream to read. + virtual void readPacketData(GameConnection *conn, BitStream *stream); + + /// Gets the checksum for packet data. + /// + /// Basically writes a packet, does a CRC check on it, and returns + /// that CRC. + /// + /// @see writePacketData + /// @param conn Game connection + virtual U32 getPacketDataChecksum(GameConnection *conn); + ///@} + + /// @name User control + /// @{ + + /// Returns the client controlling this object + GameConnection *getControllingClient() { return mControllingClient; } + + /// Sets the client controlling this object + /// @param client Client that is now controlling this object + virtual void setControllingClient(GameConnection *client); + + virtual GameBase * getControllingObject() { return NULL; } + virtual GameBase * getControlObject() { return NULL; } + virtual void setControlObject(GameBase *) { } + /// @} + + virtual F32 getDefaultCameraFov() { return 90.f; } + virtual F32 getCameraFov() { return 90.f; } + virtual void setCameraFov(F32 fov) { } + virtual bool isValidCameraFov(F32 fov) { return true; } + virtual bool useObjsEyePoint() const { return false; } + virtual bool onlyFirstPerson() const { return false; } + virtual F32 getDamageFlash() const { return 1.0f; } + virtual F32 getWhiteOut() const { return 1.0f; } + + // Not implemented here, but should return the Camera to world transformation matrix + virtual void getCameraTransform(F32 *pos, MatrixF *mat) { *mat = MatrixF::Identity; } + + /// Returns the water object we are colliding with, it is up to derived + /// classes to actually set this object. + virtual WaterObject* getCurrentWaterObject() { return mCurrentWaterObject; } +//==========rpg stuff============= +protected: + std::vector _mRPGBases; +public: + void pushRPGBase(RPGBase * pBase); + void removeRPGBase(RPGBase * pBase); + void onInterrupt(); + void onMoved(const Point3F & pos); +//============================= + DECLARE_CONOBJECT(GameBase); +}; + + +#endif diff --git a/T3D/gameConnection.cpp b/T3D/gameConnection.cpp new file mode 100644 index 0000000..54e4c34 --- /dev/null +++ b/T3D/gameConnection.cpp @@ -0,0 +1,2156 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/profiler.h" +#include "core/dnet.h" +#include "console/consoleTypes.h" +#include "console/simBase.h" +#include "core/stream/bitStream.h" +#include "sfx/sfxProfile.h" +#include "app/game.h" +#include "T3D/gameConnection.h" +#include "T3D/gameConnectionEvents.h" +#include "app/auth.h" +#include "T3D/gameProcess.h" +#include "core/util/safeDelete.h" +#include "T3D/camera.h" +#include "core/stream/fileStream.h" + +//---------------------------------------------------------------------------- +#define MAX_MOVE_PACKET_SENDS 4 + +#define ControlRequestTime 5000 + +const U32 GameConnection::CurrentProtocolVersion = 12; +const U32 GameConnection::MinRequiredProtocolVersion = 12; + +//---------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GameConnection); +S32 GameConnection::mLagThresholdMS = 0; +Signal GameConnection::smFovUpdate; +Signal GameConnection::smPlayingDemo; + +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << +StringTableEntry GameConnection::server_cache_filename = ""; +StringTableEntry GameConnection::client_cache_filename = ""; +bool GameConnection::server_cache_on = true; +bool GameConnection::client_cache_on = true; +#endif // AFX CODE BLOCK (db-cache) >> + +//---------------------------------------------------------------------------- +GameConnection::GameConnection() +{ + mRolloverObj = NULL; + mPreSelectedObj = NULL; + mSelectedObj = NULL; + mChangedSelectedObj = false; + mPreSelectTimestamp = 0; + + mLagging = false; + mControlObject = NULL; + mCameraObject = NULL; + + mMoveList.setConnection(this); + + mDataBlockModifiedKey = 0; + mMaxDataBlockModifiedKey = 0; + mAuthInfo = NULL; + mControlForceMismatch = false; + mConnectArgc = 0; + for(U32 i = 0; i < MaxConnectArgs; i++) + mConnectArgv[i] = 0; + + mJoinPassword = NULL; + + mMissionCRC = 0xffffffff; + + mDamageFlash = mWhiteOut = 0; + + mCameraPos = 0; + mCameraSpeed = 10; + + mCameraFov = 90.f; + mUpdateCameraFov = false; + + mAIControlled = false; + + mDisconnectReason[0] = 0; + + //blackout vars + mBlackOut = 0.0f; + mBlackOutTimeMS = 0; + mBlackOutStartTimeMS = 0; + mFadeToBlack = false; + + // first person + mFirstPerson = false; + mUpdateFirstPerson = false; +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + client_db_stream = new InfiniteBitStream; + server_cache_CRC = 0xffffffff; +#endif // AFX CODE BLOCK (db-cache) >> +} + +GameConnection::~GameConnection() +{ + delete mAuthInfo; + for(U32 i = 0; i < mConnectArgc; i++) + dFree(mConnectArgv[i]); + dFree(mJoinPassword); +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + delete client_db_stream; +#endif // AFX CODE BLOCK (db-cache) >> +} + +//---------------------------------------------------------------------------- + +bool GameConnection::canRemoteCreate() +{ + return true; +} + +void GameConnection::setConnectArgs(U32 argc, const char **argv) +{ + if(argc > MaxConnectArgs) + argc = MaxConnectArgs; + mConnectArgc = argc; + for(U32 i = 0; i < mConnectArgc; i++) + mConnectArgv[i] = dStrdup(argv[i]); +} + +void GameConnection::setJoinPassword(const char *password) +{ + mJoinPassword = dStrdup(password); +} + +ConsoleMethod(GameConnection, setJoinPassword, void, 3, 3, "") +{ + object->setJoinPassword(argv[2]); +} + +ConsoleMethod(GameConnection, setConnectArgs, void, 3, 17, "") +{ + object->setConnectArgs(argc - 2, argv + 2); +} + +void GameConnection::onTimedOut() +{ + if(isConnectionToServer()) + { + Con::printf("Connection to server timed out"); + Con::executef(this, "onConnectionTimedOut"); + } + else + { + Con::printf("Client %d timed out.", getId()); + setDisconnectReason("TimedOut"); + } + +} + +void GameConnection::onConnectionEstablished(bool isInitiator) +{ + if(isInitiator) + { + setGhostFrom(false); + setGhostTo(true); + setSendingEvents(true); + setTranslatesStrings(true); + setIsConnectionToServer(); + mServerConnection = this; + Con::printf("Connection established %d", getId()); + Con::executef(this, "onConnectionAccepted"); + } + else + { + setGhostFrom(true); + setGhostTo(false); + setSendingEvents(true); + setTranslatesStrings(true); + Sim::getClientGroup()->addObject(this); + mMoveList.init(); + + const char *argv[MaxConnectArgs + 2]; + argv[0] = "onConnect"; + for(U32 i = 0; i < mConnectArgc; i++) + argv[i + 2] = mConnectArgv[i]; + Con::execute(this, mConnectArgc + 2, argv); + } +} + +void GameConnection::onConnectTimedOut() +{ + Con::executef(this, "onConnectRequestTimedOut"); +} + +void GameConnection::onDisconnect(const char *reason) +{ + if(isConnectionToServer()) + { + Con::printf("Connection with server lost."); + Con::executef(this, "onConnectionDropped", reason); + mMoveList.init(); + } + else + { + Con::printf("Client %d disconnected.", getId()); + setDisconnectReason(reason); + } +} + +void GameConnection::onConnectionRejected(const char *reason) +{ + Con::executef(this, "onConnectRequestRejected", reason); +} + +void GameConnection::handleStartupError(const char *errorString) +{ + Con::executef(this, "onConnectRequestRejected", errorString); +} + +void GameConnection::writeConnectAccept(BitStream *stream) +{ + Parent::writeConnectAccept(stream); + stream->write(getProtocolVersion()); +} + +bool GameConnection::readConnectAccept(BitStream *stream, const char **errorString) +{ + if(!Parent::readConnectAccept(stream, errorString)) + return false; + + U32 protocolVersion; + stream->read(&protocolVersion); + if(protocolVersion < MinRequiredProtocolVersion || protocolVersion > CurrentProtocolVersion) + { + *errorString = "CHR_PROTOCOL"; // this should never happen unless someone is faking us out. + return false; + } + return true; +} + +void GameConnection::writeConnectRequest(BitStream *stream) +{ + Parent::writeConnectRequest(stream); + stream->writeString(GameString); + stream->write(CurrentProtocolVersion); + stream->write(MinRequiredProtocolVersion); + stream->writeString(mJoinPassword); + + stream->write(mConnectArgc); + for(U32 i = 0; i < mConnectArgc; i++) + stream->writeString(mConnectArgv[i]); +} + +bool GameConnection::readConnectRequest(BitStream *stream, const char **errorString) +{ + if(!Parent::readConnectRequest(stream, errorString)) + return false; + U32 currentProtocol, minProtocol; + char gameString[256]; + stream->readString(gameString); + if(dStrcmp(gameString, GameString)) + { + *errorString = "CHR_GAME"; + return false; + } + + stream->read(¤tProtocol); + stream->read(&minProtocol); + + char joinPassword[256]; + stream->readString(joinPassword); + + if(currentProtocol < MinRequiredProtocolVersion) + { + *errorString = "CHR_PROTOCOL_LESS"; + return false; + } + if(minProtocol > CurrentProtocolVersion) + { + *errorString = "CHR_PROTOCOL_GREATER"; + return false; + } + setProtocolVersion(currentProtocol < CurrentProtocolVersion ? currentProtocol : CurrentProtocolVersion); + + const char *serverPassword = Con::getVariable("Pref::Server::Password"); + if(serverPassword[0]) + { + if(dStrcmp(joinPassword, serverPassword)) + { + *errorString = "CHR_PASSWORD"; + return false; + } + } + + stream->read(&mConnectArgc); + if(mConnectArgc > MaxConnectArgs) + { + *errorString = "CR_INVALID_ARGS"; + return false; + } + const char *connectArgv[MaxConnectArgs + 3]; + for(U32 i = 0; i < mConnectArgc; i++) + { + char argString[256]; + stream->readString(argString); + mConnectArgv[i] = dStrdup(argString); + connectArgv[i + 3] = mConnectArgv[i]; + } + connectArgv[0] = "onConnectRequest"; + char buffer[256]; + Net::addressToString(getNetAddress(), buffer); + connectArgv[2] = buffer; + + const char *ret = Con::execute(this, mConnectArgc + 3, connectArgv); + if(ret[0]) + { + *errorString = ret; + return false; + } + return true; +} +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +void GameConnection::connectionError(const char *errorString) +{ + if(isConnectionToServer()) + { + Con::printf("Connection error: %s.", errorString); + Con::executef(this, "onConnectionError", errorString); + } + else + { + Con::printf("Client %d packet error: %s.", getId(), errorString); + setDisconnectReason("Packet Error."); + } + deleteObject(); +} + + +void GameConnection::setAuthInfo(const AuthInfo *info) +{ + mAuthInfo = new AuthInfo; + *mAuthInfo = *info; +} + +const AuthInfo *GameConnection::getAuthInfo() +{ + return mAuthInfo; +} + + +//---------------------------------------------------------------------------- + +void GameConnection::setControlObject(GameBase *obj) +{ + if(mControlObject == obj) + return; + + if(mControlObject && mControlObject != mCameraObject) + mControlObject->setControllingClient(0); + + if(obj) + { + // Nothing else is permitted to control this object. + if (GameBase* coo = obj->getControllingObject()) + coo->setControlObject(0); + if (GameConnection *con = obj->getControllingClient()) + { + if(this != con) + { + // was it controlled via camera or control + if(con->getControlObject() == obj) + con->setControlObject(0); + else + con->setCameraObject(0); + } + } + + // We are now the controlling client of this object. + obj->setControllingClient(this); + } + + // Okay, set our control object. + mControlObject = obj; + mControlForceMismatch = true; + + if(mCameraObject.isNull()) + setScopeObject(mControlObject); +} + +void GameConnection::setCameraObject(GameBase *obj) +{ + if(mCameraObject == obj) + return; + + if(mCameraObject && mCameraObject != mControlObject) + mCameraObject->setControllingClient(0); + + if(obj) + { + // nothing else is permitted to control this object + if(GameBase *coo = obj->getControllingObject()) + coo->setControlObject(0); + + if(GameConnection *con = obj->getControllingClient()) + { + if(this != con) + { + // was it controlled via camera or control + if(con->getControlObject() == obj) + con->setControlObject(0); + else + con->setCameraObject(0); + } + } + + // we are now the controlling client of this object + obj->setControllingClient(this); + } + + // Okay, set our camera object. + mCameraObject = obj; + + if(mCameraObject.isNull()) + setScopeObject(mControlObject); + else + { + setScopeObject(mCameraObject); + + // if this is a client then set the fov and active image + if(isConnectionToServer()) + { + F32 fov = mCameraObject->getDefaultCameraFov(); + //GameSetCameraFov(fov); + smFovUpdate.trigger(fov); + } + } +} + +GameBase* GameConnection::getCameraObject() +{ + // If there is no camera object, or if we're first person, return + // the control object. + if( !mControlObject.isNull() && (mCameraObject.isNull() || mFirstPerson)) + return mControlObject; + return mCameraObject; +} + +static S32 sChaseQueueSize = 0; +static MatrixF* sChaseQueue = 0; +static S32 sChaseQueueHead = 0; +static S32 sChaseQueueTail = 0; + +bool GameConnection::getControlCameraTransform(F32 dt, MatrixF* mat) +{ + GameBase* obj = getCameraObject(); + if(!obj) + return false; + + GameBase* cObj = obj; + while((cObj = cObj->getControllingObject()) != 0) + { + if(cObj->useObjsEyePoint()) + obj = cObj; + } + + if (dt) + { + if (mFirstPerson || obj->onlyFirstPerson()) + { + if (mCameraPos > 0) + if ((mCameraPos -= mCameraSpeed * dt) <= 0) + mCameraPos = 0; + } + else + { + if (mCameraPos < 1) + if ((mCameraPos += mCameraSpeed * dt) > 1) + mCameraPos = 1; + } + } + + if (!sChaseQueueSize || mFirstPerson || obj->onlyFirstPerson()) + obj->getCameraTransform(&mCameraPos,mat); + else + { + MatrixF& hm = sChaseQueue[sChaseQueueHead]; + MatrixF& tm = sChaseQueue[sChaseQueueTail]; + obj->getCameraTransform(&mCameraPos,&hm); + *mat = tm; + if (dt) + { + if ((sChaseQueueHead += 1) >= sChaseQueueSize) + sChaseQueueHead = 0; + if (sChaseQueueHead == sChaseQueueTail) + if ((sChaseQueueTail += 1) >= sChaseQueueSize) + sChaseQueueTail = 0; + } + } + return true; +} + +bool GameConnection::getControlCameraFov(F32 * fov) +{ + //find the last control object in the chain (client->player->turret->whatever...) + GameBase *obj = getCameraObject(); + GameBase *cObj = NULL; + while (obj) + { + cObj = obj; + obj = obj->getControlObject(); + } + if (cObj) + { + *fov = cObj->getCameraFov(); + return(true); + } + + return(false); +} + +bool GameConnection::isValidControlCameraFov(F32 fov) +{ + //find the last control object in the chain (client->player->turret->whatever...) + GameBase *obj = getCameraObject(); + GameBase *cObj = NULL; + while (obj) + { + cObj = obj; + obj = obj->getControlObject(); + } + + return cObj ? cObj->isValidCameraFov(fov) : NULL; +} + +bool GameConnection::setControlCameraFov(F32 fov) +{ + //find the last control object in the chain (client->player->turret->whatever...) + GameBase *obj = getCameraObject(); + GameBase *cObj = NULL; + while (obj) + { + cObj = obj; + obj = obj->getControlObject(); + } + if (cObj) + { + // allow shapebase to clamp fov to its datablock values + cObj->setCameraFov(mClampF(fov, MinCameraFov, MaxCameraFov)); + fov = cObj->getCameraFov(); + + // server fov of client has 1degree resolution + if(S32(fov) != S32(mCameraFov)) + mUpdateCameraFov = true; + + mCameraFov = fov; + return(true); + } + return(false); +} + +bool GameConnection::getControlCameraVelocity(Point3F *vel) +{ + if (GameBase* obj = getCameraObject()) { + *vel = obj->getVelocity(); + return true; + } + return false; +} + +bool GameConnection::isControlObjectRotDampedCamera() +{ + if (Camera* cam = dynamic_cast(getCameraObject())) { + if(cam->isRotationDamped()) + return true; + } + return false; +} + +void GameConnection::setFirstPerson(bool firstPerson) +{ + mFirstPerson = firstPerson; + mUpdateFirstPerson = true; +} + + + +//---------------------------------------------------------------------------- + +bool GameConnection::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +void GameConnection::onRemove() +{ + if(isNetworkConnection()) + { + sendDisconnectPacket(mDisconnectReason); + } + else if (isLocalConnection() && isConnectionToServer()) + { + // we're a client-side but local connection + // delete the server side of the connection on our local server so that it updates + // clientgroup and what not (this is so that we can disconnect from a local server + // without needing to destroy and recreate the server before we can connect to it + // again) + getRemoteConnection()->deleteObject(); + setRemoteConnectionObject(NULL); + } + if(!isConnectionToServer()) + Con::executef(this, "onDrop", mDisconnectReason); + + if (mControlObject) + mControlObject->setControllingClient(0); + Parent::onRemove(); +} + +void GameConnection::setDisconnectReason(const char *str) +{ + dStrncpy(mDisconnectReason, str, sizeof(mDisconnectReason) - 1); + mDisconnectReason[sizeof(mDisconnectReason) - 1] = 0; +} + +//---------------------------------------------------------------------------- + +void GameConnection::handleRecordedBlock(U32 type, U32 size, void *data) +{ + switch(type) + { + case BlockTypeMove: + mMoveList.pushMove(*((Move *) data)); + if(isRecording()) // put it back into the stream + recordBlock(type, size, data); + break; + default: + Parent::handleRecordedBlock(type, size, data); + break; + } +} + +void GameConnection::writeDemoStartBlock(ResizeBitStream *stream) +{ + // write all the data blocks to the stream: + + for(SimObjectId i = DataBlockObjectIdFirst; i <= DataBlockObjectIdLast; i++) + { + SimDataBlock *data; + if(Sim::findObject(i, data)) + { + stream->writeFlag(true); + SimDataBlockEvent evt(data); + evt.pack(this, stream); + stream->validate(); + } + } + stream->writeFlag(false); + stream->write(mFirstPerson); + stream->write(mCameraPos); + stream->write(mCameraSpeed); + + stream->writeString(Con::getVariable("$Client::MissionFile")); + + mMoveList.writeDemoStartBlock(stream); + + // dump all the "demo" vars associated with this connection: + SimFieldDictionaryIterator itr(getFieldDictionary()); + + SimFieldDictionary::Entry *entry; + while((entry = *itr) != NULL) + { + if(!dStrnicmp(entry->slotName, "demo", 4)) + { + stream->writeFlag(true); + stream->writeString(entry->slotName + 4); + stream->writeString(entry->value); + stream->validate(); + } + ++itr; + } + stream->writeFlag(false); + Parent::writeDemoStartBlock(stream); + + stream->validate(); + + // dump out the control object ghost id + S32 idx = mControlObject ? getGhostIndex(mControlObject) : -1; + stream->write(idx); + if(mControlObject) + { +#ifdef TORQUE_NET_STATS + U32 beginPos = stream->getBitPosition(); +#endif + mControlObject->writePacketData(this, stream); +#ifdef TORQUE_NET_STATS + mControlObject->getClassRep()->updateNetStatWriteData( stream->getBitPosition() - beginPos); +#endif + } + idx = mCameraObject ? getGhostIndex(mCameraObject) : -1; + stream->write(idx); + if(mCameraObject && mCameraObject != mControlObject) + { +#ifdef TORQUE_NET_STATS + U32 beginPos = stream->getBitPosition(); +#endif + + mCameraObject->writePacketData(this, stream); + +#ifdef TORQUE_NET_STATS + mCameraObject->getClassRep()->updateNetStatWriteData( stream->getBitPosition() - beginPos); +#endif + } + mLastControlRequestTime = Platform::getVirtualMilliseconds(); +} + +bool GameConnection::readDemoStartBlock(BitStream *stream) +{ + while(stream->readFlag()) + { + SimDataBlockEvent evt; + evt.unpack(this, stream); + evt.process(this); + } + + while(mDataBlockLoadList.size()) + { + preloadNextDataBlock(false); + if(mErrorBuffer.isNotEmpty()) + return false; + } + + stream->read(&mFirstPerson); + stream->read(&mCameraPos); + stream->read(&mCameraSpeed); + + char buf[256]; + stream->readString(buf); + Con::setVariable("$Client::MissionFile",buf); + + mMoveList.readDemoStartBlock(stream); + + // read in all the demo vars associated with this recording + // they are all tagged on to the object and start with the + // string "demo" + + while(stream->readFlag()) + { + StringTableEntry slotName = StringTable->insert("demo"); + char array[256]; + char value[256]; + stream->readString(array); + stream->readString(value); + setDataField(slotName, array, value); + } + bool ret = Parent::readDemoStartBlock(stream); + // grab the control object + S32 idx; + stream->read(&idx); + + GameBase * obj = 0; + if(idx != -1) + { + obj = dynamic_cast(resolveGhost(idx)); + setControlObject(obj); + obj->readPacketData(this, stream); + } + + // Get the camera object, and read it in if it's different + S32 idx2; + stream->read(&idx2); + obj = 0; + if(idx2 != -1 && idx2 != idx) + { + obj = dynamic_cast(resolveGhost(idx2)); + setCameraObject(obj); + obj->readPacketData(this, stream); + } + return ret; +} + +void GameConnection::demoPlaybackComplete() +{ + static const char *demoPlaybackArgv[1] = { "demoPlaybackComplete" }; + Sim::postCurrentEvent(Sim::getRootGroup(), new SimConsoleEvent(1, demoPlaybackArgv, false)); + Parent::demoPlaybackComplete(); +} + +void GameConnection::ghostPreRead(NetObject * nobj, bool newGhost) +{ + Parent::ghostPreRead( nobj, newGhost ); + + mMoveList.ghostPreRead(nobj,newGhost); +} + +void GameConnection::ghostReadExtra(NetObject * nobj, BitStream * bstream, bool newGhost) +{ + Parent::ghostReadExtra( nobj, bstream, newGhost ); + + mMoveList.ghostReadExtra(nobj, bstream, newGhost); + } + +void GameConnection::ghostWriteExtra(NetObject * nobj, BitStream * bstream) +{ + Parent::ghostWriteExtra( nobj, bstream); + + mMoveList.ghostWriteExtra(nobj, bstream); +} + +//---------------------------------------------------------------------------- + +void GameConnection::readPacket(BitStream *bstream) +{ + bstream->clearStringBuffer(); + bstream->clearCompressionPoint(); + + if (isConnectionToServer()) + { + mMoveList.clientReadMovePacket(bstream); + + mDamageFlash = 0; + mWhiteOut = 0; + if(bstream->readFlag()) + { + if(bstream->readFlag()) + mDamageFlash = bstream->readFloat(7); + if(bstream->readFlag()) + mWhiteOut = bstream->readFloat(7) * 1.5; + } + + if (bstream->readFlag()) + { + if(bstream->readFlag()) + { + // the control object is dirty...so we get an update: + bool callScript = false; + if(mControlObject.isNull()) + callScript = true; + + S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); + GameBase* obj = dynamic_cast(resolveGhost(gIndex)); + if (mControlObject != obj) + setControlObject(obj); +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + obj->readPacketData(this, bstream); +#ifdef TORQUE_NET_STATS + obj->getClassRep()->updateNetStatReadData(bstream->getBitPosition() - beginSize); +#endif + + // let move list know that control object is dirty + mMoveList.markControlDirty(); + + if(callScript) + Con::executef(this, "initialControlSet"); + } + else + { + // read out the compression point + Point3F pos; + bstream->read(&pos.x); + bstream->read(&pos.y); + bstream->read(&pos.z); + bstream->setCompressionPoint(pos); + } + } + + if (bstream->readFlag()) + { + S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); + GameBase* obj = dynamic_cast(resolveGhost(gIndex)); + setCameraObject(obj); + obj->readPacketData(this, bstream); + } + else + setCameraObject(0); + + // server changed first person + if(bstream->readFlag()) + { + setFirstPerson(bstream->readFlag()); + mUpdateFirstPerson = false; + } + + // server forcing a fov change? + if(bstream->readFlag()) + { + S32 fov = bstream->readInt(8); + setControlCameraFov((F32)fov); + + // don't bother telling the server if we were able to set the fov + F32 setFov; + if(getControlCameraFov(&setFov) && (S32(setFov) == fov)) + mUpdateCameraFov = false; + + // update the games fov info + smFovUpdate.trigger((F32)fov); + } + } + else + { + mMoveList.serverReadMovePacket(bstream); + + mCameraPos = bstream->readFlag() ? 1.0f : 0.0f; + if (bstream->readFlag()) + mControlForceMismatch = true; + + // client changed first person + if(bstream->readFlag()) + { + setFirstPerson(bstream->readFlag()); + mUpdateFirstPerson = false; + } + + // check fov change.. 1degree granularity on server + if(bstream->readFlag()) + { + S32 fov = mClamp(bstream->readInt(8), S32(MinCameraFov), S32(MaxCameraFov)); + setControlCameraFov((F32)fov); + + // may need to force client back to a valid fov + F32 setFov; + if(getControlCameraFov(&setFov) && (S32(setFov) == fov)) + mUpdateCameraFov = false; + } + } + + Parent::readPacket(bstream); + bstream->clearCompressionPoint(); + bstream->clearStringBuffer(); + if (isConnectionToServer()) + { + PROFILE_START(ClientCatchup); + gClientProcessList.clientCatchup(this); + PROFILE_END(); + } +} + +void GameConnection::writePacket(BitStream *bstream, PacketNotify *note) +{ + bstream->clearCompressionPoint(); + bstream->clearStringBuffer(); + GamePacketNotify *gnote = (GamePacketNotify *) note; + + U32 startPos = bstream->getBitPosition(); + if (isConnectionToServer()) + { + mMoveList.clientWriteMovePacket(bstream); + + bstream->writeFlag(mCameraPos == 1); + + // if we're recording, we want to make sure that we get periodic updates of the + // control object "just in case" - ie if the math copro is different between the + // recording machine (SIMD vs FPU), we get periodic corrections + + bool forceUpdate = false; + if(isRecording()) + { + U32 currentTime = Platform::getVirtualMilliseconds(); + if(currentTime - mLastControlRequestTime > ControlRequestTime) + { + mLastControlRequestTime = currentTime; + forceUpdate=true;; + } + } + bstream->writeFlag(forceUpdate); + + // first person changed? + if(bstream->writeFlag(mUpdateFirstPerson)) + { + bstream->writeFlag(mFirstPerson); + mUpdateFirstPerson = false; + } + + // camera fov changed? (server fov resolution is 1 degree) + if(bstream->writeFlag(mUpdateCameraFov)) + { + bstream->writeInt(mClamp(S32(mCameraFov), S32(MinCameraFov), S32(MaxCameraFov)), 8); + mUpdateCameraFov = false; + } + DEBUG_LOG(("PKLOG %d CLIENTMOVES: %d", getId(), bstream->getCurPos() - startPos)); + } + else + { + mMoveList.serverWriteMovePacket(bstream); + + // get the ghost index of the control object, and write out + // all the damage flash & white out + + S32 gIndex = -1; + if (!mControlObject.isNull()) + { + gIndex = getGhostIndex(mControlObject); + + F32 flash = mControlObject->getDamageFlash(); + F32 whiteOut = mControlObject->getWhiteOut(); + if(bstream->writeFlag(flash != 0 || whiteOut != 0)) + { + if(bstream->writeFlag(flash != 0)) + bstream->writeFloat(flash, 7); + if(bstream->writeFlag(whiteOut != 0)) + bstream->writeFloat(whiteOut/1.5, 7); + } + } + else + bstream->writeFlag(false); + + if (bstream->writeFlag(gIndex != -1)) + { + // assume that the control object will write in a compression point + if(bstream->writeFlag(mMoveList.isMismatch() || mControlForceMismatch)) + { +#ifdef TORQUE_DEBUG_NET + if (mMoveList.isMismatch()) + Con::printf("packetDataChecksum disagree!"); + else + Con::printf("packetDataChecksum disagree! (force)"); +#endif + + bstream->writeInt(gIndex, NetConnection::GhostIdBitSize); +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + mControlObject->writePacketData(this, bstream); +#ifdef TORQUE_NET_STATS + mControlObject->getClassRep()->updateNetStatWriteData(bstream->getBitPosition() - beginSize); +#endif + mControlForceMismatch = false; + } + else + { + // we'll have to use the control object's position as the compression point + // should make this lower res for better space usage: + Point3F coPos = mControlObject->getPosition(); + bstream->write(coPos.x); + bstream->write(coPos.y); + bstream->write(coPos.z); + bstream->setCompressionPoint(coPos); + } + } + DEBUG_LOG(("PKLOG %d CONTROLOBJECTSTATE: %d", getId(), bstream->getCurPos() - startPos)); + startPos = bstream->getBitPosition(); + + if (!mCameraObject.isNull() && mCameraObject != mControlObject) + { + gIndex = getGhostIndex(mCameraObject); + if (bstream->writeFlag(gIndex != -1)) + { + bstream->writeInt(gIndex, NetConnection::GhostIdBitSize); + mCameraObject->writePacketData(this, bstream); + } + } + else + bstream->writeFlag( false ); + + // first person changed? + if(bstream->writeFlag(mUpdateFirstPerson)) + { + bstream->writeFlag(mFirstPerson); + mUpdateFirstPerson = false; + } + + // server forcing client fov? + gnote->cameraFov = -1; + if(bstream->writeFlag(mUpdateCameraFov)) + { + gnote->cameraFov = mClamp(S32(mCameraFov), S32(MinCameraFov), S32(MaxCameraFov)); + bstream->writeInt(gnote->cameraFov, 8); + mUpdateCameraFov = false; + } + DEBUG_LOG(("PKLOG %d PINGCAMSTATE: %d", getId(), bstream->getCurPos() - startPos)); + } + + Parent::writePacket(bstream, note); + bstream->clearCompressionPoint(); + bstream->clearStringBuffer(); +} + + +void GameConnection::detectLag() +{ + //see if we're lagging... + S32 curTime = Sim::getCurrentTime(); + if (curTime - mLastPacketTime > mLagThresholdMS) + { + if (!mLagging) + { + mLagging = true; + Con::executef(this, "setLagIcon", "true"); + } + } + else if (mLagging) + { + mLagging = false; + Con::executef(this, "setLagIcon", "false"); + } +} + +GameConnection::GamePacketNotify::GamePacketNotify() +{ + // need to fill in empty notifes for demo start block + cameraFov = 0; +} + +NetConnection::PacketNotify *GameConnection::allocNotify() +{ + return new GamePacketNotify; +} + +void GameConnection::packetReceived(PacketNotify *note) +{ + //record the time so we can tell if we're lagging... + mLastPacketTime = Sim::getCurrentTime(); + + // If we wanted to do something special, we grab our note like this: + //GamePacketNotify *gnote = (GamePacketNotify *) note; + + Parent::packetReceived(note); +} + +void GameConnection::packetDropped(PacketNotify *note) +{ + Parent::packetDropped(note); + GamePacketNotify *gnote = (GamePacketNotify *) note; + if(gnote->cameraFov != -1) + mUpdateCameraFov = true; +} + +//---------------------------------------------------------------------------- + +void GameConnection::play2D(SFXProfile* profile) +{ + postNetEvent(new Sim2DAudioEvent(profile)); +} + +void GameConnection::play3D(SFXProfile* profile, const MatrixF *transform) +{ + if ( !transform ) + play2D(profile); + + else if ( !mControlObject ) + postNetEvent(new Sim3DAudioEvent(profile,transform)); + + else + { + // TODO: Maybe improve this to account for the duration + // of the sound effect and if the control object can get + // into hearing range within time? + + // Only post the event if it's within audible range + // of the control object. + Point3F ear,pos; + transform->getColumn(3,&pos); + mControlObject->getTransform().getColumn(3,&ear); + if ((ear - pos).len() < profile->getDescription()->mMaxDistance) + postNetEvent(new Sim3DAudioEvent(profile,transform)); + } +} + +void GameConnection::doneScopingScene() +{ + // Could add special post-scene scoping here, such as scoping + // objects not visible to the camera, but visible to sensors. +} + +void GameConnection::preloadDataBlock(SimDataBlock *db) +{ + mDataBlockLoadList.push_back(db); + if(mDataBlockLoadList.size() == 1) + preloadNextDataBlock(false); +} + +void GameConnection::fileDownloadSegmentComplete() +{ + // this is called when a the file list has finished processing... + // at this point we can try again to add the object + // subclasses can override this to do, for example, datablock redos. + if(mDataBlockLoadList.size()) + preloadNextDataBlock(mNumDownloadedFiles != 0); + Parent::fileDownloadSegmentComplete(); +} + +void GameConnection::preloadNextDataBlock(bool hadNewFiles) +{ + if(!mDataBlockLoadList.size()) + return; + while(mDataBlockLoadList.size()) + { + // only check for new files if this is the first load, or if new + // files were downloaded from the server. +// if(hadNewFiles) +// gResourceManager->setMissingFileLogging(true); +// gResourceManager->clearMissingFileList(); + SimDataBlock *object = mDataBlockLoadList[0]; + if(!object) + { + // a null object is used to signify that the last ghost in the list is down + mDataBlockLoadList.pop_front(); + AssertFatal(mDataBlockLoadList.size() == 0, "Error! Datablock save list should be empty!"); + sendConnectionMessage(DataBlocksDownloadDone, mDataBlockSequence); +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + // This should be the last of the datablocks. An argument of false + // indicates that this is a client save. + if (clientCacheEnabled()) + saveDatablockCache(false); +#endif // AFX CODE BLOCK (db-cache) >> +// gResourceManager->setMissingFileLogging(false); + return; + } + mFilesWereDownloaded = hadNewFiles; + if(!object->preload(false, mErrorBuffer)) + { + mFilesWereDownloaded = false; + // make sure there's an error message if necessary + if(mErrorBuffer.isEmpty()) + setLastError("Invalid packet. (object preload)"); + + // if there were no new files, make sure the error message + // is the one from the last time we tried to add this object + if(!hadNewFiles) + { + mErrorBuffer = mLastFileErrorBuffer; +// gResourceManager->setMissingFileLogging(false); + return; + } + + // object failed to load, let's see if it had any missing files + if(isLocalConnection() /*|| !gResourceManager->getMissingFileList(mMissingFileList)*/) + { + // no missing files, must be an error + // connection will automagically delete the ghost always list + // when this error is reported. +// gResourceManager->setMissingFileLogging(false); + return; + } + + // ok, copy the error buffer out to a scratch pad for now + mLastFileErrorBuffer = mErrorBuffer; + mErrorBuffer = String(); + + // request the missing files... + mNumDownloadedFiles = 0; + sendNextFileDownloadRequest(); + break; + } + mFilesWereDownloaded = false; +// gResourceManager->setMissingFileLogging(false); + mDataBlockLoadList.pop_front(); + hadNewFiles = true; + } +} + + +//---------------------------------------------------------------------------- +//localconnection only blackout functions +void GameConnection::setBlackOut(bool fadeToBlack, S32 timeMS) +{ + mFadeToBlack = fadeToBlack; + mBlackOutStartTimeMS = Sim::getCurrentTime(); + mBlackOutTimeMS = timeMS; + + //if timeMS <= 0 set the value instantly + if (mBlackOutTimeMS <= 0) + mBlackOut = (mFadeToBlack ? 1.0f : 0.0f); +} + +F32 GameConnection::getBlackOut() +{ + S32 curTime = Sim::getCurrentTime(); + + //see if we're in the middle of a black out + if (curTime < mBlackOutStartTimeMS + mBlackOutTimeMS) + { + S32 elapsedTime = curTime - mBlackOutStartTimeMS; + F32 timePercent = F32(elapsedTime) / F32(mBlackOutTimeMS); + mBlackOut = (mFadeToBlack ? timePercent : 1.0f - timePercent); + } + else + mBlackOut = (mFadeToBlack ? 1.0f : 0.0f); + + //return the blackout time + return mBlackOut; +} + +void GameConnection::handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount) +{ + if(isConnectionToServer()) + { + if(message == DataBlocksDone) + { + mDataBlockLoadList.push_back(NULL); + mDataBlockSequence = sequence; + if(mDataBlockLoadList.size() == 1) + preloadNextDataBlock(true); + } + } + else + { + if(message == DataBlocksDownloadDone) + { + if(getDataBlockSequence() == sequence) + Con::executef(this, "onDataBlocksDone", Con::getIntArg(getDataBlockSequence())); + } + } + Parent::handleConnectionMessage(message, sequence, ghostCount); +} + +//---------------------------------------------------------------------------- + +ConsoleMethod( GameConnection, transmitDataBlocks, void, 3, 3, "(int sequence)") +{ + // Set the datablock sequence. + object->setDataBlockSequence(dAtoi(argv[2])); + + // Store a pointer to the datablock group. + SimDataBlockGroup* pGroup = Sim::getDataBlockGroup(); + + // Determine the size of the datablock group. + const U32 iCount = pGroup->size(); + + // If this is the local client... +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + if (GameConnection::getLocalClientConnection() == object && !GameConnection::serverCacheEnabled()) +#else + if (GameConnection::getLocalClientConnection() == object) +#endif // AFX CODE BLOCK (db-cache) >> + { + // Set up a pointer to the datablock. + SimDataBlock* pDataBlock = 0; + + // Iterate through all the datablocks... + for (U32 i = 0; i < iCount; i++) + { + // Get a pointer to the datablock in question... + pDataBlock = (SimDataBlock*)(*pGroup)[i]; + + // Set the client's new modified key. + object->setMaxDataBlockModifiedKey(pDataBlock->getModifiedKey()); + + // Set up a buffer for the datablock send. + U8 iBuffer[16384]; + BitStream mStream(iBuffer, 16384); + + // Pack the datablock stream. + pDataBlock->packData(&mStream); + + // Set the stream position back to zero. + mStream.setPosition(0); + + // Unpack the datablock stream. + pDataBlock->unpackData(&mStream); + + // Call the console function to set the number of blocks to be sent. + Con::executef("onDataBlockObjectReceived", Con::getIntArg(i), Con::getIntArg(iCount)); + + // Preload the datablock on the dummy client. + pDataBlock->preload(false, NetConnection::getErrorBuffer()); + } + + // Get the last datablock (if any)... + if (pDataBlock) + { + // Ensure the datablock modified key is set. + object->setDataBlockModifiedKey(object->getMaxDataBlockModifiedKey()); + + // Ensure that the client knows that the datablock send is done... + object->sendConnectionMessage(GameConnection::DataBlocksDone, object->getDataBlockSequence()); + } + } + else + { + // Otherwise, store the current datablock modified key. + const S32 iKey = object->getDataBlockModifiedKey(); + + // Iterate through the datablock group... + U32 i = 0; + for (; i < iCount; i++) + { + // If the datablock's modified key has already been set, break out of the loop... + if (((SimDataBlock*)(*pGroup)[i])->getModifiedKey() > iKey) + { + break; + } + } + + // If this is the last datablock in the group... + if (i == iCount) + { + // Ensure that the client knows that the datablock send is done... + object->sendConnectionMessage(GameConnection::DataBlocksDone, object->getDataBlockSequence()); + + // Then exit out since nothing else needs to be done. + return; + } + + // Set the maximum datablock modified key value. + object->setMaxDataBlockModifiedKey(iKey); + + // Get the minimum number of datablocks... + const U32 iMax = getMin(i + DataBlockQueueCount, iCount); + + // Iterate through the remaining datablocks... + for (;i < iMax; i++) + { + // Get a pointer to the datablock in question... + SimDataBlock* pDataBlock = (SimDataBlock*)(*pGroup)[i]; + + // Post the datablock event to the client. + object->postNetEvent(new SimDataBlockEvent(pDataBlock, i, iCount, object->getDataBlockSequence())); + } + } +} + +ConsoleMethod( GameConnection, activateGhosting, void, 2, 2, "") +{ + object->activateGhosting(); +} + +ConsoleMethod( GameConnection, resetGhosting, void, 2, 2, "") +{ + object->resetGhosting(); +} + +ConsoleMethod( GameConnection, setControlObject, bool, 3, 3, "(ShapeBase object)") +{ + GameBase *gb; + if(!Sim::findObject(argv[2], gb)) + return false; + + object->setControlObject(gb); + return true; +} + +ConsoleMethod( GameConnection, getControlObject, S32, 2, 2, "") +{ + TORQUE_UNUSED(argv); + SimObject* cp = object->getControlObject(); + return cp? cp->getId(): 0; +} + +ConsoleMethod( GameConnection, isAIControlled, bool, 2, 2, "") +{ + return object->isAIControlled(); +} + +ConsoleMethod( GameConnection, isControlObjectRotDampedCamera, bool, 2, 2, "") +{ + return object->isControlObjectRotDampedCamera(); +} + +ConsoleMethod( GameConnection, play2D, bool, 3, 3, "(SFXProfile ap)") +{ + SFXProfile *profile; + if(!Sim::findObject(argv[2], profile)) + return false; + object->play2D(profile); + return true; +} + +ConsoleMethod( GameConnection, play3D, bool, 4, 4, "(SFXProfile ap, Transform pos)") +{ + SFXProfile *profile; + if(!Sim::findObject(argv[2], profile)) + return false; + + Point3F pos(0,0,0); + AngAxisF aa; + aa.axis.set(0,0,1); + aa.angle = 0; + dSscanf(argv[3],"%g %g %g %g %g %g %g", + &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); + MatrixF mat; + aa.setMatrix(&mat); + mat.setColumn(3,pos); + + object->play3D(profile,&mat); + return true; +} + +ConsoleMethod( GameConnection, chaseCam, bool, 3, 3, "(int size)") +{ + S32 size = dAtoi(argv[2]); + if (size != sChaseQueueSize) + { + SAFE_DELETE_ARRAY(sChaseQueue); + + sChaseQueueSize = size; + sChaseQueueHead = sChaseQueueTail = 0; + + if (size) + { + sChaseQueue = new MatrixF[size]; + return true; + } + } + return false; +} + +ConsoleMethod( GameConnection, setControlCameraFov, void, 3, 3, "(int newFOV)" + "Set new FOV in degrees.") +{ + object->setControlCameraFov(dAtof(argv[2])); +} + +ConsoleMethod( GameConnection, getControlCameraFov, F32, 2, 2, "") +{ + F32 fov = 0.0f; + if(!object->getControlCameraFov(&fov)) + return(0.0f); + return(fov); +} + +ConsoleMethod( GameConnection, setBlackOut, void, 4, 4, "(bool doFade, int timeMS)") +{ + object->setBlackOut(dAtob(argv[2]), dAtoi(argv[3])); +} + +ConsoleMethod( GameConnection, setMissionCRC, void, 3, 3, "(int CRC)") +{ + if(object->isConnectionToServer()) + return; + + object->postNetEvent(new SetMissionCRCEvent(dAtoi(argv[2]))); +} + +ConsoleMethod( GameConnection, delete, void, 2, 3, "(string reason=NULL) Disconnect a client; reason is sent as part of the disconnect packet.") +{ + if (argc == 3) + object->setDisconnectReason(argv[2]); + object->deleteObject(); +} + + +//-------------------------------------------------------------------------- +void GameConnection::consoleInit() +{ + Con::addVariable("Pref::Net::LagThreshold", TypeS32, &mLagThresholdMS); + // Con::addVariable("specialFog", TypeBool, &SceneGraph::useSpecial); +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + Con::addVariable("$Pref::Server::DatablockCacheFilename", TypeString, &server_cache_filename); + Con::addVariable("$pref::Client::DatablockCacheFilename", TypeString, &client_cache_filename); + Con::addVariable("$Pref::Server::EnableDatablockCache", TypeBool, &server_cache_on); + Con::addVariable("$pref::Client::EnableDatablockCache", TypeBool, &client_cache_on); +#endif // AFX CODE BLOCK (db-cache) >> +} + +ConsoleMethod(GameConnection, startRecording, void, 3, 3, "(string fileName)records the network connection to a demo file.") +{ + char fileName[1024]; + Con::expandScriptFilename(fileName, sizeof(fileName), argv[2]); + object->startDemoRecord(fileName); +} + +ConsoleMethod(GameConnection, stopRecording, void, 2, 2, "()stops the demo recording.") +{ + object->stopRecording(); +} + +ConsoleMethod(GameConnection, playDemo, bool, 3, 3, "(string demoFileName)plays a previously recorded demo.") +{ + char filename[1024]; + Con::expandScriptFilename(filename, sizeof(filename), argv[2]); + + // Note that calling onConnectionEstablished will change the values in argv! + object->onConnectionEstablished(true); + object->setEstablished(); + + if(!object->replayDemoRecord(filename)) + { + Con::printf("Unable to open demo file %s.", filename); + object->deleteObject(); + return false; + } + + // After demo has loaded, execute the scene re-light the scene + //SceneLighting::lightScene(0, 0); + GameConnection::smPlayingDemo.trigger(); + + return true; +} + +ConsoleMethod(GameConnection, isDemoPlaying, bool, 2, 2, "isDemoPlaying();") +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + return object->isPlayingBack(); +} + +ConsoleMethod(GameConnection, isDemoRecording, bool, 2, 2, "()") +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + return object->isRecording(); +} + +ConsoleMethod( GameConnection, listClassIDs, void, 2, 2, "() List all of the " + "classes that this connection knows about, and what their IDs " + "are. Useful for debugging network problems.") +{ + Con::printf("--------------- Class ID Listing ----------------"); + Con::printf(" id | name"); + + for(AbstractClassRep *rep = AbstractClassRep::getClassList(); + rep; + rep = rep->getNextClass()) + { + ConsoleObject *obj = rep->create(); + if(obj && rep->getClassId(object->getNetClassGroup()) >= 0) + Con::printf("%7.7d| %s", rep->getClassId(object->getNetClassGroup()), rep->getClassName()); + delete obj; + } +} + +ConsoleStaticMethod(GameConnection, getServerConnection, S32, 1, 1, "() Get the server connection if any.") +{ + if(GameConnection::getConnectionToServer()) + return GameConnection::getConnectionToServer()->getId(); + else + { + Con::errorf("GameConnection::getServerConnection - no connection available."); + return -1; + } +} + +ConsoleMethod(GameConnection, setCameraObject, S32, 3, 3, "") +{ + NetObject *obj; + if(!Sim::findObject(argv[2], obj)) + return false; + + object->setCameraObject(dynamic_cast(obj)); + return true; +} + +ConsoleMethod(GameConnection, getCameraObject, S32, 2, 2, "") +{ + SimObject *obj = dynamic_cast(object->getCameraObject()); + return obj ? obj->getId() : 0; +} + +ConsoleMethod(GameConnection, clearCameraObject, void, 2, 2, "") +{ + object->setCameraObject(NULL); +} + +ConsoleMethod(GameConnection, isFirstPerson, bool, 2, 2, "() True if this connection is in first person mode.") +{ + // Note: Transition to first person occurs over time via mCameraPos, so this + // won't immediately return true after a set. + return object->isFirstPerson(); +} + +ConsoleMethod(GameConnection, setFirstPerson, void, 3, 3, "(bool firstPerson) Sets this connection into or out of first person mode.") +{ + object->setFirstPerson(dAtob(argv[2])); +} + +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + +void GameConnection::tempDisableStringBuffering(BitStream* bs) const +{ + //bs->setStringBuffer(0); +} + +void GameConnection::restoreStringBuffering(BitStream* bs) const +{ + //bs->setStringBuffer(curr_stringBuf); +} + +// rewind to stream postion and then move raw bytes into client_db_stream +// for caching purposes. +void GameConnection::repackClientDatablock(BitStream* bstream, S32 start_pos) +{ + static U8 bit_buffer[Net::MaxPacketDataSize]; + + if (!clientCacheEnabled() || !client_db_stream) + return; + + S32 cur_pos = bstream->getCurPos(); + S32 n_bits = cur_pos - start_pos; + if (n_bits <= 0) + return; + + bstream->setCurPos(start_pos); + bstream->readBits(n_bits, bit_buffer); + bstream->setCurPos(cur_pos); + + //S32 start_pos2 = client_db_stream->getCurPos(); + client_db_stream->writeBits(n_bits, bit_buffer); +} + +#define CLIENT_CACHE_VERSION_CODE 4724110 + +void GameConnection::saveDatablockCache(bool on_server) +{ + InfiniteBitStream bit_stream; + BitStream* bstream = 0; + + if (on_server) + { + SimDataBlockGroup *g = Sim::getDataBlockGroup(); + + // find the first one we haven't sent: + U32 i, groupCount = g->size(); + S32 key = this->getDataBlockModifiedKey(); + for (i = 0; i < groupCount; i++) + if (((SimDataBlock*)(*g)[i])->getModifiedKey() > key) + break; + + // nothing to save + if (i == groupCount) + return; + + bstream = &bit_stream; + + for (;i < groupCount; i++) + { + SimDataBlock* obj = (SimDataBlock*)(*g)[i]; + GameConnection* gc = this; + NetConnection* conn = this; + SimObjectId id = obj->getId(); + + if (bstream->writeFlag(gc->getDataBlockModifiedKey() < obj->getModifiedKey())) // A - flag + { + if (obj->getModifiedKey() > gc->getMaxDataBlockModifiedKey()) + gc->setMaxDataBlockModifiedKey(obj->getModifiedKey()); + + bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize); // B - int + + S32 classId = obj->getClassId(conn->getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeDataBlock, conn->getNetClassGroup()); // C - id + bstream->writeInt(i, DataBlockObjectIdBitSize); // D - int + bstream->writeInt(groupCount, DataBlockObjectIdBitSize + 1); // E - int + obj->packData(bstream); + } + } + } + else + { + bstream = client_db_stream; + } + + if (bstream->getPosition() <= 0) + return; + + // zero out any leftover bits short of an even byte count + U32 n_leftover_bits = (bstream->getPosition()*8) - bstream->getCurPos(); + if (n_leftover_bits >= 0 && n_leftover_bits <= 8) + { + // note - an unusual problem regarding setCurPos() results when there + // are no leftover bytes. Adding a buffer byte in this case avoids the problem. + if (n_leftover_bits == 0) + n_leftover_bits = 8; + U8 bzero = 0; + bstream->writeBits(n_leftover_bits, &bzero); + } + + // this is where we actually save the file + const char* filename = (on_server) ? server_cache_filename : client_cache_filename; + if (filename && filename[0] != '\0') + { + FileStream f_stream; + if(!f_stream.open( filename, Torque::FS::File::Write )) + //if(!ResourceManager::get().openFileForWrite(f_stream,)) + { + Con::printf("Failed to open file '%s'.", filename); + return; + } + + U32 save_sz = bstream->getPosition(); + + if (!on_server) + { + f_stream.write((U32)CLIENT_CACHE_VERSION_CODE); + f_stream.write(save_sz); + f_stream.write(server_cache_CRC); + f_stream.write((U32)CLIENT_CACHE_VERSION_CODE); + } + + f_stream.write(save_sz, bstream->getBuffer()); + + // zero out any leftover bytes short of a 4-byte multiple + while ((save_sz % 4) != 0) + { + f_stream.write((U8)0); + save_sz++; + } + + f_stream.close(); + } + + if (!on_server) + client_db_stream->clear(); +} + +static bool afx_saved_db_cache = false; +static U32 afx_saved_db_cache_CRC = 0xffffffff; + +void GameConnection::resetDatablockCache() +{ + afx_saved_db_cache = false; + afx_saved_db_cache_CRC = 0xffffffff; +} + +ConsoleFunction(resetDatablockCache, void, 1, 1, "resetDatablockCache()") +{ + GameConnection::resetDatablockCache(); +} + +ConsoleFunction(isDatablockCacheSaved, bool, 1, 1, "resetDatablockCache()") +{ + return afx_saved_db_cache; +} + +ConsoleFunction(getDatablockCacheCRC, S32, 1, 1, "getDatablockCacheCRC()") +{ + return (S32)afx_saved_db_cache_CRC; +} + +ConsoleFunction(extractDatablockCacheCRC, S32, 2, 2, "extractDatablockCacheCRC(filename)") +{ + FileStream f_stream; + if(!f_stream.open(argv[1], Torque::FS::File::Read)) + { + Con::errorf("Failed to open file '%s'.", argv[1]); + return -1; + } + + U32 stream_sz = f_stream.getStreamSize(); + if (stream_sz < 4*32) + { + Con::errorf("File '%s' is not a valid datablock cache.", argv[1]); + f_stream.close(); + return -1; + } + + U32 pre_code; f_stream.read(&pre_code); + U32 save_sz; f_stream.read(&save_sz); + U32 crc_code; f_stream.read(&crc_code); + U32 post_code; f_stream.read(&post_code); + + f_stream.close(); + + if (pre_code != post_code) + { + Con::errorf("File '%s' is not a valid datablock cache.", argv[1]); + return -1; + } + + if (pre_code != (U32)CLIENT_CACHE_VERSION_CODE) + { + Con::errorf("Version of datablock cache file '%s' does not match version of running software.", argv[1]); + return -1; + } + + return (S32)crc_code; +} + +ConsoleFunction(setDatablockCacheCRC, void, 2, 2, "setDatablockCacheCRC(crc)") +{ + GameConnection *conn = GameConnection::getConnectionToServer(); + if(!conn) + return; + + U32 crc_u = (U32)dAtoi(argv[1]); + conn->setServerCacheCRC(crc_u); +} + +ConsoleMethod( GameConnection, saveDatablockCache, void, 2, 2, "saveDatablockCache()") +{ + if (GameConnection::serverCacheEnabled() && !afx_saved_db_cache) + { + // Save the datablocks to a cache file. An argument + // of true indicates that this is a server save. + object->saveDatablockCache(true); + afx_saved_db_cache = true; + afx_saved_db_cache_CRC = 0xffffffff; + + const char* filename = object->serverCacheFilename(); + if (filename && filename[0] != '\0') + { + U32 crcVal; + FileStream f_stream; + if(f_stream.open(filename, Torque::FS::File::Read)) + { + afx_saved_db_cache_CRC = CRC::calculateCRCStream(&f_stream,crcVal); + } + else + Con::errorf("saveDatablockCache() failed to get CRC for file '%s'.", filename);\ + } + } +} + +ConsoleMethod( GameConnection, loadDatablockCache, void, 2, 2, "loadDatablockCache()") +{ + if (GameConnection::clientCacheEnabled()) + { + object->loadDatablockCache(); + } +} + +ConsoleMethod( GameConnection, loadDatablockCache_Begin, bool, 2, 2, "loadDatablockCache_Begin()") +{ + if (GameConnection::clientCacheEnabled()) + { + return object->loadDatablockCache_Begin(); + } + + return false; +} + +ConsoleMethod( GameConnection, loadDatablockCache_Continue, bool, 2, 2, "loadDatablockCache_Continue()") +{ + if (GameConnection::clientCacheEnabled()) + { + return object->loadDatablockCache_Continue(); + } + + return false; +} + +static char* afx_db_load_buf = 0; +static U32 afx_db_load_buf_sz = 0; +static BitStream* afx_db_load_bstream = 0; + +void GameConnection::loadDatablockCache() +{ + if (!loadDatablockCache_Begin()) + return; + + while (loadDatablockCache_Continue()) + ; +} + +bool GameConnection::loadDatablockCache_Begin() +{ + if (!client_cache_filename || client_cache_filename[0] == '\0') + { + Con::errorf("No filename was specified for the client datablock cache."); + return false; + } + + // open cache file + FileStream f_stream; + if(!f_stream.open(client_cache_filename, Torque::FS::File::Read)) + { + Con::errorf("Failed to open file '%s'.", client_cache_filename); + return false; + } + + // get file size + U32 stream_sz = f_stream.getStreamSize(); + if (stream_sz <= 4*4) + { + Con::errorf("File '%s' is too small to be a valid datablock cache.", client_cache_filename); + f_stream.close(); + return false; + } + + // load header data + U32 pre_code; f_stream.read(&pre_code); + U32 save_sz; f_stream.read(&save_sz); + U32 crc_code; f_stream.read(&crc_code); + U32 post_code; f_stream.read(&post_code); + + // validate header info + if (pre_code != post_code) + { + Con::errorf("File '%s' is not a valid datablock cache.", client_cache_filename); + f_stream.close(); + return false; + } + if (pre_code != (U32)CLIENT_CACHE_VERSION_CODE) + { + Con::errorf("Version of datablock cache file '%s' does not match version of running software.", client_cache_filename); + f_stream.close(); + return false; + } + + // allocated the in-memory buffer + afx_db_load_buf_sz = stream_sz - (4*4); + afx_db_load_buf = new char[afx_db_load_buf_sz]; + + // load data from file into memory + if (!f_stream.read(stream_sz, afx_db_load_buf)) + { + Con::errorf("Failed to read data from file '%s'.", client_cache_filename); + f_stream.close(); + delete [] afx_db_load_buf; + afx_db_load_buf = 0; + afx_db_load_buf_sz = 0; + return false; + } + + // close file + f_stream.close(); + + // At this point we have the whole cache in memory + + // create a bitstream from the in-memory buffer + afx_db_load_bstream = new BitStream(afx_db_load_buf, afx_db_load_buf_sz); + + return true; +} + +bool GameConnection::loadDatablockCache_Continue() +{ + if (!afx_db_load_bstream) + return false; + + // prevent repacking of datablocks during load + BitStream* save_client_db_stream = client_db_stream; + client_db_stream = 0; + + bool all_finished = false; + + // loop through at most 16 datablocks + BitStream *bstream = afx_db_load_bstream; + for (S32 i = 0; i < 16; i++) + { + S32 save_pos = bstream->getCurPos(); + if (!bstream->readFlag()) + { + all_finished = true; + break; + } + bstream->setCurPos(save_pos); + SimDataBlockEvent evt; + evt.unpack(this, bstream); + evt.process(this); + } + + client_db_stream = save_client_db_stream; + + if (all_finished) + { + delete afx_db_load_bstream; + afx_db_load_bstream = 0; + delete [] afx_db_load_buf; + afx_db_load_buf = 0; + afx_db_load_buf_sz = 0; + return false; + } + + return true; +} + +#endif // AFX CODE BLOCK (db-cache) >> + +ConsoleMethod(GameConnection, setSelectedObj, bool, 3, 4, "(object, [propagate_to_client])") +{ + SceneObject* pending_selection; + if (!Sim::findObject(argv[2], pending_selection)) + return false; + + bool propagate_to_client = (argc > 3) ? dAtob(argv[3]) : false; + object->setSelectedObj(pending_selection, propagate_to_client); + + return true; +} + +ConsoleMethod(GameConnection, getSelectedObj, S32, 2, 2, "()") +{ + SimObject* selected = object->getSelectedObj(); + return (selected) ? selected->getId(): -1; +} + +ConsoleMethod(GameConnection, clearSelectedObj, void, 2, 3, "([propagate_to_client])") +{ + bool propagate_to_client = (argc > 2) ? dAtob(argv[2]) : false; + object->setSelectedObj(NULL, propagate_to_client); +} + +ConsoleMethod(GameConnection, setPreSelectedObjFromRollover, void, 2, 2, "()") +{ + object->setPreSelectedObjFromRollover(); +} + +ConsoleMethod(GameConnection, clearPreSelectedObj, void, 2, 2, "()") +{ + object->clearPreSelectedObj(); +} + +ConsoleMethod(GameConnection, setSelectedObjFromPreSelected, void, 2, 2, "()") +{ + object->setSelectedObjFromPreSelected(); +} + + +void GameConnection::setSelectedObj(SceneObject* so, bool propagate_to_client) +{ + if (!isConnectionToServer()) + { + return; + } + + // clear previously selected object + if (mSelectedObj) + { + mSelectedObj->setSelectionFlags(mSelectedObj->getSelectionFlags() & ~SceneObject::SELECTED); + clearNotify(mSelectedObj); + Con::executef(this, "onObjectDeselected", mSelectedObj->scriptThis()); + } + + // save new selection + mSelectedObj = so; + + // mark selected object + if (mSelectedObj) + { + mSelectedObj->setSelectionFlags(mSelectedObj->getSelectionFlags() | SceneObject::SELECTED); + deleteNotify(mSelectedObj); + } + + // mark selection dirty + //mChangedSelectedObj = true; + + // notify appropriate script of the change + if (mSelectedObj) + Con::executef(this, "onObjectSelected", mSelectedObj->scriptThis()); +} + +void GameConnection::setRolloverObj(SceneObject* so) +{ + // save new selection + mRolloverObj = so; + + // notify appropriate script of the change + Con::executef(this, "onObjectRollover", (mRolloverObj) ? mRolloverObj->scriptThis() : ""); +} + +void GameConnection::setPreSelectedObjFromRollover() +{ + mPreSelectedObj = mRolloverObj; + mPreSelectTimestamp = Platform::getRealMilliseconds(); +} + +void GameConnection::clearPreSelectedObj() +{ + mPreSelectedObj = 0; + mPreSelectTimestamp = 0; +} + +void GameConnection::setSelectedObjFromPreSelected() +{ + U32 now = Platform::getRealMilliseconds(); + if (now - mPreSelectTimestamp < 1000) + setSelectedObj(mPreSelectedObj); + mPreSelectedObj = 0; +} + +void GameConnection::onDeleteNotify(SimObject* obj) +{ + if (obj == mSelectedObj) + setSelectedObj(NULL); + + Parent::onDeleteNotify(obj); +} \ No newline at end of file diff --git a/T3D/gameConnection.h b/T3D/gameConnection.h new file mode 100644 index 0000000..900f108 --- /dev/null +++ b/T3D/gameConnection.h @@ -0,0 +1,354 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMECONNECTION_H_ +#define _GAMECONNECTION_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _NETCONNECTION_H_ +#include "sim/netConnection.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "T3D/moveManager.h" +#endif +#ifndef _BITVECTOR_H_ +#include "core/bitVector.h" +#endif +#ifndef _MOVELIST_H_ +#include "T3D/moveList.h" +#endif + +enum GameConnectionConstants +{ + MaxClients = 126, + DataBlockQueueCount = 16 +}; + +class SFXProfile; +class MatrixF; +class MatrixF; +class Point3F; +class MoveManager; +struct Move; +struct AuthInfo; + +#define GameString TORQUE_APP_NAME +// AFX CODE BLOCK (db-cache) << +// +// To disable datablock caching, remove or comment out the AFX_CAP_DATABLOCK_CACHE define below. +// Also, at a minimum, the following script preferences should be set to false: +// $pref::Client::EnableDatablockCache = false; (in arcane.fx/client/defaults.cs) +// $Pref::Server::EnableDatablockCache = false; (in arcane.fx/server/defaults.cs) +// Alternatively, all script code marked with "DATABLOCK CACHE CODE" can be removed or +// commented out. +// +#define AFX_CAP_DATABLOCK_CACHE +// AFX CODE BLOCK (db-cache) >> +const F32 MinCameraFov = 1.f; ///< min camera FOV +const F32 MaxCameraFov = 179.f; ///< max camera FOV + +class GameConnection : public NetConnection +{ +private: + typedef NetConnection Parent; + + SimObjectPtr mControlObject; + SimObjectPtr mCameraObject; + U32 mDataBlockSequence; + char mDisconnectReason[256]; + + U32 mMissionCRC; // crc of the current mission file from the server + +private: + U32 mLastControlRequestTime; + S32 mDataBlockModifiedKey; + S32 mMaxDataBlockModifiedKey; + + /// @name Client side first/third person + /// @{ + + /// + bool mFirstPerson; ///< Are we currently first person or not. + bool mUpdateFirstPerson; ///< Set to notify client or server of first person change. + bool mUpdateCameraFov; ///< Set to notify server of camera FOV change. + F32 mCameraFov; ///< Current camera fov (in degrees). + F32 mCameraPos; ///< Current camera pos (0-1). + F32 mCameraSpeed; ///< Camera in/out speed. + /// @} + +public: + + /// @name Protocol Versions + /// + /// Protocol versions are used to indicated changes in network traffic. + /// These could be changes in how any object transmits or processes + /// network information. You can specify backwards compatibility by + /// specifying a MinRequireProtocolVersion. If the client + /// protocol is >= this min value, the connection is accepted. + /// + /// Torque (V12) SDK 1.0 uses protocol = 1 + /// + /// Torque SDK 1.1 uses protocol = 2 + /// Torque SDK 1.4 uses protocol = 12 + /// @{ + static const U32 CurrentProtocolVersion; + static const U32 MinRequiredProtocolVersion; + /// @} + + /// Configuration + enum Constants { + BlockTypeMove = NetConnectionBlockTypeCount, + GameConnectionBlockTypeCount, + MaxConnectArgs = 16, + DataBlocksDone = NumConnectionMessages, + DataBlocksDownloadDone, + }; + + /// Set connection arguments; these are passed to the server when we connect. + void setConnectArgs(U32 argc, const char **argv); + + /// Set the server password to use when we join. + void setJoinPassword(const char *password); + + /// @name Event Handling + /// @{ + + virtual void onTimedOut(); + virtual void onConnectTimedOut(); + virtual void onDisconnect(const char *reason); + virtual void onConnectionRejected(const char *reason); + virtual void onConnectionEstablished(bool isInitiator); + virtual void handleStartupError(const char *errorString); + /// @} + + /// @name Packet I/O + /// @{ + + virtual void writeConnectRequest(BitStream *stream); + virtual bool readConnectRequest(BitStream *stream, const char **errorString); + virtual void writeConnectAccept(BitStream *stream); + virtual bool readConnectAccept(BitStream *stream, const char **errorString); + /// @} + + bool canRemoteCreate(); + +private: + /// @name Connection State + /// This data is set with setConnectArgs() and setJoinPassword(), and + /// sent across the wire when we connect. + /// @{ + + U32 mConnectArgc; + char *mConnectArgv[MaxConnectArgs]; + char *mJoinPassword; + /// @} + +protected: + struct GamePacketNotify : public NetConnection::PacketNotify + { + S32 cameraFov; + GamePacketNotify(); + }; + PacketNotify *allocNotify(); + + bool mControlForceMismatch; + + Vector mDataBlockLoadList; + +public: + MoveList mMoveList; +protected: + bool mAIControlled; + AuthInfo * mAuthInfo; + + static S32 mLagThresholdMS; + S32 mLastPacketTime; + bool mLagging; + + /// @name Flashing + //// + /// Note, these variables are not networked, they are for the local connection only. + /// @{ + F32 mDamageFlash; + F32 mWhiteOut; + + F32 mBlackOut; + S32 mBlackOutTimeMS; + S32 mBlackOutStartTimeMS; + bool mFadeToBlack; + + /// @} + + /// @name Packet I/O + /// @{ + + void readPacket (BitStream *bstream); + void writePacket (BitStream *bstream, PacketNotify *note); + void packetReceived (PacketNotify *note); + void packetDropped (PacketNotify *note); + void connectionError (const char *errorString); + + void writeDemoStartBlock (ResizeBitStream *stream); + bool readDemoStartBlock (BitStream *stream); + void handleRecordedBlock (U32 type, U32 size, void *data); + /// @} + void ghostWriteExtra(NetObject *,BitStream *); + void ghostReadExtra(NetObject *,BitStream *, bool newGhost); + void ghostPreRead(NetObject *, bool newGhost); + +public: + + DECLARE_CONOBJECT(GameConnection); + void handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount); + void preloadDataBlock(SimDataBlock *block); + void fileDownloadSegmentComplete(); + void preloadNextDataBlock(bool hadNew); + static void consoleInit(); + + void setDisconnectReason(const char *reason); + GameConnection(); + ~GameConnection(); + + U32 getDataBlockSequence() { return mDataBlockSequence; } + void setDataBlockSequence(U32 seq) { mDataBlockSequence = seq; } + + bool onAdd(); + void onRemove(); + + static GameConnection *getConnectionToServer() + { + return dynamic_cast((NetConnection *) mServerConnection); + } + + static GameConnection *getLocalClientConnection() + { + return dynamic_cast((NetConnection *) mLocalClientConnection); + } + + /// @name Control object + /// @{ + + /// + void setControlObject(GameBase *); + GameBase* getControlObject() { return mControlObject; } + + void setCameraObject(GameBase *); + GameBase* getCameraObject(); + + bool getControlCameraTransform(F32 dt,MatrixF* mat); + bool getControlCameraVelocity(Point3F *vel); + + bool getControlCameraFov(F32 *fov); + bool setControlCameraFov(F32 fov); + bool isValidControlCameraFov(F32 fov); + + // Used by editor + bool isControlObjectRotDampedCamera(); + + void setFirstPerson(bool firstPerson); + + /// @} + + void detectLag(); + + /// @name Datablock management + /// @{ + + S32 getDataBlockModifiedKey () { return mDataBlockModifiedKey; } + void setDataBlockModifiedKey (S32 key) { mDataBlockModifiedKey = key; } + S32 getMaxDataBlockModifiedKey () { return mMaxDataBlockModifiedKey; } + void setMaxDataBlockModifiedKey (S32 key) { mMaxDataBlockModifiedKey = key; } + /// @} + + /// @name Fade control + /// @{ + + F32 getDamageFlash() { return mDamageFlash; } + F32 getWhiteOut() { return mWhiteOut; } + + void setBlackOut(bool fadeToBlack, S32 timeMS); + F32 getBlackOut(); + /// @} + + /// @name Authentication + /// + /// This is remnant code from Tribes 2. + /// @{ + + void setAuthInfo(const AuthInfo *info); + const AuthInfo *getAuthInfo(); + /// @} + + /// @name Sound + /// @{ + + void play2D(SFXProfile *profile); + void play3D(SFXProfile *profile, const MatrixF *transform); + /// @} + + /// @name Misc. + /// @{ + + bool isFirstPerson() { return mCameraPos == 0; } + bool isAIControlled() { return mAIControlled; } + + void doneScopingScene(); + void demoPlaybackComplete(); + + void setMissionCRC(U32 crc) { mMissionCRC = crc; } + U32 getMissionCRC() { return(mMissionCRC); } + /// @} + + static Signal smFovUpdate; + static Signal smPlayingDemo; +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + private: + static StringTableEntry server_cache_filename; + static StringTableEntry client_cache_filename; + static bool server_cache_on; + static bool client_cache_on; + BitStream* client_db_stream; + //char* curr_stringBuf; + U32 server_cache_CRC; + public: + void repackClientDatablock(BitStream*, S32 start_pos); + void saveDatablockCache(bool on_server); + void loadDatablockCache(); + bool loadDatablockCache_Begin(); + bool loadDatablockCache_Continue(); + void tempDisableStringBuffering(BitStream* bs) const; + void restoreStringBuffering(BitStream* bs) const; + void setServerCacheCRC(U32 crc) { server_cache_CRC = crc; } + + static void resetDatablockCache(); + static bool serverCacheEnabled() { return server_cache_on; } + static bool clientCacheEnabled() { return client_cache_on; } + static const char* serverCacheFilename() { return server_cache_filename; } + static const char* clientCacheFilename() { return client_cache_filename; } +#endif // AFX CODE BLOCK (db-cache) >> + private: + SimObjectPtr mRolloverObj; + SimObjectPtr mPreSelectedObj; + SimObjectPtr mSelectedObj; + bool mChangedSelectedObj; + U32 mPreSelectTimestamp; + protected: + virtual void onDeleteNotify(SimObject*); + public: + void setRolloverObj(SceneObject*); + SceneObject* getRolloverObj() { return mRolloverObj; } + void setSelectedObj(SceneObject*, bool propagate_to_client=false); + SceneObject* getSelectedObj() { return mSelectedObj; } + void setPreSelectedObjFromRollover(); + void clearPreSelectedObj(); + void setSelectedObjFromPreSelected(); +}; + +#endif diff --git a/T3D/gameConnectionEvents.cpp b/T3D/gameConnectionEvents.cpp new file mode 100644 index 0000000..94f174b --- /dev/null +++ b/T3D/gameConnectionEvents.cpp @@ -0,0 +1,334 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "console/simBase.h" +#include "sceneGraph/pathManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sfx/sfxSystem.h" +#include "app/game.h" +#include "T3D/gameConnection.h" +#include "T3D/gameConnectionEvents.h" + +#define DebugChecksum 0xF00DBAAD + +//-------------------------------------------------------------------------- +IMPLEMENT_CO_CLIENTEVENT_V1(SimDataBlockEvent); +IMPLEMENT_CO_CLIENTEVENT_V1(Sim2DAudioEvent); +IMPLEMENT_CO_CLIENTEVENT_V1(Sim3DAudioEvent); +IMPLEMENT_CO_CLIENTEVENT_V1(SetMissionCRCEvent); + + +//---------------------------------------------------------------------------- + +SimDataBlockEvent::~SimDataBlockEvent() +{ + delete mObj; +} + +SimDataBlockEvent::SimDataBlockEvent(SimDataBlock* obj, U32 index, U32 total, U32 missionSequence) +{ + mObj = NULL; + mIndex = index; + mTotal = total; + mMissionSequence = missionSequence; + mProcess = false; + + if(obj) + { + id = obj->getId(); + AssertFatal(id >= DataBlockObjectIdFirst && id <= DataBlockObjectIdLast, + "Out of range event data block id... check simBase.h"); +// Con::printf("queuing data block: %d", mIndex); + } +} + +#ifdef TORQUE_DEBUG_NET +const char *SimDataBlockEvent::getDebugName() +{ + SimObject *obj = Sim::findObject(id); + static char buffer[256]; + dSprintf(buffer, sizeof(buffer), "%s [%s - %s]", + getClassName(), + obj ? obj->getName() : "", + obj ? obj->getClassName() : "NONE"); + return buffer; +} +#endif // TORQUE_DEBUG_NET + +void SimDataBlockEvent::notifyDelivered(NetConnection *conn, bool ) +{ + // if the modified key for this event is not the current one, + // we've already resorted and resent some blocks, so fall out. + if(conn->isRemoved()) + return; + GameConnection *gc = (GameConnection *) conn; + if(gc->getDataBlockSequence() != mMissionSequence) + return; + + U32 nextIndex = mIndex + DataBlockQueueCount; + SimDataBlockGroup *g = Sim::getDataBlockGroup(); + + if(mIndex == g->size() - 1) + { + gc->setDataBlockModifiedKey(gc->getMaxDataBlockModifiedKey()); + gc->sendConnectionMessage(GameConnection::DataBlocksDone, mMissionSequence); + } + + if(g->size() <= nextIndex) + return; + + SimDataBlock *blk = (SimDataBlock *) (*g)[nextIndex]; + gc->postNetEvent(new SimDataBlockEvent(blk, nextIndex, g->size(), mMissionSequence)); +} + +void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream) +{ +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + ((GameConnection *)conn)->tempDisableStringBuffering(bstream); +#endif // AFX CODE BLOCK (db-cache) >> + SimDataBlock* obj; + Sim::findObject(id,obj); + GameConnection *gc = (GameConnection *) conn; + if(bstream->writeFlag(gc->getDataBlockModifiedKey() < obj->getModifiedKey())) + { + if(obj->getModifiedKey() > gc->getMaxDataBlockModifiedKey()) + gc->setMaxDataBlockModifiedKey(obj->getModifiedKey()); + + AssertFatal(obj, + "SimDataBlockEvent:: Data blocks cannot be deleted"); + bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize); + + S32 classId = obj->getClassId(conn->getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeDataBlock, conn->getNetClassGroup()); + bstream->writeInt(mIndex, DataBlockObjectIdBitSize); + bstream->writeInt(mTotal, DataBlockObjectIdBitSize + 1); + obj->packData(bstream); +#ifdef TORQUE_DEBUG_NET + bstream->writeInt(classId ^ DebugChecksum, 32); +#endif + } +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + ((GameConnection *)conn)->restoreStringBuffering(bstream); +#endif // AFX CODE BLOCK (db-cache) >> +} + +void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream) +{ +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + // stash the stream position prior to unpacking + S32 start_pos = bstream->getCurPos(); + ((GameConnection *)cptr)->tempDisableStringBuffering(bstream); +#endif // AFX CODE BLOCK (db-cache) >> + if(bstream->readFlag()) + { + mProcess = true; + id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst; + S32 classId = bstream->readClassId(NetClassTypeDataBlock, cptr->getNetClassGroup()); + mIndex = bstream->readInt(DataBlockObjectIdBitSize); + mTotal = bstream->readInt(DataBlockObjectIdBitSize + 1); + + SimObject* ptr = (SimObject *) ConsoleObject::create(cptr->getNetClassGroup(), NetClassTypeDataBlock, classId); + if ((mObj = dynamic_cast(ptr)) != 0) + { + //Con::printf(" - SimDataBlockEvent: unpacking event of type: %s", mObj->getClassName()); + mObj->unpackData(bstream); + } + else + { + //Con::printf(" - SimDataBlockEvent: INVALID PACKET! Could not create class with classID: %d", classId); + delete ptr; + cptr->setLastError("Invalid packet in SimDataBlockEvent::unpack()"); + } + +#ifdef TORQUE_DEBUG_NET + U32 checksum = bstream->readInt(32); + AssertISV( (checksum ^ DebugChecksum) == (U32)classId, + avar("unpack did not match pack for event of class %s.", + mObj->getClassName()) ); +#endif + + } +#ifdef AFX_CAP_DATABLOCK_CACHE // AFX CODE BLOCK (db-cache) << + // rewind to stream position and then process raw bytes for caching + ((GameConnection *)cptr)->repackClientDatablock(bstream, start_pos); + ((GameConnection *)cptr)->restoreStringBuffering(bstream); +#endif // AFX CODE BLOCK (db-cache) >> +} + +void SimDataBlockEvent::write(NetConnection *cptr, BitStream *bstream) +{ + if(bstream->writeFlag(mProcess)) + { + bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize); + S32 classId = mObj->getClassId(cptr->getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeDataBlock, cptr->getNetClassGroup()); + bstream->writeInt(mIndex, DataBlockObjectIdBitSize); + bstream->writeInt(mTotal, DataBlockObjectIdBitSize + 1); + mObj->packData(bstream); + } +} + +void SimDataBlockEvent::process(NetConnection *cptr) +{ + if(mProcess) + { + //call the console function to set the number of blocks to be sent + Con::executef("onDataBlockObjectReceived", Con::getIntArg(mIndex), Con::getIntArg(mTotal)); + + SimDataBlock* obj = NULL; + String &errorBuffer = NetConnection::getErrorBuffer(); + + if( Sim::findObject( id,obj ) && dStrcmp( obj->getClassName(),mObj->getClassName() ) == 0 ) + { + U8 buf[1500]; + BitStream stream(buf, 1500); + mObj->packData(&stream); + stream.setPosition(0); + obj->unpackData(&stream); + obj->preload(false, errorBuffer); + } + else + { + if( obj != NULL ) + { + Con::warnf( "A '%s' datablock with id: %d already existed. " + "Clobbering it with new '%s' datablock from server.", + obj->getClassName(), id, mObj->getClassName() ); + obj->deleteObject(); + } + + bool ret = mObj->registerObject(id); + + if(ret) + { + cptr->addObject(mObj); + + GameConnection *conn = dynamic_cast(cptr); + if(conn) + { + conn->preloadDataBlock(mObj); + mObj = NULL; + } + } + } + } +} + + +//---------------------------------------------------------------------------- + + +Sim2DAudioEvent::Sim2DAudioEvent(SFXProfile *profile) +{ + mProfile = profile; +} + +void Sim2DAudioEvent::pack(NetConnection *, BitStream *bstream) +{ + bstream->writeInt( mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize); +} + +void Sim2DAudioEvent::write(NetConnection *, BitStream *bstream) +{ + bstream->writeInt( mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize); +} + +void Sim2DAudioEvent::unpack(NetConnection *, BitStream *bstream) +{ + SimObjectId id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst; + Sim::findObject(id, mProfile); +} + +void Sim2DAudioEvent::process(NetConnection *) +{ + if (mProfile) + SFX->playOnce( mProfile ); +} + +//---------------------------------------------------------------------------- + +static F32 SoundPosAccuracy = 0.5; +static S32 SoundRotBits = 8; + +Sim3DAudioEvent::Sim3DAudioEvent(SFXProfile *profile,const MatrixF* mat) +{ + mProfile = profile; + if (mat) + mTransform = *mat; +} + +void Sim3DAudioEvent::pack(NetConnection *con, BitStream *bstream) +{ + bstream->writeInt(mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize); + + // If the sound has cone parameters, the orientation is + // transmitted as well. + SFXDescription* ad = mProfile->getDescription(); + if ( bstream->writeFlag( ad->mConeInsideAngle || ad->mConeOutsideAngle ) ) + { + QuatF q(mTransform); + q.normalize(); + + // LH - we can get a valid quat that's very slightly over 1 in and so + // this fails (barely) check against zero. So use some error- + AssertFatal((1.0 - ((q.x * q.x) + (q.y * q.y) + (q.z * q.z))) >= (0.0 - 0.001), + "QuatF::normalize() is broken in Sim3DAudioEvent"); + + bstream->writeFloat(q.x,SoundRotBits); + bstream->writeFloat(q.y,SoundRotBits); + bstream->writeFloat(q.z,SoundRotBits); + bstream->writeFlag(q.w < 0.0); + } + + Point3F pos; + mTransform.getColumn(3,&pos); + bstream->writeCompressedPoint(pos,SoundPosAccuracy); +} + +void Sim3DAudioEvent::write(NetConnection *con, BitStream *bstream) +{ + // Just do the normal pack... + pack(con,bstream); +} + +void Sim3DAudioEvent::unpack(NetConnection *con, BitStream *bstream) +{ + SimObjectId id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst; + Sim::findObject(id, mProfile); + + if (bstream->readFlag()) { + QuatF q; + q.x = bstream->readFloat(SoundRotBits); + q.y = bstream->readFloat(SoundRotBits); + q.z = bstream->readFloat(SoundRotBits); + F32 value = ((q.x * q.x) + (q.y * q.y) + (q.z * q.z)); +// #ifdef __linux + // Hmm, this should never happen, but it does... + if ( value > 1.f ) + value = 1.f; +// #endif + q.w = mSqrt(1.f - value); + if (bstream->readFlag()) + q.w = -q.w; + q.setMatrix(&mTransform); + } + else + mTransform.identity(); + + Point3F pos; + bstream->readCompressedPoint(&pos,SoundPosAccuracy); + mTransform.setColumn(3, pos); +} + +void Sim3DAudioEvent::process(NetConnection *) +{ + if (mProfile) + SFX->playOnce( mProfile, &mTransform ); +} + diff --git a/T3D/gameConnectionEvents.h b/T3D/gameConnectionEvents.h new file mode 100644 index 0000000..470866c --- /dev/null +++ b/T3D/gameConnectionEvents.h @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMECONNECTIONEVENTS_H_ +#define _GAMECONNECTIONEVENTS_H_ + +#include "console/simBase.h" +#include "T3D/gameConnection.h" +#include "sfx/sfxProfile.h" + +class QuitEvent : public SimEvent +{ + void process(SimObject *object) + { + Platform::postQuitMessage(0); + } +}; + +class SimDataBlockEvent : public NetEvent +{ + SimObjectId id; + SimDataBlock *mObj; + U32 mIndex; + U32 mTotal; + U32 mMissionSequence; + bool mProcess; + public: + ~SimDataBlockEvent(); + SimDataBlockEvent(SimDataBlock* obj = NULL, U32 index = 0, U32 total = 0, U32 missionSequence = 0); + void pack(NetConnection *, BitStream *bstream); + void write(NetConnection *, BitStream *bstream); + void unpack(NetConnection *cptr, BitStream *bstream); + void process(NetConnection*); + void notifyDelivered(NetConnection *, bool); +#ifdef TORQUE_DEBUG_NET + const char *getDebugName(); +#endif + DECLARE_CONOBJECT(SimDataBlockEvent); +}; + +class Sim2DAudioEvent: public NetEvent +{ + private: + SFXProfile *mProfile; + + public: + Sim2DAudioEvent(SFXProfile *profile=NULL); + void pack(NetConnection *, BitStream *bstream); + void write(NetConnection *, BitStream *bstream); + void unpack(NetConnection *, BitStream *bstream); + void process(NetConnection *); + DECLARE_CONOBJECT(Sim2DAudioEvent); +}; + +class Sim3DAudioEvent: public NetEvent +{ + private: + SFXProfile *mProfile; + MatrixF mTransform; + + public: + Sim3DAudioEvent(SFXProfile *profile=NULL,const MatrixF* mat=NULL); + void pack(NetConnection *, BitStream *bstream); + void write(NetConnection *, BitStream *bstream); + void unpack(NetConnection *, BitStream *bstream); + void process(NetConnection *); + DECLARE_CONOBJECT(Sim3DAudioEvent); +}; + + +//---------------------------------------------------------------------------- +// used to set the crc for the current mission (mission lighting) +//---------------------------------------------------------------------------- +class SetMissionCRCEvent : public NetEvent +{ + private: + U32 mCrc; + + public: + SetMissionCRCEvent(U32 crc = 0xffffffff) + { mCrc = crc; } + void pack(NetConnection *, BitStream * bstream) + { bstream->write(mCrc); } + void write(NetConnection * con, BitStream * bstream) + { pack(con, bstream); } + void unpack(NetConnection *, BitStream * bstream) + { bstream->read(&mCrc); } + void process(NetConnection * con) + { static_cast(con)->setMissionCRC(mCrc); } + + DECLARE_CONOBJECT(SetMissionCRCEvent); +}; + +#endif diff --git a/T3D/gameFunctions.cpp b/T3D/gameFunctions.cpp new file mode 100644 index 0000000..7393cd8 --- /dev/null +++ b/T3D/gameFunctions.cpp @@ -0,0 +1,431 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/gameFunctions.h" +#ifdef TORQUE_TGB_ONLY +#include "T2D/networking/t2dMoveManager.h" +#include "T2D/model/ModelNodeFactoryCache2D.h" +#include "T2D/model/ModelControllerFactoryCache2D.h" +#endif +#include "T3D/gameConnection.h" +#include "T3D/camera.h" +#include "T3D/decal/decalManager.h" +#include "console/consoleTypes.h" +#include "gfx/sim/debugDraw.h" +#include "gui/3d/guiTSControl.h" +#include "sceneGraph/sceneRoot.h" +#include "sceneGraph/pathManager.h" +#include "ts/tsShapeInstance.h" +#include "core/util/journal/process.h" +#include "materials/materialManager.h" + + +extern void ShowInit(); + +// Register the initialization and shutdown routines +static bool Init3dSubsystems(); +static bool Shutdown3dSubsystems(); + +static ProcessRegisterInit sgInit(Init3dSubsystems); +static ProcessRegisterShutdown sgShutdown(Shutdown3dSubsystems); + +//------------------------------------------------------------------------------ +/// Camera and FOV info +namespace { + + const U32 MaxZoomSpeed = 2000; ///< max number of ms to reach target FOV + + static F32 sConsoleCameraFov = 90.f; ///< updated to camera FOV each frame + static F32 sDefaultFov = 90.f; ///< normal FOV + static F32 sCameraFov = 90.f; ///< current camera FOV + static F32 sTargetFov = 90.f; ///< the desired FOV + static F32 sLastCameraUpdateTime = 0; ///< last time camera was updated + static S32 sZoomSpeed = 500; ///< ms per 90deg fov change + + /// A scale to apply to the normal visible distance + /// typically used for tuning performance. + static F32 sVisDistanceScale = 1.0f; + +} // namespace {} + +// query +static SimpleQueryList sgServerQueryList; +static U32 sgServerQueryIndex = 0; + +//SERVER FUNCTIONS ONLY +ConsoleFunctionGroupBegin( Containers, "Spatial query functions. Server side only!"); + +ConsoleFunction(containerFindFirst, const char*, 6, 6, "(bitset type, Point3F point, float x, float y, float z)" + "Find objects matching the bitmask type within a box centered at point, with extents x, y, z.\n\n" + "Returns the first object found; thereafter, you can get more results using containerFindNext().") +{ + //find out what we're looking for + U32 typeMask = U32(dAtoi(argv[1])); + + //find the center of the container volume + Point3F origin(0.0f, 0.0f, 0.0f); + dSscanf(argv[2], "%g %g %g", &origin.x, &origin.y, &origin.z); + + //find the box dimensions + Point3F size(0.0f, 0.0f, 0.0f); + size.x = mFabs(dAtof(argv[3])); + size.y = mFabs(dAtof(argv[4])); + size.z = mFabs(dAtof(argv[5])); + + //build the container volume + Box3F queryBox; + queryBox.minExtents = origin; + queryBox.maxExtents = origin; + queryBox.minExtents -= size; + queryBox.maxExtents += size; + + //initialize the list, and do the query + sgServerQueryList.mList.clear(); + gServerContainer.findObjects(queryBox, typeMask, SimpleQueryList::insertionCallback, &sgServerQueryList); + + //return the first element + sgServerQueryIndex = 0; + char *buff = Con::getReturnBuffer(100); + if (sgServerQueryList.mList.size()) + dSprintf(buff, 100, "%d", sgServerQueryList.mList[sgServerQueryIndex++]->getId()); + else + buff[0] = '\0'; + + return buff; +} + +ConsoleFunction( containerFindNext, const char*, 1, 1, "Get more results from a previous call to containerFindFirst().") +{ + //return the next element + char *buff = Con::getReturnBuffer(100); + if (sgServerQueryIndex < sgServerQueryList.mList.size()) + dSprintf(buff, 100, "%d", sgServerQueryList.mList[sgServerQueryIndex++]->getId()); + else + buff[0] = '\0'; + + return buff; +} + +ConsoleFunctionGroupEnd( Containers ); + +//------------------------------------------------------------------------------ + +bool GameGetCameraTransform(MatrixF *mat, Point3F *velocity) +{ + // Return the position and velocity of the control object + GameConnection* connection = GameConnection::getConnectionToServer(); + return connection && connection->getControlCameraTransform(0, mat) && + connection->getControlCameraVelocity(velocity); +} + +//------------------------------------------------------------------------------ +ConsoleFunctionGroupBegin( CameraFunctions, "Functions controlling the global camera properties defined in main.cc."); + +ConsoleFunction(setDefaultFov, void, 2,2, "(defaultFov) - Set the default FOV for a camera.") +{ + TORQUE_UNUSED(argc); + sDefaultFov = mClampF(dAtof(argv[1]), MinCameraFov, MaxCameraFov); + if(sCameraFov == sTargetFov) + sTargetFov = sDefaultFov; +} + +ConsoleFunction(setZoomSpeed, void, 2,2, "(speed) - Set the zoom speed of the camera, in ms per 90deg FOV change.") +{ + TORQUE_UNUSED(argc); + sZoomSpeed = mClamp(dAtoi(argv[1]), 0, MaxZoomSpeed); +} + +ConsoleFunction(setFov, void, 2, 2, "(fov) - Set the FOV of the camera.") +{ + TORQUE_UNUSED(argc); + sTargetFov = mClampF(dAtof(argv[1]), MinCameraFov, MaxCameraFov); +} + +ConsoleFunctionGroupEnd( CameraFunctions ); + +F32 GameGetCameraFov() +{ + return(sCameraFov); +} + +void GameSetCameraFov(F32 fov) +{ + sTargetFov = sCameraFov = fov; +} + +void GameSetCameraTargetFov(F32 fov) +{ + sTargetFov = fov; +} + +void GameUpdateCameraFov() +{ + F32 time = F32(Platform::getVirtualMilliseconds()); + + // need to update fov? + if(sTargetFov != sCameraFov) + { + F32 delta = time - sLastCameraUpdateTime; + + // snap zoom? + if((sZoomSpeed == 0) || (delta <= 0.f)) + sCameraFov = sTargetFov; + else + { + // gZoomSpeed is time in ms to zoom 90deg + F32 step = 90.f * (delta / F32(sZoomSpeed)); + + if(sCameraFov > sTargetFov) + { + sCameraFov -= step; + if(sCameraFov < sTargetFov) + sCameraFov = sTargetFov; + } + else + { + sCameraFov += step; + if(sCameraFov > sTargetFov) + sCameraFov = sTargetFov; + } + } + } + + // the game connection controls the vertical and the horizontal + GameConnection * connection = GameConnection::getConnectionToServer(); + if(connection) + { + // check if fov is valid on control object + if(connection->isValidControlCameraFov(sCameraFov)) + connection->setControlCameraFov(sCameraFov); + else + { + // will set to the closest fov (fails only on invalid control object) + if(connection->setControlCameraFov(sCameraFov)) + { + F32 setFov = sCameraFov; + connection->getControlCameraFov(&setFov); + sTargetFov = sCameraFov = setFov; + } + } + } + + // update the console variable + sConsoleCameraFov = sCameraFov; + sLastCameraUpdateTime = time; +} +//-------------------------------------------------------------------------- + +#ifdef TORQUE_DEBUG +// ConsoleFunction(dumpTSShapes, void, 1, 1, "dumpTSShapes();") +// { +// argc, argv; + +// FindMatch match("*.dts", 4096); +// gResourceManager->findMatches(&match); + +// for (U32 i = 0; i < match.numMatches(); i++) +// { +// U32 j; +// Resource shape = ResourceManager::get().load(match.matchList[i]); +// if (bool(shape) == false) +// Con::errorf(" aaa Couldn't load: %s", match.matchList[i]); + +// U32 numMeshes = 0, numSkins = 0; +// for (j = 0; j < shape->meshes.size(); j++) +// if (shape->meshes[j]) +// numMeshes++; +// for (j = 0; j < shape->skins.size(); j++) +// if (shape->skins[j]) +// numSkins++; + +// Con::printf(" aaa Shape: %s (%d meshes, %d skins)", match.matchList[i], numMeshes, numSkins); +// Con::printf(" aaa Meshes"); +// for (j = 0; j < shape->meshes.size(); j++) +// { +// if (shape->meshes[j]) +// Con::printf(" aaa %d -> nf: %d, nmf: %d, nvpf: %d (%d, %d, %d, %d, %d)", +// shape->meshes[j]->meshType & TSMesh::TypeMask, +// shape->meshes[j]->numFrames, +// shape->meshes[j]->numMatFrames, +// shape->meshes[j]->vertsPerFrame, +// shape->meshes[j]->verts.size(), +// shape->meshes[j]->norms.size(), +// shape->meshes[j]->tverts.size(), +// shape->meshes[j]->primitives.size(), +// shape->meshes[j]->indices.size()); +// } +// Con::printf(" aaa Skins"); +// for (j = 0; j < shape->skins.size(); j++) +// { +// if (shape->skins[j]) +// Con::printf(" aaa %d -> nf: %d, nmf: %d, nvpf: %d (%d, %d, %d, %d, %d)", +// shape->skins[j]->meshType & TSMesh::TypeMask, +// shape->skins[j]->numFrames, +// shape->skins[j]->numMatFrames, +// shape->skins[j]->vertsPerFrame, +// shape->skins[j]->verts.size(), +// shape->skins[j]->norms.size(), +// shape->skins[j]->tverts.size(), +// shape->skins[j]->primitives.size(), +// shape->skins[j]->indices.size()); +// } +// } +// } +#endif + +bool GameProcessCameraQuery(CameraQuery *query) +{ + GameConnection* connection = GameConnection::getConnectionToServer(); + + if (connection && connection->getControlCameraTransform(0.032f, &query->cameraMatrix)) + { + query->object = dynamic_cast(connection->getControlObject()); + query->nearPlane = gClientSceneGraph->getNearClip(); + + // Scale the normal visible distance by the performance + // tuning scale which we never let over 1. + sVisDistanceScale = mClampF( sVisDistanceScale, 0.01f, 1.0f ); + query->farPlane = gClientSceneGraph->getVisibleDistance() * sVisDistanceScale; + + F32 cameraFov; + if(!connection->getControlCameraFov(&cameraFov)) + return false; + + query->fov = mDegToRad(cameraFov); + return true; + } + return false; +} + +void GameRenderWorld() +{ + PROFILE_START(GameRenderWorld); + FrameAllocator::setWaterMark(0); + + gClientSceneGraph->renderScene( SPT_Diffuse ); + + // renderScene leaves some states dirty, which causes problems if GameTSCtrl is the last Gui object rendered + GFX->updateStates(); + + AssertFatal(FrameAllocator::getWaterMark() == 0, + "Error, someone didn't reset the water mark on the frame allocator!"); + FrameAllocator::setWaterMark(0); + PROFILE_END(); +} + + +static void Process3D() +{ + MATMGR->updateTime(); +} + +static void RegisterGameFunctions() +{ + Con::addVariable( "$pref::visibleDistanceMod", TypeF32, &sVisDistanceScale ); + Con::addVariable( "$cameraFov", TypeF32, &sConsoleCameraFov ); + + // Stuff game types into the console + Con::setIntVariable("$TypeMasks::StaticObjectType", StaticObjectType); + Con::setIntVariable("$TypeMasks::EnvironmentObjectType", EnvironmentObjectType); + Con::setIntVariable("$TypeMasks::TerrainObjectType", TerrainObjectType); + Con::setIntVariable("$TypeMasks::InteriorObjectType", InteriorObjectType); + Con::setIntVariable("$TypeMasks::WaterObjectType", WaterObjectType); + Con::setIntVariable("$TypeMasks::TriggerObjectType", TriggerObjectType); + Con::setIntVariable("$TypeMasks::MarkerObjectType", MarkerObjectType); + Con::setIntVariable("$TypeMasks::GameBaseObjectType", GameBaseObjectType); + Con::setIntVariable("$TypeMasks::ShapeBaseObjectType", ShapeBaseObjectType); + Con::setIntVariable("$TypeMasks::CameraObjectType", CameraObjectType); + Con::setIntVariable("$TypeMasks::StaticShapeObjectType", StaticShapeObjectType); + Con::setIntVariable("$TypeMasks::PlayerObjectType", PlayerObjectType); + Con::setIntVariable("$TypeMasks::ItemObjectType", ItemObjectType); + Con::setIntVariable("$TypeMasks::VehicleObjectType", VehicleObjectType); + Con::setIntVariable("$TypeMasks::VehicleBlockerObjectType", VehicleBlockerObjectType); + Con::setIntVariable("$TypeMasks::ProjectileObjectType", ProjectileObjectType); + Con::setIntVariable("$TypeMasks::ExplosionObjectType", ExplosionObjectType); + Con::setIntVariable("$TypeMasks::CorpseObjectType", CorpseObjectType); + Con::setIntVariable("$TypeMasks::DebrisObjectType", DebrisObjectType); + Con::setIntVariable("$TypeMasks::PhysicalZoneObjectType", PhysicalZoneObjectType); + Con::setIntVariable("$TypeMasks::StaticTSObjectType", StaticTSObjectType); + Con::setIntVariable("$TypeMasks::StaticRenderedObjectType", StaticRenderedObjectType); + Con::setIntVariable("$TypeMasks::DamagableItemObjectType", DamagableItemObjectType); + Con::setIntVariable("$TypeMasks::LightObjectType", LightObjectType); +} + +static bool Init3dSubsystems() +{ + // Misc other static subsystems. + MaterialManager::createSingleton(); + +#ifdef TORQUE_TGB_ONLY + ModelNodeFactoryCache2D::createSingleton(); + ModelControllerFactoryCache2D::createSingleton(); +#endif + + // Client scenegraph & root zone. + gClientSceneGraph = new SceneGraph(true); + gClientSceneRoot = new SceneRoot; + gClientSceneGraph->addObjectToScene(gClientSceneRoot); + + // Server scenegraph & root zone. + gServerSceneGraph = new SceneGraph(false); + gServerSceneRoot = new SceneRoot; + gServerSceneGraph->addObjectToScene(gServerSceneRoot); + + // Decal manager (just for clients). + gDecalManager = new DecalManager; + gClientContainer.addObject(gDecalManager); + gClientSceneGraph->addObjectToScene(gDecalManager); + + // Misc other subsystems. + TSShapeInstance::init(); + DebugDrawer::init(); + PathManager::init(); + +#ifndef TORQUE_TGB_ONLY + MoveManager::init(); +#else + t2dMoveManager::initialize(); +#endif + Process::notify(Process3D, PROCESS_TIME_ORDER); + + GameConnection::smFovUpdate.notify(GameSetCameraFov); + + RegisterGameFunctions(); + + return true; +} + +static bool Shutdown3dSubsystems() +{ + GameConnection::smFovUpdate.remove(GameSetCameraFov); + + Process::remove(Process3D); + + PathManager::destroy(); + TSShapeInstance::destroy(); + + gClientSceneGraph->removeObjectFromScene(gDecalManager); + gClientContainer.removeObject(gDecalManager); + gClientSceneGraph->removeObjectFromScene(gClientSceneRoot); + gServerSceneGraph->removeObjectFromScene(gServerSceneRoot); + + SAFE_DELETE(gClientSceneRoot); + SAFE_DELETE(gServerSceneRoot); + SAFE_DELETE(gClientSceneGraph); + SAFE_DELETE(gServerSceneGraph); + SAFE_DELETE(gDecalManager); + +#ifdef TORQUE_TGB_ONLY + ModelNodeFactoryCache2D::instance()->flushTotalCache(); + ModelNodeFactoryCache2D::deleteSingleton(); + ModelControllerFactoryCache2D::instance()->flushTotalCache(); + ModelControllerFactoryCache2D::deleteSingleton(); +#endif + + MaterialManager::deleteSingleton(); + + return true; +} diff --git a/T3D/gameFunctions.h b/T3D/gameFunctions.h new file mode 100644 index 0000000..1a8990d --- /dev/null +++ b/T3D/gameFunctions.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMEFUNCTIONS_H_ +#define _GAMEFUNCTIONS_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif + +struct CameraQuery; + + +/// Actually renders the world. This is the function that will render the +/// scene ONLY - new guis, no damage flashes. +void GameRenderWorld(); + +/// Renders overlays such as damage flashes, white outs, and water masks. +/// These are usually a color applied over the entire screen. +void GameRenderFilters(const CameraQuery& camq); + +/// Does the same thing as GameGetCameraTransform, but fills in other data +/// including information about the far and near clipping planes. +bool GameProcessCameraQuery(CameraQuery *query); + +/// Gets the position, rotation, and velocity of the camera. +bool GameGetCameraTransform(MatrixF *mat, Point3F *velocity); + +/// Gets the camera field of view angle. +F32 GameGetCameraFov(); + +/// Sets the field of view angle of the camera. +void GameSetCameraFov(F32 fov); + +/// Sets where the camera fov will be change to. This is for +/// non-instantaneous zooms/retractions. +void GameSetCameraTargetFov(F32 fov); + +/// Update the camera fov to be closer to the target fov. +void GameUpdateCameraFov(); + +#endif // _GAMEFUNCTIONS_H_ diff --git a/T3D/gameProcess.cpp b/T3D/gameProcess.cpp new file mode 100644 index 0000000..36b1dfc --- /dev/null +++ b/T3D/gameProcess.cpp @@ -0,0 +1,412 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/dnet.h" +#include "core/stream/bitStream.h" +#include "core/frameAllocator.h" +#include "math/mPoint3.h" +#include "math/mMatrix.h" +#include "math/mathUtils.h" +#include "T3D/gameConnection.h" +#include "T3D/gameBase.h" +#include "T3D/gameProcess.h" +#include "T3D/fx/cameraFXMgr.h" +#include "platform/profiler.h" +#include "console/consoleTypes.h" + +//---------------------------------------------------------------------------- + +ClientProcessList gClientProcessList; +ServerProcessList gServerProcessList; +U32 gNetOrderNextId = 0; + +ConsoleFunction(dumpProcessList,void,1,1,"dumpProcessList();") +{ + Con::printf("client process list:"); + gClientProcessList.dumpToConsole(); + Con::printf("server process list:"); + gServerProcessList.dumpToConsole(); +} + +namespace +{ + // local work class + struct GameBaseListNode + { + GameBaseListNode() + { + mPrev=this; + mNext=this; + mObject=NULL; + } + + GameBaseListNode * mPrev; + GameBaseListNode * mNext; + GameBase * mObject; + + void linkBefore(GameBaseListNode * obj) + { + // Link this before obj + mNext = obj; + mPrev = obj->mPrev; + obj->mPrev = this; + mPrev->mNext = this; + } + }; +} // namespace + +//-------------------------------------------------------------------------- +// ClientProcessList +//-------------------------------------------------------------------------- + +ClientProcessList::ClientProcessList() +{ +} + +void ClientProcessList::addObject(ProcessObject * pobj) +{ + GameBase * obj = dynamic_cast(pobj); + if (obj==NULL) + // don't add + return; + + if (obj->mNetFlags.test(GameBase::NetOrdered)) + { + obj->plLinkBefore(&mHead); + mDirty = true; + } + else if (obj->mNetFlags.test(GameBase::TickLast)) + { + obj->mOrderGUID = 0xFFFFFFFF; + obj->plLinkBefore(&mHead); + // not dirty + } + else + { + obj->plLinkAfter(&mHead); + // not dirty + } +} + +inline GameBase * ClientProcessList::getGameBase(ProcessObject * obj) +{ + return static_cast(obj); +} + +bool ClientProcessList::doBacklogged(SimTime timeDelta) +{ + #ifdef TORQUE_DEBUG + static bool backlogged = false; + static U32 backloggedTime = 0; + #endif + + // See if the control object has pending moves. + GameConnection* connection = GameConnection::getConnectionToServer(); + if (connection) + { + // If the connection to the server is backlogged + // the simulation is frozen. + if (connection->mMoveList.isBacklogged()) + { + #ifdef TORQUE_DEBUG + if (!backlogged) + { + Con::printf("client is backlogged, time is frozen"); + backlogged=true; + } + + backloggedTime += timeDelta; + #endif + return true; + } + } + + #ifdef TORQUE_DEBUG + if (backlogged) + { + Con::printf("client is no longer backlogged, time is unfrozen (%i ms elapsed)",backloggedTime); + backlogged=false; + backloggedTime=0; + } + #endif + return false; +} + +bool ClientProcessList::advanceTime(SimTime timeDelta) +{ + PROFILE_SCOPE(AdvanceClientTime); + + if (doBacklogged(timeDelta)) + return false; + + bool ret = Parent::advanceTime(timeDelta); + ProcessObject* obj = NULL; + + AssertFatal(mLastDelta>=0.0f && mLastDelta<=1.0f,"Doh! That would be bad."); + + obj = mHead.mProcessLink.next; + while (obj != &mHead) + { + GameBase * gb = getGameBase(obj); + if (gb->mProcessTick) + gb->interpolateTick(mLastDelta); + + obj = obj->mProcessLink.next; + } + + // Inform objects of total elapsed delta so they can advance + // client side animations. + F32 dt = F32(timeDelta) / 1000; + + // Update camera FX. + gCamFXMgr.update( dt ); + + obj = mHead.mProcessLink.next; + while (obj != &mHead) + { + GameBase * gb = getGameBase(obj); + gb->advanceTime(dt); + + obj = obj->mProcessLink.next; + } + + return ret; +} + +//---------------------------------------------------------------------------- +void ClientProcessList::onAdvanceObjects() +{ + GameConnection* connection = GameConnection::getConnectionToServer(); + if(connection) + { + // process any demo blocks that are NOT moves, and exactly one move + // we advance time in the demo stream by a move inserted on + // each tick. So before doing the tick processing we advance + // the demo stream until a move is ready + if(connection->isPlayingBack()) + { + U32 blockType; + do + { + blockType = connection->getNextBlockType(); + bool res = connection->processNextBlock(); + // if there are no more blocks, exit out of this function, + // as no more client time needs to process right now - we'll + // get it all on the next advanceClientTime() + if(!res) + return; + } + while(blockType != GameConnection::BlockTypeMove); + } + + connection->mMoveList.collectMove(); + advanceObjects(); + } + else + advanceObjects(); +} + +void ClientProcessList::onTickObject(ProcessObject * pobj) +{ + SimObjectPtr obj = getGameBase(pobj); + + // Each object is either advanced a single tick, or if it's + // being controlled by a client, ticked once for each pending move. + Move* movePtr; + U32 numMoves; + GameConnection* con = obj->getControllingClient(); + if (con && con->getControlObject() == obj) + { + con->mMoveList.getMoveList(&movePtr, &numMoves); + if (numMoves) + { + // Note: should only have a single move at this point + AssertFatal(numMoves==1,"ClientProccessList::onTickObject: more than one move in queue"); + + #ifdef TORQUE_DEBUG_NET_MOVES + U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); + #endif + + if( obj->mProcessTick ) + obj->processTick(movePtr); + + if (bool(obj) && obj->getControllingClient()) + { + U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); + + // set checksum if not set or check against stored value if set + movePtr->checksum = newsum; + + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("move checksum: %i, (start %i), (move %f %f %f)", + movePtr->checksum,sum,movePtr->yaw,movePtr->y,movePtr->z); + #endif + } + con->mMoveList.clearMoves(1); + } + } + else if (obj->mProcessTick) + obj->processTick(0); +} + +void ClientProcessList::advanceObjects() +{ + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("Advance client time..."); + #endif + + Parent::advanceObjects(); + + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("---------"); + #endif +} + +void ClientProcessList::clientCatchup(GameConnection * connection) +{ + SimObjectPtr control = connection->getControlObject(); + if (control) + { + Move * movePtr; + U32 numMoves; + U32 m = 0; + connection->mMoveList.getMoveList(&movePtr,&numMoves); + + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("client catching up... (%i)", numMoves); + #endif + + preTickSignal().trigger(); + + if( control->mProcessTick ) + for (m=0; mprocessTick(movePtr++); + + connection->mMoveList.clearMoves(m); + } + + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("---------"); + #endif +} + +//-------------------------------------------------------------------------- +// ServerProcessList +//-------------------------------------------------------------------------- + +ServerProcessList::ServerProcessList() +{ +} + +void ServerProcessList::addObject(ProcessObject * pobj) +{ + GameBase * obj = dynamic_cast(pobj); + if (obj==NULL) + // don't add + return; + AssertFatal(obj->isServerObject(),"Adding client object to server list"); + + if (obj->mNetFlags.test(GameBase::NetOrdered)) + { + if ((gNetOrderNextId & 0xFFFF) == 0) + // don't let it be zero + gNetOrderNextId++; + obj->mOrderGUID = (gNetOrderNextId++) & 0xFFFF; // 16 bits should be enough + obj->plLinkBefore(&mHead); + mDirty = true; + } + else if (obj->mNetFlags.test(GameBase::TickLast)) + { + obj->mOrderGUID = 0xFFFFFFFF; + obj->plLinkBefore(&mHead); + // not dirty + } + else + { + obj->plLinkAfter(&mHead); + // not dirty + } +} + +inline GameBase * ServerProcessList::getGameBase(ProcessObject * obj) +{ + return static_cast(obj); +} + +void ServerProcessList::onTickObject(ProcessObject * pobj) +{ + SimObjectPtr obj = getGameBase(pobj); + AssertFatal(obj->isServerObject(),"Client object on server process list"); + + // Each object is either advanced a single tick, or if it's + // being controlled by a client, ticked once for each pending move. + GameConnection * con = obj->getControllingClient(); + + if (con && con->getControlObject() == obj) + { + Move* movePtr; + U32 m, numMoves; + con->mMoveList.getMoveList(&movePtr, &numMoves); + + for (m=0; mgetControlObject() == obj; m++, movePtr++) + { + #ifdef TORQUE_DEBUG_NET_MOVES + U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); + #endif + + if( obj->mProcessTick ) + obj->processTick(movePtr); + + if (con && con->getControlObject() == obj) + { + U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); + + // check move checksum + if (movePtr->checksum != newsum) + { + #ifdef TORQUE_DEBUG_NET_MOVES + if (!obj->mIsAiControlled) + Con::printf("move %i checksum disagree: %i != %i, (start %i), (move %f %f %f)", + movePtr->id, movePtr->checksum,newsum,sum,movePtr->yaw,movePtr->y,movePtr->z); + #endif + + movePtr->checksum = Move::ChecksumMismatch; + } + else + { + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("move %i checksum agree: %i == %i, (start %i), (move %f %f %f)", + movePtr->id, movePtr->checksum,newsum,sum,movePtr->yaw,movePtr->y,movePtr->z); + #endif + } + } + } + con->mMoveList.clearMoves(m); + } + else if (obj->mProcessTick) + obj->processTick(0); +} + +void ServerProcessList::advanceObjects() +{ + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("Advance server time..."); + #endif + + Parent::advanceObjects(); + + // Credit all the connections with the elapsed ticks. + SimGroup *g = Sim::getClientGroup(); + for (SimGroup::iterator i = g->begin(); i != g->end(); i++) + if (GameConnection *t = dynamic_cast(*i)) + t->mMoveList.incMoveCredit(1); + + #ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("---------"); + #endif +} + + + diff --git a/T3D/gameProcess.h b/T3D/gameProcess.h new file mode 100644 index 0000000..539a578 --- /dev/null +++ b/T3D/gameProcess.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _APP_GAMEPROCESS_H_ +#define _APP_GAMEPROCESS_H_ + +#include "platform/platform.h" +#include "sim/processList.h" + +class GameBase; +class GameConnection; +struct Move; + +//---------------------------------------------------------------------------- + +/// List to keep track of GameBases to process. +class ClientProcessList : public ProcessList +{ + typedef ProcessList Parent; + +protected: + + void onTickObject(ProcessObject *); + void advanceObjects(); + void onAdvanceObjects(); + bool doBacklogged(SimTime timeDelta); + GameBase * getGameBase(ProcessObject * obj); + + +public: + ClientProcessList(); + + void addObject(ProcessObject * obj); + + bool advanceTime(SimTime timeDelta); + + /// @} + // after update from server, catch back up to where we were + void clientCatchup(GameConnection*); +}; + +class ServerProcessList : public ProcessList +{ + typedef ProcessList Parent; + +protected: + + void onTickObject(ProcessObject *); + void advanceObjects(); + GameBase * getGameBase(ProcessObject * obj); + +public: + ServerProcessList(); + + void addObject(ProcessObject * obj); +}; + +extern ClientProcessList gClientProcessList; +extern ServerProcessList gServerProcessList; + +#endif \ No newline at end of file diff --git a/T3D/gameTSCtrl.cpp b/T3D/gameTSCtrl.cpp new file mode 100644 index 0000000..d8ebe27 --- /dev/null +++ b/T3D/gameTSCtrl.cpp @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/gameTSCtrl.h" +#include "console/consoleTypes.h" +#include "T3D/gameBase.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" +#include "T3D/gameFunctions.h" + +//--------------------------------------------------------------------------- +// Debug stuff: +Point3F lineTestStart = Point3F(0, 0, 0); +Point3F lineTestEnd = Point3F(0, 1000, 0); +Point3F lineTestIntersect = Point3F(0, 0, 0); +bool gSnapLine = false; + +//---------------------------------------------------------------------------- +// Class: GameTSCtrl +//---------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GameTSCtrl); + +GameTSCtrl::GameTSCtrl() +{ +} + +//--------------------------------------------------------------------------- +bool GameTSCtrl::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + +#ifdef TORQUE_DEMO_WATERMARK + mWatermark.init(); +#endif + + return true; +} + +//--------------------------------------------------------------------------- +bool GameTSCtrl::processCameraQuery(CameraQuery *camq) +{ + GameUpdateCameraFov(); + return GameProcessCameraQuery(camq); +} + +//--------------------------------------------------------------------------- +void GameTSCtrl::renderWorld(const RectI &updateRect) +{ + GameRenderWorld(); +} + +//--------------------------------------------------------------------------- +void GameTSCtrl::makeScriptCall(const char *func, const GuiEvent &evt) const +{ + // write screen position + char *sp = Con::getArgBuffer(32); + dSprintf(sp, 32, "%d %d", evt.mousePoint.x, evt.mousePoint.y); + + // write world position + char *wp = Con::getArgBuffer(32); + Point3F camPos; + mLastCameraQuery.cameraMatrix.getColumn(3, &camPos); + dSprintf(wp, 32, "%g %g %g", camPos.x, camPos.y, camPos.z); + + // write click vector + char *vec = Con::getArgBuffer(32); + Point3F fp(evt.mousePoint.x, evt.mousePoint.y, 1.0); + Point3F ray; + unproject(fp, &ray); + ray -= camPos; + ray.normalizeSafe(); + dSprintf(vec, 32, "%g %g %g", ray.x, ray.y, ray.z); + + Con::executef( (SimObject*)this, func, sp, wp, vec ); +} + +void GameTSCtrl::onMouseDown(const GuiEvent &evt) +{ + Parent::onMouseDown(evt); + if( isMethod( "onMouseDown" ) ) + makeScriptCall( "onMouseDown", evt ); +} + +void GameTSCtrl::onRightMouseDown(const GuiEvent &evt) +{ + Parent::onRightMouseDown(evt); + if( isMethod( "onRightMouseDown" ) ) + makeScriptCall( "onRightMouseDown", evt ); +} + +void GameTSCtrl::onMiddleMouseDown(const GuiEvent &evt) +{ + Parent::onMiddleMouseDown(evt); + if( isMethod( "onMiddleMouseDown" ) ) + makeScriptCall( "onMiddleMouseDown", evt ); +} + +void GameTSCtrl::onMouseUp(const GuiEvent &evt) +{ + Parent::onMouseUp(evt); + if( isMethod( "onMouseUp" ) ) + makeScriptCall( "onMouseUp", evt ); +} + +void GameTSCtrl::onRightMouseUp(const GuiEvent &evt) +{ + Parent::onRightMouseUp(evt); + if( isMethod( "onRightMouseUp" ) ) + makeScriptCall( "onRightMouseUp", evt ); +} + +void GameTSCtrl::onMiddleMouseUp(const GuiEvent &evt) +{ + Parent::onMiddleMouseUp(evt); + if( isMethod( "onMiddleMouseUp" ) ) + makeScriptCall( "onMiddleMouseUp", evt ); +} + +void GameTSCtrl::onMouseMove(const GuiEvent &evt) +{ + if(gSnapLine) + return; + + MatrixF mat; + Point3F vel; + if ( GameGetCameraTransform(&mat, &vel) ) + { + Point3F pos; + mat.getColumn(3,&pos); + Point3F screenPoint((F32)evt.mousePoint.x, (F32)evt.mousePoint.y, -1.0f); + Point3F worldPoint; + if (unproject(screenPoint, &worldPoint)) { + Point3F vec = worldPoint - pos; + lineTestStart = pos; + vec.normalizeSafe(); + lineTestEnd = pos + vec * 1000; + } + } +} + +void GameTSCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + // check if should bother with a render + GameConnection * con = GameConnection::getConnectionToServer(); + bool skipRender = !con || (con->getWhiteOut() >= 1.f) || (con->getDamageFlash() >= 1.f) || (con->getBlackOut() >= 1.f); + + if(!skipRender || true) + Parent::onRender(offset, updateRect); + +#ifdef TORQUE_DEMO_WATERMARK + mWatermark.render(getExtent()); +#endif +} + +//-------------------------------------------------------------------------- +ConsoleFunction( snapToggle, void, 1, 1, "()" ) +{ + gSnapLine = !gSnapLine; +} diff --git a/T3D/gameTSCtrl.h b/T3D/gameTSCtrl.h new file mode 100644 index 0000000..0014624 --- /dev/null +++ b/T3D/gameTSCtrl.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAMETSCTRL_H_ +#define _GAMETSCTRL_H_ + +#ifndef _GAME_H_ +#include "app/game.h" +#endif +#ifndef _GUITSCONTROL_H_ +#include "gui/3d/guiTSControl.h" +#endif + +#ifdef TORQUE_DEMO_WATERMARK +#ifndef _WATERMARK_H_ +#include "demo/watermark/watermark.h" +#endif +#endif + +class ProjectileData; +class GameBase; + +//---------------------------------------------------------------------------- +class GameTSCtrl : public GuiTSCtrl +{ +private: + typedef GuiTSCtrl Parent; + +#ifdef TORQUE_DEMO_WATERMARK + Watermark mWatermark; +#endif + + void makeScriptCall(const char *func, const GuiEvent &evt) const; + +public: + GameTSCtrl(); + + DECLARE_CONOBJECT(GameTSCtrl); + + bool processCameraQuery(CameraQuery *query); + void renderWorld(const RectI &updateRect); + + // GuiControl + virtual void onMouseDown(const GuiEvent &evt); + virtual void onRightMouseDown(const GuiEvent &evt); + virtual void onMiddleMouseDown(const GuiEvent &evt); + + virtual void onMouseUp(const GuiEvent &evt); + virtual void onRightMouseUp(const GuiEvent &evt); + virtual void onMiddleMouseUp(const GuiEvent &evt); + + void onMouseMove(const GuiEvent &evt); + void onRender(Point2I offset, const RectI &updateRect); + + virtual bool onAdd(); +}; + +#endif diff --git a/T3D/groundPlane.cpp b/T3D/groundPlane.cpp new file mode 100644 index 0000000..c598b50 --- /dev/null +++ b/T3D/groundPlane.cpp @@ -0,0 +1,525 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/groundPlane.h" +#include "renderInstance/renderPassManager.h" +#include "sceneGraph/sceneState.h" +#include "materials/sceneData.h" +#include "materials/materialDefinition.h" +#include "materials/materialManager.h" +#include "math/util/frustum.h" +#include "math/mPlane.h" +#include "math/mathIO.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "collision/boxConvex.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsStatic.h" + + +/// Minimum square size allowed. This is a cheap way to limit the amount +/// of geometry possibly generated by the GroundPlane (vertex buffers have a +/// limit, too). Dynamically clipping extents into range is a problem since the +/// location of the horizon depends on the camera orientation. Just shifting +/// squareSize as needed also doesn't work as that causes different geometry to +/// be generated depending on the viewpoint and orientation which affects the +/// texturing. +static const F32 sMIN_SQUARE_SIZE = 16; + + +IMPLEMENT_CO_NETOBJECT_V1( GroundPlane ); + +GroundPlane::GroundPlane() + : mSquareSize( 128.0f ), + mScaleU( 1.0f ), + mScaleV( 1.0f ), + mMaterial( NULL ), + mMin( 0.0f, 0.0f ), + mMax( 0.0f, 0.0f ), + mPhysicsRep( NULL ) +{ + mTypeMask |= StaticObjectType | StaticRenderedObjectType | StaticShapeObjectType; + mNetFlags.set( Ghostable | ScopeAlways ); + + mConvexList = new Convex; +} + +GroundPlane::~GroundPlane() +{ + if( mMaterial ) + SAFE_DELETE( mMaterial ); + + mConvexList->nukeList(); + SAFE_DELETE( mConvexList ); +} + +void GroundPlane::initPersistFields() +{ + addGroup( "Plane" ); + addField( "squareSize", TypeF32, Offset( mSquareSize, GroundPlane ) ); + addField( "scaleU", TypeF32, Offset( mScaleU, GroundPlane ) ); + addField( "scaleV", TypeF32, Offset( mScaleV, GroundPlane ) ); + addField( "material", TypeMaterialName, Offset( mMaterialName, GroundPlane ) ); + endGroup( "Plane" ); + + Parent::initPersistFields(); + + removeField( "scale" ); +} + +bool GroundPlane::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + if( isClientObject() ) + _updateMaterial(); + + if( mSquareSize < sMIN_SQUARE_SIZE ) + { + Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE ); + mSquareSize = sMIN_SQUARE_SIZE; + } + + setScale( VectorF( 1.0f, 1.0f, 1.0f ) ); + setGlobalBounds(); + resetWorldBox(); + + addToScene(); + + if( gPhysicsPlugin ) + mPhysicsRep = gPhysicsPlugin->createStatic( this ); + + return true; +} + +void GroundPlane::onRemove() +{ + if ( mPhysicsRep ) + SAFE_DELETE( mPhysicsRep ); + + removeFromScene(); + Parent::onRemove(); +} + +void GroundPlane::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits( UpdateMask ); + + if( mSquareSize < sMIN_SQUARE_SIZE ) + { + Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE ); + mSquareSize = sMIN_SQUARE_SIZE; + } + + setScale( VectorF( 1.0f, 1.0f, 1.0f ) ); + resetWorldBox(); +} + +void GroundPlane::setTransform( const MatrixF &mat ) +{ + Parent::setTransform( mat ); + + // Parent::setTransforms ends up setting our worldBox to something other than + // global, so we have to set it back... but we can't actually call setGlobalBounds + // again because it does extra work adding and removing us from the container. + + mGlobalBounds = true; + mObjBox.minExtents.set(-1e10, -1e10, -1e10); + mObjBox.maxExtents.set( 1e10, 1e10, 1e10); + resetWorldBox(); + + mPlaneBox = getPlaneBox(); +} + +U32 GroundPlane::packUpdate( NetConnection* connection, U32 mask, BitStream* stream ) +{ + U32 retMask = Parent::packUpdate( connection, mask, stream ); + + stream->write( mSquareSize ); + stream->write( mScaleU ); + stream->write( mScaleV ); + stream->write( mMaterialName ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + mathWrite( *stream, getTransform() ); + } + + return retMask; +} + +void GroundPlane::unpackUpdate( NetConnection* connection, BitStream* stream ) +{ + Parent::unpackUpdate( connection, stream ); + + stream->read( &mSquareSize ); + stream->read( &mScaleU ); + stream->read( &mScaleV ); + stream->read( &mMaterialName ); + + if( stream->readFlag() ) // UpdateMask + { + MatrixF mat; + mathRead( *stream, &mat ); + setTransform( mat ); + } + + // If we're added then something possibly changed in + // the editor... do an update of the material and the + // geometry. + if ( isProperlyAdded() ) + { + _updateMaterial(); + mVertexBuffer = NULL; + } +} + +void GroundPlane::_updateMaterial() +{ + if( mMaterialName.isEmpty() ) + { + Con::warnf( "GroundPlane::_updateMaterial - no material set; defaulting to 'WarningMaterial'" ); + mMaterialName = "WarningMaterial"; + } + + // If the material name matches then don't + // bother updating it. + if ( mMaterial && + mMaterialName.compare( mMaterial->getMaterial()->getName() ) == 0 ) + return; + + SAFE_DELETE( mMaterial ); + + mMaterial = MATMGR->createMatInstance( mMaterialName, getGFXVertexFormat< VertexType >() ); + if ( !mMaterial ) + Con::errorf( "GroundPlane::_updateMaterial - no material called '%s'", mMaterialName.c_str() ); +} + +bool GroundPlane::castRay( const Point3F& start, const Point3F& end, RayInfo* info ) +{ + PlaneF plane( Point3F( 0.0f, 0.0f, 0.0f ), Point3F( 0.0f, 0.0f, 1.0f ) ); + + F32 t = plane.intersect( start, end ); + if( t >= 0.0 && t <= 1.0 ) + { + info->t = t; + info->setContactPoint( start, end ); + info->normal.set( 0, 0, 1 ); + info->material = mMaterial; + info->object = this; + info->distance = 0; + info->faceDot = 0; + info->texCoord.set( 0, 0 ); + return true; + } + + return false; +} + +void GroundPlane::buildConvex( const Box3F& box, Convex* convex ) +{ + mConvexList->collectGarbage(); + + if ( !box.isOverlapped( mPlaneBox ) ) + return; + + // See if we already have a convex in the working set. + BoxConvex *boxConvex = NULL; + CollisionWorkingList &wl = convex->getWorkingList(); + CollisionWorkingList *itr = wl.wLink.mNext; + for ( ; itr != &wl; itr = itr->wLink.mNext ) + { + if ( itr->mConvex->getType() == BoxConvexType && + itr->mConvex->getObject() == this ) + { + boxConvex = (BoxConvex*)itr->mConvex; + break; + } + } + + if ( !boxConvex ) + { + boxConvex = new BoxConvex; + mConvexList->registerObject( boxConvex ); + boxConvex->init( this ); + + convex->addToWorkingList( boxConvex ); + } + + // Update our convex to best match the queried box + if ( boxConvex ) + { + Point3F queryCenter = box.getCenter(); + + boxConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, -GROUND_PLANE_BOX_HEIGHT_HALF ); + boxConvex->mSize = Point3F( box.getExtents().x, + box.getExtents().y, + GROUND_PLANE_BOX_HEIGHT_HALF ); + } +} + +bool GroundPlane::buildPolyList( AbstractPolyList* polyList, const Box3F&, const SphereF& ) +{ + polyList->setObject( this ); + polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) ); + + polyList->addBox( mPlaneBox, mMaterial ); + + return true; +} + +bool GroundPlane::prepRenderImage( SceneState* state, const U32 stateKey, const U32, const bool ) +{ + PROFILE_SCOPE( GroundPlane_prepRenderImage ); + + if( isLastState( state, stateKey ) ) + return false; + + setLastState( state, stateKey ); + + if( !state->isObjectRendered( this ) + || !mMaterial ) + return false; + + PROFILE_SCOPE( GroundPlane_prepRender ); + + // Update the geometry. + createGeometry( state->getFrustum() ); + if( mVertexBuffer.isNull() ) + return false; + + // Add a render instance. + + RenderPassManager* pass = state->getRenderPass(); + MeshRenderInst* ri = pass->allocInst< MeshRenderInst >(); + + ri->type = RenderPassManager::RIT_Mesh; + ri->vertBuff = &mVertexBuffer; + ri->primBuff = &mPrimitiveBuffer; + ri->prim = &mPrimitive; + ri->matInst = mMaterial; + ri->objectToWorld = pass->allocUniqueXform( mRenderObjToWorld ); + ri->worldToCamera = pass->allocSharedXform(RenderPassManager::View); + ri->projection = pass->allocSharedXform(RenderPassManager::Projection); + ri->visibility = 1.0f; + ri->translucentSort = ( ri->matInst->getMaterial()->isTranslucent() ); + // NOTICE: SFXBB is removed and refraction is disabled! + //ri->backBuffTex = GFX->getSfxBackBuffer(); + ri->defaultKey = ( U32 ) mMaterial; + + // TODO: Get the best lights for the plane in a better + // way.... maybe the same way as we do for terrain? + ri->lights[0] = state->getLightManager()->getDefaultLight(); + + if( ri->translucentSort ) + ri->type = RenderPassManager::RIT_Translucent; + + pass->addInst( ri ); + + return true; +} + +/// Generate a subset of the ground plane matching the given frustum. + +void GroundPlane::createGeometry( const Frustum& frustum ) +{ + PROFILE_SCOPE( GroundPlane_createGeometry ); + + enum { MAX_WIDTH = 256, MAX_HEIGHT = 256 }; + + // Project the frustum onto the XY grid. + + Point2F min; + Point2F max; + + projectFrustum( frustum, mSquareSize, min, max ); + + // Early out if the grid projection hasn't changed. + + if( mVertexBuffer.isValid() && + min == mMin && + max == mMax ) + return; + + mMin = min; + mMax = max; + + // Determine the grid extents and allocate the buffers. + // Adjust square size permanently if with the given frustum, + // we end up producing more than a certain limit of geometry. + // This is to prevent this code from causing trouble with + // long viewing distances. + // This only affects the client object, of course, and thus + // has no permanent effect. + + U32 width = U32( ( max.x - min.x ) / mSquareSize ); + if( width > MAX_WIDTH ) + { + mSquareSize = U32( max.x - min.x ) / MAX_WIDTH; + width = MAX_WIDTH; + } + + U32 height = U32( ( max.y - min.y ) / mSquareSize ); + if( height > MAX_HEIGHT ) + { + mSquareSize = U32( max.y - min.y ) / MAX_HEIGHT; + height = MAX_HEIGHT; + } + + const U32 numVertices = ( width + 1 ) * ( height + 1 ); + const U32 numTriangles = width * height * 2; + + // Only reallocate if the vertex buffer is null or too small. + if ( mVertexBuffer.isNull() || numVertices > mVertexBuffer->mNumVerts ) + { + mVertexBuffer.set( GFX, numVertices, GFXBufferTypeDynamic ); + mPrimitiveBuffer.set( GFX, numTriangles * 3, numTriangles, GFXBufferTypeDynamic ); + } + + // Generate the grid. + + generateGrid( width, height, mSquareSize, min, max, mVertexBuffer, mPrimitiveBuffer ); + + // Set up GFX primitive. + + mPrimitive.type = GFXTriangleList; + mPrimitive.numPrimitives = numTriangles; + mPrimitive.numVertices = numVertices; +} + +/// Project the given frustum onto the ground plane and return the XY bounds in world space. + +void GroundPlane::projectFrustum( const Frustum& _frustum, F32 squareSize, Point2F& outMin, Point2F& outMax ) +{ + // Go through all the frustum's corner points and mark + // the min and max XY coordinates. + + // transform the frustum to plane object space + Frustum frustum = _frustum; + frustum.mulL( mWorldToObj ); + + Point2F minPt( F32_MAX, F32_MAX ); + Point2F maxPt( F32_MIN, F32_MIN ); + + for( U32 i = 0; i < Frustum::CornerPointCount; ++ i ) + { + const Point3F& point = frustum.getPoint( i ); + + if( point.x < minPt.x ) + minPt.x = point.x; + if( point.y < minPt.y ) + minPt.y = point.y; + + if( point.x > maxPt.x ) + maxPt.x = point.x; + if( point.y > maxPt.y ) + maxPt.y = point.y; + } + + // Round the min and max coordinates so they align on the grid. + + minPt.x -= mFmod( minPt.x, squareSize ); + minPt.y -= mFmod( minPt.y, squareSize ); + + F32 maxDeltaX = mFmod( maxPt.x, squareSize ); + F32 maxDeltaY = mFmod( maxPt.y, squareSize ); + + if( maxDeltaX != 0.0f ) + maxPt.x += ( squareSize - maxDeltaX ); + if( maxDeltaY != 0.0f ) + maxPt.y += ( squareSize - maxDeltaY ); + + // Add a safezone, so we don't touch the clipping planes. + + minPt.x -= squareSize; minPt.y -= squareSize; + maxPt.x += squareSize; maxPt.y += squareSize; + + outMin = minPt; + outMax = maxPt; +} + +/// Generate a triangulated grid spanning the given bounds into the given buffers. + +void GroundPlane::generateGrid( U32 width, U32 height, F32 squareSize, + const Point2F& min, const Point2F& max, + GFXVertexBufferHandle< VertexType >& outVertices, + GFXPrimitiveBufferHandle& outPrimitives ) +{ + // Generate the vertices. + + VertexType* vertices = outVertices.lock(); + for( F32 y = min.y; y <= max.y; y += squareSize ) + for( F32 x = min.x; x <= max.x; x += squareSize ) + { + vertices->point.x = x; + vertices->point.y = y; + vertices->point.z = 0.0; + + vertices->texCoord.x = ( x / squareSize ) * mScaleU; + vertices->texCoord.y = ( y / squareSize ) * -mScaleV; + + vertices->normal.x = 0.0f; + vertices->normal.y = 0.0f; + vertices->normal.z = 1.0f; + + vertices->tangent.x = 1.0f; + vertices->tangent.y = 0.0f; + vertices->tangent.z = 0.0f; + + vertices->binormal.x = 0.0f; + vertices->binormal.y = 1.0f; + vertices->binormal.z = 0.0f; + + vertices++; + } + outVertices.unlock(); + + // Generate the indices. + + U16* indices; + outPrimitives.lock( &indices ); + + U16 corner1 = 0; + U16 corner2 = 1; + U16 corner3 = width + 1; + U16 corner4 = width + 2; + + for( U32 y = 0; y < height; ++ y ) + { + for( U32 x = 0; x < width; ++ x ) + { + indices[ 0 ] = corner3; + indices[ 1 ] = corner2; + indices[ 2 ] = corner1; + + indices += 3; + + indices[ 0 ] = corner3; + indices[ 1 ] = corner4; + indices[ 2 ] = corner2; + + indices += 3; + + corner1 ++; + corner2 ++; + corner3 ++; + corner4 ++; + } + + corner1 ++; + corner2 ++; + corner3 ++; + corner4 ++; + } + + outPrimitives.unlock(); +} + +ConsoleMethod( GroundPlane, postApply, void, 2, 2, "") +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/T3D/groundPlane.h b/T3D/groundPlane.h new file mode 100644 index 0000000..50cbd34 --- /dev/null +++ b/T3D/groundPlane.h @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_T3D_GROUNDPLANE_H_ +#define _TORQUE_T3D_GROUNDPLANE_H_ + +#include "sceneGraph/sceneObject.h" +#include "gfx/gfxVertexBuffer.h" +#include "gfx/gfxPrimitiveBuffer.h" + +/// A virtually infinite XY ground plane primitive. +/// +/// For rendering, a subset of the plane spanning the view frustum is generated +/// and rendered. Tesselation is determined by the given squareSize property. +/// +/// For collision detection, a finite bounding box is used to deal with finite +/// precision of floating-point operations (we can't use floating-point infinity +/// as infinity*0 is undefined.) +/// +/// The ground plane can be textured like regular geometry by assigning a material +/// name to its 'material' property. UVs mirror grid coordinates so that when +/// using UV wrapping, textures will tile nicely. + +class PhysicsStatic; + +class GroundPlane : public SceneObject +{ + typedef SceneObject Parent; + +private: + enum MaskBits { + UpdateMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + +public: + + DECLARE_CONOBJECT( GroundPlane ); + + GroundPlane(); + virtual ~GroundPlane(); + + virtual bool onAdd(); + virtual void onRemove(); + virtual U32 packUpdate( NetConnection* connection, U32 mask, BitStream* stream ); + virtual void unpackUpdate( NetConnection* connection, BitStream* stream ); + virtual bool prepRenderImage( SceneState* state, const U32 stateKey, const U32, const bool ); + virtual bool castRay( const Point3F& start, const Point3F& end, RayInfo* info ); + virtual void buildConvex( const Box3F& box, Convex* convex ); + virtual bool buildPolyList( AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere ); + virtual void inspectPostApply(); + virtual void setTransform( const MatrixF &mat ); + + static void initPersistFields(); + +protected: + + typedef GFXVertexPNTBT VertexType; + + void _updateMaterial(); + + void createGeometry( const Frustum& frustum ); + void projectFrustum( const Frustum& frustum, F32 squareSize, + Point2F& outMin, Point2F& outMax ); + void generateGrid( U32 width, U32 height, F32 squareSize, + const Point2F& min, const Point2F& max, + GFXVertexBufferHandle< VertexType >& outVertices, + GFXPrimitiveBufferHandle& outPrimitives ); + + Box3F getPlaneBox(); + +private: + + typedef GFXVertexBufferHandle< VertexType > VertexBuffer; + typedef GFXPrimitiveBufferHandle PrimitiveBuffer; + + F32 mSquareSize; ///< World units per grid cell edge. + F32 mScaleU; ///< Scale factor for U texture coordinates. + F32 mScaleV; ///< Scale factor for V texture coordinates. + String mMaterialName; ///< Object name of material to use. + BaseMatInstance* mMaterial; ///< Instantiated material based on given material name. + + PhysicsStatic *mPhysicsRep; + + /// @name Rendering State + /// @{ + + Point2F mMin; + Point2F mMax; + Box3F mPlaneBox; + VertexBuffer mVertexBuffer; + PrimitiveBuffer mPrimitiveBuffer; + GFXPrimitive mPrimitive; + + /// @} + + Convex* mConvexList; ///< List of collision convexes we have created; for cleanup. +}; + +static const F32 GROUND_PLANE_BOX_HEIGHT_HALF = 1.0f; +static const F32 GROUND_PLANE_BOX_EXTENT_HALF = 16000.0f; + +inline Box3F GroundPlane::getPlaneBox() +{ + Box3F planeBox; + + EulerF rot = getTransform().toEuler(); + if( rot.x == 0 && rot.y == 0 ) + { + // rotation in Z only: use regular ground plane collision + planeBox.minExtents = Point3F( - GROUND_PLANE_BOX_EXTENT_HALF, + - GROUND_PLANE_BOX_EXTENT_HALF, + - GROUND_PLANE_BOX_HEIGHT_HALF ); + planeBox.maxExtents = Point3F( GROUND_PLANE_BOX_EXTENT_HALF, + GROUND_PLANE_BOX_EXTENT_HALF, + GROUND_PLANE_BOX_HEIGHT_HALF ); + } else + { + // ground plane is tilted: use larger collision cube + planeBox.minExtents = Point3F( - GROUND_PLANE_BOX_EXTENT_HALF, + - GROUND_PLANE_BOX_EXTENT_HALF, + - GROUND_PLANE_BOX_EXTENT_HALF ); + planeBox.maxExtents = Point3F( GROUND_PLANE_BOX_EXTENT_HALF, + GROUND_PLANE_BOX_EXTENT_HALF, + GROUND_PLANE_BOX_EXTENT_HALF ); + } + + Point3F center = getPosition(); + center.z -= GROUND_PLANE_BOX_HEIGHT_HALF; + + planeBox.setCenter( center ); + + return planeBox; +} + +#endif // _TORQUE_T3D_GROUNDPLANE_H_ diff --git a/T3D/guiMaterialPreview.cpp b/T3D/guiMaterialPreview.cpp new file mode 100644 index 0000000..eba5c6d --- /dev/null +++ b/T3D/guiMaterialPreview.cpp @@ -0,0 +1,475 @@ +//----------------------------------------------------------------------------- +// GuiMaterialPreview Control for Material Editor Written by Travis Vroman of Gaslight Studios +// Updated 2-14-09 +// Portions based off Constructor viewport code. +//----------------------------------------------------------------------------- + +#include "T3D/guiMaterialPreview.h" +#include "renderInstance/renderPassManager.h" +#include "lighting/lightManager.h" +#include "lighting/lightInfo.h" +#include "core/resourceManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" + +// GuiMaterialPreview +GuiMaterialPreview::GuiMaterialPreview() +: mMaxOrbitDist(5.0f), + mMinOrbitDist(0.0f), + mOrbitDist(5.0f), + mMouseState(None), + mModel(NULL), + mLastMousePoint(0, 0), + lastRenderTime(0), + runThread(0), + mFakeSun(NULL) +{ + mActive = true; + mCameraMatrix.identity(); + mCameraRot.set( mDegToRad(30.0f), 0, mDegToRad(-30.0f) ); + mCameraPos.set(0.0f, 1.75f, 1.25f); + mCameraMatrix.setColumn(3, mCameraPos); + mOrbitPos.set(0.0f, 0.0f, 0.0f); + mTransStep = 0.01f; + mTranMult = 4.0; + mLightTransStep = 0.01f; + mLightTranMult = 4.0; + mOrbitRelPos = Point3F(0,0,0); + + // By default don't do dynamic reflection + // updates for this viewport. + mReflectPriority = 0.0f; +} + +GuiMaterialPreview::~GuiMaterialPreview() +{ + SAFE_DELETE(mModel); + SAFE_DELETE(mFakeSun); +} + +bool GuiMaterialPreview::onWake() +{ + if( !Parent::onWake() ) + return false; + + if (!mFakeSun) + mFakeSun = LightManager::createLightInfo(); + + mFakeSun->setColor( ColorF( 1.0f, 1.0f, 1.0f ) ); + mFakeSun->setAmbient( ColorF( 0.5f, 0.5f, 0.5f ) ); + mFakeSun->setDirection( VectorF( 0.0f, 0.707f, -0.707f ) ); + mFakeSun->setPosition( mFakeSun->getDirection() * -10000.0f ); + mFakeSun->setRange( 2000000.0f ); + + return true; +} + +// This function allows the viewport's ambient color to be changed. This is exposed to script below. +void GuiMaterialPreview::setAmbientLightColor( F32 r, F32 g, F32 b ) +{ + ColorF temp(r, g, b); + temp.clamp(); + GuiMaterialPreview::mFakeSun->setAmbient( temp ); +} + +// This function allows the light's color to be changed. This is exposed to script below. +void GuiMaterialPreview::setLightColor( F32 r, F32 g, F32 b ) +{ + ColorF temp(r, g, b); + temp.clamp(); + GuiMaterialPreview::mFakeSun->setColor( temp ); +} + +// This function is for moving the light in the scene. This needs to be adjusted to keep the light +// from getting all out of whack. For now, we'll just rely on the reset function if we need it +// fixed. +void GuiMaterialPreview::setLightTranslate(S32 modifier, F32 xstep, F32 ystep) +{ + F32 _lighttransstep = (modifier & SI_SHIFT ? mLightTransStep : (mLightTransStep*mLightTranMult)); + + Point3F relativeLightDirection = GuiMaterialPreview::mFakeSun->getDirection(); + // May be able to get rid of this. For now, it helps to fix the position of the light if i gets messed up. + if (modifier & SI_PRIMARY_CTRL) + { + relativeLightDirection.x += ( xstep * _lighttransstep * -1 );//need to invert this for some reason. Otherwise, it's backwards. + relativeLightDirection.y += ( ystep * _lighttransstep ); + GuiMaterialPreview::mFakeSun->setDirection(relativeLightDirection); + } + // Default action taken by mouse wheel clicking. + else + { + relativeLightDirection.x += ( xstep * _lighttransstep * -1 ); //need to invert this for some reason. Otherwise, it's backwards. + relativeLightDirection.z += ( ystep * _lighttransstep ); + GuiMaterialPreview::mFakeSun->setDirection(relativeLightDirection); + } +} + +// This is for panning the viewport camera. +void GuiMaterialPreview::setTranslate(S32 modifier, F32 xstep, F32 ystep) +{ + F32 transstep = (modifier & SI_SHIFT ? mTransStep : (mTransStep*mTranMult)); + + F32 nominalDistance = 20.0; + Point3F vec = mCameraPos; + vec -= mOrbitPos; + transstep *= vec.len() / nominalDistance; + + if (modifier & SI_PRIMARY_CTRL) + { + mOrbitRelPos.x += ( xstep * transstep ); + mOrbitRelPos.y += ( ystep * transstep ); + } + else + { + mOrbitRelPos.x += ( xstep * transstep ); + mOrbitRelPos.z += ( ystep * transstep ); + } +} + +// Left Click +void GuiMaterialPreview::onMouseDown(const GuiEvent &event) +{ + mMouseState = MovingLight; + mLastMousePoint = event.mousePoint; + mouseLock(); +} + +// Left Click Release +void GuiMaterialPreview::onMouseUp(const GuiEvent &event) +{ + mouseUnlock(); + mMouseState = None; +} + +// Left Click Drag +void GuiMaterialPreview::onMouseDragged(const GuiEvent &event) +{ + if(mMouseState != MovingLight) + { + return; + } + // If we are MovingLight... + else + { + Point2I delta = event.mousePoint - mLastMousePoint; + mLastMousePoint = event.mousePoint; + setLightTranslate(event.modifier, delta.x, delta.y); + } +} + +// Right Click +void GuiMaterialPreview::onRightMouseDown(const GuiEvent &event) +{ + mMouseState = Rotating; + mLastMousePoint = event.mousePoint; + mouseLock(); +} + +// Right Click Release +void GuiMaterialPreview::onRightMouseUp(const GuiEvent &event) +{ + mouseUnlock(); + mMouseState = None; +} + +// Right Click Drag +void GuiMaterialPreview::onRightMouseDragged(const GuiEvent &event) +{ + if (mMouseState != Rotating) + { + return; + } + Point2I delta = event.mousePoint - mLastMousePoint; + mLastMousePoint = event.mousePoint; + mCameraRot.x += (delta.y * 0.01f); + mCameraRot.z += (delta.x * 0.01f); +} + +// Mouse Wheel Scroll Up +bool GuiMaterialPreview::onMouseWheelUp(const GuiEvent &event) +{ + mOrbitDist = (mOrbitDist - 0.10f); + return true; +} + +// Mouse Wheel Scroll Down +bool GuiMaterialPreview::onMouseWheelDown(const GuiEvent &event) +{ + mOrbitDist = (mOrbitDist + 0.10f); + return true; +} + +// Mouse Wheel Click +void GuiMaterialPreview::onMiddleMouseDown(const GuiEvent &event) +{ + if (!mActive || !mVisible || !mAwake) + { + return; + } + mMouseState = Panning; + mLastMousePoint = event.mousePoint; + mouseLock(); +} + +// Mouse Wheel Click Release +void GuiMaterialPreview::onMiddleMouseUp(const GuiEvent &event) +{ + mouseUnlock(); + mMouseState = None; +} + +// Mouse Wheel Click Drag +void GuiMaterialPreview::onMiddleMouseDragged(const GuiEvent &event) +{ + if (mMouseState != Panning) + { + return; + } + Point2I delta = event.mousePoint - mLastMousePoint; + mLastMousePoint = event.mousePoint; + setTranslate(event.modifier, delta.x, delta.y); +} + +// This is used to set the model we want to view in the control object. +void GuiMaterialPreview::setObjectModel(const char* modelName) +{ + deleteModel(); + + Resource model = ResourceManager::get().load(modelName); + if (! bool(model)) + { + Con::warnf(avar("GuiMaterialPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName)); + return; + } + + mModel = new TSShapeInstance(model, true); + AssertFatal(mModel, avar("GuiMaterialPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName)); + + // Initialize camera values: + mOrbitPos = mModel->getShape()->center; + mMinOrbitDist = mModel->getShape()->radius; + + lastRenderTime = Platform::getVirtualMilliseconds(); +} + +void GuiMaterialPreview::deleteModel() +{ + SAFE_DELETE(mModel); + runThread = 0; +} + +// This is called whenever there is a change in the camera. +bool GuiMaterialPreview::processCameraQuery(CameraQuery* query) +{ + MatrixF xRot, zRot; + Point3F vecf, vecu, vecr;; + xRot.set(EulerF(mCameraRot.x, 0.0f, 0.0f)); + zRot.set(EulerF(0.0f, 0.0f, mCameraRot.z)); + + if(mMouseState != Panning) + { + // Adjust the camera so that we are still facing the model: + Point3F vec; + + mCameraMatrix.mul(zRot, xRot); + mCameraMatrix.getColumn(1, &vec); + vec *= mOrbitDist; + mCameraPos = mOrbitPos - vec; + + query->farPlane = 2100.0f; + query->nearPlane = query->farPlane / 5000.0f; + query->fov = 45.0f; + mCameraMatrix.setColumn(3, mCameraPos); + query->cameraMatrix = mCameraMatrix; + } + else + { + mCameraMatrix.mul( zRot, xRot ); + mCameraMatrix.getColumn( 1, &vecf ); // Forward vector + mCameraMatrix.getColumn( 2, &vecu ); // Up vector + mCameraMatrix.getColumn( 0, &vecr ); // Right vector + + Point3F flatVecf(vecf.x, vecf.y, 0.0f); + + Point3F modvecf = flatVecf * mOrbitRelPos.y; + Point3F modvecu = vecu * mOrbitRelPos.z; + Point3F modvecr = vecr * mOrbitRelPos.x; + + // Change the orbit position + mOrbitPos += modvecu - modvecr + modvecf; + + F32 vecfmul = mOrbitDist; + Point3F virtualVecF = vecf * mOrbitDist; + vecf *= vecfmul; + + mCameraPos = mOrbitPos - virtualVecF; + + // Update the camera's position + mCameraMatrix.setColumn( 3, (mOrbitPos - vecf) ); + + query->farPlane = 2100.0f; + query->nearPlane = query->farPlane / 5000.0f; + query->fov = 45.0f; + query->cameraMatrix = mCameraMatrix; + + // Reset the relative position + mOrbitRelPos = Point3F(0,0,0); + } + return true; +} + +void GuiMaterialPreview::onMouseEnter(const GuiEvent & event) +{ + Con::executef(this, "onMouseEnter"); +} + +void GuiMaterialPreview::onMouseLeave(const GuiEvent & event) +{ + Con::executef(this, "onMouseLeave"); +} + +void GuiMaterialPreview::renderWorld(const RectI &updateRect) +{ + // nothing to render, punt + if ( !mModel && !mMountedModel ) + return; + + S32 time = Platform::getVirtualMilliseconds(); + //S32 dt = time - lastRenderTime; + lastRenderTime = time; + + + + F32 left, right, top, bottom, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + Frustum frust( isOrtho, left, right, bottom, top, nearPlane, farPlane, MatrixF::Identity ); + + SceneState state( + NULL, + gClientSceneGraph, + SPT_Diffuse, + 1, + frust, + GFX->getViewport(), + false, + false ); + + // Set up our TS render state here. + TSRenderState rdata; + rdata.setSceneState( &state ); + + // Set up pass transforms + RenderPassManager *renderPass = state.getRenderPass(); + renderPass->assignSharedXform(RenderPassManager::View, MatrixF::Identity); + renderPass->assignSharedXform(RenderPassManager::Projection, GFX->getProjectionMatrix()); + + LightManager* lm = gClientSceneGraph->getLightManager(); + lm->unregisterAllLights(); + lm->setSpecialLight( LightManager::slSunLightType, mFakeSun ); + lm->setupLights(NULL, SphereF( Point3F::Zero, 1.0f ) ); + + if ( mModel ) + { + mModel->render( rdata ); + } + + if ( mMountedModel ) + { + // render a weapon + /* + MatrixF mat; + + GFX->pushWorldMatrix(); + GFX->multWorld( mat ); + + GFX->popWorldMatrix(); + */ + } + + gClientSceneGraph->getRenderPass()->renderPass( &state ); +} + +// Make sure the orbit distance is within the acceptable range. +void GuiMaterialPreview::setOrbitDistance(F32 distance) +{ + mOrbitDist = mClampF(distance, mMinOrbitDist, mMaxOrbitDist); +} + +// This function is meant to be used with a button to put everything back to default settings. +void GuiMaterialPreview::resetViewport() +{ + // Reset the camera's orientation. + mCameraRot.set( mDegToRad(30.0f), 0, mDegToRad(-30.0f) ); + mCameraPos.set(0.0f, 1.75f, 1.25f); + mOrbitDist = 5.0f; + mOrbitPos = mModel->getShape()->center; + + // Reset the viewport's lighting. + GuiMaterialPreview::mFakeSun->setColor( ColorF( 1.0f, 1.0f, 1.0f ) ); + GuiMaterialPreview::mFakeSun->setAmbient( ColorF( 0.5f, 0.5f, 0.5f ) ); + GuiMaterialPreview::mFakeSun->setDirection( VectorF( 0.0f, 0.707f, -0.707f ) ); +} + +// Expose the class and functions to the console. +IMPLEMENT_CONOBJECT(GuiMaterialPreview); + +// Set the model. +ConsoleMethod(GuiMaterialPreview, setModel, void, 3, 3, + "(string shapeName)\n" + "Sets the model to be displayed in this control\n\n" + "\\param shapeName Name of the model to display.\n") +{ + TORQUE_UNUSED(argc); + object->setObjectModel(argv[2]); +} + +ConsoleMethod(GuiMaterialPreview, deleteModel, void, 2, 2, + "()\n" + "Deletes the preview model.\n") +{ + TORQUE_UNUSED(argc); + object->deleteModel(); +} + + +// Set orbit distance around the model. +ConsoleMethod(GuiMaterialPreview, setOrbitDistance, void, 3, 3, + "(float distance)\n" + "Sets the distance at which the camera orbits the object. Clamped to the acceptable range defined in the class by min and max orbit distances.\n\n" + "\\param distance The distance to set the orbit to (will be clamped).") +{ + TORQUE_UNUSED(argc); + object->setOrbitDistance(dAtof(argv[2])); +} + +// Reset control to default values. Meant to be used with a button. +ConsoleMethod(GuiMaterialPreview, reset, void, 2, 2, "Resets the viewport to default zoom, pan, rotate and lighting.") +{ + TORQUE_UNUSED(argc); + object->resetViewport(); +} + +// This function allows the user to change the light's color. +ConsoleMethod(GuiMaterialPreview, setLightColor, void, 5, 5, "Usage: %obj.setLightColor(r,g,b) Sets the color of the light in the scene. \n") +{ + ColorF color( dAtof( argv[2] ) / 255.0f, + dAtof( argv[3] ) / 255.0f, + dAtof( argv[4] ) / 255.0f ); + color.clamp(); + + TORQUE_UNUSED( argc ); + object->setLightColor( color.red, color.green, color.blue ); +} + +// This function allows the user to change the viewports's ambient color. +ConsoleMethod(GuiMaterialPreview, setAmbientLightColor, void, 5, 5, "Usage: %obj.setAmbientLightColor(r,g,b) Sets the color of the ambient light in the scene. \n") +{ + ColorF color( dAtof( argv[2] ) / 255.0f, + dAtof( argv[3] ) / 255.0f, + dAtof( argv[4] ) / 255.0f ); + color.clamp(); + + TORQUE_UNUSED( argc ); + object->setAmbientLightColor( color.red, color.green, color.blue ); +} \ No newline at end of file diff --git a/T3D/guiMaterialPreview.h b/T3D/guiMaterialPreview.h new file mode 100644 index 0000000..cefe007 --- /dev/null +++ b/T3D/guiMaterialPreview.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// GuiMaterialPreview Control for Material Editor Written by Travis Vroman of Gaslight Studios +// Updated 2-14-09 +// Portions based off Constructor viewport code. +//----------------------------------------------------------------------------- + +#ifndef _GUIMATERIALPREVIEW_H_ +#define _GUIMATERIALPREVIEW_H_ + +#include "gui/3d/guiTSControl.h" +#include "ts/tsShapeInstance.h" + +class LightInfo; + +class GuiMaterialPreview : public GuiTSCtrl +{ +private: + typedef GuiTSCtrl Parent; + +protected: + enum MouseState + { + None, + Rotating, + Zooming, + Panning, + MovingLight + }; + + MouseState mMouseState; + + TSShapeInstance* mModel; + TSShapeInstance* mMountedModel; + U32 mSkinTag; + + // For Camera Panning. + F32 mTransStep; //*** Amount of translation with each mouse move + F32 mTranMult; //*** With a modifier, how much faster to translate + + // For light translation. + F32 mLightTransStep; + F32 mLightTranMult; + + Point3F mCameraPos; + MatrixF mCameraMatrix; + EulerF mCameraRot; + Point3F mOrbitPos; + Point3F mOrbitRelPos; + Point3F mCameraTransform; + + TSThread * runThread; + S32 lastRenderTime; + + Point2I mLastMousePoint; + + LightInfo* mFakeSun; + +public: + bool onWake(); + + void onMouseEnter(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onRightMouseDown(const GuiEvent &event); + void onRightMouseUp(const GuiEvent &event); + void onRightMouseDragged(const GuiEvent &event); + bool onMouseWheelUp(const GuiEvent &event); + bool onMouseWheelDown(const GuiEvent &event); + void onMiddleMouseUp(const GuiEvent &event); + void onMiddleMouseDown(const GuiEvent &event); + void onMiddleMouseDragged(const GuiEvent &event); + + // For Camera Panning. + void setTranslate(S32 modifier, F32 xstep, F32 ystep); + + // For Light Translation. + void setLightTranslate(S32 modifier, F32 xstep, F32 ystep); + + // For changing the light color. + void setLightColor( F32 r, F32 g, F32 b ); + + // For changing the ambient light color. + void setAmbientLightColor( F32 r, F32 g, F32 b ); + + void setObjectModel(const char * modelName); + void deleteModel(); + void resetViewport(); + void setOrbitDistance(F32 distance); + + bool processCameraQuery(CameraQuery *query); + void renderWorld(const RectI &updateRect); + + DECLARE_CONOBJECT(GuiMaterialPreview); + DECLARE_CATEGORY( "Gui Editor" ); + + GuiMaterialPreview(); + ~GuiMaterialPreview(); + +private: + F32 mMaxOrbitDist; + F32 mMinOrbitDist; + F32 mOrbitDist; + +}; + +#endif diff --git a/T3D/guiNoMouseCtrl.cpp b/T3D/guiNoMouseCtrl.cpp new file mode 100644 index 0000000..344c232 --- /dev/null +++ b/T3D/guiNoMouseCtrl.cpp @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/core/guiControl.h" + +//------------------------------------------------------------------------------ +class GuiNoMouseCtrl : public GuiControl +{ + typedef GuiControl Parent; + public: + + // GuiControl + bool pointInControl(const Point2I &) { return(false); } + DECLARE_CONOBJECT(GuiNoMouseCtrl); + DECLARE_CATEGORY( "Gui Other" ); +}; +IMPLEMENT_CONOBJECT(GuiNoMouseCtrl); diff --git a/T3D/guiObjectView.cpp b/T3D/guiObjectView.cpp new file mode 100644 index 0000000..9a110d2 --- /dev/null +++ b/T3D/guiObjectView.cpp @@ -0,0 +1,382 @@ +//------------------------------------------------------------------------------ +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//------------------------------------------------------------------------------ + +#include "T3D/guiObjectView.h" +#include "renderInstance/renderPassManager.h" +#include "lighting/lightManager.h" +#include "lighting/lightInfo.h" +#include "core/resourceManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" + +//----------------------------------------------------------------------------- +// GuiObjectView +//----------------------------------------------------------------------------- + +GuiObjectView::GuiObjectView() +: mMaxOrbitDist(5.0f), + mMinOrbitDist(0.0f), + mOrbitDist(5.0f), + mMouseState(None), + mModel(NULL), + mMountedModel(NULL), + mLastMousePoint(0, 0), + lastRenderTime(0), + runThread(0), + mMountNode(NO_NODE), + mAnimationSeq(0), + mFakeSun(NULL) +{ + mActive = true; + + // TODO: lots of hardcoded things in here + mCameraMatrix.identity(); + mCameraRot.set(0.0f, 0.0f, 3.9f); + mCameraPos.set(0.0f, 1.75f, 1.25f); + mCameraMatrix.setColumn(3, mCameraPos); + mOrbitPos.set(0.0f, 0.0f, 0.0f); + + // By default don't do dynamic reflection + // updates for this viewport. + mReflectPriority = 0.0f; +} + +GuiObjectView::~GuiObjectView() +{ + if( mModel ) + SAFE_DELETE( mModel ); + if( mMountedModel ) + SAFE_DELETE( mMountedModel ); + if( mFakeSun ) + SAFE_DELETE( mFakeSun ); +} + +bool GuiObjectView::onWake() +{ + if( !Parent::onWake() ) + return false; + + if ( !mFakeSun ) + mFakeSun = gClientSceneGraph->getLightManager()->createLightInfo(); + + mFakeSun->setColor( ColorF( 1.0f, 1.0f, 1.0f ) ); + mFakeSun->setAmbient( ColorF( 0.5f, 0.5f, 0.5f ) ); + mFakeSun->setDirection( VectorF( 0.f, 0.707f, -0.707f ) ); + + return(true); +} + +void GuiObjectView::onMouseDown(const GuiEvent &event) +{ + if (!mActive || !mVisible || !mAwake) + { + return; + } + + mMouseState = Rotating; + mLastMousePoint = event.mousePoint; + mouseLock(); +} + +void GuiObjectView::onMouseUp(const GuiEvent &event) +{ + mouseUnlock(); + mMouseState = None; +} + +void GuiObjectView::onMouseDragged(const GuiEvent &event) +{ + if (mMouseState != Rotating) + { + return; + } + + Point2I delta = event.mousePoint - mLastMousePoint; + mLastMousePoint = event.mousePoint; + + mCameraRot.x += (delta.y * 0.01f); + mCameraRot.z += (delta.x * 0.01f); +} + +void GuiObjectView::onRightMouseDown(const GuiEvent &event) +{ + mMouseState = Zooming; + mLastMousePoint = event.mousePoint; + mouseLock(); +} + +void GuiObjectView::onRightMouseUp(const GuiEvent &event) +{ + mouseUnlock(); + mMouseState = None; +} + +void GuiObjectView::onRightMouseDragged(const GuiEvent &event) +{ + if (mMouseState != Zooming) + { + return; + } + + S32 delta = event.mousePoint.y - mLastMousePoint.y; + mLastMousePoint = event.mousePoint; + + mOrbitDist += (delta * 0.01f); +} + +void GuiObjectView::setObjectAnimation(S32 index) +{ + if ((0 > index) || (index > MAX_ANIMATIONS)) + { + Con::warnf(avar("GuiObjectView: The index %d is outside the permissible range. Please specify an animation index in the range [0, %d]", index, MAX_ANIMATIONS)); + return; + } + + mAnimationSeq = index; +} + +void GuiObjectView::setObjectModel(const char* modelName) +{ + SAFE_DELETE(mModel); + + runThread = 0; + + Resource model = ResourceManager::get().load(modelName); + if (! bool(model)) + { + Con::warnf(avar("GuiObjectView: Failed to load model %s. Please check your model name and load a valid model.", modelName)); + return; + } + + mModel = new TSShapeInstance(model, true); + AssertFatal(mModel, avar("GuiObjectView: Failed to load model %s. Please check your model name and load a valid model.", modelName)); + + // Initialize camera values: + mOrbitPos = mModel->getShape()->center; + mMinOrbitDist = mModel->getShape()->radius; + + // the first time recording + lastRenderTime = Platform::getVirtualMilliseconds(); + + char * mountName = new char[16]; + dStrncpy(mountName, avar("mount%d", 0), 15); + mMountNode = mModel->getShape()->findNode(mountName); + delete [] mountName; + +} + +void GuiObjectView::setMountedObject(const char * modelName, S32 mountPoint) +{ + AssertFatal( mModel != NULL, "GuiObjectView::setMountedObject - model not set; can't mount to nothing" ); + SAFE_DELETE(mMountedModel); + + // create a weapon for this dude + Resource model = ResourceManager::get().load(modelName); + + if (! bool(model)) + { + Con::warnf(avar("GuiObjectView: Failed to load mounted object model %s. Please check your model name and load a valid model.", modelName)); + return; + } + + char * mountName = new char[16]; + dStrncpy(mountName, avar("mount%d", 0), 15); + mMountNode = mModel->getShape()->findNode(mountName); + delete [] mountName; + + + mMountedModel = new TSShapeInstance(model, true); + AssertFatal(mMountedModel, avar("GuiObjectView: Failed to load mounted object model %s. Please check your model name and load a valid model.", modelName)); +} + +void GuiObjectView::getMountedObjTransform(MatrixF * mat) +{ + if ((! mMountedModel) || (mMountNode == NO_NODE)) + { + // there is no mounted model or node to mount to + return; + } + + MatrixF mountedTrans; + mountedTrans.identity(); + + S32 mountPoint = mMountedModel->getShape()->findNode("mountPoint"); + if (mountPoint != -1) + mountedTrans = mMountedModel->mNodeTransforms[mountPoint]; + + Point3F mountedOffset = -mountedTrans.getPosition(); + MatrixF modelTrans = mModel->mNodeTransforms[mMountNode]; + modelTrans.mulP(mountedOffset); + modelTrans.setPosition(mountedOffset); + *mat = modelTrans; +} + +bool GuiObjectView::processCameraQuery(CameraQuery* query) +{ + // Adjust the camera so that we are still facing the model: + Point3F vec; + MatrixF xRot, zRot; + xRot.set(EulerF(mCameraRot.x, 0.0f, 0.0f)); + zRot.set(EulerF(0.0f, 0.0f, mCameraRot.z)); + + mCameraMatrix.mul(zRot, xRot); + mCameraMatrix.getColumn(1, &vec); + vec *= mOrbitDist; + mCameraPos = mOrbitPos - vec; + + query->farPlane = 2100.0f; + query->nearPlane = query->farPlane / 5000.0f; + query->fov = 45.0f; + mCameraMatrix.setColumn(3, mCameraPos); + query->cameraMatrix = mCameraMatrix; + + return true; +} + +void GuiObjectView::onMouseEnter(const GuiEvent & event) +{ + Con::executef(this, "onMouseEnter"); +} + +void GuiObjectView::onMouseLeave(const GuiEvent & event) +{ + Con::executef(this, "onMouseLeave"); +} + +void GuiObjectView::renderWorld(const RectI &updateRect) +{ + if ((! mModel) && (! mMountedModel)) + { + // nothing to render, punt + return; + } + + // Determine the camera position, and store off render state... + MatrixF modelview; + MatrixF mv; + Point3F cp; + + modelview = GFX->getWorldMatrix(); + + mv = modelview; + mv.inverse(); + mv.getColumn(3, &cp); + + RenderPassManager *renderPass = gClientSceneGraph->getRenderPass(); + + S32 time = Platform::getVirtualMilliseconds(); + //S32 dt = time - lastRenderTime; + lastRenderTime = time; + + LightManager* lm = gClientSceneGraph->getLightManager(); + lm->setSpecialLight(LightManager::slSunLightType, mFakeSun); + + GFX->setStateBlock(mDefaultGuiSB); + + F32 left, right, top, bottom, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + + Frustum frust( false, left, right, top, bottom, nearPlane, farPlane, MatrixF::Identity ); + + SceneState state( + NULL, + gClientSceneGraph, + SPT_Diffuse, + 1, + frust, + GFX->getViewport(), + false, + false); + + // Set up pass transforms + renderPass->assignSharedXform(RenderPassManager::View, MatrixF::Identity); + renderPass->assignSharedXform(RenderPassManager::Projection, GFX->getProjectionMatrix()); + + // Set up our TS render state here. + TSRenderState rdata; + rdata.setSceneState( &state ); + + if (mModel) + { + // animate and render in a run pose + //F32 fdt = dt; + // mModel->advanceTime( fdt/1000.f, runThread ); + // mModel->animate(); + mModel->render( rdata ); + } + + if (mMountedModel) + { + // render a weapon + MatrixF mat; + getMountedObjTransform(&mat); + + GFX->pushWorldMatrix(); + GFX->multWorld( mat ); + + mMountedModel->render( rdata ); + + GFX->popWorldMatrix(); + } + + renderPass->renderPass(&state); +} + +void GuiObjectView::setOrbitDistance(F32 distance) +{ + // Make sure the orbit distance is within the acceptable range + mOrbitDist = mClampF(distance, mMinOrbitDist, mMaxOrbitDist); +} + +//----------------------------------------------------------------------------- +// Console stuff (GuiObjectView) +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiObjectView); + +ConsoleMethod(GuiObjectView, setModel, void, 3, 3, + "(string shapeName)\n" + "Sets the model to be displayed in this control\n\n" + "\\param shapeName Name of the model to display.\n") +{ + TORQUE_UNUSED( argc ); + object->setObjectModel(argv[2]); +} + +ConsoleMethod(GuiObjectView, setSeq, void, 3, 3, + "(int index)\n" + "Sets the animation to play for the viewed object.\n\n" + "\\param index The index of the animation to play.") +{ + TORQUE_UNUSED( argc ); + object->setObjectAnimation(dAtoi(argv[2])); +} + +ConsoleMethod(GuiObjectView, setMount, void, 4, 4, + "(string shapeName, int mountPoint)\n" + "Mounts the given model to the specified mount point of the primary model displayed in this control.\n\n" + "\\param shapeName Name of the model to mount." + "\\param mountPoint Index of the mount point to be mounted to. Corresponds to \"mountPointN\" in your shape where N is the number passed here.") +{ + TORQUE_UNUSED( argc ); + + if( !object->getModel() ) + { + Con::errorf( "GuiObjectView::setMount - must set model first" ); + return; + } + + object->setMountedObject( argv[ 2 ], dAtoi( argv[ 3 ] ) ); +} + +ConsoleMethod(GuiObjectView, setOrbitDistance, void, 3, 3, + "(float distance)\n" + "Sets the distance at which the camera orbits the object. Clamped to the acceptable range defined in the class by min and max orbit distances.\n\n" + "\\param distance The distance to set the orbit to (will be clamped).") +{ + TORQUE_UNUSED( argc ); + object->setOrbitDistance(dAtof(argv[2])); +} diff --git a/T3D/guiObjectView.h b/T3D/guiObjectView.h new file mode 100644 index 0000000..8073ee0 --- /dev/null +++ b/T3D/guiObjectView.h @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIOBJECTVIEW_H_ +#define _GUIOBJECTVIEW_H_ + +#include "gui/3d/guiTSControl.h" +#include "ts/tsShapeInstance.h" + +class LightInfo; + +class GuiObjectView : public GuiTSCtrl +{ +private: + typedef GuiTSCtrl Parent; + +protected: + enum MouseState + { + None, + Rotating, + Zooming + }; + + MouseState mMouseState; + + TSShapeInstance* mModel; + TSShapeInstance* mMountedModel; + U32 mSkinTag; + + Point3F mCameraPos; + MatrixF mCameraMatrix; + EulerF mCameraRot; + Point3F mOrbitPos; + S32 mMountNode; + + TSThread * runThread; + S32 lastRenderTime; + S32 mAnimationSeq; + + Point2I mLastMousePoint; + + LightInfo* mFakeSun; + +public: + bool onWake(); + + void onMouseEnter(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onRightMouseDown(const GuiEvent &event); + void onRightMouseUp(const GuiEvent &event); + void onRightMouseDragged(const GuiEvent &event); + + TSShapeInstance* getModel(); + TSShapeInstance* getMountedModel(); + + void setObjectModel(const char * modelName); + void setObjectAnimation(S32 index); + void setMountedObject(const char * modelName, S32 mountPoint); + void getMountedObjTransform(MatrixF *mat); + + /// Sets the distance at which the camera orbits the object. Clamped to the + /// acceptable range defined in the class by min and max orbit distances. + /// + /// \param distance The distance to set the orbit to (will be clamped). + void setOrbitDistance(F32 distance); + + bool processCameraQuery(CameraQuery *query); + void renderWorld(const RectI &updateRect); + + DECLARE_CONOBJECT(GuiObjectView); + + GuiObjectView(); + ~GuiObjectView(); + +private: + F32 mMaxOrbitDist; + F32 mMinOrbitDist; + F32 mOrbitDist; + + static const S32 MAX_ANIMATIONS = 6; ///< Maximum number of animations for the primary model displayed in this control + static const S32 NO_NODE = -1; ///< Indicates there is no node with a mounted object +}; + +inline TSShapeInstance* GuiObjectView::getModel() +{ + return mModel; +} + +inline TSShapeInstance* GuiObjectView::getMountedModel() +{ + return mMountedModel; +} + +#endif diff --git a/T3D/hifi/gameProcess.cpp b/T3D/hifi/gameProcess.cpp new file mode 100644 index 0000000..fde71d5 --- /dev/null +++ b/T3D/hifi/gameProcess.cpp @@ -0,0 +1,685 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/dnet.h" +#include "core/bitStream.h" +#include "core/frameAllocator.h" +#include "math/mPoint.h" +#include "math/mMatrix.h" +#include "math/mathUtils.h" +#include "T3D/gameConnection.h" +#include "T3D/gameBase.h" +#include "T3D/gameProcess.h" +#include "platform/profiler.h" +#include "console/consoleTypes.h" + +//---------------------------------------------------------------------------- + +ClientProcessList gClientProcessList; +ServerProcessList gServerProcessList; +U32 gNetOrderNextId = 0; +F32 gMaxHiFiVelSq = 100 * 100; + +ConsoleFunction(dumpProcessList,void,1,1,"dumpProcessList();") +{ + Con::printf("client process list:"); + gClientProcessList.dumpToConsole(); + Con::printf("server process list:"); + gServerProcessList.dumpToConsole(); +} + +// Structure used for synchronizing move lists on client/server +struct MoveSync +{ + enum { ActionCount = 4 }; + + S32 moveDiff; + S32 moveDiffSteadyCount; + S32 moveDiffSameSignCount; + + bool doAction() { return moveDiffSteadyCount>=ActionCount || moveDiffSameSignCount>=4*ActionCount; } + void reset() { moveDiff=0; moveDiffSteadyCount=0; moveDiffSameSignCount=0; } + void update(S32 diff); +} moveSync; + +void MoveSync::update(S32 diff) +{ + if (diff && diff==moveDiff) + { + moveDiffSteadyCount++; + moveDiffSameSignCount++; + } + else if (diff*moveDiff>0) + { + moveDiffSteadyCount = 0; + moveDiffSameSignCount++; + } + else + reset(); + moveDiff = diff; +} + + +namespace +{ + // local work class + struct GameBaseListNode + { + GameBaseListNode() + { + mPrev=this; + mNext=this; + mObject=NULL; + } + + GameBaseListNode * mPrev; + GameBaseListNode * mNext; + GameBase * mObject; + + void linkBefore(GameBaseListNode * obj) + { + // Link this before obj + mNext = obj; + mPrev = obj->mPrev; + obj->mPrev = this; + mPrev->mNext = this; + } + }; +} // namespace + +//-------------------------------------------------------------------------- +// ClientProcessList +//-------------------------------------------------------------------------- + +ClientProcessList::ClientProcessList() +{ + mSkipAdvanceObjectsMs = 0; + mForceHifiReset = false; + mCatchup = 0; +} + +void ClientProcessList::addObject(ProcessObject * pobj) +{ + GameBase * obj = dynamic_cast(pobj); + if (obj==NULL) + // don't add + return; + AssertFatal(obj->isClientObject(),"Adding non-client object to client list"); + + if (obj->mNetFlags.test(GameBase::NetOrdered)) + { + obj->plLinkBefore(&mHead); + mDirty = true; + } + else if (obj->mNetFlags.test(GameBase::TickLast)) + { + obj->mOrderGUID = 0xFFFFFFFF; + obj->plLinkBefore(&mHead); + // not dirty + } + else + { + obj->plLinkAfter(&mHead); + // not dirty + } +} + +inline GameBase * ClientProcessList::getGameBase(ProcessObject * obj) +{ + return static_cast(obj); +} + +bool ClientProcessList::doBacklogged(SimTime timeDelta) +{ +#ifdef TORQUE_DEBUG + static bool backlogged = false; + static U32 backloggedTime = 0; +#endif + + // See if the control object has pending moves. + GameConnection* connection = GameConnection::getConnectionToServer(); + if (connection) + { + // If the connection to the server is backlogged + // the simulation is frozen. + if (connection->mMoveList.isBacklogged()) + { +#ifdef TORQUE_DEBUG + if (!backlogged) + { + Con::printf("client is backlogged, time is frozen"); + backlogged=true; + } + + backloggedTime += timeDelta; +#endif + return true; + } + } + +#ifdef TORQUE_DEBUG + if (backlogged) + { + Con::printf("client is no longer backlogged, time is unfrozen (%i ms elapsed)",backloggedTime); + backlogged=false; + backloggedTime=0; + } +#endif + return false; +} + +bool ClientProcessList::advanceTime(SimTime timeDelta) +{ + PROFILE_SCOPE(AdvanceClientTime); + + if (mSkipAdvanceObjectsMs && timeDelta>mSkipAdvanceObjectsMs) + { + timeDelta -= mSkipAdvanceObjectsMs; + advanceTime(mSkipAdvanceObjectsMs); + AssertFatal(!mSkipAdvanceObjectsMs,"Doh!"); + } + + if (doBacklogged(timeDelta)) + return false; + + // remember interpolation value because we might need to set it back + F32 oldLastDelta = mLastDelta; + + bool ret = Parent::advanceTime(timeDelta); + + if (!mSkipAdvanceObjectsMs) + { + AssertFatal(mLastDelta>=0.0f && mLastDelta<=1.0f,"Doh! That would be bad."); + for (ProcessObject * obj = mHead.mProcessLink.next; obj != &mHead; obj = obj->mProcessLink.next) + { + GameBase * gb = getGameBase(obj); + if (gb->mProcessTick) + gb->interpolateTick(mLastDelta); + } + + // Inform objects of total elapsed delta so they can advance + // client side animations. + F32 dt = F32(timeDelta) / 1000; + for (ProcessObject * obj = mHead.mProcessLink.next; obj != &mHead; obj = obj->mProcessLink.next) + { + GameBase * gb = getGameBase(obj); + gb->advanceTime(dt); + } + } + else + { + mSkipAdvanceObjectsMs -= timeDelta; + mLastDelta = oldLastDelta; + } + + return ret; +} + + +//---------------------------------------------------------------------------- +void ClientProcessList::onAdvanceObjects() +{ + GameConnection* connection = GameConnection::getConnectionToServer(); + if(connection) + { + // process any demo blocks that are NOT moves, and exactly one move + // we advance time in the demo stream by a move inserted on + // each tick. So before doing the tick processing we advance + // the demo stream until a move is ready + if(connection->isPlayingBack()) + { + U32 blockType; + do + { + blockType = connection->getNextBlockType(); + bool res = connection->processNextBlock(); + // if there are no more blocks, exit out of this function, + // as no more client time needs to process right now - we'll + // get it all on the next advanceClientTime() + if(!res) + return; + } + while(blockType != GameConnection::BlockTypeMove); + } + if (!mSkipAdvanceObjectsMs) + { + connection->mMoveList.collectMove(); + advanceObjects(); + } + connection->mMoveList.onAdvanceObjects(); + } +} + +void ClientProcessList::onTickObject(ProcessObject * pobj) +{ + SimObjectPtr obj = getGameBase(pobj); + AssertFatal(obj->isClientObject(),"Server object on client process list"); + + // Each object is advanced a single tick + // If it's controlled by a client, tick using a move. + Move* movePtr; + U32 numMoves; + GameConnection* con = obj->getControllingClient(); + if (con && con->getControlObject() == obj && con->mMoveList.getMoveList(&movePtr, &numMoves)) + { +#ifdef TORQUE_DEBUG_NET_MOVES + U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); +#endif + + obj->processTick(movePtr); + + if (bool(obj) && obj->getControllingClient()) + { + U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); + + // set checksum if not set or check against stored value if set + movePtr->checksum = newsum; + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("move checksum: %i, (start %i), (move %f %f %f)", + movePtr->checksum,sum,movePtr->yaw,movePtr->y,movePtr->z); +#endif + } + con->mMoveList.clearMoves(1); + } + else if (obj->mProcessTick) + obj->processTick(0); + + if (bool(obj) && (obj->getType() & GameBaseHiFiObjectType)) + { + GameConnection * serverConnection = GameConnection::getConnectionToServer(); + TickCacheEntry * tce = obj->getTickCache().addCacheEntry(); + BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); + obj->writePacketData(serverConnection,&bs); + + Point3F vel = obj->getVelocity(); + F32 velSq = mDot(vel,vel); + gMaxHiFiVelSq = getMax(gMaxHiFiVelSq,velSq); + } +} + +void ClientProcessList::advanceObjects() +{ +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("Advance client time..."); +#endif + + // client re-computes this each time objects are advanced + gMaxHiFiVelSq = 0; + Parent::advanceObjects(); + + // We need to consume a move on the connections whether + // there is a control object to consume the move or not, + // otherwise client and server can get out of sync move-wise + // during startup. If there is a control object, we cleared + // a move above. Handle case where no control object here. + // Note that we might consume an extra move here and there when + // we had a control object in above loop but lost it during tick. + // That is no big deal so we don't bother trying to carefully + // track it. + if (GameConnection::getConnectionToServer()->getControlObject() == NULL) + GameConnection::getConnectionToServer()->mMoveList.clearMoves(1); + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("---------"); +#endif +} + +void ClientProcessList::ageTickCache(S32 numToAge, S32 len) +{ + for (ProcessObject * pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) + { + GameBase * obj = getGameBase(pobj); + if (obj->getType() & GameBaseHiFiObjectType) + obj->getTickCache().ageCache(numToAge,len); + } +} + +void ClientProcessList::updateMoveSync(S32 moveDiff) +{ + moveSync.update(moveDiff); + if (moveSync.doAction() && moveDiff<0) + { + skipAdvanceObjects(TickMs * -moveDiff); + moveSync.reset(); + } +} + +void ClientProcessList::clientCatchup(GameConnection * connection) +{ +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("client catching up... (%i)%s", mCatchup, mForceHifiReset ? " reset" : ""); +#endif + + if (connection->getControlObject() && connection->getControlObject()->isGhostUpdated()) + // if control object is reset, make sure moves are reset too + connection->mMoveList.resetCatchup(); + + const F32 maxVel = mSqrt(gMaxHiFiVelSq) * 1.25f; + F32 dt = F32(mCatchup+1) * TickSec; + Point3F bigDelta(maxVel*dt,maxVel*dt,maxVel*dt); + + // walk through all process objects looking for ones which were updated + // -- during first pass merely collect neighbors which need to be reset and updated in unison + ProcessObject * pobj; + if (mCatchup && !mForceHifiReset) + { + for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) + { + GameBase * obj = getGameBase(pobj); + static SimpleQueryList nearby; + nearby.mList.clear(); + // check for nearby objects which need to be reset and then caught up + // note the funky loop logic -- first time through obj is us, then + // we start iterating through nearby list (to look for objects nearby + // the nearby objects), which is why index starts at -1 + // [objects nearby the nearby objects also get added to the nearby list] + for (S32 i=-1; obj; obj = ++iisGhostUpdated() && (obj->getType() & GameBaseHiFiObjectType) && !obj->mNetFlags.test(GameBase::NetNearbyAdded)) + { + Point3F start = obj->getWorldSphere().center; + Point3F end = start + 1.1f * dt * obj->getVelocity(); + F32 rad = 1.5f * obj->getWorldSphere().radius; + + // find nearby items not updated but are hi fi, mark them as updated (and restore old loc) + // check to see if added items have neighbors that need updating + Box3F box; + Point3F rads(rad,rad,rad); + box.min = box.max = start; + box.min -= bigDelta + rads; + box.max += bigDelta + rads; + + // CodeReview - this is left in for MBU, but also so we can deal with the issue later. + // add marble blast hack so hifi networking can see hidden objects + // (since hidden is under control of hifi networking) + // gForceNotHidden = true; + + S32 j = nearby.mList.size(); + gClientContainer.findObjects(box, GameBaseHiFiObjectType, SimpleQueryList::insertionCallback, &nearby); + + // CodeReview - this is left in for MBU, but also so we can deal with the issue later. + // disable above hack + // gForceNotHidden = false; + + // drop anyone not heading toward us or already checked + for (; jmNetFlags.test(GameBase::HiFiPassive) && obj2->mNetFlags.test(GameBase::HiFiPassive); + if (!obj2->isGhostUpdated() && !passive) + { + // compare swept spheres of obj and obj2 + // if collide, reset obj2, setGhostUpdated(true), and continue + Point3F end2 = obj2->getWorldSphere().center; + Point3F start2 = end2 - 1.1f * dt * obj2->getVelocity(); + F32 rad2 = 1.5f * obj->getWorldSphere().radius; + if (MathUtils::capsuleCapsuleOverlap(start,end,rad,start2,end2,rad2)) + { + // better add obj2 + obj2->getTickCache().beginCacheList(); + TickCacheEntry * tce = obj2->getTickCache().incCacheList(); + BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); + obj2->readPacketData(connection,&bs); + obj2->setGhostUpdated(true); + + // continue so we later add the neighbors too + continue; + } + + } + + // didn't pass above test...so don't add it or nearby objects + nearby.mList[j] = nearby.mList.last(); + nearby.mList.decrement(); + j--; + } + obj->mNetFlags.set(GameBase::NetNearbyAdded); + } + } + } + } + + // save water mark -- for game base list + FrameAllocatorMarker mark; + + // build ordered list of client objects which need to be caught up + GameBaseListNode list; + for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) + { + GameBase * obj = getGameBase(pobj); + + if (obj->isGhostUpdated() && (obj->getType() & GameBaseHiFiObjectType)) + { + // construct process object and add it to the list + // hold pointer to our object in mAfterObject + GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode)); + po->mObject = obj; + po->linkBefore(&list); + + // begin iterating through tick list (skip first tick since that is the state we've been reset to) + obj->getTickCache().beginCacheList(); + obj->getTickCache().incCacheList(); + } + else if (mForceHifiReset && (obj->getType() & GameBaseHiFiObjectType)) + { + // add all hifi objects + obj->getTickCache().beginCacheList(); + TickCacheEntry * tce = obj->getTickCache().incCacheList(); + BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); + obj->readPacketData(connection,&bs); + obj->setGhostUpdated(true); + + // construct process object and add it to the list + // hold pointer to our object in mAfterObject + GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode)); + po->mObject = obj; + po->linkBefore(&list); + } + else if (obj == connection->getControlObject() && obj->isGhostUpdated()) + { + // construct process object and add it to the list + // hold pointer to our object in mAfterObject + // .. but this is not a hi fi object, so don't mess with tick cache + GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode)); + po->mObject = obj; + po->linkBefore(&list); + } + else if (obj->isGhostUpdated()) + { + // not hifi but we were updated, so perform net smooth now + obj->computeNetSmooth(mLastDelta); + } + + // clear out work flags + obj->mNetFlags.clear(GameBase::NetNearbyAdded); + obj->setGhostUpdated(false); + } + + // run through all the moves in the move list so we can play them with our control object + Move* movePtr; + U32 numMoves; + connection->mMoveList.resetClientMoves(); + connection->mMoveList.getMoveList(&movePtr, &numMoves); + AssertFatal(mCatchup<=numMoves,"doh"); + + // tick catchup time + for (U32 m=0; mmNext) + { + // note that we get object from after object not getGameBase function + // this is because we are an on the fly linked list which uses mAfterObject + // rather than the linked list embedded in GameBase (clean this up?) + GameBase * obj = walk->mObject; + + // it's possible for a non-hifi object to get in here, but + // only if it is a control object...make sure we don't do any + // of the tick cache stuff if we are not hifi. + bool hifi = obj->getType() & GameBaseHiFiObjectType; + TickCacheEntry * tce = hifi ? obj->getTickCache().incCacheList() : NULL; + + // tick object + if (obj==connection->getControlObject()) + { + obj->processTick(movePtr); + movePtr->checksum = obj->getPacketDataChecksum(connection); + movePtr++; + } + else + { + AssertFatal(tce && hifi,"Should not get in here unless a hi fi object!!!"); + obj->processTick(tce->move); + } + + if (hifi) + { + BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); + obj->writePacketData(connection,&bs); + } + } + if (connection->getControlObject() == NULL) + movePtr++; + } + connection->mMoveList.clearMoves(mCatchup); + + // Handle network error smoothing here...but only for control object + GameBase * control = connection->getControlObject(); + if (control && !control->isNewGhost()) + { + control->computeNetSmooth(mLastDelta); + control->setNewGhost(false); + } + + if (moveSync.doAction() && moveSync.moveDiff>0) + { + S32 moveDiff = moveSync.moveDiff; +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("client timewarping to catchup %i moves",moveDiff); +#endif + while (moveDiff--) + advanceObjects(); + moveSync.reset(); + } + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("---------"); +#endif + + // all caught up + mCatchup = 0; +} + +//-------------------------------------------------------------------------- +// ServerProcessList +//-------------------------------------------------------------------------- + +ServerProcessList::ServerProcessList() +{ +} + +void ServerProcessList::addObject(ProcessObject * pobj) +{ + GameBase * obj = dynamic_cast(pobj); + if (obj==NULL) + // don't add + return; + AssertFatal(obj->isServerObject(),"Adding client object to server list"); + + if (obj->mNetFlags.test(GameBase::NetOrdered)) + { + if ((gNetOrderNextId & 0xFFFF) == 0) + // don't let it be zero + gNetOrderNextId++; + obj->mOrderGUID = (gNetOrderNextId++) & 0xFFFF; // 16 bits should be enough + obj->plLinkBefore(&mHead); + mDirty = true; + } + else if (obj->mNetFlags.test(GameBase::TickLast)) + { + obj->mOrderGUID = 0xFFFFFFFF; + obj->plLinkBefore(&mHead); + // not dirty + } + else + { + obj->plLinkAfter(&mHead); + // not dirty + } +} + +inline GameBase * ServerProcessList::getGameBase(ProcessObject * obj) +{ + return static_cast(obj); +} + +void ServerProcessList::onTickObject(ProcessObject * pobj) +{ + SimObjectPtr obj = getGameBase(pobj); + + // Each object is advanced a single tick + // If it's controlled by a client, tick using a move. + Move* movePtr; + U32 numMoves; + GameConnection* con = obj->getControllingClient(); + if (con && con->getControlObject() == obj && con->mMoveList.getMoveList(&movePtr, &numMoves)) + { +#ifdef TORQUE_DEBUG_NET_MOVES + U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); +#endif + + obj->processTick(movePtr); + + if (bool(obj) && obj->getControllingClient()) + { + U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient()); + + // check move checksum + if (movePtr->checksum != newsum) + { +#ifdef TORQUE_DEBUG_NET_MOVES + if (!obj->mIsAiControlled) + Con::printf("move %i checksum disagree: %i != %i, (start %i), (move %f %f %f)", + movePtr->id, movePtr->checksum,newsum,sum,movePtr->yaw,movePtr->y,movePtr->z); +#endif + movePtr->checksum = Move::ChecksumMismatch; + } + else + { +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("move %i checksum agree: %i == %i, (start %i), (move %f %f %f)", + movePtr->id, movePtr->checksum,newsum,sum,movePtr->yaw,movePtr->y,movePtr->z); +#endif + } + } + } + else if (obj->mProcessTick) + obj->processTick(0); +} + +void ServerProcessList::advanceObjects() +{ +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("Advance server time..."); +#endif + + Parent::advanceObjects(); + + // We need to consume a move on the connections whether + // there is a control object to consume the move or not, + // otherwise client and server can get out of sync move-wise + // during startup. + for (S32 i=0; isize(); i++) + { + GameConnection * con = (GameConnection*)(*Sim::getClientGroup())[i]; + if (con->mMoveList.areMovesPending()) + con->mMoveList.clearMoves(1); + } + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("---------"); +#endif +} \ No newline at end of file diff --git a/T3D/hifi/gameProcess.h b/T3D/hifi/gameProcess.h new file mode 100644 index 0000000..8c87ded --- /dev/null +++ b/T3D/hifi/gameProcess.h @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _APP_GAMEPROCESS_H_ +#define _APP_GAMEPROCESS_H_ + +#include "platform/platform.h" +#include "sim/processList.h" + +class GameBase; +class GameConnection; +struct Move; + +//---------------------------------------------------------------------------- + +/// List to keep track of GameBases to process. +class ClientProcessList : public ProcessList +{ + typedef ProcessList Parent; + U32 mSkipAdvanceObjectsMs; + bool mForceHifiReset; + U32 mCatchup; + +protected: + + void onTickObject(ProcessObject *); + void advanceObjects(); + void onAdvanceObjects(); + bool doBacklogged(SimTime timeDelta); + GameBase * getGameBase(ProcessObject * obj); + + +public: + ClientProcessList(); + + void addObject(ProcessObject * obj); + + bool advanceTime(SimTime timeDelta); + + /// @} + // after update from server, catch back up to where we were + void clientCatchup(GameConnection*); + void setCatchup(U32 catchup) { mCatchup = catchup; } + + // tick cache functions -- client only + void ageTickCache(S32 numToAge, S32 len); + void forceHifiReset(bool reset) { mForceHifiReset=reset; } + U32 getTotalTicks() { return mTotalTicks; } + void updateMoveSync(S32 moveDiff); + void skipAdvanceObjects(U32 ms) { mSkipAdvanceObjectsMs += ms; } +}; + +class ServerProcessList : public ProcessList +{ + typedef ProcessList Parent; + +protected: + + void onTickObject(ProcessObject *); + void advanceObjects(); + GameBase * getGameBase(ProcessObject * obj); + +public: + ServerProcessList(); + + void addObject(ProcessObject * obj); +}; + +extern ClientProcessList gClientProcessList; +extern ServerProcessList gServerProcessList; + +#endif \ No newline at end of file diff --git a/T3D/hifi/moveList.cpp b/T3D/hifi/moveList.cpp new file mode 100644 index 0000000..c45abcc --- /dev/null +++ b/T3D/hifi/moveList.cpp @@ -0,0 +1,553 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/moveList.h" +#include "T3D/gameConnection.h" +#include "core/bitStream.h" +#include "T3D/gameBase.h" +#include "T3D/gameProcess.h" + +#define MAX_MOVE_PACKET_SENDS 4 + +const U32 DefaultTargetMoveListSize = 3; +const U32 DefaultMaxMoveSizeList = 5; +const F32 DefaultSmoothMoveAvg = 0.15f; +const F32 DefaultMoveListSizeSlack = 1.0f; + +MoveList::MoveList() +{ + mLastMoveAck = 0; + mLastClientMove = 0; + mFirstMoveIndex = 0; + mLastSentMove = 0; + mAvgMoveQueueSize = DefaultTargetMoveListSize ; + mTargetMoveListSize = DefaultTargetMoveListSize; + mMaxMoveListSize = DefaultMaxMoveSizeList; + mSmoothMoveAvg = DefaultSmoothMoveAvg; + mMoveListSizeSlack = DefaultMoveListSizeSlack; + mTotalServerTicks = ServerTicksUninitialized; + mControlMismatch = false; + mConnection = NULL; +} + +void MoveList::updateClientServerTickDiff(S32 & tickDiff) +{ + if (mLastMoveAck==0) + tickDiff=0; + + // Make adjustments to move list to account for tick mis-matches between client and server. + if (tickDiff>0) + { + // Server ticked more than client. Adjust for this by reseting all hifi objects + // to a later position in the tick cache (see ageTickCache below) and at the same + // time pulling back some moves we thought we had made (so that time on client + // doesn't change). + S32 dropTicks = tickDiff; + while (dropTicks) + { +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("dropping move%s",mLastClientMove>mFirstMoveIndex ? "" : " but none there"); +#endif + if (mLastClientMove>mFirstMoveIndex) + mLastClientMove--; + else + tickDiff--; + dropTicks--; + } + AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request"); + AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveList.size(), "Desynched first and last move."); + } + else + { + // Client ticked more than server. Adjust for this by taking extra moves + // (either adding back moves that were dropped above, or taking new ones). + for (S32 i=0; i<-tickDiff; i++) + { + if (mMoveList.size() > mLastClientMove - mFirstMoveIndex) + { +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("add back move"); +#endif + mLastClientMove++; + } + else + { +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("add back move -- create one"); +#endif + collectMove(); + mLastClientMove++; + } + } + } + + // drop moves that are not made yet (because we rolled them back) and not yet sent + U32 len = getMax(mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex); + mMoveList.setSize(len); + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("move list size: %i, last move: %i, last sent: %i",mMoveList.size(),mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex); +#endif +} + +S32 MoveList::getServerTicks(U32 serverTickNum) +{ + S32 serverTicks=0; + if (serverTicksInitialized()) + { + // handle tick wrapping... + const S32 MaxTickCount = (1<>1; + U32 prevTickNum = mTotalServerTicks & TotalTicksMask; + serverTicks = serverTickNum-prevTickNum; + if (serverTicks>HalfMaxTickCount) + serverTicks -= MaxTickCount; + else if (-serverTicks>HalfMaxTickCount) + serverTicks += MaxTickCount; + AssertFatal(serverTicks>=0,"Server can't tick backwards!!!"); + if (serverTicks<0) + serverTicks=0; + } + mTotalServerTicks = serverTickNum; + return serverTicks; +} + +void MoveList::markControlDirty() +{ + mLastClientMove = mLastMoveAck; + + // save state for future update + GameBase * obj = mConnection->getControlObject(); + AssertFatal(obj,"ClientProcessList::markControlDirty: no control object"); + obj->setGhostUpdated(true); + obj->getTickCache().beginCacheList(); + TickCacheEntry * tce = obj->getTickCache().incCacheList(); + BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); + obj->writePacketData(mConnection,&bs); +} + +void MoveList::resetMoveList() +{ + mMoveList.clear(); + mLastMoveAck = 0; + mLastClientMove = 0; + mFirstMoveIndex = 0; + mLastSentMove = 0; +} + +bool MoveList::getNextMove(Move &curMove) +{ + if(mMoveList.size() > MaxMoveQueueSize) + return false; + + F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed; + F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed; + F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed; + + curMove.pitch = MoveManager::mPitch + pitchAdd; + curMove.yaw = MoveManager::mYaw + yawAdd; + curMove.roll = MoveManager::mRoll + rollAdd; + + MoveManager::mPitch = 0; + MoveManager::mYaw = 0; + MoveManager::mRoll = 0; + + curMove.x = MoveManager::mRightAction - MoveManager::mLeftAction + MoveManager::mXAxis_L; + curMove.y = MoveManager::mForwardAction - MoveManager::mBackwardAction + MoveManager::mYAxis_L; + curMove.z = MoveManager::mUpAction - MoveManager::mDownAction; + + curMove.freeLook = MoveManager::mFreeLook; + curMove.deviceIsKeyboardMouse = MoveManager::mDeviceIsKeyboardMouse; + + for(U32 i = 0; i < MaxTriggerKeys; i++) + { + curMove.trigger[i] = false; + if(MoveManager::mTriggerCount[i] & 1) + curMove.trigger[i] = true; + else if(!(MoveManager::mPrevTriggerCount[i] & 1) && MoveManager::mPrevTriggerCount[i] != MoveManager::mTriggerCount[i]) + curMove.trigger[i] = true; + MoveManager::mPrevTriggerCount[i] = MoveManager::mTriggerCount[i]; + } + + if (mConnection->getControlObject()) + mConnection->getControlObject()->preprocessMove(&curMove); + + curMove.clamp(); // clamp for net traffic + return true; +} + +void MoveList::pushMove(const Move &mv) +{ + U32 id = mFirstMoveIndex + mMoveList.size(); + U32 sz = mMoveList.size(); + mMoveList.push_back(mv); + mMoveList[sz].id = id; + mMoveList[sz].sendCount = 0; +} + +U32 MoveList::getMoveList(Move** movePtr,U32* numMoves) +{ + if (mConnection->isConnectionToServer()) + { + // give back moves starting at the last client move... + + AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request"); + AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveList.size(), "Desynched first and last move."); + *numMoves = mMoveList.size() - mLastClientMove + mFirstMoveIndex; + *movePtr = mMoveList.address() + mLastClientMove - mFirstMoveIndex; + } + else + { + // On the server we keep our own move list. + *numMoves = mMoveList.size(); + mAvgMoveQueueSize *= (1.0f-mSmoothMoveAvg); + mAvgMoveQueueSize += mSmoothMoveAvg * F32(*numMoves); + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("moves remaining: %i, running avg: %f",*numMoves,mAvgMoveQueueSize); +#endif + + if (mAvgMoveQueueSizemMaxMoveListSize || (mAvgMoveQueueSize>mTargetMoveListSize+mMoveListSizeSlack && mMoveList.size()>mTargetMoveListSize) ) + { + U32 drop = mMoveList.size()-mTargetMoveListSize; + clearMoves(drop); + mAvgMoveQueueSize = (F32)mTargetMoveListSize; + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("too many moves on server, dropping moves (%i)",drop); +#endif + } + + *movePtr = mMoveList.begin(); + } + + return *numMoves; +} + +void MoveList::collectMove() +{ + Move mv; + if(!mConnection->isPlayingBack() && getNextMove(mv)) + { + mv.checksum=Move::ChecksumMismatch; + pushMove(mv); + mConnection->recordBlock(GameConnection::BlockTypeMove, sizeof(Move), &mv); + } +} + +void MoveList::clearMoves(U32 count) +{ + if (mConnection->isConnectionToServer()) + { + mLastClientMove += count; + AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request"); + AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveList.size(), "Desynched first and last move."); + } + else + { + AssertFatal(count <= mMoveList.size(),"GameConnection: Clearing too many moves"); + for (S32 i=0; iisConnectionToServer() ? + mMoveList.size() - mLastClientMove + mFirstMoveIndex : + mMoveList.size(); +} + +bool MoveList::isBacklogged() +{ + // If there are no pending moves and the input queue is full, + // then the connection to the server must be clogged. + if(!mConnection->isConnectionToServer()) + return false; + return mLastClientMove - mFirstMoveIndex == mMoveList.size() && + mMoveList.size() >= MaxMoveCount; +} + + +void MoveList::clientWriteMovePacket(BitStream *bstream) +{ + if (!serverTicksInitialized()) + resetMoveList(); + + AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index."); + + // enforce limit on number of moves sent + if (mLastSentMove MaxMoveCount) + count = MaxMoveCount; + bstream->writeInt(start,32); + bstream->writeInt(count,MoveCountBits); + Move * prevMove = NULL; + for (int i = 0; i < count; i++) + { + move[offset + i].sendCount++; + move[offset + i].pack(bstream,prevMove); + bstream->writeInt(move[offset + i].checksum,Move::ChecksumBits); + prevMove = &move[offset+i]; + } +} + +void MoveList::serverReadMovePacket(BitStream *bstream) +{ + // Server side packet read. + U32 start = bstream->readInt(32); + U32 count = bstream->readInt(MoveCountBits); + + Move * prevMove = NULL; + Move prevMoveHolder; + + // Skip forward (must be starting up), or over the moves + // we already have. + int skip = mLastMoveAck - start; + if (skip < 0) + { + mLastMoveAck = start; + } + else + { + if (skip > count) + skip = count; + for (int i = 0; i < skip; i++) + { + prevMoveHolder.unpack(bstream,prevMove); + prevMoveHolder.checksum = bstream->readInt(Move::ChecksumBits); + prevMove = &prevMoveHolder; + S32 idx = mMoveList.size()-skip+i; + if (idx>=0) + { +#ifdef TORQUE_DEBUG_NET_MOVES + if (mMoveList[idx].checksum != prevMoveHolder.checksum) + Con::printf("updated checksum on move %i from %i to %i",mMoveList[idx].id,mMoveList[idx].checksum,prevMoveHolder.checksum); +#endif + mMoveList[idx].checksum = prevMoveHolder.checksum; + } + } + start += skip; + count = count - skip; + } + + // Put the rest on the move list. + int index = mMoveList.size(); + mMoveList.increment(count); + while (index < mMoveList.size()) + { + mMoveList[index].unpack(bstream,prevMove); + mMoveList[index].checksum = bstream->readInt(Move::ChecksumBits); + prevMove = &mMoveList[index]; + mMoveList[index].id = start++; + index ++; + } + + mLastMoveAck += count; + + if (mMoveList.size()>mMaxMoveListSize) + { + U32 drop = mMoveList.size()-mTargetMoveListSize; + clearMoves(drop); + mAvgMoveQueueSize = (F32)mTargetMoveListSize; + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("too many moves on server, dropping moves (%i)",drop); +#endif + } +} + +void MoveList::writeDemoStartBlock(ResizeBitStream *stream) +{ + stream->write(mLastMoveAck); + stream->write(mLastClientMove); + stream->write(mFirstMoveIndex); + + stream->write(U32(mMoveList.size())); + for(U32 j = 0; j < mMoveList.size(); j++) + mMoveList[j].pack(stream); +} + +void MoveList::readDemoStartBlock(BitStream *stream) +{ + stream->read(&mLastMoveAck); + stream->read(&mLastClientMove); + stream->read(&mFirstMoveIndex); + + U32 size; + Move mv; + stream->read(&size); + mMoveList.clear(); + while(size--) + { + mv.unpack(stream); + pushMove(mv); + } +} + +void MoveList::serverWriteMovePacket(BitStream * bstream) +{ +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("ack %i minus %i",mLastMoveAck,mMoveList.size()); +#endif + + // acknowledge only those moves that have been ticked + bstream->writeInt(mLastMoveAck - mMoveList.size(),32); + + // send over the current tick count on the server... + bstream->writeInt(gServerProcessList.getTotalTicks() & TotalTicksMask, TotalTicksBits); +} + +void MoveList::clientReadMovePacket(BitStream * bstream) +{ + if (!serverTicksInitialized()) + resetMoveList(); + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("pre move ack: %i", mLastMoveAck); +#endif + + mLastMoveAck = bstream->readInt(32); + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("post move ack %i, first move %i, last move %i", mLastMoveAck, mFirstMoveIndex, mLastClientMove); +#endif + + // This is how many times we've ticked since last ack -- before adjustments below + S32 ourTicks = mLastMoveAck - mFirstMoveIndex; + + if (mLastMoveAck < mFirstMoveIndex) + mLastMoveAck = mFirstMoveIndex; + + if(mLastMoveAck > mLastClientMove) + { + ourTicks -= mLastMoveAck-mLastClientMove; + mLastClientMove = mLastMoveAck; + } + while(mFirstMoveIndex < mLastMoveAck) + { + if (mMoveList.size()) + { + mMoveList.pop_front(); + mFirstMoveIndex++; + } + else + { + AssertWarn(1, "Popping off too many moves!"); + mFirstMoveIndex = mLastMoveAck; + } + } + + // get server ticks using total number of ticks on server to date... + U32 serverTickNum = bstream->readInt(TotalTicksBits); + S32 serverTicks = getServerTicks(serverTickNum); + S32 tickDiff = serverTicks - ourTicks; + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("server ticks: %i, client ticks: %i, diff: %i%s", serverTicks, ourTicks, tickDiff, !tickDiff ? "" : " (ticks mis-match)"); +#endif + + + // Apply the first (of two) client-side synchronization mechanisms. Key is that + // we need to both synchronize client/server move streams (so first move in list is made + // at same "time" on both client and server) and maintain the "time" at which the most + // recent move was made on the server. In both cases, "time" is the number of ticks + // it took to get to that move. + updateClientServerTickDiff(tickDiff); + + // Apply the second (and final) client-side synchronization mechanism. The tickDiff adjustments above + // make sure time is preserved on client. But that assumes that a future (or previous) update will adjust + // time in the other direction, so that we don't get too far behind or ahead of the server. The updateMoveSync + // mechanism tracks us over time to make sure we eventually return to be in sync, and makes adjustments + // if we don't after a certain time period (number of updates). Unlike the tickDiff mechanism, when + // the updateMoveSync acts time is not preserved on the client. + gClientProcessList.updateMoveSync(mLastSentMove-mLastClientMove); + + // set catchup parameters... + U32 totalCatchup = mLastClientMove - mFirstMoveIndex; + + gClientProcessList.ageTickCache(ourTicks + (tickDiff>0 ? tickDiff : 0), totalCatchup+1); + gClientProcessList.forceHifiReset(tickDiff!=0); + gClientProcessList.setCatchup(totalCatchup); +} + +void MoveList::ghostPreRead(NetObject * nobj, bool newGhost) +{ + if ( (nobj->getType() & GameBaseHiFiObjectType) && !newGhost) + { + AssertFatal(dynamic_cast(nobj),"Should be a gamebase"); + GameBase * obj = static_cast(nobj); + + // set next cache entry to start + obj->getTickCache().beginCacheList(); + + // reset to old state because we are about to unpack (and then tick forward) + TickCacheEntry * tce = obj->getTickCache().incCacheList(false); + if (tce) + { + BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); + obj->readPacketData(mConnection, &bs); + } + } +} + +void MoveList::ghostReadExtra(NetObject * nobj, BitStream * bstream, bool newGhost) +{ + // Receive additional per ghost information. + // Get pending moves for ghosts that have them and add the moves to + // the tick cache. + if (nobj->getType() & GameBaseHiFiObjectType) + { + AssertFatal(dynamic_cast(nobj),"Should be a gamebase"); + GameBase * obj = static_cast(nobj); + + // mark ghost so that it updates correctly + obj->setGhostUpdated(true); + obj->setNewGhost(newGhost); + + // set next cache entry to start + obj->getTickCache().beginCacheList(); + + // save state for future update + TickCacheEntry * tce = obj->getTickCache().incCacheList(); + BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); + obj->writePacketData(mConnection, &bs); + } +} diff --git a/T3D/hifi/moveList.h b/T3D/hifi/moveList.h new file mode 100644 index 0000000..ef94fca --- /dev/null +++ b/T3D/hifi/moveList.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MOVELIST_H_ +#define _MOVELIST_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "T3D/moveManager.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/tVector.h" +#endif + +class BitStream; +class ResizeBitStream; +class NetObject; +class GameConnection; + +class MoveList +{ + enum PrivateConstants + { + MoveCountBits = 5, + /// MaxMoveCount should not exceed the MoveManager's + /// own maximum (MaxMoveQueueSize) + MaxMoveCount = 30, + }; + +public: + + MoveList(); + + void init() { mTotalServerTicks = ServerTicksUninitialized; } + + void setConnection(GameConnection * connection) { mConnection = connection; } + + /// @name Move Packets + /// Write/read move data to the packet. + /// @{ + + /// + void ghostReadExtra(NetObject *,BitStream *, bool newGhost); + void ghostWriteExtra(NetObject *,BitStream *) {} + void ghostPreRead(NetObject *, bool newGhost); + + void clientWriteMovePacket(BitStream *bstream); + void clientReadMovePacket(BitStream *); + void serverWriteMovePacket(BitStream *); + void serverReadMovePacket(BitStream *bstream); + /// @} + + void writeDemoStartBlock(ResizeBitStream *stream); + void readDemoStartBlock(BitStream *stream); + +public: + // but not members of base class + void resetClientMoves() { mLastClientMove = mFirstMoveIndex; } + void resetCatchup() { mLastClientMove = mLastMoveAck; } + +public: + void collectMove(); + void pushMove(const Move &mv); + + virtual U32 getMoveList(Move**,U32* numMoves); + virtual bool areMovesPending(); + virtual void clearMoves(U32 count); + + void markControlDirty(); + bool isMismatch() { return mControlMismatch; } + bool isBacklogged(); + + void onAdvanceObjects() { if (mMoveList.size() > mLastSentMove-mFirstMoveIndex) mLastSentMove++; } + +protected: + bool getNextMove(Move &curMove); + void resetMoveList(); + +protected: + + S32 getServerTicks(U32 serverTickNum); + void updateClientServerTickDiff(S32 & tickDiff); + + U32 mLastMoveAck; + U32 mLastClientMove; + U32 mFirstMoveIndex; + U32 mLastSentMove; + bool mControlMismatch; + F32 mAvgMoveQueueSize; + + // server side move list management + U32 mTargetMoveListSize; // Target size of move buffer on server + U32 mMaxMoveListSize; // Max size move buffer allowed to grow to + F32 mSmoothMoveAvg; // Smoothing parameter for move list size running average + F32 mMoveListSizeSlack; // Amount above/below target size move list running average allowed to diverge + + // client side tracking of server ticks + enum { TotalTicksBits=10, TotalTicksMask = (1< mMoveList; +}; + +#endif // _MOVELIST_H_ diff --git a/T3D/item.cpp b/T3D/item.cpp new file mode 100644 index 0000000..e781746 --- /dev/null +++ b/T3D/item.cpp @@ -0,0 +1,1079 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/stream/bitStream.h" +#include "app/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "T3D/item.h" +#include "collision/boxConvex.h" +#include "collision/earlyOutPolyList.h" +#include "collision/extrudedPolyList.h" +#include "math/mathIO.h" +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" +//---------------------------------------------------------------------------- + +const F32 sRotationSpeed = 6.0f; // Secs/Rotation +const F32 sAtRestVelocity = 0.15f; // Min speed after collision +const S32 sCollisionTimeout = 15; // Timout value in ticks + +// Client prediction +static F32 sMinWarpTicks = 0.5 ; // Fraction of tick at which instant warp occures +static S32 sMaxWarpTicks = 3; // Max warp duration in ticks + +F32 Item::mGravity = -20.0f; + +const U32 sClientCollisionMask = (TerrainObjectType | + InteriorObjectType | StaticShapeObjectType | + VehicleObjectType | PlayerObjectType | + StaticTSObjectType); + +const U32 sServerCollisionMask = (sClientCollisionMask | + TriggerObjectType); + +const S32 Item::csmAtRestTimer = 64; + +static const U32 sgAllowedDynamicTypes = DamagableItemObjectType; + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(ItemData); + +ItemData::ItemData() +{ + shadowEnable = true; + + + friction = 0; + elasticity = 0; + + sticky = false; + gravityMod = 1.0; + maxVelocity = -1; + + density = 2; + drag = 0.5; + + dynamicTypeField = 0; + pickUpName = StringTable->insert("an item"); + + lightOnlyStatic = false; + lightType = Item::NoLight; + lightColor.set(1.f,1.f,1.f,1.f); + lightTime = 1000; + lightRadius = 10.f; +} + +static EnumTable::Enums itemLightEnum[] = +{ + { Item::NoLight, "NoLight" }, + { Item::ConstantLight, "ConstantLight" }, + { Item::PulsingLight, "PulsingLight" } +}; +static EnumTable gItemLightTypeTable(Item::NumLightTypes, &itemLightEnum[0]); + +void ItemData::initPersistFields() +{ + addField("pickUpName", TypeCaseString,Offset(pickUpName, ItemData)); + + addField("friction", TypeF32, Offset(friction, ItemData)); + addField("elasticity", TypeF32, Offset(elasticity, ItemData)); + addField("sticky", TypeBool, Offset(sticky, ItemData)); + addField("gravityMod", TypeF32, Offset(gravityMod, ItemData)); + addField("maxVelocity", TypeF32, Offset(maxVelocity, ItemData)); + addField("dynamicType", TypeS32, Offset(dynamicTypeField, ItemData)); + + addField("lightType", TypeEnum, Offset(lightType, ItemData), 1, &gItemLightTypeTable); + addField("lightColor", TypeColorF, Offset(lightColor, ItemData)); + addField("lightTime", TypeS32, Offset(lightTime, ItemData)); + addField("lightRadius", TypeF32, Offset(lightRadius, ItemData)); + addField("lightOnlyStatic", TypeBool, Offset(lightOnlyStatic, ItemData)); + + Parent::initPersistFields(); +} + +void ItemData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->writeFloat(friction, 10); + stream->writeFloat(elasticity, 10); + stream->writeFlag(sticky); + if(stream->writeFlag(gravityMod != 1.0)) + stream->writeFloat(gravityMod, 10); + if(stream->writeFlag(maxVelocity != -1)) + stream->write(maxVelocity); + + if(stream->writeFlag(lightType != Item::NoLight)) + { + AssertFatal(Item::NumLightTypes < (1 << 2), "ItemData: light type needs more bits"); + stream->writeInt(lightType, 2); + stream->writeFloat(lightColor.red, 7); + stream->writeFloat(lightColor.green, 7); + stream->writeFloat(lightColor.blue, 7); + stream->writeFloat(lightColor.alpha, 7); + stream->write(lightTime); + stream->write(lightRadius); + stream->writeFlag(lightOnlyStatic); + } +} + +void ItemData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + friction = stream->readFloat(10); + elasticity = stream->readFloat(10); + sticky = stream->readFlag(); + if(stream->readFlag()) + gravityMod = stream->readFloat(10); + else + gravityMod = 1.0; + + if(stream->readFlag()) + stream->read(&maxVelocity); + else + maxVelocity = -1; + + if(stream->readFlag()) + { + lightType = stream->readInt(2); + lightColor.red = stream->readFloat(7); + lightColor.green = stream->readFloat(7); + lightColor.blue = stream->readFloat(7); + lightColor.alpha = stream->readFloat(7); + stream->read(&lightTime); + stream->read(&lightRadius); + lightOnlyStatic = stream->readFlag(); + } + else + lightType = Item::NoLight; +} + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(Item); + +Item::Item() +{ + mTypeMask |= ItemObjectType; + mDataBlock = 0; + mCollideable = false; + mStatic = false; + mRotate = false; + mRotate2 = false; + mVelocity = VectorF(0,0,0); + mAtRest = true; + mAtRestCounter = 0; + mInLiquid = false; + delta.warpTicks = 0; + delta.dt = 1; + mCollisionObject = 0; + mCollisionTimeout = 0; +// mGenerateShadow = true; + + mConvex.init(this); + mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9); + + mLight = NULL; +} + +Item::~Item() +{ + SAFE_DELETE(mLight); +} + + +//---------------------------------------------------------------------------- + +bool Item::onAdd() +{ + if (!Parent::onAdd() || !mDataBlock) + return false; + + mTypeMask |= (mDataBlock->dynamicTypeField & sgAllowedDynamicTypes); + + if (mStatic) + mAtRest = true; + mObjToWorld.getColumn(3,&delta.pos); + + // Setup the box for our convex object... + mObjBox.getCenter(&mConvex.mCenter); + mConvex.mSize.x = mObjBox.len_x() / 2.0; + mConvex.mSize.y = mObjBox.len_y() / 2.0; + mConvex.mSize.z = mObjBox.len_z() / 2.0; + mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9); + + addToScene(); + + if (isServerObject()) + { + scriptOnAdd(); + } + else if (mDataBlock->lightType != NoLight) + { + mDropTime = Sim::getCurrentTime(); + } + + return true; +} + +bool Item::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + +void Item::onRemove() +{ + mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9); + mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9); + + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + +void Item::onDeleteNotify(SimObject* obj) +{ + if (obj == mCollisionObject) { + mCollisionObject = 0; + mCollisionTimeout = 0; + } +} + +// Lighting: ----------------------------------------------------------------- + +void Item::registerLights(LightManager * lightManager, bool lightingScene) +{ + if(lightingScene) + return; + + if(mDataBlock->lightOnlyStatic && !mStatic) + return; + + F32 intensity; + switch(mDataBlock->lightType) + { + case ConstantLight: + intensity = mFadeVal; + break; + + case PulsingLight: + { + S32 delta = Sim::getCurrentTime() - mDropTime; + intensity = 0.5f + 0.5f * mSin(M_PI_F * F32(delta) / F32(mDataBlock->lightTime)); + intensity = 0.15f + intensity * 0.85f; + intensity *= mFadeVal; // fade out light on flags + break; + } + + default: + return; + } + + // Create a light if needed + if (!mLight) + { + mLight = lightManager->createLightInfo(); + } + mLight->setColor( mDataBlock->lightColor * intensity ); + mLight->setType( LightInfo::Point ); + mLight->setRange( mDataBlock->lightRadius ); + mLight->setPosition( getBoxCenter() ); + + lightManager->registerGlobalLight( mLight, this ); +} + + +//---------------------------------------------------------------------------- + +Point3F Item::getVelocity() const +{ + return mVelocity; +} + +void Item::setVelocity(const VectorF& vel) +{ + mVelocity = vel; + setMaskBits(PositionMask); + mAtRest = false; + mAtRestCounter = 0; +} + +void Item::applyImpulse(const Point3F&,const VectorF& vec) +{ + // Items ignore angular velocity + VectorF vel; + vel.x = vec.x / mDataBlock->mass; + vel.y = vec.y / mDataBlock->mass; + vel.z = vec.z / mDataBlock->mass; + setVelocity(vel); +} + +void Item::setCollisionTimeout(ShapeBase* obj) +{ + if (mCollisionObject) + clearNotify(mCollisionObject); + deleteNotify(obj); + mCollisionObject = obj; + mCollisionTimeout = sCollisionTimeout; + setMaskBits(ThrowSrcMask); +} + + +//---------------------------------------------------------------------------- + +void Item::processTick(const Move* move) +{ + Parent::processTick(move); + + // + if (mCollisionObject && !--mCollisionTimeout) + mCollisionObject = 0; + + // Warp to catch up to server + if (delta.warpTicks > 0) + { + delta.warpTicks--; + + // Set new pos. + MatrixF mat = mObjToWorld; + mat.getColumn(3,&delta.pos); + delta.pos += delta.warpOffset; + mat.setColumn(3,delta.pos); + Parent::setTransform(mat); + + // Backstepping + delta.posVec.x = -delta.warpOffset.x; + delta.posVec.y = -delta.warpOffset.y; + delta.posVec.z = -delta.warpOffset.z; + } + else + { + if (isServerObject() && mAtRest && (mStatic == false && mDataBlock->sticky == false)) + { + if (++mAtRestCounter > csmAtRestTimer) + { + mAtRest = false; + mAtRestCounter = 0; + setMaskBits(PositionMask); + } + } + + if (!mStatic && !mAtRest && isHidden() == false) + { + updateVelocity(TickSec); + updateWorkingCollisionSet(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec); + updatePos(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec); + } + else + { + // Need to clear out last updatePos or warp interpolation + delta.posVec.set(0,0,0); + } + } +} + +void Item::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); + + // Client side interpolation + Point3F pos = delta.pos + delta.posVec * dt; + MatrixF mat = mRenderObjToWorld; + mat.setColumn(3,pos); + setRenderTransform(mat); + delta.dt = dt; +} + + +//---------------------------------------------------------------------------- + +void Item::setTransform(const MatrixF& mat) +{ + Point3F pos; + mat.getColumn(3,&pos); + MatrixF tmat; + if (!mRotate) { + // Forces all rotation to be around the z axis + VectorF vec; + mat.getColumn(1,&vec); + tmat.set(EulerF(0,0,-mAtan2(-vec.x,vec.y))); + } + else + tmat.identity(); + tmat.setColumn(3,pos); + Parent::setTransform(tmat); + if (!mStatic) + { + mAtRest = false; + mAtRestCounter = 0; + } + setMaskBits(RotationMask | PositionMask | NoWarpMask); +} + + +//---------------------------------------------------------------------------- +void Item::updateWorkingCollisionSet(const U32 mask, const F32 dt) +{ + // It is assumed that we will never accelerate more than 10 m/s for gravity... + // + Point3F scaledVelocity = mVelocity * dt; + F32 len = scaledVelocity.len(); + F32 newLen = len + (10 * dt); + + // Check to see if it is actually necessary to construct the new working list, + // or if we can use the cached version from the last query. We use the x + // component of the min member of the mWorkingQueryBox, which is lame, but + // it works ok. + bool updateSet = false; + + Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale()); + F32 l = (newLen * 1.1) + 0.1; // from Convex::updateWorkingList + convexBox.minExtents -= Point3F(l, l, l); + convexBox.maxExtents += Point3F(l, l, l); + + // Check containment + { + if (mWorkingQueryBox.minExtents.x != -1e9) + { + if (mWorkingQueryBox.isContained(convexBox) == false) + { + // Needed region is outside the cached region. Update it. + updateSet = true; + } + else + { + // We can leave it alone, we're still inside the cached region + } + } + else + { + // Must update + updateSet = true; + } + } + + // Actually perform the query, if necessary + if (updateSet == true) + { + mWorkingQueryBox = convexBox; + mWorkingQueryBox.minExtents -= Point3F(2 * l, 2 * l, 2 * l); + mWorkingQueryBox.maxExtents += Point3F(2 * l, 2 * l, 2 * l); + + disableCollision(); + if (mCollisionObject) + mCollisionObject->disableCollision(); + + mConvex.updateWorkingList(mWorkingQueryBox, mask); + + if (mCollisionObject) + mCollisionObject->enableCollision(); + enableCollision(); + } +} + +void Item::updateVelocity(const F32 dt) +{ + // Acceleration due to gravity + mVelocity.z += (mGravity * mDataBlock->gravityMod) * dt; + F32 len; + if (mDataBlock->maxVelocity > 0 && (len = mVelocity.len()) > (mDataBlock->maxVelocity * 1.05)) { + Point3F excess = mVelocity * (1.0 - (mDataBlock->maxVelocity / len )); + excess *= 0.1f; + mVelocity -= excess; + } + + // Container buoyancy & drag + mVelocity.z -= mBuoyancy * (mGravity * mDataBlock->gravityMod * mGravityMod) * dt; + mVelocity -= mVelocity * mDrag * dt; +} + + +void Item::updatePos(const U32 /*mask*/, const F32 dt) +{ + // Try and move + Point3F pos; + mObjToWorld.getColumn(3,&pos); + delta.posVec = pos; + + bool contact = false; + bool nonStatic = false; + bool stickyNotify = false; + CollisionList collisionList; + F32 time = dt; + + static Polyhedron sBoxPolyhedron; + static ExtrudedPolyList sExtrudedPolyList; + static EarlyOutPolyList sEarlyOutPolyList; + MatrixF collisionMatrix(true); + Point3F end = pos + mVelocity * time; + U32 mask = isServerObject() ? sServerCollisionMask : sClientCollisionMask; + + // Part of our speed problem here is that we don't track contact surfaces, like we do + // with the player. In order to handle the most common and performance impacting + // instance of this problem, we'll use a ray cast to detect any contact surfaces below + // us. This won't be perfect, but it only needs to catch a few of these to make a + // big difference. We'll cast from the top center of the bounding box at the tick's + // beginning to the bottom center of the box at the end. + Point3F startCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, + (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5, + mObjBox.maxExtents.z); + Point3F endCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, + (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5, + mObjBox.minExtents.z); + collisionMatrix.setColumn(3, pos); + collisionMatrix.mulP(startCast); + collisionMatrix.setColumn(3, end); + collisionMatrix.mulP(endCast); + RayInfo rinfo; + bool doToughCollision = true; + if (mCollisionObject) + mCollisionObject->disableCollision(); + if (getContainer()->castRay(startCast, endCast, mask, &rinfo)) + { + F32 bd = -mDot(mVelocity, rinfo.normal); + + if (bd >= 0.0) + { + // Contact! + if (mDataBlock->sticky && rinfo.object->getType() & (STATIC_COLLISION_MASK)) { + mVelocity.set(0, 0, 0); + mAtRest = true; + mAtRestCounter = 0; + stickyNotify = true; + mStickyCollisionPos = rinfo.point; + mStickyCollisionNormal = rinfo.normal; + doToughCollision = false;; + } else { + // Subtract out velocity into surface and friction + VectorF fv = mVelocity + rinfo.normal * bd; + F32 fvl = fv.len(); + if (fvl) { + F32 ff = bd * mDataBlock->friction; + if (ff < fvl) { + fv *= ff / fvl; + fvl = ff; + } + } + bd *= 1 + mDataBlock->elasticity; + VectorF dv = rinfo.normal * (bd + 0.002); + mVelocity += dv; + mVelocity -= fv; + + // Keep track of what we hit + contact = true; + U32 typeMask = rinfo.object->getTypeMask(); + if (!(typeMask & StaticObjectType)) + nonStatic = true; + if (isServerObject() && (typeMask & ShapeBaseObjectType)) { + ShapeBase* col = static_cast(rinfo.object); + queueCollision(col,mVelocity - col->getVelocity()); + } + } + } + } + if (mCollisionObject) + mCollisionObject->enableCollision(); + + if (doToughCollision) + { + U32 count; + for (count = 0; count < 3; count++) + { + // Build list from convex states here... + end = pos + mVelocity * time; + + + collisionMatrix.setColumn(3, end); + Box3F wBox = getObjBox(); + collisionMatrix.mul(wBox); + Box3F testBox = wBox; + Point3F oldMin = testBox.minExtents; + Point3F oldMax = testBox.maxExtents; + testBox.minExtents.setMin(oldMin + (mVelocity * time)); + testBox.maxExtents.setMin(oldMax + (mVelocity * time)); + + sEarlyOutPolyList.clear(); + sEarlyOutPolyList.mNormal.set(0,0,0); + sEarlyOutPolyList.mPlaneList.setSize(6); + sEarlyOutPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1,0,0)); + sEarlyOutPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0,1,0)); + sEarlyOutPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1,0,0)); + sEarlyOutPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0,-1,0)); + sEarlyOutPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0,0,-1)); + sEarlyOutPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0,0,1)); + + CollisionWorkingList& eorList = mConvex.getWorkingList(); + CollisionWorkingList* eopList = eorList.wLink.mNext; + while (eopList != &eorList) { + if ((eopList->mConvex->getObject()->getType() & mask) != 0) + { + Box3F convexBox = eopList->mConvex->getBoundingBox(); + if (testBox.isOverlapped(convexBox)) + { + eopList->mConvex->getPolyList(&sEarlyOutPolyList); + if (sEarlyOutPolyList.isEmpty() == false) + break; + } + } + eopList = eopList->wLink.mNext; + } + if (sEarlyOutPolyList.isEmpty()) + { + pos = end; + break; + } + + collisionMatrix.setColumn(3, pos); + sBoxPolyhedron.buildBox(collisionMatrix, mObjBox); + + // Build extruded polyList... + VectorF vector = end - pos; + sExtrudedPolyList.extrude(sBoxPolyhedron, vector); + sExtrudedPolyList.setVelocity(mVelocity); + sExtrudedPolyList.setCollisionList(&collisionList); + + CollisionWorkingList& rList = mConvex.getWorkingList(); + CollisionWorkingList* pList = rList.wLink.mNext; + while (pList != &rList) { + if ((pList->mConvex->getObject()->getType() & mask) != 0) + { + Box3F convexBox = pList->mConvex->getBoundingBox(); + if (testBox.isOverlapped(convexBox)) + { + pList->mConvex->getPolyList(&sExtrudedPolyList); + } + } + pList = pList->wLink.mNext; + } + + if (collisionList.getTime() < 1.0) + { + // Set to collision point + F32 dt = time * collisionList.getTime(); + pos += mVelocity * dt; + time -= dt; + + // Pick the most resistant surface + F32 bd = 0; + const Collision* collision = 0; + for (int c = 0; c < collisionList.getCount(); c++) { + const Collision &cp = collisionList[c]; + F32 dot = -mDot(mVelocity,cp.normal); + if (dot > bd) { + bd = dot; + collision = &cp; + } + } + + if (collision && mDataBlock->sticky && collision->object->getType() & (STATIC_COLLISION_MASK)) { + mVelocity.set(0, 0, 0); + mAtRest = true; + mAtRestCounter = 0; + stickyNotify = true; + mStickyCollisionPos = collision->point; + mStickyCollisionNormal = collision->normal; + break; + } else { + // Subtract out velocity into surface and friction + if (collision) { + VectorF fv = mVelocity + collision->normal * bd; + F32 fvl = fv.len(); + if (fvl) { + F32 ff = bd * mDataBlock->friction; + if (ff < fvl) { + fv *= ff / fvl; + fvl = ff; + } + } + bd *= 1 + mDataBlock->elasticity; + VectorF dv = collision->normal * (bd + 0.002); + mVelocity += dv; + mVelocity -= fv; + + // Keep track of what we hit + contact = true; + U32 typeMask = collision->object->getTypeMask(); + if (!(typeMask & StaticObjectType)) + nonStatic = true; + if (isServerObject() && (typeMask & ShapeBaseObjectType)) { + ShapeBase* col = static_cast(collision->object); + queueCollision(col,mVelocity - col->getVelocity()); + } + } + } + } + else + { + pos = end; + break; + } + } + if (count == 3) + { + // Couldn't move... + mVelocity.set(0, 0, 0); + } + } + + // If on the client, calculate delta for backstepping + if (isGhost()) { + delta.pos = pos; + delta.posVec -= pos; + delta.dt = 1; + } + + // Update transform + MatrixF mat = mObjToWorld; + mat.setColumn(3,pos); + Parent::setTransform(mat); + enableCollision(); + if (mCollisionObject) + mCollisionObject->enableCollision(); + updateContainer(); + + // + if (contact) { + // Check for rest condition + if (!nonStatic && mVelocity.len() < sAtRestVelocity) { + mVelocity.x = mVelocity.y = mVelocity.z = 0; + mAtRest = true; + mAtRestCounter = 0; + } + + // Only update the client if we hit a non-static shape or + // if this is our final rest pos. + if (nonStatic || mAtRest) + setMaskBits(PositionMask); + } + + // Collision callbacks. These need to be processed whether we hit + // anything or not. + if (!isGhost()) + { + SimObjectPtr safePtr(this); + if (stickyNotify) + { + notifyCollision(); + if(bool(safePtr)) + Con::executef(mDataBlock, "onStickyCollision", scriptThis()); + } + else + notifyCollision(); + + // water + if(bool(safePtr)) + { + if(!mInLiquid && mWaterCoverage != 0.0f) + { + Con::executef( mDataBlock, "onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), mLiquidType.c_str() ); + mInLiquid = true; + } + else if(mInLiquid && mWaterCoverage == 0.0f) + { + Con::executef( mDataBlock,"onLeaveLiquid",scriptThis(), mLiquidType.c_str() ); + mInLiquid = false; + } + } + } +} + + +//---------------------------------------------------------------------------- + +static MatrixF IMat(1); + +bool Item::buildPolyList(AbstractPolyList* polyList, const Box3F&, const SphereF&) +{ + // Collision with the item is always against the item's object + // space bounding box axis aligned in world space. + Point3F pos; + mObjToWorld.getColumn(3,&pos); + IMat.setColumn(3,pos); + polyList->setTransform(&IMat, mObjScale); + polyList->setObject(this); + polyList->addBox(mObjBox); + return true; +} + + +//---------------------------------------------------------------------------- + +U32 Item::packUpdate(NetConnection *connection, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(connection,mask,stream); + + if (stream->writeFlag(mask & InitialUpdateMask)) { + stream->writeFlag(mRotate); + stream->writeFlag(mRotate2); + stream->writeFlag(mStatic); + stream->writeFlag(mCollideable); + if (stream->writeFlag(getScale() != Point3F(1, 1, 1))) + mathWrite(*stream, getScale()); + } + if (mask & ThrowSrcMask && mCollisionObject) { + S32 gIndex = connection->getGhostIndex(mCollisionObject); + if (stream->writeFlag(gIndex != -1)) + stream->writeInt(gIndex,NetConnection::GhostIdBitSize); + } + else + stream->writeFlag(false); + if (stream->writeFlag(mask & RotationMask && !mRotate)) { + // Assumes rotation is about the Z axis + AngAxisF aa(mObjToWorld); + stream->writeFlag(aa.axis.z < 0); + stream->write(aa.angle); + } + if (stream->writeFlag(mask & PositionMask)) { + Point3F pos; + mObjToWorld.getColumn(3,&pos); + mathWrite(*stream, pos); + if (!stream->writeFlag(mAtRest)) { + mathWrite(*stream, mVelocity); + } + stream->writeFlag(!(mask & NoWarpMask)); + } + return retMask; +} + +void Item::unpackUpdate(NetConnection *connection, BitStream *stream) +{ + Parent::unpackUpdate(connection,stream); + if (stream->readFlag()) { + mRotate = stream->readFlag(); + mRotate2 = stream->readFlag(); + mStatic = stream->readFlag(); + mCollideable = stream->readFlag(); + if (stream->readFlag()) + mathRead(*stream, &mObjScale); + else + mObjScale.set(1, 1, 1); + } + if (stream->readFlag()) { + S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize); + setCollisionTimeout(static_cast(connection->resolveGhost(gIndex))); + } + MatrixF mat = mObjToWorld; + if (stream->readFlag()) { + // Assumes rotation is about the Z axis + AngAxisF aa; + aa.axis.set(0.0f, 0.0f, stream->readFlag() ? -1.0f : 1.0f); + stream->read(&aa.angle); + aa.setMatrix(&mat); + Point3F pos; + mObjToWorld.getColumn(3,&pos); + mat.setColumn(3,pos); + } + if (stream->readFlag()) { + Point3F pos; + mathRead(*stream, &pos); + F32 speed = mVelocity.len(); + if ((mAtRest = stream->readFlag()) == true) + mVelocity.set(0.0f, 0.0f, 0.0f); + else + mathRead(*stream, &mVelocity); + + if (stream->readFlag() && isProperlyAdded()) { + // Determin number of ticks to warp based on the average + // of the client and server velocities. + delta.warpOffset = pos - delta.pos; + F32 as = (speed + mVelocity.len()) * 0.5f * TickSec; + F32 dt = (as > 0.00001f) ? delta.warpOffset.len() / as: sMaxWarpTicks; + delta.warpTicks = (S32)((dt > sMinWarpTicks)? getMax(mFloor(dt + 0.5f), 1.0f): 0.0f); + + if (delta.warpTicks) + { + // Setup the warp to start on the next tick, only the + // object's position is warped. + if (delta.warpTicks > sMaxWarpTicks) + delta.warpTicks = sMaxWarpTicks; + delta.warpOffset /= (F32)delta.warpTicks; + } + else { + // Going to skip the warp, server and client are real close. + // Adjust the frame interpolation to move smoothly to the + // new position within the current tick. + Point3F cp = delta.pos + delta.posVec * delta.dt; + VectorF vec = delta.pos - cp; + F32 vl = vec.len(); + if (vl) { + F32 s = delta.posVec.len() / vl; + delta.posVec = (cp - pos) * s; + } + delta.pos = pos; + mat.setColumn(3,pos); + } + } + else { + // Set the item to the server position + delta.warpTicks = 0; + delta.posVec.set(0,0,0); + delta.pos = pos; + delta.dt = 0; + mat.setColumn(3,pos); + } + } + Parent::setTransform(mat); +} + +ConsoleMethod(Item, isStatic, bool, 2, 2, "()" + "Is the object static (ie, non-movable)?") +{ + return object->isStatic(); +} + +ConsoleMethod(Item, isRotating, bool, 2, 2, "()" + "Is the object still rotating?") +{ + return object->isRotating(); +} + +ConsoleMethod(Item, setCollisionTimeout, bool, 3, 3, "(ShapeBase obj)" + "Temporarily disable collisions against obj.") +{ + ShapeBase* source; + if (Sim::findObject(dAtoi(argv[2]),source)) { + object->setCollisionTimeout(source); + return true; + } + return false; +} + +ConsoleMethod( Item, getLastStickyPos, const char *, 2, 2, "()" + "Get the position on the surface on which the object is stuck.") +{ + char* ret = Con::getReturnBuffer(256); + if (object->isServerObject()) + dSprintf(ret, 255, "%g %g %g", + object->mStickyCollisionPos.x, + object->mStickyCollisionPos.y, + object->mStickyCollisionPos.z); + else + dStrcpy(ret, "0 0 0"); + + return ret; +} + +ConsoleMethod( Item, getLastStickyNormal, const char *, 2, 2, "()" + "Get the normal of the surface on which the object is stuck.") +{ + char* ret = Con::getReturnBuffer(256); + if (object->isServerObject()) + dSprintf(ret, 255, "%g %g %g", + object->mStickyCollisionNormal.x, + object->mStickyCollisionNormal.y, + object->mStickyCollisionNormal.z); + else + dStrcpy(ret, "0 0 0"); + + return ret; +} + +//---------------------------------------------------------------------------- + +void Item::initPersistFields() +{ + addGroup("Misc"); + addField("collideable", TypeBool, Offset(mCollideable, Item)); + addField("static", TypeBool, Offset(mStatic, Item)); + addField("rotate", TypeBool, Offset(mRotate, Item)); + addField("rotate2", TypeBool, Offset(mRotate2, Item)); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +void Item::consoleInit() +{ + Con::addVariable("Item::minWarpTicks",TypeF32,&sMinWarpTicks); + Con::addVariable("Item::maxWarpTicks",TypeS32,&sMaxWarpTicks); +} + +//---------------------------------------------------------------------------- + +bool Item::prepRenderImage(SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState) +{ + // Items do NOT render if destroyed + if (getDamageState() == Destroyed) + return false; + + return Parent::prepRenderImage(state, stateKey, startZone, modifyBaseState); +} + +void Item::buildConvex(const Box3F& box, Convex* convex) +{ + if (mShapeInstance == NULL) + return; + + // These should really come out of a pool + mConvexList->collectGarbage(); + + if (box.isOverlapped(getWorldBox()) == false) + return; + + // Just return a box convex for the entire shape... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { + if (itr->mConvex->getType() == BoxConvexType && + itr->mConvex->getObject() == this) { + cc = itr->mConvex; + break; + } + } + if (cc) + return; + + // Create a new convex. + BoxConvex* cp = new BoxConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->init(this); + + mObjBox.getCenter(&cp->mCenter); + cp->mSize.x = mObjBox.len_x() / 2.0f; + cp->mSize.y = mObjBox.len_y() / 2.0f; + cp->mSize.z = mObjBox.len_z() / 2.0f; +} + +void Item::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if( mRotate || mRotate2 ) + { + F32 r = (dt / sRotationSpeed) * M_2PI; + Point3F pos = mRenderObjToWorld.getPosition(); + MatrixF rotMatrix; + if( mRotate ) + { + rotMatrix.set( EulerF( 0.0, 0.0, r ) ); + } + else + { + rotMatrix.set( EulerF( r * 0.5, 0.0, r ) ); + } + MatrixF mat = mRenderObjToWorld; + mat.setPosition( pos ); + mat.mul( rotMatrix ); + setRenderTransform(mat); + } + +} diff --git a/T3D/item.h b/T3D/item.h new file mode 100644 index 0000000..bbf0e20 --- /dev/null +++ b/T3D/item.h @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ITEM_H_ +#define _ITEM_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +//---------------------------------------------------------------------------- + +struct ItemData: public ShapeBaseData { + typedef ShapeBaseData Parent; + + F32 friction; + F32 elasticity; + + bool sticky; + F32 gravityMod; + F32 maxVelocity; + + S32 dynamicTypeField; + + StringTableEntry pickUpName; + + bool lightOnlyStatic; + S32 lightType; + ColorF lightColor; + S32 lightTime; + F32 lightRadius; + + ItemData(); + DECLARE_CONOBJECT(ItemData); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class Item: public ShapeBase +{ + typedef ShapeBase Parent; + + enum MaskBits { + HiddenMask = Parent::NextFreeMask, + ThrowSrcMask = Parent::NextFreeMask << 1, + PositionMask = Parent::NextFreeMask << 2, + RotationMask = Parent::NextFreeMask << 3, + NextFreeMask = Parent::NextFreeMask << 4 + }; + + // Client interpolation data + struct StateDelta { + Point3F pos; + VectorF posVec; + S32 warpTicks; + Point3F warpOffset; + F32 dt; + }; + StateDelta delta; + + // Static attributes + ItemData* mDataBlock; + static F32 mGravity; + bool mCollideable; + bool mStatic; + bool mRotate; + bool mRotate2; + + // + VectorF mVelocity; + bool mAtRest; + + S32 mAtRestCounter; + static const S32 csmAtRestTimer; + + bool mInLiquid; + + ShapeBase* mCollisionObject; + U32 mCollisionTimeout; + + public: + + void registerLights(LightManager * lightManager, bool lightingScene); + enum LightType + { + NoLight = 0, + ConstantLight, + PulsingLight, + + NumLightTypes, + }; + + private: + S32 mDropTime; + LightInfo* mLight; + + public: + + Point3F mStickyCollisionPos; + Point3F mStickyCollisionNormal; + + // + private: + OrthoBoxConvex mConvex; + Box3F mWorkingQueryBox; + + void updateVelocity(const F32 dt); + void updatePos(const U32 mask, const F32 dt); + void updateWorkingCollisionSet(const U32 mask, const F32 dt); + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + void buildConvex(const Box3F& box, Convex* convex); + void onDeleteNotify(SimObject*); + + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + void advanceTime(F32 dt); + + public: + DECLARE_CONOBJECT(Item); + + + Item(); + ~Item(); + static void initPersistFields(); + static void consoleInit(); + + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData* dptr); + + bool isStatic() { return mStatic; } + bool isRotating() { return mRotate || mRotate2; } + Point3F getVelocity() const; + void setVelocity(const VectorF& vel); + void applyImpulse(const Point3F& pos,const VectorF& vec); + void setCollisionTimeout(ShapeBase* obj); + ShapeBase* getCollisionObject() { return mCollisionObject; }; + + void processTick(const Move *move); + void interpolateTick(F32 delta); + void setTransform(const MatrixF &mat); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); +}; + +#endif diff --git a/T3D/levelInfo.cpp b/T3D/levelInfo.cpp new file mode 100644 index 0000000..776bab3 --- /dev/null +++ b/T3D/levelInfo.cpp @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/levelInfo.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/advanced/advancedLightManager.h" +#include "lighting/advanced/advancedLightBinManager.h" + +IMPLEMENT_CO_NETOBJECT_V1(LevelInfo); + +/// The color used to clear the canvas. +/// @see GuiCanvas +extern ColorI gCanvasClearColor; + +/// @see DecalManager +extern F32 gDecalBias; + +LevelInfo::LevelInfo() + : mNearClip( 0.1f ), + mVisibleDistance( 1000.0f ), + mDecalBias( 0.0015f ), + mCanvasClearColor( 255, 0, 255, 255 ) +{ + mFogData.density = 0.0f; + mFogData.densityOffset = 0.0f; + mFogData.atmosphereHeight = 0.0f; + mFogData.color.set( 128, 128, 128 ), + + mNetFlags.set( ScopeAlways | Ghostable ); + + mAdvancedLightmapSupport = false; + + // Register with the light manager activation signal, and we need to do it first + // so the advanced light bin manager can be instructed about MRT lightmaps + LightManager::smActivateSignal.notify(this, &LevelInfo::_onLMActivate, 0.01f); +} + +LevelInfo::~LevelInfo() +{ + LightManager::smActivateSignal.remove(this, &LevelInfo::_onLMActivate); +} + +void LevelInfo::initPersistFields() +{ + addGroup( "Visibility" ); + + addField( "nearClip", TypeF32, Offset( mNearClip, LevelInfo ) ); + addField( "visibleDistance", TypeF32, Offset( mVisibleDistance, LevelInfo ) ); + addField( "decalBias", TypeF32, Offset( mDecalBias, LevelInfo ), + "NearPlane bias used when rendering Decal and DecalRoad. This should be tuned to the visibleDistance in your level." ); + + endGroup( "Visibility" ); + + addGroup( "Fog" ); + + addField( "fogColor", TypeColorF, Offset( mFogData.color, LevelInfo ), + "The default color for the scene fog." ); + + addField( "fogDensity", TypeF32, Offset( mFogData.density, LevelInfo ), + "The 0 to 1 density value for the exponential fog falloff." ); + + addField( "fogDensityOffset", TypeF32, Offset( mFogData.densityOffset, LevelInfo ), + "An offset from the camera in meters for moving the start of the fog effect." ); + + addField( "fogAtmosphereHeight", TypeF32, Offset( mFogData.atmosphereHeight, LevelInfo ), + "A height in meters for altitude fog falloff." ); + + endGroup( "Fog" ); + + addGroup( "LevelInfo" ); + + addField( "canvasClearColor", TypeColorI, Offset( mCanvasClearColor, LevelInfo ), + "The color used to clear the background before the scene or any GUIs are rendered." ); + + endGroup( "LevelInfo" ); + + addGroup( "Lightmap Support" ); + + addField( "advancedLightmapSupport", TypeBool, Offset( mAdvancedLightmapSupport, LevelInfo ), + "Enable expanded support for mixing static and dynamic lighting (more costly)" ); + + endGroup( "Lightmap Support" ); +} + +void LevelInfo::inspectPostApply() +{ + _updateSceneGraph(); + setMaskBits( 0xFFFFFFFF ); +} + +U32 LevelInfo::packUpdate(NetConnection *conn, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + stream->write( mNearClip ); + stream->write( mVisibleDistance ); + stream->write( mDecalBias ); + + stream->write( mFogData.density ); + stream->write( mFogData.densityOffset ); + stream->write( mFogData.atmosphereHeight ); + stream->write( mFogData.color ); + + stream->write( mCanvasClearColor ); + + stream->writeFlag( mAdvancedLightmapSupport ); + + return retMask; +} + +void LevelInfo::unpackUpdate(NetConnection *conn, BitStream *stream) +{ + Parent::unpackUpdate(conn, stream); + + stream->read( &mNearClip ); + stream->read( &mVisibleDistance ); + stream->read( &mDecalBias ); + + stream->read( &mFogData.density ); + stream->read( &mFogData.densityOffset ); + stream->read( &mFogData.atmosphereHeight ); + stream->read( &mFogData.color ); + + stream->read( &mCanvasClearColor ); + + mAdvancedLightmapSupport = stream->readFlag(); + + if ( isProperlyAdded() ) + _updateSceneGraph(); +} + +bool LevelInfo::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + _updateSceneGraph(); + + return true; +} + +void LevelInfo::onRemove() +{ + Parent::onRemove(); +} + +void LevelInfo::_updateSceneGraph() +{ + // We must update both scene graphs. + SceneGraph *sm; + if ( isClientObject() ) + sm = gClientSceneGraph; + else + sm = gServerSceneGraph; + + // Clamp above zero before setting on the sceneGraph. + // If we don't we get serious crashes. + if ( mNearClip <= 0.0f ) + mNearClip = 0.001f; + + sm->setNearClip( mNearClip ); + sm->setVisibleDistance( mVisibleDistance ); + + gDecalBias = mDecalBias; + + // Copy our AirFogData into the sceneGraph. + sm->setFogData( mFogData ); + + // If the level info specifies that MRT pre-pass should be used in this scene + // enable it via the appropriate light manager + // (Basic lighting doesn't do anything different right now) +#ifndef TORQUE_DEDICATED + if(isClientObject()) + { + LightManager* manager = gClientSceneGraph->getLightManager(); + _onLMActivate(manager->getId(), true); + } +#endif + + // TODO: This probably needs to be moved. + gCanvasClearColor = mCanvasClearColor; +} + +void LevelInfo::_onLMActivate(const char *lm, bool enable) +{ +#ifndef TORQUE_DEDICATED + // Advanced light manager + if(enable && String(lm) == String("ADVLM")) + { + LightManager* manager = gClientSceneGraph->getLightManager(); + AssertFatal(dynamic_cast(manager), "Bad light manager type!"); + AdvancedLightManager *lightMgr = static_cast(manager); + lightMgr->getLightBinManager()->MRTLightmapsDuringPrePass(mAdvancedLightmapSupport); + } +#endif +} \ No newline at end of file diff --git a/T3D/levelInfo.h b/T3D/levelInfo.h new file mode 100644 index 0000000..ae1db33 --- /dev/null +++ b/T3D/levelInfo.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LEVELINFO_H_ +#define _LEVELINFO_H_ + +#ifndef _NETOBJECT_H_ +#include "sim/netObject.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _FOGSTRUCTS_H_ +#include "sceneGraph/fogStructs.h" +#endif + +class LevelInfo : public NetObject +{ + typedef NetObject Parent; + +private: + + FogData mFogData; + + F32 mNearClip; + + F32 mVisibleDistance; + + F32 mDecalBias; + + ColorI mCanvasClearColor; + + bool mAdvancedLightmapSupport; + + /// Responsible for passing on + /// the LevelInfo settings to the + /// client SceneGraph, from which + /// other systems can get at them. + void _updateSceneGraph(); + + void _onLMActivate(const char *lm, bool enable); + +public: + + LevelInfo(); + virtual ~LevelInfo(); + + DECLARE_CONOBJECT(LevelInfo); + + /// @name SimObject Inheritance + /// @{ + bool onAdd(); + void onRemove(); + static void initPersistFields(); + void inspectPostApply(); + /// @} + + /// @name NetObject Inheritance + /// @{ + enum NetMaskBits + { + UpdateMask = BIT(0) + }; + + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + /// @} +}; + +#endif // _LEVELINFO_H_ \ No newline at end of file diff --git a/T3D/lightAnimData.cpp b/T3D/lightAnimData.cpp new file mode 100644 index 0000000..a57ea7e --- /dev/null +++ b/T3D/lightAnimData.cpp @@ -0,0 +1,119 @@ + +#include "platform/platform.h" +#include "lightAnimData.h" + +#include "console/consoleTypes.h" +#include "T3D/lightBase.h" +#include "math/mRandom.h" +#include "sim/processList.h" +#include "core/stream/bitStream.h" + +LightAnimData::LightAnimData() +: mFlicker( false ), + mChanceTurnOn( 0.2f ), + mChanceTurnOff( 0.2f ), + mMinBrightness( 0.0f ), + mMaxBrightness( 1.0f ), + mAnimEnabled( true ) +{ +} + +LightAnimData::~LightAnimData() +{ +} + +IMPLEMENT_CO_DATABLOCK_V1( LightAnimData ); + +void LightAnimData::initPersistFields() +{ + addField( "animEnabled", TypeBool, Offset( mAnimEnabled, LightAnimData ) ); + addField( "flicker", TypeBool, Offset( mFlicker, LightAnimData ) ); + addField( "chanceTurnOn", TypeF32, Offset( mChanceTurnOn, LightAnimData ) ); + addField( "chanceTurnOff", TypeF32, Offset( mChanceTurnOff, LightAnimData ) ); + addField( "minBrightness", TypeF32, Offset( mMinBrightness, LightAnimData ) ); + addField( "maxBrightness", TypeF32, Offset( mMaxBrightness, LightAnimData ) ); + + Parent::initPersistFields(); +} + +bool LightAnimData::preload( bool server, String &errorStr ) +{ + if ( !Parent::preload( server, errorStr ) ) + return false; + + return true; +} + +void LightAnimData::packData( BitStream *stream ) +{ + Parent::packData( stream ); + + stream->writeFlag( mAnimEnabled ); + stream->writeFlag( mFlicker ); + stream->write( mChanceTurnOn ); + stream->write( mChanceTurnOff ); + stream->write( mMinBrightness ); + stream->write( mMaxBrightness ); +} + +void LightAnimData::unpackData( BitStream *stream ) +{ + Parent::unpackData( stream ); + + mAnimEnabled = stream->readFlag(); + mFlicker = stream->readFlag(); + stream->read( &mChanceTurnOn ); + stream->read( &mChanceTurnOff ); + stream->read( &mMinBrightness ); + stream->read( &mMaxBrightness ); +} + +void LightAnimData::animate( LightAnimState *state ) +{ + LightInfo *lightInfo = state->lightInfo; + + if ( !mAnimEnabled ) + { + lightInfo->setBrightness( state->fullBrightness ); + return; + } + + F32 timeSec = (F32)Sim::getCurrentTime() / 1000.0f; + + if ( mFlicker ) + { + F32 delta = timeSec - state->lastTime; + delta = getMax( getMin( delta, 10.0f ), 0.0f ); + + bool isOn = lightInfo->getBrightness() > 0.0f; + F32 chance = isOn ? mChanceTurnOff : mChanceTurnOn; + F32 toggledBrightness = isOn ? 0.0f : state->fullBrightness; + + while ( delta > TickSec ) + { + if ( mRandF() < chance ) + { + lightInfo->setBrightness( toggledBrightness ); + delta = 0; + break; + } + + delta -= TickSec; + } + + // We have to save the remainder of delta we did not use. + state->lastTime = timeSec - delta; + } + else + { + F32 t = ( timeSec + state->animationPhase ) / state->animationPeriod; + t = mSin( t * 2.0f ) * 0.5f + 0.5f; + + F32 brightness = mLerp( mMinBrightness, mMaxBrightness, t ); + lightInfo->setBrightness( state->fullBrightness * brightness ); + } +} + +IMPLEMENT_CONSOLETYPE( LightAnimData ) +IMPLEMENT_GETDATATYPE( LightAnimData ) +IMPLEMENT_SETDATATYPE( LightAnimData ) diff --git a/T3D/lightAnimData.h b/T3D/lightAnimData.h new file mode 100644 index 0000000..58b7cb0 --- /dev/null +++ b/T3D/lightAnimData.h @@ -0,0 +1,70 @@ + +#ifndef _LIGHTANIMDATA_H_ +#define _LIGHTANIMDATA_H_ + +#ifndef _SIMDATABLOCK_H_ +#include "console/simDatablock.h" +#endif +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif + +class LightInfo; + +struct LightAnimState +{ + /// Object calling LightAnimData::animate fills these in! + bool active; + F32 fullBrightness; + F32 animationPhase; + F32 animationPeriod; + LightInfo *lightInfo; + + /// Used internally by LightAnimData! + F32 lastTime; + + void clear() + { + fullBrightness = 1.0f; + animationPhase = 1.0f; + animationPeriod = 1.0f; + lightInfo = NULL; + lastTime = 0.0; + } +}; + +class LightAnimData : public SimDataBlock +{ + typedef SimDataBlock Parent; +public: + + LightAnimData(); + virtual ~LightAnimData(); + + DECLARE_CONOBJECT( LightAnimData ); + + static void initPersistFields(); + + // SimDataBlock + virtual bool preload( bool server, String &errorStr ); + virtual void packData( BitStream *stream ); + virtual void unpackData( BitStream *stream ); + + /// Animates parameters on the passed Light's LightInfo object. + virtual void animate( LightAnimState *state ); + + // Fields... + + bool mAnimEnabled; + + bool mFlicker; + F32 mChanceTurnOn; + F32 mChanceTurnOff; + + F32 mMinBrightness; + F32 mMaxBrightness; +}; + +DECLARE_CONSOLETYPE(LightAnimData) + +#endif // _LIGHTANIMDATA_H_ \ No newline at end of file diff --git a/T3D/lightBase.cpp b/T3D/lightBase.cpp new file mode 100644 index 0000000..cfd09b6 --- /dev/null +++ b/T3D/lightBase.cpp @@ -0,0 +1,460 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/lightBase.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "sim/netConnection.h" +#include "lighting/lightManager.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" + + +bool LightBase::smRenderViz = false; + +IMPLEMENT_CONOBJECT( LightBase ); + +LightBase::LightBase() + : mIsEnabled( true ), + mColor( ColorF::WHITE ), + mBrightness( 1.0f ), + mCastShadows( false ), + mPriority( 1.0f ), + mAnimationData( NULL ), + mFlareData( NULL ), + mFlareScale( 1.0f ), + mAnimationPeriod( 1.0f ), + mAnimationPhase( 1.0f ) +{ + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask = EnvironmentObjectType | LightObjectType; + + mLight = LightManager::createLightInfo(); + + mFlareState.clear(); + mAnimState.clear(); + mAnimState.active = true; +} + +LightBase::~LightBase() +{ + SAFE_DELETE( mLight ); +} + +void LightBase::initPersistFields() +{ + // We only add the basic lighting options that all lighting + // systems would use... the specific lighting system options + // are injected at runtime by the lighting system itself. + + addGroup( "Light" ); + + addField( "isEnabled", TypeBool, Offset( mIsEnabled, LightBase ) ); + addField( "color", TypeColorF, Offset( mColor, LightBase ) ); + addField( "brightness", TypeF32, Offset( mBrightness, LightBase ) ); + addField( "castShadows", TypeBool, Offset( mCastShadows, LightBase ) ); + addField( "priority", TypeF32, Offset( mPriority, LightBase ) ); + + endGroup( "Light" ); + + addGroup( "Light Animation" ); + + addField( "animate", TypeBool, Offset( mAnimState.active, LightBase ) ); + addField( "animationType", TypeLightAnimDataPtr, Offset( mAnimationData, LightBase ) ); + addField( "animationPeriod", TypeF32, Offset( mAnimationPeriod, LightBase ) ); + addField( "animationPhase", TypeF32, Offset( mAnimationPhase, LightBase ) ); + + endGroup( "Light Animation" ); + + addGroup( "Misc" ); + + addField( "flareType", TypeLightFlareDataPtr, Offset( mFlareData, LightBase ) ); + addField( "flareScale", TypeF32, Offset( mFlareScale, LightBase ) ); + + endGroup( "Misc" ); + + // Now inject any light manager specific fields. + LightManager::initLightFields(); + + // We do the parent fields at the end so that + // they show up that way in the inspector. + Parent::initPersistFields(); + + Con::addVariable( "$Light::renderViz", TypeBool, &smRenderViz ); +} + +bool LightBase::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Update the light parameters. + _conformLights(); + addToScene(); + + return true; +} + +void LightBase::onRemove() +{ + removeFromScene(); + Parent::onRemove(); +} + +void LightBase::onDeleteNotify(SimObject* obj) +{ + Parent::onDeleteNotify(obj); + if (obj == mMount.object) + unmount(); +} + +void LightBase::submitLights( LightManager *lm, bool staticLighting ) +{ + if ( !mIsEnabled || staticLighting ) + return; + + if ( mAnimState.active && mAnimationData ) + { + mAnimState.fullBrightness = mBrightness; + mAnimState.lightInfo = mLight; + mAnimState.animationPeriod = mAnimationPeriod; + mAnimState.animationPhase = mAnimationPhase; + + mAnimationData->animate( &mAnimState ); + } + + lm->registerGlobalLight( mLight, this ); +} + +void LightBase::inspectPostApply() +{ + // We do not call the parent here as it + // will call setScale() and screw up the + // real sizing fields on the light. + + _conformLights(); + setMaskBits( EnabledMask | UpdateMask | TransformMask | DatablockMask ); +} + +void LightBase::setTransform( const MatrixF &mat ) +{ + setMaskBits( TransformMask ); + Parent::setTransform( mat ); +} + +bool LightBase::prepRenderImage( SceneState *state, const U32 stateKey, const U32, const bool ) +{ + if ( isLastState( state, stateKey ) ) + return false; + + setLastState( state, stateKey ); + + if ( !state->isObjectRendered( this ) ) + return false; + + if ( mIsEnabled && mFlareData ) + { + mFlareState.fullBrightness = mBrightness; + mFlareState.scale = mFlareScale; + mFlareState.lightInfo = mLight; + mFlareState.lightMat = getRenderTransform(); + + mFlareData->prepRender( state, &mFlareState ); + } + + if ( !state->isDiffusePass() ) + return false; + + // If the light is selected or light visualization + // is enabled then register the callback. + if ( smRenderViz || isSelected() || ( getServerObject() && getServerObject()->isSelected() ) ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &LightBase::_onRenderViz ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void LightBase::_onRenderViz( ObjectRenderInst *ri, + SceneState *state, + BaseMatInstance *overrideMat ) +{ + if ( !overrideMat ) + _renderViz( state ); +} + +void LightBase::onMount( SceneObject *obj, S32 node ) +{ + deleteNotify(obj); + + if (!isGhost()) + { + setMaskBits(MountedMask); + } +} + +void LightBase::onUnmount( SceneObject *obj, S32 node ) +{ + clearNotify(obj); + + if (!isGhost()) + { + setMaskBits(MountedMask); + } +} + +void LightBase::unmount() +{ + if (mMount.object) + mMount.object->unmountObject(this); +} + +void LightBase::interpolateTick( F32 delta ) +{ +} + +void LightBase::processTick() +{ +} + +void LightBase::advanceTime( F32 timeDelta ) +{ + if ( mMount.object ) + { + MatrixF mat; + mMount.object->getRenderMountTransform( mMount.node, &mat ); + mLight->setTransform( mat ); + Parent::setTransform( mat ); + } +} + +U32 LightBase::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + stream->writeFlag( mIsEnabled ); + + if ( stream->writeFlag( mask & TransformMask ) ) + stream->writeAffineTransform( mObjToWorld ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + stream->write( mColor ); + stream->write( mBrightness ); + + stream->writeFlag( mCastShadows ); + + stream->write( mPriority ); + + mLight->packExtended( stream ); + + stream->writeFlag( mAnimState.active ); + stream->write( mAnimationPeriod ); + stream->write( mAnimationPhase ); + stream->write( mFlareScale ); + } + + if ( stream->writeFlag( mask & DatablockMask ) ) + { + if ( stream->writeFlag( mAnimationData ) ) + { + stream->writeRangedU32( mAnimationData->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } + + if ( stream->writeFlag( mFlareData ) ) + { + stream->writeRangedU32( mFlareData->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } + } + + if (mask & MountedMask) + { + if (mMount.object) + { + S32 gIndex = conn->getGhostIndex(mMount.object); + if (stream->writeFlag(gIndex != -1)) + { + stream->writeFlag(true); + stream->writeInt(gIndex,NetConnection::GhostIdBitSize); + stream->write(mMount.node); + } + else + // Will have to try again later + retMask |= MountedMask; + } + else + // Unmount if this isn't the initial packet + if (stream->writeFlag(!(mask & InitialUpdateMask))) + stream->writeFlag(false); + } + else + stream->writeFlag(false); + + return retMask; +} + +void LightBase::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + mIsEnabled = stream->readFlag(); + + if ( stream->readFlag() ) // TransformMask + stream->readAffineTransform( &mObjToWorld ); + + if ( stream->readFlag() ) // UpdateMask + { + stream->read( &mColor ); + stream->read( &mBrightness ); + mCastShadows = stream->readFlag(); + + stream->read( &mPriority ); + + mLight->unpackExtended( stream ); + + mAnimState.active = stream->readFlag(); + stream->read( &mAnimationPeriod ); + stream->read( &mAnimationPhase ); + stream->read( &mFlareScale ); + } + + if ( stream->readFlag() ) // DatablockMask + { + if ( stream->readFlag() ) + { + SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + LightAnimData *datablock = NULL; + + if ( Sim::findObject( id, datablock ) ) + mAnimationData = datablock; + else + { + conn->setLastError( "Light::unpackUpdate() - invalid LightAnimData!" ); + mAnimationData = NULL; + } + } + else + mAnimationData = NULL; + + if ( stream->readFlag() ) + { + SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + LightFlareData *datablock = NULL; + + if ( Sim::findObject( id, datablock ) ) + mFlareData = datablock; + else + { + conn->setLastError( "Light::unpackUpdate() - invalid LightCoronaData!" ); + mFlareData = NULL; + } + } + else + mFlareData = NULL; + } + + if ( stream->readFlag() ) + { + if ( stream->readFlag() ) + { + S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize); + SceneObject* obj = dynamic_cast(conn->resolveGhost(gIndex)); + S32 node; + stream->read(&node); + if(!obj) + { + conn->setLastError("Invalid packet from server."); + return; + } + obj->mountObject(this,node); + } + else + unmount(); + } + + if ( isProperlyAdded() ) + _conformLights(); +} + +void LightBase::setLightEnabled( bool enabled ) +{ + if ( mIsEnabled != enabled ) + { + mIsEnabled = enabled; + setMaskBits( EnabledMask ); + } +} + +ConsoleMethod( LightBase, setLightEnabled, void, 3, 3, "( bool enabled )\t" + "Toggles the light on and off." ) +{ + object->setLightEnabled( dAtob( argv[2] ) ); +} + +ConsoleMethod( LightBase, playAnimation, void, 2, 3, "( [LightAnimData anim] )\t" + "Plays a light animation on the light. If no LightAnimData is passed the " + "existing one is played." ) +{ + if ( argc == 2 ) + { + object->playAnimation(); + return; + } + + LightAnimData *animData; + if ( !Sim::findObject( argv[2], animData ) ) + { + Con::errorf( "LightBase::playAnimation() - Invalid LightAnimData '%s'.", argv[2] ); + return; + } + + // Play Animation. + object->playAnimation( animData ); +} + +void LightBase::playAnimation( void ) +{ + if ( !mAnimState.active ) + { + mAnimState.active = true; + setMaskBits( UpdateMask ); + } +} + +void LightBase::playAnimation( LightAnimData *animData ) +{ + // Play Animation. + playAnimation(); + + // Update Datablock? + if ( mAnimationData != animData ) + { + mAnimationData = animData; + setMaskBits( DatablockMask ); + } +} + +ConsoleMethod( LightBase, pauseAnimation, void, 2, 2, "Stops the light animation." ) +{ + object->pauseAnimation(); +} + +void LightBase::pauseAnimation( void ) +{ + if ( mAnimState.active ) + { + mAnimState.active = false; + setMaskBits( UpdateMask ); + } +} \ No newline at end of file diff --git a/T3D/lightBase.h b/T3D/lightBase.h new file mode 100644 index 0000000..6e33eab --- /dev/null +++ b/T3D/lightBase.h @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTBASE_H_ +#define _LIGHTBASE_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _ITICKABLE_H_ +#include "core/iTickable.h" +#endif +#ifndef _LIGHTFLAREDATA_H_ +#include "T3D/lightFlareData.h" +#endif +#ifndef _LIGHTANIMDATA_H_ +#include "T3D/lightAnimData.h" +#endif + +class LightAnimData; + +class LightBase : public SceneObject, public ISceneLight, public virtual ITickable +{ + typedef SceneObject Parent; + friend class LightAnimData; + friend class LightFlareData; + +protected: + + bool mIsEnabled; + + ColorF mColor; + + F32 mBrightness; + + bool mCastShadows; + + F32 mPriority; + + LightInfo *mLight; + + LightAnimData *mAnimationData; + LightAnimState mAnimState; + F32 mAnimationPeriod; + F32 mAnimationPhase; + + LightFlareData *mFlareData; + LightFlareState mFlareState; + F32 mFlareScale; + + static bool smRenderViz; + + virtual void _conformLights() {} + + void _onRenderViz( ObjectRenderInst *ri, + SceneState *state, + BaseMatInstance *overrideMat ); + + virtual void _renderViz( SceneState *state ) {} + + enum LightMasks + { + InitialUpdateMask = Parent::NextFreeMask, + EnabledMask = Parent::NextFreeMask << 1, + TransformMask = Parent::NextFreeMask << 2, + UpdateMask = Parent::NextFreeMask << 3, + DatablockMask = Parent::NextFreeMask << 4, + MountedMask = Parent::NextFreeMask << 5, + NextFreeMask = Parent::NextFreeMask << 6 + }; + +public: + + LightBase(); + virtual ~LightBase(); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + virtual void onDeleteNotify(SimObject *object); + + // ConsoleObject + void inspectPostApply(); + static void initPersistFields(); + DECLARE_CONOBJECT(LightBase); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // ISceneLight + virtual void submitLights( LightManager *lm, bool staticLighting ); + virtual LightInfo* getLight() { return mLight; } + + // SceneObject + virtual void setTransform( const MatrixF &mat ); + virtual bool prepRenderImage( SceneState *state, const U32 stateKey, const U32, const bool ); + virtual void onMount( SceneObject *obj, S32 node ); + virtual void onUnmount( SceneObject *obj, S32 node ); + virtual void unmount(); + + // ITickable + virtual void interpolateTick( F32 delta ); + virtual void processTick(); + virtual void advanceTime( F32 timeDelta ); + + /// Toggles the light on and off. + void setLightEnabled( bool enabled ); + bool getLightEnabled() { return mIsEnabled; }; + + /// Animate the light. + virtual void pauseAnimation( void ); + virtual void playAnimation( void ); + virtual void playAnimation( LightAnimData *animData ); +}; + +#endif // _LIGHTBASE_H_ diff --git a/T3D/lightDescription.cpp b/T3D/lightDescription.cpp new file mode 100644 index 0000000..5271231 --- /dev/null +++ b/T3D/lightDescription.cpp @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/lightDescription.h" + +#include "lighting/lightManager.h" +#include "T3D/lightFlareData.h" +#include "T3D/lightAnimData.h" +#include "core/stream/bitStream.h" +#include "lighting/lightInfo.h" + +LightDescription::LightDescription() + : color( ColorF::WHITE ), + brightness( 1.0f ), + range( 5.0f ), + castShadows( false ), + animationData( NULL ), + animationDataId( 0 ), + animationPeriod( 1.0f ), + animationPhase( 1.0f ), + flareData( NULL ), + flareDataId( 0 ), + flareScale( 1.0f ) +{ +} + +LightDescription::~LightDescription() +{ + +} + +IMPLEMENT_CO_DATABLOCK_V1( LightDescription ); + +void LightDescription::initPersistFields() +{ + addGroup( "Light" ); + + addField( "color", TypeColorF, Offset( color, LightDescription ) ); + addField( "brightness", TypeF32, Offset( brightness, LightDescription ) ); + addField( "range", TypeF32, Offset( range, LightDescription ) ); + addField( "castShadows", TypeBool, Offset( castShadows, LightDescription ) ); + + endGroup( "Light" ); + + addGroup( "Light Animation" ); + + addField( "animationType", TypeLightAnimDataPtr, Offset( animationData, LightDescription ) ); + addField( "animationPeriod", TypeF32, Offset( animationPeriod, LightDescription ) ); + addField( "animationPhase", TypeF32, Offset( animationPhase, LightDescription ) ); + + endGroup( "Light Animation" ); + + addGroup( "Misc" ); + + addField( "flareType", TypeLightFlareDataPtr, Offset( flareData, LightDescription ) ); + addField( "flareScale", TypeF32, Offset( flareScale, LightDescription ) ); + + endGroup( "Misc" ); + + LightManager::initLightFields(); + + Parent::initPersistFields(); +} + +void LightDescription::inspectPostApply() +{ + Parent::inspectPostApply(); + + // Hack to allow changing properties in game. + // Do the same work as preload. + animationData = NULL; + flareData = NULL; + + String str; + _preload( false, str ); +} + +bool LightDescription::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + return true; +} +bool LightDescription::preload( bool server, String &errorStr ) +{ + if ( !Parent::preload( server, errorStr ) ) + return false; + + return _preload( server, errorStr ); +} + +void LightDescription::packData( BitStream *stream ) +{ + Parent::packData( stream ); + + stream->write( color ); + stream->write( brightness ); + stream->write( range ); + stream->writeFlag( castShadows ); + + stream->write( animationPeriod ); + stream->write( animationPhase ); + stream->write( flareScale ); + + if ( stream->writeFlag( animationData ) ) + { + stream->writeRangedU32( animationData->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } + + if ( stream->writeFlag( flareData ) ) + { + stream->writeRangedU32( flareData->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } +} + +void LightDescription::unpackData( BitStream *stream ) +{ + Parent::unpackData( stream ); + + stream->read( &color ); + stream->read( &brightness ); + stream->read( &range ); + castShadows = stream->readFlag(); + + stream->read( &animationPeriod ); + stream->read( &animationPhase ); + stream->read( &flareScale ); + + if ( stream->readFlag() ) + animationDataId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + + if ( stream->readFlag() ) + flareDataId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); +} + +void LightDescription::submitLight( LightState *state, const MatrixF &xfm, LightManager *lm, SimObject *object ) +{ + LightInfo *li = state->lightInfo; + + li->setRange( range ); + li->setColor( color ); + li->setCastShadows( castShadows ); + li->setTransform( xfm ); + + if ( animationData ) + { + LightAnimState *animState = &state->animState; + + animState->fullBrightness = brightness; + animState->animationPeriod = animationPeriod; + animState->animationPhase = animationPhase; + + animationData->animate( animState ); + } + + lm->registerGlobalLight( li, object ); +} + +void LightDescription::prepRender( SceneState *sceneState, LightState *lightState, const MatrixF &xfm ) +{ + if ( flareData ) + { + LightFlareState *flareState = &lightState->flareState; + flareState->fullBrightness = brightness; + flareState->scale = flareScale; + flareState->lightMat = xfm; + flareState->lightInfo = lightState->lightInfo; + + flareData->prepRender( sceneState, flareState ); + } +} + +bool LightDescription::_preload( bool server, String &errorStr ) +{ + if (!animationData && animationDataId != 0) + if (Sim::findObject(animationDataId, animationData) == false) + Con::errorf(ConsoleLogEntry::General, "LightDescription::onAdd: Invalid packet, bad datablockId(animationData): %d", animationDataId); + + if (!flareData && flareDataId != 0) + if (Sim::findObject(flareDataId, flareData) == false) + Con::errorf(ConsoleLogEntry::General, "LightDescription::onAdd: Invalid packet, bad datablockId(flareData): %d", flareDataId); + + return true; +} + +IMPLEMENT_CONSOLETYPE( LightDescription ) +IMPLEMENT_GETDATATYPE( LightDescription ) +IMPLEMENT_SETDATATYPE( LightDescription ) + +ConsoleMethod( LightDescription, apply, void, 2, 2, "force an inspectPostApply for the benefit of tweaking via the console" ) +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/T3D/lightDescription.h b/T3D/lightDescription.h new file mode 100644 index 0000000..92e2d7d --- /dev/null +++ b/T3D/lightDescription.h @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTDESCRIPTION_H_ +#define _LIGHTDESCRIPTION_H_ + +#ifndef _SIMDATABLOCK_H_ +#include "console/simDatablock.h" +#endif +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _LIGHTANIMDATA_H_ +#include "T3D/lightAnimData.h" +#endif +#ifndef _LIGHTFLAREDATA_H_ +#include "T3D/lightFlareData.h" +#endif + +struct LightState +{ + LightInfo *lightInfo; + F32 fullBrightness; + + LightAnimState animState; + LightFlareState flareState; + + void clear() + { + lightInfo = NULL; + fullBrightness = 1.0f; + animState.clear(); + flareState.clear(); + } + + void setLightInfo( LightInfo *li ) + { + animState.lightInfo = flareState.lightInfo = lightInfo = li; + } +}; + +/// LightDescription is a helper datablock used by classes (such as shapebase) +/// that submit lights to the scene but do not use actual "LightBase" objects. +/// This datablock stores the properties of that light as fields that can be +/// initialized from script. + +class LightAnimData; +class LightFlareData; +class LightManager; +class ISceneLight; + +class LightDescription : public SimDataBlock +{ + typedef SimDataBlock Parent; + +public: + + LightDescription(); + virtual ~LightDescription(); + + DECLARE_CONOBJECT( LightDescription ); + + static void initPersistFields(); + virtual void inspectPostApply(); + + bool onAdd(); + + // SimDataBlock + virtual bool preload( bool server, String &errorStr ); + virtual void packData( BitStream *stream ); + virtual void unpackData( BitStream *stream ); + + //void animateLight( LightState *state ); + void submitLight( LightState *state, const MatrixF &xfm, LightManager *lm, SimObject *object ); + void prepRender( SceneState *sceneState, LightState *lightState, const MatrixF &xfm ); + + bool _preload( bool server, String &errorStr ); + + ColorF color; + F32 brightness; + F32 range; + bool castShadows; + + LightAnimData *animationData; + S32 animationDataId; + F32 animationPeriod; + F32 animationPhase; + + LightFlareData *flareData; + S32 flareDataId; + F32 flareScale; +}; + +DECLARE_CONSOLETYPE(LightDescription) + +#endif // _LIGHTDESCRIPTION_H_ \ No newline at end of file diff --git a/T3D/lightFlareData.cpp b/T3D/lightFlareData.cpp new file mode 100644 index 0000000..5a11b7a --- /dev/null +++ b/T3D/lightFlareData.cpp @@ -0,0 +1,586 @@ + +#include "platform/platform.h" +#include "lightFlareData.h" + +#include "sim/processList.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "math/mathUtils.h" +#include "math/mathIO.h" +#include "T3D/gameConnection.h" +#include "gfx/gfxOcclusionQuery.h" +#include "gfx/gfxDrawUtil.h" + +namespace +{ + void vectorRotateZAxis( Point3F &vec, F32 radians ) + { + VectorF newVec; + F32 newX = mCos(radians) * vec.x - mSin(radians) * vec.y; + F32 newY = mSin(radians) * vec.x + mCos(radians) * vec.y; + + vec.x = newX; + vec.y = newY; + } +} + +LightFlareState::~LightFlareState() +{ + delete occlusionQuery; + delete fullPixelQuery; +} + +void LightFlareState::clear() +{ + visChangedTime = 0; + visible = false; + scale = 1.0f; + fullBrightness = 1.0f; + lightMat = MatrixF::Identity; + lightInfo = NULL; + worldRadius = -1.0f; + occlusion = -1.0f; + occlusionQuery = NULL; + fullPixelQuery = NULL; +} + +LightFlareData::LightFlareData() + : mFlareEnabled( true ), + mElementCount( 0 ), + mScale( 1.0f ), + mOcclusionRadius( 0.5f ) +{ + dMemset( mElementRect, 0, sizeof( RectF ) * MAX_ELEMENTS ); + dMemset( mElementScale, 0, sizeof( F32 ) * MAX_ELEMENTS ); + dMemset( mElementTint, 0, sizeof( ColorF ) * MAX_ELEMENTS ); + dMemset( mElementRotate, 0, sizeof( bool ) * MAX_ELEMENTS ); + dMemset( mElementUseLightColor, 0, sizeof( bool ) * MAX_ELEMENTS ); + + for ( U32 i = 0; i < MAX_ELEMENTS; i++ ) + mElementDist[i] = -1.0f; +} + +LightFlareData::~LightFlareData() +{ +} + +IMPLEMENT_CO_DATABLOCK_V1( LightFlareData ); + +void LightFlareData::initPersistFields() +{ + addGroup( "LightFlareData" ); + + addField( "overallScale", TypeF32, Offset( mScale, LightFlareData ) ); + addField( "occlusionRadius", TypeF32, Offset( mOcclusionRadius, LightFlareData ), + "Radius in world units to test for occlusion if supported by hardware, disable by setting radius non-positive." ); + + endGroup( "LightFlareData" ); + + addGroup( "FlareElements" ); + + addField( "flareEnabled", TypeBool, Offset( mFlareEnabled, LightFlareData ) ); + addField( "flareTexture", TypeImageFilename, Offset( mFlareTextureName, LightFlareData ) ); + + addArray( "Elements", MAX_ELEMENTS ); + + addField( "elementRect", TypeRectF, Offset( mElementRect, LightFlareData ), MAX_ELEMENTS ); + addField( "elementDist", TypeF32, Offset( mElementDist, LightFlareData ), MAX_ELEMENTS ); + addField( "elementScale", TypeF32, Offset( mElementScale, LightFlareData ), MAX_ELEMENTS ); + addField( "elementTint", TypeColorF, Offset( mElementTint, LightFlareData ), MAX_ELEMENTS ); + addField( "elementRotate", TypeBool, Offset( mElementRotate, LightFlareData ), MAX_ELEMENTS ); + addField( "elementUseLightColor", TypeBool, Offset( mElementUseLightColor, LightFlareData ), MAX_ELEMENTS ); + + endArray( "FlareElements" ); + + endGroup( "Flares" ); + + Parent::initPersistFields(); +} + +void LightFlareData::inspectPostApply() +{ + Parent::inspectPostApply(); + + // Hack to allow changing properties in game. + // Do the same work as preload. + + String str; + _preload( false, str ); +} + +bool LightFlareData::preload( bool server, String &errorStr ) +{ + if ( !Parent::preload( server, errorStr ) ) + return false; + + return _preload( server, errorStr ); +} + +void LightFlareData::packData( BitStream *stream ) +{ + Parent::packData( stream ); + + stream->writeFlag( mFlareEnabled ); + stream->write( mFlareTextureName ); + stream->write( mScale ); + stream->write( mOcclusionRadius ); + + stream->write( mElementCount ); + + for ( U32 i = 0; i < mElementCount; i++ ) + { + mathWrite( *stream, mElementRect[i] ); + stream->write( mElementDist[i] ); + stream->write( mElementScale[i] ); + stream->write( mElementTint[i] ); + stream->writeFlag( mElementRotate[i] ); + stream->writeFlag( mElementUseLightColor[i] ); + } +} + +void LightFlareData::unpackData( BitStream *stream ) +{ + Parent::unpackData( stream ); + + mFlareEnabled = stream->readFlag(); + stream->read( &mFlareTextureName ); + stream->read( &mScale ); + stream->read( &mOcclusionRadius ); + + stream->read( &mElementCount ); + + for ( U32 i = 0; i < mElementCount; i++ ) + { + mathRead( *stream, &mElementRect[i] ); + stream->read( &mElementDist[i] ); + stream->read( &mElementScale[i] ); + stream->read( &mElementTint[i] ); + mElementRotate[i] = stream->readFlag(); + mElementUseLightColor[i] = stream->readFlag(); + } +} + +void LightFlareData::prepRender( SceneState *state, LightFlareState *flareState ) +{ + PROFILE_SCOPE( LightFlareData_prepRender ); + + // No elements then nothing to render. + if ( mElementCount == 0 ) + return; + + // We need these all over the place later. + const Point3F &camPos = state->getCameraPosition(); + const RectI &viewport = GFX->getViewport(); + const Point3F &lightPos = flareState->lightMat.getPosition(); + LightInfo *lightInfo = flareState->lightInfo; + + bool isVectorLight = lightInfo->getType() == LightInfo::Vector; + + // Perform visibility testing on the light... + // Project the light position from world to screen space, we need this + // position later, and it tells us if it is actually onscreen. + + Point3F lightPosSS; + bool onscreen = MathUtils::mProjectWorldToScreen( lightPos, &lightPosSS, viewport, GFX->getWorldMatrix(), gClientSceneGraph->getNonClipProjection() ); + + U32 visDelta = U32_MAX; + U32 fadeOutTime = 20; + U32 fadeInTime = 125; + + // Fade factor based on amount of occlusion. + F32 occlusionFade = 1.0f; + + bool lightVisible = true; + + if ( !state->isReflectPass() ) + { + // It is onscreen, so raycast as a simple occlusion test. + + U32 losMask = STATIC_COLLISION_MASK | + ShapeBaseObjectType | + StaticTSObjectType | + ItemObjectType | + PlayerObjectType; + + GameConnection *conn = GameConnection::getConnectionToServer(); + if ( !conn ) + return; + + bool needsRaycast = true; + + // NOTE: if hardware does not support HOQ it will return NULL + // and we will retry every time but there is not currently a good place + // for one-shot initialization of LightFlareState + if ( flareState->occlusionQuery == NULL ) + flareState->occlusionQuery = GFX->createOcclusionQuery(); + if ( flareState->fullPixelQuery == NULL ) + flareState->fullPixelQuery = GFX->createOcclusionQuery(); + + if ( flareState->occlusionQuery && + ( ( isVectorLight && flareState->worldRadius > 0.0f ) || + ( !isVectorLight && mOcclusionRadius > 0.0f ) ) ) + { + // Always treat light as onscreen if using HOQ + // it will be faded out if offscreen anyway. + onscreen = true; + + U32 pixels = -1; + GFXOcclusionQuery::OcclusionQueryStatus status = flareState->occlusionQuery->getStatus( true, &pixels ); + + String str = flareState->occlusionQuery->statusToString( status ); + Con::setVariable( "$Flare::OcclusionStatus", str.c_str() ); + Con::setIntVariable( "$Flare::OcclusionVal", pixels ); + + if ( status == GFXOcclusionQuery::Occluded ) + occlusionFade = 0.0f; + + if ( status != GFXOcclusionQuery::Unset ) + needsRaycast = false; + + RenderPassManager *pass = state->getRenderPass(); + + OccluderRenderInst *ri = pass->allocInst(); + + Point3F scale( Point3F::One ); + + if ( isVectorLight && flareState->worldRadius > 0.0f ) + scale *= flareState->worldRadius; + else + scale *= mOcclusionRadius; + + ri->type = RenderPassManager::RIT_Occluder; + ri->query = flareState->occlusionQuery; + ri->query2 = flareState->fullPixelQuery; + ri->position = lightPos; + ri->scale = scale; + ri->orientation = pass->allocUniqueXform( lightInfo->getTransform() ); + ri->isSphere = true; + state->getRenderPass()->addInst( ri ); + + if ( status == GFXOcclusionQuery::NotOccluded ) + { + U32 fullPixels; + flareState->fullPixelQuery->getStatus( true, &fullPixels ); + + occlusionFade = (F32)pixels / (F32)fullPixels; + + // Approximation of the full pixel count rather than doing + // two queries, but it is not very accurate. + /* + F32 dist = ( camPos - lightPos ).len(); + F32 radius = scale.x; + radius = ( radius / dist ) * state->getWorldToScreenScale().y; + + occlusionFade = (F32)pixels / (4.0f * radius * radius); + occlusionFade = mClampF( occlusionFade, 0.0f, 1.0f ); + */ + } + } + + Con::setFloatVariable( "$Flare::OcclusionFade", occlusionFade ); + + if ( needsRaycast ) + { + // Use a raycast to determine occlusion. + + bool fps = conn->isFirstPerson(); + + GameBase *control = conn->getControlObject(); + if ( control && fps ) + control->disableCollision(); + + RayInfo rayInfo; + + if ( gClientContainer.castRayRendered( camPos, lightPos, losMask, &rayInfo ) ) + occlusionFade = 0.0f; + + if ( control && fps ) + control->enableCollision(); + } + + lightVisible = onscreen && occlusionFade > 0.0f; + + // To perform a fade in/out when we gain or lose visibility + // we must update/store the visibility state and time. + + U32 currentTime = Sim::getCurrentTime(); + + if ( lightVisible != flareState->visible ) + { + flareState->visible = lightVisible; + flareState->visChangedTime = currentTime; + } + + // Save this in the state so that we have it during the reflect pass. + flareState->occlusion = occlusionFade; + + visDelta = currentTime - flareState->visChangedTime; + } + else // state->isReflectPass() + { + occlusionFade = flareState->occlusion; + lightVisible = flareState->visible; + visDelta = Sim::getCurrentTime() - flareState->visChangedTime; + } + + // We can only skip rendering if the light is not visible, and it + // has elapsed the fadeOutTime. + if ( !lightVisible && visDelta > fadeOutTime ) + return; + + // In a reflection we only render the elements with zero distance. + U32 elementCount = mElementCount; + if ( state->isReflectPass() ) + { + elementCount = 0; + for ( ; elementCount < mElementCount; elementCount++ ) + { + if ( mElementDist[elementCount] > 0.0f ) + break; + } + } + + if ( elementCount == 0 ) + return; + + // A bunch of preparatory math before generating verts... + + const Point2I &vpExtent = viewport.extent; + Point3F viewportExtent( vpExtent.x, vpExtent.y, 1.0f ); + Point2I halfViewportExtentI( viewport.extent / 2 ); + Point3F halfViewportExtentF( (F32)halfViewportExtentI.x * 0.5f, (F32)halfViewportExtentI.y, 0.0f ); + Point3F screenCenter( 0,0,0 ); + Point3F oneOverViewportExtent( 1.0f / viewportExtent.x, 1.0f / viewportExtent.y, 1.0f ); + + lightPosSS.y -= viewport.point.y; + lightPosSS *= oneOverViewportExtent; + lightPosSS = ( lightPosSS * 2.0f ) - Point3F::One; + lightPosSS.y = -lightPosSS.y; + lightPosSS.z = 0.0f; + + Point3F flareVec( screenCenter - lightPosSS ); + F32 flareLength = flareVec.len(); + flareVec.normalizeSafe(); + + Point3F basePoints[4]; + basePoints[0] = Point3F( -0.5, 0.5, 0.0 ); + basePoints[1] = Point3F( -0.5, -0.5, 0.0 ); + basePoints[2] = Point3F( 0.5, -0.5, 0.0 ); + basePoints[3] = Point3F( 0.5, 0.5, 0.0 ); + + Point3F rotatedBasePoints[4]; + rotatedBasePoints[0] = basePoints[0]; + rotatedBasePoints[1] = basePoints[1]; + rotatedBasePoints[2] = basePoints[2]; + rotatedBasePoints[3] = basePoints[3]; + + Point3F fvec( -1, 0, 0 ); + F32 rot = mAcos( mDot( fvec, flareVec ) ); + Point3F rvec( 0, -1, 0 ); + rot *= mDot( rvec, flareVec ) > 0.0f ? 1.0f : -1.0f; + + vectorRotateZAxis( rotatedBasePoints[0], rot ); + vectorRotateZAxis( rotatedBasePoints[1], rot ); + vectorRotateZAxis( rotatedBasePoints[2], rot ); + vectorRotateZAxis( rotatedBasePoints[3], rot ); + + // Here we calculate a the light source's influence on the effect's size + // and brightness... + + // Scale based on the current light brightness compared to its normal output. + F32 lightSourceBrightnessScale = lightInfo->getBrightness() / flareState->fullBrightness; + // Scale based on world space distance from camera to light source. + F32 lightSourceWSDistanceScale = ( isVectorLight ) ? 1.0f : getMin( 10.0f / ( lightPos - camPos ).len(), 1.5f ); + // Scale based on screen space distance from screen position of light source to the screen center. + F32 lightSourceSSDistanceScale = ( 1.5f - ( lightPosSS - screenCenter ).len() ) / 1.5f; + + // Scale based on recent visibility changes, fading in or out. + F32 fadeInOutScale = 1.0f; + if ( lightVisible && visDelta < fadeInTime && flareState->occlusion ) + fadeInOutScale = (F32)visDelta / (F32)fadeInTime; + else if ( !lightVisible && visDelta < fadeOutTime ) + fadeInOutScale = 1.0f - (F32)visDelta / (F32)fadeOutTime; + + // This combined scale influences the size of all elements this effect renders. + // Note we also add in a scale that is user specified in the Light. + F32 lightSourceIntensityScale = lightSourceBrightnessScale * + lightSourceWSDistanceScale * + lightSourceSSDistanceScale * + fadeInOutScale * + flareState->scale * + occlusionFade; + + // The baseColor which modulates the color of all elements. + ColorF baseColor; + if ( flareState->fullBrightness == 0.0f ) + baseColor = ColorF::BLACK; + else + // These are the factors which affect the "alpha" of the flare effect. + // Modulate more in as appropriate. + baseColor = ColorF::WHITE * lightSourceBrightnessScale * occlusionFade; + + // Fill in the vertex buffer... + const U32 vertCount = 4 * elementCount; + if ( flareState->vertBuffer.isNull() || + flareState->vertBuffer->mNumVerts != vertCount ) + flareState->vertBuffer.set( GFX, vertCount, GFXBufferTypeDynamic ); + + GFXVertexPCT *pVert = flareState->vertBuffer.lock(); + + const Point2I &widthHeightI = mFlareTexture.getWidthHeight(); + Point2F oneOverTexSize( 1.0f / (F32)widthHeightI.x, 1.0f / (F32)widthHeightI.y ); + + for ( U32 i = 0; i < elementCount; i++ ) + { + Point3F *basePos = mElementRotate[i] ? rotatedBasePoints : basePoints; + + ColorF elementColor( baseColor * mElementTint[i] ); + if ( mElementUseLightColor[i] ) + elementColor *= lightInfo->getColor(); + + Point3F elementPos; + elementPos = lightPosSS + flareVec * mElementDist[i] * flareLength; + elementPos.z = 0.0f; + + F32 maxDist = 1.5f; + F32 elementDist = mSqrt( ( elementPos.x * elementPos.x ) + ( elementPos.y * elementPos.y ) ); + F32 distanceScale = ( maxDist - elementDist ) / maxDist; + distanceScale = 1.0f; + + const RectF &elementRect = mElementRect[i]; + Point3F elementSize( elementRect.extent.x, elementRect.extent.y, 1.0f ); + elementSize *= mElementScale[i] * distanceScale * mScale * lightSourceIntensityScale; + + if ( elementSize.x < 100.0f ) + { + F32 alphaScale = mPow( elementSize.x / 100.0f, 2 ); + elementColor *= alphaScale; + } + + elementColor.clamp(); + + Point2F texCoordMin, texCoordMax; + texCoordMin = elementRect.point * oneOverTexSize; + texCoordMax = ( elementRect.point + elementRect.extent ) * oneOverTexSize; + + pVert->color = elementColor; + pVert->point = ( basePos[0] * elementSize * oneOverViewportExtent ) + elementPos; + pVert->texCoord.set( texCoordMin.x, texCoordMax.y ); + pVert++; + + pVert->color = elementColor; + pVert->point = ( basePos[1] * elementSize * oneOverViewportExtent ) + elementPos; + pVert->texCoord.set( texCoordMax.x, texCoordMax.y ); + pVert++; + + pVert->color = elementColor; + pVert->point = ( basePos[2] * elementSize * oneOverViewportExtent ) + elementPos; + pVert->texCoord.set( texCoordMax.x, texCoordMin.y ); + pVert++; + + pVert->color = elementColor; + pVert->point = ( basePos[3] * elementSize * oneOverViewportExtent ) + elementPos; + pVert->texCoord.set( texCoordMin.x, texCoordMin.y ); + pVert++; + } + + flareState->vertBuffer.unlock(); + + // Create and submit the render instance... + + RenderPassManager *renderManager = state->getRenderPass(); + ParticleRenderInst *ri = renderManager->allocInst(); + + ri->vertBuff = &flareState->vertBuffer; + ri->primBuff = &mFlarePrimBuffer; + ri->translucentSort = true; + ri->type = RenderPassManager::RIT_Particle; + ri->sortDistSq = ( lightPos - camPos ).lenSquared(); + + ri->modelViewProj = &MatrixF::Identity; + ri->bbModelViewProj = ri->modelViewProj; + + ri->count = elementCount; + + // Only draw the light flare in high-res mode, never off-screen mode + ri->systemState = ParticleRenderInst::AwaitingHighResDraw; + + ri->blendStyle = ParticleRenderInst::BlendGreyscale; + + ri->diffuseTex = &*(mFlareTexture); + + ri->softnessDistance = 1.0f; + + // Sort by texture too. + ri->defaultKey = ri->diffuseTex ? (U32)ri->diffuseTex : (U32)ri->vertBuff; + + renderManager->addInst( ri ); +} + +bool LightFlareData::_preload( bool server, String &errorStr ) +{ + mElementCount = 0; + for ( U32 i = 0; i < MAX_ELEMENTS; i++ ) + { + if ( mElementDist[i] == -1 ) + break; + mElementCount = i + 1; + } + + if ( mElementCount > 0 ) + _makePrimBuffer( &mFlarePrimBuffer, mElementCount ); + + if ( !server ) + { + if ( mFlareTextureName.isNotEmpty() ) + mFlareTexture.set( mFlareTextureName, &GFXDefaultStaticDiffuseProfile, "FlareTexture" ); + } + + return true; +} + +void LightFlareData::_makePrimBuffer( GFXPrimitiveBufferHandle *pb, U32 count ) +{ + // create index buffer based on that size + U32 indexListSize = count * 6; // 6 indices per particle + U16 *indices = new U16[ indexListSize ]; + + for ( U32 i = 0; i < count; i++ ) + { + // this index ordering should be optimal (hopefully) for the vertex cache + U16 *idx = &indices[i*6]; + volatile U32 offset = i * 4; // set to volatile to fix VC6 Release mode compiler bug + idx[0] = 0 + offset; + idx[1] = 1 + offset; + idx[2] = 3 + offset; + idx[3] = 1 + offset; + idx[4] = 3 + offset; + idx[5] = 2 + offset; + } + + U16 *ibIndices; + GFXBufferType bufferType = GFXBufferTypeStatic; + +#ifdef TORQUE_OS_XENON + // Because of the way the volatile buffers work on Xenon this is the only + // way to do this. + bufferType = GFXBufferTypeVolatile; +#endif + pb->set( GFX, indexListSize, 0, bufferType ); + pb->lock( &ibIndices ); + dMemcpy( ibIndices, indices, indexListSize * sizeof(U16) ); + pb->unlock(); + + delete [] indices; +} + +IMPLEMENT_CONSOLETYPE( LightFlareData ) +IMPLEMENT_GETDATATYPE( LightFlareData ) +IMPLEMENT_SETDATATYPE( LightFlareData ) + + +ConsoleMethod( LightFlareData, apply, void, 2, 2, "force an inspectPostApply for the benefit of tweaking via the console" ) +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/T3D/lightFlareData.h b/T3D/lightFlareData.h new file mode 100644 index 0000000..465e7e9 --- /dev/null +++ b/T3D/lightFlareData.h @@ -0,0 +1,106 @@ + +#ifndef _LIGHTFLAREDATA_H_ +#define _LIGHTFLAREDATA_H_ + +#ifndef _SIMDATABLOCK_H_ +#include "console/simDatablock.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + +class LightInfo; +struct ObjectRenderInst; +class SceneState; +class BaseMatInstance; +class GFXOcclusionQuery; + +struct LightFlareState +{ + ~LightFlareState(); + void clear(); + + /// Object calling LightFlareData::prepRender fills these in! + F32 scale; + F32 fullBrightness; + MatrixF lightMat; + LightInfo *lightInfo; + F32 worldRadius; + + /// Used internally by LightFlareData! + U32 visChangedTime; + bool visible; + F32 occlusion; + GFXVertexBufferHandle vertBuffer; + GFXOcclusionQuery *occlusionQuery; + GFXOcclusionQuery *fullPixelQuery; +}; + +class LightFlareData : public SimDataBlock +{ + typedef SimDataBlock Parent; + + #define MAX_ELEMENTS 10 + +public: + + LightFlareData(); + virtual ~LightFlareData(); + + DECLARE_CONOBJECT( LightFlareData ); + + static void initPersistFields(); + virtual void inspectPostApply(); + + // SimDataBlock + virtual bool preload( bool server, String &errorStr ); + virtual void packData( BitStream *stream ); + virtual void unpackData( BitStream *stream ); + + /// Submits render instances for corona and flare effects. + void prepRender( SceneState *state, LightFlareState *flareState ); + +protected: + + bool _preload( bool server, String &errorStr ); + void _makePrimBuffer( GFXPrimitiveBufferHandle *pb, U32 count ); + void _renderCorona( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + +protected: + + // Fields... + + F32 mScale; + bool mFlareEnabled; + String mFlareTextureName; + GFXTexHandle mFlareTexture; + F32 mOcclusionRadius; + + RectF mElementRect[MAX_ELEMENTS]; + F32 mElementDist[MAX_ELEMENTS]; + F32 mElementScale[MAX_ELEMENTS]; + ColorF mElementTint[MAX_ELEMENTS]; + bool mElementRotate[MAX_ELEMENTS]; + bool mElementUseLightColor[MAX_ELEMENTS]; + +protected: + + U32 mElementCount; + GFXPrimitiveBufferHandle mFlarePrimBuffer; +}; + +DECLARE_CONSOLETYPE(LightFlareData) + +#endif // _LIGHTFLAREDATA_H_ \ No newline at end of file diff --git a/T3D/missionArea.cpp b/T3D/missionArea.cpp new file mode 100644 index 0000000..6aa0d28 --- /dev/null +++ b/T3D/missionArea.cpp @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/missionArea.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" + +IMPLEMENT_CO_NETOBJECT_V1(MissionArea); + +RectI MissionArea::smMissionArea(Point2I(768, 768), Point2I(512, 512)); + +//------------------------------------------------------------------------------ + +MissionArea::MissionArea() +{ + mArea.set(Point2I(768, 768), Point2I(512, 512)); + mNetFlags.set(Ghostable | ScopeAlways); + + mFlightCeiling = 2000; + mFlightCeilingRange = 50; +} + +//------------------------------------------------------------------------------ + +void MissionArea::setArea(const RectI & area) +{ + // set it + mArea = MissionArea::smMissionArea = area; + + // pass along.. + if(isServerObject()) + mNetFlags.set(UpdateMask); +} + +//------------------------------------------------------------------------------ + +const MissionArea * MissionArea::getServerObject() +{ + SimSet * scopeAlwaysSet = Sim::getGhostAlwaysSet(); + for(SimSet::iterator itr = scopeAlwaysSet->begin(); itr != scopeAlwaysSet->end(); itr++) + { + MissionArea * ma = dynamic_cast(*itr); + if(ma) + { + AssertFatal(ma->isServerObject(), "MissionArea::getServerObject: found client object in ghost always set!"); + return(ma); + } + } + return(0); +} + +//------------------------------------------------------------------------------ + +bool MissionArea::onAdd() +{ + if(isServerObject() && MissionArea::getServerObject()) + { + Con::errorf(ConsoleLogEntry::General, "MissionArea::onAdd - MissionArea already instantiated!"); + return(false); + } + + if(!Parent::onAdd()) + return(false); + + setArea(mArea); + return(true); +} + +//------------------------------------------------------------------------------ + +void MissionArea::initPersistFields() +{ + addGroup("Dimensions"); + addField("area", TypeRectI, Offset(mArea, MissionArea)); + addField("flightCeiling", TypeF32, Offset(mFlightCeiling, MissionArea)); + addField("flightCeilingRange", TypeF32, Offset(mFlightCeilingRange, MissionArea)); + endGroup("Dimensions"); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( MissionArea, getArea, const char *, 2, 2, "()" + "Returns 4 fields: starting x, starting y, extents x, extents y") +{ + static char buf[48]; + + RectI area = object->getArea(); + dSprintf(buf, sizeof(buf), "%d %d %d %d", area.point.x, area.point.y, area.extent.x, area.extent.y); + return(buf); +} + +ConsoleMethod( MissionArea, setArea, void, 6, 6, "(int x, int y, int width, int height)") +{ + if(object->isClientObject()) + { + Con::errorf(ConsoleLogEntry::General, "MissionArea::cSetArea - cannot alter client object!"); + return; + } + + RectI rect; + rect.point.x = dAtoi(argv[2]); + rect.point.y = dAtoi(argv[3]); + rect.extent.x = dAtoi(argv[4]); + rect.extent.y = dAtoi(argv[5]); + + object->setArea(rect); +} + +//------------------------------------------------------------------------------ + +void MissionArea::unpackUpdate(NetConnection *, BitStream * stream) +{ + // ghost (initial) and regular updates share flag.. + if(stream->readFlag()) + { + mathRead(*stream, &mArea); + stream->read(&mFlightCeiling); + stream->read(&mFlightCeilingRange); + } +} + +U32 MissionArea::packUpdate(NetConnection *, U32 mask, BitStream * stream) +{ + if(stream->writeFlag(mask & UpdateMask)) + { + mathWrite(*stream, mArea); + stream->write(mFlightCeiling); + stream->write(mFlightCeilingRange); + } + return(0); +} diff --git a/T3D/missionArea.h b/T3D/missionArea.h new file mode 100644 index 0000000..b61242c --- /dev/null +++ b/T3D/missionArea.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MISSIONAREA_H_ +#define _MISSIONAREA_H_ + +#ifndef _NETOBJECT_H_ +#include "sim/netObject.h" +#endif + +class MissionArea : public NetObject +{ + private: + typedef NetObject Parent; + RectI mArea; + + F32 mFlightCeiling; + F32 mFlightCeilingRange; + + public: + MissionArea(); + + static RectI smMissionArea; + + static const MissionArea * getServerObject(); + + F32 getFlightCeiling() const { return mFlightCeiling; } + F32 getFlightCeilingRange() const { return mFlightCeilingRange; } + + // + const RectI & getArea(){return(mArea);} + void setArea(const RectI & area); + + /// @name SimObject Inheritance + /// @{ + bool onAdd(); + + static void initPersistFields(); + /// @} + + /// @name NetObject Inheritance + /// @{ + enum NetMaskBits { + UpdateMask = BIT(0) + }; + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + /// @} + + DECLARE_CONOBJECT(MissionArea); +}; + +#endif diff --git a/T3D/missionMarker.cpp b/T3D/missionMarker.cpp new file mode 100644 index 0000000..48536e1 --- /dev/null +++ b/T3D/missionMarker.cpp @@ -0,0 +1,452 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/missionMarker.h" +#include "console/consoleTypes.h" +#include "core/color.h" + +extern bool gEditingMission; +IMPLEMENT_CO_DATABLOCK_V1(MissionMarkerData); + +//------------------------------------------------------------------------------ +// Class: MissionMarker +//------------------------------------------------------------------------------ +IMPLEMENT_CO_NETOBJECT_V1(MissionMarker); + +MissionMarker::MissionMarker() +{ + mTypeMask |= StaticShapeObjectType | StaticObjectType; + mDataBlock = 0; + mAddedToScene = false; + mNetFlags.set(Ghostable | ScopeAlways); +} + +bool MissionMarker::onAdd() +{ + if(!Parent::onAdd() || !mDataBlock) + return(false); + + if(gEditingMission) + { + addToScene(); + mAddedToScene = true; + } + + return(true); +} + +void MissionMarker::onRemove() +{ + if( mAddedToScene ) + { + removeFromScene(); + mAddedToScene = false; + } + + Parent::onRemove(); +} + +void MissionMarker::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits(PositionMask); +} + +void MissionMarker::onEditorEnable() +{ + if(!mAddedToScene) + { + addToScene(); + mAddedToScene = true; + } +} + +void MissionMarker::onEditorDisable() +{ + if(mAddedToScene) + { + removeFromScene(); + mAddedToScene = false; + } +} + +bool MissionMarker::onNewDataBlock(GameBaseData * dptr) +{ + mDataBlock = dynamic_cast(dptr); + if(!mDataBlock || !Parent::onNewDataBlock(dptr)) + return(false); + scriptOnNewDataBlock(); + return(true); +} + +void MissionMarker::setTransform(const MatrixF& mat) +{ + Parent::setTransform(mat); + setMaskBits(PositionMask); +} + +U32 MissionMarker::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + if(stream->writeFlag(mask & PositionMask)) + { + stream->writeAffineTransform(mObjToWorld); + mathWrite(*stream, mObjScale); + } + + return(retMask); +} + +void MissionMarker::unpackUpdate(NetConnection * con, BitStream * stream) +{ + Parent::unpackUpdate(con, stream); + if(stream->readFlag()) + { + MatrixF mat; + stream->readAffineTransform(&mat); + Parent::setTransform(mat); + + Point3F scale; + mathRead(*stream, &scale); + setScale(scale); + } +} + +void MissionMarker::initPersistFields() { + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +// Class: WayPoint +//------------------------------------------------------------------------------ +IMPLEMENT_CO_NETOBJECT_V1(WayPoint); + +WayPointTeam::WayPointTeam() +{ + mTeamId = 0; + mWayPoint = 0; +} + +WayPoint::WayPoint() +{ + mName = StringTable->insert(""); +} + +void WayPoint::setHidden(bool hidden) +{ + if(isServerObject()) + setMaskBits(UpdateHiddenMask); + mHidden = hidden; +} + +bool WayPoint::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // + if(isClientObject()) + Sim::getWayPointSet()->addObject(this); + else + { + mTeam.mWayPoint = this; + setMaskBits(UpdateNameMask|UpdateTeamMask); + } + + return(true); +} + +void WayPoint::inspectPostApply() +{ + Parent::inspectPostApply(); + if(!mName || !mName[0]) + mName = StringTable->insert(""); + setMaskBits(UpdateNameMask|UpdateTeamMask); +} + +U32 WayPoint::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + if(stream->writeFlag(mask & UpdateNameMask)) + stream->writeString(mName); + if(stream->writeFlag(mask & UpdateTeamMask)) + stream->write(mTeam.mTeamId); + if(stream->writeFlag(mask & UpdateHiddenMask)) + stream->writeFlag(mHidden); + return(retMask); +} + +void WayPoint::unpackUpdate(NetConnection * con, BitStream * stream) +{ + Parent::unpackUpdate(con, stream); + if(stream->readFlag()) + mName = stream->readSTString(true); + if(stream->readFlag()) + stream->read(&mTeam.mTeamId); + if(stream->readFlag()) + mHidden = stream->readFlag(); +} + +//----------------------------------------------------------------------------- +// TypeWayPointTeam +//----------------------------------------------------------------------------- +ConsoleType( WayPointTeam, TypeWayPointTeam, WayPointTeam ) + +ConsoleGetType( TypeWayPointTeam ) +{ + char * buf = Con::getReturnBuffer(32); + dSprintf(buf, 32, "%d", ((WayPointTeam*)dptr)->mTeamId); + return(buf); +} + +ConsoleSetType( TypeWayPointTeam ) +{ + WayPointTeam * pTeam = (WayPointTeam*)dptr; + pTeam->mTeamId = dAtoi(argv[0]); + + if(pTeam->mWayPoint && pTeam->mWayPoint->isServerObject()) + pTeam->mWayPoint->setMaskBits(WayPoint::UpdateTeamMask); +} + +void WayPoint::initPersistFields() +{ + addGroup("Misc"); + addField("name", TypeCaseString, Offset(mName, WayPoint)); + addField("team", TypeWayPointTeam, Offset(mTeam, WayPoint)); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +// Class: SpawnSphere +//------------------------------------------------------------------------------ +IMPLEMENT_CO_NETOBJECT_V1(SpawnSphere); + +Sphere SpawnSphere::smSphere(Sphere::Octahedron); + +SpawnSphere::SpawnSphere() +{ + mAutoSpawn = false; + + mRadius = 100.f; + mSphereWeight = 100.f; + mIndoorWeight = 100.f; + mOutdoorWeight = 100.f; +} + +bool SpawnSphere::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + if(!isClientObject()) + setMaskBits(UpdateSphereMask); + + if (!isGhost()) + { + Con::executef(this, "onAdd",scriptThis()); + + if (mAutoSpawn) + spawnObject(); + } + + return true; +} + +SimObject* SpawnSphere::spawnObject(String additionalProps) +{ + SimObject* spawnObject = Sim::spawnObject(mSpawnClass, mSpawnDataBlock, mSpawnName, + mSpawnProperties + " " + additionalProps, mSpawnScript); + + // If we have a spawnObject add it to the MissionCleanup group + if (spawnObject) + { + SimObject* cleanup = Sim::findObject("MissionCleanup"); + + if (cleanup) + { + SimGroup* missionCleanup = dynamic_cast(cleanup); + + missionCleanup->addObject(spawnObject); + } + } + + return spawnObject; +} + +void SpawnSphere::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits(UpdateSphereMask); +} + +U32 SpawnSphere::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // + if(stream->writeFlag(mask & UpdateSphereMask)) + { + stream->writeFlag(mAutoSpawn); + + stream->write(mSpawnClass); + stream->write(mSpawnDataBlock); + stream->write(mSpawnName); + stream->write(mSpawnProperties); + stream->write(mSpawnScript); + + stream->write(mRadius); + stream->write(mSphereWeight); + stream->write(mIndoorWeight); + stream->write(mOutdoorWeight); + } + return(retMask); +} + +void SpawnSphere::unpackUpdate(NetConnection * con, BitStream * stream) +{ + Parent::unpackUpdate(con, stream); + if(stream->readFlag()) + { + mAutoSpawn = stream->readFlag(); + + stream->read(&mSpawnClass); + stream->read(&mSpawnDataBlock); + stream->read(&mSpawnName); + stream->read(&mSpawnProperties); + stream->read(&mSpawnScript); + + stream->read(&mRadius); + stream->read(&mSphereWeight); + stream->read(&mIndoorWeight); + stream->read(&mOutdoorWeight); + } +} + +void SpawnSphere::initPersistFields() +{ + addGroup("Spawn"); + addField("spawnClass", TypeRealString, Offset(mSpawnClass, SpawnSphere)); + addField("spawnDatablock", TypeRealString, Offset(mSpawnDataBlock, SpawnSphere)); + addField("spawnProperties", TypeRealString, Offset(mSpawnProperties, SpawnSphere)); + addField("spawnScript", TypeCommand, Offset(mSpawnScript, SpawnSphere), "Command to execute when spawning an object. New object id is stored in $SpawnObject. Max 255 characters." ); + addField("autoSpawn", TypeBool, Offset(mAutoSpawn, SpawnSphere)); + endGroup("Spawn"); + + addGroup("Dimensions"); + addField("radius", TypeF32, Offset(mRadius, SpawnSphere)); + endGroup("Dimensions"); + + addGroup("Weight"); + addField("sphereWeight", TypeF32, Offset(mSphereWeight, SpawnSphere)); + addField("indoorWeight", TypeF32, Offset(mIndoorWeight, SpawnSphere)); + addField("outdoorWeight", TypeF32, Offset(mOutdoorWeight, SpawnSphere)); + endGroup("Weight"); + + Parent::initPersistFields(); +} + +ConsoleMethod(SpawnSphere, spawnObject, S32, 2, 3, "([string additionalProps]) Spawns the object based \ + on the SpawnSphere's class, datablock, properties, \ + and script settings. Allows you to pass in extra properties.") +{ + String additionalProps; + + if (argc == 3) + additionalProps = String(argv[2]); + + SimObject* obj = object->spawnObject(additionalProps); + + if (obj) + return obj->getId(); + + return -1; +} + + +//------------------------------------------------------------------------------ +// Class: CameraBookmark +//------------------------------------------------------------------------------ +IMPLEMENT_CO_NETOBJECT_V1(CameraBookmark); + +CameraBookmark::CameraBookmark() +{ + mName = StringTable->insert(""); +} + +bool CameraBookmark::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // + if(!isClientObject()) + { + setMaskBits(UpdateNameMask); + } + + if( isServerObject() && isMethod("onAdd") ) + Con::executef( this, "onAdd" ); + + return(true); +} + +void CameraBookmark::onRemove() +{ + if( isServerObject() && isMethod("onRemove") ) + Con::executef( this, "onRemove" ); + + Parent::onRemove(); +} + +void CameraBookmark::onGroupAdd() +{ + if( isServerObject() && isMethod("onGroupAdd") ) + Con::executef( this, "onGroupAdd" ); +} + +void CameraBookmark::onGroupRemove() +{ + if( isServerObject() && isMethod("onGroupRemove") ) + Con::executef( this, "onGroupRemove" ); +} + +void CameraBookmark::inspectPostApply() +{ + Parent::inspectPostApply(); + if(!mName || !mName[0]) + mName = StringTable->insert(""); + setMaskBits(UpdateNameMask); + + if( isMethod("onInspectPostApply") ) + Con::executef( this, "onInspectPostApply" ); +} + +U32 CameraBookmark::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + if(stream->writeFlag(mask & UpdateNameMask)) + stream->writeString(mName); + return(retMask); +} + +void CameraBookmark::unpackUpdate(NetConnection * con, BitStream * stream) +{ + Parent::unpackUpdate(con, stream); + if(stream->readFlag()) + mName = stream->readSTString(true); +} + +void CameraBookmark::initPersistFields() +{ + //addGroup("Misc"); + //addField("name", TypeCaseString, Offset(mName, CameraBookmark)); + //endGroup("Misc"); + + Parent::initPersistFields(); + + removeField("nameTag"); // From GameBase +} diff --git a/T3D/missionMarker.h b/T3D/missionMarker.h new file mode 100644 index 0000000..96aac35 --- /dev/null +++ b/T3D/missionMarker.h @@ -0,0 +1,210 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MISSIONMARKER_H_ +#define _MISSIONMARKER_H_ + +#ifndef _BITSTREAM_H_ +#include "core/stream/bitStream.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif +#ifndef _MATHIO_H_ +#include "math/mathIO.h" +#endif +#ifndef _SPHERE_H_ +#include "T3D/sphere.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + +class MissionMarkerData : public ShapeBaseData +{ + private: + typedef ShapeBaseData Parent; + public: + DECLARE_CONOBJECT(MissionMarkerData); +}; + +//------------------------------------------------------------------------------ +// Class: MissionMarker +//------------------------------------------------------------------------------ +class MissionMarker : public ShapeBase +{ + private: + typedef ShapeBase Parent; + + protected: + enum MaskBits { + PositionMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + MissionMarkerData * mDataBlock; + bool mAddedToScene; + + public: + MissionMarker(); + + // GameBase + bool onNewDataBlock(GameBaseData *dptr); + + // SceneObject + void setTransform(const MatrixF &mat); + + // SimObject + bool onAdd(); + void onRemove(); + void onEditorEnable(); + void onEditorDisable(); + + void inspectPostApply(); + + // NetObject + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + DECLARE_CONOBJECT(MissionMarker); + static void initPersistFields(); +}; + +//------------------------------------------------------------------------------ +// Class: WayPoint +//------------------------------------------------------------------------------ +class WayPoint; +class WayPointTeam +{ + public: + WayPointTeam(); + + S32 mTeamId; + WayPoint * mWayPoint; +}; +DefineConsoleType( TypeWayPointTeam, WayPointTeam * ) + +class WayPoint : public MissionMarker +{ + private: + typedef MissionMarker Parent; + + public: + enum WayPointMasks { + UpdateNameMask = Parent::NextFreeMask, + UpdateTeamMask = Parent::NextFreeMask << 1, + UpdateHiddenMask = Parent::NextFreeMask << 2, + NextFreeMask = Parent::NextFreeMask << 3 + }; + + WayPoint(); + + // ShapeBase: only ever added to scene if in the editor + void setHidden(bool hidden); + + // SimObject + bool onAdd(); + void inspectPostApply(); + + // NetObject + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + // field data + StringTableEntry mName; + WayPointTeam mTeam; + + static void initPersistFields(); + + DECLARE_CONOBJECT(WayPoint); +}; + +//------------------------------------------------------------------------------ +// Class: SpawnSphere +//------------------------------------------------------------------------------ + +class SpawnSphere : public MissionMarker +{ + private: + typedef MissionMarker Parent; + static Sphere smSphere; + + public: + SpawnSphere(); + + // SimObject + bool onAdd(); + void inspectPostApply(); + + // NetObject + enum SpawnSphereMasks + { + UpdateSphereMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + // Spawn info + String mSpawnClass; + String mSpawnDataBlock; + String mSpawnName; + String mSpawnProperties; + String mSpawnScript; + bool mAutoSpawn; + + // Radius/weight info + F32 mRadius; + F32 mSphereWeight; + F32 mIndoorWeight; + F32 mOutdoorWeight; + + SimObject* spawnObject(String additionalProps = String::EmptyString); + + static void initPersistFields(); + + DECLARE_CONOBJECT(SpawnSphere); +}; + +#endif + +//------------------------------------------------------------------------------ +// Class: CameraBookmark +//------------------------------------------------------------------------------ + +class CameraBookmark : public MissionMarker +{ + private: + typedef MissionMarker Parent; + + public: + enum WayPointMasks { + UpdateNameMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + CameraBookmark(); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + virtual void onGroupAdd(); + virtual void onGroupRemove(); + void inspectPostApply(); + + // NetObject + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + // field data + StringTableEntry mName; + + static void initPersistFields(); + + DECLARE_CONOBJECT(CameraBookmark); +}; diff --git a/T3D/moveList.cpp b/T3D/moveList.cpp new file mode 100644 index 0000000..8d7af05 --- /dev/null +++ b/T3D/moveList.cpp @@ -0,0 +1,320 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/moveList.h" +#include "T3D/gameConnection.h" +#include "core/stream/bitStream.h" +#include "T3D/gameBase.h" + +#define MAX_MOVE_PACKET_SENDS 4 + + +MoveList::MoveList() +{ + VECTOR_SET_ASSOCIATION( mMoveList ); + + mLastMoveAck = 0; + mLastClientMove = 0; + mFirstMoveIndex = 0; + mMoveCredit = MaxMoveCount; + mControlMismatch = false; + mConnection = NULL; +} + +bool MoveList::getNextMove(Move &curMove) +{ + if(mMoveList.size() > MaxMoveQueueSize) + return false; + + F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed; + F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed; + F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed; + + curMove.pitch = MoveManager::mPitch + pitchAdd; + curMove.yaw = MoveManager::mYaw + yawAdd; + curMove.roll = MoveManager::mRoll + rollAdd; + + MoveManager::mPitch = 0; + MoveManager::mYaw = 0; + MoveManager::mRoll = 0; + + curMove.x = MoveManager::mRightAction - MoveManager::mLeftAction + MoveManager::mXAxis_L; + curMove.y = MoveManager::mForwardAction - MoveManager::mBackwardAction + MoveManager::mYAxis_L; + curMove.z = MoveManager::mUpAction - MoveManager::mDownAction; + + curMove.freeLook = MoveManager::mFreeLook; + curMove.deviceIsKeyboardMouse = MoveManager::mDeviceIsKeyboardMouse; + + for(U32 i = 0; i < MaxTriggerKeys; i++) + { + curMove.trigger[i] = false; + if(MoveManager::mTriggerCount[i] & 1) + curMove.trigger[i] = true; + else if(!(MoveManager::mPrevTriggerCount[i] & 1) && MoveManager::mPrevTriggerCount[i] != MoveManager::mTriggerCount[i]) + curMove.trigger[i] = true; + MoveManager::mPrevTriggerCount[i] = MoveManager::mTriggerCount[i]; + } + + if (mConnection->getControlObject()) + mConnection->getControlObject()->preprocessMove(&curMove); + + curMove.clamp(); // clamp for net traffic + return true; +} + +void MoveList::pushMove(const Move &mv) +{ + U32 id = mFirstMoveIndex + mMoveList.size(); + U32 sz = mMoveList.size(); + mMoveList.push_back(mv); + mMoveList[sz].id = id; + mMoveList[sz].sendCount = 0; +} + +U32 MoveList::getMoveList(Move** movePtr,U32* numMoves) +{ + if (mConnection->isConnectionToServer()) + { + // give back moves starting at the last client move... + + AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request"); + AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveList.size(), "Desynched first and last move."); + *numMoves = mMoveList.size() - mLastClientMove + mFirstMoveIndex; + *movePtr = mMoveList.address() + mLastClientMove - mFirstMoveIndex; + } + else + { + *numMoves = (mMoveList.size() < mMoveCredit)? + mMoveList.size(): mMoveCredit; + *movePtr = mMoveList.begin(); + + mMoveCredit -= *numMoves; + mMoveList.setSize(*numMoves); + } + + return *numMoves; +} + +void MoveList::collectMove() +{ + Move mv; + if(!mConnection->isPlayingBack() && getNextMove(mv)) + { + mv.checksum=Move::ChecksumMismatch; + pushMove(mv); + mConnection->recordBlock(GameConnection::BlockTypeMove, sizeof(Move), &mv); + } +} + +void MoveList::clearMoves(U32 count) +{ + if (mConnection->isConnectionToServer()) + { + mLastClientMove += count; + AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request"); + AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveList.size(), "Desynched first and last move."); + } + else + { + AssertFatal(count <= mMoveList.size(),"GameConnection: Clearing too many moves"); + for (S32 i=0; iisConnectionToServer(), "Cannot inc move credit on the client."); + // Game tick increment + mMoveCredit += ticks; + if (mMoveCredit > MaxMoveCount) + mMoveCredit = MaxMoveCount; + + // Clear pending moves for the elapsed time if there + // is no control object. + if (mConnection->getControlObject()==NULL) + mMoveList.clear(); +} + +bool MoveList::areMovesPending() +{ + return mConnection->isConnectionToServer() ? + mMoveList.size() - mLastClientMove + mFirstMoveIndex : + mMoveList.size(); +} + +bool MoveList::isBacklogged() +{ + // If there are no pending moves and the input queue is full, + // then the connection to the server must be clogged. + if(!mConnection->isConnectionToServer()) + return false; + return mLastClientMove - mFirstMoveIndex == mMoveList.size() && + mMoveList.size() >= MaxMoveCount; +} + + +void MoveList::clientWriteMovePacket(BitStream *bstream) +{ + AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index."); + U32 count = mMoveList.size(); + + Move * move = mMoveList.address(); + U32 start = mLastMoveAck; + U32 offset; + for(offset = 0; offset < count; offset++) + if(move[offset].sendCount < MAX_MOVE_PACKET_SENDS) + break; + if(offset == count && count != 0) + offset--; + + start += offset; + count -= offset; + + if (count > MaxMoveCount) + count = MaxMoveCount; + bstream->writeInt(start,32); + bstream->writeInt(count,MoveCountBits); + Move * prevMove = NULL; + for (int i = 0; i < count; i++) + { + move[offset + i].sendCount++; + move[offset + i].pack(bstream,prevMove); + bstream->writeInt(move[offset + i].checksum,Move::ChecksumBits); + prevMove = &move[offset+i]; + } +} + +void MoveList::serverReadMovePacket(BitStream *bstream) +{ + // Server side packet read. + U32 start = bstream->readInt(32); + U32 count = bstream->readInt(MoveCountBits); + + Move * prevMove = NULL; + Move prevMoveHolder; + + // Skip forward (must be starting up), or over the moves + // we already have. + int skip = mLastMoveAck - start; + if (skip < 0) + { + mLastMoveAck = start; + } + else + { + if (skip > count) + skip = count; + for (int i = 0; i < skip; i++) + { + prevMoveHolder.unpack(bstream,prevMove); + prevMoveHolder.checksum = bstream->readInt(Move::ChecksumBits); + prevMove = &prevMoveHolder; + S32 idx = mMoveList.size()-skip+i; + if (idx>=0) + { +#ifdef TORQUE_DEBUG_NET_MOVES + if (mMoveList[idx].checksum != prevMoveHolder.checksum) + Con::printf("updated checksum on move %i from %i to %i",mMoveList[idx].id,mMoveList[idx].checksum,prevMoveHolder.checksum); +#endif + mMoveList[idx].checksum = prevMoveHolder.checksum; + } + } + start += skip; + count = count - skip; + } + + // Put the rest on the move list. + int index = mMoveList.size(); + mMoveList.increment(count); + while (index < mMoveList.size()) + { + mMoveList[index].unpack(bstream,prevMove); + mMoveList[index].checksum = bstream->readInt(Move::ChecksumBits); + prevMove = &mMoveList[index]; + mMoveList[index].id = start++; + index ++; + } + + mLastMoveAck += count; +} + +void MoveList::writeDemoStartBlock(ResizeBitStream *stream) +{ + stream->write(mLastMoveAck); + stream->write(mLastClientMove); + stream->write(mFirstMoveIndex); + + stream->write(U32(mMoveList.size())); + for(U32 j = 0; j < mMoveList.size(); j++) + mMoveList[j].pack(stream); +} + +void MoveList::readDemoStartBlock(BitStream *stream) +{ + stream->read(&mLastMoveAck); + stream->read(&mLastClientMove); + stream->read(&mFirstMoveIndex); + + U32 size; + Move mv; + stream->read(&size); + mMoveList.clear(); + while(size--) + { + mv.unpack(stream); + pushMove(mv); + } +} + +void MoveList::serverWriteMovePacket(BitStream * bstream) +{ +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("ack %i minus %i",mLastMoveAck,mMoveList.size()); +#endif + + // acknowledge only those moves that have been ticked + bstream->writeInt(mLastMoveAck - mMoveList.size(),32); +} + +void MoveList::clientReadMovePacket(BitStream * bstream) +{ +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("pre move ack: %i", mLastMoveAck); +#endif + + mLastMoveAck = bstream->readInt(32); + +#ifdef TORQUE_DEBUG_NET_MOVES + Con::printf("post move ack %i, first move %i, last move %i", mLastMoveAck, mFirstMoveIndex, mLastClientMove); +#endif + + if (mLastMoveAck < mFirstMoveIndex) + mLastMoveAck = mFirstMoveIndex; + + if(mLastMoveAck > mLastClientMove) + mLastClientMove = mLastMoveAck; + while(mFirstMoveIndex < mLastMoveAck) + { + if (mMoveList.size()) + { + mMoveList.pop_front(); + mFirstMoveIndex++; + } + else + { + AssertWarn(1, "Popping off too many moves!"); + mFirstMoveIndex = mLastMoveAck; + } + } +} diff --git a/T3D/moveList.h b/T3D/moveList.h new file mode 100644 index 0000000..13cae3d --- /dev/null +++ b/T3D/moveList.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MOVELIST_H_ +#define _MOVELIST_H_ + +#ifndef _MOVEMANAGER_H_ +#include "T3D/moveManager.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class BitStream; +class ResizeBitStream; +class NetObject; +class GameConnection; + +class MoveList +{ + enum PrivateConstants + { + MoveCountBits = 5, + /// MaxMoveCount should not exceed the MoveManager's + /// own maximum (MaxMoveQueueSize) + MaxMoveCount = 30, + }; + +public: + + MoveList(); + + void init() {} + + void setConnection(GameConnection * connection) { mConnection = connection; } + + /// @name Move Packets + /// Write/read move data to the packet. + /// @{ + + /// + void ghostReadExtra(NetObject *,BitStream *, bool newGhost) {}; + void ghostWriteExtra(NetObject *,BitStream *) {} + void ghostPreRead(NetObject *, bool newGhost) {} + + void clientWriteMovePacket(BitStream *bstream); + void clientReadMovePacket(BitStream *); + + void serverWriteMovePacket(BitStream *); + void serverReadMovePacket(BitStream *bstream); + + void writeDemoStartBlock(ResizeBitStream *stream); + void readDemoStartBlock(BitStream *stream); + /// @} + +public: + + void collectMove(); + void pushMove(const Move &mv); + U32 getMoveList(Move**,U32* numMoves); + void clearMoves(U32 count); + + void markControlDirty() { mLastClientMove = mLastMoveAck; } + bool isMismatch() { return mControlMismatch; } + + bool isBacklogged(); + void incMoveCredit(U32 ticks); + +protected: + bool getNextMove(Move &curMove); + bool areMovesPending(); + +protected: + + U32 mLastMoveAck; + U32 mLastClientMove; + U32 mFirstMoveIndex; + U32 mMoveCredit; + bool mControlMismatch; + + GameConnection * mConnection; + + Vector mMoveList; +}; + +#endif // _MOVELIST_H_ diff --git a/T3D/moveManager.cpp b/T3D/moveManager.cpp new file mode 100644 index 0000000..4a044d2 --- /dev/null +++ b/T3D/moveManager.cpp @@ -0,0 +1,238 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "math/mConstants.h" +#include "T3D/moveManager.h" + +bool MoveManager::mDeviceIsKeyboardMouse = false; +F32 MoveManager::mForwardAction = 0; +F32 MoveManager::mBackwardAction = 0; +F32 MoveManager::mUpAction = 0; +F32 MoveManager::mDownAction = 0; +F32 MoveManager::mLeftAction = 0; +F32 MoveManager::mRightAction = 0; + +bool MoveManager::mFreeLook = false; +F32 MoveManager::mPitch = 0; +F32 MoveManager::mYaw = 0; +F32 MoveManager::mRoll = 0; + +F32 MoveManager::mPitchUpSpeed = 0; +F32 MoveManager::mPitchDownSpeed = 0; +F32 MoveManager::mYawLeftSpeed = 0; +F32 MoveManager::mYawRightSpeed = 0; +F32 MoveManager::mRollLeftSpeed = 0; +F32 MoveManager::mRollRightSpeed = 0; + +F32 MoveManager::mXAxis_L = 0; +F32 MoveManager::mYAxis_L = 0; +F32 MoveManager::mXAxis_R = 0; +F32 MoveManager::mYAxis_R = 0; + +U32 MoveManager::mTriggerCount[MaxTriggerKeys] = { 0, }; +U32 MoveManager::mPrevTriggerCount[MaxTriggerKeys] = { 0, }; + +F32 MoveManager::mPitchCam = 0; +F32 MoveManager::mYawCam = 0; +F32 MoveManager::mKeyYawCam = 0; +F32 MoveManager::mDistanceCam = 3; + +const Move NullMove = +{ + /*px=*/16, /*py=*/16, /*pz=*/16, + /*pyaw=*/0, /*ppitch=*/0, /*proll=*/0, + /*x=*/0, /*y=*/0,/*z=*/0, + /*yaw=*/0, /*pitch=*/0, /*roll=*/0, + /*id=*/0, + /*sendCount=*/0, + + /*checksum=*/false, + /*deviceIsKeyboardMouse=*/false, + /*freeLook=*/false, + /*triggers=*/{false,false,false,false,false,false} +}; + +void MoveManager::init() +{ + Con::addVariable("mvPitchCam", TypeF32, &mPitchCam); + Con::addVariable("mvYawCam", TypeF32, &mYawCam); + Con::addVariable("mvDistanceCam", TypeF32, &mDistanceCam); + Con::addVariable("mvKeyYawCam", TypeF32, &mKeyYawCam); + + Con::addVariable("mvForwardAction", TypeF32, &mForwardAction); + Con::addVariable("mvBackwardAction", TypeF32, &mBackwardAction); + Con::addVariable("mvUpAction", TypeF32, &mUpAction); + Con::addVariable("mvDownAction", TypeF32, &mDownAction); + Con::addVariable("mvLeftAction", TypeF32, &mLeftAction); + Con::addVariable("mvRightAction", TypeF32, &mRightAction); + + Con::addVariable("mvFreeLook", TypeBool, &mFreeLook); + Con::addVariable("mvDeviceIsKeyboardMouse", TypeBool, &mDeviceIsKeyboardMouse); + Con::addVariable("mvPitch", TypeF32, &mPitch); + Con::addVariable("mvYaw", TypeF32, &mYaw); + Con::addVariable("mvRoll", TypeF32, &mRoll); + Con::addVariable("mvPitchUpSpeed", TypeF32, &mPitchUpSpeed); + Con::addVariable("mvPitchDownSpeed", TypeF32, &mPitchDownSpeed); + Con::addVariable("mvYawLeftSpeed", TypeF32, &mYawLeftSpeed); + Con::addVariable("mvYawRightSpeed", TypeF32, &mYawRightSpeed); + Con::addVariable("mvRollLeftSpeed", TypeF32, &mRollLeftSpeed); + Con::addVariable("mvRollRightSpeed", TypeF32, &mRollRightSpeed); + + // Dual-analog + Con::addVariable( "mvXAxis_L", TypeF32, &mXAxis_L ); + Con::addVariable( "mvYAxis_L", TypeF32, &mYAxis_L ); + + Con::addVariable( "mvXAxis_R", TypeF32, &mXAxis_R ); + Con::addVariable( "mvYAxis_R", TypeF32, &mYAxis_R ); + + for(U32 i = 0; i < MaxTriggerKeys; i++) + { + char varName[256]; + dSprintf(varName, sizeof(varName), "mvTriggerCount%d", i); + Con::addVariable(varName, TypeS32, &mTriggerCount[i]); + } +} + +static inline F32 clampFloatWrap(F32 val) +{ + return val - F32(S32(val)); +} + +static inline S32 clampRangeClamp(F32 val) +{ + if(val < -1) + return 0; + if(val > 1) + return 32; + + // 0.5 / 16 = 0.03125 ... this forces a round up to + // make the precision near zero equal in the negative + // and positive directions. See... + // + // http://www.garagegames.com/community/forums/viewthread/49714 + + return (S32)((val + 1.03125) * 16); +} + + +#define FANG2IANG(x) ((U32)((S16)((F32(0x10000) / M_2PI) * x)) & 0xFFFF) +#define IANG2FANG(x) (F32)((M_2PI / F32(0x10000)) * (F32)((S16)x)) + +void Move::unclamp() +{ + yaw = IANG2FANG(pyaw); + pitch = IANG2FANG(ppitch); + roll = IANG2FANG(proll); + + x = (px - 16) / F32(16); + y = (py - 16) / F32(16); + z = (pz - 16) / F32(16); +} + +static inline F32 clampAngleClamp( F32 angle ) +{ + const F32 limit = ( M_PI_F / 180.0f ) * 179.999f; + if ( angle < -limit ) + return -limit; + if ( angle > limit ) + return limit; + + return angle; +} + +void Move::clamp() +{ + // If yaw/pitch/roll goes equal or greater than -PI/+PI it + // flips the direction of the rotation... we protect against + // that by clamping before the conversion. + + yaw = clampAngleClamp( yaw ); + pitch = clampAngleClamp( pitch ); + roll = clampAngleClamp( roll ); + + // angles are all 16 bit. + pyaw = FANG2IANG(yaw); + ppitch = FANG2IANG(pitch); + proll = FANG2IANG(roll); + + px = clampRangeClamp(x); + py = clampRangeClamp(y); + pz = clampRangeClamp(z); + unclamp(); +} + +void Move::pack(BitStream *stream, const Move * basemove) +{ + bool alwaysWriteAll = basemove!=NULL; + if (!basemove) + basemove = &NullMove; + + S32 i; + bool triggerDifferent = false; + for (i=0; i < MaxTriggerKeys; i++) + if (trigger[i] != basemove->trigger[i]) + triggerDifferent = true; + bool somethingDifferent = (pyaw!=basemove->pyaw) || + (ppitch!=basemove->ppitch) || + (proll!=basemove->proll) || + (px!=basemove->px) || + (py!=basemove->py) || + (pz!=basemove->pz) || + (deviceIsKeyboardMouse!=basemove->deviceIsKeyboardMouse) || + (freeLook!=basemove->freeLook) || + triggerDifferent; + + if (alwaysWriteAll || stream->writeFlag(somethingDifferent)) + { + if(stream->writeFlag(pyaw != basemove->pyaw)) + stream->writeInt(pyaw, 16); + if(stream->writeFlag(ppitch != basemove->ppitch)) + stream->writeInt(ppitch, 16); + if(stream->writeFlag(proll != basemove->proll)) + stream->writeInt(proll, 16); + + if (stream->writeFlag(px != basemove->px)) + stream->writeInt(px, 6); + if (stream->writeFlag(py != basemove->py)) + stream->writeInt(py, 6); + if (stream->writeFlag(pz != basemove->pz)) + stream->writeInt(pz, 6); + stream->writeFlag(freeLook); + stream->writeFlag(deviceIsKeyboardMouse); + + if (stream->writeFlag(triggerDifferent)) + for(i = 0; i < MaxTriggerKeys; i++) + stream->writeFlag(trigger[i]); + } +} + +void Move::unpack(BitStream *stream, const Move * basemove) +{ + bool alwaysReadAll = basemove!=NULL; + if (!basemove) + basemove=&NullMove; + + if (alwaysReadAll || stream->readFlag()) + { + pyaw = stream->readFlag() ? stream->readInt(16) : basemove->pyaw; + ppitch = stream->readFlag() ? stream->readInt(16) : basemove->ppitch; + proll = stream->readFlag() ? stream->readInt(16) : basemove->proll; + + px = stream->readFlag() ? stream->readInt(6) : basemove->px; + py = stream->readFlag() ? stream->readInt(6) : basemove->py; + pz = stream->readFlag() ? stream->readInt(6) : basemove->pz; + freeLook = stream->readFlag(); + deviceIsKeyboardMouse = stream->readFlag(); + + bool triggersDiffer = stream->readFlag(); + for (S32 i = 0; i< MaxTriggerKeys; i++) + trigger[i] = triggersDiffer ? stream->readFlag() : basemove->trigger[i]; + unclamp(); + } + else + *this = *basemove; +} diff --git a/T3D/moveManager.h b/T3D/moveManager.h new file mode 100644 index 0000000..81d9629 --- /dev/null +++ b/T3D/moveManager.h @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MOVEMANAGER_H_ +#define _MOVEMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +enum MoveConstants { + MaxTriggerKeys = 6, + MaxMoveQueueSize = 45, +}; + +class BitStream; + +struct Move +{ + enum { ChecksumBits = 16, ChecksumMask = ((1<(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + +//---------------------------------------------------------------------------- + +void PathCamera::onEditorEnable() +{ + mNetFlags.set(Ghostable); +} + +void PathCamera::onEditorDisable() +{ + mNetFlags.clear(Ghostable); +} + + +//---------------------------------------------------------------------------- + +void PathCamera::initPersistFields() +{ + Parent::initPersistFields(); +} + +void PathCamera::consoleInit() +{ +} + + +//---------------------------------------------------------------------------- + +void PathCamera::processTick(const Move* move) +{ + // client and server + Parent::processTick(move); + + // Move to new time + advancePosition(TickMs); + + // Set new position + MatrixF mat; + interpolateMat(mPosition,&mat); + Parent::setTransform(mat); + + updateContainer(); +} + +void PathCamera::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); + MatrixF mat; + interpolateMat(delta.time + (delta.timeVec * dt),&mat); + Parent::setRenderTransform(mat); +} + +void PathCamera::interpolateMat(F32 pos,MatrixF* mat) +{ + CameraSpline::Knot knot; + mSpline.value(pos - mNodeBase,&knot); + knot.mRotation.setMatrix(mat); + mat->setPosition(knot.mPosition); +} + +void PathCamera::advancePosition(S32 ms) +{ + delta.timeVec = mPosition; + + // Advance according to current speed + if (mState == Forward) { + mPosition = mSpline.advanceTime(mPosition - mNodeBase,ms); + if (mPosition > F32(mNodeCount - 1)) + mPosition = F32(mNodeCount - 1); + mPosition += (F32)mNodeBase; + if (mTargetSet && mPosition >= mTarget) { + mTargetSet = false; + mPosition = mTarget; + mState = Stop; + } + } + else + if (mState == Backward) { + mPosition = mSpline.advanceTime(mPosition - mNodeBase,-ms); + if (mPosition < 0) + mPosition = 0; + mPosition += mNodeBase; + if (mTargetSet && mPosition <= mTarget) { + mTargetSet = false; + mPosition = mTarget; + mState = Stop; + } + } + + // Script callbacks + if (int(mPosition) != int(delta.timeVec)) + onNode(int(mPosition)); + + // Set frame interpolation + delta.time = mPosition; + delta.timeVec -= mPosition; +} + + +//---------------------------------------------------------------------------- + +void PathCamera::getCameraTransform(F32* pos, MatrixF* mat) +{ + // Overide the ShapeBase method to skip all the first/third person support. + getRenderEyeTransform(mat); + + // Apply Camera FX. + mat->mul( gCamFXMgr.getTrans() ); +} + + +//---------------------------------------------------------------------------- + +void PathCamera::setPosition(F32 pos) +{ + mPosition = mClampF(pos, (F32)mNodeBase, (F32)(mNodeBase + mNodeCount - 1)); + MatrixF mat; + interpolateMat(mPosition,&mat); + Parent::setTransform(mat); + setMaskBits(PositionMask); +} + +void PathCamera::setTarget(F32 pos) +{ + mTarget = pos; + mTargetSet = true; + if (mTarget > mPosition) + mState = Forward; + else + if (mTarget < mPosition) + mState = Backward; + else { + mTargetSet = false; + mState = Stop; + } + setMaskBits(TargetMask | StateMask); +} + +void PathCamera::setState(State s) +{ + mState = s; + setMaskBits(StateMask); +} + + +//----------------------------------------------------------------------------- + +void PathCamera::reset(F32 speed) +{ + CameraSpline::Knot *knot = new CameraSpline::Knot; + mSpline.value(mPosition - mNodeBase,knot); + if (speed) + knot->mSpeed = speed; + mSpline.removeAll(); + mSpline.push_back(knot); + + mNodeBase = 0; + mNodeCount = 1; + mPosition = 0; + mTargetSet = false; + mState = Forward; + setMaskBits(StateMask | PositionMask | WindowMask | TargetMask); +} + +void PathCamera::pushBack(CameraSpline::Knot *knot) +{ + // Make room at the end + if (mNodeCount == NodeWindow) { + delete mSpline.remove(mSpline.getKnot(0)); + mNodeBase++; + } + else + mNodeCount++; + + // Fill in the new node + mSpline.push_back(knot); + setMaskBits(WindowMask); + + // Make sure the position doesn't fall off + if (mPosition < mNodeBase) { + mPosition = (F32)mNodeBase; + setMaskBits(PositionMask); + } +} + +void PathCamera::pushFront(CameraSpline::Knot *knot) +{ + // Make room at the front + if (mNodeCount == NodeWindow) + delete mSpline.remove(mSpline.getKnot(mNodeCount)); + else + mNodeCount++; + mNodeBase--; + + // Fill in the new node + mSpline.push_front(knot); + setMaskBits(WindowMask); + + // Make sure the position doesn't fall off + if (mPosition > F32(mNodeBase + (NodeWindow - 1))) + { + mPosition = F32(mNodeBase + (NodeWindow - 1)); + setMaskBits(PositionMask); + } +} + +void PathCamera::popFront() +{ + if (mNodeCount < 2) + return; + + // Remove the first node. Node base and position are unaffected. + mNodeCount--; + delete mSpline.remove(mSpline.getKnot(0)); + + if( mPosition > 0 ) + mPosition --; +} + + +//---------------------------------------------------------------------------- + +void PathCamera::onNode(S32 node) +{ + if (!isGhost()) + Con::executef(mDataBlock, "onNode",scriptThis(), Con::getIntArg(node)); +} + +U32 PathCamera::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + Parent::packUpdate(con,mask,stream); + + if (stream->writeFlag(mask & StateMask)) + stream->writeInt(mState,StateBits); + + if (stream->writeFlag(mask & PositionMask)) + stream->write(mPosition); + + if (stream->writeFlag(mask & TargetMask)) + if (stream->writeFlag(mTargetSet)) + stream->write(mTarget); + + if (stream->writeFlag(mask & WindowMask)) { + stream->write(mNodeBase); + stream->write(mNodeCount); + for (int i = 0; i < mNodeCount; i++) { + CameraSpline::Knot *knot = mSpline.getKnot(i); + mathWrite(*stream, knot->mPosition); + mathWrite(*stream, knot->mRotation); + stream->write(knot->mSpeed); + stream->writeInt(knot->mType, CameraSpline::Knot::NUM_TYPE_BITS); + stream->writeInt(knot->mPath, CameraSpline::Knot::NUM_PATH_BITS); + } + } + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + if(stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) + return 0; + + return 0; +} + +void PathCamera::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + // StateMask + if (stream->readFlag()) + mState = stream->readInt(StateBits); + + // PositionMask + if (stream->readFlag()) + { + stream->read(&mPosition); + delta.time = mPosition; + delta.timeVec = 0; + } + + // TargetMask + if (stream->readFlag()) + { + mTargetSet = stream->readFlag(); + if (mTargetSet) + stream->read(&mTarget); + } + + // WindowMask + if (stream->readFlag()) + { + mSpline.removeAll(); + stream->read(&mNodeBase); + stream->read(&mNodeCount); + for (int i = 0; i < mNodeCount; i++) + { + CameraSpline::Knot *knot = new CameraSpline::Knot(); + mathRead(*stream, &knot->mPosition); + mathRead(*stream, &knot->mRotation); + stream->read(&knot->mSpeed); + knot->mType = (CameraSpline::Knot::Type)stream->readInt(CameraSpline::Knot::NUM_TYPE_BITS); + knot->mPath = (CameraSpline::Knot::Path)stream->readInt(CameraSpline::Knot::NUM_PATH_BITS); + mSpline.push_back(knot); + } + } + + // Controlled by the client? + if (stream->readFlag()) + return; + +} + + +//----------------------------------------------------------------------------- +// Console access methods +//----------------------------------------------------------------------------- + +ConsoleMethod(PathCamera,setPosition,void,3, 3,"PathCamera.setPosition(pos);") +{ + object->setPosition(dAtof(argv[2])); +} + +ConsoleMethod(PathCamera,setTarget,void,3, 3,"PathCamera.setTarget(pos);") +{ + object->setTarget(dAtof(argv[2])); +} + +ConsoleMethod(PathCamera,setState,void,3, 3,"PathCamera.setState({forward,backward,stop});") +{ + if (!dStricmp(argv[2],"forward")) + object->setState(PathCamera::Forward); + else + if (!dStricmp(argv[2],"backward")) + object->setState(PathCamera::Backward); + else + object->setState(PathCamera::Stop); +} + +ConsoleMethod(PathCamera,reset,void,2,3,"PathCamera.reset(speed=0);") +{ + object->reset((argc >= 3)? dAtof(argv[2]): 1); +} + + +static CameraSpline::Knot::Type resolveKnotType(const char *arg) +{ + if (dStricmp(arg, "Position Only") == 0) + return CameraSpline::Knot::POSITION_ONLY; + if (dStricmp(arg, "Kink") == 0) + return CameraSpline::Knot::KINK; + return CameraSpline::Knot::NORMAL; +} + +static CameraSpline::Knot::Path resolveKnotPath(const char *arg) +{ + if (!dStricmp(arg, "Linear")) + return CameraSpline::Knot::LINEAR; + return CameraSpline::Knot::SPLINE; +} + +ConsoleMethod(PathCamera,pushBack,void,6, 6,"PathCamera.pushBack(transform,speed,type,path);") +{ + Point3F pos; + AngAxisF aa(Point3F(0,0,0),0); + dSscanf(argv[2], "%g %g %g %g %g %g %g", &pos.x, &pos.y, &pos.z, &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle); + QuatF rot(aa); + object->pushBack( new CameraSpline::Knot(pos, rot, dAtof(argv[3]), resolveKnotType(argv[4]), resolveKnotPath(argv[5])) ); +} + +ConsoleMethod(PathCamera,pushFront,void,6, 6,"PathCamera.pushFront(transform,speed,type,path);") +{ + Point3F pos; + AngAxisF aa(Point3F(0,0,0),0); + dSscanf(argv[2], "%g %g %g %g %g %g %g", &pos.x, &pos.y, &pos.z, &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle); + QuatF rot(aa); + object->pushFront( new CameraSpline::Knot(pos, rot, dAtof(argv[3]), resolveKnotType(argv[4]), resolveKnotPath(argv[5])) ); +} + +ConsoleMethod(PathCamera,popFront,void,2, 2,"PathCamera.popFront();") +{ + object->popFront(); +} + + + diff --git a/T3D/pathCamera.h b/T3D/pathCamera.h new file mode 100644 index 0000000..1623068 --- /dev/null +++ b/T3D/pathCamera.h @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PATHCAMERA_H_ +#define _PATHCAMERA_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif + +#ifndef _CAMERASPLINE_H_ +#include "T3D/cameraSpline.h" +#endif + + +//---------------------------------------------------------------------------- +struct PathCameraData: public ShapeBaseData { + typedef ShapeBaseData Parent; + + // + DECLARE_CONOBJECT(PathCameraData); + static void consoleInit(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- +class PathCamera: public ShapeBase +{ +public: + enum State { + Forward, + Backward, + Stop, + StateBits = 3 + }; + +private: + typedef ShapeBase Parent; + + enum MaskBits { + WindowMask = Parent::NextFreeMask, + PositionMask = Parent::NextFreeMask + 1, + TargetMask = Parent::NextFreeMask + 2, + StateMask = Parent::NextFreeMask + 3, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + struct StateDelta { + F32 time; + F32 timeVec; + }; + StateDelta delta; + + enum Constants { + NodeWindow = 128 // Maximum number of active nodes + }; + + // + PathCameraData* mDataBlock; + CameraSpline mSpline; + S32 mNodeBase; + S32 mNodeCount; + F32 mPosition; + int mState; + F32 mTarget; + bool mTargetSet; + + void interpolateMat(F32 pos,MatrixF* mat); + void advancePosition(S32 ms); + +public: + DECLARE_CONOBJECT(PathCamera); + + PathCamera(); + ~PathCamera(); + static void initPersistFields(); + static void consoleInit(); + + void onEditorEnable(); + void onEditorDisable(); + + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData* dptr); + void onNode(S32 node); + + void processTick(const Move*); + void interpolateTick(F32 dt); + void getCameraTransform(F32* pos,MatrixF* mat); + + U32 packUpdate(NetConnection *, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *, BitStream *stream); + + void reset(F32 speed = 1); + void pushFront(CameraSpline::Knot *knot); + void pushBack(CameraSpline::Knot *knot); + void popFront(); + + void setPosition(F32 pos); + void setTarget(F32 pos); + void setState(State s); +}; + + +#endif diff --git a/T3D/physicalZone.cpp b/T3D/physicalZone.cpp new file mode 100644 index 0000000..862284c --- /dev/null +++ b/T3D/physicalZone.cpp @@ -0,0 +1,404 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/physicalZone.h" +#include "core/stream/bitStream.h" +#include "collision/boxConvex.h" +#include "collision/clippedPolyList.h" +#include "console/consoleTypes.h" +#include "math/mathIO.h" +#include "sceneGraph/sceneState.h" +#include "T3D/trigger.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CO_NETOBJECT_V1(PhysicalZone); + +bool PhysicalZone::smRenderPZones = false; + +ConsoleMethod(PhysicalZone, activate, void, 2, 2, "Activate the physical zone's effects.") +{ + if (object->isClientObject()) + return; + + object->activate(); +} + +ConsoleMethod(PhysicalZone, deactivate, void, 2, 2, "Deactivate the physical zone's effects.") +{ + if (object->isClientObject()) + return; + + object->deactivate(); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +PhysicalZone::PhysicalZone() +{ + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask |= PhysicalZoneObjectType; + + mVelocityMod = 1.0f; + mGravityMod = 1.0f; + mAppliedForce.set(0, 0, 0); + + mConvexList = new Convex; + mActive = true; +} + +PhysicalZone::~PhysicalZone() +{ + delete mConvexList; + mConvexList = NULL; +} + +//-------------------------------------------------------------------------- +void PhysicalZone::consoleInit() +{ + Con::addVariable( "$PhysicalZone::renderZones", TypeBool, &smRenderPZones ); +} + +void PhysicalZone::initPersistFields() +{ + addGroup("Misc"); + addField("velocityMod", TypeF32, Offset(mVelocityMod, PhysicalZone)); + addField("gravityMod", TypeF32, Offset(mGravityMod, PhysicalZone)); + addField("appliedForce", TypePoint3F, Offset(mAppliedForce, PhysicalZone)); + addField("polyhedron", TypeTriggerPolyhedron, Offset(mPolyhedron, PhysicalZone), + "The polyhedron type is really a quadrilateral and consists of a corner" + "point followed by three vectors representing the edges extending from the corner." ); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +bool PhysicalZone::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (mVelocityMod < -40.0f || mVelocityMod > 40.0f) { + Con::errorf("PhysicalZone: velocity mod out of range. [-40, 40]"); + mVelocityMod = mVelocityMod < -40.0f ? -40.0f : 40.0f; + } + if (mGravityMod < -40.0f || mGravityMod > 40.0f) { + Con::errorf("PhysicalZone: GravityMod out of range. [-40, 40]"); + mGravityMod = mGravityMod < -40.0f ? -40.0f : 40.0f; + } + static const char* coordString[] = { "x", "y", "z" }; + F32* p = mAppliedForce; + for (U32 i = 0; i < 3; i++) { + if (p[i] < -40000.0f || p[i] > 40000.0f) { + Con::errorf("PhysicalZone: applied force: %s out of range. [-40000, 40000]", coordString[i]); + p[i] = p[i] < -40000.0f ? -40000.0f : 40000.0f; + } + } + + Polyhedron temp = mPolyhedron; + setPolyhedron(temp); + + addToScene(); + + return true; +} + + +void PhysicalZone::onRemove() +{ + mConvexList->nukeList(); + + removeFromScene(); + Parent::onRemove(); +} + +void PhysicalZone::inspectPostApply() +{ + setPolyhedron(mPolyhedron); + Parent::inspectPostApply(); +} + +//------------------------------------------------------------------------------ +void PhysicalZone::setTransform(const MatrixF & mat) +{ + Parent::setTransform(mat); + + MatrixF base(true); + base.scale(Point3F(1.0/mObjScale.x, + 1.0/mObjScale.y, + 1.0/mObjScale.z)); + base.mul(mWorldToObj); + mClippedList.setBaseTransform(base); + + if (isServerObject()) + setMaskBits(InitialUpdateMask); +} + + +bool PhysicalZone::prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + if ( isLastState(state, stateKey) ) + return false; + + // only render if selected or render flag is set + if ( !smRenderPZones && !isSelected() ) + return false; + + setLastState( state, stateKey ); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &PhysicalZone::renderObject ); + ri->type = RenderPassManager::RIT_Object; + ri->defaultKey = 0; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + + +void PhysicalZone::renderObject( ObjectRenderInst *ri, + SceneState *state, + BaseMatInstance *overrideMat ) +{ + if (overrideMat) + return; + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setBlend( true ); + desc.setCullMode( GFXCullNone ); + + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + mat.scale( getScale() ); + + GFX->multWorld( mat ); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->drawPolyhedron( desc, mPolyhedron, ColorI( 0, 255, 0, 45 ) ); +} + +//-------------------------------------------------------------------------- +U32 PhysicalZone::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 i; + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag((mask & InitialUpdateMask) != 0)) { + // Note that we don't really care about efficiency here, since this is an + // edit-only ghost... + mathWrite(*stream, mObjToWorld); + mathWrite(*stream, mObjScale); + + // Write the polyhedron + stream->write(mPolyhedron.pointList.size()); + for (i = 0; i < mPolyhedron.pointList.size(); i++) + mathWrite(*stream, mPolyhedron.pointList[i]); + + stream->write(mPolyhedron.planeList.size()); + for (i = 0; i < mPolyhedron.planeList.size(); i++) + mathWrite(*stream, mPolyhedron.planeList[i]); + + stream->write(mPolyhedron.edgeList.size()); + for (i = 0; i < mPolyhedron.edgeList.size(); i++) { + const Polyhedron::Edge& rEdge = mPolyhedron.edgeList[i]; + + stream->write(rEdge.face[0]); + stream->write(rEdge.face[1]); + stream->write(rEdge.vertex[0]); + stream->write(rEdge.vertex[1]); + } + + stream->write(mVelocityMod); + stream->write(mGravityMod); + mathWrite(*stream, mAppliedForce); + stream->writeFlag(mActive); + } else { + stream->writeFlag(mActive); + } + + return retMask; +} + +void PhysicalZone::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) { + U32 i, size; + MatrixF temp; + Point3F tempScale; + Polyhedron tempPH; + + // Transform + mathRead(*stream, &temp); + mathRead(*stream, &tempScale); + + // Read the polyhedron + stream->read(&size); + tempPH.pointList.setSize(size); + for (i = 0; i < tempPH.pointList.size(); i++) + mathRead(*stream, &tempPH.pointList[i]); + + stream->read(&size); + tempPH.planeList.setSize(size); + for (i = 0; i < tempPH.planeList.size(); i++) + mathRead(*stream, &tempPH.planeList[i]); + + stream->read(&size); + tempPH.edgeList.setSize(size); + for (i = 0; i < tempPH.edgeList.size(); i++) { + Polyhedron::Edge& rEdge = tempPH.edgeList[i]; + + stream->read(&rEdge.face[0]); + stream->read(&rEdge.face[1]); + stream->read(&rEdge.vertex[0]); + stream->read(&rEdge.vertex[1]); + } + + stream->read(&mVelocityMod); + stream->read(&mGravityMod); + mathRead(*stream, &mAppliedForce); + + setPolyhedron(tempPH); + setScale(tempScale); + setTransform(temp); + mActive = stream->readFlag(); + } else { + mActive = stream->readFlag(); + } +} + + +//-------------------------------------------------------------------------- +void PhysicalZone::setPolyhedron(const Polyhedron& rPolyhedron) +{ + mPolyhedron = rPolyhedron; + + if (mPolyhedron.pointList.size() != 0) { + mObjBox.minExtents.set(1e10, 1e10, 1e10); + mObjBox.maxExtents.set(-1e10, -1e10, -1e10); + for (U32 i = 0; i < mPolyhedron.pointList.size(); i++) { + mObjBox.minExtents.setMin(mPolyhedron.pointList[i]); + mObjBox.maxExtents.setMax(mPolyhedron.pointList[i]); + } + } else { + mObjBox.minExtents.set(-0.5, -0.5, -0.5); + mObjBox.maxExtents.set( 0.5, 0.5, 0.5); + } + + MatrixF xform = getTransform(); + setTransform(xform); + + mClippedList.clear(); + mClippedList.mPlaneList = mPolyhedron.planeList; + + MatrixF base(true); + base.scale(Point3F(1.0/mObjScale.x, + 1.0/mObjScale.y, + 1.0/mObjScale.z)); + base.mul(mWorldToObj); + + mClippedList.setBaseTransform(base); +} + + +//-------------------------------------------------------------------------- +void PhysicalZone::buildConvex(const Box3F& box, Convex* convex) +{ + // These should really come out of a pool + mConvexList->collectGarbage(); + + Box3F realBox = box; + mWorldToObj.mul(realBox); + realBox.minExtents.convolveInverse(mObjScale); + realBox.maxExtents.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + // Just return a box convex for the entire shape... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { + if (itr->mConvex->getType() == BoxConvexType && + itr->mConvex->getObject() == this) { + cc = itr->mConvex; + break; + } + } + if (cc) + return; + + // Create a new convex. + BoxConvex* cp = new BoxConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->init(this); + + mObjBox.getCenter(&cp->mCenter); + cp->mSize.x = mObjBox.len_x() / 2.0f; + cp->mSize.y = mObjBox.len_y() / 2.0f; + cp->mSize.z = mObjBox.len_z() / 2.0f; +} + +bool PhysicalZone::testObject(SceneObject* enter) +{ + // TODO: This doesn't look like it's testing against the polyhedron at + // all. And whats the point of building a convex if no collision methods + // are implemented? + + if (mPolyhedron.pointList.size() == 0) + return false; + + mClippedList.clear(); + + SphereF sphere; + sphere.center = (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5; + VectorF bv = mWorldBox.maxExtents - sphere.center; + sphere.radius = bv.len(); + + enter->buildPolyList(&mClippedList, mWorldBox, sphere); + return mClippedList.isEmpty() == false; +} + +bool PhysicalZone::testBox( const Box3F &box ) const +{ + return mWorldBox.isOverlapped( box ); +} + +void PhysicalZone::activate() +{ + AssertFatal(isServerObject(), "Client objects not allowed in ForceFieldInstance::open()"); + + if (mActive != true) + setMaskBits(ActiveMask); + mActive = true; +} + +void PhysicalZone::deactivate() +{ + AssertFatal(isServerObject(), "Client objects not allowed in ForceFieldInstance::close()"); + + if (mActive != false) + setMaskBits(ActiveMask); + mActive = false; +} + diff --git a/T3D/physicalZone.h b/T3D/physicalZone.h new file mode 100644 index 0000000..bf9fdfc --- /dev/null +++ b/T3D/physicalZone.h @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PHYSICALZONE +#define _H_PHYSICALZONE + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _EARLYOUTPOLYLIST_H_ +#include "collision/earlyOutPolyList.h" +#endif + + +class Convex; + +// ------------------------------------------------------------------------- +class PhysicalZone : public SceneObject +{ + typedef SceneObject Parent; + + enum UpdateMasks { + InitialUpdateMask = 1 << 0, + ActiveMask = 1 << 1 + }; + + protected: + static bool smRenderPZones; + + F32 mVelocityMod; + F32 mGravityMod; + Point3F mAppliedForce; + + // Basically ripped from trigger + Polyhedron mPolyhedron; + EarlyOutPolyList mClippedList; + + bool mActive; + + Convex* mConvexList; + void buildConvex(const Box3F& box, Convex* convex); + + public: + PhysicalZone(); + ~PhysicalZone(); + + // SimObject + DECLARE_CONOBJECT(PhysicalZone); + static void consoleInit(); + static void initPersistFields(); + bool onAdd(); + void onRemove(); + void inspectPostApply(); + + // NetObject + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + // SceneObject + void setTransform(const MatrixF &mat); + bool prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + + inline F32 getVelocityMod() const { return mVelocityMod; } + inline F32 getGravityMod() const { return mGravityMod; } + inline const Point3F& getForce() const { return mAppliedForce; } + + void setPolyhedron(const Polyhedron&); + bool testObject(SceneObject*); + + bool testBox( const Box3F &box ) const; + + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + void activate(); + void deactivate(); + inline bool isActive() const { return mActive; } + +}; + +#endif // _H_PHYSICALZONE + diff --git a/T3D/physics/physicsObject.cpp b/T3D/physics/physicsObject.cpp new file mode 100644 index 0000000..181153d --- /dev/null +++ b/T3D/physics/physicsObject.cpp @@ -0,0 +1,7 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physicsObject.h" diff --git a/T3D/physics/physicsObject.h b/T3D/physics/physicsObject.h new file mode 100644 index 0000000..16c37e0 --- /dev/null +++ b/T3D/physics/physicsObject.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PHYSICSOBJECT_H_ +#define _T3D_PHYSICS_PHYSICSOBJECT_H_ + + +/// +class PhysicsObject +{ +public: + + // No constructor. + // You must initialize these base members to the correct values yourself. + + inline bool isServerObject() const { return mServerObject; } + inline bool isClientObject() const { return !mServerObject; } + inline bool isSinglePlayer() const { return mSinglePlayer; } + + static void registerTypes() {} + +protected: + + /// Is this a client or server side object + bool mServerObject; + /// Does this object do special work in a single player situation? + bool mSinglePlayer; +}; + +#endif // _T3D_PHYSICS_PHYSICSOBJECT_H_ \ No newline at end of file diff --git a/T3D/physics/physicsPlayer.cpp b/T3D/physics/physicsPlayer.cpp new file mode 100644 index 0000000..91e21a4 --- /dev/null +++ b/T3D/physics/physicsPlayer.cpp @@ -0,0 +1,8 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physicsPlayer.h" + diff --git a/T3D/physics/physicsPlayer.h b/T3D/physics/physicsPlayer.h new file mode 100644 index 0000000..dfdcfbc --- /dev/null +++ b/T3D/physics/physicsPlayer.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PHYSICSPLAYER_H_ +#define _T3D_PHYSICS_PHYSICSPLAYER_H_ + +#ifndef _T3D_PHYSICS_PHYSICSOBJECT_H_ +#include "T3D/physics/physicsObject.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif + +struct Collision; +struct ObjectRenderInst; +class BaseMatInstance; +class Player; +class SceneState; +class SceneObject; + + +/// +class PhysicsPlayer : public PhysicsObject +{ + +protected: + + Collision *mLastCollision; + + Player *mPlayer; + +public: + + PhysicsPlayer( Player *player ) { mPlayer = player; } + virtual ~PhysicsPlayer() {}; + + virtual void findContact( SceneObject **contactObject, VectorF *contactNormal ) const = 0; + + virtual Point3F move( const VectorF &displacement, Collision *outCol ) = 0; + + virtual void setPosition( const MatrixF &mat ) = 0; + + virtual bool testSpacials( const Point3F &nPos, const Point3F &nSize ) const = 0; + + virtual void setSpacials( const Point3F &nPos, const Point3F &nSize ) = 0; + + virtual void renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) {}; + + virtual void enableCollision() = 0; + virtual void disableCollision() = 0; +}; + + +#endif // _T3D_PHYSICS_PHYSICSPLAYER_H_ \ No newline at end of file diff --git a/T3D/physics/physicsPlugin.cpp b/T3D/physics/physicsPlugin.cpp new file mode 100644 index 0000000..3dd91c1 --- /dev/null +++ b/T3D/physics/physicsPlugin.cpp @@ -0,0 +1,164 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physicsPlugin.h" + +#include "console/console.h" +#include "console/simSet.h" +#include "core/strings/stringFunctions.h" +#include "sceneGraph/sceneObject.h" +#include "T3D/physics/physicsObject.h" + + +PhysicsPlugin *gPhysicsPlugin = NULL; +PhysicsResetSignal PhysicsPlugin::smPhysicsResetSignal; + +String PhysicsPlugin::smServerWorldName( "server" ); +String PhysicsPlugin::smClientWorldName( "client" ); +bool PhysicsPlugin::smSinglePlayer = true; + + +PhysicsPlugin::PhysicsPlugin() +{ + mPhysicsCleanup = new SimSet(); + mPhysicsCleanup->assignName( "PhysicsCleanupSet" ); + mPhysicsCleanup->registerObject(); + Sim::getRootGroup()->addObject( mPhysicsCleanup ); +} + +PhysicsPlugin::~PhysicsPlugin() +{ + if ( mPhysicsCleanup ) + mPhysicsCleanup->deleteObject(); +} + +void PhysicsPlugin::registerObjectType( AbstractClassRep *torqueType, CreatePhysicsObjectFn createFn ) +{ + StringNoCase className( torqueType->getClassName() ); + mCreateFnMap.insert( className, createFn ); +} + +void PhysicsPlugin::unregisterObjectType( AbstractClassRep *torqueType ) +{ + StringNoCase className( torqueType->getClassName() ); + CreateFnMap::Iterator iter = mCreateFnMap.find( className ); + if ( iter != mCreateFnMap.end() ) + mCreateFnMap.erase( iter ); +} + +PhysicsObject* PhysicsPlugin::createPhysicsObject( SceneObject *torqueObj ) +{ + StringNoCase className( torqueObj->getClassName() ); + CreateFnMap::Iterator iter = mCreateFnMap.find( className ); + if ( iter == mCreateFnMap.end() ) + { + Con::warnf( "PhysicsPlugin::createPhysicsObject - abstract class %s was not registered.", className.c_str() ); + return NULL; + } + + return iter->value( torqueObj ); +} + +// Used to check if a physics plugin exists. +// This is useful for determining whether or not +// to initialize the Physics tools menu in the editor. +ConsoleFunction( physicsPluginPresent, bool, 1, 1, "bool ret = physicsPluginPresent()" ) +{ + return gPhysicsPlugin != NULL; +} + +ConsoleFunction( physicsInit, bool, 1, 1, "physicsInit()" ) +{ + if ( gPhysicsPlugin ) + { + Con::errorf( "Physics plugin already initialized!" ); + return false; + } + + #ifdef TORQUE_PHYSICS_ENABLED + extern bool physicsInitialize(); + return physicsInitialize(); + #else + return false; + #endif +} + +ConsoleFunction( physicsDestroy, bool, 1, 1, "physicsDestroy()" ) +{ + #ifdef TORQUE_PHYSICS_ENABLED + extern bool physicsDestroy(); + return physicsDestroy(); + #else + return false; + #endif +} + +ConsoleFunction( physicsInitWorld, bool, 2, 2, "physicsInitWorld( String worldName )" ) +{ + return gPhysicsPlugin && gPhysicsPlugin->createWorld( String( argv[1] ) ); +} + +ConsoleFunction( physicsDestroyWorld, void, 2, 2, "physicsDestroyWorld( String worldName )" ) +{ + if ( gPhysicsPlugin ) + gPhysicsPlugin->destroyWorld( String( argv[1] ) ); +} + + +// Control/query of the stop/started state +// of the currently running simulation. +ConsoleFunction( physicsStartSimulation, void, 2, 2, "physicsStartSimulation( String worldName )" ) +{ + if ( gPhysicsPlugin ) + gPhysicsPlugin->enableSimulation( String( argv[1] ), true ); +} + +ConsoleFunction( physicsStopSimulation, void, 2, 2, "physicsStopSimulation( String worldName )" ) +{ + if ( gPhysicsPlugin ) + gPhysicsPlugin->enableSimulation( String( argv[1] ), false ); +} + +ConsoleFunction( physicsSimulationEnabled, bool, 1, 1, "physicsSimulationEnabled()" ) +{ + return gPhysicsPlugin && gPhysicsPlugin->isSimulationEnabled(); +} + +// Used for slowing down time on the +// physics simulation, and for pausing/restarting +// the simulation. +ConsoleFunction( physicsSetTimeScale, void, 2, 2, "physicsSetTimeScale( F32 scale )" ) +{ + if ( gPhysicsPlugin ) + gPhysicsPlugin->setTimeScale( dAtof( argv[1] ) ); +} + +// Get the currently set time scale. +ConsoleFunction( physicsGetTimeScale, F32, 1, 1, "physicsGetTimeScale()" ) +{ + return gPhysicsPlugin && gPhysicsPlugin->getTimeScale(); +} + +// Used to send a signal to objects in the +// physics simulation that they should store +// their current state for later restoration, +// such as when the editor is closed. +ConsoleFunction( physicsStoreState, void, 1, 1, "physicsStoreState()" ) +{ + PhysicsPlugin::getPhysicsResetSignal().trigger( PhysicsResetEvent_Store ); +} + +// Used to send a signal to objects in the +// physics simulation that they should restore +// their saved state, such as when the editor is opened. +ConsoleFunction( physicsRestoreState, void, 1, 1, "physicsRestoreState()" ) +{ + // First delete all the cleanup objects. + if ( gPhysicsPlugin && gPhysicsPlugin->getPhysicsCleanup() ) + gPhysicsPlugin->getPhysicsCleanup()->deleteAllObjects(); + + PhysicsPlugin::getPhysicsResetSignal().trigger( PhysicsResetEvent_Restore ); +} diff --git a/T3D/physics/physicsPlugin.h b/T3D/physics/physicsPlugin.h new file mode 100644 index 0000000..bb02e70 --- /dev/null +++ b/T3D/physics/physicsPlugin.h @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PHYSICSPLUGIN_H_ +#define _T3D_PHYSICS_PHYSICSPLUGIN_H_ + +#ifndef _SIMSET_H_ +#include "console/simSet.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _UTIL_DELEGATE_H_ +#include "core/util/delegate.h" +#endif + + + +class NetObject; +class Player; +//class MatrixF; +//class Point3F; + +class SceneObject; +class PhysicsObject; +class PhysicsStatic; +class PhysicsWorld; +class PhysicsPlayer; + +enum PhysicsResetEvent +{ + PhysicsResetEvent_Store, + PhysicsResetEvent_Restore +}; + +typedef Signal PhysicsResetSignal; + +typedef Delegate CreatePhysicsObjectFn; +typedef Map CreateFnMap; + +/// +class PhysicsPlugin +{ + +protected: + + static PhysicsResetSignal smPhysicsResetSignal; + + // Our map of Strings to PhysicsWorld pointers. + Map mPhysicsWorldLookup; + + static String smServerWorldName; + static String smClientWorldName; + + /// A SimSet of objects to delete before the + /// physics reset/restore event occurs. + SimObjectPtr mPhysicsCleanup; + + static bool smSinglePlayer; + + Map mCreateFnMap; + +public: + + PhysicsPlugin(); + virtual ~PhysicsPlugin(); + + /// Returns the physics cleanup set. + SimSet* getPhysicsCleanup() const { return mPhysicsCleanup; } + + inline bool isSinglePlayer() const { return smSinglePlayer; } + + virtual PhysicsStatic* createStatic( NetObject *object ) = 0; + virtual PhysicsPlayer* createPlayer( Player *player ) = 0; + + virtual bool isSimulationEnabled() const = 0; + virtual void enableSimulation( const String &worldName, bool enable ) = 0; + + virtual void setTimeScale( const F32 timeScale ) = 0; + virtual const F32 getTimeScale() const = 0; + + static PhysicsResetSignal& getPhysicsResetSignal() { return smPhysicsResetSignal; } + + virtual bool createWorld( const String &worldName ) = 0; + virtual void destroyWorld( const String &worldName ) = 0; + + virtual PhysicsWorld* getWorld( const String &worldName ) const = 0; + + void registerObjectType( AbstractClassRep *torqueType, CreatePhysicsObjectFn createFn ); + void unregisterObjectType( AbstractClassRep *torqueType ); + + PhysicsObject* createPhysicsObject( SceneObject *torqueObj ); +}; + +/// The global pointer to the compile time +/// selected physics system. +extern PhysicsPlugin *gPhysicsPlugin; + +#endif // _T3D_PHYSICS_PHYSICSPLUGIN_H_ \ No newline at end of file diff --git a/T3D/physics/physicsStatic.cpp b/T3D/physics/physicsStatic.cpp new file mode 100644 index 0000000..3eb0d69 --- /dev/null +++ b/T3D/physics/physicsStatic.cpp @@ -0,0 +1,10 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physicsStatic.h" + + +Signal PhysicsStatic::smDeleteSignal; diff --git a/T3D/physics/physicsStatic.h b/T3D/physics/physicsStatic.h new file mode 100644 index 0000000..2ac83e5 --- /dev/null +++ b/T3D/physics/physicsStatic.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PHYSICSSTATIC_H_ +#define _T3D_PHYSICS_PHYSICSSTATIC_H_ + +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif + + +class MatrixF; +class Point3F; + + +/// Simple physics object that is normally static during gameplay. +class PhysicsStatic +{ +public: + + /// A generic signal triggered when a PhysicsStatic object is + /// deleted ( or modified ). Useful for other objects which cache static + /// physics objects as an optimization but need to not crash when in an editor + /// situation where normally static objects can change. + static Signal smDeleteSignal; + + virtual ~PhysicsStatic() { smDeleteSignal.trigger(); } + + /// This is not intended for movement during gameplay, but + /// only for infrequent changes when editing the mission. + virtual void setTransform( const MatrixF &xfm ) = 0; + + /// This is not intended for scaling during gameplay, but + /// only for infrequent changes when editing the mission. + virtual void setScale( const Point3F &scale ) = 0; + + /// This is used to signal that the Torque object has + /// changed its collision shape and it needs to be updated. + virtual void update() {} +}; + + +#endif // _T3D_PHYSICS_PHYSICSSTATIC_H_ \ No newline at end of file diff --git a/T3D/physics/physicsWorld.cpp b/T3D/physics/physicsWorld.cpp new file mode 100644 index 0000000..dd52cef --- /dev/null +++ b/T3D/physics/physicsWorld.cpp @@ -0,0 +1,7 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physicsWorld.h" diff --git a/T3D/physics/physicsWorld.h b/T3D/physics/physicsWorld.h new file mode 100644 index 0000000..1b89a7d --- /dev/null +++ b/T3D/physics/physicsWorld.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PHYSICSWORLD_H_ +#define _T3D_PHYSICS_PHYSICSWORLD_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +class ProcessList; +class Point3F; +struct RayInfo; + +class PhysicsWorld +{ + +public: + + virtual ~PhysicsWorld() {}; + + virtual bool initWorld( bool isServer, ProcessList *processList ) = 0; + virtual void destroyWorld() = 0; + + /// An abstract way to raycast into any type of PhysicsWorld, in a way + /// that mirrors a Torque-style raycast. + // + /// This method is not fully developed or very sophisticated. For example, + /// there is no system of collision groups or raycast masks, which could + /// be very complex to write in a PhysicsPlugin-Abstract way... + // + // Optional forceAmt parameter will also apply a force to hit objects. + + virtual bool castRay( const Point3F &startPnt, const Point3F &endPnt, RayInfo *ri, const Point3F &impulse ) = 0; + + virtual void explosion( const Point3F &pos, F32 radius, F32 forceMagnitude ) = 0; +}; + + +#endif // _T3D_PHYSICS_PHYSICSWORLD_H_ \ No newline at end of file diff --git a/T3D/physics/physx/px.h b/T3D/physics/physx/px.h new file mode 100644 index 0000000..688be8d --- /dev/null +++ b/T3D/physics/physx/px.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// +// This PhysX implementation for Torque was originally based on +// the "PhysX in TGEA" resource written by Shannon Scarvaci. +// +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=12711 +// + +#ifndef _PHYSX_H_ +#define _PHYSX_H_ + +/* +#ifndef _TORQUE_TYPES_H_ +# include "platform/types.h" +#endif +*/ + +#if defined(TORQUE_OS_MAC) && !defined(__APPLE__) + #define __APPLE__ +#elif defined(TORQUE_OS_LINUX) && !defined(LINUX) + #define LINUX +#elif defined(TORQUE_OS_WIN32) && !defined(WIN32) + #define WIN32 +#endif + +#ifndef NX_PHYSICS_NXPHYSICS +#include +#endif +#ifndef NX_FOUNDATION_NXSTREAM +#include +#endif +#ifndef NX_COOKING_H +#include +#endif +#ifndef NX_FOUNDATION_NXUSEROUTPUTSTREAM +#include +#endif + +#pragma comment(lib, "PhysXLoader.lib") +#pragma comment(lib, "NxCooking.lib") + +/// The single global physx sdk object for this process. +extern NxPhysicsSDK *gPhysicsSDK; + +enum PxGroups +{ + PxGroup_Default, + PxGroup_ClientOnly, +}; + +#endif // _PHYSX_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxBoxPlayer.cpp b/T3D/physics/physx/pxBoxPlayer.cpp new file mode 100644 index 0000000..7d5f6aa --- /dev/null +++ b/T3D/physics/physx/pxBoxPlayer.cpp @@ -0,0 +1,164 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxBoxPlayer.h" + +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxUserData.h" +#include "T3D/physics/physX/pxCasts.h" +#include "gfx/gfxDrawUtil.h" +#include "collision/collision.h" +#include "T3D/player.h" + +#include +#include +#include +#include +#include + + +PxBoxPlayer::PxBoxPlayer( Player *player, PxWorld *world ) + : PxPlayer( player, world ), + mBoxController( NULL ) +{ + world->releaseWriteLock(); + + PlayerData *datablock = dynamic_cast( player->getDataBlock() ); + + //NxScene *scene = world->getScene(); + + mSize = datablock->boxSize; + + Point3F pos = player->getPosition(); + pos.z += datablock->boxSize.z * 0.5f; + + Point3F size; + size.z = mSize.z - mSkinWidth; + size.x = size.y = getMax( mSize.x, mSize.y ) - mSkinWidth; + // BoxController is retarded, 'extents' is really halfExtents + size *= 0.5f; + + NxBoxControllerDesc desc; + desc.extents = pxCast(size); + desc.skinWidth = mSkinWidth; + desc.position = pxCast( pos ); + desc.upDirection = NX_Z; + desc.callback = this; // TODO: Fix this as well! + desc.slopeLimit = datablock->runSurfaceCos; + desc.stepOffset = datablock->maxStepHeight; + + mUserData.setObject( player ); + desc.userData = &mUserData; + + mController = mWorld->createController( desc ); + mBoxController = static_cast( mController ); + + mController->setInteraction( NXIF_INTERACTION_INCLUDE ); + mController->getActor()->setMass( datablock->mass ); +} + +void PxBoxPlayer::_findContact( SceneObject **contactObject, VectorF *contactNormal ) const +{ + // Do a short box sweep downward... + + // Calculate the sweep motion... + + //F32 halfCapSize = mSize.z * 0.5f; + //F32 halfSmallCapSize = halfCapSize * 0.8f; + //F32 diff = halfCapSize - halfSmallCapSize; + + F32 offsetDist = mSkinWidth + 0.01f; + NxVec3 motion(0,0,-offsetDist); + + // Construct the box... + + NxBox worldBox; + worldBox.center = pxCast( mBoxController->getDebugPosition() ); + worldBox.extents = mBoxController->getExtents(); + worldBox.rot.id(); + + NxSweepQueryHit sweepHit; + + //mBoxController->getActor()->raiseActorFlag( NX_AF_DISABLE_COLLISION ); + + NxU32 hitCount = mWorld->getScene()->linearOBBSweep( worldBox, motion, NX_SF_STATICS | NX_SF_DYNAMICS, NULL, 1, &sweepHit, NULL ); + //if ( hitCount > 0 ) + //{ + // void *data = sweepHit.hitShape->getActor().userData; + // int i = 0; + //} + //mBoxController->getActor()->clearActorFlag( NX_AF_DISABLE_COLLISION ); + + if ( hitCount > 0 ) + { + PxUserData *data = PxUserData::getData( sweepHit.hitShape->getActor() ); + if ( data ) + (*contactObject) = data->getObject(); + (*contactNormal) = pxCast( sweepHit.normal ); + } +} + +bool PxBoxPlayer::testSpacials( const Point3F &nPos, const Point3F &nSize ) const +{ + AssertFatal( nSize.least() > 0.0f, "PxBoxPlayer::testSpacials(), invalid extents!" ); + + // We are assuming the position passed in is at the bottom of the object + // box, like a standard player. + Point3F adjustedPos = nPos; + adjustedPos.z += nSize.z * 0.5f; + + Point3F size; + size.z = nSize.z - mSkinWidth; + size.x = size.y = getMax( nSize.x, nSize.y ) - mSkinWidth; + + Point3F halfSize = size * 0.5f; + + NxBounds3 worldBounds; + worldBounds.min = pxCast( adjustedPos - halfSize ); + worldBounds.max = pxCast( adjustedPos + halfSize ); + + bool hit = mWorld->getScene()->checkOverlapAABB( worldBounds, NX_STATIC_SHAPES, 0xffffffff, NULL ); + return !hit; +} + +void PxBoxPlayer::setSpacials( const Point3F &nPos, const Point3F &nSize ) +{ + AssertFatal( nSize.least() > 0.0f, "PxPlayer::setSpacials(), invalid extents!" ); + + if ( mWorld ) + mWorld->releaseWriteLock(); + + mSize = nSize; + + // We are assuming the position passed in is at the bottom of the object + // box, like a standard player. + Point3F adjustedPos = nPos; + adjustedPos.z += mSize.z * 0.5f; + + Point3F adjustedSize = nSize; + adjustedSize.z = nSize.z - mSkinWidth; + adjustedSize.x = adjustedSize.y = getMax( nSize.x, nSize.y ) - mSkinWidth; + // BoxController is retarded, 'extents' is really halfExtents + adjustedSize *= 0.5f; + + mBoxController->setPosition( pxCast( adjustedPos ) ); + mBoxController->setExtents( pxCast( adjustedSize ) ); +} + +void PxBoxPlayer::renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + Point3F pos = pxCast( mBoxController->getDebugPosition() ); + Point3F size = pxCast( mBoxController->getExtents() ); + // BoxController is retarded, 'extents' is really halfExtents + size *= 2; + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + GFX->getDrawUtil()->drawCube( desc, size, pos, ColorI(100,100,200,160) ); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxBoxPlayer.h b/T3D/physics/physx/pxBoxPlayer.h new file mode 100644 index 0000000..dbd6389 --- /dev/null +++ b/T3D/physics/physx/pxBoxPlayer.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXBOXPLAYER_H +#define _PXBOXPLAYER_H + +#ifndef _PHYSX_H_ +#include "T3D/physics/physX/px.h" +#endif +#ifndef _PXPLAYER_H +#include "T3D/physics/physX/pxPlayer.h" +#endif + +class Player; +class NxBoxController; +class PxWorld; + + +class PxBoxPlayer : public PxPlayer +{ + +protected: + + // Points to the same address as PxPlayer::mController + NxBoxController *mBoxController; + +public: + + PxBoxPlayer( Player *player, PxWorld *world ); + //virtual ~PxBoxPlayer() {} + + virtual bool testSpacials( const Point3F &nPos, const Point3F &nSize ) const; + + virtual void setSpacials( const Point3F &nPos, const Point3F &nSize ); + + virtual void renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + +protected: + + virtual void _findContact( SceneObject **contactObject, VectorF *contactNormal ) const; +}; + + +#endif // _PXBOXPLAYER_H \ No newline at end of file diff --git a/T3D/physics/physx/pxCapsulePlayer.cpp b/T3D/physics/physx/pxCapsulePlayer.cpp new file mode 100644 index 0000000..1b265f6 --- /dev/null +++ b/T3D/physics/physx/pxCapsulePlayer.cpp @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxCapsulePlayer.h" + +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxUserData.h" +#include "T3D/physics/physX/pxCasts.h" +#include "gfx/gfxDrawUtil.h" +#include "collision/collision.h" +#include "T3D/player.h" + +#include +#include +#include +#include + + +PxCapsulePlayer::PxCapsulePlayer( Player *player, PxWorld *world ) + : PxPlayer( player, world ), + mCapsuleController( NULL ) +{ + world->releaseWriteLock(); + + PlayerData *datablock = dynamic_cast( player->getDataBlock() ); + + // Don't forget to initialize base member! + mSize = datablock->boxSize; + + const Point3F &pos = player->getPosition(); + + NxCapsuleControllerDesc desc; + desc.skinWidth = mSkinWidth; + desc.radius = getMax( mSize.x, mSize.y ) * 0.5f; + desc.radius -= desc.skinWidth; + desc.height = mSize.z - ( desc.radius * 2.0f ); + desc.height -= desc.skinWidth * 2.0f; + + desc.climbingMode = CLIMB_CONSTRAINED; + desc.position.set( pos.x, pos.y, pos.z + ( mSize.z * 0.5f ) ); + desc.upDirection = NX_Z; + desc.callback = this; // TODO: Fix this as well! + desc.slopeLimit = datablock->runSurfaceCos; + desc.stepOffset = datablock->maxStepHeight; + + mUserData.setObject( player ); + desc.userData = &mUserData; + + // Don't forget to initialize base member! + mController = mWorld->createController( desc ); + + // Cast to a NxCapsuleController for our convenience + mCapsuleController = static_cast( mController ); + + mCapsuleController->setInteraction( NXIF_INTERACTION_INCLUDE ); + mCapsuleController->getActor()->setMass( datablock->mass ); +} + +void PxCapsulePlayer::_findContact( SceneObject **contactObject, VectorF *contactNormal ) const +{ + // Do a short capsule sweep downward... + + // Calculate the sweep motion... + + F32 halfCapSize = mSize.z * 0.5f; + F32 halfSmallCapSize = halfCapSize * 0.8f; + F32 diff = halfCapSize - halfSmallCapSize; + + F32 offsetDist = diff + mSkinWidth + 0.01f; + NxVec3 motion(0,0,-offsetDist); + + // Construct the capsule... + + F32 radius = mCapsuleController->getRadius(); + F32 halfHeight = mCapsuleController->getHeight() * 0.5f; + + NxCapsule capsule; + capsule.p0 = capsule.p1 = pxCast( mCapsuleController->getDebugPosition() ); + capsule.p0.z -= halfHeight; + capsule.p1.z += halfHeight; + capsule.radius = radius; + + NxSweepQueryHit sweepHit; + + NxU32 hitCount = mWorld->getScene()->linearCapsuleSweep( capsule, motion, NX_SF_STATICS | NX_SF_DYNAMICS, NULL, 1, &sweepHit, NULL ); + + if ( hitCount > 0 ) + { + PxUserData *data = PxUserData::getData( sweepHit.hitShape->getActor() ); + if ( data ) + { + (*contactObject) = data->getObject(); + (*contactNormal) = pxCast( sweepHit.normal ); + } + } +} + +bool PxCapsulePlayer::testSpacials( const Point3F &nPos, const Point3F &nSize ) const +{ + AssertFatal( nSize.least() > 0.0f, "PxCapsulePlayer::testSpacials(), invalid extents!" ); + + F32 radius = getMax( nSize.x, nSize.y ) / 2.0f; + radius -= mSkinWidth; + radius = getMax(radius,0.01f); + F32 height = nSize.z - ( radius * 2.0f ); + height -= mSkinWidth * 2.0f; + height = getMax( height, 0.01f ); + + F32 halfHeight = height * 0.5f; + + // We are assuming the position passed in is at the bottom of the object + // box, like a standard player. + Point3F adjustedPos = nPos; + adjustedPos.z += nSize.z * 0.5f; + NxVec3 origin = pxCast( adjustedPos ); + + NxCapsule worldCapsule( NxSegment(origin,origin), radius ); + worldCapsule.p0.z -= halfHeight; + worldCapsule.p1.z += halfHeight; + + bool hit = mWorld->getScene()->checkOverlapCapsule( worldCapsule, NX_STATIC_SHAPES, 0xffffffff, NULL ); + return !hit; +} + +void PxCapsulePlayer::setSpacials( const Point3F &nPos, const Point3F &nSize ) +{ + AssertFatal( nSize.least() > 0.0f, "PxCapsulePlayer::setSpacials(), invalid extents!" ); + + if ( mWorld ) + mWorld->releaseWriteLock(); + + mSize = nSize; + + F32 radius = getMax( nSize.x, nSize.y ) * 0.5f; + radius -= mSkinWidth; + radius = getMax(radius,0.01f); + F32 height = nSize.z - ( radius * 2.0f ); + height -= mSkinWidth * 2.0f; + height = getMax(height,0.01f); + + // The CapsuleController's 'actual' position we are setting. + // We are assuming the position passed in is at the bottom of the object + // box, like a standard player. + Point3F adjustedPos = nPos; + adjustedPos.z += mSize.z * 0.5f; + + mCapsuleController->setPosition( pxCast( adjustedPos ) ); + mCapsuleController->setRadius( radius ); + mCapsuleController->setHeight( height ); +} + +void PxCapsulePlayer::renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + Point3F center = pxCast( mCapsuleController->getDebugPosition() ); + F32 radius = mCapsuleController->getRadius(); + F32 height = mCapsuleController->getHeight(); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + GFX->getDrawUtil()->drawCapsule( desc, center, radius, height, ColorI(100,100,200,160) ); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxCapsulePlayer.h b/T3D/physics/physx/pxCapsulePlayer.h new file mode 100644 index 0000000..b6ef67a --- /dev/null +++ b/T3D/physics/physx/pxCapsulePlayer.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXCAPSULEPLAYER_H +#define _PXCAPSULEPLAYER_H + +#ifndef _PXPLAYER_H_ +#include "T3D/physics/physX/pxPlayer.h" +#endif + +class Player; +class PxWorld; +class NxCapsuleController; + + +class PxCapsulePlayer : public PxPlayer +{ + +protected: + + // Points to the same address as PxPlayer::mController + NxCapsuleController *mCapsuleController; + +public: + + PxCapsulePlayer( Player *player, PxWorld *world ); + //virtual ~PxCapsulePlayer() {} + + virtual bool testSpacials( const Point3F &nPos, const Point3F &nSize ) const; + + virtual void setSpacials( const Point3F &nPos, const Point3F &nSize ); + + virtual void renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + +protected: + + virtual void _findContact( SceneObject **contactObject, VectorF *contactNormal ) const; +}; + + +#endif // _PXCAPSULEPLAYER_H \ No newline at end of file diff --git a/T3D/physics/physx/pxCasts.h b/T3D/physics/physx/pxCasts.h new file mode 100644 index 0000000..d6244dc --- /dev/null +++ b/T3D/physics/physx/pxCasts.h @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PHYSX_CASTS_H_ +#define _PHYSX_CASTS_H_ + +#ifndef _PHYSX_H_ +#include "physX/physX.h" +#endif +#ifndef NX_PHYSICS_NXBIG +#include "NxExtended.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif + + +template inline T pxCast( const F &from ); + +//------------------------------------------------------------------------- + +template<> +inline Point3F pxCast( const NxVec3 &vec ) +{ + return Point3F( vec.x, vec.y, vec.z ); +} + +template<> +inline NxVec3 pxCast( const Point3F &point ) +{ + return NxVec3( point.x, point.y, point.z ); +} + +//------------------------------------------------------------------------- + +template<> +inline NxBounds3 pxCast( const Box3F &box ) +{ + NxBounds3 bounds; + bounds.set( box.minExtents.x, + box.minExtents.y, + box.minExtents.z, + box.maxExtents.x, + box.maxExtents.y, + box.maxExtents.z ); + return bounds; +} + +template<> +inline Box3F pxCast( const NxBounds3 &bounds ) +{ + return Box3F( bounds.min.x, + bounds.min.y, + bounds.min.z, + bounds.max.x, + bounds.max.y, + bounds.max.z ); +} + +//------------------------------------------------------------------------- + +template<> +inline NxVec3 pxCast( const NxExtendedVec3 &xvec ) +{ + return NxVec3( xvec.x, xvec.y, xvec.z ); +} + +template<> +inline NxExtendedVec3 pxCast( const NxVec3 &vec ) +{ + return NxExtendedVec3( vec.x, vec.y, vec.z ); +} + +//------------------------------------------------------------------------- + +template<> +inline NxExtendedVec3 pxCast( const Point3F &point ) +{ + return NxExtendedVec3( point.x, point.y, point.z ); +} + +template<> +inline Point3F pxCast( const NxExtendedVec3 &xvec ) +{ + return Point3F( xvec.x, xvec.y, xvec.z ); +} + +//------------------------------------------------------------------------- + +template<> +inline NxBox pxCast( const NxExtendedBounds3 &exBounds ) +{ + NxExtendedVec3 center; + exBounds.getCenter( center ); + NxVec3 extents; + exBounds.getExtents( extents ); + + NxBox box; + box.center.set( center.x, center.y, center.z ); + box.extents = extents; + box.rot.id(); + + return box; +} + +template<> +inline NxExtendedBounds3 pxCast( const NxBox &box ) +{ + AssertFatal( false, "Casting a NxBox to NxExtendedBounds3 is impossible without losing rotation data!" ); + return NxExtendedBounds3(); +} + +#endif // _PHYSX_CASTS_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxCloth.cpp b/T3D/physics/physx/pxCloth.cpp new file mode 100644 index 0000000..042dc19 --- /dev/null +++ b/T3D/physics/physx/pxCloth.cpp @@ -0,0 +1,890 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxCloth.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physx/pxWorld.h" +#include "T3D/physics/physx/pxStream.h" +#include "gfx/gfxDrawUtil.h" +#include "math/mathIO.h" +#include "core/stream/bitStream.h" +#include "materials/materialManager.h" + +IMPLEMENT_CO_NETOBJECT_V1( PxCloth ); + +static EnumTable::Enums gAttachmentFlagEnums[] = +{ + { 0, "Bottom Right" }, + { 1, "Bottom Left" }, + { 2, "Top Right" }, + { 3, "Top Left" }, + { 4, "Top Center" }, + { 5, "Bottom Center" }, + { 6, "Right Center" }, + { 7, "Left Center" }, + { 8, "Top Edge" }, + { 9, "Bottom Edge" }, + { 10, "Right Edge" }, + { 11, "Left Edge" } +}; +EnumTable PxCloth::mAttachmentFlagTable( 12, gAttachmentFlagEnums ); + +PxCloth::PxCloth() + : mWorld( NULL ), + mScene( NULL ) +{ + mMaterialName = "wooden_beams"; + mMatInst = NULL; + mMaterial = NULL; + + mVertexRenderBuffer = NULL; + mIndexRenderBuffer = NULL; + + mMaxVertices = 0; + mMaxIndices = 0; + + mTeared = false; + mIsDummy = false; + + mClothMesh = NULL; + mCloth = NULL; + + mPatchSamples.set( 8.0f, 8.0f ); + mPatchSize.set( 8.0f, 8.0f ); + + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType | ShadowCasterObjectType; + + mClothMeshDesc.setToDefault(); + mClothDesc.setToDefault(); + mReceiveBuffers.setToDefault(); + + mBendingEnabled = false; + mDampingEnabled = false; + mTriangleCollisionEnabled = false; + mSelfCollisionEnabled = false; + + mRecreatePending = false; + + mDensity = 1.0f; + mThickness = 0.1f; + mFriction = 0.25f; + mBendingStiffness = 0.5f; + mStretchingStiffness = 0.5f; + mDampingCoefficient = 0.25f; + mCollisionResponseCoefficient = 1.0f; + mAttachmentResponseCoefficient = 1.0f; + + mAttachmentMask = 0; +} + +PxCloth::~PxCloth() +{ +} + +bool PxCloth::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + mIsDummy = isClientObject() && gPhysicsPlugin->isSinglePlayer(); + String worldName = isServerObject() ? "server" : "client"; + + // SinglePlayer objects only have server-side physics representations. + if ( mIsDummy ) + worldName = "server"; + + mWorld = dynamic_cast( gPhysicsPlugin->getWorld( worldName ) ); + + if ( !mWorld || !mWorld->getScene() ) + { + Con::errorf( "PxCloth::onAdd() - PhysXWorld not initialized!" ); + return false; + } + + mScene = mWorld->getScene(); + + Point3F halfScale = Point3F::One * 0.5f; + mObjBox.minExtents = -halfScale; + mObjBox.maxExtents = halfScale; + resetWorldBox(); + + if ( !mIsDummy ) + { + _initClothMesh(); + _initReceiveBuffers( mClothMeshDesc.numVertices, mClothMeshDesc.numTriangles ); + _createClothPatch( getTransform() ); + _setupAttachments(); + mResetXfm = getTransform(); + } + + if ( !mIsDummy ) + PhysicsPlugin::getPhysicsResetSignal().notify( this, &PxCloth::onPhysicsReset, 1053.0f ); + + addToScene(); + + return true; +} + +void PxCloth::onRemove() +{ + SAFE_DELETE( mMatInst ); + + mWorld->getPhysicsResults(); + + if ( mCloth && !mIsDummy ) + mWorld->releaseCloth( *mCloth ); + + if ( mClothMesh && !mIsDummy ) + mWorld->releaseClothMesh( *mClothMesh ); + + mCloth = NULL; + mClothMesh = NULL; + + if ( !mIsDummy ) + { + delete [] mVertexRenderBuffer; + delete [] mIndexRenderBuffer; + } + + removeFromScene(); + + if ( !mIsDummy ) + PhysicsPlugin::getPhysicsResetSignal().remove( this, &PxCloth::onPhysicsReset ); + + Parent::onRemove(); +} + +void PxCloth::onPhysicsReset( PhysicsResetEvent reset ) +{ + PxCloth *serverObj = NULL; + if ( isServerObject() ) + serverObj = this; + else + serverObj = static_cast( mServerObject.getObject() ); + + if ( !serverObj ) + return; + + // Store the reset transform for later use. + if ( reset == PhysicsResetEvent_Store ) + { + serverObj->mResetXfm = serverObj->getTransform(); + mRecreatePending = true; + } + else if ( reset == PhysicsResetEvent_Restore ) + mRecreatePending = true; +} + +void PxCloth::initPersistFields() +{ + Parent::initPersistFields(); + + addField( "material", TypeMaterialName, Offset( mMaterialName, PxCloth ) ); + addField( "samples", TypePoint2F, Offset( mPatchSamples, PxCloth ) ); + addField( "size", TypePoint2F, Offset( mPatchSize, PxCloth ) ); + + addField( "bending", TypeBool, Offset( mBendingEnabled, PxCloth ) ); + addField( "damping", TypeBool, Offset( mDampingEnabled, PxCloth ) ); + addField( "triangleCollision", TypeBool, Offset( mTriangleCollisionEnabled, PxCloth ) ); + addField( "selfCollision", TypeBool, Offset( mSelfCollisionEnabled, PxCloth ) ); + + addField( "density", TypeF32, Offset( mDensity, PxCloth ) ); + addField( "thickness", TypeF32, Offset( mThickness, PxCloth ) ); + addField( "friction", TypeF32, Offset( mFriction, PxCloth ) ); + + addField( "bendingStiffness", TypeF32, Offset( mBendingStiffness, PxCloth ) ); + addField( "stretchingStiffness", TypeF32, Offset( mStretchingStiffness, PxCloth ) ); + + addField( "dampingCoefficient", TypeF32, Offset( mDampingCoefficient, PxCloth ) ); + addField( "collisionResponseCoefficient", TypeF32, Offset( mCollisionResponseCoefficient, PxCloth ) ); + addField( "attachmentResponseCoefficient", TypeF32, Offset( mAttachmentResponseCoefficient, PxCloth ) ); + + addField( "attachments", TypeBitMask32, Offset( mAttachmentMask, PxCloth ), 1, &mAttachmentFlagTable ); +} + +void PxCloth::inspectPostApply() +{ + Parent::inspectPostApply(); + + setMaskBits( UpdateMask ); + mRecreatePending = true; +} + +U32 PxCloth::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + mathWrite( *stream, getTransform() ); + mathWrite( *stream, getScale() ); + mathWrite( *stream, mPatchSamples ); + mathWrite( *stream, mPatchSize ); + + stream->write( mMaterialName ); + + stream->writeFlag( mBendingEnabled ); + stream->writeFlag( mDampingEnabled ); + stream->writeFlag( mTriangleCollisionEnabled ); + stream->writeFlag( mSelfCollisionEnabled ); + + stream->write( mDensity ); + stream->write( mThickness ); + stream->write( mFriction ); + stream->write( mBendingStiffness ); + stream->write( mStretchingStiffness ); + stream->write( mDampingCoefficient ); + stream->write( mCollisionResponseCoefficient ); + stream->write( mAttachmentResponseCoefficient ); + + stream->write( mAttachmentMask ); + } + + return retMask; +} + +void PxCloth::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + // UpdateMask + if ( stream->readFlag() ) + { + MatrixF mat; + mathRead( *stream, &mat ); + Point3F scale; + mathRead( *stream, &scale ); + + setScale( scale ); + setTransform( mat ); + + mathRead( *stream, &mPatchSamples ); + mathRead( *stream, &mPatchSize ); + + stream->read( &mMaterialName ); + _updateMaterial(); + + mBendingEnabled = stream->readFlag(); + mDampingEnabled = stream->readFlag(); + mTriangleCollisionEnabled = stream->readFlag(); + mSelfCollisionEnabled = stream->readFlag(); + + stream->read( &mDensity ); + stream->read( &mThickness ); + stream->read( &mFriction ); + stream->read( &mBendingStiffness ); + stream->read( &mStretchingStiffness ); + stream->read( &mDampingCoefficient ); + stream->read( &mCollisionResponseCoefficient ); + stream->read( &mAttachmentResponseCoefficient ); + + stream->read( &mAttachmentMask ); + } +} + +void PxCloth::_recreateCloth( const MatrixF &transform ) +{ + if ( !mWorld ) + return; + + mWorld->getPhysicsResults(); + + if ( mCloth ) + { + mWorld->releaseCloth( *mCloth ); + mCloth = NULL; + } + + if ( mClothMesh ) + { + mWorld->releaseClothMesh( *mClothMesh ); + mClothMesh = NULL; + } + + // TODO: We don't need to recreate the mesh if just + // a parameter of the cloth was changed. + _initClothMesh(); + + _initReceiveBuffers( mClothMeshDesc.numVertices, mClothMeshDesc.numTriangles ); + + _createClothPatch( transform ); + + _setupAttachments(); +} + +void PxCloth::_setClothFromServer( PxCloth *serverObj ) +{ + mCloth = serverObj->mCloth; + mClothMesh = serverObj->mClothMesh; + + mClothDesc = serverObj->mClothDesc; + mClothMeshDesc = serverObj->mClothMeshDesc; + + mReceiveBuffers = serverObj->mReceiveBuffers; + + mVertexRenderBuffer = serverObj->mVertexRenderBuffer; + mIndexRenderBuffer = serverObj->mIndexRenderBuffer; + + mNumVertices = serverObj->mNumVertices; + mNumIndices = serverObj->mNumIndices; + mMaxVertices = serverObj->mMaxVertices; + mMaxIndices = serverObj->mMaxIndices; +} + +void PxCloth::setTransform( const MatrixF &mat ) +{ + Parent::setTransform( mat ); + + if ( isServerObject() ) + mRecreatePending = true; + else + { + PxCloth *serverObj = static_cast( mServerObject.getObject() ); + + if ( !serverObj ) + return; + + _setClothFromServer( serverObj ); + } +} + +void PxCloth::setScale( const VectorF &scale ) +{ + Parent::setScale( scale ); +} + +bool PxCloth::prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + if ( isLastState(state, stateKey) || + !state->isObjectRendered(this) ) + return false; + + setLastState( state, stateKey ); + + if ( mIsVBDirty ) + _updateVBIB(); + + MeshRenderInst *ri = state->getRenderPass()->allocInst(); + + // If this isn't the shadow pass then setup + // lights for the cloth mesh. + if ( !state->isShadowPass() ) + { + LightManager *lm = gClientSceneGraph->getLightManager(); + lm->setupLights( this, getWorldSphere() ); + lm->getBestLights( ri->lights, 8 ); + lm->resetLights(); + } + + ri->projection = state->getRenderPass()->allocSharedXform(RenderPassManager::Projection); + + if ( mNumIndices > 0 ) + ri->objectToWorld = &MatrixF::Identity; + else + ri->objectToWorld = state->getRenderPass()->allocUniqueXform( getTransform() ); + + ri->worldToCamera = state->getRenderPass()->allocSharedXform(RenderPassManager::View); + ri->type = RenderPassManager::RIT_Mesh; + + ri->primBuff = &mPrimBuffer; + ri->vertBuff = &mVB; + + ri->matInst = mMatInst; + + ri->prim = state->getRenderPass()->allocPrim(); + ri->prim->type = GFXTriangleList; + ri->prim->minIndex = 0; + ri->prim->startIndex = 0; + + if ( mNumIndices > 0 ) + ri->prim->numPrimitives = mNumIndices / 3; + else + ri->prim->numPrimitives = 2; + + ri->prim->startVertex = 0; + + if ( mNumVertices > 0 ) + ri->prim->numVertices = mNumVertices; + else + ri->prim->numVertices = 4; + + ri->defaultKey = (U32)ri->vertBuff; + ri->defaultKey2 = 0; + + state->getRenderPass()->addInst( ri ); + + return true; +} + +void PxCloth::_initClothMesh() +{ + // Generate a uniform cloth patch, + // w and h are the width and height, + // d is the distance between vertices. + U32 numX = (U32)mPatchSamples.x + 1; + U32 numY = (U32)mPatchSamples.x + 1; + + mClothMeshDesc.numVertices = (numX+1) * (numY+1); + mClothMeshDesc.numTriangles = numX*numY*2; + mClothMeshDesc.pointStrideBytes = sizeof(NxVec3); + mClothMeshDesc.triangleStrideBytes = 3*sizeof(NxU32); + mClothMeshDesc.points = (NxVec3*)dMalloc(sizeof(NxVec3)*mClothMeshDesc.numVertices); + mClothMeshDesc.triangles = (NxU32*)dMalloc(sizeof(NxU32)*mClothMeshDesc.numTriangles*3); + mClothMeshDesc.flags = 0; + + U32 i,j; + NxVec3 *p = (NxVec3*)mClothMeshDesc.points; + + F32 patchWidth = (F32)(mPatchSize.x / (F32)mPatchSamples.x); + F32 patchHeight = (F32)(mPatchSize.y / (F32)mPatchSamples.y); + + for (i = 0; i <= numY; i++) + { + for (j = 0; j <= numX; j++) + { + p->set( patchWidth * j, 0.0f, patchHeight * i ); + p++; + } + } + + NxU32 *id = (NxU32*)mClothMeshDesc.triangles; + + for (i = 0; i < numY; i++) + { + for (j = 0; j < numX; j++) + { + NxU32 i0 = i * (numX+1) + j; + NxU32 i1 = i0 + 1; + NxU32 i2 = i0 + (numX+1); + NxU32 i3 = i2 + 1; + if ( (j+i) % 2 ) + { + *id++ = i0; + *id++ = i2; + *id++ = i1; + *id++ = i1; + *id++ = i2; + *id++ = i3; + } + else + { + *id++ = i0; + *id++ = i2; + *id++ = i3; + *id++ = i0; + *id++ = i3; + *id++ = i1; + } + } + } + + NxInitCooking(); + + // Ok... cook the mesh! + NxCookingParams params; + params.targetPlatform = PLATFORM_PC; + params.skinWidth = 0.01f; + params.hintCollisionSpeed = false; + + NxSetCookingParams( params ); + + PxMemStream cooked; + + if ( NxCookClothMesh( mClothMeshDesc, cooked ) ) + { + cooked.resetPosition(); + mClothMesh = gPhysicsSDK->createClothMesh( cooked ); + } + + NxCloseCooking(); + NxVec3 *ppoints = (NxVec3*)mClothMeshDesc.points; + NxU32 *triangs = (NxU32*)mClothMeshDesc.triangles; + + dFree( ppoints ); + dFree( triangs ); +} + +void PxCloth::_createClothPatch( const MatrixF &transform ) +{ + mClothDesc.globalPose.setRowMajor44( transform ); + mClothDesc.thickness = mThickness; + mClothDesc.density = mDensity; + mClothDesc.bendingStiffness = mBendingStiffness; + //clothDesc.stretchingStiffness = mStretchingStiffness; + mClothDesc.dampingCoefficient = mDampingCoefficient; + mClothDesc.friction = mFriction; + mClothDesc.collisionResponseCoefficient = mCollisionResponseCoefficient; + //clothDesc.attachmentResponseCoefficient = mAttachmentResponseCoefficient; + //clothDesc.solverIterations = 5; + //clothDesc.flags |= NX_CLF_STATIC; + //clothDesc.flags |= NX_CLF_DISABLE_COLLISION; + //clothDesc.flags |= NX_CLF_VISUALIZATION; + // clothDesc.flags &= ~NX_CLF_GRAVITY; + if ( mBendingEnabled ) + mClothDesc.flags |= NX_CLF_BENDING; + + //clothDesc.flags |= NX_CLF_BENDING_ORTHO; + + if ( mDampingEnabled ) + mClothDesc.flags |= NX_CLF_DAMPING; + //clothDesc.flags |= NX_CLF_COMDAMPING; + if ( mTriangleCollisionEnabled ) + mClothDesc.flags |= NX_CLF_TRIANGLE_COLLISION; + if ( mSelfCollisionEnabled ) + mClothDesc.flags |= NX_CLF_SELFCOLLISION; + //clothDesc.flags |= NX_CLF_COLLISION_TWOWAY; + + mClothDesc.clothMesh = mClothMesh; + mClothDesc.meshData = mReceiveBuffers; + + if ( !mClothDesc.isValid() ) + return; + + mCloth = mScene->createCloth( mClothDesc ); + if ( !mCloth ) + return; + + NxBounds3 box; + mCloth->getWorldBounds( box ); + + Point3F min = pxCast( box.min ); + Point3F max = pxCast( box.max ); + + mWorldBox.set( min, max ); + mObjBox = mWorldBox; + + getWorldTransform().mul( mObjBox ); + resetWorldBox(); +} + +void PxCloth::_initReceiveBuffers( U32 numVertices, U32 numTriangles ) +{ + // here we setup the buffers through which the SDK returns the dynamic cloth data + // we reserve more memory for vertices than the initial mesh takes + // because tearing creates new vertices + // the SDK only tears cloth as long as there is room in these buffers + + mMaxVertices = 3 * numVertices; + mMaxIndices = 3 * numTriangles; + + mNumIndices = numTriangles; + mNumVertices = numVertices; + + // Allocate Render Buffer for Vertices if it hasn't been done before + delete [] mVertexRenderBuffer; + mVertexRenderBuffer = new GFXVertexPNTT[mMaxVertices]; + + delete [] mIndexRenderBuffer; + mIndexRenderBuffer = new U16[mMaxIndices]; + + mReceiveBuffers.verticesPosBegin = &(mVertexRenderBuffer[0].point); + mReceiveBuffers.verticesNormalBegin = &(mVertexRenderBuffer[0].normal); + mReceiveBuffers.verticesPosByteStride = sizeof(GFXVertexPNTT); + mReceiveBuffers.verticesNormalByteStride = sizeof(GFXVertexPNTT); + mReceiveBuffers.maxVertices = mMaxVertices; + mReceiveBuffers.numVerticesPtr = &mNumVertices; + + // the number of triangles is constant, even if the cloth is torn + mReceiveBuffers.indicesBegin = &mIndexRenderBuffer[0]; + mReceiveBuffers.indicesByteStride = sizeof(NxU16); + mReceiveBuffers.maxIndices = mMaxIndices; + mReceiveBuffers.numIndicesPtr = &mNumIndices; + + // Set up texture coords. + + U32 numX = (U32)(mPatchSamples.x) + 1; + U32 numY = (U32)(mPatchSamples.y) + 1; + + F32 dx = 1.0f; if (numX > 0) dx /= numX; + F32 dy = 1.0f; if (numY > 0) dy /= numY; + + F32 *coord = (F32*)&mVertexRenderBuffer[0].texCoord; + for ( U32 i = 0; i <= numY; i++) + { + for ( U32 j = 0; j <= numX; j++) + { + coord[0] = j*dx; + coord[1] = i*-dy; + coord += sizeof( GFXVertexPNTT ) / sizeof( F32 ); + } + } + + // the parent index information would be needed if we used textured cloth + //mReceiveBuffers.parentIndicesBegin = (U32*)malloc(sizeof(U32)*mMaxVertices); + //mReceiveBuffers.parentIndicesByteStride = sizeof(U32); + //mReceiveBuffers.maxParentIndices = mMaxVertices; + //mReceiveBuffers.numParentIndicesPtr = &mNumParentIndices; + + mReceiveBuffers.dirtyBufferFlagsPtr = &mMeshDirtyFlags; + + // init the buffers in case we want to draw the mesh + // before the SDK as filled in the correct values + + mReceiveBuffers.flags |= NX_MDF_16_BIT_INDICES; + + mMeshDirtyFlags = 0; + mNumParentIndices = 0; + mNumVertices = 0; + mNumIndices = 0; +} + +void PxCloth::_updateMaterial() +{ + if ( mMaterialName.isEmpty() ) + return; + + Material *pMat = NULL; + if ( !Sim::findObject( mMaterialName, pMat ) ) + { + Con::printf( "PxCloth::unpackUpdate, failed to find Material of name &s!", mMaterialName.c_str() ); + return; + } + + mMaterial = pMat; + + // Only update material instance if we have one allocated. + _initMaterial(); +} + +void PxCloth::_initMaterial() +{ + SAFE_DELETE( mMatInst ); + + if ( mMaterial ) + mMatInst = mMaterial->createMatInstance(); + else + mMatInst = MATMGR->createMatInstance( "WarningMaterial" ); + + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + mMatInst->addStateBlockDesc( desc ); + + mMatInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); +} + +void PxCloth::_updateVBIB() +{ + PROFILE_SCOPE( PxCloth_UpdateVBIB ); + + if ( mIsDummy ) + { + PxCloth *serverObj = static_cast( mServerObject.getObject() ); + if ( serverObj ) + _setClothFromServer( serverObj ); + } + + mIsVBDirty = false; + + if ( !mNumIndices ) + { + _alternateUpdateVBIB(); + return; + } + + // Don't set the VB if the vertex count is the same! + if ( mVB.isNull() || mVB->mNumVerts < mNumVertices ) + mVB.set( GFX, mNumVertices, GFXBufferTypeDynamic ); + + GFXVertexPNTT *vert = mVertexRenderBuffer; + GFXVertexPNTT *secondVert = NULL; + + for ( U32 i = 0; i < mNumVertices; i++ ) + { + if ( i % (U32)mPatchSize.x == 0 && i != 0 ) + { + secondVert = vert; + secondVert--; + vert->tangent = -(vert->point - secondVert->point); + } + else + { + secondVert = vert; + secondVert++; + vert->tangent = vert->point - secondVert->point; + } + + vert->tangent.normalize(); + vert++; + } + + GFXVertexPNTT *vpPtr = mVB.lock(); + dMemcpy( vpPtr, mVertexRenderBuffer, sizeof( GFXVertexPNTT ) * mNumVertices ); + mVB.unlock(); + + if ( mPrimBuffer.isNull() || mPrimBuffer->mIndexCount < mNumIndices ) + mPrimBuffer.set( GFX, mNumIndices, 0, GFXBufferTypeDynamic ); + + U16 *pbPtr; + mPrimBuffer.lock( &pbPtr ); + dMemcpy( pbPtr, mIndexRenderBuffer, sizeof( U16 ) * mNumIndices ); + mPrimBuffer.unlock(); +} + +void PxCloth::_alternateUpdateVBIB() +{ + if ( mVB.isNull() || mVB->mNumVerts < 4 ) + mVB.set( GFX, 4, GFXBufferTypeDynamic ); + + GFXVertexPNTT *vpPtr = mVB.lock(); + + vpPtr[0].point.set( 0, 0, mPatchSize.y + 1.0f ); + vpPtr[1].point.set( mPatchSize.x + 1.0f, 0, mPatchSize.y + 1.0f ); + vpPtr[2].point.set( 0, 0, 0 ); + vpPtr[3].point.set( mPatchSize.x + 1.0f, 0, 0 ); + + vpPtr[0].normal.set( getTransform().getForwardVector() ); + vpPtr[1].normal.set( getTransform().getForwardVector() ); + vpPtr[2].normal.set( getTransform().getForwardVector() ); + vpPtr[3].normal.set( getTransform().getForwardVector() ); + + vpPtr[0].texCoord.set( 0, -1.0f ); + vpPtr[1].texCoord.set( 1.0f, -1.0f ); + vpPtr[2].texCoord.set( 0, 0 ); + vpPtr[3].texCoord.set( 1.0f, 0 ); + + vpPtr[0].tangent.set( getTransform().getRightVector() ); + vpPtr[1].tangent.set( getTransform().getRightVector() ); + vpPtr[2].tangent.set( getTransform().getRightVector() ); + vpPtr[3].tangent.set( getTransform().getRightVector() ); + + mVB.unlock(); + + if ( mPrimBuffer.isNull() || mPrimBuffer->mIndexCount < 6 ) + mPrimBuffer.set( GFX, 6, 0, GFXBufferTypeDynamic ); + + U16 *pbPtr; + mPrimBuffer.lock( &pbPtr ); + + pbPtr[0] = 0; + pbPtr[1] = 1; + pbPtr[2] = 2; + pbPtr[3] = 2; + pbPtr[4] = 1; + pbPtr[5] = 3; + + mPrimBuffer.unlock(); +} + +void PxCloth::processTick( const Move *move ) +{ + PxCloth *serverObj = static_cast( mServerObject.getObject() ); + if ( serverObj ) + _setClothFromServer( serverObj ); + else if ( !serverObj && mIsDummy ) + mCloth = NULL; + + if ( mRecreatePending && !mIsDummy ) + { + mRecreatePending = false; + _recreateCloth( mResetXfm ); + } + + if ( !mCloth ) + return; + + if ( mWorld->isWritable() && Con::getBoolVariable( "$PxCloth::enableWind", false ) ) + { + NxVec3 windVec( 25.0f + NxMath::rand(-5.0f, 5.0f), + NxMath::rand(-5.0f, 5.0f), + NxMath::rand(-5.0f, 5.0f) ); + + mCloth->setWindAcceleration( windVec ); + + // Wake the cloth! + mCloth->wakeUp(); + } + else if ( mWorld->isWritable() && !Con::getBoolVariable( "$PxCloth::enableWind", false ) ) + mCloth->setWindAcceleration( NxVec3( 0, 0, 0 ) ); + + // Update bounds. + if ( mWorld->getEnabled() ) + { + NxBounds3 box; + mCloth->getWorldBounds( box ); + + Point3F min = pxCast( box.min ); + Point3F max = pxCast( box.max ); + + mWorldBox.set( min, max ); + mObjBox = mWorldBox; + + getWorldTransform().mul( mObjBox ); + } + else + { + Point3F extents( mPatchSize.x + 1.0f, mThickness * 2.0f, mPatchSize.y + 1.0f ); + mObjBox.set( Point3F::Zero, extents ); + } + + resetWorldBox(); + + // Mark VB as dirty + mIsVBDirty = true; +} + +void PxCloth::interpolateTick( F32 delta ) +{ +} + +bool PxCloth::onNewDataBlock( GameBaseData *dptr ) +{ + return false; +} + +void PxCloth::_setupAttachments() +{ + if ( !mCloth || !mWorld ) + return; + + // Set up attachments + // Bottom right = bit 0 + // Bottom left = bit 1 + // Top right = bit 2 + // Top left = bit 3 + U32 numX = (U32)(mPatchSamples.x) + 1; + U32 numY = (U32)(mPatchSamples.y) + 1; + + if ( mAttachmentMask & BIT( 0 ) ) + mCloth->attachVertexToGlobalPosition( 0, mCloth->getPosition( 0 ) ); + if ( mAttachmentMask & BIT( 1 ) ) + mCloth->attachVertexToGlobalPosition( numX, mCloth->getPosition( numX ) ); + if ( mAttachmentMask & BIT( 2 ) ) + mCloth->attachVertexToGlobalPosition( (numX+1) * (numY+1) - (numX+1), mCloth->getPosition( (numX+1) * (numY+1) - (numX+1) ) ); + if ( mAttachmentMask & BIT( 3 ) ) + mCloth->attachVertexToGlobalPosition( (numX+1) * (numY+1) - 1, mCloth->getPosition( (numX+1) * (numY+1) - 1 ) ); + if ( mAttachmentMask & BIT( 4 ) ) + mCloth->attachVertexToGlobalPosition( (numX+1) * (numY+1) - ((numX+1)/2), mCloth->getPosition( (numX+1) * (numY+1) - ((numX+1)/2) ) ); + if ( mAttachmentMask & BIT( 5 ) ) + mCloth->attachVertexToGlobalPosition( ((numX+1)/2), mCloth->getPosition( ((numX+1)/2) ) ); + if ( mAttachmentMask & BIT( 6 ) ) + mCloth->attachVertexToGlobalPosition( (numX+1) * ((numY+1)/2), mCloth->getPosition( (numX+1) * ((numY+1)/2) ) ); + if ( mAttachmentMask & BIT( 7 ) ) + mCloth->attachVertexToGlobalPosition( (numX+1) * ((numY+1)/2) + (numX), mCloth->getPosition( (numX+1) * ((numY+1)/2) + (numX) ) ); + + if ( mAttachmentMask & BIT( 8 ) ) + for ( U32 i = (numX+1) * (numY+1) - (numX+1); i < (numX+1) * (numY+1); i++ ) + mCloth->attachVertexToGlobalPosition( i, mCloth->getPosition( i ) ); + + if ( mAttachmentMask & BIT( 9 ) ) + for ( U32 i = 0; i < (numX+1); i++ ) + mCloth->attachVertexToGlobalPosition( i, mCloth->getPosition( i ) ); + + if ( mAttachmentMask & BIT( 10 ) ) + for ( U32 i = 0; i < (numX+1) * (numY+1); i+=(numX+1) ) + mCloth->attachVertexToGlobalPosition( i, mCloth->getPosition( i ) ); + + if ( mAttachmentMask & BIT( 11 ) ) + for ( U32 i = numX; i < (numX+1) * (numY+1); i+=(numX+1) ) + mCloth->attachVertexToGlobalPosition( i, mCloth->getPosition( i ) ); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxCloth.h b/T3D/physics/physx/pxCloth.h new file mode 100644 index 0000000..97b7e49 --- /dev/null +++ b/T3D/physics/physx/pxCloth.h @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXCLOTH_H_ +#define _PXCLOTH_H_ + + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + +#ifndef _PHYSX_H_ +#include "T3D/physics/physx/px.h" +#endif +#ifndef _T3D_PHYSICS_PHYSICSPLUGIN_H_ +#include "T3D/physics/physicsPlugin.h" +#endif + +class Material; + +class PxWorld; + +class NxScene; +class NxClothMesh; +class NxCloth; + +class PxCloth : public GameBase +{ + typedef GameBase Parent; + + enum MaskBits + { + MoveMask = Parent::NextFreeMask << 0, + WarpMask = Parent::NextFreeMask << 1, + LightMask = Parent::NextFreeMask << 2, + SleepMask = Parent::NextFreeMask << 3, + ForceSleepMask = Parent::NextFreeMask << 4, + ImpulseMask = Parent::NextFreeMask << 5, + UpdateMask = Parent::NextFreeMask << 6, + MountedMask = Parent::NextFreeMask << 7, + NextFreeMask = Parent::NextFreeMask << 8 + }; + +public: + + PxCloth(); + virtual ~PxCloth(); + + DECLARE_CONOBJECT( PxCloth ); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + static void initPersistFields(); + virtual void inspectPostApply(); + void onPhysicsReset( PhysicsResetEvent reset ); + + // NetObject + virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + virtual void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + virtual void setTransform( const MatrixF &mat ); + virtual void setScale( const VectorF &scale ); + bool prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + + // GameBase + virtual bool onNewDataBlock( GameBaseData *dptr ); + virtual void processTick( const Move *move ); + virtual void interpolateTick( F32 delta ); + +protected: + + PxWorld *mWorld; + NxScene *mScene; + + NxClothMesh *mClothMesh; + NxCloth *mCloth; + + NxMeshData mReceiveBuffers; + NxClothDesc mClothDesc; + NxClothMeshDesc mClothMeshDesc; + + bool mBendingEnabled; + bool mDampingEnabled; + bool mTriangleCollisionEnabled; + bool mSelfCollisionEnabled; + + F32 mDensity; + F32 mThickness; + F32 mFriction; + F32 mBendingStiffness; + F32 mStretchingStiffness; + F32 mDampingCoefficient; + F32 mCollisionResponseCoefficient; + F32 mAttachmentResponseCoefficient; + + U32 mAttachmentMask; + + static EnumTable mAttachmentFlagTable; + + String mMaterialName; + SimObjectPtr mMaterial; + BaseMatInstance *mMatInst; + + String lookupName; + + /// The output verts from the PhysX simulation. + GFXVertexPNTT *mVertexRenderBuffer; + + /// The output indices from the PhysX simulation. + U16 *mIndexRenderBuffer; + + U32 mMaxVertices; + U32 mMaxIndices; + U32 mNumParentIndices; + + /// The number of indices in the cloth which + /// is updated by the PhysX simulation. + U32 mNumIndices; + + /// The number of verts in the cloth which + /// is updated by the PhysX simulation. + U32 mNumVertices; + + U32 mMeshDirtyFlags; + bool mTeared; + bool mIsDummy; + bool mIsVBDirty; + bool mRecreatePending; + + GFXPrimitiveBufferHandle mPrimBuffer; + GFXVertexBufferHandle mVB; + + Point2F mPatchSamples; + Point2F mPatchSize; + + MatrixF mResetXfm; + + void _updateMaterial(); + void _initMaterial(); + + void _recreateCloth( const MatrixF &transform ); + void _setClothFromServer( PxCloth *serverObj ); + + void _initClothMesh(); + void _createClothPatch( const MatrixF &transform ); + void _initReceiveBuffers( U32 numVertices, U32 numTriangles ); + + void _setupAttachments(); + + void _updateVBIB(); + void _alternateUpdateVBIB(); +}; + +#endif // _PXCLOTH_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxContactReporter.cpp b/T3D/physics/physx/pxContactReporter.cpp new file mode 100644 index 0000000..e0c4245 --- /dev/null +++ b/T3D/physics/physx/pxContactReporter.cpp @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxContactReporter.h" + +#include "T3D/physics/physX/pxUserData.h" +#include "T3D/physics/physX/pxCasts.h" +#include "platform/profiler.h" + + +PxContactReporter::PxContactReporter() +{ +} + +PxContactReporter::~PxContactReporter() +{ +} + +void PxContactReporter::onContactNotify( NxContactPair &pair, NxU32 events ) +{ + PROFILE_SCOPE( PxContactReporter_OnContactNotify ); + + // For now we only care about start touch events. + if ( !( events & NX_NOTIFY_ON_START_TOUCH ) ) + return; + + // Skip if either actor is deleted. + if ( pair.isDeletedActor[0] || pair.isDeletedActor[1] ) + return; + + NxActor *actor0 = pair.actors[0]; + NxActor *actor1 = pair.actors[1]; + + PxUserData *userData0 = PxUserData::getData( *actor0 ); + PxUserData *userData1 = PxUserData::getData( *actor1 ); + + // Early out if we don't have user data or signals to notify. + if ( ( !userData0 || userData0->getContactSignal().isEmpty() ) && + ( !userData1 || userData1->getContactSignal().isEmpty() ) ) + return; + + // Get an average contact point. + U32 points = 0; + NxVec3 hitPoint( 0.0f ); + NxContactStreamIterator iter( pair.stream ); + while( iter.goNextPair() ) + { + while( iter.goNextPatch() ) + { + while( iter.goNextPoint() ) + { + hitPoint += iter.getPoint(); + ++points; + } + } + } + hitPoint /= (F32)points; + + if ( userData0 ) + userData0->getContactSignal().trigger( actor0, + actor1, + userData1 ? userData1->getObject() : NULL, + pxCast( hitPoint ), + pxCast( pair.sumNormalForce ) ); + + if ( userData1 ) + userData1->getContactSignal().trigger( actor1, + actor0, + userData0 ? userData0->getObject() : NULL, + pxCast( hitPoint ), + pxCast( -pair.sumNormalForce ) ); +} + + +bool PxUserNotify::onJointBreak( NxReal breakingForce, NxJoint &brokenJoint ) +{ + PROFILE_SCOPE( PxUserNotify_OnJointBreak ); + + PxJointUserData *userData = PxJointUserData::getData( brokenJoint ); + + if ( userData ) + userData->getOnJointBreakSignal().trigger( breakingForce, brokenJoint ); + + // NOTE: Returning true here will tell the + // PhysX SDK to delete the joint, which will + // cause MANY problems if any of the user app's + // objects still hold references to it. + return false; +} \ No newline at end of file diff --git a/T3D/physics/physx/pxContactReporter.h b/T3D/physics/physx/pxContactReporter.h new file mode 100644 index 0000000..984c7ef --- /dev/null +++ b/T3D/physics/physx/pxContactReporter.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXCONTACTREPORTER_H_ +#define _PXCONTACTREPORTER_H_ + +#ifndef _PHYSX_H_ +#include "T3D/physics/physX/px.h" +#endif + + +class PxContactReporter : public NxUserContactReport +{ +protected: + + virtual void onContactNotify( NxContactPair& pair, NxU32 events ); + +public: + + PxContactReporter(); + virtual ~PxContactReporter(); +}; + + + +class PxUserNotify : public NxUserNotify +{ +public: + virtual bool onJointBreak( NxReal breakingForce, NxJoint &brokenJoint ); + virtual void onWake( NxActor **actors, NxU32 count ) {} + virtual void onSleep ( NxActor **actors, NxU32 count ) {} +}; + + +#endif // _PXCONTACTREPORTER_H_ diff --git a/T3D/physics/physx/pxFluid.cpp b/T3D/physics/physx/pxFluid.cpp new file mode 100644 index 0000000..e98ffab --- /dev/null +++ b/T3D/physics/physx/pxFluid.cpp @@ -0,0 +1,291 @@ + +#include "platform/platform.h" +#include "pxFluid.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physx/pxWorld.h" +#include "gfx/gfxDrawUtil.h" +#include "math/mathIO.h" +#include "core/stream/bitStream.h" + +PxFluid::PxFluid() + : mWorld( NULL ), + mScene( NULL ), + mParticles( NULL ), + mFluid( NULL ), + mEmitter( NULL ), + mParticleCount( 0 ) +{ + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType | ShadowCasterObjectType; +} + +PxFluid::~PxFluid() +{ + +} + +IMPLEMENT_CO_NETOBJECT_V1( PxFluid ); + +bool PxFluid::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + mWorld = dynamic_cast( gPhysicsPlugin->getWorld( isServerObject() ? "server" : "client" ) ); + + if ( !mWorld || !mWorld->getScene() ) + { + Con::errorf( "PxMultiActor::onAdd() - PhysXWorld not initialized!" ); + return false; + } + + mScene = mWorld->getScene(); + + if ( isClientObject() ) + _createFluid(); + + Point3F halfScale = Point3F::One * 0.5f; + mObjBox.minExtents = -halfScale; + mObjBox.maxExtents = halfScale; + resetWorldBox(); + + addToScene(); + + return true; +} + +void PxFluid::onRemove() +{ + if ( isClientObject() ) + _destroyFluid(); + + removeFromScene(); + + Parent::onRemove(); +} + +void PxFluid::initPersistFields() +{ + Parent::initPersistFields(); +} + +void PxFluid::inspectPostApply() +{ + Parent::inspectPostApply(); + + setMaskBits( UpdateMask ); +} + +U32 PxFluid::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + mathWrite( *stream, getTransform() ); + mathWrite( *stream, getScale() ); + + stream->write( mEmitter ? mEmitter->getRate() : 0 ); + } + + stream->writeFlag( isProperlyAdded() && mask & ResetMask ); + + return retMask; +} + +void PxFluid::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + // UpdateMask + if ( stream->readFlag() ) + { + MatrixF mat; + mathRead( *stream, &mat ); + Point3F scale; + mathRead( *stream, &scale ); + + setScale( scale ); + setTransform( mat ); + + F32 rate; + stream->read( &rate ); + setRate( rate ); + } + + // ResetMask + if ( stream->readFlag() ) + resetParticles(); +} + +void PxFluid::setTransform( const MatrixF &mat ) +{ + Parent::setTransform( mat ); + + if ( mEmitter ) + { + NxMat34 nxMat; + nxMat.setRowMajor44( mat ); + mEmitter->setGlobalPose( nxMat ); + } +} + +void PxFluid::setScale( const VectorF &scale ) +{ + Point3F lastScale = getScale(); + + Point3F halfScale = Point3F::One * 0.5f; + mObjBox.minExtents = -halfScale; + mObjBox.maxExtents = halfScale; + resetWorldBox(); + + Parent::setScale( scale ); + + if ( lastScale != getScale() && + mEmitter ) + { + _destroyFluid(); + _createFluid(); + } +} + +bool PxFluid::prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + if ( !state->isDiffusePass() || + isLastState(state, stateKey) || + !state->isObjectRendered(this) ) + return false; + + setLastState( state, stateKey ); + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &PxFluid::renderObject ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + + return true; +} + +void PxFluid::resetParticles() +{ + if ( mEmitter ) + mEmitter->resetEmission( MAX_PARTICLES ); + setMaskBits( ResetMask ); +} + +void PxFluid::setRate( F32 rate ) +{ + if ( mEmitter ) + mEmitter->setRate( rate ); + setMaskBits( UpdateMask ); +} + +void PxFluid::renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + for ( U32 i = 0; i < mParticleCount; i++ ) + { + FluidParticle &particle = mParticles[i]; + Point3F pnt = pxCast( particle.position ); + + Box3F box( 0.2f ); + box.minExtents += pnt; + box.maxExtents += pnt; + + GFX->getDrawUtil()->drawCube( desc, box, ColorI::BLUE ); + } +} + +void PxFluid::_createFluid() +{ + /* + // Set structure to pass particles, and receive them after every simulation step + NxParticleData particleData; + particleData.numParticlesPtr = &mParticleCount; + particleData.bufferPos = &mParticles[0].position.x; + particleData.bufferPosByteStride = sizeof(FluidParticle); + particleData.bufferVel = &mParticles[0].velocity.x; + particleData.bufferVelByteStride = sizeof(FluidParticle); + particleData.bufferLife = &mParticles[0].lifetime; + particleData.bufferLifeByteStride = sizeof(FluidParticle); + + // Create a fluid descriptor + NxFluidDesc fluidDesc; + fluidDesc.kernelRadiusMultiplier = 2.3f; + fluidDesc.restParticlesPerMeter = 10.0f; + fluidDesc.stiffness = 200.0f; + fluidDesc.viscosity = 22.0f; + fluidDesc.restDensity = 1000.0f; + fluidDesc.damping = 0.0f; + fluidDesc.simulationMethod = NX_F_SPH; + fluidDesc.initialParticleData = particleData; + fluidDesc.particlesWriteData = particleData; + */ + + NxFluidDesc fluidDesc; + fluidDesc.setToDefault(); + fluidDesc.simulationMethod = NX_F_SPH; + fluidDesc.maxParticles = MAX_PARTICLES; + fluidDesc.restParticlesPerMeter = 50; + fluidDesc.stiffness = 1; + fluidDesc.viscosity = 6; + fluidDesc.flags = NX_FF_VISUALIZATION|NX_FF_ENABLED; + + mParticles = new FluidParticle[MAX_PARTICLES]; + dMemset( mParticles, 0, sizeof(FluidParticle) * MAX_PARTICLES ); + + NxParticleData &particleData = fluidDesc.particlesWriteData; + + particleData.numParticlesPtr = &mParticleCount; + particleData.bufferPos = &mParticles[0].position.x; + particleData.bufferPosByteStride = sizeof(FluidParticle); + particleData.bufferVel = &mParticles[0].velocity.x; + particleData.bufferVelByteStride = sizeof(FluidParticle); + particleData.bufferLife = &mParticles[0].lifetime; + particleData.bufferLifeByteStride = sizeof(FluidParticle); + + mFluid = mScene->createFluid( fluidDesc ); + + + //Create Emitter. + NxFluidEmitterDesc emitterDesc; + emitterDesc.setToDefault(); + emitterDesc.dimensionX = getScale().x; + emitterDesc.dimensionY = getScale().y; + emitterDesc.relPose.setColumnMajor44( getTransform() ); + emitterDesc.rate = 5.0f; + emitterDesc.randomAngle = 0.1f; + emitterDesc.fluidVelocityMagnitude = 6.5f; + emitterDesc.maxParticles = 0; + emitterDesc.particleLifetime = 4.0f; + emitterDesc.type = NX_FE_CONSTANT_FLOW_RATE; + emitterDesc.shape = NX_FE_ELLIPSE; + mEmitter = mFluid->createEmitter(emitterDesc); +} + +void PxFluid::_destroyFluid() +{ + delete[] mParticles; + mScene->releaseFluid( *mFluid ); + mEmitter = NULL; +} + +ConsoleMethod( PxFluid, resetParticles, void, 2, 2, "" ) +{ + object->resetParticles(); +} + +ConsoleMethod( PxFluid, setRate, void, 2, 2, "" ) +{ + object->setRate( dAtof(argv[2]) ); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxFluid.h b/T3D/physics/physx/pxFluid.h new file mode 100644 index 0000000..4562961 --- /dev/null +++ b/T3D/physics/physx/pxFluid.h @@ -0,0 +1,88 @@ + +#ifndef _PXFLUID_H_ +#define _PXFLUID_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +#ifndef _PHYSX_H_ +#include "T3D/physics/physx/px.h" +#endif + +class PxWorld; +class NxScene; + +class PxFluid : public SceneObject +{ + typedef SceneObject Parent; + +protected: + + enum NetMasks + { + UpdateMask = Parent::NextFreeMask, + ResetMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + struct FluidParticle + { + NxVec3 position; + NxVec3 velocity; + NxReal density; + NxReal lifetime; + NxU32 id; + NxVec3 collisionNormal; + }; + + #define MAX_PARTICLES 100 + +public: + + PxFluid(); + virtual ~PxFluid(); + + DECLARE_CONOBJECT( PxFluid ); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + static void initPersistFields(); + virtual void inspectPostApply(); + + // NetObject + virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + virtual void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + virtual void setTransform( const MatrixF &mat ); + virtual void setScale( const VectorF &scale ); + bool prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + + void resetParticles(); + void setRate( F32 rate ); + +protected: + + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + void _createFluid(); + void _destroyFluid(); + +protected: + + PxWorld *mWorld; + NxScene *mScene; + + FluidParticle *mParticles; + //NxParticleData *mParticleData; + NxFluid *mFluid; + U32 mParticleCount; + NxFluidEmitter *mEmitter; +}; + +#endif // _PXFLUID_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxMaterial.cpp b/T3D/physics/physx/pxMaterial.cpp new file mode 100644 index 0000000..cac4d06 --- /dev/null +++ b/T3D/physics/physx/pxMaterial.cpp @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxMaterial.h" + +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physicsPlugin.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + + +IMPLEMENT_CO_DATABLOCK_V1( PxMaterial ); + +IMPLEMENT_CONSOLETYPE( PxMaterial ) +IMPLEMENT_GETDATATYPE( PxMaterial ) +IMPLEMENT_SETDATATYPE( PxMaterial ) + +PxMaterial::PxMaterial() +: mNxMat( NULL ), + mNxMatId( -1 ), + restitution( 0.0f ), + staticFriction( 0.1f ), + dynamicFriction( 0.95f ), + mServer( false ) +{ +} + +PxMaterial::~PxMaterial() +{ +} + +void PxMaterial::consoleInit() +{ + Parent::consoleInit(); +} + +void PxMaterial::initPersistFields() +{ + Parent::initPersistFields(); + + addGroup("PxMaterial"); + + addField( "restitution", TypeF32, Offset( restitution, PxMaterial ) ); + addField( "staticFriction", TypeF32, Offset( staticFriction, PxMaterial ) ); + addField( "dynamicFriction", TypeF32, Offset( dynamicFriction, PxMaterial ) ); + + endGroup("PxMaterial"); +} + +void PxMaterial::onStaticModified( const char *slotName, const char *newValue ) +{ + if ( isProperlyAdded() && mNxMat != NULL ) + { + mNxMat->setRestitution( restitution ); + mNxMat->setStaticFriction( staticFriction ); + mNxMat->setDynamicFriction( dynamicFriction ); + } +} + +bool PxMaterial::preload( bool server, String &errorBuffer ) +{ + mServer = server; + + PxWorld *world = dynamic_cast( gPhysicsPlugin->getWorld( server ? "server" : "client" ) ); + + if ( !world ) + { + // TODO: Error... in error buffer? + return false; + } + + NxMaterialDesc material; + material.restitution = restitution; + material.staticFriction = staticFriction; + material.dynamicFriction = dynamicFriction; + + mNxMat = world->createMaterial( material ); + mNxMatId = mNxMat->getMaterialIndex(); + + if ( mNxMatId == -1 ) + { + errorBuffer = "PxMaterial::preload() - unable to create material!"; + return false; + } + + return Parent::preload( server, errorBuffer ); +} + +void PxMaterial::packData( BitStream* stream ) +{ + Parent::packData( stream ); + + stream->write( restitution ); + stream->write( staticFriction ); + stream->write( dynamicFriction ); +} + +void PxMaterial::unpackData( BitStream* stream ) +{ + Parent::unpackData( stream ); + + stream->read( &restitution ); + stream->read( &staticFriction ); + stream->read( &dynamicFriction ); +} diff --git a/T3D/physics/physx/pxMaterial.h b/T3D/physics/physx/pxMaterial.h new file mode 100644 index 0000000..837b358 --- /dev/null +++ b/T3D/physics/physx/pxMaterial.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PHYSX_MATERIAL_H +#define _PHYSX_MATERIAL_H + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _DYNAMIC_CONSOLETYPES_H_ +#include "console/dynamicTypes.h" +#endif + +class NxMaterial; + +class PxMaterial : public SimDataBlock +{ + typedef SimDataBlock Parent; + +protected: + + F32 restitution; + F32 staticFriction; + F32 dynamicFriction; + + NxMaterial *mNxMat; + S32 mNxMatId; + + bool mServer; + +public: + + DECLARE_CONOBJECT( PxMaterial ); + + PxMaterial(); + ~PxMaterial(); + + static void consoleInit(); + static void initPersistFields(); + virtual void onStaticModified( const char *slotName, const char *newValue ); + + bool preload( bool server, String &errorBuffer ); + virtual void packData( BitStream* stream ); + virtual void unpackData( BitStream* stream ); + + S32 getMaterialId() const { return mNxMatId; } + +}; + +DECLARE_CONSOLETYPE( PxMaterial ) + +#endif // _PHYSX_MATERIAL_H \ No newline at end of file diff --git a/T3D/physics/physx/pxMeshRoad.cpp b/T3D/physics/physx/pxMeshRoad.cpp new file mode 100644 index 0000000..eae9bed --- /dev/null +++ b/T3D/physics/physx/pxMeshRoad.cpp @@ -0,0 +1,177 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxMeshRoad.h" + +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxMaterial.h" +#include "T3D/physics/physX/pxStream.h" +#include "T3D/physics/physX/pxUserData.h" +#include "collision/concretePolyList.h" +#include "core/frameAllocator.h" +#include "environment/meshRoad.h" + + + +PxMeshRoad::PxMeshRoad() + : mWorld( NULL ), + mActor( NULL ), + mRoad( NULL ) +{ +} + +PxMeshRoad::~PxMeshRoad() +{ + _releaseActor(); +} + +void PxMeshRoad::_releaseActor() +{ + if ( !mWorld ) + return; + + if ( mActor ) + mWorld->releaseActor( *mActor ); + + mWorld = NULL; + mActor = NULL; + + // TODO: We need to check the getReferenceCount() on the + // mesh objects and release them... if not unscaled collision + // shapes never get freed. + + if ( !mTriangleMeshes.empty() ) + { + // releaseConvexMesh() requires that both the server + // and client scenes to be writable. + PxWorld::releaseWriteLocks(); + + for ( U32 i=0; i < mTriangleMeshes.size(); i++ ) + gPhysicsSDK->releaseTriangleMesh( *mTriangleMeshes[i] ); + + mTriangleMeshes.clear(); + } +} + +PxMeshRoad* PxMeshRoad::create( MeshRoad *road, PxWorld *world ) +{ + AssertFatal( world, "PxMeshRoad::create() - No world found!" ); + + PxMeshRoad *pxMeshRoad = new PxMeshRoad(); + + bool result = pxMeshRoad->_initTriangle( world, road ); + + if ( !result ) + { + delete pxMeshRoad; + return NULL; + } + + return pxMeshRoad; +} + +bool PxMeshRoad::_initTriangle( PxWorld *world, MeshRoad *road ) +{ + mWorld = world; + mRoad = road; + + // Mesh cooking requires that both + // scenes not be write locked! + PxWorld::releaseWriteLocks(); + + // Loop through MeshRoadSegment(s) building TriangleMeshShapeDesc(s). + // If the road is long enough, we split it into multiples. + + Vector triangleShapeDescs; + F32 length = 0.0f; + //const F32 tolLen = 25.0f; + S32 start = -1; + + NxInitCooking(); + + for ( U32 i = 0; i < road->getSegmentCount(); i++ ) + { + const MeshRoadSegment &seg = road->getSegment( i ); + + if ( start == -1 ) + start = i; + + length += seg.length(); + + //if ( true || length > tolLen || i == road->getSegmentCount() - 1 ) + { + ConcretePolyList polyList; + bool capFront = ( start == 0 ); + bool capEnd = ( i == road->getSegmentCount() - 1 ); + + road->buildSegmentPolyList( &polyList, start, i, capFront, capEnd ); + + // Build the triangle mesh. + NxTriangleMeshDesc meshDesc; + meshDesc.numVertices = polyList.mVertexList.size(); + meshDesc.numTriangles = polyList.mIndexList.size() / 3; + meshDesc.pointStrideBytes = sizeof(NxVec3); + meshDesc.triangleStrideBytes = 3*sizeof(NxU32); + meshDesc.points = polyList.mVertexList.address(); + meshDesc.triangles = polyList.mIndexList.address(); + meshDesc.flags = NX_MF_FLIPNORMALS; + + // Ok... cook the mesh! + NxCookingParams params; + params.targetPlatform = PLATFORM_PC; + params.skinWidth = 0.01f; + params.hintCollisionSpeed = false; + NxSetCookingParams( params ); + PxMemStream cooked; + if ( NxCookTriangleMesh( meshDesc, cooked ) ) + { + cooked.resetPosition(); + NxTriangleMesh *pxMesh = gPhysicsSDK->createTriangleMesh( cooked ); + mTriangleMeshes.push_back( pxMesh ); + triangleShapeDescs.increment(); + triangleShapeDescs.last().meshData = pxMesh; + } + + start = -1; + length = 0.0f; + } + } + + NxCloseCooking(); + + // Create the actor. + NxActorDesc actorDesc; + actorDesc.body = NULL; + actorDesc.name = road->getName(); + //actorDesc.globalPose.setRowMajor44( road->getTransform() ); + //actorDesc.shapes.push_back( meshDesc ); + for ( U32 i = 0; i < triangleShapeDescs.size(); i++ ) + actorDesc.shapes.push_back( &triangleShapeDescs[i] ); + + mUserData.setObject( road ); + actorDesc.userData = &mUserData; + + mActor = mWorld->getScene()->createActor( actorDesc ); + + return true; +} + +void PxMeshRoad::setTransform( const MatrixF &xfm ) +{ + if ( !mActor ) + return; + + mWorld->releaseWriteLock(); + + NxMat34 pose; + pose.setRowMajor44( xfm ); + mActor->setGlobalPose( pose ); +} + +void PxMeshRoad::setScale( const Point3F &scale ) +{ +} diff --git a/T3D/physics/physx/pxMeshRoad.h b/T3D/physics/physx/pxMeshRoad.h new file mode 100644 index 0000000..bee8f4b --- /dev/null +++ b/T3D/physics/physx/pxMeshRoad.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PXMESHROAD_H_ +#define _T3D_PHYSICS_PXMESHROAD_H_ + +#ifndef _T3D_PHYSICS_PHYSICSSTATIC_H_ +#include "T3D/physics/physicsStatic.h" +#endif +#ifndef _PXUSERDATA_H_ +#include "T3D/physics/physx/pxUserData.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +class NxActor; +class NxTriangleMesh; +class PxWorld; +class MeshRoad; + + +class PxMeshRoad : public PhysicsStatic +{ +protected: + + MeshRoad *mRoad; + + PxWorld *mWorld; + + NxActor *mActor; + + /// The userdata object assigned to the actor. + PxUserData mUserData; + + Vector mTriangleMeshes; + //NxTriangleMesh *mTriangleMesh; + + //static void _loadTriangleMeshes( MeshRoad *meshRoad, Vector *triangleMeshes ); + + void _releaseActor(); + + PxMeshRoad(); + + bool _initTriangle( PxWorld *world, MeshRoad *meshRoad ); + +public: + + virtual ~PxMeshRoad(); + + // PhysicsStatic + void setTransform( const MatrixF &xfm ); + void setScale( const Point3F &scale ); + + + static PxMeshRoad* create( MeshRoad *road, PxWorld *world ); +}; + +#endif // _T3D_PHYSICS_PXMESHROAD_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxMultiActor.cpp b/T3D/physics/physx/pxMultiActor.cpp new file mode 100644 index 0000000..b089bdc --- /dev/null +++ b/T3D/physics/physx/pxMultiActor.cpp @@ -0,0 +1,2629 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxMultiActor.h" + +#include "console/consoleTypes.h" +#include "core/stream/fileStream.h" +#include "core/stream/bitStream.h" +#include "core/resourceManager.h" +#include "core/strings/stringUnit.h" +#include "sim/netConnection.h" +#include "math/mathIO.h" +#include "math/mathUtils.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/primBuilder.h" +#include "collision/collision.h" +#include "collision/abstractPolyList.h" +#include "ts/tsShapeInstance.h" +#include "ts/tsPartInstance.h" +#include "lighting/lightManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "T3D/objectTypes.h" +#include "T3D/containerQuery.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/debris.h" +#include "renderInstance/renderPassManager.h" +#include "gui/worldEditor/editor.h" // For gEditingMission +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxMaterial.h" +#include "T3D/physics/physX/pxCasts.h" +#include "T3D/physics/physx/pxUserData.h" +#include "T3D/physics/physx/pxUtils.h" +#include "sfx/sfxSystem.h" + +#include +#include +#include + + +class PxMultiActor_Notify : public NXU_userNotify +{ +protected: + + Vector mActors; + + Vector mShapes; + + Vector mJoints; + + const NxMat34 mTransform; + + const Point3F mScale; + + F32 mMassScale; + + NxCompartment *mCompartment; + + PxMaterial *mMaterial; + + Vector *mActorUserProperties; + + Vector *mJointUserProperties; + +public: + + void NXU_notifyJoint( NxJoint *joint, const char *userProperties ) + { + if ( mJointUserProperties ) + mJointUserProperties->push_back( userProperties ); + mJoints.push_back( joint ); + } + + bool NXU_preNotifyJoint( NxJointDesc &joint, const char *userProperties ) + { + joint.localAnchor[0].x *= mScale.x; + joint.localAnchor[0].y *= mScale.y; + joint.localAnchor[0].z *= mScale.z; + + joint.localAnchor[1].x *= mScale.x; + joint.localAnchor[1].y *= mScale.y; + joint.localAnchor[1].z *= mScale.z; + + // The PhysX exporter from 3dsMax doesn't allow creation + // of fixed joints. It also doesn't seem to export the + // joint names! So look for joints which all all the + // motion axes are locked... make those fixed joints. + if ( joint.getType() == NX_JOINT_D6 ) + { + NxD6JointDesc *d6Joint = static_cast( &joint ); + + if ( d6Joint->xMotion == NX_D6JOINT_MOTION_LOCKED && + d6Joint->yMotion == NX_D6JOINT_MOTION_LOCKED && + d6Joint->zMotion == NX_D6JOINT_MOTION_LOCKED && + d6Joint->swing1Motion == NX_D6JOINT_MOTION_LOCKED && + d6Joint->swing2Motion == NX_D6JOINT_MOTION_LOCKED && + d6Joint->twistMotion == NX_D6JOINT_MOTION_LOCKED ) + { + // Ok... build a new fixed joint. + NxFixedJointDesc fixed; + fixed.actor[0] = joint.actor[0]; + fixed.actor[1] = joint.actor[1]; + fixed.localNormal[0] = joint.localNormal[0]; + fixed.localNormal[1] = joint.localNormal[1]; + fixed.localAxis[0] = joint.localAxis[0]; + fixed.localAxis[1] = joint.localAxis[1]; + fixed.localAnchor[0] = joint.localAnchor[0]; + fixed.localAnchor[1] = joint.localAnchor[1]; + fixed.maxForce = joint.maxForce; + fixed.maxTorque = joint.maxTorque; + fixed.name = joint.name; + fixed.userData = joint.userData; + fixed.jointFlags = joint.jointFlags; + + // What scene are we adding this to? + NxActor *actor = fixed.actor[0] ? fixed.actor[0] : fixed.actor[1]; + NxScene &scene = actor->getScene(); + + NxJoint* theJoint = scene.createJoint( fixed ); + mJoints.push_back( theJoint ); + if ( mJointUserProperties ) + mJointUserProperties->push_back( userProperties ); + + // Don't generate this joint. + return false; + } + } + + return true; + } + + void NXU_notifyActor( NxActor *actor, const char *userProperties ) + { + mActors.push_back( actor ); + + // Save the shapes. + for ( U32 i=0; i < actor->getNbShapes(); i++ ) + mShapes.push_back( actor->getShapes()[i] ); + + mActorUserProperties->push_back( userProperties ); + }; + + bool NXU_preNotifyMaterial( NxMaterialDesc &t, const char *userProperties ) + { + // Don't generate materials if we have one defined! + return !mMaterial; + } + + bool NXU_preNotifyActor( NxActorDesc &actor, const char *userProperties ) + { + // Set the right compartment. + actor.compartment = mCompartment; + + if ( actor.shapes.size() == 0 ) + Con::warnf( "PxMultiActor_Notify::NXU_preNotifyActor, got an actor (%s) with no shapes, was this intentional?", actor.name ); + + // For every shape, cast to its particular type + // and apply the scale to size, mass and localPosition. + for( S32 i = 0; i < actor.shapes.size(); i++ ) + { + // If we have material then set it. + if ( mMaterial ) + actor.shapes[i]->materialIndex = mMaterial->getMaterialId(); + + switch( actor.shapes[i]->getType() ) + { + case NX_SHAPE_BOX: + { + NxBoxShapeDesc *boxDesc = (NxBoxShapeDesc*)actor.shapes[i]; + + boxDesc->mass *= mMassScale; + + boxDesc->dimensions.x *= mScale.x; + boxDesc->dimensions.y *= mScale.y; + boxDesc->dimensions.z *= mScale.z; + + boxDesc->localPose.t.x *= mScale.x; + boxDesc->localPose.t.y *= mScale.y; + boxDesc->localPose.t.z *= mScale.z; + break; + } + + case NX_SHAPE_SPHERE: + { + NxSphereShapeDesc *sphereDesc = (NxSphereShapeDesc*)actor.shapes[i]; + + sphereDesc->mass *= mMassScale; + + // TODO: Spheres do not work with non-uniform + // scales very well... how do we fix this? + sphereDesc->radius *= mScale.x; + + sphereDesc->localPose.t.x *= mScale.x; + sphereDesc->localPose.t.y *= mScale.y; + sphereDesc->localPose.t.z *= mScale.z; + break; + } + + case NX_SHAPE_CAPSULE: + { + NxCapsuleShapeDesc *capsuleDesc = (NxCapsuleShapeDesc*)actor.shapes[i]; + + capsuleDesc->mass *= mMassScale; + + // TODO: Capsules do not work with non-uniform + // scales very well... how do we fix this? + capsuleDesc->radius *= mScale.x; + capsuleDesc->height *= mScale.y; + + capsuleDesc->localPose.t.x *= mScale.x; + capsuleDesc->localPose.t.y *= mScale.y; + capsuleDesc->localPose.t.z *= mScale.z; + break; + } + + default: + { + static String lookup[] = + { + "PLANE", + "SPHERE", + "BOX", + "CAPSULE", + "WHEEL", + "CONVEX", + "MESH", + "HEIGHTFIELD" + }; + + Con::warnf( "PxMultiActor_Notify::NXU_preNotifyActor, unsupported shape type (%s), on Actor (%s)", lookup[actor.shapes[i]->getType()].c_str(), actor.name ); + + delete actor.shapes[i]; + actor.shapes.erase( actor.shapes.begin() + i ); + --i; + break; + } + } + } + + NxBodyDesc *body = const_cast( actor.body ); + if ( body ) + { + // Must scale all of these parameters, else there will be odd results! + body->mass *= mMassScale; + body->massLocalPose.t.multiply( mMassScale, body->massLocalPose.t ); + body->massSpaceInertia.multiply( mMassScale, body->massSpaceInertia ); + + // Ragdoll damping! + //body->sleepDamping = 1.7f; + //body->linearDamping = 0.4f; + //body->angularDamping = 0.08f; + //body->wakeUpCounter = 0.3f; + } + + return true; + }; + +public: + + PxMultiActor_Notify( NxCompartment *compartment, + PxMaterial *material, + const NxMat34& mat, + const Point3F& scale, + Vector *actorProps = NULL, + Vector *jointProps = NULL ) + : mCompartment( compartment ), + mMaterial( material ), + mScale( scale ), + mTransform( mat ), + mActorUserProperties( actorProps ), + mJointUserProperties( jointProps ) + { + const F32 unit = VectorF( 1.0f, 1.0f, 1.0f ).len(); + mMassScale = mScale.len() / unit; + } + + virtual ~PxMultiActor_Notify() + { + } + + const Vector& getActors() { return mActors; } + const Vector& getShapes() { return mShapes; } + const Vector& getJoints() { return mJoints; } +}; + +IMPLEMENT_CO_DATABLOCK_V1(PxMultiActorData); +IMPLEMENT_CONSOLETYPE(PxMultiActorData) +IMPLEMENT_GETDATATYPE(PxMultiActorData) +IMPLEMENT_SETDATATYPE(PxMultiActorData) + +PxMultiActorData::PxMultiActorData() + : material( NULL ), + collection( NULL ), + waterDragScale( 1.0f ), + buoyancyDensity( 1.0f ), + angularDrag( 0.0f ), + linearDrag( 0.0f ), + clientOnly( false ), + singlePlayerOnly( false ), + shapeName( StringTable->insert( "" ) ), + physXStream( StringTable->insert( "" ) ), + breakForce( 0.0f ) +{ + for ( S32 i = 0; i < MaxCorrectionNodes; i++ ) + correctionNodeNames[i] = StringTable->insert( "" ); + + for ( S32 i = 0; i < MaxCorrectionNodes; i++ ) + correctionNodes[i] = -1; +} + +PxMultiActorData::~PxMultiActorData() +{ + if ( collection ) + NXU::releaseCollection( collection ); +} + +void PxMultiActorData::initPersistFields() +{ + Parent::initPersistFields(); + + addGroup("Media"); + addField( "shapeName", TypeFilename, Offset( shapeName, PxMultiActorData ) ); + endGroup("Media"); + + // PhysX collision properties. + addGroup( "Physics" ); + + addField( "physXStream", TypeFilename, Offset( physXStream, PxMultiActorData ) ); + addField( "material", TypePxMaterialPtr, Offset( material, PxMultiActorData ) ); + + addField( "noCorrection", TypeBool, Offset( noCorrection, PxMultiActorData ) ); + + UTF8 buff[256]; + for ( S32 i=0; i < MaxCorrectionNodes; i++ ) + { + dSprintf( buff, sizeof(buff), "correctionNode%d", i ); + addField( buff, TypeString, Offset( correctionNodeNames[i], PxMultiActorData ) ); + } + + addField( "angularDrag", TypeF32, Offset( angularDrag, PxMultiActorData ) ); + addField( "linearDrag", TypeF32, Offset( linearDrag, PxMultiActorData ) ); + addField( "waterDragScale", TypeF32, Offset( waterDragScale, PxMultiActorData ) ); + addField( "buoyancyDensity", TypeF32, Offset( buoyancyDensity, PxMultiActorData ) ); + + endGroup( "Physics" ); + + addField( "clientOnly", TypeBool, Offset( clientOnly, PxMultiActorData ) ); + addField( "singlePlayerOnly", TypeBool, Offset( singlePlayerOnly, PxMultiActorData ) ); + addField( "breakForce", TypeF32, Offset( breakForce, PxMultiActorData ) ); +} + +void PxMultiActorData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString( shapeName ); + stream->writeString( physXStream ); + + if( stream->writeFlag( material ) ) + stream->writeRangedU32( packed ? SimObjectId( material ) : material->getId(), + DataBlockObjectIdFirst, DataBlockObjectIdLast ); + + if ( !stream->writeFlag( noCorrection ) ) + { + // Write the correction node indices for the client. + for ( S32 i = 0; i < MaxCorrectionNodes; i++ ) + stream->write( correctionNodes[i] ); + } + + stream->write( waterDragScale ); + stream->write( buoyancyDensity ); + stream->write( angularDrag ); + stream->write( linearDrag ); + + stream->writeFlag( clientOnly ); + stream->writeFlag( singlePlayerOnly ); + stream->write( breakForce ); +} + +void PxMultiActorData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + shapeName = stream->readSTString(); + physXStream = stream->readSTString(); + + if( stream->readFlag() ) + material = (PxMaterial*)stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + + noCorrection = stream->readFlag(); + if ( !noCorrection ) + { + for ( S32 i = 0; i < MaxCorrectionNodes; i++ ) + stream->read( &correctionNodes[i] ); + } + + stream->read( &waterDragScale ); + stream->read( &buoyancyDensity ); + stream->read( &angularDrag ); + stream->read( &linearDrag ); + + clientOnly = stream->readFlag(); + singlePlayerOnly = stream->readFlag(); + stream->read( &breakForce ); +} + +bool PxMultiActorData::preload( bool server, String &errorBuffer ) +{ + if ( !Parent::preload( server, errorBuffer ) ) + return false; + + // If the stream is null, exit. + if ( !physXStream || !physXStream[0] ) + { + errorBuffer = "PxMultiActorData::preload: physXStream is unset!"; + return false; + } + + // Set up our buffer for the binary stream filename path. + UTF8 binPhysXStream[260] = { 0 }; + const UTF8* ext = dStrrchr( physXStream, '.' ); + + // Copy the xml stream path except for the extension. + if ( ext ) + dStrncpy( binPhysXStream, physXStream, getMin( 260, ext - physXStream ) ); + else + dStrncpy( binPhysXStream, physXStream, 260 ); + + // Concatenate the binary extension. + dStrcat( binPhysXStream, ".nxb" ); + + // Get the modified times of the two files. + FileTime xmlTime = {0}, binTime = {0}; + Platform::getFileTimes( physXStream, NULL, &xmlTime ); + Platform::getFileTimes( binPhysXStream, NULL, &binTime ); + + // If the binary is newer... load that. + if ( Platform::compareFileTimes( binTime, xmlTime ) >= 0 ) + _loadCollection( binPhysXStream, true ); + + // If the binary failed... then load the xml. + if ( !collection ) + { + _loadCollection( physXStream, false ); + + // If loaded... resave the xml in binary format + // for quicker subsequent loads. + if ( collection ) + NXU::saveCollection( collection, binPhysXStream, NXU::FT_BINARY ); + } + + // If it still isn't loaded then we've failed! + if ( !collection ) + { + errorBuffer = String::ToString( "PxMultiActorDatas::preload: could not load '%s'!", physXStream ); + return false; + } + + if (!shapeName || shapeName == '\0') + { + errorBuffer = "PxMultiActorDatas::preload: no shape name!"; + return false; + } + + shape = ResourceManager::get().load( shapeName ); + + if (bool(shape) == false) + { + errorBuffer = String::ToString( "PxMultiActorData::preload: unable to load shape: %s", shapeName ); + return false; + } + + // Find the client side material. + if ( !server && material ) + Sim::findObject( SimObjectId(material), material ); + + // Get the ignore node indexes from the names. + for ( S32 i = 0; i < MaxCorrectionNodes; i++ ) + { + if( !correctionNodeNames[i] || !correctionNodeNames[i][0] ) + continue; + + correctionNodes[i] = shape->findNode( correctionNodeNames[i] ); + } + + // Resolve mount point node indexes + for ( S32 i = 0; i < NumMountPoints; i++) { + char fullName[256]; + dSprintf(fullName,sizeof(fullName),"mount%d",i); + mountPointNode[i] = shape->findNode(fullName); + } + + // Register for file change notification to reload the collection + if ( server ) + FS::AddChangeNotification( physXStream, this, &PxMultiActorData::_onFileChanged ); + + return true; +} + +void PxMultiActorData::_onFileChanged( const Torque::Path &path ) +{ + reload(); +} + +void PxMultiActorData::reload() +{ + bool result = _loadCollection( physXStream, false ); + + if ( !result ) + Con::errorf( "PxMultiActorData::reload(), _loadCollection failed..." ); + + // Inform MultiActors who use this datablock to reload. + mReloadSignal.trigger(); +} + +bool PxMultiActorData::_loadCollection( const UTF8 *path, bool isBinary ) +{ + if ( collection ) + { + NXU::releaseCollection( collection ); + collection = NULL; + } + + FileStream fs; + if ( !fs.open( path, Torque::FS::File::Read ) ) + return false; + + // Load the data into memory. + U32 size = fs.getStreamSize(); + FrameTemp buff( size ); + fs.read( size, buff ); + + // If the stream didn't read anything, there's a problem. + if ( size <= 0 ) + return false; + + // Ok... try to load it. + collection = NXU::loadCollection( path, + isBinary ? NXU::FT_BINARY : NXU::FT_XML, + buff, + size ); + + return collection != NULL; +} + + +bool PxMultiActorData::createActors( NxScene *scene, + NxCompartment *compartment, + const NxMat34 *nxMat, + const Point3F& scale, + Vector *outActors, + Vector *outShapes, + Vector *outJoints, + Vector *outActorUserProperties, + Vector *outJointUserProperties ) +{ + if ( !scene ) + { + Con::errorf( "PxMultiActorData::createActor() - returned null NxScene" ); + return NULL; + } + + PxMultiActor_Notify pxNotify( compartment, material, *nxMat, scale, outActorUserProperties, outJointUserProperties ); + + NXU::instantiateCollection( collection, *gPhysicsSDK, scene, nxMat, &pxNotify ); + + *outActors = pxNotify.getActors(); + *outJoints = pxNotify.getJoints(); + if ( outShapes ) + *outShapes = pxNotify.getShapes(); + + if ( outActors->empty() ) + { + Con::errorf( "PxMultiActorData::createActors() - NXUStream notifier returned empty actors or joints!" ); + return false; + } + + return true; +} + +IMPLEMENT_CO_NETOBJECT_V1(PxMultiActor); + +PxMultiActor::PxMultiActor() + : mShapeInstance( NULL ), + mRootActor( NULL ), + mWorld( NULL ), + mStartImpulse( 0, 0, 0 ), + mResetXfm( true ), + mActorScale( 0, 0, 0 ), + mDebugRender( false ), + mIsDummy( false ), + mBroken( false ), + mDataBlock( NULL ) +{ + mNetFlags.set( Ghostable | ScopeAlways ); + + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType | ShadowCasterObjectType; + + //mUserData.setObject( this ); + + overrideOptions = false; +} + +void PxMultiActor::initPersistFields() +{ + Parent::initPersistFields(); + + /* + // We're overloading these fields from SceneObject + // in order to force it to go thru setTransform! + removeField( "position" ); + removeField( "rotation" ); + removeField( "scale" ); + + addGroup( "Transform" ); + + addProtectedField( "position", TypeMatrixPosition, 0, + &PxMultiActor::_setPositionField, + &PxMultiActor::_getPositionField, + "" ); + + addProtectedField( "rotation", TypeMatrixRotation, 0, + &PxMultiActor::_setRotationField, + &PxMultiActor::_getRotationField, + "" ); + + addField( "scale", TypePoint3F, Offset( mObjScale, PxMultiActor ) ); + + endGroup( "Transform" ); + */ + + //addGroup("Physics"); + // addField( "AngularDrag", TypeF32, ) + //endGroup("Physics"); + + addGroup("Lighting"); + addField("receiveSunLight", TypeBool, Offset(receiveSunLight, PxMultiActor)); + addField("receiveLMLighting", TypeBool, Offset(receiveLMLighting, PxMultiActor)); + //addField("useAdaptiveSelfIllumination", TypeBool, Offset(useAdaptiveSelfIllumination, TSStatic)); + addField("useCustomAmbientLighting", TypeBool, Offset(useCustomAmbientLighting, PxMultiActor)); + //addField("customAmbientSelfIllumination", TypeBool, Offset(customAmbientForSelfIllumination, TSStatic)); + addField("customAmbientLighting", TypeColorF, Offset(customAmbientLighting, PxMultiActor)); + addField("lightGroupName", TypeRealString, Offset(lightGroupName, PxMultiActor)); + endGroup("Lighting"); + + addGroup( "Debug" ); + addField( "debugRender", TypeBool, Offset( mDebugRender, PxMultiActor ) ); + addField( "broken", TypeBool, Offset( mBroken, PxMultiActor ) ); + endGroup( "Debug" ); + + //addGroup("Collision"); + //endGroup("Collision"); +} + +bool PxMultiActor::onAdd() +{ + PROFILE_SCOPE( PxMultiActor_OnAdd ); + + if (!Parent::onAdd() || !mDataBlock ) + return false; + + mIsDummy = isClientObject() && gPhysicsPlugin->isSinglePlayer(); //&& mDataBlock->singlePlayerOnly; + + mShapeInstance = new TSShapeInstance( mDataBlock->shape, isClientObject() ); + + mObjBox = mDataBlock->shape->bounds; + resetWorldBox(); + + addToScene(); + + String worldName = isServerObject() ? "server" : "client"; + + // SinglePlayer objects only have server-side physics representations. + if ( mIsDummy ) + worldName = "server"; + + mWorld = dynamic_cast( gPhysicsPlugin->getWorld( worldName ) ); + if ( !mWorld || !mWorld->getScene() ) + { + Con::errorf( "PxMultiActor::onAdd() - PhysXWorld not initialized!" ); + return false; + } + + applyWarp( getTransform(), true, false ); + mResetXfm = getTransform(); + + if ( !_createActors( getTransform() ) ) + { + Con::errorf( "PxMultiActor::onAdd(), _createActors failed" ); + return false; + } + + if ( !mIsDummy ) + mDataBlock->mReloadSignal.notify( this, &PxMultiActor::onFileNotify ); + + // If the editor is on... let it know! + //if ( gEditingMission ) + //onEditorEnable(); // TODO: Fix this up. + + PhysicsPlugin::getPhysicsResetSignal().notify( this, &PxMultiActor::onPhysicsReset, 1050.0f ); + + setAllBroken( false ); + + if ( isServerObject() ) + scriptOnAdd(); + + return true; +} + + +void PxMultiActor::onRemove() +{ + removeFromScene(); + + _destroyActors(); + mWorld = NULL; + + SAFE_DELETE( mShapeInstance ); + + PhysicsPlugin::getPhysicsResetSignal().remove( this, &PxMultiActor::onPhysicsReset ); + + if ( !mIsDummy && mDataBlock ) + mDataBlock->mReloadSignal.remove( this, &PxMultiActor::onFileNotify ); + + Parent::onRemove(); +} + +void PxMultiActor::_destroyActors() +{ + // Dummies don't have physics objects. + if ( mIsDummy || !mWorld ) + return; + + mWorld->releaseWriteLock(); + + // Clear the root actor. + mRootActor = NULL; + + // Clear the relative transforms. + //mRelXfms.clear(); + + // The shapes are owned by the actors, so + // we just need to clear them. + mShapes.clear(); + + // Release the joints first. + for( S32 i = 0; i < mJoints.size(); i++ ) + { + NxJoint *joint = mJoints[i]; + + if ( !joint ) + continue; + + // We allocate per joint userData and we must free it. + PxJointUserData *jointData = PxJointUserData::getData( *joint ); + if ( jointData ) + delete jointData; + + mWorld->releaseJoint( *joint ); + } + mJoints.clear(); + + // Now release the actors. + for( S32 i = 0; i < mActors.size(); i++ ) + { + NxActor *actor = mActors[i]; + + PxUserData *actorData = PxUserData::getData( *actor ); + if ( actorData ) + delete actorData; + + if ( actor ) + mWorld->releaseActor( *actor ); + } + mActors.clear(); +} + +bool PxMultiActor::_createActors( const MatrixF &xfm ) +{ + if ( mIsDummy ) + { + // Dummies don't have physics objects, but + // they do handle actor deltas. + + PxMultiActor *serverObj = static_cast( mServerObject.getObject() ); + mActorDeltas.setSize( serverObj->mActors.size() ); + dMemset( mActorDeltas.address(), 0, mActorDeltas.memSize() ); + + return true; + } + + NxMat34 nxMat; + nxMat.setRowMajor44( xfm ); + + // Store the scale for comparison in setScale(). + mActorScale = getScale(); + + // Release the write lock so we can create actors. + mWorld->releaseWriteLock(); + + Vector actorUserProperties; + Vector jointUserProperties; + bool created = mDataBlock->createActors( mWorld->getScene(), + mWorld->getRigidCompartment(), + &nxMat, + mActorScale, + &mActors, + &mShapes, + &mJoints, + &actorUserProperties, + &jointUserProperties ); + + // Debug output... + //for ( U32 i = 0; i < mJoints.size(); i++ ) + // Con::printf( "Joint0 name: '%s'", mJoints[i]->getName() ); + //for ( U32 i = 0; i < actorUserProperties.size(); i++ ) + //Con::printf( "actor%i UserProperties: '%s'", i, actorUserProperties[i].c_str() ); + //for ( U32 i = 0; i < jointUserProperties.size(); i++ ) + // Con::printf( "joint%i UserProperties: '%s'", i, jointUserProperties[i].c_str() ); + + if ( !created ) + { + Con::errorf( "PxMultiActor::_createActors() - failed!" ); + return false; + } + + // Make the first actor the root actor by default, but + // if we have a kinematic actor then use that. + mRootActor = mActors[0]; + for ( S32 i = 0; i < mActors.size(); i++ ) + { + if ( mActors[i]->readBodyFlag( NX_BF_KINEMATIC ) ) + { + mRootActor = mActors[i]; + break; + } + } + + mDelta.pos = mDelta.lastPos = getPosition(); + mDelta.rot = mDelta.lastRot = getTransform(); + + bool *usedActors = new bool[mActors.size()]; + dMemset( usedActors, 0, sizeof(bool) * mActors.size() ); + + TSShape *shape = mShapeInstance->getShape(); + + // Should already be done when actors are destroyed. + mMappedActors.clear(); + Vector mappedActorProperties; + + // Remap the actors to the shape instance's bone indices. + for( S32 i = 0; i < mShapeInstance->mNodeTransforms.size(); i++ ) + { + if ( !shape ) + break; + + UTF8 comparisonName[260] = { 0 }; + NxActor *actor = NULL; + NxActor *pushActor = NULL; + String actorProperties; + + S32 nodeNameIdx = shape->nodes[i].nameIndex; + const UTF8 *nodeName = shape->getName( nodeNameIdx ); + + S32 dl = -1; + dStrcpy( comparisonName, String::GetTrailingNumber( nodeName, dl ) ); //, ext - nodeName ); + dSprintf( comparisonName, sizeof( comparisonName ), "%s_pxactor", comparisonName ); + + //String test( nodeName ); + //AssertFatal( test.find("gableone",0,String::NoCase) == String::NPos, "found it" ); + + // If we find an actor that corresponds to this node we will + // push it back into the remappedActors vector, otherwise + // we will push back NULL. + for ( S32 j = 0; j < mActors.size(); j++ ) + { + actor = mActors[j]; + const UTF8 *actorName = actor->getName(); + + if ( dStricmp( comparisonName, actorName ) == 0 ) + { + pushActor = actor; + actorProperties = actorUserProperties[j]; + usedActors[j] = true; + break; + } + } + + mMappedActors.push_back( pushActor ); + mappedActorProperties.push_back( actorProperties ); + if ( !pushActor ) + dl = -1; + mMappedActorDL.push_back( dl ); + + // Increase the sleep tolerance. + if ( pushActor ) + { + //pushActor->raiseBodyFlag( NX_BF_ENERGY_SLEEP_TEST ); + //pushActor->setSleepEnergyThreshold( 2 ); + //pushActor->userData = NULL; + } + } + + // Delete any unused/orphaned actors. + for ( S32 i = 0; i < mActors.size(); i++ ) + { + if ( usedActors[i] ) + continue; + + NxActor *actor = mActors[i]; + + Con::errorf( "PxMultiActor::_createActors() - Orphan NxActor - '%s'!", actor->getName() ); + + if ( actor == mRootActor ) + { + Con::errorf( "PxMultiActor::_createActors() - root actor (%s) was orphan, cannot continue.", actor->getName() ); + return false; + } + + // Remove references to shapes of the deleted actor. + for ( S32 i = 0; i < mShapes.size(); i++ ) + { + if ( &(mShapes[i]->getActor()) == actor ) + { + mShapes.erase_fast(i); + i--; + } + } + + mWorld->releaseActor( *actor ); + } + + // Done with this helper. + delete [] usedActors; + + // Repopulate mActors with one entry per real actor we own. + mActors.clear(); + mMappedToActorIndex.clear(); + actorUserProperties.clear(); + for ( S32 i = 0; i < mMappedActors.size(); i++ ) + { + S32 index = -1; + if ( mMappedActors[i] ) + { + index = mActors.push_back_unique( mMappedActors[i] ); + while ( index >= actorUserProperties.size() ) + actorUserProperties.push_back( String::EmptyString ); + actorUserProperties[index] = mappedActorProperties[i]; + } + mMappedToActorIndex.push_back( index ); + } + + if ( mActors.size() == 0 ) + { + Con::errorf( "PxMultiActor::_createActors, got zero actors! Were all actors orphans?" ); + return false; + } + + // Initialize the actor deltas. + mActorDeltas.setSize( mActors.size() ); + dMemset( mActorDeltas.address(), 0, mActorDeltas.memSize() ); + + // Assign user data for actors. + for ( U32 i = 0; i < mActors.size(); i++ ) + { + NxActor *actor = mActors[i]; + if ( !actor ) + continue; + + actor->userData = _createActorUserData( actor, actorUserProperties[i] ); + } + + NxActor *actor1; + NxActor *actor2; + PxUserData *pUserData; + + // Allocate user data for joints. + for ( U32 i = 0; i < mJoints.size(); i++ ) + { + NxJoint *joint = mJoints[i]; + if ( !joint ) + continue; + + joint->userData = _createJointUserData( joint, jointUserProperties[i] ); + + // Set actors attached to joints as not-pushable (by the player). + joint->getActors( &actor1, &actor2 ); + if ( actor1 ) + { + pUserData = PxUserData::getData( *actor1 ); + if ( pUserData ) + pUserData->mCanPush = false; + } + if ( actor2 ) + { + pUserData = PxUserData::getData( *actor2 ); + if ( pUserData ) + pUserData->mCanPush = false; + } + } + + // Set actors and meshes to the unbroken state. + setAllBroken( false ); + + return true; +} + +PxUserData* PxMultiActor::_createActorUserData( NxActor *actor, String &userProperties ) +{ + PxUserData *actorData = new PxUserData(); + actorData->setObject( this ); + + // We use this for saving relative xfms for 'broken' actors. + NxMat34 actorPose = actor->getGlobalPose(); + NxMat34 actorSpaceXfm; + actorPose.getInverse( actorSpaceXfm ); + + const String actorName( actor->getName() ); + + static const String showStr( "PxBrokenShow" ); + static const String hideStr( "PxBrokenHide" ); + + // 3DSMax saves out double newlines, replace them with one. + userProperties.replace( "\r\n", "\n" ); + + U32 propertyCount = StringUnit::getUnitCount( userProperties, "\n" ); + for ( U32 i = 0; i < propertyCount; i++ ) + { + String propertyStr = StringUnit::getUnit( userProperties, i, "\n" ); + U32 wordCount = StringUnit::getUnitCount( propertyStr, "=" ); + + if ( wordCount == 0 ) + { + // We sometimes get empty lines between properties, + // which doesn't break anything. + continue; + } + + if ( wordCount != 2 ) + { + Con::warnf( "PxMultiActor::_createActorUserData, malformed UserProperty string (%s) for actor (%s)", propertyStr.c_str(), actorName.c_str() ); + continue; + } + + String propertyName = StringUnit::getUnit( propertyStr, 0, "=" ); + String propertyValue = StringUnit::getUnit( propertyStr, 1, "=" ); + + Vector *dstVector = NULL; + if ( propertyName.equal( showStr, String::NoCase ) ) + dstVector = &actorData->mBrokenActors; + else if ( propertyName.equal( hideStr, String::NoCase ) ) + dstVector = &actorData->mUnbrokenActors; + + if ( !dstVector ) + continue; + + U32 valueCount = StringUnit::getUnitCount( propertyValue, "," ); + for ( U32 j = 0; j < valueCount; j++ ) + { + String val = StringUnit::getUnit( propertyValue, j, "," ); + + NxActor *pActor = _findActor( val ); + if ( !pActor ) + Con::warnf( "PxMultiActor::_createActorUserData, actor (%s) was not found when parsing UserProperties for actor (%s)", val.c_str(), actorName.c_str() ); + else + { + dstVector->push_back( pActor ); + + if ( dstVector == &actorData->mBrokenActors ) + { + NxMat34 relXfm = pActor->getGlobalPose(); + relXfm.multiply( relXfm, actorSpaceXfm ); + actorData->mRelXfm.push_back( relXfm ); + } + } + } + } + + // Only add a contact signal to this actor if + // we have objects we can break. + if ( actorData->mBrokenActors.size() > 0 && + mDataBlock->breakForce > 0.0f ) + { + actor->setContactReportFlags( NX_NOTIFY_ON_START_TOUCH_FORCE_THRESHOLD | NX_NOTIFY_FORCES ); + actor->setContactReportThreshold( mDataBlock->breakForce ); + actorData->getContactSignal().notify( this, &PxMultiActor::_onContact ); + } + + return actorData; +} + +PxJointUserData* PxMultiActor::_createJointUserData( NxJoint *joint, String &userProperties ) +{ + PxJointUserData *jointData = new PxJointUserData(); + jointData->setObject( this ); + + // We use this for saving relative xfms for 'broken' actors. + NxActor *actor0; + NxActor *actor1; + joint->getActors( &actor0, &actor1 ); + NxMat34 actorPose = actor0->getGlobalPose(); + NxMat34 actorSpaceXfm; + actorPose.getInverse( actorSpaceXfm ); + + // The PxMultiActor will live longer than the joint + // so this notify shouldn't ever need to be removed. Although if someone + // other than this multiactor were to register for this notify and their + // lifetime could be shorter, then 'they' might have to. + jointData->getOnJointBreakSignal().notify( this, &PxMultiActor::_onJointBreak ); + + // JCFHACK: put this in userProperties too. + Sim::findObject( "JointBreakEmitter", jointData->mParticleEmitterData ); + + String showStr( "PxBrokenShow" ); + String hideStr( "PxBrokenHide" ); + + // Max saves out double newlines, replace them with one. + userProperties.replace( "\r\n", "\n" ); + + U32 propertyCount = StringUnit::getUnitCount( userProperties, "\n" ); + for ( U32 i = 0; i < propertyCount; i++ ) + { + String propertyStr = StringUnit::getUnit( userProperties, i, "\n" ); + U32 wordCount = StringUnit::getUnitCount( propertyStr, "=" ); + + if ( wordCount == 0 ) + { + // We sometimes get empty lines between properties, + // which doesn't break anything. + continue; + } + + if ( wordCount != 2 ) + { + Con::warnf( "PxMultiActor::_createJointUserData, malformed UserProperty string (%s) for joint (%s)", propertyStr.c_str(), joint->getName() ); + continue; + } + + String propertyName = StringUnit::getUnit( propertyStr, 0, "=" ); + String propertyValue = StringUnit::getUnit( propertyStr, 1, "=" ); + + Vector *dstVector = NULL; + if ( propertyName.equal( showStr, String::NoCase ) ) + dstVector = &jointData->mBrokenActors; + else if ( propertyName.equal( hideStr, String::NoCase ) ) + dstVector = &jointData->mUnbrokenActors; + + if ( !dstVector ) + continue; + + U32 valueCount = StringUnit::getUnitCount( propertyValue, "," ); + for ( U32 j = 0; j < valueCount; j++ ) + { + String val = StringUnit::getUnit( propertyValue, j, "," ); + + NxActor *pActor = _findActor( val ); + if ( !pActor ) + Con::warnf( "PxMultiActor::_createJointUserData, actor (%s) was not found when parsing UserProperties for joint (%s)", val.c_str(), joint->getName() ); + else + { + dstVector->push_back( pActor ); + + if ( dstVector == &jointData->mBrokenActors ) + { + NxMat34 relXfm = pActor->getGlobalPose(); + relXfm.multiply( relXfm, actorSpaceXfm ); + jointData->mRelXfm.push_back( relXfm ); + } + } + } + } + + return jointData; +} + +NxActor* PxMultiActor::_findActor( const String &actorName ) const +{ + for ( U32 i = 0; i < mActors.size(); i++ ) + { + NxActor *actor = mActors[i]; + if ( !actor ) + continue; + + if ( dStricmp( actor->getName(), actorName ) == 0 ) + return actor; + } + + return NULL; +} + +String PxMultiActor::_getMeshName( const NxActor *actor ) const +{ + String meshName = actor->getName(); + meshName.replace( "_pxactor", "" ); + //meshName = StringUnit::getUnit( meshName, 0, "_" ); + return meshName; +} + +bool PxMultiActor::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + + if ( !mDataBlock || !Parent::onNewDataBlock( dptr ) ) + return false; + + // JCF: if we supported it, we would recalculate the value of mIsDummy now, + // but that would really hose everything since an object that was a dummy + // wouldn't have any actors and would need to create them, etc... + + scriptOnNewDataBlock(); + + return true; +} + +void PxMultiActor::inspectPostApply() +{ + // Make sure we call the parent... else + // we won't get transform and scale updates! + Parent::inspectPostApply(); + + //setMaskBits( LightMask ); + setMaskBits( UpdateMask ); +} + +void PxMultiActor::onStaticModified( const char *slotName, const char *newValue ) +{ + if ( isProperlyAdded() && dStricmp( slotName, "broken" ) == 0 ) + setAllBroken( dAtob(newValue) ); +} + +void PxMultiActor::onDeleteNotify( SimObject *obj ) +{ + Parent::onDeleteNotify(obj); + if ( obj == mMount.object ) + unmount(); +} + +void PxMultiActor::onFileNotify() +{ + // Destroy the existing actors and recreate them... + + mWorld->getPhysicsResults(); + _destroyActors(); + _createActors( mResetXfm ); +} + +void PxMultiActor::onPhysicsReset( PhysicsResetEvent reset ) +{ + // Dummies don't create or destroy actors, they just reuse the + // server object's ones. + if ( mIsDummy ) + return; + + // Store the reset transform for later use. + if ( reset == PhysicsResetEvent_Store ) + { + mRootActor->getGlobalPose().getRowMajor44( mResetXfm ); + } + else if ( reset == PhysicsResetEvent_Restore ) + { + // Destroy the existing actors and recreate them to + // ensure they are in the proper mission startup state. + mWorld->getPhysicsResults(); + + _destroyActors(); + _createActors( mResetXfm ); + } + + for ( U32 i = 0; i < mActors.size(); i++ ) + { + if ( !mActors[i] ) + continue; + + mActors[i]->wakeUp(); + } +} + +bool PxMultiActor::prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + PROFILE_SCOPE( PxMultiActor_PrepRenderImage ); + + if ( isLastState( state, stateKey ) || + !mShapeInstance || + !state->isObjectRendered( this ) ) + return false; + + Point3F cameraOffset; + getTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getDiffuseCameraPosition(); + F32 dist = cameraOffset.len(); + if ( dist < 0.01f ) + dist = 0.01f; + + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + + S32 dl = mShapeInstance->setDetailFromDistance( state, dist * invScale ); + if ( dl < 0 ) + return false; + + GFXTransformSaver saver; + + // Set up our TS render state here. + TSRenderState rdata; + rdata.setSceneState( state ); + + LightManager *lm = gClientSceneGraph->getLightManager(); + if ( !state->isShadowPass() ) + lm->setupLights( this, getWorldSphere() ); + + MatrixF mat = getRenderTransform(); + mat.scale( getScale() ); + GFX->setWorldMatrix( mat ); + + if ( mDebugRender || Con::getBoolVariable( "$PxDebug::render", false ) ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &PxMultiActor::_debugRender ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + else + mShapeInstance->render( rdata ); + + lm->resetLights(); + + return true; +} + +void PxMultiActor::_debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + if ( mShapeInstance ) + { + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + GFX->multWorld( mat ); + + //mShapeInstance->renderDebugNodes(); + } + + Vector *actors = &mActors; + if ( mIsDummy ) + { + PxMultiActor *serverObj = static_cast( mServerObject.getObject() ); + if ( serverObj ) + actors = &serverObj->mActors; + } + + if ( !actors ) + return; + + for ( U32 i = 0; i < actors->size(); i++ ) + { + NxActor *pActor = (*actors)[i]; + if ( !pActor ) + continue; + + PxUtils::drawActor( pActor ); + } +} + +void PxMultiActor::_onJointBreak( NxReal breakForce, NxJoint &brokenJoint ) +{ + // Dummies do not have physics objects + // and shouldn't receive this callback. + if ( mIsDummy ) + return; + + NxActor *actor0 = NULL; + NxActor *actor1 = NULL; + brokenJoint.getActors( &actor0, &actor1 ); + NxMat34 parentPose = actor0->getGlobalPose(); + + Point3F jointPos = pxCast( brokenJoint.getGlobalAnchor() ); + + PxJointUserData *jointData = PxJointUserData::getData( brokenJoint ); + setBroken( parentPose, NxVec3( 0.0f ), jointData, true ); + + // NOTE: We do not NULL the joint in the list, + // or release it here, as we allow it to be released + // by the _destroyActors function on a reset or destruction + // of the PxMultiActor. + + // Server objects don't do the following client side effects. + // ... unless it's single player! + if ( isServerObject() && !gPhysicsPlugin->isSinglePlayer() ) + return; + + // JCFHACK: If this is a server object trying to play a client side + // effect in a single player situation, it could happen before the client + // is actually initialized and connected! + SimObject *temp = Sim::findObject( "ClientMissionCleanup"); + if ( !temp ) + return; + + ParticleEmitterData *particleData = NULL; + if ( !Sim::findObject( "JointBreakEmitter", particleData ) ) + return; + + ParticleEmitter * emitter = new ParticleEmitter; + emitter->onNewDataBlock( particleData ); + + MatrixF jointMat( true ); + jointMat.setPosition( jointPos ); + + if ( !emitter->registerObject() ) + { + delete emitter; + emitter = NULL; + } + else + { + emitter->emitParticles( jointPos, Point3F( 0.0, 0.0, 1.0 ), 0.25f, Point3F( 0, 0, 0 ), 10 ); + emitter->deleteWhenEmpty(); + } +} + +void PxMultiActor::_onContact( NxActor *ourActor, + NxActor *hitActor, + SceneObject *hitObject, + const Point3F &hitPoint, + const Point3F &hitForce ) +{ + PxUserData *data = PxUserData::getData( *ourActor ); + if ( data && + !data->mIsBroken && + hitForce.len() > mDataBlock->breakForce ) + setAllBroken( true ); +} + +U32 PxMultiActor::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + stream->writeFlag( mDebugRender ); + + stream->writeFlag( mask & SleepMask ); + + if ( stream->writeFlag( mask & WarpMask ) ) + { + stream->writeAffineTransform( getTransform() ); + } + else if ( stream->writeFlag( mask & MoveMask ) ) + { + /* + stream->writeAffineTransform( getTransform() ); + + NxActor *actor = mActors[ mDataBlock->correctionNodes[0] ]; + + const NxVec3& linVel = actor->getLinearVelocity(); + stream->write( linVel.x ); + stream->write( linVel.y ); + stream->write( linVel.z ); + */ + } + + // This internally uses the mask passed to it. + if ( mLightPlugin ) + retMask |= mLightPlugin->packUpdate( this, LightMask, con, mask, stream ); + + return retMask; +} + + +void PxMultiActor::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + + mDebugRender = stream->readFlag(); + + if ( stream->readFlag() ) // SleepMask + { + for ( S32 i = 0; i < mActors.size(); i++ ) + { + NxActor *actor = mActors[i]; + + if ( !actor ) + continue; + + if ( actor ) + actor->putToSleep(); + } + } + + if ( stream->readFlag() ) // WarpMask + { + // If we set a warp mask, + // we need to instantly move + // the actor to the new position + // without applying any corrections. + MatrixF mat; + stream->readAffineTransform( &mat ); + + applyWarp( mat, true, false ); + } + else if ( stream->readFlag() ) // MoveMask + { + /* + MatrixF mat; + stream->readAffineTransform( &mat ); + + NxVec3 linVel, angVel; + stream->read( &linVel.x ); + stream->read( &linVel.y ); + stream->read( &linVel.z ); + + applyCorrection( mat, linVel, angVel ); + */ + } +/* + if ( stream->readFlag() ) // ImpulseMask + { + // TODO : Set up correction nodes. + + NxVec3 linVel; + stream->read( &linVel.x ); + stream->read( &linVel.y ); + stream->read( &linVel.z ); + + NxActor *actor = mActors[ mDataBlock->correctionNodes[0] ]; + + if ( actor ) + { + mWorld->releaseWriteLock(); + actor->setLinearVelocity( linVel ); + mStartImpulse.zero(); + } + else + mStartImpulse.set( linVel.x, linVel.y, linVel.z ); + } +*/ + if ( mLightPlugin ) + mLightPlugin->unpackUpdate( this, con, stream ); +} + +void PxMultiActor::setScale( const VectorF& scale ) +{ + if ( scale == getScale() ) + return; + + // This is so that the level + // designer can change the scale + // of a PhysXSingleActor in the editor + // and have the PhysX representation updated properly. + + // First we call the parent's setScale + // so that the ScaleMask can be set. + Parent::setScale( scale ); + + // Check to see if the scale has really changed. + if ( !isProperlyAdded() || mActorScale.equal( scale ) ) + return; + + // Recreate the physics actors. + _destroyActors(); + _createActors( getTransform() ); +} + +void PxMultiActor::applyWarp( const MatrixF& newMat, bool interpRender, bool sweep ) +{ + // Do we have actors to move? + if ( mRootActor ) + { + // Get ready to change the physics state. + mWorld->releaseWriteLock(); + + /// Convert the new transform to nx. + NxMat34 destXfm; + destXfm.setRowMajor44( newMat ); + + // Get the inverse of the root actor transform + // so we can move all the actors relative to it. + NxMat34 rootInverseXfm; + mRootActor->getGlobalPose().getInverse( rootInverseXfm ); + + // Offset all the actors. + MatrixF tMat; + NxMat34 newXfm, relXfm; + for ( S32 i = 0; i < mActors.size(); i++ ) + { + NxActor *actor = mActors[i]; + if ( !actor ) + continue; + + const bool isKinematic = actor->readBodyFlag( NX_BF_KINEMATIC ); + + // Stop any velocity on it. + if ( !isKinematic ) + { + actor->setAngularVelocity( NxVec3( 0.0f ) ); + actor->setLinearVelocity( NxVec3( 0.0f ) ); + } + + // Get the transform relative to the current root. + relXfm.multiply( actor->getGlobalPose(), rootInverseXfm ); + + /* + if ( sweep ) + { + actor->getGl obalPose().getRowMajor44( mResetPos[i] ); + sweepTest( &newMat ); + } + */ + + // + newXfm.multiply( relXfm, destXfm ); + //if ( isKinematic ) + //actor->moveGlobalPose( newXfm ); + //else + actor->setGlobalPose( newXfm ); + + // Reset the delta. + Delta &delta = mActorDeltas[i]; + delta.pos = pxCast( newXfm.t ); + newXfm.getRowMajor44( tMat ); + delta.rot.set( tMat ); + + if ( !interpRender ) + { + mActorDeltas[i].lastPos = mActorDeltas[i].pos; + mActorDeltas[i].lastRot = mActorDeltas[i].rot; + } + } + } + + Parent::setTransform( newMat ); + + mDelta.pos = newMat.getPosition(); + mDelta.rot = newMat; + + if ( !interpRender ) + { + mDelta.lastPos = mDelta.pos; + mDelta.lastRot = mDelta.rot; + } +} + +/* +bool PxMultiActor::_setPositionField( void *obj, const char *data ) +{ + PxMultiActor *object = reinterpret_cast( obj ); + + MatrixF transform( object->getTransform() ); + Con::setData( TypeMatrixPosition, &transform, 0, 1, &data ); + + object->setTransform( transform ); + + return false; +} + +const char* PxMultiActor::_getPositionField( void *obj, const char *data ) +{ + PxMultiActor *object = reinterpret_cast( obj ); + return Con::getData( TypeMatrixPosition, + &object->mObjToWorld, + 0 ); +} + +bool PxMultiActor::_setRotationField( void *obj, const char *data ) +{ + PxMultiActor *object = reinterpret_cast( obj ); + + MatrixF transform( object->getTransform() ); + Con::setData( TypeMatrixRotation, &transform, 0, 1, &data ); + + object->setTransform( transform ); + + return false; +} + +const char* PxMultiActor::_getRotationField( void *obj, const char *data ) +{ + PxMultiActor *object = reinterpret_cast( obj ); + return Con::getData( TypeMatrixRotation, + &object->mObjToWorld, + 0 ); +} +*/ + +void PxMultiActor::setTransform( const MatrixF& mat ) +{ + applyWarp( mat, false, true ); + setMaskBits( WarpMask ); +} + +void PxMultiActor::mountObject( SceneObject *obj, U32 node ) +{ + //if (obj->mMount.object == this) + // return; + + if (obj->mMount.object) + obj->unmount(); + + obj->mMount.object = this; + obj->mMount.node = (node >= 0 && node < PxMultiActorData::NumMountPoints)? node: 0; + obj->mMount.link = mMount.list; + mMount.list = obj; + + obj->onMount( this, node ); +} + + +void PxMultiActor::unmountObject( SceneObject *obj ) +{ + if ( obj->mMount.object == this ) + { + // Find and unlink the object + for ( SceneObject **ptr = &mMount.list; *ptr; ptr = &(*ptr)->mMount.link ) + { + if ( *ptr == obj ) + { + *ptr = obj->mMount.link; + break; + } + } + + obj->mMount.object = 0; + obj->mMount.link = 0; + + obj->onUnmount( this, obj->mMount.node ); + } +} + +bool PxMultiActor::_getNodeTransform( U32 nodeIdx, MatrixF *outXfm ) +{ + if ( !mShapeInstance ) + return false; + + PxMultiActor *actorOwner = this; + if ( mIsDummy ) + { + actorOwner = static_cast( mServerObject.getObject() ); + if ( !actorOwner ) + return false; + } + + TSShape *shape = mShapeInstance->getShape(); + String nodeName = shape->getNodeName( nodeIdx ); + + NxActor *pActor = NULL; + UTF8 comparisonName[260] = { 0 }; + S32 dummy = -1; + + // Convert the passed node name to a valid actor name. + dStrcpy( comparisonName, String::GetTrailingNumber( nodeName, dummy ) ); + dSprintf( comparisonName, sizeof( comparisonName ), "%s_pxactor", comparisonName ); + + // If we have an actor with that name, we are done. + pActor = actorOwner->_findActor( comparisonName ); + if ( pActor ) + { + pActor->getGlobalPose().getRowMajor44( *outXfm ); + return true; + } + + // Check if the parent node has an actor... + + S32 parentIdx = shape->nodes[nodeIdx].parentIndex; + if ( parentIdx == -1 ) + return false; + + const String &parentName = shape->getNodeName( parentIdx ); + dStrcpy( comparisonName, String::GetTrailingNumber( parentName, dummy ) ); + dSprintf( comparisonName, sizeof( comparisonName ), "%s_pxactor", comparisonName ); + + pActor = actorOwner->_findActor( comparisonName ); + if ( !pActor ) + return false; + + MatrixF actorMat; + pActor->getGlobalPose().getRowMajor44( actorMat ); + + MatrixF nmat; + QuatF q; + TSTransform::setMatrix( shape->defaultRotations[nodeIdx].getQuatF(&q),shape->defaultTranslations[nodeIdx],&nmat); + *outXfm->mul( actorMat, nmat ); + + return true; +} + +void PxMultiActor::getMountTransform(U32 mountPoint,MatrixF* mat) +{ + /* + // Returns mount point to world space transform + if (mountPoint < PxMultiActorData::NumMountPoints) { + S32 ni = mDataBlock->mountPointNode[mountPoint]; + if (ni != -1) { + if ( _getNodeTransform( ni, mat ) ) + return; + } + } + *mat = mObjToWorld; + */ +} + +void PxMultiActor::getRenderMountTransform(U32 mountPoint,MatrixF* mat) +{ + /* + // Returns mount point to world space transform + if (mountPoint < PxMultiActorData::NumMountPoints) { + S32 ni = mDataBlock->mountPointNode[mountPoint]; + if (ni != -1) { + if ( _getNodeTransform( ni, mat ) ) + return; + } + } + *mat = getRenderTransform(); + */ +} + +void PxMultiActor::processTick( const Move *move ) +{ + PROFILE_SCOPE( PxMultiActor_ProcessTick ); + + // Set the last pos/rot to the + // values of the previous tick for interpolateTick. + mDelta.lastPos = mDelta.pos; + mDelta.lastRot = mDelta.rot; + + /* + NxActor *corrActor = mActors[ mDataBlock->correctionNodes[0] ]; + + if ( corrActor->isSleeping() || corrActor->readBodyFlag( NX_BF_FROZEN ) ) + { + if ( !mSleepingLastTick ) + setMaskBits( WarpMask | SleepMask ); + + mSleepingLastTick = true; + + // HACK! Refactor sleeping so that we don't + // sleep when only one correction actor does. + _updateBounds(); + + return; + } + + mSleepingLastTick = false; + */ + + MatrixF mat; + Vector *actors; + + if ( mIsDummy ) + { + PxMultiActor *serverObj = static_cast( mServerObject.getObject() ); + if ( !serverObj ) + return; + + mat = serverObj->getTransform(); + actors = &serverObj->mActors; + } + else + { + // Container buoyancy & drag + _updateContainerForces(); + + // Save the transform from the root actor. + mRootActor->getGlobalPose().getRowMajor44( mat ); + actors = &mActors; + } + + // Update the transform and the root delta. + Parent::setTransform( mat ); + mDelta.pos = mat.getPosition(); + mDelta.rot.set( mat ); + + // On the client we update the individual + // actor deltas as well for interpolation. + if ( isClientObject() ) + { + PROFILE_SCOPE( PxMultiActor_ProcessTick_UpdateDeltas ); + + for ( U32 i = 0; i < mActorDeltas.size(); i++ ) + { + if ( !(*actors)[i] ) + continue; + + Delta &delta = mActorDeltas[i]; + + // Store the last position. + delta.lastPos = delta.pos; + delta.lastRot = delta.rot; + + // Get the new position. + (*actors)[i]->getGlobalPose().getRowMajor44( (NxF32*)mat ); + + // Calculate the delta between the current + // global pose and the last global pose. + delta.pos = mat.getPosition(); + delta.rot.set( mat ); + } + } + + // Update the bounding box to match the physics. + _updateBounds(); + + // Set the MoveMask so this will be updated to the client. + //setMaskBits( MoveMask ); +} + +void PxMultiActor::interpolateTick( F32 delta ) +{ + PROFILE_SCOPE( PxMultiActor_InterpolateTick ); + + Point3F interpPos; + QuatF interpRot; + { + // Interpolate the position based on the delta. + interpPos.interpolate( mDelta.pos, mDelta.lastPos, delta ); + + // Interpolate the rotation based on the delta. + interpRot.interpolate( mDelta.rot, mDelta.lastRot, delta ); + + // Set up the interpolated transform. + MatrixF interpMat; + interpRot.setMatrix( &interpMat ); + interpMat.setPosition( interpPos ); + + Parent::setRenderTransform( interpMat ); + } + + PxMultiActor *srcObj = NULL; + if ( mIsDummy ) + srcObj = static_cast( mServerObject.getObject() ); + else + srcObj = this; + + // JCF: to disable applying NxActor positions to the renderable mesh + // you can uncomment this line. + //srcObj = NULL; + if ( mShapeInstance && srcObj != NULL ) + { + mShapeInstance->animate(); + getDynamicXfms( srcObj, delta ); + } +} + +/* +void PxMultiActor::sweepTest( MatrixF *mat ) +{ + NxVec3 nxCurrPos = getPosition(); + + // If the position is zero, + // the parent hasn't been updated yet + // and we don't even need to do the sweep test. + // This is a fix for a problem that was happening + // where on the add of the PhysXSingleActor, it would + // set the position to a very small value because it would be getting a hit + // even though the current position was 0. + if ( nxCurrPos.isZero() ) + return; + + // Set up the flags and the query structure. + NxU32 flags = NX_SF_STATICS | NX_SF_DYNAMICS; + + NxSweepQueryHit sweepResult; + dMemset( &sweepResult, 0, sizeof( sweepResult ) ); + + NxVec3 nxNewPos = mat->getPosition(); + + // Get the velocity which will be our sweep direction and distance. + NxVec3 nxDir = nxNewPos - nxCurrPos; + if ( nxDir.isZero() ) + return; + + NxActor *corrActor = mActors[ mDataBlock->correctionNodes[0] ]; + + // Get the scene and do the sweep. + corrActor->wakeUp(); + corrActor->linearSweep( nxDir, flags, NULL, 1, &sweepResult, NULL ); + + if ( sweepResult.hitShape && sweepResult.t < nxDir.magnitude() ) + { + nxDir.normalize(); + nxDir *= sweepResult.t; + nxCurrPos += nxDir; + + mat->setPosition( Point3F( nxCurrPos.x, nxCurrPos.y, nxCurrPos.z ) ); + } +} +*/ + +/* +void PxMultiActor::applyCorrection( const MatrixF& mat, const NxVec3& linVel, const NxVec3& angVel ) +{ + // Sometimes the actor hasn't been + // created yet during the call from unpackUpdate. + NxActor *corrActor = mActors[ mDataBlock->correctionNodes[0] ]; + + if ( !corrActor || mForceSleep ) + return; + + NxVec3 newPos = mat.getPosition(); + NxVec3 currPos = getPosition(); + + NxVec3 offset = newPos - currPos; + + // If the difference isn't large enough, + // just set the new transform, no correction. + if ( offset.magnitude() > 0.3f ) + { + // If we're going to set the linear or angular velocity, + // we do it before we add a corrective force, since it would be overwritten otherwise. + NxVec3 currLinVel, currAngVel; + currLinVel = corrActor->getLinearVelocity(); + currAngVel = corrActor->getAngularVelocity(); + + // Scale the corrective force by half, + // otherwise it will over correct and oscillate. + NxVec3 massCent = corrActor->getCMassGlobalPosition(); + corrActor->addForceAtPos( offset, massCent, NX_SMOOTH_VELOCITY_CHANGE ); + + // If the linear velocity is divergent enough, change to server linear velocity. + if ( (linVel - currLinVel).magnitude() > 0.3f ) + corrActor->setLinearVelocity( linVel ); + // Same for angular. + if ( (angVel - currAngVel).magnitude() > 0.3f ) + corrActor->setAngularVelocity( angVel ); + } + + Parent::setTransform( mat ); +} +*/ + +void PxMultiActor::_updateBounds() +{ + PROFILE_SCOPE( PxMultiActor_UpdateBounds ); + + if ( mIsDummy ) + { + PxMultiActor *serverObj = static_cast( mServerObject.getObject() ); + if ( !serverObj ) + return; + + mWorldBox = serverObj->getWorldBox(); + mWorldSphere = serverObj->getWorldSphere(); + mObjBox = serverObj->getObjBox(); + mRenderWorldBox = serverObj->getRenderWorldBox(); + mRenderWorldSphere = mWorldSphere; + + return; + } + + NxBounds3 bounds; + bounds.setEmpty(); + + NxBounds3 shapeBounds; + + for ( U32 i = 0; i < mActors.size(); i++ ) + { + NxActor *pActor = mActors[i]; + + if ( !pActor || pActor->readActorFlag( NX_AF_DISABLE_COLLISION ) ) + continue; + + NxShape *const* pShapeArray = pActor->getShapes(); + U32 shapeCount = pActor->getNbShapes(); + for ( U32 i = 0; i < shapeCount; i++ ) + { + // Get the shape's bounds. + pShapeArray[i]->getWorldBounds( shapeBounds ); + + // Combine them into the total bounds. + bounds.combine( shapeBounds ); + } + } + + mWorldBox = pxCast( bounds ); + + mWorldBox.getCenter(&mWorldSphere.center); + mWorldSphere.radius = (mWorldBox.maxExtents - mWorldSphere.center).len(); + + mObjBox = mWorldBox; + mWorldToObj.mul(mObjBox); + + mRenderWorldBox = mWorldBox; + mRenderWorldSphere = mWorldSphere; +} + +void PxMultiActor::getDynamicXfms( PxMultiActor *srcObj, F32 dt ) +{ + PROFILE_SCOPE( PxMultiActor_getDynamicXfms ); + + Vector *torqueXfms = &mShapeInstance->mNodeTransforms; + const MatrixF &objectXfm = getRenderWorldTransform(); + + AssertFatal( torqueXfms->size() == srcObj->mMappedActors.size(), "The two skeletons are different!" ); + + TSShape *shape = mShapeInstance->getShape(); + + // TODO: We're currently preparing deltas and getting + // dynamic xforms even if the object isn't visible. + // we should probably try to delay all this until + // we're about to render. + // + /* + // TODO: Set up deltas! + if ( mCurrPos.empty() || mCurrRot.empty() ) + _prepareDeltas(); + */ + + MatrixF globalXfm; + MatrixF mat, tmp; + QuatF newRot; + Point3F newPos; + + S32 dl = mShapeInstance->getCurrentDetail(); + if ( dl < 0 ) + return; + + const String &detailName = shape->getName( shape->details[dl].nameIndex ); + S32 detailSize = -1; + String::GetTrailingNumber( detailName, detailSize ); + + for( S32 i = 0; i < srcObj->mMappedActors.size(); i++ ) + { + NxActor *actor = srcObj->mMappedActors[i]; + + if ( !actor || actor->readBodyFlag( NX_BF_KINEMATIC ) ) + continue; + + // see if the node at this index is part of the + // currently visible detail level. + if ( srcObj->mMappedActorDL[i] != detailSize ) + continue; + + // Get the right actor delta structure. + U32 index = srcObj->mMappedToActorIndex[i]; + const Delta &delta = mActorDeltas[index]; + + // Do the interpolation. + newRot.interpolate( delta.rot, delta.lastRot, dt ); + newRot.setMatrix( &globalXfm ); + newPos.interpolate( delta.pos, delta.lastPos, dt ); + globalXfm.setPosition( newPos ); + + (*torqueXfms)[i].mul( objectXfm, globalXfm ); + } +} + +void PxMultiActor::applyImpulse( const Point3F &pos, const VectorF &vec ) +{ + // TODO : Implement this based on correction nodes. + /* + if ( !mWorld || !mActor ) + return; + + mWorld->releaseWriteLock(); + + NxVec3 linVel = mActor->getLinearVelocity(); + NxVec3 nxVel( vel.x, vel.y, vel.z ); + + mActor->setLinearVelocity(linVel + nxVel); + */ + + // JCF: something more complex is required to apply forces / breakage + // on only individual actors, and we don't have enough data to do that + // within this method. + + if ( vec.len() > mDataBlock->breakForce ) + setAllBroken( true ); + + NxVec3 nxvec = pxCast( vec ); + NxVec3 nxpos = pxCast( pos ); + + for ( U32 i = 0; i < mActors.size(); i++ ) + { + NxActor *actor = mActors[i]; + if ( actor->isDynamic() && + !actor->readBodyFlag( NX_BF_KINEMATIC ) && + !actor->readActorFlag( NX_AF_DISABLE_COLLISION ) ) + { + actor->addForceAtPos( nxvec, nxpos, NX_IMPULSE ); + } + } + + //setMaskBits( ImpulseMask ); +} + +void PxMultiActor::applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude ) +{ + mWorld->releaseWriteLock(); + + // Find all currently enabled actors hit by the impulse radius... + Vector hitActors; + NxVec3 nxorigin = pxCast(origin); + NxSphere impulseSphere( nxorigin, radius ); + + for ( U32 i = 0; i < mActors.size(); i++ ) + { + NxActor *pActor = mActors[i]; + + if ( pActor->readActorFlag( NX_AF_DISABLE_COLLISION ) || + !pActor->isDynamic() || + pActor->readBodyFlag( NX_BF_KINEMATIC ) ) + continue; + + U32 numShapes = pActor->getNbShapes(); + NxShape *const* pShapeArray = pActor->getShapes(); + + for ( U32 j = 0; j < numShapes; j++ ) + { + const NxShape *pShape = pShapeArray[j]; + + if ( pShape->checkOverlapSphere( impulseSphere ) ) + { + hitActors.push_back( pActor ); + break; + } + } + } + + // Apply forces to hit actors, but swap out for broken + // actors first if appropriate... + for ( U32 i = 0; i < hitActors.size(); i++ ) + { + NxActor *pActor = hitActors[i]; + + PxUserData *pUserData = PxUserData::getData( *pActor ); + + // TODO: We should calculate the real force accounting + // for falloff before we break things with it. + + // If we have enough force, and this is an actor that + // can be 'broken' by impacts, break it now. + if ( pUserData && + pUserData->mCanPush && + pUserData->mBrokenActors.size() > 0 && + magnitude > mDataBlock->breakForce ) + { + setBroken( pActor->getGlobalPose(), + pActor->getLinearVelocity(), + pUserData, + true ); + + // apply force that would have been applied to this actor + // to the broken actors we just enabled. + + for ( U32 j = 0; j < pUserData->mBrokenActors.size(); j++ ) + { + NxActor *pBrokenActor = pUserData->mBrokenActors[j]; + _applyActorRadialForce( pBrokenActor, nxorigin, radius, magnitude ); + } + } + else + { + // Apply force to the actor. + _applyActorRadialForce( pActor, nxorigin, radius, magnitude ); + } + } +} + +void PxMultiActor::_applyActorRadialForce( NxActor *inActor, const NxVec3 &origin, F32 radius, F32 magnitude ) +{ + // TODO: We're not getting a good torque force + // out of explosions because we're not picking + // the nearest point on the actor to the origin + // of the radial force. + + NxVec3 force = inActor->getCMassGlobalPosition() - origin; + NxF32 dist = force.magnitude(); + force.normalize(); + force *= ( dist / radius ) * magnitude; + + // HACK: Make the position we push the force thru between the + // actor pos and its center of mass. This gives us some + // rotational force as well as make the covered structure + // explode better. + NxVec3 forcePos = ( inActor->getGlobalPosition() + inActor->getCMassGlobalPosition() ) / 2.0f; + inActor->addForceAtPos( force, forcePos, NX_VELOCITY_CHANGE ); +} + +void PxMultiActor::_updateContainerForces() +{ + if ( !mWorld->getEnabled() ) + return; + + PROFILE_SCOPE( PxMultiActor_updateContainerForces ); + + // Update container drag and buoyancy properties ( for each Actor ) + + for ( U32 i = 0; i < mActors.size(); i++ ) + { + NxActor *pActor = mActors[i]; + + if ( !pActor || + pActor->readBodyFlag(NX_BF_KINEMATIC) || + pActor->readActorFlag(NX_AF_DISABLE_COLLISION) ) + continue; + + // Get world bounds of this actor ( the combination of all shape bounds ) + NxShape *const* shapes = pActor->getShapes(); + NxBounds3 bounds; + bounds.setEmpty(); + NxBounds3 shapeBounds; + + for ( U32 i = 0; i < pActor->getNbShapes(); i++ ) + { + NxShape *pShape = shapes[i]; + pShape->getWorldBounds(shapeBounds); + + bounds.combine( shapeBounds ); + } + + Box3F boundsBox = pxCast(bounds); + + ContainerQueryInfo info; + info.box = boundsBox; + info.mass = pActor->getMass(); + + // Find and retreive physics info from intersecting WaterObject(s) + mContainer->findObjects( boundsBox, WaterObjectType|PhysicalZoneObjectType, findRouter, &info ); + + // Calculate buoyancy and drag + F32 angDrag = mDataBlock->angularDrag; + F32 linDrag = mDataBlock->linearDrag; + F32 buoyancy = 0.0f; + + if ( true ) //info.waterCoverage >= 0.1f) + { + F32 waterDragScale = info.waterViscosity * mDataBlock->waterDragScale; + F32 powCoverage = mPow( info.waterCoverage, 0.25f ); + + if ( info.waterCoverage > 0.0f ) + { + //angDrag = mBuildAngDrag * waterDragScale; + //linDrag = mBuildLinDrag * waterDragScale; + angDrag = mLerp( angDrag, angDrag * waterDragScale, powCoverage ); + linDrag = mLerp( linDrag, linDrag * waterDragScale, powCoverage ); + } + + buoyancy = ( info.waterDensity / mDataBlock->buoyancyDensity ) * mPow( info.waterCoverage, 2.0f ); + } + + // Apply drag (dampening) + pActor->setLinearDamping( linDrag ); + pActor->setAngularDamping( angDrag ); + + // Apply buoyancy force + if ( buoyancy != 0 ) + { + // A little hackery to prevent oscillation + // Based on this blog post: + // (http://reinot.blogspot.com/2005/11/oh-yes-they-float-georgie-they-all.html) + // JCF: disabled! + NxVec3 gravity; + mWorld->getScene()->getGravity(gravity); + //NxVec3 velocity = pActor->getLinearVelocity(); + + NxVec3 buoyancyForce = buoyancy * -gravity * TickSec * pActor->getMass(); + //F32 currHeight = getPosition().z; + //const F32 C = 2.0f; + //const F32 M = 0.1f; + + //if ( currHeight + velocity.z * TickSec * C > info.waterHeight ) + // buoyancyForce *= M; + + pActor->addForceAtPos( buoyancyForce, pActor->getCMassGlobalPosition(), NX_IMPULSE ); + } + + // Apply physical zone forces + if ( info.appliedForce.len() > 0.001f ) + pActor->addForceAtPos( pxCast(info.appliedForce), pActor->getCMassGlobalPosition(), NX_IMPULSE ); + } +} + +/* +ConsoleMethod( PxMultiActor, applyImpulse, void, 3, 3, "applyImpulse - takes a velocity vector to apply") +{ + VectorF vec; + dSscanf( argv[2],"%g %g %g", + &vec.x,&vec.y,&vec.z ); + + object->applyImpulse( vec ); +} +*/ + +void PxMultiActor::setAllBroken( bool isBroken ) +{ + PROFILE_SCOPE( PxMultiActor_SetAllBroken ); + + if ( mIsDummy ) + { + PxMultiActor *serverObj = static_cast( mServerObject.getObject() ); + serverObj->setAllBroken( isBroken ); + return; + } + + mWorld->releaseWriteLock(); + + NxActor *actor0 = NULL; + NxActor *actor1 = NULL; + NxMat34 parentPose; + + for ( U32 i = 0; i < mJoints.size(); i++ ) + { + NxJoint *joint = mJoints[i]; + if ( !joint ) + continue; + + PxJointUserData *jointData = PxJointUserData::getData( *joint ); + if ( !jointData ) + continue; + + joint->getActors( &actor0, &actor1 ); + parentPose = actor0->getGlobalPose(); + + setBroken( parentPose, NxVec3(0.0f), jointData, isBroken ); + } + + for ( U32 i = 0; i < mActors.size(); i++ ) + { + NxActor *actor = mActors[i]; + if ( !actor ) + continue; + + PxUserData *actorData = PxUserData::getData( *actor ); + if ( !actorData ) + continue; + + setBroken( actor->getGlobalPose(), + actor->getLinearVelocity(), + actorData, + isBroken ); + } +} + +void PxMultiActor::setBroken( const NxMat34 &parentPose, + const NxVec3 &parentVel, + PxUserData *userData, + bool isBroken ) +{ + PROFILE_SCOPE( PxMultiActor_SetBroken ); + + // TODO: This function is highly inefficent and + // way too complex to follow... the hacked single + // player mode doesn't help. + + // Be careful not to set something broken twice. + if ( isBroken && + userData->mIsBroken == isBroken ) + return; + + userData->mIsBroken = isBroken; + + Vector *hideActors = NULL; + Vector *showActors = NULL; + + if ( isBroken ) + { + hideActors = &userData->mUnbrokenActors; + showActors = &userData->mBrokenActors; + } + else + { + hideActors = &userData->mBrokenActors; + showActors = &userData->mUnbrokenActors; + } + + NxActor *pActor = NULL; + MatrixF tMat; + for ( U32 i = 0; i < hideActors->size(); i++ ) + { + pActor = (*hideActors)[i]; + + pActor->raiseActorFlag( NX_AF_DISABLE_COLLISION ); + pActor->raiseBodyFlag( NX_BF_KINEMATIC ); + pActor->putToSleep(); + + NxShape *const* pShapeArray = pActor->getShapes(); + U32 shapeCount = pActor->getNbShapes(); + for ( U32 i = 0; i < shapeCount; i++ ) + pShapeArray[i]->setFlag( NX_SF_DISABLE_RAYCASTING, true ); + + setMeshHidden( _getMeshName( pActor ), true ); + } + + // Get the client side delta array. + Vector *actorDeltas = NULL; + if ( isClientObject() ) + actorDeltas = &mActorDeltas; + else if ( isServerObject() && gPhysicsPlugin->isSinglePlayer() ) + { + PxMultiActor *clientObj = static_cast( getClientObject() ); + if ( clientObj ) + actorDeltas = &clientObj->mActorDeltas; + } + U32 index; + + for ( U32 i = 0; i < showActors->size(); i++ ) + { + pActor = (*showActors)[i]; + + if ( showActors == &userData->mBrokenActors ) + { + NxMat34 pose; + pose.multiply( parentPose, userData->mRelXfm[i] ); + pActor->setGlobalPose( pose ); + + if ( actorDeltas ) + { + for ( U32 j=0; j < mMappedActors.size(); j++ ) + { + if ( mMappedActors[j] == pActor ) + { + index = mMappedToActorIndex[j]; + + // Reset the delta. + Delta &delta = (*actorDeltas)[index]; + delta.pos = pxCast( pose.t ); + pose.getRowMajor44( tMat ); + delta.rot.set( tMat ); + delta.lastPos = delta.pos; + delta.lastRot = delta.rot; + + break; + } + } + } + } + + pActor->clearActorFlag( NX_AF_DISABLE_COLLISION ); + pActor->clearBodyFlag( NX_BF_KINEMATIC ); + pActor->setLinearVelocity( parentVel ); + pActor->wakeUp(); + + NxShape *const* pShapeArray = pActor->getShapes(); + U32 shapeCount = pActor->getNbShapes(); + for ( U32 i = 0; i < shapeCount; i++ ) + pShapeArray[i]->setFlag( NX_SF_DISABLE_RAYCASTING, false ); + + setMeshHidden( _getMeshName(pActor), false ); + } +} + +void PxMultiActor::setAllHidden( bool hide ) +{ + for ( U32 i = 0; i < mShapeInstance->mMeshObjects.size(); i++ ) + mShapeInstance->setMeshForceHidden( i, hide ); +} + +ConsoleMethod( PxMultiActor, setAllHidden, void, 3, 3, "( bool )" ) +{ + object->setAllHidden( dAtob(argv[2]) ); +} + +void PxMultiActor::setMeshHidden( String namePrefix, bool hidden ) +{ + if ( isServerObject() && gPhysicsPlugin->isSinglePlayer() ) + { + PxMultiActor *clientObj = static_cast( getClientObject() ); + if ( clientObj ) + clientObj->setMeshHidden( namePrefix, hidden ); + } + + for ( U32 i = 0; i < mShapeInstance->mMeshObjects.size(); i++ ) + { + String meshName = mShapeInstance->getShape()->getMeshName( i ); + + if ( meshName.find( namePrefix ) != String::NPos ) + { + mShapeInstance->setMeshForceHidden( i, hidden ); + return; + } + } + + Con::warnf( "PxMultiActor::setMeshHidden - could not find mesh containing substring (%s)", namePrefix.c_str() ); +} + +ConsoleMethod( PxMultiActor, setBroken, void, 3, 3, "( bool )" ) +{ + object->setAllBroken( dAtob( argv[2] ) ); +} + +void PxMultiActorData::dumpModel() +{ + TSShapeInstance *inst = new TSShapeInstance( shape, true ); + + String path = Platform::getMainDotCsDir(); + path += "/model.dump"; + + FileStream *st; + if((st = FileStream::createAndOpen( path, Torque::FS::File::Write )) != NULL) + { + if ( inst ) + inst->dump( *st ); + else + Con::errorf( "PxMultiActor::dumpModel, no ShapeInstance." ); + + delete st; + } + else + Con::errorf( "PxMultiActor::dumpModel, error opening dump file." ); +} + +ConsoleMethod( PxMultiActorData, dumpModel, void, 2, 2, "" ) +{ + object->dumpModel(); +} + +ConsoleMethod( PxMultiActor, setMeshHidden, void, 4, 4, "(string meshName, bool isHidden)" ) +{ + object->setMeshHidden( argv[2], dAtob( argv[3] ) ); +} + +void PxMultiActor::listMeshes( const String &state ) const +{ + if ( mShapeInstance ) + mShapeInstance->listMeshes( state ); +} + +ConsoleMethod( PxMultiActor, listMeshes, void, 3, 3, "(enum Hidden/Shown/All)" ) +{ + object->listMeshes( argv[2] ); +}; + +ConsoleMethod( PxMultiActorData, reload, void, 2, 2, "" ) +{ + object->reload(); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxMultiActor.h b/T3D/physics/physx/pxMultiActor.h new file mode 100644 index 0000000..22d4279 --- /dev/null +++ b/T3D/physics/physx/pxMultiActor.h @@ -0,0 +1,333 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXMULTIACTOR_H +#define _PXMULTIACTOR_H + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _T3D_PHYSICS_PHYSICSPLUGIN_H_ +#include "T3D/physics/physicsPlugin.h" +#endif +#ifndef _PHYSX_H_ +#include "T3D/physics/physx/px.h" +#endif +#ifndef _PXUSERDATA_H_ +#include "T3D/physics/physx/pxUserData.h" +#endif +#ifndef _STRINGUNIT_H_ +#include "core/strings/stringUnit.h" +#endif + +class TSShape; +class TSShapeInstance; +class PxMultiActor; +class PxWorld; +class PxMaterial; +class NxScene; +class NxActor; +class NxShape; +class NxCompartment; +class NxJoint; +class NxMat34; +class NxVec3; + +namespace NXU +{ + class NxuPhysicsCollection; +} + +class ParticleEmitterData; + +class PxMultiActorData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + + PxMultiActorData(); + virtual ~PxMultiActorData(); + + DECLARE_CONOBJECT(PxMultiActorData); + + static void initPersistFields(); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + bool preload( bool server, String &errorBuffer ); + //bool onAdd(); + + void allocPrimBuffer( S32 overrideSize = -1 ); + + bool _loadCollection( const UTF8 *path, bool isBinary ); + + void _onFileChanged( const Torque::Path &path ); + + void reload(); + + void dumpModel(); + + Signal mReloadSignal; + +public: + + // Rendering + StringTableEntry shapeName; + Resource shape; + + PxMaterial *material; + + /// Filename to load the physics actor from. + StringTableEntry physXStream; + + enum + { + NumMountPoints = 32, + MaxCorrectionNodes = 2 + }; + + StringTableEntry correctionNodeNames[MaxCorrectionNodes]; + S32 correctionNodes[MaxCorrectionNodes]; + S32 mountPointNode[NumMountPoints]; ///< Node index of mountPoint + + /// If true no network corrections will + /// be done during gameplay. + bool noCorrection; + + /// Physics collection that holds the actor + /// and all associated shapes and data. + NXU::NxuPhysicsCollection *collection; + + bool createActors( NxScene *scene, + NxCompartment *compartment, + const NxMat34 *nxMat, + const Point3F& scale, + Vector *outActors, + Vector *outShapes, + Vector *outJoints, + Vector *outActorUserProperties, + Vector *outJointUserProperties ); + + /// Angular and Linear Drag (dampening) is scaled by this when in water. + F32 waterDragScale; + + /// The density of this object (for purposes of buoyancy calculation only). + F32 buoyancyDensity; + + F32 angularDrag; + F32 linearDrag; + + /// If this flag is set to true, + /// the physics actors will only be + /// created on the client, and the server + /// object is only responsible for ghosting. + /// Objects with this flag set will never stop + /// the physics player from moving through them. + bool clientOnly; + + bool singlePlayerOnly; + + /// When applyImpulse is passed a force of this magnitude or greater + /// any actors hit by the force vector that have broken versions + /// will become 'broken'. + F32 breakForce; +}; + +DECLARE_CONSOLETYPE( PxMultiActorData ) + + +class PxMultiActor : public GameBase +{ + typedef GameBase Parent; + + enum MaskBits + { + MoveMask = Parent::NextFreeMask << 0, + WarpMask = Parent::NextFreeMask << 1, + LightMask = Parent::NextFreeMask << 2, + SleepMask = Parent::NextFreeMask << 3, + ForceSleepMask = Parent::NextFreeMask << 4, + ImpulseMask = Parent::NextFreeMask << 5, + UpdateMask = Parent::NextFreeMask << 6, + MountedMask = Parent::NextFreeMask << 7, + NextFreeMask = Parent::NextFreeMask << 8 + }; + +public: + + PxMultiActor(); + + DECLARE_CONOBJECT( PxMultiActor ); + static void initPersistFields(); + + // SimObject + bool onAdd(); + void onRemove(); + void inspectPostApply(); + void onPhysicsReset( PhysicsResetEvent reset ); + void onStaticModified( const char *slotName, const char *newValue ); + void onDeleteNotify( SimObject *obj ); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + bool prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + void setScale( const VectorF &scale ); + void setTransform( const MatrixF &mat ); + virtual void mountObject( SceneObject *obj, U32 node ); + virtual void unmountObject( SceneObject *obj ); + virtual void getMountTransform( U32 mountPoint, MatrixF *mat ); + virtual void getRenderMountTransform( U32 index, MatrixF *mat ); + + // GameBase + virtual bool onNewDataBlock( GameBaseData *dptr ); + virtual void processTick( const Move *move ); + virtual void interpolateTick( F32 delta ); + virtual void applyImpulse( const Point3F &pos, const VectorF &vec ); + virtual void applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude ); + + /// PxMultiActor + /// @{ + + /// Set visibility of all broken/unbroken meshes to match this state. + void setAllBroken( bool isBroken ); + + /// Sets up actors and meshes associated with the passed joint to reflect + /// the desired state. + void setBroken( const NxMat34 &parentPose, + const NxVec3 &parentVel, + PxUserData *userData, + bool isBroken ); + + /// + void setMeshHidden( String namePrefix, bool hidden ); + + void setAllHidden( bool hide ); + + void listMeshes( const String &state ) const; + + void _onJointBreak( NxReal breakForce, NxJoint &brokenJoint ); + + void _onContact( NxActor *ourActor, + NxActor *hitActor, + SceneObject *hitObject, + const Point3F &hitPoint, + const Point3F &hitForce ); + + void applyWarp( const MatrixF& mat, bool interpRender, bool sweep ); + + void getDynamicXfms( PxMultiActor *srcObj, F32 dt ); + + /// @} + +protected: + + /// This creates the physics objects. + bool _createActors( const MatrixF &xfm ); + + /// Creates a PxJointUserData and parses userProperties into it. + PxJointUserData* _createJointUserData( NxJoint *joint, String &userProperties ); + + /// Creates a PxActorUserData and parses userProperties into it. + PxUserData* _createActorUserData( NxActor *actor, String &userProperties ); + + /// Called to cleanup the physics objects. + void _destroyActors(); + + NxActor* _findActor( const String &actorName ) const; + + /// Get the corresponding meshName for a given actor. + String _getMeshName( const NxActor *actor ) const; + + /// + void _updateBounds(); + + void _updateContainerForces(); + + void _debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + void onFileNotify(); + + void _applyActorRadialForce( NxActor *inActor, const NxVec3 &origin, F32 radius, F32 magnitude ); + + void _updateDeltas( bool clearDelta ); + + bool _getNodeTransform( U32 nodeIdx, MatrixF *outXfm ); + +protected: + + PxMultiActorData *mDataBlock; + + PxWorld *mWorld; + + Vector mActors; + Vector mMappedActors; + Vector mMappedToActorIndex; + Vector mMappedActorDL; + Vector mJoints; + Vector mShapes; + + /// This is the root actor whose transform is the + /// transform of this SceneObject. + NxActor *mRootActor; + + TSShapeInstance *mShapeInstance; + Resource< TSShape > mDebrisShape; + + struct Delta + { + Point3F pos; + Point3F lastPos; + QuatF rot; + QuatF lastRot; + }; + + Delta mDelta; + + Vector mActorDeltas; + + /// The transform of this actor when it was first + /// created. It is used to reset the physics state + /// when the editor is enabled. + MatrixF mResetXfm; + + + /// The userdata object assigned to all actors + /// and joints of this multi-actor. + //PxUserData mUserData; + + /// + //Vector mRelXfms; + + /// This is the scale the actors were built at and + /// is used to decide if we need to recreate them. + VectorF mActorScale; + //F32 mBuildAngDrag; + //F32 mBuildLinDrag; + + VectorF mStartImpulse; + + bool mDebugRender; + + /// A helper set to true if is a client object and + /// is a singlePlayerOnly object. + bool mIsDummy; + + /// Helper for + bool mBroken; +}; + +#endif // _PXMULTIACTOR_H + diff --git a/T3D/physics/physx/pxPlane.cpp b/T3D/physics/physx/pxPlane.cpp new file mode 100644 index 0000000..d8d8a6b --- /dev/null +++ b/T3D/physics/physx/pxPlane.cpp @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxPlane.h" + +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxWorld.h" + +#include "T3D/groundPlane.h" + +PxPlane::PxPlane() : + mActor( NULL ), + mWorld( NULL ) +{ +} + +PxPlane::~PxPlane() +{ + _releaseActor(); +} + +void PxPlane::_releaseActor() +{ + if ( !mWorld ) + return; + + if ( mActor ) + mWorld->releaseActor( *mActor ); + + mWorld = NULL; + mActor = NULL; +} + +bool PxPlane::init( GroundPlane *plane, PxWorld *world ) +{ + // Note: PhysX plane shapes DO NOT work with + // character controllers! Thus a gigantic box + // is used instead. + + NxActorDesc actorDesc; + NxBoxShapeDesc boxDesc; + boxDesc.dimensions = NxVec3( 20000.0f, 20000.0f, 100.0f); + actorDesc.shapes.pushBack( &boxDesc ); + actorDesc.body = NULL; + actorDesc.globalPose.id(); + actorDesc.globalPose.t = NxVec3( 0, 0, -100.0f ); + + NxScene *scene = world->getScene(); + if ( !scene ) + return false; + + mUserData.setObject( plane ); + actorDesc.userData = &mUserData; + + mWorld = world; + mActor = scene->createActor( actorDesc ); + + return true; +} + +PxPlane* PxPlane::create( GroundPlane *groundPlane, PxWorld *world ) +{ + AssertFatal( world, "PxPlane::create() - No world found!" ); + + PxPlane *plane = new PxPlane(); + + if ( !plane->init( groundPlane, world ) ) + { + delete plane; + return NULL; + } + + return plane; +} + +void PxPlane::setTransform( const MatrixF &xfm ) +{ + // GroundPlane cannot be transformed, + // so we don't do any work here. +} + +void PxPlane::setScale( const Point3F &scale ) +{ +} \ No newline at end of file diff --git a/T3D/physics/physx/pxPlane.h b/T3D/physics/physx/pxPlane.h new file mode 100644 index 0000000..703e684 --- /dev/null +++ b/T3D/physics/physx/pxPlane.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PXPLANE_H_ +#define _T3D_PHYSICS_PXPLANE_H_ + +#ifndef _T3D_PHYSICS_PHYSICSSTATIC_H_ +#include "T3D/physics/physicsStatic.h" +#endif +#ifndef _PXUSERDATA_H_ +#include "T3D/physics/physx/pxUserData.h" +#endif + +class NxActor; +class PxWorld; +class GroundPlane; + +class PxPlane : public PhysicsStatic +{ + +protected: + + PxWorld *mWorld; + NxActor *mActor; + + /// The userdata object assigned to our actor. + PxUserData mUserData; + + void _releaseActor(); + + PxPlane(); + +public: + + virtual ~PxPlane(); + + // PhysicsStatic + void setTransform( const MatrixF &xfm ); + void setScale( const Point3F &scale ); + + bool init( GroundPlane *groundPlane, PxWorld *world ); + + static PxPlane* create( GroundPlane *plane, PxWorld *world ); + +}; + +#endif // _T3D_PHYSICS_PXPLANE_H_ diff --git a/T3D/physics/physx/pxPlayer.cpp b/T3D/physics/physx/pxPlayer.cpp new file mode 100644 index 0000000..6dd89f5 --- /dev/null +++ b/T3D/physics/physx/pxPlayer.cpp @@ -0,0 +1,328 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxPlayer.h" + +#include "T3D/physics/physX/pxCapsulePlayer.h" +#include "T3D/physics/physX/pxBoxPlayer.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxUserData.h" +#include "T3D/physics/physX/pxCasts.h" +#include "T3D/physics/physX/pxSingleActor.h" +#include "T3D/physics/physicsStatic.h" +#include "gfx/gfxDrawUtil.h" +#include "sim/netConnection.h" +#include "collision/collision.h" +#include "T3D/player.h" + +#include +#include +#include +#include + + +PxPlayer::PxPlayer( Player *player, PxWorld *world ) + : PhysicsPlayer( player ), + mController( NULL ), + mSkinWidth( 0.1f ), + mSize(1,1,1), + mWorld( world ), + mDummyMove( false ), + mPositionSaved( false ), + mSavedServerPosition( 0,0,0 ) +{ + PhysicsStatic::smDeleteSignal.notify( this, &PxPlayer::_onStaticDeleted ); +} + +PxPlayer::~PxPlayer() +{ + // If a single player (dummy) PxPlayer we might not have a controller. + if ( mController ) + mWorld->releaseController( *mController ); + + // JCFHACK: see PxPlayer::create + if ( mSinglePlayer && mServerObject ) + { + gClientProcessList.preTickSignal().remove( this, &PxPlayer::_savePosition ); + gServerProcessList.preTickSignal().remove( this, &PxPlayer::_restorePosition ); + } + + PhysicsStatic::smDeleteSignal.remove( this, &PxPlayer::_onStaticDeleted ); +} + +PxPlayer* PxPlayer::create( Player *player, PxWorld *world ) +{ + // Determine type of CharacterController to create... + + const char *typeStr = static_cast( player->getDataBlock() )->physicsPlayerType; + PxPlayer *pxPlayer = NULL; + + if ( dStricmp( typeStr, "None" ) == 0 ) + return pxPlayer; + + if ( gPhysicsPlugin->isSinglePlayer() && player->isClientObject() ) + { + pxPlayer = new PxPlayer( player, world ); + pxPlayer->mSinglePlayer = true; + pxPlayer->mServerObject = false; + + return pxPlayer; + } + + if ( dStricmp( typeStr, "Capsule" ) == 0 ) + pxPlayer = new PxCapsulePlayer( player, world ); + else + pxPlayer = new PxBoxPlayer( player, world ); + + pxPlayer->mSinglePlayer = gPhysicsPlugin->isSinglePlayer(); + pxPlayer->mServerObject = player->isServerObject(); + + if ( gPhysicsPlugin->isSinglePlayer() && player->isServerObject() ) + { + // JCFHACK: The server pxPlayer saves its position (if it hasn't already) + // when the client begins a tick (preTickSignal), because the client will + // reuse the server object for calculating its moved position. Then the server + // pxPlayer will restore its saved position before it begins processing + // ITS tick ( ServerProcessList preTickSignal ). + gClientProcessList.preTickSignal().notify( pxPlayer, &PxPlayer::_savePosition ); + gServerProcessList.preTickSignal().notify( pxPlayer, &PxPlayer::_restorePosition ); + } + + return pxPlayer; +} + +Point3F PxPlayer::move( const VectorF &displacement, Collision *outCol ) +{ + if ( !isSinglePlayer() ) + return _move( displacement, outCol ); + + if ( isClientObject() ) + { + PxPlayer *sister = getServerObj(); + if ( !sister ) + return mPlayer->getPosition(); + + sister->mDummyMove = true; + Point3F endPos = sister->_move( displacement, outCol ); + sister->mDummyMove = false; + + return endPos; + + //Player *serverPlayer = NetConnection::getServerObject( mPlayer ); + //return serverPlayer->getPosition(); + } + else + { + return _move( displacement, outCol ); + } +} + +Point3F PxPlayer::_move( const VectorF &displacement, Collision *outCol ) +{ + if ( mWorld ) + mWorld->releaseWriteLock(); + + mLastCollision = outCol; + + NxVec3 dispNx( displacement.x, displacement.y, displacement.z ); + NxU32 activeGroups = 0xFFFFFFFF; + NxU32 collisionFlags = NXCC_COLLISION_SIDES | NXCC_COLLISION_DOWN | NXCC_COLLISION_UP; + + mController->move( dispNx, activeGroups, 0.0001f, collisionFlags ); + + Point3F newPos = pxCast( mController->getDebugPosition() ); + newPos.z -= mSize.z * 0.5f; + + mLastCollision = NULL; + + return newPos; +} + +void PxPlayer::setPosition( const MatrixF &mat ) +{ + // No physics objects to set position on. + if ( isSinglePlayer() && isClientObject() ) + return; + + if ( mWorld ) + mWorld->releaseWriteLock(); + + Point3F newPos = mat.getPosition(); + newPos.z += mSize.z * 0.5f; + + const Point3F &curPos = pxCast(mController->getDebugPosition()); + + if ( !(newPos - curPos ).isZero() ) + mController->setPosition( pxCast(newPos) ); +} + +void PxPlayer::findContact( SceneObject **contactObject, VectorF *contactNormal ) const +{ + if ( isSinglePlayer() && isClientObject() ) + { + // In single player the client actually calls findContact on the + // serverObject. + PxPlayer *sister = getServerObj(); + if ( sister ) + sister->_findContact( contactObject, contactNormal ); + } + else + _findContact( contactObject, contactNormal ); +} + +void PxPlayer::renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + // Dummies render the server side PhysicsPlayer. + if ( isSinglePlayer() && isClientObject() ) + { + PhysicsPlayer *serverPhysicsPlayer = getServerObj(); + if ( serverPhysicsPlayer ) + serverPhysicsPlayer->renderDebug( ri, state, overrideMat ); + } +} + +NxControllerAction PxPlayer::onShapeHit( const NxControllerShapeHit& hit ) +{ + // Shouldn't be called anyway since dummies have no shapes. + if ( isSinglePlayer() && isClientObject() ) + return NX_ACTION_NONE; + + NxActor *controllerActor = mController->getActor(); + // TODO: Sometimes the shape or + // actor is NULL? Why does this happen? + //if ( !hit.shape ) + //return NX_ACTION_NONE; + + NxActor *actor = &hit.shape->getActor(); + PxUserData *userData = PxUserData::getData( *actor ); + + // Fill out the Collision + // structure for use later. + if ( mLastCollision ) + { + mLastCollision->normal = pxCast( hit.worldNormal ); + mLastCollision->point.set( hit.worldPos.x, hit.worldPos.y, hit.worldPos.z ); + mLastCollision->distance = hit.length; + if ( userData ) + mLastCollision->object = userData->getObject(); + } + + if ( userData && + userData->mCanPush && + actor->isDynamic() && + !actor->readBodyFlag( NX_BF_KINEMATIC ) && + !mDummyMove ) + { + // So the object is neither + // a static or a kinematic, + // meaning we need to figure out + // if we have enough force to push it. + + // Get the hit object's force + // and scale it by the amount + // that it's acceleration is going + // against our acceleration. + const Point3F &hitObjLinVel = pxCast( actor->getLinearVelocity() ); + + F32 hitObjMass = actor->getMass(); + + VectorF hitObjDeltaVel = hitObjLinVel * TickSec; + VectorF hitObjAccel = hitObjDeltaVel / TickSec; + + VectorF controllerLinVel = pxCast( controllerActor->getLinearVelocity() ); + VectorF controllerDeltaVel = controllerLinVel * TickSec; + VectorF controllerAccel = controllerDeltaVel / TickSec; + + Point3F hitObjForce = (hitObjMass * hitObjAccel); + Point3F playerForce = (controllerActor->getMass() * controllerAccel); + + VectorF normalizedObjVel( hitObjLinVel ); + normalizedObjVel.normalizeSafe(); + + VectorF normalizedPlayerVel( pxCast( controllerActor->getLinearVelocity() ) ); + normalizedPlayerVel.normalizeSafe(); + + F32 forceDot = mDot( normalizedObjVel, normalizedPlayerVel ); + + hitObjForce *= forceDot; + + playerForce = playerForce - hitObjForce; + + if ( playerForce.x > 0.0f || playerForce.y > 0.0f || playerForce.z > 0.0f ) + actor->addForceAtPos( NxVec3( playerForce.x, playerForce.y, playerForce.z ), actor->getCMassGlobalPosition() ); + + //Con::printf( "onShapeHit: %f %f %f", playerForce.x, playerForce.y, playerForce.z ); + } + + //if ( actor->getGroup() == PxGroup_ClientOnly ) + return NX_ACTION_PUSH; + //else + // return NX_ACTION_NONE; +} + +NxControllerAction PxPlayer::onControllerHit( const NxControllersHit& hit ) +{ + return NX_ACTION_NONE; +} + +PxPlayer* PxPlayer::getServerObj() const +{ + Player *sister = static_cast( mPlayer->getServerObject() ); + if ( !sister ) + return NULL; + + return static_cast( sister->getPhysicsPlayer() ); +} + +PxPlayer* PxPlayer::getClientObj() const +{ + Player *sister = static_cast( mPlayer->getClientObject() ); + if ( !sister ) + return NULL; + + return static_cast( sister->getPhysicsPlayer() ); +} + +void PxPlayer::_restorePosition() +{ + // JCFHACK: see PxPlayer::create + if ( mPositionSaved ) + { + mController->setPosition( mSavedServerPosition ); + mPositionSaved = false; + } +} + +void PxPlayer::_savePosition() +{ + // JCFHACK: see PxPlayer::create + if ( mPositionSaved ) + return; + + mSavedServerPosition = mController->getDebugPosition(); + mPositionSaved = true; +} + +void PxPlayer::_onStaticDeleted() +{ + if ( mController ) + mController->reportSceneChanged(); +} + +void PxPlayer::enableCollision() +{ + if ( !mController ) + return; + mController->setCollision( true ); +} + +void PxPlayer::disableCollision() +{ + if ( !mController ) + return; + mController->setCollision( false ); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxPlayer.h b/T3D/physics/physx/pxPlayer.h new file mode 100644 index 0000000..34b1b9c --- /dev/null +++ b/T3D/physics/physx/pxPlayer.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXPLAYER_H +#define _PXPLAYER_H + +#ifndef _PHYSX_H_ +#include "T3D/physics/physX/px.h" +#endif +#ifndef _T3D_PHYSICS_PHYSICSPLAYER_H_ +#include "T3D/physics/physicsPlayer.h" +#endif +#ifndef _PXUSERDATA_H_ +#include "T3D/physics/physx/pxUserData.h" +#endif +#ifndef NX_PHYSICS_NXCONTROLLER +#include +#endif + +class Player; +class PxWorld; + + +class PxPlayer : public PhysicsPlayer, public NxUserControllerHitReport +{ + +protected: + + NxController *mController; + + F32 mSkinWidth; + + Point3F mSize; + + PxWorld *mWorld; + + /// The userdata object assigned to the controller. + PxUserData mUserData; + + /// Helper flag for singleplayer hack. + bool mDummyMove; + + // NxUserControllerHitReport + virtual NxControllerAction onShapeHit( const NxControllerShapeHit& hit ); + virtual NxControllerAction onControllerHit( const NxControllersHit& hit ); + +public: + + PxPlayer( Player *player, PxWorld *world ); + virtual ~PxPlayer(); + + static PxPlayer* create( Player *player, PxWorld *world ); + + virtual Point3F move( const VectorF &displacement, Collision *outCol ); + + virtual void setPosition( const MatrixF &mat ); + + virtual void findContact( SceneObject **contactObject, VectorF *contactNormal ) const; + + virtual bool testSpacials( const Point3F &nPos, const Point3F &nSize ) const { return true; } + + virtual void setSpacials( const Point3F &nPos, const Point3F &nSize ) {} + + virtual void renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + inline PxPlayer* getServerObj() const; + inline PxPlayer* getClientObj() const; + + virtual void enableCollision(); + virtual void disableCollision(); + +protected: + + virtual Point3F _move( const VectorF &displacement, Collision *outCol ); + virtual void _findContact( SceneObject **contactObject, VectorF *contactNormal ) const {} + + //void _onPreTick(); + //void _onPostTick( U32 elapsed ); + void _savePosition(); + void _restorePosition(); + + // Because of the way NxController works we must notify it when + // static objects in the scene are deleted, otherwise we can get a crash! + void _onStaticDeleted(); + +private: + + NxExtendedVec3 mSavedServerPosition; + bool mPositionSaved; +}; + + +#endif // _PXPLAYER_H \ No newline at end of file diff --git a/T3D/physics/physx/pxPlugin.cpp b/T3D/physics/physx/pxPlugin.cpp new file mode 100644 index 0000000..0fd8669 --- /dev/null +++ b/T3D/physics/physx/pxPlugin.cpp @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxPlugin.h" + +#include "sim/netObject.h" +#include "T3D/tsStatic.h" +#include "terrain/terrData.h" +#include "T3D/player.h" +#include "environment/meshRoad.h" +#include "T3D/groundPlane.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxTSStatic.h" +#include "T3D/physics/physX/pxTerrain.h" +#include "T3D/physics/physX/pxPlayer.h" +#include "T3D/physics/physX/pxMeshRoad.h" +#include "T3D/physics/physX/pxPlane.h" +#include "T3D/gameProcess.h" + +// This is a global function pre-declared and externed +// in the base PhysicsPlugin class, but only +// defined in the derived plugin class's source file. +bool physicsInitialize() +{ + AssertFatal( gPhysicsPlugin == NULL, "PxPlugin() - Physics plugin already present!" ); + + // Only create the plugin if + // it hasn't been set up AND + // the PhysX world is successfully + // initialized. + bool success = PxWorld::restartSDK( false ); + + if ( !success ) + { + Con::errorf( "Plugin was already initialized!" ); + return false; + } + + gPhysicsPlugin = new PxPlugin(); + return true; +} + +bool physicsDestroy() +{ + PxWorld *clientWorld = static_cast( gPhysicsPlugin->getWorld( "client" ) ); + PxWorld *serverWorld = static_cast( gPhysicsPlugin->getWorld( "server" ) ); + + PxWorld::restartSDK( true, clientWorld, serverWorld ); + delete gPhysicsPlugin; + gPhysicsPlugin = NULL; + + return gPhysicsPlugin == NULL; +} + +PxPlugin::PxPlugin() +{ +} + +PxPlugin::~PxPlugin() +{ +} + +PhysicsStatic* PxPlugin::createStatic( NetObject *object ) +{ + // What sort of object is this? + TSStatic *tsStatic = dynamic_cast( object ); + TerrainBlock *terrainBlock = dynamic_cast( object ); + MeshRoad *meshRoad = dynamic_cast( object ); + GroundPlane *plane = dynamic_cast( object ); + + // Get the world to use. + PxWorld *world = static_cast( gPhysicsPlugin->getWorld( object->isServerObject() ? "server" : "client" ) ); + + // Now create the physics representation for it. + + if ( tsStatic ) + return PxTSStatic::create( tsStatic, world ); + else if ( terrainBlock ) + return PxTerrain::create( terrainBlock, world ); + else if ( meshRoad ) + return PxMeshRoad::create( meshRoad, world ); + else if ( plane ) + return PxPlane::create( plane, world ); + + return NULL; +} + +PhysicsPlayer* PxPlugin::createPlayer( Player *player ) +{ + // Get the world to use. + PxWorld *world = static_cast( gPhysicsPlugin->getWorld( player->isServerObject() ? "server" : "client" ) ); + return PxPlayer::create( player, world ); +} + +bool PxPlugin::isSimulationEnabled() const +{ + bool ret = false; + PxWorld *world = static_cast( getWorld( smClientWorldName ) ); + if ( world ) + { + ret = world->getEnabled(); + return ret; + } + + world = static_cast( getWorld( smServerWorldName ) ); + if ( world ) + { + ret = world->getEnabled(); + return ret; + } + + return ret; +} + +void PxPlugin::enableSimulation( const String &worldName, bool enable ) +{ + PxWorld *world = static_cast( getWorld( worldName ) ); + if ( world ) + world->setEnabled( enable ); +} + +void PxPlugin::setTimeScale( const F32 timeScale ) +{ + // Grab both the client and + // server worlds and set their time + // scales to the passed value. + PxWorld *world = static_cast( getWorld( smClientWorldName ) ); + if ( world ) + world->setEditorTimeScale( timeScale ); + + world = static_cast( getWorld( smServerWorldName ) ); + if ( world ) + world->setEditorTimeScale( timeScale ); +} + +const F32 PxPlugin::getTimeScale() const +{ + // Grab both the client and + // server worlds and call + // setEnabled( true ) on them. + PxWorld *world = static_cast( getWorld( smClientWorldName ) ); + if ( !world ) + { + world = static_cast( getWorld( smServerWorldName ) ); + if ( !world ) + return 0.0f; + } + + return world->getEditorTimeScale(); +} + +bool PxPlugin::createWorld( const String &worldName ) +{ + Map::Iterator iter = mPhysicsWorldLookup.find( worldName ); + PhysicsWorld *world = NULL; + + iter != mPhysicsWorldLookup.end() ? world = (*iter).value : world = NULL; + + if ( world ) + { + Con::errorf( "PhysXWorld::initWorld - %s world already exists!", worldName.c_str() ); + return false; + } + + world = new PxWorld(); + + if ( worldName.equal( smServerWorldName, String::NoCase ) ) + world->initWorld( true, &gServerProcessList ); + else if ( worldName.equal( smClientWorldName, String::NoCase ) ) + world->initWorld( false, &gClientProcessList ); + else + world->initWorld( true, &gServerProcessList ); + + mPhysicsWorldLookup.insert( worldName, world ); + + return world != NULL; +} + +void PxPlugin::destroyWorld( const String &worldName ) +{ + Map::Iterator iter = mPhysicsWorldLookup.find( worldName ); + if ( iter == mPhysicsWorldLookup.end() ) + return; + + PhysicsWorld *world = (*iter).value; + world->destroyWorld(); + delete world; + + mPhysicsWorldLookup.erase( iter ); +} + +PhysicsWorld* PxPlugin::getWorld( const String &worldName ) const +{ + if ( mPhysicsWorldLookup.isEmpty() ) + return NULL; + + Map::ConstIterator iter = mPhysicsWorldLookup.find( worldName ); + + return iter != mPhysicsWorldLookup.end() ? (*iter).value : NULL; +} + +PhysicsWorld* PxPlugin::getWorld() const +{ + if ( mPhysicsWorldLookup.size() == 0 ) + return NULL; + + Map::ConstIterator iter = mPhysicsWorldLookup.begin(); + return iter->value; +} + +U32 PxPlugin::getWorldCount() const +{ + return mPhysicsWorldLookup.size(); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxPlugin.h b/T3D/physics/physx/pxPlugin.h new file mode 100644 index 0000000..7dcdcd4 --- /dev/null +++ b/T3D/physics/physx/pxPlugin.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PXPLUGIN_H_ +#define _T3D_PHYSICS_PXPLUGIN_H_ + +#ifndef _T3D_PHYSICS_PHYSICSPLUGIN_H_ +#include "T3D/physics/physicsPlugin.h" +#endif + + +class PxPlugin : public PhysicsPlugin +{ + +public: + + PxPlugin(); + ~PxPlugin(); + + // PhysicsPlugin + virtual PhysicsStatic* createStatic( NetObject *object ); + virtual PhysicsPlayer* createPlayer( Player *player ); + + virtual bool isSimulationEnabled() const; + virtual void enableSimulation( const String &worldName, bool enable ); + + virtual void setTimeScale( const F32 timeScale ); + virtual const F32 getTimeScale() const; + + virtual bool createWorld( const String &worldName ); + virtual void destroyWorld( const String &worldName ); + + virtual PhysicsWorld* getWorld( const String &worldName ) const; + virtual PhysicsWorld* getWorld() const; + virtual U32 getWorldCount() const; +}; + +#endif // _T3D_PHYSICS_PXPLUGIN_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxSingleActor.cpp b/T3D/physics/physx/pxSingleActor.cpp new file mode 100644 index 0000000..b57955e --- /dev/null +++ b/T3D/physics/physx/pxSingleActor.cpp @@ -0,0 +1,1137 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxSingleActor.h" + +#include "lighting/lightManager.h" +#include "math/mathIO.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "gfx/gfxTransformSaver.h" +#include "collision/collision.h" +#include "collision/abstractPolyList.h" +#include "T3D/objectTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "core/stream/fileStream.h" +#include "core/stream/bitStream.h" +#include "core/resourceManager.h" +#include "T3D/containerQuery.h" +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxUserData.h" +#include "T3D/physics/physX/pxCasts.h" +#include "T3D/physics/physicsPlugin.h" + +#include +#include +#include + + +class PxSingleActor_Notify : public NXU_userNotify +{ +protected: + + NxActor *mActor; + + bool mFirstActor; + + const Point3F& mScale; + + +public: + + void NXU_notifyActor(NxActor *actor, const char *userProperties) + { + if ( mFirstActor ) + { + mActor = actor; + mFirstActor = false; + } + }; + + bool NXU_preNotifyActor(NxActorDesc &actor, const char *userProperties) + { + // PxSingleActor only cares about one actor, + // so skip work on anything after the first. + if ( !mFirstActor ) + return false; + + // For every shape, cast to its particular type + // and apply the scale to size, mass and localPosition. + for( S32 i = 0; i < actor.shapes.size(); i++ ) + { + switch( actor.shapes[i]->getType() ) + { + case NX_SHAPE_BOX: + { + NxBoxShapeDesc *boxDesc = (NxBoxShapeDesc*)actor.shapes[i]; + + if ( boxDesc->mass > 0.0f ) + boxDesc->mass *= mScale.x; + + boxDesc->dimensions.x *= mScale.x; + boxDesc->dimensions.y *= mScale.y; + boxDesc->dimensions.z *= mScale.z; + + boxDesc->localPose.t.x *= mScale.x; + boxDesc->localPose.t.y *= mScale.y; + boxDesc->localPose.t.z *= mScale.z; + break; + } + + case NX_SHAPE_SPHERE: + { + NxSphereShapeDesc *sphereDesc = (NxSphereShapeDesc*)actor.shapes[i]; + + if ( sphereDesc->mass > 0.0f ) + sphereDesc->mass *= mScale.x; + + sphereDesc->radius *= mScale.x; + + sphereDesc->localPose.t.x *= mScale.x; + sphereDesc->localPose.t.y *= mScale.y; + sphereDesc->localPose.t.z *= mScale.z; + break; + } + + case NX_SHAPE_CAPSULE: + { + NxCapsuleShapeDesc *capsuleDesc = (NxCapsuleShapeDesc*)actor.shapes[i]; + if ( capsuleDesc->mass > 0.0f ) + capsuleDesc->mass *= mScale.x; + capsuleDesc->radius *= mScale.x; + capsuleDesc->height *= mScale.y; + + capsuleDesc->localPose.t.x *= mScale.x; + capsuleDesc->localPose.t.y *= mScale.y; + capsuleDesc->localPose.t.z *= mScale.z; + break; + } + + default: + { + delete actor.shapes[i]; + actor.shapes.erase( actor.shapes.begin() + i ); + --i; + break; + } + } + } + + NxBodyDesc *body = const_cast( actor.body ); + body->mass *= mScale.x; + body->massLocalPose.t.multiply( mScale.x, body->massLocalPose.t ); + body->massSpaceInertia.multiply( mScale.x, body->massSpaceInertia ); + + return true; + }; + +public: + + PxSingleActor_Notify( const Point3F& scale ) + : mScale( scale ), + mFirstActor( true ) + { + } + + virtual ~PxSingleActor_Notify() + { + } + + NxActor* getActor() const { return mActor; }; + +}; + +IMPLEMENT_CO_DATABLOCK_V1(PxSingleActorData); + +PxSingleActorData::PxSingleActorData() +{ + shapeName = ""; + physXStream = ""; + waterDragScale = 1.0f; + forceThreshold = 1.0f; + clientOnly = false; + + clientOnly = false; + + physicsCollection = NULL; +} + +PxSingleActorData::~PxSingleActorData() +{ + if ( physicsCollection ) + NXU::releaseCollection( physicsCollection ); +} + +void PxSingleActorData::initPersistFields() +{ + Parent::initPersistFields(); + + + addGroup("Media"); + addField( "shapeName", TypeFilename, Offset( shapeName, PxSingleActorData ) ); + endGroup("Media"); + + // PhysX collision properties. + addGroup( "Physics" ); + addField( "physXStream", TypeFilename, Offset( physXStream, PxSingleActorData ) ); + addField( "waterDragScale", TypeF32, Offset( waterDragScale, PxSingleActorData ) ); + addField( "buoyancyDensity", TypeF32, Offset( buoyancyDensity, PxSingleActorData ) ); + addField( "forceThreshold", TypeF32, Offset( forceThreshold, PxSingleActorData ) ); + endGroup( "Physics" ); + + addField( "clientOnly", TypeBool, Offset( clientOnly, PxSingleActorData ) ); +} + +//---------------------------------------------------------------------------- + +void PxSingleActorData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString( shapeName ); + stream->writeString( physXStream ); + stream->write( forceThreshold ); + stream->write( waterDragScale ); + stream->writeFlag( clientOnly ); +} + +//---------------------------------------------------------------------------- + +void PxSingleActorData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + shapeName = stream->readSTString(); + physXStream = stream->readSTString(); + stream->read( &forceThreshold ); + stream->read( &waterDragScale ); + clientOnly = stream->readFlag(); +} + +bool PxSingleActorData::preload( bool server, String &errorBuffer ) +{ + if ( !Parent::preload( server, errorBuffer ) ) + return false; + + // If the stream is null, exit. + if ( !physXStream || !physXStream[0] ) + { + errorBuffer = "PxSingleActorData::preload: physXStream is unset!"; + return false; + } + + // Set up our buffer for the binary stream filename path. + UTF8 binPhysXStream[260] = { 0 }; + const UTF8* ext = dStrrchr( physXStream, '.' ); + + // Copy the xml stream path except for the extension. + if ( ext ) + dStrncpy( binPhysXStream, physXStream, getMin( 260, ext - physXStream ) ); + else + dStrncpy( binPhysXStream, physXStream, 260 ); + + // Concatenate the binary extension. + dStrcat( binPhysXStream, ".nxb" ); + + // Get the modified times of the two files. + FileTime xmlTime = {0}, binTime = {0}; + Platform::getFileTimes( physXStream, NULL, &xmlTime ); + Platform::getFileTimes( binPhysXStream, NULL, &binTime ); + + // If the binary is newer... load that. + if ( Platform::compareFileTimes( binTime, xmlTime ) >= 0 ) + _loadCollection( binPhysXStream, true ); + + // If the binary failed... then load the xml. + if ( !physicsCollection ) + { + _loadCollection( physXStream, false ); + + // If loaded... resave the xml in binary format + // for quicker subsequent loads. + if ( physicsCollection ) + NXU::saveCollection( physicsCollection, binPhysXStream, NXU::FT_BINARY ); + } + + // If it still isn't loaded then we've failed! + if ( !physicsCollection ) + { + errorBuffer = String::ToString( "PxSingleActorData::preload: could not load '%s'!", physXStream ); + return false; + } + + if (!shapeName || shapeName == '\0') + { + errorBuffer = "PxSingleActorData::preload: no shape name!"; + return false; + } + + // Now load the shape. + shape = ResourceManager::get().load( shapeName ); + + if (bool(shape) == false) + { + errorBuffer = String::ToString( "PxSingleActorData::preload: unable to load shape: %s", shapeName ); + return false; + } + + return true; +} + +bool PxSingleActorData::_loadCollection( const UTF8 *path, bool isBinary ) +{ + if ( physicsCollection ) + { + NXU::releaseCollection( physicsCollection ); + physicsCollection = NULL; + } + + FileStream fs; + if ( !fs.open( path, Torque::FS::File::Read ) ) + return false; + + // Load the data into memory. + U32 size = fs.getStreamSize(); + FrameTemp buff( size ); + fs.read( size, buff ); + + // If the stream didn't read anything, there's a problem. + if ( size <= 0 ) + return false; + + // Ok... try to load it. + physicsCollection = NXU::loadCollection( path, + isBinary ? NXU::FT_BINARY : NXU::FT_XML, + buff, + size ); + + return physicsCollection != NULL; +} + +NxActor* PxSingleActorData::createActor( NxScene *scene, const NxMat34 *nxMat, const Point3F& scale ) +{ + if ( !scene ) + { + Con::errorf( "PxSingleActorData::createActor() - returned null NxScene" ); + return NULL; + } + + PxSingleActor_Notify pxNotify( scale ); + + NXU::instantiateCollection( physicsCollection, *gPhysicsSDK, scene, nxMat, &pxNotify ); + + NxActor *actor = pxNotify.getActor(); + + actor->setGlobalPose( *nxMat ); + + if ( !actor ) + { + Con::errorf( "PxSingleActorData::createActor() - Could not create actor!" ); + return NULL; + } + + return actor; +} + +IMPLEMENT_CO_NETOBJECT_V1(PxSingleActor); + +PxSingleActor::PxSingleActor() + : mShapeInstance( NULL ), + mWorld( NULL ), + mActor( NULL ), + mSleepingLastTick( false ), + mStartImpulse( Point3F::Zero ), + mResetPos( MatrixF::Identity ), + mBuildScale( Point3F::Zero ), + mBuildLinDrag( 0.0f ), + mBuildAngDrag( 0.0f ) +{ + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType | ShadowCasterObjectType; + + overrideOptions = false; + + mUserData.setObject( this ); + mUserData.getContactSignal().notify( this, &PxSingleActor::_onContact ); +} + +void PxSingleActor::initPersistFields() +{ + Parent::initPersistFields(); + + addGroup("Lighting"); + addField("receiveSunLight", TypeBool, Offset(receiveSunLight, PxSingleActor)); + addField("receiveLMLighting", TypeBool, Offset(receiveLMLighting, PxSingleActor)); + //addField("useAdaptiveSelfIllumination", TypeBool, Offset(useAdaptiveSelfIllumination, TSStatic)); + addField("useCustomAmbientLighting", TypeBool, Offset(useCustomAmbientLighting, PxSingleActor)); + //addField("customAmbientSelfIllumination", TypeBool, Offset(customAmbientForSelfIllumination, TSStatic)); + addField("customAmbientLighting", TypeColorF, Offset(customAmbientLighting, PxSingleActor)); + addField("lightGroupName", TypeRealString, Offset(lightGroupName, PxSingleActor)); + endGroup("Lighting"); + + //addGroup("Collision"); + //endGroup("Collision"); +} + +bool PxSingleActor::onAdd() +{ + PROFILE_SCOPE(PxSingleActor_onAdd); + + // onNewDatablock for the server is called here + // for the client it is called in unpackUpdate + if ( !Parent::onAdd() ) + return false; + + // JCFNOTE: we definitely have a datablock here because GameBase::onAdd checks that + + mNextPos = mLastPos = getPosition(); + mNextRot = mLastRot = getTransform(); + mResetPos = getTransform(); + + //Con::printf( "Set reset position to %g, %g, %g", mResetPos.getPosition().x, mResetPos.getPosition().y, mResetPos.getPosition().z ); + if ( !mStartImpulse.isZero() ) + applyImpulse( mStartImpulse ); + + PhysicsPlugin::getPhysicsResetSignal().notify( this, &PxSingleActor::onPhysicsReset, 1051.0f ); + + addToScene(); + + return true; +} + +bool PxSingleActor::onNewDataBlock( GameBaseData *dptr ) +{ + // Since onNewDataBlock is actually called before onAdd for client objects + // we need to initialize this here. + mWorld = dynamic_cast( gPhysicsPlugin->getWorld( isServerObject() ? "server" : "client" ) ); + if ( !mWorld ) + return false; + + mDataBlock = dynamic_cast(dptr); + + if ( !mDataBlock || !Parent::onNewDataBlock( dptr ) ) + return false; + + if ( isClientObject() ) + { + if ( mShapeInstance ) + SAFE_DELETE(mShapeInstance); + mShapeInstance = new TSShapeInstance( mDataBlock->shape, isClientObject() ); + } + + mObjBox = mDataBlock->shape->bounds; + resetWorldBox(); + + // Create the actor. + _createActor(); + + // Must be called by the leaf class (of GameBase) once everything is loaded. + scriptOnNewDataBlock(); + + return true; +} + +/* +void PxSingleActor::processMessage(const String & msgName, SimObjectMessageData * data) +{ + Parent::processMessage(msgName,data); + + if ( !isServerObject() ) + return; + + static const String sEditorEnable( "EditorEnable" ); + static const String sEditorDisable( "EditorDisable" ); + static const String sInspectPostApply( "InspectPostApply" ); + + if ( msgName == sInspectPostApply ) + { + setMaskBits( LightMask ); + + bool forceSleep = mForceSleep; + mForceSleep = !mForceSleep; + setForceSleep( forceSleep ); + } + else if ( msgName == sEditorEnable ) + { + mForceSleep = false; + setForceSleep( true ); + applyWarp( mResetPos, true, false ); + } + else if ( msgName == sEditorDisable ) + { + mForceSleep = true; + setForceSleep( false ); + mResetPos = getTransform(); + } +} +*/ + +void PxSingleActor::onRemove() +{ + removeFromScene(); + + delete mShapeInstance; + mShapeInstance = NULL; + + if ( mActor ) + mWorld->releaseActor( *mActor ); + + PhysicsPlugin::getPhysicsResetSignal().remove( this, &PxSingleActor::onPhysicsReset ); + + Parent::onRemove(); +} + +bool PxSingleActor::prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + if ( !mShapeInstance || !state->isObjectRendered(this) ) + return false; + + Point3F cameraOffset; + getTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getDiffuseCameraPosition(); + F32 dist = cameraOffset.len(); + if ( dist < 0.01f ) + dist = 0.01f; + + F32 invScale = (1.0f/getMax(getMax(getScale().x,getScale().y),getScale().z)); + dist *= invScale; + S32 dl = mShapeInstance->setDetailFromDistance( state, dist ); + if (dl<0) + return false; + + renderObject( state ); + + return false; +} + +void PxSingleActor::renderObject(SceneState* state) +{ + GFXTransformSaver saver; + + // Set up our TS render state here. + TSRenderState rdata; + rdata.setSceneState( state ); + //rdata.setObjScale( &getScale() ); + + LightManager *lm = gClientSceneGraph->getLightManager(); + if ( !state->isShadowPass() ) + lm->setupLights( this, getWorldSphere() ); + + MatrixF mat = getTransform(); + mat.scale( getScale() ); + GFX->setWorldMatrix( mat ); + + mShapeInstance->animate(); + mShapeInstance->render( rdata ); + + lm->resetLights(); +} + +U32 PxSingleActor::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + /* + if ( stream->writeFlag( mask & ForceSleepMask ) ) + stream->writeFlag( mForceSleep ); + + + stream->writeFlag( mask & SleepMask ); + */ + if ( stream->writeFlag( mask & WarpMask ) ) + { + stream->writeAffineTransform( getTransform() ); + } + + if ( stream->writeFlag( mask & MoveMask ) ) + { + stream->writeAffineTransform( getTransform() ); + + if ( stream->writeFlag( mActor ) ) + { + const NxVec3& linVel = mActor->getLinearVelocity(); + stream->write( linVel.x ); + stream->write( linVel.y ); + stream->write( linVel.z ); + + const NxVec3& angVel = mActor->getAngularVelocity(); + stream->write( angVel.x ); + stream->write( angVel.y ); + stream->write( angVel.z ); + } + } + + if ( stream->writeFlag( mask & ImpulseMask ) ) + { + NxVec3 linVel( 0, 0, 0 ); + if ( mActor ) + linVel = mActor->getLinearVelocity(); + + stream->write( linVel.x ); + stream->write( linVel.y ); + stream->write( linVel.z ); + } + + // This internally uses the mask passed to it. + if ( mLightPlugin ) + retMask |= mLightPlugin->packUpdate( this, LightMask, con, mask, stream ); + + return retMask; +} + + +void PxSingleActor::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + + /* + if ( stream->readFlag() ) // ForceSleepMask + { + bool forceSleep = stream->readFlag(); + + if ( isProperlyAdded() ) + setForceSleep( forceSleep ); + else + mForceSleep = forceSleep; + } + + + if ( stream->readFlag() ) // SleepMask + { + if ( mActor ) + mActor->putToSleep(); + + // TODO: The sleep should start packing its own + // position warp data and not rely on the WarpMask + // below. This will allow us to place the actor + // in a way that is less prone to explosions. + } + */ + if ( stream->readFlag() ) // WarpMask + { + // If we set a warp mask, + // we need to instantly move + // the actor to the new position + // without applying any corrections. + MatrixF mat; + stream->readAffineTransform( &mat ); + + applyWarp( mat, true, false ); + } + + if ( stream->readFlag() ) // MoveMask + { + MatrixF mat; + stream->readAffineTransform( &mat ); + + NxVec3 linVel, angVel; + if ( stream->readFlag() ) + { + stream->read( &linVel.x ); + stream->read( &linVel.y ); + stream->read( &linVel.z ); + + stream->read( &angVel.x ); + stream->read( &angVel.y ); + stream->read( &angVel.z ); + + if ( !mDataBlock->clientOnly ) + applyCorrection( mat, linVel, angVel ); + } + } + + if ( stream->readFlag() ) // ImpulseMask + { + NxVec3 linVel; + stream->read( &linVel.x ); + stream->read( &linVel.y ); + stream->read( &linVel.z ); + + if ( mActor ) + { + mWorld->releaseWriteLock(); + mActor->setLinearVelocity( linVel ); + mStartImpulse.zero(); + } + else + mStartImpulse.set( linVel.x, linVel.y, linVel.z ); + } + + if ( mLightPlugin ) + mLightPlugin->unpackUpdate( this, con, stream ); +} + +void PxSingleActor::setScale( const VectorF& scale ) +{ + // This is so that the level + // designer can change the scale + // of a PxSingleActor in the editor + // and have the PhysX representation updated properly. + + // First we call the parent's setScale + // so that the ScaleMask can be set. + Parent::setScale( scale ); + + if ( !mActor || mBuildScale.equal( scale ) ) + return; + + _createActor(); +} + +void PxSingleActor::applyWarp( const MatrixF& mat, bool interpRender, bool sweep ) +{ + MatrixF newMat( mat ); + + if ( mActor ) + { + mWorld->releaseWriteLock(); + + mActor->wakeUp(); + + NxMat34 nxMat; + nxMat.setRowMajor44( newMat ); + + { + mActor->setAngularVelocity( NxVec3( 0.0f ) ); + mActor->setLinearVelocity( NxVec3( 0.0f ) ); + mActor->setGlobalOrientation( nxMat.M ); + } + + /* + if ( sweep ) + { + mResetPos = mat; + sweepTest( &newMat ); + } + */ + + nxMat.setRowMajor44( newMat ); + + mActor->setGlobalPose( nxMat ); + } + + Parent::setTransform( newMat ); + + mNextPos = newMat.getPosition(); + mNextRot = newMat; + + if ( !interpRender ) + { + mLastPos = mNextPos; + mLastRot = mNextRot; + } +} + +void PxSingleActor::setTransform( const MatrixF& mat ) +{ + applyWarp( mat, false, true ); + setMaskBits( WarpMask ); +} + +F32 PxSingleActor::getMass() const +{ + if ( !mActor ) + return 0.0f; + + return mActor->getMass(); +} + +Point3F PxSingleActor::getVelocity() const +{ + if ( !mActor ) + return Point3F::Zero; + + return pxCast(mActor->getLinearVelocity()); +} + + +void PxSingleActor::processTick( const Move *move ) +{ + PROFILE_SCOPE( PxSingleActor_ProcessTick ); + + if ( !mActor ) + return; + + // Set the last pos/rot to the + // values of the previous tick for interpolateTick. + mLastPos = mNextPos; + mLastRot = mNextRot; + + if ( mActor->isSleeping() || mActor->readBodyFlag( NX_BF_FROZEN ) ) + { + /* + if ( !mSleepingLastTick ) + { + // TODO: We cannot use a warp when we sleep. The warp + // can cause interpenetration with other actors which + // then cause explosions on the next simulation tick. + // + // Instead lets have the sleep mask itself send the sleep + // position and let it set the actor in a safe manner + // that is specific to sleeping. + + setMaskBits( WarpMask | SleepMask ); + } + */ + mSleepingLastTick = true; + + return; + } + + mSleepingLastTick = false; + + // Container buoyancy & drag + _updateContainerForces(); + + // Set the Torque transform to the dynamic actor's transform. + MatrixF mat; + mActor->getGlobalPose().getRowMajor44( mat ); + Parent::setTransform( mat ); + + // Set the next pos/rot to the current values. + mNextPos = mat.getPosition(); + mNextRot.set( mat ); + + // TODO: We removed passing move updates to the client + // on every tick because it was a waste of bandwidth. The + // client is correct or within limits the vast majority of + // the time. + // + // Still it can get out of sync. To correct this when the + // actor sleeps can cause large ugly jumps in position. One + // thought here was to hide the correction by waiting until + // the object and its new position are offscreen. Short of + // that we have to apply corrections gradually over the time + // of movement if we want corrections to work well. + // + // Lets consider some techniques for future improvement. + // + // First lets send updates to all non-sleeping actors in some + // sort of round robin fashion, so that the bandwidth used is + // limited by the number of actors moving. If 1 actor is moving + // it sends updates every tick... if 10 actors are moving each + // actor gets an update every 10 ticks. Lets say a really ugly + // case... 100 moving actors... every actor is updated every + // 3.2 seconds... thats probably acceptable. + // + // Second lets also consider the idea of sending a velocity only + // correction more regularly. If the actors are in the same start + // position, a correct velocity would do as much good as a position. + // Plus the linear velocity can me compressed really well.. less + // than 64 bits. Maybe sending velocity regularly and positions + // less regular would work? + // + // A more radical idea would be to have a special scene object that + // manages all the packUpdates for all actors. This object would + // ensure that the right actors are updated and break up the updates + // in a way to limit bandwidth usage. + + // Set the MoveMask so this will be updated to the client. + //setMaskBits( MoveMask ); +} + +void PxSingleActor::interpolateTick( F32 delta ) +{ + Point3F interpPos; + QuatF interpRot; + + // Interpolate the position based on the delta. + interpPos.interpolate( mNextPos, mLastPos, delta ); + + // Interpolate the rotation based on the delta. + interpRot.interpolate( mNextRot, mLastRot, delta ); + + // Set up the interpolated transform. + MatrixF interpMat; + + // Set the interpolated position and rotation. + interpRot.setMatrix( &interpMat ); + interpMat.setPosition( interpPos ); + + // Set the transform to the interpolated transform. + Parent::setTransform( interpMat ); +} + +void PxSingleActor::sweepTest( MatrixF *mat ) +{ + NxVec3 nxCurrPos = getPosition(); + + // If the position is zero, + // the parent hasn't been updated yet + // and we don't even need to do the sweep test. + // This is a fix for a problem that was happening + // where on the add of the PxSingleActor, it would + // set the position to a very small value because it would be getting a hit + // even though the current position was 0. + if ( nxCurrPos.isZero() ) + return; + + // Set up the flags and the query structure. + NxU32 flags = NX_SF_STATICS | NX_SF_DYNAMICS; + + NxSweepQueryHit sweepResult; + dMemset( &sweepResult, 0, sizeof( sweepResult ) ); + + NxVec3 nxNewPos = mat->getPosition(); + + // Get the velocity which will be our sweep direction and distance. + NxVec3 nxDir = nxNewPos - nxCurrPos; + if ( nxDir.isZero() ) + return; + + // Get the scene and do the sweep. + mActor->wakeUp(); + mActor->linearSweep( nxDir, flags, NULL, 1, &sweepResult, NULL ); + + if ( sweepResult.hitShape && sweepResult.t < nxDir.magnitude() ) + { + nxDir.normalize(); + nxDir *= sweepResult.t; + nxCurrPos += nxDir; + + mat->setPosition( Point3F( nxCurrPos.x, nxCurrPos.y, nxCurrPos.z ) ); + } +} + +void PxSingleActor::applyCorrection( const MatrixF& mat, const NxVec3& linVel, const NxVec3& angVel ) +{ + // Sometimes the actor hasn't been + // created yet during the call from unpackUpdate. + if ( !mActor || !mWorld ) + return; + + mWorld->releaseWriteLock(); + + NxVec3 newPos = mat.getPosition(); + NxVec3 currPos = getPosition(); + + NxVec3 offset = newPos - currPos; + + // If the difference isn't large enough, + // just set the new transform, no correction. + if ( offset.magnitude() > 0.3f ) + { + // If we're going to set the linear or angular velocity, + // we do it before we add a corrective force, since it would be overwritten otherwise. + NxVec3 currLinVel, currAngVel; + currLinVel = mActor->getLinearVelocity(); + currAngVel = mActor->getAngularVelocity(); + + // Scale the corrective force by half, + // otherwise it will over correct and oscillate. + NxVec3 massCent = mActor->getCMassGlobalPosition(); + mActor->addForceAtPos( offset, massCent, NX_VELOCITY_CHANGE ); + + // If the linear velocity is divergent enough, change to server linear velocity. + if ( (linVel - currLinVel).magnitude() > 0.3f ) + mActor->setLinearVelocity( linVel ); + // Same for angular. + if ( (angVel - currAngVel).magnitude() > 0.3f ) + mActor->setAngularVelocity( angVel ); + } + + Parent::setTransform( mat ); +} + +void PxSingleActor::applyImpulse( const VectorF& vel ) +{ + if ( !mWorld || !mActor ) + return; + + mWorld->releaseWriteLock(); + + NxVec3 linVel = mActor->getLinearVelocity(); + NxVec3 nxVel( vel.x, vel.y, vel.z ); + + mActor->setLinearVelocity(linVel + nxVel); + + setMaskBits( ImpulseMask ); +} + +void PxSingleActor::onPhysicsReset( PhysicsResetEvent reset ) +{ + if ( !mActor ) + return; + + // Store the reset transform for later use. + if ( reset == PhysicsResetEvent_Store ) + mActor->getGlobalPose().getRowMajor44( mResetPos ); + else if ( reset == PhysicsResetEvent_Restore ) + { + mActor->wakeUp(); + setTransform( mResetPos ); + } +} + +void PxSingleActor::_updateContainerForces() +{ + if ( !mWorld->getEnabled() ) + return; + + PROFILE_SCOPE( PxSingleActor_updateContainerForces ); + + // Update container drag and buoyancy properties + + ContainerQueryInfo info; + info.box = getWorldBox(); + info.mass = getMass(); + + // Find and retreive physics info from intersecting WaterObject(s) + mContainer->findObjects( getWorldBox(), WaterObjectType|PhysicalZoneObjectType, findRouter, &info ); + + // Calculate buoyancy and drag + F32 angDrag = mBuildAngDrag; + F32 linDrag = mBuildLinDrag; + F32 buoyancy = 0.0f; + + if ( true ) //info.waterCoverage >= 0.1f) + { + F32 waterDragScale = info.waterViscosity * mDataBlock->waterDragScale; + F32 powCoverage = mPow( info.waterCoverage, 0.25f ); + + if ( info.waterCoverage > 0.0f ) + { + //angDrag = mBuildAngDrag * waterDragScale; + //linDrag = mBuildLinDrag * waterDragScale; + angDrag = mLerp( mBuildAngDrag, mBuildAngDrag * waterDragScale, powCoverage ); + linDrag = mLerp( mBuildLinDrag, mBuildLinDrag * waterDragScale, powCoverage ); + } + + buoyancy = ( info.waterDensity / mDataBlock->buoyancyDensity ) * mPow( info.waterCoverage, 2.0f ); + } + + // Apply drag (dampening) + mActor->setLinearDamping( linDrag ); + mActor->setAngularDamping( angDrag ); + + // Apply buoyancy force + if ( buoyancy != 0 ) + { + // A little hackery to prevent oscillation + // Based on this blog post (http://reinot.blogspot.com/2005/11/oh-yes-they-float-georgie-they-all.html) + // JCF: DISABLED + NxVec3 gravity; + mWorld->getScene()->getGravity(gravity); + //NxVec3 velocity = mActor->getLinearVelocity(); + + NxVec3 buoyancyForce = buoyancy * -gravity * TickSec; + //F32 currHeight = getPosition().z; + //const F32 C = 2.0f; + //const F32 M = 0.1f; + + //if ( currHeight + velocity.z * TickSec * C > info.waterHeight ) + // buoyancyForce *= M; + + mActor->addForceAtPos( buoyancyForce, mActor->getCMassGlobalPosition(), NX_IMPULSE ); + } + + // Apply physical zone forces + if ( info.appliedForce.len() > 0.001f ) + mActor->addForceAtPos( pxCast(info.appliedForce), mActor->getCMassGlobalPosition(), NX_IMPULSE ); +} + +void PxSingleActor::onCollision( GameBase *hitObject, const VectorF &impactForce ) +{ + if ( isGhost() ) + return; + + PROFILE_SCOPE( PxSingleActor_OnCollision ); + + // TODO: Why do we not get a hit position? + Point3F hitPos = getPosition(); + + String strHitPos = String::ToString( "%g %g %g", hitPos.x, hitPos.y, hitPos.z ); + String strImpactVec = String::ToString( "%g %g %g", impactForce.x, impactForce.y, impactForce.z ); + String strImpactForce = String::ToString( "%g", impactForce.len() ); + + Con::executef( mDataBlock, "onCollision", scriptThis(), + hitObject ? hitObject->scriptThis() : NULL, + strHitPos.c_str(), strImpactVec.c_str(), strImpactForce.c_str() ); +} + +void PxSingleActor::_onContact( NxActor *ourActor, + NxActor *hitActor, + SceneObject *hitObject, + const Point3F &hitPoint, + const Point3F &impactForce ) +{ + if ( isGhost() ) + return; + + String strHitPos = String::ToString( "%g %g %g", hitPoint.x, hitPoint.y, hitPoint.z ); + String strImpactVec = String::ToString( "%g %g %g", impactForce.x, impactForce.y, impactForce.z ); + String strImpactForce = String::ToString( "%g", impactForce.len() ); + + Con::executef( mDataBlock, "onCollision", getIdString(), + hitObject ? hitObject->scriptThis() : "", + strHitPos.c_str(), strImpactVec.c_str(), strImpactForce.c_str() ); +} + +void PxSingleActor::_createActor() +{ + // NXU::instantiateCollection sometimes calls methods that need + // to have write access. + mWorld->releaseWriteLock(); + + if ( mActor ) + { + mWorld->releaseActor( *mActor ); + mActor = NULL; + } + + NxScene *scene = mWorld->getScene(); + + NxMat34 nxMat; + nxMat.setRowMajor44( getTransform() ); + + Point3F scale = getScale(); + + // Look for a zero scale in any component. + if ( mIsZero( scale.least() ) ) + return; + + bool createActors = false; + if ( !mDataBlock->clientOnly || (mDataBlock->clientOnly && isClientObject()) ) + createActors = true; + + mActor = createActors ? mDataBlock->createActor( scene, &nxMat, scale ) : NULL; + + if ( !mActor ) + { + mBuildScale = getScale(); + mBuildAngDrag = 0; + mBuildLinDrag = 0; + return; + } + + U32 group = mDataBlock->clientOnly ? PxGroup_ClientOnly : PxGroup_Default; + mActor->setGroup( group ); + + mActor->userData = &mUserData; + + mActor->setContactReportFlags( NX_NOTIFY_ON_START_TOUCH_FORCE_THRESHOLD | NX_NOTIFY_FORCES ); + mActor->setContactReportThreshold( mDataBlock->forceThreshold ); + + mBuildScale = getScale(); + mBuildAngDrag = mActor->getAngularDamping(); + mBuildLinDrag = mActor->getLinearDamping(); +} + +ConsoleMethod( PxSingleActor, applyImpulse, void, 3, 3, "applyImpulse - takes a velocity vector to apply") +{ + VectorF vec; + dSscanf( argv[2],"%g %g %g", + &vec.x,&vec.y,&vec.z ); + + object->applyImpulse( vec ); +} + diff --git a/T3D/physics/physx/pxSingleActor.h b/T3D/physics/physx/pxSingleActor.h new file mode 100644 index 0000000..a7ec20a --- /dev/null +++ b/T3D/physics/physx/pxSingleActor.h @@ -0,0 +1,210 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXSINGLEACTOR_H +#define _PXSINGLEACTOR_H + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _T3D_PHYSICS_PHYSICSPLUGIN_H_ +#include "T3D/physics/physicsPlugin.h" +#endif +#ifndef _PXUSERDATA_H_ +#include "T3D/physics/physx/pxUserData.h" +#endif + + +class TSShape; +class TSShapeInstance; +class PxSingleActor; +class PxWorld; +class NxScene; +class NxActor; +class NxMat34; +class NxVec3; + +namespace NXU +{ + class NxuPhysicsCollection; +} + + +class PxSingleActorData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + + PxSingleActorData(); + virtual ~PxSingleActorData(); + + DECLARE_CONOBJECT(PxSingleActorData); + + static void initPersistFields(); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + bool preload(bool server, String &errorBuffer ); + + void allocPrimBuffer( S32 overrideSize = -1 ); + + bool _loadCollection( const UTF8 *path, bool isBinary ); + +public: + + // Rendering + StringTableEntry shapeName; + Resource shape; + + /// Filename to load the physics actor from. + StringTableEntry physXStream; + + F32 forceThreshold; + + /// Physics collection that holds the actor + /// and all associated shapes and data. + NXU::NxuPhysicsCollection *physicsCollection; + + // Angular and Linear Drag (dampening) is scaled by this when in water. + F32 waterDragScale; + + // The density of this object (for purposes of buoyancy calculation only). + F32 buoyancyDensity; + + /// If this flag is set to true, + /// the physics actor will only be + /// created on the client, and the server + /// object is only responsible for ghosting. + /// Objects with this flag set will never stop + /// the physics player from moving through them. + bool clientOnly; + + NxActor* createActor( NxScene *scene, const NxMat34 *nxMat, const Point3F& scale ); +}; + +//-------------------------------------------------------------------------- + +class PxSingleActor : public GameBase //, public LightReceiver +{ + typedef GameBase Parent; + + enum MaskBits + { + MoveMask = Parent::NextFreeMask << 0, + WarpMask = Parent::NextFreeMask << 1, + LightMask = Parent::NextFreeMask << 2, + SleepMask = Parent::NextFreeMask << 3, + ForceSleepMask = Parent::NextFreeMask << 4, + ImpulseMask = Parent::NextFreeMask << 5, + NextFreeMask = Parent::NextFreeMask << 6 + }; + +// PhysX +protected: + + PxWorld *mWorld; + + NxActor *mActor; + + /// The userdata object assigned to our actor. + PxUserData mUserData; + + MatrixF mResetPos; + + VectorF mBuildScale; + F32 mBuildAngDrag; + F32 mBuildLinDrag; + + VectorF mStartImpulse; + + bool mSleepingLastTick; + + void sweepTest( MatrixF *mat ); + void applyCorrection( const MatrixF& mat, const NxVec3& linVel, const NxVec3& angVel ); + void applyWarp( const MatrixF& mat, bool interpRender, bool sweep ); + +protected: + + PxSingleActorData *mDataBlock; + + void setScale( const VectorF& scale ); + void setTransform( const MatrixF& mat ); + + bool onAdd(); + void onRemove(); + + bool onNewDataBlock( GameBaseData *dptr ); + +protected: + + TSShapeInstance *mShapeInstance; + + // Interpolation + Point3F mLastPos; + Point3F mNextPos; + + QuatF mLastRot; + QuatF mNextRot; + + bool prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + void renderObject ( SceneState *state); + + //void processMessage(const String & msgName, SimObjectMessageData *); + +public: + + PxSingleActor(); + + DECLARE_CONOBJECT(PxSingleActor); + static void initPersistFields(); + + U32 packUpdate ( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + /// Processes a move event and updates object state once every 32 milliseconds. + /// + /// This takes place both on the client and server, every 32 milliseconds (1 tick). + /// + /// @see ProcessList + /// @param move Move event corresponding to this tick, or NULL. + void processTick( const Move *move ); + + /// Interpolates between tick events. This takes place on the CLIENT ONLY. + /// + /// @param delta Time since last call to interpolate + void interpolateTick( F32 delta ); + + void applyImpulse( const VectorF& vec ); + + void onPhysicsReset( PhysicsResetEvent reset ); + + F32 getMass() const; + Point3F getVelocity() const; + + /// @see GameBase + void onCollision( GameBase *object, const VectorF &vec ); + +protected: + + void _onContact( NxActor *ourActor, + NxActor *hitActor, + SceneObject *hitObject, + const Point3F &hitPoint, + const Point3F &normalForce ); + + void _updateContainerForces(); + void _createActor(); +}; + +#endif // _PXSINGLEACTOR_H + diff --git a/T3D/physics/physx/pxStream.cpp b/T3D/physics/physx/pxStream.cpp new file mode 100644 index 0000000..9e5841a --- /dev/null +++ b/T3D/physics/physx/pxStream.cpp @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxStream.h" + +#include "console/console.h" +#include "console/consoleTypes.h" + + +PxMemStream::PxMemStream() + : mMemStream( 1024 ) +{ +} + +PxMemStream::~PxMemStream() +{ +} + +void PxMemStream::resetPosition() +{ + mMemStream.setPosition( 0 ); +} + +NxU8 PxMemStream::readByte() const +{ + NxU8 out; + mMemStream.read( &out ); + return out; +} + +NxU16 PxMemStream::readWord() const +{ + NxU16 out; + mMemStream.read( &out ); + return out; +} + +NxU32 PxMemStream::readDword() const +{ + NxU32 out; + mMemStream.read( &out ); + return out; +} + +float PxMemStream::readFloat() const +{ + float out; + mMemStream.read( &out ); + return out; +} + +double PxMemStream::readDouble() const +{ + double out; + mMemStream.read( &out ); + return out; +} + +void PxMemStream::readBuffer( void *buffer, NxU32 size ) const +{ + mMemStream.read( size, buffer ); +} + +NxStream& PxMemStream::storeByte( NxU8 b ) +{ + mMemStream.write( b ); + return *this; +} + +NxStream& PxMemStream::storeWord( NxU16 w ) +{ + mMemStream.write( w ); + return *this; +} + +NxStream& PxMemStream::storeDword( NxU32 d ) +{ + mMemStream.write( d ); + return *this; +} + +NxStream& PxMemStream::storeFloat( NxReal f ) +{ + mMemStream.write( f ); + return *this; +} + +NxStream& PxMemStream::storeDouble( NxF64 f ) +{ + mMemStream.write( f ); + return *this; +} + +NxStream& PxMemStream::storeBuffer( const void *buffer, NxU32 size ) +{ + mMemStream.write( size, buffer ); + return *this; +} + + +bool PxConsoleStream::smLogWarnings = false; + +PxConsoleStream::PxConsoleStream() +{ + #ifndef TORQUE_RELEASE + smLogWarnings = true; + #endif + + Con::addVariable( "$PhysXLogWarnings", TypeBool, &smLogWarnings ); +} + +PxConsoleStream::~PxConsoleStream() +{ + Con::removeVariable( "$PhysXLogWarnings" ); +} + +void PxConsoleStream::reportError( NxErrorCode code, const char *message, const char* file, int line ) +{ + #ifdef TORQUE_DEBUG + + // If we're in debug mode and the error code is serious then + // pop up a message box to make sure we see it. + if ( code < NXE_DB_INFO ) + { + UTF8 info[1024]; + dSprintf( info, 1024, "File: %s\nLine: %d\n%s", file, line, message ); + Platform::AlertOK( "PhysX Error", info ); + } + + #endif + + // In all other cases we just dump the message to the console. + if ( code == NXE_DB_WARNING ) + { + if ( smLogWarnings ) + Con::errorf( "PhysX Warning:\n %s(%d) : %s\n", file, line, message ); + } + else + Con::errorf( "PhysX Error:\n %s(%d) : %s\n", file, line, message ); +} + +NxAssertResponse PxConsoleStream::reportAssertViolation (const char *message, const char *file,int line) +{ + // Assert if we're in debug mode... + bool triggerBreak = false; + #ifdef TORQUE_DEBUG + triggerBreak = PlatformAssert::processAssert( PlatformAssert::Fatal, file, line, message ); + #endif + + // In all other cases we just dump the message to the console. + Con::errorf( "PhysX Assert:\n %s(%d) : %s\n", file, line, message ); + + return triggerBreak ? NX_AR_BREAKPOINT : NX_AR_CONTINUE; +} + +void PxConsoleStream::print( const char *message ) +{ + Con::printf( "PhysX Says: %s\n", message ); +} diff --git a/T3D/physics/physx/pxStream.h b/T3D/physics/physx/pxStream.h new file mode 100644 index 0000000..ddf0c4e --- /dev/null +++ b/T3D/physics/physx/pxStream.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PXSTREAM_H_ +#define _T3D_PHYSICS_PXSTREAM_H_ + +#ifndef _PHYSX_H_ +#include "T3D/physics/physX/px.h" +#endif +#ifndef _MEMSTREAM_H_ +#include "core/stream/memStream.h" +#endif + + +class PxMemStream : public NxStream +{ +public: + + PxMemStream(); + virtual ~PxMemStream(); + + void resetPosition(); + + // NxStream + NxU8 readByte() const; + NxU16 readWord() const; + NxU32 readDword() const; + float readFloat() const; + double readDouble() const; + void readBuffer( void *buffer, NxU32 size ) const; + NxStream& storeByte( NxU8 b ); + NxStream& storeWord( NxU16 w ); + NxStream& storeDword( NxU32 d ); + NxStream& storeFloat( NxReal f ); + NxStream& storeDouble( NxF64 f ); + NxStream& storeBuffer( const void* buffer, NxU32 size ); + +protected: + + mutable MemStream mMemStream; +}; + + +class PxConsoleStream : public NxUserOutputStream +{ +protected: + + // NxUserOutputStream + void reportError( NxErrorCode code, const char *message, const char* file, int line ); + NxAssertResponse reportAssertViolation( const char *message, const char *file, int line ); + void print( const char *message ); + + static bool smLogWarnings; + +public: + + PxConsoleStream(); + ~PxConsoleStream(); +}; + +#endif // _T3D_PHYSICS_PXSTREAM_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxTSStatic.cpp b/T3D/physics/physx/pxTSStatic.cpp new file mode 100644 index 0000000..4446b4b --- /dev/null +++ b/T3D/physics/physx/pxTSStatic.cpp @@ -0,0 +1,390 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxTSStatic.h" + +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxMaterial.h" + +#include "T3D/physics/physX/pxStream.h" + +#include "T3D/physics/physX/pxUserData.h" + +#include "collision/concretePolyList.h" +#include "core/frameAllocator.h" +#include "ts/tsShapeInstance.h" +#include "T3D/tsStatic.h" + + +PxTSStatic::TriangleMeshMap PxTSStatic::smCachedTriangleMeshes; + + +PxTSStatic::PxTSStatic() : + mWorld( NULL ), + mActor( NULL ), + mTSStatic( NULL ), + mScale( Point3F::One ) +{ +} + +PxTSStatic::~PxTSStatic() +{ + _releaseActor(); +} + +void PxTSStatic::_releaseActor() +{ + if ( !mWorld ) + return; + + if ( mActor ) + mWorld->releaseActor( *mActor ); + + mWorld = NULL; + mActor = NULL; + mScale = Point3F::One; + + // TODO: We need to check the getReferenceCount() on the + // mesh objects and release them... if not unscaled collision + // shapes never get freed. + + if ( !mTriangleMeshes.empty() ) + { + // releaseConvexMesh() requires that both the server + // and client scenes to be writeable. + PxWorld::releaseWriteLocks(); + + for ( U32 i=0; i < mTriangleMeshes.size(); i++ ) + gPhysicsSDK->releaseTriangleMesh( *mTriangleMeshes[i] ); + + mTriangleMeshes.clear(); + } +} + +void PxTSStatic::freeMeshCache() +{ + TriangleMeshMap::Iterator trimesh = smCachedTriangleMeshes.begin(); + for ( ; trimesh != smCachedTriangleMeshes.end(); trimesh++ ) + { + if ( trimesh->value ) + gPhysicsSDK->releaseTriangleMesh( *trimesh->value ); + } + smCachedTriangleMeshes.clear(); +} + +PxTSStatic* PxTSStatic::create( TSStatic *tsStatic, PxWorld *world ) +{ + AssertFatal( world, "PxTSStatic::create() - No world found!" ); + + if ( tsStatic->getCollisionType() == TSStatic::None ) + return NULL; + + PxTSStatic *pxStatic = new PxTSStatic(); + + bool isOk = false; + if ( tsStatic->getCollisionType() == TSStatic::CollisionMesh || + tsStatic->getCollisionType() == TSStatic::VisibleMesh ) + isOk = pxStatic->_initTriangle( world, tsStatic ); + + if ( !isOk ) + { + delete pxStatic; + return NULL; + } + + return pxStatic; +} + +bool PxTSStatic::_initTriangle( PxWorld *world, TSStatic *tsStatic ) +{ + // Without a shape we have nothing to do! + if ( !tsStatic->getShapeInstance() ) + return false; + + mWorld = world; + mTSStatic = tsStatic; + mScale = tsStatic->getScale(); + mUserData.setObject( tsStatic ); + + // Mesh cooking requires that both + // scenes not be write locked! + PxWorld::releaseWriteLocks(); + + Vector triangleShapeDescs; + + // If the static is unscaled then grab the + // convex meshes from the cache. + const bool unscaled = mScale.equal( VectorF( 1.0f, 1.0f, 1.0f ) ); + if ( unscaled ) + { + TriangleMeshMap::Iterator iter = smCachedTriangleMeshes.find( tsStatic->getShape() ); + + while ( iter != smCachedTriangleMeshes.end() && + iter->key == tsStatic->getShape() ) + { + triangleShapeDescs.increment(); + triangleShapeDescs.last().meshData = iter->value; + iter++; + } + } + + // If we still don't have shapes either they + // haven't been cached yet or we're scaled. + if ( triangleShapeDescs.empty() ) + { + _loadTriangleMeshes( tsStatic, &mTriangleMeshes ); + + for ( U32 i=0; i < mTriangleMeshes.size(); i++ ) + { + triangleShapeDescs.increment(); + triangleShapeDescs.last().meshData = mTriangleMeshes[i]; + + // If it was unscaled cache it for later! + if ( unscaled ) + smCachedTriangleMeshes.insertEqual( tsStatic->getShape(), + mTriangleMeshes[i] ); + } + + // If we're unscaled we can clear the + // meshes as the cache will delete them. + if ( unscaled ) + mTriangleMeshes.clear(); + } + + // Still without shapes? We have nothing to build. + if ( triangleShapeDescs.empty() ) + return false; + + // Create the actor. + NxActorDesc actorDesc; + actorDesc.body = NULL; + actorDesc.name = tsStatic->getName(); + actorDesc.userData = &mUserData; + actorDesc.globalPose.setRowMajor44( tsStatic->getTransform() ); + for ( U32 i=0; i < triangleShapeDescs.size(); i++ ) + actorDesc.shapes.push_back( &triangleShapeDescs[i] ); + + mActor = mWorld->getScene()->createActor( actorDesc ); + + return true; +} + +void PxTSStatic::_loadTriangleMeshes( TSStatic *tsStatic, Vector *triangleMeshes ) +{ + TSShapeInstance *shapeInst = tsStatic->getShapeInstance(); + TSShape *shape = shapeInst->getShape(); + + MatrixF scaledMat; + + NxInitCooking(); + + Vector triangles; + Vector verts; + Point3F tempVert; + + for ( S32 i = 0; i < tsStatic->mCollisionDetails.size(); i++ ) + { + const TSShape::Detail& detail = shape->details[tsStatic->mCollisionDetails[i]]; + + const S32 ss = detail.subShapeNum; + if ( ss < 0 ) + return; + + const S32 start = shape->subShapeFirstObject[ss]; + const S32 end = start + shape->subShapeNumObjects[ss]; + + for ( S32 j = start; j < end; j++ ) + { + TSShapeInstance::MeshObjectInstance* meshInst = &shapeInst->mMeshObjects[j]; + if ( !meshInst ) + continue; + + TSMesh* mesh = meshInst->getMesh( 0 ); + + if( !mesh ) + continue; + + // Clear the temp vectors for the next mesh. + triangles.clear(); + verts.clear(); + + // Figure out how many triangles we have... + U32 triCount = 0; + const U32 base = 0; + for ( U32 j = 0; j < mesh->primitives.size(); j++ ) + { + TSDrawPrimitive & draw = mesh->primitives[j]; + + const U32 start = draw.start; + + AssertFatal(draw.matIndex & TSDrawPrimitive::Indexed,"TSMesh::buildPolyList (1)"); + + // gonna depend on what kind of primitive it is... + if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles) + triCount += draw.numElements / 3; + else + { + // Have to walk the tristrip to get a count... may have degenerates + U32 idx0 = base + mesh->indices[start + 0]; + U32 idx1; + U32 idx2 = base + mesh->indices[start + 1]; + U32 * nextIdx = &idx1; + for ( S32 k = 2; k < draw.numElements; k++ ) + { + *nextIdx = idx2; + // nextIdx = (j%2)==0 ? &idx0 : &idx1; + nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1); + idx2 = base + mesh->indices[start + k]; + if (idx0 == idx1 || idx0 == idx2 || idx1 == idx2) + continue; + + triCount++; + } + } + } + + // add the polys... + for ( U32 j = 0; j < mesh->primitives.size(); j++ ) + { + TSDrawPrimitive & draw = mesh->primitives[j]; + const U32 start = draw.start; + + AssertFatal(draw.matIndex & TSDrawPrimitive::Indexed,"TSMesh::buildPolyList (1)"); + + // gonna depend on what kind of primitive it is... + if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles) + { + for ( S32 k = 0; k < draw.numElements; ) + { + + triangles.push_back( base + mesh->indices[start + k + 2] ); + triangles.push_back( base + mesh->indices[start + k + 1] ); + triangles.push_back( base + mesh->indices[start + k + 0] ); + + k += 3; + } + } + else + { + AssertFatal((draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Strip,"TSMesh::buildPolyList (2)"); + + U32 idx0 = base + mesh->indices[start + 0]; + U32 idx1; + U32 idx2 = base + mesh->indices[start + 1]; + U32 * nextIdx = &idx1; + for ( S32 k = 2; k < draw.numElements; k++ ) + { + *nextIdx = idx2; + // nextIdx = (j%2)==0 ? &idx0 : &idx1; + nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1); + idx2 = base + mesh->indices[start + k]; + if (idx0 == idx1 || idx0 == idx2 || idx1 == idx2) + continue; + + triangles.push_back( idx2 ); + triangles.push_back( idx1 ); + triangles.push_back( idx0 ); + } + } + } + + scaledMat = meshInst->getTransform(); + const Point3F &scale = tsStatic->getScale(); + + // MatrixF .scale scales the + // orthagonal rotation vectors. + // So we need to scale the position after. + scaledMat.scale( scale ); + scaledMat[3] *= scale.x; + scaledMat[7] *= scale.y; + scaledMat[11] *= scale.z; + + if ( mesh->mVertexData.isReady() ) + { + for ( S32 j = 0; j < mesh->mNumVerts; j++ ) + { + tempVert = mesh->mVertexData[j].vert(); + scaledMat.mulP( tempVert ); + verts.push_back( tempVert ); + } + } + else + { + for ( S32 j = 0; j < mesh->verts.size(); j++ ) + { + tempVert = mesh->verts[j]; + scaledMat.mulP( tempVert ); + verts.push_back( tempVert ); + } + } + + // Build thr triangle mesh. + NxTriangleMeshDesc meshDesc; + meshDesc.numVertices = verts.size(); + meshDesc.numTriangles = triCount; + meshDesc.pointStrideBytes = sizeof(NxVec3); + meshDesc.triangleStrideBytes = 3*sizeof(NxU32); + meshDesc.points = verts.address(); + meshDesc.triangles = triangles.address(); + meshDesc.flags = 0; + + + // Ok... cook the mesh! + NxCookingParams params; + params.targetPlatform = PLATFORM_PC; + params.skinWidth = 0.01f; + params.hintCollisionSpeed = false; + NxSetCookingParams( params ); + PxMemStream cooked; + if ( NxCookTriangleMesh( meshDesc, cooked ) ) + { + cooked.resetPosition(); + NxTriangleMesh *pxMesh = gPhysicsSDK->createTriangleMesh( cooked ); + triangleMeshes->push_back( pxMesh ); + } + } + } + + NxCloseCooking(); +} + +void PxTSStatic::setTransform( const MatrixF &xfm ) +{ + if ( !mActor ) + return; + + mWorld->releaseWriteLock(); + + NxMat34 pose; + pose.setRowMajor44( xfm ); + mActor->setGlobalPose( pose ); +} + +void PxTSStatic::setScale( const Point3F &scale ) +{ + if ( !mWorld ) + return; + + // This sucks... be the only way to scale collision data + // in PhysX is to recreate the actor and collision data. + + // To avoid unnessasary expensive work of recreating collision + // data be sure that the scale has actually changed. + if ( mActor && mScale.equal( scale ) ) + return; + + PxWorld *world = mWorld; + + _releaseActor(); + + if ( mTSStatic->getCollisionType() == TSStatic::None ) + return; + + if ( mTSStatic->getCollisionType() == TSStatic::CollisionMesh || + mTSStatic->getCollisionType() == TSStatic::VisibleMesh ) + _initTriangle( world, mTSStatic ); +} diff --git a/T3D/physics/physx/pxTSStatic.h b/T3D/physics/physx/pxTSStatic.h new file mode 100644 index 0000000..0bfae54 --- /dev/null +++ b/T3D/physics/physx/pxTSStatic.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PXTSSTATIC_H_ +#define _T3D_PHYSICS_PXTSSTATIC_H_ + +#ifndef _T3D_PHYSICS_PHYSICSSTATIC_H_ +#include "T3D/physics/physicsStatic.h" +#endif +#ifndef _PXUSERDATA_H_ +#include "T3D/physics/physx/pxUserData.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +class NxActor; +class NxConvexMesh; +class NxTriangleMesh; +class PxWorld; +class TSStatic; +class TSShape; + + +class PxTSStatic : public PhysicsStatic +{ +protected: + + TSStatic *mTSStatic; + + PxWorld *mWorld; + + NxActor *mActor; + + /// The userdata object assigned to our actor. + PxUserData mUserData; + + Point3F mScale; + + Vector mTriangleMeshes; + + typedef HashTable TriangleMeshMap; + static TriangleMeshMap smCachedTriangleMeshes; + + static void _loadTriangleMeshes( TSStatic *tsStatic, Vector *triangleMeshes ); + + void _releaseActor(); + + PxTSStatic(); + + bool _initTriangle( PxWorld *world, TSStatic *tsStatic ); + +public: + + virtual ~PxTSStatic(); + + // PhysicsStatic + void setTransform( const MatrixF &xfm ); + void setScale( const Point3F &scale ); + + + static PxTSStatic* create( TSStatic *tsStatic, PxWorld *world ); + + static void freeMeshCache(); + +}; + +#endif // _T3D_PHYSICS_PXTSSTATIC_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxTerrain.cpp b/T3D/physics/physx/pxTerrain.cpp new file mode 100644 index 0000000..f5ff62a --- /dev/null +++ b/T3D/physics/physx/pxTerrain.cpp @@ -0,0 +1,215 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxTerrain.h" + +#include "terrain/terrData.h" + +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxWorld.h" +#include "T3D/physics/physX/pxMaterial.h" +#include "T3D/physics/physX/pxUserData.h" + + +PxTerrain::PxTerrain() + : mTerrain( NULL ), + mWorld( NULL ), + mActor( NULL ), + mHeightField( NULL ) +{ +} + +PxTerrain::~PxTerrain() +{ + if ( mWorld ) + mWorld->unscheduleUpdate( this ); + _releaseActor(); +} + +NxHeightField* PxTerrain::_createHeightField( TerrainBlock *terrain + /*const Vector &materialIds*/ ) +{ + // Since we're creating SDK level data we + // have to have access to all active worlds. + PxWorld::releaseWriteLocks(); + + const U32 blockSize = terrain->getBlockSize() + 1; + const TerrainFile *file = terrain->getFile(); + + // Init the heightfield description. + NxHeightFieldDesc heightFieldDesc; + heightFieldDesc.nbColumns = blockSize; + heightFieldDesc.nbRows = blockSize; + heightFieldDesc.thickness = -10.0f; + heightFieldDesc.convexEdgeThreshold = 0; + + // Allocate the samples. + heightFieldDesc.samples = new NxU32[ blockSize * blockSize ]; + heightFieldDesc.sampleStride = sizeof(NxU32); + NxU8 *currentByte = (NxU8*)heightFieldDesc.samples; + + for ( U32 row = 0; row < blockSize; row++ ) + { + const U32 tess = ( row + 1 ) % 2; + + for ( U32 column = 0; column < blockSize; column++ ) + { + NxHeightFieldSample *currentSample = (NxHeightFieldSample*)currentByte; + + currentSample->height = file->getHeight( blockSize - row - 1, column ); + + currentSample->materialIndex0 = 1; //materialIds[0]; + currentSample->materialIndex1 = 1; //materialIds[0]; + + currentSample->tessFlag = ( column + tess ) % 2; + + currentByte += heightFieldDesc.sampleStride; + } + } + + // Build it. + NxHeightField* heightField = gPhysicsSDK->createHeightField( heightFieldDesc ); + + // Destroy the temp sample array. + delete [] heightFieldDesc.samples; + + return heightField; +} + +void PxTerrain::_releaseActor() +{ + if ( mWorld && mActor && mHeightField ) + { + mWorld->releaseActor( *mActor ); + mActor = NULL; + mWorld->releaseHeightField( *mHeightField ); + mHeightField = NULL; + } +} + +PxTerrain* PxTerrain::create( TerrainBlock *terrain, PxWorld *world + /*, const Vector &materialIds */ ) +{ + PROFILE_SCOPE( PxTerrain_create ); + + AssertFatal( world, "PxTerrain::create() - No world found!" ); + + /* + PhysXWorld *world = PhysXWorld::getWorld( isServerObject() ); + if ( world && mPhysXMaterials[0] ) + { + // Gather the material ids. + Vector materialIds; + for ( U32 i=0; i < MAX_PHYSX_MATERIALS; i++ ) + { + if ( !mPhysXMaterials[i] ) + break; + + materialIds.push_back( mPhysXMaterials[i]->getMaterialId() ); + } + + mPhysXActor = new PhysXTerrainComponent(); + mPhysXActor->init( world, this, materialIds ); + } + */ + + // This doesn't fail... so create the object. + PxTerrain *pxTerrain = new PxTerrain(); + pxTerrain->mTerrain = terrain; + pxTerrain->mWorld = world; + + // Create the actor, heightfield, all that stuff. + pxTerrain->_createActor(); + + return pxTerrain; +} + +void PxTerrain::_makeTransform( NxMat34 *outPose, + const MatrixF &xfm, + TerrainBlock *terrain ) +{ + NxMat34 rot; + { + NxMat33 rotX; + rotX.rotX( Float_Pi / 2.0f ); + NxMat33 rotZ; + rotZ.rotZ( Float_Pi ); + rot.M.multiply( rotZ, rotX ); + + F32 terrSize = terrain->getWorldBlockSize(); + rot.t.set( terrSize, 0, 0 ); + } + + NxMat34 mat; + mat.setRowMajor44( xfm ); + + outPose->multiply( mat, rot ); +} + +void PxTerrain::_createActor() +{ + // Setup the shape description. + NxHeightFieldShapeDesc desc; + + mHeightField = desc.heightField = _createHeightField( mTerrain /*, materialIds*/ ); + + // TerrainBlock uses a 11.5 fixed point height format + // giving it a maximum height range of 0 to 2048. + desc.heightScale = 0.03125f; + + desc.rowScale = mTerrain->getSquareSize(); + desc.columnScale = mTerrain->getSquareSize(); + desc.materialIndexHighBits = 0; + + NxActorDesc actorDesc; + actorDesc.shapes.pushBack( &desc ); + actorDesc.body = NULL; + actorDesc.name = mTerrain->getName(); + + mUserData.setObject( mTerrain ); + actorDesc.userData = &mUserData; + + _makeTransform( &actorDesc.globalPose, mTerrain->getTransform(), mTerrain ); + + mActor = mWorld->getScene()->createActor( actorDesc ); +} + +void PxTerrain::setTransform( const MatrixF &xfm ) +{ + if ( !mActor ) + return; + + mWorld->releaseWriteLock(); + + NxMat34 pose; + _makeTransform( &pose, xfm, mTerrain ); + + mActor->setGlobalPose( pose ); +} + +void PxTerrain::setScale( const Point3F &scale ) +{ + // Terrain does not scale! +} + +void PxTerrain::update() +{ + PROFILE_SCOPE( PxTerrain_update ); + + // NOTE: NxHeightField saveToDesc and loadFromDesc does not work properly. + // Currently the only way to change an NxHeightFieldShape is to recreate it. + // Therefore is method is NOT appropriate for frequent calls or very large terrains. + + mWorld->scheduleUpdate( this ); +} + +void PxTerrain::_scheduledUpdate() +{ + _releaseActor(); + _createActor(); + + smDeleteSignal.trigger(); +} \ No newline at end of file diff --git a/T3D/physics/physx/pxTerrain.h b/T3D/physics/physx/pxTerrain.h new file mode 100644 index 0000000..d7c4b58 --- /dev/null +++ b/T3D/physics/physx/pxTerrain.h @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3D_PHYSICS_PXTERRAIN_H_ +#define _T3D_PHYSICS_PXTERRAIN_H_ + +#ifndef _T3D_PHYSICS_PHYSICSSTATIC_H_ +#include "T3D/physics/physicsStatic.h" +#endif +#ifndef _PXUSERDATA_H_ +#include "T3D/physics/physx/pxUserData.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +class TerrainBlock; +class PxWorld; +class SceneObject; +class PhysXMaterial; +class TerrainFile; +class MatrixF; + +class NxActor; +class NxHeightField; +class NxMat34; + + +class PxTerrain : public PhysicsStatic +{ +protected: + + TerrainBlock *mTerrain; + + NxActor *mActor; + + PxWorld *mWorld; + + NxHeightField *mHeightField; + + /// The userdata object assigned to + /// the terrain actor. + PxUserData mUserData; + + void _init(); + + void _releaseActor(); + + void _createActor(); + + /// + static NxHeightField* _createHeightField( TerrainBlock *terrBlock /*, + const Vector &materialIds*/ ); + + PxTerrain(); + + static void _makeTransform( NxMat34 *outPose, + const MatrixF &xfm, + TerrainBlock *terrain ); + +public: + + virtual ~PxTerrain(); + + static PxTerrain* create( TerrainBlock *terrain, PxWorld *world /*, const Vector &materialIds */ ); + + // PhysicsStatic + void setTransform( const MatrixF &xfm ); + void setScale( const Point3F &scale ); + virtual void update(); + + void _scheduledUpdate(); +}; + +#endif // _T3D_PHYSICS_PXTERRAIN_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxUserData.cpp b/T3D/physics/physx/pxUserData.cpp new file mode 100644 index 0000000..1c5023a --- /dev/null +++ b/T3D/physics/physx/pxUserData.cpp @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxUserData.h" + +#include "math/mPoint3.h" +#include "T3D/fx/particleEmitter.h" +#include "core/strings/stringFunctions.h" +#include "T3D/physics/physX/px.h" +#include + + +//------------------------------------------------------------------------- +// PxUserData +//------------------------------------------------------------------------- + +PxUserData::PxUserData() + : mObject( NULL ), + mIsBroken( false ), + mParticleEmitterData( NULL ), + mCanPush( true ) +{ +} + +PxUserData::~PxUserData() +{ +} + +PxUserData* PxUserData::getData( const NxActor &actor ) +{ + PxUserData *data = (PxUserData*)actor.userData; + + AssertFatal( !data || dStrcmp( typeid(*data).name(), "class PxUserData" ) == 0, + String::ToString( "PxUserData::getData() - Wrong user data type on actor '%s'!", actor.getName() ).c_str() ); + + return data; +} + +/* +//------------------------------------------------------------------------- +// PxBreakableUserData +//------------------------------------------------------------------------- + +PxBreakableUserData::PxBreakableUserData() + : mBreakForce( NULL ), + mParticleEmitterData( NULL ) +{ +} + +PxBreakableUserData::~PxBreakableUserData() +{ + if ( mBreakForce ) + delete mBreakForce; +} + +PxBreakableUserData* PxBreakableUserData::getData( const NxActor &actor ) +{ + PxBreakableUserData *data = (PxBreakableUserData*)actor.userData; + + AssertFatal( !data || dStrcmp( typeid(*data).name(), "class PxBreakableUserData" ) == 0, + String::ToString( "PxBreakableUserData::getData() - Wrong user data type on actor '%s'!", actor.getName() ).c_str() ); + + return data; +} +*/ + +//------------------------------------------------------------------------- +// PxJointUserData +//------------------------------------------------------------------------- + +PxJointUserData* PxJointUserData::getData( const NxJoint &joint ) +{ + PxJointUserData *data = (PxJointUserData*)joint.userData; + + AssertFatal( !data || dStrcmp( typeid(*data).name(), "class PxJointUserData" ) == 0, + String::ToString( "PxJointUserData::getData() - Wrong user data type on joint '%s'!", joint.getName() ).c_str() ); + + return data; +} \ No newline at end of file diff --git a/T3D/physics/physx/pxUserData.h b/T3D/physics/physx/pxUserData.h new file mode 100644 index 0000000..16b9167 --- /dev/null +++ b/T3D/physics/physx/pxUserData.h @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXUSERDATA_H_ +#define _PXUSERDATA_H_ + +#ifndef _SIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _PHYSX_H_ +#include "T3D/physics/physX/px.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class SceneObject; +class NxActor; +class NxJoint; +class NxController; +class Point3F; + + +//------------------------------------------------------------------------- +// PxUserData +//------------------------------------------------------------------------- + +/// Signal used for contact reports. +/// +/// @param ourActor The actor owned by the signaling object. +/// @param hitActor The other actor involved in the contact. +/// @param hitObject The SceneObject that was hit. +/// @param hitPoint The approximate position of the impact. +/// @param hitForce +/// +/// @see PxUserData +/// +typedef Signal PxUserContactSignal; + +class ParticleEmitterData; + +class PxUserData +{ +public: + + /// The constructor. + PxUserData(); + + /// The destructor. + virtual ~PxUserData(); + + /// Returns the user data on an actor. + static PxUserData* getData( const NxActor &actor ); + + void setObject( SceneObject *object ) { mObject = object; } + + const SceneObject* getObject() const { return mObject; } + + SceneObject* getObject() { return mObject; } + + PxUserContactSignal& getContactSignal() { return mContactSignal; } + + // Breakable stuff... does this belong here?? + Vector mUnbrokenActors; + Vector mBrokenActors; + Vector mRelXfm; + ParticleEmitterData *mParticleEmitterData; + bool mIsBroken; + + // Can the player push this actor? + bool mCanPush; + +protected: + + PxUserContactSignal mContactSignal; + + SceneObject *mObject; +}; + + +//------------------------------------------------------------------------- +// PxBreakableUserData +//------------------------------------------------------------------------- + +/* +class ParticleEmitterData; + +class PxBreakableUserData : public PxUserData +{ +public: + + PxBreakableUserData(); + ~PxBreakableUserData(); + + static PxBreakableUserData* getData( const NxActor &actor ); + + Point3F *mBreakForce; + + Vector mUnbrokenActors; + Vector mBrokenActors; + ParticleEmitterData *mParticleEmitterData; +}; +*/ + + +//------------------------------------------------------------------------- +// PxJointUserData +//------------------------------------------------------------------------- + +typedef Signal PxOnJointBreakSignal; + +class PxJointUserData : public PxUserData +{ +public: + + PxJointUserData() {} + + static PxJointUserData* getData( const NxJoint &joint ); + + PxOnJointBreakSignal& getOnJointBreakSignal() { return mOnJointBreakSignal; } + +protected: + + PxOnJointBreakSignal mOnJointBreakSignal; +}; + +#endif // _PXUSERDATA_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxUtils.cpp b/T3D/physics/physx/pxUtils.cpp new file mode 100644 index 0000000..7fac3ec --- /dev/null +++ b/T3D/physics/physx/pxUtils.cpp @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physx/pxUtils.h" + +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDrawUtil.h" +#include "math/mMatrix.h" +#include "math/mPoint3.h" +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxCasts.h" + +namespace PxUtils { + +void drawActor( NxActor *inActor ) +{ + GFXDrawUtil *drawer = GFX->getDrawUtil(); + //drawer->setZRead( false ); + + // Determine alpha we render shapes with. + const U8 enabledAlpha = 255; + const U8 disabledAlpha = 100; + U8 renderAlpha = inActor->readActorFlag( NX_AF_DISABLE_COLLISION ) ? disabledAlpha : enabledAlpha; + + // Determine color we render actors and shapes with. + ColorI actorColor( 0, 0, 255, 200 ); + ColorI shapeColor = ( inActor->isSleeping() ? ColorI( 0, 0, 255, renderAlpha ) : ColorI( 255, 0, 255, renderAlpha ) ); + + MatrixF actorMat(true); + inActor->getGlobalPose().getRowMajor44( actorMat ); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + desc.setCullMode( GFXCullNone ); + + // Draw an xfm gizmo for the actor's globalPose... + //drawer->drawTransform( desc, actorMat, Point3F::One, actorColor ); + + // Loop through and render all the actor's shapes.... + + NxShape *const*pShapeArray = inActor->getShapes(); + U32 numShapes = inActor->getNbShapes(); + + for ( U32 i = 0; i < numShapes; i++ ) + { + const NxShape *shape = pShapeArray[i]; + + Point3F shapePos = pxCast( shape->getGlobalPosition() ); + MatrixF shapeMat(true); + shape->getGlobalPose().getRowMajor44(shapeMat); + shapeMat.setPosition( Point3F::Zero ); + + switch ( shape->getType() ) + { + case NX_SHAPE_SPHERE: + { + NxSphereShape *sphere = (NxSphereShape*)shape; + drawer->drawSphere( desc, sphere->getRadius(), shapePos, shapeColor ); + + break; + } + case NX_SHAPE_BOX: + { + NxBoxShape *box = (NxBoxShape*)shape; + Point3F size = pxCast( box->getDimensions() ); + drawer->drawCube( desc, size*2, shapePos, shapeColor, &shapeMat ); + break; + } + case NX_SHAPE_CAPSULE: + { + shapeMat.mul( MatrixF( EulerF( mDegToRad(90.0f), mDegToRad(90.0f), 0 ) ) ); + + NxCapsuleShape *capsule = (NxCapsuleShape*)shape; + drawer->drawCapsule( desc, shapePos, capsule->getRadius(), capsule->getHeight(), shapeColor, &shapeMat ); + + break; + } + default: + { + break; + } + } + } + + //drawer->clearZDefined(); +} + +} // namespace PxUtils \ No newline at end of file diff --git a/T3D/physics/physx/pxUtils.h b/T3D/physics/physx/pxUtils.h new file mode 100644 index 0000000..666e50d --- /dev/null +++ b/T3D/physics/physx/pxUtils.h @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PXUTILS_H_ +#define _PXUTILS_H_ + + +class NxActor; + + +namespace PxUtils { + + /// Debug render an actor, loops through all shapes + /// and translates primitive types into drawUtil calls. + void drawActor( NxActor *inActor ); + +} // namespace PxUtils + +#endif // _PXUTILS_H_ \ No newline at end of file diff --git a/T3D/physics/physx/pxWorld.cpp b/T3D/physics/physx/pxWorld.cpp new file mode 100644 index 0000000..7c49871 --- /dev/null +++ b/T3D/physics/physx/pxWorld.cpp @@ -0,0 +1,858 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/physics/physX/pxWorld.h" + +#include "T3D/physics/physX/px.h" +#include "T3D/physics/physX/pxPlugin.h" +#include "T3D/physics/physX/pxMaterial.h" +#include "T3D/physics/physX/pxContactReporter.h" +#include "T3D/physics/physX/pxUserData.h" +#include "T3D/physics/physX/pxTSStatic.h" +#include "T3D/physics/physX/pxTerrain.h" +#include "T3D/physics/physX/pxStream.h" + +#ifdef TORQUE_DEBUG +#include +#endif +#include +#include +#include +#include +#include + +#include "core/stream/bitStream.h" +#include "platform/profiler.h" +#include "sim/netConnection.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/util/safeDelete.h" +#include "T3D/tsstatic.h" +#include "T3D/gameProcess.h" +#include "gfx/sim/debugDraw.h" + +#include + + +static const F32 PhysicsStepTime = (F32)TickMs / 1000.0f; +static const U32 PhysicsMaxIterations = 8; +static const F32 PhysicsMaxTimeStep = PhysicsStepTime / 2.0f; + +// NOTE: This matches the gravity used for player objects. +const VectorF PxWorld::smDefaultGravity( 0, 0, -20.0 ); + +NxPhysicsSDK *gPhysicsSDK = NULL; + +// The one and only physX console output stream for the process. +PxConsoleStream *gPxConsoleStream = NULL; + + + +PxWorld::PxWorld() : + mScene( NULL ), + mRigidCompartment( NULL ), + mConactReporter( NULL ), + mProcessList( NULL ), + mIsSimulating( false ), + mErrorReport( false ), + mTickCount( 0 ), + mIsEnabled( false ), + mEditorTimeScale( 1.0f ) +{ + if ( !CCTAllocator::mAllocator ) + CCTAllocator::mAllocator = new NxUserAllocatorDefault(); + mControllerManager = new CharacterControllerManager( CCTAllocator::mAllocator ); +} + +PxWorld::~PxWorld() +{ + delete mControllerManager; +} + +bool PxWorld::_init( bool isServer, ProcessList *processList ) +{ + if ( !gPhysicsSDK ) + { + Con::errorf( "PhysXWorld::init - PhysXSDK not initialized!" ); + return false; + } + + // Create the scene description. + NxSceneDesc sceneDesc; + sceneDesc.userData = this; + + // Set up default gravity. + sceneDesc.gravity.set( smDefaultGravity.x, smDefaultGravity.y, smDefaultGravity.z ); + + // The master scene is always on the CPU and is used + // mostly for static shapes. + sceneDesc.simType = NX_SIMULATION_SW; + + // Threading... seems to improve performance. + // + // TODO: I was getting random crashes in debug when doing + // edit and continue... lets see if i still get them with + // the threading disabled. + // + sceneDesc.flags |= NX_SF_ENABLE_MULTITHREAD | NX_SF_DISABLE_SCENE_MUTEX; + sceneDesc.threadMask = 0xfffffffe; + sceneDesc.internalThreadCount = 2; + + // Create the scene. + mScene = gPhysicsSDK->createScene(sceneDesc); + if ( !mScene ) + { + Con::errorf( "PhysXWorld - %s world createScene returned a null scene!", isServer ? "Server" : "Client" ); + return false; + } + + /* + // Make note of what we've created. + String simType = sceneDesc.simType == NX_SIMULATION_SW ? "software" : "hardware"; + String clientOrServer = this == isServer ? "server" : "client"; + Con::printf( "PhysXWorld::init() - Created %s %s simulation!", + clientOrServer.c_str(), + simType.c_str() ); + */ + + mScene->setTiming( PhysicsMaxTimeStep, PhysicsMaxIterations, NX_TIMESTEP_FIXED ); + + // TODO: Add dummy actor with scene name! + + // Set the global contact reporter. + + mConactReporter = new PxContactReporter(); + mScene->setUserContactReport( mConactReporter ); + + // Set the global PxUserNotify + + mUserNotify = new PxUserNotify(); + mScene->setUserNotify( mUserNotify ); + + // Now create the dynamic rigid body compartment which + // can reside on the hardware when hardware is present. + /* + NxCompartmentDesc compartmentDesc; + compartmentDesc.type = NX_SCT_RIGIDBODY; + compartmentDesc.deviceCode = NX_DC_PPU_AUTO_ASSIGN; + mRigidCompartment = mScene->createCompartment( compartmentDesc ); + if ( !mRigidCompartment ) + { + Con::errorf( "PhysXWorld - Creation of rigid body compartment failed!" ); + return false; + } + */ + + // Hook up the tick processing signals for advancing physics. + // + // First an overview of how physics and the game ticks + // interact with each other. + // + // In Torque you normally tick the server and then the client + // approximately every 32ms. So before the game tick we call + // getPhysicsResults() to get the new physics state and call + // tickPhysics() when the game tick is done to start processing + // the next physics state. This means PhysX is happily doing + // physics in a separate thread while we're doing rendering, + // sound, input, networking, etc. + // + // Because your frame rate is rarely perfectly even you can + // get cases where you may tick the server or the client + // several times in a row. This happens most often in debug + // mode, but can also happen in release. + // + // The simple implementation is to do a getPhysicsResults() and + // tickPhysics() for each tick. But this very bad! It forces + // immediate results from PhysX which blocks the primary thread + // and further slows down processing. It leads to slower and + // slower frame rates as the simulation is never able to catch + // up to the current tick. + // + // The trick is processing physics once for backlogged ticks + // with the total of the elapsed tick time. This is a huge + // performance gain and keeps you from blocking on PhysX. + // + // This does have a side effect that when it occurs you'll get + // ticks where the physics state hasn't advanced, but this beats + // single digit frame rates. + // + AssertFatal( processList, "PhysXWorld::init() - We need a process list to create the world!" ); + mProcessList = processList; + mProcessList->preTickSignal().notify( this, &PxWorld::getPhysicsResults ); + mProcessList->postTickSignal().notify( this, &PxWorld::tickPhysics, 1000.0f ); + //mProcessList->catchupModeSignal().notify( this, &PxWorld::enableCatchupMode, 1001.0f ); + + return true; +} + +void PxWorld::_destroy() +{ + // Make sure the simulation is stopped! + getPhysicsResults(); + _releaseQueues(); + + #ifdef TORQUE_DEBUG + + U32 actorCount = mScene->getNbActors(); + U32 jointCount = mScene->getNbJoints(); + + if ( actorCount != 0 || jointCount != 0 ) + { + // Dump the names of any actors or joints that + // were not released before the destruction of + // this scene. + + for ( U32 i=0; i < actorCount; i++ ) + { + const NxActor *actor = mScene->getActors()[i]; + Con::errorf( "Orphan NxActor - '%s'!", actor->getName() ); + } + + mScene->resetJointIterator(); + for ( ;; ) + { + const NxJoint *joint = mScene->getNextJoint(); + if ( !joint ) + break; + + Con::errorf( "Orphan NxJoint - '%s'!", joint->getName() ); + } + + AssertFatal( false, "PhysXWorld::_destroy() - Some actors and/or joints were not released!" ); + } + + #endif // TORQUE_DEBUG + + //NxCloseCooking(); + + // Release the tick processing signals. + if ( mProcessList ) + { + mProcessList->preTickSignal().remove( this, &PxWorld::getPhysicsResults ); + mProcessList->postTickSignal().remove( this, &PxWorld::tickPhysics ); + //mProcessList->catchupModeSignal().remove( this, &PxWorld::enableCatchupMode ); + mProcessList = NULL; + } + + // Destroy the scene. + if ( mScene ) + { + // You never destroy compartments... they are + // released along with the scene. + mRigidCompartment = NULL; + + // Delete the contact reporter. + mScene->setUserContactReport( NULL ); + SAFE_DELETE( mConactReporter ); + + // First shut down threads... this makes it + // safe to release the scene. + mScene->shutdownWorkerThreads(); + + // Release the scene. + gPhysicsSDK->releaseScene( *mScene ); + mScene = NULL; + } + + // Try to restart the sdk if we can. + //restartSDK(); +} + +bool PxWorld::restartSDK( bool destroyOnly, PxWorld *clientWorld, PxWorld *serverWorld ) +{ + // If either the client or the server still exist + // then we cannot reset the SDK. + if ( clientWorld || serverWorld ) + return false; + + // Destroy the existing SDK. + if ( gPhysicsSDK ) + { + PxTSStatic::freeMeshCache(); + + NXU::releasePersistentMemory(); + gPhysicsSDK->release(); + gPhysicsSDK = NULL; + + SAFE_DELETE( gPxConsoleStream ); + } + + // If we're not supposed to restart... return. + if ( destroyOnly ) + return true; + + gPxConsoleStream = new PxConsoleStream(); + + NxPhysicsSDKDesc sdkDesc; + sdkDesc.flags |= NX_SDKF_NO_HARDWARE; + + NxSDKCreateError error; + gPhysicsSDK = NxCreatePhysicsSDK( NX_PHYSICS_SDK_VERSION, + NULL, + gPxConsoleStream, + sdkDesc, + &error ); + if ( !gPhysicsSDK ) + { + Con::errorf( "PhysX failed to initialize! Error code: %d", error ); + Platform::messageBox( Con::getVariable( "$appName" ), + avar("PhysX could not be started!\r\n" + "Please be sure you have the latest version of PhysX installed.\r\n" + "Error Code: %d", error), + MBOk, MIStop ); + Platform::forceShutdown( -1 ); + } + + // Set the default skin width for all actors. + gPhysicsSDK->setParameter( NX_SKIN_WIDTH, 0.01f ); + + return true; +} + +void PxWorld::tickPhysics( U32 elapsedMs ) +{ + if ( !mScene || !mIsEnabled ) + return; + + // Did we forget to call getPhysicsResults somewhere? + AssertFatal( !mIsSimulating, "PhysXWorld::tickPhysics() - Already simulating!" ); + + // The elapsed time should be non-zero and + // a multiple of TickMs! + AssertFatal( elapsedMs != 0 && + ( elapsedMs % TickMs ) == 0 , "PhysXWorld::tickPhysics() - Got bad elapsed time!" ); + + PROFILE_SCOPE(PxWorld_TickPhysics); + + // Convert it to seconds. + const F32 elapsedSec = (F32)elapsedMs * 0.001f; + + // For some reason this gets reset all the time + // and it must be called before the simulate. + mScene->setFilterOps( NX_FILTEROP_OR, + NX_FILTEROP_OR, + NX_FILTEROP_AND ); + mScene->setFilterBool( false ); + NxGroupsMask zeroMask; + zeroMask.bits0=zeroMask.bits1=zeroMask.bits2=zeroMask.bits3=0; + mScene->setFilterConstant0( zeroMask ); + mScene->setFilterConstant1( zeroMask ); + + // Simulate it. + if ( mEditorTimeScale <= 0.0f ) + Con::printf( "Editor time scale is %g", mEditorTimeScale ); + + mScene->simulate( elapsedSec * mEditorTimeScale ); + mScene->flushStream(); + mIsSimulating = true; + + //Con::printf( "%s PhysXWorld::tickPhysics!", this == smClientWorld ? "Client" : "Server" ); +} + +/* +void PxWorld::enableCatchupMode( GameBase *obj ) +{ + PxUserData *testUserData = NULL; + NxActor *testActor = NULL; + + if ( obj ) + { + U32 actorCount = mScene->getNbActors(); + NxActor **actors = mScene->getActors(); + + for ( U32 i = 0; i < actorCount; i++ ) + { + testActor = actors[i]; + + testUserData = static_cast( testActor->userData ); + + // If this actor is for our control object + // or it's a kinematic, skip it. + if ( !testUserData || + testUserData->getObject() == (SceneObject*)obj || + testUserData->isStatic() || + testActor->readBodyFlag( NX_BF_KINEMATIC ) ) + continue; + + // If we got down here, the actor + // isn't for our control object, + // it's not a static, and it's not kinematic. + + // So turn it kinematic and + // push it into the actor list. + testActor->raiseBodyFlag( NX_BF_KINEMATIC ); + mCatchupQueue.push_back( testActor ); + } + } + else if ( !obj ) + { + // Easy case, we want to restore the + // state of all the actors in the catchup queue. + for ( U32 i = 0; i < mCatchupQueue.size(); i++ ) + { + testActor = mCatchupQueue[i]; + testActor->clearBodyFlag( NX_BF_KINEMATIC ); + } + } +} +*/ + +void PxWorld::releaseWriteLocks() +{ + PxWorld *world = dynamic_cast( gPhysicsPlugin->getWorld( "server" ) ); + + if ( world ) + world->releaseWriteLock(); + + world = dynamic_cast( gPhysicsPlugin->getWorld( "client" ) ); + + if ( world ) + world->releaseWriteLock(); +} + +void PxWorld::releaseWriteLock() +{ + if ( !mScene || !mIsSimulating ) + return; + + PROFILE_SCOPE(PxWorld_ReleaseWriteLock); + + // We use checkResults here to release the write lock + // but we do not change the simulation flag or increment + // the tick count... we may have gotten results, but the + // simulation hasn't really ticked! + mScene->checkResults( NX_RIGID_BODY_FINISHED, true ); + AssertFatal( mScene->isWritable(), "PhysXWorld::releaseWriteLock() - We should have been writable now!" ); +} + +void PxWorld::getPhysicsResults() +{ + if ( !mScene || !mIsSimulating ) + return; + + PROFILE_SCOPE(PxWorld_GetPhysicsResults); + + // Get results from scene. + mScene->fetchResults( NX_RIGID_BODY_FINISHED, true ); + mIsSimulating = false; + mTickCount++; + + // Take this opportunity to update PxTerrain objects. + _updateTerrain(); + + // Release any joints/actors that were waiting + // for the scene to become writable. + _releaseQueues(); + + //Con::printf( "%s PhysXWorld::getPhysicsResults!", this == smClientWorld ? "Client" : "Server" ); +} + +NxMaterial* PxWorld::createMaterial( NxMaterialDesc &material ) +{ + if ( !mScene ) + return NULL; + + // We need the writelock to create a material! + releaseWriteLock(); + + NxMaterial *mat = mScene->createMaterial( material ); + if ( !mat ) + return NULL; + + return mat; +} + +NxController* PxWorld::createController( NxControllerDesc &desc ) +{ + if ( !mScene ) + return NULL; + + // We need the writelock! + releaseWriteLock(); + + return mControllerManager->createController( mScene, desc ); +} + +void PxWorld::releaseActor( NxActor &actor ) +{ + AssertFatal( &actor.getScene() == mScene, "PhysXWorld::releaseActor() - Bad scene!" ); + + // Clear the userdata. + actor.userData = NULL; + + // If the scene is not simulating then we have the + // write lock and can safely delete it now. + if ( !mIsSimulating ) + { + bool isStatic = !actor.isDynamic(); + mScene->releaseActor( actor ); + if ( isStatic ) + PhysicsStatic::smDeleteSignal.trigger(); + } + else + mReleaseActorQueue.push_back( &actor ); +} + +void PxWorld::releaseHeightField( NxHeightField &heightfield ) +{ + // Always delay releasing a heightfield, for whatever reason, + // it causes lots of deadlock asserts if we do it here, even if + // the scene "says" its writable. + // + // Actually this is probably because a heightfield is owned by the "sdk" and + // not an individual scene so if either the client "or" server scene are + // simulating it asserts, thats just my theory. + + mReleaseHeightFieldQueue.push_back( &heightfield ); +} + +void PxWorld::releaseJoint( NxJoint &joint ) +{ + AssertFatal( &joint.getScene() == mScene, "PhysXWorld::releaseJoint() - Bad scene!" ); + + AssertFatal( !mReleaseJointQueue.contains( &joint ), + "PhysXWorld::releaseJoint() - Joint already exists in the release queue!" ); + + // Clear the userdata. + joint.userData = NULL; + + // If the scene is not simulating then we have the + // write lock and can safely delete it now. + if ( !mIsSimulating ) + mScene->releaseJoint( joint ); + else + mReleaseJointQueue.push_back( &joint ); +} + +void PxWorld::releaseCloth( NxCloth &cloth ) +{ + AssertFatal( &cloth.getScene() == mScene, "PhysXWorld::releaseCloth() - Bad scene!" ); + + // Clear the userdata. + cloth.userData = NULL; + + // If the scene is not simulating then we have the + // write lock and can safely delete it now. + if ( !mIsSimulating ) + mScene->releaseCloth( cloth ); + else + mReleaseClothQueue.push_back( &cloth ); +} + +void PxWorld::releaseClothMesh( NxClothMesh &clothMesh ) +{ + // We need the writelock to release. + releaseWriteLock(); + + gPhysicsSDK->releaseClothMesh( clothMesh ); +} + +void PxWorld::releaseController( NxController &controller ) +{ + // TODO: This isn't safe to do with actors and + // joints, so we probably need a queue like we + // do for them. + + // We need the writelock to release. + releaseWriteLock(); + + mControllerManager->releaseController( controller ); +} + +void PxWorld::_updateTerrain() +{ + for ( U32 i = 0; i < mTerrainUpdateQueue.size(); i++ ) + { + mTerrainUpdateQueue[i]->_scheduledUpdate(); + mTerrainUpdateQueue.erase_fast(i); + i--; + } +} + +void PxWorld::_releaseQueues() +{ + AssertFatal( mScene, "PhysXWorld::_releaseQueues() - The scene is null!" ); + + // We release joints still pending in the queue + // first as they depend on the actors. + for ( S32 i = 0; i < mReleaseJointQueue.size(); i++ ) + { + NxJoint *currJoint = mReleaseJointQueue[i]; + mScene->releaseJoint( *currJoint ); + } + + // All the joints should be released, clear the queue. + mReleaseJointQueue.clear(); + + // Now release any actors still pending in the queue. + for ( S32 i = 0; i < mReleaseActorQueue.size(); i++ ) + { + NxActor *currActor = mReleaseActorQueue[i]; + + bool isStatic = !currActor->isDynamic(); + + mScene->releaseActor( *currActor ); + + if ( isStatic ) + PhysicsStatic::smDeleteSignal.trigger(); + } + + // All the actors should be released, clear the queue. + mReleaseActorQueue.clear(); + + // Now release any cloth still pending in the queue. + for ( S32 i = 0; i < mReleaseClothQueue.size(); i++ ) + { + NxCloth *currCloth = mReleaseClothQueue[i]; + mScene->releaseCloth( *currCloth ); + } + + // All the actors should be released, clear the queue. + mReleaseClothQueue.clear(); + + // Release heightfields that don't still have references. + for ( S32 i = 0; i < mReleaseHeightFieldQueue.size(); i++ ) + { + NxHeightField *currHeightfield = mReleaseHeightFieldQueue[i]; + + if ( currHeightfield->getReferenceCount() == 0 ) + { + gPhysicsSDK->releaseHeightField( *currHeightfield ); + mReleaseHeightFieldQueue.erase_fast( i ); + i--; + } + } +} + +void PxWorld::setEnabled( bool enabled ) +{ + mIsEnabled = enabled; + + if ( !mIsEnabled ) + getPhysicsResults(); +} + +ConsoleFunction( physXRemoteDebuggerConnect, bool, 1, 3, "" ) +{ + if ( !gPhysicsSDK ) + { + Con::errorf( "PhysX SDK not initialized!" ); + return false; + } + + NxRemoteDebugger *debugger = gPhysicsSDK->getFoundationSDK().getRemoteDebugger(); + + if ( debugger->isConnected() ) + { + Con::errorf( "RemoteDebugger already connected... call disconnect first!" ); + return false; + } + + const UTF8 *host = "localhost"; + U32 port = 5425; + + if ( argc >= 2 ) + host = argv[1]; + if ( argc >= 3 ) + port = dAtoi( argv[2] ); + + // Before we connect we need to have write access + // to both the client and server worlds. + PxWorld::releaseWriteLocks(); + + // Connect! + debugger->connect( host, port ); + if ( !debugger->isConnected() ) + { + Con::errorf( "RemoteDebugger failed to connect!" ); + return false; + } + + Con::printf( "RemoteDebugger connected to %s at port %u!", host, port ); + return true; +} + +ConsoleFunction( physXRemoteDebuggerDisconnect, void, 1, 1, "" ) +{ + if ( !gPhysicsSDK ) + { + Con::errorf( "PhysX SDK not initialized!" ); + return; + } + + NxRemoteDebugger *debugger = gPhysicsSDK->getFoundationSDK().getRemoteDebugger(); + + if ( debugger->isConnected() ) + { + debugger->flush(); + debugger->disconnect(); + Con::printf( "RemoteDebugger disconnected!" ); + } +} + +bool PxWorld::initWorld( bool isServer, ProcessList *processList ) +{ + /* This stuff is handled outside. + PxWorld* world = PxWorld::getWorld( isServer ); + if ( world ) + { + Con::errorf( "PhysXWorld::initWorld - %s world already exists!", isServer ? "Server" : "Client" ); + return false; + } + */ + + if ( !_init( isServer, processList ) ) + return false; + + return true; +} + +void PxWorld::destroyWorld() +{ + //PxWorld* world = PxWorld::getWorld( serverWorld ); + /* + if ( !world ) + { + Con::errorf( "PhysXWorld::destroyWorld - %s world already destroyed!", serverWorld ? "Server" : "Client" ); + return; + } + */ + //world->_destroy(); + //delete world; + + _destroy(); +} + +bool PxWorld::castRay( const Point3F &startPnt, const Point3F &endPnt, RayInfo *ri, const Point3F &impulse ) +{ + NxRay worldRay; + worldRay.orig = pxCast( startPnt ); + worldRay.dir = pxCast( endPnt - startPnt ); + NxF32 maxDist = worldRay.dir.magnitude(); + worldRay.dir.normalize(); + + NxRaycastHit hitInfo; + NxShape *hitShape = mScene->raycastClosestShape( worldRay, NX_ALL_SHAPES, hitInfo, 0xffffffff, maxDist ); + + if ( !hitShape ) + return false; + + //if ( hitShape->userData != NULL ) + // return false; + + NxActor &actor = hitShape->getActor(); + PxUserData *userData = PxUserData::getData( actor ); + + if ( ri ) + { + ri->object = ( userData != NULL ) ? userData->getObject() : NULL; + + // If we were passed a RayInfo, we can only return true signifying a collision + // if we hit an object that actually has a torque object associated with it. + // + // In some ways this could be considered an error, either a physx object + // has raycast-collision enabled that shouldn't or someone did not set + // an object in this actor's userData. + // + if ( ri->object == NULL ) + return false; + + ri->distance = hitInfo.distance; + ri->normal = pxCast( hitInfo.worldNormal ); + ri->point = pxCast( hitInfo.worldImpact ); + ri->t = maxDist / hitInfo.distance; + } + + if ( impulse.isZero() || + !actor.isDynamic() || + actor.readBodyFlag( NX_BF_KINEMATIC ) ) + return true; + + NxVec3 force = pxCast( impulse );//worldRay.dir * forceAmt; + actor.addForceAtPos( force, hitInfo.worldImpact, NX_IMPULSE ); + + return true; +} + +void PxWorld::explosion( const Point3F &pos, F32 radius, F32 forceMagnitude ) +{ + // Find Actors at the position within the radius + // and apply force to them. + + NxVec3 nxPos = pxCast( pos ); + NxShape **shapes = (NxShape**)NxAlloca(10*sizeof(NxShape*)); + NxSphere worldSphere( nxPos, radius ); + + NxU32 numHits = mScene->overlapSphereShapes( worldSphere, NX_ALL_SHAPES, 10, shapes, NULL ); + + for ( NxU32 i = 0; i < numHits; i++ ) + { + NxActor &actor = shapes[i]->getActor(); + + bool dynamic = actor.isDynamic(); + + if ( !dynamic ) + continue; + + bool kinematic = actor.readBodyFlag( NX_BF_KINEMATIC ); + + if ( kinematic ) + continue; + + NxVec3 force = actor.getGlobalPosition() - nxPos; + force.normalize(); + force *= forceMagnitude; + + actor.addForceAtPos( force, nxPos, NX_IMPULSE, true ); + } +} + +void PxWorld::scheduleUpdate( PxTerrain *terrain ) +{ + mTerrainUpdateQueue.push_back_unique( terrain ); +} + +void PxWorld::unscheduleUpdate( PxTerrain *terrain ) +{ + mTerrainUpdateQueue.remove( terrain ); +} + +ConsoleFunction( castForceRay, const char*, 4, 4, "( Point3F startPnt, Point3F endPnt, VectorF impulseVec )" ) +{ + PhysicsWorld *world = gPhysicsPlugin->getWorld( "server" ); + if ( !world ) + return NULL; + + char *returnBuffer = Con::getReturnBuffer(256); + + Point3F impulse; + Point3F startPnt, endPnt; + dSscanf( argv[1], "%f %f %f", &startPnt.x, &startPnt.y, &startPnt.z ); + dSscanf( argv[2], "%f %f %f", &endPnt.x, &endPnt.y, &endPnt.z ); + dSscanf( argv[3], "%f %f %f", &impulse.x, &impulse.y, &impulse.z ); + + Point3F hitPoint; + + RayInfo rinfo; + + bool hit = world->castRay( startPnt, endPnt, &rinfo, impulse ); + + DebugDrawer *ddraw = DebugDrawer::get(); + if ( ddraw ) + { + ddraw->drawLine( startPnt, endPnt, hit ? ColorF::RED : ColorF::GREEN ); + ddraw->setLastTTL( 3000 ); + } + + if ( hit ) + { + dSprintf(returnBuffer, 256, "%g %g %g", + rinfo.point.x, rinfo.point.y, rinfo.point.z ); + return returnBuffer; + } + else + return NULL; +} \ No newline at end of file diff --git a/T3D/physics/physx/pxWorld.h b/T3D/physics/physx/pxWorld.h new file mode 100644 index 0000000..45b88d2 --- /dev/null +++ b/T3D/physics/physx/pxWorld.h @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PHYSX_WORLD_H_ +#define _PHYSX_WORLD_H_ + +#ifndef _T3D_PHYSICS_PHYSICSWORLD_H_ +#include "T3D/physics/physicsWorld.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _PHYSX_H_ +#include "T3D/physics/physX/px.h" +#endif +#ifndef _PHYSX_CASTS_H_ +#include "T3D/physics/physX/pxCasts.h" +#endif +#ifndef _SIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class PxContactReporter; +class PxUserNotify; +class NxController; +class NxControllerDesc; +class ShapeBase; +class TSStatic; +class SceneObject; +class ProcessList; +class GameBase; +class CharacterControllerManager; +class PxTerrain; + + +class PxWorld : public PhysicsWorld +{ +protected: + + F32 mEditorTimeScale; + + Vector mReleaseClothQueue; + Vector mReleaseJointQueue; + Vector mReleaseActorQueue; + Vector mReleaseHeightFieldQueue; + + Vector mTerrainUpdateQueue; + + Vector mCatchupQueue; + + PxContactReporter *mConactReporter; + + PxUserNotify *mUserNotify; + + NxScene *mScene; + + CharacterControllerManager *mControllerManager; + + /// The hardware accelerated compartment used + /// for high performance dynamic rigid bodies. + NxCompartment *mRigidCompartment; + + static const VectorF smDefaultGravity; + + bool mErrorReport; + + bool mIsEnabled; + + bool mIsSimulating; + + U32 mTickCount; + + ProcessList *mProcessList; + + bool _init( bool isServer, ProcessList *processList ); + + void _destroy(); + + void _releaseQueues(); + + void _updateTerrain(); + +public: + + /// @name Overloaded PhysicWorld Methods + /// @{ + + virtual bool initWorld( bool isServer, ProcessList *processList ); + + virtual void destroyWorld(); + + virtual bool castRay( const Point3F &startPnt, const Point3F &endPnt, RayInfo *ri, const Point3F &impulse ); + + virtual void explosion( const Point3F &pos, F32 radius, F32 forceMagnitude ); + + /// @} + + /// @name Static Methods + /// @{ + + static bool restartSDK( bool destroyOnly = false, PxWorld *clientWorld = NULL, PxWorld *serverWorld = NULL ); + + static void releaseWriteLocks(); + + /// @} + + PxWorld(); + virtual ~PxWorld(); + +public: + + NxScene* getScene() { return mScene; } + + NxCompartment* getRigidCompartment() { return mRigidCompartment; } + + U32 getTick() { return mTickCount; } + + void tickPhysics( U32 elapsedMs ); + + void getPhysicsResults(); + + //void enableCatchupMode( GameBase *obj ); + + bool isWritable() const { return !mIsSimulating; /* mScene->isWritable(); */ } + + void releaseWriteLock(); + + void setEnabled( bool enabled ); + bool getEnabled() const { return mIsEnabled; } + + NxMaterial* createMaterial( NxMaterialDesc &material ); + + /// + /// @see releaseController + NxController* createController( NxControllerDesc &desc ); + + //U16 setMaterial(NxMaterialDesc &material, U16 id); + + void releaseActor( NxActor &actor ); + + void releaseJoint( NxJoint &joint ); + + void releaseCloth( NxCloth &cloth ); + + void releaseClothMesh( NxClothMesh &clothMesh ); + + void releaseController( NxController &controller ); + + void releaseHeightField( NxHeightField &heightfield ); + + /// Returns the contact reporter for this scene. + PxContactReporter* getContactReporter() { return mConactReporter; } + + void setEditorTimeScale( F32 timeScale ) { mEditorTimeScale = timeScale; } + const F32 getEditorTimeScale() const { return mEditorTimeScale; } + + void scheduleUpdate( PxTerrain *terrain ); + void unscheduleUpdate( PxTerrain *terrain ); + +}; + +#endif // _PHYSX_WORLD_H_ diff --git a/T3D/player.cpp b/T3D/player.cpp new file mode 100644 index 0000000..4c0f5b4 --- /dev/null +++ b/T3D/player.cpp @@ -0,0 +1,5015 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/player.h" + +#include "platform/profiler.h" +#include "math/mMath.h" +#include "math/mathIO.h" +#include "core/stringTable.h" +#include "core/stream/bitStream.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "collision/extrudedPolyList.h" +#include "collision/clippedPolyList.h" +#include "collision/earlyOutPolyList.h" +#include "ts/tsShapeInstance.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "environment/waterBlock.h" +#include "app/game.h" +#include "T3D/gameConnection.h" +#include "T3D/trigger.h" +#include "T3D/physicalZone.h" +#include "T3D/item.h" +#include "T3D/missionArea.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/fx/cameraFXMgr.h" +#include "T3D/fx/splash.h" +#include "T3D/tsStatic.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsPlayer.h" +#include "T3D/decal/decalManager.h" +#include "T3D/decal/decalData.h" + + +//---------------------------------------------------------------------------- + +// Amount of time if takes to transition to a new action sequence. +static F32 sAnimationTransitionTime = 0.25f; +static bool sUseAnimationTransitions = true; +static F32 sLandReverseScale = 0.25f; +static F32 sSlowStandThreshSquared = 1.69f; +static S32 sRenderMyPlayer = true; +static S32 sRenderMyItems = true; +static S32 sRenderPlayerCollision = false; + +// Chooses new action animations every n ticks. +static const F32 sNewAnimationTickTime = 4.0f; +static const F32 sMountPendingTickWait = 13.0f * F32(TickMs); + +// Number of ticks before we pick non-contact animations +static const S32 sContactTickTime = 10; + +// Downward velocity at which we consider the player falling +static const F32 sFallingThreshold = -10.0f; + +// Movement constants +static F32 sVerticalStepDot = 0.173f; // 80 +static F32 sMinFaceDistance = 0.01f; +static F32 sTractionDistance = 0.04f; +static F32 sNormalElasticity = 0.01f; +static U32 sMoveRetryCount = 5; + +// Client prediction +static F32 sMinWarpTicks = 0.5f; // Fraction of tick at which instant warp occurs +static S32 sMaxWarpTicks = 3; // Max warp duration in ticks +static S32 sMaxPredictionTicks = 30; // Number of ticks to predict + +// Anchor point compression +const F32 sAnchorMaxDistance = 32.0f; + +// +static U32 sCollisionMoveMask = TerrainObjectType | + InteriorObjectType | + WaterObjectType | + PlayerObjectType | + StaticShapeObjectType | + VehicleObjectType | + PhysicalZoneObjectType | + StaticTSObjectType; + +static U32 sServerCollisionContactMask = sCollisionMoveMask | + ItemObjectType | + TriggerObjectType | + CorpseObjectType; + +static U32 sClientCollisionContactMask = sCollisionMoveMask | + TriggerObjectType; + +enum PlayerConstants { + JumpSkipContactsMax = 8 +}; + +//---------------------------------------------------------------------------- +// Player shape animation sequences: + +// look Used to control the upper body arm motion. Must animate +// vertically +-80 deg. +Player::Range Player::mArmRange(mDegToRad(-80.0f),mDegToRad(+80.0f)); + +// head Used to control the direction the head is looking. Must +// animated vertically +-80 deg . +Player::Range Player::mHeadVRange(mDegToRad(-80.0f),mDegToRad(+80.0f)); + +// Action Animations: +PlayerData::ActionAnimationDef PlayerData::ActionAnimationList[NumTableActionAnims] = +{ + // *** WARNING *** + // This array is indexed using the enum values defined in player.h + + // Root is the default animation + { "root" }, // RootAnim, + + // These are selected in the move state based on velocity + { "run", { 0.0f, 1.0f, 0.0f } }, // RunForwardAnim, + { "back", { 0.0f, -1.0f, 0.0f } }, // BackBackwardAnim + { "side", { -1.0f, 0.0f, 0.0f } }, // SideLeftAnim, + + { "crouch_root" }, + { "crouch_forward" }, + { "prone_root" }, + { "prone_forward" }, + { "swim_root" }, + { "swim_forward" }, + { "swim_backward" }, + { "swim_left" }, + { "swim_right" }, + + // These are set explicitly based on player actions + { "fall" }, // FallAnim + { "jump" }, // JumpAnim + { "standjump" }, // StandJumpAnim + { "land" }, // LandAnim + { "jet" }, // JetAnim +}; + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(PlayerData); + +PlayerData::PlayerData() +{ + shadowEnable = true; + shadowSize = 256; + shadowProjectionDistance = 14.0f; + + + renderFirstPerson = true; + pickupRadius = 0.0f; + minLookAngle = -1.4f; + maxLookAngle = 1.4f; + maxFreelookAngle = 3.0f; + maxTimeScale = 1.5f; + + mass = 9.0f; // from ShapeBase + maxEnergy = 60.0f; // from ShapeBase + drag = 0.3f; // from ShapeBase + density = 1.1f; // from ShapeBase + + runForce = 40.0f * 9.0f; + runEnergyDrain = 0.0f; + minRunEnergy = 0.0f; + maxForwardSpeed = 10.0f; + maxBackwardSpeed = 10.0f; + maxSideSpeed = 10.0f; + + maxStepHeight = 1.0f; + runSurfaceAngle = 80.0f; + + recoverDelay = 30; + recoverRunForceScale = 1.0f; + + // Jumping + jumpForce = 75.0f; + jumpEnergyDrain = 0.0f; + minJumpEnergy = 0.0f; + jumpSurfaceAngle = 78.0f; + jumpDelay = 30; + minJumpSpeed = 500.0f; + maxJumpSpeed = 2.0f * minJumpSpeed; + + // Swimming + swimForce = 55.0f * 9.0f; + maxUnderwaterForwardSpeed = 6.0f; + maxUnderwaterBackwardSpeed = 6.0f; + maxUnderwaterSideSpeed = 6.0f; + + // Crouching + crouchForce = 45.0f * 9.0f; + maxCrouchForwardSpeed = 4.0f; + maxCrouchBackwardSpeed = 4.0f; + maxCrouchSideSpeed = 4.0f; + + // Prone + proneForce = 45.0f * 9.0f; + maxProneForwardSpeed = 2.0f; + maxProneBackwardSpeed = 2.0f; + maxProneSideSpeed = 0.0f; + + // Jetting + jetJumpForce = 0; + jetJumpEnergyDrain = 0; + jetMinJumpEnergy = 0; + jetJumpSurfaceAngle = 78; + jetMinJumpSpeed = 20; + jetMaxJumpSpeed = 100; + + horizMaxSpeed = 80.0f; + horizResistSpeed = 38.0f; + horizResistFactor = 1.0f; + + upMaxSpeed = 80.0f; + upResistSpeed = 38.0f; + upResistFactor = 1.0f; + + minImpactSpeed = 25.0f; + + decalData = NULL; + decalID = 0; + decalOffset = 0.0f; + + lookAction = 0; + + // size of bounding box + boxSize.set(1.0f, 1.0f, 2.3f); + crouchBoxSize.set(1.0f, 1.0f, 2.0f); + proneBoxSize.set(1.0f, 2.3f, 1.0f); + swimBoxSize.set(1.0f, 2.3f, 1.0f); + + // location of head, torso, legs + boxHeadPercentage = 0.85f; + boxTorsoPercentage = 0.55f; + + // damage locations + boxHeadLeftPercentage = 0; + boxHeadRightPercentage = 1; + boxHeadBackPercentage = 0; + boxHeadFrontPercentage = 1; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = NULL; + + footPuffEmitter = NULL; + footPuffID = 0; + footPuffNumParts = 15; + footPuffRadius = .25f; + + dustEmitter = NULL; + dustID = 0; + + splash = NULL; + splashId = 0; + splashVelocity = 1.0f; + splashAngle = 45.0f; + splashFreqMod = 300.0f; + splashVelEpsilon = 0.25f; + bubbleEmitTime = 0.4f; + + medSplashSoundVel = 2.0f; + hardSplashSoundVel = 3.0f; + exitSplashSoundVel = 2.0f; + footSplashHeight = 0.1f; + + dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) ); + dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) ); + + groundImpactMinSpeed = 10.0f; + groundImpactShakeFreq.set( 10.0f, 10.0f, 10.0f ); + groundImpactShakeAmp.set( 20.0f, 20.0f, 20.0f ); + groundImpactShakeDuration = 1.0f; + groundImpactShakeFalloff = 10.0f; + + // Air control + airControl = 0.0f; + + jumpTowardsNormal = true; + + physicsPlayerType = StringTable->insert(""); + + dMemset( actionList, 0, sizeof(actionList) ); +} + +bool PlayerData::preload(bool server, String &errorStr) +{ + if(!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) { + for (S32 i = 0; i < MaxSounds; i++) + if (sound[i]) + Sim::findObject(SimObjectId(sound[i]),sound[i]); + } + + // + runSurfaceCos = mCos(mDegToRad(runSurfaceAngle)); + jumpSurfaceCos = mCos(mDegToRad(jumpSurfaceAngle)); + if (minJumpEnergy < jumpEnergyDrain) + minJumpEnergy = jumpEnergyDrain; + + // Jetting + if (jetMinJumpEnergy < jetJumpEnergyDrain) + jetMinJumpEnergy = jetJumpEnergyDrain; + + // Validate some of the data + if (recoverDelay > (1 << RecoverDelayBits) - 1) { + recoverDelay = (1 << RecoverDelayBits) - 1; + Con::printf("PlayerData:: Recover delay exceeds range (0-%d)",recoverDelay); + } + if (jumpDelay > (1 << JumpDelayBits) - 1) { + jumpDelay = (1 << JumpDelayBits) - 1; + Con::printf("PlayerData:: Jump delay exceeds range (0-%d)",jumpDelay); + } + + // If we don't have a shape don't crash out trying to + // setup animations and sequences. + if ( mShape ) + { + // Go ahead a pre-load the player shape + TSShapeInstance* si = new TSShapeInstance(mShape, false); + TSThread* thread = si->addThread(); + + // Extract ground transform velocity from animations + // Get the named ones first so they can be indexed directly. + ActionAnimation *dp = &actionList[0]; + for (int i = 0; i < NumTableActionAnims; i++,dp++) + { + ActionAnimationDef *sp = &ActionAnimationList[i]; + dp->name = sp->name; + dp->dir.set(sp->dir.x,sp->dir.y,sp->dir.z); + dp->sequence = mShape->findSequence(sp->name); + dp->velocityScale = true; + dp->death = false; + if (dp->sequence != -1) + getGroundInfo(si,thread,dp); + + // No real reason to spam the console about a missing jet animation + if (dStricmp(sp->name, "jet") != 0) + AssertWarn(dp->sequence != -1, avar("PlayerData::preload - Unable to find named animation sequence '%s'!", sp->name)); + } + for (int b = 0; b < mShape->sequences.size(); b++) + { + if (!isTableSequence(b)) + { + dp->sequence = b; + dp->name = mShape->getName(mShape->sequences[b].nameIndex); + dp->velocityScale = false; + getGroundInfo(si,thread,dp++); + } + } + actionCount = dp - actionList; + AssertFatal(actionCount <= NumActionAnims, "Too many action animations!"); + delete si; + + // Resolve lookAction index + dp = &actionList[0]; + String lookName("look"); + for (int c = 0; c < actionCount; c++,dp++) + if( dStricmp( dp->name, lookName ) == 0 ) + lookAction = c; + + // Resolve spine + spineNode[0] = mShape->findNode("Bip01 Pelvis"); + spineNode[1] = mShape->findNode("Bip01 Spine"); + spineNode[2] = mShape->findNode("Bip01 Spine1"); + spineNode[3] = mShape->findNode("Bip01 Spine2"); + spineNode[4] = mShape->findNode("Bip01 Neck"); + spineNode[5] = mShape->findNode("Bip01 Head"); + + // Recoil animations + recoilSequence[0] = mShape->findSequence("light_recoil"); + recoilSequence[1] = mShape->findSequence("medium_recoil"); + recoilSequence[2] = mShape->findSequence("heavy_recoil"); + } + + // Convert pickupRadius to a delta of boundingBox + // + // NOTE: it is not really correct to precalculate a pickupRadius based + // on boxSize since the actual player's bounds can vary by "pose". + // + F32 dr = (boxSize.x > boxSize.y)? boxSize.x: boxSize.y; + if (pickupRadius < dr) + pickupRadius = dr; + else + if (pickupRadius > 2.0f * dr) + pickupRadius = 2.0f * dr; + pickupDelta = (S32)(pickupRadius - dr); + + // Validate jump speed + if (maxJumpSpeed <= minJumpSpeed) + maxJumpSpeed = minJumpSpeed + 0.1f; + + // Load up all the emitters + if (!footPuffEmitter && footPuffID != 0) + if (!Sim::findObject(footPuffID, footPuffEmitter)) + Con::errorf(ConsoleLogEntry::General, "PlayerData::preload - Invalid packet, bad datablockId(footPuffEmitter): 0x%x", footPuffID); + + if (!decalData && decalID != 0 ) + if (!Sim::findObject(decalID, decalData)) + Con::errorf(ConsoleLogEntry::General, "PlayerData::preload Invalid packet, bad datablockId(decalData): 0x%x", decalID); + + if (!dustEmitter && dustID != 0 ) + if (!Sim::findObject(dustID, dustEmitter)) + Con::errorf(ConsoleLogEntry::General, "PlayerData::preload - Invalid packet, bad datablockId(dustEmitter): 0x%x", dustID); + + for (int i=0; ideath = !dStrnicmp(dp->name, "death", 5); + if (dp->death) + { + // Death animations use roll frame-to-frame changes in ground transform into position + dp->speed = 0.0f; + dp->dir.set(0.0f, 0.0f, 0.0f); + } + else + { + VectorF save = dp->dir; + si->setSequence(thread,dp->sequence,0); + si->animate(); + si->advanceTime(1); + si->animateGround(); + si->getGroundTransform().getColumn(3,&dp->dir); + if ((dp->speed = dp->dir.len()) < 0.01f) + { + // No ground displacement... In this case we'll use the + // default table entry, if there is one. + if (save.len() > 0.01f) + { + dp->dir = save; + dp->speed = 1.0f; + dp->velocityScale = false; + } + else + dp->speed = 0.0f; + } + else + dp->dir *= 1.0f / dp->speed; + } +} + +bool PlayerData::isTableSequence(S32 seq) +{ + // The sequences from the table must already have + // been loaded for this to work. + for (int i = 0; i < NumTableActionAnims; i++) + if (actionList[i].sequence == seq) + return true; + return false; +} + +bool PlayerData::isJumpAction(U32 action) +{ + return (action == JumpAnim || action == StandJumpAnim); +} + +void PlayerData::initPersistFields() +{ + addField("renderFirstPerson", TypeBool, Offset(renderFirstPerson, PlayerData)); + addField("pickupRadius", TypeF32, Offset(pickupRadius, PlayerData)); + + addField("minLookAngle", TypeF32, Offset(minLookAngle, PlayerData)); + addField("maxLookAngle", TypeF32, Offset(maxLookAngle, PlayerData)); + addField("maxFreelookAngle", TypeF32, Offset(maxFreelookAngle, PlayerData)); + + addField("maxTimeScale", TypeF32, Offset(maxTimeScale, PlayerData)); + + addField("maxStepHeight", TypeF32, Offset(maxStepHeight, PlayerData)); + addField("runForce", TypeF32, Offset(runForce, PlayerData)); + addField("runEnergyDrain", TypeF32, Offset(runEnergyDrain, PlayerData)); + addField("minRunEnergy", TypeF32, Offset(minRunEnergy, PlayerData)); + addField("maxForwardSpeed", TypeF32, Offset(maxForwardSpeed, PlayerData)); + addField("maxBackwardSpeed", TypeF32, Offset(maxBackwardSpeed, PlayerData)); + addField("maxSideSpeed", TypeF32, Offset(maxSideSpeed, PlayerData)); + addField("runSurfaceAngle", TypeF32, Offset(runSurfaceAngle, PlayerData)); + addField("minImpactSpeed", TypeF32, Offset(minImpactSpeed, PlayerData)); + + addField("recoverDelay", TypeS32, Offset(recoverDelay, PlayerData)); + addField("recoverRunForceScale", TypeF32, Offset(recoverRunForceScale, PlayerData)); + + addField("jumpForce", TypeF32, Offset(jumpForce, PlayerData)); + addField("jumpEnergyDrain", TypeF32, Offset(jumpEnergyDrain, PlayerData)); + addField("minJumpEnergy", TypeF32, Offset(minJumpEnergy, PlayerData)); + addField("minJumpSpeed", TypeF32, Offset(minJumpSpeed, PlayerData)); + addField("maxJumpSpeed", TypeF32, Offset(maxJumpSpeed, PlayerData)); + addField("jumpSurfaceAngle", TypeF32, Offset(jumpSurfaceAngle, PlayerData)); + addField("jumpDelay", TypeS32, Offset(jumpDelay, PlayerData)); + + // Swimming + addField("swimForce", TypeF32, Offset(swimForce, PlayerData)); + addField("maxUnderwaterForwardSpeed", TypeF32, Offset(maxUnderwaterForwardSpeed, PlayerData)); + addField("maxUnderwaterBackwardSpeed", TypeF32, Offset(maxUnderwaterBackwardSpeed, PlayerData)); + addField("maxUnderwaterSideSpeed", TypeF32, Offset(maxUnderwaterSideSpeed, PlayerData)); + + // Crouching + addField("crouchForce", TypeF32, Offset(crouchForce, PlayerData)); + addField("maxCrouchForwardSpeed", TypeF32, Offset(maxCrouchForwardSpeed, PlayerData)); + addField("maxCrouchBackwardSpeed", TypeF32, Offset(maxCrouchBackwardSpeed, PlayerData)); + addField("maxCrouchSideSpeed", TypeF32, Offset(maxCrouchSideSpeed, PlayerData)); + + // Prone + addField("proneForce", TypeF32, Offset(proneForce, PlayerData)); + addField("maxProneForwardSpeed", TypeF32, Offset(maxProneForwardSpeed, PlayerData)); + addField("maxProneBackwardSpeed", TypeF32, Offset(maxProneBackwardSpeed, PlayerData)); + addField("maxProneSideSpeed", TypeF32, Offset(maxProneSideSpeed, PlayerData)); + + // Jetting + addField("jetJumpForce", TypeF32, Offset(jetJumpForce, PlayerData)); + addField("jetJumpEnergyDrain", TypeF32, Offset(jetJumpEnergyDrain, PlayerData)); + addField("jetMinJumpEnergy", TypeF32, Offset(jetMinJumpEnergy, PlayerData)); + addField("jetMinJumpSpeed", TypeF32, Offset(jetMinJumpSpeed, PlayerData)); + addField("jetMaxJumpSpeed", TypeF32, Offset(jetMaxJumpSpeed, PlayerData)); + addField("jetJumpSurfaceAngle", TypeF32, Offset(jetJumpSurfaceAngle, PlayerData)); + + addField("boundingBox", TypePoint3F, Offset(boxSize, PlayerData)); + addField("crouchBoundingBox", TypePoint3F, Offset(crouchBoxSize, PlayerData)); + addField("proneBoundingBox", TypePoint3F, Offset(proneBoxSize, PlayerData)); + addField("swimBoundingBox", TypePoint3F, Offset(swimBoxSize, PlayerData)); + + addField("boxHeadPercentage", TypeF32, Offset(boxHeadPercentage, PlayerData)); + addField("boxTorsoPercentage", TypeF32, Offset(boxTorsoPercentage, PlayerData)); + addField("boxHeadLeftPercentage", TypeS32, Offset(boxHeadLeftPercentage, PlayerData)); + addField("boxHeadRightPercentage", TypeS32, Offset(boxHeadRightPercentage, PlayerData)); + addField("boxHeadBackPercentage", TypeS32, Offset(boxHeadBackPercentage, PlayerData)); + addField("boxHeadFrontPercentage", TypeS32, Offset(boxHeadFrontPercentage, PlayerData)); + + addField("horizMaxSpeed", TypeF32, Offset(horizMaxSpeed, PlayerData)); + addField("horizResistSpeed", TypeF32, Offset(horizResistSpeed, PlayerData)); + addField("horizResistFactor", TypeF32, Offset(horizResistFactor, PlayerData)); + + addField("upMaxSpeed", TypeF32, Offset(upMaxSpeed, PlayerData)); + addField("upResistSpeed", TypeF32, Offset(upResistSpeed, PlayerData)); + addField("upResistFactor", TypeF32, Offset(upResistFactor, PlayerData)); + + addField("decalData", TypeDecalDataPtr, Offset(decalData, PlayerData)); + addField("decalOffset",TypeF32, Offset(decalOffset, PlayerData)); + + addField("footPuffEmitter", TypeParticleEmitterDataPtr, Offset(footPuffEmitter, PlayerData)); + addField("footPuffNumParts", TypeS32, Offset(footPuffNumParts, PlayerData)); + addField("footPuffRadius", TypeF32, Offset(footPuffRadius, PlayerData)); + addField("dustEmitter", TypeParticleEmitterDataPtr, Offset(dustEmitter, PlayerData)); + + addField("FootSoftSound", TypeSFXProfilePtr, Offset(sound[FootSoft], PlayerData)); + addField("FootHardSound", TypeSFXProfilePtr, Offset(sound[FootHard], PlayerData)); + addField("FootMetalSound", TypeSFXProfilePtr, Offset(sound[FootMetal], PlayerData)); + addField("FootSnowSound", TypeSFXProfilePtr, Offset(sound[FootSnow], PlayerData)); + addField("FootShallowSound", TypeSFXProfilePtr, Offset(sound[FootShallowSplash], PlayerData)); + addField("FootWadingSound", TypeSFXProfilePtr, Offset(sound[FootWading], PlayerData)); + addField("FootUnderwaterSound", TypeSFXProfilePtr, Offset(sound[FootUnderWater], PlayerData)); + addField("FootBubblesSound", TypeSFXProfilePtr, Offset(sound[FootBubbles], PlayerData)); + addField("movingBubblesSound", TypeSFXProfilePtr, Offset(sound[MoveBubbles], PlayerData)); + addField("waterBreathSound", TypeSFXProfilePtr, Offset(sound[WaterBreath], PlayerData)); + + addField("impactSoftSound", TypeSFXProfilePtr, Offset(sound[ImpactSoft], PlayerData)); + addField("impactHardSound", TypeSFXProfilePtr, Offset(sound[ImpactHard], PlayerData)); + addField("impactMetalSound", TypeSFXProfilePtr, Offset(sound[ImpactMetal], PlayerData)); + addField("impactSnowSound", TypeSFXProfilePtr, Offset(sound[ImpactSnow], PlayerData)); + + addField("mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, PlayerData)); + addField("hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, PlayerData)); + addField("exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, PlayerData)); + + addField("impactWaterEasy", TypeSFXProfilePtr, Offset(sound[ImpactWaterEasy], PlayerData)); + addField("impactWaterMedium", TypeSFXProfilePtr, Offset(sound[ImpactWaterMedium], PlayerData)); + addField("impactWaterHard", TypeSFXProfilePtr, Offset(sound[ImpactWaterHard], PlayerData)); + addField("exitingWater", TypeSFXProfilePtr, Offset(sound[ExitWater], PlayerData)); + + addField("splash", TypeSplashDataPtr, Offset(splash, PlayerData)); + addField("splashVelocity", TypeF32, Offset(splashVelocity, PlayerData)); + addField("splashAngle", TypeF32, Offset(splashAngle, PlayerData)); + addField("splashFreqMod", TypeF32, Offset(splashFreqMod, PlayerData)); + addField("splashVelEpsilon", TypeF32, Offset(splashVelEpsilon, PlayerData)); + addField("bubbleEmitTime", TypeF32, Offset(bubbleEmitTime, PlayerData)); + addField("splashEmitter", TypeParticleEmitterDataPtr, Offset(splashEmitterList, PlayerData), NUM_SPLASH_EMITTERS); + addField("footstepSplashHeight", TypeF32, Offset(footSplashHeight, PlayerData)); + + addField("groundImpactMinSpeed", TypeF32, Offset(groundImpactMinSpeed, PlayerData)); + addField("groundImpactShakeFreq", TypePoint3F, Offset(groundImpactShakeFreq, PlayerData)); + addField("groundImpactShakeAmp", TypePoint3F, Offset(groundImpactShakeAmp, PlayerData)); + addField("groundImpactShakeDuration", TypeF32, Offset(groundImpactShakeDuration, PlayerData)); + addField("groundImpactShakeFalloff", TypeF32, Offset(groundImpactShakeFalloff, PlayerData)); + + // Air control + addField("airControl", TypeF32, Offset(airControl,PlayerData)); + addField("jumpTowardsNormal", TypeBool, Offset(jumpTowardsNormal,PlayerData)); + + // PhysicsPlayer + addField("physicsPlayerType", TypeString, Offset(physicsPlayerType,PlayerData)); + + Parent::initPersistFields(); +} + +void PlayerData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeFlag(renderFirstPerson); + + stream->write(minLookAngle); + stream->write(maxLookAngle); + stream->write(maxFreelookAngle); + stream->write(maxTimeScale); + + stream->write(mass); + stream->write(maxEnergy); + stream->write(drag); + stream->write(density); + + stream->write(maxStepHeight); + + stream->write(runForce); + stream->write(runEnergyDrain); + stream->write(minRunEnergy); + stream->write(maxForwardSpeed); + stream->write(maxBackwardSpeed); + stream->write(maxSideSpeed); + stream->write(runSurfaceAngle); + + stream->write(recoverDelay); + stream->write(recoverRunForceScale); + + // Jumping + stream->write(jumpForce); + stream->write(jumpEnergyDrain); + stream->write(minJumpEnergy); + stream->write(minJumpSpeed); + stream->write(maxJumpSpeed); + stream->write(jumpSurfaceAngle); + stream->writeInt(jumpDelay,JumpDelayBits); + + // Swimming + stream->write(swimForce); + stream->write(maxUnderwaterForwardSpeed); + stream->write(maxUnderwaterBackwardSpeed); + stream->write(maxUnderwaterSideSpeed); + + // Crouching + stream->write(crouchForce); + stream->write(maxCrouchForwardSpeed); + stream->write(maxCrouchBackwardSpeed); + stream->write(maxCrouchSideSpeed); + + // Prone + stream->write(proneForce); + stream->write(maxProneForwardSpeed); + stream->write(maxProneBackwardSpeed); + stream->write(maxProneSideSpeed); + + // Jetting + stream->write(jetJumpForce); + stream->write(jetJumpEnergyDrain); + stream->write(jetMinJumpEnergy); + stream->write(jetMinJumpSpeed); + stream->write(jetMaxJumpSpeed); + stream->write(jetJumpSurfaceAngle); + + stream->write(horizMaxSpeed); + stream->write(horizResistSpeed); + stream->write(horizResistFactor); + + stream->write(upMaxSpeed); + stream->write(upResistSpeed); + stream->write(upResistFactor); + + stream->write(splashVelocity); + stream->write(splashAngle); + stream->write(splashFreqMod); + stream->write(splashVelEpsilon); + stream->write(bubbleEmitTime); + + stream->write(medSplashSoundVel); + stream->write(hardSplashSoundVel); + stream->write(exitSplashSoundVel); + stream->write(footSplashHeight); + // Don't need damage scale on the client + stream->write(minImpactSpeed); + + S32 i; + for ( i = 0; i < MaxSounds; i++) + if (stream->writeFlag(sound[i])) + stream->writeRangedU32(packed? SimObjectId(sound[i]): + sound[i]->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + + mathWrite(*stream, boxSize); + mathWrite(*stream, crouchBoxSize); + mathWrite(*stream, proneBoxSize); + mathWrite(*stream, swimBoxSize); + + if( stream->writeFlag( footPuffEmitter ) ) + { + stream->writeRangedU32( footPuffEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + stream->write( footPuffNumParts ); + stream->write( footPuffRadius ); + + if( stream->writeFlag( decalData ) ) + { + stream->writeRangedU32( decalData->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + stream->write(decalOffset); + + if( stream->writeFlag( dustEmitter ) ) + { + stream->writeRangedU32( dustEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + + if (stream->writeFlag( splash )) + { + stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + for( i=0; iwriteFlag( splashEmitterList[i] != NULL ) ) + { + stream->writeRangedU32( splashEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + + stream->write(groundImpactMinSpeed); + stream->write(groundImpactShakeFreq.x); + stream->write(groundImpactShakeFreq.y); + stream->write(groundImpactShakeFreq.z); + stream->write(groundImpactShakeAmp.x); + stream->write(groundImpactShakeAmp.y); + stream->write(groundImpactShakeAmp.z); + stream->write(groundImpactShakeDuration); + stream->write(groundImpactShakeFalloff); + + // Air control + stream->write(airControl); + + // Jump off at normal + stream->writeFlag(jumpTowardsNormal); + + stream->writeString(physicsPlayerType); +} + +void PlayerData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + renderFirstPerson = stream->readFlag(); + + stream->read(&minLookAngle); + stream->read(&maxLookAngle); + stream->read(&maxFreelookAngle); + stream->read(&maxTimeScale); + + stream->read(&mass); + stream->read(&maxEnergy); + stream->read(&drag); + stream->read(&density); + + stream->read(&maxStepHeight); + + stream->read(&runForce); + stream->read(&runEnergyDrain); + stream->read(&minRunEnergy); + stream->read(&maxForwardSpeed); + stream->read(&maxBackwardSpeed); + stream->read(&maxSideSpeed); + stream->read(&runSurfaceAngle); + + stream->read(&recoverDelay); + stream->read(&recoverRunForceScale); + + // Jumping + stream->read(&jumpForce); + stream->read(&jumpEnergyDrain); + stream->read(&minJumpEnergy); + stream->read(&minJumpSpeed); + stream->read(&maxJumpSpeed); + stream->read(&jumpSurfaceAngle); + jumpDelay = stream->readInt(JumpDelayBits); + + // Swimming + stream->read(&swimForce); + stream->read(&maxUnderwaterForwardSpeed); + stream->read(&maxUnderwaterBackwardSpeed); + stream->read(&maxUnderwaterSideSpeed); + + // Crouching + stream->read(&crouchForce); + stream->read(&maxCrouchForwardSpeed); + stream->read(&maxCrouchBackwardSpeed); + stream->read(&maxCrouchSideSpeed); + + // Prone + stream->read(&proneForce); + stream->read(&maxProneForwardSpeed); + stream->read(&maxProneBackwardSpeed); + stream->read(&maxProneSideSpeed); + + // Jetting + stream->read(&jetJumpForce); + stream->read(&jetJumpEnergyDrain); + stream->read(&jetMinJumpEnergy); + stream->read(&jetMinJumpSpeed); + stream->read(&jetMaxJumpSpeed); + stream->read(&jetJumpSurfaceAngle); + + stream->read(&horizMaxSpeed); + stream->read(&horizResistSpeed); + stream->read(&horizResistFactor); + + stream->read(&upMaxSpeed); + stream->read(&upResistSpeed); + stream->read(&upResistFactor); + + stream->read(&splashVelocity); + stream->read(&splashAngle); + stream->read(&splashFreqMod); + stream->read(&splashVelEpsilon); + stream->read(&bubbleEmitTime); + + stream->read(&medSplashSoundVel); + stream->read(&hardSplashSoundVel); + stream->read(&exitSplashSoundVel); + stream->read(&footSplashHeight); + + stream->read(&minImpactSpeed); + + S32 i; + for (i = 0; i < MaxSounds; i++) { + sound[i] = NULL; + if (stream->readFlag()) + sound[i] = (SFXProfile*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + mathRead(*stream, &boxSize); + mathRead(*stream, &crouchBoxSize); + mathRead(*stream, &proneBoxSize); + mathRead(*stream, &swimBoxSize); + + if( stream->readFlag() ) + { + footPuffID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + stream->read(&footPuffNumParts); + stream->read(&footPuffRadius); + + if( stream->readFlag() ) + { + decalID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + stream->read(&decalOffset); + + if( stream->readFlag() ) + { + dustID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + + if (stream->readFlag()) + { + splashId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + for( i=0; ireadFlag() ) + { + splashEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + stream->read(&groundImpactMinSpeed); + stream->read(&groundImpactShakeFreq.x); + stream->read(&groundImpactShakeFreq.y); + stream->read(&groundImpactShakeFreq.z); + stream->read(&groundImpactShakeAmp.x); + stream->read(&groundImpactShakeAmp.y); + stream->read(&groundImpactShakeAmp.z); + stream->read(&groundImpactShakeDuration); + stream->read(&groundImpactShakeFalloff); + + // Air control + stream->read(&airControl); + + // Jump off at normal + jumpTowardsNormal = stream->readFlag(); + + physicsPlayerType = stream->readSTString(); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(Player); +F32 Player::mGravity = -20; + + +//---------------------------------------------------------------------------- + +Player::Player() +{ + mTypeMask |= PlayerObjectType; + + delta.pos = mAnchorPoint = Point3F(0,0,100); + delta.rot = delta.head = Point3F(0,0,0); + delta.rotOffset.set(0.0f,0.0f,0.0f); + delta.warpOffset.set(0.0f,0.0f,0.0f); + delta.posVec.set(0.0f,0.0f,0.0f); + delta.rotVec.set(0.0f,0.0f,0.0f); + delta.headVec.set(0.0f,0.0f,0.0f); + delta.warpTicks = 0; + delta.dt = 1.0f; + delta.move = NullMove; + mPredictionCount = sMaxPredictionTicks; + mObjToWorld.setColumn(3,delta.pos); + mRot = delta.rot; + mHead = delta.head; + mVelocity.set(0.0f, 0.0f, 0.0f); + mDataBlock = 0; + mHeadHThread = mHeadVThread = mRecoilThread = 0; + mArmAnimation.action = PlayerData::NullAnimation; + mArmAnimation.thread = 0; + mActionAnimation.action = PlayerData::NullAnimation; + mActionAnimation.thread = 0; + mActionAnimation.delayTicks = 0; + mActionAnimation.forward = true; + mActionAnimation.firstPerson = false; + //mActionAnimation.time = 1.0f; //ActionAnimation::Scale; + mActionAnimation.waitForEnd = false; + mActionAnimation.holdAtEnd = false; + mActionAnimation.animateOnServer = false; + mActionAnimation.atEnd = false; + mState = MoveState; + mFalling = false; + mSwimming = false; + mInWater = false; + mPose = StandPose; + mContactTimer = 0; + mJumpDelay = 0; + mJumpSurfaceLastContact = 0; + mJumpSurfaceNormal.set(0.0f, 0.0f, 1.0f); + mControlObject = 0; + dMemset( mSplashEmitter, 0, sizeof( mSplashEmitter ) ); + + mImpactSound = 0; + mRecoverTicks = 0; + mReversePending = 0; + + mLastPos.set( 0.0f, 0.0f, 0.0f ); + + mMoveBubbleSound = 0; + mWaterBreathSound = 0; + + mConvex.init(this); + mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f); + mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f); + + mWeaponBackFraction = 0.0f; + + mInMissionArea = true; + + mBubbleEmitterTime = 10.0; + mLastWaterPos.set( 0.0, 0.0, 0.0 ); + + mMountPending = 0; + + mNSLinkMask = LinkSuperClassName | LinkClassName; + + mPhysicsPlayer = NULL; +} + +Player::~Player() +{ +} + + +//---------------------------------------------------------------------------- + +bool Player::onAdd() +{ + ActionAnimation serverAnim = mActionAnimation; + if(!Parent::onAdd() || !mDataBlock) + return false; + + mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f); + mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f); + + addToScene(); + + // Make sure any state and animation passed from the server + // in the initial update is set correctly. + ActionState state = mState; + mState = NullState; + setState(state); + setPose(StandPose); + + if (serverAnim.action != PlayerData::NullAnimation) + { + setActionThread(serverAnim.action, true, serverAnim.holdAtEnd, true, false, true); + if (serverAnim.atEnd) + { + mShapeInstance->clearTransition(mActionAnimation.thread); + mShapeInstance->setPos(mActionAnimation.thread, + mActionAnimation.forward ? 1.0f : 0.0f); + if (inDeathAnim()) + mDeath.lastPos = 1.0f; + } + + // We have to leave them sitting for a while since mounts don't come through right + // away (and sometimes not for a while). Still going to let this time out because + // I'm not sure if we're guaranteed another anim will come through and cancel. + if (!isServerObject() && inSittingAnim()) + mMountPending = (S32) sMountPendingTickWait; + else + mMountPending = 0; + } + if (mArmAnimation.action != PlayerData::NullAnimation) + setArmThread(mArmAnimation.action); + + // + if (isServerObject()) + { + scriptOnAdd(); + } + else + { + U32 i; + for( i=0; isplashEmitterList[i] ) + { + mSplashEmitter[i] = new ParticleEmitter; + mSplashEmitter[i]->onNewDataBlock( mDataBlock->splashEmitterList[i] ); + if( !mSplashEmitter[i]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register splash emitter for class: %s", mDataBlock->getName() ); + delete mSplashEmitter[i].getObject(); + mSplashEmitter[i] = NULL; + } + } + } + mLastWaterPos = getPosition(); + + // clear out all camera effects + gCamFXMgr.clear(); + } + + if ( gPhysicsPlugin ) + mPhysicsPlayer = gPhysicsPlugin->createPlayer( this ); + + return true; +} + +void Player::onRemove() +{ + setControlObject(0); + scriptOnRemove(); + removeFromScene(); + + U32 i; + for( i=0; ideleteWhenEmpty(); + mSplashEmitter[i] = NULL; + } + } + + mWorkingQueryBox.minExtents.set(-1e9f, -1e9f, -1e9f); + mWorkingQueryBox.maxExtents.set(-1e9f, -1e9f, -1e9f); + + SAFE_DELETE( mPhysicsPlayer ); + + Parent::onRemove(); +} + +void Player::onScaleChanged() +{ + const Point3F& scale = getScale(); + mScaledBox = mObjBox; + mScaledBox.minExtents.convolve( scale ); + mScaledBox.maxExtents.convolve( scale ); +} + + +//---------------------------------------------------------------------------- + +bool Player::onNewDataBlock(GameBaseData* dptr) +{ + PlayerData* prevData = mDataBlock; + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + // Initialize arm thread, preserve arm sequence from last datablock. + // Arm animation can be from last datablock, or sent from the server. + U32 prevAction = mArmAnimation.action; + mArmAnimation.action = PlayerData::NullAnimation; + if (mDataBlock->lookAction) { + mArmAnimation.thread = mShapeInstance->addThread(); + mShapeInstance->setTimeScale(mArmAnimation.thread,0); + if (prevData) { + if (prevAction != prevData->lookAction && prevAction != PlayerData::NullAnimation) + setArmThread(prevData->actionList[prevAction].name); + prevAction = PlayerData::NullAnimation; + } + if (mArmAnimation.action == PlayerData::NullAnimation) { + mArmAnimation.action = (prevAction != PlayerData::NullAnimation)? + prevAction: mDataBlock->lookAction; + mShapeInstance->setSequence(mArmAnimation.thread, + mDataBlock->actionList[mArmAnimation.action].sequence,0); + } + } + else + mArmAnimation.thread = 0; + + // Initialize head look thread + TSShape const* shape = mShapeInstance->getShape(); + S32 headSeq = shape->findSequence("head"); + if (headSeq != -1) { + mHeadVThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mHeadVThread,headSeq,0); + mShapeInstance->setTimeScale(mHeadVThread,0); + } + else + mHeadVThread = 0; + + headSeq = shape->findSequence("headside"); + if (headSeq != -1) { + mHeadHThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mHeadHThread,headSeq,0); + mShapeInstance->setTimeScale(mHeadHThread,0); + } + else + mHeadHThread = 0; + + // Recoil thread. The server player does not play this animation. + mRecoilThread = 0; + if (isGhost()) + for (U32 s = 0; s < PlayerData::NumRecoilSequences; s++) + if (mDataBlock->recoilSequence[s] != -1) { + mRecoilThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mRecoilThread,mDataBlock->recoilSequence[s],0); + mShapeInstance->setTimeScale(mRecoilThread,0); + } + + // Initialize the primary thread, the actual sequence is + // set later depending on player actions. + mActionAnimation.action = PlayerData::NullAnimation; + mActionAnimation.thread = mShapeInstance->addThread(); + updateAnimationTree(!isGhost()); + + if ( isGhost() ) + { + // Create the sounds ahead of time. This reduces runtime + // costs and makes the system easier to understand. + + SFX_DELETE( mMoveBubbleSound ); + SFX_DELETE( mWaterBreathSound ); + + if ( mDataBlock->sound[PlayerData::MoveBubbles] ) + mMoveBubbleSound = SFX->createSource( mDataBlock->sound[PlayerData::MoveBubbles] ); + + if ( mDataBlock->sound[PlayerData::WaterBreath] ) + mWaterBreathSound = SFX->createSource( mDataBlock->sound[PlayerData::WaterBreath] ); + } + + mObjBox.maxExtents.x = mDataBlock->boxSize.x * 0.5f; + mObjBox.maxExtents.y = mDataBlock->boxSize.y * 0.5f; + mObjBox.maxExtents.z = mDataBlock->boxSize.z; + mObjBox.minExtents.x = -mObjBox.maxExtents.x; + mObjBox.minExtents.y = -mObjBox.maxExtents.y; + mObjBox.minExtents.z = 0.0f; + + // Setup the box for our convex object... + mObjBox.getCenter(&mConvex.mCenter); + mConvex.mSize.x = mObjBox.len_x() / 2.0f; + mConvex.mSize.y = mObjBox.len_y() / 2.0f; + mConvex.mSize.z = mObjBox.len_z() / 2.0f; + + // Initialize our scaled attributes as well + onScaleChanged(); + resetWorldBox(); + + scriptOnNewDataBlock(); + return true; +} + + +//---------------------------------------------------------------------------- + +void Player::setControllingClient(GameConnection* client) +{ + Parent::setControllingClient(client); + if (mControlObject) + mControlObject->setControllingClient(client); +} + +void Player::setControlObject(ShapeBase* obj) +{ + if (mControlObject) { + mControlObject->setControllingObject(0); + mControlObject->setControllingClient(0); + } + if (obj == this || obj == 0) + mControlObject = 0; + else { + if (ShapeBase* coo = obj->getControllingObject()) + coo->setControlObject(0); + if (GameConnection* con = obj->getControllingClient()) + con->setControlObject(0); + + mControlObject = obj; + mControlObject->setControllingObject(this); + mControlObject->setControllingClient(getControllingClient()); + } +} + +void Player::onCameraScopeQuery(NetConnection *connection, CameraScopeQuery *query) +{ + // First, we are certainly in scope, and whatever we're riding is too... + if(mControlObject.isNull() || mControlObject == mMount.object) + Parent::onCameraScopeQuery(connection, query); + else + { + connection->objectInScope(this); + if (isMounted()) + connection->objectInScope(mMount.object); + mControlObject->onCameraScopeQuery(connection, query); + } +} + +ShapeBase* Player::getControlObject() +{ + return mControlObject; +} + +void Player::disableCollision() +{ + Parent::disableCollision(); + + if ( mPhysicsPlayer ) + mPhysicsPlayer->disableCollision(); +} + +void Player::enableCollision() +{ + Parent::enableCollision(); + + if ( mPhysicsPlayer ) + mPhysicsPlayer->enableCollision(); +} + +void Player::processTick(const Move* move) +{ + PROFILE_SCOPE(Player_ProcessTick); + + // If we're not being controlled by a client, let the + // AI sub-module get a chance at producing a move. + Move aiMove; + if (!move && isServerObject() && getAIMove(&aiMove)) + move = &aiMove; + + // Manage the control object and filter moves for the player + Move pMove,cMove; + if (mControlObject) { + if (!move) + mControlObject->processTick(0); + else { + pMove = NullMove; + cMove = *move; + if (isMounted()) { + // Filter Jump trigger if mounted + pMove.trigger[2] = move->trigger[2]; + cMove.trigger[2] = false; + } + if (move->freeLook) { + // Filter yaw/picth/roll when freelooking. + pMove.yaw = move->yaw; + pMove.pitch = move->pitch; + pMove.roll = move->roll; + pMove.freeLook = true; + cMove.freeLook = false; + cMove.yaw = cMove.pitch = cMove.roll = 0.0f; + } + mControlObject->processTick((mDamageState == Enabled)? &cMove: &NullMove); + move = &pMove; + } + } + + Parent::processTick(move); + // Warp to catch up to server + if (delta.warpTicks > 0) { + delta.warpTicks--; + + // Set new pos. + getTransform().getColumn(3,&delta.pos); + delta.pos += delta.warpOffset; + delta.rot += delta.rotOffset; + setPosition(delta.pos,delta.rot); + setRenderPosition(delta.pos,delta.rot); + updateDeathOffsets(); + updateLookAnimation(); + + // Backstepping + delta.posVec.x = -delta.warpOffset.x; + delta.posVec.y = -delta.warpOffset.y; + delta.posVec.z = -delta.warpOffset.z; + delta.rotVec.x = -delta.rotOffset.x; + delta.rotVec.y = -delta.rotOffset.y; + delta.rotVec.z = -delta.rotOffset.z; + } + else { + // If there is no move, the player is either an + // unattached player on the server, or a player's + // client ghost. + if (!move) { + if (isGhost()) { + // If we haven't run out of prediction time, + // predict using the last known move. + if (mPredictionCount-- <= 0) + return; + + move = &delta.move; + } + else + move = &NullMove; + } + if (!isGhost()) + updateAnimation(TickSec); + + PROFILE_START(Player_PhysicsSection); + if(isServerObject() || (didRenderLastRender() || getControllingClient())) + { + if ( !mPhysicsPlayer ) + updateWorkingCollisionSet(); + + updateState(); + updateMove(move); + updateLookAnimation(); + updateDeathOffsets(); + updatePos(); + } + PROFILE_END(); + + if (!isGhost()) + { + // Animations are advanced based on frame rate on the + // client and must be ticked on the server. + updateActionThread(); + updateAnimationTree(true); + } + } +} + +void Player::interpolateTick(F32 dt) +{ + if (mControlObject) + mControlObject->interpolateTick(dt); + + // Client side interpolation + Parent::interpolateTick(dt); + + Point3F pos = delta.pos + delta.posVec * dt; + Point3F rot = delta.rot + delta.rotVec * dt; + + setRenderPosition(pos,rot,dt); + +/* + // apply camera effects - is this the best place? - bramage + GameConnection* connection = GameConnection::getConnectionToServer(); + if( connection->isFirstPerson() ) + { + ShapeBase *obj = dynamic_cast(connection->getControlObject()); + if( obj == this ) + { + MatrixF curTrans = getRenderTransform(); + curTrans.mul( gCamFXMgr.getTrans() ); + Parent::setRenderTransform( curTrans ); + } + } +*/ + + updateLookAnimation(dt); + delta.dt = dt; +} + +void Player::advanceTime(F32 dt) +{ + // Client side animations + Parent::advanceTime(dt); + updateActionThread(); + updateAnimation(dt); + updateSplash(); + updateFroth(dt); + updateWaterSounds(dt); + + mLastPos = getPosition(); + + if (mImpactSound) + playImpactSound(); + + // update camera effects. Definitely need to find better place for this - bramage + if( isControlObject() ) + { + if( mDamageState == Disabled || mDamageState == Destroyed ) + { + // clear out all camera effects being applied to player if dead + gCamFXMgr.clear(); + } + } +} + +bool Player::getAIMove(Move* move) +{ + return false; +} + +void Player::setState(ActionState state, U32 recoverTicks) +{ + if (state != mState) { + // Skip initialization if there is no manager, the state + // will get reset when the object is added to a manager. + if (isProperlyAdded()) { + switch (state) { + case RecoverState: { + mRecoverTicks = recoverTicks; + mReversePending = U32(F32(mRecoverTicks) / sLandReverseScale); + setActionThread(PlayerData::LandAnim, true, false, true, true); + break; + } + + default: + break; + } + } + + mState = state; + } +} + +void Player::updateState() +{ + switch (mState) + { + case RecoverState: + if (mRecoverTicks-- <= 0) + { + if (mReversePending && mActionAnimation.action != PlayerData::NullAnimation) + { + // this serves and counter, and direction state + mRecoverTicks = mReversePending; + mActionAnimation.forward = false; + + S32 seq = mDataBlock->actionList[mActionAnimation.action].sequence; + F32 pos = mShapeInstance->getPos(mActionAnimation.thread); + + mShapeInstance->setTimeScale(mActionAnimation.thread, -sLandReverseScale); + mShapeInstance->transitionToSequence(mActionAnimation.thread, + seq, pos, sAnimationTransitionTime, true); + mReversePending = 0; + } + else + { + setState(MoveState); + } + } // Stand back up slowly only if not moving much- + else if (!mReversePending && mVelocity.lenSquared() > sSlowStandThreshSquared) + { + mActionAnimation.waitForEnd = false; + setState(MoveState); + } + break; + + default: + break; + } +} + +const char* Player::getStateName() +{ + if (mDamageState != Enabled) + return "Dead"; + if (isMounted()) + return "Mounted"; + if (mState == RecoverState) + return "Recover"; + return "Move"; +} + +void Player::getDamageLocation(const Point3F& in_rPos, const char *&out_rpVert, const char *&out_rpQuad) +{ + // TODO: This will be WRONG when player is prone! + + Point3F newPoint; + mWorldToObj.mulP(in_rPos, &newPoint); + + Point3F boxSize = mObjBox.getExtents(); + F32 zHeight = boxSize.z; + F32 zTorso = mDataBlock->boxTorsoPercentage; + F32 zHead = mDataBlock->boxHeadPercentage; + + zTorso *= zHeight; + zHead *= zHeight; + + if (newPoint.z <= zTorso) + out_rpVert = "legs"; + else if (newPoint.z <= zHead) + out_rpVert = "torso"; + else + out_rpVert = "head"; + + if(dStrcmp(out_rpVert, "head") != 0) + { + if (newPoint.y >= 0.0f) + { + if (newPoint.x <= 0.0f) + out_rpQuad = "front_left"; + else + out_rpQuad = "front_right"; + } + else + { + if (newPoint.x <= 0.0f) + out_rpQuad = "back_left"; + else + out_rpQuad = "back_right"; + } + } + else + { + F32 backToFront = boxSize.x; + F32 leftToRight = boxSize.y; + + F32 backPoint = backToFront * (mDataBlock->boxHeadBackPercentage - 0.5f); + F32 frontPoint = backToFront * (mDataBlock->boxHeadFrontPercentage - 0.5f); + F32 leftPoint = leftToRight * (mDataBlock->boxHeadLeftPercentage - 0.5f); + F32 rightPoint = leftToRight * (mDataBlock->boxHeadRightPercentage - 0.5f); + + S32 index = 0; + if (newPoint.y < backPoint) + index += 0; + else if (newPoint.y <= frontPoint) + index += 3; + else + index += 6; + + if (newPoint.x < leftPoint) + index += 0; + else if (newPoint.x <= rightPoint) + index += 1; + else + index += 2; + + switch (index) + { + case 0: + out_rpQuad = "left_back"; + break; + + case 1: out_rpQuad = "middle_back"; break; + case 2: out_rpQuad = "right_back"; break; + case 3: out_rpQuad = "left_middle"; break; + case 4: out_rpQuad = "middle_middle"; break; + case 5: out_rpQuad = "right_middle"; break; + case 6: out_rpQuad = "left_front"; break; + case 7: out_rpQuad = "middle_front"; break; + case 8: out_rpQuad = "right_front"; break; + + default: + AssertFatal(0, "Bad non-tant index"); + }; + } +} + +void Player::setPose( Pose pose ) +{ + // Already the set pose, return. + if ( pose == mPose ) + return; + + mPose = pose; + + // Not added yet, just assign the pose and return. + if ( !isProperlyAdded() ) + return; + + Point3F boxSize(1,1,1); + + // Resize the player boxes + switch (pose) + { + case StandPose: + boxSize = mDataBlock->boxSize; + break; + case CrouchPose: + boxSize = mDataBlock->crouchBoxSize; + break; + case PronePose: + boxSize = mDataBlock->proneBoxSize; + break; + case SwimPose: + boxSize = mDataBlock->swimBoxSize; + break; + } + + // Object and World Boxes... + mObjBox.maxExtents.x = boxSize.x * 0.5f; + mObjBox.maxExtents.y = boxSize.y * 0.5f; + mObjBox.maxExtents.z = boxSize.z; + mObjBox.minExtents.x = -mObjBox.maxExtents.x; + mObjBox.minExtents.y = -mObjBox.maxExtents.y; + mObjBox.minExtents.z = 0.0f; + + resetWorldBox(); + + // Setup the box for our convex object... + mObjBox.getCenter(&mConvex.mCenter); + mConvex.mSize.x = mObjBox.len_x() / 2.0f; + mConvex.mSize.y = mObjBox.len_y() / 2.0f; + mConvex.mSize.z = mObjBox.len_z() / 2.0f; + + // Initialize our scaled attributes as well... + onScaleChanged(); + + // Resize the PhysicsPlayer rep. should we have one + if ( mPhysicsPlayer ) + mPhysicsPlayer->setSpacials( getPosition(), boxSize ); + +} + +void Player::updateMove(const Move* move) +{ + delta.move = *move; + + // Is waterCoverage high enough to be 'swimming'? + { + bool swimming = mWaterCoverage > 0.65f; + + if ( swimming != mSwimming ) + { + if ( !isGhost() ) + { + String buf = swimming ? "onStartSwim" : "onStopSwim"; + Con::executef( mDataBlock, buf, scriptThis() ); + } + + mSwimming = swimming; + } + } + + // Trigger images + if (mDamageState == Enabled) + { + setImageTriggerState( 0, move->trigger[0] ); + + // If you have a secondary mounted image then + // send the second trigger to it. Else give it + // to the first image as an alt fire. + if ( getMountedImage( 1 ) ) + setImageTriggerState( 1, move->trigger[1] ); + else + setImageAltTriggerState( 0, move->trigger[1] ); + } + + // Update current orientation + if (mDamageState == Enabled) { + F32 prevZRot = mRot.z; + delta.headVec = mHead; + + F32 p = move->pitch; + if (p > M_PI_F) + p -= M_2PI_F; + mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle, + mDataBlock->maxLookAngle); + + F32 y = move->yaw; + if (y > M_PI_F) + y -= M_2PI_F; + + GameConnection* con = getControllingClient(); + if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson()))) + { + mHead.z = mClampF(mHead.z + y, + -mDataBlock->maxFreelookAngle, + mDataBlock->maxFreelookAngle); + } + else + { + mRot.z += y; + // Rotate the head back to the front, center horizontal + // as well if we're controlling another object. + mHead.z *= 0.5f; + if (mControlObject) + mHead.x *= 0.5f; + } + + // constrain the range of mRot.z + while (mRot.z < 0.0f) + mRot.z += M_2PI_F; + while (mRot.z > M_2PI_F) + mRot.z -= M_2PI_F; + + delta.rot = mRot; + delta.rotVec.x = delta.rotVec.y = 0.0f; + delta.rotVec.z = prevZRot - mRot.z; + if (delta.rotVec.z > M_PI_F) + delta.rotVec.z -= M_2PI_F; + else if (delta.rotVec.z < -M_PI_F) + delta.rotVec.z += M_2PI_F; + + delta.head = mHead; + delta.headVec -= mHead; + } + MatrixF zRot; + zRot.set(EulerF(0.0f, 0.0f, mRot.z)); + + // Desired move direction & speed + VectorF moveVec; + F32 moveSpeed; + if (mState == MoveState && mDamageState == Enabled) + { + zRot.getColumn(0,&moveVec); + moveVec *= move->x; + VectorF tv; + zRot.getColumn(1,&tv); + moveVec += tv * move->y; + + // Clamp water movement + if (move->y > 0.0f) + { + if ( mSwimming ) + moveSpeed = getMax(mDataBlock->maxUnderwaterForwardSpeed * move->y, + mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x)); + else if ( mPose == PronePose ) + moveSpeed = getMax(mDataBlock->maxProneForwardSpeed * move->y, + mDataBlock->maxProneSideSpeed * mFabs(move->x)); + else if ( mPose == CrouchPose ) + moveSpeed = getMax(mDataBlock->maxCrouchForwardSpeed * move->y, + mDataBlock->maxCrouchSideSpeed * mFabs(move->x)); + else // StandPose + moveSpeed = getMax(mDataBlock->maxForwardSpeed * move->y, + mDataBlock->maxSideSpeed * mFabs(move->x)); + } + else + { + if ( mSwimming ) + moveSpeed = getMax(mDataBlock->maxUnderwaterBackwardSpeed * mFabs(move->y), + mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x)); + else if ( mPose == PronePose ) + moveSpeed = getMax(mDataBlock->maxProneBackwardSpeed * mFabs(move->y), + mDataBlock->maxProneSideSpeed * mFabs(move->x)); + else if ( mPose == CrouchPose ) + moveSpeed = getMax(mDataBlock->maxCrouchBackwardSpeed * mFabs(move->y), + mDataBlock->maxCrouchSideSpeed * mFabs(move->x)); + else // StandPose + moveSpeed = getMax(mDataBlock->maxBackwardSpeed * mFabs(move->y), + mDataBlock->maxSideSpeed * mFabs(move->x)); + } + + // Cancel any script driven animations if we are going to move. + if (moveVec.x + moveVec.y + moveVec.z != 0.0f && + (mActionAnimation.action >= PlayerData::NumTableActionAnims + || mActionAnimation.action == PlayerData::LandAnim)) + mActionAnimation.action = PlayerData::NullAnimation; + } + else + { + moveVec.set(0.0f, 0.0f, 0.0f); + moveSpeed = 0.0f; + } + + // Acceleration due to gravity + VectorF acc(0.0f, 0.0f, mGravity * mGravityMod * TickSec); + + // Determine ground contact normal. Only look for contacts if + // we can move - and we aren't swimming. + VectorF contactNormal(0,0,0); + bool jumpSurface = false, runSurface = false; + if ( !isMounted() && !mSwimming ) + findContact( &runSurface, &jumpSurface, &contactNormal ); + if ( jumpSurface ) + mJumpSurfaceNormal = contactNormal; + + // If we don't have a runSurface but we do have a contactNormal, + // then we are standing on something that is too steep. + // Deflect the force of gravity by the normal so we slide. + // We could also try aligning it to the runSurface instead, + // but this seems to work well. + if ( !runSurface && !contactNormal.isZero() ) + acc = ( acc - 2 * contactNormal * mDot( acc, contactNormal ) ); + + // Acceleration on run surface + if (runSurface) { + mContactTimer = 0; + + // Remove acc into contact surface (should only be gravity) + // Clear out floating point acc errors, this will allow + // the player to "rest" on the ground. + F32 vd = -mDot(acc,contactNormal); + if (vd > 0.0f) { + VectorF dv = contactNormal * (vd + 0.002f); + acc += dv; + if (acc.len() < 0.0001f) + acc.set(0.0f, 0.0f, 0.0f); + } + + // Force a 0 move if there is no energy, and only drain + // move energy if we're moving. + VectorF pv; + if (mEnergy >= mDataBlock->minRunEnergy) { + if (moveSpeed) + mEnergy -= mDataBlock->runEnergyDrain; + pv = moveVec; + } + else + pv.set(0.0f, 0.0f, 0.0f); + + // Adjust the player's requested dir. to be parallel + // to the contact surface. + F32 pvl = pv.len(); + if(mJetting) + { + pvl = moveVec.len(); + if (pvl) + { + VectorF nn; + mCross(pv,VectorF(0.0f, 0.0f, 0.0f),&nn); + nn *= 1 / pvl; + VectorF cv(0.0f, 0.0f, 0.0f); + cv -= nn * mDot(nn,cv); + pv -= cv * mDot(pv,cv); + pvl = pv.len(); + } + } + else + { + if (pvl) + { + VectorF nn; + mCross(pv,VectorF(0.0f, 0.0f, 1.0f),&nn); + nn *= 1.0f / pvl; + VectorF cv = contactNormal; + cv -= nn * mDot(nn,cv); + pv -= cv * mDot(pv,cv); + pvl = pv.len(); + } + } + + // Convert to acceleration + if ( pvl ) + pv *= moveSpeed / pvl; + VectorF runAcc = pv - (mVelocity + acc); + F32 runSpeed = runAcc.len(); + + // Clamp acceleration, player also accelerates faster when + // in his hard landing recover state. + F32 maxAcc = (mDataBlock->runForce / getMass()) * TickSec; + if (mState == RecoverState) + maxAcc *= mDataBlock->recoverRunForceScale; + if (runSpeed > maxAcc) + runAcc *= maxAcc / runSpeed; + acc += runAcc; + + // If we are running on the ground, then we're not jumping + if (mDataBlock->isJumpAction(mActionAnimation.action)) + mActionAnimation.action = PlayerData::NullAnimation; + } + else if (!mSwimming && mDataBlock->airControl > 0.0f) + { + VectorF pv; + pv = moveVec; + F32 pvl = pv.len(); + + if (pvl) + pv *= moveSpeed / pvl; + + VectorF runAcc = pv - acc; + runAcc.z = 0; + runAcc.x = runAcc.x * mDataBlock->airControl; + runAcc.y = runAcc.y * mDataBlock->airControl; + F32 runSpeed = runAcc.len(); + F32 maxAcc = (mDataBlock->runForce / getMass()) * TickSec * 0.3f; + + if (runSpeed > maxAcc) + runAcc *= maxAcc / runSpeed; + + acc += runAcc; + + // There are no special air control animations + // so... increment this unless you really want to + // play the run anims in the air. + mContactTimer++; + } + else if (mSwimming) + { + // get the head pitch and add it to the moveVec + // This more accurate swim vector calc comes from Matt Fairfax + MatrixF xRot, zRot; + xRot.set(EulerF(mHead.x, 0, 0)); + zRot.set(EulerF(0, 0, mRot.z)); + MatrixF rot; + rot.mul(zRot, xRot); + rot.getColumn(0,&moveVec); + + moveVec *= move->x; + VectorF tv; + rot.getColumn(1,&tv); + moveVec += tv * move->y; + rot.getColumn(2,&tv); + moveVec += tv * move->z; + + // Force a 0 move if there is no energy, and only drain + // move energy if we're moving. + VectorF swimVec; + if (mEnergy >= mDataBlock->minRunEnergy) { + if (moveSpeed) + mEnergy -= mDataBlock->runEnergyDrain; + swimVec = moveVec; + } + else + swimVec.set(0,0,0); + + F32 swimVecLen = swimVec.len(); + + // Convert to acceleration + if ( swimVecLen ) + swimVec *= moveSpeed / swimVecLen; + + VectorF swimAcc = swimVec - mVelocity; + + F32 swimSpeed = swimAcc.len(); + + // Clamp acceleration, player also accelerates faster when + // in his hard landing recover state. + F32 maxAcc = (mDataBlock->swimForce / getMass()) * TickSec; + if ( swimSpeed > maxAcc ) + swimAcc *= maxAcc / swimSpeed; + + acc += swimAcc; + + mContactTimer++; + } + else + mContactTimer++; + + // Acceleration from Jumping + if (move->trigger[2] && !isMounted() && canJump()) + { + // Scale the jump impulse base on maxJumpSpeed + F32 zSpeedScale = mVelocity.z; + if (zSpeedScale <= mDataBlock->maxJumpSpeed) + { + zSpeedScale = (zSpeedScale <= mDataBlock->minJumpSpeed)? 1: + 1 - (zSpeedScale - mDataBlock->minJumpSpeed) / + (mDataBlock->maxJumpSpeed - mDataBlock->minJumpSpeed); + + // Desired jump direction + VectorF pv = moveVec; + F32 len = pv.len(); + if (len > 0) + pv *= 1 / len; + + // We want to scale the jump size by the player size, somewhat + // in reduced ratio so a smaller player can jump higher in + // proportion to his size, than a larger player. + F32 scaleZ = (getScale().z * 0.25) + 0.75; + + // Calculate our jump impulse + F32 impulse = mDataBlock->jumpForce / getMass(); + + if (mDataBlock->jumpTowardsNormal) + { + // If we are facing into the surface jump up, otherwise + // jump away from surface. + F32 dot = mDot(pv,mJumpSurfaceNormal); + if (dot <= 0) + acc.z += mJumpSurfaceNormal.z * scaleZ * impulse * zSpeedScale; + else + { + acc.x += pv.x * impulse * dot; + acc.y += pv.y * impulse * dot; + acc.z += mJumpSurfaceNormal.z * scaleZ * impulse * zSpeedScale; + } + } + else + acc.z += scaleZ * impulse * zSpeedScale; + + mJumpDelay = mDataBlock->jumpDelay; + mEnergy -= mDataBlock->jumpEnergyDrain; + + // If we don't have a StandJumpAnim, just play the JumpAnim... + S32 seq = (mVelocity.len() < 0.5) ? PlayerData::StandJumpAnim: PlayerData::JumpAnim; + if ( mDataBlock->actionList[seq].sequence == -1 ) + seq = PlayerData::JumpAnim; + setActionThread( seq, true, false, true ); + + mJumpSurfaceLastContact = JumpSkipContactsMax; + } + } + else + { + if (jumpSurface) + { + if (mJumpDelay > 0) + mJumpDelay--; + mJumpSurfaceLastContact = 0; + } + else + mJumpSurfaceLastContact++; + } + + if (move->trigger[1] && !isMounted() && canJetJump()) + { + mJetting = true; + + // Scale the jump impulse base on maxJumpSpeed + F32 zSpeedScale = mVelocity.z; + + if (zSpeedScale <= mDataBlock->jetMaxJumpSpeed) + { + zSpeedScale = (zSpeedScale <= mDataBlock->jetMinJumpSpeed)? 1: + 1 - (zSpeedScale - mDataBlock->jetMinJumpSpeed) / (mDataBlock->jetMaxJumpSpeed - mDataBlock->jetMinJumpSpeed); + + // Desired jump direction + VectorF pv = moveVec; + F32 len = pv.len(); + + if (len > 0.0f) + pv *= 1 / len; + + // If we are facing into the surface jump up, otherwise + // jump away from surface. + F32 dot = mDot(pv,mJumpSurfaceNormal); + F32 impulse = mDataBlock->jetJumpForce / getMass(); + + if (dot <= 0) + acc.z += mJumpSurfaceNormal.z * impulse * zSpeedScale; + else + { + acc.x += pv.x * impulse * dot; + acc.y += pv.y * impulse * dot; + acc.z += mJumpSurfaceNormal.z * impulse * zSpeedScale; + } + + mEnergy -= mDataBlock->jetJumpEnergyDrain; + } + } + else + { + mJetting = false; + } + + // Add in force from physical zones... + acc += (mAppliedForce / getMass()) * TickSec; + + // Adjust velocity with all the move & gravity acceleration + // TG: I forgot why doesn't the TickSec multiply happen here... + mVelocity += acc; + + // apply horizontal air resistance + + F32 hvel = mSqrt(mVelocity.x * mVelocity.x + mVelocity.y * mVelocity.y); + + if(hvel > mDataBlock->horizResistSpeed) + { + F32 speedCap = hvel; + if(speedCap > mDataBlock->horizMaxSpeed) + speedCap = mDataBlock->horizMaxSpeed; + speedCap -= mDataBlock->horizResistFactor * TickSec * (speedCap - mDataBlock->horizResistSpeed); + F32 scale = speedCap / hvel; + mVelocity.x *= scale; + mVelocity.y *= scale; + } + if(mVelocity.z > mDataBlock->upResistSpeed) + { + if(mVelocity.z > mDataBlock->upMaxSpeed) + mVelocity.z = mDataBlock->upMaxSpeed; + mVelocity.z -= mDataBlock->upResistFactor * TickSec * (mVelocity.z - mDataBlock->upResistSpeed); + } + + // Container buoyancy & drag + if (mBuoyancy != 0) + { + // Applying buoyancy when standing still causing some jitters- + if (mBuoyancy > 1.0 || !mVelocity.isZero() || !runSurface) + { + // A little hackery to prevent oscillation + // based on http://reinot.blogspot.com/2005/11/oh-yes-they-float-georgie-they-all.html + + F32 buoyancyForce = mBuoyancy * mGravity * mGravityMod * TickSec; + F32 currHeight = getPosition().z; + const F32 C = 2.0f; + const F32 M = 0.1f; + + if ( currHeight + mVelocity.z * TickSec * C > mLiquidHeight ) + buoyancyForce *= M; + + mVelocity.z -= buoyancyForce; + } + } + + // Apply drag + mVelocity -= mVelocity * mDrag * TickSec; + + // Clamp very small velocity to zero + if ( mVelocity.isZero() ) + mVelocity = Point3F::Zero; + + // If we are not touching anything and have sufficient -z vel, + // we are falling. + if (runSurface) + mFalling = false; + else + { + VectorF vel; + mWorldToObj.mulV(mVelocity,&vel); + mFalling = vel.z < sFallingThreshold; + } + + // Vehicle Dismount + if ( !isGhost() && move->trigger[2] && isMounted()) + Con::executef(mDataBlock, "doDismount", scriptThis()); + + // Enter/Leave Liquid + if ( !mInWater && mWaterCoverage > 0.0f ) + { + mInWater = true; + + if ( !isGhost() ) + Con::executef( mDataBlock, "onEnterLiquid", scriptThis(), Con::getFloatArg(mWaterCoverage), mLiquidType.c_str() ); + } + else if ( mInWater && mWaterCoverage <= 0.0f ) + { + mInWater = false; + + if ( !isGhost() ) + Con::executef( mDataBlock, "onLeaveLiquid", scriptThis(), mLiquidType.c_str() ); + else + { + // exit-water splash sound happens for client only + if ( getSpeed() >= mDataBlock->exitSplashSoundVel && !isMounted() ) + SFX->playOnce( mDataBlock->sound[PlayerData::ExitWater], &getTransform() ); + } + } + + // Update the PlayerPose + Pose desiredPose = mPose; + + if ( mSwimming ) + desiredPose = SwimPose; + else if ( runSurface && move->trigger[3] && canCrouch() ) + desiredPose = CrouchPose; + else if ( runSurface && move->trigger[4] && canProne() ) + desiredPose = PronePose; + else if ( canStand() ) + desiredPose = StandPose; + + setPose( desiredPose ); +} + + +//---------------------------------------------------------------------------- + +bool Player::checkDismountPosition(const MatrixF& oldMat, const MatrixF& mat) +{ + AssertFatal(getContainer() != NULL, "Error, must have a container!"); + AssertFatal(getObjectMount() != NULL, "Error, must be mounted!"); + + Point3F pos; + Point3F oldPos; + mat.getColumn(3, &pos); + oldMat.getColumn(3, &oldPos); + RayInfo info; + disableCollision(); + getObjectMount()->disableCollision(); + if (getContainer()->castRay(oldPos, pos, sCollisionMoveMask, &info)) + { + enableCollision(); + getObjectMount()->enableCollision(); + return false; + } + + Box3F wBox = mObjBox; + wBox.minExtents += pos; + wBox.maxExtents += pos; + + EarlyOutPolyList polyList; + polyList.mNormal.set(0.0f, 0.0f, 0.0f); + polyList.mPlaneList.clear(); + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1.0f, 0.0f, 0.0f)); + polyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0.0f, 1.0f, 0.0f)); + polyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1.0f, 0.0f, 0.0f)); + polyList.mPlaneList[3].set(wBox.minExtents,VectorF(0.0f, -1.0f, 0.0f)); + polyList.mPlaneList[4].set(wBox.minExtents,VectorF(0.0f, 0.0f, -1.0f)); + polyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0.0f, 0.0f, 1.0f)); + + if (getContainer()->buildPolyList(wBox, sCollisionMoveMask, &polyList)) + { + enableCollision(); + getObjectMount()->enableCollision(); + return false; + } + + enableCollision(); + getObjectMount()->enableCollision(); + return true; +} + + +//---------------------------------------------------------------------------- + +bool Player::canJump() +{ + return mState == MoveState && mDamageState == Enabled && !isMounted() && !mJumpDelay && mEnergy >= mDataBlock->minJumpEnergy && mJumpSurfaceLastContact < JumpSkipContactsMax; +} + +bool Player::canJetJump() +{ + return mState == MoveState && mDamageState == Enabled && !isMounted() && mEnergy >= mDataBlock->jetMinJumpEnergy && mDataBlock->jetJumpForce != 0.0f; +} + +bool Player::canSwim() +{ + // Not used! + //return mState == MoveState && mDamageState == Enabled && !isMounted() && mEnergy >= mDataBlock->minSwimEnergy && mWaterCoverage >= 0.8f; + return true; +} + +bool Player::canCrouch() +{ + if ( mState != MoveState || + mDamageState != Enabled || + isMounted() || + mSwimming || + mFalling ) + return false; + + // Can't crouch if no crouch animation! + if ( mDataBlock->actionList[PlayerData::CrouchRootAnim].sequence == -1 ) + return false; + + // We are already in this pose, so don't test it again... + if ( mPose == CrouchPose ) + return true; + + // NOTE: we aren't testing for overlaps if there is no physics player! + // Do standard Torque collision tests here!! + if ( !mPhysicsPlayer ) + return true; + + return mPhysicsPlayer->testSpacials( getPosition(), mDataBlock->crouchBoxSize ); +} + +bool Player::canStand() +{ + if ( mState != MoveState || + mDamageState != Enabled || + isMounted() || + mSwimming ) + return false; + + // Do standard Torque physics test here! + if ( !mPhysicsPlayer ) + return true; + + // We are already in this pose, so don't test it again... + if ( mPose == StandPose ) + return true; + + return mPhysicsPlayer->testSpacials( getPosition(), mDataBlock->boxSize ); +} + +bool Player::canProne() +{ + if ( mState != MoveState || + mDamageState != Enabled || + isMounted() || + mSwimming || + mFalling ) + return false; + + // Can't go prone if no prone animation! + if ( mDataBlock->actionList[PlayerData::ProneRootAnim].sequence == -1 ) + return false; + + // Do standard Torque physics test here! + if ( !mPhysicsPlayer ) + return true; + + // We are already in this pose, so don't test it again... + if ( mPose == PronePose ) + return true; + + return mPhysicsPlayer->testSpacials( getPosition(), mDataBlock->proneBoxSize ); +} + +//---------------------------------------------------------------------------- + +void Player::updateDamageLevel() +{ + if (!isGhost()) + setDamageState((mDamage >= mDataBlock->maxDamage)? Disabled: Enabled); + if (mDamageThread) + mShapeInstance->setPos(mDamageThread, mDamage / mDataBlock->destroyedLevel); +} + +void Player::updateDamageState() +{ + // Become a corpse when we're disabled (dead). + if (mDamageState == Enabled) { + mTypeMask &= ~CorpseObjectType; + mTypeMask |= PlayerObjectType; + } + else { + mTypeMask &= ~PlayerObjectType; + mTypeMask |= CorpseObjectType; + } + + Parent::updateDamageState(); +} + + +//---------------------------------------------------------------------------- + +void Player::updateLookAnimation(F32 dT) +{ + // Calculate our interpolated head position. + Point3F renderHead = delta.head + delta.headVec * dT; + + // Adjust look pos. This assumes that the animations match + // the min and max look angles provided in the datablock. + if (mArmAnimation.thread) + { + // TG: Adjust arm position to avoid collision. + F32 tp = mControlObject? 0.5: + (renderHead.x - mArmRange.min) / mArmRange.delta; + mShapeInstance->setPos(mArmAnimation.thread,mClampF(tp,0,1)); + } + + if (mHeadVThread) + { + F32 tp = (renderHead.x - mHeadVRange.min) / mHeadVRange.delta; + mShapeInstance->setPos(mHeadVThread,mClampF(tp,0,1)); + } + + if (mHeadHThread) + { + F32 dt = 2 * mDataBlock->maxLookAngle; + F32 tp = (renderHead.z + mDataBlock->maxLookAngle) / dt; + mShapeInstance->setPos(mHeadHThread,mClampF(tp,0,1)); + } +} + + +//---------------------------------------------------------------------------- +// Methods to get delta (as amount to affect velocity by) + +bool Player::inDeathAnim() +{ + if (mActionAnimation.thread && mActionAnimation.action >= 0) + if (mActionAnimation.action < mDataBlock->actionCount) + return mDataBlock->actionList[mActionAnimation.action].death; + + return false; +} + +// Get change from mLastDeathPos - return current pos. Assumes we're in death anim. +F32 Player::deathDelta(Point3F & delta) +{ + // Get ground delta from the last time we offset this. + MatrixF mat; + F32 pos = mShapeInstance->getPos(mActionAnimation.thread); + mShapeInstance->deltaGround1(mActionAnimation.thread, mDeath.lastPos, pos, mat); + mat.getColumn(3, & delta); + return pos; +} + +// Called before updatePos() to prepare it's needed change to velocity, which +// must roll over. Should be updated on tick, this is where we remember last +// position of animation that was used to roll into velocity. +void Player::updateDeathOffsets() +{ + if (inDeathAnim()) + // Get ground delta from the last time we offset this. + mDeath.lastPos = deathDelta(mDeath.posAdd); + else + mDeath.clear(); +} + + +//---------------------------------------------------------------------------- + +static const U32 sPlayerConformMask = InteriorObjectType|StaticShapeObjectType| + StaticObjectType|TerrainObjectType; + +static void accel(F32& from, F32 to, F32 rate) +{ + if (from < to) + from = getMin(from += rate, to); + else + from = getMax(from -= rate, to); +} + +// if (dt == -1) +// normal tick, so we advance. +// else +// interpolate with dt as % of tick, don't advance +// +MatrixF * Player::Death::fallToGround(F32 dt, const Point3F& loc, F32 curZ, F32 boxRad) +{ + static const F32 sConformCheckDown = 4.0f; + RayInfo coll; + bool conformToStairs = false; + Point3F pos(loc.x, loc.y, loc.z + 0.1f); + Point3F below(pos.x, pos.y, loc.z - sConformCheckDown); + MatrixF * retVal = NULL; + + PROFILE_SCOPE(ConformToGround); + + if (gClientContainer.castRay(pos, below, sPlayerConformMask, &coll)) + { + F32 adjust, height = (loc.z - coll.point.z), sink = curSink; + VectorF desNormal = coll.normal; + VectorF normal = curNormal; + + // dt >= 0 means we're interpolating and don't accel the numbers + if (dt >= 0.0f) + adjust = dt * TickSec; + else + adjust = TickSec; + + // Try to get them to conform to stairs by doing several LOS calls. We do this if + // normal is within about 5 deg. of vertical. + if (desNormal.z > 0.995f) + { + Point3F corners[3], downpts[3]; + S32 c; + + for (c = 0; c < 3; c++) { // Build 3 corners to cast down from- + corners[c].set(loc.x - boxRad, loc.y - boxRad, loc.z + 1.0f); + if (c) // add (0,boxWidth) and (boxWidth,0) + corners[c][c - 1] += (boxRad * 2.0f); + downpts[c].set(corners[c].x, corners[c].y, loc.z - sConformCheckDown); + } + + // Do the three casts- + for (c = 0; c < 3; c++) + if (gClientContainer.castRay(corners[c], downpts[c], sPlayerConformMask, &coll)) + downpts[c] = coll.point; + else + break; + + // Do the math if everything hit below- + if (c == 3) { + mCross(downpts[1] - downpts[0], downpts[2] - downpts[1], &desNormal); + AssertFatal(desNormal.z > 0, "Abnormality in Player::Death::fallToGround()"); + downpts[2] = downpts[2] - downpts[1]; + downpts[1] = downpts[1] - downpts[0]; + desNormal.normalize(); + conformToStairs = true; + } + } + + // Move normal in direction we want- + F32 * cur = normal, * des = desNormal; + for (S32 i = 0; i < 3; i++) + accel(*cur++, *des++, adjust * 0.25f); + + if (mFabs(height) < 2.2f && !normal.isZero() && desNormal.z > 0.01f) + { + VectorF upY(0.0f, 1.0f, 0.0f), ahead; + VectorF sideVec; + MatrixF mat(true); + + normal.normalize(); + mat.set(EulerF (0.0f, 0.0f, curZ)); + mat.mulV(upY, & ahead); + mCross(ahead, normal, &sideVec); + sideVec.normalize(); + mCross(normal, sideVec, &ahead); + + static MatrixF resMat(true); + resMat.setColumn(0, sideVec); + resMat.setColumn(1, ahead); + resMat.setColumn(2, normal); + + // Adjust Z down to account for box offset on slope. Figure out how + // much we want to sink, and gradually accel to this amount. Don't do if + // we're conforming to stairs though + F32 xy = mSqrt(desNormal.x * desNormal.x + desNormal.y * desNormal.y); + F32 desiredSink = (boxRad * xy / desNormal.z); + if (conformToStairs) + desiredSink *= 0.5f; + + accel(sink, desiredSink, adjust * 0.15f); + + Point3F position(pos); + position.z -= sink; + resMat.setColumn(3, position); + + if (dt < 0.0f) + { // we're moving, so update normal and sink amount + curNormal = normal; + curSink = sink; + } + + retVal = &resMat; + } + } + return retVal; +} + + +//------------------------------------------------------------------------------------- + +// This is called ::onAdd() to see if we're in a sitting animation. These then +// can use a longer tick delay for the mount to get across. +bool Player::inSittingAnim() +{ + U32 action = mActionAnimation.action; + if (mActionAnimation.thread && action < mDataBlock->actionCount) { + const char * name = mDataBlock->actionList[action].name; + if (!dStricmp(name, "Sitting") || !dStricmp(name, "Scoutroot")) + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- + +bool Player::setArmThread(const char* sequence) +{ + // The arm sequence must be in the action list. + for (U32 i = 1; i < mDataBlock->actionCount; i++) + if (!dStricmp(mDataBlock->actionList[i].name,sequence)) + return setArmThread(i); + return false; +} + +bool Player::setArmThread(U32 action) +{ + PlayerData::ActionAnimation &anim = mDataBlock->actionList[action]; + if (anim.sequence != -1 && + anim.sequence != mShapeInstance->getSequence(mArmAnimation.thread)) + { + mShapeInstance->setSequence(mArmAnimation.thread,anim.sequence,0); + mArmAnimation.action = action; + setMaskBits(ActionMask); + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- + +bool Player::setActionThread(const char* sequence,bool hold,bool wait,bool fsp) +{ + for (U32 i = 1; i < mDataBlock->actionCount; i++) + { + PlayerData::ActionAnimation &anim = mDataBlock->actionList[i]; + if (!dStricmp(anim.name,sequence)) + { + setActionThread(i,true,hold,wait,fsp); + setMaskBits(ActionMask); + return true; + } + } + return false; +} + +void Player::setActionThread(U32 action,bool forward,bool hold,bool wait,bool fsp, bool forceSet) +{ + if (mActionAnimation.action == action && !forceSet) + return; + + if (action >= PlayerData::NumActionAnims) + { + Con::errorf("Player::setActionThread(%d): Player action out of range", action); + return; + } + + PlayerData::ActionAnimation &anim = mDataBlock->actionList[action]; + if (anim.sequence != -1) + { + mActionAnimation.action = action; + mActionAnimation.forward = forward; + mActionAnimation.firstPerson = fsp; + mActionAnimation.holdAtEnd = hold; + mActionAnimation.waitForEnd = hold? true: wait; + mActionAnimation.animateOnServer = fsp; + mActionAnimation.atEnd = false; + mActionAnimation.delayTicks = (S32)sNewAnimationTickTime; + mActionAnimation.atEnd = false; + + if (sUseAnimationTransitions && (isGhost()/* || mActionAnimation.animateOnServer*/)) + { + // The transition code needs the timeScale to be set in the + // right direction to know which way to go. + F32 transTime = sAnimationTransitionTime; + if (mDataBlock && mDataBlock->isJumpAction(action)) + transTime = 0.15f; + + F32 timeScale = mActionAnimation.forward ? 1.0f : -1.0f; + if (mDataBlock && mDataBlock->isJumpAction(action)) + timeScale *= 1.5f; + + mShapeInstance->setTimeScale(mActionAnimation.thread,timeScale); + mShapeInstance->transitionToSequence(mActionAnimation.thread,anim.sequence, + mActionAnimation.forward ? 0.0f : 1.0f, transTime, true); + } + else + mShapeInstance->setSequence(mActionAnimation.thread,anim.sequence, + mActionAnimation.forward ? 0.0f : 1.0f); + } +} + +void Player::updateActionThread() +{ + PROFILE_START(UpdateActionThread); + + // Select an action animation sequence, this assumes that + // this function is called once per tick. + if(mActionAnimation.action != PlayerData::NullAnimation) + if (mActionAnimation.forward) + mActionAnimation.atEnd = mShapeInstance->getPos(mActionAnimation.thread) == 1; + else + mActionAnimation.atEnd = mShapeInstance->getPos(mActionAnimation.thread) == 0; + + // Only need to deal with triggers on the client + if (isGhost()) { + bool triggeredLeft = false; + bool triggeredRight = false; + F32 offset = 0.0f; + if(mShapeInstance->getTriggerState(1)) { + triggeredLeft = true; + offset = -mDataBlock->decalOffset; + } + else if(mShapeInstance->getTriggerState(2)) { + triggeredRight = true; + offset = mDataBlock->decalOffset; + } + + + if (triggeredLeft || triggeredRight) + { + Point3F rot, pos; + RayInfo rInfo; + MatrixF mat = getRenderTransform(); + mat.getColumn(1, &rot); + mat.mulP(Point3F(offset,0.0f,0.0f), &pos); + if (gClientContainer.castRay(Point3F(pos.x, pos.y, pos.z + 0.01f), + Point3F(pos.x, pos.y, pos.z - 2.0f ), + STATIC_COLLISION_MASK | VehicleObjectType, &rInfo)) + { + Material* material = ( rInfo.material ? dynamic_cast< Material* >( rInfo.material->getMaterial() ) : 0 ); + + // Put footprints on surface, if appropriate for material. + + if( /*material && material->mShowFootprints + &&*/ mDataBlock->decalData != 0 ) + { + Point3F normal; + Point3F tangent; + mObjToWorld.getColumn( 0, &tangent ); + mObjToWorld.getColumn( 2, &normal ); + gDecalManager->addDecal( rInfo.point, normal, tangent, mDataBlock->decalData ); + } + + // Emit footpuffs. + + if( rInfo.t <= 0.5 && mWaterCoverage == 0.0 + && material && material->mShowDust ) + { + // New emitter every time for visibility reasons + ParticleEmitter * emitter = new ParticleEmitter; + emitter->onNewDataBlock( mDataBlock->footPuffEmitter ); + + ColorF colorList[ ParticleData::PDC_NUM_KEYS]; + + for( U32 x = 0; x < getMin( Material::NUM_EFFECT_COLOR_STAGES, ParticleData::PDC_NUM_KEYS ); ++ x ) + colorList[ x ].set( material->mEffectColor[ x ].red, + material->mEffectColor[ x ].green, + material->mEffectColor[ x ].blue, + material->mEffectColor[ x ].alpha ); + for( U32 x = Material::NUM_EFFECT_COLOR_STAGES; x < ParticleData::PDC_NUM_KEYS; ++ x ) + colorList[ x ].set( 1.0, 1.0, 1.0, 0.0 ); + + emitter->setColors( colorList ); + if( !emitter->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() ); + delete emitter; + emitter = NULL; + } + else + { + emitter->emitParticles( pos, Point3F( 0.0, 0.0, 1.0 ), mDataBlock->footPuffRadius, + Point3F( 0, 0, 0 ), mDataBlock->footPuffNumParts ); + emitter->deleteWhenEmpty(); + } + } + + // Play footstep sound. + + MatrixF footMat = getTransform(); + if( mWaterCoverage > 0.0 ) + { + // Treading water. + + if ( mWaterCoverage < mDataBlock->footSplashHeight ) + SFX->playOnce( mDataBlock->sound[ PlayerData::FootShallowSplash ], &footMat ); + else + { + if ( mWaterCoverage < 1.0 ) + SFX->playOnce( mDataBlock->sound[ PlayerData::FootWading ], &footMat ); + else + { + if ( triggeredLeft ) + { + SFX->playOnce( mDataBlock->sound[ PlayerData::FootUnderWater ], &footMat ); + SFX->playOnce( mDataBlock->sound[ PlayerData::FootBubbles ], &footMat ); + } + } + } + } + else if( material && material->mFootstepSoundCustom ) + { + // Footstep sound defined on material. This is the way it should be + // done now. + + SFX->playOnce( material->mFootstepSoundCustom, &footMat ); + } + else + { + // Play default sound. + + S32 sound = -1; + if( material && material->mFootstepSoundId != -1 ) + sound = material->mFootstepSoundId; + else if( rInfo.object->getTypeMask() & VehicleObjectType ) + sound = 2; + + switch ( sound ) + { + case 0: // Soft + SFX->playOnce( mDataBlock->sound[PlayerData::FootSoft], &footMat ); + break; + case 1: // Hard + SFX->playOnce( mDataBlock->sound[PlayerData::FootHard], &footMat ); + break; + case 2: // Metal + SFX->playOnce( mDataBlock->sound[PlayerData::FootMetal], &footMat ); + break; + case 3: // Snow + SFX->playOnce( mDataBlock->sound[PlayerData::FootSnow], &footMat ); + break; + /* + default: //Hard + SFX->playOnce( mDataBlock->sound[PlayerData::FootHard], &footMat ); + break; + */ + } + } + } + } + } + + // Mount pending variable puts a hold on the delayTicks below so players don't + // inadvertently stand up because their mount has not come over yet. + if (mMountPending) + mMountPending = (isMounted() ? 0 : (mMountPending - 1)); + + if (mActionAnimation.action == PlayerData::NullAnimation || + ((!mActionAnimation.waitForEnd || mActionAnimation.atEnd)) && + !mActionAnimation.holdAtEnd && (mActionAnimation.delayTicks -= !mMountPending) <= 0) + { + //The scripting language will get a call back when a script animation has finished... + // example: When the chat menu animations are done playing... + if ( isServerObject() && mActionAnimation.action >= PlayerData::NumTableActionAnims ) + Con::executef(mDataBlock, "animationDone",scriptThis()); + pickActionAnimation(); + } + + if ( (mActionAnimation.action != PlayerData::LandAnim) && + (mActionAnimation.action != PlayerData::NullAnimation) ) + { + // Update action animation time scale to match ground velocity + PlayerData::ActionAnimation &anim = + mDataBlock->actionList[mActionAnimation.action]; + F32 scale = 1; + if (anim.velocityScale && anim.speed) { + VectorF vel; + mWorldToObj.mulV(mVelocity,&vel); + scale = mFabs(mDot(vel, anim.dir) / anim.speed); + + if (scale > mDataBlock->maxTimeScale) + scale = mDataBlock->maxTimeScale; + } + + mShapeInstance->setTimeScale(mActionAnimation.thread, + mActionAnimation.forward? scale: -scale); + } + PROFILE_END(); +} + +void Player::pickActionAnimation() +{ + // Only select animations in our normal move state. + if (mState != MoveState || mDamageState != Enabled) + return; + + if (isMounted()) + { + // Go into root position unless something was set explicitly + // from a script. + if (mActionAnimation.action != PlayerData::RootAnim && + mActionAnimation.action < PlayerData::NumTableActionAnims) + setActionThread(PlayerData::RootAnim,true,false,false); + return; + } + + bool forward = true; + U32 action = PlayerData::RootAnim; + bool fsp = false; + + // Jetting overrides the fall animation condition + if (mJetting) + { + // Play the jetting animation + action = PlayerData::JetAnim; + } + else if (mFalling) + { + // Not in contact with any surface and falling + action = PlayerData::FallAnim; + } + else if ( mSwimming ) + { + action = PlayerData::SwimRootAnim; + + F32 curMax = 0.1f; + VectorF vel; + mWorldToObj.mulV(mVelocity,&vel); + for (U32 i = PlayerData::SwimForwardAnim; i <= PlayerData::SwimRightAnim; i++) + { + PlayerData::ActionAnimation &anim = mDataBlock->actionList[i]; + if (anim.sequence != -1 && anim.speed) + { + // Bias towards picking the forward/backward anims over + // the side to prevent oscillation between anims. This seems to work + // ok but the real fix is to have 8 way directional animations. + VectorF biasedDir = anim.dir * VectorF(0.5f,1.0f,0.5f); + F32 d = mDot(vel, biasedDir); + if (d > curMax) + { + curMax = d; + action = i; + forward = true; + } + } + } + } + else if ( mPose == StandPose ) + { + if (mContactTimer >= sContactTickTime) { + // Nothing under our feet + action = PlayerData::RootAnim; + } + else + { + // Our feet are on something + // Pick animation that is the best fit for our current velocity. + // Assumes that root is the first animation in the list. + F32 curMax = 0.1f; + VectorF vel; + mWorldToObj.mulV(mVelocity,&vel); + for (U32 i = 1; i < PlayerData::NumMoveActionAnims; i++) + { + PlayerData::ActionAnimation &anim = mDataBlock->actionList[i]; + if (anim.sequence != -1 && anim.speed) + { + // We bias towards picking the forward/backward anims over + // the side to prevent oscillation between anims. This seems + // to work ok but the real fix is to have 8 way directional + // animations. + VectorF biasedDir = anim.dir * VectorF(0.5f,1.0f,0.5f); + F32 d = mDot(vel, biasedDir); + if (d > curMax) + { + curMax = d; + action = i; + forward = true; + } + else + { + // Special case, re-use slide left animation to slide right + if (i == PlayerData::SideLeftAnim && -d > curMax) + { + curMax = -d; + action = i; + forward = false; + } + } + } + } + } + } + else if ( mPose == CrouchPose ) + { + VectorF vel; + mWorldToObj.mulV(mVelocity,&vel); + + action = PlayerData::CrouchRootAnim; + fsp = true; + + if ( vel.y > 0.1f ) + { + action = PlayerData::CrouchForwardAnim; + forward = true; + } + else if ( vel.y < -0.1f ) + { + action = PlayerData::CrouchForwardAnim; + forward = false; + } + } + else if ( mPose == PronePose ) + { + VectorF vel; + mWorldToObj.mulV(mVelocity,&vel); + + action = PlayerData::ProneRootAnim; + fsp = true; + + if ( vel.y > 0.1f ) + { + action = PlayerData::ProneForwardAnim; + forward = true; + } + else if ( vel.y < -0.1f ) + { + action = PlayerData::ProneForwardAnim; + forward = false; + } + } + setActionThread(action,forward,false,false,fsp); +} + +void Player::onImageRecoil(U32,ShapeBaseImageData::StateData::RecoilState) +{ + if (mRecoilThread) + { + mShapeInstance->setPos(mRecoilThread,0); + mShapeInstance->setTimeScale(mRecoilThread,1); + } +} + +void Player::onUnmount( ShapeBase *obj, S32 node ) +{ + // Reset back to root position during dismount. + setActionThread(PlayerData::RootAnim,true,false,false); + + // Re-orient the player straight up + Point3F pos,vec; + getTransform().getColumn(1,&vec); + getTransform().getColumn(3,&pos); + Point3F rot(0.0f,0.0f,-mAtan2(-vec.x,vec.y)); + setPosition(pos,rot); + + // Parent function will call script + Parent::onUnmount( obj, node ); +} + + +//---------------------------------------------------------------------------- + +void Player::updateAnimation(F32 dt) +{ + if ((isGhost() || mActionAnimation.animateOnServer) && mActionAnimation.thread) + mShapeInstance->advanceTime(dt,mActionAnimation.thread); + if (mRecoilThread) + mShapeInstance->advanceTime(dt,mRecoilThread); + + // If we are the client's player on this machine, then we need + // to make sure the transforms are up to date as they are used + // to setup the camera. + if (isGhost()) + { + if (getControllingClient()) + { + updateAnimationTree(isFirstPerson()); + mShapeInstance->animate(); + } + else + { + updateAnimationTree(false); + } + } +} + +void Player::updateAnimationTree(bool firstPerson) +{ + S32 mode = 0; + if (firstPerson) + if (mActionAnimation.firstPerson) + mode = 0; +// TSShapeInstance::MaskNodeRotation; +// TSShapeInstance::MaskNodePosX | +// TSShapeInstance::MaskNodePosY; + else + mode = TSShapeInstance::MaskNodeAllButBlend; + + for (U32 i = 0; i < PlayerData::NumSpineNodes; i++) + if (mDataBlock->spineNode[i] != -1) + mShapeInstance->setNodeAnimationState(mDataBlock->spineNode[i],mode); +} + + +//---------------------------------------------------------------------------- + +bool Player::step(Point3F *pos,F32 *maxStep,F32 time) +{ + const Point3F& scale = getScale(); + Box3F box; + VectorF offset = mVelocity * time; + box.minExtents = mObjBox.minExtents + offset + *pos; + box.maxExtents = mObjBox.maxExtents + offset + *pos; + box.maxExtents.z += mDataBlock->maxStepHeight * scale.z + sMinFaceDistance; + + SphereF sphere; + sphere.center = (box.minExtents + box.maxExtents) * 0.5f; + VectorF bv = box.maxExtents - sphere.center; + sphere.radius = bv.len(); + + ClippedPolyList polyList; + polyList.mPlaneList.clear(); + polyList.mNormal.set(0.0f, 0.0f, 0.0f); + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(box.minExtents,VectorF(-1.0f, 0.0f, 0.0f)); + polyList.mPlaneList[1].set(box.maxExtents,VectorF(0.0f, 1.0f, 0.0f)); + polyList.mPlaneList[2].set(box.maxExtents,VectorF(1.0f, 0.0f, 0.0f)); + polyList.mPlaneList[3].set(box.minExtents,VectorF(0.0f, -1.0f, 0.0f)); + polyList.mPlaneList[4].set(box.minExtents,VectorF(0.0f, 0.0f, -1.0f)); + polyList.mPlaneList[5].set(box.maxExtents,VectorF(0.0f, 0.0f, 1.0f)); + + CollisionWorkingList& rList = mConvex.getWorkingList(); + CollisionWorkingList* pList = rList.wLink.mNext; + while (pList != &rList) { + Convex* pConvex = pList->mConvex; + + // Alright, here's the deal... a polysoup mesh really needs to be + // designed with stepping in mind. If there are too many smallish polygons + // the stepping system here gets confused and allows you to run up walls + // or on the edges/seams of meshes. + + TSStatic *st = dynamic_cast (pConvex->getObject()); + bool skip = false; + if (st && !st->allowPlayerStep()) + skip = true; + + if ((pConvex->getObject()->getType() & StaticObjectType) != 0 && !skip) + { + Box3F convexBox = pConvex->getBoundingBox(); + if (box.isOverlapped(convexBox)) + pConvex->getPolyList(&polyList); + } + pList = pList->wLink.mNext; + } + + // Find max step height + F32 stepHeight = pos->z - sMinFaceDistance; + U32* vp = polyList.mIndexList.begin(); + U32* ep = polyList.mIndexList.end(); + for (; vp != ep; vp++) { + F32 h = polyList.mVertexList[*vp].point.z + sMinFaceDistance; + if (h > stepHeight) + stepHeight = h; + } + + F32 step = stepHeight - pos->z; + if (stepHeight > pos->z && step < *maxStep) { + // Go ahead and step + pos->z = stepHeight; + *maxStep -= step; + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +inline Point3F createInterpPos(const Point3F& s, const Point3F& e, const F32 t, const F32 d) +{ + Point3F ret; + ret.interpolate(s, e, t/d); + return ret; +} + +Point3F Player::_move( const F32 travelTime, Collision *outCol ) +{ + // Try and move to new pos + F32 totalMotion = 0.0f; + + // TODO: not used? + //F32 initialSpeed = mVelocity.len(); + + Point3F start; + Point3F initialPosition; + getTransform().getColumn(3,&start); + initialPosition = start; + + static CollisionList collisionList; + static CollisionList physZoneCollisionList; + + collisionList.clear(); + physZoneCollisionList.clear(); + + MatrixF collisionMatrix(true); + collisionMatrix.setColumn(3, start); + + VectorF firstNormal(0.0f, 0.0f, 0.0f); + F32 maxStep = mDataBlock->maxStepHeight; + F32 time = travelTime; + U32 count = 0; + + const Point3F& scale = getScale(); + + static Polyhedron sBoxPolyhedron; + static ExtrudedPolyList sExtrudedPolyList; + static ExtrudedPolyList sPhysZonePolyList; + + for (; count < sMoveRetryCount; count++) { + F32 speed = mVelocity.len(); + if (!speed && !mDeath.haveVelocity()) + break; + + Point3F end = start + mVelocity * time; + if (mDeath.haveVelocity()) { + // Add in death movement- + VectorF deathVel = mDeath.getPosAdd(); + VectorF resVel; + getTransform().mulV(deathVel, & resVel); + end += resVel; + } + Point3F distance = end - start; + + if (mFabs(distance.x) < mObjBox.len_x() && + mFabs(distance.y) < mObjBox.len_y() && + mFabs(distance.z) < mObjBox.len_z()) + { + // We can potentially early out of this. If there are no polys in the clipped polylist at our + // end position, then we can bail, and just set start = end; + Box3F wBox = mScaledBox; + wBox.minExtents += end; + wBox.maxExtents += end; + + static EarlyOutPolyList eaPolyList; + eaPolyList.clear(); + eaPolyList.mNormal.set(0.0f, 0.0f, 0.0f); + eaPolyList.mPlaneList.clear(); + eaPolyList.mPlaneList.setSize(6); + eaPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1.0f, 0.0f, 0.0f)); + eaPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0.0f, 1.0f, 0.0f)); + eaPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1.0f, 0.0f, 0.0f)); + eaPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0.0f, -1.0f, 0.0f)); + eaPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0.0f, 0.0f, -1.0f)); + eaPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0.0f, 0.0f, 1.0f)); + + // Build list from convex states here... + CollisionWorkingList& rList = mConvex.getWorkingList(); + CollisionWorkingList* pList = rList.wLink.mNext; + while (pList != &rList) { + Convex* pConvex = pList->mConvex; + if (pConvex->getObject()->getTypeMask() & sCollisionMoveMask) { + Box3F convexBox = pConvex->getBoundingBox(); + if (wBox.isOverlapped(convexBox)) + { + // No need to separate out the physical zones here, we want those + // to cause a fallthrough as well... + pConvex->getPolyList(&eaPolyList); + } + } + pList = pList->wLink.mNext; + } + + if (eaPolyList.isEmpty()) + { + totalMotion += (end - start).len(); + start = end; + break; + } + } + + collisionMatrix.setColumn(3, start); + sBoxPolyhedron.buildBox(collisionMatrix, mScaledBox); + + // Setup the bounding box for the extrudedPolyList + Box3F plistBox = mScaledBox; + collisionMatrix.mul(plistBox); + Point3F oldMin = plistBox.minExtents; + Point3F oldMax = plistBox.maxExtents; + plistBox.minExtents.setMin(oldMin + (mVelocity * time) - Point3F(0.1f, 0.1f, 0.1f)); + plistBox.maxExtents.setMax(oldMax + (mVelocity * time) + Point3F(0.1f, 0.1f, 0.1f)); + + // Build extruded polyList... + VectorF vector = end - start; + sExtrudedPolyList.extrude(sBoxPolyhedron,vector); + sExtrudedPolyList.setVelocity(mVelocity); + sExtrudedPolyList.setCollisionList(&collisionList); + + sPhysZonePolyList.extrude(sBoxPolyhedron,vector); + sPhysZonePolyList.setVelocity(mVelocity); + sPhysZonePolyList.setCollisionList(&physZoneCollisionList); + + // Build list from convex states here... + CollisionWorkingList& rList = mConvex.getWorkingList(); + CollisionWorkingList* pList = rList.wLink.mNext; + while (pList != &rList) { + Convex* pConvex = pList->mConvex; + if (pConvex->getObject()->getTypeMask() & sCollisionMoveMask) { + Box3F convexBox = pConvex->getBoundingBox(); + if (plistBox.isOverlapped(convexBox)) + { + if (pConvex->getObject()->getTypeMask() & PhysicalZoneObjectType) + pConvex->getPolyList(&sPhysZonePolyList); + else + pConvex->getPolyList(&sExtrudedPolyList); + } + } + pList = pList->wLink.mNext; + } + + // Take into account any physical zones... + for (U32 j = 0; j < physZoneCollisionList.getCount(); j++) + { + AssertFatal(dynamic_cast(physZoneCollisionList[j].object), "Bad phys zone!"); + const PhysicalZone* pZone = (PhysicalZone*)physZoneCollisionList[j].object; + if (pZone->isActive()) + mVelocity *= pZone->getVelocityMod(); + } + + if (collisionList.getCount() != 0 && collisionList.getTime() < 1.0f) + { + // Set to collision point + F32 velLen = mVelocity.len(); + + F32 dt = time * getMin(collisionList.getTime(), 1.0f); + start += mVelocity * dt; + time -= dt; + + totalMotion += velLen * dt; + + mFalling = false; + + // Back off... + if ( velLen > 0.f ) { + F32 newT = getMin(0.01f / velLen, dt); + start -= mVelocity * newT; + totalMotion -= velLen * newT; + } + + // Try stepping if there is a vertical surface + if (collisionList.getMaxHeight() < start.z + mDataBlock->maxStepHeight * scale.z) + { + bool stepped = false; + for (U32 c = 0; c < collisionList.getCount(); c++) + { + const Collision& cp = collisionList[c]; + // if (mFabs(mDot(cp.normal,VectorF(0,0,1))) < sVerticalStepDot) + // Dot with (0,0,1) just extracts Z component [lh]- + if (mFabs(cp.normal.z) < sVerticalStepDot) + { + stepped = step(&start,&maxStep,time); + break; + } + } + if (stepped) + { + continue; + } + } + + // Pick the surface most parallel to the face that was hit. + const Collision *collision = &collisionList[0]; + const Collision *cp = collision + 1; + const Collision *ep = collision + collisionList.getCount(); + for (; cp != ep; cp++) + { + if (cp->faceDot > collision->faceDot) + collision = cp; + } + + // Copy this collision out so + // we can use it to do impacts + // and query collision. + *outCol = *collision; + + F32 bd = -mDot( mVelocity, collision->normal ); + + // Subtract out velocity + VectorF dv = collision->normal * (bd + sNormalElasticity); + mVelocity += dv; + if (count == 0) + { + firstNormal = collision->normal; + } + else + { + if (count == 1) + { + // Re-orient velocity along the crease. + if (mDot(dv,firstNormal) < 0.0f && + mDot(collision->normal,firstNormal) < 0.0f) + { + VectorF nv; + mCross(collision->normal,firstNormal,&nv); + F32 nvl = nv.len(); + if (nvl) + { + if (mDot(nv,mVelocity) < 0.0f) + nvl = -nvl; + nv *= mVelocity.len() / nvl; + mVelocity = nv; + } + } + } + } + } + else + { + totalMotion += (end - start).len(); + start = end; + break; + } + } + + if (count == sMoveRetryCount) + { + // Failed to move + start = initialPosition; + mVelocity.set(0.0f, 0.0f, 0.0f); + } + + return start; +} + +void Player::_handleCollision( const Collision &collision ) +{ + F32 bd = -mDot( mVelocity, collision.normal ); + + // shake camera on ground impact + if( bd > mDataBlock->groundImpactMinSpeed && isControlObject() ) + { + F32 ampScale = (bd - mDataBlock->groundImpactMinSpeed) / mDataBlock->minImpactSpeed; + + CameraShake *groundImpactShake = new CameraShake; + groundImpactShake->setDuration( mDataBlock->groundImpactShakeDuration ); + groundImpactShake->setFrequency( mDataBlock->groundImpactShakeFreq ); + + VectorF shakeAmp = mDataBlock->groundImpactShakeAmp * ampScale; + groundImpactShake->setAmplitude( shakeAmp ); + groundImpactShake->setFalloff( mDataBlock->groundImpactShakeFalloff ); + groundImpactShake->init(); + gCamFXMgr.addFX( groundImpactShake ); + } + + if ( bd > mDataBlock->minImpactSpeed && !mMountPending ) + { + if ( !isGhost() ) + onImpact( collision.object, collision.normal * bd ); + + if (mDamageState == Enabled && mState != RecoverState) + { + // Scale how long we're down for + F32 value = (bd - mDataBlock->minImpactSpeed); + F32 range = (mDataBlock->minImpactSpeed * 0.9f); + U32 recover = mDataBlock->recoverDelay; + if (value < range) + recover = 1 + S32(mFloor( F32(recover) * value / range) ); + //Con::printf("Used %d recover ticks", recover); + //Con::printf(" minImpact = %g, this one = %g", mDataBlock->minImpactSpeed, bd); + setState(RecoverState, recover); + } + } + + if ( isServerObject() && bd > (mDataBlock->minImpactSpeed / 3.0f) ) + { + mImpactSound = PlayerData::ImpactNormal; + setMaskBits(ImpactMask); + } + + // Track collisions + if ( !isGhost() && + collision.object && + collision.object != mContactInfo.contactObject ) + queueCollision( collision.object, mVelocity - collision.object->getVelocity() ); +} + +bool Player::updatePos(const F32 travelTime) +{ + PROFILE_SCOPE(Player_UpdatePos); + getTransform().getColumn(3,&delta.posVec); + + // When mounted to another object, only Z rotation used. + if (isMounted()) { + mVelocity = mMount.object->getVelocity(); + setPosition(Point3F(0.0f, 0.0f, 0.0f), mRot); + setMaskBits(MoveMask); + return true; + } + + Point3F newPos; + + Collision col; + dMemset( &col, 0, sizeof( col ) ); + + // DEBUG: + //Point3F savedVelocity = mVelocity; + + // This avoids unnecessary work, but also avoids problems with + // the physicsPlayer jitters while stationary. + if ( mVelocity.isZero() ) + newPos = delta.posVec; + else + { + if ( mPhysicsPlayer ) + { + newPos = mPhysicsPlayer->move( mVelocity * travelTime, &col ); + mVelocity = ( newPos - delta.posVec ) / travelTime; + } + else + newPos = _move( travelTime, &col ); + + // DEBUG: + //if ( isClientObject() ) + // Con::printf( "(client) vel: %g %g %g", mVelocity.x, mVelocity.y, mVelocity.z ); + //else + // Con::printf( "(server) vel: %g %g %g", mVelocity.x, mVelocity.y, mVelocity.z ); + } + + _handleCollision( col ); + + // Set new position + // If on the client, calc delta for backstepping + if (isClientObject()) + { + delta.pos = newPos; + delta.posVec = delta.posVec - delta.pos; + delta.dt = 1.0f; + } + + setPosition( newPos, mRot ); + setMaskBits( MoveMask ); + updateContainer(); + + if (!isGhost()) + { + // Collisions are only queued on the server and can be + // generated by either updateMove or updatePos + notifyCollision(); + + // Do mission area callbacks on the server as well + checkMissionArea(); + } + + // Check the total distance moved. If it is more than 1000th of the velocity, then + // we moved a fair amount... + //if (totalMotion >= (0.001f * initialSpeed)) + return true; + //else + //return false; +} + + +//---------------------------------------------------------------------------- + +void Player::_findContact( SceneObject **contactObject, VectorF *contactNormal ) +{ + Point3F pos; + getTransform().getColumn(3,&pos); + + Box3F wBox; + Point3F exp(0,0,sTractionDistance); + wBox.minExtents = pos + mScaledBox.minExtents - exp; + wBox.maxExtents.x = pos.x + mScaledBox.maxExtents.x; + wBox.maxExtents.y = pos.y + mScaledBox.maxExtents.y; + wBox.maxExtents.z = pos.z + mScaledBox.minExtents.z + sTractionDistance; + + static ClippedPolyList polyList; + polyList.clear(); + polyList.doConstruct(); + polyList.mNormal.set(0.0f, 0.0f, 0.0f); + polyList.setInterestNormal(Point3F(0.0f, 0.0f, -1.0f)); + + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].setYZ(wBox.minExtents, -1.0f); + polyList.mPlaneList[1].setXZ(wBox.maxExtents, 1.0f); + polyList.mPlaneList[2].setYZ(wBox.maxExtents, 1.0f); + polyList.mPlaneList[3].setXZ(wBox.minExtents, -1.0f); + polyList.mPlaneList[4].setXY(wBox.minExtents, -1.0f); + polyList.mPlaneList[5].setXY(wBox.maxExtents, 1.0f); + Box3F plistBox = wBox; + + // Expand build box as it will be used to collide with items. + // PickupRadius will be at least the size of the box. + F32 pd = (F32)mDataBlock->pickupDelta; + wBox.minExtents.x -= pd; wBox.minExtents.y -= pd; + wBox.maxExtents.x += pd; wBox.maxExtents.y += pd; + wBox.maxExtents.z = pos.z + mScaledBox.maxExtents.z; + + // Build list from convex states here... + CollisionWorkingList& rList = mConvex.getWorkingList(); + CollisionWorkingList* pList = rList.wLink.mNext; + U32 mask = isGhost() ? sClientCollisionContactMask : sServerCollisionContactMask; + while (pList != &rList) + { + Convex* pConvex = pList->mConvex; + + U32 objectMask = pConvex->getObject()->getTypeMask(); + + // Check: triggers, corpses and items... + // + if (objectMask & TriggerObjectType) + { + Trigger* pTrigger = static_cast(pConvex->getObject()); + pTrigger->potentialEnterObject(this); + } + else if (objectMask & CorpseObjectType) + { + // If we've overlapped the worldbounding boxes, then that's it... + if (getWorldBox().isOverlapped(pConvex->getObject()->getWorldBox())) + { + ShapeBase* col = static_cast(pConvex->getObject()); + queueCollision(col,getVelocity() - col->getVelocity()); + } + } + else if (objectMask & ItemObjectType) + { + // If we've overlapped the worldbounding boxes, then that's it... + Item* item = static_cast(pConvex->getObject()); + if (getWorldBox().isOverlapped(item->getWorldBox())) + if (this != item->getCollisionObject()) + queueCollision(item,getVelocity() - item->getVelocity()); + } + else if ((objectMask & mask) && !(objectMask & PhysicalZoneObjectType)) + { + Box3F convexBox = pConvex->getBoundingBox(); + if (plistBox.isOverlapped(convexBox)) + pConvex->getPolyList(&polyList); + } + + pList = pList->wLink.mNext; + } + + if (!polyList.isEmpty()) + { + // Pick flattest surface + F32 bestVd = -1.0f; + ClippedPolyList::Poly* poly = polyList.mPolyList.begin(); + ClippedPolyList::Poly* end = polyList.mPolyList.end(); + for (; poly != end; poly++) + { + F32 vd = poly->plane.z; // i.e. mDot(Point3F(0,0,1), poly->plane); + if (vd > bestVd) + { + bestVd = vd; + *contactObject = poly->object; + *contactNormal = poly->plane; + } + } + } +} + +void Player::findContact( bool *run, bool *jump, VectorF *contactNormal ) +{ + SceneObject *contactObject = NULL; + + if ( mPhysicsPlayer ) + mPhysicsPlayer->findContact( &contactObject, contactNormal ); + else + _findContact( &contactObject, contactNormal ); + + F32 vd = (*contactNormal).z; + *run = vd > mDataBlock->runSurfaceCos; + *jump = vd > mDataBlock->jumpSurfaceCos; + + mContactInfo.clear(); + + mContactInfo.contacted = contactObject != NULL; + mContactInfo.contactObject = contactObject; + + if ( mContactInfo.contacted ) + mContactInfo.contactNormal = *contactNormal; + + mContactInfo.run = *run; + mContactInfo.jump = *jump; +} + +//---------------------------------------------------------------------------- + +void Player::checkMissionArea() +{ + // Checks to see if the player is in the Mission Area... + Point3F pos; + MissionArea * obj = dynamic_cast(Sim::findObject("GlobalMissionArea")); + + if(!obj) + return; + + const RectI &area = obj->getArea(); + getTransform().getColumn(3, &pos); + + if ((pos.x < area.point.x || pos.x > area.point.x + area.extent.x || + pos.y < area.point.y || pos.y > area.point.y + area.extent.y)) { + if(mInMissionArea) { + mInMissionArea = false; + Con::executef(mDataBlock, "onLeaveMissionArea",scriptThis()); + } + } + else if(!mInMissionArea) + { + mInMissionArea = true; + Con::executef(mDataBlock, "onEnterMissionArea",scriptThis()); + } +} + + +//---------------------------------------------------------------------------- + +bool Player::isDisplacable() const +{ + return true; +} + +Point3F Player::getMomentum() const +{ + return mVelocity * getMass(); +} + +void Player::setMomentum(const Point3F& newMomentum) +{ + Point3F newVelocity = newMomentum / getMass(); + mVelocity = newVelocity; +} + +#define LH_HACK 1 +// Hack for short-term soln to Training crash - +#if LH_HACK +static U32 sBalance; + +bool Player::displaceObject(const Point3F& displacement) +{ + F32 vellen = mVelocity.len(); + if (vellen < 0.001f || sBalance > 16) { + mVelocity.set(0.0f, 0.0f, 0.0f); + return false; + } + + F32 dt = displacement.len() / vellen; + + sBalance++; + + bool result = updatePos(dt); + + sBalance--; + + getTransform().getColumn(3, &delta.pos); + delta.posVec.set(0.0f, 0.0f, 0.0f); + + return result; +} + +#else + +bool Player::displaceObject(const Point3F& displacement) +{ + F32 vellen = mVelocity.len(); + if (vellen < 0.001f) { + mVelocity.set(0.0f, 0.0f, 0.0f); + return false; + } + + F32 dt = displacement.len() / vellen; + + bool result = updatePos(dt); + + mObjToWorld.getColumn(3, &delta.pos); + delta.posVec.set(0.0f, 0.0f, 0.0f); + + return result; +} + +#endif + +//---------------------------------------------------------------------------- + +void Player::setPosition(const Point3F& pos,const Point3F& rot) +{ + MatrixF mat; + if (isMounted()) { + // Use transform from mounted object + MatrixF nmat,zrot; + mMount.object->getMountTransform(mMount.node,&nmat); + zrot.set(EulerF(0.0f, 0.0f, rot.z)); + mat.mul(nmat,zrot); + } + else { + mat.set(EulerF(0.0f, 0.0f, rot.z)); + mat.setColumn(3,pos); + } + Parent::setTransform(mat); + mRot = rot; + + if ( mPhysicsPlayer ) + mPhysicsPlayer->setPosition( mat ); +} + + +void Player::setRenderPosition(const Point3F& pos, const Point3F& rot, F32 dt) +{ + MatrixF mat; + if (isMounted()) { + // Use transform from mounted object + MatrixF nmat,zrot; + mMount.object->getRenderMountTransform(mMount.node,&nmat); + zrot.set(EulerF(0.0f, 0.0f, rot.z)); + mat.mul(nmat,zrot); + } + else { + EulerF orient(0.0f, 0.0f, rot.z); + + mat.set(orient); + mat.setColumn(3, pos); + + if (inDeathAnim()) { + F32 boxRad = (mDataBlock->boxSize.x * 0.5f); + if (MatrixF * fallMat = mDeath.fallToGround(dt, pos, rot.z, boxRad)) + mat = * fallMat; + } + else + mDeath.initFall(); + } + Parent::setRenderTransform(mat); +} + +//---------------------------------------------------------------------------- + +void Player::setTransform(const MatrixF& mat) +{ + // This method should never be called on the client. + + // This currently converts all rotation in the mat into + // rotations around the z axis. + Point3F pos,vec; + mat.getColumn(1,&vec); + mat.getColumn(3,&pos); + Point3F rot(0.0f, 0.0f, -mAtan2(-vec.x,vec.y)); + setPosition(pos,rot); + setMaskBits(MoveMask | NoWarpMask); +} + +void Player::getEyeTransform(MatrixF* mat) +{ + // Eye transform in world space. We only use the eye position + // from the animation and supply our own rotation. + MatrixF pmat,xmat,zmat; + xmat.set(EulerF(mHead.x, 0.0f, 0.0f)); + zmat.set(EulerF(0.0f, 0.0f, mHead.z)); + pmat.mul(zmat,xmat); + + F32 *dp = pmat; + + F32* sp; + MatrixF eyeMat(true); + if (mDataBlock->eyeNode != -1) + { + sp = mShapeInstance->mNodeTransforms[mDataBlock->eyeNode]; + } + else + { + Point3F center; + mObjBox.getCenter(¢er); + eyeMat.setPosition(center); + sp = eyeMat; + } + + const Point3F& scale = getScale(); + dp[3] = sp[3] * scale.x; + dp[7] = sp[7] * scale.y; + dp[11] = sp[11] * scale.z; + mat->mul(getTransform(),pmat); +} + +void Player::getRenderEyeTransform(MatrixF* mat) +{ + // Eye transform in world space. We only use the eye position + // from the animation and supply our own rotation. + MatrixF pmat,xmat,zmat; + xmat.set(EulerF(delta.head.x + delta.headVec.x * delta.dt, 0.0f, 0.0f)); + zmat.set(EulerF(0.0f, 0.0f, delta.head.z + delta.headVec.z * delta.dt)); + pmat.mul(zmat,xmat); + + F32 *dp = pmat; + + F32* sp; + MatrixF eyeMat(true); + if (mDataBlock->eyeNode != -1) + { + sp = mShapeInstance->mNodeTransforms[mDataBlock->eyeNode]; + } + else + { + Point3F center; + mObjBox.getCenter(¢er); + eyeMat.setPosition(center); + sp = eyeMat; + } + + const Point3F& scale = getScale(); + dp[3] = sp[3] * scale.x; + dp[7] = sp[7] * scale.y; + dp[11] = sp[11] * scale.z; + mat->mul(getRenderTransform(), pmat); +} + +void Player::getMuzzleTransform(U32 imageSlot,MatrixF* mat) +{ + MatrixF nmat; + Parent::getRetractionTransform(imageSlot,&nmat); + MatrixF smat; + Parent::getImageTransform(imageSlot,&smat); + + disableCollision(); + + // See if we are pushed into a wall... + if (getDamageState() == Enabled) { + Point3F start, end; + smat.getColumn(3, &start); + nmat.getColumn(3, &end); + + RayInfo rinfo; + if (getContainer()->castRay(start, end, sCollisionMoveMask, &rinfo)) { + Point3F finalPoint; + finalPoint.interpolate(start, end, rinfo.t); + nmat.setColumn(3, finalPoint); + } + else + Parent::getMuzzleTransform(imageSlot,&nmat); + } + else + Parent::getMuzzleTransform(imageSlot,&nmat); + + enableCollision(); + + // If we are in one of the standard player animations, adjust the + // muzzle to point in the direction we are looking. + if (mActionAnimation.action < PlayerData::NumTableActionAnims) + { + MatrixF xmat; + xmat.set(EulerF(mHead.x, 0.0f, 0.0f)); + mat->mul(getTransform(),xmat); + F32 *sp = nmat; + F32 *dp = *mat; + dp[3] = sp[3]; + dp[7] = sp[7]; + dp[11] = sp[11]; + } + else + { + *mat = nmat; + } +} + + +void Player::getRenderMuzzleTransform(U32 imageSlot,MatrixF* mat) +{ + MatrixF nmat; + Parent::getRenderRetractionTransform(imageSlot,&nmat); + MatrixF smat; + Parent::getRenderImageTransform(imageSlot,&smat); + + disableCollision(); + + // See if we are pushed into a wall... + if (getDamageState() == Enabled) + { + Point3F start, end; + smat.getColumn(3, &start); + nmat.getColumn(3, &end); + + RayInfo rinfo; + if (getContainer()->castRay(start, end, sCollisionMoveMask, &rinfo)) { + Point3F finalPoint; + finalPoint.interpolate(start, end, rinfo.t); + nmat.setColumn(3, finalPoint); + } + else + { + Parent::getRenderMuzzleTransform(imageSlot,&nmat); + } + } + else + { + Parent::getRenderMuzzleTransform(imageSlot,&nmat); + } + + enableCollision(); + + // If we are in one of the standard player animations, adjust the + // muzzle to point in the direction we are looking. + if (mActionAnimation.action < PlayerData::NumTableActionAnims) { + MatrixF xmat; + xmat.set(EulerF(mHead.x, 0.0f, 0.0f)); + mat->mul(getRenderTransform(),xmat); + F32 *sp = nmat; + F32 *dp = *mat; + dp[3] = sp[3]; + dp[7] = sp[7]; + dp[11] = sp[11]; + } + else + { + *mat = nmat; + } +} + +void Player::renderMountedImage( U32 imageSlot, TSRenderState &rstate ) +{ + GFX->pushWorldMatrix(); + + MatrixF world; + + MountedImage& image = mMountedImageList[imageSlot]; + ShapeBaseImageData& data = *image.dataBlock; + + MatrixF nmat; + + if ( !rstate.getSceneState()->isShadowPass() && isFirstPerson() && data.useEyeOffset ) + { + if ( imageSlot == 0 ) + { + //MatrixF mat; + //Parent::getRenderImageTransform( imageSlot, &mat); + + //if (rimage->mIndex == 0 && mWeaponBackFraction != 0.0 && getDamageState() == Enabled) { + + MatrixF nmat; + MatrixF smat; + + getRenderRetractionTransform(0,&nmat); + getRenderImageTransform(0,&smat); + + // See if we are pushed into a wall... + Point3F start, end; + smat.getColumn(3, &start); + nmat.getColumn(3, &end); + + Point3F displace = (start - end) * mWeaponBackFraction; + + MatrixF offsetMat( data.eyeOffset ); + + getRenderEyeTransform(&nmat); + world.mul(nmat,offsetMat); + + world.setPosition( world.getPosition() + displace ); + } + else + { + getRenderEyeTransform(&nmat); + world.mul(nmat,data.eyeOffset); + } + } + else + { + getRenderMountTransform(data.mountPoint,&nmat); + world.mul(nmat,data.mountTransform); + } + + GFX->setWorldMatrix( world ); + + image.shapeInstance->animate(); + image.shapeInstance->render( rstate ); + + GFX->popWorldMatrix(); +} + +// Bot aiming code calls this frequently and will work fine without the check +// for being pushed into a wall, which shows up on profile at ~ 3% (eight bots) +void Player::getMuzzlePointAI(U32 imageSlot, Point3F* point) +{ + MatrixF nmat; + Parent::getMuzzleTransform(imageSlot, &nmat); + + // If we are in one of the standard player animations, adjust the + // muzzle to point in the direction we are looking. + if (mActionAnimation.action < PlayerData::NumTableActionAnims) + { + MatrixF xmat; + xmat.set(EulerF(mHead.x, 0, 0)); + MatrixF result; + result.mul(getTransform(), xmat); + F32 *sp = nmat, *dp = result; + dp[3] = sp[3]; dp[7] = sp[7]; dp[11] = sp[11]; + result.getColumn(3, point); + } + else + nmat.getColumn(3, point); +} + +void Player::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot) +{ + if (!mControlObject.isNull() && mControlObject == getObjectMount()) { + mControlObject->getCameraParameters(min,max,off,rot); + return; + } + const Point3F& scale = getScale(); + *min = mDataBlock->cameraMinDist * scale.y; + *max = mDataBlock->cameraMaxDist * scale.y; + off->set(0.0f, 0.0f, 0.0f); + rot->identity(); +} + + +//---------------------------------------------------------------------------- + +Point3F Player::getVelocity() const +{ + return mVelocity; +} + +F32 Player::getSpeed() const +{ + return mVelocity.len(); +} + +void Player::setVelocity(const VectorF& vel) +{ + AssertFatal( !mIsNaN( vel ), "Player::setVelocity() - The velocity is NaN!" ); + + mVelocity = vel; + setMaskBits(MoveMask); +} + +void Player::applyImpulse(const Point3F&,const VectorF& vec) +{ + AssertFatal( !mIsNaN( vec ), "Player::applyImpulse() - The vector is NaN!" ); + + // Players ignore angular velocity + VectorF vel; + vel.x = vec.x / getMass(); + vel.y = vec.y / getMass(); + vel.z = vec.z / getMass(); + setVelocity(mVelocity + vel); +} + + +//---------------------------------------------------------------------------- + +bool Player::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + if (getDamageState() != Enabled) + return false; + + // Collide against bounding box. Need at least this for the editor. + F32 st,et,fst = 0.0f,fet = 1.0f; + F32 *bmin = &mObjBox.minExtents.x; + F32 *bmax = &mObjBox.maxExtents.x; + F32 const *si = &start.x; + F32 const *ei = &end.x; + + for (int i = 0; i < 3; i++) { + if (*si < *ei) { + if (*si > *bmax || *ei < *bmin) + return false; + F32 di = *ei - *si; + st = (*si < *bmin)? (*bmin - *si) / di: 0.0f; + et = (*ei > *bmax)? (*bmax - *si) / di: 1.0f; + } + else { + if (*ei > *bmax || *si < *bmin) + return false; + F32 di = *ei - *si; + st = (*si > *bmax)? (*bmax - *si) / di: 0.0f; + et = (*ei < *bmin)? (*bmin - *si) / di: 1.0f; + } + if (st > fst) fst = st; + if (et < fet) fet = et; + if (fet < fst) + return false; + bmin++; bmax++; + si++; ei++; + } + + info->normal = start - end; + info->normal.normalizeSafe(); + getTransform().mulV( info->normal ); + + info->t = fst; + info->object = this; + info->point.interpolate(start,end,fst); + info->material = 0; + return true; +} + + +//---------------------------------------------------------------------------- + +static MatrixF IMat(1); + +bool Player::buildPolyList(AbstractPolyList* polyList, const Box3F&, const SphereF&) +{ + // Collision with the player is always against the player's object + // space bounding box axis aligned in world space. + Point3F pos; + getTransform().getColumn(3,&pos); + IMat.setColumn(3,pos); + polyList->setTransform(&IMat, Point3F(1.0f,1.0f,1.0f)); + polyList->setObject(this); + polyList->addBox(mObjBox); + return true; +} + + +void Player::buildConvex(const Box3F& box, Convex* convex) +{ + if (mShapeInstance == NULL) + return; + + // These should really come out of a pool + mConvexList->collectGarbage(); + + Box3F realBox = box; + mWorldToObj.mul(realBox); + realBox.minExtents.convolveInverse(mObjScale); + realBox.maxExtents.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { + if (itr->mConvex->getType() == BoxConvexType && + itr->mConvex->getObject() == this) { + cc = itr->mConvex; + break; + } + } + if (cc) + return; + + // Create a new convex. + BoxConvex* cp = new OrthoBoxConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->init(this); + + mObjBox.getCenter(&cp->mCenter); + cp->mSize.x = mObjBox.len_x() / 2.0f; + cp->mSize.y = mObjBox.len_y() / 2.0f; + cp->mSize.z = mObjBox.len_z() / 2.0f; +} + + +//---------------------------------------------------------------------------- + +void Player::updateWorkingCollisionSet() +{ + // First, we need to adjust our velocity for possible acceleration. It is assumed + // that we will never accelerate more than 20 m/s for gravity, plus 10 m/s for + // jetting, and an equivalent 10 m/s for jumping. We also assume that the + // working list is updated on a Tick basis, which means we only expand our + // box by the possible movement in that tick. + Point3F scaledVelocity = mVelocity * TickSec; + F32 len = scaledVelocity.len(); + F32 newLen = len + (10.0f * TickSec); + + // Check to see if it is actually necessary to construct the new working list, + // or if we can use the cached version from the last query. We use the x + // component of the min member of the mWorkingQueryBox, which is lame, but + // it works ok. + bool updateSet = false; + + Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale()); + F32 l = (newLen * 1.1f) + 0.1f; // from Convex::updateWorkingList + const Point3F lPoint( l, l, l ); + convexBox.minExtents -= lPoint; + convexBox.maxExtents += lPoint; + + // Check containment + if (mWorkingQueryBox.minExtents.x != -1e9f) + { + if (mWorkingQueryBox.isContained(convexBox) == false) + // Needed region is outside the cached region. Update it. + updateSet = true; + } + else + { + // Must update + updateSet = true; + } + // Actually perform the query, if necessary + if (updateSet == true) { + const Point3F twolPoint( 2.0f * l, 2.0f * l, 2.0f * l ); + mWorkingQueryBox = convexBox; + mWorkingQueryBox.minExtents -= twolPoint; + mWorkingQueryBox.maxExtents += twolPoint; + + disableCollision(); + mConvex.updateWorkingList(mWorkingQueryBox, + isGhost() ? sClientCollisionContactMask : sServerCollisionContactMask); + enableCollision(); + } +} + + +//---------------------------------------------------------------------------- + +void Player::writePacketData(GameConnection *connection, BitStream *stream) +{ + Parent::writePacketData(connection, stream); + + stream->writeInt(mState,NumStateBits); + if (stream->writeFlag(mState == RecoverState)) + stream->writeInt(mRecoverTicks,PlayerData::RecoverDelayBits); + if (stream->writeFlag(mJumpDelay > 0)) + stream->writeInt(mJumpDelay,PlayerData::JumpDelayBits); + + Point3F pos; + getTransform().getColumn(3,&pos); + if (stream->writeFlag(!isMounted())) { + // Will get position from mount + stream->setCompressionPoint(pos); + stream->write(pos.x); + stream->write(pos.y); + stream->write(pos.z); + stream->write(mVelocity.x); + stream->write(mVelocity.y); + stream->write(mVelocity.z); + stream->writeInt(mJumpSurfaceLastContact > 15 ? 15 : mJumpSurfaceLastContact, 4); + } + stream->write(mHead.x); + stream->write(mHead.z); + stream->write(mRot.z); + + if (mControlObject) { + S32 gIndex = connection->getGhostIndex(mControlObject); + if (stream->writeFlag(gIndex != -1)) { + stream->writeInt(gIndex,NetConnection::GhostIdBitSize); + mControlObject->writePacketData(connection, stream); + } + } + else + stream->writeFlag(false); +} + + +void Player::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + + mState = (ActionState)stream->readInt(NumStateBits); + if (stream->readFlag()) + mRecoverTicks = stream->readInt(PlayerData::RecoverDelayBits); + if (stream->readFlag()) + mJumpDelay = stream->readInt(PlayerData::JumpDelayBits); + else + mJumpDelay = 0; + + Point3F pos,rot; + if (stream->readFlag()) { + // Only written if we are not mounted + stream->read(&pos.x); + stream->read(&pos.y); + stream->read(&pos.z); + stream->read(&mVelocity.x); + stream->read(&mVelocity.y); + stream->read(&mVelocity.z); + stream->setCompressionPoint(pos); + delta.pos = pos; + mJumpSurfaceLastContact = stream->readInt(4); + } + else + pos = delta.pos; + stream->read(&mHead.x); + stream->read(&mHead.z); + stream->read(&rot.z); + rot.x = rot.y = 0; + setPosition(pos,rot); + delta.head = mHead; + delta.rot = rot; + + if (stream->readFlag()) { + S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize); + ShapeBase* obj = static_cast(connection->resolveGhost(gIndex)); + setControlObject(obj); + obj->readPacketData(connection, stream); + } + else + setControlObject(0); +} + +U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag((mask & ImpactMask) && !(mask & InitialUpdateMask))) + stream->writeInt(mImpactSound, PlayerData::ImpactBits); + + if (stream->writeFlag(mask & ActionMask && + mActionAnimation.action != PlayerData::NullAnimation && + mActionAnimation.action >= PlayerData::NumTableActionAnims)) { + stream->writeInt(mActionAnimation.action,PlayerData::ActionAnimBits); + stream->writeFlag(mActionAnimation.holdAtEnd); + stream->writeFlag(mActionAnimation.atEnd); + stream->writeFlag(mActionAnimation.firstPerson); + if (!mActionAnimation.atEnd) { + // If somewhere in middle on initial update, must send position- + F32 where = mShapeInstance->getPos(mActionAnimation.thread); + if (stream->writeFlag((mask & InitialUpdateMask) != 0 && where > 0)) + stream->writeSignedFloat(where, 6); + } + } + + if (stream->writeFlag(mask & ActionMask && + mArmAnimation.action != PlayerData::NullAnimation && + (!(mask & InitialUpdateMask) || + mArmAnimation.action != mDataBlock->lookAction))) { + stream->writeInt(mArmAnimation.action,PlayerData::ActionAnimBits); + } + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + // we only need to send it if this is the initial update - in that case, + // the client won't know this is the control object yet. + if(stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) + return(retMask); + + if (stream->writeFlag(mask & MoveMask)) + { + stream->writeFlag(mFalling); + + stream->writeInt(mState,NumStateBits); + if (stream->writeFlag(mState == RecoverState)) + stream->writeInt(mRecoverTicks,PlayerData::RecoverDelayBits); + + Point3F pos; + getTransform().getColumn(3,&pos); + stream->writeCompressedPoint(pos); + F32 len = mVelocity.len(); + if(stream->writeFlag(len > 0.02f)) + { + Point3F outVel = mVelocity; + outVel *= 1.0f/len; + stream->writeNormalVector(outVel, 10); + len *= 32.0f; // 5 bits of fraction + if(len > 8191) + len = 8191; + stream->writeInt((S32)len, 13); + } + stream->writeFloat(mRot.z / M_2PI_F, 7); + stream->writeSignedFloat(mHead.x / mDataBlock->maxLookAngle, 6); + stream->writeSignedFloat(mHead.z / mDataBlock->maxLookAngle, 6); + delta.move.pack(stream); + stream->writeFlag(!(mask & NoWarpMask)); + } + // Ghost need energy to predict reliably + stream->writeFloat(getEnergyLevel() / mDataBlock->maxEnergy,EnergyLevelBits); + return retMask; +} + +void Player::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + if (stream->readFlag()) + mImpactSound = stream->readInt(PlayerData::ImpactBits); + + // Server specified action animation + if (stream->readFlag()) { + U32 action = stream->readInt(PlayerData::ActionAnimBits); + bool hold = stream->readFlag(); + bool atEnd = stream->readFlag(); + bool fsp = stream->readFlag(); + + F32 animPos = -1.0f; + if (!atEnd && stream->readFlag()) + animPos = stream->readSignedFloat(6); + + if (isProperlyAdded()) { + setActionThread(action,true,hold,true,fsp); + bool inDeath = inDeathAnim(); + if (atEnd) + { + mShapeInstance->clearTransition(mActionAnimation.thread); + mShapeInstance->setPos(mActionAnimation.thread, + mActionAnimation.forward? 1: 0); + if (inDeath) + mDeath.lastPos = 1.0f; + } + else if (animPos > 0) { + mShapeInstance->setPos(mActionAnimation.thread, animPos); + if (inDeath) + mDeath.lastPos = animPos; + } + + // mMountPending suppresses tickDelay countdown so players will sit until + // their mount, or another animation, comes through (or 13 seconds elapses). + mMountPending = (S32) (inSittingAnim() ? sMountPendingTickWait : 0); + } + else { + mActionAnimation.action = action; + mActionAnimation.holdAtEnd = hold; + mActionAnimation.atEnd = atEnd; + mActionAnimation.firstPerson = fsp; + } + } + + // Server specified arm animation + if (stream->readFlag()) { + U32 action = stream->readInt(PlayerData::ActionAnimBits); + if (isProperlyAdded()) + setArmThread(action); + else + mArmAnimation.action = action; + } + + // controlled by the client? + if(stream->readFlag()) + return; + + if (stream->readFlag()) { + mPredictionCount = sMaxPredictionTicks; + mFalling = stream->readFlag(); + + ActionState actionState = (ActionState)stream->readInt(NumStateBits); + if (stream->readFlag()) { + mRecoverTicks = stream->readInt(PlayerData::RecoverDelayBits); + setState(actionState, mRecoverTicks); + } + else + setState(actionState); + + Point3F pos,rot; + stream->readCompressedPoint(&pos); + F32 speed = mVelocity.len(); + if(stream->readFlag()) + { + stream->readNormalVector(&mVelocity, 10); + mVelocity *= stream->readInt(13) / 32.0f; + } + else + { + mVelocity.set(0.0f, 0.0f, 0.0f); + } + + rot.y = rot.x = 0.0f; + rot.z = stream->readFloat(7) * M_2PI_F; + mHead.x = stream->readSignedFloat(6) * mDataBlock->maxLookAngle; + mHead.z = stream->readSignedFloat(6) * mDataBlock->maxLookAngle; + delta.move.unpack(stream); + + delta.head = mHead; + delta.headVec.set(0.0f, 0.0f, 0.0f); + + if (stream->readFlag() && isProperlyAdded()) + { + // Determine number of ticks to warp based on the average + // of the client and server velocities. + delta.warpOffset = pos - delta.pos; + F32 as = (speed + mVelocity.len()) * 0.5f * TickSec; + F32 dt = (as > 0.00001f) ? delta.warpOffset.len() / as: sMaxWarpTicks; + delta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f); + + if (delta.warpTicks) + { + // Setup the warp to start on the next tick. + if (delta.warpTicks > sMaxWarpTicks) + delta.warpTicks = sMaxWarpTicks; + delta.warpOffset /= (F32)delta.warpTicks; + + delta.rotOffset = rot - delta.rot; + if(delta.rotOffset.z < - M_PI_F) + delta.rotOffset.z += M_2PI_F; + else if(delta.rotOffset.z > M_PI_F) + delta.rotOffset.z -= M_2PI_F; + delta.rotOffset /= (F32)delta.warpTicks; + } + else + { + // Going to skip the warp, server and client are real close. + // Adjust the frame interpolation to move smoothly to the + // new position within the current tick. + Point3F cp = delta.pos + delta.posVec * delta.dt; + if (delta.dt == 0) + { + delta.posVec.set(0.0f, 0.0f, 0.0f); + delta.rotVec.set(0.0f, 0.0f, 0.0f); + } + else + { + F32 dti = 1.0f / delta.dt; + delta.posVec = (cp - pos) * dti; + delta.rotVec.z = mRot.z - rot.z; + + if(delta.rotVec.z > M_PI_F) + delta.rotVec.z -= M_2PI_F; + else if(delta.rotVec.z < -M_PI_F) + delta.rotVec.z += M_2PI_F; + + delta.rotVec.z *= dti; + } + delta.pos = pos; + delta.rot = rot; + setPosition(pos,rot); + } + } + else + { + // Set the player to the server position + delta.pos = pos; + delta.rot = rot; + delta.posVec.set(0.0f, 0.0f, 0.0f); + delta.rotVec.set(0.0f, 0.0f, 0.0f); + delta.warpTicks = 0; + delta.dt = 0.0f; + setPosition(pos,rot); + } + } + F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy; + setEnergyLevel(energy); +} + + +//---------------------------------------------------------------------------- +ConsoleMethod( Player, getState, const char*, 2, 2, "Return the current state name.") +{ + return object->getStateName(); +} + +ConsoleMethod( Player, getDamageLocation, const char*, 3, 3, "(Point3F pos)") +{ + const char *buffer1; + const char *buffer2; + char *buff = Con::getReturnBuffer(128); + + Point3F pos(0.0f, 0.0f, 0.0f); + dSscanf(argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z); + object->getDamageLocation(pos, buffer1, buffer2); + + dSprintf(buff, 128, "%s %s", buffer1, buffer2); + return buff; +} + +ConsoleMethod( Player, setArmThread, bool, 3, 3, "(string sequenceName)") +{ + return object->setArmThread(argv[2]); +} + +ConsoleMethod( Player, setActionThread, bool, 3, 5, "(string sequenceName, bool hold, bool fsp)") +{ + bool hold = (argc > 3)? dAtob(argv[3]): false; + bool fsp = (argc > 4)? dAtob(argv[4]): true; + return object->setActionThread(argv[2],hold,true,fsp); +} + +ConsoleMethod( Player, setControlObject, bool, 3, 3, "(ShapeBase obj)") +{ + ShapeBase* controlObject; + if (Sim::findObject(argv[2],controlObject)) { + object->setControlObject(controlObject); + return true; + } + else + object->setControlObject(0); + return false; +} + +ConsoleMethod( Player, getControlObject, S32, 2, 2, "Get the current control object.") +{ + ShapeBase* controlObject = object->getControlObject(); + return controlObject ? controlObject->getId(): 0; +} + +ConsoleMethod( Player, clearControlObject, void, 2, 2, "") +{ + object->setControlObject(0); +} + +ConsoleMethod( Player, checkDismountPoint, bool, 4, 4, "(Point3F oldPos, Point3F pos)") +{ + Point3F oldPos(0.0f, 0.0f, 0.0f); + Point3F pos(0.0f, 0.0f, 0.0f); + dSscanf(argv[2], "%g %g %g", + &oldPos.x, + &oldPos.y, + &oldPos.z); + dSscanf(argv[3], "%g %g %g", + &pos.x, + &pos.y, + &pos.z); + MatrixF oldPosMat(true); + oldPosMat.setColumn(3, oldPos); + MatrixF posMat(true); + posMat.setColumn(3, pos); + return object->checkDismountPosition(oldPosMat, posMat); +} + +//---------------------------------------------------------------------------- +void Player::consoleInit() +{ + Con::addVariable("pref::Player::renderMyPlayer",TypeBool, &sRenderMyPlayer); + Con::addVariable("pref::Player::renderMyItems",TypeBool, &sRenderMyItems); + + Con::addVariable("Player::minWarpTicks",TypeF32,&sMinWarpTicks); + Con::addVariable("Player::maxWarpTicks",TypeS32,&sMaxWarpTicks); + Con::addVariable("Player::maxPredictionTicks",TypeS32,&sMaxPredictionTicks); + Con::addVariable("Player::renderCollision", TypeBool, &sRenderPlayerCollision); +} + +//-------------------------------------------------------------------------- +void Player::calcClassRenderData() +{ + Parent::calcClassRenderData(); + + disableCollision(); + MatrixF nmat; + MatrixF smat; + Parent::getRetractionTransform(0,&nmat); + Parent::getImageTransform(0, &smat); + + // See if we are pushed into a wall... + Point3F start, end; + smat.getColumn(3, &start); + nmat.getColumn(3, &end); + + RayInfo rinfo; + if (getContainer()->castRay(start, end, 0xFFFFFFFF & ~(WaterObjectType|DefaultObjectType), &rinfo)) { + if (rinfo.t < 1.0f) + mWeaponBackFraction = 1.0f - rinfo.t; + else + mWeaponBackFraction = 0.0f; + } else { + mWeaponBackFraction = 0.0f; + } + enableCollision(); +} + + +void Player::playFootstepSound(bool triggeredLeft, S32 sound) +{ + // [rene, 03/11/08 - more or less obsolete; footstep sounds should be + // defined as material property now] + + MatrixF footMat = getTransform(); + + if ( mWaterCoverage == 0.0f ) + { + switch ( sound ) + { + case 0: // Soft + SFX->playOnce( mDataBlock->sound[PlayerData::FootSoft], &footMat ); + break; + case 1: // Hard + SFX->playOnce( mDataBlock->sound[PlayerData::FootHard], &footMat ); + break; + case 2: // Metal + SFX->playOnce( mDataBlock->sound[PlayerData::FootMetal], &footMat ); + break; + case 3: // Snow + SFX->playOnce( mDataBlock->sound[PlayerData::FootSnow], &footMat ); + break; + default: //Hard + SFX->playOnce( mDataBlock->sound[PlayerData::FootHard], &footMat ); + break; + } + } + else + { + if ( mWaterCoverage < mDataBlock->footSplashHeight ) + SFX->playOnce( mDataBlock->sound[PlayerData::FootShallowSplash], &footMat ); + else + { + if ( mWaterCoverage < 1.0 ) + SFX->playOnce( mDataBlock->sound[PlayerData::FootWading], &footMat ); + else + { + if ( triggeredLeft ) + { + SFX->playOnce( mDataBlock->sound[PlayerData::FootUnderWater], &footMat ); + SFX->playOnce( mDataBlock->sound[PlayerData::FootBubbles], &footMat ); + } + } + } + } +} + +void Player:: playImpactSound() +{ + if( mWaterCoverage == 0.0f ) + { + Point3F pos; + RayInfo rInfo; + MatrixF mat = getTransform(); + mat.mulP(Point3F(mDataBlock->decalOffset,0.0f,0.0f), &pos); + + if( gClientContainer.castRay( Point3F( pos.x, pos.y, pos.z + 0.01f ), + Point3F( pos.x, pos.y, pos.z - 2.0f ), + STATIC_COLLISION_MASK | VehicleObjectType, + &rInfo ) ) + { + Material* material = ( rInfo.material ? dynamic_cast< Material* >( rInfo.material->getMaterial() ) : 0 ); + + if( material && material->mImpactSoundCustom ) + SFX->playOnce( material->mImpactSoundCustom, &getTransform() ); + else + { + S32 sound = -1; + if( material && material->mImpactSoundId ) + sound = material->mImpactSoundId; + else if( rInfo.object->getTypeMask() & VehicleObjectType ) + sound = 2; // Play metal; + + switch( sound ) + { + case 0: + //Soft + SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactSoft ], &getTransform() ); + break; + case 1: + //Hard + SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactHard ], &getTransform() ); + break; + case 2: + //Metal + SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactMetal ], &getTransform() ); + break; + case 3: + //Snow + SFX->playOnce( mDataBlock->sound[ PlayerData::ImpactSnow ], &getTransform() ); + break; + /* + default: + //Hard + alxPlay(mDataBlock->sound[PlayerData::ImpactHard], &getTransform()); + break; + */ + } + } + } + } + + mImpactSound = 0; +} + +//-------------------------------------------------------------------------- +// Update splash +//-------------------------------------------------------------------------- + +void Player::updateSplash() +{ + F32 speed = getVelocity().len(); + if( speed < mDataBlock->splashVelocity || isMounted() ) return; + + Point3F curPos = getPosition(); + + if ( curPos.equal( mLastPos ) ) + return; + + if (pointInWater( curPos )) { + if (!pointInWater( mLastPos )) { + Point3F norm = getVelocity(); + norm.normalize(); + + // make sure player is moving vertically at good pace before playing splash + F32 splashAng = mDataBlock->splashAngle / 360.0; + if( mDot( norm, Point3F(0.0, 0.0, -1.0) ) < splashAng ) + return; + + + RayInfo rInfo; + if (gClientContainer.castRay(mLastPos, curPos, + WaterObjectType, &rInfo)) { + createSplash( rInfo.point, speed ); + mBubbleEmitterTime = 0.0; + } + + } + } +} + + +//-------------------------------------------------------------------------- + +void Player::updateFroth( F32 dt ) +{ + // update bubbles + Point3F moveDir = getVelocity(); + mBubbleEmitterTime += dt; + + if (mBubbleEmitterTime < mDataBlock->bubbleEmitTime) { + if (mSplashEmitter[PlayerData::BUBBLE_EMITTER]) { + Point3F emissionPoint = getRenderPosition(); + U32 emitNum = PlayerData::BUBBLE_EMITTER; + mSplashEmitter[emitNum]->emitParticles(mLastPos, emissionPoint, + Point3F( 0.0, 0.0, 1.0 ), moveDir, (U32)(dt * 1000.0)); + } + } + + Point3F contactPoint; + if (!collidingWithWater(contactPoint)) { + mLastWaterPos = mLastPos; + return; + } + + F32 speed = moveDir.len(); + if ( speed < mDataBlock->splashVelEpsilon ) + speed = 0.0; + + U32 emitRate = (U32) (speed * mDataBlock->splashFreqMod * dt); + + // If we're in the water, swimming, but not + // moving, then lets emit some particles because + // we're treading water. + if ( mSwimming && speed == 0.0 ) + { + emitRate = (U32) (2.0f * mDataBlock->splashFreqMod * dt); + } + + U32 i; + for ( i=0; iemitParticles( mLastWaterPos, + contactPoint, Point3F( 0.0, 0.0, 1.0 ), + moveDir, emitRate ); + } + mLastWaterPos = contactPoint; +} + +void Player::updateWaterSounds(F32 dt) +{ + if ( mWaterCoverage < 1.0f || mDamageState != Enabled ) + { + // Stop everything + if ( mMoveBubbleSound ) + mMoveBubbleSound->stop(); + if ( mWaterBreathSound ) + mWaterBreathSound->stop(); + return; + } + + if ( mMoveBubbleSound ) + { + // We're under water and still alive, so let's play something + if ( mVelocity.len() > 1.0f ) + { + if ( !mMoveBubbleSound->isPlaying() ) + mMoveBubbleSound->play(); + + mMoveBubbleSound->setTransform( getTransform() ); + } + else + mMoveBubbleSound->stop(); + } + + if ( mWaterBreathSound ) + { + if ( !mWaterBreathSound->isPlaying() ) + mWaterBreathSound->play(); + + mWaterBreathSound->setTransform( getTransform() ); + } +} + + +//-------------------------------------------------------------------------- +// Returns true if player is intersecting a water surface +//-------------------------------------------------------------------------- +bool Player::collidingWithWater( Point3F &waterHeight ) +{ + if ( !mCurrentWaterObject ) + return false; + + Point3F curPos = getPosition(); + + if ( mWorldBox.maxExtents.z < mLiquidHeight ) + return false; + + curPos.z = mLiquidHeight; + + waterHeight = getPosition(); + waterHeight.z = mLiquidHeight; + + return true; +} + +//-------------------------------------------------------------------------- + +void Player::createSplash( Point3F &pos, F32 speed ) +{ + if ( speed >= mDataBlock->hardSplashSoundVel ) + SFX->playOnce( mDataBlock->sound[PlayerData::ImpactWaterHard], &getTransform() ); + else if ( speed >= mDataBlock->medSplashSoundVel ) + SFX->playOnce( mDataBlock->sound[PlayerData::ImpactWaterMedium], &getTransform() ); + else + SFX->playOnce( mDataBlock->sound[PlayerData::ImpactWaterEasy], &getTransform() ); + + if( mDataBlock->splash ) + { + MatrixF trans = getTransform(); + trans.setPosition( pos ); + Splash *splash = new Splash; + splash->onNewDataBlock( mDataBlock->splash ); + splash->setTransform( trans ); + splash->setInitialState( trans.getPosition(), Point3F( 0.0, 0.0, 1.0 ) ); + if (!splash->registerObject()) + delete splash; + } +} + + +bool Player::isControlObject() +{ + GameConnection* connection = GameConnection::getConnectionToServer(); + if( !connection ) return false; + ShapeBase *obj = dynamic_cast(connection->getControlObject()); + return ( obj == this ); +} + + +bool Player::prepRenderImage( SceneState* state, const U32 stateKey, + const U32 startZone, const bool modifyBaseState ) +{ + bool renderPlayer = true; + bool renderItems = true; + + /* + if ( mPhysicsPlayer && Con::getBoolVariable("$PhysicsPlayer::DebugRender",false) ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( mPhysicsPlayer, &PhysicsPlayer::renderDebug ); + ri->objectIndex = -1; + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + */ + + // Debug rendering for all convexes in the Players working list. + if ( sRenderPlayerCollision ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Player::renderConvex ); + ri->objectIndex = -1; + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + + GameConnection* connection = GameConnection::getConnectionToServer(); + if( connection && connection->getControlObject() == this && connection->isFirstPerson() ) + { + renderPlayer = mDataBlock->renderFirstPerson || !state->isDiffusePass(); + + if( !sRenderMyPlayer ) + renderPlayer = false; + if( !sRenderMyItems ) + renderItems = false; + } + + // Call the protected base class to do the work + // now that we know if we're rendering the player + // and mounted shapes. + return ShapeBase::_prepRenderImage( state, + stateKey, + startZone, + modifyBaseState, + renderPlayer, + renderItems ); +} + +void Player::renderConvex( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + GFX->enterDebugEvent( ColorI(255,0,255), "Player_renderConvex" ); + mConvex.renderWorkingList(); + GFX->leaveDebugEvent(); +} + diff --git a/T3D/player.h b/T3D/player.h new file mode 100644 index 0000000..2747a2c --- /dev/null +++ b/T3D/player.h @@ -0,0 +1,631 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLAYER_H_ +#define _PLAYER_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +#include "T3D/gameProcess.h" + +class ParticleEmitter; +class ParticleEmitterData; +class DecalData; +class SplashData; +class PhysicsPlayer; + +//---------------------------------------------------------------------------- + +struct PlayerData: public ShapeBaseData { + typedef ShapeBaseData Parent; + enum Constants { + RecoverDelayBits = 7, + JumpDelayBits = 7, + NumSpineNodes = 6, + ImpactBits = 3, + NUM_SPLASH_EMITTERS = 3, + BUBBLE_EMITTER = 2, + }; + bool renderFirstPerson; ///< Render the player shape in first person + + F32 pickupRadius; ///< Radius around player for items (on server) + F32 maxTimeScale; ///< Max timeScale for action animations + + F32 minLookAngle; ///< Lowest angle (radians) the player can look + F32 maxLookAngle; ///< Highest angle (radians) the player can look + F32 maxFreelookAngle; ///< Max left/right angle the player can look + + /// @name Physics constants + /// @{ + + F32 runForce; ///< Force used to accelerate player + F32 runEnergyDrain; ///< Energy drain/tick + F32 minRunEnergy; ///< Minimum energy required to run + F32 maxForwardSpeed; ///< Maximum forward speed when running + F32 maxBackwardSpeed; ///< Maximum backward speed when running + F32 maxSideSpeed; ///< Maximum side speed when running + + F32 maxStepHeight; ///< Maximum height the player can step up + F32 runSurfaceAngle; ///< Maximum angle from vertical in degrees the player can run up + + F32 horizMaxSpeed; ///< Max speed attainable in the horizontal + F32 horizResistSpeed; ///< Speed at which resistance will take place + F32 horizResistFactor; ///< Factor of resistance once horizResistSpeed has been reached + + F32 upMaxSpeed; ///< Max vertical speed attainable + F32 upResistSpeed; ///< Speed at which resistance will take place + F32 upResistFactor; ///< Factor of resistance once upResistSpeed has been reached + + S32 recoverDelay; ///< # tick + F32 recoverRunForceScale; ///< RunForce multiplier in recover state + + // Jumping + F32 jumpForce; ///< Force exerted per jump + F32 jumpEnergyDrain; ///< Energy drained per jump + F32 minJumpEnergy; ///< Minimum energy required to jump + F32 minJumpSpeed; ///< Minimum speed needed to jump + F32 maxJumpSpeed; ///< Maximum speed before the player can no longer jump + F32 jumpSurfaceAngle; ///< Angle from vertical in degrees where the player can jump + S32 jumpDelay; ///< Delay time in ticks between jumps + + // Swimming + F32 swimForce; ///< Force used to accelerate player while swimming + F32 maxUnderwaterForwardSpeed; ///< Maximum underwater forward speed when running + F32 maxUnderwaterBackwardSpeed; ///< Maximum underwater backward speed when running + F32 maxUnderwaterSideSpeed; ///< Maximum underwater side speed when running + + // Crouching + F32 crouchForce; ///< Force used to accelerate player while crouching + F32 maxCrouchForwardSpeed; ///< Maximum forward speed when crouching + F32 maxCrouchBackwardSpeed; ///< Maximum backward speed when crouching + F32 maxCrouchSideSpeed; ///< Maximum side speed when crouching + + // Prone + F32 proneForce; ///< Force used to accelerate player while prone + F32 maxProneForwardSpeed; ///< Maximum forward speed when prone + F32 maxProneBackwardSpeed; ///< Maximum backward speed when prone + F32 maxProneSideSpeed; ///< Maximum side speed when prone + + // Jetting + F32 jetJumpForce; + F32 jetJumpEnergyDrain; ///< Energy per jump + F32 jetMinJumpEnergy; + F32 jetMinJumpSpeed; + F32 jetMaxJumpSpeed; + F32 jetJumpSurfaceAngle; ///< Angle vertical degrees + /// @} + + /// @name Hitboxes + /// @{ + + F32 boxHeadPercentage; + F32 boxTorsoPercentage; + + S32 boxHeadLeftPercentage; + S32 boxHeadRightPercentage; + S32 boxHeadBackPercentage; + S32 boxHeadFrontPercentage; + /// @} + + F32 minImpactSpeed; ///< Minimum impact speed required to apply fall damage + + F32 decalOffset; + + F32 groundImpactMinSpeed; ///< Minimum impact speed required to apply fall damage with the ground + VectorF groundImpactShakeFreq; ///< Frequency in each direction for the camera to shake + VectorF groundImpactShakeAmp; ///< How much to shake + F32 groundImpactShakeDuration; ///< How long to shake + F32 groundImpactShakeFalloff; ///< How fast the shake disapates + + /// Zounds! + enum Sounds { + FootSoft, + FootHard, + FootMetal, + FootSnow, + FootShallowSplash, + FootWading, + FootUnderWater, + FootBubbles, + MoveBubbles, + WaterBreath, + ImpactSoft, + ImpactHard, + ImpactMetal, + ImpactSnow, + ImpactWaterEasy, + ImpactWaterMedium, + ImpactWaterHard, + ExitWater, + MaxSounds + }; + SFXProfile* sound[MaxSounds]; + + Point3F boxSize; ///< Width, depth, height + Point3F crouchBoxSize; + Point3F proneBoxSize; + Point3F swimBoxSize; + + /// Animation and other data initialized in onAdd + struct ActionAnimationDef { + const char* name; ///< Sequence name + struct Vector { + F32 x,y,z; + } dir; ///< Default direction + }; + struct ActionAnimation { + const char* name; ///< Sequence name + S32 sequence; ///< Sequence index + VectorF dir; ///< Dir of animation ground transform + F32 speed; ///< Speed in m/s + bool velocityScale; ///< Scale animation by velocity + bool death; ///< Are we dying? + }; + enum { + // *** WARNING *** + // These enum values are used to index the ActionAnimationList + // array instantiated in player.cc + // The first several are selected in the move state based on velocity + RootAnim, + RunForwardAnim, + BackBackwardAnim, + SideLeftAnim, + + CrouchRootAnim, + CrouchForwardAnim, + ProneRootAnim, + ProneForwardAnim, + SwimRootAnim, + SwimForwardAnim, + SwimBackwardAnim, + SwimLeftAnim, + SwimRightAnim, + + // These are set explicitly based on player actions + FallAnim, + JumpAnim, + StandJumpAnim, + LandAnim, + JetAnim, + + // + NumMoveActionAnims = SideLeftAnim + 1, + NumTableActionAnims = JetAnim + 1, + + NumExtraActionAnims = 512 - NumTableActionAnims, + NumActionAnims = NumTableActionAnims + NumExtraActionAnims, + ActionAnimBits = 9, + NullAnimation = (1 << ActionAnimBits) - 1 + }; + + static ActionAnimationDef ActionAnimationList[NumTableActionAnims]; + ActionAnimation actionList[NumActionAnims]; + U32 actionCount; + U32 lookAction; + S32 spineNode[NumSpineNodes]; + S32 pickupDelta; ///< Base off of pcikupRadius + F32 runSurfaceCos; ///< Angle from vertical in cos(runSurfaceAngle) + F32 jumpSurfaceCos; ///< Angle from vertical in cos(jumpSurfaceAngle) + + enum Impacts { + ImpactNone, + ImpactNormal, + }; + + enum Recoil { + LightRecoil, + MediumRecoil, + HeavyRecoil, + NumRecoilSequences + }; + S32 recoilSequence[NumRecoilSequences]; + + /// @name Particles + /// All of the data relating to environmental effects + /// @{ + + ParticleEmitterData * footPuffEmitter; + S32 footPuffID; + S32 footPuffNumParts; + F32 footPuffRadius; + + DecalData* decalData; + S32 decalID; + + ParticleEmitterData * dustEmitter; + S32 dustID; + + SplashData* splash; + S32 splashId; + F32 splashVelocity; + F32 splashAngle; + F32 splashFreqMod; + F32 splashVelEpsilon; + F32 bubbleEmitTime; + + F32 medSplashSoundVel; + F32 hardSplashSoundVel; + F32 exitSplashSoundVel; + F32 footSplashHeight; + + // Air control + F32 airControl; + + // Jump off surfaces at their normal rather than straight up + bool jumpTowardsNormal; + + // For use if/when mPhysicsPlayer is created + StringTableEntry physicsPlayerType; + + ParticleEmitterData* splashEmitterList[NUM_SPLASH_EMITTERS]; + S32 splashEmitterIDList[NUM_SPLASH_EMITTERS]; + /// @} + + // + DECLARE_CONOBJECT(PlayerData); + PlayerData(); + bool preload(bool server, String &errorStr); + void getGroundInfo(TSShapeInstance*,TSThread*,ActionAnimation*); + bool isTableSequence(S32 seq); + bool isJumpAction(U32 action); + + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class Player: public ShapeBase +{ + typedef ShapeBase Parent; + +protected: + + /// Bit masks for different types of events + enum MaskBits { + ActionMask = Parent::NextFreeMask << 0, + MoveMask = Parent::NextFreeMask << 1, + ImpactMask = Parent::NextFreeMask << 2, + NextFreeMask = Parent::NextFreeMask << 3 + }; + + struct Range { + Range(F32 _min,F32 _max) { + min = _min; + max = _max; + delta = _max - _min; + }; + F32 min,max; + F32 delta; + }; + + SimObjectPtr mSplashEmitter[PlayerData::NUM_SPLASH_EMITTERS]; + F32 mBubbleEmitterTime; + + /// Client interpolation/warp data + struct StateDelta { + Move move; ///< Last move from server + F32 dt; ///< Last interpolation time + /// @name Interpolation data + /// @{ + + Point3F pos; + Point3F rot; + Point3F head; + VectorF posVec; + VectorF rotVec; + VectorF headVec; + /// @} + + /// @name Warp data + /// @{ + + S32 warpTicks; + Point3F warpOffset; + Point3F rotOffset; + /// @} + }; + StateDelta delta; ///< Used for interpolation on the client. @see StateDelta + S32 mPredictionCount; ///< Number of ticks to predict + + // Current pos, vel etc. + Point3F mHead; ///< Head rotation, uses only x & z + Point3F mRot; ///< Body rotation, uses only z + VectorF mVelocity; ///< Velocity + Point3F mAnchorPoint; ///< Pos compression anchor + static F32 mGravity; ///< Gravity + S32 mImpactSound; + + S32 mMountPending; ///< mMountPending suppresses tickDelay countdown so players will sit until + ///< their mount, or another animation, comes through (or 13 seconds elapses). + + /// Main player state + enum ActionState { + NullState, + MoveState, + RecoverState, + NumStateBits = 3 + }; + ActionState mState; ///< What is the player doing? @see ActionState + bool mFalling; ///< Falling in mid-air? + S32 mJumpDelay; ///< Delay till next jump + + enum Pose { + StandPose = 0, + CrouchPose, + PronePose, + SwimPose, + NumPoseBits = 3 + }; + Pose mPose; + + S32 mContactTimer; ///< Ticks since last contact + + Point3F mJumpSurfaceNormal; ///< Normal of the surface the player last jumped on + U32 mJumpSurfaceLastContact; ///< How long it's been since the player landed (ticks) + F32 mWeaponBackFraction; ///< Amount to slide the weapon back (if it's up against something) + + SFXSource* mMoveBubbleSound; ///< Sound for moving bubbles + SFXSource* mWaterBreathSound; ///< Sound for underwater breath + + SimObjectPtr mControlObject; ///< Controlling object + + /// @name Animation threads & data + /// @{ + + struct ActionAnimation { + U32 action; + TSThread* thread; + S32 delayTicks; // before picking another. + bool forward; + bool firstPerson; + bool waitForEnd; + bool holdAtEnd; + bool animateOnServer; + bool atEnd; + } mActionAnimation; + + struct ArmAnimation { + U32 action; + TSThread* thread; + } mArmAnimation; + TSThread* mArmThread; + + TSThread* mHeadVThread; + TSThread* mHeadHThread; + TSThread* mRecoilThread; + static Range mArmRange; + static Range mHeadVRange; + static Range mHeadHRange; + /// @} + + bool mInMissionArea; ///< Are we in the mission area? + // + S32 mRecoverTicks; ///< same as recoverTicks in the player datablock + U32 mReversePending; + + bool mInWater; ///< Is true if WaterCoverage is greater than zero + bool mSwimming; ///< Is true if WaterCoverage is above the swimming threshold + // + PlayerData* mDataBlock; ///< MMmmmmm...datablock... + + Point3F mLastPos; ///< Holds the last position for physics updates + Point3F mLastWaterPos; ///< Same as mLastPos, but for water + + struct ContactInfo + { + bool contacted, jump, run; + SceneObject *contactObject; + VectorF contactNormal; + + void clear() + { + contacted=jump=run=false; + contactObject = NULL; + contactNormal.set(1,1,1); + } + + ContactInfo() { clear(); } + + } mContactInfo; + + struct Death { + F32 lastPos; + Point3F posAdd; + VectorF rotate; + VectorF curNormal; + F32 curSink; + void clear() {dMemset(this, 0, sizeof(*this)); initFall();} + VectorF getPosAdd() {VectorF ret(posAdd); posAdd.set(0,0,0); return ret;} + bool haveVelocity() {return posAdd.x != 0 || posAdd.y != 0;} + void initFall() {curNormal.set(0,0,1); curSink = 0;} + Death() {clear();} + MatrixF* fallToGround(F32 adjust, const Point3F& pos, F32 zrot, F32 boxRad); + } mDeath; + + PhysicsPlayer *mPhysicsPlayer; + + + public: + + // New collision + OrthoBoxConvex mConvex; + Box3F mWorkingQueryBox; + + /// Standing / Crouched / Prone or Swimming + Pose getPose() const { return mPose; } + + /// Setting this from script directly might not actually work, + /// This is really just a helper for the player class so that its bounding box + /// will get resized appropriately when the pose changes + void setPose( Pose pose ); + + PhysicsPlayer* getPhysicsPlayer() const { return mPhysicsPlayer; } + + protected: + void setState(ActionState state, U32 ticks=0); + void updateState(); + + // Jetting + bool mJetting; + + ///Update the movement + virtual void updateMove(const Move *move); + + ///Interpolate movement + Point3F _move( const F32 travelTime, Collision *outCol ); + void _handleCollision( const Collision &collision ); + virtual bool updatePos(const F32 travelTime = TickSec); + + ///Update head animation + void updateLookAnimation(F32 dT = 0.f); + + ///Update other animations + void updateAnimation(F32 dt); + void updateAnimationTree(bool firstPerson); + bool step(Point3F *pos,F32 *maxStep,F32 time); + + ///See if the player is still in the mission area + void checkMissionArea(); + + virtual bool setArmThread(U32 action); + virtual void setActionThread(U32 action,bool forward,bool hold = false,bool wait = false,bool fsp = false, bool forceSet = false); + virtual void updateActionThread(); + virtual void pickActionAnimation(); + + /// @name Mounted objects + /// @{ + virtual void onUnmount( ShapeBase *obj, S32 node ); + /// @} + + void setPosition(const Point3F& pos,const Point3F& viewRot); + void setRenderPosition(const Point3F& pos,const Point3F& viewRot,F32 dt=-1); + void _findContact( SceneObject **contactObject, VectorF *contactNormal ); + void findContact( bool *run, bool *jump, VectorF *contactNormal ); + virtual void onImageRecoil(U32 imageSlot,ShapeBaseImageData::StateData::RecoilState); + virtual void updateDamageLevel(); + virtual void updateDamageState(); + /// Set which client is controlling this player + void setControllingClient(GameConnection* client); + + void calcClassRenderData(); + /// Play a footstep sound + void playFootstepSound(bool triggeredLeft, S32 sound); + /// Play an impact sound + void playImpactSound(); + + /// Are we in the process of dying? + bool inDeathAnim(); + F32 deathDelta(Point3F &delta); + void updateDeathOffsets(); + bool inSittingAnim(); + + /// @name Water + /// @{ + + void updateSplash(); ///< Update the splash effect + void updateFroth( F32 dt ); ///< Update any froth + void updateWaterSounds( F32 dt ); ///< Update water sounds + void createSplash( Point3F &pos, F32 speed ); ///< Creates a splash + bool collidingWithWater( Point3F &waterHeight ); ///< Are we colliding with water? + /// @} + +public: + DECLARE_CONOBJECT(Player); + + Player(); + ~Player(); + static void consoleInit(); + + /// @name Transforms + /// @{ + + void setTransform(const MatrixF &mat); + void getEyeTransform(MatrixF* mat); + void getRenderEyeTransform(MatrixF* mat); + void getCameraParameters(F32 *min, F32 *max, Point3F *offset, MatrixF *rot); + void getMuzzleTransform(U32 imageSlot,MatrixF* mat); + void getRenderMuzzleTransform(U32 imageSlot,MatrixF* mat); + + /// @} + + F32 getSpeed() const; + Point3F getVelocity() const; + void setVelocity(const VectorF& vel); + /// Apply an impulse at the given point, with magnitude/direction of vec + void applyImpulse(const Point3F& pos,const VectorF& vec); + /// Get the rotation of the player + const Point3F& getRotation() { return mRot; } + /// Get the rotation of the head of the player + const Point3F& getHeadRotation() { return mHead; } + void getDamageLocation(const Point3F& in_rPos, const char *&out_rpVert, const char *&out_rpQuad); + + bool canJump(); ///< Can the player jump? + bool canJetJump(); ///< Can the player jet? + bool canSwim(); ///< Can the player swim? + bool canCrouch(); + bool canStand(); + bool canProne(); + bool haveContact() const { return !mContactTimer; } ///< Is it in contact with something + void getMuzzlePointAI( U32 imageSlot, Point3F *point ); + F32 getMaxForwardVelocity() const { return (mDataBlock != NULL ? mDataBlock->maxForwardSpeed : 0); } + + virtual bool isDisplacable() const; + virtual Point3F getMomentum() const; + virtual void setMomentum(const Point3F &momentum); + virtual bool displaceObject(const Point3F& displaceVector); + virtual bool getAIMove(Move*); + + bool checkDismountPosition(const MatrixF& oldPos, const MatrixF& newPos); ///< Is it safe to dismount here? + + // + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData* dptr); + void onScaleChanged(); + Box3F mScaledBox; + + // Animation + const char* getStateName(); + bool setActionThread(const char* sequence,bool hold,bool wait,bool fsp = false); + bool setArmThread(const char* sequence); + + // Object control + void setControlObject(ShapeBase *obj); + ShapeBase* getControlObject(); + + virtual void disableCollision(); + virtual void enableCollision(); + + // + void updateWorkingCollisionSet(); + virtual void processTick(const Move *move); + void interpolateTick(F32 delta); + void advanceTime(F32 dt); + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + void buildConvex(const Box3F& box, Convex* convex); + bool isControlObject(); + + void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *); + void writePacketData(GameConnection *conn, BitStream *stream); + void readPacketData (GameConnection *conn, BitStream *stream); + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + virtual bool prepRenderImage(SceneState* state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + virtual void renderConvex( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + virtual void renderMountedImage( U32 imageSlot, TSRenderState &rstate ); +}; + + +#endif diff --git a/T3D/pointLight.cpp b/T3D/pointLight.cpp new file mode 100644 index 0000000..534cfe4 --- /dev/null +++ b/T3D/pointLight.cpp @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/pointLight.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CO_NETOBJECT_V1( PointLight ); + +PointLight::PointLight() + : mRadius( 5.0f ) +{ + // We set the type here to ensure the extended + // parameter validation works when setting fields. + mLight->setType( LightInfo::Point ); +} + +PointLight::~PointLight() +{ +} + +void PointLight::initPersistFields() +{ + addGroup( "Light" ); + + addField( "radius", TypeF32, Offset( mRadius, PointLight ) ); + + endGroup( "Light" ); + + // We do the parent fields at the end so that + // they show up that way in the inspector. + Parent::initPersistFields(); + + // Remove the scale field... it's already + // defined by the light radius. + removeField( "scale" ); +} + +void PointLight::_conformLights() +{ + mLight->setTransform( getTransform() ); + + mLight->setRange( mRadius ); + + mLight->setColor( mColor ); + + mLight->setBrightness( mBrightness ); + mLight->setCastShadows( mCastShadows ); + mLight->setPriority( mPriority ); + + // Update the bounds and scale to fit our light. + mObjBox.minExtents.set( -1, -1, -1 ); + mObjBox.maxExtents.set( 1, 1, 1 ); + mObjScale.set( mRadius, mRadius, mRadius ); + + // Skip our transform... it just dirties mask bits. + Parent::setTransform( mObjToWorld ); +} + +U32 PointLight::packUpdate(NetConnection *conn, U32 mask, BitStream *stream ) +{ + if ( stream->writeFlag( mask & UpdateMask ) ) + stream->write( mRadius ); + + return Parent::packUpdate( conn, mask, stream ); +} + +void PointLight::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + if ( stream->readFlag() ) // UpdateMask + stream->read( &mRadius ); + + Parent::unpackUpdate( conn, stream ); +} + +void PointLight::setScale( const VectorF &scale ) +{ + // Use the average of the three coords. + mRadius = ( scale.x + scale.y + scale.z ) / 3.0f; + + // We changed our settings so notify the client. + setMaskBits( UpdateMask ); + + // Let the parent do the final scale. + Parent::setScale( VectorF( mRadius, mRadius, mRadius ) ); +} + +void PointLight::_renderViz( SceneState *state ) +{ + GFXDrawUtil *draw = GFX->getDrawUtil(); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setCullMode( GFXCullNone ); + desc.setBlend( true ); + + // Base the sphere color on the light color. + ColorI color( mColor ); + color.alpha = 16; + + draw->drawSphere( desc, mRadius, getPosition(), color ); +} diff --git a/T3D/pointLight.h b/T3D/pointLight.h new file mode 100644 index 0000000..9c6ebcf --- /dev/null +++ b/T3D/pointLight.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POINTLIGHT_H_ +#define _POINTLIGHT_H_ + +#ifndef _LIGHTBASE_H_ +#include "T3D/lightBase.h" +#endif + + +class PointLight : public LightBase +{ + typedef LightBase Parent; + +protected: + + F32 mRadius; + + // LightBase + void _conformLights(); + void _renderViz( SceneState *state ); + +public: + + PointLight(); + virtual ~PointLight(); + + // ConsoleObject + DECLARE_CONOBJECT( PointLight ); + static void initPersistFields(); + + // SceneObject + virtual void setScale( const VectorF &scale ); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); +}; + +#endif // _POINTLIGHT_H_ diff --git a/T3D/portal.cpp b/T3D/portal.cpp new file mode 100644 index 0000000..3266492 --- /dev/null +++ b/T3D/portal.cpp @@ -0,0 +1,416 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#include "platform/platform.h" +#include "T3D/portal.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxTransformSaver.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/gfxDrawUtil.h" +#include "math/mathUtils.h" +#include "gfx/gfxTransformSaver.h" +#include "sceneGraph/sceneRoot.h" + +IMPLEMENT_CO_NETOBJECT_V1( Portal ); + +bool Portal::smRenderPortals = false; + +Portal::Portal() +{ + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask |= StaticObjectType; + + mObjScale.set( 1.0f, 0.25f, 1.0f ); +} + +Portal::~Portal() +{ +} + +void Portal::initPersistFields() +{ + Parent::initPersistFields(); +} + +void Portal::consoleInit() +{ + Con::addVariable( "$Portal::renderPortals", TypeBool, &smRenderPortals ); +} + +bool Portal::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + generateOBBPoints(); + + return true; +} + +void Portal::onRemove() +{ + _clearZones(); + Parent::onRemove(); +} + +void Portal::setTransform(const MatrixF & mat) +{ + Parent::setTransform( mat ); + generateOBBPoints(); + + setMaskBits( TransformMask ); +} + +U32 Portal::packUpdate( NetConnection *con, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( con, mask, stream ); + + if ( stream->writeFlag( mask & TransformMask ) ) + { + mathWrite( *stream, mObjToWorld ); + mathWrite( *stream, mObjScale ); + } + + return retMask; +} + +void Portal::unpackUpdate( NetConnection *con, BitStream *stream ) +{ + Parent::unpackUpdate( con, stream ); + + if ( stream->readFlag() ) // TransformMask + { + mathRead(*stream, &mObjToWorld); + mathRead(*stream, &mObjScale); + + if ( isProperlyAdded() ) + setTransform( mObjToWorld ); + } +} + +U32 Portal::getPointZone( const Point3F &p ) +{ + PlaneF portalPlane( getPosition(), mObjToWorld.getForwardVector() ); + + // If the point is contained, + // do a test to see which side of the + // Portal's plane it's on, and return the + // appropriate Zone's mZoneRangeStart value. + Point3F objPoint( 0, 0, 0 ); + getWorldTransform().mulP( p, &objPoint ); + objPoint.convolveInverse( getScale() ); + + if ( mObjBox.isContained( objPoint ) ) + { + S32 pointSide = portalPlane.whichSide( p ); + S32 zoneOneSide = mZones[0] ? portalPlane.whichSide( mZones[0]->getPosition() ) : -2; + S32 zoneTwoSide = mZones[1] ? portalPlane.whichSide( mZones[1]->getPosition() ) : -2; + + U32 frontZoneIdx = zoneOneSide == PlaneF::Front ? 0 : 1; + + if ( pointSide == zoneOneSide && zoneOneSide != -2 ) + return mZones[0]->getZoneRangeStart(); + else if ( pointSide == zoneTwoSide && zoneTwoSide != -2 ) + return mZones[1]->getZoneRangeStart(); + else if ( mZones[frontZoneIdx] ) + return mZones[frontZoneIdx]->getZoneRangeStart(); + } + + return 0; +} + +bool Portal::getOverlappingZones( SceneObject *obj, + U32 *zones, + U32 *numZones ) +{ + // If this portal is connected + // to nothing, don't treat it + // like a Zone. + if ( !mZones[0] && !mZones[1] ) + { + *numZones = 0; + return true; + } + + return Parent::getOverlappingZones( obj, zones, numZones ); +} + +bool Portal::scopeObject( const Point3F &rootPosition, + const F32 rootDistance, + bool *zoneScopeState ) +{ + // The sky zone is always in scope! + //*zoneScopeState = true; + return false; +} + +bool Portal::prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + if ( isLastState(state, stateKey ) ) + return false; + + setLastState( state, stateKey ); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Portal::renderObject ); + ri->type = RenderPassManager::RIT_Object; + ri->defaultKey = 0; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void Portal::renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat ) +{ + if ( overrideMat ) + return; + + // Only render if the portal render + // flag is enabled, or this object + // is currently selected in the editor. + if ( !smRenderPortals && !isSelected() ) + return; + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + desc.setCullMode( GFXCullNone ); + + { + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + + // Modify the scale to have + // a very thin y-extent. + Point3F scale = getScale(); + scale.y = 0.0f; + mat.scale( scale ); + + GFX->multWorld( mat ); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + if ( !mZones[0] || !mZones[1] ) + drawer->drawCube( desc, mObjBox, ColorI( 0, 0, 255, 45 ) ); + else + drawer->drawCube( desc, mObjBox, ColorI( 0, 255, 0, 45 ) ); + } + + { + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + mat.scale( getScale() ); + + GFX->multWorld( mat ); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + if ( !mZones[0] || !mZones[1] ) + drawer->drawCube( desc, mObjBox, ColorI( 255, 255, 255, 45 ) ); + else + drawer->drawCube( desc, mObjBox, ColorI( 255, 255, 255, 45 ) ); + } +} + +void Portal::_clearZones() +{ + if ( mZones[0] ) + mZones[0]->_removePortal( this ); + if ( mZones[1] ) + mZones[1]->_removePortal( this ); + + SceneRoot *root = isServerObject() ? gServerSceneRoot : gClientSceneRoot; + root->_removePortal( this ); + + mZones[0] = mZones[1] = 0; +} + +void Portal::onRezone() +{ + _clearZones(); + + SceneObjectRef* walk = mZoneRefHead; + U32 zoneNum = 0; + for ( ; walk != NULL; walk = walk->nextInObj ) + { + // Skip over the outside zone. + if ( walk->zone == 0 ) + continue; + + Zone *zone = dynamic_cast( mSceneManager->getZoneOwner( walk->zone ) ); + if ( !zone ) + continue; + + mZones[zoneNum++] = zone; + if ( zoneNum == 2 ) + break; + } + + if ( mZones[0] ) + mZones[0]->_addPortal( this ); + if ( mZones[1] ) + mZones[1]->_addPortal( this ); + + // If we have *one* NULL zone, + // we are linked to the outside zone. + // Need to add ourselves to the SceneRoot. + if ( (!mZones[0] || !mZones[1]) && !( !mZones[0] && !mZones[1] ) ) + { + SceneRoot *root = isServerObject() ? gServerSceneRoot : gClientSceneRoot; + root->_addPortal( this ); + } +} + +void Portal::generateOBBPoints() +{ + Point3F boxHalfExtents = mObjScale * 0.5f; + + Point3F center; + mObjToWorld.getColumn( 3, ¢er ); + + VectorF right, fwd, up; + mObjToWorld.getColumn( 0, &right ); + mObjToWorld.getColumn( 1, &fwd ); + mObjToWorld.getColumn( 2, &up ); + + // Near bottom right. + mOBBPoints[0] = center - (fwd * boxHalfExtents.y) - ( right * boxHalfExtents.x ) - ( up * boxHalfExtents.z ); + // Near top right. + mOBBPoints[1] = center - (fwd * boxHalfExtents.y) + ( right * boxHalfExtents.x ) + ( up * boxHalfExtents.z ); + // Near top left. + mOBBPoints[2] = center - (fwd * boxHalfExtents.y) - ( right * boxHalfExtents.x ) + ( up * boxHalfExtents.z ); + // Near bottom left. + mOBBPoints[3] = center - (fwd * boxHalfExtents.y) - ( right * boxHalfExtents.x ) - ( up * boxHalfExtents.z ); + + // Far bottom right. + mOBBPoints[4] = center + (fwd * boxHalfExtents.y) + ( right * boxHalfExtents.x ) - ( up * boxHalfExtents.z ); + // Far top right. + mOBBPoints[5] = center + (fwd * boxHalfExtents.y) + ( right * boxHalfExtents.x ) + ( up * boxHalfExtents.z ); + // Far top left. + mOBBPoints[6] = center + (fwd * boxHalfExtents.y) - ( right * boxHalfExtents.x ) + ( up * boxHalfExtents.z ); + // Far bottom left. + mOBBPoints[7] = center + (fwd * boxHalfExtents.y) - ( right * boxHalfExtents.x ) - ( up * boxHalfExtents.z ); + + // Bottom right. + mOrientedPortalPoints[0] = center + ( right * boxHalfExtents.x ) - ( up * boxHalfExtents.z ); + // Bottom left. + mOrientedPortalPoints[1] = center - ( right * boxHalfExtents.x ) - ( up * boxHalfExtents.z ); + // Top right. + mOrientedPortalPoints[2] = center + ( right * boxHalfExtents.x ) + ( up * boxHalfExtents.z ); + // Top left. + mOrientedPortalPoints[3] = center - ( right * boxHalfExtents.x ) + ( up * boxHalfExtents.z ); +} + +void Portal::generatePortalFrustum( SceneState *state, Frustum *outFrustum ) +{ + // None of this data changes. + const Frustum &frust = state->getFrustum(); + const RectI &viewport = GFX->getViewport(); + const MatrixF &worldMat = GFX->getWorldMatrix(); + const MatrixF &projMat = GFX->getProjectionMatrix(); + + // Temp variables. + Point2F vpExtent( (F32)viewport.extent.x, (F32)viewport.extent.y ); + Point3F boxPointsSS[4]; + + Point3F *portalPoints = &mOrientedPortalPoints[0]; + + U32 count = 0; + // Project the points to screen space. + for ( U32 i = 0; i < 4; i++ ) + { + bool projected = MathUtils::mProjectWorldToScreen( portalPoints[i], &boxPointsSS[i], viewport, worldMat, projMat ); + if ( projected ) + continue; + + count++; + + // Point3F intersectionPt( 0, 0, 0 ); + // const Point3F *points = frust.getPoints(); + // for ( U32 j = 0; j < 4; j++ ) + // { + // bool intersects = frust.edgeFaceIntersect( points[Frustum::smEdgeIndices[j][0]], points[Frustum::smEdgeIndices[j][1]], + // mOrientedPortalPoints[0], + // mOrientedPortalPoints[2], + // mOrientedPortalPoints[3], + // mOrientedPortalPoints[1], &intersectionPt ); + + // const VectorF &fwd = frust.getTransform().getForwardVector(); + + // if ( intersects ) + // MathUtils::mProjectWorldToScreen( intersectionPt + fwd, &boxPointsSS[i], viewport, worldMat, projMat ); + // } + } + + // If more than 3 points failed to project + // and the camera is fairly close to the portal's + // plane, go ahead and use the full frustum. + if ( count > 3 && (state->getCameraPosition() - getPosition()).lenSquared() < 4.0f ) + { + outFrustum->set( frust ); + return; + } + + // Clamp results to the viewport. + for ( U32 i = 0; i < 4; i++ ) + { + if ( boxPointsSS[i].z > 1.0f ) + { + boxPointsSS[i].x = -boxPointsSS[i].x; + boxPointsSS[i].y = -boxPointsSS[i].y; + } + boxPointsSS[i].x = mClampF( boxPointsSS[i].x, (F32)viewport.point.x, (F32)viewport.point.x + viewport.extent.x ); + boxPointsSS[i].y = mClampF( boxPointsSS[i].y, (F32)viewport.point.y, (F32)viewport.point.y + viewport.extent.y ); + } + + // Get the min x of all points. + F32 minX = getMin( getMin( boxPointsSS[0].x, boxPointsSS[1].x ), getMin( boxPointsSS[2].x, boxPointsSS[3].x ) ); + + // Get the max x of all points. + F32 maxX = getMax( getMax( boxPointsSS[0].x, boxPointsSS[1].x ), getMax( boxPointsSS[2].x, boxPointsSS[3].x ) ); + + // Get the min y of all points. + F32 minY = getMin( getMin( boxPointsSS[0].y, boxPointsSS[1].y ), getMin( boxPointsSS[2].y, boxPointsSS[3].y ) ); + + // Get the max y of all points. + F32 maxY = getMax( getMax( boxPointsSS[0].y, boxPointsSS[1].y ), getMax( boxPointsSS[2].y, boxPointsSS[3].y ) ); + + boxPointsSS[0].set( minX, minY, boxPointsSS[0].z ); + boxPointsSS[1].set( maxX, maxY, boxPointsSS[1].z ); + + // Get the extent of the current frustum. + F32 frustXExtent = mFabs( frust.getNearLeft() - frust.getNearRight() ); + F32 frustYExtent = mFabs( frust.getNearTop() - frust.getNearBottom() ); + + // Normalize pixel cordinates to 0 to 1, + // then convert into the range of negative half frustum extents. + boxPointsSS[0].x = ( ( boxPointsSS[0].x / vpExtent.x ) * frustXExtent ) - (frustXExtent / 2.0f); + boxPointsSS[0].y = ( ( boxPointsSS[0].y / vpExtent.y ) * frustYExtent ) - (frustYExtent / 2.0f); + boxPointsSS[1].x = ( ( boxPointsSS[1].x / vpExtent.x ) * frustXExtent ) - (frustXExtent / 2.0f); + boxPointsSS[1].y = ( ( boxPointsSS[1].y / vpExtent.y ) * frustYExtent ) - (frustYExtent / 2.0f); + + // Find the real top, left, right, and bottom. + F32 realRight = getMax( boxPointsSS[0].x, boxPointsSS[1].x ); + F32 realLeft = getMin( boxPointsSS[0].x, boxPointsSS[1].x ); + F32 realTop = getMax( boxPointsSS[0].y, boxPointsSS[1].y ); + F32 realBottom = getMin( boxPointsSS[0].y, boxPointsSS[1].y ); + + outFrustum->set( false, realLeft, realRight, -realTop, -realBottom, frust.getNearDist(), frust.getFarDist(), frust.getTransform() ); +}; \ No newline at end of file diff --git a/T3D/portal.h b/T3D/portal.h new file mode 100644 index 0000000..101f04a --- /dev/null +++ b/T3D/portal.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#ifndef _PORTAL_H_ +#define _PORTAL_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +#ifndef _ZONE_H_ +#include "T3D/zone.h" +#endif + +/// +class Portal : public Zone +{ + typedef Zone Parent; + +protected: + + static bool smRenderPortals; + + enum + { + TransformMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1, + }; + + U32 mPortalKey; + + Point3F mOBBPoints[8]; + Point3F mOrientedPortalPoints[4]; + + SimObjectPtr mZones[2]; + + // SceneObject + void onRezone(); + + // + void _clearZones(); + +public: + + Portal(); + virtual ~Portal(); + + // SimObject + DECLARE_CONOBJECT( Portal ); + + static void consoleInit(); + + static void initPersistFields(); + bool onAdd(); + void onRemove(); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + U32 getPointZone( const Point3F &p ); + bool getOverlappingZones( SceneObject *obj, + U32 *zones, + U32 *numZones ); + bool scopeObject( const Point3F &rootPosition, + const F32 rootDistance, + bool *zoneScopeState ); + void setTransform( const MatrixF &mat ); + bool prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + + /// Renders the one and only sky zone if it + /// exists on the client. + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat ); + + Zone* getZone( U32 zoneNum ) { return mZones[zoneNum]; } + void generatePortalFrustum( SceneState *state, Frustum *outFrustum ); + + void getBoxCorners( Point3F points[2] ) const; + void generateOBBPoints(); + Point3F* getOBBPoints() { return &mOBBPoints[0]; } + Point3F* getOrientedPortalPoints() { return &mOrientedPortalPoints[0]; } + + U32 getPortalKey() const { return mPortalKey; } + void setPortalKey( U32 key ) { mPortalKey = key; } +}; + +#endif // _PORTAL_H_ + diff --git a/T3D/projectile.cpp b/T3D/projectile.cpp new file mode 100644 index 0000000..0594323 --- /dev/null +++ b/T3D/projectile.cpp @@ -0,0 +1,1278 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" +#include "console/consoleTypes.h" +#include "console/typeValidators.h" +#include "core/resourceManager.h" +#include "core/stream/bitStream.h" +#include "T3D/fx/explosion.h" +#include "T3D/shapeBase.h" +#include "ts/tsShapeInstance.h" +#include "T3D/projectile.h" +#include "sfx/sfxSystem.h" +#include "math/mathUtils.h" +#include "math/mathIO.h" +#include "sim/netConnection.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/fx/splash.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsWorld.h" +#include "gfx/gfxTransformSaver.h" +#include "T3D/containerQuery.h" +#include "T3D/decal/decalManager.h" +#include "T3D/decal/decalData.h" +#include "T3D/lightDescription.h" + +IMPLEMENT_CO_DATABLOCK_V1(ProjectileData); +IMPLEMENT_CO_NETOBJECT_V1(Projectile); + +const U32 Projectile::csmStaticCollisionMask = TerrainObjectType | + InteriorObjectType | + StaticObjectType; + +const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType | + VehicleObjectType | + DamagableItemObjectType; + +const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask; + +U32 Projectile::smProjectileWarpTicks = 5; + + +//-------------------------------------------------------------------------- +// +ProjectileData::ProjectileData() +{ + projectileShapeName = NULL; + + sound = NULL; + soundId = 0; + + explosion = NULL; + explosionId = 0; + + waterExplosion = NULL; + waterExplosionId = 0; + + //hasLight = false; + //lightRadius = 1; + //lightColor.set(1, 1, 1); + lightDesc = NULL; + + faceViewer = false; + scale.set( 1.0f, 1.0f, 1.0f ); + + isBallistic = false; + + velInheritFactor = 1.0f; + muzzleVelocity = 50; + impactForce = 0.0f; + + armingDelay = 0; + fadeDelay = 20000 / 32; + lifetime = 20000 / 32; + + activateSeq = -1; + maintainSeq = -1; + + gravityMod = 1.0; + bounceElasticity = 0.999f; + bounceFriction = 0.3f; + + particleEmitter = NULL; + particleEmitterId = 0; + + particleWaterEmitter = NULL; + particleWaterEmitterId = 0; + + splash = NULL; + splashId = 0; + + decal = NULL; + decalId = 0; + + lightDesc = NULL; + lightDescId = 0; +} + +//-------------------------------------------------------------------------- +IMPLEMENT_CONSOLETYPE(ProjectileData) +IMPLEMENT_GETDATATYPE(ProjectileData) +IMPLEMENT_SETDATATYPE(ProjectileData) + +void ProjectileData::initPersistFields() +{ + addNamedField(particleEmitter, TypeParticleEmitterDataPtr, ProjectileData); + addNamedField(particleWaterEmitter, TypeParticleEmitterDataPtr, ProjectileData); + + addNamedField(projectileShapeName, TypeFilename, ProjectileData); + addNamedField(scale, TypePoint3F, ProjectileData); + + addNamedField(sound, TypeSFXProfilePtr, ProjectileData); + + addNamedField(explosion, TypeExplosionDataPtr, ProjectileData); + addNamedField(waterExplosion, TypeExplosionDataPtr, ProjectileData); + + addNamedField(splash, TypeSplashDataPtr, ProjectileData); + + addNamedField(decal, TypeDecalDataPtr, ProjectileData); + + addNamedField(lightDesc, TypeLightDescriptionPtr, ProjectileData ); + + static FRangeValidator lightRadiusValidator( 1, 20 ); + static FRangeValidator velInheritFactorValidator( 0, 1 ); + static FRangeValidator muzzleVelocityValidator( 0, 10000 ); + + addNamedField(isBallistic, TypeBool, ProjectileData); + addNamedFieldV(velInheritFactor, TypeF32, ProjectileData, &velInheritFactorValidator ); + addNamedFieldV(muzzleVelocity, TypeF32, ProjectileData, &muzzleVelocityValidator ); + addNamedField(impactForce, TypeF32, ProjectileData); + + static IRangeValidatorScaled lifetimeValidator( TickMs, 0, Projectile::MaxLivingTicks ); + static IRangeValidatorScaled armingDelayValidator( TickMs, 0, Projectile::MaxLivingTicks ); + static IRangeValidatorScaled fadeDelayValidator( TickMs, 0, Projectile::MaxLivingTicks ); + + char message[1024]; + dSprintf(message, sizeof(message), "Milliseconds, values will be adjusted to fit %d millisecond tick intervals", TickMs); + addProtectedField("lifetime", TypeS32, Offset(lifetime, ProjectileData), &setLifetime, &getScaledValue, message); + addProtectedField("armingDelay", TypeS32, Offset(armingDelay, ProjectileData), &setArmingDelay, &getScaledValue, message); + addProtectedField("fadeDelay", TypeS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue, message); + + static FRangeValidator bounceElasticityValidator( 0, 0.999f ); + static FRangeValidator bounceFrictionValidator( 0, 1 ); + static FRangeValidator gravityModValidator( 0, 1 ); + + addNamedFieldV(bounceElasticity, TypeF32, ProjectileData, &bounceElasticityValidator ); + addNamedFieldV(bounceFriction, TypeF32, ProjectileData, &bounceFrictionValidator ); + addNamedFieldV(gravityMod, TypeF32, ProjectileData, &gravityModValidator ); + + Parent::initPersistFields(); +} + + +//-------------------------------------------------------------------------- +bool ProjectileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (!particleEmitter && particleEmitterId != 0) + if (Sim::findObject(particleEmitterId, particleEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(particleEmitter): %d", particleEmitterId); + + if (!particleWaterEmitter && particleWaterEmitterId != 0) + if (Sim::findObject(particleWaterEmitterId, particleWaterEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId); + + if (!explosion && explosionId != 0) + if (Sim::findObject(explosionId, explosion) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(explosion): %d", explosionId); + + if (!waterExplosion && waterExplosionId != 0) + if (Sim::findObject(waterExplosionId, waterExplosion) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(waterExplosion): %d", waterExplosionId); + + if (!splash && splashId != 0) + if (Sim::findObject(splashId, splash) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(splash): %d", splashId); + + if (!decal && decalId != 0) + if (Sim::findObject(decalId, decal) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(decal): %d", decalId); + + if (!sound && soundId != 0) + if (Sim::findObject(soundId, sound) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockid(sound): %d", soundId); + + if (!lightDesc && lightDescId != 0) + if (Sim::findObject(lightDescId, lightDesc) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockid(lightDesc): %d", lightDescId); + + return true; +} + + +bool ProjectileData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + if (projectileShapeName && projectileShapeName[0] != '\0') + { + projectileShape = ResourceManager::get().load(projectileShapeName); + if (bool(projectileShape) == false) + { + errorStr = String::ToString("ProjectileData::load: Couldn't load shape \"%s\"", projectileShapeName); + return false; + } + activateSeq = projectileShape->findSequence("activate"); + maintainSeq = projectileShape->findSequence("maintain"); + } + + if (bool(projectileShape)) // create an instance to preload shape data + { + TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server); + delete pDummy; + } + + return true; +} + +//-------------------------------------------------------------------------- +void ProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(projectileShapeName); + stream->writeFlag(faceViewer); + if(stream->writeFlag(scale.x != 1 || scale.y != 1 || scale.z != 1)) + { + stream->write(scale.x); + stream->write(scale.y); + stream->write(scale.z); + } + + if (stream->writeFlag(particleEmitter != NULL)) + stream->writeRangedU32(particleEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(particleWaterEmitter != NULL)) + stream->writeRangedU32(particleWaterEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(explosion != NULL)) + stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(waterExplosion != NULL)) + stream->writeRangedU32(waterExplosion->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(splash != NULL)) + stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(decal != NULL)) + stream->writeRangedU32(decal->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(sound != NULL)) + stream->writeRangedU32(sound->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if ( stream->writeFlag(lightDesc != NULL)) + stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + stream->write(impactForce); + +// stream->writeRangedU32(lifetime, 0, Projectile::MaxLivingTicks); +// stream->writeRangedU32(armingDelay, 0, Projectile::MaxLivingTicks); +// stream->writeRangedU32(fadeDelay, 0, Projectile::MaxLivingTicks); + + // [tom, 3/21/2007] Changing these to write all 32 bits as the previous + // code limited these to a max value of 4095. + stream->write(lifetime); + stream->write(armingDelay); + stream->write(fadeDelay); + + if(stream->writeFlag(isBallistic)) + { + stream->write(gravityMod); + stream->write(bounceElasticity); + stream->write(bounceFriction); + } + +} + +void ProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + projectileShapeName = stream->readSTString(); + + faceViewer = stream->readFlag(); + if(stream->readFlag()) + { + stream->read(&scale.x); + stream->read(&scale.y); + stream->read(&scale.z); + } + else + scale.set(1,1,1); + + if (stream->readFlag()) + particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + particleWaterEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + decalId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + soundId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + // [tom, 3/21/2007] See comment in packData() +// lifetime = stream->readRangedU32(0, Projectile::MaxLivingTicks); +// armingDelay = stream->readRangedU32(0, Projectile::MaxLivingTicks); +// fadeDelay = stream->readRangedU32(0, Projectile::MaxLivingTicks); + + stream->read(&impactForce); + + stream->read(&lifetime); + stream->read(&armingDelay); + stream->read(&fadeDelay); + + isBallistic = stream->readFlag(); + if(isBallistic) + { + stream->read(&gravityMod); + stream->read(&bounceElasticity); + stream->read(&bounceFriction); + } +} + +bool ProjectileData::setLifetime( void *obj, const char *data ) +{ + S32 value = dAtoi(data); + value = scaleValue(value); + + ProjectileData *object = static_cast(obj); + object->lifetime = value; + + return false; +} + +bool ProjectileData::setArmingDelay( void *obj, const char *data ) +{ + S32 value = dAtoi(data); + value = scaleValue(value); + + ProjectileData *object = static_cast(obj); + object->armingDelay = value; + + return false; +} + +bool ProjectileData::setFadeDelay( void *obj, const char *data ) +{ + S32 value = dAtoi(data); + value = scaleValue(value); + + ProjectileData *object = static_cast(obj); + object->fadeDelay = value; + + return false; +} + +const char *ProjectileData::getScaledValue( void *obj, const char *data) +{ + + S32 value = dAtoi(data); + value = scaleValue(value, false); + + String stringData = String::ToString(value); + char *strBuffer = Con::getReturnBuffer(stringData.size()); + dMemcpy( strBuffer, stringData, stringData.size() ); + + return strBuffer; +} + +S32 ProjectileData::scaleValue( S32 value, bool down ) +{ + S32 minV = 0; + S32 maxV = Projectile::MaxLivingTicks; + + // scale down to ticks before we validate + if( down ) + value /= TickMs; + + if(value < minV || value > maxV) + { + Con::errorf("ProjectileData::scaleValue(S32 value = %d, bool down = %b) - Scaled value must be between %d and %d", value, down, minV, maxV); + if(value < minV) + value = minV; + else if(value > maxV) + value = maxV; + } + + // scale up from ticks after we validate + if( !down ) + value *= TickMs; + + return value; +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +Projectile::Projectile() + : mPhysicsWorld( NULL ), + mCurrPosition( 0, 0, 0 ), + mCurrVelocity( 0, 0, 1 ), + mSourceObjectId( -1 ), + mSourceObjectSlot( -1 ), + mCurrTick( 0 ), + mParticleEmitter( NULL ), + mParticleWaterEmitter( NULL ), + mSound( NULL ), + mProjectileShape( NULL ), + mActivateThread( NULL ), + mMaintainThread( NULL ), + mHidden( false ), + mFadeValue( 1.0f ) +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + mTypeMask |= ProjectileObjectType | LightObjectType; + + mLight = LightManager::createLightInfo(); + mLight->setType( LightInfo::Point ); + + mLightState.clear(); + mLightState.setLightInfo( mLight ); +} + +Projectile::~Projectile() +{ + SAFE_DELETE(mLight); + + delete mProjectileShape; + mProjectileShape = NULL; +} + +//-------------------------------------------------------------------------- +void Projectile::initPersistFields() +{ + addGroup("Physics"); + addField("initialPosition", TypePoint3F, Offset(mCurrPosition, Projectile)); + addField("initialVelocity", TypePoint3F, Offset(mCurrVelocity, Projectile)); + endGroup("Physics"); + + addGroup("Source"); + addField("sourceObject", TypeS32, Offset(mSourceObjectId, Projectile)); + addField("sourceSlot", TypeS32, Offset(mSourceObjectSlot, Projectile)); + endGroup("Source"); + + Parent::initPersistFields(); +} + +bool Projectile::calculateImpact(float, + Point3F& pointOfImpact, + float& impactTime) +{ + Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: Should never be called"); + + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; +} + + +//-------------------------------------------------------------------------- +F32 Projectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) +{ + F32 ret = Parent::getUpdatePriority(camInfo, updateMask, updateSkips); + // if the camera "owns" this object, it should have a slightly higher priority + if(mSourceObject == camInfo->camera) + return ret + 0.2; + return ret; +} + +bool Projectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) + { + ShapeBase* ptr; + if (Sim::findObject(mSourceObjectId, ptr)) + mSourceObject = ptr; + else + { + if (mSourceObjectId != -1) + Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid"); + mSourceObject = NULL; + } + + // If we're on the server, we need to inherit some of our parent's velocity + // + mCurrTick = 0; + } + else + { + if (bool(mDataBlock->projectileShape)) + { + mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, isClientObject()); + + if (mDataBlock->activateSeq != -1) + { + mActivateThread = mProjectileShape->addThread(); + mProjectileShape->setTimeScale(mActivateThread, 1); + mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0); + } + } + if (mDataBlock->particleEmitter != NULL) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mDataBlock->particleEmitter); + if (pEmitter->registerObject() == false) + { + Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mParticleEmitter = pEmitter; + } + + if (mDataBlock->particleWaterEmitter != NULL) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mDataBlock->particleWaterEmitter); + if (pEmitter->registerObject() == false) + { + Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mParticleWaterEmitter = pEmitter; + } + } + if (mSourceObject.isValid()) + processAfter(mSourceObject); + + // Setup our bounding box + if (bool(mDataBlock->projectileShape) == true) + mObjBox = mDataBlock->projectileShape->bounds; + else + mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); + resetWorldBox(); + addToScene(); + + if ( gPhysicsPlugin ) + mPhysicsWorld = gPhysicsPlugin->getWorld( isServerObject() ? "Server" : "Client" ); + + return true; +} + + +void Projectile::onRemove() +{ + if( !mParticleEmitter.isNull() ) + { + mParticleEmitter->deleteWhenEmpty(); + mParticleEmitter = NULL; + } + + if( !mParticleWaterEmitter.isNull() ) + { + mParticleWaterEmitter->deleteWhenEmpty(); + mParticleWaterEmitter = NULL; + } + + SFX_DELETE( mSound ); + + removeFromScene(); + Parent::onRemove(); +} + + +bool Projectile::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + if ( isGhost() ) + { + // Create the sound ahead of time. This reduces runtime + // costs and makes the system easier to understand. + + SFX_DELETE( mSound ); + + if ( mDataBlock->sound ) + mSound = SFX->createSource( mDataBlock->sound ); + } + + return true; +} + +void Projectile::submitLights( LightManager *lm, bool staticLighting ) +{ + if ( staticLighting || mHidden || !mDataBlock->lightDesc ) + return; + + mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this ); +} + +bool Projectile::pointInWater(const Point3F &point) +{ + // This is pretty much a hack so we can use the existing ContainerQueryInfo + // and findObject router. + + // We only care if we intersect with water at all + // so build a box at the point that has only 1 z extent. + // And test if water coverage is anything other than zero. + + Box3F boundsBox( point, point ); + boundsBox.maxExtents.z += 1.0f; + + ContainerQueryInfo info; + info.box = boundsBox; + info.mass = 0.0f; + + // Find and retreive physics info from intersecting WaterObject(s) + if(mContainer != NULL) + { + mContainer->findObjects( boundsBox, WaterObjectType, findRouter, &info ); + } + else + { + // Handle special case where the projectile has exploded prior to having + // called onAdd() on the client. This occurs when the projectile on the + // server is created and then explodes in the same network update tick. + // On the client end in NetConnection::ghostReadPacket() the ghost is + // created and then Projectile::unpackUpdate() is called prior to the + // projectile being registered. Within unpackUpdate() the explosion + // is triggered, but without being registered onAdd() isn't called and + // the container is not set. As all we're doing is checking if the + // given explosion point is within water, we should be able to use the + // global container here. We could likely always get away with this, + // but using the actual defined container when possible is the right + // thing to do. DAW + AssertFatal(isClientObject(), "Server projectile has not been properly added"); + gClientContainer.findObjects( boundsBox, WaterObjectType, findRouter, &info ); + } + + return ( info.waterCoverage > 0.0f ); +} + +//---------------------------------------------------------------------------- + +void Projectile::emitParticles(const Point3F& from, const Point3F& to, const Point3F& vel, const U32 ms) +{ + if( mHidden ) + return; + + Point3F axis = -vel; + + if( axis.isZero() ) + axis.set( 0.0, 0.0, 1.0 ); + else + axis.normalize(); + + bool fromWater = pointInWater(from); + bool toWater = pointInWater(to); + + if (!fromWater && !toWater && bool(mParticleEmitter)) // not in water + mParticleEmitter->emitParticles(from, to, axis, vel, ms); + else if (fromWater && toWater && bool(mParticleWaterEmitter)) // in water + mParticleWaterEmitter->emitParticles(from, to, axis, vel, ms); + else if (!fromWater && toWater && mDataBlock->splash) // entering water + { + // cast the ray to get the surface point of the water + RayInfo rInfo; + if (gClientContainer.castRay(from, to, WaterObjectType, &rInfo)) + { + MatrixF trans = getTransform(); + trans.setPosition(rInfo.point); + + Splash *splash = new Splash(); + splash->onNewDataBlock(mDataBlock->splash); + splash->setTransform(trans); + splash->setInitialState(trans.getPosition(), Point3F(0.0, 0.0, 1.0)); + if (!splash->registerObject()) + { + delete splash; + splash = NULL; + } + + // create an emitter for the particles out of water and the particles in water + if (mParticleEmitter) + mParticleEmitter->emitParticles(from, rInfo.point, axis, vel, ms); + + if (mParticleWaterEmitter) + mParticleWaterEmitter->emitParticles(rInfo.point, to, axis, vel, ms); + } + } + else if (fromWater && !toWater && mDataBlock->splash) // leaving water + { + // cast the ray in the opposite direction since that point is out of the water, otherwise + // we hit water immediately and wont get the appropriate surface point + RayInfo rInfo; + if (gClientContainer.castRay(to, from, WaterObjectType, &rInfo)) + { + MatrixF trans = getTransform(); + trans.setPosition(rInfo.point); + + Splash *splash = new Splash(); + splash->onNewDataBlock(mDataBlock->splash); + splash->setTransform(trans); + splash->setInitialState(trans.getPosition(), Point3F(0.0, 0.0, 1.0)); + if (!splash->registerObject()) + { + delete splash; + splash = NULL; + } + + // create an emitter for the particles out of water and the particles in water + if (mParticleEmitter) + mParticleEmitter->emitParticles(rInfo.point, to, axis, vel, ms); + + if (mParticleWaterEmitter) + mParticleWaterEmitter->emitParticles(from, rInfo.point, axis, vel, ms); + } + } +} + + +//---------------------------------------------------------------------------- + +class ObjectDeleteEvent : public SimEvent +{ +public: + void process(SimObject *object) + { + object->deleteObject(); + } +}; + +void Projectile::explode(const Point3F& p, const Point3F& n, const U32 collideType) +{ + // Make sure we don't explode twice... + if (mHidden == true) + return; + + mHidden = true; + if (isServerObject()) { + // Do what the server needs to do, damage the surrounding objects, etc. + mExplosionPosition = p; + mExplosionNormal = n; + mCollideHitType = collideType; + + char buffer[128]; + dSprintf(buffer, sizeof(buffer), "%g %g %g", mExplosionPosition.x, + mExplosionPosition.y, + mExplosionPosition.z); + Con::executef(mDataBlock, "onExplode", scriptThis(), buffer, Con::getFloatArg(mFadeValue)); + + setMaskBits(ExplosionMask); + safeDeleteObject(); + } + else + { + // Client just plays the explosion at the right place... + // + Explosion* pExplosion = NULL; + + if (mDataBlock->waterExplosion && pointInWater(p)) + { + pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->waterExplosion); + } + else + if (mDataBlock->explosion) + { + pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->explosion); + } + + if( pExplosion ) + { + MatrixF xform(true); + xform.setPosition(p); + pExplosion->setTransform(xform); + pExplosion->setInitialState(p, n); + pExplosion->setCollideType( collideType ); + if (pExplosion->registerObject() == false) + { + Con::errorf(ConsoleLogEntry::General, "Projectile(%s)::explode: couldn't register explosion", + mDataBlock->getName() ); + delete pExplosion; + pExplosion = NULL; + } + } + + // Client (impact) decal. + if ( mDataBlock->decal ) + gDecalManager->addDecal( p, n, 0.0f, mDataBlock->decal ); + + // Client object + updateSound(); + } + + /* + // Client and Server both should apply forces to PhysicsWorld objects + // within the explosion. + if ( false && mPhysicsWorld ) + { + F32 force = 200.0f; + mPhysicsWorld->explosion( p, 15.0f, force ); + } + */ +} + +void Projectile::updateSound() +{ + if (!mDataBlock->sound) + return; + + if ( mHidden && mSound ) + mSound->stop(); + + else if ( !mHidden && mSound ) + { + if ( !mSound->isPlaying() ) + mSound->play(); + + mSound->setVelocity( getVelocity() ); + mSound->setTransform( getRenderTransform() ); + } +} + +Point3F Projectile::getVelocity() const +{ + return mCurrVelocity; +} + +void Projectile::processTick(const Move* move) +{ + Parent::processTick(move); + + mCurrTick++; + if(mSourceObject && mCurrTick > SourceIdTimeoutTicks) + { + mSourceObject = 0; + mSourceObjectId = 0; + } + + // See if we can get out of here the easy way ... + if (isServerObject() && mCurrTick >= mDataBlock->lifetime) + { + deleteObject(); + return; + } + else if (mHidden == true) + return; + + // ... otherwise, we have to do some simulation work. + RayInfo rInfo; + Point3F oldPosition; + Point3F newPosition; + + oldPosition = mCurrPosition; + if(mDataBlock->isBallistic) + mCurrVelocity.z -= 9.81 * mDataBlock->gravityMod * (F32(TickMs) / 1000.0f); + + newPosition = oldPosition + mCurrVelocity * (F32(TickMs) / 1000.0f); + + // disable the source objects collision reponse while we determine + // if the projectile is capable of moving from the old position + // to the new position, otherwise we'll hit ourself + if (mSourceObject.isValid()) + mSourceObject->disableCollision(); + disableCollision(); + + // Determine if the projectile is going to hit any object between the previous + // position and the new position. This code is executed both on the server + // and on the client (for prediction purposes). It is possible that the server + // will have registered a collision while the client prediction has not. If this + // happens the client will be corrected in the next packet update. + + // Raycast the abstract PhysicsWorld if a PhysicsPlugin exists. + bool hit = false; + + if ( gPhysicsPlugin ) + { + // TODO: hacked for single player... fix me! + PhysicsWorld *world = gPhysicsPlugin->getWorld( "Server"/*isServerObject() ? "Server" : "Client"*/ ); + if ( world ) + hit = world->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce ); + + } + else + hit = getContainer()->castRay(oldPosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo); + + if ( hit ) + { + // make sure the client knows to bounce + if(isServerObject() && (rInfo.object->getType() & csmStaticCollisionMask) == 0) + setMaskBits(BounceMask); + + // Next order of business: do we explode on this hit? + if(mCurrTick > mDataBlock->armingDelay) + { + MatrixF xform(true); + xform.setColumn(3, rInfo.point); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + + // Get the object type before the onCollision call, in case + // the object is destroyed. + U32 objectType = rInfo.object->getType(); + + // re-enable the collision response on the source object since + // we need to process the onCollision and explode calls + if(mSourceObject) + mSourceObject->enableCollision(); + + // Ok, here is how this works: + // onCollision is called to notify the server scripts that a collision has occurred, then + // a call to explode is made to start the explosion process. The call to explode is made + // twice, once on the server and once on the client. + // The server process is responsible for two things: + // 1) setting the ExplosionMask network bit to guarantee that the client calls explode + // 2) initiate the explosion process on the server scripts + // The client process is responsible for only one thing: + // 1) drawing the appropriate explosion + + // It is possible that during the processTick the server may have decided that a hit + // has occurred while the client prediction has decided that a hit has not occurred. + // In this particular scenario the client will have failed to call onCollision and + // explode during the processTick. However, the explode function will be called + // during the next packet update, due to the ExplosionMask network bit being set. + // onCollision will remain uncalled on the client however, therefore no client + // specific code should be placed inside the function! + onCollision(rInfo.point, rInfo.normal, rInfo.object); + explode(rInfo.point, rInfo.normal, objectType ); + + // break out of the collision check, since we've exploded + // we don't want to mess with the position and velocity + } + else + { + if(mDataBlock->isBallistic) + { + // Otherwise, this represents a bounce. First, reflect our velocity + // around the normal... + Point3F bounceVel = mCurrVelocity - rInfo.normal * (mDot( mCurrVelocity, rInfo.normal ) * 2.0);; + mCurrVelocity = bounceVel; + + // Add in surface friction... + Point3F tangent = bounceVel - rInfo.normal * mDot(bounceVel, rInfo.normal); + mCurrVelocity -= tangent * mDataBlock->bounceFriction; + + // Now, take elasticity into account for modulating the speed of the grenade + mCurrVelocity *= mDataBlock->bounceElasticity; + + // Set the new position to the impact and the bounce + // will apply on the next frame. + //F32 timeLeft = 1.0f - rInfo.t; + newPosition = oldPosition = rInfo.point + rInfo.normal * 0.05f; + } + } + } + + // re-enable the collision response on the source object now + // that we are done processing the ballistic movement + if (mSourceObject.isValid()) + mSourceObject->enableCollision(); + enableCollision(); + + if(isClientObject()) + { + emitParticles(mCurrPosition, newPosition, mCurrVelocity, TickMs); + updateSound(); + } + + mCurrDeltaBase = newPosition; + mCurrBackDelta = mCurrPosition - newPosition; + mCurrPosition = newPosition; + + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); +} + + +void Projectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (mHidden == true || dt == 0.0) + return; + + if (mActivateThread && + mProjectileShape->getDuration(mActivateThread) > mProjectileShape->getTime(mActivateThread) + dt) + { + mProjectileShape->advanceTime(dt, mActivateThread); + } + else + { + + if (mMaintainThread) + { + mProjectileShape->advanceTime(dt, mMaintainThread); + } + else if (mActivateThread && mDataBlock->maintainSeq != -1) + { + mMaintainThread = mProjectileShape->addThread(); + mProjectileShape->setTimeScale(mMaintainThread, 1); + mProjectileShape->setSequence(mMaintainThread, mDataBlock->maintainSeq, 0); + mProjectileShape->advanceTime(dt, mMaintainThread); + } + } +} + +void Projectile::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + if(mHidden == true) + return; + + Point3F interpPos = mCurrDeltaBase + mCurrBackDelta * delta; + Point3F dir = mCurrVelocity; + if(dir.isZero()) + dir.set(0,0,1); + else + dir.normalize(); + + MatrixF xform(true); + xform = MathUtils::createOrientFromDir(dir); + xform.setPosition(interpPos); + setRenderTransform(xform); + + // fade out the projectile image + S32 time = (S32)(mCurrTick - delta); + if(time > mDataBlock->fadeDelay) + { + F32 fade = F32(time - mDataBlock->fadeDelay); + mFadeValue = 1.0 - (fade / F32(mDataBlock->lifetime)); + } + else + mFadeValue = 1.0; + + updateSound(); +} + + + +//-------------------------------------------------------------------------- +void Projectile::onCollision(const Point3F& hitPosition, const Point3F& hitNormal, SceneObject* hitObject) +{ + // No client specific code should be placed or branched from this function + if(isClientObject()) + return; + + if (hitObject != NULL && isServerObject()) + { + char *posArg = Con::getArgBuffer(64); + char *normalArg = Con::getArgBuffer(64); + + dSprintf(posArg, 64, "%g %g %g", hitPosition.x, hitPosition.y, hitPosition.z); + dSprintf(normalArg, 64, "%g %g %g", hitNormal.x, hitNormal.y, hitNormal.z); + + Con::executef(mDataBlock, "onCollision", + scriptThis(), + Con::getIntArg(hitObject->getId()), + Con::getFloatArg(mFadeValue), + posArg, + normalArg); + } +} + +//-------------------------------------------------------------------------- +U32 Projectile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // Initial update + if (stream->writeFlag(mask & GameBase::InitialUpdateMask)) + { + Point3F pos; + getTransform().getColumn(3, &pos); + stream->writeCompressedPoint(pos); + F32 len = mCurrVelocity.len(); + if(stream->writeFlag(len > 0.02)) + { + Point3F outVel = mCurrVelocity; + outVel *= 1 / len; + stream->writeNormalVector(outVel, 10); + len *= 32.0; // 5 bits for fraction + if(len > 8191) + len = 8191; + stream->writeInt((S32)len, 13); + } + + stream->writeRangedU32(mCurrTick, 0, MaxLivingTicks); + if (mSourceObject.isValid()) + { + // Potentially have to write this to the client, let's make sure it has a + // ghost on the other side... + S32 ghostIndex = con->getGhostIndex(mSourceObject); + if (stream->writeFlag(ghostIndex != -1)) + { + stream->writeRangedU32(U32(ghostIndex), 0, NetConnection::MaxGhostCount); + stream->writeRangedU32(U32(mSourceObjectSlot), + 0, ShapeBase::MaxMountedImages - 1); + } + else // havn't recieved the ghost for the source object yet, try again later + retMask |= GameBase::InitialUpdateMask; + } + else + stream->writeFlag(false); + } + + // explosion update + if (stream->writeFlag((mask & ExplosionMask) && mHidden)) + { + mathWrite(*stream, mExplosionPosition); + mathWrite(*stream, mExplosionNormal); + stream->write(mCollideHitType); + } + + // bounce update + if (stream->writeFlag(mask & BounceMask)) + { + // Bounce against dynamic object + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mCurrVelocity); + } + + return retMask; +} + +void Projectile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + // initial update + if (stream->readFlag()) + { + Point3F pos; + stream->readCompressedPoint(&pos); + if(stream->readFlag()) + { + stream->readNormalVector(&mCurrVelocity, 10); + mCurrVelocity *= stream->readInt(13) / 32.0f; + } + else + mCurrVelocity.set(0, 0, 0); + + mCurrDeltaBase = pos; + mCurrBackDelta = mCurrPosition - pos; + mCurrPosition = pos; + setPosition(mCurrPosition); + + mCurrTick = stream->readRangedU32(0, MaxLivingTicks); + if (stream->readFlag()) + { + mSourceObjectId = stream->readRangedU32(0, NetConnection::MaxGhostCount); + mSourceObjectSlot = stream->readRangedU32(0, ShapeBase::MaxMountedImages - 1); + + NetObject* pObject = con->resolveGhost(mSourceObjectId); + if (pObject != NULL) + mSourceObject = dynamic_cast(pObject); + } + else + { + mSourceObjectId = -1; + mSourceObjectSlot = -1; + mSourceObject = NULL; + } + } + + // explosion update + if (stream->readFlag()) + { + Point3F explodePoint; + Point3F explodeNormal; + mathRead(*stream, &explodePoint); + mathRead(*stream, &explodeNormal); + stream->read(&mCollideHitType); + + // start the explosion visuals + explode(explodePoint, explodeNormal, mCollideHitType); + } + + // bounce update + if (stream->readFlag()) + { + mathRead(*stream, &mCurrPosition); + mathRead(*stream, &mCurrVelocity); + } +} + +//-------------------------------------------------------------------------- +bool Projectile::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if (mHidden == true || mFadeValue <= (1.0/255.0)) + return false; + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + if ( mDataBlock->lightDesc ) + { + mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() ); + } + + /* + if ( mFlareData ) + { + mFlareState.fullBrightness = mDataBlock->lightDesc->mBrightness; + mFlareState.scale = mFlareScale; + mFlareState.lightInfo = mLight; + mFlareState.lightMat = getTransform(); + + mFlareData->prepRender( state, &mFlareState ); + } + */ + + prepBatchRender( state ); + } + + return false; +} + +void Projectile::prepBatchRender( SceneState *state ) +{ + GFXTransformSaver saver; + + // Set up our TS render state. + TSRenderState rdata; + rdata.setSceneState( state ); + + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + mat.scale( mDataBlock->scale ); + GFX->setWorldMatrix( mat ); + + if(mProjectileShape) + { + AssertFatal(mProjectileShape != NULL, + "Projectile::renderObject: Error, projectile shape should always be present in renderObject"); + mProjectileShape->setDetailFromPosAndScale( state, mat.getPosition(), mObjScale ); + mProjectileShape->animate(); + + mProjectileShape->render( rdata ); + } +} diff --git a/T3D/projectile.h b/T3D/projectile.h new file mode 100644 index 0000000..d383e40 --- /dev/null +++ b/T3D/projectile.h @@ -0,0 +1,245 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROJECTILE_H_ +#define _PROJECTILE_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _SFXSOURCE_H_ +#include "sfx/sfxSource.h" +#endif +#ifndef _H_PARTICLE_EMITTER +#include "T3D/fx/particleEmitter.h" +#endif +#ifndef _LIGHTDESCRIPTION_H_ +#include "T3D/lightDescription.h" +#endif + +class ExplosionData; +class SplashData; +class ShapeBase; +class TSShapeInstance; +class TSThread; +class PhysicsWorld; +class DecalData; +class LightDescription; + +//-------------------------------------------------------------------------- +/// Datablock for projectiles. This class is the base class for all other projectiles. +class ProjectileData : public GameBaseData +{ + typedef GameBaseData Parent; + +protected: + bool onAdd(); + +public: + // variables set in datablock definition: + // Shape related + const char* projectileShapeName; + + /// Set to true if it is a billboard and want it to always face the viewer, false otherwise + bool faceViewer; + Point3F scale; + + + /// [0,1] scale of how much velocity should be inherited from the parent object + F32 velInheritFactor; + /// Speed of the projectile when fired + F32 muzzleVelocity; + + /// Force imparted on a hit object. + F32 impactForce; + + /// Should it arc? + bool isBallistic; + + /// How HIGH should it bounce (parallel to normal), [0,1] + F32 bounceElasticity; + /// How much momentum should be lost when it bounces (perpendicular to normal), [0,1] + F32 bounceFriction; + + /// Should this projectile fall/rise different than a default object? + F32 gravityMod; + + /// How long the projectile should exist before deleting itself + U32 lifetime; // all times are internally represented as ticks + /// How long it should not detonate on impact + S32 armingDelay; // the values are converted on initialization with + S32 fadeDelay; // the IRangeValidatorScaled field validator + + ExplosionData* explosion; + S32 explosionId; + + ExplosionData* waterExplosion; // Water Explosion Datablock + S32 waterExplosionId; // Water Explosion ID + + SplashData* splash; // Water Splash Datablock + S32 splashId; // Water splash ID + + DecalData *decal; // (impact) Decal Datablock + S32 decalId; // (impact) Decal ID + + SFXProfile* sound; // Projectile Sound + S32 soundId; // Projectile Sound ID + + LightDescription *lightDesc; + S32 lightDescId; + + // variables set on preload: + Resource projectileShape; + S32 activateSeq; + S32 maintainSeq; + + ParticleEmitterData* particleEmitter; + S32 particleEmitterId; + + ParticleEmitterData* particleWaterEmitter; + S32 particleWaterEmitterId; + + ProjectileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, String &errorStr); + + static bool setLifetime( void *obj, const char *data ); + static bool setArmingDelay( void *obj, const char *data ); + static bool setFadeDelay( void *obj, const char *data ); + static const char *getScaledValue( void *obj, const char *data); + static S32 scaleValue( S32 value, bool down = true ); + + static void initPersistFields(); + DECLARE_CONOBJECT(ProjectileData); +}; +DECLARE_CONSOLETYPE(ProjectileData) + + +//-------------------------------------------------------------------------- +/// Base class for all projectiles. +class Projectile : public GameBase, public ISceneLight +{ + typedef GameBase Parent; + +public: + // Initial conditions + enum ProjectileConstants { + SourceIdTimeoutTicks = 7, // = 231 ms + DeleteWaitTime = 500, ///< 500 ms delete timeout (for network transmission delays) + ExcessVelDirBits = 7, + MaxLivingTicks = 4095, + }; + enum UpdateMasks { + BounceMask = Parent::NextFreeMask, + ExplosionMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; +protected: + + PhysicsWorld *mPhysicsWorld; + + ProjectileData* mDataBlock; + + SimObjectPtr< ParticleEmitter > mParticleEmitter; + SimObjectPtr< ParticleEmitter > mParticleWaterEmitter; + + SFXSource* mSound; + + Point3F mCurrPosition; + Point3F mCurrVelocity; + S32 mSourceObjectId; + S32 mSourceObjectSlot; + + // Time related variables common to all projectiles, managed by processTick + + U32 mCurrTick; ///< Current time in ticks + SimObjectPtr mSourceObject; ///< Actual pointer to the source object, times out after SourceIdTimeoutTicks + + // Rendering related variables + TSShapeInstance* mProjectileShape; + TSThread* mActivateThread; + TSThread* mMaintainThread; + + /// ISceneLight + virtual void submitLights( LightManager *lm, bool staticLighting ); + virtual LightInfo* getLight() { return mLight; } + + LightInfo *mLight; + LightState mLightState; + + bool mHidden; ///< set by the derived class, if true, projectile doesn't render + F32 mFadeValue; ///< set in processTick, interpolation between fadeDelay and lifetime + ///< in data block + + // Warping and back delta variables. Only valid on the client + // + Point3F mWarpStart; + Point3F mWarpEnd; + U32 mWarpTicksRemaining; + + Point3F mCurrDeltaBase; + Point3F mCurrBackDelta; + + Point3F mExplosionPosition; + Point3F mExplosionNormal; + U32 mCollideHitType; + + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData *dptr); + + void processTick(const Move *move); + void advanceTime(F32 dt); + void interpolateTick(F32 delta); + + /// What to do once this projectile collides with something + virtual void onCollision(const Point3F& p, const Point3F& n, SceneObject*); + + /// What to do when this projectile explodes + virtual void explode(const Point3F& p, const Point3F& n, const U32 collideType ); + + /// Returns the velocity of the projectile + Point3F getVelocity() const; + bool pointInWater(const Point3F &point); + void emitParticles(const Point3F&, const Point3F&, const Point3F&, const U32); + void updateSound(); + + // Rendering + bool prepRenderImage ( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false); + void prepBatchRender ( SceneState *state); + + + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + +public: + F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + + Projectile(); + ~Projectile(); + + DECLARE_CONOBJECT(Projectile); + static void initPersistFields(); + + virtual bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + static U32 smProjectileWarpTicks; + +protected: + static const U32 csmStaticCollisionMask; + static const U32 csmDynamicCollisionMask; + static const U32 csmDamageableMask; +}; + +#endif // _H_PROJECTILE + diff --git a/T3D/resource.h b/T3D/resource.h new file mode 100644 index 0000000..ef0d756 --- /dev/null +++ b/T3D/resource.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#define IDI_ICON1 103 +#define IDI_ICON2 107 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 108 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/T3D/rigid.cpp b/T3D/rigid.cpp new file mode 100644 index 0000000..707c6b3 --- /dev/null +++ b/T3D/rigid.cpp @@ -0,0 +1,349 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/rigid.h" +#include "console/console.h" + + +//---------------------------------------------------------------------------- + +Rigid::Rigid() +{ + force.set(0.0f,0.0f,0.0f); + torque.set(0.0f,0.0f,0.0f); + linVelocity.set(0.0f,0.0f,0.0f); + linPosition.set(0.0f,0.0f,0.0f); + linMomentum.set(0.0f,0.0f,0.0f); + angVelocity.set(0.0f,0.0f,0.0f); + angMomentum.set(0.0f,0.0f,0.0f); + + angPosition.identity(); + invWorldInertia.identity(); + + centerOfMass.set(0.0f,0.0f,0.0f); + worldCenterOfMass = linPosition; + mass = oneOverMass = 1.0f; + invObjectInertia.identity(); + restitution = 0.3f; + friction = 0.5f; + atRest = false; +} + +void Rigid::clearForces() +{ + force.set(0.0f,0.0f,0.0f); + torque.set(0.0f,0.0f,0.0f); +} + +//----------------------------------------------------------------------------- +void Rigid::integrate(F32 delta) +{ + // Update Angular position + F32 angle = angVelocity.len(); + if (angle != 0.0f) { + QuatF dq; + F32 sinHalfAngle; + mSinCos(angle * delta * -0.5f, sinHalfAngle, dq.w); + sinHalfAngle *= 1.0f / angle; + dq.x = angVelocity.x * sinHalfAngle; + dq.y = angVelocity.y * sinHalfAngle; + dq.z = angVelocity.z * sinHalfAngle; + QuatF tmp = angPosition; + angPosition.mul(tmp, dq); + angPosition.normalize(); + + // Rotate the position around the center of mass + Point3F lp = linPosition - worldCenterOfMass; + dq.mulP(lp,&linPosition); + linPosition += worldCenterOfMass; + } + + // Update angular momentum + angMomentum = angMomentum + torque * delta; + + // Update linear position, momentum + linPosition = linPosition + linVelocity * delta; + linMomentum = linMomentum + force * delta; + linVelocity = linMomentum * oneOverMass; + + // Update dependent state variables + updateInertialTensor(); + updateVelocity(); + updateCenterOfMass(); +} + +void Rigid::updateVelocity() +{ + linVelocity.x = linMomentum.x * oneOverMass; + linVelocity.y = linMomentum.y * oneOverMass; + linVelocity.z = linMomentum.z * oneOverMass; + invWorldInertia.mulV(angMomentum,&angVelocity); +} + +void Rigid::updateInertialTensor() +{ + MatrixF iv,qmat; + angPosition.setMatrix(&qmat); + iv.mul(qmat,invObjectInertia); + qmat.transpose(); + invWorldInertia.mul(iv,qmat); +} + +void Rigid::updateCenterOfMass() +{ + // Move the center of mass into world space + angPosition.mulP(centerOfMass,&worldCenterOfMass); + worldCenterOfMass += linPosition; +} + +void Rigid::applyImpulse(const Point3F &r, const Point3F &impulse) +{ + atRest = false; + + // Linear momentum and velocity + linMomentum += impulse; + linVelocity.x = linMomentum.x * oneOverMass; + linVelocity.y = linMomentum.y * oneOverMass; + linVelocity.z = linMomentum.z * oneOverMass; + + // Rotational momentum and velocity + Point3F tv; + mCross(r,impulse,&tv); + angMomentum += tv; + invWorldInertia.mulV(angMomentum, &angVelocity); +} + +//----------------------------------------------------------------------------- +/** Resolve collision with another rigid body + Computes & applies the collision impulses needed to keep the bodies + from interpenetrating. + + tg: This function was commented out... I uncommented it, but haven't + double checked the math. +*/ +bool Rigid::resolveCollision(const Point3F& p, const Point3F &normal, Rigid* rigid) +{ + atRest = false; + Point3F v1,v2,r1,r2; + getOriginVector(p,&r1); + getVelocity(r1,&v1); + rigid->getOriginVector(p,&r2); + rigid->getVelocity(r2,&v2); + + // Make sure they are converging + F32 nv = mDot(v1,normal); + nv -= mDot(v2,normal); + if (nv > 0.0f) + return false; + + // Compute impulse + F32 d, n = -nv * (1.0f + restitution * rigid->restitution); + Point3F a1,b1,c1; + mCross(r1,normal,&a1); + invWorldInertia.mulV(a1,&b1); + mCross(b1,r1,&c1); + + Point3F a2,b2,c2; + mCross(r2,normal,&a2); + rigid->invWorldInertia.mulV(a2,&b2); + mCross(b2,r2,&c2); + + Point3F c3 = c1 + c2; + d = oneOverMass + rigid->oneOverMass + mDot(c3,normal); + Point3F impulse = normal * (n / d); + + applyImpulse(r1,impulse); + impulse.neg(); + applyImpulse(r2,impulse); + return true; +} + +//----------------------------------------------------------------------------- +/** Resolve collision with an immovable object + Computes & applies the collision impulse needed to keep the body + from penetrating the given surface. +*/ +bool Rigid::resolveCollision(const Point3F& p, const Point3F &normal) +{ + atRest = false; + Point3F v,r; + getOriginVector(p,&r); + getVelocity(r,&v); + F32 n = -mDot(v,normal); + if (n >= 0.0f) { + + // Collision impulse, straight forward force stuff. + F32 d = getZeroImpulse(r,normal); + F32 j = n * (1.0f + restitution) * d; + Point3F impulse = normal * j; + + // Friction impulse, calculated as a function of the + // amount of force it would take to stop the motion + // perpendicular to the normal. + Point3F uv = v + (normal * n); + F32 ul = uv.len(); + if (ul) { + uv /= -ul; + F32 u = ul * getZeroImpulse(r,uv); + j *= friction; + if (u > j) + u = j; + impulse += uv * u; + } + + // + applyImpulse(r,impulse); + } + return true; +} + +//----------------------------------------------------------------------------- +/** Calculate the inertia along the given vector + This function can be used to calculate the amount of force needed to + affect a change in velocity along the specified normal applied at + the given point. +*/ +F32 Rigid::getZeroImpulse(const Point3F& r,const Point3F& normal) +{ + Point3F a,b,c; + mCross(r,normal,&a); + invWorldInertia.mulV(a,&b); + mCross(b,r,&c); + return 1 / (oneOverMass + mDot(c,normal)); +} + +F32 Rigid::getKineticEnergy() +{ + Point3F w; + QuatF qmat = angPosition; + qmat.inverse(); + qmat.mulP(angVelocity,&w); + const F32* f = invObjectInertia; + return 0.5f * ((mass * mDot(linVelocity,linVelocity)) + + w.x * w.x / f[0] + + w.y * w.y / f[5] + + w.z * w.z / f[10]); +} + +void Rigid::getOriginVector(const Point3F &p,Point3F* r) +{ + *r = p - worldCenterOfMass; +} + +void Rigid::setCenterOfMass(const Point3F &newCenter) +{ + // Sets the center of mass relative to the origin. + centerOfMass = newCenter; + + // Update world center of mass + angPosition.mulP(centerOfMass,&worldCenterOfMass); + worldCenterOfMass += linPosition; +} + +void Rigid::translateCenterOfMass(const Point3F &oldPos,const Point3F &newPos) +{ + // I + mass * (crossmatrix(centerOfMass)^2 - crossmatrix(newCenter)^2) + MatrixF oldx,newx; + oldx.setCrossProduct(oldPos); + newx.setCrossProduct(newPos); + for (int row = 0; row < 3; row++) + for (int col = 0; col < 3; col++) { + F32 n = newx(row,col), o = oldx(row,col); + objectInertia(row,col) += mass * ((o * o) - (n * n)); + } + + // Make sure the matrix is symetrical + objectInertia(1,0) = objectInertia(0,1); + objectInertia(2,0) = objectInertia(0,2); + objectInertia(2,1) = objectInertia(1,2); +} + +void Rigid::getVelocity(const Point3F& r, Point3F* v) +{ + mCross(angVelocity, r, v); + *v += linVelocity; +} + +void Rigid::getTransform(MatrixF* mat) +{ + angPosition.setMatrix(mat); + mat->setColumn(3,linPosition); +} + +void Rigid::setTransform(const MatrixF& mat) +{ + angPosition.set(mat); + mat.getColumn(3,&linPosition); + + // Update center of mass + angPosition.mulP(centerOfMass,&worldCenterOfMass); + worldCenterOfMass += linPosition; +} + + +//---------------------------------------------------------------------------- +/** Set the rigid body moment of inertia + The moment is calculated as a box with the given dimensions. +*/ +void Rigid::setObjectInertia(const Point3F& r) +{ + // Rotational moment of inertia of a box + F32 ot = mass / 12.0f; + F32 a = r.x * r.x; + F32 b = r.y * r.y; + F32 c = r.z * r.z; + + objectInertia.identity(); + F32* f = objectInertia; + f[0] = ot * (b + c); + f[5] = ot * (c + a); + f[10] = ot * (a + b); + + invertObjectInertia(); + updateInertialTensor(); +} + + +//---------------------------------------------------------------------------- +/** Set the rigid body moment of inertia + The moment is calculated as a unit sphere. +*/ +void Rigid::setObjectInertia() +{ + objectInertia.identity(); + F32 radius = 1.0f; + F32* f = objectInertia; + f[0] = f[5] = f[10] = (0.4f * mass * radius * radius); + invertObjectInertia(); + updateInertialTensor(); +} + +void Rigid::invertObjectInertia() +{ + invObjectInertia = objectInertia; + invObjectInertia.fullInverse(); +} + + +//---------------------------------------------------------------------------- + +bool Rigid::checkRestCondition() +{ +// F32 k = getKineticEnergy(mWorldToObj); +// F32 G = -force.z * oneOverMass * 0.032; +// F32 Kg = 0.5 * mRigid.mass * G * G; +// if (k < Kg * restTol) +// mRigid.setAtRest(); + return atRest; +} + +void Rigid::setAtRest() +{ + atRest = true; + linVelocity.set(0.0f,0.0f,0.0f); + linMomentum.set(0.0f,0.0f,0.0f); + angVelocity.set(0.0f,0.0f,0.0f); + angMomentum.set(0.0f,0.0f,0.0f); +} diff --git a/T3D/rigid.h b/T3D/rigid.h new file mode 100644 index 0000000..2e13e9e --- /dev/null +++ b/T3D/rigid.h @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RIGID_H_ +#define _RIGID_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _MQUAT_H_ +#include "math/mQuat.h" +#endif + +//---------------------------------------------------------------------------- + +class Rigid +{ +public: + MatrixF objectInertia; ///< Moment of inertia + MatrixF invObjectInertia; ///< Inverse moment of inertia + MatrixF invWorldInertia; ///< Inverse moment of inertia in world space + + Point3F force; + Point3F torque; + + Point3F linVelocity; ///< Linear velocity + Point3F linPosition; ///< Current position + Point3F linMomentum; ///< Linear momentum + Point3F angVelocity; ///< Angular velocity + QuatF angPosition; ///< Current rotation + Point3F angMomentum; ///< Angular momentum + + Point3F centerOfMass; ///< Center of mass in object space + Point3F worldCenterOfMass; ///< CofM in world space + F32 mass; ///< Rigid body mass + F32 oneOverMass; ///< 1 / mass + F32 restitution; ///< Collision restitution + F32 friction; ///< Friction coefficient + bool atRest; + +private: + void translateCenterOfMass(const Point3F &oldPos,const Point3F &newPos); + +public: + // + Rigid(); + void clearForces(); + void integrate(F32 delta); + + void updateInertialTensor(); + void updateVelocity(); + void updateCenterOfMass(); + + void applyImpulse(const Point3F &v,const Point3F &impulse); + bool resolveCollision(const Point3F& p,const Point3F &normal,Rigid*); + bool resolveCollision(const Point3F& p,const Point3F &normal); + + F32 getZeroImpulse(const Point3F& r,const Point3F& normal); + F32 getKineticEnergy(); + void getOriginVector(const Point3F &r,Point3F* v); + void setCenterOfMass(const Point3F &v); + void getVelocity(const Point3F &p,Point3F* r); + void getTransform(MatrixF* mat); + void setTransform(const MatrixF& mat); + + void setObjectInertia(const Point3F& r); + void setObjectInertia(); + void invertObjectInertia(); + + bool checkRestCondition(); + void setAtRest(); +}; + + +#endif diff --git a/T3D/rigidShape.cpp b/T3D/rigidShape.cpp new file mode 100644 index 0000000..c9df04b --- /dev/null +++ b/T3D/rigidShape.cpp @@ -0,0 +1,1588 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/rigidShape.h" + +#include "platform/platform.h" +//#include "dgl/dgl.h" +#include "app/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "collision/clippedPolyList.h" +#include "collision/planeExtractor.h" +#include "T3D/moveManager.h" +#include "core/stream/bitStream.h" +#include "core/dnet.h" +#include "T3D/gameConnection.h" +#include "ts/tsShapeInstance.h" +//#include "T3D/fx/particleEngine.h" +//#include "audio/audioDataBlock.h" +#include "math/mathIO.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "environment/waterBlock.h" +#include "T3D/fx/cameraFXMgr.h" +#include "T3D/trigger.h" +#include "T3D/item.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "sfx/sfxProfile.h" + + +IMPLEMENT_CO_DATABLOCK_V1(RigidShapeData); +IMPLEMENT_CO_NETOBJECT_V1(RigidShape); + +//---------------------------------------------------------------------------- + +namespace { + + const U32 sMoveRetryCount = 3; + + // Client prediction + const S32 sMaxWarpTicks = 3; // Max warp duration in ticks + const S32 sMaxPredictionTicks = 30; // Number of ticks to predict + const F32 sRigidShapeGravity = -20; + + // Physics and collision constants + static F32 sRestTol = 0.5; // % of gravity energy to be at rest + static int sRestCount = 10; // Consecutive ticks before comming to rest + + const U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + PlayerObjectType | StaticTSObjectType | + StaticShapeObjectType | VehicleObjectType | + VehicleBlockerObjectType); + + const U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType + const U32 sClientCollisionMask = sCollisionMoveMask; + + void nonFilter(SceneObject* object,void *key) + { + Container::CallbackInfo* info = reinterpret_cast(key); + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); + } + +} // namespace {} + + +// Trigger objects that are not normally collided with. +static U32 sTriggerMask = ItemObjectType | +TriggerObjectType | +CorpseObjectType; + + +//---------------------------------------------------------------------------- + +RigidShapeData::RigidShapeData() +{ + shadowEnable = true; + + body.friction = 0; + body.restitution = 1; + + minImpactSpeed = 25; + softImpactSpeed = 25; + hardImpactSpeed = 50; + minRollSpeed = 0; + + cameraRoll = true; + cameraLag = 0; + cameraDecay = 0; + cameraOffset = 0; + + minDrag = 0; + maxDrag = 0; + integration = 1; + collisionTol = 0.1f; + contactTol = 0.1f; + massCenter.set(0,0,0); + massBox.set(0,0,0); + + drag = 0.7f; + density = 4; + + for (S32 i = 0; i < Body::MaxSounds; i++) + body.sound[i] = 0; + + dustEmitter = NULL; + dustID = 0; + triggerDustHeight = 3.0; + dustHeight = 1.0; + + dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) ); + dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) ); + + splashFreqMod = 300.0; + splashVelEpsilon = 0.50; + exitSplashSoundVel = 2.0; + softSplashSoundVel = 1.0; + medSplashSoundVel = 2.0; + hardSplashSoundVel = 3.0; + + dMemset(waterSound, 0, sizeof(waterSound)); + + dragForce = 0; + vertFactor = 0.25; + + normalForce = 30; + restorativeForce = 10; + rollForce = 2.5; + pitchForce = 2.5; + + dustTrailEmitter = NULL; + dustTrailID = 0; + dustTrailOffset.set( 0.0, 0.0, 0.0 ); + dustTrailFreqMod = 15.0; + triggerTrailHeight = 2.5; + +} + +RigidShapeData::~RigidShapeData() +{ + +} + +//---------------------------------------------------------------------------- + + +bool RigidShapeData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + + +bool RigidShapeData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) { + for (S32 i = 0; i < Body::MaxSounds; i++) + if (body.sound[i]) + Sim::findObject(SimObjectId(body.sound[i]),body.sound[i]); + } + + if( !dustEmitter && dustID != 0 ) + { + if( !Sim::findObject( dustID, dustEmitter ) ) + { + Con::errorf( ConsoleLogEntry::General, "RigidShapeData::preload Invalid packet, bad datablockId(dustEmitter): 0x%x", dustID ); + } + } + + U32 i; + for( i=0; i 1.0f) + { + Con::warnf("RigidShapeData::preload: vert factor must be [0, 1]"); + vertFactor = vertFactor < 0.0f ? 0.0f : 1.0f; + } + + massCenter = Point3F(0, 0, 0); + + if( !dustTrailEmitter && dustTrailID != 0 ) + { + if( !Sim::findObject( dustTrailID, dustTrailEmitter ) ) + { + Con::errorf( ConsoleLogEntry::General, "RigidShapeData::preload Invalid packet, bad datablockId(dustTrailEmitter): 0x%x", dustTrailID ); + } + } + + return true; +} + + +//---------------------------------------------------------------------------- + +void RigidShapeData::packData(BitStream* stream) +{ + S32 i; + Parent::packData(stream); + + stream->write(body.restitution); + stream->write(body.friction); + for (i = 0; i < Body::MaxSounds; i++) + if (stream->writeFlag(body.sound[i])) + stream->writeRangedU32(packed? SimObjectId(body.sound[i]): + body.sound[i]->getId(),DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + stream->write(minImpactSpeed); + stream->write(softImpactSpeed); + stream->write(hardImpactSpeed); + stream->write(minRollSpeed); + + stream->write(maxDrag); + stream->write(minDrag); + stream->write(integration); + stream->write(collisionTol); + stream->write(contactTol); + mathWrite(*stream,massCenter); + mathWrite(*stream,massBox); + + stream->writeFlag(cameraRoll); + stream->write(cameraLag); + stream->write(cameraDecay); + stream->write(cameraOffset); + + stream->write( triggerDustHeight ); + stream->write( dustHeight ); + + stream->write(exitSplashSoundVel); + stream->write(softSplashSoundVel); + stream->write(medSplashSoundVel); + stream->write(hardSplashSoundVel); + + // write the water sound profiles + for(i = 0; i < MaxSounds; i++) + if(stream->writeFlag(waterSound[i])) + stream->writeRangedU32(waterSound[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->writeFlag( dustEmitter )) + { + stream->writeRangedU32( dustEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++) + { + if( stream->writeFlag( splashEmitterList[i] != NULL ) ) + { + stream->writeRangedU32( splashEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + stream->write(splashFreqMod); + stream->write(splashVelEpsilon); + + stream->write(dragForce); + stream->write(vertFactor); + stream->write(normalForce); + stream->write(restorativeForce); + stream->write(rollForce); + stream->write(pitchForce); + mathWrite(*stream, dustTrailOffset); + stream->write(triggerTrailHeight); + stream->write(dustTrailFreqMod); + + if (stream->writeFlag( dustTrailEmitter )) + { + stream->writeRangedU32( dustTrailEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } +} + +void RigidShapeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&body.restitution); + stream->read(&body.friction); + S32 i; + for (i = 0; i < Body::MaxSounds; i++) { + body.sound[i] = NULL; + if (stream->readFlag()) + body.sound[i] = (SFXProfile*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + stream->read(&minImpactSpeed); + stream->read(&softImpactSpeed); + stream->read(&hardImpactSpeed); + stream->read(&minRollSpeed); + + stream->read(&maxDrag); + stream->read(&minDrag); + stream->read(&integration); + stream->read(&collisionTol); + stream->read(&contactTol); + mathRead(*stream,&massCenter); + mathRead(*stream,&massBox); + + cameraRoll = stream->readFlag(); + stream->read(&cameraLag); + stream->read(&cameraDecay); + stream->read(&cameraOffset); + + stream->read( &triggerDustHeight ); + stream->read( &dustHeight ); + + stream->read(&exitSplashSoundVel); + stream->read(&softSplashSoundVel); + stream->read(&medSplashSoundVel); + stream->read(&hardSplashSoundVel); + + // write the water sound profiles + for(i = 0; i < MaxSounds; i++) + if(stream->readFlag()) + { + U32 id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + waterSound[i] = dynamic_cast( Sim::findObject(id) ); + } + + if( stream->readFlag() ) + { + dustID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++) + { + if( stream->readFlag() ) + { + splashEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + stream->read(&splashFreqMod); + stream->read(&splashVelEpsilon); + + stream->read(&dragForce); + stream->read(&vertFactor); + stream->read(&normalForce); + stream->read(&restorativeForce); + stream->read(&rollForce); + stream->read(&pitchForce); + mathRead(*stream, &dustTrailOffset); + stream->read(&triggerTrailHeight); + stream->read(&dustTrailFreqMod); + + if( stream->readFlag() ) + { + dustTrailID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } +} + + +//---------------------------------------------------------------------------- + +void RigidShapeData::initPersistFields() +{ + addField("massCenter", TypePoint3F, Offset(massCenter, RigidShapeData)); + addField("massBox", TypePoint3F, Offset(massBox, RigidShapeData)); + addField("bodyRestitution", TypeF32, Offset(body.restitution, RigidShapeData)); + addField("bodyFriction", TypeF32, Offset(body.friction, RigidShapeData)); + addField("softImpactSound", TypeSFXProfilePtr, Offset(body.sound[Body::SoftImpactSound], RigidShapeData)); + addField("hardImpactSound", TypeSFXProfilePtr, Offset(body.sound[Body::HardImpactSound], RigidShapeData)); + + addField("minImpactSpeed", TypeF32, Offset(minImpactSpeed, RigidShapeData)); + addField("softImpactSpeed", TypeF32, Offset(softImpactSpeed, RigidShapeData)); + addField("hardImpactSpeed", TypeF32, Offset(hardImpactSpeed, RigidShapeData)); + addField("minRollSpeed", TypeF32, Offset(minRollSpeed, RigidShapeData)); + + addField("maxDrag", TypeF32, Offset(maxDrag, RigidShapeData)); + addField("minDrag", TypeF32, Offset(minDrag, RigidShapeData)); + addField("integration", TypeS32, Offset(integration, RigidShapeData)); + addField("collisionTol", TypeF32, Offset(collisionTol, RigidShapeData)); + addField("contactTol", TypeF32, Offset(contactTol, RigidShapeData)); + + addField("cameraRoll", TypeBool, Offset(cameraRoll, RigidShapeData)); + addField("cameraLag", TypeF32, Offset(cameraLag, RigidShapeData)); + addField("cameraDecay", TypeF32, Offset(cameraDecay, RigidShapeData)); + addField("cameraOffset", TypeF32, Offset(cameraOffset, RigidShapeData)); + + addField("dustEmitter", TypeParticleEmitterDataPtr, Offset(dustEmitter, RigidShapeData)); + addField("triggerDustHeight", TypeF32, Offset(triggerDustHeight, RigidShapeData)); + addField("dustHeight", TypeF32, Offset(dustHeight, RigidShapeData)); + + addField("splashEmitter", TypeParticleEmitterDataPtr, Offset(splashEmitterList, RigidShapeData), VC_NUM_SPLASH_EMITTERS); + + addField("splashFreqMod", TypeF32, Offset(splashFreqMod, RigidShapeData)); + addField("splashVelEpsilon", TypeF32, Offset(splashVelEpsilon, RigidShapeData)); + + addField("exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, RigidShapeData)); + addField("softSplashSoundVelocity", TypeF32, Offset(softSplashSoundVel, RigidShapeData)); + addField("mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, RigidShapeData)); + addField("hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, RigidShapeData)); + addField("exitingWater", TypeSFXProfilePtr, Offset(waterSound[ExitWater], RigidShapeData)); + addField("impactWaterEasy", TypeSFXProfilePtr, Offset(waterSound[ImpactSoft], RigidShapeData)); + addField("impactWaterMedium", TypeSFXProfilePtr, Offset(waterSound[ImpactMedium], RigidShapeData)); + addField("impactWaterHard", TypeSFXProfilePtr, Offset(waterSound[ImpactHard], RigidShapeData)); + addField("waterWakeSound", TypeSFXProfilePtr, Offset(waterSound[Wake], RigidShapeData)); + + addField("dragForce", TypeF32, Offset(dragForce, RigidShapeData)); + addField("vertFactor", TypeF32, Offset(vertFactor, RigidShapeData)); + + addField("normalForce", TypeF32, Offset(normalForce, RigidShapeData)); + addField("restorativeForce", TypeF32, Offset(restorativeForce, RigidShapeData)); + addField("rollForce", TypeF32, Offset(rollForce, RigidShapeData)); + addField("pitchForce", TypeF32, Offset(pitchForce, RigidShapeData)); + + addField("dustTrailEmitter", TypeParticleEmitterDataPtr, Offset(dustTrailEmitter, RigidShapeData)); + addField("dustTrailOffset", TypePoint3F, Offset(dustTrailOffset, RigidShapeData)); + addField("triggerTrailHeight", TypeF32, Offset(triggerTrailHeight, RigidShapeData)); + addField("dustTrailFreqMod", TypeF32, Offset(dustTrailFreqMod, RigidShapeData)); + + Parent::initPersistFields(); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +RigidShape::RigidShape() +{ + + mNetFlags.set(Ghostable); + + mDustTrailEmitter = NULL; + + mDataBlock = 0; + mTypeMask |= VehicleObjectType; + + mDelta.pos = Point3F(0,0,0); + mDelta.posVec = Point3F(0,0,0); + mDelta.warpTicks = mDelta.warpCount = 0; + mDelta.dt = 1; + mDelta.move = NullMove; + mPredictionCount = 0; + mDelta.cameraOffset.set(0,0,0); + mDelta.cameraVec.set(0,0,0); + mDelta.cameraRot.set(0,0,0); + mDelta.cameraRotVec.set(0,0,0); + + mRigid.linPosition.set(0, 0, 0); + mRigid.linVelocity.set(0, 0, 0); + mRigid.angPosition.identity(); + mRigid.angVelocity.set(0, 0, 0); + mRigid.linMomentum.set(0, 0, 0); + mRigid.angMomentum.set(0, 0, 0); + mContacts.clear(); + + mCameraOffset.set(0,0,0); + + dMemset( mDustEmitterList, 0, sizeof( mDustEmitterList ) ); + dMemset( mSplashEmitterList, 0, sizeof( mSplashEmitterList ) ); + + mDisableMove = false; // start frozen by default + restCount = 0; + + inLiquid = false; +} + +RigidShape::~RigidShape() +{ + // +} + +U32 RigidShape::getCollisionMask() +{ + if (isServerObject()) + return sServerCollisionMask; + else + return sClientCollisionMask; +} + +Point3F RigidShape::getVelocity() const +{ + return mRigid.linVelocity; +} + +//---------------------------------------------------------------------------- + +bool RigidShape::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // When loading from a mission script, the base SceneObject's transform + // will have been set and needs to be transfered to the rigid body. + mRigid.setTransform(mObjToWorld); + + // Initialize interpolation vars. + mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition; + mDelta.pos = mRigid.linPosition; + mDelta.posVec = Point3F(0,0,0); + + // Create Emitters on the client + if( isClientObject() ) + { + if( mDataBlock->dustEmitter ) + { + for( int i=0; ionNewDataBlock( mDataBlock->dustEmitter ); + if( !mDustEmitterList[i]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); + delete mDustEmitterList[i]; + mDustEmitterList[i] = NULL; + } + } + } + + U32 j; + for( j=0; jsplashEmitterList[j] ) + { + mSplashEmitterList[j] = new ParticleEmitter; + mSplashEmitterList[j]->onNewDataBlock( mDataBlock->splashEmitterList[j] ); + if( !mSplashEmitterList[j]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register splash emitter for class: %s", mDataBlock->getName() ); + delete mSplashEmitterList[j]; + mSplashEmitterList[j] = NULL; + } + + } + } + } + + // Create a new convex. + AssertFatal(mDataBlock->collisionDetails[0] != -1, "Error, a rigid shape must have a collision-1 detail!"); + mConvex.mObject = this; + mConvex.pShapeBase = this; + mConvex.hullId = 0; + mConvex.box = mObjBox; + mConvex.box.minExtents.convolve(mObjScale); + mConvex.box.maxExtents.convolve(mObjScale); + mConvex.findNodeTransform(); + + addToScene(); + + + if( !isServerObject() ) + { + if( mDataBlock->dustTrailEmitter ) + { + mDustTrailEmitter = new ParticleEmitter; + mDustTrailEmitter->onNewDataBlock( mDataBlock->dustTrailEmitter ); + if( !mDustTrailEmitter->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); + delete mDustTrailEmitter; + mDustTrailEmitter = NULL; + } + } + } + + + if (isServerObject()) + scriptOnAdd(); + + return true; +} + +void RigidShape::onRemove() +{ + scriptOnRemove(); + removeFromScene(); + + U32 i=0; + for( i=0; ideleteWhenEmpty(); + mDustEmitterList[i] = NULL; + } + } + + for( i=0; ideleteWhenEmpty(); + mSplashEmitterList[i] = NULL; + } + } + + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- + +void RigidShape::processTick(const Move* move) +{ + Parent::processTick(move); + + // Warp to catch up to server + if (mDelta.warpCount < mDelta.warpTicks) + { + mDelta.warpCount++; + + // Set new pos. + mObjToWorld.getColumn(3,&mDelta.pos); + mDelta.pos += mDelta.warpOffset; + mDelta.rot[0] = mDelta.rot[1]; + mDelta.rot[1].interpolate(mDelta.warpRot[0],mDelta.warpRot[1],F32(mDelta.warpCount)/mDelta.warpTicks); + setPosition(mDelta.pos,mDelta.rot[1]); + + // Pos backstepping + mDelta.posVec.x = -mDelta.warpOffset.x; + mDelta.posVec.y = -mDelta.warpOffset.y; + mDelta.posVec.z = -mDelta.warpOffset.z; + } + else + { + if (!move) + { + if (isGhost()) + { + // If we haven't run out of prediction time, + // predict using the last known move. + if (mPredictionCount-- <= 0) + return; + move = &mDelta.move; + } + else + move = &NullMove; + } + + // Process input move + updateMove(move); + + // Save current rigid state interpolation + mDelta.posVec = mRigid.linPosition; + mDelta.rot[0] = mRigid.angPosition; + + // Update the physics based on the integration rate + S32 count = mDataBlock->integration; + updateWorkingCollisionSet(getCollisionMask()); + for (U32 i = 0; i < count; i++) + updatePos(TickSec / count); + + // Wrap up interpolation info + mDelta.pos = mRigid.linPosition; + mDelta.posVec -= mRigid.linPosition; + mDelta.rot[1] = mRigid.angPosition; + + // Update container database + setPosition(mRigid.linPosition, mRigid.angPosition); + setMaskBits(PositionMask); + updateContainer(); + } +} + +void RigidShape::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); + + if(dt == 0.0f) + setRenderPosition(mDelta.pos, mDelta.rot[1]); + else + { + QuatF rot; + rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt); + Point3F pos = mDelta.pos + mDelta.posVec * dt; + setRenderPosition(pos,rot); + } + mDelta.dt = dt; +} + +void RigidShape::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + updateFroth(dt); + + // Update 3rd person camera offset. Camera update is done + // here as it's a client side only animation. + mCameraOffset -= + (mCameraOffset * mDataBlock->cameraDecay + + mRigid.linVelocity * mDataBlock->cameraLag) * dt; +} + + +//---------------------------------------------------------------------------- + +bool RigidShape::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + // Update Rigid Info + mRigid.mass = mDataBlock->mass; + mRigid.oneOverMass = 1 / mRigid.mass; + mRigid.friction = mDataBlock->body.friction; + mRigid.restitution = mDataBlock->body.restitution; + mRigid.setCenterOfMass(mDataBlock->massCenter); + + // Ignores massBox, just set sphere for now. Derived objects + // can set what they want. + mRigid.setObjectInertia(); + + scriptOnNewDataBlock(); + + return true; +} + + +//---------------------------------------------------------------------------- + +void RigidShape::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot) +{ + *min = mDataBlock->cameraMinDist; + *max = mDataBlock->cameraMaxDist; + + off->set(0,0,mDataBlock->cameraOffset); + rot->identity(); +} + + +//---------------------------------------------------------------------------- + +void RigidShape::getCameraTransform(F32* pos,MatrixF* mat) +{ + // Returns camera to world space transform + // Handles first person / third person camera position + if (isServerObject() && mShapeInstance) + mShapeInstance->animateNodeSubtrees(true); + + if (*pos == 0) + { + getRenderEyeTransform(mat); + return; + } + + // Get the shape's camera parameters. + F32 min,max; + MatrixF rot; + Point3F offset; + getCameraParameters(&min,&max,&offset,&rot); + + // Start with the current eye position + MatrixF eye; + getRenderEyeTransform(&eye); + + // Build a transform that points along the eye axis + // but where the Z axis is always up. + if (mDataBlock->cameraRoll) + mat->mul(eye,rot); + else + { + MatrixF cam(1); + VectorF x,y,z(0,0,1); + eye.getColumn(1, &y); + + mCross(y, z, &x); + x.normalize(); + mCross(x, y, &z); + z.normalize(); + + cam.setColumn(0,x); + cam.setColumn(1,y); + cam.setColumn(2,z); + mat->mul(cam,rot); + } + + // Camera is positioned straight back along the eye's -Y axis. + // A ray is cast to make sure the camera doesn't go through + // anything solid. + VectorF vp,vec; + vp.x = vp.z = 0; + vp.y = -(max - min) * *pos; + eye.mulV(vp,&vec); + + // Use the camera node as the starting position if it exists. + Point3F osp,sp; + if (mDataBlock->cameraNode != -1) + { + mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp); + getRenderTransform().mulP(osp,&sp); + } + else + eye.getColumn(3,&sp); + + // Make sure we don't hit ourself... + disableCollision(); + if (isMounted()) + getObjectMount()->disableCollision(); + + // Cast the ray into the container database to see if we're going + // to hit anything. + RayInfo collision; + Point3F ep = sp + vec + offset + mCameraOffset; + if (mContainer->castRay(sp, ep, + ~(WaterObjectType | GameBaseObjectType | DefaultObjectType), + &collision) == true) + { + + // Shift the collision point back a little to try and + // avoid clipping against the front camera plane. + F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1; + if (t > 0.0f) + ep = sp + offset + mCameraOffset + (vec * t); + else + eye.getColumn(3,&ep); + } + mat->setColumn(3,ep); + + // Re-enable our collision. + if (isMounted()) + getObjectMount()->enableCollision(); + enableCollision(); + + // Apply Camera FX. + mat->mul( gCamFXMgr.getTrans() ); +} + + +//---------------------------------------------------------------------------- + +void RigidShape::getVelocity(const Point3F& r, Point3F* v) +{ + mRigid.getVelocity(r, v); +} + +void RigidShape::applyImpulse(const Point3F &pos, const Point3F &impulse) +{ + Point3F r; + mRigid.getOriginVector(pos,&r); + mRigid.applyImpulse(r, impulse); +} + + +//---------------------------------------------------------------------------- + +void RigidShape::updateMove(const Move* move) +{ + mDelta.move = *move; +} + +//---------------------------------------------------------------------------- + +void RigidShape::setPosition(const Point3F& pos,const QuatF& rot) +{ + MatrixF mat; + rot.setMatrix(&mat); + mat.setColumn(3,pos); + Parent::setTransform(mat); +} + +void RigidShape::setRenderPosition(const Point3F& pos, const QuatF& rot) +{ + MatrixF mat; + rot.setMatrix(&mat); + mat.setColumn(3,pos); + Parent::setRenderTransform(mat); +} + +void RigidShape::setTransform(const MatrixF& newMat) +{ + mRigid.setTransform(newMat); + Parent::setTransform(newMat); + mRigid.atRest = false; + mContacts.clear(); +} + + +//----------------------------------------------------------------------------- + +void RigidShape::disableCollision() +{ + Parent::disableCollision(); +} + +void RigidShape::enableCollision() +{ + Parent::enableCollision(); +} + + +//---------------------------------------------------------------------------- +/** Update the physics +*/ + +void RigidShape::updatePos(F32 dt) +{ + Point3F origVelocity = mRigid.linVelocity; + + // Update internal forces acting on the body. + mRigid.clearForces(); + updateForces(dt); + + // Update collision information based on our current pos. + bool collided = false; + if (!mRigid.atRest && !mDisableMove) + { + collided = updateCollision(dt); + + // Now that all the forces have been processed, lets + // see if we're at rest. Basically, if the kinetic energy of + // the shape is less than some percentage of the energy added + // by gravity for a short period, we're considered at rest. + // This should really be part of the rigid class... + if (mCollisionList.getCount()) + { + F32 k = mRigid.getKineticEnergy(); + F32 G = sRigidShapeGravity * dt; + F32 Kg = 0.5 * mRigid.mass * G * G; + if (k < sRestTol * Kg && ++restCount > sRestCount) + mRigid.setAtRest(); + } + else + restCount = 0; + } + + // Integrate forward + if (!mRigid.atRest && !mDisableMove) + mRigid.integrate(dt); + + // Deal with client and server scripting, sounds, etc. + if (isServerObject()) + { + + // Check triggers and other objects that we normally don't + // collide with. This function must be called before notifyCollision + // as it will queue collision. + checkTriggers(); + + // Invoke the onCollision notify callback for all the objects + // we've just hit. + notifyCollision(); + + // Server side impact script callback + if (collided) + { + VectorF collVec = mRigid.linVelocity - origVelocity; + F32 collSpeed = collVec.len(); + if (collSpeed > mDataBlock->minImpactSpeed) + onImpact(collVec); + } + + // Water script callbacks + if (!inLiquid && mWaterCoverage != 0.0f) + { + Con::executef(mDataBlock,"onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), mLiquidType.c_str() ); + inLiquid = true; + } + else if (inLiquid && mWaterCoverage == 0.0f) + { + Con::executef(mDataBlock,"onLeaveLiquid",scriptThis(), mLiquidType.c_str() ); + inLiquid = false; + } + + } + else { + + // Play impact sounds on the client. + if (collided) { + F32 collSpeed = (mRigid.linVelocity - origVelocity).len(); + S32 impactSound = -1; + if (collSpeed >= mDataBlock->hardImpactSpeed) + impactSound = RigidShapeData::Body::HardImpactSound; + else + if (collSpeed >= mDataBlock->softImpactSpeed) + impactSound = RigidShapeData::Body::SoftImpactSound; + + if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL) + SFX->playOnce(mDataBlock->body.sound[impactSound], &getTransform()); + } + + // Water volume sounds + F32 vSpeed = getVelocity().len(); + if (!inLiquid && mWaterCoverage >= 0.8f) { + if (vSpeed >= mDataBlock->hardSplashSoundVel) + SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactHard], &getTransform()); + else + if (vSpeed >= mDataBlock->medSplashSoundVel) + SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactMedium], &getTransform()); + else + if (vSpeed >= mDataBlock->softSplashSoundVel) + SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ImpactSoft], &getTransform()); + inLiquid = true; + } + else + if(inLiquid && mWaterCoverage < 0.8f) { + if (vSpeed >= mDataBlock->exitSplashSoundVel) + SFX->playOnce(mDataBlock->waterSound[RigidShapeData::ExitWater], &getTransform()); + inLiquid = false; + } + } +} + + +//---------------------------------------------------------------------------- + +void RigidShape::updateForces(F32 /*dt*/) +{ + Point3F gravForce(0, 0, sRigidShapeGravity * mRigid.mass * mGravityMod); + + MatrixF currTransform; + mRigid.getTransform(&currTransform); + mRigid.atRest = false; + + Point3F torque(0, 0, 0); + Point3F force(0, 0, 0); + + Point3F vel = mRigid.linVelocity; + + // Gravity + force += gravForce; + + // Apply drag + Point3F vDrag = mRigid.linVelocity; + vDrag.convolve(Point3F(1, 1, mDataBlock->vertFactor)); + force -= vDrag * mDataBlock->dragForce; + + // Add in physical zone force + force += mAppliedForce; + + // Container buoyancy & drag + force += Point3F(0, 0,-mBuoyancy * sRigidShapeGravity * mRigid.mass * mGravityMod); + force -= mRigid.linVelocity * mDrag; + torque -= mRigid.angMomentum * mDrag; + + mRigid.force = force; + mRigid.torque = torque; +} + + +//----------------------------------------------------------------------------- +/** Update collision information +Update the convex state and check for collisions. If the object is in +collision, impact and contact forces are generated. +*/ + +bool RigidShape::updateCollision(F32 dt) +{ + // Update collision information + MatrixF mat,cmat; + mConvex.transform = &mat; + mRigid.getTransform(&mat); + cmat = mConvex.getTransform(); + + mCollisionList.clear(); + CollisionState *state = mConvex.findClosestState(cmat, getScale(), mDataBlock->collisionTol); + if (state && state->dist <= mDataBlock->collisionTol) + { + //resolveDisplacement(ns,state,dt); + mConvex.getCollisionInfo(cmat, getScale(), &mCollisionList, mDataBlock->collisionTol); + } + + // Resolve collisions + bool collided = resolveCollision(mRigid,mCollisionList); + resolveContacts(mRigid,mCollisionList,dt); + return collided; +} + + +//---------------------------------------------------------------------------- +/** Resolve collision impacts +Handle collision impacts, as opposed to contacts. Impulses are calculated based +on standard collision resolution formulas. +*/ +bool RigidShape::resolveCollision(Rigid& ns,CollisionList& cList) +{ + // Apply impulses to resolve collision + bool colliding, collided = false; + + do + { + colliding = false; + for (S32 i = 0; i < cList.getCount(); i++) + { + Collision& c = cList[i]; + if (c.distance < mDataBlock->collisionTol) + { + // Velocity into surface + Point3F v,r; + ns.getOriginVector(c.point,&r); + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + + // Only interested in velocities greater than sContactTol, + // velocities less than that will be dealt with as contacts + // "constraints". + if (vn < -mDataBlock->contactTol) + { + + // Apply impulses to the rigid body to keep it from + // penetrating the surface. + ns.resolveCollision(cList[i].point, + cList[i].normal); + colliding = collided = true; + + // Keep track of objects we collide with + if (!isGhost() && c.object->getTypeMask() & ShapeBaseObjectType) + { + ShapeBase* col = static_cast(c.object); + queueCollision(col,v - col->getVelocity()); + } + } + } + } + } while (colliding); + + return collided; +} + +//---------------------------------------------------------------------------- +/** Resolve contact forces +Resolve contact forces using the "penalty" method. Forces are generated based +on the depth of penetration and the moment of inertia at the point of contact. +*/ +bool RigidShape::resolveContacts(Rigid& ns,CollisionList& cList,F32 dt) +{ + // Use spring forces to manage contact constraints. + bool collided = false; + Point3F t,p(0,0,0),l(0,0,0); + for (S32 i = 0; i < cList.getCount(); i++) + { + Collision& c = cList[i]; + if (c.distance < mDataBlock->collisionTol) + { + + // Velocity into the surface + Point3F v,r; + ns.getOriginVector(c.point,&r); + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + + // Only interested in velocities less than mDataBlock->contactTol, + // velocities greater than that are dealt with as collisions. + if (mFabs(vn) < mDataBlock->contactTol) + { + collided = true; + + // Penetration force. This is actually a spring which + // will seperate the body from the collision surface. + F32 zi = 2 * mFabs(mRigid.getZeroImpulse(r,c.normal)); + F32 s = (mDataBlock->collisionTol - c.distance) * zi - ((vn / mDataBlock->contactTol) * zi); + Point3F f = c.normal * s; + + // Friction impulse, calculated as a function of the + // amount of force it would take to stop the motion + // perpendicular to the normal. + Point3F uv = v - (c.normal * vn); + F32 ul = uv.len(); + if (s > 0 && ul) + { + uv /= -ul; + F32 u = ul * ns.getZeroImpulse(r,uv); + s *= mRigid.friction; + if (u > s) + u = s; + f += uv * u; + } + + // Accumulate forces + p += f; + mCross(r,f,&t); + l += t; + } + } + } + + // Contact constraint forces act over time... + ns.linMomentum += p * dt; + ns.angMomentum += l * dt; + ns.updateVelocity(); + return true; +} + + +//---------------------------------------------------------------------------- + +bool RigidShape::resolveDisplacement(Rigid& ns,CollisionState *state, F32 dt) +{ + SceneObject* obj = (state->a->getObject() == this)? + state->b->getObject(): state->a->getObject(); + + if (obj->isDisplacable() && ((obj->getTypeMask() & ShapeBaseObjectType) != 0)) + { + // Try to displace the object by the amount we're trying to move + Point3F objNewMom = ns.linVelocity * obj->getMass() * 1.1f; + Point3F objOldMom = obj->getMomentum(); + Point3F objNewVel = objNewMom / obj->getMass(); + + Point3F myCenter; + Point3F theirCenter; + getWorldBox().getCenter(&myCenter); + obj->getWorldBox().getCenter(&theirCenter); + if (mDot(myCenter - theirCenter, objNewMom) >= 0.0f || objNewVel.len() < 0.01) + { + objNewMom = (theirCenter - myCenter); + objNewMom.normalize(); + objNewMom *= 1.0f * obj->getMass(); + objNewVel = objNewMom / obj->getMass(); + } + + obj->setMomentum(objNewMom); + if (obj->displaceObject(objNewVel * 1.1f * dt) == true) + { + // Queue collision and change in velocity + VectorF dv = (objOldMom - objNewMom) / obj->getMass(); + queueCollision(static_cast(obj), dv); + return true; + } + } + + return false; +} + + +//---------------------------------------------------------------------------- + +void RigidShape::updateWorkingCollisionSet(const U32 mask) +{ + Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale()); + F32 len = (mRigid.linVelocity.len() + 50) * TickSec; + F32 l = (len * 1.1) + 0.1; // fudge factor + convexBox.minExtents -= Point3F(l, l, l); + convexBox.maxExtents += Point3F(l, l, l); + + disableCollision(); + mConvex.updateWorkingList(convexBox, mask); + enableCollision(); +} + + +//---------------------------------------------------------------------------- +/** Check collisions with trigger and items +Perform a container search using the current bounding box +of the main body, wheels are not included. This method should +only be called on the server. +*/ +void RigidShape::checkTriggers() +{ + Box3F bbox = mConvex.getBoundingBox(getTransform(), getScale()); + gServerContainer.findObjects(bbox,sTriggerMask,findCallback,this); +} + +/** The callback used in by the checkTriggers() method. +The checkTriggers method uses a container search which will +invoke this callback on each obj that matches. +*/ +void RigidShape::findCallback(SceneObject* obj,void *key) +{ + RigidShape* shape = reinterpret_cast(key); + U32 objectMask = obj->getTypeMask(); + + // Check: triggers, corpses and items, basically the same things + // that the player class checks for + if (objectMask & TriggerObjectType) { + Trigger* pTrigger = static_cast(obj); + pTrigger->potentialEnterObject(shape); + } + else if (objectMask & CorpseObjectType) { + ShapeBase* col = static_cast(obj); + shape->queueCollision(col,shape->getVelocity() - col->getVelocity()); + } + else if (objectMask & ItemObjectType) { + Item* item = static_cast(obj); + if (shape != item->getCollisionObject()) + shape->queueCollision(item,shape->getVelocity() - item->getVelocity()); + } +} + + +//---------------------------------------------------------------------------- + +void RigidShape::writePacketData(GameConnection *connection, BitStream *stream) +{ + Parent::writePacketData(connection, stream); + + mathWrite(*stream, mRigid.linPosition); + mathWrite(*stream, mRigid.angPosition); + mathWrite(*stream, mRigid.linMomentum); + mathWrite(*stream, mRigid.angMomentum); + stream->writeFlag(mRigid.atRest); + stream->writeFlag(mContacts.getCount() == 0); + + stream->writeFlag(mDisableMove); + stream->setCompressionPoint(mRigid.linPosition); +} + +void RigidShape::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + + mathRead(*stream, &mRigid.linPosition); + mathRead(*stream, &mRigid.angPosition); + mathRead(*stream, &mRigid.linMomentum); + mathRead(*stream, &mRigid.angMomentum); + mRigid.atRest = stream->readFlag(); + if (stream->readFlag()) + mContacts.clear(); + mRigid.updateInertialTensor(); + mRigid.updateVelocity(); + + mDisableMove = stream->readFlag(); + stream->setCompressionPoint(mRigid.linPosition); +} + + +//---------------------------------------------------------------------------- + +U32 RigidShape::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) + return retMask; + + mDelta.move.pack(stream); + + if (stream->writeFlag(mask & PositionMask)) + { + stream->writeCompressedPoint(mRigid.linPosition); + mathWrite(*stream, mRigid.angPosition); + mathWrite(*stream, mRigid.linMomentum); + mathWrite(*stream, mRigid.angMomentum); + stream->writeFlag(mRigid.atRest); + } + + if(stream->writeFlag(mask & FreezeMask)) + stream->writeFlag(mDisableMove); + + return retMask; +} + +void RigidShape::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + if (stream->readFlag()) + return; + + mDelta.move.unpack(stream); + + if (stream->readFlag()) + { + mPredictionCount = sMaxPredictionTicks; + F32 speed = mRigid.linVelocity.len(); + mDelta.warpRot[0] = mRigid.angPosition; + + // Read in new position and momentum values + stream->readCompressedPoint(&mRigid.linPosition); + mathRead(*stream, &mRigid.angPosition); + mathRead(*stream, &mRigid.linMomentum); + mathRead(*stream, &mRigid.angMomentum); + mRigid.atRest = stream->readFlag(); + mRigid.updateVelocity(); + + if (isProperlyAdded()) + { + // Determine number of ticks to warp based on the average + // of the client and server velocities. + Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt; + mDelta.warpOffset = mRigid.linPosition - cp; + + // Calc the distance covered in one tick as the average of + // the old speed and the new speed from the server. + F32 dt,as = (speed + mRigid.linVelocity.len()) * 0.5 * TickSec; + + // Cal how many ticks it will take to cover the warp offset. + // If it's less than what's left in the current tick, we'll just + // warp in the remaining time. + if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks) + dt = mDelta.dt + sMaxWarpTicks; + else + dt = (dt <= mDelta.dt)? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt; + + // Adjust current frame interpolation + if (mDelta.dt) + { + mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt)); + mDelta.posVec = (cp - mDelta.pos) / mDelta.dt; + QuatF cr; + cr.interpolate(mDelta.rot[1],mDelta.rot[0],mDelta.dt); + mDelta.rot[1].interpolate(cr,mRigid.angPosition,mDelta.dt / dt); + mDelta.rot[0].extrapolate(mDelta.rot[1],cr,mDelta.dt); + } + + // Calculated multi-tick warp + mDelta.warpCount = 0; + mDelta.warpTicks = (S32)(mFloor(dt)); + if (mDelta.warpTicks) + { + mDelta.warpOffset = mRigid.linPosition - mDelta.pos; + mDelta.warpOffset /= mDelta.warpTicks; + mDelta.warpRot[0] = mDelta.rot[1]; + mDelta.warpRot[1] = mRigid.angPosition; + } + } + else + { + // Set the shape to the server position + mDelta.dt = 0; + mDelta.pos = mRigid.linPosition; + mDelta.posVec.set(0,0,0); + mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition; + mDelta.warpCount = mDelta.warpTicks = 0; + setPosition(mRigid.linPosition, mRigid.angPosition); + } + } + + if(stream->readFlag()) + mDisableMove = stream->readFlag(); +} + + +//---------------------------------------------------------------------------- + +void RigidShape::initPersistFields() +{ + //addField("disableMove", TypeBool, Offset(mDisableMove, RigidShape)); + + Parent::initPersistFields(); +} + +//---------------------------------------------------------------------------- + +void RigidShape::updateLiftoffDust( F32 dt ) +{ + Point3F offset( 0.0, 0.0, mDataBlock->dustHeight ); + emitDust( mDustEmitterList[ 0 ], mDataBlock->triggerDustHeight, offset, + ( U32 )( dt * 1000 ) ); +} + +//-------------------------------------------------------------------------- +void RigidShape::updateFroth( F32 dt ) +{ + // update bubbles + Point3F moveDir = getVelocity(); + + Point3F contactPoint; + + F32 speed = moveDir.len(); + if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0; + + U32 emitRate = (U32)(speed * mDataBlock->splashFreqMod * dt); + + U32 i; + for( i=0; iemitParticles( contactPoint, contactPoint, Point3F( 0.0, 0.0, 1.0 ), + moveDir, emitRate ); + } + } + +} + +//-------------------------------------------------------------------------- +// Returns true if shape is intersecting a water surface (roughly) +//-------------------------------------------------------------------------- +bool RigidShape::collidingWithWater( Point3F &waterHeight ) +{ + Point3F curPos = getPosition(); + + F32 height = mFabs( mObjBox.maxExtents.z - mObjBox.minExtents.z ); + + RayInfo rInfo; + if( gClientContainer.castRay( curPos + Point3F(0.0, 0.0, height), curPos, WaterObjectType, &rInfo) ) + { + waterHeight = rInfo.point; + return true; + } + + return false; +} + +void RigidShape::setEnergyLevel(F32 energy) +{ + Parent::setEnergyLevel(energy); + setMaskBits(EnergyMask); +} + +void RigidShape::prepBatchRender( SceneState *state, S32 mountedImageIndex ) +{ + Parent::prepBatchRender( state, mountedImageIndex ); + + if ( !gShowBoundingBox ) + return; + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &RigidShape::_renderMassAndContacts ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); +} + +void RigidShape::_renderMassAndContacts( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + // Box for the center of Mass + GFXStateBlockDesc desc; + desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setZReadWrite(false); + desc.fillMode = GFXFillWireframe; + + GFX->getDrawUtil()->drawCube( desc, Point3F(0.1f,0.1f,0.1f), mDataBlock->massCenter, ColorI(255, 255, 255), &mRenderObjToWorld ); + + // Collision points... + for (int i = 0; i < mCollisionList.getCount(); i++) + { + const Collision& collision = mCollisionList[i]; + GFX->getDrawUtil()->drawCube( desc, Point3F(0.05f,0.05f,0.05f), collision.point, ColorI(0, 0, 255) ); + } + + // Render the normals as one big batch... + PrimBuild::begin(GFXLineList, mCollisionList.getCount() * 2); + for (int i = 0; i < mCollisionList.getCount(); i++) + { + + const Collision& collision = mCollisionList[i]; + PrimBuild::color3f(1, 1, 1); + PrimBuild::vertex3fv(collision.point); + PrimBuild::vertex3fv(collision.point + collision.normal * 0.05f); + } + PrimBuild::end(); + + // Build and render the collision polylist which is returned + // in the server's world space. + ClippedPolyList polyList; + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(getWorldBox().minExtents,VectorF(-1,0,0)); + polyList.mPlaneList[1].set(getWorldBox().minExtents,VectorF(0,-1,0)); + polyList.mPlaneList[2].set(getWorldBox().minExtents,VectorF(0,0,-1)); + polyList.mPlaneList[3].set(getWorldBox().maxExtents,VectorF(1,0,0)); + polyList.mPlaneList[4].set(getWorldBox().maxExtents,VectorF(0,1,0)); + polyList.mPlaneList[5].set(getWorldBox().maxExtents,VectorF(0,0,1)); + Box3F dummyBox; + SphereF dummySphere; + buildPolyList(&polyList, dummyBox, dummySphere); + //polyList.render(); +} + +void RigidShape::reset() +{ + mRigid.clearForces(); + mRigid.setAtRest(); +} + +void RigidShape::freezeSim(bool frozen) +{ + mDisableMove = frozen; + setMaskBits(FreezeMask); +} + +ConsoleMethod(RigidShape, reset, void, 2, 2, "") +{ + object->reset(); +} + +ConsoleMethod(RigidShape, freezeSim, void, 3, 3, "") +{ + object->freezeSim(dAtob(argv[2])); +} diff --git a/T3D/rigidShape.h b/T3D/rigidShape.h new file mode 100644 index 0000000..07a8820 --- /dev/null +++ b/T3D/rigidShape.h @@ -0,0 +1,293 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RIGIDSHAPE_H_ +#define _RIGIDSHAPE_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif +#ifndef _RIGID_H_ +#include "T3D/rigid.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +#include "sfx/sfxSystem.h" +#include "T3D/fx/particleEmitter.h" + +class ParticleEmitter; +class ParticleEmitterData; +class ClippedPolyList; + +//---------------------------------------------------------------------------- + +class RigidShapeData : public ShapeBaseData +{ + typedef ShapeBaseData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + + struct Body + { + enum Sounds + { + SoftImpactSound, + HardImpactSound, + MaxSounds, + }; + SFXProfile* sound[MaxSounds]; + F32 restitution; + F32 friction; + } body; + + enum RigidShapeConsts + { + VC_NUM_DUST_EMITTERS = 1, + VC_NUM_BUBBLE_EMITTERS = 1, + VC_NUM_SPLASH_EMITTERS = 2, + VC_BUBBLE_EMITTER = VC_NUM_BUBBLE_EMITTERS, + }; + + enum Sounds + { + ExitWater, + ImpactSoft, + ImpactMedium, + ImpactHard, + Wake, + MaxSounds + }; + SFXProfile* waterSound[MaxSounds]; + + F32 exitSplashSoundVel; + F32 softSplashSoundVel; + F32 medSplashSoundVel; + F32 hardSplashSoundVel; + + F32 minImpactSpeed; + F32 softImpactSpeed; + F32 hardImpactSpeed; + F32 minRollSpeed; + + bool cameraRoll; ///< Roll the 3rd party camera + F32 cameraLag; ///< Amount of camera lag (lag += car velocity * lag) + F32 cameraDecay; ///< Rate at which camera returns to target pos. + F32 cameraOffset; ///< Vertical offset + + F32 minDrag; + F32 maxDrag; + S32 integration; ///< # of physics steps per tick + F32 collisionTol; ///< Collision distance tolerance + F32 contactTol; ///< Contact velocity tolerance + Point3F massCenter; ///< Center of mass for rigid body + Point3F massBox; ///< Size of inertial box + + ParticleEmitterData * dustEmitter; + S32 dustID; + F32 triggerDustHeight; ///< height shape has to be under to kick up dust + F32 dustHeight; ///< dust height above ground + + ParticleEmitterData* splashEmitterList[VC_NUM_SPLASH_EMITTERS]; + S32 splashEmitterIDList[VC_NUM_SPLASH_EMITTERS]; + F32 splashFreqMod; + F32 splashVelEpsilon; + + + F32 dragForce; + F32 vertFactor; + + F32 normalForce; + F32 restorativeForce; + F32 rollForce; + F32 pitchForce; + + ParticleEmitterData * dustTrailEmitter; + S32 dustTrailID; + Point3F dustTrailOffset; + F32 triggerTrailHeight; + F32 dustTrailFreqMod; + + //-------------------------------------- load set variables + + public: + RigidShapeData(); + ~RigidShapeData(); + + static void initPersistFields(); + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, String &errorStr); + + DECLARE_CONOBJECT(RigidShapeData); + +}; + + +//---------------------------------------------------------------------------- + +class RigidShape: public ShapeBase +{ + typedef ShapeBase Parent; + + private: + RigidShapeData* mDataBlock; + ParticleEmitter * mDustTrailEmitter; + + protected: + enum CollisionFaceFlags + { + BodyCollision = BIT(0), + WheelCollision = BIT(1), + }; + enum MaskBits { + PositionMask = Parent::NextFreeMask << 0, + EnergyMask = Parent::NextFreeMask << 1, + FreezeMask = Parent::NextFreeMask << 2, + NextFreeMask = Parent::NextFreeMask << 3 + }; + + void updateDustTrail( F32 dt ); + + + struct StateDelta + { + Move move; ///< Last move from server + F32 dt; ///< Last interpolation time + // Interpolation data + Point3F pos; + Point3F posVec; + QuatF rot[2]; + // Warp data + S32 warpTicks; ///< Number of ticks to warp + S32 warpCount; ///< Current pos in warp + Point3F warpOffset; + QuatF warpRot[2]; + // + Point3F cameraOffset; + Point3F cameraVec; + Point3F cameraRot; + Point3F cameraRotVec; + }; + + StateDelta mDelta; + S32 mPredictionCount; ///< Number of ticks to predict + bool inLiquid; + + Point3F mCameraOffset; ///< 3rd person camera + + // Rigid Body + bool mDisableMove; + + CollisionList mCollisionList; + CollisionList mContacts; + Rigid mRigid; + ShapeBaseConvex mConvex; + int restCount; + + ParticleEmitter *mDustEmitterList[RigidShapeData::VC_NUM_DUST_EMITTERS]; + ParticleEmitter *mSplashEmitterList[RigidShapeData::VC_NUM_SPLASH_EMITTERS]; + + GFXStateBlockRef mSolidSB; + + // + bool onNewDataBlock(GameBaseData* dptr); + void updatePos(F32 dt); + bool updateCollision(F32 dt); + bool resolveCollision(Rigid& ns,CollisionList& cList); + bool resolveContacts(Rigid& ns,CollisionList& cList,F32 dt); + bool resolveDisplacement(Rigid& ns,CollisionState *state,F32 dt); + bool findContacts(Rigid& ns,CollisionList& cList); + void checkTriggers(); + static void findCallback(SceneObject* obj,void * key); + + void setPosition(const Point3F& pos,const QuatF& rot); + void setRenderPosition(const Point3F& pos,const QuatF& rot); + void setTransform(const MatrixF& mat); + +// virtual bool collideBody(const MatrixF& mat,Collision* info) = 0; + void updateMove(const Move* move); + + void writePacketData(GameConnection * conn, BitStream *stream); + void readPacketData (GameConnection * conn, BitStream *stream); + + void updateLiftoffDust( F32 dt ); + + void updateWorkingCollisionSet(const U32 mask); + U32 getCollisionMask(); + + void updateFroth( F32 dt ); + bool collidingWithWater( Point3F &waterHeight ); + + void _renderMassAndContacts( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + void updateForces(F32); + +public: + // Test code... + static ClippedPolyList* sPolyList; + + // + RigidShape(); + ~RigidShape(); + + static void initPersistFields(); + void processTick(const Move *move); + bool onAdd(); + void onRemove(); + + /// Interpolates between move ticks @see processTick + /// @param dt Change in time between the last call and this call to the function + void interpolateTick(F32 dt); + void advanceTime(F32 dt); + + /// Disables collisions for this shape + void disableCollision(); + + /// Enables collisions for this shape + void enableCollision(); + + /// Returns the velocity of the shape + Point3F getVelocity() const; + + void setEnergyLevel(F32 energy); + + void prepBatchRender( SceneState *state, S32 mountedImageIndex ); + + // xgalaxy cool hacks + void reset(); + void freezeSim(bool frozen); + + ///@name Rigid body methods + ///@{ + + /// This method will get the velocity of the object, taking into account + /// angular velocity. + /// @param r Point on the object you want the velocity of, relative to Center of Mass + /// @param vel Velocity (out) + void getVelocity(const Point3F& r, Point3F* vel); + + /// Applies an impulse force + /// @param r Point on the object to apply impulse to, r is relative to Center of Mass + /// @param impulse Impulse vector to apply. + void applyImpulse(const Point3F &r, const Point3F &impulse); + + void getCameraParameters(F32 *min, F32* max, Point3F* offset, MatrixF* rot); + void getCameraTransform(F32* pos, MatrixF* mat); + ///@} + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + DECLARE_CONOBJECT(RigidShape); +}; + + +#endif diff --git a/T3D/sceneComponent/T3DSceneClient.cpp b/T3D/sceneComponent/T3DSceneClient.cpp new file mode 100644 index 0000000..36f0a3c --- /dev/null +++ b/T3D/sceneComponent/T3DSceneClient.cpp @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3DSceneClient.h" + +//--------------------------------------------------- +// T3DSceneClient +//--------------------------------------------------- + +void T3DSceneClient::setSceneGroupName(const char * name) +{ + _sceneGroupName = StringTable->insert(name); + if (getOwner() != NULL) + { + if (_sceneGroup != NULL) + _sceneGroup->RemoveClientObject(this); + _sceneGroup = NULL; + + ValueWrapperInterface * iface = getInterface >("sceneComponent", _sceneGroupName); + if (iface != NULL) + { + _sceneGroup = iface->get(); + _sceneGroup->AddSceneClient(this); + } + } +} + +bool T3DSceneClient::onComponentRegister(SimComponent * owner) +{ + if (!Parent::onComponentRegister(owner)) + return false; + + // lookup scene group and add ourself + setSceneGroupName(_sceneGroupName); + + if (_sceneGroupName != NULL && dStricmp(_sceneGroupName, "none") && _sceneGroup == NULL) + // tried to add ourself to a scene group but failed, fail to add component + return false; + + return true; +} + +void T3DSceneClient::registerInterfaces(SimComponent * owner) +{ + Parent::registerInterfaces(owner); + registerCachedInterface("sceneClient", NULL, this, new ValueWrapperInterface()); +} + +//--------------------------------------------------- +// T3DSceneClient +//--------------------------------------------------- + +Box3F T3DSolidSceneClient::getWorldBox() +{ + MatrixF mat = getTransform(); + Box3F box = _objectBox->get(); + mat.mul(box); + return box; +} + +const MatrixF & T3DSolidSceneClient::getTransform() +{ + if (_transform != NULL) + return _transform->getWorldMatrix(); + else if (getSceneGroup() != NULL) + return getSceneGroup()->getTransform3D()->getWorldMatrix(); + else + return MatrixF::smIdentity; +} + +void T3DSolidSceneClient::setTransform3D(Transform3D * transform) +{ + if (_transform != NULL) + _transform->setDirtyListener(NULL); + _transform = transform; + + _transform->setDirtyListener(this); + OnTransformDirty(); +} + +void T3DSolidSceneClient::OnTransformDirty() +{ + // TODO: need a way to skip this...a flag, but we don't want to add a bool just for that + // reason we might want to skip it is if we have a renderable that orbits an object but always + // stays within object box. Want to be able to use that info to skip object box updates. + if (getSceneGroup() != NULL) + getSceneGroup()->setDirtyObjectBox(true); +} diff --git a/T3D/sceneComponent/T3DSceneClient.h b/T3D/sceneComponent/T3DSceneClient.h new file mode 100644 index 0000000..b938d69 --- /dev/null +++ b/T3D/sceneComponent/T3DSceneClient.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3DSCENECLIENT_H_ +#define _T3DSCENECLIENT_H_ + +#include "component/simComponent.h" +#include "T3DSceneComponent.h" + +class T3DSceneClient : public SimComponent +{ + typedef SimComponent Parent; + +public: + T3DSceneClient() + { + _nextClient = NULL; + _sceneGroup = NULL; + _sceneGroupName = NULL; + _sceneClientName = NULL; + } + + T3DSceneClient * getNextSceneClient() { return _nextClient; } + // TODO: internal + void setNextSceneClient(T3DSceneClient * client) { _nextClient = client; } + + T3DSceneComponent * getSceneGroup() { return _sceneGroup; } + + StringTableEntry getSceneGroupName() { return _sceneGroupName; } + void setSceneGroupName(const char * name); + + StringTableEntry getSceneClientName() { return _sceneClientName; } + void setSceneClientName(const char * name) { _sceneClientName = StringTable->insert(name); } + +protected: + + bool onComponentRegister(SimComponent * owner); + void registerInterfaces(SimComponent * owner); + + T3DSceneClient * _nextClient; + T3DSceneComponent * _sceneGroup; + StringTableEntry _sceneGroupName; + StringTableEntry _sceneClientName; +}; + +class T3DSolidSceneClient : public T3DSceneClient, public ISolid3D, public Transform3D::IDirtyListener +{ +public: + + T3DSolidSceneClient() + { + _transform = NULL; + _objectBox = new ValueWrapperInterface(); + } + + Box3F getObjectBox() { return _objectBox->get(); } + void setObjectBox(const Box3F & box) { _objectBox->set(box); } + Box3F getWorldBox(); + const MatrixF & getTransform(); + Transform3D * getTransform3D() { return _transform; } + + void OnTransformDirty(); + +protected: + + + // TODO: internal + void setTransform3D(Transform3D * transform); + + Transform3D * _transform; + ValueWrapperInterface * _objectBox; +}; + +#endif // #ifndef _T3DSCENECLIENT_H_ diff --git a/T3D/sceneComponent/T3DSceneComponent.cpp b/T3D/sceneComponent/T3DSceneComponent.cpp new file mode 100644 index 0000000..de9405b --- /dev/null +++ b/T3D/sceneComponent/T3DSceneComponent.cpp @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3DSceneComponent.h" +#include "T3DSceneClient.h" + +void T3DSceneComponent::setSceneGroup(const char * sceneGroup) +{ + AssertFatal(getOwner()==NULL, "Changing scene group name after registration will have no effect."); + if (sceneGroup == NULL) + sceneGroup = StringTable->insert(""); + _sceneGroup = StringTable->insert(sceneGroup); +} + +void T3DSceneComponent::setParentTransformName(const char * name) +{ + _parentTransformName = StringTable->insert(name); + if (getOwner() != NULL) + { + Transform3D * old = _transform->getParentTransform(); + ValueWrapperInterface * iface = NULL; + if (_parentTransformName != NULL) + iface = getInterface >("transform3D", _parentTransformName); + _transform->setParentTransform(iface == NULL ? NULL : iface->get()); + if (_transform->getParentTransform() != old) + setDirtyWorldBox(true); + } +} + +void T3DSceneComponent::setObjectType(U32 objectTypeMask) +{ + _objectType = objectTypeMask; + setUseOwnerObjectType(true); +} + +void T3DSceneComponent::setDirtyObjectBox(bool val) +{ + _SetFlag(T3DSceneComponent::DirtyObjectBox, val); + if (val && !isObjectBoxLocked()) + _ComputeObjectBox(); +} + +void T3DSceneComponent::setDirtyWorldBox(bool val) +{ + _SetFlag(T3DSceneComponent::DirtyWorldBox, val); + if (val && !isWorldBoxLocked()) + _UpdateWorldBox(); +} + +void T3DSceneComponent::setObjectBoxLocked(bool val) +{ + _SetFlag(T3DSceneComponent::LockObjectBox, val); + if (!val && isDirtyObjectBox()) + _ComputeObjectBox(); +} + +void T3DSceneComponent::setWorldBoxLocked(bool val) +{ + _SetFlag(T3DSceneComponent::LockWorldBox, val); + if (!val && isDirtyWorldBox()) + _UpdateWorldBox(); +} + +void T3DSceneComponent::setPosition(const Point3F & pos) +{ + _transform->setPosition(pos); + setDirtyWorldBox(true); +} + +void T3DSceneComponent::setRotation(const QuatF & rotation) +{ + _transform->setRotation(rotation); + setDirtyWorldBox(true); +} + +void T3DSceneComponent::setScale(const Point3F & scale) +{ + _transform->setScale (scale); + Parent::setScale(scale); + setDirtyWorldBox(true); +} + +void T3DSceneComponent::setTransform3D(Transform3D * transform) +{ + if (transform == NULL) + // never let it be null + return; + if (_transform != NULL) + delete _transform; + _transform = transform; + _transform->setDirtyListener(this); +} + +void T3DSceneComponent::setTransform(const MatrixF & mat) +{ + _transform->setLocalMatrix(mat); + Parent::setTransform(mat); + setDirtyWorldBox(true); +} + +Box3F T3DSceneComponent::getObjectBox() +{ + if (DirtyObjectBox && !LockObjectBox) + _ComputeObjectBox(); + + return _objectBox->get(); +} + +Box3F T3DSceneComponent::getWorldBox() +{ + if (DirtyWorldBox && !LockWorldBox) + _UpdateWorldBox(); + + MatrixF mat = getTransform3D()->getWorldMatrix(); + Box3F box = getObjectBox(); + + mat.mul(box); + return box; +} + +void T3DSceneComponent::Render() +{ + if (_sceneClientList == NULL) + return; + + PROFILE_SCOPE(T3DSceneComponent_Render); + + GFX->multWorld(_transform->getWorldMatrix()); + + if (doRenderObjectBounds()) + { + // TODO + } + if (doRenderWorldBounds()) + { + // TODO + } + + for (T3DSceneClient * walk = _sceneClientList; walk != NULL; walk = walk->getNextSceneClient()) + { + IRenderable3D * render = dynamic_cast(walk); + if (render != NULL) + render->Render(); + } + + GFX->popWorldMatrix(); +} + +void T3DSceneComponent::AddSceneClient(T3DSceneClient * sceneClient) +{ + AssertFatal(sceneClient,"Cannot add a NULL scene client"); + + // add to the front of the list + sceneClient->setNextSceneClient(_sceneClientList); + _sceneClientList = sceneClient; + + // extend object box + ISolid3D * solid = dynamic_cast(sceneClient); + if (solid != NULL) + { + if (isObjectBoxLocked()) + setDirtyObjectBox(true); + else + { + Box3F box = solid->getObjectBox(); + if (solid->getTransform3D() != NULL) + { + MatrixF mat; + solid->getTransform3D()->getObjectMatrix(mat, true); + mat.mul(box); + } + box.extend(_objectBox->get().min); + box.extend(_objectBox->get().max); + _objectBox->set(box); + } + + // policy is that we become parent transform + if (solid->getTransform3D() != NULL && solid->getTransform3D()->isChildOf(_transform, true)) + solid->getTransform3D()->setParentTransform(_transform); + } +} + +void T3DSceneComponent::RemoveClientObject(T3DSceneClient * sceneClient) +{ + AssertFatal(sceneClient != NULL, "Removing null scene client"); + if (sceneClient == NULL) + return; + if (_sceneClientList == sceneClient) + _sceneClientList = _sceneClientList->getNextSceneClient(); + else + { + T3DSceneClient * walk = _sceneClientList; + while (walk->getNextSceneClient() != sceneClient && walk != NULL) + walk = walk->getNextSceneClient(); + if (walk != NULL) + walk->setNextSceneClient(sceneClient->getNextSceneClient()); + } + + if (dynamic_cast(sceneClient)) + setDirtyObjectBox(true); +} + +bool T3DSceneComponent::onComponentRegister(SimComponent * owner) +{ + if (!Parent::onComponentRegister(owner) || !registerObject()) + return false; + + // Note: was added to scene graph in register object + + setTransform3D(_transform); + setParentTransformName(_parentTransformName); + + return true; +} + +void T3DSceneComponent::onComponentUnRegister() +{ + _sceneClientList = NULL; + Parent::onComponentUnRegister(); +} + +void T3DSceneComponent::registerInterfaces(SimComponent * owner) +{ + Parent::registerInterfaces(owner); + + // TODO: need to figure out the best way to wrap these + // we don't need to track these interfaces ourselves -- just create them here + ComponentInterface * sceneComponent = new ComponentInterface(); + ValueWrapperInterface * transform = new ValueWrapperInterface(_transform); + + registerCachedInterface("sceneComponent", _sceneGroup, owner, sceneComponent); + registerCachedInterface("transform3D", _sceneGroup, owner, transform); + registerCachedInterface("box", _sceneGroup, this, _objectBox); +} + +void T3DSceneComponent::_ComputeObjectBox() +{ + _objectBox->set(Box3F(10E30f, 10E30f, 10E30f, -10E30f, -10E30f, -10E30f)); + bool gotone = false; + for (T3DSceneClient * walk = _sceneClientList; walk != NULL; walk = walk->getNextSceneClient()) + { + ISolid3D * solid = dynamic_cast(walk); + if (solid == NULL) + continue; + + Box3F box = solid->getObjectBox(); + if (solid->getTransform3D() != NULL) + { + MatrixF mat; + solid->getTransform3D()->getObjectMatrix(mat, true); + mat.mul(box); + } + box.extend(_objectBox->get().min); + box.extend(_objectBox->get().max); + _objectBox->set(box); + gotone = true; + } + if (!gotone) + _objectBox->set(Box3F()); + + setDirtyObjectBox(false); + setDirtyWorldBox(true); +} + +void T3DSceneComponent::_UpdateWorldBox() +{ + setDirtyWorldBox(false); + // TODO: + // T3DSceneGraph.Instance.UpdateObject(this); +} diff --git a/T3D/sceneComponent/T3DSceneComponent.h b/T3D/sceneComponent/T3DSceneComponent.h new file mode 100644 index 0000000..d9328f0 --- /dev/null +++ b/T3D/sceneComponent/T3DSceneComponent.h @@ -0,0 +1,161 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3DSCENECOMPONENT_H_ +#define _T3DSCENECOMPONENT_H_ + +#include "T3DTransform.h" +#include "component/simComponent.h" +#include "sceneGraph/sceneobject.h" + +class T3DSceneClient; + +class IRenderable3D +{ +public: + // TODO: what parameters to use? + virtual void Render() = 0; +}; + +class ISolid3D +{ +public: + virtual Box3F getObjectBox() = 0; + virtual Transform3D * getTransform3D() = 0; +}; + +class T3DSceneComponent : public SceneObject, public Transform3D::IDirtyListener +{ + typedef SceneObject Parent; + +public: + + T3DSceneComponent() + { + _transform = new Transform3DInPlace(); + _objectBox = new ValueWrapperInterface(); + _flags = T3DSceneComponent::Visible | T3DSceneComponent::DirtyObjectBox | T3DSceneComponent::DirtyWorldBox | T3DSceneComponent::UseOwnerObjectType; + _visibilityLevel = 1.0f; + _objectType = 0; + _sceneGroup = NULL; + _parentTransformName = NULL; + setSceneGroup(NULL); + } + + StringTableEntry getSceneGroup() const { return _sceneGroup; } + void setSceneGroup(const char * sceneGroup); + + StringTableEntry getParentTransformName() const { return _parentTransformName; } + void setParentTransformName(const char * name); + + U32 getObjectType() const { return _objectType; } // TODO: use owner if useowner set + void setObjectType(U32 objectTypeMask); + + void setUseOwnerObjectType(bool val) { _SetFlag(T3DSceneComponent::UseOwnerObjectType, val); } + bool useOwnerObjectType() const { return _TestFlag(T3DSceneComponent::UseOwnerObjectType); } + + bool isDirtyObjectBox() const { return _TestFlag(T3DSceneComponent::DirtyObjectBox); } + void setDirtyObjectBox(bool val); + + bool isDirtyWorldBox() const { return _TestFlag(T3DSceneComponent::DirtyWorldBox); } + void setDirtyWorldBox(bool val); + + bool isObjectBoxLocked() const { return _TestFlag(T3DSceneComponent::LockObjectBox); } + void setObjectBoxLocked(bool val); + + bool isWorldBoxLocked() const { return _TestFlag(T3DSceneComponent::LockWorldBox); } + void setWorldBoxLocked(bool val); + + bool doRenderObjectBounds() const { return _TestFlag(T3DSceneComponent::RenderObjectBounds); } + void setRenderObjectBounds(bool val) { _SetFlag(T3DSceneComponent::RenderObjectBounds, val); } + + bool doRenderWorldBounds() const { return _TestFlag(T3DSceneComponent::RenderWorldBounds); } + void setRenderWorldBounds(bool val) { _SetFlag(T3DSceneComponent::RenderWorldBounds, val); } + + bool doRenderSubBounds() const { return _TestFlag(T3DSceneComponent::RenderSubBounds); } + void setRenderSubBounds(bool val) { _SetFlag(T3DSceneComponent::RenderSubBounds, val); } + + bool isVisible() const { return _TestFlag(T3DSceneComponent::Visible); } + void setVisible(bool val) { _SetFlag(T3DSceneComponent::Visible, val); } + + float getVisibilityLevel() const { return _visibilityLevel; } + void setVisibilityLevel(float val) { _visibilityLevel = val; } + + Point3F getPosition() const { return _transform->getPosition(); } + void setPosition(const Point3F & pos); + + QuatF getRotation() const { return _transform->getRotation(); } + void setRotation(const QuatF & rotation); + + Point3F getScale() const { return _transform->getScale(); } + void setScale(const Point3F & scale); + + Transform3D * getTransform3D(){ return _transform; } + + void setTransform(const MatrixF & mat); + + Box3F getObjectBox(); + Box3F getWorldBox(); + + T3DSceneClient * getSceneClientList() const { return _sceneClientList; } + + void Render(); + + void AddSceneClient(T3DSceneClient * sceneClient); + void RemoveClientObject(T3DSceneClient * sceneClient); + + void OnTransformDirty() { setDirtyWorldBox(true); } + +protected: + + bool onComponentRegister(SimComponent * owner); + void onComponentUnRegister(); + + void registerInterfaces(SimComponent * owner); + + void _ComputeObjectBox(); + void _UpdateWorldBox(); + void setTransform3D(Transform3D * transform); + + bool _TestFlag(U32 test) const + { + return (_flags & test) != T3DSceneComponent::None; + } + + void _SetFlag(U32 test, bool value) + { + if (value) + _flags |= test; + else + _flags &= ~test; + } + + enum SceneFlags + { + None = 0, + Visible = 1 << 0, + DirtyObjectBox = 1 << 1, + DirtyWorldBox = 1 << 2, + LockObjectBox = 1 << 3, + LockWorldBox = 1 << 4, + UseOwnerObjectType = 1 << 5, + RenderDebug = 1 << 6, + RenderObjectBounds = 1 << 7, + RenderWorldBounds = 1 << 8, + RenderSubBounds = 1 << 9, + LastFlag = 1 << 9 + }; + + Transform3D * _transform; + T3DSceneClient * _sceneClientList; + ValueWrapperInterface * _objectBox; + U32 _flags; + float _visibilityLevel; + U32 _objectType; + StringTableEntry _sceneGroup; + StringTableEntry _parentTransformName ; +}; + +#endif // #ifndef _T3DSCENECOMPONENT_H_ diff --git a/T3D/sceneComponent/T3DTransform.cpp b/T3D/sceneComponent/T3DTransform.cpp new file mode 100644 index 0000000..921772a --- /dev/null +++ b/T3D/sceneComponent/T3DTransform.cpp @@ -0,0 +1,369 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3DTransform.h" +#include "math/mPoint.h" +#include "math/mQuat.h" + +//--------------------------------------------------------- +// T3DTransform +//--------------------------------------------------------- + +void Transform3D::setParentTransform(Transform3D * parent) +{ + if (_parentTransform == parent) + return; + _flags |= Transform3D::ParentDirty; + _parentTransform = parent; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +bool Transform3D::hasObjectScale() const +{ + if (hasLocalScale()) + return true; + + // check all parent transforms except last for scale + Transform3D * walk = _parentTransform; + while (walk != NULL && walk->_parentTransform != NULL) + { + if (walk->hasLocalScale()) + return true; + walk = walk->getParentTransform(); + } + return false; +} + +bool Transform3D::hasWorldScale() const +{ + if (hasLocalScale()) + return true; + + // check all parent transforms for scale + Transform3D * walk = _parentTransform; + while (walk != NULL) + { + if (walk->hasLocalScale()) + return true; + walk = walk->getParentTransform(); + } + return false; +} + +void Transform3D::setWorldMatrix(const MatrixF & world) +{ + if (_parentTransform != NULL) + { + MatrixF parentMatrix; + _parentTransform->getWorldMatrix(parentMatrix, true); + MatrixF parentMatrixInv = parentMatrix; + parentMatrixInv.inverse(); + MatrixF localMat; + localMat = parentMatrixInv * world; + setLocalMatrix(localMat); + } + else + setLocalMatrix(world); +} + +void Transform3D::setObjectMatrix(const MatrixF & objMatrix) +{ + if (_parentTransform != NULL) + { + MatrixF parentMatrix; + _parentTransform->getObjectMatrix(parentMatrix, true); + MatrixF parentMatrixInv = parentMatrix; + parentMatrixInv.inverse(); + MatrixF localMat; + localMat = parentMatrixInv * objMatrix; + setLocalMatrix(localMat); + } + else + setLocalMatrix(objMatrix); +} + +bool Transform3D::isChildOf(Transform3D * parent, bool recursive) const +{ + if (_parentTransform != NULL) + { + if (_parentTransform == parent) + return true; + else if (recursive) + return _parentTransform->isChildOf(parent, true); + else + return false; + } + else + { + return false; + } +} + +//--------------------------------------------------------- +// Transform3DInPlace +//--------------------------------------------------------- + +Point3F Transform3DInPlace::getPosition() const +{ + return _position; +} + +void Transform3DInPlace::setPosition(const Point3F & position) +{ + _position = position; + _flags |= Transform3D::LocalPositionDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +QuatF Transform3DInPlace::getRotation() const +{ + return _rotation; +} + +void Transform3DInPlace::setRotation(const QuatF & rotation) +{ + _rotation = rotation; + _flags |= Transform3D::LocalRotationDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +Point3F Transform3DInPlace::getScale() const +{ + return _scale; +} + +void Transform3DInPlace::setScale(const Point3F & scale) +{ + _scale = scale; + _flags |= Transform3D::LocalScaleDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +void Transform3DInPlace::getWorldMatrix(MatrixF & worldMat, bool includeLocalScale) const +{ + if (_parentTransform == NULL) + getLocalMatrix(worldMat, includeLocalScale); + else + { + MatrixF localMat, parentMat; + getLocalMatrix(localMat, includeLocalScale); + _parentTransform->getWorldMatrix(parentMat, true); + worldMat = parentMat * localMat; + } +} + +void Transform3DInPlace::getObjectMatrix(MatrixF & objectMat, bool includeLocalScale) const +{ + if (_parentTransform == NULL) + objectMat = MatrixF::smIdentity; + else if (_parentTransform->getParentTransform() == NULL) + getLocalMatrix(objectMat, includeLocalScale); + else + { + MatrixF localMat, parentMat; + getLocalMatrix(localMat, includeLocalScale); + _parentTransform->getObjectMatrix(parentMat, true); + objectMat = parentMat * localMat; + } +} + +void Transform3DInPlace::getLocalMatrix(MatrixF & localMat, bool includeLocalScale) const +{ + _rotation.setMatrix(&localMat); + localMat.setColumn(3,_position); + if (includeLocalScale) + localMat.scale(_scale); +} + +void Transform3DInPlace::setLocalMatrix(const MatrixF & localMat) +{ + _rotation.set(localMat); + _position = localMat.getPosition(); + _scale = localMat.getScale(); + _flags |= Transform3D::LocalScaleDirty | Transform3D::LocalRotationDirty | Transform3D::LocalPositionDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +//--------------------------------------------------------- +// TSTransform3D +//--------------------------------------------------------- + +TSTransform3D::TSTransform3D(TSShapeInstance * si, S32 nodeIndex) +{ + _shapeInstance = si; + _nodeIndex = nodeIndex; + AssertFatal(_nodeIndex >= 0 && _nodeIndex < _shapeInstance->mNodeTransforms.size(), "TSTransform3D nodeIndex out of range"); +} + +Point3F TSTransform3D::getPosition() const +{ + if (!doHandleLocal()) + { + MatrixF mat; + return getTSLocal(mat).getPosition(); + } + return _position; +} + +void TSTransform3D::setPosition(const Point3F & position) +{ + setHandleLocal(true); + _position = position; + _flags |= Transform3D::LocalPositionDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +QuatF TSTransform3D::getRotation() const +{ + if (!doHandleLocal()) + { + MatrixF mat; + return QuatF(getTSLocal(mat)); + } + return _rotation; +} +void TSTransform3D::setRotation(const QuatF & rotation) +{ + setHandleLocal(true); + _rotation = rotation; + _flags |= Transform3D::LocalRotationDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +Point3F TSTransform3D::getScale() const +{ + if (!doHandleLocal()) + { + MatrixF mat; + return getTSLocal(mat).getScale(); + } + return _scale; +} +void TSTransform3D::setScale(const Point3F & scale) +{ + setHandleLocal(true); + _scale = scale; + _flags |= Transform3D::LocalScaleDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +void TSTransform3D::getWorldMatrix(MatrixF & worldMat, bool includeLocalScale) const +{ + _shapeInstance->animate(); + if (_parentTransform == NULL) + { + worldMat = _shapeInstance->mNodeTransforms[_nodeIndex]; + } + else + { + MatrixF parentMat; + _parentTransform->getWorldMatrix(parentMat, true); + worldMat = parentMat * _shapeInstance->mNodeTransforms[_nodeIndex]; + } +} + +void TSTransform3D::getObjectMatrix(MatrixF & objectMat, bool includeLocalScale) const +{ + if (_parentTransform == NULL) + objectMat = MatrixF::smIdentity; + else if (_parentTransform->getParentTransform() == NULL) + { + _shapeInstance->animate(); + objectMat = _shapeInstance->mNodeTransforms[_nodeIndex]; + } + else + { + _shapeInstance->animate(); + + MatrixF parentMat; + _parentTransform->getObjectMatrix(parentMat, true); + objectMat = parentMat * _shapeInstance->mNodeTransforms[_nodeIndex]; + } +} + +void TSTransform3D::getLocalMatrix(MatrixF & localMat, bool includeLocalScale) const +{ + if (doHandleLocal()) + { + _rotation.setMatrix(&localMat); + localMat.setPosition(_position); + if (includeLocalScale) + localMat.scale(_scale); + } + else + { + _shapeInstance->animate(); + localMat = _shapeInstance->mNodeTransforms[_nodeIndex]; + if (!includeLocalScale && (_flags & Transform3D::LocalHasScale) != Transform3D::None) + { + // reverse any scale on matrix -- this is a inconvenient, but not a common case + Point3F scale = localMat.getScale(); + scale.x = 1.0f / scale.x; + scale.y = 1.0f / scale.y; + scale.z = 1.0f / scale.z; + localMat.scale(scale); + } + } +} + +void TSTransform3D::setLocalMatrix(const MatrixF & localMatrix) +{ + setHandleLocal(true); + _position = localMatrix.getPosition(); + _rotation.set(localMatrix); + _scale = localMatrix.getScale(); + _flags |= Transform3D::LocalScaleDirty | Transform3D::LocalRotationDirty | Transform3D::LocalPositionDirty; + if (_dirtyListener != NULL) + _dirtyListener->onTransformDirty(); +} + +MatrixF & TSTransform3D::getTSLocal(MatrixF & mat) const +{ + _shapeInstance->animate(); + + // if node has no parent, easy enough to just grab the matrix of the node + int parentIdx = _shapeInstance->getShape()->nodes[_nodeIndex].parentIndex; + if (parentIdx < 0) + { + return _shapeInstance->mNodeTransforms[_nodeIndex]; + } + + // has parent, local is transform from this node to parent so get local matrix the hard way + MatrixF parentMat = _shapeInstance->mNodeTransforms[parentIdx]; + parentMat.inverse(); + mat = parentMat * _shapeInstance->mNodeTransforms[_nodeIndex]; + return mat; +} + +void TSTransform3D::setHandleLocal(bool handleLocal) +{ + if (handleLocal == doHandleLocal()) + return; + + if (handleLocal) + { + _position = getPosition(); + _rotation = getRotation(); + _scale = getScale(); + _shapeInstance->setNodeAnimationState(_nodeIndex, 0, this); + } + else + _shapeInstance->setNodeAnimationState(_nodeIndex, 0); + _flags ^= TSTransform3D::HandleLocal; +} + +void TSTransform3D::setNodeTransform(TSShapeInstance * si, S32 nodeIndex, MatrixF & localTransform) +{ + AssertFatal(si == _shapeInstance,"TSTransform3D hooked up to wrong shape."); + getLocalMatrix(localTransform, true); +} diff --git a/T3D/sceneComponent/T3DTransform.h b/T3D/sceneComponent/T3DTransform.h new file mode 100644 index 0000000..c79bdfc --- /dev/null +++ b/T3D/sceneComponent/T3DTransform.h @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _T3DTRANSFORM_H_ +#define _T3DTRANSFORM_H_ + +#include "ts/tsShapeInstance.h" +#include "math/mMatrix.h" + +//--------------------------------------------------------- +// T3DTransform +//--------------------------------------------------------- + +class Transform3D +{ +public: + + class IDirtyListener + { + public: + virtual void onTransformDirty() = 0; + }; + + // local/object/world matrix access + + void setWorldMatrix(const MatrixF & world); + void setObjectMatrix(const MatrixF & objMatrix); + virtual void setLocalMatrix(const MatrixF & localMatrix) = 0; + + virtual void getWorldMatrix(MatrixF & worldMatrix, bool includeLocalScale) const = 0; + virtual void getObjectMatrix(MatrixF & objectMatrix, bool includeLocalScale) const = 0; + virtual void getLocalMatrix(MatrixF & localMatrix, bool includeLocalScale) const = 0; + + MatrixF getWorldMatrix() const + { + MatrixF world; + getWorldMatrix(world, true); + return world; + } + + MatrixF getObjectMatrix() const + { + MatrixF objMatrix; + getObjectMatrix(objMatrix, true); + return objMatrix; + } + + MatrixF getLocalMatrix() const + { + MatrixF loc; + getLocalMatrix(loc, true); + return loc; + } + + // local position/rotation/scale + + virtual Point3F getPosition() const = 0; + virtual void setPosition(const Point3F & position) = 0; + + virtual QuatF getRotation() const = 0; + virtual void setRotation(const QuatF & rotation) = 0; + + virtual Point3F getScale() const = 0; + virtual void setScale(const Point3F & scale) = 0; + + // scale tests + + bool hasLocalScale() const + { + return (_flags & Transform3D::LocalHasScale) != Transform3D::None; + } + + bool hasObjectScale() const; + bool hasWorldScale() const; + + // parent/child methods + + Transform3D * getParentTransform() const + { + return _parentTransform; + } + + void setParentTransform(Transform3D * parent); + + IDirtyListener * getDirtyListener() const + { + return _dirtyListener; + } + + void setDirtyListener(IDirtyListener * dirtyListener) + { + _dirtyListener = dirtyListener; + } + + bool isChildOf(Transform3D * parent, bool recursive) const; + +protected: + + enum TransformFlags + { + None = 0, + LocalHasScale = 1 << 0, + LocalPositionDirty = 1 << 1, + LocalRotationDirty = 1 << 2, + LocalScaleDirty = 1 << 3, + LocalDirty = LocalPositionDirty | LocalRotationDirty | LocalScaleDirty, + ParentDirty = 1 << 4, + LastFlag = 1 << 4 + }; + + Transform3D * _parentTransform; + IDirtyListener * _dirtyListener; + U32 _flags; +}; + +//--------------------------------------------------------- +// Transform3DInPlace +//--------------------------------------------------------- + +class Transform3DInPlace : public Transform3D +{ +public: + + Transform3DInPlace() : _position(0,0,0), _rotation(0,0,0,1), _scale(1,1,1) + { + } + + Point3F getPosition() const; + void setPosition(const Point3F & position); + + QuatF getRotation() const; + void setRotation(const QuatF & rotation); + + Point3F getScale() const; + void setScale(const Point3F & scale); + + void getWorldMatrix(MatrixF & worldMat, bool includeLocalScale) const; + void getObjectMatrix(MatrixF & objectMat, bool includeLocalScale) const; + void getLocalMatrix(MatrixF & localMat, bool includeLocalScale) const; + void setLocalMatrix(const MatrixF & localMat); + +protected: + + Point3F _position; + QuatF _rotation; + Point3F _scale; +}; + +//--------------------------------------------------------- +// TSTransform3D +//--------------------------------------------------------- + +class TSTransform3D : public Transform3D, public TSCallback +{ +public: + TSTransform3D(TSShapeInstance * si, S32 nodeIndex); + + Point3F getPosition() const; + void setPosition(const Point3F & position); + + QuatF getRotation() const; + void setRotation(const QuatF & rotation); + + Point3F getScale() const; + void setScale(const Point3F & scale); + + void getWorldMatrix(MatrixF & worldMat, bool includeLocalScale) const; + void getObjectMatrix(MatrixF & objectMat, bool includeLocalScale) const; + void getLocalMatrix(MatrixF & localMat, bool includeLocalScale) const; + void setLocalMatrix(const MatrixF & localMatrix); + + // Define TSCallback interface + void setNodeTransform(TSShapeInstance * si, S32 nodeIndex, MatrixF & localTransform); + +protected: + + enum TSTransformFlags + { + HandleLocal = Transform3D::LastFlag << 1, + LastFlag = Transform3D::LastFlag << 1 + }; + + bool doHandleLocal() const + { + return (_flags & (TransformFlags)TSTransform3D::HandleLocal) != Transform3D::None; + } + + void setHandleLocal(bool handleLocal); + MatrixF & getTSLocal(MatrixF & mat) const; + + TSShapeInstance * _shapeInstance; + int _nodeIndex; + + Point3F _position; + QuatF _rotation; + Point3F _scale; +}; + +#endif // _T3DTRANSFORM_H_ \ No newline at end of file diff --git a/T3D/scopeAlwaysShape.cpp b/T3D/scopeAlwaysShape.cpp new file mode 100644 index 0000000..2233788 --- /dev/null +++ b/T3D/scopeAlwaysShape.cpp @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/staticShape.h" + +class ScopeAlwaysShape : public StaticShape +{ + typedef StaticShape Parent; + + public: + ScopeAlwaysShape(); + static void initPersistFields(); + DECLARE_CONOBJECT(ScopeAlwaysShape); +}; + +ScopeAlwaysShape::ScopeAlwaysShape() +{ + mNetFlags.set(Ghostable|ScopeAlways); + mTypeMask |= ShadowCasterObjectType; +} + +void ScopeAlwaysShape::initPersistFields() +{ + Parent::initPersistFields(); +} + +IMPLEMENT_CO_NETOBJECT_V1(ScopeAlwaysShape); diff --git a/T3D/sfxEmitter.cpp b/T3D/sfxEmitter.cpp new file mode 100644 index 0000000..16d7bd8 --- /dev/null +++ b/T3D/sfxEmitter.cpp @@ -0,0 +1,619 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/sfxEmitter.h" +#include "renderInstance/renderPassManager.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" +//#include "T3D/ambientAudioManager.h" +#include "sceneGraph/sceneState.h" +#include "core/stream/bitStream.h" +#include "sim/netConnection.h" + + +extern bool gEditingMission; + +IMPLEMENT_CO_NETOBJECT_V1(SFXEmitter); + + +SFXEmitter::SFXEmitter() + : SceneObject(), + mSource( NULL ), + mProfile( NULL ), + mLocalProfile( &mDescription ), + mPlayOnAdd( true ) +{ + mTypeMask |= MarkerObjectType; + mNetFlags.set( Ghostable | ScopeAlways ); + + mDescription.mIs3D = true; + mDescription.mIsLooping = true; + mDescription.mIsStreaming = false; + + mLocalProfile._registerSignals(); +} + +SFXEmitter::~SFXEmitter() +{ + mLocalProfile._unregisterSignals(); +} + +void SFXEmitter::initPersistFields() +{ + //[rene 07/04/09] + // This entire profile/local profile split thing back from TGE-days is no good and should be removed. + // The emitter should link to a single SFXProfile and there should be a separate means of creating/editing/managing + // profiles as part of the standard editor toolset (datablock editor?). + // + // The way it is now, it is totally confusing, inconsistent, and difficult to handle in script (example: + // what's the "is3D" supposed to mean? Nothing, if a profile is selected. So how do I determine whether + // a profile is 3D? Hmmm, check for profile, it set, check it's description, if not, check the emitter...). + + addGroup("Media"); + addField("profile", TypeSFXProfilePtr, Offset(mProfile, SFXEmitter)); + addField("fileName", TypeStringFilename, Offset(mLocalProfile.mFilename, SFXEmitter)); + endGroup("Media"); + + addGroup("Sound"); + addField("playOnAdd", TypeBool, Offset(mPlayOnAdd, SFXEmitter)); + addField("isLooping", TypeBool, Offset(mDescription.mIsLooping, SFXEmitter)); + addField("isStreaming", TypeBool, Offset(mDescription.mIsStreaming, SFXEmitter)); + addField("channel", TypeS32, Offset(mDescription.mChannel, SFXEmitter)); + addField("volume", TypeF32, Offset(mDescription.mVolume, SFXEmitter)); + addField("pitch", TypeF32, Offset(mDescription.mPitch, SFXEmitter)); + addField("fadeInTime", TypeF32, Offset(mDescription.mFadeInTime, SFXEmitter)); + addField("fadeOutTime", TypeF32, Offset(mDescription.mFadeOutTime, SFXEmitter)); + endGroup("Sound"); + + addGroup("3D Sound"); + addField("is3D", TypeBool, Offset(mDescription.mIs3D, SFXEmitter)); + addField("referenceDistance", TypeF32, Offset(mDescription.mReferenceDistance, SFXEmitter)); + addField("maxDistance", TypeF32, Offset(mDescription.mMaxDistance, SFXEmitter)); + addField("coneInsideAngle", TypeS32, Offset(mDescription.mConeInsideAngle, SFXEmitter)); + addField("coneOutsideAngle", TypeS32, Offset(mDescription.mConeOutsideAngle, SFXEmitter)); + addField("coneOutsideVolume", TypeF32, Offset(mDescription.mConeOutsideVolume, SFXEmitter)); + endGroup("3D Sound"); + + Parent::initPersistFields(); +} + +U32 SFXEmitter::packUpdate( NetConnection *con, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( con, mask, stream ); + + if ( stream->writeFlag( mask & InitialUpdateMask ) ) + { + // If this is the initial update then all the source + // values are dirty and must be transmitted. + mask |= TransformUpdateMask; + mDirty = AllDirtyMask; + + // Clear the source masks... they are not + // used during an initial update! + mask &= ~AllSourceMasks; + } + + stream->writeFlag( mPlayOnAdd ); + + // transform + if ( stream->writeFlag( mask & TransformUpdateMask ) ) + stream->writeAffineTransform( mObjToWorld ); + + // profile + if ( stream->writeFlag( mDirty.test( Profile ) ) ) + if ( stream->writeFlag( mProfile ) ) + stream->writeRangedU32( mProfile->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + + // filename + if ( stream->writeFlag( mDirty.test( Filename ) ) ) + stream->writeString( mLocalProfile.mFilename ); + + // volume + if ( stream->writeFlag( mDirty.test( Volume ) ) ) + stream->write( mDescription.mVolume ); + + // pitch + if( stream->writeFlag( mDirty.test( Pitch ) ) ) + stream->write( mDescription.mPitch ); + + // islooping + if ( stream->writeFlag( mDirty.test( IsLooping ) ) ) + stream->writeFlag( mDescription.mIsLooping ); + + // isStreaming + if( stream->writeFlag( mDirty.test( IsStreaming ) ) ) + stream->writeFlag( mDescription.mIsStreaming ); + + // is3d + if ( stream->writeFlag( mDirty.test( Is3D ) ) ) + stream->writeFlag( mDescription.mIs3D ); + + // ReferenceDistance + if ( stream->writeFlag( mDirty.test( ReferenceDistance ) ) ) + stream->write( mDescription.mReferenceDistance ); + + // maxdistance + if ( stream->writeFlag( mDirty.test( MaxDistance) ) ) + stream->write( mDescription.mMaxDistance ); + + // coneinsideangle + if ( stream->writeFlag( mDirty.test( ConeInsideAngle ) ) ) + stream->write( mDescription.mConeInsideAngle ); + + // coneoutsideangle + if ( stream->writeFlag( mDirty.test( ConeOutsideAngle ) ) ) + stream->write( mDescription.mConeOutsideAngle ); + + // coneoutsidevolume + if ( stream->writeFlag( mDirty.test( ConeOutsideVolume ) ) ) + stream->write( mDescription.mConeOutsideVolume ); + + // channel + if ( stream->writeFlag( mDirty.test( Channel ) ) ) + stream->write( mDescription.mChannel ); + + // fadein + if( stream->writeFlag( mDirty.test( FadeInTime ) ) ) + stream->write( mDescription.mFadeInTime ); + + // fadeout + if( stream->writeFlag( mDirty.test( FadeOutTime ) ) ) + stream->write( mDescription.mFadeOutTime ); + + mDirty.clear(); + + // We should never have both source masks + // enabled at the same time! + AssertFatal( ( mask & AllSourceMasks ) != AllSourceMasks, + "SFXEmitter::packUpdate() - Bad source mask!" ); + + // Write the source playback state. + stream->writeFlag( mask & SourcePlayMask ); + stream->writeFlag( mask & SourceStopMask ); + + return retMask; +} + +bool SFXEmitter::_readDirtyFlag( BitStream* stream, U32 mask ) +{ + bool flag = stream->readFlag(); + if ( flag ) + mDirty.set( mask ); + + return flag; +} + +void SFXEmitter::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + // initial update? + bool initialUpdate = stream->readFlag(); + + mPlayOnAdd = stream->readFlag(); + + // transform + if ( _readDirtyFlag( stream, Transform ) ) + { + MatrixF mat; + stream->readAffineTransform(&mat); + Parent::setTransform(mat); + } + + // profile + if ( _readDirtyFlag( stream, Profile ) ) + { + if ( stream->readFlag() ) + { + S32 profileId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + mProfile = dynamic_cast( Sim::findObject( profileId ) ); + } + else + mProfile = NULL; + } + + // filename + if ( _readDirtyFlag( stream, Filename ) ) + mLocalProfile.mFilename = stream->readSTString(); + + // volume + if ( _readDirtyFlag( stream, Volume ) ) + stream->read( &mDescription.mVolume ); + + // pitch + if( _readDirtyFlag( stream, Pitch ) ) + stream->read( &mDescription.mPitch ); + + // islooping + if ( _readDirtyFlag( stream, IsLooping ) ) + mDescription.mIsLooping = stream->readFlag(); + + if( _readDirtyFlag( stream, IsStreaming ) ) + mDescription.mIsStreaming = stream->readFlag(); + + // is3d + if ( _readDirtyFlag( stream, Is3D ) ) + mDescription.mIs3D = stream->readFlag(); + + // ReferenceDistance + if ( _readDirtyFlag( stream, ReferenceDistance ) ) + stream->read( &mDescription.mReferenceDistance ); + + // maxdistance + if ( _readDirtyFlag( stream, MaxDistance ) ) + stream->read( &mDescription.mMaxDistance ); + + // coneinsideangle + if ( _readDirtyFlag( stream, ConeInsideAngle ) ) + stream->read( &mDescription.mConeInsideAngle ); + + // coneoutsideangle + if ( _readDirtyFlag( stream, ConeOutsideAngle ) ) + stream->read( &mDescription.mConeOutsideAngle ); + + // coneoutsidevolume + if ( _readDirtyFlag( stream, ConeOutsideVolume ) ) + stream->read( &mDescription.mConeOutsideVolume ); + + // channel + if ( _readDirtyFlag( stream, Channel ) ) + stream->read( &mDescription.mChannel ); + + // fadein + if ( _readDirtyFlag( stream, FadeInTime ) ) + stream->read( &mDescription.mFadeInTime ); + + // fadeout + if( _readDirtyFlag( stream, FadeOutTime ) ) + stream->read( &mDescription.mFadeOutTime ); + + // update the emitter now? + if ( !initialUpdate ) + _update(); + + // Check the source playback masks. + if ( stream->readFlag() ) // SourcePlayMask + play(); + if ( stream->readFlag() ) // SourceStopMask + stop(); +} + +void SFXEmitter::onStaticModified( const char* slotName, const char* newValue ) +{ + // NOTE: The signature for this function is very + // misleading... slotName is a StringTableEntry. + + // We don't check for changes on the client side. + if ( isClientObject() ) + return; + + // Lookup and store the property names once here + // and we can then just do pointer compares. + static StringTableEntry slotPosition = StringTable->lookup( "position" ); + static StringTableEntry slotRotation = StringTable->lookup( "rotation" ); + static StringTableEntry slotScale = StringTable->lookup( "scale" ); + static StringTableEntry slotProfile = StringTable->lookup( "profile" ); + static StringTableEntry slotFilename = StringTable->lookup( "fileName" ); + static StringTableEntry slotVolume = StringTable->lookup( "volume" ); + static StringTableEntry slotPitch = StringTable->lookup( "pitch" ); + static StringTableEntry slotIsLooping = StringTable->lookup( "isLooping" ); + static StringTableEntry slotIsStreaming= StringTable->lookup( "isStreaming" ); + static StringTableEntry slotIs3D = StringTable->lookup( "is3D" ); + static StringTableEntry slotRefDist = StringTable->lookup( "referenceDistance" ); + static StringTableEntry slotMaxDist = StringTable->lookup( "maxDistance" ); + static StringTableEntry slotConeInAng = StringTable->lookup( "coneInsideAngle" ); + static StringTableEntry slotConeOutAng = StringTable->lookup( "coneOutsideAngle" ); + static StringTableEntry slotConeOutVol = StringTable->lookup( "coneOutsideVolume" ); + static StringTableEntry slotFadeInTime = StringTable->lookup( "fadeInTime" ); + static StringTableEntry slotFadeOutTime= StringTable->lookup( "fadeOutTime" ); + + // Set the dirty flags. + mDirty.clear(); + if ( slotName == slotPosition || + slotName == slotRotation || + slotName == slotScale ) + mDirty.set( Transform ); + + else if ( slotName == slotProfile ) + mDirty.set( Profile ); + + else if ( slotName == slotFilename ) + mDirty.set( Filename ); + + else if ( slotName == slotVolume ) + mDirty.set( Volume ); + + else if( slotName == slotPitch ) + mDirty.set( Pitch ); + + else if ( slotName == slotIsLooping ) + mDirty.set( IsLooping ); + + else if ( slotName == slotIsStreaming ) + mDirty.set( IsStreaming ); + + else if ( slotName == slotIs3D ) + mDirty.set( Is3D ); + + else if ( slotName == slotRefDist ) + mDirty.set( ReferenceDistance ); + + else if ( slotName == slotMaxDist ) + mDirty.set( MaxDistance ); + + else if ( slotName == slotConeInAng ) + mDirty.set( ConeInsideAngle ); + + else if ( slotName == slotConeOutAng ) + mDirty.set( ConeOutsideAngle ); + + else if ( slotName == slotConeOutVol ) + mDirty.set( ConeOutsideVolume ); + + else if( slotName == slotFadeInTime ) + mDirty.set( FadeInTime ); + + else if( slotName == slotFadeOutTime ) + mDirty.set( FadeOutTime ); + + if ( mDirty ) + setMaskBits( DirtyUpdateMask ); +} + +bool SFXEmitter::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + if ( isServerObject() ) + { + // Validate the data we'll be passing across + // the network to the client. + mDescription.validate(); + } + else + { + _update(); + + // Do we need to start playback? + if ( mPlayOnAdd && mSource ) + mSource->play(); + } + + // Setup the bounds. + mObjBox.maxExtents = mObjScale; + mObjBox.minExtents = mObjScale; + mObjBox.minExtents.neg(); + resetWorldBox(); + addToScene(); + + return true; +} + +void SFXEmitter::onRemove() +{ + SFX_DELETE( mSource ); + + removeFromScene(); + Parent::onRemove(); +} + +void SFXEmitter::_update() +{ + AssertFatal( isClientObject(), "SFXEmitter::_update() - This shouldn't happen on the server!" ); + + // Store the playback status so we + // we can restore it. + SFXStatus prevState = mSource ? mSource->getStatus() : SFXStatusNull; + + // Make sure all the settings are valid. + mDescription.validate(); + + const MatrixF &transform = getTransform(); + const VectorF &velocity = getVelocity(); + + // Did we change the source? + if( mDirty.test( Profile | Filename | Is3D | IsLooping | IsStreaming | FadeInTime | FadeOutTime | Channel ) ) + { + SFX_DELETE( mSource ); + + // Do we have a profile? + if( mProfile ) + { + mSource = SFX->createSource( mProfile, &transform, &velocity ); + AssertFatal( mSource != NULL, "SFXEmitter::_update() - failed to create source!" ); + + // If we're supposed to play when the emitter is + // added to the scene then also restart playback + // when the profile changes. + prevState = mPlayOnAdd ? SFXStatusPlaying : prevState; + + // Force an update of properties set on the local description. + + mDirty.set( AllDirtyMask ); + } + + // Else take the local profile + else + { + // Clear the resource and buffer to force a + // reload if the filename changed. + if( mDirty.test( Filename ) ) + { + mLocalProfile.mResource = NULL; + mLocalProfile.mBuffer = NULL; + } + + if( !mLocalProfile.mFilename.isEmpty() ) + { + mSource = SFX->createSource( &mLocalProfile, &transform, &velocity ); + AssertFatal( mSource != NULL, "SFXEmitter::_update() - failed to create source!" ); + + prevState = mPlayOnAdd ? SFXStatusPlaying : prevState; + } + } + + mDirty.clear( Profile | Filename | Is3D | IsLooping | IsStreaming | FadeInTime | FadeOutTime | Channel ); + } + + // Cheat if the editor is open and the looping state + // is toggled on a local profile sound. It makes the + // editor feel responsive and that things are working. + if ( gEditingMission && + !mProfile && + mPlayOnAdd && + mDirty.test( IsLooping ) ) + prevState = SFXStatusPlaying; + + // The rest only applies if we have a source. + if( mSource ) + { + // Set the volume irrespective of the profile. + if( mDirty.test( Volume ) ) + mSource->setVolume( mDescription.mVolume ); + + if( mDirty.test( Pitch ) ) + mSource->setPitch( mDescription.mPitch ); + + // Skip these 3d only settings. + if( mDescription.mIs3D ) + { + if( mDirty.test( Transform ) ) + { + mSource->setTransform( transform ); + mSource->setVelocity( velocity ); + } + + if( mDirty.test( ReferenceDistance | MaxDistance ) ) + { + mSource->setMinMaxDistance( mDescription.mReferenceDistance, + mDescription.mMaxDistance ); + } + + if( mDirty.test( ConeInsideAngle | ConeOutsideAngle | ConeOutsideVolume ) ) + { + mSource->setCone( F32( mDescription.mConeInsideAngle ), + F32( mDescription.mConeOutsideAngle ), + mDescription.mConeOutsideVolume ); + } + + mDirty.clear( Transform | ReferenceDistance | MaxDistance | ConeInsideAngle | ConeOutsideAngle | ConeOutsideVolume ); + } + + // Restore the pre-update playback state. + if ( prevState == SFXStatusPlaying ) + mSource->play(); + + mDirty.clear( Volume | Pitch | Transform ); + } +} + +void SFXEmitter::play() +{ + if ( mSource ) + mSource->play(); + else + { + // By clearing the playback masks first we + // ensure the last playback command called + // within a single tick is the one obeyed. + clearMaskBits( AllSourceMasks ); + + setMaskBits( SourcePlayMask ); + } +} + +void SFXEmitter::stop() +{ + if ( mSource ) + mSource->stop(); + else + { + // By clearing the playback masks first we + // ensure the last playback command called + // within a single tick is the one obeyed. + clearMaskBits( AllSourceMasks ); + + setMaskBits( SourceStopMask ); + } +} + +SFXStatus SFXEmitter::getPlaybackStatus() const +{ + const SFXEmitter* emitter = this; + + // We only have a source playing on client objects, so if this is a server + // object, we want to know the playback status on the local client connection's + // version of this emitter. + + if( isServerObject() ) + { + S32 index = NetConnection::getLocalClientConnection()->getGhostIndex( ( NetObject* ) this ); + if( index != -1 ) + emitter = dynamic_cast< SFXEmitter* >( NetConnection::getConnectionToServer()->resolveGhost( index ) ); + else + emitter = NULL; + } + + if( emitter && emitter->mSource ) + return emitter->mSource->getStatus(); + + return SFXStatusNull; +} + +bool SFXEmitter::isInRange() const +{ + if( !is3D() ) + return true; + + SFXListener& listener = SFX->getListener(); + + const Point3F listenerPos = listener.getTransform().getPosition(); + const Point3F emitterPos = getPosition(); + const F32 dist = getSFXDescription().mMaxDistance; + + return ( ( emitterPos - listenerPos ).len() <= dist ); +} + +void SFXEmitter::setTransform( const MatrixF &mat ) +{ + // Set the transform directly from the + // matrix created by inspector. + Parent::setTransform( mat ); + setMaskBits( TransformUpdateMask ); +} + +void SFXEmitter::setScale( const VectorF &scale ) +{ + // We ignore scale... it doesn't effect us. +} + +ConsoleMethod( SFXEmitter, play, void, 2, 2, + "SFXEmitter.play()\n" + "Sends network event to start playback if " + "the emitter source is not already playing." ) +{ + object->play(); +} + +ConsoleMethod( SFXEmitter, stop, void, 2, 2, + "SFXEmitter.stop()\n" + "Sends network event to stop emitter " + "playback on all ghosted clients." ) +{ + object->stop(); +} + +ConsoleMethod( SFXEmitter, getPlaybackStatus, const char*, 2, 2, "() - Return the playback status of the emitter's sound." ) +{ + return SFXStatusToString( object->getPlaybackStatus() ); +} + +ConsoleMethod( SFXEmitter, isInRange, bool, 2, 2, "( vector pos ) - Return true if the emitter is currently in range of the listener." ) +{ + return object->isInRange(); +} diff --git a/T3D/sfxEmitter.h b/T3D/sfxEmitter.h new file mode 100644 index 0000000..0d1c067 --- /dev/null +++ b/T3D/sfxEmitter.h @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXEMITTER_H_ +#define _SFXEMITTER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _SFXPROFILE_H_ +#include "sfx/sfxProfile.h" +#endif + +class SFXSource; + + +/// The SFXEmitter is used to place 2D or 3D sounds into a +/// mission. +/// +/// If the profile is set then the emitter plays that. If the +/// profile is null and the filename is set then the local emitter +/// options are used. +/// +/// Note that you can call SFXEmitter.play() and SFXEmitter.stop() +/// to control playback from script. +/// +class SFXEmitter : public SceneObject +{ + typedef SceneObject Parent; + + protected: + + /// The sound source for the emitter. + SFXSource *mSource; + + /// The selected profile or null if the local + /// profile should be used. + SFXProfile *mProfile; + + /// A local profile object used to coax the + /// sound system to play a custom sound. + SFXProfile mLocalProfile; + + /// The description used by the local profile. + SFXDescription mDescription; + + /// If true playback starts when the emitter + /// is added to the scene. + bool mPlayOnAdd; + + /// Network update masks. + enum UpdateMasks + { + InitialUpdateMask = BIT(0), + TransformUpdateMask = BIT(1), + DirtyUpdateMask = BIT(2), + + SourcePlayMask = BIT(3), + SourceStopMask = BIT(4), + + AllSourceMasks = SourcePlayMask | SourceStopMask, + }; + + /// Dirty flags used to handle sound property + /// updates locally and across the network. + enum Dirty + { + Profile = BIT(0), + Filename = BIT(2), + Volume = BIT(4), + IsLooping = BIT(5), + Is3D = BIT(6), + ReferenceDistance = BIT(7), + MaxDistance = BIT(8), + ConeInsideAngle = BIT(9), + ConeOutsideAngle = BIT(10), + ConeOutsideVolume = BIT(11), + Transform = BIT(12), + Channel = BIT(13), + OutsideAmbient = BIT(14), + IsStreaming = BIT(15), + FadeInTime = BIT(16), + FadeOutTime = BIT(17), + Pitch = BIT(18), + + AllDirtyMask = 0xFFFFFFFF, + }; + + /// The current dirty flags. + BitSet32 mDirty; + + /// Helper which reads a flag from the stream and + /// updates the mDirty bits. + bool _readDirtyFlag( BitStream *stream, U32 flag ); + + /// Called when the emitter state has been marked + /// dirty and the source needs to be updated. + void _update(); + + public: + + SFXEmitter(); + virtual ~SFXEmitter(); + + DECLARE_CONOBJECT( SFXEmitter ); + + /// Return the playback status of the emitter's associated sound. + SFXStatus getPlaybackStatus() const; + + /// + const SFXDescription& getSFXDescription() const { return ( mProfile ? *( mProfile->getDescription() ) : mDescription ); } + + /// Return true if the SFX system's listener is in range of this emitter. + bool isInRange() const; + + /// Return true if the emitter defines a 3D sound. + bool is3D() const { return getSFXDescription().mIs3D; } + + /// Return true if the emitter using streaming playback. + bool isStreaming() const { return getSFXDescription().mIsStreaming; } + + /// Return true if the emitter loops playback of the associated sound. + bool isLooping() const { return getSFXDescription().mIsLooping; } + + // SimObject + bool onAdd(); + void onRemove(); + void onStaticModified( const char *slotName, const char *newValue = NULL ); + static void initPersistFields(); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + void setTransform( const MatrixF &mat ); + void setScale( const VectorF &scale ); + + /// Sends network event to start playback if + /// the emitter source is not already playing. + void play(); + + /// Sends network event to stop emitter + /// playback on all ghosted clients. + void stop(); + +}; + +#endif // _SFXEMITTER_H_ diff --git a/T3D/shapeBase.cpp b/T3D/shapeBase.cpp new file mode 100644 index 0000000..9e36c5a --- /dev/null +++ b/T3D/shapeBase.cpp @@ -0,0 +1,4298 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/shapeBase.h" + +#include "core/dnet.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxSource.h" +#include "sfx/sfxProfile.h" +#include "T3D/gameConnection.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "ts/tsPartInstance.h" +#include "ts/tsShapeInstance.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "T3D/fx/explosion.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/fx/cameraFXMgr.h" +#include "environment/waterBlock.h" +#include "T3D/debris.h" +#include "T3D/physicalZone.h" +#include "T3D/containerQuery.h" +#include "math/mathUtils.h" +#include "math/mMatrix.h" +#include "math/mRandom.h" +#include "platform/profiler.h" +#include "gfx/gfxCubemap.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" +#include "collision/earlyOutPolyList.h" +#include "core/resourceManager.h" +#include "sceneGraph/reflectionManager.h" +#include "gfx/sim/cubemapData.h" +#include "materials/materialManager.h" +#include "materials/materialFeatureTypes.h" +#include "renderInstance/renderOcclusionMgr.h" +#include "core/stream/fileStream.h" + +IMPLEMENT_CO_DATABLOCK_V1(ShapeBaseData); + + +//---------------------------------------------------------------------------- +// Timeout for non-looping sounds on a channel +static SimTime sAudioTimeout = 500; +bool ShapeBase::gRenderEnvMaps = true; +F32 ShapeBase::sWhiteoutDec = 0.007f; +F32 ShapeBase::sDamageFlashDec = 0.007f; +U32 ShapeBase::sLastRenderFrame = 0; + +static const char *sDamageStateName[] = +{ + // Index by enum ShapeBase::DamageState + "Enabled", + "Disabled", + "Destroyed" +}; + + +//---------------------------------------------------------------------------- + +ShapeBaseData::ShapeBaseData() + : shadowEnable( false ), + shadowSize( 128 ), + shadowMaxVisibleDistance( 80.0f ), + shadowProjectionDistance( 10.0f ), + shadowSphereAdjust( 1.0f ), + shapeName( StringTable->insert("") ), + cloakTexName( StringTable->insert("") ), + mass( 1.0f ), + drag( 0.0f ), + density( 1.0f ), + maxEnergy( 0.0f ), + maxDamage( 1.0f ), + disabledLevel( 1.0f ), + destroyedLevel( 1.0f ), + repairRate( 0.0033f ), + eyeNode( -1 ), + cameraNode( -1 ), + damageSequence( -1 ), + hulkSequence( -1 ), + cameraMaxDist( 0.0f ), + cameraMinDist( 0.2f ), + cameraDefaultFov( 90.0f ), + cameraMinFov( 5.0f ), + cameraMaxFov( 120.f ), + aiAvoidThis( false ), + isInvincible( false ), + renderWhenDestroyed( true ), + debris( NULL ), + debrisID( 0 ), + debrisShapeName( StringTable->insert("") ), + explosion( NULL ), + explosionID( 0 ), + underwaterExplosion( NULL ), + underwaterExplosionID( 0 ), + firstPersonOnly( false ), + useEyePoint( false ), + reflectorDesc( NULL ), + observeThroughObject( false ), + computeCRC( false ), + inheritEnergyFromMount( false ), + mCRC( 0 ), + debrisDetail( -1 ) +{ + dMemset( hudRenderCenter, 0, sizeof( bool ) * NumHudRenderImages ); + dMemset( hudRenderModulated, 0, sizeof( bool ) * NumHudRenderImages ); + dMemset( hudRenderAlways, 0, sizeof( bool ) * NumHudRenderImages ); + dMemset( hudRenderDistance, 0, sizeof( bool ) * NumHudRenderImages ); + dMemset( hudRenderName, 0, sizeof( bool ) * NumHudRenderImages ); + + StringTableEntry emptyStr = StringTable->insert(""); + + for(U32 j = 0; j < NumHudRenderImages; j++) + { + hudImageNameFriendly[j] = emptyStr; + hudImageNameEnemy[j] = emptyStr; + } + + dMemset( mountPointNode, -1, sizeof( S32 ) * NumMountPoints ); +} + +struct ShapeBaseDataProto +{ + F32 mass; + F32 drag; + F32 density; + F32 maxEnergy; + F32 cameraMaxDist; + F32 cameraMinDist; + F32 cameraDefaultFov; + F32 cameraMinFov; + F32 cameraMaxFov; + + + ShapeBaseDataProto() + { + mass = 1; + drag = 0; + density = 1; + maxEnergy = 0; + cameraMaxDist = 0; + cameraMinDist = 0.2f; + cameraDefaultFov = 90.f; + cameraMinFov = 5.0f; + cameraMaxFov = 120.f; + } +}; + +static ShapeBaseDataProto gShapeBaseDataProto; + +ShapeBaseData::~ShapeBaseData() +{ + +} + +bool ShapeBaseData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + bool shapeError = false; + + // Resolve objects transmitted from server + if (!server) { + + if( !explosion && explosionID != 0 ) + { + if( Sim::findObject( explosionID, explosion ) == false) + { + Con::errorf( ConsoleLogEntry::General, "ShapeBaseData::preload: Invalid packet, bad datablockId(explosion): 0x%x", explosionID ); + } + AssertFatal(!(explosion && ((explosionID < DataBlockObjectIdFirst) || (explosionID > DataBlockObjectIdLast))), + "ShapeBaseData::preload: invalid explosion data"); + } + + if( !underwaterExplosion && underwaterExplosionID != 0 ) + { + if( Sim::findObject( underwaterExplosionID, underwaterExplosion ) == false) + { + Con::errorf( ConsoleLogEntry::General, "ShapeBaseData::preload: Invalid packet, bad datablockId(underwaterExplosion): 0x%x", underwaterExplosionID ); + } + AssertFatal(!(underwaterExplosion && ((underwaterExplosionID < DataBlockObjectIdFirst) || (underwaterExplosionID > DataBlockObjectIdLast))), + "ShapeBaseData::preload: invalid underwaterExplosion data"); + } + + if( !debris && debrisID != 0 ) + { + Sim::findObject( debrisID, debris ); + AssertFatal(!(debris && ((debrisID < DataBlockObjectIdFirst) || (debrisID > DataBlockObjectIdLast))), + "ShapeBaseData::preload: invalid debris data"); + } + + + if( debrisShapeName && debrisShapeName[0] != '\0' && !bool(debrisShape) ) + { + debrisShape = ResourceManager::get().load(debrisShapeName); + if( bool(debrisShape) == false ) + { + errorStr = String::ToString("ShapeBaseData::load: Couldn't load shape \"%s\"", debrisShapeName); + return false; + } + else + { + if(!server && !debrisShape->preloadMaterialList(debrisShape.getPath()) && NetConnection::filesWereDownloaded()) + shapeError = true; + + TSShapeInstance* pDummy = new TSShapeInstance(debrisShape, !server); + delete pDummy; + } + } + } + + // + if (shapeName && shapeName[0]) { + S32 i; + + // Resolve shapename + mShape = ResourceManager::get().load(shapeName); + if (bool(mShape) == false) + { + errorStr = String::ToString("ShapeBaseData: Couldn't load shape \"%s\"",shapeName); + return false; + } + if(!server && !mShape->preloadMaterialList(mShape.getPath()) && NetConnection::filesWereDownloaded()) + shapeError = true; + + if(computeCRC) + { + Con::printf("Validation required for shape: %s", shapeName); + + Torque::FS::FileNodeRef fileRef = Torque::FS::GetFileNode(mShape.getPath()); + + if (!fileRef) + return false; + + if(server) + mCRC = fileRef->getChecksum(); + else if(mCRC != fileRef->getChecksum()) + { + errorStr = String::ToString("Shape \"%s\" does not match version on server.",shapeName); + return false; + } + } + // Resolve details and camera node indexes. + static const String sCollisionStr( "collision-" ); + + for (i = 0; i < mShape->details.size(); i++) + { + const String &name = mShape->names[mShape->details[i].nameIndex]; + + if (name.compare( sCollisionStr, sCollisionStr.length(), String::NoCase ) == 0) + { + collisionDetails.push_back(i); + collisionBounds.increment(); + + mShape->computeBounds(collisionDetails.last(), collisionBounds.last()); + mShape->getAccelerator(collisionDetails.last()); + + if (!mShape->bounds.isContained(collisionBounds.last())) + { + Con::warnf("Warning: shape %s collision detail %d (Collision-%d) bounds exceed that of shape.", shapeName, collisionDetails.size() - 1, collisionDetails.last()); + collisionBounds.last() = mShape->bounds; + } + else if (collisionBounds.last().isValidBox() == false) + { + Con::errorf("Error: shape %s-collision detail %d (Collision-%d) bounds box invalid!", shapeName, collisionDetails.size() - 1, collisionDetails.last()); + collisionBounds.last() = mShape->bounds; + } + + // The way LOS works is that it will check to see if there is a LOS detail that matches + // the the collision detail + 1 + MaxCollisionShapes (this variable name should change in + // the future). If it can't find a matching LOS it will simply use the collision instead. + // We check for any "unmatched" LOS's further down + LOSDetails.increment(); + + String buff = String::ToString("LOS-%d", i + 1 + MaxCollisionShapes); + U32 los = mShape->findDetail(buff); + if (los == -1) + LOSDetails.last() = i; + else + LOSDetails.last() = los; + } + } + + // Snag any "unmatched" LOS details + static const String sLOSStr( "LOS-" ); + + for (i = 0; i < mShape->details.size(); i++) + { + const String &name = mShape->names[mShape->details[i].nameIndex]; + + if (name.compare( sLOSStr, sLOSStr.length(), String::NoCase ) == 0) + { + // See if we already have this LOS + bool found = false; + for (U32 j = 0; j < LOSDetails.size(); j++) + { + if (LOSDetails[j] == i) + { + found = true; + break; + } + } + + if (!found) + LOSDetails.push_back(i); + } + } + + debrisDetail = mShape->findDetail("Debris-17"); + eyeNode = mShape->findNode("eye"); + cameraNode = mShape->findNode("cam"); + if (cameraNode == -1) + cameraNode = eyeNode; + + // Resolve mount point node indexes + for (i = 0; i < NumMountPoints; i++) { + char fullName[256]; + dSprintf(fullName,sizeof(fullName),"mount%d",i); + mountPointNode[i] = mShape->findNode(fullName); + } + + // find the AIRepairNode - hardcoded to be the last node in the array... + mountPointNode[AIRepairNode] = mShape->findNode("AIRepairNode"); + + // + hulkSequence = mShape->findSequence("Visibility"); + damageSequence = mShape->findSequence("Damage"); + + // + F32 w = mShape->bounds.len_y() / 2; + if (cameraMaxDist < w) + cameraMaxDist = w; + } + + if(!server) + { +/* + // grab all the hud images + for(U32 i = 0; i < NumHudRenderImages; i++) + { + if(hudImageNameFriendly[i] && hudImageNameFriendly[i][0]) + hudImageFriendly[i] = TextureHandle(hudImageNameFriendly[i], BitmapTexture); + + if(hudImageNameEnemy[i] && hudImageNameEnemy[i][0]) + hudImageEnemy[i] = TextureHandle(hudImageNameEnemy[i], BitmapTexture); + } +*/ + } + + // Resolve CubeReflectorDesc. + if ( !server && cubeDescName.isNotEmpty() ) + { + Sim::findObject( cubeDescName, reflectorDesc ); + } + + return !shapeError; +} + + +void ShapeBaseData::initPersistFields() +{ + addGroup( "Shadows" ); + + addField( "shadowEnable", TypeBool, Offset(shadowEnable, ShapeBaseData) ); + addField( "shadowSize", TypeS32, Offset(shadowSize, ShapeBaseData) ); + addField( "shadowMaxVisibleDistance", TypeF32, Offset(shadowMaxVisibleDistance, ShapeBaseData) ); + addField( "shadowProjectionDistance", TypeF32, Offset(shadowProjectionDistance, ShapeBaseData) ); + addField( "shadowSphereAdjust", TypeF32, Offset(shadowSphereAdjust, ShapeBaseData) ); + + endGroup( "Shadows" ); + + + addGroup("Render"); + + addField( "shapeFile", TypeFilename, Offset(shapeName, ShapeBaseData) ); + + endGroup("Render"); + + addGroup( "Destruction", "Parameters related to the destruction effects of this object." ); + + addField( "explosion", TypeExplosionDataPtr, Offset(explosion, ShapeBaseData) ); + addField( "underwaterExplosion", TypeExplosionDataPtr, Offset(underwaterExplosion, ShapeBaseData) ); + addField( "debris", TypeDebrisDataPtr, Offset(debris, ShapeBaseData) ); + addField( "renderWhenDestroyed", TypeBool, Offset(renderWhenDestroyed, ShapeBaseData) ); + addField( "debrisShapeName", TypeFilename, Offset(debrisShapeName, ShapeBaseData) ); + + endGroup( "Destruction" ); + + addGroup( "Physics" ); + + addField( "mass", TypeF32, Offset(mass, ShapeBaseData) ); + addField( "drag", TypeF32, Offset(drag, ShapeBaseData) ); + addField( "density", TypeF32, Offset(density, ShapeBaseData) ); + + endGroup( "Physics" ); + + addGroup( "Damage/Energy" ); + + addField( "maxEnergy", TypeF32, Offset(maxEnergy, ShapeBaseData) ); + addField( "maxDamage", TypeF32, Offset(maxDamage, ShapeBaseData) ); + addField( "disabledLevel", TypeF32, Offset(disabledLevel, ShapeBaseData) ); + addField( "destroyedLevel", TypeF32, Offset(destroyedLevel, ShapeBaseData) ); + addField( "repairRate", TypeF32, Offset(repairRate, ShapeBaseData) ); + addField( "inheritEnergyFromMount", TypeBool, Offset(inheritEnergyFromMount, ShapeBaseData) ); + addField( "isInvincible", TypeBool, Offset(isInvincible, ShapeBaseData) ); + + endGroup( "Damage/Energy" ); + + addGroup( "Camera" ); + + addField( "cameraMaxDist", TypeF32, Offset(cameraMaxDist, ShapeBaseData) ); + addField( "cameraMinDist", TypeF32, Offset(cameraMinDist, ShapeBaseData) ); + addField( "cameraDefaultFov", TypeF32, Offset(cameraDefaultFov, ShapeBaseData) ); + addField( "cameraMinFov", TypeF32, Offset(cameraMinFov, ShapeBaseData) ); + addField( "cameraMaxFov", TypeF32, Offset(cameraMaxFov, ShapeBaseData) ); + addField( "firstPersonOnly", TypeBool, Offset(firstPersonOnly, ShapeBaseData) ); + addField( "useEyePoint", TypeBool, Offset(useEyePoint, ShapeBaseData) ); + addField( "observeThroughObject", TypeBool, Offset(observeThroughObject, ShapeBaseData) ); + + endGroup("Camera"); + + // This hud code is going to get ripped out soon... + addGroup( "HUD", "@deprecated Likely to be removed soon." ); + + addField( "hudImageName", TypeFilename, Offset(hudImageNameFriendly, ShapeBaseData), NumHudRenderImages ); + addField( "hudImageNameFriendly", TypeFilename, Offset(hudImageNameFriendly, ShapeBaseData), NumHudRenderImages ); + addField( "hudImageNameEnemy", TypeFilename, Offset(hudImageNameEnemy, ShapeBaseData), NumHudRenderImages ); + addField( "hudRenderCenter", TypeBool, Offset(hudRenderCenter, ShapeBaseData), NumHudRenderImages ); + addField( "hudRenderModulated", TypeBool, Offset(hudRenderModulated, ShapeBaseData), NumHudRenderImages ); + addField( "hudRenderAlways", TypeBool, Offset(hudRenderAlways, ShapeBaseData), NumHudRenderImages ); + addField( "hudRenderDistance", TypeBool, Offset(hudRenderDistance, ShapeBaseData), NumHudRenderImages ); + addField( "hudRenderName", TypeBool, Offset(hudRenderName, ShapeBaseData), NumHudRenderImages ); + + endGroup("HUD"); + + addGroup( "Misc" ); + + addField( "aiAvoidThis", TypeBool, Offset(aiAvoidThis, ShapeBaseData) ); + addField( "computeCRC", TypeBool, Offset(computeCRC, ShapeBaseData) ); + + endGroup( "Misc" ); + + addGroup("Reflection"); + + addField( "cubeReflectorDesc", TypeRealString, Offset( cubeDescName, ShapeBaseData ) ); + //addField( "reflectMaxRateMs", TypeS32, Offset( reflectMaxRateMs, ShapeBaseData ), "reflection will not be updated more frequently than this" ); + //addField( "reflectMaxDist", TypeF32, Offset( reflectMaxDist, ShapeBaseData ), "distance at which reflection is never updated" ); + //addField( "reflectMinDist", TypeF32, Offset( reflectMinDist, ShapeBaseData ), "distance at which reflection is always updated" ); + //addField( "reflectDetailAdjust", TypeF32, Offset( reflectDetailAdjust, ShapeBaseData ), "scale up or down the detail level for objects rendered in a reflection" ); + + endGroup("Reflection"); + + Parent::initPersistFields(); +} + +ConsoleMethod( ShapeBaseData, checkDeployPos, bool, 3, 3, "(Transform xform)") +{ + if (bool(object->mShape) == false) + return false; + + Point3F pos(0, 0, 0); + AngAxisF aa(Point3F(0, 0, 1), 0); + dSscanf(argv[2],"%g %g %g %g %g %g %g", + &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); + MatrixF mat; + aa.setMatrix(&mat); + mat.setColumn(3,pos); + + Box3F objBox = object->mShape->bounds; + Point3F boxCenter = (objBox.minExtents + objBox.maxExtents) * 0.5f; + objBox.minExtents = boxCenter + (objBox.minExtents - boxCenter) * 0.9f; + objBox.maxExtents = boxCenter + (objBox.maxExtents - boxCenter) * 0.9f; + + Box3F wBox = objBox; + mat.mul(wBox); + + EarlyOutPolyList polyList; + polyList.mNormal.set(0,0,0); + polyList.mPlaneList.clear(); + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(objBox.minExtents,VectorF(-1,0,0)); + polyList.mPlaneList[1].set(objBox.maxExtents,VectorF(0,1,0)); + polyList.mPlaneList[2].set(objBox.maxExtents,VectorF(1,0,0)); + polyList.mPlaneList[3].set(objBox.minExtents,VectorF(0,-1,0)); + polyList.mPlaneList[4].set(objBox.minExtents,VectorF(0,0,-1)); + polyList.mPlaneList[5].set(objBox.maxExtents,VectorF(0,0,1)); + + for (U32 i = 0; i < 6; i++) + { + PlaneF temp; + mTransformPlane(mat, Point3F(1, 1, 1), polyList.mPlaneList[i], &temp); + polyList.mPlaneList[i] = temp; + } + + if (gServerContainer.buildPolyList(wBox, InteriorObjectType | StaticShapeObjectType, &polyList)) + return false; + return true; +} + + +ConsoleMethod(ShapeBaseData, getDeployTransform, const char *, 4, 4, "(Point3F pos, Point3F normal)") +{ + Point3F normal; + Point3F position; + dSscanf(argv[2], "%g %g %g", &position.x, &position.y, &position.z); + dSscanf(argv[3], "%g %g %g", &normal.x, &normal.y, &normal.z); + normal.normalize(); + + VectorF xAxis; + if( mFabs(normal.z) > mFabs(normal.x) && mFabs(normal.z) > mFabs(normal.y)) + mCross( VectorF( 0, 1, 0 ), normal, &xAxis ); + else + mCross( VectorF( 0, 0, 1 ), normal, &xAxis ); + + VectorF yAxis; + mCross( normal, xAxis, &yAxis ); + + MatrixF testMat(true); + testMat.setColumn( 0, xAxis ); + testMat.setColumn( 1, yAxis ); + testMat.setColumn( 2, normal ); + testMat.setPosition( position ); + + char *returnBuffer = Con::getReturnBuffer(256); + Point3F pos; + testMat.getColumn(3,&pos); + AngAxisF aa(testMat); + dSprintf(returnBuffer,256,"%g %g %g %g %g %g %g", + pos.x,pos.y,pos.z,aa.axis.x,aa.axis.y,aa.axis.z,aa.angle); + return returnBuffer; +} + +void ShapeBaseData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if(stream->writeFlag(computeCRC)) + stream->write(mCRC); + + stream->writeFlag(shadowEnable); + stream->write(shadowSize); + stream->write(shadowMaxVisibleDistance); + stream->write(shadowProjectionDistance); + stream->write(shadowSphereAdjust); + + + stream->writeString(shapeName); + stream->writeString(cloakTexName); + if(stream->writeFlag(mass != gShapeBaseDataProto.mass)) + stream->write(mass); + if(stream->writeFlag(drag != gShapeBaseDataProto.drag)) + stream->write(drag); + if(stream->writeFlag(density != gShapeBaseDataProto.density)) + stream->write(density); + if(stream->writeFlag(maxEnergy != gShapeBaseDataProto.maxEnergy)) + stream->write(maxEnergy); + if(stream->writeFlag(cameraMaxDist != gShapeBaseDataProto.cameraMaxDist)) + stream->write(cameraMaxDist); + if(stream->writeFlag(cameraMinDist != gShapeBaseDataProto.cameraMinDist)) + stream->write(cameraMinDist); + cameraDefaultFov = mClampF(cameraDefaultFov, cameraMinFov, cameraMaxFov); + if(stream->writeFlag(cameraDefaultFov != gShapeBaseDataProto.cameraDefaultFov)) + stream->write(cameraDefaultFov); + if(stream->writeFlag(cameraMinFov != gShapeBaseDataProto.cameraMinFov)) + stream->write(cameraMinFov); + if(stream->writeFlag(cameraMaxFov != gShapeBaseDataProto.cameraMaxFov)) + stream->write(cameraMaxFov); + stream->writeString( debrisShapeName ); + + stream->writeFlag(observeThroughObject); + + if( stream->writeFlag( debris != NULL ) ) + { + stream->writeRangedU32(packed? SimObjectId(debris): + debris->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + + stream->writeFlag(isInvincible); + stream->writeFlag(renderWhenDestroyed); + + if( stream->writeFlag( explosion != NULL ) ) + { + stream->writeRangedU32( explosion->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + if( stream->writeFlag( underwaterExplosion != NULL ) ) + { + stream->writeRangedU32( underwaterExplosion->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + stream->writeFlag(inheritEnergyFromMount); + stream->writeFlag(firstPersonOnly); + stream->writeFlag(useEyePoint); + + stream->write(cubeDescName); + //stream->write(reflectPriority); + //stream->write(reflectMaxRateMs); + //stream->write(reflectMinDist); + //stream->write(reflectMaxDist); + //stream->write(reflectDetailAdjust); +} + +void ShapeBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + computeCRC = stream->readFlag(); + if(computeCRC) + stream->read(&mCRC); + + shadowEnable = stream->readFlag(); + stream->read(&shadowSize); + stream->read(&shadowMaxVisibleDistance); + stream->read(&shadowProjectionDistance); + stream->read(&shadowSphereAdjust); + + shapeName = stream->readSTString(); + cloakTexName = stream->readSTString(); + if(stream->readFlag()) + stream->read(&mass); + else + mass = gShapeBaseDataProto.mass; + + if(stream->readFlag()) + stream->read(&drag); + else + drag = gShapeBaseDataProto.drag; + + if(stream->readFlag()) + stream->read(&density); + else + density = gShapeBaseDataProto.density; + + if(stream->readFlag()) + stream->read(&maxEnergy); + else + maxEnergy = gShapeBaseDataProto.maxEnergy; + + if(stream->readFlag()) + stream->read(&cameraMaxDist); + else + cameraMaxDist = gShapeBaseDataProto.cameraMaxDist; + + if(stream->readFlag()) + stream->read(&cameraMinDist); + else + cameraMinDist = gShapeBaseDataProto.cameraMinDist; + + if(stream->readFlag()) + stream->read(&cameraDefaultFov); + else + cameraDefaultFov = gShapeBaseDataProto.cameraDefaultFov; + + if(stream->readFlag()) + stream->read(&cameraMinFov); + else + cameraMinFov = gShapeBaseDataProto.cameraMinFov; + + if(stream->readFlag()) + stream->read(&cameraMaxFov); + else + cameraMaxFov = gShapeBaseDataProto.cameraMaxFov; + + debrisShapeName = stream->readSTString(); + + observeThroughObject = stream->readFlag(); + + if( stream->readFlag() ) + { + debrisID = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + isInvincible = stream->readFlag(); + renderWhenDestroyed = stream->readFlag(); + + if( stream->readFlag() ) + { + explosionID = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + if( stream->readFlag() ) + { + underwaterExplosionID = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + inheritEnergyFromMount = stream->readFlag(); + firstPersonOnly = stream->readFlag(); + useEyePoint = stream->readFlag(); + + stream->read(&cubeDescName); + //stream->read(&reflectPriority); + //stream->read(&reflectMaxRateMs); + //stream->read(&reflectMinDist); + //stream->read(&reflectMaxDist); + //stream->read(&reflectDetailAdjust); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +Chunker sTimeoutChunker; +ShapeBase::CollisionTimeout* ShapeBase::sFreeTimeoutList = 0; + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(ShapeBase); + +ShapeBase::ShapeBase() + : mDrag( 0.0f ), + mBuoyancy( 0.0f ), + mWaterCoverage( 0.0f ), + mLiquidHeight( 0.0f ), + mControllingObject( NULL ), + mGravityMod( 1.0f ), + mAppliedForce( Point3F::Zero ), + mTimeoutList( NULL ), + mDataBlock( NULL ), + mShapeInstance( NULL ), + mEnergy( 0.0f ), + mRechargeRate( 0.0f ), + mDamage( 0.0f ), + mRepairRate( 0.0f ), + mRepairReserve( 0.0f ), + mDamageState( Enabled ), + mDamageThread( NULL ), + mHulkThread( NULL ), + mLastRenderFrame( 0 ), + mLastRenderDistance( 0.0f ), + mCloaked( false ), + mCloakLevel( 0.0f ), + mHidden( false ), + mDamageFlash( 0.0f ), + mWhiteOut( 0.0f ), + mInvincibleEffect( 0.0f ), + mInvincibleDelta( 0.0f ), + mInvincibleCount( 0.0f ), + mInvincibleSpeed( 0.0f ), + mInvincibleTime( 0.0f ), + mInvincibleFade( 0.1f ), + mInvincibleOn( false ), + mIsControlled( false ), + mConvexList( new Convex ), + mCameraFov( 90.0f ), + mShieldNormal( 0.0f, 0.0f, 1.0f ), + mFadeOut( true ), + mFading( false ), + mFadeVal( 1.0f ), + mFadeTime( 1.0f ), + mFadeElapsedTime( 0.0f ), + mFadeDelay( 0.0f ), + mFlipFadeVal( false ), + damageDir( 0.0f, 0.0f, 1.0f ), + mShapeBaseMount( NULL ), + mMass( 1.0f ), + mOneOverMass( 1.0f ), + mSkinHash( 0 ) +{ + mTypeMask |= ShapeBaseObjectType | LightObjectType; + + S32 i; + + for (i = 0; i < MaxSoundThreads; i++) { + mSoundThread[i].play = false; + mSoundThread[i].profile = 0; + mSoundThread[i].sound = 0; + } + + for (i = 0; i < MaxScriptThreads; i++) { + mScriptThread[i].sequence = -1; + mScriptThread[i].thread = 0; + mScriptThread[i].sound = 0; + mScriptThread[i].state = Thread::Stop; + mScriptThread[i].atEnd = false; + mScriptThread[i].timescale = 1.f; + mScriptThread[i].position = -1.f; + } + + for (i = 0; i < MaxTriggerKeys; i++) + mTrigger[i] = false; +} + + +ShapeBase::~ShapeBase() +{ + SAFE_DELETE( mConvexList ); + + AssertFatal(mMount.link == 0,"ShapeBase::~ShapeBase: An object is still mounted"); + if( mShapeInstance && (mShapeInstance->getDebrisRefCount() == 0) ) + { + delete mShapeInstance; + } + + CollisionTimeout* ptr = mTimeoutList; + while (ptr) { + CollisionTimeout* cur = ptr; + ptr = ptr->next; + cur->next = sFreeTimeoutList; + sFreeTimeoutList = cur; + } +} + + +//---------------------------------------------------------------------------- + +bool ShapeBase::onAdd() +{ + if(!Parent::onAdd() || !mDataBlock) + return false; + + // Resolve sounds that arrived in the initial update + S32 i; + for (i = 0; i < MaxSoundThreads; i++) + updateAudioState(mSoundThread[i]); + + for (i = 0; i < MaxScriptThreads; i++) + { + Thread& st = mScriptThread[i]; + if(st.thread) + updateThread(st); + } + +/* + if(mDataBlock->cloakTexName != StringTable->insert("")) + mCloakTexture = TextureHandle(mDataBlock->cloakTexName, MeshTexture, false); +*/ + + return true; +} + +void ShapeBase::onRemove() +{ + mConvexList->nukeList(); + + unmount(); + Parent::onRemove(); + + // Stop any running sounds on the client + if (isGhost()) + for (S32 i = 0; i < MaxSoundThreads; i++) + stopAudio(i); + + if ( isClientObject() ) + mCubeReflector.unregisterReflector(); +} + + +void ShapeBase::onSceneRemove() +{ + mConvexList->nukeList(); + Parent::onSceneRemove(); +} + +bool ShapeBase::onNewDataBlock( GameBaseData *dptr ) +{ + ShapeBaseData *prevDB = dynamic_cast( mDataBlock ); + + bool isInitialDataBlock = ( mDataBlock == 0 ); + + if (Parent::onNewDataBlock(dptr) == false) + return false; + + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock) + return false; + + setMaskBits(DamageMask); + mDamageThread = 0; + mHulkThread = 0; + + // Even if loadShape succeeds, there may not actually be + // a shape assigned to this object. + if (bool(mDataBlock->mShape)) { + delete mShapeInstance; + mShapeInstance = new TSShapeInstance(mDataBlock->mShape, isClientObject()); + if (isClientObject()) + mShapeInstance->cloneMaterialList(); + + mObjBox = mDataBlock->mShape->bounds; + resetWorldBox(); + + // Set the initial mesh hidden state. + mMeshHidden.setSize( mDataBlock->mShape->objects.size() ); + mMeshHidden.clear(); + + // Initialize the threads + for (U32 i = 0; i < MaxScriptThreads; i++) { + Thread& st = mScriptThread[i]; + if (st.sequence != -1) { + // TG: Need to see about suppressing non-cyclic sounds + // if the sequences were activated before the object was + // ghosted. + // TG: Cyclic animations need to have a random pos if + // they were started before the object was ghosted. + + // If there was something running on the old shape, the thread + // needs to be reset. Otherwise we assume that it's been + // initialized either by the constructor or from the server. + bool reset = st.thread != 0; + st.thread = 0; + + // New datablock/shape may not actually HAVE this sequence. + // In that case stop playing it. + + AssertFatal( prevDB != NULL, "ShapeBase::onNewDataBlock - how did you have a sequence playing without a prior datablock?" ); + + const TSShape *prevShape = prevDB->mShape; + const TSShape::Sequence &prevSeq = prevShape->sequences[st.sequence]; + const String &prevSeqName = prevShape->names[prevSeq.nameIndex]; + + st.sequence = mDataBlock->mShape->findSequence( prevSeqName ); + + if ( st.sequence != -1 ) + { + setThreadSequence( i, st.sequence, reset ); + } + } + } + + if (mDataBlock->damageSequence != -1) { + mDamageThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mDamageThread, + mDataBlock->damageSequence,0); + } + if (mDataBlock->hulkSequence != -1) { + mHulkThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mHulkThread, + mDataBlock->hulkSequence,0); + } + } + + if( isGhost() ) + reSkin(); + + // + mEnergy = 0; + mDamage = 0; + mDamageState = Enabled; + mRepairReserve = 0; + updateMass(); + updateDamageLevel(); + updateDamageState(); + + mDrag = mDataBlock->drag; + mCameraFov = mDataBlock->cameraDefaultFov; + updateMass(); + + if( !isInitialDataBlock && mLightPlugin ) + mLightPlugin->reset(); + + if ( isClientObject() ) + { + mCubeReflector.unregisterReflector(); + + if ( mDataBlock->reflectorDesc ) + mCubeReflector.registerReflector( this, mDataBlock->reflectorDesc ); + } + + return true; +} + +void ShapeBase::onDeleteNotify(SimObject* obj) +{ + if ( obj == getProcessAfter() ) + clearProcessAfter(); + Parent::onDeleteNotify( obj ); + if ( obj == mMount.object ) + unmount(); + if ( obj == mCurrentWaterObject ) + mCurrentWaterObject = NULL; +} + +void ShapeBase::onImpact(SceneObject* obj, VectorF vec) +{ + if (!isGhost()) { + char buff1[256]; + char buff2[32]; + + dSprintf(buff1,sizeof(buff1),"%g %g %g",vec.x, vec.y, vec.z); + dSprintf(buff2,sizeof(buff2),"%g",vec.len()); + + Con::executef(mDataBlock,"onImpact",scriptThis(), obj ? obj->getIdString() : "", buff1, buff2); + } +} + +void ShapeBase::onImpact(VectorF vec) +{ + if (!isGhost()) { + char buff1[256]; + char buff2[32]; + + dSprintf(buff1,sizeof(buff1),"%g %g %g",vec.x, vec.y, vec.z); + dSprintf(buff2,sizeof(buff2),"%g",vec.len()); + Con::executef(mDataBlock,"onImpact",scriptThis(), "0", buff1, buff2); + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::processTick(const Move* move) +{ + // Energy management + if (mDamageState == Enabled && mDataBlock->inheritEnergyFromMount == false) { + F32 store = mEnergy; + mEnergy += mRechargeRate; + if (mEnergy > mDataBlock->maxEnergy) + mEnergy = mDataBlock->maxEnergy; + else + if (mEnergy < 0) + mEnergy = 0; + + // Virtual setEnergyLevel is used here by some derived classes to + // decide whether they really want to set the energy mask bit. + if (mEnergy != store) + setEnergyLevel(mEnergy); + } + + // Repair management + if (mDataBlock->isInvincible == false) + { + F32 store = mDamage; + mDamage -= mRepairRate; + mDamage = mClampF(mDamage, 0.f, mDataBlock->maxDamage); + + if (mRepairReserve > mDamage) + mRepairReserve = mDamage; + if (mRepairReserve > 0.0) + { + F32 rate = getMin(mDataBlock->repairRate, mRepairReserve); + mDamage -= rate; + mRepairReserve -= rate; + } + + if (store != mDamage) + { + updateDamageLevel(); + if (isServerObject()) { + char delta[100]; + dSprintf(delta,sizeof(delta),"%g",mDamage - store); + setMaskBits(DamageMask); + Con::executef(mDataBlock, "onDamage",scriptThis(),delta); + } + } + } + + if (isServerObject()) { + // Server only... + advanceThreads(TickSec); + updateServerAudio(); + + // update wet state + setImageWetState(0, mWaterCoverage > 0.4); // more than 40 percent covered + + if(mFading) + { + F32 dt = TickMs / 1000.0f; + F32 newFadeET = mFadeElapsedTime + dt; + if(mFadeElapsedTime < mFadeDelay && newFadeET >= mFadeDelay) + setMaskBits(HideCloakMask); + mFadeElapsedTime = newFadeET; + if(mFadeElapsedTime > mFadeTime + mFadeDelay) + { + mFadeVal = F32(!mFadeOut); + mFading = false; + } + } + + //update the rpg effects pos + onMoved(getPosition()); + } + + // Advance images + for (int i = 0; i < MaxMountedImages; i++) + { + if (mMountedImageList[i].dataBlock != NULL) + updateImageState(i, TickSec); + } + + // Call script on trigger state changes + if (move && mDataBlock && isServerObject()) + { + for (S32 i = 0; i < MaxTriggerKeys; i++) + { + if (move->trigger[i] != mTrigger[i]) + { + mTrigger[i] = move->trigger[i]; + char buf1[20],buf2[20]; + dSprintf(buf1,sizeof(buf1),"%d",i); + dSprintf(buf2,sizeof(buf2),"%d",(move->trigger[i]?1:0)); + Con::executef(mDataBlock, "onTrigger",scriptThis(),buf1,buf2); + } + } + } + + // Update the damage flash and the whiteout + // + if (mDamageFlash > 0.0) + { + mDamageFlash -= sDamageFlashDec; + if (mDamageFlash <= 0.0) + mDamageFlash = 0.0; + } + if (mWhiteOut > 0.0) + { + mWhiteOut -= sWhiteoutDec; + if (mWhiteOut <= 0.0) + mWhiteOut = 0.0; + } +} + +void ShapeBase::advanceTime(F32 dt) +{ + // On the client, the shape threads and images are + // advanced at framerate. + advanceThreads(dt); + updateAudioPos(); + for (int i = 0; i < MaxMountedImages; i++) + if (mMountedImageList[i].dataBlock) + updateImageAnimation(i,dt); + + // Cloaking takes 0.5 seconds + if (mCloaked && mCloakLevel != 1.0) { + mCloakLevel += dt * 2; + if (mCloakLevel >= 1.0) + mCloakLevel = 1.0; + } else if (!mCloaked && mCloakLevel != 0.0) { + mCloakLevel -= dt * 2; + if (mCloakLevel <= 0.0) + mCloakLevel = 0.0; + } + if(mInvincibleOn) + updateInvincibleEffect(dt); + + if(mFading) + { + mFadeElapsedTime += dt; + if(mFadeElapsedTime > mFadeTime) + { + mFadeVal = F32(!mFadeOut); + mFading = false; + } + else + { + mFadeVal = mFadeElapsedTime / mFadeTime; + if(mFadeOut) + mFadeVal = 1 - mFadeVal; + } + } +} + +void ShapeBase::setControllingObject(ShapeBase* obj) +{ + if (obj) { + setProcessTick(false); + // Even though we don't processTick, we still need to + // process after the controller in case anyone is mounted + // on this object. + processAfter(obj); + } + else { + setProcessTick(true); + clearProcessAfter(); + // Catch the case of the controlling object actually + // mounted on this object. + if (mControllingObject->mMount.object == this) + mControllingObject->processAfter(this); + } + mControllingObject = obj; +} + +ShapeBase* ShapeBase::getControlObject() +{ + return 0; +} + +void ShapeBase::setControlObject(ShapeBase*) +{ +} + +bool ShapeBase::isFirstPerson() +{ + // Always first person as far as the server is concerned. + if (!isGhost()) + return true; + + if (GameConnection* con = getControllingClient()) + return con->getControlObject() == this && con->isFirstPerson(); + return false; +} + +// Camera: (in degrees) ------------------------------------------------------ +F32 ShapeBase::getCameraFov() +{ + return(mCameraFov); +} + +F32 ShapeBase::getDefaultCameraFov() +{ + return(mDataBlock->cameraDefaultFov); +} + +bool ShapeBase::isValidCameraFov(F32 fov) +{ + return((fov >= mDataBlock->cameraMinFov) && (fov <= mDataBlock->cameraMaxFov)); +} + +void ShapeBase::setCameraFov(F32 fov) +{ + mCameraFov = mClampF(fov, mDataBlock->cameraMinFov, mDataBlock->cameraMaxFov); +} + +//---------------------------------------------------------------------------- +static void scopeCallback(SceneObject* obj, void *conPtr) +{ + NetConnection * ptr = reinterpret_cast(conPtr); + if (obj->isScopeable()) + ptr->objectInScope(obj); +} + +void ShapeBase::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery * query) +{ + // update the camera query + query->camera = this; + // bool grabEye = true; + if(GameConnection * con = dynamic_cast(cr)) + { + // get the fov from the connection (in deg) + F32 fov; + if (con->getControlCameraFov(&fov)) + { + query->fov = mDegToRad(fov/2); + query->sinFov = mSin(query->fov); + query->cosFov = mCos(query->fov); + } + } + + // use eye rather than camera transform (good enough and faster) + MatrixF eyeTransform; + getEyeTransform(&eyeTransform); + eyeTransform.getColumn(3, &query->pos); + eyeTransform.getColumn(1, &query->orientation); + + // Get the visible distance. + query->visibleDistance = gServerSceneGraph->getVisibleDistance(); + + // First, we are certainly in scope, and whatever we're riding is too... + cr->objectInScope(this); + if (isMounted()) + cr->objectInScope(mMount.object); + + if (mSceneManager == NULL) + { + // Scope everything... + gServerContainer.findObjects(0xFFFFFFFF,scopeCallback,cr); + return; + } + + // update the scenemanager + mSceneManager->scopeScene(query->pos, query->visibleDistance, cr); + + // let the (game)connection do some scoping of its own (commandermap...) + cr->doneScopingScene(); +} + + +//---------------------------------------------------------------------------- +F32 ShapeBase::getEnergyLevel() +{ + if ( mDataBlock->inheritEnergyFromMount && mShapeBaseMount ) + return mShapeBaseMount->getEnergyLevel(); + return mEnergy; +} + +F32 ShapeBase::getEnergyValue() +{ + if ( mDataBlock->inheritEnergyFromMount && mShapeBaseMount ) + { + F32 maxEnergy = mShapeBaseMount->mDataBlock->maxEnergy; + if ( maxEnergy > 0.f ) + return (mShapeBaseMount->getEnergyLevel() / maxEnergy); + } + else + { + F32 maxEnergy = mDataBlock->maxEnergy; + if ( maxEnergy > 0.f ) + return (mEnergy / mDataBlock->maxEnergy); + } + + return 0.0f; +} + +void ShapeBase::setEnergyLevel(F32 energy) +{ + if (mDataBlock->inheritEnergyFromMount == false || !mShapeBaseMount) { + if (mDamageState == Enabled) { + mEnergy = (energy > mDataBlock->maxEnergy)? + mDataBlock->maxEnergy: (energy < 0)? 0: energy; + } + } else { + // Pass the set onto whatever we're mounted to... + if ( mShapeBaseMount ) + { + mShapeBaseMount->setEnergyLevel(energy); + } + } +} + +void ShapeBase::setDamageLevel(F32 damage) +{ + if (!mDataBlock->isInvincible) { + F32 store = mDamage; + mDamage = mClampF(damage, 0.f, mDataBlock->maxDamage); + + if (store != mDamage) { + updateDamageLevel(); + if (isServerObject()) { + setMaskBits(DamageMask); + char delta[100]; + dSprintf(delta,sizeof(delta),"%g",mDamage - store); + Con::executef(mDataBlock, "onDamage",scriptThis(),delta); + } + } + } +} + +void ShapeBase::updateContainer() +{ + // Update container drag and buoyancy properties + + // Set default values. + mDrag = mDataBlock->drag; + mBuoyancy = 0.0f; + mGravityMod = 1.0; + mAppliedForce.set(0,0,0); + + ContainerQueryInfo info; + info.box = getWorldBox(); + info.mass = getMass(); + + mContainer->findObjects(info.box, WaterObjectType|PhysicalZoneObjectType,findRouter,&info); + + mWaterCoverage = info.waterCoverage; + mLiquidType = info.liquidType; + mLiquidHeight = info.waterHeight; + setCurrentWaterObject( info.waterObject ); + + // This value might be useful as a datablock value, + // This is what allows the player to stand in shallow water (below this coverage) + // without jiggling from buoyancy + if (mWaterCoverage >= 0.25f) + { + // water viscosity is used as drag for in water. + // ShapeBaseData drag is used for drag outside of water. + // Combine these two components to calculate this ShapeBase object's + // current drag. + mDrag = ( info.waterCoverage * info.waterViscosity ) + + ( 1.0f - info.waterCoverage ) * mDataBlock->drag; + mBuoyancy = (info.waterDensity / mDataBlock->density) * info.waterCoverage; + } + + mAppliedForce = info.appliedForce; + mGravityMod = info.gravityScale; + + //Con::printf( "WaterCoverage: %f", mWaterCoverage ); + //Con::printf( "Drag: %f", mDrag ); +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::applyRepair(F32 amount) +{ + // Repair increases the repair reserve + if (amount > 0 && ((mRepairReserve += amount) > mDamage)) + mRepairReserve = mDamage; +} + +void ShapeBase::applyDamage(F32 amount) +{ + if (amount > 0) + setDamageLevel(mDamage + amount); +} + +F32 ShapeBase::getDamageValue() +{ + // Return a 0-1 damage value. + return mDamage / mDataBlock->maxDamage; +} + +void ShapeBase::updateDamageLevel() +{ + if (mDamageThread) { + // mDamage is already 0-1 on the client + if (mDamage >= mDataBlock->destroyedLevel) { + if (getDamageState() == Destroyed) + mShapeInstance->setPos(mDamageThread, 0); + else + mShapeInstance->setPos(mDamageThread, 1); + } else { + mShapeInstance->setPos(mDamageThread, mDamage / mDataBlock->destroyedLevel); + } + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::setDamageState(DamageState state) +{ + if (mDamageState == state) + return; + + const char* script = 0; + const char* lastState = 0; + + if (!isGhost()) { + if (state != getDamageState()) + setMaskBits(DamageMask); + + lastState = getDamageStateName(); + switch (state) { + case Destroyed: { + if (mDamageState == Enabled) + setDamageState(Disabled); + script = "onDestroyed"; + break; + } + case Disabled: + if (mDamageState == Enabled) + script = "onDisabled"; + break; + case Enabled: + script = "onEnabled"; + break; + default: + AssertFatal(false, "Invalid damage state"); + break; + } + } + + mDamageState = state; + if (mDamageState != Enabled) { + mRepairReserve = 0; + mEnergy = 0; + } + if (script) { + // Like to call the scripts after the state has been intialize. + // This should only end up being called on the server. + Con::executef(mDataBlock, script,scriptThis(),lastState); + } + updateDamageState(); + updateDamageLevel(); +} + +bool ShapeBase::setDamageState(const char* state) +{ + for (S32 i = 0; i < NumDamageStates; i++) + if (!dStricmp(state,sDamageStateName[i])) { + setDamageState(DamageState(i)); + return true; + } + return false; +} + +const char* ShapeBase::getDamageStateName() +{ + return sDamageStateName[mDamageState]; +} + +void ShapeBase::updateDamageState() +{ + if (mHulkThread) { + F32 pos = (mDamageState == Destroyed) ? 1.0f : 0.0f; + if (mShapeInstance->getPos(mHulkThread) != pos) { + mShapeInstance->setPos(mHulkThread,pos); + + if (isClientObject()) + mShapeInstance->animate(); + } + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::blowUp() +{ + Point3F center; + mObjBox.getCenter(¢er); + center += getPosition(); + MatrixF trans = getTransform(); + trans.setPosition( center ); + + // explode + Explosion* pExplosion = NULL; + + if( pointInWater( (Point3F &)center ) && mDataBlock->underwaterExplosion ) + { + pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->underwaterExplosion); + } + else + { + if (mDataBlock->explosion) + { + pExplosion = new Explosion; + pExplosion->onNewDataBlock(mDataBlock->explosion); + } + } + + if( pExplosion ) + { + pExplosion->setTransform(trans); + pExplosion->setInitialState(center, damageDir); + if (pExplosion->registerObject() == false) + { + Con::errorf(ConsoleLogEntry::General, "ShapeBase(%s)::explode: couldn't register explosion", + mDataBlock->getName() ); + delete pExplosion; + pExplosion = NULL; + } + } + + TSShapeInstance *debShape = NULL; + + if( mDataBlock->debrisShape == NULL ) + { + return; + } + else + { + debShape = new TSShapeInstance( mDataBlock->debrisShape, true); + } + + + Vector< TSPartInstance * > partList; + TSPartInstance::breakShape( debShape, 0, partList, NULL, NULL, 0 ); + + if( !mDataBlock->debris ) + { + mDataBlock->debris = new DebrisData; + } + + // cycle through partlist and create debris pieces + for( U32 i=0; isetPartInstance( partList[i] ); + debris->init( center, randomDir ); + debris->onNewDataBlock( mDataBlock->debris ); + debris->setTransform( trans ); + + if( !debris->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register debris for class: %s", mDataBlock->getName() ); + delete debris; + debris = NULL; + } + else + { + debShape->incDebrisRefCount(); + } + } + + damageDir.set(0, 0, 1); +} + + +//---------------------------------------------------------------------------- +void ShapeBase::mountObject(SceneObject* obj,U32 node) +{ + //if (obj->mMount.object == this) + // return; + + if (obj->mMount.object) + obj->unmount(); + + obj->mMount.object = this; + obj->mMount.node = (node >= 0 && node < ShapeBaseData::NumMountPoints)? node: 0; + obj->mMount.link = mMount.list; + mMount.list = obj; + + obj->onMount( this, node ); +} + + +void ShapeBase::unmountObject(SceneObject* obj) +{ + if ( obj->mMount.object == this ) + { + // Find and unlink the object + for ( SceneObject **ptr = &mMount.list; *ptr; ptr = &(*ptr)->mMount.link ) + { + if ( *ptr == obj ) + { + *ptr = obj->mMount.link; + break; + } + } + + obj->mMount.object = 0; + obj->mMount.link = 0; + + obj->onUnmount( this, obj->mMount.node ); + } +} + +void ShapeBase::unmount() +{ + if (mMount.object) + mMount.object->unmountObject(this); +} + +void ShapeBase::onMount( SceneObject *obj, S32 node ) +{ + mConvexList->nukeList(); + deleteNotify(obj); + + // Are we mounting to a ShapeBase object? + mShapeBaseMount = dynamic_cast( obj ); + + if ( mShapeBaseMount && mShapeBaseMount->getControllingObject() != this ) + processAfter( mShapeBaseMount ); + + if (!isGhost()) { + setMaskBits(MountedMask); + char buff1[32]; + dSprintf(buff1,sizeof(buff1),"%d",node); + Con::executef(mDataBlock, "onMount",scriptThis(),obj->scriptThis(),buff1); + } +} + +void ShapeBase::onUnmount( SceneObject *obj, S32 node ) +{ + clearNotify(obj); + + if ( mShapeBaseMount && mShapeBaseMount->getControlObject() != this ) + clearProcessAfter(); + + mShapeBaseMount = NULL; + + if (!isGhost()) { + setMaskBits(MountedMask); + char buff1[32]; + dSprintf(buff1,sizeof(buff1),"%d",node); + Con::executef(mDataBlock, "onUnmount",scriptThis(),obj->scriptThis(),buff1); + } +} + +Point3F ShapeBase::getAIRepairPoint() +{ + if (mDataBlock->mountPointNode[ShapeBaseData::AIRepairNode] < 0) + return Point3F(0, 0, 0); + MatrixF xf(true); + getMountTransform(ShapeBaseData::AIRepairNode,&xf); + Point3F pos(0, 0, 0); + xf.getColumn(3,&pos); + return pos; +} + +//---------------------------------------------------------------------------- + +void ShapeBase::getEyeTransform(MatrixF* mat) +{ + // Returns eye to world space transform + S32 eyeNode = mDataBlock->eyeNode; + if (eyeNode != -1) + mat->mul(getTransform(), mShapeInstance->mNodeTransforms[eyeNode]); + else + *mat = getTransform(); +} + +void ShapeBase::getRenderEyeTransform(MatrixF* mat) +{ + // Returns eye to world space transform + S32 eyeNode = mDataBlock->eyeNode; + if (eyeNode != -1) + mat->mul(getRenderTransform(), mShapeInstance->mNodeTransforms[eyeNode]); + else + *mat = getRenderTransform(); +} + +void ShapeBase::getCameraTransform(F32* pos,MatrixF* mat) +{ + // Returns camera to world space transform + // Handles first person / third person camera position + + if (isServerObject() && mShapeInstance) + mShapeInstance->animateNodeSubtrees(true); + + if (*pos != 0) + { + F32 min,max; + Point3F offset; + MatrixF eye,rot; + getCameraParameters(&min,&max,&offset,&rot); + getRenderEyeTransform(&eye); + mat->mul(eye,rot); + + // Use the eye transform to orient the camera + VectorF vp,vec; + vp.x = vp.z = 0; + vp.y = -(max - min) * *pos; + eye.mulV(vp,&vec); + + // Use the camera node's pos. + Point3F osp,sp; + if (mDataBlock->cameraNode != -1) { + mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp); + + // Scale the camera position before applying the transform + const Point3F& scale = getScale(); + osp.convolve( scale ); + + getRenderTransform().mulP(osp,&sp); + } + else + getRenderTransform().getColumn(3,&sp); + + // Make sure we don't extend the camera into anything solid + Point3F ep = sp + vec + offset; + disableCollision(); + if (isMounted()) + getObjectMount()->disableCollision(); + RayInfo collision; + if( mContainer && mContainer->castRay(sp, ep, + (0xFFFFFFFF & ~(WaterObjectType | + GameBaseObjectType | + TriggerObjectType | + DefaultObjectType)), + &collision) == true) { + F32 vecLenSq = vec.lenSquared(); + F32 adj = (-mDot(vec, collision.normal) / vecLenSq) * 0.1; + F32 newPos = getMax(0.0f, collision.t - adj); + if (newPos == 0.0f) + eye.getColumn(3,&ep); + else + ep = sp + offset + (vec * newPos); + } + mat->setColumn(3,ep); + if (isMounted()) + getObjectMount()->enableCollision(); + enableCollision(); + } + else + { + getRenderEyeTransform(mat); + } + + // Apply Camera FX. + mat->mul( gCamFXMgr.getTrans() ); +} + +// void ShapeBase::getCameraTransform(F32* pos,MatrixF* mat) +// { +// // Returns camera to world space transform +// // Handles first person / third person camera position + +// if (isServerObject() && mShapeInstance) +// mShapeInstance->animateNodeSubtrees(true); + +// if (*pos != 0) { +// F32 min,max; +// Point3F offset; +// MatrixF eye,rot; +// getCameraParameters(&min,&max,&offset,&rot); +// getRenderEyeTransform(&eye); +// mat->mul(eye,rot); + +// // Use the eye transform to orient the camera +// VectorF vp,vec; +// vp.x = vp.z = 0; +// vp.y = -(max - min) * *pos; +// eye.mulV(vp,&vec); + +// // Use the camera node's pos. +// Point3F osp,sp; +// if (mDataBlock->cameraNode != -1) { +// mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp); +// getRenderTransform().mulP(osp,&sp); +// } +// else +// getRenderTransform().getColumn(3,&sp); + +// // Make sure we don't extend the camera into anything solid +// Point3F ep = sp + vec; +// ep += offset; +// disableCollision(); +// if (isMounted()) +// getObjectMount()->disableCollision(); +// RayInfo collision; +// if (mContainer->castRay(sp,ep,(0xFFFFFFFF & ~(WaterObjectType|ForceFieldObjectType|GameBaseObjectType|DefaultObjectType)),&collision)) { +// *pos = collision.t *= 0.9; +// if (*pos == 0) +// eye.getColumn(3,&ep); +// else +// ep = sp + vec * *pos; +// } +// mat->setColumn(3,ep); +// if (isMounted()) +// getObjectMount()->enableCollision(); +// enableCollision(); +// } +// else +// { +// getRenderEyeTransform(mat); +// } +// } + + +// void ShapeBase::getRenderCameraTransform(F32* pos,MatrixF* mat) +// { +// // Returns camera to world space transform +// // Handles first person / third person camera position + +// if (isServerObject() && mShapeInstance) +// mShapeInstance->animateNodeSubtrees(true); + +// if (*pos != 0) { +// F32 min,max; +// Point3F offset; +// MatrixF eye,rot; +// getCameraParameters(&min,&max,&offset,&rot); +// getRenderEyeTransform(&eye); +// mat->mul(eye,rot); + +// // Use the eye transform to orient the camera +// VectorF vp,vec; +// vp.x = vp.z = 0; +// vp.y = -(max - min) * *pos; +// eye.mulV(vp,&vec); + +// // Use the camera node's pos. +// Point3F osp,sp; +// if (mDataBlock->cameraNode != -1) { +// mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp); +// getRenderTransform().mulP(osp,&sp); +// } +// else +// getRenderTransform().getColumn(3,&sp); + +// // Make sure we don't extend the camera into anything solid +// Point3F ep = sp + vec; +// ep += offset; +// disableCollision(); +// if (isMounted()) +// getObjectMount()->disableCollision(); +// RayInfo collision; +// if (mContainer->castRay(sp,ep,(0xFFFFFFFF & ~(WaterObjectType|ForceFieldObjectType|GameBaseObjectType|DefaultObjectType)),&collision)) { +// *pos = collision.t *= 0.9; +// if (*pos == 0) +// eye.getColumn(3,&ep); +// else +// ep = sp + vec * *pos; +// } +// mat->setColumn(3,ep); +// if (isMounted()) +// getObjectMount()->enableCollision(); +// enableCollision(); +// } +// else +// { +// getRenderEyeTransform(mat); +// } +// } + +void ShapeBase::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot) +{ + *min = mDataBlock->cameraMinDist; + *max = mDataBlock->cameraMaxDist; + off->set(0,0,0); + rot->identity(); +} + + +//---------------------------------------------------------------------------- +F32 ShapeBase::getDamageFlash() const +{ + return mDamageFlash; +} + +void ShapeBase::setDamageFlash(const F32 flash) +{ + mDamageFlash = flash; + if (mDamageFlash < 0.0) + mDamageFlash = 0; + else if (mDamageFlash > 1.0) + mDamageFlash = 1.0; +} + + +//---------------------------------------------------------------------------- +F32 ShapeBase::getWhiteOut() const +{ + return mWhiteOut; +} + +void ShapeBase::setWhiteOut(const F32 flash) +{ + mWhiteOut = flash; + if (mWhiteOut < 0.0) + mWhiteOut = 0; + else if (mWhiteOut > 1.5) + mWhiteOut = 1.5; +} + + +//---------------------------------------------------------------------------- + +bool ShapeBase::onlyFirstPerson() const +{ + return mDataBlock->firstPersonOnly; +} + +bool ShapeBase::useObjsEyePoint() const +{ + return mDataBlock->useEyePoint; +} + + +//---------------------------------------------------------------------------- +F32 ShapeBase::getInvincibleEffect() const +{ + return mInvincibleEffect; +} + +void ShapeBase::setupInvincibleEffect(F32 time, F32 speed) +{ + if(isClientObject()) + { + mInvincibleCount = mInvincibleTime = time; + mInvincibleSpeed = mInvincibleDelta = speed; + mInvincibleEffect = 0.0f; + mInvincibleOn = true; + mInvincibleFade = 1.0f; + } + else + { + mInvincibleTime = time; + mInvincibleSpeed = speed; + setMaskBits(InvincibleMask); + } +} + +void ShapeBase::updateInvincibleEffect(F32 dt) +{ + if(mInvincibleCount > 0.0f ) + { + if(mInvincibleEffect >= ((0.3 * mInvincibleFade) + 0.05f) && mInvincibleDelta > 0.0f) + mInvincibleDelta = -mInvincibleSpeed; + else if(mInvincibleEffect <= 0.05f && mInvincibleDelta < 0.0f) + { + mInvincibleDelta = mInvincibleSpeed; + mInvincibleFade = mInvincibleCount / mInvincibleTime; + } + mInvincibleEffect += mInvincibleDelta; + mInvincibleCount -= dt; + } + else + { + mInvincibleEffect = 0.0f; + mInvincibleOn = false; + } +} + +//---------------------------------------------------------------------------- +void ShapeBase::setVelocity(const VectorF&) +{ +} + +void ShapeBase::applyImpulse(const Point3F&,const VectorF&) +{ +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::playAudio(U32 slot,SFXProfile* profile) +{ + AssertFatal( slot < MaxSoundThreads, "ShapeBase::playAudio() bad slot index" ); + Sound& st = mSoundThread[slot]; + if ( profile && ( !st.play || st.profile != profile ) ) + { + setMaskBits(SoundMaskN << slot); + st.play = true; + st.profile = profile; + updateAudioState(st); + } +} + +void ShapeBase::stopAudio(U32 slot) +{ + AssertFatal( slot < MaxSoundThreads, "ShapeBase::stopAudio() bad slot index" ); + + Sound& st = mSoundThread[slot]; + if ( st.play ) + { + st.play = false; + setMaskBits(SoundMaskN << slot); + updateAudioState(st); + } +} + +void ShapeBase::updateServerAudio() +{ + // Timeout non-looping sounds + for (int i = 0; i < MaxSoundThreads; i++) { + Sound& st = mSoundThread[i]; + if (st.play && st.timeout && st.timeout < Sim::getCurrentTime()) { + clearMaskBits(SoundMaskN << i); + st.play = false; + } + } +} + +void ShapeBase::updateAudioState(Sound& st) +{ + SFX_DELETE( st.sound ); + + if ( st.play && st.profile ) + { + if ( isGhost() ) + { + if ( Sim::findObject( SimObjectId( st.profile ), st.profile ) ) + { + st.sound = SFX->createSource( st.profile, &getTransform() ); + if ( st.sound ) + st.sound->play(); + } + else + st.play = false; + } + else + { + // Non-looping sounds timeout on the server + st.timeout = 0; + if ( !st.profile->getDescription()->mIsLooping ) + st.timeout = Sim::getCurrentTime() + sAudioTimeout; + } + } + else + st.play = false; +} + +void ShapeBase::updateAudioPos() +{ + for (int i = 0; i < MaxSoundThreads; i++) + { + SFXSource* source = mSoundThread[i].sound; + if ( source ) + source->setTransform( getTransform() ); + } +} + +//---------------------------------------------------------------------------- + +const char *ShapeBase::getThreadSequenceName( U32 slot ) +{ + Thread& st = mScriptThread[slot]; + if ( st.sequence == -1 ) + { + // Invalid Animation. + return ""; + } + + // Name Index + const U32 nameIndex = getShape()->sequences[st.sequence].nameIndex; + + // Return Name. + return getShape()->getName( nameIndex ); +} + +bool ShapeBase::setThreadSequence(U32 slot,S32 seq,bool reset) +{ + Thread& st = mScriptThread[slot]; + if (st.thread && st.sequence == seq && st.state == Thread::Play) + return true; + + if (seq < MaxSequenceIndex) { + setMaskBits(ThreadMaskN << slot); + st.sequence = seq; + if (reset) { + st.state = Thread::Play; + st.atEnd = false; + st.timescale = 1.f; + st.position = 0.f; + } + if (mShapeInstance) { + if (!st.thread) + st.thread = mShapeInstance->addThread(); + mShapeInstance->setSequence(st.thread,seq,0); + stopThreadSound(st); + updateThread(st); + } + return true; + } + return false; +} + +void ShapeBase::updateThread(Thread& st) +{ + switch (st.state) + { + case Thread::Stop: + { + mShapeInstance->setTimeScale( st.thread, 1.f ); + mShapeInstance->setPos( st.thread, 0.f ); + } // Drop through to pause state + + case Thread::Pause: + { + if ( st.position != -1.f ) + { + mShapeInstance->setTimeScale( st.thread, 1.f ); + mShapeInstance->setPos( st.thread, st.position ); + } + + mShapeInstance->setTimeScale( st.thread, 0.f ); + stopThreadSound( st ); + } break; + + case Thread::Play: + { + if (st.atEnd) + { + mShapeInstance->setTimeScale(st.thread,1); + mShapeInstance->setPos( st.thread, ( st.timescale > 0.f ) ? 1.0f : 0.0f ); + mShapeInstance->setTimeScale(st.thread,0); + stopThreadSound(st); + st.state = Thread::Stop; + } + else + { + if ( st.position != -1.f ) + { + mShapeInstance->setTimeScale( st.thread, 1.f ); + mShapeInstance->setPos( st.thread, st.position ); + } + + mShapeInstance->setTimeScale(st.thread, st.timescale ); + if (!st.sound) + { + startSequenceSound(st); + } + } + } break; + } +} + +bool ShapeBase::stopThread(U32 slot) +{ + Thread& st = mScriptThread[slot]; + if (st.sequence != -1 && st.state != Thread::Stop) { + setMaskBits(ThreadMaskN << slot); + st.state = Thread::Stop; + updateThread(st); + return true; + } + return false; +} + +bool ShapeBase::pauseThread(U32 slot) +{ + Thread& st = mScriptThread[slot]; + if (st.sequence != -1 && st.state != Thread::Pause) { + setMaskBits(ThreadMaskN << slot); + st.state = Thread::Pause; + updateThread(st); + return true; + } + return false; +} + +bool ShapeBase::playThread(U32 slot) +{ + Thread& st = mScriptThread[slot]; + if (st.sequence != -1 && st.state != Thread::Play) { + setMaskBits(ThreadMaskN << slot); + st.state = Thread::Play; + updateThread(st); + return true; + } + return false; +} + +bool ShapeBase::setThreadPosition( U32 slot, F32 pos ) +{ + Thread& st = mScriptThread[slot]; + if (st.sequence != -1) + { + setMaskBits(ThreadMaskN << slot); + st.position = pos; + st.atEnd = false; + updateThread(st); + + return true; + } + return false; +} + +bool ShapeBase::setThreadDir(U32 slot,bool forward) +{ + Thread& st = mScriptThread[slot]; + if (st.sequence != -1) + { + if ( ( st.timescale >= 0.f ) != forward ) + { + setMaskBits(ThreadMaskN << slot); + st.timescale *= -1.f ; + st.atEnd = false; + updateThread(st); + } + return true; + } + return false; +} + +bool ShapeBase::setThreadTimeScale( U32 slot, F32 timeScale ) +{ + Thread& st = mScriptThread[slot]; + if (st.sequence != -1) + { + if (st.timescale != timeScale) + { + setMaskBits(ThreadMaskN << slot); + st.timescale = timeScale; + updateThread(st); + } + return true; + } + return false; +} + +void ShapeBase::stopThreadSound(Thread& thread) +{ + if (thread.sound) { + } +} + +void ShapeBase::startSequenceSound(Thread& thread) +{ + if (!isGhost() || !thread.thread) + return; + stopThreadSound(thread); +} + +void ShapeBase::advanceThreads(F32 dt) +{ + for (U32 i = 0; i < MaxScriptThreads; i++) { + Thread& st = mScriptThread[i]; + if (st.thread) { + if (!mShapeInstance->getShape()->sequences[st.sequence].isCyclic() && !st.atEnd && + ( ( st.timescale > 0.f )? mShapeInstance->getPos(st.thread) >= 1.0: + mShapeInstance->getPos(st.thread) <= 0)) { + st.atEnd = true; + updateThread(st); + if (!isGhost()) { + char slot[16]; + dSprintf(slot,sizeof(slot),"%d",i); + Con::executef(mDataBlock, "onEndSequence",scriptThis(),slot); + } + } + mShapeInstance->advanceTime(dt,st.thread); + } + } +} + +//---------------------------------------------------------------------------- + +/// Emit particles on the given emitter, if we're within triggerHeight above some static surface with a +/// material that has 'showDust' set to true. The particles will have a lifetime of 'numMilliseconds' +/// and be emitted at the given offset from the contact point having a direction of 'axis'. + +void ShapeBase::emitDust( ParticleEmitter* emitter, F32 triggerHeight, const Point3F& offset, U32 numMilliseconds, const Point3F& axis ) +{ + if( !emitter ) + return; + + Point3F startPos = getPosition(); + Point3F endPos = startPos + Point3F( 0.0, 0.0, - triggerHeight ); + + RayInfo rayInfo; + if( getContainer()->castRay( startPos, endPos, STATIC_COLLISION_MASK, &rayInfo ) ) + { + Material* material = ( rayInfo.material ? dynamic_cast< Material* >( rayInfo.material->getMaterial() ) : 0 ); + if( material && material->mShowDust ) + { + ColorF colorList[ ParticleData::PDC_NUM_KEYS ]; + + for( U32 x = 0; x < getMin( Material::NUM_EFFECT_COLOR_STAGES, ParticleData::PDC_NUM_KEYS ); ++ x ) + colorList[ x ] = material->mEffectColor[ x ]; + for( U32 x = Material::NUM_EFFECT_COLOR_STAGES; x < ParticleData::PDC_NUM_KEYS; ++ x ) + colorList[ x ].set( 1.0, 1.0, 1.0, 0.0 ); + + emitter->setColors( colorList ); + + Point3F contactPoint = rayInfo.point + offset; + emitter->emitParticles( contactPoint, true, ( axis == Point3F::Zero ? rayInfo.normal : axis ), + getVelocity(), numMilliseconds ); + } + } +} + +//---------------------------------------------------------------------------- + +TSShape const* ShapeBase::getShape() +{ + return mShapeInstance? mShapeInstance->getShape(): 0; +} + +bool ShapeBase::prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState) +{ + return _prepRenderImage( state, stateKey, startZone, modifyBaseState, true, true ); +} + +bool ShapeBase::_prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState, + bool renderSelf, + bool renderMountedImages ) +{ + AssertFatal(modifyBaseState == false, "Error, should never be called with this parameter set"); + AssertFatal(startZone == 0xFFFFFFFF, "Error, startZone should indicate -1"); + + PROFILE_SCOPE( ShapeBase_PrepRenderImage ); + + if (isLastState(state, stateKey)) + return false; + + setLastState(state, stateKey); + + //if ( mIsCubemapUpdate ) + // return false; + + if( ( getDamageState() == Destroyed ) && ( !mDataBlock->renderWhenDestroyed ) ) + return false; + + // We don't need to render if all the meshes are forced hidden. + if ( mMeshHidden.testAll() ) + return false; + + // If we're rendering shadows don't render the mounted + // images unless the shape is also rendered. + if ( state->isShadowPass() && !renderSelf ) + return false; + + // If we're currently rendering our own reflection we + // don't want to render ourselves into it. + if ( mCubeReflector.isRendering() ) + return false; + + // We force all the shapes to use the highest detail + // if we're the control object or mounted. + bool forceHighestDetail = false; + { + GameConnection *con = GameConnection::getConnectionToServer(); + ShapeBase *co = NULL; + if(con && ( (co = dynamic_cast(con->getControlObject())) != NULL) ) + { + if(co == this || co->getObjectMount() == this) + forceHighestDetail = true; + } + } + + if ( state->isObjectRendered(this) || state->isReflectPass() ) + { + mLastRenderFrame = sLastRenderFrame; + + // get shape detail...we might not even need to be drawn + Point3F cameraOffset = getWorldBox().getClosestPoint( state->getDiffuseCameraPosition() ) - state->getDiffuseCameraPosition(); + F32 dist = cameraOffset.len(); + if (dist < 0.01f) + dist = 0.01f; + + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + + if (mShapeInstance) + { + if ( forceHighestDetail ) + mShapeInstance->setCurrentDetail( 0 ); + else + mShapeInstance->setDetailFromDistance( state, dist * invScale ); + + mShapeInstance->animate(); + } + + if ( ( mShapeInstance && mShapeInstance->getCurrentDetail() < 0 ) || + ( !mShapeInstance && !gShowBoundingBox ) ) + { + // no, don't draw anything + return false; + } + + if( renderMountedImages ) + { + for (U32 i = 0; i < MaxMountedImages; i++) + { + MountedImage& image = mMountedImageList[i]; + if (image.dataBlock && image.shapeInstance) + { + // Select detail levels on mounted items but... always + // draw the control object's mounted images in high detail. + if ( forceHighestDetail ) + image.shapeInstance->setCurrentDetail( 0 ); + else + image.shapeInstance->setDetailFromDistance( state, dist * invScale ); + + if (mCloakLevel == 0.0f && image.shapeInstance->hasSolid() && mFadeVal == 1.0f) + { + prepBatchRender( state, i ); + + // Debug rendering of the mounted shape bounds. + if ( gShowBoundingBox ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &ShapeBase::_renderBoundingBox ); + ri->objectIndex = i; + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + } + } + } + } + + // Debug rendering of the shape bounding box. + if ( gShowBoundingBox ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &ShapeBase::_renderBoundingBox ); + ri->objectIndex = -1; + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + + if ( mShapeInstance && renderSelf ) + prepBatchRender( state, -1 ); + + calcClassRenderData(); + } + + return false; +} + +//---------------------------------------------------------------------------- +// prepBatchRender +//---------------------------------------------------------------------------- +void ShapeBase::prepBatchRender(SceneState* state, S32 mountedImageIndex ) +{ + // CHANGES IN HERE SHOULD BE DUPLICATED IN TSSTATIC! + + GFXTransformSaver saver; + + // Set up our TS render state. + TSRenderState rdata; + rdata.setSceneState( state ); + if ( mCubeReflector.isEnabled() ) + rdata.setCubemap( mCubeReflector.getCubemap() ); + rdata.setFadeOverride( mFadeVal ); + + LightManager *lm = gClientSceneGraph->getLightManager(); + if ( !state->isShadowPass() ) + lm->setupLights( this, getWorldSphere() ); + + if( mountedImageIndex != -1 ) + { + MountedImage& image = mMountedImageList[mountedImageIndex]; + + if( image.dataBlock && image.shapeInstance ) + { + renderMountedImage( mountedImageIndex, rdata ); + } + } + else + { + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + GFX->setWorldMatrix( mat ); + + if ( state->isDiffusePass() && mCubeReflector.isEnabled() && mCubeReflector.getOcclusionQuery() ) + { + RenderPassManager *pass = state->getRenderPass(); + + OccluderRenderInst *ri = pass->allocInst(); + + ri->type = RenderPassManager::RIT_Occluder; + ri->query = mCubeReflector.getOcclusionQuery(); + mObjToWorld.mulP( mObjBox.getCenter(), &ri->position ); + ri->scale.set( mObjBox.getExtents() ); + ri->orientation = pass->allocUniqueXform( mObjToWorld ); + ri->isSphere = false; + state->getRenderPass()->addInst( ri ); + } + + mShapeInstance->animate(); + mShapeInstance->render( rdata ); + } + + lm->resetLights(); +} + +void ShapeBase::renderMountedImage( U32 imageSlot, TSRenderState &rstate ) +{ + GFX->pushWorldMatrix(); + + MatrixF mat; + getRenderImageTransform(imageSlot, &mat, rstate.getSceneState()->isShadowPass()); + GFX->setWorldMatrix( mat ); + + MountedImage& image = mMountedImageList[imageSlot]; + image.shapeInstance->animate(); + image.shapeInstance->render( rstate ); + + GFX->popWorldMatrix(); +} + +void ShapeBase::_renderBoundingBox( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + // If we got an override mat then this some + // special rendering pass... skip out of it. + if ( overrideMat ) + return; + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setBlend( true ); + desc.fillMode = GFXFillWireframe; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + if ( ri->objectIndex != -1 ) + { + MountedImage &image = mMountedImageList[ ri->objectIndex ]; + + if ( image.shapeInstance ) + { + MatrixF mat; + getRenderImageTransform( ri->objectIndex, &mat ); + + const Box3F &objBox = image.shapeInstance->getShape()->bounds; + + drawer->drawCube( desc, objBox, ColorI( 255, 255, 255 ), &mat ); + } + } + else + drawer->drawCube( desc, mObjBox, ColorI( 255, 255, 255 ), &mRenderObjToWorld ); +} + +bool ShapeBase::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + if (mShapeInstance) + { + RayInfo shortest; + shortest.t = 1e8; + + info->object = NULL; + for (U32 i = 0; i < mDataBlock->LOSDetails.size(); i++) + { + mShapeInstance->animate(mDataBlock->LOSDetails[i]); + if (mShapeInstance->castRay(start, end, info, mDataBlock->LOSDetails[i])) + { + info->object = this; + if (info->t < shortest.t) + shortest = *info; + } + } + + if (info->object == this) + { + // Copy out the shortest time... + *info = shortest; + return true; + } + } + + return false; +} + +bool ShapeBase::castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info) +{ + if (mShapeInstance) + { + RayInfo localInfo; + mShapeInstance->animate(); + bool res = mShapeInstance->castRayRendered(start, end, &localInfo, mShapeInstance->getCurrentDetail()); + if (res) + { + *info = localInfo; + info->object = this; + return true; + } + } + + return false; +} + + +//---------------------------------------------------------------------------- + +bool ShapeBase::buildPolyList(AbstractPolyList* polyList, const Box3F &, const SphereF &) +{ + if (mShapeInstance) { + bool ret = false; + + polyList->setTransform(&mObjToWorld, mObjScale); + polyList->setObject(this); + + for (U32 i = 0; i < mDataBlock->collisionDetails.size(); i++) + { + mShapeInstance->buildPolyList(polyList,mDataBlock->collisionDetails[i]); + ret = true; + } + + return ret; + } + + return false; +} + +bool ShapeBase::buildRenderedPolyList(AbstractPolyList* polyList, const Box3F &, const SphereF &) +{ + if (mShapeInstance) { + polyList->setTransform(&mObjToWorld, mObjScale); + polyList->setObject(this); + + mShapeInstance->animate(); + mShapeInstance->buildPolyList(polyList,mShapeInstance->getCurrentDetail()); + + return true; + } + + return false; +} + + +void ShapeBase::buildConvex(const Box3F& box, Convex* convex) +{ + if (mShapeInstance == NULL) + return; + + // These should really come out of a pool + mConvexList->collectGarbage(); + + Box3F realBox = box; + mWorldToObj.mul(realBox); + realBox.minExtents.convolveInverse(mObjScale); + realBox.maxExtents.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + for (U32 i = 0; i < mDataBlock->collisionDetails.size(); i++) + { + Box3F newbox = mDataBlock->collisionBounds[i]; + newbox.minExtents.convolve(mObjScale); + newbox.maxExtents.convolve(mObjScale); + mObjToWorld.mul(newbox); + if (box.isOverlapped(newbox) == false) + continue; + + // See if this hull exists in the working set already... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { + if (itr->mConvex->getType() == ShapeBaseConvexType && + (static_cast(itr->mConvex)->pShapeBase == this && + static_cast(itr->mConvex)->hullId == i)) { + cc = itr->mConvex; + break; + } + } + if (cc) + continue; + + // Create a new convex. + ShapeBaseConvex* cp = new ShapeBaseConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->mObject = this; + cp->pShapeBase = this; + cp->hullId = i; + cp->box = mDataBlock->collisionBounds[i]; + cp->transform = 0; + cp->findNodeTransform(); + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::queueCollision( SceneObject *obj, const VectorF &vec) +{ + // Add object to list of collisions. + SimTime time = Sim::getCurrentTime(); + S32 num = obj->getId(); + + CollisionTimeout** adr = &mTimeoutList; + CollisionTimeout* ptr = mTimeoutList; + while (ptr) { + if (ptr->objectNumber == num) { + if (ptr->expireTime < time) { + ptr->expireTime = time + CollisionTimeoutValue; + ptr->object = obj; + ptr->vector = vec; + } + return; + } + // Recover expired entries + if (ptr->expireTime < time) { + CollisionTimeout* cur = ptr; + *adr = ptr->next; + ptr = ptr->next; + cur->next = sFreeTimeoutList; + sFreeTimeoutList = cur; + } + else { + adr = &ptr->next; + ptr = ptr->next; + } + } + + // New entry for the object + if (sFreeTimeoutList != NULL) + { + ptr = sFreeTimeoutList; + sFreeTimeoutList = ptr->next; + ptr->next = NULL; + } + else + { + ptr = sTimeoutChunker.alloc(); + } + + ptr->object = obj; + ptr->objectNumber = obj->getId(); + ptr->vector = vec; + ptr->expireTime = time + CollisionTimeoutValue; + ptr->next = mTimeoutList; + + mTimeoutList = ptr; +} + +void ShapeBase::notifyCollision() +{ + // Notify all the objects that were just stamped during the queueing + // process. + SimTime expireTime = Sim::getCurrentTime() + CollisionTimeoutValue; + for (CollisionTimeout* ptr = mTimeoutList; ptr; ptr = ptr->next) + { + if (ptr->expireTime == expireTime && ptr->object) + { + SimObjectPtr safePtr(ptr->object); + SimObjectPtr safeThis(this); + onCollision(ptr->object,ptr->vector); + ptr->object = 0; + + if(!bool(safeThis)) + return; + + if(bool(safePtr)) + safePtr->onCollision(this,ptr->vector); + + if(!bool(safeThis)) + return; + } + } +} + +void ShapeBase::onCollision( SceneObject *object, const VectorF &vec ) +{ + if (!isGhost()) { + char buff1[256]; + char buff2[32]; + + dSprintf(buff1,sizeof(buff1),"%g %g %g",vec.x, vec.y, vec.z); + dSprintf(buff2,sizeof(buff2),"%g",vec.len()); + Con::executef(mDataBlock, "onCollision",scriptThis(),object->scriptThis(), buff1, buff2); + } +} + +//-------------------------------------------------------------------------- +bool ShapeBase::pointInWater( Point3F &point ) +{ + if ( mCurrentWaterObject == NULL ) + return false; + + return mCurrentWaterObject->isUnderwater( point ); +} + +//---------------------------------------------------------------------------- + +void ShapeBase::writePacketData(GameConnection *connection, BitStream *stream) +{ + Parent::writePacketData(connection, stream); + + stream->write(getEnergyLevel()); + stream->write(mRechargeRate); +} + +void ShapeBase::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + + F32 energy; + stream->read(&energy); + setEnergyLevel(energy); + + stream->read(&mRechargeRate); +} + +F32 ShapeBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) +{ + // If it's the scope object, must be high priority + if (camInfo->camera == this) { + // Most priorities are between 0 and 1, so this + // should be something larger. + return 10.0f; + } + if (camInfo->camera && (camInfo->camera->getType() & ShapeBaseObjectType)) + { + // see if the camera is mounted to this... + // if it is, this should have a high priority + if(((ShapeBase *) camInfo->camera)->getObjectMount() == this) + return 10.0f; + } + return Parent::getUpdatePriority(camInfo, updateMask, updateSkips); +} + +U32 ShapeBase::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (mask & InitialUpdateMask) { + // mask off sounds that aren't playing + S32 i; + for (i = 0; i < MaxSoundThreads; i++) + if (!mSoundThread[i].play) + mask &= ~(SoundMaskN << i); + + // mask off threads that aren't running + for (i = 0; i < MaxScriptThreads; i++) + if (mScriptThread[i].sequence == -1) + mask &= ~(ThreadMaskN << i); + + // mask off images that aren't updated + for(i = 0; i < MaxMountedImages; i++) + if(!mMountedImageList[i].dataBlock) + mask &= ~(ImageMaskN << i); + } + + if(!stream->writeFlag(mask & (NameMask | DamageMask | SoundMask | MeshHiddenMask | + ThreadMask | ImageMask | HideCloakMask | MountedMask | InvincibleMask | + ShieldMask | SkinMask))) + return retMask; + + if (stream->writeFlag(mask & DamageMask)) { + stream->writeFloat(mClampF(mDamage / mDataBlock->maxDamage, 0.f, 1.f), DamageLevelBits); + stream->writeInt(mDamageState,NumDamageStateBits); + stream->writeNormalVector( damageDir, 8 ); + } + + if (stream->writeFlag(mask & ThreadMask)) { + for (int i = 0; i < MaxScriptThreads; i++) { + Thread& st = mScriptThread[i]; + if (stream->writeFlag(st.sequence != -1 && (mask & (ThreadMaskN << i)))) { + stream->writeInt(st.sequence,ThreadSequenceBits); + stream->writeInt(st.state,2); + stream->write(st.timescale); + stream->write(st.position); + stream->writeFlag(st.atEnd); + + // Clear Update. + st.position = -1.f; + } + } + } + + if (stream->writeFlag(mask & SoundMask)) { + for (int i = 0; i < MaxSoundThreads; i++) { + Sound& st = mSoundThread[i]; + if (stream->writeFlag(mask & (SoundMaskN << i))) + if (stream->writeFlag(st.play)) + stream->writeRangedU32(st.profile->getId(),DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + } + + if (stream->writeFlag(mask & ImageMask)) { + for (int i = 0; i < MaxMountedImages; i++) + if (stream->writeFlag(mask & (ImageMaskN << i))) { + MountedImage& image = mMountedImageList[i]; + if (stream->writeFlag(image.dataBlock)) + stream->writeInt(image.dataBlock->getId() - DataBlockObjectIdFirst, + DataBlockObjectIdBitSize); + con->packNetStringHandleU(stream, image.skinNameHandle); + stream->writeFlag(image.wet); + stream->writeFlag(image.ammo); + stream->writeFlag(image.loaded); + stream->writeFlag(image.target); + stream->writeFlag(image.triggerDown); + stream->writeFlag(image.altTriggerDown); + stream->writeInt(image.fireCount,3); + if (mask & InitialUpdateMask) + stream->writeFlag(isImageFiring(i)); + } + } + + // Group some of the uncommon stuff together. + if (stream->writeFlag(mask & (NameMask | ShieldMask | HideCloakMask | InvincibleMask | SkinMask | MeshHiddenMask ))) { + + if (stream->writeFlag(mask & HideCloakMask)) + { + // Write hiding state. + stream->writeFlag( mHidden ); + + // cloaking + stream->writeFlag( mCloaked ); + + // piggyback control update + stream->writeFlag(bool(getControllingClient())); + + // fading + if(stream->writeFlag(mFading && mFadeElapsedTime >= mFadeDelay)) { + stream->writeFlag(mFadeOut); + stream->write(mFadeTime); + } + else + stream->writeFlag(mFadeVal == 1.0f); + } + if (stream->writeFlag(mask & NameMask)) { + con->packNetStringHandleU(stream, mShapeNameHandle); + } + if (stream->writeFlag(mask & ShieldMask)) { + stream->writeNormalVector(mShieldNormal, ShieldNormalBits); + stream->writeFloat( getEnergyValue(), EnergyLevelBits ); + } + if (stream->writeFlag(mask & InvincibleMask)) { + stream->write(mInvincibleTime); + stream->write(mInvincibleSpeed); + } + + if ( stream->writeFlag( mask & MeshHiddenMask ) ) + stream->writeBits( mMeshHidden ); + + if (stream->writeFlag(mask & SkinMask)) + con->packNetStringHandleU(stream, mSkinNameHandle); + } + + if (mask & MountedMask) { + if (mMount.object) { + S32 gIndex = con->getGhostIndex(mMount.object); + if (stream->writeFlag(gIndex != -1)) { + stream->writeFlag(true); + stream->writeInt(gIndex,NetConnection::GhostIdBitSize); + stream->writeInt(mMount.node,ShapeBaseData::NumMountPointBits); + } + else + // Will have to try again later + retMask |= MountedMask; + } + else + // Unmount if this isn't the initial packet + if (stream->writeFlag(!(mask & InitialUpdateMask))) + stream->writeFlag(false); + } + else + stream->writeFlag(false); + + return retMask; +} + +void ShapeBase::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + mLastRenderFrame = sLastRenderFrame; // make sure we get a process after the event... + + if(!stream->readFlag()) + return; + + if (stream->readFlag()) { + mDamage = mClampF(stream->readFloat(DamageLevelBits) * mDataBlock->maxDamage, 0.f, mDataBlock->maxDamage); + DamageState prevState = mDamageState; + mDamageState = DamageState(stream->readInt(NumDamageStateBits)); + stream->readNormalVector( &damageDir, 8 ); + if (prevState != Destroyed && mDamageState == Destroyed && isProperlyAdded()) + blowUp(); + updateDamageLevel(); + updateDamageState(); + } + + if (stream->readFlag()) { + for (S32 i = 0; i < MaxScriptThreads; i++) { + if (stream->readFlag()) { + Thread& st = mScriptThread[i]; + U32 seq = stream->readInt(ThreadSequenceBits); + st.state = stream->readInt(2); + stream->read( &st.timescale ); + stream->read( &st.position ); + st.atEnd = stream->readFlag(); + if (st.sequence != seq) + setThreadSequence(i,seq,false); + else + updateThread(st); + } + } + } + + if ( stream->readFlag() ) + { + for ( S32 i = 0; i < MaxSoundThreads; i++ ) + { + if ( stream->readFlag() ) + { + Sound& st = mSoundThread[i]; + st.play = stream->readFlag(); + if ( st.play ) + { + st.profile = (SFXProfile*) stream->readRangedU32( DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } + + if ( isProperlyAdded() ) + updateAudioState( st ); + } + } + } + + if (stream->readFlag()) { + for (int i = 0; i < MaxMountedImages; i++) { + if (stream->readFlag()) { + MountedImage& image = mMountedImageList[i]; + ShapeBaseImageData* imageData = 0; + if (stream->readFlag()) { + SimObjectId id = stream->readInt(DataBlockObjectIdBitSize) + + DataBlockObjectIdFirst; + if (!Sim::findObject(id,imageData)) { + con->setLastError("Invalid packet (mounted images)."); + return; + } + } + + NetStringHandle skinDesiredNameHandle = con->unpackNetStringHandleU(stream); + + image.wet = stream->readFlag(); + + image.ammo = stream->readFlag(); + + image.loaded = stream->readFlag(); + + image.target = stream->readFlag(); + + image.triggerDown = stream->readFlag(); + image.altTriggerDown = stream->readFlag(); + + int count = stream->readInt(3); + + if ((image.dataBlock != imageData) || (image.skinNameHandle != skinDesiredNameHandle)) + setImage( i, imageData, + skinDesiredNameHandle, image.loaded, + image.ammo, image.triggerDown, image.altTriggerDown ); + + if (isProperlyAdded()) { + // Normal processing + if (count != image.fireCount) + { + image.fireCount = count; + setImageState(i,getImageFireState(i),true); + + if( imageData && imageData->lightType == ShapeBaseImageData::WeaponFireLight ) + { + image.lightStart = Sim::getCurrentTime(); + } + } + updateImageState(i,0); + } + else + { + bool firing = stream->readFlag(); + if(imageData) + { + // Initial state + image.fireCount = count; + if (firing) + setImageState(i,getImageFireState(i),true); + } + } + } + } + } + + if (stream->readFlag()) + { + if(stream->readFlag()) // HideCloakMask and control + { + // Read hiding state. + + setHidden( stream->readFlag() ); + + // Read cloaking state. + + setCloakedState(stream->readFlag()); + mIsControlled = stream->readFlag(); + + if (( mFading = stream->readFlag()) == true) { + mFadeOut = stream->readFlag(); + if(mFadeOut) + mFadeVal = 1.0f; + else + mFadeVal = 0; + stream->read(&mFadeTime); + mFadeDelay = 0; + mFadeElapsedTime = 0; + } + else + mFadeVal = F32(stream->readFlag()); + } + if (stream->readFlag()) { // NameMask + mShapeNameHandle = con->unpackNetStringHandleU(stream); + } + if(stream->readFlag()) // ShieldMask + { + // Cloaking, Shield, and invul masking + Point3F shieldNormal; + stream->readNormalVector(&shieldNormal, ShieldNormalBits); + + // CodeReview [bjg 4/6/07] This is our energy level - why aren't we storing it? Was in a + // local variable called energyPercent. + stream->readFloat(EnergyLevelBits); + } + + if (stream->readFlag()) + { + // InvincibleMask + F32 time, speed; + stream->read(&time); + stream->read(&speed); + setupInvincibleEffect(time, speed); + } + + if ( stream->readFlag() ) // MeshHiddenMask + { + stream->readBits( &mMeshHidden ); + _updateHiddenMeshes(); + } + + if (stream->readFlag()) { // SkinMask + + NetStringHandle skinDesiredNameHandle = con->unpackNetStringHandleU(stream);; + + if (mSkinNameHandle != skinDesiredNameHandle) { + + mSkinNameHandle = skinDesiredNameHandle; + reSkin(); + } + + } + } + + if (stream->readFlag()) { + if (stream->readFlag()) { + S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize); + SceneObject* obj = dynamic_cast(con->resolveGhost(gIndex)); + S32 node = stream->readInt(ShapeBaseData::NumMountPointBits); + if(!obj) + { + con->setLastError("Invalid packet from server."); + return; + } + obj->mountObject(this,node); + } + else + unmount(); + } +} + + +//-------------------------------------------------------------------------- + +void ShapeBase::forceUncloak(const char * reason) +{ + AssertFatal(isServerObject(), "ShapeBase::forceUncloak: server only call"); + if(!mCloaked) + return; + + Con::executef(mDataBlock, "onForceUncloak", scriptThis(), reason ? reason : ""); +} + +void ShapeBase::setCloakedState(bool cloaked) +{ + if (cloaked == mCloaked) + return; + + if (isServerObject()) + setMaskBits(HideCloakMask); + + // Have to do this for the client, if we are ghosted over in the initial + // packet as cloaked, we set the state immediately to the extreme + if (isProperlyAdded() == false) { + mCloaked = cloaked; + if (mCloaked) + mCloakLevel = 1.0; + else + mCloakLevel = 0.0; + } else { + mCloaked = cloaked; + } +} + + +//-------------------------------------------------------------------------- + +void ShapeBase::setHidden( bool hidden ) +{ + if( hidden != mHidden ) + { + setMaskBits( HideCloakMask ); + + if( mHidden ) + { + addToScene(); + setProcessTick( true ); + } + else + { + removeFromScene(); + setProcessTick( false ); + } + + mHidden = hidden; + } +} + +//-------------------------------------------------------------------------- + +void ShapeBaseConvex::findNodeTransform() +{ + S32 dl = pShapeBase->mDataBlock->collisionDetails[hullId]; + + TSShapeInstance* si = pShapeBase->getShapeInstance(); + TSShape* shape = si->getShape(); + + const TSShape::Detail* detail = &shape->details[dl]; + const S32 subs = detail->subShapeNum; + const S32 start = shape->subShapeFirstObject[subs]; + const S32 end = start + shape->subShapeNumObjects[subs]; + + // Find the first object that contains a mesh for this + // detail level. There should only be one mesh per + // collision detail level. + for (S32 i = start; i < end; i++) + { + const TSShape::Object* obj = &shape->objects[i]; + if (obj->numMeshes && detail->objectDetailNum < obj->numMeshes) + { + nodeTransform = &si->mNodeTransforms[obj->nodeIndex]; + return; + } + } + return; +} + +const MatrixF& ShapeBaseConvex::getTransform() const +{ + // If the transform isn't specified, it's assumed to be the + // origin of the shape. + const MatrixF& omat = (transform != 0)? *transform: mObject->getTransform(); + + // Multiply on the mesh shape offset + // tg: Returning this static here is not really a good idea, but + // all this Convex code needs to be re-organized. + if (nodeTransform) { + static MatrixF mat; + mat.mul(omat,*nodeTransform); + return mat; + } + return omat; +} + +Box3F ShapeBaseConvex::getBoundingBox() const +{ + const MatrixF& omat = (transform != 0)? *transform: mObject->getTransform(); + return getBoundingBox(omat, mObject->getScale()); +} + +Box3F ShapeBaseConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const +{ + Box3F newBox = box; + newBox.minExtents.convolve(scale); + newBox.maxExtents.convolve(scale); + mat.mul(newBox); + return newBox; +} + +Point3F ShapeBaseConvex::support(const VectorF& v) const +{ + TSShape::ConvexHullAccelerator* pAccel = + pShapeBase->mShapeInstance->getShape()->getAccelerator(pShapeBase->mDataBlock->collisionDetails[hullId]); + AssertFatal(pAccel != NULL, "Error, no accel!"); + + F32 currMaxDP = mDot(pAccel->vertexList[0], v); + U32 index = 0; + for (U32 i = 1; i < pAccel->numVerts; i++) { + F32 dp = mDot(pAccel->vertexList[i], v); + if (dp > currMaxDP) { + currMaxDP = dp; + index = i; + } + } + + return pAccel->vertexList[index]; +} + + +void ShapeBaseConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf) +{ + cf->material = 0; + cf->object = mObject; + + TSShape::ConvexHullAccelerator* pAccel = + pShapeBase->mShapeInstance->getShape()->getAccelerator(pShapeBase->mDataBlock->collisionDetails[hullId]); + AssertFatal(pAccel != NULL, "Error, no accel!"); + + F32 currMaxDP = mDot(pAccel->vertexList[0], n); + U32 index = 0; + U32 i; + for (i = 1; i < pAccel->numVerts; i++) { + F32 dp = mDot(pAccel->vertexList[i], n); + if (dp > currMaxDP) { + currMaxDP = dp; + index = i; + } + } + + const U8* emitString = pAccel->emitStrings[index]; + U32 currPos = 0; + U32 numVerts = emitString[currPos++]; + for (i = 0; i < numVerts; i++) { + cf->mVertexList.increment(); + U32 index = emitString[currPos++]; + mat.mulP(pAccel->vertexList[index], &cf->mVertexList.last()); + } + + U32 numEdges = emitString[currPos++]; + for (i = 0; i < numEdges; i++) { + U32 ev0 = emitString[currPos++]; + U32 ev1 = emitString[currPos++]; + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = ev0; + cf->mEdgeList.last().vertex[1] = ev1; + } + + U32 numFaces = emitString[currPos++]; + for (i = 0; i < numFaces; i++) { + cf->mFaceList.increment(); + U32 plane = emitString[currPos++]; + mat.mulV(pAccel->normalList[plane], &cf->mFaceList.last().normal); + for (U32 j = 0; j < 3; j++) + cf->mFaceList.last().vertex[j] = emitString[currPos++]; + } +} + + +void ShapeBaseConvex::getPolyList(AbstractPolyList* list) +{ + list->setTransform(&pShapeBase->getTransform(), pShapeBase->getScale()); + list->setObject(pShapeBase); + + pShapeBase->mShapeInstance->animate(pShapeBase->mDataBlock->collisionDetails[hullId]); + pShapeBase->mShapeInstance->buildPolyList(list,pShapeBase->mDataBlock->collisionDetails[hullId]); +} + + +//-------------------------------------------------------------------------- + +bool ShapeBase::isInvincible() +{ + if( mDataBlock ) + { + return mDataBlock->isInvincible; + } + return false; +} + +void ShapeBase::startFade( F32 fadeTime, F32 fadeDelay, bool fadeOut ) +{ + setMaskBits(HideCloakMask); + mFadeElapsedTime = 0; + mFading = true; + if(fadeDelay < 0) + fadeDelay = 0; + if(fadeTime < 0) + fadeTime = 0; + mFadeTime = fadeTime; + mFadeDelay = fadeDelay; + mFadeOut = fadeOut; + mFadeVal = F32(mFadeOut); +} + +//-------------------------------------------------------------------------- + +void ShapeBase::setShapeName(const char* name) +{ + if (!isGhost()) { + if (name[0] != '\0') { + // Use tags for better network performance + // Should be a tag, but we'll convert to one if it isn't. + if (name[0] == StringTagPrefixByte) + mShapeNameHandle = NetStringHandle(U32(dAtoi(name + 1))); + else + mShapeNameHandle = NetStringHandle(name); + } + else { + mShapeNameHandle = NetStringHandle(); + } + setMaskBits(NameMask); + } +} + + +void ShapeBase::setSkinName(const char* name) +{ + if (!isGhost()) { + if (name[0] != '\0') { + + // Use tags for better network performance + // Should be a tag, but we'll convert to one if it isn't. + if (name[0] == StringTagPrefixByte) { + mSkinNameHandle = NetStringHandle(U32(dAtoi(name + 1))); + } + else { + mSkinNameHandle = NetStringHandle(name); + } + } + else { + mSkinNameHandle = NetStringHandle(); + } + setMaskBits(SkinMask); + } +} + +//---------------------------------------------------------------------------- + +void ShapeBase::reSkin() +{ + if ( isGhost() && mShapeInstance && mSkinNameHandle.isValidString() ) + { + const char* newSkin = mSkinNameHandle.getString(); + mShapeInstance->reSkin( newSkin, mAppliedSkinName ); + mAppliedSkinName = newSkin; + mSkinHash = _StringTable::hashString( newSkin ); + } +} + +void ShapeBase::setCurrentWaterObject( WaterObject *obj ) +{ + if ( obj ) + deleteNotify( obj ); + if ( mCurrentWaterObject ) + clearNotify( mCurrentWaterObject ); + + mCurrentWaterObject = obj; +} + +//-------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +ConsoleMethod( ShapeBase, setHidden, void, 3, 3, "(bool show)") +{ + object->setHidden(dAtob(argv[2])); +} + +ConsoleMethod( ShapeBase, isHidden, bool, 2, 2, "") +{ + return object->isHidden(); +} + +//---------------------------------------------------------------------------- +ConsoleMethod( ShapeBase, playAudio, bool, 4, 4, "(int slot, SFXProfile profile)") +{ + U32 slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + SFXProfile* profile; + if (Sim::findObject(argv[3],profile)) { + object->playAudio(slot,profile); + return true; + } + } + return false; +} + +ConsoleMethod( ShapeBase, stopAudio, bool, 3, 3, "(int slot)") +{ + U32 slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + object->stopAudio(slot); + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- +ConsoleMethod( ShapeBase, playThread, bool, 3, 4, "(int slot, string sequenceName)") +{ + U32 slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (argc == 4) { + if (object->getShape()) { + S32 seq = object->getShape()->findSequence(argv[3]); + if (seq != -1 && object->setThreadSequence(slot,seq)) + return true; + } + } + else + if (object->playThread(slot)) + return true; + } + return false; +} + +ConsoleMethod( ShapeBase, setThreadDir, bool, 4, 4, "(int slot, bool isForward)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (object->setThreadDir(slot,dAtob(argv[3]))) + return true; + } + return false; +} + +ConsoleMethod( ShapeBase, setThreadTimeScale, bool, 4, 4, "( int pSlot, float pTimeScale )" ) +{ + return object->setThreadTimeScale( dAtoi( argv[2] ), dAtof( argv[3] ) ); +} + +ConsoleMethod( ShapeBase, stopThread, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (object->stopThread(slot)) + return true; + } + return false; +} + +ConsoleMethod( ShapeBase, pauseThread, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxScriptThreads) { + if (object->pauseThread(slot)) + return true; + } + return false; +} + +//---------------------------------------------------------------------------- +ConsoleMethod( ShapeBase, mountImage, bool, 4, 6, "(ShapeBaseImageData image, int slot, bool loaded=true, string skinTag=NULL)") +{ + ShapeBaseImageData* imageData; + if (Sim::findObject(argv[2],imageData)) { + U32 slot = dAtoi(argv[3]); + bool loaded = (argc == 5)? dAtob(argv[4]): true; + NetStringHandle team; + if(argc == 6) + { + if(argv[5][0] == StringTagPrefixByte) + team = NetStringHandle(U32(dAtoi(argv[5]+1))); + } + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + object->mountImage(imageData,slot,loaded,team); + } + return false; +} + +ConsoleMethod( ShapeBase, unmountImage, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->unmountImage(slot); + return false; +} + +ConsoleMethod( ShapeBase, getMountedImage, S32, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + if (ShapeBaseImageData* data = object->getMountedImage(slot)) + return data->getId(); + return 0; +} + +ConsoleMethod( ShapeBase, getPendingImage, S32, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + if (ShapeBaseImageData* data = object->getPendingImage(slot)) + return data->getId(); + return 0; +} + +ConsoleMethod( ShapeBase, isImageFiring, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->isImageFiring(slot); + return false; +} + +ConsoleMethod( ShapeBase, isImageMounted, bool, 3, 3, "(ShapeBaseImageData db)") +{ + ShapeBaseImageData* imageData; + if (Sim::findObject(argv[2],imageData)) + return object->isImageMounted(imageData); + return false; +} + +ConsoleMethod( ShapeBase, getMountSlot, S32, 3, 3, "(ShapeBaseImageData db)") +{ + ShapeBaseImageData* imageData; + if (Sim::findObject(argv[2],imageData)) + return object->getMountSlot(imageData); + return -1; +} + +ConsoleMethod( ShapeBase, getImageSkinTag, S32, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->getImageSkinTag(slot).getIndex(); + return -1; +} + +ConsoleMethod( ShapeBase, getImageState, const char*, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->getImageState(slot); + return "Error"; +} + +ConsoleMethod( ShapeBase, getImageTrigger, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->getImageTriggerState(slot); + return false; +} + +ConsoleMethod( ShapeBase, setImageTrigger, bool, 4, 4, "(int slot, bool isTriggered)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + object->setImageTriggerState(slot,dAtob(argv[3])); + return object->getImageTriggerState(slot); + } + return false; +} + +ConsoleMethod( ShapeBase, getImageAltTrigger, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->getImageAltTriggerState(slot); + return false; +} + +ConsoleMethod( ShapeBase, setImageAltTrigger, bool, 4, 4, "(int slot, bool isTriggered)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + object->setImageAltTriggerState(slot,dAtob(argv[3])); + return object->getImageAltTriggerState(slot); + } + return false; +} + +ConsoleMethod( ShapeBase, getImageAmmo, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->getImageAmmoState(slot); + return false; +} + +ConsoleMethod( ShapeBase, setImageAmmo, bool, 4, 4, "(int slot, bool hasAmmo)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + bool ammo = dAtob(argv[3]); + object->setImageAmmoState(slot,dAtob(argv[3])); + return ammo; + } + return false; +} + +ConsoleMethod( ShapeBase, getImageLoaded, bool, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + return object->getImageLoadedState(slot); + return false; +} + +ConsoleMethod( ShapeBase, setImageLoaded, bool, 4, 4, "(int slot, bool loaded)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + bool loaded = dAtob(argv[3]); + object->setImageLoadedState(slot, dAtob(argv[3])); + return loaded; + } + return false; +} + +ConsoleMethod( ShapeBase, getMuzzleVector, const char*, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + VectorF v; + object->getMuzzleVector(slot,&v); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff,100,"%g %g %g",v.x,v.y,v.z); + return buff; + } + return "0 1 0"; +} + +ConsoleMethod( ShapeBase, getMuzzlePoint, const char*, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) { + Point3F p; + object->getMuzzlePoint(slot,&p); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff,100,"%g %g %g",p.x,p.y,p.z); + return buff; + } + return "0 0 0"; +} + +ConsoleMethod( ShapeBase, getSlotTransform, const char*, 3, 3, "(int slot)") +{ + int slot = dAtoi(argv[2]); + MatrixF xf(true); + if (slot >= 0 && slot < ShapeBase::MaxMountedImages) + object->getMountTransform(slot,&xf); + + Point3F pos; + xf.getColumn(3,&pos); + AngAxisF aa(xf); + char* buff = Con::getReturnBuffer(200); + dSprintf(buff,200,"%g %g %g %g %g %g %g", + pos.x,pos.y,pos.z,aa.axis.x,aa.axis.y,aa.axis.z,aa.angle); + return buff; +} + +ConsoleMethod( ShapeBase, getAIRepairPoint, const char*, 2, 2, "Get the position at which the AI should stand to repair things.") +{ + Point3F pos = object->getAIRepairPoint(); + char* buff = Con::getReturnBuffer(200); + dSprintf(buff,200,"%g %g %g", pos.x,pos.y,pos.z); + return buff; +} + +ConsoleMethod( ShapeBase, getVelocity, const char *, 2, 2, "") +{ + const VectorF& vel = object->getVelocity(); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff,100,"%g %g %g",vel.x,vel.y,vel.z); + return buff; +} + +ConsoleMethod( ShapeBase, setVelocity, bool, 3, 3, "(Vector3F vel)") +{ + VectorF vel(0,0,0); + dSscanf(argv[2],"%g %g %g",&vel.x,&vel.y,&vel.z); + object->setVelocity(vel); + return true; +} + +ConsoleMethod( ShapeBase, applyImpulse, bool, 4, 4, "(Point3F Pos, VectorF vel)") +{ + Point3F pos(0,0,0); + VectorF vel(0,0,0); + dSscanf(argv[2],"%g %g %g",&pos.x,&pos.y,&pos.z); + dSscanf(argv[3],"%g %g %g",&vel.x,&vel.y,&vel.z); + object->applyImpulse(pos,vel); + return true; +} + +ConsoleMethod( ShapeBase, getEyeVector, const char*, 2, 2, "") +{ + MatrixF mat; + object->getEyeTransform(&mat); + VectorF v2; + mat.getColumn(1,&v2); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff, 100,"%g %g %g",v2.x,v2.y,v2.z); + return buff; +} + +ConsoleMethod( ShapeBase, getEyePoint, const char*, 2, 2, "") +{ + MatrixF mat; + object->getEyeTransform(&mat); + Point3F ep; + mat.getColumn(3,&ep); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff, 100,"%g %g %g",ep.x,ep.y,ep.z); + return buff; +} + +ConsoleMethod( ShapeBase, getEyeTransform, const char*, 2, 2, "") +{ + MatrixF mat; + object->getEyeTransform(&mat); + + Point3F pos; + mat.getColumn(3,&pos); + AngAxisF aa(mat); + char* buff = Con::getReturnBuffer(100); + dSprintf(buff,100,"%g %g %g %g %g %g %g", + pos.x,pos.y,pos.z,aa.axis.x,aa.axis.y,aa.axis.z,aa.angle); + return buff; +} + +ConsoleMethod( ShapeBase, getLookAtPoint, const char*, 2, 4, + "( [float distance, bitset mask] )" + " - Return look-at information as \"Object HitX HitY HitZ [Material]\" or empty string for no hit" ) +{ + F32 distance = 2000.f; + if( argc > 2 ) + distance = dAtof( argv[ 2 ] ); + + S32 typeMask = 0xFFFFFFFF; // Default: hit anything. + if( argc > 3 ) + typeMask = dAtoi( argv[ 3 ] ); + + MatrixF mat; + object->getEyeTransform( &mat ); + + // Get eye vector. + + VectorF eyeVector; + mat.getColumn( 1, &eyeVector ); + + // Get eye position. + + VectorF eyePos; + mat.getColumn( 3, &eyePos ); + + // Make sure the eye vector covers the distance. + + eyeVector *= distance; + + // Do a container search. + + VectorF start = eyePos; + VectorF end = eyePos + eyeVector; + + RayInfo ri; + if( !gServerContainer.castRay( start, end, typeMask, &ri ) || !ri.object ) + return ""; // No hit. + + // Gather hit info. + + enum { BUFFER_SIZE = 256 }; + char* buffer = Con::getReturnBuffer( BUFFER_SIZE ); + if( ri.material ) + dSprintf( buffer, BUFFER_SIZE, "%u %f %f %f %u", + ri.object->getId(), + ri.point.x, + ri.point.y, + ri.point.z, + ri.material->getMaterial()->getId() ); + else + dSprintf( buffer, BUFFER_SIZE, "%u %f %f %f", + ri.object->getId(), + ri.point.x, + ri.point.y, + ri.point.z ); + + return buffer; +} + +ConsoleMethod( ShapeBase, setEnergyLevel, void, 3, 3, "(float level)") +{ + object->setEnergyLevel(dAtof(argv[2])); +} + +ConsoleMethod( ShapeBase, getEnergyLevel, F32, 2, 2, "") +{ + return object->getEnergyLevel(); +} + +ConsoleMethod( ShapeBase, getEnergyPercent, F32, 2, 2, "") +{ + return object->getEnergyValue(); +} + +ConsoleMethod( ShapeBase, setDamageLevel, void, 3, 3, "(float level)") +{ + object->setDamageLevel(dAtof(argv[2])); +} + +ConsoleMethod( ShapeBase, getDamageLevel, F32, 2, 2, "") +{ + return object->getDamageLevel(); +} + +ConsoleMethod( ShapeBase, getDamagePercent, F32, 2, 2, "") +{ + return object->getDamageValue(); +} + +ConsoleMethod( ShapeBase, setDamageState, bool, 3, 3, "(string state)") +{ + return object->setDamageState(argv[2]); +} + +ConsoleMethod( ShapeBase, getDamageState, const char*, 2, 2, "") +{ + return object->getDamageStateName(); +} + +ConsoleMethod( ShapeBase, isDestroyed, bool, 2, 2, "") +{ + return object->isDestroyed(); +} + +ConsoleMethod( ShapeBase, isDisabled, bool, 2, 2, "True if the state is not Enabled.") +{ + return object->getDamageState() != ShapeBase::Enabled; +} + +ConsoleMethod( ShapeBase, isEnabled, bool, 2, 2, "") +{ + return object->getDamageState() == ShapeBase::Enabled; +} + +ConsoleMethod( ShapeBase, applyDamage, void, 3, 3, "(float amt)") +{ + object->applyDamage(dAtof(argv[2])); +} + +ConsoleMethod( ShapeBase, applyRepair, void, 3, 3, "(float amt)") +{ + object->applyRepair(dAtof(argv[2])); +} + +ConsoleMethod( ShapeBase, setRepairRate, void, 3, 3, "(float amt)") +{ + F32 rate = dAtof(argv[2]); + if(rate < 0) + rate = 0; + object->setRepairRate(rate); +} + +ConsoleMethod( ShapeBase, getRepairRate, F32, 2, 2, "") +{ + return object->getRepairRate(); +} + +ConsoleMethod( ShapeBase, setRechargeRate, void, 3, 3, "(float rate)") +{ + object->setRechargeRate(dAtof(argv[2])); +} + +ConsoleMethod( ShapeBase, getRechargeRate, F32, 2, 2, "") +{ + return object->getRechargeRate(); +} + +ConsoleMethod( ShapeBase, getControllingClient, S32, 2, 2, "Returns a GameConnection.") +{ + if (GameConnection* con = object->getControllingClient()) + return con->getId(); + return 0; +} + +ConsoleMethod( ShapeBase, getControllingObject, S32, 2, 2, "") +{ + if (ShapeBase* con = object->getControllingObject()) + return con->getId(); + return 0; +} + +// return true if can cloak, otherwise the reason why object cannot cloak +ConsoleMethod( ShapeBase, canCloak, bool, 2, 2, "") +{ + return true; +} + +ConsoleMethod( ShapeBase, setCloaked, void, 3, 3, "(bool isCloaked)") +{ + bool cloaked = dAtob(argv[2]); + if (object->isServerObject()) + object->setCloakedState(cloaked); +} + +ConsoleMethod( ShapeBase, isCloaked, bool, 2, 2, "") +{ + return object->getCloakedState(); +} + +ConsoleMethod( ShapeBase, setDamageFlash, void, 3, 3, "(float lvl)") +{ + F32 flash = dAtof(argv[2]); + if (object->isServerObject()) + object->setDamageFlash(flash); +} + +ConsoleMethod( ShapeBase, getDamageFlash, F32, 2, 2, "") +{ + return object->getDamageFlash(); +} + +ConsoleMethod( ShapeBase, setWhiteOut, void, 3, 3, "(float flashLevel)") +{ + F32 flash = dAtof(argv[2]); + if (object->isServerObject()) + object->setWhiteOut(flash); +} + +ConsoleMethod( ShapeBase, getWhiteOut, F32, 2, 2, "") +{ + return object->getWhiteOut(); +} + +ConsoleMethod( ShapeBase, getCameraFov, F32, 2, 2, "") +{ + if (object->isServerObject()) + return object->getCameraFov(); + return 0.0; +} + +ConsoleMethod( ShapeBase, setCameraFov, void, 3, 3, "(float fov)") +{ + if (object->isServerObject()) + object->setCameraFov(dAtof(argv[2])); +} + +ConsoleMethod( ShapeBase, setInvincibleMode, void, 4, 4, "(float time, float speed)") +{ + object->setupInvincibleEffect(dAtof(argv[2]), dAtof(argv[3])); +} + +ConsoleMethod( ShapeBase, startFade, void, 5, 5, "( int fadeTimeMS, int fadeDelayMS, bool fadeOut )") +{ + U32 fadeTime; + U32 fadeDelay; + bool fadeOut; + + dSscanf(argv[2], "%d", &fadeTime ); + dSscanf(argv[3], "%d", &fadeDelay ); + fadeOut = dAtob(argv[4]); + + object->startFade( fadeTime / 1000.0, fadeDelay / 1000.0, fadeOut ); +} + +ConsoleMethod( ShapeBase, setDamageVector, void, 3, 3, "(Vector3F origin)") +{ + VectorF normal; + dSscanf(argv[2], "%g %g %g", &normal.x, &normal.y, &normal.z); + normal.normalize(); + object->setDamageDir(VectorF(normal.x, normal.y, normal.z)); +} + +ConsoleMethod( ShapeBase, setShapeName, void, 3, 3, "(string tag)") +{ + object->setShapeName(argv[2]); +} + + +ConsoleMethod( ShapeBase, setSkinName, void, 3, 3, "(string tag)") +{ + object->setSkinName(argv[2]); +} + +ConsoleMethod( ShapeBase, getShapeName, const char*, 2, 2, "") +{ + return object->getShapeName(); +} + + +ConsoleMethod( ShapeBase, getSkinName, const char*, 2, 2, "") +{ + return object->getSkinName(); +} + +//---------------------------------------------------------------------------- +void ShapeBase::consoleInit() +{ + Con::addVariable("SB::DFDec", TypeF32, &sDamageFlashDec); + Con::addVariable("SB::WODec", TypeF32, &sWhiteoutDec); + Con::addVariable("pref::environmentMaps", TypeBool, &gRenderEnvMaps); +} + +void ShapeBase::_updateHiddenMeshes() +{ + if ( !mShapeInstance ) + return; + + // This may happen at some point in the future... lets + // detect it so that it can be fixed at that time. + AssertFatal( mMeshHidden.getSize() == mShapeInstance->mMeshObjects.size(), + "ShapeBase::_updateMeshVisibility() - Mesh visibility size mismatch!" ); + + for ( U32 i = 0; i < mMeshHidden.getSize(); i++ ) + setMeshHidden( i, mMeshHidden.test( i ) ); +} + +void ShapeBase::setMeshHidden( const char *meshName, bool forceHidden ) +{ + setMeshHidden( mDataBlock->mShape->findObject( meshName ), forceHidden ); +} + +void ShapeBase::setMeshHidden( S32 meshIndex, bool forceHidden ) +{ + if ( meshIndex == -1 || meshIndex >= mMeshHidden.getSize() ) + return; + + if ( forceHidden ) + mMeshHidden.set( meshIndex ); + else + mMeshHidden.clear( meshIndex ); + + if ( mShapeInstance ) + mShapeInstance->setMeshForceHidden( meshIndex, forceHidden ); + + setMaskBits( MeshHiddenMask ); +} + +void ShapeBase::setAllMeshesHidden( bool forceHidden ) +{ + if ( forceHidden ) + mMeshHidden.set(); + else + mMeshHidden.clear(); + + if ( mShapeInstance ) + { + for ( U32 i = 0; i < mMeshHidden.getSize(); i++ ) + mShapeInstance->setMeshForceHidden( i, forceHidden ); + } + + setMaskBits( MeshHiddenMask ); +} + +ConsoleMethod( ShapeBase, setAllMeshesHidden, void, 3, 3, + "( bool forceHidden )\n" + "Set the hidden state on all the shape meshes." ) +{ + object->setAllMeshesHidden( dAtob( argv[2] ) ); +} + +ConsoleMethod( ShapeBase, setMeshHidden, void, 4, 4, + "( string meshName, bool forceHidden )\n" + "Set the force hidden state on the named mesh." ) +{ + object->setMeshHidden( argv[2], dAtob( argv[3] ) ); +} + +// Some development-handy functions +#ifndef TORQUE_SHIPPING + +void ShapeBase::dumpMeshVisibility() +{ + if ( !mShapeInstance ) + return; + + const Vector &meshes = mShapeInstance->mMeshObjects; + + for ( U32 i = 0; i < meshes.size(); i++) + { + const TSShapeInstance::MeshObjectInstance &mesh = meshes[i]; + + const String &meshName = mDataBlock->mShape->getMeshName( i ); + + Con::printf( "%d - %s - forceHidden = %s, visibility = %f", + i, + meshName.c_str(), + mesh.forceHidden ? "true" : "false", + mesh.visible ); + } +} + +ConsoleMethod( ShapeBase, dumpMeshVisibility, void, 2, 2, + "Prints list of visible and hidden meshes to the console for debugging purposes." ) +{ + object->dumpMeshVisibility(); +} + +#endif // #ifndef TORQUE_SHIPPING + +//------------------------------------------------------------------------ +//These functions are duplicated in tsStatic, shapeBase, and interiorInstance. +//They each function a little differently; but achieve the same purpose of gathering +//target names/counts without polluting simObject. + +ConsoleMethod( ShapeBase, getTargetName, const char*, 3, 3, "") +{ + S32 idx = dAtoi(argv[2]); + + ShapeBase *obj = dynamic_cast< ShapeBase* > ( object ); + if(obj) + return obj->getShape()->getTargetName(idx); + + return ""; +} + +ConsoleMethod( ShapeBase, getTargetCount, S32, 2, 2, "") +{ + ShapeBase *obj = dynamic_cast< ShapeBase* > ( object ); + if(obj) + return obj->getShape()->getTargetCount(); + + return -1; +} + +// This method is able to change materials per map to with others. The material that is being replaced is being mapped to +// unmapped_mat as a part of this transition + +// Warning, right now this only sort of works. It doesn't do a live update like it should. +ConsoleMethod( ShapeBase, changeMaterial, void, 5, 5, "(mapTo, fromMaterial, ToMaterial)") +{ + // initilize server/client versions + ShapeBase *serverObj = object; + ShapeBase *clientObj = dynamic_cast< ShapeBase* > ( object->getClientObject() ); + + if(serverObj) + { + // Lets get ready to switch out materials + Material *oldMat = dynamic_cast(Sim::findObject(argv[3])); + Material *newMat = dynamic_cast(Sim::findObject(argv[4])); + + // if no valid new material, theres no reason for doing this + if( !newMat ) + return; + + // Lets remap the old material off, so as to let room for our current material room to claim its spot + if( oldMat ) + oldMat->mMapTo = String("unmapped_mat"); + + newMat->mMapTo = argv[2]; + + // Map the material in the in the matmgr + MATMGR->mapMaterial( argv[2], argv[4] ); + + U32 i = 0; + // Replace instances with the new material being traded in. Lets make sure that we only + // target the specific targets per inst. For shape base class we have to update the server/client objects + // seperately so both represent our changes + for (; i < serverObj->getShape()->materialList->getMaterialNameList().size(); i++) + { + if( String(argv[2]) == serverObj->getShape()->materialList->getMaterialName(i)) + { + delete [] serverObj->getShape()->materialList->mMatInstList[i]; + serverObj->getShape()->materialList->mMatInstList[i] = newMat->createMatInstance(); + + delete [] clientObj->getShapeInstance()->mMaterialList->mMatInstList[i]; + clientObj->getShapeInstance()->mMaterialList->mMatInstList[i] = newMat->createMatInstance(); + break; + } + } + + + // Finish up preparing the material instances for rendering. Prep render for both + // server and client objects + const GFXVertexFormat *flags = getGFXVertexFormat(); + FeatureSet features = MATMGR->getDefaultFeatures(); + serverObj->getShape()->materialList->getMaterialInst(i)->init( features, flags ); + clientObj->getShapeInstance()->mMaterialList->getMaterialInst(i)->init( features, flags ); + } +} + +ConsoleMethod( ShapeBase, getModelFile, const char *, 2, 2, "getModelFile( String )") +{ + + GameBaseData * datablock = object->getDataBlock(); + if( !datablock ) + return String::EmptyString; + + const char *fieldName = StringTable->insert( String("shapeFile") ); + return datablock->getDataField( fieldName, NULL ); +} \ No newline at end of file diff --git a/T3D/shapeBase.h b/T3D/shapeBase.h new file mode 100644 index 0000000..fc00365 --- /dev/null +++ b/T3D/shapeBase.h @@ -0,0 +1,1692 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHAPEBASE_H_ +#define _SHAPEBASE_H_ + +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _MOVEMANAGER_H_ +#include "T3D/moveManager.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif +#ifndef _SCENESTATE_H_ +#include "sceneGraph/sceneState.h" +#endif +#ifndef _NETSTRINGTABLE_H_ +#include "sim/netStringTable.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _BITVECTOR_H_ +#include "core/bitVector.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _REFLECTOR_H_ +#include "sceneGraph/reflector.h" +#endif + +class GFXCubemap; +class TSShapeInstance; +class SceneState; +class TSThread; +class GameConnection; +struct CameraScopeQuery; +class ParticleEmitter; +class ParticleEmitterData; +class ProjectileData; +class ExplosionData; +struct DebrisData; +class ShapeBase; +class SFXSource; +class SFXProfile; + +typedef void* Light; + + +//-------------------------------------------------------------------------- + +extern void collisionFilter(SceneObject* object,S32 key); +extern void defaultFilter(SceneObject* object,S32 key); + + +//-------------------------------------------------------------------------- +class ShapeBaseConvex : public Convex +{ + typedef Convex Parent; + friend class ShapeBase; + friend class Vehicle; + friend class RigidShape; + + protected: + ShapeBase* pShapeBase; + MatrixF* nodeTransform; + + public: + MatrixF* transform; + U32 hullId; + Box3F box; + + public: + ShapeBaseConvex() { mType = ShapeBaseConvexType; nodeTransform = 0; } + ShapeBaseConvex(const ShapeBaseConvex& cv) { + mObject = cv.mObject; + pShapeBase = cv.pShapeBase; + hullId = cv.hullId; + nodeTransform = cv.nodeTransform; + box = cv.box; + transform = 0; + } + + void findNodeTransform(); + const MatrixF& getTransform() const; + Box3F getBoundingBox() const; + Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const; + Point3F support(const VectorF& v) const; + void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + void getPolyList(AbstractPolyList* list); +}; + +//-------------------------------------------------------------------------- + +struct ShapeBaseImageData: public GameBaseData { + private: + typedef GameBaseData Parent; + + public: + enum Constants { + MaxStates = 31, ///< We get one less than state bits because of + /// the way data is packed. + NumStateBits = 5, + }; + enum LightType { + NoLight = 0, + ConstantLight, + SpotLight, + PulsingLight, + WeaponFireLight, + NumLightTypes + }; + struct StateData { + StateData(); + const char* name; ///< State name + + /// @name Transition states + /// + /// @{ + + /// + struct Transition { + S32 loaded[2]; ///< NotLoaded/Loaded + S32 ammo[2]; ///< Noammo/ammo + S32 target[2]; ///< target/noTarget + S32 trigger[2]; ///< Trigger up/down + S32 altTrigger[2]; ///< Second trigger up/down + S32 wet[2]; ///< wet/notWet + S32 timeout; ///< Transition after delay + } transition; + bool ignoreLoadedForReady; + + /// @} + + /// @name State attributes + /// @{ + + bool fire; ///< Can only have one fire state + bool ejectShell; ///< Should we eject a shell casing in this state? + bool allowImageChange; ///< Can we switch to another image while in this state? + /// + /// For instance, if we have a rocket launcher, the player + /// shouldn't be able to switch out while firing. So, + /// you'd set allowImageChange to false in firing states, and + /// true the rest of the time. + bool scaleAnimation; ///< Scale animation to fit the state timeout + bool direction; ///< Animation direction + bool waitForTimeout; ///< Require the timeout to pass before advancing to the next + /// state. + F32 timeoutValue; ///< A timeout value; the effect of this value is determined + /// by the flags scaleAnimation and waitForTimeout + F32 energyDrain; ///< Sets the energy drain rate per second of this state. + /// + /// Energy is drained at energyDrain units/sec as long as + /// we are in this state. + enum LoadedState { + IgnoreLoaded, ///< Don't change loaded state. + Loaded, ///< Set us as loaded. + NotLoaded, ///< Set us as not loaded. + NumLoadedBits = 3 ///< How many bits to allocate to representing this state. (3 states needs 2 bits) + } loaded; ///< Is the image considered loaded? + enum SpinState { + IgnoreSpin, ///< Don't change spin state. + NoSpin, ///< Mark us as having no spin (ie, stop spinning). + SpinUp, ///< Mark us as spinning up. + SpinDown, ///< Mark us as spinning down. + FullSpin, ///< Mark us as being at full spin. + NumSpinBits = 3 ///< How many bits to allocate to representing this state. (5 states needs 3 bits) + } spin; ///< Spin thread state. (Used to control spinning weapons, e.g. chainguns) + enum RecoilState { + NoRecoil, + LightRecoil, + MediumRecoil, + HeavyRecoil, + NumRecoilBits = 3 + } recoil; ///< Recoil thread state. + /// + /// @note At this point, the only check being done is to see if we're in a + /// state which isn't NoRecoil; ie, no differentiation is made between + /// Light/Medium/Heavy recoils. Player::onImageRecoil() is the place + /// where this is handled. + bool flashSequence; ///< Is this a muzzle flash sequence? + /// + /// A muzzle flash sequence is used as a source to randomly display frames from, + /// so if this is a flashSequence, we'll display a random piece each frame. + S32 sequence; ///< Main thread sequence ID. + /// + /// + S32 sequenceVis; ///< Visibility thread sequence. + const char* script; ///< Function on datablock to call when we enter this state; passed the id of + /// the imageSlot. + ParticleEmitterData* emitter; ///< A particle emitter; this emitter will emit as long as the gun is in this + /// this state. + SFXProfile* sound; + F32 emitterTime; ///< + S32 emitterNode; + }; + + /// @name State Data + /// Individual state data used to initialize struct array + /// @{ + const char* fireStateName; + + const char* stateName [MaxStates]; + + const char* stateTransitionLoaded [MaxStates]; + const char* stateTransitionNotLoaded [MaxStates]; + const char* stateTransitionAmmo [MaxStates]; + const char* stateTransitionNoAmmo [MaxStates]; + const char* stateTransitionTarget [MaxStates]; + const char* stateTransitionNoTarget [MaxStates]; + const char* stateTransitionWet [MaxStates]; + const char* stateTransitionNotWet [MaxStates]; + const char* stateTransitionTriggerUp [MaxStates]; + const char* stateTransitionTriggerDown [MaxStates]; + const char* stateTransitionAltTriggerUp[MaxStates]; + const char* stateTransitionAltTriggerDown[MaxStates]; + const char* stateTransitionTimeout [MaxStates]; + F32 stateTimeoutValue [MaxStates]; + bool stateWaitForTimeout [MaxStates]; + + bool stateFire [MaxStates]; + bool stateEjectShell [MaxStates]; + F32 stateEnergyDrain [MaxStates]; + bool stateAllowImageChange [MaxStates]; + bool stateScaleAnimation [MaxStates]; + bool stateDirection [MaxStates]; + StateData::LoadedState stateLoaded [MaxStates]; + StateData::SpinState stateSpin [MaxStates]; + StateData::RecoilState stateRecoil [MaxStates]; + const char* stateSequence [MaxStates]; + bool stateSequenceRandomFlash [MaxStates]; + bool stateIgnoreLoadedForReady [MaxStates]; + + SFXProfile* stateSound [MaxStates]; + const char* stateScript [MaxStates]; + + ParticleEmitterData* stateEmitter [MaxStates]; + F32 stateEmitterTime [MaxStates]; + const char* stateEmitterNode [MaxStates]; + + /// @} + + /// Maximum number of sounds this image can play at a time. + /// Any value <= 0 indicates that it can play an infinite number of sounds. + S32 maxConcurrentSounds; + + // + bool emap; ///< Environment mapping on? + bool correctMuzzleVector; ///< Adjust firing vector to eye's LOS point? + bool firstPerson; ///< Render the image when in first person? + bool useEyeOffset; ///< In first person, should we use the eyeTransform? + + StringTableEntry shapeName; ///< Name of shape to render. + U32 mountPoint; ///< Mount point for the image. + MatrixF mountOffset; ///< Mount point offset, so we know where the image is. + MatrixF eyeOffset; ///< Offset from eye for first person. + + ProjectileData* projectile; ///< Information about what projectile this + /// image fires. + + F32 mass; ///< Mass! + bool usesEnergy; ///< Does this use energy instead of ammo? + F32 minEnergy; ///< Minimum energy for the weapon to be operable. + bool accuFire; ///< Should we automatically make image's aim converge with the crosshair? + bool cloakable; ///< Is this image cloakable when mounted? + + /// @name Lighting + /// @{ + S32 lightType; ///< Indicates the type of the light. + /// + /// One of: ConstantLight, PulsingLight, WeaponFireLight. + ColorF lightColor; + S32 lightDuration; ///< The duration in SimTime of Pulsing or WeaponFire type lights. + F32 lightRadius; ///< Extent of light. + /// @} + + /// @name Shape Data + /// @{ + Resource shape; ///< Shape handle + + U32 mCRC; ///< Checksum of shape. + /// + /// Calculated by the ResourceManager, see + /// ResourceManager::load(). + bool computeCRC; ///< Should the shape's CRC be checked? + MatrixF mountTransform; ///< Transformation to get to the mountNode. + /// @} + + /// @name Nodes + /// @{ + S32 retractNode; ///< Retraction node ID. + /// + /// When the player bumps against an object and the image is retracted to + /// avoid having it interpenetrating the object, it is retracted towards + /// this node. + S32 muzzleNode; ///< Muzzle node ID. + /// + /// + S32 ejectNode; ///< Ejection node ID. + /// + /// The eject node is the node on the image from which shells are ejected. + S32 emitterNode; ///< Emitter node ID. + /// + /// The emitter node is the node from which particles are emitted. + /// @} + + /// @name Animation + /// @{ + S32 spinSequence; ///< ID of the spin animation sequence. + S32 ambientSequence; ///< ID of the ambient animation sequence. + + bool isAnimated; ///< This image contains at least one animated states + bool hasFlash; ///< This image contains at least one muzzle flash animation state + S32 fireState; ///< The ID of the fire state. + /// @} + + /// @name Shell casing data + /// @{ + DebrisData * casing; ///< Information about shell casings. + + S32 casingID; ///< ID of casing datablock. + /// + /// When the network tells the client about the casing, it + /// it transmits the ID of the datablock. The datablocks + /// having previously been transmitted, all the client + /// needs to do is call Sim::findObject() and look up the + /// the datablock. + + Point3F shellExitDir; ///< Vector along which to eject shells from the image. + F32 shellExitVariance; ///< Variance from this vector in degrees. + F32 shellVelocity; ///< Velocity with which to eject shell casings. + /// @} + + /// @name State Array + /// + /// State array is initialized onAdd from the individual state + /// struct array elements. + /// + /// @{ + StateData state[MaxStates]; ///< Array of states. + bool statesLoaded; ///< Are the states loaded yet? + /// @} + + /// @name Infrastructure + /// + /// Miscellaneous inherited methods. + /// @{ + + DECLARE_CONOBJECT(ShapeBaseImageData); + ShapeBaseImageData(); + ~ShapeBaseImageData(); + bool onAdd(); + bool preload(bool server, String &errorStr); + S32 lookupState(const char* name); ///< Get a state by name. + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + /// @} +}; + +//-------------------------------------------------------------------------- +/// @nosubgrouping +struct ShapeBaseData : public GameBaseData { + private: + typedef GameBaseData Parent; + +public: + /// Various constants relating to the ShapeBaseData + enum Constants { + NumMountPoints = 32, + NumMountPointBits = 5, + MaxCollisionShapes = 8, + AIRepairNode = 31 + }; + + // TODO: These are only really used in Basic Lighting + // mode... we should probably move them somewhere else. + bool shadowEnable; + U32 shadowSize; + F32 shadowMaxVisibleDistance; + F32 shadowProjectionDistance; + F32 shadowSphereAdjust; + + + StringTableEntry shapeName; + StringTableEntry cloakTexName; + + String cubeDescName; + ReflectorDesc *reflectorDesc; + + /// @name Destruction + /// + /// Everyone likes to blow things up! + /// @{ + DebrisData * debris; + S32 debrisID; + StringTableEntry debrisShapeName; + Resource debrisShape; + + ExplosionData* explosion; + S32 explosionID; + + ExplosionData* underwaterExplosion; + S32 underwaterExplosionID; + /// @} + + /// @name Physical Properties + /// @{ + F32 mass; + F32 drag; + F32 density; + F32 maxEnergy; + F32 maxDamage; + F32 repairRate; ///< Rate per tick. + + F32 disabledLevel; + F32 destroyedLevel; + /// @} + + /// @name 3rd Person Camera + /// @{ + F32 cameraMaxDist; ///< Maximum distance from eye + F32 cameraMinDist; ///< Minumumistance from eye + /// @} + + /// @name Camera FOV + /// + /// These are specified in degrees. + /// @{ + F32 cameraDefaultFov; ///< Default FOV. + F32 cameraMinFov; ///< Min FOV allowed. + F32 cameraMaxFov; ///< Max FOV allowed. + /// @} + + /// @name Data initialized on preload + /// @{ + + Resource mShape; ///< Shape handle + U32 mCRC; + bool computeCRC; + + S32 eyeNode; ///< Shape's eye node index + S32 cameraNode; ///< Shape's camera node index + S32 mountPointNode[NumMountPoints]; ///< Node index of mountPoint + S32 debrisDetail; ///< Detail level used to generate debris + S32 damageSequence; ///< Damage level decals + S32 hulkSequence; ///< Destroyed hulk + + bool observeThroughObject; // observe this object through its camera transform and default fov + + /// @name HUD + /// + /// @note This may be only semi-functional. + /// @{ + + enum { + NumHudRenderImages = 8, + }; + + StringTableEntry hudImageNameFriendly[NumHudRenderImages]; + StringTableEntry hudImageNameEnemy[NumHudRenderImages]; +// TextureHandle hudImageFriendly[NumHudRenderImages]; +// TextureHandle hudImageEnemy[NumHudRenderImages]; + + bool hudRenderCenter[NumHudRenderImages]; + bool hudRenderModulated[NumHudRenderImages]; + bool hudRenderAlways[NumHudRenderImages]; + bool hudRenderDistance[NumHudRenderImages]; + bool hudRenderName[NumHudRenderImages]; + /// @} + + /// @name Collision Data + /// @{ + Vector collisionDetails; ///< Detail level used to collide with. + /// + /// These are detail IDs, see TSShape::findDetail() + Vector collisionBounds; ///< Detail level bounding boxes. + + Vector LOSDetails; ///< Detail level used to perform line-of-sight queries against. + /// + /// These are detail IDs, see TSShape::findDetail() + /// @} + + /// @name Misc. Settings + /// @{ + bool firstPersonOnly; ///< Do we allow only first person view of this image? + bool useEyePoint; ///< Do we use this object's eye point to view from? + bool aiAvoidThis; ///< If set, the AI's will try to walk around this object... + /// + /// @note There isn't really any AI code that can take advantage of this. + bool isInvincible; ///< If set, object cannot take damage (won't show up with damage bar either) + bool renderWhenDestroyed; ///< If set, will not render this object when destroyed. + + bool inheritEnergyFromMount; + + /// @} + + bool preload(bool server, String &errorStr); + void computeAccelerator(U32 i); + S32 findMountPoint(U32 n); + + /// @name Infrastructure + /// The derived class should provide the following: + /// @{ + DECLARE_CONOBJECT(ShapeBaseData); + ShapeBaseData(); + ~ShapeBaseData(); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + /// @} +}; + + +//---------------------------------------------------------------------------- + +class WaterObject; + +/// ShapeBase is the renderable shape from which most of the scriptable objects +/// are derived, including the player, vehicle and items classes. ShapeBase +/// provides basic shape loading, audio channels, and animation as well as damage +/// (and damage states), energy, and the ability to mount images and objects. +/// +/// @nosubgrouping +class ShapeBase : public GameBase, public ISceneLight +{ + typedef GameBase Parent; + friend class ShapeBaseConvex; + friend struct ShapeBaseImageData; + friend void waterFind(SceneObject*, void*); + friend void physicalZoneFind(SceneObject*, void*); + +public: + enum PublicConstants { + ThreadSequenceBits = 6, + MaxSequenceIndex = (1 << ThreadSequenceBits) - 1, + EnergyLevelBits = 5, + DamageLevelBits = 6, + DamageStateBits = 2, + // The thread and image limits should not be changed without + // also changing the ShapeBaseMasks enum values declared + // further down. + MaxSoundThreads = 4, ///< Should be a power of 2 + MaxScriptThreads = 4, ///< Should be a power of 2 + MaxMountedImages = 4, ///< Should be a power of 2 + MaxImageEmitters = 3, + NumImageBits = 3, + ShieldNormalBits = 8, + CollisionTimeoutValue = 250 ///< Timeout in ms. + }; + + /// This enum indexes into the sDamageStateName array + enum DamageState { + Enabled, + Disabled, + Destroyed, + NumDamageStates, + NumDamageStateBits = 2, ///< Should be log2 of the number of states. + }; + +private: + ShapeBaseData* mDataBlock; ///< Datablock + //GameConnection* mControllingClient; ///< Controlling client + ShapeBase* mControllingObject; ///< Controlling object + bool mTrigger[MaxTriggerKeys]; ///< What triggers are set, if any. + + + /// @name Scripted Sound + /// @{ + struct Sound { + bool play; ///< Are we playing this sound? + SimTime timeout; ///< Time until we stop playing this sound. + SFXProfile* profile; ///< Profile on server + SFXSource* sound; ///< Sound on client + }; + Sound mSoundThread[MaxSoundThreads]; + /// @} + + /// @name Scripted Animation Threads + /// @{ + + struct Thread { + /// State of the animation thread. + enum State { + Play, Stop, Pause + }; + TSThread* thread; ///< Pointer to 3space data. + U32 state; ///< State of the thread + /// + /// @see Thread::State + S32 sequence; ///< The animation sequence which is running in this thread. + F32 timescale; ///< Timescale + U32 sound; ///< Handle to sound. + bool atEnd; ///< Are we at the end of this thread? + F32 position; + }; + Thread mScriptThread[MaxScriptThreads]; + + /// @} + + /// @name Invincibility + /// @{ + F32 mInvincibleCount; + F32 mInvincibleTime; + F32 mInvincibleSpeed; + F32 mInvincibleDelta; + F32 mInvincibleEffect; + F32 mInvincibleFade; + bool mInvincibleOn; + /// @} + +protected: + + // ShapeBase pointer to our mount object if it is ShapeBase, else it is NULL. + ShapeBase *mShapeBaseMount; + + /// @name Mounted Images + /// @{ + + /// An image mounted on a shapebase. + struct MountedImage { + ShapeBaseImageData* dataBlock; + ShapeBaseImageData::StateData *state; + ShapeBaseImageData* nextImage; + NetStringHandle skinNameHandle; + NetStringHandle nextSkinNameHandle; + String appliedSkinName; + + /// @name State + /// + /// Variables tracking the state machine + /// representing this specific mounted image. + /// @{ + + bool loaded; ///< Is the image loaded? + bool nextLoaded; ///< Is the next state going to result in the image being loaded? + F32 delayTime; ///< Time till next state. + U32 fireCount; ///< Fire skip count. + /// + /// This is incremented every time the triggerDown bit is changed, + /// so that the engine won't be too confused if the player toggles the + /// trigger a bunch of times in a short period. + /// + /// @note The network deals with this variable at 3-bit precision, so it + /// can only range 0-7. + /// + /// @see ShapeBase::setImageState() + + bool triggerDown; ///< Is the trigger down? + bool altTriggerDown; ///< Is the second trigger down? + + bool ammo; ///< Do we have ammo? + /// + /// May be true based on either energy OR ammo. + + bool target; ///< Have we acquired a targer? + bool wet; ///< Is the weapon wet? + + /// @} + + /// @name 3space + /// + /// Handles to threads and shapeinstances in the 3space system. + /// @{ + TSShapeInstance* shapeInstance; + TSThread *ambientThread; + TSThread *visThread; + TSThread *animThread; + TSThread *flashThread; + TSThread *spinThread; + /// @} + + /// @name Effects + /// + /// Variables relating to lights, sounds, and particles. + /// @{ + SimTime lightStart; ///< Starting time for light flashes. + LightInfo* lightInfo; ///< The real light (if any) associated with this weapon image. + + Vector mSoundSources; ///< Vector of currently playing sounds + void updateSoundSources(const MatrixF& renderTransform); + void addSoundSource(SFXSource* source); + + /// Represent the state of a specific particle emitter on the image. + struct ImageEmitter { + S32 node; + F32 time; + SimObjectPtr emitter; + }; + ImageEmitter emitter[MaxImageEmitters]; + + /// @} + + // + MountedImage(); + ~MountedImage(); + }; + MountedImage mMountedImageList[MaxMountedImages]; + + /// @} + + /// @name Render settings + /// @{ + + TSShapeInstance* mShapeInstance; + Convex * mConvexList; + NetStringHandle mSkinNameHandle; + String mAppliedSkinName; + + NetStringHandle mShapeNameHandle; ///< Name sent to client + /// @} + + /// @name Physical Properties + /// @{ + + F32 mEnergy; ///< Current enery level. + F32 mRechargeRate; ///< Energy recharge rate (in units/tick). + + F32 mMass; ///< Mass. + F32 mOneOverMass; ///< Inverse of mass. + /// @note This is used to optimize certain physics calculations. + + /// @} + + /// @name Physical Properties + /// + /// Properties for the current object, which are calculated + /// based on the container we are in. + /// + /// @see ShapeBase::updateContainer() + /// @see ShapeBase::mContainer + /// @{ + F32 mDrag; ///< Drag. + F32 mBuoyancy; ///< Buoyancy factor. + String mLiquidType; ///< Type of liquid (if any) we are in. + F32 mLiquidHeight; ///< Height of liquid around us (from 0..1). + F32 mWaterCoverage; ///< Percent of this object covered by water + + Point3F mAppliedForce; + F32 mGravityMod; + /// @} + + F32 mDamageFlash; + F32 mWhiteOut; + + bool mFlipFadeVal; + + /// Last shield direction (cur. unused) + Point3F mShieldNormal; + + public: + + /// @name Collision Notification + /// This is used to keep us from spamming collision notifications. When + /// a collision occurs, we add to this list; then we don't notify anyone + /// of the collision until the CollisionTimeout expires (which by default + /// occurs in 1/10 of a second). + /// + /// @see notifyCollision(), queueCollision() + /// @{ + struct CollisionTimeout + { + CollisionTimeout* next; + SceneObject* object; + U32 objectNumber; + SimTime expireTime; + VectorF vector; + }; + CollisionTimeout* mTimeoutList; + static CollisionTimeout* sFreeTimeoutList; + + /// Go through all the items in the collision queue and call onCollision on them all + /// @see onCollision + void notifyCollision(); + + /// Add a collision to the queue of collisions waiting to be handled @see onCollision + /// @param object Object collision occurs with + /// @param vec Vector along which collision occurs + void queueCollision( SceneObject *object, const VectorF &vec); + + /// @see SceneObject + virtual void onCollision( SceneObject *object, const VectorF &vec ); + + /// @} + protected: + + /// @name Damage + /// @{ + F32 mDamage; + F32 mRepairRate; + F32 mRepairReserve; + DamageState mDamageState; + TSThread *mDamageThread; + TSThread *mHulkThread; + VectorF damageDir; + /// @} + + /// @name Cloaking + /// @{ + bool mCloaked; + F32 mCloakLevel; +// TextureHandle mCloakTexture; + bool mHidden; ///< in/out of world + + /// @} + + /// @name Fading + /// @{ + bool mFadeOut; + bool mFading; + F32 mFadeVal; + F32 mFadeElapsedTime; + F32 mFadeTime; + F32 mFadeDelay; +public: + F32 getFadeVal() { return mFadeVal; } + /// @} +protected: + + /// @name Control info + /// @{ + F32 mCameraFov; ///< Camera FOV(in degrees) + bool mIsControlled; ///< Client side controlled flag + + /// @} +public: + static U32 sLastRenderFrame; +protected: + + U32 mLastRenderFrame; + F32 mLastRenderDistance; + U32 mSkinHash; + + /// Do a reskin if necessary. + void reSkin(); + + /// This recalculates the total mass of the object, and all mounted objects + void updateMass(); + + /// @name Image Manipulation + /// @{ + + /// Utility function to call script functions which have to do with ShapeBase + /// objects. + /// @param imageSlot Image Slot id + /// @param function Function + void scriptCallback(U32 imageSlot,const char* function); + + /// Assign a ShapeBaseImage to an image slot + /// @param imageSlot Image Slot ID + /// @param imageData ShapeBaseImageData to assign + /// @param skinNameHandle Skin texture name + /// @param loaded Is the image loaded? + /// @param ammo Does the image have ammo? + /// @param triggerDown Is the trigger on this image down? + /// @param altTriggerDown Is the second trigger on this image down? + /// @param target Does the image have a target? + virtual void setImage( U32 imageSlot, + ShapeBaseImageData* imageData, + NetStringHandle &skinNameHandle, + bool loaded = true, bool ammo = false, + bool triggerDown = false, + bool altTriggerDown = false, + bool target = false ); + + /// Clear out an image slot + /// @param imageSlot Image slot id + void resetImageSlot(U32 imageSlot); + + /// Get the firing action state of the image + /// @param imageSlot Image slot id + U32 getImageFireState(U32 imageSlot); + + /// Sets the state of the image + /// @param imageSlot Image slot id + /// @param state State id + /// @param force Force image to state or let it finish then change + void setImageState(U32 imageSlot, U32 state, bool force = false); + + /// Advance animation on a image + /// @param imageSlot Image slot id + /// @param dt Change in time since last animation update + void updateImageAnimation(U32 imageSlot, F32 dt); + + /// Advance state of image + /// @param imageSlot Image slot id + /// @param dt Change in time since last state update + void updateImageState(U32 imageSlot,F32 dt); + + /// Start up the particle emitter for the this shapebase + /// @param image Mounted image + /// @param state State of shape base image + void startImageEmitter(MountedImage &image,ShapeBaseImageData::StateData &state); + + /// Get light information for a mounted image + /// @param imageSlot Image slot id + Light* getImageLight(U32 imageSlot); + + /// @} + + /// Prune out non looping sounds from the sound manager which have expired + void updateServerAudio(); + + /// Updates the audio state of the supplied sound + /// @param st Sound + void updateAudioState(Sound& st); + + /// Recalculates the spacial sound based on the current position of the object + /// emitting the sound. + void updateAudioPos(); + + /// Update bouyency and drag properties + void updateContainer(); + + /// @name Events + /// @{ + virtual void onDeleteNotify(SimObject*); + virtual void onImageRecoil(U32 imageSlot,ShapeBaseImageData::StateData::RecoilState); + virtual void ejectShellCasing( U32 imageSlot ); + virtual void updateDamageLevel(); + virtual void updateDamageState(); + virtual void blowUp(); + virtual void onImpact(SceneObject* obj, VectorF vec); + virtual void onImpact(VectorF vec); + /// @} + + /// The inner prep render function that does the + /// standard work to render the shapes. + bool _prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseZoneState, + bool renderSelf, + bool renderMountedImages ); + + /// Renders the shape bounds as well as the + /// bounds of all mounted shape images. + void _renderBoundingBox( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ); + + void emitDust( ParticleEmitter* emitter, F32 triggerHeight, const Point3F& offset, U32 numMilliseconds, const Point3F& axis = Point3F::Zero ); + +public: + ShapeBase(); + ~ShapeBase(); + + TSShapeInstance* getShapeInstance() { return mShapeInstance; } + + /// @name Network state masks + /// @{ + + /// + enum ShapeBaseMasks { + NameMask = Parent::NextFreeMask, + DamageMask = Parent::NextFreeMask << 1, + NoWarpMask = Parent::NextFreeMask << 2, + MountedMask = Parent::NextFreeMask << 3, + HideCloakMask = Parent::NextFreeMask << 4, ///< Hiding/cloaking state changed. + ShieldMask = Parent::NextFreeMask << 5, + InvincibleMask = Parent::NextFreeMask << 6, + SkinMask = Parent::NextFreeMask << 7, + MeshHiddenMask = Parent::NextFreeMask << 8, + SoundMaskN = Parent::NextFreeMask << 9, ///< Extends + MaxSoundThreads bits + ThreadMaskN = SoundMaskN << MaxSoundThreads, ///< Extends + MaxScriptThreads bits + ImageMaskN = ThreadMaskN << MaxScriptThreads, ///< Extends + MaxMountedImage bits + NextFreeMask = ImageMaskN << MaxMountedImages + }; + + enum BaseMaskConstants { + SoundMask = (SoundMaskN << MaxSoundThreads) - SoundMaskN, + ThreadMask = (ThreadMaskN << MaxScriptThreads) - ThreadMaskN, + ImageMask = (ImageMaskN << MaxMountedImages) - ImageMaskN + }; + + /// @} + + static bool gRenderEnvMaps; ///< Global flag which turns on or off all environment maps + static F32 sWhiteoutDec; + static F32 sDamageFlashDec; + + CubeReflector mCubeReflector; + + /// @name Initialization + /// @{ + + bool onAdd(); + void onRemove(); + void onSceneRemove(); + static void consoleInit(); + bool onNewDataBlock(GameBaseData* dptr); + + /// @} + + /// @name Name & Skin tags + /// @{ + void setShapeName(const char*); + const char* getShapeName(); + void setSkinName(const char*); + const char* getSkinName(); + /// @} + + /// @name Mesh Visibility + /// @{ + +protected: + + /// A bit vector of the meshes forced to be hidden. + BitVector mMeshHidden; + + /// Sync the shape instance with the hidden mesh bit vector. + void _updateHiddenMeshes(); + +public: + + /// Change the hidden state on all the meshes. + void setAllMeshesHidden( bool forceHidden ); + + /// Set the force hidden state on a mesh. + void setMeshHidden( S32 meshIndex, bool forceHidden ); + + /// Set the force hidden state on a named mesh. + void setMeshHidden( const char *meshName, bool forceHidden ); + +#ifndef TORQUE_SHIPPING + + /// Prints the list of meshes and their visibility state + /// to the console for debugging purposes. + void dumpMeshVisibility(); + +#endif + + /// @} + +public: + + /// @name Basic attributes + /// @{ + + /// Sets the amount of damage on this object. + void setDamageLevel(F32 damage); + + /// Changes the object's damage state. + /// @param state New state of the object + void setDamageState(DamageState state); + + /// Changes the object's damage state, based on a named state. + /// @see setDamageState + /// @param state New state of the object as a string. + bool setDamageState(const char* state); + + /// Returns the name of the current damage state as a string. + const char* getDamageStateName(); + + /// Returns the current damage state. + DamageState getDamageState() { return mDamageState; } + + /// Returns true if the object is destroyed. + bool isDestroyed() { return mDamageState == Destroyed; } + + /// Sets the rate at which the object regenerates damage. + /// + /// @param rate Repair rate in units/second. + void setRepairRate(F32 rate) { mRepairRate = rate; } + + /// Returns damage amount. + F32 getDamageLevel() { return mDamage; } + + /// Returns the damage percentage. + /// + /// @return Damage factor, between 0.0 - 1.0 + F32 getDamageValue(); + + /// Returns the rate at which the object regenerates damage + F32 getRepairRate() { return mRepairRate; } + + /// Adds damage to an object + /// @param amount Amount of of damage to add + void applyDamage(F32 amount); + + /// Removes damage to an object + /// @param amount Amount to repair object by + void applyRepair(F32 amount); + + /// Sets the direction from which the damage is coming + /// @param vec Vector indicating the direction of the damage + void setDamageDir(const VectorF& vec) { damageDir = vec; } + + /// Sets the level of energy for this object + /// @param energy Level of energy to assign to this object + virtual void setEnergyLevel(F32 energy); + + /// Sets the rate at which the energy replentishes itself + /// @param rate Rate at which energy restores + void setRechargeRate(F32 rate) { mRechargeRate = rate; } + + /// Returns the amount of energy in the object + F32 getEnergyLevel(); + + /// Returns the percentage of energy, 0.0 - 1.0 + F32 getEnergyValue(); + + /// Returns the recharge rate + F32 getRechargeRate() { return mRechargeRate; } + + /// @} + + /// @name Script sounds + /// @{ + + /// Plays an audio sound from a mounted object + /// @param slot Mount slot ID + /// @param profile Audio profile to play + void playAudio(U32 slot,SFXProfile* profile); + + /// Stops audio from a mounted object + /// @param slot Mount slot ID + void stopAudio(U32 slot); + /// @} + + /// @name Script animation + /// @{ + + const char *getThreadSequenceName( U32 slot ); + + /// Sets the animation thread for a mounted object + /// @param slot Mount slot ID + /// @param seq Sequence id + /// @param reset Reset the sequence + bool setThreadSequence(U32 slot, S32 seq, bool reset = true); + + /// Update the animation thread + /// @param st Thread to update + void updateThread(Thread& st); + + /// Stop the current thread from playing on a mounted object + /// @param slot Mount slot ID + bool stopThread(U32 slot); + + /// Pause the running animation thread + /// @param slot Mount slot ID + bool pauseThread(U32 slot); + + /// Start playing the running animation thread again + /// @param slot Mount slot ID + bool playThread(U32 slot); + + /// Set the thread position + /// @param slot Mount slot ID + /// @param pos Position + bool setThreadPosition( U32 slot, F32 pos ); + + /// Toggle the thread as reversed or normal (For example, sidestep-right reversed is sidestep-left) + /// @param slot Mount slot ID + /// @param forward True if the animation is to be played normally + bool setThreadDir(U32 slot,bool forward); + + /// Set the thread time scale + /// @param slot Mount slot ID + /// @param timescale Timescale + bool setThreadTimeScale( U32 slot, F32 timeScale ); + + /// Start the sound associated with an animation thread + /// @param thread Thread + void startSequenceSound(Thread& thread); + + /// Stop the sound associated with an animation thread + /// @param thread Thread + void stopThreadSound(Thread& thread); + + /// Advance all animation threads attached to this shapebase + /// @param dt Change in time from last call to this function + void advanceThreads(F32 dt); + /// @} + + /// @name Cloaking + /// @{ + + /// Force uncloaking of object + /// @param reason Reason this is being forced to uncloak, this is passed directly to script control + void forceUncloak(const char *reason); + + /// Set cloaked state of object + /// @param cloaked True if object is cloaked + void setCloakedState(bool cloaked); + + /// Returns true if object is cloaked + bool getCloakedState(); + + /// Returns level of cloaking, as it's not an instant "now you see it, now you don't" + F32 getCloakLevel(); + /// @} + + /// @name Mounted objects + /// @{ + virtual void mountObject(SceneObject* obj,U32 node); + virtual void unmountObject(SceneObject *obj); + virtual void unmount(); + virtual void onMount( SceneObject *obj, S32 node ); + virtual void onUnmount( SceneObject *obj,S32 node ); + virtual void getMountTransform( U32 mountPoint, MatrixF *mat ); + virtual void getRenderMountTransform( U32 index, MatrixF *mat ); + /// @} + + /// Returns where the AI should be to repair this object + /// + /// @note Legacy code from Tribes 2, but still works + Point3F getAIRepairPoint(); + + /// @name Mounted Images + /// @{ + + /// Mount an image (ShapeBaseImage) onto an image slot + /// @param image ShapeBaseImage to mount + /// @param imageSlot Image mount point + /// @param loaded True if weapon is loaded (it assumes it's a weapon) + /// @param skinNameHandle Skin name for object + virtual bool mountImage(ShapeBaseImageData* image,U32 imageSlot,bool loaded, NetStringHandle &skinNameHandle); + + /// Unmount an image from a slot + /// @param imageSlot Mount point + virtual bool unmountImage(U32 imageSlot); + + /// Gets the information on the image mounted in a slot + /// @param imageSlot Mount point + ShapeBaseImageData* getMountedImage(U32 imageSlot); + + /// Gets the mounted image on on a slot + /// @param imageSlot Mount Point + MountedImage* getImageStruct(U32 imageSlot); + + TSShapeInstance* getImageShapeInstance(U32 imageSlot) + { + const MountedImage &image = mMountedImageList[imageSlot]; + if(image.dataBlock && image.shapeInstance) + return image.shapeInstance; + return NULL; + } + + /// Gets the next image which will be put in an image slot + /// @see setImageState + /// @param imageSlot mount Point + ShapeBaseImageData* getPendingImage(U32 imageSlot); + + + /// Returns true if the mounted image is firing + /// @param imageSlot Mountpoint + bool isImageFiring(U32 imageSlot); + + /// This will return true if, when triggered, the object will fire. + /// @param imageSlot mount point + /// @param ns Used internally for recursion, do not mess with + /// @param depth Used internally for recursion, do not mess with + bool isImageReady(U32 imageSlot,U32 ns = (U32)-1,U32 depth = 0); + + /// Returns true if the specified image is mounted + /// @param image ShapeBase image + bool isImageMounted(ShapeBaseImageData* image); + + /// Returns the slot which the image specified is mounted on + /// @param image Image to test for + S32 getMountSlot(ShapeBaseImageData* image); + + /// Returns the skin for the image in a slot + /// @param imageSlot Image slot to get the skin from + NetStringHandle getImageSkinTag(U32 imageSlot); + + /// Returns the image state as a string + /// @param imageSlot Image slot to check state + const char* getImageState(U32 imageSlot); + + /// Sets the trigger state of the image (Ie trigger pulled down on gun) + /// @param imageSlot Image slot + /// @param trigger True if trigger is down + void setImageTriggerState(U32 imageSlot,bool trigger); + + /// Returns the trigger state of the image + /// @param imageSlot Image slot + bool getImageTriggerState(U32 imageSlot); + + /// Sets the alt trigger state of the image (Ie trigger pulled down on gun) + /// @param imageSlot Image slot + /// @param trigger True if trigger is down + void setImageAltTriggerState( U32 imageSlot, bool trigger ); + + /// Returns the alt trigger state of the image + /// @param imageSlot Image slot + bool getImageAltTriggerState( U32 imageSlot ); + + /// Sets the flag if the image uses ammo or energy + /// @param imageSlot Image slot + /// @param ammo True if the weapon uses ammo, not energy + void setImageAmmoState(U32 imageSlot,bool ammo); + + /// Returns true if the image uses ammo, not energy + /// @param imageSlot Image slot + bool getImageAmmoState(U32 imageSlot); + + /// Sets the image as wet or not, IE if you wanted a gun not to function underwater + /// @param imageSlot Image slot + /// @param wet True if image is wet + void setImageWetState(U32 imageSlot,bool wet); + + /// Returns true if image is wet + /// @param imageSlot image slot + bool getImageWetState(U32 imageSlot); + + /// Sets the flag of if the image is loaded with ammo + /// @param imageSlot Image slot + /// @param loaded True if object is loaded with ammo + void setImageLoadedState(U32 imageSlot,bool loaded); + + /// Returns true if object is loaded with ammo + /// @param imageSlot Image slot + bool getImageLoadedState(U32 imageSlot); + + /// Modify muzzle, if needed, to aim at whatever is straight in front of eye. + /// Returns true if result is actually modified. + /// @param muzMat Muzzle transform (in/out) + /// @param result Corrected muzzle vector (out) + bool getCorrectedAim(const MatrixF& muzMat, VectorF* result); + + /// Gets the muzzle vector of a specified slot + /// @param imageSlot Image slot to check transform for + /// @param vec Muzzle vector (out) + virtual void getMuzzleVector(U32 imageSlot,VectorF* vec); + + /// Gets the point of the muzzle of the image + /// @param imageSlot Image slot + /// @param pos Muzzle point (out) + void getMuzzlePoint(U32 imageSlot,Point3F* pos); + + /// @} + + /// @name Transforms + /// @{ + + /// Gets the minimum viewing distance, maximum viewing distance, camera offsetand rotation + /// for this object, if the world were to be viewed through its eyes + /// @param min Minimum viewing distance + /// @param max Maximum viewing distance + /// @param offset Offset of the camera from the origin in local space + /// @param rot Rotation matrix + virtual void getCameraParameters(F32 *min,F32* max,Point3F* offset,MatrixF* rot); + + /// Gets the camera to world space transform matrix + /// @todo Find out what pos does + /// @param pos TODO: Find out what this does + /// @param mat Camera transform (out) + virtual void getCameraTransform(F32* pos,MatrixF* mat); + + /// Gets the index of a node inside a mounted image given the name + /// @param imageSlot Image slot + /// @param nodeName Node name + S32 getNodeIndex(U32 imageSlot,StringTableEntry nodeName); + + /// @} + + /// @name Object Transforms + /// @{ + + /// Returns the eye transform of this shape, IE the eyes of a player + /// @param mat Eye transform (out) + virtual void getEyeTransform(MatrixF* mat); + + /// The retraction transform is the muzzle transform in world space. + /// + /// If the gun is pushed back, for instance, if the player ran against something, + /// the muzzle of the gun gets pushed back towards the player, towards this location. + /// @param imageSlot Image slot + /// @param mat Transform (out) + virtual void getRetractionTransform(U32 imageSlot,MatrixF* mat); + + /// Muzzle transform of mounted object in world space + /// @param imageSlot Image slot + /// @param mat Muzzle transform (out) + virtual void getMuzzleTransform(U32 imageSlot,MatrixF* mat); + + /// Gets the transform of a mounted image in world space + /// @param imageSlot Image slot + /// @param mat Transform (out) + virtual void getImageTransform(U32 imageSlot,MatrixF* mat); + + /// Gets the transform of a node on a mounted image in world space + /// @param imageSlot Image Slot + /// @param node node on image + /// @param mat Transform (out) + virtual void getImageTransform(U32 imageSlot,S32 node, MatrixF* mat); + + /// Gets the transform of a node on a mounted image in world space + /// @param imageSlot Image Slot + /// @param nodeName Name of node on image + /// @param mat Transform (out) + virtual void getImageTransform(U32 imageSlot, StringTableEntry nodeName, MatrixF* mat); + + ///@} + + /// @name Render transforms + /// Render transforms are different from object transforms in that the render transform of an object + /// is where, in world space, the object is actually rendered. The object transform is the + /// absolute position of the object, as in where it should be. + /// + /// The render transforms typically vary from object transforms due to client side prediction. + /// + /// Other than that, these functions are identical to their object-transform counterparts + /// + /// @note These are meaningless on the server. + /// @{ + virtual void getRenderRetractionTransform(U32 index,MatrixF* mat); + virtual void getRenderMuzzleTransform(U32 index,MatrixF* mat); + virtual void getRenderImageTransform(U32 imageSlot,MatrixF* mat,bool noEyeOffset=false); + virtual void getRenderImageTransform(U32 index,S32 node, MatrixF* mat); + virtual void getRenderImageTransform(U32 index, StringTableEntry nodeName, MatrixF* mat); + virtual void getRenderMuzzleVector(U32 imageSlot,VectorF* vec); + virtual void getRenderMuzzlePoint(U32 imageSlot,Point3F* pos); + virtual void getRenderEyeTransform(MatrixF* mat); + /// @} + + + + /// @name Screen Flashing + /// @{ + + /// Returns the level of screenflash that should be used + virtual F32 getDamageFlash() const; + + /// Sets the flash level + /// @param amt Level of flash + virtual void setDamageFlash(const F32 amt); + + /// White out is the flash-grenade blindness effect + /// This returns the level of flash to create + virtual F32 getWhiteOut() const; + + /// Set the level of flash blindness + virtual void setWhiteOut(const F32); + /// @} + + /// @name Invincibility effect + /// This is the screen effect when invincible in the HUD + /// @see GameRenderFilters() + /// @{ + + /// Returns the level of invincibility effect + virtual F32 getInvincibleEffect() const; + + /// Initializes invincibility effect and interpolation parameters + /// + /// @param time Time it takes to become invincible + /// @param speed Speed at which invincibility effects progress + virtual void setupInvincibleEffect(F32 time, F32 speed); + + /// Advance invincibility effect animation + /// @param dt Time since last call of this function + virtual void updateInvincibleEffect(F32 dt); + + /// @} + + /// @name Movement & velocity + /// @{ + + /// Sets the velocity of this object + /// @param vel Velocity vector + virtual void setVelocity(const VectorF& vel); + + /// Applies an impulse force to this object + /// @param pos Position where impulse came from in world space + /// @param vec Velocity vector (Impulse force F = m * v) + virtual void applyImpulse(const Point3F& pos,const VectorF& vec); + + /// @} + + /// @name Cameras and Control + /// @{ + + /// Returns the object controlling this object + ShapeBase* getControllingObject() { return mControllingObject; } + + /// Sets the controlling object + /// @param obj New controlling object + virtual void setControllingObject(ShapeBase* obj); + + /// Returns the object this is controlling + virtual ShapeBase* getControlObject(); + + /// sets the object this is controlling + /// @param obj New controlled object + virtual void setControlObject(ShapeBase *obj); + + /// Returns true if this object is controlling by something + bool isControlled() { return(mIsControlled); } + + /// Returns true if this object is being used as a camera in first person + bool isFirstPerson(); + + /// Returns true if the camera uses this objects eye point (defined by modeler) + bool useObjsEyePoint() const; + + /// Returns true if this object can only be used as a first person camera + bool onlyFirstPerson() const; + + /// Returns the Field of Vision for this object if used as a camera + virtual F32 getCameraFov(); + + /// Returns the default FOV if this object is used as a camera + virtual F32 getDefaultCameraFov(); + + /// Sets the FOV for this object if used as a camera + virtual void setCameraFov(F32 fov); + + /// Returns true if the FOV supplied is within allowable parameters + /// @param fov FOV to test + virtual bool isValidCameraFov(F32 fov); + /// @} + + + void processTick(const Move *move); + void advanceTime(F32 dt); + + /// @name Rendering + /// @{ + + /// Returns the renderable shape of this object + TSShape const* getShape(); + + /// @see SceneObject + virtual bool prepRenderImage(SceneState* state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + + /// Used from ShapeBase::_prepRenderImage() to submit render + /// instances for the main shape or its mounted elements. + virtual void prepBatchRender( SceneState *state, S32 mountedImageIndex ); + + /// Preprender logic + virtual void calcClassRenderData() { } + + /// Virtualize this so other classes may override it for custom reasons. + virtual void renderMountedImage( U32 imageSlot, TSRenderState &state ); + /// @} + + /// Control object scoping + void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo); + + /// @name Collision + /// @{ + + /// Casts a ray from start to end, stores gathered information in 'info' returns true if successful + /// @param start Start point for ray + /// @param end End point for ray + /// @param info Information from raycast (out) + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + + /// Casts a ray from start to end testing against rendered geometry. + /// Stores gathered information in 'info' returns true if successful. + /// @param start Start point for ray + /// @param end End point for ray + /// @param info Information from raycast (out) + bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info); + + /// Builds a polylist of the polygons in this object returns true if successful + /// @param polyList Returned polylist (out) + /// @param box Not used + /// @param sphere Not used + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF& sphere); + + /// Same as buildPolyList but operates against the rendered geometry. + /// @see buildPolyList + virtual bool buildRenderedPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + + /// Builds a convex hull for this object @see Convex + /// @param box Bounding box + /// @param convex New convex hull (out) + void buildConvex(const Box3F& box, Convex* convex); + + /// @} + + /// @name Rendering + /// @{ + + /// Increments the last rendered frame number + static void incRenderFrame() { sLastRenderFrame++; } + + /// Returns true if the last frame calculated rendered + bool didRenderLastRender() { return mLastRenderFrame == sLastRenderFrame; } + + /// Sets the state of this object as hidden or not. If an object is hidden + /// it is removed entirely from collisions, it is not ghosted and is + /// essentially "non existant" as far as simulation is concerned. + /// @param hidden True if object is to be hidden + virtual void setHidden(bool hidden); + + /// Returns true if this object is hidden + /// @see setHidden + bool isHidden() { return mHidden; } + + /// Returns true if this object can be damaged + bool isInvincible(); + + /// Start fade of object in/out + /// @param fadeTime Time fade should take + /// @param fadeDelay Delay before starting fade + /// @param fadeOut True if object is fading out, false if fading in. + void startFade( F32 fadeTime, F32 fadeDelay = 0.0, bool fadeOut = true ); + + /// Traverses mounted objects and registers light sources with the light manager + /// @param lightManager Light manager to register with + /// @param lightingScene Set to true if the scene is being lit, in which case these lights will not be used + //void registerLights(LightManager * lightManager, bool lightingScene); + + // ISceneLight + virtual void submitLights( LightManager *lm, bool staticLighting ); + virtual LightInfo* getLight() { return NULL; } + + /// @} + + /// Returns true if the point specified is in the water + /// @param point Point to test in world space + bool pointInWater( Point3F &point ); + + /// Returns the percentage of this object covered by water + F32 getWaterCoverage() { return mWaterCoverage; } + + /// Returns the height of the liquid on this object + F32 getLiquidHeight() { return mLiquidHeight; } + + virtual WaterObject* getCurrentWaterObject(); + + void setCurrentWaterObject( WaterObject *obj ); + + virtual F32 getMass() const { return mMass; } + + /// @name Network + /// @{ + + F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + void writePacketData(GameConnection *conn, BitStream *stream); + void readPacketData(GameConnection *conn, BitStream *stream); + + /// @} + + DECLARE_CONOBJECT(ShapeBase); +}; + + +//------------------------------------------------------------------------------ +// inlines +//------------------------------------------------------------------------------ + +inline bool ShapeBase::getCloakedState() +{ + return(mCloaked); +} + +inline F32 ShapeBase::getCloakLevel() +{ + return(mCloakLevel); +} + +inline const char* ShapeBase::getShapeName() +{ + return mShapeNameHandle.getString(); +} + +inline const char* ShapeBase::getSkinName() +{ + return mSkinNameHandle.getString(); +} + +inline WaterObject* ShapeBase::getCurrentWaterObject() +{ + if ( isMounted() && mShapeBaseMount ) + return mShapeBaseMount->getCurrentWaterObject(); + + return mCurrentWaterObject; +} + +#endif // _H_SHAPEBASE_ diff --git a/T3D/shapeCollision.cpp b/T3D/shapeCollision.cpp new file mode 100644 index 0000000..77cd297 --- /dev/null +++ b/T3D/shapeCollision.cpp @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/shapeBase.h" +#include "T3D/item.h" +#include "T3D/trigger.h" + +//---------------------------------------------------------------------------- + +void collisionFilter(SceneObject* object,void *key) +{ + Container::CallbackInfo* info = reinterpret_cast(key); + ShapeBase* ptr = reinterpret_cast(info->key); + + if (object->getTypeMask() & ItemObjectType) { + // We've hit it's bounding box, that's close enough for items. + Item* item = static_cast(object); + if (ptr != item->getCollisionObject()) + ptr->queueCollision(item,ptr->getVelocity() - item->getVelocity()); + } + else + if (object->getTypeMask() & TriggerObjectType) { + // We've hit it's bounding box, that's close enough for triggers + Trigger* pTrigger = static_cast(object); + pTrigger->potentialEnterObject(ptr); + } + else + if (object->getTypeMask() & CorpseObjectType) { + // Ok, guess it's close enough for corpses too... + ShapeBase* col = static_cast(object); + ptr->queueCollision(col,ptr->getVelocity() - col->getVelocity()); + } + else + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + +void defaultFilter(SceneObject* object,void * key) +{ + Container::CallbackInfo* info = reinterpret_cast(key); + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + diff --git a/T3D/shapeImage.cpp b/T3D/shapeImage.cpp new file mode 100644 index 0000000..ea0bc11 --- /dev/null +++ b/T3D/shapeImage.cpp @@ -0,0 +1,2028 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/resourceManager.h" +#include "core/stream/bitStream.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleInternal.h" +#include "console/consoleTypes.h" +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/shapeBase.h" +#include "T3D/projectile.h" +#include "T3D/gameConnection.h" +#include "math/mathIO.h" +#include "T3D/debris.h" +#include "math/mathUtils.h" +#include "sim/netObject.h" +#include "sfx/sfxSystem.h" +#include "sceneGraph/sceneGraph.h" +#include "core/stream/fileStream.h" + +//---------------------------------------------------------------------------- + +ShapeBaseImageData* InvalidImagePtr = (ShapeBaseImageData*) 1; + +static EnumTable::Enums enumLoadedStates[] = +{ + { ShapeBaseImageData::StateData::IgnoreLoaded, "Ignore" }, + { ShapeBaseImageData::StateData::Loaded, "Loaded" }, + { ShapeBaseImageData::StateData::NotLoaded, "Empty" }, +}; +static EnumTable EnumLoadedState(3, &enumLoadedStates[0]); + +static EnumTable::Enums enumSpinStates[] = +{ + { ShapeBaseImageData::StateData::IgnoreSpin,"Ignore" }, + { ShapeBaseImageData::StateData::NoSpin, "Stop" }, + { ShapeBaseImageData::StateData::SpinUp, "SpinUp" }, + { ShapeBaseImageData::StateData::SpinDown, "SpinDown" }, + { ShapeBaseImageData::StateData::FullSpin, "FullSpeed" }, +}; +static EnumTable EnumSpinState(5, &enumSpinStates[0]); + +static EnumTable::Enums enumRecoilStates[] = +{ + { ShapeBaseImageData::StateData::NoRecoil, "NoRecoil" }, + { ShapeBaseImageData::StateData::LightRecoil, "LightRecoil" }, + { ShapeBaseImageData::StateData::MediumRecoil, "MediumRecoil" }, + { ShapeBaseImageData::StateData::HeavyRecoil, "HeavyRecoil" }, +}; +static EnumTable EnumRecoilState(4, &enumRecoilStates[0]); + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(ShapeBaseImageData); + +ShapeBaseImageData::StateData::StateData() +{ + name = 0; + transition.loaded[0] = transition.loaded[1] = -1; + transition.ammo[0] = transition.ammo[1] = -1; + transition.target[0] = transition.target[1] = -1; + transition.trigger[0] = transition.trigger[1] = -1; + transition.altTrigger[0] = transition.altTrigger[1] = -1; + transition.wet[0] = transition.wet[1] = -1; + transition.timeout = -1; + waitForTimeout = true; + timeoutValue = 0; + fire = false; + energyDrain = 0; + allowImageChange = true; + loaded = IgnoreLoaded; + spin = IgnoreSpin; + recoil = NoRecoil; + flashSequence = false; + sequence = -1; + sequenceVis = -1; + sound = 0; + emitter = NULL; + script = 0; + ignoreLoadedForReady = false; + + ejectShell = false; + scaleAnimation = false; + direction = false; + emitterTime = 0.0f; + emitterNode = -1; +} + +static ShapeBaseImageData::StateData gDefaultStateData; + +//---------------------------------------------------------------------------- + +ShapeBaseImageData::ShapeBaseImageData() +{ + emap = false; + + mountPoint = 0; + mountOffset.identity(); + eyeOffset.identity(); + correctMuzzleVector = true; + firstPerson = true; + useEyeOffset = false; + mass = 0; + + usesEnergy = false; + minEnergy = 2; + accuFire = false; + + projectile = NULL; + + cloakable = true; + + lightType = ShapeBaseImageData::NoLight; + lightColor.set(1.f,1.f,1.f,1.f); + lightDuration = 1000; + lightRadius = 10.f; + + mountTransform.identity(); + shapeName = ""; + fireState = -1; + computeCRC = false; + + // + for (int i = 0; i < MaxStates; i++) { + stateName[i] = 0; + stateTransitionLoaded[i] = 0; + stateTransitionNotLoaded[i] = 0; + stateTransitionAmmo[i] = 0; + stateTransitionNoAmmo[i] = 0; + stateTransitionTarget[i] = 0; + stateTransitionNoTarget[i] = 0; + stateTransitionWet[i] = 0; + stateTransitionNotWet[i] = 0; + stateTransitionTriggerUp[i] = 0; + stateTransitionTriggerDown[i] = 0; + stateTransitionAltTriggerUp[i] = 0; + stateTransitionAltTriggerDown[i] = 0; + stateTransitionTimeout[i] = 0; + stateWaitForTimeout[i] = true; + stateTimeoutValue[i] = 0; + stateFire[i] = false; + stateEjectShell[i] = false; + stateEnergyDrain[i] = 0; + stateAllowImageChange[i] = true; + stateScaleAnimation[i] = true; + stateDirection[i] = true; + stateLoaded[i] = StateData::IgnoreLoaded; + stateSpin[i] = StateData::IgnoreSpin; + stateRecoil[i] = StateData::NoRecoil; + stateSequence[i] = 0; + stateSequenceRandomFlash[i] = false; + stateSound[i] = 0; + stateScript[i] = 0; + stateEmitter[i] = 0; + stateEmitterTime[i] = 0; + stateEmitterNode[i] = 0; + stateIgnoreLoadedForReady[i] = false; + } + statesLoaded = false; + + maxConcurrentSounds = 0; + + casing = NULL; + casingID = 0; + shellExitDir.set( 1.0, 0.0, 1.0 ); + shellExitDir.normalize(); + shellExitVariance = 20.0; + shellVelocity = 1.0; + + fireStateName = NULL; + mCRC = U32_MAX; + retractNode = -1; + muzzleNode = -1; + ejectNode = -1; + emitterNode = -1; + spinSequence = -1; + ambientSequence = -1; + isAnimated = false; + hasFlash = false; +} + +ShapeBaseImageData::~ShapeBaseImageData() +{ +} + +bool ShapeBaseImageData::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // Copy state data from the scripting arrays into the + // state structure array. If we have state data already, + // we are on the client and need to leave it alone. + for (U32 i = 0; i < MaxStates; i++) { + StateData& s = state[i]; + if (statesLoaded == false) { + s.name = stateName[i]; + s.transition.loaded[0] = lookupState(stateTransitionNotLoaded[i]); + s.transition.loaded[1] = lookupState(stateTransitionLoaded[i]); + s.transition.ammo[0] = lookupState(stateTransitionNoAmmo[i]); + s.transition.ammo[1] = lookupState(stateTransitionAmmo[i]); + s.transition.target[0] = lookupState(stateTransitionNoTarget[i]); + s.transition.target[1] = lookupState(stateTransitionTarget[i]); + s.transition.wet[0] = lookupState(stateTransitionNotWet[i]); + s.transition.wet[1] = lookupState(stateTransitionWet[i]); + s.transition.trigger[0] = lookupState(stateTransitionTriggerUp[i]); + s.transition.trigger[1] = lookupState(stateTransitionTriggerDown[i]); + s.transition.altTrigger[0] = lookupState(stateTransitionAltTriggerUp[i]); + s.transition.altTrigger[1] = lookupState(stateTransitionAltTriggerDown[i]); + s.transition.timeout = lookupState(stateTransitionTimeout[i]); + s.waitForTimeout = stateWaitForTimeout[i]; + s.timeoutValue = stateTimeoutValue[i]; + s.fire = stateFire[i]; + s.ejectShell = stateEjectShell[i]; + s.energyDrain = stateEnergyDrain[i]; + s.allowImageChange = stateAllowImageChange[i]; + s.scaleAnimation = stateScaleAnimation[i]; + s.direction = stateDirection[i]; + s.loaded = stateLoaded[i]; + s.spin = stateSpin[i]; + s.recoil = stateRecoil[i]; + s.sequence = -1; // Sequence is resolved in load + s.sequenceVis = -1; // Vis Sequence is resolved in load + s.sound = stateSound[i]; + s.script = stateScript[i]; + s.emitter = stateEmitter[i]; + s.emitterTime = stateEmitterTime[i]; + s.emitterNode = -1; // Sequnce is resolved in load + } + + // The first state marked as "fire" is the state entered on the + // client when it recieves a fire event. + if (s.fire && fireState == -1) + fireState = i; + } + + // Always preload images, this is needed to avoid problems with + // resolving sequences before transmission to a client. + return true; +} + +bool ShapeBaseImageData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) { + if (projectile) + if (Sim::findObject(SimObjectId(projectile), projectile) == false) + Con::errorf(ConsoleLogEntry::General, "Error, unable to load projectile for shapebaseimagedata"); + + for (U32 i = 0; i < MaxStates; i++) { + if (state[i].emitter) + if (!Sim::findObject(SimObjectId(state[i].emitter), state[i].emitter)) + Con::errorf(ConsoleLogEntry::General, "Error, unable to load emitter for image datablock"); + if (state[i].sound) + if (!Sim::findObject(SimObjectId(state[i].sound), state[i].sound)) + Con::errorf(ConsoleLogEntry::General, "Error, unable to load sound profile for image datablock"); + } + } + + // Use the first person eye offset if it's set. + useEyeOffset = !eyeOffset.isIdentity(); + + if (shapeName && shapeName[0]) { + // Resolve shapename + shape = ResourceManager::get().load(shapeName); + if (!bool(shape)) { + errorStr = String::ToString("Unable to load shape: %s", shapeName); + return false; + } + if(computeCRC) + { + Con::printf("Validation required for shape: %s", shapeName); + + Torque::FS::FileNodeRef fileRef = Torque::FS::GetFileNode(shape.getPath()); + + if (!fileRef) + return false; + + if(server) + mCRC = fileRef->getChecksum(); + else if(mCRC != fileRef->getChecksum()) + { + errorStr = String::ToString("Shape \"%s\" does not match version on server.",shapeName); + return false; + } + } + + // Resolve nodes & build mount transform + ejectNode = shape->findNode("ejectPoint"); + muzzleNode = shape->findNode("muzzlePoint"); + retractNode = shape->findNode("retractionPoint"); + mountTransform = mountOffset; + S32 node = shape->findNode("mountPoint"); + if (node != -1) { + MatrixF total(1); + do { + MatrixF nmat; + QuatF q; + TSTransform::setMatrix(shape->defaultRotations[node].getQuatF(&q),shape->defaultTranslations[node],&nmat); + total.mul(nmat); + node = shape->nodes[node].parentIndex; + } + while(node != -1); + total.inverse(); + mountTransform.mul(total); + } + + // Resolve state sequence names & emitter nodes + isAnimated = false; + hasFlash = false; + for (U32 i = 0; i < MaxStates; i++) { + StateData& s = state[i]; + if (stateSequence[i] && stateSequence[i][0]) + s.sequence = shape->findSequence(stateSequence[i]); + if (s.sequence != -1) + { + isAnimated = true; + } + + if (stateSequence[i] && stateSequence[i][0] && stateSequenceRandomFlash[i]) { + char bufferVis[128]; + dStrncpy(bufferVis, stateSequence[i], 100); + dStrcat(bufferVis, "_vis"); + s.sequenceVis = shape->findSequence(bufferVis); + } + if (s.sequenceVis != -1) + { + s.flashSequence = true; + hasFlash = true; + } + s.ignoreLoadedForReady = stateIgnoreLoadedForReady[i]; + + if (stateEmitterNode[i] && stateEmitterNode[i][0]) + s.emitterNode = shape->findNode(stateEmitterNode[i]); + if (s.emitterNode == -1) + s.emitterNode = muzzleNode; + } + ambientSequence = shape->findSequence("ambient"); + spinSequence = shape->findSequence("spin"); + } + else { + errorStr = "Bad Datablock from server"; + return false; + } + + if( !casing && casingID != 0 ) + { + if( !Sim::findObject( SimObjectId( casingID ), casing ) ) + { + Con::errorf( ConsoleLogEntry::General, "ShapeBaseImageData::preload: Invalid packet, bad datablockId(casing): 0x%x", casingID ); + } + } + + + TSShapeInstance* pDummy = new TSShapeInstance(shape, !server); + delete pDummy; + return true; +} + +S32 ShapeBaseImageData::lookupState(const char* name) +{ + if (!name || !name[0]) + return -1; + for (U32 i = 0; i < MaxStates; i++) + if (stateName[i] && !dStricmp(name,stateName[i])) + return i; + Con::errorf(ConsoleLogEntry::General,"ShapeBaseImageData:: Could not resolve state \"%s\" for image \"%s\"",name,getName()); + return 0; +} + +static EnumTable::Enums imageLightEnum[] = +{ + { ShapeBaseImageData::NoLight, "NoLight" }, + { ShapeBaseImageData::ConstantLight, "ConstantLight" }, + { ShapeBaseImageData::SpotLight, "SpotLight" }, + { ShapeBaseImageData::PulsingLight, "PulsingLight" }, + { ShapeBaseImageData::WeaponFireLight, "WeaponFireLight" } +}; + +static EnumTable gImageLightTypeTable(ShapeBaseImageData::NumLightTypes, &imageLightEnum[0]); + +void ShapeBaseImageData::initPersistFields() +{ + addField("emap", TypeBool, Offset(emap, ShapeBaseImageData)); + addField("shapeFile", TypeFilename, Offset(shapeName, ShapeBaseImageData)); + + addField("projectile", TypeProjectileDataPtr, Offset(projectile, ShapeBaseImageData)); + + addField("cloakable", TypeBool, Offset(cloakable, ShapeBaseImageData)); + + addField("mountPoint", TypeS32, Offset(mountPoint,ShapeBaseImageData)); + addField("offset", TypeMatrixPosition, Offset(mountOffset,ShapeBaseImageData)); + addField("rotation", TypeMatrixRotation, Offset(mountOffset,ShapeBaseImageData)); + addField("eyeOffset", TypeMatrixPosition, Offset(eyeOffset,ShapeBaseImageData)); + addField("eyeRotation", TypeMatrixRotation, Offset(eyeOffset,ShapeBaseImageData)); + addField("correctMuzzleVector", TypeBool, Offset(correctMuzzleVector, ShapeBaseImageData)); + addField("firstPerson", TypeBool, Offset(firstPerson, ShapeBaseImageData)); + addField("mass", TypeF32, Offset(mass, ShapeBaseImageData)); + + addField("usesEnergy", TypeBool, Offset(usesEnergy,ShapeBaseImageData)); + addField("minEnergy", TypeF32, Offset(minEnergy,ShapeBaseImageData)); + addField("accuFire", TypeBool, Offset(accuFire, ShapeBaseImageData)); + + addField("lightType", TypeEnum, Offset(lightType, ShapeBaseImageData), 1, &gImageLightTypeTable); + addField("lightColor", TypeColorF, Offset(lightColor, ShapeBaseImageData)); + addField("lightDuration", TypeS32, Offset(lightDuration, ShapeBaseImageData), "Duration in SimTime of Pulsing and WeaponFire type lights."); + addField("lightRadius", TypeF32, Offset(lightRadius, ShapeBaseImageData)); + + addField("casing", TypeDebrisDataPtr, Offset(casing, ShapeBaseImageData)); + addField("shellExitDir", TypePoint3F, Offset(shellExitDir, ShapeBaseImageData)); + addField("shellExitVariance", TypeF32, Offset(shellExitVariance, ShapeBaseImageData)); + addField("shellVelocity", TypeF32, Offset(shellVelocity, ShapeBaseImageData)); + + // State arrays + addField("stateName", TypeCaseString, Offset(stateName, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnLoaded", TypeString, Offset(stateTransitionLoaded, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnNotLoaded", TypeString, Offset(stateTransitionNotLoaded, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnAmmo", TypeString, Offset(stateTransitionAmmo, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnNoAmmo", TypeString, Offset(stateTransitionNoAmmo, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnTarget", TypeString, Offset(stateTransitionTarget, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnNoTarget", TypeString, Offset(stateTransitionNoTarget, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnWet", TypeString, Offset(stateTransitionWet, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnNotWet", TypeString, Offset(stateTransitionNotWet, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnTriggerUp", TypeString, Offset(stateTransitionTriggerUp, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnTriggerDown", TypeString, Offset(stateTransitionTriggerDown, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnAltTriggerUp", TypeString, Offset(stateTransitionAltTriggerUp, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnAltTriggerDown", TypeString, Offset(stateTransitionAltTriggerDown, ShapeBaseImageData), MaxStates); + addField("stateTransitionOnTimeout", TypeString, Offset(stateTransitionTimeout, ShapeBaseImageData), MaxStates); + addField("stateTimeoutValue", TypeF32, Offset(stateTimeoutValue, ShapeBaseImageData), MaxStates); + addField("stateWaitForTimeout", TypeBool, Offset(stateWaitForTimeout, ShapeBaseImageData), MaxStates); + addField("stateFire", TypeBool, Offset(stateFire, ShapeBaseImageData), MaxStates); + addField("stateEjectShell", TypeBool, Offset(stateEjectShell, ShapeBaseImageData), MaxStates); + addField("stateEnergyDrain", TypeF32, Offset(stateEnergyDrain, ShapeBaseImageData), MaxStates); + addField("stateAllowImageChange", TypeBool, Offset(stateAllowImageChange, ShapeBaseImageData), MaxStates); + addField("stateDirection", TypeBool, Offset(stateDirection, ShapeBaseImageData), MaxStates); + addField("stateLoadedFlag", TypeEnum, Offset(stateLoaded, ShapeBaseImageData), MaxStates, &EnumLoadedState); + addField("stateSpinThread", TypeEnum, Offset(stateSpin, ShapeBaseImageData), MaxStates, &EnumSpinState); + addField("stateRecoil", TypeEnum, Offset(stateRecoil, ShapeBaseImageData), MaxStates, &EnumRecoilState); + addField("stateSequence", TypeString, Offset(stateSequence, ShapeBaseImageData), MaxStates); + addField("stateSequenceRandomFlash", TypeBool, Offset(stateSequenceRandomFlash, ShapeBaseImageData), MaxStates); + addField("stateScaleAnimation", TypeBool, Offset(stateScaleAnimation, ShapeBaseImageData), MaxStates); + addField("stateSound", TypeSFXProfilePtr, Offset(stateSound, ShapeBaseImageData), MaxStates); + addField("stateScript", TypeCaseString, Offset(stateScript, ShapeBaseImageData), MaxStates); + addField("stateEmitter", TypeParticleEmitterDataPtr, Offset(stateEmitter, ShapeBaseImageData), MaxStates); + addField("stateEmitterTime", TypeF32, Offset(stateEmitterTime, ShapeBaseImageData), MaxStates); + addField("stateEmitterNode", TypeString, Offset(stateEmitterNode, ShapeBaseImageData), MaxStates); + addField("stateIgnoreLoadedForReady", TypeBool, Offset(stateIgnoreLoadedForReady, ShapeBaseImageData), MaxStates); + addField("computeCRC", TypeBool, Offset(computeCRC, ShapeBaseImageData)); + + addField("maxConcurrentSounds", TypeS32, Offset(maxConcurrentSounds, ShapeBaseImageData)); + + Parent::initPersistFields(); +} + +void ShapeBaseImageData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if(stream->writeFlag(computeCRC)) + stream->write(mCRC); + + stream->writeString(shapeName); + stream->write(mountPoint); + if (!stream->writeFlag(mountOffset.isIdentity())) + stream->writeAffineTransform(mountOffset); + if (!stream->writeFlag(eyeOffset.isIdentity())) + stream->writeAffineTransform(eyeOffset); + + stream->writeFlag(correctMuzzleVector); + stream->writeFlag(firstPerson); + stream->write(mass); + stream->writeFlag(usesEnergy); + stream->write(minEnergy); + stream->writeFlag(hasFlash); + // Client doesn't need accuFire + + // Write the projectile datablock + if (stream->writeFlag(projectile)) + stream->writeRangedU32(packed? SimObjectId(projectile): + projectile->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + + stream->writeFlag(cloakable); + stream->writeRangedU32(lightType, 0, NumLightTypes-1); + if(lightType != NoLight) + { + stream->write(lightRadius); + stream->write(lightDuration); + stream->writeFloat(lightColor.red, 7); + stream->writeFloat(lightColor.green, 7); + stream->writeFloat(lightColor.blue, 7); + stream->writeFloat(lightColor.alpha, 7); + } + + mathWrite( *stream, shellExitDir ); + stream->write(shellExitVariance); + stream->write(shellVelocity); + + if( stream->writeFlag( casing ) ) + { + stream->writeRangedU32(packed? SimObjectId(casing): + casing->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + + for (U32 i = 0; i < MaxStates; i++) + if (stream->writeFlag(state[i].name && state[i].name[0])) { + StateData& s = state[i]; + // States info not needed on the client: + // s.allowImageChange + // s.scriptNames + // Transitions are inc. one to account for -1 values + stream->writeString(state[i].name); + + stream->writeInt(s.transition.loaded[0]+1,NumStateBits); + stream->writeInt(s.transition.loaded[1]+1,NumStateBits); + stream->writeInt(s.transition.ammo[0]+1,NumStateBits); + stream->writeInt(s.transition.ammo[1]+1,NumStateBits); + stream->writeInt(s.transition.target[0]+1,NumStateBits); + stream->writeInt(s.transition.target[1]+1,NumStateBits); + stream->writeInt(s.transition.wet[0]+1,NumStateBits); + stream->writeInt(s.transition.wet[1]+1,NumStateBits); + stream->writeInt(s.transition.trigger[0]+1,NumStateBits); + stream->writeInt(s.transition.trigger[1]+1,NumStateBits); + stream->writeInt(s.transition.altTrigger[0]+1,NumStateBits); + stream->writeInt(s.transition.altTrigger[1]+1,NumStateBits); + stream->writeInt(s.transition.timeout+1,NumStateBits); + + if(stream->writeFlag(s.timeoutValue != gDefaultStateData.timeoutValue)) + stream->write(s.timeoutValue); + + stream->writeFlag(s.waitForTimeout); + stream->writeFlag(s.fire); + stream->writeFlag(s.ejectShell); + stream->writeFlag(s.scaleAnimation); + stream->writeFlag(s.direction); + if(stream->writeFlag(s.energyDrain != gDefaultStateData.energyDrain)) + stream->write(s.energyDrain); + + stream->writeInt(s.loaded,StateData::NumLoadedBits); + stream->writeInt(s.spin,StateData::NumSpinBits); + stream->writeInt(s.recoil,StateData::NumRecoilBits); + if(stream->writeFlag(s.sequence != gDefaultStateData.sequence)) + stream->writeSignedInt(s.sequence, 16); + + if(stream->writeFlag(s.sequenceVis != gDefaultStateData.sequenceVis)) + stream->writeSignedInt(s.sequenceVis,16); + stream->writeFlag(s.flashSequence); + stream->writeFlag(s.ignoreLoadedForReady); + + if (stream->writeFlag(s.emitter)) { + stream->writeRangedU32(packed? SimObjectId(s.emitter): + s.emitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + stream->write(s.emitterTime); + stream->write(s.emitterNode); + } + + if (stream->writeFlag(s.sound)) + stream->writeRangedU32(packed? SimObjectId(s.sound): + s.sound->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + stream->write(maxConcurrentSounds); +} + +void ShapeBaseImageData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + computeCRC = stream->readFlag(); + if(computeCRC) + stream->read(&mCRC); + + shapeName = stream->readSTString(); + stream->read(&mountPoint); + if (stream->readFlag()) + mountOffset.identity(); + else + stream->readAffineTransform(&mountOffset); + if (stream->readFlag()) + eyeOffset.identity(); + else + stream->readAffineTransform(&eyeOffset); + + correctMuzzleVector = stream->readFlag(); + firstPerson = stream->readFlag(); + stream->read(&mass); + usesEnergy = stream->readFlag(); + stream->read(&minEnergy); + hasFlash = stream->readFlag(); + + projectile = (stream->readFlag() ? + (ProjectileData*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast) : 0); + + cloakable = stream->readFlag(); + lightType = stream->readRangedU32(0, NumLightTypes-1); + if(lightType != NoLight) + { + stream->read(&lightRadius); + stream->read(&lightDuration); + lightColor.red = stream->readFloat(7); + lightColor.green = stream->readFloat(7); + lightColor.blue = stream->readFloat(7); + lightColor.alpha = stream->readFloat(7); + } + + mathRead( *stream, &shellExitDir ); + stream->read(&shellExitVariance); + stream->read(&shellVelocity); + + if(stream->readFlag()) + { + casingID = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + for (U32 i = 0; i < MaxStates; i++) { + if (stream->readFlag()) { + StateData& s = state[i]; + // States info not needed on the client: + // s.allowImageChange + // s.scriptNames + // Transitions are dec. one to restore -1 values + s.name = stream->readSTString(); + + s.transition.loaded[0] = stream->readInt(NumStateBits) - 1; + s.transition.loaded[1] = stream->readInt(NumStateBits) - 1; + s.transition.ammo[0] = stream->readInt(NumStateBits) - 1; + s.transition.ammo[1] = stream->readInt(NumStateBits) - 1; + s.transition.target[0] = stream->readInt(NumStateBits) - 1; + s.transition.target[1] = stream->readInt(NumStateBits) - 1; + s.transition.wet[0] = stream->readInt(NumStateBits) - 1; + s.transition.wet[1] = stream->readInt(NumStateBits) - 1; + s.transition.trigger[0] = stream->readInt(NumStateBits) - 1; + s.transition.trigger[1] = stream->readInt(NumStateBits) - 1; + s.transition.altTrigger[0] = stream->readInt(NumStateBits) - 1; + s.transition.altTrigger[1] = stream->readInt(NumStateBits) - 1; + s.transition.timeout = stream->readInt(NumStateBits) - 1; + if(stream->readFlag()) + stream->read(&s.timeoutValue); + else + s.timeoutValue = gDefaultStateData.timeoutValue; + + s.waitForTimeout = stream->readFlag(); + s.fire = stream->readFlag(); + s.ejectShell = stream->readFlag(); + s.scaleAnimation = stream->readFlag(); + s.direction = stream->readFlag(); + if(stream->readFlag()) + stream->read(&s.energyDrain); + else + s.energyDrain = gDefaultStateData.energyDrain; + + s.loaded = (StateData::LoadedState)stream->readInt(StateData::NumLoadedBits); + s.spin = (StateData::SpinState)stream->readInt(StateData::NumSpinBits); + s.recoil = (StateData::RecoilState)stream->readInt(StateData::NumRecoilBits); + if(stream->readFlag()) + s.sequence = stream->readSignedInt(16); + else + s.sequence = gDefaultStateData.sequence; + + if(stream->readFlag()) + s.sequenceVis = stream->readSignedInt(16); + else + s.sequenceVis = gDefaultStateData.sequenceVis; + + s.flashSequence = stream->readFlag(); + s.ignoreLoadedForReady = stream->readFlag(); + + if (stream->readFlag()) { + s.emitter = (ParticleEmitterData*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + stream->read(&s.emitterTime); + stream->read(&s.emitterNode); + } + else + s.emitter = 0; + s.sound = stream->readFlag()? (SFXProfile*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + } + } + + stream->read(&maxConcurrentSounds); + + statesLoaded = true; +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +ShapeBase::MountedImage::MountedImage() +{ + shapeInstance = 0; + state = 0; + dataBlock = 0; + nextImage = InvalidImagePtr; + delayTime = 0; + ammo = false; + target = false; + triggerDown = false; + altTriggerDown = false; + loaded = false; + fireCount = 0; + wet = false; + lightStart = 0; + lightInfo = NULL; + + nextLoaded = false; + ambientThread = NULL; + visThread = NULL; + animThread = NULL; + flashThread = NULL; + spinThread = NULL; +} + +ShapeBase::MountedImage::~MountedImage() +{ + delete shapeInstance; + + // stop sound + for(Vector::iterator i = mSoundSources.begin(); i != mSoundSources.end(); i++) + { + SFX_DELETE((*i)); + } + mSoundSources.clear(); + + for (S32 i = 0; i < MaxImageEmitters; i++) + if (bool(emitter[i].emitter)) + emitter[i].emitter->deleteWhenEmpty(); + + if ( lightInfo != NULL ) + delete lightInfo; +} + +void ShapeBase::MountedImage::addSoundSource(SFXSource* source) +{ + if(source != NULL) + { + if(dataBlock->maxConcurrentSounds > 0 && mSoundSources.size() > dataBlock->maxConcurrentSounds) + { + SFX_DELETE(mSoundSources.first()); + mSoundSources.pop_front(); + } + source->play(); + mSoundSources.push_back(source); + } +} + +void ShapeBase::MountedImage::updateSoundSources( const MatrixF& renderTransform ) +{ + //iterate through sources. if any of them have stopped playing, delete them. + //otherwise, update the transform + for (Vector::iterator i = mSoundSources.begin(); i != mSoundSources.end(); i++) + { + if((*i)->isStopped()) + { + SFX_DELETE((*i)); + mSoundSources.erase(i); + i--; + } + else + { + (*i)->setTransform(renderTransform); + } + } +} + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +// Any item with an item image is selectable + +bool ShapeBase::mountImage(ShapeBaseImageData* imageData,U32 imageSlot,bool loaded,NetStringHandle &skinNameHandle) +{ + AssertFatal(imageSlotfire; +} + +bool ShapeBase::isImageReady(U32 imageSlot,U32 ns,U32 depth) +{ + // Will pressing the trigger lead to a fire state? + MountedImage& image = mMountedImageList[imageSlot]; + if (depth++ > 5 || !image.dataBlock) + return false; + ShapeBaseImageData::StateData& stateData = (ns == -1) ? + *image.state : image.dataBlock->state[ns]; + if (stateData.fire) + return true; + + // Try the transitions... + if (stateData.ignoreLoadedForReady == true) { + if ((ns = stateData.transition.loaded[true]) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + } else { + if ((ns = stateData.transition.loaded[image.loaded]) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + } + if ((ns = stateData.transition.ammo[image.ammo]) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + if ((ns = stateData.transition.target[image.target]) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + if ((ns = stateData.transition.wet[image.wet]) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + if ((ns = stateData.transition.trigger[1]) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + if ((ns = stateData.transition.altTrigger[1]) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + if ((ns = stateData.transition.timeout) != -1) + if (isImageReady(imageSlot,ns,depth)) + return true; + return false; +} + +bool ShapeBase::isImageMounted(ShapeBaseImageData* imageData) +{ + for (U32 i = 0; i < MaxMountedImages; i++) + if (imageData == mMountedImageList[i].dataBlock) + return true; + return false; +} + +S32 ShapeBase::getMountSlot(ShapeBaseImageData* imageData) +{ + for (U32 i = 0; i < MaxMountedImages; i++) + if (imageData == mMountedImageList[i].dataBlock) + return i; + return -1; +} + +NetStringHandle ShapeBase::getImageSkinTag(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + return image.dataBlock? image.skinNameHandle : NetStringHandle(); +} + +const char* ShapeBase::getImageState(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + return image.dataBlock? image.state->name: 0; +} + +void ShapeBase::setImageAmmoState(U32 imageSlot,bool ammo) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock && !image.dataBlock->usesEnergy && image.ammo != ammo) { + setMaskBits(ImageMaskN << imageSlot); + image.ammo = ammo; + } +} + +bool ShapeBase::getImageAmmoState(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (!image.dataBlock) + return false; + return image.ammo; +} + +void ShapeBase::setImageWetState(U32 imageSlot,bool wet) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock && image.wet != wet) { + setMaskBits(ImageMaskN << imageSlot); + image.wet = wet; + } +} + +bool ShapeBase::getImageWetState(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (!image.dataBlock) + return false; + return image.wet; +} + +void ShapeBase::setImageLoadedState(U32 imageSlot,bool loaded) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock && image.loaded != loaded) { + setMaskBits(ImageMaskN << imageSlot); + image.loaded = loaded; + } +} + +bool ShapeBase::getImageLoadedState(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (!image.dataBlock) + return false; + return image.loaded; +} + +void ShapeBase::getMuzzleVector(U32 imageSlot,VectorF* vec) +{ + MatrixF mat; + getMuzzleTransform(imageSlot,&mat); + + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock->correctMuzzleVector) + if (GameConnection * gc = getControllingClient()) + if (gc->isFirstPerson() && !gc->isAIControlled()) + if (getCorrectedAim(mat, vec)) + return; + + mat.getColumn(1,vec); +} + +void ShapeBase::getMuzzlePoint(U32 imageSlot,Point3F* pos) +{ + MatrixF mat; + getMuzzleTransform(imageSlot,&mat); + mat.getColumn(3,pos); +} + + +void ShapeBase::getRenderMuzzleVector(U32 imageSlot,VectorF* vec) +{ + MatrixF mat; + getRenderMuzzleTransform(imageSlot,&mat); + + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock->correctMuzzleVector) + if (GameConnection * gc = getControllingClient()) + if (gc->isFirstPerson() && !gc->isAIControlled()) + if (getCorrectedAim(mat, vec)) + return; + + mat.getColumn(1,vec); +} + +void ShapeBase::getRenderMuzzlePoint(U32 imageSlot,Point3F* pos) +{ + MatrixF mat; + getRenderMuzzleTransform(imageSlot,&mat); + mat.getColumn(3,pos); +} + +//---------------------------------------------------------------------------- + +void ShapeBase::scriptCallback(U32 imageSlot,const char* function) +{ + MountedImage& image = mMountedImageList[imageSlot]; + char buff1[32]; + dSprintf(buff1,sizeof(buff1),"%d",imageSlot); + Con::executef(image.dataBlock, function,scriptThis(),buff1); +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::getMountTransform(U32 mountPoint,MatrixF* mat) +{ + // Returns mount point to world space transform + if (mountPoint < ShapeBaseData::NumMountPoints) { + S32 ni = mDataBlock->mountPointNode[mountPoint]; + if (ni != -1) { + MatrixF mountTransform = mShapeInstance->mNodeTransforms[ni]; + const Point3F& scale = getScale(); + + // The position of the mount point needs to be scaled. + Point3F position = mountTransform.getPosition(); + position.convolve( scale ); + mountTransform.setPosition( position ); + + // Also we would like the object to be scaled to the model. + mat->mul(mObjToWorld, mountTransform); + return; + } + } + *mat = mObjToWorld; +} + +void ShapeBase::getImageTransform(U32 imageSlot,MatrixF* mat) +{ + // Image transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + ShapeBaseImageData& data = *image.dataBlock; + + MatrixF nmat; + if (data.useEyeOffset && isFirstPerson()) { + getEyeTransform(&nmat); + mat->mul(nmat,data.eyeOffset); + } + else { + getMountTransform(image.dataBlock->mountPoint,&nmat); + mat->mul(nmat,data.mountTransform); + } + } + else + *mat = mObjToWorld; +} + +void ShapeBase::getImageTransform(U32 imageSlot,S32 node,MatrixF* mat) +{ + // Muzzle transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + if (node != -1) { + MatrixF imat; + getImageTransform(imageSlot,&imat); + mat->mul(imat,image.shapeInstance->mNodeTransforms[node]); + } + else + getImageTransform(imageSlot,mat); + } + else + *mat = mObjToWorld; +} + +void ShapeBase::getImageTransform(U32 imageSlot,StringTableEntry nodeName,MatrixF* mat) +{ + getImageTransform( imageSlot, getNodeIndex( imageSlot, nodeName ), mat ); +} + +void ShapeBase::getMuzzleTransform(U32 imageSlot,MatrixF* mat) +{ + // Muzzle transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) + getImageTransform(imageSlot,image.dataBlock->muzzleNode,mat); + else + *mat = mObjToWorld; +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::getRenderMountTransform(U32 mountPoint,MatrixF* mat) +{ + // Returns mount point to world space transform + if (mountPoint < ShapeBaseData::NumMountPoints) { + S32 ni = mDataBlock->mountPointNode[mountPoint]; + if (ni != -1) { + MatrixF mountTransform = mShapeInstance->mNodeTransforms[ni]; + const Point3F& scale = getScale(); + + // The position of the mount point needs to be scaled. + Point3F position = mountTransform.getPosition(); + position.convolve( scale ); + mountTransform.setPosition( position ); + + // Also we would like the object to be scaled to the model. + mountTransform.scale( scale ); + mat->mul(getRenderTransform(), mountTransform); + return; + } + } + *mat = getRenderTransform(); +} + + +void ShapeBase::getRenderImageTransform( U32 imageSlot, MatrixF* mat, bool noEyeOffset ) +{ + // Image transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) + { + ShapeBaseImageData& data = *image.dataBlock; + + MatrixF nmat; + if ( !noEyeOffset && data.useEyeOffset && isFirstPerson() ) + { + getRenderEyeTransform(&nmat); + mat->mul(nmat,data.eyeOffset); + } + else + { + getRenderMountTransform(data.mountPoint,&nmat); + mat->mul(nmat,data.mountTransform); + } + } + else + *mat = getRenderTransform(); +} + +void ShapeBase::getRenderImageTransform(U32 imageSlot,S32 node,MatrixF* mat) +{ + // Muzzle transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + if (node != -1) { + MatrixF imat; + getRenderImageTransform(imageSlot,&imat); + mat->mul(imat,image.shapeInstance->mNodeTransforms[node]); + } + else + getRenderImageTransform(imageSlot,mat); + } + else + *mat = getRenderTransform(); +} + +void ShapeBase::getRenderImageTransform(U32 imageSlot,StringTableEntry nodeName,MatrixF* mat) +{ + getRenderImageTransform( imageSlot, getNodeIndex( imageSlot, nodeName ), mat ); +} + +void ShapeBase::getRenderMuzzleTransform(U32 imageSlot,MatrixF* mat) +{ + // Muzzle transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) + getRenderImageTransform(imageSlot,image.dataBlock->muzzleNode,mat); + else + *mat = getRenderTransform(); +} + + +void ShapeBase::getRetractionTransform(U32 imageSlot,MatrixF* mat) +{ + // Muzzle transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + if (image.dataBlock->retractNode != -1) + getImageTransform(imageSlot,image.dataBlock->retractNode,mat); + else + getImageTransform(imageSlot,image.dataBlock->muzzleNode,mat); + } else { + *mat = getTransform(); + } +} + + +void ShapeBase::getRenderRetractionTransform(U32 imageSlot,MatrixF* mat) +{ + // Muzzle transform in world space + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) { + if (image.dataBlock->retractNode != -1) + getRenderImageTransform(imageSlot,image.dataBlock->retractNode,mat); + else + getRenderImageTransform(imageSlot,image.dataBlock->muzzleNode,mat); + } else { + *mat = getRenderTransform(); + } +} + + +//---------------------------------------------------------------------------- + +S32 ShapeBase::getNodeIndex(U32 imageSlot,StringTableEntry nodeName) +{ + MountedImage& image = mMountedImageList[imageSlot]; + if (image.dataBlock) + return image.dataBlock->shape->findNode(nodeName); + else + return -1; +} + +// Modify muzzle if needed to aim at whatever is straight in front of eye. Let the +// caller know if we actually modified the result. +bool ShapeBase::getCorrectedAim(const MatrixF& muzzleMat, VectorF* result) +{ + const F32 pullInD = 6.0; + const F32 maxAdjD = 500; + + VectorF aheadVec(0, maxAdjD, 0); + + MatrixF eyeMat; + Point3F eyePos; + getEyeTransform(&eyeMat); + eyeMat.getColumn(3, &eyePos); + eyeMat.mulV(aheadVec); + Point3F aheadPoint = (eyePos + aheadVec); + + // Should we check if muzzle point is really close to eye? Does that happen? + Point3F muzzlePos; + muzzleMat.getColumn(3, &muzzlePos); + + Point3F collidePoint; + VectorF collideVector; + disableCollision(); + RayInfo rinfo; + if (getContainer()->castRay(eyePos, aheadPoint, STATIC_COLLISION_MASK|DAMAGEABLE_MASK, &rinfo)) + collideVector = ((collidePoint = rinfo.point) - eyePos); + else + collideVector = ((collidePoint = aheadPoint) - eyePos); + enableCollision(); + + // For close collision we want to NOT aim at ground since we're bending + // the ray here as it is. But we don't want to pop, so adjust continuously. + F32 lenSq = collideVector.lenSquared(); + if (lenSq < (pullInD * pullInD) && lenSq > 0.04) + { + F32 len = mSqrt(lenSq); + F32 mid = pullInD; // (pullInD + len) / 2.0; + // This gives us point beyond to focus on- + collideVector *= (mid / len); + collidePoint = (eyePos + collideVector); + } + + VectorF muzzleToCollide = (collidePoint - muzzlePos); + lenSq = muzzleToCollide.lenSquared(); + if (lenSq > 0.04) + { + muzzleToCollide *= (1 / mSqrt(lenSq)); + * result = muzzleToCollide; + return true; + } + return false; +} + +//---------------------------------------------------------------------------- + +void ShapeBase::updateMass() +{ + if (mDataBlock) { + F32 imass = 0; + for (U32 i = 0; i < MaxMountedImages; i++) { + MountedImage& image = mMountedImageList[i]; + if (image.dataBlock) + imass += image.dataBlock->mass; + } + // + mMass = mDataBlock->mass + imass; + mOneOverMass = 1 / mMass; + } +} + +void ShapeBase::onImageRecoil(U32,ShapeBaseImageData::StateData::RecoilState) +{ +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::setImage( U32 imageSlot, + ShapeBaseImageData* imageData, + NetStringHandle& skinNameHandle, + bool loaded, + bool ammo, + bool triggerDown, + bool altTriggerDown, + bool target) +{ + AssertFatal(imageSlotreSkin(newSkin, image.appliedSkinName); + image.appliedSkinName = newSkin; + } + } + } + return; + } + + // Check to see if we need to delay image changes until state change. + if (!isGhost()) { + if (imageData && image.dataBlock && !image.state->allowImageChange) { + image.nextImage = imageData; + image.nextSkinNameHandle = skinNameHandle; + image.nextLoaded = loaded; + return; + } + } + + // Mark that updates are happenin'. + setMaskBits(ImageMaskN << imageSlot); + + // Notify script unmount since we're swapping datablocks. + if (image.dataBlock && !isGhost()) { + scriptCallback(imageSlot,"onUnmount"); + } + + // Stop anything currently going on with the image. + resetImageSlot(imageSlot); + + // If we're just unselecting the current shape without swapping + // in a new one, then bail. + if (!imageData) { + return; + } + + // Otherwise, init the new shape. + image.dataBlock = imageData; + image.state = &image.dataBlock->state[0]; + image.skinNameHandle = skinNameHandle; + image.shapeInstance = new TSShapeInstance(image.dataBlock->shape, isClientObject()); + if (isClientObject()) { + if (image.shapeInstance) { + image.shapeInstance->cloneMaterialList(); + String newSkin = skinNameHandle.getString(); + image.shapeInstance->reSkin(newSkin, image.appliedSkinName); + image.appliedSkinName = newSkin; + } + } + image.loaded = loaded; + image.ammo = ammo; + image.triggerDown = triggerDown; + image.altTriggerDown = altTriggerDown; + image.target = target; + + // The server needs the shape loaded for muzzle mount nodes + // but it doesn't need to run any of the animations. + image.ambientThread = 0; + image.animThread = 0; + image.flashThread = 0; + image.spinThread = 0; + if (isGhost()) { + if (image.dataBlock->isAnimated) { + image.animThread = image.shapeInstance->addThread(); + image.shapeInstance->setTimeScale(image.animThread,0); + } + if (image.dataBlock->hasFlash) { + image.flashThread = image.shapeInstance->addThread(); + image.shapeInstance->setTimeScale(image.flashThread,0); + } + if (image.dataBlock->ambientSequence != -1) { + image.ambientThread = image.shapeInstance->addThread(); + image.shapeInstance->setTimeScale(image.ambientThread,1); + image.shapeInstance->setSequence(image.ambientThread, + image.dataBlock->ambientSequence,0); + } + if (image.dataBlock->spinSequence != -1) { + image.spinThread = image.shapeInstance->addThread(); + image.shapeInstance->setTimeScale(image.spinThread,1); + image.shapeInstance->setSequence(image.spinThread, + image.dataBlock->spinSequence,0); + } + } + + // Set the image to its starting state. + setImageState(imageSlot, 0, true); + + // Update the mass for the mount object. + updateMass(); + + // Notify script mount. + if ( !isGhost() ) + scriptCallback(imageSlot,"onMount"); + else + { + if ( imageData->lightType == ShapeBaseImageData::PulsingLight ) + image.lightStart = Sim::getCurrentTime(); + } + + // Done. +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::resetImageSlot(U32 imageSlot) +{ + AssertFatal(imageSlot::iterator i = image.mSoundSources.begin(); i != image.mSoundSources.end(); i++) + { + SFX_DELETE((*i)); + } + image.mSoundSources.clear(); + + for (S32 i = 0; i < MaxImageEmitters; i++) { + MountedImage::ImageEmitter& em = image.emitter[i]; + if (bool(em.emitter)) { + em.emitter->deleteWhenEmpty(); + em.emitter = 0; + } + } + + image.dataBlock = 0; + image.nextImage = InvalidImagePtr; + image.skinNameHandle = NetStringHandle(); + image.nextSkinNameHandle = NetStringHandle(); + image.state = 0; + image.delayTime = 0; + image.ammo = false; + image.triggerDown = false; + image.altTriggerDown = false; + image.loaded = false; + image.lightStart = 0; + if ( image.lightInfo != NULL ) + SAFE_DELETE( image.lightInfo ); + + updateMass(); +} + + +//---------------------------------------------------------------------------- + +bool ShapeBase::getImageTriggerState(U32 imageSlot) +{ + if (isGhost() || !mMountedImageList[imageSlot].dataBlock) + return false; + return mMountedImageList[imageSlot].triggerDown; +} + +void ShapeBase::setImageTriggerState(U32 imageSlot,bool trigger) +{ + if (isGhost() || !mMountedImageList[imageSlot].dataBlock) + return; + MountedImage& image = mMountedImageList[imageSlot]; + + if (trigger) { + if (!image.triggerDown && image.dataBlock) { + image.triggerDown = true; + if (!isGhost()) { + setMaskBits(ImageMaskN << imageSlot); + updateImageState(imageSlot,0); + } + } + } + else + if (image.triggerDown) { + image.triggerDown = false; + if (!isGhost()) { + setMaskBits(ImageMaskN << imageSlot); + updateImageState(imageSlot,0); + } + } +} + +bool ShapeBase::getImageAltTriggerState(U32 imageSlot) +{ + if (isGhost() || !mMountedImageList[imageSlot].dataBlock) + return false; + return mMountedImageList[imageSlot].altTriggerDown; +} + +void ShapeBase::setImageAltTriggerState(U32 imageSlot,bool trigger) +{ + if (isGhost() || !mMountedImageList[imageSlot].dataBlock) + return; + MountedImage& image = mMountedImageList[imageSlot]; + + if (trigger) { + if (!image.altTriggerDown && image.dataBlock) { + image.altTriggerDown = true; + if (!isGhost()) { + setMaskBits(ImageMaskN << imageSlot); + updateImageState(imageSlot,0); + } + } + } + else + if (image.altTriggerDown) { + image.altTriggerDown = false; + if (!isGhost()) { + setMaskBits(ImageMaskN << imageSlot); + updateImageState(imageSlot,0); + } + } +} + +//---------------------------------------------------------------------------- + +U32 ShapeBase::getImageFireState(U32 imageSlot) +{ + MountedImage& image = mMountedImageList[imageSlot]; + // If there is no fire state, then try state 0 + if (image.dataBlock && image.dataBlock->fireState != -1) + return image.dataBlock->fireState; + return 0; +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::setImageState(U32 imageSlot, U32 newState,bool force) +{ + if (!mMountedImageList[imageSlot].dataBlock) + return; + MountedImage& image = mMountedImageList[imageSlot]; + + + // The client never enters the initial fire state on its own, but it + // will continue to set that state... + if (isGhost() && !force && newState == image.dataBlock->fireState) { + if (image.state != &image.dataBlock->state[newState]) + return; + } + + // Eject shell casing on every state change + ShapeBaseImageData::StateData& nextStateData = image.dataBlock->state[newState]; + if (isGhost() && nextStateData.ejectShell) { + ejectShellCasing( imageSlot ); + } + + + // Server must animate the shape if it is a firestate... + if (newState == image.dataBlock->fireState && isServerObject()) + mShapeInstance->animate(); + + // If going back into the same state, just reset the timer + // and invoke the script callback + if (!force && image.state == &image.dataBlock->state[newState]) { + image.delayTime = image.state->timeoutValue; + if (image.state->script && !isGhost()) + scriptCallback(imageSlot,image.state->script); + + // If this is a flash sequence, we need to select a new position for the + // animation if we're returning to that state... + if (image.animThread && image.state->sequence != -1 && image.state->flashSequence) { + F32 randomPos = Platform::getRandom(); + image.shapeInstance->setPos(image.animThread, randomPos); + image.shapeInstance->setTimeScale(image.animThread, 0); + if (image.flashThread) + image.shapeInstance->setPos(image.flashThread, 0); + } + + return; + } + + F32 lastDelay = image.delayTime; + ShapeBaseImageData::StateData& lastState = *image.state; + image.state = &image.dataBlock->state[newState]; + + // + // Do state cleanup first... + // + ShapeBaseImageData& imageData = *image.dataBlock; + ShapeBaseImageData::StateData& stateData = *image.state; + + // Mount pending images + if (image.nextImage != InvalidImagePtr && stateData.allowImageChange) { + setImage(imageSlot,image.nextImage,image.nextSkinNameHandle,image.nextLoaded); + return; + } + + // Reset cyclic sequences back to the first frame to turn it off + // (the first key frame should be it's off state). + if (image.animThread && image.animThread->getSequence()->isCyclic()) { + image.shapeInstance->setPos(image.animThread,0); + image.shapeInstance->setTimeScale(image.animThread,0); + } + if (image.flashThread) { + image.shapeInstance->setPos(image.flashThread,0); + image.shapeInstance->setTimeScale(image.flashThread,0); + } + + // Check for immediate transitions + S32 ns; + if ((ns = stateData.transition.loaded[image.loaded]) != -1) { + setImageState(imageSlot,ns); + return; + } + //if (!imageData.usesEnergy) + if ((ns = stateData.transition.ammo[image.ammo]) != -1) { + setImageState(imageSlot,ns); + return; + } + if ((ns = stateData.transition.target[image.target]) != -1) { + setImageState(imageSlot, ns); + return; + } + if ((ns = stateData.transition.wet[image.wet]) != -1) { + setImageState(imageSlot, ns); + return; + } + if ((ns = stateData.transition.trigger[image.triggerDown]) != -1) { + setImageState(imageSlot,ns); + return; + } + if ((ns = stateData.transition.altTrigger[image.altTriggerDown]) != -1) { + setImageState(imageSlot,ns); + return; + } + + // + // Initialize the new state... + // + image.delayTime = stateData.timeoutValue; + if (stateData.loaded != ShapeBaseImageData::StateData::IgnoreLoaded) + image.loaded = stateData.loaded == ShapeBaseImageData::StateData::Loaded; + if (!isGhost() && newState == imageData.fireState) { + setMaskBits(ImageMaskN << imageSlot); + image.fireCount = (image.fireCount + 1) & 0x7; + } + + // Apply recoil + if (stateData.recoil != ShapeBaseImageData::StateData::NoRecoil) + onImageRecoil(imageSlot,stateData.recoil); + + // Play sound + if( stateData.sound && isGhost() ) + { + const Point3F& velocity = getVelocity(); + image.addSoundSource(SFX->createSource( stateData.sound, &getRenderTransform(), &velocity )); + } + + // Play animation + if (image.animThread && stateData.sequence != -1) + { + image.shapeInstance->setSequence(image.animThread,stateData.sequence, stateData.direction ? 0.0f : 1.0f); + if (stateData.flashSequence == false) + { + F32 timeScale = (stateData.scaleAnimation && stateData.timeoutValue) ? + image.shapeInstance->getDuration(image.animThread) / stateData.timeoutValue : 1.0f; + image.shapeInstance->setTimeScale(image.animThread, stateData.direction ? timeScale : -timeScale); + } + else + { + F32 randomPos = Platform::getRandom(); + image.shapeInstance->setPos(image.animThread, randomPos); + image.shapeInstance->setTimeScale(image.animThread, 0); + + image.shapeInstance->setSequence(image.flashThread, stateData.sequenceVis, 0); + image.shapeInstance->setPos(image.flashThread, 0); + F32 timeScale = (stateData.scaleAnimation && stateData.timeoutValue) ? + image.shapeInstance->getDuration(image.animThread) / stateData.timeoutValue : 1.0f; + image.shapeInstance->setTimeScale(image.flashThread, timeScale); + } + } + + // Start particle emitter on the client + if (isGhost() && stateData.emitter) + startImageEmitter(image,stateData); + + // Start spin thread + if (image.spinThread) { + switch (stateData.spin) { + case ShapeBaseImageData::StateData::IgnoreSpin: + image.shapeInstance->setTimeScale(image.spinThread, image.shapeInstance->getTimeScale(image.spinThread)); + break; + case ShapeBaseImageData::StateData::NoSpin: + image.shapeInstance->setTimeScale(image.spinThread,0); + break; + case ShapeBaseImageData::StateData::SpinUp: + if (lastState.spin == ShapeBaseImageData::StateData::SpinDown) + image.delayTime *= 1.0f - (lastDelay / stateData.timeoutValue); + break; + case ShapeBaseImageData::StateData::SpinDown: + if (lastState.spin == ShapeBaseImageData::StateData::SpinUp) + image.delayTime *= 1.0f - (lastDelay / stateData.timeoutValue); + break; + case ShapeBaseImageData::StateData::FullSpin: + image.shapeInstance->setTimeScale(image.spinThread,1); + break; + } + } + + // Script callback on server + if (stateData.script && stateData.script[0] && !isGhost()) + scriptCallback(imageSlot,stateData.script); + + // If there is a zero timeout, and a timeout transition, then + // go ahead and transition imediately. + if (!image.delayTime) + { + if ((ns = stateData.transition.timeout) != -1) + { + setImageState(imageSlot,ns); + return; + } + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::updateImageState(U32 imageSlot,F32 dt) +{ + if (!mMountedImageList[imageSlot].dataBlock) + return; + MountedImage& image = mMountedImageList[imageSlot]; + ShapeBaseImageData& imageData = *image.dataBlock; + ShapeBaseImageData::StateData& stateData = *image.state; + image.delayTime -= dt; + + // Energy management + if (imageData.usesEnergy) { + F32 newEnergy = getEnergyLevel() - stateData.energyDrain * dt; + if (newEnergy < 0) + newEnergy = 0; + setEnergyLevel(newEnergy); + + if (!isGhost()) { + bool ammo = newEnergy > imageData.minEnergy; + if (ammo != image.ammo) { + setMaskBits(ImageMaskN << imageSlot); + image.ammo = ammo; + } + } + } + + // Check for transitions. On some states we must wait for the + // full timeout value before moving on. + S32 ns; + if (image.delayTime <= 0 || !stateData.waitForTimeout) { + if ((ns = stateData.transition.loaded[image.loaded]) != -1) { + setImageState(imageSlot,ns); + return; + } + if ((ns = stateData.transition.ammo[image.ammo]) != -1) { + setImageState(imageSlot,ns); + return; + } + if ((ns = stateData.transition.target[image.target]) != -1) { + setImageState(imageSlot,ns); + return; + } + if ((ns = stateData.transition.wet[image.wet]) != -1) { + setImageState(imageSlot,ns); + return; + } + if ((ns = stateData.transition.trigger[image.triggerDown]) != -1) { + setImageState(imageSlot,ns); + return; + } + if ((ns = stateData.transition.altTrigger[image.altTriggerDown]) != -1) { + setImageState(imageSlot,ns); + return; + } + if (image.delayTime <= 0 && + (ns = stateData.transition.timeout) != -1) { + setImageState(imageSlot,ns); + return; + } + } + + // Update the spinning thread timeScale + if (image.spinThread) { + float timeScale; + + switch (stateData.spin) { + case ShapeBaseImageData::StateData::IgnoreSpin: + case ShapeBaseImageData::StateData::NoSpin: + case ShapeBaseImageData::StateData::FullSpin: { + timeScale = 0; + image.shapeInstance->setTimeScale(image.spinThread, image.shapeInstance->getTimeScale(image.spinThread)); + break; + } + + case ShapeBaseImageData::StateData::SpinUp: { + timeScale = 1.0f - image.delayTime / stateData.timeoutValue; + image.shapeInstance->setTimeScale(image.spinThread,timeScale); + break; + } + + case ShapeBaseImageData::StateData::SpinDown: { + timeScale = image.delayTime / stateData.timeoutValue; + image.shapeInstance->setTimeScale(image.spinThread,timeScale); + break; + } + } + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::updateImageAnimation(U32 imageSlot, F32 dt) +{ + if (!mMountedImageList[imageSlot].dataBlock) + return; + MountedImage& image = mMountedImageList[imageSlot]; + + // Advance animation threads + if (image.ambientThread) + image.shapeInstance->advanceTime(dt,image.ambientThread); + if (image.animThread) + image.shapeInstance->advanceTime(dt,image.animThread); + if (image.spinThread) + image.shapeInstance->advanceTime(dt,image.spinThread); + if (image.flashThread) + image.shapeInstance->advanceTime(dt,image.flashThread); + + image.updateSoundSources(getRenderTransform()); + + // Particle emission + for (S32 i = 0; i < MaxImageEmitters; i++) { + MountedImage::ImageEmitter& em = image.emitter[i]; + if (bool(em.emitter)) { + if (em.time > 0) { + em.time -= dt; + + MatrixF mat; + getRenderImageTransform(imageSlot,em.node,&mat); + Point3F pos,axis; + mat.getColumn(3,&pos); + mat.getColumn(1,&axis); + em.emitter->emitParticles(pos,true,axis,getVelocity(),(U32) (dt * 1000)); + } + else { + em.emitter->deleteWhenEmpty(); + em.emitter = 0; + } + } + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::startImageEmitter(MountedImage& image,ShapeBaseImageData::StateData& state) +{ + MountedImage::ImageEmitter* bem = 0; + MountedImage::ImageEmitter* em = image.emitter; + MountedImage::ImageEmitter* ee = &image.emitter[MaxImageEmitters]; + + // If we are already emitting the same particles from the same + // node, then simply extend the time. Otherwise, find an empty + // emitter slot, or grab the one with the least amount of time left. + for (; em != ee; em++) { + if (bool(em->emitter)) { + if (state.emitter == em->emitter->getDataBlock() && state.emitterNode == em->node) { + if (state.emitterTime > em->time) + em->time = state.emitterTime; + return; + } + if (!bem || (bool(bem->emitter) && bem->time > em->time)) + bem = em; + } + else + bem = em; + } + + bem->time = state.emitterTime; + bem->node = state.emitterNode; + bem->emitter = new ParticleEmitter; + bem->emitter->onNewDataBlock(state.emitter); + if( !bem->emitter->registerObject() ) + { + delete bem->emitter.getObject(); + bem->emitter = NULL; + } +} + +void ShapeBase::submitLights( LightManager *lm, bool staticLighting ) +{ + if ( staticLighting ) + return; + + // Submit lights for MountedImage(s) + for ( S32 i = 0; i < MaxMountedImages; i++ ) + { + ShapeBaseImageData *imageData = getMountedImage( i ); + + if ( imageData != NULL && imageData->lightType != ShapeBaseImageData::NoLight ) + { + MountedImage &image = mMountedImageList[i]; + + F32 intensity; + + switch ( imageData->lightType ) + { + case ShapeBaseImageData::ConstantLight: + case ShapeBaseImageData::SpotLight: + intensity = 1.0f; + break; + + case ShapeBaseImageData::PulsingLight: + intensity = 0.5f + 0.5f * mSin( M_PI_F * (F32)Sim::getCurrentTime() / (F32)imageData->lightDuration + image.lightStart ); + intensity = 0.15f + intensity * 0.85f; + break; + + case ShapeBaseImageData::WeaponFireLight: + { + S32 elapsed = Sim::getCurrentTime() - image.lightStart; + if ( elapsed > imageData->lightDuration ) + return; + intensity = 1.0 - (F32)elapsed / (F32)imageData->lightDuration; + break; + } + default: + intensity = 1.0f; + return; + } + + if ( !image.lightInfo ) + image.lightInfo = LightManager::createLightInfo(); + + image.lightInfo->setColor( imageData->lightColor ); + image.lightInfo->setBrightness( intensity ); + image.lightInfo->setRange( imageData->lightRadius ); + + if ( imageData->lightType == ShapeBaseImageData::SpotLight ) + { + image.lightInfo->setType( LightInfo::Spot ); + // Do we want to expose these or not? + image.lightInfo->setInnerConeAngle( 15 ); + image.lightInfo->setOuterConeAngle( 40 ); + } + else + image.lightInfo->setType( LightInfo::Point ); + + MatrixF imageMat; + getRenderImageTransform( i, &imageMat ); + + image.lightInfo->setTransform( imageMat ); + + lm->registerGlobalLight( image.lightInfo, NULL ); + } + } +} + + +//---------------------------------------------------------------------------- + +void ShapeBase::ejectShellCasing( U32 imageSlot ) +{ + MountedImage& image = mMountedImageList[imageSlot]; + ShapeBaseImageData* imageData = image.dataBlock; + + if (!imageData->casing) + return; + + MatrixF ejectTrans; + getImageTransform( imageSlot, imageData->ejectNode, &ejectTrans ); + + Point3F ejectDir = imageData->shellExitDir; + ejectDir.normalize(); + + F32 ejectSpread = mDegToRad( imageData->shellExitVariance ); + MatrixF ejectOrient = MathUtils::createOrientFromDir( ejectDir ); + + Point3F randomDir; + randomDir.x = mSin( gRandGen.randF( -ejectSpread, ejectSpread ) ); + randomDir.y = 1.0; + randomDir.z = mSin( gRandGen.randF( -ejectSpread, ejectSpread ) ); + randomDir.normalizeSafe(); + + ejectOrient.mulV( randomDir ); + + MatrixF imageTrans = getTransform(); + imageTrans.mulV( randomDir ); + + Point3F shellVel = randomDir * imageData->shellVelocity; + Point3F shellPos = ejectTrans.getPosition(); + + + Debris *casing = new Debris; + casing->onNewDataBlock( imageData->casing ); + casing->setTransform( imageTrans ); + + if (!casing->registerObject()) + delete casing; + + casing->init( shellPos, shellVel ); +} diff --git a/T3D/sphere.cpp b/T3D/sphere.cpp new file mode 100644 index 0000000..abec6fb --- /dev/null +++ b/T3D/sphere.cpp @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "T3D/sphere.h" + +Sphere::Sphere(U32 baseType) +{ + VECTOR_SET_ASSOCIATION(mDetails); + + switch(baseType) + { + case Tetrahedron: + mDetails.push_back(createTetrahedron()); + break; + + case Octahedron: + mDetails.push_back(createOctahedron()); + break; + + case Icosahedron: + mDetails.push_back(createIcosahedron()); + break; + } + calcNormals(mDetails[0]); +} + +//------------------------------------------------------------------------------ + +Sphere::TriangleMesh * Sphere::createTetrahedron() +{ + const F32 sqrt3 = 0.5773502692f; + + static Point3F spherePnts[] = { + Point3F( sqrt3, sqrt3, sqrt3 ), + Point3F(-sqrt3,-sqrt3, sqrt3 ), + Point3F(-sqrt3, sqrt3,-sqrt3 ), + Point3F( sqrt3,-sqrt3,-sqrt3 ) + }; + + static Triangle tetrahedron[] = { + Triangle(spherePnts[0], spherePnts[1], spherePnts[2]), + Triangle(spherePnts[0], spherePnts[3], spherePnts[1]), + Triangle(spherePnts[2], spherePnts[1], spherePnts[3]), + Triangle(spherePnts[3], spherePnts[0], spherePnts[2]), + }; + + static TriangleMesh tetrahedronMesh = { + Tetrahedron, + &tetrahedron[0] + }; + + return(&tetrahedronMesh); +} + +//------------------------------------------------------------------------------ + +Sphere::TriangleMesh * Sphere::createOctahedron() +{ + // + static Point3F spherePnts[] = { + Point3F( 1, 0, 0), + Point3F(-1, 0, 0), + Point3F( 0, 1, 0), + Point3F( 0,-1, 0), + Point3F( 0, 0, 1), + Point3F( 0, 0,-1) + }; + + // + static Triangle octahedron[] = { + Triangle(spherePnts[0], spherePnts[4], spherePnts[2]), + Triangle(spherePnts[2], spherePnts[4], spherePnts[1]), + Triangle(spherePnts[1], spherePnts[4], spherePnts[3]), + Triangle(spherePnts[3], spherePnts[4], spherePnts[0]), + Triangle(spherePnts[0], spherePnts[2], spherePnts[5]), + Triangle(spherePnts[2], spherePnts[1], spherePnts[5]), + Triangle(spherePnts[1], spherePnts[3], spherePnts[5]), + Triangle(spherePnts[3], spherePnts[0], spherePnts[5]) + }; + + // + static TriangleMesh octahedronMesh = { + Octahedron, + &octahedron[0] + }; + + return(&octahedronMesh); +} + +Sphere::TriangleMesh * Sphere::createIcosahedron() +{ + const F32 tau = 0.8506508084f; + const F32 one = 0.5257311121f; + + static Point3F spherePnts[] = { + Point3F( tau, one, 0), + Point3F(-tau, one, 0), + Point3F(-tau,-one, 0), + Point3F( tau,-one, 0), + Point3F( one, 0, tau), + Point3F( one, 0,-tau), + Point3F(-one, 0,-tau), + Point3F(-one, 0, tau), + Point3F( 0, tau, one), + Point3F( 0,-tau, one), + Point3F( 0,-tau,-one), + Point3F( 0, tau,-one), + }; + + static Triangle icosahedron[] = { + Triangle(spherePnts[4], spherePnts[8], spherePnts[7]), + Triangle(spherePnts[4], spherePnts[7], spherePnts[9]), + Triangle(spherePnts[5], spherePnts[6], spherePnts[11]), + Triangle(spherePnts[5], spherePnts[10], spherePnts[6]), + Triangle(spherePnts[0], spherePnts[4], spherePnts[3]), + Triangle(spherePnts[0], spherePnts[3], spherePnts[5]), + Triangle(spherePnts[2], spherePnts[7], spherePnts[1]), + Triangle(spherePnts[2], spherePnts[1], spherePnts[6]), + Triangle(spherePnts[8], spherePnts[0], spherePnts[11]), + Triangle(spherePnts[8], spherePnts[11], spherePnts[1]), + Triangle(spherePnts[9], spherePnts[10], spherePnts[3]), + Triangle(spherePnts[9], spherePnts[2], spherePnts[10]), + Triangle(spherePnts[8], spherePnts[4], spherePnts[0]), + Triangle(spherePnts[11], spherePnts[0], spherePnts[5]), + Triangle(spherePnts[4], spherePnts[9], spherePnts[3]), + Triangle(spherePnts[5], spherePnts[3], spherePnts[10]), + Triangle(spherePnts[7], spherePnts[8], spherePnts[1]), + Triangle(spherePnts[6], spherePnts[1], spherePnts[11]), + Triangle(spherePnts[7], spherePnts[2], spherePnts[9]), + Triangle(spherePnts[6], spherePnts[10], spherePnts[2]), + }; + + static TriangleMesh icosahedronMesh = { + Icosahedron, + &icosahedron[0] + }; + + return(&icosahedronMesh); +} + +//------------------------------------------------------------------------------ + +void Sphere::calcNormals(TriangleMesh * mesh) +{ + for(U32 i = 0; i < mesh->numPoly; i++) + { + Triangle & tri = mesh->poly[i]; + mCross(tri.pnt[1] - tri.pnt[0], tri.pnt[2] - tri.pnt[0], &tri.normal); + } +} + +//------------------------------------------------------------------------------ + +Sphere::~Sphere() +{ + // level 0 is static data + for(U32 i = 1; i < mDetails.size(); i++) + { + delete [] mDetails[i]->poly; + delete mDetails[i]; + } +} + +//------------------------------------------------------------------------------ + +const Sphere::TriangleMesh * Sphere::getMesh(U32 level) +{ + AssertFatal(mDetails.size(), "Sphere::getMesh: no details!"); + + if(level > MaxLevel) + level = MaxLevel; + + // + while(mDetails.size() <= level) + mDetails.push_back(subdivideMesh(mDetails.last())); + + return(mDetails[level]); +} + +Sphere::TriangleMesh * Sphere::subdivideMesh(TriangleMesh * prevMesh) +{ + AssertFatal(prevMesh, "Sphere::subdivideMesh: invalid previous mesh level!"); + + // + TriangleMesh * mesh = new TriangleMesh; + + mesh->numPoly = prevMesh->numPoly * 4; + mesh->poly = new Triangle [mesh->numPoly]; + + // + for(U32 i = 0; i < prevMesh->numPoly; i++) + { + Triangle * pt = &prevMesh->poly[i]; + Triangle * nt = &mesh->poly[i*4]; + + Point3F a = (pt->pnt[0] + pt->pnt[2]) / 2; + Point3F b = (pt->pnt[0] + pt->pnt[1]) / 2; + Point3F c = (pt->pnt[1] + pt->pnt[2]) / 2; + + // force the point onto the unit sphere surface + a.normalize(); + b.normalize(); + c.normalize(); + + // + nt->pnt[0] = pt->pnt[0]; + nt->pnt[1] = b; + nt->pnt[2] = a; + nt++; + + // + nt->pnt[0] = b; + nt->pnt[1] = pt->pnt[1]; + nt->pnt[2] = c; + nt++; + + // + nt->pnt[0] = a; + nt->pnt[1] = b; + nt->pnt[2] = c; + nt++; + + // + nt->pnt[0] = a; + nt->pnt[1] = c; + nt->pnt[2] = pt->pnt[2]; + } + + calcNormals(mesh); + return(mesh); +} diff --git a/T3D/sphere.h b/T3D/sphere.h new file mode 100644 index 0000000..4bf5e25 --- /dev/null +++ b/T3D/sphere.h @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SPHERE_H_ +#define _SPHERE_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +//------------------------------------------------------------------------------ +// Class: Sphere +//------------------------------------------------------------------------------ +// * ctor takes type of base polyhedron that is subdivided to create sphere +// * getMesh(...) will subdivide the current mesh to the desired level where +// (each level has 4 times the polys of the previous level) + +class Sphere +{ + public: + + // regular polyhedra with triangle face polygons (num of faces) + enum { + Tetrahedron = 4, + Octahedron = 8, + Icosahedron = 20, + + MaxLevel = 5 + }; + + struct Triangle { + Triangle() {} + Triangle(const Point3F &a, const Point3F &b, const Point3F &c) + { + pnt[0] = a; + pnt[1] = b; + pnt[2] = c; + } + + Point3F pnt[3]; + Point3F normal; + }; + + struct TriangleMesh { + U32 numPoly; + Triangle * poly; + }; + + Sphere(U32 baseType = Octahedron); + ~Sphere(); + + const TriangleMesh * getMesh(U32 level = 0); + + private: + + TriangleMesh * createTetrahedron(); + TriangleMesh * createOctahedron(); + TriangleMesh * createIcosahedron(); + + Vector mDetails; + + void calcNormals(TriangleMesh *); + TriangleMesh * subdivideMesh(TriangleMesh*); +}; + +#endif diff --git a/T3D/spotLight.cpp b/T3D/spotLight.cpp new file mode 100644 index 0000000..956a311 --- /dev/null +++ b/T3D/spotLight.cpp @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/spotLight.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CO_NETOBJECT_V1( SpotLight ); + +SpotLight::SpotLight() + : mRange( 10.0f ), + mInnerConeAngle( 40.0f ), + mOuterConeAngle( 45.0f ) +{ + // We set the type here to ensure the extended + // parameter validation works when setting fields. + mLight->setType( LightInfo::Spot ); +} + +SpotLight::~SpotLight() +{ +} + +void SpotLight::initPersistFields() +{ + addGroup( "Light" ); + + addField( "range", TypeF32, Offset( mRange, SpotLight ) ); + addField( "innerAngle", TypeF32, Offset( mInnerConeAngle, SpotLight ) ); + addField( "outerAngle", TypeF32, Offset( mOuterConeAngle, SpotLight ) ); + + endGroup( "Light" ); + + // We do the parent fields at the end so that + // they show up that way in the inspector. + Parent::initPersistFields(); + + // Remove the scale field... it's already + // defined by the range and angle. + removeField( "scale" ); +} + +void SpotLight::_conformLights() +{ + mLight->setTransform( getTransform() ); + + mRange = getMax( mRange, 0.05f ); + mLight->setRange( mRange ); + + mLight->setColor( mColor ); + mLight->setBrightness( mBrightness ); + mLight->setCastShadows( mCastShadows ); + mLight->setPriority( mPriority ); + + mOuterConeAngle = getMax( 0.01f, mOuterConeAngle ); + mInnerConeAngle = getMin( mInnerConeAngle, mOuterConeAngle ); + + mLight->setInnerConeAngle( mInnerConeAngle ); + mLight->setOuterConeAngle( mOuterConeAngle ); + + // Update the bounds and scale to fit our spotlight. + F32 radius = mRange * mSin( mDegToRad( mOuterConeAngle ) * 0.5f ); + mObjBox.minExtents.set( -1, 0, -1 ); + mObjBox.maxExtents.set( 1, 1, 1 ); + mObjScale.set( radius, mRange, radius ); + + // Skip our transform... it just dirties mask bits. + Parent::setTransform( mObjToWorld ); +} + +U32 SpotLight::packUpdate(NetConnection *conn, U32 mask, BitStream *stream ) +{ + if ( stream->writeFlag( mask & UpdateMask ) ) + { + stream->write( mRange ); + stream->write( mInnerConeAngle ); + stream->write( mOuterConeAngle ); + } + + return Parent::packUpdate( conn, mask, stream ); +} + +void SpotLight::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + if ( stream->readFlag() ) // UpdateMask + { + stream->read( &mRange ); + stream->read( &mInnerConeAngle ); + stream->read( &mOuterConeAngle ); + } + + Parent::unpackUpdate( conn, stream ); +} + +void SpotLight::setScale( const VectorF &scale ) +{ + // The y coord is the spotlight range. + mRange = getMax( scale.y, 0.05f ); + + // Use the average of the x and z to get a radius. This + // is the best method i've found to make the manipulation + // from the WorldEditor gizmo to feel right. + F32 radius = mClampF( ( scale.x + scale.z ) * 0.5f, 0.05f, mRange ); + mOuterConeAngle = mRadToDeg( mAsin( radius / mRange ) ) * 2.0f; + + // Make sure the inner angle is less than the outer. + // + // TODO: Maybe we should make the inner angle a scale + // and not an absolute angle? + // + mInnerConeAngle = getMin( mInnerConeAngle, mOuterConeAngle ); + + // We changed a bunch of our settings + // so notify the client. + setMaskBits( UpdateMask ); + + // Let the parent do the final scale. + Parent::setScale( VectorF( radius, mRange, radius ) ); +} + +void SpotLight::_renderViz( SceneState *state ) +{ + GFXDrawUtil *draw = GFX->getDrawUtil(); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setCullMode( GFXCullNone ); + desc.setBlend( true ); + + // Base the color on the light color. + ColorI color( mColor ); + color.alpha = 16; + + F32 radius = mRange * mSin( mDegToRad( mOuterConeAngle * 0.5f ) ); + draw->drawCone( desc, + getPosition() + ( getTransform().getForwardVector() * mRange ), + getPosition(), + radius, + color ); +} diff --git a/T3D/spotLight.h b/T3D/spotLight.h new file mode 100644 index 0000000..649b8a2 --- /dev/null +++ b/T3D/spotLight.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SPOTLIGHT_H_ +#define _SPOTLIGHT_H_ + +#ifndef _LIGHTBASE_H_ +#include "T3D/lightBase.h" +#endif + + +class SpotLight : public LightBase +{ + typedef LightBase Parent; + +protected: + + F32 mRange; + + F32 mInnerConeAngle; + + F32 mOuterConeAngle; + + // LightBase + void _conformLights(); + void _renderViz( SceneState *state ); + +public: + + SpotLight(); + virtual ~SpotLight(); + + // ConsoleObject + DECLARE_CONOBJECT( SpotLight ); + static void initPersistFields(); + + // SceneObject + virtual void setScale( const VectorF &scale ); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); +}; + +#endif // _SPOTLIGHT_H_ diff --git a/T3D/staticShape.cpp b/T3D/staticShape.cpp new file mode 100644 index 0000000..46c87c7 --- /dev/null +++ b/T3D/staticShape.cpp @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "core/stream/bitStream.h" +#include "app/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "T3D/moveManager.h" +#include "ts/tsShapeInstance.h" +#include "T3D/staticShape.h" +#include "math/mathIO.h" +#include "sim/netConnection.h" + +extern void wireCube(F32 size,Point3F pos); + +static const U32 sgAllowedDynamicTypes = 0xffffff; + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(StaticShapeData); + +StaticShapeData::StaticShapeData() +{ + dynamicTypeField = 0; + + shadowEnable = true; + + noIndividualDamage = false; +} + +void StaticShapeData::initPersistFields() +{ + addField("noIndividualDamage", TypeBool, Offset(noIndividualDamage, StaticShapeData)); + addField("dynamicType", TypeS32, Offset(dynamicTypeField, StaticShapeData)); + + Parent::initPersistFields(); +} + +void StaticShapeData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->writeFlag(noIndividualDamage); + stream->write(dynamicTypeField); +} + +void StaticShapeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + noIndividualDamage = stream->readFlag(); + stream->read(&dynamicTypeField); +} + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(StaticShape); + +StaticShape::StaticShape() +{ + overrideOptions = false; + + mTypeMask |= StaticShapeObjectType | StaticObjectType; + mDataBlock = 0; +} + +StaticShape::~StaticShape() +{ +} + + +//---------------------------------------------------------------------------- + +void StaticShape::initPersistFields() +{ + addGroup("Lighting"); + addField("receiveSunLight", TypeBool, Offset(receiveSunLight, SceneObject)); + addField("receiveLMLighting", TypeBool, Offset(receiveLMLighting, SceneObject)); + //addField("useAdaptiveSelfIllumination", TypeBool, Offset(useAdaptiveSelfIllumination, SceneObject)); + addField("useCustomAmbientLighting", TypeBool, Offset(useCustomAmbientLighting, SceneObject)); + //addField("customAmbientSelfIllumination", TypeBool, Offset(customAmbientForSelfIllumination, SceneObject)); + addField("customAmbientLighting", TypeColorF, Offset(customAmbientLighting, SceneObject)); + addField("lightGroupName", TypeRealString, Offset(lightGroupName, SceneObject)); + endGroup("Lighting"); + + Parent::initPersistFields(); +} + +bool StaticShape::onAdd() +{ + if(!Parent::onAdd() || !mDataBlock) + return false; + + // We need to modify our type mask based on what our datablock says... + mTypeMask |= (mDataBlock->dynamicTypeField & sgAllowedDynamicTypes); + + addToScene(); + + if (isServerObject()) + scriptOnAdd(); + return true; +} + +bool StaticShape::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + +void StaticShape::onRemove() +{ + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- + +void StaticShape::processTick(const Move* move) +{ + Parent::processTick(move); + + // Image Triggers + if (move && mDamageState == Enabled) { + setImageTriggerState(0,move->trigger[0]); + setImageTriggerState(1,move->trigger[1]); + } + + if (isMounted()) { + MatrixF mat; + mMount.object->getMountTransform(mMount.node,&mat); + Parent::setTransform(mat); + Parent::setRenderTransform(mat); + } +} + +void StaticShape::interpolateTick(F32) +{ + if (isMounted()) { + MatrixF mat; + mMount.object->getRenderMountTransform(mMount.node,&mat); + Parent::setRenderTransform(mat); + } +} + +void StaticShape::setTransform(const MatrixF& mat) +{ + Parent::setTransform(mat); + setMaskBits(PositionMask); +} + +void StaticShape::onUnmount(ShapeBase*,S32) +{ + // Make sure the client get's the final server pos. + setMaskBits(PositionMask); +} + + +//---------------------------------------------------------------------------- + +U32 StaticShape::packUpdate(NetConnection *connection, U32 mask, BitStream *bstream) +{ + U32 retMask = Parent::packUpdate(connection,mask,bstream); + if (bstream->writeFlag(mask & PositionMask | ExtendedInfoMask)) + { + + // Write the transform (do _not_ use writeAffineTransform. Since this is a static + // object, the transform must be RIGHT THE *&)*$&^ ON or it will goof up the + // synchronization between the client and the server. + mathWrite(*bstream,mObjToWorld); + mathWrite(*bstream, mObjScale); + } + + // powered? + bstream->writeFlag(mPowered); + + if (mLightPlugin) + { + retMask |= mLightPlugin->packUpdate(this, ExtendedInfoMask, connection, mask, bstream); + } + + return retMask; +} + +void StaticShape::unpackUpdate(NetConnection *connection, BitStream *bstream) +{ + Parent::unpackUpdate(connection,bstream); + if (bstream->readFlag()) + { + MatrixF mat; + mathRead(*bstream,&mat); + Parent::setTransform(mat); + Parent::setRenderTransform(mat); + + VectorF scale; + mathRead(*bstream, &scale); + setScale(scale); + } + + // powered? + mPowered = bstream->readFlag(); + + if (mLightPlugin) + { + mLightPlugin->unpackUpdate(this, connection, bstream); + } +} + + +//---------------------------------------------------------------------------- +ConsoleMethod( StaticShape, setPoweredState, void, 3, 3, "(bool isPowered)") +{ + if(!object->isServerObject()) + return; + object->setPowered(dAtob(argv[2])); +} + +ConsoleMethod( StaticShape, getPoweredState, bool, 2, 2, "") +{ + if(!object->isServerObject()) + return(false); + return(object->isPowered()); +} diff --git a/T3D/staticShape.h b/T3D/staticShape.h new file mode 100644 index 0000000..2e626ee --- /dev/null +++ b/T3D/staticShape.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STATICSHAPE_H_ +#define _STATICSHAPE_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif + +//---------------------------------------------------------------------------- + +struct StaticShapeData: public ShapeBaseData { + typedef ShapeBaseData Parent; + + public: + StaticShapeData(); + + bool noIndividualDamage; + S32 dynamicTypeField; + bool isShielded; + F32 energyPerDamagePoint; + + // + DECLARE_CONOBJECT(StaticShapeData); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class StaticShape: public ShapeBase +{ + typedef ShapeBase Parent; + + StaticShapeData* mDataBlock; + bool mPowered; + + void onUnmount(ShapeBase* obj,S32 node); + +protected: + enum MaskBits { + PositionMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + +public: + DECLARE_CONOBJECT(StaticShape); + + StaticShape(); + ~StaticShape(); + + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData* dptr); + + void processTick(const Move *move); + void interpolateTick(F32 delta); + void setTransform(const MatrixF &mat); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + // power + void setPowered(bool power) {mPowered = power;} + bool isPowered() {return(mPowered);} + + static void initPersistFields(); +}; + + +#endif diff --git a/T3D/tickCache.cpp b/T3D/tickCache.cpp new file mode 100644 index 0000000..a4c5391 --- /dev/null +++ b/T3D/tickCache.cpp @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/gameBase.h" +#include "console/consoleTypes.h" +#include "console/consoleInternal.h" +#include "core/stream/bitStream.h" +#include "sim/netConnection.h" +#include "T3D/gameConnection.h" +#include "math/mathIO.h" +#include "T3D/moveManager.h" +#include "T3D/gameProcess.h" + +struct TickCacheHead +{ + TickCacheEntry * oldest; + TickCacheEntry * newest; + TickCacheEntry * next; + U32 numEntry; +}; + +namespace +{ + FreeListChunker sgTickCacheHeadStore; + FreeListChunker sgTickCacheEntryStore; + FreeListChunker sgMoveStore; + + static TickCacheHead * allocHead() { return sgTickCacheHeadStore.alloc(); } + static void freeHead(TickCacheHead * head) { sgTickCacheHeadStore.free(head); } + + static TickCacheEntry * allocEntry() { return sgTickCacheEntryStore.alloc(); } + static void freeEntry(TickCacheEntry * entry) { sgTickCacheEntryStore.free(entry); } + + static Move * allocMove() { return sgMoveStore.alloc(); } + static void freeMove(Move * move) { sgMoveStore.free(move); } +} + +//---------------------------------------------------------------------------- + +TickCache::~TickCache() +{ + if (mTickCacheHead) + { + setCacheSize(0); + freeHead(mTickCacheHead); + mTickCacheHead = NULL; + } +} + +Move * TickCacheEntry::allocateMove() +{ + return allocMove(); +} + +TickCacheEntry * TickCache::addCacheEntry() +{ + // Add a new entry, creating head if needed + if (!mTickCacheHead) + { + mTickCacheHead = allocHead(); + mTickCacheHead->newest = mTickCacheHead->oldest = mTickCacheHead->next = NULL; + mTickCacheHead->numEntry = 0; + } + if (!mTickCacheHead->newest) + { + mTickCacheHead->newest = mTickCacheHead->oldest = allocEntry(); + } + else + { + mTickCacheHead->newest->next = allocEntry(); + mTickCacheHead->newest = mTickCacheHead->newest->next; + } + mTickCacheHead->newest->next = NULL; + mTickCacheHead->newest->move = NULL; + mTickCacheHead->numEntry++; + return mTickCacheHead->newest; +} + +void TickCache::setCacheSize(S32 len) +{ + // grow cache to len size, adding to newest side of the list + while (!mTickCacheHead || mTickCacheHead->numEntry < len) + addCacheEntry(); + // shrink tick cache down to given size, popping off oldest entries first + while (mTickCacheHead && mTickCacheHead->numEntry > len) + dropOldest(); +} + +void TickCache::dropOldest() +{ + AssertFatal(mTickCacheHead->oldest,"Popping off too many tick cache entries"); + TickCacheEntry * oldest = mTickCacheHead->oldest; + mTickCacheHead->oldest = oldest->next; + if (oldest->move) + freeMove(oldest->move); + freeEntry(oldest); + mTickCacheHead->numEntry--; + if (mTickCacheHead->numEntry < 2) + mTickCacheHead->newest = mTickCacheHead->oldest; +} + +void TickCache::dropNextOldest() +{ + AssertFatal(mTickCacheHead->oldest && mTickCacheHead->numEntry>1,"Popping off too many tick cache entries"); + TickCacheEntry * oldest = mTickCacheHead->oldest; + TickCacheEntry * nextoldest = mTickCacheHead->oldest->next; + oldest->next = nextoldest->next; + if (nextoldest->move) + freeMove(nextoldest->move); + freeEntry(nextoldest); + mTickCacheHead->numEntry--; + if (mTickCacheHead->numEntry==1) + mTickCacheHead->newest = mTickCacheHead->oldest; +} + +void TickCache::ageCache(S32 numToAge, S32 len) +{ + AssertFatal(mTickCacheHead,"No tick cache head"); + AssertFatal(mTickCacheHead->numEntry>=numToAge,"Too few entries!"); + AssertFatal(mTickCacheHead->numEntry>numToAge,"Too few entries!"); + + while (numToAge--) + dropOldest(); + while (mTickCacheHead->numEntry>len) + dropNextOldest(); + while (mTickCacheHead->numEntrynext = mTickCacheHead->oldest; + // if no head, that's ok, we'll just add entries as we go +} + +TickCacheEntry * TickCache::incCacheList(bool addIfNeeded) +{ + // continue iterating through cache, returning current entry + // we'll add new entries if need be + TickCacheEntry * ret = NULL; + if (mTickCacheHead && mTickCacheHead->next) + { + ret = mTickCacheHead->next; + mTickCacheHead->next = mTickCacheHead->next->next; + } + else if (addIfNeeded) + { + addCacheEntry(); + ret = mTickCacheHead->newest; + } + return ret; +} \ No newline at end of file diff --git a/T3D/tickCache.h b/T3D/tickCache.h new file mode 100644 index 0000000..10bf7a4 --- /dev/null +++ b/T3D/tickCache.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TICKCACHE_H_ +#define _TICKCACHE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +struct Move; + +struct TickCacheEntry +{ + enum { MaxPacketSize=140 }; + + U8 packetData[MaxPacketSize]; + TickCacheEntry * next; + Move * move; + + // If you want to assign moves to tick cache for later playback, allocate them here + Move * allocateMove(); +}; + +struct TickCacheHead; + +class TickCache +{ +public: + TickCache(); + ~TickCache(); + + TickCacheEntry * addCacheEntry(); + void dropOldest(); + void dropNextOldest(); + void ageCache(S32 numToAge, S32 len); + void setCacheSize(S32 len); + void beginCacheList(); + TickCacheEntry * incCacheList(bool addIfNeeded=true); + +private: + TickCacheHead * mTickCacheHead; +}; + +inline TickCache::TickCache() +{ + mTickCacheHead = NULL; +} + +#endif // _TICKCACHE_H_ \ No newline at end of file diff --git a/T3D/trigger.cpp b/T3D/trigger.cpp new file mode 100644 index 0000000..2b8f735 --- /dev/null +++ b/T3D/trigger.cpp @@ -0,0 +1,746 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "collision/boxConvex.h" + +#include "core/stream/bitStream.h" +#include "math/mathIO.h" + +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/gfxDrawUtil.h" + +#include "T3D/trigger.h" + +bool Trigger::smRenderTriggers = false; + +//----------------------------------------------------------------------------- + +ConsoleMethod( TriggerData, onEnterTrigger, void, 4, 4, "( Trigger t, SimObject intruder)") +{ + Trigger* trigger = NULL; + if (Sim::findObject(argv[2], trigger) == false) + return; + + // Do nothing with the trigger object id by default... + SimGroup* pGroup = trigger->getGroup(); + for (SimGroup::iterator itr = pGroup->begin(); itr != pGroup->end(); itr++) + Con::executef(*itr, "onTrigger", Con::getIntArg(trigger->getId()), "1"); +} + +ConsoleMethod( TriggerData, onLeaveTrigger, void, 4, 4, "( Trigger t, SimObject intruder)") +{ + Trigger* trigger = NULL; + if (Sim::findObject(argv[2], trigger) == false) + return; + + if (trigger->getNumTriggeringObjects() == 0) { + SimGroup* pGroup = trigger->getGroup(); + for (SimGroup::iterator itr = pGroup->begin(); itr != pGroup->end(); itr++) + Con::executef(*itr, "onTrigger", Con::getIntArg(trigger->getId()), "0"); + } +} + +ConsoleMethod( TriggerData, onTickTrigger, void, 3, 3, "( Trigger t )") +{ + Trigger* trigger = NULL; + if (Sim::findObject(argv[2], trigger) == false) + return; + + // Do nothing with the trigger object id by default... + SimGroup* pGroup = trigger->getGroup(); + for (SimGroup::iterator itr = pGroup->begin(); itr != pGroup->end(); itr++) + Con::executef(*itr, "onTriggerTick", Con::getIntArg(trigger->getId())); +} + +ConsoleMethod( Trigger, getNumObjects, S32, 2, 2, "") +{ + return object->getNumTriggeringObjects(); +} + +ConsoleMethod( Trigger, getObject, S32, 3, 3, "(int idx)") +{ + S32 index = dAtoi(argv[2]); + + if (index >= object->getNumTriggeringObjects() || index < 0) + return -1; + else + return object->getObject(U32(index))->getId(); +} + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(TriggerData); + +TriggerData::TriggerData() +{ + tickPeriodMS = 100; + isClientSide = false; +} + +bool TriggerData::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +void TriggerData::initPersistFields() +{ + addField("tickPeriodMS", TypeS32, Offset(tickPeriodMS, TriggerData), "Time between calls to TriggerData::onTickTrigger()."); + addField("clientSide", TypeBool, Offset(isClientSide, TriggerData), "Only trigger on clients."); + + Parent::initPersistFields(); +} + + +//-------------------------------------------------------------------------- +void TriggerData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(tickPeriodMS); + stream->write(isClientSide); +} + +void TriggerData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&tickPeriodMS); + stream->read(&isClientSide); +} + + +//-------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(Trigger); + +Trigger::Trigger() +{ + // Don't ghost by default. + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask |= TriggerObjectType; + + mObjScale.set(1, 1, 1); + mObjToWorld.identity(); + mWorldToObj.identity(); + + mDataBlock = NULL; + + mLastThink = 0; + mCurrTick = 0; + + mConvexList = new Convex; +} + +Trigger::~Trigger() +{ + delete mConvexList; + mConvexList = NULL; +} + +bool Trigger::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + // Collide against bounding box + F32 st,et,fst = 0,fet = 1; + F32 *bmin = &mObjBox.minExtents.x; + F32 *bmax = &mObjBox.maxExtents.x; + F32 const *si = &start.x; + F32 const *ei = &end.x; + + for (int i = 0; i < 3; i++) + { + if (*si < *ei) + { + if (*si > *bmax || *ei < *bmin) + return false; + F32 di = *ei - *si; + st = (*si < *bmin)? (*bmin - *si) / di: 0; + et = (*ei > *bmax)? (*bmax - *si) / di: 1; + } + else + { + if (*ei > *bmax || *si < *bmin) + return false; + F32 di = *ei - *si; + st = (*si > *bmax)? (*bmax - *si) / di: 0; + et = (*ei < *bmin)? (*bmin - *si) / di: 1; + } + if (st > fst) fst = st; + if (et < fet) fet = et; + if (fet < fst) + return false; + bmin++; bmax++; + si++; ei++; + } + + info->normal = start - end; + info->normal.normalizeSafe(); + getTransform().mulV( info->normal ); + + info->t = fst; + info->object = this; + info->point.interpolate(start,end,fst); + info->material = 0; + return true; +} + + +//-------------------------------------------------------------------------- +/* Console polyhedron data type exporter + The polyhedron type is really a quadrilateral and consists of a corner + point follow by three vectors representing the edges extending from the + corner. +*/ +ConsoleType( TriggerPolyhedron, TypeTriggerPolyhedron, Polyhedron ) + + +ConsoleGetType( TypeTriggerPolyhedron ) +{ + U32 i; + Polyhedron* pPoly = reinterpret_cast(dptr); + + // First point is corner, need to find the three vectors...` + Point3F origin = pPoly->pointList[0]; + U32 currVec = 0; + Point3F vecs[3]; + for (i = 0; i < pPoly->edgeList.size(); i++) { + const U32 *vertex = pPoly->edgeList[i].vertex; + if (vertex[0] == 0) + vecs[currVec++] = pPoly->pointList[vertex[1]] - origin; + else + if (vertex[1] == 0) + vecs[currVec++] = pPoly->pointList[vertex[0]] - origin; + } + AssertFatal(currVec == 3, "Internal error: Bad trigger polyhedron"); + + // Build output string. + char* retBuf = Con::getReturnBuffer(1024); + dSprintf(retBuf, 1023, "%7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f", + origin.x, origin.y, origin.z, + vecs[0].x, vecs[0].y, vecs[0].z, + vecs[2].x, vecs[2].y, vecs[2].z, + vecs[1].x, vecs[1].y, vecs[1].z); + + return retBuf; +} + +/* Console polyhedron data type loader + The polyhedron type is really a quadrilateral and consists of an corner + point follow by three vectors representing the edges extending from the + corner. +*/ +ConsoleSetType( TypeTriggerPolyhedron ) +{ + if (argc != 1) { + Con::printf("(TypeTriggerPolyhedron) multiple args not supported for polyhedra"); + return; + } + + Point3F origin; + Point3F vecs[3]; + + U32 numArgs = dSscanf(argv[0], "%g %g %g %g %g %g %g %g %g %g %g %g", + &origin.x, &origin.y, &origin.z, + &vecs[0].x, &vecs[0].y, &vecs[0].z, + &vecs[1].x, &vecs[1].y, &vecs[1].z, + &vecs[2].x, &vecs[2].y, &vecs[2].z); + if (numArgs != 12) { + Con::printf("Bad polyhedron!"); + return; + } + + Polyhedron* pPoly = reinterpret_cast(dptr); + + pPoly->pointList.setSize(8); + pPoly->pointList[0] = origin; + pPoly->pointList[1] = origin + vecs[0]; + pPoly->pointList[2] = origin + vecs[1]; + pPoly->pointList[3] = origin + vecs[2]; + pPoly->pointList[4] = origin + vecs[0] + vecs[1]; + pPoly->pointList[5] = origin + vecs[0] + vecs[2]; + pPoly->pointList[6] = origin + vecs[1] + vecs[2]; + pPoly->pointList[7] = origin + vecs[0] + vecs[1] + vecs[2]; + + Point3F normal; + pPoly->planeList.setSize(6); + + mCross(vecs[2], vecs[0], &normal); + pPoly->planeList[0].set(origin, normal); + mCross(vecs[0], vecs[1], &normal); + pPoly->planeList[1].set(origin, normal); + mCross(vecs[1], vecs[2], &normal); + pPoly->planeList[2].set(origin, normal); + mCross(vecs[1], vecs[0], &normal); + pPoly->planeList[3].set(pPoly->pointList[7], normal); + mCross(vecs[2], vecs[1], &normal); + pPoly->planeList[4].set(pPoly->pointList[7], normal); + mCross(vecs[0], vecs[2], &normal); + pPoly->planeList[5].set(pPoly->pointList[7], normal); + + pPoly->edgeList.setSize(12); + pPoly->edgeList[0].vertex[0] = 0; pPoly->edgeList[0].vertex[1] = 1; pPoly->edgeList[0].face[0] = 0; pPoly->edgeList[0].face[1] = 1; + pPoly->edgeList[1].vertex[0] = 1; pPoly->edgeList[1].vertex[1] = 5; pPoly->edgeList[1].face[0] = 0; pPoly->edgeList[1].face[1] = 4; + pPoly->edgeList[2].vertex[0] = 5; pPoly->edgeList[2].vertex[1] = 3; pPoly->edgeList[2].face[0] = 0; pPoly->edgeList[2].face[1] = 3; + pPoly->edgeList[3].vertex[0] = 3; pPoly->edgeList[3].vertex[1] = 0; pPoly->edgeList[3].face[0] = 0; pPoly->edgeList[3].face[1] = 2; + pPoly->edgeList[4].vertex[0] = 3; pPoly->edgeList[4].vertex[1] = 6; pPoly->edgeList[4].face[0] = 3; pPoly->edgeList[4].face[1] = 2; + pPoly->edgeList[5].vertex[0] = 6; pPoly->edgeList[5].vertex[1] = 2; pPoly->edgeList[5].face[0] = 2; pPoly->edgeList[5].face[1] = 5; + pPoly->edgeList[6].vertex[0] = 2; pPoly->edgeList[6].vertex[1] = 0; pPoly->edgeList[6].face[0] = 2; pPoly->edgeList[6].face[1] = 1; + pPoly->edgeList[7].vertex[0] = 1; pPoly->edgeList[7].vertex[1] = 4; pPoly->edgeList[7].face[0] = 4; pPoly->edgeList[7].face[1] = 1; + pPoly->edgeList[8].vertex[0] = 4; pPoly->edgeList[8].vertex[1] = 2; pPoly->edgeList[8].face[0] = 1; pPoly->edgeList[8].face[1] = 5; + pPoly->edgeList[9].vertex[0] = 4; pPoly->edgeList[9].vertex[1] = 7; pPoly->edgeList[9].face[0] = 4; pPoly->edgeList[9].face[1] = 5; + pPoly->edgeList[10].vertex[0] = 5; pPoly->edgeList[10].vertex[1] = 7; pPoly->edgeList[10].face[0] = 3; pPoly->edgeList[10].face[1] = 4; + pPoly->edgeList[11].vertex[0] = 7; pPoly->edgeList[11].vertex[1] = 6; pPoly->edgeList[11].face[0] = 3; pPoly->edgeList[11].face[1] = 5; +} + + +//----------------------------------------------------------------------------- +void Trigger::consoleInit() +{ + Con::addVariable( "$Trigger::renderTriggers", TypeBool, &smRenderTriggers ); +} + +void Trigger::initPersistFields() +{ + addField("polyhedron", TypeTriggerPolyhedron, Offset(mTriggerPolyhedron, Trigger), + "The polyhedron type is really a quadrilateral and consists of a corner" + "point followed by three vectors representing the edges extending from the corner." ); + + addProtectedField("enterCommand", TypeCommand, Offset(mEnterCommand, Trigger), &setEnterCmd, &defaultProtectedGetFn, + "The command to execute when an object enters this trigger. Object id stored in %obj. Maximum 1023 characters." ); + addProtectedField("leaveCommand", TypeCommand, Offset(mLeaveCommand, Trigger), &setLeaveCmd, &defaultProtectedGetFn, + "The command to execute when an object leaves this trigger. Object id stored in %obj. Maximum 1023 characters." ); + addProtectedField("tickCommand", TypeCommand, Offset(mTickCommand, Trigger), &setTickCmd, &defaultProtectedGetFn, + "The command to execute while an object is inside this trigger. Maximum 1023 characters." ); + + Parent::initPersistFields(); +} + +bool Trigger::setEnterCmd(void *obj, const char*) +{ + static_cast(obj)->setMaskBits(EnterCmdMask); + return true; // to update the actual field +} + +bool Trigger::setLeaveCmd(void *obj, const char*) +{ + static_cast(obj)->setMaskBits(LeaveCmdMask); + return true; // to update the actual field +} + +bool Trigger::setTickCmd(void *obj, const char*) +{ + static_cast(obj)->setMaskBits(TickCmdMask); + return true; // to update the actual field +} + +//-------------------------------------------------------------------------- + +bool Trigger::onAdd() +{ + if(!Parent::onAdd()) + return false; + + Con::executef(this, "onAdd", Con::getIntArg(getId())); + + Polyhedron temp = mTriggerPolyhedron; + setTriggerPolyhedron(temp); + + addToScene(); + + if (isServerObject()) + scriptOnAdd(); + + return true; +} + +void Trigger::onRemove() +{ + Con::executef(this, "onRemove", Con::getIntArg(getId())); + + mConvexList->nukeList(); + + removeFromScene(); + Parent::onRemove(); +} + +bool Trigger::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + scriptOnNewDataBlock(); + return true; +} + +void Trigger::onDeleteNotify(SimObject* obj) +{ + GameBase* pScene = dynamic_cast(obj); + if (pScene != NULL && mDataBlock != NULL) + { + for (U32 i = 0; i < mObjects.size(); i++) + { + if (pScene == mObjects[i]) + { + mObjects.erase(i); + Con::executef(mDataBlock, "onLeaveTrigger", scriptThis(), Con::getIntArg(pScene->getId())); + break; + } + } + } + + Parent::onDeleteNotify(obj); +} + +void Trigger::inspectPostApply() +{ + setTriggerPolyhedron(mTriggerPolyhedron); + setMaskBits(PolyMask); + Parent::inspectPostApply(); +} + +//-------------------------------------------------------------------------- + +void Trigger::buildConvex(const Box3F& box, Convex* convex) +{ + // These should really come out of a pool + mConvexList->collectGarbage(); + + Box3F realBox = box; + mWorldToObj.mul(realBox); + realBox.minExtents.convolveInverse(mObjScale); + realBox.maxExtents.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + // Just return a box convex for the entire shape... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { + if (itr->mConvex->getType() == BoxConvexType && + itr->mConvex->getObject() == this) { + cc = itr->mConvex; + break; + } + } + if (cc) + return; + + // Create a new convex. + BoxConvex* cp = new BoxConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->init(this); + + mObjBox.getCenter(&cp->mCenter); + cp->mSize.x = mObjBox.len_x() / 2.0f; + cp->mSize.y = mObjBox.len_y() / 2.0f; + cp->mSize.z = mObjBox.len_z() / 2.0f; +} + + +//------------------------------------------------------------------------------ + +void Trigger::setTransform(const MatrixF & mat) +{ + Parent::setTransform(mat); + + if (isServerObject()) { + MatrixF base(true); + base.scale(Point3F(1.0/mObjScale.x, + 1.0/mObjScale.y, + 1.0/mObjScale.z)); + base.mul(mWorldToObj); + mClippedList.setBaseTransform(base); + + setMaskBits(TransformMask | ScaleMask); + } +} + +bool Trigger::prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + if ( isLastState(state, stateKey) ) + return false; + + // only render if selected or render flag is set + if ( !smRenderTriggers && !isSelected() ) + return false; + + setLastState( state, stateKey ); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Trigger::renderObject ); + ri->type = RenderPassManager::RIT_Object; + ri->defaultKey = 0; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void Trigger::renderObject( ObjectRenderInst *ri, + SceneState *state, + BaseMatInstance *overrideMat ) +{ + if (overrideMat) + return; + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setBlend( true ); + desc.setCullMode( GFXCullNone ); + + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + mat.scale( getScale() ); + + GFX->multWorld( mat ); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->drawPolyhedron( desc, mTriggerPolyhedron, ColorI( 255, 192, 0, 45 ) ); +} + +void Trigger::setTriggerPolyhedron(const Polyhedron& rPolyhedron) +{ + mTriggerPolyhedron = rPolyhedron; + + if (mTriggerPolyhedron.pointList.size() != 0) { + mObjBox.minExtents.set(1e10, 1e10, 1e10); + mObjBox.maxExtents.set(-1e10, -1e10, -1e10); + for (U32 i = 0; i < mTriggerPolyhedron.pointList.size(); i++) { + mObjBox.minExtents.setMin(mTriggerPolyhedron.pointList[i]); + mObjBox.maxExtents.setMax(mTriggerPolyhedron.pointList[i]); + } + } else { + mObjBox.minExtents.set(-0.5, -0.5, -0.5); + mObjBox.maxExtents.set( 0.5, 0.5, 0.5); + } + + MatrixF xform = getTransform(); + setTransform(xform); + + mClippedList.clear(); + mClippedList.mPlaneList = mTriggerPolyhedron.planeList; +// for (U32 i = 0; i < mClippedList.mPlaneList.size(); i++) +// mClippedList.mPlaneList[i].neg(); + + MatrixF base(true); + base.scale(Point3F(1.0/mObjScale.x, + 1.0/mObjScale.y, + 1.0/mObjScale.z)); + base.mul(mWorldToObj); + + mClippedList.setBaseTransform(base); +} + + +//-------------------------------------------------------------------------- + +bool Trigger::testObject(GameBase* enter) +{ + if (mTriggerPolyhedron.pointList.size() == 0) + return false; + + mClippedList.clear(); + + SphereF sphere; + sphere.center = (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5; + VectorF bv = mWorldBox.maxExtents - sphere.center; + sphere.radius = bv.len(); + + enter->buildPolyList(&mClippedList, mWorldBox, sphere); + return mClippedList.isEmpty() == false; +} + + +void Trigger::potentialEnterObject(GameBase* enter) +{ + if( (!mDataBlock || mDataBlock->isClientSide) && isServerObject() ) + return; + if( (mDataBlock && !mDataBlock->isClientSide) && isGhost() ) + return; + + for (U32 i = 0; i < mObjects.size(); i++) { + if (mObjects[i] == enter) + return; + } + + if (testObject(enter) == true) { + mObjects.push_back(enter); + deleteNotify(enter); + + if(!mEnterCommand.isEmpty()) + { + String command = String("%obj = ") + enter->scriptThis() + ";" + mEnterCommand; + Con::evaluate(command.c_str()); + } + + if( mDataBlock ) + Con::executef(mDataBlock, "onEnterTrigger", scriptThis(), enter->scriptThis()); + } +} + + +void Trigger::processTick(const Move* move) +{ + Parent::processTick(move); + + if (!mDataBlock) + return; + if (mDataBlock->isClientSide && isServerObject()) + return; + if (!mDataBlock->isClientSide && isClientObject()) + return; + + // + if (mObjects.size() == 0) + return; + + if (mLastThink + mDataBlock->tickPeriodMS < mCurrTick) { + mCurrTick = 0; + mLastThink = 0; + + for (S32 i = S32(mObjects.size() - 1); i >= 0; i--) { + if (testObject(mObjects[i]) == false) { + GameBase* remove = mObjects[i]; + mObjects.erase(i); + clearNotify(remove); + if(!mLeaveCommand.isEmpty()) + { + String command = String("%obj = ") + remove->scriptThis() + ";" + mLeaveCommand; + Con::evaluate(command.c_str()); + } + Con::executef(mDataBlock, "onLeaveTrigger", scriptThis(), remove->scriptThis()); + } + } + + if (!mTickCommand.isEmpty()) + Con::evaluate(mTickCommand.c_str()); + + if (mObjects.size() != 0) + Con::executef(mDataBlock, "onTickTrigger", scriptThis()); + } else { + mCurrTick += TickMs; + } +} + +//-------------------------------------------------------------------------- + +U32 Trigger::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 i; + U32 retMask = Parent::packUpdate(con, mask, stream); + + if( stream->writeFlag( mask & TransformMask ) ) + { + stream->writeAffineTransform(mObjToWorld); + } + + // Write the polyhedron + if( stream->writeFlag( mask & PolyMask ) ) + { + stream->write(mTriggerPolyhedron.pointList.size()); + for (i = 0; i < mTriggerPolyhedron.pointList.size(); i++) + mathWrite(*stream, mTriggerPolyhedron.pointList[i]); + + stream->write(mTriggerPolyhedron.planeList.size()); + for (i = 0; i < mTriggerPolyhedron.planeList.size(); i++) + mathWrite(*stream, mTriggerPolyhedron.planeList[i]); + + stream->write(mTriggerPolyhedron.edgeList.size()); + for (i = 0; i < mTriggerPolyhedron.edgeList.size(); i++) { + const Polyhedron::Edge& rEdge = mTriggerPolyhedron.edgeList[i]; + + stream->write(rEdge.face[0]); + stream->write(rEdge.face[1]); + stream->write(rEdge.vertex[0]); + stream->write(rEdge.vertex[1]); + } + } + + if( stream->writeFlag( mask & EnterCmdMask ) ) + stream->writeLongString(CMD_SIZE-1, mEnterCommand.c_str()); + if( stream->writeFlag( mask & LeaveCmdMask ) ) + stream->writeLongString(CMD_SIZE-1, mLeaveCommand.c_str()); + if( stream->writeFlag( mask & TickCmdMask ) ) + stream->writeLongString(CMD_SIZE-1, mTickCommand.c_str()); + + return retMask; +} + +void Trigger::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + U32 i, size; + + // Transform + if( stream->readFlag() ) + { + MatrixF temp; + stream->readAffineTransform(&temp); + setTransform(temp); + } + + // Read the polyhedron + if( stream->readFlag() ) + { + Polyhedron tempPH; + stream->read(&size); + tempPH.pointList.setSize(size); + for (i = 0; i < tempPH.pointList.size(); i++) + mathRead(*stream, &tempPH.pointList[i]); + + stream->read(&size); + tempPH.planeList.setSize(size); + for (i = 0; i < tempPH.planeList.size(); i++) + mathRead(*stream, &tempPH.planeList[i]); + + stream->read(&size); + tempPH.edgeList.setSize(size); + for (i = 0; i < tempPH.edgeList.size(); i++) { + Polyhedron::Edge& rEdge = tempPH.edgeList[i]; + + stream->read(&rEdge.face[0]); + stream->read(&rEdge.face[1]); + stream->read(&rEdge.vertex[0]); + stream->read(&rEdge.vertex[1]); + } + setTriggerPolyhedron(tempPH); + } + + if( stream->readFlag() ) + { + char buf[CMD_SIZE]; + stream->readLongString(CMD_SIZE-1, buf); + mEnterCommand = buf; + } + if( stream->readFlag() ) + { + char buf[CMD_SIZE]; + stream->readLongString(CMD_SIZE-1, buf); + mLeaveCommand = buf; + } + if( stream->readFlag() ) + { + char buf[CMD_SIZE]; + stream->readLongString(CMD_SIZE-1, buf); + mTickCommand = buf; + } +} diff --git a/T3D/trigger.h b/T3D/trigger.h new file mode 100644 index 0000000..a4fea5f --- /dev/null +++ b/T3D/trigger.h @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_TRIGGER +#define _H_TRIGGER + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif +#ifndef _EARLYOUTPOLYLIST_H_ +#include "collision/earlyOutPolyList.h" +#endif + +class Convex; + +typedef const char * TriggerPolyhedronType; +DefineConsoleType( TypeTriggerPolyhedron, TriggerPolyhedronType * ) + +struct TriggerData: public GameBaseData { + typedef GameBaseData Parent; + + public: + S32 tickPeriodMS; + bool isClientSide; + + TriggerData(); + DECLARE_CONOBJECT(TriggerData); + bool onAdd(); + static void initPersistFields(); + virtual void packData (BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + +class Trigger : public GameBase +{ + typedef GameBase Parent; + + Polyhedron mTriggerPolyhedron; + EarlyOutPolyList mClippedList; + Vector mObjects; + + TriggerData* mDataBlock; + + U32 mLastThink; + U32 mCurrTick; + Convex *mConvexList; + + String mEnterCommand; + String mLeaveCommand; + String mTickCommand; + + enum TriggerUpdateBits + { + TransformMask = Parent::NextFreeMask << 0, + PolyMask = Parent::NextFreeMask << 1, + EnterCmdMask = Parent::NextFreeMask << 2, + LeaveCmdMask = Parent::NextFreeMask << 3, + TickCmdMask = Parent::NextFreeMask << 4, + NextFreeMask = Parent::NextFreeMask << 5, + }; + + static const U32 CMD_SIZE = 1024; + + protected: + + static bool smRenderTriggers; + bool testObject(GameBase* enter); + void processTick(const Move *move); + + void buildConvex(const Box3F& box, Convex* convex); + + static bool setEnterCmd(void *obj, const char *data); + static bool setLeaveCmd(void *obj, const char *data); + static bool setTickCmd(void *obj, const char *data); + + public: + Trigger(); + ~Trigger(); + + // SimObject + DECLARE_CONOBJECT(Trigger); + static void consoleInit(); + static void initPersistFields(); + bool onAdd(); + void onRemove(); + void onDeleteNotify(SimObject*); + void inspectPostApply(); + + // NetObject + U32 packUpdate (NetConnection *conn, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection *conn, BitStream* stream); + + // SceneObject + void setTransform(const MatrixF &mat); + bool prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + + // GameBase + bool onNewDataBlock(GameBaseData* dptr); + + // Trigger + void setTriggerPolyhedron(const Polyhedron&); + + void potentialEnterObject(GameBase*); + U32 getNumTriggeringObjects() const; + GameBase* getObject(const U32); + + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); +}; + +inline U32 Trigger::getNumTriggeringObjects() const +{ + return mObjects.size(); +} + +inline GameBase* Trigger::getObject(const U32 index) +{ + AssertFatal(index < getNumTriggeringObjects(), "Error, out of range object index"); + + return mObjects[index]; +} + +#endif // _H_TRIGGER + diff --git a/T3D/tsStatic.cpp b/T3D/tsStatic.cpp new file mode 100644 index 0000000..1b46bd2 --- /dev/null +++ b/T3D/tsStatic.cpp @@ -0,0 +1,961 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/tsStatic.h" + +#include "core/resourceManager.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightManager.h" +#include "math/mathIO.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleTypes.h" +#include "T3D/shapeBase.h" +#include "sim/netConnection.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "ts/tsRenderState.h" +#include "collision/boxConvex.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsStatic.h" +#include "materials/materialDefinition.h" +#include "materials/materialManager.h" +#include "materials/matInstance.h" +#include "materials/materialFeatureData.h" +#include "materials/materialFeatureTypes.h" + +IMPLEMENT_CO_NETOBJECT_V1(TSStatic); + +//-------------------------------------------------------------------------- +TSStatic::TSStatic() +{ + overrideOptions = false; + + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask |= StaticObjectType | StaticTSObjectType | + StaticRenderedObjectType | ShadowCasterObjectType; + + mShapeName = ""; + mShapeInstance = NULL; + + mPlayAmbient = true; + mAmbientThread = NULL; + + mAllowPlayerStep = true; + + mConvexList = new Convex; + + mRenderNormalScalar = 0; + + mPhysicsRep = NULL; + + mCollisionType = CollisionMesh; +} + +TSStatic::~TSStatic() +{ + delete mConvexList; + mConvexList = NULL; +} + +//-------------------------------------------------------------------------- +static const EnumTable::Enums collisionTypeEnums[] = +{ + { TSStatic::None, "None" }, + { TSStatic::Bounds, "Bounds" }, + { TSStatic::CollisionMesh, "Collision Mesh" }, + { TSStatic::VisibleMesh, "Visible Mesh" }, +}; + +static const EnumTable gCollisionTypeTable( 4, &collisionTypeEnums[0] ); + +void TSStatic::initPersistFields() +{ + addGroup("Media"); + addField("shapeName", TypeFilename, Offset(mShapeName, TSStatic), "Name and path to model file."); + addField("playAmbient", TypeBool, Offset(mPlayAmbient, TSStatic), "Play the \"ambient\" animation."); + endGroup("Media"); + + addGroup("Lighting"); + addField("receiveSunLight", TypeBool, Offset(receiveSunLight, SceneObject), "Shape lighting affected by global Sun"); + addField("receiveLMLighting", TypeBool, Offset(receiveLMLighting, SceneObject), "Shape lighting affected by nearby lightmaps"); + //addField("useAdaptiveSelfIllumination", TypeBool, Offset(useAdaptiveSelfIllumination, SceneObject)); + addField("useCustomAmbientLighting", TypeBool, Offset(useCustomAmbientLighting, SceneObject), "Ambient light color (in low/no lighting condition))" + "which overrides other sources, such as Sun."); + //addField("customAmbientSelfIllumination", TypeBool, Offset(customAmbientForSelfIllumination, SceneObject)); + addField("customAmbientLighting", TypeColorF, Offset(customAmbientLighting, SceneObject)); + addField("lightGroupName", TypeRealString, Offset(lightGroupName, SceneObject), "Groups shape in a set with other objects affected by a designated" + "light source."); + endGroup("Lighting"); + + addGroup("Collision"); + addField( "collisionType", TypeEnum, Offset( mCollisionType, TSStatic ), 1, &gCollisionTypeTable ); + addField( "allowPlayerStep", TypeBool, Offset( mAllowPlayerStep, TSStatic ), "Allow a player to collide with this object."); + endGroup("Collision"); + + addGroup("Debug"); + addField("renderNormals", TypeF32, Offset( mRenderNormalScalar, TSStatic ), "Debug rendering mode which highlights shape normals." ); + endGroup("Debug"); + + Parent::initPersistFields(); +} + +void TSStatic::inspectPostApply() +{ + // Apply any transformations set in the editor + Parent::inspectPostApply(); + + if(isServerObject()) + { + setMaskBits(AdvancedStaticOptionsMask); + prepCollision(); + } +} + +//-------------------------------------------------------------------------- +bool TSStatic::onAdd() +{ + PROFILE_SCOPE(TSStatic_onAdd); + + if ( isServerObject() ) + { + // Handle the old "usePolysoup" field + SimFieldDictionary* fieldDict = getFieldDictionary(); + + if ( fieldDict ) + { + StringTableEntry slotName = StringTable->insert( "usePolysoup" ); + + SimFieldDictionary::Entry * entry = fieldDict->findDynamicField( slotName ); + + if ( entry ) + { + // Was "usePolysoup" set? + bool usePolysoup = dAtob( entry->value ); + + // "usePolysoup" maps to the new VisibleMesh type + if ( usePolysoup ) + mCollisionType = VisibleMesh; + + // Remove the field in favor on the new "collisionType" field + fieldDict->setFieldValue( slotName, "" ); + } + } + } + + if ( !Parent::onAdd() ) + return false; + + // Setup the shape. + if ( !_createShape() ) + { + Con::errorf( "TSStatic::onAdd() - Shape creation failed!" ); + return false; + } + + setRenderTransform(mObjToWorld); + + // Register for the resource change signal. + ResourceManager::get().getChangedSignal().notify( this, &TSStatic::_onResourceChanged ); + + addToScene(); + + return true; +} + +bool TSStatic::_createShape() +{ + // Cleanup before we create. + mCollisionDetails.clear(); + mLOSDetails.clear(); + SAFE_DELETE( mPhysicsRep ); + SAFE_DELETE( mShapeInstance ); + mShape = NULL; + + if (!mShapeName || mShapeName[0] == '\0') + { + Con::errorf( "TSStatic::_createShape() - No shape name!" ); + return false; + } + + mShapeHash = _StringTable::hashString(mShapeName); + + mShape = ResourceManager::get().load(mShapeName); + if ( bool(mShape) == false ) + { + Con::errorf( "TSStatic::_createShape() - Unable to load shape: %s", mShapeName ); + return false; + } + + if ( isClientObject() && + !mShape->preloadMaterialList(mShape.getPath()) && + NetConnection::filesWereDownloaded() ) + return false; + + mObjBox = mShape->bounds; + resetWorldBox(); + + mShapeInstance = new TSShapeInstance( mShape, isClientObject() ); + + prepCollision(); + + // Find the "ambient" animation if it exists + S32 ambientSeq = mShape->findSequence("ambient"); + + if ( ambientSeq > -1 && !mAmbientThread ) + mAmbientThread = mShapeInstance->addThread(); + + if ( mAmbientThread ) + mShapeInstance->setSequence( mAmbientThread, ambientSeq, 0); + + return true; +} + +void TSStatic::prepCollision() +{ + // Let the client know that the collision was updated + setMaskBits( UpdateCollisionMask ); + + // Allow the ShapeInstance to prep its collision if it hasn't already + if ( mShapeInstance ) + mShapeInstance->prepCollision(); + + // Cleanup any old collision data + mCollisionDetails.clear(); + mLOSDetails.clear(); + + if ( mPhysicsRep ) + SAFE_DELETE( mPhysicsRep ); + + // Any detail or mesh that starts with these names is considered + // to be a "collision" mesh ("LOS" allows for specific line of sight meshes) + static const String sCollisionStr( "Collision" ); + static const String sLOSStr( "LOS" ); + + if ( mCollisionType == None || mCollisionType == Bounds ) + mConvexList->nukeList(); + else if ( mCollisionType == CollisionMesh ) + { + // Scan out the collision hulls... + for (U32 i = 0; i < mShape->details.size(); i++) + { + const String &name = mShape->names[mShape->details[i].nameIndex]; + + if (name.compare( sCollisionStr, sCollisionStr.length(), String::NoCase ) == 0) + { + mCollisionDetails.push_back(i); + + // The way LOS works is that it will check to see if there is a LOS detail that matches + // the the collision detail + 1 + MaxCollisionShapes (this variable name should change in + // the future). If it can't find a matching LOS it will simply use the collision instead. + // We check for any "unmatched" LOS's further down + mLOSDetails.increment(); + + S32 number = 0; + String::GetTrailingNumber( name, number ); + + String buff = String::ToString("LOS-%d", mAbs(number) + 1 + LOSOverrideOffset); + S32 los = mShape->findDetail(buff); + if (los == -1) + mLOSDetails.last() = i; + else + mLOSDetails.last() = los; + } + } + + // Snag any "unmatched" LOS details + for (U32 i = 0; i < mShape->details.size(); i++) + { + const String &name = mShape->names[mShape->details[i].nameIndex]; + + if (name.compare( sLOSStr, sLOSStr.length(), String::NoCase ) == 0) + { + // See if we already have this LOS + bool found = false; + for (U32 j = 0; j < mLOSDetails.size(); j++) + { + if (mLOSDetails[j] == i) + { + found = true; + break; + } + } + + if (!found) + mLOSDetails.push_back(i); + } + } + + // Since it looks odd to continue to collide against a mesh with + // no collision details under the current type be sure to nuke it + if ( mCollisionDetails.size() == 0 ) + mConvexList->nukeList(); + } + else // VisibleMesh + { + // With the VisbileMesh we do our collision against the highest LOD + // visible mesh + if ( mShape->details.size() > 0 ) + { + U32 highestDetail = 0; + F32 highestSize = mShape->details[0].size; + + for ( U32 i = 1; i < mShape->details.size(); i++ ) + { + // Make sure we skip any details that shouldn't be rendered + if ( mShape->details[i].size < 0 ) + continue; + + // Also make sure we skip any collision details with a size + const String &name = mShape->names[mShape->details[i].nameIndex]; + + if ( name.compare( sCollisionStr, sCollisionStr.length(), String::NoCase ) == 0 ) + continue; + + if ( name.compare( sLOSStr, sLOSStr.length(), String::NoCase ) == 0 ) + continue; + + // Otherwise test against the current highest size + if ( mShape->details[i].size > highestSize ) + { + highestDetail = i; + highestSize = mShape->details[i].size; + } + } + + mCollisionDetails.push_back( highestDetail ); + mLOSDetails.push_back( highestDetail ); + } + + // Since it looks odd to continue to collide against a mesh with + // no collision details under the current type be sure to nuke it + if ( mCollisionDetails.size() == 0 ) + mConvexList->nukeList(); + } + + if ( gPhysicsPlugin ) + mPhysicsRep = gPhysicsPlugin->createStatic( this ); +} + +void TSStatic::onRemove() +{ + SAFE_DELETE( mPhysicsRep ); + + mConvexList->nukeList(); + + removeFromScene(); + + // Remove the resource change signal. + ResourceManager::get().getChangedSignal().remove( this, &TSStatic::_onResourceChanged ); + + delete mShapeInstance; + mShapeInstance = NULL; + + mAmbientThread = NULL; + + Parent::onRemove(); +} + +void TSStatic::_onResourceChanged( ResourceBase::Signature sig, const Torque::Path &path ) +{ + if ( sig != Resource::signature() || + path != Path( mShapeName ) ) + return; + + _createShape(); +} + +void TSStatic::interpolateTick( F32 delta ) +{ +} + +void TSStatic::processTick() +{ + if ( mPlayAmbient && mAmbientThread && isServerObject() ) + mShapeInstance->advanceTime( getTickSec(), mAmbientThread ); +} + +void TSStatic::advanceTime( F32 timeDelta ) +{ + if ( mPlayAmbient && mAmbientThread ) + mShapeInstance->advanceTime( timeDelta, mAmbientThread ); +} + +//-------------------------------------------------------------------------- +bool TSStatic::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + + if ( !mShapeInstance || + ( !state->isObjectRendered(this) && !state->isReflectPass() ) ) + return false; + + Point3F cameraOffset; + getRenderTransform().getColumn(3,&cameraOffset); + cameraOffset -= state->getDiffuseCameraPosition(); + F32 dist = cameraOffset.len(); + if (dist < 0.01f) + dist = 0.01f; + + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + + mShapeInstance->setDetailFromDistance( state, dist * invScale ); + if ( mShapeInstance->getCurrentDetail() < 0 ) + return false; + + GFXTransformSaver saver; + + // Set up our TS render state. + TSRenderState rdata; + rdata.setSceneState( state ); + rdata.setFadeOverride( 1.0f ); + + LightManager *lm = gClientSceneGraph->getLightManager(); + if ( !state->isShadowPass() ) + lm->setupLights( this, getWorldSphere() ); + + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + GFX->setWorldMatrix( mat ); + + mShapeInstance->animate(); + mShapeInstance->render( rdata ); + + lm->resetLights(); + + if ( mRenderNormalScalar > 0 ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &TSStatic::_renderNormals ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void TSStatic::_renderNormals( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + PROFILE_SCOPE( TSStatic_RenderNormals ); + + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + GFX->multWorld( mat ); + + S32 dl = mShapeInstance->getCurrentDetail(); + mShapeInstance->renderDebugNormals( mRenderNormalScalar, dl ); +} + +void TSStatic::setScale( const VectorF &scale ) +{ + Parent::setScale( scale ); + + if ( mPhysicsRep ) + mPhysicsRep->setScale( scale ); +} + +void TSStatic::setTransform(const MatrixF & mat) +{ + Parent::setTransform(mat); + + if ( mPhysicsRep ) + mPhysicsRep->setTransform( mat ); + + // Since this is a static it's render transform changes 1 + // to 1 with it's collision transform... no interpolation. + setRenderTransform(mat); +} + +U32 TSStatic::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + stream->writeString(mShapeName); + + if ( stream->writeFlag( mask & UpdateCollisionMask ) ) + stream->write( (U32)mCollisionType ); + + stream->writeFlag(mAllowPlayerStep); + + stream->write( mRenderNormalScalar ); + + stream->writeFlag( mPlayAmbient ); + + if (mLightPlugin) + { + retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream); + } + + return retMask; +} + +void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + + MatrixF mat; + Point3F scale; + mathRead(*stream, &mat); + mathRead(*stream, &scale); + setScale(scale); + setTransform(mat); + + mShapeName = stream->readSTString(); + + if ( stream->readFlag() ) // UpdateCollisionMask + { + U32 collisionType = CollisionMesh; + + stream->read( &collisionType ); + + // Handle it if we have changed CollisionType's + if ( (CollisionType)collisionType != mCollisionType ) + { + mCollisionType = (CollisionType)collisionType; + + if ( isProperlyAdded() && mShapeInstance ) + prepCollision(); + } + } + + mAllowPlayerStep = stream->readFlag(); + + stream->read( &mRenderNormalScalar ); + + mPlayAmbient = stream->readFlag(); + + if (mLightPlugin) + { + mLightPlugin->unpackUpdate(this, con, stream); + } +} + +//---------------------------------------------------------------------------- +bool TSStatic::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + if ( mCollisionType == None ) + return false; + + if ( !mShapeInstance ) + return false; + + if ( mCollisionType == Bounds ) + { + F32 st, et, fst = 0.0f, fet = 1.0f; + F32 *bmin = &mObjBox.minExtents.x; + F32 *bmax = &mObjBox.maxExtents.x; + F32 const *si = &start.x; + F32 const *ei = &end.x; + + for ( U32 i = 0; i < 3; i++ ) + { + if (*si < *ei) + { + if ( *si > *bmax || *ei < *bmin ) + return false; + F32 di = *ei - *si; + st = ( *si < *bmin ) ? ( *bmin - *si ) / di : 0.0f; + et = ( *ei > *bmax ) ? ( *bmax - *si ) / di : 1.0f; + } + else + { + if ( *ei > *bmax || *si < *bmin ) + return false; + F32 di = *ei - *si; + st = ( *si > *bmax ) ? ( *bmax - *si ) / di : 0.0f; + et = ( *ei < *bmin ) ? ( *bmin - *si ) / di : 1.0f; + } + if ( st > fst ) fst = st; + if ( et < fet ) fet = et; + if ( fet < fst ) + return false; + bmin++; bmax++; + si++; ei++; + } + + info->normal = start - end; + info->normal.normalizeSafe(); + getTransform().mulV( info->normal ); + + info->t = fst; + info->object = this; + info->point.interpolate( start, end, fst ); + info->material = NULL; + return true; + } + else + { + RayInfo shortest = *info; + RayInfo localInfo; + shortest.t = 1e8f; + + for ( U32 i = 0; i < mLOSDetails.size(); i++ ) + { + mShapeInstance->animate( mLOSDetails[i] ); + + if ( mShapeInstance->castRayOpcode( mLOSDetails[i], start, end, &localInfo ) ) + { + localInfo.object = this; + + if (localInfo.t < shortest.t) + shortest = localInfo; + } + } + + if (shortest.object == this) + { + // Copy out the shortest time... + *info = shortest; + return true; + } + } + + return false; +} + +bool TSStatic::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info) +{ + if ( !mShapeInstance ) + return false; + + // Cast the ray against the currently visible detail + RayInfo localInfo; + bool res = mShapeInstance->castRayOpcode( mShapeInstance->getCurrentDetail(), start, end, &localInfo ); + + if ( res ) + { + *info = localInfo; + info->object = this; + return true; + } + + return false; +} + + +//---------------------------------------------------------------------------- +bool TSStatic::buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &) +{ + if ( mCollisionType == None ) + return false; + + if ( !mShapeInstance ) + return false; + + polyList->setTransform( &mObjToWorld, mObjScale ); + polyList->setObject( this ); + + if ( mCollisionType == Bounds ) + { + polyList->setObject( this ); + polyList->addBox( mObjBox ); + } + else // CollisionMesh || VisibleMesh + { + for (U32 i = 0; i < mCollisionDetails.size(); i++) + mShapeInstance->buildPolyListOpcode( mCollisionDetails[i], polyList, box ); + } + + return true; +} + +bool TSStatic::buildRenderedPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &) +{ + if ( mShapeInstance ) + { + polyList->setTransform( &mObjToWorld, mObjScale ); + polyList->setObject(this); + + // Add the currently rendered detail to the polyList + mShapeInstance->buildPolyListOpcode( mShapeInstance->getCurrentDetail(), polyList, box ); + + return true; + } + + return false; +} + +void TSStatic::buildConvex(const Box3F& box, Convex* convex) +{ + if ( mCollisionType == None ) + return; + + if ( mShapeInstance == NULL ) + return; + + // These should really come out of a pool + mConvexList->collectGarbage(); + + if ( mCollisionType == Bounds ) + { + // Just return a box convex for the entire shape... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) + { + if (itr->mConvex->getType() == BoxConvexType && + itr->mConvex->getObject() == this) + { + cc = itr->mConvex; + break; + } + } + if (cc) + return; + + // Create a new convex. + BoxConvex* cp = new BoxConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->init(this); + + mObjBox.getCenter(&cp->mCenter); + cp->mSize.x = mObjBox.len_x() / 2.0f; + cp->mSize.y = mObjBox.len_y() / 2.0f; + cp->mSize.z = mObjBox.len_z() / 2.0f; + } + else // CollisionMesh || VisibleMesh + { + TSStaticPolysoupConvex::smCurObject = this; + + for (U32 i = 0; i < mCollisionDetails.size(); i++) + mShapeInstance->buildConvexOpcode( mObjToWorld, mObjScale, mCollisionDetails[i], box, convex, mConvexList ); + + TSStaticPolysoupConvex::smCurObject = NULL; + } +} + +//-------------------------------------------------------------------------- +SceneObject* TSStaticPolysoupConvex::smCurObject = NULL; + +TSStaticPolysoupConvex::TSStaticPolysoupConvex() +: box( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ), + normal( 0.0f, 0.0f, 0.0f, 0.0f ), + idx( 0 ), + mesh( NULL ) +{ + mType = TSPolysoupConvexType; + + for ( U32 i = 0; i < 4; ++i ) + { + verts[i].set( 0.0f, 0.0f, 0.0f ); + } +} + +Point3F TSStaticPolysoupConvex::support(const VectorF& vec) const +{ + F32 bestDot = mDot( verts[0], vec ); + + const Point3F *bestP = &verts[0]; + for(S32 i=1; i<4; i++) + { + F32 newD = mDot(verts[i], vec); + if(newD > bestDot) + { + bestDot = newD; + bestP = &verts[i]; + } + } + + return *bestP; +} + +Box3F TSStaticPolysoupConvex::getBoundingBox() const +{ + Box3F wbox = box; + wbox.minExtents.convolve( mObject->getScale() ); + wbox.maxExtents.convolve( mObject->getScale() ); + mObject->getTransform().mul(wbox); + return wbox; +} + +Box3F TSStaticPolysoupConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const +{ + AssertISV(false, "TSStaticPolysoupConvex::getBoundingBox(m,p) - Not implemented. -- XEA"); + return box; +} + +void TSStaticPolysoupConvex::getPolyList(AbstractPolyList *list) +{ + // Transform the list into object space and set the pointer to the object + MatrixF i( mObject->getTransform() ); + Point3F iS( mObject->getScale() ); + list->setTransform(&i, iS); + list->setObject(mObject); + + // Add only the original collision triangle + S32 base = list->addPoint(verts[0]); + list->addPoint(verts[2]); + list->addPoint(verts[1]); + + list->begin(0, (U32)idx ^ (U32)mesh); + list->vertex(base + 2); + list->vertex(base + 1); + list->vertex(base + 0); + list->plane(base + 0, base + 1, base + 2); + list->end(); +} + +void TSStaticPolysoupConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf) +{ + cf->material = 0; + cf->object = mObject; + + // For a tetrahedron this is pretty easy... first + // convert everything into world space. + Point3F tverts[4]; + mat.mulP(verts[0], &tverts[0]); + mat.mulP(verts[1], &tverts[1]); + mat.mulP(verts[2], &tverts[2]); + mat.mulP(verts[3], &tverts[3]); + + // points... + S32 firstVert = cf->mVertexList.size(); + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[0]; + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[1]; + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[2]; + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[3]; + + // edges... + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+0; + cf->mEdgeList.last().vertex[1] = firstVert+1; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+1; + cf->mEdgeList.last().vertex[1] = firstVert+2; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+2; + cf->mEdgeList.last().vertex[1] = firstVert+0; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+3; + cf->mEdgeList.last().vertex[1] = firstVert+0; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+3; + cf->mEdgeList.last().vertex[1] = firstVert+1; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+3; + cf->mEdgeList.last().vertex[1] = firstVert+2; + + // triangles... + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[0]); + cf->mFaceList.last().vertex[0] = firstVert+2; + cf->mFaceList.last().vertex[1] = firstVert+1; + cf->mFaceList.last().vertex[2] = firstVert+0; + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[1], tverts[0], tverts[3]); + cf->mFaceList.last().vertex[0] = firstVert+1; + cf->mFaceList.last().vertex[1] = firstVert+0; + cf->mFaceList.last().vertex[2] = firstVert+3; + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[3]); + cf->mFaceList.last().vertex[0] = firstVert+2; + cf->mFaceList.last().vertex[1] = firstVert+1; + cf->mFaceList.last().vertex[2] = firstVert+3; + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[0], tverts[2], tverts[3]); + cf->mFaceList.last().vertex[0] = firstVert+0; + cf->mFaceList.last().vertex[1] = firstVert+2; + cf->mFaceList.last().vertex[2] = firstVert+3; + + // All done! +} + +//------------------------------------------------------------------------ +//These functions are duplicated in tsStatic, shapeBase, and interiorInstance. +//They each function a little differently; but achieve the same purpose of gathering +//target names/counts without polluting simObject. + +ConsoleMethod( TSStatic, getTargetName, const char*, 3, 3, "") +{ + S32 idx = dAtoi(argv[2]); + + TSStatic *obj = dynamic_cast< TSStatic* > ( object ); + if(obj) + return obj->getShape()->getTargetName(idx); + + return ""; +} + +ConsoleMethod( TSStatic, getTargetCount, S32, 2, 2, "") +{ + TSStatic *obj = dynamic_cast< TSStatic* > ( object ); + if(obj) + return obj->getShape()->getTargetCount(); + + return -1; +} + +// This method is able to change materials per map to with others. The material that is being replaced is being mapped to +// unmapped_mat as a part of this transition +ConsoleMethod( TSStatic, changeMaterial, void, 5, 5, "(mapTo, fromMaterial, ToMaterial)") +{ + TSStatic *obj = dynamic_cast< TSStatic* > ( object ); + + if(obj) + { + // Lets get ready to switch out materials + Material *oldMat = dynamic_cast(Sim::findObject(argv[3])); + Material *newMat = dynamic_cast(Sim::findObject(argv[4])); + + // if no valid new material, theres no reason for doing this + if( !newMat ) + return; + + // Lets remap the old material off, so as to let room for our current material room to claim its spot + if( oldMat ) + oldMat->mMapTo = String("unmapped_mat"); + + newMat->mMapTo = argv[2]; + + // Map the material in the in the matmgr + MATMGR->mapMaterial( argv[2], argv[4] ); + + U32 i = 0; + // Replace instances with the new material being traded in. Lets make sure that we only + // target the specific targets per inst, this is actually doing more than we thought + for (; i < obj->getShape()->materialList->getMaterialNameList().size(); i++) + { + if( String(argv[2]) == obj->getShape()->materialList->getMaterialName(i)) + { + delete [] obj->getShape()->materialList->mMatInstList[i]; + obj->getShape()->materialList->mMatInstList[i] = newMat->createMatInstance(); + break; + } + } + + // Finish up preparing the material instances for rendering + const GFXVertexFormat *flags = getGFXVertexFormat(); + FeatureSet features = MATMGR->getDefaultFeatures(); + obj->getShape()->materialList->getMaterialInst(i)->init( features, flags ); + } +} + +ConsoleMethod( TSStatic, getModelFile, const char *, 2, 2, "getModelFile( String )") +{ + return object->getShapeFileName(); +} \ No newline at end of file diff --git a/T3D/tsStatic.h b/T3D/tsStatic.h new file mode 100644 index 0000000..9d061a8 --- /dev/null +++ b/T3D/tsStatic.h @@ -0,0 +1,180 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSSTATIC_H_ +#define _TSSTATIC_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _ITICKABLE_H_ +#include "core/iTickable.h" +#endif + +class TSShapeInstance; +class TSThread; +class TSStatic; +class PhysicsStatic; +struct ObjectRenderInst; + +//-------------------------------------------------------------------------- +class TSStaticPolysoupConvex : public Convex +{ + typedef Convex Parent; + friend class TSMesh; + +public: + TSStaticPolysoupConvex(); + ~TSStaticPolysoupConvex() {}; + +public: + Box3F box; + Point3F verts[4]; + PlaneF normal; + S32 idx; + TSMesh *mesh; + + static SceneObject* smCurObject; + +public: + + // Returns the bounding box in world coordinates + Box3F getBoundingBox() const; + Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const; + + void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + + // This returns a list of convex faces to collide against + void getPolyList(AbstractPolyList* list); + + // This returns the furthest point from the input vector + Point3F support(const VectorF& v) const; +}; + +//-------------------------------------------------------------------------- +class TSStatic : public SceneObject, public ITickable +{ + typedef SceneObject Parent; + + static U32 smUniqueIdentifier; + + enum Constants + { + LOSOverrideOffset = 8 + }; + + enum MaskBits + { + AdvancedStaticOptionsMask = Parent::NextFreeMask, + UpdateCollisionMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + +public: + + enum CollisionType + { + None = 0, + Bounds = 1, + CollisionMesh = 2, + VisibleMesh = 3 + }; + + protected: + bool onAdd(); + void onRemove(); + + // Collision + void prepCollision(); + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info); + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF& sphere); + bool buildRenderedPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + void buildConvex(const Box3F& box, Convex* convex); + + bool _createShape(); + + void _renderNormals( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + void _onResourceChanged( ResourceBase::Signature sig, const Torque::Path &path ); + + // iTickable interface + virtual void interpolateTick( F32 delta ); + virtual void processTick(); + virtual void advanceTime( F32 timeDelta ); + + protected: + + Convex* mConvexList; + + StringTableEntry mShapeName; + U32 mShapeHash; + Resource mShape; + TSShapeInstance* mShapeInstance; + + bool mPlayAmbient; + TSThread* mAmbientThread; + + CollisionType mCollisionType; + + bool mAllowPlayerStep; + + PhysicsStatic *mPhysicsRep; + + F32 mRenderNormalScalar; + +public: + Vector mCollisionDetails; + Vector mLOSDetails; + + // Rendering + protected: + + bool prepRenderImage ( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false); + void renderObject ( SceneState *state); + + public: + TSStatic(); + ~TSStatic(); + + DECLARE_CONOBJECT(TSStatic); + static void initPersistFields(); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + void setTransform( const MatrixF &mat ); + void setScale( const VectorF &scale ); + + void inspectPostApply(); + + CollisionType getCollisionType() { return mCollisionType; } + + bool allowPlayerStep() const { return mAllowPlayerStep; } + + Resource getShape() const { return mShape; } + StringTableEntry getShapeFileName() { return mShapeName; } + + TSShapeInstance* getShapeInstance() const { return mShapeInstance; } + + const Vector& getCollisionDetails() const { return mCollisionDetails; } + + const Vector& getLOSDetails() const { return mLOSDetails; } + +}; + +#endif // _H_TSSTATIC + diff --git a/T3D/vehicles/flyingVehicle.cpp b/T3D/vehicles/flyingVehicle.cpp new file mode 100644 index 0000000..4650a98 --- /dev/null +++ b/T3D/vehicles/flyingVehicle.cpp @@ -0,0 +1,726 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/vehicles/flyingVehicle.h" + +#include "app/game.h" +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "collision/clippedPolyList.h" +#include "collision/planeExtractor.h" +#include "core/stream/bitStream.h" +#include "core/dnet.h" +#include "T3D/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "T3D/fx/particleEmitter.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" +#include "T3D/missionArea.h" + +//---------------------------------------------------------------------------- + +const static U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + WaterObjectType | PlayerObjectType | + StaticShapeObjectType | VehicleObjectType | + VehicleBlockerObjectType | StaticTSObjectType); +static U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +static U32 sClientCollisionMask = sCollisionMoveMask; + +static F32 sFlyingVehicleGravity = -20.0f; + +// +const char* FlyingVehicle::sJetSequence[FlyingVehicle::JetAnimCount] = +{ + "activateBack", + "maintainBack", + "activateBot", + "maintainBot", +}; + +const char* FlyingVehicleData::sJetNode[FlyingVehicleData::MaxJetNodes] = +{ + "JetNozzle0", // Thrust Forward + "JetNozzle1", + "JetNozzleX", // Thrust Backward + "JetNozzleY", + "JetNozzle2", // Thrust Downward + "JetNozzle3", + "contrail0", // Trail + "contrail1", + "contrail2", + "contrail3", +}; + +// Convert thrust direction into nodes & emitters +FlyingVehicle::JetActivation FlyingVehicle::sJetActivation[NumThrustDirections] = { + { FlyingVehicleData::ForwardJetNode, FlyingVehicleData::ForwardJetEmitter }, + { FlyingVehicleData::BackwardJetNode, FlyingVehicleData::BackwardJetEmitter }, + { FlyingVehicleData::DownwardJetNode, FlyingVehicleData::DownwardJetEmitter }, +}; + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(FlyingVehicleData); + +FlyingVehicleData::FlyingVehicleData() +{ + maneuveringForce = 0; + horizontalSurfaceForce = 0; + verticalSurfaceForce = 0; + autoInputDamping = 1; + steeringForce = 1; + steeringRollForce = 1; + rollForce = 1; + autoAngularForce = 0; + rotationalDrag = 0; + autoLinearForce = 0; + maxAutoSpeed = 0; + hoverHeight = 2; + createHoverHeight = 2; + maxSteeringAngle = M_PI_F; + minTrailSpeed = 1; + maxSpeed = 100; + + for (S32 k = 0; k < MaxJetNodes; k++) + jetNode[k] = -1; + + for (S32 j = 0; j < MaxJetEmitters; j++) + jetEmitter[j] = 0; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = 0; + + vertThrustMultiple = 1.0; +} + +bool FlyingVehicleData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + TSShapeInstance* si = new TSShapeInstance(mShape, false); + + // Resolve objects transmitted from server + if (!server) { + for (S32 i = 0; i < MaxSounds; i++) + if (sound[i]) + Sim::findObject(SimObjectId(sound[i]),sound[i]); + + for (S32 j = 0; j < MaxJetEmitters; j++) + if (jetEmitter[j]) + Sim::findObject(SimObjectId(jetEmitter[j]),jetEmitter[j]); + } + + // Extract collision planes from shape collision detail level + if (collisionDetails[0] != -1) + { + MatrixF imat(1); + PlaneExtractorPolyList polyList; + polyList.mPlaneList = &rigidBody.mPlaneList; + polyList.setTransform(&imat, Point3F(1,1,1)); + si->animate(collisionDetails[0]); + si->buildPolyList(&polyList,collisionDetails[0]); + } + + // Resolve jet nodes + for (S32 j = 0; j < MaxJetNodes; j++) + jetNode[j] = mShape->findNode(sJetNode[j]); + + // + maxSpeed = maneuveringForce / minDrag; + + delete si; + return true; +} + +void FlyingVehicleData::initPersistFields() +{ + addField("jetSound", TypeSFXProfilePtr, Offset(sound[JetSound], FlyingVehicleData)); + addField("engineSound", TypeSFXProfilePtr, Offset(sound[EngineSound], FlyingVehicleData)); + + addField("maneuveringForce", TypeF32, Offset(maneuveringForce, FlyingVehicleData)); + addField("horizontalSurfaceForce", TypeF32, Offset(horizontalSurfaceForce, FlyingVehicleData)); + addField("verticalSurfaceForce", TypeF32, Offset(verticalSurfaceForce, FlyingVehicleData)); + addField("autoInputDamping", TypeF32, Offset(autoInputDamping, FlyingVehicleData)); + addField("steeringForce", TypeF32, Offset(steeringForce, FlyingVehicleData)); + addField("steeringRollForce", TypeF32, Offset(steeringRollForce, FlyingVehicleData)); + addField("rollForce", TypeF32, Offset(rollForce, FlyingVehicleData)); + addField("autoAngularForce", TypeF32, Offset(autoAngularForce, FlyingVehicleData)); + addField("rotationalDrag", TypeF32, Offset(rotationalDrag, FlyingVehicleData)); + addField("autoLinearForce", TypeF32, Offset(autoLinearForce, FlyingVehicleData)); + addField("maxAutoSpeed", TypeF32, Offset(maxAutoSpeed, FlyingVehicleData)); + addField("hoverHeight", TypeF32, Offset(hoverHeight, FlyingVehicleData)); + addField("createHoverHeight", TypeF32, Offset(createHoverHeight, FlyingVehicleData)); + + addField("forwardJetEmitter",TypeParticleEmitterDataPtr, Offset(jetEmitter[ForwardJetEmitter], FlyingVehicleData)); + addField("backwardJetEmitter",TypeParticleEmitterDataPtr, Offset(jetEmitter[BackwardJetEmitter], FlyingVehicleData)); + addField("downJetEmitter",TypeParticleEmitterDataPtr, Offset(jetEmitter[DownwardJetEmitter], FlyingVehicleData)); + addField("trailEmitter",TypeParticleEmitterDataPtr, Offset(jetEmitter[TrailEmitter], FlyingVehicleData)); + addField("minTrailSpeed", TypeF32, Offset(minTrailSpeed, FlyingVehicleData)); + addField("vertThrustMultiple", TypeF32, Offset(vertThrustMultiple, FlyingVehicleData)); + + Parent::initPersistFields(); +} + +void FlyingVehicleData::packData(BitStream* stream) +{ + Parent::packData(stream); + + for (S32 i = 0; i < MaxSounds; i++) + { + if (stream->writeFlag(sound[i])) + { + SimObjectId writtenId = packed ? SimObjectId(sound[i]) : sound[i]->getId(); + stream->writeRangedU32(writtenId, DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + } + + for (S32 j = 0; j < MaxJetEmitters; j++) + { + if (stream->writeFlag(jetEmitter[j])) + { + SimObjectId writtenId = packed ? SimObjectId(jetEmitter[j]) : jetEmitter[j]->getId(); + stream->writeRangedU32(writtenId, DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + } + + stream->write(maneuveringForce); + stream->write(horizontalSurfaceForce); + stream->write(verticalSurfaceForce); + stream->write(autoInputDamping); + stream->write(steeringForce); + stream->write(steeringRollForce); + stream->write(rollForce); + stream->write(autoAngularForce); + stream->write(rotationalDrag); + stream->write(autoLinearForce); + stream->write(maxAutoSpeed); + stream->write(hoverHeight); + stream->write(createHoverHeight); + stream->write(minTrailSpeed); + stream->write(vertThrustMultiple); +} + +void FlyingVehicleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + for (S32 i = 0; i < MaxSounds; i++) { + sound[i] = NULL; + if (stream->readFlag()) + sound[i] = (SFXProfile*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + for (S32 j = 0; j < MaxJetEmitters; j++) { + jetEmitter[j] = NULL; + if (stream->readFlag()) + jetEmitter[j] = (ParticleEmitterData*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + stream->read(&maneuveringForce); + stream->read(&horizontalSurfaceForce); + stream->read(&verticalSurfaceForce); + stream->read(&autoInputDamping); + stream->read(&steeringForce); + stream->read(&steeringRollForce); + stream->read(&rollForce); + stream->read(&autoAngularForce); + stream->read(&rotationalDrag); + stream->read(&autoLinearForce); + stream->read(&maxAutoSpeed); + stream->read(&hoverHeight); + stream->read(&createHoverHeight); + stream->read(&minTrailSpeed); + stream->read(&vertThrustMultiple); +} + + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(FlyingVehicle); + +FlyingVehicle::FlyingVehicle() +{ + mSteering.set(0,0); + mThrottle = 0; + mJetting = false; + + mJetSound = 0; + mEngineSound = 0; + + mBackMaintainOn = false; + mBottomMaintainOn = false; + createHeightOn = false; + + for (S32 i = 0; i < JetAnimCount; i++) + mJetThread[i] = 0; +} + +FlyingVehicle::~FlyingVehicle() +{ +} + + +//---------------------------------------------------------------------------- + +bool FlyingVehicle::onAdd() +{ + if(!Parent::onAdd()) + return false; + + addToScene(); + + if (isServerObject()) + scriptOnAdd(); + return true; +} + +bool FlyingVehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + // Sounds + if ( isGhost() ) + { + // Create the sounds ahead of time. This reduces runtime + // costs and makes the system easier to understand. + + SFX_DELETE( mJetSound ); + SFX_DELETE( mEngineSound ); + + if ( mDataBlock->sound[FlyingVehicleData::EngineSound] ) + mEngineSound = SFX->createSource( mDataBlock->sound[FlyingVehicleData::EngineSound], &getTransform() ); + + if ( mDataBlock->sound[FlyingVehicleData::JetSound] ) + mJetSound = SFX->createSource( mDataBlock->sound[FlyingVehicleData::JetSound], &getTransform() ); + } + + // Jet Sequences + for (S32 i = 0; i < JetAnimCount; i++) { + TSShape const* shape = mShapeInstance->getShape(); + mJetSeq[i] = shape->findSequence(sJetSequence[i]); + if (mJetSeq[i] != -1) { + if (i == BackActivate || i == BottomActivate) { + mJetThread[i] = mShapeInstance->addThread(); + mShapeInstance->setSequence(mJetThread[i],mJetSeq[i],0); + mShapeInstance->setTimeScale(mJetThread[i],0); + } + } + else + mJetThread[i] = 0; + } + + scriptOnNewDataBlock(); + return true; +} + +void FlyingVehicle::onRemove() +{ + SFX_DELETE( mJetSound ); + SFX_DELETE( mEngineSound ); + + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- + +void FlyingVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + updateEngineSound(1); + updateJet(dt); +} + + +//---------------------------------------------------------------------------- + +void FlyingVehicle::updateMove(const Move* move) +{ + PROFILE_SCOPE( FlyingVehicle_UpdateMove ); + + Parent::updateMove(move); + + if (move == &NullMove) + mSteering.set(0,0); + + F32 speed = mRigid.linVelocity.len(); + if (speed < mDataBlock->maxAutoSpeed) + mSteering *= mDataBlock->autoInputDamping; + + // Check the mission area to get the factor for the flight ceiling + MissionArea * obj = dynamic_cast(Sim::findObject("GlobalMissionArea")); + mCeilingFactor = 1.0f; + if (obj != NULL) + { + F32 flightCeiling = obj->getFlightCeiling(); + F32 ceilingRange = obj->getFlightCeilingRange(); + + if (mRigid.linPosition.z > flightCeiling) + { + // Thrust starts to fade at the ceiling, and is 0 at ceil + range + if (ceilingRange == 0) + { + mCeilingFactor = 0; + } + else + { + mCeilingFactor = 1.0f - ((mRigid.linPosition.z - flightCeiling) / (flightCeiling + ceilingRange)); + if (mCeilingFactor < 0.0f) + mCeilingFactor = 0.0f; + } + } + } + + mThrust.x = move->x; + mThrust.y = move->y; + + if (mThrust.y != 0.0f) + if (mThrust.y > 0) + mThrustDirection = ThrustForward; + else + mThrustDirection = ThrustBackward; + else + mThrustDirection = ThrustDown; + + if (mCeilingFactor != 1.0f) + mJetting = false; +} + + +//---------------------------------------------------------------------------- + +void FlyingVehicle::updateForces(F32 /*dt*/) +{ + PROFILE_SCOPE( FlyingVehicle_UpdateForces ); + + MatrixF currPosMat; + mRigid.getTransform(&currPosMat); + mRigid.atRest = false; + + Point3F massCenter; + currPosMat.mulP(mDataBlock->massCenter,&massCenter); + + Point3F xv,yv,zv; + currPosMat.getColumn(0,&xv); + currPosMat.getColumn(1,&yv); + currPosMat.getColumn(2,&zv); + F32 speed = mRigid.linVelocity.len(); + + Point3F force = Point3F(0, 0, sFlyingVehicleGravity * mRigid.mass * mGravityMod); + Point3F torque = Point3F(0, 0, 0); + + // Drag at any speed + force -= mRigid.linVelocity * mDataBlock->minDrag; + torque -= mRigid.angMomentum * mDataBlock->rotationalDrag; + + // Auto-stop at low speeds + if (speed < mDataBlock->maxAutoSpeed) { + F32 autoScale = 1 - speed / mDataBlock->maxAutoSpeed; + + // Gyroscope + F32 gf = mDataBlock->autoAngularForce * autoScale; + torque -= xv * gf * mDot(yv,Point3F(0,0,1)); + + // Manuevering jets + F32 sf = mDataBlock->autoLinearForce * autoScale; + force -= yv * sf * mDot(yv, mRigid.linVelocity); + force -= xv * sf * mDot(xv, mRigid.linVelocity); + } + + // Hovering Jet + F32 vf = -sFlyingVehicleGravity * mRigid.mass * mGravityMod; + F32 h = getHeight(); + if (h <= 1) { + if (h > 0) { + vf -= vf * h * 0.1; + } else { + vf += mDataBlock->jetForce * -h; + } + } + force += zv * vf; + + // Damping "surfaces" + force -= xv * mDot(xv,mRigid.linVelocity) * mDataBlock->horizontalSurfaceForce; + force -= zv * mDot(zv,mRigid.linVelocity) * mDataBlock->verticalSurfaceForce; + + // Turbo Jet + if (mJetting) { + if (mThrustDirection == ThrustForward) + force += yv * mDataBlock->jetForce * mCeilingFactor; + else if (mThrustDirection == ThrustBackward) + force -= yv * mDataBlock->jetForce * mCeilingFactor; + else + force += zv * mDataBlock->jetForce * mDataBlock->vertThrustMultiple * mCeilingFactor; + } + + // Maneuvering jets + force += yv * (mThrust.y * mDataBlock->maneuveringForce * mCeilingFactor); + force += xv * (mThrust.x * mDataBlock->maneuveringForce * mCeilingFactor); + + // Steering + Point2F steering; + steering.x = mSteering.x / mDataBlock->maxSteeringAngle; + steering.x *= mFabs(steering.x); + steering.y = mSteering.y / mDataBlock->maxSteeringAngle; + steering.y *= mFabs(steering.y); + torque -= xv * steering.y * mDataBlock->steeringForce; + torque -= zv * steering.x * mDataBlock->steeringForce; + + // Roll + torque += yv * steering.x * mDataBlock->steeringRollForce; + F32 ar = mDataBlock->autoAngularForce * mDot(xv,Point3F(0,0,1)); + ar -= mDataBlock->rollForce * mDot(xv, mRigid.linVelocity); + torque += yv * ar; + + // Add in force from physical zones... + force += mAppliedForce; + + // Container buoyancy & drag + force -= Point3F(0, 0, 1) * (mBuoyancy * sFlyingVehicleGravity * mRigid.mass * mGravityMod); + force -= mRigid.linVelocity * mDrag; + + // + mRigid.force = force; + mRigid.torque = torque; +} + + +//---------------------------------------------------------------------------- + +F32 FlyingVehicle::getHeight() +{ + Point3F sp,ep; + RayInfo collision; + F32 height = (createHeightOn) ? mDataBlock->createHoverHeight : mDataBlock->hoverHeight; + F32 r = 10 + height; + getTransform().getColumn(3, &sp); + ep.x = sp.x; + ep.y = sp.y; + ep.z = sp.z - r; + disableCollision(); + if( !mContainer->castRay(sp, ep, sClientCollisionMask, &collision) == true ) + collision.t = 1; + enableCollision(); + return (r * collision.t - height) / 10; +} + + +//---------------------------------------------------------------------------- +U32 FlyingVehicle::getCollisionMask() +{ + if (isServerObject()) + return sServerCollisionMask; + else + return sClientCollisionMask; +} + +//---------------------------------------------------------------------------- + +void FlyingVehicle::updateEngineSound(F32 level) +{ + if ( mEngineSound ) + { + mEngineSound->setTransform( getTransform() ); + mEngineSound->setVelocity( getVelocity() ); + mEngineSound->setVolume( level ); + } +} + +void FlyingVehicle::updateJet(F32 dt) +{ + // Thrust Animation threads + // Back + if (mJetSeq[BackActivate] >=0 ) { + if(!mBackMaintainOn || mThrustDirection != ThrustForward) { + if(mBackMaintainOn) { + mShapeInstance->setPos(mJetThread[BackActivate], 1); + mShapeInstance->destroyThread(mJetThread[BackMaintain]); + mBackMaintainOn = false; + } + mShapeInstance->setTimeScale(mJetThread[BackActivate], + (mThrustDirection == ThrustForward)? 1.0f : -1.0f); + mShapeInstance->advanceTime(dt,mJetThread[BackActivate]); + } + if(mJetSeq[BackMaintain] >= 0 && !mBackMaintainOn && + mShapeInstance->getPos(mJetThread[BackActivate]) >= 1.0) { + mShapeInstance->setPos(mJetThread[BackActivate], 0); + mShapeInstance->setTimeScale(mJetThread[BackActivate], 0); + mJetThread[BackMaintain] = mShapeInstance->addThread(); + mShapeInstance->setSequence(mJetThread[BackMaintain],mJetSeq[BackMaintain],0); + mShapeInstance->setTimeScale(mJetThread[BackMaintain],1); + mBackMaintainOn = true; + } + if(mBackMaintainOn) + mShapeInstance->advanceTime(dt,mJetThread[BackMaintain]); + } + + // Thrust Animation threads + // Bottom + if (mJetSeq[BottomActivate] >=0 ) { + if(!mBottomMaintainOn || mThrustDirection != ThrustDown || !mJetting) { + if(mBottomMaintainOn) { + mShapeInstance->setPos(mJetThread[BottomActivate], 1); + mShapeInstance->destroyThread(mJetThread[BottomMaintain]); + mBottomMaintainOn = false; + } + mShapeInstance->setTimeScale(mJetThread[BottomActivate], + (mThrustDirection == ThrustDown && mJetting)? 1.0f : -1.0f); + mShapeInstance->advanceTime(dt,mJetThread[BottomActivate]); + } + if(mJetSeq[BottomMaintain] >= 0 && !mBottomMaintainOn && + mShapeInstance->getPos(mJetThread[BottomActivate]) >= 1.0) { + mShapeInstance->setPos(mJetThread[BottomActivate], 0); + mShapeInstance->setTimeScale(mJetThread[BottomActivate], 0); + mJetThread[BottomMaintain] = mShapeInstance->addThread(); + mShapeInstance->setSequence(mJetThread[BottomMaintain],mJetSeq[BottomMaintain],0); + mShapeInstance->setTimeScale(mJetThread[BottomMaintain],1); + mBottomMaintainOn = true; + } + if(mBottomMaintainOn) + mShapeInstance->advanceTime(dt,mJetThread[BottomMaintain]); + } + + // Jet particles + for (S32 j = 0; j < NumThrustDirections; j++) { + JetActivation& jet = sJetActivation[j]; + updateEmitter(mJetting && j == mThrustDirection,dt,mDataBlock->jetEmitter[jet.emitter], + jet.node,FlyingVehicleData::MaxDirectionJets); + } + + // Trail jets + Point3F yv; + mObjToWorld.getColumn(1,&yv); + F32 speed = mFabs(mDot(yv,mRigid.linVelocity)); + F32 trail = 0; + if (speed > mDataBlock->minTrailSpeed) { + trail = dt; + if (speed < mDataBlock->maxSpeed) + trail *= (speed - mDataBlock->minTrailSpeed) / mDataBlock->maxSpeed; + } + updateEmitter(trail,trail,mDataBlock->jetEmitter[FlyingVehicleData::TrailEmitter], + FlyingVehicleData::TrailNode,FlyingVehicleData::MaxTrails); + + // Allocate/Deallocate voice on demand. + if ( !mJetSound ) + return; + + if ( !mJetting ) + mJetSound->stop(); + else + { + if ( !mJetSound->isPlaying() ) + mJetSound->play(); + + mJetSound->setTransform( getTransform() ); + mJetSound->setVelocity( getVelocity() ); + } +} + +//---------------------------------------------------------------------------- + +void FlyingVehicle::updateEmitter(bool active,F32 dt,ParticleEmitterData *emitter,S32 idx,S32 count) +{ + if (!emitter) + return; + for (S32 j = idx; j < idx + count; j++) + if (active) { + if (mDataBlock->jetNode[j] != -1) { + if (!bool(mJetEmitter[j])) { + mJetEmitter[j] = new ParticleEmitter; + mJetEmitter[j]->onNewDataBlock(emitter); + mJetEmitter[j]->registerObject(); + } + MatrixF mat; + Point3F pos,axis; + mat.mul(getRenderTransform(), + mShapeInstance->mNodeTransforms[mDataBlock->jetNode[j]]); + mat.getColumn(1,&axis); + mat.getColumn(3,&pos); + mJetEmitter[j]->emitParticles(pos,true,axis,getVelocity(),(U32)(dt * 1000)); + } + } + else { + for (S32 j = idx; j < idx + count; j++) + if (bool(mJetEmitter[j])) { + mJetEmitter[j]->deleteWhenEmpty(); + mJetEmitter[j] = 0; + } + } +} + + +//---------------------------------------------------------------------------- + +void FlyingVehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + Parent::writePacketData(connection, stream); +} + +void FlyingVehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + + setPosition(mRigid.linPosition,mRigid.angPosition); + mDelta.pos = mRigid.linPosition; + mDelta.rot[1] = mRigid.angPosition; +} + +U32 FlyingVehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + if(stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) + return retMask; + + stream->writeFlag(createHeightOn); + + stream->writeInt(mThrustDirection,NumThrustBits); + + return retMask; +} + +void FlyingVehicle::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + if(stream->readFlag()) + return; + + createHeightOn = stream->readFlag(); + + mThrustDirection = ThrustDirection(stream->readInt(NumThrustBits)); +} + +void FlyingVehicle::initPersistFields() +{ + Parent::initPersistFields(); +} + +ConsoleMethod( FlyingVehicle, useCreateHeight, void, 3, 3, "(bool enabled)" + "Should the vehicle temporarily use the create height specified in the datablock? This can help avoid problems with spawning.") +{ + object->useCreateHeight(dAtob(argv[2])); +} + +void FlyingVehicle::useCreateHeight(bool val) +{ + createHeightOn = val; + setMaskBits(HoverHeight); +} diff --git a/T3D/vehicles/flyingVehicle.h b/T3D/vehicles/flyingVehicle.h new file mode 100644 index 0000000..7f4ab99 --- /dev/null +++ b/T3D/vehicles/flyingVehicle.h @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FLYINGVEHICLE_H_ +#define _FLYINGVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "T3D/vehicles/vehicle.h" +#endif + +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; + + +//---------------------------------------------------------------------------- + +struct FlyingVehicleData: public VehicleData { + typedef VehicleData Parent; + + enum Sounds { + JetSound, + EngineSound, + MaxSounds, + }; + SFXProfile* sound[MaxSounds]; + + enum Jets { + // These enums index into a static name list. + ForwardJetEmitter, // Thrust forward + BackwardJetEmitter, // Thrust backward + DownwardJetEmitter, // Thrust down + TrailEmitter, // Contrail + MaxJetEmitters, + }; + ParticleEmitterData* jetEmitter[MaxJetEmitters]; + F32 minTrailSpeed; + + // + F32 maneuveringForce; + F32 horizontalSurfaceForce; + F32 verticalSurfaceForce; + F32 autoInputDamping; + F32 steeringForce; + F32 steeringRollForce; + F32 rollForce; + F32 autoAngularForce; + F32 rotationalDrag; + F32 maxAutoSpeed; + F32 autoLinearForce; + F32 hoverHeight; + F32 createHoverHeight; + + F32 vertThrustMultiple; + + // Initialized in preload + ClippedPolyList rigidBody; + S32 surfaceCount; + F32 maxSpeed; + + enum JetNodes { + // These enums index into a static name list. + ForwardJetNode, + ForwardJetNode1, + BackwardJetNode, + BackwardJetNode1, + DownwardJetNode, + DownwardJetNode1, + // + TrailNode, + TrailNode1, + TrailNode2, + TrailNode3, + // + MaxJetNodes, + MaxDirectionJets = 2, + ThrustJetStart = ForwardJetNode, + NumThrustJets = TrailNode, + MaxTrails = 4, + }; + static const char *sJetNode[MaxJetNodes]; + S32 jetNode[MaxJetNodes]; + + // + FlyingVehicleData(); + DECLARE_CONOBJECT(FlyingVehicleData); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + void packData(BitStream* stream); + void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class FlyingVehicle: public Vehicle +{ + typedef Vehicle Parent; + + FlyingVehicleData* mDataBlock; + + SFXSource* mJetSound; + SFXSource* mEngineSound; + + enum NetMaskBits { + InitMask = BIT(0), + HoverHeight = BIT(1) + }; + bool createHeightOn; + F32 mCeilingFactor; + + enum ThrustDirection { + // Enums index into sJetActivationTable + ThrustForward, + ThrustBackward, + ThrustDown, + NumThrustDirections, + NumThrustBits = 3 + }; + Point2F mThrust; + ThrustDirection mThrustDirection; + + // Jet Threads + enum Jets { + // These enums index into a static name list. + BackActivate, + BackMaintain, + BottomActivate, + BottomMaintain, + JetAnimCount + }; + static const char* sJetSequence[FlyingVehicle::JetAnimCount]; + TSThread* mJetThread[JetAnimCount]; + S32 mJetSeq[JetAnimCount]; + bool mBackMaintainOn; + bool mBottomMaintainOn; + // Jet Particles + struct JetActivation { + // Convert thrust direction into nodes & emitters + S32 node; + S32 emitter; + }; + static JetActivation sJetActivation[NumThrustDirections]; + SimObjectPtr mJetEmitter[FlyingVehicleData::MaxJetNodes]; + + // + bool onNewDataBlock(GameBaseData* dptr); + void updateMove(const Move *move); + void updateForces(F32); +// bool collideBody(const MatrixF& mat,Collision* info); + F32 getHeight(); + + // Client sounds & particles + void updateJet(F32 dt); + void updateEngineSound(F32 level); + void updateEmitter(bool active,F32 dt,ParticleEmitterData *emitter,S32 idx,S32 count); + + U32 getCollisionMask(); + public: + DECLARE_CONOBJECT(FlyingVehicle); + static void initPersistFields(); + + FlyingVehicle(); + ~FlyingVehicle(); + + bool onAdd(); + void onRemove(); + void advanceTime(F32 dt); + + void writePacketData(GameConnection *conn, BitStream *stream); + void readPacketData(GameConnection *conn, BitStream *stream); + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + void useCreateHeight(bool val); +}; + + +#endif diff --git a/T3D/vehicles/guiSpeedometer.cpp b/T3D/vehicles/guiSpeedometer.cpp new file mode 100644 index 0000000..51f2cb7 --- /dev/null +++ b/T3D/vehicles/guiSpeedometer.cpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/controls/guiBitmapCtrl.h" +#include "console/consoleTypes.h" +#include "T3D/gameConnection.h" +#include "T3D/vehicles/vehicle.h" +#include "gfx/primBuilder.h" + +//----------------------------------------------------------------------------- +/// A Speedometer control. +/// This gui displays the speed of the current Vehicle based +/// control object. This control only works if a server +/// connection exists and it's control object is a vehicle. If +/// either of these requirements is false, the control is not rendered. +class GuiSpeedometerHud : public GuiBitmapCtrl +{ + typedef GuiBitmapCtrl Parent; + + F32 mSpeed; ///< Current speed + F32 mMaxSpeed; ///< Max speed at max need pos + F32 mMaxAngle; ///< Max pos of needle + F32 mMinAngle; ///< Min pos of needle + Point2F mCenter; ///< Center of needle rotation + ColorF mColor; ///< Needle Color + F32 mNeedleLength; + F32 mNeedleWidth; + F32 mTailLength; + + GFXStateBlockRef mBlendSB; + +public: + GuiSpeedometerHud(); + + void onRender( Point2I, const RectI &); + static void initPersistFields(); + DECLARE_CONOBJECT( GuiSpeedometerHud ); + DECLARE_CATEGORY( "Gui Game" ); +}; + + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT( GuiSpeedometerHud ); + +GuiSpeedometerHud::GuiSpeedometerHud() +{ + mSpeed = 0; + mMaxSpeed = 100; + mMaxAngle = 0; + mMinAngle = 200; + mCenter.set(0,0); + mNeedleWidth = 3; + mNeedleLength = 10; + mTailLength = 5; + mColor.set(1,0,0,1); +} + +void GuiSpeedometerHud::initPersistFields() +{ + addGroup("Needle"); + addField("maxSpeed", TypeF32, Offset( mMaxSpeed, GuiSpeedometerHud ) ); + addField("minAngle", TypeF32, Offset( mMinAngle, GuiSpeedometerHud ) ); + addField("maxAngle", TypeF32, Offset( mMaxAngle, GuiSpeedometerHud ) ); + addField("color", TypeColorF, Offset( mColor, GuiSpeedometerHud ) ); + addField("center", TypePoint2F, Offset( mCenter, GuiSpeedometerHud ) ); + addField("length", TypeF32, Offset( mNeedleLength, GuiSpeedometerHud ) ); + addField("width", TypeF32, Offset( mNeedleWidth, GuiSpeedometerHud ) ); + addField("tail", TypeF32, Offset( mTailLength, GuiSpeedometerHud ) ); + endGroup("Needle"); + + Parent::initPersistFields(); +} + + +//----------------------------------------------------------------------------- +/** + Gui onRender method. + Renders a health bar with filled background and border. +*/ +void GuiSpeedometerHud::onRender(Point2I offset, const RectI &updateRect) +{ + // Must have a connection and player control object + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) + return; + Vehicle* control = dynamic_cast(conn->getControlObject()); + if (!control) + return; + + Parent::onRender(offset,updateRect); + + // Use the vehicle's velocity as it's speed... + mSpeed = control->getVelocity().len(); + if (mSpeed > mMaxSpeed) + mSpeed = mMaxSpeed; + + // Render the needle + GFX->pushWorldMatrix(); + Point2F center = mCenter; + if (mIsZero(center.x) && mIsZero(center.y)) + { + center.x = getExtent().x / 2.0f; + center.y = getExtent().y / 2.0f; + } + MatrixF newMat(1); + + newMat.setPosition(Point3F(getLeft() + center.x, getTop() + center.y, 0.0f)); + + F32 rotation = mMinAngle + (mMaxAngle - mMinAngle) * (mSpeed / mMaxSpeed); + AngAxisF newRot(Point3F(0.0f,0.0f,-1.0f), rotation); + + newRot.setMatrix(&newMat); + + if (mBlendSB.isNull()) + { + GFXStateBlockDesc desc; + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.samplersDefined = true; + desc.samplers[0].textureColorOp = GFXTOPDisable; + mBlendSB = GFX->createStateBlock(desc); + } + + GFX->setStateBlock(mBlendSB); + + GFX->setTexture(0, NULL); + + PrimBuild::begin(GFXLineStrip, 5); + PrimBuild::color4f(mColor.red, mColor.green, mColor.blue, mColor.alpha); + + PrimBuild::vertex2f(+mNeedleLength,-mNeedleWidth); + PrimBuild::vertex2f(+mNeedleLength,+mNeedleWidth); + PrimBuild::vertex2f(-mTailLength ,+mNeedleWidth); + PrimBuild::vertex2f(-mTailLength ,-mNeedleWidth); + + //// Get back to the start! + PrimBuild::vertex2f(+mNeedleLength,-mNeedleWidth); + + PrimBuild::end(); +} + + diff --git a/T3D/vehicles/hoverVehicle.cpp b/T3D/vehicles/hoverVehicle.cpp new file mode 100644 index 0000000..72e2411 --- /dev/null +++ b/T3D/vehicles/hoverVehicle.cpp @@ -0,0 +1,858 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/vehicles/hoverVehicle.h" + +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneState.h" +#include "collision/clippedPolyList.h" +#include "collision/planeExtractor.h" +#include "T3D/moveManager.h" +#include "ts/tsShapeInstance.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" +#include "T3D/fx/particleEmitter.h" +#include "math/mathIO.h" +#include "environment/waterBlock.h" + +IMPLEMENT_CO_DATABLOCK_V1(HoverVehicleData); +IMPLEMENT_CO_NETOBJECT_V1(HoverVehicle); + +namespace { + +const U32 sIntergrationsPerTick = 1; +const F32 sHoverVehicleGravity = -20; + +const U32 sCollisionMoveMask = (TerrainObjectType | InteriorObjectType | + PlayerObjectType | StaticTSObjectType | + StaticShapeObjectType | VehicleObjectType | + VehicleBlockerObjectType); + +const U32 sServerCollisionMask = sCollisionMoveMask; // ItemObjectType +const U32 sClientCollisionMask = sCollisionMoveMask; + +void nonFilter(SceneObject* object,void *key) +{ + Container::CallbackInfo* info = reinterpret_cast(key); + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + +} // namespace {} + +const char* HoverVehicle::sJetSequence[HoverVehicle::JetAnimCount] = +{ + "activateBack", + "maintainBack", +}; + +const char* HoverVehicleData::sJetNode[HoverVehicleData::MaxJetNodes] = +{ + "JetNozzle0", // Thrust Forward + "JetNozzle1", + "JetNozzleX", // Thrust Backward + "JetNozzleX", + "JetNozzle2", // Thrust Downward + "JetNozzle3", +}; + +// Convert thrust direction into nodes & emitters +HoverVehicle::JetActivation HoverVehicle::sJetActivation[NumThrustDirections] = { + { HoverVehicleData::ForwardJetNode, HoverVehicleData::ForwardJetEmitter }, + { HoverVehicleData::BackwardJetNode, HoverVehicleData::BackwardJetEmitter }, + { HoverVehicleData::DownwardJetNode, HoverVehicleData::DownwardJetEmitter }, +}; + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +HoverVehicleData::HoverVehicleData() +{ + dragForce = 0; + vertFactor = 0.25f; + floatingThrustFactor = 0.15f; + + mainThrustForce = 0; + reverseThrustForce = 0; + strafeThrustForce = 0; + turboFactor = 1.0f; + + stabLenMin = 0.5f; + stabLenMax = 2.0f; + stabSpringConstant = 30; + stabDampingConstant = 10; + + gyroDrag = 10; + normalForce = 30; + restorativeForce = 10; + steeringForce = 25; + rollForce = 2.5f; + pitchForce = 2.5f; + + dustTrailEmitter = NULL; + dustTrailID = 0; + dustTrailOffset.set( 0.0f, 0.0f, 0.0f ); + dustTrailFreqMod = 15.0f; + triggerTrailHeight = 2.5f; + + floatingGravMag = 1; + brakingForce = 0; + brakingActivationSpeed = 0; + + for (S32 k = 0; k < MaxJetNodes; k++) + jetNode[k] = -1; + + for (S32 j = 0; j < MaxJetEmitters; j++) + jetEmitter[j] = 0; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = 0; +} + +HoverVehicleData::~HoverVehicleData() +{ + +} + + +//-------------------------------------------------------------------------- +void HoverVehicleData::initPersistFields() +{ + addField("dragForce", TypeF32, Offset(dragForce, HoverVehicleData)); + addField("vertFactor", TypeF32, Offset(vertFactor, HoverVehicleData)); + addField("floatingThrustFactor", TypeF32, Offset(floatingThrustFactor, HoverVehicleData)); + addField("mainThrustForce", TypeF32, Offset(mainThrustForce, HoverVehicleData)); + addField("reverseThrustForce", TypeF32, Offset(reverseThrustForce, HoverVehicleData)); + addField("strafeThrustForce", TypeF32, Offset(strafeThrustForce, HoverVehicleData)); + addField("turboFactor", TypeF32, Offset(turboFactor, HoverVehicleData)); + + addField("stabLenMin", TypeF32, Offset(stabLenMin, HoverVehicleData)); + addField("stabLenMax", TypeF32, Offset(stabLenMax, HoverVehicleData)); + addField("stabSpringConstant", TypeF32, Offset(stabSpringConstant, HoverVehicleData)); + addField("stabDampingConstant", TypeF32, Offset(stabDampingConstant, HoverVehicleData)); + + addField("gyroDrag", TypeF32, Offset(gyroDrag, HoverVehicleData)); + addField("normalForce", TypeF32, Offset(normalForce, HoverVehicleData)); + addField("restorativeForce", TypeF32, Offset(restorativeForce, HoverVehicleData)); + addField("steeringForce", TypeF32, Offset(steeringForce, HoverVehicleData)); + addField("rollForce", TypeF32, Offset(rollForce, HoverVehicleData)); + addField("pitchForce", TypeF32, Offset(pitchForce, HoverVehicleData)); + + addField("jetSound", TypeSFXProfilePtr, Offset(sound[JetSound], HoverVehicleData)); + addField("engineSound", TypeSFXProfilePtr, Offset(sound[EngineSound], HoverVehicleData)); + addField("floatSound", TypeSFXProfilePtr, Offset(sound[FloatSound], HoverVehicleData)); + + addField("dustTrailEmitter", TypeParticleEmitterDataPtr, Offset(dustTrailEmitter, HoverVehicleData)); + addField("dustTrailOffset", TypePoint3F, Offset(dustTrailOffset, HoverVehicleData)); + addField("triggerTrailHeight", TypeF32, Offset(triggerTrailHeight, HoverVehicleData)); + addField("dustTrailFreqMod", TypeF32, Offset(dustTrailFreqMod, HoverVehicleData)); + + addField("floatingGravMag", TypeF32, Offset(floatingGravMag, HoverVehicleData)); + addField("brakingForce", TypeF32, Offset(brakingForce, HoverVehicleData)); + addField("brakingActivationSpeed", TypeF32, Offset(brakingActivationSpeed, HoverVehicleData)); + + addField("forwardJetEmitter",TypeParticleEmitterDataPtr, Offset(jetEmitter[ForwardJetEmitter], HoverVehicleData)); + + Parent::initPersistFields(); +} + + +//-------------------------------------------------------------------------- +bool HoverVehicleData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + + +bool HoverVehicleData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + if (dragForce <= 0.01f) { + Con::warnf("HoverVehicleData::preload: dragForce must be at least 0.01"); + dragForce = 0.01f; + } + if (vertFactor < 0.0f || vertFactor > 1.0f) { + Con::warnf("HoverVehicleData::preload: vert factor must be [0, 1]"); + vertFactor = vertFactor < 0.0f ? 0.0f : 1.0f; + } + if (floatingThrustFactor < 0.0f || floatingThrustFactor > 1.0f) { + Con::warnf("HoverVehicleData::preload: floatingThrustFactor must be [0, 1]"); + floatingThrustFactor = floatingThrustFactor < 0.0f ? 0.0f : 1.0f; + } + + maxThrustSpeed = (mainThrustForce + strafeThrustForce) / dragForce; + + massCenter = Point3F(0, 0, 0); + + // Resolve objects transmitted from server + if (!server) { + for (S32 i = 0; i < MaxSounds; i++) + if (sound[i]) + Sim::findObject(SimObjectId(sound[i]),sound[i]); + for (S32 j = 0; j < MaxJetEmitters; j++) + if (jetEmitter[j]) + Sim::findObject(SimObjectId(jetEmitter[j]),jetEmitter[j]); + } + + if( !dustTrailEmitter && dustTrailID != 0 ) + { + if( !Sim::findObject( dustTrailID, dustTrailEmitter ) ) + { + Con::errorf( ConsoleLogEntry::General, "HoverVehicleData::preload Invalid packet, bad datablockId(dustTrailEmitter): 0x%x", dustTrailID ); + } + } + // Resolve jet nodes + for (S32 j = 0; j < MaxJetNodes; j++) + jetNode[j] = mShape->findNode(sJetNode[j]); + + return true; +} + + +//-------------------------------------------------------------------------- +void HoverVehicleData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(dragForce); + stream->write(vertFactor); + stream->write(floatingThrustFactor); + stream->write(mainThrustForce); + stream->write(reverseThrustForce); + stream->write(strafeThrustForce); + stream->write(turboFactor); + stream->write(stabLenMin); + stream->write(stabLenMax); + stream->write(stabSpringConstant); + stream->write(stabDampingConstant); + stream->write(gyroDrag); + stream->write(normalForce); + stream->write(restorativeForce); + stream->write(steeringForce); + stream->write(rollForce); + stream->write(pitchForce); + mathWrite(*stream, dustTrailOffset); + stream->write(triggerTrailHeight); + stream->write(dustTrailFreqMod); + + for (S32 i = 0; i < MaxSounds; i++) + if (stream->writeFlag(sound[i])) + stream->writeRangedU32(packed? SimObjectId(sound[i]): + sound[i]->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + + for (S32 j = 0; j < MaxJetEmitters; j++) + { + if (stream->writeFlag(jetEmitter[j])) + { + SimObjectId writtenId = packed ? SimObjectId(jetEmitter[j]) : jetEmitter[j]->getId(); + stream->writeRangedU32(writtenId, DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + } + + if (stream->writeFlag( dustTrailEmitter )) + { + stream->writeRangedU32( dustTrailEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + stream->write(floatingGravMag); + stream->write(brakingForce); + stream->write(brakingActivationSpeed); +} + + +void HoverVehicleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&dragForce); + stream->read(&vertFactor); + stream->read(&floatingThrustFactor); + stream->read(&mainThrustForce); + stream->read(&reverseThrustForce); + stream->read(&strafeThrustForce); + stream->read(&turboFactor); + stream->read(&stabLenMin); + stream->read(&stabLenMax); + stream->read(&stabSpringConstant); + stream->read(&stabDampingConstant); + stream->read(&gyroDrag); + stream->read(&normalForce); + stream->read(&restorativeForce); + stream->read(&steeringForce); + stream->read(&rollForce); + stream->read(&pitchForce); + mathRead(*stream, &dustTrailOffset); + stream->read(&triggerTrailHeight); + stream->read(&dustTrailFreqMod); + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = stream->readFlag()? + (SFXProfile*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + + for (S32 j = 0; j < MaxJetEmitters; j++) { + jetEmitter[j] = NULL; + if (stream->readFlag()) + jetEmitter[j] = (ParticleEmitterData*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + if( stream->readFlag() ) + { + dustTrailID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + stream->read(&floatingGravMag); + stream->read(&brakingForce); + stream->read(&brakingActivationSpeed); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +HoverVehicle::HoverVehicle() +{ + // Todo: ScopeAlways? + mNetFlags.set(Ghostable); + + mFloating = false; + mForwardThrust = 0; + mReverseThrust = 0; + mLeftThrust = 0; + mRightThrust = 0; + + mJetSound = NULL; + mEngineSound = NULL; + mFloatSound = NULL; + + mDustTrailEmitter = NULL; + + mBackMaintainOn = false; + for (S32 i = 0; i < JetAnimCount; i++) + mJetThread[i] = 0; +} + +HoverVehicle::~HoverVehicle() +{ + // +} + +//-------------------------------------------------------------------------- +bool HoverVehicle::onAdd() +{ + if(!Parent::onAdd()) + return false; + + addToScene(); + + + if( !isServerObject() ) + { + if( mDataBlock->dustTrailEmitter ) + { + mDustTrailEmitter = new ParticleEmitter; + mDustTrailEmitter->onNewDataBlock( mDataBlock->dustTrailEmitter ); + if( !mDustTrailEmitter->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); + delete mDustTrailEmitter; + mDustTrailEmitter = NULL; + } + } + // Jet Sequences + for (S32 i = 0; i < JetAnimCount; i++) { + TSShape const* shape = mShapeInstance->getShape(); + mJetSeq[i] = shape->findSequence(sJetSequence[i]); + if (mJetSeq[i] != -1) { + if (i == BackActivate) { + mJetThread[i] = mShapeInstance->addThread(); + mShapeInstance->setSequence(mJetThread[i],mJetSeq[i],0); + mShapeInstance->setTimeScale(mJetThread[i],0); + } + } + else + mJetThread[i] = 0; + } + } + + + if (isServerObject()) + scriptOnAdd(); + + return true; +} + + +void HoverVehicle::onRemove() +{ + SFX_DELETE( mJetSound ); + SFX_DELETE( mEngineSound ); + SFX_DELETE( mFloatSound ); + + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +bool HoverVehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + if (isGhost()) + { + // Create the sounds ahead of time. This reduces runtime + // costs and makes the system easier to understand. + + SFX_DELETE( mEngineSound ); + SFX_DELETE( mFloatSound ); + SFX_DELETE( mJetSound ); + + if ( mDataBlock->sound[HoverVehicleData::EngineSound] ) + mEngineSound = SFX->createSource( mDataBlock->sound[HoverVehicleData::EngineSound], &getTransform() ); + + if ( !mDataBlock->sound[HoverVehicleData::FloatSound] ) + mFloatSound = SFX->createSource( mDataBlock->sound[HoverVehicleData::FloatSound], &getTransform() ); + + if ( mDataBlock->sound[HoverVehicleData::JetSound] ) + mJetSound = SFX->createSource( mDataBlock->sound[HoverVehicleData::JetSound], &getTransform() ); + } + + // Todo: Uncomment if this is a "leaf" class + scriptOnNewDataBlock(); + + return true; +} + + + +//-------------------------------------------------------------------------- +void HoverVehicle::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + // Update jetsound... + if ( mJetSound ) + { + if ( mJetting ) + { + if ( !mJetSound->isPlaying() ) + mJetSound->play(); + + mJetSound->setTransform( getTransform() ); + } + else + mJetSound->stop(); + } + + // Update engine sound... + if ( mEngineSound ) + { + if ( !mEngineSound->isPlaying() ) + mEngineSound->play(); + + mEngineSound->setTransform( getTransform() ); + + F32 denom = mDataBlock->mainThrustForce + mDataBlock->strafeThrustForce; + F32 factor = getMin(mThrustLevel, denom) / denom; + F32 vol = 0.25 + factor * 0.75; + mEngineSound->setVolume( vol ); + } + + // Are we floating? If so, start the floating sound... + if ( mFloatSound ) + { + if ( mFloating ) + { + if ( !mFloatSound->isPlaying() ) + mFloatSound->play(); + + mFloatSound->setTransform( getTransform() ); + } + else + mFloatSound->stop(); + } + + updateJet(dt); + updateDustTrail( dt ); +} + + +//-------------------------------------------------------------------------- + +U32 HoverVehicle::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // + stream->writeInt(mThrustDirection,NumThrustBits); + + return retMask; +} + +void HoverVehicle::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + mThrustDirection = ThrustDirection(stream->readInt(NumThrustBits)); + // +} + + +//-------------------------------------------------------------------------- +void HoverVehicle::updateMove(const Move* move) +{ + Parent::updateMove(move); + + mForwardThrust = mThrottle > 0.0f ? mThrottle : 0.0f; + mReverseThrust = mThrottle < 0.0f ? -mThrottle : 0.0f; + mLeftThrust = move->x < 0.0f ? -move->x : 0.0f; + mRightThrust = move->x > 0.0f ? move->x : 0.0f; + + mThrustDirection = (!move->y)? ThrustDown: (move->y > 0)? ThrustForward: ThrustBackward; +} + +F32 HoverVehicle::getBaseStabilizerLength() const +{ + F32 base = mDataBlock->stabLenMin; + F32 lengthDiff = mDataBlock->stabLenMax - mDataBlock->stabLenMin; + F32 velLength = mRigid.linVelocity.len(); + F32 minVel = getMin(velLength, mDataBlock->maxThrustSpeed); + F32 velDiff = mDataBlock->maxThrustSpeed - minVel; + F32 velRatio = velDiff / mDataBlock->maxThrustSpeed; + F32 inc = lengthDiff * ( 1.0 - velRatio ); + base += inc; + + return base; +} + + +struct StabPoint +{ + Point3F osPoint; // + Point3F wsPoint; // + F32 extension; + Point3F wsExtension; // + Point3F wsVelocity; // +}; + + +void HoverVehicle::updateForces(F32 /*dt*/) +{ + PROFILE_SCOPE( HoverVehicle_UpdateForces ); + + Point3F gravForce(0, 0, sHoverVehicleGravity * mRigid.mass * mGravityMod); + + MatrixF currTransform; + mRigid.getTransform(&currTransform); + mRigid.atRest = false; + + mThrustLevel = (mForwardThrust * mDataBlock->mainThrustForce + + mReverseThrust * mDataBlock->reverseThrustForce + + mLeftThrust * mDataBlock->strafeThrustForce + + mRightThrust * mDataBlock->strafeThrustForce); + + Point3F thrustForce = ((Point3F( 0, 1, 0) * (mForwardThrust * mDataBlock->mainThrustForce)) + + (Point3F( 0, -1, 0) * (mReverseThrust * mDataBlock->reverseThrustForce)) + + (Point3F(-1, 0, 0) * (mLeftThrust * mDataBlock->strafeThrustForce)) + + (Point3F( 1, 0, 0) * (mRightThrust * mDataBlock->strafeThrustForce))); + currTransform.mulV(thrustForce); + if (mJetting) + thrustForce *= mDataBlock->turboFactor; + + Point3F torque(0, 0, 0); + Point3F force(0, 0, 0); + + Point3F vel = mRigid.linVelocity; + F32 baseStabLen = getBaseStabilizerLength(); + Point3F stabExtend(0, 0, -baseStabLen); + currTransform.mulV(stabExtend); + + StabPoint stabPoints[2]; + stabPoints[0].osPoint = Point3F((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, + mObjBox.maxExtents.y, + (mObjBox.minExtents.z + mObjBox.maxExtents.z) * 0.5); + stabPoints[1].osPoint = Point3F((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5, + mObjBox.minExtents.y, + (mObjBox.minExtents.z + mObjBox.maxExtents.z) * 0.5); + U32 j, i; + for (i = 0; i < 2; i++) { + currTransform.mulP(stabPoints[i].osPoint, &stabPoints[i].wsPoint); + stabPoints[i].wsExtension = stabExtend; + stabPoints[i].extension = baseStabLen; + stabPoints[i].wsVelocity = mRigid.linVelocity; + } + + RayInfo rinfo; + + mFloating = true; + bool reallyFloating = true; + F32 compression[2] = { 0.0f, 0.0f }; + F32 normalMod[2] = { 0.0f, 0.0f }; + bool normalSet[2] = { false, false }; + Point3F normal[2]; + + for (j = 0; j < 2; j++) { + if (getContainer()->castRay(stabPoints[j].wsPoint, stabPoints[j].wsPoint + stabPoints[j].wsExtension * 2.0, + TerrainObjectType | + InteriorObjectType | WaterObjectType, &rinfo)) + { + reallyFloating = false; + + if (rinfo.t <= 0.5) { + // Ok, stab is in contact with the ground, let's calc the forces... + compression[j] = (1.0 - (rinfo.t * 2.0)) * baseStabLen; + } + normalSet[j] = true; + normalMod[j] = rinfo.t < 0.5 ? 1.0 : (1.0 - ((rinfo.t - 0.5) * 2.0)); + + normal[j] = rinfo.normal; + } + + if ( pointInWater( stabPoints[j].wsPoint ) ) + compression[j] = baseStabLen; + } + + for (j = 0; j < 2; j++) { + if (compression[j] != 0.0) { + mFloating = false; + + // Spring force and damping + Point3F springForce = -stabPoints[j].wsExtension; + springForce.normalize(); + springForce *= compression[j] * mDataBlock->stabSpringConstant; + + Point3F springDamping = -stabPoints[j].wsExtension; + springDamping.normalize(); + springDamping *= -getMin(mDot(springDamping, stabPoints[j].wsVelocity), 0.7f) * mDataBlock->stabDampingConstant; + + force += springForce + springDamping; + } + } + + // Gravity + if (reallyFloating == false) + force += gravForce; + else + force += gravForce * mDataBlock->floatingGravMag; + + // Braking + F32 vellen = mRigid.linVelocity.len(); + if (mThrottle == 0.0f && + mLeftThrust == 0.0f && + mRightThrust == 0.0f && + vellen != 0.0f && + vellen < mDataBlock->brakingActivationSpeed) + { + Point3F dir = mRigid.linVelocity; + dir.normalize(); + dir.neg(); + force += dir * mDataBlock->brakingForce; + } + + // Gyro Drag + torque = -mRigid.angMomentum * mDataBlock->gyroDrag; + + // Move to proper normal + Point3F sn, r; + currTransform.getColumn(2, &sn); + if (normalSet[0] || normalSet[1]) { + if (normalSet[0] && normalSet[1]) { + F32 dot = mDot(normal[0], normal[1]); + if (dot > 0.999) { + // Just pick the first normal. They're too close to call + if ((sn - normal[0]).lenSquared() > 0.00001) { + mCross(sn, normal[0], &r); + torque += r * mDataBlock->normalForce * normalMod[0]; + } + } else { + Point3F rotAxis; + mCross(normal[0], normal[1], &rotAxis); + rotAxis.normalize(); + + F32 angle = mAcos(dot) * (normalMod[0] / (normalMod[0] + normalMod[1])); + AngAxisF aa(rotAxis, angle); + QuatF q(aa); + MatrixF tempMat(true); + q.setMatrix(&tempMat); + Point3F newNormal; + tempMat.mulV(normal[1], &newNormal); + + if ((sn - newNormal).lenSquared() > 0.00001) { + mCross(sn, newNormal, &r); + torque += r * (mDataBlock->normalForce * ((normalMod[0] + normalMod[1]) * 0.5)); + } + } + } else { + Point3F useNormal; + F32 useMod; + if (normalSet[0]) { + useNormal = normal[0]; + useMod = normalMod[0]; + } else { + useNormal = normal[1]; + useMod = normalMod[1]; + } + + if ((sn - useNormal).lenSquared() > 0.00001) { + mCross(sn, useNormal, &r); + torque += r * mDataBlock->normalForce * useMod; + } + } + } else { + if ((sn - Point3F(0, 0, 1)).lenSquared() > 0.00001) { + mCross(sn, Point3F(0, 0, 1), &r); + torque += r * mDataBlock->restorativeForce; + } + } + + Point3F sn2; + currTransform.getColumn(0, &sn); + currTransform.getColumn(1, &sn2); + mCross(sn, sn2, &r); + r.normalize(); + torque -= r * (mSteering.x * mDataBlock->steeringForce); + + currTransform.getColumn(0, &sn); + currTransform.getColumn(2, &sn2); + mCross(sn, sn2, &r); + r.normalize(); + torque -= r * (mSteering.x * mDataBlock->rollForce); + + currTransform.getColumn(1, &sn); + currTransform.getColumn(2, &sn2); + mCross(sn, sn2, &r); + r.normalize(); + torque -= r * (mSteering.y * mDataBlock->pitchForce); + + // Apply drag + Point3F vDrag = mRigid.linVelocity; + if (!mFloating) { + vDrag.convolve(Point3F(1, 1, mDataBlock->vertFactor)); + } else { + vDrag.convolve(Point3F(0.25, 0.25, mDataBlock->vertFactor)); + } + force -= vDrag * mDataBlock->dragForce; + + force += mFloating ? thrustForce * mDataBlock->floatingThrustFactor : thrustForce; + + // Add in physical zone force + force += mAppliedForce; + + // Container buoyancy & drag + force += Point3F(0, 0,-mBuoyancy * sHoverVehicleGravity * mRigid.mass * mGravityMod); + force -= mRigid.linVelocity * mDrag; + torque -= mRigid.angMomentum * mDrag; + + mRigid.force = force; + mRigid.torque = torque; +} + + +//-------------------------------------------------------------------------- +U32 HoverVehicle::getCollisionMask() +{ + if (isServerObject()) + return sServerCollisionMask; + else + return sClientCollisionMask; +} + +void HoverVehicle::updateDustTrail( F32 dt ) +{ + // Check to see if we're moving. + + VectorF velocityVector = getVelocity(); + F32 velocity = velocityVector.len(); + + if( velocity > 2.0 ) + { + velocityVector.normalize(); + emitDust( mDustTrailEmitter, mDataBlock->triggerTrailHeight, mDataBlock->dustTrailOffset, + ( U32 )( dt * 1000 * ( velocity / mDataBlock->dustTrailFreqMod ) ), + velocityVector ); + } +} + +void HoverVehicle::updateJet(F32 dt) +{ + if (mJetThread[BackActivate] == NULL) + return; + + // Thrust Animation threads + // Back + if (mJetSeq[BackActivate] >=0 ) { + if (!mBackMaintainOn || mThrustDirection != ThrustForward) { + if (mBackMaintainOn) { + mShapeInstance->setPos(mJetThread[BackActivate], 1); + mShapeInstance->destroyThread(mJetThread[BackMaintain]); + mBackMaintainOn = false; + } + mShapeInstance->setTimeScale(mJetThread[BackActivate], + (mThrustDirection == ThrustForward)? 1.0f : -1.0f); + mShapeInstance->advanceTime(dt,mJetThread[BackActivate]); + } + } + + if (mJetSeq[BackMaintain] >= 0 && !mBackMaintainOn && + mShapeInstance->getPos(mJetThread[BackActivate]) >= 1.0f) + { + mShapeInstance->setPos(mJetThread[BackActivate], 0); + mShapeInstance->setTimeScale(mJetThread[BackActivate], 0); + mJetThread[BackMaintain] = mShapeInstance->addThread(); + mShapeInstance->setSequence(mJetThread[BackMaintain],mJetSeq[BackMaintain],0); + mShapeInstance->setTimeScale(mJetThread[BackMaintain],1); + mBackMaintainOn = true; + } + + if(mBackMaintainOn) + mShapeInstance->advanceTime(dt,mJetThread[BackMaintain]); + + // Jet particles + for (S32 j = 0; j < NumThrustDirections; j++) { + JetActivation& jet = sJetActivation[j]; + updateEmitter(mJetting && j == mThrustDirection,dt,mDataBlock->jetEmitter[jet.emitter], + jet.node,HoverVehicleData::MaxDirectionJets); + } +} + +void HoverVehicle::updateEmitter(bool active,F32 dt,ParticleEmitterData *emitter,S32 idx,S32 count) +{ + if (!emitter) + return; + for (S32 j = idx; j < idx + count; j++) + if (active) { + if (mDataBlock->jetNode[j] != -1) { + if (!bool(mJetEmitter[j])) { + mJetEmitter[j] = new ParticleEmitter; + mJetEmitter[j]->onNewDataBlock(emitter); + mJetEmitter[j]->registerObject(); + } + MatrixF mat; + Point3F pos,axis; + mat.mul(getRenderTransform(), + mShapeInstance->mNodeTransforms[mDataBlock->jetNode[j]]); + mat.getColumn(1,&axis); + mat.getColumn(3,&pos); + mJetEmitter[j]->emitParticles(pos,true,axis,getVelocity(),(U32)(dt * 1000.0f)); + } + } + else { + for (S32 j = idx; j < idx + count; j++) + if (bool(mJetEmitter[j])) { + mJetEmitter[j]->deleteWhenEmpty(); + mJetEmitter[j] = 0; + } + } +} diff --git a/T3D/vehicles/hoverVehicle.h b/T3D/vehicles/hoverVehicle.h new file mode 100644 index 0000000..80536e4 --- /dev/null +++ b/T3D/vehicles/hoverVehicle.h @@ -0,0 +1,196 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HOVERVEHICLE_H_ +#define _HOVERVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "T3D/vehicles/vehicle.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; + +// ------------------------------------------------------------------------- +class HoverVehicleData : public VehicleData +{ + typedef VehicleData Parent; + + protected: + bool onAdd(); + + //-------------------------------------- Console set variables + public: + enum Sounds { + JetSound, + EngineSound, + FloatSound, + MaxSounds + }; + SFXProfile* sound[MaxSounds]; + + enum Jets { + // These enums index into a static name list. + ForwardJetEmitter, // Thrust forward + BackwardJetEmitter, // Thrust backward + DownwardJetEmitter, // Thrust down + MaxJetEmitters, + }; + ParticleEmitterData* jetEmitter[MaxJetEmitters]; + + enum JetNodes { + // These enums index into a static name list. + ForwardJetNode, + ForwardJetNode1, + BackwardJetNode, + BackwardJetNode1, + DownwardJetNode, + DownwardJetNode1, + // + MaxJetNodes, + MaxDirectionJets = 2, + ThrustJetStart = ForwardJetNode, + MaxTrails = 4, + }; + static const char *sJetNode[MaxJetNodes]; + S32 jetNode[MaxJetNodes]; + + + F32 dragForce; + F32 vertFactor; + F32 floatingThrustFactor; + + F32 mainThrustForce; + F32 reverseThrustForce; + F32 strafeThrustForce; + F32 turboFactor; + + F32 stabLenMin; + F32 stabLenMax; + F32 stabSpringConstant; + F32 stabDampingConstant; + + F32 gyroDrag; + F32 normalForce; + F32 restorativeForce; + F32 steeringForce; + F32 rollForce; + F32 pitchForce; + + F32 floatingGravMag; + + F32 brakingForce; + F32 brakingActivationSpeed; + + ParticleEmitterData * dustTrailEmitter; + S32 dustTrailID; + Point3F dustTrailOffset; + F32 triggerTrailHeight; + F32 dustTrailFreqMod; + + //-------------------------------------- load set variables + public: + F32 maxThrustSpeed; + + public: + HoverVehicleData(); + ~HoverVehicleData(); + + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, String &errorStr); + + DECLARE_CONOBJECT(HoverVehicleData); + static void initPersistFields(); +}; + + +// ------------------------------------------------------------------------- +class HoverVehicle : public Vehicle +{ + typedef Vehicle Parent; + + private: + HoverVehicleData* mDataBlock; + ParticleEmitter * mDustTrailEmitter; + + protected: + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData *dptr); + void updateDustTrail( F32 dt ); + + // Vehicle overrides + protected: + void updateMove(const Move *move); + + // Physics + protected: + void updateForces(F32); + F32 getBaseStabilizerLength() const; + + bool mFloating; + F32 mThrustLevel; + + F32 mForwardThrust; + F32 mReverseThrust; + F32 mLeftThrust; + F32 mRightThrust; + + SFXSource* mJetSound; + SFXSource* mEngineSound; + SFXSource* mFloatSound; + + enum ThrustDirection { + // Enums index into sJetActivationTable + ThrustForward, + ThrustBackward, + ThrustDown, + NumThrustDirections, + NumThrustBits = 3 + }; + ThrustDirection mThrustDirection; + + // Jet Threads + enum Jets { + // These enums index into a static name list. + BackActivate, + BackMaintain, + JetAnimCount + }; + static const char* sJetSequence[HoverVehicle::JetAnimCount]; + TSThread* mJetThread[JetAnimCount]; + S32 mJetSeq[JetAnimCount]; + bool mBackMaintainOn; + + // Jet Particles + struct JetActivation { + // Convert thrust direction into nodes & emitters + S32 node; + S32 emitter; + }; + static JetActivation sJetActivation[NumThrustDirections]; + SimObjectPtr mJetEmitter[HoverVehicleData::MaxJetNodes]; + + U32 getCollisionMask(); + void updateJet(F32 dt); + void updateEmitter(bool active,F32 dt,ParticleEmitterData *emitter,S32 idx,S32 count); + public: + HoverVehicle(); + ~HoverVehicle(); + + // Time/Move Management + public: + void advanceTime(F32 dt); + + DECLARE_CONOBJECT(HoverVehicle); +// static void initPersistFields(); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); +}; + +#endif // _H_HOVERVEHICLE + diff --git a/T3D/vehicles/vehicle.cpp b/T3D/vehicles/vehicle.cpp new file mode 100644 index 0000000..80f4e23 --- /dev/null +++ b/T3D/vehicles/vehicle.cpp @@ -0,0 +1,1713 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/vehicles/vehicle.h" + +#include "math/mMath.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "collision/clippedPolyList.h" +#include "collision/planeExtractor.h" +#include "core/stream/bitStream.h" +#include "core/dnet.h" +#include "T3D/gameConnection.h" +#include "T3D/fx/cameraFXMgr.h" +#include "ts/tsShapeInstance.h" +#include "T3D/fx/particleEmitter.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" +#include "math/mathIO.h" +#include "sceneGraph/sceneState.h" +#include "T3D/trigger.h" +#include "T3D/item.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "materials/materialDefinition.h" + + +namespace { + +const U32 sMoveRetryCount = 3; + +// Client prediction +const S32 sMaxWarpTicks = 3; // Max warp duration in ticks +const S32 sMaxPredictionTicks = 30; // Number of ticks to predict +const F32 sVehicleGravity = -20; + +// Physics and collision constants +static F32 sRestTol = 0.5; // % of gravity energy to be at rest +static int sRestCount = 10; // Consecutive ticks before comming to rest + +} // namespace {} + +// Trigger objects that are not normally collided with. +static U32 sTriggerMask = ItemObjectType | + TriggerObjectType | + CorpseObjectType; + +IMPLEMENT_CONOBJECT(VehicleData); + +//---------------------------------------------------------------------------- + +VehicleData::VehicleData() +{ + shadowEnable = true; + shadowSize = 256; + shadowProjectionDistance = 14.0f; + + + body.friction = 0; + body.restitution = 1; + + minImpactSpeed = 25; + softImpactSpeed = 25; + hardImpactSpeed = 50; + minRollSpeed = 0; + maxSteeringAngle = M_PI_F/4.0f; // 45 deg. + + cameraRoll = true; + cameraLag = 0; + cameraDecay = 0; + cameraOffset = 0; + + minDrag = 0; + maxDrag = 0; + integration = 1; + collisionTol = 0.1f; + contactTol = 0.1f; + massCenter.set(0,0,0); + massBox.set(0,0,0); + + drag = 0.7f; + density = 4; + + jetForce = 500; + jetEnergyDrain = 0.8f; + minJetEnergy = 1; + + for (S32 i = 0; i < Body::MaxSounds; i++) + body.sound[i] = 0; + + dustEmitter = NULL; + dustID = 0; + triggerDustHeight = 3.0; + dustHeight = 1.0; + + dMemset( damageEmitterList, 0, sizeof( damageEmitterList ) ); + dMemset( damageEmitterIDList, 0, sizeof( damageEmitterIDList ) ); + dMemset( damageLevelTolerance, 0, sizeof( damageLevelTolerance ) ); + dMemset( splashEmitterList, 0, sizeof( splashEmitterList ) ); + dMemset( splashEmitterIDList, 0, sizeof( splashEmitterIDList ) ); + + numDmgEmitterAreas = 0; + + splashFreqMod = 300.0; + splashVelEpsilon = 0.50; + exitSplashSoundVel = 2.0; + softSplashSoundVel = 1.0; + medSplashSoundVel = 2.0; + hardSplashSoundVel = 3.0; + + dMemset(waterSound, 0, sizeof(waterSound)); + + collDamageThresholdVel = 20; + collDamageMultiplier = 0.05f; +} + + +//---------------------------------------------------------------------------- + +bool VehicleData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) { + for (S32 i = 0; i < Body::MaxSounds; i++) + if (body.sound[i]) + Sim::findObject(SimObjectId(body.sound[i]),body.sound[i]); + } + + if( !dustEmitter && dustID != 0 ) + { + if( !Sim::findObject( dustID, dustEmitter ) ) + { + Con::errorf( ConsoleLogEntry::General, "VehicleData::preload Invalid packet, bad datablockId(dustEmitter): 0x%x", dustID ); + } + } + + U32 i; + for( i=0; iwrite(body.restitution); + stream->write(body.friction); + for (i = 0; i < Body::MaxSounds; i++) + if (stream->writeFlag(body.sound[i])) + stream->writeRangedU32(packed? SimObjectId(body.sound[i]): + body.sound[i]->getId(),DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + stream->write(minImpactSpeed); + stream->write(softImpactSpeed); + stream->write(hardImpactSpeed); + stream->write(minRollSpeed); + stream->write(maxSteeringAngle); + + stream->write(maxDrag); + stream->write(minDrag); + stream->write(integration); + stream->write(collisionTol); + stream->write(contactTol); + mathWrite(*stream,massCenter); + mathWrite(*stream,massBox); + + stream->write(jetForce); + stream->write(jetEnergyDrain); + stream->write(minJetEnergy); + + stream->writeFlag(cameraRoll); + stream->write(cameraLag); + stream->write(cameraDecay); + stream->write(cameraOffset); + + stream->write( triggerDustHeight ); + stream->write( dustHeight ); + + stream->write( numDmgEmitterAreas ); + + stream->write(exitSplashSoundVel); + stream->write(softSplashSoundVel); + stream->write(medSplashSoundVel); + stream->write(hardSplashSoundVel); + + // write the water sound profiles + for(i = 0; i < MaxSounds; i++) + if(stream->writeFlag(waterSound[i])) + stream->writeRangedU32(waterSound[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->writeFlag( dustEmitter )) + { + stream->writeRangedU32( dustEmitter->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + + for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++) + { + if( stream->writeFlag( damageEmitterList[i] != NULL ) ) + { + stream->writeRangedU32( damageEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++) + { + if( stream->writeFlag( splashEmitterList[i] != NULL ) ) + { + stream->writeRangedU32( splashEmitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for (int j = 0; j < VC_NUM_DAMAGE_EMITTER_AREAS; j++) + { + stream->write( damageEmitterOffset[j].x ); + stream->write( damageEmitterOffset[j].y ); + stream->write( damageEmitterOffset[j].z ); + } + + for (int k = 0; k < VC_NUM_DAMAGE_LEVELS; k++) + { + stream->write( damageLevelTolerance[k] ); + } + + stream->write(splashFreqMod); + stream->write(splashVelEpsilon); + + stream->write(collDamageThresholdVel); + stream->write(collDamageMultiplier); +} + +void VehicleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&body.restitution); + stream->read(&body.friction); + S32 i; + for (i = 0; i < Body::MaxSounds; i++) { + body.sound[i] = NULL; + if (stream->readFlag()) + body.sound[i] = (SFXProfile*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + + stream->read(&minImpactSpeed); + stream->read(&softImpactSpeed); + stream->read(&hardImpactSpeed); + stream->read(&minRollSpeed); + stream->read(&maxSteeringAngle); + + stream->read(&maxDrag); + stream->read(&minDrag); + stream->read(&integration); + stream->read(&collisionTol); + stream->read(&contactTol); + mathRead(*stream,&massCenter); + mathRead(*stream,&massBox); + + stream->read(&jetForce); + stream->read(&jetEnergyDrain); + stream->read(&minJetEnergy); + + cameraRoll = stream->readFlag(); + stream->read(&cameraLag); + stream->read(&cameraDecay); + stream->read(&cameraOffset); + + stream->read( &triggerDustHeight ); + stream->read( &dustHeight ); + + stream->read( &numDmgEmitterAreas ); + + stream->read(&exitSplashSoundVel); + stream->read(&softSplashSoundVel); + stream->read(&medSplashSoundVel); + stream->read(&hardSplashSoundVel); + + // write the water sound profiles + for(i = 0; i < MaxSounds; i++) + if(stream->readFlag()) + { + U32 id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + waterSound[i] = dynamic_cast( Sim::findObject(id) ); + } + + if( stream->readFlag() ) + { + dustID = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + for (i = 0; i < VC_NUM_DAMAGE_EMITTERS; i++) + { + if( stream->readFlag() ) + { + damageEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for (i = 0; i < VC_NUM_SPLASH_EMITTERS; i++) + { + if( stream->readFlag() ) + { + splashEmitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + } + } + + for( int j=0; jread( &damageEmitterOffset[j].x ); + stream->read( &damageEmitterOffset[j].y ); + stream->read( &damageEmitterOffset[j].z ); + } + + for( int k=0; kread( &damageLevelTolerance[k] ); + } + + stream->read(&splashFreqMod); + stream->read(&splashVelEpsilon); + + stream->read(&collDamageThresholdVel); + stream->read(&collDamageMultiplier); +} + + +//---------------------------------------------------------------------------- + +void VehicleData::initPersistFields() +{ + addField("jetForce", TypeF32, Offset(jetForce, VehicleData)); + addField("jetEnergyDrain", TypeF32, Offset(jetEnergyDrain, VehicleData)); + addField("minJetEnergy", TypeF32, Offset(minJetEnergy, VehicleData)); + + addField("massCenter", TypePoint3F, Offset(massCenter, VehicleData)); + addField("massBox", TypePoint3F, Offset(massBox, VehicleData)); + addField("bodyRestitution", TypeF32, Offset(body.restitution, VehicleData)); + addField("bodyFriction", TypeF32, Offset(body.friction, VehicleData)); + addField("softImpactSound", TypeSFXProfilePtr, Offset(body.sound[Body::SoftImpactSound], VehicleData)); + addField("hardImpactSound", TypeSFXProfilePtr, Offset(body.sound[Body::HardImpactSound], VehicleData)); + + addField("minImpactSpeed", TypeF32, Offset(minImpactSpeed, VehicleData)); + addField("softImpactSpeed", TypeF32, Offset(softImpactSpeed, VehicleData)); + addField("hardImpactSpeed", TypeF32, Offset(hardImpactSpeed, VehicleData)); + addField("minRollSpeed", TypeF32, Offset(minRollSpeed, VehicleData)); + addField("maxSteeringAngle", TypeF32, Offset(maxSteeringAngle, VehicleData)); + + addField("maxDrag", TypeF32, Offset(maxDrag, VehicleData)); + addField("minDrag", TypeF32, Offset(minDrag, VehicleData)); + addField("integration", TypeS32, Offset(integration, VehicleData)); + addField("collisionTol", TypeF32, Offset(collisionTol, VehicleData)); + addField("contactTol", TypeF32, Offset(contactTol, VehicleData)); + + addField("cameraRoll", TypeBool, Offset(cameraRoll, VehicleData)); + addField("cameraLag", TypeF32, Offset(cameraLag, VehicleData)); + addField("cameraDecay", TypeF32, Offset(cameraDecay, VehicleData)); + addField("cameraOffset", TypeF32, Offset(cameraOffset, VehicleData)); + + addField("dustEmitter", TypeParticleEmitterDataPtr, Offset(dustEmitter, VehicleData)); + addField("triggerDustHeight", TypeF32, Offset(triggerDustHeight, VehicleData)); + addField("dustHeight", TypeF32, Offset(dustHeight, VehicleData)); + + addField("damageEmitter", TypeParticleEmitterDataPtr, Offset(damageEmitterList, VehicleData), VC_NUM_DAMAGE_EMITTERS); + addField("splashEmitter", TypeParticleEmitterDataPtr, Offset(splashEmitterList, VehicleData), VC_NUM_SPLASH_EMITTERS); + addField("damageEmitterOffset", TypePoint3F, Offset(damageEmitterOffset, VehicleData), VC_NUM_DAMAGE_EMITTER_AREAS); + addField("damageLevelTolerance", TypeF32, Offset(damageLevelTolerance, VehicleData), VC_NUM_DAMAGE_LEVELS); + addField("numDmgEmitterAreas", TypeF32, Offset(numDmgEmitterAreas, VehicleData)); + + addField("splashFreqMod", TypeF32, Offset(splashFreqMod, VehicleData)); + addField("splashVelEpsilon", TypeF32, Offset(splashVelEpsilon, VehicleData)); + + addField("exitSplashSoundVelocity", TypeF32, Offset(exitSplashSoundVel, VehicleData)); + addField("softSplashSoundVelocity", TypeF32, Offset(softSplashSoundVel, VehicleData)); + addField("mediumSplashSoundVelocity", TypeF32, Offset(medSplashSoundVel, VehicleData)); + addField("hardSplashSoundVelocity", TypeF32, Offset(hardSplashSoundVel, VehicleData)); + addField("exitingWater", TypeSFXProfilePtr, Offset(waterSound[ExitWater], VehicleData)); + addField("impactWaterEasy", TypeSFXProfilePtr, Offset(waterSound[ImpactSoft], VehicleData)); + addField("impactWaterMedium", TypeSFXProfilePtr, Offset(waterSound[ImpactMedium], VehicleData)); + addField("impactWaterHard", TypeSFXProfilePtr, Offset(waterSound[ImpactHard], VehicleData)); + addField("waterWakeSound", TypeSFXProfilePtr, Offset(waterSound[Wake], VehicleData)); + + addField("collDamageThresholdVel", TypeF32, Offset(collDamageThresholdVel, VehicleData)); + addField("collDamageMultiplier", TypeF32, Offset(collDamageMultiplier, VehicleData)); + + Parent::initPersistFields(); +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(Vehicle); + +Vehicle::Vehicle() +{ + mDataBlock = 0; + mTypeMask |= VehicleObjectType; + + mDelta.pos = Point3F(0,0,0); + mDelta.posVec = Point3F(0,0,0); + mDelta.warpTicks = mDelta.warpCount = 0; + mDelta.dt = 1; + mDelta.move = NullMove; + mPredictionCount = 0; + mDelta.cameraOffset.set(0,0,0); + mDelta.cameraVec.set(0,0,0); + mDelta.cameraRot.set(0,0,0); + mDelta.cameraRotVec.set(0,0,0); + + mRigid.linPosition.set(0, 0, 0); + mRigid.linVelocity.set(0, 0, 0); + mRigid.angPosition.identity(); + mRigid.angVelocity.set(0, 0, 0); + mRigid.linMomentum.set(0, 0, 0); + mRigid.angMomentum.set(0, 0, 0); + mContacts.clear(); + + mSteering.set(0,0); + mThrottle = 0; + mJetting = false; + + mCameraOffset.set(0,0,0); + + dMemset( mDustEmitterList, 0, sizeof( mDustEmitterList ) ); + dMemset( mDamageEmitterList, 0, sizeof( mDamageEmitterList ) ); + dMemset( mSplashEmitterList, 0, sizeof( mSplashEmitterList ) ); + + mDisableMove = false; + restCount = 0; + + inLiquid = false; + mWakeSound = NULL; +} + +U32 Vehicle::getCollisionMask() +{ + AssertFatal(false, "Vehicle::getCollisionMask is pure virtual!"); + return 0; +} + +Point3F Vehicle::getVelocity() const +{ + return mRigid.linVelocity; +} + +//---------------------------------------------------------------------------- + +bool Vehicle::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // When loading from a mission script, the base SceneObject's transform + // will have been set and needs to be transfered to the rigid body. + mRigid.setTransform(mObjToWorld); + + // Initialize interpolation vars. + mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition; + mDelta.pos = mRigid.linPosition; + mDelta.posVec = Point3F(0,0,0); + + // Create Emitters on the client + if( isClientObject() ) + { + if( mDataBlock->dustEmitter ) + { + for( int i=0; ionNewDataBlock( mDataBlock->dustEmitter ); + if( !mDustEmitterList[i]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register dust emitter for class: %s", mDataBlock->getName() ); + delete mDustEmitterList[i]; + mDustEmitterList[i] = NULL; + } + } + } + + U32 j; + for( j=0; jdamageEmitterList[j] ) + { + mDamageEmitterList[j] = new ParticleEmitter; + mDamageEmitterList[j]->onNewDataBlock( mDataBlock->damageEmitterList[j] ); + if( !mDamageEmitterList[j]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register damage emitter for class: %s", mDataBlock->getName() ); + delete mDamageEmitterList[j]; + mDamageEmitterList[j] = NULL; + } + + } + } + + for( j=0; jsplashEmitterList[j] ) + { + mSplashEmitterList[j] = new ParticleEmitter; + mSplashEmitterList[j]->onNewDataBlock( mDataBlock->splashEmitterList[j] ); + if( !mSplashEmitterList[j]->registerObject() ) + { + Con::warnf( ConsoleLogEntry::General, "Could not register splash emitter for class: %s", mDataBlock->getName() ); + delete mSplashEmitterList[j]; + mSplashEmitterList[j] = NULL; + } + + } + } + } + + // Create a new convex. + AssertFatal(mDataBlock->collisionDetails[0] != -1, "Error, a vehicle must have a collision-1 detail!"); + mConvex.mObject = this; + mConvex.pShapeBase = this; + mConvex.hullId = 0; + mConvex.box = mObjBox; + mConvex.box.minExtents.convolve(mObjScale); + mConvex.box.maxExtents.convolve(mObjScale); + mConvex.findNodeTransform(); + + return true; +} + +void Vehicle::onRemove() +{ + U32 i=0; + for( i=0; ideleteWhenEmpty(); + mDustEmitterList[i] = NULL; + } + } + + for( i=0; ideleteWhenEmpty(); + mDamageEmitterList[i] = NULL; + } + } + + for( i=0; ideleteWhenEmpty(); + mSplashEmitterList[i] = NULL; + } + } + + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::processTick(const Move* move) +{ + PROFILE_SCOPE( Vehicle_ProcessTick ); + + Parent::processTick(move); + + // Warp to catch up to server + if (mDelta.warpCount < mDelta.warpTicks) + { + mDelta.warpCount++; + + // Set new pos. + mObjToWorld.getColumn(3,&mDelta.pos); + mDelta.pos += mDelta.warpOffset; + mDelta.rot[0] = mDelta.rot[1]; + mDelta.rot[1].interpolate(mDelta.warpRot[0],mDelta.warpRot[1],F32(mDelta.warpCount)/mDelta.warpTicks); + setPosition(mDelta.pos,mDelta.rot[1]); + + // Pos backstepping + mDelta.posVec.x = -mDelta.warpOffset.x; + mDelta.posVec.y = -mDelta.warpOffset.y; + mDelta.posVec.z = -mDelta.warpOffset.z; + } + else + { + if (!move) + { + if (isGhost()) + { + // If we haven't run out of prediction time, + // predict using the last known move. + if (mPredictionCount-- <= 0) + return; + move = &mDelta.move; + } + else + move = &NullMove; + } + + // Process input move + updateMove(move); + + // Save current rigid state interpolation + mDelta.posVec = mRigid.linPosition; + mDelta.rot[0] = mRigid.angPosition; + + // Update the physics based on the integration rate + S32 count = mDataBlock->integration; + updateWorkingCollisionSet(getCollisionMask()); + for (U32 i = 0; i < count; i++) + updatePos(TickSec / count); + + // Wrap up interpolation info + mDelta.pos = mRigid.linPosition; + mDelta.posVec -= mRigid.linPosition; + mDelta.rot[1] = mRigid.angPosition; + + // Update container database + setPosition(mRigid.linPosition, mRigid.angPosition); + setMaskBits(PositionMask); + updateContainer(); + } +} + +void Vehicle::interpolateTick(F32 dt) +{ + PROFILE_SCOPE( Vehicle_InterpolateTick ); + + Parent::interpolateTick(dt); + + if(dt == 0.0f) + setRenderPosition(mDelta.pos, mDelta.rot[1]); + else + { + QuatF rot; + rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt); + Point3F pos = mDelta.pos + mDelta.posVec * dt; + setRenderPosition(pos,rot); + } + mDelta.dt = dt; +} + +void Vehicle::advanceTime(F32 dt) +{ + PROFILE_SCOPE( Vehicle_AdvanceTime ); + + Parent::advanceTime(dt); + + updateLiftoffDust( dt ); + updateDamageSmoke( dt ); + updateFroth(dt); + + // Update 3rd person camera offset. Camera update is done + // here as it's a client side only animation. + mCameraOffset -= + (mCameraOffset * mDataBlock->cameraDecay + + mRigid.linVelocity * mDataBlock->cameraLag) * dt; +} + + +//---------------------------------------------------------------------------- + +bool Vehicle::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + // Update Rigid Info + mRigid.mass = mDataBlock->mass; + mRigid.oneOverMass = 1 / mRigid.mass; + mRigid.friction = mDataBlock->body.friction; + mRigid.restitution = mDataBlock->body.restitution; + mRigid.setCenterOfMass(mDataBlock->massCenter); + + // Ignores massBox, just set sphere for now. Derived objects + // can set what they want. + mRigid.setObjectInertia(); + + if (isGhost()) + { + // Create the sound ahead of time. This reduces runtime + // costs and makes the system easier to understand. + SFX_DELETE( mWakeSound ); + + if ( mDataBlock->waterSound[VehicleData::Wake] ) + mWakeSound = SFX->createSource( mDataBlock->waterSound[VehicleData::Wake], &getTransform() ); + } + + return true; +} + + +//---------------------------------------------------------------------------- + +void Vehicle::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot) +{ + *min = mDataBlock->cameraMinDist; + *max = mDataBlock->cameraMaxDist; + + off->set(0,0,mDataBlock->cameraOffset); + rot->identity(); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::getCameraTransform(F32* pos,MatrixF* mat) +{ + // Returns camera to world space transform + // Handles first person / third person camera position + if (isServerObject() && mShapeInstance) + mShapeInstance->animateNodeSubtrees(true); + + if (*pos == 0) { + getRenderEyeTransform(mat); + return; + } + + // Get the shape's camera parameters. + F32 min,max; + MatrixF rot; + Point3F offset; + getCameraParameters(&min,&max,&offset,&rot); + + // Start with the current eye position + MatrixF eye; + getRenderEyeTransform(&eye); + + // Build a transform that points along the eye axis + // but where the Z axis is always up. + if (mDataBlock->cameraRoll) + mat->mul(eye,rot); + else + { + MatrixF cam(1); + VectorF x,y,z(0,0,1); + eye.getColumn(1, &y); + mCross(y, z, &x); + x.normalize(); + mCross(x, y, &z); + z.normalize(); + cam.setColumn(0,x); + cam.setColumn(1,y); + cam.setColumn(2,z); + mat->mul(cam,rot); + } + + // Camera is positioned straight back along the eye's -Y axis. + // A ray is cast to make sure the camera doesn't go through + // anything solid. + VectorF vp,vec; + vp.x = vp.z = 0; + vp.y = -(max - min) * *pos; + eye.mulV(vp,&vec); + + // Use the camera node as the starting position if it exists. + Point3F osp,sp; + if (mDataBlock->cameraNode != -1) + { + mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp); + getRenderTransform().mulP(osp,&sp); + } + else + eye.getColumn(3,&sp); + + // Make sure we don't hit ourself... + disableCollision(); + if (isMounted()) + getObjectMount()->disableCollision(); + + // Cast the ray into the container database to see if we're going + // to hit anything. + RayInfo collision; + Point3F ep = sp + vec + offset + mCameraOffset; + if (mContainer->castRay(sp, ep, + ~(WaterObjectType | GameBaseObjectType | DefaultObjectType), + &collision) == true) { + + // Shift the collision point back a little to try and + // avoid clipping against the front camera plane. + F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1; + if (t > 0.0f) + ep = sp + offset + mCameraOffset + (vec * t); + else + eye.getColumn(3,&ep); + } + mat->setColumn(3,ep); + + // Re-enable our collision. + if (isMounted()) + getObjectMount()->enableCollision(); + enableCollision(); + + // Apply Camera FX. + mat->mul( gCamFXMgr.getTrans() ); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::getVelocity(const Point3F& r, Point3F* v) +{ + mRigid.getVelocity(r, v); +} + +void Vehicle::applyImpulse(const Point3F &pos, const Point3F &impulse) +{ + Point3F r; + mRigid.getOriginVector(pos,&r); + mRigid.applyImpulse(r, impulse); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::updateMove(const Move* move) +{ + PROFILE_SCOPE( Vehicle_UpdateMove ); + + mDelta.move = *move; + + // Image Triggers + if (mDamageState == Enabled) { + setImageTriggerState(0,move->trigger[0]); + setImageTriggerState(1,move->trigger[1]); + } + + // Throttle + if(!mDisableMove) + mThrottle = move->y; + + // Steering + if (move != &NullMove) { + F32 y = move->yaw; + mSteering.x = mClampF(mSteering.x + y,-mDataBlock->maxSteeringAngle, + mDataBlock->maxSteeringAngle); + F32 p = move->pitch; + mSteering.y = mClampF(mSteering.y + p,-mDataBlock->maxSteeringAngle, + mDataBlock->maxSteeringAngle); + } + else { + mSteering.x = 0; + mSteering.y = 0; + } + + // Jetting flag + if (move->trigger[3]) { + if (!mJetting && getEnergyLevel() >= mDataBlock->minJetEnergy) + mJetting = true; + if (mJetting) { + F32 newEnergy = getEnergyLevel() - mDataBlock->jetEnergyDrain; + if (newEnergy < 0) { + newEnergy = 0; + mJetting = false; + } + setEnergyLevel(newEnergy); + } + } + else + mJetting = false; +} + + +//---------------------------------------------------------------------------- + +void Vehicle::setPosition(const Point3F& pos,const QuatF& rot) +{ + MatrixF mat; + rot.setMatrix(&mat); + mat.setColumn(3,pos); + Parent::setTransform(mat); +} + +void Vehicle::setRenderPosition(const Point3F& pos, const QuatF& rot) +{ + MatrixF mat; + rot.setMatrix(&mat); + mat.setColumn(3,pos); + Parent::setRenderTransform(mat); +} + +void Vehicle::setTransform(const MatrixF& newMat) +{ + mRigid.setTransform(newMat); + Parent::setTransform(newMat); + mRigid.atRest = false; + mContacts.clear(); +} + + +//----------------------------------------------------------------------------- + +void Vehicle::disableCollision() +{ + Parent::disableCollision(); + for (SceneObject* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + ptr->disableCollision(); +} + +void Vehicle::enableCollision() +{ + Parent::enableCollision(); + for (SceneObject* ptr = getMountList(); ptr; ptr = ptr->getMountLink()) + ptr->enableCollision(); +} + + +//---------------------------------------------------------------------------- +/** Update the physics +*/ + +void Vehicle::updatePos(F32 dt) +{ + PROFILE_SCOPE( Vehicle_UpdatePos ); + + Point3F origVelocity = mRigid.linVelocity; + + // Update internal forces acting on the body. + mRigid.clearForces(); + updateForces(dt); + + // Update collision information based on our current pos. + bool collided = false; + if (!mRigid.atRest) { + collided = updateCollision(dt); + + // Now that all the forces have been processed, lets + // see if we're at rest. Basically, if the kinetic energy of + // the vehicles is less than some percentage of the energy added + // by gravity for a short period, we're considered at rest. + // This should really be part of the rigid class... + if (mCollisionList.getCount()) + { + F32 k = mRigid.getKineticEnergy(); + F32 G = sVehicleGravity * dt; + F32 Kg = 0.5 * mRigid.mass * G * G; + if (k < sRestTol * Kg && ++restCount > sRestCount) + mRigid.setAtRest(); + } + else + restCount = 0; + } + + // Integrate forward + if (!mRigid.atRest) + mRigid.integrate(dt); + + // Deal with client and server scripting, sounds, etc. + if (isServerObject()) { + + // Check triggers and other objects that we normally don't + // collide with. This function must be called before notifyCollision + // as it will queue collision. + checkTriggers(); + + // Invoke the onCollision notify callback for all the objects + // we've just hit. + notifyCollision(); + + // Server side impact script callback + if (collided) { + VectorF collVec = mRigid.linVelocity - origVelocity; + F32 collSpeed = collVec.len(); + if (collSpeed > mDataBlock->minImpactSpeed) + onImpact(collVec); + } + + // Water script callbacks + if (!inLiquid && mWaterCoverage != 0.0f) { + Con::executef( mDataBlock, "onEnterLiquid",scriptThis(), Con::getFloatArg(mWaterCoverage), mLiquidType.c_str() ); + inLiquid = true; + } + else if (inLiquid && mWaterCoverage == 0.0f) { + Con::executef( mDataBlock, "onLeaveLiquid",scriptThis(), mLiquidType.c_str() ); + inLiquid = false; + } + + } + else { + + // Play impact sounds on the client. + if (collided) { + F32 collSpeed = (mRigid.linVelocity - origVelocity).len(); + S32 impactSound = -1; + if (collSpeed >= mDataBlock->hardImpactSpeed) + impactSound = VehicleData::Body::HardImpactSound; + else + if (collSpeed >= mDataBlock->softImpactSpeed) + impactSound = VehicleData::Body::SoftImpactSound; + + if (impactSound != -1 && mDataBlock->body.sound[impactSound] != NULL) + SFX->playOnce( mDataBlock->body.sound[impactSound], &getTransform() ); + } + + // Water volume sounds + F32 vSpeed = getVelocity().len(); + if (!inLiquid && mWaterCoverage >= 0.8f) { + if (vSpeed >= mDataBlock->hardSplashSoundVel) + SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactHard], &getTransform() ); + else + if (vSpeed >= mDataBlock->medSplashSoundVel) + SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactMedium], &getTransform() ); + else + if (vSpeed >= mDataBlock->softSplashSoundVel) + SFX->playOnce( mDataBlock->waterSound[VehicleData::ImpactSoft], &getTransform() ); + inLiquid = true; + } + else + if(inLiquid && mWaterCoverage < 0.8f) { + if (vSpeed >= mDataBlock->exitSplashSoundVel) + SFX->playOnce( mDataBlock->waterSound[VehicleData::ExitWater], &getTransform() ); + inLiquid = false; + } + } +} + + +//---------------------------------------------------------------------------- + +void Vehicle::updateForces(F32 /*dt*/) +{ + // Nothing here. +} + + +//----------------------------------------------------------------------------- +/** Update collision information + Update the convex state and check for collisions. If the object is in + collision, impact and contact forces are generated. +*/ + +bool Vehicle::updateCollision(F32 dt) +{ + PROFILE_SCOPE( Vehicle_UpdateCollision ); + + // Update collision information + MatrixF mat,cmat; + mConvex.transform = &mat; + mRigid.getTransform(&mat); + cmat = mConvex.getTransform(); + + mCollisionList.clear(); + CollisionState *state = mConvex.findClosestState(cmat, getScale(), mDataBlock->collisionTol); + if (state && state->dist <= mDataBlock->collisionTol) + { + //resolveDisplacement(ns,state,dt); + mConvex.getCollisionInfo(cmat, getScale(), &mCollisionList, mDataBlock->collisionTol); + } + + // Resolve collisions + bool collided = resolveCollision(mRigid,mCollisionList); + resolveContacts(mRigid,mCollisionList,dt); + return collided; +} + + +//---------------------------------------------------------------------------- +/** Resolve collision impacts + Handle collision impacts, as opposed to contacts. Impulses are calculated based + on standard collision resolution formulas. +*/ +bool Vehicle::resolveCollision(Rigid& ns,CollisionList& cList) +{ + PROFILE_SCOPE( Vehicle_ResolveCollision ); + + // Apply impulses to resolve collision + bool collided = false; + for (S32 i = 0; i < cList.getCount(); i++) + { + Collision& c = cList[i]; + if (c.distance < mDataBlock->collisionTol) + { + // Velocity into surface + Point3F v,r; + ns.getOriginVector(c.point,&r); + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + + // Only interested in velocities greater than sContactTol, + // velocities less than that will be dealt with as contacts + // "constraints". + if (vn < -mDataBlock->contactTol) + { + + // Apply impulses to the rigid body to keep it from + // penetrating the surface. + ns.resolveCollision(cList[i].point, + cList[i].normal); + collided = true; + + // Keep track of objects we collide with + if (!isGhost() && c.object->getTypeMask() & ShapeBaseObjectType) + { + ShapeBase* col = static_cast(c.object); + queueCollision(col,v - col->getVelocity()); + } + } + } + } + + return collided; +} + +//---------------------------------------------------------------------------- +/** Resolve contact forces + Resolve contact forces using the "penalty" method. Forces are generated based + on the depth of penetration and the moment of inertia at the point of contact. +*/ +bool Vehicle::resolveContacts(Rigid& ns,CollisionList& cList,F32 dt) +{ + PROFILE_SCOPE( Vehicle_ResolveContacts ); + + // Use spring forces to manage contact constraints. + bool collided = false; + Point3F t,p(0,0,0),l(0,0,0); + for (S32 i = 0; i < cList.getCount(); i++) + { + const Collision& c = cList[i]; + if (c.distance < mDataBlock->collisionTol) + { + + // Velocity into the surface + Point3F v,r; + ns.getOriginVector(c.point,&r); + ns.getVelocity(r,&v); + F32 vn = mDot(v,c.normal); + + // Only interested in velocities less than mDataBlock->contactTol, + // velocities greater than that are dealt with as collisions. + if (mFabs(vn) < mDataBlock->contactTol) + { + collided = true; + + // Penetration force. This is actually a spring which + // will seperate the body from the collision surface. + F32 zi = 2 * mFabs(mRigid.getZeroImpulse(r,c.normal)); + F32 s = (mDataBlock->collisionTol - c.distance) * zi - ((vn / mDataBlock->contactTol) * zi); + Point3F f = c.normal * s; + + // Friction impulse, calculated as a function of the + // amount of force it would take to stop the motion + // perpendicular to the normal. + Point3F uv = v - (c.normal * vn); + F32 ul = uv.len(); + if (s > 0 && ul) + { + uv /= -ul; + F32 u = ul * ns.getZeroImpulse(r,uv); + s *= mRigid.friction; + if (u > s) + u = s; + f += uv * u; + } + + // Accumulate forces + p += f; + mCross(r,f,&t); + l += t; + } + } + } + + // Contact constraint forces act over time... + ns.linMomentum += p * dt; + ns.angMomentum += l * dt; + ns.updateVelocity(); + return true; +} + + +//---------------------------------------------------------------------------- + +bool Vehicle::resolveDisplacement(Rigid& ns,CollisionState *state, F32 dt) +{ + PROFILE_SCOPE( Vehicle_ResolveDisplacement ); + + SceneObject* obj = (state->a->getObject() == this)? + state->b->getObject(): state->a->getObject(); + + if (obj->isDisplacable() && ((obj->getTypeMask() & ShapeBaseObjectType) != 0)) + { + // Try to displace the object by the amount we're trying to move + Point3F objNewMom = ns.linVelocity * obj->getMass() * 1.1f; + Point3F objOldMom = obj->getMomentum(); + Point3F objNewVel = objNewMom / obj->getMass(); + + Point3F myCenter; + Point3F theirCenter; + getWorldBox().getCenter(&myCenter); + obj->getWorldBox().getCenter(&theirCenter); + if (mDot(myCenter - theirCenter, objNewMom) >= 0.0f || objNewVel.len() < 0.01) + { + objNewMom = (theirCenter - myCenter); + objNewMom.normalize(); + objNewMom *= 1.0f * obj->getMass(); + objNewVel = objNewMom / obj->getMass(); + } + + obj->setMomentum(objNewMom); + if (obj->displaceObject(objNewVel * 1.1f * dt) == true) + { + // Queue collision and change in velocity + VectorF dv = (objOldMom - objNewMom) / obj->getMass(); + queueCollision(static_cast(obj), dv); + return true; + } + } + + return false; +} + + +//---------------------------------------------------------------------------- + +void Vehicle::updateWorkingCollisionSet(const U32 mask) +{ + PROFILE_SCOPE( Vehicle_UpdateWorkingCollisionSet ); + + // First, we need to adjust our velocity for possible acceleration. It is assumed + // that we will never accelerate more than 20 m/s for gravity, plus 30 m/s for + // jetting, and an equivalent 10 m/s for vehicle accel. We also assume that our + // working list is updated on a Tick basis, which means we only expand our box by + // the possible movement in that tick, plus some extra for caching purposes + Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale()); + F32 len = (mRigid.linVelocity.len() + 50) * TickSec; + F32 l = (len * 1.1) + 0.1; // fudge factor + convexBox.minExtents -= Point3F(l, l, l); + convexBox.maxExtents += Point3F(l, l, l); + + disableCollision(); + mConvex.updateWorkingList(convexBox, mask); + enableCollision(); +} + + +//---------------------------------------------------------------------------- +/** Check collisions with trigger and items + Perform a container search using the current bounding box + of the main body, wheels are not included. This method should + only be called on the server. +*/ +void Vehicle::checkTriggers() +{ + Box3F bbox = mConvex.getBoundingBox(getTransform(), getScale()); + gServerContainer.findObjects(bbox,sTriggerMask,findCallback,this); +} + +/** The callback used in by the checkTriggers() method. + The checkTriggers method uses a container search which will + invoke this callback on each obj that matches. +*/ +void Vehicle::findCallback(SceneObject* obj,void *key) +{ + Vehicle* vehicle = reinterpret_cast(key); + U32 objectMask = obj->getTypeMask(); + + // Check: triggers, corpses and items, basically the same things + // that the player class checks for + if (objectMask & TriggerObjectType) { + Trigger* pTrigger = static_cast(obj); + pTrigger->potentialEnterObject(vehicle); + } + else if (objectMask & CorpseObjectType) { + ShapeBase* col = static_cast(obj); + vehicle->queueCollision(col,vehicle->getVelocity() - col->getVelocity()); + } + else if (objectMask & ItemObjectType) { + Item* item = static_cast(obj); + if (vehicle != item->getCollisionObject()) + vehicle->queueCollision(item,vehicle->getVelocity() - item->getVelocity()); + } +} + + +//---------------------------------------------------------------------------- + +void Vehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + Parent::writePacketData(connection, stream); + mathWrite(*stream, mSteering); + + mathWrite(*stream, mRigid.linPosition); + mathWrite(*stream, mRigid.angPosition); + mathWrite(*stream, mRigid.linMomentum); + mathWrite(*stream, mRigid.angMomentum); + stream->writeFlag(mRigid.atRest); + stream->writeFlag(mContacts.getCount() == 0); + + stream->writeFlag(mDisableMove); + stream->setCompressionPoint(mRigid.linPosition); +} + +void Vehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + mathRead(*stream, &mSteering); + + mathRead(*stream, &mRigid.linPosition); + mathRead(*stream, &mRigid.angPosition); + mathRead(*stream, &mRigid.linMomentum); + mathRead(*stream, &mRigid.angMomentum); + mRigid.atRest = stream->readFlag(); + if (stream->readFlag()) + mContacts.clear(); + mRigid.updateInertialTensor(); + mRigid.updateVelocity(); + mRigid.updateCenterOfMass(); + + mDisableMove = stream->readFlag(); + stream->setCompressionPoint(mRigid.linPosition); +} + + +//---------------------------------------------------------------------------- + +U32 Vehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + stream->writeFlag(mJetting); + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) + return retMask; + + F32 yaw = (mSteering.x + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle); + F32 pitch = (mSteering.y + mDataBlock->maxSteeringAngle) / (2 * mDataBlock->maxSteeringAngle); + stream->writeFloat(yaw,9); + stream->writeFloat(pitch,9); + mDelta.move.pack(stream); + + if (stream->writeFlag(mask & PositionMask)) + { + stream->writeCompressedPoint(mRigid.linPosition); + mathWrite(*stream, mRigid.angPosition); + mathWrite(*stream, mRigid.linMomentum); + mathWrite(*stream, mRigid.angMomentum); + stream->writeFlag(mRigid.atRest); + } + + + stream->writeFloat(mClampF(getEnergyValue(), 0.f, 1.f), 8); + + return retMask; +} + +void Vehicle::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + mJetting = stream->readFlag(); + + if (stream->readFlag()) + return; + + F32 yaw = stream->readFloat(9); + F32 pitch = stream->readFloat(9); + mSteering.x = (2 * yaw * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle; + mSteering.y = (2 * pitch * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle; + mDelta.move.unpack(stream); + + if (stream->readFlag()) + { + mPredictionCount = sMaxPredictionTicks; + F32 speed = mRigid.linVelocity.len(); + mDelta.warpRot[0] = mRigid.angPosition; + + // Read in new position and momentum values + stream->readCompressedPoint(&mRigid.linPosition); + mathRead(*stream, &mRigid.angPosition); + mathRead(*stream, &mRigid.linMomentum); + mathRead(*stream, &mRigid.angMomentum); + mRigid.atRest = stream->readFlag(); + mRigid.updateVelocity(); + + if (isProperlyAdded()) + { + // Determine number of ticks to warp based on the average + // of the client and server velocities. + Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt; + mDelta.warpOffset = mRigid.linPosition - cp; + + // Calc the distance covered in one tick as the average of + // the old speed and the new speed from the server. + F32 dt,as = (speed + mRigid.linVelocity.len()) * 0.5 * TickSec; + + // Cal how many ticks it will take to cover the warp offset. + // If it's less than what's left in the current tick, we'll just + // warp in the remaining time. + if (!as || (dt = mDelta.warpOffset.len() / as) > sMaxWarpTicks) + dt = mDelta.dt + sMaxWarpTicks; + else + dt = (dt <= mDelta.dt)? mDelta.dt : mCeil(dt - mDelta.dt) + mDelta.dt; + + // Adjust current frame interpolation + if (mDelta.dt) { + mDelta.pos = cp + (mDelta.warpOffset * (mDelta.dt / dt)); + mDelta.posVec = (cp - mDelta.pos) / mDelta.dt; + QuatF cr; + cr.interpolate(mDelta.rot[1],mDelta.rot[0],mDelta.dt); + mDelta.rot[1].interpolate(cr,mRigid.angPosition,mDelta.dt / dt); + mDelta.rot[0].extrapolate(mDelta.rot[1],cr,mDelta.dt); + } + + // Calculated multi-tick warp + mDelta.warpCount = 0; + mDelta.warpTicks = (S32)(mFloor(dt)); + if (mDelta.warpTicks) + { + mDelta.warpOffset = mRigid.linPosition - mDelta.pos; + mDelta.warpOffset /= (F32)mDelta.warpTicks; + mDelta.warpRot[0] = mDelta.rot[1]; + mDelta.warpRot[1] = mRigid.angPosition; + } + } + else + { + // Set the vehicle to the server position + mDelta.dt = 0; + mDelta.pos = mRigid.linPosition; + mDelta.posVec.set(0,0,0); + mDelta.rot[1] = mDelta.rot[0] = mRigid.angPosition; + mDelta.warpCount = mDelta.warpTicks = 0; + setPosition(mRigid.linPosition, mRigid.angPosition); + } + mRigid.updateCenterOfMass(); + } + + setEnergyLevel(stream->readFloat(8) * mDataBlock->maxEnergy); +} + + +//---------------------------------------------------------------------------- + +void Vehicle::initPersistFields() +{ + addField("disableMove", TypeBool, Offset(mDisableMove, Vehicle)); + + Parent::initPersistFields(); +} + + +void Vehicle::mountObject(SceneObject *obj, U32 node) +{ + Parent::mountObject(obj, node); + + // Clear objects off the working list that are from objects mounted to us. + // (This applies mostly to players...) + for ( CollisionWorkingList* itr = mConvex.getWorkingList().wLink.mNext; + itr != &mConvex.getWorkingList(); + itr = itr->wLink.mNext) + { + if (itr->mConvex->getObject() == obj) + { + CollisionWorkingList* cl = itr; + itr = itr->wLink.mPrev; + cl->free(); + } + } +} + +//---------------------------------------------------------------------------- + +void Vehicle::updateLiftoffDust( F32 dt ) +{ + Point3F offset( 0.0, 0.0, mDataBlock->dustHeight ); + emitDust( mDustEmitterList[ 0 ], mDataBlock->triggerDustHeight, offset, + ( U32 )( dt * 1000 ) ); +} + +//---------------------------------------------------------------------------- + +void Vehicle::updateDamageSmoke( F32 dt ) +{ + + for( S32 j=VehicleData::VC_NUM_DAMAGE_LEVELS-1; j>=0; j-- ) + { + F32 damagePercent = mDamage / mDataBlock->maxDamage; + if( damagePercent >= mDataBlock->damageLevelTolerance[j] ) + { + for( int i=0; inumDmgEmitterAreas; i++ ) + { + MatrixF trans = getTransform(); + Point3F offset = mDataBlock->damageEmitterOffset[i]; + trans.mulP( offset ); + Point3F emitterPoint = offset; + + if( pointInWater(offset ) ) + { + U32 emitterOffset = VehicleData::VC_BUBBLE_EMITTER; + if( mDamageEmitterList[emitterOffset] ) + { + mDamageEmitterList[emitterOffset]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)( dt * 1000 ) ); + } + } + else + { + if( mDamageEmitterList[j] ) + { + mDamageEmitterList[j]->emitParticles( emitterPoint, emitterPoint, Point3F( 0.0, 0.0, 1.0 ), getVelocity(), (U32)(dt * 1000)); + } + } + } + break; + } + } + +} + + +//-------------------------------------------------------------------------- +void Vehicle::updateFroth( F32 dt ) +{ + // update bubbles + Point3F moveDir = getVelocity(); + + Point3F contactPoint; + if( !collidingWithWater( contactPoint ) ) + { + if ( mWakeSound ) + mWakeSound->stop(); + return; + } + + F32 speed = moveDir.len(); + if( speed < mDataBlock->splashVelEpsilon ) speed = 0.0; + + U32 emitRate = (U32)(speed * mDataBlock->splashFreqMod * dt); + + U32 i; + + if ( mWakeSound ) + { + if ( !mWakeSound->isPlaying() ) + mWakeSound->play(); + + mWakeSound->setTransform( getTransform() ); + mWakeSound->setVelocity( getVelocity() ); + } + + for( i=0; iemitParticles( contactPoint, contactPoint, Point3F( 0.0, 0.0, 1.0 ), + moveDir, emitRate ); + } + } + +} + + +//-------------------------------------------------------------------------- +// Returns true if vehicle is intersecting a water surface (roughly) +//-------------------------------------------------------------------------- +bool Vehicle::collidingWithWater( Point3F &waterHeight ) +{ + Point3F curPos = getPosition(); + + F32 height = mFabs( mObjBox.maxExtents.z - mObjBox.minExtents.z ); + + RayInfo rInfo; + if( gClientContainer.castRay( curPos + Point3F(0.0, 0.0, height), curPos, WaterObjectType, &rInfo) ) + { + waterHeight = rInfo.point; + return true; + } + + return false; +} + +void Vehicle::setEnergyLevel(F32 energy) +{ + Parent::setEnergyLevel(energy); + setMaskBits(EnergyMask); +} + +void Vehicle::prepBatchRender( SceneState *state, S32 mountedImageIndex ) +{ + Parent::prepBatchRender( state, mountedImageIndex ); + + if ( !gShowBoundingBox ) + return; + + if ( mountedImageIndex != -1 ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Vehicle::_renderMuzzleVector ); + ri->objectIndex = mountedImageIndex; + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + return; + } + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Vehicle::_renderMassAndContacts ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); +} + +void Vehicle::_renderMassAndContacts( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + GFXStateBlockDesc desc; + desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setZReadWrite(false,true); + desc.fillMode = GFXFillWireframe; + + // Render the mass center. + GFX->getDrawUtil()->drawCube(desc, Point3F(0.1f,0.1f,0.1f),mDataBlock->massCenter, ColorI(255, 255, 255), &mRenderObjToWorld); + + // Now render all the contact points. + for (int i = 0; i < mCollisionList.getCount(); i++) + { + const Collision& collision = mCollisionList[i]; + GFX->getDrawUtil()->drawCube(desc, Point3F(0.05f,0.05f,0.05f),collision.point, ColorI(0, 0, 255)); + } + + // Finally render the normals as one big batch. + PrimBuild::begin(GFXLineList, mCollisionList.getCount() * 2); + for (int i = 0; i < mCollisionList.getCount(); i++) + { + const Collision& collision = mCollisionList[i]; + PrimBuild::color3f(1, 1, 1); + PrimBuild::vertex3fv(collision.point); + PrimBuild::vertex3fv(collision.point + collision.normal * 0.05f); + } + PrimBuild::end(); + + // Build and render the collision polylist which is returned + // in the server's world space. + ClippedPolyList polyList; + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(getWorldBox().minExtents,VectorF(-1,0,0)); + polyList.mPlaneList[1].set(getWorldBox().minExtents,VectorF(0,-1,0)); + polyList.mPlaneList[2].set(getWorldBox().minExtents,VectorF(0,0,-1)); + polyList.mPlaneList[3].set(getWorldBox().maxExtents,VectorF(1,0,0)); + polyList.mPlaneList[4].set(getWorldBox().maxExtents,VectorF(0,1,0)); + polyList.mPlaneList[5].set(getWorldBox().maxExtents,VectorF(0,0,1)); + Box3F dummyBox; + SphereF dummySphere; + buildPolyList(&polyList, dummyBox, dummySphere); + //polyList.render(); +} + +void Vehicle::_renderMuzzleVector( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + const U32 index = ri->objectIndex; + + AssertFatal( index > 0 && index < MaxMountedImages, "Vehicle::_renderMuzzleVector() - Bad object index!" ); + AssertFatal( mMountedImageList[index].dataBlock, "Vehicle::_renderMuzzleVector() - Bad object index!" ); + + Point3F muzzlePoint, muzzleVector, endpoint; + getMuzzlePoint(index, &muzzlePoint); + getMuzzleVector(index, &muzzleVector); + endpoint = muzzlePoint + muzzleVector * 250; + + if (mSolidSB.isNull()) + { + GFXStateBlockDesc desc; + desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setZReadWrite(false); + mSolidSB = GFX->createStateBlock(desc); + } + + GFX->setStateBlock(mSolidSB); + + PrimBuild::begin(GFXLineList, 2); + + PrimBuild::color4f(0, 1, 0, 1); + PrimBuild::vertex3fv(muzzlePoint); + PrimBuild::vertex3fv(endpoint); + + PrimBuild::end(); +} diff --git a/T3D/vehicles/vehicle.h b/T3D/vehicles/vehicle.h new file mode 100644 index 0000000..1a1e835 --- /dev/null +++ b/T3D/vehicles/vehicle.h @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VEHICLE_H_ +#define _VEHICLE_H_ + +#ifndef _SHAPEBASE_H_ +#include "T3D/shapeBase.h" +#endif +#ifndef _RIGID_H_ +#include "T3D/rigid.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; +class ClippedPolyList; +struct RenderInst; + +//---------------------------------------------------------------------------- + +struct VehicleData: public ShapeBaseData +{ + typedef ShapeBaseData Parent; + + struct Body { + enum Sounds { + SoftImpactSound, + HardImpactSound, + MaxSounds, + }; + SFXProfile* sound[MaxSounds]; + F32 restitution; + F32 friction; + } body; + + enum VehicleConsts + { + VC_NUM_DUST_EMITTERS = 1, + VC_NUM_DAMAGE_EMITTER_AREAS = 2, + VC_NUM_DAMAGE_LEVELS = 2, + VC_NUM_BUBBLE_EMITTERS = 1, + VC_NUM_DAMAGE_EMITTERS = VC_NUM_DAMAGE_LEVELS + VC_NUM_BUBBLE_EMITTERS, + VC_NUM_SPLASH_EMITTERS = 2, + VC_BUBBLE_EMITTER = VC_NUM_DAMAGE_EMITTERS - VC_NUM_BUBBLE_EMITTERS, + }; + + enum Sounds { + ExitWater, + ImpactSoft, + ImpactMedium, + ImpactHard, + Wake, + MaxSounds + }; + SFXProfile* waterSound[MaxSounds]; + F32 exitSplashSoundVel; + F32 softSplashSoundVel; + F32 medSplashSoundVel; + F32 hardSplashSoundVel; + + F32 minImpactSpeed; + F32 softImpactSpeed; + F32 hardImpactSpeed; + F32 minRollSpeed; + F32 maxSteeringAngle; + + F32 collDamageThresholdVel; + F32 collDamageMultiplier; + + bool cameraRoll; ///< Roll the 3rd party camera + F32 cameraLag; ///< Amount of camera lag (lag += car velocity * lag) + F32 cameraDecay; ///< Rate at which camera returns to target pos. + F32 cameraOffset; ///< Vertical offset + + F32 minDrag; + F32 maxDrag; + S32 integration; ///< # of physics steps per tick + F32 collisionTol; ///< Collision distance tolerance + F32 contactTol; ///< Contact velocity tolerance + Point3F massCenter; ///< Center of mass for rigid body + Point3F massBox; ///< Size of inertial box + + F32 jetForce; + F32 jetEnergyDrain; ///< Energy drain/tick + F32 minJetEnergy; + + ParticleEmitterData * dustEmitter; + S32 dustID; + F32 triggerDustHeight; ///< height vehicle has to be under to kick up dust + F32 dustHeight; ///< dust height above ground + + ParticleEmitterData * damageEmitterList[ VC_NUM_DAMAGE_EMITTERS ]; + Point3F damageEmitterOffset[ VC_NUM_DAMAGE_EMITTER_AREAS ]; + S32 damageEmitterIDList[ VC_NUM_DAMAGE_EMITTERS ]; + F32 damageLevelTolerance[ VC_NUM_DAMAGE_LEVELS ]; + F32 numDmgEmitterAreas; + + ParticleEmitterData* splashEmitterList[VC_NUM_SPLASH_EMITTERS]; + S32 splashEmitterIDList[VC_NUM_SPLASH_EMITTERS]; + F32 splashFreqMod; + F32 splashVelEpsilon; + + // + VehicleData(); + bool preload(bool server, String &errorStr); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + DECLARE_CONOBJECT(VehicleData); +}; + + +//---------------------------------------------------------------------------- + +class Vehicle: public ShapeBase +{ + typedef ShapeBase Parent; + + protected: + enum CollisionFaceFlags { + BodyCollision = 0x1, + WheelCollision = 0x2, + }; + enum MaskBits { + PositionMask = Parent::NextFreeMask << 0, + EnergyMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + struct StateDelta { + Move move; ///< Last move from server + F32 dt; ///< Last interpolation time + // Interpolation data + Point3F pos; + Point3F posVec; + QuatF rot[2]; + // Warp data + S32 warpTicks; ///< Number of ticks to warp + S32 warpCount; ///< Current pos in warp + Point3F warpOffset; + QuatF warpRot[2]; + // + Point3F cameraOffset; + Point3F cameraVec; + Point3F cameraRot; + Point3F cameraRotVec; + }; + + StateDelta mDelta; + S32 mPredictionCount; ///< Number of ticks to predict + VehicleData* mDataBlock; + bool inLiquid; + SFXSource* mWakeSound; + + Point3F mCameraOffset; ///< 3rd person camera + + // Control + Point2F mSteering; + F32 mThrottle; + bool mJetting; + + // Rigid Body + bool mDisableMove; + + GFXStateBlockRef mSolidSB; + + CollisionList mCollisionList; + CollisionList mContacts; + Rigid mRigid; + ShapeBaseConvex mConvex; + int restCount; + + ParticleEmitter *mDustEmitterList[VehicleData::VC_NUM_DUST_EMITTERS]; + ParticleEmitter *mDamageEmitterList[VehicleData::VC_NUM_DAMAGE_EMITTERS]; + ParticleEmitter *mSplashEmitterList[VehicleData::VC_NUM_SPLASH_EMITTERS]; + + // + bool onNewDataBlock(GameBaseData* dptr); + void updatePos(F32 dt); + bool updateCollision(F32 dt); + bool resolveCollision(Rigid& ns,CollisionList& cList); + bool resolveContacts(Rigid& ns,CollisionList& cList,F32 dt); + bool resolveDisplacement(Rigid& ns,CollisionState *state,F32 dt); + bool findContacts(Rigid& ns,CollisionList& cList); + void checkTriggers(); + static void findCallback(SceneObject* obj,void * key); + + void setPosition(const Point3F& pos,const QuatF& rot); + void setRenderPosition(const Point3F& pos,const QuatF& rot); + void setTransform(const MatrixF& mat); + +// virtual bool collideBody(const MatrixF& mat,Collision* info) = 0; + virtual void updateMove(const Move* move); + virtual void updateForces(F32 dt); + + void writePacketData(GameConnection * conn, BitStream *stream); + void readPacketData (GameConnection * conn, BitStream *stream); + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + void updateLiftoffDust( F32 dt ); + void updateDamageSmoke( F32 dt ); + + void updateWorkingCollisionSet(const U32 mask); + virtual U32 getCollisionMask(); + + void updateFroth( F32 dt ); + bool collidingWithWater( Point3F &waterHeight ); + + /// ObjectRenderInst delegate hooked up in prepBatchRender + /// if GameBase::gShowBoundingBox is true. + void _renderMassAndContacts( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + /// ObjectRenderInst delegate hooked up in prepBatchRender + /// if GameBase::gShowBoundingBox is true. + void _renderMuzzleVector( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + +public: + // Test code... + static ClippedPolyList* sPolyList; + static S32 sVehicleCount; + + // + Vehicle(); + static void initPersistFields(); + void processTick(const Move *move); + bool onAdd(); + void onRemove(); + + /// Interpolates between move ticks @see processTick + /// @param dt Change in time between the last call and this call to the function + void interpolateTick(F32 dt); + void advanceTime(F32 dt); + + /// Disables collisions for this vehicle and all mounted objects + void disableCollision(); + + /// Enables collisions for this vehicle and all mounted objects + void enableCollision(); + + /// Returns the velocity of the vehicle + Point3F getVelocity() const; + + void setEnergyLevel(F32 energy); + + void prepBatchRender( SceneState *state, S32 mountedImageIndex ); + + ///@name Rigid body methods + ///@{ + + /// This method will get the velocity of the object, taking into account + /// angular velocity. + /// @param r Point on the object you want the velocity of, relative to Center of Mass + /// @param vel Velocity (out) + void getVelocity(const Point3F& r, Point3F* vel); + + /// Applies an impulse force + /// @param r Point on the object to apply impulse to, r is relative to Center of Mass + /// @param impulse Impulse vector to apply. + void applyImpulse(const Point3F &r, const Point3F &impulse); + + void getCameraParameters(F32 *min, F32* max, Point3F* offset, MatrixF* rot); + void getCameraTransform(F32* pos, MatrixF* mat); + ///@} + + /// @name Mounted objects + /// @{ + virtual void mountObject(SceneObject *obj, U32 node); + /// @} + + DECLARE_CONOBJECT(Vehicle); +}; + + +#endif diff --git a/T3D/vehicles/vehicleBlocker.cpp b/T3D/vehicles/vehicleBlocker.cpp new file mode 100644 index 0000000..f6e1077 --- /dev/null +++ b/T3D/vehicles/vehicleBlocker.cpp @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/vehicles/vehicleBlocker.h" + +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "math/mathIO.h" +#include "console/consoleTypes.h" + +IMPLEMENT_CO_NETOBJECT_V1(VehicleBlocker); + + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +VehicleBlocker::VehicleBlocker() +{ + mNetFlags.set(Ghostable | ScopeAlways); + + mTypeMask = VehicleBlockerObjectType; + + mConvexList = new Convex; +} + +VehicleBlocker::~VehicleBlocker() +{ + delete mConvexList; + mConvexList = NULL; +} + +//-------------------------------------------------------------------------- +void VehicleBlocker::initPersistFields() +{ + addField("dimensions", TypePoint3F, Offset(mDimensions, VehicleBlocker)); + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +bool VehicleBlocker::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.minExtents.set(-mDimensions.x, -mDimensions.y, 0); + mObjBox.maxExtents.set( mDimensions.x, mDimensions.y, mDimensions.z); + resetWorldBox(); + setRenderTransform(mObjToWorld); + + addToScene(); + + return true; +} + + +void VehicleBlocker::onRemove() +{ + mConvexList->nukeList(); + removeFromScene(); + + Parent::onRemove(); +} + + +U32 VehicleBlocker::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + mathWrite(*stream, mDimensions); + + return retMask; +} + + +void VehicleBlocker::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + + MatrixF mat; + Point3F scale; + Box3F objBox; + mathRead(*stream, &mat); + mathRead(*stream, &scale); + mathRead(*stream, &mDimensions); + mObjBox.minExtents.set(-mDimensions.x, -mDimensions.y, 0); + mObjBox.maxExtents.set( mDimensions.x, mDimensions.y, mDimensions.z); + setScale(scale); + setTransform(mat); +} + + +void VehicleBlocker::buildConvex(const Box3F& box, Convex* convex) +{ + // These should really come out of a pool + mConvexList->collectGarbage(); + + if (box.isOverlapped(getWorldBox()) == false) + return; + + // Just return a box convex for the entire shape... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) + { + if (itr->mConvex->getType() == BoxConvexType && + itr->mConvex->getObject() == this) { + cc = itr->mConvex; + break; + } + } + if (cc) + return; + + // Create a new convex. + BoxConvex* cp = new BoxConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->init(this); + + mObjBox.getCenter(&cp->mCenter); + cp->mSize.x = mObjBox.len_x() / 2.0f; + cp->mSize.y = mObjBox.len_y() / 2.0f; + cp->mSize.z = mObjBox.len_z() / 2.0f; +} + diff --git a/T3D/vehicles/vehicleBlocker.h b/T3D/vehicles/vehicleBlocker.h new file mode 100644 index 0000000..4165d02 --- /dev/null +++ b/T3D/vehicles/vehicleBlocker.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VEHICLEBLOCKER_H_ +#define _VEHICLEBLOCKER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _BOXCONVEX_H_ +#include "collision/boxConvex.h" +#endif + +//-------------------------------------------------------------------------- +class VehicleBlocker : public SceneObject +{ + typedef SceneObject Parent; + friend class VehicleBlockerConvex; + + protected: + bool onAdd(); + void onRemove(); + + // Collision + void buildConvex(const Box3F& box, Convex* convex); + protected: + Convex* mConvexList; + + Point3F mDimensions; + + public: + VehicleBlocker(); + ~VehicleBlocker(); + + DECLARE_CONOBJECT(VehicleBlocker); + static void initPersistFields(); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); +}; + +#endif // _H_VEHICLEBLOCKER diff --git a/T3D/vehicles/wheeledVehicle.cpp b/T3D/vehicles/wheeledVehicle.cpp new file mode 100644 index 0000000..a1c2c7d --- /dev/null +++ b/T3D/vehicles/wheeledVehicle.cpp @@ -0,0 +1,1517 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "T3D/vehicles/wheeledVehicle.h" + +#include "math/mMath.h" +#include "math/mathIO.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "collision/clippedPolyList.h" +#include "collision/planeExtractor.h" +#include "core/stream/bitStream.h" +#include "core/dnet.h" +#include "T3D/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "T3D/fx/particleEmitter.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" +#include "sceneGraph/sceneGraph.h" +#include "core/resourceManager.h" +#include "materials/materialDefinition.h" + +//---------------------------------------------------------------------------- + +// Collision masks are used to determin what type of objects the +// wheeled vehicle will collide with. +static U32 sClientCollisionMask = + TerrainObjectType | InteriorObjectType | + PlayerObjectType | StaticShapeObjectType | + VehicleObjectType | VehicleBlockerObjectType | + StaticTSObjectType; + +// Gravity constant +static F32 sWheeledVehicleGravity = -20; + +// Misc. sound constants +static F32 sMinSquealVolume = 0.05f; +static F32 sIdleEngineVolume = 0.2f; + + +//---------------------------------------------------------------------------- +// Vehicle Tire Data Block +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleTire); + +WheeledVehicleTire::WheeledVehicleTire() +{ + shape = 0; + shapeName = ""; + staticFriction = 1; + kineticFriction = 0.5f; + restitution = 1; + radius = 0.6f; + lateralForce = 10; + lateralDamping = 1; + lateralRelaxation = 1; + longitudinalForce = 10; + longitudinalDamping = 1; + longitudinalRelaxation = 1; +} + +bool WheeledVehicleTire::preload(bool server, String &errorStr) +{ + // Load up the tire shape. ShapeBase has an option to force a + // CRC check, this is left out here, but could be easily added. + if (shapeName && shapeName[0]) + { + + // Load up the shape resource + shape = ResourceManager::get().load(shapeName); + if (!bool(shape)) + { + errorStr = String::ToString("WheeledVehicleTire: Couldn't load shape \"%s\"",shapeName); + return false; + } + + // Determinw wheel radius from the shape's bounding box. + // The tire should be built with it's hub axis along the + // object's Y axis. + radius = shape->bounds.len_z() / 2; + } + + return true; +} + +void WheeledVehicleTire::initPersistFields() +{ + addField("shapeFile",TypeFilename,Offset(shapeName,WheeledVehicleTire)); + addField("mass", TypeF32, Offset(mass, WheeledVehicleTire)); + addField("radius", TypeF32, Offset(radius, WheeledVehicleTire)); + addField("staticFriction", TypeF32, Offset(staticFriction, WheeledVehicleTire)); + addField("kineticFriction", TypeF32, Offset(kineticFriction, WheeledVehicleTire)); + addField("restitution", TypeF32, Offset(restitution, WheeledVehicleTire)); + addField("lateralForce", TypeF32, Offset(lateralForce, WheeledVehicleTire)); + addField("lateralDamping", TypeF32, Offset(lateralDamping, WheeledVehicleTire)); + addField("lateralRelaxation", TypeF32, Offset(lateralRelaxation, WheeledVehicleTire)); + addField("longitudinalForce", TypeF32, Offset(longitudinalForce, WheeledVehicleTire)); + addField("longitudinalDamping", TypeF32, Offset(longitudinalDamping, WheeledVehicleTire)); + addField("logitudinalRelaxation", TypeF32, Offset(longitudinalRelaxation, WheeledVehicleTire)); + + Parent::initPersistFields(); +} + +void WheeledVehicleTire::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(shapeName); + stream->write(mass); + stream->write(staticFriction); + stream->write(kineticFriction); + stream->write(restitution); + stream->write(radius); + stream->write(lateralForce); + stream->write(lateralDamping); + stream->write(lateralRelaxation); + stream->write(longitudinalForce); + stream->write(longitudinalDamping); + stream->write(longitudinalRelaxation); +} + +void WheeledVehicleTire::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + shapeName = stream->readSTString(); + stream->read(&mass); + stream->read(&staticFriction); + stream->read(&kineticFriction); + stream->read(&restitution); + stream->read(&radius); + stream->read(&lateralForce); + stream->read(&lateralDamping); + stream->read(&lateralRelaxation); + stream->read(&longitudinalForce); + stream->read(&longitudinalDamping); + stream->read(&longitudinalRelaxation); +} + + +//---------------------------------------------------------------------------- +// Vehicle Spring Data Block +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleSpring); + +WheeledVehicleSpring::WheeledVehicleSpring() +{ + length = 1; + force = 10; + damping = 1; + antiSway = 1; +} + +void WheeledVehicleSpring::initPersistFields() +{ + addField("length", TypeF32, Offset(length, WheeledVehicleSpring)); + addField("force", TypeF32, Offset(force, WheeledVehicleSpring)); + addField("damping", TypeF32, Offset(damping, WheeledVehicleSpring)); + addField("antiSwayForce", TypeF32, Offset(antiSway, WheeledVehicleSpring)); + + Parent::initPersistFields(); +} + +void WheeledVehicleSpring::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(length); + stream->write(force); + stream->write(damping); + stream->write(antiSway); +} + +void WheeledVehicleSpring::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&length); + stream->read(&force); + stream->read(&damping); + stream->read(&antiSway); +} + + +//---------------------------------------------------------------------------- +// Wheeled Vehicle Data Block +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1(WheeledVehicleData); + +WheeledVehicleData::WheeledVehicleData() +{ + tireEmitter = 0; + maxWheelSpeed = 40; + engineTorque = 1; + engineBrake = 1; + brakeTorque = 1; + brakeLightSequence = -1; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = 0; +} + + +//---------------------------------------------------------------------------- +/** Load the vehicle shape + Loads and extracts information from the vehicle shape. + + Wheel Sequences + spring# Wheel spring motion: time 0 = wheel fully extended, + the hub must be displaced, but not directly animated + as it will be rotated in code. + Other Sequences + steering Wheel steering: time 0 = full right, 0.5 = center + breakLight Break light, time 0 = off, 1 = breaking + + Wheel Nodes + hub# Wheel hub + + The steering and animation sequences are optional. +*/ +bool WheeledVehicleData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // A temporary shape instance is created so that we can + // animate the shape and extract wheel information. + TSShapeInstance* si = new TSShapeInstance(mShape, false); + + // Resolve objects transmitted from server + if (!server) { + for (S32 i = 0; i < MaxSounds; i++) + if (sound[i]) + Sim::findObject(SimObjectId(sound[i]),sound[i]); + + if (tireEmitter) + Sim::findObject(SimObjectId(tireEmitter),tireEmitter); + } + + // Extract wheel information from the shape + TSThread* thread = si->addThread(); + Wheel* wp = wheel; + char buff[10]; + for (S32 i = 0; i < MaxWheels; i++) { + + // The wheel must have a hub node to operate at all. + dSprintf(buff,sizeof(buff),"hub%d",i); + wp->springNode = mShape->findNode(buff); + if (wp->springNode != -1) { + + // Check for spring animation.. If there is none we just grab + // the current position of the hub. Otherwise we'll animate + // and get the position at time 0. + dSprintf(buff,sizeof(buff),"spring%d",i); + wp->springSequence = mShape->findSequence(buff); + if (wp->springSequence == -1) + si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos); + else { + si->setSequence(thread,wp->springSequence,0); + si->animate(); + si->mNodeTransforms[wp->springNode].getColumn(3, &wp->pos); + + // Determin the length of the animation so we can scale it + // according the actual wheel position. + Point3F downPos; + si->setSequence(thread,wp->springSequence,1); + si->animate(); + si->mNodeTransforms[wp->springNode].getColumn(3, &downPos); + wp->springLength = wp->pos.z - downPos.z; + if (!wp->springLength) + wp->springSequence = -1; + } + + // Match wheels that are mirrored along the Y axis. + mirrorWheel(wp); + wp++; + } + } + wheelCount = wp - wheel; + + // Check for steering. Should think about normalizing the + // steering animation the way the suspension is, but I don't + // think it's as critical. + steeringSequence = mShape->findSequence("steering"); + + // Brakes + brakeLightSequence = mShape->findSequence("brakelight"); + + // Extract collision planes from shape collision detail level + if (collisionDetails[0] != -1) { + MatrixF imat(1); + SphereF sphere; + sphere.center = mShape->center; + sphere.radius = mShape->radius; + PlaneExtractorPolyList polyList; + polyList.mPlaneList = &rigidBody.mPlaneList; + polyList.setTransform(&imat, Point3F(1,1,1)); + si->buildPolyList(&polyList,collisionDetails[0]); + } + + delete si; + return true; +} + + +//---------------------------------------------------------------------------- +/** Find a matching lateral wheel + Looks for a matching wheeling mirrored along the Y axis, within some + tolerance (current 0.5m), if one is found, the two wheels are lined up. +*/ +bool WheeledVehicleData::mirrorWheel(Wheel* we) +{ + we->opposite = -1; + for (Wheel* wp = wheel; wp != we; wp++) + if (mFabs(wp->pos.y - we->pos.y) < 0.5) + { + we->pos.x = -wp->pos.x; + we->pos.y = wp->pos.y; + we->pos.z = wp->pos.z; + we->opposite = wp - wheel; + wp->opposite = we - wheel; + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicleData::initPersistFields() +{ + addField("jetSound", TypeSFXProfilePtr, Offset(sound[JetSound], WheeledVehicleData)); + addField("engineSound", TypeSFXProfilePtr, Offset(sound[EngineSound], WheeledVehicleData)); + addField("squealSound", TypeSFXProfilePtr, Offset(sound[SquealSound], WheeledVehicleData)); + addField("WheelImpactSound", TypeSFXProfilePtr, Offset(sound[WheelImpactSound], WheeledVehicleData)); + + addField("tireEmitter",TypeParticleEmitterDataPtr, Offset(tireEmitter, WheeledVehicleData)); + addField("maxWheelSpeed", TypeF32, Offset(maxWheelSpeed, WheeledVehicleData)); + addField("engineTorque", TypeF32, Offset(engineTorque, WheeledVehicleData)); + addField("engineBrake", TypeF32, Offset(engineBrake, WheeledVehicleData)); + addField("brakeTorque", TypeF32, Offset(brakeTorque, WheeledVehicleData)); + + Parent::initPersistFields(); +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicleData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (stream->writeFlag(tireEmitter)) + stream->writeRangedU32(packed? SimObjectId(tireEmitter): + tireEmitter->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + + for (S32 i = 0; i < MaxSounds; i++) + if (stream->writeFlag(sound[i])) + stream->writeRangedU32(packed? SimObjectId(sound[i]): + sound[i]->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + + stream->write(maxWheelSpeed); + stream->write(engineTorque); + stream->write(engineBrake); + stream->write(brakeTorque); +} + +void WheeledVehicleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + tireEmitter = stream->readFlag()? + (ParticleEmitterData*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + + for (S32 i = 0; i < MaxSounds; i++) + sound[i] = stream->readFlag()? + (SFXProfile*) stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast): 0; + + stream->read(&maxWheelSpeed); + stream->read(&engineTorque); + stream->read(&engineBrake); + stream->read(&brakeTorque); +} + + +//---------------------------------------------------------------------------- +// Wheeled Vehicle Class +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +IMPLEMENT_CO_NETOBJECT_V1(WheeledVehicle); + +WheeledVehicle::WheeledVehicle() +{ + mDataBlock = 0; + mBraking = false; + mJetSound = NULL; + mEngineSound = NULL; + mSquealSound = NULL; + mTailLightThread = 0; + mSteeringThread = 0; + + for (S32 i = 0; i < WheeledVehicleData::MaxWheels; i++) { + mWheel[i].springThread = 0; + mWheel[i].Dy = mWheel[i].Dx = 0; + mWheel[i].tire = 0; + mWheel[i].spring = 0; + mWheel[i].shapeInstance = 0; + mWheel[i].steering = 0; + mWheel[i].powered = true; + mWheel[i].slipping = false; + } +} + +WheeledVehicle::~WheeledVehicle() +{ +} + +void WheeledVehicle::initPersistFields() +{ + Parent::initPersistFields(); +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::onAdd() +{ + if(!Parent::onAdd()) + return false; + + addToScene(); + if (isServerObject()) + scriptOnAdd(); + return true; +} + +void WheeledVehicle::onRemove() +{ + // Delete the wheel resources + if (mDataBlock != NULL) { + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) { + if (!wheel->emitter.isNull()) + wheel->emitter->deleteWhenEmpty(); + delete wheel->shapeInstance; + } + } + + // Stop the sounds + SFX_DELETE( mJetSound ); + SFX_DELETE( mEngineSound ); + SFX_DELETE( mSquealSound ); + + // + scriptOnRemove(); + removeFromScene(); + Parent::onRemove(); +} + + +//---------------------------------------------------------------------------- + +bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr) +{ + // Delete any existing wheel resources if we're switching + // datablocks. + if (mDataBlock) + { + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (!wheel->emitter.isNull()) + { + wheel->emitter->deleteWhenEmpty(); + wheel->emitter = 0; + } + delete wheel->shapeInstance; + wheel->shapeInstance = 0; + } + } + + // Load up the new datablock + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + // Set inertial tensor, default for the vehicle is sphere + if (mDataBlock->massBox.x > 0 && mDataBlock->massBox.y > 0 && mDataBlock->massBox.z > 0) + mRigid.setObjectInertia(mDataBlock->massBox); + else + mRigid.setObjectInertia(mObjBox.maxExtents - mObjBox.minExtents); + + // Initialize the wheels... + for (S32 i = 0; i < mDataBlock->wheelCount; i++) + { + Wheel* wheel = &mWheel[i]; + wheel->data = &mDataBlock->wheel[i]; + wheel->tire = 0; + wheel->spring = 0; + + wheel->surface.contact = false; + wheel->surface.object = NULL; + wheel->avel = 0; + wheel->apos = 0; + wheel->extension = 1; + wheel->slip = 0; + + wheel->springThread = 0; + wheel->emitter = 0; + + // Steering on the front tires by default + if (wheel->data->pos.y > 0) + wheel->steering = 1; + + // Build wheel animation threads + if (wheel->data->springSequence != -1) { + wheel->springThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(wheel->springThread,wheel->data->springSequence,0); + } + + // Each wheel get's it's own particle emitter + if (mDataBlock->tireEmitter) { + wheel->emitter = new ParticleEmitter; + wheel->emitter->onNewDataBlock(mDataBlock->tireEmitter); + wheel->emitter->registerObject(); + } + } + + // Steering sequence + if (mDataBlock->steeringSequence != -1) { + mSteeringThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mSteeringThread,mDataBlock->steeringSequence,0); + } + else + mSteeringThread = 0; + + // Break light sequence + if (mDataBlock->brakeLightSequence != -1) { + mTailLightThread = mShapeInstance->addThread(); + mShapeInstance->setSequence(mTailLightThread,mDataBlock->brakeLightSequence,0); + } + else + mTailLightThread = 0; + + if (isGhost()) + { + // Create the sounds ahead of time. This reduces runtime + // costs and makes the system easier to understand. + + SFX_DELETE( mEngineSound ); + SFX_DELETE( mSquealSound ); + SFX_DELETE( mJetSound ); + + if ( mDataBlock->sound[WheeledVehicleData::EngineSound] ) + mEngineSound = SFX->createSource( mDataBlock->sound[WheeledVehicleData::EngineSound], &getTransform() ); + + if ( mDataBlock->sound[WheeledVehicleData::SquealSound] ) + mSquealSound = SFX->createSource( mDataBlock->sound[WheeledVehicleData::SquealSound], &getTransform() ); + + if ( mDataBlock->sound[WheeledVehicleData::JetSound] ) + mJetSound = SFX->createSource( mDataBlock->sound[WheeledVehicleData::JetSound], &getTransform() ); + } + + scriptOnNewDataBlock(); + return true; +} + + +//---------------------------------------------------------------------------- + +S32 WheeledVehicle::getWheelCount() +{ + // Return # of hubs defined on the car body + return mDataBlock? mDataBlock->wheelCount: 0; +} + +void WheeledVehicle::setWheelSteering(S32 wheel,F32 steering) +{ + AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds"); + mWheel[wheel].steering = mClampF(steering,-1,1); + setMaskBits(WheelMask); +} + +void WheeledVehicle::setWheelPowered(S32 wheel,bool powered) +{ + AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds"); + mWheel[wheel].powered = powered; + setMaskBits(WheelMask); +} + +void WheeledVehicle::setWheelTire(S32 wheel,WheeledVehicleTire* tire) +{ + AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds"); + mWheel[wheel].tire = tire; + setMaskBits(WheelMask); +} + +void WheeledVehicle::setWheelSpring(S32 wheel,WheeledVehicleSpring* spring) +{ + AssertFatal(wheel >= 0 && wheel < WheeledVehicleData::MaxWheels,"Wheel index out of bounds"); + mWheel[wheel].spring = spring; + setMaskBits(WheelMask); +} + +void WheeledVehicle::getWheelInstAndTransform( U32 index, TSShapeInstance** inst, MatrixF* xfrm ) const +{ + AssertFatal( index < WheeledVehicleData::MaxWheels, + "WheeledVehicle::getWheelInstAndTransform() - Bad wheel index!" ); + + const Wheel* wheel = &mWheel[index]; + *inst = wheel->shapeInstance; + + if ( !xfrm || !wheel->shapeInstance ) + return; + + MatrixF world = getRenderTransform(); + world.scale( mObjScale ); + + // Steering & spring extension + MatrixF hub(EulerF(0,0,mSteering.x * wheel->steering)); + Point3F pos = wheel->data->pos; + pos.z -= wheel->spring->length * wheel->extension; + hub.setColumn(3,pos); + world.mul(hub); + + // Wheel rotation + MatrixF rot(EulerF(wheel->apos * M_2PI,0,0)); + world.mul(rot); + + // Rotation the tire to face the right direction + // (could pre-calculate this) + MatrixF wrot(EulerF(0,0,(wheel->data->pos.x > 0)? M_PI/2: -M_PI/2)); + world.mul(wrot); + + *xfrm = world; +} + +//---------------------------------------------------------------------------- + +void WheeledVehicle::processTick(const Move* move) +{ + Parent::processTick(move); +} + +void WheeledVehicle::updateMove(const Move* move) +{ + Parent::updateMove(move); + + // Break on trigger + mBraking = move->trigger[2]; + + // Set the tail brake light thread direction based on the brake state. + if (mTailLightThread) + mShapeInstance->setTimeScale(mTailLightThread, mBraking? 1.0f : -1.0f); +} + + +//---------------------------------------------------------------------------- + +void WheeledVehicle::advanceTime(F32 dt) +{ + PROFILE_SCOPE( WheeledVehicle_AdvanceTime ); + + Parent::advanceTime(dt); + + // Stick the wheels to the ground. This is purely so they look + // good while the vehicle is being interpolated. + extendWheels(); + + // Update wheel angular position and slip, this is a client visual + // feature only, it has no affect on the physics. + F32 slipTotal = 0; + F32 torqueTotal = 0; + + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + if (wheel->tire && wheel->spring) { + // Update angular position + wheel->apos += (wheel->avel * dt) / M_2PI; + wheel->apos -= mFloor(wheel->apos); + if (wheel->apos < 0) + wheel->apos = 1 - wheel->apos; + + // Keep track of largest slip + slipTotal += wheel->slip; + torqueTotal += wheel->torqueScale; + } + + // Update the sounds based on wheel slip and torque output + updateSquealSound(slipTotal / mDataBlock->wheelCount); + updateEngineSound(sIdleEngineVolume + (1 - sIdleEngineVolume) * + (1 - (torqueTotal / mDataBlock->wheelCount))); + updateJetSound(); + + updateWheelThreads(); + updateWheelParticles(dt); + + // Update the steering animation: sequence time 0 is full right, + // and time 0.5 is straight ahead. + if (mSteeringThread) { + F32 t = (mSteering.x * mFabs(mSteering.x)) / mDataBlock->maxSteeringAngle; + mShapeInstance->setPos(mSteeringThread,0.5 - t * 0.5); + } + + // Animate the tail light. The direction of the thread is + // set based on vehicle braking. + if (mTailLightThread) + mShapeInstance->advanceTime(dt,mTailLightThread); +} + + +//---------------------------------------------------------------------------- +/** Update the rigid body forces on the vehicle + This method calculates the forces acting on the body, including gravity, + suspension & tire forces. +*/ +void WheeledVehicle::updateForces(F32 dt) +{ + PROFILE_SCOPE( WheeledVehicle_UpdateForces ); + + extendWheels(); + + F32 aMomentum = mMass / mDataBlock->wheelCount; + + // Get the current matrix and extact vectors + MatrixF currMatrix; + mRigid.getTransform(&currMatrix); + + Point3F bx,by,bz; + currMatrix.getColumn(0,&bx); + currMatrix.getColumn(1,&by); + currMatrix.getColumn(2,&bz); + + // Steering angles from current steering wheel position + F32 quadraticSteering = -(mSteering.x * mFabs(mSteering.x)); + F32 cosSteering,sinSteering; + mSinCos(quadraticSteering, sinSteering, cosSteering); + + // Calculate Engine and brake torque values used later by in + // wheel calculations. + F32 engineTorque,brakeVel; + if (mBraking) + { + brakeVel = (mDataBlock->brakeTorque / aMomentum) * dt; + engineTorque = 0; + } + else + { + if (mThrottle) + { + engineTorque = mDataBlock->engineTorque * mThrottle; + brakeVel = 0; + // Double the engineTorque to help out the jets + if (mThrottle > 0 && mJetting) + engineTorque *= 2; + } + else + { + // Engine break. + brakeVel = (mDataBlock->engineBrake / aMomentum) * dt; + engineTorque = 0; + } + } + + // Integrate forces, we'll do this ourselves here instead of + // relying on the rigid class which does it during movement. + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + mRigid.force.set(0, 0, 0); + mRigid.torque.set(0, 0, 0); + + // Calculate vertical load for friction. Divide up the spring + // forces across all the wheels that are in contact with + // the ground. + U32 contactCount = 0; + F32 verticalLoad = 0; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (wheel->tire && wheel->spring && wheel->surface.contact) + { + verticalLoad += wheel->spring->force * (1 - wheel->extension); + contactCount++; + } + } + if (contactCount) + verticalLoad /= contactCount; + + // Sum up spring and wheel torque forces + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (!wheel->tire || !wheel->spring) + continue; + + F32 Fy = 0; + if (wheel->surface.contact) + { + + // First, let's compute the wheel's position, and worldspace velocity + Point3F pos, r, localVel; + currMatrix.mulP(wheel->data->pos, &pos); + mRigid.getOriginVector(pos,&r); + mRigid.getVelocity(r, &localVel); + + // Spring force & damping + F32 spring = wheel->spring->force * (1 - wheel->extension); + + if (wheel->extension == 0) //spring fully compressed + { + // Apply impulses to the rigid body to keep it from + // penetrating the surface. + F32 n = -mDot(localVel,Point3F(0,0,1)); + if (n >= 0) + { + // Collision impulse, straight forward force stuff. + F32 d = mRigid.getZeroImpulse(r,Point3F(0,0,1)); + F32 j = n * (1 + mRigid.restitution) * d; + mRigid.force += Point3F(0,0,1) * j; + } + } + + F32 damping = wheel->spring->damping * -(mDot(bz, localVel) / wheel->spring->length); + if (damping < 0) + damping = 0; + + // Anti-sway force based on difference in suspension extension + F32 antiSway = 0; + if (wheel->data->opposite != -1) + { + Wheel* oppositeWheel = &mWheel[wheel->data->opposite]; + if (oppositeWheel->surface.contact) + antiSway = ((oppositeWheel->extension - wheel->extension) * + wheel->spring->antiSway); + if (antiSway < 0) + antiSway = 0; + } + + // Spring forces act straight up and are applied at the + // spring's root position. + Point3F t, forceVector = bz * (spring + damping + antiSway); + mCross(r, forceVector, &t); + mRigid.torque += t; + mRigid.force += forceVector; + + // Tire direction vectors perpendicular to surface normal + Point3F wheelXVec = bx * cosSteering; + wheelXVec += by * sinSteering * wheel->steering; + Point3F tireX, tireY; + mCross(wheel->surface.normal, wheelXVec, &tireY); + tireY.normalize(); + mCross(tireY, wheel->surface.normal, &tireX); + tireX.normalize(); + + // Velocity of tire at the surface contact + Point3F wheelContact, wheelVelocity; + mRigid.getOriginVector(wheel->surface.pos,&wheelContact); + mRigid.getVelocity(wheelContact, &wheelVelocity); + + F32 xVelocity = mDot(tireX, wheelVelocity); + F32 yVelocity = mDot(tireY, wheelVelocity); + + // Tires act as springs and generate lateral and longitudinal + // forces to move the vehicle. These distortion/spring forces + // are what convert wheel angular velocity into forces that + // act on the rigid body. + + // Longitudinal tire deformation force + F32 ddy = (wheel->avel * wheel->tire->radius - yVelocity) - + wheel->tire->longitudinalRelaxation * + mFabs(wheel->avel) * wheel->Dy; + wheel->Dy += ddy * dt; + Fy = (wheel->tire->longitudinalForce * wheel->Dy + + wheel->tire->longitudinalDamping * ddy); + + // Lateral tire deformation force + F32 ddx = xVelocity - wheel->tire->lateralRelaxation * + mFabs(wheel->avel) * wheel->Dx; + wheel->Dx += ddx * dt; + F32 Fx = -(wheel->tire->lateralForce * wheel->Dx + + wheel->tire->lateralDamping * ddx); + + // Vertical load on the tire + verticalLoad = spring + damping + antiSway; + if (verticalLoad < 0) + verticalLoad = 0; + + // Adjust tire forces based on friction + F32 surfaceFriction = 1; + F32 mu = surfaceFriction * (wheel->slipping ? wheel->tire->kineticFriction : wheel->tire->staticFriction); + F32 Fn = verticalLoad * mu; Fn *= Fn; + F32 Fw = Fx * Fx + Fy * Fy; + if (Fw > Fn) + { + F32 K = mSqrt(Fn / Fw); + Fy *= K; + Fx *= K; + wheel->Dy *= K; + wheel->Dx *= K; + wheel->slip = 1 - K; + wheel->slipping = true; + } + else + { + wheel->slipping = false; + wheel->slip = 0; + } + + // Tire forces act through the tire direction vectors parallel + // to the surface and are applied at the wheel hub. + forceVector = (tireX * Fx) + (tireY * Fy); + pos -= bz * (wheel->spring->length * wheel->extension); + mRigid.getOriginVector(pos,&r); + mCross(r, forceVector, &t); + mRigid.torque += t; + mRigid.force += forceVector; + } + else + { + // Wheel not in contact with the ground + wheel->torqueScale = 0; + wheel->slip = 0; + + // Relax the tire deformation + wheel->Dy += (-wheel->tire->longitudinalRelaxation * + mFabs(wheel->avel) * wheel->Dy) * dt; + wheel->Dx += (-wheel->tire->lateralRelaxation * + mFabs(wheel->avel) * wheel->Dx) * dt; + } + + // Adjust the wheel's angular velocity based on engine torque + // and tire deformation forces. + if (wheel->powered) + { + F32 maxAvel = mDataBlock->maxWheelSpeed / wheel->tire->radius; + wheel->torqueScale = (mFabs(wheel->avel) > maxAvel) ? 0 : + 1 - (mFabs(wheel->avel) / maxAvel); + } + else + wheel->torqueScale = 0; + wheel->avel += (((wheel->torqueScale * engineTorque) - Fy * + wheel->tire->radius) / aMomentum) * dt; + + // Adjust the wheel's angular velocity based on break torque. + // This is done after avel update to make sure we come to a + // complete stop. + if (brakeVel > mFabs(wheel->avel)) + wheel->avel = 0; + else + if (wheel->avel > 0) + wheel->avel -= brakeVel; + else + wheel->avel += brakeVel; + } + + // Jet Force + if (mJetting) + mRigid.force += by * mDataBlock->jetForce; + + // Container drag & buoyancy + mRigid.force += Point3F(0, 0, -mBuoyancy * sWheeledVehicleGravity * mRigid.mass); + mRigid.force -= mRigid.linVelocity * mDrag; + mRigid.torque -= mRigid.angMomentum * mDrag; + + // If we've added anything other than gravity, then we're no + // longer at rest. Could test this a little more efficiently... + if (mRigid.atRest && (mRigid.force.len() || mRigid.torque.len())) + mRigid.atRest = false; + + // Gravity + mRigid.force += Point3F(0, 0, sWheeledVehicleGravity * mRigid.mass); + + // Integrate and update velocity + mRigid.linMomentum += mRigid.force * dt; + mRigid.angMomentum += mRigid.torque * dt; + mRigid.updateVelocity(); + + // Since we've already done all the work, just need to clear this out. + mRigid.force.set(0, 0, 0); + mRigid.torque.set(0, 0, 0); + + // If we're still atRest, make sure we're not accumulating anything + if (mRigid.atRest) + mRigid.setAtRest(); +} + + +//---------------------------------------------------------------------------- +/** Extend the wheels + The wheels are extended until they contact a surface. The extension + is instantaneous. The wheels are extended before force calculations and + also on during client side interpolation (so that the wheels are glued + to the ground). +*/ +void WheeledVehicle::extendWheels(bool clientHack) +{ + PROFILE_SCOPE( WheeledVehicle_ExtendWheels ); + + disableCollision(); + + MatrixF currMatrix; + + if(clientHack) + currMatrix = getRenderTransform(); + else + mRigid.getTransform(&currMatrix); + + + // Does a single ray cast down for now... this will have to be + // changed to something a little more complicated to avoid getting + // stuck in cracks. + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (wheel->tire && wheel->spring) + { + wheel->extension = 1; + + // The ray is cast from the spring mount point to the tip of + // the tire. If there is a collision the spring extension is + // adjust to remove the tire radius. + Point3F sp,vec; + currMatrix.mulP(wheel->data->pos,&sp); + currMatrix.mulV(VectorF(0,0,-wheel->spring->length),&vec); + F32 ts = wheel->tire->radius / wheel->spring->length; + Point3F ep = sp + (vec * (1 + ts)); + ts = ts / (1+ts); + + RayInfo rInfo; + if (mContainer->castRay(sp, ep, sClientCollisionMask & ~PlayerObjectType, &rInfo)) + { + wheel->surface.contact = true; + wheel->extension = (rInfo.t < ts)? 0: (rInfo.t - ts) / (1 - ts); + wheel->surface.normal = rInfo.normal; + wheel->surface.pos = rInfo.point; + wheel->surface.material = rInfo.material; + wheel->surface.object = rInfo.object; + } + else + { + wheel->surface.contact = false; + wheel->slipping = true; + } + } + } + enableCollision(); +} + + +//---------------------------------------------------------------------------- +/** Update wheel steering and suspension threads. + These animations are purely cosmetic and this method is only invoked + on the client. +*/ +void WheeledVehicle::updateWheelThreads() +{ + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (wheel->tire && wheel->spring && wheel->springThread) + { + // Scale the spring animation time to match the current + // position of the wheel. We'll also check to make sure + // the animation is long enough, if it isn't, just stick + // it at the end. + F32 pos = wheel->extension * wheel->spring->length; + if (pos > wheel->data->springLength) + pos = 1; + else + pos /= wheel->data->springLength; + mShapeInstance->setPos(wheel->springThread,pos); + } + } +} + +//---------------------------------------------------------------------------- +/** Update wheel particles effects + These animations are purely cosmetic and this method is only invoked + on the client. Particles are emitted as long as the moving. +*/ +void WheeledVehicle::updateWheelParticles(F32 dt) +{ + // OMG l33t hax + extendWheels(true); + + Point3F vel = Parent::getVelocity(); + F32 speed = vel.len(); + + // Don't bother if we're not moving. + if (speed > 1.0f) + { + Point3F axis = vel; + axis.normalize(); + + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + // Is this wheel in contact with the ground? + if (wheel->tire && wheel->spring && !wheel->emitter.isNull() && + wheel->surface.contact && wheel->surface.object ) + { + Material* material = ( wheel->surface.material ? dynamic_cast< Material* >( wheel->surface.material->getMaterial() ) : 0 ); + + if( material && material->mShowDust ) + { + ColorF colorList[ ParticleData::PDC_NUM_KEYS ]; + + for( U32 x = 0; x < getMin( Material::NUM_EFFECT_COLOR_STAGES, ParticleData::PDC_NUM_KEYS ); ++ x ) + colorList[ x ] = material->mEffectColor[ x ]; + for( U32 x = Material::NUM_EFFECT_COLOR_STAGES; x < ParticleData::PDC_NUM_KEYS; ++ x ) + colorList[ x ].set( 1.0, 1.0, 1.0, 0.0 ); + + wheel->emitter->setColors( colorList ); + + // Emit the dust, the density (time) is scaled by the + // the vehicles velocity. + wheel->emitter->emitParticles( wheel->surface.pos, true, + axis, vel, (U32)(3/*dt * (speed / mDataBlock->maxWheelSpeed) * 1000 * wheel->slip*/)); + } + } + } + } +} + + +//---------------------------------------------------------------------------- +/** Update engine sound + This method is only invoked by clients. +*/ +void WheeledVehicle::updateEngineSound(F32 level) +{ + if ( !mEngineSound ) + return; + + if ( !mEngineSound->isPlaying() ) + mEngineSound->play(); + + mEngineSound->setTransform( getTransform() ); + mEngineSound->setVelocity( getVelocity() ); + //mEngineSound->setVolume( level ); + + // Adjust pitch + F32 pitch = ((level-sIdleEngineVolume) * 1.3f); + if (pitch < 0.4f) + pitch = 0.4f; + + mEngineSound->setPitch( pitch ); +} + + +//---------------------------------------------------------------------------- +/** Update wheel skid sound + This method is only invoked by clients. +*/ +void WheeledVehicle::updateSquealSound(F32 level) +{ + if ( !mSquealSound ) + return; + + if ( level < sMinSquealVolume ) + { + mSquealSound->stop(); + return; + } + + if ( !mSquealSound->isPlaying() ) + mSquealSound->play(); + + mSquealSound->setTransform( getTransform() ); + mSquealSound->setVolume( level ); +} + + +//---------------------------------------------------------------------------- +/** Update jet sound + This method is only invoked by clients. +*/ +void WheeledVehicle::updateJetSound() +{ + if ( !mJetSound ) + return; + + if ( !mJetting ) + { + mJetSound->stop(); + return; + } + + if ( !mJetSound->isPlaying() ) + mJetSound->play(); + + mJetSound->setTransform( getTransform() ); +} + + +//---------------------------------------------------------------------------- + +U32 WheeledVehicle::getCollisionMask() +{ + return sClientCollisionMask; +} + + +//---------------------------------------------------------------------------- +/** Build a collision polylist + The polylist is filled with polygons representing the collision volume + and the wheels. +*/ +bool WheeledVehicle::buildPolyList(AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere) +{ + PROFILE_SCOPE( WheeledVehicle_BuildPolyList ); + + // Parent will take care of body collision. + Parent::buildPolyList(polyList,box,sphere); + + // Add wheels as boxes. + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) { + if (wheel->tire && wheel->spring) { + Box3F wbox; + F32 radius = wheel->tire->radius; + wbox.minExtents.x = -(wbox.maxExtents.x = radius / 2); + wbox.minExtents.y = -(wbox.maxExtents.y = radius); + wbox.minExtents.z = -(wbox.maxExtents.z = radius); + MatrixF mat = mObjToWorld; + + Point3F sp,vec; + mObjToWorld.mulP(wheel->data->pos,&sp); + mObjToWorld.mulV(VectorF(0,0,-wheel->spring->length),&vec); + Point3F ep = sp + (vec * wheel->extension); + mat.setColumn(3,ep); + polyList->setTransform(&mat,Point3F(1,1,1)); + polyList->addBox(wbox); + } + } + return !polyList->isEmpty(); +} + +void WheeledVehicle::prepBatchRender(SceneState* state, S32 mountedImageIndex ) +{ + Parent::prepBatchRender( state, mountedImageIndex ); + + if ( mountedImageIndex != -1 ) + return; + + // Set up our render state *here*, + // before the push world matrix, so + // that wheel rendering will be correct. + TSRenderState rdata; + rdata.setSceneState( state ); + + // Shape transform + GFX->pushWorldMatrix(); + + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + GFX->setWorldMatrix( mat ); + + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (wheel->shapeInstance) + { + GFX->pushWorldMatrix(); + + // Steering & spring extension + MatrixF hub(EulerF(0,0,mSteering.x * wheel->steering)); + Point3F pos = wheel->data->pos; + pos.z -= wheel->spring->length * wheel->extension; + hub.setColumn(3,pos); + + GFX->multWorld(hub); + + // Wheel rotation + MatrixF rot(EulerF(wheel->apos * M_2PI,0,0)); + GFX->multWorld(rot); + + // Rotation the tire to face the right direction + // (could pre-calculate this) + MatrixF wrot(EulerF(0,0,(wheel->data->pos.x > 0)? M_PI/2: -M_PI/2)); + GFX->multWorld(wrot); + + // Render! + wheel->shapeInstance->animate(); + wheel->shapeInstance->render( rdata ); + + if (mCloakLevel != 0.0f) + wheel->shapeInstance->setAlphaAlways(1.0f - mCloakLevel); + else + wheel->shapeInstance->setAlphaAlways(1.0f); + + GFX->popWorldMatrix(); + } + } + + GFX->popWorldMatrix(); + +} + +//---------------------------------------------------------------------------- + +void WheeledVehicle::writePacketData(GameConnection *connection, BitStream *stream) +{ + Parent::writePacketData(connection, stream); + stream->writeFlag(mBraking); + + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + stream->write(wheel->avel); + stream->write(wheel->Dy); + stream->write(wheel->Dx); + stream->writeFlag(wheel->slipping); + } +} + +void WheeledVehicle::readPacketData(GameConnection *connection, BitStream *stream) +{ + Parent::readPacketData(connection, stream); + mBraking = stream->readFlag(); + + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + stream->read(&wheel->avel); + stream->read(&wheel->Dy); + stream->read(&wheel->Dx); + wheel->slipping = stream->readFlag(); + } + + // Rigid state is transmitted by the parent... + setPosition(mRigid.linPosition,mRigid.angPosition); + mDelta.pos = mRigid.linPosition; + mDelta.rot[1] = mRigid.angPosition; +} + + +//---------------------------------------------------------------------------- + +U32 WheeledVehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // Update wheel datablock information + if (stream->writeFlag(mask & WheelMask)) + { + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (stream->writeFlag(wheel->tire && wheel->spring)) + { + stream->writeRangedU32(wheel->tire->getId(), + DataBlockObjectIdFirst,DataBlockObjectIdLast); + stream->writeRangedU32(wheel->spring->getId(), + DataBlockObjectIdFirst,DataBlockObjectIdLast); + stream->writeFlag(wheel->powered); + + // Steering must be sent with full precision as it's + // used directly in state force calculations. + stream->write(wheel->steering); + } + } + } + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + if (stream->writeFlag(getControllingClient() == con && !(mask & InitialUpdateMask))) + return retMask; + + stream->writeFlag(mBraking); + + if (stream->writeFlag(mask & PositionMask)) + { + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + stream->write(wheel->avel); + stream->write(wheel->Dy); + stream->write(wheel->Dx); + } + } + return retMask; +} + +void WheeledVehicle::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con,stream); + + // Update wheel datablock information + if (stream->readFlag()) + { + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + if (stream->readFlag()) + { + SimObjectId tid = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast); + SimObjectId sid = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast); + if (!Sim::findObject(tid,wheel->tire) || !Sim::findObject(sid,wheel->spring)) + { + con->setLastError("Invalid packet WheeledVehicle::unpackUpdate()"); + return; + } + wheel->powered = stream->readFlag(); + stream->read(&wheel->steering); + + // Create an instance of the tire for rendering + delete wheel->shapeInstance; + wheel->shapeInstance = (wheel->tire->shape == NULL) ? 0: + new TSShapeInstance(wheel->tire->shape); + } + } + } + + // After this is data that we only need if we're not the + // controlling client. + if (stream->readFlag()) + return; + + mBraking = stream->readFlag(); + + if (stream->readFlag()) + { + Wheel* wend = &mWheel[mDataBlock->wheelCount]; + for (Wheel* wheel = mWheel; wheel < wend; wheel++) + { + stream->read(&wheel->avel); + stream->read(&wheel->Dy); + stream->read(&wheel->Dx); + } + } +} + + +//---------------------------------------------------------------------------- +// Console Methods +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +ConsoleMethod(WheeledVehicle, setWheelSteering, bool, 4, 4, "obj.setWheelSteering(wheel#,float)") +{ + S32 wheel = dAtoi(argv[2]); + if (wheel >= 0 && wheel < object->getWheelCount()) { + object->setWheelSteering(wheel,dAtof(argv[3])); + return true; + } + else + Con::warnf("setWheelSteering: wheel index out of bounds, vehicle has %d hubs", + argv[3],object->getWheelCount()); + return false; +} + +ConsoleMethod(WheeledVehicle, setWheelPowered, bool, 4, 4, "obj.setWheelPowered(wheel#,bool)") +{ + S32 wheel = dAtoi(argv[2]); + if (wheel >= 0 && wheel < object->getWheelCount()) { + object->setWheelPowered(wheel,dAtob(argv[3])); + return true; + } + else + Con::warnf("setWheelPowered: wheel index out of bounds, vehicle has %d hubs", + argv[3],object->getWheelCount()); + return false; +} + +ConsoleMethod(WheeledVehicle, setWheelTire, bool, 4, 4, "obj.setWheelTire(wheel#,tire)") +{ + WheeledVehicleTire* tire; + if (Sim::findObject(argv[3],tire)) { + S32 wheel = dAtoi(argv[2]); + if (wheel >= 0 && wheel < object->getWheelCount()) { + object->setWheelTire(wheel,tire); + return true; + } + else + Con::warnf("setWheelTire: wheel index out of bounds, vehicle has %d hubs", + argv[3],object->getWheelCount()); + } + else + Con::warnf("setWheelTire: %s datablock does not exist (or is not a tire)",argv[3]); + return false; +} + +ConsoleMethod(WheeledVehicle, setWheelSpring, bool, 4, 4, "obj.setWheelSpring(wheel#,spring)") +{ + WheeledVehicleSpring* spring; + if (Sim::findObject(argv[3],spring)) { + S32 wheel = dAtoi(argv[2]); + if (wheel >= 0 && wheel < object->getWheelCount()) { + object->setWheelSpring(wheel,spring); + return true; + } + else + Con::warnf("setWheelSpring: wheel index out of bounds, vehicle has %d hubs", + argv[3],object->getWheelCount()); + } + else + Con::warnf("setWheelSpring: %s datablock does not exist (or is not a spring)",argv[3]); + return false; +} + +ConsoleMethod(WheeledVehicle, getWheelCount, S32, 2, 2, "obj.getWheelCount()") +{ + return object->getWheelCount(); +} diff --git a/T3D/vehicles/wheeledVehicle.h b/T3D/vehicles/wheeledVehicle.h new file mode 100644 index 0000000..b340d41 --- /dev/null +++ b/T3D/vehicles/wheeledVehicle.h @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WHEELEDVEHICLE_H_ +#define _WHEELEDVEHICLE_H_ + +#ifndef _VEHICLE_H_ +#include "T3D/vehicles/vehicle.h" +#endif + +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif + +class ParticleEmitter; +class ParticleEmitterData; + + +//---------------------------------------------------------------------------- + +struct WheeledVehicleTire: public SimDataBlock +{ + typedef SimDataBlock Parent; + + // + StringTableEntry shapeName;// Max shape to render + + // Physical properties + F32 mass; // Mass of the whole wheel + F32 kineticFriction; // Tire friction coefficient + F32 staticFriction; // Tire friction coefficient + F32 restitution; // Currently not used + + // Tires act as springs and generate lateral and longitudinal + // forces to move the vehicle. These distortion/spring forces + // are what convert wheel angular velocity into forces that + // act on the rigid body. + F32 lateralForce; // Spring force + F32 lateralDamping; // Damping force + F32 lateralRelaxation; // The tire will relax if left alone + F32 longitudinalForce; + F32 longitudinalDamping; + F32 longitudinalRelaxation; + + // Shape information initialized in the preload + Resource shape; // The loaded shape + F32 radius; // Tire radius + + // + WheeledVehicleTire(); + DECLARE_CONOBJECT(WheeledVehicleTire); + static void initPersistFields(); + bool preload(bool, String &errorStr); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +struct WheeledVehicleSpring: public SimDataBlock +{ + typedef SimDataBlock Parent; + + F32 length; // Travel distance from root hub position + F32 force; // Spring force + F32 damping; // Damping force + F32 antiSway; // Opposite wheel anti-sway + + // + WheeledVehicleSpring(); + DECLARE_CONOBJECT(WheeledVehicleSpring); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +struct WheeledVehicleData: public VehicleData +{ + typedef VehicleData Parent; + + enum Constants + { + MaxWheels = 8, + MaxWheelBits = 3 + }; + + enum Sounds + { + JetSound, + EngineSound, + SquealSound, + WheelImpactSound, + MaxSounds, + }; + SFXProfile* sound[MaxSounds]; + + ParticleEmitterData* tireEmitter; + + F32 maxWheelSpeed; // Engine torque is scale based on wheel speed + F32 engineTorque; // Engine force controlled through throttle + F32 engineBrake; // Break force applied when throttle is 0 + F32 brakeTorque; // Force used when brakeing + + // Initialized onAdd + struct Wheel + { + S32 opposite; // Opposite wheel on Y axis (or -1 for none) + Point3F pos; // Root pos of spring + S32 springNode; // Wheel spring/hub node + S32 springSequence; // Suspension animation + F32 springLength; // Suspension animation length + } wheel[MaxWheels]; + U32 wheelCount; + ClippedPolyList rigidBody; // Extracted from shape + S32 brakeLightSequence; // Brakes + S32 steeringSequence; // Steering animation + + // + WheeledVehicleData(); + DECLARE_CONOBJECT(WheeledVehicleData); + static void initPersistFields(); + bool preload(bool, String &errorStr); + bool mirrorWheel(Wheel* we); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- + +class WheeledVehicle: public Vehicle +{ + typedef Vehicle Parent; + + enum MaskBits + { + WheelMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + WheeledVehicleData* mDataBlock; + + bool mBraking; + TSThread* mTailLightThread; + SFXSource* mJetSound; + SFXSource* mEngineSound; + SFXSource* mSquealSound; + + struct Wheel + { + WheeledVehicleTire *tire; + WheeledVehicleSpring *spring; + WheeledVehicleData::Wheel* data; + + F32 extension; // Spring extension (0-1) + F32 avel; // Angular velocity + F32 apos; // Anuglar position (client side only) + F32 Dy,Dx; // Current tire deformation + + struct Surface + { + bool contact; // Wheel is touching a surface + Point3F normal; // Surface normal + BaseMatInstance* material; // Surface material + Point3F pos; // Point of contact + SceneObject* object; // Object in contact with + } surface; + + TSShapeInstance* shapeInstance; + TSThread* springThread; + + F32 steering; // Wheel steering scale + bool powered; // Powered by engine + bool slipping; // Traction on last tick + F32 torqueScale; // Max torque % applied to wheel (0-1) + F32 slip; // Amount of wheel slip (0-1) + SimObjectPtr emitter; + }; + Wheel mWheel[WheeledVehicleData::MaxWheels]; + TSThread* mSteeringThread; + + // + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move *move); + void updateMove(const Move *move); + void updateForces(F32 dt); + void extendWheels(bool clientHack = false); + void prepBatchRender( SceneState *state, S32 mountedImageIndex ); + + // Client sounds & particles + void updateWheelThreads(); + void updateWheelParticles(F32 dt); + void updateEngineSound(F32 level); + void updateSquealSound(F32 level); + void updateJetSound(); + + virtual U32 getCollisionMask(); + +public: + DECLARE_CONOBJECT(WheeledVehicle); + static void initPersistFields(); + + WheeledVehicle(); + ~WheeledVehicle(); + + bool onAdd(); + void onRemove(); + void advanceTime(F32 dt); + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + + S32 getWheelCount(); + Wheel *getWheel(U32 index) {return &mWheel[index];} + void setWheelSteering(S32 wheel,F32 steering); + void setWheelPowered(S32 wheel,bool powered); + void setWheelTire(S32 wheel,WheeledVehicleTire*); + void setWheelSpring(S32 wheel,WheeledVehicleSpring*); + + void getWheelInstAndTransform( U32 wheel, TSShapeInstance** inst, MatrixF* xfrm ) const; + + void writePacketData(GameConnection * conn, BitStream *stream); + void readPacketData(GameConnection * conn, BitStream *stream); + U32 packUpdate(NetConnection * conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection * conn, BitStream *stream); +}; + + +#endif diff --git a/T3D/zone.cpp b/T3D/zone.cpp new file mode 100644 index 0000000..e581fbe --- /dev/null +++ b/T3D/zone.cpp @@ -0,0 +1,457 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#include "platform/platform.h" +#include "T3D/zone.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxTransformSaver.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" +#include "T3D/portal.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/gfxDrawUtil.h" +#include "math/mathUtils.h" +#include "gfx/gfxTransformSaver.h" + +IMPLEMENT_CO_NETOBJECT_V1( Zone ); + +U32 Zone::smZoneKey = 0; +bool Zone::smRenderZones = false; + +static Point3F gSortPoint; + +int QSORT_CALLBACK cmpZonePortalDistance( const void *p1, const void *p2 ) +{ + const Portal** pd1 = (const Portal**)p1; + const Portal** pd2 = (const Portal**)p2; + + F32 dist1 = ( (*pd1)->getPosition() - gSortPoint ).lenSquared(); + F32 dist2 = ( (*pd2)->getPosition() - gSortPoint ).lenSquared(); + + return dist2 - dist1; +} + +Zone::Zone() +{ + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask |= StaticObjectType; + + mObjScale.set( 10, 10, 10 ); + + mZoneKey = 0; + mOutdoorZoneVisible = false; +} + +Zone::~Zone() +{ +} + +void Zone::initPersistFields() +{ + Parent::initPersistFields(); +} + +void Zone::consoleInit() +{ + Con::addVariable( "$Zone::renderZones", TypeBool, &smRenderZones ); +} + +bool Zone::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ), + Point3F( 0.5f, 0.5f, 0.5f ) ); + + resetWorldBox(); + + mZoneBox = mWorldBox; + + addToScene(); + mSceneManager->registerZones( this, 1 ); + + return true; +} + +void Zone::onRemove() +{ + mSceneManager->unregisterZones( this ); + removeFromScene(); + + Parent::onRemove(); +} + +void Zone::setTransform(const MatrixF & mat) +{ + Parent::setTransform( mat ); + setMaskBits( TransformMask ); +} + +U32 Zone::packUpdate( NetConnection *con, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( con, mask, stream ); + + if ( stream->writeFlag( mask & TransformMask ) ) + { + mathWrite( *stream, mObjToWorld ); + mathWrite( *stream, mObjScale ); + } + + return retMask; +} + +void Zone::unpackUpdate( NetConnection *con, BitStream *stream ) +{ + Parent::unpackUpdate( con, stream ); + + if ( stream->readFlag() ) // TransformMask + { + mathRead(*stream, &mObjToWorld); + mathRead(*stream, &mObjScale); + + setTransform( mObjToWorld ); + } +} + +U32 Zone::getPointZone( const Point3F &p ) +{ + Point3F objPoint( 0, 0, 0 ); + getWorldTransform().mulP( p, &objPoint ); + objPoint.convolveInverse( getScale() ); + + if ( mObjBox.isContained( objPoint ) ) + return mZoneRangeStart; + + return 0; +} + +bool Zone::getOverlappingZones( SceneObject *obj, + U32 *zones, + U32 *numZones ) +{ + const bool isOverlapped = mWorldBox.isOverlapped( obj->getWorldBox() ); + const bool isContained = isOverlapped && mWorldBox.isContained( obj->getWorldBox() ); + + const bool isPortal = dynamic_cast( obj ); + const bool isZone = dynamic_cast( obj ) && !isPortal; + + if ( !obj->isGlobalBounds() && + !isContained && + isOverlapped && + !isZone ) + { + *numZones = 1; + zones[0] = mZoneRangeStart; + return true; + } + + if ( isContained && !isZone ) + { + *numZones = 1; + zones[0] = mZoneRangeStart; + return false; + } + + *numZones = 0; + return true; +} + +bool Zone::scopeObject( const Point3F &rootPosition, + const F32 rootDistance, + bool *zoneScopeState ) +{ + if ( getPointZone( rootPosition ) != 0 ) + { + zoneScopeState[mZoneRangeStart] = true; + + Vector zoneStack; + zoneStack.push_back( this ); + + while ( zoneStack.size() ) + { + Zone *zone = zoneStack.last(); + if ( !zone ) + continue; + + zone->mLastStateKey = mLastStateKey; + + if ( (zone->getPosition() - rootPosition).len() <= rootDistance ) + zoneScopeState[zone->mZoneRangeStart] = true; + + zoneStack.pop_back(); + + // Go through this zone's portals + // and determine if the camera point + // is on the same side of the portal's + // plane as this zone. + for ( U32 i = 0; i < zone->mPortals.size(); i++ ) + { + Portal *portal = zone->mPortals[i]; + + PlaneF portalPlane( portal->getPosition(), portal->getTransform().getForwardVector() ); + + PlaneF::Side camSide = portalPlane.whichSide( rootPosition ); + PlaneF::Side zoneSide = portalPlane.whichSide( getPosition() ); + + if ( camSide == zoneSide ) + { + // Push back the zones for this portal. + Zone *z0 = portal->getZone( 0 ); + Zone *z1 = portal->getZone( 1 ); + + if ( z0 && z0->mLastStateKey != mLastStateKey ) + zoneStack.push_back( z0 ); + + if ( z1 && z1->mLastStateKey != mLastStateKey ) + zoneStack.push_back( z1 ); + } + } + } + + return true; + } + + return false; +} + +bool Zone::prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ) +{ + if ( isLastState(state, stateKey ) ) + return false; + + setLastState( state, stateKey ); + + // This flag will be set + // if the zone traversal + // determines that a portal + // linking to the outside + // zone is currently visible. + bool renderOutside = false; + + if ( startZone == mZoneRangeStart ) + renderOutside = _traverseZones( state ); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Zone::renderObject ); + ri->type = RenderPassManager::RIT_Object; + ri->defaultKey = 0; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return renderOutside; +} + +void Zone::renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat ) +{ + if (overrideMat) + return; + + // Only render if the zone render + // flag is enabled, or this object + // is currently selected in the editor. + if ( !smRenderZones && !isSelected() ) + return; + + GFXTransformSaver saver; + + MatrixF mat = getRenderTransform(); + mat.scale( getScale() ); + + GFX->multWorld( mat ); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setBlend( true ); + desc.setCullMode( GFXCullNone ); + //desc.fillMode = GFXFillWireframe; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->drawCube( desc, mObjBox, ColorI( 255, 0, 0, 45 ) ); +} + +void Zone::_addPortal( Portal *p ) +{ + mPortals.push_back( p ); + + if ( !p->getZone( 0 ) || !p->getZone( 1 ) ) + mOutdoorZoneVisible = true; +} + +void Zone::_removePortal( Portal *p ) +{ + bool outdoorVisible = false; + bool removePortalOutdoorVisible = false; + + if ( !p->getZone( 0 ) || !p->getZone( 1 ) ) + removePortalOutdoorVisible = true; + + mPortals.remove( p ); + + for ( U32 i = 0; i < mPortals.size(); i++ ) + { + Portal *portal = mPortals[i]; + if ( !portal->getZone( 0 ) || !portal->getZone( 1 ) ) + outdoorVisible = true; + } + + if ( !outdoorVisible && removePortalOutdoorVisible ) + mOutdoorZoneVisible = false; +} + +bool Zone::_traverseZones( SceneState *state ) +{ + const Frustum &frust = state->getFrustum(); + Frustum currFrustum( frust ); + + Vector zoneStack; + zoneStack.push_back( this ); + + smZoneKey++; + + bool renderOutside = false; + + while ( zoneStack.size() ) + { + Zone *zone = zoneStack.last(); + if ( !zone ) + { + renderOutside = true; + zoneStack.pop_back(); + continue; + } + + zone->mZoneKey = smZoneKey; + + SceneState::ZoneState &zoneState = state->getZoneStateNC( zone->mZoneRangeStart ); + zoneState.render = true; + + // We extend the zone box + // by the bounds of any + // zones this zone connects + // to in order to properly + // grab the potentially rendered + // objects during scene traversal. + mZoneBox.extend( zone->getWorldBox().maxExtents ); + mZoneBox.extend( zone->getWorldBox().minExtents ); + + zoneStack.pop_back(); + + // Only do this sort if the + // zone has more than one portal. + Vector tmpPortals; + if ( zone->mPortals.size() > 1 ) + { + gSortPoint = state->getCameraPosition(); + for ( U32 i = 0; i < zone->mPortals.size(); i++ ) + { + Portal *portal = zone->mPortals[i]; + if ( !frust.intersectOBB( portal->getOBBPoints() ) ) + continue; + + tmpPortals.push_back( portal ); + } + + // Sort portals by distance from near to far. + dQsort( tmpPortals.address(), tmpPortals.size(), sizeof( Portal* ), cmpZonePortalDistance ); + } + else + tmpPortals.merge( zone->mPortals ); + + for ( U32 i = 0; i < tmpPortals.size(); i++ ) + { + // Is the Portal in the frustum? + // If so, we need to process + // the Zone that it's connected to that + // is not us. + Portal *portal = tmpPortals[i]; + SceneState::ZoneState &portalState = state->getZoneStateNC( portal->getZoneRangeStart() ); + + // If the portal is in the frustum + // go ahead and set its zone state render + // variable to true. + if ( portal->getPointZone( frust.getPosition() ) ) + portalState.render = true; + + if ( !currFrustum.intersectOBB( portal->getOBBPoints() ) ) + continue; + + // Also set it if the current frustum + // intersects the portal. + portalState.render = true; + + Frustum newFrustum( frust ); + portal->generatePortalFrustum( state, &newFrustum ); + newFrustum.invert(); + + // We set the currFrustum + // to the newFrustum in order + // to ensure that the visibility + // of portals further down the chain + // is determined by the clipped down + // frustum. + currFrustum = newFrustum; + + // If this zone is not us, + // and is not null, set the + // new zone key on it, and set + // the sized down frustum on it as well. + Zone *z0 = portal->getZone( 0 ); + if ( z0 && z0->mZoneKey != smZoneKey ) + { + SceneState::ZoneState &z0State = state->getZoneStateNC( z0->mZoneRangeStart ); + z0State.frustum = newFrustum; + + z0->mZoneKey = smZoneKey; + zoneStack.push_back( z0 ); + } + else if ( !z0 ) + { + // If the zone is null, + // set the sized down frustum + // on the outside zone instead, + // since this portal links to the outside. + SceneState::ZoneState &z0State = state->getZoneStateNC( 0 ); + + z0State.frustum = newFrustum; + zoneStack.push_back( NULL ); + } + + // Same thing as above, but + // for the second zone the + // portal holds on to. + Zone *z1 = portal->getZone( 1 ); + if ( z1 && z1->mZoneKey != smZoneKey ) + { + SceneState::ZoneState &z1State = state->getZoneStateNC( z1->mZoneRangeStart ); + + z1State.frustum = newFrustum; + z1->mZoneKey = smZoneKey; + zoneStack.push_back( z1 ); + } + else if ( !z1 ) + { + SceneState::ZoneState &zoneState = state->getZoneStateNC( 0 ); + + zoneState.frustum = newFrustum; + zoneStack.push_back( NULL ); + } + } + } + + return renderOutside; +} diff --git a/T3D/zone.h b/T3D/zone.h new file mode 100644 index 0000000..58ce96c --- /dev/null +++ b/T3D/zone.h @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#ifndef _ZONE_H_ +#define _ZONE_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif + +class Portal; +class SceneState; + +/// +class Zone : public SceneObject +{ + typedef SceneObject Parent; + + friend class Portal; + +protected: + + static U32 smZoneKey; + static bool smRenderZones; + + enum + { + TransformMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1, + }; + + Vector mPortals; + + U32 mZoneKey; + U32 mPortalKey; + + Box3F mZoneBox; + + bool mOutdoorZoneVisible; + + void _removePortal( Portal *p ); + + void _addPortal( Portal *p ); + + bool _traverseZones( SceneState *state ); + +public: + + Zone(); + virtual ~Zone(); + + // SimObject + DECLARE_CONOBJECT( Zone ); + + static void consoleInit(); + + static void initPersistFields(); + bool onAdd(); + void onRemove(); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + void setTransform( const MatrixF &mat ); + bool prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseState ); + U32 getPointZone( const Point3F &p ); + bool getOverlappingZones( SceneObject *obj, + U32 *zones, + U32 *numZones ); + const Box3F& getZoneBox() const { return mZoneBox; } + bool scopeObject( const Point3F &rootPosition, + const F32 rootDistance, + bool *zoneScopeState ); + + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat ); + + const Vector& getPortals() const { return mPortals; } + + U32 getPortalKey() const { return mPortalKey; } + void setPortalKey( U32 portalKey ) { mPortalKey = portalKey; } +}; + +#endif // _ZONE_H_ + diff --git a/add/GUI/GuiCellArray.cpp b/add/GUI/GuiCellArray.cpp new file mode 100644 index 0000000..b0810e7 --- /dev/null +++ b/add/GUI/GuiCellArray.cpp @@ -0,0 +1,462 @@ +#include "GuiCellArray.h" +#include "../RPGPack/RPGBook.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiCanvas.h" +#include "../RPGPack/RPGBaseData.h" + +IMPLEMENT_CONOBJECT(guiCellArray); + +bool guiCellArray::isValidPosition( U8 page,U8 slot ) +{ + return page >= 0 && page < m_nPages && slot >= 0 && slot < m_nColumnsPerPage * m_nRowsPerPage; +} + +U8 guiCellArray::getSlotByLocalPosition( Point2I pos ) +{ + U8 row = pos.y / (m_nCellSizeY + m_nCellPadingY); + U8 column = pos.x / (m_nCellSizeX + m_nCellPadingX); + return row * m_nColumnsPerPage + column; +} + +F32 guiCellArray::getRatioOfCoolDown(const U8 & idx) +{ + return m_pBook ? m_pBook->getRatioOfCDTime(idx) : 0; +} + +void guiCellArray::initPersistFields() +{ + Parent::initPersistFields(); + addGroup("guiCellArray"); + addField("RowsPerPage", TypeS8, Offset(m_nRowsPerPage, guiCellArray)); + addField("ColumnsPerPage", TypeS8, Offset(m_nColumnsPerPage, guiCellArray)); + addField("Pages", TypeS8, Offset(m_nPages, guiCellArray)); + addField("xPad", TypeS8, Offset(m_nCellPadingX, guiCellArray)); + addField("yPad", TypeS8, Offset(m_nCellPadingY, guiCellArray)); + addField("cellSizeX", TypeS8, Offset(m_nCellSizeX, guiCellArray)); + addField("cellSizeY", TypeS8, Offset(m_nCellSizeY, guiCellArray)); + addField("locked", TypeBool , Offset(m_bLocked, guiCellArray)); + addField("clientOnly", TypeBool , Offset(m_bClientOnly, guiCellArray)); + endGroup("guiCellArray"); +} + +U8 guiCellArray::getPickedIndex() +{ + return _nPickedIndex; +} + +U8 guiCellArray::getCurrentPage() +{ + return m_nCurrentPage; +} + +void guiCellArray::setPickedIndex( U8 idx ) +{ + _nPickedIndex = idx; +} + +void guiCellArray::clearPickedIndex() +{ + _nPickedIndex = -1; +} + +void guiCellArray::setRolloverIndex( U8 idx ) +{ + if (_nRolloveIndex >= 0) + { + m_nCellItems[_nRolloveIndex]._statu = CellItem::Item_Normal; + } + _nRolloveIndex = idx; + if (_nRolloveIndex >= 0) + { + m_nCellItems[_nRolloveIndex]._statu = CellItem::Item_Rollover; + } +} + +void guiCellArray::clearRolloverIndex() +{ + setRolloverIndex(-1); +} + +RPGBook * guiCellArray::getPlayerBook() +{ + return m_pBook; +} + +U8 guiCellArray::getIndex( U8 page,U8 slot ) +{ + if (isValidPosition(page,slot)) + { + return page * m_nRowsPerPage * m_nColumnsPerPage + slot; + } + return -1; +} + +U8 guiCellArray::getSlotByGlobalPosition( Point2I pos ) +{ + return getSlotByLocalPosition(globalToLocalCoord(pos)); +} + +void guiCellArray::sendDragEvent( GuiControl* target, const char* event,Point2I mousePoint ) +{ + if(!target || !target->isMethod(event)) + return; + + /* + function guiCellArray::onControlDropped(%this,%srcarray,%srcPickedIndex,%globalMouseX,%globalMouseY) + */ + Con::executef(target, event, Con::getIntArg(this->getId()),\ + Con::getIntArg(this->getPickedIndex()), \ + Con::getIntArg(mousePoint.x), \ + Con::getIntArg(mousePoint.y)); +} + +GuiControl* guiCellArray::findDragTarget( Point2I mousePoint, const char* method ) +{ + // If there are any children and we have a parent. + GuiControl* parent = (GuiControl*)getRoot(); + //if (size() && parent) + if(parent) + { + //mVisible = false; + GuiControl* dropControl = parent->findHitControl(mousePoint); + //mVisible = true; + while( dropControl ) + { + if (dropControl->isMethod(method)) + return dropControl; + else + dropControl = dropControl->getParent(); + } + } + return NULL; +} + +void guiCellArray::onRender( Point2I offset, const RectI &updateRect ) +{ + //draw lines + Point2I ptUperLeft = updateRect.point; + Point2I ptLowerRight = ptUperLeft + updateRect.extent; + + Point2I ptDrawStart = ptUperLeft; + Point2I ptDrawEnd = ptDrawStart; + ColorI color(255,0,0); + + ptDrawEnd.x += m_nCellSizeX * m_nColumnsPerPage; + ptDrawEnd.x += m_nCellPadingX * (m_nColumnsPerPage - 1); + for (int n = 0 ; n <= m_nRowsPerPage ; n++) + { + //GFX->getDrawUtil()->drawLine(ptDrawStart,ptDrawEnd,color); + ptDrawStart.y += m_nCellSizeY; + ptDrawEnd.y += m_nCellSizeY; + //GFX->getDrawUtil()->drawLine(ptDrawStart,ptDrawEnd,color); + ptDrawStart.y += m_nCellPadingY; + ptDrawEnd.y += m_nCellPadingY; + } + + ptDrawStart = ptDrawEnd = ptUperLeft; + ptDrawEnd.y += m_nCellSizeY * m_nRowsPerPage; + ptDrawEnd.y += m_nCellPadingY * (m_nRowsPerPage - 1); + for (int n = 0 ; n <= m_nColumnsPerPage ; n++) + { + //GFX->getDrawUtil()->drawLine(ptDrawStart,ptDrawEnd,color); + ptDrawStart.x += m_nCellSizeX; + ptDrawEnd.x += m_nCellSizeX; + //GFX->getDrawUtil()->drawLine(ptDrawStart,ptDrawEnd,color); + ptDrawStart.x += m_nCellPadingX; + ptDrawEnd.x += m_nCellPadingX; + } + + //draw items from book + if (m_pBook) + { + F32 ratio = 0;//getRatioOfCoolDown(); + U8 idx = 0; + //Point2I pt = ptUperLeft; + RectI rect,rect2; + rect.point = ptUperLeft; + rect.extent = Point2I(m_nCellSizeX,m_nCellSizeY); + RPGBaseData * pRPGData = NULL; + + Point2I center; + Point2I diff(4,4); + ColorI color(0,0,0,128); + GFX->getDrawUtil()->clearBitmapModulation(); + for (int n = 0 ; n < m_nRowsPerPage ; n++) + { + for (int m = 0 ; m < m_nColumnsPerPage ; m++) + { + idx = n * m_nColumnsPerPage + m; + rect.point.x = ptUperLeft.x + m * (m_nCellSizeX + m_nCellPadingX); + rect.point.y = ptUperLeft.y + n * (m_nCellSizeY + m_nCellPadingY); + + rect2 = rect; + rect2.point += diff; + rect2.extent -= diff * 2; + + if (mTextureObject) + { + GFX->getDrawUtil()->drawBitmapStretch(mTextureObject,rect); + } + + pRPGData = m_pBook->getRpgBaseData(idx); + ratio = getRatioOfCoolDown(idx); + if (pRPGData) + { + if(m_nCellItems[idx]._statu == CellItem::Item_Normal || + m_nCellItems[idx]._statu == CellItem::Item_Rollover) + { + GFX->getDrawUtil()->drawBitmapStretch(m_nCellItems[idx]._textureHandle,rect2); + if (mFabs(ratio) > 0.01 && mFabs(ratio) < 0.99) + { + center.x = rect2.point.x + rect2.extent.x / 2; + center.y = rect2.point.y + rect2.extent.y / 2; + GFX->getDrawUtil()->drawCDRectFill(center,rect2.extent,M_2PI * ratio,color); + } + } + } + } + } + } + + renderChildControls(offset, updateRect); +} + +void guiCellArray::onMouseDown( const GuiEvent &event ) +{ + if (m_bLocked) + return; + GuiCanvas* canvas = getRoot(); + AssertFatal(canvas, "guiCellArray wasn't added to the gui before the drag started."); + if (canvas->getMouseLockedControl()) + { + GuiEvent event; + canvas->getMouseLockedControl()->onMouseLeave(event); + canvas->mouseUnlock(canvas->getMouseLockedControl()); + } + canvas->mouseLock(this); + canvas->setFirstResponder(this); + + U8 slot = getSlotByGlobalPosition(event.mousePoint); + if (isValidPosition(m_nCurrentPage,slot)) + { + U8 n = getIndex(m_nCurrentPage,slot); + m_nCellItems[n]._statu = CellItem::Item_PickedUp; + setPickedIndex(n); + if (isItemEmpty(n)) + { + return; + } + GuiCanvas* Canvas = getRoot(); + GuiCursor * pCursor = Canvas->getCurrentCursor(); + if (pCursor) + { + pCursor->setPickedBmp(m_nCellItems[n]._bmpPath); + } + } +} + +void guiCellArray::onMouseUp( const GuiEvent &event ) +{ + if (m_bLocked) + return; + mouseUnlock(); + GuiCanvas* Canvas = getRoot(); + GuiCursor * pCursor = Canvas->getCurrentCursor(); + if (pCursor) + { + pCursor->clearPickedBmp(); + } + + GuiControl* target = findDragTarget(event.mousePoint, "onControlDropped"); + if (target) + { + sendDragEvent(target, "onControlDropped",event.mousePoint); + } + else + { + cancelMove(); + } +} + +void guiCellArray::onRightMouseDown( const GuiEvent &event ) +{ + if (m_bLocked) + return; + + U8 slot = getSlotByGlobalPosition(event.mousePoint); + if (isValidPosition(m_nCurrentPage,slot)) + { + U8 n = getIndex(m_nCurrentPage,slot); + F32 ratio = getRatioOfCoolDown(n); + if (ratio < 1.f && ratio > 0) + { + return; + } + Con::executef(this,"onRightMouseDown",Con::getIntArg(n)); + } +} + +void guiCellArray::onMouseMove( const GuiEvent &event ) +{ + if (NULL == m_pBook) + { + return; + } + Parent::onMouseMove(event); + U8 slot = getSlotByGlobalPosition(event.mousePoint); + if (isValidPosition(m_nCurrentPage,slot)) + { + U8 n = getIndex(m_nCurrentPage,slot); + if (_nRolloveIndex == n) + { + return; + } + RPGBaseData * pRPGDataOld = m_pBook->getRpgBaseData(_nRolloveIndex); + if (pRPGDataOld) + { + Con::executef(this,"onLeaveSlot",Con::getIntArg(pRPGDataOld->getId())); + } + setRolloverIndex(n); + if (isItemEmpty(n)) + { + return; + } + RPGBaseData * pRPGData = m_pBook->getRpgBaseData(n); + if (pRPGData) + { + Con::executef(this,"onEnterSlot",Con::getIntArg(pRPGData->getId())); + } + } +} + +void guiCellArray::onMouseLeave( const GuiEvent &event ) +{ + clearRolloverIndex(); + Con::executef(this,"onLeaveSlot"); +} + +void guiCellArray::cancelMove() +{ + if (_nPickedIndex != -1) + { + m_nCellItems[_nPickedIndex]._statu = CellItem::Item_Normal; + } + clearPickedIndex(); +} + +void guiCellArray::setBook( RPGBook * pBook ) +{ + m_pBook = pBook; + refreshItems(); +} + +void guiCellArray::pageUp() +{ + if (m_nCurrentPage > 0 && m_nCurrentPage <= m_nPages - 1) + { + --m_nCurrentPage; + } +} + +void guiCellArray::pageDown() +{ + if (m_nCurrentPage >= 0 && m_nCurrentPage < m_nPages - 1) + { + ++m_nCurrentPage; + } +} + +void guiCellArray::refreshItems() +{ + RPGBaseData * pRPGData = NULL; + int idx = 0; + if (NULL == m_pBook) + { + return; + } + for (int n = 0 ; n < m_nRowsPerPage ; n++) + { + for (int m = 0 ; m < m_nColumnsPerPage ; m++) + { + idx = n * m_nColumnsPerPage + m; + pRPGData = m_pBook->getRpgBaseData(idx); + if (pRPGData) + { + if (dStrcmp(pRPGData->getIconName(),m_nCellItems[idx]._bmpPath) != 0) + { + dStrcpy(m_nCellItems[idx]._bmpPath,pRPGData->getIconName()); + if(m_nCellItems[idx]._bmpPath[0] == 0) + m_nCellItems[idx]._textureHandle = NULL; + else + m_nCellItems[idx]._textureHandle.set(m_nCellItems[idx]._bmpPath,&GFXDefaultGUIProfile,"cell item bmp"); + } + } + m_nCellItems[idx]._statu = CellItem::Item_Normal; + } + } +} + +bool guiCellArray::isItemEmpty( U8 index ) +{ + RPGBook * pBook = getPlayerBook(); + if (pBook) + { + return pBook->isItemEmpty(index); + } + return true; +} + +guiCellArray::guiCellArray():m_pBook(NULL),m_nPages(0),m_nCurrentPage(0),\ +m_nRowsPerPage(0),m_nColumnsPerPage(0),m_nCellSizeX(42),m_nCellSizeY(42),\ +_nPickedIndex(-1),m_nCellPadingX(0),m_nCellPadingY(0),_nRolloveIndex(-1),\ +m_bLocked(false),m_bClientOnly(false) +{ + +} + +guiCellArray * guiCellArray::getBookGUI( const U8 & bookType ) +{ + guiCellArray * pGUI = NULL; + char guiName[128]; + switch(bookType) + { + case BOOK_PACK: + dStrcpy(guiName,"backpack"); + break; + case BOOK_SHOTCUTBAR1: + dStrcpy(guiName,"shortcut1_gui"); + break; + default: + dStrcpy(guiName,"nothing"); + break; + } + pGUI = dynamic_cast(Sim::findObject(guiName)); + return pGUI; +} + +ConsoleMethod(guiCellArray,getCurrentPage,S32,2,2,"return current page") +{ + return object->getCurrentPage(); +} + +ConsoleMethod(guiCellArray,getIndex,S32,4,4,"%page,%slot") +{ + return object->getIndex(dAtoi(argv[2]),dAtoi(argv[3])); +} + +ConsoleMethod(guiCellArray,getSlotByGlobalPosition,S32,4,4,"%x,%y") +{ + return object->getSlotByGlobalPosition(Point2I(dAtoi(argv[2]),dAtoi(argv[3]))); +} + +ConsoleMethod(guiCellArray,getPlayerBook,S32,2,2,"return the rpgbook") +{ + RPGBook * pBook = object->getPlayerBook(); + return pBook ? pBook->getId() : -1; +} + +ConsoleMethod(guiCellArray,isItemEmpty,bool,3,3,"%idx") +{ + return ((guiCellArray*)object)->isItemEmpty(dAtoi(argv[2])); +} \ No newline at end of file diff --git a/add/GUI/GuiCellArray.h b/add/GUI/GuiCellArray.h new file mode 100644 index 0000000..1191ca4 --- /dev/null +++ b/add/GUI/GuiCellArray.h @@ -0,0 +1,81 @@ +#ifndef __GUI_CELLARRAY_H__ +#define __GUI_CELLARRAY_H__ + +#include "gui/controls/guiBitmapCtrl.h" +#include "../RPGPack/RPGDefs.h" +class RPGBook; + +class guiCellArray : public GuiBitmapCtrl, public RPGDefs +{ +private: + typedef GuiBitmapCtrl Parent; +protected: + RPGBook * m_pBook; + U8 m_nPages; + U8 m_nCurrentPage; + U8 m_nRowsPerPage; + U8 m_nColumnsPerPage; + U8 m_nCellSizeX; + U8 m_nCellSizeY; + U8 m_nCellPadingX; + U8 m_nCellPadingY; + bool m_bLocked;//²»ÄÜÓÉÓû§²Ù×÷£¬Ä¬ÈÏfalse + bool m_bClientOnly;//²»ºÏ·þÎñÆ÷½»»¥£¬Ä¬ÈÏfalse + + struct CellItem + { + enum ItemStatu + { + Item_Normal, + Item_PickedUp, + Item_Rollover, + }; + ItemStatu _statu; + char _bmpPath[256]; + GFXTexHandle _textureHandle; + CellItem():_textureHandle(NULL),_statu(Item_Normal){_bmpPath[0] = 0;} + ~CellItem(){_textureHandle = NULL;} + }; + CellItem m_nCellItems[BOOK_MAX]; + U8 _nPickedIndex; + U8 _nRolloveIndex; + + bool isValidPosition(U8 page,U8 slot); + U8 getSlotByLocalPosition(Point2I pos); + F32 getRatioOfCoolDown(const U8 & idx); +public: + DECLARE_CONOBJECT(guiCellArray); + guiCellArray(); + static void initPersistFields(); + + U8 getPickedIndex(); + U8 getCurrentPage(); + void setPickedIndex(U8 idx); + void clearPickedIndex(); + void setRolloverIndex(U8 idx); + void clearRolloverIndex(); + + RPGBook * getPlayerBook(); + U8 getIndex(U8 page,U8 slot); + U8 getSlotByGlobalPosition(Point2I pos); + + //get a guicontrol by the book's type id + static guiCellArray * getBookGUI(const U8 & bookType); + + void sendDragEvent(GuiControl* target, const char* event,Point2I mousePoint); + GuiControl* findDragTarget(Point2I mousePoint, const char* method); + void onRender(Point2I offset, const RectI &updateRect); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onRightMouseDown(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void cancelMove(); + void setBook(RPGBook * pBook); + void pageUp(); + void pageDown(); + void refreshItems(); + bool isItemEmpty(U8 index); +}; + +#endif \ No newline at end of file diff --git a/add/GUI/GuiRPGStatuBar.cpp b/add/GUI/GuiRPGStatuBar.cpp new file mode 100644 index 0000000..e7b443e --- /dev/null +++ b/add/GUI/GuiRPGStatuBar.cpp @@ -0,0 +1,152 @@ +#include "gui/core/guiControl.h" +#include "console/consoleTypes.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" +#include "gfx/gfxDrawUtil.h" + + +class rpgStatusBar : public GuiControl +{ + typedef GuiControl Parent; + + ColorF rgba_fill; + + F32 fraction; + ShapeBase* shape; + bool show_energy; + bool monitor_player; + +public: + /*C*/ rpgStatusBar(); + + virtual void onRender(Point2I, const RectI&); + + void setFraction(F32 frac); + F32 getFraction() const { return fraction; } + + void setShape(ShapeBase* s); + void clearShape() { setShape(NULL); } + + virtual bool onWake(); + virtual void onSleep(); + virtual void onMouseDown(const GuiEvent &event); + virtual void onDeleteNotify(SimObject*); + + static void initPersistFields(); + + DECLARE_CONOBJECT(rpgStatusBar); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CONOBJECT(rpgStatusBar); + +rpgStatusBar::rpgStatusBar() +{ + rgba_fill.set(0.0f, 1.0f, 1.0f, 1.0f); + + fraction = 1.0f; + shape = 0; + show_energy = false; + monitor_player = false; +} + +void rpgStatusBar::setFraction(F32 frac) +{ + fraction = mClampF(frac, 0.0f, 1.0f); +} + +void rpgStatusBar::setShape(ShapeBase* s) +{ + if (shape) + clearNotify(shape); + shape = s; + if (shape) + deleteNotify(shape); +} + +void rpgStatusBar::onDeleteNotify(SimObject* obj) +{ + if (shape == (ShapeBase*)obj) + { + shape = NULL; + return; + } + + Parent::onDeleteNotify(obj); +} + +bool rpgStatusBar::onWake() +{ + if (!Parent::onWake()) + return false; + + return true; +} + +void rpgStatusBar::onSleep() +{ + //clearShape(); + Parent::onSleep(); +} + +// STATIC +void rpgStatusBar::initPersistFields() +{ + Parent::initPersistFields(); + + addField("fillColor", TypeColorF, Offset(rgba_fill, rpgStatusBar)); + addField("displayEnergy", TypeBool, Offset(show_energy, rpgStatusBar)); + addField("monitorPlayer", TypeBool, Offset(monitor_player, rpgStatusBar)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + + +void rpgStatusBar::onRender(Point2I offset, const RectI &updateRect) +{ + if (!shape) + return; + + if (shape->getDamageState() != ShapeBase::Enabled) + fraction = 0.0f; + else + fraction = (show_energy) ? shape->getEnergyValue() : (1.0f - shape->getDamageValue()); + + // set alpha value for the fill area + rgba_fill.alpha = 1.0f; + + // calculate the rectangle dimensions + RectI rect(updateRect); + rect.extent.x = (S32)(rect.extent.x*fraction); + + // draw the filled part of bar + GFX->getDrawUtil()->drawRectFill(rect, rgba_fill); +} + +void rpgStatusBar::onMouseDown(const GuiEvent &event) +{ + GuiControl *parent = getParent(); + if (parent) + parent->onMouseDown(event); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +ConsoleMethod(rpgStatusBar, setProgress, void, 3, 3, "setProgress(percent_done)") +{ + object->setFraction(dAtof(argv[2])); +} + +ConsoleMethod(rpgStatusBar, setShape, void, 3, 3, "setShape(shape)") +{ + object->setShape(dynamic_cast(Sim::findObject(argv[2]))); +} + +ConsoleMethod(rpgStatusBar, clearShape, void, 2, 2, "clearShape()") +{ + object->clearShape(); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/add/GUI/GuiRPGStatuBar.h b/add/GUI/GuiRPGStatuBar.h new file mode 100644 index 0000000..e69de29 diff --git a/add/GUI/GuiRPGStatuBox.cpp b/add/GUI/GuiRPGStatuBox.cpp new file mode 100644 index 0000000..e1cb17f --- /dev/null +++ b/add/GUI/GuiRPGStatuBox.cpp @@ -0,0 +1,21 @@ +#include "console/consoleTypes.h" +#include "GuiRPGStatuBox.h" + +IMPLEMENT_CONOBJECT(rpgStatusBox); + +rpgStatusBox::rpgStatusBox() +{ +} + +void rpgStatusBox::onMouseDown(const GuiEvent &event) +{ + Parent::onMouseDown(event); + Con::executef(this, "onMouseDown"); +} + +void rpgStatusBox::onSleep() +{ + Parent::onSleep(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/add/GUI/GuiRPGStatuBox.h b/add/GUI/GuiRPGStatuBox.h new file mode 100644 index 0000000..c36b610 --- /dev/null +++ b/add/GUI/GuiRPGStatuBox.h @@ -0,0 +1,23 @@ + +#ifndef _AFX_STATUS_BOX_H_ +#define _AFX_STATUS_BOX_H_ + +#include "gui/controls/guiBitmapCtrl.h" + +class rpgStatusBox : public GuiBitmapCtrl +{ +private: + typedef GuiBitmapCtrl Parent; + +public: + /*C*/ rpgStatusBox(); + + virtual void onMouseDown(const GuiEvent &event); + virtual void onSleep(); + + DECLARE_CONOBJECT(rpgStatusBox); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif //_AFX_STATUS_BOX_H_ diff --git a/add/GUI/GuiRPGStatuLabel.cpp b/add/GUI/GuiRPGStatuLabel.cpp new file mode 100644 index 0000000..38eb247 --- /dev/null +++ b/add/GUI/GuiRPGStatuLabel.cpp @@ -0,0 +1,14 @@ +#include "GuiRPGStatuLabel.h" + +IMPLEMENT_CONOBJECT(rpgStatusLabel); + +rpgStatusLabel::rpgStatusLabel() +{ +} + +void rpgStatusLabel::onMouseDown(const GuiEvent &event) +{ + GuiControl *parent = getParent(); + if (parent) + parent->onMouseDown(event); +} \ No newline at end of file diff --git a/add/GUI/GuiRPGStatuLabel.h b/add/GUI/GuiRPGStatuLabel.h new file mode 100644 index 0000000..0a45d51 --- /dev/null +++ b/add/GUI/GuiRPGStatuLabel.h @@ -0,0 +1,21 @@ +#ifndef _AFX_STATUS_LABEL_H_ +#define _AFX_STATUS_LABEL_H_ + +#include "gui/controls/guiMLTextCtrl.h" + +class rpgStatusLabel : public GuiMLTextCtrl +{ +private: + typedef GuiMLTextCtrl Parent; + +public: + /*C*/ rpgStatusLabel(); + + virtual void onMouseDown(const GuiEvent &event); + + DECLARE_CONOBJECT(rpgStatusLabel); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif //_AFX_STATUS_LABEL_H_ diff --git a/add/GUI/GuiRPGTsCtrl.cpp b/add/GUI/GuiRPGTsCtrl.cpp new file mode 100644 index 0000000..c593962 --- /dev/null +++ b/add/GUI/GuiRPGTsCtrl.cpp @@ -0,0 +1,181 @@ +#include "GuiRPGTsCtrl.h" +#include "gui/core/guiCanvas.h" +#include "T3D/objectTypes.h" +#include "T3D/gameFunctions.h" +#include "../RPGPack/RPGUtils.h" +#include "T3D/gameConnection.h" +#include "T3D/aiPlayer.h" +#include "../RPGPack/Constraint/CST_TerrainClickedPoint.h" + +IMPLEMENT_CONOBJECT(RPGTSCtrl); + +RPGTSCtrl::RPGTSCtrl() +{ + mMouse3DVec.zero(); + mMouse3DPos.zero(); + mouse_dn_timestamp = 0; +} + +bool RPGTSCtrl::processCameraQuery(CameraQuery *camq) +{ + return Parent::processCameraQuery(camq); +} + +void RPGTSCtrl::renderWorld(const RectI &updateRect) +{ + Parent::renderWorld(updateRect); +} + +void RPGTSCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + Parent::onRender(offset,updateRect); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void RPGTSCtrl::onMouseDown(const GuiEvent &evt) +{ + //Con::printf("#### RPGTSCtrl::onLeftMouseDown() ####"); + GameConnection * pGameConn = GameConnection::getConnectionToServer(); + if (pGameConn) + { + AIPlayer * pPlayer = dynamic_cast(pGameConn->getControlObject()); + if (pPlayer) + { + MatrixF mat; + Point3F face; + if (GameGetCameraTransform(&mat,&face)) + { + Point3F camPos; + mat.getColumn(3,&camPos); + Point3F worldPos; + Point3F screenPos(evt.mousePoint.x,evt.mousePoint.y,1); + if (unproject(screenPos,&worldPos)) + { + Point3F pt3DPos = camPos; + Point3F pt3DVec = worldPos - camPos; + pt3DVec.normalizeSafe(); + Point3F ptEnd = pt3DPos + pt3DVec * 2000; + RayInfo ray; + pPlayer->disableCollision(); + U32 mask = PlayerObjectType | ItemObjectType | AIObjectType | TerrainObjectType; + if (gClientContainer.castRay(pt3DPos,ptEnd,mask,&ray)) + { + if (ray.object->getType() & TerrainObjectType) + { + //pPlayer->setMoveDestination(ray.point,false); + CST_TerrainClicked::smTerrainClicked = ray.point; + char position[256]; + dSprintf(position,256,"%2f %2f %2f",ray.point.x,ray.point.y,ray.point.z); + Con::executef("onClickTerrian",position); + } + } + pPlayer->enableCollision(); + } + } + } + } + // save a timestamp so we can measure how long the button is down + mouse_dn_timestamp = Platform::getRealMilliseconds(); + + + GuiCanvas* Canvas = getRoot(); + + + // clear button down status because the ActionMap is going to capture + // the mouse and the button up will never happen + Canvas->clearMouseButtonDown(); + + // indicate that processing of event should continue (pass down to ActionMap) + Canvas->setConsumeLastInputEvent(false); +} + +void RPGTSCtrl::onMouseUp(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onLeftMouseUp() ####"); +} + +void RPGTSCtrl::onRightMouseDown(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onRightMouseDown() ####"); + GuiCanvas* Canvas = getRoot(); + + + // clear right button down status because the ActionMap is going to capture + // the mouse and the right button up will never happen + Canvas->clearMouseRightButtonDown(); + + // indicate that processing of event should continue (pass down to ActionMap) + Canvas->setConsumeLastInputEvent(false); +} + +void RPGTSCtrl::onRightMouseUp(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onRightMouseUp() ####"); +} + +void RPGTSCtrl::onMouseMove(const GuiEvent& evt) +{ + MatrixF cam_xfm; + Point3F dummy_pt; + if (GameGetCameraTransform(&cam_xfm, &dummy_pt)) + { + // get cam pos + Point3F cameraPoint; cam_xfm.getColumn(3,&cameraPoint); + // construct 3D screen point from mouse coords + Point3F screen_pt((F32)evt.mousePoint.x, (F32)evt.mousePoint.y, 1.0f); + // convert screen point to world point + Point3F world_pt; + if (unproject(screen_pt, &world_pt)) + { + Point3F mouseVec = world_pt - cameraPoint; + mouseVec.normalizeSafe(); + + mMouse3DPos = cameraPoint; + mMouse3DVec = mouseVec; + + F32 selectRange = 200.f;// + Point3F mouseScaled = mouseVec*selectRange; + Point3F rangeEnd = cameraPoint + mouseScaled; + + U32 selectionMask = PlayerObjectType; + RPGUtils::rolloverRayCast(cameraPoint, rangeEnd, selectionMask); + } + } +} + +void RPGTSCtrl::onMouseEnter(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onMouseEnter() ####"); +} + +void RPGTSCtrl::onMouseDragged(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onMouseDragged() ####"); +} + + +void RPGTSCtrl::onMouseLeave(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onMouseLeave() ####"); +} + +bool RPGTSCtrl::onMouseWheelUp(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onMouseWheelUp() ####"); + Con::executef(this, "onMouseWheelUp"); + return true; +} + +bool RPGTSCtrl::onMouseWheelDown(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onMouseWheelDown() ####"); + Con::executef(this, "onMouseWheelDown"); + return true; +} + + +void RPGTSCtrl::onRightMouseDragged(const GuiEvent& evt) +{ + //Con::printf("#### RPGTSCtrl::onRightMouseDragged() ####"); +} \ No newline at end of file diff --git a/add/GUI/GuiRPGTsCtrl.h b/add/GUI/GuiRPGTsCtrl.h new file mode 100644 index 0000000..8ec3bce --- /dev/null +++ b/add/GUI/GuiRPGTsCtrl.h @@ -0,0 +1,44 @@ +#ifndef __GUIRPGTS_H__ +#define __GUIRPGTS_H__ + +#include "T3D/gameTSCtrl.h" + +class RPGTSCtrl : public GameTSCtrl +{ +private: + typedef GameTSCtrl Parent; + + Point3F mMouse3DVec; + Point3F mMouse3DPos; + + U32 mouse_dn_timestamp; + +public: + /*C*/ RPGTSCtrl(); + + virtual bool processCameraQuery(CameraQuery *query); + virtual void renderWorld(const RectI &updateRect); + virtual void onRender(Point2I offset, const RectI &updateRect); + + virtual void onMouseUp(const GuiEvent&); + virtual void onMouseDown(const GuiEvent&); + virtual void onMouseMove(const GuiEvent&); + virtual void onMouseDragged(const GuiEvent&); + virtual void onMouseEnter(const GuiEvent&); + virtual void onMouseLeave(const GuiEvent&); + + virtual bool onMouseWheelUp(const GuiEvent&); + virtual bool onMouseWheelDown(const GuiEvent&); + + virtual void onRightMouseDown(const GuiEvent&); + virtual void onRightMouseUp(const GuiEvent&); + virtual void onRightMouseDragged(const GuiEvent&); + + Point3F getMouse3DVec() {return mMouse3DVec;}; + Point3F getMouse3DPos() {return mMouse3DPos;}; + + + DECLARE_CONOBJECT(RPGTSCtrl); +}; + +#endif \ No newline at end of file diff --git a/add/GUI/GuiRadarMap.cpp b/add/GUI/GuiRadarMap.cpp new file mode 100644 index 0000000..3e0bee6 --- /dev/null +++ b/add/GUI/GuiRadarMap.cpp @@ -0,0 +1,144 @@ +#include "GuiRadarMap.h" +#include "console/consoleTypes.h" +#include "T3D/gameConnection.h" +#include "math/mathUtils.h" +#include "terrain/terrData.h" +#include "gfx/gfxDrawUtil.h" +#include "T3D/fps/guiShapeNameHud.h" + +IMPLEMENT_CONOBJECT(guiRadarMap); + +guiRadarMap::guiRadarMap():_m_n_RadaRaius(20),_m_tex_PlayerBmp(NULL),_m_str_PlayerBmp(NULL) +{ + +} + +void guiRadarMap::initPersistFields() +{ + Parent::initPersistFields(); + addGroup("guiRadarMap"); + addProtectedField( "playerBmp", TypeFilename, Offset( _m_str_PlayerBmp, guiRadarMap ), &setPlayerBmpName, &defaultProtectedGetFn, "" ); + addField("radaRadius", TypeS32, Offset(_m_n_RadaRaius, guiRadarMap)); + endGroup("guiRadarMap"); +} + +void guiRadarMap::setPlayerBmp(const char *bmpName) +{ + _m_str_PlayerBmp = StringTable->insert(bmpName); + if (*_m_str_PlayerBmp) + { + _m_tex_PlayerBmp.set( _m_str_PlayerBmp, &GFXDefaultGUIProfile ,"player arrow"); + } + else + _m_tex_PlayerBmp = NULL; + + setUpdate(); +} + +bool guiRadarMap::setPlayerBmpName( void *obj, const char *data ) +{ + // Prior to this, you couldn't do bitmap.bitmap = "foo.jpg" and have it work. + // With protected console types you can now call the setBitmap function and + // make it load the image. + static_cast( obj )->setPlayerBmp( data ); + + // Return false because the setBitmap method will assign 'mBitmapName' to the + // argument we are specifying in the call. + return false; +} + +void guiRadarMap::renderPlayer(Point2I offset,Point3F selfPos,Point3F otherPos,int type /* = 0 */) +{ + //to see if the distance is too large + Point3F diff = otherPos - selfPos; + F32 lenth = diff.len(); + if (lenth > _m_n_RadaRaius) + return; + + S32 W = 4; + S32 H = 4; + F32 angle,pitch; + MathUtils::getAnglesFromVector(diff,angle,pitch); + F32 ratio = lenth / (F32)_m_n_RadaRaius; + F32 screenLen = (((F32)(getExtent().x < getExtent().y ? getExtent().x : getExtent().y)) / 2) * ratio; + F32 x = screenLen * mSin(angle); + F32 y = screenLen * mCos(angle); + + RectI rect(offset.x + getExtent().x / 2 - W / 2 + 0.5, + offset.y + getExtent().y / 2 - H / 2 + 0.5, + W,H); + rect.point.x += x; + rect.point.y -= y; + + ColorI color(255,0,0); + GFX->getDrawUtil()->drawRectFill(rect,color); +} + +RectI guiRadarMap::getMapRect(Point3F selfPos) +{ + RectI rect(0,0,0,0); + TerrainBlock* pBlock = gClientSceneGraph->getCurrentTerrain(); + if(pBlock && mTextureObject) + { + F32 xRatio = (selfPos.x - pBlock->getPosition().x) / 2048; + F32 yRatio = (2048 - selfPos.y + pBlock->getPosition().y) / 2048; + F32 wRatio = (F32)(_m_n_RadaRaius) * 2 / 2048; + Point2I pos,ext; + ext.x = mTextureObject->getWidth() * wRatio + 0.5; + ext.y = mTextureObject->getHeight() * wRatio + 0.5; + rect.extent = ext; + + pos.x = mTextureObject->getWidth() * xRatio + 0.5; + pos.y = mTextureObject->getHeight() * yRatio + 0.5; + pos.x -= rect.extent.x / 2 + 0.5; + pos.y -= rect.extent.y / 2 + 0.5; + rect.point = pos; + } + return rect; +} + +void guiRadarMap::onRender(Point2I offset, const RectI &updateRect) +{ + GameConnection * conn = GameConnection::getConnectionToServer(); + GameBase * pControled = conn ? conn->getControlObject() : NULL; + F32 angle = 0; + F32 pitch = 0; + Point3F selfPos(0,0,0); + Point3F otherPos(0,0,0); + if (pControled) + { + Point3F face; + pControled->getTransform().getColumn(1,&face); + pControled->getTransform().getColumn(3,&selfPos); + face.z = 0; + face.normalize(); + MathUtils::getAnglesFromVector(face,angle,pitch); + } + + if (mTextureObject) + { + RectI rect(offset, getExtent()); + RectI srcRect = getMapRect(selfPos); + GFX->getDrawUtil()->drawBitmapStretchSRCircle(mTextureObject,rect,srcRect); + } + if (_m_tex_PlayerBmp) + { + RectI desRect(offset.x + getExtent().x / 2 - _m_tex_PlayerBmp.getWidth() / 2 + 0.5 , + offset.y + getExtent().y / 2 - _m_tex_PlayerBmp.getHeight() / 2 + 0.5, + _m_tex_PlayerBmp.getWidth(), + _m_tex_PlayerBmp.getHeight()); + GFX->getDrawUtil()->drawBitmapStretchRotate(_m_tex_PlayerBmp,desRect,angle); + } + if (!GuiShapeNameHud::PlayersInScene.empty()) + { + for (int n = 0 ; n < GuiShapeNameHud::PlayersInScene.size() ; n++) + { + if (GuiShapeNameHud::PlayersInScene[n] != pControled) + { + GuiShapeNameHud::PlayersInScene[n] ->getTransform().getColumn(3,&otherPos); + renderPlayer(offset,selfPos,otherPos,0); + } + } + } + renderChildControls(offset, updateRect); +} \ No newline at end of file diff --git a/add/GUI/GuiRadarMap.h b/add/GUI/GuiRadarMap.h new file mode 100644 index 0000000..2d5285e --- /dev/null +++ b/add/GUI/GuiRadarMap.h @@ -0,0 +1,26 @@ +#ifndef __RADAR_MAP__ +#define __RADAR_MAP__ + +#include "gui/controls/guiBitmapCtrl.h" + +class guiRadarMap : public GuiBitmapCtrl +{ +private: + typedef GuiBitmapCtrl Parent; +public: + DECLARE_CONOBJECT(guiRadarMap); + guiRadarMap(); + static void initPersistFields(); + static bool setPlayerBmpName( void *obj, const char *data ); + void onRender(Point2I offset, const RectI &updateRect); + void setPlayerBmp(const char * bmpName); +protected: + S32 _m_n_RadaRaius; + StringTableEntry _m_str_PlayerBmp; + GFXTexHandle _m_tex_PlayerBmp; + + void renderPlayer(Point2I offset,Point3F selfPos,Point3F otherPos,int type = 0); + RectI getMapRect(Point3F selfPos); +}; + +#endif \ No newline at end of file diff --git a/add/GUI/GuiRoleList.cpp b/add/GUI/GuiRoleList.cpp new file mode 100644 index 0000000..df51b39 --- /dev/null +++ b/add/GUI/GuiRoleList.cpp @@ -0,0 +1,252 @@ +#include "guiRoleList.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CONOBJECT(guiRoleList); + +guiRoleList::guiRoleList(): +_mFirstRoleOffset(0,0), +_mRoleHeightOffset(0), +_mRoleSize(0,0), +_mRoleBitmapName(0), +_mTextureRoleBitmap(0), +_mRoleBitmapNameHL(0), +_mTextureRoleBitmapHL(0), +_mHeadOffset(0,0), +_mHeadSize(10,10), +_mNameOffset(0,0) +{ + +} + +void guiRoleList::initPersistFields() +{ + Parent::initPersistFields(); + addGroup("guiRoleList"); + + addField("firstRoleOffset", TypePoint2I, Offset(_mFirstRoleOffset, guiRoleList)); + addField("roleSize", TypePoint2I, Offset(_mRoleSize, guiRoleList)); + addField("roleHeightOffset", TypeS32, Offset(_mRoleHeightOffset, guiRoleList)); + addProtectedField( "roleBitmap", TypeFilename, Offset( _mRoleBitmapName, guiRoleList ), &setRoleBitmap, &defaultProtectedGetFn, "" ); + addProtectedField( "roleBitmapHL", TypeFilename, Offset( _mRoleBitmapNameHL, guiRoleList ), &setRoleBitmapHL, &defaultProtectedGetFn, "" ); + addField("headOffset", TypePoint2I, Offset(_mHeadOffset, guiRoleList)); + addField("headSize", TypePoint2I, Offset(_mHeadSize, guiRoleList)); + addField("nameOffset", TypePoint2I, Offset(_mNameOffset, guiRoleList)); + + endGroup("guiRoleList"); +} + +bool guiRoleList::setRoleBitmap( void *obj, const char *data ) +{ + static_cast( obj )->setRoleBitmap( data ); + return false; +} + +void guiRoleList::setRoleBitmap( const char * fileName ) +{ + _mRoleBitmapName = StringTable->insert(fileName); + if (*_mRoleBitmapName) + { + _mTextureRoleBitmap.set( _mRoleBitmapName, &GFXDefaultGUIProfile ,"role rect"); + } + else + _mTextureRoleBitmap = NULL; + + setUpdate(); +} + +bool guiRoleList::setRoleBitmapHL( void *obj, const char *data ) +{ + static_cast( obj )->setRoleBitmapHL( data ); + return false; +} + +void guiRoleList::setRoleBitmapHL( const char * fileName ) +{ + _mRoleBitmapNameHL = StringTable->insert(fileName); + if (*_mRoleBitmapNameHL) + { + _mTextureRoleBitmapHL.set( _mRoleBitmapNameHL, &GFXDefaultGUIProfile ,"role rect hightlight"); + } + else + _mTextureRoleBitmapHL = NULL; + + setUpdate(); +} + +void guiRoleList::onRender( Point2I offset, const RectI &updateRect ) +{ + if (mTextureObject) + { + GFX->getDrawUtil()->clearBitmapModulation(); + RectI rect(offset, getExtent()); + GFX->getDrawUtil()->drawBitmapStretch(mTextureObject, rect); + } + + Point2I offsetRole(offset); + RectI rectRole; + offsetRole += _mFirstRoleOffset; + rectRole.point = offsetRole; + rectRole.extent = _mRoleSize; + std::list::iterator it = _mListRole.begin(); + GFont *font = getControlProfile()->mFont; + for ( ; it != _mListRole.end() ; ++it) + { + //role bg + if (_mTextureRoleBitmap && !(it->roleSelected)) + { + GFX->getDrawUtil()->drawBitmapStretch(_mTextureRoleBitmap, rectRole); + } + else if (_mTextureRoleBitmapHL && it->roleSelected) + { + GFX->getDrawUtil()->drawBitmapStretch(_mTextureRoleBitmapHL, rectRole); + } + //role head + rectRole.point += _mHeadOffset; + rectRole.extent = _mHeadSize; + if (it->textureRoleHead) + { + GFX->getDrawUtil()->drawBitmapStretch(it->textureRoleHead, rectRole); + } + rectRole.point -= _mHeadOffset; + rectRole.extent = _mRoleSize; + //role name + GFX->getDrawUtil()->drawText( font, rectRole.point + _mNameOffset, it->roleNameUTF8, getControlProfile()->mFontColors ); + + rectRole.point.y += _mRoleHeightOffset; + rectRole.point.y += _mRoleSize.y; + } + + renderChildControls(offset, updateRect); +} + +void guiRoleList::onMouseDown( const GuiEvent &event ) +{ + Point2I localPt = globalToLocalCoord(event.mousePoint); + RectI cellRect; + cellRect.point = _mFirstRoleOffset; + cellRect.extent = _mRoleSize; + std::list::iterator it = _mListRole.begin(); + for ( ; it != _mListRole.end() ; ++it) + { + if (cellRect.pointInRect(localPt)) + { + it->roleSelected = true; + break; + } + cellRect.point.y += _mRoleHeightOffset; + cellRect.point.y += _mRoleSize.y; + } + if (it == _mListRole.end()) + { + return; + } + + cellRect.point = _mFirstRoleOffset; + cellRect.extent = _mRoleSize; + std::list::iterator it2 = _mListRole.begin(); + for ( ; it2 != _mListRole.end() ; ++it2) + { + if (it2 != it) + { + it2->roleSelected = false; + } + } + + //fire the script + Con::executef(this,"onSelected",it->roleNameUTF8,Con::getIntArg(it->roleJob),Con::getIntArg( it->roleIsMale)); +} + +S32 guiRoleList::getRoleCount() +{ + return _mListRole.size(); +} + +void guiRoleList::addRole( const char * roleName , bool isMale , int job ) +{ + RoleInfo info(roleName,isMale,job); + _mListRole.push_back(info); +} + +void guiRoleList::clear() +{ + _mListRole.clear(); +} + +void guiRoleList::setSelect( int id ) +{ + if (id < _mListRole.size()) + { + std::list::iterator it = _mListRole.begin(); + for (int i = 0 ; it != _mListRole.end() ; ++it,++i) + { + if (i == id) + { + it->roleSelected = true; + Con::executef(this,"onSelected",it->roleNameUTF8,Con::getIntArg(it->roleJob),Con::getIntArg( it->roleIsMale)); + } + else + { + it->roleSelected = false; + } + } + } +} + +guiRoleList::RoleInfo * guiRoleList::getSelected() +{ + std::list::iterator it = _mListRole.begin(); + for (int i = 0 ; it != _mListRole.end() ; ++it,++i) + { + if (it->roleSelected) + { + return &(*it); + } + } + return NULL; +} + +void guiRoleList::clearSelected() +{ + std::list::iterator it = _mListRole.begin(); + for (int i = 0 ; it != _mListRole.end() ; ++it,++i) + { + if (it->roleSelected) + { + _mListRole.erase(it); + break; + } + } + setSelect(0); +} + +ConsoleMethod(guiRoleList,setSelect,void,3,3,"id") +{ + object->setSelect(dAtoi(argv[2])); +} + +ConsoleMethod(guiRoleList,getSelectedName,const char *,2,2,"") +{ + guiRoleList::RoleInfo * pRole = object->getSelected(); + return pRole ? pRole->roleNameUTF8 : NULL; +} + +ConsoleMethod(guiRoleList,addRole,void,5,5,"name,ismale,job") +{ + object->addRole(argv[2],dAtob(argv[3]),dAtoi(argv[4])); +} + +ConsoleMethod(guiRoleList,getRoleCount,S32,2,2,"") +{ + return object->getRoleCount(); +} + +ConsoleMethod(guiRoleList,clear,void,2,2,"") +{ + object->clear(); +} + +ConsoleMethod(guiRoleList,clearSelected,void,2,2,"") +{ + object->clearSelected(); +} \ No newline at end of file diff --git a/add/GUI/GuiRoleList.h b/add/GUI/GuiRoleList.h new file mode 100644 index 0000000..e4f3121 --- /dev/null +++ b/add/GUI/GuiRoleList.h @@ -0,0 +1,60 @@ +#ifndef __GUIROLELIST__ +#define __GUIROLELIST__ + +#include "gui/controls/guiBitmapCtrl.h" +#include + +class guiRoleList : public GuiBitmapCtrl +{ +public: + typedef GuiBitmapCtrl Parent; + + struct RoleInfo + { + char roleNameUTF8[128]; + bool roleIsMale; + int roleJob; + bool roleSelected; + GFXTexHandle textureRoleHead; + RoleInfo():textureRoleHead(0),roleSelected(false) + { + roleNameUTF8[0] = 0; + } + RoleInfo(const char * roleName , bool isMale , int job): + roleIsMale(isMale),roleJob(job),textureRoleHead(0) + { + dStrcpy(roleNameUTF8,roleName); + } + }; +private: + std::list _mListRole; + + Point2I _mFirstRoleOffset; + U32 _mRoleHeightOffset; + Point2I _mRoleSize; + StringTableEntry _mRoleBitmapName; + GFXTexHandle _mTextureRoleBitmap; + StringTableEntry _mRoleBitmapNameHL; + GFXTexHandle _mTextureRoleBitmapHL; + Point2I _mHeadOffset; + Point2I _mHeadSize; + Point2I _mNameOffset; +public: + DECLARE_CONOBJECT(guiRoleList); + guiRoleList(); + static void initPersistFields(); + static bool setRoleBitmap( void *obj, const char *data ); + void setRoleBitmap(const char * fileName); + static bool setRoleBitmapHL( void *obj, const char *data ); + void setRoleBitmapHL(const char * fileName); + void onRender(Point2I offset, const RectI &updateRect); + void onMouseDown(const GuiEvent &event); + S32 getRoleCount(); + void setSelect(int id); + RoleInfo * getSelected(); + void addRole(const char * roleName , bool isMale , int job); + void clear(); + void clearSelected(); +}; + +#endif \ No newline at end of file diff --git a/add/Global/GlobalStatic.cpp b/add/Global/GlobalStatic.cpp new file mode 100644 index 0000000..052adfd --- /dev/null +++ b/add/Global/GlobalStatic.cpp @@ -0,0 +1,247 @@ +#include "GlobalStatic.h" +#include "../wllib/wlmgr.h" +#include "T3D/gameConnection.h" +#include "T3D/player.h" +#include "terrain/terrData.h" +#include "sceneGraph/sceneGraph.h" +#include "../others/CSLock.h" + +NetConnection * CGlobalStatic::g_pScopingConn = NULL; +std::vector * CGlobalStatic::g_pActorsFounded = NULL; +WL::LockFreeChunker CGlobalStatic::g_ChunkListPkt; +WL::LockFreeChunker CGlobalStatic::g_ChunkListPktSend; +bool CGlobalStatic::g_bShutDown = false; +HANDLE CGlobalStatic::g_hRcvThread = NULL; +HANDLE CGlobalStatic::g_hSendThread = NULL; +HANDLE CGlobalStatic::g_hEventSend = NULL; + +extern int udpSocket; +extern void IPSocketToNetAddress(const struct sockaddr_in *sockAddr, NetAddress *address); +extern void netToIPSocketAddress(const NetAddress *address, struct sockaddr_in *sockAddr); + +DWORD WINAPI ThreadSend( LPVOID lpParam ) +{ + WL::CLockFreeQueue * pListSend = CWLMgr::getInstance()->getStack_SendPkt(); + PKTSEND * pkt = NULL; + sockaddr_in ipAddr; + while (!CGlobalStatic::g_bShutDown) + { + WaitForSingleObject(CGlobalStatic::g_hEventSend,2000); + //Platform::sleep(1); + while(pListSend->size()) + { + pkt = (PKTSEND*)(pListSend->pop()); + if (pkt == NULL) + continue; + netToIPSocketAddress(&(pkt->address), &ipAddr); + ::sendto(udpSocket,(const char *)pkt->data,pkt->dataSize,0,(sockaddr *) &ipAddr, sizeof(sockaddr_in)); + CGlobalStatic::freePktSend(pkt); + } + } + return 0; +} + +DWORD WINAPI ThreadRecv( LPVOID lpParam ) +{ + sockaddr sa; + sa.sa_family = AF_UNSPEC; + PKT * tmpBuffer = NULL; + int addrLen = sizeof(sa); + S32 bytesRead = -1; + while (!CGlobalStatic::g_bShutDown) + { + addrLen = sizeof(sa); + bytesRead = -1; + tmpBuffer = CGlobalStatic::allocPkt(); + //tmpBuffer->data.alloc(MAXPACKETSIZE); + + if(udpSocket != InvalidSocket) + bytesRead = recvfrom(udpSocket, (char *) tmpBuffer->data, MAXPACKETSIZE, 0, &sa, &addrLen); + + if(bytesRead == -1) + { + CGlobalStatic::freePkt(tmpBuffer); + continue; + } + + if(sa.sa_family == AF_INET) + IPSocketToNetAddress((sockaddr_in *) &sa, &(tmpBuffer->address)); + else + { + CGlobalStatic::freePkt(tmpBuffer); + continue; + } + + if(bytesRead <= 0) + { + CGlobalStatic::freePkt(tmpBuffer); + continue; + } + + if(tmpBuffer->address.type == NetAddress::IPAddress && + tmpBuffer->address.netNum[0] == 127 && + tmpBuffer->address.netNum[1] == 0 && + tmpBuffer->address.netNum[2] == 0 && + tmpBuffer->address.netNum[3] == 1 && + tmpBuffer->address.port == 0) + { + CGlobalStatic::freePkt(tmpBuffer); + continue; + } + + tmpBuffer->bytesRead = bytesRead; + CWLMgr::getInstance()->getStack_RecvPkt()->push(tmpBuffer); + } + return 0; +} + + +void CGlobalStatic::init() +{ + CWLMgr::getInstance(); + g_hRcvThread = CreateThread(NULL,0,ThreadRecv,NULL,0,NULL); + g_hSendThread = CreateThread(NULL,0,ThreadSend,NULL,0,NULL); + g_hEventSend = CreateEvent( + NULL, // default security attributes + FALSE, // auto reset event + FALSE, // initial state is nonsignaled + NULL + ); +} + +void CGlobalStatic::shutdown() +{ + g_bShutDown = true; + WaitForSingleObject(g_hRcvThread,2000); + ::TerminateThread(g_hRcvThread,0); + ::TerminateThread(g_hSendThread,0); + CloseHandle(g_hRcvThread); + CloseHandle(g_hSendThread); + CloseHandle(g_hEventSend); + CWLMgr::destroy(); +} + +void CGlobalStatic::scope(void * pContent) +{ + SceneObject * pObj = reinterpret_cast(pContent); + if(pObj->isScopeable()) + g_pScopingConn->objectInScope(pObj); +} + +void CGlobalStatic::setScopingConnection(NetConnection * pConn) +{ + g_pScopingConn = pConn; +} + +void CGlobalStatic::tick() +{ + static U32 time = Platform::getVirtualMilliseconds(); + static U32 timeDelta; + timeDelta = Platform::getVirtualMilliseconds() - time; + time = Platform::getVirtualMilliseconds(); + + static int sendCount; + static int recvCount; + sendCount = CWLMgr::getInstance()->getStack_SendPkt()->size(); + recvCount = CWLMgr::getInstance()->getStack_RecvPkt()->size(); + if (sendCount > 10) + { + Con::printf("send list %d",sendCount); + } + if (recvCount > 10) + { + Con::printf("recv list %d",recvCount); + } + + //monsters tick + /* + static CMonsterManager * pMonsterMgr = CMonsterManager::getSingleTon(); + if (pMonsterMgr) + pMonsterMgr->onTick(timeDelta); + else + pMonsterMgr = CMonsterManager::getSingleTon(); + */ +} + +void CGlobalStatic::freePkt(PKT* pkt) +{ + pkt->~PKT(); + g_ChunkListPkt.free(pkt); +} + +PKT * CGlobalStatic::allocPkt() +{ + PKT * p = g_ChunkListPkt.alloc(); + if (p) + { + new(p) PKT(); + } + return p; +} + +void CGlobalStatic::freePktSend(PKTSEND* pkt) +{ + pkt->~PKTSEND(); + g_ChunkListPktSend.free(pkt); +} + +PKTSEND * CGlobalStatic::allocPktSend(const char * data,int size) +{ + PKTSEND * p = g_ChunkListPktSend.alloc(); + if (p) + { + new(p) PKTSEND(data,size); + } + return p; +} + +F32 CGlobalStatic::getMapHeight(const Point2F xy) +{ + TerrainBlock* pBlock = gServerSceneGraph->getCurrentTerrain(); + F32 height; + Point2F mXY(xy); + Point3F position = pBlock->getPosition(); + mXY.x -= position.x; + mXY.y -= position.y; + if (pBlock->getHeight(mXY,&height)) + return height; + return -1; +} + +void CGlobalStatic::getActorsSurrounded( Player * pSelf , std::vector * actorsID ) +{ + Point3F pos; + MatrixF transform = pSelf->getTransform(); + transform.getColumn(3,&pos); + g_pActorsFounded = actorsID; + CWLMgr::getInstance()->getSpaceMesh()->visitExceptSelf(pSelf,WL::Pt3F(pos.x,pos.y,pos.z),CGlobalStatic::actorFounded); +} + +void CGlobalStatic::actorFounded( void * pContent ) +{ + Player * pObj = reinterpret_cast(pContent); + if(pObj) + g_pActorsFounded->push_back(pObj->getId()); +} + +//×Ö·û´®hash +unsigned int hash(const char *str) +{ + register unsigned int h; + register unsigned char *p; + for(h=0, p = (unsigned char *)str; *p ; p++) + h = 31 * h + *p; + return h; +} + +//¿í×Ö·û´®hash +unsigned int hash(const wchar_t *key) +{ + register wchar_t *s; + register int c, k; + s = (wchar_t *)key; + k = *s; + while ((c = *++s)) + k = 31 * k + c; + return k; +} \ No newline at end of file diff --git a/add/Global/GlobalStatic.h b/add/Global/GlobalStatic.h new file mode 100644 index 0000000..08a15b2 --- /dev/null +++ b/add/Global/GlobalStatic.h @@ -0,0 +1,60 @@ +#ifndef __GLOBAL_STATIC__ +#define __GLOBAL_STATIC__ +#include "platform/platformNet.h" +#include "../Others/LockFreeChunker.h" +#include "math/mMath.h" +#include + +#include +class NetConnection; +class Player; + +struct PKT +{ + char data[MAXPACKETSIZE]; + NetAddress address; + int bytesRead; + ~PKT(){/*data.~RawData();*/} +}; +struct PKTSEND +{ + char data[MAXPACKETSIZE]; + int dataSize; + NetAddress address; + PKTSEND(const char * pData,int size):dataSize(size) + { + if (pData && size > 0) + { + dMemcpy(data,pData,size); + } + } + ~PKTSEND(){/*if(data)delete[] data;*/} +}; +class CGlobalStatic +{ +protected: + static std::vector * g_pActorsFounded; + static NetConnection * g_pScopingConn; + static WL::LockFreeChunker g_ChunkListPkt; + static WL::LockFreeChunker g_ChunkListPktSend; + static HANDLE g_hRcvThread; + static HANDLE g_hSendThread; +public: + static bool g_bShutDown; + static HANDLE g_hEventSend; + + static void init(); + static void shutdown(); + static void tick(); + static void scope(void * pContent); + static void setScopingConnection(NetConnection * pConn); + static PKT * allocPkt(); + static void freePkt(PKT* pkt); + static PKTSEND * allocPktSend(const char * data,int size); + static void freePktSend(PKTSEND* pkt); + static F32 getMapHeight(const Point2F xy); + static void getActorsSurrounded(Player * pSelf , std::vector * actorsID);//»ñµÃÖÜΧµÄplayer + static void actorFounded(void * pContent); +}; + +#endif \ No newline at end of file diff --git a/add/Others/CSLock.h b/add/Others/CSLock.h new file mode 100644 index 0000000..498e95c --- /dev/null +++ b/add/Others/CSLock.h @@ -0,0 +1,14 @@ +#ifndef __CS_LOCK__ +#define __CS_LOCK__ + +#include + +class CSLock +{ + CRITICAL_SECTION * _pCS; +public: + CSLock(CRITICAL_SECTION * cs):_pCS(cs){EnterCriticalSection(_pCS);} + ~CSLock(){LeaveCriticalSection(_pCS);} +}; + +#endif \ No newline at end of file diff --git a/add/Others/LockFreeChunker.cpp b/add/Others/LockFreeChunker.cpp new file mode 100644 index 0000000..6ebad3a --- /dev/null +++ b/add/Others/LockFreeChunker.cpp @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Element Engine +// +//----------------------------------------------------------------------------- + +#include "LockFreeChunker.h" +#include +//---------------------------------------------------------------------------- +namespace WL +{ + +DataChunker::DataChunker(S32 size) +{ + chunkSize = size; + curBlock = new DataBlock(size); + curBlock->next = 0; + curBlock->curIndex = 0; +} + +DataChunker::~DataChunker() +{ + freeBlocks(); +} + +void *DataChunker::alloc(S32 size) +{ + assert(size <= chunkSize); + if(!curBlock || size + curBlock->curIndex > chunkSize) + { + DataBlock *temp = new DataBlock(chunkSize); + temp->next = curBlock; + temp->curIndex = 0; + curBlock = temp; + } + void *ret = curBlock->data + curBlock->curIndex; + curBlock->curIndex += (size + 3) & ~3; // dword align + return ret; +} + +DataChunker::DataBlock::DataBlock(S32 size) +{ + data = new U8[size]; +} + +DataChunker::DataBlock::~DataBlock() +{ + delete[] data; +} + +void DataChunker::freeBlocks() +{ + while(curBlock) + { + DataBlock *temp = curBlock->next; + delete curBlock; + curBlock = temp; + } +} + +} \ No newline at end of file diff --git a/add/Others/LockFreeChunker.h b/add/Others/LockFreeChunker.h new file mode 100644 index 0000000..649b10f --- /dev/null +++ b/add/Others/LockFreeChunker.h @@ -0,0 +1,171 @@ +#ifndef _LOCKFREEDATACHUNKER_H_ +#define _LOCKFREEDATACHUNKER_H_ + +#include "CSLock.h" + +namespace WL +{ + + typedef signed char S8; ///< Compiler independent Signed Char + typedef unsigned char U8; ///< Compiler independent Unsigned Char + + typedef signed short S16; ///< Compiler independent Signed 16-bit short + typedef unsigned short U16; ///< Compiler independent Unsigned 16-bit short + + typedef signed int S32; ///< Compiler independent Signed 32-bit integer + typedef unsigned int U32; ///< Compiler independent Unsigned 32-bit integer + + typedef float F32; ///< Compiler independent 32-bit float + typedef double F64; ///< Compiler independent 64-bit float + +class DataChunker +{ + public: + enum { + ChunkSize = 16376 ///< Default size for chunks. + }; + + private: + + struct DataBlock + { + DataBlock *next; + U8 *data; + S32 curIndex; + DataBlock(S32 size); + ~DataBlock(); + }; + DataBlock *curBlock; + S32 chunkSize; + + public: + + void *alloc(S32 size); + + void freeBlocks(); + + DataChunker(S32 size=ChunkSize); + virtual ~DataChunker(); +}; + + +//---------------------------------------------------------------------------- + +template +class Chunker: private DataChunker +{ +public: + Chunker(S32 size = DataChunker::ChunkSize) : DataChunker(size) {}; + T* alloc() { return reinterpret_cast(DataChunker::alloc(S32(sizeof(T)))); } + void clear() { freeBlocks(); }; +}; + +template +class FreeListChunker: private DataChunker +{ + S32 numAllocated; + S32 elementSize; + T *freeListHead; +public: + FreeListChunker(S32 size = DataChunker::ChunkSize) : DataChunker(size) + { + numAllocated = 0; + elementSize = getMax(U32(sizeof(T)), U32(sizeof(T *))); + freeListHead = NULL; + } + + T *alloc() + { + numAllocated++; + if(freeListHead == NULL) + return reinterpret_cast(DataChunker::alloc(elementSize)); + T* ret = freeListHead; + freeListHead = *(reinterpret_cast(freeListHead)); + return ret; + } + + void free(T* elem) + { + numAllocated--; + *(reinterpret_cast(elem)) = freeListHead; + freeListHead = elem; + + // If nothing's allocated, clean up! + if(!numAllocated) + { + freeBlocks(); + freeListHead = NULL; + } + } + + // Allow people to free all their memory if they want. + void freeBlocks() + { + DataChunker::freeBlocks(); + + // We have to terminate the freelist as well or else we'll run + // into crazy unused memory. + freeListHead = NULL; + } +}; + + +template +class LockFreeChunker: private DataChunker +{ + S32 numAllocated; + S32 elementSize; + T *freeListHead; + CRITICAL_SECTION _mCS; +public: + LockFreeChunker(S32 size = DataChunker::ChunkSize) : DataChunker(size) + { + numAllocated = 0; + elementSize = getMax(U32(sizeof(T)), U32(sizeof(T *))); + freeListHead = NULL; + ::InitializeCriticalSection(&_mCS); + } + ~LockFreeChunker() + { + ::DeleteCriticalSection(&_mCS); + } + + T *alloc() + { + CSLock lock(&_mCS); + numAllocated++; + if(freeListHead == NULL) + return reinterpret_cast(DataChunker::alloc(elementSize)); + T* ret = freeListHead; + freeListHead = *(reinterpret_cast(freeListHead)); + return ret; + } + + void free(T* elem) + { + CSLock lock(&_mCS); + numAllocated--; + *(reinterpret_cast(elem)) = freeListHead; + freeListHead = elem; + + // If nothing's allocated, clean up! + if(!numAllocated) + { + freeBlocks(); + freeListHead = NULL; + } + } + + // Allow people to free all their memory if they want. + void freeBlocks() + { + CSLock lock(&_mCS); + DataChunker::freeBlocks(); + // We have to terminate the freelist as well or else we'll run + // into crazy unused memory. + freeListHead = NULL; + } +}; + +} +#endif diff --git a/add/Others/StringHash.h b/add/Others/StringHash.h new file mode 100644 index 0000000..a9c14ff --- /dev/null +++ b/add/Others/StringHash.h @@ -0,0 +1,36 @@ +#ifndef __STRINGHASH_H__ +#define __STRINGHASH_H__ + +#include +#include + +//¿í×Ö·û´®hash +unsigned int hash(const wchar_t *key); +struct widestring_hash_compare : public stdext::hash_compare +{ + size_t operator()(const std::wstring& A)const + { + return hash(A.c_str()); + } + bool operator()(const std::wstring& a1, const std::wstring& a2)const + { + return a1 != a2; + } +}; + +//×Ö·û´®hash +unsigned int hash(const char *str); +struct string_hash_compare : public stdext::hash_compare +{ + size_t operator()(const std::string& A)const + { + return hash(A.c_str()); + } + + bool operator()(const std::string& a1, const std::string& a2)const + { + return a1 != a2; + } +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_Caster.cpp b/add/RPGPack/Constraint/CST_Caster.cpp new file mode 100644 index 0000000..2b73df5 --- /dev/null +++ b/add/RPGPack/Constraint/CST_Caster.cpp @@ -0,0 +1,34 @@ +#include "CST_Caster.h" +#include "T3D/player.h" + +IMPLEMENT_CONOBJECT(CST_Caster); +CST_Caster::CST_Caster() +{ + +} + +void CST_Caster::onCasterAndTargetSetted() +{ + setSceneObj(dynamic_cast(Sim::findObject(getCasterSimID()))); +} +//=========================DESC===================== +class CST_Caster_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_Caster_Desc::isMatchDesc( const char * szConstraint ) +{ + return dStrstr(szConstraint,"#caster") != NULL; +} + +Constraint * CST_Caster_Desc::createConstraint( const char * szConstraint ) +{ + CST_Caster * pInstance = NULL; + pInstance = new CST_Caster(); + + return pInstance; +} +IMPLEMENT_CSTDESC(CST_Caster_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_Caster.h b/add/RPGPack/Constraint/CST_Caster.h new file mode 100644 index 0000000..16258dc --- /dev/null +++ b/add/RPGPack/Constraint/CST_Caster.h @@ -0,0 +1,22 @@ +#ifndef __CST_CASTER__ +#define __CST_CASTER__ + +#include "CST_Player.h" + +/* +supported constraint string : +#caster //ÊÍ·ÅÕß +*/ + +class CST_Caster : public CST_Player +{ + typedef CST_Player Parent; +public: + CST_Caster(); + + void onCasterAndTargetSetted(); + + DECLARE_CONOBJECT(CST_Caster); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_GameBase.cpp b/add/RPGPack/Constraint/CST_GameBase.cpp new file mode 100644 index 0000000..2777752 --- /dev/null +++ b/add/RPGPack/Constraint/CST_GameBase.cpp @@ -0,0 +1,36 @@ +#include "CST_GameBase.h" +#include "T3D/gameBase.h" + +IMPLEMENT_CONOBJECT(CST_GameBase); + +CST_GameBase::CST_GameBase() +{ + +} + +//=========================DESC===================== +class CST_GameBase_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_GameBase_Desc::isMatchDesc( const char * szConstraint ) +{ + return dStrstr(szConstraint,"#gamebase") != NULL; +} + +Constraint * CST_GameBase_Desc::createConstraint( const char * szConstraint ) +{ + CST_GameBase * pInstance = NULL; + const char * pName = dStrchr(szConstraint,'.'); + SceneObject * pObj = dynamic_cast( Sim::findObject(pName) ); + if (pName && pObj) + { + pInstance = new CST_GameBase(); + pInstance->setSceneObj(pObj); + } + return pInstance; +} +IMPLEMENT_CSTDESC(CST_GameBase_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_GameBase.h b/add/RPGPack/Constraint/CST_GameBase.h new file mode 100644 index 0000000..cf928ca --- /dev/null +++ b/add/RPGPack/Constraint/CST_GameBase.h @@ -0,0 +1,20 @@ +#ifndef __CST_GAMEBASE__ +#define __CST_GAMEBASE__ + +#include "CST_SceneObject.h" + +/* +supported constraint string : +#gamebase.objName //³¡¾°ÖнÐobjNameµÄÒ»¸ösceneObject +*/ + +class CST_GameBase : public CST_SceneObj +{ + typedef CST_SceneObj Parent; +public: + CST_GameBase(); + + DECLARE_CONOBJECT(CST_GameBase); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_Player.cpp b/add/RPGPack/Constraint/CST_Player.cpp new file mode 100644 index 0000000..da1ecc7 --- /dev/null +++ b/add/RPGPack/Constraint/CST_Player.cpp @@ -0,0 +1,44 @@ +#include "CST_Player.h" +#include "T3D/player.h" + +IMPLEMENT_CONOBJECT(CST_Player); + +CST_Player::CST_Player() +{ + +} + +void CST_Player::setAnimClip( const char * clipName , bool locked /*= false*/ ) +{ + Player * pPlayer = dynamic_cast(getSceneObj()); + if (pPlayer) + { + pPlayer->setActionThread(clipName,false,true,false); + } +} +//=========================DESC===================== +class CST_Player_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_Player_Desc::isMatchDesc( const char * szConstraint ) +{ + return dStrstr(szConstraint,"#player") != NULL; +} + +Constraint * CST_Player_Desc::createConstraint( const char * szConstraint ) +{ + CST_Player * pInstance = NULL; + const char * pName = dStrchr(szConstraint,'.'); + SceneObject * pObj = dynamic_cast( Sim::findObject(pName) ); + if (pName && pObj) + { + pInstance = new CST_Player(); + pInstance->setSceneObj(pObj); + } + return pInstance; +} +IMPLEMENT_CSTDESC(CST_Player_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_Player.h b/add/RPGPack/Constraint/CST_Player.h new file mode 100644 index 0000000..b2efedd --- /dev/null +++ b/add/RPGPack/Constraint/CST_Player.h @@ -0,0 +1,23 @@ +#ifndef __CST_PLAYER__ +#define __CST_PLAYER__ + +#include "CST_ShapeBase.h" + +/* +supported constraint string : +#player.objName //Ò»¸öÖ¸¶¨µÄplayer +*/ + +class Player; +class CST_Player : public CST_ShapeBase +{ + typedef CST_ShapeBase Parent; +public: + CST_Player(); + + void setAnimClip(const char * clipName , bool locked = false); + + DECLARE_CONOBJECT(CST_Player); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_Point.cpp b/add/RPGPack/Constraint/CST_Point.cpp new file mode 100644 index 0000000..2514595 --- /dev/null +++ b/add/RPGPack/Constraint/CST_Point.cpp @@ -0,0 +1,86 @@ +#include "CST_Point.h" +#include "T3D/decal/decalData.h" +#include "T3D/decal/decalManager.h" + +IMPLEMENT_CONOBJECT(CST_Point); +CST_Point::CST_Point(): +_mPosition(Point3F::Zero), +_mTranform(MatrixF::Identity) +{ + +} + +const Point3F & CST_Point::getConstraintPos() +{ + return _mPosition; +} + +const MatrixF & CST_Point::getConstraintTransform() +{ + return _mTranform; +} + +DecalInstance* CST_Point::addGroundDecal( DecalData * decalData ) +{ + if (decalData) + { + Point3F pos; + RayInfo rInfo; + MatrixF mat = _mTranform; + mat.getColumn(3, &pos); + if (gClientContainer.castRay(Point3F(pos.x, pos.y, pos.z + 0.01f), + Point3F(pos.x, pos.y, pos.z - 2.0f ), + STATIC_COLLISION_MASK | VehicleObjectType, &rInfo)) + { + Point3F normal; + Point3F tangent; + mat = _mTranform; + mat.getColumn( 0, &tangent ); + mat.getColumn( 2, &normal ); + DecalInstance* pDecal = gDecalManager->addDecal( rInfo.point, normal, tangent, decalData ,1,0,CustomDecal); + return pDecal; + } + } + return NULL; +} + +CST_Point::~CST_Point() +{ + +} +//=========================DESC===================== +class CST_Point_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_Point_Desc::isMatchDesc( const char * szConstraint ) +{ + F32 x,y,z,ax,ay,az,axis; + S32 readed = dSscanf(szConstraint,"%g %g %g %g %g %g %g",&x,&y,&z,&ax,&ay,&az,&axis); + return readed == 3 || readed == 7; +} + +Constraint * CST_Point_Desc::createConstraint( const char * szConstraint ) +{ + Point3F pos; + const MatrixF& tmat = MatrixF::Identity; + tmat.getColumn(3,&pos); + AngAxisF aa(tmat); + + dSscanf(szConstraint,"%g %g %g %g %g %g %g", + &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); + + MatrixF mat; + aa.setMatrix(&mat); + mat.setColumn(3,pos); + + CST_Point * pInstance = new CST_Point(); + pInstance->_mPosition = pos; + pInstance->_mTranform = mat; + + return pInstance; +} +IMPLEMENT_CSTDESC(CST_Point_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_Point.h b/add/RPGPack/Constraint/CST_Point.h new file mode 100644 index 0000000..f717a01 --- /dev/null +++ b/add/RPGPack/Constraint/CST_Point.h @@ -0,0 +1,28 @@ +#ifndef __CST_POINT__ +#define __CST_POINT__ + +#include "Constraint.h" + +/* +supported constraint string : +"x y z" //ijһ¸ö×ø±ê +"x y z ax ay az axi" //transform +*/ + +class CST_Point : public Constraint +{ + typedef Constraint Parent; +public: + Point3F _mPosition; + MatrixF _mTranform; + CST_Point(); + ~CST_Point(); + + const Point3F & getConstraintPos(); + const MatrixF & getConstraintTransform(); + DecalInstance* addGroundDecal(DecalData * decalData); + + DECLARE_CONOBJECT(CST_Point); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_SceneObject.cpp b/add/RPGPack/Constraint/CST_SceneObject.cpp new file mode 100644 index 0000000..9c3f603 --- /dev/null +++ b/add/RPGPack/Constraint/CST_SceneObject.cpp @@ -0,0 +1,105 @@ +#include "CST_SceneObject.h" +#include "sceneGraph/sceneObject.h" +#include "T3D/decal/decalData.h" +#include "T3D/decal/decalManager.h" + +IMPLEMENT_CONOBJECT(CST_SceneObj); +CST_SceneObj::CST_SceneObj():_mbSceneObj(NULL), +_mPosition(Point3F::Zero) +{ + +} + +const Point3F & CST_SceneObj::getConstraintPos() +{ + if (_mbSceneObj) + { + _mPosition = _mbSceneObj->getPosition(); + } + return _mPosition; +} + +const MatrixF & CST_SceneObj::getConstraintTransform() +{ + return _mbSceneObj ? _mbSceneObj->getTransform() : MatrixF::Identity; +} + +void CST_SceneObj::setSceneObj( SceneObject * pObj ) +{ + if (pObj != _mbSceneObj) + { + if(_mbSceneObj) + clearNotify(_mbSceneObj); + if(pObj) + deleteNotify(pObj); + _mbSceneObj = pObj; + } +} + +void CST_SceneObj::onDeleteNotify( SimObject *object ) +{ + if (object == _mbSceneObj) + { + _mbSceneObj = NULL; + } +} + +DecalInstance* CST_SceneObj::addGroundDecal( DecalData * decalData ) +{ + if (_mbSceneObj && decalData) + { + Point3F pos; + RayInfo rInfo; + MatrixF mat = _mbSceneObj->getTransform(); + mat.getColumn(3, &pos); + if (gClientContainer.castRay(Point3F(pos.x, pos.y, pos.z + 0.01f), + Point3F(pos.x, pos.y, pos.z - 2.0f ), + STATIC_COLLISION_MASK | VehicleObjectType, &rInfo)) + { + Point3F normal; + Point3F tangent; + mat = _mbSceneObj->getTransform(); + mat.getColumn( 0, &tangent ); + mat.getColumn( 2, &normal ); + DecalInstance* pDecal = gDecalManager->addDecal( rInfo.point, normal, tangent, decalData ,1,0,CustomDecal); + return pDecal; + } + } + return NULL; +} + +SceneObject * CST_SceneObj::getSceneObj() +{ + return _mbSceneObj; +} + +CST_SceneObj::~CST_SceneObj() +{ + setSceneObj(NULL); +} +//=========================DESC===================== +class CST_SceneObj_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_SceneObj_Desc::isMatchDesc( const char * szConstraint ) +{ + return dStrstr(szConstraint,"#scene") != NULL; +} + +Constraint * CST_SceneObj_Desc::createConstraint( const char * szConstraint ) +{ + CST_SceneObj * pInstance = NULL; + const char * pName = dStrchr(szConstraint,'.'); + SceneObject * pObj = dynamic_cast( Sim::findObject(pName) ); + if (pName && pObj) + { + pInstance = new CST_SceneObj(); + pInstance->setSceneObj(pObj); + } + return pInstance; +} +IMPLEMENT_CSTDESC(CST_SceneObj_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_SceneObject.h b/add/RPGPack/Constraint/CST_SceneObject.h new file mode 100644 index 0000000..313e16f --- /dev/null +++ b/add/RPGPack/Constraint/CST_SceneObject.h @@ -0,0 +1,32 @@ +#ifndef __CST_SCENEOBJ__ +#define __CST_SCENEOBJ__ + +#include "Constraint.h" + +/* +supported constraint string : +#scene.objName //³¡¾°ÖнÐobjNameµÄÒ»¸ösceneObject +*/ +class DecalInstance; +class SceneObject; +class CST_SceneObj : public Constraint +{ + typedef Constraint Parent; + SceneObject * _mbSceneObj; + Point3F _mPosition; +public: + CST_SceneObj(); + ~CST_SceneObj(); + void setSceneObj(SceneObject * pObj); + SceneObject * getSceneObj(); + void onDeleteNotify(SimObject *object); + + const Point3F & getConstraintPos(); + const MatrixF & getConstraintTransform(); + + DecalInstance* addGroundDecal(DecalData * decalData); + + DECLARE_CONOBJECT(CST_SceneObj); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_ShapeBase.cpp b/add/RPGPack/Constraint/CST_ShapeBase.cpp new file mode 100644 index 0000000..16c1e57 --- /dev/null +++ b/add/RPGPack/Constraint/CST_ShapeBase.cpp @@ -0,0 +1,36 @@ +#include "CST_ShapeBase.h" +#include "T3D/shapeBase.h" + +IMPLEMENT_CONOBJECT(CST_ShapeBase); + +CST_ShapeBase::CST_ShapeBase() +{ + +} + +//=========================DESC===================== +class CST_ShapeBase_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_ShapeBase_Desc::isMatchDesc( const char * szConstraint ) +{ + return dStrstr(szConstraint,"#shapebase") != NULL; +} + +Constraint * CST_ShapeBase_Desc::createConstraint( const char * szConstraint ) +{ + CST_ShapeBase * pInstance = NULL; + const char * pName = dStrchr(szConstraint,'.'); + SceneObject * pObj = dynamic_cast( Sim::findObject(pName) ); + if (pName && pObj) + { + pInstance = new CST_ShapeBase(); + pInstance->setSceneObj(pObj); + } + return pInstance; +} +IMPLEMENT_CSTDESC(CST_ShapeBase_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_ShapeBase.h b/add/RPGPack/Constraint/CST_ShapeBase.h new file mode 100644 index 0000000..2ec6a07 --- /dev/null +++ b/add/RPGPack/Constraint/CST_ShapeBase.h @@ -0,0 +1,20 @@ +#ifndef __CST_SHAPEBASE__ +#define __CST_SHAPEBASE__ + +#include "CST_GameBase.h" + +/* +supported constraint string : +#shapebase.objName //³¡¾°ÖнÐobjNameµÄÒ»¸ösceneObject +*/ + +class CST_ShapeBase : public CST_GameBase +{ + typedef CST_GameBase Parent; +public: + CST_ShapeBase(); + + DECLARE_CONOBJECT(CST_ShapeBase); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_Target.cpp b/add/RPGPack/Constraint/CST_Target.cpp new file mode 100644 index 0000000..e69de29 diff --git a/add/RPGPack/Constraint/CST_Target.h b/add/RPGPack/Constraint/CST_Target.h new file mode 100644 index 0000000..e69de29 diff --git a/add/RPGPack/Constraint/CST_TerrainClickedPoint.cpp b/add/RPGPack/Constraint/CST_TerrainClickedPoint.cpp new file mode 100644 index 0000000..00cfa3b --- /dev/null +++ b/add/RPGPack/Constraint/CST_TerrainClickedPoint.cpp @@ -0,0 +1,30 @@ +#include "CST_TerrainClickedPoint.h" +#include "T3D/decal/decalData.h" +#include "T3D/decal/decalManager.h" + +IMPLEMENT_CONOBJECT(CST_TerrainClicked); +Point3F CST_TerrainClicked::smTerrainClicked(Point3F::Zero); +//=========================DESC===================== +class CST_TerrainClicked_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_TerrainClicked_Desc::isMatchDesc( const char * szConstraint ) +{ + return dStrstr(szConstraint,"#terrain_clicked") != NULL; +} + +Constraint * CST_TerrainClicked_Desc::createConstraint( const char * szConstraint ) +{ + CST_TerrainClicked * pInstance = new CST_TerrainClicked(); + pInstance->_mPosition = CST_TerrainClicked::smTerrainClicked; + pInstance->_mTranform.setPosition(CST_TerrainClicked::smTerrainClicked); + pInstance->_mTranform.setColumn(0,Point3F(1.f,0.f,0.f)); + pInstance->_mTranform.setColumn(1,Point3F(0.f,1.f,0.f)); + pInstance->_mTranform.setColumn(2,Point3F(0.f,0.f,1.f)); + return pInstance; +} +IMPLEMENT_CSTDESC(CST_TerrainClicked_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_TerrainClickedPoint.h b/add/RPGPack/Constraint/CST_TerrainClickedPoint.h new file mode 100644 index 0000000..6b62262 --- /dev/null +++ b/add/RPGPack/Constraint/CST_TerrainClickedPoint.h @@ -0,0 +1,19 @@ +#ifndef __CST_CLICKED_POINT__ +#define __CST_CLICKED_POINT__ + +#include "CST_Point.h" + +/* +supported constraint string : +"#terrain_clicked" //Êó±êµã»÷µØÃæ +*/ + +class CST_TerrainClicked : public CST_Point +{ + typedef CST_Point Parent; +public: + static Point3F smTerrainClicked; + DECLARE_CONOBJECT(CST_TerrainClicked); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_TerrainRolloverPoint.cpp b/add/RPGPack/Constraint/CST_TerrainRolloverPoint.cpp new file mode 100644 index 0000000..5c2e647 --- /dev/null +++ b/add/RPGPack/Constraint/CST_TerrainRolloverPoint.cpp @@ -0,0 +1,28 @@ +#include "CST_TerrainRolloverPoint.h" +#include "T3D/decal/decalData.h" +#include "T3D/decal/decalManager.h" + +IMPLEMENT_CONOBJECT(CST_TerrainRollover); +Point3F CST_TerrainRollover::smTerrainRollover(Point3F::Zero); +//=========================DESC===================== +class CST_TerrainRollover_Desc : public ConstraintDesc +{ +public: + bool isMatchDesc(const char * szConstraint); + Constraint * createConstraint(const char * szConstraint); +}; + +bool CST_TerrainRollover_Desc::isMatchDesc( const char * szConstraint ) +{ + return dStrstr(szConstraint,"#terrain_rollover") != NULL; +} + +Constraint * CST_TerrainRollover_Desc::createConstraint( const char * szConstraint ) +{ + CST_TerrainRollover * pInstance = new CST_TerrainRollover(); + pInstance->_mPosition = CST_TerrainRollover::smTerrainRollover; + pInstance->_mTranform.setPosition(CST_TerrainRollover::smTerrainRollover); + + return pInstance; +} +IMPLEMENT_CSTDESC(CST_TerrainRollover_Desc); \ No newline at end of file diff --git a/add/RPGPack/Constraint/CST_TerrainRolloverPoint.h b/add/RPGPack/Constraint/CST_TerrainRolloverPoint.h new file mode 100644 index 0000000..0c3a360 --- /dev/null +++ b/add/RPGPack/Constraint/CST_TerrainRolloverPoint.h @@ -0,0 +1,19 @@ +#ifndef __CST_ROLLOVER_POINT__ +#define __CST_ROLLOVER_POINT__ + +#include "CST_Point.h" + +/* +supported constraint string : +"#terrain_rollover" //Êó±êÒƶ¯ +*/ + +class CST_TerrainRollover : public CST_Point +{ + typedef CST_Point Parent; +public: + static Point3F smTerrainRollover; + DECLARE_CONOBJECT(CST_TerrainRollover); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Constraint/Constraint.cpp b/add/RPGPack/Constraint/Constraint.cpp new file mode 100644 index 0000000..b17a528 --- /dev/null +++ b/add/RPGPack/Constraint/Constraint.cpp @@ -0,0 +1,71 @@ +#include "Constraint.h" + +IMPLEMENT_CONOBJECT(Constraint); + +std::vector< ConstraintDesc * > ConstraintDesc::smDescs; +ConstraintDesc::ConstraintDesc() +{ + smDescs.push_back(this); +} + +Constraint * ConstraintDesc::getConstraint( const char * szConstraint) +{ + for (U32 i = 0 ; i < smDescs.size() ; ++i) + { + if (smDescs[i]->isMatchDesc(szConstraint)) + { + return smDescs[i]->createConstraint(szConstraint); + } + } + return NULL; +} + +const Point3F & Constraint::getConstraintPos() +{ + return Point3F::Zero; +} + +void Constraint::setCasterSimID( const U32 & id ) +{ + _casterSimID = id; +} + +void Constraint::setTargetSimID( const U32 & id ) +{ + _targetSimID = id; +} + +const U32 & Constraint::getCasterSimID() +{ + return _casterSimID; +} + +const U32 & Constraint::getTargetSimID() +{ + return _targetSimID; +} + +const MatrixF & Constraint::getConstraintTransform() +{ + return MatrixF::Identity; +} + +void Constraint::setAnimClip( const char * clipName , bool locked /*= false*/ ) +{ + +} + +void Constraint::onCasterAndTargetSetted() +{ + +} + +DecalInstance* Constraint::addGroundDecal( DecalData * decalData ) +{ + return NULL; +} + +Constraint::~Constraint() +{ + +} \ No newline at end of file diff --git a/add/RPGPack/Constraint/Constraint.h b/add/RPGPack/Constraint/Constraint.h new file mode 100644 index 0000000..861be87 --- /dev/null +++ b/add/RPGPack/Constraint/Constraint.h @@ -0,0 +1,52 @@ +#ifndef __CONSTRAINT_H__ +#define __CONSTRAINT_H__ + +#include +#include "math/mMath.h" +#include "console/simObject.h" + +class GameConnection; +class DecalData; +class DecalInstance; + +class Constraint : public SimObject +{ + typedef SimObject Parent; + U32 _casterSimID; + U32 _targetSimID; +public: + virtual ~Constraint(); + void setCasterSimID(const U32 & id); + void setTargetSimID(const U32 & id); + const U32 & getCasterSimID(); + const U32 & getTargetSimID(); + + //commen + virtual void onCasterAndTargetSetted(); + virtual const Point3F & getConstraintPos(); //Position + virtual const MatrixF & getConstraintTransform(); //transform + virtual DecalInstance* addGroundDecal(DecalData * decalData); //decal + //sceneobject + //gamebase + //shapebase + //player + virtual void setAnimClip(const char * clipName , bool locked = false); + //caster & target + + DECLARE_CONOBJECT(Constraint); +}; + +#define IMPLEMENT_CSTDESC(T) T g_##T +class ConstraintDesc +{ +public: + ConstraintDesc(); + static std::vector< ConstraintDesc * > smDescs; + //¸ù¾ÝszConstraintºÍ4¸ö²»¹Ì¶¨µÄ²ÎÊý»ñµÃÒ»¸ö¾ßÌåµÄConstraint¶ÔÏó + static Constraint * getConstraint( const char * szConstraint ); + //¸ù¾Ýdesc×Ö·û´®ÅжÏÊÇ·ñ¶ÔӦij¸öconstraint + virtual bool isMatchDesc(const char * szConstraint) = 0; + //´´½¨Ò»¸öconstraint + virtual Constraint * createConstraint(const char * szConstraint) = 0; +}; +#endif \ No newline at end of file diff --git a/add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.cpp b/add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.cpp new file mode 100644 index 0000000..e9d966a --- /dev/null +++ b/add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.cpp @@ -0,0 +1,19 @@ +#include "EffectWrapperDataPhrase.h" +#include "../EffectWrapper/EffectWrapperData.h" + +void EffectWrapperDataPhrase::addEffectWrapperData( EffectWrapperData * pData ) +{ + _mEffectWrapperDatasVec.push_back(pData); +} + +U32 EffectWrapperDataPhrase::getLastingTime() +{ + U32 maxTime = 0; + U32 temp; + for (S32 i = 0 ; i < _mEffectWrapperDatasVec.size() ; ++i) + { + temp = _mEffectWrapperDatasVec[i]->getLastingTime(); + maxTime = maxTime > temp ? maxTime : temp; + } + return maxTime; +} \ No newline at end of file diff --git a/add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.h b/add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.h new file mode 100644 index 0000000..ff0d029 --- /dev/null +++ b/add/RPGPack/EffectPhrase/EffectWrapperDataPhrase.h @@ -0,0 +1,20 @@ +#ifndef __EFFECTDATAPHRASE_H__ +#define __EFFECTDATAPHRASE_H__ + +#include +#include "math/mMath.h" + +class EffectWrapperData; +class EffectWrapperDataPhrase +{ +public: + typedef std::vector< EffectWrapperData * > EWDATALIST; +protected: + EWDATALIST _mEffectWrapperDatasVec; +public: + void addEffectWrapperData(EffectWrapperData * pData); + EWDATALIST & getEffectWrapperDatas(){return _mEffectWrapperDatasVec;} + U32 getLastingTime(); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/EffectPhrase/EffectWrapperPhrase.cpp b/add/RPGPack/EffectPhrase/EffectWrapperPhrase.cpp new file mode 100644 index 0000000..10143eb --- /dev/null +++ b/add/RPGPack/EffectPhrase/EffectWrapperPhrase.cpp @@ -0,0 +1,102 @@ +#include "EffectWrapperPhrase.h" +#include "EffectWrapperDataPhrase.h" +#include "../EffectWrapper/EffectWrapperDesc.h" +#include "../EffectWrapper/EffectWrapper.h" + +void EffectWrapperPhrase::_addEffectWrapper( EffectWrapper * pEW ) +{ + _mEffectWrappers.push_back(pEW); +} + +void EffectWrapperPhrase::constructEWList(EffectWrapperDataPhrase * EWDList , bool isServer,const U32 & casterSimID,const U32 & targetSimID ) +{ + _mEWDList = EWDList; + EffectWrapper * ew = NULL; + EffectWrapperDataPhrase::EWDATALIST & EWDS = EWDList->getEffectWrapperDatas(); + for (U32 i = 0 ; i < EWDS.size() ; ++i) + { + ew = EffectWrapperDesc::getEffectWrapper(EWDS[i],isServer,casterSimID,targetSimID); + if (ew) + { + _addEffectWrapper(ew); + } + } +} + +void EffectWrapperPhrase::phraseStart(EffectWrapperDataPhrase * EWDList , bool isServer,const U32 & casterSimID,const U32 & targetSimID) +{ + constructEWList(EWDList,isServer,casterSimID,targetSimID); + EffectWrapper * ew = NULL; + ERRORS error; + for (U32 i = 0 ; i < _mEffectWrappers.size() ; ++i) + { + ew = _mEffectWrappers[i]; + if (ew) + { + ew->ea_Start(error); + } + } +} + +void EffectWrapperPhrase::phraseUpdate(const U32 & dt ) +{ + EffectWrapper * ew = NULL; + ERRORS error; + for (U32 i = 0 ; i < _mEffectWrappers.size() ; ++i) + { + ew = _mEffectWrappers[i]; + if (ew) + { + ew->ea_Update(dt); + } + } +} + +void EffectWrapperPhrase::phraseEnd() +{ + EffectWrapper * ew = NULL; + ERRORS error; + for (U32 i = 0 ; i < _mEffectWrappers.size() ; ++i) + { + ew = _mEffectWrappers[i]; + if (ew) + { + ew->ea_End(); + } + } + destructEWList(); +} + +void EffectWrapperPhrase::destructEWList() +{ + EffectWrapper * ew = NULL; + for (U32 i = 0 ; i < _mEffectWrappers.size() ; ++i) + { + ew = _mEffectWrappers[i]; + if (ew) + { + delete ew; + } + } + _mEffectWrappers.clear(); +} + +const U32 & EffectWrapperPhrase::getLastingTime() +{ + return _nLastTime; +} + +void EffectWrapperPhrase::setLastingTime( const U32 & time ) +{ + _nLastTime = time; +} + +EffectWrapperPhrase::EffectWrapperPhrase(): +_nLastTime(0) +{ +} + +void EffectWrapperPhrase::phraseInit(EffectWrapperDataPhrase * EWDList ) +{ + setLastingTime(EWDList->getLastingTime()); +} \ No newline at end of file diff --git a/add/RPGPack/EffectPhrase/EffectWrapperPhrase.h b/add/RPGPack/EffectPhrase/EffectWrapperPhrase.h new file mode 100644 index 0000000..17a1fb6 --- /dev/null +++ b/add/RPGPack/EffectPhrase/EffectWrapperPhrase.h @@ -0,0 +1,32 @@ +#ifndef __EFFECTWRAPPERPHRASE_H__ +#define __EFFECTWRAPPERPHRASE_H__ + +#include +#include "math/mMath.h" +#include "../RPGDefs.h" + +class EffectWrapper; +class EffectWrapperDataPhrase; + +class EffectWrapperPhrase : public RPGDefs +{ +public: + typedef std::vector< EffectWrapper * > EWLIST; +protected: + U32 _nLastTime; + EWLIST _mEffectWrappers; + const EffectWrapperDataPhrase * _mEWDList; + void _addEffectWrapper(EffectWrapper * pEW); + void constructEWList(EffectWrapperDataPhrase * EWDList , bool isServer,const U32 & casterSimID,const U32 & targetSimID); + void destructEWList(); +public: + EffectWrapperPhrase(); + void phraseInit(EffectWrapperDataPhrase * EWDList ); + void phraseStart(EffectWrapperDataPhrase * EWDList , bool isServer,const U32 & casterSimID,const U32 & targetSimID); + void phraseUpdate(const U32 & dt); + void phraseEnd(); + const U32 & getLastingTime(); + void setLastingTime(const U32 & time); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/EffectWrapper/EffectWrapper.cpp b/add/RPGPack/EffectWrapper/EffectWrapper.cpp new file mode 100644 index 0000000..ff7b787 --- /dev/null +++ b/add/RPGPack/EffectWrapper/EffectWrapper.cpp @@ -0,0 +1,57 @@ +#include "EffectWrapper.h" +#include "../Constraint/Constraint.h" + +IMPLEMENT_CONOBJECT(EffectWrapper); + +bool EffectWrapper::ea_SetData( SimDataBlock * pData , ERRORS & error ) +{ + error = ERR_UNKOWN; + return false; +} + +bool EffectWrapper::ea_Start( ERRORS & error ) +{ + error = ERR_UNKOWN; + return false; +} + +void EffectWrapper::ea_Update(const U32 & dt ) +{ + +} + +void EffectWrapper::ea_End() +{ + +} + +void EffectWrapper::ea_setLastingTime( const U32 & time ) +{ + _nLastTime = time; +} + +const U32 & EffectWrapper::ea_getLastingTime() +{ + return _nLastTime; +} + +EffectWrapper::EffectWrapper() +{ + _nLastTime = 0; + _pConstraint = NULL; +} + +void EffectWrapper::ea_setConstraint( Constraint * pConstraint ) +{ + _pConstraint = pConstraint; +} + +Constraint * EffectWrapper::ea_getConstraint() +{ + return _pConstraint; +} + +EffectWrapper::~EffectWrapper() +{ + delete _pConstraint; +} \ No newline at end of file diff --git a/add/RPGPack/EffectWrapper/EffectWrapper.h b/add/RPGPack/EffectWrapper/EffectWrapper.h new file mode 100644 index 0000000..e47d97e --- /dev/null +++ b/add/RPGPack/EffectWrapper/EffectWrapper.h @@ -0,0 +1,30 @@ +#ifndef __EFFECTWRAPPER_H__ +#define __EFFECTWRAPPER_H__ + +#include "console/simObject.h" +#include "../RPGDefs.h" + +class SimDataBlock; +class Constraint; +class EffectWrapper : public SimObject , public RPGDefs +{ + typedef SimObject Parent; + U32 _nLastTime;//³ÖÐøʱ¼ä + Constraint * _pConstraint; +public: + EffectWrapper(); + ~EffectWrapper(); + void ea_setConstraint(Constraint * pConstraint); + Constraint * ea_getConstraint(); + + void ea_setLastingTime(const U32 & time); + const U32 & ea_getLastingTime(); + + virtual bool ea_SetData( SimDataBlock * pData , ERRORS & error); + virtual bool ea_Start(ERRORS & error); + virtual void ea_Update(const U32 & dt); + virtual void ea_End(); + DECLARE_CONOBJECT(EffectWrapper); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/EffectWrapper/EffectWrapperData.cpp b/add/RPGPack/EffectWrapper/EffectWrapperData.cpp new file mode 100644 index 0000000..e77e2d4 --- /dev/null +++ b/add/RPGPack/EffectWrapper/EffectWrapperData.cpp @@ -0,0 +1,76 @@ +#include "EffectWrapperData.h" +#include "../RPGUtils.h" +#include "console/consoleTypes.h" + +IMPLEMENT_CONSOLETYPE(EffectWrapperData) +IMPLEMENT_GETDATATYPE(EffectWrapperData) +IMPLEMENT_SETDATATYPE(EffectWrapperData) +IMPLEMENT_CO_DATABLOCK_V1(EffectWrapperData); + +EffectWrapperData::EffectWrapperData(): +_effect(NULL), +_szConstraint(NULL), +_nLastTime(0), +_doConvert(false) +{ + +} + +void EffectWrapperData::packData( BitStream* stream) +{ + Parent::packData(stream); + RPGUtils::writeDatablockID(stream,_effect,packed); + stream->writeString(_szConstraint); + stream->write(_nLastTime); +} + +void EffectWrapperData::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); + _doConvert = true; + _effect = (GameBaseData*)RPGUtils::readDatablockID(stream); + _szConstraint = stream->readSTString(); + stream->read(&_nLastTime); +} + +#define myOffset(field) Offset(field, EffectWrapperData) +void EffectWrapperData::initPersistFields() +{ + Parent::initPersistFields(); + addField("lastingTime", TypeS32, myOffset(_nLastTime)); + addField("effect", TypeSimObjectPtr, myOffset(_effect)); + addField("constraint", TypeString, myOffset(_szConstraint)); +} + +const U32 & EffectWrapperData::getLastingTime() +{ + return _nLastTime; +} + +SimDataBlock * EffectWrapperData::getEffectWrapperData() +{ + return _effect; +} + +StringTableEntry EffectWrapperData::getConstraintString() +{ + return _szConstraint; +} + +bool EffectWrapperData::preload( bool server, String &errorStr ) +{ + if (!server) + { + if (_doConvert) + { + SimObjectId db_id; + db_id = (SimObjectId)_effect; + if (!Sim::findObject(db_id, _effect)) + { + Con::errorf("EffectWrapperData::preload error _effect = %d",db_id); + } + _doConvert = false; + } + } + return Parent::preload(server,errorStr); +} \ No newline at end of file diff --git a/add/RPGPack/EffectWrapper/EffectWrapperData.h b/add/RPGPack/EffectWrapper/EffectWrapperData.h new file mode 100644 index 0000000..b566d79 --- /dev/null +++ b/add/RPGPack/EffectWrapper/EffectWrapperData.h @@ -0,0 +1,27 @@ +#ifndef __EFFECTWRAPPERDATA_H__ +#define __EFFECTWRAPPERDATA_H__ + +#include "T3D/gameBase.h" + +class EffectWrapperData : public GameBaseData +{ + typedef GameBaseData Parent; + U32 _nLastTime; + SimDataBlock * _effect; + StringTableEntry _szConstraint; + bool _doConvert; +public: + EffectWrapperData(); + void packData(BitStream*); + void unpackData(BitStream*); + bool preload(bool server, String &errorStr); + static void initPersistFields(); +//=============rpg stuff========== + const U32 & getLastingTime(); + SimDataBlock * getEffectWrapperData(); + StringTableEntry getConstraintString(); +//============================= + DECLARE_CONOBJECT(EffectWrapperData); +}; +DECLARE_CONSOLETYPE(EffectWrapperData); +#endif \ No newline at end of file diff --git a/add/RPGPack/EffectWrapper/EffectWrapperDesc.cpp b/add/RPGPack/EffectWrapper/EffectWrapperDesc.cpp new file mode 100644 index 0000000..827364c --- /dev/null +++ b/add/RPGPack/EffectWrapper/EffectWrapperDesc.cpp @@ -0,0 +1,40 @@ +#include "EffectWrapperDesc.h" +#include "EffectWrapperData.h" +#include "EffectWrapper.h" +#include "../Constraint/Constraint.h" +#include "math/mMath.h" + +std::vector< EffectWrapperDesc * > EffectWrapperDesc::smEffectWrapperDescs; + +EffectWrapperDesc::EffectWrapperDesc() +{ + smEffectWrapperDescs.push_back(this); +} + +EffectWrapper * EffectWrapperDesc::getEffectWrapper( SimDataBlock * pData, const char * szConstraint ,bool isServer,const U32 & casterSimID,const U32 & targetSimID) +{ + EffectWrapper * pRet = NULL; + Constraint * pConstraint = NULL; + for (U32 i = 0 ; i < smEffectWrapperDescs.size() ; ++i) + { + if (smEffectWrapperDescs[i]->isMatch(pData)) + { + if ((isServer && smEffectWrapperDescs[i]->canRunOnServer()) || + (!isServer && smEffectWrapperDescs[i]->canRunOnClient())) + { + pRet = smEffectWrapperDescs[i]->createEffectWrapper(pData); + pConstraint = ConstraintDesc::getConstraint(szConstraint); + pConstraint->setCasterSimID(casterSimID); + pConstraint->setTargetSimID(targetSimID); + pConstraint->onCasterAndTargetSetted(); + pRet->ea_setConstraint(pConstraint); + } + } + } + return pRet; +} + +EffectWrapper * EffectWrapperDesc::getEffectWrapper( EffectWrapperData * pData,bool isServer ,const U32 & casterSimID,const U32 & targetSimID) +{ + return getEffectWrapper(pData->getEffectWrapperData(), pData->getConstraintString(), isServer,casterSimID,targetSimID); +} \ No newline at end of file diff --git a/add/RPGPack/EffectWrapper/EffectWrapperDesc.h b/add/RPGPack/EffectWrapper/EffectWrapperDesc.h new file mode 100644 index 0000000..bab25c6 --- /dev/null +++ b/add/RPGPack/EffectWrapper/EffectWrapperDesc.h @@ -0,0 +1,29 @@ +#ifndef __EFFECTWD_H__ +#define __EFFECTWD_H__ + +#include +#include "../RPGDefs.h" +#include "math/mMath.h" + +class EffectWrapper; +class SimDataBlock; +class EffectWrapperData; + +#define IMPLEMENT_EFXDESC(T) T g_##T + +class EffectWrapperDesc : public RPGDefs +{ +public: + EffectWrapperDesc(); + static std::vector< EffectWrapperDesc * > smEffectWrapperDescs; + static EffectWrapper * getEffectWrapper(SimDataBlock * pData,const char * szConstraint,bool isServer,const U32 & casterSimID,const U32 & targetSimID); + static EffectWrapper * getEffectWrapper(EffectWrapperData * pData,bool isServer,const U32 & casterSimID,const U32 & targetSimID); + + virtual bool canRunOnServer() = 0; + virtual bool canRunOnClient() = 0; + virtual bool isMatch( SimDataBlock * pData) = 0; + virtual EffectWrapper * createEffectWrapper(SimDataBlock * pData) = 0; + virtual EFFECTRUN getEffectRunsOn() = 0; +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Effects/EFX_AnimClip.cpp b/add/RPGPack/Effects/EFX_AnimClip.cpp new file mode 100644 index 0000000..afb95f9 --- /dev/null +++ b/add/RPGPack/Effects/EFX_AnimClip.cpp @@ -0,0 +1,111 @@ +#include "EFX_AnimClip.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "../Constraint/Constraint.h" + +IMPLEMENT_CO_DATABLOCK_V1(EFX_AnimClip_Data); + +EFX_AnimClip_Data::EFX_AnimClip_Data(): +_anim_name(NULL) +{ + +} + +void EFX_AnimClip_Data::packData( BitStream* stream) +{ + Parent::packData(stream); + stream->writeString(_anim_name); +} + +void EFX_AnimClip_Data::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); + _anim_name = stream->readSTString(); +} + +#define myOffset(field) Offset(field, EFX_AnimClip_Data) +void EFX_AnimClip_Data::initPersistFields() +{ + Parent::initPersistFields(); + addField("clipName", TypeString,myOffset(_anim_name)); +} + +bool EFX_AnimClip_Data::preload( bool server, String &errorStr ) +{ + return Parent::preload(server,errorStr); +} + +IMPLEMENT_CONOBJECT(EFX_AnimClip_Wrapper); + +EFX_AnimClip_Wrapper::EFX_AnimClip_Wrapper(): +mDataBlock(NULL) +{ + +} + +bool EFX_AnimClip_Wrapper::ea_SetData( SimDataBlock * pData , ERRORS & error ) +{ + mDataBlock = dynamic_cast(pData); + if (mDataBlock) + { + error = ERR_NONE; + return true; + } + error = ERR_DATANOTMATCH; + return false; +} + +bool EFX_AnimClip_Wrapper::ea_Start( ERRORS & error ) +{ + Constraint * pContraint = ea_getConstraint(); + if (pContraint) + { + pContraint->setAnimClip(mDataBlock->_anim_name); + } + error = ERR_NONE; + return true; +} + +void EFX_AnimClip_Wrapper::ea_Update( const U32 & dt ) +{ + +} + +void EFX_AnimClip_Wrapper::ea_End() +{ + +} + +bool EFX_AnimClip_Desc::canRunOnServer() +{ + return false; +} + +bool EFX_AnimClip_Desc::canRunOnClient() +{ + return true; +} + +bool EFX_AnimClip_Desc::isMatch( SimDataBlock * pData ) +{ + return dynamic_cast(pData) != NULL; +} + +EffectWrapper * EFX_AnimClip_Desc::createEffectWrapper( SimDataBlock * pData ) +{ + EFX_AnimClip_Wrapper * pEffectWrapper = new EFX_AnimClip_Wrapper; + ERRORS error = ERR_UNKOWN; + if (pEffectWrapper->ea_SetData(pData,error)) + { + return pEffectWrapper; + } + delete pEffectWrapper; + return NULL; +} + +RPGDefs::EFFECTRUN EFX_AnimClip_Desc::getEffectRunsOn() +{ + return RPGDefs::EFFECTRUN::ONCLIENT; +} + +IMPLEMENT_EFXDESC(EFX_AnimClip_Desc); \ No newline at end of file diff --git a/add/RPGPack/Effects/EFX_AnimClip.h b/add/RPGPack/Effects/EFX_AnimClip.h new file mode 100644 index 0000000..51aacf3 --- /dev/null +++ b/add/RPGPack/Effects/EFX_AnimClip.h @@ -0,0 +1,45 @@ +#ifndef __EFX_ANIMCLIP__ +#define __EFX_ANIMCLIP__ + +#include "T3D/gameBase.h" +#include "../EffectWrapper/EffectWrapper.h" +#include "../EffectWrapper/EffectWrapperDesc.h" + +class EFX_AnimClip_Data : public GameBaseData +{ + typedef GameBaseData Parent; +public: + StringTableEntry _anim_name; + EFX_AnimClip_Data(); + void packData(BitStream*); + void unpackData(BitStream*); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + DECLARE_CONOBJECT(EFX_AnimClip_Data); +}; + +class EFX_AnimClip_Wrapper : public EffectWrapper +{ + typedef EffectWrapper Parent; + EFX_AnimClip_Data * mDataBlock; +public: + EFX_AnimClip_Wrapper(); + virtual bool ea_SetData(SimDataBlock * pData , ERRORS & error); + virtual bool ea_Start(ERRORS & error); + virtual void ea_Update(const U32 & dt); + virtual void ea_End(); + DECLARE_CONOBJECT(EFX_AnimClip_Wrapper); +}; + +class EFX_AnimClip_Desc : public EffectWrapperDesc +{ + typedef EffectWrapperDesc Parent; +public: + virtual bool canRunOnServer(); + virtual bool canRunOnClient(); + virtual bool isMatch( SimDataBlock * pData); + virtual EffectWrapper * createEffectWrapper(SimDataBlock * pData); + virtual EFFECTRUN getEffectRunsOn(); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Effects/EFX_Decal.cpp b/add/RPGPack/Effects/EFX_Decal.cpp new file mode 100644 index 0000000..56673f7 --- /dev/null +++ b/add/RPGPack/Effects/EFX_Decal.cpp @@ -0,0 +1,131 @@ +#include "EFX_Decal.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "../Constraint/Constraint.h" +#include "T3D/decal/decalManager.h" + +IMPLEMENT_CO_DATABLOCK_V1(EFX_Decal_Data); + +EFX_Decal_Data::EFX_Decal_Data(): +mSpinSpeed(0) +{ + +} + +void EFX_Decal_Data::packData( BitStream* stream) +{ + Parent::packData(stream); + stream->write(mSpinSpeed); +} + +void EFX_Decal_Data::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&mSpinSpeed); +} + +#define myOffset(field) Offset(field, EFX_Decal_Data) +void EFX_Decal_Data::initPersistFields() +{ + Parent::initPersistFields(); + addField("spinSpeed", TypeF32, myOffset(mSpinSpeed)); +} + +bool EFX_Decal_Data::preload( bool server, String &errorStr ) +{ + return Parent::preload(server,errorStr); +} + +IMPLEMENT_CONOBJECT(EFX_Decal_Wrapper); + +EFX_Decal_Wrapper::EFX_Decal_Wrapper(): +mDataBlock(NULL), +mDecalInstance(NULL) +{ + +} + +bool EFX_Decal_Wrapper::ea_SetData( SimDataBlock * pData , ERRORS & error ) +{ + mDataBlock = dynamic_cast(pData); + if (mDataBlock) + { + error = ERR_NONE; + return true; + } + error = ERR_DATANOTMATCH; + return false; +} + +bool EFX_Decal_Wrapper::ea_Start( ERRORS & error ) +{ + Constraint * pContraint = ea_getConstraint(); + if (pContraint) + { + mDecalInstance = pContraint->addGroundDecal(mDataBlock); + } + error = ERR_NONE; + return true; +} + +void EFX_Decal_Wrapper::ea_Update( const U32 & dt ) +{ + Constraint * pContraint = ea_getConstraint(); + if (mDecalInstance && pContraint) + { + F32 angle = mDataBlock->mSpinSpeed * dt / 1000; + Point3F tangent; + MatrixF matOut; + mDecalInstance->getWorldMatrix(&matOut); + Point3F position = mDecalInstance->getPosition(); + MatrixF rot(EulerF(0,0,angle)); + matOut.mul(rot); + matOut.getColumn(0,&tangent); + tangent.normalizeSafe(); + mDecalInstance->setTangent(tangent); + mDecalInstance->setPosition(pContraint->getConstraintPos()); + } +} + +void EFX_Decal_Wrapper::ea_End() +{ + Constraint * pContraint = ea_getConstraint(); + if (pContraint && mDecalInstance) + { + gDecalManager->removeDecal(mDecalInstance); + } +} + +bool EFX_Decal_Desc::canRunOnServer() +{ + return false; +} + +bool EFX_Decal_Desc::canRunOnClient() +{ + return true; +} + +bool EFX_Decal_Desc::isMatch( SimDataBlock * pData ) +{ + return dynamic_cast(pData) != NULL; +} + +EffectWrapper * EFX_Decal_Desc::createEffectWrapper( SimDataBlock * pData ) +{ + EFX_Decal_Wrapper * pEffectWrapper = new EFX_Decal_Wrapper; + ERRORS error = ERR_UNKOWN; + if (pEffectWrapper->ea_SetData(pData,error)) + { + return pEffectWrapper; + } + delete pEffectWrapper; + return NULL; +} + +RPGDefs::EFFECTRUN EFX_Decal_Desc::getEffectRunsOn() +{ + return RPGDefs::EFFECTRUN::ONCLIENT; +} + +IMPLEMENT_EFXDESC(EFX_Decal_Desc); \ No newline at end of file diff --git a/add/RPGPack/Effects/EFX_Decal.h b/add/RPGPack/Effects/EFX_Decal.h new file mode 100644 index 0000000..44a2f6b --- /dev/null +++ b/add/RPGPack/Effects/EFX_Decal.h @@ -0,0 +1,49 @@ +#ifndef __EFX_DECAL__ +#define __EFX_DECAL__ + +#include "T3D/gameBase.h" +#include "../EffectWrapper/EffectWrapper.h" +#include "../EffectWrapper/EffectWrapperDesc.h" +#include "T3D/decal/decalData.h" + +class DecalInstance; + +class EFX_Decal_Data : public DecalData +{ + typedef DecalData Parent; +public: + F32 mSpinSpeed; //ÐýתËٶȣ¬»¡¶ÈÿÃë + EFX_Decal_Data(); + void packData(BitStream*); + void unpackData(BitStream*); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + DECLARE_CONOBJECT(EFX_Decal_Data); +}; + +class EFX_Decal_Wrapper : public EffectWrapper +{ + typedef EffectWrapper Parent; + EFX_Decal_Data * mDataBlock; + DecalInstance * mDecalInstance; +public: + EFX_Decal_Wrapper(); + virtual bool ea_SetData(SimDataBlock * pData , ERRORS & error); + virtual bool ea_Start(ERRORS & error); + virtual void ea_Update(const U32 & dt); + virtual void ea_End(); + DECLARE_CONOBJECT(EFX_Decal_Wrapper); +}; + +class EFX_Decal_Desc : public EffectWrapperDesc +{ + typedef EffectWrapperDesc Parent; +public: + virtual bool canRunOnServer(); + virtual bool canRunOnClient(); + virtual bool isMatch( SimDataBlock * pData); + virtual EffectWrapper * createEffectWrapper(SimDataBlock * pData); + virtual EFFECTRUN getEffectRunsOn(); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/Effects/EFX_Particle.cpp b/add/RPGPack/Effects/EFX_Particle.cpp new file mode 100644 index 0000000..f0c402b --- /dev/null +++ b/add/RPGPack/Effects/EFX_Particle.cpp @@ -0,0 +1,133 @@ +#include "EFX_Particle.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "../Constraint/Constraint.h" +#include "../RPGUtils.h" + +IMPLEMENT_CO_DATABLOCK_V1(EFX_Particle_Data); + +EFX_Particle_Data::EFX_Particle_Data() +{ + +} + +void EFX_Particle_Data::packData( BitStream* stream) +{ + Parent::packData(stream); +} + +void EFX_Particle_Data::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); +} + +#define myOffset(field) Offset(field, EFX_Particle_Data) +void EFX_Particle_Data::initPersistFields() +{ + Parent::initPersistFields(); +} + +bool EFX_Particle_Data::preload( bool server, String &errorStr ) +{ + return Parent::preload(server,errorStr); +} + +IMPLEMENT_CONOBJECT(EFX_Particle_Wrapper); + +EFX_Particle_Wrapper::EFX_Particle_Wrapper(): +mDataBlock(NULL), +mEmitter(NULL) +{ + +} + +bool EFX_Particle_Wrapper::ea_SetData( SimDataBlock * pData , ERRORS & error ) +{ + mDataBlock = dynamic_cast(pData); + if (mDataBlock) + { + error = ERR_NONE; + return true; + } + error = ERR_DATANOTMATCH; + return false; +} + +bool EFX_Particle_Wrapper::ea_Start( ERRORS & error ) +{ + Constraint * pContraint = ea_getConstraint(); + if (pContraint) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + if( pEmitter->setDataBlock(mDataBlock) && pEmitter->registerObject()) + { + mEmitter = pEmitter; + error = ERR_NONE; + return true; + } + delete pEmitter; + } + error = ERR_UNKOWN; + return false; +} + +void EFX_Particle_Wrapper::ea_Update( const U32 & dt ) +{ + Constraint * pContraint = ea_getConstraint(); + if (pContraint && !mEmitter.isNull()) + { + Point3F emitPoint, emitVelocity; + Point3F emitAxis(0, 0, 1); + MatrixF mat = pContraint->getConstraintTransform(); + mat.mulV(emitAxis); + mat.getColumn(3, &emitPoint); + emitVelocity = emitAxis; + + mEmitter->emitParticles(emitPoint, emitPoint, + emitAxis, + emitVelocity, dt); + } +} + +void EFX_Particle_Wrapper::ea_End() +{ + if( mEmitter ) + { + mEmitter->safeDeleteObject(); + mEmitter = NULL; + } +} + +bool EFX_Particle_Desc::canRunOnServer() +{ + return false; +} + +bool EFX_Particle_Desc::canRunOnClient() +{ + return true; +} + +bool EFX_Particle_Desc::isMatch( SimDataBlock * pData ) +{ + return dynamic_cast(pData) != NULL; +} + +EffectWrapper * EFX_Particle_Desc::createEffectWrapper( SimDataBlock * pData ) +{ + EFX_Particle_Wrapper * pEffectWrapper = new EFX_Particle_Wrapper; + ERRORS error = ERR_UNKOWN; + if (pEffectWrapper->ea_SetData(pData,error)) + { + return pEffectWrapper; + } + delete pEffectWrapper; + return NULL; +} + +RPGDefs::EFFECTRUN EFX_Particle_Desc::getEffectRunsOn() +{ + return RPGDefs::EFFECTRUN::ONCLIENT; +} + +IMPLEMENT_EFXDESC(EFX_Particle_Desc); \ No newline at end of file diff --git a/add/RPGPack/Effects/EFX_Particle.h b/add/RPGPack/Effects/EFX_Particle.h new file mode 100644 index 0000000..e492a6c --- /dev/null +++ b/add/RPGPack/Effects/EFX_Particle.h @@ -0,0 +1,47 @@ +#ifndef __EFX_PARTICLE__ +#define __EFX_PARTICLE__ + +#include "T3D/gameBase.h" +#include "../EffectWrapper/EffectWrapper.h" +#include "../EffectWrapper/EffectWrapperDesc.h" +#include "T3D/fx/particleEmitter.h" + + +class EFX_Particle_Data : public ParticleEmitterData +{ + typedef ParticleEmitterData Parent; +public: + EFX_Particle_Data(); + void packData(BitStream*); + void unpackData(BitStream*); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + DECLARE_CONOBJECT(EFX_Particle_Data); +}; + +class EFX_Particle_Wrapper : public EffectWrapper +{ + typedef EffectWrapper Parent; + EFX_Particle_Data * mDataBlock; + SimObjectPtr mEmitter; +public: + EFX_Particle_Wrapper(); + virtual bool ea_SetData(SimDataBlock * pData , ERRORS & error); + virtual bool ea_Start(ERRORS & error); + virtual void ea_Update(const U32 & dt); + virtual void ea_End(); + DECLARE_CONOBJECT(EFX_Particle_Wrapper); +}; + +class EFX_Particle_Desc : public EffectWrapperDesc +{ + typedef EffectWrapperDesc Parent; +public: + virtual bool canRunOnServer(); + virtual bool canRunOnClient(); + virtual bool isMatch( SimDataBlock * pData); + virtual EffectWrapper * createEffectWrapper(SimDataBlock * pData); + virtual EFFECTRUN getEffectRunsOn(); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGBase.cpp b/add/RPGPack/RPGBase.cpp new file mode 100644 index 0000000..b543567 --- /dev/null +++ b/add/RPGPack/RPGBase.cpp @@ -0,0 +1,152 @@ +#include "RPGBase.h" +#include "RPGBaseData.h" +#include "core/stream/bitStream.h" +#include "T3D/gameConnection.h" + +IMPLEMENT_CO_NETOBJECT_V1(RPGBase); + +RPGBase::RPGBase() +{ + mDataBlock = NULL; + mCasterSimID = mTargetSimID = 0; +} + +RPGBase::~RPGBase() +{ + +} + +bool RPGBase::onNewDataBlock( GameBaseData* dptr ) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + return true; +} + +void RPGBase::processTick( const Move* m ) +{ + Parent::processTick(m); +} + +void RPGBase::advanceTime( F32 dt ) +{ + Parent::advanceTime(dt); +} + +bool RPGBase::onAdd() +{ + if (!Parent::onAdd()) + return(false); + + addToScene(); + + GameBase * pCaster = dynamic_cast( Sim::findObject(mCasterSimID) ); + if (pCaster) + { + pCaster->pushRPGBase(this); + } + + return(true); +} + +void RPGBase::onRemove() +{ + GameBase * pCaster = dynamic_cast( Sim::findObject(mCasterSimID) ); + if (pCaster) + { + pCaster->removeRPGBase(this); + } + removeFromScene(); + Parent::onRemove(); +} + +U32 RPGBase::packUpdate( NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + if (stream->writeFlag(mask & InitialUpdateMask)) + { + NetObject* pObject; + if (stream->writeFlag(Sim::findObject( getCasterSimID(), pObject))) + { + stream->write(con->getGhostIndex(pObject)); + } + if (stream->writeFlag(Sim::findObject( getTargetSimID(), pObject))) + { + stream->write(con->getGhostIndex(pObject)); + } + } + return(retMask); +} + +void RPGBase::unpackUpdate( NetConnection* con, BitStream* stream ) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + S32 nIndex; + if (stream->readFlag()) + { + stream->read(&nIndex); + NetObject* pObject = NULL; + if( con->isGhostingTo()) + pObject = con->resolveGhost(nIndex); + else if( con->isGhostingFrom()) + pObject = con->resolveObjectFromGhostIndex(nIndex); + if (pObject) + { + setCasterSimID(pObject->getId()); + } + } + if (stream->readFlag()) + { + stream->read(&nIndex); + NetObject* pObject = NULL; + if( con->isGhostingTo()) + pObject = con->resolveGhost(nIndex); + else if( con->isGhostingFrom()) + pObject = con->resolveObjectFromGhostIndex(nIndex); + if (pObject) + { + setCasterSimID(pObject->getId()); + } + } + } +} + +void RPGBase::initPersistFields() +{ + Parent::initPersistFields(); +} + +const U32 & RPGBase::getCasterSimID() +{ + return mCasterSimID; +} + +const U32 & RPGBase::getTargetSimID() +{ + return mTargetSimID; +} + +void RPGBase::setCasterSimID( const U32 & id ) +{ + mCasterSimID = id; +} + +void RPGBase::setTargetSimID( const U32 & id ) +{ + mTargetSimID = id; +} + +void RPGBase::onInterrupt() +{ + +} + +void RPGBase::interpolateTick( F32 delta ) +{ + Parent::interpolateTick(delta); +} \ No newline at end of file diff --git a/add/RPGPack/RPGBase.h b/add/RPGPack/RPGBase.h new file mode 100644 index 0000000..cfe344e --- /dev/null +++ b/add/RPGPack/RPGBase.h @@ -0,0 +1,47 @@ +#ifndef __RPGBASE_H__ +#define __RPGBASE_H__ + +#include "T3D/gameBase.h" + +class RPGBaseData; + +class RPGBase : public GameBase +{ + typedef GameBase Parent; +public: + enum MaskBits + { + NextFreeMask = Parent::NextFreeMask << 0, + }; +protected: + RPGBaseData* mDataBlock; + U32 mCasterSimID; + U32 mTargetSimID; +public: + RPGBase(); + ~RPGBase(); + bool onNewDataBlock(GameBaseData* dptr); + void interpolateTick(F32 delta); + void processTick(const Move*); + void advanceTime(F32 dt); + + bool onAdd(); + void onRemove(); + + U32 packUpdate(NetConnection*, U32, BitStream*); + void unpackUpdate(NetConnection*, BitStream*); + + static void initPersistFields(); + + //===========rpg stuff============ + const U32 & getCasterSimID(); + const U32 & getTargetSimID(); + void setCasterSimID(const U32 & id); + void setTargetSimID(const U32 & id); + + virtual void onInterrupt(); + //============================ + DECLARE_CONOBJECT(RPGBase); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGBaseData.cpp b/add/RPGPack/RPGBaseData.cpp new file mode 100644 index 0000000..22d16c2 --- /dev/null +++ b/add/RPGPack/RPGBaseData.cpp @@ -0,0 +1,70 @@ +#include "RPGBaseData.h" +#include "T3D/gameConnection.h" +#include "RPGBook.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +IMPLEMENT_CONSOLETYPE(RPGBaseData) +IMPLEMENT_GETDATATYPE(RPGBaseData) +IMPLEMENT_SETDATATYPE(RPGBaseData) +IMPLEMENT_CO_DATABLOCK_V1(RPGBaseData); + +RPGBaseData::RPGBaseData(): +_icon_name(NULL), +_nDescIdx(-1), +_nRPGType(RPGTYPE_ALL) +{ + +} + +void RPGBaseData::packData( BitStream* stream) +{ + Parent::packData(stream); + stream->write(_nDescIdx); + stream->write(_nRPGType); + stream->writeString(_icon_name); +} + +void RPGBaseData::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&_nDescIdx); + stream->read(&_nRPGType); + _icon_name = stream->readSTString(); +} + +#define myOffset(field) Offset(field, RPGBaseData) +void RPGBaseData::initPersistFields() +{ + Parent::initPersistFields(); + addField("iconName", TypeString, myOffset(_icon_name)); + addField("rpgType", TypeS8, myOffset(_nRPGType)); + addField("descStringIdx", TypeS32, myOffset(_nDescIdx)); +} + +U32 RPGBaseData::onActivate( ERRORS & error , const U32 & casterSimID, const U32 & targetSimID) +{ + error = ERR_NONE; + return 0; +} + +bool RPGBaseData::onDeActivate( ERRORS & error , const U32 & casterSimID, const U32 & targetSimID) +{ + error = ERR_NONE; + return true; +} + +void RPGBaseData::onItemMoved( RPGBook * pSrcBook, S32 srcIdx, RPGBook * pDestBook, S32 destIdx ) +{ + +} + +const U8 & RPGBaseData::getRPGDataType() +{ + return _nRPGType; +} + +StringTableEntry RPGBaseData::getIconName() +{ + return _icon_name; +} \ No newline at end of file diff --git a/add/RPGPack/RPGBaseData.h b/add/RPGPack/RPGBaseData.h new file mode 100644 index 0000000..cb91ee1 --- /dev/null +++ b/add/RPGPack/RPGBaseData.h @@ -0,0 +1,36 @@ +#ifndef __RPGBASEDATA_H__ +#define __RPGBASEDATA_H__ + +#include "T3D/gameBase.h" +#include "RPGDefs.h" + +class GameConnection; +class RPGBook; + +class RPGBaseData : public GameBaseData , public RPGDefs +{ + typedef GameBaseData Parent; + S32 _nDescIdx; + S8 _nRPGType; + StringTableEntry _icon_name; +public: + RPGBaseData(); + void packData(BitStream*); + void unpackData(BitStream*); + static void initPersistFields(); + //icon name + StringTableEntry getIconName(); + //»ñµÃÕâ¸örpgdataµÄÀàÐÍ + const U8 & getRPGDataType(); + //µ±Ò»¸öitem±»¼¤»î£¬Èç¹ûÄܹ»¼¤»î£¬error·µ»Ø0 + //U32·µ»ØÀäȴʱ¼ä£¬0±íʾÎÞÀäȴʱ¼ä£¬µ¥Î»MS£¬-1±íʾÓÀÔ¶Ö´ÐÐ + virtual U32 onActivate(ERRORS & error , const U32 & casterSimID = 0, const U32 & targetSimID = 0); + //µ±Ò»¸öitem±»¼¤»îºó£¬ÓÖ±»·´¼¤»î£¬Èç¹ûÄܹ»·´¼¤»î£¬Ôò·µ»Øtrue£¬·ñÔò·µ»Øfalse + virtual bool onDeActivate(ERRORS & error , const U32 & casterSimID = 0, const U32 & targetSimID = 0); + //µ±Õâ¸öitem±»´ÓÒ»¸öµØ·½Òƶ¯µ½ÁíÒ»¸öµØ·½Ê±£¬»á±»µ÷Óà + //ÒѾ­´ÓsrcBookÒƶ¯µ½ÁËdestBook + virtual void onItemMoved(RPGBook * pSrcBook, S32 srcIdx, RPGBook * pDestBook, S32 destIdx); + DECLARE_CONOBJECT(RPGBaseData); +}; +DECLARE_CONSOLETYPE(RPGBaseData); +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGBook.cpp b/add/RPGPack/RPGBook.cpp new file mode 100644 index 0000000..8f34342 --- /dev/null +++ b/add/RPGPack/RPGBook.cpp @@ -0,0 +1,340 @@ +#include "RPGBook.h" +#include "RPGBookData.h" +#include "RPGBaseData.h" +#include "core/stream/bitStream.h" +#include "../gui/GuiCellArray.h" +#include "console/consoleTypes.h" + +IMPLEMENT_CO_NETOBJECT_V1(RPGBook); + +RPGBook::RPGBook() : mDataBlock(NULL) +{ + //²¢²»Êǹ㲥¸øÿ¸öÈË + mNetFlags.clear(Ghostable); + _nRPGType = RPGTYPE_ALL; + _nBookType = BOOK_PACK; + clearBook(); +} + +RPGBook::~RPGBook() +{ + +} + +bool RPGBook::onNewDataBlock( GameBaseData* dptr ) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + return true; +} + +void RPGBook::processTick( const Move* m) +{ + Parent::processTick(m); + for ( S32 i = 0 ; i < BOOK_MAX ; ++i) + { + if (_mBookDataFreezeTime[i] > 0) + { + _mBookDataFreezeTime[i] = _mBookDataFreezeTime[i] > TickMs ? _mBookDataFreezeTime[i] - TickMs : 0; + } + if (_mBookDataIdx[i] >= 0) + { + _mBookDataStatu[i] = _mBookDataFreezeTime[i] == 0 ? STATU_NORMAL : _mBookDataStatu[i]; + } + } +} + +void RPGBook::advanceTime( F32 dt ) +{ + Parent::advanceTime(dt); +} + +bool RPGBook::onAdd() +{ + if (!Parent::onAdd()) + return(false); + + //if there is a gui control which math this book + //then attach to it + if (isClientObject()) + { + guiCellArray * pBookGui = guiCellArray::getBookGUI(_nBookType); + if ( pBookGui ) + pBookGui->setBook(this); + } + + return(true); +} + +void RPGBook::onRemove() +{ + Parent::onRemove(); +} + +U32 RPGBook::packUpdate( NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & InitialUpdateMask)) + { + stream->write(_nRPGType); + stream->write(_nBookType); + for (S32 i = 0 ; i < BOOK_MAX ; ++i) + { + stream->write(_mBookDataIdx[i]); + } + } + + if (stream->writeFlag( (mask & BookChangedMask) && !(mask & InitialUpdateMask))) + { + for (U8 n = 0 ; n < _vecChangedIdxs.size() ; ++n) + { + stream->writeFlag(true); + stream->write(_vecChangedIdxs[n]); + stream->write(_mBookDataIdx[_vecChangedIdxs[n]]); + } + stream->writeFlag(false); + } + + if (stream->writeFlag( (mask & BookCoolingMask) && !(mask & InitialUpdateMask))) + { + for (U8 i = 0 ; i < BOOK_MAX ; ++i) + { + if ( _mBookDataFreezeTime[i] !=0 ) + { + stream->writeFlag(true); + stream->write(i); + stream->write(_mBookDataFreezeTime[i]); + } + } + stream->writeFlag(false); + } + + if (mask & BookChangedMask) + _vecChangedIdxs.clear(); + + return(retMask); +} + +void RPGBook::unpackUpdate( NetConnection* con, BitStream* stream ) +{ + Parent::unpackUpdate(con, stream); + + if (stream->readFlag()) + { + stream->read(&_nRPGType); + stream->read(&_nBookType); + S32 item; + for (S32 i = 0 ; i < BOOK_MAX ; ++i) + { + stream->read(&item); + insertItem(i,item); + } + } + + if (stream->readFlag()) + { + U8 idx; + S32 item; + while(stream->readFlag()) + { + stream->read(&idx); + stream->read(&item); + insertItem(idx,item); + } + guiCellArray * pBookGui = guiCellArray::getBookGUI(_nBookType); + if ( pBookGui ) + pBookGui->refreshItems(); + } + + if (stream->readFlag()) + { + U8 idx = 0; + while(stream->readFlag()) + { + stream->read(&idx); + stream->read(&_mBookDataFreezeTime[idx]); + _mClientFreezeTimeTotal[idx] = _mBookDataFreezeTime[idx]; + } + } +} + +#define myOffset(field) Offset(field, RPGBook) +void RPGBook::initPersistFields() +{ + Parent::initPersistFields(); + addField("bookType", TypeS8, myOffset(_nBookType)); + addField("rpgType", TypeS8, myOffset(_nRPGType)); + addField("slots",TypeS32,myOffset(_mBookDataIdx),BOOK_MAX); +} + +void RPGBook::clearBook() +{ + for ( S32 i = 0 ; i < BOOK_MAX ; ++i) + clearItem(i); +} + +void RPGBook::clearItem(const U8 & idx ) +{ + if (_mBookDataIdx[idx] == -1) + return; + _mBookDataIdx[idx] = -1; + _mBookDataFreezeTime[idx] = 0; + _mClientFreezeTimeTotal[idx] = 0; + _mBookDataStatu[idx] = STATU_INVALID; + if (isServerObject()) + { + _vecChangedIdxs.push_back(idx); + setMaskBits(BookChangedMask); + } +} + +void RPGBook::insertItem(const U8 & idx , const S32 & item ) +{ + _mBookDataIdx[idx] = item; + _mBookDataFreezeTime[idx] = 0; + _mClientFreezeTimeTotal[idx] = 0; + _mBookDataStatu[idx] = STATU_NORMAL; + if (isServerObject()) + { + _vecChangedIdxs.push_back(idx); + setMaskBits(BookChangedMask); + } +} + +bool RPGBook::swapItem( const U8 & idx , RPGBook * pBook2 ,const U8 & idx2 ) +{ + RPGBaseData * RpgBaseData1 = getRpgBaseData(idx); + RPGBaseData * RpgBaseData2 = pBook2->getRpgBaseData(idx2); + const U8 & bookType1 = getBookRpgDataType(); + const U8 & bookType2 = pBook2->getBookRpgDataType(); + const U8 & itemType1 = RpgBaseData1 ? RpgBaseData1->getRPGDataType() : RPGTYPE_ALL; + const U8 & itemType2 = RpgBaseData2 ? RpgBaseData2->getRPGDataType() : RPGTYPE_ALL; + if ( (bookType1 & itemType2) && (bookType2 & itemType1) ) + { + S32 item1 = getItem(idx); + S32 item2 = pBook2->getItem(idx2); + clearItem(idx); + pBook2->clearItem(idx2); + insertItem(idx,item2); + pBook2->insertItem(idx2,item1); + if (RpgBaseData1) + { + RpgBaseData1->onItemMoved(this,idx,pBook2,idx2); + } + if (RpgBaseData2) + { + RpgBaseData2->onItemMoved(pBook2,idx2,this,idx); + } + return true; + } + return false; +} + +const S32 & RPGBook::getItem( const U8 & idx ) +{ + return _mBookDataIdx[idx]; +} + +RPGBaseData * RPGBook::getRpgBaseData( const U8 & idx ) +{ + return mDataBlock->getRpgBaseData(_mBookDataIdx[idx]); +} + +const U8 & RPGBook::getBookRpgDataType() +{ + return _nBookType; +} + +bool RPGBook::useBook( const U8 & idx , ERRORS & error, const U32 & casterSimID, const U32 & targetSimID) +{ + RPGBaseData * pRPGBaseData = getRpgBaseData(idx); + bool ret = false; + error = ERR_UNKOWN; + if (pRPGBaseData) + { + switch(_mBookDataStatu[idx]) + { + case STATU_NORMAL: + { + U32 coolDownTime = pRPGBaseData->onActivate(error ,casterSimID,targetSimID); + if (error == ERR_NONE) + { + _mBookDataFreezeTime[idx] = coolDownTime; + _mBookDataStatu[idx] = coolDownTime == 0 ? STATU_NORMAL : STATU_COOLDOWN ; + ret = true; + } + else + ret = false; + break; + } + case STATU_COOLDOWN: + { + if (_mBookDataFreezeTime[idx] == -1) + { + if (pRPGBaseData->onDeActivate(error,casterSimID,targetSimID)) + { + _mBookDataFreezeTime[idx] = 0; + _mBookDataStatu[idx] = STATU_NORMAL; + ret = true; + } + else + ret = false; + } + break; + } + } + } + + if (ret) + { + setMaskBits(BookCoolingMask); + } + + return ret; +} + +bool RPGBook::isItemEmpty( const U8 & idx ) +{ + return getItem(idx) == -1; +} + +F32 RPGBook::getRatioOfCDTime( const U8 & idx ) +{ + if (_mClientFreezeTimeTotal[idx] > 0) + { + return 1.f - ((F32)_mBookDataFreezeTime[idx] / (F32)_mClientFreezeTimeTotal[idx]); + } + else if (_mClientFreezeTimeTotal[idx] == -1) + { + return -1.f; + } + else + return 0; +} + +ConsoleMethod(RPGBook,getRpgBaseData,S32,3,3,"%idx") +{ + RPGBaseData * pRPGBaseData = object->getRpgBaseData(dAtoi(argv[2])); + return pRPGBaseData ? pRPGBaseData->getId() : 0; +} + +ConsoleMethod(RPGBook,swapItem,void,5,5,"%idx,%destBook,%destIdx") +{ + RPGBook * pSelf = (RPGBook *)object; + RPGBook * pOther = (RPGBook *)Sim::findObject(argv[3]); + if (pSelf && pOther) + { + pSelf->swapItem(dAtoi(argv[2]),pOther,dAtoi(argv[4])); + } +} + +ConsoleMethod(RPGBook,useBook,S32,5,5,"%idx,%casterSimID,%targetSimID") +{ + RPGBook * pBook = (RPGBook *)object; + RPGDefs::ERRORS error; + pBook->useBook(dAtoi(argv[2]),error,dAtoi(argv[3]),dAtoi(argv[4])); + return error; +} \ No newline at end of file diff --git a/add/RPGPack/RPGBook.h b/add/RPGPack/RPGBook.h new file mode 100644 index 0000000..91c8db5 --- /dev/null +++ b/add/RPGPack/RPGBook.h @@ -0,0 +1,66 @@ +#ifndef __RPGBOOK_H__ +#define __RPGBOOK_H__ + +#include "T3D/gameBase.h" +#include "RPGDefs.h" +#include + +class RPGBookData; +class RPGBaseData; + +class RPGBook : public GameBase , public RPGDefs +{ + typedef GameBase Parent; + enum MaskBits + { + BookChangedMask = Parent::NextFreeMask << 0, + BookCoolingMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2, + }; + S32 _mBookDataIdx[BOOK_MAX]; + U32 _mBookDataFreezeTime[BOOK_MAX]; + //client only , used for cd fill + U32 _mClientFreezeTimeTotal[BOOK_MAX]; + U8 _mBookDataStatu[BOOK_MAX]; + U8 _nRPGType; + U8 _nBookType; + std::vector _vecChangedIdxs; +public: + RPGBook(); + ~RPGBook(); + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move*); + void advanceTime(F32 dt); + + bool onAdd(); + void onRemove(); + + U32 packUpdate(NetConnection*, U32, BitStream*); + void unpackUpdate(NetConnection*, BitStream*); + + static void initPersistFields(); + + //============PRG Stuffs================ + //server only + void clearBook(); + //Èç¹ûÊǸöÎÞ¾¡µÄʱ¼ä£¬Ôò±»onDeActivate + bool useBook(const U8 & idx , ERRORS & error ,const U32 & casterSimID = 0, const U32 & targetSimID = 0); + const U8 & getBookRpgDataType(); + + void clearItem(const U8 & idx); + void insertItem(const U8 & idx , const S32 & item); + const S32 & getItem(const U8 & idx); + bool isItemEmpty(const U8 & idx); + + bool swapItem(const U8 & idx , RPGBook * pBook2 , const U8 & idx2); + + RPGBaseData * getRpgBaseData(const U8 & idx); + F32 getRatioOfCDTime(const U8 & idx); + //=================================== +protected: + RPGBookData * mDataBlock; +public: + DECLARE_CONOBJECT(RPGBook); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGBookData.cpp b/add/RPGPack/RPGBookData.cpp new file mode 100644 index 0000000..f0a1c4c --- /dev/null +++ b/add/RPGPack/RPGBookData.cpp @@ -0,0 +1,105 @@ +#include "RPGBookData.h" +#include "RPGBaseData.h" +#include "RPGUtils.h" +#include "console/consoleTypes.h" + +IMPLEMENT_CONSOLETYPE(RPGBookData) +IMPLEMENT_GETDATATYPE(RPGBookData) +IMPLEMENT_SETDATATYPE(RPGBookData) +IMPLEMENT_CO_DATABLOCK_V1(RPGBookData); + +RPGBookData::RPGBookData() +{ + _mbDoIDConvert = false; + _nRPGBookDataIdx = -1; + for (S32 i = 0 ; i < BOOK_MAX ; ++i) + { + _mRpgDatas[i] = NULL; + } +} + +void RPGBookData::packData( BitStream* stream) +{ + Parent::packData(stream); + stream->write(_nRPGBookDataIdx); + for (S32 i = 0 ; i < BOOK_MAX ; ++i) + { + RPGUtils::writeDatablockID(stream,_mRpgDatas[i],packed); + } +} + +void RPGBookData::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&_nRPGBookDataIdx); + _mbDoIDConvert = true; + for (S32 i = 0 ; i < BOOK_MAX ; ++i) + { + _mRpgDatas[i] = (RPGBaseData*)RPGUtils::readDatablockID(stream); + } +} + +#define myOffset(field) Offset(field, RPGBookData) +void RPGBookData::initPersistFields() +{ + Parent::initPersistFields(); + addField("rpgBookDataIdx", TypeS32, myOffset(_nRPGBookDataIdx)); + addField("rpgDatas",TypeGameBaseDataPtr,myOffset(_mRpgDatas),BOOK_MAX); +} + +RPGBaseData * RPGBookData::getRpgBaseData( const S32 & idx ) +{ + if (idx == -1) + { + return NULL; + } + S32 dataBlockIdx = idx / BOOK_MAX; + U8 offsetIdx = idx % BOOK_MAX; + char szBookData[64]; + dSprintf(szBookData,64,"gRPGBookData%d",dataBlockIdx); + RPGBookData * pBookData = dynamic_cast (Sim::findObject(szBookData)); + return pBookData ? pBookData->_getRpgBaseData(offsetIdx) : NULL; +} + +bool RPGBookData::preload( bool server, String &errorStr ) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (_mbDoIDConvert) + { + SimObjectId db_id; + for (S32 i = 0 ; i < BOOK_MAX ; ++i) + { + db_id = (SimObjectId)_mRpgDatas[i]; + if (!Sim::findObject(db_id, _mRpgDatas[i])) + { + //Con::errorf("RPGBookData::preload error _mRpgDatas[%d] = %d",i,db_id); + } + } + _mbDoIDConvert = false; + } + + //change the name + if (_nRPGBookDataIdx == -1) + { + assignName("gRPGBookData"); + } + else + { + char szName[64]; + dSprintf(szName,64,"gRPGBookData%d",_nRPGBookDataIdx); + assignName(szName); + } + } + + return true; +} + +RPGBaseData * RPGBookData::_getRpgBaseData( const U8 & idx ) +{ + return _mRpgDatas[idx]; +} \ No newline at end of file diff --git a/add/RPGPack/RPGBookData.h b/add/RPGPack/RPGBookData.h new file mode 100644 index 0000000..2d1f37b --- /dev/null +++ b/add/RPGPack/RPGBookData.h @@ -0,0 +1,34 @@ +#ifndef __RPGBOOKDATA_H__ +#define __RPGBOOKDATA_H__ + +#include "T3D/gameBase.h" +#include "RPGDefs.h" + +class RPGBaseData; +/* +gRPGBookData ×÷Ϊÿ¸öbookµÄdatablock +¶øgRPGBookDataÓÖ¹ÜÀígRPGBookData0~gRPGBookData10£¬ÉõÖÁ +¸ü¶àµÄdatablock£¬ÒòΪÕâÑù¸ü·½±ã´«Êä +*/ +class RPGBookData : public GameBaseData , public RPGDefs +{ + typedef GameBaseData Parent; + RPGBaseData * _mRpgDatas[BOOK_MAX]; + bool _mbDoIDConvert; + S32 _nRPGBookDataIdx; +public: + RPGBookData(); + void packData(BitStream*); + void unpackData(BitStream*); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + //===============RPG Stuff================ +protected: + RPGBaseData * _getRpgBaseData(const U8 & idx); +public: + RPGBaseData * getRpgBaseData(const S32 & idx); + //====================================== + DECLARE_CONOBJECT(RPGBookData); +}; +DECLARE_CONSOLETYPE(RPGBookData); +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGDefs.h b/add/RPGPack/RPGDefs.h new file mode 100644 index 0000000..e700bb1 --- /dev/null +++ b/add/RPGPack/RPGDefs.h @@ -0,0 +1,52 @@ +#ifndef __RPGDEFS_H__ +#define __RPGDEFS_H__ + +class RPGDefs +{ +public: + enum RPGDataType + { + RPGTYPE_ALL = -1, + RPGTYPE_SPELL = 1 << 0, + RPGTYPE_ITEM = 1 << 1, + }; + enum ERRORS + { + ERR_NONE = 0, //ÎÞ´íÎó + ERR_UNKOWN, //δ֪´íÎó + ERR_NOTENOUGHMANA, //ħ·¨²»¹» + ERR_DATANOTMATCH, //Êý¾Ý²»Æ¥Åä + }; + enum EFFECTRUN + { + ONCLIENT = 1 << 0 , + ONSERVER = 1 << 1 , + }; + enum EFFECTPHRASE + { + PHRASE_CAST = 0, //Ò÷³ª½×¶Î + PHRASE_LAUNCH, //Ê©·¨½×¶Î + PHRASE_IMPACT, //ÃüÖн׶Π+ PHRASE_RESIDUE, //ºóÐø½×¶Î + PHRASE_MAX, + }; + enum RPGBASESTATU + { + STATU_NORMAL = 0, //ij¸öÎïÆ·Õý³£×´Ì¬ + STATU_COOLDOWN, //ÕýÔÚÀäÈ´ÖÐ + STATU_INVALID, //²»¿ÉÓà + }; + enum BOOKTYPE + { + BOOK_PACK = 0, //±³°ü + BOOK_SHOTCUTBAR1, //Ï·½µÄ¿ì½ÝÀ¸ + }; + enum + { + BOOK_MAX = 50, + MAX_EFFECTS_PERPHRASE = 1024, + MAX_EEFECTS_PERPHRASEBITS = 10, + }; +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGEffectron/RPGEffectron.cpp b/add/RPGPack/RPGEffectron/RPGEffectron.cpp new file mode 100644 index 0000000..2b83e63 --- /dev/null +++ b/add/RPGPack/RPGEffectron/RPGEffectron.cpp @@ -0,0 +1,126 @@ +#include "RPGEffectron.h" +#include "RPGEffectronData.h" +#include "core/stream/bitStream.h" +#include "T3D/gameConnection.h" + +IMPLEMENT_CO_NETOBJECT_V1(RPGEffectron); + +RPGEffectron::RPGEffectron() +{ + //²»¹ã²¥ + mNetFlags.clear(Ghostable); +#ifdef CLIENT_ONLY_CODE + mNetFlags = IsGhost; +#endif + mDataBlock = NULL; + _mTimePassed = 0; +} + +RPGEffectron::~RPGEffectron() +{ + +} + +bool RPGEffectron::onNewDataBlock( GameBaseData* dptr ) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + return true; +} + +void RPGEffectron::processTick( const Move* m ) +{ + Parent::processTick(m); + + if (isClientObject()) + return; + + _processServer(); + _mTimePassed += TickMs; +} + +void RPGEffectron::advanceTime( F32 dt ) +{ + Parent::advanceTime(dt); + AssertFatal(isClientObject(),"RPGEffectron::advanceTime"); + U32 ndt = dt * 1000; + + _processClient(ndt); + _mTimePassed += ndt; +} + +bool RPGEffectron::onAdd() +{ + if (!Parent::onAdd()) + return(false); + + _prepareFhrasese(); + _mPhrases.phraseStart(mDataBlock->getDataPhrases(),isServerObject(),0,0); + + return(true); +} + +void RPGEffectron::onRemove() +{ + _clearPhrases(); + Parent::onRemove(); + Con::printf("RPGEffectron::onRemove()"); +} + +U32 RPGEffectron::packUpdate( NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + return(retMask); +} + +void RPGEffectron::unpackUpdate( NetConnection* con, BitStream* stream ) +{ + Parent::unpackUpdate(con, stream); +} + +void RPGEffectron::initPersistFields() +{ + Parent::initPersistFields(); +} + +void RPGEffectron::interpolateTick( F32 delta ) +{ + Parent::interpolateTick(delta); +} + +void RPGEffectron::_processServer() +{ + const U32 & castTime = _mPhrases.getLastingTime(); + if (_mTimePassed >= castTime) + { + Con::printf("server === RPGEffectron->safeDeleteObject()"); + this->safeDeleteObject(); + } + else + _mPhrases.phraseUpdate(TickMs); +} + +void RPGEffectron::_processClient(U32 dt) +{ + const U32 & castTime = _mPhrases.getLastingTime(); + if (_mTimePassed >= castTime) + { + Con::printf("client === RPGEffectron->safeDeleteObject()"); + this->safeDeleteObject(); + } + else + _mPhrases.phraseUpdate(dt); +} + +void RPGEffectron::_clearPhrases() +{ + _mPhrases.phraseEnd(); +} + +void RPGEffectron::_prepareFhrasese() +{ + _mPhrases.phraseInit(mDataBlock->getDataPhrases()); +} \ No newline at end of file diff --git a/add/RPGPack/RPGEffectron/RPGEffectron.h b/add/RPGPack/RPGEffectron/RPGEffectron.h new file mode 100644 index 0000000..26087a1 --- /dev/null +++ b/add/RPGPack/RPGEffectron/RPGEffectron.h @@ -0,0 +1,45 @@ +#ifndef __RPGEFFECTRON_H__ +#define __RPGEFFECTRON_H__ + +#include "../RPGBase.h" +#include "../EffectPhrase/EffectWrapperPhrase.h" + +class RPGEffectronData; + +//this class is not ghosted , only on client or only on server + +class RPGEffectron : public RPGBase , public RPGDefs +{ + typedef RPGBase Parent; +protected: + U32 _mTimePassed; + RPGEffectronData* mDataBlock; + EffectWrapperPhrase _mPhrases; + + //===============rpg stuff================ + void _processServer(); + void _processClient(U32 dt); + + void _prepareFhrasese(); + void _clearPhrases(); + //===================================== +public: + RPGEffectron(); + ~RPGEffectron(); + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move*); + void interpolateTick(F32 delta); + void advanceTime(F32 dt); + + bool onAdd(); + void onRemove(); + + U32 packUpdate(NetConnection*, U32, BitStream*); + void unpackUpdate(NetConnection*, BitStream*); + + static void initPersistFields(); + + DECLARE_CONOBJECT(RPGEffectron); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGEffectron/RPGEffectronData.cpp b/add/RPGPack/RPGEffectron/RPGEffectronData.cpp new file mode 100644 index 0000000..c25ab04 --- /dev/null +++ b/add/RPGPack/RPGEffectron/RPGEffectronData.cpp @@ -0,0 +1,131 @@ +#include "RPGEffectronData.h" +#include "RPGEffectron.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "../RPGUtils.h" +#include "../EffectWrapper/EffectWrapperData.h" + +IMPLEMENT_CONSOLETYPE(RPGEffectronData) +IMPLEMENT_GETDATATYPE(RPGEffectronData) +IMPLEMENT_SETDATATYPE(RPGEffectronData) +IMPLEMENT_CO_DATABLOCK_V1(RPGEffectronData); + +RPGEffectronData::RPGEffectronData(): +_mDummyPtr(NULL), +_doConvert(false) +{ + +} + +void RPGEffectronData::packData( BitStream* stream) +{ + Parent::packData(stream); + packEffects(stream); +} + +void RPGEffectronData::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); + _doConvert = true; + unPackEffects(stream); +} + +#define myOffset(field) Offset(field, RPGEffectronData) +void RPGEffectronData::initPersistFields() +{ + Parent::initPersistFields(); + + static ewValidator _ewPhrase; + + addFieldV("addEffect", TypeGameBaseDataPtr , myOffset(_mDummyPtr),&_ewPhrase); +} + +U32 RPGEffectronData::onActivate( ERRORS & error , const U32 & casterSimID /*= 0*/, const U32 & targetSimID /*= 0*/ ) +{ + error = ERR_NONE; + + RPGEffectron * pSpell = new RPGEffectron(); + pSpell->setCasterSimID(casterSimID); + pSpell->setTargetSimID(targetSimID); + Con::printf("=== RPGEffectronData::onActivate"); + if (pSpell->onNewDataBlock(this) && pSpell->registerObject()) + { + error = ERR_NONE; + return 0; + } + delete pSpell; + return 0; +} + +bool RPGEffectronData::onDeActivate( ERRORS & error ,const U32 & casterSimID /*= 0*/, const U32 & targetSimID /*= 0*/ ) +{ + error = ERR_NONE; + return true; +} + +void RPGEffectronData::addEffect( EffectWrapperData * pEffectWrapperData) +{ + _mDataPhrase.addEffectWrapperData(pEffectWrapperData); +} + +void RPGEffectronData::packEffects( BitStream * stream ) +{ + EffectWrapperDataPhrase::EWDATALIST & ewdList = _mDataPhrase.getEffectWrapperDatas(); + S32 size = ewdList.size(); + stream->writeInt(size,MAX_EEFECTS_PERPHRASEBITS); + for (S32 j = 0 ; j < size ; ++j) + { + RPGUtils::writeDatablockID(stream,(SimObject*)ewdList[j],packed); + } +} + +void RPGEffectronData::unPackEffects( BitStream * stream ) +{ + EffectWrapperDataPhrase::EWDATALIST & ewdList = _mDataPhrase.getEffectWrapperDatas(); + ewdList.clear(); + S32 size = stream->readInt(MAX_EEFECTS_PERPHRASEBITS); + for (S32 j = 0 ; j < size ; ++j) + { + ewdList.push_back( ( EffectWrapperData *)RPGUtils::readDatablockID(stream) ); + } +} + +void RPGEffectronData::expandEffects() +{ + EffectWrapperDataPhrase::EWDATALIST & ewdList = _mDataPhrase.getEffectWrapperDatas(); + S32 size = ewdList.size(); + for (S32 j = 0 ; j < size ; ++j) + { + SimObjectId id = (SimObjectId)ewdList[j]; + if (id != 0) + { + if (!Sim::findObject(id, ewdList[j])) + { + Con::errorf("RPGEffectronData::expandEffects -- bad datablockId: 0x%x ",id); + } + } + } +} + +bool RPGEffectronData::preload( bool server, String &errorStr ) +{ + if ( !server && _doConvert) + { + expandEffects(); + _doConvert = false; + } + return Parent::preload(server,errorStr); +} + +void RPGEffectronData::ewValidator::validateType( SimObject *object, void *typePtr ) +{ + RPGEffectronData* spelldata = dynamic_cast(object); + EffectWrapperData** ew = (EffectWrapperData**)(typePtr); + spelldata->addEffect(*ew); +} + +ConsoleMethod(RPGEffectronData,start,void,2,2,"%effectronData.start();") +{ + RPGDefs::ERRORS error; + object->onActivate(error,0,0); +} \ No newline at end of file diff --git a/add/RPGPack/RPGEffectron/RPGEffectronData.h b/add/RPGPack/RPGEffectron/RPGEffectronData.h new file mode 100644 index 0000000..a462d3b --- /dev/null +++ b/add/RPGPack/RPGEffectron/RPGEffectronData.h @@ -0,0 +1,42 @@ +#ifndef __RPGEFFECTRONDATA_H__ +#define __RPGEFFECTRONDATA_H__ + +#include "../RPGBaseData.h" +#include "../EffectPhrase/EffectWrapperDataPhrase.h" +#include "console/typeValidators.h" + +class RPGEffectronData : public RPGBaseData +{ + typedef RPGBaseData Parent; + EffectWrapperDataPhrase _mDataPhrase; + EffectWrapperData * _mDummyPtr; + bool _doConvert; +public: + class ewValidator : public TypeValidator + { + public: + void validateType(SimObject *object, void *typePtr); + }; + + RPGEffectronData(); + void packData(BitStream*); + void unpackData(BitStream*); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + //===============rpg stuff================= + //´´½¨Õâ¸öЧ¹û + virtual U32 onActivate(ERRORS & error , const U32 & casterSimID = 0, const U32 & targetSimID = 0); + //Ïú»ÙÕâ¸öЧ¹û + virtual bool onDeActivate(ERRORS & error ,const U32 & casterSimID = 0, const U32 & targetSimID = 0); + EffectWrapperDataPhrase * getDataPhrases(){return &_mDataPhrase;} + //Ìí¼ÓЧ¹û + void addEffect(EffectWrapperData * pEffectWrapperData); + //effectlistµÄ´ò°üÓë½â°ü + void packEffects(BitStream * stream); + void unPackEffects(BitStream * stream); + void expandEffects(); + //====================================== + DECLARE_CONOBJECT(RPGEffectronData); +}; +DECLARE_CONSOLETYPE(RPGEffectronData); +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGSpell/RPGSpell.cpp b/add/RPGPack/RPGSpell/RPGSpell.cpp new file mode 100644 index 0000000..b8b87b8 --- /dev/null +++ b/add/RPGPack/RPGSpell/RPGSpell.cpp @@ -0,0 +1,304 @@ +#include "RPGSpell.h" +#include "RPGSpellData.h" +#include "core/stream/bitStream.h" +#include "T3D/gameConnection.h" + +IMPLEMENT_CO_NETOBJECT_V1(RPGSpell); + +RPGSpell::RPGSpell() +{ + mDataBlock = NULL; + _mPhrase = PHRASE_CAST; + _mTimePassed = 0; +} + +RPGSpell::~RPGSpell() +{ + +} + +bool RPGSpell::onNewDataBlock( GameBaseData* dptr ) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr)) + return false; + + return true; +} + +void RPGSpell::processTick( const Move* m ) +{ + Parent::processTick(m); + + if (isClientObject()) + return; + + _processServer(); + _mTimePassed += TickMs; +} + +void RPGSpell::advanceTime( F32 dt ) +{ + Parent::advanceTime(dt); + AssertFatal(isClientObject(),"RPGSpell::advanceTime"); + //client don't delete by self , this will be deleted by ghost + if (PHRASE_MAX == _mPhrase) + { + return; + } + U32 ndt = dt * 1000; + _processClient(ndt); + _mTimePassed += ndt; +} + +bool RPGSpell::onAdd() +{ + if (!Parent::onAdd()) + return(false); + + _prepareFhrasese(); + + if (isServerObject()) + { + Con::printf("servser === RPGSpell::onAdd"); + _enter_cast_s(); + } + else + { + Con::printf("client === RPGSpell::onAdd"); + _enter_cast_c(); + } + + return(true); +} + +void RPGSpell::onRemove() +{ + _clearPhrases(); + Parent::onRemove(); + Con::printf("RPGSpell::onRemove()"); +} + +U32 RPGSpell::packUpdate( NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + return(retMask); +} + +void RPGSpell::unpackUpdate( NetConnection* con, BitStream* stream ) +{ + Parent::unpackUpdate(con, stream); +} + +void RPGSpell::initPersistFields() +{ + Parent::initPersistFields(); +} + +void RPGSpell::interpolateTick( F32 delta ) +{ + Parent::interpolateTick(delta); +} + +void RPGSpell::_processServer() +{ + const U32 & castTime = _mPhrases[PHRASE_CAST].getLastingTime(); + const U32 & launchTime = _mPhrases[PHRASE_LAUNCH].getLastingTime(); + const U32 & impactTime = _mPhrases[PHRASE_IMPACT].getLastingTime(); + const U32 & residueTime = _mPhrases[PHRASE_RESIDUE].getLastingTime(); + EFFECTPHRASE oldPhrase = _mPhrase; + switch(_mPhrase) + { + case PHRASE_MAX: + Con::printf("server === this->safeDeleteObject()"); + this->safeDeleteObject(); + break; + case PHRASE_CAST: + if (_mTimePassed >= castTime) + { + _leave_cast_s(); + _enter_launch_s(); + } + break; + case PHRASE_LAUNCH: + if (_mTimePassed >= castTime + launchTime) + { + _leave_launch_s(); + _enter_impact_s(); + } + break; + case PHRASE_IMPACT: + if (_mTimePassed >= castTime + launchTime + impactTime) + { + _leave_impact_s(); + _enter_residue_s(); + } + break; + case PHRASE_RESIDUE: + if (_mTimePassed >= castTime + launchTime + impactTime + residueTime) + { + _leave_residue_s(); + } + break; + } + if (oldPhrase == _mPhrase && PHRASE_MAX != _mPhrase) + _mPhrases[_mPhrase].phraseUpdate(TickMs); +} + +void RPGSpell::_processClient(U32 dt) +{ + const U32 & castTime = _mPhrases[PHRASE_CAST].getLastingTime(); + const U32 & launchTime = _mPhrases[PHRASE_LAUNCH].getLastingTime(); + const U32 & impactTime = _mPhrases[PHRASE_IMPACT].getLastingTime(); + const U32 & residueTime = _mPhrases[PHRASE_RESIDUE].getLastingTime(); + EFFECTPHRASE oldPhrase = _mPhrase; + switch(_mPhrase) + { + case PHRASE_MAX: + Con::printf("client === this->safeDeleteObject()"); + this->safeDeleteObject(); + break; + case PHRASE_CAST: + if (_mTimePassed >= castTime) + { + _leave_cast_c(); + _enter_launch_c(); + } + break; + case PHRASE_LAUNCH: + if (_mTimePassed >= castTime + launchTime) + { + _leave_launch_c(); + _enter_impact_c(); + } + break; + case PHRASE_IMPACT: + if (_mTimePassed >= castTime + launchTime + impactTime) + { + _leave_impact_c(); + _enter_residue_c(); + } + break; + case PHRASE_RESIDUE: + if (_mTimePassed >= castTime + launchTime + impactTime + residueTime) + { + _leave_residue_c(); + } + break; + } + if (oldPhrase == _mPhrase && PHRASE_MAX != _mPhrase) + _mPhrases[_mPhrase].phraseUpdate(dt); +} + +void RPGSpell::_enter_cast_s() +{ + _enter_cast_c(); +} + +void RPGSpell::_leave_cast_s() +{ + _leave_cast_c(); +} + +void RPGSpell::_enter_cast_c() +{ + _mPhrase = PHRASE_CAST; + _mTimePassed = 0; + _mPhrases[_mPhrase].phraseStart(mDataBlock->getDataPhrases() + _mPhrase,isServerObject(),getCasterSimID(),getTargetSimID()); +} + +void RPGSpell::_leave_cast_c() +{ + _mPhrases[PHRASE_CAST].phraseEnd(); +} + +void RPGSpell::_enter_launch_s() +{ + _enter_launch_c(); +} + +void RPGSpell::_leave_launch_s() +{ + _leave_launch_c(); +} + +void RPGSpell::_enter_launch_c() +{ + _mPhrase = PHRASE_LAUNCH; + _mPhrases[_mPhrase].phraseStart(mDataBlock->getDataPhrases() + _mPhrase,isServerObject(),getCasterSimID(),getTargetSimID()); +} + +void RPGSpell::_leave_launch_c() +{ + _mPhrases[PHRASE_LAUNCH].phraseEnd(); +} + +void RPGSpell::_enter_impact_s() +{ + _enter_impact_c(); +} + +void RPGSpell::_leave_impact_s() +{ + _leave_impact_c(); +} + +void RPGSpell::_enter_impact_c() +{ + _mPhrase = PHRASE_IMPACT; + _mPhrases[_mPhrase].phraseStart(mDataBlock->getDataPhrases() + _mPhrase,isServerObject(),getCasterSimID(),getTargetSimID()); +} + +void RPGSpell::_leave_impact_c() +{ + _mPhrases[PHRASE_IMPACT].phraseEnd(); +} + +void RPGSpell::_enter_residue_s() +{ + _enter_residue_c(); +} + +void RPGSpell::_leave_residue_s() +{ + _leave_residue_c(); +} + +void RPGSpell::_enter_residue_c() +{ + _mPhrase = PHRASE_RESIDUE; + _mPhrases[_mPhrase].phraseStart(mDataBlock->getDataPhrases() + _mPhrase,isServerObject(),getCasterSimID(),getTargetSimID()); +} + +void RPGSpell::_leave_residue_c() +{ + _mPhrases[PHRASE_RESIDUE].phraseEnd(); + _mPhrase = PHRASE_MAX; +} + +void RPGSpell::_prepareFhrasese() +{ + for (U8 i = PHRASE_CAST ; i < PHRASE_MAX ; ++i) + { + _mPhrases[i].phraseInit(mDataBlock->getDataPhrases() + i); + } +} + +void RPGSpell::_clearPhrases() +{ + if (isServerObject()) + { + _leave_cast_s(); + _leave_launch_s(); + _leave_impact_s(); + _leave_residue_s(); + } + else + { + _leave_cast_c(); + _leave_launch_c(); + _leave_impact_c(); + _leave_residue_c(); + } +} \ No newline at end of file diff --git a/add/RPGPack/RPGSpell/RPGSpell.h b/add/RPGPack/RPGSpell/RPGSpell.h new file mode 100644 index 0000000..8a0f6b1 --- /dev/null +++ b/add/RPGPack/RPGSpell/RPGSpell.h @@ -0,0 +1,70 @@ +#ifndef __RPGSPELL_H__ +#define __RPGSPELL_H__ + +#include "../RPGBase.h" +#include "../EffectPhrase/EffectWrapperPhrase.h" + +class RPGSpellData; + +class RPGSpell : public RPGBase , public RPGDefs +{ + typedef RPGBase Parent; +public: + enum MaskBits + { + SpellPhraseMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1, + }; +protected: + EFFECTPHRASE _mPhrase; + U32 _mTimePassed; + RPGSpellData* mDataBlock; + EffectWrapperPhrase _mPhrases[PHRASE_MAX]; + + //===============rpg stuff================ + void _processServer(); + void _processClient(U32 dt); + + void _prepareFhrasese(); + void _clearPhrases(); + + void _enter_cast_s(); + void _leave_cast_s(); + void _enter_cast_c(); + void _leave_cast_c(); + + void _enter_launch_s(); + void _leave_launch_s(); + void _enter_launch_c(); + void _leave_launch_c(); + + void _enter_impact_s(); + void _leave_impact_s(); + void _enter_impact_c(); + void _leave_impact_c(); + + void _enter_residue_s(); + void _leave_residue_s(); + void _enter_residue_c(); + void _leave_residue_c(); + //===================================== +public: + RPGSpell(); + ~RPGSpell(); + bool onNewDataBlock(GameBaseData* dptr); + void processTick(const Move*); + void interpolateTick(F32 delta); + void advanceTime(F32 dt); + + bool onAdd(); + void onRemove(); + + U32 packUpdate(NetConnection*, U32, BitStream*); + void unpackUpdate(NetConnection*, BitStream*); + + static void initPersistFields(); + + DECLARE_CONOBJECT(RPGSpell); +}; + +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGSpell/RPGSpellData.cpp b/add/RPGPack/RPGSpell/RPGSpellData.cpp new file mode 100644 index 0000000..5c65ced --- /dev/null +++ b/add/RPGPack/RPGSpell/RPGSpellData.cpp @@ -0,0 +1,149 @@ +#include "RPGSpellData.h" +#include "RPGSpell.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "../RPGUtils.h" +#include "../EffectWrapper/EffectWrapperData.h" + +IMPLEMENT_CONSOLETYPE(RPGSpellData) +IMPLEMENT_GETDATATYPE(RPGSpellData) +IMPLEMENT_SETDATATYPE(RPGSpellData) +IMPLEMENT_CO_DATABLOCK_V1(RPGSpellData); + +RPGSpellData::RPGSpellData(): +_mDummyPtr(NULL), +_nCoolDownTime(0), +_doConvert(false) +{ + +} + +void RPGSpellData::packData( BitStream* stream) +{ + Parent::packData(stream); + stream->write(_nCoolDownTime); + packEffects(stream); +} + +void RPGSpellData::unpackData( BitStream* stream) +{ + Parent::unpackData(stream); + _doConvert = true; + stream->read(&_nCoolDownTime); + unPackEffects(stream); +} + +#define myOffset(field) Offset(field, RPGSpellData) +void RPGSpellData::initPersistFields() +{ + Parent::initPersistFields(); + + static ewValidator _castingPhrase(PHRASE_CAST); + static ewValidator _launchPhrase(PHRASE_LAUNCH); + static ewValidator _impactPhrase(PHRASE_IMPACT); + static ewValidator _residuePhrase(PHRASE_RESIDUE); + + addField("coolDownMS", TypeS32, myOffset(_nCoolDownTime)); + + addFieldV("addCastingEffect", TypeGameBaseDataPtr , myOffset(_mDummyPtr),&_castingPhrase); + addFieldV("addLaunchEffect", TypeGameBaseDataPtr, myOffset(_mDummyPtr),&_launchPhrase); + addFieldV("addImpactEffect", TypeGameBaseDataPtr, myOffset(_mDummyPtr),&_impactPhrase); + addFieldV("addResidueEffect", TypeGameBaseDataPtr, myOffset(_mDummyPtr),&_residuePhrase); +} + +U32 RPGSpellData::onActivate( ERRORS & error , const U32 & casterSimID /*= 0*/, const U32 & targetSimID /*= 0*/ ) +{ + RPGSpell * pSpell = new RPGSpell(); + pSpell->setCasterSimID(casterSimID); + pSpell->setTargetSimID(targetSimID); + Con::printf("servser === RPGSpellData::onActivate"); + if (pSpell->onNewDataBlock(this) && pSpell->registerObject()) + { + error = ERR_NONE; + return _nCoolDownTime; + } + delete pSpell; + error = ERR_UNKOWN; + return 0; +} + +bool RPGSpellData::onDeActivate( ERRORS & error ,const U32 & casterSimID /*= 0*/, const U32 & targetSimID /*= 0*/ ) +{ + error = ERR_NONE; + GameBase * pCaster = dynamic_cast( Sim::findObject(casterSimID) ); + if (pCaster) + { + pCaster->onInterrupt(); + } + return true; +} + +void RPGSpellData::addEffect( EffectWrapperData * pEffectWrapperData , EFFECTPHRASE phrase ) +{ + _mDataPhrase[phrase].addEffectWrapperData(pEffectWrapperData); +} + +void RPGSpellData::packEffects( BitStream * stream ) +{ + for (S32 i = 0 ; i < PHRASE_MAX ; ++i) + { + EffectWrapperDataPhrase::EWDATALIST & ewdList = _mDataPhrase[i].getEffectWrapperDatas(); + S32 size = ewdList.size(); + stream->writeInt(size,MAX_EEFECTS_PERPHRASEBITS); + for (S32 j = 0 ; j < size ; ++j) + { + RPGUtils::writeDatablockID(stream,(SimObject*)ewdList[j],packed); + } + } +} + +void RPGSpellData::unPackEffects( BitStream * stream ) +{ + for (S32 i = 0 ; i < PHRASE_MAX ; ++i) + { + EffectWrapperDataPhrase::EWDATALIST & ewdList = _mDataPhrase[i].getEffectWrapperDatas(); + ewdList.clear(); + S32 size = stream->readInt(MAX_EEFECTS_PERPHRASEBITS); + for (S32 j = 0 ; j < size ; ++j) + { + ewdList.push_back( ( EffectWrapperData *)RPGUtils::readDatablockID(stream) ); + } + } +} + +void RPGSpellData::expandEffects() +{ + for (S32 i = 0 ; i < PHRASE_MAX ; ++i) + { + EffectWrapperDataPhrase::EWDATALIST & ewdList = _mDataPhrase[i].getEffectWrapperDatas(); + S32 size = ewdList.size(); + for (S32 j = 0 ; j < size ; ++j) + { + SimObjectId id = (SimObjectId)ewdList[j]; + if (id != 0) + { + if (!Sim::findObject(id, ewdList[j])) + { + Con::errorf("RPGSpellData::expandEffects -- bad datablockId: 0x%x ",id); + } + } + } + } +} + +bool RPGSpellData::preload( bool server, String &errorStr ) +{ + if ( !server && _doConvert) + { + expandEffects(); + _doConvert = false; + } + return Parent::preload(server,errorStr); +} + +void RPGSpellData::ewValidator::validateType( SimObject *object, void *typePtr ) +{ + RPGSpellData* spelldata = dynamic_cast(object); + EffectWrapperData** ew = (EffectWrapperData**)(typePtr); + spelldata->addEffect(*ew,(EFFECTPHRASE)_id); +} \ No newline at end of file diff --git a/add/RPGPack/RPGSpell/RPGSpellData.h b/add/RPGPack/RPGSpell/RPGSpellData.h new file mode 100644 index 0000000..6d85a16 --- /dev/null +++ b/add/RPGPack/RPGSpell/RPGSpellData.h @@ -0,0 +1,46 @@ +#ifndef __RPGSPELLDATA_H__ +#define __RPGSPELLDATA_H__ + +#include "../RPGBaseData.h" +#include "../EffectPhrase/EffectWrapperDataPhrase.h" +#include "console/typeValidators.h" + +class RPGSpellData : public RPGBaseData +{ + typedef RPGBaseData Parent; + U32 _nCoolDownTime; + EffectWrapperDataPhrase _mDataPhrase[PHRASE_MAX]; + EffectWrapperData * _mDummyPtr; + bool _doConvert; +public: + class ewValidator : public TypeValidator + { + U32 _id; + public: + ewValidator(U32 id) { _id = id; } + void validateType(SimObject *object, void *typePtr); + }; + + RPGSpellData(); + void packData(BitStream*); + void unpackData(BitStream*); + static void initPersistFields(); + bool preload(bool server, String &errorStr); + //===============rpg stuff================= + //µ±Ò»¸öitem±»¼¤»î£¬Èç¹ûÄܹ»¼¤»î£¬error·µ»Ø0 + //U32·µ»ØÀäȴʱ¼ä£¬0±íʾÎÞÀäȴʱ¼ä£¬µ¥Î»MS£¬-1±íʾÓÀÔ¶Ö´ÐÐ + virtual U32 onActivate(ERRORS & error , const U32 & casterSimID = 0, const U32 & targetSimID = 0); + //µ±Ò»¸öitem±»¼¤»îºó£¬ÓÖ±»·´¼¤»î£¬Èç¹ûÄܹ»·´¼¤»î£¬Ôò·µ»Øtrue£¬·ñÔò·µ»Øfalse + virtual bool onDeActivate(ERRORS & error ,const U32 & casterSimID = 0, const U32 & targetSimID = 0); + EffectWrapperDataPhrase * getDataPhrases(){return _mDataPhrase;} + //Ìí¼Ó¸÷¸ö½ÚµãµÄЧ¹û + void addEffect(EffectWrapperData * pEffectWrapperData , EFFECTPHRASE phrase); + //effectlistµÄ´ò°üÓë½â°ü + void packEffects(BitStream * stream); + void unPackEffects(BitStream * stream); + void expandEffects(); + //====================================== + DECLARE_CONOBJECT(RPGSpellData); +}; +DECLARE_CONSOLETYPE(RPGSpellData); +#endif \ No newline at end of file diff --git a/add/RPGPack/RPGUtils.cpp b/add/RPGPack/RPGUtils.cpp new file mode 100644 index 0000000..f78aacb --- /dev/null +++ b/add/RPGPack/RPGUtils.cpp @@ -0,0 +1,86 @@ +#include "RPGUtils.h" +#include "T3D/gameConnection.h" + + +S32 RPGUtils::rolloverRayCast(Point3F start, Point3F end, U32 mask) +{ + GameConnection* conn = GameConnection::getConnectionToServer(); + SceneObject* ctrl_obj = NULL; + + if (conn != NULL) + ctrl_obj = conn->getControlObject(); + + //if (ctrl_obj) + // ctrl_obj->disableCollision(); + + static SceneObject* rollover_obj = 0; + SceneObject* picked_obj = 0; + + RayInfo hit_info; + if (gClientContainer.castRay(start, end, mask, &hit_info)) + picked_obj = dynamic_cast(hit_info.object); + + //if (ctrl_obj) + // ctrl_obj->enableCollision(); + + if (picked_obj != rollover_obj) + { + if (rollover_obj) + rollover_obj->setSelectionFlags(rollover_obj->getSelectionFlags() & ~SceneObject::PRE_SELECTED); + if (picked_obj) + picked_obj->setSelectionFlags(picked_obj->getSelectionFlags() | SceneObject::PRE_SELECTED); + rollover_obj = picked_obj; + + if (conn) + conn->setRolloverObj(rollover_obj); + } + + return (picked_obj) ? picked_obj->getId() : -1; +} + +//=========================================== +std::vector ClientOnlyNetObject::_mClientOnlys; + +ClientOnlyNetObject::ClientOnlyNetObject( const type_info * typeinfo ) +{ + _mClientOnlys.push_back(typeinfo); +} + +bool ClientOnlyNetObject::isClientOnly( const type_info * typeinfo ) +{ + for (S32 i = 0 ; i < _mClientOnlys.size() ; ++i) + { + if ((*_mClientOnlys[i]) == *typeinfo) + { + return true; + } + } + return false; +} + +//CLIENTONLY(TSStatic); + + +ConsoleMethod(NetConnection, GetGhostIndex, S32, 3, 3, "") +{ + NetObject* pObject; + if (Sim::findObject(argv[2], pObject)) + return object->getGhostIndex(pObject); + return 0; +} + +ConsoleMethod(NetConnection, ResolveGhost, S32, 3, 3, "") +{ + S32 nIndex = dAtoi(argv[2]); + if (nIndex != -1) + { + NetObject* pObject = NULL; + if( object->isGhostingTo()) + pObject = object->resolveGhost(nIndex); + else if( object->isGhostingFrom()) + pObject = object->resolveObjectFromGhostIndex(nIndex); + if (pObject) + return pObject->getId(); + } + return 0; +} \ No newline at end of file diff --git a/add/RPGPack/RPGUtils.h b/add/RPGPack/RPGUtils.h new file mode 100644 index 0000000..68bfb67 --- /dev/null +++ b/add/RPGPack/RPGUtils.h @@ -0,0 +1,39 @@ +#ifndef __RPGUTILS_H__ +#define __RPGUTILS_H__ + +#include "core/stream/bitStream.h" +#include "console/simObject.h" +#include +#include + +#define CLIENTONLY(T) class T;\ + ClientOnlyNetObject gClinetOnly##T(&(typeid(T))) + +class ClientOnlyNetObject +{ + static std::vector _mClientOnlys; +public: + ClientOnlyNetObject(const type_info *); + static bool isClientOnly(const type_info *); +}; + +class RPGUtils +{ +public: + static inline void writeDatablockID(BitStream* s, SimObject* simobj, bool packed=false) + { + if (s->writeFlag(simobj)) + s->writeRangedU32(packed ? SimObjectId(simobj) : simobj->getId(), + DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + + static inline S32 readDatablockID(BitStream* s) + { + return (!s->readFlag()) ? 0 : ((S32)s->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast)); + } + + static S32 rolloverRayCast(Point3F start, Point3F end, U32 mask); +}; + +#endif \ No newline at end of file diff --git a/add/WLLib/wlmgr.cpp b/add/WLLib/wlmgr.cpp new file mode 100644 index 0000000..9edf1dd --- /dev/null +++ b/add/WLLib/wlmgr.cpp @@ -0,0 +1,210 @@ +#include "wlmgr.h" +#include "core/util/safeDelete.h" +#include "../Global/GlobalStatic.h" + +CWLMgr * CWLMgr::_instance = 0; +static const char * wldll = "WLLib.dll"; + +CWLMgr::CWLMgr():_dll(0),_spaceHashTable(0),_lockFreeQueue_RecvPkt(0),\ +_threadPool(0),_spaceIndexedMesh(0),_strOp(0),_lockFreeQueue_SendPkt(NULL),\ +_lockFreeQueue_MonsterAction(NULL) +{ + _dll = new WL::WINDOWS_DLL(wldll); + //Æô¶¯Ïß³Ì³Ø + //createThreadPool(2,100); + //_threadPool->setActiveThreadCount(2); + //Æô¶¯½ÓÊÕudp°üµÄÁ´±í + createStack_RecvPkt(); + createStack_SendPkt(); + //Æô¶¯¹ÖÎïactionÁ´±í + createStack_MonsterAction(); +} + +CWLMgr::~CWLMgr() +{ + SAFE_DELETE(_spaceHashTable); + SAFE_DELETE(_spaceIndexedMesh); + + while(_lockFreeQueue_RecvPkt->size()) + CGlobalStatic::freePkt((PKT*)_lockFreeQueue_RecvPkt->pop()); + _lockFreeQueue_RecvPkt->destroy(); + + while(_lockFreeQueue_SendPkt->size()) + CGlobalStatic::freePktSend((PKTSEND*)_lockFreeQueue_SendPkt->pop()); + _lockFreeQueue_SendPkt->destroy(); + + while(_lockFreeQueue_MonsterAction->size()) + _lockFreeQueue_MonsterAction->pop();//CMonsterManager::freeParam((CActionParam*)_lockFreeQueue_MonsterAction->pop()); + _lockFreeQueue_MonsterAction->destroy(); + + //SAFE_DELETE(_threadPool); + //SAFE_DELETE(_strOp);Õâ¸ö²»ÐèÒªÏú»Ù + SAFE_DELETE(_dll); +} + +void CWLMgr::destroy() +{ + SAFE_DELETE(_instance); +} + +CWLMgr * CWLMgr::getInstance() +{ + static CWLMgr instance; + return & instance; + // if (_instance == 0) + // { + // _instance = new CWLMgr(); + // } + // return _instance; +} + +void CWLMgr::createSpaceHashTable(WL::Box world,int xBlock,int yBlock,int zBlock) +{ + if (_dll && _spaceHashTable == 0) + { + typedef WL::CSpaceHashTable * (*CREATESPACEHASH) (WL::Box,int,int,int); + CREATESPACEHASH fun = (CREATESPACEHASH)(_dll->getProcessInDll("CreateSpaceHashTable")); + if (fun) + { + _spaceHashTable = fun(world,xBlock,yBlock,zBlock); + } + } +} + + +void CWLMgr::createStack_RecvPkt() +{ + if (_dll && _lockFreeQueue_RecvPkt == 0) + { + typedef WL::CLockFreeQueue * (*CREATESTACK) (); + CREATESTACK fun = (CREATESTACK)(_dll->getProcessInDll("CreateLockFreeQueue")); + if (fun) + { + _lockFreeQueue_RecvPkt = fun(); + } + } +} + +void CWLMgr::createStack_SendPkt() +{ + if (_dll && _lockFreeQueue_SendPkt == 0) + { + typedef WL::CLockFreeQueue * (*CREATESTACK) (); + CREATESTACK fun = (CREATESTACK)(_dll->getProcessInDll("CreateLockFreeQueue")); + if (fun) + { + _lockFreeQueue_SendPkt = fun(); + } + } +} + +void CWLMgr::createStrOp() +{ + if (_dll && _strOp == 0) + { + typedef WL::CStrOp * (*CREATESTROP) (); + CREATESTROP fun = (CREATESTROP)(_dll->getProcessInDll("CreateStrOp")); + if (fun) + { + _strOp = fun(); + } + } +} + +void CWLMgr::createSpaceIndexedMesh(WL::Box world,int xBlock,int yBlock,int zBlock) +{ + if (_dll && _spaceIndexedMesh == 0) + { + typedef WL::CSpaceIndexedMesh * (*CREATESPACEMESH) (WL::Box,int,int,int); + CREATESPACEMESH fun = (CREATESPACEMESH)(_dll->getProcessInDll("CreateSpaceIndexedMesh")); + if (fun) + { + _spaceIndexedMesh = fun(world,xBlock,yBlock,zBlock); + } + } +} + +void CWLMgr::createThreadPool(int nThreadCount, int nThreadCapability) +{ + if (_dll && _threadPool == 0) + { + typedef WL::CThreadPool * (*CREATETHREADPOOL) (int,int); + CREATETHREADPOOL fun = (CREATETHREADPOOL)(_dll->getProcessInDll("CreateThreadPool")); + if(fun) + _threadPool = fun(nThreadCount,nThreadCapability); + } +} + +WL::CSpaceHashTable * CWLMgr::getSpaceTable() +{ + return _spaceHashTable; +} + +WL::CSpaceIndexedMesh * CWLMgr::getSpaceMesh() +{ + return _spaceIndexedMesh; +} + +WL::CLockFreeQueue * CWLMgr::getStack_RecvPkt() +{ + return _lockFreeQueue_RecvPkt; +} + +WL::CLockFreeQueue * CWLMgr::getStack_SendPkt() +{ + return _lockFreeQueue_SendPkt; +} + +WL::CThreadPool * CWLMgr::getThreadPool() +{ + return _threadPool; +} + +WL::CStrOp * CWLMgr::getStrOp() +{ + if (_strOp == NULL) + { + createStrOp(); + } + return _strOp; +} + +void CWLMgr::createStack_MonsterAction() +{ + if (_dll && _lockFreeQueue_MonsterAction == 0) + { + typedef WL::CLockFreeQueue * (*CREATESTACK) (); + CREATESTACK fun = (CREATESTACK)(_dll->getProcessInDll("CreateLockFreeQueue")); + if (fun) + { + _lockFreeQueue_MonsterAction = fun(); + } + } +} + +WL::CLockFreeQueue * CWLMgr::getStack_MonsterAction() +{ + return _lockFreeQueue_MonsterAction; +} + +WL::CLockFreeQueue * CWLMgr::createLockFreeQueueInstance() +{ + WL::CLockFreeQueue * pInstance = NULL; + if (_dll) + { + typedef WL::CLockFreeQueue * (*CREATESTACK) (); + CREATESTACK fun = (CREATESTACK)(_dll->getProcessInDll("CreateLockFreeQueue")); + if (fun) + { + pInstance = fun(); + } + } + return pInstance; +} + +void CWLMgr::destroyLockFreeQueueInstance(WL::CLockFreeQueue * pInstance) +{ + while(pInstance->size()) + pInstance->pop(); + pInstance->destroy(); +} \ No newline at end of file diff --git a/add/WLLib/wlmgr.h b/add/WLLib/wlmgr.h new file mode 100644 index 0000000..8f52e7f --- /dev/null +++ b/add/WLLib/wlmgr.h @@ -0,0 +1,41 @@ +#ifndef __WL_MGR__ +#define __WL_MGR__ +#include "D:\\My Documents\\Visual Studio 2008\\Projects\\WLLib\\WLLib\\WLLib.h" + +class CWLMgr +{ + CWLMgr(); + ~CWLMgr(); + static CWLMgr * _instance; + WL::WINDOWS_DLL * _dll; + WL::CSpaceHashTable * _spaceHashTable; + WL::CLockFreeQueue * _lockFreeQueue_RecvPkt; + WL::CLockFreeQueue * _lockFreeQueue_SendPkt; + WL::CLockFreeQueue * _lockFreeQueue_MonsterAction; + WL::CThreadPool * _threadPool; + WL::CSpaceIndexedMesh * _spaceIndexedMesh; + WL::CStrOp * _strOp; +public: + static CWLMgr * getInstance(); + static void destroy(); + + void createSpaceHashTable(WL::Box world,int xBlock,int yBlock,int zBlock); + void createSpaceIndexedMesh(WL::Box world,int xBlock,int yBlock,int zBlock); + void createThreadPool(int nThreadCount, int nThreadCapability); + void createStrOp(); + void createStack_RecvPkt(); + void createStack_SendPkt(); + void createStack_MonsterAction(); + WL::CSpaceHashTable * getSpaceTable(); + WL::CSpaceIndexedMesh * getSpaceMesh(); + WL::CLockFreeQueue * getStack_RecvPkt(); + WL::CLockFreeQueue * getStack_SendPkt(); + WL::CLockFreeQueue * getStack_MonsterAction(); + WL::CThreadPool * getThreadPool(); + WL::CStrOp * getStrOp(); + + WL::CLockFreeQueue * createLockFreeQueueInstance(); + void destroyLockFreeQueueInstance(WL::CLockFreeQueue * pInstance); +}; + +#endif \ No newline at end of file diff --git a/app/auth.h b/app/auth.h new file mode 100644 index 0000000..a589f9c --- /dev/null +++ b/app/auth.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUTH_H_ +#define _AUTH_H_ + +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +/// Formerly contained a certificate, showing that something was valid. +class Auth2Certificate +{ + U32 xxx; +}; + +/// Formerly contained data indicating whether a user is valid. +struct AuthInfo +{ + enum { + MaxNameLen = 31, + }; + + bool valid; + char name[MaxNameLen + 1]; +}; + +/// Formerly validated the server's authentication info. +inline bool validateAuthenticatedServer() +{ + return true; +} + +/// Formerly validated the client's authentication info. +inline bool validateAuthenticatedClient() +{ + return true; +} + +#endif diff --git a/app/badWordFilter.cpp b/app/badWordFilter.cpp new file mode 100644 index 0000000..0b834ae --- /dev/null +++ b/app/badWordFilter.cpp @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" + +#include "console/consoleTypes.h" +#include "app/badWordFilter.h" + + +BadWordFilter *gBadWordFilter = NULL; +bool BadWordFilter::filteringEnabled = true; + +BadWordFilter::BadWordFilter() +{ + VECTOR_SET_ASSOCIATION( filterTables ); + + dStrcpy(defaultReplaceStr, "knqwrtlzs"); + filterTables.push_back(new FilterTable); + curOffset = 0; +} + +BadWordFilter::~BadWordFilter() +{ + for(U32 i = 0; i < filterTables.size(); i++) + delete filterTables[i]; +} + +void BadWordFilter::create() +{ + Con::addVariable("pref::enableBadWordFilter", TypeBool, &filteringEnabled); + gBadWordFilter = new BadWordFilter; + gBadWordFilter->addBadWord("shit"); + gBadWordFilter->addBadWord("fuck"); + gBadWordFilter->addBadWord("cock"); + gBadWordFilter->addBadWord("bitch"); + gBadWordFilter->addBadWord("cunt"); + gBadWordFilter->addBadWord("nigger"); + gBadWordFilter->addBadWord("bastard"); + gBadWordFilter->addBadWord("dick"); + gBadWordFilter->addBadWord("whore"); + gBadWordFilter->addBadWord("goddamn"); + gBadWordFilter->addBadWord("asshole"); +} + +void BadWordFilter::destroy() +{ + delete gBadWordFilter; + gBadWordFilter = NULL; +} + + +U8 BadWordFilter::remapTable[257] = "------------------------------------------------OI---------------ABCDEFGHIJKLMNOPQRSTUVWXYZ------ABCDEFGHIJKLMNOPQRSTUVWXYZ-----C--F--TT--S-C-Z-----------S-C-ZY--CLOY-S-CA---R---UT-UP--IO-----AAAAAAACEEEEIIIIDNOOOOOXOUUUUYDBAAAAAAACEEEEIIIIDNOOOOO-OUUUUYDY"; +U8 BadWordFilter::randomJunk[MaxBadwordLength+1] = "REMsg rk34n4ksqow;xnskq;KQoaWnZa"; + +BadWordFilter::FilterTable::FilterTable() +{ + for(U32 i = 0; i < 26; i++) + nextState[i] = TerminateNotFound; +} + +bool BadWordFilter::addBadWord(const char *cword) +{ + FilterTable *curFilterTable = filterTables[0]; + // prescan the word to see if it has any skip chars + const U8 *word = (const U8 *) cword; + const U8 *walk = word; + if(dStrlen(cword) > MaxBadwordLength) + return false; + while(*walk) + { + if(remapTable[*walk] == '-') + return false; + walk++; + } + while(*word) + { + U8 remap = remapTable[*word] - 'A'; + U16 state = curFilterTable->nextState[remap]; + + if(state < TerminateNotFound) + { + // this character is already in the state table... + curFilterTable = filterTables[state]; + } + else if(state == TerminateFound) + { + // a subset of this word is already in the table... + // exit out. + return false; + } + else if(state == TerminateNotFound) + { + if(word[1]) + { + curFilterTable->nextState[remap] = filterTables.size(); + filterTables.push_back(new FilterTable); + curFilterTable = filterTables[filterTables.size() - 1]; + } + else + curFilterTable->nextState[remap] = TerminateFound; + } + word++; + } + return true; +} + +bool BadWordFilter::setDefaultReplaceStr(const char *str) +{ + U32 len = dStrlen(str); + if(len < 2 || len >= sizeof(defaultReplaceStr)) + return false; + dStrcpy(defaultReplaceStr, str); + return true; +} + +void BadWordFilter::filterString(char *cstring, const char *replaceStr) +{ + if(!replaceStr) + replaceStr = defaultReplaceStr; + U8 *string = (U8 *) cstring; + U8 *starts[MaxBadwordLength]; + U8 *curStart = string; + U32 replaceLen = dStrlen(replaceStr); + while(*curStart) + { + FilterTable *curFilterTable = filterTables[0]; + S32 index = 0; + U8 *walk = curStart; + while(*walk) + { + U8 remap = remapTable[*walk]; + if(remap != '-') + { + starts[index++] = walk; + U16 table = curFilterTable->nextState[remap - 'A']; + if(table < TerminateNotFound) + curFilterTable = filterTables[table]; + else if(table == TerminateNotFound) + { + curStart++; + break; + } + else // terminate found + { + for(U32 i = 0; i < index; i++) + { + starts[i][0] = (U8 )replaceStr[curOffset % replaceLen]; + curOffset += randomJunk[curOffset & (MaxBadwordLength - 1)]; + } + curStart = walk + 1; + break; + } + } + walk++; + } + if(!*walk) + curStart++; + } +} + +bool BadWordFilter::containsBadWords(const char *cstring) +{ + U8 *string = (U8 *) cstring; + U8 *curStart = string; + while(*curStart) + { + FilterTable *curFilterTable = filterTables[0]; + U8 *walk = curStart; + while(*walk) + { + U8 remap = remapTable[*walk]; + if(remap != '-') + { + U16 table = curFilterTable->nextState[remap - 'A']; + if(table < TerminateNotFound) + curFilterTable = filterTables[table]; + else if(table == TerminateNotFound) + { + curStart++; + break; + } + else // terminate found + return true; + } + walk++; + } + if(!*walk) + curStart++; + } + return false; +} + +ConsoleFunction(addBadWord, bool, 2, 2, "addBadWord(someReallyNastyWord);") +{ + TORQUE_UNUSED(argc); + return gBadWordFilter->addBadWord(argv[1]); +} + +ConsoleFunction(filterString, const char *, 2, 3, "filterString(baseString,replacementChars);") +{ + const char *replaceStr = NULL; + if(argc == 3) + replaceStr = argv[2]; + + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1); + dStrcpy(ret, argv[1]); + gBadWordFilter->filterString(ret, replaceStr); + return ret; +} + +ConsoleFunction(containsBadWords, bool, 2, 2, "containsBadWords(text);") +{ + TORQUE_UNUSED(argc); + return gBadWordFilter->containsBadWords(argv[1]); +} \ No newline at end of file diff --git a/app/badWordFilter.h b/app/badWordFilter.h new file mode 100644 index 0000000..f2c5d7b --- /dev/null +++ b/app/badWordFilter.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _H_BADWORDFILTER +#define _H_BADWORDFILTER + +#include "core/util/tVector.h" + +class BadWordFilter +{ +private: + struct FilterTable + { + U16 nextState[26]; // only 26 alphabetical chars. + FilterTable(); + }; + friend struct FilterTable; + Vector filterTables; + + enum { + TerminateNotFound = 0xFFFE, + TerminateFound = 0xFFFF, + MaxBadwordLength = 32, + }; + char defaultReplaceStr[32]; + + BadWordFilter(); + ~BadWordFilter(); + U32 curOffset; + static U8 remapTable[257]; + static U8 randomJunk[MaxBadwordLength + 1]; + static bool filteringEnabled; + +public: + bool addBadWord(const char *word); + bool setDefaultReplaceStr(const char *str); + void filterString(char *string, const char *replaceStr = NULL); + bool containsBadWords(const char *string); + + static bool isEnabled() { return filteringEnabled; } + static void setEnabled(bool enable) { filteringEnabled = enable; } + static void create(); + static void destroy(); +}; + +extern BadWordFilter *gBadWordFilter; + +#endif \ No newline at end of file diff --git a/app/banList.cpp b/app/banList.cpp new file mode 100644 index 0000000..890912a --- /dev/null +++ b/app/banList.cpp @@ -0,0 +1,186 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "app/banList.h" +#include "console/consoleTypes.h" +#include "core/stream/fileStream.h" + +IMPLEMENT_CONOBJECT(BanList); +BanList gBanList; + +//------------------------------------------------------------------------------ + +void BanList::addBan(S32 uniqueId, const char *TA, S32 banTime) +{ + S32 curTime = Platform::getTime(); + + if(banTime != 0 && banTime < curTime) + return; + + // make sure this bastard isn't already banned on this server + Vector::iterator i; + for(i = list.begin();i != list.end();i++) + { + if(uniqueId == i->uniqueId) + { + i->bannedUntil = banTime; + return; + } + } + + BanInfo b; + dStrcpy(b.transportAddress, TA); + b.uniqueId = uniqueId; + b.bannedUntil = banTime; + + if(!dStrnicmp(b.transportAddress, "ip:", 3)) + { + char *c = dStrchr(b.transportAddress+3, ':'); + if(c) + { + *(c+1) = '*'; + *(c+2) = 0; + } + } + + list.push_back(b); +} + +//------------------------------------------------------------------------------ + +void BanList::addBanRelative(S32 uniqueId, const char *TA, S32 numSeconds) +{ + S32 curTime = Platform::getTime(); + S32 banTime = 0; + if(numSeconds != -1) + banTime = curTime + numSeconds; + + addBan(uniqueId, TA, banTime); +} + +//------------------------------------------------------------------------------ + +void BanList::removeBan(S32 uniqueId, const char *) +{ + Vector::iterator i; + for(i = list.begin();i != list.end();i++) + { + if(uniqueId == i->uniqueId) + { + list.erase(i); + return; + } + } +} + +//------------------------------------------------------------------------------ + +bool BanList::isBanned(S32 uniqueId, const char *) +{ + S32 curTime = Platform::getTime(); + + Vector::iterator i; + for(i = list.begin();i != list.end();) + { + if(i->bannedUntil != 0 && i->bannedUntil < curTime) + { + list.erase(i); + continue; + } + else if(uniqueId == i->uniqueId) + return true; + i++; + } + return false; +} + +//------------------------------------------------------------------------------ + +bool BanList::isTAEq(const char *bannedTA, const char *TA) +{ + char a, b; + for(;;) + { + a = *bannedTA++; + b = *TA++; + if(a == '*' || (!a && b == ':')) // ignore port + return true; + if(dTolower(a) != dTolower(b)) + return false; + if(!a) + return true; + } +} + +//------------------------------------------------------------------------------ + +void BanList::exportToFile(const char *name) +{ + FileStream *banlist; + + char filename[1024]; + Con::expandScriptFilename(filename, sizeof(filename), name); + if((banlist = FileStream::createAndOpen( filename, Torque::FS::File::Write )) == NULL) + return; + + char buf[1024]; + Vector::iterator i; + for(i = list.begin(); i != list.end(); i++) + { + dSprintf(buf, sizeof(buf), "BanList::addAbsolute(%d, \"%s\", %d);\r\n", i->uniqueId, i->transportAddress, i->bannedUntil); + banlist->write(dStrlen(buf), buf); + } + + delete banlist; +} + +// --------------------------------------------------------- +// console methods +// --------------------------------------------------------- + +ConsoleStaticMethod( BanList, addAbsolute, void, 4, 4, "( int ID, TransportAddress TA, int banTime )" + "Ban a user until a given time.\n\n" + "@param ID Unique ID of the player.\n" + "@param TA Address from which the player connected.\n" + "@param banTime Time at which they will be allowed back in.") +{ + gBanList.addBan( dAtoi( argv[1] ), argv[2], dAtoi( argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleStaticMethod( BanList, add, void, 4, 4, "( int ID, TransportAddress TA, int banLength )" + "Ban a user for banLength seconds.\n\n" + "@param ID Unique ID of the player.\n" + "@param TA Address from which the player connected.\n" + "@param banTime Time at which they will be allowed back in.") +{ + gBanList.addBanRelative( dAtoi( argv[1] ), argv[2], dAtoi( argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleStaticMethod( BanList, removeBan, void, 3, 3, "( int ID, TransportAddress TA )" + "Unban someone.\n\n" + "@param ID Unique ID of the player.\n" + "@param TA Address from which the player connected.\n") + +{ + gBanList.removeBan( dAtoi( argv[1] ), argv[2] ); +} + +//------------------------------------------------------------------------------ +ConsoleStaticMethod( BanList, isBanned, bool, 3, 3, "( int ID, TransportAddress TA )" + "Is someone banned?\n\n" + "@param ID Unique ID of the player.\n" + "@param TA Address from which the player connected.\n") +{ + return (gBanList.isBanned( dAtoi( argv[1] ), argv[2] )); +} + +//------------------------------------------------------------------------------ +ConsoleStaticMethod( BanList, export, void, 2, 2, "(string filename)" + "Dump the banlist to a file.") +{ + gBanList.exportToFile( argv[1] ); +} diff --git a/app/banList.h b/app/banList.h new file mode 100644 index 0000000..7cacbc4 --- /dev/null +++ b/app/banList.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BANLIST_H_ +#define _BANLIST_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +/// Helper class to keep track of bans. +class BanList : public SimObject +{ + typedef SimObject Parent; +public: + + struct BanInfo + { + S32 uniqueId; + char transportAddress[128]; + S32 bannedUntil; + }; + + Vector list; + + BanList() + { + VECTOR_SET_ASSOCIATION( list ); + } + ~BanList(){} + + void addBan(S32 uniqueId, const char *TA, S32 banTime); + void addBanRelative(S32 uniqueId, const char *TA, S32 numSeconds); + void removeBan(S32 uniqueId, const char *TA); + bool isBanned(S32 uniqueId, const char *TA); + bool isTAEq(const char *bannedTA, const char *TA); + void exportToFile(const char *fileName); + + DECLARE_CONOBJECT(BanList); +}; + +extern BanList gBanList; + +#endif diff --git a/app/game.cpp b/app/game.cpp new file mode 100644 index 0000000..406ec68 --- /dev/null +++ b/app/game.cpp @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformInput.h" + +#include "app/game.h" +#include "math/mMath.h" +#include "core/dnet.h" +#include "core/stream/fileStream.h" +#include "core/frameAllocator.h" +#include "core/iTickable.h" +#include "core/strings/findMatch.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gui/controls/guiMLTextCtrl.h" +#ifdef TORQUE_TGB_ONLY +#include "T2D/networking/t2dGameConnection.h" +#include "T2D/networking/t2dNetworkServerSceneProcess.h" +#include "T2D/networking/t2dNetworkClientSceneProcess.h" +#else +#include "T3D/gameConnection.h" +#include "T3D/gameFunctions.h" +#include "T3D/gameProcess.h" +#endif +#include "platform/profiler.h" +#include "gfx/gfxCubemap.h" +#include "gfx/gfxTextureManager.h" +#include "sfx/sfxSystem.h" + +#ifdef TORQUE_PLAYER +// See matching #ifdef in editor/editor.cpp +bool gEditingMission = false; +#endif + +//-------------------------------------------------------------------------- + +ConsoleFunctionGroupBegin( InputManagement, "Functions that let you deal with input from scripts" ); + +ConsoleFunction( deactivateDirectInput, void, 1, 1, "Deactivate input. (ie, ungrab the mouse so the user can do other things." ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + if ( Input::isActive() ) + Input::deactivate(); +} + +ConsoleFunction( activateDirectInput, void, 1, 1, "Activate input. (ie, grab the mouse again so the user can play our game." ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + if ( !Input::isActive() ) + Input::activate(); +} +ConsoleFunctionGroupEnd( InputManagement ); + +//-------------------------------------------------------------------------- + +static const U32 MaxPlayerNameLength = 16; +ConsoleFunction( strToPlayerName, const char*, 2, 2, "strToPlayerName( string )" ) +{ + TORQUE_UNUSED(argc); + + const char* ptr = argv[1]; + + // Strip leading spaces and underscores: + while ( *ptr == ' ' || *ptr == '_' ) + ptr++; + + U32 len = dStrlen( ptr ); + if ( len ) + { + char* ret = Con::getReturnBuffer( MaxPlayerNameLength + 1 ); + char* rptr = ret; + ret[MaxPlayerNameLength - 1] = '\0'; + ret[MaxPlayerNameLength] = '\0'; + bool space = false; + + U8 ch; + while ( *ptr && dStrlen( ret ) < MaxPlayerNameLength ) + { + ch = (U8) *ptr; + + // Strip all illegal characters: + if ( ch < 32 || ch == ',' || ch == '.' || ch == '\'' || ch == '`' ) + { + ptr++; + continue; + } + + // Don't allow double spaces or space-underline combinations: + if ( ch == ' ' || ch == '_' ) + { + if ( space ) + { + ptr++; + continue; + } + else + space = true; + } + else + space = false; + + *rptr++ = *ptr; + ptr++; + } + *rptr = '\0'; + + //finally, strip out the ML text control chars... + return GuiMLTextCtrl::stripControlChars(ret); + } + + return( "" ); +} + +ConsoleFunctionGroupBegin( Platform , "General platform functions."); + +ConsoleFunction( lockMouse, void, 2, 2, "(bool isLocked)" + "Lock the mouse (or not, depending on the argument's value) to the window.") +{ + Platform::setWindowLocked(dAtob(argv[1])); +} + + +ConsoleFunction( setNetPort, bool, 2, 3, "(int port, bool bind=true)" + "Set the network port for the game to use. If bind is true, bind()" + "will be called on the port. This will trigger a windows firewall prompt." + "If you don't have firewall tunneling tech you can set this to false to avoid the prompt.") +{ + bool bind = true; + if (argc == 3) + bind = dAtob(argv[2]); + return Net::openPort(dAtoi(argv[1]), bind); +} + +ConsoleFunction( closeNetPort, void, 1, 1, "()") +{ + Net::closePort(); +} + +ConsoleFunction( saveJournal, void, 2, 2, "(string filename)" + "Save the journal to the specified file.") +{ + Journal::Record(argv[1]); +} + +ConsoleFunction( playJournal, void, 2, 3, "(string filename, bool break=false)" + "Begin playback of a journal from a specified field, optionally breaking at the start.") +{ + // CodeReview - BJG 4/24/2007 - The break flag needs to be wired back in. + // bool jBreak = (argc > 2)? dAtob(argv[2]): false; + Journal::Play(argv[1]); +} + +ConsoleFunction( getSimTime, S32, 1, 1, "Return the current sim time in milliseconds.\n\n" + "Sim time is time since the game started.") +{ + return Sim::getCurrentTime(); +} + +ConsoleFunction( getRealTime, S32, 1, 1, "Return the current real time in milliseconds.\n\n" + "Real time is platform defined; typically time since the computer booted.") +{ + return Platform::getRealMilliseconds(); +} + +ConsoleFunctionGroupEnd(Platform); + +//----------------------------------------------------------------------------- + +bool clientProcess(U32 timeDelta) +{ + bool ret = true; + +#ifndef TORQUE_TGB_ONLY + ret = gClientProcessList.advanceTime(timeDelta); +#else + ret = gt2dNetworkClientProcess.advanceTime( timeDelta ); +#endif + + ITickable::advanceTime(timeDelta); + +#ifndef TORQUE_TGB_ONLY + // Run the collision test and update the Audio system + // by checking the controlObject + MatrixF mat; + Point3F velocity; + + if (GameGetCameraTransform(&mat, &velocity)) + { + SFX->getListener().setTransform(mat); + SFX->getListener().setVelocity(velocity); + } + + // Determine if we're lagging + GameConnection* connection = GameConnection::getConnectionToServer(); + if(connection) + { + connection->detectLag(); + } +#else + // Determine if we're lagging + t2dGameConnection* connection = t2dGameConnection::getConnectionToServer(); + if(connection) + { + connection->detectLag(); + } +#endif + + // Let SFX process. + SFX->_update(); + + return ret; +} + +bool serverProcess(U32 timeDelta) +{ + bool ret = true; +#ifndef TORQUE_TGB_ONLY + ret = gServerProcessList.advanceTime(timeDelta); +#else + ret = gt2dNetworkServerProcess.advanceTime( timeDelta ); +#endif + return ret; +} + diff --git a/app/game.h b/app/game.h new file mode 100644 index 0000000..47f3faa --- /dev/null +++ b/app/game.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GAME_H_ +#define _GAME_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +/// Processes the next frame, including gui, rendering, and tick interpolation. +/// This function will only have an effect when executed on the client. +bool clientProcess(U32 timeDelta); + +/// Processes the next cycle on the server. This function will only have an effect when executed on the server. +bool serverProcess(U32 timeDelta); + +#endif diff --git a/app/mainLoop.cpp b/app/mainLoop.cpp new file mode 100644 index 0000000..dcd9771 --- /dev/null +++ b/app/mainLoop.cpp @@ -0,0 +1,567 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "app/mainLoop.h" +#include "app/badWordFilter.h" +#include "app/game.h" + +#include "platform/platformTimer.h" +#include "platform/platformRedBook.h" +#include "platform/platformVolume.h" +#include "platform/platformMemory.h" +#include "platform/platformTimer.h" +#include "platform/nativeDialogs/fileDialog.h" + +#include "core/threadStatic.h" +#include "core/iTickable.h" +#include "core/stream/fileStream.h" + +#include "windowManager/platformWindowMgr.h" + +#include "core/util/journal/process.h" +#include "util/fpsTracker.h" + +#include "console/telnetConsole.h" +#include "console/telnetDebugger.h" +#include "console/debugOutputConsumer.h" +#include "console/consoleTypes.h" + +#include "gfx/bitmap/gBitmap.h" +#include "gfx/gFont.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gfxInit.h" + +#include "sim/netStringTable.h" +#include "sim/actionMap.h" +#include "sim/netInterface.h" + +#include "sfx/sfxSystem.h" + +#include "util/sampler.h" +#include "platform/threads/threadPool.h" + +#ifdef TORQUE_ENABLE_VFS +#include "platform/platformVFS.h" +#endif + +#include "../add/Global/GlobalStatic.h" + +DITTS( F32, gTimeScale, 1.0 ); +DITTS( U32, gTimeAdvance, 0 ); +DITTS( U32, gFrameSkip, 0 ); + +extern S32 sgBackgroundProcessSleepTime; +extern S32 sgTimeManagerProcessInterval; + +extern void netInit(); + +extern FPSTracker gFPS; + +TimeManager* tm = NULL; + +static bool gRequiresRestart = false; + +#ifdef TORQUE_DEBUG + +/// Temporary timer used to time startup times. +static PlatformTimer* gStartupTimer; + +#endif + +// The following are some tricks to make the memory leak checker run after global +// dtors have executed by placing some code in the termination segments. + +#if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER ) + + #ifdef TORQUE_COMPILER_VISUALC + # pragma data_seg( ".CRT$XTU" ) + + static void* sCheckMemBeforeTermination = &Memory::ensureAllFreed; + + # pragma data_seg() + #elif defined( TORQUE_COMPILER_GCC ) + + __attribute__ ( ( destructor ) ) static void _ensureAllFreed() + { + Memory::ensureAllFreed(); + } + + #endif + +#endif + +// Process a time event and update all sub-processes +void processTimeEvent(S32 elapsedTime) +{ + PROFILE_START(ProcessTimeEvent); + + // cap the elapsed time to one second + // if it's more than that we're probably in a bad catch-up situation + if(elapsedTime > 1024) + elapsedTime = 1024; + + U32 timeDelta; + if(ATTS(gTimeAdvance)) + timeDelta = ATTS(gTimeAdvance); + else + timeDelta = (U32) (elapsedTime * ATTS(gTimeScale)); + + Platform::advanceTime(elapsedTime); + + bool tickPass; + + PROFILE_START(ServerProcess); + tickPass = serverProcess(timeDelta); + PROFILE_END(); + + PROFILE_START(ServerNetProcess); + // only send packets if a tick happened + if(tickPass) + GNet->processServer(); + PROFILE_END(); + + PROFILE_START(SimAdvanceTime); + Sim::advanceTime(timeDelta); + PROFILE_END(); + + PROFILE_START(ClientProcess); + tickPass = clientProcess(timeDelta); + PROFILE_END_NAMED(ClientProcess); + + PROFILE_START(ClientNetProcess); + if(tickPass) + GNet->processClient(); + PROFILE_END(); + + GNet->checkTimeouts(); + + gFPS.update(); + + PROFILE_END(); + + // Update the console time + Con::setFloatVariable("Sim::Time",F32(Platform::getVirtualMilliseconds()) / 1000); +} + +void StandardMainLoop::init() +{ + #ifdef TORQUE_DEBUG + gStartupTimer = PlatformTimer::create(); + #endif + + #ifdef TORQUE_DEBUG_GUARD + Memory::flagCurrentAllocs( Memory::FLAG_Global ); + #endif + + Platform::setMathControlStateKnown(); + + // Asserts should be created FIRST + PlatformAssert::create(); + + // Yell if we can't initialize the network. + if(!Net::init()) + { + AssertISV(false, "StandardMainLoop::initCore - could not initialize networking!"); + } + + FrameAllocator::init(TORQUE_FRAME_SIZE); // See comments in torqueConfig.h + _StringTable::create(); + + // Set up the resource manager and get some basic file types in it. + Con::init(); + Platform::initConsole(); + NetStringTable::create(); + + // Use debug output logging on the Xbox and OSX builds +#if defined( _XBOX ) || defined( TORQUE_OS_MAC ) + DebugOutputConsumer::init(); +#endif + + TelnetConsole::create(); + TelnetDebugger::create(); + + Processor::init(); + Math::init(); + Platform::init(); // platform specific initialization + RedBook::init(); + Platform::initConsole(); + SFXSystem::init(); + GFXDevice::initConsole(); + GFXTextureManager::init(); + + // Initialise ITickable. +#ifdef TORQUE_TGB_ONLY + ITickable::init( 4 ); +#endif + +#ifdef TORQUE_ENABLE_VFS + // [tom, 10/28/2006] Load the VFS here so that it stays loaded + Zip::ZipArchive *vfs = openEmbeddedVFSArchive(); + gResourceManager->addVFSRoot(vfs); +#endif + + Con::addVariable("timeScale", TypeF32, &ATTS(gTimeScale)); + Con::addVariable("timeAdvance", TypeS32, &ATTS(gTimeAdvance)); + Con::addVariable("frameSkip", TypeS32, &ATTS(gFrameSkip)); + + Con::setVariable( "defaultGame", StringTable->insert("scripts") ); + + Con::addVariable( "_forceAllMainThread", TypeBool, &ThreadPool::getForceAllMainThread() ); + +#if !defined( _XBOX ) && !defined( TORQUE_DEDICATED ) + initMessageBoxVars(); +#endif + + netInit(); + Sim::init(); + + ActionMap* globalMap = new ActionMap; + globalMap->registerObject("GlobalActionMap"); + Sim::getActiveActionMapSet()->pushObject(globalMap); + + BadWordFilter::create(); + + // Do this before we init the process so that process notifiees can get the time manager + tm = new TimeManager; + tm->timeEvent.notify(&::processTimeEvent); + + Process::init(); + Sampler::init(); + + // Hook in for UDP notification + Net::smPacketReceive.notify(GNet, &NetInterface::processPacketReceiveEvent); + + CGlobalStatic::init(); + + #ifdef TORQUE_DEBUG_GUARD + Memory::flagCurrentAllocs( Memory::FLAG_Static ); + #endif +} + +void StandardMainLoop::shutdown() +{ + delete tm; + preShutdown(); + + CGlobalStatic::shutdown(); + + BadWordFilter::destroy(); + + // Shut down SFX before SIM so that it clears out any audio handles + SFXSystem::destroy(); + + GFXInit::cleanup(); + + // Note: tho the SceneGraphs are created after the Manager, delete them after, rather + // than before to make sure that all the objects are removed from the graph. + Sim::shutdown(); + + Process::shutdown(); + + //necessary for DLL unloading + ThreadPool::GLOBAL().shutdown(); + +#ifdef TORQUE_ENABLE_VFS + closeEmbeddedVFSArchive(); +#endif + + RedBook::destroy(); + + Platform::shutdown(); + GFXDevice::destroy(); + + TelnetDebugger::destroy(); + TelnetConsole::destroy(); + +#if defined( _XBOX ) || defined( TORQUE_OS_MAC ) + DebugOutputConsumer::destroy(); +#endif + + NetStringTable::destroy(); + Con::shutdown(); + + _StringTable::destroy(); + FrameAllocator::destroy(); + Net::shutdown(); + Sampler::destroy(); + + // asserts should be destroyed LAST + PlatformAssert::destroy(); + + +#if defined( TORQUE_DEBUG ) && !defined( TORQUE_DISABLE_MEMORY_MANAGER ) + Memory::validate(); +#endif +} + +void StandardMainLoop::preShutdown() +{ +#ifdef TORQUE_TOOLS + // Tools are given a chance to do pre-quit processing + // - This is because for tools we like to do things such + // as prompting to save changes before shutting down + // and onExit is packaged which means we can't be sure + // where in the shutdown namespace chain we are when using + // onExit since some components of the tools may already be + // destroyed that may be vital to saving changes to avoid + // loss of work [1/5/2007 justind] + if( Con::isFunction("onPreExit") ) + Con::executef( "onPreExit"); +#endif + + //exec the script onExit() function + if ( Con::isFunction( "onExit" ) ) + Con::executef("onExit"); +} + +bool StandardMainLoop::handleCommandLine( S32 argc, const char **argv ) +{ + // Allow the window manager to process command line inputs; this is + // done to let web plugin functionality happen in a fairly transparent way. + PlatformWindowManager::get()->processCmdLineArgs(argc, argv); + + Process::handleCommandLine( argc, argv ); + + // Set up the command line args for the console scripts... + Con::setIntVariable("Game::argc", argc); + U32 i; + for (i = 0; i < argc; i++) + Con::setVariable(avar("Game::argv%d", i), argv[i]); + + Platform::FS::InstallFileSystems(); // install all drives for now until we have everything using the volume stuff + Platform::FS::MountDefaults(); + + // Set our working directory. + Torque::FS::SetCwd( "game:/" ); + + // Set our working directory. + Platform::setCurrentDirectory( Platform::getMainDotCsDir() ); + +#ifdef TORQUE_PLAYER + if(argc > 2 && dStricmp(argv[1], "-project") == 0) + { + char playerPath[1024]; + Platform::makeFullPathName(argv[2], playerPath, sizeof(playerPath)); + Platform::setCurrentDirectory(playerPath); + + argv += 2; + argc -= 2; + + // Re-locate the game:/ asset mount. + + Torque::FS::Unmount( "game" ); + Torque::FS::Mount( "game", Platform::FS::createNativeFS( playerPath ) ); + } +#endif + + // Executes an entry script file. This is "main.cs" + // by default, but any file name (with no whitespace + // in it) may be run if it is specified as the first + // command-line parameter. The script used, default + // or otherwise, is not compiled and is loaded here + // directly because the resource system restricts + // access to the "root" directory. + +#ifdef TORQUE_ENABLE_VFS + Zip::ZipArchive *vfs = openEmbeddedVFSArchive(); + bool useVFS = vfs != NULL; +#endif + + Stream *mainCsStream = NULL; + + // The working filestream. + FileStream str; + + const char *defaultScriptName = "main.cs"; + bool useDefaultScript = true; + + // Check if any command-line parameters were passed (the first is just the app name). + if (argc > 1) + { + // If so, check if the first parameter is a file to open. + if ( (str.open(argv[1], Torque::FS::File::Read)) && (dStrcmp(argv[1], "") != 0 ) ) + { + // If it opens, we assume it is the script to run. + useDefaultScript = false; +#ifdef TORQUE_ENABLE_VFS + useVFS = false; +#endif + mainCsStream = &str; + } + } + + if (useDefaultScript) + { + bool success = false; + +#ifdef TORQUE_ENABLE_VFS + if(useVFS) + success = (mainCsStream = vfs->openFile(defaultScriptName, Zip::ZipArchive::Read)) != NULL; + else +#endif + success = str.open(defaultScriptName, Torque::FS::File::Read); + +#if defined( TORQUE_DEBUG ) && defined (TORQUE_TOOLS) && !defined( _XBOX ) + if (!success) + { + OpenFileDialog ofd; + FileDialogData &fdd = ofd.getData(); + fdd.mFilters = StringTable->insert("Main Entry Script (main.cs)|main.cs|"); + fdd.mTitle = StringTable->insert("Locate Game Entry Script"); + + // Get the user's selection + if( !ofd.Execute() ) + return false; + + // Process and update CWD so we can run the selected main.cs + S32 pathLen = dStrlen( fdd.mFile ); + FrameTemp szPathCopy( pathLen + 1); + + dStrcpy( szPathCopy, fdd.mFile ); + //forwardslash( szPathCopy ); + + const char *path = dStrrchr(szPathCopy, '/'); + if(path) + { + U32 len = path - (const char*)szPathCopy; + szPathCopy[len+1] = 0; + + Platform::setCurrentDirectory(szPathCopy); + + // Re-locate the game:/ asset mount. + + Torque::FS::Unmount( "game" ); + Torque::FS::Mount( "game", Platform::FS::createNativeFS( ( const char* ) szPathCopy ) ); + + success = str.open(fdd.mFile, Torque::FS::File::Read); + if(success) + defaultScriptName = fdd.mFile; + } + } +#endif + if( !success ) + { + char msg[1024]; + dSprintf(msg, sizeof(msg), "Failed to open \"%s\".", defaultScriptName); + Platform::AlertOK("Error", msg); +#ifdef TORQUE_ENABLE_VFS + closeEmbeddedVFSArchive(); +#endif + + return false; + } + +#ifdef TORQUE_ENABLE_VFS + if(! useVFS) +#endif + mainCsStream = &str; + } + + U32 size = mainCsStream->getStreamSize(); + char *script = new char[size + 1]; + mainCsStream->read(size, script); + +#ifdef TORQUE_ENABLE_VFS + if(useVFS) + vfs->closeFile(mainCsStream); + else +#endif + str.close(); + + script[size] = 0; + + char buffer[1024], *ptr; + Platform::makeFullPathName(useDefaultScript ? defaultScriptName : argv[1], buffer, sizeof(buffer), Platform::getCurrentDirectory()); + ptr = dStrrchr(buffer, '/'); + if(ptr != NULL) + *ptr = 0; + Platform::setMainDotCsDir(buffer); + Platform::setCurrentDirectory(buffer); + + Con::evaluate(script, false, useDefaultScript ? defaultScriptName : argv[1]); + delete[] script; + +#ifdef TORQUE_ENABLE_VFS + closeEmbeddedVFSArchive(); +#endif + + return true; +} + +bool StandardMainLoop::doMainLoop() +{ + #ifdef TORQUE_DEBUG + if( gStartupTimer ) + { + Con::printf( "Started up in %.2f seconds...", + F32( gStartupTimer->getElapsedMs() ) / 1000.f ); + SAFE_DELETE( gStartupTimer ); + } + #endif + + bool keepRunning = true; +// while(keepRunning) + { + tm->setBackgroundThreshold(mClamp(sgBackgroundProcessSleepTime, 1, 200)); + tm->setForegroundThreshold(mClamp(sgTimeManagerProcessInterval, 1, 200)); + // update foreground/background status + if(WindowManager->getFirstWindow()) + { + static bool lastFocus = false; + bool newFocus = ( WindowManager->getFocusedWindow() != NULL ); + if(lastFocus != newFocus) + { +#ifndef TORQUE_SHIPPING + Con::printf("Window focus status changed: focus: %d", newFocus); + if (!newFocus) + Con::printf(" Using background sleep time: %u", Platform::getBackgroundSleepTime()); +#endif + +#ifdef TORQUE_OS_MAC + if (newFocus) + WindowManager->getFirstWindow()->show(); + +#endif + lastFocus = newFocus; + } + +#ifndef TORQUE_OS_MAC + // under the web plugin do not sleep the process when the child window loses focus as this will cripple the browser perfomance + if (!Platform::getWebDeployment()) + tm->setBackground(!newFocus); + else + tm->setBackground(false); +#else + tm->setBackground(false); +#endif + } + else + { + tm->setBackground(false); + } + + PROFILE_START(MainLoop); + Sampler::beginFrame(); + + if(!Process::processEvents()) + keepRunning = false; + + ThreadPool::processMainThreadWorkItems(); + Sampler::endFrame(); + PROFILE_END_NAMED(MainLoop); + } + + return keepRunning; +} + +void StandardMainLoop::setRestart(bool restart ) +{ + gRequiresRestart = restart; +} + +bool StandardMainLoop::requiresRestart() +{ + return gRequiresRestart; +} diff --git a/app/mainLoop.h b/app/mainLoop.h new file mode 100644 index 0000000..a6f2544 --- /dev/null +++ b/app/mainLoop.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _APP_MAINLOOP_H_ +#define _APP_MAINLOOP_H_ + +#include "platform/platform.h" + +/// Support class to simplify the process of writing a main loop for Torque apps. +class StandardMainLoop +{ +public: + /// Initialize core libraries and call registered init functions + static void init(); + + /// Pass command line arguments to registered functions and main.cs + static bool handleCommandLine(S32 argc, const char **argv); + + /// A standard mainloop implementation. + static bool doMainLoop(); + + /// Shut down the core libraries and call registered shutdown fucntions. + static void shutdown(); + + static void setRestart( bool restart ); + static bool requiresRestart(); + +private: + /// Handle "pre shutdown" tasks like notifying scripts BEFORE we delete + /// stuff from under them. + static void preShutdown(); +}; + +#endif \ No newline at end of file diff --git a/app/net/httpObject.cpp b/app/net/httpObject.cpp new file mode 100644 index 0000000..062a956 --- /dev/null +++ b/app/net/httpObject.cpp @@ -0,0 +1,309 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "app/net/httpObject.h" + +#include "platform/platform.h" +#include "platform/event.h" +#include "core/stream/fileStream.h" +#include "console/simBase.h" +#include "console/consoleInternal.h" + +IMPLEMENT_CONOBJECT(HTTPObject); + +//-------------------------------------- + +HTTPObject::HTTPObject() +{ + mHostName = 0; + mPath = 0; + mQuery = 0; + mPost = 0; + mBufferSave = 0; +} + +HTTPObject::~HTTPObject() +{ + dFree(mHostName); + dFree(mPath); + dFree(mQuery); + dFree(mPost); + + mHostName = 0; + mPath = 0; + mQuery = 0; + mPost = 0; + dFree(mBufferSave); +} + +//-------------------------------------- +//-------------------------------------- +void HTTPObject::get(const char *host, const char *path, const char *query) +{ + if(mHostName) + dFree(mHostName); + if(mPath) + dFree(mPath); + if(mQuery) + dFree(mQuery); + if(mPost) + dFree(mPost); + if(mBufferSave) + dFree(mBufferSave); + + mBufferSave = 0; + mHostName = dStrdup(host); + mPath = dStrdup(path); + if(query) + mQuery = dStrdup(query); + else + mQuery = NULL; + mPost = NULL; + + connect(host); +} + +void HTTPObject::post(const char *host, const char *path, const char *query, const char *post) +{ + if(mHostName) + dFree(mHostName); + if(mPath) + dFree(mPath); + if(mQuery) + dFree(mQuery); + if(mPost) + dFree(mPost); + if(mBufferSave) + dFree(mBufferSave); + + mBufferSave = 0; + mHostName = dStrdup(host); + mPath = dStrdup(path); + if(query && query[0]) + mQuery = dStrdup(query); + else + mQuery = NULL; + mPost = dStrdup(post); + connect(host); +} + +static char getHex(char c) +{ + if(c <= 9) + return c + '0'; + return c - 10 + 'A'; +} + +static S32 getHexVal(char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if(c >= 'a' && c <= 'z') + return c - 'a' + 10; + return -1; +} + +void HTTPObject::expandPath(char *dest, const char *path, U32 destSize) +{ + static bool asciiEscapeTableBuilt = false; + static bool asciiEscapeTable[256]; + if(!asciiEscapeTableBuilt) + { + asciiEscapeTableBuilt = true; + U32 i; + for(i = 0; i <= ' '; i++) + asciiEscapeTable[i] = true; + for(;i <= 0x7F; i++) + asciiEscapeTable[i] = false; + for(;i <= 0xFF; i++) + asciiEscapeTable[i] = true; + asciiEscapeTable[static_cast('\"')] = true; + asciiEscapeTable[static_cast('_')] = true; + asciiEscapeTable[static_cast('\'')] = true; + asciiEscapeTable[static_cast('#')] = true; + asciiEscapeTable[static_cast('$')] = true; + asciiEscapeTable[static_cast('%')] = true; + asciiEscapeTable[static_cast('&')] = true; + asciiEscapeTable[static_cast('+')] = true; + asciiEscapeTable[static_cast('-')] = true; + asciiEscapeTable[static_cast('~')] = true; + } + + U32 destIndex = 0; + U32 srcIndex = 0; + while(path[srcIndex] && destIndex < destSize - 3) + { + char c = path[srcIndex++]; + if(asciiEscapeTable[static_cast(c)]) + { + dest[destIndex++] = '%'; + dest[destIndex++] = getHex((c >> 4) & 0xF); + dest[destIndex++] = getHex(c & 0xF); + } + else + dest[destIndex++] = c; + } + dest[destIndex] = 0; +} + +//-------------------------------------- +void HTTPObject::onConnected() +{ + Parent::onConnected(); + char expPath[8192]; + char buffer[8192]; + + if(mQuery) + { + dSprintf(buffer, sizeof(buffer), "%s?%s", mPath, mQuery); + expandPath(expPath, buffer, sizeof(expPath)); + } + else + expandPath(expPath, mPath, sizeof(expPath)); + + char *pt = dStrchr(mHostName, ':'); + if(pt) + *pt = 0; + dSprintf(buffer, sizeof(buffer), "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", expPath, mHostName); + if(pt) + *pt = ':'; + + send((U8*)buffer, dStrlen(buffer)); + mParseState = ParsingStatusLine; + mChunkedEncoding = false; +} + +void HTTPObject::onConnectFailed() +{ + dFree(mHostName); + dFree(mPath); + dFree(mQuery); + mHostName = 0; + mPath = 0; + mQuery = 0; + Parent::onConnectFailed(); +} + + +void HTTPObject::onDisconnect() +{ + dFree(mHostName); + dFree(mPath); + dFree(mQuery); + mHostName = 0; + mPath = 0; + mQuery = 0; + Parent::onDisconnect(); +} + +bool HTTPObject::processLine(U8 *line) +{ + if(mParseState == ParsingStatusLine) + { + mParseState = ParsingHeader; + } + else if(mParseState == ParsingHeader) + { + if(!dStricmp((char *) line, "transfer-encoding: chunked")) + mChunkedEncoding = true; + if(line[0] == 0) + { + if(mChunkedEncoding) + mParseState = ParsingChunkHeader; + else + mParseState = ProcessingBody; + return true; + } + } + else if(mParseState == ParsingChunkHeader) + { + if(line[0]) // strip off the crlf if necessary + { + mChunkSize = 0; + S32 hexVal; + while((hexVal = getHexVal(*line++)) != -1) + { + mChunkSize *= 16; + mChunkSize += hexVal; + } + if(mBufferSave) + { + mBuffer = mBufferSave; + mBufferSize = mBufferSaveSize; + mBufferSave = 0; + } + if(mChunkSize) + mParseState = ProcessingBody; + else + { + mParseState = ProcessingDone; + finishLastLine(); + } + } + } + else + { + return Parent::processLine((UTF8*)line); + } + return true; +} + +U32 HTTPObject::onDataReceive(U8 *buffer, U32 bufferLen) +{ + U32 start = 0; + parseLine(buffer, &start, bufferLen); + return start; +} + +//-------------------------------------- +U32 HTTPObject::onReceive(U8 *buffer, U32 bufferLen) +{ + if(mParseState == ProcessingBody) + { + if(mChunkedEncoding && bufferLen >= mChunkSize) + { + U32 ret = onDataReceive(buffer, mChunkSize); + mChunkSize -= ret; + if(mChunkSize == 0) + { + if(mBuffer) + { + mBufferSaveSize = mBufferSize; + mBufferSave = mBuffer; + mBuffer = 0; + mBufferSize = 0; + } + mParseState = ParsingChunkHeader; + } + return ret; + } + else + { + U32 ret = onDataReceive(buffer, bufferLen); + mChunkSize -= ret; + return ret; + } + } + else if(mParseState != ProcessingDone) + { + U32 start = 0; + parseLine(buffer, &start, bufferLen); + return start; + } + return bufferLen; +} + +//-------------------------------------- +ConsoleMethod( HTTPObject, get, void, 4, 5, "(TransportAddress addr, string requirstURI, string query=NULL)") +{ + object->get(argv[2], argv[3], argc == 4 ? NULL : argv[4]); +} + +ConsoleMethod( HTTPObject, post, void, 6, 6, "(TransportAddress addr, string requestURI, string query, string post)") +{ + object->post(argv[2], argv[3], argv[4], argv[5]); +} diff --git a/app/net/httpObject.h b/app/net/httpObject.h new file mode 100644 index 0000000..63c3c05 --- /dev/null +++ b/app/net/httpObject.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HTTPOBJECT_H_ +#define _HTTPOBJECT_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TCPOBJECT_H_ +#include "app/net/tcpObject.h" +#endif + +class HTTPObject : public TCPObject +{ +private: + typedef TCPObject Parent; +protected: + enum ParseState { + ParsingStatusLine, + ParsingHeader, + ParsingChunkHeader, + ProcessingBody, + ProcessingDone, + }; + ParseState mParseState; + U32 mTotalBytes; + U32 mBytesRemaining; + public: + U32 mStatus; + F32 mVersion; + U32 mContentLength; + bool mChunkedEncoding; + U32 mChunkSize; + const char *mContentType; + char *mHostName; + char *mPath; + char *mQuery; + char *mPost; + U8 *mBufferSave; + U32 mBufferSaveSize; +public: + static void expandPath(char *dest, const char *path, U32 destSize); + void get(const char *hostName, const char *urlName, const char *query); + void post(const char *host, const char *path, const char *query, const char *post); + HTTPObject(); + ~HTTPObject(); + + //static HTTPObject *find(U32 tag); + + virtual U32 onDataReceive(U8 *buffer, U32 bufferLen); + virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data + virtual void onConnected(); + virtual void onConnectFailed(); + virtual void onDisconnect(); + bool processLine(U8 *line); + + DECLARE_CONOBJECT(HTTPObject); +}; + + +#endif // _H_HTTPOBJECT_ diff --git a/app/net/net.cpp b/app/net/net.cpp new file mode 100644 index 0000000..3990e60 --- /dev/null +++ b/app/net/net.cpp @@ -0,0 +1,277 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "core/idGenerator.h" +#include "core/stream/bitStream.h" +#include "console/simBase.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "sim/netObject.h" +#include "app/net/serverQuery.h" + +StringTableEntry gMasterAddress; +bool gIsDedicated = false; +//---------------------------------------------------------------- +// remote procedure call console functions +//---------------------------------------------------------------- + +class RemoteCommandEvent : public NetEvent +{ +public: + enum { + MaxRemoteCommandArgs = 20, + CommandArgsBits = 5 + }; + +private: + S32 mArgc; + char *mArgv[MaxRemoteCommandArgs + 1]; + NetStringHandle mTagv[MaxRemoteCommandArgs + 1]; + static char mBuf[1024]; +public: + RemoteCommandEvent(S32 argc=0, const char **argv=NULL, NetConnection *conn = NULL) + { + mArgc = argc; + for(S32 i = 0; i < argc; i++) + { + if(argv[i][0] == StringTagPrefixByte) + { + char buffer[256]; + mTagv[i+1] = NetStringHandle(dAtoi(argv[i]+1)); + if(conn) + { + dSprintf(buffer + 1, sizeof(buffer) - 1, "%d", conn->getNetSendId(mTagv[i+1])); + buffer[0] = StringTagPrefixByte; + mArgv[i+1] = dStrdup(buffer); + } + } + else + mArgv[i+1] = dStrdup(argv[i]); + } + } + +#ifdef TORQUE_DEBUG_NET + const char *getDebugName() + { + static char buffer[256]; + dSprintf(buffer, sizeof(buffer), "%s [%s]", getClassName(), mTagv[1].isValidString() ? mTagv[1].getString() : "--unknown--" ); + return buffer; + } +#endif + + ~RemoteCommandEvent() + { + for(S32 i = 0; i < mArgc; i++) + dFree(mArgv[i+1]); + } + + virtual void pack(NetConnection* conn, BitStream *bstream) + { + bstream->writeInt(mArgc, CommandArgsBits); + // write it out reversed... why? + // automatic string substitution with later arguments - + // handled automatically by the system. + + for(S32 i = 0; i < mArgc; i++) + conn->packString(bstream, mArgv[i+1]); + } + + virtual void write(NetConnection* conn, BitStream *bstream) + { + pack(conn, bstream); + } + + virtual void unpack(NetConnection* conn, BitStream *bstream) + { + + mArgc = bstream->readInt(CommandArgsBits); + // read it out backwards + for(S32 i = 0; i < mArgc; i++) + { + conn->unpackString(bstream, mBuf); + mArgv[i+1] = dStrdup(mBuf); + } + } + + virtual void process(NetConnection *conn) + { + static char idBuf[10]; + + // de-tag the command name + + for(S32 i = mArgc - 1; i >= 0; i--) + { + char *arg = mArgv[i+1]; + if(*arg == StringTagPrefixByte) + { + // it's a tag: + U32 localTag = dAtoi(arg + 1); + NetStringHandle tag = conn->translateRemoteStringId(localTag); + NetStringTable::expandString( tag, + mBuf, + sizeof(mBuf), + (mArgc - 1) - i, + (const char**)(mArgv + i + 2) ); + dFree(mArgv[i+1]); + mArgv[i+1] = dStrdup(mBuf); + } + } + const char *rmtCommandName = dStrchr(mArgv[1], ' ') + 1; + if(conn->isConnectionToServer()) + { + dStrcpy(mBuf, "clientCmd"); + dStrcat(mBuf, rmtCommandName); + + char *temp = mArgv[1]; + mArgv[1] = mBuf; + + Con::execute(mArgc, (const char **) mArgv+1); + mArgv[1] = temp; + } + else + { + dStrcpy(mBuf, "serverCmd"); + dStrcat(mBuf, rmtCommandName); + char *temp = mArgv[1]; + + dSprintf(idBuf, sizeof(idBuf), "%d", conn->getId()); + mArgv[0] = mBuf; + mArgv[1] = idBuf; + + Con::execute(mArgc+1, (const char **) mArgv); + mArgv[1] = temp; + } + } + + DECLARE_CONOBJECT(RemoteCommandEvent); +}; +char RemoteCommandEvent::mBuf[1024]; + +IMPLEMENT_CO_NETEVENT_V1(RemoteCommandEvent); + +static void sendRemoteCommand(NetConnection *conn, S32 argc, const char **argv) +{ + if(U8(argv[0][0]) != StringTagPrefixByte) + { + Con::errorf(ConsoleLogEntry::Script, "Remote Command Error - command must be a tag."); + return; + } + S32 i; + for(i = argc - 1; i >= 0; i--) + { + if(argv[i][0] != 0) + break; + argc = i; + } + for(i = 0; i < argc; i++) + conn->validateSendString(argv[i]); + RemoteCommandEvent *cevt = new RemoteCommandEvent(argc, argv, conn); + conn->postNetEvent(cevt); +} + +ConsoleFunctionGroupBegin( Net, "Functions for use with the network; tagged strings and remote commands."); + +ConsoleFunction( commandToServer, void, 2, RemoteCommandEvent::MaxRemoteCommandArgs + 1, "(string func, ...)" + "Send a command to the server.") +{ + NetConnection *conn = NetConnection::getConnectionToServer(); + if(!conn) + return; + sendRemoteCommand(conn, argc - 1, argv + 1); +} + +ConsoleFunction( commandToClient, void, 3, RemoteCommandEvent::MaxRemoteCommandArgs + 2, "(NetConnection client, string func, ...)") +{ + NetConnection *conn; + if(!Sim::findObject(argv[1], conn)) + return; + sendRemoteCommand(conn, argc - 2, argv + 2); +} + +ConsoleFunction(removeTaggedString, void, 2, 2, "(int tag)") +{ + gNetStringTable->removeString(dAtoi(argv[1]+1), true); +} + +ConsoleFunction( addTaggedString, const char*, 2, 2, "(string str)") +{ + NetStringHandle s(argv[1]); + gNetStringTable->incStringRefScript(s.getIndex()); + + char *ret = Con::getReturnBuffer(10); + ret[0] = StringTagPrefixByte; + dSprintf(ret + 1, 9, "%d", s.getIndex()); + return ret; +} + +ConsoleFunction( getTaggedString, const char*, 2, 2, "(int tag)") +{ + const char *indexPtr = argv[1]; + if (*indexPtr == StringTagPrefixByte) + indexPtr++; + return gNetStringTable->lookupString(dAtoi(indexPtr)); +} + +ConsoleFunction( buildTaggedString, const char*, 2, 11, "(string format, ...)") +{ + const char *indexPtr = argv[1]; + if (*indexPtr == StringTagPrefixByte) + indexPtr++; + const char *fmtString = gNetStringTable->lookupString(dAtoi(indexPtr)); + char *strBuffer = Con::getReturnBuffer(512); + const char *fmtStrPtr = fmtString; + char *strBufPtr = strBuffer; + S32 strMaxLength = 511; + if (!fmtString) + goto done; + + //build the string + while (*fmtStrPtr) + { + //look for an argument tag + if (*fmtStrPtr == '%') + { + if (fmtStrPtr[1] >= '1' && fmtStrPtr[1] <= '9') + { + S32 argIndex = S32(fmtStrPtr[1] - '0') + 1; + if (argIndex >= argc) + goto done; + const char *argStr = argv[argIndex]; + if (!argStr) + goto done; + S32 strLength = dStrlen(argStr); + if (strLength > strMaxLength) + goto done; + dStrcpy(strBufPtr, argStr); + strBufPtr += strLength; + strMaxLength -= strLength; + fmtStrPtr += 2; + continue; + } + } + + //if we don't continue, just copy the character + if (strMaxLength <= 0) + goto done; + *strBufPtr++ = *fmtStrPtr++; + strMaxLength--; + } + +done: + *strBufPtr = '\0'; + return strBuffer; +} + +ConsoleFunctionGroupEnd( Net ); + +void netInit() +{ + gMasterAddress = ""; + Con::addVariable( "MasterServerAddress", TypeString, &gMasterAddress ); + Con::addVariable( "isDedicated", TypeBool, &gIsDedicated ); +} diff --git a/app/net/netTest.cpp b/app/net/netTest.cpp new file mode 100644 index 0000000..ffefc64 --- /dev/null +++ b/app/net/netTest.cpp @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simBase.h" +#include "platform/event.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "sim/netObject.h" + +class SimpleMessageEvent : public NetEvent +{ + typedef NetEvent Parent; + char *msg; +public: + SimpleMessageEvent(const char *message = NULL) + { + if(message) + msg = dStrdup(message); + else + msg = NULL; + } + ~SimpleMessageEvent() + { dFree(msg); } + + virtual void pack(NetConnection* /*ps*/, BitStream *bstream) + { bstream->writeString(msg); } + virtual void write(NetConnection*, BitStream *bstream) + { bstream->writeString(msg); } + virtual void unpack(NetConnection* /*ps*/, BitStream *bstream) + { char buf[256]; bstream->readString(buf); msg = dStrdup(buf); } + virtual void process(NetConnection *) + { Con::printf("RMSG %d %s", mSourceId, msg); } + + DECLARE_CONOBJECT(SimpleMessageEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent); + +class SimpleNetObject : public NetObject +{ + typedef NetObject Parent; +public: + char message[256]; + SimpleNetObject() + { + mNetFlags.set(ScopeAlways | Ghostable); + dStrcpy(message, "Hello World!"); + } + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream) + { + stream->writeString(message); + return 0; + } + void unpackUpdate(NetConnection *conn, BitStream *stream) + { + stream->readString(message); + Con::printf("Got message: %s", message); + } + void setMessage(const char *msg) + { + setMaskBits(1); + dStrcpy(message, msg); + } + + DECLARE_CONOBJECT(SimpleNetObject); +}; + +IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject); + +ConsoleMethod( SimpleNetObject, setMessage, void, 3, 3, "(string msg)") +{ + object->setMessage(argv[2]); +} + +ConsoleFunction( msg, void, 3, 3, "(NetConnection id, string message)" + "Send a SimpleNetObject message to the specified connection.") +{ + NetConnection *con = (NetConnection *) Sim::findObject(argv[1]); + if(con) + con->postNetEvent(new SimpleMessageEvent(argv[2])); +} diff --git a/app/net/serverQuery.cpp b/app/net/serverQuery.cpp new file mode 100644 index 0000000..92383f3 --- /dev/null +++ b/app/net/serverQuery.cpp @@ -0,0 +1,2073 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Server Query States: +// 1: Master server query status -> wait for master response +// 2: Master server packet status -> wait for master packets to arrive +// 3: Server ping status -> wait for servers to respond to pings +// 4: Server query status -> wait for servers to respond to queries +// 5: Done + +// Master Server Packets: +// Header +// message Message id +// flags Query flags +// sequenceNumber Packet sequence id + +// Server Query Filter Packet +// packetIndex Request specific page # (rest empty) +// gameType Game type string +// missionType Mission type string +// minPlayers At least this many players +// maxPlayers No more than this many +// regions Region mask, 0 = all +// version Server version, 0 = any +// filterFlags Server flags (dedicated, etc), 0 = any +// maxBots No more than maxBots +// minCPUSpeed At least this fast +// playerCount Buddy list search +// playerList[playerCount] + +// Master Server Info Packet +// gameType Game type string +// missionType Mission type string +// maxPlayers Max allowed +// regions Region mask +// version Server version # +// infoFlags Server flags (dedicated, etc) +// numBots Current bot count +// CPUSpeed Server CPU speed +// playerCount Current player count +// playerList[playerCount] + +// Game Info Query Packet +// gameType Game type string +// missionType Mission type string +// missionName You get one guess... +// satusFlags Dedicated, etc. +// playerCount Current player count +// maxPlayers Max allowed +// numBots Current bot count +// CPUSpeed Server CPU speed +// statusString Server info message +// statusString Server status message + +// Accessed Environment Vars +// Server::MissionType +// Server::MissionName +// Server::GameType +// Server::ServerType +// Server::PlayerCount +// Server::BotCount +// Server::GuidList[playerCount] +// Server::Dedicated +// Server::Status +// Pref::Server::Name +// Pref::Server::Password +// Pref::Server::Info +// Pref::Server::MaxPlayers +// Pref::Server::RegionMask +// Pref::Net::RegionMask +// Pref::Client::Master[n] +// Pref::Client::ServerFavoriteCount +// Pref::Client::ServerFavorite[ServerFavoriteCount] +//----------------------------------------------------------------------------- + +#include "app/net/serverQuery.h" + +#include "platform/platform.h" +#include "platform/event.h" +#include "core/dnet.h" +#include "core/util/tVector.h" +#include "core/stream/bitStream.h" +#include "console/console.h" +#include "console/simBase.h" +#include "app/banList.h" +#include "app/auth.h" +#include "sim/netConnection.h" +#include "sim/netInterface.h" + +// cafTODO: breaks T2D +#include "T3D/gameConnection.h" + +// This is basically the server query protocol version now: +static const char* versionString = "VER1"; + +struct MasterInfo +{ + NetAddress address; + U32 region; +}; + +Vector gServerList(__FILE__, __LINE__); +static Vector gMasterServerList(__FILE__, __LINE__); +static Vector gFinishedList(__FILE__, __LINE__); // timed out servers and finished servers go here +NetAddress gMasterServerQueryAddress; +bool gServerBrowserDirty = false; + +static const U32 gHeartbeatInterval = 120000; +static const S32 gMasterServerRetryCount = 3; +static const S32 gMasterServerTimeout = 2000; +static const S32 gPacketRetryCount = 4; +static const S32 gPacketTimeout = 1000; +static const S32 gMaxConcurrentPings = 10; +static const S32 gMaxConcurrentQueries = 2; +static const S32 gPingRetryCount = 4; +static const S32 gPingTimeout = 800; +static const S32 gQueryRetryCount = 4; +static const S32 gQueryTimeout = 1000; + +// State variables: +static bool sgServerQueryActive = false; +static S32 gPingSession = 0; +static S32 gKey = 0; +static bool gGotFirstListPacket = false; + +// Variables used for the interface: +static U32 gServerPingCount = 0; +static U32 gServerQueryCount = 0; +static U32 gHeartbeatSeq = 0; + +class DemoNetInterface : public NetInterface +{ +public: + void handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream); +}; + +DemoNetInterface gNetInterface; + +ConsoleFunctionGroupBegin( ServerQuery, "Functions which allow you to query the LAN or a master server for online games."); + +//----------------------------------------------------------------------------- + +struct Ping +{ + NetAddress address; + S32 session; + S32 key; + U32 time; + U32 tryCount; + bool broadcast; +}; + +static Ping gMasterServerPing; +static Vector gPingList(__FILE__, __LINE__); +static Vector gQueryList(__FILE__, __LINE__); + +//----------------------------------------------------------------------------- + +struct PacketStatus +{ + U8 index; + S32 key; + U32 time; + U32 tryCount; + + PacketStatus() : index( 0 ), key(-1), time(0), tryCount( gPacketRetryCount ) {}; + + PacketStatus( U8 _index, S32 _key, U32 _time ) + { + index = _index; + key = _key; + time = _time; + tryCount = gPacketRetryCount; + } +}; + +static Vector gPacketStatusList(__FILE__, __LINE__); + +//----------------------------------------------------------------------------- + +struct ServerFilter +{ + enum Type + { + Normal = 0, + Buddy = 1, + Offline = 2, + Favorites = 3, + }; + + Type type; + char* gameType; + char* missionType; + + enum // Query Flags + { + OnlineQuery = 0, // Authenticated with master + OfflineQuery = BIT(0), // On our own + NoStringCompress = BIT(1), + }; + + enum // Filter flags: + { + Dedicated = BIT(0), + NotPassworded = BIT(1), + Linux = BIT(2), + CurrentVersion = BIT(7), + }; + + U8 queryFlags; + U8 minPlayers; + U8 maxPlayers; + U8 maxBots; + U32 regionMask; + U32 maxPing; + U8 filterFlags; + U16 minCPU; + U8 buddyCount; + U32* buddyList; + + ServerFilter() + { + queryFlags = 0; + gameType = NULL; + missionType = NULL; + minPlayers = 0; + maxPlayers = 255; + maxBots = 16; + regionMask = 0xFFFFFFFF; + maxPing = 0; + filterFlags = 0; + minCPU = 0; + buddyCount = 0; + buddyList = NULL; + } + + ~ServerFilter() + { + if ( gameType ) + dFree( gameType ); + if ( missionType ) + dFree( missionType ); + if ( buddyList ) + dFree( buddyList ); + } +}; + +static ServerFilter sActiveFilter; + + +//----------------------------------------------------------------------------- +// Forward function declarations: +//----------------------------------------------------------------------------- + +static void pushPingRequest( const NetAddress *addr ); +static void pushPingBroadcast( const NetAddress *addr ); +static void pushServerFavorites(); +static bool pickMasterServer(); +static S32 findPingEntry( Vector &v, const NetAddress* addr ); +static bool addressFinished( const NetAddress* addr ); +static ServerInfo* findServerInfo( const NetAddress* addr ); +static ServerInfo* findOrCreateServerInfo( const NetAddress* addr ); +static void removeServerInfo( const NetAddress* addr ); +static void sendPacket( U8 pType, const NetAddress* addr, U32 key, U32 session, U8 flags ); +static void writeCString( BitStream* stream, const char* string ); +static void readCString( BitStream* stream, char* buffer ); +static void writeLongCString( BitStream* stream, const char* string ); +static void readLongCString( BitStream* stream, char* buffer ); +static void processMasterServerQuery( U32 session ); +static void processPingsAndQueries( U32 session, bool schedule = true); +static void processServerListPackets( U32 session ); +static void processHeartbeat(U32); +static void updatePingProgress(); +static void updateQueryProgress(); +Vector* getMasterServerList(); +bool pickMasterServer(); +void clearServerList(); + + +//----------------------------------------------------------------------------- +// Events +//----------------------------------------------------------------------------- + +//---------------------------------------------------------------- +class ProcessMasterQueryEvent : public SimEvent +{ + U32 session; + public: + ProcessMasterQueryEvent( U32 _session ) + { + session = _session; + } + void process( SimObject *object ) + { + processMasterServerQuery( session ); + } +}; + +//---------------------------------------------------------------- +class ProcessPingEvent : public SimEvent +{ + U32 session; + public: + ProcessPingEvent( U32 _session ) + { + session = _session; + } + void process( SimObject *object ) + { + processPingsAndQueries( session ); + } +}; + +//---------------------------------------------------------------- +class ProcessPacketEvent : public SimEvent +{ + U32 session; + public: + ProcessPacketEvent( U32 _session ) + { + session = _session; + } + + void process( SimObject *object ) + { + processServerListPackets( session ); + } +}; + +//---------------------------------------------------------------- +class HeartbeatEvent : public SimEvent +{ + U32 mSeq; + public: + HeartbeatEvent(U32 seq) + { + mSeq = seq; + } + void process( SimObject *object ) + { + processHeartbeat(mSeq); + } +}; + + +//----------------------------------------------------------------------------- +// Public query methods +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +void queryLanServers(U32 port, U8 flags, const char* gameType, const char* missionType, + U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU, + U8 filterFlags) +{ + sgServerQueryActive = true; + clearServerList(); + pushServerFavorites(); + + sActiveFilter.type = ServerFilter::Offline; + + // Clear the filter: + if ( !sActiveFilter.gameType || dStricmp( sActiveFilter.gameType, "Any" ) != 0 ) + { + sActiveFilter.gameType = (char*) dRealloc( sActiveFilter.gameType, 4 ); + dStrcpy( sActiveFilter.gameType, "Any" ); + } + if ( !sActiveFilter.missionType || dStricmp( sActiveFilter.missionType, "Any" ) != 0 ) + { + sActiveFilter.missionType = (char*) dRealloc( sActiveFilter.missionType, 4 ); + dStrcpy( sActiveFilter.missionType, "Any" ); + } + sActiveFilter.queryFlags = 0; + sActiveFilter.minPlayers = minPlayers; + sActiveFilter.maxPlayers = maxPlayers; + sActiveFilter.maxBots = maxBots; + sActiveFilter.regionMask = regionMask; + sActiveFilter.maxPing = maxPing; + sActiveFilter.minCPU = minCPU; + sActiveFilter.filterFlags = filterFlags; + + NetAddress addr; + char addrText[256]; + dSprintf( addrText, sizeof( addrText ), "IP:BROADCAST:%d", port ); + Net::stringToAddress( addrText, &addr ); + pushPingBroadcast( &addr ); + + Con::executef("onServerQueryStatus", "start", "Querying LAN servers", "0"); + processPingsAndQueries( gPingSession ); +} + +//----------------------------------------------------------------------------- +ConsoleFunction( queryLanServers, void, 12, 12, "queryLanServers(...);" ) +{ + TORQUE_UNUSED(argc); + + U32 lanPort = dAtoi(argv[1]); + U8 flags = dAtoi(argv[2]); + + // It's not a good idea to hold onto args, recursive calls to + // console exec will trash them. + char* gameType = dStrdup(argv[3]); + char* missionType = dStrdup(argv[4]); + + U8 minPlayers = dAtoi(argv[5]); + U8 maxPlayers = dAtoi(argv[6]); + U8 maxBots = dAtoi(argv[7]); + U32 regionMask = dAtoi(argv[8]); + U32 maxPing = dAtoi(argv[9]); + U16 minCPU = dAtoi(argv[10]); + U8 filterFlags = dAtoi(argv[11]); + + queryLanServers(lanPort, flags, gameType, missionType, minPlayers, maxPlayers, maxBots, + regionMask, maxPing, minCPU, filterFlags); + + dFree(gameType); + dFree(missionType); +} + +//----------------------------------------------------------------------------- + +void queryMasterGameTypes() +{ + Vector *masterList = getMasterServerList(); + if ( masterList->size() != 0 ) + { + U32 master = Sim::getCurrentTime() % masterList->size(); + // Send a request to the master server for the game types: + Con::printf( "Requesting game types from the master server..." ); + sendPacket( NetInterface::MasterServerGameTypesRequest, &(*masterList)[master].address, gKey, gPingSession, 0 ); + } +} + +//----------------------------------------------------------------------------- + +void queryMasterServer(U8 flags, const char* gameType, const char* missionType, + U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, + U16 minCPU, U8 filterFlags, U8 buddyCount, U32* buddyList ) +{ + // Reset the list packet flag: + gGotFirstListPacket = false; + sgServerQueryActive = true; + clearServerList(); + + Con::executef( "onServerQueryStatus", "start", "Querying master server", "0"); + + if ( buddyCount == 0 ) + { + sActiveFilter.type = ServerFilter::Normal; + + // Update the active filter: + if ( !sActiveFilter.gameType || dStrcmp( sActiveFilter.gameType, gameType ) != 0 ) + { + sActiveFilter.gameType = (char*) dRealloc( sActiveFilter.gameType, dStrlen( gameType ) + 1 ); + dStrcpy( sActiveFilter.gameType, gameType ); + } + + if ( !sActiveFilter.missionType || dStrcmp( sActiveFilter.missionType, missionType ) != 0 ) + { + sActiveFilter.missionType = (char*) dRealloc( sActiveFilter.missionType, dStrlen( missionType ) + 1 ); + dStrcpy( sActiveFilter.missionType, missionType ); + } + + sActiveFilter.queryFlags = flags; + sActiveFilter.minPlayers = minPlayers; + sActiveFilter.maxPlayers = maxPlayers; + sActiveFilter.maxBots = maxBots; + sActiveFilter.regionMask = regionMask; + sActiveFilter.maxPing = maxPing; + sActiveFilter.minCPU = minCPU; + sActiveFilter.filterFlags = filterFlags; + sActiveFilter.buddyCount = buddyCount; + dFree( sActiveFilter.buddyList ); + sActiveFilter.buddyList = NULL; + } + else + { + sActiveFilter.type = ServerFilter::Buddy; + sActiveFilter.buddyCount = buddyCount; + sActiveFilter.buddyList = (U32*) dRealloc( sActiveFilter.buddyList, buddyCount * 4 ); + dMemcpy( sActiveFilter.buddyList, buddyList, buddyCount * 4 ); + clearServerList(); + } + + // Pick a random master server from the list: + gMasterServerList.clear(); + Vector *masterList = getMasterServerList(); + for ( U32 i = 0; i < masterList->size(); i++ ) + gMasterServerList.push_back( (*masterList)[i] ); + + // Clear the master server ping: + gMasterServerPing.time = 0; + gMasterServerPing.tryCount = gMasterServerRetryCount; + + if ( !pickMasterServer() ) + Con::errorf( "No master servers found!" ); + else + processMasterServerQuery( gPingSession ); +} + +ConsoleFunction( queryMasterServer, void, 11, 11, "queryMasterServer(...);" ) +{ + TORQUE_UNUSED(argc); + + U8 flags = dAtoi(argv[1]); + + // It's not a good idea to hold onto args, recursive calls to + // console exec will trash them. + char* gameType = dStrdup(argv[2]); + char* missionType = dStrdup(argv[3]); + + U8 minPlayers = dAtoi(argv[4]); + U8 maxPlayers = dAtoi(argv[5]); + U8 maxBots = dAtoi(argv[6]); + U32 regionMask = dAtoi(argv[7]); + U32 maxPing = dAtoi(argv[8]); + U16 minCPU = dAtoi(argv[9]); + U8 filterFlags = dAtoi(argv[10]); + U32 buddyList = 0; + + queryMasterServer(flags,gameType,missionType,minPlayers,maxPlayers, + maxBots,regionMask,maxPing,minCPU,filterFlags,0,&buddyList); + + dFree(gameType); + dFree(missionType); +} + +//----------------------------------------------------------------------------- + +ConsoleFunction( querySingleServer, void, 3, 3, "querySingleServer(address, flags);" ) +{ + TORQUE_UNUSED(argc); + + NetAddress addr; + char* addrText; + + addrText = dStrdup(argv[1]); + U8 flags = dAtoi(argv[2]); + + + Net::stringToAddress( addrText, &addr ); + querySingleServer(&addr,flags); +} + +//----------------------------------------------------------------------------- + +void queryFavoriteServers( U8 /*flags*/ ) +{ + sgServerQueryActive = true; + clearServerList(); + sActiveFilter.type = ServerFilter::Favorites; + pushServerFavorites(); + + Con::executef( "onServerQueryStatus", "start", "Query favorites...", "0" ); + processPingsAndQueries( gPingSession ); +} + +//----------------------------------------------------------------------------- + +void querySingleServer( const NetAddress* addr, U8 /*flags*/ ) +{ + sgServerQueryActive = true; + ServerInfo* si = findServerInfo( addr ); + if ( si ) + si->status = ServerInfo::Status_New | ServerInfo::Status_Updating; + + // Remove the server from the finished list (if it's there): + for ( U32 i = 0; i < gFinishedList.size(); i++ ) + { + if ( Net::compareAddresses( addr, &gFinishedList[i] ) ) + { + gFinishedList.erase( i ); + break; + } + } + + Con::executef( "onServerQueryStatus", "start", "Refreshing server...", "0" ); + gServerPingCount = gServerQueryCount = 0; + pushPingRequest( addr ); + processPingsAndQueries( gPingSession ); +} + +//----------------------------------------------------------------------------- + +void cancelServerQuery() +{ + // Cancel the current query, if there is anything left + // on the ping list, it's dropped. + if ( sgServerQueryActive ) + { + Con::printf( "Server query canceled." ); + ServerInfo* si; + + // Clear the master server packet list: + gPacketStatusList.clear(); + + // Clear the ping list: + while ( gPingList.size() ) + { + si = findServerInfo( &gPingList[0].address ); + if ( si && !si->status.test( ServerInfo::Status_Responded ) ) + si->status = ServerInfo::Status_TimedOut; + + gPingList.erase( U32( 0 ) ); + } + + // Clear the query list: + while ( gQueryList.size() ) + { + si = findServerInfo( &gQueryList[0].address ); + if ( si && !si->status.test( ServerInfo::Status_Responded ) ) + si->status = ServerInfo::Status_TimedOut; + + gQueryList.erase( U32( 0 ) ); + } + + sgServerQueryActive = false; + gServerBrowserDirty = true; + } +} + +ConsoleFunction( cancelServerQuery, void, 1, 1, "cancelServerQuery()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + cancelServerQuery(); +} + +//----------------------------------------------------------------------------- + +void stopServerQuery() +{ + // Cancel the current query, anything left on the ping + // list is moved to the finished list as "done". + if ( sgServerQueryActive ) + { + gPacketStatusList.clear(); + + if ( gPingList.size() ) + { + while ( gPingList.size() ) + { + gFinishedList.push_back( gPingList[0].address ); + gPingList.erase( U32( 0 ) ); + } + } + else + cancelServerQuery(); + } +} + +ConsoleFunction( stopServerQuery, void, 1, 1, "stopServerQuery()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + stopServerQuery(); +} + +//----------------------------------------------------------------------------- + +ConsoleFunction(startHeartbeat, void, 1, 1, "startHeartbeat()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + + if (validateAuthenticatedServer()) { + gHeartbeatSeq++; + processHeartbeat(gHeartbeatSeq); // thump-thump... + } +} + +ConsoleFunction(stopHeartbeat, void, 1, 1, "stopHeartbeat();") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + gHeartbeatSeq++; +} + +//----------------------------------------------------------------------------- + +ConsoleFunction( getServerCount, int, 1, 1, "getServerCount();" ) +{ + TORQUE_UNUSED(argv); TORQUE_UNUSED(argc); + return gServerList.size(); +} + +ConsoleFunction( setServerInfo, bool, 2, 2, "setServerInfo(index);" ) +{ + TORQUE_UNUSED(argc); + U32 index = dAtoi(argv[1]); + if (index >= 0 && index < gServerList.size()) { + ServerInfo& info = gServerList[index]; + + char addrString[256]; + Net::addressToString( &info.address, addrString ); + + Con::setIntVariable("ServerInfo::Status",info.status); + Con::setVariable("ServerInfo::Address",addrString); + Con::setVariable("ServerInfo::Name",info.name); + Con::setVariable("ServerInfo::GameType",info.gameType); + Con::setVariable("ServerInfo::MissionName",info.missionName); + Con::setVariable("ServerInfo::MissionType",info.missionType); + Con::setVariable("ServerInfo::State",info.statusString); + Con::setVariable("ServerInfo::Info",info.infoString); + Con::setIntVariable("ServerInfo::PlayerCount",info.numPlayers); + Con::setIntVariable("ServerInfo::MaxPlayers",info.maxPlayers); + Con::setIntVariable("ServerInfo::BotCount",info.numBots); + Con::setIntVariable("ServerInfo::Version",info.version); + Con::setIntVariable("ServerInfo::Ping",info.ping); + Con::setIntVariable("ServerInfo::CPUSpeed",info.cpuSpeed); + Con::setBoolVariable("ServerInfo::Favorite",info.isFavorite); + Con::setBoolVariable("ServerInfo::Dedicated",info.isDedicated()); + Con::setBoolVariable("ServerInfo::Password",info.isPassworded()); + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +// Internal +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +ServerInfo::~ServerInfo() +{ + if ( name ) + dFree( name ); + if ( gameType ) + dFree( gameType ); + if ( missionType ) + dFree( missionType ); + if ( statusString ) + dFree( statusString ); + if ( infoString ) + dFree( infoString ); +} + +//----------------------------------------------------------------------------- + +Vector* getMasterServerList() +{ + // This code used to get the master server list from the + // WON.net directory. + static Vector masterList; + masterList.clear(); + + for (U32 i = 0; i < 10; i++) { + char buffer[50]; + dSprintf(buffer,sizeof(buffer),"Pref::Master%d",i); + const char* master = Con::getVariable(buffer); + if (master && *master) { + NetAddress address; + // Format for master server variable: + // regionMask:netAddress + U32 region = 1; // needs to default to something > 0 + dSscanf(master,"%d:",®ion); + const char* madd = dStrchr(master,':') + 1; + if (region && Net::stringToAddress(madd,&address)) { + masterList.increment(); + MasterInfo& info = masterList.last(); + info.address = address; + info.region = region; + } + else + Con::errorf("Bad master server address: %s",master); + } + } + + if (!masterList.size()) + Con::errorf("No master servers found"); + + return &masterList; +} + + +//----------------------------------------------------------------------------- + +bool pickMasterServer() +{ + // Reset the master server ping: + gMasterServerPing.time = 0; + gMasterServerPing.key = 0; + gMasterServerPing.tryCount = gMasterServerRetryCount; + gMasterServerPing.session = gPingSession; + + char addrString[256]; + U32 serverCount = gMasterServerList.size(); + if ( !serverCount ) + { + // There are no more servers left to try...:( + return( false ); + } + + U32 region = Con::getIntVariable( "$pref::Net::RegionMask" ); + U32 index = Sim::getCurrentTime() % serverCount; + + // First try to find a master server in the same region: + for ( U32 i = 0; i < serverCount; i++ ) + { + if ( gMasterServerList[index].region == region ) + { + Net::addressToString( &gMasterServerList[index].address, addrString ); + Con::printf( "Found master server %s in same region.", addrString ); + gMasterServerPing.address = gMasterServerList[index].address; + return( true ); + } + + index = index < serverCount - 1 ? index + 1 : 0; + } + + // Settle for the one we first picked: + Net::addressToString( &gMasterServerList[index].address, addrString ); + Con::printf( "No master servers found in this region, trying %s.", addrString ); + gMasterServerPing.address = gMasterServerList[index].address; + + return( true ); +} + +//----------------------------------------------------------------------------- + +void clearServerList() +{ + gPacketStatusList.clear(); + gServerList.clear(); + gFinishedList.clear(); + gPingList.clear(); + gQueryList.clear(); + gServerPingCount = gServerQueryCount = 0; + + gPingSession++; +} + +//----------------------------------------------------------------------------- + +void sendHeartbeat( U8 flags ) +{ + // send heartbeats to all of the master servers: + Vector *masterList = getMasterServerList(); + for(U32 i = 0; i < masterList->size(); i++) + { + char buffer[256]; + Net::addressToString(&(*masterList)[i].address, buffer); + // Send a request to the master server for the game types: + Con::printf( "Sending heartbeat to master server [%s]", buffer ); + sendPacket( NetInterface::GameHeartbeat, &(*masterList)[i].address, 0, gPingSession, flags ); + } +} + +//----------------------------------------------------------------------------- + +static void pushPingRequest( const NetAddress* addr ) +{ + if( addressFinished( addr ) ) + return; + + Ping p; + p.address = *addr; + p.session = gPingSession; + p.key = 0; + p.time = 0; + p.tryCount = gPingRetryCount; + p.broadcast = false; + gPingList.push_back( p ); + gServerPingCount++; +} + +//----------------------------------------------------------------------------- + +static void pushPingBroadcast( const NetAddress* addr ) +{ + if( addressFinished( addr ) ) + return; + + Ping p; + p.address = *addr; + p.session = gPingSession; + p.key = 0; + p.time = 0; + p.tryCount = 1; // only try this once + p.broadcast = true; + gPingList.push_back( p ); + // Don't increment gServerPingCount, broadcasts are not + // counted as requests. +} + +//----------------------------------------------------------------------------- + +static S32 countPingRequests() +{ + // Need a function here because the ping list also includes + // broadcast pings we don't want counted. + U32 pSize = gPingList.size(), count = pSize; + for (U32 i = 0; i < pSize; i++) + if (gPingList[i].broadcast) + count--; + return count; +} + + +//----------------------------------------------------------------------------- + +static void pushServerFavorites() +{ + S32 count = Con::getIntVariable( "$pref::Client::ServerFavoriteCount" ); + if ( count < 0 ) + { + Con::setIntVariable( "$pref::Client::ServerFavoriteCount", 0 ); + return; + } + + NetAddress addr; + const char* server = NULL; + char buf[256], serverName[25], addrString[256]; + U32 sz, len; + for ( S32 i = 0; i < count; i++ ) + { + dSprintf( buf, sizeof( buf ), "Pref::Client::ServerFavorite%d", i ); + server = Con::getVariable( buf ); + if ( server ) + { + sz = dStrcspn( server, "\t" ); + if ( sz > 0 ) + { + len = sz > 24 ? 24 : sz; + dStrncpy( serverName, server, len ); + serverName[len] = 0; + dStrncpy( addrString, server + ( sz + 1 ), 255 ); + + //Con::errorf( "Pushing server favorite \"%s\" - %s...", serverName, addrString ); + Net::stringToAddress( addrString, &addr ); + ServerInfo* si = findOrCreateServerInfo( &addr ); + AssertFatal(si, "pushServerFavorites - failed to create Server Info!" ); + si->name = (char*) dRealloc( (void*) si->name, dStrlen( serverName ) + 1 ); + dStrcpy( si->name, serverName ); + si->isFavorite = true; + pushPingRequest( &addr ); + } + } + } +} + +//----------------------------------------------------------------------------- + +static S32 findPingEntry( Vector &v, const NetAddress* addr ) +{ + for ( U32 i = 0; i < v.size(); i++ ) + if ( Net::compareAddresses( addr, &v[i].address ) ) + return S32( i ); + return -1; +} + +//----------------------------------------------------------------------------- + +static bool addressFinished( const NetAddress* addr ) +{ + for ( U32 i = 0; i < gFinishedList.size(); i++ ) + if ( Net::compareAddresses( addr, &gFinishedList[i] ) ) + return true; + return false; +} + +//----------------------------------------------------------------------------- + +static ServerInfo* findServerInfo( const NetAddress* addr ) +{ + for ( U32 i = 0; i < gServerList.size(); i++ ) + if ( Net::compareAddresses( addr, &gServerList[i].address ) ) + return &gServerList[i]; + return NULL; +} + +//----------------------------------------------------------------------------- + +static ServerInfo* findOrCreateServerInfo( const NetAddress* addr ) +{ + ServerInfo* ret = findServerInfo( addr ); + if ( ret ) + return ret; + + ServerInfo si; + si.address = *addr; + gServerList.push_back( si ); + + return &gServerList.last(); +} + +//----------------------------------------------------------------------------- + +static void removeServerInfo( const NetAddress* addr ) +{ + for ( U32 i = 0; i < gServerList.size(); i++ ) + { + if ( Net::compareAddresses( addr, &gServerList[i].address ) ) + { + gServerList.erase( i ); + gServerBrowserDirty = true; + } + } +} + +//----------------------------------------------------------------------------- + +#if defined(TORQUE_DEBUG) +// This function is solely for testing the functionality of the server browser +// with more servers in the list. +void addFakeServers( S32 howMany ) +{ + static S32 sNumFakeServers = 1; + ServerInfo newServer; + + for ( S32 i = 0; i < howMany; i++ ) + { + newServer.numPlayers = (U8)(Platform::getRandom() * 64.0f); + newServer.maxPlayers = 64; + char buf[256]; + dSprintf( buf, 255, "Fake server #%d", sNumFakeServers ); + newServer.name = (char*) dMalloc( dStrlen( buf ) + 1 ); + dStrcpy( newServer.name, buf ); + newServer.gameType = (char*) dMalloc( 5 ); + dStrcpy( newServer.gameType, "Fake" ); + newServer.missionType = (char*) dMalloc( 4 ); + dStrcpy( newServer.missionType, "FakeMissionType" ); + newServer.missionName = (char*) dMalloc( 14 ); + dStrcpy( newServer.missionName, "FakeMapName" ); + Net::stringToAddress( "IP:198.74.33.35:28000", &newServer.address ); + newServer.ping = (U32)( Platform::getRandom() * 200.0f ); + newServer.cpuSpeed = 470; + newServer.status = ServerInfo::Status_Responded; + + gServerList.push_back( newServer ); + sNumFakeServers++; + } + + gServerBrowserDirty = true; +} +#endif // DEBUG + +//----------------------------------------------------------------------------- + +static void sendPacket( U8 pType, const NetAddress* addr, U32 key, U32 session, U8 flags ) +{ + BitStream *out = BitStream::getPacketStream(); + out->clearStringBuffer(); + out->write( pType ); + out->write( flags ); + out->write( U32( ( session << 16 ) | ( key & 0xFFFF ) ) ); + + BitStream::sendPacketStream(addr); +} + +//----------------------------------------------------------------------------- + +static void writeCString( BitStream* stream, const char* string ) +{ + U8 strLen = ( string != NULL ) ? dStrlen( string ) : 0; + stream->write( strLen ); + for ( U32 i = 0; i < strLen; i++ ) + stream->write( U8( string[i] ) ); +} + +//----------------------------------------------------------------------------- + +static void readCString( BitStream* stream, char* buffer ) +{ + U32 i; + U8 strLen; + stream->read( &strLen ); + for ( i = 0; i < strLen; i++ ) + { + U8* ptr = (U8*) buffer; + stream->read( &ptr[i] ); + } + buffer[i] = 0; +} + +//----------------------------------------------------------------------------- + +static void writeLongCString( BitStream* stream, const char* string ) +{ + U16 strLen = ( string != NULL ) ? dStrlen( string ) : 0; + stream->write( strLen ); + for ( U32 i = 0; i < strLen; i++ ) + stream->write( U8( string[i] ) ); +} + +//----------------------------------------------------------------------------- + +static void readLongCString( BitStream* stream, char* buffer ) +{ + U32 i; + U16 strLen; + stream->read( &strLen ); + for ( i = 0; i < strLen; i++ ) + { + U8* ptr = (U8*) buffer; + stream->read( &ptr[i] ); + } + buffer[i] = 0; +} + +//----------------------------------------------------------------------------- +// Event processing +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +static void processMasterServerQuery( U32 session ) +{ + if ( session != gPingSession || !sgServerQueryActive ) + return; + + if ( !gGotFirstListPacket ) + { + bool keepGoing = true; + U32 time = Platform::getVirtualMilliseconds(); + char addressString[256]; + + if ( gMasterServerPing.time + gMasterServerTimeout < time ) + { + Net::addressToString( &gMasterServerPing.address, addressString ); + if ( !gMasterServerPing.tryCount ) + { + // The query timed out. + Con::printf( "Server list request to %s timed out.", addressString ); + + // Remove this server from the list: + for ( U32 i = 0; i < gMasterServerList.size(); i++ ) + { + if ( Net::compareAddresses( &gMasterServerList[i].address, &gMasterServerPing.address ) ) + { + gMasterServerList.erase( i ); + break; + } + } + + // Pick a new master server to try: + keepGoing = pickMasterServer(); + if ( keepGoing ) + { + Con::executef( "onServerQueryStatus", "update", "Switching master servers...", "0" ); + Net::addressToString( &gMasterServerPing.address, addressString ); + } + } + + if ( keepGoing ) + { + gMasterServerPing.tryCount--; + gMasterServerPing.time = time; + gMasterServerPing.key = gKey++; + + // Send a request to the master server for the server list: + BitStream *out = BitStream::getPacketStream(); + out->clearStringBuffer(); + out->write( U8( NetInterface::MasterServerListRequest ) ); + out->write( U8( sActiveFilter.queryFlags) ); + out->write( ( gMasterServerPing.session << 16 ) | ( gMasterServerPing.key & 0xFFFF ) ); + out->write( U8( 255 ) ); + writeCString( out, sActiveFilter.gameType ); + writeCString( out, sActiveFilter.missionType ); + out->write( sActiveFilter.minPlayers ); + out->write( sActiveFilter.maxPlayers ); + out->write( sActiveFilter.regionMask ); + U32 version = ( sActiveFilter.filterFlags & ServerFilter::CurrentVersion ) ? getVersionNumber() : 0; + out->write( version ); + out->write( sActiveFilter.filterFlags ); + out->write( sActiveFilter.maxBots ); + out->write( sActiveFilter.minCPU ); + out->write( sActiveFilter.buddyCount ); + for ( U32 i = 0; i < sActiveFilter.buddyCount; i++ ) + out->write( sActiveFilter.buddyList[i] ); + + BitStream::sendPacketStream( &gMasterServerPing.address ); + + Con::printf( "Requesting the server list from master server %s (%d tries left)...", addressString, gMasterServerPing.tryCount ); + if ( gMasterServerPing.tryCount < gMasterServerRetryCount - 1 ) + Con::executef( "onServerQueryStatus", "update", "Retrying the master server...", "0" ); + } + } + + if ( keepGoing ) + { + // schedule another check: + Sim::postEvent( Sim::getRootGroup(), new ProcessMasterQueryEvent( session ), Sim::getTargetTime() + 1 ); + } + else + { + Con::errorf( "There are no more master servers to try!" ); + Con::executef( "onServerQueryStatus", "done", "No master servers found.", "0" ); + } + } +} + +//----------------------------------------------------------------------------- + +static void processPingsAndQueries( U32 session, bool schedule ) +{ + if( session != gPingSession ) + return; + + U32 i = 0; + U32 time = Platform::getVirtualMilliseconds(); + char addressString[256]; + U8 flags = ServerFilter::OnlineQuery; + bool waitingForMaster = ( sActiveFilter.type == ServerFilter::Normal ) && !gGotFirstListPacket && sgServerQueryActive; + + for ( i = 0; i < gPingList.size() && i < gMaxConcurrentPings; ) + { + Ping &p = gPingList[i]; + + if ( p.time + gPingTimeout < time ) + { + if ( !p.tryCount ) + { + // it's timed out. + if (!p.broadcast) + { + Net::addressToString( &p.address, addressString ); + Con::printf( "Ping to server %s timed out.", addressString ); + } + + // If server info is in list (favorite), set its status: + ServerInfo* si = findServerInfo( &p.address ); + if ( si ) + { + si->status = ServerInfo::Status_TimedOut; + gServerBrowserDirty = true; + } + + gFinishedList.push_back( p.address ); + gPingList.erase( i ); + + if ( !waitingForMaster ) + updatePingProgress(); + } + else + { + p.tryCount--; + p.time = time; + p.key = gKey++; + + Net::addressToString( &p.address, addressString ); + + if (p.broadcast) + Con::printf( "LAN server ping: %s...", addressString ); + else + Con::printf( "Pinging Server %s (%d)...", addressString, p.tryCount ); + sendPacket( NetInterface::GamePingRequest, &p.address, p.key, p.session, flags ); + i++; + } + } + else + i++; + } + + if ( !gPingList.size() && !waitingForMaster ) + { + // Start the query phase: + for ( U32 i = 0; i < gQueryList.size() && i < gMaxConcurrentQueries; ) + { + Ping &p = gQueryList[i]; + if ( p.time + gPingTimeout < time ) + { + ServerInfo* si = findServerInfo( &p.address ); + if ( !si ) + { + // Server info not found, so remove the query: + gQueryList.erase( i ); + gServerBrowserDirty = true; + continue; + } + + Net::addressToString( &p.address, addressString ); + if ( !p.tryCount ) + { + Con::printf( "Query to server %s timed out.", addressString ); + si->status = ServerInfo::Status_TimedOut; + gQueryList.erase( i ); + gServerBrowserDirty = true; + } + else + { + p.tryCount--; + p.time = time; + p.key = gKey++; + + Con::printf( "Querying Server %s (%d)...", addressString, p.tryCount ); + sendPacket( NetInterface::GameInfoRequest, &p.address, p.key, p.session, flags ); + if ( !si->isQuerying() ) + { + si->status |= ServerInfo::Status_Querying; + gServerBrowserDirty = true; + } + i++; + } + } + else + i++; + } + } + + if ( gPingList.size() || gQueryList.size() || waitingForMaster ) + { + // The LAN query function doesn't always want to schedule + // the next ping. + if (schedule) + Sim::postEvent( Sim::getRootGroup(), new ProcessPingEvent( session ), Sim::getTargetTime() + 1 ); + } + else + { + // All done! + char msg[64]; + U32 foundCount = gServerList.size(); + if ( foundCount == 0 ) + dStrcpy( msg, "No servers found." ); + else if ( foundCount == 1 ) + dStrcpy( msg, "One server found." ); + else + dSprintf( msg, sizeof( msg ), "%d servers found.", foundCount ); + + Con::executef( "onServerQueryStatus", "done", msg, "1"); + } +} + +//----------------------------------------------------------------------------- + +static void processServerListPackets( U32 session ) +{ + if ( session != gPingSession || !sgServerQueryActive ) + return; + + U32 currentTime = Platform::getVirtualMilliseconds(); + + // Loop through the packet status list and resend packet requests where necessary: + for ( U32 i = 0; i < gPacketStatusList.size(); i++ ) + { + PacketStatus &p = gPacketStatusList[i]; + if ( p.time + gPacketTimeout < currentTime ) + { + if ( !p.tryCount ) + { + // Packet timed out :( + Con::printf( "Server list packet #%d timed out.", p.index + 1 ); + gPacketStatusList.erase( i ); + } + else + { + // Try again... + Con::printf( "Rerequesting server list packet #%d...", p.index + 1 ); + p.tryCount--; + p.time = currentTime; + p.key = gKey++; + + BitStream *out = BitStream::getPacketStream(); + out->clearStringBuffer(); + out->write( U8( NetInterface::MasterServerListRequest ) ); + out->write( U8( sActiveFilter.queryFlags ) ); // flags + out->write( ( session << 16) | ( p.key & 0xFFFF ) ); + out->write( p.index ); // packet index + out->write( U8( 0 ) ); // game type + out->write( U8( 0 ) ); // mission type + out->write( U8( 0 ) ); // minPlayers + out->write( U8( 0 ) ); // maxPlayers + out->write( U32( 0 ) ); // region mask + out->write( U32( 0 ) ); // version + out->write( U8( 0 ) ); // filter flags + out->write( U8( 0 ) ); // max bots + out->write( U16( 0 ) ); // min CPU + out->write( U8( 0 ) ); // buddy count + + BitStream::sendPacketStream(&gMasterServerQueryAddress); + } + } + } + + if ( gPacketStatusList.size() ) + Sim::postEvent( Sim::getRootGroup(), new ProcessPacketEvent( session ), Sim::getCurrentTime() + 30 ); + else + processPingsAndQueries( gPingSession ); +} + +//----------------------------------------------------------------------------- + +static void processHeartbeat(U32 seq) +{ + if(seq != gHeartbeatSeq) + return; + sendHeartbeat( 0 ); + Sim::postEvent( Sim::getRootGroup(), new HeartbeatEvent(seq), Sim::getCurrentTime() + gHeartbeatInterval ); +} + +//----------------------------------------------------------------------------- + +static void updatePingProgress() +{ + if ( !gPingList.size() ) + { + updateQueryProgress(); + return; + } + + char msg[64]; + U32 pingsLeft = countPingRequests(); + dSprintf( msg, sizeof(msg), + (!pingsLeft && gPingList.size())? + "Waiting for lan servers...": + "Pinging servers: %d left...", + pingsLeft ); + + // Ping progress is 0 -> 0.5 + F32 progress = 0.0f; + if ( gServerPingCount ) + progress = F32( gServerPingCount - pingsLeft ) / F32( gServerPingCount * 2 ); + + //Con::errorf( ConsoleLogEntry::General, "Ping progress - %d of %d left - progress = %.2f", pingsLeft, gServerPingCount, progress ); + Con::executef( "onServerQueryStatus", "ping", msg, Con::getFloatArg( progress ) ); +} + +//----------------------------------------------------------------------------- + +static void updateQueryProgress() +{ + if ( gPingList.size() ) + return; + + char msg[64]; + U32 queriesLeft = gQueryList.size(); + dSprintf( msg, sizeof( msg ), "Querying servers: %d left...", queriesLeft ); + + // Query progress is 0.5 -> 1 + F32 progress = 0.5f; + if ( gServerQueryCount ) + progress += ( F32( gServerQueryCount - queriesLeft ) / F32( gServerQueryCount * 2 ) ); + + //Con::errorf( ConsoleLogEntry::General, "Query progress - %d of %d left - progress = %.2f", queriesLeft, gServerQueryCount, progress ); + Con::executef( "onServerQueryStatus", "query", msg, Con::getFloatArg( progress ) ); +} + + +//----------------------------------------------------------------------------- +// Server packet handlers: +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +static void handleMasterServerGameTypesResponse( BitStream* stream, U32 /*key*/, U8 /*flags*/ ) +{ + Con::printf( "Received game type list from the master server." ); + + U32 i; + U8 temp; + char stringBuf[256]; + stream->read( &temp ); + Con::executef("onClearGameTypes"); + for ( i = 0; i < U32( temp ); i++ ) + { + readCString( stream, stringBuf ); + Con::executef("onAddGameType", stringBuf); + } + + stream->read( &temp ); + Con::executef("onClearMissionTypes"); + for ( i = 0; i < U32( temp ); i++ ) + { + readCString( stream, stringBuf ); + Con::executef("onAddMissionType", stringBuf); + } +} + +//----------------------------------------------------------------------------- + +static void handleMasterServerListResponse( BitStream* stream, U32 key, U8 /*flags*/ ) +{ + U8 packetIndex, packetTotal; + U32 i; + U16 serverCount, port; + U8 netNum[4]; + char addressBuffer[256]; + NetAddress addr; + + stream->read( &packetIndex ); + // Validate the packet key: + U32 packetKey = gMasterServerPing.key; + if ( gGotFirstListPacket ) + { + for ( i = 0; i < gPacketStatusList.size(); i++ ) + { + if ( gPacketStatusList[i].index == packetIndex ) + { + packetKey = gPacketStatusList[i].key; + break; + } + } + } + + U32 testKey = ( gPingSession << 16 ) | ( packetKey & 0xFFFF ); + if ( testKey != key ) + return; + + stream->read( &packetTotal ); + stream->read( &serverCount ); + + Con::printf( "Received server list packet %d of %d from the master server (%d servers).", ( packetIndex + 1 ), packetTotal, serverCount ); + + // Enter all of the servers in this packet into the ping list: + for ( i = 0; i < serverCount; i++ ) + { + stream->read( &netNum[0] ); + stream->read( &netNum[1] ); + stream->read( &netNum[2] ); + stream->read( &netNum[3] ); + stream->read( &port ); + + dSprintf( addressBuffer, sizeof( addressBuffer ), "IP:%d.%d.%d.%d:%d", netNum[0], netNum[1], netNum[2], netNum[3], port ); + Net::stringToAddress( addressBuffer, &addr ); + pushPingRequest( &addr ); + } + + // If this is the first list packet we have received, fill the packet status list + // and start processing: + if ( !gGotFirstListPacket ) + { + gGotFirstListPacket = true; + gMasterServerQueryAddress = gMasterServerPing.address; + U32 currentTime = Platform::getVirtualMilliseconds(); + for ( i = 0; i < packetTotal; i++ ) + { + if ( i != packetIndex ) + { + PacketStatus* p = new PacketStatus( i, gMasterServerPing.key, currentTime ); + gPacketStatusList.push_back( *p ); + } + } + + processServerListPackets( gPingSession ); + } + else + { + // Remove the packet we just received from the status list: + for ( i = 0; i < gPacketStatusList.size(); i++ ) + { + if ( gPacketStatusList[i].index == packetIndex ) + { + gPacketStatusList.erase( i ); + break; + } + } + } +} + +//----------------------------------------------------------------------------- + +static void handleGameMasterInfoRequest( const NetAddress* address, U32 key, U8 flags ) +{ + if ( GNet->doesAllowConnections() ) + { + U8 temp8; + U32 temp32; + + char netString[256]; + Net::addressToString(address, netString); + + Vector *masterList = getMasterServerList(); + const NetAddress *masterAddr; + bool fromMaster = false; + for(U32 i = 0; i < masterList->size(); i++) + { + masterAddr = &(*masterList)[i].address; + if (*(U32*)(masterAddr->netNum) == *(U32*)(address->netNum)) + { + fromMaster = true; + break; + } + } + + Con::printf( "Received info request from %s [%s].", fromMaster?"a master server":"a machine", netString); + + BitStream *out = BitStream::getPacketStream(); + out->clearStringBuffer(); + + out->write( U8( NetInterface::GameMasterInfoResponse ) ); + out->write( U8( flags ) ); + out->write( key ); + + writeCString( out, Con::getVariable( "Server::GameType" ) ); + writeCString( out, Con::getVariable( "Server::MissionType" ) ); + temp8 = U8( Con::getIntVariable( "Pref::Server::MaxPlayers" ) ); + out->write( temp8 ); + temp32 = Con::getIntVariable( "Pref::Server::RegionMask" ); + out->write( temp32 ); + temp32 = getVersionNumber(); + out->write( temp32 ); + temp8 = 0; +#if defined(TORQUE_OS_LINUX) || defined(TORQUE_OS_OPENBSD) + temp8 |= ServerInfo::Status_Linux; +#endif + if ( Con::getBoolVariable( "Server::Dedicated" ) ) + temp8 |= ServerInfo::Status_Dedicated; + if ( dStrlen( Con::getVariable( "Pref::Server::Password" ) ) > 0 ) + temp8 |= ServerInfo::Status_Passworded; + out->write( temp8 ); + temp8 = U8( Con::getIntVariable( "Server::BotCount" ) ); + out->write( temp8 ); + out->write( Platform::SystemInfo.processor.mhz ); + + U8 playerCount = U8( Con::getIntVariable( "Server::PlayerCount" ) ); + out->write( playerCount ); + + const char* guidList = Con::getVariable( "Server::GuidList" ); + char* buf = new char[dStrlen( guidList ) + 1]; + dStrcpy( buf, guidList ); + char* temp = dStrtok( buf, "\t" ); + temp8 = 0; + for ( ; temp && temp8 < playerCount; temp8++ ) + { + out->write( U32( dAtoi( temp ) ) ); + temp = dStrtok( NULL, "\t" ); + temp8++; + } + + for ( ; temp8 < playerCount; temp8++ ) + out->write( U32( 0 ) ); + + delete [] buf; + + BitStream::sendPacketStream(address); + } +} + +//----------------------------------------------------------------------------- + +static void handleGamePingRequest( const NetAddress* address, U32 key, U8 flags ) +{ + // Do not respond if a mission is not running: + if ( GNet->doesAllowConnections() ) + { + // Do not respond if this is a single-player game: + if ( dStricmp( Con::getVariable( "Server::ServerType" ), "SinglePlayer" ) == 0 ) + return; + + // Do not respond to offline queries if this is an online server: + if ( flags & ServerFilter::OfflineQuery ) + return; + + // some banning code here (?) + + BitStream *out = BitStream::getPacketStream(); + out->clearStringBuffer(); + + out->write( U8( NetInterface::GamePingResponse ) ); + out->write( flags ); + out->write( key ); + if ( flags & ServerFilter::NoStringCompress ) + writeCString( out, versionString ); + else + out->writeString( versionString ); + out->write( GameConnection::CurrentProtocolVersion ); + out->write( GameConnection::MinRequiredProtocolVersion ); + out->write( getVersionNumber() ); + + // Enforce a 24-character limit on the server name: + char serverName[25]; + dStrncpy( serverName, Con::getVariable( "Pref::Server::Name" ), 24 ); + serverName[24] = 0; + if ( flags & ServerFilter::NoStringCompress ) + writeCString( out, serverName ); + else + out->writeString( serverName ); + + BitStream::sendPacketStream(address); + } +} + +//----------------------------------------------------------------------------- + +static void handleGamePingResponse( const NetAddress* address, BitStream* stream, U32 key, U8 /*flags*/ ) +{ + // Broadcast has timed out or query has been cancelled: + if( !gPingList.size() ) + return; + + S32 index = findPingEntry( gPingList, address ); + if( index == -1 ) + { + // an anonymous ping response - if it's not already timed + // out or finished, ping it. Probably from a broadcast + if( !addressFinished( address ) ) + pushPingRequest( address ); + return; + } + Ping &p = gPingList[index]; + U32 infoKey = ( p.session << 16 ) | ( p.key & 0xFFFF ); + if( infoKey != key ) + return; + + // Find if the server info already exists (favorite or refreshing): + ServerInfo* si = findServerInfo( address ); + bool applyFilter = false; + if ( sActiveFilter.type == ServerFilter::Normal ) + applyFilter = si ? !si->isUpdating() : true; + + char addrString[256]; + Net::addressToString( address, addrString ); + bool waitingForMaster = ( sActiveFilter.type == ServerFilter::Normal ) && !gGotFirstListPacket; + + // Verify the version: + char buf[256]; + stream->readString( buf ); + if ( dStrcmp( buf, versionString ) != 0 ) + { + // Version is different, so remove it from consideration: + Con::printf( "Server %s is a different version.", addrString ); + Con::printf( "Wanted version %s, got version %s", versionString, buf); + gFinishedList.push_back( *address ); + gPingList.erase( index ); + if ( si ) + { + si->status = ServerInfo::Status_TimedOut; + gServerBrowserDirty = true; + } + if ( !waitingForMaster ) + updatePingProgress(); + return; + } + + // See if the server meets our minimum protocol: + U32 temp32; + stream->read( &temp32 ); + if ( temp32 < GameConnection::MinRequiredProtocolVersion ) + { + Con::printf( "Protocol for server %s does not meet minimum protocol.", addrString ); + gFinishedList.push_back( *address ); + gPingList.erase( index ); + if ( si ) + { + si->status = ServerInfo::Status_TimedOut; + gServerBrowserDirty = true; + } + if ( !waitingForMaster ) + updatePingProgress(); + return; + } + + // See if we meet the server's minimum protocol: + stream->read( &temp32 ); + if ( GameConnection::CurrentProtocolVersion < temp32 ) + { + Con::printf( "You do not meet the minimum protocol for server %s.", addrString ); + gFinishedList.push_back( *address ); + gPingList.erase( index ); + if ( si ) + { + si->status = ServerInfo::Status_TimedOut; + gServerBrowserDirty = true; + } + if ( !waitingForMaster ) + updatePingProgress(); + return; + } + + // Calculate the ping: + U32 time = Platform::getVirtualMilliseconds(); + U32 ping = ( time > p.time ) ? time - p.time : 0; + + // Check for max ping filter: + if ( applyFilter && sActiveFilter.maxPing > 0 && ping > sActiveFilter.maxPing ) + { + // Ping is too high, so remove this server from consideration: + Con::printf( "Server %s filtered out by maximum ping.", addrString ); + gFinishedList.push_back( *address ); + gPingList.erase( index ); + if ( si ) + removeServerInfo( address ); + if ( !waitingForMaster ) + updatePingProgress(); + return; + } + + // Get the server build version: + stream->read( &temp32 ); + if ( applyFilter + && ( sActiveFilter.filterFlags & ServerFilter::CurrentVersion ) + && ( temp32 != getVersionNumber() ) ) + { + Con::printf( "Server %s filtered out by version number.", addrString ); + gFinishedList.push_back( *address ); + gPingList.erase( index ); + if ( si ) + removeServerInfo( address ); + if ( !waitingForMaster ) + updatePingProgress(); + return; + } + + // OK, we can finally create the server info structure: + if ( !si ) + si = findOrCreateServerInfo( address ); + si->ping = ping; + si->version = temp32; + + // Get the server name: + stream->readString( buf ); + if ( !si->name ) + { + si->name = (char*) dMalloc( dStrlen( buf ) + 1 ); + dStrcpy( si->name, buf ); + } + + // Set the server up to be queried: + gFinishedList.push_back( *address ); + p.key = 0; + p.time = 0; + p.tryCount = gQueryRetryCount; + gQueryList.push_back( p ); + gServerQueryCount++; + gPingList.erase( index ); + if ( !waitingForMaster ) + updatePingProgress(); + + // Update the server browser gui! + gServerBrowserDirty = true; +} + +//----------------------------------------------------------------------------- + +static void handleGameInfoRequest( const NetAddress* address, U32 key, U8 flags ) +{ + // Do not respond unless there is a server running: + if ( GNet->doesAllowConnections() ) + { + // Do not respond to offline queries if this is an online server: + if ( flags & ServerFilter::OfflineQuery ) + return; + + bool compressStrings = !( flags & ServerFilter::NoStringCompress ); + BitStream *out = BitStream::getPacketStream(); + out->clearStringBuffer(); + + out->write( U8( NetInterface::GameInfoResponse ) ); + out->write( flags ); + out->write( key ); + + if ( compressStrings ) { + out->writeString( Con::getVariable( "Server::GameType" ) ); + out->writeString( Con::getVariable( "Server::MissionType" ) ); + out->writeString( Con::getVariable( "Server::MissionName" ) ); + } + else { + writeCString( out, Con::getVariable( "Server::GameType" ) ); + writeCString( out, Con::getVariable( "Server::MissionType" ) ); + writeCString( out, Con::getVariable( "Server::MissionName" ) ); + } + + U8 status = 0; +#if defined(TORQUE_OS_LINUX) || defined(TORQUE_OS_OPENBSD) + status |= ServerInfo::Status_Linux; +#endif + if ( Con::getBoolVariable( "Server::Dedicated" ) ) + status |= ServerInfo::Status_Dedicated; + if ( dStrlen( Con::getVariable( "Pref::Server::Password" ) ) ) + status |= ServerInfo::Status_Passworded; + out->write( status ); + + out->write( U8( Con::getIntVariable( "Server::PlayerCount" ) ) ); + out->write( U8( Con::getIntVariable( "Pref::Server::MaxPlayers" ) ) ); + out->write( U8( Con::getIntVariable( "Server::BotCount" ) ) ); + out->write( U16( Platform::SystemInfo.processor.mhz ) ); + if ( compressStrings ) + out->writeString( Con::getVariable( "Pref::Server::Info" ) ); + else + writeCString( out, Con::getVariable( "Pref::Server::Info" ) ); + writeLongCString( out, Con::evaluate( "onServerInfoQuery();" ) ); + + BitStream::sendPacketStream(address); + } +} + +//----------------------------------------------------------------------------- + +static void handleGameInfoResponse( const NetAddress* address, BitStream* stream, U32 /*key*/, U8 /*flags*/ ) +{ + if ( !gQueryList.size() ) + return; + + S32 index = findPingEntry( gQueryList, address ); + if ( index == -1 ) + return; + + // Remove the server from the query list since it has been so kind as to respond: + gQueryList.erase( index ); + updateQueryProgress(); + ServerInfo *si = findServerInfo( address ); + if ( !si ) + return; + + bool isUpdate = si->isUpdating(); + bool applyFilter = !isUpdate && ( sActiveFilter.type == ServerFilter::Normal ); + char addrString[256]; + Net::addressToString( address, addrString ); + + // Get the rules set: + char stringBuf[2048]; // Who knows how big this should be? + stream->readString( stringBuf ); + if ( !si->gameType || dStricmp( si->gameType, stringBuf ) != 0 ) + { + si->gameType = (char*) dRealloc( (void*) si->gameType, dStrlen( stringBuf ) + 1 ); + dStrcpy( si->gameType, stringBuf ); + + // Test against the active filter: + if ( applyFilter && dStricmp( sActiveFilter.gameType, "any" ) != 0 + && dStricmp( si->gameType, sActiveFilter.gameType ) != 0 ) + { + Con::printf( "Server %s filtered out by rules set. (%s:%s)", addrString, sActiveFilter.gameType, si->gameType ); + removeServerInfo( address ); + return; + } + } + + // Get the mission type: + stream->readString( stringBuf ); + if ( !si->missionType || dStrcmp( si->missionType, stringBuf ) != 0 ) + { + si->missionType = (char*) dRealloc( (void*) si->missionType, dStrlen( stringBuf ) + 1 ); + dStrcpy( si->missionType, stringBuf ); + + // Test against the active filter: + if ( applyFilter && dStricmp( sActiveFilter.missionType, "any" ) != 0 + && dStricmp( si->missionType, sActiveFilter.missionType ) != 0 ) + { + Con::printf( "Server %s filtered out by mission type. (%s:%s)", addrString, sActiveFilter.missionType, si->missionType ); + removeServerInfo( address ); + return; + } + } + + // Get the mission name: + stream->readString( stringBuf ); + // Clip the file extension off: + char* temp = dStrstr( static_cast( stringBuf ), const_cast( ".mis" ) ); + if ( temp ) + *temp = '\0'; + if ( !si->missionName || dStrcmp( si->missionName, stringBuf ) != 0 ) + { + si->missionName = (char*) dRealloc( (void*) si->missionName, dStrlen( stringBuf ) + 1 ); + dStrcpy( si->missionName, stringBuf ); + } + + // Get the server status: + U8 temp_U8; + stream->read( &temp_U8 ); + si->status = temp_U8; + + // Filter by the flags: + if ( applyFilter ) + { + if ( sActiveFilter.filterFlags & ServerFilter::Dedicated && !si->isDedicated() ) + { + Con::printf( "Server %s filtered out by dedicated flag.", addrString ); + removeServerInfo( address ); + return; + } + + if ( sActiveFilter.filterFlags & ServerFilter::NotPassworded && si->isPassworded() ) + { + Con::printf( "Server %s filtered out by no-password flag.", addrString ); + removeServerInfo( address ); + return; + } + } + si->status.set( ServerInfo::Status_Responded ); + + // Get the player count: + stream->read( &si->numPlayers ); + + // Test player count against active filter: + if ( applyFilter && ( si->numPlayers < sActiveFilter.minPlayers || si->numPlayers > sActiveFilter.maxPlayers ) ) + { + Con::printf( "Server %s filtered out by player count.", addrString ); + removeServerInfo( address ); + return; + } + + // Get the max players and bot count: + stream->read( &si->maxPlayers ); + stream->read( &si->numBots ); + + // Test bot count against active filter: + if ( applyFilter && ( si->numBots > sActiveFilter.maxBots ) ) + { + Con::printf( "Server %s filtered out by maximum bot count.", addrString ); + removeServerInfo( address ); + return; + } + + // Get the CPU speed; + U16 temp_U16; + stream->read( &temp_U16 ); + si->cpuSpeed = temp_U16; + + // Test CPU speed against active filter: + if ( applyFilter && ( si->cpuSpeed < sActiveFilter.minCPU ) ) + { + Con::printf( "Server %s filtered out by minimum CPU speed.", addrString ); + removeServerInfo( address ); + return; + } + + // Get the server info: + stream->readString( stringBuf ); + if ( !si->statusString || ( isUpdate && dStrcmp( si->statusString, stringBuf ) != 0 ) ) + { + si->infoString = (char*) dRealloc( (void*) si->infoString, dStrlen( stringBuf ) + 1 ); + dStrcpy( si->infoString, stringBuf ); + } + + // Get the content string: + readLongCString( stream, stringBuf ); + if ( !si->statusString || ( isUpdate && dStrcmp( si->statusString, stringBuf ) != 0 ) ) + { + si->statusString = (char*) dRealloc( (void*) si->statusString, dStrlen( stringBuf ) + 1 ); + dStrcpy( si->statusString, stringBuf ); + } + + // Update the server browser gui! + gServerBrowserDirty = true; +} + +//----------------------------------------------------------------------------- +// Packet Dispatch + +void DemoNetInterface::handleInfoPacket( const NetAddress* address, U8 packetType, BitStream* stream ) +{ + U8 flags; + U32 key; + + stream->read( &flags ); + stream->read( &key ); + stream->clearStringBuffer(); + + switch( packetType ) + { + case GamePingRequest: + handleGamePingRequest( address, key, flags ); + break; + + case GamePingResponse: + handleGamePingResponse( address, stream, key, flags ); + break; + + case GameInfoRequest: + handleGameInfoRequest( address, key, flags ); + break; + + case GameInfoResponse: + handleGameInfoResponse( address, stream, key, flags ); + break; + + case MasterServerGameTypesResponse: + handleMasterServerGameTypesResponse( stream, key, flags ); + break; + + case MasterServerListResponse: + handleMasterServerListResponse( stream, key, flags ); + break; + + case GameMasterInfoRequest: + handleGameMasterInfoRequest( address, key, flags ); + break; + } +} + + +ConsoleFunctionGroupEnd( ServerQuery ); diff --git a/app/net/serverQuery.h b/app/net/serverQuery.h new file mode 100644 index 0000000..569f808 --- /dev/null +++ b/app/net/serverQuery.h @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SERVERQUERY_H_ +#define _SERVERQUERY_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif +#ifndef _BITSET_H_ +#include "core/bitSet.h" +#endif + +#include "platform/platformNet.h" + +//----------------------------------------------------------------------------- +// Game Server Information + +struct ServerInfo +{ + enum StatusFlags + { + // Info flags (0-7): + Status_Dedicated = BIT(0), + Status_Passworded = BIT(1), + Status_Linux = BIT(2), + + // Status flags: + Status_New = 0, + Status_Querying = BIT(28), + Status_Updating = BIT(29), + Status_Responded = BIT(30), + Status_TimedOut = BIT(31), + }; + + U8 numPlayers; + U8 maxPlayers; + U8 numBots; + char* name; + char* gameType; + char* missionName; + char* missionType; + char* statusString; + char* infoString; + NetAddress address; + U32 version; + U32 ping; + U32 cpuSpeed; + bool isFavorite; + BitSet32 status; + + ServerInfo() + { + numPlayers = 0; + maxPlayers = 0; + numBots = 0; + name = NULL; + gameType = NULL; + missionType = NULL; + missionName = NULL; + statusString = NULL; + infoString = NULL; + version = 0; + ping = 0; + cpuSpeed = 0; + isFavorite = false; + status = Status_New; + } + ~ServerInfo(); + + bool isNew() { return( status == Status_New ); } + bool isQuerying() { return( status.test( Status_Querying ) ); } + bool isUpdating() { return( status.test( Status_Updating ) ); } + bool hasResponded() { return( status.test( Status_Responded ) ); } + bool isTimedOut() { return( status.test( Status_TimedOut ) ); } + + bool isDedicated() { return( status.test( Status_Dedicated ) ); } + bool isPassworded() { return( status.test( Status_Passworded ) ); } + bool isLinux() { return( status.test( Status_Linux ) ); } +}; + + +//----------------------------------------------------------------------------- + +extern Vector gServerList; +extern bool gServerBrowserDirty; +extern void clearServerList(); +extern void queryLanServers(U32 port, U8 flags, const char* gameType, const char* missionType, + U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU, + U8 filterFlags); +extern void queryMasterGameTypes(); +extern void queryMasterServer(U8 flags, const char* gameType, const char* missionType, + U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU, + U8 filterFlags, U8 buddyCount, U32* buddyList ); +extern void queryFavoriteServers( U8 flags ); +extern void querySingleServer(const NetAddress* addr, U8 flags); +extern void startHeartbeat(); +extern void sendHeartbeat( U8 flags ); + +#ifdef TORQUE_DEBUG +extern void addFakeServers( S32 howMany ); +#endif // DEBUG + +#endif diff --git a/app/net/tcpObject.cpp b/app/net/tcpObject.cpp new file mode 100644 index 0000000..1c85e85 --- /dev/null +++ b/app/net/tcpObject.cpp @@ -0,0 +1,334 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "app/net/tcpObject.h" + +#include "platform/platform.h" +#include "platform/event.h" +#include "console/simBase.h" +#include "console/consoleInternal.h" + +TCPObject *TCPObject::table[TCPObject::TableSize] = {0, }; + +IMPLEMENT_CONOBJECT(TCPObject); + +TCPObject *TCPObject::find(NetSocket tag) +{ + for(TCPObject *walk = table[U32(tag) & TableMask]; walk; walk = walk->mNext) + if(walk->mTag == tag) + return walk; + return NULL; +} + +void TCPObject::addToTable(NetSocket newTag) +{ + removeFromTable(); + mTag = newTag; + mNext = table[U32(mTag) & TableMask]; + table[U32(mTag) & TableMask] = this; +} + +void TCPObject::removeFromTable() +{ + for(TCPObject **walk = &table[U32(mTag) & TableMask]; *walk; walk = &((*walk)->mNext)) + { + if(*walk == this) + { + *walk = mNext; + return; + } + } +} + +void processConnectedReceiveEvent(NetSocket sock, RawData incomingData); +void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress); +void processConnectedNotifyEvent( NetSocket sock, U32 state ); + +S32 gTCPCount = 0; + +TCPObject::TCPObject() +{ + mBuffer = NULL; + mBufferSize = 0; + mPort = 0; + mTag = InvalidSocket; + mNext = NULL; + mState = Disconnected; + + gTCPCount++; + + if(gTCPCount == 1) + { + Net::smConnectionAccept.notify(processConnectedAcceptEvent); + Net::smConnectionReceive.notify(processConnectedReceiveEvent); + Net::smConnectionNotify.notify(processConnectedNotifyEvent); + } +} + +TCPObject::~TCPObject() +{ + disconnect(); + dFree(mBuffer); + + gTCPCount--; + + if(gTCPCount == 0) + { + Net::smConnectionAccept.remove(processConnectedAcceptEvent); + Net::smConnectionReceive.remove(processConnectedReceiveEvent); + Net::smConnectionNotify.remove(processConnectedNotifyEvent); + } +} + +bool TCPObject::processArguments(S32 argc, const char **argv) +{ + if(argc == 0) + return true; + else if(argc == 1) + { + addToTable(U32(dAtoi(argv[0]))); + return true; + } + return false; +} + +bool TCPObject::onAdd() +{ + if(!Parent::onAdd()) + return false; + + const char *name = getName(); + + if(name && name[0] && getClassRep()) + { + Namespace *parent = getClassRep()->getNameSpace(); + Con::linkNamespaces(parent->mName, name); + mNameSpace = Con::lookupNamespace(name); + + } + + Sim::getTCPGroup()->addObject(this); + + return true; +} + +U32 TCPObject::onReceive(U8 *buffer, U32 bufferLen) +{ + // we got a raw buffer event + // default action is to split the buffer into lines of text + // and call processLine on each + // for any incomplete lines we have mBuffer + U32 start = 0; + parseLine(buffer, &start, bufferLen); + return start; +} + +void TCPObject::parseLine(U8 *buffer, U32 *start, U32 bufferLen) +{ + // find the first \n in buffer + U32 i; + U8 *line = buffer + *start; + + for(i = *start; i < bufferLen; i++) + if(buffer[i] == '\n' || buffer[i] == 0) + break; + U32 len = i - *start; + + if(i == bufferLen || mBuffer) + { + // we've hit the end with no newline + mBuffer = (U8 *) dRealloc(mBuffer, mBufferSize + len + 1); + dMemcpy(mBuffer + mBufferSize, line, len); + mBufferSize += len; + *start = i; + + // process the line + if(i != bufferLen) + { + mBuffer[mBufferSize] = 0; + if(mBufferSize && mBuffer[mBufferSize-1] == '\r') + mBuffer[mBufferSize - 1] = 0; + U8 *temp = mBuffer; + mBuffer = 0; + mBufferSize = 0; + + processLine((UTF8*)temp); + dFree(temp); + } + } + else if(i != bufferLen) + { + line[len] = 0; + if(len && line[len-1] == '\r') + line[len-1] = 0; + processLine((UTF8*)line); + } + if(i != bufferLen) + *start = i + 1; +} + +void TCPObject::onConnectionRequest(const NetAddress *addr, U32 connectId) +{ + char idBuf[16]; + char addrBuf[256]; + Net::addressToString(addr, addrBuf); + dSprintf(idBuf, sizeof(idBuf), "%d", connectId); + Con::executef(this, "onConnectRequest", addrBuf, idBuf); +} + +bool TCPObject::processLine(UTF8 *line) +{ + Con::executef(this, "onLine", line); + return true; +} + +void TCPObject::onDNSResolved() +{ + mState = DNSResolved; + Con::executef(this, "onDNSResolved"); +} + +void TCPObject::onDNSFailed() +{ + mState = Disconnected; + Con::executef(this, "onDNSFailed"); +} + +void TCPObject::onConnected() +{ + mState = Connected; + Con::executef(this, "onConnected"); +} + +void TCPObject::onConnectFailed() +{ + mState = Disconnected; + Con::executef(this, "onConnectFailed"); +} + +void TCPObject::finishLastLine() +{ + if(mBufferSize) + { + mBuffer[mBufferSize] = 0; + processLine((UTF8*)mBuffer); + dFree(mBuffer); + mBuffer = 0; + mBufferSize = 0; + } +} + +void TCPObject::onDisconnect() +{ + finishLastLine(); + mState = Disconnected; + Con::executef(this, "onDisconnect"); +} + +void TCPObject::listen(U16 port) +{ + mState = Listening; + U32 newTag = Net::openListenPort(port); + addToTable(newTag); +} + +void TCPObject::connect(const char *address) +{ + NetSocket newTag = Net::openConnectTo(address); + addToTable(newTag); +} + +void TCPObject::disconnect() +{ + if( mTag != InvalidSocket ) { + Net::closeConnectTo(mTag); + } + removeFromTable(); +} + +void TCPObject::send(const U8 *buffer, U32 len) +{ + Net::sendtoSocket(mTag, buffer, S32(len)); +} + +ConsoleMethod( TCPObject, send, void, 3, 0, "(...)" + "Parameters are transmitted as strings, one at a time.") +{ + for(S32 i = 2; i < argc; i++) + object->send((const U8 *) argv[i], dStrlen(argv[i])); +} + +ConsoleMethod( TCPObject, listen, void, 3, 3, "(int port)" + "Start listening on the specified ports for connections.") +{ + object->listen(U32(dAtoi(argv[2]))); +} + +ConsoleMethod( TCPObject, connect, void, 3, 3, "(string addr)" + "Connect to the given address.") +{ + object->connect(argv[2]); +} + +ConsoleMethod( TCPObject, disconnect, void, 2, 2, "Disconnect from whatever we're connected to, if anything.") +{ + object->disconnect(); +} + +void processConnectedReceiveEvent(NetSocket sock, RawData incomingData) +{ + TCPObject *tcpo = TCPObject::find(sock); + if(!tcpo) + { + Con::printf("Got bad connected receive event."); + return; + } + + U32 size = incomingData.size; + U8 *buffer = (U8*)incomingData.data; + + while(size) + { + U32 ret = tcpo->onReceive(buffer, size); + AssertFatal(ret <= size, "Invalid return size"); + size -= ret; + buffer += ret; + } +} + +void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress) +{ + TCPObject *tcpo = TCPObject::find(listeningPort); + if(!tcpo) + return; + + tcpo->onConnectionRequest(&originatingAddress, newConnection); +} + +void processConnectedNotifyEvent( NetSocket sock, U32 state ) +{ + TCPObject *tcpo = TCPObject::find(sock); + if(!tcpo) + return; + + switch(state) + { + case Net::DNSResolved: + tcpo->onDNSResolved(); + break; + case Net::DNSFailed: + tcpo->onDNSFailed(); + break; + case Net::Connected: + tcpo->onConnected(); + break; + case Net::ConnectFailed: + tcpo->onConnectFailed(); + break; + case Net::Disconnected: + tcpo->onDisconnect(); + break; + } +} diff --git a/app/net/tcpObject.h b/app/net/tcpObject.h new file mode 100644 index 0000000..55aa971 --- /dev/null +++ b/app/net/tcpObject.h @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TCPOBJECT_H_ +#define _TCPOBJECT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +#include "platform/platformNet.h" + +class TCPObject : public SimObject +{ +public: + enum State {Disconnected, DNSResolved, Connected, Listening }; + +private: + NetSocket mTag; + TCPObject *mNext; + enum { TableSize = 256, TableMask = 0xFF }; + static TCPObject *table[TableSize]; + State mState; + +protected: + typedef SimObject Parent; + U8 *mBuffer; + U32 mBufferSize; + U16 mPort; + +public: + TCPObject(); + virtual ~TCPObject(); + + void parseLine(U8 *buffer, U32 *start, U32 bufferLen); + void finishLastLine(); + + static TCPObject *find(NetSocket tag); + + // onReceive gets called continuously until all bytes are processed + // return # of bytes processed each time. + virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data + virtual bool processLine(UTF8 *line); // process a complete line of text... default action is to call into script + virtual void onDNSResolved(); + virtual void onDNSFailed(); + virtual void onConnected(); + virtual void onConnectFailed(); + virtual void onConnectionRequest(const NetAddress *addr, U32 connectId); + virtual void onDisconnect(); + void connect(const char *address); + void listen(U16 port); + void disconnect(); + State getState() { return mState; } + + bool processArguments(S32 argc, const char **argv); + void send(const U8 *buffer, U32 bufferLen); + void addToTable(NetSocket newTag); + void removeFromTable(); + + void setPort(U16 port) { mPort = port; } + + bool onAdd(); + + DECLARE_CONOBJECT(TCPObject); + +}; + + +#endif // _H_TCPOBJECT_ diff --git a/app/version.cpp b/app/version.cpp new file mode 100644 index 0000000..cd89503 --- /dev/null +++ b/app/version.cpp @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "app/version.h" +#include "console/console.h" + +static const U32 csgVersionNumber = TORQUE_GAME_ENGINE; + +U32 getVersionNumber() +{ + return csgVersionNumber; +} + +const char* getVersionString() +{ + return TORQUE_GAME_ENGINE_VERSION_STRING; +} + +/// TGE 0001 +/// TGEA 0002 +/// TGB 0003 +/// TGEA 360 0004 +/// TGE WII 0005 +/// Torque 3D 0006 + +const char* getEngineProductString() +{ + return "Element"; +#ifndef TORQUE_ENGINE_PRODUCT + return "Torque Engine"; +#else + switch (TORQUE_ENGINE_PRODUCT) + { + case 0001: + return "Torque Game Engine"; + case 0002: + return "Torque Game Engine Advanced"; + case 0003: + return "Torque 2D"; + case 0004: + return "Torque 360"; + case 0005: + return "Torque for Wii"; + case 0006: + return "Torque 3D"; + + default: + return "Torque Engine"; + }; +#endif +} + +const char* getCompileTimeString() +{ + return __DATE__ " at " __TIME__; +} +//---------------------------------------------------------------- + +ConsoleFunctionGroupBegin( CompileInformation, "Functions to get version information about the current executable." ); + +ConsoleFunction( getVersionNumber, S32, 1, 1, "Get the version of the build, as a string.") +{ + return getVersionNumber(); +} + +ConsoleFunction( getVersionString, const char*, 1, 1, "Get the version of the build, as a string.") +{ + return getVersionString(); +} + +ConsoleFunction( getEngineName, const char*, 1, 1, "Get the name of the engine product that this is running from, as a string.") +{ + return getEngineProductString(); +} + +ConsoleFunction( getCompileTimeString, const char*, 1, 1, "Get the time of compilation.") +{ + return getCompileTimeString(); +} + +ConsoleFunction( getBuildString, const char*, 1, 1, "Get the type of build, \"Debug\" or \"Release\".") +{ +#ifdef TORQUE_DEBUG + return "Debug"; +#else + return "Release"; +#endif +} + +ConsoleFunctionGroupEnd( CompileInformation ); + +ConsoleFunction(isDemo, bool, 1, 1, "") +{ +#ifdef TORQUE_DEMO + return true; +#else + return false; +#endif +} + +ConsoleFunction(isWebDemo, bool, 1, 1, "") +{ +#ifdef TORQUE_DEMO + return Platform::getWebDeployment(); +#else + return false; +#endif +} \ No newline at end of file diff --git a/app/version.h b/app/version.h new file mode 100644 index 0000000..42b49b6 --- /dev/null +++ b/app/version.h @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _VERSION_H_ +#define _VERSION_H_ + +/// This is our global version number for the engine source code that +/// we are using. See /source/torqueConfig.h for the game's source +/// code version, the game name, and which type of game it is (TGB, TGE, TGEA, etc.). +/// +/// Version number is major * 1000 + minor * 100 + revision * 10. +#define TORQUE_GAME_ENGINE 1010 + +/// Human readable engine version string. +#define TORQUE_GAME_ENGINE_VERSION_STRING "2009" + +/// Gets the specified version number. The version number is specified as a global in version.cc +U32 getVersionNumber(); +/// Gets the version number in string form +const char* getVersionString(); +/// Gets the engine product name in string form +const char* getEngineProductString(); +/// Gets the compile date and time +const char* getCompileTimeString(); + +#endif diff --git a/cinterface/cinterface.cpp b/cinterface/cinterface.cpp new file mode 100644 index 0000000..c5ca916 --- /dev/null +++ b/cinterface/cinterface.cpp @@ -0,0 +1,415 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/compiler.h" +#include "console/consoleInternal.h" +#include "core/util/tDictionary.h" +#include "core/strings/stringFunctions.h" +#include "app/mainLoop.h" +#include "windowManager/platformWindow.h" +#include "windowManager/platformWindowMgr.h" + +#ifdef TORQUE_OS_WIN32 +#include "windowManager/win32/win32Window.h" +#include "windowManager/win32/winDispatch.h" +extern void createFontInit(void); +extern void createFontShutdown(void); +#endif + +static HashTable gSecureScript; + +#ifdef TORQUE_OS_MAC + +// ObjC hooks for shared library support +// See: macMain.mm + +void torque_mac_engineinit(int argc, const char **argv); +void torque_mac_enginetick(); +void torque_mac_engineshutdown(); + +#endif + +extern bool LinkConsoleFunctions; + +extern "C" { + + // reset the engine, unloading any current level and returning to the main menu + void torque_reset() + { + Con::evaluate("disconnect();"); + } + + // initialize Torque 3D including argument handling + bool torque_engineinit(S32 argc, const char **argv) + { + + LinkConsoleFunctions = true; + +#if !defined(TORQUE_OS_XENON) && !defined(TORQUE_OS_PS3) && defined(_MSC_VER) + createFontInit(); +#endif + + +#ifdef TORQUE_OS_MAC + torque_mac_engineinit(argc, argv); +#endif + // Initialize the subsystems. + StandardMainLoop::init(); + + // Handle any command line args. + if(!StandardMainLoop::handleCommandLine(argc, argv)) + { + Platform::AlertOK("Error", "Failed to initialize game, shutting down."); + return false; + } + + return true; + } + + // tick Torque 3D's main loop + int torque_enginetick() + { +#ifdef TORQUE_OS_MAC + torque_mac_enginetick(); +#endif + return StandardMainLoop::doMainLoop(); + } + + // signal an engine shutdown (as with the quit(); console command) + void torque_enginesignalshutdown() + { + Con::evaluate("quit();"); + } + + // shutdown the engine + bool torque_engineshutdown() + { + + // Clean everything up. + StandardMainLoop::shutdown(); + +#if !defined(TORQUE_OS_XENON) && !defined(TORQUE_OS_PS3) && defined(_MSC_VER) + createFontShutdown(); +#endif + +#ifdef TORQUE_OS_MAC + torque_mac_engineshutdown(); +#endif + + + // Return. + return true; + + } + + bool torque_isdebugbuild() + { +#ifdef _DEBUG + return true; +#else + return false; +#endif + + } + + bool torque_getconsolebool(const char* name) + { + return Con::getBoolVariable(name); + } + + void torque_setconsolebool(const char* name, bool value) + { + Con::setBoolVariable(name, value); + } + + static char* gExecutablePath = NULL; + + const char* torque_getexecutablepath() + { + return gExecutablePath; + } + + void torque_setexecutablepath(const char* directory) + { + gExecutablePath = new char[strlen(directory)+1]; + strcpy(gExecutablePath, directory); + } + + // set Torque 3D into web deployment mode (disable fullscreen exlusive mode, etc) + void torque_setwebdeployment() + { + Platform::setWebDeployment(true); + } + + // Get a console variable + const char* torque_getvariable(const char* name) + { + return Con::getVariable(StringTable->insert(name)); + } + + // Set a console variable + void torque_setvariable(const char* name, const char* value) + { + Con::setVariable(StringTable->insert(name), StringTable->insert(value)); + } + + static Namespace::Entry* GetEntry(const char* nameSpace, const char* name) + { + Namespace* ns = NULL; + + if (!nameSpace || !dStrlen(nameSpace)) + ns = Namespace::mGlobalNamespace; + else + { + nameSpace = StringTable->insert(nameSpace); + ns = Namespace::find(nameSpace); //can specify a package here, maybe need, maybe not + } + + if (!ns) + return NULL; + + name = StringTable->insert(name); + + Namespace::Entry* entry = ns->lookupRecursive(name); + + return entry; + } + + // Export a function to the Torque 3D console system which matches the StringCallback function prototype + // specify the nameSpace, functionName, usage, min and max arguments + void torque_exportstringcallback(StringCallback cb, const char *nameSpace, const char *funcName, const char* usage, S32 minArgs, S32 maxArgs) + { + if (!nameSpace || !dStrlen(nameSpace)) + Con::addCommand(funcName, cb, usage, minArgs + 1, maxArgs + 1); + else + Con::addCommand(nameSpace, funcName, cb, usage, minArgs + 1, maxArgs + 1); + } + + void torque_callvoidfunction(const char* nameSpace, const char* name, S32 argc, const char ** argv) + { + + Namespace::Entry* entry = GetEntry(nameSpace, name); + + if (!entry) + return; + + entry->cb.mVoidCallbackFunc(NULL, argc, argv); + } + + F32 torque_callfloatfunction(const char* nameSpace, const char* name, S32 argc, const char ** argv) + { + + Namespace::Entry* entry = GetEntry(nameSpace, name); + + if (!entry) + return 0.0f; + + return entry->cb.mFloatCallbackFunc(NULL, argc, argv); + } + + S32 torque_callintfunction(const char* nameSpace, const char* name, S32 argc, const char ** argv) + { + + Namespace::Entry* entry = GetEntry(nameSpace, name); + + if (!entry) + return 0; + + return entry->cb.mIntCallbackFunc(NULL, argc, argv); + } + + + const char * torque_callstringfunction(const char* nameSpace, const char* name, S32 argc, const char ** argv) + { + Namespace::Entry* entry = GetEntry(nameSpace, name); + + if (!entry) + return ""; + + return entry->cb.mStringCallbackFunc(NULL, argc, argv); + } + + bool torque_callboolfunction(const char* nameSpace, const char* name, S32 argc, const char ** argv) + { + Namespace::Entry* entry = GetEntry(nameSpace, name); + + if (!entry) + return ""; + + return entry->cb.mBoolCallbackFunc(NULL, argc, argv); + } + + + const char * torque_callscriptfunction(const char* nameSpace, const char* name, S32 argc, const char ** argv) + { + Namespace::Entry* entry = GetEntry(nameSpace, name); + + if (!entry) + return ""; + + if(!entry->mFunctionOffset) + return ""; + + const char* ret = entry->mCode->exec(entry->mFunctionOffset, StringTable->insert(name), entry->mNamespace, argc, argv, false, entry->mPackage); + + if (!ret || !dStrlen(ret)) + return ""; + + return ret; + + } + + + // Call a TorqueScript console function that has been marked as secure + const char* torque_callsecurefunction(const char* nameSpace, const char* name, S32 argc, const char ** argv) + { + static const char* invalidChars = "()=:{}"; + String s = nameSpace; + s += "::"; + s += name; + s = String::ToUpper(s); + + if (!gSecureScript.count(StringTable->insert(s.c_str()))) + { + Con::warnf("\nAttempt to call insecure script: %s\n", s.c_str()); + return ""; + } + + // scan through for invalid characters + for (S32 i = 0; i < argc ; i++) + for (S32 j = 0; j < dStrlen(invalidChars) ; j++) + for (S32 k = 0; k < dStrlen(argv[i]); k++) + if (invalidChars[j] == argv[i][k]) + { + Con::warnf("\nInvalid parameter passed to secure script: %s, %s\n", s.c_str(), argv[i]); + return ""; + } + + Namespace::Entry* entry = GetEntry(nameSpace, name); + + if (!entry) + return ""; + + static char returnBuffer[32]; + + switch(entry->mType) + { + case Namespace::Entry::ConsoleFunctionType: + return torque_callscriptfunction(nameSpace, name, argc, argv); + + case Namespace::Entry::StringCallbackType: + return torque_callstringfunction(nameSpace, name, argc, argv); + + case Namespace::Entry::IntCallbackType: + dSprintf(returnBuffer, sizeof(returnBuffer), "%d", torque_callintfunction(nameSpace, name, argc, argv)); + return returnBuffer; + + case Namespace::Entry::FloatCallbackType: + dSprintf(returnBuffer, sizeof(returnBuffer), "%g", torque_callfloatfunction(nameSpace, name, argc, argv)); + return returnBuffer; + + case Namespace::Entry::VoidCallbackType: + torque_callvoidfunction(nameSpace, name, argc, argv); + return ""; + + case Namespace::Entry::BoolCallbackType: + dSprintf(returnBuffer, sizeof(returnBuffer), "%d", (U32) torque_callboolfunction(nameSpace, name, argc, argv)); + return returnBuffer; + }; + + return ""; + + } + + // Set a TorqueScript console function as secure and available for JavaScript via the callScript plugin method + void torque_addsecurefunction(const char* nameSpace, const char* fname) + { + String s = nameSpace; + s += "::"; + s += fname; + s = String::ToUpper(s); + + gSecureScript.insertEqual(StringTable->insert(s.c_str()), StringTable->insert(s.c_str())); + } + + + // Evaluate arbitrary TorqueScript (ONLY CALL torque_evaluate FROM TRUSTED CODE!!!) + const char* torque_evaluate(const char* code) + { + return Con::evaluate(code); + } + + // resize the Torque 3D child window to the specified width and height + void torque_resizewindow(S32 width, S32 height) + { + if (PlatformWindowManager::get() && PlatformWindowManager::get()->getFirstWindow()) + PlatformWindowManager::get()->getFirstWindow()->setSize(Point2I(width,height)); + } + +#ifdef TORQUE_OS_WIN32 + // retrieve the hwnd of our render window + void* torque_gethwnd() + { + if (PlatformWindowManager::get() && PlatformWindowManager::get()->getFirstWindow()) + { + Win32Window* w = (Win32Window*) PlatformWindowManager::get()->getFirstWindow(); + return (void *) w->getHWND(); + } + + return NULL; + } + + // directly add a message to the Torque 3D event queue, bypassing the Windows event queue + // this is useful in the case of the IE plugin, where we are hooking into an application + // level message, and posting to the windows queue would cause a hang + void torque_directmessage(U32 message, U32 wparam, U32 lparam) + { + if (PlatformWindowManager::get() && PlatformWindowManager::get()->getFirstWindow()) + { + Win32Window* w = (Win32Window*) PlatformWindowManager::get()->getFirstWindow(); + Dispatch(DelayedDispatch,w->getHWND(),message,wparam,lparam); + } + } + +#endif +} + +// This function is solely to test the TorqueScript <-> Javascript binding +// By default, it is marked as secure by the web plugins and then can be called from +// Javascript on the web page to ensure that function calls across the language +// boundry are working with arguments and return values +ConsoleFunction(testJavaScriptBridge, const char *, 4, 4, "testBridge(arg1, arg2, arg3)") +{ + S32 failed = 0; + if(argc != 4) + failed = 1; + else + { + if (dStrcmp(argv[1],"one")) + failed = 2; + if (dStrcmp(argv[2],"two")) + failed = 2; + if (dStrcmp(argv[3],"three")) + failed = 2; + } + + //attempt to call from TorqueScript -> JavaScript + const char* jret = Con::evaluate("JS::bridgeCallback(\"one\",\"two\",\"three\");"); + + if (dStrcmp(jret,"42")) + failed = 3; + + char *ret = Con::getReturnBuffer(256); + + dSprintf(ret, 256, "%i", failed); + + return ret; +} + + + + + diff --git a/cinterface/cinterface.h b/cinterface/cinterface.h new file mode 100644 index 0000000..2f536aa --- /dev/null +++ b/cinterface/cinterface.h @@ -0,0 +1,8 @@ +#pragma once + +// cinterface can override this (useful for plugins, etc) +extern "C" { + +const char* torque_getexecutablepath(); + +} diff --git a/collision/abstractPolyList.cpp b/collision/abstractPolyList.cpp new file mode 100644 index 0000000..11f7839 --- /dev/null +++ b/collision/abstractPolyList.cpp @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "collision/abstractPolyList.h" + + +//---------------------------------------------------------------------------- + +AbstractPolyList::~AbstractPolyList() +{ + mInterestNormalRegistered = false; +} + +static U32 PolyFace[6][4] = { + { 3, 2, 1, 0 }, + { 7, 4, 5, 6 }, + { 0, 5, 4, 3 }, + { 6, 5, 0, 1 }, + { 7, 6, 1, 2 }, + { 4, 7, 2, 3 }, +}; + +void AbstractPolyList::addBox(const Box3F &box, BaseMatInstance* material) +{ + Point3F pos = box.minExtents; + F32 dx = box.maxExtents.x - box.minExtents.x; + F32 dy = box.maxExtents.y - box.minExtents.y; + F32 dz = box.maxExtents.z - box.minExtents.z; + + U32 base = addPoint(pos); + pos.y += dy; addPoint(pos); + pos.x += dx; addPoint(pos); + pos.y -= dy; addPoint(pos); + pos.z += dz; addPoint(pos); + pos.x -= dx; addPoint(pos); + pos.y += dy; addPoint(pos); + pos.x += dx; addPoint(pos); + + for (S32 i = 0; i < 6; i++) { + begin(material, i); + S32 v1 = base + PolyFace[i][0]; + S32 v2 = base + PolyFace[i][1]; + S32 v3 = base + PolyFace[i][2]; + S32 v4 = base + PolyFace[i][3]; + vertex(v1); + vertex(v2); + vertex(v3); + vertex(v4); + plane(v1, v2, v3); + end(); + } +} + +bool AbstractPolyList::getMapping(MatrixF *, Box3F *) +{ + // return list transform and bounds in list space...optional + return false; +} + + +bool AbstractPolyList::isInterestedInPlane(const PlaneF& plane) +{ + if (mInterestNormalRegistered == false) { + return true; + } + else { + PlaneF xformed; + mPlaneTransformer.transform(plane, xformed); + if (mDot(xformed, mInterestNormal) >= 0.0f) + return false; + else + return true; + } +} + +bool AbstractPolyList::isInterestedInPlane(const U32 index) +{ + if (mInterestNormalRegistered == false) { + return true; + } + else { + const PlaneF& rPlane = getIndexedPlane(index); + if (mDot(rPlane, mInterestNormal) >= 0.0f) + return false; + else + return true; + } +} + +void AbstractPolyList::setInterestNormal(const Point3F& normal) +{ + mInterestNormalRegistered = true; + mInterestNormal = normal; +} + diff --git a/collision/abstractPolyList.h b/collision/abstractPolyList.h new file mode 100644 index 0000000..5744cec --- /dev/null +++ b/collision/abstractPolyList.h @@ -0,0 +1,242 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ABSTRACTPOLYLIST_H_ +#define _ABSTRACTPOLYLIST_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _MPLANETRANSFORMER_H_ +#include "math/mPlaneTransformer.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class SceneObject; +class BaseMatInstance; + +//---------------------------------------------------------------------------- + +/// A polygon filtering interface. +/// +/// The various AbstractPolyList subclasses are used in Torque as an interface to +/// handle spatial queries. SceneObject::buildPolyList() takes an implementor of +/// AbstractPolyList (such as ConcretePolyList, ClippedPolyList, etc.) and a +/// bounding volume. The function runs geometry data from all the objects in the +/// bounding volume through the passed PolyList. +/// +/// This interface only provides a method to get data INTO your implementation. Different +/// subclasses provide different interfaces to get data back out, depending on their +/// specific quirks. +/// +/// The physics engine now uses convex hulls for collision detection. +/// +/// @see Convex +class AbstractPolyList +{ +protected: + // User set state + SceneObject* mCurrObject; + + MatrixF mBaseMatrix; // Base transform + MatrixF mTransformMatrix; // Current object transform + MatrixF mMatrix; // Base * current transform + Point3F mScale; + + PlaneTransformer mPlaneTransformer; + + bool mInterestNormalRegistered; + Point3F mInterestNormal; + +public: + AbstractPolyList(); + virtual ~AbstractPolyList(); + + /// @name Common Interface + /// @{ + void setBaseTransform(const MatrixF& mat); + + /// Sets the transform applying to the current stream of + /// vertices. + /// + /// @param mat Transformation of the object. (in) + /// @param scale Scaling of the object. (in) + void setTransform(const MatrixF* mat, const Point3F& scale); + + /// Gets the transform applying to the current stream of + /// vertices. + /// + /// @param mat Transformation of the object. (out) + /// @param scale Scaling of the object. (out) + void getTransform(MatrixF* mat, Point3F * scale); + + /// This is called by the object which is currently feeding us + /// vertices, to tell us who it is. + void setObject(SceneObject*); + + /// Add a box via the query interface (below). This wraps some calls + /// to addPoint and addPlane. + void addBox(const Box3F &box, BaseMatInstance* material = NULL); + + void doConstruct(); + /// @} + + /// @name Query Interface + /// + /// It is through this interface that geometry data is fed to the + /// PolyList. The order of calls are: + /// - begin() + /// - One or more calls to vertex() and one call to plane(), in any order. + /// - end() + /// + /// @code + /// // Example code that adds data to a PolyList. + /// // See AbstractPolyList::addBox() for the function this was taken from. + /// + /// // First, we add points... (note that we use base to track the start of adding.) + /// U32 base = addPoint(pos); + /// pos.y += dy; addPoint(pos); + /// pos.x += dx; addPoint(pos); + /// pos.y -= dy; addPoint(pos); + /// pos.z += dz; addPoint(pos); + /// pos.x -= dx; addPoint(pos); + /// pos.y += dy; addPoint(pos); + /// pos.x += dx; addPoint(pos); + /// + /// // Now we add our surfaces. (there are six, as we are adding a cube here) + /// for (S32 i = 0; i < 6; i++) { + /// // Begin a surface + /// begin(0,i); + /// + /// // Calculate the vertex ids; we have a lookup table to tell us offset from base. + /// // In your own code, you might use a different method. + /// S32 v1 = base + PolyFace[i][0]; + /// S32 v2 = base + PolyFace[i][1]; + /// S32 v3 = base + PolyFace[i][2]; + /// S32 v4 = base + PolyFace[i][3]; + /// + /// // Reference the four vertices that make up this surface. + /// vertex(v1); + /// vertex(v2); + /// vertex(v3); + /// vertex(v4); + /// + /// // Indicate the plane of this surface. + /// plane(v1,v2,v3); + /// + /// // End the surface. + /// end(); + /// } + /// @endcode + /// @{ + + /// Are we empty of data? + virtual bool isEmpty() const = 0; + + /// Adds a point to the poly list, and returns + /// an ID number for that point. + virtual U32 addPoint(const Point3F& p) = 0; + + /// Adds a plane to the poly list, and returns + /// an ID number for that point. + virtual U32 addPlane(const PlaneF& plane) = 0; + + /// Start a surface. + /// + /// @param material A material ID for this surface. + /// @param surfaceKey A key value to associate with this surface. + virtual void begin(BaseMatInstance* material,U32 surfaceKey) = 0; + + /// Indicate the plane of the surface. + virtual void plane(U32 v1,U32 v2,U32 v3) = 0; + /// Indicate the plane of the surface. + virtual void plane(const PlaneF& p) = 0; + /// Indicate the plane of the surface. + virtual void plane(const U32 index) = 0; + + /// Reference a vertex which is in this surface. + virtual void vertex(U32 vi) = 0; + + /// Mark the end of a surface. + virtual void end() = 0; + + /// Return list transform and bounds in list space. + /// + /// @returns False if no data is available. + virtual bool getMapping(MatrixF *, Box3F *); + /// @} + + /// @name Interest + /// + /// This is a mechanism to let you specify interest in a specific normal. + /// If you set a normal you're interested in, then any planes facing "away" + /// from that normal are culled from the results. + /// + /// This is handy if you're using this to do a physics check, as you're not + /// interested in polygons facing away from you (since you don't collide with + /// the backsides/insides of things). + /// @{ + + void setInterestNormal(const Point3F& normal); + void clearInterestNormal() { mInterestNormalRegistered = false; } + + + virtual bool isInterestedInPlane(const PlaneF& plane); + virtual bool isInterestedInPlane(const U32 index); + + /// @} + + protected: + /// A helper function to convert a plane index to a PlaneF structure. + virtual const PlaneF& getIndexedPlane(const U32 index) = 0; +}; + +inline AbstractPolyList::AbstractPolyList() +{ + doConstruct(); +} + +inline void AbstractPolyList::doConstruct() +{ + mCurrObject = NULL; + mBaseMatrix.identity(); + mMatrix.identity(); + mScale.set(1, 1, 1); + + mPlaneTransformer.setIdentity(); + + mInterestNormalRegistered = false; +} + +inline void AbstractPolyList::setBaseTransform(const MatrixF& mat) +{ + mBaseMatrix = mat; +} + +inline void AbstractPolyList::setTransform(const MatrixF* mat, const Point3F& scale) +{ + mMatrix = mBaseMatrix; + mTransformMatrix = *mat; + mMatrix.mul(*mat); + mScale = scale; + + mPlaneTransformer.set(mMatrix, mScale); +} + +inline void AbstractPolyList::getTransform(MatrixF* mat, Point3F * scale) +{ + *mat = mTransformMatrix; + *scale = mScale; +} + +inline void AbstractPolyList::setObject(SceneObject* obj) +{ + mCurrObject = obj; +} + + +#endif diff --git a/collision/boxConvex.cpp b/collision/boxConvex.cpp new file mode 100644 index 0000000..59d6a70 --- /dev/null +++ b/collision/boxConvex.cpp @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMath.h" +#include "T3D/gameBase.h" +#include "collision/boxConvex.h" + + +//---------------------------------------------------------------------------- + +static struct Corner { + S32 a,b,c; + S32 ab,ac,bc; +} sCorner[] = +{ + { 1,2,4, 4,0,1 }, + { 0,3,5, 4,0,3 }, + { 0,3,6, 4,1,2 }, + { 1,2,7, 4,3,2 }, + { 0,5,6, 0,1,5 }, + { 1,4,7, 0,3,5 }, + { 2,4,7, 1,2,5 }, + { 3,5,6, 3,2,5 }, +}; + +static struct Face { + S32 vertex[4]; + S32 axis; + bool flip; +} sFace[] = +{ + { {0,4,5,1}, 1,true }, + { {0,2,6,4}, 0,true }, + { {3,7,6,2}, 1,false }, + { {3,1,5,7}, 0,false }, + { {0,1,3,2}, 2,true }, + { {4,6,7,5}, 2,false }, +}; + +Point3F BoxConvex::support(const VectorF& v) const +{ + Point3F p = mCenter; + p.x += (v.x >= 0)? mSize.x: -mSize.x; + p.y += (v.y >= 0)? mSize.y: -mSize.y; + p.z += (v.z >= 0)? mSize.z: -mSize.z; + return p; +} + +Point3F BoxConvex::getVertex(S32 v) +{ + Point3F p = mCenter; + p.x += (v & 1)? mSize.x: -mSize.x; + p.y += (v & 2)? mSize.y: -mSize.y; + p.z += (v & 4)? mSize.z: -mSize.z; + return p; +} + +inline bool isOnPlane(Point3F p,PlaneF& plane) +{ + F32 dist = mDot(plane,p) + plane.d; + return dist < 0.1 && dist > -0.1; +} + +void BoxConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf) +{ + cf->material = 0; + cf->object = mObject; + + S32 v = 0; + v += (n.x >= 0)? 1: 0; + v += (n.y >= 0)? 2: 0; + v += (n.z >= 0)? 4: 0; + + PlaneF plane; + plane.set(getVertex(v),n); + + // Emit vertex and edge + S32 mask = 0; + Corner& corner = sCorner[v]; + mask |= isOnPlane(getVertex(corner.a),plane)? 1: 0; + mask |= isOnPlane(getVertex(corner.b),plane)? 2: 0; + mask |= isOnPlane(getVertex(corner.c),plane)? 4: 0; + + switch(mask) { + case 0: { + cf->mVertexList.increment(); + mat.mulP(getVertex(v),&cf->mVertexList.last()); + break; + } + case 1: + emitEdge(v,corner.a,mat,cf); + break; + case 2: + emitEdge(v,corner.b,mat,cf); + break; + case 4: + emitEdge(v,corner.c,mat,cf); + break; + case 1 | 2: + emitFace(corner.ab,mat,cf); + break; + case 2 | 4: + emitFace(corner.bc,mat,cf); + break; + case 1 | 4: + emitFace(corner.ac,mat,cf); + break; + } +} + +void BoxConvex::getPolyList(AbstractPolyList* list) +{ + list->setTransform(&getTransform(), getScale()); + list->setObject(getObject()); + + U32 base = list->addPoint(mCenter + Point3F(-mSize.x, -mSize.y, -mSize.z)); + list->addPoint(mCenter + Point3F( mSize.x, -mSize.y, -mSize.z)); + list->addPoint(mCenter + Point3F(-mSize.x, mSize.y, -mSize.z)); + list->addPoint(mCenter + Point3F( mSize.x, mSize.y, -mSize.z)); + list->addPoint(mCenter + Point3F(-mSize.x, -mSize.y, mSize.z)); + list->addPoint(mCenter + Point3F( mSize.x, -mSize.y, mSize.z)); + list->addPoint(mCenter + Point3F(-mSize.x, mSize.y, mSize.z)); + list->addPoint(mCenter + Point3F( mSize.x, mSize.y, mSize.z)); + + for (U32 i = 0; i < 6; i++) { + list->begin(0, i); + + list->vertex(base + sFace[i].vertex[0]); + list->vertex(base + sFace[i].vertex[1]); + list->vertex(base + sFace[i].vertex[2]); + list->vertex(base + sFace[i].vertex[3]); + + list->plane(base + sFace[i].vertex[0], + base + sFace[i].vertex[1], + base + sFace[i].vertex[2]); + list->end(); + } +} + + +void BoxConvex::emitEdge(S32 v1,S32 v2,const MatrixF& mat,ConvexFeature* cf) +{ + S32 vc = cf->mVertexList.size(); + cf->mVertexList.increment(2); + Point3F *vp = cf->mVertexList.begin(); + mat.mulP(getVertex(v1),&vp[vc]); + mat.mulP(getVertex(v2),&vp[vc + 1]); + + cf->mEdgeList.increment(); + ConvexFeature::Edge& edge = cf->mEdgeList.last(); + edge.vertex[0] = vc; + edge.vertex[1] = vc + 1; +} + +void BoxConvex::emitFace(S32 fi,const MatrixF& mat,ConvexFeature* cf) +{ + Face& face = sFace[fi]; + + // Emit vertices + S32 vc = cf->mVertexList.size(); + cf->mVertexList.increment(4); + Point3F *vp = cf->mVertexList.begin(); + for (S32 v = 0; v < 4; v++) + mat.mulP(getVertex(face.vertex[v]),&vp[vc + v]); + + // Emit edges + cf->mEdgeList.increment(4); + ConvexFeature::Edge* edge = cf->mEdgeList.end() - 4; + for (S32 e = 0; e < 4; e++) { + edge[e].vertex[0] = vc + e; + edge[e].vertex[1] = vc + ((e + 1) & 3); + } + + // Emit 2 triangle faces + cf->mFaceList.increment(2); + ConvexFeature::Face* ef = cf->mFaceList.end() - 2; + mat.getColumn(face.axis,&ef->normal); + if (face.flip) + ef[0].normal.neg(); + ef[1].normal = ef[0].normal; + ef[1].vertex[0] = ef[0].vertex[0] = vc; + ef[1].vertex[1] = ef[0].vertex[2] = vc + 2; + ef[0].vertex[1] = vc + 1; + ef[1].vertex[2] = vc + 3; +} + + + +const MatrixF& OrthoBoxConvex::getTransform() const +{ + Point3F translation; + Parent::getTransform().getColumn(3, &translation); + mOrthoMatrixCache.setColumn(3, translation); + return mOrthoMatrixCache; +} + diff --git a/collision/boxConvex.h b/collision/boxConvex.h new file mode 100644 index 0000000..5694f88 --- /dev/null +++ b/collision/boxConvex.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BOXCONVEX_H_ +#define _BOXCONVEX_H_ + +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif + + +//---------------------------------------------------------------------------- + +class BoxConvex: public Convex +{ + Point3F getVertex(S32 v); + void emitEdge(S32 v1,S32 v2,const MatrixF& mat,ConvexFeature* cf); + void emitFace(S32 fi,const MatrixF& mat,ConvexFeature* cf); +public: + // + Point3F mCenter; + VectorF mSize; + + BoxConvex() { mType = BoxConvexType; } + void init(SceneObject* obj) { mObject = obj; } + + Point3F support(const VectorF& v) const; + void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + void getPolyList(AbstractPolyList* list); +}; + + +class OrthoBoxConvex: public BoxConvex +{ + typedef BoxConvex Parent; + mutable MatrixF mOrthoMatrixCache; + + public: + OrthoBoxConvex() { mOrthoMatrixCache.identity(); } + + virtual const MatrixF& getTransform() const; +}; + +#endif diff --git a/collision/clippedPolyList.cpp b/collision/clippedPolyList.cpp new file mode 100644 index 0000000..1e43eba --- /dev/null +++ b/collision/clippedPolyList.cpp @@ -0,0 +1,431 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "collision/clippedPolyList.h" + +#include "math/mMath.h" +#include "console/console.h" +#include "platform/profiler.h" + +#include "core/tAlgorithm.h" + +bool ClippedPolyList::allowClipping = true; + + +//---------------------------------------------------------------------------- + +ClippedPolyList::ClippedPolyList() +{ + VECTOR_SET_ASSOCIATION(mPolyList); + VECTOR_SET_ASSOCIATION(mVertexList); + VECTOR_SET_ASSOCIATION(mIndexList); + VECTOR_SET_ASSOCIATION(mPolyPlaneList); + VECTOR_SET_ASSOCIATION(mPlaneList); + VECTOR_SET_ASSOCIATION(mNormalList); + + mNormal.set(0,0,0); + mIndexList.reserve(IndexListReserveSize); +} + +ClippedPolyList::~ClippedPolyList() +{ +} + + +//---------------------------------------------------------------------------- + +void ClippedPolyList::clear() +{ + // Only clears internal data + mPolyList.clear(); + mVertexList.clear(); + mIndexList.clear(); + mPolyPlaneList.clear(); + mNormalList.clear(); +} + +bool ClippedPolyList::isEmpty() const +{ + return mPolyList.size() == 0; +} + + +//---------------------------------------------------------------------------- + +U32 ClippedPolyList::addPoint(const Point3F& p) +{ + mVertexList.increment(); + Vertex& v = mVertexList.last(); + v.point.x = p.x * mScale.x; + v.point.y = p.y * mScale.y; + v.point.z = p.z * mScale.z; + mMatrix.mulP(v.point); + + // Build the plane mask + register U32 mask = 1; + register S32 count = mPlaneList.size(); + register PlaneF * plane = mPlaneList.address(); + + v.mask = 0; + while(--count >= 0) { + if (plane++->distToPlane(v.point) > 0) + v.mask |= mask; + mask <<= 1; + } + + return mVertexList.size() - 1; +} + + +U32 ClippedPolyList::addPlane(const PlaneF& plane) +{ + mPolyPlaneList.increment(); + mPlaneTransformer.transform(plane, mPolyPlaneList.last()); + + return mPolyPlaneList.size() - 1; +} + + +//---------------------------------------------------------------------------- + +void ClippedPolyList::begin(BaseMatInstance* material,U32 surfaceKey) +{ + mPolyList.increment(); + Poly& poly = mPolyList.last(); + poly.object = mCurrObject; + poly.material = material; + poly.vertexStart = mIndexList.size(); + poly.surfaceKey = surfaceKey; + + poly.polyFlags = 0; + if(ClippedPolyList::allowClipping) + poly.polyFlags = CLIPPEDPOLYLIST_FLAG_ALLOWCLIPPING; +} + + +//---------------------------------------------------------------------------- + +void ClippedPolyList::plane(U32 v1,U32 v2,U32 v3) +{ + mPolyList.last().plane.set(mVertexList[v1].point, + mVertexList[v2].point,mVertexList[v3].point); +} + +void ClippedPolyList::plane(const PlaneF& p) +{ + mPlaneTransformer.transform(p, mPolyList.last().plane); +} + +void ClippedPolyList::plane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + mPolyList.last().plane = mPolyPlaneList[index]; +} + +const PlaneF& ClippedPolyList::getIndexedPlane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + return mPolyPlaneList[index]; +} + + +//---------------------------------------------------------------------------- + +void ClippedPolyList::vertex(U32 vi) +{ + mIndexList.push_back(vi); +} + + +//---------------------------------------------------------------------------- + +void ClippedPolyList::end() +{ + Poly& poly = mPolyList.last(); + + // Anything facing away from the mNormal is rejected + if (mDot(poly.plane,mNormal) > 0) { + mIndexList.setSize(poly.vertexStart); + mPolyList.decrement(); + return; + } + + // Build initial inside/outside plane masks + U32 indexStart = poly.vertexStart; + U32 vertexCount = mIndexList.size() - indexStart; + + U32 frontMask = 0,backMask = 0; + U32 i; + for (i = indexStart; i < mIndexList.size(); i++) { + U32 mask = mVertexList[mIndexList[i]].mask; + frontMask |= mask; + backMask |= ~mask; + } + + // Trivial accept if all the vertices are on the backsides of + // all the planes. + if (!frontMask) { + poly.vertexCount = vertexCount; + return; + } + + // Trivial reject if any plane not crossed has all it's points + // on the front. + U32 crossMask = frontMask & backMask; + if (~crossMask & frontMask) { + mIndexList.setSize(poly.vertexStart); + mPolyList.decrement(); + return; + } + + // Potentially, this will add up to mPlaneList.size() * (indexStart - indexEnd) + // elements to mIndexList, so ensure that it has enough space to store that + // so we can use push_back_noresize. If you find this code block getting hit + // frequently, changing the value of 'IndexListReserveSize' or doing some selective + // allocation is suggested + // + // TODO: Re-visit this, since it obviously does not work correctly, and than + // re-enable the push_back_noresize + //while(mIndexList.size() + mPlaneList.size() * (mIndexList.size() - indexStart) > mIndexList.capacity() ) + // mIndexList.reserve(mIndexList.capacity() * 2); + + // Need to do some clipping + for (U32 p = 0; p < mPlaneList.size(); p++) { + U32 pmask = 1 << p; + // Only test against this plane if we have something + // on both sides + if (crossMask & pmask) { + U32 indexEnd = mIndexList.size(); + U32 i1 = indexEnd - 1; + U32 mask1 = mVertexList[mIndexList[i1]].mask; + + for (U32 i2 = indexStart; i2 < indexEnd; i2++) { + U32 mask2 = mVertexList[mIndexList[i2]].mask; + if ((mask1 ^ mask2) & pmask) { + // + mVertexList.increment(); + VectorF& v1 = mVertexList[mIndexList[i1]].point; + VectorF& v2 = mVertexList[mIndexList[i2]].point; + VectorF vv = v2 - v1; + F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv); + + mIndexList.push_back/*_noresize*/(mVertexList.size() - 1); + Vertex& iv = mVertexList.last(); + iv.point.x = v1.x + vv.x * t; + iv.point.y = v1.y + vv.y * t; + iv.point.z = v1.z + vv.z * t; + iv.mask = 0; + + // Test against the remaining planes + for (U32 i = p + 1; i < mPlaneList.size(); i++) + if (mPlaneList[i].distToPlane(iv.point) > 0) { + iv.mask = 1 << i; + break; + } + } + if (!(mask2 & pmask)) { + U32 index = mIndexList[i2]; + mIndexList.push_back/*_noresize*/(index); + } + mask1 = mask2; + i1 = i2; + } + + // Check for degenerate + indexStart = indexEnd; + if (mIndexList.size() - indexStart < 3) { + mIndexList.setSize(poly.vertexStart); + mPolyList.decrement(); + return; + } + } + } + + // Emit what's left and compress the index list. + poly.vertexCount = mIndexList.size() - indexStart; + memcpy(&mIndexList[poly.vertexStart], + &mIndexList[indexStart],poly.vertexCount); + mIndexList.setSize(poly.vertexStart + poly.vertexCount); +} + + +//---------------------------------------------------------------------------- + +void ClippedPolyList::memcpy(U32* dst, U32* src,U32 size) +{ + U32* end = src + size; + while (src != end) + *dst++ = *src++; +} + +void ClippedPolyList::cullUnusedVerts() +{ + PROFILE_SCOPE( ClippedPolyList_CullUnusedVerts ); + + U32 i = 0; + U32 k, n, numDeleted; + bool result; + + IndexListIterator iNextIter; + VertexListIterator nextVIter; + VertexListIterator vIter; + + for ( vIter = mVertexList.begin(); vIter != mVertexList.end(); vIter++, i++ ) + { + // Is this vertex used? + iNextIter = find( mIndexList.begin(), mIndexList.end(), i ); + if ( iNextIter != mIndexList.end() ) + continue; + + // If not, find the next used vertex. + + // i is an unused vertex + // k is a used vertex + // delete the vertices from i to j - 1 + k = 0; + n = i + 1; + result = false; + numDeleted = 0; + + for ( nextVIter = vIter + 1; nextVIter != mVertexList.end(); nextVIter++, n++ ) + { + iNextIter = find( mIndexList.begin(), mIndexList.end(), n ); + + // If we found a used vertex + // grab its index for later use + // and set our result bool. + if ( (*iNextIter) == n ) + { + k = n; + result = true; + break; + } + } + + // All the remaining verts are unused. + if ( !result ) + { + mVertexList.setSize( i ); + break; + } + + // Erase unused verts. + numDeleted = (k-1) - i + 1; + mVertexList.erase( i, numDeleted ); + + // Find any references to vertices after those deleted + // in the mIndexList and correct with an offset + for ( iNextIter = mIndexList.begin(); iNextIter != mIndexList.end(); iNextIter++ ) + { + if ( (*iNextIter) > i ) + (*iNextIter) -= numDeleted; + } + + // After the erase the current iter should + // point at the used vertex we found... the + // loop will continue with the next vert. + } +} + +void ClippedPolyList::triangulate() +{ + PROFILE_SCOPE( ClippedPolyList_Triangulate ); + + // Build into a new polylist and index list. + // + // TODO: There are potential performance issues + // here as we're not reserving enough space for + // new generated triangles. + // + // We need to either over estimate and shrink or + // better yet fix vector to internally grow in + // large chunks. + // + PolyList polyList; + polyList.reserve( mPolyList.size() ); + IndexList indexList; + indexList.reserve( mIndexList.size() ); + + U32 j, numTriangles; + + // + PolyListIterator polyIter = mPolyList.begin(); + for ( ; polyIter != mPolyList.end(); polyIter++ ) + { + const Poly &poly = *polyIter; + + // How many triangles in this poly? + numTriangles = poly.vertexCount - 2; + + // Build out the triangles. + for ( j = 0; j < numTriangles; j++ ) + { + polyList.increment(); + + Poly &triangle = polyList.last(); + triangle = poly; + triangle.vertexCount = 3; + triangle.vertexStart = indexList.size(); + + indexList.push_back( mIndexList[ poly.vertexStart ] ); + indexList.push_back( mIndexList[ poly.vertexStart + 1 + j ] ); + indexList.push_back( mIndexList[ poly.vertexStart + 2 + j ] ); + } + } + + mPolyList = polyList; + mIndexList = indexList; +} + +void ClippedPolyList::generateNormals() +{ + PROFILE_SCOPE( ClippedPolyList_GenerateNormals ); + + mNormalList.setSize( mVertexList.size() ); + + U32 i, polyCount; + VectorF normal; + PolyListIterator polyIter; + IndexListIterator indexIter; + + Vector::iterator normalIter = mNormalList.begin(); + U32 n = 0; + for ( ; normalIter != mNormalList.end(); normalIter++, n++ ) + { + // Average all the face normals which + // share this vertex index. + indexIter = mIndexList.begin(); + normal.zero(); + polyCount = 0; + i = 0; + + for ( ; indexIter != mIndexList.end(); indexIter++, i++ ) + { + if ( n != *indexIter ) + continue; + + polyIter = mPolyList.begin(); + for ( ; polyIter != mPolyList.end(); polyIter++ ) + { + const Poly& poly = *polyIter; + if ( i < poly.vertexStart || i > poly.vertexStart + poly.vertexCount ) + continue; + + ++polyCount; + normal += poly.plane; + } + } + + // Average it. + if ( polyCount > 0 ) + normal /= (F32)polyCount; + + // Note: we use a temporary for the normal averaging + // then copy the result to limit the number of arrays + // we're touching during the innermost loop. + *normalIter = normal; + } +} diff --git a/collision/clippedPolyList.h b/collision/clippedPolyList.h new file mode 100644 index 0000000..772bc8a --- /dev/null +++ b/collision/clippedPolyList.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CLIPPEDPOLYLIST_H_ +#define _CLIPPEDPOLYLIST_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTORSPEC_H_ +#include "core/util/tVectorSpecializations.h" +#endif +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif + + +#define CLIPPEDPOLYLIST_FLAG_ALLOWCLIPPING 0x01 + + +/// The clipped polylist class takes the geometry passed to it and clips +/// it against the PlaneList set. +/// +/// It also contains helper functions for +/// @see AbstractPolyList +class ClippedPolyList : public AbstractPolyList +{ + void memcpy(U32* d, U32* s,U32 size); + +public: + struct Vertex { + Point3F point; + U32 mask; + }; + + struct Poly { + PlaneF plane; + SceneObject* object; + BaseMatInstance* material; + + U32 vertexStart; + U32 vertexCount; + U32 surfaceKey; + U32 polyFlags; + }; + + /// ??? + static bool allowClipping; + + typedef Vector PlaneList; + typedef Vector VertexList; + typedef Vector PolyList; + typedef FastVector IndexList; + + typedef PlaneList::iterator PlaneListIterator; + typedef VertexList::iterator VertexListIterator; + typedef PolyList::iterator PolyListIterator; + typedef IndexList::iterator IndexListIterator; + + // Internal data + PolyList mPolyList; + VertexList mVertexList; + IndexList mIndexList; + + const static U32 IndexListReserveSize = 128; + + /// The per-vertex normals. + /// @see generateNormals() + Vector mNormalList; + + PlaneList mPolyPlaneList; + + /// The list of planes to clip against. + /// + /// This should be set before filling the polylist. + PlaneList mPlaneList; + + /// If non-zero any poly facing away from this + /// normal is removed from the list. + /// + /// This should be set before filling the polylist. + VectorF mNormal; + + // + ClippedPolyList(); + ~ClippedPolyList(); + void clear(); + + // AbstractPolyList + bool isEmpty() const; + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(BaseMatInstance* material,U32 surfaceKey); + void plane(U32 v1,U32 v2,U32 v3); + void plane(const PlaneF& p); + void plane(const U32 index); + void vertex(U32 vi); + void end(); + + /// Often after clipping you'll end up with orphan verticies + /// that are unused by the poly list. This removes these unused + /// verts and updates the index list. + void cullUnusedVerts(); + + /// This breaks all polys in the polylist into triangles. + void triangulate(); + + /// Generates averaged normals from the poly normals. + /// @see mNormalList + void generateNormals(); + + protected: + + // AbstractPolyList + const PlaneF& getIndexedPlane(const U32 index); +}; + + +#endif diff --git a/collision/collision.h b/collision/collision.h new file mode 100644 index 0000000..1f93dc6 --- /dev/null +++ b/collision/collision.h @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLISION_H_ +#define _COLLISION_H_ + +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif +#ifndef _MPLANE_H_ +#include "math/mPlane.h" +#endif +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif + +class SceneObject; +class BaseMatInstance; + +//---------------------------------------------------------------------------- + +struct Collision +{ + SceneObject* object; + Point3F point; + VectorF normal; + BaseMatInstance* material; + Point2F texCoord; + + // Face and Face dot are currently only set by the extrudedPolyList + // clipper. Values are otherwise undefined. + U32 face; // Which face was hit + F32 faceDot; // -Dot of face with poly normal + F32 distance; + + Collision() : + material( 0 ) + { + } +}; + +class CollisionList +{ +public: + enum + { + MaxCollisions = 64 + }; + +protected: + dsize_t mCount; + Collision mCollision[MaxCollisions]; + F32 mT; + // MaxHeight is currently only set by the extrudedPolyList + // clipper. It represents the maximum vertex z value of + // the returned collision surfaces. + F32 mMaxHeight; + +public: + // Constructor + CollisionList( /* const dsize_t reserveSize = MaxCollisions */ ) : + mCount( 0 ), mT( 0.0f ), mMaxHeight( 0.0f ) + { + + } + + // Accessors + int getCount() const { return mCount; } + F32 getTime() const { return mT; } + F32 getMaxHeight() const { return mMaxHeight; } + + const Collision &operator[] ( const dsize_t idx ) const + { + AssertFatal( idx < mCount, "Out of bounds index." ); + return mCollision[idx]; + } + + Collision &operator[] ( const dsize_t idx ) + { + AssertFatal( idx < mCount, "Out of bounds index." ); + return mCollision[idx]; + } + + // Increment does NOT reset the collision which it returns. It is the job of + // the caller to make sure that the entry has data properly assigned to it. + Collision &increment() + { + return mCollision[mCount++]; + } + + void clear() + { + mCount = 0; + } + + void setTime( const F32 t ) + { + mT = t; + } + + void setMaxHeight( const F32 height ) + { + mMaxHeight = height; + } +}; + + +//---------------------------------------------------------------------------- +// BSP Collision tree +// Solid nodes are represented by structures with NULL frontNode and +// backNode pointers. The material field is only valid on a solid node. +// There is no structure for empty nodes, frontNode or backNode +// should be set to NULL to represent empty half-spaces. + +struct BSPNode +{ + U32 material; + PlaneF plane; + BSPNode *frontNode, *backNode; +}; + +typedef Chunker BSPTree; + +/// Extension of the collision structure to allow use with raycasting. +/// @see Collision +struct RayInfo : public Collision +{ + RayInfo() : userData( NULL ) {} + + // The collision struct has object, point, normal & material. + + /// Distance along ray to contact point. + F32 t; + + /// Set the point of intersection according to t and the given ray. + /// + /// Several pieces of code will not use ray information but rather rely + /// on contact points directly, so it is a good thing to always set + /// this in castRay functions. + void setContactPoint( const Point3F& start, const Point3F& end ) + { + Point3F startToEnd = end - start; + startToEnd *= t; + point = startToEnd + start; + } + + /// A generic data void pointer. + void *userData; +}; + + +#endif // _COLLISION_H_ diff --git a/collision/concretePolyList.cpp b/collision/concretePolyList.cpp new file mode 100644 index 0000000..7d980de --- /dev/null +++ b/collision/concretePolyList.cpp @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMath.h" +#include "console/console.h" +#include "collision/concretePolyList.h" +#include "gfx/gfxDevice.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxStateBlock.h" + + +//---------------------------------------------------------------------------- + +ConcretePolyList::ConcretePolyList() +{ + VECTOR_SET_ASSOCIATION(mPolyList); + VECTOR_SET_ASSOCIATION(mVertexList); + VECTOR_SET_ASSOCIATION(mIndexList); + VECTOR_SET_ASSOCIATION(mPolyPlaneList); + + mIndexList.reserve(100); +} + +ConcretePolyList::~ConcretePolyList() +{ + +} + + +//---------------------------------------------------------------------------- +void ConcretePolyList::clear() +{ + // Only clears internal data + mPolyList.clear(); + mVertexList.clear(); + mIndexList.clear(); + mPolyPlaneList.clear(); +} + +//---------------------------------------------------------------------------- + +U32 ConcretePolyList::addPoint(const Point3F& p) +{ + mVertexList.increment(); + Point3F& v = mVertexList.last(); + v.x = p.x * mScale.x; + v.y = p.y * mScale.y; + v.z = p.z * mScale.z; + mMatrix.mulP(v); + + return mVertexList.size() - 1; +} + + +U32 ConcretePolyList::addPlane(const PlaneF& plane) +{ + mPolyPlaneList.increment(); + mPlaneTransformer.transform(plane, mPolyPlaneList.last()); + + return mPolyPlaneList.size() - 1; +} + + +//---------------------------------------------------------------------------- + +void ConcretePolyList::begin(BaseMatInstance* material,U32 surfaceKey) +{ + mPolyList.increment(); + Poly& poly = mPolyList.last(); + poly.object = mCurrObject; + poly.material = material; + poly.vertexStart = mIndexList.size(); + poly.surfaceKey = surfaceKey; +} + + +//---------------------------------------------------------------------------- + +void ConcretePolyList::plane(U32 v1,U32 v2,U32 v3) +{ + mPolyList.last().plane.set(mVertexList[v1], + mVertexList[v2],mVertexList[v3]); +} + +void ConcretePolyList::plane(const PlaneF& p) +{ + mPlaneTransformer.transform(p, mPolyList.last().plane); +} + +void ConcretePolyList::plane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + mPolyList.last().plane = mPolyPlaneList[index]; +} + +const PlaneF& ConcretePolyList::getIndexedPlane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + return mPolyPlaneList[index]; +} + + +//---------------------------------------------------------------------------- + +void ConcretePolyList::vertex(U32 vi) +{ + mIndexList.push_back(vi); +} + + +//---------------------------------------------------------------------------- + +bool ConcretePolyList::isEmpty() const +{ + return mPolyList.empty(); +} + +void ConcretePolyList::end() +{ + Poly& poly = mPolyList.last(); + poly.vertexCount = mIndexList.size() - poly.vertexStart; +} + +void ConcretePolyList::render() +{ + GFXStateBlockDesc solidZDisable; + solidZDisable.setCullMode( GFXCullNone ); + solidZDisable.setZReadWrite( false, false ); + GFXStateBlockRef sb = GFX->createStateBlock( solidZDisable ); + GFX->setStateBlock( sb ); + + PrimBuild::color3i( 255, 0, 255 ); + + Poly *p; + Point3F *pnt; + + for ( p = mPolyList.begin(); p < mPolyList.end(); p++ ) + { + PrimBuild::begin( GFXLineStrip, p->vertexCount + 1 ); + + for ( U32 i = 0; i < p->vertexCount; i++ ) + { + pnt = &mVertexList[mIndexList[p->vertexStart + i]]; + PrimBuild::vertex3fv( pnt ); + } + + pnt = &mVertexList[mIndexList[p->vertexStart]]; + PrimBuild::vertex3fv( pnt ); + + PrimBuild::end(); + } +} \ No newline at end of file diff --git a/collision/concretePolyList.h b/collision/concretePolyList.h new file mode 100644 index 0000000..ba09746 --- /dev/null +++ b/collision/concretePolyList.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONCRETEPOLYLIST_H_ +#define _CONCRETEPOLYLIST_H_ + +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif + +/// A concrete, renderable PolyList +/// +/// This class is used to store geometry from a PolyList query. +/// +/// It allows you to render this data, as well. +/// +/// @see AbstractPolyList +class ConcretePolyList : public AbstractPolyList +{ + public: + + struct Poly { + PlaneF plane; + SceneObject* object; + BaseMatInstance* material; + U32 vertexStart; + U32 vertexCount; + U32 surfaceKey; + + Poly() + { + object = NULL; + material = NULL; + } + }; + + typedef Vector PlaneList; + typedef Vector VertexList; + typedef Vector PolyList; + typedef Vector IndexList; + + PolyList mPolyList; + VertexList mVertexList; + IndexList mIndexList; + + PlaneList mPolyPlaneList; + + public: + ConcretePolyList(); + ~ConcretePolyList(); + void clear(); + + // Virtual methods + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(BaseMatInstance* material,U32 surfaceKey); + void plane(U32 v1,U32 v2,U32 v3); + void plane(const PlaneF& p); + void plane(const U32 index); + void vertex(U32 vi); + void end(); + void render(); + + bool isEmpty() const; + + protected: + const PlaneF& getIndexedPlane(const U32 index); +}; + +#endif // _H_EARLYOUTPOLYLIST_ diff --git a/collision/convex.cpp b/collision/convex.cpp new file mode 100644 index 0000000..ed792bd --- /dev/null +++ b/collision/convex.cpp @@ -0,0 +1,673 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/types.h" +#include "core/dataChunker.h" +#include "collision/collision.h" +#include "sceneGraph/sceneObject.h" +#include "collision/convex.h" +#include "collision/gjk.h" +#include "collision/concretePolyList.h" + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +static DataChunker sChunker; + +CollisionStateList CollisionStateList::sFreeList; +CollisionWorkingList CollisionWorkingList::sFreeList; +F32 sqrDistanceEdges(const Point3F& start0, + const Point3F& end0, + const Point3F& start1, + const Point3F& end1, + Point3F* is, + Point3F* it); + + +//---------------------------------------------------------------------------- +// Collision State +//---------------------------------------------------------------------------- + +CollisionState::CollisionState() +{ + mLista = mListb = 0; +} + +CollisionState::~CollisionState() +{ + if (mLista) + mLista->free(); + if (mListb) + mListb->free(); +} + +void CollisionState::swap() +{ +} + +void CollisionState::set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w) +{ +} + + +F32 CollisionState::distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist, + const MatrixF* w2a, const MatrixF* _w2b) +{ + return 0; +} + +//---------------------------------------------------------------------------- +// Feature Collision +//---------------------------------------------------------------------------- + +bool ConvexFeature::collide(ConvexFeature& cf,CollisionList* cList, F32 tol) +{ + // Our vertices vs. other faces + const Point3F* vert = mVertexList.begin(); + const Point3F* vend = mVertexList.end(); + while (vert != vend) { + cf.testVertex(*vert,cList,false, tol); + vert++; + } + + // Other vertices vs. our faces + vert = cf.mVertexList.begin(); + vend = cf.mVertexList.end(); + while (vert != vend) { + U32 storeCount = cList->getCount(); + testVertex(*vert,cList,true, tol); + + // Fix up last reference. material and object are copied from this rather + // than the object we're colliding against. + if (storeCount != cList->getCount()) + { + Collision &col = (*cList)[cList->getCount() - 1]; + col.material = cf.material; + col.object = cf.object; + } + vert++; + } + + // Edge vs. Edge + const Edge* edge = mEdgeList.begin(); + const Edge* eend = mEdgeList.end(); + while (edge != eend) { + cf.testEdge(this,mVertexList[edge->vertex[0]], + mVertexList[edge->vertex[1]],cList, tol); + edge++; + } + + return true; +} + +inline bool isInside(const Point3F& p, const Point3F& a, const Point3F& b, const VectorF& n) +{ + VectorF v; + mCross(n,b - a,&v); + return mDot(v,p - a) < 0.0f; +} + +void ConvexFeature::testVertex(const Point3F& v,CollisionList* cList,bool flip, F32 tol) +{ + // Test vertex against all faces + const Face* face = mFaceList.begin(); + const Face* end = mFaceList.end(); + for (; face != end; face++) { + if (cList->getCount() >= CollisionList::MaxCollisions) + return; + + const Point3F& p0 = mVertexList[face->vertex[0]]; + const Point3F& p1 = mVertexList[face->vertex[1]]; + const Point3F& p2 = mVertexList[face->vertex[2]]; + + // Point near the plane? + F32 distance = mDot(face->normal,v - p0); + if (distance > tol || distance < -tol) + continue; + + // Make sure it's within the bounding edges + if (isInside(v,p0,p1,face->normal) && isInside(v,p1,p2,face->normal) && + isInside(v,p2,p0,face->normal)) { + + // Add collision to this face + Collision& info = cList->increment(); + info.point = v; + info.normal = face->normal; + if (flip) + info.normal.neg(); + info.material = material; + info.object = object; + info.distance = distance; + } + } +} + +void ConvexFeature::testEdge(ConvexFeature* cf,const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol) +{ + F32 tolSquared = tol*tol; + + // Test edges against edges + const Edge* edge = mEdgeList.begin(); + const Edge* end = mEdgeList.end(); + for (; edge != end; edge++) { + if (cList->getCount() >= CollisionList::MaxCollisions) + return; + + const Point3F& s2 = mVertexList[edge->vertex[0]]; + const Point3F& e2 = mVertexList[edge->vertex[1]]; + + // Get the distance and closest points + Point3F i1,i2; + F32 distance = sqrDistanceEdges(s1, e1, s2, e2, &i1, &i2); + if (distance > tolSquared) + continue; + distance = mSqrt(distance); + + // Need to figure out how to orient the collision normal. + // The current test involves checking to see whether the collision + // points are contained within the convex volumes, which is slow. + if (inVolume(i1) || cf->inVolume(i2)) + distance = -distance; + + // Contact normal + VectorF normal = i1 - i2; + if ( mIsZero( distance ) ) + normal.zero(); + else + normal *= 1 / distance; + + // Return a collision + Collision& info = cList->increment(); + info.point = i1; + info.normal = normal; + info.distance = distance; + info.material = material; + info.object = object; + } +} + +bool ConvexFeature::inVolume(const Point3F& v) +{ + // Test the point to see if it's inside the volume + const Face* face = mFaceList.begin(); + const Face* end = mFaceList.end(); + for (; face != end; face++) { + const Point3F& p0 = mVertexList[face->vertex[0]]; + if (mDot(face->normal,v - p0) > 0) + return false; + } + return true; +} + + +//---------------------------------------------------------------------------- +// Collision State management +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +CollisionStateList::CollisionStateList() +{ + mPrev = mNext = this; + mState = NULL; +} + +void CollisionStateList::linkAfter(CollisionStateList* ptr) +{ + mPrev = ptr; + mNext = ptr->mNext; + ptr->mNext = this; + mNext->mPrev = this; +} + +void CollisionStateList::unlink() +{ + mPrev->mNext = mNext; + mNext->mPrev = mPrev; + mPrev = mNext = this; +} + +CollisionStateList* CollisionStateList::alloc() +{ + if (!sFreeList.isEmpty()) { + CollisionStateList* nxt = sFreeList.mNext; + nxt->unlink(); + nxt->mState = NULL; + return nxt; + } + return constructInPlace((CollisionStateList*)sChunker.alloc(sizeof(CollisionStateList))); +} + +void CollisionStateList::free() +{ + unlink(); + linkAfter(&sFreeList); +} + + +//---------------------------------------------------------------------------- + +CollisionWorkingList::CollisionWorkingList() +{ + wLink.mPrev = wLink.mNext = this; + rLink.mPrev = rLink.mNext = this; +} + +void CollisionWorkingList::wLinkAfter(CollisionWorkingList* ptr) +{ + wLink.mPrev = ptr; + wLink.mNext = ptr->wLink.mNext; + ptr->wLink.mNext = this; + wLink.mNext->wLink.mPrev = this; +} + +void CollisionWorkingList::rLinkAfter(CollisionWorkingList* ptr) +{ + rLink.mPrev = ptr; + rLink.mNext = ptr->rLink.mNext; + ptr->rLink.mNext = this; + rLink.mNext->rLink.mPrev = this; +} + +void CollisionWorkingList::unlink() +{ + wLink.mPrev->wLink.mNext = wLink.mNext; + wLink.mNext->wLink.mPrev = wLink.mPrev; + wLink.mPrev = wLink.mNext = this; + + rLink.mPrev->rLink.mNext = rLink.mNext; + rLink.mNext->rLink.mPrev = rLink.mPrev; + rLink.mPrev = rLink.mNext = this; +} + +CollisionWorkingList* CollisionWorkingList::alloc() +{ + if (sFreeList.wLink.mNext != &sFreeList) { + CollisionWorkingList* nxt = sFreeList.wLink.mNext; + nxt->unlink(); + return nxt; + } + return constructInPlace((CollisionWorkingList*)sChunker.alloc(sizeof(CollisionWorkingList))); +} + +void CollisionWorkingList::free() +{ + unlink(); + wLinkAfter(&sFreeList); +} + + +//---------------------------------------------------------------------------- +// Convex Base Class +//---------------------------------------------------------------------------- + +U32 Convex::sTag = (U32)-1; + +//---------------------------------------------------------------------------- + +Convex::Convex() +{ + mNext = mPrev = this; + mTag = 0; +} + +Convex::~Convex() +{ + // Unlink from Convex Database + unlink(); + + // Delete collision states + while (mList.mNext != &mList) + delete mList.mNext->mState; + + // Free up working list + while (mWorking.wLink.mNext != &mWorking) + mWorking.wLink.mNext->free(); + + // Free up references + while (mReference.rLink.mNext != &mReference) + mReference.rLink.mNext->free(); +} + + +//---------------------------------------------------------------------------- + +void Convex::collectGarbage() +{ + // Delete unreferenced Convex Objects + for (Convex* itr = mNext; itr != this; itr = itr->mNext) { + if (itr->mReference.rLink.mNext == &itr->mReference) { + Convex* ptr = itr; + itr = itr->mPrev; + delete ptr; + } + } +} + +void Convex::nukeList() +{ + // Delete all Convex Objects + for (Convex* itr = mNext; itr != this; itr = itr->mNext) { + Convex* ptr = itr; + itr = itr->mPrev; + delete ptr; + } +} + +void Convex::registerObject(Convex *convex) +{ + convex->linkAfter(this); +} + + +//---------------------------------------------------------------------------- + +void Convex::linkAfter(Convex* ptr) +{ + mPrev = ptr; + mNext = ptr->mNext; + ptr->mNext = this; + mNext->mPrev = this; +} + +void Convex::unlink() +{ + mPrev->mNext = mNext; + mNext->mPrev = mPrev; + mPrev = mNext = this; +} + + +//---------------------------------------------------------------------------- + +Point3F Convex::support(const VectorF&) const +{ + return Point3F(0,0,0); +} + +void Convex::getFeatures(const MatrixF&,const VectorF&,ConvexFeature* f) +{ + f->object = NULL; +} + +const MatrixF& Convex::getTransform() const +{ + return mObject->getTransform(); +} + +const Point3F& Convex::getScale() const +{ + return mObject->getScale(); +} + +Box3F Convex::getBoundingBox() const +{ + return mObject->getWorldBox(); +} + +Box3F Convex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const +{ + Box3F wBox = mObject->getObjBox(); + wBox.minExtents.convolve(scale); + wBox.maxExtents.convolve(scale); + mat.mul(wBox); + return wBox; +} + +//---------------------------------------------------------------------------- + +void Convex::addToWorkingList(Convex* ptr) +{ + CollisionWorkingList* cl = CollisionWorkingList::alloc(); + cl->wLinkAfter(&mWorking); + cl->rLinkAfter(&ptr->mReference); + cl->mConvex = ptr; +}; + + +//---------------------------------------------------------------------------- + +void Convex::updateWorkingList(const Box3F& box, const U32 colMask) +{ + sTag++; + + // Clear objects off the working list that are no longer intersecting + for (CollisionWorkingList* itr = mWorking.wLink.mNext; itr != &mWorking; itr = itr->wLink.mNext) { + itr->mConvex->mTag = sTag; + if ((!box.isOverlapped(itr->mConvex->getBoundingBox())) || (!itr->mConvex->getObject()->isCollisionEnabled())) { + CollisionWorkingList* cl = itr; + itr = itr->wLink.mPrev; + cl->free(); + } + } + + // Special processing for the terrain and interiors... + AssertFatal(mObject->getContainer(), "Must be in a container!"); + + SimpleQueryList sql; + mObject->getContainer()->findObjects(box, colMask,SimpleQueryList::insertionCallback, &sql); + for (U32 i = 0; i < sql.mList.size(); i++) + sql.mList[i]->buildConvex(box, this); +} + +// --------------------------------------------------------------------------- + +void Convex::updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement) +{ + PROFILE_SCOPE( Convex_UpdateStateList ); + + Box3F box1 = getBoundingBox(mat, scale); + box1.minExtents -= Point3F(1, 1, 1); + box1.maxExtents += Point3F(1, 1, 1); + if (displacement) { + Point3F oldMin = box1.minExtents; + Point3F oldMax = box1.maxExtents; + + box1.minExtents.setMin(oldMin + *displacement); + box1.minExtents.setMin(oldMax + *displacement); + box1.maxExtents.setMax(oldMin + *displacement); + box1.maxExtents.setMax(oldMax + *displacement); + } + sTag++; + + // Destroy states which are no longer intersecting + for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) { + Convex* cv = (itr->mState->a == this)? itr->mState->b: itr->mState->a; + cv->mTag = sTag; + if (!box1.isOverlapped(cv->getBoundingBox())) { + CollisionState* cs = itr->mState; + itr = itr->mPrev; + delete cs; + } + } + + // Add collision states for new overlapping objects + for (CollisionWorkingList* itr0 = mWorking.wLink.mNext; itr0 != &mWorking; itr0 = itr0->wLink.mNext) { + register Convex* cv = itr0->mConvex; + if (cv->mTag != sTag && box1.isOverlapped(cv->getBoundingBox())) { + CollisionState* state = new GjkCollisionState; + state->set(this,cv,mat,cv->getTransform()); + state->mLista->linkAfter(&mList); + state->mListb->linkAfter(&cv->mList); + } + } +} + + +//---------------------------------------------------------------------------- + +CollisionState* Convex::findClosestState(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist) +{ + PROFILE_SCOPE( Convex_FindClosestState ); + + updateStateList(mat, scale); + F32 dist = +1E30f; + CollisionState *st = 0; + + // Prepare scaled version of transform + MatrixF axform = mat; + axform.scale(scale); + MatrixF axforminv(true); + MatrixF temp(mat); + axforminv.scale(Point3F(1.0f/scale.x, 1.0f/scale.y, 1.0f/scale.z)); + temp.affineInverse(); + axforminv.mul(temp); + + for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) + { + CollisionState* state = itr->mState; + if (state->mLista != itr) + state->swap(); + + // Prepare scaled version of transform + MatrixF bxform = state->b->getTransform(); + temp = bxform; + Point3F bscale = state->b->getScale(); + bxform.scale(bscale); + MatrixF bxforminv(true); + bxforminv.scale(Point3F(1.0f/bscale.x, 1.0f/bscale.y, 1.0f/bscale.z)); + temp.affineInverse(); + bxforminv.mul(temp); + + // + F32 dd = state->distance(axform, bxform, dontCareDist, &axforminv, &bxforminv); + if (dd < dist) + { + dist = dd; + st = state; + } + } + if (dist < dontCareDist) + return st; + else + return NULL; +} + + +//---------------------------------------------------------------------------- + +bool Convex::getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol) +{ + PROFILE_SCOPE( Convex_GetCollisionInfo ); + + for ( CollisionStateList* itr = mList.mNext; + itr != &mList; + itr = itr->mNext) + { + + CollisionState* state = itr->mState; + + if (state->mLista != itr) + state->swap(); + + if (state->dist <= tol) + { + ConvexFeature fa,fb; + VectorF v; + + // The idea is that we need to scale the matrix, so we need to + // make a copy of it, before we can pass it in to getFeatures. + // This is used to scale us for comparison against the other + // convex, which is correctly scaled. + MatrixF omat = mat; + omat.scale(scale); + + MatrixF imat = omat; + imat.inverse(); + imat.mulV(-state->v,&v); + + getFeatures(omat,v,&fa); + + imat = state->b->getTransform(); + imat.scale(state->b->getScale()); + + MatrixF bxform = imat; + imat.inverse(); + imat.mulV(state->v,&v); + + state->b->getFeatures(bxform,v,&fb); + + fa.collide(fb,cList,tol); + } + } + + return (cList->getCount() != 0); +} + +void Convex::getPolyList(AbstractPolyList*) +{ + +} + +void Convex::renderWorkingList() +{ + //bool rendered = false; + + CollisionWorkingList& rList = getWorkingList(); + CollisionWorkingList* pList = rList.wLink.mNext; + while (pList != &rList) { + Convex* pConvex = pList->mConvex; + pConvex->render(); + //rendered = true; + pList = pList->wLink.mNext; + } + + //Con::warnf( "convex rendered - %s", rendered ? "YES" : "NO" ); +} + +void Convex::render() +{ + ConcretePolyList polyList; + getPolyList( &polyList ); + polyList.render(); +} + +//----------------------------------------------------------------------------- +// This function based on code originally written for the book: +// 3D Game Engine Design, by David H. Eberly +// +F32 sqrDistanceEdges(const Point3F& start0, const Point3F& end0, + const Point3F& start1, const Point3F& end1, + Point3F* is, Point3F* it) +{ + Point3F direction0 = end0 - start0; + F32 fA00 = direction0.lenSquared(); + + Point3F direction1 = end1 - start1; + F32 fA11 = direction1.lenSquared(); + F32 fA01 = -mDot(direction0, direction1); + + Point3F kDiff = start0 - start1; + F32 fC = kDiff.lenSquared(); + F32 fB0 = mDot(kDiff, direction0); + F32 fDet = mFabs(fA00*fA11 - fA01*fA01); + + // Since the endpoints are tested as vertices, we're not interested + // in parallel lines, and intersections that don't involve end-points. + if (fDet >= 0.00001) { + + // Calculate time of intersection for each line + F32 fB1 = -mDot(kDiff, direction1); + F32 fS = fA01*fB1-fA11*fB0; + F32 fT = fA01*fB0-fA00*fB1; + + // Only interested in collisions that don't involve the end points + if (fS >= 0.0 && fS <= fDet && fT >= 0.0 && fT <= fDet) { + F32 fInvDet = 1.0 / fDet; + fS *= fInvDet; + fT *= fInvDet; + F32 fSqrDist = (fS*(fA00*fS + fA01*fT + 2.0*fB0) + + fT*(fA01*fS + fA11*fT + 2.0*fB1) + fC); + + // Intersection points. + *is = start0 + direction0 * fS; + *it = start1 + direction1 * fT; + return mFabs(fSqrDist); + } + } + + // Return a large number in the cases where endpoints are involved. + return 1e10f; +} diff --git a/collision/convex.h b/collision/convex.h new file mode 100644 index 0000000..b5fad7c --- /dev/null +++ b/collision/convex.h @@ -0,0 +1,263 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONVEX_H_ +#define _CONVEX_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +struct Collision; +class CollisionList; +struct CollisionStateList; +class AbstractPolyList; +class SceneObject; +class BaseMatInstance; +class Convex; + + +//---------------------------------------------------------------------------- + +class ConvexFeature +{ +public: + struct Edge { + S32 vertex[2]; + }; + struct Face { + VectorF normal; + S32 vertex[3]; + }; + + Vector mVertexList; + Vector mEdgeList; + Vector mFaceList; + BaseMatInstance* material; + SceneObject* object; + + ConvexFeature() + : mVertexList(64), mEdgeList(128), mFaceList(64), material( 0 ) + { + VECTOR_SET_ASSOCIATION(mVertexList); + VECTOR_SET_ASSOCIATION(mEdgeList); + VECTOR_SET_ASSOCIATION(mFaceList); + } + + bool collide(ConvexFeature& cf,CollisionList* cList, F32 tol = 0.1); + void testVertex(const Point3F& v,CollisionList* cList,bool,F32 tol); + void testEdge(ConvexFeature* cf,const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol); + bool inVolume(const Point3F& v); +}; + + +//---------------------------------------------------------------------------- + +// TODO: This sucks... replace with registration object +// for assigning type ids. +enum ConvexType { + TSConvexType, + BoxConvexType, + TerrainConvexType, + InteriorConvexType, + ShapeBaseConvexType, + TSStaticConvexType, + AtlasChunkConvexType, ///< @deprecated + AtlasConvexType, + TSPolysoupConvexType, + MeshRoadConvexType, + ForestConvexType +}; + + +//---------------------------------------------------------------------------- + +struct CollisionState +{ + CollisionStateList* mLista; + CollisionStateList* mListb; + Convex* a; + Convex* b; + + F32 dist; // Current estimated distance + VectorF v; // Vector between closest points + + // + CollisionState(); + virtual ~CollisionState(); + virtual void swap(); + virtual void set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w); + virtual F32 distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist, + const MatrixF* w2a = NULL, const MatrixF* _w2b = NULL); +}; + + +//---------------------------------------------------------------------------- + +struct CollisionStateList +{ + static CollisionStateList sFreeList; + CollisionStateList* mNext; + CollisionStateList* mPrev; + CollisionState* mState; + + CollisionStateList(); + + void linkAfter(CollisionStateList* next); + void unlink(); + bool isEmpty() { return mNext == this; } + + static CollisionStateList* alloc(); + void free(); +}; + + +//---------------------------------------------------------------------------- + +struct CollisionWorkingList +{ + static CollisionWorkingList sFreeList; + struct WLink { + CollisionWorkingList* mNext; + CollisionWorkingList* mPrev; + } wLink; + struct RLink { + CollisionWorkingList* mNext; + CollisionWorkingList* mPrev; + } rLink; + Convex* mConvex; + + void wLinkAfter(CollisionWorkingList* next); + void rLinkAfter(CollisionWorkingList* next); + void unlink(); + CollisionWorkingList(); + + static CollisionWorkingList* alloc(); + void free(); +}; + + +//---------------------------------------------------------------------------- + +class Convex { + + /// @name Linked list management + /// @{ + + /// Next item in linked list of Convexes. + Convex* mNext; + + /// Previous item in linked list of Convexes. + Convex* mPrev; + + /// Insert this Convex after the provided convex + /// @param next + void linkAfter(Convex* next); + + /// Remove this Convex from the linked list + void unlink(); + /// @} + + U32 mTag; + static U32 sTag; + +protected: + CollisionStateList mList; ///< Objects we're testing against + CollisionWorkingList mWorking; ///< Objects within our bounds + CollisionWorkingList mReference; ///< Other convex testing against us + SceneObject* mObject; ///< Object this Convex is built around + ConvexType mType; ///< Type of Convex this is @see ConvexType + +public: + + /// Constructor + Convex(); + + /// Destructor + virtual ~Convex(); + + /// Registers another Convex by linking it after this one + void registerObject(Convex *convex); + + /// Runs through the linked list of Convex objects and removes the ones + /// with no references + void collectGarbage(); + + /// Deletes all convex objects in the list + void nukeList(); + + /// Returns the type of this Convex + ConvexType getType() { return mType; } + + /// Returns the object this Convex is built from + SceneObject* getObject() { return mObject; } + + /// Adds the provided Convex to the list of objects within the bounds of this Convex + /// @param ptr Convex to add to the working list of this object + void addToWorkingList(Convex* ptr); + + /// Returns the list of objects currently inside the bounds of this Convex + CollisionWorkingList& getWorkingList() { return mWorking; } + + /// Finds the closest + CollisionState* findClosestState(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist = 1); + + /// Returns the list of objects this object is testing against + CollisionStateList* getStateList() { return mList.mNext; } + + /// Updates the CollisionStateList (mList) with new collision states and removing + /// ones no longer under consideration + /// @param mat Used as the matrix to create a bounding box for updating the list + /// @param scale Used to scale the bounding box + /// @param displacement Bounding box displacement (optional) + void updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement = NULL); + + /// Updates the working collision list of objects which are currently colliding with + /// (inside the bounds of) this Convex. + /// + /// @param box Used as the bounding box. + /// @param colMask Mask of objects to check against. + void updateWorkingList(const Box3F& box, const U32 colMask); + + /// Returns the transform of the object this is built around + virtual const MatrixF& getTransform() const; + + /// Returns the scale of the object this is built around + virtual const Point3F& getScale() const; + + /// Returns the bounding box for the object this is built around in world space + virtual Box3F getBoundingBox() const; + + /// Returns the object space bounding box for the object this is built around + /// transformed and scaled + /// @param mat Matrix to transform the object-space box by + /// @param scale Scaling factor to scale the bounding box by + virtual Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const; + + /// Returns the farthest point, along a vector, still bound by the convex + /// @param v Vector + virtual Point3F support(const VectorF& v) const; + + /// + virtual void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + + /// Builds a collision poly list out of this convex + /// @param list (Out) Poly list built + virtual void getPolyList(AbstractPolyList* list); + + /// + bool getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol); + + /// Render convex(s) for debugging purposes. + virtual void renderWorkingList(); + + /// Render this convex for debugging purposes. + virtual void render(); +}; + +#endif // _CONVEX_H_ diff --git a/collision/depthSortList.cpp b/collision/depthSortList.cpp new file mode 100644 index 0000000..4a7b171 --- /dev/null +++ b/collision/depthSortList.cpp @@ -0,0 +1,833 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMath.h" +#include "console/console.h" +#include "collision/depthSortList.h" +#include "core/color.h" +#include "core/stream/fileStream.h" // TODO, remove this + +//---------------------------------------------------------------------------- + +// some defines and global parameters that affect poly split routine +F32 SPLIT_TOL = 0.0005f; +bool ALWAYS_RETURN_FRONT_AND_BACK = false; // if false, split routine will return polys only if a split occurs + +// more global parameters +F32 XZ_TOL = 0.0f; +F32 DEPTH_TOL = 0.01f; +#define MIN_Y_DOT 0.05f +DepthSortList * gCurrentSort = NULL; +S32 gForceOverlap = -1; // force an overlap test to result in an overlap +S32 gNoOverlapCount; +S32 gBadSpots = 0; + +//---------------------------------------------------------------------------- + +DepthSortList::DepthSortList() +{ + VECTOR_SET_ASSOCIATION(mPolyExtentsList); + VECTOR_SET_ASSOCIATION(mPolyIndexList); +} + +DepthSortList::~DepthSortList() +{ +} + +//---------------------------------------------------------------------------- + +void DepthSortList::clear() +{ + Parent::clear(); + + mPolyExtentsList.clear(); + mPolyIndexList.clear(); + + clearSort(); +} + +void DepthSortList::clearSort() +{ + mBase = -1; + mMaxTouched = 0; + gNoOverlapCount = 0; +} + +//---------------------------------------------------------------------------- + +void DepthSortList::end() +{ + S32 numPoly = mPolyList.size(); + + if (mPolyList.last().plane.y >= -MIN_Y_DOT) + { + mIndexList.setSize(mPolyList.last().vertexStart); + mPolyList.decrement(); + return; + } + + Parent::end(); + + // we deleted this poly, be sure not to add anything more... + if (mPolyList.size()!=numPoly) + return; + + AssertFatal(mPolyList.last().vertexCount>2,"DepthSortList::end: only two vertices in poly"); + + mPolyExtentsList.increment(); + setExtents(mPolyList.last(),mPolyExtentsList.last()); + mPolyIndexList.push_back(numPoly-1); +} + +//---------------------------------------------------------------------------- + +bool DepthSortList::getMapping(MatrixF * mat, Box3F * box) +{ + // return list transform and bounds in list space...optional + *mat = mMatrix; + mat->inverse(); + box->minExtents.set(-mExtent.x, 0.0f, -mExtent.z); + box->maxExtents.set( mExtent.x, 2.0f * mExtent.y, mExtent.z); + + return true; +} + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +void DepthSortList::setExtents(Poly & poly, PolyExtents & polyExtents) +{ + Point3F p = mVertexList[mIndexList[poly.vertexStart]].point; + polyExtents.xMin = polyExtents.xMax = p.x; + polyExtents.yMin = polyExtents.yMax = p.y; + polyExtents.zMin = polyExtents.zMax = p.z; + for (S32 i=poly.vertexStart+1; i polyExtents.xMax) + polyExtents.xMax = p.x; + + // y + if (p.y < polyExtents.yMin) + polyExtents.yMin = p.y; + else if (p.y > polyExtents.yMax) + polyExtents.yMax = p.y; + + // z + if (p.z < polyExtents.zMin) + polyExtents.zMin = p.z; + else if (p.z > polyExtents.zMax) + polyExtents.zMax = p.z; + } +} + +//---------------------------------------------------------------------------- + +// function for comparing two poly indices +S32 FN_CDECL compareYExtents( const void* e1, const void* e2) +{ + DepthSortList::PolyExtents & poly1 = gCurrentSort->getExtents(*(U32*)e1); + DepthSortList::PolyExtents & poly2 = gCurrentSort->getExtents(*(U32*)e2); + + if (poly1.yMin < poly2.yMin) + return -1; + if (poly2.yMin < poly1.yMin) + return 1; + return 0; +} + +//---------------------------------------------------------------------------- + +void DepthSortList::sortByYExtents() +{ + gCurrentSort = this; + dQsort(mPolyIndexList.address(),mPolyIndexList.size(),sizeof(U32),compareYExtents); +} + +//---------------------------------------------------------------------------- + +void DepthSortList::set(const MatrixF & mat, Point3F & extents) +{ + setBaseTransform(mat); + mNormal.set(0,1,0); // ignore polys not facing up... + mExtent = extents; + mExtent *= 0.5f; + + // set clip planes + mPlaneList.clear(); + + mPlaneList.increment(); + mPlaneList.last().set(-1.0f, 0.0f, 0.0f); + mPlaneList.last().d = -mExtent.x; + mPlaneList.increment(); + mPlaneList.last().set( 1.0f, 0.0f, 0.0f); + mPlaneList.last().d = -mExtent.x; + + mPlaneList.increment(); + mPlaneList.last().set( 0.0f,-1.0f, 0.0f); + mPlaneList.last().d = 0; + mPlaneList.increment(); + mPlaneList.last().set( 0.0f, 1.0f, 0.0f); + mPlaneList.last().d = -2.0f * mExtent.y; + + mPlaneList.increment(); + mPlaneList.last().set( 0.0f, 0.0f,-1.0f); + mPlaneList.last().d = -mExtent.z; + mPlaneList.increment(); + mPlaneList.last().set( 0.0f, 0.0f, 1.0f); + mPlaneList.last().d = -mExtent.z; +} + +//---------------------------------------------------------------------------- + +void DepthSortList::setBase(S32 base) +{ + mBase = base; + getOrderedPoly(mBase, &mBasePoly, &mBaseExtents); + mBaseNormal = &mBasePoly->plane; + mBaseDot = -mBasePoly->plane.d; + mBaseYMax = mBaseExtents->yMax; +} + +//---------------------------------------------------------------------------- + +bool DepthSortList::sortNext() +{ + // find the next poly in the depth order + // NOTE: a closer poly may occur before a farther away poly so long as + // they don't overlap in the x-z plane... + if (++mBase>=mPolyIndexList.size()) + return false; + + setBase(mBase); + + gBadSpots = 0; + ALWAYS_RETURN_FRONT_AND_BACK = false; // split routine will return polys only if a split occurs + + bool switched = false; // haven't switched base poly yet + S32 i = 0; // currently comparing base to base+i + Poly * testPoly; + PolyExtents * testExtents; + + while (mBase+i+1plane; + F32 testDot = -testPoly->plane.d; + + // if base poly's y extents don't overlap test poly's, base poly can stay where it is... + if (mBase+i>mMaxTouched && mBaseYMax<=testExtents->yMin+DEPTH_TOL) + break; + + // if base poly and test poly don't have overlapping x & z extents, then order doesn't matter...stay the same + if (mBaseExtents->xMin>=testExtents->xMax-XZ_TOL || mBaseExtents->xMax<=testExtents->xMin+XZ_TOL || + mBaseExtents->zMin>=testExtents->zMax-XZ_TOL || mBaseExtents->zMax<=testExtents->zMin+XZ_TOL) + continue; + + // is test poly completely behind base poly? if so, order is fine as it is + S32 v; + for (v=0; vvertexCount; v++) + if (mDot(mVertexList[mIndexList[testPoly->vertexStart+v]].point,*mBaseNormal)>mBaseDot+DEPTH_TOL) + break; + if (v==testPoly->vertexCount) + // test behind base + continue; + + // is base poly completely in front of test poly? if so, order is fine as it is + for (v=0; vvertexCount; v++) + if (mDot(mVertexList[mIndexList[mBasePoly->vertexStart+v]].point,testNormal)vertexCount) + // base in front of test + continue; + + // if the polys don't overlap in the x-z plane, then order doesn't matter, stay as we are + if (!overlap(mBasePoly,testPoly)) + { + gNoOverlapCount++; + if (gNoOverlapCount!=gForceOverlap) + continue; + } + + // handle switching/splitting of polys due to overlap + handleOverlap(testPoly,testNormal,testDot,i,switched); + } + return true; +} + +//---------------------------------------------------------------------------- + +void DepthSortList::sort() +{ + // depth sort mPolyIndexList -- entries are indices into mPolyList (where poly is found) & mPolyExtentsList + + // sort by min y extent + sortByYExtents(); + + mBase = -1; + + while (sortNext()) + ; +} + +//---------------------------------------------------------------------------- + +void DepthSortList::handleOverlap(Poly * testPoly, Point3F & testNormal, F32 testDot, S32 & testOffset, bool & switched) +{ + // first reverse the plane tests (i.e., test to see if basePoly behind testPoly or testPoly in front of basePoly... + // if either succeeds, switch poly + // if they both fail, split base poly + // But split anyway if basePoly has already been switched... + bool doSwitch = false; + + if (!switched) + { + S32 v; + for (v=0; vvertexCount; v++) + if (mDot(mVertexList[mIndexList[mBasePoly->vertexStart+v]].point,testNormal)>testDot+DEPTH_TOL) + break; + if (v==mBasePoly->vertexCount) + doSwitch = true; + else + { + for (v=0; vvertexCount; v++) + if (mDot(mVertexList[mIndexList[testPoly->vertexStart+v]].point,*mBaseNormal)vertexCount) + doSwitch = true; + } + } + + // try to split base poly along plane of test poly + Poly frontPoly, backPoly; + bool splitBase = false, splitTest = false; + if (!doSwitch) + { + splitBase = splitPoly(*mBasePoly,testNormal,testDot,frontPoly,backPoly); + if (!splitBase) + // didn't take...no splitting happened...try splitting test poly by base poly + splitTest = splitPoly(*testPoly,*mBaseNormal,mBaseDot,frontPoly,backPoly); + } + + U32 testIdx = mPolyIndexList[mBase+testOffset]; + + // should we switch order of test and base poly? Might have to even if we + // don't want to if there's no splitting to do... + // Note: possibility that infinite loop can be introduced here...if that happens, + // then we need to split along edges of polys + if (doSwitch || (!splitTest && !splitBase)) + { + if (!doSwitch && gBadSpots++ > (mPolyIndexList.size()-mBase)<<1) + // got here one too many times...just leave and don't touch poly -- avoid infinite loop + return; + + // move test poly to the front of the order + dMemmove(&mPolyIndexList[mBase+1],&mPolyIndexList[mBase],testOffset*sizeof(U32)); + mPolyIndexList[mBase] = testIdx; + + // base poly changed... + setBase(mBase); + + if (mBase+testOffset>mMaxTouched) + mMaxTouched=mBase+testOffset; + + testOffset=1; // don't need to compare against old base + switched=true; + return; + } + + if (splitBase) + { + // need one more spot...frontPoly and backPoly replace basePoly + setExtents(frontPoly,mPolyExtentsList[mPolyIndexList[mBase]]); + mPolyExtentsList.increment(); + setExtents(backPoly,mPolyExtentsList.last()); + + mPolyList[mPolyIndexList[mBase]] = frontPoly; + mPolyIndexList.insert(mBase+1); + mPolyIndexList[mBase+1] = mPolyList.size(); + mPolyList.push_back(backPoly); + + // new base poly... + setBase(mBase); + + // increase tsetOffset & mMaxTouched because of insertion of back poly + testOffset++; + mMaxTouched++; + + // + switched=false; + return; + } + + // splitTest -- no other way to get here + AssertFatal(splitTest,"DepthSortList::handleOverlap: how did we get here like this?"); + + // put frontPoly in front of basePoly, leave backPoly where testPoly was + + // we need one more spot (testPoly broken into front and back) + // and we need to shift everything from base up to test down one spot + mPolyIndexList.insert(mBase); + + // need one more poly for front poly + mPolyIndexList[mBase] = mPolyList.size(); + mPolyList.push_back(frontPoly); + mPolyExtentsList.increment(); + setExtents(mPolyList.last(),mPolyExtentsList.last()); + + // set up back poly + mPolyList[testIdx] = backPoly; + setExtents(mPolyList[testIdx],mPolyExtentsList[testIdx]); + + // new base poly... + setBase(mBase); + + // we inserted an element, increase mMaxTouched... + mMaxTouched++; + + testOffset=0; + switched = false; +} + +//---------------------------------------------------------------------------- + +bool DepthSortList::overlap(Poly * poly1, Poly * poly2) +{ + // check for overlap without any shortcuts + S32 sz1 = poly1->vertexCount; + S32 sz2 = poly2->vertexCount; + + Point3F * a1, * b1; + Point3F * a2, * b2; + Point2F norm; + F32 dot; + b1 = &mVertexList[mIndexList[poly1->vertexStart+sz1-1]].point; + S32 i; + for (i=0; ivertexStart+i]].point; + + // get the mid-point of this edge + Point3F mid1 = *a1+*b1; + mid1 *= 0.5f; + bool midOutside = false; + + b2 = &mVertexList[mIndexList[poly2->vertexStart+sz2-1]].point; + for (S32 j=0; jvertexStart+j]].point; + + // test for intersection of a1-b1 and a2-b2 (on x-z plane) + + // they intersect if a1 & b1 are on opp. sides of line a2-b2 + // and a2 & b2 are on opp. sides of line a1-b1 + + norm.set(a2->z - b2->z, b2->x - a2->x); // normal to line a2-b2 + dot = norm.x * a2->x + norm.y * a2->z; // dot of a2 and norm + if (norm.x * mid1.x + norm.y * mid1.z - dot >= 0) // special check for midpoint of line + midOutside = true; + if ( ((norm.x * a1->x + norm.y * a1->z) - dot) * ((norm.x * b1->x + norm.y * b1->z) - dot) >= 0 ) + // a1 & b1 are on the same side of the line a2-b2...edges don't overlap + continue; + + norm.set(a1->z - b1->z, b1->x - a1->x); // normal to line a1-b1 + dot = norm.x * a1->x + norm.y * a1->z; // dot of a1 and norm + if ( ((norm.x * a2->x + norm.y * a2->z) - dot) * ((norm.x * b2->x + norm.y * b2->z) - dot) >= 0 ) + // a2 & b2 are on the same side of the line a1-b1...edges don't overlap + continue; + + return true; // edges overlap, so polys overlap + } + if (!midOutside) + return true; // midpoint of a1-b1 is inside the poly + } + + // edges don't overlap...but one poly might be contained inside the other + Point3F center = mVertexList[mIndexList[poly2->vertexStart]].point; + for (i=1; ivertexStart+i]].point; + center *= 1.0f / (F32)poly2->vertexCount; + b1 = &mVertexList[mIndexList[poly1->vertexStart+sz1-1]].point; + for (i=0; ivertexStart+i]].point; + + norm.set(a1->z - b1->z, b1->x - a1->x); // normal to line a1-b1 + dot = norm.x * a1->x + norm.y * a1->z; // dot of a1 and norm + if (center.x * norm.x + center.z * norm.y > dot) + // center is outside this edge, poly2 is not inside poly1 + break; + } + if (i==sz1) + return true; // first vert of poly2 inside poly1 (so all of poly2 inside poly1) + + center = mVertexList[mIndexList[poly1->vertexStart]].point; + for (i=1; ivertexStart+i]].point; + center *= 1.0f / (F32)poly1->vertexCount; + b2 = &mVertexList[mIndexList[poly2->vertexStart+sz2-1]].point; + for (i=0; ivertexStart+i]].point; + + norm.set(a2->z - b2->z, b2->x - a2->x); // normal to line a2-b2 + dot = norm.x * a2->x + norm.y * a2->z; // dot of a1 and norm + if (center.x * norm.x + center.z * norm.y > dot) + // v is outside this edge, poly1 is not inside poly2 + break; + } + if (i==sz2) + return true; // first vert of poly1 inside poly2 (so all of poly1 inside poly2) + + return false; // we survived, no overlap +} + +//---------------------------------------------------------------------------- + +Vector frontVerts(__FILE__, __LINE__); +Vector backVerts(__FILE__, __LINE__); + +// Split source poly into front and back. If either front or back is degenerate, don't do anything. +// If we have a front and a back, then add the verts to our vertex list and fill out the poly structures. +bool DepthSortList::splitPoly(const Poly & src, Point3F & normal, F32 k, Poly & frontPoly, Poly & backPoly) +{ + frontVerts.clear(); + backVerts.clear(); + + // already degenerate... + AssertFatal(src.vertexCount>=3,"DepthSortList::splitPoly - Don't need to split a triangle!"); + + S32 startSize = mVertexList.size(); + + // Assume back and front are degenerate polygons until proven otherwise. + bool backDegen = true, frontDegen = true; + + U32 bIdx; + Point3F * a, * b; + F32 dota, dotb; + S32 signA, signB; + + F32 splitTolSq = SPLIT_TOL * SPLIT_TOL * mDot(normal,normal); + + bIdx = mIndexList[src.vertexStart+src.vertexCount-1]; + b = &mVertexList[bIdx].point; + dotb = mDot(normal,*b)-k; + + // Sign variable coded as follows: 1 for outside, 0 on the plane and -1 for inside. + if (dotb*dotb > splitTolSq) + signB = dotb > 0.0f ? 1 : -1; + else + signB = 0; + + S32 i; + for (i = 0; i splitTolSq) + signB = dotb > 0.0f ? 1 : -1; + else + signB = 0; + + switch(signA*3 + signB + 4) // +4 is to make values go from 0 up...hopefully enticing compiler to make a jump-table + { + case 0: // A-, B- + case 3: // A., B- + backVerts.push_back(bIdx); + backDegen = false; + break; + case 8: // A+, B+ + case 5: // A., B+ + frontVerts.push_back(bIdx); + frontDegen = false; + break; + + case 1: // A-, B. + case 4: // A., B. + case 7: // A+, B. + backVerts.push_back(bIdx); + frontVerts.push_back(bIdx); + break; + + case 2: // A-, B+ + { + // intersect line A-B with plane + F32 dotA = mDot(*a,normal); + F32 dotB = mDot(*b,normal); + Vertex v; + v.point = *a-*b; + v.point *= (k-dotB)/(dotA-dotB); + v.point += *b; + v.mask = 0; + frontVerts.push_back(mVertexList.size()); + backVerts.push_back(mVertexList.size()); + frontVerts.push_back(bIdx); + mVertexList.push_back(v); + b = &mVertexList[bIdx].point; // better get this pointer again since we just incremented vector + frontDegen = false; + break; + } + case 6: // A+, B- + { + // intersect line A-B with plane + F32 dotA = mDot(*a,normal); + F32 dotB = mDot(*b,normal); + Vertex v; + v.point = *a-*b; + v.point *= (k-dotB)/(dotA-dotB); + v.point += *b; + v.mask = 0; + frontVerts.push_back(mVertexList.size()); + backVerts.push_back(mVertexList.size()); + backVerts.push_back(bIdx); + mVertexList.push_back(v); + b = &mVertexList[bIdx].point; // better get this pointer again since we just incremented vector + backDegen = false; + break; + } + } + } + + // Check for degeneracy. + + if (!ALWAYS_RETURN_FRONT_AND_BACK) + { + if (frontVerts.size()<3 || backVerts.size()<3 || frontDegen || backDegen) + { + // didn't need to be split...return two empty polys + // and restore vertex list to how it was + mVertexList.setSize(startSize); + frontPoly.vertexCount = backPoly.vertexCount = 0; + return false; + } + } + else + { + if (frontDegen) + frontVerts.clear(); + if (backDegen) + backVerts.clear(); + } + + // front poly + frontPoly.plane = src.plane; + frontPoly.object = src.object; + frontPoly.material = src.material; + frontPoly.vertexStart = mIndexList.size(); + frontPoly.vertexCount = frontVerts.size(); + frontPoly.surfaceKey = src.surfaceKey; + frontPoly.polyFlags = src.polyFlags; + + // back poly + backPoly.plane = src.plane; + backPoly.object = src.object; + backPoly.material = src.material; + backPoly.vertexStart = frontPoly.vertexStart + frontPoly.vertexCount; + backPoly.vertexCount = backVerts.size(); + backPoly.surfaceKey = src.surfaceKey; + backPoly.polyFlags = src.polyFlags; + + // add indices + mIndexList.setSize(backPoly.vertexStart+backPoly.vertexCount); + + if( frontPoly.vertexCount ) { + dMemcpy(&mIndexList[frontPoly.vertexStart], + frontVerts.address(), + sizeof(U32)*frontPoly.vertexCount); + } + + if( backPoly.vertexCount ) { + dMemcpy(&mIndexList[backPoly.vertexStart], + backVerts.address(), + sizeof(U32)*backPoly.vertexCount); + } + + return true; +} + +//---------------------------------------------------------------------------- + +Vector gWorkListA(256, __FILE__, __LINE__ ); +Vector gWorkListB(256, __FILE__, __LINE__ ); +Vector gWorkListJunkBin(256, __FILE__, __LINE__ ); + +void DepthSortList::depthPartition(const Point3F * sourceVerts, U32 numVerts, Vector & partition, Vector & partitionVerts) +{ + // create the depth partition of the passed poly + // a depth partition is a partition of the poly on the + // x-z plane so that each sub-poly in the partition can be + // mapped onto exactly one plane in the depth list (i.e., + // those polys found in mPolyIndexList... the ones that are + // depth sorted). The plane the sub-polys are mapped onto + // is the plane of the closest facing poly. + // + // y-coord of input polys are ignored, and are remapped + // on output to put the output polys on the + // corresponding planes. + + // This routine is confusing because there are three lists of polys. + // + // The source list (passed in as a single poly, but becomes a list as + // it is split up) comprises the poly to be partitioned. Verts for sourcePoly + // are held in sourceVerts when passed to this routine, but immediately copied + // to mVertexList (and indices are added for each vert to mIndexList). + // + // The scraps list is generated from the source poly (it contains the outside + // piece of each cut that is made). Indices for polys in the scraps list are + // found in mIndexList and verts are found in mVerts list. Note that the depthPartition + // routine will add verts and indices to the member lists, but not polys. + // + // Finally, the partition list is the end result -- the depth partition. These + // polys are not indexed polys. The vertexStart field indexes directly into partitionVerts + // array. + + if (mBase<0) + // begin the depth sort + sortByYExtents(); + + // apply cookie cutter to these polys + Vector * sourceList = &gWorkListA; + sourceList->clear(); + + // add source poly for to passed verts + sourceList->increment(); + sourceList->last().vertexStart = mIndexList.size(); + sourceList->last().vertexCount = numVerts; + + // add verts of source poly to mVertexList and mIndexList + mVertexList.setSize(mVertexList.size()+numVerts); + mIndexList.setSize(mIndexList.size()+numVerts); + for (S32 v=0; v * scraps = &gWorkListB; + scraps->clear(); + + gWorkListJunkBin.clear(); + + S32 i=0; + while (sourceList->size()) + { + if (i>=mBase && !sortNext()) + // that's it, no more polys to sort + break; + AssertFatal(i<=mBase,"DepthSortList::depthPartition - exceeded mBase."); + + // use the topmost poly as the cookie cutter + Poly & cutter = mPolyList[mPolyIndexList[i]]; + S32 startVert = partitionVerts.size(); + + bool allowclipping = cutter.polyFlags & CLIPPEDPOLYLIST_FLAG_ALLOWCLIPPING; + + S32 j; + for (j=0; jsize(); j++) + { + Poly toCut = (*sourceList)[j]; + + if(allowclipping) + cookieCutter(cutter,toCut,*scraps,partition,partitionVerts); + else + cookieCutter(cutter,toCut,gWorkListJunkBin,partition,partitionVerts); + } + + // project all the new verts onto the cutter's plane + AssertFatal(mFabs(cutter.plane.y)>=MIN_Y_DOT,"DepthSortList::depthPartition - below MIN_Y_DOT."); + F32 invY = -1.0f / cutter.plane.y; + for (j=startVert; jclear(); + // swap work lists -- scraps become source for next closest poly + Vector * tmpListPtr = sourceList; + sourceList = scraps; + scraps = tmpListPtr; + } + i++; + } +} + +//---------------------------------------------------------------------------- + +void DepthSortList::cookieCutter(Poly & cutter, Poly & cuttee, + Vector & scraps, // outsides + Vector & cookies, Vector & cookieVerts) // insides +{ + // perhaps going too far with the cookie cutter analogy, but... + // cutter is used to cut cuttee + // + // the part that is inside of all cutter edges (on x-z plane) + // is put into the "cookie" list, parts that are outside are put + // onto the "scraps" list. "scraps" are indexed polys with indices + // and vertices in mIndexList and mVertexList. Cookies aren't indexed + // and points go into cookieVerts list. + + ALWAYS_RETURN_FRONT_AND_BACK = true; // split routine will return polys even if no split occurs + + // save off current state in case nothing inside all the edges of cutter (i.e., no "cookie") + S32 vsStart = cuttee.vertexStart; + S32 vcStart = cuttee.vertexCount; + S32 milStart = mIndexList.size(); + S32 mvlStart = mVertexList.size(); + S32 scStart = scraps.size(); + + Point3F a, b; + Poly scrap; + a = mVertexList[mIndexList[cutter.vertexStart+cutter.vertexCount-1]].point; + for (S32 i=0; i PolyExtentsList; + typedef Vector PolyIndexList; + + // Internal data + PolyExtentsList mPolyExtentsList; + PolyIndexList mPolyIndexList; + Point3F mExtent; // dimensions of the sort area + S32 mBase; // base position in the list...everything before this is sorted correctly + Poly * mBasePoly; // poly currently in base position + Point3F * mBaseNormal; // normal of poly currently in base position + F32 mBaseDot; // dot of basePoly with baseNormal + F32 mBaseYMax; // max y extent of base poly + S32 mMaxTouched; // highest index swapped into thus far...y-extents may be improperly sorted before this index + PolyExtents * mBaseExtents; // x,y,z extents of basePoly + + // set the base position -- everything before this point should be correctly sorted + void setBase(S32); + + // some utilities used for sorting + bool splitPoly(const Poly & sourcePoly, Point3F & normal, F32 k, Poly & front, Poly & back); + bool overlap(Poly *, Poly *); + void handleOverlap(Poly * testPoly, Point3F & testNormal, F32 testDot, S32 & testOffset, bool & switched); + void sortByYExtents(); + void setExtents(Poly &, PolyExtents &); + + // one iteration of the sort routine -- finds a poly to fill current base position + bool sortNext(); + + // used by depthPartition + void cookieCutter(Poly & cutter, Poly & cuttee, + Vector & scraps, + Vector & cookies, Vector & cookieVerts); + + public: + + // + DepthSortList(); + ~DepthSortList(); + void set(const MatrixF & mat, Point3F & extents); + void clear(); + void clearSort(); + + // the point of this class + void sort(); + void depthPartition(const Point3F * sourceVerts, U32 numVerts, Vector & partition, Vector & partitionVerts); + + // Virtual methods + void end(); + // U32 addPoint(const Point3F& p); + // bool isEmpty() const; + // void begin(U32 material,U32 surfaceKey); + // void plane(U32 v1,U32 v2,U32 v3); + // void plane(const PlaneF& p); + // void vertex(U32 vi); + bool getMapping(MatrixF *, Box3F *); + + // access to the polys in order (note: returned pointers are volatile, may change if polys added). + void getOrderedPoly(U32 ith, Poly ** poly, PolyExtents ** polyExtent); + + // unordered access + PolyExtents & getExtents(U32 idx) { return mPolyExtentsList[idx]; } + Poly & getPoly(U32 idx) { return mPolyList[idx]; } +}; + +inline void DepthSortList::getOrderedPoly(U32 ith, Poly ** poly, PolyExtents ** polyExtent) +{ + *poly = &mPolyList[mPolyIndexList[ith]]; + *polyExtent = &mPolyExtentsList[mPolyIndexList[ith]]; +} + +#endif diff --git a/collision/earlyOutPolyList.cpp b/collision/earlyOutPolyList.cpp new file mode 100644 index 0000000..01277d7 --- /dev/null +++ b/collision/earlyOutPolyList.cpp @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMath.h" +#include "console/console.h" +#include "collision/earlyOutPolyList.h" + + +//---------------------------------------------------------------------------- + +EarlyOutPolyList::EarlyOutPolyList() +{ + VECTOR_SET_ASSOCIATION(mPolyList); + VECTOR_SET_ASSOCIATION(mVertexList); + VECTOR_SET_ASSOCIATION(mIndexList); + VECTOR_SET_ASSOCIATION(mPolyPlaneList); + VECTOR_SET_ASSOCIATION(mPlaneList); + + mNormal.set(0, 0, 0); + mIndexList.reserve(100); + + mEarlyOut = false; +} + +EarlyOutPolyList::~EarlyOutPolyList() +{ + +} + + +//---------------------------------------------------------------------------- +void EarlyOutPolyList::clear() +{ + // Only clears internal data + mPolyList.clear(); + mVertexList.clear(); + mIndexList.clear(); + mPolyPlaneList.clear(); + + mEarlyOut = false; +} + +bool EarlyOutPolyList::isEmpty() const +{ + return mEarlyOut == false; +} + + +//---------------------------------------------------------------------------- + +U32 EarlyOutPolyList::addPoint(const Point3F& p) +{ + if (mEarlyOut == true) + return 0; + + mVertexList.increment(); + Vertex& v = mVertexList.last(); + v.point.x = p.x * mScale.x; + v.point.y = p.y * mScale.y; + v.point.z = p.z * mScale.z; + mMatrix.mulP(v.point); + + // Build the plane mask + v.mask = 0; + for (U32 i = 0; i < mPlaneList.size(); i++) + if (mPlaneList[i].distToPlane(v.point) > 0) + v.mask |= 1 << i; + + // If the point is inside all the planes, then we're done! + if (v.mask == 0) + mEarlyOut = true; + + return mVertexList.size() - 1; +} + + +U32 EarlyOutPolyList::addPlane(const PlaneF& plane) +{ + mPolyPlaneList.increment(); + mPlaneTransformer.transform(plane, mPolyPlaneList.last()); + + return mPolyPlaneList.size() - 1; +} + + +//---------------------------------------------------------------------------- + +void EarlyOutPolyList::begin(BaseMatInstance* material,U32 surfaceKey) +{ + if (mEarlyOut == true) + return; + + mPolyList.increment(); + Poly& poly = mPolyList.last(); + poly.object = mCurrObject; + poly.material = material; + poly.vertexStart = mIndexList.size(); + poly.surfaceKey = surfaceKey; +} + + +//---------------------------------------------------------------------------- + +void EarlyOutPolyList::plane(U32 v1,U32 v2,U32 v3) +{ + if (mEarlyOut == true) + return; + + mPolyList.last().plane.set(mVertexList[v1].point, + mVertexList[v2].point,mVertexList[v3].point); +} + +void EarlyOutPolyList::plane(const PlaneF& p) +{ + if (mEarlyOut == true) + return; + + mPlaneTransformer.transform(p, mPolyList.last().plane); +} + +void EarlyOutPolyList::plane(const U32 index) +{ + if (mEarlyOut == true) + return; + + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + mPolyList.last().plane = mPolyPlaneList[index]; +} + +const PlaneF& EarlyOutPolyList::getIndexedPlane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + return mPolyPlaneList[index]; +} + + +//---------------------------------------------------------------------------- + +void EarlyOutPolyList::vertex(U32 vi) +{ + if (mEarlyOut == true) + return; + + mIndexList.push_back(vi); +} + + +//---------------------------------------------------------------------------- + +void EarlyOutPolyList::end() +{ + if (mEarlyOut == true) + return; + + Poly& poly = mPolyList.last(); + + // Anything facing away from the mNormal is rejected + if (mDot(poly.plane,mNormal) > 0) { + mIndexList.setSize(poly.vertexStart); + mPolyList.decrement(); + return; + } + + // Build intial inside/outside plane masks + U32 indexStart = poly.vertexStart; + U32 vertexCount = mIndexList.size() - indexStart; + + U32 frontMask = 0,backMask = 0; + U32 i; + for (i = indexStart; i < mIndexList.size(); i++) { + U32 mask = mVertexList[mIndexList[i]].mask; + frontMask |= mask; + backMask |= ~mask; + } + + // Trivial accept if all the vertices are on the backsides of + // all the planes. + if (!frontMask) { + poly.vertexCount = vertexCount; + mEarlyOut = true; + return; + } + + // Trivial reject if any plane not crossed has all it's points + // on the front. + U32 crossMask = frontMask & backMask; + if (~crossMask & frontMask) { + mIndexList.setSize(poly.vertexStart); + mPolyList.decrement(); + return; + } + + // Need to do some clipping + for (U32 p = 0; p < mPlaneList.size(); p++) { + U32 pmask = 1 << p; + // Only test against this plane if we have something + // on both sides + if (crossMask & pmask) { + U32 indexEnd = mIndexList.size(); + U32 i1 = indexEnd - 1; + U32 mask1 = mVertexList[mIndexList[i1]].mask; + + for (U32 i2 = indexStart; i2 < indexEnd; i2++) { + U32 mask2 = mVertexList[mIndexList[i2]].mask; + if ((mask1 ^ mask2) & pmask) { + // + mVertexList.increment(); + VectorF& v1 = mVertexList[mIndexList[i1]].point; + VectorF& v2 = mVertexList[mIndexList[i2]].point; + VectorF vv = v2 - v1; + F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv); + + mIndexList.push_back(mVertexList.size() - 1); + Vertex& iv = mVertexList.last(); + iv.point.x = v1.x + vv.x * t; + iv.point.y = v1.y + vv.y * t; + iv.point.z = v1.z + vv.z * t; + iv.mask = 0; + + // Test against the remaining planes + for (U32 i = p + 1; i < mPlaneList.size(); i++) + if (mPlaneList[i].distToPlane(iv.point) > 0) { + iv.mask = 1 << i; + break; + } + } + if (!(mask2 & pmask)) { + U32 index = mIndexList[i2]; + mIndexList.push_back(index); + } + mask1 = mask2; + i1 = i2; + } + + // Check for degenerate + indexStart = indexEnd; + if (mIndexList.size() - indexStart < 3) { + mIndexList.setSize(poly.vertexStart); + mPolyList.decrement(); + return; + } + } + } + + // If we reach here, then there's a poly! + mEarlyOut = true; + + // Emit what's left and compress the index list. + poly.vertexCount = mIndexList.size() - indexStart; + memcpy(&mIndexList[poly.vertexStart], + &mIndexList[indexStart],poly.vertexCount); + mIndexList.setSize(poly.vertexStart + poly.vertexCount); +} + + +//---------------------------------------------------------------------------- + +void EarlyOutPolyList::memcpy(U32* dst, U32* src,U32 size) +{ + U32* end = src + size; + while (src != end) + *dst++ = *src++; +} + diff --git a/collision/earlyOutPolyList.h b/collision/earlyOutPolyList.h new file mode 100644 index 0000000..25842c2 --- /dev/null +++ b/collision/earlyOutPolyList.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EARLYOUTPOLYLIST_H_ +#define _EARLYOUTPOLYLIST_H_ + +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif + + +/// Early out check PolyList +/// +/// This class is used primarily for triggers and similar checks. It checks to see +/// if any of the geometry you feed it is inside its area, and if it is, it stops +/// checking for any more data and returns a true value. This is good if you want +/// to know if anything is in your "trigger" area, for instance. +/// +/// @see AbstractPolyList +class EarlyOutPolyList : public AbstractPolyList +{ + void memcpy(U32* d, U32* s,U32 size); + + // Internal data + struct Vertex { + Point3F point; + U32 mask; + }; + + struct Poly { + PlaneF plane; + SceneObject* object; + BaseMatInstance* material; + U32 vertexStart; + U32 vertexCount; + U32 surfaceKey; + }; + + public: + typedef Vector PlaneList; + private: + typedef Vector VertexList; + typedef Vector PolyList; + typedef Vector IndexList; + + PolyList mPolyList; + VertexList mVertexList; + IndexList mIndexList; + bool mEarlyOut; + + PlaneList mPolyPlaneList; + + public: + // Data set by caller + PlaneList mPlaneList; + VectorF mNormal; + + public: + EarlyOutPolyList(); + ~EarlyOutPolyList(); + void clear(); + + // Virtual methods + bool isEmpty() const; + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(BaseMatInstance* material,U32 surfaceKey); + void plane(U32 v1,U32 v2,U32 v3); + void plane(const PlaneF& p); + void plane(const U32 index); + void vertex(U32 vi); + void end(); + + protected: + const PlaneF& getIndexedPlane(const U32 index); +}; + +#endif // _H_EARLYOUTPOLYLIST_ diff --git a/collision/extrudedPolyList.cpp b/collision/extrudedPolyList.cpp new file mode 100644 index 0000000..19395fd --- /dev/null +++ b/collision/extrudedPolyList.cpp @@ -0,0 +1,493 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMath.h" +#include "console/console.h" +#include "collision/extrudedPolyList.h" +#include "collision/polyhedron.h" +#include "collision/collision.h" + +// Minimum distance from a face +F32 ExtrudedPolyList::FaceEpsilon = 0.01f; + +// Value used to compare collision times +F32 ExtrudedPolyList::EqualEpsilon = 0.0001f; + +ExtrudedPolyList::ExtrudedPolyList() +{ + VECTOR_SET_ASSOCIATION(mVertexList); + VECTOR_SET_ASSOCIATION(mIndexList); + VECTOR_SET_ASSOCIATION(mExtrudedList); + VECTOR_SET_ASSOCIATION(mPlaneList); + VECTOR_SET_ASSOCIATION(mPolyPlaneList); + + mVelocity.set(0.0f,0.0f,0.0f); + mIndexList.reserve(128); + mVertexList.reserve(64); + mPolyPlaneList.reserve(64); + mPlaneList.reserve(64); + mCollisionList = 0; +} + +ExtrudedPolyList::~ExtrudedPolyList() +{ +} + +//---------------------------------------------------------------------------- + +bool ExtrudedPolyList::isEmpty() const +{ + return mCollisionList->getCount() == 0; +} + + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::extrude(const Polyhedron& pt, const VectorF& vector) +{ + // Clear state + mIndexList.clear(); + mVertexList.clear(); + mPlaneList.clear(); + mPolyPlaneList.clear(); + + // Determine which faces will be extruded. + mExtrudedList.setSize(pt.planeList.size()); + + for (U32 f = 0; f < pt.planeList.size(); f++) + { + const PlaneF& face = pt.planeList[f]; + ExtrudedFace& eface = mExtrudedList[f]; + F32 dot = mDot(face,vector); + eface.active = dot > EqualEpsilon; + + if (eface.active) + { + eface.maxDistance = dot; + eface.plane = face; + eface.planeMask = BIT(mPlaneList.size()); + + // Add the face as a plane to clip against. + mPlaneList.increment(2); + PlaneF* plane = mPlaneList.end() - 2; + plane[0] = plane[1] = face; + plane[0].invert(); + } + } + + // Produce extruded planes for bounding and internal edges + for (U32 e = 0; e < pt.edgeList.size(); e++) + { + Polyhedron::Edge const& edge = pt.edgeList[e]; + ExtrudedFace& ef1 = mExtrudedList[edge.face[0]]; + ExtrudedFace& ef2 = mExtrudedList[edge.face[1]]; + if (ef1.active || ef2.active) + { + + // Assumes that the edge points are clockwise + // for face[0]. + const Point3F& p1 = pt.pointList[edge.vertex[1]]; + const Point3F &p2 = pt.pointList[edge.vertex[0]]; + Point3F p3 = p2 + vector; + + mPlaneList.increment(2); + PlaneF* plane = mPlaneList.end() - 2; + plane[0].set(p3,p2,p1); + plane[1] = plane[0]; + plane[1].invert(); + + U32 pmask = BIT(mPlaneList.size()-2); + ef1.planeMask |= pmask; + ef2.planeMask |= pmask << 1; + } + } +} + + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::setCollisionList(CollisionList* info) +{ + mCollisionList = info; + mCollisionList->clear(); + mCollisionList->setTime( 2.0f ); +} + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::adjustCollisionTime() +{ + if( !mCollisionList->getCount() ) + return; + + mCollisionList->setTime( mClampF( mCollisionList->getTime(), 0.f, 1.f ) ); +} + + +//---------------------------------------------------------------------------- + +U32 ExtrudedPolyList::addPoint(const Point3F& p) +{ + mVertexList.increment(); + Vertex& v = mVertexList.last(); + + v.point.x = p.x * mScale.x; + v.point.y = p.y * mScale.y; + v.point.z = p.z * mScale.z; + mMatrix.mulP(v.point); + + // Build the plane mask, planes come in pairs + v.mask = 0; + for (U32 i = 0; i < mPlaneList.size(); i ++) + if (mPlaneList[i].distToPlane(v.point) >= 0.f) + v.mask |= BIT(i); + + return mVertexList.size() - 1; +} + + +U32 ExtrudedPolyList::addPlane(const PlaneF& plane) +{ + mPolyPlaneList.increment(); + mPlaneTransformer.transform(plane, mPolyPlaneList.last()); + + return mPolyPlaneList.size() - 1; +} + + +//---------------------------------------------------------------------------- + +void ExtrudedPolyList::begin(BaseMatInstance* material, U32 /*surfaceKey*/) +{ + mPoly.object = mCurrObject; + mPoly.material = material; + mIndexList.clear(); +} + +void ExtrudedPolyList::plane(U32 v1, U32 v2, U32 v3) +{ + mPoly.plane.set(mVertexList[v1].point, + mVertexList[v2].point, + mVertexList[v3].point); + + // We hope this isn't needed but we're leaving it in anyway -- BJG/EGH + mPoly.plane.normalizeSafe(); +} + +void ExtrudedPolyList::plane(const PlaneF& p) +{ + mPlaneTransformer.transform(p, mPoly.plane); +} + +void ExtrudedPolyList::plane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + mPoly.plane = mPolyPlaneList[index]; +} + +const PlaneF& ExtrudedPolyList::getIndexedPlane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + return mPolyPlaneList[index]; +} + + +void ExtrudedPolyList::vertex(U32 vi) +{ + mIndexList.push_back(vi); +} + +void ExtrudedPolyList::end() +{ + // Anything facing away from the mVelocity is rejected (and also + // cap to max collisions) + if (mDot(mPoly.plane, mNormalVelocity) > 0.f || + mCollisionList->getCount() >= CollisionList::MaxCollisions) + return; + + // Test the built up poly (stored in mPoly) against all our extruded + // faces. + U32 cFaceCount = 0; + ExtrudedFace* cFace[30]; + bool cEdgeColl[30]; + ExtrudedFace* face = mExtrudedList.begin(); + ExtrudedFace* end = mExtrudedList.end(); + + for (; face != end; face++) + { + // Skip inactive.. + if (!face->active) + continue; + + // Update the dot product. + face->faceDot = -mDot(face->plane,mPoly.plane); + + // Skip it if we're facing towards... + if(face->faceDot <= 0.f) + continue; + + // Test, and skip if colliding. + if (!testPoly(*face)) + continue; + + // Note collision. + cFace[cFaceCount] = face; + cEdgeColl[cFaceCount++] = false; + } + + if (!cFaceCount) + { + face = mExtrudedList.begin(); + end = mExtrudedList.end(); + for (; face != end; face++) + { + // Don't need to do dot product second time, so just check if it's + // active (we already did the dot product in the previous loop). + if (!face->active) + continue; + + // Skip it if we're facing away... + if(face->faceDot > 0.f) + continue; + + // Do collision as above. + if (!testPoly(*face)) + continue; + + // Note the collision. + cFace[cFaceCount] = face; + cEdgeColl[cFaceCount++] = true; + } + } + + // If we STILL don't have any collisions, just skip out. + if (!cFaceCount) + return; + + // Pick the best collision face based on best alignment with respective + // face. + face = cFace[0]; + bool edge = cEdgeColl[0]; + for (U32 f = 1; f < cFaceCount; f++) + { + if (cFace[f]->faceDot <= face->faceDot) + continue; + + face = cFace[f]; + edge = cEdgeColl[f]; + } + + // Add it to the collision list if it's better and/or equal + // to our current best. + + // Don't add it to the collision list if it's too far away. + if (face->time > mCollisionList->getTime() + EqualEpsilon || face->time >= 1.0) + return; + + if (face->time < mCollisionList->getTime() - EqualEpsilon) + { + // If this is significantly closer than before, then clear out the + // list, as it's a better match than the old stuff. + mCollisionList->clear(); + mCollisionList->setTime( face->time ); + mCollisionList->setMaxHeight( face->height ); + } + else + { + // Otherwise, just update some book-keeping stuff. + if ( face->height > mCollisionList->getMaxHeight() ) + mCollisionList->setMaxHeight( face->height ); + } + + // Note the collision in our collision list. + Collision& collision = mCollisionList->increment(); + collision.point = face->point; + collision.faceDot = face->faceDot; + collision.face = face - mExtrudedList.begin(); + collision.object = mPoly.object; + collision.normal = mPoly.plane; + collision.material = mPoly.material; +} + + +//---------------------------------------------------------------------------- + +bool ExtrudedPolyList::testPoly(ExtrudedFace& face) +{ + // Build intial inside/outside plane masks + U32 indexStart = 0; + U32 indexEnd = mIndexList.size(); + U32 oVertexSize = mVertexList.size(); + U32 oIndexSize = mIndexList.size(); + + U32 frontMask = 0,backMask = 0; + for (U32 i = indexStart; i < indexEnd; i++) + { + U32 mask = mVertexList[mIndexList[i]].mask & face.planeMask; + frontMask |= mask; + backMask |= ~mask; + } + + // Clip the mPoly against the planes that bound the face... + // Trivial accept if all the vertices are on the backsides of + // all the planes. + if (frontMask) + { + // Trivial reject if any plane not crossed has all it's points + // on the front. + U32 crossMask = frontMask & backMask; + if (~crossMask & frontMask) + return false; + + // Need to do some clipping + for (U32 p=0; p < mPlaneList.size(); p++) + { + U32 pmask = BIT(p); + U32 newStart = mIndexList.size(); + + // Only test against this plane if we have something + // on both sides - otherwise skip. + if (!(face.planeMask & crossMask & pmask)) + continue; + + U32 i1 = indexEnd - 1; + U32 mask1 = mVertexList[mIndexList[i1]].mask; + + for (U32 i2 = indexStart; i2 < indexEnd; i2++) + { + const U32 mask2 = mVertexList[mIndexList[i2]].mask; + if ((mask1 ^ mask2) & pmask) + { + // Clip the edge against the plane. + mVertexList.increment(); + VectorF& v1 = mVertexList[mIndexList[i1]].point; + VectorF& v2 = mVertexList[mIndexList[i2]].point; + VectorF vv = v2 - v1; + F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv); + + mIndexList.push_back(mVertexList.size() - 1); + Vertex& iv = mVertexList.last(); + iv.point.x = v1.x + vv.x * t; + iv.point.y = v1.y + vv.y * t; + iv.point.z = v1.z + vv.z * t; + iv.mask = 0; + + // Test against the remaining planes + for (U32 i = p+1; i < mPlaneList.size(); i ++) + { + if (mPlaneList[i].distToPlane(iv.point) > 0.f) + iv.mask |= BIT(i); + } + } + + if (!(mask2 & pmask)) + { + U32 index = mIndexList[i2]; + mIndexList.push_back(index); + } + + mask1 = mask2; + i1 = i2; + } + + // Check for degenerate + indexStart = newStart; + indexEnd = mIndexList.size(); + if (mIndexList.size() - indexStart < 3) + { + mVertexList.setSize(oVertexSize); + mIndexList.setSize(oIndexSize); + return false; + } + } + } + + // Find closest point on the mPoly + Point3F bp(0.0f, 0.0f, 0.0f); + F32 bd = 1E30f; + F32 height = -1E30f; + for (U32 b = indexStart; b < indexEnd; b++) + { + Vertex& vertex = mVertexList[mIndexList[b]]; + F32 dist = face.plane.distToPlane(vertex.point); + if (dist <= bd) + { + bd = (dist < 0)? 0: dist; + bp = vertex.point; + } + + // Since we don't clip against the back plane, we'll + // only include vertex heights that are within range. + if (vertex.point.z > height && dist < face.maxDistance) + height = vertex.point.z; + } + + // hack for not jetting up through the cieling + F32 fudge = 0.01f; + F32 fudgeB = 0.2f; + if(mNormalVelocity.z > 0.0) + { + fudge = 0.01f; //0.015; + fudgeB = 0.2f; + } + + // Do extruded points for back-off. + F32 oldBd=bd; + for (U32 b = indexStart; b < indexEnd; b++) + { + Vertex& vertex = mVertexList[mIndexList[b]]; + + // Extrude out just a tad to make sure we don't end up getting too close to the + // geometry and getting stuck - but cap it so we don't introduce error into long + // sweeps. + F32 dist = face.plane.distToPlane( vertex.point + + Point3F(mPoly.plane) * getMin(face.maxDistance * fudgeB, fudge)); + + if (dist <= bd) + { + bd = (dist < 0)? 0: dist; + bp = vertex.point; + } + } + + // Remove temporary data + mVertexList.setSize(oVertexSize); + mIndexList.setSize(oIndexSize); + + // Don't add it to the collision list if it's worse then our current best. + if (oldBd >= face.maxDistance) + return false; + + // Update our info and indicate we should add to the model. + F32 oldT = oldBd / face.maxDistance; + F32 pushBackT = bd / face.maxDistance; + + if(oldT - pushBackT > 0.1) + face.time = oldT - fudge; + else + face.time = pushBackT; + + face.height = height; + face.point = bp; + return true; +} + +//-------------------------------------------------------------------------- +void ExtrudedPolyList::setVelocity(const VectorF& velocity) +{ + mVelocity = velocity; + if (velocity.isZero() == false) + { + mNormalVelocity = velocity; + mNormalVelocity.normalize(); + setInterestNormal(mNormalVelocity); + } + else + { + mNormalVelocity.set(0.0f, 0.0f, 0.0f); + clearInterestNormal(); + } +} diff --git a/collision/extrudedPolyList.h b/collision/extrudedPolyList.h new file mode 100644 index 0000000..b2036c1 --- /dev/null +++ b/collision/extrudedPolyList.h @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EXTRUDEDPOLYLIST_H_ +#define _EXTRUDEDPOLYLIST_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif + +struct Polyhedron; +class CollisionList; + +//---------------------------------------------------------------------------- +/// Extruded Polytope PolyList +/// +/// This class is used primarily for collision detection, by objects which need +/// to check for obstructions along their path. You feed it a polytope to +/// extrude along the direction of movement, and it gives you a list of collisions. +/// +/// @see AbstractPolyList +class ExtrudedPolyList: public AbstractPolyList +{ +public: + struct Vertex { + Point3F point; + U32 mask; + }; + + struct Poly { + PlaneF plane; + SceneObject* object; + BaseMatInstance* material; + }; + + struct ExtrudedFace { + bool active; + PlaneF plane; + F32 maxDistance; + U32 planeMask; + F32 faceDot; + F32 faceShift; + F32 time; + Point3F point; + F32 height; + }; + + typedef Vector ExtrudedList; + typedef Vector PlaneList; + typedef Vector VertexList; + typedef Vector IndexList; + + static F32 EqualEpsilon; + static F32 FaceEpsilon; + + // Internal data + VertexList mVertexList; + IndexList mIndexList; + ExtrudedList mExtrudedList; + PlaneList mPlaneList; + VectorF mVelocity; + VectorF mNormalVelocity; + F32 mFaceShift; + Poly mPoly; + + // Returned info + CollisionList* mCollisionList; + + PlaneList mPolyPlaneList; + + // +private: + bool testPoly(ExtrudedFace&); + +public: + ExtrudedPolyList(); + ~ExtrudedPolyList(); + void extrude(const Polyhedron&, const VectorF& vec); + void setVelocity(const VectorF& velocity); + void setCollisionList(CollisionList*); + void adjustCollisionTime(); + + // Virtual methods + bool isEmpty() const; + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(BaseMatInstance* material, U32 surfaceKey); + void plane(U32 v1,U32 v2,U32 v3); + void plane(const PlaneF& p); + void plane(const U32 index); + void vertex(U32 vi); + void end(); + + protected: + const PlaneF& getIndexedPlane(const U32 index); +}; + +#endif diff --git a/collision/gjk.cpp b/collision/gjk.cpp new file mode 100644 index 0000000..f5cefcd --- /dev/null +++ b/collision/gjk.cpp @@ -0,0 +1,370 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +// +// The core algorithms in this file are based on code written +// by G. van den Bergen for his interference detection library, +// "SOLID 2.0" +//----------------------------------------------------------------------------- + +#include "core/dataChunker.h" +#include "collision/collision.h" +#include "sceneGraph/sceneObject.h" +#include "collision/convex.h" +#include "collision/gjk.h" + + +//---------------------------------------------------------------------------- + +static F32 rel_error = 1E-5f; // relative error in the computed distance +static F32 sTolerance = 1E-3f; // Distance tolerance +static F32 sEpsilon2 = 1E-20f; // Zero length vector +static U32 sIteration = 15; // Stuck in a loop? + +S32 num_iterations = 0; +S32 num_irregularities = 0; + + +//---------------------------------------------------------------------------- + +GjkCollisionState::GjkCollisionState() +{ + a = b = 0; +} + +GjkCollisionState::~GjkCollisionState() +{ +} + + +//---------------------------------------------------------------------------- + +void GjkCollisionState::swap() +{ + Convex* t = a; a = b; b = t; + CollisionStateList* l = mLista; mLista = mListb; mListb = l; + v.neg(); +} + + +//---------------------------------------------------------------------------- + +void GjkCollisionState::compute_det() +{ + // Dot new point with current set + for (int i = 0, bit = 1; i < 4; ++i, bit <<=1) + if (bits & bit) + dp[i][last] = dp[last][i] = mDot(y[i], y[last]); + dp[last][last] = mDot(y[last], y[last]); + + // Calulate the determinent + det[last_bit][last] = 1; + for (int j = 0, sj = 1; j < 4; ++j, sj <<= 1) { + if (bits & sj) { + int s2 = sj | last_bit; + det[s2][j] = dp[last][last] - dp[last][j]; + det[s2][last] = dp[j][j] - dp[j][last]; + for (int k = 0, sk = 1; k < j; ++k, sk <<= 1) { + if (bits & sk) { + int s3 = sk | s2; + det[s3][k] = det[s2][j] * (dp[j][j] - dp[j][k]) + + det[s2][last] * (dp[last][j] - dp[last][k]); + det[s3][j] = det[sk | last_bit][k] * (dp[k][k] - dp[k][j]) + + det[sk | last_bit][last] * (dp[last][k] - dp[last][j]); + det[s3][last] = det[sk | sj][k] * (dp[k][k] - dp[k][last]) + + det[sk | sj][j] * (dp[j][k] - dp[j][last]); + } + } + } + } + + if (all_bits == 15) { + det[15][0] = det[14][1] * (dp[1][1] - dp[1][0]) + + det[14][2] * (dp[2][1] - dp[2][0]) + + det[14][3] * (dp[3][1] - dp[3][0]); + det[15][1] = det[13][0] * (dp[0][0] - dp[0][1]) + + det[13][2] * (dp[2][0] - dp[2][1]) + + det[13][3] * (dp[3][0] - dp[3][1]); + det[15][2] = det[11][0] * (dp[0][0] - dp[0][2]) + + det[11][1] * (dp[1][0] - dp[1][2]) + + det[11][3] * (dp[3][0] - dp[3][2]); + det[15][3] = det[7][0] * (dp[0][0] - dp[0][3]) + + det[7][1] * (dp[1][0] - dp[1][3]) + + det[7][2] * (dp[2][0] - dp[2][3]); + } +} + + +//---------------------------------------------------------------------------- + +inline void GjkCollisionState::compute_vector(int bits, VectorF& v) +{ + F32 sum = 0; + v.set(0, 0, 0); + for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) { + if (bits & bit) { + sum += det[bits][i]; + v += y[i] * det[bits][i]; + } + } + v *= 1 / sum; +} + + +//---------------------------------------------------------------------------- + +inline bool GjkCollisionState::valid(int s) +{ + for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) { + if (all_bits & bit) { + if (s & bit) { + if (det[s][i] <= 0) + return false; + } + else + if (det[s | bit][i] > 0) + return false; + } + } + return true; +} + + +//---------------------------------------------------------------------------- + +inline bool GjkCollisionState::closest(VectorF& v) +{ + compute_det(); + for (int s = bits; s; --s) { + if ((s & bits) == s) { + if (valid(s | last_bit)) { + bits = s | last_bit; + if (bits != 15) + compute_vector(bits, v); + return true; + } + } + } + if (valid(last_bit)) { + bits = last_bit; + v = y[last]; + return true; + } + return false; +} + + +//---------------------------------------------------------------------------- + +inline bool GjkCollisionState::degenerate(const VectorF& w) +{ + for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) + if ((all_bits & bit) && y[i] == w) + return true; + return false; +} + + +//---------------------------------------------------------------------------- + +inline void GjkCollisionState::nextBit() +{ + last = 0; + last_bit = 1; + while (bits & last_bit) { + ++last; + last_bit <<= 1; + } +} + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- + +void GjkCollisionState::set(Convex* aa, Convex* bb, + const MatrixF& a2w, const MatrixF& b2w) +{ + a = aa; + b = bb; + + bits = 0; + all_bits = 0; + reset(a2w,b2w); + + // link + mLista = CollisionStateList::alloc(); + mLista->mState = this; + mListb = CollisionStateList::alloc(); + mListb->mState = this; +} + + +//---------------------------------------------------------------------------- + +void GjkCollisionState::reset(const MatrixF& a2w, const MatrixF& b2w) +{ + VectorF zero(0,0,0),sa,sb; + a2w.mulP(a->support(zero),&sa); + b2w.mulP(b->support(zero),&sb); + v = sa - sb; + dist = v.len(); +} + + +//---------------------------------------------------------------------------- + +void GjkCollisionState::getCollisionInfo(const MatrixF& mat, Collision* info) +{ + AssertFatal(false, "GjkCollisionState::getCollisionInfo() - There remain scaling problems here."); + // This assumes that the shapes do not intersect + Point3F pa,pb; + if (bits) { + getClosestPoints(pa,pb); + mat.mulP(pa,&info->point); + b->getTransform().mulP(pb,&pa); + info->normal = info->point - pa; + } + else { + mat.mulP(p[last],&info->point); + info->normal = v; + } + info->normal.normalize(); + info->object = b->getObject(); +} + +void GjkCollisionState::getClosestPoints(Point3F& p1, Point3F& p2) +{ + F32 sum = 0; + p1.set(0, 0, 0); + p2.set(0, 0, 0); + for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) { + if (bits & bit) { + sum += det[bits][i]; + p1 += p[i] * det[bits][i]; + p2 += q[i] * det[bits][i]; + } + } + F32 s = 1 / sum; + p1 *= s; + p2 *= s; +} + + +//---------------------------------------------------------------------------- + +bool GjkCollisionState::intersect(const MatrixF& a2w, const MatrixF& b2w) +{ + num_iterations = 0; + MatrixF w2a,w2b; + + w2a = a2w; + w2b = b2w; + w2a.inverse(); + w2b.inverse(); + reset(a2w,b2w); + + bits = 0; + all_bits = 0; + + do { + nextBit(); + + VectorF va,sa; + w2a.mulV(-v,&va); + p[last] = a->support(va); + a2w.mulP(p[last],&sa); + + VectorF vb,sb; + w2b.mulV(v,&vb); + q[last] = b->support(vb); + b2w.mulP(q[last],&sb); + + VectorF w = sa - sb; + if (mDot(v,w) > 0) + return false; + if (degenerate(w)) { + ++num_irregularities; + return false; + } + + y[last] = w; + all_bits = bits | last_bit; + + ++num_iterations; + if (!closest(v) || num_iterations > sIteration) { + ++num_irregularities; + return false; + } + } + while (bits < 15 && v.lenSquared() > sEpsilon2); + return true; +} + +F32 GjkCollisionState::distance(const MatrixF& a2w, const MatrixF& b2w, + const F32 dontCareDist, const MatrixF* _w2a, const MatrixF* _w2b) +{ + num_iterations = 0; + MatrixF w2a,w2b; + + if (_w2a == NULL || _w2b == NULL) { + w2a = a2w; + w2b = b2w; + w2a.inverse(); + w2b.inverse(); + } + else { + w2a = *_w2a; + w2b = *_w2b; + } + + reset(a2w,b2w); + bits = 0; + all_bits = 0; + F32 mu = 0; + + do { + nextBit(); + + VectorF va,sa; + w2a.mulV(-v,&va); + p[last] = a->support(va); + a2w.mulP(p[last],&sa); + + VectorF vb,sb; + w2b.mulV(v,&vb); + q[last] = b->support(vb); + b2w.mulP(q[last],&sb); + + VectorF w = sa - sb; + F32 nm = mDot(v, w) / dist; + if (nm > mu) + mu = nm; + if (mu > dontCareDist) + return mu; + if (mFabs(dist - mu) <= dist * rel_error) + return dist; + + ++num_iterations; + if (degenerate(w) || num_iterations > sIteration) { + ++num_irregularities; + return dist; + } + + y[last] = w; + all_bits = bits | last_bit; + + if (!closest(v)) { + ++num_irregularities; + return dist; + } + + dist = v.len(); + } + while (bits < 15 && dist > sTolerance) ; + + if (bits == 15 && mu <= 0) + dist = 0; + return dist; +} diff --git a/collision/gjk.h b/collision/gjk.h new file mode 100644 index 0000000..a130474 --- /dev/null +++ b/collision/gjk.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GJK_H_ +#define _GJK_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif + +class Convex; +struct CollisionStateList; +struct Collision; + +//---------------------------------------------------------------------------- + +struct GjkCollisionState: public CollisionState +{ + /// @name Temporary values + /// @{ + Point3F p[4]; ///< support points of object A in local coordinates + Point3F q[4]; ///< support points of object B in local coordinates + VectorF y[4]; ///< support points of A - B in world coordinates + + S32 bits; ///< identifies current simplex + S32 all_bits; ///< all_bits = bits | last_bit + F32 det[16][4]; ///< cached sub-determinants + F32 dp[4][4]; ///< cached dot products + + S32 last; ///< identifies last found support point + S32 last_bit; ///< last_bit = 1<(baseMat->getMaterial()); + + for (U32 i = 0; i < mMaterialList.size(); i++) + { + Material* testMat = dynamic_cast(mMaterialList[i]->getMaterial()); + + if (mat && testMat) + { + if (testMat == mat) + { + retIdx = i; + break; + } + } + else if (mMaterialList[i] == baseMat) + { + retIdx = i; + break; + } + } + + if (retIdx == -1) + { + retIdx = mMaterialList.size(); + mMaterialList.push_back(baseMat); + } + + return (U32)retIdx; +} + +U32 OptimizedPolyList::insertVertex(const Point3F& point, const Point3F& normal, + const Point2F& uv0, const Point2F& uv1) +{ + VertIndex vert; + + vert.vertIdx = insertPoint(point); + vert.normalIdx = insertNormal(normal); + vert.uv0Idx = insertUV0(uv0); + vert.uv1Idx = insertUV1(uv1); + + return mVertexList.push_back_unique(vert); +} + +U32 OptimizedPolyList::addPoint(const Point3F& p) +{ + return insertVertex(p); +} + +U32 OptimizedPolyList::addPlane(const PlaneF& plane) +{ + return insertPlane(plane); +} + + +//---------------------------------------------------------------------------- + +void OptimizedPolyList::begin(BaseMatInstance* material, U32 surfaceKey) +{ + mPolyList.increment(); + Poly& poly = mPolyList.last(); + poly.material = insertMaterial(material); + poly.vertexStart = mIndexList.size(); + poly.surfaceKey = surfaceKey; + poly.type = TriangleFan; + poly.object = mCurrObject; +} + +void OptimizedPolyList::begin(BaseMatInstance* material, U32 surfaceKey, PolyType type) +{ + begin(material, surfaceKey); + + // Set the type + mPolyList.last().type = type; +} + + +//---------------------------------------------------------------------------- + +void OptimizedPolyList::plane(U32 v1, U32 v2, U32 v3) +{ + AssertFatal(v1 < mPoints.size() && v2 < mPoints.size() && v3 < mPoints.size(), + "OptimizedPolyList::plane(): Vertex indices are larger than vertex list size"); + + mPolyList.last().plane = addPlane(PlaneF(mPoints[v1], mPoints[v2], mPoints[v3])); +} + +void OptimizedPolyList::plane(const PlaneF& p) +{ + mPolyList.last().plane = addPlane(p); +} + +void OptimizedPolyList::plane(const U32 index) +{ + AssertFatal(index < mPlaneList.size(), "Out of bounds index!"); + mPolyList.last().plane = index; +} + +const PlaneF& OptimizedPolyList::getIndexedPlane(const U32 index) +{ + AssertFatal(index < mPlaneList.size(), "Out of bounds index!"); + return mPlaneList[index]; +} + + +//---------------------------------------------------------------------------- + +void OptimizedPolyList::vertex(U32 vi) +{ + mIndexList.push_back(vi); +} + +void OptimizedPolyList::vertex(const Point3F& p) +{ + mIndexList.push_back(addPoint(p)); +} + +void OptimizedPolyList::vertex(const Point3F& p, + const Point3F& normal, + const Point2F& uv0, + const Point2F& uv1) +{ + mIndexList.push_back(insertVertex(p, normal, uv0, uv1)); +} + + +//---------------------------------------------------------------------------- + +bool OptimizedPolyList::isEmpty() const +{ + return !mPolyList.size(); +} + +void OptimizedPolyList::end() +{ + Poly& poly = mPolyList.last(); + poly.vertexCount = mIndexList.size() - poly.vertexStart; +} diff --git a/collision/optimizedPolyList.h b/collision/optimizedPolyList.h new file mode 100644 index 0000000..9846d18 --- /dev/null +++ b/collision/optimizedPolyList.h @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _OPTIMIZEDPOLYLIST_H_ +#define _OPTIMIZEDPOLYLIST_H_ + +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif + +#define DEV 0.01 + +/// A concrete, renderable PolyList +/// +/// This class is used to store geometry from a PolyList query. +/// +/// It allows you to render this data, as well. +/// +/// @see AbstractPolyList +class OptimizedPolyList : public AbstractPolyList +{ + public: + + enum PolyType + { + TriangleFan, + TriangleStrip, + TriangleList + }; + + struct VertIndex + { + S32 vertIdx; + S32 normalIdx; + S32 uv0Idx; + S32 uv1Idx; + + VertIndex() + : vertIdx( -1 ), + normalIdx ( -1 ), + uv0Idx( -1 ), + uv1Idx( -1 ) + { + } + + bool operator==(const VertIndex& _test) const + { + return ( vertIdx == _test.vertIdx && + normalIdx == _test.normalIdx && + uv0Idx == _test.uv0Idx && + uv1Idx == _test.uv1Idx ); + } + }; + + struct Poly + { + S32 plane; + S32 material; + U32 vertexStart; + U32 vertexCount; + U32 surfaceKey; + + SceneObject* object; + + PolyType type; + + Poly() + : plane( -1 ), + object( NULL ), + vertexCount( 0 ), + material( NULL ), + type( TriangleFan ) + { + } + }; + + // Vertex data + Vector mPoints; + Vector mNormals; + Vector mUV0s; + Vector mUV1s; + + // List of the VertIndex structure that puts + // all of the vertex data together + Vector mVertexList; + + // Polygon data + Vector mIndexList; + Vector mPlaneList; + + Vector mMaterialList; + + // The Polygon structure puts the vertex data + // and the polygon together + Vector mPolyList; + + public: + OptimizedPolyList(); + ~OptimizedPolyList(); + void clear(); + + // Virtual methods + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + + void begin(BaseMatInstance* material, U32 surfaceKey); + void begin(BaseMatInstance* material, U32 surfaceKey, PolyType type); + void plane(U32 v1, U32 v2, U32 v3); + void plane(const PlaneF& p); + void plane(const U32 index); + void vertex(U32 vi); + void vertex(const Point3F& p); + void vertex(const Point3F& p, + const Point3F& normal, + const Point2F& uv0 = Point2F(0.0f, 0.0f), + const Point2F& uv1 = Point2F(0.0f, 0.0f)); + void end(); + + U32 insertPoint(const Point3F& point); + U32 insertNormal(const Point3F& normal); + U32 insertUV0(const Point2F& uv); + U32 insertUV1(const Point2F& uv); + U32 insertPlane(const PlaneF& plane); + U32 insertMaterial(BaseMatInstance* baseMat); + + U32 insertVertex(const Point3F& point, + const Point3F& normal = Point3F(0.0f, 0.0f, 1.0f), + const Point2F& uv0 = Point2F(0.0f, 0.0f), + const Point2F& uv1 = Point2F(0.0f, 0.0f)); + + bool isEmpty() const; + + protected: + const PlaneF& getIndexedPlane(const U32 index); +}; + +#endif // _OPTIMIZEDPOLYLIST_H_ diff --git a/collision/planeExtractor.cpp b/collision/planeExtractor.cpp new file mode 100644 index 0000000..00de1f0 --- /dev/null +++ b/collision/planeExtractor.cpp @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMath.h" +#include "console/console.h" +#include "collision/planeExtractor.h" + +//---------------------------------------------------------------------------- +// Plane matching parameters +static F32 NormalEpsilon = 0.93969f; // 20 deg. +static F32 DistanceEpsilon = 100.0f; + + +//---------------------------------------------------------------------------- + +PlaneExtractorPolyList::PlaneExtractorPolyList() +{ + VECTOR_SET_ASSOCIATION(mVertexList); + VECTOR_SET_ASSOCIATION(mPolyPlaneList); +} + +PlaneExtractorPolyList::~PlaneExtractorPolyList() +{ +} + + +//---------------------------------------------------------------------------- + +void PlaneExtractorPolyList::clear() +{ + mVertexList.clear(); + mPolyPlaneList.clear(); +} + +U32 PlaneExtractorPolyList::addPoint(const Point3F& p) +{ + mVertexList.increment(); + Point3F& v = mVertexList.last(); + v.x = p.x * mScale.x; + v.y = p.y * mScale.y; + v.z = p.z * mScale.z; + mMatrix.mulP(v); + return mVertexList.size() - 1; +} + +U32 PlaneExtractorPolyList::addPlane(const PlaneF& plane) +{ + mPolyPlaneList.increment(); + mPlaneTransformer.transform(plane, mPolyPlaneList.last()); + + return mPolyPlaneList.size() - 1; +} + + +//---------------------------------------------------------------------------- + +void PlaneExtractorPolyList::plane(U32 v1,U32 v2,U32 v3) +{ + mPlaneList->last().set(mVertexList[v1], + mVertexList[v2],mVertexList[v3]); +} + +void PlaneExtractorPolyList::plane(const PlaneF& p) +{ + mPlaneTransformer.transform(p, mPlaneList->last()); +} + +void PlaneExtractorPolyList::plane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + mPlaneList->last() = mPolyPlaneList[index]; +} + +const PlaneF& PlaneExtractorPolyList::getIndexedPlane(const U32 index) +{ + AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!"); + return mPolyPlaneList[index]; +} + + +//---------------------------------------------------------------------------- + +bool PlaneExtractorPolyList::isEmpty() const +{ + return true; +} + +void PlaneExtractorPolyList::begin(BaseMatInstance*,U32) +{ + mPlaneList->increment(); +} + +void PlaneExtractorPolyList::end() +{ + // See if there are any duplicate planes + PlaneF &plane = mPlaneList->last(); + PlaneF *ptr = mPlaneList->begin(); + for (; ptr != &plane; ptr++) + if (mFabs(ptr->d - plane.d) < DistanceEpsilon && + mDot(*ptr,plane) > NormalEpsilon) { + mPlaneList->decrement(); + return; + } +} + +void PlaneExtractorPolyList::vertex(U32) +{ +} + diff --git a/collision/planeExtractor.h b/collision/planeExtractor.h new file mode 100644 index 0000000..02aa1f4 --- /dev/null +++ b/collision/planeExtractor.h @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLANEEXTRACTOR_H_ +#define _PLANEEXTRACTOR_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif + + +//---------------------------------------------------------------------------- + +/// Fill a Vector with the planes from the geometry passed through this +/// PolyList. +/// +/// @see AbstractPolyList +class PlaneExtractorPolyList: public AbstractPolyList +{ +public: + // Internal data + typedef Vector VertexList; + VertexList mVertexList; + + Vector mPolyPlaneList; + + // Set by caller + Vector* mPlaneList; + + // + PlaneExtractorPolyList(); + ~PlaneExtractorPolyList(); + void clear(); + + // Virtual methods + bool isEmpty() const; + U32 addPoint(const Point3F& p); + U32 addPlane(const PlaneF& plane); + void begin(BaseMatInstance* material,U32 surfaceKey); + void plane(U32 v1,U32 v2,U32 v3); + void plane(const PlaneF& p); + void plane(const U32 index); + void vertex(U32 vi); + void end(); + + protected: + const PlaneF& getIndexedPlane(const U32 index); +}; + + +#endif diff --git a/collision/polyhedron.cpp b/collision/polyhedron.cpp new file mode 100644 index 0000000..6fab2f3 --- /dev/null +++ b/collision/polyhedron.cpp @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "collision/polyhedron.h" + + +//---------------------------------------------------------------------------- + +void Polyhedron::buildBox(const MatrixF& transform,const Box3F& box) +{ + // Box is assumed to be axis aligned in the source space. + // Transform into geometry space + Point3F xvec,yvec,zvec,min; + transform.getColumn(0,&xvec); + xvec *= box.len_x(); + transform.getColumn(1,&yvec); + yvec *= box.len_y(); + transform.getColumn(2,&zvec); + zvec *= box.len_z(); + transform.mulP(box.minExtents,&min); + + // Initial vertices + pointList.setSize(8); + pointList[0] = min; + pointList[1] = min + yvec; + pointList[2] = min + xvec + yvec; + pointList[3] = min + xvec; + pointList[4] = pointList[0] + zvec; + pointList[5] = pointList[1] + zvec; + pointList[6] = pointList[2] + zvec; + pointList[7] = pointList[3] + zvec; + + // Initial faces + planeList.setSize(6); + planeList[0].set(pointList[0],xvec); + planeList[0].invert(); + planeList[1].set(pointList[2],yvec); + planeList[2].set(pointList[2],xvec); + planeList[3].set(pointList[0],yvec); + planeList[3].invert(); + planeList[4].set(pointList[0],zvec); + planeList[4].invert(); + planeList[5].set(pointList[4],zvec); + + // The edges are constructed so that the vertices + // are oriented clockwise for face[0] + edgeList.setSize(12); + Edge* edge = edgeList.begin(); + for (int i = 0; i < 4; i++) { + S32 n = (i == 3)? 0: i + 1; + S32 p = (i == 0)? 3: i - 1; + edge->vertex[0] = i; + edge->vertex[1] = n; + edge->face[0] = i; + edge->face[1] = 4; + edge++; + edge->vertex[0] = 4 + i; + edge->vertex[1] = 4 + n; + edge->face[0] = 5; + edge->face[1] = i; + edge++; + edge->vertex[0] = i; + edge->vertex[1] = 4 + i; + edge->face[0] = p; + edge->face[1] = i; + edge++; + } +} diff --git a/collision/polyhedron.h b/collision/polyhedron.h new file mode 100644 index 0000000..a179b61 --- /dev/null +++ b/collision/polyhedron.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POLYHEDRON_H_ +#define _POLYHEDRON_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +//---------------------------------------------------------------------------- + +struct Polyhedron +{ + struct Edge + { + // Edge vertices must be oriented clockwise + // for face[0] + U32 face[2]; + U32 vertex[2]; + }; + + Vector pointList; + Vector planeList; + Vector edgeList; + + // Misc support methods + Polyhedron() { + VECTOR_SET_ASSOCIATION(pointList); + VECTOR_SET_ASSOCIATION(planeList); + VECTOR_SET_ASSOCIATION(edgeList); + } + + void buildBox(const MatrixF& mat, const Box3F& box); +}; + + +#endif diff --git a/collision/polytope.cpp b/collision/polytope.cpp new file mode 100644 index 0000000..894fce7 --- /dev/null +++ b/collision/polytope.cpp @@ -0,0 +1,412 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMath.h" +#include "collision/collision.h" +#include "collision/polytope.h" + +//---------------------------------------------------------------------------- + +Polytope::Polytope() +{ + VECTOR_SET_ASSOCIATION(mEdgeList); + VECTOR_SET_ASSOCIATION(mFaceList); + VECTOR_SET_ASSOCIATION(mVertexList); + VECTOR_SET_ASSOCIATION(mVolumeList); + + mVertexList.reserve(100); + mFaceList.reserve(200); + mEdgeList.reserve(100); + mVolumeList.reserve(20); + sideCount = 0; +} + + +//---------------------------------------------------------------------------- +// Box should be axis aligned in the transform space provided. + +void Polytope::buildBox(const MatrixF& transform,const Box3F& box) +{ + // Box is assumed to be axis aligned in the source space. + // Transform into geometry space + Point3F xvec,yvec,zvec,min; + transform.getColumn(0,&xvec); + xvec *= box.len_x(); + transform.getColumn(1,&yvec); + yvec *= box.len_y(); + transform.getColumn(2,&zvec); + zvec *= box.len_z(); + transform.mulP(box.minExtents,&min); + + // Initial vertices + mVertexList.setSize(8); + mVertexList[0].point = min; + mVertexList[1].point = min + yvec; + mVertexList[2].point = min + xvec + yvec; + mVertexList[3].point = min + xvec; + mVertexList[4].point = mVertexList[0].point + zvec; + mVertexList[5].point = mVertexList[1].point + zvec; + mVertexList[6].point = mVertexList[2].point + zvec; + mVertexList[7].point = mVertexList[3].point + zvec; + S32 i; + for (i = 0; i < 8; i++) + mVertexList[i].side = 0; + + // Initial faces + mFaceList.setSize(6); + for (S32 f = 0; f < 6; f++) { + Face& face = mFaceList[f]; + face.original = true; + face.vertex = 0; + } + + mFaceList[0].plane.set(mVertexList[0].point,xvec); + mFaceList[0].plane.invert(); + mFaceList[1].plane.set(mVertexList[2].point,yvec); + mFaceList[2].plane.set(mVertexList[2].point,xvec); + mFaceList[3].plane.set(mVertexList[0].point,yvec); + mFaceList[3].plane.invert(); + mFaceList[4].plane.set(mVertexList[0].point,zvec); + mFaceList[4].plane.invert(); + mFaceList[5].plane.set(mVertexList[4].point,zvec); + + // Initial edges + mEdgeList.setSize(12); + Edge* edge = mEdgeList.begin(); + S32 nextEdge = 0; + for (i = 0; i < 4; i++) { + S32 n = (i == 3)? 0: i + 1; + S32 p = (i == 0)? 3: i - 1; + edge->vertex[0] = i; + edge->vertex[1] = n; + edge->face[0] = i; + edge->face[1] = 4; + edge->next = ++nextEdge; + edge++; + edge->vertex[0] = 4 + i; + edge->vertex[1] = 4 + n; + edge->face[0] = i; + edge->face[1] = 5; + edge->next = ++nextEdge; + edge++; + edge->vertex[0] = i; + edge->vertex[1] = 4 + i; + edge->face[0] = i; + edge->face[1] = p; + edge->next = ++nextEdge; + edge++; + } + edge[-1].next = -1; + + // Volume + mVolumeList.setSize(1); + Volume& volume = mVolumeList.last(); + volume.edgeList = 0; + volume.material = -1; + volume.object = 0; + sideCount = 0; +} + + +//---------------------------------------------------------------------------- + +void Polytope::intersect(SimObject* object,const BSPNode* root) +{ + AssertFatal(mVolumeList.size() > 0,"Polytope::intersect: Missing initial volume"); + + // Always clips the first volume against the BSP + VolumeStack stack; + stack.reserve(50); + stack.increment(); + stack.last().edgeList = mVolumeList[0].edgeList; + stack.last().node = root; + + while (!stack.empty()) { + StackElement volume = stack.last(); + stack.pop_back(); + + // If it's a solid node, export the volume + const BSPNode* node = volume.node; + if (!node->backNode && !node->frontNode) { + mVolumeList.increment(); + Volume& vol = mVolumeList.last(); + vol.object = object; + vol.material = node->material; + vol.edgeList = volume.edgeList; + continue; + } + + // New front and back faces + mFaceList.increment(2); + Face& frontFace = mFaceList.last(); + Face& backFace = *(&frontFace - 1); + + backFace.original = frontFace.original = false; + backFace.vertex = frontFace.vertex = 0; + backFace.plane = frontFace.plane = node->plane; + backFace.plane.invert(); + + // New front and back volumes + StackElement frontVolume,backVolume; + frontVolume.edgeList = backVolume.edgeList = -1; + + const PlaneF& plane = node->plane; + S32 startVertex = mVertexList.size(); + + // Test & clip all the edges + S32 sideBase = ++sideCount << 1; + for (S32 i = volume.edgeList; i >= 0; i = mEdgeList[i].next) { + + // Copy into tmp first to avoid problems with the array. + Edge edge = mEdgeList[i]; + + Vertex& v0 = mVertexList[edge.vertex[0]]; + if (v0.side < sideBase) + v0.side = sideBase + ((plane.distToPlane(v0.point) >= 0)? 0: 1); + Vertex& v1 = mVertexList[edge.vertex[1]]; + if (v1.side < sideBase) + v1.side = sideBase + ((plane.distToPlane(v1.point) >= 0)? 0: 1); + + if (v0.side != v1.side) { + S32 s = v0.side - sideBase; + intersect(plane,v0.point,v1.point); + + // Split the edge into each volume + mEdgeList.increment(2); + Edge& e0 = mEdgeList.last(); + e0.next = frontVolume.edgeList; + frontVolume.edgeList = mEdgeList.size() - 1; + + Edge& e1 = *(&e0 - 1); + e1.next = backVolume.edgeList; + backVolume.edgeList = frontVolume.edgeList - 1; + + e0.vertex[0] = edge.vertex[s]; + e1.vertex[0] = edge.vertex[s ^ 1]; + e0.vertex[1] = e1.vertex[1] = mVertexList.size() - 1; + e0.face[0] = e1.face[0] = edge.face[0]; + e0.face[1] = e1.face[1] = edge.face[1]; + + // Add new edges on the plane, one to each volume + for (S32 f = 0; f < 2; f++) { + Face& face = mFaceList[edge.face[f]]; + if (face.vertex < startVertex) + face.vertex = mVertexList.size() - 1; + else { + mEdgeList.increment(2); + Edge& e0 = mEdgeList.last(); + e0.next = frontVolume.edgeList; + frontVolume.edgeList = mEdgeList.size() - 1; + + Edge& e1 = *(&e0 - 1); + e1.next = backVolume.edgeList; + backVolume.edgeList = frontVolume.edgeList - 1; + + e1.vertex[0] = e0.vertex[0] = face.vertex; + e1.vertex[1] = e0.vertex[1] = mVertexList.size() - 1; + e1.face[0] = e0.face[0] = edge.face[f]; + e1.face[1] = mFaceList.size() - 1; + e0.face[1] = e1.face[1] - 1; + } + } + } + else + if (v0.side == sideBase) { + mEdgeList.push_back(edge); + Edge& ne = mEdgeList.last(); + ne.next = frontVolume.edgeList; + frontVolume.edgeList = mEdgeList.size() - 1; + } + else { + mEdgeList.push_back(edge); + Edge& ne = mEdgeList.last(); + ne.next = backVolume.edgeList; + backVolume.edgeList = mEdgeList.size() - 1; + } + } + + // Push the front and back nodes + if (node->frontNode && frontVolume.edgeList >= 0) { + frontVolume.node = node->frontNode; + stack.push_back(frontVolume); + } + if (node->backNode && backVolume.edgeList >= 0) { + backVolume.node = node->backNode; + stack.push_back(backVolume); + } + } +} + + +//---------------------------------------------------------------------------- + +bool Polytope::intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep) +{ + // If den == 0 then the line and plane are parallel. + F32 den; + Point3F dt = ep - sp; + if ((den = plane.x * dt.x + plane.y * dt.y + plane.z * dt.z) == 0) + return false; + + mVertexList.increment(); + Vertex& v = mVertexList.last(); + F32 s = -(plane.x * sp.x + plane.y * sp.y + plane.z * sp.z + plane.d) / den; + v.point.x = sp.x + dt.x * s; + v.point.y = sp.y + dt.y * s; + v.point.z = sp.z + dt.z * s; + v.side = 0; + return true; +} + +//---------------------------------------------------------------------------- + +void Polytope::extrudeFace(int faceIdx,const VectorF& vec,Polytope* out) +{ + // Assumes the face belongs to the first volume. + out->mVertexList.clear(); + out->mFaceList.clear(); + out->mEdgeList.clear(); + out->mVolumeList.clear(); + sideCount++; + + // Front & end faces + Face nface; + nface.original = true; + nface.vertex = 0; + nface.plane = mFaceList[faceIdx].plane; + out->mFaceList.setSize(2); + out->mFaceList[0] = out->mFaceList[1] = nface; + out->mFaceList[0].plane.invert(); + + for (S32 e = mVolumeList[0].edgeList; e >= 0; e = mEdgeList[e].next) { + Edge& edge = mEdgeList[e]; + if (edge.face[0] == faceIdx || edge.face[1] == faceIdx) { + + // Build face for this edge + // Should think about calulating the plane + S32 fi = out->mFaceList.size(); + out->mFaceList.push_back(nface); + + // Reserve 4 entries to make sure the ve[] pointers + // into the list don't get invalidated. + out->mEdgeList.reserve(out->mEdgeList.size() + 4); + Edge* ve[2]; + + // Build edges for each vertex + for (S32 v = 0; v < 2; v++) { + if (mVertexList[edge.vertex[v]].side < sideCount) { + mVertexList[edge.vertex[v]].side = sideCount + out->mEdgeList.size(); + + out->mVertexList.increment(2); + out->mVertexList.end()[-1] = + out->mVertexList.end()[-2] = + mVertexList[edge.vertex[v]]; + out->mVertexList.last().point += vec; + + out->mEdgeList.increment(); + Edge& ne = out->mEdgeList.last(); + ne.next = out->mEdgeList.size(); + ne.vertex[1] = out->mVertexList.size() - 1; + ne.vertex[0] = ne.vertex[1] - 1; + ne.face[0] = ne.face[1] = -1; + ve[v] = ≠ + } + else { + S32 ei = mVertexList[edge.vertex[v]].side - sideCount; + ve[v] = &out->mEdgeList[ei]; + } + + // Edge should share this face + if (ve[v]->face[0] == -1) + ve[v]->face[0] = fi; + else + ve[v]->face[1] = fi; + } + + // Build parallel edges + out->mEdgeList.increment(2); + for (S32 i = 0; i < 2; i++ ) { + Edge& ne = out->mEdgeList.end()[i - 2]; + ne.next = out->mEdgeList.size() - 1 + i; + ne.vertex[0] = ve[0]->vertex[i]; + ne.vertex[1] = ve[1]->vertex[i]; + ne.face[0] = i; + ne.face[1] = fi; + } + } + } + + out->mEdgeList.last().next = -1; + out->mVolumeList.increment(); + Volume& nv = out->mVolumeList.last(); + nv.edgeList = 0; + nv.object = 0; + nv.material = -1; +} + + +//---------------------------------------------------------------------------- + +bool Polytope::findCollision(const VectorF& vec,Polytope::Collision *best) +{ + if (mVolumeList.size() <= 1) + return false; + if (!best->object) + best->distance = 1.0E30f; + int bestVertex = -1; + Polytope::Volume* bestVolume = NULL; + sideCount++; + + // Find the closest point + for (Volume* vol = mVolumeList.begin() + 1; + vol < mVolumeList.end(); vol++) { + for (S32 e = vol->edgeList; e >= 0; e = mEdgeList[e].next) { + Edge& edge = mEdgeList[e]; + if (mFaceList[edge.face[0]].original && + mFaceList[edge.face[1]].original) + continue; + for (S32 v = 0; v < 2; v++) { + S32 vi = edge.vertex[v]; + Vertex& vr = mVertexList[vi]; + if (vr.side != sideCount) { + vr.side = sideCount; + F32 dist = mDot(vr.point,vec); + if (dist < best->distance) { + best->distance = dist; + bestVertex = vi; + bestVolume = vol; + } + } + } + } + } + + if (bestVertex == -1) + return false; + + // Fill in the return value + best->point = mVertexList[bestVertex].point; + best->object = bestVolume->object; + best->material = bestVolume->material; + + // Pick the best face + F32 bestFaceDot = 1; + for (S32 e = bestVolume->edgeList; e >= 0; e = mEdgeList[e].next) { + Edge& edge = mEdgeList[e]; + if (edge.vertex[0] == bestVertex || edge.vertex[1] == bestVertex) { + for (S32 f = 0; f < 2; f++) { + Face& tf = mFaceList[edge.face[f]]; + F32 fd = mDot(tf.plane,vec); + if (fd < bestFaceDot) { + bestFaceDot = fd; + best->plane = tf.plane; + } + } + } + } + return true; +} + diff --git a/collision/polytope.h b/collision/polytope.h new file mode 100644 index 0000000..783bc42 --- /dev/null +++ b/collision/polytope.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POLYTOPE_H_ +#define _POLYTOPE_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + + +//---------------------------------------------------------------------------- + +class SimObject; + + +//---------------------------------------------------------------------------- + +class Polytope +{ + // Convex Polyhedron +public: + struct Vertex { + Point3F point; + /// Temp BSP clip info + S32 side; + }; + struct Edge { + S32 vertex[2]; + S32 face[2]; + S32 next; + }; + struct Face { + PlaneF plane; + S32 original; + /// Temp BSP clip info + S32 vertex; + }; + struct Volume + { + S32 edgeList; + S32 material; + SimObject* object; + }; + struct StackElement + { + S32 edgeList; + const BSPNode *node; + }; + struct Collision { + SimObject* object; + S32 material; + PlaneF plane; + Point3F point; + F32 distance; + + Collision() + { + object = NULL; + distance = 0.0; + } + }; + + typedef Vector EdgeList; + typedef Vector FaceList; + typedef Vector VertexList; + typedef Vector VolumeList; + typedef Vector VolumeStack; + + // + S32 sideCount; + EdgeList mEdgeList; + FaceList mFaceList; + VertexList mVertexList; + VolumeList mVolumeList; + +private: + bool intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep); + +public: + // + Polytope(); + void buildBox(const MatrixF& transform,const Box3F& box); + void intersect(SimObject*, const BSPNode* node); + inline bool didIntersect() { return mVolumeList.size() > 1; } + void extrudeFace(int fi,const VectorF& vec,Polytope* out); + bool findCollision(const VectorF& vec,Polytope::Collision *best); +}; + + + + +#endif diff --git a/component/componentInterface.cpp b/component/componentInterface.cpp new file mode 100644 index 0000000..b18cc7d --- /dev/null +++ b/component/componentInterface.cpp @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "component/simComponent.h" +#include "component/componentInterface.h" +#include "core/strings/findMatch.h" +#include "core/stringTable.h" + +bool ComponentInterfaceCache::add( const char *type, const char *name, const SimComponent *owner, ComponentInterface *cinterface ) +{ + if( ( mInterfaceList.size() == 0 ) || ( enumerate( NULL, type, name, owner ) == 0 ) ) + { + mInterfaceList.increment(); + // CodeReview [tom, 3/9/2007] Seems silly to keep calling last(), why not cache the var? Yes, I know I am pedantic. + mInterfaceList.last().type = ( type == NULL ? NULL : StringTable->insert( type ) ); + mInterfaceList.last().name = ( name == NULL ? NULL : StringTable->insert( name ) ); + mInterfaceList.last().owner = owner; + mInterfaceList.last().iface = cinterface; + + return true; + } + + return false; +} + +//------------------------------------------------------------------------------ + +void ComponentInterfaceCache::clear() +{ + mInterfaceList.clear(); +} + +//------------------------------------------------------------------------------ + +U32 ComponentInterfaceCache::enumerate( ComponentInterfaceList *list, const char *type /* = NULL */, + const char *name /* = NULL */, const SimComponent *owner /* = NULL */, bool notOwner /* = false */ ) const +{ + U32 numMatches = 0; + + for( _InterfaceEntryItr i = mInterfaceList.begin(); i != mInterfaceList.end(); i++ ) + { + // Early out if limiting results by component owner + if( owner != NULL && ( + ( (*i).owner == owner && notOwner ) || + ( (*i).owner != owner && !notOwner ) ) ) + continue; + + // Match the type, short circuit if type == NULL + if( type == NULL || FindMatch::isMatch( type, (*i).type ) ) + { + // Match the name + if( name == NULL || FindMatch::isMatch( name, (*i).name ) ) + { + numMatches++; + + if( list != NULL ) + list->push_back( (*i).iface ); + } + } + } + + return numMatches; +} \ No newline at end of file diff --git a/component/componentInterface.h b/component/componentInterface.h new file mode 100644 index 0000000..e4e0c4e --- /dev/null +++ b/component/componentInterface.h @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COMPONENTINTERFACE_H_ +#define _COMPONENTINTERFACE_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + +#include "core/util/safeDelete.h" + + +class SimComponent; + + +// CodeReview [patw, 2, 13, 2007] The issue I have not addressed in this class is +// interface locking. I think that we want to do this, for sure, but I also want +// to keep it as light-weight as possible. For the most part, there should only +// ever be one thing doing something with a component at one time, but I can see +// many situations where this wouldn't be the case. When we decide to address +// the issues of locking, I believe it should be done here, at the ComponentInterface +// level. I would like lock functionality to be as centralized as possible, and +// so this is the place for it. The functionality is critical for safe useage of +// the ComponentProperty class, so implementation here would also be ideal. + +// CodeReview [patw, 2, 14, 2007] This really should be a ref-counted object +class ComponentInterface +{ + friend class SimComponent; +private: + SimObjectPtr mOwner; ///< SimComponent will directly modify this value + +public: + /// Default constructor + ComponentInterface() : mOwner(NULL) {}; + + /// Destructor + virtual ~ComponentInterface() + { + mOwner = NULL; + } + + /// This will return true if the interface is valid + virtual bool isValid() const + { + return mOwner != NULL; + } + + /// Get the owner of this interface + SimComponent *getOwner() { return mOwner; } + const SimComponent *getOwner() const { return mOwner; } +}; + +typedef VectorPtr ComponentInterfaceList; +typedef VectorPtr::iterator ComponentInterfaceListIterator; + +// These two asserts I found myself wanting a lot when doing interface methods +#ifdef TORQUE_ENABLE_ASSERTS +# define VALID_INTERFACE_ASSERT(OwningClassType) \ + AssertFatal( isValid(), "Interface validity check failed." ); \ + AssertFatal( dynamic_cast( getOwner() ) != NULL, avar( "Owner is not an instance of %s", #OwningClassType ) ) +#else +# define VALID_INTERFACE_ASSERT(OwningClassType) +#endif + +/// This class is designed to wrap an existing class or type easily to allow +/// a SimComponent to expose a property with custom processing code in an efficient +/// and safe way. Specialized templates could be written which include validation +/// on sets, and processing on gets. +/// +/// This class has a lot of "blow your leg off" potential, if you have bad aim. +/// I think that a lot of very intuitive functionality can be gained from using +/// this properly, however when implementing a specialized template, be mindful +/// of what you are doing, and + +// CodeReview [patw, 2, 13, 2007] I am very interested in making this as thin as +// possible. I really like the possibilities that it exposes as far as exposing +// "properties" to other components. I want it to be performant, however, so +// if anyone has notes on this, mark up the source, e-mail me, whatever. +template +class ComponentProperty : public ComponentInterface +{ + typedef ComponentInterface Parent; + +protected: + T *mValuePtr; + + // ComponentInterface Overrides +public: + + // Override this to add a check for valid memory. + virtual bool isValid() const + { + return ( mValuePtr != NULL ) && Parent::isValid(); + } + + // Operator overloads +public: + /// Dereferencing a value interface will allow get to do any processing and + /// return the reference to that + const T &operator*() + { + return get(); + } + + /// Assignment operator will invoke set. + const T &operator=( const T &lval ) + { + return set( lval ); + } + + // Constructors/Destructors, specialize these if needed +public: + /// Default Constructor. + ComponentProperty() : mValuePtr( NULL ) + { + mValuePtr = new T; + } + + /// Copy constructor + ComponentProperty( const T © ) + { + ComponentProperty(); + + // CodeReview [patw, 2, 13, 2007] So, the reasoning here is that I want to + // use the functionality that a specialized template implements in the set + // method. See the notes on the set method implementation. + set( copy ); + } + + /// Destructor, destroy memory + virtual ~ComponentProperty() + { + SAFE_DELETE( mValuePtr ); + } + + // This is the ComponentProperty interface that specializations of the class + // will be interested in. +public: + + /// Get the value associated with this interface. Processing code can be done + /// here for specialized implementations. + virtual const T &get() // 'const' is intentionally not used as a modifier here + { + return *mValuePtr; + } + + /// Set the value associated with this interface. Validation/processing code + /// can be done here. The copy-constructor uses the set method to do it's copy + /// so be mindful of that, or specialize the copy-constructor. + virtual const T &set( const T &t ) + { + // CodeReview [patw, 2, 13, 2007] So I am using the = operator here. Do you + // guys think that this should be the default behavior? I am trying to keep + // everything as object friendly as possible, so I figured I'd use this. + *mValuePtr = t; + return *mValuePtr; + } +}; + +/// This class is just designed to isolate the functionality of querying for, and +/// managing interfaces. +class ComponentInterfaceCache +{ + // CodeReview [patw, 2, 14, 2007] When we move this whole system to Juggernaught + // we may want to consider making safe pointers for ComponentInterfaces. Not + // sure why I put this note here. +private: + struct _InterfaceEntry + { + ComponentInterface *iface; + StringTableEntry type; + StringTableEntry name; + const SimComponent *owner; + }; + + Vector<_InterfaceEntry> mInterfaceList; + typedef Vector<_InterfaceEntry>::const_iterator _InterfaceEntryItr; + +public: + /// Add an interface to the cache. This function will return true if the interface + /// is added successfully. An interface will not be added successfully if an entry + /// in the list with the same values for 'type' and 'name' is present in the list. + /// + /// @param type Type of the interface being added. If NULL is passed, it will match any type string queried. + /// @param name Name of interface being added. If NULL is passed, it will match any name string queried. + /// @param owner The owner of the ComponentInterface being cached + /// @param cinterface The ComponentInterface being cached + virtual bool add( const char *type, const char *name, const SimComponent *owner, ComponentInterface *cinterface ); + + /// Clear the interface cache. This does not perform any operations on the contents + /// of the list. + virtual void clear(); + + /// Query the list for all of the interfaces it stores references to that match + /// the 'type' and 'name' parameters. The results of the query will be appended + /// to the list specified. Pattern matching is done using core/findMatch.h; for + /// more information on matching, see that code/header pair. Passing NULL for + /// one of these fields will match all values for that field. The return value + /// for the method will be the number of interfaces which match the query. + /// + /// @param list The list that this method will append search results on to. It is possible to pass NULL here and just receive the return value. + /// @param type An expression which the 'type' field on an added object must match to be included in results + /// @param name An expression which the 'name' field on an added object must match to be included in results + /// @param owner Limit results to components owned/not-owned by this SimComponent (see next param) + /// @param notOwner If set to true, this will enumerate only interfaces NOT owned by 'owner' + virtual U32 enumerate( ComponentInterfaceList *list, const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ) const; +}; + +#endif \ No newline at end of file diff --git a/component/dynamicConsoleMethodComponent.cpp b/component/dynamicConsoleMethodComponent.cpp new file mode 100644 index 0000000..7d77107 --- /dev/null +++ b/component/dynamicConsoleMethodComponent.cpp @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "component/dynamicConsoleMethodComponent.h" + +IMPLEMENT_CO_NETOBJECT_V1(DynamicConsoleMethodComponent); + +//----------------------------------------------------------- +// Function name: SimComponent::handlesConsoleMethod +// Summary: +//----------------------------------------------------------- +bool DynamicConsoleMethodComponent::handlesConsoleMethod( const char *fname, S32 *routingId ) +{ + // CodeReview: Host object is now given priority over components for method + // redirection. [6/23/2007 Pat] + + // On this object? + if( isMethod( fname ) ) + { + *routingId = -1; // -1 denotes method on object +#ifdef TORQUE_DEBUG + // Inject Method. + injectMethodCall( fname ); +#endif + return true; + } + + // on this objects components? + S32 nI = 0; + VectorPtr &componentList = lockComponentList(); + for( SimComponentIterator nItr = componentList.begin(); nItr != componentList.end(); nItr++, nI++ ) + { + SimObject *pComponent = dynamic_cast(*nItr); + if( pComponent != NULL && pComponent->isMethod( fname ) ) + { + *routingId = -2; // -2 denotes method on component + unlockComponentList(); + +#ifdef TORQUE_DEBUG + // Inject Method. + injectMethodCall( fname ); +#endif + return true; + } + } + unlockComponentList(); + + return false; +} + +const char *DynamicConsoleMethodComponent::callMethod( S32 argc, const char* methodName, ... ) +{ + const char *argv[128]; + methodName = StringTable->insert( methodName ); + + argc++; + + va_list args; + va_start(args, methodName); + for(S32 i = 0; i < argc; i++) + argv[i+2] = va_arg(args, const char *); + va_end(args); + + // FIXME: the following seems a little excessive. I wonder why it's needed? + argv[0] = methodName; + argv[1] = methodName; + argv[2] = methodName; + + return callMethodArgList( argc , argv ); +} + +#ifdef TORQUE_DEBUG +/// Inject Method Call. +void DynamicConsoleMethodComponent::injectMethodCall( const char* method ) +{ + // Get Call Method. + StringTableEntry callMethod = StringTable->insert( method ); + + // Find Call Method Metric. + callMethodMetricType::Iterator itr = mCallMethodMetrics.find( callMethod ); + + // Did we find the method? + if ( itr == mCallMethodMetrics.end() ) + { + // No, so set the call count to initially be 1. + itr = mCallMethodMetrics.insert( callMethod, 1 ); + } + else + { + // Increment Call Count. + itr->value++; + } +} +#endif + +const char* DynamicConsoleMethodComponent::callMethodArgList( U32 argc, const char *argv[], bool callThis /* = true */ ) +{ +#ifdef TORQUE_DEBUG + injectMethodCall( argv[0] ); +#endif + + return _callMethod( argc, argv, callThis ); +} + +// Call all components that implement methodName giving them a chance to operate +// Components are called in reverse order of addition +const char *DynamicConsoleMethodComponent::_callMethod( U32 argc, const char *argv[], bool callThis /* = true */ ) +{ + // Set Owner + SimObject *pThis = dynamic_cast( this ); + AssertFatal( pThis, "DynamicConsoleMethodComponent::callMethod : this should always exist!" ); + + const char *cbName = StringTable->insert(argv[0]); + + if( getComponentCount() > 0 ) + { + lockComponentList(); + for( int i = getComponentCount() - 1; i >= 0; i-- ) + //for( SimComponentIterator nItr = componentList.end(); nItr != componentList.begin(); nItr-- ) + { + argv[0] = cbName; + + SimComponent *pComponent = dynamic_cast( getComponent( i ) ); + AssertFatal( pComponent, "DynamicConsoleMethodComponent::callMethod - NULL component in list!" ); + + DynamicConsoleMethodComponent *pThisComponent = dynamic_cast( pComponent ); + AssertFatal( pThisComponent, "DynamicConsoleMethodComponent::callMethod - Non DynamicConsoleMethodComponent component attempting to callback!"); + + // Only call on first depth components + // Should isMethod check these calls? [11/22/2006 justind] + if(pComponent->isEnabled()) + Con::execute( pThisComponent, argc, argv ); + + // Bail if this was the first element + //if( nItr == componentList.begin() ) + // break; + } + unlockComponentList(); + } + + // Set Owner Field + const char* result = ""; + if(callThis) + result = Con::execute( pThis, argc, argv, true ); // true - exec method onThisOnly, not on DCMCs + + return result; +} + +ConsoleMethod( DynamicConsoleMethodComponent, callMethod, void, 3, 64 , "(methodName, argi) Calls script defined method\n" + "@param methodName The method's name as a string\n" + "@param argi Any arguments to pass to the method\n" + "@return No return value" + "@note %obj.callMethod( %methodName, %arg1, %arg2, ... );\n") + +{ + object->callMethodArgList( argc - 1, argv + 2 ); +} + +////////////////////////////////////////////////////////////////////////// + diff --git a/component/dynamicConsoleMethodComponent.h b/component/dynamicConsoleMethodComponent.h new file mode 100644 index 0000000..f85a866 --- /dev/null +++ b/component/dynamicConsoleMethodComponent.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DYNAMIC_CONSOLEMETHOD_COMPONENT_H_ +#define _DYNAMIC_CONSOLEMETHOD_COMPONENT_H_ + +#ifndef _SIMCOMPONENT_H_ +#include "component/simComponent.h" +#endif + +#ifndef _CONSOLEINTERNAL_H_ +#include "console/consoleInternal.h" +#endif + +#ifndef _ICALLMETHOD_H_ +#include "console/ICallMethod.h" +#endif + +#ifdef TORQUE_DEBUG +#ifndef CORE_TDICTIONARY_H +#include "core/util/tDictionary.h" +#endif +#endif + +//----------------------------------------------------------------------------- + +class DynamicConsoleMethodComponent : public SimComponent, public ICallMethod +{ +#ifdef TORQUE_DEBUG +public: + typedef Map callMethodMetricType; +#endif + +private: + typedef SimComponent Parent; + +#ifdef TORQUE_DEBUG + // Call Method Debug Stat. + callMethodMetricType mCallMethodMetrics; +#endif + +protected: + /// Internal callMethod : Actually does component notification and script method execution + /// @attention This method does some magic to the argc argv to make Con::execute act properly + /// as such it's internal and should not be exposed or used except by this class + virtual const char* _callMethod( U32 argc, const char *argv[], bool callThis = true ); + +public: + +#ifdef TORQUE_DEBUG + /// Call Method Metrics. + const callMethodMetricType& getCallMethodMetrics( void ) const { return mCallMethodMetrics; }; + + /// Inject Method Call. + void injectMethodCall( const char* method ); +#endif + + /// Call Method + virtual const char* callMethodArgList( U32 argc, const char *argv[], bool callThis = true ); + + /// Call Method format string + const char* callMethod( S32 argc, const char* methodName, ... ); + + // query for console method data + virtual bool handlesConsoleMethod(const char * fname, S32 * routingId); + + DECLARE_CONOBJECT(DynamicConsoleMethodComponent); +}; + +#endif \ No newline at end of file diff --git a/component/interfaces/IProcessInput.h b/component/interfaces/IProcessInput.h new file mode 100644 index 0000000..70442d0 --- /dev/null +++ b/component/interfaces/IProcessInput.h @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _I_PROCESSINPUT_H_ +#define _I_PROCESSINPUT_H_ + +#include "platform/event.h" + + +// CodeReview : Input can come from a number of places to end up in +// torque, but in the torque world we don't want to expose this +// information to the user. This interface bridges the gap between +// other input devices working details and input as torque understands it. +// Thoughts? [7/6/2007 justind] +class IProcessInput +{ +public: + virtual bool processInputEvent( InputEventInfo &inputEvent ) = 0; +}; + + + + + +#endif \ No newline at end of file diff --git a/component/moreAdvancedComponent.cpp b/component/moreAdvancedComponent.cpp new file mode 100644 index 0000000..f5b4c52 --- /dev/null +++ b/component/moreAdvancedComponent.cpp @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "component/moreAdvancedComponent.h" +#include "unit/test.h" + +// unitTest_runTests("Component/MoreAdvancedComponent"); + +////////////////////////////////////////////////////////////////////////// + +IMPLEMENT_CONOBJECT(MoreAdvancedComponent); + +bool MoreAdvancedComponent::onComponentRegister( SimComponent *owner ) +{ + if( !Parent::onComponentRegister( owner ) ) + return false; + + // This will return the first interface of type SimpleComponent that is cached + // on the parent object. + mSCInterface = owner->getInterface(); + + // If we can't find this interface, our component can't function, so false + // will be returned, and this will signify, to the top-level component, that it + // should fail the onAdd call. + return ( mSCInterface != NULL ); +} + +bool MoreAdvancedComponent::testDependentInterface() +{ + // These two requirements must be met in order for the test to proceed, so + // lets check them. + if( mSCInterface == NULL || !mSCInterface->isValid() ) + return false; + + return mSCInterface->isFortyTwo( 42 ); +} + +////////////////////////////////////////////////////////////////////////// + +using namespace UnitTesting; + +CreateUnitTest(MoreAdvancedComponentTest, "Component/MoreAdvancedComponent") +{ + void run() + { + // Create component instances and compose them. + SimComponent *parentComponent = new SimComponent(); + SimpleComponent *simpleComponent = new SimpleComponent(); + MoreAdvancedComponent *moreAdvComponent = new MoreAdvancedComponent(); + // CodeReview note that the interface pointer isn't initialized in a ctor + // on the components, so it's bad memory against which you might + // be checking in testDependentInterface [3/3/2007 justind] + parentComponent->addComponent( simpleComponent ); + parentComponent->addComponent( moreAdvComponent ); + + simpleComponent->registerObject(); + moreAdvComponent->registerObject(); + + // Put a break-point here, follow the onAdd call, and observe the order in + // which the SimComponent::onAdd function executes. You will see the interfaces + // get cached, and the dependent interface query being made. + parentComponent->registerObject(); + + // If the MoreAdvancedComponent found an interface, than the parentComponent + // should have returned true, from onAdd, and should therefore be registered + // properly with the Sim + test( parentComponent->isProperlyAdded(), "Parent component not properly added!" ); + + // Now lets test the interface. You can step through this, as well. + test( moreAdvComponent->testDependentInterface(), "Dependent interface test failed." ); + + // CodeReview is there a reason we can't just delete the parentComponent here? [3/3/2007 justind] + // + // Clean up + parentComponent->removeComponent( simpleComponent ); + parentComponent->removeComponent( moreAdvComponent ); + + parentComponent->deleteObject(); + moreAdvComponent->deleteObject(); + simpleComponent->deleteObject(); + } +}; \ No newline at end of file diff --git a/component/moreAdvancedComponent.h b/component/moreAdvancedComponent.h new file mode 100644 index 0000000..a947194 --- /dev/null +++ b/component/moreAdvancedComponent.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MOREADVANCEDCOMPONENT_H_ +#define _MOREADVANCEDCOMPONENT_H_ + +#ifndef _SIMPLECOMPONENT_H_ +#include "component/simpleComponent.h" +#endif + +/// This is a slightly more advanced component which will be used to demonstrate +/// components which are dependent on other components. +class MoreAdvancedComponent : public SimComponent +{ + typedef SimComponent Parent; + +protected: + // This component is going to be dependent on a SimpleComponentInterface being + // queried off of it's parent object. This will store that interface that + // will get queried during onComponentRegister() + SimpleComponentInterface *mSCInterface; + +public: + DECLARE_CONOBJECT(MoreAdvancedComponent); + + // Firstly, take a look at the documentation for this function in simComponent.h. + // We will be overloading this method to query the component heirarchy for our + // dependent interface, as noted above. + virtual bool onComponentRegister( SimComponent *owner ); + + // This function will try to execute a function through the interface that this + // component is dependent on. + virtual bool testDependentInterface(); +}; + +#endif \ No newline at end of file diff --git a/component/simComponent.cpp b/component/simComponent.cpp new file mode 100644 index 0000000..2a4675a --- /dev/null +++ b/component/simComponent.cpp @@ -0,0 +1,428 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simObject.h" +#include "console/consoleTypes.h" +#include "component/simComponent.h" +#include "core/stream/stream.h" + +SimComponent::SimComponent() : mOwner( NULL ) +{ + mComponentList.clear(); + mMutex = Mutex::createMutex(); + + mEnabled = true; + mTemplate = false; + + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +SimComponent::~SimComponent() +{ + Mutex::destroyMutex( mMutex ); + mMutex = NULL; +} + +IMPLEMENT_CO_NETOBJECT_V1(SimComponent); + +bool SimComponent::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + // Register + _registerInterfaces( this ); + + if( !_registerComponents( this ) ) + return false; + + //Con::executef( this, 1, "onAdd" ); + + return true; +} + +void SimComponent::_registerInterfaces( SimComponent *owner ) +{ + // First call this to expose the interfaces that this component will cache + // before examining the list of subcomponents + registerInterfaces( owner ); + + // Early out to avoid mutex lock and such + if( !hasComponents() ) + return; + + VectorPtr &components = lockComponentList(); + for( SimComponentIterator i = components.begin(); i != components.end(); i++ ) + { + (*i)->mOwner = owner; + + // Tell the component itself to register it's interfaces + (*i)->registerInterfaces( owner ); + + (*i)->mOwner = NULL; // This tests to see if the object's onComponentRegister call will call up to the parent. + + // Recurse + (*i)->_registerInterfaces( owner ); + } + + unlockComponentList(); +} + +bool SimComponent::_registerComponents( SimComponent *owner ) +{ + // This method will return true if the object contains no components. See the + // documentation for SimComponent::onComponentRegister for more information + // on this behavior. + bool ret = true; + + // If this doesn't contain components, don't even lock the list. + if( hasComponents() ) + { + VectorPtr &components = lockComponentList(); + for( SimComponentIterator i = components.begin(); i != components.end(); i++ ) + { + if( !(*i)->onComponentRegister( owner ) ) + { + ret = false; + break; + } + + AssertFatal( (*i)->mOwner == owner, "Component failed to call parent onComponentRegister!" ); + + // Recurse + if( !(*i)->_registerComponents( owner ) ) + { + ret = false; + break; + } + } + + unlockComponentList(); + } + + return ret; +} + +void SimComponent::_unregisterComponents() +{ + if( !hasComponents() ) + return; + + VectorPtr &components = lockComponentList(); + for( SimComponentIterator i = components.begin(); i != components.end(); i++ ) + { + (*i)->onComponentUnRegister(); + + AssertFatal( (*i)->mOwner == NULL, "Component failed to call parent onUnRegister" ); + + // Recurse + (*i)->_unregisterComponents(); + } + + unlockComponentList(); +} + +void SimComponent::onRemove() +{ + //Con::executef(this, 1, "onRemove"); + + _unregisterComponents(); + + // Delete all components + VectorPtr&componentList = lockComponentList(); + while(componentList.size() > 0) + { + SimComponent *c = componentList[0]; + componentList.erase( componentList.begin() ); + + if( c->isProperlyAdded() ) + c->deleteObject(); + else if( !c->isRemoved() && !c->isDeleted() ) + delete c; + // else, something else is deleting this, don't mess with it + } + unlockComponentList(); + + Parent::onRemove(); +} + +////////////////////////////////////////////////////////////////////////// + +bool SimComponent::processArguments(S32 argc, const char **argv) +{ + for(S32 i = 0; i < argc; i++) + { + SimComponent *obj = dynamic_cast (Sim::findObject(argv[i]) ); + if(obj) + addComponent(obj); + else + Con::printf("SimComponent::processArguments - Invalid Component Object \"%s\"", argv[i]); + } + return true; +} + +////////////////////////////////////////////////////////////////////////// + +void SimComponent::initPersistFields() +{ + addGroup("Component"); + addProtectedField( "Template", TypeBool, Offset(mTemplate, SimComponent), &setIsTemplate, &defaultProtectedGetFn, "Places the object in a component set for later use in new levels." ); + endGroup("Component"); + + // Call Parent. + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ + +bool SimComponent::getInterfaces( ComponentInterfaceList *list, const char *type /* = NULL */, const char *name /* = NULL */, + const SimComponent *owner /* = NULL */, bool notOwner /* = false */ ) +{ + AssertFatal( list != NULL, "Passing NULL for a list is not supported functionality for SimComponents." ); + return ( mInterfaceCache.enumerate( list, type, name, owner, notOwner ) > 0 ); +} + +bool SimComponent::registerCachedInterface( const char *type, const char *name, SimComponent *interfaceOwner, ComponentInterface *cinterface ) +{ + if( mInterfaceCache.add( type, name, interfaceOwner, cinterface ) ) + { + cinterface->mOwner = interfaceOwner; + + // Recurse + if( mOwner != NULL ) + return mOwner->registerCachedInterface( type, name, interfaceOwner, cinterface ); + + return true; + } + + // So this is not a good assert, because it will get triggered due to the recursive + // nature of interface caching. I want to keep it here, though, just so nobody + // else thinks, "Oh I'll add an assert here." + // + //AssertFatal( false, avar( "registerCachedInterface failed, probably because interface with type '%s', name '%s' and owner with SimObjectId '%d' already exists", + // type, name, interfaceOwner->getId() ) ); + + return false; +} + +////////////////////////////////////////////////////////////////////////// +// Component Management +////////////////////////////////////////////////////////////////////////// + +bool SimComponent::addComponentFromField( void* obj, const char* data ) +{ + SimComponent *pComponent = dynamic_cast( Sim::findObject( data ) ); + if( pComponent != NULL ) + static_cast(obj)->addComponent( pComponent ); + return false; +} + +// Add Component to this one +bool SimComponent::addComponent( SimComponent *component ) +{ + AssertFatal( dynamic_cast(component), "SimComponent - Cannot add non SimObject derived components!" ); + + MutexHandle mh; + if( mh.lock( mMutex, true ) ) + { + for( SimComponentIterator nItr = mComponentList.begin(); nItr != mComponentList.end(); nItr++ ) + { + SimComponent *pComponent = dynamic_cast(*nItr); + AssertFatal( pComponent, "SimComponent::addComponent - NULL component in list!" ); + if( pComponent == component ) + return true; + } + + if(component->onComponentAdd(this)) + { + component->mOwner = this; + mComponentList.push_back( component ); + return true; + } + } + + return false; +} + +// Remove Component from this one +bool SimComponent::removeComponent( SimComponent *component ) +{ + MutexHandle mh; + if( mh.lock( mMutex, true ) ) + { + for( SimComponentIterator nItr = mComponentList.begin(); nItr != mComponentList.end(); nItr++ ) + { + SimComponent *pComponent = dynamic_cast(*nItr); + AssertFatal( pComponent, "SimComponent::removeComponent - NULL component in list!" ); + if( pComponent == component ) + { + AssertFatal( component->mOwner == this, "Somehow we contain a component who doesn't think we are it's owner." ); + (*nItr)->onComponentRemove(this); + component->mOwner = NULL; + mComponentList.erase( nItr ); + return true; + } + } + } + return false; +} + +////////////////////////////////////////////////////////////////////////// + +bool SimComponent::onComponentAdd(SimComponent *target) +{ + Con::executef(this, "onComponentAdd", Con::getIntArg(target->getId())); + return true; +} + +void SimComponent::onComponentRemove(SimComponent *target) +{ + Con::executef(this, "onComponentRemove", Con::getIntArg(target->getId())); +} + +////////////////////////////////////////////////////////////////////////// + +ComponentInterface *SimComponent::getInterface(const char *type /* = NULL */, const char *name /* = NULL */, + const SimComponent *owner /* = NULL */, bool notOwner /* = false */) +{ + ComponentInterfaceList iLst; + + if( getInterfaces( &iLst, type, name, owner, notOwner ) ) + return iLst[0]; + + return NULL; +} + +////////////////////////////////////////////////////////////////////////// + +bool SimComponent::writeField(StringTableEntry fieldname, const char* value) +{ + if (!Parent::writeField(fieldname, value)) + return false; + + if( fieldname == StringTable->insert("owner") ) + return false; + + return true; +} + +void SimComponent::write(Stream &stream, U32 tabStop, U32 flags /* = 0 */) +{ + MutexHandle handle; + handle.lock(mMutex); // When this goes out of scope, it will unlock it + + // export selected only? + if((flags & SelectedOnly) && !isSelected()) + { + for(U32 i = 0; i < mComponentList.size(); i++) + mComponentList[i]->write(stream, tabStop, flags); + + return; + } + + stream.writeTabs(tabStop); + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); + stream.write(dStrlen(buffer), buffer); + writeFields(stream, tabStop + 1); + + if(mComponentList.size()) + { + stream.write(2, "\r\n"); + + stream.writeTabs(tabStop+1); + stream.writeLine((U8 *)"// Note: This is a list of behaviors, not arbitrary SimObjects as in a SimGroup or SimSet.\r\n"); + + for(U32 i = 0; i < mComponentList.size(); i++) + mComponentList[i]->write(stream, tabStop + 1, flags); + } + + stream.writeTabs(tabStop); + stream.write(4, "};\r\n"); +} + +////////////////////////////////////////////////////////////////////////// +// Console Methods +////////////////////////////////////////////////////////////////////////// + +ConsoleMethod( SimComponent, addComponents, bool, 3, 64, "%obj.addComponents( %compObjName, %compObjName2, ... );\n" + "Adds additional components to current list.\n" + "@param Up to 62 component names\n" + "@return Returns true on success, false otherwise.") +{ + for(S32 i = 2; i < argc; i++) + { + SimComponent *obj = dynamic_cast (Sim::findObject(argv[i]) ); + if(obj) + object->addComponent(obj); + else + Con::printf("SimComponent::addComponents - Invalid Component Object \"%s\"", argv[i]); + } + return true; +} + +ConsoleMethod( SimComponent, removeComponents, bool, 3, 64, "%obj.removeComponents( %compObjName, %compObjName2, ... );\n" + "Removes components by name from current list.\n" + "@param objNamex Up to 62 component names\n" + "@return Returns true on success, false otherwise.") +{ + for(S32 i = 2; i < argc; i++) + { + SimComponent *obj = dynamic_cast (Sim::findObject(argv[i]) ); + if(obj) + object->removeComponent(obj); + else + Con::printf("SimComponent::removeComponents - Invalid Component Object \"%s\"", argv[i]); + } + return true; +} + +ConsoleMethod( SimComponent, getComponentCount, S32, 2, 2, "() Get the current component count\n" + "@return The number of components in the list as an integer") +{ + return object->getComponentCount(); +} + +ConsoleMethod( SimComponent, getComponent, S32, 3, 3, "(idx) Get the component corresponding to the given index.\n" + "@param idx An integer index value corresponding to the desired component.\n" + "@return The id of the component at the given index as an integer") +{ + S32 idx = dAtoi(argv[2]); + if(idx < 0 || idx >= object->getComponentCount()) + { + Con::errorf("SimComponent::getComponent - Invalid index %d", idx); + return 0; + } + + SimComponent *c = object->getComponent(idx); + return c ? c->getId() : 0; +} + +ConsoleMethod(SimComponent, setEnabled, void, 3, 3, "(enabled) Sets or unsets the enabled flag\n" + "@param enabled Boolean value\n" + "@return No return value") +{ + object->setEnabled(dAtob(argv[2])); +} + +ConsoleMethod(SimComponent, isEnabled, bool, 2, 2, "() Check whether SimComponent is currently enabled\n" + "@return true if enabled and false if not") +{ + return object->isEnabled(); +} + +ConsoleMethod(SimComponent, setIsTemplate, void, 3, 3, "(template) Sets or unsets the template flag\n" + "@param template Boolean value\n" + "@return No return value") +{ + object->setIsTemplate(dAtob(argv[2])); +} + +ConsoleMethod(SimComponent, getIsTemplate, bool, 2, 2, "() Check whether SimComponent is currently a template\n" + "@return true if is a template and false if not") +{ + return object->getIsTemplate(); +} \ No newline at end of file diff --git a/component/simComponent.h b/component/simComponent.h new file mode 100644 index 0000000..ce4a541 --- /dev/null +++ b/component/simComponent.h @@ -0,0 +1,232 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMCOMPONENT_H_ +#define _SIMCOMPONENT_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif +#ifndef _NETOBJECT_H_ +#include "sim/netObject.h" +#endif +#ifndef _COMPONENTINTERFACE_H_ +#include "component/componentInterface.h" +#endif +#ifndef _PLATFORM_THREADS_MUTEX_H_ +#include "platform/threads/mutex.h" +#endif +#ifndef _STRINGFUNCTIONS_H_ +#include "core/strings/stringFunctions.h" +#endif + +// Forward refs +class Stream; +class ComponentInterface; +class ComponentInterfaceCache; + +class SimComponent : public NetObject +{ + typedef NetObject Parent; + +private: + VectorPtr mComponentList; ///< The Component List + void *mMutex; ///< Component List Mutex + + SimObjectPtr mOwner; ///< The component which owns this one. + + /// This is called internally to instruct the component to iterate over it's + // list of components and recursively call _registerInterfaces on their lists + // of components. + void _registerInterfaces( SimComponent *owner ); + + bool _registerComponents( SimComponent *owner ); + void _unregisterComponents(); + +protected: + ComponentInterfaceCache mInterfaceCache; ///< Stores the interfaces exposed by this component. + + bool mTemplate; + + // Non-const getOwner for derived classes + SimComponent *_getOwner() { return mOwner; } + + /// Returns a const reference to private mComponentList + typedef VectorPtr::iterator SimComponentIterator; + VectorPtr &lockComponentList() + { + Mutex::lockMutex( mMutex ); + return mComponentList; + }; + + void unlockComponentList() + { + Mutex::unlockMutex( mMutex ); + } + + /// onComponentRegister is called on each component by it's owner. If a component + /// has no owner, onComponentRegister will not be called on it. The purpose + /// of onComponentRegister is to allow a component to check for any external + /// interfaces, or other dependencies which it needs to function. If any component + /// in a component hierarchy returns false from it's onComponentRegister call + /// the entire hierarchy is invalid, and SimObject::onAdd will fail on the + /// top-level component. To put it another way, if a component contains other + /// components, it will be registered successfully with Sim iff each subcomponent + /// returns true from onComponentRegister. If a component does not contain + /// other components, it will not receive an onComponentRegister call. + /// + /// Overloads of this method must pass the call along to their parent, as is + /// shown in the example below. + /// + /// @code + /// bool FooComponent::onComponentRegister( SimComponent *owner ) + /// { + /// if( !Parent::onComponentRegister( owner ) ) + /// return false; + /// ... + /// } + /// @endcode + virtual bool onComponentRegister( SimComponent *owner ) + { + mOwner = owner; + return true; + } + + /// onUnregister is called when the owner is unregistering. Your object should + /// do cleanup here, as well as pass a call up the chain to the parent. + virtual void onComponentUnRegister() + { + mOwner = NULL; + } + + /// registerInterfaces is called on each component as it's owner is registering + /// it's interfaces. This is called before onComponentRegister, and should be used to + /// register all interfaces exposed by your component, as well as all callbacks + /// needed by your component. + virtual void registerInterfaces( SimComponent *owner ) + { + + } + +public: + DECLARE_CONOBJECT(SimComponent); + + /// Constructor + /// Add this component + SimComponent(); + + /// Destructor + /// Remove this component and destroy child references + virtual ~SimComponent(); + +public: + + virtual bool onAdd(); + virtual void onRemove(); + + static void initPersistFields(); + + virtual bool processArguments(S32 argc, const char **argv); + + /// Will return true if this object contains components. + bool hasComponents() const { return ( mComponentList.size() > 0 ); }; + + /// The component which owns this object + const SimComponent *getOwner() const { return mOwner; }; + + // Component Information + inline virtual StringTableEntry getComponentName() { return StringTable->insert( getClassName() ); }; + + /// Protected 'Component' Field setter that will add a component to the list. + static bool addComponentFromField(void* obj, const char* data); + + /// Add Component to this one + virtual bool addComponent( SimComponent *component ); + + /// Remove Component from this one + virtual bool removeComponent( SimComponent *component ); + + /// Clear Child components of this one + virtual bool clearComponents() { mComponentList.clear(); return true; }; + + virtual bool onComponentAdd(SimComponent *target); + virtual void onComponentRemove(SimComponent *target); + + S32 getComponentCount() { return mComponentList.size(); } + SimComponent *getComponent(S32 idx) { return mComponentList[idx]; } + + SimComponentIterator find(SimComponentIterator first, SimComponentIterator last, SimComponent *value) + { + return ::find(first, last, value); + } + + static bool setIsTemplate( void* obj, const char* data ) { static_cast(obj)->setIsTemplate( dAtob( data ) ); return false; }; + virtual void setIsTemplate( const bool pTemplate ) { mTemplate = pTemplate; } + bool getIsTemplate() const { return mTemplate; } + + virtual void write(Stream &stream, U32 tabStop, U32 flags = 0); + virtual bool writeField(StringTableEntry fieldname, const char* value); + + + /// getInterfaces allows the caller to enumerate the interfaces exposed by + /// this component. This method can be overwritten to expose interfaces + /// which are not cached on the object, before passing the call to the Parent. + /// This can be used delay interface creation until it is queried for, instead + /// of creating it on initialization, and caching it. Returns false if no results + /// were found + /// + /// @param list The list that this method will append search results on to. + /// @param type An expression which the 'type' field on an added object must match to be included in results + /// @param name An expression which the 'name' field on an added object must match to be included in results + /// @param owner Limit results to components owned/not-owned by this SimComponent (see next param) + /// @param notOwner If set to true, this will enumerate only interfaces NOT owned by 'owner' + virtual bool getInterfaces( ComponentInterfaceList *list, const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ); // const omission intentional + + + /// These two methods allow for easy query of component interfaces if you know + /// exactly what you are looking for, and don't mind being passed back the first + /// matching result. + ComponentInterface *getInterface( const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ); + + template + T *getInterface( const char *type = NULL, const char *name = NULL, const SimComponent *owner = NULL, bool notOwner = false ); + + /// Add an interface to the cache. This function will return true if the interface + /// is added successfully. An interface will not be added successfully if an entry + /// in this components cache with the same values for 'type' and 'name' is present. + /// + /// @param type Type of the interface being added. If NULL is passed, it will match any type string queried. + /// @param name Name of interface being added. If NULL is passed, it will match any name string queried. + /// @param interfaceOwner The component which owns the interface being cached + /// @param cinterface The ComponentInterface being cached + bool registerCachedInterface( const char *type, const char *name, SimComponent *interfaceOwner, ComponentInterface *cinterface ); +}; + +////////////////////////////////////////////////////////////////////////// + +template +T *SimComponent::getInterface( const char *type /* = NULL */, const char *name /* = NULL */, + const SimComponent *owner /* = NULL */, bool notOwner /* = false */ ) +{ + ComponentInterfaceList iLst; + + if( getInterfaces( &iLst, type, name, owner, notOwner ) ) + { + ComponentInterfaceListIterator itr = iLst.begin(); + + while( dynamic_cast( *itr ) == NULL ) + itr++; + + if( itr != iLst.end() ) + return static_cast( *itr ); + } + + return NULL; +} + +#endif // _SIMCOMPONENT_H_ diff --git a/component/simpleComponent.cpp b/component/simpleComponent.cpp new file mode 100644 index 0000000..dbdee71 --- /dev/null +++ b/component/simpleComponent.cpp @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "component/simpleComponent.h" + +IMPLEMENT_CONOBJECT(SimpleComponent); + +////////////////////////////////////////////////////////////////////////// +// It may seem like some weak sauce to use a unit test for this, however +// it is very, very easy to set breakpoints in a unit test, and trace +// execution in the debugger, so I will use a unit test. +// +// Note I am not using much actual 'test' functionality, just providing +// an easy place to examine the functionality that was described and implemented +// in the header file. +// +// If you want to run this code, simply run Torque, pull down the console, and +// type: +// unitTest_runTests("Components/SimpleComponent"); + +#include "unit/test.h" +using namespace UnitTesting; + +CreateUnitTest(TestSimpleComponent, "Components/SimpleComponent") +{ + void run() + { + // When instantiating, and working with a SimObject in C++ code, such as + // a unit test, you *may not* allocate a SimObject off of the stack. + // + // For example: + // SimpleComponent sc; + // is a stack allocation. This memory is allocated off of the program stack + // when the function is called. SimObject deletion is done via SimObject::deleteObject() + // and the last command of this method is 'delete this;' That command will + // cause an assert if it is called on stack-allocated memory. Therefor, when + // instantiating SimObjects in C++ code, it is imperitive that you keep in + // mind that if any script calls 'delete()' on that SimObject, or any other + // C++ code calls 'deleteObject()' on that SimObject, it will crash. + SimpleComponent *sc = new SimpleComponent(); + + // SimObject::registerObject must be called on a SimObject before it is + // fully 'hooked in' to the engine. + // + // Tracing execution of this function will let you see onAdd get called on + // the component, and you will see it cache the interface we exposed. + sc->registerObject(); + + // It is *not* required that a component always be owned by a component (obviously) + // however I am using an owner so that you can trace execution of recursive + // calls to cache interfaces and such. + SimComponent *testOwner = new SimComponent(); + + // Add the test component to it's owner. This will set the 'mOwner' field + // of 'sc' to the address of 'testOwner' + testOwner->addComponent( sc ); + + // If you step-into this registerObject the same way as the previous one, + // you will be able to see the recursive caching of the exposed interface. + testOwner->registerObject(); + + // Now to prove that object composition is working properly, lets ask + // both of these components for their interface lists... + + // The ComponentInterfaceList is a typedef for type 'VectorPtr' + // and it will be used by getInterfaces() to store the results of the interface + // query. This is the "complete" way to obtain an interface, and it is too + // heavy-weight for most cases. A simplified query will be performed next, + // to demonstrate the usage of both. + ComponentInterfaceList iLst; + + // This query requests all interfaces, on all components, regardless of name + // or owner. + sc->getInterfaces( &iLst, + // This is the type field. I am passing NULL here to signify that the query + // should match all values of 'type' in the list. + NULL, + + // The name field, let's pass NULL again just so when you trace execution + // you can see how queries work in the simple case, first. + NULL ); + + // Lets process the list that we've gotten back, and find the interface that + // we want. + SimpleComponentInterface *scQueriedInterface = NULL; + + for( ComponentInterfaceListIterator i = iLst.begin(); i != iLst.end(); i++ ) + { + scQueriedInterface = dynamic_cast( *i ); + + if( scQueriedInterface != NULL ) + break; + } + + AssertFatal( scQueriedInterface != NULL, "No valid SimpleComponentInterface was found in query" ); + + // Lets do it again, only we will execute the query on the parent instead, + // in a simplified way. Remember the parent component doesn't expose any + // interfaces at all, so the success of this behavior is entirely dependent + // on the recursive registration that occurs in registerInterfaces() + SimpleComponentInterface *ownerQueriedInterface = testOwner->getInterface(); + + AssertFatal( ownerQueriedInterface != NULL, "No valid SimpleComponentInterface was found in query" ); + + // We should now have two pointers to the same interface obtained by querying + // different components. + test( ownerQueriedInterface == scQueriedInterface, "This really shouldn't be possible to fail given the setup of the test" ); + + // Lets call the method that was exposed on the component via the interface. + // Trace the execution of this function, if you wish. + test( ownerQueriedInterface->isFortyTwo( 42 ), "Don't panic, but it's a bad day in the component system." ); + test( scQueriedInterface->isFortyTwo( 42 ), "Don't panic, but it's a bad day in the component system." ); + + // So there you have it. Writing a simple component that exposes a cached + // interface, and testing it. It's time to clean up. + testOwner->removeComponent( sc ); + + sc->deleteObject(); + testOwner->deleteObject(); + + // Interfaces do not need to be freed. In Juggernaught, these will be ref-counted + // for more robust behavior. Right now, however, the values of our two interface + // pointers, scQueriedInterface and ownerQueriedInterface, reference invalid + // memory. + } +}; \ No newline at end of file diff --git a/component/simpleComponent.h b/component/simpleComponent.h new file mode 100644 index 0000000..73d6014 --- /dev/null +++ b/component/simpleComponent.h @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMPLECOMPONENT_H_ +#define _SIMPLECOMPONENT_H_ + +#ifndef _SIMCOMPONENT_H_ +#include "component/simComponent.h" +#endif + +#ifndef _COMPONENTINTERFACE_H_ +#include "component/componentInterface.h" +#endif + +/// This is a very simple interface. Interfaces provide ways for components to +/// interact with each-other, and query each-other for functionality. It makes it +/// possible for two components to be interdependent on one another, as well. An +/// interface should make accessor calls to it's owner for functionality, and +/// generally be as thin of a layer as possible. +class SimpleComponentInterface : public ComponentInterface +{ +public: + bool isFortyTwo( const U32 test ); +}; + +////////////////////////////////////////////////////////////////////////// +/// The purpose of this component is to provide a minimalistic component that +/// exposes a simple, cached interface +class SimpleComponent : public SimComponent +{ + typedef SimComponent Parent; + +protected: + SimpleComponentInterface mSCInterface; + +public: + // Components are still SimObjects, and need to be declared and implemented + // with the standard macros + DECLARE_CONOBJECT(SimpleComponent); + + ////////////////////////////////////////////////////////////////////////// + // SimComponent overloads. + + // This method is called on each component as it's parent is getting onAdd + // called. The purpose of overloading this method is to expose cached interfaces + // before onComponentRegister is called, so that other components can depend on the + // interfaces you expose in order to register properly. This functionality + // will be demonstrated in a more advanced example. + virtual void registerInterfaces( SimComponent *owner ) + { + // While it is not imperative that we pass this call to the Parent in this + // example, if there existed a class-heirarchy of components, it would be + // critical, so for good practice, call up to the Parent. + Parent::registerInterfaces( owner ); + + // Now we should go ahead and register our cached interface. What we are doing + // is telling the component which contains this component (if it exists) + // all of the interfaces that we expose. When this call is made, it will + // recurse up the owner list. + // + // For example, there exists components A, B, and C. + // A owns B, and B owns C. + // + // If C exposes a cached interface, it will expose it via registerCachedInterface + // when registerInterfaces is recursively called. It will add it's interface to + // it's cache list, and then pass the register call up to it's parent. The parent + // will also cache the interface, and continue to pass the cache call up the + // child->parent chain until there exists no parent. + // + // The result is that, if C exposes an interface 'foo', and A owns B, and + // B owns C, an interface request for 'foo' given to component A will result + // in 'foo' being returned, even though A does not expose 'foo'. This makes + // it possible for a component to query it's owner for an interface, and + // not care where that interface is exposed. It also allows for game code + // to work with any SimComponent and query that component for any interface + // it wants without knowing or caring exactly where it is coming from. + // + // registerCachedInterface returns a boolean value if it was successful. + // Success results in the caching of this interface throughout the full + // child->parent chain. An interface will be added to a cache list + // successfully iff there exists no entry in that list that has matching + // values for 'type', 'name' and 'owner'. + owner->registerCachedInterface( + // The first parameter is the 'type' of the interface, this is not to be + // confused with any kind of existing console, or c++ type. It is simply + // a string which is can be set to any value + "example", + + // The next parameter is the 'name' of the interface. This is also a string + // which can be set to any value + "isfortytwo", + + // The owner of the interface. Note that the value being assigned here + // is this instance of SimpleComponent, and not the 'owner' argument + // of the function registerInterfaces that we are calling from. + this, + + // And finally the interface; a pointer to an object with type ComponentInterface + &mSCInterface ); + } + + ////////////////////////////////////////////////////////////////////////// + // Specific functionality + + /// This is the test method, it will return true if the number provided + /// is forty two + bool isFortyTwo( const U32 test ) const + { + return ( test == 42 ); + } +}; + +////////////////////////////////////////////////////////////////////////// +// Interface implementation +// +// Since interfaces themselves implement very little functionality, it is a good +// idea to inline them if at all possible. Interdependent components will be using +// these interfaces constantly, and so putting as thin of a layer between the +// functionality they expose, and the functionality the component implements is +// a good design practice. +inline bool SimpleComponentInterface::isFortyTwo( const U32 test ) +{ + // This code block will test for a valid owner in a debug build before + // performing operations on it's owner. It is worth noting that the + // ComponentInterface::isValid() method can be overridden to include + // validation specific to your interface and/or component. + AssertFatal( isValid(), "SimpleComponentInterface has not been registered properly by the component which exposes it." ); + + // This is a sanity check. The owner of this interface should have the type + // SimpleComponent, otherwise this won't work. (See further interface examples + // for some ways around this) + AssertFatal( dynamic_cast( getOwner() ) != NULL, "Owner of SimpleComponentInterface is not a SimpleComponent" ); + + // Component interfaces rely on being registered to set their mOwner + // field. This field is initialized to NULL, and then gets set by + // SimComponent when the interface is registered. + return static_cast( getOwner() )->isFortyTwo( test ); +} + +#endif \ No newline at end of file diff --git a/console/CMDgram.y b/console/CMDgram.y new file mode 100644 index 0000000..d8c8102 --- /dev/null +++ b/console/CMDgram.y @@ -0,0 +1,569 @@ +%{ + +// Make sure we don't get gram.h twice. +#define _CMDGRAM_H_ + +#include +#include +#include "console/console.h" +#include "console/compiler.h" +#include "console/consoleInternal.h" + +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif + +#define YYSSIZE 350 + +int outtext(char *fmt, ...); +extern int serrors; + +#define nil 0 +#undef YY_ARGS +#define YY_ARGS(x) x + +int CMDlex(); +void CMDerror(char *, ...); + +#ifdef alloca +#undef alloca +#endif +#define alloca dMalloc + +%} +%{ + /* Reserved Word Definitions */ +%} +%token rwDEFINE rwENDDEF rwDECLARE rwDECLARESINGLETON +%token rwBREAK rwELSE rwCONTINUE rwGLOBAL +%token rwIF rwNIL rwRETURN rwWHILE rwDO +%token rwENDIF rwENDWHILE rwENDFOR rwDEFAULT +%token rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR +%token rwCASEOR rwPACKAGE rwNAMESPACE rwCLASS +%token rwASSERT +%token ILLEGAL_TOKEN +%{ + /* Constants and Identifier Definitions */ +%} +%token CHRCONST +%token INTCONST +%token TTAG +%token VAR +%token IDENT +%token TYPE +%token DOCBLOCK +%token STRATOM +%token TAGATOM +%token FLTCONST + +%{ + /* Operator Definitions */ +%} +%token '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%' +%token '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@' +%token opINTNAME opINTNAMER +%token opMINUSMINUS opPLUSPLUS +%token STMT_SEP +%token opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN +%token opXORASN opORASN opSLASN opSRASN opCAT +%token opEQ opNE opGE opLE opAND opOR opSTREQ +%token opCOLONCOLON + +%union { + char c; + int i; + const char * s; + char * str; + double f; + StmtNode * stmt; + ExprNode * expr; + SlotAssignNode * slist; + VarNode * var; + SlotDecl slot; + InternalSlotDecl intslot; + ObjectBlockDecl odcl; + ObjectDeclNode * od; + AssignDecl asn; + IfStmtNode * ifnode; +} + +%type parent_block +%type case_block +%type switch_stmt +%type decl +%type decl_list +%type package_decl +%type fn_decl_stmt +%type fn_decl_list +%type statement_list +%type stmt +%type expr_list +%type expr_list_decl +%type aidx_expr +%type funcall_expr +%type assert_expr +%type object_name +%type object_args +%type stmt_expr +%type case_expr +%type class_name_expr +%type if_stmt +%type while_stmt +%type for_stmt +%type stmt_block +%type datablock_decl +%type object_decl +%type object_decl_list +%type object_declare_block +%type expr +%type slot_assign_list +%type slot_assign +%type slot_acc +%type intslot_acc +%type expression_stmt +%type var_list +%type var_list_decl +%type assign_op_struct + +%left '[' +%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '=' +%left '?' ':' +%left opOR +%left opAND +%left '|' +%left '^' +%left '&' +%left opEQ opNE +%left '<' opLE '>' opGE +%left '@' opCAT opSTREQ opSTRNE +%left opSHL opSHR +%left '+' '-' +%left '*' '/' '%' +%right '!' '~' opPLUSPLUS opMINUSMINUS UNARY +%left '.' +%left opINTNAME opINTNAMER + +%% + +start + : decl_list + { } + ; + +decl_list + : + { $$ = nil; } + | decl_list decl + { if(!gStatementList) { gStatementList = $2; } else { gStatementList->append($2); } } + ; + +decl + : stmt + { $$ = $1; } + | fn_decl_stmt + { $$ = $1; } + | package_decl + { $$ = $1; } + ; + +package_decl + : rwPACKAGE IDENT '{' fn_decl_list '}' ';' + { $$ = $4; for(StmtNode *walk = ($4);walk;walk = walk->getNext() ) walk->setPackage($2); } + ; + +fn_decl_list + : fn_decl_stmt + { $$ = $1; } + | fn_decl_list fn_decl_stmt + { $$ = $1; ($1)->append($2); } + ; + +statement_list + : + { $$ = nil; } + | statement_list stmt + { if(!$1) { $$ = $2; } else { ($1)->append($2); $$ = $1; } } + ; + +stmt + : if_stmt + | while_stmt + | for_stmt + | datablock_decl + | switch_stmt + | rwBREAK ';' + { $$ = BreakStmtNode::alloc(); } + | rwCONTINUE ';' + { $$ = ContinueStmtNode::alloc(); } + | rwRETURN ';' + { $$ = ReturnStmtNode::alloc(NULL); } + | rwRETURN expr ';' + { $$ = ReturnStmtNode::alloc($2); } + | expression_stmt ';' + { $$ = $1; } + | TTAG '=' expr ';' + { $$ = TTagSetStmtNode::alloc($1, $3, NULL); } + | TTAG '=' expr ',' expr ';' + { $$ = TTagSetStmtNode::alloc($1, $3, $5); } + | DOCBLOCK + { $$ = StrConstNode::alloc($1, false, true); } + ; + +fn_decl_stmt + : rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}' + { $$ = FunctionDeclStmtNode::alloc($2, NULL, $4, $7); } + | rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}' + { $$ = FunctionDeclStmtNode::alloc($4, $2, $6, $9); } + ; + +var_list_decl + : + { $$ = NULL; } + | var_list + { $$ = $1; } + ; + +var_list + : VAR + { $$ = VarNode::alloc($1, NULL); } + | var_list ',' VAR + { $$ = $1; ((StmtNode*)($1))->append((StmtNode*)VarNode::alloc($3, NULL)); } + ; + +datablock_decl + : rwDATABLOCK IDENT '(' IDENT parent_block ')' '{' slot_assign_list '}' ';' + { $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $5, $8, NULL, true, false, false); } + ; + +object_decl + : rwDECLARE class_name_expr '(' object_name parent_block object_args ')' '{' object_declare_block '}' + { $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $9.slots, $9.decls, false, false, false); } + | rwDECLARE class_name_expr '(' object_name parent_block object_args ')' + { $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false, false, false); } + | rwDECLARE class_name_expr '(' '[' object_name ']' parent_block object_args ')' '{' object_declare_block '}' + { $$ = ObjectDeclNode::alloc($2, $5, $8, $7, $11.slots, $11.decls, false, true, false); } + | rwDECLARE class_name_expr '(' '[' object_name ']' parent_block object_args ')' + { $$ = ObjectDeclNode::alloc($2, $5, $8, $7, NULL, NULL, false, true, false); } + | rwDECLARESINGLETON class_name_expr '(' object_name parent_block object_args ')' '{' object_declare_block '}' + { $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $9.slots, $9.decls, false, false, true); } + | rwDECLARESINGLETON class_name_expr '(' object_name parent_block object_args ')' + { $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false, false, true); } + ; + +parent_block + : + { $$ = NULL; } + | ':' IDENT + { $$ = $2; } + ; + +object_name + : + { $$ = StrConstNode::alloc("", false); } + | expr + { $$ = $1; } + ; + +object_args + : + { $$ = NULL; } + | ',' expr_list + { $$ = $2; } + ; + +object_declare_block + : + { $$.slots = NULL; $$.decls = NULL; } + | slot_assign_list + { $$.slots = $1; $$.decls = NULL; } + | object_decl_list + { $$.slots = NULL; $$.decls = $1; } + | slot_assign_list object_decl_list + { $$.slots = $1; $$.decls = $2; } + ; + +object_decl_list + : object_decl ';' + { $$ = $1; } + | object_decl_list object_decl ';' + { $1->append($2); $$ = $1; } + ; + +stmt_block + : '{' statement_list '}' + { $$ = $2; } + | stmt + { $$ = $1; } + ; + +switch_stmt + : rwSWITCH '(' expr ')' '{' case_block '}' + { $$ = $6; $6->propagateSwitchExpr($3, false); } + | rwSWITCHSTR '(' expr ')' '{' case_block '}' + { $$ = $6; $6->propagateSwitchExpr($3, true); } + ; + +case_block + : rwCASE case_expr ':' statement_list + { $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); } + | rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list + { $$ = IfStmtNode::alloc($1, $2, $4, $7, false); } + | rwCASE case_expr ':' statement_list case_block + { $$ = IfStmtNode::alloc($1, $2, $4, $5, true); } + ; + +case_expr + : expr + { $$ = $1;} + | case_expr rwCASEOR expr + { ($1)->append($3); $$=$1; } + ; + +if_stmt + : rwIF '(' expr ')' stmt_block + { $$ = IfStmtNode::alloc($1, $3, $5, NULL, false); } + | rwIF '(' expr ')' stmt_block rwELSE stmt_block + { $$ = IfStmtNode::alloc($1, $3, $5, $7, false); } + ; + +while_stmt + : rwWHILE '(' expr ')' stmt_block + { $$ = LoopStmtNode::alloc($1, nil, $3, nil, $5, false); } + | rwDO stmt_block rwWHILE '(' expr ')' + { $$ = LoopStmtNode::alloc($3, nil, $5, nil, $2, true); } + ; + +for_stmt + : rwFOR '(' expr ';' expr ';' expr ')' stmt_block + { $$ = LoopStmtNode::alloc($1, $3, $5, $7, $9, false); } + | rwFOR '(' expr ';' expr ';' ')' stmt_block + { $$ = LoopStmtNode::alloc($1, $3, $5, NULL, $8, false); } + | rwFOR '(' expr ';' ';' expr ')' stmt_block + { $$ = LoopStmtNode::alloc($1, $3, NULL, $6, $8, false); } + | rwFOR '(' expr ';' ';' ')' stmt_block + { $$ = LoopStmtNode::alloc($1, $3, NULL, NULL, $7, false); } + | rwFOR '(' ';' expr ';' expr ')' stmt_block + { $$ = LoopStmtNode::alloc($1, NULL, $4, $6, $8, false); } + | rwFOR '(' ';' expr ';' ')' stmt_block + { $$ = LoopStmtNode::alloc($1, NULL, $4, NULL, $7, false); } + | rwFOR '(' ';' ';' expr ')' stmt_block + { $$ = LoopStmtNode::alloc($1, NULL, NULL, $5, $7, false); } + | rwFOR '(' ';' ';' ')' stmt_block + { $$ = LoopStmtNode::alloc($1, NULL, NULL, NULL, $6, false); } + ; + +expression_stmt + : stmt_expr + { $$ = $1; } + ; + +expr + : stmt_expr + { $$ = $1; } + | '(' expr ')' + { $$ = $2; } + | expr '^' expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr '%' expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr '&' expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr '|' expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr '+' expr + { $$ = FloatBinaryExprNode::alloc($2, $1, $3); } + | expr '-' expr + { $$ = FloatBinaryExprNode::alloc($2, $1, $3); } + | expr '*' expr + { $$ = FloatBinaryExprNode::alloc($2, $1, $3); } + | expr '/' expr + { $$ = FloatBinaryExprNode::alloc($2, $1, $3); } + | '-' expr %prec UNARY + { $$ = FloatUnaryExprNode::alloc($1, $2); } + | '*' expr %prec UNARY + { $$ = TTagDerefNode::alloc($2); } + | TTAG + { $$ = TTagExprNode::alloc($1); } + | expr '?' expr ':' expr + { $$ = ConditionalExprNode::alloc($1, $3, $5); } + | expr '<' expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr '>' expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opGE expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opLE expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opEQ expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opNE expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opOR expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opSHL expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opSHR expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opAND expr + { $$ = IntBinaryExprNode::alloc($2, $1, $3); } + | expr opSTREQ expr + { $$ = StreqExprNode::alloc($1, $3, true); } + | expr opSTRNE expr + { $$ = StreqExprNode::alloc($1, $3, false); } + | expr '@' expr + { $$ = StrcatExprNode::alloc($1, $3, $2); } + | '!' expr + { $$ = IntUnaryExprNode::alloc($1, $2); } + | '~' expr + { $$ = IntUnaryExprNode::alloc($1, $2); } + | TAGATOM + { $$ = StrConstNode::alloc($1, true); } + | FLTCONST + { $$ = FloatNode::alloc($1); } + | INTCONST + { $$ = IntNode::alloc($1); } + | rwBREAK + { $$ = ConstantNode::alloc(StringTable->insert("break")); } + | slot_acc + { $$ = SlotAccessNode::alloc($1.object, $1.array, $1.slotName); } + | intslot_acc + { $$ = InternalSlotAccessNode::alloc($1.object, $1.slotExpr, $1.recurse); } + | IDENT + { $$ = ConstantNode::alloc($1); } + | STRATOM + { $$ = StrConstNode::alloc($1, false); } + | VAR + { $$ = (ExprNode*)VarNode::alloc($1, NULL); } + | VAR '[' aidx_expr ']' + { $$ = (ExprNode*)VarNode::alloc($1, $3); } + ; + +slot_acc + : expr '.' IDENT + { $$.object = $1; $$.slotName = $3; $$.array = NULL; } + | expr '.' IDENT '[' aidx_expr ']' + { $$.object = $1; $$.slotName = $3; $$.array = $5; } + ; + +intslot_acc + : expr opINTNAME class_name_expr + { $$.object = $1; $$.slotExpr = $3; $$.recurse = false; } + | expr opINTNAMER class_name_expr + { $$.object = $1; $$.slotExpr = $3; $$.recurse = true; } + ; + +class_name_expr + : IDENT + { $$ = ConstantNode::alloc($1); } + | '(' expr ')' + { $$ = $2; } + ; + +assign_op_struct + : opPLUSPLUS + { $$.token = '+'; $$.expr = FloatNode::alloc(1); } + | opMINUSMINUS + { $$.token = '-'; $$.expr = FloatNode::alloc(1); } + | opPLASN expr + { $$.token = '+'; $$.expr = $2; } + | opMIASN expr + { $$.token = '-'; $$.expr = $2; } + | opMLASN expr + { $$.token = '*'; $$.expr = $2; } + | opDVASN expr + { $$.token = '/'; $$.expr = $2; } + | opMODASN expr + { $$.token = '%'; $$.expr = $2; } + | opANDASN expr + { $$.token = '&'; $$.expr = $2; } + | opXORASN expr + { $$.token = '^'; $$.expr = $2; } + | opORASN expr + { $$.token = '|'; $$.expr = $2; } + | opSLASN expr + { $$.token = opSHL; $$.expr = $2; } + | opSRASN expr + { $$.token = opSHR; $$.expr = $2; } + ; + +stmt_expr + : funcall_expr + { $$ = $1; } + | assert_expr + { $$ = $1; } + | object_decl + { $$ = $1; } + | VAR '=' expr + { $$ = AssignExprNode::alloc($1, NULL, $3); } + | VAR '[' aidx_expr ']' '=' expr + { $$ = AssignExprNode::alloc($1, $3, $6); } + | VAR assign_op_struct + { $$ = AssignOpExprNode::alloc($1, NULL, $2.expr, $2.token); } + | VAR '[' aidx_expr ']' assign_op_struct + { $$ = AssignOpExprNode::alloc($1, $3, $5.expr, $5.token); } + | slot_acc assign_op_struct + { $$ = SlotAssignOpNode::alloc($1.object, $1.slotName, $1.array, $2.token, $2.expr); } + | slot_acc '=' expr + { $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $3); } + | slot_acc '=' '{' expr_list '}' + { $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $4); } + ; + +funcall_expr + : IDENT '(' expr_list_decl ')' + { $$ = FuncCallExprNode::alloc($1, NULL, $3, false); } + | IDENT opCOLONCOLON IDENT '(' expr_list_decl ')' + { $$ = FuncCallExprNode::alloc($3, $1, $5, false); } + | expr '.' IDENT '(' expr_list_decl ')' + { $1->append($5); $$ = FuncCallExprNode::alloc($3, NULL, $1, true); } + ; + +assert_expr + : rwASSERT '(' expr ')' + { $$ = AssertCallExprNode::alloc( $3, NULL ); } + | rwASSERT '(' expr ',' STRATOM ')' + { $$ = AssertCallExprNode::alloc( $3, $5 ); } + ; + +expr_list_decl + : + { $$ = NULL; } + | expr_list + { $$ = $1; } + ; + +expr_list + : expr + { $$ = $1; } + | expr_list ',' expr + { ($1)->append($3); $$ = $1; } + ; + +slot_assign_list + : slot_assign + { $$ = $1; } + | slot_assign_list slot_assign + { $1->append($2); $$ = $1; } + ; + +slot_assign + : IDENT '=' expr ';' + { $$ = SlotAssignNode::alloc(NULL, NULL, $1, $3); } + | TYPE IDENT '=' expr ';' + { $$ = SlotAssignNode::alloc(NULL, NULL, $2, $4, $1); } + | rwDATABLOCK '=' expr ';' + { $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); } + | IDENT '[' aidx_expr ']' '=' expr ';' + { $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); } + | TYPE IDENT '[' aidx_expr ']' '=' expr ';' + { $$ = SlotAssignNode::alloc(NULL, $4, $2, $7, $1); } + ; + +aidx_expr + : expr + { $$ = $1; } + | aidx_expr ',' expr + { $$ = CommaCatExprNode::alloc($1, $3); } + ; +%% + diff --git a/console/CMDscan.cpp b/console/CMDscan.cpp new file mode 100644 index 0000000..d0347ff --- /dev/null +++ b/console/CMDscan.cpp @@ -0,0 +1,2522 @@ +#define yy_create_buffer CMD_create_buffer +#define yy_delete_buffer CMD_delete_buffer +#define yy_scan_buffer CMD_scan_buffer +#define yy_scan_string CMD_scan_string +#define yy_scan_bytes CMD_scan_bytes +#define yy_flex_debug CMD_flex_debug +#define yy_init_buffer CMD_init_buffer +#define yy_flush_buffer CMD_flush_buffer +#define yy_load_buffer_state CMD_load_buffer_state +#define yy_switch_to_buffer CMD_switch_to_buffer +#define yyin CMDin +#define yyleng CMDleng +#define yylex CMDlex +#define yyout CMDout +#define yyrestart CMDrestart +#define yytext CMDtext +#define yywrap CMDwrap + +#line 20 "CMDscan.cpp" +/* A lexical scanner generated by flex */ + +/* Scanner skeleton version: + * $Header: /home/daffy/u0/vern/flex/RCS/flex.skl,v 2.85 95/04/24 10:48:47 vern Exp $ + */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 + +#include + + +/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */ +#ifdef c_plusplus +#ifndef __cplusplus +#define __cplusplus +#endif +#endif + + +#ifdef __cplusplus + +#include +//nclude + +/* Use prototypes in function declarations. */ +#define YY_USE_PROTOS + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_PROTOS +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef __TURBOC__ + #pragma warn -rch + #pragma warn -use +#include +#include +#define YY_USE_CONST +#define YY_USE_PROTOS +#endif + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + + +#ifdef YY_USE_PROTOS +#define YY_PROTO(proto) proto +#else +#define YY_PROTO(proto) () +#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 + * definitin 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 yyrestart( yyin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#define YY_BUF_SIZE 16384 + +typedef struct yy_buffer_state *YY_BUFFER_STATE; + +extern int yyleng; +extern FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + +/* The funky do-while in the following #define is used to turn the definition + * int a single C statement (which needs a semi-colon terminator). This + * avoids problems with code like: + * + * if ( condition_holds ) + * yyless( 5 ); + * else + * do_something_else(); + * + * Prior to using the do-while the compiler would get upset at the + * "else" because it interpreted the "if" statement as being all + * done when it reached the ';' after the yyless() call. + */ + +/* Return all but the first 'n' matched characters back to the input stream. */ + +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + *yy_cp = yy_hold_char; \ + yy_c_buf_p = yy_cp = yy_bp + n - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, yytext_ptr ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ +typedef unsigned int yy_size_t; + + +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; + + /* 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 yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + }; + +static YY_BUFFER_STATE yy_current_buffer = 0; + +/* 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". + */ +#define YY_CURRENT_BUFFER yy_current_buffer + + +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; + +static int yy_n_chars; /* number of characters read into yy_ch_buf */ + + +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 1; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yyrestart YY_PROTO(( FILE *input_file )); + +void yy_switch_to_buffer YY_PROTO(( YY_BUFFER_STATE new_buffer )); +void yy_load_buffer_state YY_PROTO(( void )); +YY_BUFFER_STATE yy_create_buffer YY_PROTO(( FILE *file, int size )); +void yy_delete_buffer YY_PROTO(( YY_BUFFER_STATE b )); +void yy_init_buffer YY_PROTO(( YY_BUFFER_STATE b, FILE *file )); +void yy_flush_buffer YY_PROTO(( YY_BUFFER_STATE b )); +#define YY_FLUSH_BUFFER yy_flush_buffer( yy_current_buffer ) + +YY_BUFFER_STATE yy_scan_buffer YY_PROTO(( char *base, yy_size_t size )); +YY_BUFFER_STATE yy_scan_string YY_PROTO(( yyconst char *str )); +YY_BUFFER_STATE yy_scan_bytes YY_PROTO(( yyconst char *bytes, int len )); + +static void *yy_flex_alloc YY_PROTO(( yy_size_t )); +static void *yy_flex_realloc YY_PROTO(( void *, yy_size_t )); +static void yy_flex_free YY_PROTO(( void * )); + +#define yy_new_buffer yy_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! yy_current_buffer ) \ + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \ + yy_current_buffer->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! yy_current_buffer ) \ + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \ + yy_current_buffer->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (yy_current_buffer->yy_at_bol) + +typedef unsigned char YY_CHAR; +FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0; +typedef int yy_state_type; +extern char *yytext; +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state YY_PROTO(( void )); +static yy_state_type yy_try_NUL_trans YY_PROTO(( yy_state_type current_state )); +static int yy_get_next_buffer YY_PROTO(( void )); +static void yy_fatal_error YY_PROTO(( yyconst char msg[] )); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yytext_ptr = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yy_c_buf_p = yy_cp; + +#define YY_NUM_RULES 91 +#define YY_END_OF_BUFFER 92 +static yyconst short int yy_accept[226] = + { 0, + 0, 0, 92, 90, 1, 5, 4, 51, 90, 90, + 58, 57, 90, 41, 42, 45, 43, 56, 44, 50, + 46, 87, 87, 52, 53, 47, 61, 48, 38, 36, + 85, 85, 85, 85, 39, 40, 59, 85, 85, 85, + 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 85, 54, 49, 55, 60, 1, 0, 9, 0, 6, + 0, 0, 17, 84, 25, 12, 26, 0, 7, 0, + 23, 16, 21, 15, 22, 31, 37, 3, 24, 0, + 87, 0, 0, 14, 19, 11, 8, 10, 20, 85, + 33, 85, 85, 27, 85, 85, 85, 85, 85, 85, + + 68, 85, 85, 85, 85, 69, 85, 85, 62, 85, + 85, 85, 85, 85, 85, 28, 13, 18, 89, 84, + 0, 32, 3, 3, 88, 0, 88, 86, 29, 30, + 35, 34, 85, 85, 85, 85, 85, 85, 85, 85, + 70, 85, 85, 73, 85, 85, 85, 85, 85, 85, + 89, 0, 2, 2, 85, 85, 76, 85, 85, 85, + 65, 85, 85, 85, 85, 85, 85, 85, 82, 85, + 2, 2, 2, 2, 2, 85, 63, 85, 85, 85, + 83, 85, 85, 85, 85, 85, 85, 67, 0, 2, + 2, 66, 85, 85, 85, 85, 85, 85, 64, 85, + + 78, 0, 2, 2, 85, 85, 79, 85, 85, 80, + 85, 77, 0, 2, 2, 2, 71, 85, 72, 85, + 85, 75, 81, 74, 0 + } ; + +static yyconst int yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 5, 6, 1, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 31, + 33, 33, 33, 33, 33, 34, 33, 35, 33, 36, + 33, 33, 37, 38, 33, 33, 33, 39, 33, 33, + 40, 41, 42, 43, 33, 1, 44, 45, 46, 47, + + 48, 49, 50, 51, 52, 33, 53, 54, 55, 56, + 57, 58, 33, 59, 60, 61, 62, 33, 63, 39, + 33, 33, 64, 65, 66, 67, 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 int yy_meta[68] = + { 0, + 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 4, 4, + 5, 1, 1, 6, 1, 1, 1, 4, 4, 4, + 4, 4, 7, 7, 7, 7, 7, 7, 7, 1, + 1, 1, 1, 4, 4, 4, 4, 4, 4, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 1, 1, 1, 1 + } ; + +static yyconst short int yy_base[241] = + { 0, + 0, 0, 370, 371, 367, 371, 371, 61, 63, 51, + 53, 65, 66, 371, 371, 344, 64, 371, 66, 371, + 68, 76, 80, 346, 371, 56, 342, 59, 371, 371, + 0, 331, 328, 335, 371, 371, 338, 301, 301, 54, + 57, 305, 59, 309, 58, 298, 312, 307, 57, 295, + 302, 371, 70, 371, 371, 350, 327, 371, 81, 371, + 346, 98, 371, 328, 371, 371, 371, 100, 371, 344, + 371, 371, 371, 322, 371, 371, 371, 328, 371, 106, + 110, 117, 0, 371, 321, 371, 371, 371, 320, 0, + 0, 313, 313, 371, 281, 292, 279, 282, 276, 287, + + 0, 275, 280, 274, 276, 0, 276, 267, 0, 283, + 267, 271, 274, 263, 272, 371, 371, 371, 302, 301, + 300, 371, 0, 135, 124, 126, 128, 0, 371, 371, + 0, 0, 272, 275, 270, 256, 272, 271, 266, 253, + 0, 266, 263, 0, 257, 247, 258, 246, 258, 251, + 283, 282, 146, 148, 243, 248, 0, 248, 254, 236, + 0, 249, 235, 235, 250, 230, 196, 196, 0, 191, + 151, 156, 158, 164, 167, 174, 0, 178, 177, 176, + 0, 171, 161, 162, 153, 160, 156, 0, 188, 174, + 176, 0, 142, 145, 139, 142, 153, 145, 0, 120, + + 168, 155, 180, 183, 117, 117, 0, 101, 94, 0, + 77, 371, 0, 185, 187, 192, 0, 79, 0, 75, + 63, 0, 0, 0, 371, 210, 214, 221, 225, 229, + 233, 240, 109, 244, 251, 258, 265, 272, 279, 286 + } ; + +static yyconst short int yy_def[241] = + { 0, + 225, 1, 225, 225, 225, 225, 225, 225, 226, 227, + 227, 225, 228, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 229, 229, 229, 229, 225, 225, 225, 229, 229, 229, + 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, + 229, 225, 225, 225, 225, 225, 225, 225, 226, 225, + 226, 230, 225, 231, 225, 225, 225, 228, 225, 228, + 225, 225, 225, 225, 225, 225, 225, 232, 225, 225, + 225, 225, 233, 225, 225, 225, 225, 225, 225, 229, + 229, 229, 229, 225, 229, 229, 229, 229, 229, 229, + + 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, + 229, 229, 229, 229, 229, 225, 225, 225, 234, 231, + 231, 225, 232, 235, 225, 225, 225, 233, 225, 225, + 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, + 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, + 234, 234, 236, 237, 229, 229, 229, 229, 229, 229, + 229, 229, 229, 229, 229, 229, 229, 229, 229, 229, + 236, 225, 236, 237, 237, 229, 229, 229, 229, 229, + 229, 229, 229, 229, 229, 229, 229, 229, 225, 236, + 237, 229, 229, 229, 229, 229, 229, 229, 229, 229, + + 229, 225, 238, 239, 229, 229, 229, 229, 229, 229, + 229, 225, 240, 236, 237, 237, 229, 229, 229, 229, + 229, 229, 229, 229, 0, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225 + } ; + +static yyconst short int yy_nxt[439] = + { 0, + 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, 31, 31, + 31, 31, 31, 31, 32, 31, 33, 34, 31, 35, + 4, 36, 37, 38, 39, 40, 41, 42, 43, 31, + 31, 44, 31, 31, 31, 45, 46, 47, 48, 49, + 50, 31, 51, 52, 53, 54, 55, 57, 60, 62, + 62, 62, 62, 66, 63, 69, 65, 72, 85, 86, + 77, 74, 88, 89, 58, 78, 60, 73, 67, 75, + 76, 79, 80, 116, 81, 81, 80, 97, 81, 81, + + 99, 107, 103, 61, 100, 108, 70, 82, 112, 69, + 98, 82, 128, 101, 83, 104, 62, 62, 224, 113, + 105, 61, 223, 82, 125, 125, 80, 82, 81, 81, + 126, 222, 126, 221, 117, 127, 127, 154, 154, 220, + 70, 82, 125, 125, 127, 127, 127, 127, 172, 172, + 172, 172, 123, 172, 172, 82, 219, 82, 172, 172, + 172, 172, 218, 173, 217, 175, 172, 172, 173, 172, + 172, 82, 213, 189, 212, 190, 172, 172, 172, 172, + 211, 175, 215, 215, 191, 215, 215, 172, 172, 172, + 172, 203, 210, 204, 172, 172, 209, 203, 208, 207, + + 204, 206, 173, 205, 175, 202, 201, 200, 199, 175, + 59, 198, 59, 59, 59, 59, 59, 64, 197, 64, + 64, 68, 196, 68, 68, 68, 68, 68, 90, 195, + 194, 90, 119, 193, 192, 119, 120, 120, 188, 120, + 123, 187, 123, 123, 123, 123, 123, 151, 151, 186, + 151, 153, 153, 153, 153, 153, 153, 153, 171, 171, + 171, 171, 171, 171, 171, 174, 174, 174, 174, 174, + 174, 174, 214, 214, 214, 214, 214, 214, 214, 216, + 216, 216, 216, 216, 216, 216, 154, 154, 185, 154, + 154, 154, 154, 184, 183, 182, 181, 180, 179, 178, + + 177, 176, 152, 152, 170, 169, 168, 167, 166, 165, + 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, + 121, 121, 152, 150, 149, 148, 147, 146, 145, 144, + 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, + 133, 132, 131, 130, 129, 124, 122, 68, 121, 59, + 118, 56, 115, 114, 111, 110, 109, 106, 102, 96, + 95, 94, 93, 92, 91, 87, 84, 71, 56, 225, + 3, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225 + } ; + +static yyconst short int yy_chk[439] = + { 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, + 1, 1, 1, 1, 1, 1, 1, 8, 9, 10, + 10, 11, 11, 12, 10, 13, 11, 17, 26, 26, + 21, 19, 28, 28, 8, 21, 59, 17, 12, 19, + 19, 21, 22, 53, 22, 22, 23, 40, 23, 23, + + 41, 45, 43, 9, 41, 45, 13, 22, 49, 68, + 40, 23, 233, 41, 22, 43, 62, 62, 221, 49, + 43, 59, 220, 22, 80, 80, 81, 23, 81, 81, + 82, 218, 82, 211, 53, 82, 82, 124, 124, 209, + 68, 81, 125, 125, 126, 126, 127, 127, 153, 153, + 154, 154, 124, 171, 171, 125, 208, 81, 172, 172, + 173, 173, 206, 153, 205, 154, 174, 174, 171, 175, + 175, 125, 202, 172, 201, 173, 190, 190, 191, 191, + 200, 174, 203, 203, 175, 204, 204, 214, 214, 215, + 215, 190, 198, 191, 216, 216, 197, 203, 196, 195, + + 204, 194, 214, 193, 215, 189, 187, 186, 185, 216, + 226, 184, 226, 226, 226, 226, 226, 227, 183, 227, + 227, 228, 182, 228, 228, 228, 228, 228, 229, 180, + 179, 229, 230, 178, 176, 230, 231, 231, 170, 231, + 232, 168, 232, 232, 232, 232, 232, 234, 234, 167, + 234, 235, 235, 235, 235, 235, 235, 235, 236, 236, + 236, 236, 236, 236, 236, 237, 237, 237, 237, 237, + 237, 237, 238, 238, 238, 238, 238, 238, 238, 239, + 239, 239, 239, 239, 239, 239, 240, 240, 166, 240, + 240, 240, 240, 165, 164, 163, 162, 160, 159, 158, + + 156, 155, 152, 151, 150, 149, 148, 147, 146, 145, + 143, 142, 140, 139, 138, 137, 136, 135, 134, 133, + 121, 120, 119, 115, 114, 113, 112, 111, 110, 108, + 107, 105, 104, 103, 102, 100, 99, 98, 97, 96, + 95, 93, 92, 89, 85, 78, 74, 70, 64, 61, + 57, 56, 51, 50, 48, 47, 46, 44, 42, 39, + 38, 37, 34, 33, 32, 27, 24, 16, 5, 3, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, + 225, 225, 225, 225, 225, 225, 225, 225 + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +/* 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 +char *yytext; +#line 1 "CMDscan.l" +#define INITIAL 0 +#line 2 "CMDscan.l" +#define YYLMAX 4096 + +#include +#include "platform/platform.h" +#include "core/stringTable.h" +#include "console/console.h" +#include "console/compiler.h" +#include "console/dynamicTypes.h" + +using namespace Compiler; + +#define YY_NEVER_INTERACTIVE 1 + +// yyunput() is not used and will generate a warning if included +#define YY_NO_UNPUT 1 + +// Some basic parsing primitives... +static int Sc_ScanDocBlock(); +static int Sc_ScanString(int ret); +static int Sc_ScanNum(); +static int Sc_ScanVar(); +static int Sc_ScanHex(); +static int Sc_ScanIdent(); + +// Deal with debuggability of FLEX. +#ifdef TORQUE_DEBUG +#define FLEX_DEBUG 1 +#else +#define FLEX_DEBUG 0 +#endif + +// Install our own input code... +#undef CMDgetc +int CMDgetc(); + +// Hack to make windows lex happy. +#ifndef isatty +inline int isatty(int) { return 0; } +#endif + +// Wrap our getc, so that lex doesn't try to do its own buffering/file IO. +#define YY_INPUT(buf,result,max_size) \ + { \ + int c = '*', n; \ + for ( n = 0; n < max_size && \ + (c = CMDgetc()) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + result = n; \ + } + +// General helper stuff. +static int lineIndex; + +// File state +void CMDSetScanBuffer(const char *sb, const char *fn); +const char * CMDgetFileLine(int &lineNumber); + +// Error reporting +void CMDerror(char * s, ...); + +// Reset the parser. +void CMDrestart(FILE *in); + +#line 620 "CMDscan.cpp" + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap YY_PROTO(( void )); +#else +extern int yywrap YY_PROTO(( void )); +#endif +#endif + +#ifndef YY_NO_UNPUT +static void yyunput YY_PROTO(( int c, char *buf_ptr )); +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy YY_PROTO(( char *, yyconst char *, int )); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput YY_PROTO(( void )); +#else +static int input YY_PROTO(( void )); +#endif +#endif + +#if YY_STACK_USED +static int yy_start_stack_ptr = 0; +static int yy_start_stack_depth = 0; +static int *yy_start_stack = 0; +#ifndef YY_NO_PUSH_STATE +static void yy_push_state YY_PROTO(( int new_state )); +#endif +#ifndef YY_NO_POP_STATE +static void yy_pop_state YY_PROTO(( void )); +#endif +#ifndef YY_NO_TOP_STATE +static int yy_top_state YY_PROTO(( void )); +#endif + +#else +#define YY_NO_PUSH_STATE 1 +#define YY_NO_POP_STATE 1 +#define YY_NO_TOP_STATE 1 +#endif + +#ifdef YY_MALLOC_DECL +YY_MALLOC_DECL +#else +#if __STDC__ +#ifndef __cplusplus +#include +#endif +#else +/* Just try to get by without declaring the routines. This will fail + * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int) + * or sizeof(void*) != sizeof(int). + */ +#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 (void) fwrite( yytext, yyleng, 1, yyout ) +#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->yy_is_interactive ) \ + { \ + int c = '*', n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else if ( ((result = fread( buf, 1, max_size, yyin )) == 0) \ + && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); +#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 + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL int yylex YY_PROTO(( void )) +#endif + +/* Code executed at the beginning of each rule, after yytext and yyleng + * 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 + +YY_DECL + { + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + +#line 84 "CMDscan.l" + + ; +#line 770 "CMDscan.cpp" + + if ( yy_init ) + { + yy_init = 0; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yy_start ) + yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! yy_current_buffer ) + yy_current_buffer = + yy_create_buffer( yyin, YY_BUF_SIZE ); + + yy_load_buffer_state(); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = yy_c_buf_p; + + /* Support of yytext. */ + *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 >= 226 ) + 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] != 371 ); + +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: +YY_RULE_SETUP +#line 86 "CMDscan.l" +{ } + YY_BREAK +case 2: +YY_RULE_SETUP +#line 87 "CMDscan.l" +{ return(Sc_ScanDocBlock()); } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 88 "CMDscan.l" +; + YY_BREAK +case 4: +YY_RULE_SETUP +#line 89 "CMDscan.l" +; + YY_BREAK +case 5: +YY_RULE_SETUP +#line 90 "CMDscan.l" +{lineIndex++;} + YY_BREAK +case 6: +YY_RULE_SETUP +#line 91 "CMDscan.l" +{ return(Sc_ScanString(STRATOM)); } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 92 "CMDscan.l" +{ return(Sc_ScanString(TAGATOM)); } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 93 "CMDscan.l" +return(CMDlval.i = opEQ); + YY_BREAK +case 9: +YY_RULE_SETUP +#line 94 "CMDscan.l" +return(CMDlval.i = opNE); + YY_BREAK +case 10: +YY_RULE_SETUP +#line 95 "CMDscan.l" +return(CMDlval.i = opGE); + YY_BREAK +case 11: +YY_RULE_SETUP +#line 96 "CMDscan.l" +return(CMDlval.i = opLE); + YY_BREAK +case 12: +YY_RULE_SETUP +#line 97 "CMDscan.l" +return(CMDlval.i = opAND); + YY_BREAK +case 13: +YY_RULE_SETUP +#line 98 "CMDscan.l" +return(CMDlval.i = opOR); + YY_BREAK +case 14: +YY_RULE_SETUP +#line 99 "CMDscan.l" +return(CMDlval.i = opCOLONCOLON); + YY_BREAK +case 15: +YY_RULE_SETUP +#line 100 "CMDscan.l" +return(CMDlval.i = opMINUSMINUS); + YY_BREAK +case 16: +YY_RULE_SETUP +#line 101 "CMDscan.l" +return(CMDlval.i = opPLUSPLUS); + YY_BREAK +case 17: +YY_RULE_SETUP +#line 102 "CMDscan.l" +return(CMDlval.i = opSTREQ); + YY_BREAK +case 18: +YY_RULE_SETUP +#line 103 "CMDscan.l" +return(CMDlval.i = opSTRNE); + YY_BREAK +case 19: +YY_RULE_SETUP +#line 104 "CMDscan.l" +return(CMDlval.i = opSHL); + YY_BREAK +case 20: +YY_RULE_SETUP +#line 105 "CMDscan.l" +return(CMDlval.i = opSHR); + YY_BREAK +case 21: +YY_RULE_SETUP +#line 106 "CMDscan.l" +return(CMDlval.i = opPLASN); + YY_BREAK +case 22: +YY_RULE_SETUP +#line 107 "CMDscan.l" +return(CMDlval.i = opMIASN); + YY_BREAK +case 23: +YY_RULE_SETUP +#line 108 "CMDscan.l" +return(CMDlval.i = opMLASN); + YY_BREAK +case 24: +YY_RULE_SETUP +#line 109 "CMDscan.l" +return(CMDlval.i = opDVASN); + YY_BREAK +case 25: +YY_RULE_SETUP +#line 110 "CMDscan.l" +return(CMDlval.i = opMODASN); + YY_BREAK +case 26: +YY_RULE_SETUP +#line 111 "CMDscan.l" +return(CMDlval.i = opANDASN); + YY_BREAK +case 27: +YY_RULE_SETUP +#line 112 "CMDscan.l" +return(CMDlval.i = opXORASN); + YY_BREAK +case 28: +YY_RULE_SETUP +#line 113 "CMDscan.l" +return(CMDlval.i = opORASN); + YY_BREAK +case 29: +YY_RULE_SETUP +#line 114 "CMDscan.l" +return(CMDlval.i = opSLASN); + YY_BREAK +case 30: +YY_RULE_SETUP +#line 115 "CMDscan.l" +return(CMDlval.i = opSRASN); + YY_BREAK +case 31: +YY_RULE_SETUP +#line 116 "CMDscan.l" +return(CMDlval.i = opINTNAME); + YY_BREAK +case 32: +YY_RULE_SETUP +#line 117 "CMDscan.l" +return(CMDlval.i = opINTNAMER); + YY_BREAK +case 33: +YY_RULE_SETUP +#line 118 "CMDscan.l" +{CMDlval.i = '\n'; return '@'; } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 119 "CMDscan.l" +{CMDlval.i = '\t'; return '@'; } + YY_BREAK +case 35: +YY_RULE_SETUP +#line 120 "CMDscan.l" +{CMDlval.i = ' '; return '@'; } + YY_BREAK +case 36: +YY_RULE_SETUP +#line 121 "CMDscan.l" +{CMDlval.i = 0; return '@'; } + YY_BREAK +case 37: +YY_RULE_SETUP +#line 122 "CMDscan.l" +{ /* this comment stops syntax highlighting from getting messed up when editing the lexer in TextPad */ + register int c = 0, l; + for ( ; ; ) + { + l = c; + c = yyinput(); + + // Is this an open comment? + if ( c == EOF ) + { + CMDerror( "unexpected end of file found in comment" ); + break; + } + + // Increment line numbers. + else if ( c == '\n' ) + lineIndex++; + + // Did we find the end of the comment? + else if ( l == '*' && c == '/' ) + break; + } + } + YY_BREAK +case 38: +#line 146 "CMDscan.l" +case 39: +#line 147 "CMDscan.l" +case 40: +#line 148 "CMDscan.l" +case 41: +#line 149 "CMDscan.l" +case 42: +#line 150 "CMDscan.l" +case 43: +#line 151 "CMDscan.l" +case 44: +#line 152 "CMDscan.l" +case 45: +#line 153 "CMDscan.l" +case 46: +#line 154 "CMDscan.l" +case 47: +#line 155 "CMDscan.l" +case 48: +#line 156 "CMDscan.l" +case 49: +#line 157 "CMDscan.l" +case 50: +#line 158 "CMDscan.l" +case 51: +#line 159 "CMDscan.l" +case 52: +#line 160 "CMDscan.l" +case 53: +#line 161 "CMDscan.l" +case 54: +#line 162 "CMDscan.l" +case 55: +#line 163 "CMDscan.l" +case 56: +#line 164 "CMDscan.l" +case 57: +#line 165 "CMDscan.l" +case 58: +#line 166 "CMDscan.l" +case 59: +#line 167 "CMDscan.l" +case 60: +#line 168 "CMDscan.l" +case 61: +YY_RULE_SETUP +#line 168 "CMDscan.l" +{ return(CMDlval.i = CMDtext[0]); } + YY_BREAK +case 62: +YY_RULE_SETUP +#line 169 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwCASEOR); } + YY_BREAK +case 63: +YY_RULE_SETUP +#line 170 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwBREAK); } + YY_BREAK +case 64: +YY_RULE_SETUP +#line 171 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwRETURN); } + YY_BREAK +case 65: +YY_RULE_SETUP +#line 172 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwELSE); } + YY_BREAK +case 66: +YY_RULE_SETUP +#line 173 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwASSERT); } + YY_BREAK +case 67: +YY_RULE_SETUP +#line 174 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwWHILE); } + YY_BREAK +case 68: +YY_RULE_SETUP +#line 175 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwDO); } + YY_BREAK +case 69: +YY_RULE_SETUP +#line 176 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwIF); } + YY_BREAK +case 70: +YY_RULE_SETUP +#line 177 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwFOR); } + YY_BREAK +case 71: +YY_RULE_SETUP +#line 178 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwCONTINUE); } + YY_BREAK +case 72: +YY_RULE_SETUP +#line 179 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwDEFINE); } + YY_BREAK +case 73: +YY_RULE_SETUP +#line 180 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwDECLARE); } + YY_BREAK +case 74: +YY_RULE_SETUP +#line 181 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwDECLARESINGLETON); } + YY_BREAK +case 75: +YY_RULE_SETUP +#line 182 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwDATABLOCK); } + YY_BREAK +case 76: +YY_RULE_SETUP +#line 183 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwCASE); } + YY_BREAK +case 77: +YY_RULE_SETUP +#line 184 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwSWITCHSTR); } + YY_BREAK +case 78: +YY_RULE_SETUP +#line 185 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwSWITCH); } + YY_BREAK +case 79: +YY_RULE_SETUP +#line 186 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwDEFAULT); } + YY_BREAK +case 80: +YY_RULE_SETUP +#line 187 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwPACKAGE); } + YY_BREAK +case 81: +YY_RULE_SETUP +#line 188 "CMDscan.l" +{ CMDlval.i = lineIndex; return(rwNAMESPACE); } + YY_BREAK +case 82: +YY_RULE_SETUP +#line 189 "CMDscan.l" +{ CMDlval.i = 1; return INTCONST; } + YY_BREAK +case 83: +YY_RULE_SETUP +#line 190 "CMDscan.l" +{ CMDlval.i = 0; return INTCONST; } + YY_BREAK +case 84: +YY_RULE_SETUP +#line 191 "CMDscan.l" +return(Sc_ScanVar()); + YY_BREAK +case 85: +YY_RULE_SETUP +#line 192 "CMDscan.l" +{ return Sc_ScanIdent(); } + YY_BREAK +case 86: +YY_RULE_SETUP +#line 193 "CMDscan.l" +return(Sc_ScanHex()); + YY_BREAK +case 87: +YY_RULE_SETUP +#line 194 "CMDscan.l" +{ CMDtext[CMDleng] = 0; CMDlval.i = dAtoi(CMDtext); return INTCONST; } + YY_BREAK +case 88: +YY_RULE_SETUP +#line 195 "CMDscan.l" +return Sc_ScanNum(); + YY_BREAK +case 89: +YY_RULE_SETUP +#line 196 "CMDscan.l" +return(ILLEGAL_TOKEN); + YY_BREAK +case 90: +YY_RULE_SETUP +#line 197 "CMDscan.l" +return(ILLEGAL_TOKEN); + YY_BREAK +case 91: +YY_RULE_SETUP +#line 198 "CMDscan.l" +ECHO; + YY_BREAK +#line 1261 "CMDscan.cpp" +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; + + if ( yy_current_buffer->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 yyin at a new source and called + * yylex(). 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->yy_n_chars; + yy_current_buffer->yy_input_file = yyin; + yy_current_buffer->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->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 ( yywrap() ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, 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->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 yylex */ + + +/* 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() + { + register char *dest = yy_current_buffer->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->yy_ch_buf[yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( yy_current_buffer->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 singled characater, 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->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_n_chars = 0; + + else + { + int num_to_read = + yy_current_buffer->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ +#ifdef YY_USES_REJECT + YY_FATAL_ERROR( +"input buffer overflow, can't enlarge buffer because scanner uses REJECT" ); +#else + + /* 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. */ + yy_flex_realloc( (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->yy_buf_size - + number_to_move - 1; +#endif + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&yy_current_buffer->yy_ch_buf[number_to_move]), + yy_n_chars, num_to_read ); + } + + if ( yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + yy_current_buffer->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + yy_n_chars += number_to_move; + yy_current_buffer->yy_ch_buf[yy_n_chars] = YY_END_OF_BUFFER_CHAR; + yy_current_buffer->yy_ch_buf[yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yytext_ptr = &yy_current_buffer->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() + { + 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 >= 226 ) + 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 ); + */ + +#ifdef YY_USE_PROTOS +static yy_state_type yy_try_NUL_trans( yy_state_type yy_current_state ) +#else +static yy_state_type yy_try_NUL_trans( yy_current_state ) +yy_state_type yy_current_state; +#endif + { + 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 >= 226 ) + 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 == 225); + + return yy_is_jam ? 0 : yy_current_state; + } + + +#ifndef YY_NO_UNPUT +#ifdef YY_USE_PROTOS +static void yyunput( int c, register char *yy_bp ) +#else +static void yyunput( c, yy_bp ) +int c; +register char *yy_bp; +#endif + { + register char *yy_cp = yy_c_buf_p; + + /* undo effects of setting up yytext */ + *yy_cp = yy_hold_char; + + if ( yy_cp < yy_current_buffer->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->yy_ch_buf[ + yy_current_buffer->yy_buf_size + 2]; + register char *source = + &yy_current_buffer->yy_ch_buf[number_to_move]; + + while ( source > yy_current_buffer->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + yy_n_chars = yy_current_buffer->yy_buf_size; + + if ( yy_cp < yy_current_buffer->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; + } +#endif /* ifndef YY_NO_UNPUT */ + + +#ifdef __cplusplus +static int yyinput() +#else +static int input() +#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->yy_ch_buf[yy_n_chars] ) + /* This was really a NUL. */ + *yy_c_buf_p = '\0'; + + else + { /* need more input */ + yytext_ptr = yy_c_buf_p; + ++yy_c_buf_p; + + switch ( yy_get_next_buffer() ) + { + case EOB_ACT_END_OF_FILE: + { + if ( yywrap() ) + { + yy_c_buf_p = + yytext_ptr + YY_MORE_ADJ; + 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 + YY_MORE_ADJ; + break; + + case EOB_ACT_LAST_MATCH: +#ifdef __cplusplus + YY_FATAL_ERROR( + "unexpected last match in yyinput()" ); +#else + YY_FATAL_ERROR( + "unexpected last match in input()" ); +#endif + } + } + } + + c = *(unsigned char *) yy_c_buf_p; /* cast for 8-bit char's */ + *yy_c_buf_p = '\0'; /* preserve yytext */ + yy_hold_char = *++yy_c_buf_p; + + + return c; + } + + +#ifdef YY_USE_PROTOS +void yyrestart( FILE *input_file ) +#else +void yyrestart( input_file ) +FILE *input_file; +#endif + { + if ( ! yy_current_buffer ) + yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); + + yy_init_buffer( yy_current_buffer, input_file ); + yy_load_buffer_state(); + } + + +#ifdef YY_USE_PROTOS +void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer ) +#else +void yy_switch_to_buffer( new_buffer ) +YY_BUFFER_STATE new_buffer; +#endif + { + 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->yy_buf_pos = yy_c_buf_p; + yy_current_buffer->yy_n_chars = yy_n_chars; + } + + yy_current_buffer = new_buffer; + yy_load_buffer_state(); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yy_did_buffer_switch_on_eof = 1; + } + + +#ifdef YY_USE_PROTOS +void yy_load_buffer_state( void ) +#else +void yy_load_buffer_state() +#endif + { + yy_n_chars = yy_current_buffer->yy_n_chars; + yytext_ptr = yy_c_buf_p = yy_current_buffer->yy_buf_pos; + yyin = yy_current_buffer->yy_input_file; + yy_hold_char = *yy_c_buf_p; + } + + +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_create_buffer( FILE *file, int size ) +#else +YY_BUFFER_STATE yy_create_buffer( file, size ) +FILE *file; +int size; +#endif + { + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_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 *) yy_flex_alloc( b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; + } + + +#ifdef YY_USE_PROTOS +void yy_delete_buffer( YY_BUFFER_STATE b ) +#else +void yy_delete_buffer( b ) +YY_BUFFER_STATE b; +#endif + { + if ( ! b ) + return; + + if ( b == yy_current_buffer ) + yy_current_buffer = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yy_flex_free( (void *) b->yy_ch_buf ); + + yy_flex_free( (void *) b ); + } + + +#ifndef YY_ALWAYS_INTERACTIVE +#ifndef YY_NEVER_INTERACTIVE +extern int isatty YY_PROTO(( int )); +#endif +#endif + +#ifdef YY_USE_PROTOS +void yy_init_buffer( YY_BUFFER_STATE b, FILE *file ) +#else +void yy_init_buffer( b, file ) +YY_BUFFER_STATE b; +FILE *file; +#endif + + + { + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + +#if YY_ALWAYS_INTERACTIVE + b->yy_is_interactive = 1; +#else +#if YY_NEVER_INTERACTIVE + b->yy_is_interactive = 0; +#else + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; +#endif +#endif + } + + +#ifdef YY_USE_PROTOS +void yy_flush_buffer( YY_BUFFER_STATE b ) +#else +void yy_flush_buffer( b ) +YY_BUFFER_STATE b; +#endif + + { + 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 ) + yy_load_buffer_state(); + } + + +#ifndef YY_NO_SCAN_BUFFER +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_buffer( char *base, yy_size_t size ) +#else +YY_BUFFER_STATE yy_scan_buffer( base, size ) +char *base; +yy_size_t size; +#endif + { + 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) yy_flex_alloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_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; + + yy_switch_to_buffer( b ); + + return b; + } +#endif + + +#ifndef YY_NO_SCAN_STRING +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_string( yyconst char *str ) +#else +YY_BUFFER_STATE yy_scan_string( str ) +yyconst char *str; +#endif + { + int len; + for ( len = 0; str[len]; ++len ) + ; + + return yy_scan_bytes( str, len ); + } +#endif + + +#ifndef YY_NO_SCAN_BYTES +#ifdef YY_USE_PROTOS +YY_BUFFER_STATE yy_scan_bytes( yyconst char *bytes, int len ) +#else +YY_BUFFER_STATE yy_scan_bytes( bytes, len ) +yyconst char *bytes; +int len; +#endif + { + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = len + 2; + buf = (char *) yy_flex_alloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < len; ++i ) + buf[i] = bytes[i]; + + buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_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; + } +#endif + + +#ifndef YY_NO_PUSH_STATE +#ifdef YY_USE_PROTOS +static void yy_push_state( int new_state ) +#else +static void yy_push_state( new_state ) +int new_state; +#endif + { + if ( yy_start_stack_ptr >= yy_start_stack_depth ) + { + yy_size_t new_size; + + yy_start_stack_depth += YY_START_STACK_INCR; + new_size = yy_start_stack_depth * sizeof( int ); + + if ( ! yy_start_stack ) + yy_start_stack = (int *) yy_flex_alloc( new_size ); + + else + yy_start_stack = (int *) yy_flex_realloc( + (void *) yy_start_stack, new_size ); + + if ( ! yy_start_stack ) + YY_FATAL_ERROR( + "out of memory expanding start-condition stack" ); + } + + yy_start_stack[yy_start_stack_ptr++] = YY_START; + + BEGIN(new_state); + } +#endif + + +#ifndef YY_NO_POP_STATE +static void yy_pop_state() + { + if ( --yy_start_stack_ptr < 0 ) + YY_FATAL_ERROR( "start-condition stack underflow" ); + + BEGIN(yy_start_stack[yy_start_stack_ptr]); + } +#endif + + +#ifndef YY_NO_TOP_STATE +static int yy_top_state() + { + return yy_start_stack[yy_start_stack_ptr - 1]; + } +#endif + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +#ifdef YY_USE_PROTOS +static void yy_fatal_error( yyconst char msg[] ) +#else +static void yy_fatal_error( msg ) +char msg[]; +#endif + { + (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 yytext. */ \ + yytext[yyleng] = yy_hold_char; \ + yy_c_buf_p = yytext + n - YY_MORE_ADJ; \ + yy_hold_char = *yy_c_buf_p; \ + *yy_c_buf_p = '\0'; \ + yyleng = n; \ + } \ + while ( 0 ) + + +/* Internal utility routines. */ + +#ifndef yytext_ptr +#ifdef YY_USE_PROTOS +static void yy_flex_strncpy( char *s1, yyconst char *s2, int n ) +#else +static void yy_flex_strncpy( s1, s2, n ) +char *s1; +yyconst char *s2; +int n; +#endif + { + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; + } +#endif + + +#ifdef YY_USE_PROTOS +static void *yy_flex_alloc( yy_size_t size ) +#else +static void *yy_flex_alloc( size ) +yy_size_t size; +#endif + { + return (void *) malloc( size ); + } + +#ifdef YY_USE_PROTOS +static void *yy_flex_realloc( void *ptr, yy_size_t size ) +#else +static void *yy_flex_realloc( ptr, size ) +void *ptr; +yy_size_t size; +#endif + { + /* 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 ); + } + +#ifdef YY_USE_PROTOS +static void yy_flex_free( void *ptr ) +#else +static void yy_flex_free( ptr ) +void *ptr; +#endif + { + free( ptr ); + } + +#if YY_MAIN +int main() + { + yylex(); + return 0; + } +#endif +#line 198 "CMDscan.l" + + +static const char *scanBuffer; +static const char *fileName; +static int scanIndex; + +const char * CMDGetCurrentFile() +{ + return fileName; +} + +int CMDGetCurrentLine() +{ + return lineIndex; +} +void CMDDestroyBuff() +{ + yy_delete_buffer(yy_current_buffer); +} +extern bool gConsoleSyntaxError; + +void CMDerror(char *format, ...) +{ + Compiler::gSyntaxError = true; + + const int BUFMAX = 1024; + char tempBuf[BUFMAX]; + va_list args; + va_start( args, format ); +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XENON) + _vsnprintf( tempBuf, BUFMAX, format, args ); +#else + vsnprintf( tempBuf, BUFMAX, format, args ); +#endif + + if(fileName) + { + Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - %s", fileName, lineIndex, tempBuf); + +#ifndef NO_ADVANCED_ERROR_REPORT + // dhc - lineIndex is bogus. let's try to add some sanity back in. + int i,j,n; + char c; + int linediv = 1; + // first, walk the buffer, trying to detect line ending type. + // this is imperfect, if inconsistant line endings exist... + for (i=0; iBUFMAX>>2) break; // at least get a little data + n++; i++; + } + // find next lineending + while (n>1) // cap at half-buf-size forward + { + c = scanBuffer[scanIndex+j]; + if (c==0) break; + if ((c=='\r' || c=='\n') && j>BUFMAX>>2) break; // at least get a little data + n++; j++; + } + if (i) i--; // chop off extra linefeed. + if (j) j--; // chop off extra linefeed. + // build our little text block + if (i) dStrncpy(tempBuf,scanBuffer+scanIndex-i,i); + dStrncpy(tempBuf+i,"##", 2); // bracketing. + tempBuf[i+2] = scanBuffer[scanIndex]; // copy the halt character. + dStrncpy(tempBuf+i+3,"##", 2); // bracketing. + if (j) dStrncpy(tempBuf+i+5,scanBuffer+scanIndex+1,j); // +1 to go past current char. + tempBuf[i+j+5] = 0; // null terminate + for(n=0; n>> Advanced script error report. Line %d.", lineIndex); + Con::warnf(ConsoleLogEntry::Script, ">>> Some error context, with ## on sides of error halt:"); + Con::errorf(ConsoleLogEntry::Script, "%s", tempBuf); + Con::warnf(ConsoleLogEntry::Script, ">>> Error report complete.\n"); +#endif + + // Update the script-visible error buffer. + const char *prevStr = Con::getVariable("$ScriptError"); + if (prevStr[0]) + dSprintf(tempBuf, sizeof(tempBuf), "%s\n%s Line: %d - Syntax error.", prevStr, fileName, lineIndex); + else + dSprintf(tempBuf, sizeof(tempBuf), "%s Line: %d - Syntax error.", fileName, lineIndex); + Con::setVariable("$ScriptError", tempBuf); + + // We also need to mark that we came up with a new error. + static S32 sScriptErrorHash=1000; + Con::setIntVariable("$ScriptErrorHash", sScriptErrorHash++); + } + else + Con::errorf(ConsoleLogEntry::Script, tempBuf); +} + +void CMDSetScanBuffer(const char *sb, const char *fn) +{ + scanBuffer = sb; + fileName = fn; + scanIndex = 0; + lineIndex = 1; +} + +int CMDgetc() +{ + int ret = scanBuffer[scanIndex]; + if(ret) + scanIndex++; + else + ret = -1; + return ret; +} + +int CMDwrap() +{ + return 1; +} + +static int Sc_ScanVar() +{ + // Truncate the temp buffer... + CMDtext[CMDleng] = 0; + + // Make it a stringtable string! + CMDlval.s = StringTable->insert(CMDtext); + return(VAR); +} + +static int charConv(int in) +{ + switch(in) + { + case 'r': + return '\r'; + case 'n': + return '\n'; + case 't': + return '\t'; + default: + return in; + } +} + +static int getHexDigit(char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c >= 'A' && c <= 'F') + return c - 'A' + 10; + if(c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; +} + +static int Sc_ScanDocBlock() +{ + S32 len = dStrlen(CMDtext); + char* text = (char *) consoleAlloc(len + 1); + + for( S32 i = 0, j = 0; j <= len; j++ ) + { + if( ( j <= (len - 2) ) && ( CMDtext[j] == '/' ) && ( CMDtext[j + 1] == '/' ) && ( CMDtext[j + 2] == '/' ) ) + { + j += 2; + continue; + } + + if( CMDtext[j] == '\r' ) + continue; + + if( CMDtext[j] == '\n' ) + lineIndex++; + + text[i++] = CMDtext[j]; + } + + CMDlval.str = text; + return(DOCBLOCK); +} + +static int Sc_ScanString(int ret) +{ + CMDtext[CMDleng - 1] = 0; + if(!collapseEscape(CMDtext+1)) + return -1; + CMDlval.str = (char *) consoleAlloc(dStrlen(CMDtext)); + dStrcpy(CMDlval.str, CMDtext + 1); + return(ret); +} + +static int Sc_ScanIdent() +{ + ConsoleBaseType *type; + + CMDtext[CMDleng] = 0; + + if((type = ConsoleBaseType::getTypeByName(CMDtext)) != NULL) + { + /* It's a type */ + CMDlval.i = type->getTypeID(); + return TYPE; + } + + /* It's an identifier */ + CMDlval.s = StringTable->insert(CMDtext); + return IDENT; +} + +void expandEscape(char *dest, const char *src) +{ + U8 c; + while((c = (U8) *src++) != 0) + { + if(c == '\"') + { + *dest++ = '\\'; + *dest++ = '\"'; + } + else if(c == '\\') + { + *dest++ = '\\'; + *dest++ = '\\'; + } + else if(c == '\r') + { + *dest++ = '\\'; + *dest++ = 'r'; + } + else if(c == '\n') + { + *dest++ = '\\'; + *dest++ = 'n'; + } + else if(c == '\t') + { + *dest++ = '\\'; + *dest++ = 't'; + } + else if(c == '\'') + { + *dest++ = '\\'; + *dest++ = '\''; + } + else if((c >= 1 && c <= 7) || + (c >= 11 && c <= 12) || + (c >= 14 && c <= 15)) + { + /* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */ + static U8 expandRemap[15] = { 0x0, + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x0, + 0x0, + 0x0, + 0x7, + 0x8, + 0x0, + 0x9 }; + + *dest++ = '\\'; + *dest++ = 'c'; + if(c == 15) + *dest++ = 'r'; + else if(c == 16) + *dest++ = 'p'; + else if(c == 17) + *dest++ = 'o'; + else + *dest++ = expandRemap[c] + '0'; + } + else if(c < 32) + { + *dest++ = '\\'; + *dest++ = 'x'; + S32 dig1 = c >> 4; + S32 dig2 = c & 0xf; + if(dig1 < 10) + dig1 += '0'; + else + dig1 += 'A' - 10; + if(dig2 < 10) + dig2 += '0'; + else + dig2 += 'A' - 10; + *dest++ = dig1; + *dest++ = dig2; + } + else + *dest++ = c; + } + *dest = '\0'; +} + +bool collapseEscape(char *buf) +{ + S32 len = dStrlen(buf) + 1; + for(S32 i = 0; i < len;) + { + if(buf[i] == '\\') + { + if(buf[i+1] == 'x') + { + S32 dig1 = getHexDigit(buf[i+2]); + if(dig1 == -1) + return false; + + S32 dig2 = getHexDigit(buf[i+3]); + if(dig2 == -1) + return false; + buf[i] = dig1 * 16 + dig2; + dMemmove(buf + i + 1, buf + i + 4, len - i - 3); + len -= 3; + i++; + } + else if(buf[i+1] == 'c') + { + /* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */ + static U8 collapseRemap[10] = { 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + 0xb, + 0xc, + 0xe }; + + if(buf[i+2] == 'r') + buf[i] = 15; + else if(buf[i+2] == 'p') + buf[i] = 16; + else if(buf[i+2] == 'o') + buf[i] = 17; + else + { + int dig1 = buf[i+2] - '0'; + if(dig1 < 0 || dig1 > 9) + return false; + buf[i] = collapseRemap[dig1]; + } + // Make sure we don't put 0x1 at the beginning of the string. + if ((buf[i] == 0x1) && (i == 0)) + { + buf[i] = 0x2; + buf[i+1] = 0x1; + dMemmove(buf + i + 2, buf + i + 3, len - i - 1); + len -= 1; + } + else + { + dMemmove(buf + i + 1, buf + i + 3, len - i - 2); + len -= 2; + } + i++; + } + else + { + buf[i] = charConv(buf[i+1]); + dMemmove(buf + i + 1, buf + i + 2, len - i - 1); + len--; + i++; + } + } + else + i++; + } + return true; +} + +static int Sc_ScanNum() +{ + CMDtext[CMDleng] = 0; + CMDlval.f = dAtof(CMDtext); + return(FLTCONST); +} + +static int Sc_ScanHex() +{ + S32 val = 0; + dSscanf(CMDtext, "%x", &val); + CMDlval.i = val; + return INTCONST; +} + +void CMD_reset() +{ + CMDrestart(NULL); +} diff --git a/console/CMDscan.l b/console/CMDscan.l new file mode 100644 index 0000000..1555642 --- /dev/null +++ b/console/CMDscan.l @@ -0,0 +1,596 @@ +%{ +#define YYLMAX 4096 + +#include +#include "platform/platform.h" +#include "core/stringTable.h" +#include "console/console.h" +#include "console/compiler.h" +#include "console/dynamicTypes.h" + +using namespace Compiler; + +#define YY_NEVER_INTERACTIVE 1 + +// yyunput() is not used and will generate a warning if included +#define YY_NO_UNPUT 1 + +// Some basic parsing primitives... +static int Sc_ScanDocBlock(); +static int Sc_ScanString(int ret); +static int Sc_ScanNum(); +static int Sc_ScanVar(); +static int Sc_ScanHex(); +static int Sc_ScanIdent(); + +// Deal with debuggability of FLEX. +#ifdef TORQUE_DEBUG +#define FLEX_DEBUG 1 +#else +#define FLEX_DEBUG 0 +#endif + +// Install our own input code... +#undef CMDgetc +int CMDgetc(); + +// Hack to make windows lex happy. +#ifndef isatty +inline int isatty(int) { return 0; } +#endif + +// Wrap our getc, so that lex doesn't try to do its own buffering/file IO. +#define YY_INPUT(buf,result,max_size) \ + { \ + int c = '*', n; \ + for ( n = 0; n < max_size && \ + (c = CMDgetc()) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + result = n; \ + } + +// General helper stuff. +static int lineIndex; + +// File state +void CMDSetScanBuffer(const char *sb, const char *fn); +const char * CMDgetFileLine(int &lineNumber); + +// Error reporting +void CMDerror(char * s, ...); + +// Reset the parser. +void CMDrestart(FILE *in); + +%} + +DIGIT [0-9] +INTEGER {DIGIT}+ +FLOAT ({INTEGER}\.{INTEGER})|({INTEGER}(\.{INTEGER})?[eE][+-]?{INTEGER}) +LETTER [A-Za-z_] +FILECHAR [A-Za-z_\.] +VARMID [:A-Za-z0-9_] +IDTAIL [A-Za-z0-9_] +VARTAIL {VARMID}*{IDTAIL} +VAR [$%]{LETTER}{VARTAIL}* +ID {LETTER}{IDTAIL}* +ILID [$%]{DIGIT}+{LETTER}{VARTAIL}* +FILENAME {FILECHAR}+ +SPACE [ \t\v\f] +HEXDIGIT [a-fA-F0-9] + +%% + ; +{SPACE}+ { } +("///"[^/][^\n\r]*[\n\r]*)+ { return(Sc_ScanDocBlock()); } +"//"[^\n\r]* ; +[\r] ; +[\n] {lineIndex++;} +\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); } +\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); } +"==" return(CMDlval.i = opEQ); +"!=" return(CMDlval.i = opNE); +">=" return(CMDlval.i = opGE); +"<=" return(CMDlval.i = opLE); +"&&" return(CMDlval.i = opAND); +"||" return(CMDlval.i = opOR); +"::" return(CMDlval.i = opCOLONCOLON); +"--" return(CMDlval.i = opMINUSMINUS); +"++" return(CMDlval.i = opPLUSPLUS); +"$=" return(CMDlval.i = opSTREQ); +"!$=" return(CMDlval.i = opSTRNE); +"<<" return(CMDlval.i = opSHL); +">>" return(CMDlval.i = opSHR); +"+=" return(CMDlval.i = opPLASN); +"-=" return(CMDlval.i = opMIASN); +"*=" return(CMDlval.i = opMLASN); +"/=" return(CMDlval.i = opDVASN); +"%=" return(CMDlval.i = opMODASN); +"&=" return(CMDlval.i = opANDASN); +"^=" return(CMDlval.i = opXORASN); +"|=" return(CMDlval.i = opORASN); +"<<=" return(CMDlval.i = opSLASN); +">>=" return(CMDlval.i = opSRASN); +"->" return(CMDlval.i = opINTNAME); +"-->" return(CMDlval.i = opINTNAMER); +"NL" {CMDlval.i = '\n'; return '@'; } +"TAB" {CMDlval.i = '\t'; return '@'; } +"SPC" {CMDlval.i = ' '; return '@'; } +"@" {CMDlval.i = 0; return '@'; } +"/*" { /* this comment stops syntax highlighting from getting messed up when editing the lexer in TextPad */ + register int c = 0, l; + for ( ; ; ) + { + l = c; + c = yyinput(); + + // Is this an open comment? + if ( c == EOF ) + { + CMDerror( "unexpected end of file found in comment" ); + break; + } + + // Increment line numbers. + else if ( c == '\n' ) + lineIndex++; + + // Did we find the end of the comment? + else if ( l == '*' && c == '/' ) + break; + } + } +"?" | +"[" | +"]" | +"(" | +")" | +"+" | +"-" | +"*" | +"/" | +"<" | +">" | +"|" | +"." | +"!" | +":" | +";" | +"{" | +"}" | +"," | +"&" | +"%" | +"^" | +"~" | +"=" { return(CMDlval.i = CMDtext[0]); } +"or" { CMDlval.i = lineIndex; return(rwCASEOR); } +"break" { CMDlval.i = lineIndex; return(rwBREAK); } +"return" { CMDlval.i = lineIndex; return(rwRETURN); } +"else" { CMDlval.i = lineIndex; return(rwELSE); } +"assert" { CMDlval.i = lineIndex; return(rwASSERT); } +"while" { CMDlval.i = lineIndex; return(rwWHILE); } +"do" { CMDlval.i = lineIndex; return(rwDO); } +"if" { CMDlval.i = lineIndex; return(rwIF); } +"for" { CMDlval.i = lineIndex; return(rwFOR); } +"continue" { CMDlval.i = lineIndex; return(rwCONTINUE); } +"function" { CMDlval.i = lineIndex; return(rwDEFINE); } +"new" { CMDlval.i = lineIndex; return(rwDECLARE); } +"singleton" { CMDlval.i = lineIndex; return(rwDECLARESINGLETON); } +"datablock" { CMDlval.i = lineIndex; return(rwDATABLOCK); } +"case" { CMDlval.i = lineIndex; return(rwCASE); } +"switch$" { CMDlval.i = lineIndex; return(rwSWITCHSTR); } +"switch" { CMDlval.i = lineIndex; return(rwSWITCH); } +"default" { CMDlval.i = lineIndex; return(rwDEFAULT); } +"package" { CMDlval.i = lineIndex; return(rwPACKAGE); } +"namespace" { CMDlval.i = lineIndex; return(rwNAMESPACE); } +"true" { CMDlval.i = 1; return INTCONST; } +"false" { CMDlval.i = 0; return INTCONST; } +{VAR} return(Sc_ScanVar()); +{ID} { return Sc_ScanIdent(); } +0[xX]{HEXDIGIT}+ return(Sc_ScanHex()); +{INTEGER} { CMDtext[CMDleng] = 0; CMDlval.i = dAtoi(CMDtext); return INTCONST; } +{FLOAT} return Sc_ScanNum(); +{ILID} return(ILLEGAL_TOKEN); +. return(ILLEGAL_TOKEN); +%% + +static const char *scanBuffer; +static const char *fileName; +static int scanIndex; + +const char * CMDGetCurrentFile() +{ + return fileName; +} + +int CMDGetCurrentLine() +{ + return lineIndex; +} + +extern bool gConsoleSyntaxError; + +void CMDerror(char *format, ...) +{ + Compiler::gSyntaxError = true; + + const int BUFMAX = 1024; + char tempBuf[BUFMAX]; + va_list args; + va_start( args, format ); +#ifdef TORQUE_OS_WIN32 + _vsnprintf( tempBuf, BUFMAX, format, args ); +#else + vsnprintf( tempBuf, BUFMAX, format, args ); +#endif + + if(fileName) + { + Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - %s", fileName, lineIndex, tempBuf); + +#ifndef NO_ADVANCED_ERROR_REPORT + // dhc - lineIndex is bogus. let's try to add some sanity back in. + int i,j,n; + char c; + int linediv = 1; + // first, walk the buffer, trying to detect line ending type. + // this is imperfect, if inconsistant line endings exist... + for (i=0; iBUFMAX>>2) break; // at least get a little data + n++; i++; + } + // find next lineending + while (n>1) // cap at half-buf-size forward + { + c = scanBuffer[scanIndex+j]; + if (c==0) break; + if ((c=='\r' || c=='\n') && j>BUFMAX>>2) break; // at least get a little data + n++; j++; + } + if (i) i--; // chop off extra linefeed. + if (j) j--; // chop off extra linefeed. + // build our little text block + if (i) dStrncpy(tempBuf,scanBuffer+scanIndex-i,i); + dStrncpy(tempBuf+i,"##", 2); // bracketing. + tempBuf[i+2] = scanBuffer[scanIndex]; // copy the halt character. + dStrncpy(tempBuf+i+3,"##", 2); // bracketing. + if (j) dStrncpy(tempBuf+i+5,scanBuffer+scanIndex+1,j); // +1 to go past current char. + tempBuf[i+j+5] = 0; // null terminate + for(n=0; n>> Advanced script error report. Line %d.", lineIndex); + Con::warnf(ConsoleLogEntry::Script, ">>> Some error context, with ## on sides of error halt:"); + Con::errorf(ConsoleLogEntry::Script, "%s", tempBuf); + Con::warnf(ConsoleLogEntry::Script, ">>> Error report complete.\n"); +#endif + + // Update the script-visible error buffer. + const char *prevStr = Con::getVariable("$ScriptError"); + if (prevStr[0]) + dSprintf(tempBuf, sizeof(tempBuf), "%s\n%s Line: %d - Syntax error.", prevStr, fileName, lineIndex); + else + dSprintf(tempBuf, sizeof(tempBuf), "%s Line: %d - Syntax error.", fileName, lineIndex); + Con::setVariable("$ScriptError", tempBuf); + + // We also need to mark that we came up with a new error. + static S32 sScriptErrorHash=1000; + Con::setIntVariable("$ScriptErrorHash", sScriptErrorHash++); + } + else + Con::errorf(ConsoleLogEntry::Script, tempBuf); +} + +void CMDSetScanBuffer(const char *sb, const char *fn) +{ + scanBuffer = sb; + fileName = fn; + scanIndex = 0; + lineIndex = 1; +} + +int CMDgetc() +{ + int ret = scanBuffer[scanIndex]; + if(ret) + scanIndex++; + else + ret = -1; + return ret; +} + +int CMDwrap() +{ + return 1; +} + +static int Sc_ScanVar() +{ + // Truncate the temp buffer... + CMDtext[CMDleng] = 0; + + // Make it a stringtable string! + CMDlval.s = StringTable->insert(CMDtext); + return(VAR); +} + +static int charConv(int in) +{ + switch(in) + { + case 'r': + return '\r'; + case 'n': + return '\n'; + case 't': + return '\t'; + default: + return in; + } +} + +static int getHexDigit(char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + if(c >= 'A' && c <= 'F') + return c - 'A' + 10; + if(c >= 'a' && c <= 'f') + return c - 'a' + 10; + return -1; +} + +static int Sc_ScanDocBlock() +{ + S32 len = dStrlen(CMDtext); + char* text = (char *) consoleAlloc(len + 1); + + for( S32 i = 0, j = 0; j <= len; j++ ) + { + if( ( j <= (len - 2) ) && ( CMDtext[j] == '/' ) && ( CMDtext[j + 1] == '/' ) && ( CMDtext[j + 2] == '/' ) ) + { + j += 2; + continue; + } + + if( CMDtext[j] == '\r' ) + continue; + + if( CMDtext[j] == '\n' ) + lineIndex++; + + text[i++] = CMDtext[j]; + } + + CMDlval.str = text; + return(DOCBLOCK); +} + +static int Sc_ScanString(int ret) +{ + CMDtext[CMDleng - 1] = 0; + if(!collapseEscape(CMDtext+1)) + return -1; + CMDlval.str = (char *) consoleAlloc(dStrlen(CMDtext)); + dStrcpy(CMDlval.str, CMDtext + 1); + return(ret); +} + +static int Sc_ScanIdent() +{ + ConsoleBaseType *type; + + CMDtext[CMDleng] = 0; + + if((type = ConsoleBaseType::getTypeByName(CMDtext)) != NULL) + { + /* It's a type */ + CMDlval.i = type->getTypeID(); + return TYPE; + } + + /* It's an identifier */ + CMDlval.s = StringTable->insert(CMDtext); + return IDENT; +} + +void expandEscape(char *dest, const char *src) +{ + U8 c; + while((c = (U8) *src++) != 0) + { + if(c == '\"') + { + *dest++ = '\\'; + *dest++ = '\"'; + } + else if(c == '\\') + { + *dest++ = '\\'; + *dest++ = '\\'; + } + else if(c == '\r') + { + *dest++ = '\\'; + *dest++ = 'r'; + } + else if(c == '\n') + { + *dest++ = '\\'; + *dest++ = 'n'; + } + else if(c == '\t') + { + *dest++ = '\\'; + *dest++ = 't'; + } + else if(c == '\'') + { + *dest++ = '\\'; + *dest++ = '\''; + } + else if((c >= 1 && c <= 7) || + (c >= 11 && c <= 12) || + (c >= 14 && c <= 15)) + { + /* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */ + static U8 expandRemap[15] = { 0x0, + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x0, + 0x0, + 0x0, + 0x7, + 0x8, + 0x0, + 0x9 }; + + *dest++ = '\\'; + *dest++ = 'c'; + if(c == 15) + *dest++ = 'r'; + else if(c == 16) + *dest++ = 'p'; + else if(c == 17) + *dest++ = 'o'; + else + *dest++ = expandRemap[c] + '0'; + } + else if(c < 32) + { + *dest++ = '\\'; + *dest++ = 'x'; + S32 dig1 = c >> 4; + S32 dig2 = c & 0xf; + if(dig1 < 10) + dig1 += '0'; + else + dig1 += 'A' - 10; + if(dig2 < 10) + dig2 += '0'; + else + dig2 += 'A' - 10; + *dest++ = dig1; + *dest++ = dig2; + } + else + *dest++ = c; + } + *dest = '\0'; +} + +bool collapseEscape(char *buf) +{ + S32 len = dStrlen(buf) + 1; + for(S32 i = 0; i < len;) + { + if(buf[i] == '\\') + { + if(buf[i+1] == 'x') + { + S32 dig1 = getHexDigit(buf[i+2]); + if(dig1 == -1) + return false; + + S32 dig2 = getHexDigit(buf[i+3]); + if(dig2 == -1) + return false; + buf[i] = dig1 * 16 + dig2; + dMemmove(buf + i + 1, buf + i + 4, len - i - 3); + len -= 3; + i++; + } + else if(buf[i+1] == 'c') + { + /* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */ + static U8 collapseRemap[10] = { 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + 0xb, + 0xc, + 0xe }; + + if(buf[i+2] == 'r') + buf[i] = 15; + else if(buf[i+2] == 'p') + buf[i] = 16; + else if(buf[i+2] == 'o') + buf[i] = 17; + else + { + int dig1 = buf[i+2] - '0'; + if(dig1 < 0 || dig1 > 9) + return false; + buf[i] = collapseRemap[dig1]; + } + // Make sure we don't put 0x1 at the beginning of the string. + if ((buf[i] == 0x1) && (i == 0)) + { + buf[i] = 0x2; + buf[i+1] = 0x1; + dMemmove(buf + i + 2, buf + i + 3, len - i - 1); + len -= 1; + } + else + { + dMemmove(buf + i + 1, buf + i + 3, len - i - 2); + len -= 2; + } + i++; + } + else + { + buf[i] = charConv(buf[i+1]); + dMemmove(buf + i + 1, buf + i + 2, len - i - 1); + len--; + i++; + } + } + else + i++; + } + return true; +} + +static int Sc_ScanNum() +{ + CMDtext[CMDleng] = 0; + CMDlval.f = dAtof(CMDtext); + return(FLTCONST); +} + +static int Sc_ScanHex() +{ + S32 val = 0; + dSscanf(CMDtext, "%x", &val); + CMDlval.i = val; + return INTCONST; +} + +void CMD_reset() +{ + CMDrestart(NULL); +} diff --git a/console/ICallMethod.h b/console/ICallMethod.h new file mode 100644 index 0000000..5eff19b --- /dev/null +++ b/console/ICallMethod.h @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ICALLMETHOD_H_ +#define _ICALLMETHOD_H_ + +class ICallMethod +{ +public: + virtual const char* callMethod( S32 argc, const char* methodName, ... ) = 0; + virtual const char* callMethodArgList( U32 argc, const char *argv[], bool callThis = true ) = 0; +}; + +#endif \ No newline at end of file diff --git a/console/SimXMLDocument.cpp b/console/SimXMLDocument.cpp new file mode 100644 index 0000000..012f115 --- /dev/null +++ b/console/SimXMLDocument.cpp @@ -0,0 +1,1012 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "tinyxml/tinyxml.h" + +//----------------------------------------------------------------------------- +// Console implementation of STL map. +//----------------------------------------------------------------------------- + +#include "core/frameAllocator.h" +#include "console/simBase.h" +#include "console/consoleInternal.h" +#include "console/SimXMLDocument.h" + +IMPLEMENT_CONOBJECT(SimXMLDocument); + +// ----------------------------------------------------------------------------- +// Constructor. +// ----------------------------------------------------------------------------- +SimXMLDocument::SimXMLDocument(): +m_qDocument(0), +m_CurrentAttribute(0) +{ +} + +// ----------------------------------------------------------------------------- +// Destructor. +// ----------------------------------------------------------------------------- +SimXMLDocument::~SimXMLDocument() +{ +} + +// ----------------------------------------------------------------------------- +// Included for completeness. +// ----------------------------------------------------------------------------- +bool SimXMLDocument::processArguments(S32 argc, const char** argv) +{ + return argc == 0; +} + +// ----------------------------------------------------------------------------- +// Script constructor. +// ----------------------------------------------------------------------------- +bool SimXMLDocument::onAdd() +{ + if(!Parent::onAdd()) + { + return false; + } + + if(!m_qDocument) + { + m_qDocument = new TiXmlDocument(); + } + return true; +} + +// ----------------------------------------------------------------------------- +// Script destructor. +// ----------------------------------------------------------------------------- +void SimXMLDocument::onRemove() +{ + Parent::onRemove(); + if(m_qDocument) + { + m_qDocument->Clear(); + delete(m_qDocument); + } +} + +// ----------------------------------------------------------------------------- +// Initialize peristent fields (datablocks). +// ----------------------------------------------------------------------------- +void SimXMLDocument::initPersistFields() +{ + Parent::initPersistFields(); +} + +// ----------------------------------------------------------------------------- +// Set this to default state at construction. +// ----------------------------------------------------------------------------- +void SimXMLDocument::reset(void) +{ + m_qDocument->Clear(); + m_paNode.clear(); + m_CurrentAttribute = 0; +} +ConsoleMethod(SimXMLDocument, reset, void, 2, 2, + "Set this to default state at construction.") +{ + object->reset(); +} + +// ----------------------------------------------------------------------------- +// Get true if file loads successfully. +// ----------------------------------------------------------------------------- +S32 SimXMLDocument::loadFile(const char* rFileName) +{ + reset(); + return m_qDocument->LoadFile(rFileName); +} +ConsoleMethod(SimXMLDocument, loadFile, S32, 3, 3, "Load file from given filename.") +{ + return object->loadFile( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Get true if file saves successfully. +// ----------------------------------------------------------------------------- +S32 SimXMLDocument::saveFile(const char* rFileName) +{ + return m_qDocument->SaveFile( rFileName ); +} +ConsoleMethod(SimXMLDocument, saveFile, S32, 3, 3, "Save file to given filename.") +{ + return object->saveFile( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Get true if XML text parses correctly. +// ----------------------------------------------------------------------------- +S32 SimXMLDocument::parse(const char* rText) +{ + reset(); + m_qDocument->Parse( rText ); + return 1; +} +ConsoleMethod(SimXMLDocument, parse, S32, 3, 3, "Create document from XML string.") +{ + return object->parse( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Clear contents of XML document. +// ----------------------------------------------------------------------------- +void SimXMLDocument::clear(void) +{ + reset(); +} +ConsoleMethod(SimXMLDocument, clear, void, 2, 2, "Clear contents of XML document.") +{ + object->clear(); +} + +// ----------------------------------------------------------------------------- +// Get error description of XML document. +// ----------------------------------------------------------------------------- +const char* SimXMLDocument::getErrorDesc(void) const +{ + if(!m_qDocument) + { + return StringTable->insert("No document"); + } + return m_qDocument->ErrorDesc(); +} +ConsoleMethod(SimXMLDocument, getErrorDesc, const char*, 2, 2, + "Get current error description.") +{ + // Create Returnable Buffer (duration of error description is unknown). + char* pBuffer = Con::getReturnBuffer(256); + // Format Buffer. + dSprintf(pBuffer, 256, "%s", object->getErrorDesc()); + // Return Velocity. + return pBuffer; +} + +// ----------------------------------------------------------------------------- +// Clear error description of this. +// ----------------------------------------------------------------------------- +void SimXMLDocument::clearError(void) +{ + m_qDocument->ClearError(); +} +ConsoleMethod(SimXMLDocument, clearError, void, 2, 2, + "Clear error description.") +{ + object->clearError(); +} + +// ----------------------------------------------------------------------------- +// Get true if first child element was successfully pushed onto stack. +// ----------------------------------------------------------------------------- +bool SimXMLDocument::pushFirstChildElement(const char* rName) +{ + // Clear the current attribute pointer + m_CurrentAttribute = 0; + + // Push the first element found under the current element of the given name + TiXmlElement* pElement; + if(!m_paNode.empty()) + { + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return false; + } + pElement = pNode->FirstChildElement(rName); + } + else + { + if(!m_qDocument) + { + return false; + } + pElement = m_qDocument->FirstChildElement(rName); + } + + if(!pElement) + { + return false; + } + m_paNode.push_back(pElement); + return true; +} +ConsoleMethod(SimXMLDocument, pushFirstChildElement, bool, 3, 3, + "Push first child element with given name onto stack.") +{ + return object->pushFirstChildElement( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Get true if first child element was successfully pushed onto stack. +// ----------------------------------------------------------------------------- +bool SimXMLDocument::pushChildElement(S32 index) +{ + // Clear the current attribute pointer + m_CurrentAttribute = 0; + + // Push the first element found under the current element of the given name + TiXmlElement* pElement; + if(!m_paNode.empty()) + { + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return false; + } + pElement = pNode->FirstChildElement(); + for( S32 i = 0; i < index; i++ ) + { + if( !pElement ) + return false; + + pElement = pElement->NextSiblingElement(); + } + } + else + { + if(!m_qDocument) + { + return false; + } + pElement = m_qDocument->FirstChildElement(); + for( S32 i = 0; i < index; i++ ) + { + if( !pElement ) + return false; + + pElement = pElement->NextSiblingElement(); + } + } + + if(!pElement) + { + return false; + } + m_paNode.push_back(pElement); + return true; +} +ConsoleMethod(SimXMLDocument, pushChildElement, bool, 3, 3, + "Push the child element at the given index onto stack.") +{ + return object->pushChildElement( dAtoi( argv[2] ) ); +} + +// ----------------------------------------------------------------------------- +// Convert top stack element into its next sibling element. +// ----------------------------------------------------------------------------- +bool SimXMLDocument::nextSiblingElement(const char* rName) +{ + // Clear the current attribute pointer + m_CurrentAttribute = 0; + + // Attempt to find the next sibling element + if(m_paNode.empty()) + { + return false; + } + const int iLastElement = m_paNode.size() - 1; + TiXmlElement*& pElement = m_paNode[iLastElement]; + if(!pElement) + { + return false; + } + + pElement = pElement->NextSiblingElement(rName); + if(!pElement) + { + return false; + } + + return true; +} +ConsoleMethod(SimXMLDocument, nextSiblingElement, bool, 3, 3, + "Set top element on stack to next element with given name.") +{ + return object->nextSiblingElement( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Get element value if it exists. Used to extract a text node from the element. +// for example. +// ----------------------------------------------------------------------------- +const char* SimXMLDocument::elementValue() +{ + if(m_paNode.empty()) + { + return StringTable->insert(""); + } + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return StringTable->insert(""); + } + + return pNode->Value(); +} +ConsoleMethod(SimXMLDocument, elementValue, const char*, 2, 2, + "Get element value if it exists (string).") +{ + // Create Returnable Buffer (because duration of value is unknown). + char* pBuffer = Con::getReturnBuffer(256); + dSprintf(pBuffer, 256, "%s", object->elementValue()); + return pBuffer; +} + +// ----------------------------------------------------------------------------- +// Pop last element off of stack. +// ----------------------------------------------------------------------------- +void SimXMLDocument::popElement(void) +{ + m_paNode.pop_back(); +} +ConsoleMethod(SimXMLDocument, popElement, void, 2, 2, + "Pop last element off of stack.") +{ + object->popElement(); +} + +// ----------------------------------------------------------------------------- +// Get attribute value if it exists. +// ----------------------------------------------------------------------------- +const char* SimXMLDocument::attribute(const char* rAttribute) +{ + if(m_paNode.empty()) + { + return StringTable->insert(""); + } + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return StringTable->insert(""); + } + + if(!pNode->Attribute(rAttribute)) + { + return StringTable->insert(""); + } + + return pNode->Attribute(rAttribute); +} +ConsoleMethod(SimXMLDocument, attribute, const char*, 3, 3, + "Get attribute value if it exists (string).") +{ + // Create Returnable Buffer (because duration of attribute is unknown). + char* pBuffer = Con::getReturnBuffer(256); + // Format Buffer. + dSprintf(pBuffer, 256, "%s", object->attribute( argv[2] )); + // Return Velocity. + return pBuffer; +} +ConsoleMethod(SimXMLDocument, attributeF32, F32, 3, 3, + "Get attribute value if it exists (float).") +{ + return dAtof( object->attribute( argv[2] ) ); +} +ConsoleMethod(SimXMLDocument, attributeS32, S32, 3, 3, + "Get attribute value if it exists (int).") +{ + return dAtoi( object->attribute( argv[2] ) ); +} + +// ----------------------------------------------------------------------------- +// Get true if given attribute exists. +// ----------------------------------------------------------------------------- +bool SimXMLDocument::attributeExists(const char* rAttribute) +{ + if(m_paNode.empty()) + { + return false; + } + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return false; + } + + if(!pNode->Attribute(rAttribute)) + { + return false; + } + + return true; +} +ConsoleMethod(SimXMLDocument, attributeExists, bool, 3, 3, + "Get true if named attribute exists.") +{ + return object->attributeExists( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Obtain the name of the current element's first attribute +// ----------------------------------------------------------------------------- +const char* SimXMLDocument::firstAttribute() +{ + // Get the current element + if(m_paNode.empty()) + { + return StringTable->insert(""); + } + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return StringTable->insert(""); + } + + // Gets its first attribute, if any + m_CurrentAttribute = pNode->FirstAttribute(); + if(!m_CurrentAttribute) + { + return StringTable->insert(""); + } + + return m_CurrentAttribute->Name(); +} +ConsoleMethod(SimXMLDocument, firstAttribute, const char*, 2, 2, + "Obtain the name of the current element's first attribute.") +{ + const char* name = object->firstAttribute(); + + // Create Returnable Buffer (because duration of attribute is unknown). + char* pBuffer = Con::getReturnBuffer(dStrlen(name)+1); + dStrcpy(pBuffer, name); + return pBuffer; +} + +// ----------------------------------------------------------------------------- +// Obtain the name of the current element's last attribute +// ----------------------------------------------------------------------------- +const char* SimXMLDocument::lastAttribute() +{ + // Get the current element + if(m_paNode.empty()) + { + return StringTable->insert(""); + } + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return StringTable->insert(""); + } + + // Gets its last attribute, if any + m_CurrentAttribute = pNode->LastAttribute(); + if(!m_CurrentAttribute) + { + return StringTable->insert(""); + } + + return m_CurrentAttribute->Name(); +} +ConsoleMethod(SimXMLDocument, lastAttribute, const char*, 2, 2, + "Obtain the name of the current element's last attribute.") +{ + const char* name = object->lastAttribute(); + + // Create Returnable Buffer (because duration of attribute is unknown). + char* pBuffer = Con::getReturnBuffer(dStrlen(name)+1); + dStrcpy(pBuffer, name); + return pBuffer; +} + +// ----------------------------------------------------------------------------- +// Get the name of the next attribute for the current element after a call +// to firstAttribute(). +// ----------------------------------------------------------------------------- +const char* SimXMLDocument::nextAttribute() +{ + if(!m_CurrentAttribute) + { + return StringTable->insert(""); + } + + // Gets its next attribute, if any + m_CurrentAttribute = m_CurrentAttribute->Next(); + if(!m_CurrentAttribute) + { + return StringTable->insert(""); + } + + return m_CurrentAttribute->Name(); +} +ConsoleMethod(SimXMLDocument, nextAttribute, const char*, 2, 2, + "Get the name of the next attribute for the current element after a call to firstAttribute().") +{ + const char* name = object->nextAttribute(); + + // Create Returnable Buffer (because duration of attribute is unknown). + char* pBuffer = Con::getReturnBuffer(dStrlen(name)+1); + dStrcpy(pBuffer, name); + return pBuffer; +} + +// ----------------------------------------------------------------------------- +// Get the name of the previous attribute for the current element after a call +// to lastAttribute(). +// ----------------------------------------------------------------------------- +const char* SimXMLDocument::prevAttribute() +{ + if(!m_CurrentAttribute) + { + return StringTable->insert(""); + } + + // Gets its next attribute, if any + m_CurrentAttribute = m_CurrentAttribute->Previous(); + if(!m_CurrentAttribute) + { + return StringTable->insert(""); + } + + return m_CurrentAttribute->Name(); +} +ConsoleMethod(SimXMLDocument, prevAttribute, const char*, 2, 2, + "Get the name of the previous attribute for the current element after a call to lastAttribute().") +{ + const char* name = object->prevAttribute(); + + // Create Returnable Buffer (because duration of attribute is unknown). + char* pBuffer = Con::getReturnBuffer(dStrlen(name)+1); + dStrcpy(pBuffer, name); + return pBuffer; +} + +// ----------------------------------------------------------------------------- +// Set attribute of top stack element to given value. +// ----------------------------------------------------------------------------- +void SimXMLDocument::setAttribute(const char* rAttribute, const char* rVal) +{ + if(m_paNode.empty()) + { + return; + } + + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pElement = m_paNode[iLastElement]; + if(!pElement) + { + return; + } + pElement->SetAttribute(rAttribute, rVal); +} +ConsoleMethod(SimXMLDocument, setAttribute, void, 4, 4, + "Set attribute of top stack element to given value.") +{ + object->setAttribute(argv[2], argv[3]); +} + +// ----------------------------------------------------------------------------- +// Set attribute of top stack element to given value. +// ----------------------------------------------------------------------------- +void SimXMLDocument::setObjectAttributes(const char* objectID) +{ + if( !objectID || !objectID[0] ) + return; + + if(m_paNode.empty()) + return; + + SimObject *pObject = Sim::findObject( objectID ); + + if( pObject == NULL ) + return; + + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pElement = m_paNode[iLastElement]; + if(!pElement) + return; + + char textbuf[1024]; + TiXmlElement field( "Field" ); + TiXmlElement group( "FieldGroup" ); + pElement->SetAttribute( "Name", pObject->getName() ); + + + // Iterate over our filed list and add them to the XML document... + AbstractClassRep::FieldList fieldList = pObject->getFieldList(); + AbstractClassRep::FieldList::iterator itr; + for(itr = fieldList.begin(); itr != fieldList.end(); itr++) + { + + if( itr->type == AbstractClassRep::DeprecatedFieldType || + itr->type == AbstractClassRep::StartGroupFieldType || + itr->type == AbstractClassRep::EndGroupFieldType) continue; + + // Not an Array + if(itr->elementCount == 1) + { + // get the value of the field as a string. + ConsoleBaseType *cbt = ConsoleBaseType::getType(itr->type); + + const char *val = Con::getData(itr->type, (void *) (((const char *)pObject) + itr->offset), 0, itr->table, itr->flag); + + // Make a copy for the field check. + if (!val) + continue; + + FrameTemp valCopy( dStrlen( val ) + 1 ); + dStrcpy( (char *)valCopy, val ); + + if (!pObject->writeField(itr->pFieldname, valCopy)) + continue; + + val = valCopy; + + + expandEscape(textbuf, val); + + if( !pObject->writeField( itr->pFieldname, textbuf ) ) + continue; + + field.SetValue( "Property" ); + field.SetAttribute( "name", itr->pFieldname ); + if( cbt != NULL ) + field.SetAttribute( "type", cbt->getTypeName() ); + else + field.SetAttribute( "type", "TypeString" ); + field.SetAttribute( "data", textbuf ); + + pElement->InsertEndChild( field ); + + continue; + } + } + + //// IS An Array + //for(U32 j = 0; S32(j) < f->elementCount; j++) + //{ + + // // If the start of a group create an element for the group and + // // the our chache to it + // const char *val = Con::getData(itr->type, (void *) (((const char *)pObject) + itr->offset), j, itr->table, itr->flag); + + // // Make a copy for the field check. + // if (!val) + // continue; + + // FrameTemp valCopy( dStrlen( val ) + 1 ); + // dStrcpy( (char *)valCopy, val ); + + // if (!pObject->writeField(itr->pFieldname, valCopy)) + // continue; + + // val = valCopy; + + // // get the value of the field as a string. + // ConsoleBaseType *cbt = ConsoleBaseType::getType(itr->type); + // const char * dstr = Con::getData(itr->type, (void *)(((const char *)pObject) + itr->offset), 0, itr->table, itr->flag); + // if(!dstr) + // dstr = ""; + // expandEscape(textbuf, dstr); + + + // if( !pObject->writeField( itr->pFieldname, dstr ) ) + // continue; + + // field.SetValue( "Property" ); + // field.SetAttribute( "name", itr->pFieldname ); + // if( cbt != NULL ) + // field.SetAttribute( "type", cbt->getTypeName() ); + // else + // field.SetAttribute( "type", "TypeString" ); + // field.SetAttribute( "data", textbuf ); + + // pElement->InsertEndChild( field ); + //} + +} +ConsoleMethod(SimXMLDocument, setObjectAttributes, void, 3, 3, + "Set attribute of top stack element to given value.") +{ + object->setObjectAttributes(argv[2]); +} + +// ----------------------------------------------------------------------------- +// Create a new element and set to child of current stack element. +// New element is placed on top of element stack. +// ----------------------------------------------------------------------------- +void SimXMLDocument::pushNewElement(const char* rName) +{ + TiXmlElement cElement( rName ); + TiXmlElement* pStackTop = 0; + if(m_paNode.empty()) + { + pStackTop = dynamic_cast + (m_qDocument->InsertEndChild( cElement ) ); + } + else + { + const int iFinalElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iFinalElement]; + if(!pNode) + { + return; + } + pStackTop = dynamic_cast + (pNode->InsertEndChild( cElement )); + } + if(!pStackTop) + { + return; + } + m_paNode.push_back(pStackTop); +} +ConsoleMethod(SimXMLDocument, pushNewElement, void, 3, 3, + "Create new element as child of current stack element " + "and push new element on to stack.") +{ + object->pushNewElement( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Create a new element and set to child of current stack element. +// New element is placed on top of element stack. +// ----------------------------------------------------------------------------- +void SimXMLDocument::addNewElement(const char* rName) +{ + TiXmlElement cElement( rName ); + TiXmlElement* pStackTop = 0; + if(m_paNode.empty()) + { + pStackTop = dynamic_cast + (m_qDocument->InsertEndChild( cElement )); + if(!pStackTop) + { + return; + } + m_paNode.push_back(pStackTop); + return; + } + + const int iParentElement = m_paNode.size() - 2; + if(iParentElement < 0) + { + pStackTop = dynamic_cast + (m_qDocument->InsertEndChild( cElement )); + if(!pStackTop) + { + return; + } + m_paNode.push_back(pStackTop); + return; + } + else + { + TiXmlElement* pNode = m_paNode[iParentElement]; + if(!pNode) + { + return; + } + pStackTop = dynamic_cast + (pNode->InsertEndChild( cElement )); + if(!pStackTop) + { + return; + } + + // Overwrite top stack position. + const int iFinalElement = m_paNode.size() - 1; + m_paNode[iFinalElement] = pStackTop; + //pNode = pStackTop; + } +} +ConsoleMethod(SimXMLDocument, addNewElement, void, 3, 3, + "Create new element as child of current stack element " + "and push new element on to stack.") +{ + object->addNewElement( argv[2] ); +} + +// ----------------------------------------------------------------------------- +// Write XML document declaration. +// ----------------------------------------------------------------------------- +void SimXMLDocument::addHeader(void) +{ + TiXmlDeclaration cDeclaration("1.0", "utf-8", "yes"); + m_qDocument->InsertEndChild(cDeclaration); +} +ConsoleMethod(SimXMLDocument, addHeader, void, 2, 2, "Add XML header to document.") +{ + object->addHeader(); +} + +void SimXMLDocument::addComment(const char* comment) +{ + TiXmlComment cComment; + cComment.SetValue(comment); + m_qDocument->InsertEndChild(cComment); +} +ConsoleMethod(SimXMLDocument, addComment, void, 3, 3, "Add the given comment as a child of current stack element.") +{ + object->addComment(argv[2]); +} + +const char* SimXMLDocument::readComment( S32 index ) +{ + // Clear the current attribute pointer + m_CurrentAttribute = 0; + + // Push the first element found under the current element of the given name + if(!m_paNode.empty()) + { + const int iLastElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iLastElement]; + if(!pNode) + { + return ""; + } + TiXmlNode* node = pNode->FirstChild(); + for( S32 i = 0; i < index; i++ ) + { + if( !node ) + return ""; + + node = node->NextSiblingElement(); + } + + if( node ) + { + TiXmlComment* comment = node->ToComment(); + if( comment ) + return comment->Value(); + } + } + else + { + if(!m_qDocument) + { + return ""; + } + TiXmlNode* node = m_qDocument->FirstChild(); + for( S32 i = 0; i < index; i++ ) + { + if( !node ) + return ""; + + node = node->NextSibling(); + } + + if( node ) + { + TiXmlComment* comment = node->ToComment(); + if( comment ) + return comment->Value(); + } + } + return ""; +} +ConsoleMethod(SimXMLDocument, readComment, const char*, 3, 3, "Returns the comment at the specified index.") +{ + return object->readComment( dAtoi( argv[2] ) ); +} + +void SimXMLDocument::addText(const char* text) +{ + if(m_paNode.empty()) + return; + + const int iFinalElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iFinalElement]; + if(!pNode) + return; + + TiXmlText cText(text); + pNode->InsertEndChild( cText ); +} + +ConsoleMethod(SimXMLDocument, addText, void, 3, 3, "Add the given text as a child of current stack element.") +{ + object->addText(argv[2]); +} + +const char* SimXMLDocument::getText() +{ + if(m_paNode.empty()) + return ""; + + const int iFinalElement = m_paNode.size() - 1; + TiXmlNode* pNode = m_paNode[iFinalElement]; + if(!pNode) + return ""; + + if(!pNode->FirstChild()) + return ""; + + TiXmlText* text = pNode->FirstChild()->ToText(); + if( !text ) + return ""; + + return text->Value(); +} + +ConsoleMethod(SimXMLDocument, getText, const char*, 2, 2, "Gets the text from the current stack element.") +{ + const char* text = object->getText(); + if( !text ) + return ""; + + char* pBuffer = Con::getReturnBuffer(dStrlen( text ) + 1); + dStrcpy( pBuffer, text ); + return pBuffer; +} + +void SimXMLDocument::removeText() +{ + if(m_paNode.empty()) + return; + + const int iFinalElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iFinalElement]; + if(!pNode) + return; + + TiXmlText* text = pNode->FirstChild()->ToText(); + if( !text ) + return; + + pNode->RemoveChild(text); +} + +ConsoleMethod(SimXMLDocument, removeText, void, 2, 2, "Remove any text on the current stack element.") +{ + object->removeText(); +} + +void SimXMLDocument::addData(const char* text) +{ + if(m_paNode.empty()) + return; + + const int iFinalElement = m_paNode.size() - 1; + TiXmlElement* pNode = m_paNode[iFinalElement]; + if(!pNode) + return; + + TiXmlText cText(text); + pNode->InsertEndChild( cText ); +} + +ConsoleMethod(SimXMLDocument, addData, void, 3, 3, "Add the given text as a child of current stack element.") +{ + object->addData(argv[2]); +} + +const char* SimXMLDocument::getData() +{ + if(m_paNode.empty()) + return ""; + + const int iFinalElement = m_paNode.size() - 1; + TiXmlNode* pNode = m_paNode[iFinalElement]; + if(!pNode) + return ""; + + TiXmlText* text = pNode->FirstChild()->ToText(); + if( !text ) + return ""; + + return text->Value(); +} + +ConsoleMethod(SimXMLDocument, getData, const char*, 2, 2, "Gets the text from the current stack element.") +{ + const char* text = object->getData(); + if( !text ) + return ""; + + char* pBuffer = Con::getReturnBuffer(dStrlen( text ) + 1); + dStrcpy( pBuffer, text ); + return pBuffer; +} + +////EOF///////////////////////////////////////////////////////////////////////// diff --git a/console/SimXMLDocument.h b/console/SimXMLDocument.h new file mode 100644 index 0000000..042cacd --- /dev/null +++ b/console/SimXMLDocument.h @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Console implementation of STL map. +//----------------------------------------------------------------------------- + +#ifndef _XMLDOC_H_ +#define _XMLDOC_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif // _TVECTOR_H_ + + +class TiXmlDocument; +class TiXmlElement; +class TiXmlAttribute; + + +class SimXMLDocument: public SimObject +{ + // This typedef is required for tie ins with the script language. + // -------------------------------------------------------------------------- + protected: + typedef SimObject Parent; + // -------------------------------------------------------------------------- + + public: + SimXMLDocument(); + ~SimXMLDocument(); + + // These are overloaded functions from SimObject that we handle for + // tie in to the script language. The .cc file has more in depth + // comments on these. + //----------------------------------------------------------------------- + bool processArguments(S32 argc, const char** argv); + bool onAdd(); + void onRemove(); + static void initPersistFields(); + + // Set this to default state at construction. + void reset(void); + + // Read / write / parse XML. + S32 loadFile(const char* rFileName); + S32 saveFile(const char* rFileName); + S32 parse(const char* rText); + + // Clear XML document. + void clear(void); + + // Get error description if it exists. + const char* getErrorDesc(void) const; + // Clear previously set error. + void clearError(void); + + // Push first/last child element onto element stack. + bool pushFirstChildElement(const char* rName); + // Convert top stack element into sibling with given name. + bool nextSiblingElement(const char* rName); + // push child element at index onto stack. + bool pushChildElement(S32 index); + // Get element value + const char* elementValue(); + + // Pop last element off of stack. + void popElement(void); + + // Get attribute from top element on element stack. + const char* attribute(const char* rAttribute); + + // Does the attribute exist in the current element + bool attributeExists(const char* rAttribute); + + // Obtain the name of the current element's first or last attribute + const char* firstAttribute(); + const char* lastAttribute(); + + // Move through the current element's attributes to obtain their names + const char* nextAttribute(); + const char* prevAttribute(); + + // Set attribute of top element on element stack. + void setAttribute(const char* rAttribute, const char* rVal); + // Set attributes of a simObject on top element of the stack. + void setObjectAttributes(const char* objectID); + + // Remove attribute with given name from top element on stack. + void removeAttribute(const char* rAttribute); + + // Create a new element and push it onto stack as a new level. + void pushNewElement(const char* rName); + // Create a new element and push it onto stack on current level. + void addNewElement(const char* rName); + // Write XML declaration to current level. + void addHeader(void); + // Write a XML comment to the current level. + void addComment(const char* comment); + // Read a comment from the current level at the specified index. + const char* readComment( S32 index ); + // Write text to the current level. + void addText(const char* text); + // Retrieve text from the current level. + const char* getText(); + // Remove Text + void removeText(); + // Write data to the current level. + void addData(const char* text); + // Retrieve data from the current level. + const char* getData(); + + private: + // Document. + TiXmlDocument* m_qDocument; + // Stack of nodes. + Vector m_paNode; + // The current attribute + TiXmlAttribute* m_CurrentAttribute; + + public: + DECLARE_CONOBJECT(SimXMLDocument); +}; + +#endif // _XMLDOC_H_ +////EOF///////////////////////////////////////////////////////////////////////// diff --git a/console/arrayObject.cpp b/console/arrayObject.cpp new file mode 100644 index 0000000..9ed5e3e --- /dev/null +++ b/console/arrayObject.cpp @@ -0,0 +1,710 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/arrayObject.h" +#include "console/consoleTypes.h" +#include "math/mMathFn.h" + + +bool ArrayObject::smIncreasing = false; + +S32 QSORT_CALLBACK ArrayObject::_valueCompare( const void* a, const void* b ) +{ + ArrayObject::Element *ea = (ArrayObject::Element *) (a); + ArrayObject::Element *eb = (ArrayObject::Element *) (b); + S32 result = dStricmp(ea->value, eb->value); + return ( smIncreasing ? -result : result ); +} + +S32 QSORT_CALLBACK ArrayObject::_valueNumCompare( const void* a, const void* b ) +{ + ArrayObject::Element *ea = (ArrayObject::Element *) (a); + ArrayObject::Element *eb = (ArrayObject::Element *) (b); + F32 aCol = dAtof(ea->value); + F32 bCol = dAtof(eb->value); + F32 result = aCol - bCol; + S32 res = result < 0 ? -1 : (result > 0 ? 1 : 0); + return ( smIncreasing ? res : -res ); +} + +S32 QSORT_CALLBACK ArrayObject::_keyCompare( const void* a, const void* b ) +{ + ArrayObject::Element *ea = (ArrayObject::Element *) (a); + ArrayObject::Element *eb = (ArrayObject::Element *) (b); + S32 result = dStricmp(ea->key, eb->key); + return ( smIncreasing ? -result : result ); +} + +S32 QSORT_CALLBACK ArrayObject::_keyNumCompare( const void* a, const void* b ) +{ + ArrayObject::Element *ea = (ArrayObject::Element *) (a); + ArrayObject::Element *eb = (ArrayObject::Element *) (b); + const char* aCol = ea->key; + const char* bCol = eb->key; + F32 result = dAtof(aCol) - dAtof(bCol); + S32 res = result < 0 ? -1 : (result > 0 ? 1 : 0); + return ( smIncreasing ? res : -res ); +} + + +IMPLEMENT_CONOBJECT(ArrayObject); + +ArrayObject::ArrayObject() + : mCurrentIndex( NULL ), + mCaseSensitive( false ) +{ +} + +void ArrayObject::initPersistFields() +{ + addField( "caseSensitive", TypeBool, Offset( mCaseSensitive, ArrayObject ) ); + + Parent::initPersistFields(); +} + +bool ArrayObject::onAdd() +{ + return Parent::onAdd(); +} + +void ArrayObject::onRemove() +{ + mArray.empty(); + Parent::onRemove(); +} + +U32 ArrayObject::getIndexFromValue( const String &value ) const +{ + U32 foundIndex = -1; + for ( U32 i = mCurrentIndex; i < mArray.size(); i++ ) + { + if ( mArray[i].value.equal( value ) ) + { + foundIndex = i; + break; + } + } + + if( foundIndex < 0 ) + { + for ( U32 i = 0; i < mCurrentIndex; i++ ) + { + if ( mArray[i].value.equal( value ) ) + { + foundIndex = i; + break; + } + } + } + + return foundIndex; +} + +U32 ArrayObject::getIndexFromKey( const String &key ) const +{ + U32 foundIndex = -1; + for ( U32 i = mCurrentIndex; i < mArray.size(); i++ ) + { + if ( mArray[i].key.equal( key ) ) + { + foundIndex = i; + break; + } + } + + if( foundIndex < 0 ) + { + for ( U32 i = 0; i < mCurrentIndex; i++ ) + { + if ( mArray[i].key.equal( key ) ) + { + foundIndex = i; + break; + } + } + } + + return foundIndex; +} + +U32 ArrayObject::getIndexFromKeyValue( const String &key, const String &value ) const +{ + U32 foundIndex = -1; + for ( U32 i = mCurrentIndex; i < mArray.size(); i++ ) + { + if ( mArray[i].key == key && mArray[i].value.equal( value ) ) + { + foundIndex = i; + break; + } + } + + if ( foundIndex < 0 ) + { + for ( U32 i = 0; i < mCurrentIndex; i++ ) + { + if ( mArray[i].key.equal( key ) && mArray[i].value.equal( value ) ) + { + foundIndex = i; + break; + } + } + } + + return foundIndex; +} + + +const String& ArrayObject::getKeyFromIndex( U32 index ) const +{ + if ( index > mArray.size() - 1 || index < 0 || mArray.size() == 0 ) + return String::EmptyString; + + return mArray[index].key; +} + +const String& ArrayObject::getValueFromIndex( U32 index ) const +{ + if( index > mArray.size() - 1 || index < 0 || mArray.size() == 0 ) + return String::EmptyString; + + return mArray[index].value; +} + +S32 ArrayObject::countValue( const String &value ) const +{ + S32 count = 0; + for ( U32 i = 0; i < mArray.size(); i++ ) + { + if ( mArray[i].value.equal( value ) ) + count++; + } + + return count; +} + +S32 ArrayObject::countKey( const String &key) const +{ + S32 count = 0; + for ( U32 i = 0; i < mArray.size(); i++ ) + { + if ( mArray[i].key.equal( key ) ) + count++; + } + + return count; +} + +void ArrayObject::push_back( const String &key, const String &value ) +{ + Element temp; + temp.value = value; + temp.key = key; + mArray.push_back(temp); +} + +void ArrayObject::push_front( const String &key, const String &value ) +{ + Element temp; + temp.value = value; + temp.key = key; + mArray.push_front(temp); +} + +void ArrayObject::insert( const String &key, const String &value, U32 index ) +{ + index = mClampF( index, 0, mArray.size() - 1 ); + + U32 size = mArray.size() - 1; + for( U32 i = size; i >= index; i-- ) + moveIndex( i, i + 1 ); + + Element temp; + temp.value = value; + temp.key = key; + mArray[index] = temp; +} + +void ArrayObject::pop_back() +{ + if(mArray.size() <= 0) + return; + mArray.pop_back(); + if( mCurrentIndex >= mArray.size() ) + mCurrentIndex = mArray.size() - 1; +} + +void ArrayObject::pop_front() +{ + if( mArray.size() <= 0 ) + return; + + mArray.pop_front(); + + if( mCurrentIndex >= mArray.size() ) + mCurrentIndex = mArray.size() - 1; +} + +void ArrayObject::erase( U32 index ) +{ + if(index < 0 || index >= mArray.size()) + return; + + mArray.erase( index ); +} + +void ArrayObject::empty() +{ + S32 size = mArray.size(); + for(U32 i=0; i= mArray.size()) + push_back(mArray[prev].key, mArray[prev].value); + else + mArray[index] = mArray[prev]; + mArray[index].value = String::EmptyString; + mArray[index].key = String::EmptyString; +} + +void ArrayObject::uniqueValue() +{ + for(U32 i=0; icount(); i++) + { + StringTableEntry tempval = obj->getValueFromIndex(i); + StringTableEntry tempkey = obj->getKeyFromIndex(i); + push_back(tempkey, tempval); + } + mCurrentIndex = obj->getCurrent(); +} + +void ArrayObject::crop( ArrayObject *obj ) +{ + for( U32 i = 0; i < obj->count(); i++ ) + { + const String &tempkey = obj->getKeyFromIndex( i ); + for( U32 j = 0; j < mArray.size(); j++ ) + { + if( mArray[j].key.equal( tempkey ) ) + { + mArray.erase( j ); + j--; + } + } + } +} + +void ArrayObject::append(ArrayObject* obj) +{ + for(U32 i=0; icount(); i++) + { + StringTableEntry tempval = obj->getValueFromIndex(i); + StringTableEntry tempkey = obj->getKeyFromIndex(i); + push_back(tempkey, tempval); + } +} + +void ArrayObject::setKey( const String &key, U32 index ) +{ + if ( index >= mArray.size() ) + return; + + mArray[index].key = key; +} + +void ArrayObject::setValue( const String &value, U32 index ) +{ + if ( index >= mArray.size() ) + return; + + mArray[index].value = value; +} + +void ArrayObject::sort( bool valsort, bool desc, bool numeric ) +{ + if ( mArray.size() <= 1 ) + return; + + smIncreasing = desc ? false : true; + + if ( numeric ) + { + if ( valsort ) + dQsort( (void *)&(mArray[0]), mArray.size(), sizeof(Element), _valueNumCompare) ; + else + dQsort( (void *)&(mArray[0]), mArray.size(), sizeof(Element), _keyNumCompare ); + } + else + { + if( valsort ) + dQsort( (void *)&(mArray[0]), mArray.size(), sizeof(Element), _valueCompare ); + else + dQsort( (void *)&(mArray[0]), mArray.size(), sizeof(Element), _keyCompare ); + } +} + +U32 ArrayObject::moveFirst() +{ + mCurrentIndex = 0; + return mCurrentIndex; +} + +U32 ArrayObject::moveLast() +{ + mCurrentIndex = mArray.size() - 1; + return mCurrentIndex; +} + +U32 ArrayObject::moveNext() +{ + if ( mCurrentIndex >= mArray.size() - 1 ) + return -1; + + mCurrentIndex++; + + return mCurrentIndex; +} + +U32 ArrayObject::movePrev() +{ + if ( mCurrentIndex <= 0 ) + return -1; + + mCurrentIndex--; + + return mCurrentIndex; +} + +void ArrayObject::setCurrent( S32 idx ) +{ + if ( idx < 0 || idx >= mArray.size() ) + { + Con::errorf( "ArrayObject::setCurrent( %d ) is out of the array bounds!", idx ); + return; + } + + mCurrentIndex = idx; +} + +void ArrayObject::echo() +{ + Con::printf( "ArrayObject Listing:" ); + Con::printf( "Index Key Value" ); + for ( U32 i = 0; i < mArray.size(); i++ ) + { + StringTableEntry key = mArray[i].key; + StringTableEntry val = mArray[i].value; + Con::printf( "%d [%s] => %s", (S32)i, key, val ); + } +} + +ConsoleMethod( ArrayObject, getIndexFromValue, S32, 3, 3, "(string value)" + "Search array from current position for the first matching value" ) +{ + StringTableEntry value = StringTable->insert( argv[2], object->isCaseSensitive() ); + return (S32)object->getIndexFromValue( value ); +} + +ConsoleMethod( ArrayObject, getIndexFromKey, S32, 3, 3, "(string key)" + "Search array from current position for the first matching key" ) +{ + StringTableEntry value = StringTable->insert( argv[2], object->isCaseSensitive() ); + return (S32)object->getIndexFromKey( value ); +} + +ConsoleMethod( ArrayObject, getValue, const char*, 3, 3, "(int index)" + "Get the value of the array element at the submitted index" ) +{ + StringTableEntry key = object->getValueFromIndex( dAtoi( argv[2] ) ); + char *buff = Con::getReturnBuffer( 512 ); + dSprintf( buff, 512, "%s", key ); + return buff; +} + +ConsoleMethod( ArrayObject, getKey, const char*, 3, 3, "(int index)" + "Get the key of the array element at the submitted index" ) +{ + StringTableEntry key = object->getKeyFromIndex( dAtoi( argv[2] ) ); + char *buff = Con::getReturnBuffer( 512 ); + dSprintf( buff, 512, "%s", key ); + return buff; +} + +ConsoleMethod( ArrayObject, setKey, void, 4, 4, "(string key, int index)" + "Set the key at the given index" ) +{ + StringTableEntry key = StringTable->insert( argv[2], object->isCaseSensitive() ); + U32 index = dAtoi( argv[3] ); + object->setKey( key, index ); +} + +ConsoleMethod( ArrayObject, setValue, void, 4, 4, "(string value, int index)" + "Set the value at the given index" ) +{ + StringTableEntry value = StringTable->insert( argv[2], object->isCaseSensitive() ); + U32 index = dAtoi( argv[3] ); + object->setValue( value, index ); +} + +ConsoleMethod( ArrayObject, count, S32, 2, 2, "Get the number of elements in the array" ) +{ + return (S32)object->count(); +} + +ConsoleMethod( ArrayObject, countValue, S32, 3, 3, "(string value)" + "Get the number of times a particular value is found in the array" ) +{ + StringTableEntry value = StringTable->insert( argv[2], object->isCaseSensitive() ); + return (S32)object->countValue( value ); +} + +ConsoleMethod( ArrayObject, countKey, S32, 3, 3, "(string key)" + "Get the number of times a particular key is found in the array" ) +{ + StringTableEntry value = StringTable->insert( argv[2], object->isCaseSensitive() ); + return (S32)object->countKey( value ); +} + +ConsoleMethod( ArrayObject, add, void, 4, 4, "(string key, string value)" + "Adds a new element to the end of an array" ) +{ + StringTableEntry key = StringTable->insert( argv[2], object->isCaseSensitive() ); + StringTableEntry value = StringTable->insert( argv[3], object->isCaseSensitive() ); + object->push_back( key, value ); +} + +ConsoleMethod( ArrayObject, push_back, void, 4, 4, "(string key, string value)" + "Adds a new element to the end of an array" ) +{ + StringTableEntry key = StringTable->insert( argv[2], object->isCaseSensitive() ); + StringTableEntry value = StringTable->insert( argv[3], object->isCaseSensitive() ); + object->push_back( key, value ); +} + +ConsoleMethod( ArrayObject, push_front, void, 4, 4, "(string key, string value)" + "Adds a new element to the front of an array" ) +{ + StringTableEntry key = StringTable->insert( argv[2], object->isCaseSensitive() ); + StringTableEntry value = StringTable->insert( argv[3], object->isCaseSensitive() ); + object->push_front( key, value ); +} + +ConsoleMethod( ArrayObject, insert, void, 5, 5, "(string key, string value, int index)" + "Adds a new element to a specified position in the array" ) +{ + StringTableEntry key = StringTable->insert( argv[2], object->isCaseSensitive() ); + StringTableEntry value = StringTable->insert( argv[3], object->isCaseSensitive() ); + object->insert( key, value, dAtoi( argv[4] ) ); +} + +ConsoleMethod( ArrayObject, pop_back, void, 2, 2, "Removes the last element from the array" ) +{ + object->pop_back(); +} + +ConsoleMethod( ArrayObject, pop_front, void, 2, 2, "Removes the first element from the array" ) +{ + object->pop_front(); +} + +ConsoleMethod( ArrayObject, erase, void, 3, 3, "(int index)" + "Removes an element at a specific position from the array" ) +{ + object->erase( dAtoi( argv[2] ) ); +} + +ConsoleMethod( ArrayObject, empty, void, 2, 2, "Emptys all elements from an array") +{ + object->empty(); +} + +ConsoleMethod( ArrayObject, uniqueValue, void, 2, 2, "Removes any elements that have duplicated values (leaving the first instance)") +{ + object->uniqueValue(); +} + +ConsoleMethod( ArrayObject, uniqueKey, void, 2, 2, "Removes any elements that have duplicated keys (leaving the first instance)") +{ + object->uniqueKey(); +} + +ConsoleMethod( ArrayObject, duplicate, bool, 3, 3, "(ArrayObject target)" + "Alters array into an exact duplicate of the target array" ) +{ + ArrayObject *target; + + if ( Sim::findObject( argv[2], target ) ) + { + object->duplicate( target ); + return true; + } + + return false; +} + +ConsoleMethod( ArrayObject, crop, bool, 3, 3, "(ArrayObject target)" + "Removes elements with matching keys from array" ) +{ + ArrayObject *target; + if ( Sim::findObject( argv[2], target ) ) + { + object->crop( target ); + return true; + } + + return false; +} + +ConsoleMethod( ArrayObject, append, bool, 3, 3, "(ArrayObject target)" + "Appends the target array to the array object" ) +{ + ArrayObject *target; + if ( Sim::findObject( argv[2], target ) ) + { + object->append( target ); + return true; + } + + return false; +} + +ConsoleMethod( ArrayObject, sort, void, 2, 3, "(bool desc)" + "Alpha sorts the array by value (default ascending sort)" ) +{ + bool descending = argc == 3 ? dAtob( argv[2] ) : false; + object->sort( true, descending, false ); +} + +ConsoleMethod( ArrayObject, sorta, void, 2, 2, "Alpha sorts the array by value in ascending order" ) +{ + object->sort( true, false, false ); +} + +ConsoleMethod( ArrayObject, sortd, void, 2, 2, "Alpha sorts the array by value in descending order" ) +{ + object->sort( true, true, false ); +} + +ConsoleMethod( ArrayObject, sortk, void, 2, 3, "(bool desc)" + "Alpha sorts the array by key (default ascending sort)" ) +{ + bool descending = argc == 3 ? dAtob( argv[2] ) : false; + object->sort( false, descending, false ); +} + +ConsoleMethod( ArrayObject, sortka, void, 2, 2, "Alpha sorts the array by key in ascending order" ) +{ + object->sort( false, false, false ); +} + +ConsoleMethod( ArrayObject, sortkd, void, 2, 2, "Alpha sorts the array by key in descending order" ) +{ + object->sort( false, true, false ); +} + +ConsoleMethod( ArrayObject, sortn, void, 2, 3, "(bool desc)" + "Numerically sorts the array by value (default ascending sort)" ) +{ + bool descending = argc == 3 ? dAtob( argv[2] ) : false; + object->sort( true, descending, true ); +} + +ConsoleMethod( ArrayObject, sortna, void, 2, 2, "Numerically sorts the array by value in ascending order" ) +{ + object->sort( true, false, true ); +} + +ConsoleMethod( ArrayObject, sortnd, void, 2, 2, "Numerically sorts the array by value in descending order" ) +{ + object->sort( true, true, true ); +} + +ConsoleMethod( ArrayObject, sortnk, void, 2, 3, "(bool desc)" + "Numerically sorts the array by key (default ascending sort)" ) +{ + bool descending = argc == 3 ? dAtob( argv[2] ) : false; + object->sort( false, descending, true ); +} + +ConsoleMethod( ArrayObject, sortnka, void, 2, 2, "Numerical sorts the array by key in ascending order" ) +{ + object->sort( false, false, true ); +} + +ConsoleMethod( ArrayObject, sortnkd, void, 2, 2, "Numerical sorts the array by key in descending order" ) +{ + object->sort( false, true, true ); +} + +ConsoleMethod( ArrayObject, moveFirst, S32, 2, 2, "Moves array pointer to start of array" ) +{ + return object->moveFirst(); +} + +ConsoleMethod( ArrayObject, moveLast, S32, 2, 2, "Moves array pointer to end of array" ) +{ + return object->moveLast(); +} + +ConsoleMethod( ArrayObject, moveNext, S32, 2, 2, "Moves array pointer to next position (returns -1 if cannot move)" ) +{ + return object->moveNext(); +} + +ConsoleMethod( ArrayObject, movePrev, S32, 2, 2, "Moves array pointer to prev position (returns -1 if cannot move)" ) +{ + return object->movePrev(); +} + +ConsoleMethod( ArrayObject, getCurrent, S32, 2, 2, "Gets the current pointer index" ) +{ + return object->getCurrent(); +} + +ConsoleMethod( ArrayObject, setCurrent, void, 3, 3, "Sets the current pointer index" ) +{ + object->setCurrent( dAtoi(argv[2]) ); +} + +ConsoleMethod( ArrayObject, echo, void, 2, 2, "Echos the array in the console" ) +{ + object->echo(); +} diff --git a/console/arrayObject.h b/console/arrayObject.h new file mode 100644 index 0000000..1fc9d09 --- /dev/null +++ b/console/arrayObject.h @@ -0,0 +1,192 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ARRAYOBJECT_H_ +#define _ARRAYOBJECT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +// This class is based on original code by community +// member Daniel Neilsen: +// +// http://www.garagegames.com/community/resources/view/4711 + + +/// +class ArrayObject : public SimObject +{ + typedef SimObject Parent; + +protected: + + static bool smIncreasing; + + struct Element + { + String key; + String value; + }; + + static S32 QSORT_CALLBACK _valueCompare( const void *a, const void *b ); + static S32 QSORT_CALLBACK _valueNumCompare( const void *a, const void *b ); + static S32 QSORT_CALLBACK _keyCompare( const void *a, const void *b ); + static S32 QSORT_CALLBACK _keyNumCompare( const void *a, const void *b ); + +public: + + ArrayObject(); + virtual ~ArrayObject() {} + + // SimObject + bool onAdd(); + void onRemove(); + + DECLARE_CONOBJECT( ArrayObject ); + static void initPersistFields(); + + /// @name Data Query + /// @{ + + /// Returns true if string handly by the array is case-sensitive. + bool isCaseSensitive() const { return mCaseSensitive; } + + /// Searches the array for the first matching value from the + /// current array position. It will return -1 if no matching + /// index is found. + U32 getIndexFromValue( const String &value ) const; + + /// Searches the array for the first matching key from the current + /// array position. It will return -1 if no matching index found. + U32 getIndexFromKey( const String &key ) const; + + /// Returns the key for a given index. + /// Will return a null value for an invalid index + const String& getKeyFromIndex( U32 index ) const; + + /// Returns the value for a given index. + /// Will return a null value for an invalid index + const String& getValueFromIndex( U32 index ) const; + + /// + U32 getIndexFromKeyValue( const String &key, const String &value ) const; + + /// Counts the number of elements in the array + S32 count() const { return mArray.size(); } + + /// Counts the number of instances of a particular value in the array + S32 countValue( const String &value ) const; + + /// Counts the number of instances of a particular key in the array + S32 countKey( const String &key ) const; + + /// @} + + /// @name Data Alteration + /// @{ + + /// Adds a new array item to the end of the array + void push_back( const String &key, const String &value ); + + /// Adds a new array item to the front of the array + void push_front( const String &key, const String &value ); + + /// Adds a new array item to a particular index of the array + void insert( const String &key, const String &value, U32 index ); + + /// Removes an array item from the end of the array + void pop_back(); + + /// Removes an array item from the end of the array + void pop_front(); + + /// Removes an array item from a particular index of the array + void erase( U32 index ); + + /// Clears an array + void empty(); + + /// Moves a key and value from one index location to another. + void moveIndex( U32 prev, U32 index ); + + /// @} + + /// @name Complex Data Alteration + /// @{ + + /// Removes any duplicate values from the array + /// (keeps the first instance only) + void uniqueValue(); + + /// Removes any duplicate keys from the array + /// (keeps the first instance only) + void uniqueKey(); + + /// Makes this array an exact duplicate of another array + void duplicate( ArrayObject *obj ); + + /// Crops the keys that exists in the target array from our current array + void crop( ArrayObject *obj ); + + /// Appends the target array to our current array + void append( ArrayObject *obj ); + + /// Sets the key at the given index + void setKey( const String &key, U32 index ); + + /// Sets the key at the given index + void setValue( const String &value, U32 index ); + + /// This sorts the array. + /// @param valtest Determines whether sorting by value or key. + /// @param desc Determines if sorting ascending or descending. + /// @param numeric Determines if sorting alpha or numeric search. + void sort( bool valtest, bool desc, bool numeric ); + + /// @} + + /// @name Pointer Manipulation + /// @{ + + /// Moves pointer to arrays first position + U32 moveFirst(); + + /// Moves pointer to arrays last position + U32 moveLast(); + + /// Moves pointer to arrays next position + /// If last position it returns -1 and no move occurs; + U32 moveNext(); + + /// Moves pointer to arrays prev position + /// If first position it returns -1 and no move occurs; + U32 movePrev(); + + /// Returns current pointer index. + U32 getCurrent() const { return mCurrentIndex; } + + /// + void setCurrent( S32 idx ); + + /// @} + + + /// @name Data Listing + /// @{ + + /// Echos the content of the array to the console. + void echo(); + + /// @} + +protected: + + bool mCaseSensitive; + U32 mCurrentIndex; + Vector mArray; +}; + +#endif // _ARRAYOBJECT_H_ \ No newline at end of file diff --git a/console/ast.h b/console/ast.h new file mode 100644 index 0000000..83f058f --- /dev/null +++ b/console/ast.h @@ -0,0 +1,543 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AST_H_ +#define _AST_H_ + +class ExprEvalState; +class Namespace; +class SimObject; +class SimGroup; + +/// Enable this #define if you are seeing the message "precompile size mismatch" in the console. +/// This will help track down which node type is causing the error. It could be +/// due to incorrect compiler optimization. +//#define DEBUG_AST_NODES + +enum TypeReq +{ + TypeReqNone, + TypeReqUInt, + TypeReqFloat, + TypeReqString +}; + +/// Representation of a node for the scripting language parser. +/// +/// When the scripting language is evaluated, it is turned from a string representation, +/// into a parse tree, thence into byte code, which is ultimately interpreted by the VM. +/// +/// This is the base class for the nodes in the parse tree. There are a great many subclasses, +/// each representing a different language construct. +struct StmtNode +{ + StmtNode *next; ///< Next entry in parse tree. + + StmtNode(); + virtual ~StmtNode() {} + + /// @name next Accessors + /// @{ + + /// + void append(StmtNode *next); + StmtNode *getNext() const { return next; } + + /// @} + + /// @name Debug Info + /// @{ + + StringTableEntry dbgFileName; ///< Name of file this node is associated with. + S32 dbgLineNumber; ///< Line number this node is associated with. +#ifdef DEBUG_AST_NODES + virtual String dbgStmtType() const = 0; +#endif + /// @} + + /// @name Breaking + /// @{ + + void addBreakCount(); + void addBreakLine(U32 ip); + /// @} + + /// @name Compilation + /// @{ + + virtual U32 precompileStmt(U32 loopCount) = 0; + virtual U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint) = 0; + virtual void setPackage(StringTableEntry packageName); + /// @} +}; + +/// Helper macro +#ifndef DEBUG_AST_NODES +# define DBG_STMT_TYPE(s) virtual String dbgStmtType() const { return String(#s); } +#else +# define DBG_STMT_TYPE(s) +#endif + +struct BreakStmtNode : StmtNode +{ + static BreakStmtNode *alloc(); + + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + DBG_STMT_TYPE(BreakStmtNode); +}; + +struct ContinueStmtNode : StmtNode +{ + static ContinueStmtNode *alloc(); + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + DBG_STMT_TYPE(ContinueStmtNode); +}; + +/// A mathematical expression. +struct ExprNode : StmtNode +{ + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + + virtual U32 precompile(TypeReq type) = 0; + virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type) = 0; + virtual TypeReq getPreferredType() = 0; +}; + +struct ReturnStmtNode : StmtNode +{ + ExprNode *expr; + + static ReturnStmtNode *alloc(ExprNode *expr); + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + DBG_STMT_TYPE(ReturnStmtNode); +}; + +struct IfStmtNode : StmtNode +{ + ExprNode *testExpr; + StmtNode *ifBlock, *elseBlock; + U32 endifOffset; + U32 elseOffset; + bool integer; + bool propagate; + + static IfStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagateThrough); + void propagateSwitchExpr(ExprNode *left, bool string); + ExprNode *getSwitchOR(ExprNode *left, ExprNode *list, bool string); + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + DBG_STMT_TYPE(IfStmtNode); +}; + +struct LoopStmtNode : StmtNode +{ + ExprNode *testExpr; + ExprNode *initExpr; + ExprNode *endLoopExpr; + StmtNode *loopBlock; + bool isDoLoop; + U32 breakOffset; + U32 continueOffset; + U32 loopBlockStartOffset; + bool integer; + + static LoopStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, ExprNode *initExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop); + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + DBG_STMT_TYPE(LoopStmtNode); +}; + +/// A binary mathematical expression (ie, left op right). +struct BinaryExprNode : ExprNode +{ + S32 op; + ExprNode *left; + ExprNode *right; +}; + +struct FloatBinaryExprNode : BinaryExprNode +{ + static FloatBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(FloatBinaryExprNode); +}; + +struct ConditionalExprNode : ExprNode +{ + ExprNode *testExpr; + ExprNode *trueExpr; + ExprNode *falseExpr; + bool integer; + static ConditionalExprNode *alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr); + virtual U32 precompile(TypeReq type); + virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type); + virtual TypeReq getPreferredType(); + DBG_STMT_TYPE(ConditionalExprNode); +}; + +struct IntBinaryExprNode : BinaryExprNode +{ + TypeReq subType; + U32 operand; + + static IntBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right); + + void getSubTypeOperand(); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(IntBinaryExprNode); +}; + +struct StreqExprNode : BinaryExprNode +{ + bool eq; + static StreqExprNode *alloc(ExprNode *left, ExprNode *right, bool eq); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(StreqExprNode); +}; + +struct StrcatExprNode : BinaryExprNode +{ + int appendChar; + static StrcatExprNode *alloc(ExprNode *left, ExprNode *right, int appendChar); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(StrcatExprNode); +}; + +struct CommaCatExprNode : BinaryExprNode +{ + static CommaCatExprNode *alloc(ExprNode *left, ExprNode *right); + + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(CommaCatExprNode); +}; + +struct IntUnaryExprNode : ExprNode +{ + S32 op; + ExprNode *expr; + bool integer; + + static IntUnaryExprNode *alloc(S32 op, ExprNode *expr); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(IntUnaryExprNode); +}; + +struct FloatUnaryExprNode : ExprNode +{ + S32 op; + ExprNode *expr; + + static FloatUnaryExprNode *alloc(S32 op, ExprNode *expr); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(FloatUnaryExprNode); +}; + +struct VarNode : ExprNode +{ + StringTableEntry varName; + ExprNode *arrayIndex; + + static VarNode *alloc(StringTableEntry varName, ExprNode *arrayIndex); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(VarNode); +}; + +struct IntNode : ExprNode +{ + S32 value; + U32 index; // if it's converted to float/string + + static IntNode *alloc(S32 value); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(IntNode); +}; + +struct FloatNode : ExprNode +{ + F64 value; + U32 index; + + static FloatNode *alloc(F64 value); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(FloatNode); +}; + +struct StrConstNode : ExprNode +{ + char *str; + F64 fVal; + U32 index; + bool tag; + bool doc; // Specifies that this string is a documentation block. + + static StrConstNode *alloc(char *str, bool tag, bool doc = false); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(StrConstNode); +}; + +struct ConstantNode : ExprNode +{ + StringTableEntry value; + F64 fVal; + U32 index; + + static ConstantNode *alloc(StringTableEntry value); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(ConstantNode); +}; + +struct AssignExprNode : ExprNode +{ + StringTableEntry varName; + ExprNode *expr; + ExprNode *arrayIndex; + TypeReq subType; + + static AssignExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(AssignExprNode); +}; + +struct AssignDecl +{ + S32 token; + ExprNode *expr; + bool integer; +}; + +struct AssignOpExprNode : ExprNode +{ + StringTableEntry varName; + ExprNode *expr; + ExprNode *arrayIndex; + S32 op; + U32 operand; + TypeReq subType; + + static AssignOpExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(AssignOpExprNode); +}; + +struct TTagSetStmtNode : StmtNode +{ + StringTableEntry tag; + ExprNode *valueExpr; + ExprNode *stringExpr; + + static TTagSetStmtNode *alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr); + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + DBG_STMT_TYPE(TTagSetStmtNode); +}; + +struct TTagDerefNode : ExprNode +{ + ExprNode *expr; + + static TTagDerefNode *alloc(ExprNode *expr); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(TTagDerefNode); +}; + +struct TTagExprNode : ExprNode +{ + StringTableEntry tag; + + static TTagExprNode *alloc(StringTableEntry tag); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(TTagExprNode); +}; + +struct FuncCallExprNode : ExprNode +{ + StringTableEntry funcName; + StringTableEntry nameSpace; + ExprNode *args; + U32 callType; + enum { + FunctionCall, + MethodCall, + ParentCall + }; + + static FuncCallExprNode *alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(FuncCallExprNode); +}; + +struct AssertCallExprNode : ExprNode +{ + ExprNode *testExpr; + const char *message; + U32 messageIndex; + + static AssertCallExprNode *alloc( ExprNode *testExpr, const char *message ); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(AssertCallExprNode); +}; + +struct SlotDecl +{ + ExprNode *object; + StringTableEntry slotName; + ExprNode *array; +}; + +struct SlotAccessNode : ExprNode +{ + ExprNode *objectExpr, *arrayExpr; + StringTableEntry slotName; + + static SlotAccessNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(SlotAccessNode); +}; + +struct InternalSlotDecl +{ + ExprNode *object; + ExprNode *slotExpr; + bool recurse; +}; + +struct InternalSlotAccessNode : ExprNode +{ + ExprNode *objectExpr, *slotExpr; + bool recurse; + + static InternalSlotAccessNode *alloc(ExprNode *objectExpr, ExprNode *slotExpr, bool recurse); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(InternalSlotAccessNode); +}; + +struct SlotAssignNode : ExprNode +{ + ExprNode *objectExpr, *arrayExpr; + StringTableEntry slotName; + ExprNode *valueExpr; + U32 typeID; + + static SlotAssignNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr, U32 typeID = -1); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(SlotAssignNode); +}; + +struct SlotAssignOpNode : ExprNode +{ + ExprNode *objectExpr, *arrayExpr; + StringTableEntry slotName; + S32 op; + ExprNode *valueExpr; + U32 operand; + TypeReq subType; + + static SlotAssignOpNode *alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr); + U32 precompile(TypeReq type); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + TypeReq getPreferredType(); + DBG_STMT_TYPE(SlotAssignOpNode); +}; + +struct ObjectDeclNode : ExprNode +{ + ExprNode *classNameExpr; + StringTableEntry parentObject; + ExprNode *objectNameExpr; + ExprNode *argList; + SlotAssignNode *slotDecls; + ObjectDeclNode *subObjects; + bool isDatablock; + U32 failOffset; + bool isClassNameInternal; + bool isSingleton; + + static ObjectDeclNode *alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool isDatablock, bool classNameInternal, bool isSingleton); + U32 precompile(TypeReq type); + U32 precompileSubObject(bool); + U32 compile(U32 *codeStream, U32 ip, TypeReq type); + U32 compileSubObject(U32 *codeStream, U32 ip, bool); + TypeReq getPreferredType(); + DBG_STMT_TYPE(ObjectDeclNode); +}; + +struct ObjectBlockDecl +{ + SlotAssignNode *slots; + ObjectDeclNode *decls; +}; + +struct FunctionDeclStmtNode : StmtNode +{ + StringTableEntry fnName; + VarNode *args; + StmtNode *stmts; + StringTableEntry nameSpace; + StringTableEntry package; + U32 endOffset; + U32 argc; + + static FunctionDeclStmtNode *alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts); + U32 precompileStmt(U32 loopCount); + U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + void setPackage(StringTableEntry packageName); + DBG_STMT_TYPE(FunctionDeclStmtNode); +}; + +extern StmtNode *gStatementList; +extern void createFunction(const char *fnName, VarNode *args, StmtNode *statements); +extern ExprEvalState gEvalState; +extern bool lookupFunction(const char *fnName, VarNode **args, StmtNode **statements); +typedef const char *(*cfunc)(S32 argc, char **argv); +extern bool lookupCFunction(const char *fnName, cfunc *f); + + +#endif diff --git a/console/astAlloc.cpp b/console/astAlloc.cpp new file mode 100644 index 0000000..5dd57e6 --- /dev/null +++ b/console/astAlloc.cpp @@ -0,0 +1,368 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/compiler.h" +#include "console/consoleInternal.h" + +using namespace Compiler; + +/// @file +/// +/// TorqueScript AST node allocators. +/// +/// These static methods exist to allocate new AST node for the compiler. They +/// all allocate memory from the consoleAllocator for efficiency, and often take +/// arguments relating to the state of the nodes. They are called from gram.y +/// (really gram.c) as the lexer analyzes the script code. + +//------------------------------------------------------------ + +BreakStmtNode *BreakStmtNode::alloc() +{ + BreakStmtNode *ret = (BreakStmtNode *) consoleAlloc(sizeof(BreakStmtNode)); + constructInPlace(ret); + return ret; +} + +ContinueStmtNode *ContinueStmtNode::alloc() +{ + ContinueStmtNode *ret = (ContinueStmtNode *) consoleAlloc(sizeof(ContinueStmtNode)); + constructInPlace(ret); + return ret; +} + +ReturnStmtNode *ReturnStmtNode::alloc(ExprNode *expr) +{ + ReturnStmtNode *ret = (ReturnStmtNode *) consoleAlloc(sizeof(ReturnStmtNode)); + constructInPlace(ret); + ret->expr = expr; + + return ret; +} + +IfStmtNode *IfStmtNode::alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagate) +{ + IfStmtNode *ret = (IfStmtNode *) consoleAlloc(sizeof(IfStmtNode)); + constructInPlace(ret); + ret->dbgLineNumber = lineNumber; + + ret->testExpr = testExpr; + ret->ifBlock = ifBlock; + ret->elseBlock = elseBlock; + ret->propagate = propagate; + + return ret; +} + +LoopStmtNode *LoopStmtNode::alloc(S32 lineNumber, ExprNode *initExpr, ExprNode *testExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop) +{ + LoopStmtNode *ret = (LoopStmtNode *) consoleAlloc(sizeof(LoopStmtNode)); + constructInPlace(ret); + ret->dbgLineNumber = lineNumber; + ret->testExpr = testExpr; + ret->initExpr = initExpr; + ret->endLoopExpr = endLoopExpr; + ret->loopBlock = loopBlock; + ret->isDoLoop = isDoLoop; + + // Deal with setting some dummy constant nodes if we weren't provided with + // info... This allows us to play nice with missing parts of for(;;) for + // instance. + if(!ret->testExpr) ret->testExpr = IntNode::alloc(1); + + return ret; +} + +FloatBinaryExprNode *FloatBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right) +{ + FloatBinaryExprNode *ret = (FloatBinaryExprNode *) consoleAlloc(sizeof(FloatBinaryExprNode)); + constructInPlace(ret); + + ret->op = op; + ret->left = left; + ret->right = right; + + return ret; +} + +IntBinaryExprNode *IntBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right) +{ + IntBinaryExprNode *ret = (IntBinaryExprNode *) consoleAlloc(sizeof(IntBinaryExprNode)); + constructInPlace(ret); + + ret->op = op; + ret->left = left; + ret->right = right; + + return ret; +} + +StreqExprNode *StreqExprNode::alloc(ExprNode *left, ExprNode *right, bool eq) +{ + StreqExprNode *ret = (StreqExprNode *) consoleAlloc(sizeof(StreqExprNode)); + constructInPlace(ret); + ret->left = left; + ret->right = right; + ret->eq = eq; + + return ret; +} + +StrcatExprNode *StrcatExprNode::alloc(ExprNode *left, ExprNode *right, int appendChar) +{ + StrcatExprNode *ret = (StrcatExprNode *) consoleAlloc(sizeof(StrcatExprNode)); + constructInPlace(ret); + ret->left = left; + ret->right = right; + ret->appendChar = appendChar; + + return ret; +} + +CommaCatExprNode *CommaCatExprNode::alloc(ExprNode *left, ExprNode *right) +{ + CommaCatExprNode *ret = (CommaCatExprNode *) consoleAlloc(sizeof(CommaCatExprNode)); + constructInPlace(ret); + ret->left = left; + ret->right = right; + + return ret; +} + +IntUnaryExprNode *IntUnaryExprNode::alloc(S32 op, ExprNode *expr) +{ + IntUnaryExprNode *ret = (IntUnaryExprNode *) consoleAlloc(sizeof(IntUnaryExprNode)); + constructInPlace(ret); + ret->op = op; + ret->expr = expr; + return ret; +} + +FloatUnaryExprNode *FloatUnaryExprNode::alloc(S32 op, ExprNode *expr) +{ + FloatUnaryExprNode *ret = (FloatUnaryExprNode *) consoleAlloc(sizeof(FloatUnaryExprNode)); + constructInPlace(ret); + ret->op = op; + ret->expr = expr; + return ret; +} + +VarNode *VarNode::alloc(StringTableEntry varName, ExprNode *arrayIndex) +{ + VarNode *ret = (VarNode *) consoleAlloc(sizeof(VarNode)); + constructInPlace(ret); + ret->varName = varName; + ret->arrayIndex = arrayIndex; + return ret; +} + +IntNode *IntNode::alloc(S32 value) +{ + IntNode *ret = (IntNode *) consoleAlloc(sizeof(IntNode)); + constructInPlace(ret); + ret->value = value; + return ret; +} + +ConditionalExprNode *ConditionalExprNode::alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr) +{ + ConditionalExprNode *ret = (ConditionalExprNode *) consoleAlloc(sizeof(ConditionalExprNode)); + constructInPlace(ret); + ret->testExpr = testExpr; + ret->trueExpr = trueExpr; + ret->falseExpr = falseExpr; + ret->integer = false; + return ret; +} + +FloatNode *FloatNode::alloc(F64 value) +{ + FloatNode *ret = (FloatNode *) consoleAlloc(sizeof(FloatNode)); + constructInPlace(ret); + ret->value = value; + return ret; +} + +StrConstNode *StrConstNode::alloc(char *str, bool tag, bool doc) +{ + StrConstNode *ret = (StrConstNode *) consoleAlloc(sizeof(StrConstNode)); + constructInPlace(ret); + ret->str = (char *) consoleAlloc(dStrlen(str) + 1); + ret->tag = tag; + ret->doc = doc; + dStrcpy(ret->str, str); + + return ret; +} + +ConstantNode *ConstantNode::alloc(StringTableEntry value) +{ + ConstantNode *ret = (ConstantNode *) consoleAlloc(sizeof(ConstantNode)); + constructInPlace(ret); + ret->value = value; + return ret; +} + +AssignExprNode *AssignExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr) +{ + AssignExprNode *ret = (AssignExprNode *) consoleAlloc(sizeof(AssignExprNode)); + constructInPlace(ret); + ret->varName = varName; + ret->expr = expr; + ret->arrayIndex = arrayIndex; + + return ret; +} + +AssignOpExprNode *AssignOpExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op) +{ + AssignOpExprNode *ret = (AssignOpExprNode *) consoleAlloc(sizeof(AssignOpExprNode)); + constructInPlace(ret); + ret->varName = varName; + ret->expr = expr; + ret->arrayIndex = arrayIndex; + ret->op = op; + return ret; +} + +TTagSetStmtNode *TTagSetStmtNode::alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr) +{ + TTagSetStmtNode *ret = (TTagSetStmtNode *) consoleAlloc(sizeof(TTagSetStmtNode)); + constructInPlace(ret); + ret->tag = tag; + ret->valueExpr = valueExpr; + ret->stringExpr = stringExpr; + return ret; +} + +TTagDerefNode *TTagDerefNode::alloc(ExprNode *expr) +{ + TTagDerefNode *ret = (TTagDerefNode *) consoleAlloc(sizeof(TTagDerefNode)); + constructInPlace(ret); + ret->expr = expr; + return ret; +} + +TTagExprNode *TTagExprNode::alloc(StringTableEntry tag) +{ + TTagExprNode *ret = (TTagExprNode *) consoleAlloc(sizeof(TTagExprNode)); + constructInPlace(ret); + ret->tag = tag; + return ret; +} + +FuncCallExprNode *FuncCallExprNode::alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot) +{ + FuncCallExprNode *ret = (FuncCallExprNode *) consoleAlloc(sizeof(FuncCallExprNode)); + constructInPlace(ret); + ret->funcName = funcName; + ret->nameSpace = nameSpace; + ret->args = args; + if(dot) + ret->callType = MethodCall; + else + { + if(nameSpace && !dStricmp(nameSpace, "Parent")) + ret->callType = ParentCall; + else + ret->callType = FunctionCall; + } + return ret; +} + +AssertCallExprNode *AssertCallExprNode::alloc( ExprNode *testExpr, const char *message ) +{ + #ifdef TORQUE_ENABLE_SCRIPTASSERTS + + AssertCallExprNode *ret = (AssertCallExprNode *) consoleAlloc(sizeof(FuncCallExprNode)); + constructInPlace(ret); + ret->testExpr = testExpr; + ret->message = message ? message : "TorqueScript assert!"; + return ret; + + #else + + return NULL; + + #endif +} + +SlotAccessNode *SlotAccessNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName) +{ + SlotAccessNode *ret = (SlotAccessNode *) consoleAlloc(sizeof(SlotAccessNode)); + constructInPlace(ret); + ret->objectExpr = objectExpr; + ret->arrayExpr = arrayExpr; + ret->slotName = slotName; + return ret; +} + +InternalSlotAccessNode *InternalSlotAccessNode::alloc(ExprNode *objectExpr, ExprNode *slotExpr, bool recurse) +{ + InternalSlotAccessNode *ret = (InternalSlotAccessNode *) consoleAlloc(sizeof(InternalSlotAccessNode)); + constructInPlace(ret); + ret->objectExpr = objectExpr; + ret->slotExpr = slotExpr; + ret->recurse = recurse; + return ret; +} + +SlotAssignNode *SlotAssignNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr, U32 typeID /* = -1 */) +{ + SlotAssignNode *ret = (SlotAssignNode *) consoleAlloc(sizeof(SlotAssignNode)); + constructInPlace(ret); + ret->objectExpr = objectExpr; + ret->arrayExpr = arrayExpr; + ret->slotName = slotName; + ret->valueExpr = valueExpr; + ret->typeID = typeID; + return ret; +} + +SlotAssignOpNode *SlotAssignOpNode::alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr) +{ + SlotAssignOpNode *ret = (SlotAssignOpNode *) consoleAlloc(sizeof(SlotAssignOpNode)); + constructInPlace(ret); + ret->objectExpr = objectExpr; + ret->arrayExpr = arrayExpr; + ret->slotName = slotName; + ret->op = op; + ret->valueExpr = valueExpr; + return ret; +} + +ObjectDeclNode *ObjectDeclNode::alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool isDatablock, bool classNameInternal, bool isSingleton) +{ + ObjectDeclNode *ret = (ObjectDeclNode *) consoleAlloc(sizeof(ObjectDeclNode)); + constructInPlace(ret); + ret->classNameExpr = classNameExpr; + ret->objectNameExpr = objectNameExpr; + ret->argList = argList; + ret->slotDecls = slotDecls; + ret->subObjects = subObjects; + ret->isDatablock = isDatablock; + ret->isClassNameInternal = classNameInternal; + ret->isSingleton = isSingleton; + ret->failOffset = 0; + if(parentObject) + ret->parentObject = parentObject; + else + ret->parentObject = StringTable->insert(""); + return ret; +} + +FunctionDeclStmtNode *FunctionDeclStmtNode::alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts) +{ + FunctionDeclStmtNode *ret = (FunctionDeclStmtNode *) consoleAlloc(sizeof(FunctionDeclStmtNode)); + constructInPlace(ret); + ret->fnName = fnName; + ret->args = args; + ret->stmts = stmts; + ret->nameSpace = nameSpace; + ret->package = NULL; + return ret; +} diff --git a/console/astNodes.cpp b/console/astNodes.cpp new file mode 100644 index 0000000..7b22bf6 --- /dev/null +++ b/console/astNodes.cpp @@ -0,0 +1,1798 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/telnetDebugger.h" +#include "platform/event.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" + +#include "core/strings/findMatch.h" +#include "console/consoleInternal.h" +#include "core/stream/fileStream.h" +#include "console/compiler.h" + +#include "console/simBase.h" + + +namespace Compiler +{ + U32 precompileBlock(StmtNode *block, U32 loopCount) + { + U32 sum = 0; + for(StmtNode *walk = block; walk; walk = walk->getNext()) + { + const U32 temp = walk->precompileStmt(loopCount); +#ifdef DEBUG_AST_NODES + if(temp > 1000) + Con::printf("suspect %s '%s' line %d", walk->dbgStmtType().c_str(), walk->dbgFileName, walk->dbgLineNumber); +#endif + sum += temp; + } + return sum; + } + + U32 compileBlock(StmtNode *block, U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint) + { + for(StmtNode *walk = block; walk; walk = walk->getNext()) + ip = walk->compileStmt(codeStream, ip, continuePoint, breakPoint); + return ip; + } +} + +using namespace Compiler; + +//----------------------------------------------------------------------------- + +void StmtNode::addBreakCount() +{ + CodeBlock::smBreakLineCount++; +} + +void StmtNode::addBreakLine(U32 ip) +{ + U32 line = CodeBlock::smBreakLineCount * 2; + CodeBlock::smBreakLineCount++; + + if(getBreakCodeBlock()->lineBreakPairs) + { + getBreakCodeBlock()->lineBreakPairs[line] = dbgLineNumber; + getBreakCodeBlock()->lineBreakPairs[line+1] = ip; + } +} + +//------------------------------------------------------------ + +StmtNode::StmtNode() +{ + next = NULL; + dbgFileName = CodeBlock::smCurrentParser->getCurrentFile(); + dbgLineNumber = CodeBlock::smCurrentParser->getCurrentLine(); +} + +void StmtNode::setPackage(StringTableEntry) +{ +} + +void StmtNode::append(StmtNode *next) +{ + StmtNode *walk = this; + while(walk->next) + walk = walk->next; + walk->next = next; +} + + +void FunctionDeclStmtNode::setPackage(StringTableEntry packageName) +{ + package = packageName; +} + +//------------------------------------------------------------ +// +// Console language compilers +// +//------------------------------------------------------------ + +static U32 conversionOp(TypeReq src, TypeReq dst) +{ + if(src == TypeReqString) + { + switch(dst) + { + case TypeReqUInt: + return OP_STR_TO_UINT; + case TypeReqFloat: + return OP_STR_TO_FLT; + case TypeReqNone: + return OP_STR_TO_NONE; + default: + break; + } + } + else if(src == TypeReqFloat) + { + switch(dst) + { + case TypeReqUInt: + return OP_FLT_TO_UINT; + case TypeReqString: + return OP_FLT_TO_STR; + case TypeReqNone: + return OP_FLT_TO_NONE; + default: + break; + } + } + else if(src == TypeReqUInt) + { + switch(dst) + { + case TypeReqFloat: + return OP_UINT_TO_FLT; + case TypeReqString: + return OP_UINT_TO_STR; + case TypeReqNone: + return OP_UINT_TO_NONE; + default: + break; + } + } + return OP_INVALID; +} + +//------------------------------------------------------------ + +U32 BreakStmtNode::precompileStmt(U32 loopCount) +{ + if(loopCount) + { + addBreakCount(); + return 2; + } + Con::warnf(ConsoleLogEntry::General, "%s (%d): break outside of loop... ignoring.", dbgFileName, dbgLineNumber); + return 0; +} + +U32 BreakStmtNode::compileStmt(U32 *codeStream, U32 ip, U32, U32 breakPoint) +{ + if(breakPoint) + { + addBreakLine(ip); + codeStream[ip++] = OP_JMP; + codeStream[ip++] = breakPoint; + } + return ip; +} + +//------------------------------------------------------------ + +U32 ContinueStmtNode::precompileStmt(U32 loopCount) +{ + if(loopCount) + { + addBreakCount(); + return 2; + } + Con::warnf(ConsoleLogEntry::General, "%s (%d): continue outside of loop... ignoring.", dbgFileName, dbgLineNumber); + return 0; +} + +U32 ContinueStmtNode::compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32) +{ + if(continuePoint) + { + addBreakLine(ip); + codeStream[ip++] = OP_JMP; + codeStream[ip++] = continuePoint; + } + return ip; +} + +//------------------------------------------------------------ + +U32 ExprNode::precompileStmt(U32) +{ + addBreakCount(); + return precompile(TypeReqNone); +} + +U32 ExprNode::compileStmt(U32 *codeStream, U32 ip, U32, U32) +{ + addBreakLine(ip); + return compile(codeStream, ip, TypeReqNone); +} + +//------------------------------------------------------------ + +U32 ReturnStmtNode::precompileStmt(U32) +{ + addBreakCount(); + return 1 + (expr ? expr->precompile(TypeReqString) : 0); +} + +U32 ReturnStmtNode::compileStmt(U32 *codeStream, U32 ip, U32, U32) +{ + addBreakLine(ip); + if(!expr) + codeStream[ip++] = OP_RETURN_VOID; + else + { + ip = expr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_RETURN; + } + return ip; +} + +//------------------------------------------------------------ + +ExprNode *IfStmtNode::getSwitchOR(ExprNode *left, ExprNode *list, bool string) +{ + ExprNode *nextExpr = (ExprNode *) list->getNext(); + ExprNode *test; + if(string) + test = StreqExprNode::alloc(left, list, true); + else + test = IntBinaryExprNode::alloc(opEQ, left, list); + if(!nextExpr) + return test; + return IntBinaryExprNode::alloc(opOR, test, getSwitchOR(left, nextExpr, string)); +} + +void IfStmtNode::propagateSwitchExpr(ExprNode *left, bool string) +{ + testExpr = getSwitchOR(left, testExpr, string); + if(propagate && elseBlock) + ((IfStmtNode *) elseBlock)->propagateSwitchExpr(left, string); +} + +U32 IfStmtNode::precompileStmt(U32 loopCount) +{ + U32 exprSize; + addBreakCount(); + + if(testExpr->getPreferredType() == TypeReqUInt) + { + exprSize = testExpr->precompile(TypeReqUInt); + integer = true; + } + else + { + exprSize = testExpr->precompile(TypeReqFloat); + integer = false; + } + // next is the JMPIFNOT or JMPIFFNOT - size of 2 + U32 ifSize = precompileBlock(ifBlock, loopCount); + if(!elseBlock) + endifOffset = ifSize + 2 + exprSize; + else + { + elseOffset = exprSize + 2 + ifSize + 2; + U32 elseSize = precompileBlock(elseBlock, loopCount); + endifOffset = elseOffset + elseSize; + } + return endifOffset; +} + +U32 IfStmtNode::compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint) +{ + U32 start = ip; + addBreakLine(ip); + + ip = testExpr->compile(codeStream, ip, integer ? TypeReqUInt : TypeReqFloat); + codeStream[ip++] = integer ? OP_JMPIFNOT : OP_JMPIFFNOT; + + if(elseBlock) + { + codeStream[ip++] = start + elseOffset; + ip = compileBlock(ifBlock, codeStream, ip, continuePoint, breakPoint); + codeStream[ip++] = OP_JMP; + codeStream[ip++] = start + endifOffset; + ip = compileBlock(elseBlock, codeStream, ip, continuePoint, breakPoint); + } + else + { + codeStream[ip++] = start + endifOffset; + ip = compileBlock(ifBlock, codeStream, ip, continuePoint, breakPoint); + } + return ip; +} + +//------------------------------------------------------------ + +U32 LoopStmtNode::precompileStmt(U32 loopCount) +{ + U32 initSize = 0; + addBreakCount(); + + if(initExpr) + initSize = initExpr->precompile(TypeReqNone); + + U32 testSize; + + if(testExpr->getPreferredType() == TypeReqUInt) + { + integer = true; + testSize = testExpr->precompile(TypeReqUInt); + } + else + { + integer = false; + testSize = testExpr->precompile(TypeReqFloat); + } + + U32 blockSize = precompileBlock(loopBlock, loopCount + 1); + + U32 endLoopSize = 0; + if(endLoopExpr) + endLoopSize = endLoopExpr->precompile(TypeReqNone); + + // if it's a for loop or a while loop it goes: + // initExpr + // testExpr + // OP_JMPIFNOT to break point + // loopStartPoint: + // loopBlock + // continuePoint: + // endLoopExpr + // testExpr + // OP_JMPIF loopStartPoint + // breakPoint: + + // otherwise if it's a do ... while() it goes: + // initExpr + // loopStartPoint: + // loopBlock + // continuePoint: + // endLoopExpr + // testExpr + // OP_JMPIF loopStartPoint + // breakPoint: + + if(!isDoLoop) + { + loopBlockStartOffset = initSize + testSize + 2; + continueOffset = loopBlockStartOffset + blockSize; + breakOffset = continueOffset + endLoopSize + testSize + 2; + } + else + { + loopBlockStartOffset = initSize; + continueOffset = initSize + blockSize; + breakOffset = continueOffset + endLoopSize + testSize + 2; + } + return breakOffset; +} + +U32 LoopStmtNode::compileStmt(U32 *codeStream, U32 ip, U32, U32) +{ + addBreakLine(ip); + U32 start = ip; + if(initExpr) + ip = initExpr->compile(codeStream, ip, TypeReqNone); + + if(!isDoLoop) + { + ip = testExpr->compile(codeStream, ip, integer ? TypeReqUInt : TypeReqFloat); + codeStream[ip++] = integer ? OP_JMPIFNOT : OP_JMPIFFNOT; + codeStream[ip++] = start + breakOffset; + } + + // Compile internals of loop. + ip = compileBlock(loopBlock, codeStream, ip, start + continueOffset, start + breakOffset); + + if(endLoopExpr) + ip = endLoopExpr->compile(codeStream, ip, TypeReqNone); + + ip = testExpr->compile(codeStream, ip, integer ? TypeReqUInt : TypeReqFloat); + + codeStream[ip++] = integer ? OP_JMPIF : OP_JMPIFF; + codeStream[ip++] = start + loopBlockStartOffset; + + return ip; +} + +//------------------------------------------------------------ + +U32 ConditionalExprNode::precompile(TypeReq type) +{ + // code is testExpr + // JMPIFNOT falseStart + // trueExpr + // JMP end + // falseExpr + U32 exprSize; + + if(testExpr->getPreferredType() == TypeReqUInt) + { + exprSize = testExpr->precompile(TypeReqUInt); + integer = true; + } + else + { + exprSize = testExpr->precompile(TypeReqFloat); + integer = false; + } + return exprSize + + trueExpr->precompile(type) + + falseExpr->precompile(type) + 4; +} + +U32 ConditionalExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = testExpr->compile(codeStream, ip, integer ? TypeReqUInt : TypeReqFloat); + codeStream[ip++] = integer ? OP_JMPIFNOT : OP_JMPIFFNOT; + U32 jumpElseIp = ip++; + ip = trueExpr->compile(codeStream, ip, type); + codeStream[ip++] = OP_JMP; + U32 jumpEndIp = ip++; + codeStream[jumpElseIp] = ip; + ip = falseExpr->compile(codeStream, ip, type); + codeStream[jumpEndIp] = ip; + return ip; +} + +TypeReq ConditionalExprNode::getPreferredType() +{ + return trueExpr->getPreferredType(); +} + +//------------------------------------------------------------ + +U32 FloatBinaryExprNode::precompile(TypeReq type) +{ + U32 addSize = left->precompile(TypeReqFloat) + right->precompile(TypeReqFloat) + 1; + if(type != TypeReqFloat) + addSize++; + + return addSize; +} + +U32 FloatBinaryExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = right->compile(codeStream, ip, TypeReqFloat); + ip = left->compile(codeStream, ip, TypeReqFloat); + U32 operand = OP_INVALID; + switch(op) + { + case '+': + operand = OP_ADD; + break; + case '-': + operand = OP_SUB; + break; + case '/': + operand = OP_DIV; + break; + case '*': + operand = OP_MUL; + break; + } + codeStream[ip++] = operand; + if(type != TypeReqFloat) + codeStream[ip++] =conversionOp(TypeReqFloat, type); + return ip; +} + +TypeReq FloatBinaryExprNode::getPreferredType() +{ + return TypeReqFloat; +} + +//------------------------------------------------------------ + +void IntBinaryExprNode::getSubTypeOperand() +{ + subType = TypeReqUInt; + switch(op) + { + case '^': + operand = OP_XOR; + break; + case '%': + operand = OP_MOD; + break; + case '&': + operand = OP_BITAND; + break; + case '|': + operand = OP_BITOR; + break; + case '<': + operand = OP_CMPLT; + subType = TypeReqFloat; + break; + case '>': + operand = OP_CMPGR; + subType = TypeReqFloat; + break; + case opGE: + operand = OP_CMPGE; + subType = TypeReqFloat; + break; + case opLE: + operand = OP_CMPLE; + subType = TypeReqFloat; + break; + case opEQ: + operand = OP_CMPEQ; + subType = TypeReqFloat; + break; + case opNE: + operand = OP_CMPNE; + subType = TypeReqFloat; + break; + case opOR: + operand = OP_OR; + break; + case opAND: + operand = OP_AND; + break; + case opSHR: + operand = OP_SHR; + break; + case opSHL: + operand = OP_SHL; + break; + } +} + +U32 IntBinaryExprNode::precompile(TypeReq type) +{ + getSubTypeOperand(); + U32 addSize = left->precompile(subType) + right->precompile(subType) + 1; + if(operand == OP_OR || operand == OP_AND) + addSize++; + + if(type != TypeReqUInt) + addSize++; + + return addSize; +} + +U32 IntBinaryExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + if(operand == OP_OR || operand == OP_AND) + { + ip = left->compile(codeStream, ip, subType); + codeStream[ip++] = operand == OP_OR ? OP_JMPIF_NP : OP_JMPIFNOT_NP; + U32 jmpIp = ip++; + ip = right->compile(codeStream, ip, subType); + codeStream[jmpIp] = ip; + } + else + { + ip = right->compile(codeStream, ip, subType); + ip = left->compile(codeStream, ip, subType); + codeStream[ip++] = operand; + } + if(type != TypeReqUInt) + codeStream[ip++] =conversionOp(TypeReqUInt, type); + return ip; +} + +TypeReq IntBinaryExprNode::getPreferredType() +{ + return TypeReqUInt; +} + +//------------------------------------------------------------ + +U32 StreqExprNode::precompile(TypeReq type) +{ + // eval str left + // OP_ADVANCE_STR_NUL + // eval str right + // OP_COMPARE_STR + // optional conversion + U32 addSize = left->precompile(TypeReqString) + right->precompile(TypeReqString) + 2; + if(!eq) + addSize ++; + if(type != TypeReqUInt) + addSize ++; + return addSize; +} + +U32 StreqExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = left->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_ADVANCE_STR_NUL; + ip = right->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_COMPARE_STR; + if(!eq) + codeStream[ip++] = OP_NOT; + if(type != TypeReqUInt) + codeStream[ip++] = conversionOp(TypeReqUInt, type); + return ip; +} + +TypeReq StreqExprNode::getPreferredType() +{ + return TypeReqUInt; +} + +//------------------------------------------------------------ + +U32 StrcatExprNode::precompile(TypeReq type) +{ + U32 addSize = left->precompile(TypeReqString) + right->precompile(TypeReqString) + 2; + if(appendChar) + addSize++; + + if(type != TypeReqString) + addSize ++; + return addSize; +} + +U32 StrcatExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = left->compile(codeStream, ip, TypeReqString); + if(!appendChar) + codeStream[ip++] = OP_ADVANCE_STR; + else + { + codeStream[ip++] = OP_ADVANCE_STR_APPENDCHAR; + codeStream[ip++] = appendChar; + } + ip = right->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_REWIND_STR; + if(type == TypeReqUInt) + codeStream[ip++] = OP_STR_TO_UINT; + else if(type == TypeReqFloat) + codeStream[ip++] = OP_STR_TO_FLT; + return ip; +} + +TypeReq StrcatExprNode::getPreferredType() +{ + return TypeReqString; +} + +//------------------------------------------------------------ + +U32 CommaCatExprNode::precompile(TypeReq type) +{ + U32 addSize = left->precompile(TypeReqString) + right->precompile(TypeReqString) + 2; + if(type != TypeReqString) + addSize ++; + return addSize; +} + +U32 CommaCatExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = left->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_ADVANCE_STR_COMMA; + ip = right->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_REWIND_STR; + + // At this point the stack has the concatenated string. + + // But we're paranoid, so accept (but whine) if we get an oddity... + if(type == TypeReqUInt || type == TypeReqFloat) + Con::warnf(ConsoleLogEntry::General, "%s (%d): converting comma string to a number... probably wrong.", dbgFileName, dbgLineNumber); + if(type == TypeReqUInt) + codeStream[ip++] = OP_STR_TO_UINT; + else if(type == TypeReqFloat) + codeStream[ip++] = OP_STR_TO_FLT; + return ip; +} + +TypeReq CommaCatExprNode::getPreferredType() +{ + return TypeReqString; +} + +//------------------------------------------------------------ + +U32 IntUnaryExprNode::precompile(TypeReq type) +{ + integer = true; + TypeReq prefType = expr->getPreferredType(); + if(op == '!' && (prefType == TypeReqFloat || prefType == TypeReqString)) + integer = false; + + U32 exprSize = expr->precompile(integer ? TypeReqUInt : TypeReqFloat); + if(type != TypeReqUInt) + return exprSize + 2; + else + return exprSize + 1; +} + +U32 IntUnaryExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = expr->compile(codeStream, ip, integer ? TypeReqUInt : TypeReqFloat); + if(op == '!') + codeStream[ip++] = integer ? OP_NOT : OP_NOTF; + else if(op == '~') + codeStream[ip++] = OP_ONESCOMPLEMENT; + if(type != TypeReqUInt) + codeStream[ip++] =conversionOp(TypeReqUInt, type); + return ip; +} + +TypeReq IntUnaryExprNode::getPreferredType() +{ + return TypeReqUInt; +} + +//------------------------------------------------------------ + +U32 FloatUnaryExprNode::precompile(TypeReq type) +{ + U32 exprSize = expr->precompile(TypeReqFloat); + if(type != TypeReqFloat) + return exprSize + 2; + else + return exprSize + 1; +} + +U32 FloatUnaryExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = expr->compile(codeStream, ip, TypeReqFloat); + codeStream[ip++] = OP_NEG; + if(type != TypeReqFloat) + codeStream[ip++] =conversionOp(TypeReqFloat, type); + return ip; +} + +TypeReq FloatUnaryExprNode::getPreferredType() +{ + return TypeReqFloat; +} + +//------------------------------------------------------------ + +U32 VarNode::precompile(TypeReq type) +{ + // if this has an arrayIndex... + // OP_LOADIMMED_IDENT + // varName + // OP_ADVANCE_STR + // evaluate arrayIndex TypeReqString + // OP_REWIND_STR + // OP_SETCURVAR_ARRAY + // OP_LOADVAR (type) + + // else + // OP_SETCURVAR + // varName + // OP_LOADVAR (type) + if(type == TypeReqNone) + return 0; + + precompileIdent(varName); + return (arrayIndex ? arrayIndex->precompile(TypeReqString) + 6 : 3); +} + +U32 VarNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + if(type == TypeReqNone) + return ip; + + codeStream[ip++] = arrayIndex ? OP_LOADIMMED_IDENT : OP_SETCURVAR; + codeStream[ip] = STEtoU32(varName, ip); + ip++; + if(arrayIndex) + { + codeStream[ip++] = OP_ADVANCE_STR; + ip = arrayIndex->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_REWIND_STR; + codeStream[ip++] = OP_SETCURVAR_ARRAY; + } + switch(type) + { + case TypeReqUInt: + codeStream[ip++] = OP_LOADVAR_UINT; + break; + case TypeReqFloat: + codeStream[ip++] = OP_LOADVAR_FLT; + break; + case TypeReqString: + codeStream[ip++] = OP_LOADVAR_STR; + break; + case TypeReqNone: + break; + } + return ip; +} + +TypeReq VarNode::getPreferredType() +{ + return TypeReqNone; // no preferred type +} + +//------------------------------------------------------------ + +U32 IntNode::precompile(TypeReq type) +{ + if(type == TypeReqNone) + return 0; + if(type == TypeReqString) + index = getCurrentStringTable()->addIntString(value); + else if(type == TypeReqFloat) + index = getCurrentFloatTable()->add(value); + return 2; +} + +U32 IntNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + switch(type) + { + case TypeReqUInt: + codeStream[ip++] = OP_LOADIMMED_UINT; + codeStream[ip++] = value; + break; + case TypeReqString: + codeStream[ip++] = OP_LOADIMMED_STR; + codeStream[ip++] = index; + break; + case TypeReqFloat: + codeStream[ip++] = OP_LOADIMMED_FLT; + codeStream[ip++] = index; + break; + case TypeReqNone: + break; + } + return ip; +} + +TypeReq IntNode::getPreferredType() +{ + return TypeReqUInt; +} + +//------------------------------------------------------------ + +U32 FloatNode::precompile(TypeReq type) +{ + if(type == TypeReqNone) + return 0; + if(type == TypeReqString) + index = getCurrentStringTable()->addFloatString(value); + else if(type == TypeReqFloat) + index = getCurrentFloatTable()->add(value); + return 2; +} + +U32 FloatNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + switch(type) + { + case TypeReqUInt: + codeStream[ip++] = OP_LOADIMMED_UINT; + codeStream[ip++] = U32(value); + break; + case TypeReqString: + codeStream[ip++] = OP_LOADIMMED_STR; + codeStream[ip++] = index; + break; + case TypeReqFloat: + codeStream[ip++] = OP_LOADIMMED_FLT; + codeStream[ip++] = index; + break; + case TypeReqNone: + break; + } + return ip; +} + +TypeReq FloatNode::getPreferredType() +{ + return TypeReqFloat; +} + +//------------------------------------------------------------ + +U32 StrConstNode::precompile(TypeReq type) +{ + // Early out for documentation block. + if( doc ) + { + index = getCurrentStringTable()->add(str, true, tag); + return 2; + } + + if(type == TypeReqString) + { + index = getCurrentStringTable()->add(str, true, tag); + return 2; + } + else if(type == TypeReqNone) + { + return 0; + } + + fVal = consoleStringToNumber(str, dbgFileName, dbgLineNumber); + if(type == TypeReqFloat) + index = getCurrentFloatTable()->add(fVal); + return 2; +} + +U32 StrConstNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + // If this is a DOCBLOCK, then process w/ appropriate op... + if( doc ) + { + codeStream[ip++] = OP_DOCBLOCK_STR; + codeStream[ip++] = index; + return ip; + } + + // Otherwise, deal with it normally as a string literal case. + switch(type) + { + case TypeReqString: + codeStream[ip++] = tag ? OP_TAG_TO_STR : OP_LOADIMMED_STR; + codeStream[ip++] = index; + break; + case TypeReqUInt: + codeStream[ip++] = OP_LOADIMMED_UINT; + codeStream[ip++] = U32(fVal); + break; + case TypeReqFloat: + codeStream[ip++] = OP_LOADIMMED_FLT; + codeStream[ip++] = index; + break; + case TypeReqNone: + break; + } + return ip; +} + +TypeReq StrConstNode::getPreferredType() +{ + return TypeReqString; +} + +//------------------------------------------------------------ + +U32 ConstantNode::precompile(TypeReq type) +{ + if(type == TypeReqString) + { + precompileIdent(value); + return 2; + } + else if(type == TypeReqNone) + return 0; + + fVal = consoleStringToNumber(value, dbgFileName, dbgLineNumber); + if(type == TypeReqFloat) + index = getCurrentFloatTable()->add(fVal); + return 2; +} + +U32 ConstantNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + switch(type) + { + case TypeReqString: + codeStream[ip++] = OP_LOADIMMED_IDENT; + codeStream[ip] = STEtoU32(value, ip); + ip++; + break; + case TypeReqUInt: + codeStream[ip++] = OP_LOADIMMED_UINT; + codeStream[ip++] = U32(fVal); + break; + case TypeReqFloat: + codeStream[ip++] = OP_LOADIMMED_FLT; + codeStream[ip++] = index; + break; + case TypeReqNone: + break; + } + return ip; +} + +TypeReq ConstantNode::getPreferredType() +{ + return TypeReqString; +} + +//------------------------------------------------------------ + +U32 AssignExprNode::precompile(TypeReq type) +{ + subType = expr->getPreferredType(); + if(subType == TypeReqNone) + subType = type; + if(subType == TypeReqNone) + subType = TypeReqString; + // if it's an array expr, the formula is: + // eval expr + // (push and pop if it's TypeReqString) OP_ADVANCE_STR + // OP_LOADIMMED_IDENT + // varName + // OP_ADVANCE_STR + // eval array + // OP_REWIND_STR + // OP_SETCURVAR_ARRAY_CREATE + // OP_TERMINATE_REWIND_STR + // OP_SAVEVAR + + //else + // eval expr + // OP_SETCURVAR_CREATE + // varname + // OP_SAVEVAR + const U32 addSize = (type != subType ? 1 : 0); + const U32 retSize = expr->precompile(subType); + +#ifdef DEBUG_AST_NODES + if(retSize > 1000) + Con::printf("Bad expr %s", expr->dbgStmtType().c_str()); +#endif + + precompileIdent(varName); + + return retSize + addSize + (arrayIndex ? arrayIndex->precompile(TypeReqString) + (subType == TypeReqString ? 8 : 6 ) : 3); +} + +U32 AssignExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = expr->compile(codeStream, ip, subType); + if(arrayIndex) + { + if(subType == TypeReqString) + codeStream[ip++] = OP_ADVANCE_STR; + + codeStream[ip++] = OP_LOADIMMED_IDENT; + codeStream[ip] = STEtoU32(varName, ip); + ip++; + codeStream[ip++] = OP_ADVANCE_STR; + ip = arrayIndex->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_REWIND_STR; + codeStream[ip++] = OP_SETCURVAR_ARRAY_CREATE; + if(subType == TypeReqString) + codeStream[ip++] = OP_TERMINATE_REWIND_STR; + } + else + { + codeStream[ip++] = OP_SETCURVAR_CREATE; + codeStream[ip] = STEtoU32(varName, ip); + ip++; + } + switch(subType) + { + case TypeReqString: + codeStream[ip++] = OP_SAVEVAR_STR; + break; + case TypeReqUInt: + codeStream[ip++] = OP_SAVEVAR_UINT; + break; + case TypeReqFloat: + codeStream[ip++] = OP_SAVEVAR_FLT; + break; + case TypeReqNone: + break; + } + if(type != subType) + codeStream[ip++] = conversionOp(subType, type); + return ip; +} + +TypeReq AssignExprNode::getPreferredType() +{ + return expr->getPreferredType(); +} + +//------------------------------------------------------------ + +static void getAssignOpTypeOp(S32 op, TypeReq &type, U32 &operand) +{ + switch(op) + { + case '+': + type = TypeReqFloat; + operand = OP_ADD; + break; + case '-': + type = TypeReqFloat; + operand = OP_SUB; + break; + case '*': + type = TypeReqFloat; + operand = OP_MUL; + break; + case '/': + type = TypeReqFloat; + operand = OP_DIV; + break; + case '%': + type = TypeReqUInt; + operand = OP_MOD; + break; + case '&': + type = TypeReqUInt; + operand = OP_BITAND; + break; + case '^': + type = TypeReqUInt; + operand = OP_XOR; + break; + case '|': + type = TypeReqUInt; + operand = OP_BITOR; + break; + case opSHL: + type = TypeReqUInt; + operand = OP_SHL; + break; + case opSHR: + type = TypeReqUInt; + operand = OP_SHR; + break; + } +} + +U32 AssignOpExprNode::precompile(TypeReq type) +{ + // goes like this... + // eval expr as float or int + // if there's an arrayIndex + + // OP_LOADIMMED_IDENT + // varName + // OP_ADVANCE_STR + // eval arrayIndex stringwise + // OP_REWIND_STR + // OP_SETCURVAR_ARRAY_CREATE + + // else + // OP_SETCURVAR_CREATE + // varName + + // OP_LOADVAR_FLT or UINT + // operand + // OP_SAVEVAR_FLT or UINT + + // conversion OP if necessary. + getAssignOpTypeOp(op, subType, operand); + precompileIdent(varName); + U32 size = expr->precompile(subType); + if(type != subType) + size++; + if(!arrayIndex) + return size + 5; + else + { + size += arrayIndex->precompile(TypeReqString); + return size + 8; + } +} + +U32 AssignOpExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = expr->compile(codeStream, ip, subType); + if(!arrayIndex) + { + codeStream[ip++] = OP_SETCURVAR_CREATE; + codeStream[ip] = STEtoU32(varName, ip); + ip++; + } + else + { + codeStream[ip++] = OP_LOADIMMED_IDENT; + codeStream[ip] = STEtoU32(varName, ip); + ip++; + codeStream[ip++] = OP_ADVANCE_STR; + ip = arrayIndex->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_REWIND_STR; + codeStream[ip++] = OP_SETCURVAR_ARRAY_CREATE; + } + codeStream[ip++] = (subType == TypeReqFloat) ? OP_LOADVAR_FLT : OP_LOADVAR_UINT; + codeStream[ip++] = operand; + codeStream[ip++] = (subType == TypeReqFloat) ? OP_SAVEVAR_FLT : OP_SAVEVAR_UINT; + if(subType != type) + codeStream[ip++] = conversionOp(subType, type); + return ip; +} + +TypeReq AssignOpExprNode::getPreferredType() +{ + getAssignOpTypeOp(op, subType, operand); + return subType; +} + +//------------------------------------------------------------ + +U32 TTagSetStmtNode::precompileStmt(U32 loopCount) +{ + TORQUE_UNUSED(loopCount); + return 0; +} + +U32 TTagSetStmtNode::compileStmt(U32*, U32 ip, U32, U32) +{ + return ip; +} + +//------------------------------------------------------------ + +U32 TTagDerefNode::precompile(TypeReq) +{ + return 0; +} + +U32 TTagDerefNode::compile(U32*, U32 ip, TypeReq) +{ + return ip; +} + +TypeReq TTagDerefNode::getPreferredType() +{ + return TypeReqNone; +} + +//------------------------------------------------------------ + +U32 TTagExprNode::precompile(TypeReq) +{ + return 0; +} + +U32 TTagExprNode::compile(U32*, U32 ip, TypeReq) +{ + return ip; +} + +TypeReq TTagExprNode::getPreferredType() +{ + return TypeReqNone; +} + +//------------------------------------------------------------ + +U32 FuncCallExprNode::precompile(TypeReq type) +{ + // OP_PUSH_FRAME + // arg OP_PUSH arg OP_PUSH arg OP_PUSH + // eval all the args, then call the function. + + // OP_CALLFUNC + // function + // namespace + // isDot + + U32 size = 0; + if(type != TypeReqString) + size++; + precompileIdent(funcName); + precompileIdent(nameSpace); + for(ExprNode *walk = args; walk; walk = (ExprNode *) walk->getNext()) + size += walk->precompile(TypeReqString) + 1; + return size + 5; +} + +U32 FuncCallExprNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + codeStream[ip++] = OP_PUSH_FRAME; + for(ExprNode *walk = args; walk; walk = (ExprNode *) walk->getNext()) + { + ip = walk->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_PUSH; + } + if(callType == MethodCall || callType == ParentCall) + codeStream[ip++] = OP_CALLFUNC; + else + codeStream[ip++] = OP_CALLFUNC_RESOLVE; + + codeStream[ip] = STEtoU32(funcName, ip); + ip++; + codeStream[ip] = STEtoU32(nameSpace, ip); + ip++; + codeStream[ip++] = callType; + if(type != TypeReqString) + codeStream[ip++] = conversionOp(TypeReqString, type); + return ip; +} + +TypeReq FuncCallExprNode::getPreferredType() +{ + return TypeReqString; +} + + +//------------------------------------------------------------ + +U32 AssertCallExprNode::precompile( TypeReq type ) +{ + #ifdef TORQUE_ENABLE_SCRIPTASSERTS + + messageIndex = getCurrentStringTable()->add( message, true, false ); + + U32 exprSize = testExpr->precompile(TypeReqUInt); + return exprSize + 2; + + #else + + return 0; + + #endif +} + +U32 AssertCallExprNode::compile( U32 *codeStream, U32 ip, TypeReq type ) +{ + #ifdef TORQUE_ENABLE_SCRIPTASSERTS + + ip = testExpr->compile( codeStream, ip, TypeReqUInt ); + codeStream[ip++] = OP_ASSERT; + codeStream[ip++] = messageIndex; + + #endif + + return ip; +} + +TypeReq AssertCallExprNode::getPreferredType() +{ + return TypeReqNone; +} + +//------------------------------------------------------------ + +U32 SlotAccessNode::precompile(TypeReq type) +{ + if(type == TypeReqNone) + return 0; + U32 size = 0; + precompileIdent(slotName); + if(arrayExpr) + { + // eval array + // OP_ADVANCE_STR + // evaluate object expression sub (OP_SETCURFIELD) + // OP_TERMINATE_REWIND_STR + // OP_SETCURFIELDARRAY + // total add of 4 + array precomp + size += 3 + arrayExpr->precompile(TypeReqString); + } + // eval object expression sub + 3 (op_setCurField + OP_SETCUROBJECT) + size += objectExpr->precompile(TypeReqString) + 3; + + // get field in desired type: + return size + 1; +} + +U32 SlotAccessNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + if(type == TypeReqNone) + return ip; + + if(arrayExpr) + { + ip = arrayExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_ADVANCE_STR; + } + ip = objectExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_SETCUROBJECT; + + codeStream[ip++] = OP_SETCURFIELD; + + codeStream[ip] = STEtoU32(slotName, ip); + ip++; + + if(arrayExpr) + { + codeStream[ip++] = OP_TERMINATE_REWIND_STR; + codeStream[ip++] = OP_SETCURFIELD_ARRAY; + } + + switch(type) + { + case TypeReqUInt: + codeStream[ip++] = OP_LOADFIELD_UINT; + break; + case TypeReqFloat: + codeStream[ip++] = OP_LOADFIELD_FLT; + break; + case TypeReqString: + codeStream[ip++] = OP_LOADFIELD_STR; + break; + case TypeReqNone: + break; + } + return ip; +} + +TypeReq SlotAccessNode::getPreferredType() +{ + return TypeReqNone; +} + +//----------------------------------------------------------------------------- + +U32 InternalSlotAccessNode::precompile(TypeReq type) +{ + if(type == TypeReqNone) + return 0; + + U32 size = 3; + + // eval object expression sub + 3 (op_setCurField + OP_SETCUROBJECT) + size += objectExpr->precompile(TypeReqString); + size += slotExpr->precompile(TypeReqString); + if(type != TypeReqUInt) + size++; + + // get field in desired type: + return size; +} + +U32 InternalSlotAccessNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + if(type == TypeReqNone) + return ip; + + ip = objectExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_SETCUROBJECT; + + ip = slotExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_SETCUROBJECT_INTERNAL; + codeStream[ip++] = recurse; + + if(type != TypeReqUInt) + codeStream[ip++] = conversionOp(TypeReqUInt, type); + return ip; +} + +TypeReq InternalSlotAccessNode::getPreferredType() +{ + return TypeReqUInt; +} + +//----------------------------------------------------------------------------- + +//------------------------------------------------------------ + +U32 SlotAssignNode::precompile(TypeReq type) +{ + // first eval the expression TypeReqString + + // if it's an array: + + // if OP_ADVANCE_STR 1 + // eval array + + // OP_ADVANCE_STR 1 + // evaluate object expr + // OP_SETCUROBJECT 1 + // OP_SETCURFIELD 1 + // fieldName 1 + // OP_TERMINATE_REWIND_STR 1 + + // OP_SETCURFIELDARRAY 1 + // OP_TERMINATE_REWIND_STR 1 + + // else + // OP_ADVANCE_STR + // evaluate object expr + // OP_SETCUROBJECT + // OP_SETCURFIELD + // fieldName + // OP_TERMINATE_REWIND_STR + + // OP_SAVEFIELD + // convert to return type if necessary. + + U32 size = 0; + if(type != TypeReqString) + size++; + + precompileIdent(slotName); + + size += valueExpr->precompile(TypeReqString); + + if(objectExpr) + size += objectExpr->precompile(TypeReqString) + 5; + else + size += 5; + + if(arrayExpr) + size += arrayExpr->precompile(TypeReqString) + 3; + + if(typeID != -1) + size += 2; + + return size + 1; +} + +U32 SlotAssignNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = valueExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_ADVANCE_STR; + if(arrayExpr) + { + ip = arrayExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_ADVANCE_STR; + } + if(objectExpr) + { + ip = objectExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_SETCUROBJECT; + } + else + codeStream[ip++] = OP_SETCUROBJECT_NEW; + codeStream[ip++] = OP_SETCURFIELD; + codeStream[ip] = STEtoU32(slotName, ip); + ip++; + if(arrayExpr) + { + codeStream[ip++] = OP_TERMINATE_REWIND_STR; + codeStream[ip++] = OP_SETCURFIELD_ARRAY; + } + + codeStream[ip++] = OP_TERMINATE_REWIND_STR; + codeStream[ip++] = OP_SAVEFIELD_STR; + + if(typeID != -1) + { + codeStream[ip++] = OP_SETCURFIELD_TYPE; + codeStream[ip++] = typeID; + } + + if(type != TypeReqString) + codeStream[ip++] = conversionOp(TypeReqString, type); + return ip; +} + +TypeReq SlotAssignNode::getPreferredType() +{ + return TypeReqString; +} + +//------------------------------------------------------------ + +U32 SlotAssignOpNode::precompile(TypeReq type) +{ + // first eval the expression as its type + + // if it's an array: + // eval array + // OP_ADVANCE_STR + // evaluate object expr + // OP_SETCUROBJECT + // OP_SETCURFIELD + // fieldName + // OP_TERMINATE_REWIND_STR + // OP_SETCURFIELDARRAY + + // else + // evaluate object expr + // OP_SETCUROBJECT + // OP_SETCURFIELD + // fieldName + + // OP_LOADFIELD of appropriate type + // operand + // OP_SAVEFIELD of appropriate type + // convert to return type if necessary. + + getAssignOpTypeOp(op, subType, operand); + precompileIdent(slotName); + U32 size = valueExpr->precompile(subType); + if(type != subType) + size++; + if(arrayExpr) + return size + 9 + arrayExpr->precompile(TypeReqString) + objectExpr->precompile(TypeReqString); + else + return size + 6 + objectExpr->precompile(TypeReqString); +} + +U32 SlotAssignOpNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + ip = valueExpr->compile(codeStream, ip, subType); + if(arrayExpr) + { + ip = arrayExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_ADVANCE_STR; + } + ip = objectExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_SETCUROBJECT; + codeStream[ip++] = OP_SETCURFIELD; + codeStream[ip] = STEtoU32(slotName, ip); + ip++; + if(arrayExpr) + { + codeStream[ip++] = OP_TERMINATE_REWIND_STR; + codeStream[ip++] = OP_SETCURFIELD_ARRAY; + } + codeStream[ip++] = (subType == TypeReqFloat) ? OP_LOADFIELD_FLT : OP_LOADFIELD_UINT; + codeStream[ip++] = operand; + codeStream[ip++] = (subType == TypeReqFloat) ? OP_SAVEFIELD_FLT : OP_SAVEFIELD_UINT; + if(subType != type) + codeStream[ip++] = conversionOp(subType, type); + return ip; +} + +TypeReq SlotAssignOpNode::getPreferredType() +{ + getAssignOpTypeOp(op, subType, operand); + return subType; +} + +//------------------------------------------------------------ + +U32 ObjectDeclNode::precompileSubObject(bool) +{ + // goes + + // OP_PUSHFRAME 1 + // name expr + // OP_PUSH 1 + // args... PUSH + // OP_CREATE_OBJECT 1 + // parentObject 1 + // isDatablock 1 + // internalName 1 + // isSingleton 1 + // lineNumber 1 + // fail point 1 + + // for each field, eval + // OP_ADD_OBJECT (to UINT[0]) 1 + // root? 1 + + // add all the sub objects. + // OP_END_OBJECT 1 + // root? 1 + // To fix the stack issue [7/9/2007 Black] + // OP_FINISH_OBJECT <-- fail point jumps to this opcode + + U32 argSize = 0; + precompileIdent(parentObject); + for(ExprNode *exprWalk = argList; exprWalk; exprWalk = (ExprNode *) exprWalk->getNext()) + argSize += exprWalk->precompile(TypeReqString) + 1; + argSize += classNameExpr->precompile(TypeReqString) + 1; + + U32 nameSize = objectNameExpr->precompile(TypeReqString) + 1; + + U32 slotSize = 0; + for(SlotAssignNode *slotWalk = slotDecls; slotWalk; slotWalk = (SlotAssignNode *) slotWalk->getNext()) + slotSize += slotWalk->precompile(TypeReqNone); + + // OP_ADD_OBJECT + U32 subObjSize = 0; + for(ObjectDeclNode *objectWalk = subObjects; objectWalk; objectWalk = (ObjectDeclNode *) objectWalk->getNext()) + subObjSize += objectWalk->precompileSubObject(false); + + failOffset = 12 + nameSize + argSize + slotSize + subObjSize; + // +1 because the failOffset should jump to OP_FINISH_OBJECT [7/9/2007 Black] + return failOffset + 1; +} + +U32 ObjectDeclNode::precompile(TypeReq type) +{ + // root object decl does: + + // push 0 onto the UINT stack OP_LOADIMMED_UINT + // precompiles the subObject(true) + // UINT stack now has object id + // type conv to type + + U32 ret = 2 + precompileSubObject(true); + if(type != TypeReqUInt) + return ret + 1; + return ret; +} + +U32 ObjectDeclNode::compileSubObject(U32 *codeStream, U32 ip, bool root) +{ + U32 start = ip; + codeStream[ip++] = OP_PUSH_FRAME; + ip = classNameExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_PUSH; + + ip = objectNameExpr->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_PUSH; + for(ExprNode *exprWalk = argList; exprWalk; exprWalk = (ExprNode *) exprWalk->getNext()) + { + ip = exprWalk->compile(codeStream, ip, TypeReqString); + codeStream[ip++] = OP_PUSH; + } + codeStream[ip++] = OP_CREATE_OBJECT; + codeStream[ip] = STEtoU32(parentObject, ip); + ip++; + codeStream[ip++] = isDatablock; + codeStream[ip++] = isClassNameInternal; + codeStream[ip++] = isSingleton; + codeStream[ip++] = dbgLineNumber; + codeStream[ip++] = start + failOffset; + for(SlotAssignNode *slotWalk = slotDecls; slotWalk; slotWalk = (SlotAssignNode *) slotWalk->getNext()) + ip = slotWalk->compile(codeStream, ip, TypeReqNone); + codeStream[ip++] = OP_ADD_OBJECT; + codeStream[ip++] = root; + for(ObjectDeclNode *objectWalk = subObjects; objectWalk; objectWalk = (ObjectDeclNode *) objectWalk->getNext()) + ip = objectWalk->compileSubObject(codeStream, ip, false); + codeStream[ip++] = OP_END_OBJECT; + codeStream[ip++] = root || isDatablock; + // Added to fix the object creation issue [7/9/2007 Black] + codeStream[ip++] = OP_FINISH_OBJECT; + return ip; +} + +U32 ObjectDeclNode::compile(U32 *codeStream, U32 ip, TypeReq type) +{ + codeStream[ip++] = OP_LOADIMMED_UINT; + codeStream[ip++] = 0; + ip = compileSubObject(codeStream, ip, true); + if(type != TypeReqUInt) + codeStream[ip++] = conversionOp(TypeReqUInt, type); + return ip; +} +TypeReq ObjectDeclNode::getPreferredType() +{ + return TypeReqUInt; +} + +//------------------------------------------------------------ + +U32 FunctionDeclStmtNode::precompileStmt(U32) +{ + // OP_FUNC_DECL + // func name + // namespace + // package + // func end ip + // argc + // ident array[argc] + // code + // OP_RETURN_VOID + setCurrentStringTable(&getFunctionStringTable()); + setCurrentFloatTable(&getFunctionFloatTable()); + + argc = 0; + for(VarNode *walk = args; walk; walk = (VarNode *)((StmtNode*)walk)->getNext()) + argc++; + + CodeBlock::smInFunction = true; + + precompileIdent(fnName); + precompileIdent(nameSpace); + precompileIdent(package); + + U32 subSize = precompileBlock(stmts, 0); + + addBreakCount(); + + CodeBlock::smInFunction = false; + + setCurrentStringTable(&getGlobalStringTable()); + setCurrentFloatTable(&getGlobalFloatTable()); + + endOffset = argc + subSize + 8; + return endOffset; +} + +U32 FunctionDeclStmtNode::compileStmt(U32 *codeStream, U32 ip, U32, U32) +{ + U32 start = ip; + codeStream[ip++] = OP_FUNC_DECL; + codeStream[ip] = STEtoU32(fnName, ip); + ip++; + codeStream[ip] = STEtoU32(nameSpace, ip); + ip++; + codeStream[ip] = STEtoU32(package, ip); + ip++; + codeStream[ip++] = bool(stmts != NULL); + codeStream[ip++] = start + endOffset; + codeStream[ip++] = argc; + for(VarNode *walk = args; walk; walk = (VarNode *)((StmtNode*)walk)->getNext()) + { + codeStream[ip] = STEtoU32(walk->varName, ip); + ip++; + } + CodeBlock::smInFunction = true; + ip = compileBlock(stmts, codeStream, ip, 0, 0); + + addBreakLine(ip); + + CodeBlock::smInFunction = false; + codeStream[ip++] = OP_RETURN_VOID; + return ip; +} diff --git a/console/bison.bat b/console/bison.bat new file mode 100644 index 0000000..2fcfe84 --- /dev/null +++ b/console/bison.bat @@ -0,0 +1,6 @@ +echo Changing to %4 ... +cd %4 +echo Generating %2 and %3 with prefix %1. +..\..\bin\bison\bison.exe -o %2 %3 --defines -p %1 +echo Renaming %2 to %5 . +move /Y %2 %5 \ No newline at end of file diff --git a/console/bison.simple b/console/bison.simple new file mode 100644 index 0000000..f45278f --- /dev/null +++ b/console/bison.simple @@ -0,0 +1,686 @@ +/* -*-C-*- Note some compilers choke on comments on `#line' lines. */ +#line 3 "bison.simple" + +/* Skeleton output parser for bison, + Copyright (C) 1984, 1989, 1990 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 2, 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +#ifndef alloca +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not GNU C. */ +#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi) +#include +#else /* not sparc */ +#if defined (MSDOS) && !defined (__TURBOC__) +#include +#else /* not MSDOS, or __TURBOC__ */ +#if defined(_AIX) +#include + #pragma alloca +#else /* not MSDOS, __TURBOC__, or _AIX */ +#ifdef __hpux +#ifdef __cplusplus +extern "C" { +void *alloca (unsigned int); +}; +#else /* not __cplusplus */ +void *alloca (); +#endif /* not __cplusplus */ +#endif /* __hpux */ +#endif /* not _AIX */ +#endif /* not MSDOS, or __TURBOC__ */ +#endif /* not sparc. */ +#endif /* not GNU C. */ +#endif /* alloca not defined. */ + +/* This is the parser code that is written into each bison parser + when the %semantic_parser declaration is not specified in the grammar. + It was written by Richard Stallman by simplifying the hairy parser + used when %semantic_parser is specified. */ + +/* Note: there must be only one dollar sign in this file. + It is replaced by the list of actions, each action + as one case of the switch. */ + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY -2 +#define YYEOF 0 +#define YYACCEPT return(0) +#define YYABORT return(1) +#define YYERROR goto yyerrlab1 +/* 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. */ +#define YYFAIL goto yyerrlab +#define YYRECOVERING() (!!yyerrstatus) +#define YYBACKUP(token, value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { yychar = (token), yylval = (value); \ + yychar1 = YYTRANSLATE (yychar); \ + YYPOPSTACK; \ + goto yybackup; \ + } \ + else \ + { yyerror ("syntax error: cannot back up"); YYERROR; } \ +while (0) + +#define YYTERROR 1 +#define YYERRCODE 256 + +#ifndef YYPURE +#define YYLEX yylex() +#endif + +#ifdef YYPURE +#ifdef YYLSP_NEEDED +#ifdef YYLEX_PARAM +#define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM) +#else +#define YYLEX yylex(&yylval, &yylloc) +#endif +#else /* not YYLSP_NEEDED */ +#ifdef YYLEX_PARAM +#define YYLEX yylex(&yylval, YYLEX_PARAM) +#else +#define YYLEX yylex(&yylval) +#endif +#endif /* not YYLSP_NEEDED */ +#endif + +/* If nonreentrant, generate the variables here */ + +#ifndef YYPURE + +int yychar; /* the lookahead symbol */ +YYSTYPE yylval; /* the semantic value of the */ + /* lookahead symbol */ + +#ifdef YYLSP_NEEDED +YYLTYPE yylloc; /* location data for the lookahead */ + /* symbol */ +#endif + +int yynerrs; /* number of parse errors so far */ +#endif /* not YYPURE */ + +#if YYDEBUG != 0 +int yydebug; /* nonzero means print parse trace */ +/* Since this is uninitialized, it does not stop multiple parsers + from coexisting. */ +#endif + +/* YYINITDEPTH indicates the initial size of the parser's stacks */ + +#ifndef YYINITDEPTH +#define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH is the maximum size the stacks can grow to + (effective only if the built-in stack extension method is used). */ + +#if YYMAXDEPTH == 0 +#undef YYMAXDEPTH +#endif + +#ifndef YYMAXDEPTH +#define YYMAXDEPTH 10000 +#endif + +/* Prevent warning if -Wstrict-prototypes. */ +#ifdef __GNUC__ +int yyparse (void); +#endif + +#if __GNUC__ > 1 /* GNU C and GNU C++ define this. */ +#define __yy_memcpy(FROM,TO,COUNT) __builtin_memcpy(TO,FROM,COUNT) +#else /* not GNU C or C++ */ +#ifndef __cplusplus + +/* This is the most reliable way to avoid incompatibilities + in available built-in functions on various systems. */ +static void +__yy_memcpy (from, to, count) + char *from; + char *to; + int count; +{ + register char *f = from; + register char *t = to; + register int i = count; + + while (i-- > 0) + *t++ = *f++; +} + +#else /* __cplusplus */ + +/* This is the most reliable way to avoid incompatibilities + in available built-in functions on various systems. */ +static void +__yy_memcpy (char *from, char *to, int count) +{ + register char *f = from; + register char *t = to; + register int i = count; + + while (i-- > 0) + *t++ = *f++; +} + +#endif +#endif + +#line 192 "bison.simple" + +/* The user can define YYPARSE_PARAM as the name of an argument to be passed + into yyparse. The argument should have type void *. + It should actually point to an object. + Grammar actions can access the variable by casting it + to the proper pointer type. */ + +#ifdef YYPARSE_PARAM +#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM; +#else +#define YYPARSE_PARAM +#define YYPARSE_PARAM_DECL +#endif + +int +yyparse(YYPARSE_PARAM) + YYPARSE_PARAM_DECL +{ + register int yystate; + register int yyn; + register short *yyssp; + register YYSTYPE *yyvsp; + int yyerrstatus; /* number of tokens to shift before error messages enabled */ + int yychar1 = 0; /* lookahead token as an internal (translated) token number */ + + short yyssa[YYINITDEPTH]; /* the state stack */ + YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */ + + short *yyss = yyssa; /* refer to the stacks thru separate pointers */ + YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */ + +#ifdef YYLSP_NEEDED + YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */ + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + +#define YYPOPSTACK (yyvsp--, yyssp--, yylsp--) +#else +#define YYPOPSTACK (yyvsp--, yyssp--) +#endif + + int yystacksize = YYINITDEPTH; + +#ifdef YYPURE + int yychar; + YYSTYPE yylval; + int yynerrs; +#ifdef YYLSP_NEEDED + YYLTYPE yylloc; +#endif +#endif + + YYSTYPE yyval; /* the variable used to return */ + /* semantic values from the action */ + /* routines */ + + int yylen; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Starting parse\n"); +#endif + + 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 - 1; + yyvsp = yyvs; +#ifdef YYLSP_NEEDED + yylsp = yyls; +#endif + +/* Push a new state, which is found in yystate . */ +/* In all cases, when you get here, the value and location stacks + have just been pushed. so pushing a state here evens the stacks. */ +yynewstate: + + *++yyssp = yystate; + + if (yyssp >= yyss + yystacksize - 1) + { + /* 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; + short *yyss1 = yyss; +#ifdef YYLSP_NEEDED + YYLTYPE *yyls1 = yyls; +#endif + + /* Get the current used size of the three stacks, in elements. */ + int size = yyssp - yyss + 1; + +#ifdef yyoverflow + /* Each stack pointer address is followed by the size of + the data in use in that stack, in bytes. */ +#ifdef YYLSP_NEEDED + /* This used to be a conditional around just the two extra args, + but that might be undefined if yyoverflow is a macro. */ + yyoverflow("parser stack overflow", + &yyss1, size * sizeof (*yyssp), + &yyvs1, size * sizeof (*yyvsp), + &yyls1, size * sizeof (*yylsp), + &yystacksize); +#else + yyoverflow("parser stack overflow", + &yyss1, size * sizeof (*yyssp), + &yyvs1, size * sizeof (*yyvsp), + &yystacksize); +#endif + + yyss = yyss1; yyvs = yyvs1; +#ifdef YYLSP_NEEDED + yyls = yyls1; +#endif +#else /* no yyoverflow */ + /* Extend the stack our own way. */ + if (yystacksize >= YYMAXDEPTH) + { + yyerror("parser stack overflow"); + return 2; + } + yystacksize *= 2; + if (yystacksize > YYMAXDEPTH) + yystacksize = YYMAXDEPTH; + yyss = (short *) alloca (yystacksize * sizeof (*yyssp)); + __yy_memcpy ((char *)yyss1, (char *)yyss, size * sizeof (*yyssp)); + yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp)); + __yy_memcpy ((char *)yyvs1, (char *)yyvs, size * sizeof (*yyvsp)); +#ifdef YYLSP_NEEDED + yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp)); + __yy_memcpy ((char *)yyls1, (char *)yyls, size * sizeof (*yylsp)); +#endif +#endif /* no yyoverflow */ + + yyssp = yyss + size - 1; + yyvsp = yyvs + size - 1; +#ifdef YYLSP_NEEDED + yylsp = yyls + size - 1; +#endif + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Stack size increased to %d\n", yystacksize); +#endif + + if (yyssp >= yyss + yystacksize - 1) + YYABORT; + } + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Entering state %d\n", yystate); +#endif + + goto yybackup; + yybackup: + +/* Do appropriate processing given the current state. */ +/* Read a lookahead token if we need one and don't already have one. */ +/* yyresume: */ + + /* First try to decide what to do without reference to lookahead token. */ + + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* yychar is either YYEMPTY or YYEOF + or a valid token in external form. */ + + if (yychar == YYEMPTY) + { +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Reading a token: "); +#endif + yychar = YYLEX; + } + + /* Convert token to internal form (in yychar1) for indexing tables with */ + + if (yychar <= 0) /* This means end of input. */ + { + yychar1 = 0; + yychar = YYEOF; /* Don't call YYLEX any more */ + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Now at end of input.\n"); +#endif + } + else + { + yychar1 = YYTRANSLATE(yychar); + +#if YYDEBUG != 0 + if (yydebug) + { + fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]); + /* Give the individual parser a way to print the precise meaning + of a token, for further debugging info. */ +#ifdef YYPRINT + YYPRINT (stderr, yychar, yylval); +#endif + fprintf (stderr, ")\n"); + } +#endif + } + + yyn += yychar1; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1) + goto yydefault; + + yyn = yytable[yyn]; + + /* yyn is what to do for this token type in this state. + Negative => reduce, -yyn is rule number. + Positive => shift, yyn is new state. + New state is final state => don't bother to shift, + just return success. + 0, or most negative number => error. */ + + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrlab; + + if (yyn == YYFINAL) + YYACCEPT; + + /* Shift the lookahead token. */ + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]); +#endif + + /* Discard the token being shifted unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + *++yyvsp = yylval; +#ifdef YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + /* count tokens shifted since error; after three, turn off error status. */ + if (yyerrstatus) yyerrstatus--; + + yystate = yyn; + goto yynewstate; + +/* Do the default action for the current state. */ +yydefault: + + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + +/* Do a reduction. yyn is the number of a rule to reduce with. */ +yyreduce: + yylen = yyr2[yyn]; + if (yylen > 0) + yyval = yyvsp[1-yylen]; /* implement default value of the action */ + +#if YYDEBUG != 0 + if (yydebug) + { + int i; + + fprintf (stderr, "Reducing via rule %d (line %d), ", + yyn, yyrline[yyn]); + + /* Print the symbols being reduced, and their result. */ + for (i = yyprhs[yyn]; yyrhs[i] > 0; i++) + fprintf (stderr, "%s ", yytname[yyrhs[i]]); + fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]); + } +#endif + +$ /* the action file gets copied in in place of this dollarsign */ +#line 487 "bison.simple" + + yyvsp -= yylen; + yyssp -= yylen; +#ifdef YYLSP_NEEDED + yylsp -= yylen; +#endif + +#if YYDEBUG != 0 + if (yydebug) + { + short *ssp1 = yyss - 1; + fprintf (stderr, "state stack now"); + while (ssp1 != yyssp) + fprintf (stderr, " %d", *++ssp1); + fprintf (stderr, "\n"); + } +#endif + + *++yyvsp = yyval; + +#ifdef YYLSP_NEEDED + yylsp++; + if (yylen == 0) + { + yylsp->first_line = yylloc.first_line; + yylsp->first_column = yylloc.first_column; + yylsp->last_line = (yylsp-1)->last_line; + yylsp->last_column = (yylsp-1)->last_column; + yylsp->text = 0; + } + else + { + yylsp->last_line = (yylsp+yylen-1)->last_line; + yylsp->last_column = (yylsp+yylen-1)->last_column; + } +#endif + + /* 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 - YYNTBASE] + *yyssp; + if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTBASE]; + + goto yynewstate; + +yyerrlab: /* here on detecting error */ + + if (! yyerrstatus) + /* If not already recovering from an error, report this error. */ + { + ++yynerrs; + +#ifdef YYERROR_VERBOSE + yyn = yypact[yystate]; + + if (yyn > YYFLAG && yyn < YYLAST) + { + int size = 0; + char *msg; + int x, count; + + count = 0; + /* Start X at -yyn if nec to avoid negative indexes in yycheck. */ + for (x = (yyn < 0 ? -yyn : 0); + x < (sizeof(yytname) / sizeof(char *)); x++) + if (yycheck[x + yyn] == x) + size += strlen(yytname[x]) + 15, count++; + msg = (char *) malloc(size + 15); + if (msg != 0) + { + strcpy(msg, "parse error"); + + if (count < 5) + { + count = 0; + for (x = (yyn < 0 ? -yyn : 0); + x < (sizeof(yytname) / sizeof(char *)); x++) + if (yycheck[x + yyn] == x) + { + strcat(msg, count == 0 ? ", expecting `" : " or `"); + strcat(msg, yytname[x]); + strcat(msg, "'"); + count++; + } + } + yyerror(msg); + free(msg); + } + else + yyerror ("parse error; also virtual memory exceeded"); + } + else +#endif /* YYERROR_VERBOSE */ + yyerror("parse error"); + } + + goto yyerrlab1; +yyerrlab1: /* here on error raised explicitly by an action */ + + if (yyerrstatus == 3) + { + /* if just tried and failed to reuse lookahead token after an error, discard it. */ + + /* return failure if at end of input */ + if (yychar == YYEOF) + YYABORT; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]); +#endif + + yychar = YYEMPTY; + } + + /* Else will try to reuse lookahead token + after shifting the error token. */ + + yyerrstatus = 3; /* Each real token shifted decrements this */ + + goto yyerrhandle; + +yyerrdefault: /* current state does not do anything special for the error token. */ + +#if 0 + /* This is wrong; only states that explicitly want error tokens + should shift them. */ + yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/ + if (yyn) goto yydefault; +#endif + +yyerrpop: /* pop the current state because it cannot handle the error token */ + + if (yyssp == yyss) YYABORT; + yyvsp--; + yystate = *--yyssp; +#ifdef YYLSP_NEEDED + yylsp--; +#endif + +#if YYDEBUG != 0 + if (yydebug) + { + short *ssp1 = yyss - 1; + fprintf (stderr, "Error: state stack now"); + while (ssp1 != yyssp) + fprintf (stderr, " %d", *++ssp1); + fprintf (stderr, "\n"); + } +#endif + +yyerrhandle: + + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yyerrdefault; + + yyn += YYTERROR; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR) + goto yyerrdefault; + + yyn = yytable[yyn]; + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrpop; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrpop; + + if (yyn == YYFINAL) + YYACCEPT; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Shifting error token, "); +#endif + + *++yyvsp = yylval; +#ifdef YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + yystate = yyn; + goto yynewstate; +} diff --git a/console/cmdgram.cpp b/console/cmdgram.cpp new file mode 100644 index 0000000..56b2f21 --- /dev/null +++ b/console/cmdgram.cpp @@ -0,0 +1,2318 @@ + +/* A Bison parser, made from cmdgram.y with Bison version GNU Bison version 1.24 + */ + +#define YYBISON 1 /* Identify Bison output. */ + +#define yyparse CMDparse +#define yylex CMDlex +#define yyerror CMDerror +#define yylval CMDlval +#define yychar CMDchar +#define yydebug CMDdebug +#define yynerrs CMDnerrs +#define rwDEFINE 258 +#define rwENDDEF 259 +#define rwDECLARE 260 +#define rwDECLARESINGLETON 261 +#define rwBREAK 262 +#define rwELSE 263 +#define rwCONTINUE 264 +#define rwGLOBAL 265 +#define rwIF 266 +#define rwNIL 267 +#define rwRETURN 268 +#define rwWHILE 269 +#define rwDO 270 +#define rwENDIF 271 +#define rwENDWHILE 272 +#define rwENDFOR 273 +#define rwDEFAULT 274 +#define rwFOR 275 +#define rwDATABLOCK 276 +#define rwSWITCH 277 +#define rwCASE 278 +#define rwSWITCHSTR 279 +#define rwCASEOR 280 +#define rwPACKAGE 281 +#define rwNAMESPACE 282 +#define rwCLASS 283 +#define rwASSERT 284 +#define ILLEGAL_TOKEN 285 +#define CHRCONST 286 +#define INTCONST 287 +#define TTAG 288 +#define VAR 289 +#define IDENT 290 +#define TYPE 291 +#define DOCBLOCK 292 +#define STRATOM 293 +#define TAGATOM 294 +#define FLTCONST 295 +#define opINTNAME 296 +#define opINTNAMER 297 +#define opMINUSMINUS 298 +#define opPLUSPLUS 299 +#define STMT_SEP 300 +#define opSHL 301 +#define opSHR 302 +#define opPLASN 303 +#define opMIASN 304 +#define opMLASN 305 +#define opDVASN 306 +#define opMODASN 307 +#define opANDASN 308 +#define opXORASN 309 +#define opORASN 310 +#define opSLASN 311 +#define opSRASN 312 +#define opCAT 313 +#define opEQ 314 +#define opNE 315 +#define opGE 316 +#define opLE 317 +#define opAND 318 +#define opOR 319 +#define opSTREQ 320 +#define opCOLONCOLON 321 +#define opMDASN 322 +#define opNDASN 323 +#define opNTASN 324 +#define opSTRNE 325 +#define UNARY 326 + +#line 1 "cmdgram.y" + + +// Make sure we don't get gram.h twice. +#define _CMDGRAM_H_ + +#include +#include +#include "console/console.h" +#include "console/compiler.h" +#include "console/consoleInternal.h" + +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif + +#define YYSSIZE 350 + +int outtext(char *fmt, ...); +extern int serrors; + +#define nil 0 +#undef YY_ARGS +#define YY_ARGS(x) x + +int CMDlex(); +void CMDerror(char *, ...); + +#ifdef alloca +#undef alloca +#endif +#define alloca dMalloc + +#line 34 "cmdgram.y" + + /* Reserved Word Definitions */ +#line 45 "cmdgram.y" + + /* Constants and Identifier Definitions */ +#line 59 "cmdgram.y" + + /* Operator Definitions */ + +#line 72 "cmdgram.y" +typedef union { + char c; + int i; + const char * s; + char * str; + double f; + StmtNode * stmt; + ExprNode * expr; + SlotAssignNode * slist; + VarNode * var; + SlotDecl slot; + InternalSlotDecl intslot; + ObjectBlockDecl odcl; + ObjectDeclNode * od; + AssignDecl asn; + IfStmtNode * ifnode; +} YYSTYPE; + +#ifndef YYLTYPE +typedef + struct yyltype + { + int timestamp; + int first_line; + int first_column; + int last_line; + int last_column; + char *text; + } + yyltype; + +#define YYLTYPE yyltype +#endif + +#include + +#ifndef __cplusplus +#ifndef __STDC__ +#define const +#endif +#endif + + + +#define YYFINAL 364 +#define YYFLAG -32768 +#define YYNTBASE 97 + +#define YYTRANSLATE(x) ((unsigned)(x) <= 326 ? yytranslate[x] : 135) + +static const char 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, 61, 2, 2, 2, 51, 50, 2, 52, + 53, 43, 41, 54, 42, 48, 44, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 55, 56, 45, + 47, 46, 93, 62, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 89, 2, 96, 59, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 57, 49, 58, 60, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 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, 35, + 36, 37, 38, 39, 40, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 90, 91, 92, 94, 95 +}; + +#if YYDEBUG != 0 +static const short yyprhs[] = { 0, + 0, 2, 3, 6, 8, 10, 12, 19, 21, 24, + 25, 28, 30, 32, 34, 36, 38, 41, 44, 47, + 51, 54, 59, 66, 68, 77, 88, 89, 91, 93, + 97, 108, 119, 127, 140, 150, 161, 169, 170, 173, + 174, 176, 177, 180, 181, 183, 185, 188, 191, 195, + 199, 201, 209, 217, 222, 230, 236, 238, 242, 248, + 256, 262, 269, 279, 288, 297, 305, 314, 322, 330, + 337, 339, 341, 345, 349, 353, 357, 361, 365, 369, + 373, 377, 380, 383, 385, 391, 395, 399, 403, 407, + 411, 415, 419, 423, 427, 431, 435, 439, 443, 446, + 449, 451, 453, 455, 457, 459, 461, 463, 465, 467, + 472, 476, 483, 487, 491, 493, 497, 499, 501, 504, + 507, 510, 513, 516, 519, 522, 525, 528, 531, 533, + 535, 537, 541, 548, 551, 557, 560, 564, 570, 575, + 582, 589, 594, 601, 602, 604, 606, 610, 612, 615, + 620, 626, 631, 639, 648, 650 +}; + +static const short yyrhs[] = { 98, + 0, 0, 98, 99, 0, 103, 0, 104, 0, 100, + 0, 26, 35, 57, 101, 58, 56, 0, 104, 0, + 101, 104, 0, 0, 102, 103, 0, 118, 0, 119, + 0, 120, 0, 107, 0, 115, 0, 7, 56, 0, + 9, 56, 0, 13, 56, 0, 13, 122, 56, 0, + 121, 56, 0, 33, 47, 122, 56, 0, 33, 47, + 122, 54, 122, 56, 0, 37, 0, 3, 35, 52, + 105, 53, 57, 102, 58, 0, 3, 35, 88, 35, + 52, 105, 53, 57, 102, 58, 0, 0, 106, 0, + 34, 0, 106, 54, 34, 0, 21, 35, 52, 35, + 109, 53, 57, 132, 58, 56, 0, 5, 125, 52, + 110, 109, 111, 53, 57, 112, 58, 0, 5, 125, + 52, 110, 109, 111, 53, 0, 5, 125, 52, 89, + 110, 96, 109, 111, 53, 57, 112, 58, 0, 5, + 125, 52, 89, 110, 96, 109, 111, 53, 0, 6, + 125, 52, 110, 109, 111, 53, 57, 112, 58, 0, + 6, 125, 52, 110, 109, 111, 53, 0, 0, 55, + 35, 0, 0, 122, 0, 0, 54, 131, 0, 0, + 132, 0, 113, 0, 132, 113, 0, 108, 56, 0, + 113, 108, 56, 0, 57, 102, 58, 0, 103, 0, + 22, 52, 122, 53, 57, 116, 58, 0, 24, 52, + 122, 53, 57, 116, 58, 0, 23, 117, 55, 102, + 0, 23, 117, 55, 102, 19, 55, 102, 0, 23, + 117, 55, 102, 116, 0, 122, 0, 117, 25, 122, + 0, 11, 52, 122, 53, 114, 0, 11, 52, 122, + 53, 114, 8, 114, 0, 14, 52, 122, 53, 114, + 0, 15, 114, 14, 52, 122, 53, 0, 20, 52, + 122, 56, 122, 56, 122, 53, 114, 0, 20, 52, + 122, 56, 122, 56, 53, 114, 0, 20, 52, 122, + 56, 56, 122, 53, 114, 0, 20, 52, 122, 56, + 56, 53, 114, 0, 20, 52, 56, 122, 56, 122, + 53, 114, 0, 20, 52, 56, 122, 56, 53, 114, + 0, 20, 52, 56, 56, 122, 53, 114, 0, 20, + 52, 56, 56, 53, 114, 0, 127, 0, 127, 0, + 52, 122, 53, 0, 122, 59, 122, 0, 122, 51, + 122, 0, 122, 50, 122, 0, 122, 49, 122, 0, + 122, 41, 122, 0, 122, 42, 122, 0, 122, 43, + 122, 0, 122, 44, 122, 0, 42, 122, 0, 43, + 122, 0, 33, 0, 122, 93, 122, 55, 122, 0, + 122, 45, 122, 0, 122, 46, 122, 0, 122, 83, + 122, 0, 122, 84, 122, 0, 122, 81, 122, 0, + 122, 82, 122, 0, 122, 86, 122, 0, 122, 68, + 122, 0, 122, 69, 122, 0, 122, 85, 122, 0, + 122, 87, 122, 0, 122, 94, 122, 0, 122, 62, + 122, 0, 61, 122, 0, 60, 122, 0, 39, 0, + 40, 0, 32, 0, 7, 0, 123, 0, 124, 0, + 35, 0, 38, 0, 34, 0, 34, 89, 134, 96, + 0, 122, 48, 35, 0, 122, 48, 35, 89, 134, + 96, 0, 122, 63, 125, 0, 122, 64, 125, 0, + 35, 0, 52, 122, 53, 0, 66, 0, 65, 0, + 70, 122, 0, 71, 122, 0, 72, 122, 0, 73, + 122, 0, 74, 122, 0, 75, 122, 0, 76, 122, + 0, 77, 122, 0, 78, 122, 0, 79, 122, 0, + 128, 0, 129, 0, 108, 0, 34, 47, 122, 0, + 34, 89, 134, 96, 47, 122, 0, 34, 126, 0, + 34, 89, 134, 96, 126, 0, 123, 126, 0, 123, + 47, 122, 0, 123, 47, 57, 131, 58, 0, 35, + 52, 130, 53, 0, 35, 88, 35, 52, 130, 53, + 0, 122, 48, 35, 52, 130, 53, 0, 29, 52, + 122, 53, 0, 29, 52, 122, 54, 38, 53, 0, + 0, 131, 0, 122, 0, 131, 54, 122, 0, 133, + 0, 132, 133, 0, 35, 47, 122, 56, 0, 36, + 35, 47, 122, 56, 0, 21, 47, 122, 56, 0, + 35, 89, 134, 96, 47, 122, 56, 0, 36, 35, + 89, 134, 96, 47, 122, 56, 0, 122, 0, 134, + 54, 122, 0 +}; + +#endif + +#if YYDEBUG != 0 +static const short yyrline[] = { 0, + 149, 154, 156, 161, 163, 165, 170, 175, 177, 182, + 184, 189, 190, 191, 192, 193, 194, 196, 198, 200, + 202, 204, 206, 208, 213, 215, 220, 222, 227, 229, + 234, 239, 241, 243, 245, 247, 249, 254, 256, 261, + 263, 268, 270, 275, 277, 279, 281, 286, 288, 293, + 295, 300, 302, 307, 309, 311, 316, 318, 323, 325, + 330, 332, 337, 339, 341, 343, 345, 347, 349, 351, + 356, 361, 363, 365, 367, 369, 371, 373, 375, 377, + 379, 381, 383, 385, 387, 389, 391, 393, 395, 397, + 399, 401, 403, 405, 407, 409, 411, 413, 415, 417, + 419, 421, 423, 425, 427, 429, 431, 433, 435, 437, + 442, 444, 449, 451, 456, 458, 463, 465, 467, 469, + 471, 473, 475, 477, 479, 481, 483, 485, 490, 492, + 494, 496, 498, 500, 502, 504, 506, 508, 513, 515, + 517, 522, 524, 529, 531, 536, 538, 543, 545, 550, + 552, 554, 556, 558, 563, 565 +}; + +static const char * const yytname[] = { "$","error","$undefined.","rwDEFINE", +"rwENDDEF","rwDECLARE","rwDECLARESINGLETON","rwBREAK","rwELSE","rwCONTINUE", +"rwGLOBAL","rwIF","rwNIL","rwRETURN","rwWHILE","rwDO","rwENDIF","rwENDWHILE", +"rwENDFOR","rwDEFAULT","rwFOR","rwDATABLOCK","rwSWITCH","rwCASE","rwSWITCHSTR", +"rwCASEOR","rwPACKAGE","rwNAMESPACE","rwCLASS","rwASSERT","ILLEGAL_TOKEN","CHRCONST", +"INTCONST","TTAG","VAR","IDENT","TYPE","DOCBLOCK","STRATOM","TAGATOM","FLTCONST", +"'+'","'-'","'*'","'/'","'<'","'>'","'='","'.'","'|'","'&'","'%'","'('","')'", +"','","':'","';'","'{'","'}'","'^'","'~'","'!'","'@'","opINTNAME","opINTNAMER", +"opMINUSMINUS","opPLUSPLUS","STMT_SEP","opSHL","opSHR","opPLASN","opMIASN","opMLASN", +"opDVASN","opMODASN","opANDASN","opXORASN","opORASN","opSLASN","opSRASN","opCAT", +"opEQ","opNE","opGE","opLE","opAND","opOR","opSTREQ","opCOLONCOLON","'['","opMDASN", +"opNDASN","opNTASN","'?'","opSTRNE","UNARY","']'","start","decl_list","decl", +"package_decl","fn_decl_list","statement_list","stmt","fn_decl_stmt","var_list_decl", +"var_list","datablock_decl","object_decl","parent_block","object_name","object_args", +"object_declare_block","object_decl_list","stmt_block","switch_stmt","case_block", +"case_expr","if_stmt","while_stmt","for_stmt","expression_stmt","expr","slot_acc", +"intslot_acc","class_name_expr","assign_op_struct","stmt_expr","funcall_expr", +"assert_expr","expr_list_decl","expr_list","slot_assign_list","slot_assign", +"aidx_expr","" +}; +#endif + +static const short yyr1[] = { 0, + 97, 98, 98, 99, 99, 99, 100, 101, 101, 102, + 102, 103, 103, 103, 103, 103, 103, 103, 103, 103, + 103, 103, 103, 103, 104, 104, 105, 105, 106, 106, + 107, 108, 108, 108, 108, 108, 108, 109, 109, 110, + 110, 111, 111, 112, 112, 112, 112, 113, 113, 114, + 114, 115, 115, 116, 116, 116, 117, 117, 118, 118, + 119, 119, 120, 120, 120, 120, 120, 120, 120, 120, + 121, 122, 122, 122, 122, 122, 122, 122, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, + 123, 123, 124, 124, 125, 125, 126, 126, 126, 126, + 126, 126, 126, 126, 126, 126, 126, 126, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, + 128, 129, 129, 130, 130, 131, 131, 132, 132, 133, + 133, 133, 133, 133, 134, 134 +}; + +static const short yyr2[] = { 0, + 1, 0, 2, 1, 1, 1, 6, 1, 2, 0, + 2, 1, 1, 1, 1, 1, 2, 2, 2, 3, + 2, 4, 6, 1, 8, 10, 0, 1, 1, 3, + 10, 10, 7, 12, 9, 10, 7, 0, 2, 0, + 1, 0, 2, 0, 1, 1, 2, 2, 3, 3, + 1, 7, 7, 4, 7, 5, 1, 3, 5, 7, + 5, 6, 9, 8, 8, 7, 8, 7, 7, 6, + 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 1, 5, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, + 3, 6, 3, 3, 1, 3, 1, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 3, 6, 2, 5, 2, 3, 5, 4, 6, + 6, 4, 6, 0, 1, 1, 3, 1, 2, 4, + 5, 4, 7, 8, 1, 3 +}; + +static const short yydefact[] = { 2, + 1, 0, 0, 0, 104, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 103, 84, 109, 107, + 24, 108, 101, 102, 0, 0, 0, 0, 0, 3, + 6, 4, 5, 15, 131, 16, 12, 13, 14, 0, + 0, 105, 106, 72, 129, 130, 0, 115, 0, 0, + 0, 17, 18, 0, 104, 84, 19, 0, 72, 0, + 10, 51, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 118, 117, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 134, 144, 0, 82, 83, 0, + 100, 99, 21, 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, 136, + 27, 0, 0, 40, 40, 0, 20, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 132, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 155, + 0, 146, 0, 145, 0, 73, 78, 79, 80, 81, + 86, 87, 111, 77, 76, 75, 74, 98, 113, 114, + 93, 94, 90, 91, 88, 89, 95, 92, 96, 0, + 97, 0, 137, 29, 0, 28, 0, 116, 40, 38, + 41, 38, 0, 0, 50, 11, 0, 0, 0, 0, + 38, 0, 0, 0, 8, 142, 0, 0, 22, 0, + 110, 139, 0, 144, 144, 0, 0, 0, 0, 0, + 27, 0, 0, 42, 42, 59, 61, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, + 156, 0, 135, 147, 0, 0, 0, 85, 138, 10, + 30, 0, 38, 39, 0, 0, 0, 0, 62, 70, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 143, 23, 133, 140, 141, 112, 0, 0, 42, + 43, 33, 37, 60, 69, 68, 0, 66, 0, 0, + 0, 0, 0, 57, 52, 53, 25, 10, 0, 44, + 44, 67, 65, 64, 0, 0, 0, 0, 0, 148, + 0, 10, 0, 35, 0, 0, 46, 45, 0, 63, + 0, 0, 0, 0, 0, 149, 58, 54, 26, 44, + 48, 32, 0, 47, 36, 0, 0, 0, 0, 0, + 31, 0, 56, 0, 49, 152, 150, 0, 0, 0, + 10, 34, 0, 151, 0, 55, 0, 0, 153, 0, + 154, 0, 0, 0 +}; + +static const short yydefgoto[] = { 362, + 1, 30, 31, 204, 129, 62, 33, 185, 186, 34, + 35, 224, 190, 256, 316, 317, 63, 36, 269, 293, + 37, 38, 39, 40, 41, 42, 43, 50, 85, 59, + 45, 46, 153, 154, 318, 310, 151 +}; + +static const short yypact[] = {-32768, + 391, -11, -3, -3, -20, 21, 28, 699, 32, 477, + 35, -4, 36, 38, 57, 43,-32768, 51, 2526, -33, +-32768,-32768,-32768,-32768, 972, 972, 972, 972, 972,-32768, +-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768, 46, + 2273, 2546,-32768, 70,-32768,-32768, 1,-32768, 972, 53, + 78,-32768,-32768, 972,-32768,-32768,-32768, 1085,-32768, 972, +-32768,-32768, 123, 738, 92, 972, 972, 100, 972, 972, + 972,-32768,-32768, 972, 972, 972, 972, 972, 972, 972, + 972, 972, 972, 972,-32768, 972, 125, -25, -25, 1139, + -25, -25,-32768, 972, 972, 972, 972, 972, 972, 134, + 972, 972, 972, 972, 972, -3, -3, 972, 972, 972, + 972, 972, 972, 972, 972, 972, 972, 972, 172,-32768, + 133, 146, 1193, 302, 972, 1247,-32768, 1301, 534, 130, + 777, 1355, 152, 1409, 1463, 191, 977, 1031, 2273, 2273, + 2273, 2273, 2273, 2273, 2273, 2273, 2273, 2273, 2273, 2273, + -44, 2273, 144, 145, 148,-32768, 132, 132, -25, -25, + 122, 122, -8, 2414, 2472, -25, 2443, 2600,-32768,-32768, + 179, 179, 2501, 2501, 122, 122, 2385, 2356, 2600, 1517, + 2600, 972, 2273,-32768, 155, 159, 165,-32768, 972, 143, + 2273, 143, 477, 477,-32768,-32768, 972, 8, 1571, 816, + 143, 161, 168, 15,-32768,-32768, 193, 972,-32768, 972, + 2561,-32768, 972, 972, 972, 972, 972, -24, 182, 202, + 133, 150, 205, 187, 187, 236,-32768, 1625, 477, 1679, + 855, 894, 1733, 195, 226, 226, 199,-32768, 203, 1787, + 2273, 972,-32768, 2273, 206, 209, -42, 2327,-32768,-32768, +-32768, 211, 143,-32768, 972, 212, 213, 477,-32768,-32768, + 477, 477, 1841, 477, 1895, 933, 201, 972, 214, 216, +-32768,-32768,-32768, 2273,-32768,-32768,-32768, 576, 218, 187, + 145, 219, 223,-32768,-32768,-32768, 477,-32768, 477, 477, + 1949, 58, 2, 2273,-32768,-32768,-32768,-32768, 228, 153, + 153,-32768,-32768,-32768, 477, 235, -31, 248, 64,-32768, + 972,-32768, 618, 227, 229, 232, 23, 153, 233,-32768, + 972, 972, 972, -30, 231,-32768, 2273, 435,-32768, 153, +-32768,-32768, 237, 23,-32768, 2003, 2057, -29, 972, 972, +-32768, 239,-32768, 242,-32768,-32768,-32768, 250, 2111, -21, +-32768,-32768, 972,-32768, 254, 660, 2165, 972,-32768, 2219, +-32768, 295, 306,-32768 +}; + +static const short yypgoto[] = {-32768, +-32768,-32768,-32768,-32768, -242, 0, -130, 81,-32768,-32768, + 49, -181, -118, -204, -158, -7, 9,-32768, -232,-32768, +-32768,-32768,-32768,-32768, 37,-32768,-32768, 18, -40, -1, +-32768,-32768, -132, -177, 24, -283, -213 +}; + + +#define YYLAST 2669 + + +static const short yytable[] = { 44, + 32, 120, 247, 270, 218, 205, 192, 278, 44, 210, + 225, 210, 3, 4, 55, 322, 339, 2, 86, 234, + 257, 51, 100, 47, 210, 326, 311, 3, 4, 213, + 65, 48, 210, 249, 326, 52, 16, 106, 107, 17, + 56, 19, 20, 215, 58, 22, 23, 24, 49, 25, + 26, 211, 121, 277, 87, 313, 312, 323, 340, 27, + 229, 88, 89, 90, 91, 92, 348, 28, 29, 328, + 222, 280, 237, 238, 355, 299, 53, 281, 306, 54, + 216, 245, 246, 60, 306, 123, 64, 66, 122, 67, + 126, 68, 307, 308, 69, 343, 128, 70, 307, 308, + 132, 93, 134, 135, 124, 137, 138, 139, 356, 338, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 325, 152, 169, 170, -71, 350, 44, 196, 125, + 157, 158, 159, 160, 161, 162, 130, 164, 165, 166, + 167, 168, 319, 133, 171, 172, 173, 174, 175, 176, + 177, 178, 179, 180, 181, 183, 136, 3, 4, 155, + 191, 191, 94, 95, 96, 97, 184, 199, 163, 100, + 243, 344, 103, 306, 96, 97, 3, 4, 55, 100, + 187, 197, 103, 105, 106, 107, 201, 307, 308, 108, + 109, 44, 44, 2, 106, 107, 212, 223, 213, 214, + 16, 226, 227, 17, 56, 19, 20, 219, 116, 22, + 23, 24, 220, 25, 26, 118, 221, 235, 152, 94, + 95, 96, 97, 27, 236, 191, 100, 44, 182, 103, + 239, 28, 29, 228, 230, 251, 233, 260, 250, 254, + 255, 106, 107, 258, 240, 253, 241, 267, 268, 244, + 152, 152, 150, 248, 271, 272, 44, 292, 275, 44, + 44, 276, 44, 279, 282, 283, 284, 263, 265, 285, + 286, 295, 288, 296, 298, 300, 44, 196, 274, 301, + 314, 321, 324, 330, 331, 44, 341, 44, 44, 332, + 335, 152, 345, 351, 363, 302, 353, 303, 304, 352, + 358, 252, 291, 44, 294, 364, 3, 4, 55, 0, + 334, 44, 196, 320, 0, 309, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 44, 196, 0, 0, + 16, 0, 0, 17, 56, 19, 20, 0, 0, 22, + 23, 24, 0, 25, 26, 0, 0, 327, 315, 315, + 0, 0, 0, 27, 44, 196, 0, 336, 337, 150, + 0, 28, 29, 0, 0, 333, 315, 0, 0, 0, + 0, 0, 0, 0, 0, 349, 150, 0, 315, 0, + 0, 0, 333, 0, 0, 0, 0, 0, 0, 357, + 189, 0, 0, 2, 360, 3, 4, 5, 0, 6, + 0, 7, 0, 8, 9, 10, 0, 0, 0, 0, + 11, 12, 13, 0, 14, 0, 15, 0, 0, 16, + 0, 0, 17, 18, 19, 20, 0, 21, 22, 23, + 24, 0, 25, 26, 0, 0, 0, 0, 0, 3, + 4, 5, 27, 6, 0, 7, 0, 8, 9, 10, + 28, 29, 0, 342, 11, 12, 13, 268, 14, 0, + 0, 0, 0, 16, 0, 0, 17, 18, 19, 20, + 0, 21, 22, 23, 24, 0, 25, 26, 0, 0, + 0, 3, 4, 5, 0, 6, 27, 7, 0, 8, + 9, 10, 0, 0, 28, 29, 11, 12, 13, 0, + 14, 0, 0, 0, 0, 16, 0, 0, 17, 18, + 19, 20, 0, 21, 22, 23, 24, 0, 25, 26, + 0, 0, 0, 0, 0, 0, 0, 0, 27, 0, + 0, 0, 0, 61, 0, 0, 28, 29, 3, 4, + 5, 0, 6, 0, 7, 0, 8, 9, 10, 0, + 0, 0, 0, 11, 12, 13, 0, 14, 0, 0, + 0, 0, 16, 0, 0, 17, 18, 19, 20, 0, + 21, 22, 23, 24, 0, 25, 26, 0, 0, 0, + 3, 4, 5, 0, 6, 27, 7, 0, 8, 9, + 10, 195, 0, 28, 29, 11, 12, 13, 0, 14, + 0, 0, 0, 0, 16, 0, 0, 17, 18, 19, + 20, 0, 21, 22, 23, 24, 0, 25, 26, 0, + 0, 0, 3, 4, 5, 0, 6, 27, 7, 0, + 8, 9, 10, 297, 0, 28, 29, 11, 12, 13, + 0, 14, 0, 0, 0, 0, 16, 0, 0, 17, + 18, 19, 20, 0, 21, 22, 23, 24, 0, 25, + 26, 0, 0, 0, 3, 4, 5, 0, 6, 27, + 7, 0, 8, 9, 10, 329, 0, 28, 29, 11, + 12, 13, 0, 14, 0, 0, 0, 0, 16, 0, + 0, 17, 18, 19, 20, 0, 21, 22, 23, 24, + 0, 25, 26, 3, 4, 55, 0, 0, 0, 0, + 0, 27, 0, 0, 0, 0, 0, 0, 0, 28, + 29, 0, 0, 0, 0, 0, 0, 16, 0, 0, + 17, 56, 19, 20, 0, 0, 22, 23, 24, 0, + 25, 26, 3, 4, 55, 0, 0, 0, 0, 0, + 27, 0, 0, 0, 57, 0, 0, 0, 28, 29, + 0, 0, 0, 0, 0, 0, 16, 0, 0, 17, + 56, 19, 20, 0, 0, 22, 23, 24, 0, 25, + 26, 3, 4, 55, 0, 0, 0, 0, 0, 27, + 0, 0, 0, 131, 0, 0, 0, 28, 29, 0, + 0, 0, 0, 0, 0, 16, 0, 0, 17, 56, + 19, 20, 0, 0, 22, 23, 24, 0, 25, 26, + 3, 4, 55, 0, 0, 0, 0, 0, 27, 0, + 0, 0, 198, 0, 0, 0, 28, 29, 0, 0, + 0, 0, 0, 0, 16, 0, 0, 17, 56, 19, + 20, 0, 0, 22, 23, 24, 0, 25, 26, 3, + 4, 55, 0, 0, 0, 0, 0, 27, 0, 0, + 0, 232, 0, 0, 0, 28, 29, 0, 0, 0, + 0, 0, 0, 16, 0, 0, 17, 56, 19, 20, + 0, 0, 22, 23, 24, 0, 25, 26, 3, 4, + 55, 0, 0, 0, 0, 0, 27, 262, 0, 0, + 0, 0, 0, 0, 28, 29, 0, 0, 0, 0, + 0, 0, 16, 0, 0, 17, 56, 19, 20, 0, + 0, 22, 23, 24, 0, 25, 26, 3, 4, 55, + 0, 0, 0, 0, 0, 27, 264, 0, 0, 0, + 0, 0, 0, 28, 29, 0, 0, 0, 0, 0, + 0, 16, 0, 0, 17, 56, 19, 20, 0, 0, + 22, 23, 24, 0, 25, 26, 3, 4, 55, 0, + 0, 0, 0, 0, 27, 290, 0, 0, 0, 0, + 0, 0, 28, 29, 0, 0, 0, 0, 0, 0, + 16, 0, 0, 17, 56, 19, 20, 0, 0, 22, + 23, 24, 0, 25, 26, 0, 0, 94, 95, 96, + 97, 98, 99, 27, 100, 101, 102, 103, 0, 206, + 207, 28, 29, 0, 0, 104, 0, 0, 105, 106, + 107, 0, 0, 0, 108, 109, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 110, 111, 112, + 113, 114, 115, 116, 0, 0, 0, 0, 0, 117, + 118, 94, 95, 96, 97, 98, 99, 0, 100, 101, + 102, 103, 0, 0, 208, 0, 209, 0, 0, 104, + 0, 0, 105, 106, 107, 0, 0, 0, 108, 109, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 110, 111, 112, 113, 114, 115, 116, 0, 0, + 0, 0, 0, 117, 118, 94, 95, 96, 97, 98, + 99, 0, 100, 101, 102, 103, 0, 0, 0, 0, + 127, 0, 0, 104, 0, 0, 105, 106, 107, 0, + 0, 0, 108, 109, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 110, 111, 112, 113, 114, + 115, 116, 0, 0, 0, 0, 0, 117, 118, 94, + 95, 96, 97, 98, 99, 0, 100, 101, 102, 103, + 0, 156, 0, 0, 0, 0, 0, 104, 0, 0, + 105, 106, 107, 0, 0, 0, 108, 109, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, + 111, 112, 113, 114, 115, 116, 0, 0, 0, 0, + 0, 117, 118, 94, 95, 96, 97, 98, 99, 0, + 100, 101, 102, 103, 0, 188, 0, 0, 0, 0, + 0, 104, 0, 0, 105, 106, 107, 0, 0, 0, + 108, 109, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 110, 111, 112, 113, 114, 115, 116, + 0, 0, 0, 0, 0, 117, 118, 94, 95, 96, + 97, 98, 99, 0, 100, 101, 102, 103, 0, 193, + 0, 0, 0, 0, 0, 104, 0, 0, 105, 106, + 107, 0, 0, 0, 108, 109, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 110, 111, 112, + 113, 114, 115, 116, 0, 0, 0, 0, 0, 117, + 118, 94, 95, 96, 97, 98, 99, 0, 100, 101, + 102, 103, 0, 194, 0, 0, 0, 0, 0, 104, + 0, 0, 105, 106, 107, 0, 0, 0, 108, 109, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 110, 111, 112, 113, 114, 115, 116, 0, 0, + 0, 0, 0, 117, 118, 94, 95, 96, 97, 98, + 99, 0, 100, 101, 102, 103, 0, 0, 0, 0, + 200, 0, 0, 104, 0, 0, 105, 106, 107, 0, + 0, 0, 108, 109, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 110, 111, 112, 113, 114, + 115, 116, 0, 0, 0, 0, 0, 117, 118, 94, + 95, 96, 97, 98, 99, 0, 100, 101, 102, 103, + 0, 202, 0, 0, 0, 0, 0, 104, 0, 0, + 105, 106, 107, 0, 0, 0, 108, 109, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, + 111, 112, 113, 114, 115, 116, 0, 0, 0, 0, + 0, 117, 118, 94, 95, 96, 97, 98, 99, 0, + 100, 101, 102, 103, 0, 203, 0, 0, 0, 0, + 0, 104, 0, 0, 105, 106, 107, 0, 0, 0, + 108, 109, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 110, 111, 112, 113, 114, 115, 116, + 0, 0, 0, 0, 0, 117, 118, 94, 95, 96, + 97, 98, 99, 0, 100, 101, 102, 103, 0, 0, + 0, 217, 0, 0, 0, 104, 0, 0, 105, 106, + 107, 0, 0, 0, 108, 109, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 110, 111, 112, + 113, 114, 115, 116, 0, 0, 0, 0, 0, 117, + 118, 94, 95, 96, 97, 98, 99, 0, 100, 101, + 102, 103, 0, 0, 0, 0, 231, 0, 0, 104, + 0, 0, 105, 106, 107, 0, 0, 0, 108, 109, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 110, 111, 112, 113, 114, 115, 116, 0, 0, + 0, 0, 0, 117, 118, 94, 95, 96, 97, 98, + 99, 0, 100, 101, 102, 103, 0, 259, 0, 0, + 0, 0, 0, 104, 0, 0, 105, 106, 107, 0, + 0, 0, 108, 109, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 110, 111, 112, 113, 114, + 115, 116, 0, 0, 0, 0, 0, 117, 118, 94, + 95, 96, 97, 98, 99, 0, 100, 101, 102, 103, + 0, 261, 0, 0, 0, 0, 0, 104, 0, 0, + 105, 106, 107, 0, 0, 0, 108, 109, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, + 111, 112, 113, 114, 115, 116, 0, 0, 0, 0, + 0, 117, 118, 94, 95, 96, 97, 98, 99, 0, + 100, 101, 102, 103, 0, 0, 0, 0, 266, 0, + 0, 104, 0, 0, 105, 106, 107, 0, 0, 0, + 108, 109, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 110, 111, 112, 113, 114, 115, 116, + 0, 0, 0, 0, 0, 117, 118, 94, 95, 96, + 97, 98, 99, 0, 100, 101, 102, 103, 0, 0, + 0, 0, 273, 0, 0, 104, 0, 0, 105, 106, + 107, 0, 0, 0, 108, 109, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 110, 111, 112, + 113, 114, 115, 116, 0, 0, 0, 0, 0, 117, + 118, 94, 95, 96, 97, 98, 99, 0, 100, 101, + 102, 103, 0, 287, 0, 0, 0, 0, 0, 104, + 0, 0, 105, 106, 107, 0, 0, 0, 108, 109, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 110, 111, 112, 113, 114, 115, 116, 0, 0, + 0, 0, 0, 117, 118, 94, 95, 96, 97, 98, + 99, 0, 100, 101, 102, 103, 0, 289, 0, 0, + 0, 0, 0, 104, 0, 0, 105, 106, 107, 0, + 0, 0, 108, 109, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 110, 111, 112, 113, 114, + 115, 116, 0, 0, 0, 0, 0, 117, 118, 94, + 95, 96, 97, 98, 99, 0, 100, 101, 102, 103, + 0, 305, 0, 0, 0, 0, 0, 104, 0, 0, + 105, 106, 107, 0, 0, 0, 108, 109, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, + 111, 112, 113, 114, 115, 116, 0, 0, 0, 0, + 0, 117, 118, 94, 95, 96, 97, 98, 99, 0, + 100, 101, 102, 103, 0, 0, 0, 0, 346, 0, + 0, 104, 0, 0, 105, 106, 107, 0, 0, 0, + 108, 109, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 110, 111, 112, 113, 114, 115, 116, + 0, 0, 0, 0, 0, 117, 118, 94, 95, 96, + 97, 98, 99, 0, 100, 101, 102, 103, 0, 0, + 0, 0, 347, 0, 0, 104, 0, 0, 105, 106, + 107, 0, 0, 0, 108, 109, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 110, 111, 112, + 113, 114, 115, 116, 0, 0, 0, 0, 0, 117, + 118, 94, 95, 96, 97, 98, 99, 0, 100, 101, + 102, 103, 0, 0, 0, 0, 354, 0, 0, 104, + 0, 0, 105, 106, 107, 0, 0, 0, 108, 109, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 110, 111, 112, 113, 114, 115, 116, 0, 0, + 0, 0, 0, 117, 118, 94, 95, 96, 97, 98, + 99, 0, 100, 101, 102, 103, 0, 0, 0, 0, + 359, 0, 0, 104, 0, 0, 105, 106, 107, 0, + 0, 0, 108, 109, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 110, 111, 112, 113, 114, + 115, 116, 0, 0, 0, 0, 0, 117, 118, 94, + 95, 96, 97, 98, 99, 0, 100, 101, 102, 103, + 0, 0, 0, 0, 361, 0, 0, 104, 0, 0, + 105, 106, 107, 0, 0, 0, 108, 109, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, + 111, 112, 113, 114, 115, 116, 0, 0, 0, 0, + 0, 117, 118, 94, 95, 96, 97, 98, 99, 0, + 100, 101, 102, 103, 0, 0, 0, 0, 0, 0, + 0, 104, 0, 0, 105, 106, 107, 0, 0, 0, + 108, 109, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 110, 111, 112, 113, 114, 115, 116, + 0, 0, 0, 0, 0, 117, 118, 94, 95, 96, + 97, 98, 99, 0, 100, 101, 102, 103, 0, 0, + 0, 0, 0, 0, 0, 104, 0, 0, 105, 106, + 107, 0, 0, 0, 108, 109, 94, 95, 96, 97, + 98, 99, 0, 100, 101, 102, 103, 110, 111, 112, + 113, 114, 115, 116, 104, 0, 0, 105, 106, 107, + 118, 0, 0, 108, 109, 94, 95, 96, 97, 98, + 99, 0, 100, 101, 102, 103, 110, 111, 112, 113, + 114, 0, 116, 104, 0, 0, 105, 106, 107, 118, + 0, 0, 108, 109, 94, 95, 96, 97, 98, 99, + 0, 100, 0, 102, 103, 110, 111, 112, 113, 0, + 0, 116, 104, 0, 0, 105, 106, 107, 118, 0, + 0, 108, 109, 94, 95, 96, 97, 98, 99, 0, + 100, 0, 102, 103, 110, 111, 112, 113, 0, 0, + 116, 0, 0, 0, 105, 106, 107, 118, 0, 0, + 108, 109, 94, 95, 96, 97, 98, 99, 0, 100, + 0, 0, 103, 110, 111, 112, 113, 0, 0, 116, + 0, 0, 0, 105, 106, 107, 118, 0, 0, 108, + 109, 94, 95, 96, 97, 98, 99, 0, 100, 0, + 0, 103, 110, 111, 112, 113, 0, 0, 116, 0, + 0, 0, 105, 106, 107, 118, 0, 0, 108, 109, + 0, 0, 71, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 112, 113, 0, 0, 116, 0, 0, + 72, 73, 119, 0, 118, 74, 75, 76, 77, 78, + 79, 80, 81, 82, 83, 0, 0, 242, 0, 0, + 72, 73, 0, 0, 84, 74, 75, 76, 77, 78, + 79, 80, 81, 82, 83, 72, 73, 0, 0, 0, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 94, 95, 96, 97, 0, 0, 0, 100, 0, 0, + 103, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 106, 107, 0, 0, 0, 108, 109 +}; + +static const short yycheck[] = { 1, + 1, 42, 216, 236, 182, 136, 125, 250, 10, 54, + 192, 54, 5, 6, 7, 47, 47, 3, 52, 201, + 225, 4, 48, 35, 54, 309, 25, 5, 6, 54, + 35, 35, 54, 58, 318, 56, 29, 63, 64, 32, + 33, 34, 35, 52, 8, 38, 39, 40, 52, 42, + 43, 96, 52, 96, 88, 298, 55, 89, 89, 52, + 53, 25, 26, 27, 28, 29, 96, 60, 61, 312, + 189, 253, 58, 204, 96, 280, 56, 255, 21, 52, + 89, 214, 215, 52, 21, 49, 52, 52, 88, 52, + 54, 35, 35, 36, 52, 328, 60, 47, 35, 36, + 64, 56, 66, 67, 52, 69, 70, 71, 351, 323, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 58, 86, 106, 107, 56, 340, 129, 129, 52, + 94, 95, 96, 97, 98, 99, 14, 101, 102, 103, + 104, 105, 301, 52, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 57, 5, 6, 35, + 124, 125, 41, 42, 43, 44, 34, 131, 35, 48, + 211, 330, 51, 21, 43, 44, 5, 6, 7, 48, + 35, 52, 51, 62, 63, 64, 35, 35, 36, 68, + 69, 193, 194, 3, 63, 64, 53, 55, 54, 52, + 29, 193, 194, 32, 33, 34, 35, 53, 87, 38, + 39, 40, 54, 42, 43, 94, 52, 57, 182, 41, + 42, 43, 44, 52, 57, 189, 48, 229, 57, 51, + 38, 60, 61, 197, 198, 34, 200, 229, 57, 35, + 54, 63, 64, 8, 208, 96, 210, 53, 23, 213, + 214, 215, 216, 217, 56, 53, 258, 57, 53, 261, + 262, 53, 264, 53, 53, 53, 258, 231, 232, 261, + 262, 58, 264, 58, 57, 57, 278, 278, 242, 57, + 53, 47, 35, 57, 56, 287, 56, 289, 290, 58, + 58, 255, 56, 55, 0, 287, 47, 289, 290, 58, + 47, 221, 266, 305, 268, 0, 5, 6, 7, -1, + 318, 313, 313, 305, -1, 292, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 328, 328, -1, -1, + 29, -1, -1, 32, 33, 34, 35, -1, -1, 38, + 39, 40, -1, 42, 43, -1, -1, 311, 300, 301, + -1, -1, -1, 52, 356, 356, -1, 321, 322, 323, + -1, 60, 61, -1, -1, 317, 318, -1, -1, -1, + -1, -1, -1, -1, -1, 339, 340, -1, 330, -1, + -1, -1, 334, -1, -1, -1, -1, -1, -1, 353, + 89, -1, -1, 3, 358, 5, 6, 7, -1, 9, + -1, 11, -1, 13, 14, 15, -1, -1, -1, -1, + 20, 21, 22, -1, 24, -1, 26, -1, -1, 29, + -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, + 40, -1, 42, 43, -1, -1, -1, -1, -1, 5, + 6, 7, 52, 9, -1, 11, -1, 13, 14, 15, + 60, 61, -1, 19, 20, 21, 22, 23, 24, -1, + -1, -1, -1, 29, -1, -1, 32, 33, 34, 35, + -1, 37, 38, 39, 40, -1, 42, 43, -1, -1, + -1, 5, 6, 7, -1, 9, 52, 11, -1, 13, + 14, 15, -1, -1, 60, 61, 20, 21, 22, -1, + 24, -1, -1, -1, -1, 29, -1, -1, 32, 33, + 34, 35, -1, 37, 38, 39, 40, -1, 42, 43, + -1, -1, -1, -1, -1, -1, -1, -1, 52, -1, + -1, -1, -1, 57, -1, -1, 60, 61, 5, 6, + 7, -1, 9, -1, 11, -1, 13, 14, 15, -1, + -1, -1, -1, 20, 21, 22, -1, 24, -1, -1, + -1, -1, 29, -1, -1, 32, 33, 34, 35, -1, + 37, 38, 39, 40, -1, 42, 43, -1, -1, -1, + 5, 6, 7, -1, 9, 52, 11, -1, 13, 14, + 15, 58, -1, 60, 61, 20, 21, 22, -1, 24, + -1, -1, -1, -1, 29, -1, -1, 32, 33, 34, + 35, -1, 37, 38, 39, 40, -1, 42, 43, -1, + -1, -1, 5, 6, 7, -1, 9, 52, 11, -1, + 13, 14, 15, 58, -1, 60, 61, 20, 21, 22, + -1, 24, -1, -1, -1, -1, 29, -1, -1, 32, + 33, 34, 35, -1, 37, 38, 39, 40, -1, 42, + 43, -1, -1, -1, 5, 6, 7, -1, 9, 52, + 11, -1, 13, 14, 15, 58, -1, 60, 61, 20, + 21, 22, -1, 24, -1, -1, -1, -1, 29, -1, + -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, + -1, 42, 43, 5, 6, 7, -1, -1, -1, -1, + -1, 52, -1, -1, -1, -1, -1, -1, -1, 60, + 61, -1, -1, -1, -1, -1, -1, 29, -1, -1, + 32, 33, 34, 35, -1, -1, 38, 39, 40, -1, + 42, 43, 5, 6, 7, -1, -1, -1, -1, -1, + 52, -1, -1, -1, 56, -1, -1, -1, 60, 61, + -1, -1, -1, -1, -1, -1, 29, -1, -1, 32, + 33, 34, 35, -1, -1, 38, 39, 40, -1, 42, + 43, 5, 6, 7, -1, -1, -1, -1, -1, 52, + -1, -1, -1, 56, -1, -1, -1, 60, 61, -1, + -1, -1, -1, -1, -1, 29, -1, -1, 32, 33, + 34, 35, -1, -1, 38, 39, 40, -1, 42, 43, + 5, 6, 7, -1, -1, -1, -1, -1, 52, -1, + -1, -1, 56, -1, -1, -1, 60, 61, -1, -1, + -1, -1, -1, -1, 29, -1, -1, 32, 33, 34, + 35, -1, -1, 38, 39, 40, -1, 42, 43, 5, + 6, 7, -1, -1, -1, -1, -1, 52, -1, -1, + -1, 56, -1, -1, -1, 60, 61, -1, -1, -1, + -1, -1, -1, 29, -1, -1, 32, 33, 34, 35, + -1, -1, 38, 39, 40, -1, 42, 43, 5, 6, + 7, -1, -1, -1, -1, -1, 52, 53, -1, -1, + -1, -1, -1, -1, 60, 61, -1, -1, -1, -1, + -1, -1, 29, -1, -1, 32, 33, 34, 35, -1, + -1, 38, 39, 40, -1, 42, 43, 5, 6, 7, + -1, -1, -1, -1, -1, 52, 53, -1, -1, -1, + -1, -1, -1, 60, 61, -1, -1, -1, -1, -1, + -1, 29, -1, -1, 32, 33, 34, 35, -1, -1, + 38, 39, 40, -1, 42, 43, 5, 6, 7, -1, + -1, -1, -1, -1, 52, 53, -1, -1, -1, -1, + -1, -1, 60, 61, -1, -1, -1, -1, -1, -1, + 29, -1, -1, 32, 33, 34, 35, -1, -1, 38, + 39, 40, -1, 42, 43, -1, -1, 41, 42, 43, + 44, 45, 46, 52, 48, 49, 50, 51, -1, 53, + 54, 60, 61, -1, -1, 59, -1, -1, 62, 63, + 64, -1, -1, -1, 68, 69, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 81, 82, 83, + 84, 85, 86, 87, -1, -1, -1, -1, -1, 93, + 94, 41, 42, 43, 44, 45, 46, -1, 48, 49, + 50, 51, -1, -1, 54, -1, 56, -1, -1, 59, + -1, -1, 62, 63, 64, -1, -1, -1, 68, 69, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 81, 82, 83, 84, 85, 86, 87, -1, -1, + -1, -1, -1, 93, 94, 41, 42, 43, 44, 45, + 46, -1, 48, 49, 50, 51, -1, -1, -1, -1, + 56, -1, -1, 59, -1, -1, 62, 63, 64, -1, + -1, -1, 68, 69, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 81, 82, 83, 84, 85, + 86, 87, -1, -1, -1, -1, -1, 93, 94, 41, + 42, 43, 44, 45, 46, -1, 48, 49, 50, 51, + -1, 53, -1, -1, -1, -1, -1, 59, -1, -1, + 62, 63, 64, -1, -1, -1, 68, 69, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, + 82, 83, 84, 85, 86, 87, -1, -1, -1, -1, + -1, 93, 94, 41, 42, 43, 44, 45, 46, -1, + 48, 49, 50, 51, -1, 53, -1, -1, -1, -1, + -1, 59, -1, -1, 62, 63, 64, -1, -1, -1, + 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 81, 82, 83, 84, 85, 86, 87, + -1, -1, -1, -1, -1, 93, 94, 41, 42, 43, + 44, 45, 46, -1, 48, 49, 50, 51, -1, 53, + -1, -1, -1, -1, -1, 59, -1, -1, 62, 63, + 64, -1, -1, -1, 68, 69, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 81, 82, 83, + 84, 85, 86, 87, -1, -1, -1, -1, -1, 93, + 94, 41, 42, 43, 44, 45, 46, -1, 48, 49, + 50, 51, -1, 53, -1, -1, -1, -1, -1, 59, + -1, -1, 62, 63, 64, -1, -1, -1, 68, 69, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 81, 82, 83, 84, 85, 86, 87, -1, -1, + -1, -1, -1, 93, 94, 41, 42, 43, 44, 45, + 46, -1, 48, 49, 50, 51, -1, -1, -1, -1, + 56, -1, -1, 59, -1, -1, 62, 63, 64, -1, + -1, -1, 68, 69, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 81, 82, 83, 84, 85, + 86, 87, -1, -1, -1, -1, -1, 93, 94, 41, + 42, 43, 44, 45, 46, -1, 48, 49, 50, 51, + -1, 53, -1, -1, -1, -1, -1, 59, -1, -1, + 62, 63, 64, -1, -1, -1, 68, 69, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, + 82, 83, 84, 85, 86, 87, -1, -1, -1, -1, + -1, 93, 94, 41, 42, 43, 44, 45, 46, -1, + 48, 49, 50, 51, -1, 53, -1, -1, -1, -1, + -1, 59, -1, -1, 62, 63, 64, -1, -1, -1, + 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 81, 82, 83, 84, 85, 86, 87, + -1, -1, -1, -1, -1, 93, 94, 41, 42, 43, + 44, 45, 46, -1, 48, 49, 50, 51, -1, -1, + -1, 55, -1, -1, -1, 59, -1, -1, 62, 63, + 64, -1, -1, -1, 68, 69, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 81, 82, 83, + 84, 85, 86, 87, -1, -1, -1, -1, -1, 93, + 94, 41, 42, 43, 44, 45, 46, -1, 48, 49, + 50, 51, -1, -1, -1, -1, 56, -1, -1, 59, + -1, -1, 62, 63, 64, -1, -1, -1, 68, 69, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 81, 82, 83, 84, 85, 86, 87, -1, -1, + -1, -1, -1, 93, 94, 41, 42, 43, 44, 45, + 46, -1, 48, 49, 50, 51, -1, 53, -1, -1, + -1, -1, -1, 59, -1, -1, 62, 63, 64, -1, + -1, -1, 68, 69, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 81, 82, 83, 84, 85, + 86, 87, -1, -1, -1, -1, -1, 93, 94, 41, + 42, 43, 44, 45, 46, -1, 48, 49, 50, 51, + -1, 53, -1, -1, -1, -1, -1, 59, -1, -1, + 62, 63, 64, -1, -1, -1, 68, 69, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, + 82, 83, 84, 85, 86, 87, -1, -1, -1, -1, + -1, 93, 94, 41, 42, 43, 44, 45, 46, -1, + 48, 49, 50, 51, -1, -1, -1, -1, 56, -1, + -1, 59, -1, -1, 62, 63, 64, -1, -1, -1, + 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 81, 82, 83, 84, 85, 86, 87, + -1, -1, -1, -1, -1, 93, 94, 41, 42, 43, + 44, 45, 46, -1, 48, 49, 50, 51, -1, -1, + -1, -1, 56, -1, -1, 59, -1, -1, 62, 63, + 64, -1, -1, -1, 68, 69, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 81, 82, 83, + 84, 85, 86, 87, -1, -1, -1, -1, -1, 93, + 94, 41, 42, 43, 44, 45, 46, -1, 48, 49, + 50, 51, -1, 53, -1, -1, -1, -1, -1, 59, + -1, -1, 62, 63, 64, -1, -1, -1, 68, 69, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 81, 82, 83, 84, 85, 86, 87, -1, -1, + -1, -1, -1, 93, 94, 41, 42, 43, 44, 45, + 46, -1, 48, 49, 50, 51, -1, 53, -1, -1, + -1, -1, -1, 59, -1, -1, 62, 63, 64, -1, + -1, -1, 68, 69, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 81, 82, 83, 84, 85, + 86, 87, -1, -1, -1, -1, -1, 93, 94, 41, + 42, 43, 44, 45, 46, -1, 48, 49, 50, 51, + -1, 53, -1, -1, -1, -1, -1, 59, -1, -1, + 62, 63, 64, -1, -1, -1, 68, 69, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, + 82, 83, 84, 85, 86, 87, -1, -1, -1, -1, + -1, 93, 94, 41, 42, 43, 44, 45, 46, -1, + 48, 49, 50, 51, -1, -1, -1, -1, 56, -1, + -1, 59, -1, -1, 62, 63, 64, -1, -1, -1, + 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 81, 82, 83, 84, 85, 86, 87, + -1, -1, -1, -1, -1, 93, 94, 41, 42, 43, + 44, 45, 46, -1, 48, 49, 50, 51, -1, -1, + -1, -1, 56, -1, -1, 59, -1, -1, 62, 63, + 64, -1, -1, -1, 68, 69, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 81, 82, 83, + 84, 85, 86, 87, -1, -1, -1, -1, -1, 93, + 94, 41, 42, 43, 44, 45, 46, -1, 48, 49, + 50, 51, -1, -1, -1, -1, 56, -1, -1, 59, + -1, -1, 62, 63, 64, -1, -1, -1, 68, 69, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 81, 82, 83, 84, 85, 86, 87, -1, -1, + -1, -1, -1, 93, 94, 41, 42, 43, 44, 45, + 46, -1, 48, 49, 50, 51, -1, -1, -1, -1, + 56, -1, -1, 59, -1, -1, 62, 63, 64, -1, + -1, -1, 68, 69, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 81, 82, 83, 84, 85, + 86, 87, -1, -1, -1, -1, -1, 93, 94, 41, + 42, 43, 44, 45, 46, -1, 48, 49, 50, 51, + -1, -1, -1, -1, 56, -1, -1, 59, -1, -1, + 62, 63, 64, -1, -1, -1, 68, 69, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, + 82, 83, 84, 85, 86, 87, -1, -1, -1, -1, + -1, 93, 94, 41, 42, 43, 44, 45, 46, -1, + 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, + -1, 59, -1, -1, 62, 63, 64, -1, -1, -1, + 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 81, 82, 83, 84, 85, 86, 87, + -1, -1, -1, -1, -1, 93, 94, 41, 42, 43, + 44, 45, 46, -1, 48, 49, 50, 51, -1, -1, + -1, -1, -1, -1, -1, 59, -1, -1, 62, 63, + 64, -1, -1, -1, 68, 69, 41, 42, 43, 44, + 45, 46, -1, 48, 49, 50, 51, 81, 82, 83, + 84, 85, 86, 87, 59, -1, -1, 62, 63, 64, + 94, -1, -1, 68, 69, 41, 42, 43, 44, 45, + 46, -1, 48, 49, 50, 51, 81, 82, 83, 84, + 85, -1, 87, 59, -1, -1, 62, 63, 64, 94, + -1, -1, 68, 69, 41, 42, 43, 44, 45, 46, + -1, 48, -1, 50, 51, 81, 82, 83, 84, -1, + -1, 87, 59, -1, -1, 62, 63, 64, 94, -1, + -1, 68, 69, 41, 42, 43, 44, 45, 46, -1, + 48, -1, 50, 51, 81, 82, 83, 84, -1, -1, + 87, -1, -1, -1, 62, 63, 64, 94, -1, -1, + 68, 69, 41, 42, 43, 44, 45, 46, -1, 48, + -1, -1, 51, 81, 82, 83, 84, -1, -1, 87, + -1, -1, -1, 62, 63, 64, 94, -1, -1, 68, + 69, 41, 42, 43, 44, 45, 46, -1, 48, -1, + -1, 51, 81, 82, 83, 84, -1, -1, 87, -1, + -1, -1, 62, 63, 64, 94, -1, -1, 68, 69, + -1, -1, 47, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 83, 84, -1, -1, 87, -1, -1, + 65, 66, 47, -1, 94, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, -1, -1, 47, -1, -1, + 65, 66, -1, -1, 89, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 65, 66, -1, -1, -1, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 41, 42, 43, 44, -1, -1, -1, 48, -1, -1, + 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 63, 64, -1, -1, -1, 68, 69 +}; +/* -*-C-*- Note some compilers choke on comments on `#line' lines. */ +#line 3 "bison.simple" + +/* Skeleton output parser for bison, + Copyright (C) 1984, 1989, 1990 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 2, 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +#ifndef alloca +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not GNU C. */ +#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi) +#include +#else /* not sparc */ +#if defined (MSDOS) && !defined (__TURBOC__) +#include +#else /* not MSDOS, or __TURBOC__ */ +#if defined(_AIX) +#include + #pragma alloca +#else /* not MSDOS, __TURBOC__, or _AIX */ +#ifdef __hpux +#ifdef __cplusplus +extern "C" { +void *alloca (unsigned int); +}; +#else /* not __cplusplus */ +void *alloca (); +#endif /* not __cplusplus */ +#endif /* __hpux */ +#endif /* not _AIX */ +#endif /* not MSDOS, or __TURBOC__ */ +#endif /* not sparc. */ +#endif /* not GNU C. */ +#endif /* alloca not defined. */ + +/* This is the parser code that is written into each bison parser + when the %semantic_parser declaration is not specified in the grammar. + It was written by Richard Stallman by simplifying the hairy parser + used when %semantic_parser is specified. */ + +/* Note: there must be only one dollar sign in this file. + It is replaced by the list of actions, each action + as one case of the switch. */ + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY -2 +#define YYEOF 0 +#define YYACCEPT return(0) +#define YYABORT return(1) +#define YYERROR goto yyerrlab1 +/* 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. */ +#define YYFAIL goto yyerrlab +#define YYRECOVERING() (!!yyerrstatus) +#define YYBACKUP(token, value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { yychar = (token), yylval = (value); \ + yychar1 = YYTRANSLATE (yychar); \ + YYPOPSTACK; \ + goto yybackup; \ + } \ + else \ + { yyerror ("syntax error: cannot back up"); YYERROR; } \ +while (0) + +#define YYTERROR 1 +#define YYERRCODE 256 + +#ifndef YYPURE +#define YYLEX yylex() +#endif + +#ifdef YYPURE +#ifdef YYLSP_NEEDED +#ifdef YYLEX_PARAM +#define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM) +#else +#define YYLEX yylex(&yylval, &yylloc) +#endif +#else /* not YYLSP_NEEDED */ +#ifdef YYLEX_PARAM +#define YYLEX yylex(&yylval, YYLEX_PARAM) +#else +#define YYLEX yylex(&yylval) +#endif +#endif /* not YYLSP_NEEDED */ +#endif + +/* If nonreentrant, generate the variables here */ + +#ifndef YYPURE + +int yychar; /* the lookahead symbol */ +YYSTYPE yylval; /* the semantic value of the */ + /* lookahead symbol */ + +#ifdef YYLSP_NEEDED +YYLTYPE yylloc; /* location data for the lookahead */ + /* symbol */ +#endif + +int yynerrs; /* number of parse errors so far */ +#endif /* not YYPURE */ + +#if YYDEBUG != 0 +int yydebug; /* nonzero means print parse trace */ +/* Since this is uninitialized, it does not stop multiple parsers + from coexisting. */ +#endif + +/* YYINITDEPTH indicates the initial size of the parser's stacks */ + +#ifndef YYINITDEPTH +#define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH is the maximum size the stacks can grow to + (effective only if the built-in stack extension method is used). */ + +#if YYMAXDEPTH == 0 +#undef YYMAXDEPTH +#endif + +#ifndef YYMAXDEPTH +#define YYMAXDEPTH 10000 +#endif + +/* Prevent warning if -Wstrict-prototypes. */ +#ifdef __GNUC__ +int yyparse (void); +#endif + +#if __GNUC__ > 1 /* GNU C and GNU C++ define this. */ +#define __yy_memcpy(FROM,TO,COUNT) __builtin_memcpy(TO,FROM,COUNT) +#else /* not GNU C or C++ */ +#ifndef __cplusplus + +/* This is the most reliable way to avoid incompatibilities + in available built-in functions on various systems. */ +static void +__yy_memcpy (from, to, count) + char *from; + char *to; + int count; +{ + register char *f = from; + register char *t = to; + register int i = count; + + while (i-- > 0) + *t++ = *f++; +} + +#else /* __cplusplus */ + +/* This is the most reliable way to avoid incompatibilities + in available built-in functions on various systems. */ +static void +__yy_memcpy (char *from, char *to, int count) +{ + register char *f = from; + register char *t = to; + register int i = count; + + while (i-- > 0) + *t++ = *f++; +} + +#endif +#endif + +#line 192 "bison.simple" + +/* The user can define YYPARSE_PARAM as the name of an argument to be passed + into yyparse. The argument should have type void *. + It should actually point to an object. + Grammar actions can access the variable by casting it + to the proper pointer type. */ + +#ifdef YYPARSE_PARAM +#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM; +#else +#define YYPARSE_PARAM +#define YYPARSE_PARAM_DECL +#endif + +int +yyparse(YYPARSE_PARAM) + YYPARSE_PARAM_DECL +{ + register int yystate; + register int yyn; + register short *yyssp; + register YYSTYPE *yyvsp; + int yyerrstatus; /* number of tokens to shift before error messages enabled */ + int yychar1 = 0; /* lookahead token as an internal (translated) token number */ + + short yyssa[YYINITDEPTH]; /* the state stack */ + YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */ + + short *yyss = yyssa; /* refer to the stacks thru separate pointers */ + YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */ + +#ifdef YYLSP_NEEDED + YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */ + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; + +#define YYPOPSTACK (yyvsp--, yyssp--, yylsp--) +#else +#define YYPOPSTACK (yyvsp--, yyssp--) +#endif + + int yystacksize = YYINITDEPTH; + +#ifdef YYPURE + int yychar; + YYSTYPE yylval; + int yynerrs; +#ifdef YYLSP_NEEDED + YYLTYPE yylloc; +#endif +#endif + + YYSTYPE yyval; /* the variable used to return */ + /* semantic values from the action */ + /* routines */ + + int yylen; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Starting parse\n"); +#endif + + 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 - 1; + yyvsp = yyvs; +#ifdef YYLSP_NEEDED + yylsp = yyls; +#endif + +/* Push a new state, which is found in yystate . */ +/* In all cases, when you get here, the value and location stacks + have just been pushed. so pushing a state here evens the stacks. */ +yynewstate: + + *++yyssp = yystate; + + if (yyssp >= yyss + yystacksize - 1) + { + /* 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; + short *yyss1 = yyss; +#ifdef YYLSP_NEEDED + YYLTYPE *yyls1 = yyls; +#endif + + /* Get the current used size of the three stacks, in elements. */ + int size = yyssp - yyss + 1; + +#ifdef yyoverflow + /* Each stack pointer address is followed by the size of + the data in use in that stack, in bytes. */ +#ifdef YYLSP_NEEDED + /* This used to be a conditional around just the two extra args, + but that might be undefined if yyoverflow is a macro. */ + yyoverflow("parser stack overflow", + &yyss1, size * sizeof (*yyssp), + &yyvs1, size * sizeof (*yyvsp), + &yyls1, size * sizeof (*yylsp), + &yystacksize); +#else + yyoverflow("parser stack overflow", + &yyss1, size * sizeof (*yyssp), + &yyvs1, size * sizeof (*yyvsp), + &yystacksize); +#endif + + yyss = yyss1; yyvs = yyvs1; +#ifdef YYLSP_NEEDED + yyls = yyls1; +#endif +#else /* no yyoverflow */ + /* Extend the stack our own way. */ + if (yystacksize >= YYMAXDEPTH) + { + yyerror("parser stack overflow"); + return 2; + } + yystacksize *= 2; + if (yystacksize > YYMAXDEPTH) + yystacksize = YYMAXDEPTH; + yyss = (short *) alloca (yystacksize * sizeof (*yyssp)); + __yy_memcpy ((char *)yyss1, (char *)yyss, size * sizeof (*yyssp)); + yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp)); + __yy_memcpy ((char *)yyvs1, (char *)yyvs, size * sizeof (*yyvsp)); +#ifdef YYLSP_NEEDED + yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp)); + __yy_memcpy ((char *)yyls1, (char *)yyls, size * sizeof (*yylsp)); +#endif +#endif /* no yyoverflow */ + + yyssp = yyss + size - 1; + yyvsp = yyvs + size - 1; +#ifdef YYLSP_NEEDED + yylsp = yyls + size - 1; +#endif + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Stack size increased to %d\n", yystacksize); +#endif + + if (yyssp >= yyss + yystacksize - 1) + YYABORT; + } + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Entering state %d\n", yystate); +#endif + + goto yybackup; + yybackup: + +/* Do appropriate processing given the current state. */ +/* Read a lookahead token if we need one and don't already have one. */ +/* yyresume: */ + + /* First try to decide what to do without reference to lookahead token. */ + + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* yychar is either YYEMPTY or YYEOF + or a valid token in external form. */ + + if (yychar == YYEMPTY) + { +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Reading a token: "); +#endif + yychar = YYLEX; + } + + /* Convert token to internal form (in yychar1) for indexing tables with */ + + if (yychar <= 0) /* This means end of input. */ + { + yychar1 = 0; + yychar = YYEOF; /* Don't call YYLEX any more */ + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Now at end of input.\n"); +#endif + } + else + { + yychar1 = YYTRANSLATE(yychar); + +#if YYDEBUG != 0 + if (yydebug) + { + fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]); + /* Give the individual parser a way to print the precise meaning + of a token, for further debugging info. */ +#ifdef YYPRINT + YYPRINT (stderr, yychar, yylval); +#endif + fprintf (stderr, ")\n"); + } +#endif + } + + yyn += yychar1; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1) + goto yydefault; + + yyn = yytable[yyn]; + + /* yyn is what to do for this token type in this state. + Negative => reduce, -yyn is rule number. + Positive => shift, yyn is new state. + New state is final state => don't bother to shift, + just return success. + 0, or most negative number => error. */ + + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrlab; + + if (yyn == YYFINAL) + YYACCEPT; + + /* Shift the lookahead token. */ + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]); +#endif + + /* Discard the token being shifted unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + *++yyvsp = yylval; +#ifdef YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + /* count tokens shifted since error; after three, turn off error status. */ + if (yyerrstatus) yyerrstatus--; + + yystate = yyn; + goto yynewstate; + +/* Do the default action for the current state. */ +yydefault: + + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + +/* Do a reduction. yyn is the number of a rule to reduce with. */ +yyreduce: + yylen = yyr2[yyn]; + if (yylen > 0) + yyval = yyvsp[1-yylen]; /* implement default value of the action */ + +#if YYDEBUG != 0 + if (yydebug) + { + int i; + + fprintf (stderr, "Reducing via rule %d (line %d), ", + yyn, yyrline[yyn]); + + /* Print the symbols being reduced, and their result. */ + for (i = yyprhs[yyn]; yyrhs[i] > 0; i++) + fprintf (stderr, "%s ", yytname[yyrhs[i]]); + fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]); + } +#endif + + + switch (yyn) { + +case 1: +#line 150 "cmdgram.y" +{ ; + break;} +case 2: +#line 155 "cmdgram.y" +{ yyval.stmt = nil; ; + break;} +case 3: +#line 157 "cmdgram.y" +{ if(!gStatementList) { gStatementList = yyvsp[0].stmt; } else { gStatementList->append(yyvsp[0].stmt); } ; + break;} +case 4: +#line 162 "cmdgram.y" +{ yyval.stmt = yyvsp[0].stmt; ; + break;} +case 5: +#line 164 "cmdgram.y" +{ yyval.stmt = yyvsp[0].stmt; ; + break;} +case 6: +#line 166 "cmdgram.y" +{ yyval.stmt = yyvsp[0].stmt; ; + break;} +case 7: +#line 171 "cmdgram.y" +{ yyval.stmt = yyvsp[-2].stmt; for(StmtNode *walk = (yyvsp[-2].stmt);walk;walk = walk->getNext() ) walk->setPackage(yyvsp[-4].s); ; + break;} +case 8: +#line 176 "cmdgram.y" +{ yyval.stmt = yyvsp[0].stmt; ; + break;} +case 9: +#line 178 "cmdgram.y" +{ yyval.stmt = yyvsp[-1].stmt; (yyvsp[-1].stmt)->append(yyvsp[0].stmt); ; + break;} +case 10: +#line 183 "cmdgram.y" +{ yyval.stmt = nil; ; + break;} +case 11: +#line 185 "cmdgram.y" +{ if(!yyvsp[-1].stmt) { yyval.stmt = yyvsp[0].stmt; } else { (yyvsp[-1].stmt)->append(yyvsp[0].stmt); yyval.stmt = yyvsp[-1].stmt; } ; + break;} +case 17: +#line 195 "cmdgram.y" +{ yyval.stmt = BreakStmtNode::alloc(); ; + break;} +case 18: +#line 197 "cmdgram.y" +{ yyval.stmt = ContinueStmtNode::alloc(); ; + break;} +case 19: +#line 199 "cmdgram.y" +{ yyval.stmt = ReturnStmtNode::alloc(NULL); ; + break;} +case 20: +#line 201 "cmdgram.y" +{ yyval.stmt = ReturnStmtNode::alloc(yyvsp[-1].expr); ; + break;} +case 21: +#line 203 "cmdgram.y" +{ yyval.stmt = yyvsp[-1].stmt; ; + break;} +case 22: +#line 205 "cmdgram.y" +{ yyval.stmt = TTagSetStmtNode::alloc(yyvsp[-3].s, yyvsp[-1].expr, NULL); ; + break;} +case 23: +#line 207 "cmdgram.y" +{ yyval.stmt = TTagSetStmtNode::alloc(yyvsp[-5].s, yyvsp[-3].expr, yyvsp[-1].expr); ; + break;} +case 24: +#line 209 "cmdgram.y" +{ yyval.stmt = StrConstNode::alloc(yyvsp[0].str, false, true); ; + break;} +case 25: +#line 214 "cmdgram.y" +{ yyval.stmt = FunctionDeclStmtNode::alloc(yyvsp[-6].s, NULL, yyvsp[-4].var, yyvsp[-1].stmt); ; + break;} +case 26: +#line 216 "cmdgram.y" +{ yyval.stmt = FunctionDeclStmtNode::alloc(yyvsp[-6].s, yyvsp[-8].s, yyvsp[-4].var, yyvsp[-1].stmt); ; + break;} +case 27: +#line 221 "cmdgram.y" +{ yyval.var = NULL; ; + break;} +case 28: +#line 223 "cmdgram.y" +{ yyval.var = yyvsp[0].var; ; + break;} +case 29: +#line 228 "cmdgram.y" +{ yyval.var = VarNode::alloc(yyvsp[0].s, NULL); ; + break;} +case 30: +#line 230 "cmdgram.y" +{ yyval.var = yyvsp[-2].var; ((StmtNode*)(yyvsp[-2].var))->append((StmtNode*)VarNode::alloc(yyvsp[0].s, NULL)); ; + break;} +case 31: +#line 235 "cmdgram.y" +{ yyval.stmt = ObjectDeclNode::alloc(ConstantNode::alloc(yyvsp[-8].s), ConstantNode::alloc(yyvsp[-6].s), NULL, yyvsp[-5].s, yyvsp[-2].slist, NULL, true, false, false); ; + break;} +case 32: +#line 240 "cmdgram.y" +{ yyval.od = ObjectDeclNode::alloc(yyvsp[-8].expr, yyvsp[-6].expr, yyvsp[-4].expr, yyvsp[-5].s, yyvsp[-1].odcl.slots, yyvsp[-1].odcl.decls, false, false, false); ; + break;} +case 33: +#line 242 "cmdgram.y" +{ yyval.od = ObjectDeclNode::alloc(yyvsp[-5].expr, yyvsp[-3].expr, yyvsp[-1].expr, yyvsp[-2].s, NULL, NULL, false, false, false); ; + break;} +case 34: +#line 244 "cmdgram.y" +{ yyval.od = ObjectDeclNode::alloc(yyvsp[-10].expr, yyvsp[-7].expr, yyvsp[-4].expr, yyvsp[-5].s, yyvsp[-1].odcl.slots, yyvsp[-1].odcl.decls, false, true, false); ; + break;} +case 35: +#line 246 "cmdgram.y" +{ yyval.od = ObjectDeclNode::alloc(yyvsp[-7].expr, yyvsp[-4].expr, yyvsp[-1].expr, yyvsp[-2].s, NULL, NULL, false, true, false); ; + break;} +case 36: +#line 248 "cmdgram.y" +{ yyval.od = ObjectDeclNode::alloc(yyvsp[-8].expr, yyvsp[-6].expr, yyvsp[-4].expr, yyvsp[-5].s, yyvsp[-1].odcl.slots, yyvsp[-1].odcl.decls, false, false, true); ; + break;} +case 37: +#line 250 "cmdgram.y" +{ yyval.od = ObjectDeclNode::alloc(yyvsp[-5].expr, yyvsp[-3].expr, yyvsp[-1].expr, yyvsp[-2].s, NULL, NULL, false, false, true); ; + break;} +case 38: +#line 255 "cmdgram.y" +{ yyval.s = NULL; ; + break;} +case 39: +#line 257 "cmdgram.y" +{ yyval.s = yyvsp[0].s; ; + break;} +case 40: +#line 262 "cmdgram.y" +{ yyval.expr = StrConstNode::alloc("", false); ; + break;} +case 41: +#line 264 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 42: +#line 269 "cmdgram.y" +{ yyval.expr = NULL; ; + break;} +case 43: +#line 271 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 44: +#line 276 "cmdgram.y" +{ yyval.odcl.slots = NULL; yyval.odcl.decls = NULL; ; + break;} +case 45: +#line 278 "cmdgram.y" +{ yyval.odcl.slots = yyvsp[0].slist; yyval.odcl.decls = NULL; ; + break;} +case 46: +#line 280 "cmdgram.y" +{ yyval.odcl.slots = NULL; yyval.odcl.decls = yyvsp[0].od; ; + break;} +case 47: +#line 282 "cmdgram.y" +{ yyval.odcl.slots = yyvsp[-1].slist; yyval.odcl.decls = yyvsp[0].od; ; + break;} +case 48: +#line 287 "cmdgram.y" +{ yyval.od = yyvsp[-1].od; ; + break;} +case 49: +#line 289 "cmdgram.y" +{ yyvsp[-2].od->append(yyvsp[-1].od); yyval.od = yyvsp[-2].od; ; + break;} +case 50: +#line 294 "cmdgram.y" +{ yyval.stmt = yyvsp[-1].stmt; ; + break;} +case 51: +#line 296 "cmdgram.y" +{ yyval.stmt = yyvsp[0].stmt; ; + break;} +case 52: +#line 301 "cmdgram.y" +{ yyval.stmt = yyvsp[-1].ifnode; yyvsp[-1].ifnode->propagateSwitchExpr(yyvsp[-4].expr, false); ; + break;} +case 53: +#line 303 "cmdgram.y" +{ yyval.stmt = yyvsp[-1].ifnode; yyvsp[-1].ifnode->propagateSwitchExpr(yyvsp[-4].expr, true); ; + break;} +case 54: +#line 308 "cmdgram.y" +{ yyval.ifnode = IfStmtNode::alloc(yyvsp[-3].i, yyvsp[-2].expr, yyvsp[0].stmt, NULL, false); ; + break;} +case 55: +#line 310 "cmdgram.y" +{ yyval.ifnode = IfStmtNode::alloc(yyvsp[-6].i, yyvsp[-5].expr, yyvsp[-3].stmt, yyvsp[0].stmt, false); ; + break;} +case 56: +#line 312 "cmdgram.y" +{ yyval.ifnode = IfStmtNode::alloc(yyvsp[-4].i, yyvsp[-3].expr, yyvsp[-1].stmt, yyvsp[0].ifnode, true); ; + break;} +case 57: +#line 317 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr;; + break;} +case 58: +#line 319 "cmdgram.y" +{ (yyvsp[-2].expr)->append(yyvsp[0].expr); yyval.expr=yyvsp[-2].expr; ; + break;} +case 59: +#line 324 "cmdgram.y" +{ yyval.stmt = IfStmtNode::alloc(yyvsp[-4].i, yyvsp[-2].expr, yyvsp[0].stmt, NULL, false); ; + break;} +case 60: +#line 326 "cmdgram.y" +{ yyval.stmt = IfStmtNode::alloc(yyvsp[-6].i, yyvsp[-4].expr, yyvsp[-2].stmt, yyvsp[0].stmt, false); ; + break;} +case 61: +#line 331 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-4].i, nil, yyvsp[-2].expr, nil, yyvsp[0].stmt, false); ; + break;} +case 62: +#line 333 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-3].i, nil, yyvsp[-1].expr, nil, yyvsp[-4].stmt, true); ; + break;} +case 63: +#line 338 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-8].i, yyvsp[-6].expr, yyvsp[-4].expr, yyvsp[-2].expr, yyvsp[0].stmt, false); ; + break;} +case 64: +#line 340 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-7].i, yyvsp[-5].expr, yyvsp[-3].expr, NULL, yyvsp[0].stmt, false); ; + break;} +case 65: +#line 342 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-7].i, yyvsp[-5].expr, NULL, yyvsp[-2].expr, yyvsp[0].stmt, false); ; + break;} +case 66: +#line 344 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-6].i, yyvsp[-4].expr, NULL, NULL, yyvsp[0].stmt, false); ; + break;} +case 67: +#line 346 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-7].i, NULL, yyvsp[-4].expr, yyvsp[-2].expr, yyvsp[0].stmt, false); ; + break;} +case 68: +#line 348 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-6].i, NULL, yyvsp[-3].expr, NULL, yyvsp[0].stmt, false); ; + break;} +case 69: +#line 350 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-6].i, NULL, NULL, yyvsp[-2].expr, yyvsp[0].stmt, false); ; + break;} +case 70: +#line 352 "cmdgram.y" +{ yyval.stmt = LoopStmtNode::alloc(yyvsp[-5].i, NULL, NULL, NULL, yyvsp[0].stmt, false); ; + break;} +case 71: +#line 357 "cmdgram.y" +{ yyval.stmt = yyvsp[0].expr; ; + break;} +case 72: +#line 362 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 73: +#line 364 "cmdgram.y" +{ yyval.expr = yyvsp[-1].expr; ; + break;} +case 74: +#line 366 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 75: +#line 368 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 76: +#line 370 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 77: +#line 372 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 78: +#line 374 "cmdgram.y" +{ yyval.expr = FloatBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 79: +#line 376 "cmdgram.y" +{ yyval.expr = FloatBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 80: +#line 378 "cmdgram.y" +{ yyval.expr = FloatBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 81: +#line 380 "cmdgram.y" +{ yyval.expr = FloatBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 82: +#line 382 "cmdgram.y" +{ yyval.expr = FloatUnaryExprNode::alloc(yyvsp[-1].i, yyvsp[0].expr); ; + break;} +case 83: +#line 384 "cmdgram.y" +{ yyval.expr = TTagDerefNode::alloc(yyvsp[0].expr); ; + break;} +case 84: +#line 386 "cmdgram.y" +{ yyval.expr = TTagExprNode::alloc(yyvsp[0].s); ; + break;} +case 85: +#line 388 "cmdgram.y" +{ yyval.expr = ConditionalExprNode::alloc(yyvsp[-4].expr, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 86: +#line 390 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 87: +#line 392 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 88: +#line 394 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 89: +#line 396 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 90: +#line 398 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 91: +#line 400 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 92: +#line 402 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 93: +#line 404 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 94: +#line 406 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 95: +#line 408 "cmdgram.y" +{ yyval.expr = IntBinaryExprNode::alloc(yyvsp[-1].i, yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +case 96: +#line 410 "cmdgram.y" +{ yyval.expr = StreqExprNode::alloc(yyvsp[-2].expr, yyvsp[0].expr, true); ; + break;} +case 97: +#line 412 "cmdgram.y" +{ yyval.expr = StreqExprNode::alloc(yyvsp[-2].expr, yyvsp[0].expr, false); ; + break;} +case 98: +#line 414 "cmdgram.y" +{ yyval.expr = StrcatExprNode::alloc(yyvsp[-2].expr, yyvsp[0].expr, yyvsp[-1].i); ; + break;} +case 99: +#line 416 "cmdgram.y" +{ yyval.expr = IntUnaryExprNode::alloc(yyvsp[-1].i, yyvsp[0].expr); ; + break;} +case 100: +#line 418 "cmdgram.y" +{ yyval.expr = IntUnaryExprNode::alloc(yyvsp[-1].i, yyvsp[0].expr); ; + break;} +case 101: +#line 420 "cmdgram.y" +{ yyval.expr = StrConstNode::alloc(yyvsp[0].str, true); ; + break;} +case 102: +#line 422 "cmdgram.y" +{ yyval.expr = FloatNode::alloc(yyvsp[0].f); ; + break;} +case 103: +#line 424 "cmdgram.y" +{ yyval.expr = IntNode::alloc(yyvsp[0].i); ; + break;} +case 104: +#line 426 "cmdgram.y" +{ yyval.expr = ConstantNode::alloc(StringTable->insert("break")); ; + break;} +case 105: +#line 428 "cmdgram.y" +{ yyval.expr = SlotAccessNode::alloc(yyvsp[0].slot.object, yyvsp[0].slot.array, yyvsp[0].slot.slotName); ; + break;} +case 106: +#line 430 "cmdgram.y" +{ yyval.expr = InternalSlotAccessNode::alloc(yyvsp[0].intslot.object, yyvsp[0].intslot.slotExpr, yyvsp[0].intslot.recurse); ; + break;} +case 107: +#line 432 "cmdgram.y" +{ yyval.expr = ConstantNode::alloc(yyvsp[0].s); ; + break;} +case 108: +#line 434 "cmdgram.y" +{ yyval.expr = StrConstNode::alloc(yyvsp[0].str, false); ; + break;} +case 109: +#line 436 "cmdgram.y" +{ yyval.expr = (ExprNode*)VarNode::alloc(yyvsp[0].s, NULL); ; + break;} +case 110: +#line 438 "cmdgram.y" +{ yyval.expr = (ExprNode*)VarNode::alloc(yyvsp[-3].s, yyvsp[-1].expr); ; + break;} +case 111: +#line 443 "cmdgram.y" +{ yyval.slot.object = yyvsp[-2].expr; yyval.slot.slotName = yyvsp[0].s; yyval.slot.array = NULL; ; + break;} +case 112: +#line 445 "cmdgram.y" +{ yyval.slot.object = yyvsp[-5].expr; yyval.slot.slotName = yyvsp[-3].s; yyval.slot.array = yyvsp[-1].expr; ; + break;} +case 113: +#line 450 "cmdgram.y" +{ yyval.intslot.object = yyvsp[-2].expr; yyval.intslot.slotExpr = yyvsp[0].expr; yyval.intslot.recurse = false; ; + break;} +case 114: +#line 452 "cmdgram.y" +{ yyval.intslot.object = yyvsp[-2].expr; yyval.intslot.slotExpr = yyvsp[0].expr; yyval.intslot.recurse = true; ; + break;} +case 115: +#line 457 "cmdgram.y" +{ yyval.expr = ConstantNode::alloc(yyvsp[0].s); ; + break;} +case 116: +#line 459 "cmdgram.y" +{ yyval.expr = yyvsp[-1].expr; ; + break;} +case 117: +#line 464 "cmdgram.y" +{ yyval.asn.token = '+'; yyval.asn.expr = FloatNode::alloc(1); ; + break;} +case 118: +#line 466 "cmdgram.y" +{ yyval.asn.token = '-'; yyval.asn.expr = FloatNode::alloc(1); ; + break;} +case 119: +#line 468 "cmdgram.y" +{ yyval.asn.token = '+'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 120: +#line 470 "cmdgram.y" +{ yyval.asn.token = '-'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 121: +#line 472 "cmdgram.y" +{ yyval.asn.token = '*'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 122: +#line 474 "cmdgram.y" +{ yyval.asn.token = '/'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 123: +#line 476 "cmdgram.y" +{ yyval.asn.token = '%'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 124: +#line 478 "cmdgram.y" +{ yyval.asn.token = '&'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 125: +#line 480 "cmdgram.y" +{ yyval.asn.token = '^'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 126: +#line 482 "cmdgram.y" +{ yyval.asn.token = '|'; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 127: +#line 484 "cmdgram.y" +{ yyval.asn.token = opSHL; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 128: +#line 486 "cmdgram.y" +{ yyval.asn.token = opSHR; yyval.asn.expr = yyvsp[0].expr; ; + break;} +case 129: +#line 491 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 130: +#line 493 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 131: +#line 495 "cmdgram.y" +{ yyval.expr = yyvsp[0].od; ; + break;} +case 132: +#line 497 "cmdgram.y" +{ yyval.expr = AssignExprNode::alloc(yyvsp[-2].s, NULL, yyvsp[0].expr); ; + break;} +case 133: +#line 499 "cmdgram.y" +{ yyval.expr = AssignExprNode::alloc(yyvsp[-5].s, yyvsp[-3].expr, yyvsp[0].expr); ; + break;} +case 134: +#line 501 "cmdgram.y" +{ yyval.expr = AssignOpExprNode::alloc(yyvsp[-1].s, NULL, yyvsp[0].asn.expr, yyvsp[0].asn.token); ; + break;} +case 135: +#line 503 "cmdgram.y" +{ yyval.expr = AssignOpExprNode::alloc(yyvsp[-4].s, yyvsp[-2].expr, yyvsp[0].asn.expr, yyvsp[0].asn.token); ; + break;} +case 136: +#line 505 "cmdgram.y" +{ yyval.expr = SlotAssignOpNode::alloc(yyvsp[-1].slot.object, yyvsp[-1].slot.slotName, yyvsp[-1].slot.array, yyvsp[0].asn.token, yyvsp[0].asn.expr); ; + break;} +case 137: +#line 507 "cmdgram.y" +{ yyval.expr = SlotAssignNode::alloc(yyvsp[-2].slot.object, yyvsp[-2].slot.array, yyvsp[-2].slot.slotName, yyvsp[0].expr); ; + break;} +case 138: +#line 509 "cmdgram.y" +{ yyval.expr = SlotAssignNode::alloc(yyvsp[-4].slot.object, yyvsp[-4].slot.array, yyvsp[-4].slot.slotName, yyvsp[-1].expr); ; + break;} +case 139: +#line 514 "cmdgram.y" +{ yyval.expr = FuncCallExprNode::alloc(yyvsp[-3].s, NULL, yyvsp[-1].expr, false); ; + break;} +case 140: +#line 516 "cmdgram.y" +{ yyval.expr = FuncCallExprNode::alloc(yyvsp[-3].s, yyvsp[-5].s, yyvsp[-1].expr, false); ; + break;} +case 141: +#line 518 "cmdgram.y" +{ yyvsp[-5].expr->append(yyvsp[-1].expr); yyval.expr = FuncCallExprNode::alloc(yyvsp[-3].s, NULL, yyvsp[-5].expr, true); ; + break;} +case 142: +#line 523 "cmdgram.y" +{ yyval.expr = AssertCallExprNode::alloc( yyvsp[-1].expr, NULL ); ; + break;} +case 143: +#line 525 "cmdgram.y" +{ yyval.expr = AssertCallExprNode::alloc( yyvsp[-3].expr, yyvsp[-1].str ); ; + break;} +case 144: +#line 530 "cmdgram.y" +{ yyval.expr = NULL; ; + break;} +case 145: +#line 532 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 146: +#line 537 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 147: +#line 539 "cmdgram.y" +{ (yyvsp[-2].expr)->append(yyvsp[0].expr); yyval.expr = yyvsp[-2].expr; ; + break;} +case 148: +#line 544 "cmdgram.y" +{ yyval.slist = yyvsp[0].slist; ; + break;} +case 149: +#line 546 "cmdgram.y" +{ yyvsp[-1].slist->append(yyvsp[0].slist); yyval.slist = yyvsp[-1].slist; ; + break;} +case 150: +#line 551 "cmdgram.y" +{ yyval.slist = SlotAssignNode::alloc(NULL, NULL, yyvsp[-3].s, yyvsp[-1].expr); ; + break;} +case 151: +#line 553 "cmdgram.y" +{ yyval.slist = SlotAssignNode::alloc(NULL, NULL, yyvsp[-3].s, yyvsp[-1].expr, yyvsp[-4].i); ; + break;} +case 152: +#line 555 "cmdgram.y" +{ yyval.slist = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), yyvsp[-1].expr); ; + break;} +case 153: +#line 557 "cmdgram.y" +{ yyval.slist = SlotAssignNode::alloc(NULL, yyvsp[-4].expr, yyvsp[-6].s, yyvsp[-1].expr); ; + break;} +case 154: +#line 559 "cmdgram.y" +{ yyval.slist = SlotAssignNode::alloc(NULL, yyvsp[-4].expr, yyvsp[-6].s, yyvsp[-1].expr, yyvsp[-7].i); ; + break;} +case 155: +#line 564 "cmdgram.y" +{ yyval.expr = yyvsp[0].expr; ; + break;} +case 156: +#line 566 "cmdgram.y" +{ yyval.expr = CommaCatExprNode::alloc(yyvsp[-2].expr, yyvsp[0].expr); ; + break;} +} + /* the action file gets copied in in place of this dollarsign */ +#line 487 "bison.simple" + + yyvsp -= yylen; + yyssp -= yylen; +#ifdef YYLSP_NEEDED + yylsp -= yylen; +#endif + +#if YYDEBUG != 0 + if (yydebug) + { + short *ssp1 = yyss - 1; + fprintf (stderr, "state stack now"); + while (ssp1 != yyssp) + fprintf (stderr, " %d", *++ssp1); + fprintf (stderr, "\n"); + } +#endif + + *++yyvsp = yyval; + +#ifdef YYLSP_NEEDED + yylsp++; + if (yylen == 0) + { + yylsp->first_line = yylloc.first_line; + yylsp->first_column = yylloc.first_column; + yylsp->last_line = (yylsp-1)->last_line; + yylsp->last_column = (yylsp-1)->last_column; + yylsp->text = 0; + } + else + { + yylsp->last_line = (yylsp+yylen-1)->last_line; + yylsp->last_column = (yylsp+yylen-1)->last_column; + } +#endif + + /* 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 - YYNTBASE] + *yyssp; + if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTBASE]; + + goto yynewstate; + +yyerrlab: /* here on detecting error */ + + if (! yyerrstatus) + /* If not already recovering from an error, report this error. */ + { + ++yynerrs; + +#ifdef YYERROR_VERBOSE + yyn = yypact[yystate]; + + if (yyn > YYFLAG && yyn < YYLAST) + { + int size = 0; + char *msg; + int x, count; + + count = 0; + /* Start X at -yyn if nec to avoid negative indexes in yycheck. */ + for (x = (yyn < 0 ? -yyn : 0); + x < (sizeof(yytname) / sizeof(char *)); x++) + if (yycheck[x + yyn] == x) + size += strlen(yytname[x]) + 15, count++; + msg = (char *) malloc(size + 15); + if (msg != 0) + { + strcpy(msg, "parse error"); + + if (count < 5) + { + count = 0; + for (x = (yyn < 0 ? -yyn : 0); + x < (sizeof(yytname) / sizeof(char *)); x++) + if (yycheck[x + yyn] == x) + { + strcat(msg, count == 0 ? ", expecting `" : " or `"); + strcat(msg, yytname[x]); + strcat(msg, "'"); + count++; + } + } + yyerror(msg); + free(msg); + } + else + yyerror ("parse error; also virtual memory exceeded"); + } + else +#endif /* YYERROR_VERBOSE */ + yyerror("parse error"); + } + + goto yyerrlab1; +yyerrlab1: /* here on error raised explicitly by an action */ + + if (yyerrstatus == 3) + { + /* if just tried and failed to reuse lookahead token after an error, discard it. */ + + /* return failure if at end of input */ + if (yychar == YYEOF) + YYABORT; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]); +#endif + + yychar = YYEMPTY; + } + + /* Else will try to reuse lookahead token + after shifting the error token. */ + + yyerrstatus = 3; /* Each real token shifted decrements this */ + + goto yyerrhandle; + +yyerrdefault: /* current state does not do anything special for the error token. */ + +#if 0 + /* This is wrong; only states that explicitly want error tokens + should shift them. */ + yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/ + if (yyn) goto yydefault; +#endif + +yyerrpop: /* pop the current state because it cannot handle the error token */ + + if (yyssp == yyss) YYABORT; + yyvsp--; + yystate = *--yyssp; +#ifdef YYLSP_NEEDED + yylsp--; +#endif + +#if YYDEBUG != 0 + if (yydebug) + { + short *ssp1 = yyss - 1; + fprintf (stderr, "Error: state stack now"); + while (ssp1 != yyssp) + fprintf (stderr, " %d", *++ssp1); + fprintf (stderr, "\n"); + } +#endif + +yyerrhandle: + + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yyerrdefault; + + yyn += YYTERROR; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR) + goto yyerrdefault; + + yyn = yytable[yyn]; + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrpop; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrpop; + + if (yyn == YYFINAL) + YYACCEPT; + +#if YYDEBUG != 0 + if (yydebug) + fprintf(stderr, "Shifting error token, "); +#endif + + *++yyvsp = yylval; +#ifdef YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + yystate = yyn; + goto yynewstate; +} +#line 568 "cmdgram.y" + + diff --git a/console/cmdgram.h b/console/cmdgram.h new file mode 100644 index 0000000..96c46b6 --- /dev/null +++ b/console/cmdgram.h @@ -0,0 +1,89 @@ +typedef union { + char c; + int i; + const char * s; + char * str; + double f; + StmtNode * stmt; + ExprNode * expr; + SlotAssignNode * slist; + VarNode * var; + SlotDecl slot; + InternalSlotDecl intslot; + ObjectBlockDecl odcl; + ObjectDeclNode * od; + AssignDecl asn; + IfStmtNode * ifnode; +} YYSTYPE; +#define rwDEFINE 258 +#define rwENDDEF 259 +#define rwDECLARE 260 +#define rwDECLARESINGLETON 261 +#define rwBREAK 262 +#define rwELSE 263 +#define rwCONTINUE 264 +#define rwGLOBAL 265 +#define rwIF 266 +#define rwNIL 267 +#define rwRETURN 268 +#define rwWHILE 269 +#define rwDO 270 +#define rwENDIF 271 +#define rwENDWHILE 272 +#define rwENDFOR 273 +#define rwDEFAULT 274 +#define rwFOR 275 +#define rwDATABLOCK 276 +#define rwSWITCH 277 +#define rwCASE 278 +#define rwSWITCHSTR 279 +#define rwCASEOR 280 +#define rwPACKAGE 281 +#define rwNAMESPACE 282 +#define rwCLASS 283 +#define rwASSERT 284 +#define ILLEGAL_TOKEN 285 +#define CHRCONST 286 +#define INTCONST 287 +#define TTAG 288 +#define VAR 289 +#define IDENT 290 +#define TYPE 291 +#define DOCBLOCK 292 +#define STRATOM 293 +#define TAGATOM 294 +#define FLTCONST 295 +#define opINTNAME 296 +#define opINTNAMER 297 +#define opMINUSMINUS 298 +#define opPLUSPLUS 299 +#define STMT_SEP 300 +#define opSHL 301 +#define opSHR 302 +#define opPLASN 303 +#define opMIASN 304 +#define opMLASN 305 +#define opDVASN 306 +#define opMODASN 307 +#define opANDASN 308 +#define opXORASN 309 +#define opORASN 310 +#define opSLASN 311 +#define opSRASN 312 +#define opCAT 313 +#define opEQ 314 +#define opNE 315 +#define opGE 316 +#define opLE 317 +#define opAND 318 +#define opOR 319 +#define opSTREQ 320 +#define opCOLONCOLON 321 +#define opMDASN 322 +#define opNDASN 323 +#define opNTASN 324 +#define opSTRNE 325 +#define UNARY 326 + + +extern YYSTYPE CMDlval; diff --git a/console/codeBlock.cpp b/console/codeBlock.cpp new file mode 100644 index 0000000..398e782 --- /dev/null +++ b/console/codeBlock.cpp @@ -0,0 +1,661 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/compiler.h" +#include "console/codeBlock.h" +#include "console/telnetDebugger.h" +#include "core/strings/unicode.h" +#include "core/strings/stringFunctions.h" +#include "core/stringTable.h" +#include "core/stream/fileStream.h" + +using namespace Compiler; + +bool CodeBlock::smInFunction = false; +U32 CodeBlock::smBreakLineCount = 0; +CodeBlock * CodeBlock::smCodeBlockList = NULL; +CodeBlock * CodeBlock::smCurrentCodeBlock = NULL; +ConsoleParser *CodeBlock::smCurrentParser = NULL; + +//------------------------------------------------------------------------- + +CodeBlock::CodeBlock() +{ + globalStrings = NULL; + functionStrings = NULL; + functionStringsMaxLen = 0; + globalStringsMaxLen = 0; + globalFloats = NULL; + functionFloats = NULL; + lineBreakPairs = NULL; + breakList = NULL; + breakListSize = 0; + + refCount = 0; + code = NULL; + name = NULL; + fullPath = NULL; + modPath = NULL; + mRoot = StringTable->insert(""); +} + +CodeBlock::~CodeBlock() +{ + // Make sure we aren't lingering in the current code block... + AssertFatal(smCurrentCodeBlock != this, "CodeBlock::~CodeBlock - Caught lingering in smCurrentCodeBlock!") + + if(name) + removeFromCodeList(); + delete[] const_cast(globalStrings); + delete[] const_cast(functionStrings); + + functionStringsMaxLen = 0; + globalStringsMaxLen = 0; + + delete[] globalFloats; + delete[] functionFloats; + delete[] code; + delete[] breakList; +} + +//------------------------------------------------------------------------- + +StringTableEntry CodeBlock::getCurrentCodeBlockName() +{ + if (CodeBlock::getCurrentBlock()) + return CodeBlock::getCurrentBlock()->name; + else + return NULL; +} + +StringTableEntry CodeBlock::getCurrentCodeBlockFullPath() +{ + if (CodeBlock::getCurrentBlock()) + return CodeBlock::getCurrentBlock()->fullPath; + else + return NULL; +} + +StringTableEntry CodeBlock::getCurrentCodeBlockModName() +{ + if (CodeBlock::getCurrentBlock()) + return CodeBlock::getCurrentBlock()->modPath; + else + return NULL; +} + +CodeBlock *CodeBlock::find(StringTableEntry name) +{ + for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) + if(walk->name == name) + return walk; + return NULL; +} + +//------------------------------------------------------------------------- + +void CodeBlock::addToCodeList() +{ + // remove any code blocks with my name + for(CodeBlock **walk = &smCodeBlockList; *walk;walk = &((*walk)->nextFile)) + { + if((*walk)->name == name) + { + *walk = (*walk)->nextFile; + break; + } + } + nextFile = smCodeBlockList; + smCodeBlockList = this; +} + +void CodeBlock::clearAllBreaks() +{ + if(!lineBreakPairs) + return; + for(U32 i = 0; i < lineBreakPairCount; i++) + { + U32 *p = lineBreakPairs + i * 2; + code[p[1]] = p[0] & 0xFF; + } +} + +void CodeBlock::clearBreakpoint(U32 lineNumber) +{ + if(!lineBreakPairs) + return; + for(U32 i = 0; i < lineBreakPairCount; i++) + { + U32 *p = lineBreakPairs + i * 2; + if((p[0] >> 8) == lineNumber) + { + code[p[1]] = p[0] & 0xFF; + return; + } + } +} + +void CodeBlock::setAllBreaks() +{ + if(!lineBreakPairs) + return; + for(U32 i = 0; i < lineBreakPairCount; i++) + { + U32 *p = lineBreakPairs + i * 2; + code[p[1]] = OP_BREAK; + } +} + +bool CodeBlock::setBreakpoint(U32 lineNumber) +{ + if(!lineBreakPairs) + return false; + + for(U32 i = 0; i < lineBreakPairCount; i++) + { + U32 *p = lineBreakPairs + i * 2; + if((p[0] >> 8) == lineNumber) + { + code[p[1]] = OP_BREAK; + return true; + } + } + + return false; +} + +U32 CodeBlock::findFirstBreakLine(U32 lineNumber) +{ + if(!lineBreakPairs) + return 0; + + for(U32 i = 0; i < lineBreakPairCount; i++) + { + U32 *p = lineBreakPairs + i * 2; + U32 line = (p[0] >> 8); + + if( lineNumber <= line ) + return line; + } + + return 0; +} + +struct LinePair +{ + U32 instLine; + U32 ip; +}; + +void CodeBlock::findBreakLine(U32 ip, U32 &line, U32 &instruction) +{ + U32 min = 0; + U32 max = lineBreakPairCount - 1; + LinePair *p = (LinePair *) lineBreakPairs; + + U32 found; + if(!lineBreakPairCount || p[min].ip > ip || p[max].ip < ip) + { + line = 0; + instruction = OP_INVALID; + return; + } + else if(p[min].ip == ip) + found = min; + else if(p[max].ip == ip) + found = max; + else + { + for(;;) + { + if(min == max - 1) + { + found = min; + break; + } + U32 mid = (min + max) >> 1; + if(p[mid].ip == ip) + { + found = mid; + break; + } + else if(p[mid].ip > ip) + max = mid; + else + min = mid; + } + } + instruction = p[found].instLine & 0xFF; + line = p[found].instLine >> 8; +} + +const char *CodeBlock::getFileLine(U32 ip) +{ + static char nameBuffer[256]; + U32 line, inst; + findBreakLine(ip, line, inst); + + dSprintf(nameBuffer, sizeof(nameBuffer), "%s (%d)", name ? name : "", line); + return nameBuffer; +} + +void CodeBlock::removeFromCodeList() +{ + for(CodeBlock **walk = &smCodeBlockList; *walk; walk = &((*walk)->nextFile)) + { + if(*walk == this) + { + *walk = nextFile; + + // clear out all breakpoints + clearAllBreaks(); + break; + } + } + + // Let the telnet debugger know that this code + // block has been unloaded and that it needs to + // remove references to it. + if ( TelDebugger ) + TelDebugger->clearCodeBlockPointers( this ); +} + +void CodeBlock::calcBreakList() +{ + U32 size = 0; + S32 line = -1; + U32 seqCount = 0; + U32 i; + for(i = 0; i < lineBreakPairCount; i++) + { + U32 lineNumber = lineBreakPairs[i * 2]; + if(lineNumber == U32(line + 1)) + seqCount++; + else + { + if(seqCount) + size++; + size++; + seqCount = 1; + } + line = lineNumber; + } + if(seqCount) + size++; + + breakList = new U32[size]; + breakListSize = size; + line = -1; + seqCount = 0; + size = 0; + + for(i = 0; i < lineBreakPairCount; i++) + { + U32 lineNumber = lineBreakPairs[i * 2]; + + if(lineNumber == U32(line + 1)) + seqCount++; + else + { + if(seqCount) + breakList[size++] = seqCount; + breakList[size++] = lineNumber - getMax(0, line) - 1; + seqCount = 1; + } + + line = lineNumber; + } + + if(seqCount) + breakList[size++] = seqCount; + + for(i = 0; i < lineBreakPairCount; i++) + { + U32 *p = lineBreakPairs + i * 2; + p[0] = (p[0] << 8) | code[p[1]]; + } + + // Let the telnet debugger know that this code + // block has been loaded and that it can add break + // points it has for it. + if ( TelDebugger ) + TelDebugger->addAllBreakpoints( this ); +} + +bool CodeBlock::read(StringTableEntry fileName, Stream &st) +{ + const StringTableEntry exePath = Platform::getMainDotCsDir(); + const StringTableEntry cwd = Platform::getCurrentDirectory(); + + name = fileName; + + if(fileName) + { + fullPath = NULL; + + if(Platform::isFullPath(fileName)) + fullPath = fileName; + + if(dStrnicmp(exePath, fileName, dStrlen(exePath)) == 0) + name = StringTable->insert(fileName + dStrlen(exePath) + 1, true); + else if(dStrnicmp(cwd, fileName, dStrlen(cwd)) == 0) + name = StringTable->insert(fileName + dStrlen(cwd) + 1, true); + + if(fullPath == NULL) + { + char buf[1024]; + fullPath = StringTable->insert(Platform::makeFullPathName(fileName, buf, sizeof(buf)), true); + } + + modPath = Con::getModNameFromPath(fileName); + } + + // + if (name) + { + if (const char *slash = dStrchr(this->name, '/')) + { + char root[512]; + dStrncpy(root, this->name, slash-this->name); + root[slash-this->name] = 0; + mRoot = StringTable->insert(root); + } + } + + // + addToCodeList(); + + U32 globalSize,size,i; + st.read(&size); + if(size) + { + globalStrings = new char[size]; + globalStringsMaxLen = size; + st.read(size, globalStrings); + } + globalSize = size; + st.read(&size); + if(size) + { + functionStrings = new char[size]; + functionStringsMaxLen = size; + st.read(size, functionStrings); + } + st.read(&size); + if(size) + { + globalFloats = new F64[size]; + for(U32 i = 0; i < size; i++) + st.read(&globalFloats[i]); + } + st.read(&size); + if(size) + { + functionFloats = new F64[size]; + for(U32 i = 0; i < size; i++) + st.read(&functionFloats[i]); + } + U32 codeSize; + st.read(&codeSize); + st.read(&lineBreakPairCount); + + U32 totSize = codeSize + lineBreakPairCount * 2; + code = new U32[totSize]; + + for(i = 0; i < codeSize; i++) + { + U8 b; + st.read(&b); + if(b == 0xFF) + st.read(&code[i]); + else + code[i] = b; + } + + for(i = codeSize; i < totSize; i++) + st.read(&code[i]); + + lineBreakPairs = code + codeSize; + + // StringTable-ize our identifiers. + U32 identCount; + st.read(&identCount); + while(identCount--) + { + U32 offset; + st.read(&offset); + StringTableEntry ste; + if(offset < globalSize) + ste = StringTable->insert(globalStrings + offset); + else + ste = StringTable->insert(""); + U32 count; + st.read(&count); + while(count--) + { + U32 ip; + st.read(&ip); + code[ip] = *((U32 *) &ste); + } + } + + if(lineBreakPairCount) + calcBreakList(); + + return true; +} + + +bool CodeBlock::compile(const char *codeFileName, StringTableEntry fileName, const char *inScript, bool overrideNoDso) +{ + // This will return true, but return value is ignored + char *script; + chompUTF8BOM( inScript, &script ); + + gSyntaxError = false; + + consoleAllocReset(); + + STEtoU32 = compileSTEtoU32; + + gStatementList = NULL; + + // Set up the parser. + smCurrentParser = getParserForFile(fileName); + AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName)); + + // Now do some parsing. + smCurrentParser->setScanBuffer(script, fileName); + smCurrentParser->restart(NULL); + smCurrentParser->parse(); + + if(gSyntaxError) + { + consoleAllocReset(); + return false; + } + +#ifdef TORQUE_NO_DSO_GENERATION + if(!overrideNoDso) + return false; +#endif // !TORQUE_NO_DSO_GENERATION + + FileStream st; + if(!st.open(codeFileName, Torque::FS::File::Write)) + return false; + st.write(U32(Con::DSOVersion)); + + // Reset all our value tables... + resetTables(); + + smInFunction = false; + smBreakLineCount = 0; + setBreakCodeBlock(this); + + if(gStatementList) + codeSize = precompileBlock(gStatementList, 0) + 1; + else + codeSize = 1; + + lineBreakPairCount = smBreakLineCount; + code = new U32[codeSize + smBreakLineCount * 2]; + lineBreakPairs = code + codeSize; + + // Write string table data... + getGlobalStringTable().write(st); + getFunctionStringTable().write(st); + + // Write float table data... + getGlobalFloatTable().write(st); + getFunctionFloatTable().write(st); + + smBreakLineCount = 0; + U32 lastIp; + if(gStatementList) + lastIp = compileBlock(gStatementList, code, 0, 0, 0); + else + lastIp = 0; + + if(lastIp != codeSize - 1) + Con::errorf(ConsoleLogEntry::General, "CodeBlock::compile - precompile size mismatch, a precompile/compile function pair is probably mismatched."); + + code[lastIp++] = OP_RETURN; + U32 totSize = codeSize + smBreakLineCount * 2; + st.write(codeSize); + st.write(lineBreakPairCount); + + // Write out our bytecode, doing a bit of compression for low numbers. + U32 i; + for(i = 0; i < codeSize; i++) + { + if(code[i] < 0xFF) + st.write(U8(code[i])); + else + { + st.write(U8(0xFF)); + st.write(code[i]); + } + } + + // Write the break info... + for(i = codeSize; i < totSize; i++) + st.write(code[i]); + + getIdentTable().write(st); + + consoleAllocReset(); + st.close(); + + return true; + + +} + +const char *CodeBlock::compileExec(StringTableEntry fileName, const char *inString, bool noCalls, int setFrame) +{ + // Check for a UTF8 script file + char *string; + chompUTF8BOM( inString, &string ); + + STEtoU32 = evalSTEtoU32; + consoleAllocReset(); + + name = fileName; + + if(fileName) + { + const StringTableEntry exePath = Platform::getMainDotCsDir(); + const StringTableEntry cwd = Platform::getCurrentDirectory(); + + fullPath = NULL; + + if(Platform::isFullPath(fileName)) + fullPath = fileName; + + if(dStrnicmp(exePath, fileName, dStrlen(exePath)) == 0) + name = StringTable->insert(fileName + dStrlen(exePath) + 1, true); + else if(dStrnicmp(cwd, fileName, dStrlen(cwd)) == 0) + name = StringTable->insert(fileName + dStrlen(cwd) + 1, true); + + if(fullPath == NULL) + { + char buf[1024]; + fullPath = StringTable->insert(Platform::makeFullPathName(fileName, buf, sizeof(buf)), true); + } + + modPath = Con::getModNameFromPath(fileName); + } + + if(name) + addToCodeList(); + + gStatementList = NULL; + + // Set up the parser. + smCurrentParser = getParserForFile(fileName); + AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName)); + + // Now do some parsing. + smCurrentParser->setScanBuffer(string, fileName); + smCurrentParser->restart(NULL); + smCurrentParser->parse(); + + if(!gStatementList) + { + delete this; + return ""; + } + + resetTables(); + + smInFunction = false; + smBreakLineCount = 0; + setBreakCodeBlock(this); + + codeSize = precompileBlock(gStatementList, 0) + 1; + + lineBreakPairCount = smBreakLineCount; + + globalStrings = getGlobalStringTable().build(); + globalStringsMaxLen = getGlobalStringTable().totalLen; + + functionStrings = getFunctionStringTable().build(); + functionStringsMaxLen = getFunctionStringTable().totalLen; + + globalFloats = getGlobalFloatTable().build(); + functionFloats = getFunctionFloatTable().build(); + + code = new U32[codeSize + lineBreakPairCount * 2]; + lineBreakPairs = code + codeSize; + + smBreakLineCount = 0; + U32 lastIp = compileBlock(gStatementList, code, 0, 0, 0); + code[lastIp++] = OP_RETURN; + + consoleAllocReset(); + + if(lineBreakPairCount && fileName) + calcBreakList(); + + if(lastIp != codeSize) + Con::warnf(ConsoleLogEntry::General, "precompile size mismatch, precompile: %d compile: %d", codeSize, lastIp); + + return exec(0, fileName, NULL, 0, 0, noCalls, NULL, setFrame); +} + +//------------------------------------------------------------------------- + +void CodeBlock::incRefCount() +{ + refCount++; +} + +void CodeBlock::decRefCount() +{ + refCount--; + if(!refCount) + delete this; +} diff --git a/console/codeBlock.h b/console/codeBlock.h new file mode 100644 index 0000000..e70c4db --- /dev/null +++ b/console/codeBlock.h @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CODEBLOCK_H_ +#define _CODEBLOCK_H_ + +#include "console/compiler.h" +#include "console/consoleParser.h" + +class Stream; + +/// Core TorqueScript code management class. +/// +/// This class represents a block of code, usually mapped directly to a file. +class CodeBlock +{ +private: + static CodeBlock* smCodeBlockList; + static CodeBlock* smCurrentCodeBlock; + +public: + static U32 smBreakLineCount; + static bool smInFunction; + static Compiler::ConsoleParser * smCurrentParser; + + static CodeBlock* getCurrentBlock() + { + return smCurrentCodeBlock; + } + + static CodeBlock *getCodeBlockList() + { + return smCodeBlockList; + } + + static StringTableEntry getCurrentCodeBlockName(); + static StringTableEntry getCurrentCodeBlockFullPath(); + static StringTableEntry getCurrentCodeBlockModName(); + static CodeBlock *find(StringTableEntry); + + CodeBlock(); + ~CodeBlock(); + + StringTableEntry name; + StringTableEntry fullPath; + StringTableEntry modPath; + + char *globalStrings; + char *functionStrings; + + U32 functionStringsMaxLen; + U32 globalStringsMaxLen; + + F64 *globalFloats; + F64 *functionFloats; + + U32 codeSize; + U32 *code; + + U32 refCount; + U32 lineBreakPairCount; + U32 *lineBreakPairs; + U32 breakListSize; + U32 *breakList; + CodeBlock *nextFile; + StringTableEntry mRoot; + + + void addToCodeList(); + void removeFromCodeList(); + void calcBreakList(); + void clearAllBreaks(); + void setAllBreaks(); + + /// Returns the first breakable line or 0 if none was found. + /// @param lineNumber The one based line number. + U32 findFirstBreakLine(U32 lineNumber); + + void clearBreakpoint(U32 lineNumber); + + /// Set a OP_BREAK instruction on a line. If a break + /// is not possible on that line it returns false. + /// @param lineNumber The one based line number. + bool setBreakpoint(U32 lineNumber); + + void findBreakLine(U32 ip, U32 &line, U32 &instruction); + void getFunctionArgs(char buffer[1024], U32 offset); + const char *getFileLine(U32 ip); + + bool read(StringTableEntry fileName, Stream &st); + bool compile(const char *dsoName, StringTableEntry fileName, const char *script, bool overrideNoDso = false); + + void incRefCount(); + void decRefCount(); + + /// Compiles and executes a block of script storing the compiled code in this + /// CodeBlock. If there is no filename breakpoints will not be generated and + /// the CodeBlock will not be added to the linked list of loaded CodeBlocks. + /// Note that if the script contains no executable statements the CodeBlock + /// will delete itself on return an empty string. The return string is any + /// result of the code executed, if any, or an empty string. + /// + /// @param fileName The file name, including path and extension, for the + /// block of code or an empty string. + /// @param script The script code to compile and execute. + /// @param noCalls Skips calling functions from the script. + /// @param setFrame A zero based index of the stack frame to execute the code + /// with, zero being the top of the stack. If the the index is + /// -1 a new frame is created. If the index is out of range the + /// top stack frame is used. + const char *compileExec(StringTableEntry fileName, const char *script, + bool noCalls, int setFrame = -1 ); + + /// Executes the existing code in the CodeBlock. The return string is any + /// result of the code executed, if any, or an empty string. + /// + /// @param offset The instruction offset to start executing from. + /// @param fnName The name of the function to execute or null. + /// @param ns The namespace of the function to execute or null. + /// @param argc The number of parameters passed to the function or + /// zero to execute code outside of a function. + /// @param argv The function parameter list. + /// @param noCalls Skips calling functions from the script. + /// @param setFrame A zero based index of the stack frame to execute the code + /// with, zero being the top of the stack. If the the index is + /// -1 a new frame is created. If the index is out of range the + /// top stack frame is used. + /// @param packageName The code package name or null. + const char *exec(U32 offset, const char *fnName, Namespace *ns, U32 argc, + const char **argv, bool noCalls, StringTableEntry packageName, + S32 setFrame = -1); +}; + +#endif \ No newline at end of file diff --git a/console/compiledEval.cpp b/console/compiledEval.cpp new file mode 100644 index 0000000..95c8887 --- /dev/null +++ b/console/compiledEval.cpp @@ -0,0 +1,1798 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" + +#include "core/strings/findMatch.h" +#include "core/strings/stringUnit.h" +#include "console/consoleInternal.h" +#include "core/stream/fileStream.h" +#include "console/compiler.h" + +#include "console/simBase.h" +#include "console/telnetDebugger.h" +#include "sim/netStringTable.h" +#include "console/ICallMethod.h" +#include "console/stringStack.h" +#include "util/messaging/message.h" +#include "core/frameAllocator.h" + +#ifndef TORQUE_TGB_ONLY +#include "materials/materialDefinition.h" +#include "materials/materialManager.h" +#endif + +using namespace Compiler; + +enum EvalConstants { + MaxStackSize = 1024, + MethodOnComponent = -2 +}; + +namespace Con +{ +// Current script file name and root, these are registered as +// console variables. +extern StringTableEntry gCurrentFile; +extern StringTableEntry gCurrentRoot; +} + +F64 floatStack[MaxStackSize]; +S64 intStack[MaxStackSize]; + +StringStack STR; + +U32 _FLT = 0; +U32 _UINT = 0; + +namespace Con +{ + const char *getNamespaceList(Namespace *ns) + { + U32 size = 1; + Namespace * walk; + for(walk = ns; walk; walk = walk->mParent) + size += dStrlen(walk->mName) + 4; + char *ret = Con::getReturnBuffer(size); + ret[0] = 0; + for(walk = ns; walk; walk = walk->mParent) + { + dStrcat(ret, walk->mName); + if(walk->mParent) + dStrcat(ret, " -> "); + } + return ret; + } +} + +//------------------------------------------------------------ + +F64 consoleStringToNumber(const char *str, StringTableEntry file, U32 line) +{ + F64 val = dAtof(str); + if(val != 0) + return val; + else if(!dStricmp(str, "true")) + return 1; + else if(!dStricmp(str, "false")) + return 0; + else if(file) + { + Con::warnf(ConsoleLogEntry::General, "%s (%d): string always evaluates to 0.", file, line); + return 0; + } + return 0; +} + +//------------------------------------------------------------ + +namespace Con +{ + + char *getReturnBuffer(U32 bufferSize) + + { + return STR.getReturnBuffer(bufferSize); + } + + char *getReturnBuffer( const char *stringToCopy ) + { + char *ret = STR.getReturnBuffer( dStrlen( stringToCopy ) + 1 ); + dStrcpy( ret, stringToCopy ); + ret[dStrlen( stringToCopy )] = '\0'; + return ret; + } + + char *getArgBuffer(U32 bufferSize) + { + return STR.getArgBuffer(bufferSize); + } + + char *getFloatArg(F64 arg) + { + char *ret = STR.getArgBuffer(32); + dSprintf(ret, 32, "%g", arg); + return ret; + } + + char *getIntArg(S32 arg) + { + char *ret = STR.getArgBuffer(32); + dSprintf(ret, 32, "%d", arg); + return ret; + } + + char* getStringArg( const String& arg ) + { + const U32 size = arg.size(); + char* ret = STR.getArgBuffer( size ); + dMemcpy( ret, arg.c_str(), size ); + return ret; + } +} + +//------------------------------------------------------------ + +inline void ExprEvalState::setCurVarName(StringTableEntry name) +{ + if(name[0] == '$') + currentVariable = globalVars.lookup(name); + else if(stack.size()) + currentVariable = stack.last()->lookup(name); + if(!currentVariable && gWarnUndefinedScriptVariables) + Con::warnf(ConsoleLogEntry::Script, "Variable referenced before assignment: %s", name); +} + +inline void ExprEvalState::setCurVarNameCreate(StringTableEntry name) +{ + if(name[0] == '$') + currentVariable = globalVars.add(name); + else if(stack.size()) + currentVariable = stack.last()->add(name); + else + { + currentVariable = NULL; + Con::warnf(ConsoleLogEntry::Script, "Accessing local variable in global scope... failed: %s", name); + } +} + +//------------------------------------------------------------ + +inline S32 ExprEvalState::getIntVariable() +{ + return currentVariable ? currentVariable->getIntValue() : 0; +} + +inline F64 ExprEvalState::getFloatVariable() +{ + return currentVariable ? currentVariable->getFloatValue() : 0; +} + +inline const char *ExprEvalState::getStringVariable() +{ + return currentVariable ? currentVariable->getStringValue() : ""; +} + +//------------------------------------------------------------ + +inline void ExprEvalState::setIntVariable(S32 val) +{ + AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!"); + currentVariable->setIntValue(val); +} + +inline void ExprEvalState::setFloatVariable(F64 val) +{ + AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!"); + currentVariable->setFloatValue(val); +} + +inline void ExprEvalState::setStringVariable(const char *val) +{ + AssertFatal(currentVariable != NULL, "Invalid evaluator state - trying to set null variable!"); + currentVariable->setStringValue(val); +} + +//------------------------------------------------------------ + +void CodeBlock::getFunctionArgs(char buffer[1024], U32 ip) +{ + U32 fnArgc = code[ip + 5]; + buffer[0] = 0; + for(U32 i = 0; i < fnArgc; i++) + { + StringTableEntry var = U32toSTE(code[ip + i + 6]); + + // Add a comma so it looks nice! + if(i != 0) + dStrcat(buffer, ", "); + + dStrcat(buffer, "var "); + + // Try to capture junked parameters + if(var[0]) + dStrcat(buffer, var+1); + else + dStrcat(buffer, "JUNK"); + } +} + +// Gets a component of an object's field value or a variable and returns it +// in val. +static void getFieldComponent( SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField, char val[] ) +{ + const char* prevVal = NULL; + + // Grab value from object. + if( object && field ) + prevVal = object->getDataField( field, array ); + + // Otherwise, grab from the string stack. The value coming in will always + // be a string because that is how multicomponent variables are handled. + else + prevVal = STR.getStringValue(); + + // Make sure we got a value. + if ( prevVal && *prevVal ) + { + static const StringTableEntry xyzw[] = + { + StringTable->insert( "x" ), + StringTable->insert( "y" ), + StringTable->insert( "z" ), + StringTable->insert( "w" ) + }; + + static const StringTableEntry rgba[] = + { + StringTable->insert( "r" ), + StringTable->insert( "g" ), + StringTable->insert( "b" ), + StringTable->insert( "a" ) + }; + + // Translate xyzw and rgba into the indexed component + // of the variable or field. + if ( subField == xyzw[0] || subField == rgba[0] ) + dStrcpy( val, StringUnit::getUnit( prevVal, 0, " \t\n") ); + + else if ( subField == xyzw[1] || subField == rgba[1] ) + dStrcpy( val, StringUnit::getUnit( prevVal, 1, " \t\n") ); + + else if ( subField == xyzw[2] || subField == rgba[2] ) + dStrcpy( val, StringUnit::getUnit( prevVal, 2, " \t\n") ); + + else if ( subField == xyzw[3] || subField == rgba[3] ) + dStrcpy( val, StringUnit::getUnit( prevVal, 3, " \t\n") ); + + else + val[0] = 0; + } + else + val[0] = 0; +} + +// Sets a component of an object's field value based on the sub field. 'x' will +// set the first field, 'y' the second, and 'z' the third. +static void setFieldComponent( SimObject* object, StringTableEntry field, const char* array, StringTableEntry subField ) +{ + // Copy the current string value + char strValue[1024]; + dStrncpy( strValue, STR.getStringValue(), 1024 ); + + char val[1024] = ""; + const char* prevVal = NULL; + + // Set the value on an object field. + if( object && field ) + prevVal = object->getDataField( field, array ); + + // Set the value on a variable. + else if( gEvalState.currentVariable ) + prevVal = gEvalState.getStringVariable(); + + // Ensure that the variable has a value + if (!prevVal) + return; + + static const StringTableEntry xyzw[] = + { + StringTable->insert( "x" ), + StringTable->insert( "y" ), + StringTable->insert( "z" ), + StringTable->insert( "w" ) + }; + + static const StringTableEntry rgba[] = + { + StringTable->insert( "r" ), + StringTable->insert( "g" ), + StringTable->insert( "b" ), + StringTable->insert( "a" ) + }; + + // Insert the value into the specified + // component of the string. + if ( subField == xyzw[0] || subField == rgba[0] ) + dStrcpy( val, StringUnit::setUnit( prevVal, 0, strValue, " \t\n") ); + + else if ( subField == xyzw[1] || subField == rgba[1] ) + dStrcpy( val, StringUnit::setUnit( prevVal, 1, strValue, " \t\n") ); + + else if ( subField == xyzw[2] || subField == rgba[2] ) + dStrcpy( val, StringUnit::setUnit( prevVal, 2, strValue, " \t\n") ); + + else if ( subField == xyzw[3] || subField == rgba[3] ) + dStrcpy( val, StringUnit::setUnit( prevVal, 3, strValue, " \t\n") ); + + if ( val[0] != 0 ) + { + // Update the field or variable. + if( object && field ) + object->setDataField( field, 0, val ); + else if( gEvalState.currentVariable ) + gEvalState.setStringVariable( val ); + } +} + +const char *CodeBlock::exec(U32 ip, const char *functionName, Namespace *thisNamespace, U32 argc, const char **argv, bool noCalls, StringTableEntry packageName, S32 setFrame) +{ +#ifdef TORQUE_DEBUG + U32 stackStart = STR.mStartStackSize; +#endif + + static char traceBuffer[1024]; + U32 i; + + incRefCount(); + F64 *curFloatTable; + char *curStringTable; + S32 curStringTableLen = 0; //clint to ensure we dont overwrite it + STR.clearFunctionOffset(); + StringTableEntry thisFunctionName = NULL; + bool popFrame = false; + if(argv) + { + // assume this points into a function decl: + U32 fnArgc = code[ip + 5]; + thisFunctionName = U32toSTE(code[ip]); + argc = getMin(argc-1, fnArgc); // argv[0] is func name + if(gEvalState.traceOn) + { + traceBuffer[0] = 0; + dStrcat(traceBuffer, "Entering "); + if(packageName) + { + dStrcat(traceBuffer, "["); + dStrcat(traceBuffer, packageName); + dStrcat(traceBuffer, "]"); + } + if(thisNamespace && thisNamespace->mName) + { + dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), + "%s::%s(", thisNamespace->mName, thisFunctionName); + } + else + { + dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), + "%s(", thisFunctionName); + } + for(i = 0; i < argc; i++) + { + dStrcat(traceBuffer, argv[i+1]); + if(i != argc - 1) + dStrcat(traceBuffer, ", "); + } + dStrcat(traceBuffer, ")"); + Con::printf("%s", traceBuffer); + } + gEvalState.pushFrame(thisFunctionName, thisNamespace); + popFrame = true; + for(i = 0; i < argc; i++) + { + StringTableEntry var = U32toSTE(code[ip + i + 6]); + gEvalState.setCurVarNameCreate(var); + gEvalState.setStringVariable(argv[i+1]); + } + ip = ip + fnArgc + 6; + curFloatTable = functionFloats; + curStringTable = functionStrings; + curStringTableLen = functionStringsMaxLen; + } + else + { + curFloatTable = globalFloats; + curStringTable = globalStrings; + curStringTableLen = globalStringsMaxLen; + + // If requested stack frame isn't available, request a new one + // (this prevents assert failures when creating local + // variables without a stack frame) + if (gEvalState.stack.size() <= setFrame) + setFrame = -1; + + // Do we want this code to execute using a new stack frame? + if (setFrame < 0) + { + gEvalState.pushFrame(NULL, NULL); + popFrame = true; + } + else + { + // We want to copy a reference to an existing stack frame + // on to the top of the stack. Any change that occurs to + // the locals during this new frame will also occur in the + // original frame. + S32 stackIndex = gEvalState.stack.size() - setFrame - 1; + gEvalState.pushFrameRef( stackIndex ); + popFrame = true; + } + } + + // Grab the state of the telenet debugger here once + // so that the push and pop frames are always balanced. + const bool telDebuggerOn = TelDebugger && TelDebugger->isConnected(); + if ( telDebuggerOn && setFrame < 0 ) + TelDebugger->pushStackFrame(); + + StringTableEntry var, objParent; + StringTableEntry fnName; + StringTableEntry fnNamespace, fnPackage; + + // Add local object creation stack [7/9/2007 Black] + static const U32 objectCreationStackSize = 16; + U32 objectCreationStackIndex = 0; + struct { + SimObject *newObject; + U32 failJump; + } objectCreationStack[ objectCreationStackSize ]; + + SimObject *currentNewObject = 0; + U32 failJump = 0; + StringTableEntry prevField = NULL; + StringTableEntry curField = NULL; + SimObject *prevObject = NULL; + SimObject *curObject = NULL; + SimObject *saveObject=NULL; + Namespace::Entry *nsEntry; + Namespace *ns; + const char* curFNDocBlock = NULL; + const char* curNSDocBlock = NULL; + const S32 nsDocLength = 128; + char nsDocBlockClass[nsDocLength]; + + U32 callArgc; + const char **callArgv; + + static char curFieldArray[256]; + static char prevFieldArray[256]; + + CodeBlock *saveCodeBlock = smCurrentCodeBlock; + smCurrentCodeBlock = this; + if(this->name) + { + Con::gCurrentFile = this->name; + Con::gCurrentRoot = mRoot; + } + const char * val; + + // The frame temp is used by the variable accessor ops (OP_SAVEFIELD_* and + // OP_LOADFIELD_*) to store temporary values for the fields. + static S32 VAL_BUFFER_SIZE = 1024; + FrameTemp valBuffer( VAL_BUFFER_SIZE ); + + for(;;) + { + U32 instruction = code[ip++]; +breakContinue: + switch(instruction) + { + case OP_FUNC_DECL: + if(!noCalls) + { + fnName = U32toSTE(code[ip]); + fnNamespace = U32toSTE(code[ip+1]); + fnPackage = U32toSTE(code[ip+2]); + bool hasBody = bool(code[ip+3]); + + if (dStricmp(fnName, "isDemo") == 0 || + dStricmp(fnName, "getObjectLimit") == 0 || + dStricmp(fnName, "realQuit") == 0) + { + ip = code[ip + 4]; + break; + } + + Namespace::unlinkPackages(); + ns = Namespace::find(fnNamespace, fnPackage); + ns->addFunction(fnName, this, hasBody ? ip : 0, curFNDocBlock ? dStrdup( curFNDocBlock ) : NULL );// if no body, set the IP to 0 + if( curNSDocBlock ) + { + if( fnNamespace == StringTable->lookup( nsDocBlockClass ) ) + { + char *usageStr = dStrdup( curNSDocBlock ); + usageStr[dStrlen(usageStr)] = '\0'; + ns->mUsage = usageStr; + ns->mCleanUpUsage = true; + curNSDocBlock = NULL; + } + } + Namespace::relinkPackages(); + + // If we had a docblock, it's definitely not valid anymore, so clear it out. + curFNDocBlock = NULL; + + //Con::printf("Adding function %s::%s (%d)", fnNamespace, fnName, ip); + } + ip = code[ip + 4]; + break; + + case OP_CREATE_OBJECT: + { + // Read some useful info. + objParent = U32toSTE(code[ip ]); + bool isDataBlock = code[ip + 1]; + bool isInternal = code[ip + 2]; + bool isSingleton = code[ip + 3]; + U32 lineNumber = code[ip + 4]; + failJump = code[ip + 5]; + + // If we don't allow calls, we certainly don't allow creating objects! + // Moved this to after failJump is set. Engine was crashing when + // noCalls = true and an object was being created at the beginning of + // a file. ADL. + if(noCalls) + { + ip = failJump; + break; + } + + // Push the old info to the stack + //Assert( objectCreationStackIndex < objectCreationStackSize ); + objectCreationStack[ objectCreationStackIndex ].newObject = currentNewObject; + objectCreationStack[ objectCreationStackIndex++ ].failJump = failJump; + + // Get the constructor information off the stack. + STR.getArgcArgv(NULL, &callArgc, &callArgv, true); + const char* objectName = callArgv[ 2 ]; + + // Con::printf("Creating object..."); + + // objectName = argv[1]... + currentNewObject = NULL; + + // Are we creating a datablock? If so, deal with case where we override + // an old one. + if(isDataBlock) + { + // Con::printf(" - is a datablock"); + + // Find the old one if any. + SimObject *db = Sim::getDataBlockGroup()->findObject( objectName ); + + // Make sure we're not changing types on ourselves... + if(db && dStricmp(db->getClassName(), callArgv[1])) + { + Con::errorf(ConsoleLogEntry::General, "Cannot re-declare data block %s with a different class.", objectName); + ip = failJump; + break; + } + + // If there was one, set the currentNewObject and move on. + if(db) + currentNewObject = db; + } + else if (!isInternal) + { + // IF we aren't looking at a local/internal object, then check if + // this object already exists in the global space + + SimObject *obj = Sim::findObject( objectName ); + if (obj /*&& !obj->isLocalName()*/) + { + if ( isSingleton ) + { + // Make sure we're not trying to change types + if ( dStricmp( obj->getClassName(), callArgv[1] ) != 0 ) + { + Con::errorf(ConsoleLogEntry::General, "%d: Cannot re-declare object [%s] with a different class [%s] - was [%s].", + lineNumber, objectName, callArgv[1], obj->getClassName()); + ip = failJump; + break; + } + + // We're creating a singleton, so use the found object + // instead of creating a new object. + currentNewObject = obj; + } + else + { + const char* redefineBehavior = Con::getVariable( "$Con::redefineBehavior" ); + + if( dStricmp( redefineBehavior, "replaceExisting" ) == 0 ) + { + obj->deleteObject(); + obj = NULL; + } + else if( dStricmp( redefineBehavior, "renameNew" ) == 0 ) + { + for( U32 i = 1;; ++ i ) + { + String newName = String::ToString( "%s%i", objectName, i ); + if( !Sim::findObject( newName ) ) + { + objectName = StringTable->insert( newName ); + break; + } + } + } + else if( dStricmp( redefineBehavior, "unnameNew" ) == 0 ) + { + objectName = StringTable->insert( "" ); + } + else if( dStricmp( redefineBehavior, "postfixNew" ) == 0 ) + { + const char* postfix = Con::getVariable( "$Con::redefineBehaviorPostfix" ); + String newName = String::ToString( "%s%s", objectName, postfix ); + + if( Sim::findObject( newName ) ) + { + Con::errorf( ConsoleLogEntry::General, "%d: Cannot re-declare object with postfix [%s].", + lineNumber, newName.c_str() ); + ip = failJump; + break; + } + else + objectName = StringTable->insert( newName ); + } + else + { + Con::errorf(ConsoleLogEntry::General, "%d: Cannot re-declare object [%s].", + lineNumber, objectName); + ip = failJump; + break; + } + } + } + } + + if(!currentNewObject) + { + // Well, looks like we have to create a new object. + ConsoleObject *object = ConsoleObject::create(callArgv[1]); + + // Deal with failure! + if(!object) + { + Con::errorf(ConsoleLogEntry::General, "%d: Unable to instantiate non-conobject class %s.", lineNumber, callArgv[1]); + ip = failJump; + break; + } + + // Do special datablock init if appropros + if(isDataBlock) + { + SimDataBlock *dataBlock = dynamic_cast(object); + if(dataBlock) + { + dataBlock->assignId(); + } + else + { + // They tried to make a non-datablock with a datablock keyword! + Con::errorf(ConsoleLogEntry::General, "%d: Unable to instantiate non-datablock class %s.", lineNumber, callArgv[1]); + + // Clean up... + delete object; + ip = failJump; + break; + } + } + + // Finally, set currentNewObject to point to the new one. + currentNewObject = dynamic_cast(object); + + // Deal with the case of a non-SimObject. + if(!currentNewObject) + { + Con::errorf(ConsoleLogEntry::General, "%d: Unable to instantiate non-SimObject class %s.", lineNumber, callArgv[1]); + delete object; + ip = failJump; + break; + } + + // Set the declaration line + currentNewObject->setDeclarationLine(lineNumber); + + // Set the file that this object was created in + currentNewObject->setFilename(name); + + // Does it have a parent object? (ie, the copy constructor : syntax, not inheriance) + if(*objParent) + { + // Find it! + SimObject *parent; + if(Sim::findObject(objParent, parent)) + { + // Con::printf(" - Parent object found: %s", parent->getClassName()); + + // and suck the juices from it! + currentNewObject->assignFieldsFrom(parent); + } + else + Con::errorf(ConsoleLogEntry::General, "%d: Unable to find parent object %s for %s.", lineNumber, objParent, callArgv[1]); + + // Mm! Juices! + } + + // If a name was passed, assign it. + if( objectName[ 0 ] ) + { + if( !isInternal ) + currentNewObject->assignName( objectName ); + else + currentNewObject->setInternalName( objectName ); + + // Set the original name + currentNewObject->setOriginalName( objectName ); + } + + // Do the constructor parameters. + if(!currentNewObject->processArguments(callArgc-3, callArgv+3)) + { + delete currentNewObject; + currentNewObject = NULL; + ip = failJump; + break; + } + + // If it's not a datablock, allow people to modify bits of it. + if(!isDataBlock) + { + currentNewObject->setModStaticFields(true); + currentNewObject->setModDynamicFields(true); + } + } + + // Advance the IP past the create info... + ip += 6; + break; + } + + case OP_ADD_OBJECT: + { + // See OP_SETCURVAR for why we do this. + curFNDocBlock = NULL; + curNSDocBlock = NULL; + + // Do we place this object at the root? + bool placeAtRoot = code[ip++]; + + // Con::printf("Adding object %s", currentNewObject->getName()); + + // Make sure it wasn't already added, then add it. + if(currentNewObject->isProperlyAdded() == false) + { + bool ret = false; + + Message *msg = dynamic_cast(currentNewObject); + if(msg) + { + SimObjectId id = Message::getNextMessageID(); + if(id != 0xffffffff) + ret = currentNewObject->registerObject(id); + else + Con::errorf("%d: No more object IDs available for messages", currentNewObject->getDeclarationLine()); + } + else + ret = currentNewObject->registerObject(); + + if(! ret) + { + // This error is usually caused by failing to call Parent::initPersistFields in the class' initPersistFields(). + Con::warnf(ConsoleLogEntry::General, "%d: Register object failed for object %s of class %s.", currentNewObject->getDeclarationLine(), currentNewObject->getName(), currentNewObject->getClassName()); + delete currentNewObject; + ip = failJump; + break; + } + } + + // Are we dealing with a datablock? + SimDataBlock *dataBlock = dynamic_cast(currentNewObject); + static String errorStr; + + // If so, preload it. + if(dataBlock && !dataBlock->preload(true, errorStr)) + { + Con::errorf(ConsoleLogEntry::General, "%d: preload failed for %s: %s.", currentNewObject->getDeclarationLine(), + currentNewObject->getName(), errorStr.c_str()); + dataBlock->deleteObject(); + ip = failJump; + break; + } + + // What group will we be added to, if any? + U32 groupAddId = intStack[_UINT]; + SimGroup *grp = NULL; + SimSet *set = NULL; + bool isMessage = dynamic_cast(currentNewObject) != NULL; + + if(!placeAtRoot || !currentNewObject->getGroup()) + { + if(! isMessage) + { + if(! placeAtRoot) + { + // Otherwise just add to the requested group or set. + if(!Sim::findObject(groupAddId, grp)) + Sim::findObject(groupAddId, set); + } + + if(placeAtRoot) + { + // Deal with the instantGroup if we're being put at the root or we're adding to a component. + if( Con::gInstantGroup.isEmpty() + || !Sim::findObject( Con::gInstantGroup, grp ) ) + Sim::findObject(RootGroupId, grp); + } + } + + // If we didn't get a group, then make sure we have a pointer to + // the rootgroup. + if(!grp) + Sim::findObject(RootGroupId, grp); + + // add to the parent group + grp->addObject(currentNewObject); + + // add to any set we might be in + if(set) + set->addObject(currentNewObject); + } + + // store the new object's ID on the stack (overwriting the group/set + // id, if one was given, otherwise getting pushed) + if(placeAtRoot) + intStack[_UINT] = currentNewObject->getId(); + else + intStack[++_UINT] = currentNewObject->getId(); + + break; + } + + case OP_END_OBJECT: + { + // If we're not to be placed at the root, make sure we clean up + // our group reference. + bool placeAtRoot = code[ip++]; + if(!placeAtRoot) + _UINT--; + break; + } + + case OP_FINISH_OBJECT: + { + //Assert( objectCreationStackIndex >= 0 ); + // Restore the object info from the stack [7/9/2007 Black] + currentNewObject = objectCreationStack[ --objectCreationStackIndex ].newObject; + failJump = objectCreationStack[ objectCreationStackIndex ].failJump; + break; + } + + case OP_JMPIFFNOT: + if(floatStack[_FLT--]) + { + ip++; + break; + } + ip = code[ip]; + break; + case OP_JMPIFNOT: + if(intStack[_UINT--]) + { + ip++; + break; + } + ip = code[ip]; + break; + case OP_JMPIFF: + if(!floatStack[_FLT--]) + { + ip++; + break; + } + ip = code[ip]; + break; + case OP_JMPIF: + if(!intStack[_UINT--]) + { + ip ++; + break; + } + ip = code[ip]; + break; + case OP_JMPIFNOT_NP: + if(intStack[_UINT]) + { + _UINT--; + ip++; + break; + } + ip = code[ip]; + break; + case OP_JMPIF_NP: + if(!intStack[_UINT]) + { + _UINT--; + ip++; + break; + } + ip = code[ip]; + break; + case OP_JMP: + ip = code[ip]; + break; + + // This fixes a bug when not explicitly returning a value. + case OP_RETURN_VOID: + STR.setStringValue(""); + // We're falling thru here on purpose. + + case OP_RETURN: + goto execFinished; + + case OP_CMPEQ: + intStack[_UINT+1] = bool(floatStack[_FLT] == floatStack[_FLT-1]); + _UINT++; + _FLT -= 2; + break; + + case OP_CMPGR: + intStack[_UINT+1] = bool(floatStack[_FLT] > floatStack[_FLT-1]); + _UINT++; + _FLT -= 2; + break; + + case OP_CMPGE: + intStack[_UINT+1] = bool(floatStack[_FLT] >= floatStack[_FLT-1]); + _UINT++; + _FLT -= 2; + break; + + case OP_CMPLT: + intStack[_UINT+1] = bool(floatStack[_FLT] < floatStack[_FLT-1]); + _UINT++; + _FLT -= 2; + break; + + case OP_CMPLE: + intStack[_UINT+1] = bool(floatStack[_FLT] <= floatStack[_FLT-1]); + _UINT++; + _FLT -= 2; + break; + + case OP_CMPNE: + intStack[_UINT+1] = bool(floatStack[_FLT] != floatStack[_FLT-1]); + _UINT++; + _FLT -= 2; + break; + + case OP_XOR: + intStack[_UINT-1] = intStack[_UINT] ^ intStack[_UINT-1]; + _UINT--; + break; + + case OP_MOD: + if( intStack[_UINT-1] != 0 ) + intStack[_UINT-1] = intStack[_UINT] % intStack[_UINT-1]; + else + intStack[_UINT-1] = 0; + _UINT--; + break; + + case OP_BITAND: + intStack[_UINT-1] = intStack[_UINT] & intStack[_UINT-1]; + _UINT--; + break; + + case OP_BITOR: + intStack[_UINT-1] = intStack[_UINT] | intStack[_UINT-1]; + _UINT--; + break; + + case OP_NOT: + intStack[_UINT] = !intStack[_UINT]; + break; + + case OP_NOTF: + intStack[_UINT+1] = !floatStack[_FLT]; + _FLT--; + _UINT++; + break; + + case OP_ONESCOMPLEMENT: + intStack[_UINT] = ~intStack[_UINT]; + break; + + case OP_SHR: + intStack[_UINT-1] = intStack[_UINT] >> intStack[_UINT-1]; + _UINT--; + break; + + case OP_SHL: + intStack[_UINT-1] = intStack[_UINT] << intStack[_UINT-1]; + _UINT--; + break; + + case OP_AND: + intStack[_UINT-1] = intStack[_UINT] && intStack[_UINT-1]; + _UINT--; + break; + + case OP_OR: + intStack[_UINT-1] = intStack[_UINT] || intStack[_UINT-1]; + _UINT--; + break; + + case OP_ADD: + floatStack[_FLT-1] = floatStack[_FLT] + floatStack[_FLT-1]; + _FLT--; + break; + + case OP_SUB: + floatStack[_FLT-1] = floatStack[_FLT] - floatStack[_FLT-1]; + _FLT--; + break; + + case OP_MUL: + floatStack[_FLT-1] = floatStack[_FLT] * floatStack[_FLT-1]; + _FLT--; + break; + case OP_DIV: + floatStack[_FLT-1] = floatStack[_FLT] / floatStack[_FLT-1]; + _FLT--; + break; + case OP_NEG: + floatStack[_FLT] = -floatStack[_FLT]; + break; + + case OP_SETCURVAR: + var = U32toSTE(code[ip]); + ip++; + + // If a variable is set, then these must be NULL. It is necessary + // to set this here so that the vector parser can appropriately + // identify whether it's dealing with a vector. + prevField = NULL; + prevObject = NULL; + curObject = NULL; + + gEvalState.setCurVarName(var); + + // In order to let docblocks work properly with variables, we have + // clear the current docblock when we do an assign. This way it + // won't inappropriately carry forward to following function decls. + curFNDocBlock = NULL; + curNSDocBlock = NULL; + break; + + case OP_SETCURVAR_CREATE: + var = U32toSTE(code[ip]); + ip++; + + // See OP_SETCURVAR + prevField = NULL; + prevObject = NULL; + curObject = NULL; + + gEvalState.setCurVarNameCreate(var); + + // See OP_SETCURVAR for why we do this. + curFNDocBlock = NULL; + curNSDocBlock = NULL; + break; + + case OP_SETCURVAR_ARRAY: + var = STR.getSTValue(); + + // See OP_SETCURVAR + prevField = NULL; + prevObject = NULL; + curObject = NULL; + + gEvalState.setCurVarName(var); + + // See OP_SETCURVAR for why we do this. + curFNDocBlock = NULL; + curNSDocBlock = NULL; + break; + + case OP_SETCURVAR_ARRAY_CREATE: + var = STR.getSTValue(); + + // See OP_SETCURVAR + prevField = NULL; + prevObject = NULL; + curObject = NULL; + + gEvalState.setCurVarNameCreate(var); + + // See OP_SETCURVAR for why we do this. + curFNDocBlock = NULL; + curNSDocBlock = NULL; + break; + + case OP_LOADVAR_UINT: + intStack[_UINT+1] = gEvalState.getIntVariable(); + _UINT++; + break; + + case OP_LOADVAR_FLT: + floatStack[_FLT+1] = gEvalState.getFloatVariable(); + _FLT++; + break; + + case OP_LOADVAR_STR: + val = gEvalState.getStringVariable(); + STR.setStringValue(val); + break; + + case OP_SAVEVAR_UINT: + gEvalState.setIntVariable(intStack[_UINT]); + break; + + case OP_SAVEVAR_FLT: + gEvalState.setFloatVariable(floatStack[_FLT]); + break; + + case OP_SAVEVAR_STR: + gEvalState.setStringVariable(STR.getStringValue()); + break; + + case OP_SETCUROBJECT: + // Save the previous object for parsing vector fields. + prevObject = curObject; + val = STR.getStringValue(); + + // Sim::findObject will sometimes find valid objects from + // multi-component strings. This makes sure that doesn't + // happen. + for( const char* check = val; *check; check++ ) + { + if( *check == ' ' ) + { + val = ""; + break; + } + } + curObject = Sim::findObject(val); + break; + + case OP_SETCUROBJECT_INTERNAL: + ++ip; // To skip the recurse flag if the object wasn't found + if(curObject) + { + SimSet *set = dynamic_cast(curObject); + if(set) + { + StringTableEntry intName = StringTable->insert(STR.getStringValue()); + bool recurse = code[ip-1]; + SimObject *obj = set->findObjectByInternalName(intName, recurse); + intStack[_UINT+1] = obj ? obj->getId() : 0; + _UINT++; + } + else + { + Con::errorf(ConsoleLogEntry::Script, "%s: Attempt to use -> on non-set %s of class %s.", getFileLine(ip-2), curObject->getName(), curObject->getClassName()); + intStack[_UINT] = 0; + } + } + break; + + case OP_SETCUROBJECT_NEW: + curObject = currentNewObject; + break; + + case OP_SETCURFIELD: + // Save the previous field for parsing vector fields. + prevField = curField; + dStrcpy( prevFieldArray, curFieldArray ); + curField = U32toSTE(code[ip]); + curFieldArray[0] = 0; + ip++; + break; + + case OP_SETCURFIELD_ARRAY: + dStrcpy(curFieldArray, STR.getStringValue()); + break; + + case OP_SETCURFIELD_TYPE: + if(curObject) + curObject->setDataFieldType(code[ip], curField, curFieldArray); + ip++; + break; + + case OP_LOADFIELD_UINT: + if(curObject) + intStack[_UINT+1] = U32(dAtoi(curObject->getDataField(curField, curFieldArray))); + else + { + // The field is not being retrieved from an object. Maybe it's + // a special accessor? + getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer ); + intStack[_UINT+1] = dAtoi( valBuffer ); + } + _UINT++; + break; + + case OP_LOADFIELD_FLT: + if(curObject) + floatStack[_FLT+1] = dAtof(curObject->getDataField(curField, curFieldArray)); + else + { + // The field is not being retrieved from an object. Maybe it's + // a special accessor? + getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer ); + floatStack[_FLT+1] = dAtof( valBuffer ); + } + _FLT++; + break; + + case OP_LOADFIELD_STR: + if(curObject) + { + val = curObject->getDataField(curField, curFieldArray); + STR.setStringValue( val ); + } + else + { + // The field is not being retrieved from an object. Maybe it's + // a special accessor? + getFieldComponent( prevObject, prevField, prevFieldArray, curField, valBuffer ); + STR.setStringValue( valBuffer ); + } + break; + + case OP_SAVEFIELD_UINT: + STR.setIntValue(intStack[_UINT]); + if(curObject) + curObject->setDataField(curField, curFieldArray, STR.getStringValue()); + else + { + // The field is not being set on an object. Maybe it's + // a special accessor? + setFieldComponent( prevObject, prevField, prevFieldArray, curField ); + prevObject = NULL; + } + break; + + case OP_SAVEFIELD_FLT: + STR.setFloatValue(floatStack[_FLT]); + if(curObject) + curObject->setDataField(curField, curFieldArray, STR.getStringValue()); + else + { + // The field is not being set on an object. Maybe it's + // a special accessor? + setFieldComponent( prevObject, prevField, prevFieldArray, curField ); + prevObject = NULL; + } + break; + + case OP_SAVEFIELD_STR: + if(curObject) + curObject->setDataField(curField, curFieldArray, STR.getStringValue()); + else + { + // The field is not being set on an object. Maybe it's + // a special accessor? + setFieldComponent( prevObject, prevField, prevFieldArray, curField ); + prevObject = NULL; + } + break; + + case OP_STR_TO_UINT: + intStack[_UINT+1] = STR.getIntValue(); + _UINT++; + break; + + case OP_STR_TO_FLT: + floatStack[_FLT+1] = STR.getFloatValue(); + _FLT++; + break; + + case OP_STR_TO_NONE: + // This exists simply to deal with certain typecast situations. + break; + + case OP_FLT_TO_UINT: + intStack[_UINT+1] = (S64)floatStack[_FLT]; + _FLT--; + _UINT++; + break; + + case OP_FLT_TO_STR: + STR.setFloatValue(floatStack[_FLT]); + _FLT--; + break; + + case OP_FLT_TO_NONE: + _FLT--; + break; + + case OP_UINT_TO_FLT: + floatStack[_FLT+1] = (F32)intStack[_UINT]; + _UINT--; + _FLT++; + break; + + case OP_UINT_TO_STR: + STR.setIntValue(intStack[_UINT]); + _UINT--; + break; + + case OP_UINT_TO_NONE: + _UINT--; + break; + + case OP_LOADIMMED_UINT: + intStack[_UINT+1] = code[ip++]; + _UINT++; + break; + + case OP_LOADIMMED_FLT: + floatStack[_FLT+1] = curFloatTable[code[ip]]; + ip++; + _FLT++; + break; + case OP_TAG_TO_STR: + code[ip-1] = OP_LOADIMMED_STR; + // it's possible the string has already been converted + if(U8(curStringTable[code[ip]]) != StringTagPrefixByte) + { + U32 id = GameAddTaggedString(curStringTable + code[ip]); + dSprintf(curStringTable + code[ip] + 1, 7, "%d", id); + *(curStringTable + code[ip]) = StringTagPrefixByte; + } + case OP_LOADIMMED_STR: + STR.setStringValue(curStringTable + code[ip++]); + break; + + case OP_DOCBLOCK_STR: + { + // If the first word of the doc is '\class' or '@class', then this + // is a namespace doc block, otherwise it is a function doc block. + const char* docblock = curStringTable + code[ip++]; + + const char* sansClass = dStrstr( docblock, "@class" ); + if( !sansClass ) + sansClass = dStrstr( docblock, "\\class" ); + + if( sansClass ) + { + // Don't save the class declaration. Scan past the 'class' + // keyword and up to the first whitespace. + sansClass += 7; + S32 index = 0; + while( ( *sansClass != ' ' ) && ( *sansClass != '\n' ) && *sansClass && ( index < ( nsDocLength - 1 ) ) ) + { + nsDocBlockClass[index++] = *sansClass; + sansClass++; + } + nsDocBlockClass[index] = '\0'; + + curNSDocBlock = sansClass + 1; + } + else + curFNDocBlock = docblock; + } + + break; + + case OP_LOADIMMED_IDENT: + STR.setStringValue(U32toSTE(code[ip++])); + break; + + case OP_CALLFUNC_RESOLVE: + // This deals with a function that is potentially living in a namespace. + fnNamespace = U32toSTE(code[ip+1]); + fnName = U32toSTE(code[ip]); + + // Try to look it up. + ns = Namespace::find(fnNamespace); + nsEntry = ns->lookup(fnName); + if(!nsEntry) + { + ip+= 3; + Con::warnf(ConsoleLogEntry::General, + "%s: Unable to find function %s%s%s", + getFileLine(ip-4), fnNamespace ? fnNamespace : "", + fnNamespace ? "::" : "", fnName); + STR.popFrame(); + break; + } + // Now, rewrite our code a bit (ie, avoid future lookups) and fall + // through to OP_CALLFUNC + code[ip+1] = *((U32 *) &nsEntry); + code[ip-1] = OP_CALLFUNC; + + case OP_CALLFUNC: + { + // This routingId is set when we query the object as to whether + // it handles this method. It is set to an enum from the table + // above indicating whether it handles it on a component it owns + // or just on the object. + S32 routingId = 0; + + fnName = U32toSTE(code[ip]); + + //if this is called from inside a function, append the ip and codeptr + if (!gEvalState.stack.empty()) + { + gEvalState.stack.last()->code = this; + gEvalState.stack.last()->ip = ip - 1; + } + + U32 callType = code[ip+2]; + + ip += 3; + STR.getArgcArgv(fnName, &callArgc, &callArgv); + + const char *componentReturnValue = ""; + + if(callType == FuncCallExprNode::FunctionCall) + { + nsEntry = *((Namespace::Entry **) &code[ip-2]); + ns = NULL; + } + else if(callType == FuncCallExprNode::MethodCall) + { + saveObject = gEvalState.thisObject; + gEvalState.thisObject = Sim::findObject(callArgv[1]); + if(!gEvalState.thisObject) + { + // Go back to the previous saved object. + gEvalState.thisObject = saveObject; + + Con::warnf(ConsoleLogEntry::General,"%s: Unable to find object: '%s' attempting to call function '%s'", getFileLine(ip-4), callArgv[1], fnName); + STR.popFrame(); + break; + } + + bool handlesMethod = gEvalState.thisObject->handlesConsoleMethod(fnName,&routingId); + if( handlesMethod && routingId == MethodOnComponent ) + { + ICallMethod *pComponent = dynamic_cast( gEvalState.thisObject ); + if( pComponent ) + componentReturnValue = pComponent->callMethodArgList( callArgc, callArgv, false ); + } + + ns = gEvalState.thisObject->getNamespace(); + if(ns) + nsEntry = ns->lookup(fnName); + else + nsEntry = NULL; + } + else // it's a ParentCall + { + if(thisNamespace) + { + ns = thisNamespace->mParent; + if(ns) + nsEntry = ns->lookup(fnName); + else + nsEntry = NULL; + } + else + { + ns = NULL; + nsEntry = NULL; + } + } + + S32 nsType = -1; + S32 nsMinArgs = 0; + S32 nsMaxArgs = 0; + Namespace::Entry::CallbackUnion * nsCb = NULL; + const char * nsUsage = NULL; + if (nsEntry) + { + nsType = nsEntry->mType; + nsMinArgs = nsEntry->mMinArgs; + nsMaxArgs = nsEntry->mMaxArgs; + nsCb = &nsEntry->cb; + nsUsage = nsEntry->mUsage; + routingId = 0; + } + if(!nsEntry || noCalls) + { + if(!noCalls && !( routingId == MethodOnComponent ) ) + { + Con::warnf(ConsoleLogEntry::General,"%s: Unknown command %s.", getFileLine(ip-4), fnName); + if(callType == FuncCallExprNode::MethodCall) + { + Con::warnf(ConsoleLogEntry::General, " Object %s(%d) %s", + gEvalState.thisObject->getName() ? gEvalState.thisObject->getName() : "", + gEvalState.thisObject->getId(), Con::getNamespaceList(ns) ); + } + } + STR.popFrame(); + + if( routingId == MethodOnComponent ) + STR.setStringValue( componentReturnValue ); + else + STR.setStringValue( "" ); + + break; + } + if(nsEntry->mType == Namespace::Entry::ConsoleFunctionType) + { + const char *ret = ""; + if(nsEntry->mFunctionOffset) + ret = nsEntry->mCode->exec(nsEntry->mFunctionOffset, fnName, nsEntry->mNamespace, callArgc, callArgv, false, nsEntry->mPackage); + + STR.popFrame(); + STR.setStringValue(ret); + } + else + { + const char* nsName = ns? ns->mName: ""; +#ifndef TORQUE_DEBUG + // [tom, 12/13/2006] This stops tools functions from working in the console, + // which is useful behavior when debugging so I'm ifdefing this out for debug builds. + if(nsEntry->mToolOnly && ! Con::isCurrentScriptToolScript()) + { + Con::errorf(ConsoleLogEntry::Script, "%s: %s::%s - attempting to call tools only function from outside of tools.", getFileLine(ip-4), nsName, fnName); + } + else +#endif + if((nsEntry->mMinArgs && S32(callArgc) < nsEntry->mMinArgs) || (nsEntry->mMaxArgs && S32(callArgc) > nsEntry->mMaxArgs)) + { + Con::warnf(ConsoleLogEntry::Script, "%s: %s::%s - wrong number of arguments.", getFileLine(ip-4), nsName, fnName); + Con::warnf(ConsoleLogEntry::Script, "%s: usage: %s", getFileLine(ip-4), nsEntry->mUsage); + STR.popFrame(); + } + else + { + switch(nsEntry->mType) + { + case Namespace::Entry::StringCallbackType: + { + const char *ret = nsEntry->cb.mStringCallbackFunc(gEvalState.thisObject, callArgc, callArgv); + STR.popFrame(); + if(ret != STR.getStringValue()) + STR.setStringValue(ret); + else + STR.setLen(dStrlen(ret)); + break; + } + case Namespace::Entry::IntCallbackType: + { + S32 result = nsEntry->cb.mIntCallbackFunc(gEvalState.thisObject, callArgc, callArgv); + STR.popFrame(); + if(code[ip] == OP_STR_TO_UINT) + { + ip++; + intStack[++_UINT] = result; + break; + } + else if(code[ip] == OP_STR_TO_FLT) + { + ip++; + floatStack[++_FLT] = result; + break; + } + else if(code[ip] == OP_STR_TO_NONE) + ip++; + else + STR.setIntValue(result); + break; + } + case Namespace::Entry::FloatCallbackType: + { + F64 result = nsEntry->cb.mFloatCallbackFunc(gEvalState.thisObject, callArgc, callArgv); + STR.popFrame(); + if(code[ip] == OP_STR_TO_UINT) + { + ip++; + intStack[++_UINT] = (S64)result; + break; + } + else if(code[ip] == OP_STR_TO_FLT) + { + ip++; + floatStack[++_FLT] = result; + break; + } + else if(code[ip] == OP_STR_TO_NONE) + ip++; + else + STR.setFloatValue(result); + break; + } + case Namespace::Entry::VoidCallbackType: + nsEntry->cb.mVoidCallbackFunc(gEvalState.thisObject, callArgc, callArgv); + if(code[ip] != OP_STR_TO_NONE) + Con::warnf(ConsoleLogEntry::General, "%s: Call to %s in %s uses result of void function call.", getFileLine(ip-4), fnName, functionName); + + STR.popFrame(); + STR.setStringValue(""); + break; + case Namespace::Entry::BoolCallbackType: + { + bool result = nsEntry->cb.mBoolCallbackFunc(gEvalState.thisObject, callArgc, callArgv); + STR.popFrame(); + if(code[ip] == OP_STR_TO_UINT) + { + ip++; + intStack[++_UINT] = result; + break; + } + else if(code[ip] == OP_STR_TO_FLT) + { + ip++; + floatStack[++_FLT] = result; + break; + } + else if(code[ip] == OP_STR_TO_NONE) + ip++; + else + STR.setIntValue(result); + break; + } + } + } + } + + if(callType == FuncCallExprNode::MethodCall) + gEvalState.thisObject = saveObject; + break; + } + case OP_ADVANCE_STR: + STR.advance(); + break; + case OP_ADVANCE_STR_APPENDCHAR: + STR.advanceChar(code[ip++]); + break; + + case OP_ADVANCE_STR_COMMA: + STR.advanceChar('_'); + break; + + case OP_ADVANCE_STR_NUL: + STR.advanceChar(0); + break; + + case OP_REWIND_STR: + STR.rewind(); + break; + + case OP_TERMINATE_REWIND_STR: + STR.rewindTerminate(); + break; + + case OP_COMPARE_STR: + intStack[++_UINT] = STR.compare(); + break; + case OP_PUSH: + STR.push(); + break; + + case OP_PUSH_FRAME: + STR.pushFrame(); + break; + + case OP_ASSERT: + { + if( !intStack[_UINT--] ) + { + const char *message = curStringTable + code[ip]; + + U32 breakLine, inst; + findBreakLine( ip - 1, breakLine, inst ); + + if ( PlatformAssert::processAssert( PlatformAssert::Fatal, + name ? name : "eval", + breakLine, + message ) ) + { + if ( TelDebugger && TelDebugger->isConnected() && breakLine > 0 ) + { + TelDebugger->breakProcess(); + } + else + Platform::debugBreak(); + } + } + + ip++; + break; + } + + case OP_BREAK: + { + //append the ip and codeptr before managing the breakpoint! + AssertFatal( !gEvalState.stack.empty(), "Empty eval stack on break!"); + gEvalState.stack.last()->code = this; + gEvalState.stack.last()->ip = ip - 1; + + U32 breakLine; + findBreakLine(ip-1, breakLine, instruction); + if(!breakLine) + goto breakContinue; + TelDebugger->executionStopped(this, breakLine); + goto breakContinue; + } + case OP_INVALID: + + default: + // error! + goto execFinished; + } + } +execFinished: + + if ( telDebuggerOn && setFrame < 0 ) + TelDebugger->popStackFrame(); + + if ( popFrame ) + gEvalState.popFrame(); + + if(argv) + { + if(gEvalState.traceOn) + { + traceBuffer[0] = 0; + dStrcat(traceBuffer, "Leaving "); + + if(packageName) + { + dStrcat(traceBuffer, "["); + dStrcat(traceBuffer, packageName); + dStrcat(traceBuffer, "]"); + } + if(thisNamespace && thisNamespace->mName) + { + dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), + "%s::%s() - return %s", thisNamespace->mName, thisFunctionName, STR.getStringValue()); + } + else + { + dSprintf(traceBuffer + dStrlen(traceBuffer), sizeof(traceBuffer) - dStrlen(traceBuffer), + "%s() - return %s", thisFunctionName, STR.getStringValue()); + } + Con::printf("%s", traceBuffer); + } + } + else + { + delete[] globalStrings; + globalStringsMaxLen = 0; + + delete[] globalFloats; + globalStrings = NULL; + globalFloats = NULL; + } + smCurrentCodeBlock = saveCodeBlock; + if(saveCodeBlock && saveCodeBlock->name) + { + Con::gCurrentFile = saveCodeBlock->name; + Con::gCurrentRoot = saveCodeBlock->mRoot; + } + + decRefCount(); + +#ifdef TORQUE_DEBUG + AssertFatal(!(STR.mStartStackSize > stackStart), "String stack not popped enough in script exec"); + AssertFatal(!(STR.mStartStackSize < stackStart), "String stack popped too much in script exec"); +#endif + return STR.getStringValue(); +} + +//------------------------------------------------------------ diff --git a/console/compiler.cpp b/console/compiler.cpp new file mode 100644 index 0000000..9df7bef --- /dev/null +++ b/console/compiler.cpp @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/telnetDebugger.h" +#include "platform/event.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" + +#include "core/strings/findMatch.h" +#include "console/consoleInternal.h" +#include "core/stream/fileStream.h" +#include "console/compiler.h" + +#include "console/simBase.h" + +namespace Compiler +{ + + F64 consoleStringToNumber(const char *str, StringTableEntry file, U32 line) + { + F64 val = dAtof(str); + if(val != 0) + return val; + else if(!dStricmp(str, "true")) + return 1; + else if(!dStricmp(str, "false")) + return 0; + else if(file) + { + Con::warnf(ConsoleLogEntry::General, "%s (%d): string always evaluates to 0.", file, line); + return 0; + } + return 0; + } + + //------------------------------------------------------------ + + CompilerStringTable *gCurrentStringTable, gGlobalStringTable, gFunctionStringTable; + CompilerFloatTable *gCurrentFloatTable, gGlobalFloatTable, gFunctionFloatTable; + DataChunker gConsoleAllocator; + CompilerIdentTable gIdentTable; + CodeBlock *gCurBreakBlock; + + //------------------------------------------------------------ + + + CodeBlock *getBreakCodeBlock() { return gCurBreakBlock; } + void setBreakCodeBlock(CodeBlock *cb) { gCurBreakBlock = cb; } + + //------------------------------------------------------------ + + U32 evalSTEtoU32(StringTableEntry ste, U32) + { + return *((U32 *) &ste); + } + + U32 compileSTEtoU32(StringTableEntry ste, U32 ip) + { + if(ste) + getIdentTable().add(ste, ip); + return 0; + } + + U32 (*STEtoU32)(StringTableEntry ste, U32 ip) = evalSTEtoU32; + + //------------------------------------------------------------ + + bool gSyntaxError = false; + + //------------------------------------------------------------ + + CompilerStringTable *getCurrentStringTable() { return gCurrentStringTable; } + CompilerStringTable &getGlobalStringTable() { return gGlobalStringTable; } + CompilerStringTable &getFunctionStringTable() { return gFunctionStringTable; } + + void setCurrentStringTable (CompilerStringTable* cst) { gCurrentStringTable = cst; } + + CompilerFloatTable *getCurrentFloatTable() { return gCurrentFloatTable; } + CompilerFloatTable &getGlobalFloatTable() { return gGlobalFloatTable; } + CompilerFloatTable &getFunctionFloatTable() { return gFunctionFloatTable; } + + void setCurrentFloatTable (CompilerFloatTable* cst) { gCurrentFloatTable = cst; } + + CompilerIdentTable &getIdentTable() { return gIdentTable; } + + void precompileIdent(StringTableEntry ident) + { + if(ident) + gGlobalStringTable.add(ident); + } + + void resetTables() + { + setCurrentStringTable(&gGlobalStringTable); + setCurrentFloatTable(&gGlobalFloatTable); + getGlobalFloatTable().reset(); + getGlobalStringTable().reset(); + getFunctionFloatTable().reset(); + getFunctionStringTable().reset(); + getIdentTable().reset(); + } + + void *consoleAlloc(U32 size) { return gConsoleAllocator.alloc(size); } + void consoleAllocReset() { gConsoleAllocator.freeBlocks(); } + +} + +//------------------------------------------------------------------------- + +using namespace Compiler; + +//------------------------------------------------------------------------- + + +U32 CompilerStringTable::add(const char *str, bool caseSens, bool tag) +{ + // Is it already in? + Entry **walk; + for(walk = &list; *walk; walk = &((*walk)->next)) + { + if((*walk)->tag != tag) + continue; + + if(caseSens) + { + if(!dStrcmp((*walk)->string, str)) + return (*walk)->start; + } + else + { + if(!dStricmp((*walk)->string, str)) + return (*walk)->start; + } + } + + // Write it out. + Entry *newStr = (Entry *) consoleAlloc(sizeof(Entry)); + *walk = newStr; + newStr->next = NULL; + newStr->start = totalLen; + U32 len = dStrlen(str) + 1; + if(tag && len < 7) // alloc space for the numeric tag 1 for tag, 5 for # and 1 for nul + len = 7; + totalLen += len; + newStr->string = (char *) consoleAlloc(len); + newStr->len = len; + newStr->tag = tag; + dStrcpy(newStr->string, str); + return newStr->start; +} + +U32 CompilerStringTable::addIntString(U32 value) +{ + dSprintf(buf, sizeof(buf), "%d", value); + return add(buf); +} + +U32 CompilerStringTable::addFloatString(F64 value) +{ + dSprintf(buf, sizeof(buf), "%g", value); + return add(buf); +} + +void CompilerStringTable::reset() +{ + list = NULL; + totalLen = 0; +} + +char *CompilerStringTable::build() +{ + char *ret = new char[totalLen]; + for(Entry *walk = list; walk; walk = walk->next) + dStrcpy(ret + walk->start, walk->string); + return ret; +} + +void CompilerStringTable::write(Stream &st) +{ + st.write(totalLen); + for(Entry *walk = list; walk; walk = walk->next) + st.write(walk->len, walk->string); +} + +//------------------------------------------------------------ + +U32 CompilerFloatTable::add(F64 value) +{ + Entry **walk; + U32 i = 0; + for(walk = &list; *walk; walk = &((*walk)->next), i++) + if(value == (*walk)->val) + return i; + Entry *newFloat = (Entry *) consoleAlloc(sizeof(Entry)); + newFloat->val = value; + newFloat->next = NULL; + count++; + *walk = newFloat; + return count-1; +} +void CompilerFloatTable::reset() +{ + list = NULL; + count = 0; +} +F64 *CompilerFloatTable::build() +{ + F64 *ret = new F64[count]; + U32 i = 0; + for(Entry *walk = list; walk; walk = walk->next, i++) + ret[i] = walk->val; + return ret; +} + +void CompilerFloatTable::write(Stream &st) +{ + st.write(count); + for(Entry *walk = list; walk; walk = walk->next) + st.write(walk->val); +} + +//------------------------------------------------------------ + +void CompilerIdentTable::reset() +{ + list = NULL; +} + +void CompilerIdentTable::add(StringTableEntry ste, U32 ip) +{ + U32 index = gGlobalStringTable.add(ste, false); + Entry *newEntry = (Entry *) consoleAlloc(sizeof(Entry)); + newEntry->offset = index; + newEntry->ip = ip; + for(Entry *walk = list; walk; walk = walk->next) + { + if(walk->offset == index) + { + newEntry->nextIdent = walk->nextIdent; + walk->nextIdent = newEntry; + return; + } + } + newEntry->next = list; + list = newEntry; + newEntry->nextIdent = NULL; +} + +void CompilerIdentTable::write(Stream &st) +{ + U32 count = 0; + Entry * walk; + for(walk = list; walk; walk = walk->next) + count++; + st.write(count); + for(walk = list; walk; walk = walk->next) + { + U32 ec = 0; + Entry * el; + for(el = walk; el; el = el->nextIdent) + ec++; + st.write(walk->offset); + st.write(ec); + for(el = walk; el; el = el->nextIdent) + st.write(el->ip); + } +} diff --git a/console/compiler.h b/console/compiler.h new file mode 100644 index 0000000..00d76ae --- /dev/null +++ b/console/compiler.h @@ -0,0 +1,244 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#ifndef _COMPILER_H_ +#define _COMPILER_H_ + +class Stream; +class DataChunker; + +#include "platform/platform.h" +#include "console/ast.h" +#include "console/codeBlock.h" + +// Autogenerated, so we should only ever include from once place - here. +// (We can't stick include guards in it without patching bison.) +#ifndef _CMDGRAM_H_ +#define _CMDGRAM_H_ +#include "console/cmdgram.h" +#endif + +namespace Compiler +{ + /// The opcodes for the TorqueScript VM. + enum CompiledInstructions + { + OP_FUNC_DECL, + OP_CREATE_OBJECT, + OP_ADD_OBJECT, + OP_END_OBJECT, + // Added to fix the stack issue [7/9/2007 Black] + OP_FINISH_OBJECT, + + OP_JMPIFFNOT, + OP_JMPIFNOT, + OP_JMPIFF, + OP_JMPIF, + OP_JMPIFNOT_NP, + OP_JMPIF_NP, + OP_JMP, + OP_RETURN, + // fixes a bug when not explicitly returning a value + OP_RETURN_VOID, + OP_CMPEQ, + OP_CMPGR, + OP_CMPGE, + OP_CMPLT, + OP_CMPLE, + OP_CMPNE, + OP_XOR, + OP_MOD, + OP_BITAND, + OP_BITOR, + OP_NOT, + OP_NOTF, + OP_ONESCOMPLEMENT, + + OP_SHR, + OP_SHL, + OP_AND, + OP_OR, + + OP_ADD, + OP_SUB, + OP_MUL, + OP_DIV, + OP_NEG, + + OP_SETCURVAR, + OP_SETCURVAR_CREATE, + OP_SETCURVAR_ARRAY, + OP_SETCURVAR_ARRAY_CREATE, + + OP_LOADVAR_UINT, + OP_LOADVAR_FLT, + OP_LOADVAR_STR, + + OP_SAVEVAR_UINT, + OP_SAVEVAR_FLT, + OP_SAVEVAR_STR, + + OP_SETCUROBJECT, + OP_SETCUROBJECT_NEW, + OP_SETCUROBJECT_INTERNAL, + + OP_SETCURFIELD, + OP_SETCURFIELD_ARRAY, + OP_SETCURFIELD_TYPE, + + OP_LOADFIELD_UINT, + OP_LOADFIELD_FLT, + OP_LOADFIELD_STR, + + OP_SAVEFIELD_UINT, + OP_SAVEFIELD_FLT, + OP_SAVEFIELD_STR, + + OP_STR_TO_UINT, + OP_STR_TO_FLT, + OP_STR_TO_NONE, + OP_FLT_TO_UINT, + OP_FLT_TO_STR, + OP_FLT_TO_NONE, + OP_UINT_TO_FLT, + OP_UINT_TO_STR, + OP_UINT_TO_NONE, + + OP_LOADIMMED_UINT, + OP_LOADIMMED_FLT, + OP_TAG_TO_STR, + OP_LOADIMMED_STR, + OP_DOCBLOCK_STR, + OP_LOADIMMED_IDENT, + + OP_CALLFUNC_RESOLVE, + OP_CALLFUNC, + + OP_ADVANCE_STR, + OP_ADVANCE_STR_APPENDCHAR, + OP_ADVANCE_STR_COMMA, + OP_ADVANCE_STR_NUL, + OP_REWIND_STR, + OP_TERMINATE_REWIND_STR, + OP_COMPARE_STR, + + OP_PUSH, + OP_PUSH_FRAME, + + OP_ASSERT, + OP_BREAK, + + OP_INVALID + }; + + //------------------------------------------------------------ + + F64 consoleStringToNumber(const char *str, StringTableEntry file = 0, U32 line = 0); + U32 precompileBlock(StmtNode *block, U32 loopCount); + U32 compileBlock(StmtNode *block, U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint); + + //------------------------------------------------------------ + + struct CompilerIdentTable + { + struct Entry + { + U32 offset; + U32 ip; + Entry *next; + Entry *nextIdent; + }; + Entry *list; + void add(StringTableEntry ste, U32 ip); + void reset(); + void write(Stream &st); + }; + + //------------------------------------------------------------ + + struct CompilerStringTable + { + U32 totalLen; + struct Entry + { + char *string; + U32 start; + U32 len; + bool tag; + Entry *next; + }; + Entry *list; + + char buf[256]; + + U32 add(const char *str, bool caseSens = true, bool tag = false); + U32 addIntString(U32 value); + U32 addFloatString(F64 value); + void reset(); + char *build(); + void write(Stream &st); + }; + + //------------------------------------------------------------ + + struct CompilerFloatTable + { + struct Entry + { + F64 val; + Entry *next; + }; + U32 count; + Entry *list; + + U32 add(F64 value); + void reset(); + F64 *build(); + void write(Stream &st); + }; + + //------------------------------------------------------------ + + inline StringTableEntry U32toSTE(U32 u) + { + return *((StringTableEntry *) &u); + } + + extern U32 (*STEtoU32)(StringTableEntry ste, U32 ip); + + U32 evalSTEtoU32(StringTableEntry ste, U32); + U32 compileSTEtoU32(StringTableEntry ste, U32 ip); + + CompilerStringTable *getCurrentStringTable(); + CompilerStringTable &getGlobalStringTable(); + CompilerStringTable &getFunctionStringTable(); + + void setCurrentStringTable (CompilerStringTable* cst); + + CompilerFloatTable *getCurrentFloatTable(); + CompilerFloatTable &getGlobalFloatTable(); + CompilerFloatTable &getFunctionFloatTable(); + + void setCurrentFloatTable (CompilerFloatTable* cst); + + CompilerIdentTable &getIdentTable(); + + void precompileIdent(StringTableEntry ident); + + CodeBlock *getBreakCodeBlock(); + void setBreakCodeBlock(CodeBlock *cb); + + /// Helper function to reset the float, string, and ident tables to a base + /// starting state. + void resetTables(); + + void *consoleAlloc(U32 size); + void consoleAllocReset(); + + extern bool gSyntaxError; +}; + +#endif diff --git a/console/console.cpp b/console/console.cpp new file mode 100644 index 0000000..95e99a4 --- /dev/null +++ b/console/console.cpp @@ -0,0 +1,1384 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformTLS.h" +#include "platform/threads/thread.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/consoleObject.h" +#include "console/consoleParser.h" +#include "core/stream/fileStream.h" +#include "console/ast.h" +#include "core/tAlgorithm.h" +#include "console/consoleTypes.h" +#include "console/telnetDebugger.h" +#include "console/simBase.h" +#include "console/compiler.h" +#include "console/stringStack.h" +#include "console/ICallMethod.h" +#include +#include "platform/threads/mutex.h" + + +extern StringStack STR; + +ExprEvalState gEvalState; +StmtNode *gStatementList; +ConsoleConstructor *ConsoleConstructor::first = NULL; +bool gWarnUndefinedScriptVariables; + +static char scratchBuffer[4096]; + +CON_DECLARE_PARSER(CMD); + +static const char * prependDollar ( const char * name ) +{ + if(name[0] != '$') + { + S32 len = dStrlen(name); + AssertFatal(len < sizeof(scratchBuffer)-2, "CONSOLE: name too long"); + scratchBuffer[0] = '$'; + dMemcpy(scratchBuffer + 1, name, len + 1); + name = scratchBuffer; + } + return name; +} + +static const char * prependPercent ( const char * name ) +{ + if(name[0] != '%') + { + S32 len = dStrlen(name); + AssertFatal(len < sizeof(scratchBuffer)-2, "CONSOLE: name too long"); + scratchBuffer[0] = '%'; + dMemcpy(scratchBuffer + 1, name, len + 1); + name = scratchBuffer; + } + return name; +} + +//-------------------------------------- +void ConsoleConstructor::init(const char *cName, const char *fName, const char *usg, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + mina = minArgs; + maxa = maxArgs; + funcName = fName; + usage = usg; + className = cName; + sc = 0; fc = 0; vc = 0; bc = 0; ic = 0; + callback = group = false; + next = first; + ns = false; + first = this; + toolOnly = isToolOnly; +} + +void ConsoleConstructor::setup() +{ + for(ConsoleConstructor *walk = first; walk; walk = walk->next) + { +#ifdef TORQUE_DEBUG + walk->validate(); +#endif + + if(walk->sc) + Con::addCommand(walk->className, walk->funcName, walk->sc, walk->usage, walk->mina, walk->maxa, walk->toolOnly); + else if(walk->ic) + Con::addCommand(walk->className, walk->funcName, walk->ic, walk->usage, walk->mina, walk->maxa, walk->toolOnly); + else if(walk->fc) + Con::addCommand(walk->className, walk->funcName, walk->fc, walk->usage, walk->mina, walk->maxa, walk->toolOnly); + else if(walk->vc) + Con::addCommand(walk->className, walk->funcName, walk->vc, walk->usage, walk->mina, walk->maxa, walk->toolOnly); + else if(walk->bc) + Con::addCommand(walk->className, walk->funcName, walk->bc, walk->usage, walk->mina, walk->maxa, walk->toolOnly); + else if(walk->group) + Con::markCommandGroup(walk->className, walk->funcName, walk->usage); + else if(walk->overload) + Con::addOverload(walk->className, walk->funcName, walk->usage); + else if(walk->callback) + Con::noteScriptCallback(walk->className, walk->funcName, walk->usage); + else if(walk->ns) + { + Namespace* ns = Namespace::find(StringTable->insert(walk->className)); + if( ns ) + ns->mUsage = walk->usage; + } + else + { + AssertISV(false, "Found a ConsoleConstructor with an indeterminate type!"); + } + } +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + init(className, funcName, usage, minArgs, maxArgs, isToolOnly); + sc = sfunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + init(className, funcName, usage, minArgs, maxArgs, isToolOnly); + ic = ifunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + init(className, funcName, usage, minArgs, maxArgs, isToolOnly); + fc = ffunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + init(className, funcName, usage, minArgs, maxArgs, isToolOnly); + vc = vfunc; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + init(className, funcName, usage, minArgs, maxArgs, isToolOnly); + bc = bfunc; +} + +ConsoleConstructor::ConsoleConstructor(const char* className, const char* groupName, const char* aUsage) +{ + init(className, groupName, usage, -1, -2); + + group = true; + + // Somewhere, the entry list is getting flipped, partially. + // so we have to do tricks to deal with making sure usage + // is properly populated. + + // This is probably redundant. + static char * lastUsage = NULL; + if(aUsage) + lastUsage = (char *)aUsage; + + usage = lastUsage; +} + +ConsoleConstructor::ConsoleConstructor(const char *className, const char *callbackName, + const char *usage, bool isRequired) +{ + init(className, callbackName, usage, -2, -3); + callback = true; + ns = true; +} + +void ConsoleConstructor::validate() +{ +#ifdef TORQUE_DEBUG + // Don't do the following check if we're not a method/func. + if(this->group) + return; + + // In debug, walk the list and make sure this isn't a duplicate. + for(ConsoleConstructor *walk = first; walk; walk = walk->next) + { + // Skip mismatching func/method names. + if(dStricmp(walk->funcName, this->funcName)) + continue; + + // Don't compare functions with methods or vice versa. + if(bool(this->className) != bool(walk->className)) + continue; + + // Skip mismatching classnames, if they're present. + if(this->className && walk->className && dStricmp(walk->className, this->className)) + continue; + + // If we encounter ourselves, stop searching; this prevents duplicate + // firing of the assert, instead only firing for the latter encountered + // entry. + if(this == walk) + break; + + // Match! + if(this->className) + { + AssertISV(false, avar("ConsoleConstructor::setup - ConsoleMethod '%s::%s' collides with another of the same name.", this->className, this->funcName)); + } + else + { + AssertISV(false, avar("ConsoleConstructor::setup - ConsoleFunction '%s' collides with another of the same name.", this->funcName)); + } + } +#endif +} + +// We comment out the implementation of the Con namespace when doxygenizing because +// otherwise Doxygen decides to ignore our docs in console.h +#ifndef DOXYGENIZING + +namespace Con +{ + +static Vector gConsumers(__FILE__, __LINE__); +static Vector< String > sInstantGroupStack( __FILE__, __LINE__ ); +static DataChunker consoleLogChunker; +static Vector consoleLog(__FILE__, __LINE__); +static bool consoleLogLocked; +static bool logBufferEnabled=true; +static S32 printLevel = 10; +static FileStream consoleLogFile; +static const char *defLogFileName = "console.log"; +static S32 consoleLogMode = 0; +static bool active = false; +static bool newLogFile; +static const char *logFileName; + +static const int MaxCompletionBufferSize = 4096; +static char completionBuffer[MaxCompletionBufferSize]; +static char tabBuffer[MaxCompletionBufferSize] = {0}; +static SimObjectPtr tabObject; +static U32 completionBaseStart; +static U32 completionBaseLen; + +String gInstantGroup; +Con::ConsoleInputEvent smConsoleInput; + +/// Current script file name and root, these are registered as +/// console variables. +/// @{ + +/// +StringTableEntry gCurrentFile; +StringTableEntry gCurrentRoot; +/// @} + +bool alwaysUseDebugOutput = true; +bool useTimestamp = false; + +ConsoleFunctionGroupBegin( Clipboard, "Miscellaneous functions to control the clipboard and clear the console."); + +ConsoleFunction( cls, void, 1, 1, "Clear the screen.") +{ + if(consoleLogLocked) + return; + consoleLogChunker.freeBlocks(); + consoleLog.setSize(0); +}; + +ConsoleFunction( getClipboard, const char*, 1, 1, "Get text from the clipboard.") +{ + return Platform::getClipboard(); +}; + +ConsoleFunction( setClipboard, bool, 2, 2, "(string text)" + "Set the system clipboard.") +{ + return Platform::setClipboard(argv[1]); +}; + +ConsoleFunctionGroupEnd( Clipboard ); + + +void postConsoleInput( RawData data ); + +void init() +{ + AssertFatal(active == false, "Con::init should only be called once."); + + // Set up general init values. + active = true; + logFileName = NULL; + newLogFile = true; + gWarnUndefinedScriptVariables = false; + + // Initialize subsystems. + Namespace::init(); + ConsoleConstructor::setup(); + + // Set up the parser(s) + CON_ADD_PARSER(CMD, "cs", true); // TorqueScript + + // Variables + setVariable("Con::prompt", "% "); + addVariable("Con::logBufferEnabled", TypeBool, &logBufferEnabled); + addVariable("Con::printLevel", TypeS32, &printLevel); + addVariable("Con::warnUndefinedVariables", TypeBool, &gWarnUndefinedScriptVariables); + addVariable( "instantGroup", TypeRealString, &gInstantGroup ); + + // Current script file name and root + Con::addVariable( "Con::File", TypeString, &gCurrentFile ); + Con::addVariable( "Con::Root", TypeString, &gCurrentRoot ); + + // alwaysUseDebugOutput determines whether to send output to the platform's + // "debug" system. see winConsole for an example. + // in ship builds we don't expose this variable to script + // and we set it to false by default (don't want to provide more information + // to potential hackers). platform code should also ifdef out the code that + // pays attention to this in ship builds (see winConsole.cpp) + // note that enabling this can slow down your game + // if you are running from the debugger and printing a lot of console messages. +#ifndef TORQUE_SHIPPING + addVariable("pref::Console::alwaysUseDebugOutput", TypeBool, &alwaysUseDebugOutput); +#else + alwaysUseDebugOutput = false; +#endif + + // controls whether a timestamp is prepended to every console message + addVariable("$pref::Console::useTimestamp", TypeBool, &useTimestamp); + + // Setup the console types. + ConsoleBaseType::initialize(); + + // And finally, the ACR... + AbstractClassRep::initialize(); + + // Plug us into the journaled console input signal. + smConsoleInput.notify(postConsoleInput); +} + +//-------------------------------------- + +void shutdown() +{ + AssertFatal(active == true, "Con::shutdown should only be called once."); + active = false; + + smConsoleInput.remove(postConsoleInput); + + consoleLogFile.close(); + Namespace::shutdown(); + AbstractClassRep::shutdown(); + Compiler::freeConsoleParserList(); +} + +bool isActive() +{ + return active; +} + +bool isMainThread() +{ +#ifdef TORQUE_MULTITHREAD + return ThreadManager::isMainThread(); +#else + // If we're single threaded we're always in the main thread. + return true; +#endif +} + +//-------------------------------------- + +void getLockLog(ConsoleLogEntry *&log, U32 &size) +{ + consoleLogLocked = true; + log = consoleLog.address(); + size = consoleLog.size(); +} + +void unlockLog() +{ + consoleLogLocked = false; +} + +U32 tabComplete(char* inputBuffer, U32 cursorPos, U32 maxResultLength, bool forwardTab) +{ + // Check for null input. + if (!inputBuffer[0]) + { + return cursorPos; + } + + // Cap the max result length. + if (maxResultLength > MaxCompletionBufferSize) + { + maxResultLength = MaxCompletionBufferSize; + } + + // See if this is the same partial text as last checked. + if (dStrcmp(tabBuffer, inputBuffer)) + { + // If not... + // Save it for checking next time. + dStrcpy(tabBuffer, inputBuffer); + // Scan backward from the cursor position to find the base to complete from. + S32 p = cursorPos; + while ((p > 0) && (inputBuffer[p - 1] != ' ') && (inputBuffer[p - 1] != '.') && (inputBuffer[p - 1] != '(')) + { + p--; + } + completionBaseStart = p; + completionBaseLen = cursorPos - p; + // Is this function being invoked on an object? + if (inputBuffer[p - 1] == '.') + { + // If so... + if (p <= 1) + { + // Bail if no object identifier. + return cursorPos; + } + + // Find the object identifier. + S32 objLast = --p; + while ((p > 0) && (inputBuffer[p - 1] != ' ') && (inputBuffer[p - 1] != '(')) + { + p--; + } + + if (objLast == p) + { + // Bail if no object identifier. + return cursorPos; + } + + // Look up the object identifier. + dStrncpy(completionBuffer, inputBuffer + p, objLast - p); + completionBuffer[objLast - p] = 0; + tabObject = Sim::findObject(completionBuffer); + if (tabObject == NULL) + { + // Bail if not found. + return cursorPos; + } + } + else + { + // Not invoked on an object; we'll use the global namespace. + tabObject = 0; + } + } + + // Chop off the input text at the cursor position. + inputBuffer[cursorPos] = 0; + + // Try to find a completion in the appropriate namespace. + const char *newText; + + if (tabObject != 0) + { + newText = tabObject->tabComplete(inputBuffer + completionBaseStart, completionBaseLen, forwardTab); + } + else + { + // In the global namespace, we can complete on global vars as well as functions. + if (inputBuffer[completionBaseStart] == '$') + { + newText = gEvalState.globalVars.tabComplete(inputBuffer + completionBaseStart, completionBaseLen, forwardTab); + } + else + { + newText = Namespace::global()->tabComplete(inputBuffer + completionBaseStart, completionBaseLen, forwardTab); + } + } + + if (newText) + { + // If we got something, append it to the input text. + S32 len = dStrlen(newText); + if (len + completionBaseStart > maxResultLength) + { + len = maxResultLength - completionBaseStart; + } + dStrncpy(inputBuffer + completionBaseStart, newText, len); + inputBuffer[completionBaseStart + len] = 0; + // And set the cursor after it. + cursorPos = completionBaseStart + len; + } + + // Save the modified input buffer for checking next time. + dStrcpy(tabBuffer, inputBuffer); + + // Return the new (maybe) cursor position. + return cursorPos; +} + +//------------------------------------------------------------------------------ +static void log(const char *string) +{ + // Bail if we ain't logging. + if (!consoleLogMode) + { + return; + } + + // In mode 1, we open, append, close on each log write. + if ((consoleLogMode & 0x3) == 1) + { + consoleLogFile.open(defLogFileName, Torque::FS::File::ReadWrite); + } + + // Write to the log if its status is hunky-dory. + if ((consoleLogFile.getStatus() == Stream::Ok) || (consoleLogFile.getStatus() == Stream::EOS)) + { + consoleLogFile.setPosition(consoleLogFile.getStreamSize()); + // If this is the first write... + if (newLogFile) + { + // Make a header. + Platform::LocalTime lt; + Platform::getLocalTime(lt); + char buffer[128]; + dSprintf(buffer, sizeof(buffer), "//-------------------------- %d/%d/%d -- %02d:%02d:%02d -----\r\n", + lt.month + 1, + lt.monthday, + lt.year + 1900, + lt.hour, + lt.min, + lt.sec); + consoleLogFile.write(dStrlen(buffer), buffer); + newLogFile = false; + if (consoleLogMode & 0x4) + { + // Dump anything that has been printed to the console so far. + consoleLogMode -= 0x4; + U32 size, line; + ConsoleLogEntry *log; + getLockLog(log, size); + for (line = 0; line < size; line++) + { + consoleLogFile.write(dStrlen(log[line].mString), log[line].mString); + consoleLogFile.write(2, "\r\n"); + } + unlockLog(); + } + } + // Now write what we came here to write. + consoleLogFile.write(dStrlen(string), string); + consoleLogFile.write(2, "\r\n"); + } + + if ((consoleLogMode & 0x3) == 1) + { + consoleLogFile.close(); + } +} + +//------------------------------------------------------------------------------ + +static void _printf(ConsoleLogEntry::Level level, ConsoleLogEntry::Type type, const char* fmt, va_list argptr) +{ + if (!active) + return; + Con::active = false; + + char buffer[4096]; + U32 offset = 0; + if(gEvalState.traceOn && gEvalState.stack.size()) + { + offset = gEvalState.stack.size() * 3; + for(U32 i = 0; i < offset; i++) + buffer[i] = ' '; + } + + if (useTimestamp) + { + static U32 startTime = Platform::getRealMilliseconds(); + U32 curTime = Platform::getRealMilliseconds() - startTime; + offset += dSprintf(buffer + offset, sizeof(buffer) - offset, "[+%4d.%03d]", U32(curTime * 0.001), curTime % 1000); + } + dVsprintf(buffer + offset, sizeof(buffer) - offset, fmt, argptr); + + for(S32 i = 0; i < gConsumers.size(); i++) + gConsumers[i](level, buffer); + + if(logBufferEnabled || consoleLogMode) + { + char *pos = buffer; + while(*pos) + { + if(*pos == '\t') + *pos = '^'; + pos++; + } + pos = buffer; + + for(;;) + { + char *eofPos = dStrchr(pos, '\n'); + if(eofPos) + *eofPos = 0; + + log(pos); + if(logBufferEnabled && !consoleLogLocked) + { + ConsoleLogEntry entry; + entry.mLevel = level; + entry.mType = type; +#ifndef TORQUE_SHIPPING // this is equivalent to a memory leak, turn it off in ship build + entry.mString = (const char *)consoleLogChunker.alloc(dStrlen(pos) + 1); + dStrcpy(const_cast(entry.mString), pos); + + // This prevents infinite recursion if the console itself needs to + // re-allocate memory to accommodate the new console log entry, and + // LOG_PAGE_ALLOCS is defined. It is kind of a dirty hack, but the + // uses for LOG_PAGE_ALLOCS are limited, and it is not worth writing + // a lot of special case code to support this situation. -patw + const bool save = Con::active; + Con::active = false; + consoleLog.push_back(entry); + Con::active = save; +#endif + } + if(!eofPos) + break; + pos = eofPos + 1; + } + } + + Con::active = true; +} + +//------------------------------------------------------------------------------ +void printf(const char* fmt,...) +{ + va_list argptr; + va_start(argptr, fmt); + _printf(ConsoleLogEntry::Normal, ConsoleLogEntry::General, fmt, argptr); + va_end(argptr); +} + +void warnf(ConsoleLogEntry::Type type, const char* fmt,...) +{ + va_list argptr; + va_start(argptr, fmt); + _printf(ConsoleLogEntry::Warning, type, fmt, argptr); + va_end(argptr); +} + +void errorf(ConsoleLogEntry::Type type, const char* fmt,...) +{ + va_list argptr; + va_start(argptr, fmt); + _printf(ConsoleLogEntry::Error, type, fmt, argptr); + va_end(argptr); +} + +void warnf(const char* fmt,...) +{ + va_list argptr; + va_start(argptr, fmt); + _printf(ConsoleLogEntry::Warning, ConsoleLogEntry::General, fmt, argptr); + va_end(argptr); +} + +void errorf(const char* fmt,...) +{ + va_list argptr; + va_start(argptr, fmt); + _printf(ConsoleLogEntry::Error, ConsoleLogEntry::General, fmt, argptr); + va_end(argptr); +} + +//--------------------------------------------------------------------------- + +void setVariable(const char *name, const char *value) +{ + // get the field info from the object.. + if(name[0] != '$' && dStrchr(name, '.') && !isFunction(name)) + { + S32 len = dStrlen(name); + AssertFatal(len < sizeof(scratchBuffer)-1, "Sim::getVariable - name too long"); + dMemcpy(scratchBuffer, name, len+1); + + char * token = dStrtok(scratchBuffer, "."); + SimObject * obj = Sim::findObject(token); + if(!obj) + return; + + token = dStrtok(0, ".\0"); + if(!token) + return; + + while(token != NULL) + { + const char * val = obj->getDataField(StringTable->insert(token), 0); + if(!val) + return; + + char *fieldToken = token; + token = dStrtok(0, ".\0"); + if(token) + { + obj = Sim::findObject(token); + if(!obj) + return; + } + else + { + obj->setDataField(StringTable->insert(fieldToken), 0, value); + } + } + } + + name = prependDollar(name); + gEvalState.globalVars.setVariable(StringTable->insert(name), value); +} + +void setLocalVariable(const char *name, const char *value) +{ + name = prependPercent(name); + gEvalState.stack.last()->setVariable(StringTable->insert(name), value); +} + +void setBoolVariable(const char *varName, bool value) +{ + setVariable(varName, value ? "1" : "0"); +} + +void setIntVariable(const char *varName, S32 value) +{ + char scratchBuffer[32]; + dSprintf(scratchBuffer, sizeof(scratchBuffer), "%d", value); + setVariable(varName, scratchBuffer); +} + +void setFloatVariable(const char *varName, F32 value) +{ + char scratchBuffer[32]; + dSprintf(scratchBuffer, sizeof(scratchBuffer), "%g", value); + setVariable(varName, scratchBuffer); +} + +//--------------------------------------------------------------------------- +void addConsumer(ConsumerCallback consumer) +{ + gConsumers.push_back(consumer); +} + +// dhc - found this empty -- trying what I think is a reasonable impl. +void removeConsumer(ConsumerCallback consumer) +{ + for(S32 i = 0; i < gConsumers.size(); i++) + { + if (gConsumers[i] == consumer) + { + // remove it from the list. + gConsumers.erase(i); + break; + } + } +} + +void stripColorChars(char* line) +{ + char* c = line; + char cp = *c; + while (cp) + { + if (cp < 18) + { + // Could be a color control character; let's take a closer look. + if ((cp != 8) && (cp != 9) && (cp != 10) && (cp != 13)) + { + // Yep... copy following chars forward over this. + char* cprime = c; + char cpp; + do + { + cpp = *++cprime; + *(cprime - 1) = cpp; + } + while (cpp); + // Back up 1 so we'll check this position again post-copy. + c--; + } + } + cp = *++c; + } +} + +const char *getVariable(const char *name) +{ + // get the field info from the object.. + if(name[0] != '$' && dStrchr(name, '.') && !isFunction(name)) + { + S32 len = dStrlen(name); + AssertFatal(len < sizeof(scratchBuffer)-1, "Sim::getVariable - name too long"); + dMemcpy(scratchBuffer, name, len+1); + + char * token = dStrtok(scratchBuffer, "."); + SimObject * obj = Sim::findObject(token); + if(!obj) + return(""); + + token = dStrtok(0, ".\0"); + if(!token) + return(""); + + while(token != NULL) + { + const char * val = obj->getDataField(StringTable->insert(token), 0); + if(!val) + return(""); + + token = dStrtok(0, ".\0"); + if(token) + { + obj = Sim::findObject(token); + if(!obj) + return(""); + } + else + return(val); + } + } + + name = prependDollar(name); + return gEvalState.globalVars.getVariable(StringTable->insert(name)); +} + +const char *getLocalVariable(const char *name) +{ + name = prependPercent(name); + + return gEvalState.stack.last()->getVariable(StringTable->insert(name)); +} + +bool getBoolVariable(const char *varName, bool def) +{ + const char *value = getVariable(varName); + return *value ? dAtob(value) : def; +} + +S32 getIntVariable(const char *varName, S32 def) +{ + const char *value = getVariable(varName); + return *value ? dAtoi(value) : def; +} + +F32 getFloatVariable(const char *varName, F32 def) +{ + const char *value = getVariable(varName); + return *value ? dAtof(value) : def; +} + +//--------------------------------------------------------------------------- + +bool addVariable(const char *name, S32 t, void *dp) +{ + gEvalState.globalVars.addVariable(name, t, dp); + return true; +} + +bool removeVariable(const char *name) +{ + name = StringTable->lookup(prependDollar(name)); + return name!=0 && gEvalState.globalVars.removeVariable(name); +} + +//--------------------------------------------------------------------------- + +void addCommand(const char *nsName, const char *name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *nsName, const char *name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *nsName, const char *name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *nsName, const char *name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *nsName, const char *name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void noteScriptCallback(const char *className, const char *funcName, const char *usage) +{ + Namespace *ns = lookupNamespace(className); + ns->addScriptCallback(StringTable->insert(funcName), usage); +} + +void markCommandGroup(const char * nsName, const char *name, const char* usage) +{ + Namespace *ns = lookupNamespace(nsName); + ns->markGroup(name,usage); +} + +void beginCommandGroup(const char * nsName, const char *name, const char* usage) +{ + markCommandGroup(nsName, name, usage); +} + +void endCommandGroup(const char * nsName, const char *name) +{ + markCommandGroup(nsName, name, NULL); +} + +void addOverload(const char * nsName, const char * name, const char * altUsage) +{ + Namespace *ns = lookupNamespace(nsName); + ns->addOverload(name,altUsage); +} + +void addCommand(const char *name,StringCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *name,VoidCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *name,IntCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *name,FloatCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +void addCommand(const char *name,BoolCallback cb,const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Namespace::global()->addCommand(StringTable->insert(name), cb, usage, minArgs, maxArgs, isToolOnly); +} + +const char *evaluate(const char* string, bool echo, const char *fileName) +{ + if (echo) + Con::printf("%s%s", getVariable( "$Con::Prompt" ), string); + + if(fileName) + fileName = StringTable->insert(fileName); + + CodeBlock *newCodeBlock = new CodeBlock(); + return newCodeBlock->compileExec(fileName, string, false, fileName ? -1 : 0); +} + +//------------------------------------------------------------------------------ +const char *evaluatef(const char* string, ...) +{ + char buffer[4096]; + va_list args; + va_start(args, string); + dVsprintf(buffer, sizeof(buffer), string, args); + CodeBlock *newCodeBlock = new CodeBlock(); + return newCodeBlock->compileExec(NULL, buffer, false, 0); +} + +const char *execute(S32 argc, const char *argv[]) +{ +#ifdef TORQUE_MULTITHREAD + if(isMainThread()) + { +#endif + Namespace::Entry *ent; + StringTableEntry funcName = StringTable->insert(argv[0]); + ent = Namespace::global()->lookup(funcName); + + if(!ent) + { + warnf(ConsoleLogEntry::Script, "%s: Unknown command.", argv[0]); + + // Clean up arg buffers, if any. + STR.clearFunctionOffset(); + return ""; + } + return ent->execute(argc, argv, &gEvalState); +#ifdef TORQUE_MULTITHREAD + } + else + { + SimConsoleThreadExecCallback cb; + SimConsoleThreadExecEvent *evt = new SimConsoleThreadExecEvent(argc, argv, false, &cb); + Sim::postEvent(Sim::getRootGroup(), evt, Sim::getCurrentTime()); + + return cb.waitForResult(); + } +#endif +} + +//------------------------------------------------------------------------------ +const char *execute(SimObject *object, S32 argc, const char *argv[], bool thisCallOnly) +{ + static char idBuf[16]; + if(argc < 2) + return ""; + + // [neo, 10/05/2007 - #3010] + // Make sure we don't get recursive calls, respect the flag! + // Should we be calling handlesMethod() first? + if( !thisCallOnly ) + { + ICallMethod *com = dynamic_cast(object); + if(com) + com->callMethodArgList(argc, argv, false); + } + + if(object->getNamespace()) + { + dSprintf(idBuf, sizeof(idBuf), "%d", object->getId()); + argv[1] = idBuf; + + StringTableEntry funcName = StringTable->insert(argv[0]); + Namespace::Entry *ent = object->getNamespace()->lookup(funcName); + + if(ent == NULL) + { + //warnf(ConsoleLogEntry::Script, "%s: undefined for object '%s' - id %d", funcName, object->getName(), object->getId()); + + // Clean up arg buffers, if any. + STR.clearFunctionOffset(); + return ""; + } + + // Twiddle %this argument + const char *oldArg1 = argv[1]; + dSprintf(idBuf, sizeof(idBuf), "%d", object->getId()); + argv[1] = idBuf; + + SimObject *save = gEvalState.thisObject; + gEvalState.thisObject = object; + const char *ret = ent->execute(argc, argv, &gEvalState); + gEvalState.thisObject = save; + + // Twiddle it back + argv[1] = oldArg1; + + return ret; + } + warnf(ConsoleLogEntry::Script, "Con::execute - %d has no namespace: %s", object->getId(), argv[0]); + return ""; +} + +#define B( a ) const char* a = NULL +#define A const char* +inline const char*_executef(SimObject *obj, S32 checkArgc, S32 argc, + A a, B(b), B(c), B(d), B(e), B(f), B(g), B(h), B(i), B(j), B(k)) +{ +#undef A +#undef B + const U32 maxArg = 12; + AssertWarn(checkArgc == argc, "Incorrect arg count passed to Con::executef(SimObject*)"); + AssertFatal(argc <= maxArg - 1, "Too many args passed to Con::_executef(SimObject*). Please update the function to handle more."); + const char* argv[maxArg]; + argv[0] = a; + argv[1] = a; + argv[2] = b; + argv[3] = c; + argv[4] = d; + argv[5] = e; + argv[6] = f; + argv[7] = g; + argv[8] = h; + argv[9] = i; + argv[10] = j; + argv[11] = k; + return execute(obj, argc+1, argv); +} + +#define A const char* +#define OBJ SimObject* obj +const char *executef(OBJ, A a) { return _executef(obj, 1, 1, a); } +const char *executef(OBJ, A a, A b) { return _executef(obj, 2, 2, a, b); } +const char *executef(OBJ, A a, A b, A c) { return _executef(obj, 3, 3, a, b, c); } +const char *executef(OBJ, A a, A b, A c, A d) { return _executef(obj, 4, 4, a, b, c, d); } +const char *executef(OBJ, A a, A b, A c, A d, A e) { return _executef(obj, 5, 5, a, b, c, d, e); } +const char *executef(OBJ, A a, A b, A c, A d, A e, A f) { return _executef(obj, 6, 6, a, b, c, d, e, f); } +const char *executef(OBJ, A a, A b, A c, A d, A e, A f, A g) { return _executef(obj, 7, 7, a, b, c, d, e, f, g); } +const char *executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h) { return _executef(obj, 8, 8, a, b, c, d, e, f, g, h); } +const char *executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h, A i) { return _executef(obj, 9, 9, a, b, c, d, e, f, g, h, i); } +const char *executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h, A i, A j) { return _executef(obj,10,10, a, b, c, d, e, f, g, h, i, j); } +const char *executef(OBJ, A a, A b, A c, A d, A e, A f, A g, A h, A i, A j, A k) { return _executef(obj,11,11, a, b, c, d, e, f, g, h, i, j, k); } +#undef A + +//------------------------------------------------------------------------------ +#define B( a ) const char* a = NULL +#define A const char* +inline const char*_executef(S32 checkArgc, S32 argc, A a, B(b), B(c), B(d), B(e), B(f), B(g), B(h), B(i), B(j)) +{ +#undef A +#undef B + const U32 maxArg = 10; + AssertFatal(checkArgc == argc, "Incorrect arg count passed to Con::executef()"); + AssertFatal(argc <= maxArg, "Too many args passed to Con::_executef(). Please update the function to handle more."); + const char* argv[maxArg]; + argv[0] = a; + argv[1] = b; + argv[2] = c; + argv[3] = d; + argv[4] = e; + argv[5] = f; + argv[6] = g; + argv[7] = h; + argv[8] = i; + argv[9] = j; + return execute(argc, argv); +} + +#define A const char* +const char *executef(A a) { return _executef(1, 1, a); } +const char *executef(A a, A b) { return _executef(2, 2, a, b); } +const char *executef(A a, A b, A c) { return _executef(3, 3, a, b, c); } +const char *executef(A a, A b, A c, A d) { return _executef(4, 4, a, b, c, d); } +const char *executef(A a, A b, A c, A d, A e) { return _executef(5, 5, a, b, c, d, e); } +const char *executef(A a, A b, A c, A d, A e, A f) { return _executef(6, 6, a, b, c, d, e, f); } +const char *executef(A a, A b, A c, A d, A e, A f, A g) { return _executef(7, 7, a, b, c, d, e, f, g); } +const char *executef(A a, A b, A c, A d, A e, A f, A g, A h) { return _executef(8, 8, a, b, c, d, e, f, g, h); } +const char *executef(A a, A b, A c, A d, A e, A f, A g, A h, A i) { return _executef(9, 9, a, b, c, d, e, f, g, h, i); } +const char *executef(A a, A b, A c, A d, A e, A f, A g, A h, A i, A j) { return _executef(10,10,a, b, c, d, e, f, g, h, i, j); } +#undef A + + +//------------------------------------------------------------------------------ +bool isFunction(const char *fn) +{ + const char *string = StringTable->lookup(fn); + if(!string) + return false; + else + return Namespace::global()->lookup(string) != NULL; +} + +//------------------------------------------------------------------------------ + +void setLogMode(S32 newMode) +{ + if ((newMode & 0x3) != (consoleLogMode & 0x3)) { + if (newMode && !consoleLogMode) { + // Enabling logging when it was previously disabled. + newLogFile = true; + } + if ((consoleLogMode & 0x3) == 2) { + // Changing away from mode 2, must close logfile. + consoleLogFile.close(); + } + else if ((newMode & 0x3) == 2) { +#ifdef _XBOX + // Xbox is not going to support logging to a file. Use the OutputDebugStr + // log consumer + Platform::debugBreak(); +#endif + // Starting mode 2, must open logfile. + consoleLogFile.open(defLogFileName, Torque::FS::File::Write); + } + consoleLogMode = newMode; + } +} + +Namespace *lookupNamespace(const char *ns) +{ + if(!ns) + return Namespace::global(); + return Namespace::find(StringTable->insert(ns)); +} + +bool linkNamespaces(const char *parent, const char *child) +{ + Namespace *pns = lookupNamespace(parent); + Namespace *cns = lookupNamespace(child); + if(pns && cns) + return cns->classLinkTo(pns); + return false; +} + +bool unlinkNamespaces(const char *parent, const char *child) +{ + Namespace *pns = lookupNamespace(parent); + Namespace *cns = lookupNamespace(child); + + if(pns == cns) + { + Con::warnf("Con::unlinkNamespaces - trying to unlink '%s' from itself, aborting.", parent); + return false; + } + + if(pns && cns) + return cns->unlinkClass(pns); + + return false; +} + +bool classLinkNamespaces(Namespace *parent, Namespace *child) +{ + if(parent && child) + return child->classLinkTo(parent); + return false; +} + +void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, const EnumTable *tbl, BitSet32 flag) +{ + ConsoleBaseType *cbt = ConsoleBaseType::getType(type); + AssertFatal(cbt, "Con::setData - could not resolve type ID!"); + cbt->setData((void *) (((const char *)dptr) + index * cbt->getTypeSize()),argc, argv, tbl, flag); +} + +const char *getData(S32 type, void *dptr, S32 index, const EnumTable *tbl, BitSet32 flag) +{ + ConsoleBaseType *cbt = ConsoleBaseType::getType(type); + AssertFatal(cbt, "Con::getData - could not resolve type ID!"); + return cbt->getData((void *) (((const char *)dptr) + index * cbt->getTypeSize()), tbl, flag); +} + +const char *getFormattedData(S32 type, const char *data, const EnumTable *tbl, BitSet32 flag) +{ + ConsoleBaseType *cbt = ConsoleBaseType::getType( type ); + AssertFatal(cbt, "Con::getData - could not resolve type ID!"); + + // Datablock types are just a datablock + // name and don't ever need formatting. + if ( cbt->isDatablock() ) + return data; + + bool currWarn = gWarnUndefinedScriptVariables; + gWarnUndefinedScriptVariables = false; + + const char* globalValue = Con::getVariable(data); + + gWarnUndefinedScriptVariables = currWarn; + + if (dStrlen(globalValue) > 0) + return globalValue; + + void* variable = cbt->getNativeVariable(); + + if (variable) + { + Con::setData(type, variable, 0, 1, &data, tbl, flag); + const char* formattedVal = Con::getData(type, variable, 0, tbl, flag); + + char* returnBuffer = Con::getReturnBuffer(2048); + dSprintf(returnBuffer, 2048, "%s\0", formattedVal ); + + cbt->deleteNativeVariable(variable); + + return returnBuffer; + } + else + return data; +} + +//------------------------------------------------------------------------------ + +bool isCurrentScriptToolScript() +{ + // With a player build we ALWAYS return false +#ifndef TORQUE_TOOLS + return false; +#else + const StringTableEntry cbFullPath = CodeBlock::getCurrentCodeBlockFullPath(); + if(cbFullPath == NULL) + return false; + const StringTableEntry exePath = Platform::getMainDotCsDir(); + + return dStrnicmp(exePath, cbFullPath, dStrlen(exePath)) == 0; +#endif +} + +//------------------------------------------------------------------------------ + +StringTableEntry getModNameFromPath(const char *path) +{ + if(path == NULL || *path == 0) + return NULL; + + char buf[1024]; + buf[0] = 0; + + if(path[0] == '/' || path[1] == ':') + { + // It's an absolute path + const StringTableEntry exePath = Platform::getMainDotCsDir(); + U32 len = dStrlen(exePath); + if(dStrnicmp(exePath, path, len) == 0) + { + const char *ptr = path + len + 1; + const char *slash = dStrchr(ptr, '/'); + if(slash) + { + dStrncpy(buf, ptr, slash - ptr); + buf[slash - ptr] = 0; + } + else + return NULL; + } + else + return NULL; + } + else + { + const char *slash = dStrchr(path, '/'); + if(slash) + { + dStrncpy(buf, path, slash - path); + buf[slash - path] = 0; + } + else + return NULL; + } + + return StringTable->insert(buf); +} + +void postConsoleInput( RawData data ) +{ + // Schedule this to happen at the next time event. + char *argv[2]; + argv[0] = "eval"; + argv[1] = ( char* ) data.data; + Sim::postCurrentEvent(Sim::getRootGroup(), new SimConsoleEvent(2, const_cast(argv), false)); +} + +//------------------------------------------------------------------------------ + +void pushInstantGroup( String name ) +{ + sInstantGroupStack.push_back( gInstantGroup ); + gInstantGroup = name; +} + +void popInstantGroup() +{ + if( sInstantGroupStack.empty() ) + gInstantGroup = String::EmptyString; + else + { + gInstantGroup = sInstantGroupStack.last(); + sInstantGroupStack.pop_back(); + } +} + +} // end of Console namespace + +EnumTable::EnumTable(S32 sSize, const Enums *sTable, S32 sFirstFlag) +{ + size = sSize; + table = sTable; + if (sFirstFlag == -1) + { + firstFlag = size; + mask = -1; + } + else + { + U32 max = 0; + S32 i = 0; + for (i=0; i< size; i++) + { + if (table[i].index == sFirstFlag) + break; + if (table[i].index > max) + max = table[i].index; + } + firstFlag = i; + mask = getNextPow2(max) - 1; + } +} + +#endif diff --git a/console/console.h b/console/console.h new file mode 100644 index 0000000..da856e2 --- /dev/null +++ b/console/console.h @@ -0,0 +1,914 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLE_H_ +#define _CONSOLE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _BITSET_H_ +#include "core/bitSet.h" +#endif +#include + +#include "core/util/journal/journaledSignal.h" + +class SimObject; +struct EnumTable; +class Namespace; + +/// Indicates that warnings about undefined script variables should be displayed. +/// +/// @note This is set and controlled by script. +extern bool gWarnUndefinedScriptVariables; + +enum StringTableConstants +{ + StringTagPrefixByte = 0x01 ///< Magic value prefixed to tagged strings. +}; + +/// Represents an entry in the log. +struct ConsoleLogEntry +{ + /// This field indicates the severity of the log entry. + /// + /// Log entries are filtered and displayed differently based on + /// their severity. Errors are highlighted red, while normal entries + /// are displayed as normal text. Often times, the engine will be + /// configured to hide all log entries except warnings or errors, + /// or to perform a special notification when it encounters an error. + enum Level + { + Normal = 0, + Warning, + Error, + NUM_CLASS + } mLevel; + + /// Used to associate a log entry with a module. + /// + /// Log entries can come from different sources; for instance, + /// the scripting engine, or the network code. This allows the + /// logging system to be aware of where different log entries + /// originated from. + enum Type + { + General = 0, + Assert, + Script, + GUI, + Network, + GGConnect, + NUM_TYPE + } mType; + + /// Indicates the actual log entry. + /// + /// This contains a description of the event being logged. + /// For instance, "unable to access file", or "player connected + /// successfully", or nearly anything else you might imagine. + /// + /// Typically, the description should contain a concise, descriptive + /// string describing whatever is being logged. Whenever possible, + /// include useful details like the name of the file being accessed, + /// or the id of the player or GuiControl, so that if a log needs + /// to be used to locate a bug, it can be done as painlessly as + /// possible. + const char *mString; +}; + +/// Scripting engine representation of an enum. +/// +/// This data structure is used by the scripting engine +/// to expose enumerations to the scripting language. It +/// acts to relate named constants to integer values, just +/// like an enum in C++. +struct EnumTable +{ + /// Number of enumerated items in the table. + S32 size; + + // All entries after this will be considered as flags + S32 firstFlag; + S32 mask; + + /// This represents a specific item in the enumeration. + struct Enums + { + S32 index; ///< Index label maps to. + const char* label; ///< Label for this index. + }; + + const Enums *table; + + /// Constructor. + /// + /// This sets up the EnumTable with predefined data. + /// + /// @param sSize Size of the table. + /// @param sTable Pointer to table of Enums. + /// + /// @see gLiquidTypeTable + /// @see gAlignTable + EnumTable(S32 sSize, const Enums *sTable, S32 sFirstFlag = -1); +}; + +typedef const char *StringTableEntry; + +/// @defgroup console_callbacks Scripting Engine Callbacks +/// +/// The scripting engine makes heavy use of callbacks to represent +/// function exposed to the scripting language. StringCallback, +/// IntCallback, FloatCallback, VoidCallback, and BoolCallback all +/// represent exposed script functions returning different types. +/// +/// ConsumerCallback is used with the function Con::addConsumer; functions +/// registered with Con::addConsumer are called whenever something is outputted +/// to the console. For instance, the TelnetConsole registers itself with the +/// console so it can echo the console over the network. +/// +/// @note Callbacks to the scripting language - for instance, onExit(), which is +/// a script function called when the engine is shutting down - are handled +/// using Con::executef() and kin. +/// @{ + +/// +typedef const char * (*StringCallback)(SimObject *obj, S32 argc, const char *argv[]); +typedef S32 (*IntCallback)(SimObject *obj, S32 argc, const char *argv[]); +typedef F32 (*FloatCallback)(SimObject *obj, S32 argc, const char *argv[]); +typedef void (*VoidCallback)(SimObject *obj, S32 argc, const char *argv[]); // We have it return a value so things don't break.. +typedef bool (*BoolCallback)(SimObject *obj, S32 argc, const char *argv[]); + +typedef void (*ConsumerCallback)(ConsoleLogEntry::Level level, const char *consoleLine); +/// @} + +/// @defgroup console_types Scripting Engine Type Functions +/// +/// @see Con::registerType +/// @{ +typedef const char* (*GetDataFunction)(void *dptr, EnumTable *tbl, BitSet32 flag); +typedef void (*SetDataFunction)(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); +/// @} + +/// This namespace contains the core of the console functionality. +/// +/// @section con_intro Introduction +/// +/// The console is a key part of Torque's architecture. It allows direct run-time control +/// of many aspects of the engine. +/// +/// @nosubgrouping +namespace Con +{ + /// Various configuration constants. + enum Constants + { + /// This is the version number associated with DSO files. + /// + /// If you make any changes to the way the scripting language works + /// (such as DSO format changes, adding/removing op-codes) that would + /// break compatibility, then you should increment this. + /// + /// If you make a really major change, increment it to the next multiple + /// of ten. + /// + /// 12/29/04 - BJG - 33->34 Removed some opcodes, part of namespace upgrade. + /// 12/30/04 - BJG - 34->35 Reordered some things, further general shuffling. + /// 11/03/05 - BJG - 35->36 Integrated new debugger code. + // 09/08/06 - THB - 36->37 New opcode for internal names + // 09/15/06 - THB - 37->38 Added unit conversions + // 11/23/06 - THB - 38->39 Added recursive internal name operator + // 02/15/07 - THB - 39->40 Bumping to 40 for TGB since the console has been + // majorly hacked without the version number being bumped + // 02/16/07 - THB - 40->41 newmsg operator + // 06/15/07 - THB - 41->42 script types + /// 07/31/07 - THB - 42->43 Patch from Andreas Kirsch: Added opcode to support nested new declarations. + /// 09/12/07 - CAF - 43->44 remove newmsg operator + /// 09/27/07 - RDB - 44->45 Patch from Andreas Kirsch: Added opcode to support correct void return + /// 01/13/09 - TMS - 45->46 Added script assert + DSOVersion = 46, + + MaxLineLength = 512, ///< Maximum length of a line of console input. + MaxDataTypes = 256 ///< Maximum number of registered data types. + }; + + /// @name Control Functions + /// + /// The console must be initialized and shutdown appropriately during the + /// lifetime of the app. These functions are used to manage this behavior. + /// + /// @note Torque deals with this aspect of console management, so you don't need + /// to call these functions in normal usage of the engine. + /// @{ + + /// Initializes the console. + /// + /// This performs the following steps: + /// - Calls Namespace::init() to initialize the scripting namespace hierarchy. + /// - Calls ConsoleConstructor::setup() to initialize globally defined console + /// methods and functions. + /// - Registers some basic global script variables. + /// - Calls AbstractClassRep::init() to initialize Torque's class database. + /// - Registers some basic global script functions that couldn't usefully + /// be defined anywhere else. + void init(); + + /// Shuts down the console. + /// + /// This performs the following steps: + /// - Closes the console log file. + /// - Calls Namespace::shutdown() to shut down the scripting namespace hierarchy. + void shutdown(); + + /// Is the console active at this time? + bool isActive(); + + /// @} + + /// @name Console Consumers + /// + /// The console distributes its output through Torque by using + /// consumers. Every time a new line is printed to the console, + /// all the ConsumerCallbacks registered using addConsumer are + /// called, in order. + /// + /// @note The GuiConsole control, which provides the on-screen + /// in-game console, uses a different technique to render + /// the console. It calls getLockLog() to lock the Vector + /// of on-screen console entries, then it renders them as + /// needed. While the Vector is locked, the console will + /// not change the Vector. When the GuiConsole control is + /// done with the console entries, it calls unlockLog() + /// to tell the console that it is again safe to modify + /// the Vector. + /// + /// @see TelnetConsole + /// @see TelnetDebugger + /// @see WinConsole + /// @see MacCarbConsole + /// @see StdConsole + /// @see ConsoleLogger + /// + /// @{ + + /// + void addConsumer(ConsumerCallback cb); + void removeConsumer(ConsumerCallback cb); + + typedef JournaledSignal ConsoleInputEvent; + + /// Called from the native consoles to provide lines of console input + /// to process. This will schedule it for execution ASAP. + extern ConsoleInputEvent smConsoleInput; + + /// @} + + /// @name Miscellaneous + /// @{ + + /// Remove color marking information from a string. + /// + /// @note It does this in-place, so be careful! It may + /// potentially blast data if you're not careful. + /// When in doubt, make a copy of the string first. + void stripColorChars(char* line); + + /// Convert from a relative script path to an absolute script path. + /// + /// This is used in (among other places) the exec() script function, which + /// takes a parameter indicating a script file and executes it. Script paths + /// can be one of: + /// - Absolute: fps/foo/bar.cs Paths of this sort are passed + /// through. + /// - Mod-relative: ~/foo/bar.cs Paths of this sort have their + /// replaced with the name of the current mod. + /// - File-relative: ./baz/blip.cs Paths of this sort are + /// calculated relative to the path of the current scripting file. + /// + /// @note This function determines paths relative to the currently executing + /// CodeBlock. Calling it outside of script execution will result in + /// it directly copying src to filename, since it won't know to what the + /// path is relative! + /// + /// @param filename Pointer to string buffer to fill with absolute path. + /// @param size Size of buffer pointed to by filename. + /// @param src Original, possibly relative script path. + bool expandScriptFilename(char *filename, U32 size, const char *src); + bool expandGameScriptFilename(char *filename, U32 size, const char *src); + bool expandToolScriptFilename(char *filename, U32 size, const char *src); + bool collapseScriptFilename(char *filename, U32 size, const char *src); + + bool isCurrentScriptToolScript(); + + StringTableEntry getModNameFromPath(const char *path); + + /// Returns true if fn is a global scripting function. + /// + /// This looks in the global namespace. It also checks to see if fn + /// is in the StringTable; if not, it returns false. + bool isFunction(const char *fn); + + /// This is the basis for tab completion in the console. + /// + /// @note This is an internally used function. You probably don't + /// care much about how this works. + /// + /// This function does some basic parsing to try to ascertain the namespace in which + /// we are attempting to do tab completion, then bumps control off to the appropriate + /// tabComplete function, either in SimObject or Namespace. + /// + /// @param inputBuffer Pointer to buffer containing starting data, or last result. + /// @param cursorPos Location of cursor in this buffer. This is used to indicate + /// what part of the string should be kept and what part should + /// be advanced to the next match if any. + /// @param maxResultLength Maximum amount of result data to put into inputBuffer. This + /// is capped by MaxCompletionBufferSize. + /// @param forwardTab Should we go forward to next match or backwards to previous + /// match? True indicates forward. + U32 tabComplete(char* inputBuffer, U32 cursorPos, U32 maxResultLength, bool forwardTab); + + /// @} + + + /// @name Variable Management + /// @{ + + /// Add a console variable that references the value of a variable in C++ code. + /// + /// If a value is assigned to the console variable the C++ variable is updated, + /// and vice-versa. + /// + /// @param name Global console variable name to create + /// @param type The type of the C++ variable; see the ConsoleDynamicTypes enum for a complete list. + /// @param pointer Pointer to the variable. + /// @see ConsoleDynamicTypes + bool addVariable(const char *name, S32 type, void *pointer); + + /// Remove a console variable. + /// + /// @param name Global console variable name to remove + /// @return true if variable existed before removal. + bool removeVariable(const char *name); + + /// Assign a string value to a locally scoped console variable + /// + /// @note The context of the variable is determined by gEvalState; that is, + /// by the currently executing code. + /// + /// @param name Local console variable name to set + /// @param value String value to assign to name + void setLocalVariable(const char *name, const char *value); + + /// Retrieve the string value to a locally scoped console variable + /// + /// @note The context of the variable is determined by gEvalState; that is, + /// by the currently executing code. + /// + /// @param name Local console variable name to get + const char* getLocalVariable(const char* name); + + /// @} + + /// @name Global Variable Accessors + /// @{ + /// Assign a string value to a global console variable + /// @param name Global console variable name to set + /// @param value String value to assign to this variable. + void setVariable(const char *name, const char *value); + + /// Retrieve the string value of a global console variable + /// @param name Global Console variable name to query + /// @return The string value of the variable or "" if the variable does not exist. + const char* getVariable(const char* name); + + /// Same as setVariable(), but for bools. + void setBoolVariable (const char* name,bool var); + + /// Same as getVariable(), but for bools. + /// + /// @param name Name of the variable. + /// @param def Default value to supply if no matching variable is found. + bool getBoolVariable (const char* name,bool def = false); + + /// Same as setVariable(), but for ints. + void setIntVariable (const char* name,S32 var); + + /// Same as getVariable(), but for ints. + /// + /// @param name Name of the variable. + /// @param def Default value to supply if no matching variable is found. + S32 getIntVariable (const char* name,S32 def = 0); + + /// Same as setVariable(), but for floats. + void setFloatVariable(const char* name,F32 var); + + /// Same as getVariable(), but for floats. + /// + /// @param name Name of the variable. + /// @param def Default value to supply if no matching variable is found. + F32 getFloatVariable(const char* name,F32 def = .0f); + + /// @} + + /// @name Global Function Registration + /// @{ + + /// Register a C++ function with the console making it a global function callable from the scripting engine. + /// + /// @param name Name of the new function. + /// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value. + /// @param usage Documentation for this function. @ref console_autodoc + /// @param minArgs Minimum number of arguments this function accepts + /// @param maxArgs Maximum number of arguments this function accepts + void addCommand(const char *name, StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + + void addCommand(const char *name, IntCallback cb, const char *usage, S32 minArgs, S32 maxArg, bool toolOnly = false); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32) + void addCommand(const char *name, FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32) + void addCommand(const char *name, VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32) + void addCommand(const char *name, BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32) + /// @} + + /// @name Namespace Function Registration + /// @{ + + /// Register a C++ function with the console making it callable + /// as a method of the given namespace from the scripting engine. + /// + /// @param nameSpace Name of the namespace to associate the new function with; this is usually the name of a class. + /// @param name Name of the new function. + /// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value. + /// @param usage Documentation for this function. @ref console_autodoc + /// @param minArgs Minimum number of arguments this function accepts + /// @param maxArgs Maximum number of arguments this function accepts + void addCommand(const char *nameSpace, const char *name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + void addCommand(const char *nameSpace, const char *name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32) + void addCommand(const char *nameSpace, const char *name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32) + void addCommand(const char *nameSpace, const char *name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32) + void addCommand(const char *nameSpace, const char *name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32) + /// @} + + /// @name Special Purpose Registration + /// + /// These are special-purpose functions that exist to allow commands to be grouped, so + /// that when we generate console docs, they can be more meaningfully presented. + /// + /// @ref console_autodoc "Click here for more information about console docs and grouping." + /// + /// @{ + + void markCommandGroup (const char * nsName, const char *name, const char* usage=NULL); + void beginCommandGroup(const char * nsName, const char *name, const char* usage); + void endCommandGroup (const char * nsName, const char *name); + + void noteScriptCallback(const char *className, const char *funcName, const char *usage); + + /// @deprecated + void addOverload (const char * nsName, const char *name, const char *altUsage); + + /// @} + + /// @name Console Output + /// + /// These functions process the formatted string and pass it to all the ConsumerCallbacks that are + /// currently registered. The console log file and the console window callbacks are installed by default. + /// + /// @see addConsumer() + /// @see removeConsumer() + /// @{ + + /// @param _format A stdlib printf style formatted out put string + /// @param ... Variables to be written + void printf(const char *_format, ...); + + /// @note The console window colors warning text as LIGHT GRAY. + /// @param _format A stdlib printf style formatted out put string + /// @param ... Variables to be written + void warnf(const char *_format, ...); + + /// @note The console window colors warning text as RED. + /// @param _format A stdlib printf style formatted out put string + /// @param ... Variables to be written + void errorf(const char *_format, ...); + + /// @note The console window colors warning text as LIGHT GRAY. + /// @param type Allows you to associate the warning message with an internal module. + /// @param _format A stdlib printf style formatted out put string + /// @param ... Variables to be written + /// @see Con::warnf() + void warnf(ConsoleLogEntry::Type type, const char *_format, ...); + + /// @note The console window colors warning text as RED. + /// @param type Allows you to associate the warning message with an internal module. + /// @param _format A stdlib printf style formatted out put string + /// @param ... Variables to be written + /// @see Con::errorf() + void errorf(ConsoleLogEntry::Type type, const char *_format, ...); + + /// @} + + /// Returns true when called from the main thread, false otherwise + bool isMainThread(); + + + /// @name Console Execution + /// + /// These are functions relating to the execution of script code. + /// + /// @{ + + /// Call a script function from C/C++ code. + /// + /// @param argc Number of elements in the argv parameter + /// @param argv A character string array containing the name of the function + /// to call followed by the arguments to that function. + /// @code + /// // Call a Torque script function called mAbs, having one parameter. + /// char* argv[] = {"abs", "-9"}; + /// char* result = execute(2, argv); + /// @endcode + const char *execute(S32 argc, const char* argv[]); + + /// @see execute(S32 argc, const char* argv[]) +#define ARG const char* + const char *executef( ARG); + const char *executef( ARG, ARG); + const char *executef( ARG, ARG, ARG); + const char *executef( ARG, ARG, ARG, ARG); + const char *executef( ARG, ARG, ARG, ARG, ARG); + const char *executef( ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef( ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG); +#undef ARG + + + /// Call a Torque Script member function of a SimObject from C/C++ code. + /// @param object Object on which to execute the method call. + /// @param argc Number of elements in the argv parameter (must be >2, see argv) + /// @param argv A character string array containing the name of the member function + /// to call followed by an empty parameter (gets filled with object ID) + /// followed by arguments to that function. + /// @code + /// // Call the method setMode() on an object, passing it one parameter. + /// + /// char* argv[] = {"setMode", "", "2"}; + /// char* result = execute(mysimobject, 3, argv); + /// @endcode + const char *execute(SimObject *object, S32 argc, const char *argv[], bool thisCallOnly = false); + + /// @see execute(SimObject *, S32 argc, const char *argv[]) +#define ARG const char* + const char *executef(SimObject *, ARG); + const char *executef(SimObject *, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG); + const char *executef(SimObject *, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG, ARG); +#undef ARG + + /// Evaluate an arbitrary chunk of code. + /// + /// @param string Buffer containing code to execute. + /// @param echo Should we echo the string to the console? + /// @param fileName Indicate what file this code is coming from; used in error reporting and such. + const char *evaluate(const char* string, bool echo = false, const char *fileName = NULL); + + /// Evaluate an arbitrary line of script. + /// + /// This wraps dVsprintf(), so you can substitute parameters into the code being executed. + const char *evaluatef(const char* string, ...); + + /// @} + + /// @name Console Function Implementation Helpers + /// + /// The functions Con::getIntArg, Con::getFloatArg and Con::getArgBuffer(size) are used to + /// allocate on the console stack string variables that will be passed into the next console + // function called. This allows the console to avoid copying some data. + /// + /// getReturnBuffer lets you allocate stack space to return data in. + /// @{ + + /// + char *getReturnBuffer(U32 bufferSize); + char *getReturnBuffer(const char *stringToCopy); + + char *getArgBuffer(U32 bufferSize); + char *getFloatArg(F64 arg); + char *getIntArg (S32 arg); + char* getStringArg( const String& arg ); + /// @} + + /// @name Namespaces + /// @{ + + Namespace *lookupNamespace(const char *nsName); + bool linkNamespaces(const char *parentName, const char *childName); + bool unlinkNamespaces(const char *parentName, const char *childName); + + /// @note This should only be called from consoleObject.h + bool classLinkNamespaces(Namespace *parent, Namespace *child); + + const char *getNamespaceList(Namespace *ns); + /// @} + + /// @name Logging + /// @{ + + void getLockLog(ConsoleLogEntry * &log, U32 &size); + void unlockLog(void); + void setLogMode(S32 mode); + + /// @} + + /// @name Instant Group + /// @{ + + void pushInstantGroup( String name = String() ); + void popInstantGroup(); + + /// @} + + /// @name Dynamic Type System + /// @{ + + /// +/* void registerType( const char *typeName, S32 type, S32 size, GetDataFunction gdf, SetDataFunction sdf, bool isDatablockType = false ); + void registerType( const char* typeName, S32 type, S32 size, bool isDatablockType = false ); + void registerTypeGet( S32 type, GetDataFunction gdf ); + void registerTypeSet( S32 type, SetDataFunction sdf ); + + const char *getTypeName(S32 type); + bool isDatablockType( S32 type ); */ + + void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, const EnumTable *tbl = NULL, BitSet32 flag = 0); + const char *getData(S32 type, void *dptr, S32 index, const EnumTable *tbl = NULL, BitSet32 flag = 0); + const char *getFormattedData(S32 type, const char *data, const EnumTable *tbl = NULL, BitSet32 flag = 0); + /// @} +}; + +extern void expandEscape(char *dest, const char *src); +extern bool collapseEscape(char *buf); +extern S32 HashPointer(StringTableEntry ptr); + +/// This is the backend for the ConsoleMethod()/ConsoleFunction() macros. +/// +/// See the group ConsoleConstructor Innards for specifics on how this works. +/// +/// @see @ref console_autodoc +/// @nosubgrouping +class ConsoleConstructor +{ +public: + /// @name Entry Type Fields + /// + /// One of these is set based on the type of entry we want + /// inserted in the console. + /// + /// @ref console_autodoc + /// @{ + StringCallback sc; ///< A function/method that returns a string. + IntCallback ic; ///< A function/method that returns an int. + FloatCallback fc; ///< A function/method that returns a float. + VoidCallback vc; ///< A function/method that returns nothing. + BoolCallback bc; ///< A function/method that returns a bool. + bool group; ///< Indicates that this is a group marker. + bool overload; ///< Indicates that this is an overload marker. + bool ns; ///< Indicates that this is a namespace marker. + /// @deprecated Unused. + bool callback; ///< Is this a callback into script? + /// @} + + /// Minimum/maximum number of arguments for the function. + S32 mina, maxa; + const char *usage; ///< Usage string. + const char *funcName; ///< Function name. + const char *className; ///< Class name. + + bool toolOnly; + + /// @name ConsoleConstructer Innards + /// + /// The ConsoleConstructor class is used as the backend for the ConsoleFunction() and + /// ConsoleMethod() macros. The way it works takes advantage of several properties of + /// C++. + /// + /// The ConsoleFunction()/ConsoleMethod() macros wrap the declaration of a ConsoleConstructor. + /// + /// @code + /// // The definition of a ConsoleFunction using the macro + /// ConsoleFunction(ExpandFilename, const char*, 2, 2, "(string filename)") + /// { + /// argc; + /// char* ret = Con::getReturnBuffer( 1024 ); + /// Con::expandScriptFilename(ret, 1024, argv[1]); + /// return ret; + /// } + /// + /// // Resulting code + /// static const char* cExpandFilename(SimObject *, S32, const char **argv); + /// static ConsoleConstructor + /// gExpandFilenameobj(NULL,"ExpandFilename", cExpandFilename, + /// "(string filename)", 2, 2); + /// static const char* cExpandFilename(SimObject *, S32 argc, const char **argv) + /// { + /// argc; + /// char* ret = Con::getReturnBuffer( 1024 ); + /// Con::expandScriptFilename(ret, 1024, argv[1]); + /// return ret; + /// } + /// + /// // A similar thing happens when you do a ConsoleMethod. + /// @endcode + /// + /// As you can see, several global items are defined when you use the ConsoleFunction method. + /// The macro constructs the name of these items from the parameters you passed it. Your + /// implementation of the console function is is placed in a function with a name based on + /// the actual name of the console funnction. In addition, a ConsoleConstructor is declared. + /// + /// Because it is defined as a global, the constructor for the ConsoleConstructor is called + /// before execution of main() is started. The constructor is called once for each global + /// ConsoleConstructor variable, in the order in which they were defined (this property only holds true + /// within file scope). + /// + /// We have ConsoleConstructor create a linked list at constructor time, by storing a static + /// pointer to the head of the list, and keeping a pointer to the next item in each instance + /// of ConsoleConstructor. init() is a helper function in this process, automatically filling + /// in commonly used fields and updating first and next as needed. In this way, a list of + /// items to add to the console is assemble in memory, ready for use, before we start + /// execution of the program proper. + /// + /// In Con::init(), ConsoleConstructor::setup() is called to process this prepared list. Each + /// item in the list is iterated over, and the appropriate Con namespace functions (usually + /// Con::addCommand) are invoked to register the ConsoleFunctions and ConsoleMethods in + /// the appropriate namespaces. + /// + /// @see Namespace + /// @see Con + /// @{ + + /// + ConsoleConstructor *next; + static ConsoleConstructor *first; + + void init(const char *cName, const char *fName, const char *usg, S32 minArgs, S32 maxArgs, bool toolOnly = false); + + static void setup(); + + /// Validate there are no duplicate entries for this item. + void validate(); + + /// @} + + /// @name Basic Console Constructors + /// @{ + + ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char* usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char* usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char* usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char* usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char* usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + /// @} + + /// @name Magic Console Constructors + /// + /// These perform various pieces of "magic" related to consoleDoc functionality. + /// @ref console_autodoc + /// @{ + + /// Indicates a group marker. (A doxygen illusion) + /// + /// @see Con::markCommandGroup + /// @ref console_autodoc + ConsoleConstructor(const char *className, const char *groupName, const char* usage); + + /// Indicates a callback declared with the DECLARE_SCRIPT_CALLBACK macro and friends. + ConsoleConstructor(const char *className, const char *callbackName, const char *usage, bool isRequired); + + /// @} +}; + +/// @name Global Console Definition Macros +/// +/// @note If TORQUE_DEBUG is defined, then we gather documentation information, and +/// do some extra sanity checks. +/// +/// @see ConsoleConstructor +/// @ref console_autodoc +/// @{ + + +/// Define a C++ method that calls back to script on an object. +/// +/// @see consoleCallback.h +#define DECLARE_CONSOLE_CALLBACK(theType, name, args) \ + virtual theType name args + +// O hackery of hackeries +#define conmethod_return_const return (const +#define conmethod_return_S32 return (S32 +#define conmethod_return_F32 return (F32 +#define conmethod_nullify(val) +#define conmethod_return_void conmethod_nullify(void +#define conmethod_return_bool return (bool + +#if !defined(TORQUE_SHIPPING) + +// Console function macros +# define ConsoleFunctionGroupBegin(groupName, usage) \ + static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupBegin(NULL,#groupName,usage) + +# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \ + static returnType c##name(SimObject *, S32, const char **argv); \ + static ConsoleConstructor g##name##obj(NULL,#name,c##name,usage1,minArgs,maxArgs); \ + static returnType c##name(SimObject *, S32 argc, const char **argv) + +# define ConsoleToolFunction(name,returnType,minArgs,maxArgs,usage1) \ + static returnType c##name(SimObject *, S32, const char **argv); \ + static ConsoleConstructor g##name##obj(NULL,#name,c##name,usage1,minArgs,maxArgs, true); \ + static returnType c##name(SimObject *, S32 argc, const char **argv) + +# define ConsoleFunctionGroupEnd(groupName) \ + static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupEnd(NULL,#groupName,NULL) + +// Console method macros +# define ConsoleNamespace(className, usage) \ + static ConsoleConstructor className##__Namespace(#className, usage) + +# define ConsoleMethodGroupBegin(className, groupName, usage) \ + static ConsoleConstructor className##groupName##__GroupBegin(#className,#groupName,usage) + +# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \ + static inline returnType c##className##name(className *, S32, const char **argv); \ + static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \ + AssertFatal( dynamic_cast( object ), "Object passed to " #name " is not a " #className "!" ); \ + conmethod_return_##returnType ) c##className##name(static_cast(object),argc,argv); \ + }; \ + static ConsoleConstructor className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \ + static inline returnType c##className##name(className *object, S32 argc, const char **argv) + +# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \ + static inline returnType c##className##name(S32, const char **); \ + static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \ + conmethod_return_##returnType ) c##className##name(argc,argv); \ + }; \ + static ConsoleConstructor \ + className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \ + static inline returnType c##className##name(S32 argc, const char **argv) + +# define ConsoleMethodGroupEnd(className, groupName) \ + static ConsoleConstructor className##groupName##__GroupEnd(#className,#groupName,NULL) + +#else + +// These do nothing if we don't want doc information. +# define ConsoleFunctionGroupBegin(groupName, usage) +# define ConsoleFunctionGroupEnd(groupName) +# define ConsoleNamespace(className, usage) +# define ConsoleMethodGroupBegin(className, groupName, usage) +# define ConsoleMethodGroupEnd(className, groupName) + +// These are identical to what's above, we just want to null out the usage strings. +# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \ + static returnType c##name(SimObject *, S32, const char **); \ + static ConsoleConstructor g##name##obj(NULL,#name,c##name,"",minArgs,maxArgs);\ + static returnType c##name(SimObject *, S32 argc, const char **argv) + +# define ConsoleToolFunction(name,returnType,minArgs,maxArgs,usage1) \ + static returnType c##name(SimObject *, S32, const char **); \ + static ConsoleConstructor g##name##obj(NULL,#name,c##name,"",minArgs,maxArgs, true);\ + static returnType c##name(SimObject *, S32 argc, const char **argv) + +# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \ + static inline returnType c##className##name(className *, S32, const char **argv); \ + static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \ + conmethod_return_##returnType ) c##className##name(static_cast(object),argc,argv); \ + }; \ + static ConsoleConstructor \ + className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \ + static inline returnType c##className##name(className *object, S32 argc, const char **argv) + +# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \ + static inline returnType c##className##name(S32, const char **); \ + static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \ + conmethod_return_##returnType ) c##className##name(argc,argv); \ + }; \ + static ConsoleConstructor \ + className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \ + static inline returnType c##className##name(S32 argc, const char **argv) + + +#endif + +/// @} + +#endif diff --git a/console/consoleCallback.cpp b/console/consoleCallback.cpp new file mode 100644 index 0000000..0512cef --- /dev/null +++ b/console/consoleCallback.cpp @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleCallback.h" +#include "console/simBase.h" +#include "math/mathTypes.h" +#include "math/mPoint3.h" + +// FIXME [tom, 3/14/2007] Temporarily commenting this out because it's broken and +// we don't currently need it. It will more then likely require fixing before we +// have a working build, but for now it's one less error to worry about. + + class CBTest : public SimObject + { + typedef SimObject Parent; + public: + DECLARE_CONOBJECT(CBTest); + DECLARE_CONSOLE_CALLBACK(Point3F, onCollide, + (Point3F pos, Point3F normal, S32 b, F32 c)); + }; + + IMPLEMENT_CONOBJECT(CBTest); + + IMPLEMENT_CONSOLE_CALLBACK(CBTest, Point3F, onCollide, + (Point3F pos, Point3F normal, S32 b, F32 c), (pos, normal, b, c), + true, "Simple callback issued on collision events.") + + ConsoleFunction(testCB, void, 1, 1, "Test that callbacks can happen!") + { + CBTest *testObj = new CBTest(); + testObj->registerObject("testCallback"); + Point3F foo = testObj->onCollide(Point3F(1,2,3), Point3F(4,5,6), 1, 2.0); + } diff --git a/console/consoleCallback.h b/console/consoleCallback.h new file mode 100644 index 0000000..f64527b --- /dev/null +++ b/console/consoleCallback.h @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLE_CONSOLECALLBACK_H_ +#define _CONSOLE_CONSOLECALLBACK_H_ + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/frameAllocator.h" +#include "core/stringTable.h" + +/// Matching implement for DECLARE_CONSOLE_CALLBACK. +#define IMPLEMENT_CONSOLE_CALLBACK(theClass, theType, name, args, argNames, requiredFlag, usageString) \ + theType theClass::name args \ +{ \ + ScriptCallbackHelper cbh; \ + cbh.setCallback(#name, this); \ + cbh.storeArgs argNames; \ + cbh.issueCallback(); \ + theType res; dMemset(&res, 0, sizeof(res)); castConsoleTypeFromString(res, cbh._result); \ + return res; \ +} \ + ConsoleConstructor cs_##theClass##_##retType##_##name(#theClass, #name, #theType " " #name #args " - " usageString, requiredFlag); + +/// Helper class to interface with the console for script callbacks. +/// +/// @see IMPLEMENT_CONSOLE_CALLBACK, DECLARE_CONSOLE_CALLBACK +struct ScriptCallbackHelper +{ + StringTableEntry _cbName; + SimObject *_self; + S32 _argc; + + /// Matches up to storeArgs. + const static S32 csmMaxArguments=10; + + const char *_argv[csmMaxArguments+2]; + const char *_result; + FrameAllocatorMarker _famAlloc; + + ScriptCallbackHelper() + { + _argc = 0; + } + + ~ScriptCallbackHelper() + { + + } + + inline void setCallback(const char * name, SimObject *obj) + { + _cbName = StringTable->insert(name); + _self = obj; + _argc = _self != 0 ? 2 : 1; + _argv[0] = name; + } + + template void processArg(T &arg) + { + // Convert to string and add to list of args. + const char *tmpRes = castConsoleTypeToString(arg); + char *tmp = (char *)_famAlloc.alloc(dStrlen(tmpRes) + 1); + dStrcpy(tmp, tmpRes); + _argv[_argc++] = tmp; + } + + void issueCallback() + { + if(_self) + _result = Con::execute(_self, _argc, _argv); + else + _result = Con::execute(_argc, _argv); + } + + template void storeArgs(A &a) { processArg(a); } + template void storeArgs(A &a, B &b) { processArg(a); processArg(b); } + + template + void storeArgs(A &a, B &b, C &c) + { processArg(a); processArg(b); processArg(c); } + + template + void storeArgs(A &a, B &b, C &c, D &d) + { processArg(a); processArg(b); processArg(c); processArg(d); } + + template + void storeArgs(A &a, B &b, C &c, D &d, E&e) + { processArg(a); processArg(b); processArg(c); processArg(d); processArg(e); } + + template + void storeArgs(A &a, B &b, C &c, D &d, E&e, F&f) + { processArg(a); processArg(b); processArg(c); processArg(d); processArg(e); processArg(f); } + + template + void storeArgs(A &a, B &b, C &c, D &d, E&e, F&f, G&g) + { processArg(a); processArg(b); processArg(c); processArg(d); processArg(e); processArg(f); } + + template + void storeArgs(A &a, B &b, C &c, D &d, E&e, F&f, G&g, H&h) + { processArg(a); processArg(b); processArg(c); processArg(d); processArg(e); processArg(f); processArg(g); } + + template + void storeArgs(A &a, B &b, C &c, D &d, E&e, F&f, G&g, H&h, I&i) + { processArg(a); processArg(b); processArg(c); processArg(d); processArg(e); processArg(f); processArg(g); processArg(h); } + + template + void storeArgs(A &a, B &b, C &c, D &d, E&e, F&f, G&g, H&h, I&i, J&j) + { processArg(a); processArg(b); processArg(c); processArg(d); processArg(e); processArg(f); processArg(g); processArg(h); processArg(i); } + +}; + + +#endif \ No newline at end of file diff --git a/console/consoleDoc.cpp b/console/consoleDoc.cpp new file mode 100644 index 0000000..1b0b085 --- /dev/null +++ b/console/consoleDoc.cpp @@ -0,0 +1,612 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" + +#include "core/strings/findMatch.h" +#include "console/consoleInternal.h" +#include "console/consoleObject.h" +#include "core/stream/fileStream.h" +#include "console/compiler.h" +#include "core/frameAllocator.h" + +//--- Information pertaining to this page... ------------------ +/// @file +/// +/// For specifics on using the consoleDoc functionality, see @ref console_autodoc + +ConsoleFunctionGroupBegin(ConsoleDoc, "Console self-documentation functions. These output psuedo C++ suitable for feeeding through Doxygen or another auto documentation tool."); + +ConsoleFunction(dumpConsoleClasses, void, 1, 3, "(bool dumpScript = true, bool dumpEngine = true) dumps all declared console classes to the console.\n" + "@param dumpScript Specifies whether or not classes defined in script should be dumped.\n" + "@param dumpEngine Specifies whether or not classes defined in the engine should be dumped.") +{ + bool dumpScript = true; + if( argc > 1 ) + dumpScript = dAtob( argv[1] ); + + bool dumpEngine = true; + if( argc > 2 ) + dumpEngine = dAtob( argv[2] ); + + Namespace::dumpClasses( dumpScript, dumpEngine ); +} + +ConsoleFunction(dumpConsoleFunctions, void, 1, 3, "(bool dumpScript = true, bool dumpEngine = true) Dumps all declared console functions to the console.\n" + "@param dumpScript Specifies whether or not functions defined in script should be dumped.\n" + "@param dumpEngine Specifies whether or not functions defined in the engine should be dumped.") +{ + bool dumpScript = true; + if( argc > 1 ) + dumpScript = dAtob( argv[1] ); + + bool dumpEngine = true; + if( argc > 2 ) + dumpEngine = dAtob( argv[2] ); + + Namespace::dumpFunctions( dumpScript, dumpEngine ); +} + +ConsoleFunctionGroupEnd(ConsoleDoc); + +/// Helper table to convert type ids to human readable names. +const char *typeNames[] = +{ + "Script", + "string", + "int", + "float", + "void", + "bool", + "", + "", + "unknown_overload" +}; + +void printClassHeader(const char* usage, const char * className, const char * superClassName, const bool stub) +{ + if(stub) + { + Con::printf("/// Stub class"); + Con::printf("/// "); + Con::printf("/// @note This is a stub class to ensure a proper class hierarchy. No "); + Con::printf("/// information was available for this class."); + } + + if( usage != NULL ) + { + // Copy Usage Document + S32 usageLen = dStrlen( usage ); + FrameTemp usageStr( usageLen ); + dStrcpy( usageStr, usage ); + + // Print Header + Con::printf( "/*!" ); + + // Print line by line, skipping the @field lines. + // + // fetch first line end + char *newLine = dStrchr( usageStr, '\n' ); + char *usagePtr = usageStr; + do + { + // Copy of one line + static char lineStr[2048] = {0}; + // Keyword will hold the last keyword (word following '@' or '\') encountered. + static char keyword[8] = {0}; + + S32 lineLen = 0; + + // If not the last line, increment pointer + if( newLine != NULL ) + { + *newLine = '\0'; + newLine ++; + } + + // Copy line and update usagePtr + dStrcpy( lineStr, usagePtr ); + usagePtr = (newLine != NULL ) ? newLine : usagePtr; + lineLen = dStrlen( lineStr ); + + // Get the keyword. This is the first word after an '@' or '\'. + const char* tempkw = dStrchr( lineStr, '@' ); + if( !tempkw ) + tempkw = dStrchr( lineStr, '\\' ); + + // If we found a new keyword, set it, otherwise, keep using the + // most recently found. + if( tempkw ) + { + dStrncpy( keyword, tempkw + 1, 5 ); + keyword[5] = '\0'; + } + + // Print all fields that aren't associated with the 'field' keyword. + if( dStrcmp( keyword, "field" ) ) + Con::printf( lineStr ); + + + // Fetch next line ending + newLine = dStrchr( usagePtr, '\n' ); + } while( newLine != NULL ); + + // DocBlock Footer + Con::printf( " */" ); + + } + + // Print out appropriate class header + if(superClassName) + Con::printf("class %s : public %s {", className, superClassName ? superClassName : ""); + else if(!className) + Con::printf("namespace Global {"); + else + Con::printf("class %s {", className); + + if(className) + Con::printf(" public:"); + +} + +void printClassMethod(const bool isVirtual, const char *retType, const char *methodName, const char* args, const char*usage) +{ + if(usage && usage[0] != ';' && usage[0] != 0) + Con::printf(" /*! %s */", usage); + Con::printf(" %s%s %s(%s) {}", isVirtual ? "virtual " : "", retType, methodName, args); +} + +void printGroupStart(const char * aName, const char * aDocs) +{ + Con::printf(""); + Con::printf(" /*! @name %s", aName); + + if(aDocs) + { + Con::printf(" "); + Con::printf(" %s", aDocs); + } + + Con::printf(" @{ */"); + + // Add a blank comment in order to make sure groups are parsed properly. + Con::printf(" /*! */"); +} + +void printClassMember(const bool isDeprec, const char * aType, const char * aName, const char * aDocs) +{ + Con::printf(" /*!"); + + if(aDocs) + { + Con::printf(" %s", aDocs); + Con::printf(" "); + } + + if(isDeprec) + Con::printf(" @deprecated This member is deprecated, which means that its value is always undefined."); + + Con::printf(" */"); + + Con::printf(" %s %s;", isDeprec ? "deprecated" : aType, aName); +} + +void printGroupEnd() +{ + Con::printf(" /// @}"); + Con::printf(""); +} + +void printClassFooter() +{ + Con::printf("};"); + Con::printf(""); +} + +void Namespace::printNamespaceEntries(Namespace * g, bool dumpScript, bool dumpEngine ) +{ + static bool inGroup = false; + + // Go through all the entries. + // Iterate through the methods of the namespace... + for(Entry *ewalk = g->mEntryList; ewalk; ewalk = ewalk->mNext) + { + char buffer[1024]; //< This will bite you in the butt someday. + int eType = ewalk->mType; + const char * funcName = ewalk->mFunctionName; + + if( ( eType == Entry::ConsoleFunctionType ) && !dumpScript ) + continue; + + if( ( eType != Entry::ConsoleFunctionType ) && !dumpEngine ) + continue; + + // If it's a function + if(eType >= Entry::ConsoleFunctionType || eType == Entry::OverloadMarker) + { + if(eType==Entry::OverloadMarker) + { + // Deal with crap from the OverloadMarker case. + // It has no type information so we have to "correct" its type. + + // Find the original + eType = 8; + for(Entry *eseek = g->mEntryList; eseek; eseek = eseek->mNext) + { + if(!dStrcmp(eseek->mFunctionName, ewalk->cb.mGroupName)) + { + eType = eseek->mType; + break; + } + } + // And correct the name + funcName = ewalk->cb.mGroupName; + } + + // A quick note - if your usage field starts with a (, then it's auto-integrated into + // the script docs! Use this HEAVILY! + + // We add some heuristics here as well. If you're of the form: + // *.methodName(*) + // then we will also extract parameters. + + const char *use = ewalk->mUsage ? ewalk->mUsage : ""; + const char *bgn = dStrchr(use, '('); + const char *end = dStrchr(use, ')'); + const char *dot = dStrchr(use, '.'); + + while( *use == ' ' ) + use++; + + if(use[0] == '(') + { + if(!end) + end = use + 1; + + use++; + + U32 len = end - use; + dStrncpy(buffer, use, len); + buffer[len] = 0; + + printClassMethod(true, typeNames[eType], funcName, buffer, end+1); + + continue; // Skip to next one. + } + + // We check to see if they're giving a prototype. + if(dot && bgn && end) // If there's two parentheses, and a dot... + if(dot < bgn && bgn < end) // And they're in the order dot, bgn, end... + { + use++; + U32 len = end - bgn - 1; + dStrncpy(buffer, bgn+1, len); + buffer[len] = 0; + + // Then let's do the heuristic-trick + printClassMethod(true, typeNames[eType], funcName, buffer, end+1); + continue; // Get to next item. + } + + // Finally, see if they did it foo(*) style. + char* func_pos = dStrstr(use, funcName); + if((func_pos) && (func_pos < bgn) && (end > bgn)) + { + U32 len = end - bgn - 1; + dStrncpy(buffer, bgn+1, len); + buffer[len] = 0; + + printClassMethod(true, typeNames[eType], funcName, buffer, end+1); + continue; + } + + // Default... + printClassMethod(true, typeNames[eType], funcName, "", ewalk->mUsage); + } + else if(ewalk->mType == Entry::GroupMarker) + { + if(!inGroup) + printGroupStart(ewalk->cb.mGroupName, ewalk->mUsage); + else + printGroupEnd(); + + inGroup = !inGroup; + } + else if(ewalk->mType == Entry::ScriptCallbackType) + { + // It's a script callback - emit some sort of appropriate info. + // Grab the signature from the usage. + char *splitPt = (char*)dStrchr(ewalk->mUsage, '-'); + + Con::printf(" /*! %s */", splitPt + 2); + + char signature[1024]; + dMemset(signature, 0, 1024); + dStrncpy(signature, ewalk->mUsage, splitPt - ewalk->mUsage - 1); + Con::printf(" %s;", signature); + Con::printf(""); + + } + else if(ewalk->mFunctionOffset) // If it's a builtin function... + { + ewalk->mCode->getFunctionArgs(buffer, ewalk->mFunctionOffset); + printClassMethod(false, typeNames[ewalk->mType], ewalk->mFunctionName, buffer, ""); + } + else + { + Con::printf(" // got an unknown thing?? %d", ewalk->mType ); + } + } + +} + +void Namespace::dumpClasses( bool dumpScript, bool dumpEngine ) +{ + VectorPtr vec; + trashCache(); + vec.reserve( 1024 ); + + // We use mHashSequence to mark if we have traversed... + // so mark all as zero to start. + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + walk->mHashSequence = 0; + + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + { + VectorPtr stack; + stack.reserve( 1024 ); + + // Get all the parents of this namespace... (and mark them as we go) + Namespace *parentWalk = walk; + while(parentWalk) + { + if(parentWalk->mHashSequence != 0) + break; + if(parentWalk->mPackage == 0) + { + parentWalk->mHashSequence = 1; // Mark as traversed. + stack.push_back(parentWalk); + } + parentWalk = parentWalk->mParent; + } + + // Load stack into our results vector. + while(stack.size()) + { + vec.push_back(stack[stack.size() - 1]); + stack.pop_back(); + } + } + + // Go through previously discovered classes + U32 i; + for(i = 0; i < vec.size(); i++) + { + const char *className = vec[i]->mName; + const char *superClassName = vec[i]->mParent ? vec[i]->mParent->mName : NULL; + + // Skip the global namespace, that gets dealt with in dumpFunctions + if(!className) continue; + + // If we're just dumping script functions, then we don't want to dump + // a class that only contains script functions. So, we iterate over all + // the functions. + if( !dumpScript ) + { + bool found = false; + for(Entry *ewalk = vec[i]->mEntryList; ewalk; ewalk = ewalk->mNext) + { + if( ewalk->mType != Entry::ConsoleFunctionType ) + { + found = true; + break; + } + } + if( !found ) + continue; + } + + // And we do the same for engine functions. + if( !dumpEngine ) + { + bool found = false; + for(Entry *ewalk = vec[i]->mEntryList; ewalk; ewalk = ewalk->mNext) + { + if( ewalk->mType == Entry::ConsoleFunctionType ) + { + found = true; + break; + } + } + if( !found ) + continue; + } + + // If we hit a class with no members and no classRep, do clever filtering. + if(vec[i]->mEntryList == NULL && vec[i]->mClassRep == NULL) + { + // Print out a short stub so we get a proper class hierarchy. + if(superClassName) { // Filter hack; we don't want non-inheriting classes... + printClassHeader( NULL, className,superClassName, true); + printClassFooter(); + } + continue; + } + + // Print the header for the class.. + printClassHeader(vec[i]->mUsage, className, superClassName, false); + + // Deal with entries. + printNamespaceEntries(vec[i], dumpScript, dumpEngine); + + // Deal with the classRep (to get members)... + AbstractClassRep *rep = vec[i]->mClassRep; + AbstractClassRep::FieldList emptyList; + AbstractClassRep::FieldList *parentList = &emptyList; + AbstractClassRep::FieldList *fieldList = &emptyList; + + // Since all fields are defined in the engine, if we're not dumping + // engine stuff, than we shouldn't dump the fields. + if(dumpEngine && rep) + { + // Get information about the parent's fields... + AbstractClassRep *parentRep = vec[i]->mParent ? vec[i]->mParent->mClassRep : NULL; + if(parentRep) + parentList = &(parentRep->mFieldList); + + // Get information about our fields + fieldList = &(rep->mFieldList); + + // Go through all our fields... + for(U32 j = 0; j < fieldList->size(); j++) + { + switch((*fieldList)[j].type) + { + case AbstractClassRep::StartArrayFieldType: + case AbstractClassRep::EndArrayFieldType: + break; + case AbstractClassRep::StartGroupFieldType: + printGroupStart((*fieldList)[j].pGroupname, (*fieldList)[j].pFieldDocs); + break; + case AbstractClassRep::EndGroupFieldType: + printGroupEnd(); + break; + default: + case AbstractClassRep::DeprecatedFieldType: + { + bool isDeprecated = ((*fieldList)[j].type == AbstractClassRep::DeprecatedFieldType); + + if(isDeprecated) + { + printClassMember( + true, + "", + (*fieldList)[j].pFieldname, + (*fieldList)[j].pFieldDocs + ); + } + else + { + ConsoleBaseType *cbt = ConsoleBaseType::getType((*fieldList)[j].type); + + printClassMember( + false, + cbt ? cbt->getTypeClassName() : "", + (*fieldList)[j].pFieldname, + (*fieldList)[j].pFieldDocs + ); + } + } + } + } + } + + if( dumpScript ) + { + // Print out fields defined in script docs for this namespace. + // These fields are specified by the 'field' keyword in the usage + // string. + + // The field type and name. + char fieldName[256]; + char fieldDoc[1024]; + + // Usage string iterator. + const char* field = vec[i]->mUsage; + + while( field ) + { + // Find the first field keyword. + const char* tempField = dStrstr( field, "@field" ); + if( !tempField ) + tempField = dStrstr( field, "\\field" ); + + field = tempField; + + if( !field ) + break; + + // Move to the field name. + field += 7; + + // Copy the field type and name. These should both be followed by a + // space so only in this case will we actually store it. + S32 spaceCount = 0; + S32 index = 0; + bool valid = false; + while( field && ( *field != '\n' ) ) + { + if( index >= 255 ) + break; + + if( *field == ' ' ) + spaceCount++; + + if( spaceCount == 2 ) + { + valid = true; + break; + } + + fieldName[index++] = *field; + field++; + } + + if( !valid ) + continue; + + fieldName[index] = '\0'; + + // Now copy from field to the next keyword. + const char* nextKeyword = dStrchr( field, '@' ); + if( !nextKeyword ) + nextKeyword = dStrchr( field, '\\' ); + + // Grab the length of the doc string. + S32 docLen = dStrlen( field ); + if( nextKeyword ) + docLen = nextKeyword - field; + + // Make sure it will fit in the buffer. + if( docLen > 1023 ) + docLen = 1023; + + // Copy. + dStrncpy( fieldDoc, field, docLen ); + fieldDoc[docLen] = '\0'; + field += docLen; + + // Print + Con::printf( " /*!" ); + Con::printf( " %s", fieldDoc ); + Con::printf( " */" ); + Con::printf( " %s;", fieldName ); + } + } + + // Close the class/namespace. + printClassFooter(); + } +} + +void Namespace::dumpFunctions( bool dumpScript, bool dumpEngine ) +{ + // Get the global namespace. + Namespace* g = find(NULL); //->mParent; + + printClassHeader(NULL, NULL,NULL, false); + + while(g) + { + printNamespaceEntries(g, dumpScript, dumpEngine ); + g = g->mParent; + } + + printClassFooter(); +} diff --git a/console/consoleDoc.h b/console/consoleDoc.h new file mode 100644 index 0000000..64a8e3e --- /dev/null +++ b/console/consoleDoc.h @@ -0,0 +1,176 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// This file exists solely to document consoleDoc.cc + +/// @page console_autodoc Console Auto-Documentation +/// +/// @see consoleDoc.cc +/// +/// @section console_autodoc_using Using Console Auto-Documentation +/// +/// There are on the order of three hundred functions exposed to the script language +/// through the console. It is therefore extremely important that they be documented, +/// but due to their number, it is difficult to maintain a seperate reference document. +/// +/// Therefore, a simple documentation system has been built into the scripting engine. It +/// was initially added by Mark Frohnmayer, and later enhanced by Ben Garney. The +/// scripting engine supports grouping functions and methods, to help organize the +/// several hundred functions, as well as associating a "usage string" with functions and +/// groups. +/// +/// @note The results of a console doc dump will vary depending on when you run it. If +/// you run it, for example, while in the game menu, it won't output any data for +/// the script-defined classes which are defined for gameplay. To get comprehensive +/// documentation, you may need to write a special script that will get all your +/// classes loaded into the scripting engine. +/// +/// The console documentation system is designed to output a dump of the current state +/// of the scripting engine in a format understandable by Doxygen. It does this by +/// traversing the namespace/class hierarchy in memory at the time of the dump, and +/// outputting psuedo-C++ code equivalent to this class hierarchy. +/// +/// @subsection console_autodoc_using_script For the Scripter... +/// +/// Currently, there is no way to associate usage strings or other documentation with script code +/// like you can with C++ code. +/// +/// You can get a list of all the methods and fields of an object from any object which inherits +/// from SimObject (ie, every object), as well as the documentation on those objects by using the +/// dump() method from the console: +/// +/// @code +/// ==>$foo = new SimObject(); +/// ==>$foo.dump(); +/// Member Fields: +/// Tagged Fields: +/// Methods: +/// delete() - obj.delete() +/// dump() - obj.dump() +/// getClassName() - obj.getClassName() +/// getGroup() - obj.getGroup() +/// getId() - obj.getId() +/// getName() - obj.getName() +/// getType() - obj.getType() +/// save() - obj.save(fileName, ) +/// schedule() - object.schedule(time, command, ); +/// setName() - obj.setName(newName) +/// @endcode +/// +/// In the Torque example app, there are two functions defined in common\\client\\scriptDoc.cs +/// which automate the process of dumping the documentation. They make use of the ConsoleLogger +/// object to output the documentation to a file, and look like this: +/// +/// @note You may want to add this code, or code like it, to your project if you have +/// rewritten the script code in common. +/// +/// @code +/// // Writes out all script functions to a file +/// function writeOutFunctions() { +/// new ConsoleLogger( logger, "ConsoleFunctions.txt", false ); +/// dumpConsoleFunctions(); +/// logger.delete(); +/// } +/// +/// // Writes out all script classes to a file +/// function writeOutClasses() { +/// new ConsoleLogger( logger, "scriptClasses.txt", false ); +/// dumpConsoleClasses(); +/// logger.delete(); +/// } +/// @endcode +/// +/// @subsection console_autodoc_using_coder For the C++ Coder... +/// +/// @note It is of the utmost important that you keep your usage strings up to date! +/// Usage strings are the only way that a scripter has to know how to use the methods, +/// functions, and variables you expose. Misleading, missing, or out of date documentation +/// will make their lives much harder - and yours, too, because you'll have to keep +/// explaining things to them! So make everyone's lives easier - keep your usage strings +/// clear, concise, and up to date. +/// +/// There are four types of items which can be documented using the autodocumentation system: +/// - Fields, which are defined using the addField() calls. They are documented +/// by passing a string to the usage parameter. +/// - Field groups, which are defined using the beginGroup() and endGroup() calls. +/// They are documented by passing a descriptive string to the usage parameter. +/// - Method groups, which are defined using beginCommandGroup(), endCommandGroup(), +/// ConsoleMethodGroupEnd(), ConsoleMethodGroupBegin(), ConsoleFunctionGroupEnd(), and +/// ConsoleFunctionGroupBegin(). +/// - Methods and functions, which are defined using either SimObject::addCommand(), +/// the ConsoleMethod() macro, or the ConsoleFunction() macro. Methods and functions are +/// special in that the usage strings should be in a specific format, so +/// that parameter information can be extracted from them and placed into the Doxygen +/// output. +/// +/// You can use standard Doxygen commands in your comments, to make the documentation clearer. +/// Of particular use are \@returns, \@param, \@note, and \@deprecated. +/// +/// Examples using global definitions. +/// +/// @code +/// // Example of using Doxygen commands. +/// ConsoleFunction(alxGetWaveLen, S32, 2, 2, "(string filename)" +/// "Get length of a wave file\n\n" +/// "@param filename File to determine length of.\n" +/// "@returns Length in milliseconds.") +/// +/// // A function group... +/// ConsoleFunctionGroupBegin(Example, "This is an example group! Notice that the name for the group" +/// "must be a valid identifier, due to limitations in the C preprocessor."); +/// +/// // ConsoleFunction definitions go here. +/// +/// ConsoleFunctionGroupEnd(Example); +/// +/// // You can do similar things with methods... +/// ConsoleMethodGroupBegin(SimSet, UsefulFuncs, "Here are some useful functions involving a SimSet."); +/// ConsoleMethod(SimSet, listObjects, void, 2, 2, "set.listObjects();") +/// ConsoleMethodGroupEnd(SimSet, UsefulFuncs, "Here are some more useful functions involving a SimSet."); +/// @endcode +/// +/// Examples using addField +/// +/// @note Using addCommand is strongly deprecated. +/// +/// @code +/// // Example of a field group. +/// addGroup( "Logging", "Things relating to logging." ); +/// addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable ); +/// endGroup( "Logging" ); +/// @endcode +/// +/// @section console_autodoc_makingdocs How to Generate Console Docs +/// +/// Console docs can be generated by running the dumpConsoleFunctions() and +/// dumpConsoleClasses(), then running the output through Doxygen. There is an +/// example Doxygen configuration file to do this in HEAD, +/// at doc\\doxygen\\html\\script_doxygen.html.cfg. Doxygen will parse the psuedo-C++ +/// generated by the console doc code and produce a class hierarchy and documentation +/// of the global namespace. You may need to tweak the paths in the configuration file +/// slightly to reflect your individual setup. +/// +/// @section console_autodoc_internals Console Auto-Documentation Internals +/// +/// The consoleDoc system works by inserting "hidden" entries in Namespace and +/// AbstractClassRep; these hidden entries are assigned special type IDs so that +/// they aren't touched by the standard name resolution code. At documentation +/// creation time, the dumpConsole functions iterate through the Namespace hierarchy +/// and the AbstractClassRep data and extract this "hidden" information, outputting +/// it in a Doxygen-compatible format. +/// +/// @note You can customize the output of the console documentation system by modifying +/// these functions: +/// - printClassHeader() +/// - printClassMethod() +/// - printGroupStart() +/// - printClassMember() +/// - printGroupEnd() +/// - printClassFooter() +/// +/// @note There was once support for 'overloaded' script functions; ie, script functions +/// with multiple usage strings. Certain functions in the audio library used this. +/// However, it was deemed too complex, and removed from the scripting engine. There +/// are still some latent traces of it, however. diff --git a/console/consoleFunctions.cpp b/console/consoleFunctions.cpp new file mode 100644 index 0000000..4025236 --- /dev/null +++ b/console/consoleFunctions.cpp @@ -0,0 +1,1641 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "core/strings/findMatch.h" +#include "core/stream/fileStream.h" +#include "console/compiler.h" +#include "platform/event.h" +#include "platform/platformInput.h" +#include "core/util/journal/journal.h" + +#ifdef TORQUE_DEMO_PURCHASE +#include "gui/core/guiCanvas.h" +#endif + +// This is a temporary hack to get tools using the library to +// link in this module which contains no other references. +bool LinkConsoleFunctions = false; + +// Buffer for expanding script filenames. +static char scriptFilenameBuffer[1024]; + +//---------------------------------------------------------------- + +ConsoleFunctionGroupBegin(StringFunctions, "General string manipulation functions."); + +ConsoleFunction(strasc, int, 2, 2, "(char)") +{ + TORQUE_UNUSED(argc); + return (int)(*argv[1]); +} + +ConsoleFunction(strformat, const char *, 3, 3, "(string format, value)" + "Formats the given given value as a string, given the printf-style format string.") +{ + TORQUE_UNUSED(argc); + char* pBuffer = Con::getReturnBuffer(64); + const char *pch = argv[1]; + + pBuffer[0] = '\0'; + while (*pch != '\0' && *pch !='%') + pch++; + while (*pch != '\0' && !dIsalpha(*pch)) + pch++; + if (*pch == '\0') + { + Con::errorf("strFormat: Invalid format string!\n"); + return pBuffer; + } + + switch(*pch) + { + case 'c': + case 'C': + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + dSprintf(pBuffer, 64, argv[1], dAtoi(argv[2])); + break; + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + dSprintf(pBuffer, 64, argv[1], dAtof(argv[2])); + break; + + default: + Con::errorf("strFormat: Invalid format string!\n"); + break; + } + + return pBuffer; +} + +ConsoleFunction(strcmp, S32, 3, 3, "(string one, string two)" + "Case sensitive string compare.") +{ + TORQUE_UNUSED(argc); + return dStrcmp(argv[1], argv[2]); +} + +ConsoleFunction(stricmp, S32, 3, 3, "(string one, string two)" + "Case insensitive string compare.") +{ + TORQUE_UNUSED(argc); + return dStricmp(argv[1], argv[2]); +} + +ConsoleFunction(strlen, S32, 2, 2, "(string str)" + "Calculate the length of a string in characters.") +{ + TORQUE_UNUSED(argc); + return dStrlen(argv[1]); +} + +ConsoleFunction(strstr, S32 , 3, 3, "(string one, string two) " + "Returns the start of the sub string two in one or" + " -1 if not found.") +{ + TORQUE_UNUSED(argc); + // returns the start of the sub string argv[2] in argv[1] + // or -1 if not found. + + const char *retpos = dStrstr(argv[1], argv[2]); + if(!retpos) + return -1; + return retpos - argv[1]; +} + +ConsoleFunction(strpos, S32, 3, 4, "(string hay, string needle, int offset=0) " + "Find needle in hay, starting offset bytes in.") +{ + S32 start = 0; + if(argc == 4) + start = dAtoi(argv[3]); + U32 sublen = dStrlen(argv[2]); + U32 strlen = dStrlen(argv[1]); + if(start < 0) + return -1; + if(sublen + start > strlen) + return -1; + for(; start + sublen <= strlen; start++) + if(!dStrncmp(argv[1] + start, argv[2], sublen)) + return start; + return -1; +} + +ConsoleFunction(ltrim, const char *,2,2,"(string value)") +{ + TORQUE_UNUSED(argc); + const char *ret = argv[1]; + while(*ret == ' ' || *ret == '\n' || *ret == '\t') + ret++; + return ret; +} + +ConsoleFunction(rtrim, const char *,2,2,"(string value)") +{ + TORQUE_UNUSED(argc); + S32 firstWhitespace = 0; + S32 pos = 0; + const char *str = argv[1]; + while(str[pos]) + { + if(str[pos] != ' ' && str[pos] != '\n' && str[pos] != '\t') + firstWhitespace = pos + 1; + pos++; + } + char *ret = Con::getReturnBuffer(firstWhitespace + 1); + dStrncpy(ret, argv[1], firstWhitespace); + ret[firstWhitespace] = 0; + return ret; +} + +ConsoleFunction(trim, const char *,2,2,"(string)") +{ + TORQUE_UNUSED(argc); + const char *ptr = argv[1]; + while(*ptr == ' ' || *ptr == '\n' || *ptr == '\t') + ptr++; + S32 firstWhitespace = 0; + S32 pos = 0; + const char *str = ptr; + while(str[pos]) + { + if(str[pos] != ' ' && str[pos] != '\n' && str[pos] != '\t') + firstWhitespace = pos + 1; + pos++; + } + char *ret = Con::getReturnBuffer(firstWhitespace + 1); + dStrncpy(ret, ptr, firstWhitespace); + ret[firstWhitespace] = 0; + return ret; +} + +ConsoleFunction(stripChars, const char*, 3, 3, "(string value, string chars) " + "Remove all the characters in chars from value." ) +{ + TORQUE_UNUSED(argc); + char* ret = Con::getReturnBuffer( dStrlen( argv[1] ) + 1 ); + dStrcpy( ret, argv[1] ); + U32 pos = dStrcspn( ret, argv[2] ); + while ( pos < dStrlen( ret ) ) + { + dStrcpy( ret + pos, ret + pos + 1 ); + pos = dStrcspn( ret, argv[2] ); + } + return( ret ); +} + +ConsoleFunction(stripColorCodes, const char*, 2,2, "(stringtoStrip) - " + "remove TorqueML color codes from the string.") +{ + char* ret = Con::getReturnBuffer( dStrlen( argv[1] ) + 1 ); + dStrcpy(ret, argv[1]); + Con::stripColorChars(ret); + return ret; +} + +ConsoleFunction(strlwr,const char *,2,2,"(string) " + "Convert string to lower case.") +{ + TORQUE_UNUSED(argc); + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1); + dStrcpy(ret, argv[1]); + return dStrlwr(ret); +} + +ConsoleFunction(strupr,const char *,2,2,"(string) " + "Convert string to upper case.") +{ + TORQUE_UNUSED(argc); + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1); + dStrcpy(ret, argv[1]); + return dStrupr(ret); +} + +ConsoleFunction(strchr,const char *,3,3,"(string,char)") +{ + TORQUE_UNUSED(argc); + const char *ret = dStrchr(argv[1], argv[2][0]); + return ret ? ret : ""; +} + +ConsoleFunction(strrchr,const char *,3,3,"(string,char)") +{ + TORQUE_UNUSED(argc); + const char *ret = dStrrchr(argv[1], argv[2][0]); + return ret ? ret : ""; +} + +ConsoleFunction(strreplace, const char *, 4, 4, "(string source, string from, string to)") +{ + TORQUE_UNUSED(argc); + S32 fromLen = dStrlen(argv[2]); + if(!fromLen) + return argv[1]; + + S32 toLen = dStrlen(argv[3]); + S32 count = 0; + const char *scan = argv[1]; + while(scan) + { + scan = dStrstr(scan, argv[2]); + if(scan) + { + scan += fromLen; + count++; + } + } + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1 + (toLen - fromLen) * count); + U32 scanp = 0; + U32 dstp = 0; + for(;;) + { + const char *scan = dStrstr(argv[1] + scanp, argv[2]); + if(!scan) + { + dStrcpy(ret + dstp, argv[1] + scanp); + return ret; + } + U32 len = scan - (argv[1] + scanp); + dStrncpy(ret + dstp, argv[1] + scanp, len); + dstp += len; + dStrcpy(ret + dstp, argv[3]); + dstp += toLen; + scanp += len + fromLen; + } + return ret; +} + +ConsoleFunction(getSubStr, const char *, 4, 4, "getSubStr(string str, int start, int numChars) " + "Returns the substring of str, starting at start, and continuing " + "to either the end of the string, or numChars characters, whichever " + "comes first.") +{ + TORQUE_UNUSED(argc); + // Returns the substring of argv[1], starting at argv[2], and continuing + // to either the end of the string, or argv[3] characters, whichever + // comes first. + // + S32 startPos = dAtoi(argv[2]); + S32 desiredLen = dAtoi(argv[3]); + if (startPos < 0 || desiredLen < 0) { + Con::errorf(ConsoleLogEntry::Script, "getSubStr(...): error, starting position and desired length must be >= 0: (%d, %d)", startPos, desiredLen); + + return ""; + } + + S32 baseLen = dStrlen(argv[1]); + if (baseLen < startPos) + return ""; + + U32 actualLen = desiredLen; + if (startPos + desiredLen > baseLen) + actualLen = baseLen - startPos; + + char *ret = Con::getReturnBuffer(actualLen + 1); + dStrncpy(ret, argv[1] + startPos, actualLen); + ret[actualLen] = '\0'; + + return ret; +} + +ConsoleFunction( strIsMatchExpr, bool, 3, 4, "(string pattern, string str, [bool case=false])\n" + "Return true if the string matches the pattern.") +{ + bool caseSensy = ((argc > 3) ? dAtob(argv[3]) : false); + return FindMatch::isMatch(argv[1], argv[2], caseSensy); +} + +ConsoleFunction( strIsMatchMultipleExpr, bool, 3, 4, "(string patterns, string str, [bool case=false])\n" + "Return true if the string matches any of the patterns.") +{ + bool caseSensy = ((argc > 3) ? dAtob(argv[3]) : false); + return FindMatch::isMatchMultipleExprs(argv[1], argv[2], caseSensy); +} + +// Used? +ConsoleFunction( stripTrailingSpaces, const char*, 2, 2, "stripTrailingSpaces( string )" ) +{ + TORQUE_UNUSED(argc); + S32 temp = S32(dStrlen( argv[1] )); + if ( temp ) + { + while ( ( argv[1][temp - 1] == ' ' || argv[1][temp - 1] == '_' ) && temp >= 1 ) + temp--; + + if ( temp ) + { + char* returnString = Con::getReturnBuffer( temp + 1 ); + dStrncpy( returnString, argv[1], U32(temp) ); + returnString[temp] = '\0'; + return( returnString ); + } + } + + return( "" ); +} + +ConsoleFunctionGroupEnd(StringFunctions); + +//-------------------------------------- + +#include "core/strings/stringUnit.h" + +//-------------------------------------- +ConsoleFunctionGroupBegin( FieldManipulators, "Functions to manipulate data returned in the form of \"x y z\"."); + +ConsoleFunction(getWord, const char *, 3, 3, "(string text, int index)") +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::getUnit(argv[1], dAtoi(argv[2]), " \t\n") ); +} + +ConsoleFunction(getWords, const char *, 3, 4, "(string text, int index, int endIndex=INF)") +{ + U32 endIndex; + if(argc==3) + endIndex = 1000000; + else + endIndex = dAtoi(argv[3]); + return Con::getReturnBuffer( StringUnit::getUnits(argv[1], dAtoi(argv[2]), endIndex, " \t\n") ); +} + +ConsoleFunction(setWord, const char *, 4, 4, "newText = setWord(text, index, replace)") +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::setUnit(argv[1], dAtoi(argv[2]), argv[3], " \t\n") ); +} + +ConsoleFunction(removeWord, const char *, 3, 3, "newText = removeWord(text, index)") +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::removeUnit(argv[1], dAtoi(argv[2]), " \t\n") ); +} + +ConsoleFunction(getWordCount, S32, 2, 2, "getWordCount(text)") +{ + TORQUE_UNUSED(argc); + return StringUnit::getUnitCount(argv[1], " \t\n"); +} + +//-------------------------------------- +ConsoleFunction(getField, const char *, 3, 3, "getField(text, index)") +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::getUnit(argv[1], dAtoi(argv[2]), "\t\n") ); +} + +ConsoleFunction(getFields, const char *, 3, 4, "getFields(text, index [,endIndex])") +{ + U32 endIndex; + if(argc==3) + endIndex = 1000000; + else + endIndex = dAtoi(argv[3]); + return Con::getReturnBuffer( StringUnit::getUnits(argv[1], dAtoi(argv[2]), endIndex, "\t\n") ); +} + +ConsoleFunction(setField, const char *, 4, 4, "newText = setField(text, index, replace)") +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::setUnit(argv[1], dAtoi(argv[2]), argv[3], "\t\n") ); +} + +ConsoleFunction(removeField, const char *, 3, 3, "newText = removeField(text, index)" ) +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::removeUnit(argv[1], dAtoi(argv[2]), "\t\n") ); +} + +ConsoleFunction(getFieldCount, S32, 2, 2, "getFieldCount(text)") +{ + TORQUE_UNUSED(argc); + return StringUnit::getUnitCount(argv[1], "\t\n"); +} + +//-------------------------------------- +ConsoleFunction(getRecord, const char *, 3, 3, "getRecord(text, index)") +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::getUnit(argv[1], dAtoi(argv[2]), "\n") ); +} + +ConsoleFunction(getRecords, const char *, 3, 4, "getRecords(text, index [,endIndex])") +{ + U32 endIndex; + if(argc==3) + endIndex = 1000000; + else + endIndex = dAtoi(argv[3]); + return Con::getReturnBuffer( StringUnit::getUnits(argv[1], dAtoi(argv[2]), endIndex, "\n") ); +} + +ConsoleFunction(setRecord, const char *, 4, 4, "newText = setRecord(text, index, replace)") +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::setUnit(argv[1], dAtoi(argv[2]), argv[3], "\n") ); +} + +ConsoleFunction(removeRecord, const char *, 3, 3, "newText = removeRecord(text, index)" ) +{ + TORQUE_UNUSED(argc); + return Con::getReturnBuffer( StringUnit::removeUnit(argv[1], dAtoi(argv[2]), "\n") ); +} + +ConsoleFunction(getRecordCount, S32, 2, 2, "getRecordCount(text)") +{ + TORQUE_UNUSED(argc); + return StringUnit::getUnitCount(argv[1], "\n"); +} +//-------------------------------------- +ConsoleFunction(firstWord, const char *, 2, 2, "firstWord(text)") +{ + TORQUE_UNUSED(argc); + const char *word = dStrchr(argv[1], ' '); + U32 len; + if(word == NULL) + len = dStrlen(argv[1]); + else + len = word - argv[1]; + char *ret = Con::getReturnBuffer(len + 1); + dStrncpy(ret, argv[1], len); + ret[len] = 0; + return ret; +} + +ConsoleFunction(restWords, const char *, 2, 2, "restWords(text)") +{ + TORQUE_UNUSED(argc); + const char *word = dStrchr(argv[1], ' '); + if(word == NULL) + return ""; + char *ret = Con::getReturnBuffer(dStrlen(word + 1) + 1); + dStrcpy(ret, word + 1); + return ret; +} + +static bool isInSet(char c, const char *set) +{ + if (set) + while (*set) + if (c == *set++) + return true; + + return false; +} + +ConsoleFunction(NextToken,const char *,4,4,"nextToken(str,token,delim)") +{ + TORQUE_UNUSED(argc); + + char *str = (char *) argv[1]; + const char *token = argv[2]; + const char *delim = argv[3]; + + if (str) + { + // skip over any characters that are a member of delim + // no need for special '\0' check since it can never be in delim + while (isInSet(*str, delim)) + str++; + + // skip over any characters that are NOT a member of delim + const char *tmp = str; + + while (*str && !isInSet(*str, delim)) + str++; + + // terminate the token + if (*str) + *str++ = 0; + + // set local variable if inside a function + if (gEvalState.stack.size() && + gEvalState.stack.last()->scopeName) + Con::setLocalVariable(token,tmp); + else + Con::setVariable(token,tmp); + + // advance str past the 'delim space' + while (isInSet(*str, delim)) + str++; + } + + return str; +} + +ConsoleFunctionGroupEnd( FieldManipulators ); +//---------------------------------------------------------------- + +ConsoleFunctionGroupBegin( TaggedStrings, "Functions dealing with tagging/detagging strings."); + +ConsoleFunction(detag, const char *, 2, 2, "detag(textTagString)") +{ + TORQUE_UNUSED(argc); + if(argv[1][0] == StringTagPrefixByte) + { + const char *word = dStrchr(argv[1], ' '); + if(word == NULL) + return ""; + char *ret = Con::getReturnBuffer(dStrlen(word + 1) + 1); + dStrcpy(ret, word + 1); + return ret; + } + else + return argv[1]; +} + +ConsoleFunction(getTag, const char *, 2, 2, "getTag(textTagString)") +{ + TORQUE_UNUSED(argc); + if(argv[1][0] == StringTagPrefixByte) + { + const char * space = dStrchr(argv[1], ' '); + + U32 len; + if(space) + len = space - argv[1]; + else + len = dStrlen(argv[1]) + 1; + + char * ret = Con::getReturnBuffer(len); + dStrncpy(ret, argv[1] + 1, len - 1); + ret[len - 1] = 0; + + return(ret); + } + else + return(argv[1]); +} + +ConsoleFunctionGroupEnd( TaggedStrings ); + +//---------------------------------------------------------------- + +ConsoleFunctionGroupBegin( Output, "Functions to output to the console." ); + +ConsoleFunction(echo, void, 2, 0, "echo(text [, ... ])") +{ + U32 len = 0; + S32 i; + for(i = 1; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + for(i = 1; i < argc; i++) + dStrcat(ret, argv[i]); + + Con::printf("%s", ret); + ret[0] = 0; +} + +ConsoleFunction(warn, void, 2, 0, "warn(text [, ... ])") +{ + U32 len = 0; + S32 i; + for(i = 1; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + for(i = 1; i < argc; i++) + dStrcat(ret, argv[i]); + + Con::warnf(ConsoleLogEntry::General, "%s", ret); + ret[0] = 0; +} + +ConsoleFunction(error, void, 2, 0, "error(text [, ... ])") +{ + U32 len = 0; + S32 i; + for(i = 1; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + for(i = 1; i < argc; i++) + dStrcat(ret, argv[i]); + + Con::errorf(ConsoleLogEntry::General, "%s", ret); + ret[0] = 0; +} + +ConsoleFunction(debugv, void, 2, 2, "debugv(\"\") outputs the value of the in the format = ") +{ + if (argv[1][0] == '%') + Con::errorf("%s = %s", argv[1], Con::getLocalVariable(argv[1])); + else + Con::errorf("%s = %s", argv[1], Con::getVariable(argv[1])); +} + +ConsoleFunction(expandEscape, const char *, 2, 2, "expandEscape(text)") +{ + TORQUE_UNUSED(argc); + char *ret = Con::getReturnBuffer(dStrlen(argv[1])*2 + 1); // worst case situation + expandEscape(ret, argv[1]); + return ret; +} + +ConsoleFunction(collapseEscape, const char *, 2, 2, "collapseEscape(text)") +{ + TORQUE_UNUSED(argc); + char *ret = Con::getReturnBuffer(dStrlen(argv[1]) + 1); // worst case situation + dStrcpy( ret, argv[1] ); + collapseEscape( ret ); + return ret; +} + +ConsoleFunction(setLogMode, void, 2, 2, "setLogMode(mode);") +{ + TORQUE_UNUSED(argc); + Con::setLogMode(dAtoi(argv[1])); +} + +ConsoleFunctionGroupEnd( Output ); + +//---------------------------------------------------------------- + +ConsoleFunction( quit, void, 1, 1, "quit()\nPerforms a clean shutdown of the engine." ) +{ +#ifndef TORQUE_DEMO_PURCHASE + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + Platform::postQuitMessage(0); +#else + GuiCanvas *canvas = NULL; + if ( !Sim::findObject( "Canvas", canvas ) ) + { + Con::errorf( "quit() - Canvas was not found." ); + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + Platform::postQuitMessage(0); + } + else + canvas->showPurchaseScreen(true, "exit", true); +#endif +} + +#ifdef TORQUE_DEMO_PURCHASE +ConsoleFunction( realQuit, void, 1, 1, "" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + Platform::postQuitMessage(0); +} +#endif + +ConsoleFunction( quitWithErrorMessage, void, 2, 2, "quitWithErrorMessage( msg )\n" + "Logs the error message to disk, displays a message box, and forces the " + "immediate shutdown of the process." ) +{ + Con::errorf( argv[1] ); + Platform::AlertOK( "Error", argv[1] ); + Platform::forceShutdown( -1 ); +} + +//---------------------------------------------------------------- + +ConsoleFunction( gotoWebPage, void, 2, 2, "( address ) - Open a URL in the user's favorite web browser." ) +{ + TORQUE_UNUSED(argc); + char* protocolSep = dStrstr(argv[1],"://"); + + if( protocolSep != NULL ) + { + Platform::openWebBrowser(argv[1]); + return; + } + + // if we don't see a protocol seperator, then we know that some bullethead + // sent us a bad url. We'll first check to see if a file inside the sandbox + // with that name exists, then we'll just glom "http://" onto the front of + // the bogus url, and hope for the best. + + char urlBuf[2048]; + if(Platform::isFile(argv[1]) || Platform::isDirectory(argv[1])) + { +#ifdef TORQUE2D_TOOLS_FIXME + dSprintf(urlBuf, sizeof(urlBuf), "file://%s",argv[1]); +#else + dSprintf(urlBuf, sizeof(urlBuf), "file://%s/%s",Platform::getCurrentDirectory(),argv[1]); +#endif + } + else + dSprintf(urlBuf, sizeof(urlBuf), "http://%s",argv[1]); + + Platform::openWebBrowser(urlBuf); + return; +} + +ConsoleFunction( displaySplashWindow, bool, 1, 1, "displaySplashWindow();" ) +{ + + return Platform::displaySplashWindow(); + +} + +ConsoleFunction( getWebDeployment, bool, 1, 1, "getWebDeployment();" ) +{ + + return Platform::getWebDeployment(); + +} + +//---------------------------------------------------------------- + +ConsoleFunctionGroupBegin(MetaScripting, "Functions that let you manipulate the scripting engine programmatically."); + +ConsoleFunction(call, const char *, 2, 0, "call(funcName [,args ...])") +{ + return Con::execute(argc - 1, argv + 1); +} + +static U32 execDepth = 0; +static U32 journalDepth = 1; + +static StringTableEntry getDSOPath(const char *scriptPath) +{ +#ifndef TORQUE2D_TOOLS_FIXME + + // [tom, 11/17/2006] Force old behavior for the player. May not want to do this. + const char *slash = dStrrchr(scriptPath, '/'); + if(slash != NULL) + return StringTable->insertn(scriptPath, slash - scriptPath, true); + + slash = dStrrchr(scriptPath, ':'); + if(slash != NULL) + return StringTable->insertn(scriptPath, (slash - scriptPath) + 1, true); + + return ""; + +#else + + char relPath[1024], dsoPath[1024]; + bool isPrefs = false; + + // [tom, 11/17/2006] Prefs are handled slightly differently to avoid dso name clashes + StringTableEntry prefsPath = Platform::getPrefsPath(); + if(dStrnicmp(scriptPath, prefsPath, dStrlen(prefsPath)) == 0) + { + relPath[0] = 0; + isPrefs = true; + } + else + { + StringTableEntry strippedPath = Platform::stripBasePath(scriptPath); + dStrcpy(relPath, strippedPath); + + char *slash = dStrrchr(relPath, '/'); + if(slash) + *slash = 0; + } + + const char *overridePath; + if(! isPrefs) + overridePath = Con::getVariable("$Scripts::OverrideDSOPath"); + else + overridePath = prefsPath; + + if(overridePath && *overridePath) + Platform::makeFullPathName(relPath, dsoPath, sizeof(dsoPath), overridePath); + else + { + char t[1024]; + dSprintf(t, sizeof(t), "compiledScripts/%s", relPath); + Platform::makeFullPathName(t, dsoPath, sizeof(dsoPath), Platform::getPrefsPath()); + } + + return StringTable->insert(dsoPath); + +#endif +} + +ConsoleFunction(getDSOPath, const char *, 2, 2, "(scriptFileName)") +{ + Con::expandScriptFilename(scriptFilenameBuffer, sizeof(scriptFilenameBuffer), argv[1]); + + const char *filename = getDSOPath(scriptFilenameBuffer); + if(filename == NULL || *filename == 0) + return ""; + + return filename; +} + +ConsoleFunction(compile, bool, 2, 3, "compile(fileName, overrideNoDso)") +{ + TORQUE_UNUSED(argc); + bool overrideNoDso = false; + + if(argc >= 3) + overrideNoDso = dAtob(argv[2]); + + Con::expandScriptFilename(scriptFilenameBuffer, sizeof(scriptFilenameBuffer), argv[1]); + + // Figure out where to put DSOs + StringTableEntry dsoPath = getDSOPath(scriptFilenameBuffer); + if(dsoPath && *dsoPath == 0) + return false; + + // If the script file extention is '.ed.cs' then compile it to a different compiled extention + bool isEditorScript = false; + const char *ext = dStrrchr( scriptFilenameBuffer, '.' ); + if( ext && ( dStricmp( ext, ".cs" ) == 0 ) ) + { + const char* ext2 = ext - 3; + if( dStricmp( ext2, ".ed.cs" ) == 0 ) + isEditorScript = true; + } + else if( ext && ( dStricmp( ext, ".gui" ) == 0 ) ) + { + const char* ext2 = ext - 3; + if( dStricmp( ext2, ".ed.gui" ) == 0 ) + isEditorScript = true; + } + + const char *filenameOnly = dStrrchr(scriptFilenameBuffer, '/'); + if(filenameOnly) + ++filenameOnly; + else + filenameOnly = scriptFilenameBuffer; + + char nameBuffer[512]; + + if( isEditorScript ) + dStrcpyl(nameBuffer, sizeof(nameBuffer), dsoPath, "/", filenameOnly, ".edso", NULL); + else + dStrcpyl(nameBuffer, sizeof(nameBuffer), dsoPath, "/", filenameOnly, ".dso", NULL); + + void *data = NULL; + U32 dataSize = 0; + Torque::FS::ReadFile(scriptFilenameBuffer, data, dataSize, true); + if(data == NULL) + { + Con::errorf(ConsoleLogEntry::Script, "compile: invalid script file %s.", scriptFilenameBuffer); + return false; + } + + const char *script = static_cast(data); + +#ifdef TORQUE_DEBUG + Con::printf("Compiling %s...", scriptFilenameBuffer); +#endif + + CodeBlock *code = new CodeBlock(); + code->compile(nameBuffer, scriptFilenameBuffer, script, overrideNoDso); + delete code; + delete[] script; + + return true; +} + +ConsoleFunction(exec, bool, 2, 4, "exec(fileName [, nocalls [,journalScript]])") +{ + bool journal = false; + + execDepth++; + if(journalDepth >= execDepth) + journalDepth = execDepth + 1; + else + journal = true; + + bool noCalls = false; + bool ret = false; + + if(argc >= 3 && dAtoi(argv[2])) + noCalls = true; + + if(argc >= 4 && dAtoi(argv[3]) && !journal) + { + journal = true; + journalDepth = execDepth; + } + + // Determine the filename we actually want... + Con::expandScriptFilename(scriptFilenameBuffer, sizeof(scriptFilenameBuffer), argv[1]); + + // since this function expects a script file reference, if it's a .dso + // lets terminate the string before the dso so it will act like a .cs + if(dStrEndsWith(scriptFilenameBuffer, ".dso")) + { + scriptFilenameBuffer[dStrlen(scriptFilenameBuffer) - dStrlen(".dso")] = '\0'; + } + + // Figure out where to put DSOs + StringTableEntry dsoPath = getDSOPath(scriptFilenameBuffer); + + const char *ext = dStrrchr(scriptFilenameBuffer, '.'); + + if(!ext) + { + // We need an extension! + Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file name %s.", scriptFilenameBuffer); + execDepth--; + return false; + } + + // Check Editor Extensions + bool isEditorScript = false; + + // If the script file extension is '.ed.cs' then compile it to a different compiled extension + if( dStricmp( ext, ".cs" ) == 0 ) + { + const char* ext2 = ext - 3; + if( dStricmp( ext2, ".ed.cs" ) == 0 ) + isEditorScript = true; + } + else if( dStricmp( ext, ".gui" ) == 0 ) + { + const char* ext2 = ext - 3; + if( dStricmp( ext2, ".ed.gui" ) == 0 ) + isEditorScript = true; + } + + + StringTableEntry scriptFileName = StringTable->insert(scriptFilenameBuffer); + +#ifndef TORQUE_OS_XENON + // Is this a file we should compile? (anything in the prefs path should not be compiled) + StringTableEntry prefsPath = Platform::getPrefsPath(); + bool compiled = dStricmp(ext, ".mis") && !journal && !Con::getBoolVariable("Scripts::ignoreDSOs"); + + // [tom, 12/5/2006] stripBasePath() fucks up if the filename is not in the exe + // path, current directory or prefs path. Thus, getDSOFilename() will also screw + // up and so this allows the scripts to still load but without a DSO. + if(Platform::isFullPath(Platform::stripBasePath(scriptFilenameBuffer))) + compiled = false; + + // [tom, 11/17/2006] It seems to make sense to not compile scripts that are in the + // prefs directory. However, getDSOPath() can handle this situation and will put + // the dso along with the script to avoid name clashes with tools/game dsos. + if( (dsoPath && *dsoPath == 0) || (prefsPath && prefsPath[ 0 ] && dStrnicmp(scriptFileName, prefsPath, dStrlen(prefsPath)) == 0) ) + compiled = false; +#else + bool compiled = false; // Don't try to compile things on the 360, ignore DSO's when debugging + // because PC prefs will screw up stuff like SFX. +#endif + + // If we're in a journaling mode, then we will read the script + // from the journal file. + if(journal && Journal::IsPlaying()) + { + char fileNameBuf[256]; + bool fileRead = false; + U32 fileSize; + + Journal::ReadString(fileNameBuf); + Journal::Read(&fileRead); + + if(!fileRead) + { + Con::errorf(ConsoleLogEntry::Script, "Journal script read (failed) for %s", fileNameBuf); + execDepth--; + return false; + } + Journal::Read(&fileSize); + char *script = new char[fileSize + 1]; + Journal::Read(fileSize, script); + script[fileSize] = 0; + Con::printf("Executing (journal-read) %s.", scriptFileName); + CodeBlock *newCodeBlock = new CodeBlock(); + newCodeBlock->compileExec(scriptFileName, script, noCalls, 0); + delete [] script; + + execDepth--; + return true; + } + + // Ok, we let's try to load and compile the script. + Torque::FS::FileNodeRef scriptFile = Torque::FS::GetFileNode(scriptFileName); + Torque::FS::FileNodeRef dsoFile; + +// ResourceObject *rScr = gResourceManager->find(scriptFileName); +// ResourceObject *rCom = NULL; + + char nameBuffer[512]; + char* script = NULL; + U32 version; + + Stream *compiledStream = NULL; + Torque::Time scriptModifiedTime, dsoModifiedTime; + + // Check here for .edso + bool edso = false; + if( dStricmp( ext, ".edso" ) == 0 && scriptFile != NULL ) + { + edso = true; + dsoFile = scriptFile; + scriptFile = NULL; + + dsoModifiedTime = dsoFile->getModifiedTime(); + dStrcpy( nameBuffer, scriptFileName ); + } + + // If we're supposed to be compiling this file, check to see if there's a DSO + if(compiled && !edso) + { + const char *filenameOnly = dStrrchr(scriptFileName, '/'); + if(filenameOnly) + ++filenameOnly; + else + filenameOnly = scriptFileName; + + char pathAndFilename[1024]; + Platform::makeFullPathName(filenameOnly, pathAndFilename, sizeof(pathAndFilename), dsoPath); + + if( isEditorScript ) + dStrcpyl(nameBuffer, sizeof(nameBuffer), pathAndFilename, ".edso", NULL); + else + dStrcpyl(nameBuffer, sizeof(nameBuffer), pathAndFilename, ".dso", NULL); + + dsoFile = Torque::FS::GetFileNode(nameBuffer); + + if(scriptFile != NULL) + scriptModifiedTime = scriptFile->getModifiedTime(); + + if(dsoFile != NULL) + dsoModifiedTime = dsoFile->getModifiedTime(); + } + + // Let's do a sanity check to complain about DSOs in the future. + // + // MM: This doesn't seem to be working correctly for now so let's just not issue + // the warning until someone knows how to resolve it. + // + //if(compiled && rCom && rScr && Platform::compareFileTimes(comModifyTime, scrModifyTime) < 0) + //{ + //Con::warnf("exec: Warning! Found a DSO from the future! (%s)", nameBuffer); + //} + + // If we had a DSO, let's check to see if we should be reading from it. + if(compiled && dsoFile != NULL && (scriptFile == NULL|| (scriptModifiedTime - dsoModifiedTime) > Torque::Time(0))) + { + compiledStream = FileStream::createAndOpen( nameBuffer, Torque::FS::File::Read ); + if (compiledStream) + { + // Check the version! + compiledStream->read(&version); + if(version != Con::DSOVersion) + { + Con::warnf("exec: Found an old DSO (%s, ver %d < %d), ignoring.", nameBuffer, version, Con::DSOVersion); + delete compiledStream; + compiledStream = NULL; + } + } + } + + // If we're journalling, let's write some info out. + if(journal && Journal::IsRecording()) + Journal::WriteString(scriptFileName); + + if(scriptFile != NULL && !compiledStream) + { + // If we have source but no compiled version, then we need to compile + // (and journal as we do so, if that's required). + + void *data; + U32 dataSize = 0; + Torque::FS::ReadFile(scriptFileName, data, dataSize, true); + + if(journal && Journal::IsRecording()) + Journal::Write(bool(data != NULL)); + + if( data == NULL ) + { + Con::errorf(ConsoleLogEntry::Script, "exec: invalid script file %s.", scriptFileName); + execDepth--; + return false; + } + else + { + if( !dataSize ) + { + execDepth --; + return false; + } + + script = (char *)data; + + if(journal && Journal::IsRecording()) + { + Journal::Write(dataSize); + Journal::Write(dataSize, data); + } + } + +#ifndef TORQUE_NO_DSO_GENERATION + if(compiled) + { + // compile this baddie. +#ifdef TORQUE_DEBUG + Con::printf("Compiling %s...", scriptFileName); +#endif + + CodeBlock *code = new CodeBlock(); + code->compile(nameBuffer, scriptFileName, script); + delete code; + code = NULL; + + compiledStream = FileStream::createAndOpen( nameBuffer, Torque::FS::File::Read ); + if(compiledStream) + { + compiledStream->read(&version); + } + else + { + // We have to exit out here, as otherwise we get double error reports. + delete [] script; + execDepth--; + return false; + } + } +#endif + } + else + { + if(journal && Journal::IsRecording()) + Journal::Write(bool(false)); + } + + if(compiledStream) + { + // Delete the script object first to limit memory used + // during recursive execs. + delete [] script; + script = 0; + + // We're all compiled, so let's run it. +#ifdef TORQUE_DEBUG + Con::printf("Loading compiled script %s.", scriptFileName); +#endif + CodeBlock *code = new CodeBlock; + code->read(scriptFileName, *compiledStream); + delete compiledStream; + code->exec(0, scriptFileName, NULL, 0, NULL, noCalls, NULL, 0); + ret = true; + } + else + if(scriptFile) + { + // No compiled script, let's just try executing it + // directly... this is either a mission file, or maybe + // we're on a readonly volume. +#ifdef TORQUE_DEBUG + Con::printf("Executing %s.", scriptFileName); +#endif + + CodeBlock *newCodeBlock = new CodeBlock(); + StringTableEntry name = StringTable->insert(scriptFileName); + + newCodeBlock->compileExec(name, script, noCalls, 0); + ret = true; + } + else + { + // Don't have anything. + Con::warnf(ConsoleLogEntry::Script, "Missing file: %s!", scriptFileName); + ret = false; + } + + delete [] script; + execDepth--; + return ret; +} + +ConsoleFunction(eval, const char *, 2, 2, "eval(consoleString)") +{ + TORQUE_UNUSED(argc); + return Con::evaluate(argv[1], false, NULL); +} + +ConsoleFunction(getVariable, const char *, 2, 2, "(string varName)") +{ + return Con::getVariable(argv[1]); +} + +ConsoleFunction(isFunction, bool, 2, 2, "(string funcName)") +{ + return Con::isFunction(argv[1]); +} + +ConsoleFunction(isMethod, bool, 3, 3, "(string namespace, string method)" ) +{ + Namespace* ns = Namespace::find( StringTable->insert( argv[1] ) ); + Namespace::Entry* nse = ns->lookup( StringTable->insert( argv[2] ) ); + if( !nse ) + return false; + + return true; +} + +ConsoleFunction(isDefined, bool, 2, 3, "isDefined(variable name [, value if not defined])") +{ + if(dStrlen(argv[1]) == 0) + { + Con::errorf("isDefined() - did you forget to put quotes around the variable name?"); + return false; + } + + StringTableEntry name = StringTable->insert(argv[1]); + + // Deal with . + if (dStrchr(name, '.')) + { + static char scratchBuffer[4096]; + + S32 len = dStrlen(name); + AssertFatal(len < sizeof(scratchBuffer)-1, "isDefined() - name too long"); + dMemcpy(scratchBuffer, name, len+1); + + char * token = dStrtok(scratchBuffer, "."); + + if (!token || token[0] == '\0') + return false; + + StringTableEntry objName = StringTable->insert(token); + + // Attempt to find the object + SimObject * obj = Sim::findObject(objName); + + // If we didn't find the object then we can safely + // assume that the field variable doesn't exist + if (!obj) + return false; + + // Get the name of the field + token = dStrtok(0, ".\0"); + if (!token) + return false; + + while (token != NULL) + { + StringTableEntry valName = StringTable->insert(token); + + // Store these so we can restore them after we search for the variable + bool saveModStatic = obj->canModStaticFields(); + bool saveModDyn = obj->canModDynamicFields(); + + // Set this so that we can search both static and dynamic fields + obj->setModStaticFields(true); + obj->setModDynamicFields(true); + + const char* value = obj->getDataField(valName, 0); + + // Restore our mod flags to be safe + obj->setModStaticFields(saveModStatic); + obj->setModDynamicFields(saveModDyn); + + if (!value) + { + obj->setDataField(valName, 0, argv[2]); + + return false; + } + else + { + // See if we are field on a field + token = dStrtok(0, ".\0"); + if (token) + { + // The previous field must be an object + obj = Sim::findObject(value); + if (!obj) + return false; + } + else + { + if (dStrlen(value) > 0) + return true; + else if (argc > 2) + obj->setDataField(valName, 0, argv[2]); + } + } + } + } + else if (name[0] == '%') + { + // Look up a local variable + if (gEvalState.stack.size()) + { + Dictionary::Entry* ent = gEvalState.stack.last()->lookup(name); + + if (ent) + return true; + else if (argc > 2) + gEvalState.stack.last()->setVariable(name, argv[2]); + } + else + Con::errorf("%s() - no local variable frame.", __FUNCTION__); + } + else if (name[0] == '$') + { + // Look up a global value + Dictionary::Entry* ent = gEvalState.globalVars.lookup(name); + + if (ent) + return true; + else if (argc > 2) + gEvalState.globalVars.setVariable(name, argv[2]); + } + else + { + // Is it an object? + if (dStrcmp(argv[1], "0") && dStrcmp(argv[1], "") && (Sim::findObject(argv[1]) != NULL)) + return true; + else if (argc > 2) + Con::errorf("%s() - can't assign a value to a variable of the form \"%s\"", __FUNCTION__, argv[1]); + } + + return false; +} + +//------------------------------------------------------------------------------ + +ConsoleFunction(isCurrentScriptToolScript, bool, 1, 1, "() Returns true if the calling script is a tools script") +{ + return Con::isCurrentScriptToolScript(); +} + +ConsoleFunction(getModNameFromPath, const char *, 2, 2, "(string path) Attempts to extract a mod directory from path. Returns empty string on failure.") +{ + StringTableEntry modPath = Con::getModNameFromPath(argv[1]); + return modPath ? modPath : ""; +} + +//---------------------------------------------------------------- + +ConsoleFunction( pushInstantGroup, void, 1, 2, "([group]) Pushes the current $instantGroup on a stack and sets it to the given value (or clears it)." ) +{ + if( argc > 1 ) + Con::pushInstantGroup( argv[ 1 ] ); + else + Con::pushInstantGroup(); +} + +ConsoleFunction( popInstantGroup, void, 1, 1, "() Pop and restore the last setting of $instantGroup off the stack." ) +{ + Con::popInstantGroup(); +} + +//---------------------------------------------------------------- + +ConsoleFunction(getPrefsPath, const char *, 1, 2, "([relativeFileName])") +{ + const char *filename = Platform::getPrefsPath(argc > 1 ? argv[1] : NULL); + if(filename == NULL || *filename == 0) + return ""; + + return filename; +} + +ConsoleFunction(execPrefs, bool, 2, 4, "execPrefs(relativeFileName [, nocalls [,journalScript]])") +{ + const char *filename = Platform::getPrefsPath(argv[1]); + if(filename == NULL || *filename == 0) + return false; + + // Scripts do this a lot, so we may as well help them out + if(! Platform::isFile(filename) && ! Torque::FS::IsFile(filename)) + return true; + + argv[0] = "exec"; + argv[1] = filename; + return dAtob(Con::execute(argc, argv)); +} + +ConsoleFunction(export, void, 2, 4, "export(searchString [, relativeFileName [,append]])") +{ + const char *filename = NULL; + bool append = (argc == 4) ? dAtob(argv[3]) : false; + + if (argc >= 3) + { +#ifndef TORQUE2D_TOOLS_FIXME + if(Con::expandScriptFilename(scriptFilenameBuffer, sizeof(scriptFilenameBuffer), argv[2])) + filename = scriptFilenameBuffer; +#else + filename = Platform::getPrefsPath(argv[2]); + if(filename == NULL || *filename == 0) + return; +#endif + } + + gEvalState.globalVars.exportVariables(argv[1], filename, append); +} + +ConsoleFunction(deleteVariables, void, 2, 2, "deleteVariables(wildCard)") +{ + TORQUE_UNUSED(argc); + gEvalState.globalVars.deleteVariables(argv[1]); +} + +//---------------------------------------------------------------- + +ConsoleFunction(trace, void, 2, 2, "trace(bool)") +{ + TORQUE_UNUSED(argc); + gEvalState.traceOn = dAtob(argv[1]); + Con::printf("Console trace is %s", gEvalState.traceOn ? "on." : "off."); +} + +//---------------------------------------------------------------- + +#if defined(TORQUE_DEBUG) || !defined(TORQUE_SHIPPING) +ConsoleFunction(debug, void, 1, 1, "debug()") +{ + TORQUE_UNUSED(argv); TORQUE_UNUSED(argc); + Platform::debugBreak(); +} +#endif + +ConsoleFunctionGroupEnd( MetaScripting ); + +//---------------------------------------------------------------- + +ConsoleFunction(isspace, bool, 3, 3, "(string, index): return true if character at specified index in string is whitespace") +{ + S32 idx = dAtoi(argv[2]); + if (idx >= 0 && idx < dStrlen(argv[1])) + return dIsspace(argv[1][idx]); + else + return false; +} + +ConsoleFunction(isalnum, bool, 3, 3, "(string, index): return true if character at specified index in string is alnum") +{ + S32 idx = dAtoi(argv[2]); + if (idx >= 0 && idx < dStrlen(argv[1])) + return dIsalnum(argv[1][idx]); + else + return false; +} + +ConsoleFunction(startswith, bool, 3, 3, "(src string, target string) case insensitive") +{ + const char* src = argv[1]; + const char* target = argv[2]; + + // if the target string is empty, return true (all strings start with the empty string) + S32 srcLen = dStrlen(src); + S32 targetLen = dStrlen(target); + if (targetLen == 0) + return true; + // else if the src string is empty, return false (empty src does not start with non-empty target) + else if (srcLen == 0) + return false; + + // both src and target are non empty, create temp buffers for lowercase operation + char* srcBuf = new char[srcLen + 1]; + char* targetBuf = new char[targetLen + 1]; + + // copy src and target into buffers + dStrcpy(srcBuf, src); + dStrcpy(targetBuf, target); + + // reassign src/target pointers to lowercase versions + src = dStrlwr(srcBuf); + target = dStrlwr(targetBuf); + + // do the comparison + bool startsWith = dStrncmp(src, target, targetLen) == 0; + + // delete temp buffers + delete [] srcBuf; + delete [] targetBuf; + + return startsWith; +} + +ConsoleFunction(endswith, bool, 3, 3, "(src string, target string) case insensitive") +{ + const char* src = argv[1]; + const char* target = argv[2]; + + // if the target string is empty, return true (all strings end with the empty string) + S32 srcLen = dStrlen(src); + S32 targetLen = dStrlen(target); + if (targetLen == 0) + return true; + // else if the src string is empty, return false (empty src does not end with non-empty target) + else if (srcLen == 0) + return false; + + // both src and target are non empty, create temp buffers for lowercase operation + char* srcBuf = new char[srcLen + 1]; + char* targetBuf = new char[targetLen + 1]; + + // copy src and target into buffers + dStrcpy(srcBuf, src); + dStrcpy(targetBuf, target); + + // reassign src/target pointers to lowercase versions + src = dStrlwr(srcBuf); + target = dStrlwr(targetBuf); + + // set the src pointer to the appropriate place to check the end of the string + src += srcLen - targetLen; + + // do the comparison + bool endsWith = dStrcmp(src, target) == 0; + + // delete temp buffers + delete [] srcBuf; + delete [] targetBuf; + + return endsWith; +} + +ConsoleFunction(strrchrpos,S32,3,3,"strrchrpos(string,char)") +{ + TORQUE_UNUSED(argc); + const char *ret = dStrrchr(argv[1], argv[2][0]); + return ret ? ret - argv[1] : -1; +} + +ConsoleFunction(strswiz, const char *,3,3,"strswiz(string,len)") +{ + TORQUE_UNUSED(argc); + S32 lenIn = dStrlen(argv[1]); + S32 lenOut = getMin(dAtoi(argv[2]),lenIn); + char * ret = Con::getReturnBuffer(lenOut+1); + for (S32 i=0; i>1]; + else + ret[i] = argv[1][lenIn-(i>>1)-1]; + } + ret[lenOut]='\0'; + return ret; +} + +ConsoleFunction(countBits, S32, 2, 2, "count the number of bits in the specified 32 bit integer") +{ + S32 c = 0; + S32 v = dAtoi(argv[1]); + + // from + // http://graphics.stanford.edu/~seander/bithacks.html + + // for at most 32-bit values in v: + c = ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f; + c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % + 0x1f; + c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f; + +#ifndef TORQUE_SHIPPING + // since the above isn't very obvious, for debugging compute the count in a more + // traditional way and assert if it is different + { + S32 c2 = 0; + S32 v2 = v; + for (c2 = 0; v2; v2 >>= 1) + { + c2 += v2 & 1; + } + if (c2 != c) + Con::errorf("countBits: Uh oh bit count mismatch"); + AssertFatal(c2 == c, "countBits: uh oh, bit count mismatch"); + } +#endif + + return c; +} + +ConsoleFunction(isShippingBuild, bool, 1, 1, "Returns true if this is a shipping build, false otherwise") +{ +#ifdef TORQUE_SHIPPING + return true; +#else + return false; +#endif +} + +ConsoleFunction(isDebugBuild, bool, 1, 1, "isDebugBuild() - Returns true if the script is running in a debug Torque executable" ) +{ +#ifdef TORQUE_DEBUG + return true; +#else + return false; +#endif +} + +ConsoleFunction(isToolBuild, bool, 1, 1, "() Returns true if running application is an editor/tools build or false if a game build" ) +{ +#ifdef TORQUE_TOOLS + return true; +#else + return false; +#endif +} \ No newline at end of file diff --git a/console/consoleInternal.cpp b/console/consoleInternal.cpp new file mode 100644 index 0000000..23555d8 --- /dev/null +++ b/console/consoleInternal.cpp @@ -0,0 +1,1266 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" + +#include "console/ast.h" +#include "core/tAlgorithm.h" + +#include "core/strings/findMatch.h" +#include "console/consoleInternal.h" +#include "core/stream/fileStream.h" +#include "console/compiler.h" + +#define ST_INIT_SIZE 15 + +static char scratchBuffer[1024]; +U32 Namespace::mCacheSequence = 0; +DataChunker Namespace::mCacheAllocator; +DataChunker Namespace::mAllocator; +Namespace *Namespace::mNamespaceList = NULL; +Namespace *Namespace::mGlobalNamespace = NULL; + +bool canTabComplete(const char *prevText, const char *bestMatch, + const char *newText, S32 baseLen, bool fForward) +{ + // test if it matches the first baseLen chars: + if(dStrnicmp(newText, prevText, baseLen)) + return false; + + if (fForward) + { + if(!bestMatch) + return dStricmp(newText, prevText) > 0; + else + return (dStricmp(newText, prevText) > 0) && + (dStricmp(newText, bestMatch) < 0); + } + else + { + if (dStrlen(prevText) == (U32) baseLen) + { + // look for the 'worst match' + if(!bestMatch) + return dStricmp(newText, prevText) > 0; + else + return dStricmp(newText, bestMatch) > 0; + } + else + { + if (!bestMatch) + return (dStricmp(newText, prevText) < 0); + else + return (dStricmp(newText, prevText) < 0) && + (dStricmp(newText, bestMatch) > 0); + } + } +} + +//--------------------------------------------------------------- +// +// Dictionary functions +// +//--------------------------------------------------------------- +struct StringValue +{ + S32 size; + char *val; + + operator char *() { return val; } + StringValue &operator=(const char *string); + + StringValue() { size = 0; val = NULL; } + ~StringValue() { dFree(val); } +}; + + +StringValue & StringValue::operator=(const char *string) +{ + if(!val) + { + val = dStrdup(string); + size = dStrlen(val); + } + else + { + S32 len = dStrlen(string); + if(len < size) + dStrcpy(val, string); + else + { + size = len; + dFree(val); + val = dStrdup(string); + } + } + return *this; +} + +static S32 QSORT_CALLBACK varCompare(const void* a,const void* b) +{ + return dStricmp( (*((Dictionary::Entry **) a))->name, (*((Dictionary::Entry **) b))->name ); +} + +void Dictionary::exportVariables(const char *varString, const char *fileName, bool append) +{ + const char *searchStr = varString; + Vector sortList(__FILE__, __LINE__); + + for(S32 i = 0; i < hashTable->size;i ++) + { + Entry *walk = hashTable->data[i]; + while(walk) + { + if(FindMatch::isMatch((char *) searchStr, (char *) walk->name)) + sortList.push_back(walk); + + walk = walk->nextEntry; + } + } + + if(!sortList.size()) + return; + + dQsort((void *) &sortList[0], sortList.size(), sizeof(Entry *), varCompare); + + Vector::iterator s; + char expandBuffer[1024]; + FileStream *strm = NULL; + + if(fileName) + { + if((strm = FileStream::createAndOpen( fileName, append ? Torque::FS::File::ReadWrite : Torque::FS::File::Write )) == NULL) + { + Con::errorf(ConsoleLogEntry::General, "Unable to open file '%s for writing.", fileName); + return; + } + if(append) + strm->setPosition(strm->getStreamSize()); + } + + char buffer[1024]; + const char *cat = fileName ? "\r\n" : ""; + + for(s = sortList.begin(); s != sortList.end(); s++) + { + switch((*s)->type) + { + case Entry::TypeInternalInt: + dSprintf(buffer, sizeof(buffer), "%s = %d;%s", (*s)->name, (*s)->ival, cat); + break; + case Entry::TypeInternalFloat: + dSprintf(buffer, sizeof(buffer), "%s = %g;%s", (*s)->name, (*s)->fval, cat); + break; + default: + expandEscape(expandBuffer, (*s)->getStringValue()); + dSprintf(buffer, sizeof(buffer), "%s = \"%s\";%s", (*s)->name, expandBuffer, cat); + break; + } + if(strm) + strm->write(dStrlen(buffer), buffer); + else + Con::printf("%s", buffer); + } + if(strm) + delete strm; +} + +void Dictionary::exportVariables( const char *varString, Vector *names, Vector *values ) +{ + const char *searchStr = varString; + Vector sortList(__FILE__, __LINE__); + + for ( S32 i = 0; i < hashTable->size; i++ ) + { + Entry *walk = hashTable->data[i]; + while ( walk ) + { + if ( FindMatch::isMatch( (char*)searchStr, (char*)walk->name ) ) + sortList.push_back( walk ); + + walk = walk->nextEntry; + } + } + + if ( !sortList.size() ) + return; + + dQsort((void *) &sortList[0], sortList.size(), sizeof(Entry *), varCompare); + + if ( names ) + names->reserve( sortList.size() ); + if ( values ) + values->reserve( sortList.size() ); + + char expandBuffer[1024]; + + Vector::iterator s; + + for ( s = sortList.begin(); s != sortList.end(); s++ ) + { + if ( names ) + names->push_back( String( (*s)->name ) ); + + if ( values ) + { + switch ( (*s)->type ) + { + case Entry::TypeInternalInt: + values->push_back( String::ToString( (*s)->ival ) ); + break; + case Entry::TypeInternalFloat: + values->push_back( String::ToString( (*s)->fval ) ); + break; + default: + expandEscape( expandBuffer, (*s)->getStringValue() ); + values->push_back( expandBuffer ); + break; + } + } + } +} + +void Dictionary::deleteVariables(const char *varString) +{ + const char *searchStr = varString; + + for(S32 i = 0; i < hashTable->size; i++) + { + Entry *walk = hashTable->data[i]; + while(walk) + { + Entry *matchedEntry = (FindMatch::isMatch((char *) searchStr, (char *) walk->name)) ? walk : NULL; + walk = walk->nextEntry; + if (matchedEntry) + remove(matchedEntry); // assumes remove() is a stable remove (will not reorder entries on remove) + } + } +} + +S32 HashPointer(StringTableEntry ptr) +{ + return (S32)(((dsize_t)ptr) >> 2); +} + +Dictionary::Entry *Dictionary::lookup(StringTableEntry name) +{ + Entry *walk = hashTable->data[HashPointer(name) % hashTable->size]; + while(walk) + { + if(walk->name == name) + return walk; + else + walk = walk->nextEntry; + } + + return NULL; +} + +Dictionary::Entry *Dictionary::add(StringTableEntry name) +{ + Entry *walk = hashTable->data[HashPointer(name) % hashTable->size]; + while(walk) + { + if(walk->name == name) + return walk; + else + walk = walk->nextEntry; + } + Entry *ret; + hashTable->count++; + + if(hashTable->count > hashTable->size * 2) + { + Entry head(NULL), *walk; + S32 i; + walk = &head; + walk->nextEntry = 0; + for(i = 0; i < hashTable->size; i++) { + while(walk->nextEntry) { + walk = walk->nextEntry; + } + walk->nextEntry = hashTable->data[i]; + } + delete[] hashTable->data; + hashTable->size = hashTable->size * 4 - 1; + hashTable->data = new Entry *[hashTable->size]; + for(i = 0; i < hashTable->size; i++) + hashTable->data[i] = NULL; + walk = head.nextEntry; + while(walk) + { + Entry *temp = walk->nextEntry; + S32 idx = HashPointer(walk->name) % hashTable->size; + walk->nextEntry = hashTable->data[idx]; + hashTable->data[idx] = walk; + walk = temp; + } + } + + ret = new Entry(name); + S32 idx = HashPointer(name) % hashTable->size; + ret->nextEntry = hashTable->data[idx]; + hashTable->data[idx] = ret; + return ret; +} + +// deleteVariables() assumes remove() is a stable remove (will not reorder entries on remove) +void Dictionary::remove(Dictionary::Entry *ent) +{ + Entry **walk = &hashTable->data[HashPointer(ent->name) % hashTable->size]; + while(*walk != ent) + walk = &((*walk)->nextEntry); + + *walk = (ent->nextEntry); + delete ent; + hashTable->count--; +} + +Dictionary::Dictionary() + : hashTable( NULL ), + exprState( NULL ), + scopeName( NULL ), + scopeNamespace( NULL ), + code( NULL ), + ip( 0 ) +{ +} + +Dictionary::Dictionary(ExprEvalState *state, Dictionary* ref) + : hashTable( NULL ), + exprState( NULL ), + scopeName( NULL ), + scopeNamespace( NULL ), + code( NULL ), + ip( 0 ) +{ + setState(state,ref); +} + +void Dictionary::setState(ExprEvalState *state, Dictionary* ref) +{ + exprState = state; + + if (ref) + hashTable = ref->hashTable; + else + { + hashTable = new HashTableData; + hashTable->owner = this; + hashTable->count = 0; + hashTable->size = ST_INIT_SIZE; + hashTable->data = new Entry *[hashTable->size]; + + for(S32 i = 0; i < hashTable->size; i++) + hashTable->data[i] = NULL; + } +} + +Dictionary::~Dictionary() +{ + if ( hashTable->owner == this ) + { + reset(); + delete [] hashTable->data; + delete hashTable; + } +} + +void Dictionary::reset() +{ + S32 i; + Entry *walk, *temp; + + for(i = 0; i < hashTable->size; i++) + { + walk = hashTable->data[i]; + while(walk) + { + temp = walk->nextEntry; + delete walk; + walk = temp; + } + hashTable->data[i] = NULL; + } + hashTable->size = ST_INIT_SIZE; + hashTable->count = 0; +} + + +const char *Dictionary::tabComplete(const char *prevText, S32 baseLen, bool fForward) +{ + S32 i; + + const char *bestMatch = NULL; + for(i = 0; i < hashTable->size; i++) + { + Entry *walk = hashTable->data[i]; + while(walk) + { + if(canTabComplete(prevText, bestMatch, walk->name, baseLen, fForward)) + bestMatch = walk->name; + walk = walk->nextEntry; + } + } + return bestMatch; +} + + +char *typeValueEmpty = ""; + +Dictionary::Entry::Entry(StringTableEntry in_name) +{ + dataPtr = NULL; + name = in_name; + type = -1; + ival = 0; + fval = 0; + sval = typeValueEmpty; +} + +Dictionary::Entry::~Entry() +{ + if(sval != typeValueEmpty) + dFree(sval); +} + +const char *Dictionary::getVariable(StringTableEntry name, bool *entValid) +{ + Entry *ent = lookup(name); + if(ent) + { + if(entValid) + *entValid = true; + return ent->getStringValue(); + } + if(entValid) + *entValid = false; + + // Warn users when they access a variable that isn't defined. + if(gWarnUndefinedScriptVariables) + Con::warnf(" *** Accessed undefined variable '%s'", name); + + return ""; +} + +void Dictionary::Entry::setStringValue(const char * value) +{ + if(type <= TypeInternalString) + { + // Let's not remove empty-string-valued global vars from the dict. + // If we remove them, then they won't be exported, and sometimes + // it could be necessary to export such a global. There are very + // few empty-string global vars so there's no performance-related + // need to remove them from the dict. +/* + if(!value[0] && name[0] == '$') + { + gEvalState.globalVars.remove(this); + return; + } +*/ + + U32 stringLen = dStrlen(value); + + // If it's longer than 256 bytes, it's certainly not a number. + // + // (This decision may come back to haunt you. Shame on you if it + // does.) + if(stringLen < 256) + { + fval = dAtof(value); + ival = dAtoi(value); + } + else + { + fval = 0.f; + ival = 0; + } + + type = TypeInternalString; + + // may as well pad to the next cache line + U32 newLen = ((stringLen + 1) + 15) & ~15; + + if(sval == typeValueEmpty) + sval = (char *) dMalloc(newLen); + else if(newLen > bufferLen) + sval = (char *) dRealloc(sval, newLen); + + bufferLen = newLen; + dStrcpy(sval, value); + } + else + Con::setData(type, dataPtr, 0, 1, &value); +} + +void Dictionary::setVariable(StringTableEntry name, const char *value) +{ + Entry *ent = add(name); + if(!value) + value = ""; + ent->setStringValue(value); +} + +void Dictionary::addVariable(const char *name, S32 type, void *dataPtr) +{ + if(name[0] != '$') + { + scratchBuffer[0] = '$'; + dStrcpy(scratchBuffer + 1, name); + name = scratchBuffer; + } + Entry *ent = add(StringTable->insert(name)); + ent->type = type; + if(ent->sval != typeValueEmpty) + { + dFree(ent->sval); + ent->sval = typeValueEmpty; + } + ent->dataPtr = dataPtr; +} + +bool Dictionary::removeVariable(StringTableEntry name) +{ + if( Entry *ent = lookup(name) ){ + remove( ent ); + return true; + } + return false; +} + +void ExprEvalState::pushFrame(StringTableEntry frameName, Namespace *ns) +{ + Dictionary *newFrame = new Dictionary(this); + newFrame->scopeName = frameName; + newFrame->scopeNamespace = ns; + stack.push_back(newFrame); + currentVariable = NULL; +} + +void ExprEvalState::popFrame() +{ + Dictionary *last = stack.last(); + stack.pop_back(); + delete last; + currentVariable = NULL; +} + +void ExprEvalState::pushFrameRef(S32 stackIndex) +{ + AssertFatal( stackIndex >= 0 && stackIndex < stack.size(), "You must be asking for a valid frame!" ); + Dictionary *newFrame = new Dictionary(this, stack[stackIndex]); + stack.push_back(newFrame); + currentVariable = NULL; +} + +ExprEvalState::ExprEvalState() +{ + VECTOR_SET_ASSOCIATION(stack); + globalVars.setState(this); + thisObject = NULL; + traceOn = false; + currentVariable = NULL; +} + +ExprEvalState::~ExprEvalState() +{ + while(stack.size()) + popFrame(); +} + +ConsoleFunction(backtrace, void, 1, 1, "Print the call stack.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + U32 totalSize = 1; + + for(U32 i = 0; i < gEvalState.stack.size(); i++) + { + totalSize += dStrlen(gEvalState.stack[i]->scopeName) + 3; + if(gEvalState.stack[i]->scopeNamespace && gEvalState.stack[i]->scopeNamespace->mName) + totalSize += dStrlen(gEvalState.stack[i]->scopeNamespace->mName) + 2; + } + + char *buf = Con::getReturnBuffer(totalSize); + buf[0] = 0; + for(U32 i = 0; i < gEvalState.stack.size(); i++) + { + dStrcat(buf, "->"); + if(gEvalState.stack[i]->scopeNamespace && gEvalState.stack[i]->scopeNamespace->mName) + { + dStrcat(buf, gEvalState.stack[i]->scopeNamespace->mName); + dStrcat(buf, "::"); + } + dStrcat(buf, gEvalState.stack[i]->scopeName); + } + Con::printf("BackTrace: %s", buf); + +} + +Namespace::Entry::Entry() +{ + mCode = NULL; + mType = InvalidFunctionType; +} + +void Namespace::Entry::clear() +{ + if(mCode) + { + mCode->decRefCount(); + mCode = NULL; + } + + // Clean up usage strings generated for script functions. + if( ( mType == Namespace::Entry::ConsoleFunctionType ) && mUsage ) + { + delete mUsage; + mUsage = NULL; + } +} + +Namespace::Namespace() +{ + mPackage = NULL; + mUsage = NULL; + mCleanUpUsage = false; + mName = NULL; + mParent = NULL; + mNext = NULL; + mEntryList = NULL; + mHashSize = 0; + mHashTable = 0; + mHashSequence = 0; + mRefCountToParent = 0; + mClassRep = 0; +} + +Namespace::~Namespace() +{ + clearEntries(); + if( mUsage && mCleanUpUsage ) + { + delete mUsage; + mUsage = NULL; + mCleanUpUsage = false; + } +} + +void Namespace::clearEntries() +{ + for(Entry *walk = mEntryList; walk; walk = walk->mNext) + walk->clear(); +} + +Namespace *Namespace::find(StringTableEntry name, StringTableEntry package) +{ + if ( name == NULL && package == NULL ) + return mGlobalNamespace; + + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + { + if(walk->mName == name && walk->mPackage == package) + return walk; + } + + Namespace *ret = (Namespace *) mAllocator.alloc(sizeof(Namespace)); + constructInPlace(ret); + ret->mPackage = package; + ret->mName = name; + ret->mNext = mNamespaceList; + mNamespaceList = ret; + return ret; +} + +bool Namespace::unlinkClass(Namespace *parent) +{ + Namespace *walk = this; + while(walk->mParent && walk->mParent->mName == mName) + walk = walk->mParent; + + if(walk->mParent && walk->mParent != parent) + { + Con::errorf(ConsoleLogEntry::General, "Namespace::unlinkClass - cannot unlink namespace parent linkage for %s for %s.", + walk->mName, walk->mParent->mName); + return false; + } + + mRefCountToParent--; + AssertFatal(mRefCountToParent >= 0, "Namespace::unlinkClass - reference count to parent is less than 0"); + + if(mRefCountToParent == 0) + walk->mParent = NULL; + + trashCache(); + + return true; +} + + +bool Namespace::classLinkTo(Namespace *parent) +{ + Namespace *walk = this; + while(walk->mParent && walk->mParent->mName == mName) + walk = walk->mParent; + + if(walk->mParent && walk->mParent != parent) + { + Con::errorf(ConsoleLogEntry::General, "Error: cannot change namespace parent linkage of %s from %s to %s.", + walk->mName, walk->mParent->mName, parent->mName); + return false; + } + mRefCountToParent++; + walk->mParent = parent; + + trashCache(); + + return true; +} + +void Namespace::buildHashTable() +{ + if(mHashSequence == mCacheSequence) + return; + + if(!mEntryList && mParent) + { + mParent->buildHashTable(); + mHashTable = mParent->mHashTable; + mHashSize = mParent->mHashSize; + mHashSequence = mCacheSequence; + return; + } + + U32 entryCount = 0; + Namespace * ns; + for(ns = this; ns; ns = ns->mParent) + for(Entry *walk = ns->mEntryList; walk; walk = walk->mNext) + if(lookupRecursive(walk->mFunctionName) == walk) + entryCount++; + + mHashSize = entryCount + (entryCount >> 1) + 1; + + if(!(mHashSize & 1)) + mHashSize++; + + mHashTable = (Entry **) mCacheAllocator.alloc(sizeof(Entry *) * mHashSize); + for(U32 i = 0; i < mHashSize; i++) + mHashTable[i] = NULL; + + for(ns = this; ns; ns = ns->mParent) + { + for(Entry *walk = ns->mEntryList; walk; walk = walk->mNext) + { + U32 index = HashPointer(walk->mFunctionName) % mHashSize; + while(mHashTable[index] && mHashTable[index]->mFunctionName != walk->mFunctionName) + { + index++; + if(index >= mHashSize) + index = 0; + } + + if(!mHashTable[index]) + mHashTable[index] = walk; + } + } + + mHashSequence = mCacheSequence; +} + +void Namespace::getUniqueEntryLists( Namespace *other, VectorPtr *outThisList, VectorPtr *outOtherList ) +{ + // All namespace entries in the common ACR should be + // ignored when checking for duplicate entry names. + static VectorPtr commonEntries; + commonEntries.clear(); + + AbstractClassRep *commonACR = mClassRep->getCommonParent( other->mClassRep ); + commonACR->getNameSpace()->getEntryList( &commonEntries ); + + // Make life easier + VectorPtr &thisEntries = *outThisList; + VectorPtr &compEntries = *outOtherList; + + // Clear, just in case they aren't + thisEntries.clear(); + compEntries.clear(); + + getEntryList( &thisEntries ); + other->getEntryList( &compEntries ); + + // Run through all of the entries in the common ACR, and remove them from + // the other two entry lists + for( NamespaceEntryListIterator itr = commonEntries.begin(); itr != commonEntries.end(); itr++ ) + { + // Check this entry list + for( NamespaceEntryListIterator thisItr = thisEntries.begin(); thisItr != thisEntries.end(); thisItr++ ) + { + if( *thisItr == *itr ) + { + thisEntries.erase( thisItr ); + break; + } + } + + // Same check for component entry list + for( NamespaceEntryListIterator compItr = compEntries.begin(); compItr != compEntries.end(); compItr++ ) + { + if( *compItr == *itr ) + { + compEntries.erase( compItr ); + break; + } + } + } +} + +void Namespace::init() +{ + // create the global namespace + mGlobalNamespace = (Namespace *) mAllocator.alloc(sizeof(Namespace)); + constructInPlace(mGlobalNamespace); + mGlobalNamespace->mPackage = NULL; + mGlobalNamespace->mName = NULL; + mGlobalNamespace->mNext = NULL; + mNamespaceList = mGlobalNamespace; +} + +Namespace *Namespace::global() +{ + return mGlobalNamespace; +} + +void Namespace::shutdown() +{ + // The data chunker will release all memory in one go + // without calling destructors, so we do this manually here. + + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + walk->~Namespace(); +} + +void Namespace::trashCache() +{ + mCacheSequence++; + mCacheAllocator.freeBlocks(); +} + +const char *Namespace::tabComplete(const char *prevText, S32 baseLen, bool fForward) +{ + if(mHashSequence != mCacheSequence) + buildHashTable(); + + const char *bestMatch = NULL; + for(U32 i = 0; i < mHashSize; i++) + if(mHashTable[i] && canTabComplete(prevText, bestMatch, mHashTable[i]->mFunctionName, baseLen, fForward)) + bestMatch = mHashTable[i]->mFunctionName; + return bestMatch; +} + +Namespace::Entry *Namespace::lookupRecursive(StringTableEntry name) +{ + for(Namespace *ns = this; ns; ns = ns->mParent) + for(Entry *walk = ns->mEntryList; walk; walk = walk->mNext) + if(walk->mFunctionName == name) + return walk; + + return NULL; +} + +Namespace::Entry *Namespace::lookup(StringTableEntry name) +{ + if(mHashSequence != mCacheSequence) + buildHashTable(); + + U32 index = HashPointer(name) % mHashSize; + while(mHashTable[index] && mHashTable[index]->mFunctionName != name) + { + index++; + if(index >= mHashSize) + index = 0; + } + return mHashTable[index]; +} + +static S32 QSORT_CALLBACK compareEntries(const void* a,const void* b) +{ + const Namespace::Entry* fa = *((Namespace::Entry**)a); + const Namespace::Entry* fb = *((Namespace::Entry**)b); + + return dStricmp(fa->mFunctionName, fb->mFunctionName); +} + +void Namespace::getEntryList(VectorPtr *vec) +{ + if(mHashSequence != mCacheSequence) + buildHashTable(); + + for(U32 i = 0; i < mHashSize; i++) + if(mHashTable[i]) + vec->push_back(mHashTable[i]); + + dQsort(vec->address(),vec->size(),sizeof(Namespace::Entry *),compareEntries); +} + +Namespace::Entry *Namespace::createLocalEntry(StringTableEntry name) +{ + for(Entry *walk = mEntryList; walk; walk = walk->mNext) + { + if(walk->mFunctionName == name) + { + walk->clear(); + return walk; + } + } + + Entry *ent = (Entry *) mAllocator.alloc(sizeof(Entry)); + constructInPlace(ent); + + ent->mNamespace = this; + ent->mFunctionName = name; + ent->mNext = mEntryList; + ent->mPackage = mPackage; + ent->mToolOnly = false; + mEntryList = ent; + return ent; +} + +void Namespace::addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset, const char* usage) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mCode = cb; + ent->mFunctionOffset = functionOffset; + ent->mCode->incRefCount(); + ent->mType = Entry::ConsoleFunctionType; +} + +void Namespace::addCommand(StringTableEntry name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + ent->mToolOnly = isToolOnly; + + ent->mType = Entry::StringCallbackType; + ent->cb.mStringCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + ent->mToolOnly = isToolOnly; + + ent->mType = Entry::IntCallbackType; + ent->cb.mIntCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + ent->mToolOnly = isToolOnly; + + ent->mType = Entry::VoidCallbackType; + ent->cb.mVoidCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + ent->mToolOnly = isToolOnly; + + ent->mType = Entry::FloatCallbackType; + ent->cb.mFloatCallbackFunc = cb; +} + +void Namespace::addCommand(StringTableEntry name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs, bool isToolOnly /* = false */) +{ + Entry *ent = createLocalEntry(name); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = minArgs; + ent->mMaxArgs = maxArgs; + ent->mToolOnly = isToolOnly; + + ent->mType = Entry::BoolCallbackType; + ent->cb.mBoolCallbackFunc = cb; +} + +void Namespace::addOverload(const char * name, const char *altUsage) +{ + static U32 uid=0; + char buffer[1024]; + char lilBuffer[32]; + dStrcpy(buffer, name); + dSprintf(lilBuffer, 32, "_%d", uid++); + dStrcat(buffer, lilBuffer); + + Entry *ent = createLocalEntry(StringTable->insert( buffer )); + trashCache(); + + ent->mUsage = altUsage; + ent->mMinArgs = -1; + ent->mMaxArgs = -2; + + ent->mType = Entry::OverloadMarker; + ent->cb.mGroupName = name; +} + +void Namespace::addScriptCallback(const char *funcName, const char *usage) +{ + static U32 uid=0; + char buffer[1024]; + char lilBuffer[32]; + dStrcpy(buffer, funcName); + dSprintf(lilBuffer, 32, "_%d_cb", uid++); + dStrcat(buffer, lilBuffer); + + Entry *ent = createLocalEntry(StringTable->insert( buffer )); + trashCache(); + + ent->mUsage = usage; + ent->mMinArgs = -2; + ent->mMaxArgs = -3; + + ent->mType = Entry::ScriptCallbackType; + ent->cb.mCallbackName = funcName; +} + +void Namespace::markGroup(const char* name, const char* usage) +{ + static U32 uid=0; + char buffer[1024]; + char lilBuffer[32]; + dStrcpy(buffer, name); + dSprintf(lilBuffer, 32, "_%d", uid++); + dStrcat(buffer, lilBuffer); + + Entry *ent = createLocalEntry(StringTable->insert( buffer )); + trashCache(); + + if(usage != NULL) + lastUsage = (char*)(ent->mUsage = usage); + else + ent->mUsage = lastUsage; + + ent->mMinArgs = -1; // Make sure it explodes if somehow we run this entry. + ent->mMaxArgs = -2; + + ent->mType = Entry::GroupMarker; + ent->cb.mGroupName = name; +} + +extern S32 executeBlock(StmtNode *block, ExprEvalState *state); + +const char *Namespace::Entry::execute(S32 argc, const char **argv, ExprEvalState *state) +{ + if(mType == ConsoleFunctionType) + { + if(mFunctionOffset) + return mCode->exec(mFunctionOffset, argv[0], mNamespace, argc, argv, false, mPackage); + else + return ""; + } + +#ifndef TORQUE_DEBUG + // [tom, 12/13/2006] This stops tools functions from working in the console, + // which is useful behavior when debugging so I'm ifdefing this out for debug builds. + if(mToolOnly && ! Con::isCurrentScriptToolScript()) + { + Con::errorf(ConsoleLogEntry::Script, "%s::%s - attempting to call tools only function from outside of tools", mNamespace->mName, mFunctionName); + return ""; + } +#endif + + if((mMinArgs && argc < mMinArgs) || (mMaxArgs && argc > mMaxArgs)) + { + Con::warnf(ConsoleLogEntry::Script, "%s::%s - wrong number of arguments.", mNamespace->mName, mFunctionName); + Con::warnf(ConsoleLogEntry::Script, "usage: %s", mUsage); + return ""; + } + + static char returnBuffer[32]; + switch(mType) + { + case StringCallbackType: + return cb.mStringCallbackFunc(state->thisObject, argc, argv); + case IntCallbackType: + dSprintf(returnBuffer, sizeof(returnBuffer), "%d", + cb.mIntCallbackFunc(state->thisObject, argc, argv)); + return returnBuffer; + case FloatCallbackType: + dSprintf(returnBuffer, sizeof(returnBuffer), "%g", + cb.mFloatCallbackFunc(state->thisObject, argc, argv)); + return returnBuffer; + case VoidCallbackType: + cb.mVoidCallbackFunc(state->thisObject, argc, argv); + return ""; + case BoolCallbackType: + dSprintf(returnBuffer, sizeof(returnBuffer), "%d", + (U32)cb.mBoolCallbackFunc(state->thisObject, argc, argv)); + return returnBuffer; + } + + return ""; +} + +StringTableEntry Namespace::mActivePackages[Namespace::MaxActivePackages]; +U32 Namespace::mNumActivePackages = 0; +U32 Namespace::mOldNumActivePackages = 0; + +bool Namespace::isPackage(StringTableEntry name) +{ + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + if(walk->mPackage == name) + return true; + return false; +} + +void Namespace::activatePackage(StringTableEntry name) +{ + if(mNumActivePackages == MaxActivePackages) + { + Con::printf("ActivatePackage(%s) failed - Max package limit reached: %d", name, MaxActivePackages); + return; + } + if(!name) + return; + + // see if this one's already active + for(U32 i = 0; i < mNumActivePackages; i++) + if(mActivePackages[i] == name) + return; + + // kill the cache + trashCache(); + + // find all the package namespaces... + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + { + if(walk->mPackage == name) + { + Namespace *parent = Namespace::find(walk->mName); + // hook the parent + walk->mParent = parent->mParent; + parent->mParent = walk; + + // now swap the entries: + Entry *ew; + for(ew = parent->mEntryList; ew; ew = ew->mNext) + ew->mNamespace = walk; + + for(ew = walk->mEntryList; ew; ew = ew->mNext) + ew->mNamespace = parent; + + ew = walk->mEntryList; + walk->mEntryList = parent->mEntryList; + parent->mEntryList = ew; + } + } + mActivePackages[mNumActivePackages++] = name; +} + +void Namespace::deactivatePackage(StringTableEntry name) +{ + S32 i, j; + for(i = 0; i < mNumActivePackages; i++) + if(mActivePackages[i] == name) + break; + if(i == mNumActivePackages) + return; + + trashCache(); + + for(j = mNumActivePackages - 1; j >= i; j--) + { + // gotta unlink em in reverse order... + for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext) + { + if(walk->mPackage == mActivePackages[j]) + { + Namespace *parent = Namespace::find(walk->mName); + // hook the parent + parent->mParent = walk->mParent; + walk->mParent = NULL; + + // now swap the entries: + Entry *ew; + for(ew = parent->mEntryList; ew; ew = ew->mNext) + ew->mNamespace = walk; + + for(ew = walk->mEntryList; ew; ew = ew->mNext) + ew->mNamespace = parent; + + ew = walk->mEntryList; + walk->mEntryList = parent->mEntryList; + parent->mEntryList = ew; + } + } + } + mNumActivePackages = i; +} + +void Namespace::unlinkPackages() +{ + mOldNumActivePackages = mNumActivePackages; + if(!mNumActivePackages) + return; + deactivatePackage(mActivePackages[0]); +} + +void Namespace::relinkPackages() +{ + if(!mOldNumActivePackages) + return; + for(U32 i = 0; i < mOldNumActivePackages; i++) + activatePackage(mActivePackages[i]); +} + +ConsoleFunctionGroupBegin( Packages, "Functions relating to the control of packages."); + +ConsoleFunction(isPackage,bool,2,2,"isPackage(packageName)") +{ + TORQUE_UNUSED(argc); + StringTableEntry packageName = StringTable->insert(argv[1]); + return Namespace::isPackage(packageName); +} + +ConsoleFunction(activatePackage, void,2,2,"activatePackage(packageName)") +{ + TORQUE_UNUSED(argc); + StringTableEntry packageName = StringTable->insert(argv[1]); + Namespace::activatePackage(packageName); +} + +ConsoleFunction(deactivatePackage, void,2,2,"deactivatePackage(packageName)") +{ + TORQUE_UNUSED(argc); + StringTableEntry packageName = StringTable->insert(argv[1]); + Namespace::deactivatePackage(packageName); +} + +ConsoleFunctionGroupEnd( Packages ); \ No newline at end of file diff --git a/console/consoleInternal.h b/console/consoleInternal.h new file mode 100644 index 0000000..8ac1d56 --- /dev/null +++ b/console/consoleInternal.h @@ -0,0 +1,335 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLEINTERNAL_H_ +#define _CONSOLEINTERNAL_H_ + +#ifndef _STRINGFUNCTIONS_H_ +#include "core/strings/stringFunctions.h" +#endif + +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif + +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif + + +class ExprEvalState; +struct FunctionDecl; +class CodeBlock; +class AbstractClassRep; + +class Namespace +{ + enum { + MaxActivePackages = 512, + }; + + static U32 mNumActivePackages; + static U32 mOldNumActivePackages; + static StringTableEntry mActivePackages[MaxActivePackages]; +public: + StringTableEntry mName; + StringTableEntry mPackage; + + Namespace *mParent; + Namespace *mNext; + AbstractClassRep *mClassRep; + U32 mRefCountToParent; + const char* mUsage; + // Script defined usage strings need to be cleaned up. This + // field indicates whether or not the usage was set from script. + bool mCleanUpUsage; + + struct Entry + { + enum + { + ScriptCallbackType = -4, + GroupMarker = -3, + OverloadMarker = -2, + InvalidFunctionType = -1, + ConsoleFunctionType, + StringCallbackType, + IntCallbackType, + FloatCallbackType, + VoidCallbackType, + BoolCallbackType + }; + + Namespace *mNamespace; + Entry *mNext; + StringTableEntry mFunctionName; + S32 mType; + S32 mMinArgs; + S32 mMaxArgs; + const char *mUsage; + StringTableEntry mPackage; + bool mToolOnly; + + CodeBlock *mCode; + U32 mFunctionOffset; + union CallbackUnion { + StringCallback mStringCallbackFunc; + IntCallback mIntCallbackFunc; + VoidCallback mVoidCallbackFunc; + FloatCallback mFloatCallbackFunc; + BoolCallback mBoolCallbackFunc; + const char *mGroupName; + const char *mCallbackName; + } cb; + Entry(); + void clear(); + + const char *execute(S32 argc, const char **argv, ExprEvalState *state); + + }; + Entry *mEntryList; + + Entry **mHashTable; + U32 mHashSize; + U32 mHashSequence; ///< @note The hash sequence is used by the autodoc console facility + /// as a means of testing reference state. + + Namespace(); + ~Namespace(); + void addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset, const char* usage = NULL); + void addCommand(StringTableEntry name,StringCallback, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + void addCommand(StringTableEntry name,IntCallback, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + void addCommand(StringTableEntry name,FloatCallback, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + void addCommand(StringTableEntry name,VoidCallback, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + void addCommand(StringTableEntry name,BoolCallback, const char *usage, S32 minArgs, S32 maxArgs, bool toolOnly = false); + + void addOverload(const char *name, const char* altUsage); + + void addScriptCallback(const char *funcName, const char *usage); + + void markGroup(const char* name, const char* usage); + char * lastUsage; + + void getEntryList(VectorPtr *); + + Entry *lookup(StringTableEntry name); + Entry *lookupRecursive(StringTableEntry name); + Entry *createLocalEntry(StringTableEntry name); + void buildHashTable(); + void clearEntries(); + bool classLinkTo(Namespace *parent); + bool unlinkClass(Namespace *parent); + void getUniqueEntryLists( Namespace *other, VectorPtr *outThisList, VectorPtr *outOtherList ); + + const char *tabComplete(const char *prevText, S32 baseLen, bool fForward); + + static U32 mCacheSequence; + static DataChunker mCacheAllocator; + static DataChunker mAllocator; + static void trashCache(); + static Namespace *mNamespaceList; + static Namespace *mGlobalNamespace; + + static void init(); + static void shutdown(); + static Namespace *global(); + + static Namespace *find(StringTableEntry name, StringTableEntry package=NULL); + + static void activatePackage(StringTableEntry name); + static void deactivatePackage(StringTableEntry name); + static void dumpClasses( bool dumpScript = true, bool dumpEngine = true ); + static void dumpFunctions( bool dumpScript = true, bool dumpEngine = true ); + static void printNamespaceEntries(Namespace * g, bool dumpScript = true, bool dumpEngine = true); + static void unlinkPackages(); + static void relinkPackages(); + static bool isPackage(StringTableEntry name); +}; + +typedef VectorPtr::iterator NamespaceEntryListIterator; + +extern char *typeValueEmpty; + +class Dictionary +{ +public: + struct Entry + { + enum + { + TypeInternalInt = -3, + TypeInternalFloat = -2, + TypeInternalString = -1, + }; + + StringTableEntry name; + Entry *nextEntry; + S32 type; + char *sval; + U32 ival; // doubles as strlen when type = -1 + F32 fval; + U32 bufferLen; + void *dataPtr; + + Entry(StringTableEntry name); + ~Entry(); + + U32 getIntValue() + { + if(type <= TypeInternalString) + return ival; + else + return dAtoi(Con::getData(type, dataPtr, 0)); + } + F32 getFloatValue() + { + if(type <= TypeInternalString) + return fval; + else + return dAtof(Con::getData(type, dataPtr, 0)); + } + const char *getStringValue() + { + if(type == TypeInternalString) + return sval; + if(type == TypeInternalFloat) + return Con::getData(TypeF32, &fval, 0); + else if(type == TypeInternalInt) + return Con::getData(TypeS32, &ival, 0); + else + return Con::getData(type, dataPtr, 0); + } + void setIntValue(U32 val) + { + if(type <= TypeInternalString) + { + fval = (F32)val; + ival = val; + if(sval != typeValueEmpty) + { + dFree(sval); + sval = typeValueEmpty; + } + type = TypeInternalInt; + return; + } + else + { + const char *dptr = Con::getData(TypeS32, &val, 0); + Con::setData(type, dataPtr, 0, 1, &dptr); + } + } + void setFloatValue(F32 val) + { + if(type <= TypeInternalString) + { + fval = val; + ival = static_cast(val); + if(sval != typeValueEmpty) + { + dFree(sval); + sval = typeValueEmpty; + } + type = TypeInternalFloat; + return; + } + else + { + const char *dptr = Con::getData(TypeF32, &val, 0); + Con::setData(type, dataPtr, 0, 1, &dptr); + } + } + void setStringValue(const char *value); + }; + +private: + struct HashTableData + { + Dictionary* owner; + S32 size; + S32 count; + Entry **data; + }; + + HashTableData *hashTable; + ExprEvalState *exprState; +public: + StringTableEntry scopeName; + Namespace *scopeNamespace; + CodeBlock *code; + U32 ip; + + Dictionary(); + Dictionary(ExprEvalState *state, Dictionary* ref=NULL); + ~Dictionary(); + Entry *lookup(StringTableEntry name); + Entry *add(StringTableEntry name); + void setState(ExprEvalState *state, Dictionary* ref=NULL); + void remove(Entry *); + void reset(); + + void exportVariables( const char *varString, const char *fileName, bool append ); + void exportVariables( const char *varString, Vector *names, Vector *values ); + void deleteVariables( const char *varString ); + + void setVariable(StringTableEntry name, const char *value); + const char *getVariable(StringTableEntry name, bool *valid = NULL); + + void addVariable(const char *name, S32 type, void *dataPtr); + bool removeVariable(StringTableEntry name); + + /// Return the best tab completion for prevText, with the length + /// of the pre-tab string in baseLen. + const char *tabComplete(const char *prevText, S32 baseLen, bool); +}; + +class ExprEvalState +{ +public: + /// @name Expression Evaluation + /// @{ + + /// + SimObject *thisObject; + Dictionary::Entry *currentVariable; + bool traceOn; + + ExprEvalState(); + ~ExprEvalState(); + + /// @} + + /// @name Stack Management + /// @{ + + /// + Dictionary globalVars; + Vector stack; + void setCurVarName(StringTableEntry name); + void setCurVarNameCreate(StringTableEntry name); + S32 getIntVariable(); + F64 getFloatVariable(); + const char *getStringVariable(); + void setIntVariable(S32 val); + void setFloatVariable(F64 val); + void setStringVariable(const char *str); + + void pushFrame(StringTableEntry frameName, Namespace *ns); + void popFrame(); + + /// Puts a reference to an existing stack frame + /// on the top of the stack. + void pushFrameRef(S32 stackIndex); + + /// @} +}; + +namespace Con +{ + /// The current $instantGroup setting. + extern String gInstantGroup; +} + +#endif diff --git a/console/consoleLogger.cpp b/console/consoleLogger.cpp new file mode 100644 index 0000000..ac54a51 --- /dev/null +++ b/console/consoleLogger.cpp @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "console/consoleLogger.h" +#include "console/consoleTypes.h" + +Vector ConsoleLogger::mActiveLoggers; +bool ConsoleLogger::smInitialized = false; + +IMPLEMENT_CONOBJECT( ConsoleLogger ); + +//----------------------------------------------------------------------------- + +ConsoleLogger::ConsoleLogger() +{ + mFilename = NULL; + mLogging = false; + mAppend = false; + + mLevel = ConsoleLogEntry::Normal; +} + +//----------------------------------------------------------------------------- + +ConsoleLogger::ConsoleLogger( const char *fileName, bool append ) +{ + mLogging = false; + + mLevel = ConsoleLogEntry::Normal; + mFilename = StringTable->insert( fileName ); + mAppend = append; + + init(); +} + +//----------------------------------------------------------------------------- + +static const EnumTable::Enums logLevelEnums[] = +{ + { ConsoleLogEntry::Normal, "normal" }, + { ConsoleLogEntry::Warning, "warning" }, + { ConsoleLogEntry::Error, "error" }, +}; + +static const EnumTable gLogLevelTable( 3, &logLevelEnums[0] ); + +void ConsoleLogger::initPersistFields() +{ + addGroup( "Logging" ); + addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable ); + endGroup( "Logging" ); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- + +bool ConsoleLogger::processArguments( S32 argc, const char **argv ) +{ + if( argc == 0 ) + return false; + + bool append = false; + + if( argc == 2 ) + append = dAtob( argv[1] ); + + mAppend = append; + mFilename = StringTable->insert( argv[0] ); + + if( init() ) + { + attach(); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- + +ConsoleLogger::~ConsoleLogger() +{ + detach(); +} + +//----------------------------------------------------------------------------- + +bool ConsoleLogger::init() +{ + if( smInitialized ) + return true; + + Con::addConsumer( ConsoleLogger::logCallback ); + smInitialized = true; + + return true; +} + +//----------------------------------------------------------------------------- + +bool ConsoleLogger::attach() +{ + if( mFilename == NULL ) + { + Con::errorf( "ConsoleLogger failed to attach: no filename supplied." ); + return false; + } + + // Check to see if this is initialized before using it + if( !smInitialized ) + { + if( !init() ) + { + Con::errorf( "ConsoleLogger failed to initalize." ); + return false; + } + } + + if( mLogging ) + return false; + + // Open the filestream + mStream.open( mFilename, ( mAppend ? Torque::FS::File::WriteAppend : Torque::FS::File::Write ) ); + + // Add this to list of active loggers + mActiveLoggers.push_back( this ); + mLogging = true; + + return true; +} + +//----------------------------------------------------------------------------- + +bool ConsoleLogger::detach() +{ + + // Make sure this is valid before messing with it + if( !smInitialized ) + { + if( !init() ) + { + return false; + } + } + + if( !mLogging ) + return false; + + // Close filestream + mStream.close(); + + // Remove this object from the list of active loggers + for( int i = 0; i < mActiveLoggers.size(); i++ ) + { + if( mActiveLoggers[i] == this ) + { + mActiveLoggers.erase( i ); + mLogging = false; + return true; + } + } + + return false; // If this happens, it's bad... +} + +//----------------------------------------------------------------------------- + +void ConsoleLogger::logCallback( ConsoleLogEntry::Level level, const char *consoleLine ) +{ + + ConsoleLogger *curr; + + // Loop through active consumers and send them the message + for( int i = 0; i < mActiveLoggers.size(); i++ ) + { + curr = mActiveLoggers[i]; + + // If the log level is within the log threshhold, log it + if( curr->mLevel <= level ) + curr->log( consoleLine ); + } +} + +//----------------------------------------------------------------------------- + +void ConsoleLogger::log( const char *consoleLine ) +{ + // Check to see if this is intalized before using it + if( !smInitialized ) + { + if( !init() ) + { + Con::errorf( "I don't know how this happened, but log called on this without it being initialized" ); + return; + } + } + + mStream.writeLine( (U8 *)consoleLine ); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( ConsoleLogger, attach, bool, 2, 2, "() Attaches this object to the console and begins logging" ) +{ + ConsoleLogger *logger = static_cast( object ); + return logger->attach(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( ConsoleLogger, detach, bool, 2, 2, "() Detaches this object from the console and stops logging" ) +{ + ConsoleLogger *logger = static_cast( object ); + return logger->detach(); +} diff --git a/console/consoleLogger.h b/console/consoleLogger.h new file mode 100644 index 0000000..808f8ef --- /dev/null +++ b/console/consoleLogger.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/simBase.h" +#include "console/console.h" +#include "core/stream/fileStream.h" + +#ifndef _CONSOLE_LOGGER_H_ +#define _CONSOLE_LOGGER_H_ + +/// A class designed to be used as a console consumer and log +/// the data it receives to a file. +class ConsoleLogger : public SimObject +{ + typedef SimObject Parent; + + private: + bool mLogging; ///< True if it is currently consuming and logging + FileStream mStream; ///< File stream this object writes to + static bool smInitialized; ///< This is for use with the default constructor + bool mAppend; ///< If false, it will clear the file before logging to it. + StringTableEntry mFilename; ///< The file name to log to. + + /// List of active ConsoleLoggers to send log messages to + static Vector mActiveLoggers; + + /// The log function called by the consumer callback + /// @param consoleLine Line of text to log + void log( const char *consoleLine ); + + /// Utility function, sets up the object (for script interface) returns true if successful + bool init(); + + public: + + // @name Public console variables + /// @{ + ConsoleLogEntry::Level mLevel; ///< The level of log messages to log + /// @} + + DECLARE_CONOBJECT( ConsoleLogger ); + + static void initPersistFields(); + + /// Console constructor + /// + /// @code + /// // Example script constructor usage. + /// %obj = new ConsoleLogger( objName, logFileName, [append = false] ); + /// @endcode + bool processArguments( S32 argc, const char **argv ); + + /// Default constructor, make sure to initalize + ConsoleLogger(); + + /// Constructor + /// @param fileName File name to log to + /// @param append If false, it will clear the file, then start logging, else it will append + ConsoleLogger( const char *fileName, bool append = false ); + + /// Destructor + ~ConsoleLogger(); + + /// Attach to the console and begin logging + /// + /// Returns true if the action is successful + bool attach(); + + /// Detach from the console and stop logging + /// + /// Returns true if the action is successful + bool detach(); + + /// Sets the level of console messages to log. + /// + /// @param level Log level. Only items of the specified level or + /// lower are logged. + /// @see ConsoleLogEntry::Level + void setLogLevel( ConsoleLogEntry::Level level ); + + /// Returns the level of console messages to log + ConsoleLogEntry::Level getLogLevel() const; + + /// The callback for the console consumer + /// + /// @note This is a global callback, not executed per-instance. + /// @see Con::addConsumer + static void logCallback( ConsoleLogEntry::Level level, const char *consoleLine ); +}; + +#endif diff --git a/console/consoleObject.cpp b/console/consoleObject.cpp new file mode 100644 index 0000000..a8e8faa --- /dev/null +++ b/console/consoleObject.cpp @@ -0,0 +1,840 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/consoleObject.h" +#include "core/stringTable.h" +#include "core/crc.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/typeValidators.h" + +AbstractClassRep * AbstractClassRep::classLinkList = NULL; +AbstractClassRep::FieldList sg_tempFieldList( __FILE__, __LINE__ ); +U32 AbstractClassRep::NetClassCount [NetClassGroupsCount][NetClassTypesCount] = {{0, },}; +U32 AbstractClassRep::NetClassBitSize[NetClassGroupsCount][NetClassTypesCount] = {{0, },}; + +AbstractClassRep ** AbstractClassRep::classTable[NetClassGroupsCount][NetClassTypesCount]; + +U32 AbstractClassRep::classCRC[NetClassGroupsCount] = {CRC::INITIAL_CRC_VALUE, }; +bool AbstractClassRep::initialized = false; + +#ifdef TORQUE_DEBUG +U32 ConsoleObject::smNumConObjects; +ConsoleObject* ConsoleObject::smFirstConObject; +#endif + + +void AbstractClassRep::init() +{ + // Register the global visibility boolean. + Con::addVariable( avar( "$%s::isRenderable", getClassName() ), TypeBool, &mIsRenderEnabled ); +} + +const AbstractClassRep::Field *AbstractClassRep::findField(StringTableEntry name) const +{ + for(U32 i = 0; i < mFieldList.size(); i++) + if(mFieldList[i].pFieldname == name) + return &mFieldList[i]; + + return NULL; +} + +AbstractClassRep* AbstractClassRep::findClassRep(const char* in_pClassName) +{ + AssertFatal(initialized, + "AbstractClassRep::findClassRep() - Tried to find an AbstractClassRep before AbstractClassRep::initialize()."); + + for (AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass) + if (!dStricmp(walk->getClassName(), in_pClassName)) + return walk; + + return NULL; +} + +//-------------------------------------- +void AbstractClassRep::registerClassRep(AbstractClassRep* in_pRep) +{ + AssertFatal(in_pRep != NULL, "AbstractClassRep::registerClassRep was passed a NULL pointer!"); + +#ifdef TORQUE_DEBUG // assert if this class is already registered. + for(AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass) + { + AssertFatal(dStrcmp(in_pRep->mClassName, walk->mClassName), + "Duplicate class name registered in AbstractClassRep::registerClassRep()"); + } +#endif + + in_pRep->nextClass = classLinkList; + classLinkList = in_pRep; +} + +//-------------------------------------- +void AbstractClassRep::removeClassRep(AbstractClassRep* in_pRep) +{ + for( AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass ) + { + // This is the case that will most likely get hit. + if( walk->nextClass == in_pRep ) + walk->nextClass = walk->nextClass->nextClass; + else if( walk == in_pRep ) + { + AssertFatal( in_pRep == classLinkList, "Pat failed in his logic for un linking RuntimeClassReps from the class linked list" ); + classLinkList = in_pRep->nextClass; + } + } +} + +//-------------------------------------- + +ConsoleObject* AbstractClassRep::create(const char* in_pClassName) +{ + AssertFatal(initialized, + "AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize()."); + + const AbstractClassRep *rep = AbstractClassRep::findClassRep(in_pClassName); + if(rep) + return rep->create(); + + AssertWarn(0, avar("Couldn't find class rep for dynamic class: %s", in_pClassName)); + return NULL; +} + +//-------------------------------------- +ConsoleObject* AbstractClassRep::create(const U32 groupId, const U32 typeId, const U32 in_classId) +{ + AssertFatal(initialized, + "AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize()."); + AssertFatal(in_classId < NetClassCount[groupId][typeId], + "AbstractClassRep::create() - Class id out of range."); + AssertFatal(classTable[groupId][typeId][in_classId] != NULL, + "AbstractClassRep::create() - No class with requested ID type."); + + // Look up the specified class and create it. + if(classTable[groupId][typeId][in_classId]) + return classTable[groupId][typeId][in_classId]->create(); + + return NULL; +} + +//-------------------------------------- + +static S32 QSORT_CALLBACK ACRCompare(const void *aptr, const void *bptr) +{ + const AbstractClassRep *a = *((const AbstractClassRep **) aptr); + const AbstractClassRep *b = *((const AbstractClassRep **) bptr); + + if(a->mClassType != b->mClassType) + return a->mClassType - b->mClassType; + return dStrnatcasecmp(a->getClassName(), b->getClassName()); +} + +void AbstractClassRep::initialize() +{ + AssertFatal(!initialized, "Duplicate call to AbstractClassRep::initialize()!"); + Vector dynamicTable(__FILE__, __LINE__); + + AbstractClassRep *walk; + + // Initialize namespace references... + for (walk = classLinkList; walk; walk = walk->nextClass) + { + walk->mNamespace = Con::lookupNamespace(StringTable->insert(walk->getClassName())); + walk->mNamespace->mClassRep = walk; + } + + // Initialize field lists... (and perform other console registration). + for (walk = classLinkList; walk; walk = walk->nextClass) + { + // sg_tempFieldList is used as a staging area for field lists + // (see addField, addGroup, etc.) + sg_tempFieldList.setSize(0); + + walk->init(); + + // So if we have things in it, copy it over... + if (sg_tempFieldList.size() != 0) + walk->mFieldList = sg_tempFieldList; + + // And of course delete it every round. + sg_tempFieldList.clear(); + } + + // Calculate counts and bit sizes for the various NetClasses. + for (U32 group = 0; group < NetClassGroupsCount; group++) + { + U32 groupMask = 1 << group; + + // Specifically, for each NetClass of each NetGroup... + for(U32 type = 0; type < NetClassTypesCount; type++) + { + // Go through all the classes and find matches... + for (walk = classLinkList; walk; walk = walk->nextClass) + { + if(walk->mClassType == type && walk->mClassGroupMask & groupMask) + dynamicTable.push_back(walk); + } + + // Set the count for this NetGroup and NetClass + NetClassCount[group][type] = dynamicTable.size(); + if(!NetClassCount[group][type]) + continue; // If no classes matched, skip to next. + + // Sort by type and then by name. + dQsort((void *) &dynamicTable[0], dynamicTable.size(), sizeof(AbstractClassRep *), ACRCompare); + + // Allocate storage in the classTable + classTable[group][type] = new AbstractClassRep*[NetClassCount[group][type]]; + + // Fill this in and assign class ids for this group. + for(U32 i = 0; i < NetClassCount[group][type];i++) + { + classTable[group][type][i] = dynamicTable[i]; + dynamicTable[i]->mClassId[group] = i; + } + + // And calculate the size of bitfields for this group and type. + NetClassBitSize[group][type] = + getBinLog2(getNextPow2(NetClassCount[group][type] + 1)); + AssertFatal(NetClassCount[group][type] < (1 << NetClassBitSize[group][type]), "NetClassBitSize too small!"); + + dynamicTable.clear(); + } + } + + // Ok, we're golden! + initialized = true; +} + +void AbstractClassRep::shutdown() +{ + AssertFatal( initialized, "AbstractClassRep::shutdown - not initialized" ); + + // Release storage allocated to the class table. + + for (U32 group = 0; group < NetClassGroupsCount; group++) + for(U32 type = 0; type < NetClassTypesCount; type++) + if( classTable[ group ][ type ] ) + SAFE_DELETE_ARRAY( classTable[ group ][ type ] ); + + initialized = false; +} + +AbstractClassRep *AbstractClassRep::getCommonParent( const AbstractClassRep *otherClass ) const +{ + // CodeReview: This may be a noob way of doing it. There may be some kind of + // super-spiffy algorithm to do what the code below does, but this appeared + // to make sense to me, and it is pretty easy to see what it is doing [6/23/2007 Pat] + + static VectorPtr thisClassHeirarchy; + thisClassHeirarchy.clear(); + + AbstractClassRep *walk = const_cast( this ); + + while( walk != NULL ) + { + thisClassHeirarchy.push_front( walk ); + walk = walk->getParentClass(); + } + + static VectorPtr compClassHeirarchy; + compClassHeirarchy.clear(); + walk = const_cast( otherClass ); + while( walk != NULL ) + { + compClassHeirarchy.push_front( walk ); + walk = walk->getParentClass(); + } + + // Make sure we only iterate over the list the number of times we can + S32 maxIterations = getMin( compClassHeirarchy.size(), thisClassHeirarchy.size() ); + + U32 i = 0; + for( ; i < maxIterations; i++ ) + { + if( compClassHeirarchy[i] != thisClassHeirarchy[i] ) + break; + } + + return compClassHeirarchy[i]; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- ConsoleObject + +static char replacebuf[1024]; +static char* suppressSpaces(const char* in_pname) +{ + U32 i = 0; + char chr; + do + { + chr = in_pname[i]; + replacebuf[i++] = (chr != 32) ? chr : '_'; + } while(chr); + + return replacebuf; +} + +void ConsoleObject::addGroup(const char* in_pGroupname, const char* in_pGroupDocs) +{ + // Remove spaces. + char* pFieldNameBuf = suppressSpaces(in_pGroupname); + + // Append group type to fieldname. + dStrcat(pFieldNameBuf, "_begingroup"); + + // Create Field. + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(pFieldNameBuf); + f.pGroupname = StringTable->insert(in_pGroupname); + + if(in_pGroupDocs) + f.pFieldDocs = StringTable->insert(in_pGroupDocs); + else + f.pFieldDocs = NULL; + + f.type = AbstractClassRep::StartGroupFieldType; + f.elementCount = 0; + f.groupExpand = false; + f.validator = NULL; + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + + // Add to field list. + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::endGroup(const char* in_pGroupname) +{ + // Remove spaces. + char* pFieldNameBuf = suppressSpaces(in_pGroupname); + + // Append group type to fieldname. + dStrcat(pFieldNameBuf, "_endgroup"); + + // Create Field. + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(pFieldNameBuf); + f.pGroupname = StringTable->insert(in_pGroupname); + f.pFieldDocs = NULL; + f.type = AbstractClassRep::EndGroupFieldType; + f.groupExpand = false; + f.validator = NULL; + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + f.elementCount = 0; + + // Add to field list. + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::addArray( const char *arrayName, S32 count ) +{ + char *nameBuff = suppressSpaces(arrayName); + dStrcat(nameBuff, "_beginarray"); + + // Create Field. + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(nameBuff); + f.pGroupname = StringTable->insert(arrayName); + f.pFieldDocs = NULL; + + f.type = AbstractClassRep::StartArrayFieldType; + f.elementCount = count; + f.groupExpand = false; + f.validator = NULL; + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + + // Add to field list. + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::endArray( const char *arrayName ) +{ + char *nameBuff = suppressSpaces(arrayName); + dStrcat(nameBuff, "_endarray"); + + // Create Field. + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(nameBuff); + f.pGroupname = StringTable->insert(arrayName); + f.pFieldDocs = NULL; + f.type = AbstractClassRep::EndArrayFieldType; + f.groupExpand = false; + f.validator = NULL; + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + f.elementCount = 0; + + // Add to field list. + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::addField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + const char* in_pFieldDocs) +{ + addField( + in_pFieldname, + in_fieldType, + in_fieldOffset, + 1, + NULL, + in_pFieldDocs); +} + +void ConsoleObject::addProtectedField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + AbstractClassRep::SetDataNotify in_setDataFn, + AbstractClassRep::GetDataNotify in_getDataFn, + const char* in_pFieldDocs) +{ + addProtectedField( + in_pFieldname, + in_fieldType, + in_fieldOffset, + in_setDataFn, + in_getDataFn, + 1, + NULL, + in_pFieldDocs); +} + + +void ConsoleObject::addField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + const U32 in_elementCount, + const EnumTable *in_table, + const char* in_pFieldDocs) +{ + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(in_pFieldname); + f.pGroupname = NULL; + + if(in_pFieldDocs) + f.pFieldDocs = StringTable->insert(in_pFieldDocs); + else + f.pFieldDocs = NULL; + + f.type = in_fieldType; + f.offset = in_fieldOffset; + f.elementCount = in_elementCount; + f.table = in_table; + f.validator = NULL; + + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::addProtectedField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + AbstractClassRep::SetDataNotify in_setDataFn, + AbstractClassRep::GetDataNotify in_getDataFn, + const U32 in_elementCount, + const EnumTable *in_table, + const char* in_pFieldDocs) +{ + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(in_pFieldname); + f.pGroupname = NULL; + + if(in_pFieldDocs) + f.pFieldDocs = StringTable->insert(in_pFieldDocs); + else + f.pFieldDocs = NULL; + + f.type = in_fieldType; + f.offset = in_fieldOffset; + f.elementCount = in_elementCount; + f.table = in_table; + f.validator = NULL; + + f.setDataFn = in_setDataFn; + f.getDataFn = in_getDataFn; + + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::addFieldV(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + TypeValidator *v, + const char* in_pFieldDocs) +{ + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(in_pFieldname); + f.pGroupname = NULL; + if(in_pFieldDocs) + f.pFieldDocs = StringTable->insert(in_pFieldDocs); + else + f.pFieldDocs = NULL; + f.type = in_fieldType; + f.offset = in_fieldOffset; + f.elementCount = 1; + f.table = NULL; + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + f.validator = v; + v->fieldIndex = sg_tempFieldList.size(); + + sg_tempFieldList.push_back(f); +} + +void ConsoleObject::addDeprecatedField(const char *fieldName) +{ + AbstractClassRep::Field f; + f.pFieldname = StringTable->insert(fieldName); + f.pGroupname = NULL; + f.pFieldDocs = NULL; + f.type = AbstractClassRep::DeprecatedFieldType; + f.offset = 0; + f.elementCount = 0; + f.table = NULL; + f.validator = NULL; + f.setDataFn = &defaultProtectedSetFn; + f.getDataFn = &defaultProtectedGetFn; + + sg_tempFieldList.push_back(f); +} + + +bool ConsoleObject::removeField(const char* in_pFieldname) +{ + for (U32 i = 0; i < sg_tempFieldList.size(); i++) { + if (dStricmp(in_pFieldname, sg_tempFieldList[i].pFieldname) == 0) { + sg_tempFieldList.erase(i); + return true; + } + } + + return false; +} + +//-------------------------------------- +void ConsoleObject::initPersistFields() +{ +} + +//-------------------------------------- +void ConsoleObject::consoleInit() +{ +} + +ConsoleObject::ConsoleObject() +{ +#ifdef TORQUE_DEBUG + // Add to instance list. + + mNextConObject = smFirstConObject; + mPrevConObject = NULL; + + if( smFirstConObject ) + smFirstConObject->mPrevConObject = this; + smFirstConObject = this; + + smNumConObjects ++; +#endif +} + +ConsoleObject::~ConsoleObject() +{ +#ifdef TORQUE_DEBUG + // Unlink from instance list. + + if( mPrevConObject ) + mPrevConObject->mNextConObject = mNextConObject; + else + smFirstConObject = mNextConObject; + if( mNextConObject ) + mNextConObject->mPrevConObject = mPrevConObject; + + smNumConObjects --; +#endif +} + +//-------------------------------------- +String ConsoleObject::describeSelf() +{ + String desc = avar( "%s|c++: %s", getClassName(), typeid( *this ).name() ); + return desc; +} + +//-------------------------------------- +AbstractClassRep* ConsoleObject::getClassRep() const +{ + return NULL; +} + +String ConsoleObject::_getLogMessage(const char* fmt, void* args) const +{ + String objClass = "UnknownClass"; + if(getClassRep()) + objClass = getClassRep()->getClassName(); + + String formattedMessage = String::VToString(fmt, args); + return String::ToString("%s - Object at %x - %s", + objClass.c_str(), this, formattedMessage.c_str()); +} + +void ConsoleObject::logMessage(const char* fmt, ...) const +{ + va_list args; + va_start(args, fmt); + Con::printf(_getLogMessage(fmt, args)); + va_end(args); +} + +void ConsoleObject::logWarning(const char* fmt, ...) const +{ + va_list args; + va_start(args, fmt); + Con::warnf(_getLogMessage(fmt, args)); + va_end(args); +} + +void ConsoleObject::logError(const char* fmt, ...) const +{ + va_list args; + va_start(args, fmt); + Con::errorf(_getLogMessage(fmt, args)); + va_end(args); +} + + +//------------------------------------------------------------------------------ + +static const char* returnClassList( Vector< AbstractClassRep* >& classes, U32 bufSize ) +{ + if( !classes.size() ) + return ""; + + dQsort( classes.address(), classes.size(), sizeof( AbstractClassRep* ), ACRCompare ); + + char* ret = Con::getReturnBuffer( bufSize ); + dStrcpy( ret, classes[ 0 ]->getClassName() ); + for( U32 i = 1; i < classes.size(); i ++ ) + { + dStrcat( ret, "\t" ); + dStrcat( ret, classes[ i ]->getClassName() ); + } + + return ret; +} + +static const char* returnString( const String& str ) +{ + const U32 size = str.size(); + + char* buffer = Con::getReturnBuffer( size ); + dMemcpy( buffer, str.c_str(), size ); + + return buffer; +} + +//------------------------------------------------------------------------------ + +ConsoleFunction( isClass, bool, 2, 2, "( string className ) - Returns if the passed string is a defined class" ) +{ + AbstractClassRep* rep = AbstractClassRep::findClassRep( argv[ 1 ] ); + return ( rep != NULL ); +} + +ConsoleFunction( isMemberOfClass, bool, 3, 3, "(classA, classB") +{ + AbstractClassRep *pRep = AbstractClassRep::findClassRep(argv[1]); + while (pRep) + { + if (!dStricmp(pRep->getClassName(), argv[2])) + return true; + pRep = pRep->getParentClass(); + } + return false; +} + +ConsoleFunction( getDescriptionOfClass, const char*, 2, 2, "( string className ) - Return the description string for the given class." ) +{ + AbstractClassRep* rep = AbstractClassRep::findClassRep( argv[ 1 ] ); + if( !rep ) + { + Con::errorf( "getDescriptionOfClass - no class called '%s'", argv[ 1 ] ); + return ""; + } + else + return returnString( rep->getDescription() ); +} + +ConsoleFunction( getCategoryOfClass, const char*, 2, 2, "( string className ) - Return the category of the given class." ) +{ + AbstractClassRep* rep = AbstractClassRep::findClassRep( argv[ 1 ] ); + if( !rep ) + { + Con::errorf( "getCategoryOfClass - no class called '%s'", argv[ 1 ] ); + return ""; + } + else + return returnString( rep->getCategory() ); +} + +ConsoleFunction( enumerateConsoleClasses, const char*, 1, 2, "enumerateConsoleClasses(<\"base class\">);") +{ + AbstractClassRep *base = NULL; + if(argc > 1) + { + base = AbstractClassRep::findClassRep(argv[1]); + if(!base) + return ""; + } + + Vector classes; + U32 bufSize = 0; + for(AbstractClassRep *rep = AbstractClassRep::getClassList(); rep; rep = rep->getNextClass()) + { + if( !base || rep->isClass(base)) + { + classes.push_back(rep); + bufSize += dStrlen(rep->getClassName()) + 1; + } + } + + return returnClassList( classes, bufSize ); +} + +ConsoleFunction( enumerateConsoleClassesByCategory, const char*, 2, 2, "( string category ) - Return a list of classes that belong to the given category." ) +{ + String category = argv[ 1 ]; + U32 categoryLength = category.length(); + + U32 bufSize = 0; + Vector< AbstractClassRep* > classes; + + for( AbstractClassRep* rep = AbstractClassRep::getClassList(); rep != NULL; rep = rep->getNextClass() ) + { + const String& repCategory = rep->getCategory(); + + if( repCategory.length() >= categoryLength + && ( repCategory.compare( category, categoryLength, String::NoCase ) == 0 ) + && ( repCategory[ categoryLength ] == ' ' || repCategory[ categoryLength ] == '\0' ) ) + { + classes.push_back( rep ); + bufSize += dStrlen( rep->getClassName() + 1 ); + } + } + + return returnClassList( classes, bufSize ); +} + +ConsoleFunction(dumpNetStats,void,1,1,"") +{ +#ifdef TORQUE_NET_STATS + for (AbstractClassRep * rep = AbstractClassRep::getClassList(); rep; rep = rep->getNextClass()) + { + if (rep->mNetStatPack.numEvents || rep->mNetStatUnpack.numEvents || rep->mNetStatWrite.numEvents || rep->mNetStatRead.numEvents) + { + Con::errorf("class %s net info",rep->getClassName()); + if (rep->mNetStatPack.numEvents) + Con::errorf(" packUpdate: avg (%f), min (%i), max (%i), num (%i)", + F32(rep->mNetStatPack.total)/F32(rep->mNetStatPack.numEvents), + rep->mNetStatPack.min, + rep->mNetStatPack.max, + rep->mNetStatPack.numEvents); + if (rep->mNetStatUnpack.numEvents) + Con::errorf(" unpackUpdate: avg (%f), min (%i), max (%i), num (%i)", + F32(rep->mNetStatUnpack.total)/F32(rep->mNetStatUnpack.numEvents), + rep->mNetStatUnpack.min, + rep->mNetStatUnpack.max, + rep->mNetStatUnpack.numEvents); + if (rep->mNetStatWrite.numEvents) + Con::errorf(" write: avg (%f), min (%i), max (%i), num (%i)", + F32(rep->mNetStatWrite.total)/F32(rep->mNetStatWrite.numEvents), + rep->mNetStatWrite.min, + rep->mNetStatWrite.max, + rep->mNetStatWrite.numEvents); + if (rep->mNetStatRead.numEvents) + Con::errorf(" read: avg (%f), min (%i), max (%i), num (%i)", + F32(rep->mNetStatRead.total)/F32(rep->mNetStatRead.numEvents), + rep->mNetStatRead.min, + rep->mNetStatRead.max, + rep->mNetStatRead.numEvents); + S32 sum = 0; + for (S32 i=0; i<32; i++) + sum += rep->mDirtyMaskFrequency[i]; + if (sum) + { + Con::errorf(" Mask bits:"); + for (S32 i=0; i<8; i++) + { + F32 avg0 = rep->mDirtyMaskFrequency[i] ? F32(rep->mDirtyMaskTotal[i])/F32(rep->mDirtyMaskFrequency[i]) : 0.0f; + F32 avg8 = rep->mDirtyMaskFrequency[i+8] ? F32(rep->mDirtyMaskTotal[i+8])/F32(rep->mDirtyMaskFrequency[i+8]) : 0.0f; + F32 avg16 = rep->mDirtyMaskFrequency[i+16] ? F32(rep->mDirtyMaskTotal[i+16])/F32(rep->mDirtyMaskFrequency[i+16]) : 0.0f; + F32 avg24 = rep->mDirtyMaskFrequency[i+24] ? F32(rep->mDirtyMaskTotal[i+24])/F32(rep->mDirtyMaskFrequency[i+24]) : 0.0f; + Con::errorf(" %2i - %4i (%6.2f) %2i - %4i (%6.2f) %2i - %4i (%6.2f) %2i - %4i, (%6.2f)", + i ,rep->mDirtyMaskFrequency[i],avg0, + i+8 ,rep->mDirtyMaskFrequency[i+8],avg8, + i+16,rep->mDirtyMaskFrequency[i+16],avg16, + i+24,rep->mDirtyMaskFrequency[i+24],avg24); + } + } + } + rep->resetNetStats(); + } +#endif +} + +#if defined(TORQUE_DEBUG) + +ConsoleFunction( sizeof, S32, 2, 2, "sizeof( object | classname)") +{ + AbstractClassRep *acr = NULL; + U32 objId = dAtoi(argv[1]); + SimObject *obj = Sim::findObject(objId); + if(obj) + acr = obj->getClassRep(); + + if(!acr) + acr = AbstractClassRep::findClassRep(argv[1]); + + if(acr) + return acr->getSizeof(); + + if(dStricmp("ConsoleObject", argv[1]) == 0) + return sizeof(ConsoleObject); + + Con::warnf("could not find a class rep for that object or class name."); + return 0; +} + +void ConsoleObject::debugDumpInstances() +{ + Con::printf( "----------- Dumping ConsoleObjects ----------------" ); + for( ConsoleObject* object = smFirstConObject; object != NULL; object = object->mNextConObject ) + Con::printf( object->describeSelf() ); + Con::printf( "%i ConsoleObjects", smNumConObjects ); +} + +void ConsoleObject::debugEnumInstances( const std::type_info& type, DebugEnumInstancesCallback callback ) +{ + for( ConsoleObject* object = smFirstConObject; object != NULL; object = object->mNextConObject ) + if( typeid( *object ) == type ) + { + // Set breakpoint here to break for each instance. + if( callback ) + callback( object ); + } +} + +ConsoleFunction( dumpAllObjects, void, 1, 1, "dumpAllObjects() - dump information about all ConsoleObject instances" ) +{ + ConsoleObject::debugDumpInstances(); +} + +#endif //defined(TORQUE_DEBUG) diff --git a/console/consoleObject.h b/console/consoleObject.h new file mode 100644 index 0000000..d9c63c9 --- /dev/null +++ b/console/consoleObject.h @@ -0,0 +1,984 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLEOBJECT_H_ +#define _CONSOLEOBJECT_H_ + +#include + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif +#ifndef _STRINGFUNCTIONS_H_ +#include "core/strings/stringFunctions.h" +#endif +#ifndef _BITSET_H_ +#include "core/bitSet.h" +#endif +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif + +#include "core/util/safeDelete.h" + + +class Namespace; +class ConsoleObject; + +enum NetClassTypes { + NetClassTypeObject = 0, + NetClassTypeDataBlock, + NetClassTypeEvent, + NetClassTypesCount, +}; + +enum NetClassGroups { + NetClassGroupGame = 0, + NetClassGroupCommunity, + NetClassGroup3, + NetClassGroup4, + NetClassGroupsCount, +}; + +enum NetClassMasks { + NetClassGroupGameMask = BIT(NetClassGroupGame), + NetClassGroupCommunityMask = BIT(NetClassGroupCommunity), +}; + +enum NetDirection +{ + NetEventDirAny, + NetEventDirServerToClient, + NetEventDirClientToServer, +}; + +class SimObject; +class TypeValidator; + +/// Core functionality for class manipulation. +/// +/// @section AbstractClassRep_intro Introduction (or, Why AbstractClassRep?) +/// +/// Many of Torque's subsystems, especially network, console, and sim, +/// require the ability to programatically instantiate classes. For instance, +/// when objects are ghosted, the networking layer needs to be able to create +/// an instance of the object on the client. When the console scripting +/// language runtime encounters the "new" keyword, it has to be able to fill +/// that request. +/// +/// Since standard C++ doesn't provide a function to create a new instance of +/// an arbitrary class at runtime, one must be created. This is what +/// AbstractClassRep and ConcreteClassRep are all about. They allow the registration +/// and instantiation of arbitrary classes at runtime. +/// +/// In addition, ACR keeps track of the fields (registered via addField() and co.) of +/// a class, allowing programmatic access of class fields. +/// +/// @see ConsoleObject +/// +/// @note In general, you will only access the functionality implemented in this class via +/// ConsoleObject::create(). Most of the time, you will only ever need to use this part +/// part of the engine indirectly - ie, you will use the networking system or the console, +/// or ConsoleObject, and they will indirectly use this code. The following discussion +/// is really only relevant for advanced engine users. +/// +/// @section AbstractClassRep_netstuff NetClasses and Class IDs +/// +/// Torque supports a notion of group, type, and direction for objects passed over +/// the network. Class IDs are assigned sequentially per-group, per-type, so that, for instance, +/// the IDs assigned to Datablocks are seperate from the IDs assigned to NetObjects or NetEvents. +/// This can translate into significant bandwidth savings (especially since the size of the fields +/// for transmitting these bits are determined at run-time based on the number of IDs given out. +/// +/// @section AbstractClassRep_details AbstractClassRep Internals +/// +/// Much like ConsoleConstructor, ACR does some preparatory work at runtime before execution +/// is passed to main(). In actual fact, this preparatory work is done by the ConcreteClassRep +/// template. Let's examine this more closely. +/// +/// If we examine ConsoleObject, we see that two macros must be used in the definition of a +/// properly integrated objects. From the ConsoleObject example: +/// +/// @code +/// // This is from inside the class definition... +/// DECLARE_CONOBJECT(TorqueObject); +/// +/// // And this is from outside the class definition... +/// IMPLEMENT_CONOBJECT(TorqueObject); +/// @endcode +/// +/// What do these things actually do? +/// +/// Not all that much, in fact. They expand to code something like this: +/// +/// @code +/// // This is from inside the class definition... +/// static ConcreteClassRep dynClassRep; +/// static AbstractClassRep* getParentStaticClassRep(); +/// static AbstractClassRep* getStaticClassRep(); +/// virtual AbstractClassRep* getClassRep() const; +/// @endcode +/// +/// @code +/// // And this is from outside the class definition... +/// AbstractClassRep* TorqueObject::getClassRep() const { return &TorqueObject::dynClassRep; } +/// AbstractClassRep* TorqueObject::getStaticClassRep() { return &dynClassRep; } +/// AbstractClassRep* TorqueObject::getParentStaticClassRep() { return Parent::getStaticClassRep(); } +/// ConcreteClassRep TorqueObject::dynClassRep("TorqueObject", 0, -1, 0); +/// @endcode +/// +/// As you can see, getClassRep(), getStaticClassRep(), and getParentStaticClassRep() are just +/// accessors to allow access to various ConcreteClassRep instances. This is where the Parent +/// typedef comes into play as well - it lets getParentStaticClassRep() get the right +/// class rep. +/// +/// In addition, dynClassRep is declared as a member of TorqueObject, and defined later +/// on. Much like ConsoleConstructor, ConcreteClassReps add themselves to a global linked +/// list in their constructor. +/// +/// Then, when AbstractClassRep::initialize() is called, from Con::init(), we iterate through +/// the list and perform the following tasks: +/// - Sets up a Namespace for each class. +/// - Call the init() method on each ConcreteClassRep. This method: +/// - Links namespaces between parent and child classes, using Con::classLinkNamespaces. +/// - Calls initPersistFields() and consoleInit(). +/// - As a result of calling initPersistFields, the field list for the class is populated. +/// - Assigns network IDs for classes based on their NetGroup membership. Determines +/// bit allocations for network ID fields. +/// +/// @nosubgrouping +class AbstractClassRep +{ + friend class ConsoleObject; + +public: + + /// @name 'Tructors + /// @{ + + AbstractClassRep() + { + VECTOR_SET_ASSOCIATION(mFieldList); + parentClass = NULL; + mIsRenderEnabled = true; + } + virtual ~AbstractClassRep() { } + + /// @} + + /// @name Representation Interface + /// @{ + + S32 mClassGroupMask; ///< Mask indicating in which NetGroups this object belongs. + S32 mClassType; ///< Stores the NetClass of this class. + S32 mNetEventDir; ///< Stores the NetDirection of this class. + S32 mClassId[NetClassGroupsCount]; ///< Stores the IDs assigned to this class for each group. +#ifdef TORQUE_DEBUG + S32 mClassSizeof; +#endif +#ifdef TORQUE_NET_STATS + struct NetStatInstance + { + U32 numEvents; + U32 total; + S32 min; + S32 max; + + void reset() + { + numEvents = 0; + total = 0; + min = S32_MAX; + max = S32_MIN; + } + + void update(U32 amount) + { + numEvents++; + total += amount; + min = getMin((S32)amount, min); + max = getMax((S32)amount, max); + } + + NetStatInstance() + { + reset(); + } + }; + + NetStatInstance mNetStatPack; + NetStatInstance mNetStatUnpack; + NetStatInstance mNetStatWrite; + NetStatInstance mNetStatRead; + + U32 mDirtyMaskFrequency[32]; + U32 mDirtyMaskTotal[32]; + + void resetNetStats() + { + mNetStatPack.reset(); + mNetStatUnpack.reset(); + mNetStatWrite.reset(); + mNetStatRead.reset(); + + for(S32 i=0; i<32; i++) + { + mDirtyMaskFrequency[i] = 0; + mDirtyMaskTotal[i] = 0; + } + } + + void updateNetStatPack(U32 dirtyMask, U32 length) + { + mNetStatPack.update(length); + + for(S32 i=0; i<32; i++) + if(BIT(i) & dirtyMask) + { + mDirtyMaskFrequency[i]++; + mDirtyMaskTotal[i] += length; + } + } + + void updateNetStatUnpack(U32 length) + { + mNetStatUnpack.update(length); + } + + void updateNetStatWriteData(U32 length) + { + mNetStatWrite.update(length); + } + + void updateNetStatReadData(U32 length) + { + mNetStatRead.update(length); + } +#endif + S32 getClassId (U32 netClassGroup) const; + static U32 getClassCRC (U32 netClassGroup); + const char* getClassName() const; + static AbstractClassRep* getClassList(); + Namespace* getNameSpace(); + AbstractClassRep* getNextClass(); + AbstractClassRep* getParentClass(); + AbstractClassRep* getCommonParent( const AbstractClassRep *otherClass ) const; +#ifdef TORQUE_DEBUG + S32 getSizeof() const; +#endif + /// Helper class to see if we are a given class, or a subclass thereof. + bool isClass(AbstractClassRep *acr) + { + AbstractClassRep *walk = this; + + // Walk up parents, checking for equivalence. + while(walk) + { + if(walk == acr) + return true; + + walk = walk->parentClass; + }; + + return false; + } + + virtual ConsoleObject* create () const = 0; + +protected: + + virtual void init(); + + const char * mClassName; + AbstractClassRep * nextClass; + AbstractClassRep * parentClass; + Namespace * mNamespace; + + /// @} + +public: + + bool mIsRenderEnabled; + + bool isRenderEnabled() const { return mIsRenderEnabled; } + + /// @name Categories + +protected: + + String mCategory; + String mDescription; + +public: + + const String& getCategory() const { return mCategory; } + const String& getDescription() const { return mDescription; } + + /// @name Fields + /// @{ +public: + + /// This is a function pointer typedef to support get/set callbacks for fields + typedef bool (*SetDataNotify)( void *obj, const char *data ); + typedef const char *(*GetDataNotify)( void *obj, const char *data ); + + /// These are special field type values used to mark + /// groups and arrays in the field list. + /// @see Field::type + /// @see addArray, endArray + /// @see addGroup, endGroup + /// @see addGroup, endGroup + /// @see addDeprecatedField + enum ACRFieldTypes + { + /// The first custom field type... all fields + /// types greater or equal to this one are not + /// console data types. + ARCFirstCustomField = 0xFFFFFFFB, + + /// Marks the start of a fixed size array of fields. + /// @see addArray + StartArrayFieldType = 0xFFFFFFFB, + + /// Marks the end of a fixed size array of fields. + /// @see endArray + EndArrayFieldType = 0xFFFFFFFC, + + /// Marks the beginning of a group of fields. + /// @see addGroup + StartGroupFieldType = 0xFFFFFFFD, + + /// Marks the beginning of a group of fields. + /// @see endGroup + EndGroupFieldType = 0xFFFFFFFE, + + /// Marks a field that is depreciated and no + /// longer stores a value. + /// @see addDeprecatedField + DeprecatedFieldType = 0xFFFFFFFF + }; + + struct Field + { + const char* pFieldname; ///< Name of the field. + const char* pGroupname; ///< Optionally filled field containing the group name. + /// + /// This is filled when type is StartField or EndField + + const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc. + bool groupExpand; ///< Flag to track expanded/not state of this group in the editor. + U32 type; ///< A data type ID or one of the special custom fields. @see ACRFieldTypes + U32 offset; ///< Memory offset from beginning of class for this field. + S32 elementCount; ///< Number of elements, if this is an array. + const EnumTable * table; ///< If this is an enum, this points to the table defining it. + BitSet32 flag; ///< Stores various flags + TypeValidator *validator; ///< Validator, if any. + SetDataNotify setDataFn; ///< Set data notify Fn + GetDataNotify getDataFn; ///< Get data notify Fn + }; + typedef Vector FieldList; + + FieldList mFieldList; + + bool mDynamicGroupExpand; + + const Field* findField( StringTableEntry fieldName ) const; + + /// @} + + /// @name Abstract Class Database + /// @{ + +protected: + static AbstractClassRep ** classTable[NetClassGroupsCount][NetClassTypesCount]; + static AbstractClassRep * classLinkList; + static U32 classCRC[NetClassGroupsCount]; + static bool initialized; + + static ConsoleObject* create(const char* in_pClassName); + static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId); + +public: + static U32 NetClassCount [NetClassGroupsCount][NetClassTypesCount]; + static U32 NetClassBitSize[NetClassGroupsCount][NetClassTypesCount]; + + static void registerClassRep(AbstractClassRep*); + static AbstractClassRep* findClassRep(const char* in_pClassName); + static void removeClassRep(AbstractClassRep*); // This should not be used lightly + static void initialize(); // Called from Con::init once on startup + static void shutdown(); + + + /// @} +}; + +extern AbstractClassRep::FieldList sg_tempFieldList; + +inline AbstractClassRep *AbstractClassRep::getClassList() +{ + return classLinkList; +} + +inline U32 AbstractClassRep::getClassCRC(U32 group) +{ + return classCRC[group]; +} + +inline AbstractClassRep *AbstractClassRep::getNextClass() +{ + return nextClass; +} + +inline AbstractClassRep *AbstractClassRep::getParentClass() +{ + return parentClass; +} + +inline S32 AbstractClassRep::getClassId(U32 group) const +{ + return mClassId[group]; +} + +inline const char* AbstractClassRep::getClassName() const +{ + return mClassName; +} + +inline Namespace *AbstractClassRep::getNameSpace() +{ + return mNamespace; +} + +#ifdef TORQUE_DEBUG +inline S32 AbstractClassRep::getSizeof() const +{ + return mClassSizeof; +} +#endif +//------------------------------------------------------------------------------ +//-------------------------------------- ConcreteClassRep +// + + +/// Helper class for AbstractClassRep. +/// +/// @see AbtractClassRep +/// @see ConsoleObject +template +class ConcreteClassRep : public AbstractClassRep +{ +public: + ConcreteClassRep(const char *name, S32 netClassGroupMask, S32 netClassType, S32 netEventDir, AbstractClassRep *parent, const char* ( *parentDesc )() ) + { + mClassName = name; // name is a static compiler string so no need to worry about copying or deleting + mCategory = T::__category(); + + if( &T::__description != parentDesc ) + mDescription = T::__description(); + + // Clean up mClassId + for(U32 i = 0; i < NetClassGroupsCount; i++) + mClassId[i] = -1; + + // Set properties for this ACR + mClassType = netClassType; + mClassGroupMask = netClassGroupMask; + mNetEventDir = netEventDir; + parentClass = parent; +#ifdef TORQUE_DEBUG + mClassSizeof = sizeof(T); +#endif + // Finally, register ourselves. + registerClassRep(this); + }; + + /// Perform class specific initialization tasks. + /// + /// Link namespaces, call initPersistFields() and consoleInit(). + void init() + { + // Get handle to our parent class, if any, and ourselves (we are our parent's child). + AbstractClassRep *parent = T::getParentStaticClassRep(); + AbstractClassRep *child = T::getStaticClassRep(); + + // If we got reps, then link those namespaces! (To get proper inheritance.) + if(parent && child) + Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace()); + + // Finally, do any class specific initialization... + T::initPersistFields(); + T::consoleInit(); + + // Let the base finish up. + AbstractClassRep::init(); + } + + /// Wrap constructor. + ConsoleObject* create() const { return new T; } +}; + +//------------------------------------------------------------------------------ +// Forward declaration of this function so it can be used in the class +const char *defaultProtectedGetFn( void *obj, const char *data ); + +/// Interface class to the console. +/// +/// @section ConsoleObject_basics The Basics +/// +/// Any object which you want to work with the console system should derive from this, +/// and access functionality through the static interface. +/// +/// This class is always used with the DECLARE_CONOBJECT and IMPLEMENT_* macros. +/// +/// @code +/// // A very basic example object. It will do nothing! +/// class TorqueObject : public ConsoleObject { +/// // Must provide a Parent typedef so the console system knows what we inherit from. +/// typedef ConsoleObject Parent; +/// +/// // This does a lot of menial declaration for you. +/// DECLARE_CONOBJECT(TorqueObject); +/// +/// // This is for us to register our fields in. +/// static void initPersistFields(); +/// +/// // A sample field. +/// S8 mSample; +/// } +/// @endcode +/// +/// @code +/// // And the accordant implementation... +/// IMPLEMENT_CONOBJECT(TorqueObject); +/// +/// void TorqueObject::initPersistFields() +/// { +/// // If you want to inherit any fields from the parent (you do), do this: +/// Parent::initPersistFields(); +/// +/// // Pass the field, the type, the offset, and a usage string. +/// addField("sample", TypeS8, Offset(mSample, TorqueObject), "A test field."); +/// } +/// @endcode +/// +/// That's all you need to do to get a class registered with the console system. At this point, +/// you can instantiate it via script, tie methods to it using ConsoleMethod, register fields, +/// and so forth. You can also register any global variables related to the class by creating +/// a consoleInit() method. +/// +/// You will need to use different IMPLEMENT_ macros in different cases; for instance, if you +/// are making a NetObject (for ghosting), a DataBlock, or a NetEvent. +/// +/// @see AbstractClassRep for gory implementation details. +/// @nosubgrouping +class ConsoleObject +{ +protected: + + /// @deprecated This is disallowed. + ConsoleObject(); + /// @deprecated This is disallowed. + ConsoleObject(const ConsoleObject&); + +public: + + /// Get a reference to a field by name. + const AbstractClassRep::Field *findField(StringTableEntry fieldName) const; + + /// Gets the ClassRep. + virtual AbstractClassRep* getClassRep() const; + + /// Set the value of a field. + bool setField(const char *fieldName, const char *value); + virtual ~ConsoleObject(); + + /// Return a string that describes this instance. Meant primarily for debugging. + virtual String describeSelf(); + +public: + + /// @name Object Creation + /// @{ + static ConsoleObject* create(const char* in_pClassName); + static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId); + /// @} + +public: + + /// Get the classname from a class tag. + static const char* lookupClassName(const U32 in_classTag); + + /// @name Fields + /// @{ + + /// Mark the beginning of a group of fields. + /// + /// This is used in the consoleDoc system. + /// @see console_autodoc + static void addGroup(const char* in_pGroupname, const char* in_pGroupDocs = NULL); + + /// Mark the end of a group of fields. + /// + /// This is used in the consoleDoc system. + /// @see console_autodoc + static void endGroup(const char* in_pGroupname); + + /// Marks the start of a fixed size array of fields. + /// @see console_autodoc + static void addArray( const char *arrayName, S32 count ); + + /// Marks the end of an array of fields. + /// @see console_autodoc + static void endArray( const char *arrayName ); + + /// Register a complex field. + /// + /// @param in_pFieldname Name of the field. + /// @param in_fieldType Type of the field. @see ConsoleDynamicTypes + /// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro. + /// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory. + /// @param in_table An EnumTable, if this is an enumerated field. + /// @param in_pFieldDocs Usage string for this field. @see console_autodoc + static void addField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + const U32 in_elementCount = 1, + const EnumTable * in_table = NULL, + const char* in_pFieldDocs = NULL); + + /// Register a simple field. + /// + /// @param in_pFieldname Name of the field. + /// @param in_fieldType Type of the field. @see ConsoleDynamicTypes + /// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro. + /// @param in_pFieldDocs Usage string for this field. @see console_autodoc + static void addField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + const char* in_pFieldDocs); + + /// Register a validated field. + /// + /// A validated field is just like a normal field except that you can't + /// have it be an array, and that you give it a pointer to a TypeValidator + /// subclass, which is then used to validate any value placed in it. Invalid + /// values are ignored and an error is printed to the console. + /// + /// @see addField + /// @see typeValidators.h + static void addFieldV(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + TypeValidator *v, + const char * in_pFieldDocs = NULL); + + /// Register a complex protected field. + /// + /// @param in_pFieldname Name of the field. + /// @param in_fieldType Type of the field. @see ConsoleDynamicTypes + /// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro. + /// @param in_setDataFn When this field gets set, it will call the callback provided. @see console_protected + /// @param in_getDataFn When this field is accessed for it's data, it will return the value of this function + /// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory. + /// @param in_table An EnumTable, if this is an enumerated field. + /// @param in_pFieldDocs Usage string for this field. @see console_autodoc + static void addProtectedField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + AbstractClassRep::SetDataNotify in_setDataFn, + AbstractClassRep::GetDataNotify in_getDataFn = &defaultProtectedGetFn, + const U32 in_elementCount = 1, + const EnumTable * in_table = NULL, + const char* in_pFieldDocs = NULL); + + /// Register a simple protected field. + /// + /// @param in_pFieldname Name of the field. + /// @param in_fieldType Type of the field. @see ConsoleDynamicTypes + /// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro. + /// @param in_setDataFn When this field gets set, it will call the callback provided. @see console_protected + /// @param in_getDataFn When this field is accessed for it's data, it will return the value of this function + /// @param in_pFieldDocs Usage string for this field. @see console_autodoc + static void addProtectedField(const char* in_pFieldname, + const U32 in_fieldType, + const dsize_t in_fieldOffset, + AbstractClassRep::SetDataNotify in_setDataFn, + AbstractClassRep::GetDataNotify in_getDataFn = &defaultProtectedGetFn, + const char* in_pFieldDocs = NULL); + + /// Add a deprecated field. + /// + /// A deprecated field will always be undefined, even if you assign a value to it. This + /// is useful when you need to make sure that a field is not being used anymore. + static void addDeprecatedField(const char *fieldName); + + /// Remove a field. + /// + /// Sometimes, you just have to remove a field! + /// @returns True on success. + static bool removeField(const char* in_pFieldname); + + /// @} + + /// @name Logging + /// @{ + + /// Overload this in subclasses to change the message formatting. + /// @param fmt A printf style format string. + /// @param args A va_list containing the args passed ot a log function. + /// @note It is suggested that you use String::VToString. + virtual String _getLogMessage(const char* fmt, void* args) const; + + /// @} + +public: + + /// @name Logging + /// These functions will try to print out a message along the lines + /// of "ObjectClass - ObjectName(ObjectId) - formatted message" + /// @{ + + /// Logs with Con::printf. + void logMessage(const char* fmt, ...) const; + + /// Logs with Con::warnf. + void logWarning(const char* fmt, ...) const; + + /// Logs with Con::errorf. + void logError(const char* fmt, ...) const; + + /// @} + + /// Register dynamic fields in a subclass of ConsoleObject. + /// + /// @see addField(), addFieldV(), addDeprecatedField(), addGroup(), endGroup() + static void initPersistFields(); + + /// Register global constant variables and do other one-time initialization tasks in + /// a subclass of ConsoleObject. + /// + /// @deprecated You should use ConsoleMethod and ConsoleFunction, not this, to + /// register methods or commands. + /// @see console + static void consoleInit(); + + /// @name Field List + /// @{ + + /// Get a list of all the fields. This information cannot be modified. + const AbstractClassRep::FieldList& getFieldList() const; + + /// Get a list of all the fields, set up so we can modify them. + /// + /// @note This is a bad trick to pull if you aren't very careful, + /// since you can blast field data! + AbstractClassRep::FieldList& getModifiableFieldList(); + + /// Get a handle to a boolean telling us if we expanded the dynamic group. + /// + /// @see GuiInspector::Inspect() + bool& getDynamicGroupExpand(); + /// @} + + /// @name ConsoleObject Implementation + /// + /// These functions are implemented in every subclass of + /// ConsoleObject by an IMPLEMENT_CONOBJECT or IMPLEMENT_CO_* macro. + /// @{ + + /// Get the abstract class information for this class. + static AbstractClassRep *getStaticClassRep() { return NULL; } + + /// Get the abstract class information for this class's superclass. + static AbstractClassRep *getParentStaticClassRep() { return NULL; } + + /// Get our network-layer class id. + /// + /// @param netClassGroup The net class for which we want our ID. + /// @see + S32 getClassId(U32 netClassGroup) const; + + /// Get our compiler and platform independent class name. + /// + /// @note This name can be used to instantiate another instance using create() + const char *getClassName() const; + + /// @} + + static const char* __category() { return ""; } + static const char* __description() { return ""; } + +#ifdef TORQUE_DEBUG + /// @name Instance Tracking (debugging only) + /// @note This is NOT thread-safe. + /// @{ + +public: + typedef void ( *DebugEnumInstancesCallback )( ConsoleObject* ); + + /// Dump describeSelf()s of all live ConsoleObjects to the console. + static void debugDumpInstances(); + + /// Call the given callback for all instances of the given type. + /// Callback may also be NULL in which case the method just iterates + /// over all instances of the given type. This is useful for setting + /// a breakpoint during debugging. + static void debugEnumInstances( const std::type_info& type, DebugEnumInstancesCallback callback ); + +private: + ConsoleObject* mNextConObject; + ConsoleObject* mPrevConObject; + + static U32 smNumConObjects; + static ConsoleObject* smFirstConObject; + + /// @} +#endif +}; + +#define addNamedField(fieldName,type,className) addField(#fieldName, type, Offset(fieldName,className)) +#define addNamedFieldV(fieldName,type,className, validator) addFieldV(#fieldName, type, Offset(fieldName,className), validator) + + +//------------------------------------------------------------------------------ +//-------------------------------------- Inlines +// +inline S32 ConsoleObject::getClassId(U32 netClassGroup) const +{ + AssertFatal(getClassRep() != NULL,"Cannot get tag from non-declared dynamic class!"); + return getClassRep()->getClassId(netClassGroup); +} + +inline const char * ConsoleObject::getClassName() const +{ + AssertFatal(getClassRep() != NULL, + "Cannot get tag from non-declared dynamic class"); + return getClassRep()->getClassName(); +} + +inline const AbstractClassRep::Field * ConsoleObject::findField(StringTableEntry name) const +{ + AssertFatal(getClassRep() != NULL, + avar("Cannot get field '%s' from non-declared dynamic class.", name)); + return getClassRep()->findField(name); +} + +inline bool ConsoleObject::setField(const char *fieldName, const char *value) +{ + //sanity check + if ((! fieldName) || (! fieldName[0]) || (! value)) + return false; + + if (! getClassRep()) + return false; + + const AbstractClassRep::Field *myField = getClassRep()->findField(StringTable->insert(fieldName)); + + if (! myField) + return false; + + Con::setData( + myField->type, + (void *) (((const char *)(this)) + myField->offset), + 0, + 1, + &value, + myField->table, + myField->flag); + + return true; +} + +inline ConsoleObject* ConsoleObject::create(const char* in_pClassName) +{ + return AbstractClassRep::create(in_pClassName); +} + +inline ConsoleObject* ConsoleObject::create(const U32 groupId, const U32 typeId, const U32 in_classId) +{ + return AbstractClassRep::create(groupId, typeId, in_classId); +} + +inline const AbstractClassRep::FieldList& ConsoleObject::getFieldList() const +{ + return getClassRep()->mFieldList; +} + +inline AbstractClassRep::FieldList& ConsoleObject::getModifiableFieldList() +{ + return getClassRep()->mFieldList; +} + +inline bool& ConsoleObject::getDynamicGroupExpand() +{ + return getClassRep()->mDynamicGroupExpand; +} + +/// @name ConsoleObject Macros +/// @{ + +#define DECLARE_CONOBJECT(className) \ + static ConcreteClassRep dynClassRep; \ + static AbstractClassRep* getParentStaticClassRep(); \ + static AbstractClassRep* getStaticClassRep(); \ + virtual AbstractClassRep* getClassRep() const + +#define DECLARE_CATEGORY( string ) \ + static const char* __category() { return string; } + +#define DECLARE_DESCRIPTION( string ) \ + static const char* __description() { return string; } + +#define IMPLEMENT_CONOBJECT(className) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className, 0, -1, 0, className::getParentStaticClassRep(), &Parent::__description) + +#define IMPLEMENT_CO_NETOBJECT_V1(className) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeObject, 0, className::getParentStaticClassRep(), &Parent::__description) + +#define IMPLEMENT_CO_DATABLOCK_V1(className) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeDataBlock, 0, className::getParentStaticClassRep(), &Parent::__description) + +/// @} + +//------------------------------------------------------------------------------ +// Protected field default get/set functions +// +// The reason for these functions is that it will save one branch per console +// data request and script functions will still execute at the same speed as +// before the modifications to allow protected static fields. These will just +// inline and the code should be roughly the same size, and just as fast as +// before the modifications. -pw +inline bool defaultProtectedSetFn( void *obj, const char *data ) +{ + return true; +} + +inline const char *defaultProtectedGetFn( void *obj, const char *data ) +{ + return data; +} + +inline const char *emptyStringProtectedGetFn( void *obj, const char *data ) +{ + return ""; +} + +#endif //_CONSOLEOBJECT_H_ diff --git a/console/consoleParser.cpp b/console/consoleParser.cpp new file mode 100644 index 0000000..e847ee7 --- /dev/null +++ b/console/consoleParser.cpp @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/consoleParser.h" + +#include "core/strings/stringFunctions.h" +#include "console/console.h" + + +namespace Compiler +{ + +static ConsoleParser *gParserList = NULL; +static ConsoleParser *gDefaultParser = NULL; + +void freeConsoleParserList(void) +{ + while(gParserList) + { + ConsoleParser * pParser = gParserList; + gParserList = pParser->next; + delete pParser; + } + + gDefaultParser = NULL; +} + +bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb,fnDestroyBuffer db,bool def) +{ + AssertFatal(ext && gcf && gcl && p && r, "AddConsoleParser called with one or more NULL arguments"); + + ConsoleParser * pParser = new ConsoleParser; + if(pParser != NULL) + { + pParser->ext = ext; + pParser->getCurrentFile = gcf; + pParser->getCurrentLine = gcl; + pParser->parse = p; + pParser->restart = r; + pParser->setScanBuffer = ssb; + pParser->destroyBuffer = db; + if(def) + gDefaultParser = pParser; + + pParser->next = gParserList; + gParserList = pParser; + + return true; + } + return false; +} + +ConsoleParser * getParserForFile(const char *filename) +{ + if(filename == NULL) + return gDefaultParser; + + char *ptr = dStrrchr((char *)filename, '.'); + if(ptr != NULL) + { + ptr++; + + ConsoleParser *p; + for(p = gParserList; p; p = p->next) + { + if(dStricmp(ptr, p->ext) == 0) + return p; + } + } + + return gDefaultParser; +} + +} // end namespace Con diff --git a/console/consoleParser.h b/console/consoleParser.h new file mode 100644 index 0000000..910760a --- /dev/null +++ b/console/consoleParser.h @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +// +// TorqueBASIC +// (c) Copyright 2004 Burnt Wasp +//----------------------------------------------------------------------------- + +#ifndef _CONSOLE_PARSER_H_ +#define _CONSOLE_PARSER_H_ + +#include + +namespace Compiler +{ + +//----------------------------------------------------------------------------- +/// \brief Function for GetCurrentFile from the lexer +//----------------------------------------------------------------------------- +typedef const char *(*fnGetCurrentFile)(); +//----------------------------------------------------------------------------- +/// \brief Function for GetCurrentLine from the lexer +//----------------------------------------------------------------------------- +typedef S32 (*fnGetCurrentLine)(); +//----------------------------------------------------------------------------- +/// \brief Function for Parse from the lexer +//----------------------------------------------------------------------------- +typedef S32 (*fnParse)(); +//----------------------------------------------------------------------------- +/// \brief Function for Restart from the lexer +//----------------------------------------------------------------------------- +typedef void (*fnRestart)(FILE *input_file); +//----------------------------------------------------------------------------- +/// \brief Function for SetScanBuffer from the lexer +//----------------------------------------------------------------------------- +typedef void (*fnSetScanBuffer)(const char *sb, const char *fn); +typedef void (*fnDestroyBuffer) (); +//----------------------------------------------------------------------------- +/// \brief List of parsers for the compiler +//----------------------------------------------------------------------------- +struct ConsoleParser +{ + struct ConsoleParser *next; //!< Next object in list or NULL + + char *ext; //!< Filename extension handled by this parser + + fnGetCurrentFile getCurrentFile; //!< GetCurrentFile lexer function + fnGetCurrentLine getCurrentLine; //!< GetCurrentLine lexer function + fnParse parse; //!< Parse lexer function + fnRestart restart; //!< Restart lexer function + fnSetScanBuffer setScanBuffer; //!< SetScanBuffer lexer function + fnDestroyBuffer destroyBuffer; + ~ConsoleParser() + { + destroyBuffer(); + } +}; + +// Macros + +//----------------------------------------------------------------------------- +/// \brief Declare a parser's function prototypes +//----------------------------------------------------------------------------- +#define CON_DECLARE_PARSER(prefix) \ + const char * prefix##GetCurrentFile(); \ + S32 prefix##GetCurrentLine(); \ + void prefix##SetScanBuffer(const char *sb, const char *fn); \ + S32 prefix##parse(); \ + void prefix##restart(FILE *input_file);\ + void prefix##DestroyBuff() + +//----------------------------------------------------------------------------- +/// \brief Helper macro to add console parsers +//----------------------------------------------------------------------------- +#define CON_ADD_PARSER(prefix, ext, def) \ + Compiler::addConsoleParser(ext, prefix##GetCurrentFile, prefix##GetCurrentLine, prefix##parse, \ + prefix##restart, prefix##SetScanBuffer,prefix##DestroyBuff, def) + +//----------------------------------------------------------------------------- +/// \brief Free the console parser list +/// +/// \sa AddConsoleParser() +//----------------------------------------------------------------------------- +void freeConsoleParserList(void); + +//----------------------------------------------------------------------------- +/// \brief Add a console parser to the list +/// +/// \param ext Filename extension +/// \param gcf GetCurrentFile function +/// \param gcl GetCurrentLine function +/// \param p Parse function +/// \param r Restart function +/// \param ssb SetScanBuffer function +/// \param def true if this is the default parser (Note: set this only on the .cs parser!) +/// \return true for success, false for failure (out of memory) +/// \sa FreeConsoleParserList(), ConsoleParser +//----------------------------------------------------------------------------- +bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb,fnDestroyBuffer db, bool def = false); + +//----------------------------------------------------------------------------- +/// \brief Get the parser for a particular file based on its extension +/// +/// \param filename Filename of file to obtain parser for +/// \sa ConsoleParser +//----------------------------------------------------------------------------- +ConsoleParser * getParserForFile(const char *filename); + +} // end namespace Con + +#endif // _CONSOLE_PARSER_H_ diff --git a/console/consoleTypes.cpp b/console/consoleTypes.cpp new file mode 100644 index 0000000..fef2c52 --- /dev/null +++ b/console/consoleTypes.cpp @@ -0,0 +1,822 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/stringTable.h" +#include "core/util/str.h" +#include "core/color.h" +#include "console/simBase.h" + +//----------------------------------------------------------------------------- +// TypeString +//----------------------------------------------------------------------------- +ConsoleType( string, TypeString, const char* ) + +ConsoleGetType( TypeString ) +{ + return *((const char **)(dptr)); +} + +ConsoleSetType( TypeString ) +{ + if(argc == 1) + *((const char **) dptr) = StringTable->insert(argv[0]); + else + Con::printf("(TypeString) Cannot set multiple args to a single string."); +} + +//----------------------------------------------------------------------------- +// TypeCaseString +//----------------------------------------------------------------------------- +ConsoleType( caseString, TypeCaseString, const char* ) + +ConsoleSetType( TypeCaseString ) +{ + if(argc == 1) + *((const char **) dptr) = StringTable->insert(argv[0], true); + else + Con::printf("(TypeCaseString) Cannot set multiple args to a single string."); +} + +ConsoleGetType( TypeCaseString ) +{ + return *((const char **)(dptr)); +} + +//----------------------------------------------------------------------------- +// TypeRealString +//----------------------------------------------------------------------------- +ConsoleType( String, TypeRealString, String ) + +ConsoleGetType( TypeRealString ) +{ + const String *theString = static_cast(dptr); + + return theString->c_str(); +} + +ConsoleSetType( TypeRealString ) +{ + String *theString = static_cast(dptr); + + if(argc == 1) + *theString = argv[0]; + else + Con::printf("(TypeRealString) Cannot set multiple args to a single string."); +} + +//----------------------------------------------------------------------------- +// TypeCommand +//----------------------------------------------------------------------------- +ConsoleType( String, TypeCommand, String ) + +ConsoleGetType( TypeCommand ) +{ + const String *theString = static_cast(dptr); + + return theString->c_str(); +} + +ConsoleSetType( TypeCommand ) +{ + String *theString = static_cast(dptr); + + if(argc == 1) + *theString = argv[0]; + else + Con::printf("(TypeCommand) Cannot set multiple args to a single command."); +} + +//----------------------------------------------------------------------------- +// TypeFileName +//----------------------------------------------------------------------------- +ConsolePrepType( filename, TypeFilename, const char * ) + +ConsoleSetType( TypeFilename ) +{ + if(argc == 1) + { + char buffer[1024]; + if(argv[0][0] == '$') + { + dStrncpy(buffer, argv[0], sizeof(buffer) - 1); + buffer[sizeof(buffer)-1] = 0; + } + else if (! Con::expandScriptFilename(buffer, 1024, argv[0])) + { + Con::warnf("(TypeFilename) illegal filename detected: %s", argv[0]); + return; + } + + *((const char **) dptr) = StringTable->insert(buffer); + } + else + Con::printf("(TypeFilename) Cannot set multiple args to a single filename."); +} + +ConsoleGetType( TypeFilename ) +{ + return *((const char **)(dptr)); +} + +ConsoleProcessData( TypeFilename ) +{ + if( Con::expandScriptFilename( buffer, bufferSz, data ) ) + return buffer; + else + { + Con::warnf("(TypeFilename) illegal filename detected: %s", data); + return data; + } +} + +//----------------------------------------------------------------------------- +// TypeStringFilename +//----------------------------------------------------------------------------- +ConsolePrepType( filename, TypeStringFilename, String ) + +ConsoleSetType( TypeStringFilename ) +{ + if(argc == 1) + { + char buffer[1024]; + if(argv[0][0] == '$') + { + dStrncpy(buffer, argv[0], sizeof(buffer) - 1); + buffer[sizeof(buffer)-1] = 0; + } + else if (! Con::expandScriptFilename(buffer, 1024, argv[0])) + { + Con::warnf("(TypeStringFilename) illegal filename detected: %s", argv[0]); + return; + } + + *((String*)dptr) = String(buffer); + } + else + Con::printf("(TypeStringFilename) Cannot set multiple args to a single filename."); +} + +ConsoleGetType( TypeStringFilename ) +{ + return *((String*)dptr); +} + +ConsoleProcessData( TypeStringFilename ) +{ + //char buffer[2048]; + + if( Con::expandScriptFilename( buffer, bufferSz, data ) ) + { + return buffer; + } + else + { + Con::warnf("(TypeFilename) illegal filename detected: %s", data); + return data; + } +} + +//----------------------------------------------------------------------------- +// TypeImageFilename +//----------------------------------------------------------------------------- +ConsolePrepType( filename, TypeImageFilename, String ) + +ConsoleSetType( TypeImageFilename ) +{ + if(argc == 1) + { + char buffer[1024]; + if(argv[0][0] == '$') + { + dStrncpy(buffer, argv[0], sizeof(buffer) - 1); + buffer[sizeof(buffer)-1] = 0; + } + else if (! Con::expandScriptFilename(buffer, 1024, argv[0])) + { + Con::warnf("(TypeImageFilename) illegal filename detected: %s", argv[0]); + return; + } + + *((String*)dptr) = String(buffer); + } + else + Con::printf("(TypeImageFilename) Cannot set multiple args to a single filename."); +} + +ConsoleGetType( TypeImageFilename ) +{ + return *((String*)dptr); +} + +ConsoleProcessData( TypeImageFilename ) +{ + if( Con::expandScriptFilename( buffer, 2048, data ) ) + return buffer; + else + { + Con::warnf("(TypeImageFilename) illegal filename detected: %s", data); + return data; + } +} + +//----------------------------------------------------------------------------- +// TypeS8 +//----------------------------------------------------------------------------- +ConsoleType( char, TypeS8, S8 ) + +ConsoleGetType( TypeS8 ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d", *((U8 *) dptr) ); + return returnBuffer; +} + +ConsoleSetType( TypeS8 ) +{ + if(argc == 1) + *((U8 *) dptr) = dAtoi(argv[0]); + else + Con::printf("(TypeU8) Cannot set multiple args to a single S8."); +} + +//----------------------------------------------------------------------------- +// TypeS32 +//----------------------------------------------------------------------------- +ConsoleType( int, TypeS32, S32 ) +ImplementConsoleTypeCasters(TypeS32, S32) + +ConsoleGetType( TypeS32 ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d", *((S32 *) dptr) ); + return returnBuffer; +} + +ConsoleSetType( TypeS32 ) +{ + if(argc == 1) + *((S32 *) dptr) = dAtoi(argv[0]); + else + Con::printf("(TypeS32) Cannot set multiple args to a single S32."); +} + +//----------------------------------------------------------------------------- +// TypeS32Mask +//----------------------------------------------------------------------------- +ConsoleType( int, TypeBitMask32, S32 ) + +ConsoleGetType( TypeBitMask32 ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "0x%08x", *((S32 *) dptr) ); + return returnBuffer; +} + +ConsoleSetType( TypeBitMask32 ) +{ + if(argc == 1) + *((S32 *) dptr) = dAtoui(argv[0],0); + else + Con::printf("(TypeBitMask32) Cannot set multiple args to a single S32."); +} + +//----------------------------------------------------------------------------- +// TypeS32Vector +//----------------------------------------------------------------------------- +ConsoleType( intList, TypeS32Vector, Vector ) + +ConsoleGetType( TypeS32Vector ) +{ + Vector *vec = (Vector *)dptr; + S32 buffSize = ( vec->size() * 15 ) + 16 ; + char* returnBuffer = Con::getReturnBuffer( buffSize ); + S32 maxReturn = buffSize; + returnBuffer[0] = '\0'; + S32 returnLeng = 0; + for (Vector::iterator itr = vec->begin(); itr != vec->end(); itr++) + { + // concatenate the next value onto the return string + dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", *itr); + // update the length of the return string (so far) + returnLeng = dStrlen(returnBuffer); + } + // trim off that last extra space + if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ') + returnBuffer[returnLeng - 1] = '\0'; + return returnBuffer; +} + +ConsoleSetType( TypeS32Vector ) +{ + Vector *vec = (Vector *)dptr; + // we assume the vector should be cleared first (not just appending) + vec->clear(); + if(argc == 1) + { + const char *values = argv[0]; + const char *endValues = values + dStrlen(values); + S32 value; + // advance through the string, pulling off S32's and advancing the pointer + while (values < endValues && dSscanf(values, "%d", &value) != 0) + { + vec->push_back(value); + const char *nextValues = dStrchr(values, ' '); + if (nextValues != 0 && nextValues < endValues) + values = nextValues + 1; + else + break; + } + } + else if (argc > 1) + { + for (S32 i = 0; i < argc; i++) + vec->push_back(dAtoi(argv[i])); + } + else + Con::printf("Vector must be set as { a, b, c, ... } or \"a b c ...\""); +} + +//----------------------------------------------------------------------------- +// TypeF32 +//----------------------------------------------------------------------------- +ConsoleType( float, TypeF32, F32 ) +ImplementConsoleTypeCasters(TypeF32, F32) + +ConsoleGetType( TypeF32 ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g", *((F32 *) dptr) ); + return returnBuffer; +} +ConsoleSetType( TypeF32 ) +{ + if(argc == 1) + *((F32 *) dptr) = dAtof(argv[0]); + else + Con::printf("(TypeF32) Cannot set multiple args to a single F32."); +} + +//----------------------------------------------------------------------------- +// TypeF32Vector +//----------------------------------------------------------------------------- +ConsoleType( floatList, TypeF32Vector, Vector ) + +ConsoleGetType( TypeF32Vector ) +{ + Vector *vec = (Vector *)dptr; + S32 buffSize = ( vec->size() * 15 ) + 16 ; + char* returnBuffer = Con::getReturnBuffer( buffSize ); + S32 maxReturn = buffSize; + returnBuffer[0] = '\0'; + S32 returnLeng = 0; + for (Vector::iterator itr = vec->begin(); itr != vec->end(); itr++) + { + // concatenate the next value onto the return string + dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%g ", *itr); + // update the length of the return string (so far) + returnLeng = dStrlen(returnBuffer); + } + // trim off that last extra space + if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ') + returnBuffer[returnLeng - 1] = '\0'; + return returnBuffer; +} + +ConsoleSetType( TypeF32Vector ) +{ + Vector *vec = (Vector *)dptr; + // we assume the vector should be cleared first (not just appending) + vec->clear(); + if(argc == 1) + { + const char *values = argv[0]; + const char *endValues = values + dStrlen(values); + F32 value; + // advance through the string, pulling off F32's and advancing the pointer + while (values < endValues && dSscanf(values, "%g", &value) != 0) + { + vec->push_back(value); + const char *nextValues = dStrchr(values, ' '); + if (nextValues != 0 && nextValues < endValues) + values = nextValues + 1; + else + break; + } + } + else if (argc > 1) + { + for (S32 i = 0; i < argc; i++) + vec->push_back(dAtof(argv[i])); + } + else + Con::printf("Vector must be set as { a, b, c, ... } or \"a b c ...\""); +} + +//----------------------------------------------------------------------------- +// TypeBool +//----------------------------------------------------------------------------- +ConsoleType( bool, TypeBool, bool ) + +ConsoleGetType( TypeBool ) +{ + return *((bool *) dptr) ? "1" : "0"; +} + +ConsoleSetType( TypeBool ) +{ + if(argc == 1) + *((bool *) dptr) = dAtob(argv[0]); + else + Con::printf("(TypeBool) Cannot set multiple args to a single bool."); +} + + +//----------------------------------------------------------------------------- +// TypeBoolVector +//----------------------------------------------------------------------------- +ConsoleType( boolList, TypeBoolVector, Vector ) + +ConsoleGetType( TypeBoolVector ) +{ + Vector *vec = (Vector*)dptr; + char* returnBuffer = Con::getReturnBuffer(1024); + S32 maxReturn = 1024; + returnBuffer[0] = '\0'; + S32 returnLeng = 0; + for (Vector::iterator itr = vec->begin(); itr < vec->end(); itr++) + { + // concatenate the next value onto the return string + dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", (*itr == true ? 1 : 0)); + returnLeng = dStrlen(returnBuffer); + } + // trim off that last extra space + if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ') + returnBuffer[returnLeng - 1] = '\0'; + return(returnBuffer); +} + +ConsoleSetType( TypeBoolVector ) +{ + Vector *vec = (Vector*)dptr; + // we assume the vector should be cleared first (not just appending) + vec->clear(); + if (argc == 1) + { + const char *values = argv[0]; + const char *endValues = values + dStrlen(values); + S32 value; + // advance through the string, pulling off bool's and advancing the pointer + while (values < endValues && dSscanf(values, "%d", &value) != 0) + { + vec->push_back(value == 0 ? false : true); + const char *nextValues = dStrchr(values, ' '); + if (nextValues != 0 && nextValues < endValues) + values = nextValues + 1; + else + break; + } + } + else if (argc > 1) + { + for (S32 i = 0; i < argc; i++) + vec->push_back(dAtob(argv[i])); + } + else + Con::printf("Vector must be set as { a, b, c, ... } or \"a b c ...\""); +} + +//----------------------------------------------------------------------------- +// TypeEnum +//----------------------------------------------------------------------------- +ConsoleType( enumval, TypeEnum, S32 ) + +ConsoleGetType( TypeEnum ) +{ + AssertFatal(tbl, "Null enum table passed to getDataTypeEnum()"); + S32 dptrVal = *(S32*)dptr; + for (S32 i = 0; i < tbl->size; i++) + { + if (dptrVal == tbl->table[i].index) + { + return tbl->table[i].label; + } + } + + //not found + return ""; +} + +ConsoleSetType( TypeEnum ) +{ + AssertFatal(tbl, "Null enum table passed to setDataTypeEnum()"); + if (argc != 1) return; + + S32 val = 0; + for (S32 i = 0; i < tbl->size; i++) + { + if (! dStricmp(argv[0], tbl->table[i].label)) + { + val = tbl->table[i].index; + break; + } + } + *((S32 *) dptr) = val; +} + +//----------------------------------------------------------------------------- +// TypeModifiedEnum +//----------------------------------------------------------------------------- +ConsoleType( modenumval, TypeModifiedEnum, S32 ) + +#define _TME_ModifierBit 0x40000000 + +ConsoleGetType( TypeModifiedEnum ) +{ + AssertFatal(tbl, "Null enum table passed to getDataTypeModifiedEnum()"); + + U32 dptrVal = *(U32*)dptr; + + U32 modMask = 0xFFFFFFFF; + for (S32 i = 0; i < tbl->size; i++) + { + if( tbl->table[i].index & _TME_ModifierBit ) + modMask ^= tbl->table[i].index ^ _TME_ModifierBit; + } + + String retString = ""; + for (S32 i = 0; i < tbl->size; i++) + { + if( ( dptrVal & modMask ) == tbl->table[i].index || + ( tbl->table[i].index & _TME_ModifierBit && ( dptrVal & ( tbl->table[i].index ^ _TME_ModifierBit ) ) ) ) + { + retString += ( i == 0 ? tbl->table[i].label : ' ' + tbl->table[i].label ); + } + } + + return retString.c_str(); +} + +ConsoleSetType( TypeModifiedEnum ) +{ + AssertFatal(tbl, "Null enum table passed to setDataTypeModifiedEnum()"); + if (argc != 1) return; + + Vector enumVals; + String::SizeType strpos = 0; + + String data(argv[0]); + + while( strpos < data.length() ) + { + String::SizeType n = data.find( ' ', strpos, String::NoCase ); + if( n == String::NPos ) + n = data.length(); + + enumVals.increment(); + enumVals.last() = data.substr( strpos, n - strpos ); + + strpos = n + 1; // to eat the ' ' + } + + U32 val = 0; + for( S32 j = 0; j < enumVals.size(); j++ ) + { + for( S32 i = 0; i < tbl->size; i++ ) + { + if( dStricmp(tbl->table[i].label, enumVals[j]) == 0 ) + { + val |= ( tbl->table[i].index & _TME_ModifierBit ? tbl->table[i].index ^ _TME_ModifierBit : tbl->table[i].index ); + break; + } + } + } + *((U32 *) dptr) = val; +} + +//----------------------------------------------------------------------------- +// TypeFlag +//----------------------------------------------------------------------------- +ConsoleType( flag, TypeFlag, S32 ) + +ConsoleGetType( TypeFlag ) +{ + BitSet32 tempFlags = *(BitSet32 *)dptr; + if (tempFlags.test(flag)) return "true"; + else return "false"; +} + +ConsoleSetType( TypeFlag ) +{ + bool value = true; + if (argc != 1) + { + Con::printf("flag must be true or false"); + } + else + { + value = dAtob(argv[0]); + } + ((BitSet32 *)dptr)->set(flag, value); +} + +//----------------------------------------------------------------------------- +// TypeColorF +//----------------------------------------------------------------------------- +ConsoleType( ColorF, TypeColorF, ColorF ) + +ConsoleGetType( TypeColorF ) +{ + ColorF * color = (ColorF*)dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g %g", color->red, color->green, color->blue, color->alpha); + return(returnBuffer); +} + +ConsoleSetType( TypeColorF ) +{ + ColorF *tmpColor = (ColorF *) dptr; + if(argc == 1) + { + tmpColor->set(0, 0, 0, 1); + F32 r,g,b,a; + S32 args = dSscanf(argv[0], "%g %g %g %g", &r, &g, &b, &a); + tmpColor->red = r; + tmpColor->green = g; + tmpColor->blue = b; + if (args == 4) + tmpColor->alpha = a; + } + else if(argc == 3) + { + tmpColor->red = dAtof(argv[0]); + tmpColor->green = dAtof(argv[1]); + tmpColor->blue = dAtof(argv[2]); + tmpColor->alpha = 1.f; + } + else if(argc == 4) + { + tmpColor->red = dAtof(argv[0]); + tmpColor->green = dAtof(argv[1]); + tmpColor->blue = dAtof(argv[2]); + tmpColor->alpha = dAtof(argv[3]); + } + else + Con::printf("Color must be set as { r, g, b [,a] }"); +} + +//----------------------------------------------------------------------------- +// TypeColorI +//----------------------------------------------------------------------------- +ConsoleType( ColorI, TypeColorI, ColorI ) + +ConsoleGetType( TypeColorI ) +{ + ColorI *color = (ColorI *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d %d %d %d", color->red, color->green, color->blue, color->alpha); + return returnBuffer; +} + +ConsoleSetType( TypeColorI ) +{ + ColorI *tmpColor = (ColorI *) dptr; + if(argc == 1) + { + tmpColor->set(0, 0, 0, 255); + S32 r,g,b,a; + S32 args = dSscanf(argv[0], "%d %d %d %d", &r, &g, &b, &a); + tmpColor->red = r; + tmpColor->green = g; + tmpColor->blue = b; + if (args == 4) + tmpColor->alpha = a; + } + else if(argc == 3) + { + tmpColor->red = dAtoi(argv[0]); + tmpColor->green = dAtoi(argv[1]); + tmpColor->blue = dAtoi(argv[2]); + tmpColor->alpha = 255; + } + else if(argc == 4) + { + tmpColor->red = dAtoi(argv[0]); + tmpColor->green = dAtoi(argv[1]); + tmpColor->blue = dAtoi(argv[2]); + tmpColor->alpha = dAtoi(argv[3]); + } + else + Con::printf("Color must be set as { r, g, b [,a] }"); +} + +//----------------------------------------------------------------------------- +// TypeSimObjectPtr +//----------------------------------------------------------------------------- +ConsoleType( SimObjectPtr, TypeSimObjectPtr, SimObject* ) + +ConsoleSetType( TypeSimObjectPtr ) +{ + if(argc == 1) + { + SimObject **obj = (SimObject **)dptr; + *obj = Sim::findObject(argv[0]); + } + else + Con::printf("(TypeSimObjectPtr) Cannot set multiple args to a single S32."); +} + +ConsoleGetType( TypeSimObjectPtr ) +{ + SimObject **obj = (SimObject**)dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%s", *obj ? (*obj)->getName() ? (*obj)->getName() : (*obj)->getIdString() : ""); + return returnBuffer; +} + +//----------------------------------------------------------------------------- +// TypeSimObjectName +//----------------------------------------------------------------------------- +ConsoleType( SimObjectPtr, TypeSimObjectName, SimObject* ) + +ConsoleSetType( TypeSimObjectName ) +{ + if(argc == 1) + { + SimObject **obj = (SimObject **)dptr; + *obj = Sim::findObject(argv[0]); + } + else + Con::printf("(TypeSimObjectName) Cannot set multiple args to a single S32."); +} + +ConsoleGetType( TypeSimObjectName ) +{ + SimObject **obj = (SimObject**)dptr; + char* returnBuffer = Con::getReturnBuffer(128); + dSprintf(returnBuffer, 128, "%s", *obj && (*obj)->getName() ? (*obj)->getName() : ""); + return returnBuffer; +} + +//----------------------------------------------------------------------------- +// TypeName +//----------------------------------------------------------------------------- +ConsoleType( name, TypeName, const char* ) + +ConsoleGetType( TypeName ) +{ + return *((const char **)(dptr)); +} + +ConsoleSetType( TypeName ) +{ + Con::warnf( "ConsoleSetType( TypeName ) should not be called. A ProtectedSetMethod does this work!" ); +} + +//----------------------------------------------------------------------------- +// TypeMaterialName +//----------------------------------------------------------------------------- + +ConsoleType( MaterialName, TypeMaterialName, String ) + +ConsoleGetType( TypeMaterialName ) +{ + const String *theString = static_cast(dptr); + return theString->c_str(); +} + +ConsoleSetType( TypeMaterialName ) +{ + String *theString = static_cast(dptr); + + if(argc == 1) + *theString = argv[0]; + else + Con::printf("(TypeMaterialName) Cannot set multiple args to a single string."); +} + +//----------------------------------------------------------------------------- +// TypeCubemapName +//----------------------------------------------------------------------------- + +ConsoleType( CubemapName, TypeCubemapName, String ) + +ConsoleGetType( TypeCubemapName ) +{ + const String *theString = static_cast(dptr); + return theString->c_str(); +} + +ConsoleSetType( TypeCubemapName ) +{ + String *theString = static_cast(dptr); + + if(argc == 1) + *theString = argv[0]; + else + Con::printf("(TypeCubemapName) Cannot set multiple args to a single string."); +} + diff --git a/console/consoleTypes.h b/console/consoleTypes.h new file mode 100644 index 0000000..32a0d12 --- /dev/null +++ b/console/consoleTypes.h @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CONSOLETYPES_H_ +#define _CONSOLETYPES_H_ + +#ifndef _DYNAMIC_CONSOLETYPES_H_ +#include "console/dynamicTypes.h" +#endif +#ifndef _MATHTYPES_H_ +#include "math/mathTypes.h" +#endif + +#ifndef Offset +#if defined(TORQUE_COMPILER_GCC) && (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) +#define Offset(m,T) ((int)(&((T *)1)->m) - 1) +#else +#define Offset(x, cls) ((dsize_t)((const char *)&(((cls *)0)->x)-(const char *)0)) +#endif +#endif + +class ColorI; +class ColorF; +class GFXShader; +class GFXCubemap; +class CustomMaterial; +class ProjectileData; +class ParticleEmitterData; + +// Define Core Console Types +DefineConsoleType( TypeBool, bool ) +DefineConsoleType( TypeBoolVector, Vector) +DefineConsoleType( TypeS8, S8 ) +DefineConsoleType( TypeS32, S32 ) +DefineConsoleType( TypeBitMask32, S32 ) +DefineConsoleType( TypeS32Vector, Vector ) +DefineConsoleType( TypeF32, F32 ) +DefineConsoleType( TypeF32Vector, Vector ) +DefineConsoleType( TypeString, const char * ) +DefineConsoleType( TypeCaseString, const char * ) +DefineConsoleType( TypeRealString, String ) +DefineConsoleType( TypeCommand, String ) +DefineConsoleType( TypeFilename, const char * ) +DefineConsoleType( TypeStringFilename, String ) + +/// TypeImageFilename is equivalent to TypeStringFilename in its usage, +/// it exists for the benefit of GuiInspector, which will provide a custom +/// InspectorField for this type that can display a texture preview. +DefineConsoleType( TypeImageFilename, String ) + +/// TypeMaterialName is equivalent to TypeRealString in its usage, +/// it exists for the benefit of GuiInspector, which will provide a +/// custom InspectorField for this type. +DefineConsoleType( TypeMaterialName, String ) + +/// TypeCubemapName is equivalent to TypeRealString in its usage, +/// but the Inspector will provide a drop-down list of CubemapData objects. +DefineConsoleType( TypeCubemapName, String ) + +DefineConsoleType( TypeEnum, S32 ) +DefineConsoleType( TypeModifiedEnum, S32 ) +DefineConsoleType( TypeFlag, S32 ) +DefineConsoleType( TypeColorI, ColorI ) +DefineConsoleType( TypeColorF, ColorF ) +DefineConsoleType( TypeSimObjectPtr, SimObject* ) +DefineConsoleType( TypeSimObjectName, SimObject* ) +DefineConsoleType( TypeShader, GFXShader * ) +DefineConsoleType( TypeCustomMaterial, CustomMaterial * ) +DefineConsoleType( TypeProjectileDataPtr, ProjectileData* ) +DefineConsoleType( TypeParticleEmitterDataPtr, ParticleEmitterData* ); + +/// Special field type for SimObject::objectName +DefineConsoleType( TypeName, const char* ) + +#endif diff --git a/console/debugOutputConsumer.cpp b/console/debugOutputConsumer.cpp new file mode 100644 index 0000000..7bc4a1b --- /dev/null +++ b/console/debugOutputConsumer.cpp @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/debugOutputConsumer.h" + +namespace DebugOutputConsumer +{ +#ifndef DISABLE_DEBUG_SPEW +bool debugOutputEnabled = true; +#else +bool debugOutputEnabled = false; +#endif + + +void init() +{ + Con::addConsumer( DebugOutputConsumer::logCallback ); +} + +void destroy() +{ + Con::removeConsumer( DebugOutputConsumer::logCallback ); +} + +void logCallback( ConsoleLogEntry::Level level, const char *consoleLine ) +{ + if( debugOutputEnabled ) + { + Platform::outputDebugString( "%s", consoleLine ); + } +} + +} diff --git a/console/debugOutputConsumer.h b/console/debugOutputConsumer.h new file mode 100644 index 0000000..e1120e9 --- /dev/null +++ b/console/debugOutputConsumer.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DEBUGOUTPUTCONSUMER_H_ +#define _DEBUGOUTPUTCONSUMER_H_ + +#include "platform/platform.h" + +//#define TORQUE_LOCBUILD + +#if !defined(TORQUE_DEBUG) && defined(TORQUE_OS_XENON) && !defined(TORQUE_LOCBUILD) +#define DISABLE_DEBUG_SPEW +#endif + +#include "console/console.h" + +namespace DebugOutputConsumer +{ + extern bool debugOutputEnabled; + + void init(); + void destroy(); + void logCallback( ConsoleLogEntry::Level level, const char *consoleLine ); + + void enableDebugOutput( bool enable = true ); +}; + +#endif \ No newline at end of file diff --git a/console/dynamicTypes.cpp b/console/dynamicTypes.cpp new file mode 100644 index 0000000..89deec3 --- /dev/null +++ b/console/dynamicTypes.cpp @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/dynamicTypes.h" + +#include "core/strings/stringFunctions.h" + + +// Init the globals. +ConsoleBaseType *ConsoleBaseType::smListHead = NULL; +S32 ConsoleBaseType::smConsoleTypeCount = 0; + +// And, we also privately store the types lookup table. +static VectorPtr gConsoleTypeTable( __FILE__, __LINE__ ); + +ConsoleBaseType *ConsoleBaseType::getListHead() +{ + return smListHead; +} + +void ConsoleBaseType::initialize() +{ + // Prep and empty the vector. + gConsoleTypeTable.setSize(smConsoleTypeCount+1); + dMemset(gConsoleTypeTable.address(), 0, sizeof(ConsoleBaseType*) * gConsoleTypeTable.size()); + + // Walk the list and register each one with the console system. + ConsoleBaseType *walk = getListHead(); + while(walk) + { + const S32 id = walk->getTypeID(); + AssertFatal(gConsoleTypeTable[id]==NULL, "ConsoleBaseType::initialize - encountered a table slot that contained something!"); + gConsoleTypeTable[id] = walk; + + walk = walk->getListNext(); + } +} + +ConsoleBaseType *ConsoleBaseType::getType(const S32 typeID) +{ + if( typeID == -1 ) + return NULL; + return gConsoleTypeTable[typeID]; +} + +ConsoleBaseType *ConsoleBaseType::getTypeByName( const char *typeName ) +{ + ConsoleBaseType *walk = getListHead(); + while( walk != NULL ) + { + if( dStrcmp( walk->getTypeName(), typeName ) == 0 ) + return walk; + + walk = walk->getListNext(); + } + + return NULL; +} + +ConsoleBaseType * ConsoleBaseType::getTypeByClassName(const char *typeName) +{ + ConsoleBaseType *walk = getListHead(); + while( walk != NULL ) + { + if( dStrcmp( walk->getTypeClassName(), typeName ) == 0 ) + return walk; + + walk = walk->getListNext(); + } + + return NULL; +} + +//------------------------------------------------------------------------- + +ConsoleBaseType::ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName) +{ + // General initialization. + mInspectorFieldType = NULL; + + mTypeSize = size; + mTypeName = aTypeName; + + mTypeID = smConsoleTypeCount++; + *idPtr = mTypeID; + + // Link ourselves into the list. + mListNext = smListHead; + smListHead = this; +} + +ConsoleBaseType::~ConsoleBaseType() +{ + // Nothing to do for now; we could unlink ourselves from the list, but why? +} \ No newline at end of file diff --git a/console/dynamicTypes.h b/console/dynamicTypes.h new file mode 100644 index 0000000..a23590f --- /dev/null +++ b/console/dynamicTypes.h @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _DYNAMIC_CONSOLETYPES_H_ +#define _DYNAMIC_CONSOLETYPES_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +class ConsoleBaseType +{ +protected: + /// This is used to generate unique IDs for each type. + static S32 smConsoleTypeCount; + + /// We maintain a linked list of all console types; this is its head. + static ConsoleBaseType *smListHead; + + /// Next item in the list of all console types. + ConsoleBaseType *mListNext; + + /// Destructor is private to avoid people mucking up the list. + ~ConsoleBaseType(); + + S32 mTypeID; + dsize_t mTypeSize; + const char *mTypeName; + const char *mInspectorFieldType; + +public: + + /// @name cbt_list List Interface + /// + /// Interface for accessing/traversing the list of types. + + /// Get the head of the list. + static ConsoleBaseType *getListHead(); + + /// Get the item that follows this item in the list. + ConsoleBaseType *getListNext() const + { + return mListNext; + } + + /// Called once to initialize the console type system. + static void initialize(); + + /// Call me to get a pointer to a type's info. + static ConsoleBaseType *getType(const S32 typeID); + + /// Call to get a pointer to a type's info + static ConsoleBaseType *getTypeByName( const char *typeName ); + static ConsoleBaseType *getTypeByClassName( const char *typeName ); + /// @} + + /// The constructor is responsible for linking an element into the + /// master list, registering the type ID, etc. + ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName); + + const S32 getTypeID() const { return mTypeID; } + const S32 getTypeSize() const { return mTypeSize; } + const char *getTypeName() const { return mTypeName; } + + void setInspectorFieldType(const char *type) { mInspectorFieldType = type; } + const char *getInspectorFieldType() { return mInspectorFieldType; } + + virtual void setData(void *dptr, S32 argc, const char **argv, const EnumTable *tbl, BitSet32 flag)=0; + virtual const char *getData(void *dptr, const EnumTable *tbl, BitSet32 flag )=0; + virtual const char *getTypeClassName()=0; + virtual void *getNativeVariable()=0; + virtual void deleteNativeVariable(void* var) = 0; + virtual const bool isDatablock() { return false; }; + virtual const char *prepData(const char *data, char *buffer, U32 bufferLen) { return data; }; +}; + +#define DefineConsoleType( type, nativeType ) \ + extern S32 type; \ + extern const char *castConsoleTypeToString( nativeType arg ); \ + extern bool castConsoleTypeFromString( nativeType &arg, const char *str ); + +#define ConsoleType( typeName, type, nativeType ) \ + class ConsoleType##type : public ConsoleBaseType \ + { \ + public: \ + ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { nativeType *make_Sure_You_Dont_Pass_SizeOf_Here = (nativeType*)0; (void)make_Sure_You_Dont_Pass_SizeOf_Here; } \ + virtual void setData(void *dptr, S32 argc, const char **argv, const EnumTable *tbl, BitSet32 flag); \ + virtual const char *getData(void *dptr, const EnumTable *tbl, BitSet32 flag ); \ + virtual const char *getTypeClassName() { return #typeName ; } \ + virtual void *getNativeVariable() { nativeType* var = new nativeType; return (void*)var; } \ + virtual void deleteNativeVariable(void* var) { nativeType* nativeVar = reinterpret_cast(var); delete nativeVar; }\ + }; \ + S32 type = -1; \ + ConsoleType##type gConsoleType##type##Instance(sizeof(nativeType),&type,#type); \ + +#define ImplementConsoleTypeCasters(type, nativeType) \ + const char *castConsoleTypeToString( nativeType arg ) { return Con::getData(type, &arg, 0); } \ + bool castConsoleTypeFromString( nativeType &arg, const char *str ) { Con::setData(type, &arg, 0, 1, &str); return true; } + + +#define ConsolePrepType( typeName, type, nativeType ) \ + class ConsoleType##type : public ConsoleBaseType \ + { \ + public: \ + ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \ + virtual void setData(void *dptr, S32 argc, const char **argv, const EnumTable *tbl, BitSet32 flag); \ + virtual const char *getData(void *dptr, const EnumTable *tbl, BitSet32 flag ); \ + virtual const char *getTypeClassName() { return #typeName; }; \ + virtual void *getNativeVariable() { nativeType* var = new nativeType; return (void*)var; } \ + virtual void deleteNativeVariable(void* var) { nativeType* nativeVar = reinterpret_cast(var); delete nativeVar; }\ + virtual const char *prepData(const char *data, char *buffer, U32 bufferLen); \ + }; \ + S32 type = -1; \ + ConsoleType##type gConsoleType##type##Instance(sizeof( nativeType ),&type,#type); \ + +#define ConsoleSetType( type ) \ + void ConsoleType##type::setData(void *dptr, S32 argc, const char **argv, const EnumTable *tbl, BitSet32 flag) + +#define ConsoleGetType( type ) \ + const char *ConsoleType##type::getData(void *dptr, const EnumTable *tbl, BitSet32 flag) + +#define ConsoleProcessData( type ) \ + const char *ConsoleType##type::prepData(const char *data, char *buffer, U32 bufferSz) + +#define DatablockConsoleType( typeName, type, size, className ) \ + class ConsoleType##type : public ConsoleBaseType \ + { \ + public: \ + ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \ + virtual void setData(void *dptr, S32 argc, const char **argv, const EnumTable *tbl, BitSet32 flag); \ + virtual const char *getData(void *dptr, const EnumTable *tbl, BitSet32 flag ); \ + virtual const char *getTypeClassName() { return #className; }; \ + virtual void *getNativeVariable() { className* var = new className; return (void*)var; } \ + virtual void deleteNativeVariable(void* var) { className* nativeVar = reinterpret_cast(var); delete nativeVar; } \ + virtual const bool isDatablock() { return true; }; \ + }; \ + S32 type = -1; \ + ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \ + + +#endif diff --git a/console/fieldBrushObject.cpp b/console/fieldBrushObject.cpp new file mode 100644 index 0000000..5fdabed --- /dev/null +++ b/console/fieldBrushObject.cpp @@ -0,0 +1,564 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringUnit.h" +#include "console/fieldBrushObject.h" + +// Prefix added to dynamic-fields when they're used to store any copied static-fields when peristing. +#define INTERNAL_FIELD_PREFIX "_fieldBrush_" + +// Size of return buffers used. +const U32 bufferSizes = 1024 * 4; + + +IMPLEMENT_CONOBJECT(FieldBrushObject); + +FieldBrushObject::FieldBrushObject() +{ + // Reset Description. + mDescription = StringTable->insert(""); + mSortName = StringTable->insert(""); +} + + +//----------------------------------------------------------------------------- +// Persist Fields. +//----------------------------------------------------------------------------- +void FieldBrushObject::initPersistFields() +{ + // Add Fields. + addProtectedField("description", TypeCaseString, Offset(mDescription, FieldBrushObject), setDescription, defaultProtectedGetFn, ""); + addProtectedField("sortName", TypeString, Offset(mSortName, FieldBrushObject), setSortName, defaultProtectedGetFn, ""); + + // Call Parent. + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// Remove from Sim. +//----------------------------------------------------------------------------- +void FieldBrushObject::onRemove() +{ + // Destroy any fields. + destroyFields(); + + // Call Parent. + Parent::onRemove(); +} + + +//----------------------------------------------------------------------------- +// Destroy any fields. +//----------------------------------------------------------------------------- +void FieldBrushObject::destroyFields() +{ + // Fetch Dynamic-Field Dictionary. + SimFieldDictionary* pFieldDictionary = getFieldDictionary(); + + // Any Field Dictionary? + if ( pFieldDictionary == NULL ) + { + // No, so we're done. + return; + } + + // Iterate fields. + for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr ) + { + // Fetch Field Entry. + SimFieldDictionary::Entry* fieldEntry = *itr; + + // Internal Field? + if ( dStrstr( fieldEntry->slotName, INTERNAL_FIELD_PREFIX ) == fieldEntry->slotName ) + { + // Yes, so remove it. + pFieldDictionary->setFieldValue( fieldEntry->slotName, "" ); + } + } +} + + +//----------------------------------------------------------------------------- +// Suppress Spaces. +//----------------------------------------------------------------------------- +static char replacebuf[1024]; +static char* suppressSpaces(const char* in_pname) +{ + U32 i = 0; + char chr; + do + { + chr = in_pname[i]; + replacebuf[i++] = (chr != 32) ? chr : '_'; + } while(chr); + + return replacebuf; +} + +//----------------------------------------------------------------------------- +// Query Groups. +//----------------------------------------------------------------------------- +ConsoleMethod(FieldBrushObject, queryGroups, const char*, 3, 3, "(simObject) Query available static-field groups for selected object./\n" + "@param simObject Object to query static-field groups on.\n" + "@return Space-seperated static-field group list.") +{ + // Fetch selected object. + SimObject* pSimObject = dynamic_cast( Sim::findObject( argv[2] ) ); + + // Valid object? + if ( pSimObject == NULL ) + { + // No, so warn. + Con::warnf("FieldBrushObject::queryFieldGroups() - Invalid SimObject!"); + return NULL; + } + + // Create Returnable Buffer. + S32 maxBuffer = bufferSizes; + char* pReturnBuffer = Con::getReturnBuffer(bufferSizes); + char* pBuffer = pReturnBuffer; + + // Fetch Field List. + const AbstractClassRep::FieldList& staticFields = pSimObject->getFieldList(); + + // Iterate Fields. + for( U32 fieldIndex = 0; fieldIndex < staticFields.size(); ++fieldIndex ) + { + // Fetch Field. + const AbstractClassRep::Field& staticField = staticFields[fieldIndex]; + + // Start Group? + if ( staticField.type == AbstractClassRep::StartGroupFieldType ) + { + // Yes, so write-out group-name without spaces... + char* pGroupNameNoSpaces = suppressSpaces(staticField.pGroupname); + + // Will the field fit? + // NOTE:- We used "-1" to include the suffix space. + if ( (maxBuffer - (S32)dStrlen(pGroupNameNoSpaces) - 1) >= 0 ) + { + // Yes... + // NOTE:- The group-name does not have the "_begingroup" suffix which should stay hidden. + S32 charsWritten = dSprintf( pBuffer, maxBuffer, "%s ", pGroupNameNoSpaces ); + pBuffer += charsWritten; + maxBuffer -= charsWritten; + } + else + { + // No, so warn. + Con::warnf("FieldBrushObject::queryGroups() - Couldn't fit all groups into return string!"); + break; + } + } + } + + // Strip final space. + if ( pBuffer != pReturnBuffer ) + { + *(pBuffer-1) = 0; + } + + // Return Buffer. + return pReturnBuffer; +} + + +//----------------------------------------------------------------------------- +// Query Fields. +//----------------------------------------------------------------------------- +ConsoleMethod(FieldBrushObject, queryFields, const char*, 3, 4, "(simObject, [groupList]) Query available static-fields for selected object./\n" + "@param simObject Object to query static-fields on.\n" + "@param groupList groups to filter static-fields against.\n" + "@return Space-seperated static-field list.") +{ + // Fetch selected object. + SimObject* pSimObject = dynamic_cast( Sim::findObject( argv[2] ) ); + + // Valid object? + if ( pSimObject == NULL ) + { + // No, so warn. + Con::warnf("FieldBrushObject::queryFields() - Invalid SimObject!"); + return NULL; + } + + // Create Returnable Buffer. + S32 maxBuffer = bufferSizes; + char* pReturnBuffer = Con::getReturnBuffer(bufferSizes); + char* pBuffer = pReturnBuffer; + + // Fetch Field List. + const AbstractClassRep::FieldList& staticFields = pSimObject->getFieldList(); + + // Did we specify a groups list? + if ( argc < 4 ) + { + // No, so return all fields... + + // Iterate fields. + for( U32 fieldIndex = 0; fieldIndex < staticFields.size(); ++fieldIndex ) + { + // Fetch Field. + const AbstractClassRep::Field& staticField = staticFields[fieldIndex]; + + // Standard Field? + if ( staticField.type != AbstractClassRep::StartGroupFieldType && + staticField.type != AbstractClassRep::EndGroupFieldType && + staticField.type != AbstractClassRep::DeprecatedFieldType ) + { + // Yes, so will the field fit? + // NOTE:- We used "-1" to include the suffix space. + if ( (maxBuffer - (S32)dStrlen(staticField.pFieldname) - 1) >= 0 ) + { + // Yes, so write-out field-name. + S32 charsWritten = dSprintf( pBuffer, maxBuffer, "%s ", staticField.pFieldname ); + pBuffer += charsWritten; + maxBuffer -= charsWritten; + } + else + { + // No, so warn. + Con::warnf("FieldBrushObject::queryFields() - Couldn't fit all fields into return string!"); + break; + } + } + } + + // Strip final space. + if ( pBuffer != pReturnBuffer ) + { + *(pBuffer-1) = 0; + } + + // Return field list. + return pReturnBuffer; + } + + // Yes, so filter by groups... + + // Group List. + Vector groups; + // Yes, so fetch group list. + const char* groupList = argv[3]; + // Yes, so calculate group Count. + const U32 groupCount = StringUnit::getUnitCount( groupList, " \t\n" ); + + char tempBuf[256]; + + // Iterate groups... + for ( U32 groupIndex = 0; groupIndex < groupCount; ++groupIndex ) + { + // Copy string element. + dStrcpy( tempBuf, StringUnit::getUnit( groupList, groupIndex, " \t\n" ) ); + // Append internal name. + dStrcat( tempBuf, "_begingroup" ); + // Store Group. + groups.push_back( StringTable->insert( tempBuf ) ); + } + + // Reset Valid Group. + bool validGroup = false; + + // Iterate fields. + for( U32 fieldIndex = 0; fieldIndex < staticFields.size(); ++fieldIndex ) + { + // Fetch Field. + const AbstractClassRep::Field& staticField = staticFields[fieldIndex]; + + // Handle Group Type. + switch( staticField.type ) + { + // Start Group. + case AbstractClassRep::StartGroupFieldType: + { + // Is this group valid? + + // Iterate groups... + for ( U32 groupIndex = 0; groupIndex < groups.size(); ++groupIndex ) + { + // Group selected? + if ( groups[groupIndex] == staticField.pFieldname ) + { + // Yes, so flag as valid. + validGroup = true; + break; + } + } + + } break; + + // End Group. + case AbstractClassRep::EndGroupFieldType: + { + // Reset Valid Group. + validGroup = false; + + } break; + + // Deprecated. + case AbstractClassRep::DeprecatedFieldType: + { + } break; + + // Standard. + default: + { + // Do we have a valid group? + if ( validGroup ) + { + // Yes, so will the field fit? + // NOTE:- We used "-1" to include the suffix space. + if ( (maxBuffer - (S32)dStrlen(staticField.pFieldname) - 1) >= 0 ) + { + // Yes, so write-out field-name. + S32 charsWritten = dSprintf( pBuffer, maxBuffer, "%s ", staticField.pFieldname ); + pBuffer += charsWritten; + maxBuffer -= charsWritten; + } + else + { + // No, so warn. + Con::warnf("FieldBrushObject::queryFields() - Couldn't fit all fields into return string!"); + // HACK: Easy way to finish iterating fields. + fieldIndex = staticFields.size(); + break; + } + } + + } break; + }; + } + + // Strip final space. + if ( pBuffer != pReturnBuffer ) + { + *(pBuffer-1) = 0; + } + + // Return field list. + return pReturnBuffer; +} + +//----------------------------------------------------------------------------- +// Copy Fields. +//----------------------------------------------------------------------------- +ConsoleMethod(FieldBrushObject, copyFields, void, 3, 4, "(simObject, [fieldList]) Copy selected static-fields for selected object./\n" + "@param simObject Object to copy static-fields from.\n" + "@param fieldList fields to filter static-fields against.\n" + "@return No return value.") +{ + // Fetch selected object. + SimObject* pSimObject = dynamic_cast( Sim::findObject( argv[2] ) ); + + // Valid object? + if ( pSimObject == NULL ) + { + // No, so warn. + Con::warnf("FieldBrushObject::copyFields() - Invalid SimObject!"); + return; + } + + // Fetch field list. + const char* pFieldList = (argc > 3 ) ? argv[3] : NULL; + + // Copy Fields. + object->copyFields( pSimObject, pFieldList ); +} +// Copy Fields. +void FieldBrushObject::copyFields( SimObject* pSimObject, const char* fieldList ) +{ + // FieldBrushObject class? + if ( dStrcmp(pSimObject->getClassName(), getClassName()) == 0 ) + { + // Yes, so warn. + Con::warnf("FieldBrushObject::copyFields() - Cannot copy FieldBrushObject objects!"); + return; + } + + char tempBuf[bufferSizes]; + + // Field List. + Vector fields; + + // Fetch valid field-list flag. + bool validFieldList = ( fieldList != NULL ); + + // Did we specify a fields list? + if ( validFieldList ) + { + // Yes, so calculate field Count. + const U32 fieldCount = StringUnit::getUnitCount( fieldList, " \t\n" ); + + // Iterate fields... + for ( U32 fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex ) + { + // Copy string element. + dStrcpy( tempBuf, StringUnit::getUnit( fieldList, fieldIndex, " \t\n" ) ); + + // Store field. + fields.push_back( StringTable->insert( tempBuf ) ); + } + } + + // Destroy Fields. + destroyFields(); + + // Fetch Field List. + const AbstractClassRep::FieldList& staticFields = pSimObject->getFieldList(); + + // Iterate fields. + for( U32 fieldIndex = 0; fieldIndex < staticFields.size(); ++fieldIndex ) + { + // Fetch Field. + const AbstractClassRep::Field& staticField = staticFields[fieldIndex]; + + // Standard Field? + if ( staticField.type != AbstractClassRep::StartGroupFieldType && + staticField.type != AbstractClassRep::EndGroupFieldType && + staticField.type != AbstractClassRep::DeprecatedFieldType ) + { + // Set field-specified flag. + bool fieldSpecified = !validFieldList; + + // Did we specify a fields list? + if ( validFieldList ) + { + // Yes, so is this field name selected? + + // Iterate fields... + for ( U32 fieldIndex = 0; fieldIndex < fields.size(); ++fieldIndex ) + { + // Field selected? + if ( staticField.pFieldname == fields[fieldIndex] ) + { + // Yes, so flag as such. + fieldSpecified = true; + break; + } + } + } + + // Field specified? + if ( fieldSpecified ) + { + if ( staticField.elementCount <= 1 ) + { + for( U32 fieldElement = 0; S32(fieldElement) < staticField.elementCount; ++fieldElement ) + { + // Fetch Field Value. + const char* fieldValue = (staticField.getDataFn)( pSimObject, Con::getData(staticField.type, (void *) (((const char *)pSimObject) + staticField.offset), fieldElement, staticField.table, staticField.flag) ); + + // Field Value? + if ( fieldValue ) + { + // Yes. + dSprintf( tempBuf, sizeof(tempBuf), INTERNAL_FIELD_PREFIX"%s", staticField.pFieldname ); + + // Fetch Dynamic-Field Dictionary. + SimFieldDictionary* pFieldDictionary = getFieldDictionary(); + + // Set field value. + if ( !pFieldDictionary ) + { + setDataField( StringTable->insert( tempBuf ), NULL, fieldValue ); + } + else + { + pFieldDictionary->setFieldValue( StringTable->insert( tempBuf ), fieldValue ); + } + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Paste Fields. +//----------------------------------------------------------------------------- +ConsoleMethod(FieldBrushObject, pasteFields, void, 3, 3, "(simObject) Paste copied static-fields to selected object./\n" + "@param simObject Object to paste static-fields to.\n" + "@return No return value.") +{ + // Fetch selected object. + SimObject* pSimObject = dynamic_cast( Sim::findObject( argv[2] ) ); + + // Valid object? + if ( pSimObject == NULL ) + { + // No, so warn. + Con::warnf("FieldBrushObject::pasteFields() - Invalid SimObject!"); + return; + } + + // Paste Fields. + object->pasteFields( pSimObject ); +} +// Paste Fields. +void FieldBrushObject::pasteFields( SimObject* pSimObject ) +{ + // FieldBrushObject class? + if ( dStrcmp(pSimObject->getClassName(), getClassName()) == 0 ) + { + // Yes, so warn. + Con::warnf("FieldBrushObject::pasteFields() - Cannot paste FieldBrushObject objects!"); + return; + } + + // Fetch Dynamic-Field Dictionary. + SimFieldDictionary* pFieldDictionary = getFieldDictionary(); + + // Any Field Dictionary? + if ( pFieldDictionary == NULL ) + { + // No, so we're done. + return; + } + + // Force modification of static-fields on target object! + pSimObject->setModStaticFields( true ); + + // Iterate fields. + for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr ) + { + // Fetch Field Entry. + SimFieldDictionary::Entry* fieldEntry = *itr; + + // Internal Field? + char* pInternalField = dStrstr( fieldEntry->slotName, INTERNAL_FIELD_PREFIX ); + if ( pInternalField == fieldEntry->slotName ) + { + // Yes, so skip the prefix. + pInternalField += dStrlen(INTERNAL_FIELD_PREFIX); + + // Is this a static-field on the target object? + // NOTE:- We're doing this so we don't end-up creating a dynamic-field if it isn't present. + + // Fetch Field List. + const AbstractClassRep::FieldList& staticFields = pSimObject->getFieldList(); + + // Iterate fields. + for( U32 fieldIndex = 0; fieldIndex < staticFields.size(); ++fieldIndex ) + { + // Fetch Field. + const AbstractClassRep::Field& staticField = staticFields[fieldIndex]; + + // Standard Field? + if ( staticField.type != AbstractClassRep::StartGroupFieldType && + staticField.type != AbstractClassRep::EndGroupFieldType && + staticField.type != AbstractClassRep::DeprecatedFieldType ) + { + // Target field? + if ( dStrcmp(staticField.pFieldname, pInternalField) == 0 ) + { + // Yes, so set data. + pSimObject->setDataField( staticField.pFieldname, NULL, fieldEntry->value ); + } + } + } + } + } +} diff --git a/console/fieldBrushObject.h b/console/fieldBrushObject.h new file mode 100644 index 0000000..4ba48da --- /dev/null +++ b/console/fieldBrushObject.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FIELDBRUSHOBJECT_H_ +#define _FIELDBRUSHOBJECT_H_ + +#ifndef _CONSOLEINTERNAL_H_ +#include "console/consoleInternal.h" +#endif + +#ifndef CORE_TDICTIONARY_H +#include "core/util/tDictionary.h" +#endif + +//----------------------------------------------------------------------------- +// Field Brush Object. +//----------------------------------------------------------------------------- + +/// FieldBrushObject for static-field copying/pasting. +/// +class FieldBrushObject : public SimObject +{ +private: + typedef SimObject Parent; + + // Destroy Fields. + void destroyFields( void ); + + StringTableEntry mDescription; ///< Description. + StringTableEntry mSortName; ///< Sort Name. + +public: + FieldBrushObject(); + + void copyFields( SimObject* pSimObject, const char* fieldList ); + void pasteFields( SimObject* pSimObject ); + + static bool setDescription(void* obj, const char* data) { static_cast(obj)->setDescription(data); return false; }; + void setDescription( const char* description ) { mDescription = StringTable->insert(description); } + StringTableEntry getDescription(void) const { return mDescription; } + + static bool setSortName(void* obj, const char* data) { static_cast(obj)->setSortName(data); return false; }; + void setSortName( const char* sortName ) { mSortName = StringTable->insert(sortName); } + StringTableEntry getSortName(void) const { return mSortName; } + + static void initPersistFields(); ///< Persist Fields. + virtual void onRemove(); ///< Called when the object is removed from the sim. + + DECLARE_CONOBJECT(FieldBrushObject); +}; + +#endif \ No newline at end of file diff --git a/console/fileSystemFunctions.cpp b/console/fileSystemFunctions.cpp new file mode 100644 index 0000000..48b8ba3 --- /dev/null +++ b/console/fileSystemFunctions.cpp @@ -0,0 +1,581 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "core/stream/fileStream.h" +#include "console/compiler.h" +#include "platform/event.h" +#include "platform/platformInput.h" +#include "torqueConfig.h" +#include "core/frameAllocator.h" + +// Buffer for expanding script filenames. +static char sgScriptFilenameBuffer[1024]; + +//-------------------------------------- Helper Functions +static void forwardslash(char *str) +{ + while(*str) + { + if(*str == '\\') + *str = '/'; + str++; + } +} + +//---------------------------------------------------------------- +ConsoleFunctionGroupBegin( FileSystem, "Functions allowing you to search for files, read them, write them, and access their properties."); + +static Vector sgFindFilesResults; +static U32 sgFindFilesPos = 0; + +static S32 buildFileList(const char* pattern, bool recurse, bool multiMatch) +{ + static const String sSlash( "/" ); + + sgFindFilesResults.clear(); + + String sPattern(Torque::Path::CleanSeparators(pattern)); + if(sPattern.isEmpty()) + { + Con::errorf("findFirstFile() requires a search pattern"); + return -1; + } + + if(!Con::expandScriptFilename(sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), sPattern.c_str())) + { + Con::errorf("findFirstFile() given initial directory cannot be expanded: '%s'", pattern); + return -1; + } + sPattern = String::ToString(sgScriptFilenameBuffer); + + String::SizeType slashPos = sPattern.find('/', 0, String::Right); +// if(slashPos == String::NPos) +// { +// Con::errorf("findFirstFile() missing search directory or expression: '%s'", sPattern.c_str()); +// return -1; +// } + + // Build the initial search path + Torque::Path givenPath(Torque::Path::CompressPath(sPattern)); + givenPath.setFileName("*"); + givenPath.setExtension("*"); + + if(givenPath.getPath().length() > 0 && givenPath.getPath().find('*', 0, String::Right) == givenPath.getPath().length()-1) + { + // Deal with legacy searches of the form '*/*.*' + String suspectPath = givenPath.getPath(); + String::SizeType newLen = suspectPath.length()-1; + if(newLen > 0 && suspectPath.find('/', 0, String::Right) == suspectPath.length()-2) + { + --newLen; + } + givenPath.setPath(suspectPath.substr(0, newLen)); + } + + Torque::FS::FileSystemRef fs = Torque::FS::GetFileSystem(givenPath); + //Torque::Path path = fs->mapTo(givenPath); + Torque::Path path = givenPath; + + // Make sure that we have a root so the correct file system can be determined when using zips + if(givenPath.isRelative()) + path = Torque::Path::Join(Torque::FS::GetCwd(), '/', givenPath); + + path.setFileName(String::EmptyString); + path.setExtension(String::EmptyString); + if(!Torque::FS::IsDirectory(path)) + { + Con::errorf("findFirstFile() invalid initial search directory: '%s'", path.getFullPath().c_str()); + return -1; + } + + // Build the search expression + const String expression(slashPos != String::NPos ? sPattern.substr(slashPos+1) : sPattern); + if(expression.isEmpty()) + { + Con::errorf("findFirstFile() requires a search expression: '%s'", sPattern.c_str()); + return -1; + } + + S32 results = Torque::FS::FindByPattern(path, expression, recurse, sgFindFilesResults, multiMatch ); + if(givenPath.isRelative() && results > 0) + { + // Strip the CWD out of the returned paths + // MakeRelativePath() returns incorrect results (it adds a leading ..) so doing this the dirty way + const String cwd = Torque::FS::GetCwd().getFullPath(); + for(S32 i = 0;i < sgFindFilesResults.size();++i) + { + String str = sgFindFilesResults[i]; + if(str.compare(cwd, cwd.length(), String::NoCase) == 0) + str = str.substr(cwd.length()); + sgFindFilesResults[i] = str; + } + } + return results; +} + +ConsoleFunction(findFirstFile, const char *, 2, 3, "(string pattern [, bool recurse]) Returns the first file in the directory system matching the given pattern.") +{ + bool recurse = true; + if(argc == 3) + { + recurse = dAtob(argv[2]); + } + + S32 numResults = buildFileList(argv[1], recurse, false); + + // For Debugging + //for ( S32 i = 0; i < sgFindFilesResults.size(); i++ ) + // Con::printf( " [%i] [%s]", i, sgFindFilesResults[i].c_str() ); + + sgFindFilesPos = 1; + + if(numResults < 0) + { + Con::errorf("findFirstFile() search directory not found: '%s'", argv[1]); + return String(); + } + + return numResults ? sgFindFilesResults[0] : String(); +} + +ConsoleFunction(findNextFile, const char*, 1, 2, "([string pattern]) Returns the next file matching a search begun in findFirstFile.") +{ + if ( sgFindFilesPos + 1 > sgFindFilesResults.size() ) + return String(); + + return sgFindFilesResults[sgFindFilesPos++]; +} + +ConsoleFunction(getFileCount, S32, 2, 3, "(string pattern [, bool recurse]) Returns the number of files in the directory tree that match the given pattern") +{ + bool recurse = true; + if(argc == 3) + { + recurse = dAtob(argv[2]); + } + + S32 numResults = buildFileList(argv[1], recurse, false); + + if(numResults < 0) + { + return 0; + } + + return numResults; +} + +ConsoleFunction(findFirstFileMultiExpr, const char *, 2, 3, "(string pattern [, bool recurse]) Returns the first file in the directory system matching the given pattern.") +{ + bool recurse = true; + if(argc == 3) + { + recurse = dAtob(argv[2]); + } + + S32 numResults = buildFileList(argv[1], recurse, true); + + // For Debugging + //for ( S32 i = 0; i < sgFindFilesResults.size(); i++ ) + // Con::printf( " [%i] [%s]", i, sgFindFilesResults[i].c_str() ); + + sgFindFilesPos = 1; + + if(numResults < 0) + { + Con::errorf("findFirstFile() search directory not found: '%s'", argv[1]); + return String(); + } + + return numResults ? sgFindFilesResults[0] : String(); +} + +ConsoleFunction(findNextFileMultiExpr, const char*, 1, 2, "([string pattern]) Returns the next file matching a search begun in findFirstFile.") +{ + if ( sgFindFilesPos + 1 > sgFindFilesResults.size() ) + return String(); + + return sgFindFilesResults[sgFindFilesPos++]; +} + +ConsoleFunction(getFileCountMultiExpr, S32, 2, 3, "(string pattern [, bool recurse]) Returns the number of files in the directory tree that match the given pattern") +{ + bool recurse = true; + if(argc == 3) + { + recurse = dAtob(argv[2]); + } + + S32 numResults = buildFileList(argv[1], recurse, true); + + if(numResults < 0) + { + return 0; + } + + return numResults; +} + +ConsoleFunction(getFileCRC, S32, 2, 2, "getFileCRC(filename)") +{ + TORQUE_UNUSED(argc); + String filename(Torque::Path::CleanSeparators(argv[1])); + Con::expandScriptFilename(sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), filename.c_str()); + + Torque::Path givenPath(Torque::Path::CompressPath(sgScriptFilenameBuffer)); + Torque::FS::FileNodeRef fileRef = Torque::FS::GetFileNode( givenPath ); + + if ( fileRef == NULL ) + { + Con::errorf("getFileCRC() - could not access file: [%s]", givenPath.getFullPath().c_str() ); + return -1; + } + + return fileRef->getChecksum(); +} + +ConsoleFunction(isFile, bool, 2, 2, "isFile(fileName)") +{ + TORQUE_UNUSED(argc); + String filename(Torque::Path::CleanSeparators(argv[1])); + Con::expandScriptFilename(sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), filename.c_str()); + + Torque::Path givenPath(Torque::Path::CompressPath(sgScriptFilenameBuffer)); + return Torque::FS::IsFile(givenPath); +} + +ConsoleFunction( IsDirectory, bool, 2, 2, "( string: directory of form \"foo/bar\", do not include trailing /, case insensitive, directory must have files in it if you expect the directory to be in a zip )" ) +{ + TORQUE_UNUSED(argc); + String dir(Torque::Path::CleanSeparators(argv[1])); + Con::expandScriptFilename(sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), dir.c_str()); + + Torque::Path givenPath(Torque::Path::CompressPath(sgScriptFilenameBuffer)); + return Torque::FS::IsDirectory( givenPath ); +} + +ConsoleFunction(isWriteableFileName, bool, 2, 2, "isWriteableFileName(fileName)") +{ + TORQUE_UNUSED(argc); + String filename(Torque::Path::CleanSeparators(argv[1])); + Con::expandScriptFilename(sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), filename.c_str()); + + Torque::Path givenPath(Torque::Path::CompressPath(sgScriptFilenameBuffer)); + Torque::FS::FileSystemRef fs = Torque::FS::GetFileSystem(givenPath); + Torque::Path path = fs->mapTo(givenPath); + + return !Torque::FS::IsReadOnly(path); +} + +ConsoleFunction(startFileChangeNotifications, void, 1, 1, "startFileChangeNotifications() - start watching resources for file changes" ) +{ + Torque::FS::StartFileChangeNotifications(); +} + +ConsoleFunction(stopFileChangeNotifications, void, 1, 1, "stopFileChangeNotifications() - stop watching resources for file changes" ) +{ + Torque::FS::StopFileChangeNotifications(); +} + + +ConsoleFunction(getDirectoryList, const char*, 2, 3, "getDirectoryList(%path, %depth)") +{ + // Grab the full path. + char path[1024]; + Platform::makeFullPathName(dStrcmp(argv[1], "/") == 0 ? "" : argv[1], path, sizeof(path)); + + //dSprintf(path, 511, "%s/%s", Platform::getWorkingDirectory(), argv[1]); + + // Append a trailing backslash if it's not present already. + if (path[dStrlen(path) - 1] != '/') + { + S32 pos = dStrlen(path); + path[pos] = '/'; + path[pos + 1] = '\0'; + } + + // Grab the depth to search. + S32 depth = 0; + if (argc > 2) + depth = dAtoi(argv[2]); + + // Dump the directories. + Vector directories; + Platform::dumpDirectories(path, directories, depth, true); + + if( directories.empty() ) + return ""; + + // Grab the required buffer length. + S32 length = 0; + + for (S32 i = 0; i < directories.size(); i++) + length += dStrlen(directories[i]) + 1; + + // Get a return buffer. + char* buffer = Con::getReturnBuffer(length); + char* p = buffer; + + // Copy the directory names to the buffer. + for (S32 i = 0; i < directories.size(); i++) + { + dStrcpy(p, directories[i]); + p += dStrlen(directories[i]); + // Tab separated. + p[0] = '\t'; + p++; + } + p--; + p[0] = '\0'; + + return buffer; +} + +ConsoleFunction(fileSize, S32, 2, 2, "fileSize(fileName) returns filesize or -1 if no file") +{ + TORQUE_UNUSED(argc); + Con::expandScriptFilename(sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), argv[1]); + return Platform::getFileSize( sgScriptFilenameBuffer ); +} + +ConsoleFunction( fileModifiedTime, const char*, 2, 2, "fileModifiedTime( string fileName )\n" + "Returns a platform specific formatted string with the last modified time for the file." ) +{ + Con::expandScriptFilename(sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), argv[1]); + + FileTime ft = {0}; + Platform::getFileTimes( sgScriptFilenameBuffer, NULL, &ft ); + + Platform::LocalTime lt = {0}; + Platform::fileToLocalTime( ft, < ); + + String fileStr = Platform::localTimeToString( lt ); + + char *buffer = Con::getReturnBuffer( fileStr.size() ); + dStrcpy( buffer, fileStr ); + + return buffer; +} + +ConsoleFunction( fileCreatedTime, const char*, 2, 2, "fileCreatedTime( string fileName )\n" + "Returns a platform specific formatted string with the creation time for the file." ) +{ + Con::expandScriptFilename( sgScriptFilenameBuffer, sizeof(sgScriptFilenameBuffer), argv[1] ); + + FileTime ft = {0}; + Platform::getFileTimes( sgScriptFilenameBuffer, &ft, NULL ); + + Platform::LocalTime lt = {0}; + Platform::fileToLocalTime( ft, < ); + + String fileStr = Platform::localTimeToString( lt ); + + char *buffer = Con::getReturnBuffer( fileStr.size() ); + dStrcpy( buffer, fileStr ); + + return buffer; +} + +ConsoleFunction(fileDelete, bool, 2,2, "fileDelete('path')") +{ + static char fileName[1024]; + static char sandboxFileName[1024]; + + Con::expandScriptFilename( fileName, sizeof( fileName ), argv[1] ); + Platform::makeFullPathName(fileName, sandboxFileName, sizeof(sandboxFileName)); + + return dFileDelete(sandboxFileName); +} + + +//---------------------------------------------------------------- + +ConsoleFunction(fileExt, const char *, 2, 2, "fileExt(fileName)") +{ + TORQUE_UNUSED(argc); + const char *ret = dStrrchr(argv[1], '.'); + if(ret) + return ret; + return ""; +} + +ConsoleFunction(fileBase, const char *, 2, 2, "fileBase(fileName)") +{ + + S32 pathLen = dStrlen( argv[1] ); + FrameTemp szPathCopy( pathLen + 1); + + dStrcpy( szPathCopy, argv[1] ); + forwardslash( szPathCopy ); + + TORQUE_UNUSED(argc); + const char *path = dStrrchr(szPathCopy, '/'); + if(!path) + path = szPathCopy; + else + path++; + char *ret = Con::getReturnBuffer(dStrlen(path) + 1); + dStrcpy(ret, path); + char *ext = dStrrchr(ret, '.'); + if(ext) + *ext = 0; + return ret; +} + +ConsoleFunction(fileName, const char *, 2, 2, "fileName(filePathName)") +{ + S32 pathLen = dStrlen( argv[1] ); + FrameTemp szPathCopy( pathLen + 1); + + dStrcpy( szPathCopy, argv[1] ); + forwardslash( szPathCopy ); + + TORQUE_UNUSED(argc); + const char *name = dStrrchr(szPathCopy, '/'); + if(!name) + name = szPathCopy; + else + name++; + char *ret = Con::getReturnBuffer(dStrlen(name)); + dStrcpy(ret, name); + return ret; +} + +ConsoleFunction(filePath, const char *, 2, 2, "filePath(fileName)") +{ + S32 pathLen = dStrlen( argv[1] ); + FrameTemp szPathCopy( pathLen + 1); + + dStrcpy( szPathCopy, argv[1] ); + forwardslash( szPathCopy ); + + TORQUE_UNUSED(argc); + const char *path = dStrrchr(szPathCopy, '/'); + if(!path) + return ""; + U32 len = path - (char*)szPathCopy; + char *ret = Con::getReturnBuffer(len + 1); + dStrncpy(ret, szPathCopy, len); + ret[len] = 0; + return ret; +} + +ConsoleFunction(getWorkingDirectory, const char *, 1, 1, "alias to getCurrentDirectory()") +{ + return Platform::getCurrentDirectory(); +} + +//----------------------------------------------------------------------------- + +// [tom, 5/1/2007] I changed these to be ordinary console functions as they +// are just string processing functions. They are needed by the 3D tools which +// are not currently built with TORQUE_TOOLS defined. + +ConsoleFunction(makeFullPath, const char *, 2, 3, "(string path, [string currentWorkingDir])") +{ + char *buf = Con::getReturnBuffer(512); + Platform::makeFullPathName(argv[1], buf, 512, argc > 2 ? argv[2] : NULL); + return buf; +} + +ConsoleFunction(makeRelativePath, const char *, 3, 3, "(string path, string to)") +{ + return Platform::makeRelativePathName(argv[1], argv[2]); +} + +ConsoleFunction(pathConcat, const char *, 3, 0, "(string path, string file1, [... fileN])") +{ + char *buf = Con::getReturnBuffer(1024); + char pathBuf[1024]; + dStrcpy(buf, argv[1]); + + // CodeReview [tom, 5/1/2007] I don't think this will work as expected with multiple file names + + for(S32 i = 2;i < argc;++i) + { + Platform::makeFullPathName(argv[i], pathBuf, 1024, buf); + dStrcpy(buf, pathBuf); + } + return buf; +} + +//----------------------------------------------------------------------------- + +ConsoleFunction(getExecutableName, const char *, 1, 1, "getExecutableName()") +{ + return Platform::getExecutableName(); +} + +ConsoleFunction(getMainDotCsDir, const char *, 1, 1, "getExecutableName()") +{ + return Platform::getMainDotCsDir(); +} + +//----------------------------------------------------------------------------- +// Tools Only Functions +//----------------------------------------------------------------------------- + +#ifdef TORQUE_TOOLS + +ConsoleToolFunction(openFolder, void, 2 ,2,"openFolder(%path);") +{ + Platform::openFolder( argv[1] ); +} + +ConsoleToolFunction(openFile, void, 2,2,"openFile(%path);") +{ + Platform::openFile( argv[1] ); +} + +ConsoleToolFunction(pathCopy, bool, 3, 4, "pathCopy(fromFile, toFile [, nooverwrite = true])") +{ + bool nooverwrite = true; + + if( argc > 3 ) + nooverwrite = dAtob( argv[3] ); + + static char fromFile[1024]; + static char toFile[1024]; + + static char qualifiedFromFile[1024]; + static char qualifiedToFile[1024]; + + Con::expandScriptFilename( fromFile, sizeof( fromFile ), argv[1] ); + Con::expandScriptFilename( toFile, sizeof( toFile ), argv[2] ); + + Platform::makeFullPathName(fromFile, qualifiedFromFile, sizeof(qualifiedFromFile)); + Platform::makeFullPathName(toFile, qualifiedToFile, sizeof(qualifiedToFile)); + + return dPathCopy( qualifiedFromFile, qualifiedToFile, nooverwrite ); +} + +ConsoleToolFunction(getCurrentDirectory, const char *, 1, 1, "getCurrentDirectory()") +{ + return Platform::getCurrentDirectory(); +} + +ConsoleToolFunction( setCurrentDirectory, bool, 2, 2, "setCurrentDirectory(absolutePathName)" ) +{ + return Platform::setCurrentDirectory( StringTable->insert( argv[1] ) ); + +} + +ConsoleToolFunction( createPath, bool, 2,2, "createPath(\"file name or path name\"); creates the path or path to the file name") +{ + static char pathName[1024]; + + Con::expandScriptFilename( pathName, sizeof( pathName ), argv[1] ); + + return Platform::createPath( pathName ); +} + +#endif // TORQUE_TOOLS + +//----------------------------------------------------------------------------- + +ConsoleFunctionGroupEnd( FileSystem ); diff --git a/console/generateCompiler.bat b/console/generateCompiler.bat new file mode 100644 index 0000000..5dba9ed --- /dev/null +++ b/console/generateCompiler.bat @@ -0,0 +1,3 @@ +@echo off +call bison.bat CMD CMDgram.c CMDgram.y . CMDgram.cpp +..\..\bin\flex\flex -PCMD -oCMDscan.cpp CMDscan.l diff --git a/console/persistenceManager.cpp b/console/persistenceManager.cpp new file mode 100644 index 0000000..ee1d5ea --- /dev/null +++ b/console/persistenceManager.cpp @@ -0,0 +1,2248 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "persistenceManager.h" +#include "console/simSet.h" +#include "console/consoleTypes.h" +#include "core/stream/fileStream.h" +#include "gui/core/guiTypes.h" +#include "materials/customMaterialDefinition.h" + +// Helper function +char* packU32List(Vector& values) +{ + // First determine how big the buffer needs to be + U32 size = 0; + + for (U32 i = 0; i < values.size(); i++) + { + if (values[i] < 10) + size += 2; + else if (values[i] < 100) + size += 3; + else if (values[i] < 1000) + size += 4; + else if (values[i] < 10000) + size += 5; + else if (values[i] < 100000) + size += 6; + else if (values[i] < 1000000) + size += 7; + else if (values[i] < 10000000) + size += 8; + else if (values[i] < 100000000) + size += 9; + else if (values[i] < 1000000000) + size += 10; + else + Con::errorf("This number is far too high: %d", values[i]); + } + + // Now create our return buffer + char* buff = Con::getReturnBuffer(size + 1); + + char* walk = buff; + + for (U32 i = 0; i < values.size(); i++) + { + U32 valueSize = 0; + + if (values[i] < 10) + valueSize += 2; + else if (values[i] < 100) + valueSize += 3; + else if (values[i] < 1000) + valueSize += 4; + else if (values[i] < 10000) + valueSize += 5; + else if (values[i] < 100000) + valueSize += 6; + else if (values[i] < 1000000) + valueSize += 7; + else if (values[i] < 10000000) + valueSize += 8; + else if (values[i] < 100000000) + valueSize += 9; + else if (values[i] < 1000000000) + valueSize += 10; + + if (valueSize > 0) + dSprintf(walk, valueSize + 1, "%d ", values[i]); + + walk += valueSize; + + *walk = 0; + } + + // trim off the final space + if (walk && *walk == ' ') + *(walk - 1) = 0; + + return buff; +} + +IMPLEMENT_CONOBJECT(PersistenceManager); + +PersistenceManager::PersistenceManager() +{ + mCurrentObject = NULL; + mCurrentFile = NULL; + + VECTOR_SET_ASSOCIATION(mLineBuffer); + + mLineBuffer.reserve(2048); +} + +PersistenceManager::~PersistenceManager() +{ + mDirtyObjects.clear(); +} + +bool PersistenceManager::onAdd() +{ + if (!Parent::onAdd()) + return false; + + return true; +} + +void PersistenceManager::onRemove() +{ + Parent::onRemove(); +} + +void PersistenceManager::clearLineBuffer() +{ + for (U32 i = 0; i < mLineBuffer.size(); i++) + SAFE_DELETE_ARRAY(mLineBuffer[i]); + + mLineBuffer.clear(); +} + +void PersistenceManager::deleteObject(ParsedObject* object) +{ + if (object) + { + // Clear up used property memory + for (U32 j = 0; j < object->properties.size(); j++) + { + ParsedProperty& prop = object->properties[j]; + + if (prop.value) + SAFE_DELETE(prop.value); + } + + object->properties.clear(); + + // Delete the parsed object + SAFE_DELETE(object); + } +} + +void PersistenceManager::clearObjects() +{ + // Clean up the object buffer + for (U32 i = 0; i < mObjectBuffer.size(); i++) + deleteObject(mObjectBuffer[i]); + + mObjectBuffer.clear(); + + // We shouldn't have anything in the object stack + // but let's clean it up just in case + // Clean up the object buffer + for (U32 i = 0; i < mObjectStack.size(); i++) + deleteObject(mObjectStack[i]); + + mObjectStack.clear(); + + // Finally make sure there isn't a current object + deleteObject(mCurrentObject); +} + +void PersistenceManager::clearFileData() +{ + // Clear the active file name + if (mCurrentFile) + SAFE_DELETE(mCurrentFile); + + // Clear the file objects + clearObjects(); + + // Clear the line buffer + clearLineBuffer(); + + // Clear the tokenizer data + mParser.clear(); +} + +void PersistenceManager::clearAll() +{ + // Clear the file data in case it hasn't cleared yet + clearFileData(); + + // Clear the dirty object list + mDirtyObjects.clear(); + + // Clear the remove field list + mRemoveFields.clear(); +} + +bool PersistenceManager::readFile(const char* fileName) +{ + // Clear our previous file buffers just in + // case saveDirtyFile() didn't catch it + clearFileData(); + + // Handle an object writing out to a new file + if ( !Torque::FS::IsFile( fileName ) ) + { + // Set our current file + mCurrentFile = dStrdup(fileName); + + return true; + } + + // Try to open the file + FileStream stream; + stream.open( fileName, Torque::FS::File::Read ); + + if ( stream.getStatus() != Stream::Ok ) + { + Con::errorf("PersistenceManager::readFile() - Failed to open %s", fileName); + + return false; + } + + // The file is good so read it in + mCurrentFile = dStrdup(fileName); + + while(stream.getStatus() != Stream::EOS) + { + U8* buffer = new U8[2048]; + dMemset(buffer, 0, 2048); + + stream.readLine(buffer, 2048); + + mLineBuffer.push_back((const char*)buffer); + } + + // Because of the way that writeLine() works we need to + // make sure we don't have an empty last line or else + // we will get an extra line break + if (mLineBuffer.size() > 0) + { + if (mLineBuffer.last() && mLineBuffer.last()[0] == 0) + { + SAFE_DELETE_ARRAY(mLineBuffer.last()); + + mLineBuffer.pop_back(); + } + } + + stream.close(); + + //Con::printf("Successfully opened and read %s", mCurrentFile); + + return true; +} + +void PersistenceManager::killObject() +{ + // Don't save this object + SAFE_DELETE(mCurrentObject); + + // If there is an object in the stack restore it + if (mObjectStack.size() > 0) + { + mCurrentObject = mObjectStack.last(); + mObjectStack.pop_back(); + } +} + +void PersistenceManager::saveObject() +{ + // Now that we have all of the data attempt to + // find the corresponding SimObject + mCurrentObject->simObject = Sim::findObject(mCurrentFile, mCurrentObject->endLine + 1); + + // Save this object + mObjectBuffer.push_back(mCurrentObject); + + mCurrentObject = NULL; + + // If there is an object in the stack restore it + if (mObjectStack.size() > 0) + { + mCurrentObject = mObjectStack.last(); + mObjectStack.pop_back(); + } +} + +void PersistenceManager::parseObject() +{ + // We *should* already be in position but just in case... + if (!mParser.tokenICmp("new") && + !mParser.tokenICmp("singleton") && + !mParser.tokenICmp("datablock")) + { + Con::errorf("PersistenceManager::parseObject() - handed a position that doesn't point to an object \ + creation keyword (new, singleton, datablock)"); + + return; + } + + // If there is an object already being parsed then + // push it into the stack to finish later + if (mCurrentObject) + mObjectStack.push_back(mCurrentObject); + + mCurrentObject = new ParsedObject; + + //// If this object declaration is being assigned to a variable then + //// consider that the "start" of the declaration (otherwise we could + //// get a script compile error if we delete the object declaration) + mParser.regressToken(true); + + if (mParser.tokenICmp("=")) + { + // Ok, we are at an '='...back up to the beginning of that variable + mParser.regressToken(true); + + // Get the startLine and startPosition + mCurrentObject->startLine = mParser.getCurrentLine(); + mCurrentObject->startPosition = mParser.getTokenLineOffset(); + + // Advance back to the object declaration + mParser.advanceToken(true); + mParser.advanceToken(true); + } + else + { + // Advance back to the object declaration + mParser.advanceToken(true); + + // Get the startLine and startPosition + mCurrentObject->startLine = mParser.getCurrentLine(); + mCurrentObject->startPosition = mParser.getTokenLineOffset(); + } + + if (mObjectStack.size() > 0) + mCurrentObject->parentObject = mObjectStack.last(); + + // The next token should be the className + mCurrentObject->className = StringTable->insert(mParser.getNextToken()); + + // Advance to '(' + mParser.advanceToken(true); + + if (!mParser.tokenICmp("(")) + { + Con::errorf("PersistenceManager::parseObject() - badly formed object \ + declaration on line %d - was expecting a '(' character", mParser.getCurrentLine()); + + // Remove this object without saving it + killObject(); + + return; + } + + // The next token should either be the object name or ')' + mParser.advanceToken(true); + + if (mParser.tokenICmp(")")) + { + mCurrentObject->name = StringTable->insert(""); + + mCurrentObject->nameLine = mParser.getCurrentLine(); + mCurrentObject->namePosition = mParser.getTokenLineOffset(); + } + else + { + mCurrentObject->name = StringTable->insert(mParser.getToken()); + + mCurrentObject->nameLine = mParser.getCurrentLine(); + mCurrentObject->namePosition = mParser.getTokenLineOffset(); + + // Advance to either ')' or ':' + mParser.advanceToken(true); + + if (mParser.tokenICmp(":")) + { + // Advance past the object we are copying from + mParser.advanceToken(true); + + // Advance to ')' + mParser.advanceToken(true); + } + + if (!mParser.tokenICmp(")")) + { + Con::errorf("PersistenceManager::parseObject() - badly formed object \ + declaration on line %d - was expecting a ')' character", mParser.getCurrentLine()); + + // Remove this object without saving it + killObject(); + + return; + } + } + + // The next token should either be a ';' or a '{' + mParser.advanceToken(true); + + if (mParser.tokenICmp(";")) + { + // Save the end line number + mCurrentObject->endLine = mParser.getCurrentLine(); + + // Save the end position + mCurrentObject->endPosition = mParser.getTokenLineOffset(); + + // Flag this object as not having braces + mCurrentObject->hasBraces = false; + + saveObject(); // Object has no fields + + return; + } + else if (!mParser.tokenICmp("{")) + { + Con::errorf("PersistenceManager::parseObject() - badly formed object \ + declaration on line %d - was expecting a '{' character", mParser.getCurrentLine()); + + // Remove this object without saving it + killObject(); + + return; + } + + while (mParser.advanceToken(true)) + { + // Check for a subobject + if (mParser.tokenICmp("new") || + mParser.tokenICmp("singleton") || + mParser.tokenICmp("datablock")) + { + parseObject(); + } + + // Check to see if we have a property + if (mParser.tokenICmp("=")) + { + // Ok, we are at an '='...back up to find out + // what variable is getting assigned + mParser.regressToken(true); + + const char* variable = mParser.getToken(); + + if (variable && dStrlen(variable) > 0) + { + // See if it is a global or a local variable + if (variable[0] == '%' || variable[0] == '$') + { + // We ignore this variable and go + // back to our previous place + mParser.advanceToken(true); + } + // Could also potentially be a . + // assignment which we don't care about either + else if (dStrchr(variable, '.')) + { + // We ignore this variable and go + // back to our previous place + mParser.advanceToken(true); + } + // If we made it to here assume it is a variable + // for the current object + else + { + // Create our new property + mCurrentObject->properties.increment(); + + ParsedProperty& prop = mCurrentObject->properties.last(); + + // Check to see if this is an array variable + if (dStrlen(variable) > 3 && variable[dStrlen(variable) - 1] == ']') + { + // The last character is a ']' which *should* mean + // there is also a corresponding '[' + const char* arrayPosStart = dStrrchr(variable, '['); + + if (!arrayPosStart) + { + Con::errorf("PersistenceManager::parseObject() - error parsing array position - \ + was expecting a '[' character"); + } + else + { + // Parse the array position for the variable name + S32 arrayPos = -1; + + dSscanf(arrayPosStart, "[%d]", &arrayPos); + + // If we got a valid array position then set it + if (arrayPos > -1) + prop.arrayPos = arrayPos; + + // Trim off the [] from the variable name + char* variableName = dStrdup(variable); + variableName[arrayPosStart - variable] = 0; + + // Set the variable name to our new shortened name + variable = StringTable->insert(variableName, true); + + // Cleanup our variableName buffer + delete variableName; + } + } + + + // Set back the variable name + prop.name = StringTable->insert(variable, true); + + // Store the start position for this variable + prop.startLine = mParser.getCurrentLine(); + prop.startPosition = mParser.getTokenLineOffset(); + + // Advance back to the '=' + mParser.advanceToken(true); + + // Sanity check + if (!mParser.tokenICmp("=")) + Con::errorf("PersistenceManager::parseObject() - somehow we aren't \ + pointing at the expected '=' character"); + else + { + // The next token should be the value + // being assigned to the variable + mParser.advanceToken(true); + + const char* value = mParser.getToken(); + + // TODO: make sure this doesn't leak + prop.value = dStrdup(value); + + // Store the line number for this value + prop.valueLine = mParser.getCurrentLine(); + + // Store the values beginning position + prop.valuePosition = mParser.getTokenLineOffset(); + + // The next token should be a ';' + mParser.advanceToken(true); + + if (!mParser.tokenICmp(";")) + Con::errorf("PersistenceManager::parseObject() - badly formed variable \ + assignment on line %d - was expecting a ';' character", mParser.getCurrentLine()); + + // Store the end position for this variable + prop.endLine = mParser.getCurrentLine(); + prop.endPosition = mParser.getTokenLineOffset(); + } + } + } + } + + // Check for the end of the object declaration + if (mParser.tokenICmp("}")) + { + // See if the next token is a ';' + mParser.advanceToken(true); + + if (mParser.tokenICmp(";")) + { + // Save the end line number + mCurrentObject->endLine = mParser.getCurrentLine(); + + // Save the end position + mCurrentObject->endPosition = mParser.getTokenLineOffset(); + + saveObject(); + + break; + } + } + } +} + +bool PersistenceManager::parseFile(const char* fileName) +{ + // Read the file into the line buffer + if (!readFile(fileName)) + return false; + + // Load it into our Tokenizer parser + if (!mParser.openFile(fileName)) + { + // Handle an object writing out to a new file + if ( !Torque::FS::IsFile( fileName ) ) + return true; + + return false; + } + + // Set our reserved "single" tokens + mParser.setSingleTokens("(){};=:"); + + // Search object declarations + while (mParser.advanceToken(true)) + { + if (mParser.tokenICmp("new") || + mParser.tokenICmp("singleton") || + mParser.tokenICmp("datablock")) + { + parseObject(); + } + } + + // If we had an object that didn't end properly + // then we could have objects on the stack + while (mCurrentObject) + saveObject(); + + //Con::errorf("Parsed Results:"); + + //for (U32 i = 0; i < mObjectBuffer.size(); i++) + //{ + // ParsedObject* parsedObject = mObjectBuffer[i]; + + // Con::warnf(" mObjectBuffer[%d]:", i); + // Con::warnf(" name = %s", parsedObject->name); + // Con::warnf(" className = %s", parsedObject->className); + // Con::warnf(" startLine = %d", parsedObject->startLine + 1); + // Con::warnf(" endLine = %d", parsedObject->endLine + 1); + + // //if (mObjectBuffer[i]->properties.size() > 0) + // //{ + // // Con::warnf(" properties:"); + // // for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++) + // // Con::warnf(" %s = %s;", mObjectBuffer[i]->properties[j].name, + // // mObjectBuffer[i]->properties[j].value); + // //} + + // if (!parsedObject->simObject.isNull()) + // { + // SimObject* simObject = parsedObject->simObject; + + // Con::warnf(" SimObject(%s) %d:", simObject->getName(), simObject->getId()); + // Con::warnf(" declaration line = %d", simObject->getDeclarationLine()); + // } + //} + + return true; +} + +S32 PersistenceManager::getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos) +{ + S32 propertyIndex = -1; + + if (!parsedObject) + return propertyIndex; + + for (U32 i = 0; i < parsedObject->properties.size(); i++) + { + if (dStricmp(fieldName, parsedObject->properties[i].name) == 0 && + parsedObject->properties[i].arrayPos == arrayPos) + { + propertyIndex = i; + break; + } + } + + return propertyIndex; +} + +char* PersistenceManager::getObjectIndent(ParsedObject* object) +{ + char* indent = Con::getReturnBuffer(2048); + indent[0] = 0; + + if (!object) + return indent; + + if (object->startLine < 0 || object->startLine >= mLineBuffer.size()) + return indent; + + const char* line = mLineBuffer[object->startLine]; + + if (line) + { + const char* nonSpace = line; + + U32 strLen = dStrlen(line); + + for (U32 i = 0; i < strLen; i++) + { + if (*nonSpace != ' ') + break; + + nonSpace++; + } + + dStrncpy(indent, line, nonSpace - line); + + indent[nonSpace - line] = 0; + } + + return indent; +} + +void PersistenceManager::updatePositions(U32 lineNumber, U32 startPos, S32 diff) +{ + if (diff == 0) + return; + + for (U32 i = 0; i < mObjectBuffer.size(); i++) + { + ParsedObject* object = mObjectBuffer[i]; + + if (object->nameLine == lineNumber && object->namePosition > startPos) + object->namePosition += diff; + + if (object->endLine == lineNumber && object->endPosition > startPos) + object->endPosition += diff; + + if (lineNumber >= object->startLine && lineNumber <= object->endLine) + { + for (U32 j = 0; j < object->properties.size(); j++) + { + ParsedProperty& prop = object->properties[j]; + + S32 propStartPos = prop.startPosition; + S32 endPos = prop.endPosition; + S32 valuePos = prop.valuePosition; + + if (lineNumber == prop.startLine && propStartPos > startPos) + { + propStartPos += diff; + + if (propStartPos < 0) + propStartPos = 0; + + prop.startPosition = valuePos; + } + if (lineNumber == prop.endLine && endPos > startPos) + { + endPos += diff; + + if (endPos < 0) + endPos = 0; + + prop.endPosition = endPos; + } + if (lineNumber == prop.valueLine && valuePos > startPos) + { + valuePos += diff; + + if (valuePos < 0) + valuePos = 0; + + prop.valuePosition = valuePos; + } + } + } + } +} + +void PersistenceManager::updateLineOffsets(U32 startLine, S32 diff, ParsedObject* skipObject) +{ + if (diff == 0) + return; + + if (startLine >= mLineBuffer.size()) + return; + + if (startLine + diff >= mLineBuffer.size()) + return; + + // Make sure we don't double offset a SimObject's + // declaration line + SimObjectList updated; + + if (skipObject && !skipObject->simObject.isNull()) + updated.push_back_unique(skipObject->simObject); + + for (U32 i = 0; i < mObjectBuffer.size(); i++) + { + ParsedObject* object = mObjectBuffer[i]; + + // See if this is the skipObject + if (skipObject && skipObject == object) + continue; + + // We can safely ignore objects that + // came earlier in the file + if (object->endLine < startLine) + continue; + + if (object->startLine >= startLine) + object->startLine += diff; + + if (object->nameLine >= startLine) + object->nameLine += diff; + + for (U32 j = 0; j < object->properties.size(); j++) + { + if (object->properties[j].startLine >= startLine) + object->properties[j].startLine += diff; + if (object->properties[j].endLine >= startLine) + object->properties[j].endLine += diff; + if (object->properties[j].valueLine >= startLine) + object->properties[j].valueLine += diff; + } + + if (object->endLine >= startLine) + object->endLine += diff; + + if (!object->simObject.isNull() && + object->simObject->getDeclarationLine() > startLine) + { + // Check for already updated SimObject's + U32 currSize = updated.size(); + updated.push_back_unique(object->simObject); + + if (updated.size() == currSize) + continue; + + S32 newDeclLine = object->simObject->getDeclarationLine() + diff; + + if (newDeclLine < 0) + newDeclLine = 0; + + object->simObject->setDeclarationLine(newDeclLine); + } + } +} + +PersistenceManager::ParsedObject* PersistenceManager::findParentObject(SimObject* object, ParsedObject* parentObject) +{ + ParsedObject* ret = NULL; + + if (!object) + return ret; + + // First test for the SimGroup it belongs to + ret = findParsedObject(object->getGroup(), parentObject); + + if (ret) + return ret; + + // TODO: Test all of the SimSet's that this object belongs to + + return ret; +} + +PersistenceManager::ParsedObject* PersistenceManager::findParsedObject(SimObject* object, ParsedObject* parentObject) +{ + if (!object) + return NULL; + + // See if our object belongs to a parent + if (!parentObject) + parentObject = findParentObject(object, parentObject); + + // First let's compare the object to the SimObject's that + // we matched to our ParsedObject's when we loaded them + for (U32 i = 0; i < mObjectBuffer.size(); i++) + { + ParsedObject* testObj = mObjectBuffer[i]; + + if (testObj->simObject == object) + { + // Deal with children objects + if (testObj->parentObject != parentObject) + continue; + + return testObj; + } + } + + // Didn't find it in our ParsedObject's SimObject's + // so see if we can find one that corresponds to the + // same name and className + const char *originalName = object->getOriginalName(); + + // Find the corresponding ParsedObject + if (originalName && originalName[0]) + { + for (U32 i = 0; i < mObjectBuffer.size(); i++) + { + ParsedObject* testObj = mObjectBuffer[i]; + + if (testObj->name == originalName) + { + // Deal with children objects + if (testObj->parentObject != parentObject) + continue; + + return testObj; + } + } + } + + return NULL; +} + +void PersistenceManager::updateToken(const U32 lineNumber, const U32 linePosition, const char* oldValue, const char* newValue) +{ + // Make sure we have a valid lineNumber + if (lineNumber < 0 || linePosition < 0 || + lineNumber >= mLineBuffer.size()) + return; + + // Grab the line that the value is on + const char* line = mLineBuffer[lineNumber]; + + U32 oldValueLen = ( oldValue ) ? dStrlen(oldValue) : 0; + U32 newValueLen = ( newValue ) ? dStrlen(newValue) : 0; + + // Make sure we have a valid linePosition + if (linePosition >= dStrlen(line) || + linePosition + oldValueLen > dStrlen(line)) + return; + + // Get all of the characters up to the value position + char* preString = dStrdup(line); + preString[linePosition] = 0; + + // Get all of the characters that occur after the value + const char* postString = dStrdup(line + linePosition + oldValueLen); + + // Calculate the length of our new line + U32 newLineLen = 0; + + newLineLen += dStrlen(preString); + newLineLen += newValueLen; + newLineLen += dStrlen(postString); + + // Create a buffer for our new line and + // null terminate it + char* newLine = new char[newLineLen + 1]; + newLine[0] = 0; + + // Build the new line with the + // preString + newValue + postString + dStrcat(newLine, preString); + if ( newValue ) + dStrcat(newLine, newValue); + dStrcat(newLine, postString); + + // Clear our existing line + if (mLineBuffer[lineNumber]) + SAFE_DELETE_ARRAY(mLineBuffer[lineNumber]); + + // Set the new line + mLineBuffer[lineNumber] = newLine; + + // Figure out the size difference of the old value + // and new value in case we need to update any else + // on the line after it + S32 diff = newValueLen - oldValueLen; + + // Update anything that is on the line after this that needs + // to change its offsets to reflect the new line + updatePositions(lineNumber, linePosition, diff); + + // Clean up our buffers + delete preString; + delete postString; +} + +const char* PersistenceManager::getFieldValue(SimObject* object, const char* fieldName, U32 arrayPos) +{ + // Our return string + char* ret = NULL; + + // Buffer to hold the string equivalent of the arrayPos + char arrayPosStr[8]; + dSprintf(arrayPosStr, 8, "%d", arrayPos); + + // Get the object's value + const char *value = object->getDataField(fieldName, arrayPosStr ); + if (value) + ret = dStrdup(value); + + return ret; +} + +const char* PersistenceManager::createNewProperty(const char* name, const char* value, bool isArray, U32 arrayPos) +{ + if (!name || !value) + return NULL; + + char* newProp = new char[2048]; + dMemset(newProp, 0, 2048); + + if (value) + { + if (isArray) + dSprintf(newProp, 2048, "%s[%d] = \"%s\";", name, arrayPos, value); + else + dSprintf(newProp, 2048, "%s = \"%s\";", name, value); + } + else + { + if (isArray) + dSprintf(newProp, 2048, "%s[%d] = \"\";", name, arrayPos); + else + dSprintf(newProp, 2048, "%s = \"\";", name); + } + + return newProp; +} + +bool PersistenceManager::isEmptyLine(const char* line) +{ + // Simple test first + if (!line || dStrlen(line) == 0) + return true; + + U32 len = dStrlen(line); + + for (U32 i = 0; i < len; i++) + { + const char& c = line[i]; + + // Skip "empty" characters + if (c == ' ' || + c == '\t' || + c == '\r' || + c == '\n') + { + continue; + } + + // If we have made it to the an end of the line + // comment then consider this an empty line + if (c == '/') + { + if (i < len - 1) + { + if (line[i + 1] == '/') + return true; + } + } + + // If it isn't an "empty" character or a comment then + // we have a valid character on the line and it isn't empty + return false; + } + + return true; +} + +void PersistenceManager::removeLine(U32 lineNumber) +{ + if (lineNumber >= mLineBuffer.size()) + return; + + if (mLineBuffer[lineNumber]) + SAFE_DELETE_ARRAY(mLineBuffer[lineNumber]); + + mLineBuffer.erase(lineNumber); + + updateLineOffsets(lineNumber, -1); +} + +void PersistenceManager::removeTextBlock(U32 startLine, U32 endLine, U32 startPos, U32 endPos, bool removeEmptyLines) +{ + // Make sure we have valid lines + if (startLine >= mLineBuffer.size() || endLine >= mLineBuffer.size()) + return; + + // We assume that the startLine is before the endLine + if (startLine > endLine) + return; + + // Grab the lines (they may be the same) + const char* startLineText = mLineBuffer[startLine]; + const char* endLineText = mLineBuffer[endLine]; + + // Make sure we have a valid startPos + if (startPos >= dStrlen(startLineText)) + return; + + // Make sure we have a valid startPos + if (endPos >= dStrlen(endLineText)) + return; + + if (startLine == endLine) + { + // Get the full property declaration + U32 len = endPos - startPos + 1; + + char* prop = new char[len + 1]; + dMemset(prop, 0, len + 1); + + dStrncpy(prop, startLineText + startPos, len); + + // Now let updateToken to the heavy lifting on removing it + updateToken(startLine, startPos, prop, ""); + + // Clean up our temp buffer + delete [] prop; + + // Handle removing an empty lines if desired + if (removeEmptyLines) + { + const char* line = mLineBuffer[startLine]; + + if (isEmptyLine(line)) + removeLine(startLine); + } + } + else + { + // Start with clearing the startLine from startPos to the end + char* prop = dStrdup(startLineText + startPos); + + // Now let updateToken to the heavy lifting on removing it + updateToken(startLine, startPos, prop, ""); + + // Clean up our temp buffer + delete [] prop; + + // Next remove everything from the beginning of the endLine to our endPos + prop = dStrdup(endLineText); + prop[endPos + 1] = 0; + + // Now let updateToken to the heavy lifting on removing it + updateToken(endLine, 0, prop, ""); + + // Clean up our temp buffer + delete [] prop; + + // Handle removing an empty endLine if desired + if (removeEmptyLines) + { + const char* line = mLineBuffer[endLine]; + + if (isEmptyLine(line)) + removeLine(endLine); + } + + // Handle removing any lines between the startLine and endLine + for (U32 i = startLine + 1; i < endLine; i++) + removeLine(startLine + 1); + + // Handle removing an empty startLine if desired + if (removeEmptyLines) + { + const char* line = mLineBuffer[startLine]; + + if (isEmptyLine(line)) + removeLine(startLine); + } + } +} + +void PersistenceManager::removeParsedObject(ParsedObject* parsedObject) +{ + if (!parsedObject) + return; + + if (parsedObject->startLine < 0 || parsedObject->startLine >= mLineBuffer.size()) + return; + + if (parsedObject->endLine < 0 || parsedObject->startLine >= mLineBuffer.size()) + return; + + removeTextBlock(parsedObject->startLine, parsedObject->endLine, + parsedObject->startPosition, parsedObject->endPosition, true); + + parsedObject->parentObject = NULL; + parsedObject->simObject = NULL; +} + +void PersistenceManager::removeField(const ParsedProperty& prop) +{ + if (prop.startLine < 0 || prop.startLine >= mLineBuffer.size()) + return; + + if (prop.endLine < 0 || prop.endLine >= mLineBuffer.size()) + return; + + removeTextBlock(prop.startLine, prop.endLine, prop.startPosition, prop.endPosition, true); +} + +U32 PersistenceManager::writeProperties(const Vector& properties, const U32 insertLine, const char* objectIndent) +{ + U32 currInsertLine = insertLine; + + for (U32 i = 0; i < properties.size(); i++) + { + const char* prop = properties[i]; + + if (!prop || dStrlen(prop) == 0) + continue; + + U32 len = dStrlen(objectIndent) + dStrlen(prop) + 4; + + char* newLine = new char[len]; + + dSprintf(newLine, len, "%s %s", objectIndent, prop); + + mLineBuffer.insert(currInsertLine, newLine); + currInsertLine++; + } + + return currInsertLine - insertLine; +} + +PersistenceManager::ParsedObject* PersistenceManager::writeNewObject(SimObject* object, const Vector& properties, const U32 insertLine, ParsedObject* parentObject) +{ + ParsedObject* parsedObject = new ParsedObject; + + parsedObject->name = object->getName(); + parsedObject->className = object->getClassName(); + parsedObject->simObject = object; + + U32 currInsertLine = insertLine; + + // If the parentObject isn't set see if + // we can find it in the file + if (!parentObject) + parentObject = findParentObject(object); + + parsedObject->parentObject = parentObject; + + char* indent = getObjectIndent(parentObject); + + if (parentObject) + dStrcat(indent, " \0"); + + // Write out the beginning of the object declaration + char dclToken[128]; + dMemset(dclToken, 0, 128); + + if (dynamic_cast(object) || + dynamic_cast(object) || + dynamic_cast(object)) + dSprintf(dclToken, 128, "singleton"); + else + { + SimDataBlock* db = dynamic_cast(object); + + if (db && !db->isClientOnly()) + dSprintf(dclToken, 128, "datablock"); + else + dSprintf(dclToken, 128, "new"); + } + + char newLine[2048]; + dMemset(newLine, 0, 2048); + + // New line before an object declaration + dSprintf(newLine, 2048, ""); + + mLineBuffer.insert(currInsertLine, dStrdup(newLine)); + currInsertLine++; + dMemset(newLine, 0, 2048); + + parsedObject->startLine = currInsertLine; + parsedObject->nameLine = currInsertLine; + parsedObject->namePosition = dStrlen(indent) + dStrlen(dclToken) + dStrlen(object->getClassName()) + 2; + + // Objects that had no name were getting saved out as: Object((null)) + if ( object->getName() != NULL ) + dSprintf(newLine, 2048, "%s%s %s(%s)", indent, dclToken, object->getClassName(), object->getName()); + else + dSprintf(newLine, 2048, "%s%s %s()", indent, dclToken, object->getClassName() ); + + mLineBuffer.insert(currInsertLine, dStrdup(newLine)); + currInsertLine++; + dMemset(newLine, 0, 2048); + + dSprintf(newLine, 2048, "%s{", indent); + + mLineBuffer.insert(currInsertLine, dStrdup(newLine)); + currInsertLine++; + dMemset(newLine, 0, 2048); + + currInsertLine += writeProperties(properties, currInsertLine, indent); + + parsedObject->endLine = currInsertLine; + parsedObject->updated = true; + + dSprintf(newLine, 2048, "%s};", indent); + + mLineBuffer.insert(currInsertLine, dStrdup(newLine)); + currInsertLine++; + + updateLineOffsets(insertLine, currInsertLine - insertLine, parsedObject); + + mObjectBuffer.push_back(parsedObject); + + // Update the SimObject to reflect its saved name and declaration line. + // These values should always reflect what is in the file, even if the object + // has not actually been re-created from an execution of that file yet. + object->setOriginalName( object->getName() ); + object->setDeclarationLine( currInsertLine ); + + if (mCurrentFile) + object->setFilename(mCurrentFile); + + return parsedObject; +} + +void PersistenceManager::updateObject(SimObject* object, ParsedObject* parentObject) +{ + // Create a default object of the same type + ConsoleObject *defaultConObject = ConsoleObject::create(object->getClassName()); + SimObject* defaultObject = dynamic_cast(defaultConObject); + + // ***Really*** shouldn't happen + if (!defaultObject) + return; + + Vector newLines; + + ParsedObject* parsedObject = findParsedObject(object, parentObject); + + // If we don't already have an association between the ParsedObject + // and the SimObject then go ahead and create it + if (parsedObject && parsedObject->simObject.isNull()) + parsedObject->simObject = object; + + // Get our field list + const AbstractClassRep::FieldList &list = object->getFieldList(); + + for(U32 i = 0; i < list.size(); i++) + { + const AbstractClassRep::Field* f = &list[i]; + + // Skip the special field types. + if ( f->type >= AbstractClassRep::ARCFirstCustomField ) + continue; + + for(U32 j = 0; S32(j) < f->elementCount; j++) + { + const char* value = getFieldValue(object, f->pFieldname, j); + + // Make sure we got a value + if (!value) + continue; + + // If this is a field we don't write out then we don't + // care either for our purposes + if (dStrlen(value) > 0 && !object->writeField(f->pFieldname, value)) + continue; + + // Let's see if this field is already in the file + S32 propertyIndex = getPropertyIndex(parsedObject, f->pFieldname, j); + + if (propertyIndex > -1) + { + ParsedProperty& prop = parsedObject->properties[propertyIndex]; + + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, f->pFieldname, j)) + { + removeField(prop); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(f->type, prop.value, f->table, f->flag); + + // If our data doesn't match then we get to update it + if (dStricmp(value, evalue) != 0) + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + updateToken(prop.valueLine, prop.valuePosition, prop.value, fnBuf); + } + else + updateToken(prop.valueLine, prop.valuePosition, prop.value, value); + } + } + else + { + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, f->pFieldname, j)) + continue; + + // If we didn't find the property in the ParsedObject + // then we need to compare against the default value + // for this property and save it out if it is different + const char* defaultValue = getFieldValue(defaultObject, f->pFieldname, j); + + // The default value for most string type fields is + // NULL so we can't just continue here or we'd never ever + // write them out... + // + //if (!defaultValue) + // continue; + + // If the object's value is different from the default + // value then add it to the ParsedObject's newLines + if ( !defaultValue || dStricmp(value, defaultValue) != 0 ) + { + // TODO: This should be wrapped in a helper method... probably. + // Detect and collapse relative path information + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename) + { + char fnBuf[1024]; + Con::collapseScriptFilename(fnBuf, 1024, value); + + newLines.push_back(createNewProperty(f->pFieldname, fnBuf, f->elementCount > 1, j)); + } + else + newLines.push_back(createNewProperty(f->pFieldname, value, f->elementCount > 1, j)); + } + + if (defaultValue) + delete defaultValue; + } + + delete value; + } + } + + // Handle dynamic fields + SimFieldDictionary* fieldDict = object->getFieldDictionary(); + + for(SimFieldDictionaryIterator itr(fieldDict); *itr; ++itr) + { + SimFieldDictionary::Entry * entry = (*itr); + + // Let's see if this field is already in the file + S32 propertyIndex = getPropertyIndex(parsedObject, entry->slotName); + + if (propertyIndex > -1) + { + ParsedProperty& prop = parsedObject->properties[propertyIndex]; + + // If this field is on the remove list then remove it and continue + if (findRemoveField(object, entry->slotName)) + { + removeField(prop); + continue; + } + + // Run the parsed value through the console system conditioners so + // that it will better match the data we got back from the object. + const char* evalue = Con::getFormattedData(TypeString, prop.value); + + // If our data doesn't match then we get to update it + if (dStricmp(entry->value, evalue) != 0) + updateToken(prop.valueLine, prop.valuePosition, prop.value, entry->value); + } + else + { + // No need to process a removed field that doesn't exist in the file + if (findRemoveField(object, entry->slotName)) + continue; + + newLines.push_back(createNewProperty(entry->slotName, entry->value)); + } + } + + // If we have a parsedObject and the name changed + // then update the parsedObject to the new name. + // NOTE: an object 'can' have a NULL name which crashes in dStricmp. + if (parsedObject && parsedObject->name != StringTable->insert(object->getName(), true) ) + { + StringTableEntry objectName = StringTable->insert(object->getName(), true); + + if (parsedObject->name != objectName) + { + // Update the name in the file + updateToken(parsedObject->nameLine, parsedObject->namePosition, parsedObject->name, object->getName()); + + // Updated the parsedObject's name + parsedObject->name = objectName; + + // Updated the object's "original" name to the one that is now in the file + object->setOriginalName(objectName); + } + } + + if (parsedObject && newLines.size() > 0) + { + U32 lastPropLine = parsedObject->endLine; + + if (parsedObject->properties.size() > 0) + lastPropLine = parsedObject->properties.last().valueLine + 1; + + U32 currInsertLine = lastPropLine; + + const char* indent = getObjectIndent(parsedObject); + + // This should handle adding the opening { to an object + // that formerly did not have {}; + if (!parsedObject->hasBraces) + { + updateToken(parsedObject->endLine, parsedObject->endPosition, ";", "\r\n{"); + + currInsertLine++; + } + + currInsertLine += writeProperties(newLines, currInsertLine, indent); + + // This should handle adding the opening } to an object + // that formerly did not have {}; + if (!parsedObject->hasBraces) + { + U32 len = dStrlen(indent) + 3; + char* newLine = new char[len]; + + dSprintf(newLine, len, "%s};", indent); + + mLineBuffer.insert(currInsertLine, newLine); + currInsertLine++; + } + + // Update the line offsets to account for the new lines + updateLineOffsets(lastPropLine, currInsertLine - lastPropLine); + } + else if (!parsedObject) + { + U32 insertLine = mLineBuffer.size(); + + if (!parentObject) + parentObject = findParentObject(object, parentObject); + + if (parentObject && parentObject->endLine > -1) + insertLine = parentObject->endLine; + + parsedObject = writeNewObject(object, newLines, insertLine, parentObject); + } + + // Clean up the newLines memory + for (U32 i = 0; i < newLines.size(); i++) + { + if (newLines[i]) + SAFE_DELETE_ARRAY(newLines[i]); + } + + newLines.clear(); + + SimSet* set = dynamic_cast(object); + + if (set) + { + for(SimSet::iterator i = set->begin(); i != set->end(); i++) + { + SimObject* subObject = (SimObject*)(*i); + updateObject(subObject, parsedObject); + } + } + + // Loop through the children of this parsedObject + // If they haven't been updated then assume that they + // don't exist in the file anymore + if (parsedObject) + { + for (S32 i = 0; i < mObjectBuffer.size(); i++) + { + ParsedObject* removeObj = mObjectBuffer[i]; + + if (removeObj->parentObject == parsedObject && !removeObj->updated) + { + removeParsedObject(removeObj); + + mObjectBuffer.erase(i); + i--; + + deleteObject(removeObj); + } + } + } + + // Flag this as an updated object + if (parsedObject) + parsedObject->updated = true; + + // Cleanup our created default object + delete defaultConObject; +} + +bool PersistenceManager::saveDirtyFile() +{ + FileStream stream; + stream.open( mCurrentFile, Torque::FS::File::Write ); + + if ( stream.getStatus() != Stream::Ok ) + { + clearFileData(); + + return false; + } + + for (U32 i = 0; i < mLineBuffer.size(); i++) + stream.writeLine((const U8*)mLineBuffer[i]); + + stream.close(); + + //Con::printf("Successfully opened and wrote %s", mCurrentFile); + + //Con::errorf("Updated Results:"); + + //for (U32 i = 0; i < mObjectBuffer.size(); i++) + //{ + // ParsedObject* parsedObject = mObjectBuffer[i]; + + // Con::warnf(" mObjectBuffer[%d]:", i); + // Con::warnf(" name = %s", parsedObject->name); + // Con::warnf(" className = %s", parsedObject->className); + // Con::warnf(" startLine = %d", parsedObject->startLine + 1); + // Con::warnf(" endLine = %d", parsedObject->endLine + 1); + + // //if (mObjectBuffer[i]->properties.size() > 0) + // //{ + // // Con::warnf(" properties:"); + // // for (U32 j = 0; j < mObjectBuffer[i]->properties.size(); j++) + // // Con::warnf(" %s = %s;", mObjectBuffer[i]->properties[j].name, + // // mObjectBuffer[i]->properties[j].value); + // //} + + // if (!parsedObject->simObject.isNull()) + // { + // SimObject* simObject = parsedObject->simObject; + + // Con::warnf(" SimObject(%s) %d:", simObject->getName(), simObject->getId()); + // Con::warnf(" declaration line = %d", simObject->getDeclarationLine()); + // } + //} + + // Clear our file data + clearFileData(); + + return true; +} + +S32 QSORT_CALLBACK PersistenceManager::compareFiles(const void* a,const void* b) +{ + DirtyObject* objectA = (DirtyObject*)(a); + DirtyObject* objectB = (DirtyObject*)(b); + + if (objectA->object.isNull()) + return -1; + else if (objectB->object.isNull()) + return 1; + + if (objectA->fileName == objectB->fileName) + return objectA->object->getDeclarationLine() - objectB->object->getDeclarationLine(); + + return dStricmp(objectA->fileName, objectB->fileName); +} + +bool PersistenceManager::setDirty(SimObject* inObject, const char* inFileName) +{ + // Check if the object is already in the dirty list... + DirtyObject *pDirty = findDirtyObject( inObject ); + + // The filename we will save this object to (later).. + String saveFile; + + // Expand the script filename if we were passed one. + if ( inFileName ) + { + char buffer[4096]; + Con::expandScriptFilename( buffer, 4096, inFileName ); + saveFile = buffer; + } + + // If no filename was passed in, and the object was already dirty, + // we have nothing to do. + if ( saveFile.isEmpty() && pDirty ) + return true; + + // Otherwise default to the simObject's filename. + if ( saveFile.isEmpty() ) + saveFile = inObject->getFilename(); + + // Error if still no filename. + if ( saveFile.isEmpty() ) + { + if (inObject->getName()) + Con::errorf("PersistenceManager::setDirty() - SimObject %s has no file name associated with it - can not save", inObject->getName()); + else + Con::errorf("PersistenceManager::setDirty() - SimObject %d has no file name associated with it - can not save", inObject->getId()); + + return false; + } + + // Update the DirtyObject's fileName if we have it + // else create a new one. + + if ( pDirty ) + pDirty->fileName = StringTable->insert( saveFile ); + else + { + // Add the newly dirty object. + mDirtyObjects.increment(); + mDirtyObjects.last().object = inObject; + mDirtyObjects.last().fileName = StringTable->insert( saveFile ); + } + + return true; +} + +void PersistenceManager::removeDirty(SimObject* object) +{ + for (U32 i = 0; i < mDirtyObjects.size(); i++) + { + const DirtyObject& dirtyObject = mDirtyObjects[i]; + + if (dirtyObject.object.isNull()) + continue; + + if (dirtyObject.object == object) + { + mDirtyObjects.erase(i); + break; + } + } + + for (U32 i = 0; i < mRemoveFields.size(); i++) + { + const RemoveField& field = mRemoveFields[i]; + + if (field.object != object) + continue; + + mRemoveFields.erase(i); + + if (i > 0) + i--; + } +} + +void PersistenceManager::addRemoveField(SimObject* object, const char* fieldName) +{ + // Check to see if this is an array variable + U32 arrayPos = 0; + const char* name = fieldName; + + if (dStrlen(fieldName) > 3 && fieldName[dStrlen(fieldName) - 1] == ']') + { + // The last character is a ']' which *should* mean + // there is also a corresponding '[' + const char* arrayPosStart = dStrrchr(fieldName, '['); + + if (!arrayPosStart) + { + Con::errorf("PersistenceManager::addRemoveField() - error parsing array position - \ + was expecting a '[' character"); + } + else + { + // Parse the array position for the variable name + dSscanf(arrayPosStart, "[%d]", &arrayPos); + + // Trim off the [] from the variable name + char* variableName = dStrdup(fieldName); + variableName[arrayPosStart - fieldName] = 0; + + // Set the variable name to our new shortened name + name = StringTable->insert(variableName, true); + + // Cleanup our variableName buffer + delete variableName; + } + } + + // Make sure this field isn't already on the list + if (!findRemoveField(object, name, arrayPos)) + { + mRemoveFields.increment(); + + RemoveField& field = mRemoveFields.last(); + + field.object = object; + field.fieldName = StringTable->insert(name); + field.arrayPos = arrayPos; + } +} + +bool PersistenceManager::isDirty(SimObject* object) +{ + return ( findDirtyObject( object ) != NULL ); +} + +PersistenceManager::DirtyObject* PersistenceManager::findDirtyObject(SimObject* object) +{ + for (U32 i = 0; i < mDirtyObjects.size(); i++) + { + const DirtyObject& dirtyObject = mDirtyObjects[i]; + + if (dirtyObject.object.isNull()) + continue; + + if (dirtyObject.object == object) + return &mDirtyObjects[i]; + } + + return NULL; +} + +bool PersistenceManager::findRemoveField(SimObject* object, const char* fieldName, U32 arrayPos) +{ + for (U32 i = 0; i < mRemoveFields.size(); i++) + { + if (mRemoveFields[i].object == object && + mRemoveFields[i].arrayPos == arrayPos && + dStricmp(mRemoveFields[i].fieldName, fieldName) == 0) + { + return true; + } + } + + return false; +} + +bool PersistenceManager::saveDirty() +{ + // Remove any null SimObject's first + for (S32 i = 0; i < mDirtyObjects.size(); i++) + { + const DirtyObject& dirtyObject = mDirtyObjects[i]; + + if (dirtyObject.object.isNull()) + { + mDirtyObjects.erase(i); + i--; + } + } + + // Sort by filename and declaration lines + dQsort(mDirtyObjects.address(), mDirtyObjects.size(), sizeof(DirtyList::value_type), compareFiles); + + for (U32 i = 0; i < mDirtyObjects.size(); i++) + { + const DirtyObject& dirtyObject = mDirtyObjects[i]; + + if (dirtyObject.object.isNull()) + continue; + + SimObject* object = dirtyObject.object; + + if (!mCurrentFile || dStricmp(mCurrentFile, dirtyObject.fileName) != 0) + { + // If mCurrentFile is set then that means we + // changed file names so save our previous one + if (mCurrentFile) + saveDirtyFile(); + + // Open our new file and parse it + bool success = parseFile(dirtyObject.fileName); + + if (!success) + { + const char *name = object->getName(); + if (name) + { + Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s %s (%d)", + dirtyObject.fileName, object->getClassName(), name, object->getId()); + } + else + { + Con::errorf("PersistenceManager::saveDirty(): Unable to open %s to save %s (%d)", + dirtyObject.fileName, object->getClassName(), object->getId()); + } + + continue; + } + } + + // Update this object's properties + // + // An empty script file (with 1 line) gets here with zero + // elements in the linebuffer, so this would prevent us from + // ever writing to it... Or is this test preventing bad things from + // happening if the file didn't exist at all? + // + if (mCurrentFile /*&& mLineBuffer.size() > 0*/) + updateObject(object); + } + + // Save out our last file + if (mCurrentFile) + saveDirtyFile(); + + // Done writing out our dirty objects so reset everything + clearAll(); + + return true; +} + +bool PersistenceManager::saveDirtyObject(SimObject* object) +{ + // find our object passed in + for (U32 i = 0; i < mDirtyObjects.size(); i++) + { + const DirtyObject& dirtyObject = mDirtyObjects[i]; + + if (dirtyObject.object.isNull()) + continue; + + if (dirtyObject.object == object) + { + // Open our new file and parse it + bool success = parseFile(dirtyObject.fileName); + + if (!success) + { + const char *name = object->getName(); + if (name) + { + Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s %s (%d)", + dirtyObject.fileName, object->getClassName(), name, object->getId()); + } + else + { + Con::errorf("PersistenceManager::saveDirtyObject(): Unable to open %s to save %s (%d)", + dirtyObject.fileName, object->getClassName(), object->getId()); + } + + return false; + } + + // if the file exists then lets update and save + if(mCurrentFile) + { + updateObject(object); + saveDirtyFile(); + } + + break; + } + } + + // remove this object from the dirty list + removeDirty(object); + + return true; +} + +void PersistenceManager::removeObjectFromFile(SimObject* object, const char* fileName) +{ + if (mCurrentFile) + { + Con::errorf("PersistenceManager::removeObjectFromFile(): Can't remove an object from a \ + file while another is currently opened"); + + return; + } + + const char* file = object->getFilename(); + if (fileName) + { + char buffer[1024]; + Con::expandScriptFilename( buffer, 1024, fileName ); + + file = StringTable->insert(buffer); + } + + bool success = false; + + if ( file ) + success = parseFile(file); + + if (!success) + { + const char *name = object->getName(); + + String errorNameStr; + if ( name ) + errorNameStr = String::ToString( "%s %s (%d)", object->getClassName(), name, object->getId() ); + else + errorNameStr = String::ToString( "%s (%d)", object->getClassName(), object->getId() ); + + if ( !file ) + Con::errorf("PersistenceManager::removeObjectFromFile(): File was null trying to save %s", errorNameStr.c_str() ); + else + Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to open %s to save %s", file, errorNameStr.c_str() ); + + // Reset everything + clearAll(); + + return; + } + + ParsedObject* parsedObject = findParsedObject(object); + + if (!parsedObject) + { + const char *name = object->getName(); + if (name) + { + Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s %s (%d) in %s", + object->getClassName(), name, object->getId(), file); + } + else + { + Con::errorf("PersistenceManager::removeObjectFromFile(): Unable to find %s (%d) in %s", + object->getClassName(), object->getId(), file); + } + + // Reset everything + clearAll(); + + return; + } + + removeParsedObject(parsedObject); + + for (U32 i = 0; i < mObjectBuffer.size(); i++) + { + ParsedObject* removeObj = mObjectBuffer[i]; + + if (removeObj == parsedObject) + { + mObjectBuffer.erase(i); + break; + } + } + + deleteObject(parsedObject); + + // Save out the file + if (mCurrentFile) + saveDirtyFile(); + + // Reset everything + clearAll(); +} + +void PersistenceManager::deleteObjectsFromFile(const char* fileName) +{ + if ( mCurrentFile ) + { + Con::errorf( "PersistenceManager::deleteObjectsFromFile(): Cannot process while file while another is currently open." ); + return; + } + + // Expand Script File. + char buffer[1024]; + Con::expandScriptFilename( buffer, 1024, fileName ); + + // Parse File. + if ( !parseFile( StringTable->insert(buffer) ) ) + { + // Invalid. + return; + } + + // Iterate over the objects. + for ( Vector::iterator itr = mObjectBuffer.begin(); itr != mObjectBuffer.end(); itr++ ) + { + SimObject *object; + if ( Sim::findObject( ( *itr )->name, object ) ) + { + // Delete the Object. + object->deleteObject(); + } + } + + // Clear. + clearAll(); +} + +ConsoleMethod( PersistenceManager, deleteObjectsFromFile, void, 3, 3, "( fileName )" + "Delete all of the objects that are created from the given file." ) +{ + // Delete Objects. + object->deleteObjectsFromFile( argv[2] ); +} + +ConsoleMethod( PersistenceManager, setDirty, void, 3, 4, "(SimObject object, [filename])" + "Mark an existing SimObject as dirty (will be written out when saveDirty() is called).") +{ + SimObject *dirtyObject = NULL; + if (argv[2][0]) + { + if (!Sim::findObject(argv[2], dirtyObject)) + { + Con::printf("%s(): Invalid SimObject: %s", argv[0], argv[2]); + return; + } + } + + if (dirtyObject) + { + if (argc == 4 && argv[3][0]) + object->setDirty(dirtyObject, argv[3]); + else + object->setDirty(dirtyObject); + } +} + +ConsoleMethod( PersistenceManager, removeDirty, void, 3, 3, "(SimObject object)" + "Remove a SimObject from the dirty list.") +{ + SimObject *dirtyObject = NULL; + if (argv[2][0]) + { + if (!Sim::findObject(argv[2], dirtyObject)) + { + Con::printf("%s(): Invalid SimObject: %s", argv[0], argv[2]); + return; + } + } + + if (dirtyObject) + object->removeDirty(dirtyObject); +} + +ConsoleMethod( PersistenceManager, isDirty, bool, 3, 3, "(SimObject object)" + "Returns true if the SimObject is on the dirty list.") +{ + SimObject *dirtyObject = NULL; + if (argv[2][0]) + { + if (!Sim::findObject(argv[2], dirtyObject)) + { + Con::printf("%s(): Invalid SimObject: %s", argv[0], argv[2]); + return false; + } + } + + if (dirtyObject) + return object->isDirty(dirtyObject); + + return false; +} + +ConsoleMethod( PersistenceManager, hasDirty, bool, 2, 2, "()" + "Returns true if the manager has dirty objects to save." ) +{ + return object->hasDirty(); +} + +ConsoleMethod( PersistenceManager, getDirtyObjectCount, S32, 2, 2, "()" + "Returns the number of dirty objects." ) +{ + return object->getDirtyList().size(); +} + +ConsoleMethod( PersistenceManager, getDirtyObject, S32, 3, 3, "( index )" + "Returns the ith dirty object." ) +{ + const S32 index = dAtoi( argv[2] ); + if ( index < 0 || index >= object->getDirtyList().size() ) + { + Con::warnf( "PersistenceManager::getDirtyObject() - Index (%s) out of range.", argv[2] ); + return 0; + } + + // Fetch Object. + const PersistenceManager::DirtyObject& dirtyObject = object->getDirtyList()[index]; + + // Return Id. + return ( dirtyObject.object ) ? dirtyObject.object->getId() : 0; +} + +ConsoleMethod( PersistenceManager, listDirty, void, 2, 2, "()" + "Prints the dirty list to the console.") +{ + const PersistenceManager::DirtyList dirtyList = object->getDirtyList(); + + for(U32 i = 0; i < dirtyList.size(); i++) + { + const PersistenceManager::DirtyObject& dirtyObject = dirtyList[i]; + + if (dirtyObject.object.isNull()) + continue; + + SimObject *obj = dirtyObject.object; + bool isSet = dynamic_cast(obj) != 0; + const char *name = obj->getName(); + if (name) + { + Con::printf(" %d,\"%s\": %s %s %s", obj->getId(), name, + obj->getClassName(), dirtyObject.fileName, isSet ? "(g)":""); + } + else + { + Con::printf(" %d: %s %s, %s", obj->getId(), obj->getClassName(), + dirtyObject.fileName, isSet ? "(g)" : ""); + } + } +} + +ConsoleMethod( PersistenceManager, saveDirty, void, 2, 2, "()" + "Saves all of the SimObject's on the dirty list to their respective files.") +{ + object->saveDirty(); +} + +ConsoleMethod( PersistenceManager, saveDirtyObject, void, 3, 3, "(SimObject object)" + "Save a dirty SimObject to it's file.") +{ + SimObject *dirtyObject = NULL; + if (argv[2][0]) + { + if (!Sim::findObject(argv[2], dirtyObject)) + { + Con::printf("%s(): Invalid SimObject: %s", argv[0], argv[2]); + return; + } + } + + if (dirtyObject) + object->saveDirtyObject(dirtyObject); +} + +ConsoleMethod( PersistenceManager, clearAll, void, 2, 2, "()" + "Clears all the tracked objects without saving them." ) +{ + object->clearAll(); +} + +ConsoleMethod( PersistenceManager, removeObjectFromFile, void, 3, 4, "(SimObject object, [filename])" + "Remove an existing SimObject from a file (can optionally specify a different file than \ + the one it was created in.") +{ + SimObject *dirtyObject = NULL; + if (argv[2][0]) + { + if (!Sim::findObject(argv[2], dirtyObject)) + { + Con::printf("%s(): Invalid SimObject: %s", argv[0], argv[2]); + return; + } + } + + if (dirtyObject) + { + if (argc == 4 && argv[3][0]) + object->removeObjectFromFile(dirtyObject, argv[3]); + else + object->removeObjectFromFile(dirtyObject); + } +} + +ConsoleMethod( PersistenceManager, removeField, void, 4, 4, "(SimObject object, string fieldName)" + "Remove a specific field from an object declaration.") +{ + SimObject *dirtyObject = NULL; + if (argv[2][0]) + { + if (!Sim::findObject(argv[2], dirtyObject)) + { + Con::printf("%s(): Invalid SimObject: %s", argv[0], argv[2]); + return; + } + } + + if (dirtyObject) + { + if (argv[3][0]) + object->addRemoveField(dirtyObject, argv[3]); + } +} \ No newline at end of file diff --git a/console/persistenceManager.h b/console/persistenceManager.h new file mode 100644 index 0000000..f00eff2 --- /dev/null +++ b/console/persistenceManager.h @@ -0,0 +1,295 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PERSISTENCEMANAGER_H_ +#define _PERSISTENCEMANAGER_H_ + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + +#ifndef _SIMOBJECTLIST_H_ +#include "console/simObjectList.h" +#endif + +#ifndef _TOKENIZER_H_ +#include "core/tokenizer.h" +#endif + +class PersistenceManager : public SimObject +{ +public: + struct ParsedProperty + { + StringTableEntry name; + const char* value; + + U32 arrayPos; + + S32 startLine; + S32 endLine; + + S32 startPosition; + S32 endPosition; + + S32 valueLine; + S32 valuePosition; + + ParsedProperty() + { + name = NULL; + value = NULL; + + arrayPos = 0; + + startLine = -1; + endLine = -1; + + startPosition = -1; + endPosition = -1; + + valueLine = -1; + valuePosition = -1; + } + }; + + struct ParsedObject + { + StringTableEntry name; + StringTableEntry className; + + ParsedObject* parentObject; + + SimObjectPtr simObject; + + S32 nameLine; + S32 namePosition; + + S32 startLine; + S32 endLine; + + S32 startPosition; + S32 endPosition; + + bool hasBraces; + + bool updated; + + Vector properties; + + ParsedObject() + { + name = NULL; + className = NULL; + + parentObject = NULL; + + simObject = NULL; + + nameLine = -1; + namePosition = -1; + + startLine = -1; + endLine = -1; + + startPosition = -1; + endPosition = -1; + + hasBraces = true; + + updated = false; + } + }; + + struct DirtyObject + { + SimObjectPtr object; + StringTableEntry fileName; + + DirtyObject() + { + object = NULL; + fileName = NULL; + } + }; + + struct RemoveField + { + SimObjectPtr object; + StringTableEntry fieldName; + U32 arrayPos; + + RemoveField() + { + object = NULL; + fieldName = NULL; + arrayPos = 0; + } + }; + + typedef Vector DirtyList; + +protected: + typedef SimObject Parent; + + // Used to walk through the file and read out + // the SimObject's and their properties + Tokenizer mParser; + + // List of the objects that are flagged as dirty + DirtyList mDirtyObjects; + + // List of fields to be removed from the objects declaration in the file + Vector mRemoveFields; + + // Temp buffers used during file parsing + ParsedObject* mCurrentObject; + Vector mObjectStack; + + // Buffers used on a per-file basis + Vector mLineBuffer; + Vector mObjectBuffer; + + // Name of the currently open file + const char* mCurrentFile; + + // Sort by filename + static S32 QSORT_CALLBACK compareFiles(const void* a, const void* b); + + // Deletes and clears the line buffer + void clearLineBuffer(); + + // Deletes the objects and its properties + void deleteObject(ParsedObject* object); + // Deletes and clears the object buffer, + // the object stack, and the current object + void clearObjects(); + + // Clears all of the data related to the + // currently loaded file + void clearFileData(); + + // Updates the changed values of a dirty object + // Also handles a new object + void updateObject(SimObject* object, ParsedObject* parentObject = NULL); + + // Removes the current object without saving it + void killObject(); + // Saves the current object and restores the last object in the stack (if any) + void saveObject(); + // Parses an object from the current position in the parser + void parseObject(); + + // Reads the file into the line buffer + bool readFile(const char* fileName); + // Parses the ParsedObjects out of the file + bool parseFile(const char* fileName); + + // Writes the line buffer out to the current file + bool saveDirtyFile(); + + // Attempts to look up the property in the ParsedObject + S32 getPropertyIndex(ParsedObject* parsedObject, const char* fieldName, U32 arrayPos = 0); + + // Gets the amount of indent on the ParsedObject. + char* getObjectIndent(ParsedObject* object); + + // Loops through all of the objects and properties that are on the same + // line after the startPos and updates their position offests accordingly + void updatePositions(U32 lineNumber, U32 startPos, S32 diff); + + // Loops thought all of the objects and updates their line offsets + void updateLineOffsets(U32 startLine, S32 diff, ParsedObject* skipObject = NULL); + + // Replaces a token on a given line with a new value + // This also calls updatePositions() to account for size + // differences between the old token and the new token + void updateToken(const U32 lineNumber, const U32 linePosition, const char* oldValue, const char* newValue); + + // Gets the field value from the SimObject. Note that this does + // allocate memory that needs to be cleaned up elsewhere + const char* getFieldValue(SimObject* object, const char* fieldName, U32 arrayPos); + + // Attempt to find the parent object + ParsedObject* findParentObject(SimObject* object, ParsedObject* parentObject = NULL); + + // Attempt to find the matching ParsedObject in our object buffer + ParsedObject* findParsedObject(SimObject* object, ParsedObject* parentObject = NULL); + + // Attempt to find the matching DirtyObject for a passed SimObject in our DirtyItems list. + DirtyObject* findDirtyObject(SimObject* object); + + // Is this field on the remove list + bool findRemoveField(SimObject* object, const char* fieldName, U32 arrayPos = 0); + + // Helper function that allocates a new string and properly formats the data into it + // Note that this allocates memory that needs to be cleaned up elsewhere + const char* createNewProperty(const char* name, const char* value, bool isArray = false, U32 arrayPos = 0); + + // Test to see if there is anything valid on the line + bool isEmptyLine(const char* line); + + // Removes a line safely from the line buffer + void removeLine(U32 lineNumber); + // Remove a block of text from the line buffer. It returns + // the number of lines removed if removeEmptyLines is set to true. + void removeTextBlock(U32 startLine, U32 endLine, U32 startPos, U32 endPos, bool removeEmptyLines); + // Removes a ParsedObject from the line buffer + // (everything from the startLine to the endLine) + void removeParsedObject(ParsedObject* parsedObject); + // Removes a property from the line buffer + void removeField(const ParsedProperty& prop); + + // Write out properties + // Returns the number of new lines added + U32 writeProperties(const Vector& properties, const U32 insertLine, const char* objectIndent); + // Write out a new object + ParsedObject* writeNewObject(SimObject* object, const Vector& properties, const U32 insertLine, ParsedObject* parentObject = NULL); + +public: + PersistenceManager(); + virtual ~PersistenceManager(); + + bool onAdd(); + void onRemove(); + + // Adds an object to the dirty list + // Optionally changes the object's filename + bool setDirty(SimObject* object, const char* fileName = NULL); + // Removes the object from the dirty list + void removeDirty(SimObject* object); + + // Add a field to the remove list to cut it out of the object's declaration + void addRemoveField(SimObject* object, const char* fieldName); + + // Test to see if an object is on the dirty list + bool isDirty(SimObject* object); + + // Returns whether or not there are dirty objects + bool hasDirty() const { return !mDirtyObjects.empty(); } + + // Saves the dirty objects out to their respective files + // and clears the dirty object data + bool saveDirty(); + + // Saves out a single object, if it's dirty + bool saveDirtyObject(SimObject* object); + + // Clears all of the dirty object data + void clearAll(); + + // Removes the object from the file + void removeObjectFromFile(SimObject* object, const char* fileName = NULL); + + // Deletes all of the objects that were created from this file + void deleteObjectsFromFile(const char* fileName); + + // Returns a list of the dirty objects + const DirtyList& getDirtyList() { return mDirtyObjects; } + + DECLARE_CONOBJECT(PersistenceManager); +}; + +#endif \ No newline at end of file diff --git a/console/propertyParsing.cpp b/console/propertyParsing.cpp new file mode 100644 index 0000000..70de2de --- /dev/null +++ b/console/propertyParsing.cpp @@ -0,0 +1,570 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "core/strings/stringFunctions.h" +#include "core/strings/stringUnit.h" +#include "console/consoleInternal.h" +#include "core/color.h" +#include "console/consoleTypes.h" +#include "math/mPoint2.h" +#include "math/mPoint3.h" +#include "math/mPoint4.h" +#include "math/mRect.h" +#include "math/mBox.h" +#include "math/mQuat.h" +#include "math/mAngAxis.h" +#include "math/mMatrix.h" +// Property system includes: +#include "console/propertyParsing.h" + +extern ExprEvalState gEvalState; + +namespace PropertyInfo +{ + //----------------------------------------------------------------------------- + // Bool + //----------------------------------------------------------------------------- + bool default_scan(const String &data, bool & result) + { + result = dAtob(data.c_str()); + return true; + } + + bool default_print(String & result, bool const & data) + { + if(data) + result = String("1"); + else + result = String("0"); + return true; + } + + //----------------------------------------------------------------------------- + // F32/U32/S32 + //----------------------------------------------------------------------------- + bool default_scan(const String &data, F32 & result) + { + result = dAtof(data.c_str()); + return true; + } + + bool default_print(String & result, F32 const & data) + { + result = String::ToString(data); + return true; + } + + bool default_scan(const String &data, U32 & result) + { + result = dAtoi(data.c_str()); + return true; + } + + bool default_print(String & result, U32 const & data) + { + result = String::ToString(data); + return true; + } + + bool default_scan(const String &data, S32 & result) + { + result = dAtoi(data.c_str()); + return true; + } + + bool default_print(String & result, S32 const & data) + { + result = String::ToString(data); + return true; + } + + //----------------------------------------------------------------------------- + // Basic Vector Types + //----------------------------------------------------------------------------- + template + inline void default_vector_scan(const String &data, Vector & result) + { + result.clear(); + for(S32 i = 0; i < StringUnit::getUnitCount(data, " \t\n"); i++) + result.push_back(dAtof(StringUnit::getUnit(data, i, " \t\n"))); + } + + template + inline void default_vector_print(String & result, Vector const & data) + { + result = String(""); + S32 items = data.size(); + for(S32 i = 0; i < items; i++) + { + result += String::ToString(data[i]); + if(i < items-1) + result += String(" "); + } + } + + bool default_scan(const String &data, Vector & result) + { + default_vector_scan(data,result); + return true; + } + + bool default_print(String & result, Vector const & data) + { + default_vector_print(result,data); + return true; + } + + bool default_scan(const String &data, Vector & result) + { + default_vector_scan(data,result); + return true; + } + + bool default_print(String & result, Vector const & data) + { + default_vector_print(result,data); + return true; + } + + bool default_scan(const String &data, Vector & result) + { + default_vector_scan(data,result); + return true; + } + + bool default_print(String & result, Vector const & data) + { + default_vector_print(result,data); + return true; + } + + //----------------------------------------------------------------------------- + // Math - Points + //----------------------------------------------------------------------------- + bool default_scan(const String &data, Point2F & result) + { + dSscanf(data.c_str(),"%g %g",&result.x,&result.y); + return true; + } + + bool default_print(String & result, Point2F const & data) + { + result = String::ToString("%g %g",data.x,data.y); + return true; + } + + bool default_scan(const String &data, Point2I & result) + { + // Handle passed as floating point from script + if(data.find('.') != String::NPos) + { + Point2F tempResult; + dSscanf(data.c_str(),"%f %f",&tempResult.x,&tempResult.y); + result.x = mFloor(tempResult.x); + result.y = mFloor(tempResult.y); + } + else + dSscanf(data.c_str(),"%d %d",&result.x,&result.y); + return true; + } + + bool default_print(String & result, Point2I const & data) + { + result = String::ToString("%d %d",data.x,data.y); + return true; + } + + bool default_scan(const String &data, Point3F & result) + { + dSscanf(data.c_str(),"%g %g %g",&result.x,&result.y,&result.z); + return true; + } + + bool default_print(String & result, Point3F const & data) + { + result = String::ToString("%g %g %g",data.x,data.y,data.z); + return true; + } + + bool default_scan(const String &data, Point3I & result) + { + // Handle passed as floating point from script + if(data.find('.') != String::NPos) + { + Point3F tempResult; + dSscanf(data.c_str(),"%f %f %f",&tempResult.x,&tempResult.y,&tempResult.z); + result.x = mFloor(tempResult.x); + result.y = mFloor(tempResult.y); + result.z = mFloor(tempResult.z); + } + else + dSscanf(data.c_str(),"%d %d %d",&result.x,&result.y,&result.z); + return true; + } + + bool default_print(String & result, Point3I const & data) + { + result = String::ToString("%d %d %d",data.x,data.y,data.z); + return true; + } + + bool default_scan(const String &data, Point4F & result) + { + dSscanf(data.c_str(),"%g %g %g %g",&result.x,&result.y,&result.z,&result.w); + return true; + } + + bool default_print(String & result, Point4F const & data) + { + result = String::ToString("%g %g %g %g",data.x,data.y,data.z,data.w); + return true; + } + + bool default_scan(const String &data, Point4I & result) + { + // Handle passed as floating point from script + if(data.find('.') != String::NPos) + { + Point4F tempResult; + dSscanf(data.c_str(),"%f %f %f %f",&tempResult.x,&tempResult.y,&tempResult.z,&tempResult.w); + result.x = mFloor(tempResult.x); + result.y = mFloor(tempResult.y); + result.z = mFloor(tempResult.z); + result.w = mFloor(tempResult.w); + } + else + dSscanf(data.c_str(),"%d %d %d %d",&result.x,&result.y,&result.z,&result.w); + return true; + } + + bool default_print(String & result, const Point4I & data) + { + result = String::ToString("%d %d %d %d", data.x, data.y, data.z, data.w); + return true; + } + + //----------------------------------------------------------------------------- + // Math - Rectangles and boxes + //----------------------------------------------------------------------------- + bool default_scan( const String &data, RectI & result ) + { + // Handle passed as floating point from script + if(data.find('.') != String::NPos) + { + RectF tempResult; + dSscanf(data.c_str(),"%f %f %f %f",&tempResult.point.x,&tempResult.point.y,&tempResult.extent.x,&tempResult.extent.y); + result.point.x = mFloor(tempResult.point.x); + result.point.y = mFloor(tempResult.point.y); + result.extent.x = mFloor(tempResult.extent.x); + result.extent.y = mFloor(tempResult.extent.y); + } + else + dSscanf(data.c_str(),"%d %d %d %d",&result.point.x,&result.point.y,&result.extent.x,&result.extent.y); + return true; + } + bool default_print( String & result, const RectI & data ) + { + result = String::ToString("%i %i %i %i",data.point.x,data.point.y,data.extent.x,data.extent.y); + return true; + } + + bool default_scan(const String &data, RectF & result) + { + dSscanf(data.c_str(),"%g %g %g %g",&result.point.x,&result.point.y,&result.extent.x,&result.extent.y); + return true; + } + + bool default_print(String & result, const RectF & data) + { + result = String::ToString("%g %g %g %g",data.point.x,data.point.y,data.extent.x,data.extent.y); + return true; + } + + bool default_scan(const String &data, Box3F & result) + { + dSscanf(data.c_str(),"%g %g %g %g %g %g", + &result.minExtents.x,&result.minExtents.y,&result.minExtents.z, + &result.maxExtents.x,&result.maxExtents.y,&result.maxExtents.z); + return true; + } + + bool default_print(String & result, const Box3F & data) + { + result = String::ToString("%g %g %g %g %g %g", + data.minExtents.x,data.minExtents.y,data.minExtents.z, + data.maxExtents.x,data.maxExtents.y,data.maxExtents.z); + return true; + } + + //----------------------------------------------------------------------------- + + bool default_scan( const String &data, AngAxisF & result ) + { + if(StringUnit::getUnitCount(data," ") < 4) + return false; + + dSscanf(data.c_str(),"%g %g %g %g", &result.axis.x,&result.axis.y,&result.axis.z,&result.angle); + result.angle = mDegToRad(result.angle); + return true; + } + + bool default_print( String & result, const AngAxisF & data ) + { + F32 angle = mRadToDeg(data.angle); + angle = mFmod(angle + 360.0f,360.0f); + result = String::ToString("%g %g %g %g", data.axis.x, data.axis.y, data.axis.z, angle); + return true; + } + + bool default_scan( const String &data, QuatF & result ) + { + if(StringUnit::getUnitCount(data," ") < 4) + return false; + + dSscanf(data.c_str(),"%g %g %g %g", &result.x,&result.y,&result.z,&result.w); + return true; + } + + bool default_print( String & result, const QuatF & data ) + { + result = String::ToString("%g %g %g %g", data.x, data.y, data.z, data.w); + return true; + } + + bool default_scan( const String &data, MatrixF & result ) + { + if(StringUnit::getUnitCount(data," ") < 16) + return false; + + F32* m = result; + dSscanf(data.c_str(),"%g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g", + &m[result.idx(0,0)], &m[result.idx(0,1)], &m[result.idx(0,2)], &m[result.idx(0,3)], + &m[result.idx(1,0)], &m[result.idx(1,1)], &m[result.idx(1,2)], &m[result.idx(1,3)], + &m[result.idx(2,0)], &m[result.idx(2,1)], &m[result.idx(2,2)], &m[result.idx(2,3)], + &m[result.idx(3,0)], &m[result.idx(3,1)], &m[result.idx(3,2)], &m[result.idx(3,3)]); + return true; + } + + bool default_print( String & result, const MatrixF & data ) + { + const F32* m = data; + result = String::ToString("%g %g %g %g %g %g %g %g %g %g %g %g %g %g %g %g", + m[data.idx(0,0)], m[data.idx(0,1)], m[data.idx(0,2)], m[data.idx(0,3)], + m[data.idx(1,0)], m[data.idx(1,1)], m[data.idx(1,2)], m[data.idx(1,3)], + m[data.idx(2,0)], m[data.idx(2,1)], m[data.idx(2,2)], m[data.idx(2,3)], + m[data.idx(3,0)], m[data.idx(3,1)], m[data.idx(3,2)], m[data.idx(3,3)]); + return true; + } + + //----------------------------------------------------------------------------- + // Colors + //----------------------------------------------------------------------------- + bool default_scan(const String &data, ColorF & result) + { + if(StringUnit::getUnitCount(data," ") == 3) + { + dSscanf(data.c_str(),"%g %g %g",&result.red,&result.green,&result.blue); + result.alpha = 1.0f; + } + else + dSscanf(data.c_str(),"%g %g %g %g",&result.red,&result.green,&result.blue,&result.alpha); + return true; + } + + bool default_print(String & result, ColorF const & data) + { + if(data.alpha == 1.0f) + result = String::ToString("%g %g %g",data.red,data.green,data.blue); + else + result = String::ToString("%g %g %g %g",data.red,data.green,data.blue,data.alpha); + return true; + } + + bool default_scan(const String &data, ColorI & result) + { + if(StringUnit::getUnitCount(data," ") == 3) + { + S32 r,g,b; + dSscanf(data.c_str(),"%i %i %i",&r,&g,&b); + result.set(r,g,b); + } + else + { + S32 r,g,b,a; + dSscanf(data.c_str(),"%i %i %i %i",&r,&g,&b,&a); + result.set(r,g,b,a); + } + return true; + } + + bool default_print(String & result, const ColorI & data) + { + if(data.alpha == 255) + result = String::ToString("%d %d %d",data.red,data.green,data.blue); + else + result = String::ToString("%d %d %d %d",data.red,data.green,data.blue,data.alpha); + return true; + } + + //----------------------------------------------------------------------------- + // String + //----------------------------------------------------------------------------- + bool default_scan(const String &data, String & result) + { + result = data; + return true; + } + + bool default_print(String & result, const String & data) + { + result = data; + return true; + } + + //----------------------------------------------------------------------------- + // FileName + //----------------------------------------------------------------------------- + bool default_scan(const String &data, FileName & result) + { + char buffer[1024]; + + if(data.c_str()[0] == '$') + { + dStrncpy(buffer, data.c_str(), sizeof(buffer) - 1); + buffer[sizeof(buffer)-1] = 0; + } + else if (!Con::expandScriptFilename(buffer, sizeof(buffer), data)) + { + Con::warnf("(TypeFilename) illegal filename detected: %s", data.c_str()); + return false; + } + result = String(buffer); + return true; + } + + bool default_print(String & result, const FileName & data) + { + result = data; + return true; + } + + //----------------------------------------------------------------------------- + // SimObject + //----------------------------------------------------------------------------- + bool default_scan(const String &data, SimObject * & result) + { + result = Sim::findObject(data); + return result != NULL; + } + + bool default_print(String & result, SimObject * const & data) + { + if(data) + { + if(String(data->getName()).isEmpty()) + result = data->getIdString(); + else + result = data->getName(); + return true; + } + return false; + } + + //----------------------------------------------------------------------------- + // Print scan ints of various sizes as hex + //----------------------------------------------------------------------------- + + //------- + // 16 bit + //------- + + bool hex_scan(const String & string, U32 & hex) + { + dSscanf(string.c_str(),"%i", &hex); + return true; + } + + bool hex_print(String & string, const U32 & hex) + { + string = String::ToString("0x%X",hex); + return true; + } + + bool hex_scan(const String & string, S32 & hex) + { + dSscanf(string.c_str(),"%i", &hex); + return true; + } + + bool hex_print(String & string, const S32 & hex) + { + string = String::ToString("0x%X",hex); + return true; + } + + //------- + // 16 bit + //------- + + bool hex_scan(const String & string, U16 & hex) + { + U32 tmp; + bool ret = hex_scan(string,tmp); + hex = tmp; + return ret; + } + + bool hex_print(String & string, const U16 & hex) + { + U32 tmp = hex; + return hex_print(string,tmp); + } + + bool hex_scan(const String & string, S16 & hex) + { + S32 tmp; + bool ret = hex_scan(string,tmp); + hex = tmp; + return ret; + } + + bool hex_print(String & string, const S16 & hex) + { + U32 tmp = hex; + return hex_print(string,tmp); + } + + //------- + // 8 bit + //------- + + bool hex_scan(const String & string, U8 & hex) + { + U32 tmp; + bool ret = hex_scan(string,tmp); + hex = tmp; + return ret; + } + + bool hex_print(String & string, const U8 & hex) + { + U32 tmp = hex; + return hex_print(string,tmp); + } + + bool hex_scan(const String & string, S8 & hex) + { + S32 tmp; + bool ret = hex_scan(string,tmp); + hex = tmp; + return ret; + } + + bool hex_print(String & string, const S8 & hex) + { + U32 tmp = hex; + return hex_print(string,tmp); + } + +} diff --git a/console/propertyParsing.h b/console/propertyParsing.h new file mode 100644 index 0000000..7bb8198 --- /dev/null +++ b/console/propertyParsing.h @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROPERTYPARSING_H_ +#define _PROPERTYPARSING_H_ + +class ColorI; +class ColorF; +class Point2I; +class Point2F; +class Point3F; +class Point4F; +class Point3I; +class Point4I; +class RectI; +class RectF; +class Box3I; +class Box3F; +class MatrixF; +class AngAxisF; +class QuatF; +class String; +class FileName; +class SimObject; +class SimObjectType; +template class Vector; + +//----------------------------------------------------------------------------- +// String scan/print methods +// +// Macros in propertyImplementation.h point the ConcretePropertyDefinition scan/print delegates to these functions. +// +namespace PropertyInfo +{ + // String + bool default_scan(const String &data, String & result); + bool default_print(String & result, const String & data); + + // Bool + bool default_scan(const String &data, bool & result); + bool default_print(String & result, const bool & data); + + // S32/F32/U32 + bool default_scan(const String &data, F32 & result); + bool default_print(String & result, const F32 & data); + bool default_scan(const String &data, U32 & result); + bool default_print(String & result, const U32 & data); + bool default_scan(const String &data, S32 & result); + bool default_print(String & result, const S32 & data); + + // Vector + bool default_scan(const String &data, Vector & result); + bool default_print(String & result, const Vector & data); + bool default_scan(const String &data, Vector & result); + bool default_print(String & result, const Vector & data); + bool default_scan(const String &data, Vector & result); + bool default_print(String & result, const Vector & data); + + // Math Points + bool default_scan(const String &data, Point2F & result); + bool default_print(String & result, const Point2F & data); + bool default_scan(const String &data, Point2I & result); + bool default_print(String & result, const Point2I & data); + bool default_scan(const String &data, Point3F & result); + bool default_print(String & result, const Point3F & data); + bool default_scan(const String &data, Point3I & result); + bool default_print(String & result, const Point3I & data); + bool default_scan(const String &data, Point4F & result); + bool default_print(String & result, const Point4F & data); + bool default_scan(const String &data, Point4I & result); + bool default_print(String & result, const Point4I & data); + + // Math Boxs & Rectangles + bool default_scan(const String &data, RectI & result); + bool default_print(String & result, const RectI & data); + bool default_scan(const String &data, RectF & result); + bool default_print(String & result, const RectF & data); + bool default_scan(const String &data, Box3I & result); + bool default_print(String & result, const Box3I & data); + bool default_scan(const String &data, Box3F & result); + bool default_print(String & result, const Box3F & data); + + //----------------------------------------------------------------------------- + bool default_scan( const String &data, AngAxisF & result ); + bool default_print( String & result, const AngAxisF & data ); + + bool default_scan( const String &data, QuatF & result ); + bool default_print( String & result, const QuatF & data ); + + bool default_scan( const String &data, MatrixF & result ); + bool default_print( String & result, const MatrixF & data ); + + // Colors + bool default_scan(const String &data, ColorF & result); + bool default_print(String & result, const ColorF & data); + bool default_scan(const String &data, ColorI & result); + bool default_print(String & result, const ColorI & data); + + // filename handler + bool default_scan(const String &data, FileName & result); + bool default_print(String & result, const FileName & data); + + // SimObjectType + bool default_scan(const String &data, SimObjectType & result); + bool default_print(String & result, SimObjectType const & data); + + // SimObject + bool default_scan(const String &data, SimObject * & result); + bool default_print(String & result, SimObject * const & data); + + template + inline bool typed_simobject_scan(const String &data, T * & result) + { + SimObject * obj; + result = default_scan(data,obj)? dynamic_cast(obj) : NULL; + return result; + } + + template + inline bool typed_simobject_print(String & result, T * const & data) + { + return default_print(result,data); + } + + bool hex_scan(const String & string, U32 & hex); + bool hex_print(String & string, const U32 & hex); + bool hex_scan(const String & string, S32 & hex); + bool hex_print(String & string, const S32 & hex); + + bool hex_scan(const String & string, U16 & hex); + bool hex_print(String & string, const U16 & hex); + bool hex_scan(const String & string, S16 & hex); + bool hex_print(String & string, const S16 & hex); + + bool hex_scan(const String & string, U8 & hex); + bool hex_print(String & string, const U8 & hex); + bool hex_scan(const String & string, S8 & hex); + bool hex_print(String & string, const S8 & hex); + + bool default_print(String & result, SimObjectType * const & data); +} + +// Default Scan/print definition +#define DEFINE_PROPERTY_DEFAULT_PRINT(dataType) bool PropertyInfo::default_print(String & resultString, dataType const & dataTyped) +#define DEFINE_PROPERTY_DEFAULT_SCAN(dataType) bool PropertyInfo::default_scan(const String &dataString, dataType & resultTyped) + +#endif // _PROPERTYPARSING_H_ diff --git a/console/runtimeClassRep.cpp b/console/runtimeClassRep.cpp new file mode 100644 index 0000000..9d69dfe --- /dev/null +++ b/console/runtimeClassRep.cpp @@ -0,0 +1,9 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/runtimeClassRep.h" + +//----------------------------------------------------------------------------- + diff --git a/console/runtimeClassRep.h b/console/runtimeClassRep.h new file mode 100644 index 0000000..0351256 --- /dev/null +++ b/console/runtimeClassRep.h @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RUNTIME_CLASSREP_H_ +#define _RUNTIME_CLASSREP_H_ + +#include "console/consoleObject.h" +#include "console/consoleInternal.h" + +/// This class is to allow new types to be added, at run-time, to Torque. +/// This is primarily for dynamic libraries, plugins etc. +/// +/// The idea is the same, one instance per type which is getting registered +/// however it doesn't get added to the dictionary until it is told to +/// add itself. It can be removed, as well, though no safe-execution +/// assurances are made by this class. If an object type is removed +/// behavior of existing console objects of that type is undefined. +/// (aka bad stuff will probably happen but I don't know exactly what) +template +class RuntimeClassRep : public AbstractClassRep +{ +protected: + static bool smConRegistered; ///< This variable will be set to true if this class-rep is currently registered + +public: + RuntimeClassRep( const char *name, S32 netClassGroupMask, S32 netClassType, S32 netEventDir, AbstractClassRep *parent ) + { + // name is a static compiler string so no need to worry about copying or deleting + mClassName = name; + + // Clean up mClassId + for( U32 i = 0; i < NetClassGroupsCount; i++ ) + mClassId[i] = -1; + + // Set properties for this ACR + mClassType = netClassType; + mClassGroupMask = netClassGroupMask; + mNetEventDir = netEventDir; + parentClass = parent; + }; + + /// Perform class specific initialization tasks. + /// + /// Link namespaces, call initPersistFields() and consoleInit(). + void init() const + { + // Get handle to our parent class, if any, and ourselves (we are our parent's child). + AbstractClassRep *parent = T::getParentStaticClassRep(); + AbstractClassRep *child = T::getStaticClassRep (); + + // If we got reps, then link those namespaces! (To get proper inheritance.) + if( parent && child ) + Con::classLinkNamespaces( parent->getNameSpace(), child->getNameSpace() ); + + // Finally, do any class specific initialization... + T::initPersistFields(); + T::consoleInit(); + } + + /// Wrap constructor. + ConsoleObject *create() const { return new T; } + + //----------------------------------------------------------------------------- + + /// Register this class with the Torque console + void consoleRegister() + { + AssertFatal( !smConRegistered, "Calling consoleRegister, but this type is already linked into the class list" ); + + if( !smConRegistered ) + registerClassRep( this ); + + // Now initialize the namespace + mNamespace = Con::lookupNamespace( StringTable->insert( getClassName() ) ); + mNamespace->mClassRep = this; + + // Perform field initialization + sg_tempFieldList.setSize(0); + + init(); + + // So if we have things in it, copy it over... + if ( sg_tempFieldList.size() != 0 ) + mFieldList = sg_tempFieldList; + + // And of course delete it every round. + sg_tempFieldList.clear(); + + smConRegistered = true; + } + + /// Unregister this class with the Torque console + void consoleUnRegister() + { + AssertFatal( smConRegistered, "Calling consoleUnRegister, but this type is not linked into the class list" ); + if( !smConRegistered ) + return; + + removeClassRep( this ); + smConRegistered = false; + } + + /// Returns true if this class type is registered with the console system + static const bool isRegistered() { return smConRegistered; } +}; + +template bool RuntimeClassRep::smConRegistered = false; + +//----------------------------------------------------------------------------- + +#define DECLARE_RUNTIME_CONOBJECT(className) \ + static RuntimeClassRep dynRTClassRep; \ + static AbstractClassRep* getParentStaticClassRep(); \ + static AbstractClassRep* getStaticClassRep(); \ + virtual AbstractClassRep* getClassRep() const + +#define IMPLEMENT_RUNTIME_CONOBJECT(className) \ + AbstractClassRep* className::getClassRep() const { return &className::dynRTClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynRTClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + RuntimeClassRep className::dynRTClassRep(#className, 0, -1, 0, className::getParentStaticClassRep()) + +#endif \ No newline at end of file diff --git a/console/scriptFilename.cpp b/console/scriptFilename.cpp new file mode 100644 index 0000000..f24a874 --- /dev/null +++ b/console/scriptFilename.cpp @@ -0,0 +1,367 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/scriptFilename.h" + +#include "core/frameAllocator.h" +#include "core/tSimpleHashTable.h" +#include "core/strings/stringFunctions.h" +#include "core/stringTable.h" +#include "console/console.h" +#include "console/compiler.h" + + +namespace Con +{ +//----------------------------------------------------------------------------- +// Local Globals +//----------------------------------------------------------------------------- + +struct PathExpando +{ + StringTableEntry mPath; + bool mIsToolsOnly; +}; + +static SimpleHashTable sgPathExpandos(64, false); + +//----------------------------------------------------------------------------- +// Global Functions +//----------------------------------------------------------------------------- + +void setScriptPathExpando(const char *expando, const char *path, bool toolsOnly /*= false*/) +{ + PathExpando *exp = sgPathExpandos.retreive(expando); + if(exp) + { + exp->mPath = StringTable->insert(path); + exp->mIsToolsOnly = toolsOnly; + } + else + { + exp = new PathExpando; + exp->mPath = StringTable->insert(path); + exp->mIsToolsOnly = toolsOnly; + sgPathExpandos.insert(exp, expando); + } +} + +void removeScriptPathExpando(const char *expando) +{ + PathExpando *exp = sgPathExpandos.remove(expando); + if(exp) + delete exp; +} + +bool isScriptPathExpando(const char *expando) +{ + PathExpando *exp = sgPathExpandos.retreive(expando); + return ( exp != NULL); +} + +//----------------------------------------------------------------------------- + +// [tom, 5/18/2006] FIXME: This needs some bounds checking +bool expandToolScriptFilename(char *filename, U32 size, const char *src) +{ + // [tom, 10/16/2006] Note: I am purposefully not early-outing here in the + // same way the old code did as it is now possible that something could + // be expanded if the name or mod is NULL. This was previously not the case. + + const StringTableEntry cbMod = CodeBlock::getCurrentCodeBlockModName(); + const StringTableEntry cbFullPath = CodeBlock::getCurrentCodeBlockFullPath(); + + char varBuf[1024], modBuf[1024]; + const char *ptr = src; + char *retPtr = filename; + char *slash; + + const char *catPath = NULL; + +#ifndef TORQUE_DEBUG + bool isTools = Con::isCurrentScriptToolScript(); +#endif + + // Check leading character + switch(*ptr) + { + case '^': + { + // Variable + const char *varPtr = ptr+1; + char *insertPtr = varBuf; + bool valid = true; + while(*varPtr != '/') + { + if(*varPtr == 0) + { + valid = false; + break; + } + *insertPtr++ = *varPtr++; + } + + if(valid) + { + // Got a valid variable + *insertPtr = 0; + + PathExpando *exp = sgPathExpandos.retreive(varBuf); + + if(exp == NULL) + { + Con::errorf("expandScriptFilename - Ignoring invalid path expando \"%s\"", varBuf); + break; + } + +#ifndef TORQUE_DEBUG + // [tom, 12/13/2006] This stops tools expandos from working in the console, + // which is useful behavior when debugging so I'm ifdefing this out for debug builds. + + if(! isTools && exp->mIsToolsOnly) + { + Con::errorf("expandScriptFilename - attempting to use tools only expando \"%s\" from outside of tools", varBuf); + + *filename = 0; + return false; + } +#endif + + catPath = exp ? exp->mPath : ""; + // swallow the expando and the slash after the expando + ptr += dStrlen(varBuf) + 1; + if(*ptr == '/') + ptr++; + } + } + break; + + case '~': + // Relative to mod + if(cbMod && cbFullPath) + { + Platform::makeFullPathName(cbMod, modBuf, sizeof(modBuf)); + catPath = modBuf; + } + else + { + // Probably not a mod, so we'll use the ^game expando + PathExpando *exp = sgPathExpandos.retreive("game"); + + if(exp == NULL) + { + Con::errorf("expandScriptFilename - ~ expansion failed for mod and ^game when processing '%s'", src); + break; + } + + catPath = exp ? exp->mPath : ""; + } + // swallow ~ and optional slash + switch(ptr[1]) + { + case '/': ptr += 2; break; + default: ptr++; + } + + break; + + case '.': + // Relative to script directory + if(cbFullPath) + { + dStrcpy(varBuf, cbFullPath); + slash = dStrrchr(varBuf, '/'); + if(slash) *slash = 0; + + catPath = varBuf; + + // swallow dot and optional slash, but dont swallow .. relative path token + switch(ptr[1]) + { + case '.': break; + case '/': ptr += 2; break; + default: ptr++; + } + } + break; + } + + // [tom, 11/20/2006] Handing off to makeFullPathName() allows us to process .. correctly. + Platform::makeFullPathName(ptr, retPtr, size, catPath); + + return true; +} + +//----------------------------------------------------------------------------- + +bool expandOldScriptFilename(char *filename, U32 size, const char *src) +{ + const StringTableEntry cbName = CodeBlock::getCurrentCodeBlockName(); + if (!cbName) + { + dStrcpy(filename, src); + return true; + } + + const char *slash; + if (dStrncmp(src, "~/", 2) == 0) + // tilde path means load from current codeblock/mod root + slash = dStrchr(cbName, '/'); + else if (dStrncmp(src, "./", 2) == 0) + // dot path means load from current codeblock/mod path + slash = dStrrchr(cbName, '/'); + else + { + // otherwise path must be fully specified + if (dStrlen(src) > size) + { + Con::errorf("Buffer overflow attempting to expand filename: %s", src); + *filename = 0; + return false; + } + dStrcpy(filename, src); + return true; + } + + if (slash == NULL) + { + Con::errorf("Illegal CodeBlock path detected (no mod directory): %s", cbName); + *filename = 0; + return false; + } + + U32 length = slash-cbName; + if ((length+dStrlen(src)) > size) + { + Con::errorf("Buffer overflow attempting to expand filename: %s", src); + *filename = 0; + return false; + } + + dStrncpy(filename, cbName, length); + dStrcpy(filename+length, src+1); + return true; +} + +//----------------------------------------------------------------------------- + +bool expandScriptFilename(char *filename, U32 size, const char *src) +{ +#ifndef TORQUE2D_TOOLS_FIXME + return expandOldScriptFilename(filename, size, src); +#else + return expandToolScriptFilename(filename, size, src); +#endif +} + +//----------------------------------------------------------------------------- + +static StringTableEntry tryGetBasePath(const char *path, const char *base) +{ + U32 len = dStrlen(base); + if(dStrnicmp(path, base, len) == 0) + { + if(*(path + len) == '/') --len; + return StringTable->insertn(path, len, true); + } + return NULL; +} + +struct CollapseTest +{ + const char *path; + const char *replace; +}; + +bool collapseScriptFilename(char *filename, U32 size, const char *src) +{ + PathExpando *tools = sgPathExpandos.retreive("tools"); + PathExpando *game = sgPathExpandos.retreive("game"); + + CollapseTest test[]= + { + { game ? game->mPath : NULL, "~" }, + { tools ? tools->mPath : NULL, "^tools" }, + { Platform::getCurrentDirectory(), "" }, + { Platform::getMainDotCsDir(), "" }, + { NULL, NULL } + }; + + for(S32 i = 0;!(test[i].path == NULL && test[i].replace == NULL);++i) + { + if(test[i].path == NULL) continue; + + StringTableEntry base = tryGetBasePath(src, test[i].path); + if(base == NULL) + continue; + + StringTableEntry rel = Platform::makeRelativePathName(src, test[i].path); + + *filename = 0; + if(*test[i].replace) + dSprintf(filename, size, "%s/", test[i].replace); + dStrcat(filename, rel); + return true; + } + + dStrncpy(filename, src, size - 1); + filename[size-1] = 0; + return true; +} + +//----------------------------------------------------------------------------- + +} // end namespace Con + +//----------------------------------------------------------------------------- +// Console Functions +//----------------------------------------------------------------------------- + +ConsoleFunction(expandFilename, const char*, 2, 2, "(string filename)") +{ + TORQUE_UNUSED(argc); + char* ret = Con::getReturnBuffer( 1024 ); + Con::expandScriptFilename(ret, 1024, argv[1]); + return ret; +} + +ConsoleFunction(expandOldFilename, const char*, 2, 2, "(string filename)") +{ + TORQUE_UNUSED(argc); + char* ret = Con::getReturnBuffer( 1024 ); + Con::expandOldScriptFilename(ret, 1024, argv[1]); + return ret; +} + +//----------------------------------------------------------------------------- +// Tool Functions +//----------------------------------------------------------------------------- + +ConsoleToolFunction(collapseFilename, const char*, 2, 2, "(string filename)") +{ + TORQUE_UNUSED(argc); + char* ret = Con::getReturnBuffer( 1024 ); + Con::collapseScriptFilename(ret, 1024, argv[1]); + return ret; +} + +ConsoleToolFunction(setScriptPathExpando, void, 3, 4, "(string expando, string path[, bool toolsOnly])") +{ + if(argc == 4) + Con::setScriptPathExpando(argv[1], argv[2], dAtob(argv[3])); + else + Con::setScriptPathExpando(argv[1], argv[2]); +} + +ConsoleToolFunction(removeScriptPathExpando, void, 2, 2, "(string expando)") +{ + Con::removeScriptPathExpando(argv[1]); +} + +ConsoleToolFunction(isScriptPathExpando, bool, 2, 2, "(string expando)") +{ + return Con::isScriptPathExpando(argv[1]); +} diff --git a/console/scriptFilename.h b/console/scriptFilename.h new file mode 100644 index 0000000..a913889 --- /dev/null +++ b/console/scriptFilename.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCRIPTFILENAME_H_ +#define _SCRIPTFILENAME_H_ + +namespace Con +{ + +extern void setScriptPathExpando(const char *expando, const char *path, bool toolsOnly = false); +extern void removeScriptPathExpando(const char *expando); +extern bool isScriptPathExpando(const char *expando); + +} // end namespace Con + +#endif // _SCRIPTFILENAME_H_ diff --git a/console/scriptObjects.cpp b/console/scriptObjects.cpp new file mode 100644 index 0000000..5b2af75 --- /dev/null +++ b/console/scriptObjects.cpp @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simBase.h" +#include "console/consoleTypes.h" +#include "console/scriptObjects.h" +#include "console/simBase.h" + +IMPLEMENT_CONOBJECT(ScriptObject); + +ScriptObject::ScriptObject() +{ + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +bool ScriptObject::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // Call onAdd in script! + Con::executef(this, "onAdd", Con::getIntArg(getId())); + return true; +} + +void ScriptObject::onRemove() +{ + // We call this on this objects namespace so we unlink them after. - jdd + // + // Call onRemove in script! + Con::executef(this, "onRemove", Con::getIntArg(getId())); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- +// Script group placeholder +//----------------------------------------------------------------------------- + +class ScriptGroup : public SimGroup +{ + typedef SimGroup Parent; + +public: + ScriptGroup(); + bool onAdd(); + void onRemove(); + + DECLARE_CONOBJECT(ScriptGroup); +}; + +IMPLEMENT_CONOBJECT(ScriptGroup); + +ScriptGroup::ScriptGroup() +{ + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +bool ScriptGroup::onAdd() +{ + if (!Parent::onAdd()) + return false; + + // Call onAdd in script! + Con::executef(this, "onAdd", Con::getIntArg(getId())); + return true; +} + +void ScriptGroup::onRemove() +{ + // Call onRemove in script! + Con::executef(this, "onRemove", Con::getIntArg(getId())); + + Parent::onRemove(); +} diff --git a/console/scriptObjects.h b/console/scriptObjects.h new file mode 100644 index 0000000..65f72b1 --- /dev/null +++ b/console/scriptObjects.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCRIPTOBJECTS_H_ +#define _SCRIPTOBJECTS_H_ + +#ifndef _CONSOLEINTERNAL_H_ +#include "console/consoleInternal.h" +#endif + +//----------------------------------------------------------------------------- +// Script object placeholder +//----------------------------------------------------------------------------- + +class ScriptObject : public SimObject +{ + typedef SimObject Parent; + +public: + ScriptObject(); + bool onAdd(); + void onRemove(); + + DECLARE_CONOBJECT(ScriptObject); +}; + +#endif \ No newline at end of file diff --git a/console/sim.cpp b/console/sim.cpp new file mode 100644 index 0000000..ef21a57 --- /dev/null +++ b/console/sim.cpp @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" + +#include "console/sim.h" +#include "console/simEvents.h" +#include "console/simObject.h" +#include "console/simSet.h" + +namespace Sim +{ + // Don't forget to InstantiateNamed* in simManager.cc - DMM + ImplementNamedSet(ActiveActionMapSet) + ImplementNamedSet(GhostAlwaysSet) + ImplementNamedSet(WayPointSet) + ImplementNamedSet(fxReplicatorSet) + ImplementNamedSet(fxFoliageSet) + ImplementNamedSet(BehaviorSet) + ImplementNamedSet(MaterialSet) + ImplementNamedSet(SFXSourceSet) + ImplementNamedSet(TerrainMaterialSet) + ImplementNamedGroup(ActionMapGroup) + ImplementNamedGroup(ClientGroup) + ImplementNamedGroup(GuiGroup) + ImplementNamedGroup(GuiDataGroup) + ImplementNamedGroup(TCPGroup) + + //groups created on the client + ImplementNamedGroup(ClientConnectionGroup) + ImplementNamedGroup(ChunkFileGroup) + ImplementNamedSet(sgMissionLightingFilterSet) +} + +//----------------------------------------------------------------------------- +// Console Functions +//----------------------------------------------------------------------------- + +ConsoleFunctionGroupBegin ( SimFunctions, "Functions relating to Sim."); + +ConsoleFunction(nameToID, S32, 2, 2, "nameToID(object)") +{ + TORQUE_UNUSED(argc); + SimObject *obj = Sim::findObject(argv[1]); + if(obj) + return obj->getId(); + else + return -1; +} + +ConsoleFunction(isObject, bool, 2, 2, "isObject(object)") +{ + TORQUE_UNUSED(argc); + if (!dStrcmp(argv[1], "0") || !dStrcmp(argv[1], "")) + return false; + else + return (Sim::findObject(argv[1]) != NULL); +} + +ConsoleFunction(spawnObject, S32, 3, 6, "spawnObject(class [, dataBlock, name, properties, script])") +{ + String spawnClass(argv[1]); + String spawnDataBlock; + String spawnName; + String spawnProperties; + String spawnScript; + + if (argc >= 3) + spawnDataBlock = argv[2]; + if (argc >= 4) + spawnName = argv[3]; + if (argc >= 5) + spawnProperties = argv[4]; + if (argc >= 6) + spawnScript = argv[5]; + + SimObject* spawnObject = Sim::spawnObject(spawnClass, spawnDataBlock, spawnName, spawnProperties, spawnScript); + + if (spawnObject) + return spawnObject->getId(); + else + return -1; +} + +ConsoleFunction(cancel,void,2,2,"cancel(eventId)") +{ + TORQUE_UNUSED(argc); + Sim::cancelEvent(dAtoi(argv[1])); +} + +ConsoleFunction(isEventPending, bool, 2, 2, "isEventPending(%scheduleId);") +{ + TORQUE_UNUSED(argc); + return Sim::isEventPending(dAtoi(argv[1])); +} + +ConsoleFunction(getEventTimeLeft, S32, 2, 2, "getEventTimeLeft(scheduleId) Get the time left in ms until this event will trigger.") +{ + return Sim::getEventTimeLeft(dAtoi(argv[1])); +} + +ConsoleFunction(getScheduleDuration, S32, 2, 2, "getScheduleDuration(%scheduleId);") +{ + TORQUE_UNUSED(argc); S32 ret = Sim::getScheduleDuration(dAtoi(argv[1])); + return ret; +} + +ConsoleFunction(getTimeSinceStart, S32, 2, 2, "getTimeSinceStart(%scheduleId);") +{ + TORQUE_UNUSED(argc); S32 ret = Sim::getTimeSinceStart(dAtoi(argv[1])); + return ret; +} + +ConsoleFunction(schedule, S32, 4, 0, "schedule(time, refobject|0, command, )") +{ + U32 timeDelta = U32(dAtof(argv[1])); + SimObject *refObject = Sim::findObject(argv[2]); + if(!refObject) + { + if(argv[2][0] != '0') + return 0; + + refObject = Sim::getRootGroup(); + } + SimConsoleEvent *evt = new SimConsoleEvent(argc - 3, argv + 3, false); + + S32 ret = Sim::postEvent(refObject, evt, Sim::getCurrentTime() + timeDelta); + // #ifdef DEBUG + // Con::printf("ref %s schedule(%s) = %d", argv[2], argv[3], ret); + // Con::executef( "backtrace"); + // #endif + return ret; +} + +ConsoleFunction(getUniqueName, const char*, 2, 2, + "( String baseName )\n" + "Returns a unique unused SimObject name based on a given base name.") +{ + String outName = Sim::getUniqueName( argv[1] ); + + if ( outName.isEmpty() ) + return NULL; + + char *buffer = Con::getReturnBuffer( outName.size() ); + dStrcpy( buffer, outName ); + + return buffer; +} + +ConsoleFunctionGroupEnd( SimFunctions ); diff --git a/console/sim.h b/console/sim.h new file mode 100644 index 0000000..6190f90 --- /dev/null +++ b/console/sim.h @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIM_H_ +#define _SIM_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +// Forward Refs +class SimSet; +class SimGroup; +class SimDataBlockGroup; +class SimObject; +class SimEvent; +class Stream; + +// Sim Types +typedef U32 SimTime; +typedef U32 SimObjectId; + +/// Definition of some basic Sim system constants. +/// +/// These constants define the range of ids assigned to datablocks +/// (DataBlockObjectIdFirst - DataBlockObjectIdLast), and the number +/// of bits used to store datablock IDs. +/// +/// Normal Sim objects are given the range of IDs starting at +/// DynamicObjectIdFirst and going to infinity. Sim objects use +/// a SimObjectId to represent their ID; this is currently a U32. +/// +/// The RootGroupId is assigned to gRootGroup, in which most SimObjects +/// are addded as child members. See simManager.cc for details, particularly +/// Sim::initRoot() and following. +enum SimObjectsConstants +{ + DataBlockObjectIdFirst = 3, + DataBlockObjectIdBitSize = 10, + DataBlockObjectIdLast = DataBlockObjectIdFirst + (1 << DataBlockObjectIdBitSize) - 1, + + MessageObjectIdFirst = DataBlockObjectIdLast + 1, + MessageObjectIdBitSize = 6, + MessageObjectIdLast = MessageObjectIdFirst + (1 << MessageObjectIdBitSize) - 1, + + DynamicObjectIdFirst = MessageObjectIdLast + 1, + InvalidEventId = 0, + RootGroupId = 0xFFFFFFFF, +}; + +//--------------------------------------------------------------------------- + +/// @defgroup simbase_helpermacros Helper Macros +/// +/// These are used for named sets and groups in the manager. +/// @{ +#define DeclareNamedSet(set) extern SimSet *g##set;inline SimSet *get##set() { return g##set; } +#define DeclareNamedGroup(set) extern SimGroup *g##set;inline SimGroup *get##set() { return g##set; } +#define ImplementNamedSet(set) SimSet *g##set; +#define ImplementNamedGroup(set) SimGroup *g##set; +/// @} + +//--------------------------------------------------------------------------- + +namespace Sim +{ + DeclareNamedSet(ActiveActionMapSet) + DeclareNamedSet(GhostAlwaysSet) + DeclareNamedSet(WayPointSet) + DeclareNamedSet(fxReplicatorSet) + DeclareNamedSet(fxFoliageSet) + DeclareNamedSet(BehaviorSet) + DeclareNamedSet(MaterialSet) + DeclareNamedSet(SFXSourceSet); + DeclareNamedSet(TerrainMaterialSet); + DeclareNamedGroup(ActionMapGroup) + DeclareNamedGroup(ClientGroup) + DeclareNamedGroup(GuiGroup) + DeclareNamedGroup(GuiDataGroup) + DeclareNamedGroup(TCPGroup) + DeclareNamedGroup(ClientConnectionGroup) + DeclareNamedGroup(ChunkFileGroup); + + DeclareNamedSet(sgMissionLightingFilterSet); + + void init(); + void shutdown(); + + SimDataBlockGroup *getDataBlockGroup(); + SimGroup* getRootGroup(); + + SimObject* findObject(SimObjectId); + SimObject* findObject(const char* name); + SimObject* findObject(const char* fileName, S32 declarationLine); + template inline bool findObject(SimObjectId iD,T*&t) + { + t = dynamic_cast(findObject(iD)); + return t != NULL; + } + template inline bool findObject(const char *objectName,T*&t) + { + t = dynamic_cast(findObject(objectName)); + return t != NULL; + } + + SimObject *spawnObject(String spawnClass, + String spawnDataBlock = String::EmptyString, + String spawnName = String::EmptyString, + String spawnProperties = String::EmptyString, + String spawnScript = String::EmptyString); + + void advanceToTime(SimTime time); + void advanceTime(SimTime delta); + SimTime getCurrentTime(); + SimTime getTargetTime(); + + /// a target time of 0 on an event means current event + U32 postEvent(SimObject*, SimEvent*, U32 targetTime); + + inline U32 postEvent(SimObjectId iD,SimEvent*evt, U32 targetTime) + { + return postEvent(findObject(iD), evt, targetTime); + } + inline U32 postEvent(const char *objectName,SimEvent*evt, U32 targetTime) + { + return postEvent(findObject(objectName), evt, targetTime); + } + inline U32 postCurrentEvent(SimObject*obj, SimEvent*evt) + { + return postEvent(obj,evt,getCurrentTime()); + } + inline U32 postCurrentEvent(SimObjectId obj,SimEvent*evt) + { + return postEvent(obj,evt,getCurrentTime()); + } + inline U32 postCurrentEvent(const char *obj,SimEvent*evt) + { + return postEvent(obj,evt,getCurrentTime()); + } + + void cancelEvent(U32 eventId); + bool isEventPending(U32 eventId); + U32 getEventTimeLeft(U32 eventId); + U32 getTimeSinceStart(U32 eventId); + U32 getScheduleDuration(U32 eventId); + + // Appends numbers to the inName until an unused SimObject name is created + String getUniqueName( const char *inName ); + + bool saveObject(SimObject *obj, Stream *stream); + SimObject *loadObjectStream(Stream *stream); + + bool saveObject(SimObject *obj, const char *filename); + SimObject *loadObjectStream(const char *filename); +} + +//---------------------------------------------------------------------------- + +#define DECLARE_CONSOLETYPE(T) \ + DefineConsoleType( Type##T##Ptr, T * ) + +#define IMPLEMENT_CONSOLETYPE(T) \ + DatablockConsoleType( T##Ptr, Type##T##Ptr, sizeof(T*), T ) + +#define IMPLEMENT_SETDATATYPE(T) \ + ConsoleSetType( Type##T##Ptr ) \ +{ \ + volatile SimDataBlock* pConstraint = static_cast((T*)NULL); \ + (void)pConstraint; \ + if (argc == 1) { \ + *reinterpret_cast(dptr) = NULL; \ + if (argv[0] && argv[0][0] && !Sim::findObject(argv[0],*reinterpret_cast(dptr))) \ + Con::printf("Object '%s' is not a member of the '%s' data block class", argv[0], #T); \ + } \ + else \ + Con::printf("Cannot set multiple args to a single pointer."); \ +} + +#define IMPLEMENT_GETDATATYPE(T) \ + ConsoleGetType( Type##T##Ptr ) \ +{ \ + volatile SimDataBlock* pConstraint = static_cast((T*)NULL); \ + (void)pConstraint; \ + T** obj = reinterpret_cast(dptr); \ + char* returnBuffer = Con::getReturnBuffer(128); \ + dSprintf(returnBuffer, 128, "%s", *obj && (*obj)->getName() ? (*obj)->getName() : "");\ + return returnBuffer; \ +} + +#endif // _SIM_H_ diff --git a/console/simBase.h b/console/simBase.h new file mode 100644 index 0000000..e2e4a95 --- /dev/null +++ b/console/simBase.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMBASE_H_ +#define _SIMBASE_H_ + +// This header is now deprecated. New code should include the relevant headers +// for what they need. simBase.h remains solely to avoid having to change a ton +// of existing code. + +#include "console/simObjectList.h" +#include "console/simEvents.h" +#include "console/simFieldDictionary.h" +#include "console/simObject.h" +#include "console/simSet.h" +#include "console/simDatablock.h" + +#endif diff --git a/console/simDatablock.cpp b/console/simDatablock.cpp new file mode 100644 index 0000000..93a0ec7 --- /dev/null +++ b/console/simDatablock.cpp @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simDatablock.h" + +#include "console/console.h" +#include "console/consoleInternal.h" + + +IMPLEMENT_CO_DATABLOCK_V1(SimDataBlock); +SimObjectId SimDataBlock::sNextObjectId = DataBlockObjectIdFirst; +S32 SimDataBlock::sNextModifiedKey = 0; + +//--------------------------------------------------------------------------- + +SimDataBlock::SimDataBlock() +{ + setModDynamicFields(true); + setModStaticFields(true); +} + +bool SimDataBlock::onAdd() +{ + Parent::onAdd(); + + // This initialization is done here, and not in the constructor, + // because some jokers like to construct and destruct objects + // (without adding them to the manager) to check what class + // they are. + modifiedKey = ++sNextModifiedKey; + AssertFatal(sNextObjectId <= DataBlockObjectIdLast, + "Exceeded maximum number of data blocks"); + + // add DataBlock to the DataBlockGroup unless it is client side ONLY DataBlock + if ( !isClientOnly() ) + if (SimGroup* grp = Sim::getDataBlockGroup()) + grp->addObject(this); + + return true; +} + +void SimDataBlock::assignId() +{ + // We don't want the id assigned by the manager, but it may have + // already been assigned a correct data block id. + if ( isClientOnly() ) + setId(sNextObjectId++); +} + +void SimDataBlock::onStaticModified(const char* slotName, const char* newValue) +{ + modifiedKey = sNextModifiedKey++; +} + +void SimDataBlock::packData(BitStream*) +{ +} + +void SimDataBlock::unpackData(BitStream*) +{ +} + +bool SimDataBlock::preload(bool, String&) +{ + return true; +} + +void SimDataBlock::write(Stream &stream, U32 tabStop, U32 flags) +{ + // Only output selected objects if they want that. + if((flags & SelectedOnly) && !isSelected()) + return; + + stream.writeTabs(tabStop); + char buffer[1024]; + + // Client side datablocks are created with 'new' while + // regular server datablocks use the 'datablock' keyword. + if ( isClientOnly() ) + dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); + else + dSprintf(buffer, sizeof(buffer), "datablock %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); + + stream.write(dStrlen(buffer), buffer); + writeFields(stream, tabStop + 1); + + stream.writeTabs(tabStop); + stream.write(4, "};\r\n"); +} + +ConsoleFunction(preloadClientDataBlocks, void, 1, 1, "Preload all datablocks in client mode. " + "(Server parameter is set to false). This will take some time to complete.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + // we go from last to first because we cut 'n pasted the loop from deleteDataBlocks + SimGroup *grp = Sim::getDataBlockGroup(); + String errorStr; + for(S32 i = grp->size() - 1; i >= 0; i--) + { + AssertFatal(dynamic_cast((*grp)[i]), "Doh! non-datablock in datablock group!"); + SimDataBlock *obj = (SimDataBlock*)(*grp)[i]; + if (!obj->preload(false, errorStr)) + Con::errorf("Failed to preload client datablock, %s: %s", obj->getName(), errorStr.c_str()); + } +} + +ConsoleFunction(deleteDataBlocks, void, 1, 1, "Delete all the datablocks we've downloaded. " + "This is usually done in preparation of downloading a new set of datablocks, " + " such as occurs on a mission change, but it's also good post-mission cleanup.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + // delete from last to first: + SimGroup *grp = Sim::getDataBlockGroup(); + grp->deleteAllObjects(); + SimDataBlock::sNextObjectId = DataBlockObjectIdFirst; + SimDataBlock::sNextModifiedKey = 0; +} diff --git a/console/simDatablock.h b/console/simDatablock.h new file mode 100644 index 0000000..e92f045 --- /dev/null +++ b/console/simDatablock.h @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMDATABLOCK_H_ +#define _SIMDATABLOCK_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +// Forward Refs +class BitStream; + +/// Root DataBlock class. +/// +/// @section SimDataBlock_intro Introduction +/// +/// Another powerful aspect of Torque's networking is the datablock. Datablocks +/// are used to provide relatively static information about entities; for instance, +/// what model a weapon should use to display itself, or how heavy a player class is. +/// +/// This gives significant gains in network efficiency, because it means that all +/// the datablocks on a server can be transferred over the network at client +/// connect time, instead of being intertwined with the update code for NetObjects. +/// +/// This makes the network code much simpler overall, as one-time initialization +/// code is segregated from the standard object update code, as well as providing +/// several powerful features, which we will discuss momentarily. +/// +/// @section SimDataBlock_preload preload() and File Downloading +/// +/// Because datablocks are sent over the wire, using SimDataBlockEvent, before +/// gameplay starts in earnest, we gain in several areas. First, we don't have to +/// try to keep up with the game state while working with incomplete information. +/// Second, we can provide the user with a nice loading screen, instead of the more +/// traditional "Connecting..." message. Finally, and most usefully, we can request +/// missing files from the server as we become aware of them, since we are under +/// no obligation to render anything for the user. +/// +/// The mechanism for this is fairly basic. After a datablock is unpacked, the +/// preload() method is called. If it returns false and sets an error, then the +/// network code checks to see if a file (or files) failed to be located by the +/// ResManager; if so, then it requests those files from the server. If preload +/// returns true, then the datablock is considered loaded. If preload returns +/// false and sets no error, then the connection is aborted. +/// +/// Once the file(s) is downloaded, the datablock's preload() method is called again. +/// If it fails with the same error, the connection is aborted. If a new error is +/// returned, then the download-retry process is repeated until the preload works. +/// +/// @section SimDataBlock_guide Guide To Datablock Code +/// +/// To make a datablock subclass, you need to extend three functions: +/// - preload() +/// - packData() +/// - unpackData() +/// +/// packData() and unpackData() simply read or write data to a network stream. If you +/// add any fields, you need to add appropriate calls to read or write. Make sure that +/// the order of reads and writes is the same in both functions. Make sure to call +/// the Parent's version of these methods in every subclass. +/// +/// preload() is a bit more complex; it is responsible for taking the raw data read by +/// unpackData() and processing it into a form useful by the datablock's owning object. For +/// instance, the Player class' datablock, PlayerData, gets handles to commonly used +/// nodes in the player model, as well as resolving handles to textures and other +/// resources. Any code which loads files or performs other actions beyond simply +/// reading the data from the packet, such as validation, must reside in preload(). +/// +/// To write your own preload() methods, see any of the existing methods in the codebase; for instance, +/// PlayerData::preload() is an excellent example of error-reporting, data validation, and so forth. +/// +/// @note A useful trick, which is used in several places in the engine, is that of temporarily +/// storing SimObjectIds in the variable which will eventually hold the "real" handle. ShapeImage +/// uses this trick in several pllaces; so do the vehicle classes. See GameBaseData for more on +/// using this trick. +/// +/// @see GameBaseData for some more information on the datablocks used throughout +/// most of the engine. +/// @see http://hosted.tribalwar.com/t2faq/datablocks.shtml for an excellent +/// explanation of the basics of datablocks from script. Note that these comments +/// mostly apply to GameBaseData and its children. +/// @nosubgrouping +class SimDataBlock: public SimObject +{ + typedef SimObject Parent; +public: + + SimDataBlock(); + DECLARE_CONOBJECT(SimDataBlock); + + /// @name Datablock Internals + /// @{ + +protected: + S32 modifiedKey; + +public: + static SimObjectId sNextObjectId; + static S32 sNextModifiedKey; + + /// Assign a new modified key. + /// + /// Datablocks are assigned a modified key which is updated every time + /// a static field of the datablock is changed. These are gotten from + /// a global store. + static S32 getNextModifiedKey() { return sNextModifiedKey; } + + /// Returns true if this is a client side only datablock (in + /// other words a datablock allocated with 'new' instead of + /// the 'datablock' keyword). + bool isClientOnly() const { return getId() < DataBlockObjectIdFirst || getId() > DataBlockObjectIdLast; } + + /// Get the modified key for this particular datablock. + S32 getModifiedKey() const { return modifiedKey; } + + bool onAdd(); + virtual void onStaticModified(const char* slotName, const char*newValue = NULL); + //void setLastError(const char*); + void assignId(); + + /// @} + + /// @name Datablock Interface + /// @{ + + /// + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + /// Called to prepare the datablock for use, after it has been unpacked. + /// + /// @param server Set if we're running on the server (and therefore don't need to load + /// things like textures or sounds). + /// @param errorStr If an error occurs in loading, this is set to a short string describing + /// the error. + /// @returns True if all went well; false if something failed. + /// + /// @see @ref SimDataBlock_preload + virtual bool preload(bool server, String &errorStr); + /// @} + + /// Output the TorqueScript to recreate this object. + /// + /// This calls writeFields internally. + /// @param stream Stream to output to. + /// @param tabStop Indentation level for this object. + /// @param flags If SelectedOnly is passed here, then + /// only objects marked as selected (using setSelected) + /// will output themselves. + virtual void write(Stream &stream, U32 tabStop, U32 flags = 0); +}; + +//--------------------------------------------------------------------------- + +class SimDataBlockGroup : public SimGroup +{ +private: + S32 mLastModifiedKey; + +public: + static S32 QSORT_CALLBACK compareModifiedKey(const void* a,const void* b); + void sort(); + SimDataBlockGroup(); +}; + +#endif // _SIMDATABLOCK_H_ diff --git a/console/simDictionary.cpp b/console/simDictionary.cpp new file mode 100644 index 0000000..c5f054e --- /dev/null +++ b/console/simDictionary.cpp @@ -0,0 +1,306 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/simDictionary.h" +#include "console/simBase.h" + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +extern S32 HashPointer(StringTableEntry e); + +SimNameDictionary::SimNameDictionary() +{ + hashTable = NULL; + mutex = Mutex::createMutex(); +} + +SimNameDictionary::~SimNameDictionary() +{ + delete[] hashTable; + Mutex::destroyMutex(mutex); +} + +void SimNameDictionary::insert(SimObject* obj) +{ + if(!obj->objectName) + return; + + SimObject* checkForDup = find(obj->objectName); + + if (checkForDup) + Con::warnf("Warning! You have a duplicate datablock name of %s. This can cause problems. You should rename one of them.", obj->objectName); + + Mutex::lockMutex(mutex); + + if(!hashTable) + { + hashTable = new SimObject *[DefaultTableSize]; + hashTableSize = DefaultTableSize; + hashEntryCount = 0; + S32 i; + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; + } + S32 idx = HashPointer(obj->objectName) % hashTableSize; + obj->nextNameObject = hashTable[idx]; + hashTable[idx] = obj; + hashEntryCount++; + if(hashEntryCount > hashTableSize) + { + // resize the hash table + S32 i; + SimObject *head = NULL, *walk, *temp; + for(i = 0; i < hashTableSize; i++) { + walk = hashTable[i]; + while(walk) + { + temp = walk->nextNameObject; + walk->nextNameObject = head; + head = walk; + walk = temp; + } + } + delete[] hashTable; + hashTableSize = hashTableSize * 2 + 1; + hashTable = new SimObject *[hashTableSize]; + + for(i = 0; i < hashTableSize;i++) + hashTable[i] = NULL; + while(head) + { + temp = head->nextNameObject; + idx = HashPointer(head->objectName) % hashTableSize; + head->nextNameObject = hashTable[idx]; + hashTable[idx] = head; + head = temp; + } + } + Mutex::unlockMutex(mutex); +} + +SimObject* SimNameDictionary::find(StringTableEntry name) +{ + // NULL is a valid lookup - it will always return NULL + if(!hashTable) + return NULL; + + Mutex::lockMutex(mutex); + + S32 idx = HashPointer(name) % hashTableSize; + SimObject *walk = hashTable[idx]; + while(walk) + { + if(walk->objectName == name) + { + Mutex::unlockMutex(mutex); + return walk; + } + walk = walk->nextNameObject; + } + + Mutex::unlockMutex(mutex); + return NULL; +} + +void SimNameDictionary::remove(SimObject* obj) +{ + if(!obj->objectName) + return; + + Mutex::lockMutex(mutex); + + SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize]; + while(*walk) + { + if(*walk == obj) + { + *walk = obj->nextNameObject; + obj->nextNameObject = (SimObject*)-1; + hashEntryCount--; + + Mutex::unlockMutex(mutex); + return; + } + walk = &((*walk)->nextNameObject); + } + + Mutex::unlockMutex(mutex); +} + +//---------------------------------------------------------------------------- + +SimManagerNameDictionary::SimManagerNameDictionary() +{ + hashTable = new SimObject *[DefaultTableSize]; + hashTableSize = DefaultTableSize; + hashEntryCount = 0; + S32 i; + for(i = 0; i < hashTableSize; i++) + hashTable[i] = NULL; + mutex = Mutex::createMutex(); +} + +SimManagerNameDictionary::~SimManagerNameDictionary() +{ + delete[] hashTable; + Mutex::destroyMutex(mutex); +} + +void SimManagerNameDictionary::insert(SimObject* obj) +{ + if(!obj->objectName) + return; + + Mutex::lockMutex(mutex); + + S32 idx = HashPointer(obj->objectName) % hashTableSize; + obj->nextManagerNameObject = hashTable[idx]; + hashTable[idx] = obj; + hashEntryCount++; + if(hashEntryCount > hashTableSize) + { + // resize the hash table + S32 i; + SimObject *head = NULL, *walk, *temp; + for(i = 0; i < hashTableSize; i++) { + walk = hashTable[i]; + while(walk) + { + temp = walk->nextManagerNameObject; + walk->nextManagerNameObject = head; + head = walk; + walk = temp; + } + } + delete[] hashTable; + hashTableSize = hashTableSize * 2 + 1; + hashTable = new SimObject *[hashTableSize]; + + for(i = 0; i < hashTableSize;i++) + hashTable[i] = NULL; + while(head) + { + temp = head->nextManagerNameObject; + idx = HashPointer(head->objectName) % hashTableSize; + head->nextManagerNameObject = hashTable[idx]; + hashTable[idx] = head; + head = temp; + } + } + + Mutex::unlockMutex(mutex); +} + +SimObject* SimManagerNameDictionary::find(StringTableEntry name) +{ + // NULL is a valid lookup - it will always return NULL + + Mutex::lockMutex(mutex); + + S32 idx = HashPointer(name) % hashTableSize; + SimObject *walk = hashTable[idx]; + while(walk) + { + if(walk->objectName == name) + { + Mutex::unlockMutex(mutex); + return walk; + } + walk = walk->nextManagerNameObject; + } + + Mutex::unlockMutex(mutex); + return NULL; +} + +void SimManagerNameDictionary::remove(SimObject* obj) +{ + if(!obj->objectName) + return; + + Mutex::lockMutex(mutex); + + SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize]; + while(*walk) + { + if(*walk == obj) + { + *walk = obj->nextManagerNameObject; + obj->nextManagerNameObject = (SimObject*)-1; + hashEntryCount--; + + Mutex::unlockMutex(mutex); + return; + } + walk = &((*walk)->nextManagerNameObject); + } + + Mutex::unlockMutex(mutex); +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +SimIdDictionary::SimIdDictionary() +{ + for(S32 i = 0; i < DefaultTableSize; i++) + table[i] = NULL; + mutex = Mutex::createMutex(); +} + +SimIdDictionary::~SimIdDictionary() +{ + Mutex::destroyMutex(mutex); +} + +void SimIdDictionary::insert(SimObject* obj) +{ + Mutex::lockMutex(mutex); + + S32 idx = obj->getId() & TableBitMask; + obj->nextIdObject = table[idx]; + AssertFatal( obj->nextIdObject != obj, "SimIdDictionary::insert - Creating Infinite Loop linking to self!" ); + table[idx] = obj; + + Mutex::unlockMutex(mutex); +} + +SimObject* SimIdDictionary::find(S32 id) +{ + Mutex::lockMutex(mutex); + + S32 idx = id & TableBitMask; + SimObject *walk = table[idx]; + while(walk) + { + if(walk->getId() == U32(id)) + { + Mutex::unlockMutex(mutex); + return walk; + } + walk = walk->nextIdObject; + } + + Mutex::unlockMutex(mutex); + + return NULL; +} + +void SimIdDictionary::remove(SimObject* obj) +{ + Mutex::lockMutex(mutex); + + SimObject **walk = &table[obj->getId() & TableBitMask]; + while(*walk && *walk != obj) + walk = &((*walk)->nextIdObject); + if(*walk) + *walk = obj->nextIdObject; + + Mutex::unlockMutex(mutex); +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + diff --git a/console/simDictionary.h b/console/simDictionary.h new file mode 100644 index 0000000..bea9093 --- /dev/null +++ b/console/simDictionary.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMDICTIONARY_H_ +#define _SIMDICTIONARY_H_ +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif + +#ifndef _PLATFORMMUTEX_H_ +#include "platform/threads/mutex.h" +#endif + +class SimObject; + +//---------------------------------------------------------------------------- +/// Map of names to SimObjects +/// +/// Provides fast lookup for name->object and +/// for fast removal of an object given object* +class SimNameDictionary +{ + enum + { + DefaultTableSize = 29 + }; + + SimObject **hashTable; // hash the pointers of the names... + S32 hashTableSize; + S32 hashEntryCount; + + void *mutex; + +public: + void insert(SimObject* obj); + void remove(SimObject* obj); + SimObject* find(StringTableEntry name); + + SimNameDictionary(); + ~SimNameDictionary(); +}; + +class SimManagerNameDictionary +{ + enum + { + DefaultTableSize = 29 + }; + + SimObject **hashTable; // hash the pointers of the names... + S32 hashTableSize; + S32 hashEntryCount; + + void *mutex; + +public: + void insert(SimObject* obj); + void remove(SimObject* obj); + SimObject* find(StringTableEntry name); + + SimManagerNameDictionary(); + ~SimManagerNameDictionary(); +}; + +//---------------------------------------------------------------------------- +/// Map of ID's to SimObjects. +/// +/// Provides fast lookup for ID->object and +/// for fast removal of an object given object* +class SimIdDictionary +{ + enum + { + DefaultTableSize = 4096, + TableBitMask = 4095 + }; + SimObject *table[DefaultTableSize]; + + void *mutex; + +public: + void insert(SimObject* obj); + void remove(SimObject* obj); + SimObject* find(S32 id); + + SimIdDictionary(); + ~SimIdDictionary(); +}; + +#endif //_SIMDICTIONARY_H_ diff --git a/console/simEvents.cpp b/console/simEvents.cpp new file mode 100644 index 0000000..24fd6a1 --- /dev/null +++ b/console/simEvents.cpp @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleInternal.h" +#include "platform/threads/semaphore.h" +#include "console/simEvents.h" + +// Stupid globals not declared in a header +extern ExprEvalState gEvalState; + +SimConsoleEvent::SimConsoleEvent(S32 argc, const char **argv, bool onObject) +{ + mOnObject = onObject; + mArgc = argc; + U32 totalSize = 0; + S32 i; + for(i = 0; i < argc; i++) + totalSize += dStrlen(argv[i]) + 1; + totalSize += sizeof(char *) * argc; + + mArgv = (char **) dMalloc(totalSize); + char *argBase = (char *) &mArgv[argc]; + + for(i = 0; i < argc; i++) + { + mArgv[i] = argBase; + dStrcpy(mArgv[i], argv[i]); + argBase += dStrlen(argv[i]) + 1; + } +} + +SimConsoleEvent::~SimConsoleEvent() +{ + dFree(mArgv); +} + +void SimConsoleEvent::process(SimObject* object) +{ + // #ifdef DEBUG + // Con::printf("Executing schedule: %d", sequenceCount); + // #endif + if(mOnObject) + Con::execute(object, mArgc, const_cast( mArgv )); + else + { + // Grab the function name. If '::' doesn't exist, then the schedule is + // on a global function. + char* func = dStrstr( mArgv[0], (char*)"::" ); + if( func ) + { + // Set the first colon to NULL, so we can reference the namespace. + // This is okay because events are deleted immediately after + // processing. Maybe a bad idea anyway? + func[0] = '\0'; + + // Move the pointer forward to the function name. + func += 2; + + // Lookup the namespace and function entry. + Namespace* ns = Namespace::find( StringTable->insert( mArgv[0] ) ); + if( ns ) + { + Namespace::Entry* nse = ns->lookup( StringTable->insert( func ) ); + if( nse ) + // Execute. + nse->execute( mArgc, (const char**)mArgv, &gEvalState ); + } + } + + else + Con::execute(mArgc, const_cast( mArgv )); + } +} + +//----------------------------------------------------------------------------- + +SimConsoleThreadExecCallback::SimConsoleThreadExecCallback() : retVal(NULL) +{ + sem = new Semaphore(0); +} + +SimConsoleThreadExecCallback::~SimConsoleThreadExecCallback() +{ + delete sem; +} + +void SimConsoleThreadExecCallback::handleCallback(const char *ret) +{ + retVal = ret; + sem->release(); +} + +const char *SimConsoleThreadExecCallback::waitForResult() +{ + if(sem->acquire(true)) + { + return retVal; + } + + return NULL; +} + +//----------------------------------------------------------------------------- + +SimConsoleThreadExecEvent::SimConsoleThreadExecEvent(S32 argc, const char **argv, bool onObject, SimConsoleThreadExecCallback *callback) : + SimConsoleEvent(argc, argv, onObject), cb(callback) +{ +} + +void SimConsoleThreadExecEvent::process(SimObject* object) +{ + const char *retVal; + if(mOnObject) + retVal = Con::execute(object, mArgc, const_cast( mArgv )); + else + retVal = Con::execute(mArgc, const_cast( mArgv )); + + if(cb) + cb->handleCallback(retVal); +} diff --git a/console/simEvents.h b/console/simEvents.h new file mode 100644 index 0000000..7c9fe0d --- /dev/null +++ b/console/simEvents.h @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/sim.h" + +#ifndef _SIMEVENTS_H_ +#define _SIMEVENTS_H_ + +// Forward Refs +class SimObject; +class Semaphore; + +/// Represents a queued event in the sim. +/// +/// Sim provides an event queue for your convenience, which +/// can be used to schedule events. A few things which use +/// this event queue: +/// +/// - The scene lighting system. In order to keep the game +/// responsive while scene lighting occurs, the lighting +/// process is divided into little chunks. In implementation +/// terms, there is a subclass of SimEvent called +/// SceneLightingProcessEvent. The process method of this +/// subclass calls into the lighting code, telling it to +/// perform the next chunk of lighting calculations. +/// - The schedule() console function uses a subclass of +/// SimEvent called SimConsoleEvent to keep track of +/// scheduled events. +class SimEvent +{ +public: + SimEvent *nextEvent; ///< Linked list details - pointer to next item in the list. + SimTime startTime; ///< When the event was posted. + SimTime time; ///< When the event is scheduled to occur. + U32 sequenceCount; ///< Unique ID. These are assigned sequentially based on order + /// of addition to the list. + SimObject *destObject; ///< Object on which this event will be applied. + + SimEvent() { destObject = NULL; } + virtual ~SimEvent() {} ///< Destructor + /// + /// A dummy virtual destructor is required + /// so that subclasses can be deleted properly + + /// Function called when event occurs. + /// + /// This is where the meat of your event's implementation goes. + /// + /// See any of the subclasses for ideas of what goes in here. + /// + /// The event is deleted immediately after processing. If the + /// object referenced in destObject is deleted, then the event + /// is not called. The even will be executed unconditionally if + /// the object referenced is NULL. + /// + /// @param object Object stored in destObject. + virtual void process(SimObject *object)=0; +}; + +/// Implementation of schedule() function. +/// +/// This allows you to set a console function to be +/// called at some point in the future. +class SimConsoleEvent : public SimEvent +{ +protected: + S32 mArgc; + char **mArgv; + bool mOnObject; +public: + + /// Constructor + /// + /// Pass the arguments of a function call, optionally on an object. + /// + /// The object for the call to be executed on is specified by setting + /// onObject and storing a reference to the object in destObject. If + /// onObject is false, you don't need to store anything into destObject. + /// + /// The parameters here are passed unmodified to Con::execute() at the + /// time of the event. + /// + /// @see Con::execute(S32 argc, const char *argv[]) + /// @see Con::execute(SimObject *object, S32 argc, const char *argv[]) + SimConsoleEvent(S32 argc, const char **argv, bool onObject); + + ~SimConsoleEvent(); + virtual void process(SimObject *object); +}; + +/// Used by Con::threadSafeExecute() +struct SimConsoleThreadExecCallback +{ + Semaphore *sem; + const char *retVal; + + SimConsoleThreadExecCallback(); + ~SimConsoleThreadExecCallback(); + + void handleCallback(const char *ret); + const char *waitForResult(); +}; + +class SimConsoleThreadExecEvent : public SimConsoleEvent +{ + SimConsoleThreadExecCallback *cb; + +public: + SimConsoleThreadExecEvent(S32 argc, const char **argv, bool onObject, SimConsoleThreadExecCallback *callback); + + virtual void process(SimObject *object); +}; + +#endif // _SIMEVENTS_H_ diff --git a/console/simFieldDictionary.cpp b/console/simFieldDictionary.cpp new file mode 100644 index 0000000..cfe4cf5 --- /dev/null +++ b/console/simFieldDictionary.cpp @@ -0,0 +1,343 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#include "platform/platform.h" +#include "console/simFieldDictionary.h" + +#include "console/console.h" +#include "console/consoleInternal.h" +#include "core/frameAllocator.h" + +SimFieldDictionary::Entry *SimFieldDictionary::smFreeList = NULL; + +static Chunker fieldChunker; + +U32 SimFieldDictionary::getHashValue( StringTableEntry slotName ) +{ + return HashPointer( slotName ) % HashTableSize; +} + +U32 SimFieldDictionary::getHashValue( const String& fieldName ) +{ + return getHashValue( StringTable->insert( fieldName ) ); +} + +SimFieldDictionary::Entry *SimFieldDictionary::addEntry( U32 bucket, StringTableEntry slotName, ConsoleBaseType* type, char* value ) +{ + Entry* ret; + if(smFreeList) + { + ret = smFreeList; + smFreeList = ret->next; + } + else + ret = fieldChunker.alloc(); + + ret->next = mHashTable[ bucket ]; + ret->slotName = slotName; + ret->type = type; + ret->value = value; + + mHashTable[ bucket ] = ret; + mNumFields ++; + mVersion ++; + + return ret; +} + +void SimFieldDictionary::freeEntry(SimFieldDictionary::Entry *ent) +{ + ent->next = smFreeList; + smFreeList = ent; + + mNumFields --; +} + +SimFieldDictionary::SimFieldDictionary() +: mNumFields( 0 ), + mVersion( 0 ) +{ + dMemset( mHashTable, 0, sizeof( mHashTable ) ); +} + +SimFieldDictionary::~SimFieldDictionary() +{ + for(U32 i = 0; i < HashTableSize; i++) + { + for(Entry *walk = mHashTable[i]; walk;) + { + Entry *temp = walk; + walk = temp->next; + + if( temp->value ) + dFree(temp->value); + freeEntry(temp); + } + } + + AssertFatal( mNumFields == 0, "Incorrect count on field dictionary" ); +} + +void SimFieldDictionary::setFieldType(StringTableEntry slotName, const char *typeString) +{ + ConsoleBaseType *cbt = ConsoleBaseType::getTypeByName( typeString ); + setFieldType(slotName, cbt); +} + +void SimFieldDictionary::setFieldType(StringTableEntry slotName, const U32 typeId) +{ + ConsoleBaseType *cbt = ConsoleBaseType::getType(typeId); + setFieldType(slotName, cbt); +} + +void SimFieldDictionary::setFieldType(StringTableEntry slotName, ConsoleBaseType *type) +{ + // If the field exists on the object, set the type + U32 bucket = getHashValue( slotName ); + + for( Entry *walk = mHashTable[bucket]; walk; walk = walk->next ) + { + if( walk->slotName == slotName ) + { + // Found and type assigned, let's bail + walk->type = type; + return; + } + } + + // Otherwise create the field, and set the type. Assign a null value. + addEntry( bucket, slotName, type ); +} + +U32 SimFieldDictionary::getFieldType(StringTableEntry slotName) const +{ + U32 bucket = getHashValue( slotName ); + + for( Entry *walk = mHashTable[bucket]; walk; walk = walk->next ) + if( walk->slotName == slotName ) + return walk->type ? walk->type->getTypeID() : TypeString; + + return TypeString; +} + +SimFieldDictionary::Entry *SimFieldDictionary::findDynamicField(const String &fieldName) const +{ + U32 bucket = getHashValue( fieldName ); + + for( Entry *walk = mHashTable[bucket]; walk; walk = walk->next ) + { + if( fieldName.equal(walk->slotName, String::NoCase) ) + return walk; + } + + return NULL; +} + +SimFieldDictionary::Entry *SimFieldDictionary::findDynamicField( StringTableEntry fieldName) const +{ + U32 bucket = getHashValue( fieldName ); + + for( Entry *walk = mHashTable[bucket]; walk; walk = walk->next ) + { + if( walk->slotName == fieldName ) + { + return walk; + } + } + + return NULL; +} + + +void SimFieldDictionary::setFieldValue(StringTableEntry slotName, const char *value) +{ + U32 bucket = getHashValue(slotName); + Entry **walk = &mHashTable[bucket]; + while(*walk && (*walk)->slotName != slotName) + walk = &((*walk)->next); + + Entry *field = *walk; + if(!*value) + { + if(field) + { + mVersion++; + + if( field->value ) + dFree(field->value); + + *walk = field->next; + freeEntry(field); + } + } + else + { + if(field) + { + if( field->value ) + dFree(field->value); + + field->value = dStrdup(value); + } + else + addEntry( bucket, slotName, 0, dStrdup( value ) ); + } +} + +const char *SimFieldDictionary::getFieldValue(StringTableEntry slotName) +{ + U32 bucket = getHashValue(slotName); + + for(Entry *walk = mHashTable[bucket];walk;walk = walk->next) + if(walk->slotName == slotName) + return walk->value; + + return NULL; +} + +void SimFieldDictionary::assignFrom(SimFieldDictionary *dict) +{ + mVersion++; + + for(U32 i = 0; i < HashTableSize; i++) + { + for(Entry *walk = dict->mHashTable[i];walk; walk = walk->next) + { + setFieldValue(walk->slotName, walk->value); + setFieldType(walk->slotName, walk->type); + } + } +} + +static S32 QSORT_CALLBACK compareEntries(const void* a,const void* b) +{ + SimFieldDictionary::Entry *fa = *((SimFieldDictionary::Entry **)a); + SimFieldDictionary::Entry *fb = *((SimFieldDictionary::Entry **)b); + return dStricmp(fa->slotName, fb->slotName); +} + +void SimFieldDictionary::writeFields(SimObject *obj, Stream &stream, U32 tabStop) +{ + const AbstractClassRep::FieldList &list = obj->getFieldList(); + Vector flist(__FILE__, __LINE__); + + for(U32 i = 0; i < HashTableSize; i++) + { + for(Entry *walk = mHashTable[i];walk; walk = walk->next) + { + // make sure we haven't written this out yet: + U32 i; + for(i = 0; i < list.size(); i++) + if(list[i].pFieldname == walk->slotName) + break; + + if(i != list.size()) + continue; + + + if (!obj->writeField(walk->slotName, walk->value)) + continue; + + flist.push_back(walk); + } + } + + // Sort Entries to prevent version control conflicts + dQsort(flist.address(),flist.size(),sizeof(Entry *),compareEntries); + + // Save them out + for(Vector::iterator itr = flist.begin(); itr != flist.end(); itr++) + { + U32 nBufferSize = (dStrlen( (*itr)->value ) * 2) + dStrlen( (*itr)->slotName ) + 16; + FrameTemp expandedBuffer( nBufferSize ); + + stream.writeTabs(tabStop+1); + + const char *typeName = (*itr)->type && (*itr)->type->getTypeID() != TypeString ? (*itr)->type->getTypeName() : ""; + dSprintf(expandedBuffer, nBufferSize, "%s%s%s = \"", typeName, *typeName ? " " : "", (*itr)->slotName); + if ( (*itr)->value ) + expandEscape((char*)expandedBuffer + dStrlen(expandedBuffer), (*itr)->value); + dStrcat(expandedBuffer, "\";\r\n"); + + stream.write(dStrlen(expandedBuffer),expandedBuffer); + } + +} +void SimFieldDictionary::printFields(SimObject *obj) +{ + const AbstractClassRep::FieldList &list = obj->getFieldList(); + char expandedBuffer[4096]; + Vector flist(__FILE__, __LINE__); + + for(U32 i = 0; i < HashTableSize; i++) + { + for(Entry *walk = mHashTable[i];walk; walk = walk->next) + { + // make sure we haven't written this out yet: + U32 i; + for(i = 0; i < list.size(); i++) + if(list[i].pFieldname == walk->slotName) + break; + + if(i != list.size()) + continue; + + flist.push_back(walk); + } + } + dQsort(flist.address(),flist.size(),sizeof(Entry *),compareEntries); + + for(Vector::iterator itr = flist.begin(); itr != flist.end(); itr++) + { + dSprintf(expandedBuffer, sizeof(expandedBuffer), " %s = \"", (*itr)->slotName); + if ( (*itr)->value ) + expandEscape(expandedBuffer + dStrlen(expandedBuffer), (*itr)->value); + Con::printf("%s\"", expandedBuffer); + } +} + +SimFieldDictionary::Entry *SimFieldDictionary::operator[](U32 index) +{ + AssertFatal ( index < mNumFields, "out of range" ); + + if ( index > mNumFields ) + return NULL; + + SimFieldDictionaryIterator itr(this); + + for (S32 i = 0; i < index && *itr; i++) + ++itr; + + return (*itr); +} + +//------------------------------------------------------------------------------ +SimFieldDictionaryIterator::SimFieldDictionaryIterator(SimFieldDictionary * dictionary) +{ + mDictionary = dictionary; + mHashIndex = -1; + mEntry = 0; + operator++(); +} + +SimFieldDictionary::Entry* SimFieldDictionaryIterator::operator++() +{ + if(!mDictionary) + return(mEntry); + + if(mEntry) + mEntry = mEntry->next; + + while(!mEntry && (mHashIndex < (SimFieldDictionary::HashTableSize-1))) + mEntry = mDictionary->mHashTable[++mHashIndex]; + + return(mEntry); +} + +SimFieldDictionary::Entry* SimFieldDictionaryIterator::operator*() +{ + return(mEntry); +} \ No newline at end of file diff --git a/console/simFieldDictionary.h b/console/simFieldDictionary.h new file mode 100644 index 0000000..362843d --- /dev/null +++ b/console/simFieldDictionary.h @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMFIELDDICTIONARY_H_ +#define _SIMFIELDDICTIONARY_H_ + +// Forward Refs +class ConsoleBaseType; +class SimObject; + +#include "core/stringTable.h" +#include "core/stream/stream.h" + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +/// Dictionary to keep track of dynamic fields on SimObject. +class SimFieldDictionary +{ + friend class SimFieldDictionaryIterator; + +public: + struct Entry + { + Entry() : type( NULL ) {}; + + StringTableEntry slotName; + char *value; + Entry *next; + ConsoleBaseType *type; + }; +private: + enum + { + HashTableSize = 19 + }; + Entry *mHashTable[HashTableSize]; + + static Entry *smFreeList; + + void freeEntry(Entry *entry); + Entry* addEntry( U32 bucket, StringTableEntry slotName, ConsoleBaseType* type, char* value = 0 ); + + static U32 getHashValue( StringTableEntry slotName ); + static U32 getHashValue( const String& fieldName ); + + U32 mNumFields; + + /// In order to efficiently detect when a dynamic field has been + /// added or deleted, we increment this every time we add or + /// remove a field. + U32 mVersion; + +public: + const U32 getVersion() const { return mVersion; } + + SimFieldDictionary(); + ~SimFieldDictionary(); + void setFieldType(StringTableEntry slotName, const char *typeString); + void setFieldType(StringTableEntry slotName, const U32 typeId); + void setFieldType(StringTableEntry slotName, ConsoleBaseType *type); + void setFieldValue(StringTableEntry slotName, const char *value); + const char *getFieldValue(StringTableEntry slotName); + U32 getFieldType(StringTableEntry slotName) const; + Entry *findDynamicField(const String &fieldName) const; + Entry *findDynamicField( StringTableEntry fieldName) const; + void writeFields(SimObject *obj, Stream &strem, U32 tabStop); + void printFields(SimObject *obj); + void assignFrom(SimFieldDictionary *dict); + U32 getNumFields() const { return mNumFields; } + + Entry *operator[](U32 index); +}; + +class SimFieldDictionaryIterator +{ + SimFieldDictionary * mDictionary; + S32 mHashIndex; + SimFieldDictionary::Entry * mEntry; + +public: + SimFieldDictionaryIterator(SimFieldDictionary*); + SimFieldDictionary::Entry* operator++(); + SimFieldDictionary::Entry* operator*(); +}; + + +#endif // _SIMFIELDDICTIONARY_H_ diff --git a/console/simManager.cpp b/console/simManager.cpp new file mode 100644 index 0000000..52d6735 --- /dev/null +++ b/console/simManager.cpp @@ -0,0 +1,684 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/threads/mutex.h" +#include "console/simBase.h" +#include "core/stringTable.h" +#include "console/console.h" +#include "core/stream/fileStream.h" +#include "core/fileObject.h" +#include "console/consoleInternal.h" +#include "core/idGenerator.h" +#include "core/util/safeDelete.h" + +#include "platform/profiler.h" + +extern ExprEvalState gEvalState; + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +// We comment out the implementation of the Con namespace when doxygenizing because +// otherwise Doxygen decides to ignore our docs in console.h +#ifndef DOXYGENIZING + +namespace Sim +{ + + +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- +// event queue variables: + +SimTime gCurrentTime; +SimTime gTargetTime; + +void *gEventQueueMutex; +SimEvent *gEventQueue; +U32 gEventSequence; + +//--------------------------------------------------------------------------- +// event queue init/shutdown + +static void initEventQueue() +{ + gCurrentTime = 0; + gTargetTime = 0; + gEventSequence = 1; + gEventQueue = NULL; + gEventQueueMutex = Mutex::createMutex(); +} + +static void shutdownEventQueue() +{ + // Delete all pending events + Mutex::lockMutex(gEventQueueMutex); + SimEvent *walk = gEventQueue; + while(walk) + { + SimEvent *temp = walk->nextEvent; + delete walk; + walk = temp; + } + Mutex::unlockMutex(gEventQueueMutex); + Mutex::destroyMutex(gEventQueueMutex); +} + +//--------------------------------------------------------------------------- +// event post + +U32 postEvent(SimObject *destObject, SimEvent* event,U32 time) +{ + AssertFatal(time == -1 || time >= getCurrentTime(), + "Sim::postEvent: Cannot go back in time. (flux capacitor unavailable -- BJG)"); + AssertFatal(destObject, "Destination object for event doesn't exist."); + + Mutex::lockMutex(gEventQueueMutex); + + if( time == -1 ) + time = gCurrentTime; + + event->time = time; + event->startTime = gCurrentTime; + event->destObject = destObject; + + if(!destObject) + { + delete event; + + Mutex::unlockMutex(gEventQueueMutex); + + return InvalidEventId; + } + event->sequenceCount = gEventSequence++; + SimEvent **walk = &gEventQueue; + SimEvent *current; + + while((current = *walk) != NULL && (current->time < event->time)) + walk = &(current->nextEvent); + + // [tom, 6/24/2005] This ensures that SimEvents are dispatched in the same order that they are posted. + // This is needed to ensure Con::threadSafeExecute() executes script code in the correct order. + while((current = *walk) != NULL && (current->time == event->time)) + walk = &(current->nextEvent); + + event->nextEvent = current; + *walk = event; + + U32 seqCount = event->sequenceCount; + + Mutex::unlockMutex(gEventQueueMutex); + + return seqCount; +} + +//--------------------------------------------------------------------------- +// event cancellation + +void cancelEvent(U32 eventSequence) +{ + Mutex::lockMutex(gEventQueueMutex); + + SimEvent **walk = &gEventQueue; + SimEvent *current; + + while((current = *walk) != NULL) + { + if(current->sequenceCount == eventSequence) + { + *walk = current->nextEvent; + delete current; + Mutex::unlockMutex(gEventQueueMutex); + return; + } + else + walk = &(current->nextEvent); + } + + Mutex::unlockMutex(gEventQueueMutex); +} + +static void cancelPendingEvents(SimObject *obj) +{ + Mutex::lockMutex(gEventQueueMutex); + + SimEvent **walk = &gEventQueue; + SimEvent *current; + + while((current = *walk) != NULL) + { + if(current->destObject == obj) + { + *walk = current->nextEvent; + delete current; + } + else + walk = &(current->nextEvent); + } + Mutex::unlockMutex(gEventQueueMutex); +} + +//--------------------------------------------------------------------------- +// event pending test + +bool isEventPending(U32 eventSequence) +{ + Mutex::lockMutex(gEventQueueMutex); + + for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent) + if(walk->sequenceCount == eventSequence) + { + Mutex::unlockMutex(gEventQueueMutex); + return true; + } + Mutex::unlockMutex(gEventQueueMutex); + return false; +} + +U32 getEventTimeLeft(U32 eventSequence) +{ + Mutex::lockMutex(gEventQueueMutex); + + for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent) + if(walk->sequenceCount == eventSequence) + { + SimTime t = walk->time - getCurrentTime(); + Mutex::unlockMutex(gEventQueueMutex); + return t; + } + + Mutex::unlockMutex(gEventQueueMutex); + + return 0; +} + +U32 getScheduleDuration(U32 eventSequence) +{ + for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent) + if(walk->sequenceCount == eventSequence) + return (walk->time-walk->startTime); + return 0; +} + +U32 getTimeSinceStart(U32 eventSequence) +{ + for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent) + if(walk->sequenceCount == eventSequence) + return (getCurrentTime()-walk->startTime); + return 0; +} + +//--------------------------------------------------------------------------- +// event timing + +void advanceToTime(SimTime targetTime) +{ + AssertFatal(targetTime >= getCurrentTime(), "EventQueue::process: cannot advance to time in the past."); + + Mutex::lockMutex(gEventQueueMutex); + gTargetTime = targetTime; + while(gEventQueue && gEventQueue->time <= targetTime) + { + SimEvent *event = gEventQueue; + gEventQueue = gEventQueue->nextEvent; + AssertFatal(event->time >= gCurrentTime, + "SimEventQueue::pop: Cannot go back in time (flux capacitor not installed - BJG)."); + gCurrentTime = event->time; + SimObject *obj = event->destObject; + + if(!obj->isDeleted()) + event->process(obj); + delete event; + } + gCurrentTime = targetTime; + Mutex::unlockMutex(gEventQueueMutex); +} + +void advanceTime(SimTime delta) +{ + advanceToTime(getCurrentTime() + delta); +} + +U32 getCurrentTime() +{ + if(gEventQueueMutex) + Mutex::lockMutex(gEventQueueMutex); + + SimTime t = gCurrentTime; + + if(gEventQueueMutex) + Mutex::unlockMutex(gEventQueueMutex); + + return t; +} + +U32 getTargetTime() +{ + return gTargetTime; +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +SimGroup *gRootGroup = NULL; +SimManagerNameDictionary *gNameDictionary; +SimIdDictionary *gIdDictionary; +U32 gNextObjectId; + +static void initRoot() +{ + gIdDictionary = new SimIdDictionary; + gNameDictionary = new SimManagerNameDictionary; + + gRootGroup = new SimGroup(); + gRootGroup->setId(RootGroupId); + gRootGroup->assignName("RootGroup"); + gRootGroup->registerObject(); + + gNextObjectId = DynamicObjectIdFirst; +} + +static void shutdownRoot() +{ + gRootGroup->deleteObject(); + gRootGroup = NULL; + + SAFE_DELETE(gNameDictionary); + SAFE_DELETE(gIdDictionary); +} + +//--------------------------------------------------------------------------- + +SimObject* findObject(const char* fileName, S32 declarationLine) +{ + PROFILE_SCOPE(SimFindObjectByLine); + + if (!fileName) + return NULL; + + if (declarationLine < 0) + return NULL; + + if (!gRootGroup) + return NULL; + + return gRootGroup->findObjectByLineNumber(fileName, declarationLine, true); +} + +SimObject* findObject(const char* name) +{ + PROFILE_SCOPE(SimFindObject); + + // Play nice with bad code - JDD + if( !name ) + return NULL; + + SimObject *obj; + char c = *name; + + if (c == '%') + { + if (gEvalState.stack.size()) + { + Dictionary::Entry* ent = gEvalState.stack.last()->lookup(StringTable->insert(name)); + + if (ent) + return Sim::findObject(ent->getIntValue()); + } + } + if(c == '/') + return gRootGroup->findObject(name + 1 ); + if(c >= '0' && c <= '9') + { + // it's an id group + const char* temp = name + 1; + for(;;) + { + c = *temp++; + if(!c) + return findObject(dAtoi(name)); + else if(c == '/') + { + obj = findObject(dAtoi(name)); + if(!obj) + return NULL; + return obj->findObject(temp); + } + } + } + S32 len; + + for(len = 0; name[len] != 0 && name[len] != '/'; len++) + ; + StringTableEntry stName = StringTable->lookupn(name, len); + if(!stName) + return NULL; + obj = gNameDictionary->find(stName); + if(!name[len]) + return obj; + if(!obj) + return NULL; + return obj->findObject(name + len + 1); +} + +SimObject* findObject(SimObjectId id) +{ + return gIdDictionary->find(id); +} + +SimObject *spawnObject(String spawnClass, String spawnDataBlock, String spawnName, + String spawnProperties, String spawnScript) +{ + if (spawnClass.isEmpty()) + { + Con::errorf("Unable to spawn an object without a spawnClass"); + return NULL; + } + + String spawnString; + + spawnString += "$SpawnObject = new " + spawnClass + "(" + spawnName + ") { "; + + if (spawnDataBlock.isNotEmpty() && !spawnDataBlock.equal( "None", String::NoCase ) ) + spawnString += "datablock = " + spawnDataBlock + "; "; + + if (spawnProperties.isNotEmpty()) + spawnString += spawnProperties + " "; + + spawnString += "};"; + + // Evaluate our spawn string + Con::evaluate(spawnString.c_str()); + + // Get our spawnObject id + const char* spawnObjectId = Con::getVariable("$SpawnObject"); + + // Get the actual spawnObject + SimObject* spawnObject = findObject(spawnObjectId); + + // If we have a spawn script go ahead and execute it last + if (spawnScript.isNotEmpty()) + Con::evaluate(spawnScript.c_str(), true); + + return spawnObject; +} + +SimGroup *getRootGroup() +{ + return gRootGroup; +} + +String getUniqueName( const char *inName ) +{ + String outName( inName ); + + if ( outName.isEmpty() ) + { + Con::errorf( "getUniqueName() - passed a null baseName" ); + return String::EmptyString; + } + + SimObject *dummy; + + if ( !Sim::findObject( outName, dummy ) ) + return outName; + + S32 suffixNumb = -1; + String nameStr( String::GetTrailingNumber( outName, suffixNumb ) ); + suffixNumb++; + + #define MAX_TRIES 100 + + for ( U32 i = 0; i < MAX_TRIES; i++ ) + { + outName = String::ToString( "%s%d", nameStr.c_str(), suffixNumb ); + + if ( !Sim::findObject( outName, dummy ) ) + return outName; + + suffixNumb++; + } + + Con::errorf( "Sim::getUniqueName( %s ) - failed after %d attempts", inName, MAX_TRIES ); + return String::EmptyString; +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +#define InstantiateNamedSet(set) g##set = new SimSet; g##set->registerObject(#set); gRootGroup->addObject(g##set); SIMSET_SET_ASSOCIATION((*g##set)) +#define InstantiateNamedGroup(set) g##set = new SimGroup; g##set->registerObject(#set); gRootGroup->addObject(g##set); SIMSET_SET_ASSOCIATION((*g##set)) + +SimDataBlockGroup *gDataBlockGroup; +SimDataBlockGroup *getDataBlockGroup() +{ + return gDataBlockGroup; +} + + +void init() +{ + initEventQueue(); + initRoot(); + + InstantiateNamedSet(ActiveActionMapSet); + InstantiateNamedSet(GhostAlwaysSet); + InstantiateNamedSet(WayPointSet); + InstantiateNamedSet(fxReplicatorSet); + InstantiateNamedSet(fxFoliageSet); + InstantiateNamedSet(MaterialSet); + InstantiateNamedSet(SFXSourceSet); + InstantiateNamedSet(TerrainMaterialSet); + InstantiateNamedGroup(ActionMapGroup); + InstantiateNamedGroup(ClientGroup); + InstantiateNamedGroup(GuiGroup); + InstantiateNamedGroup(GuiDataGroup); + InstantiateNamedGroup(TCPGroup); + InstantiateNamedGroup(ClientConnectionGroup); + InstantiateNamedGroup(ChunkFileGroup); + InstantiateNamedSet(BehaviorSet); + InstantiateNamedSet(sgMissionLightingFilterSet); + + gDataBlockGroup = new SimDataBlockGroup(); + gDataBlockGroup->registerObject("DataBlockGroup"); + gRootGroup->addObject(gDataBlockGroup); +} + +void shutdown() +{ + shutdownRoot(); + shutdownEventQueue(); +} + +} + + +#endif // DOXYGENIZING. + +SimDataBlockGroup::SimDataBlockGroup() +{ + mLastModifiedKey = 0; +} + +S32 QSORT_CALLBACK SimDataBlockGroup::compareModifiedKey(const void* a,const void* b) +{ + const SimDataBlock* dba = *((const SimDataBlock**)a); + const SimDataBlock* dbb = *((const SimDataBlock**)b); + + return dba->getModifiedKey() - dbb->getModifiedKey(); +} + + +void SimDataBlockGroup::sort() +{ + if(mLastModifiedKey != SimDataBlock::getNextModifiedKey()) + { + mLastModifiedKey = SimDataBlock::getNextModifiedKey(); + dQsort(objectList.address(),objectList.size(),sizeof(SimObject *),compareModifiedKey); + } +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- + +bool SimObject::mForceId = false; +SimObjectId SimObject::mForcedId = 0; + +bool SimObject::registerObject() +{ + AssertFatal( !mFlags.test( Added ), "reigsterObject - Object already registered!"); + mFlags.clear(Deleted | Removed); + + if(mForceId) + { + setId(mForcedId); + mForceId = false; + } + + if(!mId) + mId = Sim::gNextObjectId++; + + AssertFatal(Sim::gIdDictionary && Sim::gNameDictionary, + "SimObject::registerObject - tried to register an object before Sim::init()!"); + + Sim::gIdDictionary->insert(this); + + Sim::gNameDictionary->insert(this); + + // Notify object + bool ret = onAdd(); + + if(!ret) + unregisterObject(); + + AssertFatal(!ret || isProperlyAdded(), "Object did not call SimObject::onAdd()"); + return ret; +} + +//--------------------------------------------------------------------------- + +void SimObject::unregisterObject() +{ + mFlags.set(Removed); + + // Notify object first + onRemove(); + + // Clear out any pending notifications before + // we call our own, just in case they delete + // something that we have referenced. + clearAllNotifications(); + + // Notify all objects that are waiting for delete + // messages + if (getGroup()) + getGroup()->removeObject(this); + + processDeleteNotifies(); + + // Do removals from the Sim. + Sim::gNameDictionary->remove(this); + Sim::gIdDictionary->remove(this); + Sim::cancelPendingEvents(this); +} + +//--------------------------------------------------------------------------- + +void SimObject::deleteObject() +{ + AssertFatal(mFlags.test(Added), + "SimObject::deleteObject: Object not registered."); + AssertFatal(!isDeleted(),"SimManager::deleteObject: " + "Object has already been deleted"); + AssertFatal(!isRemoved(),"SimManager::deleteObject: " + "Object in the process of being removed"); + mFlags.set(Deleted); + + unregisterObject(); + delete this; +} + +class SimObjectDeleteEvent : public SimEvent +{ +public: + void process(SimObject *object) + { + object->deleteObject(); + } +}; + +void SimObject::safeDeleteObject() +{ + Sim::postEvent( this, new SimObjectDeleteEvent, Sim::getCurrentTime() + 1 ); +} + +//--------------------------------------------------------------------------- + + +void SimObject::setId(SimObjectId newId) +{ + if(!mFlags.test(Added)) + { + mId = newId; + return; + } + + // get this object out of the id dictionary if it's in it + Sim::gIdDictionary->remove(this); + + // Free current Id. + // Assign new one. + mId = newId ? newId : Sim::gNextObjectId++; + Sim::gIdDictionary->insert(this); +} + +void SimObject::assignName(const char *name) +{ + // Added this assert 3/30/2007 because it is dumb to try to name + // a SimObject the same thing as it's class name -patw + //AssertFatal( dStricmp( getClassName(), name ), "Attempted to assign a name to a SimObject which matches it's type name." ); + if( dStricmp( getClassName(), name ) == 0 ) + Con::errorf( "SimObject::assignName - Assigning name '%s' to instance of object with type '%s'." + " This can cause namespace linking issues.", getClassName(), name ); + + StringTableEntry newName = NULL; + if(name[0]) + newName = StringTable->insert(name); + + if(mGroup) + mGroup->nameDictionary.remove(this); + if(mFlags.test(Added)) + Sim::gNameDictionary->remove(this); + + objectName = newName; + + if(mGroup) + mGroup->nameDictionary.insert(this); + if(mFlags.test(Added)) + Sim::gNameDictionary->insert(this); +} + +//--------------------------------------------------------------------------- + +bool SimObject::registerObject(U32 id) +{ + setId(id); + return registerObject(); +} + +bool SimObject::registerObject(const char *name) +{ + assignName(name); + return registerObject(); +} + +bool SimObject::registerObject(const char *name, U32 id) +{ + setId(id); + assignName(name); + return registerObject(); +} diff --git a/console/simObject.cpp b/console/simObject.cpp new file mode 100644 index 0000000..452de8d --- /dev/null +++ b/console/simObject.cpp @@ -0,0 +1,1465 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simObject.h" + +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/simFieldDictionary.h" +#include "console/typeValidators.h" +#include "core/frameAllocator.h" +#include "core/stream/fileStream.h" +#include "core/fileObject.h" + +SimObject::SimObject( const U8 namespaceLinkMask ) : mNSLinkMask( namespaceLinkMask ) +{ + objectName = NULL; + mOriginalName = NULL; + mInternalName = NULL; + nextNameObject = (SimObject*)-1; + nextManagerNameObject = (SimObject*)-1; + nextIdObject = NULL; + + mFilename = NULL; + mDeclarationLine = -1; + + mId = 0; + mGroup = 0; + mNameSpace = NULL; + mNotifyList = NULL; + mFlags.set( ModStaticFields | ModDynamicFields ); + mTypeMask = 0; + + mFieldDictionary = NULL; + mCanSaveFieldDictionary = true; + + mClassName = NULL; + mSuperClassName = NULL; + + mEnabled = true; +} + +String SimObject::describeSelf() +{ + String desc = Parent::describeSelf(); + + if( mClassName ) + desc = avar( "%s|class: %s", desc.c_str(), mClassName ); + if( mId != 0 ) + desc = avar( "%s|id: %i", desc.c_str(), mId ); + if( objectName ) + desc = avar( "%s|name: %s", desc.c_str(), objectName ); + if( mInternalName ) + desc = avar( "%s|internal: %s", desc.c_str(), mInternalName ); + if( mNameSpace ) + desc = avar( "%s|nspace: %s", desc.c_str(), mNameSpace->mName ); + if( mGroup ) + desc = avar( "%s|group: %s", desc.c_str(), mGroup->getName() ); + + return desc; +} + +void SimObject::assignDynamicFieldsFrom(SimObject* parent) +{ + if(parent->mFieldDictionary) + { + if( mFieldDictionary == NULL ) + mFieldDictionary = new SimFieldDictionary; + mFieldDictionary->assignFrom(parent->mFieldDictionary); + } +} + +void SimObject::assignFieldsFrom(SimObject *parent) +{ + // only allow field assigns from objects of the same class: + if(getClassRep() == parent->getClassRep()) + { + const AbstractClassRep::FieldList &list = getFieldList(); + + // copy out all the fields: + for(U32 i = 0; i < list.size(); i++) + { + const AbstractClassRep::Field* f = &list[i]; + + // Skip the special field types. + if ( f->type >= AbstractClassRep::ARCFirstCustomField ) + continue; + + S32 lastField = f->elementCount - 1; + + for(S32 j = 0; j <= lastField; j++) + { + const char* fieldVal = (*f->getDataFn)( this, Con::getData(f->type, (void *) (((const char *)parent) + f->offset), j, f->table, f->flag)); + //if(fieldVal) + // Con::setData(f->type, (void *) (((const char *)this) + f->offset), j, 1, &fieldVal, f->table); + if(fieldVal) + { + // code copied from SimObject::setDataField(). + // TODO: paxorr: abstract this into a better setData / getData that considers prot fields. + FrameTemp buffer(2048); + FrameTemp bufferSecure(2048); // This buffer is used to make a copy of the data + ConsoleBaseType *cbt = ConsoleBaseType::getType( f->type ); + const char* szBuffer = cbt->prepData( fieldVal, buffer, 2048 ); + dMemset( bufferSecure, 0, 2048 ); + dMemcpy( bufferSecure, szBuffer, dStrlen( szBuffer ) ); + + if((*f->setDataFn)( this, bufferSecure ) ) + Con::setData(f->type, (void *) (((const char *)this) + f->offset), j, 1, &fieldVal, f->table); + } + } + } + } + + assignDynamicFieldsFrom(parent); +} + +bool SimObject::writeField(StringTableEntry fieldname, const char* value) +{ + // Don't write empty fields. + if (!value || !*value) + return false; + + // Don't write owner field for components + if( fieldname == StringTable->insert("owner") ) + return false; + + // Don't write ParentGroup + if( fieldname == StringTable->insert("parentGroup") ) + return false; + + // Don't write name, is within the parenthesis already + if( fieldname == StringTable->insert("name") ) + return false; + + return true; +} + +void SimObject::writeFields(Stream &stream, U32 tabStop) +{ + const AbstractClassRep::FieldList &list = getFieldList(); + + for(U32 i = 0; i < list.size(); i++) + { + const AbstractClassRep::Field* f = &list[i]; + + // Skip the special field types. + if ( f->type >= AbstractClassRep::ARCFirstCustomField ) + continue; + + for(U32 j = 0; S32(j) < f->elementCount; j++) + { + char array[8]; + dSprintf( array, 8, "%d", j ); + const char *val = getDataField(StringTable->insert( f->pFieldname ), array ); + + // Make a copy for the field check. + if (!val) + continue; + + U32 nBufferSize = dStrlen( val ) + 1; + FrameTemp valCopy( nBufferSize ); + dStrcpy( (char *)valCopy, val ); + + if (!writeField(f->pFieldname, valCopy)) + continue; + + val = valCopy; + + U32 expandedBufferSize = ( nBufferSize * 2 ) + dStrlen(f->pFieldname) + 32; + FrameTemp expandedBuffer( expandedBufferSize ); + if(f->elementCount == 1) + dSprintf(expandedBuffer, expandedBufferSize, "%s = \"", f->pFieldname); + else + dSprintf(expandedBuffer, expandedBufferSize, "%s[%d] = \"", f->pFieldname, j); + + // detect and collapse relative path information + char fnBuf[1024]; + if (f->type == TypeFilename || + f->type == TypeStringFilename || + f->type == TypeImageFilename) + { + Con::collapseScriptFilename(fnBuf, 1024, val); + val = fnBuf; + } + + expandEscape((char*)expandedBuffer + dStrlen(expandedBuffer), val); + dStrcat(expandedBuffer, "\";\r\n"); + + stream.writeTabs(tabStop); + stream.write(dStrlen(expandedBuffer),expandedBuffer); + } + } + if(mFieldDictionary && mCanSaveFieldDictionary) + mFieldDictionary->writeFields(this, stream, tabStop); +} + +void SimObject::write(Stream &stream, U32 tabStop, U32 flags) +{ + // Only output selected objects if they want that. + if((flags & SelectedOnly) && !isSelected()) + return; + + stream.writeTabs(tabStop); + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() && !(flags & NoName) ? getName() : ""); + stream.write(dStrlen(buffer), buffer); + writeFields(stream, tabStop + 1); + + stream.writeTabs(tabStop); + stream.write(4, "};\r\n"); +} + +bool SimObject::save(const char* pcFileName, bool bOnlySelected) +{ + static const char *beginMessage = "//--- OBJECT WRITE BEGIN ---"; + static const char *endMessage = "//--- OBJECT WRITE END ---"; + FileStream *stream; + FileObject f; + f.readMemory(pcFileName); + + // check for flags + U32 writeFlags = 0; + if(bOnlySelected) + writeFlags |= SimObject::SelectedOnly; + + if((stream = FileStream::createAndOpen( pcFileName, Torque::FS::File::Write )) == NULL) + return false; + + char docRoot[256]; + char modRoot[256]; + + dStrcpy(docRoot, pcFileName); + char *p = dStrrchr(docRoot, '/'); + if (p) *++p = '\0'; + else docRoot[0] = '\0'; + + dStrcpy(modRoot, pcFileName); + p = dStrchr(modRoot, '/'); + if (p) *++p = '\0'; + else modRoot[0] = '\0'; + + Con::setVariable("$DocRoot", docRoot); + Con::setVariable("$ModRoot", modRoot); + + const char *buffer; + while(!f.isEOF()) + { + buffer = (const char *) f.readLine(); + if(!dStrcmp(buffer, beginMessage)) + break; + stream->write(dStrlen(buffer), buffer); + stream->write(2, "\r\n"); + } + stream->write(dStrlen(beginMessage), beginMessage); + stream->write(2, "\r\n"); + write(*stream, 0, writeFlags); + stream->write(dStrlen(endMessage), endMessage); + stream->write(2, "\r\n"); + while(!f.isEOF()) + { + buffer = (const char *) f.readLine(); + if(!dStrcmp(buffer, endMessage)) + break; + } + while(!f.isEOF()) + { + buffer = (const char *) f.readLine(); + stream->write(dStrlen(buffer), buffer); + stream->write(2, "\r\n"); + } + + Con::setVariable("$DocRoot", NULL); + Con::setVariable("$ModRoot", NULL); + + delete stream; + + return true; + +} + +void SimObject::setInternalName(const char* newname) +{ + if(newname) + mInternalName = StringTable->insert(newname); +} + +StringTableEntry SimObject::getInternalName() const +{ + return mInternalName; +} + +void SimObject::setOriginalName(const char* originalName) +{ + if(originalName) + mOriginalName = StringTable->insert(originalName); +} + +StringTableEntry SimObject::getOriginalName() const +{ + return mOriginalName; +} + +void SimObject::setFilename(const char* file) +{ + if(file) + mFilename = StringTable->insert(file); +} + +StringTableEntry SimObject::getFilename() const +{ + return mFilename; +} + +void SimObject::setDeclarationLine(U32 lineNumber) +{ + mDeclarationLine = lineNumber; +} + +S32 SimObject::getDeclarationLine() const +{ + return mDeclarationLine; +} + +static S32 QSORT_CALLBACK compareFields(const void* a,const void* b) +{ + const AbstractClassRep::Field* fa = *((const AbstractClassRep::Field**)a); + const AbstractClassRep::Field* fb = *((const AbstractClassRep::Field**)b); + + return dStricmp(fa->pFieldname, fb->pFieldname); +} + +bool SimObject::isMethod( const char* methodName ) +{ + if( !methodName || !methodName[0] ) + return false; + + StringTableEntry stname = StringTable->insert( methodName ); + + if( getNamespace() ) + return ( getNamespace()->lookup( stname ) != NULL ); + + return false; +} + +const char *SimObject::tabComplete(const char *prevText, S32 baseLen, bool fForward) +{ + return mNameSpace->tabComplete(prevText, baseLen, fForward); +} + +void SimObject::setDataField(StringTableEntry slotName, const char *array, const char *value) +{ + // first search the static fields if enabled + if(mFlags.test(ModStaticFields)) + { + const AbstractClassRep::Field *fld = findField(slotName); + if(fld) + { + // Skip the special field types as they are not data. + if ( fld->type >= AbstractClassRep::ARCFirstCustomField ) + return; + + S32 array1 = array ? dAtoi(array) : 0; + + if(array1 >= 0 && array1 < fld->elementCount && fld->elementCount >= 1) + { + // If the set data notify callback returns true, then go ahead and + // set the data, otherwise, assume the set notify callback has either + // already set the data, or has deemed that the data should not + // be set at all. + FrameTemp buffer(2048); + FrameTemp bufferSecure(2048); // This buffer is used to make a copy of the data + // so that if the prep functions or any other functions use the string stack, the data + // is not corrupted. + + ConsoleBaseType *cbt = ConsoleBaseType::getType( fld->type ); + AssertFatal( cbt != NULL, "Could not resolve Type Id." ); + + const char* szBuffer = cbt->prepData( value, buffer, 2048 ); + dMemset( bufferSecure, 0, 2048 ); + dMemcpy( bufferSecure, szBuffer, dStrlen( szBuffer ) ); + + if( (*fld->setDataFn)( this, bufferSecure ) ) + Con::setData(fld->type, (void *) (((const char *)this) + fld->offset), array1, 1, &value, fld->table); + + if(fld->validator) + fld->validator->validateType(this, (void *) (((const char *)this) + fld->offset)); + + onStaticModified( slotName, value ); + + return; + } + + if(fld->validator) + fld->validator->validateType(this, (void *) (((const char *)this) + fld->offset)); + + onStaticModified( slotName, value ); + return; + } + } + + if(mFlags.test(ModDynamicFields)) + { + if(!mFieldDictionary) + mFieldDictionary = new SimFieldDictionary; + + if(!array) + { + mFieldDictionary->setFieldValue(slotName, value); + onDynamicModified( slotName, value ); + } + else + { + char buf[256]; + dStrcpy(buf, slotName); + dStrcat(buf, array); + StringTableEntry permanentSlotName = StringTable->insert(buf); + mFieldDictionary->setFieldValue(permanentSlotName, value); + onDynamicModified( permanentSlotName, value ); + } + } +} + + +void SimObject::dumpClassHierarchy() +{ + AbstractClassRep* pRep = getClassRep(); + while(pRep) + { + Con::warnf("%s ->", pRep->getClassName()); + pRep = pRep->getParentClass(); + } +} + +const char *SimObject::getDataField(StringTableEntry slotName, const char *array) +{ + if(mFlags.test(ModStaticFields)) + { + S32 array1 = array ? dAtoi(array) : -1; + const AbstractClassRep::Field *fld = findField(slotName); + + if(fld) + { + if(array1 == -1 && fld->elementCount == 1) + return (*fld->getDataFn)( this, Con::getData(fld->type, (void *) (((const char *)this) + fld->offset), 0, fld->table, fld->flag) ); + if(array1 >= 0 && array1 < fld->elementCount) + return (*fld->getDataFn)( this, Con::getData(fld->type, (void *) (((const char *)this) + fld->offset), array1, fld->table, fld->flag) );// + typeSizes[fld.type] * array1)); + return ""; + } + } + + if(mFlags.test(ModDynamicFields)) + { + if(!mFieldDictionary) + return ""; + + if(!array) + { + if (const char* val = mFieldDictionary->getFieldValue(slotName)) + return val; + } + else + { + static char buf[256]; + dStrcpy(buf, slotName); + dStrcat(buf, array); + if (const char* val = mFieldDictionary->getFieldValue(StringTable->insert(buf))) + return val; + } + } + + return ""; +} + +U32 SimObject::getDataFieldType( StringTableEntry slotName, const char* array ) +{ + const AbstractClassRep::Field* field = findField( slotName ); + if(field) + return field->type; + + // Check dynamic fields + if(!mFieldDictionary) + return 0; + + if(array == NULL || *array == 0) + return mFieldDictionary->getFieldType( slotName ); + else + { + static char buf[256]; + dStrcpy( buf, slotName ); + dStrcat( buf, array ); + + return mFieldDictionary->getFieldType( StringTable->insert( buf ) ); + } +} + +void SimObject::setDataFieldType(const U32 fieldTypeId, StringTableEntry slotName, const char *array) +{ + // This only works on dynamic fields, bail if we have no field dictionary + if(!mFieldDictionary) + return; + + if(array == NULL || *array == 0) + { + mFieldDictionary->setFieldType( slotName, fieldTypeId ); + onDynamicModified( slotName, mFieldDictionary->getFieldValue(slotName) ); + } + else + { + static char buf[256]; + dStrcpy( buf, slotName ); + dStrcat( buf, array ); + + mFieldDictionary->setFieldType( StringTable->insert( buf ), fieldTypeId ); + onDynamicModified( slotName, mFieldDictionary->getFieldValue(slotName) ); + } +} + +void SimObject::setDataFieldType(const char *typeName, StringTableEntry slotName, const char *array) +{ + // This only works on dynamic fields, bail if we have no field dictionary + if(!mFieldDictionary) + return; + + if(array == NULL || *array == 0) + mFieldDictionary->setFieldType( slotName, typeName ); + else + { + static char buf[256]; + dStrcpy( buf, slotName ); + dStrcat( buf, array ); + StringTableEntry permanentSlotName = StringTable->insert(buf); + + mFieldDictionary->setFieldType( permanentSlotName, typeName ); + onDynamicModified( permanentSlotName, mFieldDictionary->getFieldValue(permanentSlotName) ); + } +} + +SimObject::~SimObject() +{ + if( mFieldDictionary ) + delete mFieldDictionary; + + AssertFatal(nextNameObject == (SimObject*)-1,avar( + "SimObject::~SimObject: Not removed from dictionary: name %s, id %i", + objectName, mId)); + AssertFatal(nextManagerNameObject == (SimObject*)-1,avar( + "SimObject::~SimObject: Not removed from manager dictionary: name %s, id %i", + objectName,mId)); + AssertFatal(mFlags.test(Added) == 0, "SimObject::object " + "missing call to SimObject::onRemove"); +} + +//--------------------------------------------------------------------------- + +bool SimObject::isLocked() const +{ + if(!mFieldDictionary) + return false; + + const char * val = mFieldDictionary->getFieldValue( StringTable->insert( "locked", false ) ); + + return( val ? dAtob(val) : false ); +} + +void SimObject::setLocked( bool b = true ) +{ + setDataField(StringTable->insert("locked", false), NULL, b ? "true" : "false" ); +} + +bool SimObject::isHidden() const +{ + if(!mFieldDictionary) + return false; + + const char * val = mFieldDictionary->getFieldValue( StringTable->insert( "hidden", false ) ); + return( val ? dAtob(val) : false ); +} + +void SimObject::setHidden(bool b = true) +{ + setDataField(StringTable->insert("hidden", false), NULL, b ? "true" : "false" ); +} + +const char* SimObject::getIdString() const +{ + static char IDbuffer[64]; + dSprintf(IDbuffer, sizeof(IDbuffer), "%d", mId); + return IDbuffer; +} + +//--------------------------------------------------------------------------- + +bool SimObject::onAdd() +{ + mFlags.set(Added); + + if(getClassRep()) + mNameSpace = getClassRep()->getNameSpace(); + + linkNamespaces(); + + // onAdd() should return FALSE if there was an error + return true; +} + +void SimObject::onRemove() +{ + mFlags.clear(Added); + + unlinkNamespaces(); +} + +void SimObject::onGroupAdd() +{ +} + +void SimObject::onGroupRemove() +{ +} + +void SimObject::onDeleteNotify(SimObject*) +{ +} + +void SimObject::onNameChange(const char*) +{ +} + +void SimObject::onStaticModified(const char* slotName, const char* newValue) +{ +} + +void SimObject::onDynamicModified(const char* slotName, const char* newValue) +{ +} + +bool SimObject::processArguments(S32 argc, const char**argv) +{ + return argc == 0; +} + +bool SimObject::isChildOfGroup(SimGroup* pGroup) +{ + if(!pGroup) + return false; + + //if we *are* the group in question, + //return true: + if(pGroup == dynamic_cast(this)) + return true; + + SimGroup* temp = mGroup; + while(temp) + { + if(temp == pGroup) + return true; + temp = temp->mGroup; + } + + return false; +} + +//--------------------------------------------------------------------------- + +static Chunker notifyChunker(128000); +SimObject::Notify *SimObject::mNotifyFreeList = NULL; + +SimObject::Notify *SimObject::allocNotify() +{ + if(mNotifyFreeList) + { + SimObject::Notify *ret = mNotifyFreeList; + mNotifyFreeList = ret->next; + return ret; + } + return notifyChunker.alloc(); +} + +void SimObject::freeNotify(SimObject::Notify* note) +{ + AssertFatal(note->type != SimObject::Notify::Invalid, "Invalid notify"); + note->type = SimObject::Notify::Invalid; + note->next = mNotifyFreeList; + mNotifyFreeList = note; +} + +//------------------------------------------------------------------------------ + +SimObject::Notify* SimObject::removeNotify(void *ptr, SimObject::Notify::Type type) +{ + Notify **list = &mNotifyList; + while(*list) + { + if((*list)->ptr == ptr && (*list)->type == type) + { + SimObject::Notify *ret = *list; + *list = ret->next; + return ret; + } + list = &((*list)->next); + } + return NULL; +} + +void SimObject::deleteNotify(SimObject* obj) +{ + AssertFatal(!obj->isDeleted(), + "SimManager::deleteNotify: Object is being deleted"); + Notify *note = allocNotify(); + note->ptr = (void *) this; + note->next = obj->mNotifyList; + note->type = Notify::DeleteNotify; + obj->mNotifyList = note; + + note = allocNotify(); + note->ptr = (void *) obj; + note->next = mNotifyList; + note->type = Notify::ClearNotify; + mNotifyList = note; + + //obj->deleteNotifyList.pushBack(this); + //clearNotifyList.pushBack(obj); +} + +void SimObject::registerReference(SimObject **ptr) +{ + Notify *note = allocNotify(); + note->ptr = (void *) ptr; + note->next = mNotifyList; + note->type = Notify::ObjectRef; + mNotifyList = note; +} + +void SimObject::unregisterReference(SimObject **ptr) +{ + Notify *note = removeNotify((void *) ptr, Notify::ObjectRef); + if(note) + { + freeNotify(note); + + if( mFlags.test( AutoDelete ) ) + { + for( Notify* n = mNotifyList; n != NULL; n = n->next ) + if( n->type == Notify::ObjectRef ) + return; + + deleteObject(); + } + } +} + +void SimObject::clearNotify(SimObject* obj) +{ + Notify *note = obj->removeNotify((void *) this, Notify::DeleteNotify); + if(note) + freeNotify(note); + + note = removeNotify((void *) obj, Notify::ClearNotify); + if(note) + freeNotify(note); +} + +void SimObject::processDeleteNotifies() +{ + // clear out any delete notifies and + // object refs. + + while(mNotifyList) + { + Notify *note = mNotifyList; + mNotifyList = note->next; + + AssertFatal(note->type != Notify::ClearNotify, "Clear notes should be all gone."); + + if(note->type == Notify::DeleteNotify) + { + SimObject *obj = (SimObject *) note->ptr; + Notify *cnote = obj->removeNotify((void *)this, Notify::ClearNotify); + obj->onDeleteNotify(this); + freeNotify(cnote); + } + else + { + // it must be an object ref - a pointer refs this object + *((SimObject **) note->ptr) = NULL; + } + freeNotify(note); + } +} + +void SimObject::clearAllNotifications() +{ + for(Notify **cnote = &mNotifyList; *cnote; ) + { + Notify *temp = *cnote; + if(temp->type == Notify::ClearNotify) + { + *cnote = temp->next; + Notify *note = ((SimObject *) temp->ptr)->removeNotify((void *) this, Notify::DeleteNotify); + freeNotify(temp); + if ( note ) + freeNotify(note); + } + else + cnote = &(temp->next); + } +} + +//--------------------------------------------------------------------------- + +void SimObject::initPersistFields() +{ + addGroup( "Ungrouped" ); + addProtectedField("name", TypeName, Offset(objectName, SimObject), &setProtectedName, &defaultProtectedGetFn, "Optional global name of this object." ); + endGroup( "Ungrouped" ); + + addGroup("SimBase"); + addField("internalName", TypeString, Offset(mInternalName, SimObject), "Optional name that may be used to lookup this object within a SimSet."); + addProtectedField("parentGroup", TypeSimObjectPtr, Offset(mGroup, SimObject), &setProtectedParent, &defaultProtectedGetFn, "Group hierarchy parent of the object." ); + addField("canSaveDynamicFields", TypeBool, Offset(mCanSaveFieldDictionary, SimObject), "True if dynamic fields (added at runtime) should be saved. Defaults to true."); + endGroup("SimBase"); + + // Namespace Linking. + addGroup("Namespace Linking"); + addProtectedField("superClass", TypeString, Offset(mSuperClassName, SimObject), &setSuperClass, &defaultProtectedGetFn, + "Script super-class of object."); + addProtectedField("class", TypeString, Offset(mClassName, SimObject), &setClass, &defaultProtectedGetFn, + "Script class of object."); + // For legacy support + addProtectedField("className", TypeString, Offset(mClassName, SimObject), &setClass, &defaultProtectedGetFn, + "Script class of object."); + endGroup("Namespace Linking"); + + Parent::initPersistFields(); +} + +// Copy SimObject to another SimObject (Originally designed for T2D). +void SimObject::copyTo(SimObject* object) +{ + object->mClassName = mClassName; + object->mSuperClassName = mSuperClassName; + + linkNamespaces(); +} + +bool SimObject::setProtectedParent(void* obj, const char* data) +{ + SimGroup *parent = NULL; + SimObject *object = static_cast(obj); + + if(Sim::findObject(data, parent)) + parent->addObject(object); + + // always return false, because we've set mGroup when we called addObject + return false; +} + +bool SimObject::setProtectedName(void* obj, const char* data) +{ + SimObject *object = static_cast(obj); + + if ( object->isProperlyAdded() ) + object->assignName( data ); + + // always return false because we assign the name here + return false; +} + + +bool SimObject::addToSet(SimObjectId spid) +{ + if (mFlags.test(Added) == false) + return false; + + SimObject* ptr = Sim::findObject(spid); + if (ptr) + { + SimSet* sp = dynamic_cast(ptr); + AssertFatal(sp != 0, + "SimObject::addToSet: " + "ObjectId does not refer to a set object"); + sp->addObject(this); + return true; + } + return false; +} + +bool SimObject::addToSet(const char *ObjectName) +{ + if (mFlags.test(Added) == false) + return false; + + SimObject* ptr = Sim::findObject(ObjectName); + if (ptr) + { + SimSet* sp = dynamic_cast(ptr); + AssertFatal(sp != 0, + "SimObject::addToSet: " + "ObjectName does not refer to a set object"); + sp->addObject(this); + return true; + } + return false; +} + +bool SimObject::removeFromSet(SimObjectId sid) +{ + if (mFlags.test(Added) == false) + return false; + + SimSet *set; + if(Sim::findObject(sid, set)) + { + set->removeObject(this); + return true; + } + return false; +} + +bool SimObject::removeFromSet(const char *objectName) +{ + if (mFlags.test(Added) == false) + return false; + + SimSet *set; + if(Sim::findObject(objectName, set)) + { + set->removeObject(this); + return true; + } + return false; +} + +void SimObject::inspectPreApply() +{ +} + +void SimObject::inspectPostApply() +{ +} + +void SimObject::linkNamespaces() +{ + if( mNameSpace ) + unlinkNamespaces(); + + StringTableEntry parent = StringTable->insert( getClassName() ); + if( ( mNSLinkMask & LinkSuperClassName ) && mSuperClassName && mSuperClassName[0] ) + { + if( Con::linkNamespaces( parent, mSuperClassName ) ) + parent = mSuperClassName; + else + mSuperClassName = StringTable->insert( "" ); // CodeReview Is this behavior that we want? + // CodeReview This will result in the mSuperClassName variable getting hosed + // CodeReview if Con::linkNamespaces returns false. Looking at the code for + // CodeReview Con::linkNamespaces, and the call it makes to classLinkTo, it seems + // CodeReview like this would only fail if it had bogus data to begin with, but + // CodeReview I wanted to note this behavior which occurs in some implementations + // CodeReview but not all. -patw + } + + // ClassName -> SuperClassName + if ( ( mNSLinkMask & LinkClassName ) && mClassName && mClassName[0] ) + { + if( Con::linkNamespaces( parent, mClassName ) ) + parent = mClassName; + else + mClassName = StringTable->insert( "" ); // CodeReview (See previous note on this code) + } + + // ObjectName -> ClassName + StringTableEntry objectName = getName(); + if( objectName && objectName[0] ) + { + if( Con::linkNamespaces( parent, objectName ) ) + parent = objectName; + } + + // Store our namespace. + mNameSpace = Con::lookupNamespace( parent ); +} + +void SimObject::unlinkNamespaces() +{ + if (!mNameSpace) + return; + + // Restore NameSpaces + StringTableEntry child = getName(); + if( child && child[0] ) + { + if( ( mNSLinkMask & LinkClassName ) && mClassName && mClassName[0]) + { + if( Con::unlinkNamespaces( mClassName, child ) ) + child = mClassName; + } + + if( ( mNSLinkMask & LinkSuperClassName ) && mSuperClassName && mSuperClassName[0] ) + { + if( Con::unlinkNamespaces( mSuperClassName, child ) ) + child = mSuperClassName; + } + + Con::unlinkNamespaces( getClassName(), child ); + } + else + { + child = mClassName; + if( child && child[0] ) + { + if( ( mNSLinkMask & LinkSuperClassName ) && mSuperClassName && mSuperClassName[0] ) + { + if( Con::unlinkNamespaces( mSuperClassName, child ) ) + child = mSuperClassName; + } + + if( mNSLinkMask & LinkClassName ) + Con::unlinkNamespaces( getClassName(), child ); + } + else + { + if( ( mNSLinkMask & LinkSuperClassName ) && mSuperClassName && mSuperClassName[0] ) + Con::unlinkNamespaces( getClassName(), mSuperClassName ); + } + } + + mNameSpace = NULL; +} + +void SimObject::setClassNamespace( const char *classNamespace ) +{ + mClassName = StringTable->insert( classNamespace ); +} + +void SimObject::setSuperClassNamespace( const char *superClassNamespace ) +{ + mSuperClassName = StringTable->insert( superClassNamespace ); +} + +String SimObject::_getLogMessage(const char* fmt, void* args) const +{ + String objClass = "UnknownClass"; + if(getClassRep()) + objClass = getClassRep()->getClassName(); + + String objName = getName(); + if(objName.isEmpty()) + objName = "Unnamed"; + + String formattedMessage = String::VToString(fmt, args); + return String::ToString("%s - %s(%i) - %s", + objClass.c_str(), objName.c_str(), getId(), formattedMessage.c_str()); +} + +IMPLEMENT_CONOBJECT(SimObject); + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(SimObject, save, bool, 3, 4, "obj.save(fileName, )") +{ + bool bSelectedOnly = false; + if(argc > 3) + bSelectedOnly = dAtob(argv[3]); + + return object->save(argv[2], bSelectedOnly); + +} + +ConsoleMethod(SimObject, setName, void, 3, 3, "obj.setName(newName)") +{ + TORQUE_UNUSED(argc); + object->assignName(argv[2]); +} + +ConsoleMethod(SimObject, getName, const char *, 2, 2, "obj.getName()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + const char *ret = object->getName(); + return ret ? ret : ""; +} + +ConsoleMethod(SimObject, getClassName, const char *, 2, 2, "obj.getClassName()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + const char *ret = object->getClassName(); + return ret ? ret : ""; +} + +ConsoleMethod(SimObject, getFieldValue, const char *, 3, 3, "obj.getFieldValue(fieldName);") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + + const char *fieldName = StringTable->insert( argv[2] ); + return object->getDataField( fieldName, NULL ); +} + + +ConsoleMethod(SimObject, setFieldValue, bool, 4, 4, "obj.setFieldValue(fieldName,value);") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + const char *fieldName = StringTable->insert(argv[2]); + const char *value = argv[3]; + + object->setDataField( fieldName, NULL, value ); + + return true; + +} + +ConsoleMethod(SimObject, getFieldType, const char *, 3, 3, "obj.getFieldType(fieldName);") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + + const char *fieldName = StringTable->insert( argv[2] ); + U32 typeID = object->getDataFieldType( fieldName, NULL ); + ConsoleBaseType* type = ConsoleBaseType::getType( typeID ); + + if( type ) + return type->getTypeName(); + + return ""; +} + +ConsoleMethod(SimObject, setFieldType, void, 4, 4, "obj.setFieldType(fieldName, typeString);") +{ + const char *fieldName = StringTable->insert( argv[2] ); + object->setDataFieldType( argv[3], fieldName, NULL ); +} + +ConsoleMethod( SimObject, call, const char*, 2, 0, "( %args ) - Dynamically call a method on an object." ) +{ + argv[1] = argv[2]; + return Con::execute( object, argc - 1, argv + 1 ); +} + +//----------------------------------------------------------------------------- +// Set the internal name, can be used to find child objects +// in a meaningful way, usually from script, while keeping +// common script functionality together using the controls "Name" field. +//----------------------------------------------------------------------------- +ConsoleMethod( SimObject, setInternalName, void, 3, 3, "string InternalName") +{ + object->setInternalName(argv[2]); +} + +ConsoleMethod(SimObject, setEnabled, void, 3, 3, "(enabled)") +{ + object->setEnabled(dAtob(argv[2])); +} + +ConsoleMethod(SimObject, isEnabled, bool, 2, 2, "()") +{ + return object->isEnabled(); +} + +ConsoleMethod( SimObject, getInternalName, const char*, 2, 2, "getInternalName returns the objects internal name") +{ + return object->getInternalName(); +} + +ConsoleMethod(SimObject, dumpClassHierarchy, void, 2, 2, "obj.dumpClassHierarchy()") +{ + object->dumpClassHierarchy(); +} +ConsoleMethod(SimObject, isMemberOfClass, bool, 3, 3, " isMemberOfClass(string classname) -- returns true if this object is a member of the specified class") +{ + + AbstractClassRep* pRep = object->getClassRep(); + while(pRep) + { + if(!dStricmp(pRep->getClassName(), argv[2])) + { + //matches + return true; + } + + pRep = pRep->getParentClass(); + } + + return false; +} +ConsoleMethod(SimObject, getId, S32, 2, 2, "obj.getId()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return object->getId(); +} + +ConsoleMethod(SimObject, getGroup, S32, 2, 2, "obj.getGroup()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + SimGroup *grp = object->getGroup(); + if(!grp) + return -1; + return grp->getId(); +} + +ConsoleMethod(SimObject, delete, void, 2, 2,"obj.delete()") +{ + TORQUE_UNUSED(argc);TORQUE_UNUSED(argv); + object->deleteObject(); +} + +ConsoleMethod(SimObject,schedule, S32, 4, 0, "object.schedule(time, command, );") +{ + U32 timeDelta = U32(dAtof(argv[2])); + argv[2] = argv[3]; + argv[3] = argv[1]; + SimConsoleEvent *evt = new SimConsoleEvent(argc - 2, argv + 2, true); + S32 ret = Sim::postEvent(object, evt, Sim::getCurrentTime() + timeDelta); + // #ifdef DEBUG + // Con::printf("obj %s schedule(%s) = %d", argv[3], argv[2], ret); + // Con::executef( "backtrace"); + // #endif + return ret; +} + +ConsoleMethod(SimObject, getDynamicFieldCount, S32, 2, 2, "obj.getDynamicFieldCount()") +{ + S32 count = 0; + SimFieldDictionary* fieldDictionary = object->getFieldDictionary(); + for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr) + count++; + + return count; +} + +ConsoleMethod(SimObject, getDynamicField, const char*, 3, 3, "obj.getDynamicField(index)") +{ + SimFieldDictionary* fieldDictionary = object->getFieldDictionary(); + SimFieldDictionaryIterator itr(fieldDictionary); + S32 index = dAtoi(argv[2]); + for (S32 i = 0; i < index; i++) + { + if (!(*itr)) + { + Con::warnf("Invalid dynamic field index passed to SimObject::getDynamicField!"); + return NULL; + } + ++itr; + } + + char* buffer = Con::getReturnBuffer(256); + if (*itr) + { + SimFieldDictionary::Entry* entry = *itr; + dSprintf(buffer, 256, "%s\t%s", entry->slotName, entry->value); + return buffer; + } + + Con::warnf("Invalid dynamic field index passed to SimObject::getDynamicField!"); + return NULL; +} + +ConsoleMethod( SimObject, getFieldCount, S32, 2, 2, "() - Gets the number of persistent fields on the object." ) +{ + const AbstractClassRep::FieldList &list = object->getFieldList(); + const AbstractClassRep::Field* f; + U32 numDummyEntries = 0; + + for(int i = 0; i < list.size(); i++) + { + f = &list[i]; + + // The special field types do not need to be counted. + if ( f->type >= AbstractClassRep::ARCFirstCustomField ) + numDummyEntries++; + } + + return list.size() - numDummyEntries; +} + +ConsoleMethod( SimObject, getField, const char*, 3, 3, "(int index) - Gets the name of the field at the given index." ) +{ + S32 index = dAtoi( argv[2] ); + const AbstractClassRep::FieldList &list = object->getFieldList(); + if( ( index < 0 ) || ( index >= list.size() ) ) + return ""; + + const AbstractClassRep::Field* f; + S32 currentField = 0; + for ( U32 i = 0; i < list.size() && currentField <= index; i++ ) + { + f = &list[i]; + + // The special field types can be skipped. + if ( f->type >= AbstractClassRep::ARCFirstCustomField ) + continue; + + if(currentField == index) + return f->pFieldname; + + currentField++; + } + + // if we found nada, return nada. + return ""; +} + +ConsoleMethod(SimObject,dump, void, 2, 2, "obj.dump()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + + Con::printf( "Class: %s", object->getClassName() ); + + const AbstractClassRep::FieldList &list = object->getFieldList(); + char expandedBuffer[4096]; + + Con::printf("Member Fields:"); + Vector flist(__FILE__, __LINE__); + + for(U32 i = 0; i < list.size(); i++) + flist.push_back(&list[i]); + + dQsort(flist.address(),flist.size(),sizeof(AbstractClassRep::Field *),compareFields); + + for(Vector::iterator itr = flist.begin(); itr != flist.end(); itr++) + { + const AbstractClassRep::Field* f = *itr; + + // The special field types can be skipped. + if ( f->type >= AbstractClassRep::ARCFirstCustomField ) + continue; + + for(U32 j = 0; S32(j) < f->elementCount; j++) + { + // [neo, 07/05/2007 - #3000] + // Some objects use dummy vars and projected fields so make sure we call the get functions + //const char *val = Con::getData(f->type, (void *) (((const char *)object) + f->offset), j, f->table, f->flag); + const char *val = (*f->getDataFn)( object, Con::getData(f->type, (void *) (((const char *)object) + f->offset), j, f->table, f->flag) );// + typeSizes[fld.type] * array1)); + + if(!val /*|| !*val*/) + continue; + if(f->elementCount == 1) + dSprintf(expandedBuffer, sizeof(expandedBuffer), " %s = \"", f->pFieldname); + else + dSprintf(expandedBuffer, sizeof(expandedBuffer), " %s[%d] = \"", f->pFieldname, j); + expandEscape(expandedBuffer + dStrlen(expandedBuffer), val); + Con::printf("%s\"", expandedBuffer); + } + } + + Con::printf("Tagged Fields:"); + if(object->getFieldDictionary()) + object->getFieldDictionary()->printFields(object); + + Con::printf("Methods:"); + Namespace *ns = object->getNamespace(); + VectorPtr vec(__FILE__, __LINE__); + + if(ns) + ns->getEntryList(&vec); + + bool sawCBs = false; + + for(Vector::iterator j = vec.begin(); j != vec.end(); j++) + { + Namespace::Entry *e = *j; + + if(e->mType == Namespace::Entry::ScriptCallbackType) + sawCBs = true; + + if(e->mType < 0) + continue; + + Con::printf(" %s() - %s", e->mFunctionName, e->mUsage ? e->mUsage : ""); + } + + if(sawCBs) + { + Con::printf("Callbacks:"); + + for(Vector::iterator j = vec.begin(); j != vec.end(); j++) + { + Namespace::Entry *e = *j; + + if(e->mType != Namespace::Entry::ScriptCallbackType) + continue; + + Con::printf(" %s() - %s", e->cb.mCallbackName, e->mUsage ? e->mUsage : ""); + } + } + +} + +ConsoleMethod(SimObject, getType, S32, 2, 2, "obj.getType()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return((S32)object->getType()); +} + +ConsoleMethod(SimObject, isMethod, bool, 3, 3, "obj.isMethod(string method name)") +{ + return object->isMethod( argv[2] ); +} + +ConsoleMethod(SimObject, isChildOfGroup, bool, 3,3," returns true, if we are in the specified simgroup - or a subgroup thereof") +{ + SimGroup* pGroup = dynamic_cast(Sim::findObject(dAtoi(argv[2]))); + if(pGroup) + { + return object->isChildOfGroup(pGroup); + } + + return false; +} + +ConsoleMethod(SimObject, getClassNamespace, const char*, 2, 2, "") +{ + return object->getClassNamespace(); +} + +ConsoleMethod(SimObject, getSuperClassNamespace, const char*, 2, 2, "") +{ + return object->getSuperClassNamespace(); +} + +ConsoleMethod(SimObject, setClassNamespace, void, 2, 3, "") +{ + object->setClassNamespace(argv[2]); + + if ( object->isProperlyAdded() ) + object->linkNamespaces(); +} + +ConsoleMethod(SimObject, setSuperClassNamespace, void, 2, 3, "") +{ + object->setSuperClassNamespace(argv[2]); + + if ( object->isProperlyAdded() ) + object->linkNamespaces(); +} + +ConsoleMethod(SimObject, isSelected, bool, 2, 2, "Get whether the object has been marked as selected. (in editor)") +{ + return object->isSelected(); +} + +ConsoleMethod(SimObject, setIsSelected, void, 3, 3, "Set whether the object has been marked as selected. (in editor)") +{ + object->setSelected(dAtob(argv[2])); +} + +ConsoleMethod(SimObject, isExpanded, bool, 2, 2, "Get whether the object has been marked as expanded. (in editor)") +{ + return object->isExpanded(); +} + +ConsoleMethod(SimObject, setIsExpanded, void, 3, 3, "Set whether the object has been marked as expanded. (in editor)") +{ + object->setExpanded(dAtob(argv[2])); +} + +ConsoleMethod(SimObject, getFilename, const char*, 2, 2, "Returns the filename the object is attached to") +{ + return object->getFilename(); +} + +ConsoleMethod(SimObject, setFilename, void, 3, 3, "(fileName) Sets the object's file name and path") +{ + return object->setFilename(argv[2]); +} \ No newline at end of file diff --git a/console/simObject.h b/console/simObject.h new file mode 100644 index 0000000..120896a --- /dev/null +++ b/console/simObject.h @@ -0,0 +1,828 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIM_H_ +#include "console/sim.h" +#endif + +#include "console/consoleObject.h" +#include "core/bitSet.h" + +#ifndef _SIMOBJECT_H_ +#define _SIMOBJECT_H_ + +// Forward Refs +class Stream; +class LightManager; +class SimFieldDictionary; + +/// Base class for objects involved in the simulation. +/// +/// @section simobject_intro Introduction +/// +/// SimObject is a base class for most of the classes you'll encounter +/// working in Torque. It provides fundamental services allowing "smart" +/// object referencing, creation, destruction, organization, and location. +/// Along with SimEvent, it gives you a flexible event-scheduling system, +/// as well as laying the foundation for the in-game editors, GUI system, +/// and other vital subsystems. +/// +/// @section simobject_subclassing Subclassing +/// +/// You will spend a lot of your time in Torque subclassing, or working +/// with subclasses of, SimObject. SimObject is designed to be easy to +/// subclass. +/// +/// You should not need to override anything in a subclass except: +/// - The constructor/destructor. +/// - processArguments() +/// - onAdd()/onRemove() +/// - onGroupAdd()/onGroupRemove() +/// - onNameChange() +/// - onStaticModified() +/// - onDeleteNotify() +/// - onEditorEnable()/onEditorDisable() +/// - inspectPreApply()/inspectPostApply() +/// - things from ConsoleObject (see ConsoleObject docs for specifics) +/// +/// Of course, if you know what you're doing, go nuts! But in most cases, you +/// shouldn't need to touch things not on that list. +/// +/// When you subclass, you should define a typedef in the class, called Parent, +/// that references the class you're inheriting from. +/// +/// @code +/// class mySubClass : public SimObject { +/// typedef SimObject Parent; +/// ... +/// @endcode +/// +/// Then, when you override a method, put in: +/// +/// @code +/// bool mySubClass::onAdd() +/// { +/// if(!Parent::onAdd()) +/// return false; +/// +/// // ... do other things ... +/// } +/// @endcode +/// +/// Of course, you want to replace onAdd with the appropriate method call. +/// +/// @section simobject_lifecycle A SimObject's Life Cycle +/// +/// SimObjects do not live apart. One of the primary benefits of using a +/// SimObject is that you can uniquely identify it and easily find it (using +/// its ID). Torque does this by keeping a global hierarchy of SimGroups - +/// a tree - containing every registered SimObject. You can then query +/// for a given object using Sim::findObject() (or SimSet::findObject() if +/// you want to search only a specific set). +/// +/// @code +/// // Three examples of registering an object. +/// +/// // Method 1: +/// AIClient *aiPlayer = new AIClient(); +/// aiPlayer->registerObject(); +/// +/// // Method 2: +/// ActionMap* globalMap = new ActionMap; +/// globalMap->registerObject("GlobalActionMap"); +/// +/// // Method 3: +/// bool reg = mObj->registerObject(id); +/// @endcode +/// +/// Registering a SimObject performs these tasks: +/// - Marks the object as not cleared and not removed. +/// - Assigns the object a unique SimObjectID if it does not have one already. +/// - Adds the object to the global name and ID dictionaries so it can be found +/// again. +/// - Calls the object's onAdd() method. Note: SimObject::onAdd() performs +/// some important initialization steps. See @ref simobject_subclassing "here +/// for details" on how to properly subclass SimObject. +/// - If onAdd() fails (returns false), it calls unregisterObject(). +/// - Checks to make sure that the SimObject was properly initialized (and asserts +/// if not). +/// +/// Calling registerObject() and passing an ID or a name will cause the object to be +/// assigned that name and/or ID before it is registered. +/// +/// Congratulations, you have now registered your object! What now? +/// +/// Well, hopefully, the SimObject will have a long, useful life. But eventually, +/// it must die. +/// +/// There are a two ways a SimObject can die. +/// - First, the game can be shut down. This causes the root SimGroup +/// to be unregistered and deleted. When a SimGroup is unregistered, +/// it unregisters all of its member SimObjects; this results in everything +/// that has been registered with Sim being unregistered, as everything +/// registered with Sim is in the root group. +/// - Second, you can manually kill it off, either by calling unregisterObject() +/// or by calling deleteObject(). +/// +/// When you unregister a SimObject, the following tasks are performed: +/// - The object is flagged as removed. +/// - Notifications are cleaned up. +/// - If the object is in a group, then it removes itself from the group. +/// - Delete notifications are sent out. +/// - Finally, the object removes itself from the Sim globals, and tells +/// Sim to get rid of any pending events for it. +/// +/// If you call deleteObject(), all of the above tasks are performed, in addition +/// to some sanity checking to make sure the object was previously added properly, +/// and isn't in the process of being deleted. After the object is unregistered, it +/// deallocates itself. +/// +/// @section simobject_editor Torque Editors +/// +/// SimObjects are one of the building blocks for the in-game editors. They +/// provide a basic interface for the editor to be able to list the fields +/// of the object, update them safely and reliably, and inform the object +/// things have changed. +/// +/// This interface is implemented in the following areas: +/// - onNameChange() is called when the object is renamed. +/// - onStaticModified() is called whenever a static field is modified. +/// - inspectPreApply() is called before the object's fields are updated, +/// when changes are being applied. +/// - inspectPostApply() is called after the object's fields are updated. +/// - onEditorEnable() is called whenever an editor is enabled (for instance, +/// when you hit F11 to bring up the world editor). +/// - onEditorDisable() is called whenever the editor is disabled (for instance, +/// when you hit F11 again to close the world editor). +/// +/// (Note: you can check the variable gEditingMission to see if the mission editor +/// is running; if so, you may want to render special indicators. For instance, the +/// fxFoliageReplicator renders inner and outer radii when the mission editor is +/// runnning.) +/// +/// @section simobject_console The Console +/// +/// SimObject extends ConsoleObject by allowing you to +/// to set arbitrary dynamic fields on the object, as well as +/// statically defined fields. This is done through two methods, +/// setDataField and getDataField, which deal with the complexities of +/// allowing access to two different types of object fields. +/// +/// Static fields take priority over dynamic fields. This is to be +/// expected, as the role of dynamic fields is to allow data to be +/// stored in addition to the predefined fields. +/// +/// The fields in a SimObject are like properties (or fields) in a class. +/// +/// Some fields may be arrays, which is what the array parameter is for; if it's non-null, +/// then it is parsed with dAtoI and used as an index into the array. If you access something +/// as an array which isn't, then you get an empty string. +/// +/// You don't need to read any further than this. Right now, +/// set/getDataField are called a total of 6 times through the entire +/// Torque codebase. Therefore, you probably don't need to be familiar +/// with the details of accessing them. You may want to look at Con::setData +/// instead. Most of the time you will probably be accessing fields directly, +/// or using the scripting language, which in either case means you don't +/// need to do anything special. +/// +/// The functions to get/set these fields are very straightforward: +/// +/// @code +/// setDataField(StringTable->insert("locked", false), NULL, b ? "true" : "false" ); +/// curObject->setDataField(curField, curFieldArray, STR.getStringValue()); +/// setDataField(slotName, array, value); +/// @endcode +/// +/// For advanced users: There are two flags which control the behavior +/// of these functions. The first is ModStaticFields, which controls whether +/// or not the DataField functions look through the static fields (defined +/// with addField; see ConsoleObject for details) of the class. The second +/// is ModDynamicFields, which controls dynamically defined fields. They are +/// set automatically by the console constructor code. +/// +/// @nosubgrouping +class SimObject: public ConsoleObject +{ + typedef ConsoleObject Parent; + + friend class SimManager; + friend class SimGroup; + friend class SimNameDictionary; + friend class SimManagerNameDictionary; + friend class SimIdDictionary; + + //-------------------------------------- Structures and enumerations +private: + + /// Flags for use in mFlags + enum { + Deleted = BIT(0), ///< This object is marked for deletion. + Removed = BIT(1), ///< This object has been unregistered from the object system. + Added = BIT(3), ///< This object has been registered with the object system. + Selected = BIT(4), ///< This object has been marked as selected. (in editor) + Expanded = BIT(5), ///< This object has been marked as expanded. (in editor) + ModStaticFields = BIT(6), ///< The object allows you to read/modify static fields + ModDynamicFields = BIT(7), ///< The object allows you to read/modify dynamic fields + AutoDelete = BIT( 8 ), ///< Delete this object when the last ObjectRef is gone. + }; +public: + /// @name Notification + /// @{ + struct Notify { + enum Type { + ClearNotify, ///< Notified when the object is cleared. + DeleteNotify, ///< Notified when the object is deleted. + ObjectRef, ///< Cleverness to allow tracking of references. + Invalid ///< Mark this notification as unused (used in freeNotify). + } type; + void *ptr; ///< Data (typically referencing or interested object). + Notify *next; ///< Next notification in the linked list. + }; + + /// @} + + /// Flags passed to SimObject::write + enum WriteFlags { + SelectedOnly = BIT(0), ///< Indicates that only objects marked as selected should be outputted. Used in SimSet. + NoName = BIT(1) ///< Indicates that the object name should not be saved. + }; + +private: + // dictionary information stored on the object + StringTableEntry objectName; + StringTableEntry mOriginalName; + SimObject* nextNameObject; + SimObject* nextManagerNameObject; + SimObject* nextIdObject; + + SimGroup* mGroup; ///< SimGroup we're contained in, if any. + BitSet32 mFlags; + + /// @name Notification + /// @{ + Notify* mNotifyList; + /// @} + + Vector mFieldFilter; + + // File this SimObject was loaded from + StringTableEntry mFilename; + + // The line number that the object was + // declared on if it was loaded from a file + S32 mDeclarationLine; + +protected: + SimObjectId mId; ///< Id number for this object. + Namespace* mNameSpace; + U32 mTypeMask; + + static bool mForceId; ///< Force a registered object to use the given Id. Cleared upon use. + static SimObjectId mForcedId; ///< The Id to force upon the object. Poor object. + +protected: + /// @name Notification + /// Helper functions for notification code. + /// @{ + + static SimObject::Notify *mNotifyFreeList; + static SimObject::Notify *allocNotify(); ///< Get a free Notify structure. + static void freeNotify(SimObject::Notify*); ///< Mark a Notify structure as free. + + /// @} + +private: + SimFieldDictionary *mFieldDictionary; ///< Storage for dynamic fields. + +protected: + bool mCanSaveFieldDictionary; ///< true if dynamic fields (added at runtime) should be saved, defaults to true + StringTableEntry mInternalName; ///< Stores object Internal Name + + // Namespace linking + StringTableEntry mClassName; ///< Stores the class name to link script class namespaces + StringTableEntry mSuperClassName; ///< Stores super class name to link script class namespaces + + bool mEnabled; ///< Flag used to indicate whether object is enabled or not. + + // Namespace protected set methods + static bool setClass(void* obj, const char* data) { static_cast(obj)->setClassNamespace(data); return false; }; + static bool setSuperClass(void* obj, const char* data) { static_cast(obj)->setSuperClassNamespace(data); return false; }; + + // Group hierarchy protected set method + static bool setProtectedParent(void* obj, const char* data); + + // Object name protected set method + static bool setProtectedName(void* obj, const char* data); + + /// We can provide more detail, like object name and id. + virtual String _getLogMessage(const char* fmt, void* args) const; + + // Accessors +public: + virtual void setEnabled( const bool enabled ) { mEnabled = enabled; } + bool isEnabled() const { return mEnabled; } + + StringTableEntry getClassNamespace() const { return mClassName; }; + StringTableEntry getSuperClassNamespace() const { return mSuperClassName; }; + void setClassNamespace( const char* classNamespace ); + void setSuperClassNamespace( const char* superClassNamespace ); + // By setting the value of mNSLinkMask in the constructor of a class that + // inherits from SimObject, you can specify how the namespaces are linked + // for that class. An easy way to think about this change, if you have worked + // with this in the past is that ScriptObject uses: + // mNSLinkMask = LinkSuperClassName | LinkClassName; + // which will attempt to do a full namespace link checking mClassName and mSuperClassName + // + // and BehaviorTemplate does not set the value of NSLinkMask, which means that + // only the default link will be made, which is: ObjectName -> ClassName + enum SimObjectNSLinkType + { + LinkClassName = BIT(0), + LinkSuperClassName = BIT(1) + }; + U8 mNSLinkMask; + void linkNamespaces(); + void unlinkNamespaces(); + +public: + virtual String describeSelf(); + + /// @name Accessors + /// @{ + + /// Get the value of a field on the object. + /// + /// See @ref simobject_console "here" for a detailed discussion of what this + /// function does. + /// + /// @param slotName Field to access. + /// @param array String containing index into array + /// (if field is an array); if NULL, it is ignored. + const char *getDataField(StringTableEntry slotName, const char *array); + + /// Set the value of a field on the object. + /// + /// See @ref simobject_console "here" for a detailed discussion of what this + /// function does. + /// + /// @param slotName Field to access. + /// @param array String containing index into array; if NULL, it is ignored. + /// @param value Value to store. + void setDataField(StringTableEntry slotName, const char *array, const char *value); + + /// Get the type of a field on the object. + /// + /// @param slotName Field to access. + /// @param array String containing index into array + /// (if field is an array); if NULL, it is ignored. + U32 getDataFieldType(StringTableEntry slotName, const char *array); + + /// Set the type of a *dynamic* field on the object. + /// + /// @param typeName/Id Console base type name/id to assign to a dynamic field. + /// @param slotName Field to access. + /// @param array String containing index into array + /// (if field is an array); if NULL, it is ignored. + void setDataFieldType(const U32 fieldTypeId, StringTableEntry slotName, const char *array); + void setDataFieldType(const char *typeName, StringTableEntry slotName, const char *array); + + /// Get reference to the dictionary containing dynamic fields. + /// + /// See @ref simobject_console "here" for a detailed discussion of what this + /// function does. + /// + /// This dictionary can be iterated over using a SimFieldDictionaryIterator. + SimFieldDictionary * getFieldDictionary() {return(mFieldDictionary);} + + // Component Information + inline virtual StringTableEntry getComponentName() { return StringTable->insert( getClassName() ); }; + + /// Set whether fields created at runtime should be saved. Default is true. + void setCanSaveDynamicFields(bool bCanSave){ mCanSaveFieldDictionary = bCanSave;} + /// Get whether fields created at runtime should be saved. Default is true. + bool getCanSaveDynamicFields(bool bCanSave){ return mCanSaveFieldDictionary;} + + /// These functions support internal naming that is not namespace + /// bound for locating child controls in a generic way. + /// + /// Set the internal name of this control (Not linked to a namespace) + void setInternalName(const char* newname); + + /// Get the internal name of this control + StringTableEntry getInternalName() const; + + /// Set the original name of this control + void setOriginalName(const char* originalName); + + /// Get the original name of this control + StringTableEntry getOriginalName() const; + + /// These functions allow you to set and access the filename + /// where this object was created. + /// + /// Set the filename + void setFilename(const char* file); + + /// Get the filename + StringTableEntry getFilename() const; + + /// These functions are used to track the line number (1-based) + /// on which the object was created if it was loaded from script + /// + /// Set the declaration line number + void setDeclarationLine(U32 lineNumber); + + /// Get the declaration line number + S32 getDeclarationLine() const; + + /// Save object as a TorqueScript File. + virtual bool save(const char* pcFilePath, bool bOnlySelected=false); + + /// Check if a method exists in the objects current namespace. + virtual bool isMethod( const char* methodName ); + /// @} + + /// @name Initialization + /// @{ + + ///added this so that you can print the entire class hierarchy, including script objects, + //from the console or C++. + virtual void dumpClassHierarchy(); + /// + SimObject( const U8 namespaceLinkMask = 0 ); + virtual ~SimObject(); + + virtual bool processArguments(S32 argc, const char **argv); ///< Process constructor options. (ie, new SimObject(1,2,3)) + + /// @} + + /// @name Events + /// @{ + virtual bool onAdd(); ///< Called when the object is added to the sim. + virtual void onRemove(); ///< Called when the object is removed from the sim. + virtual void onGroupAdd(); ///< Called when the object is added to a SimGroup. + virtual void onGroupRemove(); ///< Called when the object is removed from a SimGroup. + virtual void onNameChange(const char *name); ///< Called when the object's name is changed. + /// + /// Specifically, these are called by setDataField + /// when a static or dynamic field is modified, see + /// @ref simobject_console "the console details". + virtual void onStaticModified(const char* slotName, const char*newValue = NULL); ///< Called when a static field is modified. + virtual void onDynamicModified(const char* slotName, const char*newValue = NULL); ///< Called when a dynamic field is modified. + + /// Called before any property of the object is changed in the world editor. + /// + /// The calling order here is: + /// - inspectPreApply() + /// - ... + /// - calls to setDataField() + /// - ... + /// - inspectPostApply() + virtual void inspectPreApply(); + + /// Called after any property of the object is changed in the world editor. + /// + /// @see inspectPreApply + virtual void inspectPostApply(); + + /// Called when a SimObject is deleted. + /// + /// When you are on the notification list for another object + /// and it is deleted, this method is called. + virtual void onDeleteNotify(SimObject *object); + + /// Called when the editor is activated. + virtual void onEditorEnable(){}; + + /// Called when the editor is deactivated. + virtual void onEditorDisable(){}; + + /// @} + + /// Find a named sub-object of this object. + /// + /// This is subclassed in the SimGroup and SimSet classes. + /// + /// For a single object, it just returns NULL, as normal objects cannot have children. + virtual SimObject *findObject(const char *name); + + /// @name Notification + /// @{ + Notify *removeNotify(void *ptr, Notify::Type); ///< Remove a notification from the list. + void deleteNotify(SimObject* obj); ///< Notify an object when we are deleted. + void clearNotify(SimObject* obj); ///< Notify an object when we are cleared. + void clearAllNotifications(); ///< Remove all notifications for this object. + void processDeleteNotifies(); ///< Send out deletion notifications. + + /// Register a reference to this object. + /// + /// You pass a pointer to your reference to this object. + /// + /// When the object is deleted, it will null your + /// pointer, ensuring you don't access old memory. + /// + /// @param obj Pointer to your reference to the object. + void registerReference(SimObject **obj); + + /// Unregister a reference to this object. + /// + /// Remove a reference from the list, so that it won't + /// get nulled inappropriately. + /// + /// Call this when you're done with your reference to + /// the object, especially if you're going to free the + /// memory. Otherwise, you may erroneously get something + /// overwritten. + /// + /// @see registerReference + void unregisterReference(SimObject **obj); + + /// @} + + /// @name Registration + /// + /// SimObjects must be registered with the object system. + /// @{ + + + /// Register an object with the object system. + /// + /// This must be called if you want to keep the object around. + /// In the rare case that you will delete the object immediately, or + /// don't want to be able to use Sim::findObject to locate it, then + /// you don't need to register it. + /// + /// registerObject adds the object to the global ID and name dictionaries, + /// after first assigning it a new ID number. It calls onAdd(). If onAdd fails, + /// it unregisters the object and returns false. + /// + /// If a subclass's onAdd doesn't eventually call SimObject::onAdd(), it will + /// cause an assertion. + bool registerObject(); + + /// Register the object, forcing the id. + /// + /// @see registerObject() + /// @param id ID to assign to the object. + bool registerObject(U32 id); + + /// Register the object, assigning the name. + /// + /// @see registerObject() + /// @param name Name to assign to the object. + bool registerObject(const char *name); + + /// Register the object, assigning a name and ID. + /// + /// @see registerObject() + /// @param name Name to assign to the object. + /// @param id ID to assign to the object. + bool registerObject(const char *name, U32 id); + + /// Unregister the object from Sim. + /// + /// This performs several operations: + /// - Sets the removed flag. + /// - Call onRemove() + /// - Clear out notifications. + /// - Remove the object from... + /// - its group, if any. (via getGroup) + /// - Sim::gNameDictionary + /// - Sim::gIDDictionary + /// - Finally, cancel any pending events for this object (as it can't receive them now). + void unregisterObject(); + + void deleteObject(); ///< Unregister, mark as deleted, and free the object. + + /// Performs a safe delayed delete of the object using a sim event. + void safeDeleteObject(); + + /// + /// This helper function can be used when you're done with the object + /// and don't want to be bothered with the details of cleaning it up. + + /// @} + + /// @name Accessors + /// @{ + SimObjectId getId() const { return mId; } + const char* getIdString() const; + U32 getTypeMask() const { return mTypeMask; } + U32 getType() const { return mTypeMask; } ///< same as getTypeMask() - left around for legacy code + const char* getName() const { return objectName; } + + void setId(SimObjectId id); + static void setForcedId(SimObjectId id) {mForceId = true; mForcedId = id;} ///< Force an Id on the next registered object. + void assignName(const char* name); + SimGroup* getGroup() const { return mGroup; } + bool isChildOfGroup(SimGroup* pGroup); + bool isProperlyAdded() const { return mFlags.test(Added); } + bool isDeleted() const { return mFlags.test(Deleted); } + bool isRemoved() const { return mFlags.test(Deleted | Removed); } + virtual bool isLocked() const; + virtual void setLocked( bool b ); + virtual bool isHidden() const; + virtual void setHidden(bool b); + + /// @} + + /// @name Sets + /// + /// The object must be properly registered before you can add/remove it to/from a set. + /// + /// All these functions accept either a name or ID to identify the set you wish + /// to operate on. Then they call addObject or removeObject on the set, which + /// sets up appropriate notifications. + /// + /// An object may be in multiple sets at a time. + /// @{ + bool addToSet(SimObjectId); + bool addToSet(const char *); + bool removeFromSet(SimObjectId); + bool removeFromSet(const char *); + + /// @} + + /// @name Serialization + /// @{ + + /// Determine whether or not a field should be written. + /// + /// @param fiedname The name of the field being written. + /// @param value The value of the field. + virtual bool writeField(StringTableEntry fieldname, const char* value); + + /// Output the TorqueScript to recreate this object. + /// + /// This calls writeFields internally. + /// @param stream Stream to output to. + /// @param tabStop Indentation level for this object. + /// @param flags If SelectedOnly is passed here, then + /// only objects marked as selected (using setSelected) + /// will output themselves. + virtual void write(Stream &stream, U32 tabStop, U32 flags = 0); + + /// Write the fields of this object in TorqueScript. + /// + /// @param stream Stream for output. + /// @param tabStop Indentation level for the fields. + virtual void writeFields(Stream &stream, U32 tabStop); + + virtual bool writeObject(Stream *stream); + virtual bool readObject(Stream *stream); + virtual void buildFilterList(); + + void addFieldFilter(const char *fieldName); + void removeFieldFilter(const char *fieldName); + void clearFieldFilters(); + bool isFiltered(const char *fieldName); + + /// Copy fields from another object onto this one. + /// + /// Objects must be of same type. Everything from obj + /// will overwrite what's in this object; extra fields + /// in this object will remain. This includes dynamic + /// fields. + /// + /// @param obj Object to copy from. + void assignFieldsFrom(SimObject *obj); + + /// Copy dynamic fields from another object onto this one. + /// + /// Everything from obj will overwrite what's in this + /// object. + /// + /// @param obj Object to copy from. + void assignDynamicFieldsFrom(SimObject *obj); + + /// @} + + /// Return the object's namespace. + Namespace* getNamespace() { return mNameSpace; } + + /// Get next matching item in namespace. + /// + /// This wraps a call to Namespace::tabComplete; it gets the + /// next thing in the namespace, given a starting value + /// and a base length of the string. See + /// Namespace::tabComplete for details. + const char *tabComplete(const char *prevText, S32 baseLen, bool); + + /// @name Accessors + /// @{ + bool isSelected() const { return mFlags.test(Selected); } + bool isExpanded() const { return mFlags.test(Expanded); } + void setSelected(bool sel) { if(sel) mFlags.set(Selected); else mFlags.clear(Selected); } + void setExpanded(bool exp) { if(exp) mFlags.set(Expanded); else mFlags.clear(Expanded); } + void setModDynamicFields(bool dyn) { if(dyn) mFlags.set(ModDynamicFields); else mFlags.clear(ModDynamicFields); } + void setModStaticFields(bool sta) { if(sta) mFlags.set(ModStaticFields); else mFlags.clear(ModStaticFields); } + bool canModDynamicFields() { return mFlags.test(ModDynamicFields); } + bool canModStaticFields() { return mFlags.test(ModStaticFields); } + void setAutoDelete( bool val ) { if( val ) mFlags.set( AutoDelete ); else mFlags.clear( AutoDelete ); } + + /// @} + + + /// Dump the contents of this object to the console. Use the Torque Script dump() and dumpF() functions to + /// call this. + void dumpToConsole(bool includeFunctions=true); + + static void initPersistFields(); + + /// Copy SimObject to another SimObject (Originally designed for T2D). + virtual void copyTo(SimObject* object); + + // Component Console Overrides + virtual bool handlesConsoleMethod(const char * fname, S32 * routingId) { return false; } + virtual void getConsoleMethodData(const char * fname, S32 routingId, S32 * type, S32 * minArgs, S32 * maxArgs, void ** callback, const char ** usage) {} + DECLARE_CONOBJECT(SimObject); +}; + +//--------------------------------------------------------------------------- +/// Smart SimObject pointer. +/// +/// This class keeps track of the book-keeping necessary +/// to keep a registered reference to a SimObject or subclass +/// thereof. +/// +/// Normally, if you want the SimObject to be aware that you +/// have a reference to it, you must call SimObject::registerReference() +/// when you create the reference, and SimObject::unregisterReference() when +/// you're done. If you change the reference, you must also register/unregister +/// it. This is a big headache, so this class exists to automatically +/// keep track of things for you. +/// +/// @code +/// // Assign an object to the +/// SimObjectPtr mOrbitObject = Sim::findObject("anObject"); +/// +/// // Use it as a GameBase*. +/// mOrbitObject->getWorldBox().getCenter(&mPosition); +/// +/// // And reassign it - it will automatically update the references. +/// mOrbitObject = Sim::findObject("anotherObject"); +/// @endcode +template class SimObjectPtr +{ +private: + SimObject *mObj; + +public: + SimObjectPtr() { mObj = 0; } + SimObjectPtr(T* ptr) + { + mObj = ptr; + if(mObj) + mObj->registerReference(&mObj); + } + SimObjectPtr(const SimObjectPtr& rhs) + { + mObj = const_cast(static_cast(rhs)); + if(mObj) + mObj->registerReference(&mObj); + } + SimObjectPtr& operator=(const SimObjectPtr& rhs) + { + if(this == &rhs) + return(*this); + if(mObj) + mObj->unregisterReference(&mObj); + mObj = const_cast(static_cast(rhs)); + if(mObj) + mObj->registerReference(&mObj); + return(*this); + } + ~SimObjectPtr() + { + if(mObj) + mObj->unregisterReference(&mObj); + } + SimObjectPtr& operator= (T *ptr) + { + if(mObj != (SimObject *) ptr) + { + if(mObj) + mObj->unregisterReference(&mObj); + mObj = (SimObject *) ptr; + if (mObj) + mObj->registerReference(&mObj); + } + return *this; + } +#if defined(__MWERKS__) && (__MWERKS__ < 0x2400) + // CW 5.3 seems to get confused comparing SimObjectPtrs... + bool operator == (const SimObject *ptr) { return mObj == ptr; } + bool operator != (const SimObject *ptr) { return mObj != ptr; } +#endif + bool isNull() const { return mObj == 0; } + bool isValid() const { return mObj != 0; } + T* operator->() const { return static_cast(mObj); } + T& operator*() const { return *static_cast(mObj); } + operator T*() const { return static_cast(mObj)? static_cast(mObj) : 0; } + T* getObject() const { return static_cast(mObj); } +}; + +#endif // _SIMOBJECT_H_ diff --git a/console/simObjectList.cpp b/console/simObjectList.cpp new file mode 100644 index 0000000..4c95bfc --- /dev/null +++ b/console/simObjectList.cpp @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simObjectList.h" + +#include "console/console.h" +#include "console/sim.h" +#include "console/simObject.h" + + +String SimObjectList::smSortScriptCallbackFn; + +void SimObjectList::pushBack(SimObject* obj) +{ + if (find(begin(),end(),obj) == end()) + push_back(obj); +} + +void SimObjectList::pushBackForce(SimObject* obj) +{ + iterator itr = find(begin(),end(),obj); + if (itr == end()) + { + push_back(obj); + } + else + { + // Move to the back... + // + SimObject* pBack = *itr; + removeStable(pBack); + push_back(pBack); + } +} + +void SimObjectList::pushFront(SimObject* obj) +{ + if (find(begin(),end(),obj) == end()) + push_front(obj); +} + +void SimObjectList::remove(SimObject* obj) +{ + iterator ptr = find(begin(),end(),obj); + if (ptr != end()) + erase(ptr); +} + +void SimObjectList::removeStable(SimObject* obj) +{ + iterator ptr = find(begin(),end(),obj); + if (ptr != end()) + erase(ptr); +} + +S32 QSORT_CALLBACK SimObjectList::_compareId(const void* a,const void* b) +{ + return (*reinterpret_cast(a))->getId() - + (*reinterpret_cast(b))->getId(); +} + +void SimObjectList::sortId() +{ + dQsort(address(),size(),sizeof(value_type),_compareId); +} + +S32 QSORT_CALLBACK SimObjectList::_callbackSort( const void *a, const void *b ) +{ + const SimObject *objA = *reinterpret_cast( a ); + const SimObject *objB = *reinterpret_cast( b ); + + static char idA[64]; + dSprintf( idA, sizeof( idA ), "%d", objA->getId() ); + static char idB[64]; + dSprintf( idB, sizeof( idB ), "%d", objB->getId() ); + + return dAtoi( Con::executef( smSortScriptCallbackFn, idA, idB ) ); +} + +void SimObjectList::scriptSort( const String &scriptCallback ) +{ + AssertFatal( smSortScriptCallbackFn.isEmpty(), "SimObjectList::scriptSort() - The script sort is not reentrant!" ); + + smSortScriptCallbackFn = scriptCallback; + dQsort( address(), size(), sizeof(value_type), _callbackSort ); + smSortScriptCallbackFn = String::EmptyString; +} diff --git a/console/simObjectList.h b/console/simObjectList.h new file mode 100644 index 0000000..0dec7d1 --- /dev/null +++ b/console/simObjectList.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMOBJECTLIST_H_ +#define _SIMOBJECTLIST_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TALGORITHM_H_ +#include "core/tAlgorithm.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +// Forward Refs +class SimObject; + +/// A vector of SimObjects. +/// +/// As this inherits from VectorPtr, it has the full range of vector methods. +class SimObjectList : public VectorPtr +{ + /// The script callback function for the active sort. + /// @see scriptSort + static String smSortScriptCallbackFn; + + /// The script callback comparision callback. + /// @see scriptSort + static S32 QSORT_CALLBACK _callbackSort(const void* a,const void* b); + + /// The SimObjectId comparision sort callback. + /// @see sortId + static S32 QSORT_CALLBACK _compareId( const void *a, const void *b ); + +public: + + void pushBack(SimObject*); ///< Add the SimObject* to the end of the list, unless it's already in the list. + void pushBackForce(SimObject*); ///< Add the SimObject* to the end of the list, moving it there if it's already present in the list. + void pushFront(SimObject*); ///< Add the SimObject* to the start of the list. + void remove(SimObject*); ///< Remove the SimObject* from the list; may disrupt order of the list. + + SimObject* at(S32 index) const { if(index >= 0 && index < size()) return (*this)[index]; return NULL; } + + /// Remove the SimObject* from the list; guaranteed to preserve list order. + void removeStable(SimObject* pObject); + + /// Performs a simple sort by SimObjectId. + void sortId(); + + /// Performs a sort of the objects in the set using a script + /// callback function to do the comparision. + /// @see SimSet::scriptSort + void scriptSort( const String &scriptCallback ); +}; + +#endif // _SIMOBJECTLIST_H_ diff --git a/console/simObjectMemento.cpp b/console/simObjectMemento.cpp new file mode 100644 index 0000000..7f3b0d2 --- /dev/null +++ b/console/simObjectMemento.cpp @@ -0,0 +1,85 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simObjectMemento.h" + +#include "console/simObject.h" +#include "console/simDatablock.h" +#include "core/stream/memStream.h" + + +SimObjectMemento::SimObjectMemento() + : mState( NULL ), + mIsDatablock( false ) +{ +} + +SimObjectMemento::~SimObjectMemento() +{ + dFree( mState ); +} + +void SimObjectMemento::save( SimObject *object ) +{ + // Cleanup any existing state data. + dFree( mState ); + mObjectName = String::EmptyString; + + // Use a stream to save the state. + MemStream stream( 256 ); + + SimDataBlock * db = dynamic_cast(object); + if( !db ) + stream.write( sizeof( "return " ) - 1, "return " ); + else + mIsDatablock = true; + + object->write( stream, 0, SimObject::NoName ); + stream.write( (UTF8)0 ); + + // Steal the data away from the stream. + mState = (UTF8*)stream.takeBuffer(); + mObjectName = object->getName(); +} + +SimObject *SimObjectMemento::restore() const +{ + // Make sure we have data to restore. + if ( !mState ) + return NULL; + + String uniqueName = Sim::getUniqueName( mObjectName ); + + // TODO: We could potentially make this faster by + // caching the CodeBlock generated from the string + SimObject *object; + if( !mIsDatablock ) + { + const UTF8* result = Con::evaluate( mState ); + if ( !result ) + return NULL; + + U32 objectId = dAtoi( result ); + object = Sim::findObject( objectId ); + } + else + { + Con::evaluate( mState ); + if( mObjectName == String::EmptyString ) + return NULL; + + object = Sim::findObject( mObjectName ); + } + + + + + if ( !object ) + return NULL; + + object->assignName( uniqueName ); + + return object; +} \ No newline at end of file diff --git a/console/simObjectMemento.h b/console/simObjectMemento.h new file mode 100644 index 0000000..2613077 --- /dev/null +++ b/console/simObjectMemento.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#ifndef _CONSOLE_SIMOBJECTMEMENTO_H_ +#define _CONSOLE_SIMOBJECTMEMENTO_H_ + +#ifndef _SIM_H_ +#include "console/sim.h" +#endif + + +/// This simple class is used to store an SimObject and +/// its state so it can be recreated at a later time. +/// +/// The success of restoring the object completely depends +/// on the results from SimObject::write(). +class SimObjectMemento +{ +protected: + + /// The captured object state. + UTF8 *mState; + + /// The captured object's name. + String mObjectName; + bool mIsDatablock; + +public: + + SimObjectMemento(); + virtual ~SimObjectMemento(); + + /// Returns true if we have recorded state. + bool hasState() const { return mState; } + + /// + void save( SimObject *object ); + + /// + SimObject *restore() const; + +}; + + +#endif // _CONSOLE_SIMOBJECTMEMENTO_H_ diff --git a/console/simSerialize.cpp b/console/simSerialize.cpp new file mode 100644 index 0000000..e7c83d5 --- /dev/null +++ b/console/simSerialize.cpp @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/simBase.h" +#include "core/stream/bitStream.h" +#include "core/stream/fileStream.h" + +//----------------------------------------------------------------------------- +// SimObject Methods +//----------------------------------------------------------------------------- + +bool SimObject::writeObject(Stream *stream) +{ + clearFieldFilters(); + buildFilterList(); + + stream->writeString(getName() ? getName() : ""); + + // Static fields + AbstractClassRep *rep = getClassRep(); + AbstractClassRep::FieldList &fieldList = rep->mFieldList; + AbstractClassRep::FieldList::iterator itr; + + U32 savePos = stream->getPosition(); + U32 numFields = fieldList.size(); + stream->write(numFields); + + for(itr = fieldList.begin();itr != fieldList.end();itr++) + { + if(itr->type >= AbstractClassRep::ARCFirstCustomField || isFiltered(itr->pFieldname)) + { + numFields--; + continue; + } + + const char *field = getDataField(itr->pFieldname, NULL); + if(field == NULL) + field = ""; + + stream->writeString(itr->pFieldname); + stream->writeString(field); + } + + // Dynamic Fields + if(mCanSaveFieldDictionary) + { + SimFieldDictionary * fieldDictionary = getFieldDictionary(); + for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + SimFieldDictionary::Entry * entry = (*ditr); + + if(isFiltered(entry->slotName)) + continue; + + stream->writeString(entry->slotName); + stream->writeString(entry->value); + numFields++; + } + } + + // Overwrite the number of fields with the correct value + U32 savePos2 = stream->getPosition(); + stream->setPosition(savePos); + stream->write(numFields); + stream->setPosition(savePos2); + + return true; +} + +bool SimObject::readObject(Stream *stream) +{ + const char *name = stream->readSTString(true); + if(name && *name) + assignName(name); + + U32 numFields; + stream->read(&numFields); + + for(S32 i = 0;i < numFields;i++) + { + const char *fieldName = stream->readSTString(); + const char *data = stream->readSTString(); + + setDataField(fieldName, NULL, data); + } + return true; +} + +//----------------------------------------------------------------------------- + +void SimObject::buildFilterList() +{ + Con::executef(this, "buildFilterList"); +} + +//----------------------------------------------------------------------------- + +void SimObject::addFieldFilter( const char *fieldName ) +{ + StringTableEntry st = StringTable->insert(fieldName); + for(S32 i = 0;i < mFieldFilter.size();i++) + { + if(mFieldFilter[i] == st) + return; + } + + mFieldFilter.push_back(st); +} + +void SimObject::removeFieldFilter( const char *fieldName ) +{ + StringTableEntry st = StringTable->insert(fieldName); + for(S32 i = 0;i < mFieldFilter.size();i++) + { + if(mFieldFilter[i] == st) + { + mFieldFilter.erase(i); + return; + } + } +} + +void SimObject::clearFieldFilters() +{ + mFieldFilter.clear(); +} + +//----------------------------------------------------------------------------- + +bool SimObject::isFiltered( const char *fieldName ) +{ + StringTableEntry st = StringTable->insert(fieldName); + for(S32 i = 0;i < mFieldFilter.size();i++) + { + if(mFieldFilter[i] == st) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// SimSet Methods +//----------------------------------------------------------------------------- + +bool SimSet::writeObject( Stream *stream ) +{ + if(! Parent::writeObject(stream)) + return false; + + stream->write(size()); + for(SimSet::iterator i = begin();i < end();++i) + { + if(! Sim::saveObject((*i), stream)) + return false; + } + return true; +} + +bool SimSet::readObject( Stream *stream ) +{ + if(! Parent::readObject(stream)) + return false; + + U32 numObj; + stream->read(&numObj); + + for(U32 i = 0;i < numObj;i++) + { + SimObject *obj = Sim::loadObjectStream(stream); + if(obj == NULL) + return false; + + addObject(obj); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Sim Functions +//----------------------------------------------------------------------------- + +namespace Sim +{ + +bool saveObject(SimObject *obj, const char *filename) +{ + FileStream *stream; + if((stream = FileStream::createAndOpen( filename, Torque::FS::File::Write )) == NULL) + return false; + + bool ret = saveObject(obj, stream); + delete stream; + + return ret; +} + +bool saveObject(SimObject *obj, Stream *stream) +{ + stream->writeString(obj->getClassName()); + return obj->writeObject(stream); +} + +//----------------------------------------------------------------------------- + +SimObject *loadObjectStream(const char *filename) +{ + FileStream * stream; + if((stream = FileStream::createAndOpen( filename, Torque::FS::File::Read )) == NULL) + return NULL; + + SimObject * ret = loadObjectStream(stream); + delete stream; + return ret; +} + +SimObject *loadObjectStream(Stream *stream) +{ + const char *className = stream->readSTString(true); + ConsoleObject *conObj = ConsoleObject::create(className); + if(conObj == NULL) + { + Con::errorf("Sim::restoreObjectStream - Could not create object of class \"%s\"", className); + return NULL; + } + + SimObject *simObj = dynamic_cast(conObj); + if(simObj == NULL) + { + Con::errorf("Sim::restoreObjectStream - Object of class \"%s\" is not a SimObject", className); + delete simObj; + return NULL; + } + + if( simObj->readObject(stream) + && simObj->registerObject() ) + return simObj; + + delete simObj; + return NULL; +} + +} // end namespace Sim + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(SimObject, addFieldFilter, void, 3, 3, "(fieldName)") +{ + object->addFieldFilter(argv[2]); +} + +ConsoleMethod(SimObject, removeFieldFilter, void, 3, 3, "(fieldName)") +{ + object->removeFieldFilter(argv[2]); +} + + +//----------------------------------------------------------------------------- +// Console Functions +//----------------------------------------------------------------------------- + +ConsoleFunction(saveObject, bool, 3, 3, "(object, filename)") +{ + SimObject *obj = dynamic_cast(Sim::findObject(argv[1])); + if(obj == NULL) + return false; + + return Sim::saveObject(obj, argv[2]); +} + +ConsoleFunction(loadObject, S32, 2, 2, "(filename)") +{ + SimObject *obj = Sim::loadObjectStream(argv[1]); + return obj ? obj->getId() : 0; +} diff --git a/console/simSet.cpp b/console/simSet.cpp new file mode 100644 index 0000000..54d52b6 --- /dev/null +++ b/console/simSet.cpp @@ -0,0 +1,761 @@ +//------------------------------------------------------------------------------ +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//------------------------------------------------------------------------------ + +#include "platform/platform.h" +#include "console/simSet.h" + +#include "core/stringTable.h" +#include "console/console.h" +#include "core/stream/fileStream.h" +#include "sim/actionMap.h" +#include "core/fileObject.h" +#include "console/consoleInternal.h" +#include "platform/profiler.h" +#include "console/typeValidators.h" +#include "core/frameAllocator.h" +#include "math/mMathFn.h" + +//----------------------------------------------------------------------------- +// Sim Set +//----------------------------------------------------------------------------- + +void SimSet::addObject(SimObject* obj) +{ + lock(); + U32 size = objectList.size(); + objectList.pushBack(obj); + if( objectList.size() > size ) + { + deleteNotify(obj); + } + unlock(); +} + +void SimSet::removeObject(SimObject* obj) +{ + lock(); + objectList.remove(obj); + clearNotify(obj); + unlock(); +} + +void SimSet::pushObject(SimObject* pObj) +{ + lock(); + U32 size = objectList.size(); + objectList.pushBackForce(pObj); + if( objectList.size() > size ) + { + deleteNotify(pObj); + } + unlock(); +} + +void SimSet::popObject() +{ + MutexHandle handle; + handle.lock(mMutex); + + if (objectList.size() == 0) + { + AssertWarn(false, "Stack underflow in SimSet::popObject"); + return; + } + + SimObject* pObject = objectList[objectList.size() - 1]; + + objectList.removeStable(pObject); + clearNotify(pObject); +} + +void SimSet::scriptSort( const String &scriptCallbackFn ) +{ + lock(); + objectList.scriptSort( scriptCallbackFn ); + unlock(); +} + +void SimSet::callOnChildren( const String &method, S32 argc, const char *argv[], bool executeOnChildGroups ) +{ + // Prep the arguments for the console exec... + // Make sure and leave args[1] empty. + const char* args[21]; + args[0] = method.c_str(); + for (S32 i = 0; i < argc; i++) + args[i + 2] = argv[i]; + + for ( iterator i = begin(); i != end(); i++ ) + { + SimObject *childObj = static_cast(*i); + + if ( childObj->isMethod( method.c_str() ) ) + Con::execute(childObj, argc + 2, args); + + if ( executeOnChildGroups ) + { + SimSet* childSet = dynamic_cast(*i); + if ( childSet ) + childSet->callOnChildren( method, argc, argv, executeOnChildGroups ); + } + } +} + +U32 SimSet::fullSize() +{ + U32 count = 0; + + for ( iterator i = begin(); i != end(); i++ ) + { + count++; + + SimSet* childSet = dynamic_cast(*i); + if ( childSet ) + count += childSet->fullSize(); + } + + return count; +} + +bool SimSet::reOrder( SimObject *obj, SimObject *target ) +{ + MutexHandle handle; + handle.lock(mMutex); + + iterator itrS, itrD; + if ( (itrS = find(begin(),end(),obj)) == end() ) + { + // object must be in list + return false; + } + + if ( obj == target ) + { + // don't reorder same object but don't indicate error + return true; + } + + if ( !target ) + { + // if no target, then put to back of list + + // don't move if already last object + if ( itrS != (end()-1) ) + { + // remove object from its current location and push to back of list + objectList.erase(itrS); + objectList.push_back(obj); + } + } + else + { + // if target, insert object in front of target + if ( (itrD = find(begin(),end(),target)) == end() ) + // target must be in list + return false; + + objectList.erase(itrS); + + // once itrS has been erased, itrD won't be pointing at the + // same place anymore - re-find... + itrD = find(begin(),end(),target); + objectList.insert(itrD, obj); + } + + return true; +} + +void SimSet::onDeleteNotify(SimObject *object) +{ + removeObject(object); + Parent::onDeleteNotify(object); +} + +void SimSet::onRemove() +{ + MutexHandle handle; + handle.lock(mMutex); + + objectList.sortId(); + if (objectList.size()) + { + // This backwards iterator loop doesn't work if the + // list is empty, check the size first. + for (SimObjectList::iterator ptr = objectList.end() - 1; + ptr >= objectList.begin(); ptr--) + { + clearNotify(*ptr); + } + } + + handle.unlock(); + + Parent::onRemove(); +} + +void SimSet::write(Stream &stream, U32 tabStop, U32 flags) +{ + MutexHandle handle; + handle.lock(mMutex); + + // export selected only? + if((flags & SelectedOnly) && !isSelected()) + { + for(U32 i = 0; i < size(); i++) + (*this)[i]->write(stream, tabStop, flags); + + return; + + } + + stream.writeTabs(tabStop); + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s(%s) {\r\n", getClassName(), getName() ? getName() : ""); + stream.write(dStrlen(buffer), buffer); + writeFields(stream, tabStop + 1); + + if(size()) + { + stream.write(2, "\r\n"); + for(U32 i = 0; i < size(); i++) + (*this)[i]->write(stream, tabStop + 1, flags); + } + + stream.writeTabs(tabStop); + stream.write(4, "};\r\n"); +} + +void SimSet::clear() +{ + lock(); + while (size() > 0) + removeObject(objectList.last()); + unlock(); +} + +void SimSet::deleteAllObjects() +{ + lock(); + while (size() > 0) + { + SimObject *obj = objectList.last(); + removeObject( obj ); + obj->deleteObject(); + } + unlock(); +} + +SimObject* SimSet::findObjectByInternalName(StringTableEntry internalName, bool searchChildren) +{ + iterator i; + for (i = begin(); i != end(); i++) + { + SimObject *childObj = static_cast(*i); + if(childObj->getInternalName() == internalName) + return childObj; + else if (searchChildren) + { + SimSet* childSet = dynamic_cast(*i); + if (childSet) + { + SimObject* found = childSet->findObjectByInternalName(internalName, searchChildren); + if (found) return found; + } + } + } + + return NULL; +} + +SimObject* SimSet::findObjectByLineNumber(const char* fileName, S32 declarationLine, bool searchChildren) +{ + if (!fileName) + return NULL; + + if (declarationLine < 0) + return NULL; + + StringTableEntry fileEntry = StringTable->insert(fileName); + + for (iterator i = begin(); i != end(); i++) + { + SimObject *childObj = static_cast(*i); + + if(childObj->getFilename() == fileEntry && childObj->getDeclarationLine() == declarationLine) + return childObj; + else if (searchChildren) + { + SimSet* childSet = dynamic_cast(*i); + + if (childSet) + { + SimObject* found = childSet->findObjectByLineNumber(fileName, declarationLine, searchChildren); + if (found) + return found; + } + } + } + + return NULL; +} + +SimObject* SimSet::getRandom() +{ + if (size() > 0) + return objectList[mRandI(0, size() - 1)]; + + return NULL; +} + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(SimSet); + +//----------------------------------------------------------------------------- +// SimGroup +//----------------------------------------------------------------------------- + +SimGroup::~SimGroup() +{ + lock(); + for (iterator itr = begin(); itr != end(); itr++) + nameDictionary.remove(*itr); + + // XXX Move this later into Group Class + // If we have any objects at this point, they should + // already have been removed from the manager, so we + // can just delete them directly. + objectList.sortId(); + while (!objectList.empty()) + { + delete objectList.last(); + objectList.decrement(); + } + + unlock(); +} + + +//----------------------------------------------------------------------------- + +void SimGroup::addObject(SimObject* obj) +{ + lock(); + + // Make sure we aren't adding ourself. This isn't the most robust check + // but it should be good enough to prevent some self-foot-shooting. + if(obj == this) + { + Con::errorf("SimGroup::addObject - (%d) can't add self!", getIdString()); + unlock(); + return; + } + + if (obj->mGroup != this) + { + if (obj->mGroup) + obj->mGroup->removeObject(obj); + nameDictionary.insert(obj); + obj->mGroup = this; + objectList.push_back(obj); // force it into the object list + // doesn't get a delete notify + obj->onGroupAdd(); + } + unlock(); +} + +void SimGroup::removeObject(SimObject* obj) +{ + lock(); + removeObjectNoLock( obj ); + unlock(); +} + +void SimGroup::removeObjectNoLock(SimObject* obj) +{ + if (obj->mGroup == this) + { + obj->onGroupRemove(); + nameDictionary.remove(obj); + objectList.remove(obj); + obj->mGroup = 0; + } +} + +//----------------------------------------------------------------------------- + +void SimGroup::onRemove() +{ + lock(); + objectList.sortId(); + if (objectList.size()) + { + // This backwards iterator loop doesn't work if the + // list is empty, check the size first. + for (SimObjectList::iterator ptr = objectList.end() - 1; + ptr >= objectList.begin(); ptr--) + { + (*ptr)->onGroupRemove(); + (*ptr)->mGroup = NULL; + (*ptr)->unregisterObject(); + (*ptr)->mGroup = this; + } + } + SimObject::onRemove(); + unlock(); +} + +//----------------------------------------------------------------------------- + +void SimGroup::clear() +{ + lock(); + while( size() > 0 ) + { + SimObject* object = objectList.last(); + removeObjectNoLock( object ); + object->deleteObject(); + } + unlock(); +} + +//----------------------------------------------------------------------------- + +SimObject *SimGroup::findObject(const char *namePath) +{ + // find the end of the object name + S32 len; + for(len = 0; namePath[len] != 0 && namePath[len] != '/'; len++) + ; + + StringTableEntry stName = StringTable->lookupn(namePath, len); + if(!stName) + return NULL; + + SimObject *root = nameDictionary.find(stName); + + if(!root) + return NULL; + + if(namePath[len] == 0) + return root; + + return root->findObject(namePath + len + 1); +} + +SimObject *SimSet::findObject(const char *namePath) +{ + // find the end of the object name + S32 len; + for(len = 0; namePath[len] != 0 && namePath[len] != '/'; len++) + ; + + StringTableEntry stName = StringTable->lookupn(namePath, len); + if(!stName) + return NULL; + + lock(); + for(SimSet::iterator i = begin(); i != end(); i++) + { + if((*i)->getName() == stName) + { + unlock(); + if(namePath[len] == 0) + return *i; + return (*i)->findObject(namePath + len + 1); + } + } + unlock(); + return NULL; +} + +SimObject* SimObject::findObject(const char* ) +{ + return NULL; +} + +//----------------------------------------------------------------------------- + +bool SimGroup::processArguments(S32, const char **) +{ + return true; +} + +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(SimGroup); + +//----------------------------------------------------------------------------- +// Iterators +//----------------------------------------------------------------------------- + +inline void SimSetIterator::Stack::push_back(SimSet* set) +{ + increment(); + last().set = set; + last().itr = set->begin(); +} + + +//----------------------------------------------------------------------------- + +SimSetIterator::SimSetIterator(SimSet* set) +{ + VECTOR_SET_ASSOCIATION(stack); + + if (!set->empty()) + stack.push_back(set); +} + + +//----------------------------------------------------------------------------- + +SimObject* SimSetIterator::operator++() +{ + SimSet* set; + if ((set = dynamic_cast(*stack.last().itr)) != 0) + { + if (!set->empty()) + { + stack.push_back(set); + return *stack.last().itr; + } + } + + while (++stack.last().itr == stack.last().set->end()) + { + stack.pop_back(); + if (stack.empty()) + return 0; + } + return *stack.last().itr; +} + +SimObject* SimGroupIterator::operator++() +{ + SimGroup* set; + if ((set = dynamic_cast(*stack.last().itr)) != 0) + { + if (!set->empty()) + { + stack.push_back(set); + return *stack.last().itr; + } + } + + while (++stack.last().itr == stack.last().set->end()) + { + stack.pop_back(); + if (stack.empty()) + return 0; + } + return *stack.last().itr; +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(SimSet, listObjects, void, 2, 2, "set.listObjects();") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + + object->lock(); + SimSet::iterator itr; + for(itr = object->begin(); itr != object->end(); itr++) + { + SimObject *obj = *itr; + bool isSet = dynamic_cast(obj) != 0; + const char *name = obj->getName(); + if(name) + Con::printf(" %d,\"%s\": %s %s", obj->getId(), name, + obj->getClassName(), isSet ? "(g)":""); + else + Con::printf(" %d: %s %s", obj->getId(), obj->getClassName(), + isSet ? "(g)" : ""); + } + object->unlock(); +} + +ConsoleMethod(SimSet, add, void, 3, 0, "set.add(obj1,...)") +{ + for(S32 i = 2; i < argc; i++) + { + SimObject *obj = Sim::findObject(argv[i]); + if(obj) + object->addObject(obj); + else + Con::printf("Set::add: Object \"%s\" doesn't exist", argv[i]); + } +} + +ConsoleMethod(SimSet, remove, void, 3, 0, "set.remove(obj1,...)") +{ + for(S32 i = 2; i < argc; i++) + { + SimObject *obj = Sim::findObject(argv[i]); + object->lock(); + if(obj && object->find(object->begin(),object->end(),obj) != object->end()) + object->removeObject(obj); + else + Con::printf("Set::remove: Object \"%s\" does not exist in set", argv[i]); + object->unlock(); + } +} + +ConsoleMethod(SimSet, clear, void, 2, 2, "set.clear()") +{ + object->clear(); +} + +ConsoleMethod(SimSet, deleteAllObjects, void, 2, 2, + "Deletes all the objects in the set." ) +{ + object->deleteAllObjects(); +} + +ConsoleMethod(SimSet, getRandom, S32, 2, 2, + "Deletes all the objects in the set." ) +{ + SimObject* ret = object->getRandom(); + + if (ret) + return ret->getId(); + + return -1; +} + +ConsoleMethod(SimSet, callOnChildren, void, 3, 0, "callOnChildren(funcName [,args ...])") +{ + object->callOnChildren( argv[2], argc - 3, argv + 3 ); +} + +//------------------------------------------------------------------------------ +// Make Sure Child 1 is Ordered Just Under Child 2. +//------------------------------------------------------------------------------ +ConsoleMethod(SimSet, reorderChild, void, 4,4," (child1, child2) uses simset reorder to push child 1 before child 2 - both must already be child controls of this control") +{ + SimObject* pObject = Sim::findObject(argv[2]); + SimObject* pTarget = Sim::findObject(argv[3]); + + if(pObject && pTarget) + { + object->reOrder(pObject,pTarget); + } +} + +ConsoleMethod(SimSet, getCount, S32, 2, 2, "set.getCount() returns the number of children that directly belong to this set (doesn't include the childrens' child objects") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return object->size(); +} + +ConsoleMethod(SimSet, getFullCount, S32, 2, 2, "set.getFullCount() returns the full recursive count of the children of this group") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return object->fullSize(); +} + +ConsoleMethod(SimSet, getObject, S32, 3, 3, "set.getObject(objIndex)") +{ + TORQUE_UNUSED(argc); + S32 objectIndex = dAtoi(argv[2]); + if(objectIndex < 0 || objectIndex >= S32(object->size())) + { + Con::printf("Set::getObject index out of range."); + return -1; + } + return ((*object)[objectIndex])->getId(); +} + +ConsoleMethod(SimSet, getObjectIndex, S32, 3, 3, "set.getObjectIndex(object)") +{ + TORQUE_UNUSED(argc); + SimObject *testObject = Sim::findObject(argv[2]); + if(!testObject) + { + Con::printf("SimSet::getObjectIndex: %s is not an object.", argv[2]); + return false; + } + + object->lock(); + S32 count = 0; + for(SimSet::iterator i = object->begin(); i != object->end(); i++) + { + if(*i == testObject) + { + object->unlock(); + return count; + } + + ++count; + } + object->unlock(); + + return -1; +} + +ConsoleMethod(SimSet, isMember, bool, 3, 3, "set.isMember(object)") +{ + TORQUE_UNUSED(argc); + SimObject *testObject = Sim::findObject(argv[2]); + if(!testObject) + { + Con::printf("SimSet::isMember: %s is not an object.", argv[2]); + return false; + } + + object->lock(); + for(SimSet::iterator i = object->begin(); i != object->end(); i++) + { + if(*i == testObject) + { + object->unlock(); + return true; + } + } + object->unlock(); + + return false; +} + +ConsoleMethod( SimSet, findObjectByInternalName, S32, 3, 4, "string InternalName [, bool searchChildren]") +{ + + StringTableEntry pcName = StringTable->insert(argv[2]); + bool searchChildren = false; + if (argc > 3) + searchChildren = dAtob(argv[3]); + + SimObject* child = object->findObjectByInternalName(pcName, searchChildren); + if(child) + return child->getId(); + return 0; +} + +ConsoleMethod(SimSet, bringToFront, void, 3, 3, "set.bringToFront(object)") +{ + TORQUE_UNUSED(argc); + SimObject *obj = Sim::findObject(argv[2]); + if(!obj) + return; + object->bringObjectToFront(obj); +} + +ConsoleMethod(SimSet, pushToBack, void, 3, 3, "set.pushToBack(object)") +{ + TORQUE_UNUSED(argc); + SimObject *obj = Sim::findObject(argv[2]); + if(!obj) + return; + object->pushObjectToBack(obj); +} + +ConsoleMethod(SimSet, sort, void, 3, 3, "set.sort( callbackFunction )\n" + "Performs a sort of the objects in the set using a script " + "callback function to do the comparision." ) +{ + object->scriptSort( argv[2] ); +} + diff --git a/console/simSet.h b/console/simSet.h new file mode 100644 index 0000000..06a4bf1 --- /dev/null +++ b/console/simSet.h @@ -0,0 +1,291 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/sim.h" +#include "console/simObjectList.h" +#include "console/simObject.h" +#include "console/simDictionary.h" + +#ifndef _SIMSET_H_ +#define _SIMSET_H_ + +//--------------------------------------------------------------------------- +/// A set of SimObjects. +/// +/// It is often necessary to keep track of an arbitrary set of SimObjects. +/// For instance, Torque's networking code needs to not only keep track of +/// the set of objects which need to be ghosted, but also the set of objects +/// which must always be ghosted. It does this by working with two +/// sets. The first of these is the RootGroup (which is actually a SimGroup) +/// and the second is the GhostAlwaysSet, which contains objects which must +/// always be ghosted to the client. +/// +/// Some general notes on SimSets: +/// - Membership is not exclusive. A SimObject may be a member of multiple +/// SimSets. +/// - A SimSet does not destroy subobjects when it is destroyed. +/// - A SimSet may hold an arbitrary number of objects. +/// +/// Using SimSets, the code to work with these two sets becomes +/// relatively straightforward: +/// +/// @code +/// // (Example from netObject.cc) +/// // To iterate over all the objects in the Sim: +/// for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj) +/// { +/// NetObject* nobj = dynamic_cast(*obj); +/// +/// if (nobj) +/// { +/// // ... do things ... +/// } +/// } +/// +/// // (Example from netGhost.cc) +/// // To iterate over the ghostAlways set. +/// SimSet* ghostAlwaysSet = Sim::getGhostAlwaysSet(); +/// SimSet::iterator i; +/// +/// U32 sz = ghostAlwaysSet->size(); +/// S32 j; +/// +/// for(i = ghostAlwaysSet->begin(); i != ghostAlwaysSet->end(); i++) +/// { +/// NetObject *obj = (NetObject *)(*i); +/// +/// /// ... do things with obj... +/// } +/// @endcode +/// + +class SimSet: public SimObject +{ + typedef SimObject Parent; +protected: + SimObjectList objectList; + void *mMutex; + +public: + SimSet() { + VECTOR_SET_ASSOCIATION(objectList); + + mMutex = Mutex::createMutex(); + } + + ~SimSet() + { + lock(); + unlock(); + Mutex::destroyMutex(mMutex); + mMutex = NULL; + } + + /// @name STL Interface + /// @{ + + /// + typedef SimObjectList::iterator iterator; + typedef SimObjectList::value_type value; + SimObject* front() { return objectList.front(); } + SimObject* first() { return objectList.first(); } + SimObject* last() { return objectList.last(); } + bool empty() { return objectList.empty(); } + S32 size() const { return objectList.size(); } + iterator begin() { return objectList.begin(); } + iterator end() { return objectList.end(); } + value operator[] (S32 index) { return objectList[U32(index)]; } + + iterator find( iterator first, iterator last, SimObject *obj) + { return ::find(first, last, obj); } + + virtual bool reOrder( SimObject *obj, SimObject *target=0 ); + SimObject* at(S32 index) const { return objectList.at(index); } + + virtual void clear(); + /// @} + + /// Deletes all the objects in the set. + void deleteAllObjects(); + + virtual void onRemove(); + virtual void onDeleteNotify(SimObject *object); + + /// @name Set Management + /// @{ + + virtual void addObject(SimObject*); ///< Add an object to the set. + virtual void removeObject(SimObject*); ///< Remove an object from the set. + + virtual void pushObject(SimObject*); ///< Add object to end of list. + /// + /// It will force the object to the end of the list if it already exists + /// in the list. + + virtual void popObject(); ///< Remove an object from the end of the list. + + void bringObjectToFront(SimObject* obj) { reOrder(obj, front()); } + void pushObjectToBack(SimObject* obj) { reOrder(obj, NULL); } + + /// Performs a sort of the objects in the set using a script + /// callback function to do the comparision. + /// + /// An example script sort callback: + /// + /// @code + /// function sortByName( %object1, %object2 ) + /// { + /// return strcmp( %object1.getName(), %object2.getName() ); + /// } + /// @endcode + /// + /// Note: You should never modify the SimSet itself while in + /// the sort callback function as it can cause a deadlock. + /// + void scriptSort( const String &scriptCallbackFn ); + + /// @} + + void callOnChildren( const String &method, S32 argc, const char *argv[], bool executeOnChildGroups = true ); + + U32 fullSize(); + + virtual void write(Stream &stream, U32 tabStop, U32 flags = 0); + + virtual SimObject *findObject(const char *name); + SimObject* findObjectByInternalName(StringTableEntry internalName, bool searchChildren = false); + SimObject* findObjectByLineNumber(const char* fileName, S32 declarationLine, bool searchChildren = false); + + SimObject* getRandom(); + + virtual bool writeObject(Stream *stream); + virtual bool readObject(Stream *stream); + + inline void lock() + { +#ifdef TORQUE_MULTITHREAD + Mutex::lockMutex(mMutex); +#endif + } + + inline void unlock() + { +#ifdef TORQUE_MULTITHREAD + Mutex::unlockMutex(mMutex); +#endif + } + + DECLARE_CONOBJECT(SimSet); + +#ifdef TORQUE_DEBUG_GUARD + inline void _setVectorAssoc( const char *file, const U32 line ) + { + objectList.setFileAssociation( file, line ); + } +#endif +}; +#ifdef TORQUE_DEBUG_GUARD +# define SIMSET_SET_ASSOCIATION( x ) x._setVectorAssoc( __FILE__, __LINE__ ) +#else +# define SIMSET_SET_ASSOCIATION( x ) +#endif + +/// Iterator for use with SimSets +/// +/// @see SimSet +class SimSetIterator +{ +protected: + struct Entry { + SimSet* set; + SimSet::iterator itr; + }; + class Stack: public Vector { + public: + void push_back(SimSet*); + }; + Stack stack; + +public: + SimSetIterator(SimSet*); + SimObject* operator++(); + SimObject* operator*() { + return stack.empty()? 0: *stack.last().itr; + } +}; + +//--------------------------------------------------------------------------- +/// A group of SimObjects. +/// +/// A SimGroup is a stricter form of SimSet. SimObjects may only be a member +/// of a single SimGroup at a time. +/// +/// The SimGroup will automatically enforce the single-group-membership rule. +/// +/// @code +/// // From engine/sim/simPath.cc - getting a pointer to a SimGroup +/// SimGroup* pMissionGroup = dynamic_cast(Sim::findObject("MissionGroup")); +/// +/// // From game/trigger.cc:46 - iterating over a SimObject's group. +/// SimObject* trigger = ...; +/// SimGroup* pGroup = trigger->getGroup(); +/// for (SimGroup::iterator itr = pGroup->begin(); itr != pGroup->end(); itr++) +/// { +/// // do something with *itr +/// } +/// @endcode +class SimGroup: public SimSet +{ +private: + friend class SimManager; + friend class SimObject; + + typedef SimSet Parent; + SimNameDictionary nameDictionary; + + void removeObjectNoLock(SimObject*); + +public: + ~SimGroup(); + + /// Add an object to the group. + virtual void addObject(SimObject*); + void addObject(SimObject*, SimObjectId); + void addObject(SimObject*, const char *name); + + /// Remove an object from the group. + virtual void removeObject(SimObject*); + + virtual void onRemove(); + virtual void clear(); + + /// Find an object in the group. + virtual SimObject* findObject(const char* name); + + bool processArguments(S32 argc, const char **argv); + + DECLARE_CONOBJECT(SimGroup); +}; + +inline void SimGroup::addObject(SimObject* obj, SimObjectId id) +{ + obj->mId = id; + addObject( obj ); +} + +inline void SimGroup::addObject(SimObject *obj, const char *name) +{ + addObject( obj ); + obj->assignName(name); +} + +class SimGroupIterator: public SimSetIterator +{ +public: + SimGroupIterator(SimGroup* grp): SimSetIterator(grp) {} + SimObject* operator++(); +}; + +#endif // _SIMSET_H_ diff --git a/console/stringStack.cpp b/console/stringStack.cpp new file mode 100644 index 0000000..fcd12d6 --- /dev/null +++ b/console/stringStack.cpp @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/stringStack.h" + +void StringStack::getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv, bool popStackFrame /* = false */) +{ + U32 startStack = mFrameOffsets[mNumFrames-1] + 1; + U32 argCount = getMin(mStartStackSize - startStack, (U32)MaxArgs); + + *in_argv = mArgV; + mArgV[0] = name; + + for(U32 i = 0; i < argCount; i++) + mArgV[i+1] = mBuffer + mStartOffsets[startStack + i]; + argCount++; + + *argc = argCount; + + if(popStackFrame) + popFrame(); +} \ No newline at end of file diff --git a/console/stringStack.h b/console/stringStack.h new file mode 100644 index 0000000..c6120c9 --- /dev/null +++ b/console/stringStack.h @@ -0,0 +1,271 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STRINGSTACK_H_ +#define _STRINGSTACK_H_ + +#ifndef _STRINGFUNCTIONS_H_ +#include "core/strings/stringFunctions.h" +#endif + +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif + +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif + + +/// Core stack for interpreter operations. +/// +/// This class provides some powerful semantics for working with strings, and is +/// used heavily by the console interpreter. +struct StringStack +{ + enum { + MaxStackDepth = 1024, + MaxArgs = 20, + ReturnBufferSpace = 512 + }; + char *mBuffer; + U32 mBufferSize; + const char *mArgV[MaxArgs]; + U32 mFrameOffsets[MaxStackDepth]; + U32 mStartOffsets[MaxStackDepth]; + + U32 mNumFrames; + U32 mArgc; + + U32 mStart; + U32 mLen; + U32 mStartStackSize; + U32 mFunctionOffset; + + U32 mArgBufferSize; + char *mArgBuffer; + + void validateBufferSize(U32 size) + { + if(size > mBufferSize) + { + mBufferSize = size + 2048; + mBuffer = (char *) dRealloc(mBuffer, mBufferSize); + } + } + void validateArgBufferSize(U32 size) + { + if(size > mArgBufferSize) + { + mArgBufferSize = size + 2048; + mArgBuffer = (char *) dRealloc(mArgBuffer, mArgBufferSize); + } + } + StringStack() + { + mBufferSize = 0; + mBuffer = NULL; + mArgBufferSize = 0; + mArgBuffer = NULL; + mNumFrames = 0; + mStart = 0; + mLen = 0; + mStartStackSize = 0; + mFunctionOffset = 0; + validateBufferSize(8192); + validateArgBufferSize(2048); + } + ~StringStack() + { + if( mBuffer ) + dFree( mBuffer ); + if( mArgBuffer ) + dFree( mArgBuffer ); + } + + /// Set the top of the stack to be an integer value. + void setIntValue(U32 i) + { + validateBufferSize(mStart + 32); + dSprintf(mBuffer + mStart, 32, "%d", i); + mLen = dStrlen(mBuffer + mStart); + } + + /// Set the top of the stack to be a float value. + void setFloatValue(F64 v) + { + validateBufferSize(mStart + 32); + dSprintf(mBuffer + mStart, 32, "%g", v); + mLen = dStrlen(mBuffer + mStart); + } + + /// Return a temporary buffer we can use to return data. + /// + /// @note This clobbers anything in our buffers! + char *getReturnBuffer(U32 size) + { + if(size > ReturnBufferSpace) + { + validateArgBufferSize(size); + return mArgBuffer; + } + else + { + validateBufferSize(mStart + size); + return mBuffer + mStart; + } + } + + /// Return a buffer we can use for arguments. + /// + /// This updates the function offset. + char *getArgBuffer(U32 size) + { + validateBufferSize(mStart + mFunctionOffset + size); + char *ret = mBuffer + mStart + mFunctionOffset; + mFunctionOffset += size; + return ret; + } + + /// Clear the function offset. + void clearFunctionOffset() + { + mFunctionOffset = 0; + } + + /// Set a string value on the top of the stack. + void setStringValue(const char *s) + { + if(!s) + { + mLen = 0; + mBuffer[mStart] = 0; + return; + } + mLen = dStrlen(s); + + validateBufferSize(mStart + mLen + 2); + dStrcpy(mBuffer + mStart, s); + } + + /// Get the top of the stack, as a StringTableEntry. + /// + /// @note Don't free this memory! + inline StringTableEntry getSTValue() + { + return StringTable->insert(mBuffer + mStart); + } + + /// Get an integer representation of the top of the stack. + inline U32 getIntValue() + { + return dAtoi(mBuffer + mStart); + } + + /// Get a float representation of the top of the stack. + inline F64 getFloatValue() + { + return dAtof(mBuffer + mStart); + } + + /// Get a string representation of the top of the stack. + /// + /// @note This returns a pointer to the actual top of the stack, be careful! + inline const char *getStringValue() + { + return mBuffer + mStart; + } + + /// Advance the start stack, placing a zero length string on the top. + /// + /// @note You should use StringStack::push, not this, if you want to + /// properly push the stack. + void advance() + { + mStartOffsets[mStartStackSize++] = mStart; + mStart += mLen; + mLen = 0; + } + + /// Advance the start stack, placing a single character, null-terminated strong + /// on the top. + /// + /// @note You should use StringStack::push, not this, if you want to + /// properly push the stack. + void advanceChar(char c) + { + mStartOffsets[mStartStackSize++] = mStart; + mStart += mLen; + mBuffer[mStart] = c; + mBuffer[mStart+1] = 0; + mStart += 1; + mLen = 0; + } + + /// Push the stack, placing a zero-length string on the top. + void push() + { + advanceChar(0); + } + + inline void setLen(U32 newlen) + { + mLen = newlen; + } + + /// Pop the start stack. + void rewind() + { + mStart = mStartOffsets[--mStartStackSize]; + mLen = dStrlen(mBuffer + mStart); + } + + // Terminate the current string, and pop the start stack. + void rewindTerminate() + { + mBuffer[mStart] = 0; + mStart = mStartOffsets[--mStartStackSize]; + mLen = dStrlen(mBuffer + mStart); + } + + /// Compare 1st and 2nd items on stack, consuming them in the process, + /// and returning true if they matched, false if they didn't. + U32 compare() + { + // Figure out the 1st and 2nd item offsets. + U32 oldStart = mStart; + mStart = mStartOffsets[--mStartStackSize]; + + // Compare current and previous strings. + U32 ret = !dStricmp(mBuffer + mStart, mBuffer + oldStart); + + // Put an empty string on the top of the stack. + mLen = 0; + mBuffer[mStart] = 0; + + return ret; + } + + + void pushFrame() + { + mFrameOffsets[mNumFrames++] = mStartStackSize; + mStartOffsets[mStartStackSize++] = mStart; + mStart += ReturnBufferSpace; + validateBufferSize(0); + } + + void popFrame() + { + mStartStackSize = mFrameOffsets[--mNumFrames]; + mStart = mStartOffsets[mStartStackSize]; + mLen = 0; + } + + /// Get the arguments for a function call from the stack. + void getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv, bool popStackFrame = false); +}; + +#endif diff --git a/console/telnetConsole.cpp b/console/telnetConsole.cpp new file mode 100644 index 0000000..a3e3c07 --- /dev/null +++ b/console/telnetConsole.cpp @@ -0,0 +1,292 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/telnetConsole.h" + +#include "core/strings/stringFunctions.h" +#include "platform/event.h" +#include "core/util/journal/process.h" + + +TelnetConsole *TelConsole = NULL; + +void TelnetConsole::create() +{ + TelConsole = new TelnetConsole; + Process::notify(TelConsole, &TelnetConsole::process, PROCESS_FIRST_ORDER); +} + +void TelnetConsole::destroy() +{ + Process::remove(TelConsole, &TelnetConsole::process); + delete TelConsole; + TelConsole = NULL; +} + +ConsoleFunction( telnetSetParameters, void, 4, 5, "(int port, string consolePass, string listenPass)" + "Initialize and open the telnet console.\n\n" + "@param port Port to listen on for console connections (0 will shut down listening).\n" + "@param consolePass Password for read/write access to console.\n" + "@param listenPass Password for read access to console." + "@param remoteEcho [optional] Enable echoing back to the client, off by default.") +{ + if (TelConsole) + { + bool remoteEcho = false; + if( argc == 5 ) + remoteEcho = dAtob( argv[4] ); + TelConsole->setTelnetParameters(dAtoi(argv[1]), argv[2], argv[3], remoteEcho); + } +} + +static void telnetCallback(ConsoleLogEntry::Level level, const char *consoleLine) +{ + TORQUE_UNUSED(level); + if (TelConsole) + TelConsole->processConsoleLine(consoleLine); +} + +TelnetConsole::TelnetConsole() +{ + Con::addConsumer(telnetCallback); + + mAcceptSocket = InvalidSocket; + mAcceptPort = -1; + mClientList = NULL; + mRemoteEchoEnabled = false; +} + +TelnetConsole::~TelnetConsole() +{ + Con::removeConsumer(telnetCallback); + if(mAcceptSocket != InvalidSocket) + Net::closeSocket(mAcceptSocket); + TelnetClient *walk = mClientList, *temp; + while(walk) + { + temp = walk->nextClient; + if(walk->socket != InvalidSocket) + Net::closeSocket(walk->socket); + delete walk; + walk = temp; + } +} + +void TelnetConsole::setTelnetParameters(S32 port, const char *telnetPassword, const char *listenPassword, bool remoteEcho) +{ + if(port == mAcceptPort) + return; + + mRemoteEchoEnabled = remoteEcho; + + if(mAcceptSocket != InvalidSocket) + { + Net::closeSocket(mAcceptSocket); + mAcceptSocket = InvalidSocket; + } + mAcceptPort = port; + if(mAcceptPort != -1 && mAcceptPort != 0) + { + mAcceptSocket = Net::openSocket(); + Net::bind(mAcceptSocket, mAcceptPort); + Net::listen(mAcceptSocket, 4); + + Net::setBlocking(mAcceptSocket, false); + } + dStrncpy(mTelnetPassword, telnetPassword, PasswordMaxLength); + dStrncpy(mListenPassword, listenPassword, PasswordMaxLength); +} + +void TelnetConsole::processConsoleLine(const char *consoleLine) +{ + if (mClientList==NULL) return; // just escape early. don't even do another step... + + // ok, spew this line out to all our subscribers... + S32 len = dStrlen(consoleLine)+1; + for(TelnetClient *walk = mClientList; walk; walk = walk->nextClient) + { + if(walk->state == FullAccessConnected || walk->state == ReadOnlyConnected) + { + Net::send(walk->socket, (const unsigned char*)consoleLine, len); + Net::send(walk->socket, (const unsigned char*)"\r\n", 2); + } + } +} + +void TelnetConsole::process() +{ + NetAddress address; + + if(mAcceptSocket != InvalidSocket) + { + // ok, see if we have any new connections: + NetSocket newConnection; + newConnection = Net::accept(mAcceptSocket, &address); + + if(newConnection != InvalidSocket) + { + Con::printf ("Telnet connection from %i.%i.%i.%i", + address.netNum[0], address.netNum[1], address.netNum[2], address.netNum[3]); + + TelnetClient *cl = new TelnetClient; + cl->socket = newConnection; + cl->curPos = 0; +#if defined(TORQUE_SHIPPING) && defined(TORQUE_DISABLE_TELNET_CONSOLE_PASSWORD) + // disable the password in a ship build? WTF. lets make an error: + PleaseMakeSureYouKnowWhatYouAreDoingAndCommentOutThisLineIfSo. +#elif !defined(TORQUE_SHIPPING) && defined(TORQUE_DISABLE_TELNET_CONSOLE_PASSWORD) + cl->state = FullAccessConnected; +#else + cl->state = PasswordTryOne; +#endif + + Net::setBlocking(newConnection, false); + + const char *prompt = Con::getVariable("Con::Prompt"); + char connectMessage[1024]; + dSprintf(connectMessage, sizeof(connectMessage), + "Torque Telnet Remote Console\r\n\r\n%s", + cl->state == FullAccessConnected ? prompt : "Enter Password:"); + + Net::send(cl->socket, (const unsigned char*)connectMessage, dStrlen(connectMessage)+1); + cl->nextClient = mClientList; + mClientList = cl; + } + } + + char recvBuf[256]; + char reply[1024]; + + // see if we have any input to process... + + for(TelnetClient *client = mClientList; client; client = client->nextClient) + { + S32 numBytes; + Net::Error err = Net::recv(client->socket, (unsigned char*)recvBuf, sizeof(recvBuf), &numBytes); + + if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0) + { + Net::closeSocket(client->socket); + client->socket = InvalidSocket; + continue; + } + + S32 replyPos = 0; + for(S32 i = 0; i < numBytes;i++) + { + if(recvBuf[i] == '\r') + continue; + // execute the current command + + if(recvBuf[i] == '\n') + { + reply[replyPos++] = '\r'; + reply[replyPos++] = '\n'; + + client->curLine[client->curPos] = 0; + client->curPos = 0; + + if(client->state == FullAccessConnected) + { + Net::send(client->socket, (const unsigned char*)reply, replyPos); + replyPos = 0; + + // Notify console of line to execute. + RawData rd; + rd.size = dStrlen(client->curLine) + 1; + rd.data = ( S8* ) client->curLine; + Con::smConsoleInput.trigger(rd); + + // note - send prompt next + const char *prompt = Con::getVariable("Con::Prompt"); + Net::send(client->socket, (const unsigned char*)prompt, dStrlen(prompt)); + } + else if(client->state == ReadOnlyConnected) + { + Net::send(client->socket, (const unsigned char*)reply, replyPos); + replyPos = 0; + } + else + { + client->state++; + if(!dStrncmp(client->curLine, mTelnetPassword, PasswordMaxLength)) + { + Net::send(client->socket, (const unsigned char*)reply, replyPos); + replyPos = 0; + + // send prompt + const char *prompt = Con::getVariable("Con::Prompt"); + Net::send(client->socket, (const unsigned char*)prompt, dStrlen(prompt)); + client->state = FullAccessConnected; + } + else if(!dStrncmp(client->curLine, mListenPassword, PasswordMaxLength)) + { + Net::send(client->socket, (const unsigned char*)reply, replyPos); + replyPos = 0; + + // send prompt + const char *listenConnected = "Connected.\r\n"; + Net::send(client->socket, (const unsigned char*)listenConnected, dStrlen(listenConnected)); + client->state = ReadOnlyConnected; + } + else + { + const char *sendStr; + if(client->state == DisconnectThisDude) + sendStr = "Too many tries... cya."; + else + sendStr = "Nope... try agian.\r\nEnter Password:"; + Net::send(client->socket, (const unsigned char*)sendStr, dStrlen(sendStr)); + if(client->state == DisconnectThisDude) + { + Net::closeSocket(client->socket); + client->socket = InvalidSocket; + } + } + } + } + else if(recvBuf[i] == '\b') + { + // pull the old backspace manuever... + if(client->curPos > 0) + { + client->curPos--; + if(client->state == FullAccessConnected) + { + reply[replyPos++] = '\b'; + reply[replyPos++] = ' '; + reply[replyPos++] = '\b'; + } + } + } + else if(client->curPos < Con::MaxLineLength-1) + { + client->curLine[client->curPos++] = recvBuf[i]; + // don't echo password chars... + if(client->state == FullAccessConnected) + reply[replyPos++] = recvBuf[i]; + } + } + + // Echo the character back to the user, unless the remote echo + // is disabled (by default) + if(replyPos && mRemoteEchoEnabled) + Net::send(client->socket, (const unsigned char*)reply, replyPos); + } + + TelnetClient ** walk = &mClientList; + TelnetClient *cl; + while((cl = *walk) != NULL) + { + if(cl->socket == InvalidSocket) + { + *walk = cl->nextClient; + delete cl; + } + else + walk = &cl->nextClient; + } +} diff --git a/console/telnetConsole.h b/console/telnetConsole.h new file mode 100644 index 0000000..32a5861 --- /dev/null +++ b/console/telnetConsole.h @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TELNETCONSOLE_H_ +#define _TELNETCONSOLE_H_ + +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif +#include "platform/platformNet.h" + +/// Telnet admin console. +/// +/// Torque supports remote access to its console. This is most useful when +/// running a dedicated server, as you can remotely administer the game +/// (for instance, kicking people). In the context of a MMORPG, this sort of +/// functionality would be useful for managing a server. +/// +/// There are a number of products for Tribes2 which allow remote administration +/// via a nice GUI. +/// +/// @section telnetconsole_use Using the Telnet Console +/// +/// The TelnetConsole is designed to be used globally, so you don't instantiate +/// it like a normal class. Instead, you allow it to manage itself: +/// +/// @code +/// // How to initialize the TelnetConsole. +/// TelnetConsole::create(); +/// +/// // How to shut down the TelnetConsole. +/// TelnetConsole::destroy(); +/// @endcode +/// +/// +class TelnetConsole +{ + NetSocket mAcceptSocket; + S32 mAcceptPort; + + enum { + PasswordMaxLength = 32 ///< Maximum length of the telnet and listen passwords. + }; + + bool mRemoteEchoEnabled; + char mTelnetPassword[PasswordMaxLength+1]; + char mListenPassword[PasswordMaxLength+1]; + + /// State of a TelnetClient. + enum State + { + PasswordTryOne, ///< Allow three password attempts. + PasswordTryTwo, + PasswordTryThree, + DisconnectThisDude, ///< If they've failed all three, disconnect them. + FullAccessConnected, ///< They presented the telnetPassword, they get full access. + ReadOnlyConnected ///< They presented the listenPassword, they get read only access. + }; + + /// Represents a connection to the telnet console. + /// + /// This is also a linked list. + struct TelnetClient + { + NetSocket socket; + char curLine[Con::MaxLineLength]; + S32 curPos; + S32 state; ///< State of the client. + /// @see TelnetConsole::State + TelnetClient *nextClient; + }; + TelnetClient *mClientList; + TelnetConsole(); + ~TelnetConsole(); + +public: + static void create(); ///< Initialize the telnet console. + static void destroy(); ///< Shut down the telnet console. + void process(); ///< Called by the main loop to let the console process commands + /// and connections. + + /// Configure the parameter for the telnet console. + /// + /// @param port Port on which to listen for connections. + /// @param telnetPassword Password for full access to the console. + /// @param listenPassword Password for read-only access to the console. + /// @param remoteEcho Enable/disable echoing input back to the client + void setTelnetParameters(S32 port, const char *telnetPassword, const char *listenPassword, bool remoteEcho = false); + + /// Callback to handle a line from the console. + /// + /// @note This is used internally by the class; you + /// shouldn't need to call it. + /// + /// @see Con::addConsumer() + void processConsoleLine(const char *line); +}; + +#endif + diff --git a/console/telnetDebugger.cpp b/console/telnetDebugger.cpp new file mode 100644 index 0000000..efdfc2f --- /dev/null +++ b/console/telnetDebugger.cpp @@ -0,0 +1,886 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/telnetDebugger.h" + +#include "core/frameAllocator.h" +#include "console/console.h" +#include "platform/event.h" +#include "core/stringTable.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "console/compiler.h" +#include "core/util/journal/process.h" + +// +// Enhanced TelnetDebugger for Torsion +// http://www.sickheadgames.com/torsion +// +// +// Debugger commands: +// +// CEVAL console line - evaluate the console line +// output: none +// +// BRKVARSET varName passct expr - NOT IMPLEMENTED! +// output: none +// +// BRKVARCLR varName - NOT IMPLEMENTED! +// output: none +// +// BRKSET file line clear passct expr - set a breakpoint on the file,line +// it must pass passct times for it to break and if clear is true, it +// clears when hit +// output: +// +// BRKNEXT - stop execution at the next breakable line. +// output: none +// +// BRKCLR file line - clear a breakpoint on the file,line +// output: none +// +// BRKCLRALL - clear all breakpoints +// output: none +// +// CONTINUE - continue execution +// output: RUNNING +// +// STEPIN - run until next statement +// output: RUNNING +// +// STEPOVER - run until next break <= current frame +// output: RUNNING +// +// STEPOUT - run until next break <= current frame - 1 +// output: RUNNING +// +// EVAL tag frame expr - evaluate the expr in the console, on the frame'th stack frame +// output: EVALOUT tag exprResult +// +// FILELIST - list script files loaded +// output: FILELISTOUT file1 file2 file3 file4 ... +// +// BREAKLIST file - get a list of breakpoint-able lines in the file +// output: BREAKLISTOUT file skipBreakPairs skiplinecount breaklinecount skiplinecount breaklinecount ... +// +// +// Other output: +// +// BREAK file1 line1 function1 file2 line2 function2 ... - Sent when the debugger hits a +// breakpoint. It lists out one file/line/function triplet for each stack level. +// The first one is the top of the stack. +// +// COUT console-output - echo of console output from engine +// +// BRKMOV file line newline - sent when a breakpoint is moved to a breakable line. +// +// BRKCLR file line - sent when a breakpoint cannot be moved to a breakable line on the client. +// + + +ConsoleFunction( dbgSetParameters, void, 3, 4, "(int port, string password, bool waitForClient)" + "Open a debug server port on the specified port, requiring the specified password, " + "and optionally waiting for the debug client to connect.") +{ + if (TelDebugger) + TelDebugger->setDebugParameters(dAtoi(argv[1]), argv[2], argc > 3 ? dAtob(argv[3]) : false ); +} + +ConsoleFunction( dbgIsConnected, bool, 1, 1, "()" + "Returns true if a script debugging client is connected else return false.") +{ + return TelDebugger && TelDebugger->isConnected(); +} + +ConsoleFunction( dbgDisconnect, void, 1, 1, "()" + "Forcibly disconnects any attached script debugging client.") +{ + if (TelDebugger) + TelDebugger->disconnect(); +} + +static void debuggerConsumer(ConsoleLogEntry::Level level, const char *line) +{ + TORQUE_UNUSED(level); + if (TelDebugger) + TelDebugger->processConsoleLine(line); +} + +TelnetDebugger::TelnetDebugger() +{ + Con::addConsumer(debuggerConsumer); + + mAcceptPort = -1; + mAcceptSocket = InvalidSocket; + mDebugSocket = InvalidSocket; + + mState = NotConnected; + mCurPos = 0; + + mBreakpoints = NULL; + mBreakOnNextStatement = false; + mStackPopBreakIndex = -1; + mProgramPaused = false; + mWaitForClient = false; + + // Add the version number in a global so that + // scripts can detect the presence of the + // "enhanced" debugger features. + Con::evaluatef( "$dbgVersion = %d;", Version ); +} + +TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber) +{ + Breakpoint **walk = &mBreakpoints; + Breakpoint *cur; + while((cur = *walk) != NULL) + { + // TODO: This assumes that the OS file names are case + // insensitive... Torque needs a dFilenameCmp() function. + if( dStricmp( cur->fileName, fileName ) == 0 && cur->lineNumber == U32(lineNumber)) + return walk; + walk = &cur->next; + } + return NULL; +} + + +TelnetDebugger::~TelnetDebugger() +{ + Con::removeConsumer(debuggerConsumer); + + if(mAcceptSocket != InvalidSocket) + Net::closeSocket(mAcceptSocket); + if(mDebugSocket != InvalidSocket) + Net::closeSocket(mDebugSocket); +} + +TelnetDebugger *TelDebugger = NULL; + +void TelnetDebugger::create() +{ + TelDebugger = new TelnetDebugger; + Process::notify(TelDebugger, &TelnetDebugger::process, PROCESS_FIRST_ORDER); +} + +void TelnetDebugger::destroy() +{ + Process::remove(TelDebugger, &TelnetDebugger::process); + delete TelDebugger; + TelDebugger = NULL; +} + +void TelnetDebugger::send(const char *str) +{ + Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str)); +} + +void TelnetDebugger::disconnect() +{ + if ( mDebugSocket != InvalidSocket ) + { + Net::closeSocket(mDebugSocket); + mDebugSocket = InvalidSocket; + } + + removeAllBreakpoints(); + + mState = NotConnected; + mProgramPaused = false; +} + +void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient) +{ + // Don't bail if same port... we might just be wanting to change + // the password. +// if(port == mAcceptPort) +// return; + + if(mAcceptSocket != InvalidSocket) + { + Net::closeSocket(mAcceptSocket); + mAcceptSocket = InvalidSocket; + } + mAcceptPort = port; + if(mAcceptPort != -1 && mAcceptPort != 0) + { + mAcceptSocket = Net::openSocket(); + Net::bind(mAcceptSocket, mAcceptPort); + Net::listen(mAcceptSocket, 4); + + Net::setBlocking(mAcceptSocket, false); + } + dStrncpy(mDebuggerPassword, password, PasswordMaxLength); + + mWaitForClient = waitForClient; + if ( !mWaitForClient ) + return; + + // Wait for the client to fully connect. + while ( mState != Connected ) + { + Platform::sleep(10); + process(); + } + +} + +void TelnetDebugger::processConsoleLine(const char *consoleLine) +{ + if(mState != NotConnected) + { + send("COUT "); + send(consoleLine); + send("\r\n"); + } +} + +void TelnetDebugger::process() +{ + NetAddress address; + + if(mAcceptSocket != InvalidSocket) + { + // ok, see if we have any new connections: + NetSocket newConnection; + newConnection = Net::accept(mAcceptSocket, &address); + + if(newConnection != InvalidSocket && mDebugSocket == InvalidSocket) + { + Con::printf ("Debugger connection from %i.%i.%i.%i", + address.netNum[0], address.netNum[1], address.netNum[2], address.netNum[3]); + + mState = PasswordTry; + mDebugSocket = newConnection; + + Net::setBlocking(newConnection, false); + } + else if(newConnection != InvalidSocket) + Net::closeSocket(newConnection); + } + // see if we have any input to process... + + if(mDebugSocket == InvalidSocket) + return; + + checkDebugRecv(); + if(mDebugSocket == InvalidSocket) + removeAllBreakpoints(); +} + +void TelnetDebugger::checkDebugRecv() +{ + for (;;) + { + // Process all the complete commands in the buffer. + while ( mCurPos > 0 ) + { + // Remove leading whitespace. + while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) ) + { + mCurPos--; + dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos); + } + + // Look for a complete command. + bool gotCmd = false; + for(S32 i = 0; i < mCurPos; i++) + { + if( mLineBuffer[i] == 0 ) + mLineBuffer[i] = '_'; + + else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' ) + { + // Send this command to be processed. + mLineBuffer[i] = '\n'; + processLineBuffer(i+1); + + // Remove the command from the buffer. + mCurPos -= i + 1; + dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos); + + gotCmd = true; + break; + } + } + + // If we didn't find a command in this pass + // then we have an incomplete buffer. + if ( !gotCmd ) + break; + } + + // found no or + if(mCurPos == MaxCommandSize) // this shouldn't happen + { + disconnect(); + return; + } + + S32 numBytes; + Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes); + + if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0) + { + disconnect(); + return; + } + if(err == Net::WouldBlock) + return; + + mCurPos += numBytes; + } +} + +void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber) +{ + if(mProgramPaused) + return; + + if(mBreakOnNextStatement) + { + setBreakOnNextStatement( false ); + breakProcess(); + return; + } + + Breakpoint **bp = findBreakpoint(code->name, lineNumber); + if(!bp) + return; + + Breakpoint *brk = *bp; + mProgramPaused = true; + Con::evaluatef("$Debug::result = %s;", brk->testExpression); + if(Con::getBoolVariable("$Debug::result")) + { + brk->curCount++; + if(brk->curCount >= brk->passCount) + { + brk->curCount = 0; + if(brk->clearOnHit) + removeBreakpoint(code->name, lineNumber); + breakProcess(); + } + } + mProgramPaused = false; +} + +void TelnetDebugger::pushStackFrame() +{ + if(mState == NotConnected) + return; + + if(mBreakOnNextStatement && mStackPopBreakIndex > -1 && + gEvalState.stack.size() > mStackPopBreakIndex) + setBreakOnNextStatement( false ); +} + +void TelnetDebugger::popStackFrame() +{ + if(mState == NotConnected) + return; + + if(mStackPopBreakIndex > -1 && gEvalState.stack.size()-1 <= mStackPopBreakIndex) + setBreakOnNextStatement( true ); +} + +void TelnetDebugger::breakProcess() +{ + // Send out a break with the full stack. + sendBreak(); + + mProgramPaused = true; + while(mProgramPaused) + { + Platform::sleep(10); + checkDebugRecv(); + if(mDebugSocket == InvalidSocket) + { + mProgramPaused = false; + removeAllBreakpoints(); + debugContinue(); + return; + } + } +} + +void TelnetDebugger::sendBreak() +{ + // echo out the break + send("BREAK"); + char buffer[MaxCommandSize]; + char scope[MaxCommandSize]; + + S32 last = 0; + + for(S32 i = (S32) gEvalState.stack.size() - 1; i >= last; i--) + { + CodeBlock *code = gEvalState.stack[i]->code; + const char *file = ""; + if (code && code->name && code->name[0]) + file = code->name; + + Namespace *ns = gEvalState.stack[i]->scopeNamespace; + scope[0] = 0; + if ( ns ) { + + if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) { + dStrcat( scope, ns->mParent->mPackage ); + dStrcat( scope, "::" ); + } + if ( ns->mName && ns->mName[0] ) { + dStrcat( scope, ns->mName ); + dStrcat( scope, "::" ); + } + } + + const char *function = gEvalState.stack[i]->scopeName; + if ((!function) || (!function[0])) + function = ""; + dStrcat( scope, function ); + + U32 line=0, inst; + U32 ip = gEvalState.stack[i]->ip; + if (code) + code->findBreakLine(ip, line, inst); + dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope); + send(buffer); + } + + send("\r\n"); +} + +void TelnetDebugger::processLineBuffer(S32 cmdLen) +{ + if (mState == PasswordTry) + { + if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1)) + { + // failed password: + send("PASS WrongPassword.\r\n"); + disconnect(); + } + else + { + send("PASS Connected.\r\n"); + mState = mWaitForClient ? Initialize : Connected; + } + + return; + } + else + { + char evalBuffer[MaxCommandSize]; + char varBuffer[MaxCommandSize]; + char fileBuffer[MaxCommandSize]; + char clear[MaxCommandSize]; + S32 passCount, line, frame; + + if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1) + { + RawData rd; + rd.size = dStrlen(evalBuffer) + 1; + rd.data = ( S8* ) evalBuffer; + Con::smConsoleInput.trigger(rd); + } + else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3) + addVariableBreakpoint(varBuffer, passCount, evalBuffer); + else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1) + removeVariableBreakpoint(varBuffer); + else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5) + addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer); + else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2) + removeBreakpoint(fileBuffer, line); + else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen)) + removeAllBreakpoints(); + else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen)) + debugBreakNext(); + else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen)) + debugContinue(); + else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen)) + debugStepIn(); + else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen)) + debugStepOver(); + else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen)) + debugStepOut(); + else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3) + evaluateExpression(varBuffer, frame, evalBuffer); + else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen)) + dumpFileList(); + else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1) + dumpBreakableList(fileBuffer); + else + { + S32 errorLen = dStrlen(mLineBuffer) + 32; // ~25 in error message, plus buffer + FrameTemp errorBuffer(errorLen); + + dSprintf( errorBuffer, errorLen, "DBGERR Invalid command(%s)!\r\n", mLineBuffer ); + // invalid stuff. + send( errorBuffer ); + } + } +} + +void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*) +{ + send("addVariableBreakpoint\r\n"); +} + +void TelnetDebugger::removeVariableBreakpoint(const char*) +{ + send("removeVariableBreakpoint\r\n"); +} + +void TelnetDebugger::addAllBreakpoints(CodeBlock *code) +{ + if(mState == NotConnected) + return; + + // Find the breakpoints for this code block and attach them. + Breakpoint **walk = &mBreakpoints; + Breakpoint *cur; + while((cur = *walk) != NULL) + { + // TODO: This assumes that the OS file names are case + // insensitive... Torque needs a dFilenameCmp() function. + if( dStricmp( cur->fileName, code->name ) == 0 ) + { + cur->code = code; + + // Find the fist breakline starting from and + // including the requested breakline. + S32 newLine = code->findFirstBreakLine(cur->lineNumber); + if (newLine <= 0) + { + walk = &cur->next; + + char buffer[MaxCommandSize]; + dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber); + send(buffer); + + removeBreakpoint(cur->fileName, cur->lineNumber); + continue; + } + + // If the requested breakline does not match + // the actual break line we need to inform + // the client. + if (newLine != cur->lineNumber) + { + char buffer[MaxCommandSize]; + + // If we already have a line at this breapoint then + // tell the client to clear the breakpoint. + if ( findBreakpoint(cur->fileName, newLine) ) { + + walk = &cur->next; + + dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber); + send(buffer); + + removeBreakpoint(cur->fileName, cur->lineNumber); + continue; + } + + // We're moving the breakpoint to new line... inform the + // client so it can update it's view. + dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine); + send(buffer); + cur->lineNumber = newLine; + } + + code->setBreakpoint(cur->lineNumber); + } + + walk = &cur->next; + } + + // Enable all breaks if a break next was set. + if (mBreakOnNextStatement) + code->setAllBreaks(); +} + +void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString) +{ + fileName = StringTable->insert(fileName); + Breakpoint **bp = findBreakpoint(fileName, line); + + if(bp) + { + // trying to add the same breakpoint... + Breakpoint *brk = *bp; + dFree(brk->testExpression); + brk->testExpression = dStrdup(evalString); + brk->passCount = passCount; + brk->clearOnHit = clear; + brk->curCount = 0; + } + else + { + // Note that if the code block is not already + // loaded it is handled by addAllBreakpoints. + CodeBlock* code = CodeBlock::find(fileName); + if (code) + { + // Find the fist breakline starting from and + // including the requested breakline. + S32 newLine = code->findFirstBreakLine(line); + if (newLine <= 0) + { + char buffer[MaxCommandSize]; + dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line); + send(buffer); + return; + } + + // If the requested breakline does not match + // the actual break line we need to inform + // the client. + if (newLine != line) + { + char buffer[MaxCommandSize]; + + // If we already have a line at this breapoint then + // tell the client to clear the breakpoint. + if ( findBreakpoint(fileName, newLine) ) { + dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line); + send(buffer); + return; + } + + // We're moving the breakpoint to new line... inform the client. + dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine); + send(buffer); + line = newLine; + } + + code->setBreakpoint(line); + } + + Breakpoint *brk = new Breakpoint; + brk->code = code; + brk->fileName = fileName; + brk->lineNumber = line; + brk->passCount = passCount; + brk->clearOnHit = clear; + brk->curCount = 0; + brk->testExpression = dStrdup(evalString); + brk->next = mBreakpoints; + mBreakpoints = brk; + } +} + +void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code) +{ + Breakpoint **walk = &mBreakpoints; + Breakpoint *cur; + while((cur = *walk) != NULL) + { + if(cur->code == code) + { + dFree(cur->testExpression); + *walk = cur->next; + delete walk; + } + else + walk = &cur->next; + } +} + +void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line) +{ + fileName = StringTable->insert(fileName); + Breakpoint **bp = findBreakpoint(fileName, line); + if(bp) + { + Breakpoint *brk = *bp; + *bp = brk->next; + if ( brk->code ) + brk->code->clearBreakpoint(brk->lineNumber); + dFree(brk->testExpression); + delete brk; + } +} + +void TelnetDebugger::removeAllBreakpoints() +{ + Breakpoint *walk = mBreakpoints; + while(walk) + { + Breakpoint *temp = walk->next; + if ( walk->code ) + walk->code->clearBreakpoint(walk->lineNumber); + dFree(walk->testExpression); + delete walk; + walk = temp; + } + mBreakpoints = NULL; +} + +void TelnetDebugger::debugContinue() +{ + if (mState == Initialize) { + mState = Connected; + return; + } + + setBreakOnNextStatement( false ); + mStackPopBreakIndex = -1; + mProgramPaused = false; + send("RUNNING\r\n"); +} + +void TelnetDebugger::setBreakOnNextStatement( bool enabled ) +{ + if ( enabled ) + { + // Apply breaks on all the code blocks. + for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) + walk->setAllBreaks(); + mBreakOnNextStatement = true; + } + else if ( !enabled ) + { + // Clear all the breaks on the codeblocks + // then go reapply the breakpoints. + for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) + walk->clearAllBreaks(); + for(Breakpoint *w = mBreakpoints; w; w = w->next) + { + if ( w->code ) + w->code->setBreakpoint(w->lineNumber); + } + mBreakOnNextStatement = false; + } +} + +void TelnetDebugger::debugBreakNext() +{ + if (mState != Connected) + return; + + if ( !mProgramPaused ) + setBreakOnNextStatement( true ); +} + +void TelnetDebugger::debugStepIn() +{ + // Note that step in is allowed during + // the initialize state, so that we can + // break on the first script line executed. + + setBreakOnNextStatement( true ); + mStackPopBreakIndex = -1; + mProgramPaused = false; + + // Don't bother sending this to the client + // if it's in the initialize state. It will + // just be ignored as the client knows it + // is in a running state when it connects. + if (mState != Initialize) + send("RUNNING\r\n"); + else + mState = Connected; +} + +void TelnetDebugger::debugStepOver() +{ + if (mState != Connected) + return; + + setBreakOnNextStatement( true ); + mStackPopBreakIndex = gEvalState.stack.size(); + mProgramPaused = false; + send("RUNNING\r\n"); +} + +void TelnetDebugger::debugStepOut() +{ + if (mState != Connected) + return; + + setBreakOnNextStatement( false ); + mStackPopBreakIndex = gEvalState.stack.size() - 1; + if ( mStackPopBreakIndex == 0 ) + mStackPopBreakIndex = -1; + mProgramPaused = false; + send("RUNNING\r\n"); +} + +void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer) +{ + // Make sure we're passing a valid frame to the eval. + if ( frame > gEvalState.stack.size() ) + frame = gEvalState.stack.size() - 1; + if ( frame < 0 ) + frame = 0; + + // Build a buffer just big enough for this eval. + const char* format = "return %s;"; + dsize_t len = dStrlen( format ) + dStrlen( evalBuffer ); + char* buffer = new char[ len ]; + dSprintf( buffer, len, format, evalBuffer ); + + // Execute the eval. + CodeBlock *newCodeBlock = new CodeBlock(); + const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame ); + delete [] buffer; + + // Create a new buffer that fits the result. + format = "EVALOUT %s %s\r\n"; + len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result ); + buffer = new char[ len ]; + dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" ); + + send( buffer ); + delete [] buffer; +} + +void TelnetDebugger::dumpFileList() +{ + send("FILELISTOUT "); + for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile) + { + send(walk->name); + if(walk->nextFile) + send(" "); + } + send("\r\n"); +} + +void TelnetDebugger::dumpBreakableList(const char *fileName) +{ + fileName = StringTable->insert(fileName); + CodeBlock *file = CodeBlock::find(fileName); + char buffer[MaxCommandSize]; + if(file) + { + dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1); + send(buffer); + for(U32 i = 0; i < file->breakListSize; i += 2) + { + dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]); + send(buffer); + } + send("\r\n"); + } + else + send("DBGERR No such file!\r\n"); +} + + +void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code) +{ + Breakpoint **walk = &mBreakpoints; + Breakpoint *cur; + while((cur = *walk) != NULL) + { + if(cur->code == code) + cur->code = NULL; + + walk = &cur->next; + } +} diff --git a/console/telnetDebugger.h b/console/telnetDebugger.h new file mode 100644 index 0000000..aad4198 --- /dev/null +++ b/console/telnetDebugger.h @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TELNETDEBUGGER_H_ +#define _TELNETDEBUGGER_H_ + +#ifndef _PLATFORM_PLATFORMNET_H_ +#include "platform/platformNet.h" +#endif + +class CodeBlock; + +/// Telnet debug service implementation. +/// +/// This is the C++ side of the built-in Torque debugger. +/// +/// To use the debugger, use dbgSetParameters(port, password); in the console +/// of the server to enable debugger connections. Then on some other system, +/// start up the app (you don't have to start a game or connect to the +/// server) and exec("common/debugger/debugger.cs"); in the console. Then use +/// the debugger GUI to connect to the server with the right port and password. +/// +/// @see http://www.planettribes.com/tribes2/editing.shtml for more thorough discussion. +class TelnetDebugger +{ + S32 mAcceptPort; + NetSocket mAcceptSocket; + NetSocket mDebugSocket; + + enum { + + // We should only change this is we truely + // break the protocol in a future version. + Version = 2, + + PasswordMaxLength = 32, + MaxCommandSize = 2048 + }; + + char mDebuggerPassword[PasswordMaxLength+1]; + enum State + { + NotConnected, + PasswordTry, + Initialize, + Connected + }; + S32 mState; + char mLineBuffer[MaxCommandSize]; + S32 mCurPos; + bool mWaitForClient; + + TelnetDebugger(); + ~TelnetDebugger(); + + struct Breakpoint + { + StringTableEntry fileName; + CodeBlock *code; + U32 lineNumber; + S32 passCount; + S32 curCount; + char *testExpression; + bool clearOnHit; + Breakpoint *next; + }; + Breakpoint *mBreakpoints; + + Breakpoint **findBreakpoint(StringTableEntry fileName, S32 lineNumber); + + bool mProgramPaused; + bool mBreakOnNextStatement; + S32 mStackPopBreakIndex; + + void addVariableBreakpoint(const char *varName, S32 passCount, const char *evalString); + void removeVariableBreakpoint(const char *varName); + void addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString); + void removeBreakpoint(const char *fileName, S32 line); + void removeAllBreakpoints(); + + void debugBreakNext(); + void debugContinue(); + void debugStepIn(); + void debugStepOver(); + void debugStepOut(); + void evaluateExpression(const char *tag, S32 frame, const char *evalBuffer); + void dumpFileList(); + void dumpBreakableList(const char *fileName); + void removeBreakpointsFromCode(CodeBlock *code); + + void checkDebugRecv(); + void processLineBuffer(S32); + void sendBreak(); + void setBreakOnNextStatement( bool enabled ); +public: + static void create(); + static void destroy(); + + void disconnect(); + bool isConnected() const { return mState == Connected; } + + void process(); + void popStackFrame(); + void pushStackFrame(); + void addAllBreakpoints(CodeBlock *code); + + void clearCodeBlockPointers(CodeBlock *code); + + void breakProcess(); + + virtual void executionStopped(CodeBlock *code, U32 lineNumber); + void send(const char *s); + void setDebugParameters(S32 port, const char *password, bool waitForClient); + void processConsoleLine(const char *consoleLine); +}; + +extern TelnetDebugger *TelDebugger; + +#endif + diff --git a/console/typeValidators.cpp b/console/typeValidators.cpp new file mode 100644 index 0000000..1ea7114 --- /dev/null +++ b/console/typeValidators.cpp @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/consoleObject.h" +#include "console/typeValidators.h" +#include "console/simBase.h" +#include + +void TypeValidator::consoleError(SimObject *object, const char *format, ...) +{ + char buffer[1024]; + va_list argptr; + va_start(argptr, format); + dVsprintf(buffer, sizeof(buffer), format, argptr); + va_end(argptr); + + AbstractClassRep *rep = object->getClassRep(); + AbstractClassRep::Field &fld = rep->mFieldList[fieldIndex]; + const char *objectName = object->getName(); + if(!objectName) + objectName = "unnamed"; + + + Con::warnf("%s - %s(%d) - invalid value for %s: %s", + rep->getClassName(), objectName, object->getId(), fld.pFieldname, buffer); +} + +void FRangeValidator::validateType(SimObject *object, void *typePtr) +{ + F32 *v = (F32 *) typePtr; + if(*v < minV || *v > maxV) + { + consoleError(object, "Must be between %g and %g", minV, maxV); + if(*v < minV) + *v = minV; + else if(*v > maxV) + *v = maxV; + } +} + +void IRangeValidator::validateType(SimObject *object, void *typePtr) +{ + S32 *v = (S32 *) typePtr; + if(*v < minV || *v > maxV) + { + consoleError(object, "Must be between %d and %d", minV, maxV); + if(*v < minV) + *v = minV; + else if(*v > maxV) + *v = maxV; + } +} + +void IRangeValidatorScaled::validateType(SimObject *object, void *typePtr) +{ + S32 *v = (S32 *) typePtr; + *v /= factor; + if(*v < minV || *v > maxV) + { + consoleError(object, "Scaled value must be between %d and %d", minV, maxV); + if(*v < minV) + *v = minV; + else if(*v > maxV) + *v = maxV; + } +} diff --git a/console/typeValidators.h b/console/typeValidators.h new file mode 100644 index 0000000..c16e200 --- /dev/null +++ b/console/typeValidators.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPEVALIDATORS_H_ +#define _TYPEVALIDATORS_H_ + +class TypeValidator +{ + public: + S32 fieldIndex; + + /// Prints a console error message for the validator. + /// + /// The message is prefaced with with: + /// @code + /// className objectName (objectId) - invalid value for fieldName: msg + /// @endcode + void consoleError(SimObject *object, const char *format, ...); + + /// validateType is called for each assigned value on the field this + /// validator is attached to. + virtual void validateType(SimObject *object, void *typePtr) = 0; +}; + + +/// Floating point min/max range validator +class FRangeValidator : public TypeValidator +{ + F32 minV, maxV; +public: + FRangeValidator(F32 minValue, F32 maxValue) + { + minV = minValue; + maxV = maxValue; + } + void validateType(SimObject *object, void *typePtr); +}; + +/// Signed integer min/max range validator +class IRangeValidator : public TypeValidator +{ + S32 minV, maxV; +public: + IRangeValidator(S32 minValue, S32 maxValue) + { + minV = minValue; + maxV = maxValue; + } + void validateType(SimObject *object, void *typePtr); +}; + +/// Scaled integer field validator +/// +/// @note This should NOT be used on a field that gets exported - +/// the field is only validated once on initial assignment +class IRangeValidatorScaled : public TypeValidator +{ + S32 minV, maxV; + S32 factor; +public: + IRangeValidatorScaled(S32 scaleFactor, S32 minValueScaled, S32 maxValueScaled) + { + minV = minValueScaled; + maxV = maxValueScaled; + factor = scaleFactor; + } + void validateType(SimObject *object, void *typePtr); +}; + +#endif diff --git a/core/bitMatrix.h b/core/bitMatrix.h new file mode 100644 index 0000000..99d4d10 --- /dev/null +++ b/core/bitMatrix.h @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITMATRIX_H_ +#define _BITMATRIX_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _BITVECTOR_H_ +#include "core/bitVector.h" +#endif + +/// A matrix of bits. +/// +/// This class manages an array of bits. There are no limitations on the +/// size of the bit matrix (beyond available memory). +/// +/// @note This class is currently unused. +class BitMatrix +{ + U32 mWidth; + U32 mHeight; + U32 mRowByteWidth; + + U8* mBits; + U32 mSize; + + BitVector mColFlags; + BitVector mRowFlags; + + public: + + /// Create a new bit matrix. + /// + /// @param width Width of matrix in bits. + /// @param height Height of matrix in bits. + BitMatrix(const U32 width, const U32 height); + ~BitMatrix(); + + /// @name Setters + /// @{ + + /// Set all the bits in the matrix to false. + void clearAllBits(); + + /// Set all the bits in the matrix to true. + void setAllBits(); + + /// Set a bit at a given location in the matrix. + void setBit(const U32 x, const U32 y); + + /// Clear a bit at a given location in the matrix. + void clearBit(const U32 x, const U32 y); + + /// @} + + /// @name Queries + /// @{ + + /// Is the specified bit set? + bool isSet(const U32 x, const U32 y) const; + + /// Is any bit in the given column set? + bool isAnySetCol(const U32 x); + + /// Is any bit in the given row set? + bool isAnySetRow(const U32 y); + + /// @} +}; + +inline BitMatrix::BitMatrix(const U32 width, const U32 height) + : mColFlags(width), + mRowFlags(height) +{ + AssertFatal(width != 0 && height != 0, "Error, w/h must be non-zero"); + + mWidth = width; + mHeight = height; + mRowByteWidth = (width + 7) >> 3; + + mSize = mRowByteWidth * mHeight; + mBits = new U8[mSize]; +} + +inline BitMatrix::~BitMatrix() +{ + mWidth = 0; + mHeight = 0; + mRowByteWidth = 0; + mSize = 0; + + delete [] mBits; + mBits = NULL; +} + +inline void BitMatrix::clearAllBits() +{ + AssertFatal(mBits != NULL, "Error, clearing after deletion"); + + dMemset(mBits, 0x00, mSize); + mColFlags.clear(); + mRowFlags.clear(); +} + +inline void BitMatrix::setAllBits() +{ + AssertFatal(mBits != NULL, "Error, setting after deletion"); + + dMemset(mBits, 0xFF, mSize); + mColFlags.set(); + mRowFlags.set(); +} + +inline void BitMatrix::setBit(const U32 x, const U32 y) +{ + AssertFatal(x < mWidth && y < mHeight, "Error, out of bounds bit!"); + + U8* pRow = &mBits[y * mRowByteWidth]; + + U8* pByte = &pRow[x >> 3]; + *pByte |= 1 << (x & 0x7); + + mColFlags.set(x); + mRowFlags.set(y); +} + +inline void BitMatrix::clearBit(const U32 x, const U32 y) +{ + AssertFatal(x < mWidth && y < mHeight, "Error, out of bounds bit!"); + + U8* pRow = &mBits[y * mRowByteWidth]; + + U8* pByte = &pRow[x >> 3]; + *pByte &= ~(1 << (x & 0x7)); +} + +inline bool BitMatrix::isSet(const U32 x, const U32 y) const +{ + AssertFatal(x < mWidth && y < mHeight, "Error, out of bounds bit!"); + + U8* pRow = &mBits[y * mRowByteWidth]; + + U8* pByte = &pRow[x >> 3]; + return (*pByte & (1 << (x & 0x7))) != 0; +} + +inline bool BitMatrix::isAnySetCol(const U32 x) +{ + AssertFatal(x < mWidth, "Error, out of bounds column!"); + + return mColFlags.test(x); +} + +inline bool BitMatrix::isAnySetRow(const U32 y) +{ + AssertFatal(y < mHeight, "Error, out of bounds row!"); + + return mRowFlags.test(y); +} + +#endif // _H_BITMATRIX_ diff --git a/core/bitRender.cpp b/core/bitRender.cpp new file mode 100644 index 0000000..8f5e4d3 --- /dev/null +++ b/core/bitRender.cpp @@ -0,0 +1,987 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/bitRender.h" + +U32 openTable[32] = { 0xFFFFFFFF,0xFFFFFFFE,0xFFFFFFFC,0xFFFFFFF8, + 0xFFFFFFF0,0xFFFFFFE0,0xFFFFFFC0,0xFFFFFF80, + 0xFFFFFF00,0xFFFFFE00,0xFFFFFC00,0xFFFFF800, + 0xFFFFF000,0xFFFFE000,0xFFFFC000,0xFFFF8000, + 0xFFFF0000,0xFFFE0000,0xFFFC0000,0xFFF80000, + 0xFFF00000,0xFFE00000,0xFFC00000,0xFF800000, + 0xFF000000,0xFE000000,0xFC000000,0xF8000000, + 0xF0000000,0xE0000000,0xC0000000,0x80000000 }; + +U32 closeTable[32] = { 0x00000001,0x00000003,0x00000007,0x0000000F, + 0x0000001F,0x0000003F,0x0000007F,0x000000FF, + 0x000001FF,0x000003FF,0x000007FF,0x00000FFF, + 0x00001FFF,0x00003FFF,0x00007FFF,0x0000FFFF, + 0x0001FFFF,0x0003FFFF,0x0007FFFF,0x000FFFFF, + 0x001FFFFF,0x003FFFFF,0x007FFFFF,0x00FFFFFF, + 0x01FFFFFF,0x03FFFFFF,0x07FFFFFF,0x0FFFFFFF, + 0x1FFFFFFF,0x3FFFFFFF,0x7FFFFFFF,0xFFFFFFFF }; + +struct DrawStruct +{ + S16 start; + S16 num; +}; + +void BitRender::render_strips(const U8 * draw, S32 numDraw, S32 szDraw, const U16 * indices, const Point2I * points, S32 dim, U32 * bits) +{ + const U8 * drawEnd = draw + numDraw*szDraw; + + // loop through strips... + for (; drawstart; + const U16 * iend = icurrent + drawStruct->num; + + const Point2I * vv0 = points + *(icurrent++); + const Point2I * vv1; + const Point2I * vv2 = points + *(icurrent++); + const Point2I **nextPt = &vv1; + + while (icurrentx>=0 && v0->xy>=0 && v0->yx>=0 && v1->xy>=0 && v1->yx>=0 && v2->xy>=0 && v2->yx-v1->x)*(v2->y-v1->y) > (v0->y-v1->y)*(v2->x-v1->x)) + continue; + + // rotate so that v0 holds topmost y coord + // Note: The particular inequalities used ( <=, <, then <=) are important. + // They guarantee that if there are two verts on the top row, that + // they will be vert v0 and v1 (also could be three). + if (v1->y <= v2->y) + { + if (v1->y < v0->y) + { + const Point2I * tmp = v0; + v0 = v1; + v1 = v2; + v2 = tmp; + } + } + else + { + if (v2->y <= v0->y) + { + const Point2I * tmp = v0; + v0 = v2; + v2 = v1; + v1 = tmp; + } + } + + // all the control variables we have to set up... + S32 leftDeltaY, xLeftInc, xLeftErrInc, xLeftDir, xLeft, xLeftErr = 0; + S32 rightDeltaY, xRightInc, xRightErrInc, xRightDir, xRight, xRightErr = 0; + S32 * changeThisDelta; + S32 * changeThisInc; + S32 * changeThisErrInc; + S32 * changeThisDir; + S32 toThisDelta; + S32 toThisInc; + S32 toThisErrInc; + S32 toThisDir; + S32 ySwitch; + S32 yBottom; + + // check for special case where top row holds two verts + if (v0->y==v1->y) + { + leftDeltaY = v2->y-v0->y; + rightDeltaY = v2->y-v1->y; + ySwitch = v2->y; + yBottom = v2->y; + + if (v1->y==v2->y) + { + // special-special case where top row holds all three verts + xLeft = getMin(getMin(v0->x,v1->x),v2->x); + xRight = getMax(getMax(v0->x,v1->x),v2->x); + xLeftInc = xRightInc = 0; + xLeftErrInc = xRightErrInc = 0; + xLeftDir = xRightDir = 1; + } + else + { + // standard-special case...v2 on different row from v0 and v1 + xLeft = v0->x; + xLeftInc = (v2->x-v0->x) / leftDeltaY; + xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc; + xLeftDir = v2->x-v0->x > 0 ? 1 : -1; + + xRight = v1->x; + xRightInc = (v2->x-v1->x) / rightDeltaY; + xRightErrInc = (v2->x-v1->x) - rightDeltaY*xRightInc; + xRightDir = v2->x-v1->x > 0 ? 1 : -1; + } + + // set these variables to avoid a crash... + changeThisDelta = &toThisDelta; + changeThisInc = &toThisInc; + changeThisErrInc = &toThisErrInc; + changeThisDir = &toThisDir; + } + else + { + leftDeltaY = v2->y-v0->y; + xLeftInc = (v2->x-v0->x) / leftDeltaY; + xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc; + xLeftDir = v2->x-v0->x > 0 ? 1 : -1; + + rightDeltaY = v1->y-v0->y; + xRightInc = (v1->x-v0->x) / rightDeltaY; + xRightErrInc = (v1->x-v0->x) - rightDeltaY*xRightInc; + xRightDir = v1->x-v0->x > 0 ? 1 : -1; + + xLeft = xRight = v0->x; + + if (v1->yy) + { + // right edge bends + changeThisDelta = &rightDeltaY; + changeThisInc = &xRightInc; + changeThisErrInc = &xRightErrInc; + changeThisDir = &xRightDir; + toThisDelta = v2->y-v1->y; + toThisInc = (v2->x-v1->x) / toThisDelta; + toThisErrInc = (v2->x-v1->x) - toThisDelta * toThisInc; + toThisDir = v2->x-v1->x > 0 ? 1 : -1; + ySwitch = v1->y-1; + yBottom = v2->y; + } + else + { + // left edge bends + changeThisDelta = &leftDeltaY; + changeThisInc = &xLeftInc; + changeThisErrInc = &xLeftErrInc; + changeThisDir = &xLeftDir; + toThisDelta = v1->y-v2->y; + toThisInc = toThisDelta ? (v1->x-v2->x) / toThisDelta : 0; + toThisErrInc = toThisDelta ? (v1->x-v2->x) - toThisDelta * toThisInc : 0; + toThisDir = v1->x-v2->x > 0 ? 1 : -1; + ySwitch = v2->y-1; + yBottom = v1->y; + } + } + xLeftErrInc *= xLeftDir; + xRightErrInc *= xRightDir; + toThisErrInc *= toThisDir; + + U32 * rowStart = bits + v0->y * (dim>>5); + for (S32 y=v0->y; y<=yBottom;) + { + do + { + AssertFatal(xLeft<=xRight,"BitRender::render"); + + U32 open = openTable[xLeft&31]; + U32 close = closeTable[xRight&31]; + if ( (xLeft^xRight) & ~31) + { + U32 * x = rowStart+(xLeft>>5); + *x |= open; + U32 * xEnd = rowStart+(xRight>>5); + while (++x>5] |= open & close; + + xLeft += xLeftInc; + xLeftErr += xLeftErrInc; + if (xLeftErr >= leftDeltaY) + { + xLeft += xLeftDir; + xLeftErr -= leftDeltaY; + } + + xRight += xRightInc; + xRightErr += xRightErrInc; + if (xRightErr >= rightDeltaY) + { + xRight += xRightDir; + xRightErr -= rightDeltaY; + } + + rowStart += dim>>5; + + } while (y++start; + const U16 * iend = icurrent + drawStruct->num; + + while (icurrentx>=0 && v0->xy>=0 && v0->yx>=0 && v1->xy>=0 && v1->yx>=0 && v2->xy>=0 && v2->yx-v1->x)*(v2->y-v1->y) > (v0->y-v1->y)*(v2->x-v1->x)) + return; + + // rotate so that v0 holds topmost y coord + // Note: The particular inequalities used ( <=, <, then <=) are important. + // They guarantee that if there are two verts on the top row, that + // they will be vert v0 and v1 (also could be three). + if (v1->y <= v2->y) + { + if (v1->y < v0->y) + { + const Point2I * tmp = v0; + v0 = v1; + v1 = v2; + v2 = tmp; + } + } + else + { + if (v2->y <= v0->y) + { + const Point2I * tmp = v0; + v0 = v2; + v2 = v1; + v1 = tmp; + } + } + + // all the control variables we have to set up... + S32 leftDeltaY, xLeftInc = 0, xLeftErrInc, xLeftDir = 0, xLeft, xLeftErr = 0; + S32 rightDeltaY, xRightInc = 0, xRightErrInc, xRightDir = 0, xRight, xRightErr = 0; + S32 * changeThisDelta; + S32 * changeThisInc; + S32 * changeThisErrInc; + S32 * changeThisDir; + S32 toThisDelta; + S32 toThisInc; + S32 toThisErrInc; + S32 toThisDir; + S32 ySwitch; + S32 yBottom; + + // check for special case where top row holds two verts + if (v0->y==v1->y) + { + leftDeltaY = v2->y-v0->y; + rightDeltaY = v2->y-v1->y; + ySwitch = v2->y; + yBottom = v2->y; + + if (v1->y==v2->y) + { + // special-special case where top row holds all three verts + xLeft = getMin(getMin(v0->x,v1->x),v2->x); + xRight = getMax(getMax(v0->x,v1->x),v2->x); + xLeftErrInc = xRightErrInc = 0; + } + else + { + // standard-special case...v2 on different row from v0 and v1 + xLeft = v0->x; + xLeftInc = (v2->x-v0->x) / leftDeltaY; + xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc; + xLeftDir = v2->x-v0->x > 0 ? 1 : -1; + + xRight = v1->x; + xRightInc = (v2->x-v1->x) / rightDeltaY; + xRightErrInc = (v2->x-v1->x) - rightDeltaY*xRightInc; + xRightDir = v2->x-v1->x > 0 ? 1 : -1; + } + + // set these variables to avoid a crash... + changeThisDelta = &toThisDelta; + changeThisInc = &toThisInc; + changeThisErrInc = &toThisErrInc; + changeThisDir = &toThisDir; + } + else + { + leftDeltaY = v2->y-v0->y; + xLeftInc = (v2->x-v0->x) / leftDeltaY; + xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc; + xLeftDir = v2->x-v0->x > 0 ? 1 : -1; + + rightDeltaY = v1->y-v0->y; + xRightInc = (v1->x-v0->x) / rightDeltaY; + xRightErrInc = (v1->x-v0->x) - rightDeltaY*xRightInc; + xRightDir = v1->x-v0->x > 0 ? 1 : -1; + + xLeft = xRight = v0->x; + + if (v1->yy) + { + // right edge bends + changeThisDelta = &rightDeltaY; + changeThisInc = &xRightInc; + changeThisErrInc = &xRightErrInc; + changeThisDir = &xRightDir; + toThisDelta = v2->y-v1->y; + toThisInc = (v2->x-v1->x) / toThisDelta; + toThisErrInc = (v2->x-v1->x) - toThisDelta * toThisInc; + toThisDir = v2->x-v1->x > 0 ? 1 : -1; + ySwitch = v1->y-1; + yBottom = v2->y; + } + else + { + // left edge bends + changeThisDelta = &leftDeltaY; + changeThisInc = &xLeftInc; + changeThisErrInc = &xLeftErrInc; + changeThisDir = &xLeftDir; + toThisDelta = v1->y-v2->y; + toThisInc = toThisDelta ? (v1->x-v2->x) / toThisDelta : 0; + toThisErrInc = toThisDelta ? (v1->x-v2->x) - toThisDelta * toThisInc : 0; + toThisDir = v1->x-v2->x > 0 ? 1 : -1; + ySwitch = v2->y-1; + yBottom = v1->y; + } + } + xLeftErrInc *= xLeftDir; + xRightErrInc *= xRightDir; + toThisErrInc *= toThisDir; + + U32 * rowStart = bits + v0->y * (dim>>5); + for (S32 y=v0->y; y<=yBottom;) + { + do + { + AssertFatal(xLeft<=xRight,"BitRender::render"); + + U32 open = openTable[xLeft&31]; + U32 close = closeTable[xRight&31]; + if ( (xLeft^xRight) & ~31) + { + U32 * x = rowStart+(xLeft>>5); + *x |= open; + U32 * xEnd = rowStart+(xRight>>5); + while (++x>5] |= open & close; + + xLeft += xLeftInc; + xLeftErr += xLeftErrInc; + if (xLeftErr >= leftDeltaY) + { + xLeft += xLeftDir; + xLeftErr -= leftDeltaY; + } + + xRight += xRightInc; + xRightErr += xRightErrInc; + if (xRightErr >= rightDeltaY) + { + xRight += xRightDir; + xRightErr -= rightDeltaY; + } + + rowStart += dim>>5; + + } while (y++x>=0 && v0->xy>=0 && v0->yx>=0 && v1->xy>=0 && v1->yx>=0 && v2->xy>=0 && v2->yx-v1->x)*(v2->y-v1->y) > (v0->y-v1->y)*(v2->x-v1->x)) + return; + + // rotate so that v0 holds topmost y coord + // Note: The particular inequalities used ( <=, <, then <=) are important. + // They guarantee that if there are two verts on the top row, that + // they will be vert v0 and v1 (also could be three). + if (v1->y <= v2->y) + { + if (v1->y < v0->y) + { + const Point2I * tmp = v0; + v0 = v1; + v1 = v2; + v2 = tmp; + } + } + else + { + if (v2->y <= v0->y) + { + const Point2I * tmp = v0; + v0 = v2; + v2 = v1; + v1 = tmp; + } + } + + // all the control variables we have to set up... + S32 leftDeltaY, xLeftInc = 0, xLeftErrInc, xLeftDir = 0, xLeft, xLeftErr = 0; + S32 rightDeltaY, xRightInc = 0, xRightErrInc, xRightDir = 0, xRight, xRightErr = 0; + S32 * changeThisDelta; + S32 * changeThisInc; + S32 * changeThisErrInc; + S32 * changeThisDir; + S32 toThisDelta; + S32 toThisInc; + S32 toThisErrInc; + S32 toThisDir; + S32 ySwitch; + S32 yBottom; + + // check for special case where top row holds two verts + if (v0->y==v1->y) + { + leftDeltaY = v2->y-v0->y; + rightDeltaY = v2->y-v1->y; + ySwitch = v2->y; + yBottom = v2->y; + + if (v1->y==v2->y) + { + // special-special case where top row holds all three verts + xLeft = getMin(getMin(v0->x,v1->x),v2->x); + xRight = getMax(getMax(v0->x,v1->x),v2->x); + xLeftErrInc = xRightErrInc = 0; + } + else + { + // standard-special case...v2 on different row from v0 and v1 + xLeft = v0->x; + xLeftInc = (v2->x-v0->x) / leftDeltaY; + xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc; + xLeftDir = v2->x-v0->x > 0 ? 1 : -1; + + xRight = v1->x; + xRightInc = (v2->x-v1->x) / rightDeltaY; + xRightErrInc = (v2->x-v1->x) - rightDeltaY*xRightInc; + xRightDir = v2->x-v1->x > 0 ? 1 : -1; + } + + // set these variables to avoid a crash... + changeThisDelta = &toThisDelta; + changeThisInc = &toThisInc; + changeThisErrInc = &toThisErrInc; + changeThisDir = &toThisDir; + } + else + { + leftDeltaY = v2->y-v0->y; + xLeftInc = (v2->x-v0->x) / leftDeltaY; + xLeftErrInc = (v2->x-v0->x) - leftDeltaY*xLeftInc; + xLeftDir = v2->x-v0->x > 0 ? 1 : -1; + + rightDeltaY = v1->y-v0->y; + xRightInc = (v1->x-v0->x) / rightDeltaY; + xRightErrInc = (v1->x-v0->x) - rightDeltaY*xRightInc; + xRightDir = v1->x-v0->x > 0 ? 1 : -1; + + xLeft = xRight = v0->x; + + if (v1->yy) + { + // right edge bends + changeThisDelta = &rightDeltaY; + changeThisInc = &xRightInc; + changeThisErrInc = &xRightErrInc; + changeThisDir = &xRightDir; + toThisDelta = v2->y-v1->y; + toThisInc = (v2->x-v1->x) / toThisDelta; + toThisErrInc = (v2->x-v1->x) - toThisDelta * toThisInc; + toThisDir = v2->x-v1->x > 0 ? 1 : -1; + ySwitch = v1->y-1; + yBottom = v2->y; + } + else + { + // left edge bends + changeThisDelta = &leftDeltaY; + changeThisInc = &xLeftInc; + changeThisErrInc = &xLeftErrInc; + changeThisDir = &xLeftDir; + toThisDelta = v1->y-v2->y; + toThisInc = toThisDelta ? (v1->x-v2->x) / toThisDelta : 0; + toThisErrInc = toThisDelta ? (v1->x-v2->x) - toThisDelta * toThisInc : 0; + toThisDir = v1->x-v2->x > 0 ? 1 : -1; + ySwitch = v2->y-1; + yBottom = v1->y; + } + } + xLeftErrInc *= xLeftDir; + xRightErrInc *= xRightDir; + toThisErrInc *= toThisDir; + + U32 * rowStart = bits + v0->y * (dim>>5); + for (S32 y=v0->y; y<=yBottom;) + { + do + { + AssertFatal(xLeft<=xRight,"BitRender::render"); + + U32 open = openTable[xLeft&31]; + U32 close = closeTable[xRight&31]; + if ( (xLeft^xRight) & ~31) + { + U32 * x = rowStart+(xLeft>>5); + *x |= open; + U32 * xEnd = rowStart+(xRight>>5); + while (++x>5] |= open & close; + + xLeft += xLeftInc; + xLeftErr += xLeftErrInc; + if (xLeftErr >= leftDeltaY) + { + xLeft += xLeftDir; + xLeftErr -= leftDeltaY; + } + + xRight += xRightInc; + xRightErr += xRightErrInc; + if (xRightErr >= rightDeltaY) + { + xRight += xRightDir; + xRightErr -= rightDeltaY; + } + + rowStart += dim>>5; + + } while (y++> 2) +#define SF32(a,b,c,d) {SF(a),SF(b),SF(c),SF(d)} + +/* +// endian-ordering version of the SF macro, for the blur methods. +#if PLATFORM_LITTLE_ENDIAN +#define SF32E(a,b,c,d) SF32(a,b,c,d) +#else +#define SF32E(a,b,c,d) SF32(d,c,b,a) +#endif +*/ + + +U8 bitTable[16][4] = +{ + SF32( 0, 0, 0, 0), // 0 + SF32(255, 0, 0, 0), // 1 + SF32( 0,255, 0, 0), // 2 + SF32(255,255, 0, 0), // 3 + SF32( 0, 0,255, 0), // 4 + SF32(255, 0,255, 0), // 5 + SF32( 0,255,255, 0), // 6 + SF32(255,255,255, 0), // 7 + SF32( 0, 0, 0,255), // 8 + SF32(255, 0, 0,255), // 9 + SF32( 0,255, 0,255), // 10 + SF32(255,255, 0,255), // 11 + SF32( 0, 0,255,255), // 12 + SF32(255, 0,255,255), // 13 + SF32( 0,255,255,255), // 14 + SF32(255,255,255,255), // 15 +}; + +void BitRender::bitTo8Bit(U32 * bits, U32 * eightBits, S32 dim) +{ + dim *= dim>>5; + for (S32 i=0; i>= 4; + + *eightBits++ = *(U32*)&bitTable[val&0xF]; + val >>= 4; + + *eightBits++ = *(U32*)&bitTable[val&0xF]; + val >>= 4; + + *eightBits++ = *(U32*)&bitTable[val&0xF]; + val >>= 4; + + *eightBits++ = *(U32*)&bitTable[val&0xF]; + val >>= 4; + + *eightBits++ = *(U32*)&bitTable[val&0xF]; + val >>= 4; + + *eightBits++ = *(U32*)&bitTable[val&0xF]; + val >>= 4; + + *eightBits++ = *(U32*)&bitTable[val&0xF]; + val >>= 4; + } +} + + +U8 bitTableA[16][4] = +{ + SF32( 0, 0, 0, 0), // 0 + SF32( 0, 0, 0, 0), // 1 + SF32( 0, 0, 0, 0), // 2 + SF32( 0, 0, 0, 0), // 3 + SF32( 0, 0, 0, 0), // 4 + SF32( 0, 0, 0, 0), // 5 + SF32( 0, 0, 0, 0), // 6 + SF32( 0, 0, 0, 0), // 7 + SF32( 17, 0, 0, 0), // 8 + SF32( 17, 0, 0, 0), // 9 + SF32( 17, 0, 0, 0), // 10 + SF32( 17, 0, 0, 0), // 11 + SF32( 17, 0, 0, 0), // 12 + SF32( 17, 0, 0, 0), // 13 + SF32( 17, 0, 0, 0), // 14 + SF32( 17, 0, 0, 0), // 15 +}; + +U8 bitTableB[16][4] = +{ + SF32( 0, 0, 0, 0), // 0 + SF32( 34, 17, 0, 0), // 1 + SF32( 17, 34, 17, 0), // 2 + SF32( 51, 51, 17, 0), // 3 + SF32( 0, 17, 34, 17), // 4 + SF32( 34, 34, 34, 17), // 5 + SF32( 17, 51, 51, 17), // 6 + SF32( 51, 68, 51, 17), // 7 + SF32( 0, 0, 17, 34), // 8 + SF32( 34, 17, 17, 34), // 9 + SF32( 17, 34, 34, 34), // 10 + SF32( 51, 51, 34, 34), // 11 + SF32( 0, 17, 51, 51), // 12 + SF32( 34, 34, 51, 51), // 13 + SF32( 17, 51, 68, 51), // 14 + SF32( 51, 68, 68, 51), // 15 +}; + + +U8 bitTableC[16][4] = +{ + SF32( 0, 0, 0, 0), // 0 + SF32( 0, 0, 0, 17), // 1 + SF32( 0, 0, 0, 0), // 2 + SF32( 0, 0, 0, 17), // 3 + SF32( 0, 0, 0, 0), // 4 + SF32( 0, 0, 0, 17), // 5 + SF32( 0, 0, 0, 0), // 6 + SF32( 0, 0, 0, 17), // 7 + SF32( 0, 0, 0, 0), // 8 + SF32( 0, 0, 0, 17), // 9 + SF32( 0, 0, 0, 0), // 10 + SF32( 0, 0, 0, 17), // 11 + SF32( 0, 0, 0, 0), // 12 + SF32( 0, 0, 0, 17), // 13 + SF32( 0, 0, 0, 0), // 14 + SF32( 0, 0, 0, 17), // 15 +}; + +U8 bitTableE[16][4] = +{ + SF32( 0, 0, 0, 0), // 0 + SF32( 51, 34, 0, 0), // 1 + SF32( 34, 51, 34, 0), // 2 + SF32( 85, 85, 34, 0), // 3 + SF32( 0, 34, 51, 34), // 4 + SF32( 51, 68, 51, 34), // 5 + SF32( 34, 85, 85, 34), // 6 + SF32( 85,119, 85, 34), // 7 + SF32( 0, 0, 34, 51), // 8 + SF32( 51, 34, 34, 51), // 9 + SF32( 34, 51, 68, 51), // 10 + SF32( 85, 85, 68, 51), // 11 + SF32( 0, 34, 85, 85), // 12 + SF32( 51, 68, 85, 85), // 13 + SF32( 34, 85,119, 85), // 14 + SF32( 85,119,119, 85), // 15 +}; + +void BitRender::bitTo8Bit_3(U32 * bits, U32 * eightBits, S32 dim) +{ +#if defined(TORQUE_BIG_ENDIAN) +#define MAX_SHADOW_TEXELS (256 + 4) //256 seems big enough, +4 so we can run off end of buffer. + // slow fake gaussian + int i, j, c; + U8 tmpLine[3][MAX_SHADOW_TEXELS]; + U8 *src0, *src1, *src2; + U8 *s0, *s1, *s2; + U32 dimS2 = dim>>2; + U32 *src32; + U32 *currLine = bits; + U32 currVal; + U32 sampleVal; + U8 c00, c01, c02, c10, c11, c12, c20, c21, c22; + int openBuf; + + src0 = tmpLine[0]; + src1 = tmpLine[1]; + src2 = tmpLine[2]; + openBuf = 2; // the one src2 is using right now. + + // pre-process two rows into our tmp buffers. + src32 = (U32*)(src0); + for(i=0; i<(dimS2>>3); i++) // walk 4 bytes at a time, 4 bits at a time. + { + currVal = *currLine++; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[currVal]; + } + + src32 = (U32*)(src1); + for(i=0; i<(dimS2>>3); i++) + { + currVal = *currLine++; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[currVal]; + } + + // pre-clear first row of depth buffer. + for (i=0; i0) + { + j--; + // process new line (currLine) into new tmp buffer (src2) + src32 = (U32*)(src2); + for(i=0; i<(dimS2>>3); i++) // 8 at a time. + { + currVal = *currLine++; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[0x0F & currVal]; currVal >>= 4; + *src32++ = *(U32*)&bitTable[currVal]; + } + + // blur & copy current src1 to current dest +#if NO_BLUR + // test for basic functionality. + srcTmp = src1; + src32 = (U32*)srcTmp; + for (i=0; i>= 5; // div by 32 subsamples + } + // mix into val store to hold. + currVal |= sampleVal << (8*(3-c)); + c00 = c01; c01 = c02; + c10 = c11; c11 = c12; + c20 = c21; c21 = c22; + //c#2 defd next time round + } + // put samples into dest buffer. + *eightBits++ = currVal; + } +#endif + + // flip around ptrs for next row processing. + openBuf++; + if (openBuf>2) + openBuf = 0; + src0 = src1; + src1 = src2; + src2 = tmpLine[openBuf]; + } + + // clear last dest buffer row + for (i=0; i>2); + do { *eightBits++=0; } while (eightBits>2)*(dim-1); + + U8 * p0 = (U8*)bits; + U8 bitLo10 = 0x0F & *p0; + U8 bitHi10 = (*p0) >> 4; + p0++; + + U8 * p1 = (U8*)bits + (dim>>3); + U8 bitLo11 = 0x0F & *p1; + U8 bitHi11 = (*p1) >> 4; + p1++; + + U8 * p2 = (U8*)bits + (dim>>2); + U8 bitLo12 = 0x0F & *p2; + U8 bitHi12 = (*p2) >> 4; + p2++; + + U8 bitLo20, bitHi20; + U8 bitLo21, bitHi21; + U8 bitLo22, bitHi22; + U8 bitHi00 = 0; + U8 bitHi01 = 0; + U8 bitHi02 = 0; + + // go thru penultimate row (but stop before last entry in that row) + U8 * end = (U8*)bits + dim*(dim>>3) - 1; + do + { + bitLo20 = 0x0F & *p0; + bitHi20 = (*p0) >> 4; + bitLo21 = 0x0F & *p1; + bitHi21 = (*p1) >> 4; + bitLo22 = 0x0F & *p2; + bitHi22 = (*p2) >> 4; + + *eightBits++ = *(U32*)&bitTableA[bitHi00] + *(U32*)&bitTableB[bitLo10] + *(U32*)&bitTableC[bitHi10] + + *(U32*)&bitTableA[bitHi01]*2 + *(U32*)&bitTableE[bitLo11] + *(U32*)&bitTableC[bitHi11]*2 + + *(U32*)&bitTableA[bitHi02] + *(U32*)&bitTableB[bitLo12] + *(U32*)&bitTableC[bitHi12]; + *eightBits++ = *(U32*)&bitTableA[bitLo10] + *(U32*)&bitTableB[bitHi10] + *(U32*)&bitTableC[bitLo20] + + *(U32*)&bitTableA[bitLo11]*2 + *(U32*)&bitTableE[bitHi11] + *(U32*)&bitTableC[bitLo21]*2 + + *(U32*)&bitTableA[bitLo12] + *(U32*)&bitTableB[bitHi12] + *(U32*)&bitTableC[bitLo22]; + + bitHi00 = bitHi10; + bitLo10 = bitLo20; + bitHi10 = bitHi20; + bitHi01 = bitHi11; + bitLo11 = bitLo21; + bitHi11 = bitHi21; + bitHi02 = bitHi12; + bitLo12 = bitLo22; + bitHi12 = bitHi22; + + p0++; + p1++; + p2++; + } + while (p2> ( 8 - remaider ); + return ( mBits[testBytes] & mask ) == mask; +} + +bool BitVector::testAllClear() const +{ + const U32 remaider = mSize % 8; + const U32 testBytes = mSize / 8; + + for ( U32 i=0; i < testBytes; i++ ) + if ( mBits[i] != 0 ) + return false; + + if ( remaider == 0 ) + return true; + + const U8 mask = (U8)0xFF >> ( 8 - remaider ); + return ( mBits[testBytes] & mask ) == 0; +} diff --git a/core/bitVector.h b/core/bitVector.h new file mode 100644 index 0000000..74d3d4c --- /dev/null +++ b/core/bitVector.h @@ -0,0 +1,181 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITVECTOR_H_ +#define _BITVECTOR_H_ + + +/// Manage a vector of bits of arbitrary size. +class BitVector +{ +protected: + + /// The array of bytes that stores our bits. + U8* mBits; + + /// The allocated size of the bit array. + U32 mByteSize; + + /// The size of the vector in bits. + U32 mSize; + + /// Returns a size in bytes which is 32bit aligned + /// and can hold all the requested bits. + static U32 calcByteSize( const U32 numBits ); + + /// Internal function which resizes the bit array. + void _resize( U32 sizeInBits, bool copyBits ); + +public: + + /// Default constructor which creates an bit + /// vector with a bit size of zero. + BitVector(); + + /// Constructs a bit vector with the desired size. + BitVector( U32 sizeInBits ); + + /// Destructor. + ~BitVector(); + + /// @name Size Management + /// @{ + + /// Resizes the bit vector. + void setSize( U32 sizeInBits ); + + /// Returns the size in bits. + U32 getSize() const; + + /// Returns the 32bit aligned size in bytes. + U32 getByteSize() const; + + /// Returns the allocated size in bytes + /// which may be more than getByteSize(). + U32 getAllocatedByteSize() const { return mByteSize; } + + /// Retuns the bits. + const U8* getBits() const { return mBits; } + + /// Returns the writeable bits. + U8* getNCBits() { return mBits; } + + /// @} + + /// Copy the content of another bit vector. + void copy( const BitVector &from ); + + /// @name Mutators + /// Note that bits are specified by index, unlike BitSet32. + /// @{ + + /// Clear all the bits. + void clear(); + + /// Set all the bits. + void set(); + + /// Set the specified bit. + void set(U32 bit); + + /// Clear the specified bit. + void clear(U32 bit); + + /// Test that the specified bit is set. + bool test(U32 bit) const; + + /// Return true if all bits are set. + bool testAll() const; + + /// Return true if all bits are clear. + bool testAllClear() const; + + /// @} +}; + +inline BitVector::BitVector() +{ + mBits = NULL; + mByteSize = 0; + mSize = 0; +} + + +inline BitVector::BitVector( U32 sizeInBits ) +{ + mBits = NULL; + mByteSize = 0; + mSize = 0; + setSize( sizeInBits ); +} + +inline BitVector::~BitVector() +{ + delete [] mBits; + mBits = NULL; + mByteSize = 0; + mSize = 0; +} + +inline U32 BitVector::calcByteSize( U32 numBits ) +{ + // Make sure that we are 32 bit aligned + return (((numBits + 0x7) >> 3) + 0x3) & ~0x3; +} + +inline void BitVector::setSize( const U32 sizeInBits ) +{ + _resize( sizeInBits, true ); +} + +inline U32 BitVector::getSize() const +{ + return mSize; +} + +inline U32 BitVector::getByteSize() const +{ + return calcByteSize(mSize); +} + +inline void BitVector::clear() +{ + if (mSize != 0) + dMemset( mBits, 0x00, getByteSize() ); +} + +inline void BitVector::copy( const BitVector &from ) +{ + _resize( from.getSize(), false ); + if (mSize != 0) + dMemcpy( mBits, from.getBits(), getByteSize() ); +} + +inline void BitVector::set() +{ + if (mSize != 0) + dMemset(mBits, 0xFF, getByteSize() ); +} + +inline void BitVector::set(U32 bit) +{ + AssertFatal(bit < mSize, "BitVector::set - Error, out of range bit!"); + + mBits[bit >> 3] |= U8(1 << (bit & 0x7)); +} + +inline void BitVector::clear(U32 bit) +{ + AssertFatal(bit < mSize, "BitVector::clear - Error, out of range bit!"); + mBits[bit >> 3] &= U8(~(1 << (bit & 0x7))); +} + +inline bool BitVector::test(U32 bit) const +{ + AssertFatal(bit < mSize, "BitVector::test - Error, out of range bit!"); + return (mBits[bit >> 3] & U8(1 << (bit & 0x7))) != 0; +} + +#endif //_BITVECTOR_H_ diff --git a/core/bitVectorW.h b/core/bitVectorW.h new file mode 100644 index 0000000..7f29024 --- /dev/null +++ b/core/bitVectorW.h @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITVECTORW_H_ +#define _BITVECTORW_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +/// @see BitVector +class BitVectorW +{ + U32 mNumEntries; + U32 mBitWidth; + U32 mBitMask; + U8 * mDataPtr; + + public: + BitVectorW() {mDataPtr=NULL; setDims(0,0);} + ~BitVectorW() {if(mDataPtr) delete [] mDataPtr;} + + U32 bitWidth() const {return mBitWidth;} + U32 numEntries() const {return mNumEntries;} + U8 * dataPtr() const {return mDataPtr;} + + U32 getU17(U32 idx) const; // get and set for bit widths + void setU17(U32 idx, U32 val); // of 17 or less + U32 numBytes() const; + void setDims(U32 sz, U32 w); +}; + +//------------------------------------------------------------------------------------- + +inline U32 BitVectorW::numBytes() const +{ + if (mNumEntries > 0) + return (mBitWidth * mNumEntries) + 32 >> 3; + else + return 0; +} + +// Alloc the data - note it does work for a bit width of zero (lookups return zero) +inline void BitVectorW::setDims(U32 size, U32 width) +{ + if (mDataPtr) + delete [] mDataPtr; + + if (size > 0 && width <= 17) + { + mBitWidth = width; + mNumEntries = size; + mBitMask = (1 << width) - 1; + U32 dataSize = numBytes(); + mDataPtr = new U8 [dataSize]; + dMemset(mDataPtr, 0, dataSize); + } + else + { + mDataPtr = NULL; + mBitWidth = mBitMask = mNumEntries = 0; + } +} + +//------------------------------------------------------------------------------------- +// For coding ease, the get and set methods might read or write an extra byte or two. +// If more or less max bit width is ever needed, add or remove the x[] expressions. + +inline U32 BitVectorW::getU17(U32 i) const +{ + if (mDataPtr) { + register U8 * x = &mDataPtr[(i *= mBitWidth) >> 3]; + return (U32(*x) + (U32(x[1])<<8) + (U32(x[2])<<16) >> (i&7)) & mBitMask; + } + return 0; +} + +inline void BitVectorW::setU17(U32 i, U32 value) +{ + if (mDataPtr) { + register U8 * x = &mDataPtr[(i *= mBitWidth) >> 3]; + register U32 mask = mBitMask << (i &= 7); + x[0] = (x[0] & (~mask >> 0)) | ((value <<= i) & (mask >> 0)); + x[1] = (x[1] & (~mask >> 8)) | ((value >> 8) & (mask >> 8)); + x[2] = (x[2] & (~mask >> 16)) | ((value >> 16) & (mask >> 16)); + } +} + +#endif //_BITVECTORW_H_ diff --git a/core/color.cpp b/core/color.cpp new file mode 100644 index 0000000..99beed4 --- /dev/null +++ b/core/color.cpp @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/color.h" + +const ColorF ColorF::ZERO( 0, 0, 0, 0 ); +const ColorF ColorF::ONE( 1, 1, 1, 1 ); +const ColorF ColorF::WHITE( 1, 1, 1 ); +const ColorF ColorF::BLACK( 0, 0, 0 ); +const ColorF ColorF::RED( 1, 0, 0 ); +const ColorF ColorF::GREEN( 0, 1, 0 ); +const ColorF ColorF::BLUE( 0, 0, 1 ); + +const ColorI ColorI::ZERO( 0, 0, 0, 0 ); +const ColorI ColorI::ONE( 255, 255, 255, 255 ); +const ColorI ColorI::WHITE( 255, 255, 255 ); +const ColorI ColorI::BLACK( 0, 0, 0 ); +const ColorI ColorI::RED( 255, 0, 0 ); +const ColorI ColorI::GREEN( 0, 255, 0 ); +const ColorI ColorI::BLUE( 0, 0, 255 ); diff --git a/core/color.h b/core/color.h new file mode 100644 index 0000000..1b274bd --- /dev/null +++ b/core/color.h @@ -0,0 +1,590 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLOR_H_ +#define _COLOR_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MPOINT4_H_ +#include "math/mPoint4.h" +#endif + +class ColorI; + + +class ColorF +{ + public: + F32 red; + F32 green; + F32 blue; + F32 alpha; + + public: + ColorF() { } + ColorF(const ColorF& in_rCopy); + ColorF(const F32 in_r, + const F32 in_g, + const F32 in_b, + const F32 in_a = 1.0f); + + void set(const F32 in_r, + const F32 in_g, + const F32 in_b, + const F32 in_a = 1.0f); + + ColorF& operator*=(const ColorF& in_mul); // Can be useful for lighting + ColorF operator*(const ColorF& in_mul) const; + ColorF& operator+=(const ColorF& in_rAdd); + ColorF operator+(const ColorF& in_rAdd) const; + ColorF& operator-=(const ColorF& in_rSub); + ColorF operator-(const ColorF& in_rSub) const; + + ColorF& operator*=(const F32 in_mul); + ColorF operator*(const F32 in_mul) const; + ColorF& operator/=(const F32 in_div); + ColorF operator/(const F32 in_div) const; + + ColorF operator-() const; + + bool operator==(const ColorF&) const; + bool operator!=(const ColorF&) const; + + operator const F32*() const { return &red; } + + operator Point3F() const { return Point3F( red, green, blue ); } + operator Point4F() const { return Point4F( red, green, blue, alpha ); } + + U32 getARGBPack() const; + U32 getRGBAPack() const; + U32 getABGRPack() const; + + operator ColorI() const; + + void interpolate(const ColorF& in_rC1, + const ColorF& in_rC2, + const F32 in_factor); + + bool isValidColor() const { return (red >= 0.0f && red <= 1.0f) && + (green >= 0.0f && green <= 1.0f) && + (blue >= 0.0f && blue <= 1.0f) && + (alpha >= 0.0f && alpha <= 1.0f); } + void clamp(); + + static const ColorF ZERO; + static const ColorF ONE; + static const ColorF WHITE; + static const ColorF BLACK; + static const ColorF RED; + static const ColorF GREEN; + static const ColorF BLUE; +}; + + +//-------------------------------------- ColorI's are missing some of the operations +// present in ColorF since they cannot recover +// properly from over/underflow. +class ColorI +{ + public: + U8 red; + U8 green; + U8 blue; + U8 alpha; + + public: + ColorI() { } + ColorI(const ColorI& in_rCopy); + ColorI(const U8 in_r, + const U8 in_g, + const U8 in_b, + const U8 in_a = U8(255)); + ColorI(const ColorI& in_rCopy, const U8 in_a); + + void set(const U8 in_r, + const U8 in_g, + const U8 in_b, + const U8 in_a = U8(255)); + + void set(const ColorI& in_rCopy, + const U8 in_a); + + ColorI& operator*=(const F32 in_mul); + ColorI operator*(const F32 in_mul) const; + + ColorI operator+(const ColorI& in_rAdd) const; + ColorI& operator+=(const ColorI& in_rAdd); + + ColorI& operator*=(const S32 in_mul); + ColorI& operator/=(const S32 in_mul); + ColorI operator*(const S32 in_mul) const; + ColorI operator/(const S32 in_mul) const; + + bool operator==(const ColorI&) const; + bool operator!=(const ColorI&) const; + + void interpolate(const ColorI& in_rC1, + const ColorI& in_rC2, + const F32 in_factor); + + U32 getARGBPack() const; + U32 getRGBAPack() const; + U32 getABGRPack() const; + + U32 getBGRPack() const; + U32 getRGBPack() const; + + U32 getRGBEndian() const; + U32 getARGBEndian() const; + + U16 get565() const; + U16 get4444() const; + + operator ColorF() const; + + operator const U8*() const { return &red; } + + static const ColorI ZERO; + static const ColorI ONE; + static const ColorI WHITE; + static const ColorI BLACK; + static const ColorI RED; + static const ColorI GREEN; + static const ColorI BLUE; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- INLINES (ColorF) +// +inline void ColorF::set(const F32 in_r, + const F32 in_g, + const F32 in_b, + const F32 in_a) +{ + red = in_r; + green = in_g; + blue = in_b; + alpha = in_a; +} + +inline ColorF::ColorF(const ColorF& in_rCopy) +{ + red = in_rCopy.red; + green = in_rCopy.green; + blue = in_rCopy.blue; + alpha = in_rCopy.alpha; +} + +inline ColorF::ColorF(const F32 in_r, + const F32 in_g, + const F32 in_b, + const F32 in_a) +{ + set(in_r, in_g, in_b, in_a); +} + +inline ColorF& ColorF::operator*=(const ColorF& in_mul) +{ + red *= in_mul.red; + green *= in_mul.green; + blue *= in_mul.blue; + alpha *= in_mul.alpha; + + return *this; +} + +inline ColorF ColorF::operator*(const ColorF& in_mul) const +{ + return ColorF(red * in_mul.red, + green * in_mul.green, + blue * in_mul.blue, + alpha * in_mul.alpha); +} + +inline ColorF& ColorF::operator+=(const ColorF& in_rAdd) +{ + red += in_rAdd.red; + green += in_rAdd.green; + blue += in_rAdd.blue; + alpha += in_rAdd.alpha; + + return *this; +} + +inline ColorF ColorF::operator+(const ColorF& in_rAdd) const +{ + return ColorF(red + in_rAdd.red, + green + in_rAdd.green, + blue + in_rAdd.blue, + alpha + in_rAdd.alpha); +} + +inline ColorF& ColorF::operator-=(const ColorF& in_rSub) +{ + red -= in_rSub.red; + green -= in_rSub.green; + blue -= in_rSub.blue; + alpha -= in_rSub.alpha; + + return *this; +} + +inline ColorF ColorF::operator-(const ColorF& in_rSub) const +{ + return ColorF(red - in_rSub.red, + green - in_rSub.green, + blue - in_rSub.blue, + alpha - in_rSub.alpha); +} + +inline ColorF& ColorF::operator*=(const F32 in_mul) +{ + red *= in_mul; + green *= in_mul; + blue *= in_mul; + alpha *= in_mul; + + return *this; +} + +inline ColorF ColorF::operator*(const F32 in_mul) const +{ + return ColorF(red * in_mul, + green * in_mul, + blue * in_mul, + alpha * in_mul); +} + +inline ColorF& ColorF::operator/=(const F32 in_div) +{ + AssertFatal(in_div != 0.0f, "Error, div by zero..."); + F32 inv = 1.0f / in_div; + + red *= inv; + green *= inv; + blue *= inv; + alpha *= inv; + + return *this; +} + +inline ColorF ColorF::operator/(const F32 in_div) const +{ + AssertFatal(in_div != 0.0f, "Error, div by zero..."); + F32 inv = 1.0f / in_div; + + return ColorF(red * inv, + green * inv, + blue * inv, + alpha * inv); +} + +inline ColorF ColorF::operator-() const +{ + return ColorF(-red, -green, -blue, -alpha); +} + +inline bool ColorF::operator==(const ColorF& in_Cmp) const +{ + return (red == in_Cmp.red && green == in_Cmp.green && blue == in_Cmp.blue && alpha == in_Cmp.alpha); +} + +inline bool ColorF::operator!=(const ColorF& in_Cmp) const +{ + return (red != in_Cmp.red || green != in_Cmp.green || blue != in_Cmp.blue || alpha != in_Cmp.alpha); +} + +inline U32 ColorF::getARGBPack() const +{ + return (U32(alpha * 255.0f + 0.5) << 24) | + (U32(red * 255.0f + 0.5) << 16) | + (U32(green * 255.0f + 0.5) << 8) | + (U32(blue * 255.0f + 0.5) << 0); +} + +inline U32 ColorF::getRGBAPack() const +{ + return ( U32( red * 255.0f + 0.5) << 0 ) | + ( U32( green * 255.0f + 0.5) << 8 ) | + ( U32( blue * 255.0f + 0.5) << 16 ) | + ( U32( alpha * 255.0f + 0.5) << 24 ); +} + +inline U32 ColorF::getABGRPack() const +{ + return (U32(alpha * 255.0f + 0.5) << 24) | + (U32(blue * 255.0f + 0.5) << 16) | + (U32(green * 255.0f + 0.5) << 8) | + (U32(red * 255.0f + 0.5) << 0); + +} + +inline void ColorF::interpolate(const ColorF& in_rC1, + const ColorF& in_rC2, + const F32 in_factor) +{ + F32 f2 = 1.0f - in_factor; + red = (in_rC1.red * f2) + (in_rC2.red * in_factor); + green = (in_rC1.green * f2) + (in_rC2.green * in_factor); + blue = (in_rC1.blue * f2) + (in_rC2.blue * in_factor); + alpha = (in_rC1.alpha * f2) + (in_rC2.alpha * in_factor); +} + +inline void ColorF::clamp() +{ + if (red > 1.0f) + red = 1.0f; + else if (red < 0.0f) + red = 0.0f; + + if (green > 1.0f) + green = 1.0f; + else if (green < 0.0f) + green = 0.0f; + + if (blue > 1.0f) + blue = 1.0f; + else if (blue < 0.0f) + blue = 0.0f; + + if (alpha > 1.0f) + alpha = 1.0f; + else if (alpha < 0.0f) + alpha = 0.0f; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- INLINES (ColorI) +// +inline void ColorI::set(const U8 in_r, + const U8 in_g, + const U8 in_b, + const U8 in_a) +{ + red = in_r; + green = in_g; + blue = in_b; + alpha = in_a; +} + +inline void ColorI::set(const ColorI& in_rCopy, + const U8 in_a) +{ + red = in_rCopy.red; + green = in_rCopy.green; + blue = in_rCopy.blue; + alpha = in_a; +} + +inline ColorI::ColorI(const ColorI& in_rCopy) +{ + red = in_rCopy.red; + green = in_rCopy.green; + blue = in_rCopy.blue; + alpha = in_rCopy.alpha; +} + +inline ColorI::ColorI(const U8 in_r, + const U8 in_g, + const U8 in_b, + const U8 in_a) +{ + set(in_r, in_g, in_b, in_a); +} + +inline ColorI::ColorI(const ColorI& in_rCopy, + const U8 in_a) +{ + set(in_rCopy, in_a); +} + +inline ColorI& ColorI::operator*=(const F32 in_mul) +{ + red = U8((F32(red) * in_mul) + 0.5f); + green = U8((F32(green) * in_mul) + 0.5f); + blue = U8((F32(blue) * in_mul) + 0.5f); + alpha = U8((F32(alpha) * in_mul) + 0.5f); + + return *this; +} + +inline ColorI& ColorI::operator*=(const S32 in_mul) +{ + red = red * in_mul; + green = green * in_mul; + blue = blue * in_mul; + alpha = alpha * in_mul; + + return *this; +} + +inline ColorI& ColorI::operator/=(const S32 in_mul) +{ + red = red / in_mul; + green = green / in_mul; + blue = blue / in_mul; + alpha = alpha / in_mul; + + return *this; +} + +inline ColorI ColorI::operator+(const ColorI &in_add) const +{ + ColorI tmp; + + tmp.red = red + in_add.red; + tmp.green = green + in_add.green; + tmp.blue = blue + in_add.blue; + tmp.alpha = alpha + in_add.alpha; + + return tmp; +} + +inline ColorI ColorI::operator*(const F32 in_mul) const +{ + ColorI temp(*this); + temp *= in_mul; + return temp; +} + +inline ColorI ColorI::operator*(const S32 in_mul) const +{ + ColorI temp(*this); + temp *= in_mul; + return temp; +} + +inline ColorI ColorI::operator/(const S32 in_mul) const +{ + ColorI temp(*this); + temp /= in_mul; + return temp; +} + +inline bool ColorI::operator==(const ColorI& in_Cmp) const +{ + return (red == in_Cmp.red && green == in_Cmp.green && blue == in_Cmp.blue && alpha == in_Cmp.alpha); +} + +inline bool ColorI::operator!=(const ColorI& in_Cmp) const +{ + return (red != in_Cmp.red || green != in_Cmp.green || blue != in_Cmp.blue || alpha != in_Cmp.alpha); +} + +inline ColorI& ColorI::operator+=(const ColorI& in_rAdd) +{ + red += in_rAdd.red; + green += in_rAdd.green; + blue += in_rAdd.blue; + alpha += in_rAdd.alpha; + + return *this; +} + +inline void ColorI::interpolate(const ColorI& in_rC1, + const ColorI& in_rC2, + const F32 in_factor) +{ + F32 f2= 1.0f - in_factor; + red = U8(((F32(in_rC1.red) * f2) + (F32(in_rC2.red) * in_factor)) + 0.5f); + green = U8(((F32(in_rC1.green) * f2) + (F32(in_rC2.green) * in_factor)) + 0.5f); + blue = U8(((F32(in_rC1.blue) * f2) + (F32(in_rC2.blue) * in_factor)) + 0.5f); + alpha = U8(((F32(in_rC1.alpha) * f2) + (F32(in_rC2.alpha) * in_factor)) + 0.5f); +} + +inline U32 ColorI::getARGBPack() const +{ + return (U32(alpha) << 24) | + (U32(red) << 16) | + (U32(green) << 8) | + (U32(blue) << 0); +} + +inline U32 ColorI::getRGBAPack() const +{ + return ( U32( red ) << 0 ) | + ( U32( green ) << 8 ) | + ( U32( blue ) << 16 ) | + ( U32( alpha ) << 24 ); +} + +inline U32 ColorI::getABGRPack() const +{ + return (U32(alpha) << 24) | + (U32(blue) << 16) | + (U32(green) << 8) | + (U32(red) << 0); +} + + +inline U32 ColorI::getBGRPack() const +{ + return (U32(blue) << 16) | + (U32(green) << 8) | + (U32(red) << 0); +} + +inline U32 ColorI::getRGBPack() const +{ + return (U32(red) << 16) | + (U32(green) << 8) | + (U32(blue) << 0); +} + +inline U32 ColorI::getRGBEndian() const +{ +#if defined(TORQUE_BIG_ENDIAN) + return(getRGBPack()); +#else + return(getBGRPack()); +#endif +} + +inline U32 ColorI::getARGBEndian() const +{ +#if defined(TORQUE_BIG_ENDIAN) + return(getABGRPack()); +#else + return(getARGBPack()); +#endif +} + +inline U16 ColorI::get565() const +{ + return U16((U16(red >> 3) << 11) | + (U16(green >> 2) << 5) | + (U16(blue >> 3) << 0)); +} + +inline U16 ColorI::get4444() const +{ + return U16(U16(U16(alpha >> 4) << 12) | + U16(U16(red >> 4) << 8) | + U16(U16(green >> 4) << 4) | + U16(U16(blue >> 4) << 0)); +} + +//-------------------------------------- INLINE CONVERSION OPERATORS +inline ColorF::operator ColorI() const +{ + return ColorI(U8(red * 255.0f + 0.5), + U8(green * 255.0f + 0.5), + U8(blue * 255.0f + 0.5), + U8(alpha * 255.0f + 0.5)); +} + +inline ColorI::operator ColorF() const +{ + const F32 inv255 = 1.0f / 255.0f; + + return ColorF(F32(red) * inv255, + F32(green) * inv255, + F32(blue) * inv255, + F32(alpha) * inv255); +} + +#endif //_COLOR_H_ diff --git a/core/crc.cpp b/core/crc.cpp new file mode 100644 index 0000000..38cbc92 --- /dev/null +++ b/core/crc.cpp @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/crc.h" + +#include "core/stream/stream.h" + +//----------------------------------------------------------------------------- +// simple crc function - generates lookup table on first call + +static U32 crcTable[256]; +static bool crcTableValid; + +static void calculateCRCTable() +{ + U32 val; + + for(S32 i = 0; i < 256; i++) + { + val = i; + for(S32 j = 0; j < 8; j++) + { + if(val & 0x01) + val = 0xedb88320 ^ (val >> 1); + else + val = val >> 1; + } + crcTable[i] = val; + } + + crcTableValid = true; +} + + +//----------------------------------------------------------------------------- + +U32 CRC::calculateCRC(const void * buffer, S32 len, U32 crcVal ) +{ + // check if need to generate the crc table + if(!crcTableValid) + calculateCRCTable(); + + // now calculate the crc + char * buf = (char*)buffer; + for(S32 i = 0; i < len; i++) + crcVal = crcTable[(crcVal ^ buf[i]) & 0xff] ^ (crcVal >> 8); + return(crcVal); +} + +U32 CRC::calculateCRCStream(Stream *stream, U32 crcVal ) +{ + // check if need to generate the crc table + if(!crcTableValid) + calculateCRCTable(); + + // now calculate the crc + stream->setPosition(0); + S32 len = stream->getStreamSize(); + U8 buf[4096]; + + S32 segCount = (len + 4095) / 4096; + + for(S32 j = 0; j < segCount; j++) + { + S32 slen = getMin(4096, len - (j * 4096)); + stream->read(slen, buf); + crcVal = CRC::calculateCRC(buf, slen, crcVal); + } + stream->setPosition(0); + return(crcVal); +} diff --git a/core/crc.h b/core/crc.h new file mode 100644 index 0000000..3fb191f --- /dev/null +++ b/core/crc.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CRC_H_ +#define _CRC_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + + +class Stream; + +namespace CRC +{ + /// Initial value for CRCs + const U32 INITIAL_CRC_VALUE = 0xFFFFFFFF; + + /// Value XORd with the CRC to post condition it (ones complement) + const U32 CRC_POSTCOND_VALUE = 0xFFFFFFFF; + + /// An invalid CRC + const U32 INVALID_CRC = 0xFFFFFFFF; + + U32 calculateCRC(const void * buffer, S32 len, U32 crcVal = INITIAL_CRC_VALUE); + U32 calculateCRCStream(Stream *stream, U32 crcVal = INITIAL_CRC_VALUE); +} + +#endif + diff --git a/core/dataChunker.cpp b/core/dataChunker.cpp new file mode 100644 index 0000000..e15b596 --- /dev/null +++ b/core/dataChunker.cpp @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dataChunker.h" + + +//---------------------------------------------------------------------------- + +DataChunker::DataChunker(S32 size) +{ + mChunkSize = size; + mCurBlock = NULL; +} + +DataChunker::~DataChunker() +{ + freeBlocks(); +} + +void *DataChunker::alloc(S32 size) +{ + if (size > mChunkSize) + { + DataBlock * temp = new DataBlock(size); + if (mCurBlock) + { + temp->next = mCurBlock->next; + mCurBlock->next = temp; + } + else + { + mCurBlock = temp; + temp->curIndex = mChunkSize; + } + return temp->data; + } + + if(!mCurBlock || size + mCurBlock->curIndex > mChunkSize) + { + DataBlock *temp = new DataBlock(mChunkSize); + temp->next = mCurBlock; + temp->curIndex = 0; + mCurBlock = temp; + } + + void *ret = mCurBlock->data + mCurBlock->curIndex; + mCurBlock->curIndex += (size + 3) & ~3; // dword align + return ret; +} + +DataChunker::DataBlock::DataBlock(S32 size) +{ + data = new U8[size]; +} + +DataChunker::DataBlock::~DataBlock() +{ + delete[] data; +} + +void DataChunker::freeBlocks(bool keepOne) +{ + while(mCurBlock && mCurBlock->next) + { + DataBlock *temp = mCurBlock->next; + delete mCurBlock; + mCurBlock = temp; + } + if (!keepOne) + { + delete mCurBlock; + mCurBlock = NULL; + } + else if (mCurBlock) + mCurBlock->curIndex = 0; +} + diff --git a/core/dataChunker.h b/core/dataChunker.h new file mode 100644 index 0000000..4930b13 --- /dev/null +++ b/core/dataChunker.h @@ -0,0 +1,277 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DATACHUNKER_H_ +#define _DATACHUNKER_H_ + +#ifndef _PLATFORM_H_ +# include "platform/platform.h" +#endif + +//---------------------------------------------------------------------------- +/// Implements a chunked data allocator. +/// +/// Calling new/malloc all the time is a time consuming operation. Therefore, +/// we provide the DataChunker, which allocates memory in blocks of +/// chunkSize (by default 16k, see ChunkSize, though it can be set in +/// the constructor), then doles it out as requested, in chunks of up to +/// chunkSize in size. +/// +/// It will assert if you try to get more than ChunkSize bytes at a time, +/// and it deals with the logic of allocating new blocks and giving out +/// word-aligned chunks. +/// +/// Note that new/free/realloc WILL NOT WORK on memory gotten from the +/// DataChunker. This also only grows (you can call freeBlocks to deallocate +/// and reset things). +class DataChunker +{ +public: + enum { + ChunkSize = 16376 ///< Default size of each DataBlock page in the DataChunker + }; + + /// Return a pointer to a chunk of memory from a pre-allocated block. + /// + /// This memory goes away when you call freeBlocks. + /// + /// This memory is word-aligned. + /// @param size Size of chunk to return. This must be less than chunkSize or else + /// an assertion will occur. + void *alloc(S32 size); + + /// Free all allocated memory blocks. + /// + /// This invalidates all pointers returned from alloc(). + void freeBlocks(bool keepOne = false); + + /// Initialize using blocks of a given size. + /// + /// One new block is allocated at constructor-time. + /// + /// @param size Size in bytes of the space to allocate for each block. + DataChunker(S32 size=ChunkSize); + ~DataChunker(); + + /// Swaps the memory allocated in one data chunker for another. This can be used to implement + /// packing of memory stored in a DataChunker. + void swap(DataChunker &d) + { + DataBlock *temp = d.mCurBlock; + d.mCurBlock = mCurBlock; + mCurBlock = temp; + } + +private: + /// Block of allocated memory. + /// + /// This has nothing to do with datablocks as used in the rest of Torque. + struct DataBlock + { + DataBlock *next; ///< linked list pointer to the next DataBlock for this chunker + U8 *data; ///< allocated pointer for the base of this page + S32 curIndex; ///< current allocation point within this DataBlock + DataBlock(S32 size); + ~DataBlock(); + }; + + DataBlock *mCurBlock; ///< current page we're allocating data from. If the + ///< data size request is greater than the memory space currently + ///< available in the current page, a new page will be allocated. + S32 mChunkSize; ///< The size allocated for each page in the DataChunker +}; + +//---------------------------------------------------------------------------- + +template +class Chunker: private DataChunker +{ +public: + Chunker(S32 size = DataChunker::ChunkSize) : DataChunker(size) {}; + T* alloc() { return reinterpret_cast(DataChunker::alloc(S32(sizeof(T)))); } + void clear() { freeBlocks(); } +}; + +//---------------------------------------------------------------------------- +/// This class is similar to the Chunker<> class above. But it allows for multiple +/// types of structs to be stored. +/// CodeReview: This could potentially go into DataChunker directly, but I wasn't sure if +/// CodeReview: That would be polluting it. BTR +class MultiTypedChunker : private DataChunker +{ +public: + MultiTypedChunker(S32 size = DataChunker::ChunkSize) : DataChunker(size) {}; + + /// Use like so: MyType* t = chunker.alloc(); + template + T* alloc() { return reinterpret_cast(DataChunker::alloc(S32(sizeof(T)))); } + void clear() { freeBlocks(true); } +}; + +//---------------------------------------------------------------------------- + +/// Templatized data chunker class with proper construction and destruction of its elements. +/// +/// DataChunker just allocates space. This subclass actually constructs/destructs the +/// elements. This class is appropriate for more complex classes. +template +class ClassChunker: private DataChunker +{ +public: + ClassChunker(S32 size = DataChunker::ChunkSize) : DataChunker(size) + { + mElementSize = getMax(U32(sizeof(T)), U32(sizeof(T *))); + mFreeListHead = NULL; + } + + /// Allocates and properly constructs in place a new element. + T *alloc() + { + if(mFreeListHead == NULL) + return constructInPlace(reinterpret_cast(DataChunker::alloc(mElementSize))); + T* ret = mFreeListHead; + mFreeListHead = *(reinterpret_cast(mFreeListHead)); + return constructInPlace(ret); + } + + /// Properly destructs and frees an element allocated with the alloc method. + void free(T* elem) + { + destructInPlace(elem); + *(reinterpret_cast(elem)) = mFreeListHead; + mFreeListHead = elem; + } + + void freeBlocks() { DataChunker::freeBlocks(); } + +private: + S32 mElementSize; ///< the size of each element, or the size of a pointer, whichever is greater + T *mFreeListHead; ///< a pointer to a linked list of freed elements for reuse +}; + +//---------------------------------------------------------------------------- + +template +class FreeListChunker +{ +public: + FreeListChunker(DataChunker *inChunker) + : mChunker( inChunker ), + mOwnChunker( false ), + mFreeListHead( NULL ) + { + mElementSize = getMax(U32(sizeof(T)), U32(sizeof(T *))); + } + + FreeListChunker(S32 size = DataChunker::ChunkSize) + : mFreeListHead( NULL ) + { + mChunker = new DataChunker( size ); + mOwnChunker = true; + + mElementSize = getMax(U32(sizeof(T)), U32(sizeof(T *))); + } + + ~FreeListChunker() + { + if ( mOwnChunker ) + delete mChunker; + } + + T *alloc() + { + if(mFreeListHead == NULL) + return reinterpret_cast(mChunker->alloc(mElementSize)); + T* ret = mFreeListHead; + mFreeListHead = *(reinterpret_cast(mFreeListHead)); + return ret; + } + + void free(T* elem) + { + *(reinterpret_cast(elem)) = mFreeListHead; + mFreeListHead = elem; + } + + // Allow people to free all their memory if they want. + void freeBlocks() + { + mChunker->freeBlocks(); + + // We have to terminate the freelist as well or else we'll run + // into crazy unused memory. + mFreeListHead = NULL; + } + +private: + DataChunker *mChunker; + bool mOwnChunker; + + S32 mElementSize; + T *mFreeListHead; +}; + + +class FreeListChunkerUntyped +{ +public: + FreeListChunkerUntyped(U32 inElementSize, DataChunker *inChunker) + : mChunker( inChunker ), + mOwnChunker( false ), + mElementSize( inElementSize ), + mFreeListHead( NULL ) + { + } + + FreeListChunkerUntyped(U32 inElementSize, S32 size = DataChunker::ChunkSize) + : mElementSize( inElementSize ), + mFreeListHead( NULL ) + { + mChunker = new DataChunker( size ); + mOwnChunker = true; + } + + ~FreeListChunkerUntyped() + { + if ( mOwnChunker ) + delete mChunker; + } + + void *alloc() + { + if(mFreeListHead == NULL) + return mChunker->alloc(mElementSize); + + void *ret = mFreeListHead; + mFreeListHead = *(reinterpret_cast(mFreeListHead)); + return ret; + } + + void free(void* elem) + { + *(reinterpret_cast(elem)) = mFreeListHead; + mFreeListHead = elem; + } + + // Allow people to free all their memory if they want. + void freeBlocks() + { + mChunker->freeBlocks(); + + // We have to terminate the freelist as well or else we'll run + // into crazy unused memory. + mFreeListHead = NULL; + } + + U32 getElementSize() const { return mElementSize; } + +private: + DataChunker *mChunker; + bool mOwnChunker; + + const U32 mElementSize; + void *mFreeListHead; +}; +#endif diff --git a/core/dnet.cpp b/core/dnet.cpp new file mode 100644 index 0000000..24bebd7 --- /dev/null +++ b/core/dnet.cpp @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/bitStream.h" +#include "core/dnet.h" +#include "core/strings/stringFunctions.h" + +#include "console/consoleTypes.h" + + +bool gLogToConsole = false; + +S32 gNetBitsReceived = 0; + +enum NetPacketType +{ + DataPacket, + PingPacket, + AckPacket, + InvalidPacketType, +}; + +static const char *packetTypeNames[] = +{ + "DataPacket", + "PingPacket", + "AckPacket", +}; + +//----------------------------------------------------------------- +//----------------------------------------------------------------- +//----------------------------------------------------------------- +ConsoleFunction(DNetSetLogging, void, 2, 2, "(bool enabled)") +{ + TORQUE_UNUSED(argc); + gLogToConsole = dAtob(argv[1]); +} + +ConnectionProtocol::ConnectionProtocol() +{ + mLastSeqRecvd = 0; + mHighestAckedSeq = 0; + mLastSendSeq = 0; // start sending at 1 + mAckMask = 0; + mLastRecvAckAck = 0; +} +void ConnectionProtocol::buildSendPacketHeader(BitStream *stream, S32 packetType) +{ + S32 ackByteCount = ((mLastSeqRecvd - mLastRecvAckAck + 7) >> 3); + AssertFatal(ackByteCount <= 4, "Too few ack bytes!"); + + // S32 headerSize = 3 + ackByteCount; + + if(packetType == DataPacket) + mLastSendSeq++; + + stream->writeFlag(true); + stream->writeInt(mConnectSequence & 1, 1); + stream->writeInt(mLastSendSeq, 9); + stream->writeInt(mLastSeqRecvd, 9); + stream->writeInt(packetType, 2); + stream->writeInt(ackByteCount, 3); + stream->writeInt(mAckMask, ackByteCount * 8); + + // if we're resending this header, we can't advance the + // sequence recieved (in case this packet drops and the prev one + // goes through) + + if(gLogToConsole) + Con::printf("build hdr %d %d", mLastSendSeq, packetType); + + if(packetType == DataPacket) + mLastSeqRecvdAtSend[mLastSendSeq & 0x1F] = mLastSeqRecvd; +} + +void ConnectionProtocol::sendPingPacket() +{ + U8 buffer[16]; + BitStream bs(buffer, 16); + buildSendPacketHeader(&bs, PingPacket); + if(gLogToConsole) + Con::printf("send ping %d", mLastSendSeq); + + sendPacket(&bs); +} + +void ConnectionProtocol::sendAckPacket() +{ + U8 buffer[16]; + BitStream bs(buffer, 16); + buildSendPacketHeader(&bs, AckPacket); + if(gLogToConsole) + Con::printf("send ack %d", mLastSendSeq); + + sendPacket(&bs); +} + +// packets are read directly into the data portion of +// connection notify packets... makes the events easier to post into +// the system. + +void ConnectionProtocol::processRawPacket(BitStream *pstream) +{ + // read in the packet header: + + // Fixed packet header: 3 bytes + // + // 1 bit game packet flag + // 1 bit connect sequence + // 9 bits packet seq number + // 9 bits ackstart seq number + // 2 bits packet type + // 2 bits ack byte count + // + // type is: + // 00 data packet + // 01 ping packet + // 02 ack packet + + // next 1-4 bytes are ack flags + // + // header len is 4-9 bytes + // average case 4 byte header + + gNetBitsReceived = pstream->getStreamSize(); + + pstream->readFlag(); // get rid of the game info packet bit + U32 pkConnectSeqBit = pstream->readInt(1); + U32 pkSequenceNumber = pstream->readInt(9); + U32 pkHighestAck = pstream->readInt(9); + U32 pkPacketType = pstream->readInt(2); + S32 pkAckByteCount = pstream->readInt(3); + + // check connection sequence bit + if(pkConnectSeqBit != (mConnectSequence & 1)) + return; + + if(pkAckByteCount > 4 || pkPacketType >= InvalidPacketType) + return; + + S32 pkAckMask = pstream->readInt(8 * pkAckByteCount); + + // verify packet ordering and acking and stuff + // check if the 9-bit sequence is within the packet window + // (within 31 packets of the last received sequence number). + + pkSequenceNumber |= (mLastSeqRecvd & 0xFFFFFE00); + // account for wrap around + if(pkSequenceNumber < mLastSeqRecvd) + pkSequenceNumber += 0x200; + + if(pkSequenceNumber > mLastSeqRecvd + 31) + { + // the sequence number is outside the window... must be out of order + // discard. + return; + } + + pkHighestAck |= (mHighestAckedSeq & 0xFFFFFE00); + // account for wrap around + + if(pkHighestAck < mHighestAckedSeq) + pkHighestAck += 0x200; + + if(pkHighestAck > mLastSendSeq) + { + // the ack number is outside the window... must be an out of order + // packet, discard. + return; + } + + if(gLogToConsole) + { + for(U32 i = mLastSeqRecvd+1; i < pkSequenceNumber; i++) + Con::printf("Not recv %d", i); + Con::printf("Recv %d %s", pkSequenceNumber, packetTypeNames[pkPacketType]); + } + + // shift up the ack mask by the packet difference + // this essentially nacks all the packets dropped + + mAckMask <<= pkSequenceNumber - mLastSeqRecvd; + + // if this packet is a data packet (i.e. not a ping packet or an ack packet), ack it + if(pkPacketType == DataPacket) + mAckMask |= 1; + + // do all the notifies... + for(U32 i = mHighestAckedSeq+1; i <= pkHighestAck; i++) + { + bool packetTransmitSuccess = pkAckMask & (1 << (pkHighestAck - i)); + handleNotify(packetTransmitSuccess); + if(gLogToConsole) + Con::printf("Ack %d %d", i, packetTransmitSuccess); + + if(packetTransmitSuccess) + { + mLastRecvAckAck = mLastSeqRecvdAtSend[i & 0x1F]; + if(!mConnectionEstablished) + { + mConnectionEstablished = true; + handleConnectionEstablished(); + } + } + } + + // the other side knows more about its window than we do. + if(pkSequenceNumber - mLastRecvAckAck > 32) + mLastRecvAckAck = pkSequenceNumber - 32; + + mHighestAckedSeq = pkHighestAck; + + // first things first... + // ackback any pings or accept connects + + if(pkPacketType == PingPacket) + { + // send an ack to the other side + // the ack will have the same packet sequence as our last sent packet + // if the last packet we sent was the connection accepted packet + // we must resend that packet + sendAckPacket(); + } + keepAlive(); // notification that the connection is ok + + // note: handlePacket() may delete the connection if an error occurs. + if(mLastSeqRecvd != pkSequenceNumber) + { + mLastSeqRecvd = pkSequenceNumber; + if(pkPacketType == DataPacket) + handlePacket(pstream); + } +} + +bool ConnectionProtocol::windowFull() +{ + return mLastSendSeq - mHighestAckedSeq >= 30; +} + +void ConnectionProtocol::writeDemoStartBlock(ResizeBitStream *stream) +{ + for(U32 i = 0; i < 32; i++) + stream->write(mLastSeqRecvdAtSend[i]); + stream->write(mLastSeqRecvd); + stream->write(mHighestAckedSeq); + stream->write(mLastSendSeq); + stream->write(mAckMask); + stream->write(mConnectSequence); + stream->write(mLastRecvAckAck); + stream->write(mConnectionEstablished); +} + +bool ConnectionProtocol::readDemoStartBlock(BitStream *stream) +{ + for(U32 i = 0; i < 32; i++) + stream->read(&mLastSeqRecvdAtSend[i]); + stream->read(&mLastSeqRecvd); + stream->read(&mHighestAckedSeq); + stream->read(&mLastSendSeq); + stream->read(&mAckMask); + stream->read(&mConnectSequence); + stream->read(&mLastRecvAckAck); + stream->read(&mConnectionEstablished); + return true; +} diff --git a/core/dnet.h b/core/dnet.h new file mode 100644 index 0000000..a9014b8 --- /dev/null +++ b/core/dnet.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DNET_H_ +#define _DNET_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +#include "platform/platformNet.h" + +class BitStream; +class ResizeBitStream; + +/// The base class for Torque's networking protocol. +/// +/// This implements a sliding window connected message stream over an unreliable transport (UDP). It +/// provides a simple notify protocol to allow subclasses to be aware of what packets were sent +/// succesfully and which failed. +/// +/// Basically, a window size of 32 is provided, and each packet contains in the header a bitmask, +/// acknowledging the receipt (or failure to receive) of the last 32 packets. +/// +/// @see NetConnection, @ref NetProtocol +class ConnectionProtocol +{ +protected: + U32 mLastSeqRecvdAtSend[32]; + U32 mLastSeqRecvd; + U32 mHighestAckedSeq; + U32 mLastSendSeq; + U32 mAckMask; + U32 mConnectSequence; + U32 mLastRecvAckAck; + bool mConnectionEstablished; +public: + ConnectionProtocol(); + + void buildSendPacketHeader(BitStream *bstream, S32 packetType = 0); + + void sendPingPacket(); + void sendAckPacket(); + void setConnectionEstablished() { mConnectionEstablished = true; } + + bool windowFull(); + bool connectionEstablished(); + void setConnectSequence(U32 connectSeq) { mConnectSequence = connectSeq; } + + virtual void writeDemoStartBlock(ResizeBitStream *stream); + virtual bool readDemoStartBlock(BitStream *stream); + + virtual void processRawPacket(BitStream *bstream); + virtual Net::Error sendPacket(BitStream *bstream) = 0; + virtual void keepAlive() = 0; + virtual void handleConnectionEstablished() = 0; + virtual void handleNotify(bool recvd) = 0; + virtual void handlePacket(BitStream *bstream) = 0; +}; + +#endif diff --git a/core/fileObject.cpp b/core/fileObject.cpp new file mode 100644 index 0000000..037a90d --- /dev/null +++ b/core/fileObject.cpp @@ -0,0 +1,203 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/fileObject.h" + +IMPLEMENT_CONOBJECT(FileObject); + +bool FileObject::isEOF() +{ + return mCurPos == mBufferSize; +} + +FileObject::FileObject() +{ + mFileBuffer = NULL; + mBufferSize = 0; + mCurPos = 0; + stream = NULL; +} + +FileObject::~FileObject() +{ + SAFE_DELETE_ARRAY(mFileBuffer); + SAFE_DELETE(stream); +} + +void FileObject::close() +{ + SAFE_DELETE(stream); + SAFE_DELETE_ARRAY(mFileBuffer); + mFileBuffer = NULL; + mBufferSize = mCurPos = 0; +} + +bool FileObject::openForWrite(const char *fileName, const bool append) +{ + char buffer[1024]; + Con::expandScriptFilename( buffer, sizeof( buffer ), fileName ); + + close(); + + if((stream = FileStream::createAndOpen( fileName, append ? Torque::FS::File::WriteAppend : Torque::FS::File::Write )) == NULL) + return false; + + stream->setPosition( stream->getStreamSize() ); + return( true ); +} + +bool FileObject::openForRead(const char* /*fileName*/) +{ + AssertFatal(false, "Error, not yet implemented!"); + return false; +} + +bool FileObject::readMemory(const char *fileName) +{ + char buffer[1024]; + Con::expandScriptFilename( buffer, sizeof( buffer ), fileName ); + + close(); + + void *data = NULL; + U32 dataSize = 0; + Torque::FS::ReadFile(buffer, data, dataSize, true); + if(data == NULL) + return false; + + mBufferSize = dataSize; + mFileBuffer = (U8 *)data; + mCurPos = 0; + + return true; +} + +const U8 *FileObject::readLine() +{ + if(!mFileBuffer) + return (U8 *) ""; + + U32 tokPos = mCurPos; + + for(;;) + { + if(mCurPos == mBufferSize) + break; + + if(mFileBuffer[mCurPos] == '\r') + { + mFileBuffer[mCurPos++] = 0; + if(mFileBuffer[mCurPos] == '\n') + mCurPos++; + break; + } + + if(mFileBuffer[mCurPos] == '\n') + { + mFileBuffer[mCurPos++] = 0; + break; + } + + mCurPos++; + } + + return mFileBuffer + tokPos; +} + +void FileObject::peekLine( U8* line, S32 length ) +{ + if(!mFileBuffer) + { + line[0] = '\0'; + return; + } + + // Copy the line into the buffer. We can't do this like readLine because + // we can't modify the file buffer. + S32 i = 0; + U32 tokPos = mCurPos; + while( ( tokPos != mBufferSize ) && ( mFileBuffer[tokPos] != '\r' ) && ( mFileBuffer[tokPos] != '\n' ) && ( i < ( length - 1 ) ) ) + line[i++] = mFileBuffer[tokPos++]; + + line[i++] = '\0'; + + //if( i == length ) + //Con::warnf( "FileObject::peekLine - The line contents could not fit in the buffer (size %d). Truncating.", length ); +} + +void FileObject::writeLine(const U8 *line) +{ + stream->write(dStrlen((const char *) line), line); + stream->write(2, "\r\n"); +} + +void FileObject::writeObject( SimObject* object, const U8* objectPrepend ) +{ + if( objectPrepend == NULL ) + stream->write(2, "\r\n"); + else + stream->write(dStrlen((const char *) objectPrepend), objectPrepend ); + object->write( *stream, 0 ); +} + +ConsoleMethod( FileObject, openForRead, bool, 3, 3, "(string filename)") +{ + return object->readMemory(argv[2]); +} + +ConsoleMethod( FileObject, openForWrite, bool, 3, 3, "(string filename)") +{ + return object->openForWrite(argv[2]); +} + +ConsoleMethod( FileObject, openForAppend, bool, 3, 3, "(string filename)") +{ + return object->openForWrite(argv[2], true); +} + +ConsoleMethod( FileObject, isEOF, bool, 2, 2, "Are we at the end of the file?") +{ + return object->isEOF(); +} + +ConsoleMethod( FileObject, readLine, const char*, 2, 2, "Read a line from the file.") +{ + return (const char *) object->readLine(); +} + +ConsoleMethod( FileObject, peekLine, const char*, 2, 2, "Read a line from the file without moving the stream position.") +{ + char *line = Con::getReturnBuffer( 512 ); + object->peekLine( (U8*)line, 512 ); + return line; +} + +ConsoleMethod( FileObject, writeLine, void, 3, 3, "(string text)" + "Write a line to the file, if it was opened for writing.") +{ + object->writeLine((const U8 *) argv[2]); +} + +ConsoleMethod( FileObject, close, void, 2, 2, "Close the file.") +{ + object->close(); +} + +ConsoleMethod( FileObject, writeObject, void, 3, 4, "FileObject.writeObject(SimObject, object prepend)" ) +{ + SimObject* obj = Sim::findObject( argv[2] ); + if( !obj ) + { + Con::printf("FileObject::writeObject - Invalid Object!"); + return; + } + + char *objName = NULL; + if( argc == 4 ) + objName = (char*)argv[3]; + + object->writeObject( obj, (const U8*)objName ); +} + diff --git a/core/fileObject.h b/core/fileObject.h new file mode 100644 index 0000000..b33cba0 --- /dev/null +++ b/core/fileObject.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILEOBJECT_H_ +#define _FILEOBJECT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _FILESTREAM_H_ +#include "core/stream/fileStream.h" +#endif + +class FileObject : public SimObject +{ + typedef SimObject Parent; + U8 *mFileBuffer; + U32 mBufferSize; + U32 mCurPos; + FileStream *stream; +public: + FileObject(); + ~FileObject(); + + bool openForWrite(const char *fileName, const bool append = false); + bool openForRead(const char *fileName); + bool readMemory(const char *fileName); + const U8 *buffer() { return mFileBuffer; } + const U8 *readLine(); + void peekLine(U8 *line, S32 length); + bool isEOF(); + void writeLine(const U8 *line); + void close(); + void writeObject( SimObject* object, const U8* objectPrepend = NULL ); + + DECLARE_CONOBJECT(FileObject); +}; + +#endif diff --git a/core/fileio.h b/core/fileio.h new file mode 100644 index 0000000..5cc2dd1 --- /dev/null +++ b/core/fileio.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILEIO_H_ +#define _FILEIO_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +class File +{ +public: + /// What is the status of our file handle? + enum Status + { + Ok = 0, ///< Ok! + IOError, ///< Read or Write error + EOS, ///< End of Stream reached (mostly for reads) + IllegalCall, ///< An unsupported operation used. Always accompanied by AssertWarn + Closed, ///< Tried to operate on a closed stream (or detached filter) + UnknownError ///< Catchall + }; + + /// How are we accessing the file? + enum AccessMode + { + Read = 0, ///< Open for read only, starting at beginning of file. + Write = 1, ///< Open for write only, starting at beginning of file; will blast old contents of file. + ReadWrite = 2, ///< Open for read-write. + WriteAppend = 3 ///< Write-only, starting at end of file. + }; + + /// Flags used to indicate what we can do to the file. + enum Capability + { + FileRead = BIT(0), + FileWrite = BIT(1) + }; + +private: + void *handle; ///< Pointer to the file handle. + Status currentStatus; ///< Current status of the file (Ok, IOError, etc.). + U32 capability; ///< Keeps track of file capabilities. + + File(const File&); ///< This is here to disable the copy constructor. + File& operator=(const File&); ///< This is here to disable assignment. + +public: + File(); ///< Default constructor + virtual ~File(); ///< Destructor + + /// Opens a file for access using the specified AccessMode + /// + /// @returns The status of the file + Status open(const char *filename, const AccessMode openMode); + + /// Gets the current position in the file + /// + /// This is in bytes from the beginning of the file. + U32 getPosition() const; + + /// Sets the current position in the file. + /// + /// You can set either a relative or absolute position to go to in the file. + /// + /// @code + /// File *foo; + /// + /// ... set up file ... + /// + /// // Go to byte 32 in the file... + /// foo->setPosition(32); + /// + /// // Now skip back 20 bytes... + /// foo->setPosition(-20, false); + /// + /// // And forward 17... + /// foo->setPosition(17, false); + /// @endcode + /// + /// @returns The status of the file + Status setPosition(S32 position, bool absolutePos = true); + + /// Returns the size of the file + U32 getSize() const; + + /// Make sure everything that's supposed to be written to the file gets written. + /// + /// @returns The status of the file. + Status flush(); + + /// Closes the file + /// + /// @returns The status of the file. + Status close(); + + /// Gets the status of the file + Status getStatus() const; + + /// Reads "size" bytes from the file, and dumps data into "dst". + /// The number of actual bytes read is returned in bytesRead + /// @returns The status of the file + Status read(U32 size, char *dst, U32 *bytesRead = NULL); + + /// Writes "size" bytes into the file from the pointer "src". + /// The number of actual bytes written is returned in bytesWritten + /// @returns The status of the file + Status write(U32 size, const char *src, U32 *bytesWritten = NULL); + + /// Returns whether or not this file is capable of the given function. + bool hasCapability(Capability cap) const; + +protected: + Status setStatus(); ///< Called after error encountered. + Status setStatus(Status status); ///< Setter for the current status. +}; + +#endif // _FILE_IO_H_ diff --git a/core/filterStream.cpp b/core/filterStream.cpp new file mode 100644 index 0000000..b5661cb --- /dev/null +++ b/core/filterStream.cpp @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/filterStream.h" + +FilterStream::~FilterStream() +{ + // +} + +bool FilterStream::_read(const U32 in_numBytes, void* out_pBuffer) +{ + AssertFatal(getStream() != NULL, "Error no stream to pass to"); + + bool success = getStream()->read(in_numBytes, out_pBuffer); + + setStatus(getStream()->getStatus()); + return success; +} + + +bool FilterStream::_write(const U32, const void*) +{ + AssertFatal(false, "No writing allowed to filter"); + return false; +} + +bool FilterStream::hasCapability(const Capability in_streamCap) const +{ + // Fool the compiler. We know better... + FilterStream* ncThis = const_cast(this); + AssertFatal(ncThis->getStream() != NULL, "Error no stream to pass to"); + + return ncThis->getStream()->hasCapability(in_streamCap); +} + +U32 FilterStream::getPosition() const +{ + // Fool the compiler. We know better... + FilterStream* ncThis = const_cast(this); + AssertFatal(ncThis->getStream() != NULL, "Error no stream to pass to"); + + return ncThis->getStream()->getPosition(); +} + +bool FilterStream::setPosition(const U32 in_newPosition) +{ + AssertFatal(getStream() != NULL, "Error no stream to pass to"); + + return getStream()->setPosition(in_newPosition); +} + +U32 FilterStream::getStreamSize() +{ + AssertFatal(getStream() != NULL, "Error no stream to pass to"); + + return getStream()->getStreamSize(); +} + diff --git a/core/filterStream.h b/core/filterStream.h new file mode 100644 index 0000000..4183438 --- /dev/null +++ b/core/filterStream.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILTERSTREAM_H_ +#define _FILTERSTREAM_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif + +class FilterStream : public Stream +{ + public: + virtual ~FilterStream(); + + virtual bool attachStream(Stream* io_pSlaveStream) = 0; + virtual void detachStream() = 0; + virtual Stream* getStream() = 0; + + // Mandatory overrides. By default, these are simply passed to + // whatever is returned from getStream(); + protected: + bool _read(const U32 in_numBytes, void* out_pBuffer); + bool _write(const U32 in_numBytes, const void* in_pBuffer); + public: + bool hasCapability(const Capability) const; + + U32 getPosition() const; + bool setPosition(const U32 in_newPosition); + U32 getStreamSize(); +}; + +#endif //_FILTERSTREAM_H_ diff --git a/core/frameAllocator.cpp b/core/frameAllocator.cpp new file mode 100644 index 0000000..a6533f4 --- /dev/null +++ b/core/frameAllocator.cpp @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/frameAllocator.h" +#include "console/console.h" + +U8* FrameAllocator::smBuffer = NULL; +U32 FrameAllocator::smWaterMark = 0; +U32 FrameAllocator::smHighWaterMark = 0; + +#ifdef TORQUE_DEBUG +U32 FrameAllocator::smMaxFrameAllocation = 0; + +ConsoleFunction(getMaxFrameAllocation, S32, 1,1, "getMaxFrameAllocation();") +{ + return FrameAllocator::getMaxFrameAllocation(); +} +#endif diff --git a/core/frameAllocator.h b/core/frameAllocator.h new file mode 100644 index 0000000..05b994d --- /dev/null +++ b/core/frameAllocator.h @@ -0,0 +1,291 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FRAMEALLOCATOR_H_ +#define _FRAMEALLOCATOR_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +/// Temporary memory pool for per-frame allocations. +/// +/// In the course of rendering a frame, it is often necessary to allocate +/// many small chunks of memory, then free them all in a batch. For instance, +/// say we're allocating storage for some vertex calculations: +/// +/// @code +/// // Get FrameAllocator memory... +/// U32 waterMark = FrameAllocator::getWaterMark(); +/// F32 * ptr = (F32*)FrameAllocator::alloc(sizeof(F32)*2*targetMesh->vertsPerFrame); +/// +/// ... calculations ... +/// +/// // Free frameAllocator memory +/// FrameAllocator::setWaterMark(waterMark); +/// @endcode +class FrameAllocator +{ + static U8* smBuffer; + static U32 smHighWaterMark; + static U32 smWaterMark; + +#ifdef TORQUE_DEBUG + static U32 smMaxFrameAllocation; +#endif + + public: + inline static void init(const U32 frameSize); + inline static void destroy(); + + inline static void* alloc(const U32 allocSize); + + inline static void setWaterMark(const U32); + inline static U32 getWaterMark(); + inline static U32 getHighWaterMark(); + +#ifdef TORQUE_DEBUG + static U32 getMaxFrameAllocation() { return smMaxFrameAllocation; } +#endif +}; + +void FrameAllocator::init(const U32 frameSize) +{ +#ifdef FRAMEALLOCATOR_DEBUG_GUARD + AssertISV( false, "FRAMEALLOCATOR_DEBUG_GUARD has been removed because it allows non-contiguous memory allocation by the FrameAllocator, and this is *not* ok." ); +#endif + + AssertFatal(smBuffer == NULL, "Error, already initialized"); + smBuffer = new U8[frameSize]; + smWaterMark = 0; + smHighWaterMark = frameSize; +} + +void FrameAllocator::destroy() +{ + AssertFatal(smBuffer != NULL, "Error, not initialized"); + + delete [] smBuffer; + smBuffer = NULL; + smWaterMark = 0; + smHighWaterMark = 0; +} + + +void* FrameAllocator::alloc(const U32 allocSize) +{ + U32 _allocSize = allocSize; + + AssertFatal(smBuffer != NULL, "Error, no buffer!"); + AssertFatal(smWaterMark + _allocSize <= smHighWaterMark, "Error alloc too large, increase frame size!"); + + // Keep all frame allocator allocations aligned to DWORD boundries on the 360 + // Add 3, mask out the lower 3 bits. + smWaterMark = ( smWaterMark + ( TORQUE_BYTE_ALIGNMENT - 1 ) ) & (~( TORQUE_BYTE_ALIGNMENT - 1 )); + + // Sanity check. + AssertFatal( !( smWaterMark & ( TORQUE_BYTE_ALIGNMENT - 1 ) ), "Frame allocation is not on a 4-byte boundry." ); + + U8* p = &smBuffer[smWaterMark]; + smWaterMark += _allocSize; + +#ifdef TORQUE_DEBUG + if (smWaterMark > smMaxFrameAllocation) + smMaxFrameAllocation = smWaterMark; +#endif + + return p; +} + + +void FrameAllocator::setWaterMark(const U32 waterMark) +{ + AssertFatal(waterMark < smHighWaterMark, "Error, invalid waterMark"); + smWaterMark = waterMark; +} + +U32 FrameAllocator::getWaterMark() +{ + return smWaterMark; +} + +U32 FrameAllocator::getHighWaterMark() +{ + return smHighWaterMark; +} + +/// Helper class to deal with FrameAllocator usage. +/// +/// The purpose of this class is to make it simpler and more reliable to use the +/// FrameAllocator. Simply use it like this: +/// +/// @code +/// FrameAllocatorMarker mem; +/// +/// char *buff = (char*)mem.alloc(100); +/// @endcode +/// +/// When you leave the scope you defined the FrameAllocatorMarker in, it will +/// automatically restore the watermark on the FrameAllocator. In situations +/// with complex branches, this can be a significant headache remover, as you +/// don't have to remember to reset the FrameAllocator on every posssible branch. +class FrameAllocatorMarker +{ + U32 mMarker; + +public: + FrameAllocatorMarker() + { + mMarker = FrameAllocator::getWaterMark(); + } + + ~FrameAllocatorMarker() + { + FrameAllocator::setWaterMark(mMarker); + } + + void* alloc(const U32 allocSize) const + { + return FrameAllocator::alloc(allocSize); + } +}; + +/// Class for temporary variables that you want to allocate easily using +/// the FrameAllocator. For example: +/// @code +/// FrameTemp tempStr(32); // NOTE! This parameter is NOT THE SIZE IN BYTES. See constructor docs. +/// dStrcat( tempStr, SomeOtherString ); +/// tempStr[2] = 'l'; +/// Con::printf( tempStr ); +/// Con::printf( "Foo: %s", ~tempStr ); +/// @endcode +/// +/// This will automatically handle getting and restoring the watermark of the +/// FrameAllocator when it goes out of scope. You should notice the strange +/// operator in front of tempStr on the printf call. This is normally a unary +/// operator for ones-complement, but in this class it will simply return the +/// memory of the allocation. It's the same as doing (const char *)tempStr +/// in the above case. The reason why it is necessary for the second printf +/// and not the first is because the second one is taking a variable arg +/// list and so it isn't getting the cast so that it's cast operator can +/// properly return the memory instead of the FrameTemp object itself. +/// +/// @note It is important to note that this object is designed to just be a +/// temporary array of a dynamic size. Some wierdness may occur if you try +/// to perform crazy pointer stuff with it using regular operators on it. +template +class FrameTemp +{ +protected: + U32 mWaterMark; + T *mMemory; + U32 mNumObjectsInMemory; + +public: + /// Constructor will store the FrameAllocator watermark and allocate the memory off + /// of the FrameAllocator. + /// + /// @note It is important to note that, unlike the FrameAllocatorMarker and the + /// FrameAllocator itself, the argument to allocate is NOT the size in bytes, + /// doing: + /// @code + /// FrameTemp f64s(5); + /// @endcode + /// Is the same as + /// @code + /// F64 *f64s = new F64[5]; + /// @endcode + /// + /// @param count The number of objects to allocate + FrameTemp( const U32 count = 1 ) : mNumObjectsInMemory( count ) + { + AssertFatal( count > 0, "Allocating a FrameTemp with less than one instance" ); + mWaterMark = FrameAllocator::getWaterMark(); + mMemory = reinterpret_cast( FrameAllocator::alloc( sizeof( T ) * count ) ); + + for( int i = 0; i < mNumObjectsInMemory; i++ ) + constructInPlace( &mMemory[i] ); + } + + /// Destructor restores the watermark + ~FrameTemp() + { + // Call destructor + for( int i = 0; i < mNumObjectsInMemory; i++ ) + destructInPlace( &mMemory[i] ); + + FrameAllocator::setWaterMark( mWaterMark ); + } + + /// NOTE: This will return the memory, NOT perform a ones-complement + T* operator ~() { return mMemory; }; + /// NOTE: This will return the memory, NOT perform a ones-complement + const T* operator ~() const { return mMemory; }; + + /// NOTE: This will dereference the memory, NOT do standard unary plus behavior + T& operator +() { return *mMemory; }; + /// NOTE: This will dereference the memory, NOT do standard unary plus behavior + const T& operator +() const { return *mMemory; }; + + T& operator *() { return *mMemory; }; + const T& operator *() const { return *mMemory; }; + + T** operator &() { return &mMemory; }; + const T** operator &() const { return &mMemory; }; + + operator T*() { return mMemory; } + operator const T*() const { return mMemory; } + + operator T&() { return *mMemory; } + operator const T&() const { return *mMemory; } + + operator T() { return *mMemory; } + operator const T() const { return *mMemory; } + + T& operator []( U32 i ) { return mMemory[ i ]; } + const T& operator []( U32 i ) const { return mMemory[ i ]; } + + T& operator []( S32 i ) { return mMemory[ i ]; } + const T& operator []( S32 i ) const { return mMemory[ i ]; } + + /// @name Vector-like Interface + /// @{ + T *address() const { return mMemory; } + dsize_t size() const { return mNumObjectsInMemory; } + /// @} +}; + +//----------------------------------------------------------------------------- +// FrameTemp specializations for types with no constructor/destructor +#define FRAME_TEMP_NC_SPEC(type) \ + template<> \ + inline FrameTemp::FrameTemp( const U32 count ) \ + { \ + AssertFatal( count > 0, "Allocating a FrameTemp with less than one instance" ); \ + mWaterMark = FrameAllocator::getWaterMark(); \ + mMemory = reinterpret_cast( FrameAllocator::alloc( sizeof( type ) * count ) ); \ + } \ + template<>\ + inline FrameTemp::~FrameTemp() \ + { \ + FrameAllocator::setWaterMark( mWaterMark ); \ + } \ + +FRAME_TEMP_NC_SPEC(char); +FRAME_TEMP_NC_SPEC(float); +FRAME_TEMP_NC_SPEC(double); +FRAME_TEMP_NC_SPEC(bool); +FRAME_TEMP_NC_SPEC(int); +FRAME_TEMP_NC_SPEC(short); + +FRAME_TEMP_NC_SPEC(unsigned char); +FRAME_TEMP_NC_SPEC(unsigned int); +FRAME_TEMP_NC_SPEC(unsigned short); + +#undef FRAME_TEMP_NC_SPEC + +//----------------------------------------------------------------------------- + +#endif // _H_FRAMEALLOCATOR_ diff --git a/core/iTickable.cpp b/core/iTickable.cpp new file mode 100644 index 0000000..d57bf12 --- /dev/null +++ b/core/iTickable.cpp @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/iTickable.h" + +// The statics +U32 ITickable::smLastTick = 0; +U32 ITickable::smLastTime = 0; +U32 ITickable::smLastDelta = 0; + +U32 ITickable::smTickShift = 5; +U32 ITickable::smTickMs = ( 1 << smTickShift ); +F32 ITickable::smTickSec = ( F32( ITickable::smTickMs ) / 1000.f ); +U32 ITickable::smTickMask = ( smTickMs - 1 ); + +//------------------------------------------------------------------------------ + +ITickable::ITickable() : mProcessTick( true ) +{ + getProcessList().push_back( this ); +} + +//------------------------------------------------------------------------------ + +ITickable::~ITickable() +{ + for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ ) + { + if( (*i) == this ) + { + getProcessList().erase( i ); + return; + } + } +} + +//------------------------------------------------------------------------------ + +void ITickable::init( const U32 tickShift ) +{ + // Sanity! + AssertFatal( getProcessList().size() == 0, "ITickable::init() - Cannot modify ITickable with objects in the process list!" ); + AssertFatal( tickShift == 0 || tickShift <= 31, "ITickable::init() - Invalid 'tickShift' parameter!" ); + + // Calculate tick constants. + smTickShift = tickShift; + smTickMs = ( 1 << smTickShift ); + smTickSec = ( F32( smTickMs ) / 1000.f ); + smTickMask = ( smTickMs - 1 ); +} + +//------------------------------------------------------------------------------ + +Vector& ITickable::getProcessList() +{ + // This helps to avoid the static initialization order fiasco + static Vector smProcessList( __FILE__, __LINE__ ); ///< List of tick controls + return smProcessList; +} + +//------------------------------------------------------------------------------ + +bool ITickable::advanceTime( U32 timeDelta ) +{ + U32 targetTime = smLastTime + timeDelta; + U32 targetTick = ( targetTime + smTickMask ) & ~smTickMask; + U32 tickCount = ( targetTick - smLastTick ) >> smTickShift; + + // If we are going to send a tick, call interpolateTick(0) so that the objects + // will reset back to their position at the last full tick + if( smLastDelta && tickCount ) + for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ ) + if( (*i)->isProcessingTicks() ) + (*i)->interpolateTick( 0.f ); + + // Advance objects + if( tickCount ) + for( ; smLastTick != targetTick; smLastTick += smTickMs ) + for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ ) + if( (*i)->isProcessingTicks() ) + (*i)->processTick(); + + smLastDelta = ( smTickMs - ( targetTime & smTickMask ) ) & smTickMask; + F32 dt = smLastDelta / F32( smTickMs ); + + // Now interpolate objects that want ticks + for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ ) + if( (*i)->isProcessingTicks() ) + (*i)->interpolateTick( dt ); + + + // Inform ALL objects that time was advanced + dt = F32( timeDelta ) / 1000.f; + for( ProcessListIterator i = getProcessList().begin(); i != getProcessList().end(); i++ ) + (*i)->advanceTime( dt ); + + smLastTime = targetTime; + + return tickCount != 0; +} diff --git a/core/iTickable.h b/core/iTickable.h new file mode 100644 index 0000000..bde7968 --- /dev/null +++ b/core/iTickable.h @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ITICKABLE_H_ +#define _ITICKABLE_H_ + +#include "core/util/tVector.h" + +/// This interface allows you to let any object be ticked. You use it like so: +/// @code +/// class FooClass : public SimObject, public virtual ITickable +/// { +/// // You still mark SimObject as Parent +/// typdef SimObject Parent; +/// private: +/// ... +/// +/// protected: +/// // These three methods are the interface for ITickable +/// virtual void interpolateTick( F32 delta ); +/// virtual void processTick(); +/// virtual void advanceTime( F32 timeDelta ); +/// +/// public: +/// ... +/// }; +/// @endcode +/// Please note the three methods you must implement to use ITickable, but don't +/// worry. If you forget, the compiler will tell you so. Also note that the +/// typedef for Parent should NOT BE SET to ITickable, the compiler will probably +/// also tell you if you forget that. Last, but assuridly not least is that you note +/// the way that the inheretance is done: public virtual ITickable +/// It is very important that you keep the virtual keyword in there, otherwise +/// proper behavior is not guarenteed. You have been warned. +/// +/// The point of a tickable object is that the object gets ticks at a fixed rate +/// which is one tick every 32ms. This means, also, that if an object doesn't get +/// updated for 64ms, that the next update it will get two-ticks. Basically it +/// comes down to this. You are assured to get one tick per 32ms of time passing +/// provided that isProcessingTicks returns true when ITickable calls it. +/// +/// isProcessingTicks is a virtual method and you can (should you want to) +/// override it and put some extended functionality to decide if you want to +/// recieve tick-notification or not. +/// +/// The other half of this is that you get time-notification from advanceTime. +/// advanceTime lets you know when time passes regardless of the return value +/// of isProcessingTicks. The object WILL get the advanceTime call every single +/// update. The argument passed to advanceTime is the time since the last call +/// to advanceTime. Updates are not based on the 32ms tick time. Updates are +/// dependant on framerate. So you may get 200 advanceTime calls in a second, or you +/// may only get 20. There is no way of assuring consistant calls of advanceTime +/// like there is with processTick. Both are useful for different things, and +/// it is important to understand the differences between them. +/// +/// Interpolation is the last part of the ITickable interface. It is called +/// every update, as long as isProcessingTicks evaluates to true on the object. +/// This is used to interpolate between 32ms ticks. The argument passed to +/// interpolateTick is the time since the last call to processTick. You can see +/// in the code for ITickable::advanceTime that before a tick occurs it calls +/// interpolateTick(0) on every object. This is to tell objects which do interpolate +/// between ticks to reset their interpolation because they are about to get a +/// new tick. +/// +/// This is an extremely powerful interface when used properly. An example of a class +/// that properly uses this interface is GuiTickCtrl. The documentation for that +/// class describes why it was created and why it was important that it use +/// a consistant update frequency for its effects. +/// @see GuiTickCtrl +/// +/// @todo Support processBefore/After and move the GameBase processing over to use ITickable +class ITickable +{ +private: + static U32 smLastTick; ///< Time of the last tick that occurred + static U32 smLastTime; ///< Last time value at which advanceTime was called + static U32 smLastDelta; ///< Last delta value for advanceTime + + static U32 smTickShift; ///< Shift value to control how often Ticks occur + static U32 smTickMs; ///< Number of milliseconds per tick, 32 in this case + static F32 smTickSec; ///< Fraction of a second per tick + static U32 smTickMask; + + // This just makes life easy + typedef Vector::iterator ProcessListIterator; + /// Returns a reference to the list of all ITickable objects. + static Vector& getProcessList(); + + bool mProcessTick; ///< Set to true if this object wants tick processing +protected: + /// This method is called every frame and lets the control interpolate between + /// ticks so you can smooth things as long as isProcessingTicks returns true + /// when it is called on the object + virtual void interpolateTick( F32 delta ) = 0; + + /// This method is called once every 32ms if isProcessingTicks returns true + /// when called on the object + virtual void processTick() = 0; + + /// This method is called once every frame regardless of the return value of + /// isProcessingTicks and informs the object of the passage of time + virtual void advanceTime( F32 timeDelta ) = 0; + +public: + + /// Constructor + /// This will add the object to the process list + ITickable(); + + /// Destructor + /// Remove this object from the process list + virtual ~ITickable(); + + /// Is this object wanting to receive tick notifications + /// @returns True if object wants tick notifications + bool isProcessingTicks() const { return mProcessTick; }; + + /// Sets this object as either tick processing or not + /// @param tick True if this object should process ticks + virtual void setProcessTicks( bool tick = true ); + + /// Initialise the ITickable system. + static void init( const U32 tickShift ); + + /// Gets the Tick bit-shift. + static U32 getTickShift() { return smTickShift; } + /// Gets the Tick (ms) + static U32 getTickMs() { return smTickMs; } + /// Gets the Tick (seconds) + static F32 getTickSec() { return smTickSec; } + /// Gets the Tick mask. + static U32 getTickMask() { return smTickMask; } + +//------------------------------------------------------------------------------ + + /// This is called in clientProcess to advance the time for all ITickable + /// objects + /// @returns True if any ticks were sent + /// @see clientProcess + static bool advanceTime( U32 timeDelta ); +}; + +//------------------------------------------------------------------------------ + +inline void ITickable::setProcessTicks( bool tick /* = true */ ) +{ + mProcessTick = tick; +} + +#endif diff --git a/core/idGenerator.cpp b/core/idGenerator.cpp new file mode 100644 index 0000000..e127ca7 --- /dev/null +++ b/core/idGenerator.cpp @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/idGenerator.h" + +void IdGenerator::reclaim() +{ + // attempt to keep the pool vector as small as possible by reclaiming + // pool entries back into the nextIdBlock variable + + while (!mPool.empty() && (mPool.last() == (mNextId-1)) ) + { + mNextId--; + mPool.pop_back(); + } +} + diff --git a/core/idGenerator.h b/core/idGenerator.h new file mode 100644 index 0000000..bd9112e --- /dev/null +++ b/core/idGenerator.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _IDGENERATOR_H_ +#define _IDGENERATOR_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class IdGenerator +{ +private: + U32 mIdBlockBase; + U32 mIdRangeSize; + Vector mPool; + U32 mNextId; + + void reclaim(); + +public: + IdGenerator(U32 base, U32 numIds) + { + VECTOR_SET_ASSOCIATION(mPool); + + mIdBlockBase = base; + mIdRangeSize = numIds; + mNextId = mIdBlockBase; + } + + void reset() + { + mPool.clear(); + mNextId = mIdBlockBase; + } + + U32 alloc() + { + // fist check the pool: + if(!mPool.empty()) + { + U32 id = mPool.last(); + mPool.pop_back(); + reclaim(); + return id; + } + if(mIdRangeSize && mNextId >= mIdBlockBase + mIdRangeSize) + return 0; + + return mNextId++; + } + + void free(U32 id) + { + AssertFatal(id >= mIdBlockBase, "IdGenerator::alloc: invalid id, id does not belong to this IdGenerator.") + if(id == mNextId - 1) + { + mNextId--; + reclaim(); + } + else + mPool.push_back(id); + } + + U32 numIdsUsed() + { + return mNextId - mIdBlockBase - mPool.size(); + } +}; + +#endif diff --git a/core/memVolume.cpp b/core/memVolume.cpp new file mode 100644 index 0000000..92d3bd9 --- /dev/null +++ b/core/memVolume.cpp @@ -0,0 +1,491 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- +#include "core/memVolume.h" +#include "core/crc.h" +#include "core/frameAllocator.h" +#include "core/util/str.h" +#include "core/strings/stringFunctions.h" +#include "platform/platformVolume.h" + +namespace Torque +{ + namespace Mem + { + + // Multiple MemFile's can reference the same path, so this is here to contain + // the actual data at a Path. + struct MemFileData + { + MemFileData(MemFileSystem* fs, const Path& path) + { + mPath = path; + mBufferSize = 1024; + mFileSize = 0; + mBuffer = dMalloc(mBufferSize); + dMemset(mBuffer, 0, mBufferSize); + mModified = Time::getCurrentTime(); + mLastAccess = mModified; + mFileSystem = fs; + } + + ~MemFileData() + { + dFree(mBuffer); + } + + bool getAttributes(FileNode::Attributes* attr) + { + attr->name = mPath; + attr->flags = FileNode::File; + attr->size = mFileSize; + attr->mtime = mModified; + attr->atime = mLastAccess; + return true; + } + + FileNodeRef resolve(const Path& path) + { + // Is it me? + String sThisPath(mPath); + String sTargetPath(path); + if (sThisPath == sTargetPath) + return new MemFile(mFileSystem, this); + // Nope + return NULL; + } + + Path mPath; + void* mBuffer; + U32 mBufferSize; // This is the size of the memory buffer >= mFileSize + U32 mFileSize; // This is the size of the "file" <= mBufferSize + Time mModified; // Last modified + Time mLastAccess; // Last access + MemFileSystem* mFileSystem; + }; + + struct MemDirectoryData + { + Path mPath; + MemFileSystem* mFileSystem; + Vector mFiles; + Vector mDirectories; + + MemDirectoryData(MemFileSystem* fs, const Path& path) + { + mFileSystem = fs; + mPath = path; + } + + ~MemDirectoryData() + { + for (U32 i = 0; i < mFiles.size(); i++) + { + delete mFiles[i]; + } + for (U32 i = 0; i < mDirectories.size(); i++) + { + delete mDirectories[i]; + } + } + + bool getAttributes(FileNode::Attributes* attr) + { + attr->name = mPath; + attr->flags = FileNode::Directory; + return true; + } + + FileNodeRef resolve(const Path& path) + { + // Is it me? + String sThisPath(mPath); + String sTargetPath(path); + if (sThisPath == sTargetPath) + return new MemDirectory(mFileSystem, this); + // Is it one of my children? + if (sTargetPath.find(sThisPath) == 0) + { + FileNodeRef result; + for (U32 i = 0; i < mDirectories.size() && result.isNull(); i++) + result = mDirectories[i]->resolve(path); + for (U32 i = 0; i < mFiles.size() && result.isNull(); i++) + result = mFiles[i]->resolve(path); + return result; + } + // Nope + return NULL; + } + }; + + //----------------------------------------------------------------------------- + MemFileSystem::MemFileSystem(String volume) + { + mVolume = volume; + mRootDir = new MemDirectoryData(this, volume); + } + + MemFileSystem::~MemFileSystem() + { + delete mRootDir; + } + + FileNodeRef MemFileSystem::resolve(const Path& path) + { + return mRootDir->resolve(path); + } + + // + MemDirectory* MemFileSystem::getParentDir(const Path& path, FileNodeRef& parentRef) + { + parentRef = mRootDir->resolve(path.getRoot() + ":" + path.getPath()); + if (parentRef.isNull()) + return NULL; + + MemDirectory* result = dynamic_cast(parentRef.getPointer()); + return result; + } + + FileNodeRef MemFileSystem::create(const Path& path, FileNode::Mode mode) + { + // Already exists + FileNodeRef result = mRootDir->resolve(path); + if (result.isValid()) + return result; + + // Doesn't exist, try to get parent node. + FileNodeRef parentRef; + MemDirectory* mDir = getParentDir(path, parentRef); + if (mDir) + { + MemDirectoryData* mdd = mDir->mDirectoryData; + switch (mode) + { + case FileNode::File : + { + MemFileData* mfd = new MemFileData(this, path); + mdd->mFiles.push_back(mfd); + return new MemFile(this, mfd); + } + break; + case FileNode::Directory : + { + MemDirectoryData* mfd = new MemDirectoryData(this, path); + mdd->mDirectories.push_back(mfd); + return new MemDirectory(this, mfd); + } + break; + default: + // anything else we ignore + break; + } + } + return NULL; + } + + bool MemFileSystem::remove(const Path& path) + { + FileNodeRef parentRef; + MemDirectory* mDir = getParentDir(path, parentRef); + MemDirectoryData* mdd = mDir->mDirectoryData; + for (U32 i = 0; i < mdd->mDirectories.size(); i++) + { + if (mdd->mDirectories[i]->mPath == path) + { + delete mdd->mDirectories[i]; + mdd->mDirectories.erase_fast(i); + return true; + } + } + for (U32 i = 0; i < mdd->mFiles.size(); i++) + { + if (mdd->mFiles[i]->mPath == path) + { + delete mdd->mFiles[i]; + mdd->mFiles.erase_fast(i); + return true; + } + } + return false; + } + + bool MemFileSystem::rename(const Path& from,const Path& to) + { + // Source must exist + FileNodeRef source = mRootDir->resolve(from); + if (source.isNull()) + return false; + + // Destination must not exist + FileNodeRef dest = mRootDir->resolve(to); + if (source.isValid()) + return false; + + // Get source parent + FileNodeRef sourceParentRef; + MemDirectory* sourceDir = getParentDir(from, sourceParentRef); + + // Get dest parent + FileNodeRef destRef; + MemDirectory* mDir = getParentDir(to, destRef); + + // Now move it/rename it + if (dynamic_cast(source.getPointer())) + { + MemDirectoryData* sourcedd; + MemDirectoryData* d = sourceDir->mDirectoryData; + for (U32 i = 0; i < d->mDirectories.size(); i++) + { + if (d->mDirectories[i]->mPath == from) + { + sourcedd = d->mDirectories[i]; + d->mDirectories.erase_fast(i); + sourcedd->mPath = to; + mDir->mDirectoryData->mDirectories.push_back(sourcedd); + return true; + } + } + } else { + MemFileData* sourceFile; + MemDirectoryData* d = sourceDir->mDirectoryData; + for (U32 i = 0; i < d->mFiles.size(); i++) + { + if (d->mFiles[i]->mPath == from) + { + sourceFile = d->mFiles[i]; + d->mFiles.erase_fast(i); + sourceFile->mPath = to; + mDir->mDirectoryData->mFiles.push_back(sourceFile); + return true; + } + } + } + return false; + } + + Path MemFileSystem::mapTo(const Path& path) + { + String file = mVolume; + file = Path::Join(file, '/', path.getPath()); + file = Path::Join(file, '/', path.getFileName()); + file = Path::Join(file, '.', path.getExtension()); + return file; + } + + Path MemFileSystem::mapFrom(const Path& path) + { + const String::SizeType volumePathLen = mVolume.length(); + + String pathStr = path.getFullPath(); + + if ( mVolume.compare( pathStr, volumePathLen, String::NoCase )) + return Path(); + + return pathStr.substr( volumePathLen, pathStr.length() - volumePathLen ); + } + + //----------------------------------------------------------------------------- + + MemFile::MemFile(MemFileSystem* fs, MemFileData* fileData) + { + mFileData = fileData; + mStatus = Closed; + mCurrentPos = U32_MAX; + mFileSystem = fs; + } + + MemFile::~MemFile() + { + } + + Path MemFile::getName() const + { + return mFileData->mPath; + } + + FileNode::Status MemFile::getStatus() const + { + return mStatus; + } + + bool MemFile::getAttributes(Attributes* attr) + { + return mFileData->getAttributes(attr); + } + + U32 MemFile::calculateChecksum() + { + return CRC::calculateCRC(mFileData->mBuffer, mFileData->mFileSize); + } + + bool MemFile::open(AccessMode mode) + { + mStatus = Open; + mCurrentPos = 0; + switch (mode) + { + case Read : + case ReadWrite : + mCurrentPos = 0; + break; + case Write : + mCurrentPos = 0; + mFileData->mFileSize = 0; + break; + case WriteAppend : + mCurrentPos = mFileData->mFileSize; + break; + } + return true; + } + + bool MemFile::close() + { + mStatus = Closed; + return true; + } + + U32 MemFile::getPosition() + { + if (mStatus == Open || mStatus == EndOfFile) + return mCurrentPos; + return 0; + } + + U32 MemFile::setPosition(U32 delta, SeekMode mode) + { + if (mStatus != Open && mStatus != EndOfFile) + return 0; + + switch (mode) + { + case Begin: + mCurrentPos = delta; + break; + case Current: + mCurrentPos += delta; + break; + case End: + mCurrentPos = mFileData->mFileSize - delta; + break; + } + + mStatus = Open; + + return mCurrentPos; + } + + U32 MemFile::read(void* dst, U32 size) + { + if (mStatus != Open && mStatus != EndOfFile) + return 0; + + U32 copyAmount = getMin(size, mFileData->mFileSize - mCurrentPos); + dMemcpy(dst, (U8*) mFileData->mBuffer + mCurrentPos, copyAmount); + mCurrentPos += copyAmount; + mFileData->mLastAccess = Time::getCurrentTime(); + if (mCurrentPos == mFileData->mFileSize) + mStatus = EndOfFile; + return copyAmount; + } + + U32 MemFile::write(const void* src, U32 size) + { + if ((mStatus != Open && mStatus != EndOfFile) || !size) + return 0; + + if (mFileData->mFileSize + size > mFileData->mBufferSize) + { + // Keep doubling our buffer size until we're big enough. + while (mFileData->mFileSize + size > mFileData->mBufferSize) + mFileData->mBufferSize *= 2; + mFileData->mBuffer = dRealloc(mFileData->mBuffer, mFileData->mBufferSize); + if (!mFileData->mBuffer) + { + mStatus = FileSystemFull; + return 0; + } + } + + dMemcpy((U8*)mFileData->mBuffer + mCurrentPos, src, size); + mCurrentPos += size; + mFileData->mFileSize = getMax(mFileData->mFileSize, mCurrentPos); + mFileData->mLastAccess = Time::getCurrentTime(); + mFileData->mModified = mFileData->mLastAccess; + + return size; + } + + //----------------------------------------------------------------------------- + + MemDirectory::MemDirectory(MemFileSystem* fs, MemDirectoryData* dir) + { + mStatus = Closed; + mDirectoryData = dir; + mFileSystem = fs; + } + + MemDirectory::~MemDirectory() + { + } + + Path MemDirectory::getName() const + { + return mDirectoryData->mPath; + } + + bool MemDirectory::open() + { + mSearchIndex = 0; + mStatus = Open; + return true; + } + + bool MemDirectory::close() + { + return true; + } + + bool MemDirectory::read(Attributes* entry) + { + if (mStatus != Open) + return false; + + if (mSearchIndex < mDirectoryData->mDirectories.size()) + { + mDirectoryData->mDirectories[mSearchIndex]->getAttributes(entry); + mSearchIndex++; + return true; + } + + AssertFatal(mSearchIndex > mDirectoryData->mDirectories.size(), "This should not happen!"); + U32 fileIndex = mSearchIndex - mDirectoryData->mDirectories.size(); + if (fileIndex < mDirectoryData->mFiles.size()) + { + mDirectoryData->mFiles[mSearchIndex]->getAttributes(entry); + mSearchIndex++; + return true; + } + + return false; + } + + U32 MemDirectory::calculateChecksum() + { + // Return checksum of current entry + return 0; + } + + bool MemDirectory::getAttributes(Attributes* attr) + { + return mDirectoryData->getAttributes(attr); + } + + FileNode::Status MemDirectory::getStatus() const + { + return mStatus; + } + } // Namespace Mem + +} // Namespace Torque \ No newline at end of file diff --git a/core/memVolume.h b/core/memVolume.h new file mode 100644 index 0000000..d36b7a7 --- /dev/null +++ b/core/memVolume.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- +#ifndef _MEMVOLUME_H_ +#define _MEMVOLUME_H_ + +#ifndef _VOLUME_H_ +#include "core/volume.h" +#endif + +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +namespace Torque +{ + using namespace FS; + + namespace Mem + { + + struct MemFileData; + struct MemDirectoryData; + class MemDirectory; + + //----------------------------------------------------------------------------- + class MemFileSystem: public FileSystem + { + public: + MemFileSystem(String volume); + ~MemFileSystem(); + + String getTypeStr() const { return "Mem"; } + + FileNodeRef resolve(const Path& path); + FileNodeRef create(const Path& path,FileNode::Mode); + bool remove(const Path& path); + bool rename(const Path& from,const Path& to); + Path mapTo(const Path& path); + Path mapFrom(const Path& path); + + private: + String mVolume; + MemDirectoryData* mRootDir; + + MemDirectory* getParentDir(const Path& path, FileNodeRef& parentRef); + }; + + //----------------------------------------------------------------------------- + /// Mem stdio file access. + /// This class makes use the fopen, fread and fwrite for buffered io. + class MemFile: public File + { + public: + MemFile(MemFileSystem* fs, MemFileData* fileData); + virtual ~MemFile(); + + Path getName() const; + Status getStatus() const; + bool getAttributes(Attributes*); + + U32 getPosition(); + U32 setPosition(U32,SeekMode); + + bool open(AccessMode); + bool close(); + + U32 read(void* dst, U32 size); + U32 write(const void* src, U32 size); + + private: + U32 calculateChecksum(); + + MemFileSystem* mFileSystem; + MemFileData* mFileData; + Status mStatus; + U32 mCurrentPos; + + bool _updateInfo(); + void _updateStatus(); + }; + + + //----------------------------------------------------------------------------- + + class MemDirectory: public Directory + { + public: + MemDirectory(MemFileSystem* fs, MemDirectoryData* dir); + ~MemDirectory(); + + Path getName() const; + Status getStatus() const; + bool getAttributes(Attributes*); + + bool open(); + bool close(); + bool read(Attributes*); + + private: + friend class MemFileSystem; + MemFileSystem* mFileSystem; + MemDirectoryData* mDirectoryData; + + U32 calculateChecksum(); + + Status mStatus; + U32 mSearchIndex; + }; + + } // Namespace +} // Namespace + +#endif \ No newline at end of file diff --git a/core/ogg/oggInputStream.cpp b/core/ogg/oggInputStream.cpp new file mode 100644 index 0000000..c775981 --- /dev/null +++ b/core/ogg/oggInputStream.cpp @@ -0,0 +1,245 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/ogg/oggInputStream.h" +#include "core/stream/stream.h" +#include "core/util/safeDelete.h" + + +//#define DEBUG_SPEW + + +//----------------------------------------------------------------------------- +// OggDecoder implementation. +//----------------------------------------------------------------------------- + +OggDecoder::OggDecoder( const ThreadSafeRef< OggInputStream >& stream ) + : mOggStream( stream ) +{ +} + +OggDecoder::~OggDecoder() +{ + ogg_stream_clear( &mOggStreamState ); +} + +void OggDecoder::_setStartPage( ogg_page* startPage ) +{ + ogg_stream_init( &mOggStreamState, ogg_page_serialno( startPage ) ); + ogg_stream_pagein( &mOggStreamState, startPage ); +} + +bool OggDecoder::_readNextPacket( ogg_packet* packet ) +{ + MutexHandle mutex; + mutex.lock( &mMutex, true ); + + while( 1 ) + { + int result = ogg_stream_packetout( &mOggStreamState, packet ); + if( result == 0 ) + { + if( !mOggStream->_requestData() ) + return false; + } + else if( result < 0 ) + return false; + else + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[OggDecoder] read packet %i in %s (bytes: %i, bos: %s, eos: %s)", + ( U32 ) packet->packetno, + getName(), + ( U32 ) packet->bytes, + packet->b_o_s ? "1" : "0", + packet->e_o_s ? "1" : "0" ); + #endif + + return true; + } + } +} + +bool OggDecoder::_nextPacket() +{ + MutexHandle mutex; + mutex.lock( &mMutex, true ); + + ogg_packet packet; + + do + { + if( !_readNextPacket( &packet ) ) + return false; + } + while( !_packetin( &packet ) ); + + return true; +} + +//----------------------------------------------------------------------------- +// OggInputStream implementation. +//----------------------------------------------------------------------------- + +OggInputStream::OggInputStream( Stream* stream ) + : mStream( stream ), + mIsAtEnd( false ) +{ + ogg_sync_init( &mOggSyncState ); + + VECTOR_SET_ASSOCIATION( mConstructors ); + VECTOR_SET_ASSOCIATION( mDecoders ); +} + +OggInputStream::~OggInputStream() +{ + _freeDecoders(); + ogg_sync_clear( &mOggSyncState ); + + if( mStream ) + SAFE_DELETE( mStream ); +} + +OggDecoder* OggInputStream::getDecoder( const String& name ) const +{ + for( U32 i = 0; i < mDecoders.size(); ++ i ) + if( name.equal( mDecoders[ i ]->getName(), String::NoCase ) ) + return mDecoders[ i ]; + + return NULL; +} + +bool OggInputStream::isAtEnd() +{ + MutexHandle mutex; + mutex.lock( &mMutex, true ); + + return mIsAtEnd; +} + +bool OggInputStream::init() +{ + if( !mStream->hasCapability( Stream::StreamPosition ) ) + return false; + + mStream->setPosition( 0 ); + + // Read all beginning-of-stream pages and construct decoders + // for all streams we recognize. + + while( 1 ) + { + // Read next page. + + ogg_page startPage; + _pullNextPage( &startPage ); + + // If not a beginning-of-stream page, push it to the decoders + // and stop reading headers. + + if( !ogg_page_bos( &startPage ) ) + { + _pushNextPage( &startPage ); + break; + } + + // Try the list of constructors for one that consumes + // the page. + + for( U32 i = 0; i < mConstructors.size(); ++ i ) + { + OggDecoder* decoder = mConstructors[ i ]( this ); + if( decoder->_detect( &startPage ) ) + mDecoders.push_back( decoder ); + else + delete decoder; + } + } + + // Initialize decoders and let all them finish up header processing. + + for( U32 i = 0; i < mDecoders.size(); ++ i ) + if( !mDecoders[ i ]->_init() ) + { + delete mDecoders[ i ]; + mDecoders.erase( i ); + -- i; + } + + if( !mDecoders.size() ) + return false; + + return true; +} + +void OggInputStream::_freeDecoders() +{ + for( U32 i = 0; i < mDecoders.size(); ++ i ) + delete mDecoders[ i ]; + mDecoders.clear(); +} + +bool OggInputStream::_pullNextPage( ogg_page* page) +{ + // Read another page. + + while( ogg_sync_pageout( &mOggSyncState, page ) <= 0 + && mStream->getStatus() != Stream::EOS ) + { + enum { BUFFER_SIZE = 4096 }; + + // Read more data. + + char* buffer = ogg_sync_buffer( &mOggSyncState, BUFFER_SIZE ); + const U32 oldPos = mStream->getPosition(); + mStream->read( BUFFER_SIZE, buffer ); + + const U32 numBytes = mStream->getPosition() - oldPos; + if( numBytes ) + ogg_sync_wrote( &mOggSyncState, numBytes ); + else + return false; + } + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[OggInputStream] pulled next page (header: %i, body: %i)", + page->header_len, page->body_len ); + #endif + + return true; +} + +void OggInputStream::_pushNextPage( ogg_page* page ) +{ + for( U32 i = 0; i < mDecoders.size(); ++ i ) + { + MutexHandle mutex; + mutex.lock( &mDecoders[ i ]->mMutex, true ); + + ogg_stream_pagein( &mDecoders[ i ]->mOggStreamState, page ); + } +} + +bool OggInputStream::_requestData() +{ + // Lock at this level to ensure correct ordering of page writes. + // Technically, the proper place to lock would be _pullNextPage + // but then it could happen that one thread pushes a page before + // another thread gets to push a page that has been read earlier. + + MutexHandle mutex; + mutex.lock( &mMutex, true ); + + ogg_page nextPage; + + if( !_pullNextPage( &nextPage ) ) + { + mIsAtEnd = true; + return false; + } + + _pushNextPage( &nextPage ); + return true; +} diff --git a/core/ogg/oggInputStream.h b/core/ogg/oggInputStream.h new file mode 100644 index 0000000..5d33f96 --- /dev/null +++ b/core/ogg/oggInputStream.h @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _OGGINPUTSTREAM_H_ +#define _OGGINPUTSTREAM_H_ + +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif +#ifndef _TYPETRAITS_H_ + #include "platform/typetraits.h" +#endif +#ifndef _PLATFORM_THREADS_MUTEX_H_ + #include "platform/threads/mutex.h" +#endif +#ifndef _THREADSAFEREFCOUNT_H_ + #include "platform/threads/threadSafeRefCount.h" +#endif +#include "ogg/ogg.h" + + +class Stream; +class OggInputStream; + + +/// Single substream in a multiplexed OGG stream. +class OggDecoder +{ + public: + + typedef void Parent; + friend class OggInputStream; + + protected: + + /// The Ogg container stream. + OggInputStream* mOggStream; + + /// The Ogg bitstream. + ogg_stream_state mOggStreamState; + + /// Lock for synchronizing access to Ogg stream state. + Mutex mMutex; + + /// Read the next packet in the stream. + /// @return false if there is no next packet. + bool _readNextPacket( ogg_packet* packet ); + + /// + bool _nextPacket(); + + /// + virtual bool _detect( ogg_page* startPage ) = 0; + + /// + virtual bool _init() = 0; + + /// + virtual bool _packetin( ogg_packet* packet ) = 0; + + /// + void _setStartPage( ogg_page* startPage ); + + public: + + /// + OggDecoder( const ThreadSafeRef< OggInputStream >& stream ); + + virtual ~OggDecoder(); + + /// Return the serial number of the Ogg bitstream. + U32 getStreamSerialNo() const { return mOggStreamState.serialno; } + + /// + virtual const char* getName() const = 0; +}; + +/// A multiplexed OGG input stream feeding into stream decoders. +class OggInputStream : public ThreadSafeRefCount< OggInputStream > +{ + public: + + typedef void Parent; + friend class OggDecoder; // _requestData + + protected: + + typedef OggDecoder* ( *Constructor )( const ThreadSafeRef< OggInputStream >& stream ); + + template< typename T > + struct _SpellItOutForGCC + { + static OggDecoder* _fn( const ThreadSafeRef< OggInputStream >& stream ) + { + return constructSingle< T* >( stream ); + } + }; + + /// + bool mIsAtEnd; + + /// + Stream* mStream; + + /// + Vector< Constructor > mConstructors; + + /// + Vector< OggDecoder* > mDecoders; + + /// + ogg_sync_state mOggSyncState; + + /// + Mutex mMutex; + + /// Pull the next page from the OGG stream. + bool _pullNextPage( ogg_page* page ); + + /// Push the given page to the attached decoder streams. + void _pushNextPage( ogg_page* page ); + + /// + bool _requestData(); + + /// + void _freeDecoders(); + + public: + + /// + /// @note Ownership of "stream" is transferred to OggInputStream. + OggInputStream( Stream* stream ); + + ~OggInputStream(); + + /// Register a decoder class with the stream. + template< class T > + void addDecoder() + { + mConstructors.push_back( &_SpellItOutForGCC< T >::_fn ); + } + + /// + OggDecoder* getDecoder( const String& name ) const; + + /// + bool init(); + + /// + bool isAtEnd(); +}; + +#endif // !_OGGINPUTSTREAM_H_ diff --git a/core/ogg/oggTheoraDecoder.cpp b/core/ogg/oggTheoraDecoder.cpp new file mode 100644 index 0000000..5de2b2f --- /dev/null +++ b/core/ogg/oggTheoraDecoder.cpp @@ -0,0 +1,683 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/ogg/oggTheoraDecoder.h" +#include "gfx/gfxFormatUtils.h" +#include "math/mMathFn.h" +#include "console/console.h" + + +//#define DEBUG_SPEW + + +//----------------------------------------------------------------------------- + +// Lookup tables for the transcoders. +// +// The Y, Cb, and Cr tables are used by both the SSE2 and the generic transcoder. +// For the SSE2 code, the data must be 16 byte aligned. +// +// The clamping table is only used by the generic transcoder. The SSE2 transcoder +// uses instructions to implicitly clamp out-of-range values. + +#if defined( TORQUE_COMPILER_VISUALC ) + #define ALIGN( x ) __declspec( align( 16 ) ) x +#elif defined( TORQUE_COMPILER_GCC ) + #define ALIGN( x ) x __attribute__( ( aligned( 16 ) ) ) +#else + #define ALIGN( x ) x +#endif + +ALIGN( static S32 sRGBY[ 256 ][ 4 ] ); +ALIGN( static S32 sRGBCb[ 256 ][ 4 ] ); +ALIGN( static S32 sRGBCr[ 256 ][ 4 ] ); + +static U8 sClampBuff[ 1024 ]; +static U8* sClamp = sClampBuff + 384; + +static void initLookupTables() +{ + static bool sGenerated = false; + if( !sGenerated ) + { + for( S32 i = 0; i < 256; ++ i ) + { + // Y. + + sRGBY[ i ][ 0 ] = ( 298 * ( i - 16 ) ) >> 8; // B + sRGBY[ i ][ 1 ] = ( 298 * ( i - 16 ) ) >> 8; // G + sRGBY[ i ][ 2 ] = ( 298 * ( i - 16 ) ) >> 8; // R + sRGBY[ i ][ 3 ] = 0xff; // A + + // Cb. + + sRGBCb[ i ][ 0 ] = ( 516 * ( i - 128 ) + 128 ) >> 8; // B + sRGBCb[ i ][ 1 ] = - ( ( 100 * ( i - 128 ) + 128 ) >> 8 ); // G + + // Cr. + + sRGBCr[ i ][ 1 ] = - ( ( 208 * ( i - 128 ) + 128 ) >> 8 ); // B + sRGBCr[ i ][ 2 ] = ( 409 * ( i - 128 ) + 128 ) >> 8; // R + } + + // Setup clamping table for generic transcoder. + + for( S32 i = -384; i < 640; ++ i ) + sClamp[ i ] = mClamp( i, 0, 0xFF ); + + sGenerated = true; + } +} + +static inline S32 sampleG( U8* pCb, U8* pCr ) +{ + return sRGBCr[ *pCr ][ 1 ] + sRGBCr[ *pCb ][ 1 ]; +} + +//============================================================================= +// OggTheoraDecoder. +//============================================================================= + +//----------------------------------------------------------------------------- + +OggTheoraDecoder::OggTheoraDecoder( const ThreadSafeRef< OggInputStream >& stream ) + : Parent( stream ), +#ifdef TORQUE_DEBUG + mLock( 0 ), +#endif + mTheoraSetup( NULL ), + mTheoraDecoder( NULL ), + mTranscoder( TRANSCODER_Auto ) +{ + // Initialize. + + th_info_init( &mTheoraInfo ); + th_comment_init( &mTheoraComment ); + + initLookupTables(); +} + +//----------------------------------------------------------------------------- + +OggTheoraDecoder::~OggTheoraDecoder() +{ + // Free packets on the freelist. + + OggTheoraFrame* packet; + while( mFreePackets.tryPopFront( packet ) ) + destructSingle( packet ); + + // Clean up libtheora structures. + + if( mTheoraDecoder ) + th_decode_free( mTheoraDecoder ); + if( mTheoraSetup ) + th_setup_free( mTheoraSetup ); + + th_comment_clear( &mTheoraComment ); + th_info_clear( &mTheoraInfo ); +} + +//----------------------------------------------------------------------------- + +bool OggTheoraDecoder::_detect( ogg_page* startPage ) +{ + _setStartPage( startPage ); + + // Read first header packet. + + ogg_packet nextPacket; + if( !_readNextPacket( &nextPacket ) + || th_decode_headerin( &mTheoraInfo, &mTheoraComment, &mTheoraSetup, &nextPacket ) < 0 ) + { + th_comment_clear( &mTheoraComment ); + th_info_clear( &mTheoraInfo ); + + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- + +bool OggTheoraDecoder::_init() +{ + ogg_packet nextPacket; + + // Read header packets. + + bool haveTheoraHeader = true; + while( 1 ) + { + if( !_readNextPacket( &nextPacket ) ) + { + haveTheoraHeader = false; + break; + } + + int result = th_decode_headerin( &mTheoraInfo, &mTheoraComment, &mTheoraSetup, &nextPacket ); + if( result < 0 ) + { + haveTheoraHeader = false; + break; + } + else if( result == 0 ) + break; + } + + // Fail if we have no valid and complete Theora header. + + if( !haveTheoraHeader ) + { + th_comment_clear( &mTheoraComment ); + th_info_clear( &mTheoraInfo ); + + Con::errorf( "OggTheoraDecoder::_init() - incorrect or corrupt Theora headers" ); + + return false; + } + + // Init the decoder. + + mTheoraDecoder = th_decode_alloc( &mTheoraInfo, mTheoraSetup ); + + // Feed the first video packet to the decoder. + + ogg_int64_t granulePos; + th_decode_packetin( mTheoraDecoder, &nextPacket, &granulePos ); + + mCurrentFrameTime = th_granule_time( mTheoraDecoder, granulePos ); + mCurrentFrameNumber = 0; + mFrameDuration = 1.f / getFramesPerSecond(); + + // Make sure we have a valid pitch. + + if( !mPacketFormat.mPitch ) + mPacketFormat.mPitch = getFrameWidth() * GFXFormatInfo( mPacketFormat.mFormat ).getBytesPerPixel(); + + return true; +} + +//----------------------------------------------------------------------------- + +bool OggTheoraDecoder::_packetin( ogg_packet* packet ) +{ + ogg_int64_t granulePos; + + if( th_decode_packetin( mTheoraDecoder, packet, &granulePos ) != 0 ) + return false; + + // See if we should drop this frame. + //RDTODO: if we have fallen too far behind, start skipping pages + + F32 granuleTime = th_granule_time( mTheoraDecoder, granulePos ); + mCurrentFrameTime = granuleTime; + mCurrentFrameNumber ++; + + bool dropThisFrame = false; + TimeSourceRef timeSource = mTimeSource; + if( timeSource ) + { + F32 currentTick = F32( timeSource->getPosition() ) / 1000.f; + + if( currentTick >= ( mCurrentFrameTime + mFrameDuration ) ) + dropThisFrame = true; + } + +#ifdef DEBUG_SPEW + Platform::outputDebugString( "[OggTheoraDecoder] new frame %i at %f sec%s", + U32( th_granule_frame( mTheoraDecoder, granulePos ) ), + granuleTime, + dropThisFrame ? " !! DROPPED !!" : "" ); +#endif + + return !dropThisFrame; +} + +//----------------------------------------------------------------------------- + +U32 OggTheoraDecoder::read( OggTheoraFrame** buffer, U32 num ) +{ + #ifdef TORQUE_DEBUG + AssertFatal( dCompareAndSwap( mLock, 0, 1 ), "OggTheoraDecoder::read() - simultaneous reads not thread-safe" ); + #endif + + U32 numRead = 0; + + for( U32 i = 0; i < num; ++ i ) + { + // Read and decode a packet. + + if( !_nextPacket() ) + return numRead; // End of stream. + + // Decode the frame to Y'CbCr. + + th_ycbcr_buffer ycbcr; + th_decode_ycbcr_out( mTheoraDecoder, ycbcr ); + + // Allocate a packet. + + const U32 width = getFrameWidth(); + const U32 height = getFrameHeight(); + + OggTheoraFrame* packet; + if( !mFreePackets.tryPopFront( packet ) ) + packet = constructSingle< OggTheoraFrame* >( mPacketFormat.mPitch * height ); + + packet->mFrameNumber = mCurrentFrameNumber; + packet->mFrameTime = mCurrentFrameTime; + packet->mFrameDuration = mFrameDuration; + + // Transcode the packet. + + #if ( defined( TORQUE_COMPILER_GCC ) || defined( TORQUE_COMPILER_VISUALC ) ) && defined( TORQUE_CPU_X86 ) + + if( ( mTranscoder == TRANSCODER_Auto || mTranscoder == TRANSCODER_SSE2420RGBA ) && + getDecoderPixelFormat() == PIXEL_FORMAT_420 && + Platform::SystemInfo.processor.properties & CPU_PROP_SSE2 && + mPacketFormat.mFormat == GFXFormatR8G8B8A8 && + mTheoraInfo.pic_x == 0 && + mTheoraInfo.pic_y == 0 ) + { + _transcode420toRGBA_SSE2( ycbcr, ( U8* ) packet->data, width, height, mPacketFormat.mPitch ); + } + else + + #endif + + { + // Use generic transcoder. + + _transcode( ycbcr, ( U8* ) packet->data, width, height ); + } + + buffer[ i ] = packet; + ++ numRead; + } + + #ifdef TORQUE_DEBUG + AssertFatal( dCompareAndSwap( mLock, 1, 0 ), "" ); + #endif + + return numRead; +} + +//----------------------------------------------------------------------------- + +void OggTheoraDecoder::_transcode( th_ycbcr_buffer ycbcr, U8* buffer, const U32 width, const U32 height ) +{ + #define ycbcrToRGB( rgb, pY, pCb, pCr, G ) \ + { \ + GFXPackPixel( \ + mPacketFormat.mFormat, \ + rgb, \ + sClamp[ sRGBY[ *pY ][ 2 ] + sRGBCr[ *pCr ][ 2 ] ], \ + sClamp[ sRGBY[ *pY ][ 1 ] + G ], \ + sClamp[ sRGBY[ *pY ][ 0 ] + sRGBCb[ *pCb ][ 0 ] ], \ + 255 \ + ); \ + } + + // Determine number of chroma samples per 4-pixel luma block. + + U32 numChromaSamples = 4; + EPixelFormat pixelFormat = getDecoderPixelFormat(); + if( pixelFormat == PIXEL_FORMAT_422 ) + numChromaSamples = 2; + else if( pixelFormat == OggTheoraDecoder::PIXEL_FORMAT_420 ) + numChromaSamples = 1; + + // Convert and copy the pixels. Deal with all three + // possible plane configurations. + + const U32 pictOffsetY = _getPictureOffset( ycbcr, 0 ); + const U32 pictOffsetU = _getPictureOffset( ycbcr, 1 ); + const U32 pictOffsetV = _getPictureOffset( ycbcr, 2 ); + + for( U32 y = 0; y < height; y += 2 ) + { + U8* dst0 = buffer + y * mPacketFormat.mPitch; + U8* dst1 = dst0 + mPacketFormat.mPitch; + + U8* pY0 = _getPixelPtr( ycbcr, 0, pictOffsetY, 0, y ); + U8* pY1 = _getPixelPtr( ycbcr, 0, pictOffsetY, 0, y + 1 ); + U8* pU0 = _getPixelPtr( ycbcr, 1, pictOffsetU, 0, y ); + U8* pU1 = _getPixelPtr( ycbcr, 1, pictOffsetU, 0, y + 1 ); + U8* pV0 = _getPixelPtr( ycbcr, 2, pictOffsetV, 0, y ); + U8* pV1 = _getPixelPtr( ycbcr, 2, pictOffsetV, 0, y + 1 ); + + for( U32 x = 0; x < width; x += 2 ) + { + // Pixel 0x0. + + S32 G = sampleG( pU0, pV0 ); + + ycbcrToRGB( dst0, pY0, pU0, pV0, G ); + + ++ pY0; + + if( numChromaSamples == 4 ) + { + ++ pU0; + ++ pV0; + } + + // Pixel 0x1. + + if( numChromaSamples == 4 ) + G = sampleG( pU0, pV0 ); + + ycbcrToRGB( dst0, pY0, pU0, pV0, G ); + + ++ pY0; + ++ pU0; + ++ pV0; + + // Pixel 1x0. + + if( numChromaSamples != 1 ) + G = sampleG( pU1, pV1 ); + + ycbcrToRGB( dst1, pY1, pU1, pV1, G ); + + ++ pY1; + + if( numChromaSamples == 4 ) + { + ++ pU1; + ++ pV1; + } + + // Pixel 1x1. + + if( numChromaSamples == 4 ) + G = sampleG( pU1, pV1 ); + + ycbcrToRGB( dst1, pY1, pU1, pV1, G ); + + ++ pY1; + ++ pU1; + ++ pV1; + } + } + + #undef ycbcrToRGB +} + +//----------------------------------------------------------------------------- + +void OggTheoraDecoder::_transcode420toRGBA_SSE2( th_ycbcr_buffer ycbcr, U8* buffer, U32 width, U32 height, U32 pitch ) +{ + AssertFatal( width % 2 == 0, "OggTheoraDecoder::_transcode420toRGBA_SSE2() - width must be multiple of 2" ); + AssertFatal( height % 2 == 0, "OggTheoraDecoder::_transcode420toRGBA_SSE2() - height must be multiple of 2" ); + + unsigned char* ydata = ycbcr[ 0 ].data; + unsigned char* udata = ycbcr[ 1 ].data; + unsigned char* vdata = ycbcr[ 2 ].data; + + S32* ycoeff = ( S32* ) sRGBY; + S32* ucoeff = ( S32* ) sRGBCb; + S32* vcoeff = ( S32* ) sRGBCr; + + // At the end of a line loop, we need to jump over the padding resulting from the difference + // between pitch and width plus jump a whole scanline as we always operate two scanlines + // at a time. + const U32 stride = pitch - width * 4 + pitch; + + // Same thing for the Y channel. + const U32 ystrideDelta = ycbcr[ 0 ].stride - width + ycbcr[ 0 ].stride; + const U32 ypitch = ycbcr[ 0 ].stride; + + // U and V only jump a single scanline so we only need to advance by the padding on the + // right. Both planes are half-size. + const U32 ustrideDelta = ycbcr[ 1 ].stride - width / 2; + const U32 vstrideDelta = ycbcr[ 2 ].stride - width / 2; + + #if defined( TORQUE_COMPILER_VISUALC ) && defined( TORQUE_CPU_X86 ) + + __asm + { + mov ecx,height + + hloop: + + push ecx + mov ecx,width + + wloop: + + push ecx + xor eax,eax + + // Load and accumulate coefficients for U and V in XMM0. + + mov esi,udata + mov ebx,ucoeff + mov edx,ydata + mov al,[esi] + xor ecx,ecx + mov edi,vdata + shl eax,4 + movdqa xmm0,[ebx+eax] + + mov ebx,vcoeff + mov cl,[edi] + mov esi,ycoeff + shl ecx,4 + paddd xmm0,[ebx+ecx] + xor eax,eax + xor ebx,ebx + + // Load coefficients for Y of the four pixels into XMM1-XMM4. + + mov ecx,ypitch + mov al,[edx] + mov bl,[edx+1] + shl eax,4 + shl ebx,4 + movdqa xmm1,[esi+eax] + movdqa xmm2,[esi+ebx] + xor eax,eax + xor ebx,ebx + + mov al,[edx+ecx] + mov bl,[edx+ecx+1] + shl eax,4 + shl ebx,4 + movdqa xmm3,[esi+eax] + movdqa xmm4,[esi+ebx] + + mov edi,buffer + mov ecx,pitch + + // Add Cb and Cr on top of Y. + + paddd xmm1,xmm0 + paddd xmm2,xmm0 + paddd xmm3,xmm0 + paddd xmm4,xmm0 + + // Pack pixels together. We need to pack twice per pixel + // to go from 32bits via 16bits to 8bits. + // + // Right now we're simply packing two garbage pixels for the + // second packing operation. An alternative would be to pack the + // four pixels into one XMM register and then do a packed shuffle + // to split out the lower two pixels before the move. + + packssdw xmm1,xmm2 + packssdw xmm3,xmm4 + packuswb xmm1,xmm6 + packuswb xmm3,xmm7 + + // Store pixels. + + movq qword ptr [edi],xmm1 + movq qword ptr [edi+ecx],xmm3 + + // Loop width. + + pop ecx + + add ydata,2 + inc udata + inc vdata + add buffer,8 + + sub ecx,2 + jnz wloop + + // Loop height. + + pop ecx + + mov ebx,stride + mov eax,ystrideDelta + mov edi,ustrideDelta + mov esi,vstrideDelta + + add buffer,ebx + add ydata,eax + add udata,edi + add vdata,esi + + sub ecx,2 + jnz hloop + }; + + #elif defined( TORQUE_COMPILER_GCC ) && defined( TORQUE_CPU_X86 ) + + asm( "pushal\n" // Save all general-purpose registers. + + "movl %0,%%ecx\n" // Load height into ECX. + + ".hloop_sse:\n" + + "pushl %%ecx\n" // Save counter. + "movl %1,%%ecx\n" // Load width into ECX. + + ".wloop_sse:\n" + + "pushl %%ecx\n" // Save counter. + "xorl %%eax,%%eax\n" // Zero out eax for later use. + + // Load and accumulate coefficients for U and V in XMM0. + + "movl %3,%%esi\n" // Load U pointer into ESI. + "movl %8,%%ebx\n" // Load U coefficient table into EBX. + "movl %2,%%edx\n" // Load Y pointer into EDX. + "movb (%%esi),%%al\n" // Load U into AL. + "xorl %%ecx,%%ecx\n" // Clear ECX. + "movl %4,%%edi\n" // Load V pointer into EDI. + "shll $4,%%eax\n" // Multiply EAX by 16 to index into table. + "movdqa (%%ebx,%%eax),%%xmm0\n" // Load Cb coefficient into XMM0. + + "movl %9,%%ebx\n" // Load V coefficients table into EBX. + "movb (%%edi),%%cl\n" // Load V into CL. + "movl %7,%%esi\n" // Load Y coefficients table into ESI. + "shll $4,%%ecx\n" // Multiply ECX by 16 to index into table. + "paddd (%%ebx,%%ecx),%%xmm0\n" // Add Cr coefficient to Cb coefficient. + "xorl %%eax,%%eax\n" // Clear EAX. + "xorl %%ebx,%%ebx\n" // Clear EBX. + + // Load coefficients for Y of the four pixels into XMM1-XMM4. + + "movl %14,%%ecx\n" // Load Y pitch into ECX (needed later for lower two pixels). + "movb (%%edx),%%al\n" // Load upper-left pixel Y into AL. + "movb 1(%%edx),%%bl\n" // Load upper-right pixel Y into BL. + "shll $4,%%eax\n" // Multiply EAX by 16 to index into table. + "shll $4,%%ebx\n" // Multiply EBX by 16 to index into table. + "movdqa (%%esi,%%eax),%%xmm1\n" // Load coefficient for upper-left pixel Y into XMM1. + "movdqa (%%esi,%%ebx),%%xmm2\n" // Load coefficient for upper-right pixel Y into XMM2. + "xorl %%eax,%%eax\n" // Clear EAX. + "xorl %%ebx,%%ebx\n" // Clear EBX. + + "movb (%%edx,%%ecx),%%al\n" // Load lower-left pixel Y into AL. + "movb 1(%%edx,%%ecx),%%bl\n" // Load lower-right pixel Y into AL. + "shll $4,%%eax\n" // Multiply EAX by 16 to index into table. + "shll $4,%%ebx\n" // Multiply EBX by 16 to index into table. + "movdqa (%%esi,%%eax),%%xmm3\n" // Load coefficient for lower-left pixel Y into XMM3. + "movdqa (%%esi,%%ebx),%%xmm4\n" // Load coefficient for lower-right pixel Y into XMM4. + + "movl %5,%%edi\n" // Load buffer pointer into EDI (for later use). + "movl %6,%%ecx\n" // Load pitch into ECX (for later use). + + // Add Cb and Cr on top of Y. + + "paddd %%xmm0,%%xmm1\n" // Add chroma channels to upper-left pixel. + "paddd %%xmm0,%%xmm2\n" // Add chroma channels to upper-right pixel. + "paddd %%xmm0,%%xmm3\n" // Add chroma channels to lower-left pixel. + "paddd %%xmm0,%%xmm4\n" // Add chroma channels to lower-right pixel. + + // Pack pixels together. We need to pack twice per pixel + // to go from 32bits via 16bits to 8bits. + // + // Right now we're simply packing two garbage pixels for the + // second packing operation. An alternative would be to pack the + // four pixels into one XMM register and then do a packed shuffle + // to split out the lower two pixels before the move. + + "packssdw %%xmm2,%%xmm1\n" // Pack 32bit channels together into 16bit channels on upper two pixels. + "packssdw %%xmm4,%%xmm3\n" // Pack 32bit channels together into 16bit channels on lower two pixels. + "packuswb %%xmm6,%%xmm1\n" // Pack 16bit channels together into 8bit channels on upper two pixels (plus two garbage pixels). + "packuswb %%xmm7,%%xmm3\n" // Pack 16bit channels together into 8bit channels on lower two pixels (plus two garbage pixels). + + // Store pixels. + + "movq %%xmm1,(%%edi)\n" // Store upper two pixels. + "movq %%xmm3,(%%edi,%%ecx)\n" // Store lower two pixels. + + // Loop width. + + "popl %%ecx\n" // Restore width counter. + + "addl $2,%2\n" // Bump Y pointer by two pixels (1 bpp). + "incl %3\n" // Bump U pointer by one pixel (1 bpp). + "incl %4\n" // Bump V pointer by one pixel (1 bpp). + "addl $8,%5\n" // Bump buffer pointer by two pixels (4 bpp). + + "subl $2,%%ecx\n" + "jnz .wloop_sse\n" + + // Loop height. + + "popl %%ecx\n" // Restore height counter. + + "movl %10,%%ebx\n" // Load buffer stride into EBX. + "movl %11,%%eax\n" // Load Y stride delta into EAX. + "movl %12,%%edi\n" // Load U stride delta into EDI. + "movl %13,%%esi\n" // Load V stride delta into ESI. + + "addl %%ebx,%5\n" // Bump buffer pointer by stride delta. + "addl %%eax,%2\n" // Bump Y pointer by stride delta. + "addl %%edi,%3\n" // Bump U pointer by stride delta. + "addl %%esi,%4\n" // Bump V pointer by stride delta. + + "subl $2,%%ecx\n" + "jnz .hloop_sse\n" + + "popal\n" + : + : "m" ( height ), // 0 + "m" ( width ), // 1 + "m" ( ydata ), // 2 + "m" ( udata ), // 3 + "m" ( vdata ), // 4 + "m" ( buffer ), // 5 + "m" ( pitch ), // 6 + "m" ( ycoeff ), // 7 + "m" ( ucoeff ), // 8 + "m" ( vcoeff ), // 9 + "m" ( stride ), // 10 + "m" ( ystrideDelta ), // 11 + "m" ( ustrideDelta ), // 12 + "m" ( vstrideDelta ), // 13 + "m" ( ypitch ) // 14 + ); + + #endif +} diff --git a/core/ogg/oggTheoraDecoder.h b/core/ogg/oggTheoraDecoder.h new file mode 100644 index 0000000..d2b8f74 --- /dev/null +++ b/core/ogg/oggTheoraDecoder.h @@ -0,0 +1,250 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _OGGTHEORADECODER_H_ +#define _OGGTHEORADECODER_H_ + +#ifndef _OGGINPUTSTREAM_H_ + #include "core/ogg/oggInputStream.h" +#endif +#ifndef _TSTREAM_H_ + #include "core/stream/tStream.h" +#endif +#ifndef _RAWDATA_H_ + #include "core/util/rawData.h" +#endif +#ifndef _GFXENUMS_H_ + #include "gfx/gfxEnums.h" +#endif +#ifndef _THREADSAFEDEQUE_H_ + #include "platform/threads/threadSafeDeque.h" +#endif +#include "theora/theoradec.h" + + +/// A single decoded Theora video frame. +class OggTheoraFrame : public RawData +{ + public: + + typedef RawData Parent; + + OggTheoraFrame() {} + OggTheoraFrame( S8* data, U32 size, bool ownMemory = false ) + : Parent( data, size, ownMemory ) {} + + /// Serial number of this frame in the stream. + U32 mFrameNumber; + + /// Playtime in seconds at which to display this frame. + F32 mFrameTime; + + /// Seconds to display this frame. + F32 mFrameDuration; +}; + + +/// Decodes a Theora substream into frame packets. +/// +/// Frame packets contain raw pixel data in the set pixel format (default is R8G8B8). +/// Reading on a thread is safe, but remember to keep a reference to the OggInputStream +/// master from the worker thread. +class OggTheoraDecoder : public OggDecoder, + public IInputStream< OggTheoraFrame* > +{ + public: + + typedef OggDecoder Parent; + + /// Y'CbCr pixel format of the source video stream. + /// For informational purposes only. Packet out is determined + /// by PacketFormat. + enum EPixelFormat + { + PIXEL_FORMAT_444, // Full Y, full Cb, full Cr. + PIXEL_FORMAT_422, // Full Y, half-width Cb, half-width Cr. + PIXEL_FORMAT_420, // Full Y, half-widht+height Cb, half-width+height Cr. + PIXEL_FORMAT_Unknown + }; + + /// Descriptor for surface format that this stream should + /// decode into. This saves an otherwise potentitally necessary + /// swizzling step. + /// + /// @note The output channel ordering will be in device format, i.e. + /// least-significant first. + struct PacketFormat + { + /// Pixel format. + GFXFormat mFormat; + + /// Bytes per scanline. + U32 mPitch; + + /// Default descriptor sets up for RGB. + PacketFormat() + : mFormat( GFXFormatR8G8B8 ), + mPitch( 0 ) {} + + /// + PacketFormat( GFXFormat format, U32 pitch ) + : mFormat( format ), + mPitch( pitch ) {} + }; + + /// + enum ETranscoder + { + TRANSCODER_Auto, ///< Auto-detect from current formats and processor capabilities. + TRANSCODER_Generic, ///< Generic transcoder that handles all source and target formats; 32bit integer + lookup tables. + TRANSCODER_SSE2420RGBA, ///< SSE2 transcoder with fixed 4:2:0 to RGBA conversion; 32bit integer + lookup tables. + }; + + protected: + + typedef IPositionable< U32 >* TimeSourceRef; + + /// @name libtheora Data + /// @{ + + /// + th_comment mTheoraComment; + + /// + th_info mTheoraInfo; + + /// + th_setup_info* mTheoraSetup; + + /// + th_dec_ctx* mTheoraDecoder; + + /// @} + + /// + PacketFormat mPacketFormat; + + /// + F32 mFrameDuration; + + /// + F32 mCurrentFrameTime; + + /// + U32 mCurrentFrameNumber; + + /// If this is set, the decoder will drop frames that are + /// already outdated with respect to the time source. + /// + /// @note Times are in milliseconds and in video time. + TimeSourceRef mTimeSource; + + /// Transcoder to use for color space conversion. If the current + /// setting is invalid, will fall back to generic. + ETranscoder mTranscoder; + + /// + ThreadSafeDeque< OggTheoraFrame* > mFreePackets; + + #ifdef TORQUE_DEBUG + U32 mLock; + #endif + + /// Generic transcoder going from any of the Y'CbCr pixel formats to + /// any RGB format (that is supported by GFXFormatUtils). + void _transcode( th_ycbcr_buffer ycbcr, U8* buffer, U32 width, U32 height ); + + /// Transcoder with fixed 4:2:0 to RGBA conversion using SSE2 assembly. + void _transcode420toRGBA_SSE2( th_ycbcr_buffer ycbcr, U8* buffer, U32 width, U32 height, U32 pitch ); + + // OggDecoder. + virtual bool _detect( ogg_page* startPage ); + virtual bool _init(); + virtual bool _packetin( ogg_packet* packet ); + + /// + U32 _getPixelOffset( th_ycbcr_buffer buffer, U32 plane, U32 x, U32 y ) const + { + switch( getDecoderPixelFormat() ) + { + case PIXEL_FORMAT_444: break; + case PIXEL_FORMAT_422: if( plane != 0 ) x >>= 1; break; + case PIXEL_FORMAT_420: if( plane != 0 ) { x >>= 1; y >>= 1; } break; + + default: + AssertFatal( false, "OggTheoraDecoder::_getPixelOffset() - invalid pixel format" ); + } + + return ( y * buffer[ plane ].stride + x ); + } + + /// + U8* _getPixelPtr( th_ycbcr_buffer buffer, U32 plane, U32 offset, U32 x, U32 y ) const + { + return ( buffer[ plane ].data + offset + _getPixelOffset( buffer, plane, x, y ) ); + } + + /// + U32 _getPictureOffset( th_ycbcr_buffer buffer, U32 plane ) + { + return _getPixelOffset( buffer, plane, mTheoraInfo.pic_x, mTheoraInfo.pic_y ); + } + + public: + + /// + OggTheoraDecoder( const ThreadSafeRef< OggInputStream >& stream ); + + ~OggTheoraDecoder(); + + /// Return the width of video image frames in pixels. + /// @note This returns the actual picture width rather than Theora's internal encoded frame width. + U32 getFrameWidth() const { return mTheoraInfo.pic_width; } + + /// Return the height of video image frames in pixels. + /// @note This returns the actual picture height rather than Theora's internal encoded frame height. + U32 getFrameHeight() const { return mTheoraInfo.pic_height; } + + /// + F32 getFramesPerSecond() const { return ( F32( mTheoraInfo.fps_numerator ) / F32( mTheoraInfo.fps_denominator ) ); } + + /// + EPixelFormat getDecoderPixelFormat() const + { + switch( mTheoraInfo.pixel_fmt ) + { + case TH_PF_444: return PIXEL_FORMAT_444; + case TH_PF_422: return PIXEL_FORMAT_422; + case TH_PF_420: return PIXEL_FORMAT_420; + default: return PIXEL_FORMAT_Unknown; + } + } + + /// + const PacketFormat& getPacketFormat() const { return mPacketFormat; } + + /// + void setPacketFormat( const PacketFormat& format ) { mPacketFormat = format; } + + /// Set the reference time source. Frames will be dropped if the decoder + /// falls behind the time of this source. + /// + /// @note The time source must have at least the same lifetime as the decoder. + void setTimeSource( const TimeSourceRef& timeSource ) { mTimeSource = timeSource; } + + /// Set the Y'CbCr->RGB transcoder to use. + void setTranscoder( ETranscoder transcoder ) { mTranscoder = transcoder; } + + /// + void reusePacket( OggTheoraFrame* packet ) { mFreePackets.pushBack( packet ); } + + // OggDecoder. + virtual const char* getName() const { return "Theora"; } + + // IInputStream. + virtual U32 read( OggTheoraFrame** buffer, U32 num ); +}; + +#endif // !_OGGTHEORADECODER_H_ diff --git a/core/ogg/oggVorbisDecoder.cpp b/core/ogg/oggVorbisDecoder.cpp new file mode 100644 index 0000000..db9fb18 --- /dev/null +++ b/core/ogg/oggVorbisDecoder.cpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/ogg/oggVorbisDecoder.h" +#include "console/console.h" + + +//#define DEBUG_SPEW + + +//----------------------------------------------------------------------------- + +OggVorbisDecoder::OggVorbisDecoder( const ThreadSafeRef< OggInputStream >& stream ) + : Parent( stream ) +#ifdef TORQUE_DEBUG + , mLock( 0 ) +#endif +{ + // Initialize. + + vorbis_info_init( &mVorbisInfo ); + vorbis_comment_init( &mVorbisComment ); + dMemset( &mVorbisBlock, 0, sizeof( mVorbisBlock ) ); + dMemset( &mVorbisDspState, 0, sizeof( mVorbisDspState ) ); +} + +//----------------------------------------------------------------------------- + +OggVorbisDecoder::~OggVorbisDecoder() +{ + vorbis_block_clear( &mVorbisBlock ); + vorbis_dsp_clear( &mVorbisDspState ); + vorbis_info_clear( &mVorbisInfo ); + vorbis_comment_clear( &mVorbisComment ); +} + +//----------------------------------------------------------------------------- + +bool OggVorbisDecoder::_detect( ogg_page* startPage ) +{ + _setStartPage( startPage ); + + // Read initial header packet. + + ogg_packet nextPacket; + if( !_readNextPacket( &nextPacket ) + || vorbis_synthesis_headerin( &mVorbisInfo, &mVorbisComment, &nextPacket ) < 0 ) + { + vorbis_info_clear( &mVorbisInfo ); + vorbis_comment_clear( &mVorbisComment ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- + +bool OggVorbisDecoder::_init() +{ + // Read header packets. + + bool haveVorbisHeader = true; + for( U32 i = 0; i < 2; ++ i ) + { + ogg_packet nextPacket; + if( !_readNextPacket( &nextPacket ) ) + { + haveVorbisHeader = false; + break; + } + + int result = vorbis_synthesis_headerin( &mVorbisInfo, &mVorbisComment, &nextPacket ); + if( result != 0 ) + { + haveVorbisHeader = false; + break; + } + } + + // Fail if we don't have a complete and valid Vorbis header. + + if( !haveVorbisHeader ) + { + vorbis_info_clear( &mVorbisInfo ); + vorbis_comment_clear( &mVorbisComment ); + + Con::errorf( "OggVorbisDecoder::_init() - Incorrect or corrupt Vorbis headers" ); + + return false; + } + + // Init synthesis. + + vorbis_synthesis_init( &mVorbisDspState, &mVorbisInfo ); + vorbis_block_init( &mVorbisDspState, &mVorbisBlock ); + + return true; +} + +//----------------------------------------------------------------------------- + +bool OggVorbisDecoder::_packetin( ogg_packet* packet ) +{ + return ( vorbis_synthesis( &mVorbisBlock, packet ) == 0 ); +} + +//----------------------------------------------------------------------------- + +U32 OggVorbisDecoder::read( RawData** buffer, U32 num ) +{ + #ifdef TORQUE_DEBUG + AssertFatal( dCompareAndSwap( mLock, 0, 1 ), "OggVorbisDecoder::read() - simultaneous reads not thread-safe" ); + #endif + + U32 numRead = 0; + + for( U32 i = 0; i < num; ++ i ) + { + float** pcmData; + U32 numSamples; + + // Read sample data. + + while( 1 ) + { + numSamples = vorbis_synthesis_pcmout( &mVorbisDspState, &pcmData ); + if( numSamples ) + break; + else + { + if( !_nextPacket() ) + return numRead; // End of stream. + + vorbis_synthesis_blockin( &mVorbisDspState, &mVorbisBlock ); + } + } + vorbis_synthesis_read( &mVorbisDspState, numSamples ); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[OggVorbisDecoder] read %i samples", numSamples ); + #endif + + // Allocate a packet. + + const U32 numChannels = getNumChannels(); + RawData* packet = constructSingle< RawData* >( numSamples * 2 * numChannels ); // Two bytes per channel. + + // Convert and copy the samples. + + S16* samplePtr = ( S16* ) packet->data; + for( U32 n = 0; n < numSamples; ++ n ) + for( U32 c = 0; c < numChannels; ++ c ) + { + S32 val = S32( pcmData[ c ][ n ] * 32767.f ); + if( val > 32767 ) + val = 32767; + else if( val < -34768 ) + val = -32768; + + *samplePtr = val; + ++ samplePtr; + } + + // Success. + + buffer[ i ] = packet; + numRead ++; + } + + #ifdef TORQUE_DEBUG + AssertFatal( dCompareAndSwap( mLock, 1, 0 ), "" ); + #endif + + return numRead; +} diff --git a/core/ogg/oggVorbisDecoder.h b/core/ogg/oggVorbisDecoder.h new file mode 100644 index 0000000..f2268a7 --- /dev/null +++ b/core/ogg/oggVorbisDecoder.h @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _OGGVORBISDECODER_H_ +#define _OGGVORBISDECODER_H_ + +#ifndef _OGGINPUTSTREAM_H_ + #include "core/ogg/oggInputStream.h" +#endif +#ifndef _TSTREAM_H_ + #include "core/stream/tStream.h" +#endif +#ifndef _RAWDATA_H_ + #include "core/util/rawData.h" +#endif +#include "vorbis/codec.h" + + +/// Decodes a Vorbis substream into sample packets. +/// +/// Vorbis samples are always 16bits. +class OggVorbisDecoder : public OggDecoder, + public IInputStream< RawData* > +{ + public: + + typedef OggDecoder Parent; + + protected: + + /// + vorbis_info mVorbisInfo; + + /// + vorbis_comment mVorbisComment; + + /// + vorbis_dsp_state mVorbisDspState; + + /// + vorbis_block mVorbisBlock; + + #ifdef TORQUE_DEBUG + U32 mLock; + #endif + + // OggDecoder. + virtual bool _detect( ogg_page* startPage ); + virtual bool _init(); + virtual bool _packetin( ogg_packet* packet ); + + public: + + /// + OggVorbisDecoder( const ThreadSafeRef< OggInputStream >& stream ); + + ~OggVorbisDecoder(); + + /// + U32 getNumChannels() const { return mVorbisInfo.channels; } + + /// + U32 getSamplesPerSecond() const { return mVorbisInfo.rate; } + + // OggDecoder. + virtual const char* getName() const { return "Vorbis"; } + + // IInputStream. + virtual U32 read( RawData** buffer, U32 num ); +}; + +#endif // !_OGGVORBISDECODER_H_ diff --git a/core/resizeStream.cpp b/core/resizeStream.cpp new file mode 100644 index 0000000..5b539ed --- /dev/null +++ b/core/resizeStream.cpp @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/resizeStream.h" + +ResizeFilterStream::ResizeFilterStream() + : m_pStream(NULL), + m_startOffset(0), + m_streamLen(0), + m_currOffset(0), + m_lastBytesRead(0) +{ + // +} + +ResizeFilterStream::~ResizeFilterStream() +{ + detachStream(); +} + +bool ResizeFilterStream::attachStream(Stream* io_pSlaveStream) +{ + AssertFatal(io_pSlaveStream != NULL, "NULL Slave stream?"); + + m_pStream = io_pSlaveStream; + m_startOffset = 0; + m_streamLen = m_pStream->getStreamSize(); + m_currOffset = 0; + setStatus(EOS); + return true; +} + +void ResizeFilterStream::detachStream() +{ + m_pStream = NULL; + m_startOffset = 0; + m_streamLen = 0; + m_currOffset = 0; + setStatus(Closed); +} + +Stream* ResizeFilterStream::getStream() +{ + return m_pStream; +} + +bool ResizeFilterStream::setStreamOffset(const U32 in_startOffset, const U32 in_streamLen) +{ + AssertFatal(m_pStream != NULL, "stream not attached!"); + if (m_pStream == NULL) + return false; + + U32 start = in_startOffset; + U32 end = in_startOffset + in_streamLen; + U32 actual = m_pStream->getStreamSize(); + + if (start >= actual || end > actual) + return false; + + m_startOffset = start; + m_streamLen = in_streamLen; + m_currOffset = 0; + + if (m_streamLen != 0) + setStatus(Ok); + else + setStatus(EOS); + + return true; +} + +U32 ResizeFilterStream::getPosition() const +{ + AssertFatal(m_pStream != NULL, "Error, stream not attached"); + if (m_pStream == NULL) + return 0; + + return m_currOffset; +} + +bool ResizeFilterStream::setPosition(const U32 in_newPosition) +{ + AssertFatal(m_pStream != NULL, "Error, stream not attached"); + if (m_pStream == NULL) + return false; + + if (in_newPosition < m_streamLen) { + m_currOffset = in_newPosition; + return true; + } else { + m_currOffset = m_streamLen; + return false; + } +} + +U32 ResizeFilterStream::getStreamSize() +{ + AssertFatal(m_pStream != NULL, "Error, stream not attached"); + + return m_streamLen; +} + +bool ResizeFilterStream::_read(const U32 in_numBytes, void* out_pBuffer) +{ + AssertFatal(m_pStream != NULL, "Error, stream not attached"); + m_lastBytesRead = 0; + + if (in_numBytes == 0) + return true; + + AssertFatal(out_pBuffer != NULL, "Invalid output buffer"); + if (getStatus() == Closed) { + AssertFatal(false, "Attempted read from closed stream"); + return false; + } + + U32 savePosition = m_pStream->getPosition(); + if (m_pStream->setPosition(m_startOffset + m_currOffset) == false) + return false; + + U32 actualSize = in_numBytes; + U32 position = m_startOffset + m_currOffset; + if (in_numBytes + position > m_startOffset + m_streamLen) + actualSize = m_streamLen - (position - m_startOffset); + + if (actualSize == 0) { + setStatus(EOS); + return false; + } + + bool success = m_pStream->read(actualSize, out_pBuffer); + m_currOffset += actualSize; + m_lastBytesRead = actualSize; + + setStatus(m_pStream->getStatus()); + + m_pStream->setPosition(savePosition); + return success; +} + diff --git a/core/resizeStream.h b/core/resizeStream.h new file mode 100644 index 0000000..77444c2 --- /dev/null +++ b/core/resizeStream.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RESIZESTREAM_H_ +#define _RESIZESTREAM_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _FILTERSTREAM_H_ +#include "core/filterStream.h" +#endif + +class ResizeFilterStream : public FilterStream, public IStreamByteCount +{ + typedef FilterStream Parent; + + Stream* m_pStream; + U32 m_startOffset; + U32 m_streamLen; + U32 m_currOffset; + U32 m_lastBytesRead; + + public: + ResizeFilterStream(); + ~ResizeFilterStream(); + + bool attachStream(Stream* io_pSlaveStream); + void detachStream(); + Stream* getStream(); + + bool setStreamOffset(const U32 in_startOffset, + const U32 in_streamLen); + + // Mandatory overrides. + protected: + bool _read(const U32 in_numBytes, void* out_pBuffer); + public: + U32 getPosition() const; + bool setPosition(const U32 in_newPosition); + + U32 getStreamSize(); + + // IStreamByteCount + U32 getLastBytesRead() { return m_lastBytesRead; } + U32 getLastBytesWritten() { return 0; } +}; + +#endif //_RESIZESTREAM_H_ diff --git a/core/resource.cpp b/core/resource.cpp new file mode 100644 index 0000000..45217c2 --- /dev/null +++ b/core/resource.cpp @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/resourceManager.h" +#include "core/volume.h" + +#include "console/console.h" + + +FreeListChunker ResourceHolderBase::smHolderFactory; + +ResourceBase::Header ResourceBase::smBlank; + + +U32 ResourceBase::Header::getChecksum() const +{ + Torque::FS::FileNodeRef fileRef = Torque::FS::GetFileNode( mPath ); + + if ( fileRef == NULL ) + { + Con::errorf("ResourceBase::getChecksum could not access file: [%s]", mPath.getFullPath().c_str() ); + return 0; + } + + return fileRef->getChecksum(); +} + +void ResourceBase::Header::destroySelf() +{ + if (this == &smBlank) + return; + + if( mNotifyUnload ) + mNotifyUnload( getPath(), getResource() ); + + if ( mResource != NULL ) + { + mResource->~ResourceHolderBase(); + ResourceHolderBase::smHolderFactory.free( mResource ); + } + + ResourceManager::get().remove( this ); + delete this; +} + +void ResourceBase::assign(const ResourceBase &inResource, void* resource) +{ + mResourceHeader = inResource.mResourceHeader; + + if ( mResourceHeader == NULL || mResourceHeader.getPointer() == &(ResourceBase::smBlank) ) + return; + + if (mResourceHeader->getSignature()) + { + AssertFatal(inResource.mResourceHeader->getSignature() == getSignature(),"Resource::assign: mis-matching signature"); + } + else + { + mResourceHeader->mSignature = getSignature(); + + const Torque::Path path = mResourceHeader->getPath(); + + if (resource == NULL) + { + if ( !getStaticLoadSignal().trigger(path, &resource) && (resource != NULL) ) + { + mResourceHeader->mResource = createHolder(resource); + mResourceHeader->mNotifyUnload = _getNotifyUnloadFn(); + _triggerPostLoadSignal(); + return; + } + + resource = create(path); + } + + if (resource) + { + mResourceHeader->mResource = createHolder(resource); + mResourceHeader->mNotifyUnload = _getNotifyUnloadFn(); + _triggerPostLoadSignal(); + } + else + { + // Failed to create...delete signature so we can attempt to successfully create resource later + Con::warnf("Failed to create resource: [%s]", path.getFullPath().c_str() ); + + mResourceHeader->mSignature = 0; + } + } +} + diff --git a/core/resource.h b/core/resource.h new file mode 100644 index 0000000..b170331 --- /dev/null +++ b/core/resource.h @@ -0,0 +1,306 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef __RESOURCE_H__ +#define __RESOURCE_H__ + +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif + +#ifndef _PATH_H_ +#include "core/util/path.h" +#endif + +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif + +#ifndef _TIMECLASS_H_ +#include "core/util/timeClass.h" +#endif + +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif + +#ifndef _PLATFORMASSERT_H_ +#include "platform/platformAssert.h" +#endif + +class ResourceManager; + +// This is a utility class used by the resource manager. +// The prime responsibility of this class is to delete +// a resource. The base type is never used, but a template +// version is always derived that knows how to delete the +// particular type of resource. Normally, one needs not +// worry about this class. However, if one wants to delete +// a particular resource in a special manner, one may +// override the ResourceHolder::~ResourceHolder method. +class ResourceHolderBase +{ +public: + static FreeListChunker smHolderFactory; + + virtual ~ResourceHolderBase() {} + + // Return void pointer to resource data. + void *getResource() const { return mRes; } + +protected: + // Construct a resource holder pointing at 'p'. + ResourceHolderBase(void *p) : mRes(p) {} + + void *mRes; +}; + +// All resources are derived from this type. The base type +// is only instantiated by the resource manager +// (the resource manager will return a base resource which a +// derived resource, Resource, will construct itself +// with). Base class handles locking and unlocking and +// provides several virtual functions to be defined by +// derived resource. +class ResourceBase +{ + friend class ResourceManager; + +protected: + class Header; + +public: + typedef U32 Signature; + +public: + ResourceBase(Header *header) { mResourceHeader = (header ? header : &smBlank); } + virtual ~ResourceBase() {} + + const Torque::Path &getPath() const + { + AssertFatal(mResourceHeader != NULL,"ResourceBase::getPath called on invalid resource"); + return mResourceHeader->getPath(); + } + + U32 getChecksum() const + { + AssertFatal(mResourceHeader != NULL,"ResourceBase::getChecksum called on invalid resource"); + return mResourceHeader->getChecksum(); + } + +protected: + + typedef void ( *NotifyUnloadFn )( const Torque::Path& path, void* resource ); + + class Header : public StrongRefBase + { + public: + Header() + : mSignature(0), + mResource(NULL), + mNotifyUnload( NULL ) + { + } + + const Torque::Path &getPath() const { return mPath; } + + Signature getSignature() const { return mSignature; } + void *getResource() const { return (mResource ? mResource->getResource() : NULL); } + U32 getChecksum() const; + + virtual void destroySelf(); + + private: + + friend class ResourceBase; + friend class ResourceManager; + + Signature mSignature; + ResourceHolderBase* mResource; + Torque::Path mPath; + NotifyUnloadFn mNotifyUnload; + }; + +protected: + static Header smBlank; + ResourceBase() : mResourceHeader(&smBlank) {} + + StrongRefPtr
mResourceHeader; + + void assign(const ResourceBase &inResource, void* resource = NULL); + + // The following functions are virtual, but cannot be pure-virtual + // because we need to be able to instantiate this class. + + // To be defined by derived class. Creates a resource + // holder of the desired type. Resource template handles + // this, so one should never need to override. + virtual ResourceHolderBase *createHolder(void *) + { + AssertFatal(0,"ResourceBase::createHolder: should not be called"); + return NULL; + } + + // Create a Resource of desired type using passed path. Derived + // resource class must define this. + virtual void *create(const Torque::Path &path) + { + AssertFatal(0,"ResourceBase::create: should not be called"); + return NULL; + } + + // Return signature for desired type. + virtual Signature getSignature() const + { + return mResourceHeader->getSignature(); + } + + virtual Signal &getStaticLoadSignal() + { + AssertFatal(0,"ResourceBase::getStaticLoadSignal: should not be called"); + static Signal sLoadSignal; + + return sLoadSignal; + } + + virtual void _triggerPostLoadSignal() {} + virtual NotifyUnloadFn _getNotifyUnloadFn() { return ( NotifyUnloadFn ) NULL; } +}; + +// This is a utility class used by resource manager. Classes derived +// from this template pretty much just know how to delete the template's +// type. +template class ResourceHolder : public ResourceHolderBase +{ +public: + ResourceHolder(T *t) : ResourceHolderBase(t) {} + virtual ~ResourceHolder() { delete ((T*)mRes); } +}; + +// Resource template. When dealing with resources, this is the +// class that will be used. One creates resources by opening or +// creating them via the resource manager. The resource manager +// returns ResourceBases, which can be used to construct any +// type of resource (see the constructors for this template). +// When instantiating classes using this template, it is always +// necessary to define the create and getSignature methods. +// The createMethod will be responsible for loading a resource +// from disk using passed path. +template class Resource : public ResourceBase +{ +public: + Resource() {} + Resource(const ResourceBase &base) { assign(base); } + + void operator=(const ResourceBase & base) { assign(base); } + T* operator->() { return getResource(); } + T& operator*() { return *getResource(); } + operator T*() { return getResource(); } + const T* operator->() const { return getResource(); } + const T& operator*() const { return *getResource(); } + operator const T*() const { return getResource(); } + + void setResource(const ResourceBase & base, void* resource) { assign(base, resource); } + + static Signature signature(); + + /// Registering with this signal will give an opportunity to handle resource + /// creation before calling the create() function. This may be used to handle + /// file extensions differently and allow a more plugin-like approach to + /// adding resources. Using this mechanism, one could, for example, override + /// the default methods for loading DTS without touching the main source. + static Signal &getLoadSignal() + { + static Signal sLoadSignal; + return sLoadSignal; + } + + /// Register with this signal to get notified when resources of this type + /// have been loaded. + static Signal< void( Resource< T >& ) >& getPostLoadSignal() + { + static Signal< void( Resource< T >& ) > sPostLoadSignal; + return sPostLoadSignal; + } + + /// Register with this signal to get notified when resources of this type + /// are about to get unloaded. + static Signal< void( const Torque::Path&, T* ) >& getUnloadSignal() + { + static Signal< void( const Torque::Path&, T* ) > sUnloadSignal; + return sUnloadSignal; + } + +private: + T *getResource() { return (T*)mResourceHeader->getResource(); } + const T *getResource() const { return (T*)mResourceHeader->getResource(); } + + Signature getSignature() const { return Resource::signature(); } + + ResourceHolderBase *createHolder(void *); + + Signal &getStaticLoadSignal() { return getLoadSignal(); } + + static void _notifyUnload( const Torque::Path& path, void* resource ) { getUnloadSignal().trigger( path, ( T* ) resource ); } + + virtual void _triggerPostLoadSignal() { getPostLoadSignal().trigger( *this ); } + virtual NotifyUnloadFn _getNotifyUnloadFn() { return ( NotifyUnloadFn ) &_notifyUnload; } + + // These are to be define by instantiated resources + // No generic version is provided...however, since + // base resources are instantiated by resource manager, + // these are not pure virtuals if undefined (but will assert)... + void *create(const Torque::Path &path); +}; + + +template inline ResourceHolderBase *Resource::createHolder(void *ptr) +{ + ResourceHolder *resHolder = (ResourceHolder*)(ResourceHolderBase::smHolderFactory.alloc()); + + resHolder = constructInPlace(resHolder,(T*)ptr); + + return resHolder; +} + +//----------------------------------------------------------------------------- +// Load Signal Hooks. +//----------------------------------------------------------------------------- + +/// This template may be used to register a load signal as follows: +/// static ResourceRegisterLoadSignal sgAuto( staticLoadFunction ); +template +class ResourceRegisterLoadSignal +{ +public: + ResourceRegisterLoadSignal( Delegate func ) + { + Resource::getLoadSignal().notify( func ); + } +}; + +template< class T > +class ResourceRegisterPostLoadSignal +{ + public: + + ResourceRegisterPostLoadSignal( Delegate< void( Resource< T >& ) > func ) + { + Resource< T >::getPostLoadSignal().notify( func ); + } +}; + +template< class T > +class ResourceRegisterUnloadSignal +{ + public: + + ResourceRegisterUnloadSignal( Delegate< void( const Torque::Path&, T* ) > func ) + { + Resource< T >::getUnloadSignal().notify( func ); + } +}; + +#endif // __RESOURCE_H__ diff --git a/core/resourceManager.cpp b/core/resourceManager.cpp new file mode 100644 index 0000000..0533c56 --- /dev/null +++ b/core/resourceManager.cpp @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/resourceManager.h" + +#include "core/volume.h" +#include "console/console.h" +#include "core/util/autoPtr.h" + +static AutoPtr< ResourceManager > smInstance; + +ResourceManager::ResourceManager() +: mIterSigFilter( U32_MAX ) +{ +} + +ResourceManager::~ResourceManager() +{ + // TODO: Dump resources that have not been released? +} + +ResourceManager &ResourceManager::get() +{ + if ( smInstance.isNull() ) + smInstance = new ResourceManager; + return *smInstance; +} + +ResourceBase ResourceManager::load(const Torque::Path &path) +{ +#ifdef TORQUE_DEBUG_RES_MANAGER + Con::printf( "ResourceManager::load : [%s]", path.getFullPath().c_str() ); +#endif + + ResourceHeaderMap::Iterator iter = mResourceHeaderMap.findOrInsert( path.getFullPath() ); + + ResourceHeaderMap::Pair &pair = *iter; + + if ( pair.value == NULL ) + { + pair.value = new ResourceBase::Header; + FS::AddChangeNotification( path, this, &ResourceManager::notifiedFileChanged ); + } + + ResourceBase::Header *header = pair.value; + + if (header->getSignature() == 0) + header->mPath = path; + + return ResourceBase( header ); +} + +ResourceBase ResourceManager::find(const Torque::Path &path) +{ +#ifdef TORQUE_DEBUG_RES_MANAGER + Con::printf( "ResourceManager::find : [%s]", path.getFullPath().c_str() ); +#endif + + ResourceHeaderMap::Iterator iter = mResourceHeaderMap.find( path.getFullPath() ); + + if ( iter == mResourceHeaderMap.end() ) + return ResourceBase(); + + ResourceHeaderMap::Pair &pair = *iter; + + ResourceBase::Header *header = pair.value; + + return ResourceBase(header); +} + +#ifdef TORQUE_DEBUG +void ResourceManager::dumpToConsole() +{ + const U32 numResources = mResourceHeaderMap.size(); + + if ( numResources == 0 ) + { + Con::printf( "ResourceManager is not managing any resources" ); + return; + } + + Con::printf( "ResourceManager is managing %d resources:", numResources ); + Con::printf( " [ref count/signature/path]" ); + + ResourceHeaderMap::Iterator iter; + + for( iter = mResourceHeaderMap.begin(); iter != mResourceHeaderMap.end(); ++iter ) + { + ResourceBase::Header *header = (*iter).value; + + char fourCC[ 5 ]; + *( ( U32* ) fourCC ) = header->getSignature(); + fourCC[ 4 ] = 0; + + Con::printf( " %3d %s [%s] ", header->getRefCount(), fourCC, (*iter).key.c_str() ); + } +} +#endif + +bool ResourceManager::remove( ResourceBase::Header* header ) +{ + const Path &path = header->getPath(); + +#ifdef TORQUE_DEBUG_RES_MANAGER + Con::printf( "ResourceManager::remove : [%s]", path.getFullPath().c_str() ); +#endif + + ResourceHeaderMap::Iterator iter = mResourceHeaderMap.find( path.getFullPath() ); + if ( iter != mResourceHeaderMap.end() && iter->value == header ) + { + AssertISV( header && (header->getRefCount() == 0), "ResourceManager error: trying to remove resource which is still in use." ); + mResourceHeaderMap.erase( iter ); + } + else + { + iter = mPrevResourceHeaderMap.find( path.getFullPath() ); + if ( iter == mPrevResourceHeaderMap.end() || iter->value != header ) + { + Con::errorf( "ResourceManager::remove : Trying to remove non-existent resource [%s]", path.getFullPath().c_str() ); + return false; + } + + AssertISV( header && (header->getRefCount() == 0), "ResourceManager error: trying to remove resource which is still in use." ); + mPrevResourceHeaderMap.erase( iter ); + } + + FS::RemoveChangeNotification( path, this, &ResourceManager::notifiedFileChanged ); + + return true; +} + +void ResourceManager::notifiedFileChanged( const Torque::Path &path ) +{ + // First, find the resource. + ResourceHeaderMap::Iterator iter = mResourceHeaderMap.find( path.getFullPath() ); + if ( iter == mResourceHeaderMap.end() ) + { + // We aren't managing this resource, but it may be in the + // previous resource list... either way we don't do a notification. + return; + } + + Con::warnf( "[ResourceManager::notifiedFileChanged] : File changed [%s]", path.getFullPath().c_str() ); + + ResourceBase::Header *header = (*iter).value; + ResourceBase::Signature sig = header->getSignature(); + mResourceHeaderMap.erase( iter ); + + // Move the resource into the previous resource map. + iter = mPrevResourceHeaderMap.findOrInsert( path ); + iter->value = header; + + // Now notify users of the resource change so they + // can release and reload. + mChangeSignal.trigger( sig, path ); +} + +ResourceBase ResourceManager::startResourceList( ResourceBase::Signature inSignature ) +{ + mIter = mResourceHeaderMap.begin(); + + mIterSigFilter = inSignature; + + return nextResource(); +} + +ResourceBase ResourceManager::nextResource() +{ + ResourceBase::Header *header = NULL; + + while( mIter != mResourceHeaderMap.end() ) + { + header = (*mIter).value; + + ++mIter; + + if ( mIterSigFilter == U32_MAX ) + return ResourceBase(header); + + if ( header->getSignature() == mIterSigFilter ) + return ResourceBase(header); + } + + return ResourceBase(); +} + +#ifdef TORQUE_DEBUG +ConsoleFunctionGroupBegin(ResourceManagerFunctions, "Resource management functions."); + +ConsoleFunction(resourceDump, void, 1, 1, "resourceDump() - list the currently managed resources") +{ + ResourceManager::get().dumpToConsole(); +} + +ConsoleFunctionGroupEnd( ResourceManagerFunctions ); +#endif diff --git a/core/resourceManager.h b/core/resourceManager.h new file mode 100644 index 0000000..7a05ca1 --- /dev/null +++ b/core/resourceManager.h @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RESOURCEMANAGER_H_ +#define _RESOURCEMANAGER_H_ + +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif + +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +using namespace Torque; + +class ResourceManager +{ +public: + + static ResourceManager &get(); + + ResourceBase load(const Torque::Path &path); + ResourceBase find(const Torque::Path &path); + + ResourceBase startResourceList( ResourceBase::Signature inSignature = U32_MAX ); + ResourceBase nextResource(); + + typedef Signal ChangedSignal; + + /// Registering with this signal will give an opportunity to handle a change to the + /// resource on disk. For example, if a PNG file is edited by the artist and saved + /// the ResourceManager will signal that the file has changed so the TextureManager + /// may act appropriately - which probably means to re-init the materials using that PNG. + /// The signal passes the Resource's signature so the callee may filter these. + ChangedSignal &getChangedSignal() { return mChangeSignal; } + +#ifdef TORQUE_DEBUG + void dumpToConsole(); +#endif + + ~ResourceManager(); + +protected: + + friend class ResourceBase::Header; + + ResourceManager(); + + bool remove( ResourceBase::Header* header ); + + void notifiedFileChanged( const Torque::Path &path ); + + typedef HashTable ResourceHeaderMap; + + /// The map of resources. + ResourceHeaderMap mResourceHeaderMap; + + /// The map of old resources which have been replaced by + /// new resources from a file change notification. + ResourceHeaderMap mPrevResourceHeaderMap; + + ResourceHeaderMap::Iterator mIter; + + U32 mIterSigFilter; + + ChangedSignal mChangeSignal; +}; + +#endif diff --git a/core/stream/bitStream.cpp b/core/stream/bitStream.cpp new file mode 100644 index 0000000..3bdf757 --- /dev/null +++ b/core/stream/bitStream.cpp @@ -0,0 +1,1126 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/stream/bitStream.h" + +#include "core/strings/stringFunctions.h" +#include "math/mathIO.h" +#include "console/consoleObject.h" +#include "platform/platformNet.h" +#include "core/bitVector.h" + + +static BitStream gPacketStream(NULL, 0); +static U8 gPacketBuffer[Net::MaxPacketDataSize]; + +// bitstream utility functions + +void BitStream::clearStringBuffer() +{ + static char stringBuf[256]; + stringBuf[0] = 0; +// setStringBuffer( stringBuf ); +} + +void BitStream::setStringBuffer(char buffer[256]) +{ +// stringBuffer = buffer; +} + +BitStream *BitStream::getPacketStream(U32 writeSize) +{ + if(!writeSize) + writeSize = Net::MaxPacketDataSize; + + gPacketStream.setBuffer(gPacketBuffer, writeSize, Net::MaxPacketDataSize); + gPacketStream.setPosition(0); + + return &gPacketStream; +} + +void BitStream::sendPacketStream(const NetAddress *addr) +{ + Net::sendto(addr, gPacketStream.getBuffer(), gPacketStream.getPosition()); +} + +// CodeReview WTF is this additional IsEqual? - BJG, 3/29/07 + +inline bool IsEqual(F32 a, F32 b) { return a == b; } + +ResizeBitStream::ResizeBitStream(U32 minSpace, U32 initialSize) : BitStream(NULL, 0, 0) +{ + mMinSpace = minSpace; + if(!initialSize) + initialSize = minSpace * 2; + U8 *buf = (U8 *) dMalloc(initialSize); + setBuffer(buf, initialSize, initialSize); +} + +ResizeBitStream::~ResizeBitStream() +{ + dFree(dataPtr); +} + +void ResizeBitStream::validate() +{ + if(getPosition() + mMinSpace > bufSize) + { + bufSize = getPosition() + mMinSpace * 2; + dataPtr = (U8 *) dRealloc(dataPtr, bufSize); + + maxReadBitNum = bufSize << 3; + maxWriteBitNum = bufSize << 3; + } +} + + +class HuffmanProcessor +{ + static const U32 csm_charFreqs[256]; + bool m_tablesBuilt; + + void buildTables(); + + struct HuffNode { + U32 pop; + + S16 index0; + S16 index1; + }; + struct HuffLeaf { + U32 pop; + + U8 numBits; + U8 symbol; + U32 code; // no code should be longer than 32 bits. + }; + // We have to be a bit careful with these, mSince they are pointers... + struct HuffWrap { + HuffNode* pNode; + HuffLeaf* pLeaf; + + public: + HuffWrap() : pNode(NULL), pLeaf(NULL) { } + + void set(HuffLeaf* in_leaf) { pNode = NULL; pLeaf = in_leaf; } + void set(HuffNode* in_node) { pLeaf = NULL; pNode = in_node; } + + U32 getPop() { if (pNode) return pNode->pop; else return pLeaf->pop; } + }; + + Vector m_huffNodes; + Vector m_huffLeaves; + + S16 determineIndex(HuffWrap&); + + void generateCodes(BitStream&, S32, S32); + + public: + HuffmanProcessor() : m_tablesBuilt(false) { } + + static HuffmanProcessor g_huffProcessor; + + bool readHuffBuffer(BitStream* pStream, char* out_pBuffer); + bool writeHuffBuffer(BitStream* pStream, const char* out_pBuffer, S32 maxLen); +}; + +HuffmanProcessor HuffmanProcessor::g_huffProcessor; + +void BitStream::setBuffer(void *bufPtr, S32 size, S32 maxSize) +{ + dataPtr = (U8 *) bufPtr; + bitNum = 0; + bufSize = size; + maxReadBitNum = size << 3; + if(maxSize < 0) + maxSize = size; + maxWriteBitNum = maxSize << 3; + error = false; + clearCompressionPoint(); +} + +U32 BitStream::getPosition() const +{ + return (bitNum + 7) >> 3; +} + + +bool BitStream::setPosition(const U32 pos) +{ + bitNum = pos << 3; + return (true); +} + +U32 BitStream::getStreamSize() +{ + return bufSize; +} + +U8 *BitStream::getBytePtr() +{ + return dataPtr + getPosition(); +} + + +U32 BitStream::getReadByteSize() +{ + return (maxReadBitNum >> 3) - getPosition(); +} + +void BitStream::clear() +{ + dMemset(dataPtr, 0, bufSize); +} + +void BitStream::writeClassId(U32 classId, U32 classType, U32 classGroup) +{ + AssertFatal(classType < NetClassTypesCount, "Out of range class type."); + AssertFatal(classGroup < NetClassGroupsCount, "Out of range class group."); + AssertFatal(classId < AbstractClassRep::NetClassCount[classGroup][classType], "Out of range class id."); + AssertFatal(AbstractClassRep::NetClassCount[classGroup][classType] < (1 << AbstractClassRep::NetClassBitSize[classGroup][classType]), + "NetClassBitSize too small!"); + + writeInt(classId, AbstractClassRep::NetClassBitSize[classGroup][classType]); +} + +S32 BitStream::readClassId(U32 classType, U32 classGroup) +{ + AssertFatal(classType < NetClassTypesCount, "Out of range class type."); + AssertFatal(classGroup < NetClassGroupsCount, "Out of range class group."); + AssertFatal(AbstractClassRep::NetClassCount[classGroup][classType] < (1 << AbstractClassRep::NetClassBitSize[classGroup][classType]), + "NetClassBitSize too small!"); + + S32 ret = readInt(AbstractClassRep::NetClassBitSize[classGroup][classType]); + + AssertFatal(ret < AbstractClassRep::NetClassCount[classGroup][classType], "BitStream::readClassId - unexpected class ID!"); + if(ret >= AbstractClassRep::NetClassCount[classGroup][classType]) + return -1; + return ret; +} + +void BitStream::writeBits(S32 bitCount, const void *bitPtr) +{ + if(!bitCount) + return; + + if(bitCount + bitNum > maxWriteBitNum) + { + error = true; + AssertFatal(false, "Out of range write"); + return; + } + + // [tom, 8/17/2006] This is probably a lot lamer then it needs to be. However, + // at least it doesnt clobber data or overrun the buffer like the old code did. + const U8 *ptr = (U8 *)bitPtr; + + for(S32 srcBitNum = 0;srcBitNum < bitCount;srcBitNum++) + { + if((*(ptr + (srcBitNum >> 3)) & (1 << (srcBitNum & 0x7))) != 0) + *(dataPtr + (bitNum >> 3)) |= (1 << (bitNum & 0x7)); + else + *(dataPtr + (bitNum >> 3)) &= ~(1 << (bitNum & 0x7)); + bitNum++; + } +} + +void BitStream::setBit(S32 bitCount, bool set) +{ + if(set) + *(dataPtr + (bitCount >> 3)) |= (1 << (bitCount & 0x7)); + else + *(dataPtr + (bitCount >> 3)) &= ~(1 << (bitCount & 0x7)); +} + +bool BitStream::testBit(S32 bitCount) +{ + return (*(dataPtr + (bitCount >> 3)) & (1 << (bitCount & 0x7))) != 0; +} + +bool BitStream::writeFlag(bool val) +{ + if(bitNum + 1 > maxWriteBitNum) + { + error = true; + AssertFatal(false, "Out of range write"); + return false; + } + if(val) + *(dataPtr + (bitNum >> 3)) |= (1 << (bitNum & 0x7)); + else + *(dataPtr + (bitNum >> 3)) &= ~(1 << (bitNum & 0x7)); + bitNum++; + return (val); +} + +void BitStream::readBits(S32 bitCount, void *bitPtr) +{ + if(!bitCount) + return; + if(bitCount + bitNum > maxReadBitNum) + { + error = true; + //AssertFatal(false, "Out of range read"); + AssertWarn(false, "Out of range read"); + return; + } + U8 *stPtr = dataPtr + (bitNum >> 3); + S32 byteCount = (bitCount + 7) >> 3; + + U8 *ptr = (U8 *) bitPtr; + + S32 downShift = bitNum & 0x7; + S32 upShift = 8 - downShift; + + U8 curB = *stPtr; + while(byteCount--) + { + U8 nextB = *++stPtr; + *ptr++ = (curB >> downShift) | (nextB << upShift); + curB = nextB; + } + + bitNum += bitCount; +} + +bool BitStream::_read(U32 size, void *dataPtr) +{ + readBits(size << 3, dataPtr); + return true; +} + +bool BitStream::_write(U32 size, const void *dataPtr) +{ + writeBits(size << 3, dataPtr); + return true; +} + +S32 BitStream::readInt(S32 bitCount) +{ + S32 ret = 0; + readBits(bitCount, &ret); + ret = convertLEndianToHost(ret); + if(bitCount == 32) + return ret; + else + ret &= (1 << bitCount) - 1; + return ret; +} + +void BitStream::writeInt(S32 val, S32 bitCount) +{ + val = convertHostToLEndian(val); + writeBits(bitCount, &val); +} + +void BitStream::writeFloat(F32 f, S32 bitCount) +{ + writeInt((S32)(f * ((1 << bitCount) - 1)), bitCount); +} + +F32 BitStream::readFloat(S32 bitCount) +{ + return readInt(bitCount) / F32((1 << bitCount) - 1); +} + +void BitStream::writeSignedFloat(F32 f, S32 bitCount) +{ + writeInt((S32)(((f + 1) * .5) * ((1 << bitCount) - 1)), bitCount); +} + +F32 BitStream::readSignedFloat(S32 bitCount) +{ + return readInt(bitCount) * 2 / F32((1 << bitCount) - 1) - 1.0f; +} + +void BitStream::writeSignedInt(S32 value, S32 bitCount) +{ + if(writeFlag(value < 0)) + writeInt(-value, bitCount - 1); + else + writeInt(value, bitCount - 1); +} + +S32 BitStream::readSignedInt(S32 bitCount) +{ + if(readFlag()) + return -readInt(bitCount - 1); + else + return readInt(bitCount - 1); +} + +void BitStream::writeNormalVector(const Point3F& vec, S32 bitCount) +{ + F32 phi = mAtan2(vec.x, vec.y) / M_PI; + F32 theta = mAtan2(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)) / (M_PI/2.0); + + writeSignedFloat(phi, bitCount+1); + writeSignedFloat(theta, bitCount); +} + +void BitStream::readNormalVector(Point3F *vec, S32 bitCount) +{ + F32 phi = readSignedFloat(bitCount+1) * M_PI; + F32 theta = readSignedFloat(bitCount) * (M_PI/2.0); + + vec->x = mSin(phi)*mCos(theta); + vec->y = mCos(phi)*mCos(theta); + vec->z = mSin(theta); +} + +Point3F BitStream::dumbDownNormal(const Point3F& vec, S32 bitCount) +{ + U8 buffer[128]; + BitStream temp(buffer, 128); + + temp.writeNormalVector(vec, bitCount); + temp.setCurPos(0); + + Point3F ret; + temp.readNormalVector(&ret, bitCount); + return ret; +} + +void BitStream::writeNormalVector(const Point3F& vec, S32 angleBitCount, S32 zBitCount) +{ + writeSignedFloat( mClampF(vec.z,-1.0f,1.0f), zBitCount ); + + // don't need to write x and y if they are both zero, which we can assess + // by checking for |z| == 1 + if(!IsEqual(mFabs(vec.z), 1.0f)) + { + writeSignedFloat( mAtan2(vec.x,vec.y) / M_2PI, angleBitCount ); + } + else + { + // angle won't matter... + writeSignedFloat(0.0f,angleBitCount); + } +} + +void BitStream::readNormalVector(Point3F * vec, S32 angleBitCount, S32 zBitCount) +{ + vec->z = readSignedFloat(zBitCount); + + F32 angle = M_2PI * readSignedFloat(angleBitCount); + + F32 mult = 1.0f - vec->z * vec->z; + if (mult>0.0f) + // be very careful with this just in case vec.z was a little over 1 + mult = mSqrt(mult); + else + mult = 0.0f; + + vec->x = mult * mSin(angle); + vec->y = mult * mCos(angle); +} +void BitStream::writeVector(Point3F vec, F32 minMag, F32 maxMag, S32 magBits, S32 angleBits, S32 zBits) +{ + F32 mag = vec.len(); + if (writeFlag(mag>minMag)) + { + if (writeFlag(magset(0,0,0); +} + +void BitStream::writeAffineTransform(const MatrixF& matrix) +{ +// AssertFatal(matrix.isAffine() == true, +// "BitStream::writeAffineTransform: Error, must write only affine transforms!"); + + Point3F pos; + matrix.getColumn(3, &pos); + mathWrite(*this, pos); + + QuatF q(matrix); + q.normalize(); + write(q.x); + write(q.y); + write(q.z); + writeFlag(q.w < 0.0); +} + +void BitStream::readAffineTransform(MatrixF* matrix) +{ + Point3F pos; + QuatF q; + + mathRead(*this, &pos); + read(&q.x); + read(&q.y); + read(&q.z); + q.w = mSqrt(1.0 - getMin(F32(((q.x * q.x) + (q.y * q.y) + (q.z * q.z))), 1.f)); + if (readFlag()) + q.w = -q.w; + + q.setMatrix(matrix); + matrix->setColumn(3, pos); +// AssertFatal(matrix->isAffine() == true, +// "BitStream::readAffineTransform: Error, transform should be affine after this function!"); +} + +void BitStream::writeBits( const BitVector &bitvec ) +{ + U32 size = bitvec.getSize(); + if ( writeFlag( size <= 127 ) ) + writeInt( size, 7 ); + else + write( size ); + + writeBits( bitvec.getSize(), bitvec.getBits() ); +} + +void BitStream::readBits( BitVector *bitvec ) +{ + U32 size; + if ( readFlag() ) // size <= 127 + size = readInt( 7 ); + else + read( &size ); + + bitvec->setSize( size ); + readBits( size, bitvec->getNCBits() ); +} + +//---------------------------------------------------------------------------- + +void BitStream::clearCompressionPoint() +{ + mCompressPoint.set(0,0,0); +} + +void BitStream::setCompressionPoint(const Point3F& p) +{ + mCompressPoint = p; +} + +static U32 gBitCounts[4] = { + 16, 18, 20, 32 +}; + +void BitStream::writeCompressedPoint(const Point3F& p,F32 scale) +{ + // Same # of bits for all axis + Point3F vec; + F32 invScale = 1 / scale; + U32 type; + vec = p - mCompressPoint; + F32 dist = vec.len() * invScale; + if(dist < (1 << 15)) + type = 0; + else if(dist < (1 << 17)) + type = 1; + else if(dist < (1 << 19)) + type = 2; + else + type = 3; + + writeInt(type, 2); + + if (type != 3) + { + type = gBitCounts[type]; + writeSignedInt(S32(vec.x * invScale),type); + writeSignedInt(S32(vec.y * invScale),type); + writeSignedInt(S32(vec.z * invScale),type); + } + else + { + write(p.x); + write(p.y); + write(p.z); + } +} + +void BitStream::readCompressedPoint(Point3F* p,F32 scale) +{ + // Same # of bits for all axis + U32 type = readInt(2); + + if(type == 3) + { + read(&p->x); + read(&p->y); + read(&p->z); + } + else + { + type = gBitCounts[type]; + p->x = (F32)readSignedInt(type); + p->y = (F32)readSignedInt(type); + p->z = (F32)readSignedInt(type); + + p->x = mCompressPoint.x + p->x * scale; + p->y = mCompressPoint.y + p->y * scale; + p->z = mCompressPoint.z + p->z * scale; + } +} + +//------------------------------------------------------------------------------ + +InfiniteBitStream::InfiniteBitStream() +{ + // +} + +InfiniteBitStream::~InfiniteBitStream() +{ + // +} + +void InfiniteBitStream::reset() +{ + // Rewing back to beginning + setPosition(0); +} + +void InfiniteBitStream::validate(U32 upcomingBytes) +{ + if(getPosition() + upcomingBytes + mMinSpace > bufSize) + { + bufSize = getPosition() + upcomingBytes + mMinSpace; + dataPtr = (U8 *) dRealloc(dataPtr, bufSize); + + maxReadBitNum = bufSize << 3; + maxWriteBitNum = bufSize << 3; + } +} + +void InfiniteBitStream::compact() +{ + // Prepare to copy... + U32 oldSize = bufSize; + U8 *tmp = (U8*)dMalloc(bufSize); + + // Copy things... + bufSize = getPosition() + mMinSpace * 2; + dMemcpy(tmp, dataPtr, oldSize); + + // And clean up. + dFree(dataPtr); + dataPtr = tmp; + + maxReadBitNum = bufSize << 3; + maxWriteBitNum = bufSize << 3; +} + +void InfiniteBitStream::writeToStream(Stream &s) +{ + s.write(getPosition(), dataPtr); +} + +//------------------------------------------------------------------------------ + +void BitStream::readString(char buf[256]) +{ + if(stringBuffer) + { + if(readFlag()) + { + S32 offset = readInt(8); + HuffmanProcessor::g_huffProcessor.readHuffBuffer(this, stringBuffer + offset); + dStrcpy(buf, stringBuffer); + return; + } + } + HuffmanProcessor::g_huffProcessor.readHuffBuffer(this, buf); + if(stringBuffer) + dStrcpy(stringBuffer, buf); +} + +void BitStream::writeString(const char *string, S32 maxLen) +{ + if(!string) + string = ""; + if(stringBuffer) + { + S32 j; + for(j = 0; j < maxLen && stringBuffer[j] == string[j] && string[j];j++) + ; + dStrncpy(stringBuffer, string, maxLen); + stringBuffer[maxLen] = 0; + + if(writeFlag(j > 2)) + { + writeInt(j, 8); + HuffmanProcessor::g_huffProcessor.writeHuffBuffer(this, string + j, maxLen - j); + return; + } + } + HuffmanProcessor::g_huffProcessor.writeHuffBuffer(this, string, maxLen); +} + +void HuffmanProcessor::buildTables() +{ + AssertFatal(m_tablesBuilt == false, "Cannot build tables twice!"); + m_tablesBuilt = true; + + S32 i; + + // First, construct the array of wraps... + // + m_huffLeaves.setSize(256); + m_huffNodes.reserve(256); + m_huffNodes.increment(); + for (i = 0; i < 256; i++) { + HuffLeaf& rLeaf = m_huffLeaves[i]; + + rLeaf.pop = csm_charFreqs[i] + 1; + rLeaf.symbol = U8(i); + + dMemset(&rLeaf.code, 0, sizeof(rLeaf.code)); + rLeaf.numBits = 0; + } + + S32 currWraps = 256; + HuffWrap* pWrap = new HuffWrap[256]; + for (i = 0; i < 256; i++) { + pWrap[i].set(&m_huffLeaves[i]); + } + + while (currWraps != 1) { + U32 min1 = 0xfffffffe, min2 = 0xffffffff; + S32 index1 = -1, index2 = -1; + + for (i = 0; i < currWraps; i++) { + if (pWrap[i].getPop() < min1) { + min2 = min1; + index2 = index1; + + min1 = pWrap[i].getPop(); + index1 = i; + } else if (pWrap[i].getPop() < min2) { + min2 = pWrap[i].getPop(); + index2 = i; + } + } + AssertFatal(index1 != -1 && index2 != -1 && index1 != index2, "hrph"); + + // Create a node for this... + m_huffNodes.increment(); + HuffNode& rNode = m_huffNodes.last(); + rNode.pop = pWrap[index1].getPop() + pWrap[index2].getPop(); + rNode.index0 = determineIndex(pWrap[index1]); + rNode.index1 = determineIndex(pWrap[index2]); + + S32 mergeIndex = index1 > index2 ? index2 : index1; + S32 nukeIndex = index1 > index2 ? index1 : index2; + pWrap[mergeIndex].set(&rNode); + + if (index2 != (currWraps - 1)) { + pWrap[nukeIndex] = pWrap[currWraps - 1]; + } + currWraps--; + } + AssertFatal(currWraps == 1, "wrong wraps?"); + AssertFatal(pWrap[0].pNode != NULL && pWrap[0].pLeaf == NULL, "Wrong wrap type!"); + + // Ok, now we have one wrap, which is a node. we need to make sure that this + // is the first node in the node list. + m_huffNodes[0] = *(pWrap[0].pNode); + delete [] pWrap; + + U32 code = 0; + BitStream bs(&code, 4); + + generateCodes(bs, 0, 0); +} + +void HuffmanProcessor::generateCodes(BitStream& rBS, S32 index, S32 depth) +{ + if (index < 0) { + // leaf node, copy the code in, and back out... + HuffLeaf& rLeaf = m_huffLeaves[-(index + 1)]; + + dMemcpy(&rLeaf.code, rBS.dataPtr, sizeof(rLeaf.code)); + rLeaf.numBits = depth; + } else { + HuffNode& rNode = m_huffNodes[index]; + + S32 pos = rBS.getCurPos(); + + rBS.writeFlag(false); + generateCodes(rBS, rNode.index0, depth + 1); + + rBS.setCurPos(pos); + rBS.writeFlag(true); + generateCodes(rBS, rNode.index1, depth + 1); + + rBS.setCurPos(pos); + } +} + +S16 HuffmanProcessor::determineIndex(HuffWrap& rWrap) +{ + if (rWrap.pLeaf != NULL) { + AssertFatal(rWrap.pNode == NULL, "Got a non-NULL pNode in a HuffWrap with a non-NULL leaf."); + + return -((rWrap.pLeaf - m_huffLeaves.address()) + 1); + } else { + AssertFatal(rWrap.pNode != NULL, "Got a NULL pNode in a HuffWrap with a NULL leaf."); + + return rWrap.pNode - m_huffNodes.address(); + } +} + +bool HuffmanProcessor::readHuffBuffer(BitStream* pStream, char* out_pBuffer) +{ + if (m_tablesBuilt == false) + buildTables(); + + if (pStream->readFlag()) { + S32 len = pStream->readInt(8); + for (S32 i = 0; i < len; i++) { + S32 index = 0; + while (true) { + if (index >= 0) { + if (pStream->readFlag() == true) { + index = m_huffNodes[index].index1; + } else { + index = m_huffNodes[index].index0; + } + } else { + out_pBuffer[i] = m_huffLeaves[-(index+1)].symbol; + break; + } + } + } + out_pBuffer[len] = '\0'; + return true; + } else { + // Uncompressed string... + U32 len = pStream->readInt(8); + pStream->read(len, out_pBuffer); + out_pBuffer[len] = '\0'; + return true; + } +} + +bool HuffmanProcessor::writeHuffBuffer(BitStream* pStream, const char* out_pBuffer, S32 maxLen) +{ + if (out_pBuffer == NULL) { + pStream->writeFlag(false); + pStream->writeInt(0, 8); + return true; + } + + if (m_tablesBuilt == false) + buildTables(); + + S32 len = out_pBuffer ? dStrlen(out_pBuffer) : 0; + AssertWarn(len <= 255, "String TOO long for writeString"); + AssertWarn(len <= 255, out_pBuffer); + if (len > maxLen) + len = maxLen; + + S32 numBits = 0; + S32 i; + for (i = 0; i < len; i++) + numBits += m_huffLeaves[(unsigned char)out_pBuffer[i]].numBits; + + if (numBits >= (len * 8)) { + pStream->writeFlag(false); + pStream->writeInt(len, 8); + pStream->write(len, out_pBuffer); + } else { + pStream->writeFlag(true); + pStream->writeInt(len, 8); + for (i = 0; i < len; i++) { + HuffLeaf& rLeaf = m_huffLeaves[((unsigned char)out_pBuffer[i])]; + pStream->writeBits(rLeaf.numBits, &rLeaf.code); + } + } + + return true; +} + +const U32 HuffmanProcessor::csm_charFreqs[256] = { +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +329 , +21 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +0 , +2809 , +68 , +0 , +27 , +0 , +58 , +3 , +62 , +4 , +7 , +0 , +0 , +15 , +65 , +554 , +3 , +394 , +404 , +189 , +117 , +30 , +51 , +27 , +15 , +34 , +32 , +80 , +1 , +142 , +3 , +142 , +39 , +0 , +144 , +125 , +44 , +122 , +275 , +70 , +135 , +61 , +127 , +8 , +12 , +113 , +246 , +122 , +36 , +185 , +1 , +149 , +309 , +335 , +12 , +11 , +14 , +54 , +151 , +0 , +0 , +2 , +0 , +0 , +211 , +0 , +2090 , +344 , +736 , +993 , +2872 , +701 , +605 , +646 , +1552 , +328 , +305 , +1240 , +735 , +1533 , +1713 , +562 , +3 , +1775 , +1149 , +1469 , +979 , +407 , +553 , +59 , +279 , +31 , +0 , +0 , +0 , +68 , +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 , +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 , +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 +}; + diff --git a/core/stream/bitStream.h b/core/stream/bitStream.h new file mode 100644 index 0000000..ad079ee --- /dev/null +++ b/core/stream/bitStream.h @@ -0,0 +1,338 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITSTREAM_H_ +#define _BITSTREAM_H_ + +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _CRC_H_ +#include "core/crc.h" +#endif + +//-------------------------------------- Some caveats when using this class: +// - Get/setPosition semantics are changed +// to indicate bit position rather than +// byte position. +// + +class Point3F; +class MatrixF; +class HuffmanProcessor; +class BitVector; + +class BitStream : public Stream +{ +protected: + U8 *dataPtr; + S32 bitNum; + S32 bufSize; + bool error; + S32 maxReadBitNum; + S32 maxWriteBitNum; + char *stringBuffer; + Point3F mCompressPoint; + + friend class HuffmanProcessor; +public: + static BitStream *getPacketStream(U32 writeSize = 0); + static void sendPacketStream(const NetAddress *addr); + + void setBuffer(void *bufPtr, S32 bufSize, S32 maxSize = 0); + U8* getBuffer() { return dataPtr; } + U8* getBytePtr(); + + U32 getReadByteSize(); + + S32 getCurPos() const; + void setCurPos(const U32); + + // HACK: We reverted BitStream to this previous version + // because it was crashing the build. + // + // These are just here so that we don't have to revert + // the changes from the rest of the code. + // + // 9/11/2008 - Tom Spilman + // + S32 getBitPosition() const { return getCurPos(); } + void clearStringBuffer(); + + BitStream(void *bufPtr, S32 bufSize, S32 maxWriteSize = -1) { setBuffer(bufPtr, bufSize,maxWriteSize); stringBuffer = NULL; } + void clear(); + + void setStringBuffer(char buffer[256]); + void writeInt(S32 value, S32 bitCount); + S32 readInt(S32 bitCount); + + /// Use this method to write out values in a concise but ass backwards way... + /// Good for values you expect to be frequently zero, often small. Worst case + /// this will bloat values by nearly 20% (5 extra bits!) Best case you'll get + /// one bit (if it's zero). + /// + /// This is not so much for efficiency's sake, as to make life painful for + /// people that want to reverse engineer our network or file formats. + void writeCussedU32(U32 val) + { + // Is it zero? + if(writeFlag(val == 0)) + return; + + if(writeFlag(val <= 0xF)) // 4 bit + writeRangedU32(val, 0, 0xF); + else if(writeFlag(val <= 0xFF)) // 8 bit + writeRangedU32(val, 0, 0xFF); + else if(writeFlag(val <= 0xFFFF)) // 16 bit + writeRangedU32(val, 0, 0xFFFF); + else if(writeFlag(val <= 0xFFFFFF)) // 24 bit + writeRangedU32(val, 0, 0xFFFFFF); + else + writeRangedU32(val, 0, 0xFFFFFFFF); + } + + U32 readCussedU32() + { + if(readFlag()) + return 0; + + if(readFlag()) + return readRangedU32(0, 0xF); + else if(readFlag()) + return readRangedU32(0, 0xFF); + else if(readFlag()) + return readRangedU32(0, 0xFFFF); + else if(readFlag()) + return readRangedU32(0, 0xFFFFFF); + else + return readRangedU32(0, 0xFFFFFFFF); + } + + void writeSignedInt(S32 value, S32 bitCount); + S32 readSignedInt(S32 bitCount); + + void writeRangedU32(U32 value, U32 rangeStart, U32 rangeEnd); + U32 readRangedU32(U32 rangeStart, U32 rangeEnd); + + /// Writes a clamped signed integer to the stream using + /// an optimal amount of bits for the range. + void writeRangedS32( S32 value, S32 min, S32 max ); + + /// Reads a ranged signed integer written with writeRangedS32. + S32 readRangedS32( S32 min, S32 max ); + + // read and write floats... floats are 0 to 1 inclusive, signed floats are -1 to 1 inclusive + + F32 readFloat(S32 bitCount); + F32 readSignedFloat(S32 bitCount); + + void writeFloat(F32 f, S32 bitCount); + void writeSignedFloat(F32 f, S32 bitCount); + + /// Writes a clamped floating point value to the + /// stream with the desired bits of precision. + void writeRangedF32( F32 value, F32 min, F32 max, U32 numBits ); + + /// Reads a ranged floating point value written with writeRangedF32. + F32 readRangedF32( F32 min, F32 max, U32 numBits ); + + void writeClassId(U32 classId, U32 classType, U32 classGroup); + S32 readClassId(U32 classType, U32 classGroup); // returns -1 if the class type is out of range + + // writes a normalized vector + void writeNormalVector(const Point3F& vec, S32 bitCount); + void readNormalVector(Point3F *vec, S32 bitCount); + + void clearCompressionPoint(); + void setCompressionPoint(const Point3F& p); + + // Matching calls to these compression methods must, of course, + // have matching scale values. + void writeCompressedPoint(const Point3F& p,F32 scale = 0.01f); + void readCompressedPoint(Point3F* p,F32 scale = 0.01f); + + // Uses the above method to reduce the precision of a normal vector so the server can + // determine exactly what is on the client. (Pre-dumbing the vector before sending + // to the client can result in precision errors...) + static Point3F dumbDownNormal(const Point3F& vec, S32 bitCount); + + + // writes a normalized vector using alternate method + void writeNormalVector(const Point3F& vec, S32 angleBitCount, S32 zBitCount); + void readNormalVector(Point3F *vec, S32 angleBitCount, S32 zBitCount); + + void readVector(Point3F * vec, F32 minMag, F32 maxMag, S32 magBits, S32 angleBits, S32 zBits); + void writeVector(Point3F vec, F32 minMag, F32 maxMag, S32 magBits, S32 angleBits, S32 zBits); + // writes an affine transform (full precision version) + void writeAffineTransform(const MatrixF&); + void readAffineTransform(MatrixF*); + + virtual void writeBits(S32 bitCount, const void *bitPtr); + virtual void readBits(S32 bitCount, void *bitPtr); + virtual bool writeFlag(bool val); + + inline bool writeFlag(U32 val) + { + return writeFlag(val != 0); + } + + inline bool writeFlag(void *val) + { + return writeFlag(val != 0); + } + + virtual bool readFlag(); + + void writeBits(const BitVector &bitvec); + void readBits(BitVector *bitvec); + + void setBit(S32 bitCount, bool set); + bool testBit(S32 bitCount); + + bool isFull() { return bitNum > (bufSize << 3); } + bool isValid() { return !error; } + + bool _read (const U32 size,void* d); + bool _write(const U32 size,const void* d); + + void readString(char stringBuf[256]); + void writeString(const char *stringBuf, S32 maxLen=255); + + bool hasCapability(const Capability) const { return true; } + U32 getPosition() const; + bool setPosition(const U32 in_newPosition); + U32 getStreamSize(); +}; + +class ResizeBitStream : public BitStream +{ +protected: + U32 mMinSpace; +public: + ResizeBitStream(U32 minSpace = 1500, U32 initialSize = 0); + void validate(); + ~ResizeBitStream(); +}; + +/// This class acts to provide an "infinitely extending" stream. +/// +/// Basically, it does what ResizeBitStream does, but it validates +/// on every write op, so that you never have to worry about overwriting +/// the buffer. +class InfiniteBitStream : public ResizeBitStream +{ +public: + InfiniteBitStream(); + ~InfiniteBitStream(); + + /// Ensure we have space for at least upcomingBytes more bytes in the stream. + void validate(U32 upcomingBytes); + + /// Reset the stream to zero length (but don't clean memory). + void reset(); + + /// Shrink the buffer down to match the actual size of the data. + void compact(); + + /// Write us out to a stream... Results in last byte getting padded! + void writeToStream(Stream &s); + + virtual void writeBits(S32 bitCount, const void *bitPtr) + { + validate((bitCount >> 3) + 1); // Add a little safety. + BitStream::writeBits(bitCount, bitPtr); + } + + virtual bool writeFlag(bool val) + { + validate(1); // One bit will at most grow our buffer by a byte. + return BitStream::writeFlag(val); + } + + const U32 getCRC() + { + // This could be kinda inefficient - BJG + return CRC::calculateCRC(getBuffer(), getStreamSize()); + } +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- INLINES +// +inline S32 BitStream::getCurPos() const +{ + return bitNum; +} + +inline void BitStream::setCurPos(const U32 in_position) +{ + AssertFatal(in_position < (U32)(bufSize << 3), "Out of range bitposition"); + bitNum = S32(in_position); +} + +inline bool BitStream::readFlag() +{ + if(bitNum > maxReadBitNum) + { + error = true; + AssertFatal(false, "Out of range read"); + return false; + } + S32 mask = 1 << (bitNum & 0x7); + bool ret = (*(dataPtr + (bitNum >> 3)) & mask) != 0; + bitNum++; + return ret; +} + +inline void BitStream::writeRangedU32(U32 value, U32 rangeStart, U32 rangeEnd) +{ + AssertFatal(value >= rangeStart && value <= rangeEnd, "Out of bounds value!"); + AssertFatal(rangeEnd >= rangeStart, "error, end of range less than start"); + + U32 rangeSize = rangeEnd - rangeStart + 1; + U32 rangeBits = getBinLog2(getNextPow2(rangeSize)); + + writeInt(S32(value - rangeStart), S32(rangeBits)); +} + +inline U32 BitStream::readRangedU32(U32 rangeStart, U32 rangeEnd) +{ + AssertFatal(rangeEnd >= rangeStart, "error, end of range less than start"); + + U32 rangeSize = rangeEnd - rangeStart + 1; + U32 rangeBits = getBinLog2(getNextPow2(rangeSize)); + + U32 val = U32(readInt(S32(rangeBits))); + return val + rangeStart; +} + +inline void BitStream::writeRangedS32( S32 value, S32 min, S32 max ) +{ + value = mClamp( value, min, max ); + writeRangedU32( ( value - min ), 0, ( max - min ) ); +} + +inline S32 BitStream::readRangedS32( S32 min, S32 max ) +{ + return readRangedU32( 0, ( max - min ) ) + min; +} + +inline void BitStream::writeRangedF32( F32 value, F32 min, F32 max, U32 numBits ) +{ + value = ( mClampF( value, min, max ) - min ) / ( max - min ); + writeInt( (S32)mFloor(value * F32( (1 << numBits) - 1 )), numBits ); +} + +inline F32 BitStream::readRangedF32( F32 min, F32 max, U32 numBits ) +{ + F32 value = (F32)readInt( numBits ); + value /= F32( ( 1 << numBits ) - 1 ); + return min + value * ( max - min ); +} + +#endif //_BITSTREAM_H_ diff --git a/core/stream/fileStream.cpp b/core/stream/fileStream.cpp new file mode 100644 index 0000000..ca8efcd --- /dev/null +++ b/core/stream/fileStream.cpp @@ -0,0 +1,562 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/fileStream.h" + +#include "platform/platform.h" + +//----------------------------------------------------------------------------- +// FileStream methods... +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +FileStream::FileStream() +{ + // initialize the file stream + init(); +} + +FileStream *FileStream::createAndOpen(const String &inFileName, Torque::FS::File::AccessMode inMode) +{ + FileStream *newStream = new FileStream; + + if ( newStream ) + { + bool success = newStream->open( inFileName, inMode ); + + if ( !success ) + { + delete newStream; + newStream = NULL; + } + } + + return newStream; +} + +//----------------------------------------------------------------------------- +FileStream::~FileStream() +{ + // make sure the file stream is closed + close(); +} + +//----------------------------------------------------------------------------- +bool FileStream::hasCapability(const Capability i_cap) const +{ + return(0 != (U32(i_cap) & mStreamCaps)); +} + +//----------------------------------------------------------------------------- +U32 FileStream::getPosition() const +{ + AssertFatal(0 != mStreamCaps, "FileStream::getPosition: the stream isn't open"); + //AssertFatal(true == hasCapability(StreamPosition), "FileStream::getPosition(): lacks positioning capability"); + + // return the position inside the buffer if its valid, otherwise return the underlying file position + return((BUFFER_INVALID != mBuffHead) ? mBuffPos : mFile->getPosition()); +} + +//----------------------------------------------------------------------------- +bool FileStream::setPosition(const U32 i_newPosition) +{ + AssertFatal(0 != mStreamCaps, "FileStream::setPosition: the stream isn't open"); + AssertFatal(hasCapability(StreamPosition), "FileStream::setPosition: lacks positioning capability"); + + // if the buffer is valid, test the new position against the bounds of the buffer + if ((BUFFER_INVALID != mBuffHead) && (i_newPosition >= mBuffHead) && (i_newPosition <= mBuffTail)) + { + // set the position and return + mBuffPos = i_newPosition; + + // FIXME [tom, 9/5/2006] This needs to be checked. Basically, when seeking within + // the buffer, if the stream has an EOS status before the seek then if you try to + // read immediately after seeking, you'll incorrectly get an EOS. + // + // I am not 100% sure if this fix is correct, but it seems to be working for the undo system. + if(mBuffPos < mBuffTail) + Stream::setStatus(Ok); + + return(true); + } + // otherwise the new position lies in some block not in memory + else + { + if (mDirty) + flush(); + + clearBuffer(); + + mFile->setPosition(i_newPosition, Torque::FS::File::Begin); + + setStatus(); + + if (mFile->getStatus() == Torque::FS::FileNode::EndOfFile) + mEOF = true; + + return(Ok == getStatus() || EOS == getStatus()); + } +} + +//----------------------------------------------------------------------------- +U32 FileStream::getStreamSize() +{ + AssertWarn(0 != mStreamCaps, "FileStream::getStreamSize: the stream isn't open"); + AssertFatal((BUFFER_INVALID != mBuffHead && true == mDirty) || false == mDirty, "FileStream::getStreamSize: buffer must be valid if its dirty"); + + // the stream size may not match the size on-disk if its been written to... + if (mDirty) + return(getMax((U32)(mFile->getSize()), mBuffTail + 1)); ///<@todo U64 vs U32 issue + // otherwise just get the size on disk... + else + return(mFile->getSize()); +} + +//----------------------------------------------------------------------------- +bool FileStream::open(const String &inFileName, Torque::FS::File::AccessMode inMode) +{ + AssertWarn(0 == mStreamCaps, "FileStream::setPosition: the stream is already open"); + AssertFatal(inFileName.isNotEmpty(), "FileStream::open: empty filename"); + + // make sure the file stream's state is clean + clearBuffer(); + + Torque::Path filePath(inFileName); + + // IF we are writing, make sure the path exists + if( inMode == Torque::FS::File::Write || inMode == Torque::FS::File::WriteAppend || inMode == Torque::FS::File::ReadWrite ) + Torque::FS::CreatePath(filePath); + + mFile = Torque::FS::OpenFile(filePath, inMode); + + if (mFile != NULL) + { + setStatus(); + switch (inMode) + { + case Torque::FS::File::Read: + mStreamCaps = U32(StreamRead) | + U32(StreamPosition); + break; + case Torque::FS::File::Write: + case Torque::FS::File::WriteAppend: + mStreamCaps = U32(StreamWrite) | + U32(StreamPosition); + break; + case Torque::FS::File::ReadWrite: + mStreamCaps = U32(StreamRead) | + U32(StreamWrite) | + U32(StreamPosition); + break; + default: + AssertFatal(false, String::ToString( "FileStream::open: bad access mode on %s", inFileName.c_str() )); + } + } + else + { + Stream::setStatus(IOError); + return(false); + } + + return getStatus() == Ok; +} + +//----------------------------------------------------------------------------- +void FileStream::close() +{ + if (getStatus() == Closed) + return; + + if (mFile != NULL ) + { + // make sure nothing in the buffer differs from what is on disk + if (mDirty) + flush(); + + // and close the file + mFile->close(); + + AssertFatal(mFile->getStatus() == Torque::FS::FileNode::Closed, "FileStream::close: close failed"); + + mFile = NULL; + } + + // clear the file stream's state + init(); +} + +//----------------------------------------------------------------------------- +bool FileStream::flush() +{ + AssertWarn(0 != mStreamCaps, "FileStream::flush: the stream isn't open"); + AssertFatal(false == mDirty || BUFFER_INVALID != mBuffHead, "FileStream::flush: buffer must be valid if its dirty"); + + // if the buffer is dirty + if (mDirty) + { + AssertFatal(hasCapability(StreamWrite), "FileStream::flush: a buffer without write-capability should never be dirty"); + + // align the file pointer to the buffer head + if (mBuffHead != mFile->getPosition()) + { + mFile->setPosition(mBuffHead, Torque::FS::File::Begin); + if (mFile->getStatus() != Torque::FS::FileNode::Open && mFile->getStatus() != Torque::FS::FileNode::EndOfFile) + return(false); + } + + // write contents of the buffer to disk + U32 blockHead; + calcBlockHead(mBuffHead, &blockHead); + mFile->write((char *)mBuffer + (mBuffHead - blockHead), mBuffTail - mBuffHead + 1); + // and update the file stream's state + setStatus(); + if (EOS == getStatus()) + mEOF = true; + + if (Ok == getStatus() || EOS == getStatus()) + // and update the status of the buffer + mDirty = false; + else + return(false); + } + return(true); +} + +//----------------------------------------------------------------------------- +bool FileStream::_read(const U32 i_numBytes, void *o_pBuffer) +{ + AssertFatal(0 != mStreamCaps, "FileStream::_read: the stream isn't open"); + AssertFatal(NULL != o_pBuffer || i_numBytes == 0, "FileStream::_read: NULL destination pointer with non-zero read request"); + + if (!hasCapability(Stream::StreamRead)) + { + AssertFatal(false, "FileStream::_read: file stream lacks capability"); + Stream::setStatus(IllegalCall); + return(false); + } + + // exit on pre-existing errors + if (Ok != getStatus()) + return(false); + + // if a request of non-zero length was made + if (0 != i_numBytes) + { + U8 *pDst = (U8 *)o_pBuffer; + U32 readSize; + U32 remaining = i_numBytes; + U32 bytesRead; + U32 blockHead; + U32 blockTail; + + // check if the buffer has some data in it + if (BUFFER_INVALID != mBuffHead) + { + // copy as much as possible from the buffer into the destination + readSize = ((mBuffTail + 1) >= mBuffPos) ? (mBuffTail + 1 - mBuffPos) : 0; + readSize = getMin(readSize, remaining); + calcBlockHead(mBuffPos, &blockHead); + dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), readSize); + // reduce the remaining amount to read + remaining -= readSize; + // advance the buffer pointers + mBuffPos += readSize; + pDst += readSize; + + if (mBuffPos > mBuffTail && remaining != 0) + { + flush(); + mBuffHead = BUFFER_INVALID; + if (mEOF == true) + Stream::setStatus(EOS); + } + } + + // if the request wasn't satisfied by the buffer and the file has more data + if (false == mEOF && 0 < remaining) + { + // flush the buffer if its dirty, since we now need to go to disk + if (true == mDirty) + flush(); + + // make sure we know the current read location in the underlying file + mBuffPos = mFile->getPosition(); + calcBlockBounds(mBuffPos, &blockHead, &blockTail); + + // check if the data to be read falls within a single block + if ((mBuffPos + remaining) <= blockTail) + { + // fill the buffer from disk + if (true == fillBuffer(mBuffPos)) + { + // copy as much as possible from the buffer to the destination + remaining = getMin(remaining, mBuffTail - mBuffPos + 1); + dMemcpy(pDst, mBuffer + (mBuffPos - blockHead), remaining); + // advance the buffer pointer + mBuffPos += remaining; + } + else + return(false); + } + // otherwise the remaining spans multiple blocks + else + { + clearBuffer(); + // read from disk directly into the destination + bytesRead = mFile->read((char *)pDst, remaining); + setStatus(); + // check to make sure we read as much as expected + if (Ok == getStatus() || EOS == getStatus()) + { + // if not, update the end-of-file status + if (0 != bytesRead && EOS == getStatus()) + { + Stream::setStatus(Ok); + mEOF = true; + } + } + else + return(false); + } + } + } + return(true); +} + +//----------------------------------------------------------------------------- +bool FileStream::_write(const U32 i_numBytes, const void *i_pBuffer) +{ + AssertFatal(0 != mStreamCaps, "FileStream::_write: the stream isn't open"); + AssertFatal(NULL != i_pBuffer || i_numBytes == 0, "FileStream::_write: NULL source buffer pointer on non-zero write request"); + + if (!hasCapability(Stream::StreamWrite)) + { + AssertFatal(false, "FileStream::_write: file stream lacks capability"); + Stream::setStatus(IllegalCall); + return(false); + } + + // exit on pre-existing errors + if (Ok != getStatus() && EOS != getStatus()) + return(false); + + // if a request of non-zero length was made + if (0 != i_numBytes) + { + U8 *pSrc = (U8 *)i_pBuffer; + U32 writeSize; + U32 remaining = i_numBytes; + U32 bytesWrit; + U32 blockHead; + U32 blockTail; + + // check if the buffer is valid + if (BUFFER_INVALID != mBuffHead) + { + // copy as much as possible from the source to the buffer + calcBlockBounds(mBuffHead, &blockHead, &blockTail); + writeSize = (mBuffPos > blockTail) ? 0 : blockTail - mBuffPos + 1; + writeSize = getMin(writeSize, remaining); + + AssertFatal(0 == writeSize || (mBuffPos - blockHead) < BUFFER_SIZE, "FileStream::_write: out of bounds buffer position"); + dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, writeSize); + // reduce the remaining amount to be written + remaining -= writeSize; + // advance the buffer pointers + mBuffPos += writeSize; + mBuffTail = getMax(mBuffTail, mBuffPos - 1); + pSrc += writeSize; + // mark the buffer dirty + if (0 < writeSize) + mDirty = true; + } + + // if the request wasn't satisfied by the buffer + if (0 < remaining) + { + // flush the buffer if its dirty, since we now need to go to disk + if (mDirty) + flush(); + + // make sure we know the current write location in the underlying file + mBuffPos = mFile->getPosition(); + calcBlockBounds(mBuffPos, &blockHead, &blockTail); + + // check if the data to be written falls within a single block + if ((mBuffPos + remaining) <= blockTail) + { + // write the data to the buffer + dMemcpy(mBuffer + (mBuffPos - blockHead), pSrc, remaining); + // update the buffer pointers + mBuffHead = mBuffPos; + mBuffPos += remaining; + mBuffTail = mBuffPos - 1; + // mark the buffer dirty + mDirty = true; + } + // otherwise the remaining spans multiple blocks + else + { + clearBuffer(); + // write to disk directly from the source + bytesWrit = mFile->write((char *)pSrc, remaining); + setStatus(); + return(Ok == getStatus() || EOS == getStatus()); + } + } + } + return(true); +} + +//----------------------------------------------------------------------------- +void FileStream::init() +{ + mStreamCaps = 0; + Stream::setStatus(Closed); + clearBuffer(); +} + +//----------------------------------------------------------------------------- +bool FileStream::fillBuffer(const U32 i_startPosition) +{ + AssertFatal(0 != mStreamCaps, "FileStream::fillBuffer: the stream isn't open"); + AssertFatal(false == mDirty, "FileStream::fillBuffer: buffer must be clean to fill"); + + // make sure start position and file pointer jive + if (i_startPosition != mFile->getPosition()) + { + mFile->setPosition(i_startPosition, Torque::FS::File::Begin); + if (mFile->getStatus() != Torque::FS::FileNode::Open && mFile->getStatus() != Torque::FS::FileNode::EndOfFile) + { + setStatus(); + return(false); + } + else + // update buffer pointer + mBuffPos = i_startPosition; + } + + // check if file pointer is at end-of-file + if (EOS == getStatus()) + { + // invalidate the buffer + mBuffHead = BUFFER_INVALID; + // set the status to end-of-stream + mEOF = true; + } + // otherwise + else + { + U32 blockHead; + // locate bounds of buffer containing current position + calcBlockHead(mBuffPos, &blockHead); + // read as much as possible from input file + U32 bytesRead = mFile->read((char *)mBuffer + (i_startPosition - blockHead), BUFFER_SIZE - (i_startPosition - blockHead)); + setStatus(); + if (Ok == getStatus() || EOS == getStatus()) + { + // update buffer pointers + mBuffHead = i_startPosition; + mBuffPos = i_startPosition; + mBuffTail = i_startPosition + bytesRead - 1; + // update end-of-file status + if (0 != bytesRead && EOS == getStatus()) + { + Stream::setStatus(Ok); + mEOF = true; + } + } + else + { + mBuffHead = BUFFER_INVALID; + return(false); + } + } + return(true); +} + +//----------------------------------------------------------------------------- +void FileStream::clearBuffer() +{ + mBuffHead = BUFFER_INVALID; + mBuffPos = 0; + mBuffTail = 0; + mDirty = false; + mEOF = false; +} + +//----------------------------------------------------------------------------- +void FileStream::calcBlockHead(const U32 i_position, U32 *o_blockHead) +{ + AssertFatal(NULL != o_blockHead, "FileStream::calcBlockHead: NULL pointer passed for block head"); + + *o_blockHead = i_position/BUFFER_SIZE * BUFFER_SIZE; +} + +//----------------------------------------------------------------------------- +void FileStream::calcBlockBounds(const U32 i_position, U32 *o_blockHead, U32 *o_blockTail) +{ + AssertFatal(NULL != o_blockHead, "FileStream::calcBlockBounds: NULL pointer passed for block head"); + AssertFatal(NULL != o_blockTail, "FileStream::calcBlockBounds: NULL pointer passed for block tail"); + + *o_blockHead = i_position/BUFFER_SIZE * BUFFER_SIZE; + *o_blockTail = *o_blockHead + BUFFER_SIZE - 1; +} + +//----------------------------------------------------------------------------- +void FileStream::setStatus() +{ + switch (mFile->getStatus()) + { + case Torque::FS::FileNode::Open: + Stream::setStatus(Ok); + break; + + case Torque::FS::FileNode::Closed: + Stream::setStatus(Closed); + break; + + case Torque::FS::FileNode::EndOfFile: + Stream::setStatus(EOS); + break; + + case Torque::FS::FileNode::FileSystemFull: + case Torque::FS::FileNode::NoSuchFile: + case Torque::FS::FileNode::AccessDenied: + case Torque::FS::FileNode::NoDisk: + case Torque::FS::FileNode::SharingViolation: + Stream::setStatus(IOError); + break; + + case Torque::FS::FileNode::IllegalCall: + Stream::setStatus(IllegalCall); + break; + + case Torque::FS::FileNode::UnknownError: + Stream::setStatus(UnknownError); + break; + + default: + AssertFatal(false, "FileStream::setStatus: invalid error mode"); + } +} + +FileStream* FileStream::clone() const +{ + Torque::FS::File::AccessMode mode; + if( hasCapability( StreamWrite ) && hasCapability( StreamRead ) ) + mode = Torque::FS::File::ReadWrite; + else if( hasCapability( StreamWrite ) ) + mode = Torque::FS::File::Write; + else + mode = Torque::FS::File::Read; + + FileStream* copy = createAndOpen( mFile->getName(), mode ); + if( copy && copy->setPosition( getPosition() ) ) + return copy; + + delete copy; + return NULL; +} diff --git a/core/stream/fileStream.h b/core/stream/fileStream.h new file mode 100644 index 0000000..f460c0c --- /dev/null +++ b/core/stream/fileStream.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILESTREAM_H_ +#define _FILESTREAM_H_ + +#ifndef _VOLUME_H_ +#include "core/volume.h" +#endif +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif + +class FileStream : public Stream +{ +public: + enum + { + BUFFER_SIZE = 8 * 1024, // this can be changed to anything appropriate [in k] + BUFFER_INVALID = 0xffffffff // file offsets must all be less than this + }; + +public: + FileStream(); // default constructor + virtual ~FileStream(); // destructor + + // This function will allocate a new FileStream and open it. + // If it fails to allocate or fails to open, it will return NULL. + // The caller is responsible for deleting the instance. + static FileStream *createAndOpen(const String &inFileName, Torque::FS::File::AccessMode inMode); + + // mandatory methods from Stream base class... + virtual bool hasCapability(const Capability i_cap) const; + + virtual U32 getPosition() const; + virtual bool setPosition(const U32 i_newPosition); + virtual U32 getStreamSize(); + + // additional methods needed for a file stream... + virtual bool open(const String &inFileName, Torque::FS::File::AccessMode inMode); + virtual void close(); + + bool flush(); + FileStream* clone() const; + +protected: + // more mandatory methods from Stream base class... + virtual bool _read(const U32 i_numBytes, void *o_pBuffer); + virtual bool _write(const U32 i_numBytes, const void* i_pBuffer); + + void init(); + bool fillBuffer(const U32 i_startPosition); + void clearBuffer(); + static void calcBlockHead(const U32 i_position, U32 *o_blockHead); + static void calcBlockBounds(const U32 i_position, U32 *o_blockHead, U32 *o_blockTail); + void setStatus(); + + U32 mStreamCaps; // dependent on access mode + +private: + Torque::FS::FileRef mFile; // file being streamed + + U8 mBuffer[BUFFER_SIZE]; + U32 mBuffHead; // first valid position of buffer (from start-of-file) + U32 mBuffPos; // next read or write will occur here + U32 mBuffTail; // last valid position in buffer (inclusive) + bool mDirty; // whether buffer has been written to + bool mEOF; // whether disk reads have reached the end-of-file + + FileStream(const FileStream &i_fileStrm); // disable copy constructor + FileStream& operator=(const FileStream &i_fileStrm); // disable assignment operator +}; + +#endif // _FILE_STREAM_H diff --git a/core/stream/fileStreamObject.cpp b/core/stream/fileStreamObject.cpp new file mode 100644 index 0000000..ca34a16 --- /dev/null +++ b/core/stream/fileStreamObject.cpp @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/fileStreamObject.h" + +//----------------------------------------------------------------------------- +// Local Globals +//----------------------------------------------------------------------------- + +static const struct +{ + const char *strMode; + Torque::FS::File::AccessMode mode; +} gModeMap[]= +{ + { "read", Torque::FS::File::Read }, + { "write", Torque::FS::File::Write }, + { "readwrite", Torque::FS::File::ReadWrite }, + { "writeappend", Torque::FS::File::WriteAppend }, + { NULL, (Torque::FS::File::AccessMode)0 } +}; + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +FileStreamObject::FileStreamObject() +{ +} + +FileStreamObject::~FileStreamObject() +{ + close(); +} + +IMPLEMENT_CONOBJECT(FileStreamObject); + +//----------------------------------------------------------------------------- + +bool FileStreamObject::onAdd() +{ + // [tom, 2/12/2007] Skip over StreamObject's onAdd() so that we can + // be instantiated from script. + return SimObject::onAdd(); +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +bool FileStreamObject::open(const char *filename, Torque::FS::File::AccessMode mode) +{ + close(); + + if(! mFileStream.open(filename, mode)) + return false; + + mStream = &mFileStream; + return true; +} + +void FileStreamObject::close() +{ + mFileStream.close(); + mStream = NULL; +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(FileStreamObject, open, bool, 4, 4, "(filename, mode) Open a file. Mode can be one of Read, Write, ReadWrite or WriteAppend.") +{ + for(S32 i = 0;gModeMap[i].strMode;++i) + { + if(dStricmp(gModeMap[i].strMode, argv[3]) == 0) + { + Torque::FS::File::AccessMode mode = gModeMap[i].mode; + char buffer[1024]; + Con::expandScriptFilename(buffer, sizeof(buffer), argv[2]); + return object->open(buffer, mode); + } + } + + Con::errorf("FileStreamObject::open - Mode must be one of Read, Write, ReadWrite or WriteAppend."); + return false; +} + +ConsoleMethod(FileStreamObject, close, void, 2, 2, "() Close the file.") +{ + object->close(); +} diff --git a/core/stream/fileStreamObject.h b/core/stream/fileStreamObject.h new file mode 100644 index 0000000..02fdd41 --- /dev/null +++ b/core/stream/fileStreamObject.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FILESTREAMOBJECT_H_ +#define _FILESTREAMOBJECT_H_ + +#ifndef _STREAMOBJECT_H_ +#include "core/stream/streamObject.h" +#endif + +#ifndef _FILESTREAM_H_ +#include "core/stream/fileStream.h" +#endif + + +class FileStreamObject : public StreamObject +{ + typedef StreamObject Parent; + +protected: + FileStream mFileStream; + +public: + FileStreamObject(); + virtual ~FileStreamObject(); + DECLARE_CONOBJECT(FileStreamObject); + + virtual bool onAdd(); + + //----------------------------------------------------------------------------- + /// @brief Open a file + /// + /// @param filename Name of file to open + /// @param mode One of #Torque::FS::File::Read, #Torque::FS::File::Write, #Torque::FS::File::ReadWrite or #Torque::FS::File::WriteAppend + /// @return true for success, false for failure + /// @see close() + //----------------------------------------------------------------------------- + bool open(const char *filename, Torque::FS::File::AccessMode mode); + + //----------------------------------------------------------------------------- + /// @brief Close the file + /// + /// @see open() + //----------------------------------------------------------------------------- + void close(); +}; + +#endif // _FILESTREAMOBJECT_H_ diff --git a/core/stream/ioHelper.h b/core/stream/ioHelper.h new file mode 100644 index 0000000..9458e53 --- /dev/null +++ b/core/stream/ioHelper.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _UTIL_IOHELPER_H_ +#define _UTIL_IOHELPER_H_ + +#ifndef _CORE_STREAM_H_ +#include "core/stream/stream.h" +#endif + +/// Helper templates to aggregate IO operations - generally used in +/// template expansion. +namespace IOHelper +{ + template + inline bool reads(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g,H& h,I& i,J& j) + { s->read(&a); s->read(&b); s->read(&c); s->read(&d); s->read(&e); s->read(&f); s->read(&g); s->read(&h); s->read(&i); return s->read(&j); } + + template + inline bool reads(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g,H& h,I& i) + { s->read(&a); s->read(&b); s->read(&c); s->read(&d); s->read(&e); s->read(&f); s->read(&g); s->read(&h); return s->read(&i); } + + template + inline bool reads(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g,H& h) + { s->read(&a); s->read(&b); s->read(&c); s->read(&d); s->read(&e); s->read(&f); s->read(&g); return s->read(&h); } + + template + inline bool reads(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g) + { s->read(&a); s->read(&b); s->read(&c); s->read(&d); s->read(&e); s->read(&f); return s->read(&g); } + + template + inline bool reads(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f) + { s->read(&a); s->read(&b); s->read(&c); s->read(&d); s->read(&e); return s->read(&f); } + + template + inline bool reads(Stream *s,A& a,B& b,C& c,D& d,E& e) + { s->read(&a); s->read(&b); s->read(&c); s->read(&d); return s->read(&e); } + + template + inline bool reads(Stream *s,A& a,B& b,C& c,D& d) + { s->read(&a); s->read(&b); s->read(&c); return s->read(&d); } + + template + inline bool reads(Stream *s,A& a,B& b,C& c) + { s->read(&a); s->read(&b); return s->read(&c); } + + template + inline bool reads(Stream *s,A& a,B& b) + { s->read(&a); return s->read(&b); } + + template + inline bool reads(Stream *s,A& a) + { return s->read(&a); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g,H& h,I& i,J& j) + { s->write(a); s->write(b); s->write(c); s->write(d); s->write(e); s->write(f); s->write(g); s->write(h); s->write(i); return s->write(j); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g,H& h,I& i) + { s->write(a); s->write(b); s->write(c); s->write(d); s->write(e); s->write(f); s->write(g); s->write(h); return s->write(i); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g,H& h) + { s->write(a); s->write(b); s->write(c); s->write(d); s->write(e); s->write(f); s->write(g); return s->write(h); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f,G& g) + { s->write(a); s->write(b); s->write(c); s->write(d); s->write(e); s->write(f); return s->write(g); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c,D& d,E& e,F& f) + { s->write(a); s->write(b); s->write(c); s->write(d); s->write(e); return s->write(f); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c,D& d,E& e) + { s->write(a); s->write(b); s->write(c); s->write(d); return s->write(e); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c,D& d) + { s->write(a); s->write(b); s->write(c); return s->write(d); } + + template + inline bool writes(Stream *s,A& a,B& b,C& c) + { s->write(a); s->write(b); return s->write(c); } + + template + inline bool writes(Stream *s,A& a,B& b) + { s->write(a); return s->write(b); } + + template + inline bool writes(Stream *s,A& a) + { return s->write(a); } +} + +#endif \ No newline at end of file diff --git a/core/stream/memStream.cpp b/core/stream/memStream.cpp new file mode 100644 index 0000000..c0e4154 --- /dev/null +++ b/core/stream/memStream.cpp @@ -0,0 +1,216 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/stream/memStream.h" + + +MemStream::MemStream( U32 growSize, + bool allowRead, + bool allowWrite ) + : mGrowSize( growSize ), + mBufferSize( 0 ), + mStreamSize( 0 ), + mBufferBase( NULL ), + mInstCaps( 0 ), + mCurrentPosition( 0 ) +{ + AssertFatal( allowRead || allowWrite, "Either write or read must be allowed" ); + + if ( allowRead ) + mInstCaps |= Stream::StreamRead; + if ( allowWrite ) + mInstCaps |= Stream::StreamWrite; + + mOwnsMemory = true; + setStatus( mGrowSize > 0 ? Ok : EOS ); +} + +MemStream::MemStream( U32 bufferSize, + void *buffer, + bool allowRead, + bool allowWrite) + : mGrowSize( 0 ), + mBufferSize( bufferSize ), + mStreamSize( bufferSize ), + mBufferBase( buffer ), + mInstCaps( 0 ), + mCurrentPosition( 0 ), + mOwnsMemory( false ) +{ + AssertFatal( bufferSize > 0, "Invalid buffer size"); + AssertFatal( allowRead || allowWrite, "Either write or read must be allowed"); + + if ( allowRead ) + mInstCaps |= Stream::StreamRead; + if ( allowWrite ) + mInstCaps |= Stream::StreamWrite; + + if ( !buffer ) + { + mOwnsMemory = true; + mBufferBase = dMalloc( mBufferSize ); + } + + setStatus( Ok ); +} + +MemStream::~MemStream() +{ + if ( mOwnsMemory ) + dFree( mBufferBase ); + + mBufferBase = NULL; + mCurrentPosition = 0; + + setStatus( Closed ); +} + +U32 MemStream::getStreamSize() +{ + AssertFatal( getStatus() != Closed, "Stream not open, size undefined" ); + + return mStreamSize; +} + +bool MemStream::hasCapability(const Capability in_cap) const +{ + // Closed streams can't do anything + // + if (getStatus() == Closed) + return false; + + U32 totalCaps = U32(Stream::StreamPosition) | mInstCaps; + + return (U32(in_cap) & totalCaps) != 0; +} + +U32 MemStream::getPosition() const +{ + AssertFatal(getStatus() != Closed, "Position of a closed stream is undefined"); + + return mCurrentPosition; +} + +bool MemStream::setPosition(const U32 in_newPosition) +{ + AssertFatal(getStatus() != Closed, "SetPosition of a closed stream is not allowed"); + AssertFatal(in_newPosition <= mStreamSize, "Invalid position"); + + mCurrentPosition = in_newPosition; + if (mCurrentPosition > mStreamSize) { + // Never gets here in debug version, this is for the release builds... + // + setStatus(UnknownError); + return false; + } else if (mCurrentPosition == mStreamSize) { + setStatus(EOS); + } else { + setStatus(Ok); + } + + return true; +} + +bool MemStream::_read(const U32 in_numBytes, void *out_pBuffer) +{ + AssertFatal(getStatus() != Closed, "Attempted read from a closed stream"); + + if (in_numBytes == 0) + return true; + + AssertFatal(out_pBuffer != NULL, "Invalid output buffer"); + + if (hasCapability(StreamRead) == false) { + AssertWarn(false, "Reading is disallowed on this stream"); + setStatus(IllegalCall); + return false; + } + + bool success = true; + U32 actualBytes = in_numBytes; + if ((mCurrentPosition + in_numBytes) > mStreamSize) { + success = false; + actualBytes = mStreamSize - mCurrentPosition; + } + + // Obtain a current pointer, and do the copy + const void* pCurrent = (const void*)((const U8*)mBufferBase + mCurrentPosition); + dMemcpy(out_pBuffer, pCurrent, actualBytes); + + // Advance the stream position + mCurrentPosition += actualBytes; + + if (!success) + setStatus(EOS); + else + setStatus(Ok); + + return success; +} + +bool MemStream::_write(const U32 in_numBytes, const void *in_pBuffer) +{ + AssertFatal(getStatus() != Closed, "Attempted write to a closed stream"); + + if (in_numBytes == 0) + return true; + + if (hasCapability(StreamWrite) == false) + { + AssertWarn(0, "Writing is disallowed on this stream"); + setStatus(IllegalCall); + return false; + } + + bool success = true; + U32 actualBytes = in_numBytes; + if ((mCurrentPosition + in_numBytes) > mBufferSize) + { + if ( mGrowSize > 0 ) + { + U32 growSize = (mCurrentPosition + in_numBytes) - mBufferSize; + mBufferSize += growSize + ( mGrowSize - ( growSize % mGrowSize ) ); + mBufferBase = dRealloc( mBufferBase, mBufferSize ); + } + else + { + success = false; + actualBytes = mBufferSize - mCurrentPosition; + } + } + + AssertFatal(in_pBuffer != NULL, "Invalid input buffer"); + + // Obtain a current pointer, and do the copy + void* pCurrent = (void*)((U8*)mBufferBase + mCurrentPosition); + dMemcpy(pCurrent, in_pBuffer, actualBytes); + + // Advance the stream position + mCurrentPosition += actualBytes; + if (mCurrentPosition > mStreamSize) + mStreamSize = mCurrentPosition; + + if (mCurrentPosition == mStreamSize) + setStatus(EOS); + else + setStatus(Ok); + + return success; +} + +void *MemStream::takeBuffer() +{ + void *buffer = mBufferBase; + + mBufferBase = NULL; + mBufferSize = 0; + mStreamSize = 0; + mCurrentPosition = 0; + + setStatus(EOS); + + return buffer; +} diff --git a/core/stream/memStream.h b/core/stream/memStream.h new file mode 100644 index 0000000..4199838 --- /dev/null +++ b/core/stream/memStream.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MEMSTREAM_H_ +#define _MEMSTREAM_H_ + +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif + + +/// The MemStream class is used to read and write to a memory buffer. +class MemStream : public Stream +{ + typedef Stream Parent; + + protected: + + /// The amount to grow the buffer when we run out of + /// space writing to the stream. + U32 mGrowSize; + + /// The actual size of the memory buffer. It is always + /// greater than or equal to the mStreamSize. + U32 mBufferSize; + + /// The size of the data in the stream. It is always + /// less than or equal to the mBufferSize. + U32 mStreamSize; + + /// The memory buffer. + void *mBufferBase; + + /// If true the memory is owned by the steam and it + /// will be deleted in the destructor. + bool mOwnsMemory; + + /// + U32 mInstCaps; + + /// Our current read/write position within the buffer. + U32 mCurrentPosition; + + public: + + /// This constructs an empty memory stream that will grow + /// in increments as needed. + MemStream( U32 growSize, + bool allowRead = true, + bool allowWrite = true ); + + /// This constructs the stream with a fixed size memory buffer. If + /// buffer is null then it will be allocated for you. + MemStream( U32 bufferSize, + void *buffer, + bool allowRead = true, + bool allowWrite = true ); + + /// The destructor. + virtual ~MemStream(); + + protected: + + // Stream + bool _read( const U32 in_numBytes, void *out_pBuffer ); + bool _write( const U32 in_numBytes, const void *in_pBuffer ); + + public: + + // Stream + bool hasCapability( const Capability caps ) const; + U32 getPosition() const; + bool setPosition( const U32 in_newPosition ); + U32 getStreamSize(); + + /// Returns the memory buffer. + void *getBuffer() { return mBufferBase; } + const void *getBuffer() const { return mBufferBase; } + + /// Takes the memory buffer reseting the stream. + void *takeBuffer(); + +}; + +#endif //_MEMSTREAM_H_ diff --git a/core/stream/stream.cpp b/core/stream/stream.cpp new file mode 100644 index 0000000..7b2bdae --- /dev/null +++ b/core/stream/stream.cpp @@ -0,0 +1,361 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/color.h" +#include "core/util/rawData.h" +#include "core/frameAllocator.h" +#include "platform/platformNet.h" + +#include "core/stream/stream.h" + +#include "core/stringTable.h" +#include "core/strings/stringFunctions.h" + +#include "core/util/byteBuffer.h" +#include "core/util/endian.h" +#include "core/util/str.h" + + +#define IMPLEMENT_OVERLOADED_READ(type) \ + bool Stream::read(type* out_read) \ + { \ + return read(sizeof(type), out_read); \ + } + +#define IMPLEMENT_OVERLOADED_WRITE(type) \ + bool Stream::write(type in_write) \ + { \ + return write(sizeof(type), &in_write); \ + } + +#define IMPLEMENT_ENDIAN_OVERLOADED_READ(type) \ + bool Stream::read(type* out_read) \ + { \ + type temp; \ + bool success = read(sizeof(type), &temp); \ + *out_read = convertLEndianToHost(temp); \ + return success; \ + } + +#define IMPLEMENT_ENDIAN_OVERLOADED_WRITE(type) \ + bool Stream::write(type in_write) \ + { \ + type temp = convertHostToLEndian(in_write); \ + return write(sizeof(type), &temp); \ + } + +IMPLEMENT_OVERLOADED_WRITE(S8) +IMPLEMENT_OVERLOADED_WRITE(U8) + +IMPLEMENT_ENDIAN_OVERLOADED_WRITE(S16) +IMPLEMENT_ENDIAN_OVERLOADED_WRITE(S32) +IMPLEMENT_ENDIAN_OVERLOADED_WRITE(U16) +IMPLEMENT_ENDIAN_OVERLOADED_WRITE(U32) +IMPLEMENT_ENDIAN_OVERLOADED_WRITE(U64) +IMPLEMENT_ENDIAN_OVERLOADED_WRITE(F32) +IMPLEMENT_ENDIAN_OVERLOADED_WRITE(F64) + +IMPLEMENT_OVERLOADED_READ(S8) +IMPLEMENT_OVERLOADED_READ(U8) + +IMPLEMENT_ENDIAN_OVERLOADED_READ(S16) +IMPLEMENT_ENDIAN_OVERLOADED_READ(S32) +IMPLEMENT_ENDIAN_OVERLOADED_READ(U16) +IMPLEMENT_ENDIAN_OVERLOADED_READ(U32) +IMPLEMENT_ENDIAN_OVERLOADED_READ(U64) +IMPLEMENT_ENDIAN_OVERLOADED_READ(F32) +IMPLEMENT_ENDIAN_OVERLOADED_READ(F64) + + +Stream::Stream() + : m_streamStatus(Closed) +{ +} + +const char* Stream::getStatusString(const Status in_status) +{ + switch (in_status) { + case Ok: + return "StreamOk"; + case IOError: + return "StreamIOError"; + case EOS: + return "StreamEOS"; + case IllegalCall: + return "StreamIllegalCall"; + case Closed: + return "StreamClosed"; + case UnknownError: + return "StreamUnknownError"; + + default: + return "Invalid Stream::Status"; + } +} + +void Stream::writeString(const char *string, S32 maxLen) +{ + S32 len = string ? dStrlen(string) : 0; + if(len > maxLen) + len = maxLen; + + write(U8(len)); + if(len) + write(len, string); +} + +void Stream::readString(char buf[256]) +{ + U8 len; + read(&len); + read(S32(len), buf); + buf[len] = 0; +} + +const char *Stream::readSTString(bool casesens) +{ + char buf[256]; + readString(buf); + return StringTable->insert(buf, casesens); +} + +void Stream::readLongString(U32 maxStringLen, char *stringBuf) +{ + U32 len; + read(&len); + if(len > maxStringLen) + { + m_streamStatus = IOError; + return; + } + read(len, stringBuf); + stringBuf[len] = 0; +} + +void Stream::writeLongString(U32 maxStringLen, const char *string) +{ + U32 len = dStrlen(string); + if(len > maxStringLen) + len = maxStringLen; + write(len); + write(len, string); +} + +void Stream::readLine(U8 *buffer, U32 bufferSize) +{ + bufferSize--; // account for NULL terminator + U8 *buff = buffer; + U8 *buffEnd = buff + bufferSize; + *buff = '\r'; + + // strip off preceding white space + while ( *buff == '\r' ) + { + if ( !read(buff) || *buff == '\n' ) + { + *buff = 0; + return; + } + } + + // read line + while ( buff != buffEnd && read(++buff) && *buff != '\n' ) + { + if ( *buff == '\r' ) + { + +#if defined(TORQUE_OS_MAC) + U32 pushPos = getPosition(); // in case we need to back up. + if (read(buff)) // feeling free to overwrite the \r as the NULL below will overwrite again... + if (*buff != '\n') // then push our position back. + setPosition(pushPos); + break; // we're always done after seeing the CR... +#else + buff--; // 'erases' the CR of a CRLF +#endif + + } + } + *buff = 0; +} + +void Stream::writeText(const char *text) +{ + if (text && text[0]) + write(dStrlen(text), text); +} + +void Stream::writeLine(const U8 *buffer) +{ + write(dStrlen((const char *)buffer), buffer); + write(2, "\r\n"); +} + +void Stream::_write(const String & str) +{ + U32 len = str.length(); + + if (len<255) + write(U8(len)); + else + { + // longer string, write full length + write(U8(255)); + + // fail if longer than 16 bits (will truncate string modulo 2^16) + AssertFatal(len < (1<<16),"String too long"); + + len &= (1<<16)-1; + write(U16(len)); + } + + write(len,str.c_str()); +} + +void Stream::_read(String * str) +{ + U16 len; + + U8 len8; + read(&len8); + if (len8==255) + read(&len); + else + len = len8; + + char * buffer = (char*)FrameAllocator::alloc(len); + read(len, buffer); + *str = String(buffer,len); +} + + +bool Stream::write(const ColorI& rColor) +{ + bool success = write(rColor.red); + success |= write(rColor.green); + success |= write(rColor.blue); + success |= write(rColor.alpha); + + return success; +} + +bool Stream::write(const ColorF& rColor) +{ + ColorI temp = rColor; + return write(temp); +} + +bool Stream::read(ColorI* pColor) +{ + bool success = read(&pColor->red); + success |= read(&pColor->green); + success |= read(&pColor->blue); + success |= read(&pColor->alpha); + + return success; +} + +bool Stream::read(ColorF* pColor) +{ + ColorI temp; + bool success = read(&temp); + + *pColor = temp; + return success; +} + +bool Stream::write(const NetAddress &na) +{ + bool success = write(na.type); + success &= write(na.port); + success &= write(na.netNum[0]); + success &= write(na.netNum[1]); + success &= write(na.netNum[2]); + success &= write(na.netNum[3]); + success &= write(na.nodeNum[0]); + success &= write(na.nodeNum[1]); + success &= write(na.nodeNum[2]); + success &= write(na.nodeNum[3]); + success &= write(na.nodeNum[4]); + success &= write(na.nodeNum[5]); + return success; +} + +bool Stream::read(NetAddress *na) +{ + bool success = read(&na->type); + success &= read(&na->port); + success &= read(&na->netNum[0]); + success &= read(&na->netNum[1]); + success &= read(&na->netNum[2]); + success &= read(&na->netNum[3]); + success &= read(&na->nodeNum[0]); + success &= read(&na->nodeNum[1]); + success &= read(&na->nodeNum[2]); + success &= read(&na->nodeNum[3]); + success &= read(&na->nodeNum[4]); + success &= read(&na->nodeNum[5]); + return success; +} + +bool Stream::write(const RawData &rd) +{ + bool s = write(rd.size); + s &= write(rd.size, rd.data); + return s; +} + +bool Stream::read(RawData *rd) +{ + U32 size = 0; + bool s = read(&size); + + rd->alloc(size); + s &= read(rd->size, rd->data); + + return s; +} + +bool Stream::write(const Torque::ByteBuffer &rd) +{ + bool s = write(rd.getBufferSize()); + s &= write(rd.getBufferSize(), rd.getBuffer()); + return s; +} + +bool Stream::read(Torque::ByteBuffer *rd) +{ + U32 size = 0; + bool s = read(&size); + + rd->resize(size); + s &= read(rd->getBufferSize(), rd->getBuffer()); + + return s; +} + +bool Stream::copyFrom(Stream *other) +{ + U8 buffer[1024]; + U32 numBytes = other->getStreamSize() - other->getPosition(); + while((other->getStatus() != Stream::EOS) && numBytes > 0) + { + U32 numRead = numBytes > sizeof(buffer) ? sizeof(buffer) : numBytes; + if(! other->read(numRead, buffer)) + return false; + + if(! write(numRead, buffer)) + return false; + + numBytes -= numRead; + } + + return true; +} + +Stream* Stream::clone() const +{ + return NULL; +} diff --git a/core/stream/stream.h b/core/stream/stream.h new file mode 100644 index 0000000..cb0dae8 --- /dev/null +++ b/core/stream/stream.h @@ -0,0 +1,225 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STREAM_H_ +#define _STREAM_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif +#ifndef _ENDIAN_H_ +#include "core/util/endian.h" +#endif + + +/// @defgroup stream_overload Primitive Type Stream Operation Overloads +/// These macros declare the read and write functions for all primitive types. +/// @{ +#define DECLARE_OVERLOADED_READ(type) bool read(type* out_read); +#define DECLARE_OVERLOADED_WRITE(type) bool write(type in_write); +/// @} + +class ColorI; +class ColorF; +struct NetAddress; +class RawData; +class String; + +namespace Torque { + class ByteBuffer; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Base Stream class +// +/// Base stream class for streaming data across a specific media +class Stream +{ + // Public structs and enumerations... +public: + /// Status constants for the stream + enum Status { + Ok = 0, ///< Ok! + IOError, ///< Read or Write error + EOS, ///< End of Stream reached (mostly for reads) + IllegalCall, ///< An unsupported operation used. Always w/ accompanied by AssertWarn + Closed, ///< Tried to operate on a closed stream (or detached filter) + UnknownError ///< Catchall + }; + + enum Capability { + StreamWrite = BIT(0), ///< Can this stream write? + StreamRead = BIT(1), ///< Can this stream read? + StreamPosition = BIT(2) ///< Can this stream position? + }; + + // Accessible only through inline accessors +private: + Status m_streamStatus; + + // Derived accessible data modifiers... +protected: + void setStatus(const Status in_newStatus) { m_streamStatus = in_newStatus; } + +public: + Stream(); + virtual ~Stream() {} + + /// Gets the status of the stream + Stream::Status getStatus() const { return m_streamStatus; } + /// Gets a printable string form of the status + static const char* getStatusString(const Status in_status); + + // Derived classes must override these... +protected: + virtual bool _read(const U32 in_numBytes, void* out_pBuffer) = 0; + virtual bool _write(const U32 in_numBytes, const void* in_pBuffer) = 0; + + virtual void _write(const String & str); + virtual void _read(String * str); + + +public: + /// Checks to see if this stream has the capability of a given function + virtual bool hasCapability(const Capability caps) const = 0; + + /// Gets the position in the stream + virtual U32 getPosition() const = 0; + /// Sets the position of the stream. Returns if the new position is valid or not + virtual bool setPosition(const U32 in_newPosition) = 0; + /// Gets the size of the stream + virtual U32 getStreamSize() = 0; + + /// Reads a line from the stream. + /// @param buffer buffer to be read into + /// @param bufferSize max size of the buffer. Will not read more than the "bufferSize" + void readLine(U8 *buffer, U32 bufferSize); + /// writes a line to the stream + void writeLine(const U8 *buffer); + + /// Reads a string and inserts it into the StringTable + /// @see StringTable + const char *readSTString(bool casesens = false); + /// Reads a string of maximum 255 characters long + virtual void readString(char stringBuf[256]); + /// Reads a string that could potentially be more than 255 characters long. + /// @param maxStringLen Maximum length to read. If the string is longer than maxStringLen, only maxStringLen bytes will be read. + /// @param stringBuf buffer where data is read into + void readLongString(U32 maxStringLen, char *stringBuf); + /// Writes a string to the stream. This function is slightly unstable. + /// Only use this if you have a valid string that is not empty. + /// writeString is safer. + void writeLongString(U32 maxStringLen, const char *string); + + /// Write raw text to the stream + void writeText(const char *text); + + /// Writes a string to the stream. + virtual void writeString(const char *stringBuf, S32 maxLen=255); + + // read/write real strings + void write(const String & str) { _write(str); } + void read(String * str) { _read(str); } + + /// Write an integral color to the stream. + bool write(const ColorI&); + /// Write a floating point color to the stream. + bool write(const ColorF&); + /// Read an integral color from the stream. + bool read(ColorI*); + /// Read a floating point color from the stream. + bool read(ColorF*); + + /// Write a network address to the stream. + bool write(const NetAddress &); + /// Read a network address from the stream. + bool read(NetAddress*); + + /// Write some raw data onto the stream. + bool write(const RawData &); + /// Read some raw data from the stream. + bool read(RawData *); + + /// Write some raw data onto the stream. + bool write(const Torque::ByteBuffer &); + /// Read some raw data from the stream. + bool read(Torque::ByteBuffer *); + + // Overloaded write and read ops.. + public: + bool read(const U32 in_numBytes, void* out_pBuffer) { + return _read(in_numBytes, out_pBuffer); + } + bool write(const U32 in_numBytes, const void* in_pBuffer) { + return _write(in_numBytes, in_pBuffer); + } + + DECLARE_OVERLOADED_WRITE(S8) + DECLARE_OVERLOADED_WRITE(U8) + + DECLARE_OVERLOADED_WRITE(S16) + DECLARE_OVERLOADED_WRITE(S32) + DECLARE_OVERLOADED_WRITE(U16) + DECLARE_OVERLOADED_WRITE(U32) + DECLARE_OVERLOADED_WRITE(U64) + DECLARE_OVERLOADED_WRITE(F32) + DECLARE_OVERLOADED_WRITE(F64) + + DECLARE_OVERLOADED_READ(S8) + DECLARE_OVERLOADED_READ(U8) + + DECLARE_OVERLOADED_READ(S16) + DECLARE_OVERLOADED_READ(S32) + DECLARE_OVERLOADED_READ(U16) + DECLARE_OVERLOADED_READ(U32) + DECLARE_OVERLOADED_READ(U64) + DECLARE_OVERLOADED_READ(F32) + DECLARE_OVERLOADED_READ(F64) + + // We have to do the bool's by hand, since they are different sizes + // on different compilers... + // + bool read(bool* out_pRead) { + U8 translate; + bool success = read(&translate); + if (success == false) + return false; + + *out_pRead = translate != 0; + return true; + } + bool write(const bool& in_rWrite) { + U8 translate = in_rWrite ? U8(1) : U8(0); + return write(translate); + } + + /// Copy the contents of another stream into this one + bool copyFrom(Stream *other); + + /// Write a number of tabs to this stream + void writeTabs(U32 count) + { + char tab[] = " "; + while(count--) + write(3, (void*)tab); + } + + /// Create an exact replica of this stream. + /// Return NULL if the cloning fails. + virtual Stream* clone() const; +}; + +// This interface is used to provide the amount of bytes actually written/read when using the stream as a +// file. The Stream interface does not provide this information. This is a lame workaround. +class IStreamByteCount +{ +public: + virtual ~IStreamByteCount() {} + + virtual U32 getLastBytesRead() = 0; + virtual U32 getLastBytesWritten() = 0; +}; + +#endif //_STREAM_H_ diff --git a/core/stream/streamObject.cpp b/core/stream/streamObject.cpp new file mode 100644 index 0000000..3e03f8d --- /dev/null +++ b/core/stream/streamObject.cpp @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/streamObject.h" + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +StreamObject::StreamObject() +{ + mStream = NULL; +} + +StreamObject::StreamObject(Stream *stream) +{ + mStream = stream; +} + +StreamObject::~StreamObject() +{ +} + +IMPLEMENT_CONOBJECT(StreamObject); + +//----------------------------------------------------------------------------- + +bool StreamObject::onAdd() +{ + if(mStream == NULL) + { + Con::errorf("StreamObject::onAdd - StreamObject can not be instantiated from script."); + return false; + } + return Parent::onAdd(); +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +const char * StreamObject::getStatus() +{ + if(mStream == NULL) + return ""; + + switch(mStream->getStatus()) + { + case Stream::Ok: + return "Ok"; + case Stream::IOError: + return "IOError"; + case Stream::EOS: + return "EOS"; + case Stream::IllegalCall: + return "IllegalCall"; + case Stream::Closed: + return "Closed"; + case Stream::UnknownError: + return "UnknownError"; + + default: + return "Invalid"; + } +} + +//----------------------------------------------------------------------------- + +const char * StreamObject::readLine() +{ + if(mStream == NULL) + return NULL; + + char *buffer = Con::getReturnBuffer(256); + mStream->readLine((U8 *)buffer, 256); + return buffer; +} + +const char * StreamObject::readString() +{ + if(mStream == NULL) + return NULL; + + char *buffer = Con::getReturnBuffer(256); + mStream->readString(buffer); + return buffer; +} + +const char *StreamObject::readLongString(U32 maxStringLen) +{ + if(mStream == NULL) + return NULL; + + char *buffer = Con::getReturnBuffer(maxStringLen + 1); + mStream->readLongString(maxStringLen, buffer); + return buffer; +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(StreamObject, getStatus, const char *, 2, 2, "()") +{ + return object->getStatus(); +} + +ConsoleMethod(StreamObject, isEOS, bool, 2, 2, "() Test for end of stream") +{ + return object->isEOS(); +} + +ConsoleMethod(StreamObject, isEOF, bool, 2, 2, "() Test for end of stream") +{ + return object->isEOS(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(StreamObject, getPosition, S32, 2, 2, "()") +{ + return object->getPosition(); +} + +ConsoleMethod(StreamObject, setPosition, bool, 3, 3, "(newPosition)") +{ + return object->setPosition(dAtoi(argv[2])); +} + +ConsoleMethod(StreamObject, getStreamSize, S32, 2, 2, "()") +{ + return object->getStreamSize(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(StreamObject, readLine, const char *, 2, 2, "()") +{ + const char *str = object->readLine(); + return str ? str : ""; +} + +ConsoleMethod(StreamObject, writeLine, void, 3, 3, "(line)") +{ + object->writeLine((U8 *)argv[2]); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(StreamObject, readSTString, const char *, 2, 3, "([caseSensitive = false])") +{ + const char *str = object->readSTString(argc > 2 ? dAtob(argv[2]) : false); + return str ? str : ""; +} + +ConsoleMethod(StreamObject, readString, const char *, 2, 2, "()") +{ + const char *str = object->readString(); + return str ? str : ""; +} + +ConsoleMethod(StreamObject, readLongString, const char *, 3, 3, "(maxLength)") +{ + const char *str = object->readLongString(dAtoi(argv[2])); + return str ? str : ""; +} + +ConsoleMethod(StreamObject, writeLongString, void, 4, 4, "(maxLength, string)") +{ + object->writeLongString(dAtoi(argv[2]), argv[3]); +} + +ConsoleMethod(StreamObject, writeString, void, 3, 4, "(string, [maxLength = 255])") +{ + object->writeString(argv[2], argc > 3 ? dAtoi(argv[3]) : 255); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(StreamObject, copyFrom, bool, 3, 3, "(StreamObject other)") +{ + StreamObject *other = dynamic_cast(Sim::findObject(argv[2])); + if(other == NULL) + return false; + + return object->copyFrom(other); +} diff --git a/core/stream/streamObject.h b/core/stream/streamObject.h new file mode 100644 index 0000000..aa4040f --- /dev/null +++ b/core/stream/streamObject.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STREAMOBJECT_H_ +#define _STREAMOBJECT_H_ + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + +/// @addtogroup zip_group +// @{ + +//----------------------------------------------------------------------------- +/// @brief Script wrapper for the Stream class +/// +/// It is not possible to instantiate StreamObject in script. Instead, +/// it is instantiated in C++ code and returned to script. +/// +/// This was mainly intended to allow the \ref zip_group "zip code" to +/// provide the stream interface to script. +//----------------------------------------------------------------------------- +class StreamObject : public SimObject +{ + typedef SimObject Parent; + +protected: + Stream *mStream; + +public: + StreamObject(); + StreamObject(Stream *stream); + virtual ~StreamObject(); + + DECLARE_CONOBJECT(StreamObject); + + virtual bool onAdd(); + + /// Set the stream to allow reuse of the object + void setStream(Stream *stream) { mStream = stream; } + + /// Get the underlying stream. Used with setStream() to support object reuse + Stream *getStream() { return mStream; } + + /// Gets a printable string form of the status + const char* getStatus(); + + bool isEOS() { return mStream ? mStream->getStatus() == Stream::EOS : true; } + + /// Gets the position in the stream + U32 getPosition() const + { + return mStream ? mStream->getPosition() : 0; + } + + /// Sets the position of the stream. Returns if the new position is valid or not + bool setPosition(const U32 in_newPosition) + { + return mStream ? mStream->setPosition(in_newPosition) : false; + } + + /// Gets the size of the stream + U32 getStreamSize() + { + return mStream ? mStream->getStreamSize() : 0; + } + + /// Reads a line from the stream. + const char * readLine(); + + /// Writes a line to the stream + void writeLine(U8 *buffer) + { + if(mStream) + mStream->writeLine(buffer); + } + + /// Reads a string and inserts it into the StringTable + /// @see StringTable + const char *readSTString(bool casesens = false) + { + return mStream ? mStream->readSTString(casesens) : NULL; + } + + /// Reads a string of maximum 255 characters long + const char *readString(); + /// Reads a string that could potentially be more than 255 characters long. + /// @param maxStringLen Maximum length to read. If the string is longer than maxStringLen, only maxStringLen bytes will be read. + /// @param stringBuf buffer where data is read into + const char * readLongString(U32 maxStringLen); + /// Writes a string to the stream. This function is slightly unstable. + /// Only use this if you have a valid string that is not empty. + /// writeString is safer. + void writeLongString(U32 maxStringLen, const char *string) + { + if(mStream) + mStream->writeLongString(maxStringLen, string); + } + + /// Writes a string to the stream. + void writeString(const char *stringBuf, S32 maxLen=255) + { + if(mStream) + mStream->writeString(stringBuf, maxLen); + } + + /// Copy the contents of another stream into this one + bool copyFrom(StreamObject *other) + { + if(mStream) + return mStream->copyFrom(other->getStream()); + + return false; + } +}; + +// @} + +#endif // _STREAMOBJECT_H_ diff --git a/core/stream/tStream.h b/core/stream/tStream.h new file mode 100644 index 0000000..f17cdd4 --- /dev/null +++ b/core/stream/tStream.h @@ -0,0 +1,310 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSTREAM_H_ +#define _TSTREAM_H_ + +#ifndef _PLATFORM_H_ +# include "platform/platform.h" +#endif +#ifndef _TYPETRAITS_H_ +# include "platform/typetraits.h" +#endif + +//RDTODO: error handling + + +/// @file +/// Definitions for lightweight componentized streaming. +/// +/// This file is an assembly of lightweight classes/interfaces that +/// describe various aspects of streaming classes. The advantage +/// over using Torque's Stream class is that very little requirements +/// are placed on implementations, that specific abilities can be +/// mixed and matched very selectively, and that complex stream processing +/// chains can be hidden behind very simple stream interfaces. + + +/// + +struct StreamIOException +{ + //TODO +}; + +enum EAsyncIOStatus +{ + ASYNC_IO_Pending, + ASYNC_IO_Complete, + ASYNC_IO_Error +}; + +//----------------------------------------------------------------------------- +// Several component interface. +//----------------------------------------------------------------------------- + +/// Interface for streams with an explicit position property. + +template< typename P = U32 > +class IPositionable +{ + public: + + typedef void Parent; + + /// The type used to indicate positions. + typedef P PositionType; + + /// @return the current position. + virtual PositionType getPosition() const = 0; + + /// Set the current position to be "pos". + /// @param pos The new position. + virtual void setPosition( PositionType pos ) = 0; +}; + +/// Interface for structures that allow their state to be reset. + +class IResettable +{ + public: + + typedef void Parent; + + /// + virtual void reset() = 0; +}; + +/// Interface for structures of finite size. + +template< typename T = U32 > +class ISizeable +{ + public: + + typedef void Parent; + + /// The type used to indicate the structure's size. + typedef T SizeType; + + /// @return the size of the structure in number of elements. + virtual SizeType getSize() const = 0; +}; + +/// Interface for structures that represent processes. + +class IProcess +{ + public: + + typedef void Parent; + + /// + virtual void start() = 0; + + /// + virtual void stop() = 0; + + /// + virtual void pause() = 0; +}; + +/// +class IPolled +{ + public: + + typedef void Parent; + + /// + virtual bool update() = 0; +}; + + +//----------------------------------------------------------------------------- +// IInputStream. +//----------------------------------------------------------------------------- + +/// An input stream delivers a sequence of elements of type "T". + +template< typename T > +class IInputStream +{ + public: + + typedef void Parent; + + /// The element type of this input stream. + typedef T ElementType; + + /// Read the next "num" elements into "buffer". + /// + /// @param buffer The buffer into which the elements are stored. + /// @param num Number of elements to read. + /// @return the number of elements actually read; this may be less than + /// "num" or even zero if no elements are available or reading failed. + virtual U32 read( ElementType* buffer, U32 num ) = 0; +}; + +/// An input stream over elements of type "T" that reads from +/// user-specified explicit offsets. + +template< typename T, typename Offset = U32 > +class IOffsetInputStream +{ + public: + + typedef void Parent; + typedef Offset OffsetType; + typedef T ElementType; + + /// + virtual U32 readAt( OffsetType offset, T* buffer, U32 num ) = 0; +}; + +/// An input stream over elements of type "T" that works in +/// the background. + +template< typename T, typename Offset = U32 > +class IAsyncInputStream +{ + public: + + typedef void Parent; + typedef Offset OffsetType; + typedef T ElementType; + + /// + virtual void* issueReadAt( OffsetType offset, T* buffer, U32 num ) = 0; + + /// + virtual EAsyncIOStatus tryCompleteReadAt( void* handle, U32& outNumRead, bool wait = false ) = 0; + + /// + virtual void cancelReadAt( void* handle ) = 0; +}; + +//----------------------------------------------------------------------------- +// IOutputStream. +//----------------------------------------------------------------------------- + +/// An output stream that writes elements of type "T". + +template< typename T > +class IOutputStream +{ + public: + + typedef void Parent; + typedef T ElementType; + + /// + virtual void write( const ElementType* buffer, U32 num ) = 0; +}; + +/// An output stream that writes elements of type "T" to a +/// user-specified explicit offset. + +template< typename T, typename Offset = U32 > +class IOffsetOutputStream +{ + public: + + typedef void Parent; + typedef Offset OffsetType; + typedef T ElementType; + + /// + virtual void writeAt( OffsetType offset, const ElementType* buffer, U32 num ) = 0; +}; + +/// An output stream that writes elements of type "T" in the background. + +template< typename T, typename Offset = U32 > +class IAsyncOutputStream +{ + public: + + typedef void Parent; + typedef Offset OffsetType; + typedef T ElementType; + + /// + virtual void* issueWriteAt( OffsetType offset, const ElementType* buffer, U32 num ) = 0; + + /// + virtual EAsyncIOStatus tryCompleteWriteAt( void* handle, bool wait = false ) = 0; + + /// + virtual void cancelWriteAt( void* handle ) = 0; +}; + +//----------------------------------------------------------------------------- +// IInputStreamFilter. +//----------------------------------------------------------------------------- + +/// An input stream filter takes an input stream "Stream" and processes it +/// into an input stream over type "To". + +template< typename To, typename Stream > +class IInputStreamFilter : public IInputStream< To > +{ + public: + + typedef IInputStream< To > Parent; + + /// + typedef typename TypeTraits< Stream >::BaseType SourceStreamType; + + /// The element type of the source stream. + typedef typename SourceStreamType::ElementType SourceElementType; + + /// + IInputStreamFilter( const Stream& stream ) + : mSourceStream( stream ) {} + + /// + const Stream& getSourceStream() const { return mSourceStream; } + Stream& getSourceStream() { return mSourceStream; } + + private: + + Stream mSourceStream; +}; + +//----------------------------------------------------------------------------- +// IOutputStreamFilter. +//----------------------------------------------------------------------------- + +/// An output stream filter takes an output stream "Stream" and processes it +/// into an output stream over type "To". + +template< typename To, class Stream > +class IOutputStreamFilter : public IOutputStream< To > +{ + public: + + typedef IOutputStream< To > Parent; + + /// + typedef typename TypeTraits< Stream >::BaseType TargetStreamType; + + /// The element type of the target stream. + typedef typename TargetStreamType::ElementType TargetElementType; + + /// + IOutputStreamFilter( const Stream& stream ) + : mTargetStream( stream ) {} + + /// + const Stream& getTargetStream() const { return mTargetStream; } + Stream& getTargetStream() { return mTargetStream; } + + private: + + Stream mTargetStream; +}; + +#endif // _TSTREAM_H_ diff --git a/core/stringBuffer.cpp b/core/stringBuffer.cpp new file mode 100644 index 0000000..81e2f5e --- /dev/null +++ b/core/stringBuffer.cpp @@ -0,0 +1,454 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stringBuffer.h" +#include "core/frameAllocator.h" +#include "core/strings/unicode.h" +#include "core/strings/stringFunctions.h" + + +#if defined(TORQUE_DEBUG) + class StringBufferManager + { + public: + static StringBufferManager& getManager(); + Vector strings; + U64 request8; + U64 request16; + + StringBufferManager() + { + VECTOR_SET_ASSOCIATION( strings ); + } + + void add(StringBuffer* s); + void remove(StringBuffer* s); + void updateStats(); + void dumpStats(); + void dumpAllStrings(); + }; + + ConsoleFunction(sbmDumpStats, void, 1, 1, "") + { + StringBufferManager::getManager().dumpStats(); + } + + ConsoleFunction(sbmDumpStrings, void, 1, 1, "") + { + StringBufferManager::getManager().dumpAllStrings(); + } +#endif // TORQUE_DEBUG + + +#if defined(TORQUE_DEBUG) +#define SBMAddThisStringBuffer() \ + StringBufferManager::getManager().add(this); \ + rc = new RequestCounts; \ + clearRequestCounts() +#define incRequestCount8() rc->requestCount8++ +#define incRequestCount16() rc->requestCount16++ +#define decRequestCount16() rc->requestCount16-- +#else +#define SBMAddThisStringBuffer() +#define incRequestCount8() +#define incRequestCount16() +#define decRequestCount16() +#endif + +StringBuffer::StringBuffer() : mBuffer(), mBuffer8(), mDirty8(true) +{ + VECTOR_SET_ASSOCIATION( mBuffer ); + SBMAddThisStringBuffer(); + mBuffer.push_back(0); +}; + +/// Copy constructor. Very important. +StringBuffer::StringBuffer(const StringBuffer ©) : mBuffer(), mBuffer8() +{ + VECTOR_SET_ASSOCIATION( mBuffer ); + SBMAddThisStringBuffer(); + set(©); +}; + +StringBuffer::StringBuffer(const StringBuffer *in) +: mBuffer(), mBuffer8() +{ + VECTOR_SET_ASSOCIATION( mBuffer ); + SBMAddThisStringBuffer(); + set(in); +} + +StringBuffer::StringBuffer(const UTF8 *in) +: mBuffer(), mBuffer8() +{ + VECTOR_SET_ASSOCIATION( mBuffer ); + SBMAddThisStringBuffer(); + set(in); +} + +StringBuffer::StringBuffer(const UTF16 *in) +: mBuffer(), mBuffer8() +{ + VECTOR_SET_ASSOCIATION( mBuffer ); + SBMAddThisStringBuffer(); + set(in); +} + +//------------------------------------------------------------------------- + +StringBuffer::~StringBuffer() +{ + // Everything will get cleared up nicely cuz it's a vector. Sweet. + #if defined(TORQUE_DEBUG) + StringBufferManager::getManager().remove(this); + delete rc; + #endif + +} + +//------------------------------------------------------------------------- + +void StringBuffer::set(const StringBuffer *in) +{ + // Copy the vector. + mBuffer.setSize(in->mBuffer.size()); + dMemcpy(mBuffer.address(), in->mBuffer.address(), sizeof(UTF16) * mBuffer.size()); + mDirty8 = true; +} + +void StringBuffer::set(const UTF8 *in) +{ + incRequestCount8(); + // Convert and store. Note that a UTF16 version of the string cannot be longer. + FrameTemp tmpBuff(dStrlen(in)+1); + if(!in || in[0] == 0 || !convertUTF8toUTF16(in, tmpBuff, dStrlen(in)+1)) + { + // Easy out, it's a blank string, or a bad string. + mBuffer.clear(); + mBuffer.push_back(0); + AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF8 - not a null terminated string!"); + mDirty8 = true; + return; + } + + // Otherwise, we've a copy to do. (This might not be strictly necessary.) + mBuffer.setSize(dStrlen(tmpBuff)+1); + dMemcpy(mBuffer.address(), tmpBuff, sizeof(UTF16) * mBuffer.size()); + mBuffer.compact(); + AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF8 - not a null terminated string!"); + mDirty8 = true; +} + +void StringBuffer::set(const UTF16 *in) +{ + incRequestCount16(); + // Just copy, it's already UTF16. + U32 newsize = dStrlen(in); + mBuffer.setSize(newsize + 1); + dMemcpy(mBuffer.address(), in, sizeof(UTF16) * newsize); + mBuffer[newsize] = '\0'; + mBuffer.compact(); + AssertFatal(mBuffer.last() == 0, "StringBuffer::set UTF16 - not a null terminated string!"); + mDirty8 = true; +} + +//------------------------------------------------------------------------- + +void StringBuffer::append(const StringBuffer &in) +{ + append(in.mBuffer.address(), in.length()); +} + +void StringBuffer::append(const UTF8* in) +{ + incRequestCount8(); + decRequestCount16(); // because we're about to inc it when we go through append(utf16) + + // convert to UTF16, because that's our internal format. + // if the conversion fails, exit. + UTF16* tmp = convertUTF8toUTF16(in); + AssertFatal(tmp, "StringBuffer::append(UTF8) - could not convert UTF8 string!"); + if(!tmp) + return; + + append(tmp); + delete[] tmp; +} + +void StringBuffer::append(const UTF16* in) +{ + AssertFatal(in, "StringBuffer::append(UTF16) - null UTF16 string!"); + append(in, dStrlen(in)); +} + +void StringBuffer::append(const UTF16* in, const U32 len) +{ + incRequestCount16(); + + // Stick in onto the end of us - first make space. + U32 oldSize = length(); + mBuffer.increment(len); + + // Copy it in, ignoring both our terminator and theirs. + dMemcpy(&mBuffer[oldSize], in, sizeof(UTF16) * len); + + // Terminate the string. + mBuffer.last() = 0; + + // mark utf8 buffer dirty + mDirty8 = true; +} + +void StringBuffer::insert(const U32 charOffset, const StringBuffer &in) +{ + insert(charOffset, in.mBuffer.address(), in.length()); +} + +void StringBuffer::insert(const U32 charOffset, const UTF8* in) +{ + incRequestCount8(); + decRequestCount16(); + + // convert to UTF16, because that's our internal format. + // if the conversion fails, exit. + UTF16* tmp = convertUTF8toUTF16(in); + AssertFatal(tmp, "StringBuffer::insert(UTF8) - could not convert UTF8 string!"); + if(!tmp) + return; + + insert(charOffset, tmp); + delete[] tmp; +} + +void StringBuffer::insert(const U32 charOffset, const UTF16* in) +{ + AssertFatal(in, "StringBuffer::insert(UTF16) - null UTF16 string!"); + insert(charOffset, in, dStrlen(in)); +} + +void StringBuffer::insert(const U32 charOffset, const UTF16* in, const U32 len) +{ + incRequestCount16(); + + // Deal with append case. + if(charOffset >= length()) + { + append(in, len); + return; + } + + // Append was easy, now we have to do some work. + + // Copy everything we have that comes after charOffset past where the new + // string data will be. + + // Figure the number of UTF16's to copy, taking into account the possibility + // that we may be inserting a long string into a short string. + + // How many chars come after the insert point? Add 1 to deal with terminator. + const U32 copyCharLength = (S32)(length() - charOffset) + 1; + + // Make some space in our buffer. We only need in.length() more as we + // will be dropping one of the two terminators present in this operation. + mBuffer.increment(len); + + for(S32 i=copyCharLength-1; i>=0; i--) + mBuffer[charOffset+i+len] = mBuffer[charOffset+i]; + + // Can't copy directly: memcpy behavior is undefined if src and dst overlap. + //dMemcpy(&mBuffer[charOffset+len], &mBuffer[charOffset], sizeof(UTF16) * copyCharLength); + + // And finally copy the new data in, not including its terminator. + dMemcpy(&mBuffer[charOffset], in, sizeof(UTF16) * len); + + // All done! + AssertFatal(mBuffer.last() == 0, "StringBuffer::insert - not a null terminated string!"); + + // mark utf8 buffer dirty + mDirty8 = true; +} + +StringBuffer StringBuffer::substring(const U32 start, const U32 len) const +{ + // Deal with bonehead user input. + if(start >= length() || len == 0) + { + // Either they asked beyond the end of the string or we're null. Either + // way, let's give them a null string. + return StringBuffer(""); + } + + AssertFatal(start < length(), "StringBuffer::substring - invalid start!"); + AssertFatal(start+len <= length(), "StringBuffer::substring - invalid len!"); + AssertFatal(len > 0, "StringBuffer::substring - len must be >= 1."); + + StringBuffer tmp; + tmp.mBuffer.clear(); + for(S32 i=0; isubstring(start, len); + return sub.createCopy8(); +} + +void StringBuffer::cut(const U32 start, const U32 len) +{ + AssertFatal(start < length(), "StringBuffer::cut - invalid start!"); + AssertFatal(start+len <= length(), "StringBuffer::cut - invalid len!"); + AssertFatal(len > 0, "StringBuffer::cut - len >= 1."); + + AssertFatal(mBuffer.last() == 0, "StringBuffer::cut - not a null terminated string! (pre)"); + + // Now snip things. + for(S32 i=start; i(this)->updateBuffer8(); + return mBuffer8.address(); +} + +void StringBuffer::updateBuffer8() +{ + U32 slackLen = getUTF8BufferSizeEstimate(); + mBuffer8.setSize(slackLen); + U32 len = convertUTF16toUTF8(mBuffer.address(), mBuffer8.address(), slackLen); + mBuffer8.setSize(len+1); + mBuffer8.compact(); + mDirty8 = false; +} + + +#if defined(TORQUE_DEBUG) +StringBufferManager& StringBufferManager::getManager() +{ + static StringBufferManager _sbm; return _sbm; +} + +void StringBufferManager::add(StringBuffer* s) +{ + strings.push_back(s); +} + +void StringBufferManager::remove(StringBuffer* s) +{ + U32 nstrings = strings.size() - 1; + for(S32 i = nstrings; i >= 0; i--) + if(strings[i] == s) + strings.erase_fast(i); +} + +void StringBufferManager::updateStats() +{ + request8 = 0; + request16 = 0; + U32 nstrings = strings.size(); + for(int i=0; i < nstrings; i++) + { + request8 += strings[i]->rc->requestCount8; + request16 += strings[i]->rc->requestCount16; + } +} + +void StringBufferManager::dumpStats() +{ + updateStats(); + Con::printf("===== String Manager Stats ====="); + Con::printf(" strings: %i", strings.size()); + Con::printf(" utf8 requests: %Lu", request8); + Con::printf(" utf16 requests: %Lu", request16); +} + +void StringBufferManager::dumpAllStrings() +{ + U32 nstrings = strings.size(); + Con::printf("===== String Manager: All Strings ====="); + Con::printf(" utf8 | utf16 | string"); + for(int i=0; i < nstrings; i++) + { + UTF8* tmp = strings[i]->createCopy8(); + strings[i]->rc->requestCount8--; + Con::printf("%5llu %5llu \"%s\"", strings[i]->rc->requestCount8, strings[i]->rc->requestCount16, tmp); + delete[] tmp; + } +} + +#endif diff --git a/core/stringBuffer.h b/core/stringBuffer.h new file mode 100644 index 0000000..56e1ccd --- /dev/null +++ b/core/stringBuffer.h @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STRINGBUFFER_H_ +#define _STRINGBUFFER_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#include "console/console.h" + +/// Utility class to wrap string manipulation in a representation +/// independent way. +/// +/// Length does NOT include the null terminator. +class StringBuffer +{ + Vector mBuffer; + mutable Vector mBuffer8; + mutable bool mDirty8; + +public: + #if defined(TORQUE_DEBUG) + typedef struct RequestCounts + { + U64 requestCount8; + U64 requestCount16; + }; + RequestCounts *rc; + #endif + + StringBuffer(); + StringBuffer(const StringBuffer ©); + StringBuffer(const StringBuffer *in); + StringBuffer(const UTF8 *in); + StringBuffer(const UTF16 *in); + + ~StringBuffer(); + + void append(const StringBuffer &in); + void append(const UTF8* in); + void append(const UTF16* in); + void append(const UTF16* in, U32 len); + + void insert(const U32 charOffset, const StringBuffer &in); + void insert(const U32 charOffset, const UTF8* in); + void insert(const U32 charOffset, const UTF16* in); + void insert(const U32 charOffset, const UTF16* in, const U32 len); + + /// Get a StringBuffer substring of length 'len' starting from 'start'. + /// Returns a new StringBuffer by value; + StringBuffer substring(const U32 start, const U32 len) const; + + /// Get a pointer to a substring of length 'len' starting from 'start'. + /// Returns a raw pointer to a unicode string. + /// You must delete[] the returned string when you are done with it. + /// This follows the "create rule". + UTF8* createSubstring8(const U32 start, const U32 len) const; + UTF16* createSubstring16(const U32 start, const U32 len) const; + + void cut(const U32 start, const U32 len); +// UTF8* cut8(const U32 start, const U32 len); +// UTF16* cut16(const U32 start, const U32 len); + + const UTF16 getChar(const U32 offset) const; + void setChar(const U32 offset, UTF16 c); + + void set(const StringBuffer *in); + void set(const UTF8 *in); + void set(const UTF16 *in); + + inline const U32 length() const + { + return mBuffer.size() - 1; // Don't count the NULL of course. + } + + /// Get an upper bound size estimate for a UTF8 buffer to hold this + /// string. + const U32 getUTF8BufferSizeEstimate() const + { + return length() * 3 + 1; + } + + void getCopy8(UTF8 *buff, const U32 buffSize) const; + void getCopy(UTF16 *buff, const U32 buffSize) const; + + /// Get a copy of the contents of the string buffer. + /// You must delete[] the returned copy when you are done with it. + /// This follows the "create rule". + UTF8* createCopy8() const; + UTF16* createCopy() const; + + /// Get a pointer to the StringBuffer's data store. + /// Use this in situations where you can be sure that the StringBuffer will + /// not be modified out from under you. + /// The win here is, you avoid yet another data copy. Data copy is slow on + /// most modern hardware. + const UTF16* getPtr() const; + const UTF8* getPtr8() const; + +private: + void updateBuffer8(); + #if defined(TORQUE_DEBUG) + void clearRequestCounts() { rc->requestCount16 = 0; rc->requestCount8 = 0; } + #endif + +}; + +#endif \ No newline at end of file diff --git a/core/stringTable.cpp b/core/stringTable.cpp new file mode 100644 index 0000000..9bbb453 --- /dev/null +++ b/core/stringTable.cpp @@ -0,0 +1,213 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "core/stringTable.h" + +_StringTable *_gStringTable = NULL; +const U32 _StringTable::csm_stInitSize = 29; + +//--------------------------------------------------------------- +// +// StringTable functions +// +//--------------------------------------------------------------- + +namespace { +bool sgInitTable = true; +U8 sgHashTable[256]; + +void initTolowerTable() +{ + for (U32 i = 0; i < 256; i++) { + U8 c = dTolower(i); + sgHashTable[i] = c * c; + } + + sgInitTable = false; +} + +} // namespace {} + +U32 _StringTable::hashString(const char* str) +{ + if (sgInitTable) + initTolowerTable(); + + if(!str) return -1; + + U32 ret = 0; + char c; + while((c = *str++) != 0) { + ret <<= 1; + ret ^= sgHashTable[static_cast(c)]; + } + return ret; +} + +U32 _StringTable::hashStringn(const char* str, S32 len) +{ + if (sgInitTable) + initTolowerTable(); + + U32 ret = 0; + char c; + while((c = *str++) != 0 && len--) { + ret <<= 1; + ret ^= sgHashTable[static_cast(c)]; + } + return ret; +} + +//-------------------------------------- +_StringTable::_StringTable() +{ + buckets = (Node **) dMalloc(csm_stInitSize * sizeof(Node *)); + for(U32 i = 0; i < csm_stInitSize; i++) { + buckets[i] = 0; + } + + numBuckets = csm_stInitSize; + itemCount = 0; +} + +//-------------------------------------- +_StringTable::~_StringTable() +{ + dFree(buckets); +} + + +//-------------------------------------- +void _StringTable::create() +{ + //AssertFatal(_gStringTable == NULL, "StringTable::create: StringTable already exists."); + if(!_gStringTable) + _gStringTable = new _StringTable; +} + + +//-------------------------------------- +void _StringTable::destroy() +{ + AssertFatal(StringTable != NULL, "StringTable::destroy: StringTable does not exist."); + delete _gStringTable; + _gStringTable = NULL; +} + + +//-------------------------------------- +StringTableEntry _StringTable::insert(const char* _val, const bool caseSens) +{ + // Added 3/29/2007 -- If this is undesirable behavior, let me know -patw + const char *val = _val; + if( val == NULL ) + val = ""; + //- + + Node **walk, *temp; + U32 key = hashString(val); + walk = &buckets[key % numBuckets]; + while((temp = *walk) != NULL) { + if(caseSens && !dStrcmp(temp->val, val)) + return temp->val; + else if(!caseSens && !dStricmp(temp->val, val)) + return temp->val; + walk = &(temp->next); + } + char *ret = 0; + if(!*walk) { + *walk = (Node *) mempool.alloc(sizeof(Node)); + (*walk)->next = 0; + (*walk)->val = (char *) mempool.alloc(dStrlen(val) + 1); + dStrcpy((*walk)->val, val); + ret = (*walk)->val; + itemCount ++; + } + if(itemCount > 2 * numBuckets) { + resize(4 * numBuckets - 1); + } + return ret; +} + +//-------------------------------------- +StringTableEntry _StringTable::insertn(const char* src, S32 len, const bool caseSens) +{ + char val[256]; + AssertFatal(len < 255, "Invalid string to insertn"); + dStrncpy(val, src, len); + val[len] = 0; + return insert(val, caseSens); +} + +//-------------------------------------- +StringTableEntry _StringTable::lookup(const char* val, const bool caseSens) +{ + Node **walk, *temp; + U32 key = hashString(val); + walk = &buckets[key % numBuckets]; + while((temp = *walk) != NULL) { + if(caseSens && !dStrcmp(temp->val, val)) + return temp->val; + else if(!caseSens && !dStricmp(temp->val, val)) + return temp->val; + walk = &(temp->next); + } + return NULL; +} + +//-------------------------------------- +StringTableEntry _StringTable::lookupn(const char* val, S32 len, const bool caseSens) +{ + Node **walk, *temp; + U32 key = hashStringn(val, len); + walk = &buckets[key % numBuckets]; + while((temp = *walk) != NULL) { + if(caseSens && !dStrncmp(temp->val, val, len) && temp->val[len] == 0) + return temp->val; + else if(!caseSens && !dStrnicmp(temp->val, val, len) && temp->val[len] == 0) + return temp->val; + walk = &(temp->next); + } + return NULL; +} + +//-------------------------------------- +void _StringTable::resize(const U32 newSize) +{ + Node *head = NULL, *walk, *temp; + U32 i; + // reverse individual bucket lists + // we do this because new strings are added at the end of bucket + // lists so that case sens strings are always after their + // corresponding case insens strings + + for(i = 0; i < numBuckets; i++) { + walk = buckets[i]; + while(walk) + { + temp = walk->next; + walk->next = head; + head = walk; + walk = temp; + } + } + buckets = (Node **) dRealloc(buckets, newSize * sizeof(Node)); + for(i = 0; i < newSize; i++) { + buckets[i] = 0; + } + numBuckets = newSize; + walk = head; + while(walk) { + U32 key; + Node *temp = walk; + + walk = walk->next; + key = hashString(temp->val); + temp->next = buckets[key % newSize]; + buckets[key % newSize] = temp; + } +} + diff --git a/core/stringTable.h b/core/stringTable.h new file mode 100644 index 0000000..7be4ee9 --- /dev/null +++ b/core/stringTable.h @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STRINGTABLE_H_ +#define _STRINGTABLE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif + + +//-------------------------------------- +/// A global table for the hashing and tracking of strings. +/// +/// Only one _StringTable is ever instantiated in Torque. It is accessible via the +/// global variable StringTable. +/// +/// StringTable is used to manage strings in Torque. It performs the following tasks: +/// - Ensures that only one pointer is ever used for a given string (through +/// insert()). +/// - Allows the lookup of a string in the table. +/// +/// @code +/// // Adding a string to the StringTable. +/// StringTableEntry mRoot; +/// mRoot = StringTable->insert(root); +/// +/// // Looking up a string in the StringTable. +/// StringTableEntry stName = StringTable->lookupn(name, len); +/// +/// // Comparing two strings in the StringTable (see below). +/// if(mRoot == stName) Con::printf("These strings are equal!"); +/// @endcode +/// +/// But why is this useful, you ask? Because every string that's run through the +/// StringTable is stored once and only once, every string has one and only one +/// pointer mapped to it. As a pointer is an integer value (usually an unsigned int), +/// so we can do several neat things: +/// - StringTableEntrys can be compared directly for equality, instead of using +/// the time-consuming dStrcmp() or dStricmp() function. +/// - For things like object names, we can avoid storing multiple copies of the +/// string containing the name. The StringTable ensures that we only ever store +/// one copy. +/// - When we're doing lookups by name (for instances, of resources), we can determine +/// if the object is even registered in the system by looking up its name in the +/// StringTable. Then, we can use the pointer as a hash key. +/// +/// The scripting engine and the resource manager are the primary users of the +/// StringTable. +/// +/// @note Be aware that the StringTable NEVER DEALLOCATES memory, so be careful when you +/// add strings to it. If you carelessly add many strings, you will end up wasting +/// space. +class _StringTable +{ +private: + /// @name Implementation details + /// @{ + + /// This is internal to the _StringTable class. + struct Node + { + char *val; + Node *next; + }; + + Node** buckets; + U32 numBuckets; + U32 itemCount; + DataChunker mempool; + + protected: + static const U32 csm_stInitSize; + + _StringTable(); + ~_StringTable(); + + /// @} + public: + + /// Initialize StringTable. + /// + /// This is called at program start to initialize the StringTable global. + static void create(); + + /// Destroy the StringTable + /// + /// This is called at program end to destroy the StringTable global. + static void destroy(); + + /// Get a pointer from the string table, adding the string to the table + /// if it was not already present. + /// + /// @param string String to check in the table (and add). + /// @param caseSens Determines whether case matters. + StringTableEntry insert(const char *string, bool caseSens = false); + + /// Get a pointer from the string table, adding the string to the table + /// if it was not already present. + /// + /// @param string String to check in the table (and add). + /// @param len Length of the string in bytes. + /// @param caseSens Determines whether case matters. + StringTableEntry insertn(const char *string, S32 len, bool caseSens = false); + + /// Get a pointer from the string table, NOT adding the string to the table + /// if it was not already present. + /// + /// @param string String to check in the table (but not add). + /// @param caseSens Determines whether case matters. + StringTableEntry lookup(const char *string, bool caseSens = false); + + /// Get a pointer from the string table, NOT adding the string to the table + /// if it was not already present. + /// + /// @param string String to check in the table (but not add). + /// @param len Length of string in bytes. + /// @param caseSens Determines whether case matters. + StringTableEntry lookupn(const char *string, S32 len, bool caseSens = false); + + + /// Resize the StringTable to be able to hold newSize items. This + /// is called automatically by the StringTable when the table is + /// full past a certain threshhold. + /// + /// @param newSize Number of new items to allocate space for. + void resize(const U32 newSize); + + /// Hash a string into a U32. + static U32 hashString(const char* in_pString); + + /// Hash a string of given length into a U32. + static U32 hashStringn(const char* in_pString, S32 len); +}; + + +extern _StringTable *_gStringTable; + +inline _StringTable* _getStringTable() +{ + if(_gStringTable == NULL) + _StringTable::create(); + return _gStringTable; +} + +#define StringTable _getStringTable() + +#endif //_STRINGTABLE_H_ + diff --git a/core/strings/findMatch.cpp b/core/strings/findMatch.cpp new file mode 100644 index 0000000..ec51d30 --- /dev/null +++ b/core/strings/findMatch.cpp @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/findMatch.h" +#include "core/strings/stringFunctions.h" + + +//-------------------------------------------------------------------------------- +// NAME +// FindMatch::FindMatch( const char *_expression, S32 maxNumMatches ) +// +// DESCRIPTION +// Class to match regular expressions (file names) +// only works with '*','?', and 'chars' +// +// ARGUMENTS +// _expression - The regular expression you intend to match (*.??abc.bmp) +// _maxMatches - The maximum number of strings you wish to match. +// +// RETURNS +// +// NOTES +// +//-------------------------------------------------------------------------------- + +FindMatch::FindMatch( U32 _maxMatches ) +{ + VECTOR_SET_ASSOCIATION(matchList); + + expression = NULL; + maxMatches = _maxMatches; + matchList.reserve( maxMatches ); +} + +FindMatch::FindMatch( char *_expression, U32 _maxMatches ) +{ + VECTOR_SET_ASSOCIATION(matchList); + + expression = NULL; + setExpression( _expression ); + maxMatches = _maxMatches; + matchList.reserve( maxMatches ); +} + +FindMatch::~FindMatch() +{ + delete [] expression; + matchList.clear(); +} + +void FindMatch::setExpression( const char *_expression ) +{ + delete [] expression; + + expression = new char[dStrlen(_expression) + 1]; + dStrcpy(expression, _expression); + dStrupr(expression); +} + +bool FindMatch::findMatch( const char *str, bool caseSensitive ) +{ + if ( isFull() ) + return false; + + char nstr[512]; + dStrcpy( nstr,str ); + dStrupr(nstr); + if ( isMatch( expression, nstr, caseSensitive ) ) + { + matchList.push_back( (char*)str ); + return true; + } + return false; +} + +bool FindMatch::isMatch( const char *exp, const char *str, bool caseSensitive ) +{ + const char *e=exp; + const char *s=str; + bool match=true; + + while ( match && *e && *s ) + { + switch( *e ) + { + case '*': + e++; + match = false; + while( !match && (*s != '\0') && ((s=dStrchr(s,*e)) != NULL) ) + { + match = isMatch( e, s, caseSensitive ); + s++; + } + return( match ); + + case '?': + e++; + s++; + break; + + default: + if (caseSensitive) + match = ( *e++ == *s++ ); + else + match = ( dToupper(*e++) == dToupper(*s++) ); + + break; + } + } + + if (*e != *s) // both exp and str should be at '\0' if match was successful + match = false; + + return ( match ); +} + + +bool FindMatch::isMatchMultipleExprs( const char *exps, const char *str, bool caseSensitive ) +{ + char *tok = 0; + int len = dStrlen(exps); + + char *e = new char[len+1]; + dStrcpy(e,exps); + + // [tom, 12/18/2006] This no longer supports space separated expressions as + // they don't work when the paths have spaces in. + + // search for each expression. return true soon as we see one. + for( tok = dStrtok(e,"\t"); tok != NULL; tok = dStrtok(NULL,"\t")) + { + if( isMatch( tok, str, caseSensitive) ) + { + delete []e; + return true; + } + } + + delete []e; + return false; +} diff --git a/core/strings/findMatch.h b/core/strings/findMatch.h new file mode 100644 index 0000000..dcafbf7 --- /dev/null +++ b/core/strings/findMatch.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FINDMATCH_H_ +#define _FINDMATCH_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class FindMatch +{ + char* expression; + U32 maxMatches; + + public: + static bool isMatch( const char *exp, const char *string, bool caseSensitive = false ); + static bool isMatchMultipleExprs( const char *exps, const char *str, bool caseSensitive ); + Vector matchList; + + FindMatch( U32 _maxMatches = 256 ); + FindMatch( char *_expression, U32 _maxMatches = 256 ); + ~FindMatch(); + + bool findMatch(const char *string, bool caseSensitive = false); + void setExpression( const char *_expression ); + + S32 numMatches() const + { + return(matchList.size()); + } + + bool isFull() const + { + return (matchList.size() >= S32(maxMatches)); + } + + void clear() + { + matchList.clear(); + } +}; + +#endif // _FINDMATCH_H_ diff --git a/core/strings/stringFunctions.cpp b/core/strings/stringFunctions.cpp new file mode 100644 index 0000000..ab186c1 --- /dev/null +++ b/core/strings/stringFunctions.cpp @@ -0,0 +1,456 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include + +#include "core/strings/stringFunctions.h" +#include "platform/platform.h" + + +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) +// This standard function is not defined when compiling with VC7... +#define vsnprintf _vsnprintf +#endif + + +//----------------------------------------------------------------------------- + +// Original code from: http://sourcefrog.net/projects/natsort/ +// Somewhat altered here. +//TODO: proper UTF8 support; currently only working for single-byte characters + +/* -*- mode: c; c-file-style: "k&r" -*- + + strnatcmp.c -- Perform 'natural order' comparisons of strings in C. + Copyright (C) 2000, 2004 by Martin Pool + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + + +/* partial change history: + * + * 2004-10-10 mbp: Lift out character type dependencies into macros. + * + * Eric Sosman pointed out that ctype functions take a parameter whose + * value must be that of an unsigned int, even on platforms that have + * negative chars in their default char type. + */ + +typedef char nat_char; + +/* These are defined as macros to make it easier to adapt this code to + * different characters types or comparison functions. */ +static inline int +nat_isdigit( nat_char a ) +{ + return dIsdigit( a ); +} + + +static inline int +nat_isspace( nat_char a ) +{ + return dIsspace( a ); +} + + +static inline nat_char +nat_toupper( nat_char a ) +{ + return dToupper( a ); +} + + + +static int +compare_right(const nat_char* a, const nat_char* b) +{ + int bias = 0; + + /* The longest run of digits wins. That aside, the greatest + value wins, but we can't know that it will until we've scanned + both numbers to know that they have the same magnitude, so we + remember it in BIAS. */ + for (;; a++, b++) { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return bias; + else if (!nat_isdigit(*a)) + return -1; + else if (!nat_isdigit(*b)) + return +1; + else if (*a < *b) { + if (!bias) + bias = -1; + } else if (*a > *b) { + if (!bias) + bias = +1; + } else if (!*a && !*b) + return bias; + } + + return 0; +} + + +static int +compare_left(const nat_char* a, const nat_char* b) +{ + /* Compare two left-aligned numbers: the first to have a + different value wins. */ + for (;; a++, b++) { + if (!nat_isdigit(*a) && !nat_isdigit(*b)) + return 0; + else if (!nat_isdigit(*a)) + return -1; + else if (!nat_isdigit(*b)) + return +1; + else if (*a < *b) + return -1; + else if (*a > *b) + return +1; + } + + return 0; +} + + +static int strnatcmp0(const nat_char* a, const nat_char* b, int fold_case) +{ + int ai, bi; + nat_char ca, cb; + int fractional, result; + + ai = bi = 0; + while (1) { + ca = a[ai]; cb = b[bi]; + + /* skip over leading spaces or zeros */ + while (nat_isspace(ca)) + ca = a[++ai]; + + while (nat_isspace(cb)) + cb = b[++bi]; + + /* process run of digits */ + if (nat_isdigit(ca) && nat_isdigit(cb)) { + fractional = (ca == '0' || cb == '0'); + + if (fractional) { + if ((result = compare_left(a+ai, b+bi)) != 0) + return result; + } else { + if ((result = compare_right(a+ai, b+bi)) != 0) + return result; + } + } + + if (!ca && !cb) { + /* The strings compare the same. Perhaps the caller + will want to call strcmp to break the tie. */ + return 0; + } + + if (fold_case) { + ca = nat_toupper(ca); + cb = nat_toupper(cb); + } + + if (ca < cb) + return -1; + else if (ca > cb) + return +1; + + ++ai; ++bi; + } +} + + +int dStrnatcmp(const nat_char* a, const nat_char* b) { + return strnatcmp0(a, b, 0); +} + + +/* Compare, recognizing numeric string and ignoring case. */ +int dStrnatcasecmp(const nat_char* a, const nat_char* b) { + return strnatcmp0(a, b, 1); +} + +//------------------------------------------------------------------------------ +// non-standard string functions + +char *dStrdup_r(const char *src, const char *fileName, dsize_t lineNumber) +{ + char *buffer = (char *) dMalloc_r(dStrlen(src) + 1, fileName, lineNumber); + dStrcpy(buffer, src); + return buffer; +} + + +// concatenates a list of src's onto the end of dst +// the list of src's MUST be terminated by a NULL parameter +// dStrcatl(dst, sizeof(dst), src1, src2, NULL); +char* dStrcatl(char *dst, dsize_t dstSize, ...) +{ + const char* src = NULL; + char *p = dst; + + AssertFatal(dstSize > 0, "dStrcatl: destination size is set zero"); + dstSize--; // leave room for string termination + + // find end of dst + while (dstSize && *p++) + dstSize--; + + va_list args; + va_start(args, dstSize); + + // concatenate each src to end of dst + while ( (src = va_arg(args, const char*)) != NULL ) + { + while( dstSize && *src ) + { + *p++ = *src++; + dstSize--; + } + } + + va_end(args); + + // make sure the string is terminated + *p = 0; + + return dst; +} + + +// copy a list of src's into dst +// the list of src's MUST be terminated by a NULL parameter +// dStrccpyl(dst, sizeof(dst), src1, src2, NULL); +char* dStrcpyl(char *dst, dsize_t dstSize, ...) +{ + const char* src = NULL; + char *p = dst; + + AssertFatal(dstSize > 0, "dStrcpyl: destination size is set zero"); + dstSize--; // leave room for string termination + + va_list args; + va_start(args, dstSize); + + // concatenate each src to end of dst + while ( (src = va_arg(args, const char*)) != NULL ) + { + while( dstSize && *src ) + { + *p++ = *src++; + dstSize--; + } + } + + va_end(args); + + // make sure the string is terminated + *p = 0; + + return dst; +} + + +int dStrcmp( const UTF16 *str1, const UTF16 *str2) +{ +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) + return wcscmp( reinterpret_cast( str1 ), reinterpret_cast( str2 ) ); +#else + int ret; + const UTF16 *a, *b; + a = str1; + b = str2; + + while( ((ret = *a - *b) == 0) && *a && *b ) + a++, b++; + + return ret; +#endif +} + +char* dStrupr(char *str) +{ +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) + return _strupr(str); +#else + if (str == NULL) + return(NULL); + + char* saveStr = str; + while (*str) + { + *str = toupper(*str); + str++; + } + return saveStr; +#endif +} + +char* dStrlwr(char *str) +{ +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) + return _strlwr(str); +#else + if (str == NULL) + return(NULL); + + char* saveStr = str; + while (*str) + { + *str = tolower(*str); + str++; + } + return saveStr; +#endif +} + +//------------------------------------------------------------------------------ +// standard I/O functions + +void dPrintf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vprintf(format, args); +} + +S32 dVprintf(const char *format, void *arglist) +{ + return vprintf(format, (char*)arglist); +} + +S32 dSprintf(char *buffer, U32 bufferSize, const char *format, ...) +{ + va_list args; + va_start(args, format); + + S32 len = vsnprintf(buffer, bufferSize, format, args); + + AssertWarn( len < bufferSize, "Buffer too small in call to dSprintf!" ); + + return (len); +} + + +S32 dVsprintf(char *buffer, U32 bufferSize, const char *format, void *arglist) +{ + S32 len = vsnprintf(buffer, bufferSize, format, (char*)arglist); + + AssertWarn( len < bufferSize, "Buffer too small in call to dVsprintf!" ); + + return (len); +} + + +S32 dSscanf(const char *buffer, const char *format, ...) +{ +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) + va_list args; + va_start(args, format); + + // Boy is this lame. We have to scan through the format string, and find out how many + // arguments there are. We'll store them off as void*, and pass them to the sscanf + // function through specialized calls. We're going to have to put a cap on the number of args that + // can be passed, 8 for the moment. Sigh. + static void* sVarArgs[20]; + U32 numArgs = 0; + + for (const char* search = format; *search != '\0'; search++) { + if (search[0] == '%' && search[1] != '%') + numArgs++; + } + AssertFatal(numArgs <= 20, "Error, too many arguments to lame implementation of dSscanf. Fix implmentation"); + + // Ok, we have the number of arguments... + for (U32 i = 0; i < numArgs; i++) + sVarArgs[i] = va_arg(args, void*); + va_end(args); + + switch (numArgs) { + case 0: return 0; + case 1: return sscanf(buffer, format, sVarArgs[0]); + case 2: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1]); + case 3: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2]); + case 4: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3]); + case 5: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4]); + case 6: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5]); + case 7: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6]); + case 8: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7]); + case 9: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8]); + case 10: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9]); + case 11: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10]); + case 12: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11]); + case 13: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12]); + case 14: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13]); + case 15: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14]); + case 16: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15]); + case 17: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16]); + case 18: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16], sVarArgs[17]); + case 19: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16], sVarArgs[17], sVarArgs[18]); + case 20: return sscanf(buffer, format, sVarArgs[0], sVarArgs[1], sVarArgs[2], sVarArgs[3], sVarArgs[4], sVarArgs[5], sVarArgs[6], sVarArgs[7], sVarArgs[8], sVarArgs[9], sVarArgs[10], sVarArgs[11], sVarArgs[12], sVarArgs[13], sVarArgs[14], sVarArgs[15], sVarArgs[16], sVarArgs[17], sVarArgs[18], sVarArgs[19]); + } + return 0; +#else + va_list args; + va_start(args, format); + return vsscanf(buffer, format, args); +#endif +} + +/// Safe form of dStrcmp: checks both strings for NULL before comparing +bool dStrEqual(const char* str1, const char* str2) +{ + if (!str1 || !str2) + return false; + else + return (dStrcmp(str1, str2) == 0); +} + +/// Check if one string starts with another +bool dStrStartsWith(const char* str1, const char* str2) +{ + return !dStrnicmp(str1, str2, dStrlen(str2)); +} + +/// Check if one string ends with another +bool dStrEndsWith(const char* str1, const char* str2) +{ + const char *p = str1 + dStrlen(str1) - dStrlen(str2); + return ((p >= str1) && !dStricmp(p, str2)); +} + +/// Strip the path from the input filename +char* dStripPath(const char* filename) +{ + const char* itr = filename + dStrlen(filename); + while(--itr != filename) { + if (*itr == '/' || *itr == '\\') { + itr++; + break; + } + } + return dStrdup(itr); +} diff --git a/core/strings/stringFunctions.h b/core/strings/stringFunctions.h new file mode 100644 index 0000000..87da3fa --- /dev/null +++ b/core/strings/stringFunctions.h @@ -0,0 +1,200 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STRINGFUNCTIONS_H_ +#define _STRINGFUNCTIONS_H_ + +#include +#include +#include + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) +// These standard functions are not defined on Win32 and other Microsoft platforms... +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#define strtof (float)strtod +#endif + + +//------------------------------------------------------------------------------ +// standard string functions [defined in platformString.cpp] + +inline char *dStrcat(char *dst, const char *src) +{ + return strcat(dst,src); +} + +inline char *dStrncat(char *dst, const char *src, dsize_t len) +{ + return strncat(dst,src,len); +} + +inline int dStrcmp(const char *str1, const char *str2) +{ + return strcmp(str1, str2); +} + +inline int dStrncmp(const char *str1, const char *str2, dsize_t len) +{ + return strncmp(str1, str2, len); +} + +inline int dStricmp(const char *str1, const char *str2) +{ + return strcasecmp( str1, str2 ); +} + +inline int dStrnicmp(const char *str1, const char *str2, dsize_t len) +{ + return strncasecmp( str1, str2, len ); +} + +inline char *dStrcpy(char *dst, const char *src) +{ + return strcpy(dst,src); +} + +inline char *dStrncpy(char *dst, const char *src, dsize_t len) +{ + return strncpy(dst,src,len); +} + +inline dsize_t dStrlen(const char *str) +{ + return strlen(str); +} + +inline char *dStrchr(char *str, int c) +{ + return strchr(str,c); +} + +inline const char *dStrchr(const char *str, int c) +{ + return strchr(str,c); +} + +inline char *dStrrchr(char *str, int c) +{ + return strrchr(str,c); +} + +inline const char *dStrrchr(const char *str, int c) +{ + return strrchr(str,c); +} + +inline dsize_t dStrspn(const char *str, const char *set) +{ + return strspn(str, set); +} + +inline dsize_t dStrcspn(const char *str, const char *set) +{ + return strcspn(str, set); +} + +inline char *dStrstr(const char *str1, const char *str2) +{ + return strstr((char *)str1,str2); +} + + +inline char *dStrtok(char *str, const char *sep) +{ + return strtok(str, sep); +} + + +inline S32 dAtoi(const char *str) +{ + return strtol(str, NULL, 10); +} + +inline U32 dAtoui(const char *str, U32 base = 10) +{ + return strtoul(str, NULL, base); +} + +inline F32 dAtof(const char *str) +{ + return strtof(str, NULL); +} + + +inline char dToupper(const char c) +{ + return toupper( c ); +} + +inline char dTolower(const char c) +{ + return tolower( c ); +} + +inline bool dIsalnum(const char c) +{ + return isalnum(c); +} + +inline bool dIsalpha(const char c) +{ + return isalpha(c); +} + +inline bool dIsspace(const char c) +{ + return isspace(c); +} + +inline bool dIsdigit(const char c) +{ + return isdigit(c); +} + + +//------------------------------------------------------------------------------ +// non-standard string functions [defined in stringFunctions.cpp] + +#define dStrdup(x) dStrdup_r(x, __FILE__, __LINE__) +extern char *dStrdup_r(const char *src, const char*, dsize_t); + +extern char *dStrcpyl(char *dst, dsize_t dstSize, ...); +extern char *dStrcatl(char *dst, dsize_t dstSize, ...); + +extern char *dStrupr(char *str); +extern char *dStrlwr(char *str); + +extern int dStrcmp(const UTF16 *str1, const UTF16 *str2); +extern int dStrnatcmp( const char* str1, const char* str2 ); +extern int dStrnatcasecmp( const char* str1, const char* str2 ); + +inline bool dAtob(const char *str) +{ + return !dStricmp(str, "true") || dAtof(str); +} + +bool dStrEqual(const char* str1, const char* str2); + +bool dStrStartsWith(const char* str1, const char* str2); + +bool dStrEndsWith(const char* str1, const char* str2); + +char* dStripPath(const char* filename); + +//------------------------------------------------------------------------------ +// standard I/O functions [defined in platformString.cpp] + +extern void dPrintf(const char *format, ...); +extern int dVprintf(const char *format, void *arglist); +extern int dSprintf(char *buffer, U32 bufferSize, const char *format, ...); +extern int dVsprintf(char *buffer, U32 bufferSize, const char *format, void *arglist); +extern int dSscanf(const char *buffer, const char *format, ...); + +#endif diff --git a/core/strings/stringUnit.cpp b/core/strings/stringUnit.cpp new file mode 100644 index 0000000..a2c28c9 --- /dev/null +++ b/core/strings/stringUnit.cpp @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "core/strings/stringUnit.h" +#include "console/console.h" + +namespace StringUnit +{ + static char _returnBuffer[1024]; + + const char *getUnit(const char *string, U32 index, const char *set) + { + U32 sz; + while(index--) + { + if(!*string) + return ""; + sz = dStrcspn(string, set); + if (string[sz] == 0) + return ""; + string += (sz + 1); + } + sz = dStrcspn(string, set); + if (sz == 0) + return ""; + + AssertFatal( sz + 1 < sizeof( _returnBuffer ), "Size of returned string too large for return buffer" ); + + char *ret = &_returnBuffer[0]; + dStrncpy(ret, string, sz); + ret[sz] = '\0'; + return ret; + } + + const char *getUnits(const char *string, S32 startIndex, S32 endIndex, const char *set) + { + // [neo, 5/11/2007 - #2998] + // Range check + if( startIndex > endIndex ) + return ""; + + S32 sz; + S32 index = startIndex; + while(index--) + { + if(!*string) + return ""; + sz = dStrcspn(string, set); + if (string[sz] == 0) + return ""; + string += (sz + 1); + } + const char *startString = string; + while(startIndex <= endIndex--) + { + sz = dStrcspn(string, set); + string += sz; + if (*string == 0) + break; + string++; + } + if(!*string) + string++; + + // [neo, 5/11/2007 - #2998] + // Another case of the U32 wrapping from 0 to boom! If totalSize < 0 it will + // wrap to somewhere around U32 max and that is a lot for dStrncpy to overrun! + //U32 totalSize = (U32(string - startString)); + S32 totalSize = (S32)(string - startString); + + AssertFatal( totalSize < sizeof( _returnBuffer ), "Size of returned string too large for return buffer" ); + + if( totalSize > 0 ) + { + char *ret = &_returnBuffer[0]; + dStrncpy(ret, startString, totalSize - 1); + ret[totalSize-1] = '\0'; + + return ret; + } + + return ""; + } + + U32 getUnitCount(const char *string, const char *set) + { + U32 count = 0; + U8 last = 0; + while(*string) + { + last = *string++; + + for(U32 i =0; set[i]; i++) + { + if(last == set[i]) + { + count++; + last = 0; + break; + } + } + } + if(last) + count++; + return count; + } + + + const char* setUnit(const char *string, U32 index, const char *replace, const char *set) + { + U32 sz; + const char *start = string; + + AssertFatal( dStrlen(string) + dStrlen(replace) + 1 < sizeof( _returnBuffer ), "Size of returned string too large for return buffer" ); + + char *ret = &_returnBuffer[0]; + ret[0] = '\0'; + U32 padCount = 0; + + while(index--) + { + sz = dStrcspn(string, set); + if(string[sz] == 0) + { + string += sz; + padCount = index + 1; + break; + } + else + string += (sz + 1); + } + // copy first chunk + sz = string-start; + dStrncpy(ret, start, sz); + for(U32 i = 0; i < padCount; i++) + ret[sz++] = set[0]; + + // replace this unit + ret[sz] = '\0'; + dStrcat(ret, replace); + + // copy remaining chunks + sz = dStrcspn(string, set); // skip chunk we're replacing + if(!sz && !string[sz]) + return ret; + + string += sz; + dStrcat(ret, string); + return ret; + } + + + const char* removeUnit(const char *string, U32 index, const char *set) + { + U32 sz; + const char *start = string; + AssertFatal( dStrlen(string) + 1 < sizeof( _returnBuffer ), "Size of returned string too large for return buffer" ); + + char *ret = &_returnBuffer[0]; + ret[0] = '\0'; + + while(index--) + { + sz = dStrcspn(string, set); + // if there was no unit out there... return the original string + if(string[sz] == 0) + return start; + else + string += (sz + 1); + } + // copy first chunk + sz = string-start; + dStrncpy(ret, start, sz); + ret[sz] = 0; + + // copy remaining chunks + sz = dStrcspn(string, set); // skip chunk we're removing + + if(string[sz] == 0) { // if that was the last... + if(string != start) { + ret[string - start - 1] = 0; // then kill any trailing delimiter + } + return ret; // and bail + } + + string += sz + 1; // skip the extra field delimiter + dStrcat(ret, string); + return ret; + } +} diff --git a/core/strings/stringUnit.h b/core/strings/stringUnit.h new file mode 100644 index 0000000..91138d8 --- /dev/null +++ b/core/strings/stringUnit.h @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _STRINGUNIT_H_ +#define _STRINGUNIT_H_ + +#include "platform/types.h" + +/// These functions are used for chunking up strings by delimiter. +/// Especially useful for handling TorqueScript space-delimited fields +namespace StringUnit +{ + const char *getUnit(const char *string, U32 index, const char *set); + const char *getUnits(const char *string, S32 startIndex, S32 endIndex, const char *set); + U32 getUnitCount(const char *string, const char *set); + const char* setUnit(const char *string, U32 index, const char *replace, const char *set); + const char* removeUnit(const char *string, U32 index, const char *set); +}; + +#endif \ No newline at end of file diff --git a/core/strings/unicode.cpp b/core/strings/unicode.cpp new file mode 100644 index 0000000..861a5ff --- /dev/null +++ b/core/strings/unicode.cpp @@ -0,0 +1,645 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include + +#include "core/frameAllocator.h" +#include "core/strings/unicode.h" +#include "core/strings/stringFunctions.h" + +#include "platform/profiler.h" +#include "console/console.h" + +#define TORQUE_ENABLE_UTF16_CACHE + +#ifdef TORQUE_ENABLE_UTF16_CACHE +#include "core/util/tDictionary.h" +#include "core/util/hashFunction.h" +#endif + +//----------------------------------------------------------------------------- +/// replacement character. Standard correct value is 0xFFFD. +#define kReplacementChar 0xFFFD + +/// Look up table. Shift a byte >> 1, then look up how many bytes to expect after it. +/// Contains -1's for illegal values. +static const U8 sgFirstByteLUT[128] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x0F // single byte ascii + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x1F // single byte ascii + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x2F // single byte ascii + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x3F // single byte ascii + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x4F // trailing utf8 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x5F // trailing utf8 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x6F // first of 2 + 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 0, // 0x7F // first of 3,4,5,illegal in utf-8 +}; + +/// Look up table. Shift a 16-bit word >> 10, then look up whether it is a surrogate, +/// and which part. 0 means non-surrogate, 1 means 1st in pair, 2 means 2nd in pair. +static const U8 sgSurrogateLUT[64] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x0F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x2F + 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, // 0x3F +}; + +/// Look up table. Feed value from firstByteLUT in, gives you +/// the mask for the data bits of that UTF-8 code unit. +static const U8 sgByteMask8LUT[] = { 0x3f, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 }; // last 0=6, 1=7, 2=5, 4, 3, 2, 1 bits + +/// Mask for the data bits of a UTF-16 surrogate. +static const U16 sgByteMaskLow10 = 0x03ff; + +//----------------------------------------------------------------------------- + +#ifdef TORQUE_ENABLE_UTF16_CACHE + +/// Cache data for UTF16 strings. This is wrapped in a class so that data is +/// automatically freed when the hash table is deleted. +struct UTF16Cache +{ + UTF16 *mString; + U32 mLength; + + UTF16Cache() + { + mString = NULL; + mLength = 0; + } + + UTF16Cache(UTF16 *str, U32 len) + { + mLength = len; + mString = new UTF16[mLength]; + dMemcpy(mString, str, mLength * sizeof(UTF16)); + } + + UTF16Cache(const UTF16Cache &other) + { + mLength = other.mLength; + mString = new UTF16[mLength]; + dMemcpy(mString, other.mString, mLength * sizeof(UTF16)); + } + + void operator =(const UTF16Cache &other) + { + delete [] mString; + + mLength = other.mLength; + mString = new UTF16[mLength]; + dMemcpy(mString, other.mString, mLength * sizeof(UTF16)); + } + + ~UTF16Cache() + { + delete [] mString; + } + + void copyToBuffer(UTF16 *outBuffer, U32 lenToCopy, bool nullTerminate = true) const + { + U32 copy = getMin(mLength, lenToCopy); + if(mString && copy > 0) + dMemcpy(outBuffer, mString, copy * sizeof(UTF16)); + + if(nullTerminate) + outBuffer[copy] = 0; + } +}; + +/// Cache for UTF16 strings +typedef HashTable UTF16CacheTable; +static UTF16CacheTable sgUTF16Cache; + +#endif // TORQUE_ENABLE_UTF16_CACHE + +//----------------------------------------------------------------------------- +inline bool isSurrogateRange(U32 codepoint) +{ + return ( 0xd800 < codepoint && codepoint < 0xdfff ); +} + +inline bool isAboveBMP(U32 codepoint) +{ + return ( codepoint > 0xFFFF ); +} + +//----------------------------------------------------------------------------- +U32 convertUTF8toUTF16(const UTF8 *unistring, UTF16 *outbuffer, U32 len) +{ + AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator."); + PROFILE_SCOPE(convertUTF8toUTF16); + +#ifdef TORQUE_ENABLE_UTF16_CACHE + // If we have cached this conversion already, don't do it again + U32 hashKey = Torque::hash((const U8 *)unistring, dStrlen(unistring), 0); + UTF16CacheTable::Iterator cacheItr = sgUTF16Cache.find(hashKey); + if(cacheItr != sgUTF16Cache.end()) + { + const UTF16Cache &cache = (*cacheItr).value; + cache.copyToBuffer(outbuffer, len); + return cache.mLength; + } +#endif + + U32 walked, nCodepoints; + UTF32 middleman; + + nCodepoints=0; + while(*unistring != '\0' && nCodepoints < len) + { + walked = 1; + middleman = oneUTF8toUTF32(unistring,&walked); + outbuffer[nCodepoints] = oneUTF32toUTF16(middleman); + unistring+=walked; + nCodepoints++; + } + + nCodepoints = getMin(nCodepoints,len - 1); + outbuffer[nCodepoints] = '\0'; + +#ifdef TORQUE_ENABLE_UTF16_CACHE + // Cache the results. + // FIXME As written, this will result in some unnecessary memory copying due to copy constructor calls. + UTF16Cache cache(outbuffer, nCodepoints); + sgUTF16Cache.insertUnique(hashKey, cache); +#endif + + return nCodepoints; +} + +//----------------------------------------------------------------------------- +U32 convertUTF16toUTF8( const UTF16 *unistring, UTF8 *outbuffer, U32 len) +{ + AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator."); + PROFILE_START(convertUTF16toUTF8); + U32 walked, nCodeunits, codeunitLen; + UTF32 middleman; + + nCodeunits=0; + while( *unistring != '\0' && nCodeunits + 3 < len ) + { + walked = 1; + middleman = oneUTF16toUTF32(unistring,&walked); + codeunitLen = oneUTF32toUTF8(middleman, &outbuffer[nCodeunits]); + unistring += walked; + nCodeunits += codeunitLen; + } + + nCodeunits = getMin(nCodeunits,len - 1); + outbuffer[nCodeunits] = '\0'; + + PROFILE_END(); + return nCodeunits; +} + +U32 convertUTF16toUTF8DoubleNULL( const UTF16 *unistring, UTF8 *outbuffer, U32 len) +{ + AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator."); + PROFILE_START(convertUTF16toUTF8DoubleNULL); + U32 walked, nCodeunits, codeunitLen; + UTF32 middleman; + + nCodeunits=0; + while( ! (*unistring == '\0' && *(unistring + 1) == '\0') && nCodeunits + 3 < len ) + { + walked = 1; + middleman = oneUTF16toUTF32(unistring,&walked); + codeunitLen = oneUTF32toUTF8(middleman, &outbuffer[nCodeunits]); + unistring += walked; + nCodeunits += codeunitLen; + } + + nCodeunits = getMin(nCodeunits,len - 1); + outbuffer[nCodeunits] = NULL; + outbuffer[nCodeunits+1] = NULL; + + PROFILE_END(); + return nCodeunits; +} + +//----------------------------------------------------------------------------- +// Functions that convert buffers of unicode code points +//----------------------------------------------------------------------------- +UTF16* convertUTF8toUTF16( const UTF8* unistring) +{ + PROFILE_SCOPE(convertUTF8toUTF16_create); + + // allocate plenty of memory. + U32 nCodepoints, len = dStrlen(unistring) + 1; + FrameTemp buf(len); + + // perform conversion + nCodepoints = convertUTF8toUTF16( unistring, buf, len); + + // add 1 for the NULL terminator the converter promises it included. + nCodepoints++; + + // allocate the return buffer, copy over, and return it. + UTF16 *ret = new UTF16[nCodepoints]; + dMemcpy(ret, buf, nCodepoints * sizeof(UTF16)); + + return ret; +} + +//----------------------------------------------------------------------------- +UTF8* convertUTF16toUTF8( const UTF16* unistring) +{ + PROFILE_SCOPE(convertUTF16toUTF8_create); + + // allocate plenty of memory. + U32 nCodeunits, len = dStrlen(unistring) * 3 + 1; + FrameTemp buf(len); + + // perform conversion + nCodeunits = convertUTF16toUTF8( unistring, buf, len); + + // add 1 for the NULL terminator the converter promises it included. + nCodeunits++; + + // allocate the return buffer, copy over, and return it. + UTF8 *ret = new UTF8[nCodeunits]; + dMemcpy(ret, buf, nCodeunits * sizeof(UTF8)); + + return ret; +} + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Functions that converts one unicode codepoint at a time +//----------------------------------------------------------------------------- +UTF32 oneUTF8toUTF32( const UTF8* codepoint, U32 *unitsWalked) +{ + PROFILE_START(oneUTF8toUTF32); + // codepoints 6 codeunits long are read, but do not convert correctly, + // and are filtered out anyway. + + // early out for ascii + if(!(*codepoint & 0x0080)) + { + *unitsWalked = 1; + PROFILE_END(); + return (UTF32)*codepoint; + } + + U32 expectedByteCount; + UTF32 ret = 0; + U8 codeunit; + + // check the first byte ( a.k.a. codeunit ) . + unsigned char c = codepoint[0]; + c = c >> 1; + expectedByteCount = sgFirstByteLUT[c]; + if(expectedByteCount > 0) // 0 or negative is illegal to start with + { + // process 1st codeunit + ret |= sgByteMask8LUT[expectedByteCount] & codepoint[0]; // bug? + + // process trailing codeunits + for(U32 i=1;i>1] == 0 ) + { + ret <<= 6; // shift up 6 + ret |= (codeunit & 0x3f); // mask in the low 6 bits of this codeunit byte. + } + else + { + // found a bad codepoint - did not get a medial where we wanted one. + // Dump the replacement, and claim to have parsed only 1 char, + // so that we'll dump a slew of replacements, instead of eating the next char. + ret = kReplacementChar; + expectedByteCount = 1; + break; + } + } + } + else + { + // found a bad codepoint - got a medial or an illegal codeunit. + // Dump the replacement, and claim to have parsed only 1 char, + // so that we'll dump a slew of replacements, instead of eating the next char. + ret = kReplacementChar; + expectedByteCount = 1; + } + + if(unitsWalked != NULL) + *unitsWalked = expectedByteCount; + + // codepoints in the surrogate range are illegal, and should be replaced. + if(isSurrogateRange(ret)) + ret = kReplacementChar; + + // codepoints outside the Basic Multilingual Plane add complexity to our UTF16 string classes, + // we've read them correctly so they won't foul the byte stream, + // but we kill them here to make sure they wont foul anything else + if(isAboveBMP(ret)) + ret = kReplacementChar; + + PROFILE_END(); + return ret; +} + +//----------------------------------------------------------------------------- +UTF32 oneUTF16toUTF32(const UTF16* codepoint, U32 *unitsWalked) +{ + PROFILE_START(oneUTF16toUTF32); + U8 expectedType; + U32 unitCount; + UTF32 ret = 0; + UTF16 codeunit1,codeunit2; + + codeunit1 = codepoint[0]; + expectedType = sgSurrogateLUT[codeunit1 >> 10]; + switch(expectedType) + { + case 0: // simple + ret = codeunit1; + unitCount = 1; + break; + case 1: // 2 surrogates + codeunit2 = codepoint[1]; + if( sgSurrogateLUT[codeunit2 >> 10] == 2) + { + ret = ((codeunit1 & sgByteMaskLow10 ) << 10) | (codeunit2 & sgByteMaskLow10); + unitCount = 2; + break; + } + // else, did not find a trailing surrogate where we expected one, + // so fall through to the error + case 2: // error + // found a trailing surrogate where we expected a codepoint or leading surrogate. + // Dump the replacement. + ret = kReplacementChar; + unitCount = 1; + break; + default: + // unexpected return + AssertFatal(false, "oneUTF16toUTF323: unexpected type"); + ret = kReplacementChar; + unitCount = 1; + break; + } + + if(unitsWalked != NULL) + *unitsWalked = unitCount; + + // codepoints in the surrogate range are illegal, and should be replaced. + if(isSurrogateRange(ret)) + ret = kReplacementChar; + + // codepoints outside the Basic Multilingual Plane add complexity to our UTF16 string classes, + // we've read them correctly so they wont foul the byte stream, + // but we kill them here to make sure they wont foul anything else + // NOTE: these are perfectly legal codepoints, we just dont want to deal with them. + if(isAboveBMP(ret)) + ret = kReplacementChar; + + PROFILE_END(); + return ret; +} + +//----------------------------------------------------------------------------- +UTF16 oneUTF32toUTF16(const UTF32 codepoint) +{ + // found a codepoint outside the encodable UTF-16 range! + // or, found an illegal codepoint! + if(codepoint >= 0x10FFFF || isSurrogateRange(codepoint)) + return kReplacementChar; + + // these are legal, we just don't want to deal with them. + if(isAboveBMP(codepoint)) + return kReplacementChar; + + return (UTF16)codepoint; +} + +//----------------------------------------------------------------------------- +U32 oneUTF32toUTF8(const UTF32 codepoint, UTF8 *threeByteCodeunitBuf) +{ + PROFILE_START(oneUTF32toUTF8); + U32 bytecount = 0; + UTF8 *buf; + U32 working = codepoint; + buf = threeByteCodeunitBuf; + + //----------------- + if(isSurrogateRange(working)) // found an illegal codepoint! + working = kReplacementChar; + + if(isAboveBMP(working)) // these are legal, we just dont want to deal with them. + working = kReplacementChar; + + //----------------- + if( working < (1 << 7)) // codeable in 7 bits + bytecount = 1; + else if( working < (1 << 11)) // codeable in 11 bits + bytecount = 2; + else if( working < (1 << 16)) // codeable in 16 bits + bytecount = 3; + + AssertISV( bytecount > 0, "Error converting to UTF-8 in oneUTF32toUTF8(). isAboveBMP() should have caught this!"); + + //----------------- + U8 mask = sgByteMask8LUT[0]; // 0011 1111 + U8 marker = ( ~mask << 1); // 1000 0000 + + // Process the low order bytes, shifting the codepoint down 6 each pass. + for( int i = bytecount-1; i > 0; i--) + { + threeByteCodeunitBuf[i] = marker | (working & mask); + working >>= 6; + } + + // Process the 1st byte. filter based on the # of expected bytes. + mask = sgByteMask8LUT[bytecount]; + marker = ( ~mask << 1 ); + threeByteCodeunitBuf[0] = marker | working & mask; + + PROFILE_END(); + return bytecount; +} + +//----------------------------------------------------------------------------- +U32 dStrlen(const UTF16 *unistring) +{ + if(!unistring) + return 0; + + U32 i = 0; + while(unistring[i] != '\0') + i++; + +// AssertFatal( wcslen(unistring) == i, "Incorrect length" ); + + return i; +} + +//----------------------------------------------------------------------------- +U32 dStrlen(const UTF32 *unistring) +{ + U32 i = 0; + while(unistring[i] != '\0') + i++; + + return i; +} + +//----------------------------------------------------------------------------- +U32 dStrncmp(const UTF16* unistring1, const UTF16* unistring2, U32 len) +{ + UTF16 c1, c2; + for(U32 i = 0; i c2) return 1; + if(!c1) return 0; + } + return 0; +} + +//----------------------------------------------------------------------------- + +const UTF16* dStrrchr(const UTF16* unistring, U32 c) +{ + if(!unistring) return NULL; + + const UTF16* tmp = unistring + dStrlen(unistring); + while( tmp >= unistring) + { + if(*tmp == c) + return tmp; + tmp--; + } + return NULL; +} + +UTF16* dStrrchr(UTF16* unistring, U32 c) +{ + const UTF16* str = unistring; + return const_cast(dStrrchr(str, c)); +} + +const UTF16* dStrchr(const UTF16* unistring, U32 c) +{ + if(!unistring) return NULL; + const UTF16* tmp = unistring; + + while ( *tmp && *tmp != c) + tmp++; + + return (*tmp == c) ? tmp : NULL; +} + +UTF16* dStrchr(UTF16* unistring, U32 c) +{ + const UTF16* str = unistring; + return const_cast(dStrchr(str, c)); +} + +//----------------------------------------------------------------------------- +const UTF8* getNthCodepoint(const UTF8 *unistring, const U32 n) +{ + const UTF8* ret = unistring; + U32 charsseen = 0; + while( *ret && charsseen < n) + { + ret++; + if((*ret & 0xC0) != 0x80) + charsseen++; + } + + return ret; +} + +/* alternate utf-8 decode impl for speed, no error checking, + left here for your amusement: + + U32 codeunit = codepoint + expectedByteCount - 1; + U32 i = 0; + switch(expectedByteCount) + { + case 6: ret |= ( *(codeunit--) & 0x3f ); i++; + case 5: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++); + case 4: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++); + case 3: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++); + case 2: ret |= ( *(codeunit--) & 0x3f ) << (6 * i++); + case 1: ret |= *(codeunit) & byteMask8LUT[expectedByteCount] << (6 * i); + } +*/ + +//------------------------------------------------------------------------------ +// Byte Order Mark functions + +bool chompUTF8BOM( const char *inString, char **outStringPtr ) +{ + *outStringPtr = const_cast( inString ); + + U8 bom[4]; + dMemcpy( bom, inString, 4 ); + + bool valid = isValidUTF8BOM( bom ); + + // This is hackey, but I am not sure the best way to do it at the present. + // The only valid BOM is a UTF8 BOM, which is 3 bytes, even though we read + // 4 bytes because it could possibly be a UTF32 BOM, and we want to provide + // an accurate error message. Perhaps this could be re-worked when more UTF + // formats are supported to have isValidBOM return the size of the BOM, in + // bytes. + if( valid ) + (*outStringPtr) += 3; // SEE ABOVE!! -pw + + return valid; +} + +bool isValidUTF8BOM( U8 bom[4] ) +{ + // Is it a BOM? + if( bom[0] == 0 ) + { + // Could be UTF32BE + if( bom[1] == 0 && bom[2] == 0xFE && bom[3] == 0xFF ) + { + Con::warnf( "Encountered a UTF32 BE BOM in this file; Torque does NOT support this file encoding. Use UTF8!" ); + return false; + } + + return false; + } + else if( bom[0] == 0xFF ) + { + // It's little endian, either UTF16 or UTF32 + if( bom[1] == 0xFE ) + { + if( bom[2] == 0 && bom[3] == 0 ) + Con::warnf( "Encountered a UTF32 LE BOM in this file; Torque does NOT support this file encoding. Use UTF8!" ); + else + Con::warnf( "Encountered a UTF16 LE BOM in this file; Torque does NOT support this file encoding. Use UTF8!" ); + } + + return false; + } + else if( bom[0] == 0xFE && bom[1] == 0xFF ) + { + Con::warnf( "Encountered a UTF16 BE BOM in this file; Torque does NOT support this file encoding. Use UTF8!" ); + return false; + } + else if( bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF ) + { + // Can enable this if you want -pw + //Con::printf("Encountered a UTF8 BOM. Torque supports this."); + return true; + } + + // Don't print out an error message here, because it will try this with + // every script. -pw + return false; +} diff --git a/core/strings/unicode.h b/core/strings/unicode.h new file mode 100644 index 0000000..ddcd061 --- /dev/null +++ b/core/strings/unicode.h @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _UNICODE_H_ +#define _UNICODE_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + + +/// Unicode conversion utility functions +/// +/// Some definitions first: +/// - Code Point: a single character of Unicode text. Used to disabmiguate from C char type. +/// - UTF-32: a Unicode encoding format where one code point is always 32 bits wide. +/// This format can in theory contain any Unicode code point that will ever be needed, now or in the future. 4billion+ code points should be enough, right? +/// - UTF-16: a variable length Unicode encoding format where one code point can be +/// either one or two 16-bit code units long. +/// - UTF-8: a variable length Unicode endocing format where one code point can be +/// up to four 8-bit code units long. The first bit of a single byte UTF-8 code point is 0. +/// The first few bits of a multi-byte code point determine the length of the code point. +/// @see http://en.wikipedia.org/wiki/UTF-8 +/// - Surrogate Pair: a pair of special UTF-16 code units, that encode a code point +/// that is too large to fit into 16 bits. The surrogate values sit in a special reserved range of Unicode. +/// - Code Unit: a single unit of a variable length Unicode encoded code point. +/// UTF-8 has 8 bit wide code units. UTF-16 has 16 bit wide code units. +/// - BMP: "Basic Multilingual Plane". Unicode values U+0000 - U+FFFF. This range +/// of Unicode contains all the characters for all the languages of the world, that one would +/// usually be interested in. All code points in the BMP are 16 bits wide or less. + +/// The current implementation of these conversion functions deals only with the BMP. +/// Any code points above 0xFFFF, the top of the BMP, are replaced with the +/// standard unicode replacement character: 0xFFFD. +/// Any UTF16 surrogates are read correctly, but replaced. +/// UTF-8 code points up to 6 code units wide will be read, but 5+ is illegal, +/// and 4+ is above the BMP, and will be replaced. +/// This means that UTF-8 output is clamped to 3 code units ( bytes ) per code point. + +//----------------------------------------------------------------------------- +/// Functions that convert buffers of unicode code points, allocating a buffer. +/// - These functions allocate their own return buffers. You are responsible for +/// calling delete[] on these buffers. +/// - Because they allocate memory, do not use these functions in a tight loop. +/// - These are useful when you need a new long term copy of a string. +UTF16* convertUTF8toUTF16( const UTF8 *unistring); + +UTF8* convertUTF16toUTF8( const UTF16 *unistring); + +//----------------------------------------------------------------------------- +/// Functions that convert buffers of unicode code points, into a provided buffer. +/// - These functions are useful for working on existing buffers. +/// - These cannot convert a buffer in place. If unistring is the same memory as +/// outbuffer, the behavior is undefined. +/// - The converter clamps output to the BMP (Basic Multilingual Plane) . +/// - Conversion to UTF-8 requires a buffer of 3 bytes (U8's) per character, + 1. +/// - Conversion to UTF-16 requires a buffer of 1 U16 (2 bytes) per character, + 1. +/// - Conversion to UTF-32 requires a buffer of 1 U32 (4 bytes) per character, + 1. +/// - UTF-8 only requires 3 bytes per character in the worst case. +/// - Output is null terminated. Be sure to provide 1 extra byte, U16 or U32 for +/// the null terminator, or you will see truncated output. +/// - If the provided buffer is too small, the output will be truncated. +U32 convertUTF8toUTF16(const UTF8 *unistring, UTF16 *outbuffer, U32 len); + +U32 convertUTF16toUTF8( const UTF16 *unistring, UTF8 *outbuffer, U32 len); + +//----------------------------------------------------------------------------- +/// Functions that converts one unicode codepoint at a time +/// - Since these functions are designed to be used in tight loops, they do not +/// allocate buffers. +/// - oneUTF8toUTF32() and oneUTF16toUTF32() return the converted Unicode code point +/// in *codepoint, and set *unitsWalked to the \# of code units *codepoint took up. +/// The next Unicode code point should start at *(codepoint + *unitsWalked). +/// - oneUTF32toUTF8() requires a 3 byte buffer, and returns the \# of bytes used. +UTF32 oneUTF8toUTF32( const UTF8 *codepoint, U32 *unitsWalked = NULL); +UTF32 oneUTF16toUTF32(const UTF16 *codepoint, U32 *unitsWalked = NULL); +UTF16 oneUTF32toUTF16(const UTF32 codepoint); +U32 oneUTF32toUTF8( const UTF32 codepoint, UTF8 *threeByteCodeunitBuf); + +//----------------------------------------------------------------------------- +/// Functions that calculate the length of unicode strings. +/// - Since calculating the length of a UTF8 string is nearly as expensive as +/// converting it to another format, a dStrlen for UTF8 is not provided here. +/// - If *unistring does not point to a null terminated string of the correct type, +/// the behavior is undefined. +U32 dStrlen(const UTF16 *unistring); +U32 dStrlen(const UTF32 *unistring); + +//----------------------------------------------------------------------------- +/// Comparing unicode strings +U32 dStrncmp(const UTF16* unistring1, const UTF16* unistring2, U32 len); + +//----------------------------------------------------------------------------- +/// Scanning for characters in unicode strings +UTF16* dStrrchr(UTF16* unistring, U32 c); +const UTF16* dStrrchr(const UTF16* unistring, U32 c); + +UTF16* dStrchr(UTF16* unistring, U32 c); +const UTF16* dStrchr(const UTF16* unistring, U32 c); +//----------------------------------------------------------------------------- +/// Functions that scan for characters in a utf8 string. +/// - this is useful for getting a character-wise offset into a UTF8 string, +/// as opposed to a byte-wise offset into a UTF8 string: foo[i] +const UTF8* getNthCodepoint(const UTF8 *unistring, const U32 n); + +//------------------------------------------------------------------------------ +/// Functions to read and validate UTF BOMs (Byte Order Marker) +/// For reference: http://en.wikipedia.org/wiki/Byte_Order_Mark +bool chompUTF8BOM( const char *inString, char **outStringPtr ); +bool isValidUTF8BOM( U8 bom[4] ); + +#endif // _UNICODE_H_ \ No newline at end of file diff --git a/core/tAlgorithm.h b/core/tAlgorithm.h new file mode 100644 index 0000000..9b50ddd --- /dev/null +++ b/core/tAlgorithm.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TALGORITHM_H_ +#define _TALGORITHM_H_ + + +/// Finds the first matching value within the container +/// returning the the element or last if its not found. +template +Iterator find(Iterator first, Iterator last, Value value) +{ + while (first != last && *first != value) + ++first; + return first; +} + +/// Exchanges the values of the two elements. +template +inline void swap( T &left, T &right ) +{ + T temp = right; + right = left; + left = temp; +} + +/// Steps thru the elements of an array calling detete for each. +template +void for_each( Iterator first, Iterator last, Functor func ) +{ + for ( ; first != last; first++ ) + func( *first ); +} + +/// Functor for deleting a pointer. +/// @see for_each +struct delete_pointer +{ + template + void operator()(T *ptr){ delete ptr;} +}; + +#endif //_TALGORITHM_H_ diff --git a/core/tSimpleHashTable.h b/core/tSimpleHashTable.h new file mode 100644 index 0000000..805f283 --- /dev/null +++ b/core/tSimpleHashTable.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// [tom, 9/19/2006] Simple hash table. Not intended to replace map<>, but it is +// generally good enough for simple things that you don't need to iterate. +// +// Note: If you move this to another project, you need the updated tSparseArray.h +// as well as hashFunction.cc/h + +#include "platform/platform.h" + +#include "core/tSparseArray.h" +#include "core/util/hashFunction.h" +#include "core/strings/stringFunctions.h" + +#ifndef _TSIMPLEHASHTABLE_H +#define _TSIMPLEHASHTABLE_H + +template class SimpleHashTable : public SparseArray +{ + typedef SparseArray Parent; + + bool mCaseSensitive; + + char mCaseConvBuf[1024]; + + // [tom, 9/21/2006] This is incredibly lame and adds a pretty big speed penalty + inline const char *caseConv(const char *str) + { + if(mCaseSensitive) return str; + + S32 len = dStrlen(str); + if(len >= sizeof(mCaseConvBuf)) len = sizeof(mCaseConvBuf) - 1; + + char *dptr = mCaseConvBuf; + const char *sptr = str; + while(*sptr) + { + *dptr = dTolower(*sptr); + ++sptr; + ++dptr; + } + *dptr = 0; + + return mCaseConvBuf; + } + +public: + SimpleHashTable(const U32 modulusSize = 64, bool caseSensitive = true) : Parent(modulusSize), mCaseSensitive(caseSensitive) + { + } + + void insert(T* pObject, U8 *key, U32 keyLen); + T* remove(U8 *key, U32 keyLen); + T* retreive(U8 *key, U32 keyLen); + + void insert(T* pObject, const char *key); + T* remove(const char *key); + T* retreive(const char *key); +}; + +template inline void SimpleHashTable::insert(T* pObject, U8 *key, U32 keyLen) +{ + Parent::insert(pObject, Torque::hash(key, keyLen, 0)); +} + +template inline T* SimpleHashTable::remove(U8 *key, U32 keyLen) +{ + return Parent::remove(Torque::hash(key, keyLen, 0)); +} + +template inline T* SimpleHashTable::retreive(U8 *key, U32 keyLen) +{ + return Parent::retreive(Torque::hash(key, keyLen, 0)); +} + +template inline void SimpleHashTable::insert(T* pObject, const char *key) +{ + key = caseConv(key); + insert(pObject, (U8 *)key, dStrlen(key)); +} + +template T* SimpleHashTable::remove(const char *key) +{ + key = caseConv(key); + return remove((U8 *)key, dStrlen(key)); +} + +template T* SimpleHashTable::retreive(const char *key) +{ + key = caseConv(key); + return retreive((U8 *)key, dStrlen(key)); +} + + +#endif // _TSIMPLEHASHTABLE_H diff --git a/core/tSparseArray.h b/core/tSparseArray.h new file mode 100644 index 0000000..b8b262d --- /dev/null +++ b/core/tSparseArray.h @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSPARSEARRAY_H_ +#define _TSPARSEARRAY_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _PLATFORMASSERT_H_ +#include "platform/platformAssert.h" +#endif + +template +class SparseArray +{ + protected: + struct Node { + T* pObject; + U32 key; + + Node* next; + }; + + protected: + U32 mModulus; + Node* mSentryTables; + +public: + SparseArray(const U32 modulusSize = 64); + ~SparseArray(); + + void insert(T* pObject, U32 key); + T* remove(U32 key); + T* retreive(U32 key); + + void clearTables(); // Note: _deletes_ the objects! +}; + +template +inline SparseArray::SparseArray(const U32 modulusSize) +{ + AssertFatal(modulusSize > 0, "Error, modulus must be > 0"); + + mModulus = modulusSize; + mSentryTables = new Node[mModulus]; + for (U32 i = 0; i < mModulus; i++) + mSentryTables[i].next = NULL; +} + +template +inline SparseArray::~SparseArray() +{ + clearTables(); +} + +template +inline void SparseArray::clearTables() +{ + for (U32 i = 0; i < mModulus; i++) { + Node* pProbe = mSentryTables[i].next; + while (pProbe != NULL) { + Node* pNext = pProbe->next; + delete pProbe->pObject; + delete pProbe; + pProbe = pNext; + } + } + delete [] mSentryTables; + mSentryTables = NULL; + mModulus = 0; +} + +template +inline void SparseArray::insert(T* pObject, U32 key) +{ + U32 insert = key % mModulus; + Node* pNew = new Node; + pNew->pObject = pObject; + pNew->key = key; + pNew->next = mSentryTables[insert].next; + mSentryTables[insert].next = pNew; + +#ifdef TORQUE_DEBUG + Node* probe = pNew->next; + while (probe != NULL) { + AssertFatal(probe->key != key, "error, duplicate keys in sparse array!"); + probe = probe->next; + } +#endif +} + +template +inline T* SparseArray::remove(U32 key) +{ + U32 remove = key % mModulus; + Node* probe = &mSentryTables[remove]; + while (probe->next != NULL) { + if (probe->next->key == key) { + Node* remove = probe->next; + T* pReturn = remove->pObject; + probe->next = remove->next; + delete remove; + return pReturn; + } + probe = probe->next; + } + + // [tom, 8/19/2006] This assert is also utterly, utterly useless + // AssertFatal(false, "Key didn't exist in the array!"); + return NULL; +} + +template +inline T* SparseArray::retreive(U32 key) +{ + U32 retrieve = key % mModulus; + Node* probe = &mSentryTables[retrieve]; + while (probe->next != NULL) { + if (probe->next->key == key) { + return probe->next->pObject; + } + probe = probe->next; + } + + // [tom, 11/16/2005] This assert is utterly, utterly useless + // AssertFatal(false, "Key didn't exist in the array!"); + return NULL; +} + +#endif //_TSPARSEARRAY_H_ + diff --git a/core/tagDictionary.cpp b/core/tagDictionary.cpp new file mode 100644 index 0000000..df1e7d6 --- /dev/null +++ b/core/tagDictionary.cpp @@ -0,0 +1,303 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "core/tagDictionary.h" +#include "core/stream/stream.h" + +namespace { + +const char TAG_ASCII_ID[] = "[TAG]"; +const char TAG_ASCII_END[] = "[END]"; +const char TAG_ASCII_HEADER[] = "// Auto-Generated by TagDictionary class"; + +const S32 sg_tagDictAsciiUser = 1; + +} // namespace + +TagDictionary tagDictionary; + +TagDictionary::TagDictionary() +{ + numBuckets = 29; + defineHashBuckets = (TagEntry **) dMalloc(numBuckets * sizeof(TagEntry *)); + idHashBuckets = (TagEntry **) dMalloc(numBuckets * sizeof(TagEntry *)); + + S32 i; + for(i = 0; i < numBuckets; i++) + { + defineHashBuckets[i] = NULL; + idHashBuckets[i] = NULL; + } + numEntries = 0; + entryChain = NULL; +} + +TagDictionary::~TagDictionary() +{ + dFree(defineHashBuckets); + dFree(idHashBuckets); +} + +//------------------------------------------------------------------------------ + +static inline S32 hashId(S32 id, S32 tsize) +{ + return id % tsize; +} + +static inline S32 hashDefine(StringTableEntry define, S32 tsize) +{ + return ((S32)((dsize_t)define) >> 2) % tsize; +} + +//------------------------------------------------------------------------------ + +bool TagDictionary::addEntry(S32 value, StringTableEntry define, StringTableEntry string) +{ + if(!value) + return false; +//#pragma message "put console prints back" + if(idToDefine(value)) + { + AssertWarn(false, avar("Error: id %d already defined to a tag.", value)); + //Con::printf("Error: id %d already defined to a tag.", value); + return false; + } + S32 tempTag; + if((tempTag = defineToId(define)) != 0) + { + AssertWarn(false, avar("Error: define %s already defined to tag %d.", define, tempTag)); + //Con::printf("Error: define %s already defined to tag %d.", define, tempTag); + return false; + } + TagEntry *newEntry = (TagEntry *) mempool.alloc(sizeof(TagEntry)); + + newEntry->id = value; + newEntry->define = define; + newEntry->string = string; + + numEntries++; + if(numEntries > numBuckets) + { + numBuckets = numBuckets * 2 + 1; + defineHashBuckets = (TagEntry **) dRealloc(defineHashBuckets, numBuckets * sizeof(TagEntry *)); + idHashBuckets = (TagEntry **) dRealloc(idHashBuckets, numBuckets * sizeof(TagEntry *)); + S32 i; + for(i = 0; i < numBuckets; i++) + { + defineHashBuckets[i] = NULL; + idHashBuckets[i] = NULL; + } + TagEntry *walk = entryChain; + + while(walk) + { + S32 index = hashId(walk->id, numBuckets); + walk->idHashLink = idHashBuckets[index]; + idHashBuckets[index] = walk; + + index = hashDefine(walk->define, numBuckets); + walk->defineHashLink = defineHashBuckets[index]; + defineHashBuckets[index] = walk; + + walk = walk->chain; + } + } + newEntry->chain = entryChain; + entryChain = newEntry; + + S32 index = hashId(newEntry->id, numBuckets); + newEntry->idHashLink = idHashBuckets[index]; + idHashBuckets[index] = newEntry; + + index = hashDefine(newEntry->define, numBuckets); + newEntry->defineHashLink = defineHashBuckets[index]; + defineHashBuckets[index] = newEntry; + return true; +} + +//------------------------------------------------------------------------------ + +bool TagDictionary::writeHeader(Stream& io_sio) +{ + char buff[15000]; + Vector v; + + TagEntry *walk = entryChain; + while(walk) + { + v.push_back(walk->id); + walk = walk->chain; + } + + sortIdVector(v); + + io_sio.write( sizeof(TAG_ASCII_HEADER)-1, TAG_ASCII_HEADER); + io_sio.write( 4, "\r\n\r\n"); + + char exclude[256]; + char tempBuf[256]; + dSprintf(exclude, sizeof(exclude), "_TD%10.10u_H_", Platform::getVirtualMilliseconds() / 4); + + dSprintf(tempBuf, sizeof(tempBuf), "#ifndef %s\r\n", exclude); + io_sio.write(dStrlen(tempBuf), tempBuf); + dSprintf(tempBuf, sizeof(tempBuf), "#define %s\r\n\r\n", exclude); + io_sio.write(dStrlen(tempBuf), tempBuf); + + for (U32 i = 0; i < v.size(); i++) + { + dSprintf(buff, sizeof(buff), "#define %s (%d)\r\n", idToDefine(v[i]), v[i]); + io_sio.write(dStrlen(buff), buff); + } + + dSprintf(tempBuf, sizeof(tempBuf), "\r\n#endif // %s\r\n", exclude); + io_sio.write(dStrlen(tempBuf), tempBuf); + + return (io_sio.getStatus() == Stream::Ok); +} + +//------------------------------------------------------------------------------ + +StringTableEntry TagDictionary::defineToString(StringTableEntry tag) +{ + S32 index = hashDefine(tag, numBuckets); + if (index < 0) return NULL; + TagEntry *walk = defineHashBuckets[index]; + while(walk) + { + if(walk->define == tag) + return walk->string; + walk = walk->defineHashLink; + } + return NULL; +} + +S32 TagDictionary::defineToId(StringTableEntry tag) +{ + S32 index = hashDefine(tag, numBuckets); + if (index < 0) return 0; + TagEntry *walk = defineHashBuckets[index]; + while(walk) + { + if(walk->define == tag) + return walk->id; + walk = walk->defineHashLink; + } + return 0; +} + +StringTableEntry TagDictionary::idToString(S32 id) +{ + S32 index = hashId(id, numBuckets); + if (index < 0) return NULL; + TagEntry *walk = idHashBuckets[index]; + while(walk) + { + if(walk->id == id) + return walk->string; + walk = walk->idHashLink; + } + return NULL; +} + +StringTableEntry TagDictionary::idToDefine(S32 id) +{ + S32 index = hashId(id, numBuckets); + if (index < 0) return NULL; + TagEntry *walk = idHashBuckets[index]; + while(walk) + { + if(walk->id == id) + return walk->define; + walk = walk->idHashLink; + } + return NULL; +} + +//------------------------------------------------------------------------------ + +void TagDictionary::findIDs(Vector& out_v, + const S32 in_minID, + const S32 in_maxID ) +{ + //locate all IDs that lie in between minID and maxID + + TagEntry *walk = entryChain; + while(walk) + { + if(walk->id > in_minID && walk->id < in_maxID) + out_v.push_back(walk->id); + walk = walk->chain; + } + sortIdVector(out_v); +} + + +//------------------------------------------------------------------------------ +void TagDictionary::findStrings(Vector& out_v, const char* in_pPattern) +{ + //locate all strings that match the pattern + // + TagEntry *walk = entryChain; + while(walk) + { + if (match(in_pPattern, walk->string)) + out_v.push_back(walk->id); + walk = walk->chain; + } + sortIdVector(out_v); +} + + +//------------------------------------------------------------------------------ +void TagDictionary::findDefines(Vector& out_v, const char* in_pPattern) +{ + //locate all define strings that match the pattern and add their ID + //to the given vector + // + TagEntry *walk = entryChain; + while(walk) + { + if (match(in_pPattern, walk->define)) + out_v.push_back(walk->id); + walk = walk->chain; + } + sortIdVector(out_v); +} + +//------------------------------------------------------------------------------ + +bool TagDictionary::match(const char* pattern, const char* str) +{ + //quick and dirty recursive DOS-style wild-card string matcher + // + switch (*pattern) { + case '\0': + return !*str; + + case '*': + return match(pattern+1, str) || *str && match(pattern, str+1); + + case '?': + return *str && match(pattern+1, str+1); + + default: + return (*pattern == *str) && match(pattern+1, str+1); + } +} + +//------------------------------------------------------------------------------ + +static int QSORT_CALLBACK idCompare(const void *in_p1, const void *in_p2) +{ + return *((S32 *) in_p1) - *((S32 *) in_p2); +} + +void TagDictionary::sortIdVector(Vector& out_v) +{ + dQsort(out_v.address(), out_v.size(), sizeof(S32), idCompare); +} + diff --git a/core/tagDictionary.h b/core/tagDictionary.h new file mode 100644 index 0000000..8776775 --- /dev/null +++ b/core/tagDictionary.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TAGDICTIONARY_H_ +#define _TAGDICTIONARY_H_ + +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class Stream; + +class TagDictionary +{ + struct TagEntry + { + S32 id; + StringTableEntry define; + StringTableEntry string; + TagEntry *chain; // for linear traversal + TagEntry *defineHashLink; + TagEntry *idHashLink; + }; + + TagEntry **defineHashBuckets; + TagEntry **idHashBuckets; + + TagEntry *entryChain; + DataChunker mempool; + S32 numBuckets; + S32 numEntries; + + bool match(const char* pattern, const char* str); + void sortIdVector(Vector& out_v); +public: + TagDictionary(); + ~TagDictionary(); + + //IO functions + // + bool writeHeader(Stream &); + + // String/Define retrieval and search functions... + // + + bool addEntry(S32 value, StringTableEntry define, StringTableEntry string); + + StringTableEntry defineToString(StringTableEntry tag); + StringTableEntry idToString(S32 tag); + StringTableEntry idToDefine(S32 tag); + S32 defineToId(StringTableEntry tag); + + // get IDs such that minID < IDs < maxID + void findIDs( Vector &v, const S32 minID, const S32 maxID ); + void findStrings( Vector &v, const char *pattern); + void findDefines( Vector &v, const char *pattern); +}; + +extern TagDictionary tagDictionary; + +#endif //_TAGDICTIONARY_H_ diff --git a/core/threadStatic.cpp b/core/threadStatic.cpp new file mode 100644 index 0000000..2445fba --- /dev/null +++ b/core/threadStatic.cpp @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/threadStatic.h" + +//----------------------------------------------------------------------------- +// Statics +U32 _TorqueThreadStatic::mListIndex = 0; + +_TorqueThreadStaticReg *_TorqueThreadStaticReg::smFirst = NULL; +//----------------------------------------------------------------------------- + +inline Vector &_TorqueThreadStaticReg::getThreadStaticListVector() +{ + // This function assures that the static vector of ThreadStatics will get initialized + // before first use. + static Vector sTorqueThreadStaticVec( __FILE__, __LINE__ ); + + return sTorqueThreadStaticVec; +} + +//----------------------------------------------------------------------------- + +// Destructor, size should == 1 otherwise someone didn't clean up, or someone +// did horrible things to list index 0 +_TorqueThreadStaticReg::~_TorqueThreadStaticReg() +{ + AssertFatal( getThreadStaticListVector().size() == 1, "Destruction of static list was not performed on program exit" ); +} + +//----------------------------------------------------------------------------- + +void _TorqueThreadStaticReg::destroyInstances() +{ + // mThreadStaticInstances[0] does *not* need to be deallocated + // because all members of the list are pointers to static memory + while( getThreadStaticListVector().size() > 1 ) + { + // Delete the members of this list + while( getThreadStaticListVector().last().size() ) + { + _TorqueThreadStatic *biscuit = getThreadStaticListVector().last().first(); + + // Erase the vector entry + getThreadStaticListVector().last().pop_front(); + + // And finally the memory + delete biscuit; + } + + // Remove the entry from the list of lists + getThreadStaticListVector().pop_back(); + } +} + +//----------------------------------------------------------------------------- + +void _TorqueThreadStaticReg::destroyInstance( TorqueThreadStaticList *instanceList ) +{ + AssertFatal( instanceList != &getThreadStaticListVector().first(), "Cannot delete static instance list index 0" ); + + while( instanceList->size() ) + { + _TorqueThreadStatic *biscuit = getThreadStaticListVector().last().first(); + + // Erase the vector entry + getThreadStaticListVector().last().pop_front(); + + // And finally the memory + delete biscuit; + } + + getThreadStaticListVector().erase( instanceList ); +} + +//----------------------------------------------------------------------------- + +TorqueThreadStaticListHandle _TorqueThreadStaticReg::spawnThreadStaticsInstance() +{ + AssertFatal( getThreadStaticListVector().size() > 0, "List is not initialized somehow" ); + + // Add a new list of static instances + getThreadStaticListVector().increment(); + + // Copy mThreadStaticInstances[0] (master copy) into new memory, and + // pass it back. + for( int i = 0; i < getThreadStaticListVector()[0].size(); i++ ) + { + getThreadStaticListVector().last().push_back( getThreadStaticListVector()[0][i]->_createInstance() ); + } + + // Return list index of newly allocated static instance list + return &getThreadStaticListVector().last(); +} \ No newline at end of file diff --git a/core/threadStatic.h b/core/threadStatic.h new file mode 100644 index 0000000..b0f7641 --- /dev/null +++ b/core/threadStatic.h @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUETHREADSTATIC_H_ +#define _TORQUETHREADSTATIC_H_ + +#include "core/util/tVector.h" + +//----------------------------------------------------------------------------- +// TorqueThreadStatic Base Class +class _TorqueThreadStatic +{ + friend class _TorqueThreadStaticReg; + +private: + +#ifdef TORQUE_ENABLE_THREAD_STATIC_METRICS + U32 mHitCount; +#endif + +protected: + static U32 mListIndex; + virtual _TorqueThreadStatic *_createInstance() const = 0; + +public: + _TorqueThreadStatic() +#ifdef TORQUE_ENABLE_THREAD_STATIC_METRICS + : mHitCount( 0 ) +#endif + { }; + + static const U32 getListIndex(){ return mListIndex; } + + virtual void *getMemInstPtr() = 0; + virtual const dsize_t getMemInstSize() const = 0; + +#ifdef TORQUE_ENABLE_THREAD_STATIC_METRICS + _TorqueThreadStatic *_chainHit() { mHitCount++; return this; } + const U32 &trackHit() { return ++mHitCount; } + const U32 &getHitCount() const { return mHitCount; } +#endif +}; +// Typedef +typedef VectorPtr<_TorqueThreadStatic *> TorqueThreadStaticList; +typedef TorqueThreadStaticList * TorqueThreadStaticListHandle; + +//----------------------------------------------------------------------------- +// Auto-registration class and manager of the instances +class _TorqueThreadStaticReg +{ + // This will manage all of the thread static registrations + static _TorqueThreadStaticReg *smFirst; + _TorqueThreadStaticReg *mNext; + + // This is a vector of vectors which will store instances of thread static + // variables. mThreadStaticInsances[0] will be the list of the initial values + // of the statics, and then indexing for instanced versions will start at 1 + // + // Note that the list of instances in mThreadStaticInstances[0] does not, and + // must not get 'delete' called on it, because all members of the list are + // pointers to statically allocated memory. All other lists will be contain + // pointers to dynamically allocated memory, and will need to be freed upon + // termination. + // + // So this was originally a static data member, however that caused problems because + // I was relying on static initialization order to make sure the vector got initialized + // *before* any static instance of this class was created via macro. By wrapping the + // static in a function, I can be assured that the static memory will get initialized + // before it is modified. + static Vector &getThreadStaticListVector(); + +public: + /// Constructor + _TorqueThreadStaticReg( _TorqueThreadStatic *ttsInitial ) + { + // Link this entry into the list + mNext = smFirst; + smFirst = this; + + // Create list 0 (initial values) if it doesn't exist + if( getThreadStaticListVector().empty() ) + getThreadStaticListVector().increment(); + + // Set the index of the thread static for lookup + ttsInitial->mListIndex = getThreadStaticListVector()[0].size(); + + // Add the static to the initial value list + getThreadStaticListVector()[0].push_back( ttsInitial ); + } + + virtual ~_TorqueThreadStaticReg(); + + // Accessors + static const TorqueThreadStaticList &getStaticList( const U32 idx = 0 ) + { + AssertFatal( getThreadStaticListVector().size() > idx, "Out of range static list" ); + + return getThreadStaticListVector()[idx]; + } + + static void destroyInstances(); + static void destroyInstance( TorqueThreadStaticList *instanceList ); + + static const _TorqueThreadStaticReg *getFirst() { return smFirst; } + + const _TorqueThreadStaticReg *getNext() const { return mNext; } + + /// Spawn another copy of the ThreadStatics and pass back the id + static TorqueThreadStaticListHandle spawnThreadStaticsInstance(); +}; + +//----------------------------------------------------------------------------- +// Template class that will get used as a base for the thread statics +template +class TorqueThreadStatic : public _TorqueThreadStatic +{ + // The reg object will want access to mInstance + friend class _TorqueThreadStaticReg; + +private: + T mInstance; + +public: + TorqueThreadStatic( T instanceVal ) : mInstance( instanceVal ) {} + virtual void *getMemInstPtr() { return &mInstance; } + + // I am not sure these are needed, and I don't want to create confusing-to-debug code +#if 0 + // Operator overloads + operator T*() { return &mInstance; } + operator T*() const { return &mInstance; } + operator const T*() const { return &mInstance; } + + bool operator ==( const T &l ) const { return mInstance == l; } + bool operator !=( const T &l ) const { return mInstance != l; } + + T &operator =( const T &l ) { mInstance = l; return mInstance; } +#endif // if0 +}; + +//----------------------------------------------------------------------------- +// If ThreadStatic behavior is not enabled, than the macros will resolve +// to regular, static memory +#ifndef TORQUE_ENABLE_THREAD_STATICS + +#define DITTS( type, name, initialvalue ) static type name = initialvalue +#define ATTS( name ) name + +#else // TORQUE_ENABLE_THREAD_STATICS is defined + +//----------------------------------------------------------------------------- +// Declare TorqueThreadStatic, and initialize it's value +// +// This macro would be used in a .cpp file to declare a ThreadStatic +#define DITTS(type, name, initalvalue) \ +class _##name##TorqueThreadStatic : public TorqueThreadStatic \ +{ \ +protected:\ + virtual _TorqueThreadStatic *_createInstance() const { return new _##name##TorqueThreadStatic; } \ +public: \ + _##name##TorqueThreadStatic() : TorqueThreadStatic( initalvalue ) {} \ + virtual const dsize_t getMemInstSize() const { return sizeof( type ); } \ + type &_cast() { return *reinterpret_cast( getMemInstPtr() ); } \ + const type &_const_cast() const { return *reinterpret_cast( getMemInstPtr() ); } \ +}; \ +static _##name##TorqueThreadStatic name##TorqueThreadStatic; \ +static _TorqueThreadStaticReg _##name##TTSReg( reinterpret_cast<_TorqueThreadStatic *>( & name##TorqueThreadStatic ) ) + +//----------------------------------------------------------------------------- +// Access TorqueThreadStatic + +// NOTE: TEMPDEF is there as a temporary place holder for however we want to get the index of the currently running +// thread or whatever. +#define TEMPDEF 0 + +#ifdef TORQUE_ENABLE_THREAD_STATIC_METRICS +// Access counting macro +# define ATTS_(name, idx) \ + (reinterpret_cast< _##name##TorqueThreadStatic *>( _TorqueThreadStaticReg::getStaticList( idx )[ _##name##TorqueThreadStatic::getListIndex() ]->_chainHit() )->_cast() ) +// Const access counting macro +# define CATTS_(name, idx) \ + (reinterpret_cast< _##name##TorqueThreadStatic *>( _TorqueThreadStaticReg::getStaticList( idx )[ _##name##TorqueThreadStatic::getListIndex() ]->_chainHit() )->_const_cast() ) +#else +// Regular access macro +# define ATTS_(name, idx) \ + (reinterpret_cast< _##name##TorqueThreadStatic *>( _TorqueThreadStaticReg::getStaticList( idx )[ _##name##TorqueThreadStatic::getListIndex() ] )->_cast() ) +// Const access macro +# define CATTS_(name, idx) \ + (reinterpret_cast< _##name##TorqueThreadStatic *>( _TorqueThreadStaticReg::getStaticList( idx )[ _##name##TorqueThreadStatic::getListIndex() ] )->_const_cast() ) +#endif // TORQUE_ENABLE_THREAD_STATIC_METRICS + +#define ATTS(name) ATTS_(name, TEMPDEF) +#define CATTS(name) CATTS_(name, TEMPDEF) + +#endif // TORQUE_ENABLE_THREAD_STATICS + +#endif \ No newline at end of file diff --git a/core/tokenizer.cpp b/core/tokenizer.cpp new file mode 100644 index 0000000..9d1e3d6 --- /dev/null +++ b/core/tokenizer.cpp @@ -0,0 +1,596 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/tokenizer.h" +#include "platform/platform.h" +#include "core/stream/fileStream.h" +#include "core/strings/stringFunctions.h" +#include "core/util/safeDelete.h" + +Tokenizer::Tokenizer() +{ + dMemset(mFileName, 0, sizeof(mFileName)); + + mpBuffer = NULL; + mBufferSize = 0; + + mStartPos = 0; + mCurrPos = 0; + + mTokenIsQuoted = false; + + dMemset(mCurrTokenBuffer, 0, sizeof(mCurrTokenBuffer)); + mTokenIsCurrent = false; + + mSingleTokens = NULL; + + VECTOR_SET_ASSOCIATION(mLinePositions); +} + +Tokenizer::~Tokenizer() +{ + clear(); +} + +bool Tokenizer::openFile(const char* pFileName) +{ + AssertFatal(mFileName[0] == '\0', "Reuse of Tokenizers not allowed!"); + + FileStream* pStream = new FileStream; + if (pStream->open(pFileName, Torque::FS::File::Read) == false) + { + delete pStream; + return false; + } + dStrcpy(mFileName, pFileName); + + mBufferSize = pStream->getStreamSize(); + mpBuffer = new char[mBufferSize]; + pStream->read(mBufferSize, mpBuffer); + pStream->close(); + delete pStream; + + reset(); + + buildLinePositions(); + + return true; +} + +bool Tokenizer::openFile(Stream* pStream) +{ + mBufferSize = pStream->getStreamSize(); + mpBuffer = new char[mBufferSize]; + pStream->read(mBufferSize, mpBuffer); + + reset(); + + buildLinePositions(); + + return true; +} + +void Tokenizer::setBuffer(const char* buffer, U32 bufferSize) +{ + if (mpBuffer) + { + SAFE_DELETE_ARRAY(mpBuffer); + mBufferSize = 0; + } + + mBufferSize = bufferSize; + mpBuffer = new char[mBufferSize + 1]; + dStrcpy(mpBuffer, buffer); + + reset(); + + buildLinePositions(); +} + +void Tokenizer::setSingleTokens(const char* singleTokens) +{ + if (mSingleTokens) + SAFE_DELETE(mSingleTokens); + + if (singleTokens) + mSingleTokens = dStrdup(singleTokens); +} + +bool Tokenizer::reset() +{ + mStartPos = 0; + mCurrPos = 0; + + mTokenIsQuoted = false; + + dMemset(mCurrTokenBuffer, 0, sizeof(mCurrTokenBuffer)); + mTokenIsCurrent = false; + + return true; +} + +bool Tokenizer::clear() +{ + // Delete our buffer + if (mpBuffer) + SAFE_DELETE_ARRAY(mpBuffer); + + // Reset the buffer size + mBufferSize = 0; + + // Reset our active data + reset(); + + // Clear our line positions + mLinePositions.clear(); + + // Reset our file name + dMemset(mFileName, 0, 1024); + + // Wipe the single tokens + setSingleTokens(NULL); + + return true; +} + +bool Tokenizer::setCurrentPos(U32 pos) +{ + mCurrPos = pos; + mTokenIsCurrent = false; + + return advanceToken(true); +} + +void Tokenizer::buildLinePositions() +{ + if (mBufferSize == 0) + return; + + // We can safely assume that the first line is at position 0 + mLinePositions.push_back(0); + + U32 currPos = 0; + while (currPos + 1 < mBufferSize) + { + // Windows line ending + if (mpBuffer[currPos] == '\r' && mpBuffer[currPos + 1] == '\n') + { + currPos += 2; + + mLinePositions.push_back(currPos); + } + // Not sure if this ever happens but just in case + else if (mpBuffer[currPos] == '\n' && mpBuffer[currPos + 1] == '\r') + { + currPos += 2; + + mLinePositions.push_back(currPos); + } + // Unix line endings should only have a single line break character + else if (mpBuffer[currPos] == '\n' || mpBuffer[currPos] == '\r') + { + currPos++; + + mLinePositions.push_back(currPos); + } + else + currPos++; + } +} + +U32 Tokenizer::getLinePosition(const U32 pos, U32 lowIndex, S32 highIndex) +{ + // If we have one or less lines then + // the result is easy + if (mLinePositions.size() <= 1) + return 0; + + // Now that we know we have at least one position + // we can do a quick test against the last line + if (pos >= mLinePositions.last()) + return mLinePositions.size() - 1; + + // If this is the beginning of the search + // set a good starting point (the middle) + if (highIndex < 0) + highIndex = mLinePositions.size() - 1; + + // Just in case bad values got handed in + if (lowIndex > highIndex) + lowIndex = highIndex; + + // Compute our test index (middle) + U32 testIndex = (lowIndex + highIndex) / 2; + + // Make sure that our test indices are valid + if (testIndex >= mLinePositions.size() || + testIndex + 1 >= mLinePositions.size()) + return mLinePositions.size() - 1; + + // See if we are already at the right line + if (pos >= mLinePositions[testIndex] && pos < mLinePositions[testIndex + 1]) + return testIndex; + + if (pos < mLinePositions[testIndex]) + highIndex = testIndex; + else + lowIndex = testIndex; + + return getLinePosition(pos, lowIndex, highIndex); +} + +U32 Tokenizer::getCurrentLine() +{ + // Binary search for the line number whose + // position is equal to or lower than the + // current position + return getLinePosition(mStartPos); +} + +U32 Tokenizer::getTokenLineOffset() +{ + U32 lineNumber = getCurrentLine(); + + if (lineNumber >= mLinePositions.size()) + return 0; + + U32 linePosition = mLinePositions[lineNumber]; + + if (linePosition >= mStartPos) + return 0; + + return mStartPos - linePosition; +} + +bool Tokenizer::advanceToken(const bool crossLine, const bool assertAvail) +{ + if (mTokenIsCurrent == true) + { + AssertFatal(mCurrTokenBuffer[0] != '\0', "No token, but marked as current?"); + mTokenIsCurrent = false; + return true; + } + + U32 currPosition = 0; + mCurrTokenBuffer[0] = '\0'; + + mTokenIsQuoted = false; + + // Store the beginning of the previous advance + // and the beginning of the current advance + mStartPos = mCurrPos; + + while (mCurrPos < mBufferSize) + { + char c = mpBuffer[mCurrPos]; + + bool cont = true; + + if (mSingleTokens && dStrchr(mSingleTokens, c)) + { + if (currPosition == 0) + { + mCurrTokenBuffer[currPosition++] = c; + mCurrPos++; + cont = false; + break; + } + else + { + // End of token + cont = false; + } + } + else + { + switch (c) + { + case ' ': + case '\t': + if (currPosition == 0) + { + // Token hasn't started yet... + mCurrPos++; + } + else + { + // End of token + mCurrPos++; + cont = false; + } + break; + + case '\r': + case '\n': + if (crossLine == true) + { + // Windows line ending + if (mpBuffer[mCurrPos] == '\r' && mpBuffer[mCurrPos + 1] == '\n') + mCurrPos += 2; + // Not sure if this ever happens but just in case + else if (mpBuffer[mCurrPos] == '\n' && mpBuffer[mCurrPos + 1] == '\r') + mCurrPos += 2; + // Unix line endings should only have a single line break character + else + mCurrPos++; + } + else + { + cont = false; + break; + } + break; + + default: + if (c == '\"') + { + // Quoted token + U32 startLine = getCurrentLine(); + mCurrPos++; + + // Store the beginning of the token + mStartPos = mCurrPos; + + while (mpBuffer[mCurrPos] != '\"') + { + AssertISV(mCurrPos < mBufferSize, + avar("End of file before quote closed. Quote started: (%s: %d)", + getFileName(), startLine)); + AssertISV((mpBuffer[mCurrPos] != '\n' && mpBuffer[mCurrPos] != '\r'), + avar("End of line reached before end of quote. Quote started: (%s: %d)", + getFileName(), startLine)); + + mCurrTokenBuffer[currPosition++] = mpBuffer[mCurrPos++]; + } + + mTokenIsQuoted = true; + + mCurrPos++; + cont = false; + } + else if (c == '/' && mpBuffer[mCurrPos+1] == '/') + { + // Line quote... + if (currPosition == 0) + { + // continue to end of line, then let crossLine determine on the next pass + while (mCurrPos < mBufferSize && (mpBuffer[mCurrPos] != '\n' && mpBuffer[mCurrPos] != '\r')) + mCurrPos++; + } + else + { + // This is the end of the token. Continue to EOL + while (mCurrPos < mBufferSize && (mpBuffer[mCurrPos] != '\n' && mpBuffer[mCurrPos] != '\r')) + mCurrPos++; + cont = false; + } + } + else + { + // If this is the first non-token character then store the + // beginning of the token + if (currPosition == 0) + mStartPos = mCurrPos; + + mCurrTokenBuffer[currPosition++] = c; + mCurrPos++; + } + break; + } + } + + if (cont == false) + break; + } + + mCurrTokenBuffer[currPosition] = '\0'; + + if (assertAvail == true) + AssertISV(currPosition != 0, avar("Error parsing: %s at or around line: %d", getFileName(), getCurrentLine())); + + if (mCurrPos == mBufferSize) + return false; + + return true; +} + +bool Tokenizer::regressToken(const bool crossLine) +{ + if (mTokenIsCurrent == true) + { + AssertFatal(mCurrTokenBuffer[0] != '\0', "No token, but marked as current?"); + mTokenIsCurrent = false; + return true; + } + + U32 currPosition = 0; + mCurrTokenBuffer[0] = '\0'; + + mTokenIsQuoted = false; + + // Store the beginning of the previous advance + // and the beginning of the current advance + mCurrPos = mStartPos; + + // Back up to the first character of the previous token + mStartPos--; + + while (mStartPos > 0) + { + char c = mpBuffer[mStartPos]; + + bool cont = true; + + if (mSingleTokens && dStrchr(mSingleTokens, c)) + { + if (currPosition == 0) + { + mCurrTokenBuffer[currPosition++] = c; + mStartPos--; + cont = false; + break; + } + else + { + // End of token + cont = false; + } + } + else + { + switch (c) + { + case ' ': + case '\t': + if (currPosition == 0) + { + // Token hasn't started yet... + mStartPos--; + } + else + { + // End of token + mStartPos--; + cont = false; + } + break; + + case '\r': + case '\n': + if (crossLine == true && currPosition == 0) + { + // Windows line ending + if (mStartPos > 0 && mpBuffer[mStartPos] == '\r' && mpBuffer[mStartPos - 1] == '\n') + mStartPos -= 2; + // Not sure if this ever happens but just in case + else if (mStartPos > 0 && mpBuffer[mStartPos] == '\n' && mpBuffer[mStartPos - 1] == '\r') + mStartPos -= 2; + // Unix line endings should only have a single line break character + else + mStartPos--; + } + else + { + cont = false; + break; + } + break; + + default: + if (c == '\"') + { + // Quoted token + U32 endLine = getCurrentLine(); + mStartPos--; + + while (mpBuffer[mStartPos] != '\"') + { + AssertISV(mStartPos < 0, + avar("Beginning of file reached before finding begin quote. Quote ended: (%s: %d)", + getFileName(), endLine)); + + mCurrTokenBuffer[currPosition++] = mpBuffer[mStartPos--]; + } + + mTokenIsQuoted = true; + + mStartPos--; + cont = false; + } + else if (c == '/' && mStartPos > 0 && mpBuffer[mStartPos - 1] == '/') + { + // Line quote... + // Clear out anything saved already + currPosition = 0; + + mStartPos -= 2; + } + else + { + mCurrTokenBuffer[currPosition++] = c; + mStartPos--; + } + break; + } + } + + if (cont == false) + break; + } + + mCurrTokenBuffer[currPosition] = '\0'; + + // Reveres the token + for (U32 i = 0; i < currPosition / 2; i++) + { + char c = mCurrTokenBuffer[i]; + mCurrTokenBuffer[i] = mCurrTokenBuffer[currPosition - i - 1]; + mCurrTokenBuffer[currPosition - i - 1] = c; + } + + mStartPos++; + + if (mStartPos == mCurrPos) + return false; + + return true; +} + +bool Tokenizer::tokenAvailable() +{ + // Note: this implies that when advanceToken(false) fails, it must cap the + // token buffer. + // + return mCurrTokenBuffer[0] != '\0'; +} + +const char* Tokenizer::getToken() const +{ + return mCurrTokenBuffer; +} + +const char* Tokenizer::getNextToken() +{ + advanceToken(true); + + return getToken(); +} + +bool Tokenizer::tokenICmp(const char* pCmp) const +{ + return dStricmp(mCurrTokenBuffer, pCmp) == 0; +} + +bool Tokenizer::findToken(U32 start, const char* pCmp) +{ + // Move to the start + setCurrentPos(start); + + // In case the first token is what we are looking for + if (tokenICmp(pCmp)) + return true; + + // Loop through the file and see if the token exists + while (advanceToken(true)) + { + if (tokenICmp(pCmp)) + return true; + } + + return false; +} + +bool Tokenizer::findToken(const char* pCmp) +{ + return findToken(0, pCmp); +} + +bool Tokenizer::endOfFile() +{ + if (mCurrPos < mBufferSize) + return false; + else + return true; +} \ No newline at end of file diff --git a/core/tokenizer.h b/core/tokenizer.h new file mode 100644 index 0000000..df145bc --- /dev/null +++ b/core/tokenizer.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TOKENIZER_H_ +#define _TOKENIZER_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class SizedStream; + +class Tokenizer +{ + public: + enum + { + MaxTokenSize = 1023 + }; + + private: + char mFileName[1024]; + + char* mpBuffer; + U32 mBufferSize; + + S32 mCurrPos; + S32 mStartPos; + + bool mTokenIsQuoted; + + char mCurrTokenBuffer[MaxTokenSize + 1]; + bool mTokenIsCurrent; + + char* mSingleTokens; + + Vector mLinePositions; + + public: + Tokenizer(); + ~Tokenizer(); + + bool openFile(const char* pFileName); + bool openFile(Stream* pStream); + void setBuffer(const char* buffer, U32 bufferSize); + + void setSingleTokens(const char* singleTokens); + + void buildLinePositions(); + + bool advanceToken(const bool crossLine, const bool assertAvailable = false); + bool regressToken(const bool crossLine); + bool tokenAvailable(); + + const char* getToken() const; + const char* getNextToken(); + bool tokenICmp(const char* pCmp) const; + bool tokenIsQuoted() const { return mTokenIsQuoted; } + + bool findToken(const char* pCmp); + bool findToken(U32 start, const char* pCmp); + + const char* getFileName() const { return mFileName; } + + U32 getLinePosition(const U32 pos, U32 lowIndex = 0, S32 highIndex = -1); + U32 getCurrentLine(); + U32 getTokenLineOffset(); + U32 getCurrentPos() const { return mCurrPos; } + + bool setCurrentPos(U32 pos); + + // Resets our active data but leaves the buffer intact + bool reset(); + // Clears the buffer and resets the active data + bool clear(); + + bool endOfFile(); +}; + + +#endif //_TOKENIZER_H_ diff --git a/core/util/FastDelegate.h b/core/util/FastDelegate.h new file mode 100644 index 0000000..e1959f4 --- /dev/null +++ b/core/util/FastDelegate.h @@ -0,0 +1,2110 @@ +// FastDelegate.h +// Efficient delegates in C++ that generate only two lines of asm code! +// Documentation is found at http://www.codeproject.com/cpp/FastDelegate.asp +// +// - Don Clugston, Mar 2004. +// Major contributions were made by Jody Hagins. +// History: +// 24-Apr-04 1.0 * Submitted to CodeProject. +// 28-Apr-04 1.1 * Prevent most unsafe uses of evil static function hack. +// * Improved syntax for horrible_cast (thanks Paul Bludov). +// * Tested on Metrowerks MWCC and Intel ICL (IA32) +// * Compiled, but not run, on Comeau C++ and Intel Itanium ICL. +// 27-Jun-04 1.2 * Now works on Borland C++ Builder 5.5 +// * Now works on /clr "managed C++" code on VC7, VC7.1 +// * Comeau C++ now compiles without warnings. +// * Prevent the virtual inheritance case from being used on +// VC6 and earlier, which generate incorrect code. +// * Improved warning and error messages. Non-standard hacks +// now have compile-time checks to make them safer. +// * implicit_cast used instead of static_cast in many cases. +// * If calling a const member function, a const class pointer can be used. +// * MakeDelegate() global helper function added to simplify pass-by-value. +// * Added fastdelegate.clear() +// 16-Jul-04 1.2.1* Workaround for gcc bug (const member function pointers in templates) +// 30-Oct-04 1.3 * Support for (non-void) return values. +// * No more workarounds in client code! +// MSVC and Intel now use a clever hack invented by John Dlugosz: +// - The FASTDELEGATEDECLARE workaround is no longer necessary. +// - No more warning messages for VC6 +// * Less use of macros. Error messages should be more comprehensible. +// * Added include guards +// * Added FastDelegate::empty() to test if invocation is safe (Thanks Neville Franks). +// * Now tested on VS 2005 Express Beta, PGI C++ +// 24-Dec-04 1.4 * Added DelegateMemento, to allow collections of disparate delegates. +// * <,>,<=,>= comparison operators to allow storage in ordered containers. +// * Substantial reduction of code size, especially the 'Closure' class. +// * Standardised all the compiler-specific workarounds. +// * MFP conversion now works for CodePlay (but not yet supported in the full code). +// * Now compiles without warnings on _any_ supported compiler, including BCC 5.5.1 +// * New syntax: FastDelegate< int (char *, double) >. +// 14-Feb-05 1.4.1* Now treats =0 as equivalent to .clear(), ==0 as equivalent to .empty(). (Thanks elfric). +// * Now tested on Intel ICL for AMD64, VS2005 Beta for AMD64 and Itanium. +// 30-Mar-05 1.5 * Safebool idiom: "if (dg)" is now equivalent to "if (!dg.empty())" +// * Fully supported by CodePlay VectorC +// * Bugfix for Metrowerks: empty() was buggy because a valid MFP can be 0 on MWCC! +// * More optimal assignment,== and != operators for static function pointers. + +#ifndef FASTDELEGATE_H +#define FASTDELEGATE_H +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Configuration options +// +//////////////////////////////////////////////////////////////////////////////// + +// Uncomment the following #define for optimally-sized delegates. +// In this case, the generated asm code is almost identical to the code you'd get +// if the compiler had native support for delegates. +// It will not work on systems where sizeof(dataptr) < sizeof(codeptr). +// Thus, it will not work for DOS compilers using the medium model. +// It will also probably fail on some DSP systems. +#define FASTDELEGATE_USESTATICFUNCTIONHACK + +// Uncomment the next line to allow function declarator syntax. +// It is automatically enabled for those compilers where it is known to work. +#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + +//////////////////////////////////////////////////////////////////////////////// +// Compiler identification for workarounds +// +//////////////////////////////////////////////////////////////////////////////// + +// Compiler identification. It's not easy to identify Visual C++ because +// many vendors fraudulently define Microsoft's identifiers. +#if defined(_MSC_VER) && !defined(__MWERKS__) && !defined(__VECTOR_C) && !defined(__ICL) && !defined(__BORLANDC__) +#define FASTDLGT_ISMSVC + +#if (_MSC_VER <1300) // Many workarounds are required for VC6. +#define FASTDLGT_VC6 +#pragma warning(disable:4786) // disable this ridiculous warning +#endif + +#endif + +// Does the compiler uses Microsoft's member function pointer structure? +// If so, it needs special treatment. +// Metrowerks CodeWarrior, Intel, and CodePlay fraudulently define Microsoft's +// identifier, _MSC_VER. We need to filter Metrowerks out. +#if defined(_MSC_VER) && !defined(__MWERKS__) +#define FASTDLGT_MICROSOFT_MFP + +#if !defined(__VECTOR_C) +// CodePlay doesn't have the __single/multi/virtual_inheritance keywords +#define FASTDLGT_HASINHERITANCE_KEYWORDS +#endif +#endif + +// Does it allow function declarator syntax? The following compilers are known to work: +#if defined(FASTDLGT_ISMSVC) && (_MSC_VER >=1310) // VC 7.1 +#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX +#endif + +// Gcc(2.95+), and versions of Digital Mars, Intel and Comeau in common use. +#if defined (__DMC__) || defined(__GNUC__) || defined(__ICL) || defined(__COMO__) +#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX +#endif + +// It works on Metrowerks MWCC 3.2.2. From boost.Config it should work on earlier ones too. +#if defined (__MWERKS__) +#define FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX +#endif + +#ifdef __GNUC__ // Workaround GCC bug #8271 + // At present, GCC doesn't recognize constness of MFPs in templates +#define FASTDELEGATE_GCC_BUG_8271 +#endif + + + +//////////////////////////////////////////////////////////////////////////////// +// General tricks used in this code +// +// (a) Error messages are generated by typdefing an array of negative size to +// generate compile-time errors. +// (b) Warning messages on MSVC are generated by declaring unused variables, and +// enabling the "variable XXX is never used" warning. +// (c) Unions are used in a few compiler-specific cases to perform illegal casts. +// (d) For Microsoft and Intel, when adjusting the 'this' pointer, it's cast to +// (char *) first to ensure that the correct number of *bytes* are added. +// +//////////////////////////////////////////////////////////////////////////////// +// Helper templates +// +//////////////////////////////////////////////////////////////////////////////// + + +namespace fastdelegate { +namespace detail { // we'll hide the implementation details in a nested namespace. + +// implicit_cast< > +// I believe this was originally going to be in the C++ standard but +// was left out by accident. It's even milder than static_cast. +// I use it instead of static_cast<> to emphasize that I'm not doing +// anything nasty. +// Usage is identical to static_cast<> +template +inline OutputClass implicit_cast(InputClass input){ + return input; +} + +// horrible_cast< > +// This is truly evil. It completely subverts C++'s type system, allowing you +// to cast from any class to any other class. Technically, using a union +// to perform the cast is undefined behaviour (even in C). But we can see if +// it is OK by checking that the union is the same size as each of its members. +// horrible_cast<> should only be used for compiler-specific workarounds. +// Usage is identical to reinterpret_cast<>. + +// This union is declared outside the horrible_cast because BCC 5.5.1 +// can't inline a function with a nested class, and gives a warning. +template +union horrible_union{ + OutputClass out; + InputClass in; +}; + +template +inline OutputClass horrible_cast(const InputClass input){ + horrible_union u; + // Cause a compile-time error if in, out and u are not the same size. + // If the compile fails here, it means the compiler has peculiar + // unions which would prevent the cast from working. + typedef int ERROR_CantUseHorrible_cast[sizeof(InputClass)==sizeof(u) + && sizeof(InputClass)==sizeof(OutputClass) ? 1 : -1]; + u.in = input; + return u.out; +} + +//////////////////////////////////////////////////////////////////////////////// +// Workarounds +// +//////////////////////////////////////////////////////////////////////////////// + +// Backwards compatibility: This macro used to be necessary in the virtual inheritance +// case for Intel and Microsoft. Now it just forward-declares the class. +#define FASTDELEGATEDECLARE(CLASSNAME) class CLASSNAME; + +// Prevent use of the static function hack with the DOS medium model. +#ifdef __MEDIUM__ +#undef FASTDELEGATE_USESTATICFUNCTIONHACK +#endif + +// DefaultVoid - a workaround for 'void' templates in VC6. +// +// (1) VC6 and earlier do not allow 'void' as a default template argument. +// (2) They also doesn't allow you to return 'void' from a function. +// +// Workaround for (1): Declare a dummy type 'DefaultVoid' which we use +// when we'd like to use 'void'. We convert it into 'void' and back +// using the templates DefaultVoidToVoid<> and VoidToDefaultVoid<>. +// Workaround for (2): On VC6, the code for calling a void function is +// identical to the code for calling a non-void function in which the +// return value is never used, provided the return value is returned +// in the EAX register, rather than on the stack. +// This is true for most fundamental types such as int, enum, void *. +// Const void * is the safest option since it doesn't participate +// in any automatic conversions. But on a 16-bit compiler it might +// cause extra code to be generated, so we disable it for all compilers +// except for VC6 (and VC5). +#ifdef FASTDLGT_VC6 +// VC6 workaround +typedef const void * DefaultVoid; +#else +// On any other compiler, just use a normal void. +typedef void DefaultVoid; +#endif + +// Translate from 'DefaultVoid' to 'void'. +// Everything else is unchanged +template +struct DefaultVoidToVoid { typedef T type; }; + +template <> +struct DefaultVoidToVoid { typedef void type; }; + +// Translate from 'void' into 'DefaultVoid' +// Everything else is unchanged +template +struct VoidToDefaultVoid { typedef T type; }; + +template <> +struct VoidToDefaultVoid { typedef DefaultVoid type; }; + + + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 1: +// +// Conversion of member function pointer to a standard form +// +//////////////////////////////////////////////////////////////////////////////// + +// GenericClass is a fake class, ONLY used to provide a type. +// It is vitally important that it is never defined, so that the compiler doesn't +// think it can optimize the invocation. For example, Borland generates simpler +// code if it knows the class only uses single inheritance. + +// Compilers using Microsoft's structure need to be treated as a special case. +#ifdef FASTDLGT_MICROSOFT_MFP + +#ifdef FASTDLGT_HASINHERITANCE_KEYWORDS + // For Microsoft and Intel, we want to ensure that it's the most efficient type of MFP + // (4 bytes), even when the /vmg option is used. Declaring an empty class + // would give 16 byte pointers in this case.... + class __single_inheritance GenericClass; +#endif + // ...but for Codeplay, an empty class *always* gives 4 byte pointers. + // If compiled with the /clr option ("managed C++"), the JIT compiler thinks + // it needs to load GenericClass before it can call any of its functions, + // (compiles OK but crashes at runtime!), so we need to declare an + // empty class to make it happy. + // Codeplay and VC4 can't cope with the unknown_inheritance case either. + class GenericClass {}; +#else + class GenericClass; +#endif + +// The size of a single inheritance member function pointer. +const int SINGLE_MEMFUNCPTR_SIZE = sizeof(void (GenericClass::*)()); + +// SimplifyMemFunc< >::Convert() +// +// A template function that converts an arbitrary member function pointer into the +// simplest possible form of member function pointer, using a supplied 'this' pointer. +// According to the standard, this can be done legally with reinterpret_cast<>. +// For (non-standard) compilers which use member function pointers which vary in size +// depending on the class, we need to use knowledge of the internal structure of a +// member function pointer, as used by the compiler. Template specialization is used +// to distinguish between the sizes. Because some compilers don't support partial +// template specialisation, I use full specialisation of a wrapper struct. + +// general case -- don't know how to convert it. Force a compile failure +template +struct SimplifyMemFunc { + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // Unsupported member function type -- force a compile failure. + // (it's illegal to have a array with negative size). + typedef char ERROR_Unsupported_member_function_pointer_on_this_compiler[N-100]; + return 0; + } +}; + +// For compilers where all member func ptrs are the same size, everything goes here. +// For non-standard compilers, only single_inheritance classes go here. +template <> +struct SimplifyMemFunc { + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { +#if defined __DMC__ + // Digital Mars doesn't allow you to cast between abitrary PMF's, + // even though the standard says you can. The 32-bit compiler lets you + // static_cast through an int, but the DOS compiler doesn't. + bound_func = horrible_cast(function_to_bind); +#else + bound_func = reinterpret_cast(function_to_bind); +#endif + return reinterpret_cast(pthis); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 1b: +// +// Workarounds for Microsoft and Intel +// +//////////////////////////////////////////////////////////////////////////////// + + +// Compilers with member function pointers which violate the standard (MSVC, Intel, Codeplay), +// need to be treated as a special case. +#ifdef FASTDLGT_MICROSOFT_MFP + +// We use unions to perform horrible_casts. I would like to use #pragma pack(push, 1) +// at the start of each function for extra safety, but VC6 seems to ICE +// intermittently if you do this inside a template. + +// __multiple_inheritance classes go here +// Nasty hack for Microsoft and Intel (IA32 and Itanium) +template<> +struct SimplifyMemFunc< SINGLE_MEMFUNCPTR_SIZE + sizeof(int) > { + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // We need to use a horrible_cast to do this conversion. + // In MSVC, a multiple inheritance member pointer is internally defined as: + union { + XFuncType func; + struct { + GenericMemFuncType funcaddress; // points to the actual member function + int delta; // #BYTES to be added to the 'this' pointer + }s; + } u; + // Check that the horrible_cast will work + typedef int ERROR_CantUsehorrible_cast[sizeof(function_to_bind)==sizeof(u.s)? 1 : -1]; + u.func = function_to_bind; + bound_func = u.s.funcaddress; + return reinterpret_cast(reinterpret_cast(pthis) + u.s.delta); + } +}; + +// virtual inheritance is a real nuisance. It's inefficient and complicated. +// On MSVC and Intel, there isn't enough information in the pointer itself to +// enable conversion to a closure pointer. Earlier versions of this code didn't +// work for all cases, and generated a compile-time error instead. +// But a very clever hack invented by John M. Dlugosz solves this problem. +// My code is somewhat different to his: I have no asm code, and I make no +// assumptions about the calling convention that is used. + +// In VC++ and ICL, a virtual_inheritance member pointer +// is internally defined as: +struct MicrosoftVirtualMFP { + void (GenericClass::*codeptr)(); // points to the actual member function + int delta; // #bytes to be added to the 'this' pointer + int vtable_index; // or 0 if no virtual inheritance +}; +// The CRUCIAL feature of Microsoft/Intel MFPs which we exploit is that the +// m_codeptr member is *always* called, regardless of the values of the other +// members. (This is *not* true for other compilers, eg GCC, which obtain the +// function address from the vtable if a virtual function is being called). +// Dlugosz's trick is to make the codeptr point to a probe function which +// returns the 'this' pointer that was used. + +// Define a generic class that uses virtual inheritance. +// It has a trival member function that returns the value of the 'this' pointer. +struct GenericVirtualClass : virtual public GenericClass +{ + typedef GenericVirtualClass * (GenericVirtualClass::*ProbePtrType)(); + GenericVirtualClass * GetThis() { return this; } +}; + +// __virtual_inheritance classes go here +template <> +struct SimplifyMemFunc +{ + + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + union { + XFuncType func; + GenericClass* (X::*ProbeFunc)(); + MicrosoftVirtualMFP s; + } u; + u.func = function_to_bind; + bound_func = reinterpret_cast(u.s.codeptr); + union { + GenericVirtualClass::ProbePtrType virtfunc; + MicrosoftVirtualMFP s; + } u2; + // Check that the horrible_cast<>s will work + typedef int ERROR_CantUsehorrible_cast[sizeof(function_to_bind)==sizeof(u.s) + && sizeof(function_to_bind)==sizeof(u.ProbeFunc) + && sizeof(u2.virtfunc)==sizeof(u2.s) ? 1 : -1]; + // Unfortunately, taking the address of a MF prevents it from being inlined, so + // this next line can't be completely optimised away by the compiler. + u2.virtfunc = &GenericVirtualClass::GetThis; + u.s.codeptr = u2.s.codeptr; + return (pthis->*u.ProbeFunc)(); + } +}; + +#if (_MSC_VER <1300) + +// Nasty hack for Microsoft Visual C++ 6.0 +// unknown_inheritance classes go here +// There is a compiler bug in MSVC6 which generates incorrect code in this case!! +template <> +struct SimplifyMemFunc +{ + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // There is an apalling but obscure compiler bug in MSVC6 and earlier: + // vtable_index and 'vtordisp' are always set to 0 in the + // unknown_inheritance case! + // This means that an incorrect function could be called!!! + // Compiling with the /vmg option leads to potentially incorrect code. + // This is probably the reason that the IDE has a user interface for specifying + // the /vmg option, but it is disabled - you can only specify /vmg on + // the command line. In VC1.5 and earlier, the compiler would ICE if it ever + // encountered this situation. + // It is OK to use the /vmg option if /vmm or /vms is specified. + + // Fortunately, the wrong function is only called in very obscure cases. + // It only occurs when a derived class overrides a virtual function declared + // in a virtual base class, and the member function + // points to the *Derived* version of that function. The problem can be + // completely averted in 100% of cases by using the *Base class* for the + // member fpointer. Ie, if you use the base class as an interface, you'll + // stay out of trouble. + // Occasionally, you might want to point directly to a derived class function + // that isn't an override of a base class. In this case, both vtable_index + // and 'vtordisp' are zero, but a virtual_inheritance pointer will be generated. + // We can generate correct code in this case. To prevent an incorrect call from + // ever being made, on MSVC6 we generate a warning, and call a function to + // make the program crash instantly. + typedef char ERROR_VC6CompilerBug[-100]; + return 0; + } +}; + + +#else + +// Nasty hack for Microsoft and Intel (IA32 and Itanium) +// unknown_inheritance classes go here +// This is probably the ugliest bit of code I've ever written. Look at the casts! +// There is a compiler bug in MSVC6 which prevents it from using this code. +template <> +struct SimplifyMemFunc +{ + template + inline static GenericClass *Convert(X *pthis, XFuncType function_to_bind, + GenericMemFuncType &bound_func) { + // The member function pointer is 16 bytes long. We can't use a normal cast, but + // we can use a union to do the conversion. + union { + XFuncType func; + // In VC++ and ICL, an unknown_inheritance member pointer + // is internally defined as: + struct { + GenericMemFuncType m_funcaddress; // points to the actual member function + int delta; // #bytes to be added to the 'this' pointer + int vtordisp; // #bytes to add to 'this' to find the vtable + int vtable_index; // or 0 if no virtual inheritance + } s; + } u; + // Check that the horrible_cast will work + typedef int ERROR_CantUsehorrible_cast[sizeof(XFuncType)==sizeof(u.s)? 1 : -1]; + u.func = function_to_bind; + bound_func = u.s.funcaddress; + int virtual_delta = 0; + if (u.s.vtable_index) { // Virtual inheritance is used + // First, get to the vtable. + // It is 'vtordisp' bytes from the start of the class. + const int * vtable = *reinterpret_cast( + reinterpret_cast(pthis) + u.s.vtordisp ); + + // 'vtable_index' tells us where in the table we should be looking. + virtual_delta = u.s.vtordisp + *reinterpret_cast( + reinterpret_cast(vtable) + u.s.vtable_index); + } + // The int at 'virtual_delta' gives us the amount to add to 'this'. + // Finally we can add the three components together. Phew! + return reinterpret_cast( + reinterpret_cast(pthis) + u.s.delta + virtual_delta); + }; +}; +#endif // MSVC 7 and greater + +#endif // MS/Intel hacks + +} // namespace detail + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 2: +// +// Define the delegate storage, and cope with static functions +// +//////////////////////////////////////////////////////////////////////////////// + +// DelegateMemento -- an opaque structure which can hold an arbitary delegate. +// It knows nothing about the calling convention or number of arguments used by +// the function pointed to. +// It supplies comparison operators so that it can be stored in STL collections. +// It cannot be set to anything other than null, nor invoked directly: +// it must be converted to a specific delegate. + +// Implementation: +// There are two possible implementations: the Safe method and the Evil method. +// DelegateMemento - Safe version +// +// This implementation is standard-compliant, but a bit tricky. +// A static function pointer is stored inside the class. +// Here are the valid values: +// +-- Static pointer --+--pThis --+-- pMemFunc-+-- Meaning------+ +// | 0 | 0 | 0 | Empty | +// | !=0 |(dontcare)| Invoker | Static function| +// | 0 | !=0 | !=0* | Method call | +// +--------------------+----------+------------+----------------+ +// * For Metrowerks, this can be 0. (first virtual function in a +// single_inheritance class). +// When stored stored inside a specific delegate, the 'dontcare' entries are replaced +// with a reference to the delegate itself. This complicates the = and == operators +// for the delegate class. + +// DelegateMemento - Evil version +// +// For compilers where data pointers are at least as big as code pointers, it is +// possible to store the function pointer in the this pointer, using another +// horrible_cast. In this case the DelegateMemento implementation is simple: +// +--pThis --+-- pMemFunc-+-- Meaning---------------------+ +// | 0 | 0 | Empty | +// | !=0 | !=0* | Static function or method call| +// +----------+------------+-------------------------------+ +// * For Metrowerks, this can be 0. (first virtual function in a +// single_inheritance class). +// Note that the Sun C++ and MSVC documentation explicitly state that they +// support static_cast between void * and function pointers. + +class DelegateMemento { +protected: + // the data is protected, not private, because many + // compilers have problems with template friends. + typedef void (detail::GenericClass::*GenericMemFuncType)(); // arbitrary MFP. + detail::GenericClass *m_pthis; + GenericMemFuncType m_pFunction; + +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + typedef void (*GenericFuncPtr)(); // arbitrary code pointer + GenericFuncPtr m_pStaticFunction; +#endif + +public: +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + DelegateMemento() : m_pthis(0), m_pFunction(0), m_pStaticFunction(0) {}; + void clear() { + m_pthis=0; m_pFunction=0; m_pStaticFunction=0; + } +#else + DelegateMemento() : m_pthis(0), m_pFunction(0) {}; + void clear() { m_pthis=0; m_pFunction=0; } +#endif +public: +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + inline bool IsEqual (const DelegateMemento &x) const{ + // We have to cope with the static function pointers as a special case + if (m_pFunction!=x.m_pFunction) return false; + // the static function ptrs must either both be equal, or both be 0. + if (m_pStaticFunction!=x.m_pStaticFunction) return false; + if (m_pStaticFunction!=0) return m_pthis==x.m_pthis; + else return true; + } +#else // Evil Method + inline bool IsEqual (const DelegateMemento &x) const{ + return m_pthis==x.m_pthis && m_pFunction==x.m_pFunction; + } +#endif + // Provide a strict weak ordering for DelegateMementos. + inline bool IsLess(const DelegateMemento &right) const { + // deal with static function pointers first +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + if (m_pStaticFunction !=0 || right.m_pStaticFunction!=0) + return m_pStaticFunction < right.m_pStaticFunction; +#endif + if (m_pthis !=right.m_pthis) return m_pthis < right.m_pthis; + // There are no ordering operators for member function pointers, + // but we can fake one by comparing each byte. The resulting ordering is + // arbitrary (and compiler-dependent), but it permits storage in ordered STL containers. + return dMemcmp(&m_pFunction, &right.m_pFunction, sizeof(m_pFunction)) < 0; + + } + // BUGFIX (Mar 2005): + // We can't just compare m_pFunction because on Metrowerks, + // m_pFunction can be zero even if the delegate is not empty! + inline bool operator ! () const // Is it bound to anything? + { return m_pthis==0 && m_pFunction==0; } + inline bool empty() const // Is it bound to anything? + { return m_pthis==0 && m_pFunction==0; } +public: + DelegateMemento & operator = (const DelegateMemento &right) { + SetMementoFrom(right); + return *this; + } + inline bool operator <(const DelegateMemento &right) { + return IsLess(right); + } + inline bool operator >(const DelegateMemento &right) { + return right.IsLess(*this); + } + DelegateMemento (const DelegateMemento &right) : + m_pthis(right.m_pthis), m_pFunction(right.m_pFunction) +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + , m_pStaticFunction (right.m_pStaticFunction) +#endif + {} +protected: + void SetMementoFrom(const DelegateMemento &right) { + m_pFunction = right.m_pFunction; + m_pthis = right.m_pthis; +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = right.m_pStaticFunction; +#endif + } +}; + + +// ClosurePtr<> +// +// A private wrapper class that adds function signatures to DelegateMemento. +// It's the class that does most of the actual work. +// The signatures are specified by: +// GenericMemFunc: must be a type of GenericClass member function pointer. +// StaticFuncPtr: must be a type of function pointer with the same signature +// as GenericMemFunc. +// UnvoidStaticFuncPtr: is the same as StaticFuncPtr, except on VC6 +// where it never returns void (returns DefaultVoid instead). + +// An outer class, FastDelegateN<>, handles the invoking and creates the +// necessary typedefs. +// This class does everything else. + +namespace detail { + +template < class GenericMemFunc, class StaticFuncPtr, class UnvoidStaticFuncPtr> +class ClosurePtr : public DelegateMemento { +public: + // These functions are for setting the delegate to a member function. + + // Here's the clever bit: we convert an arbitrary member function into a + // standard form. XMemFunc should be a member function of class X, but I can't + // enforce that here. It needs to be enforced by the wrapper class. + template < class X, class XMemFunc > + inline void bindmemfunc(X *pthis, XMemFunc function_to_bind ) { + m_pthis = SimplifyMemFunc< sizeof(function_to_bind) > + ::Convert(pthis, function_to_bind, m_pFunction); +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = 0; +#endif + } + // For const member functions, we only need a const class pointer. + // Since we know that the member function is const, it's safe to + // remove the const qualifier from the 'this' pointer with a const_cast. + // VC6 has problems if we just overload 'bindmemfunc', so we give it a different name. + template < class X, class XMemFunc> + inline void bindconstmemfunc(const X *pthis, XMemFunc function_to_bind) { + m_pthis= SimplifyMemFunc< sizeof(function_to_bind) > + ::Convert(const_cast(pthis), function_to_bind, m_pFunction); +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = 0; +#endif + } +#ifdef FASTDELEGATE_GCC_BUG_8271 // At present, GCC doesn't recognize constness of MFPs in templates + template < class X, class XMemFunc> + inline void bindmemfunc(const X *pthis, XMemFunc function_to_bind) { + bindconstmemfunc(pthis, function_to_bind); +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + m_pStaticFunction = 0; +#endif + } +#endif + // These functions are required for invoking the stored function + inline GenericClass *GetClosureThis() const { return m_pthis; } + inline GenericMemFunc GetClosureMemPtr() const { return reinterpret_cast(m_pFunction); } + +// There are a few ways of dealing with static function pointers. +// There's a standard-compliant, but tricky method. +// There's also a straightforward hack, that won't work on DOS compilers using the +// medium memory model. It's so evil that I can't recommend it, but I've +// implemented it anyway because it produces very nice asm code. + +#if !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + +// ClosurePtr<> - Safe version +// +// This implementation is standard-compliant, but a bit tricky. +// I store the function pointer inside the class, and the delegate then +// points to itself. Whenever the delegate is copied, these self-references +// must be transformed, and this complicates the = and == operators. +public: + // The next two functions are for operator ==, =, and the copy constructor. + // We may need to convert the m_pthis pointers, so that + // they remain as self-references. + template< class DerivedClass > + inline void CopyFrom (DerivedClass *pParent, const DelegateMemento &x) { + SetMementoFrom(x); + if (m_pStaticFunction!=0) { + // transform self references... + m_pthis=reinterpret_cast(pParent); + } + } + // For static functions, the 'static_function_invoker' class in the parent + // will be called. The parent then needs to call GetStaticFunction() to find out + // the actual function to invoke. + template < class DerivedClass, class ParentInvokerSig > + inline void bindstaticfunc(DerivedClass *pParent, ParentInvokerSig static_function_invoker, + StaticFuncPtr function_to_bind ) { + if (function_to_bind==0) { // cope with assignment to 0 + m_pFunction=0; + } else { + bindmemfunc(pParent, static_function_invoker); + } + m_pStaticFunction=reinterpret_cast(function_to_bind); + } + inline UnvoidStaticFuncPtr GetStaticFunction() const { + return reinterpret_cast(m_pStaticFunction); + } +#else + +// ClosurePtr<> - Evil version +// +// For compilers where data pointers are at least as big as code pointers, it is +// possible to store the function pointer in the this pointer, using another +// horrible_cast. Invocation isn't any faster, but it saves 4 bytes, and +// speeds up comparison and assignment. If C++ provided direct language support +// for delegates, they would produce asm code that was almost identical to this. +// Note that the Sun C++ and MSVC documentation explicitly state that they +// support static_cast between void * and function pointers. + + template< class DerivedClass > + inline void CopyFrom (DerivedClass *pParent, const DelegateMemento &right) { + SetMementoFrom(right); + } + // For static functions, the 'static_function_invoker' class in the parent + // will be called. The parent then needs to call GetStaticFunction() to find out + // the actual function to invoke. + // ******** EVIL, EVIL CODE! ******* + template < class DerivedClass, class ParentInvokerSig> + inline void bindstaticfunc(DerivedClass *pParent, ParentInvokerSig static_function_invoker, + StaticFuncPtr function_to_bind) { + if (function_to_bind==0) { // cope with assignment to 0 + m_pFunction=0; + } else { + // We'll be ignoring the 'this' pointer, but we need to make sure we pass + // a valid value to bindmemfunc(). + bindmemfunc(pParent, static_function_invoker); + } + + // WARNING! Evil hack. We store the function in the 'this' pointer! + // Ensure that there's a compilation failure if function pointers + // and data pointers have different sizes. + // If you get this error, you need to #undef FASTDELEGATE_USESTATICFUNCTIONHACK. + typedef int ERROR_CantUseEvilMethod[sizeof(GenericClass *)==sizeof(function_to_bind) ? 1 : -1]; + m_pthis = horrible_cast(function_to_bind); + // MSVC, SunC++ and DMC accept the following (non-standard) code: +// m_pthis = static_cast(static_cast(function_to_bind)); + // BCC32, Comeau and DMC accept this method. MSVC7.1 needs __int64 instead of long +// m_pthis = reinterpret_cast(reinterpret_cast(function_to_bind)); + } + // ******** EVIL, EVIL CODE! ******* + // This function will be called with an invalid 'this' pointer!! + // We're just returning the 'this' pointer, converted into + // a function pointer! + inline UnvoidStaticFuncPtr GetStaticFunction() const { + // Ensure that there's a compilation failure if function pointers + // and data pointers have different sizes. + // If you get this error, you need to #undef FASTDELEGATE_USESTATICFUNCTIONHACK. + typedef int ERROR_CantUseEvilMethod[sizeof(UnvoidStaticFuncPtr)==sizeof(this) ? 1 : -1]; + return horrible_cast(this); + } +#endif // !defined(FASTDELEGATE_USESTATICFUNCTIONHACK) + + // Does the closure contain this static function? + inline bool IsEqualToStaticFuncPtr(StaticFuncPtr funcptr){ + if (funcptr==0) return empty(); + // For the Evil method, if it doesn't actually contain a static function, this will return an arbitrary + // value that is not equal to any valid function pointer. + else return funcptr==reinterpret_cast(GetStaticFunction()); + } +}; + + +} // namespace detail + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 3: +// +// Wrapper classes to ensure type safety +// +//////////////////////////////////////////////////////////////////////////////// + + +// Once we have the member function conversion templates, it's easy to make the +// wrapper classes. So that they will work with as many compilers as possible, +// the classes are of the form +// FastDelegate3 +// They can cope with any combination of parameters. The max number of parameters +// allowed is 8, but it is trivial to increase this limit. +// Note that we need to treat const member functions seperately. +// All this class does is to enforce type safety, and invoke the delegate with +// the correct list of parameters. + +// Because of the weird rule about the class of derived member function pointers, +// you sometimes need to apply a downcast to the 'this' pointer. +// This is the reason for the use of "implicit_cast(pthis)" in the code below. +// If CDerivedClass is derived from CBaseClass, but doesn't override SimpleVirtualFunction, +// without this trick you'd need to write: +// MyDelegate(static_cast(&d), &CDerivedClass::SimpleVirtualFunction); +// but with the trick you can write +// MyDelegate(&d, &CDerivedClass::SimpleVirtualFunction); + +// RetType is the type the compiler uses in compiling the template. For VC6, +// it cannot be void. DesiredRetType is the real type which is returned from +// all of the functions. It can be void. + +// Implicit conversion to "bool" is achieved using the safe_bool idiom, +// using member data pointers (MDP). This allows "if (dg)..." syntax +// Because some compilers (eg codeplay) don't have a unique value for a zero +// MDP, an extra padding member is added to the SafeBool struct. +// Some compilers (eg VC6) won't implicitly convert from 0 to an MDP, so +// in that case the static function constructor is not made explicit; this +// allows "if (dg==0) ..." to compile. + +//N=0 +template +class FastDelegate0 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(); + typedef RetType (*UnvoidStaticFunctionPtr)(); + typedef RetType (detail::GenericClass::*GenericMemFn)(); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate0 type; + + // Construction and comparison functions + FastDelegate0() { clear(); } + FastDelegate0(const FastDelegate0 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate0 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate0 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate0 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate0 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate0 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate0(Y *pthis, DesiredRetType (X::* function_to_bind)() ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)()) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate0(const Y *pthis, DesiredRetType (X::* function_to_bind)() const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)() const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate0(DesiredRetType (*function_to_bind)() ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)() ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)()) { + m_Closure.bindstaticfunc(this, &FastDelegate0::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() () const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction() const { + return (*(m_Closure.GetStaticFunction()))(); } +}; + +//N=1 +template +class FastDelegate1 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate1 type; + + // Construction and comparison functions + FastDelegate1() { clear(); } + FastDelegate1(const FastDelegate1 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate1 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate1 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate1 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate1 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate1 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate1(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate1(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate1(DesiredRetType (*function_to_bind)(Param1 p1) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1)) { + m_Closure.bindstaticfunc(this, &FastDelegate1::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1) const { + return (*(m_Closure.GetStaticFunction()))(p1); } +}; + +//N=2 +template +class FastDelegate2 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate2 type; + + // Construction and comparison functions + FastDelegate2() { clear(); } + FastDelegate2(const FastDelegate2 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate2 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate2 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate2 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate2 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate2 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate2(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate2(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate2(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2)) { + m_Closure.bindstaticfunc(this, &FastDelegate2::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2); } +}; + +//N=3 +template +class FastDelegate3 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate3 type; + + // Construction and comparison functions + FastDelegate3() { clear(); } + FastDelegate3(const FastDelegate3 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate3 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate3 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate3 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate3 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate3 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate3(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate3(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate3(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3)) { + m_Closure.bindstaticfunc(this, &FastDelegate3::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3); } +}; + +//N=4 +template +class FastDelegate4 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate4 type; + + // Construction and comparison functions + FastDelegate4() { clear(); } + FastDelegate4(const FastDelegate4 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate4 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate4 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate4 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate4 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate4 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate4(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate4(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate4(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { + m_Closure.bindstaticfunc(this, &FastDelegate4::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4); } +}; + +//N=5 +template +class FastDelegate5 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate5 type; + + // Construction and comparison functions + FastDelegate5() { clear(); } + FastDelegate5(const FastDelegate5 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate5 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate5 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate5 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate5 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate5 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate5(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate5(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate5(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { + m_Closure.bindstaticfunc(this, &FastDelegate5::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5); } +}; + +//N=6 +template +class FastDelegate6 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate6 type; + + // Construction and comparison functions + FastDelegate6() { clear(); } + FastDelegate6(const FastDelegate6 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate6 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate6 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate6 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate6 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate6 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate6(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate6(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate6(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { + m_Closure.bindstaticfunc(this, &FastDelegate6::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6); } +}; + +//N=7 +template +class FastDelegate7 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate7 type; + + // Construction and comparison functions + FastDelegate7() { clear(); } + FastDelegate7(const FastDelegate7 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate7 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate7 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate7 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate7 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate7 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate7(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate7(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate7(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { + m_Closure.bindstaticfunc(this, &FastDelegate7::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6, p7); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6, p7); } +}; + +//N=8 +template +class FastDelegate8 { +private: + typedef typename detail::DefaultVoidToVoid::type DesiredRetType; + typedef DesiredRetType (*StaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); + typedef RetType (*UnvoidStaticFunctionPtr)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); + typedef RetType (detail::GenericClass::*GenericMemFn)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8); + typedef detail::ClosurePtr ClosureType; + ClosureType m_Closure; +public: + // Typedefs to aid generic programming + typedef FastDelegate8 type; + + // Construction and comparison functions + FastDelegate8() { clear(); } + FastDelegate8(const FastDelegate8 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + void operator = (const FastDelegate8 &x) { + m_Closure.CopyFrom(this, x.m_Closure); } + bool operator ==(const FastDelegate8 &x) const { + return m_Closure.IsEqual(x.m_Closure); } + bool operator !=(const FastDelegate8 &x) const { + return !m_Closure.IsEqual(x.m_Closure); } + bool operator <(const FastDelegate8 &x) const { + return m_Closure.IsLess(x.m_Closure); } + bool operator >(const FastDelegate8 &x) const { + return x.m_Closure.IsLess(m_Closure); } + // Binding to non-const member functions + template < class X, class Y > + FastDelegate8(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { + m_Closure.bindmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Binding to const member functions. + template < class X, class Y > + FastDelegate8(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + template < class X, class Y > + inline void bind(const Y *pthis, DesiredRetType (X::* function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { + m_Closure.bindconstmemfunc(detail::implicit_cast(pthis), function_to_bind); } + // Static functions. We convert them into a member function call. + // This constructor also provides implicit conversion + FastDelegate8(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { + bind(function_to_bind); } + // for efficiency, prevent creation of a temporary + void operator = (DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) ) { + bind(function_to_bind); } + inline void bind(DesiredRetType (*function_to_bind)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { + m_Closure.bindstaticfunc(this, &FastDelegate8::InvokeStaticFunction, + function_to_bind); } + // Invoke the delegate + RetType operator() (Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const { + return (m_Closure.GetClosureThis()->*(m_Closure.GetClosureMemPtr()))(p1, p2, p3, p4, p5, p6, p7, p8); } + // Implicit conversion to "bool" using the safe_bool idiom +private: + typedef struct SafeBoolStruct { + int a_data_pointer_to_this_is_0_on_buggy_compilers; + StaticFunctionPtr m_nonzero; + } UselessTypedef; + typedef StaticFunctionPtr SafeBoolStruct::*unspecified_bool_type; +public: + operator unspecified_bool_type() const { + return empty()? 0: &SafeBoolStruct::m_nonzero; + } + // necessary to allow ==0 to work despite the safe_bool idiom + inline bool operator==(StaticFunctionPtr funcptr) { + return m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator!=(StaticFunctionPtr funcptr) { + return !m_Closure.IsEqualToStaticFuncPtr(funcptr); } + inline bool operator ! () const { // Is it bound to anything? + return !m_Closure; } + inline bool empty() const { + return !m_Closure; } + void clear() { m_Closure.clear();} + // Conversion to and from the DelegateMemento storage class + const DelegateMemento & GetMemento() { return m_Closure; } + void SetMemento(const DelegateMemento &any) { m_Closure.CopyFrom(this, any); } + +private: // Invoker for static functions + RetType InvokeStaticFunction(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const { + return (*(m_Closure.GetStaticFunction()))(p1, p2, p3, p4, p5, p6, p7, p8); } +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 4: +// +// FastDelegate<> class (Original author: Jody Hagins) +// Allows boost::function style syntax like: +// FastDelegate< double (int, long) > +// instead of: +// FastDelegate2< int, long, double > +// +//////////////////////////////////////////////////////////////////////////////// + +#ifdef FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + +// Declare FastDelegate as a class template. It will be specialized +// later for all number of arguments. +template +class FastDelegate; + +//N=0 +// Specialization to allow use of +// FastDelegate< R ( ) > +// instead of +// FastDelegate0 < R > +template +class FastDelegate< R ( ) > + // Inherit from FastDelegate0 so that it can be treated just like a FastDelegate0 + : public FastDelegate0 < R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate0 < R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=1 +// Specialization to allow use of +// FastDelegate< R ( Param1 ) > +// instead of +// FastDelegate1 < Param1, R > +template +class FastDelegate< R ( Param1 ) > + // Inherit from FastDelegate1 so that it can be treated just like a FastDelegate1 + : public FastDelegate1 < Param1, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate1 < Param1, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=2 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2 ) > +// instead of +// FastDelegate2 < Param1, Param2, R > +template +class FastDelegate< R ( Param1, Param2 ) > + // Inherit from FastDelegate2 so that it can be treated just like a FastDelegate2 + : public FastDelegate2 < Param1, Param2, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate2 < Param1, Param2, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=3 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3 ) > +// instead of +// FastDelegate3 < Param1, Param2, Param3, R > +template +class FastDelegate< R ( Param1, Param2, Param3 ) > + // Inherit from FastDelegate3 so that it can be treated just like a FastDelegate3 + : public FastDelegate3 < Param1, Param2, Param3, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate3 < Param1, Param2, Param3, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=4 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4 ) > +// instead of +// FastDelegate4 < Param1, Param2, Param3, Param4, R > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4 ) > + // Inherit from FastDelegate4 so that it can be treated just like a FastDelegate4 + : public FastDelegate4 < Param1, Param2, Param3, Param4, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate4 < Param1, Param2, Param3, Param4, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=5 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5 ) > +// instead of +// FastDelegate5 < Param1, Param2, Param3, Param4, Param5, R > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5 ) > + // Inherit from FastDelegate5 so that it can be treated just like a FastDelegate5 + : public FastDelegate5 < Param1, Param2, Param3, Param4, Param5, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate5 < Param1, Param2, Param3, Param4, Param5, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=6 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6 ) > +// instead of +// FastDelegate6 < Param1, Param2, Param3, Param4, Param5, Param6, R > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6 ) > + // Inherit from FastDelegate6 so that it can be treated just like a FastDelegate6 + : public FastDelegate6 < Param1, Param2, Param3, Param4, Param5, Param6, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate6 < Param1, Param2, Param3, Param4, Param5, Param6, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=7 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7 ) > +// instead of +// FastDelegate7 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, R > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7 ) > + // Inherit from FastDelegate7 so that it can be treated just like a FastDelegate7 + : public FastDelegate7 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate7 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + +//N=8 +// Specialization to allow use of +// FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 ) > +// instead of +// FastDelegate8 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, R > +template +class FastDelegate< R ( Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8 ) > + // Inherit from FastDelegate8 so that it can be treated just like a FastDelegate8 + : public FastDelegate8 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, R > +{ +public: + // Make using the base type a bit easier via typedef. + typedef FastDelegate8 < Param1, Param2, Param3, Param4, Param5, Param6, Param7, Param8, R > BaseType; + + // Allow users access to the specific type of this delegate. + typedef FastDelegate SelfType; + + // Mimic the base class constructors. + FastDelegate() : BaseType() { } + + template < class X, class Y > + FastDelegate(Y * pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 )) + : BaseType(pthis, function_to_bind) { } + + template < class X, class Y > + FastDelegate(const Y *pthis, + R (X::* function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 ) const) + : BaseType(pthis, function_to_bind) + { } + + FastDelegate(R (*function_to_bind)( Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8 )) + : BaseType(function_to_bind) { } + void operator = (const BaseType &x) { + *static_cast(this) = x; } +}; + + +#endif //FASTDELEGATE_ALLOW_FUNCTION_TYPE_SYNTAX + +//////////////////////////////////////////////////////////////////////////////// +// Fast Delegates, part 5: +// +// MakeDelegate() helper function +// +// MakeDelegate(&x, &X::func) returns a fastdelegate of the type +// necessary for calling x.func() with the correct number of arguments. +// This makes it possible to eliminate many typedefs from user code. +// +//////////////////////////////////////////////////////////////////////////////// + +// Also declare overloads of a MakeDelegate() global function to +// reduce the need for typedefs. +// We need seperate overloads for const and non-const member functions. +// Also, because of the weird rule about the class of derived member function pointers, +// implicit downcasts may need to be applied later to the 'this' pointer. +// That's why two classes (X and Y) appear in the definitions. Y must be implicitly +// castable to X. + +// Workaround for VC6. VC6 needs void return types converted into DefaultVoid. +// GCC 3.2 and later won't compile this unless it's preceded by 'typename', +// but VC6 doesn't allow 'typename' in this context. +// So, I have to use a macro. + +#ifdef FASTDLGT_VC6 +#define FASTDLGT_RETTYPE detail::VoidToDefaultVoid::type +#else +#define FASTDLGT_RETTYPE RetType +#endif + +//N=0 +template +FastDelegate0 MakeDelegate(Y* x, RetType (X::*func)()) { + return FastDelegate0(x, func); +} + +template +FastDelegate0 MakeDelegate(Y* x, RetType (X::*func)() const) { + return FastDelegate0(x, func); +} + +//N=1 +template +FastDelegate1 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1)) { + return FastDelegate1(x, func); +} + +template +FastDelegate1 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1) const) { + return FastDelegate1(x, func); +} + +//N=2 +template +FastDelegate2 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2)) { + return FastDelegate2(x, func); +} + +template +FastDelegate2 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2) const) { + return FastDelegate2(x, func); +} + +//N=3 +template +FastDelegate3 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3)) { + return FastDelegate3(x, func); +} + +template +FastDelegate3 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3) const) { + return FastDelegate3(x, func); +} + +//N=4 +template +FastDelegate4 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4)) { + return FastDelegate4(x, func); +} + +template +FastDelegate4 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4) const) { + return FastDelegate4(x, func); +} + +//N=5 +template +FastDelegate5 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5)) { + return FastDelegate5(x, func); +} + +template +FastDelegate5 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5) const) { + return FastDelegate5(x, func); +} + +//N=6 +template +FastDelegate6 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6)) { + return FastDelegate6(x, func); +} + +template +FastDelegate6 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6) const) { + return FastDelegate6(x, func); +} + +//N=7 +template +FastDelegate7 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7)) { + return FastDelegate7(x, func); +} + +template +FastDelegate7 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7) const) { + return FastDelegate7(x, func); +} + +//N=8 +template +FastDelegate8 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8)) { + return FastDelegate8(x, func); +} + +template +FastDelegate8 MakeDelegate(Y* x, RetType (X::*func)(Param1 p1, Param2 p2, Param3 p3, Param4 p4, Param5 p5, Param6 p6, Param7 p7, Param8 p8) const) { + return FastDelegate8(x, func); +} + + + // clean up after ourselves... +#undef FASTDLGT_RETTYPE + +} // namespace fastdelegate + +#endif // !defined(FASTDELEGATE_H) + diff --git a/core/util/autoPtr.h b/core/util/autoPtr.h new file mode 100644 index 0000000..a4ab011 --- /dev/null +++ b/core/util/autoPtr.h @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _AUTOPTR_H_ +#define _AUTOPTR_H_ + +#ifndef _TYPETRAITS_H_ +# include "platform/typetraits.h" +#endif + + +template +struct AutoPtrRef +{ + T* _ptr; + AutoPtrRef(T *ptr) + : _ptr(ptr) + {} +}; + +/// A simple smart pointer. +/// An extended version of std::auto_ptr which supports a deletion policy. +/// The delete policy indicates how the ptr is to be deleted. DeleteSingle, +/// the default, is used to delete individual objects. DeleteArray can +/// can be used to delete arrays. +/// +/// AutoPtr ptr(new Object); +/// AutoPtr ptr(new Object); +/// AutoPtr ptr(new Object[10]); +/// +/// AutoPtrs do not perform reference counting and assume total ownership +/// of any object assigned to them. Assigning an AutoPtr to another transfers +/// that ownership and resets the source AutoPtr to 0. +template +class AutoPtr +{ +public: + typedef T ValueType; + + explicit AutoPtr(T *ptr = 0): _ptr(ptr) {} + ~AutoPtr() + { + P::destroy(_ptr); + } + + // Copy constructors + AutoPtr(AutoPtr &rhs): _ptr(rhs.release()) {} + + template + AutoPtr(AutoPtr &rhs): _ptr(rhs.release()) { } + + /// Transfer ownership, any object currently be referenced is deleted and + /// rhs is set to 0. + AutoPtr& operator= (AutoPtr &rhs) + { + reset(rhs.release()); + return *this; + } + + template + AutoPtr& operator= (AutoPtr &rhs) + { + reset(rhs.release()); + return *this; + } + + // Access + T* ptr() const { return _ptr; } + T& operator*() const { return *_ptr; } + T* operator->() const { return _ptr; } + T& operator[](size_t index) { return (_ptr)[index]; } + + /// Release ownership of the object without deleting it. + T* release() + { + T* tmp(_ptr); + _ptr = 0; + return tmp; + } + + /// Equivalent to *this = (T*)ptr, except that operator=(T*) isn't provided for. + void reset(T* ptr = 0) + { + if (_ptr != ptr) + { + P::destroy(_ptr); + _ptr = ptr; + } + } + + // Conversion to/from ref type + AutoPtr(AutoPtrRef ref): _ptr(ref._ptr) {} + AutoPtr& operator= (AutoPtrRef ref) + { + reset(ref._ptr); + return *this; + } + + bool isNull() const { return _ptr == NULL; } + bool isValid() const { return !isNull(); } + + template + operator AutoPtrRef() { return AutoPtrRef(release()); } + + template + operator AutoPtr() { return AutoPtr(release()); } + +private: + T *_ptr; +}; + +#endif diff --git a/core/util/byteBuffer.cpp b/core/util/byteBuffer.cpp new file mode 100644 index 0000000..9c69679 --- /dev/null +++ b/core/util/byteBuffer.cpp @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/byteBuffer.h" + + +namespace Torque +{ + +class PrivateBBData +{ +public: + PrivateBBData() + : refCount( 1 ), + dataSize( 0 ), + data( NULL ) + { + } + + U32 refCount; ///< Reference count + U32 dataSize; ///< Length of buffer + U8 *data; ///< Our data buffer +}; + +//-------------------------------------- + +ByteBuffer::ByteBuffer() +{ + _data = new PrivateBBData; + _data->dataSize = 0; + _data->data = NULL; +} + +ByteBuffer::ByteBuffer(U8 *dataPtr, U32 bufferSize) +{ + _data = new PrivateBBData; + _data->dataSize = bufferSize; + _data->data = new U8[bufferSize]; + + dMemcpy( _data->data, dataPtr, bufferSize ); +} + +ByteBuffer::ByteBuffer(U32 bufferSize) +{ + _data = new PrivateBBData; + _data->dataSize = bufferSize; + _data->data = new U8[bufferSize]; +} + +ByteBuffer::ByteBuffer(const ByteBuffer &theBuffer) +{ + _data = theBuffer._data; + _data->refCount++; +} + +ByteBuffer &ByteBuffer::operator=(const ByteBuffer &theBuffer) +{ + _data = theBuffer._data; + _data->refCount++; + + return *this; +} + +ByteBuffer::~ByteBuffer() +{ + if (!--_data->refCount) + { + delete [] _data->data; + delete _data; + + _data = NULL; + } +} + +void ByteBuffer::setBuffer(U8 *dataPtr, U32 bufferSize, bool copyData) +{ + U8 *newData = dataPtr; + + if ( copyData ) + { + newData = new U8[bufferSize]; + + dMemcpy( newData, dataPtr, bufferSize ); + } + + delete [] _data->data; + + _data->data = newData; + _data->dataSize = bufferSize; +} + +void ByteBuffer::resize(U32 newBufferSize) +{ + U8 *newData = new U8[newBufferSize]; + + U32 copyLen = getMin( newBufferSize, _data->dataSize ); + + dMemcpy( newData, _data->data, copyLen ); + + delete [] _data->data; + + _data->data = newData; + _data->dataSize = newBufferSize; +} + +void ByteBuffer::appendBuffer(const U8 *dataBuffer, U32 bufferSize) +{ + U32 start = _data->dataSize; + resize(start + bufferSize); + dMemcpy(_data->data + start, dataBuffer, bufferSize); +} + +U32 ByteBuffer::getBufferSize() const +{ + return _data->dataSize; +} + +U8 *ByteBuffer::getBuffer() +{ + return _data->data; +} + +const U8 *ByteBuffer::getBuffer() const +{ + return _data->data; +} + +ByteBuffer ByteBuffer::getCopy() const +{ + return ByteBuffer( _data->data, _data->dataSize ); +} + +void ByteBuffer::clear() +{ + dMemset(_data->data, 0, _data->dataSize); +} + + +} diff --git a/core/util/byteBuffer.h b/core/util/byteBuffer.h new file mode 100644 index 0000000..853cd0b --- /dev/null +++ b/core/util/byteBuffer.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BYTEBUFFER_H_ +#define _BYTEBUFFER_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +namespace Torque +{ + +class PrivateBBData; + +class ByteBuffer +{ +public: + ByteBuffer(); + + /// Create a ByteBuffer from a chunk of memory. + ByteBuffer(U8 *dataPtr, U32 bufferSize); + + /// Create a ByteBuffer of the specified size. + ByteBuffer(U32 bufferSize); + + /// Copy constructor + ByteBuffer(const ByteBuffer &theBuffer); + + ByteBuffer &operator=(const ByteBuffer &theBuffer); + + ~ByteBuffer(); + + /// Set the ByteBuffer to point to a new chunk of memory. + void setBuffer(U8 *dataPtr, U32 bufferSize, bool copyData); + + /// Resize the buffer. + void resize(U32 newBufferSize); + + /// Appends the specified buffer to the end of the byte buffer. + void appendBuffer(const U8 *dataBuffer, U32 bufferSize); + + /// Appends the specified ByteBuffer to the end of this byte buffer. + void appendBuffer(const ByteBuffer &theBuffer) + { + appendBuffer(theBuffer.getBuffer(), theBuffer.getBufferSize()); + } + + U32 getBufferSize() const; + + U8 *getBuffer(); + const U8 *getBuffer() const; + + /// Copy the data in the buffer. + ByteBuffer getCopy() const; + + /// Clear the buffer. + void clear(); + +private: + PrivateBBData *_data; +}; + +} + +#endif diff --git a/core/util/byteswap.h b/core/util/byteswap.h new file mode 100644 index 0000000..de1c24a --- /dev/null +++ b/core/util/byteswap.h @@ -0,0 +1,8 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef BYTESWAP +#define BYTESWAP(x, y) x = x ^ y; y = x ^ y; x = x ^y; +#endif //defined(BYTESWAP) \ No newline at end of file diff --git a/core/util/commonSwizzles.cpp b/core/util/commonSwizzles.cpp new file mode 100644 index 0000000..33b24ee --- /dev/null +++ b/core/util/commonSwizzles.cpp @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/swizzle.h" + +namespace Swizzles +{ + dsize_t _bgra[] = { 2, 1, 0, 3 }; + dsize_t _bgr[] = { 2, 1, 0 }; + dsize_t _rgb[] = { 0, 1, 2 }; + dsize_t _argb[] = { 3, 0, 1, 2 }; + dsize_t _rgba[] = { 0, 1, 2, 3 }; + dsize_t _abgr[] = { 3, 2, 1, 0 }; + + Swizzle bgra( _bgra ); + Swizzle bgr( _bgr ); + Swizzle rgb( _rgb ); + Swizzle argb( _argb ); + Swizzle rgba( _rgba ); + Swizzle abgr( _abgr ); + + NullSwizzle null; +} \ No newline at end of file diff --git a/core/util/delegate.h b/core/util/delegate.h new file mode 100644 index 0000000..eb49539 --- /dev/null +++ b/core/util/delegate.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _UTIL_DELEGATE_H_ +#define _UTIL_DELEGATE_H_ + +#include "core/util/FastDelegate.h" + +/// Define a Delegate +/// +/// This macro allows translation of Delegate definitions into a target +/// delegate implementation. +/// +/// By default Torque Juggernaut uses a @see FastDelegate implementation +/// + +#define Delegate fastdelegate::FastDelegate +typedef fastdelegate::DelegateMemento DelegateMemento; + +template +class DelegateRemapper : public DelegateMemento +{ +public: + DelegateRemapper() : mOffset(0) {} + + void set(T * t, const DelegateMemento & memento) + { + SetMementoFrom(memento); + if (m_pthis) + mOffset = ((int)m_pthis) - ((int)t); + } + + void rethis(T * t) + { + if (m_pthis) + m_pthis = (fastdelegate::detail::GenericClass *)(mOffset + (int)t); + } + +protected: + int mOffset; +}; + +#endif \ No newline at end of file diff --git a/core/util/dxt5nmSwizzle.h b/core/util/dxt5nmSwizzle.h new file mode 100644 index 0000000..31ebfe5 --- /dev/null +++ b/core/util/dxt5nmSwizzle.h @@ -0,0 +1,86 @@ +#ifndef _DXT5nm_SWIZZLE_H_ +#define _DXT5nm_SWIZZLE_H_ + +#include "core/util/swizzle.h" +#include "core/util/byteswap.h" + +class DXT5nmSwizzle : public Swizzle +{ +public: + DXT5nmSwizzle() : Swizzle( NULL ) {}; + + virtual void InPlace( void *memory, const dsize_t size ) const + { + AssertFatal( size % 4 == 0, "Bad buffer size for DXT5nm Swizzle" ); + + volatile U8 *u8Mem = reinterpret_cast( memory ); + + for( int i = 0; i < size >> 2; i++ ) + { + // g = garbage byte + // Input: [X|Y|Z|g] (rgba) + // Output: [g|Y|0xFF|X] (bgra) + BYTESWAP( u8Mem[0], u8Mem[3] ); // Store X in Alpha + *u8Mem ^= *u8Mem; // 0 the garbage bit + u8Mem[2] |= 0xFF; // Set Red to 1.0 + + u8Mem += 4; + } + } + + virtual void ToBuffer( void *destination, const void *source, const dsize_t size ) const + { + AssertFatal( size % 4 == 0, "Bad buffer size for DXT5nm Swizzle" ); + + volatile const U8 *srcU8 = reinterpret_cast( source ); + volatile U8 *dstU8 = reinterpret_cast( destination ); + + for( int i = 0; i < size >> 2; i++ ) + { + // g = garbage byte + // Input: [X|Y|Z|g] (rgba) + // Output: [g|Y|0xFF|X] (bgra) + *dstU8++ = 0; // 0 garbage bit + *dstU8++ = srcU8[1]; // Copy Y into G + *dstU8++ |= 0xFF; // Set Red to 1.0 + *dstU8++ = srcU8[0]; // Copy X into Alpha + + srcU8 += 4; + } + } +}; + +class DXT5nmSwizzleUp24t32 : public Swizzle +{ +public: + DXT5nmSwizzleUp24t32() : Swizzle( NULL ) {}; + + virtual void InPlace( void *memory, const dsize_t size ) const + { + AssertISV( false, "Cannot swizzle in place a 24->32 bit swizzle." ); + } + + virtual void ToBuffer( void *destination, const void *source, const dsize_t size ) const + { + AssertFatal( size % 3 == 0, "Bad buffer size for DXT5nm Swizzle" ); + const int pixels = size / 3; + + volatile const U8 *srcU8 = reinterpret_cast( source ); + volatile U8 *dstU8 = reinterpret_cast( destination ); + + // destination better damn well be the right size + for( int i = 0; i < pixels; i++ ) + { + // g = garbage byte + // Input: [X|Y|Z|g] (rgba) + // Output: [g|Y|0xFF|X] (bgra) + *dstU8++ = 0; // 0 garbage bit + *dstU8++ = srcU8[1]; // Copy Y into G + *dstU8++ |= 0xFF; // Set Red to 1.0 + *dstU8++ = srcU8[0]; // Copy X into Alpha + + srcU8 += 3; + } + } +}; +#endif diff --git a/core/util/endian.h b/core/util/endian.h new file mode 100644 index 0000000..c030049 --- /dev/null +++ b/core/util/endian.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ENDIAN_H_ +#define _ENDIAN_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +//------------------------------------------------------------------------------ +// Endian conversions + +inline U8 endianSwap(const U8 in_swap) +{ + return in_swap; +} + +inline S8 endianSwap(const S8 in_swap) +{ + return in_swap; +} + +/** +Convert the byte ordering on the U16 to and from big/little endian format. +@param in_swap Any U16 +@returns swapped U16. +*/ + +inline U16 endianSwap(const U16 in_swap) +{ + return U16(((in_swap >> 8) & 0x00ff) | + ((in_swap << 8) & 0xff00)); +} + +inline S16 endianSwap(const S16 in_swap) +{ + return S16(endianSwap(U16(in_swap))); +} + +/** +Convert the byte ordering on the U32 to and from big/little endian format. +@param in_swap Any U32 +@returns swapped U32. +*/ +inline U32 endianSwap(const U32 in_swap) +{ + return U32(((in_swap >> 24) & 0x000000ff) | + ((in_swap >> 8) & 0x0000ff00) | + ((in_swap << 8) & 0x00ff0000) | + ((in_swap << 24) & 0xff000000)); +} + +inline S32 endianSwap(const S32 in_swap) +{ + return S32(endianSwap(U32(in_swap))); +} + +inline U64 endianSwap(const U64 in_swap) +{ + U32 *inp = (U32 *) &in_swap; + U64 ret; + U32 *outp = (U32 *) &ret; + outp[0] = endianSwap(inp[1]); + outp[1] = endianSwap(inp[0]); + return ret; +} + +inline S64 endianSwap(const S64 in_swap) +{ + return S64(endianSwap(U64(in_swap))); +} + +inline F32 endianSwap(const F32 in_swap) +{ + U32 result = endianSwap(* ((U32 *) &in_swap) ); + return * ((F32 *) &result); +} + +inline F64 endianSwap(const F64 in_swap) +{ + U64 result = endianSwap(* ((U64 *) &in_swap) ); + return * ((F64 *) &result); +} + +//------------------------------------------------------------------------------ +// Endian conversions + +#ifdef TORQUE_LITTLE_ENDIAN + +#define TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(type) \ + inline type convertHostToLEndian(type i) { return i; } \ + inline type convertLEndianToHost(type i) { return i; } \ + inline type convertHostToBEndian(type i) { return endianSwap(i); } \ + inline type convertBEndianToHost(type i) { return endianSwap(i); } + +#elif defined(TORQUE_BIG_ENDIAN) + +#define TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(type) \ + inline type convertHostToLEndian(type i) { return endianSwap(i); } \ + inline type convertLEndianToHost(type i) { return endianSwap(i); } \ + inline type convertHostToBEndian(type i) { return i; } \ + inline type convertBEndianToHost(type i) { return i; } + +#else +#error "Endian define not set!" +#endif + + +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(U8) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(S8) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(U16) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(S16) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(U32) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(S32) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(U64) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(S64) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(F32) +TORQUE_DECLARE_TEMPLATIZED_ENDIAN_CONV(F64) + +#endif + diff --git a/core/util/fourcc.h b/core/util/fourcc.h new file mode 100644 index 0000000..f2d8c35 --- /dev/null +++ b/core/util/fourcc.h @@ -0,0 +1,10 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef MakeFourCC +#define MakeFourCC(ch0, ch1, ch2, ch3) \ + ((U32)(U8)(ch0) | ((U32)(U8)(ch1) << 8) | \ + ((U32)(U8)(ch2) << 16) | ((U32)(U8)(ch3) << 24 )) +#endif //defined(MakeFourCC) diff --git a/core/util/hashFunction.cpp b/core/util/hashFunction.cpp new file mode 100644 index 0000000..fdf4f0a --- /dev/null +++ b/core/util/hashFunction.cpp @@ -0,0 +1,254 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// Borrowed from: http://burtleburtle.net/bob/hash/doobs.html +// +// Original code by: +// +// By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this +// code any way you wish, private, educational, or commercial. It's free. + +#include "platform/platform.h" + +#include "core/util/hashFunction.h" + +namespace Torque +{ + +#define hashsize(n) ((U32)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +/* +-------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +For every delta with one or two bits set, and the deltas of all three +high bits or all three low bits, whether the original value of a,b,c +is almost all zero or is uniformly distributed, +* If mix() is run forward or backward, at least 32 bits in a,b,c +have at least 1/4 probability of changing. +* If mix() is run forward, every bit of c will change between 1/3 and +2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) +mix() was built out of 36 single-cycle latency instructions in a +structure that could supported 2x parallelism, like so: +a -= b; +a -= c; x = (c>>13); +b -= c; a ^= x; +b -= a; x = (a<<8); +c -= a; b ^= x; +c -= b; x = (b>>13); +... +Unfortunately, superscalar Pentiums and Sparcs can't take advantage +of that parallelism. They've also turned some of those single-cycle +latency instructions into multi-cycle latency instructions. Still, +this is the fastest good hash I could find. There were about 2^^68 +to choose from. I only looked at a billion or so. +-------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +/* +-------------------------------------------------------------------- +hash() -- hash a variable-length key into a 32-bit value +k : the key (the unaligned variable-length array of bytes) +len : the length of the key, counting by bytes +initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Every 1-bit and 2-bit delta achieves avalanche. +About 6*len+35 instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do +h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (U8 **)k, do it like this: +for (i=0, h=0; i= 12) + { + a += (k[0] +((U32)k[1]<<8) +((U32)k[2]<<16) +((U32)k[3]<<24)); + b += (k[4] +((U32)k[5]<<8) +((U32)k[6]<<16) +((U32)k[7]<<24)); + c += (k[8] +((U32)k[9]<<8) +((U32)k[10]<<16)+((U32)k[11]<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c+=((U32)k[10]<<24); + case 10: c+=((U32)k[9]<<16); + case 9 : c+=((U32)k[8]<<8); + /* the first byte of c is reserved for the length */ + case 8 : b+=((U32)k[7]<<24); + case 7 : b+=((U32)k[6]<<16); + case 6 : b+=((U32)k[5]<<8); + case 5 : b+=k[4]; + case 4 : a+=((U32)k[3]<<24); + case 3 : a+=((U32)k[2]<<16); + case 2 : a+=((U32)k[1]<<8); + case 1 : a+=k[0]; + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + + +/* +-------------------------------------------------------------------- +mix -- mix 3 64-bit values reversibly. +mix() takes 48 machine instructions, but only 24 cycles on a superscalar + machine (like Intel's new MMX architecture). It requires 4 64-bit + registers for 4::2 parallelism. +All 1-bit deltas, all 2-bit deltas, all deltas composed of top bits of + (a,b,c), and all deltas of bottom bits were tested. All deltas were + tested both on random keys and on keys that were nearly all zero. + These deltas all cause every bit of c to change between 1/3 and 2/3 + of the time (well, only 113/400 to 287/400 of the time for some + 2-bit delta). These deltas all cause at least 80 bits to change + among (a,b,c) when the mix is run either forward or backward (yes it + is reversible). +This implies that a hash using mix64 has no funnels. There may be + characteristics with 3-bit deltas or bigger, I didn't test for + those. +-------------------------------------------------------------------- +*/ +#define mix64(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>43); \ + b -= c; b -= a; b ^= (a<<9); \ + c -= a; c -= b; c ^= (b>>8); \ + a -= b; a -= c; a ^= (c>>38); \ + b -= c; b -= a; b ^= (a<<23); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>35); \ + b -= c; b -= a; b ^= (a<<49); \ + c -= a; c -= b; c ^= (b>>11); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<18); \ + c -= a; c -= b; c ^= (b>>22); \ +} + +/* +-------------------------------------------------------------------- +hash64() -- hash a variable-length key into a 64-bit value + k : the key (the unaligned variable-length array of bytes) + len : the length of the key, counting by bytes + level : can be any 8-byte value +Returns a 64-bit value. Every bit of the key affects every bit of +the return value. No funnels. Every 1-bit and 2-bit delta achieves +avalanche. About 41+5len instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 64 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (ub1 **)k, do it like this: + for (i=0, h=0; i= 24) + { + a += (k[0] +((U64)k[ 1]<< 8)+((U64)k[ 2]<<16)+((U64)k[ 3]<<24) + +((U64)k[4 ]<<32)+((U64)k[ 5]<<40)+((U64)k[ 6]<<48)+((U64)k[ 7]<<56)); + b += (k[8] +((U64)k[ 9]<< 8)+((U64)k[10]<<16)+((U64)k[11]<<24) + +((U64)k[12]<<32)+((U64)k[13]<<40)+((U64)k[14]<<48)+((U64)k[15]<<56)); + c += (k[16] +((U64)k[17]<< 8)+((U64)k[18]<<16)+((U64)k[19]<<24) + +((U64)k[20]<<32)+((U64)k[21]<<40)+((U64)k[22]<<48)+((U64)k[23]<<56)); + mix64(a,b,c); + k += 24; len -= 24; + } + + /*------------------------------------- handle the last 23 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 23: c+=((U64)k[22]<<56); + case 22: c+=((U64)k[21]<<48); + case 21: c+=((U64)k[20]<<40); + case 20: c+=((U64)k[19]<<32); + case 19: c+=((U64)k[18]<<24); + case 18: c+=((U64)k[17]<<16); + case 17: c+=((U64)k[16]<<8); + /* the first byte of c is reserved for the length */ + case 16: b+=((U64)k[15]<<56); + case 15: b+=((U64)k[14]<<48); + case 14: b+=((U64)k[13]<<40); + case 13: b+=((U64)k[12]<<32); + case 12: b+=((U64)k[11]<<24); + case 11: b+=((U64)k[10]<<16); + case 10: b+=((U64)k[ 9]<<8); + case 9: b+=((U64)k[ 8]); + case 8: a+=((U64)k[ 7]<<56); + case 7: a+=((U64)k[ 6]<<48); + case 6: a+=((U64)k[ 5]<<40); + case 5: a+=((U64)k[ 4]<<32); + case 4: a+=((U64)k[ 3]<<24); + case 3: a+=((U64)k[ 2]<<16); + case 2: a+=((U64)k[ 1]<<8); + case 1: a+=((U64)k[ 0]); + /* case 0: nothing left to add */ + } + mix64(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + +} // namespace diff --git a/core/util/hashFunction.h b/core/util/hashFunction.h new file mode 100644 index 0000000..74e75b6 --- /dev/null +++ b/core/util/hashFunction.h @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _HASHFUNCTION_H_ +#define _HASHFUNCTION_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +namespace Torque +{ + +extern U32 hash(register const U8 *k, register U32 length, register U32 initval); + +extern U64 hash64(register const U8 *k, register U32 length, register U64 initval); + +} + +#endif // _HASHFUNCTION_H_ diff --git a/core/util/journal/journal.cpp b/core/util/journal/journal.cpp new file mode 100644 index 0000000..f215bed --- /dev/null +++ b/core/util/journal/journal.cpp @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "core/stream/fileStream.h" +#include "core/util/journal/journal.h" +#include "core/util/safeDelete.h" +#include "console/console.h" + +//----------------------------------------------------------------------------- + +Journal::FuncDecl* Journal::_FunctionList; +Stream *Journal::mFile; +Journal::Mode Journal::_State; +U32 Journal::_Count; +bool Journal::_Dispatching = false; + +Journal Journal::smInstance; + +//----------------------------------------------------------------------------- +Journal::~Journal() +{ + if( mFile ) + Stop(); +} + +//----------------------------------------------------------------------------- + +Journal::Functor* Journal::_create(Id id) +{ + for (FuncDecl* ptr = _FunctionList; ptr; ptr = ptr->next) + if (ptr->id == id) + return ptr->create(); + return 0; +} + +Journal::Id Journal::_getFunctionId(VoidPtr ptr,VoidMethod method) +{ + for (FuncDecl* itr = _FunctionList; itr; itr = itr->next) + if (itr->match(ptr,method)) + return itr->id; + return 0; +} + +void Journal::_removeFunctionId(VoidPtr ptr,VoidMethod method) +{ + FuncDecl ** itr = &_FunctionList; + + do + { + if((*itr)->match(ptr, method)) + { + // Unlink and break. + FuncDecl* decl = *itr; + idPool().free( decl->id ); + *itr = (*itr)->next; + delete decl; + return; + } + + // Advance to next... + itr = &((*itr)->next); + } + while(*itr); +} + +void Journal::_start() +{ +} + +void Journal::_finish() +{ + if (_State == PlayState) + --_Count; + else { + U32 pos = mFile->getPosition(); + mFile->setPosition(0); + mFile->write(++_Count); + mFile->setPosition(pos); + } +} + +void Journal::Record(const char * file) +{ + if (_State == StopState) + { + _Count = 0; + mFile = new FileStream(); + + if( ((FileStream*)mFile)->open(file, Torque::FS::File::Write) ) + { + mFile->write(_Count); + _State = RecordState; + } + else + { + AssertWarn(false,"Journal: Could not create journal file"); + Con::errorf("Journal: Could not create journal file '%s'", file); + } + } +} + +void Journal::Play(const char * file) +{ + if (_State == StopState) + { + SAFE_DELETE(mFile); + mFile = new FileStream(); + if( ((FileStream*)mFile)->open(file, Torque::FS::File::Read) ) + { + mFile->read(&_Count); + _State = PlayState; + } + else + { + AssertWarn(false,"Journal: Could not open journal file"); + Con::errorf("Journal: Could not open journal file '%s'", file); + } + } +} + +void Journal::Stop() +{ + AssertFatal(mFile, "Journal::Stop - no file stream open!"); + + SAFE_DELETE( mFile ); + _State = StopState; +} + +bool Journal::PlayNext() +{ + if (_State == PlayState) { + _start(); + Id id; + + mFile->read(&id); + + Functor* jrn = _create(id); + AssertFatal(jrn,"Journal: Undefined function found in journal"); + jrn->read(mFile); + _finish(); + + _Dispatching = true; + jrn->dispatch(); + _Dispatching = false; + + delete jrn; + if (_Count) + return true; + Stop(); + + //debugBreak(); + } + return false; +} \ No newline at end of file diff --git a/core/util/journal/journal.h b/core/util/journal/journal.h new file mode 100644 index 0000000..e39a1e2 --- /dev/null +++ b/core/util/journal/journal.h @@ -0,0 +1,617 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _UTIL_JOURNAL_JOURNAL_H_ +#define _UTIL_JOURNAL_JOURNAL_H_ + +#include "platform/platform.h" +#include "core/stream/stream.h" +#include "util/returnType.h" +#include "core/stream/ioHelper.h" +#include "core/idGenerator.h" + +/// Journaling System. +/// +/// The journaling system is used to record external events and +/// non-deterministic values from an execution run in order that the +/// run may be re-played for debugging purposes. The journaling +/// system is integrated into the platform library, though not all +/// platform calls are journaled. +/// +/// File system calls are not journaled, so any modified files must +/// be reset to their original state before playback. Only a single +/// journal can be recored or played back at a time. +/// +/// For the journals to play back correctly, journal events cannot +/// be triggered during the processing of another event. +class Journal +{ + Journal() {} + ~Journal(); + + static Journal smInstance; + + typedef U32 Id; + typedef void* VoidPtr; + typedef void (Journal::*VoidMethod)(); + + /// Functor base classes + struct Functor + { + Functor() {} + virtual ~Functor() {} + virtual void read(Stream *s) = 0; + virtual void dispatch() = 0; + }; + + /// Multiple argument function functor specialization + template + struct FunctorDecl: public Functor { + typedef void(*FuncPtr)(); + FuncPtr ptr; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) {} + void dispatch() { (*ptr)(); } + }; + + template + struct FunctorDecl< void(*)(A,B,C,D,E,F,G,H,I,J) >: public Functor { + typedef void(*FuncPtr)(A,B,C,D,E,F,G,H,I,J); + FuncPtr ptr; A a; B b; C c; D d; E e; F f; G g; H h; I i; J j; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g,h,i,j); } + void dispatch() { (*ptr)(a,b,c,d,e,f,g,h,i,j); } + }; + + template + struct FunctorDecl< void(*)(A,B,C,D,E,F,G,H,I) >: public Functor { + typedef void(*FuncPtr)(A,B,C,D,E,F,G,H,I); + FuncPtr ptr; A a; B b; C c; D d; E e; F f; G g; H h; I i; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g,h,i); } + void dispatch() { (*ptr)(a,b,c,d,e,f,g,h,i); } + }; + + template + struct FunctorDecl< void(*)(A,B,C,D,E,F,G,H) >: public Functor { + typedef void(*FuncPtr)(A,B,C,D,E,F,G,H); + FuncPtr ptr; A a; B b; C c; D d; E e; F f; G g; H h; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g,h); } + void dispatch() { (*ptr)(a,b,c,d,e,f,g,h); } + }; + + template + struct FunctorDecl< void(*)(A,B,C,D,E,F,G) >: public Functor { + typedef void(*FuncPtr)(A,B,C,D,E,F,G); + FuncPtr ptr; A a; B b; C c; D d; E e; F f; G g; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g); } + void dispatch() { (*ptr)(a,b,c,d,e,f,g); } + }; + + template + struct FunctorDecl< void(*)(A,B,C,D,E,F) >: public Functor { + typedef void(*FuncPtr)(A,B,C,D,E,F); + FuncPtr ptr; A a; B b; C c; D d; E e; F f; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f); } + void dispatch() { (*ptr)(a,b,c,d,e,f); } + }; + + template + struct FunctorDecl< void(*)(A,B,C,D,E) >: public Functor { + typedef void(*FuncPtr)(A,B,C,D,E); + FuncPtr ptr; A a; B b; C c; D d; E e; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e); } + void dispatch() { (*ptr)(a,b,c,d,e); } + }; + + template + struct FunctorDecl< void(*)(A,B,C,D) >: public Functor { + typedef void(*FuncPtr)(A,B,C,D); + FuncPtr ptr; A a; B b; C c; D d; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d); } + void dispatch() { (*ptr)(a,b,c,d); } + }; + + template + struct FunctorDecl< void(*)(A,B,C) >: public Functor { + typedef void(*FuncPtr)(A,B,C); + FuncPtr ptr; A a; B b; C c; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c); } + void dispatch() { (*ptr)(a,b,c); } + }; + + template + struct FunctorDecl< void(*)(A,B) >: public Functor { + typedef void(*FuncPtr)(A,B); + FuncPtr ptr; A a; B b; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b); } + void dispatch() { (*ptr)(a,b); } + }; + + template + struct FunctorDecl< void(*)(A) >: public Functor { + typedef void(*FuncPtr)(A); + FuncPtr ptr; A a; + FunctorDecl(FuncPtr p): ptr(p) {} + void read(Stream *file) { IOHelper::reads(file,a); } + void dispatch() { (*ptr)(a); } + }; + + // Multiple argument object member function functor specialization + template + struct MethodDecl: public Functor { + typedef T ObjPtr; + typedef U MethodPtr; + ObjPtr obj; MethodPtr method; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) {} + void dispatch() { (obj->*method)(); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C,D,E,F,G,H,I,J); + ObjPtr obj; MethodPtr method; A a; B b; C c; D d; E e; F f; G g; H h; I i; J j; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g,h,i,j); } + void dispatch() { (obj->*method)(a,b,c,d,e,f,g,h,i,j); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C,D,E,F,G,H,I); + ObjPtr obj; MethodPtr method; A a; B b; C c; D d; E e; F f; G g; H h; I i; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g,h,i); } + void dispatch() { (obj->*method)(a,b,c,d,e,f,g,h,i); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C,D,E,F,G,H); + ObjPtr obj; MethodPtr method; A a; B b; C c; D d; E e; F f; G g; H h; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g,h); } + void dispatch() { (obj->*method)(a,b,c,d,e,f,g,h); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C,D,E,F,G); + ObjPtr obj; MethodPtr method; A a; B b; C c; D d; E e; F f; G g; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f,g); } + void dispatch() { (obj->*method)(a,b,c,d,e,f,g); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C,D,E,F); + ObjPtr obj; MethodPtr method; A a; B b; C c; D d; E e; F f; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e,f); } + void dispatch() { (obj->*method)(a,b,c,d,e,f); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C,D,E); + ObjPtr obj; MethodPtr method; A a; B b; C c; D d; E e; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d,e); } + void dispatch() { (obj->*method)(a,b,c,d,e); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C,D); + ObjPtr obj; MethodPtr method; A a; B b; C c; D d; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c,d); } + void dispatch() { (obj->*method)(a,b,c,d); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B,C); + ObjPtr obj; MethodPtr method; A a; B b; C c; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b,c); } + void dispatch() { (obj->*method)(a,b,c); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A,B); + ObjPtr obj; MethodPtr method; A a; B b; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a,b); } + void dispatch() { (obj->*method)(a,b); } + }; + + template + struct MethodDecl: public Functor { + typedef T* ObjPtr; + typedef void(T::*MethodPtr)(A); + ObjPtr obj; MethodPtr method; A a; + MethodDecl(ObjPtr o,MethodPtr p): obj(o), method(p) {} + void read(Stream *file) { IOHelper::reads(file,a); } + void dispatch() { (obj->*method)(a); } + }; + + // Function declarations + struct FuncDecl { + FuncDecl* next; + Id id; + virtual ~FuncDecl() {} + virtual bool match(VoidPtr,VoidMethod) const = 0; + virtual Functor* create() const = 0; + }; + + template + struct FuncRep: public FuncDecl { + typename T::FuncPtr function; + virtual bool match(VoidPtr ptr,VoidMethod) const { + return function == (typename T::FuncPtr)ptr; + } + T* create() const { return new T(function); }; + }; + + template + struct MethodRep: public FuncDecl { + typename T::ObjPtr obj; + typename T::MethodPtr method; + virtual bool match(VoidPtr ptr,VoidMethod func) const { + return obj == (typename T::ObjPtr)ptr && method == (typename T::MethodPtr)func; + } + T* create() const { return new T(obj,method); }; + }; + + static FuncDecl* _FunctionList; + + static inline IdGenerator &idPool() + { + static IdGenerator _IdPool(1, 65535); + return _IdPool; + } + + static U32 _Count; + static Stream *mFile; + static enum Mode { + StopState, PlayState, RecordState + } _State; + static bool _Dispatching; + + static Functor* _create(Id id); + static void _start(); + static void _finish(); + static Id _getFunctionId(VoidPtr ptr,VoidMethod method); + static void _removeFunctionId(VoidPtr ptr,VoidMethod method); + +public: + static void Record(const char * file); + static void Play(const char * file); + static bool PlayNext(); + static void Stop(); + + /// Returns true if in recording mode. + static inline bool IsRecording() { + return _State == RecordState; + } + + /// Returns true if in play mode. + static inline bool IsPlaying() { + return _State == PlayState; + } + + /// Returns true if a function is being dispatched + static inline bool IsDispatching() { + return _Dispatching; + } + + template + static void Read(T *v) + { + AssertFatal(IsPlaying(), "Journal::Read - not playing right now."); + bool r = mFile->read(v); + AssertFatal(r, "Journal::Read - failed to read!"); + } + + static bool Read(U32 size, void *buffer) + { + AssertFatal(IsPlaying(), "Journal::Read - not playing right now."); + bool r = mFile->read(size, buffer); + AssertFatal(r, "Journal::Read - failed to read!"); + return r; + } + + static void ReadString(char str[256]) + { + AssertFatal(IsPlaying(), "Journal::ReadString - not playing right now."); + mFile->readString(str); + } + + template + static void Write(const T &v) + { + AssertFatal(IsRecording(), "Journal::Write - not recording right now."); + bool r = mFile->write(v); + AssertFatal(r, "Journal::Write - failed to write!"); + } + + static bool Write(U32 size, void *buffer) + { + AssertFatal(IsRecording(), "Journal::Write - not recording right now."); + bool r = mFile->write(size, buffer); + AssertFatal(r, "Journal::Write - failed to write!"); + return r; + } + + static void WriteString(const char str[256]) + { + AssertFatal(IsRecording(), "Journal::WriteString - not recording right now."); + mFile->writeString(str); + } + + /// Register a function with the journalling system. + template + static void DeclareFunction(T func) { + if (!_getFunctionId((VoidPtr)func,0)) { + FuncRep >* decl = new FuncRep >; + decl->function = func; + decl->id = idPool().alloc(); + decl->next = _FunctionList; + _FunctionList = decl; + } + } + + template + static void DeclareFunction(T obj, U method) + { + if (!_getFunctionId((VoidPtr)obj,(VoidMethod)method)) { + MethodRep >* decl = new MethodRep >; + decl->obj = obj; + decl->method = method; + decl->id = idPool().alloc(); + decl->next = _FunctionList; + _FunctionList = decl; + } + } + + template + static void RemoveFunction(T obj, U method) + { + _removeFunctionId((VoidPtr)obj,(VoidMethod)method); + } + + /// Journal a function's return value. The return value of the + /// function is stored into the journal and retrieved during + /// playback. During playback the function is not executed. + #define Method(Func,Arg1,Arg2) \ + static typename ReturnType::ValueType Result Arg1 { \ + typename ReturnType::ValueType value; \ + if (_Dispatching) \ + return; \ + if (_State == PlayState) { \ + _start(); \ + IOHelper::reads(mFile,value); \ + _finish(); \ + return value; \ + } \ + _Dispatching = true; \ + value = (*func) Arg2; \ + _Dispatching = false; \ + if (_State == RecordState) { \ + _start(); \ + IOHelper::writes(mFile,value); \ + _finish(); \ + } \ + return value; \ + } + + template + Method(Func,(Func func,A a,B b,C c,D d,E e,F f, G g, H h, I i, J j),(a,b,c,d,e,f,g,h,i,j)) + template + Method(Func,(Func func,A a,B b,C c,D d,E e,F f, G g, H h, I i),(a,b,c,d,e,f,g,h,i)) + template + Method(Func,(Func func,A a,B b,C c,D d,E e,F f, G g, H h),(a,b,c,d,e,f,g,h)) + template + Method(Func,(Func func,A a,B b,C c,D d,E e,F f, G g),(a,b,c,d,e,f,g)) + template + Method(Func,(Func func,A a,B b,C c,D d,E e,F f),(a,b,c,d,e,f)) + template + Method(Func,(Func func,A a,B b,C c,D d,E e),(a,b,c,d,e)) + template + Method(Func,(Func func,A a,B b,C c,D d),(a,b,c,d)) + template + Method(Func,(Func func,A a,B b,C c),(a,b,c)) + template + Method(Func,(Func func,A a,B b),(a,b)) + template + Method(Func,(Func func,A a),(a)) + template + Method(Func,(Func func),()) + #undef Method + + /// Journal a function call. Store the function id and all the + /// function's arguments into the journal. On journal playback the + /// function is executed with the retrieved arguments. The function + /// must have been previously declared using the declareFunction() + /// method. + #define Method(Arg1,Arg2,Arg3) \ + static void Call Arg1 { \ + if (_Dispatching) \ + return; \ + if (_State == PlayState) \ + return; \ + if (_State == RecordState) { \ + Id id = _getFunctionId((VoidPtr)func,0); \ + AssertFatal(id,"Journal: Function must be be declared before being called"); \ + _start(); \ + IOHelper::writes Arg2; \ + _finish(); \ + } \ + _Dispatching = true; \ + (*func) Arg3; \ + _Dispatching = false; \ + return; \ + } + + template + Method((Func func,A a,B b,C c,D d,E e,F f,G g,H h,I i,J j),(mFile,id,a,b,c,d,e,f,g,h,i,j),(a,b,c,d,e,f,g,h,i,j)) + template + Method((Func func,A a,B b,C c,D d,E e,F f,G g,H h,I i),(mFile,id,a,b,c,d,e,f,g,h,i),(a,b,c,d,e,f,g,h,i)) + template + Method((Func func,A a,B b,C c,D d,E e,F f,G g,H h),(mFile,id,a,b,c,d,e,f,g,h),(a,b,c,d,e,f,g,h)) + template + Method((Func func,A a,B b,C c,D d,E e,F f,G g),(mFile,id,a,b,c,d,e,f,g),(a,b,c,d,e,f,g)) + template + Method((Func func,A a,B b,C c,D d,E e,F f),(mFile,id,a,b,c,d,e,f),(a,b,c,d,e,f)) + template + Method((Func func,A a,B b,C c,D d,E e),(mFile,id,a,b,c,d,e),(a,b,c,d,e)) + template + Method((Func func,A a,B b,C c,D d),(mFile,id,a,b,c,d),(a,b,c,d)) + template + Method((Func func,A a,B b,C c),(mFile,id,a,b,c),(a,b,c)) + template + Method((Func func,A a,B b),(mFile,id,a,b),(a,b)) + template + Method((Func func,A a),(mFile,id,a),(a)) + template + Method((Func func),(mFile,id),()) + #undef Method + + #define Method(Arg1,Arg2,Arg3) \ + static void Call Arg1 { \ + if (_Dispatching) \ + return; \ + if (_State == PlayState) \ + return; \ + if (_State == RecordState) { \ + Id id = _getFunctionId((VoidPtr)obj,(VoidMethod)method); \ + AssertFatal(id != 0,"Journal: Function must be be declared before being called"); \ + _start(); \ + IOHelper::writes Arg2; \ + _finish(); \ + } \ + _Dispatching = true; \ + (obj->*method) Arg3; \ + _Dispatching = false; \ + return; \ + } + + + template + Method((Obj* obj,void (Obj::*method)(A,B,C,D,E,F,G,H,I,J),A a,B b,C c,D d,E e,F f,G g,H h,I i,J j),(mFile,id,a,b,c,d,e,f,g,h,i,j),(a,b,c,d,e,f,g,h,i,j)) + template + Method((Obj* obj,void (Obj::*method)(A,B,C,D,E,F,G,H,I),A a,B b,C c,D d,E e,F f,G g,H h,I i),(mFile,id,a,b,c,d,e,f,g,h,i),(a,b,c,d,e,f,g,h,i)) + template + Method((Obj* obj,void (Obj::*method)(A,B,C,D,E,F,G,H),A a,B b,C c,D d,E e,F f,G g,H h),(mFile,id,a,b,c,d,e,f,g,h),(a,b,c,d,e,f,g,h)) + template + Method((Obj* obj,void (Obj::*method)(A,B,C,D,E,F,G),A a,B b,C c,D d,E e,F f,G g),(mFile,id,a,b,c,d,e,f,g),(a,b,c,d,e,f,g)) + template + Method((Obj* obj,void (Obj::*method)(A,B,C,D,E,F),A a,B b,C c,D d,E e,F f),(mFile,id,a,b,c,d,e,f),(a,b,c,d,e,f)) + template + Method((Obj* obj,void (Obj::*method)(A,B,C,D,E),A a,B b,C c,D d,E e),(mFile,id,a,b,c,d,e),(a,b,c,d,e)) + template + Method((Obj* obj,void (Obj::*method)(A,B,C,D),A a,B b,C c,D d),(mFile,id,a,b,c,d),(a,b,c,d)) + template + Method((Obj* obj,void (Obj::*method)(A,B,C),A a,B b,C c),(mFile,id,a,b,c),(a,b,c)) + template + Method((Obj* obj,void (Obj::*method)(A,B),A a,B b),(mFile,id,a,b),(a,b)) + template + Method((Obj* obj,void (Obj::*method)(A),A a),(mFile,id,a),(a)) + template + Method((Obj* obj,void (Obj::*method)()),(mFile,id),()) + + #undef Method + + /// Write data into the journal. Non-deterministic data can be stored + /// into the journal for reading during playback. The function + /// returns true if the journal is record mode. + #define Method(Arg1,Arg2) \ + static inline bool Writes Arg1 { \ + if (_State == RecordState) { \ + _start(); IOHelper::writes Arg2; _finish(); \ + return true; \ + } \ + return false; \ + } + + template + Method((A& a,B& b,C& c,D& d,E& e, F& f, G& g, H& h, I& i,J& j),(mFile,a,b,c,d,e,f,g,h,i,j)); + template + Method((A& a,B& b,C& c,D& d,E& e, F& f, G& g, H& h, I& i),(mFile,a,b,c,d,e,f,g,h,i)); + template + Method((A& a,B& b,C& c,D& d,E& e, F& f, G& g, H& h),(mFile,a,b,c,d,e,f,g,h)); + template + Method((A& a,B& b,C& c,D& d,E& e, F& f, G& g),(mFile,a,b,c,d,e,f,g)); + template + Method((A& a,B& b,C& c,D& d,E& e, F& f),(mFile,a,b,c,d,e,f)); + template + Method((A& a,B& b,C& c,D& d,E& e),(mFile,a,b,c,d,e)); + template + Method((A& a,B& b,C& c,D& d),(mFile,a,b,c,d)); + template + Method((A& a,B& b,C& c),(mFile,a,b,c)); + template + Method((A& a,B& b),(mFile,a,b)); + template + Method((A& a),(mFile,a)); + #undef Method + + /// Read data from the journal. Read non-deterministic data stored + /// during the recording phase. The function returns true if the + /// journal is play mode. + #define Method(Arg1,Arg2) \ + static inline bool Reads Arg1 { \ + if (_State == PlayState) { \ + _start(); IOHelper::reads Arg2; _finish(); \ + return true; \ + } \ + return false; \ + } + + template + Method((A& a,B& b,C& c,D& d,E& e,F& f,G& g, H& h, I& i, J& j),(mFile,a,b,c,d,e,f,g,h,i,j)); + template + Method((A& a,B& b,C& c,D& d,E& e,F& f,G& g, H& h, I& i),(mFile,a,b,c,d,e,f,g,h,i)); + template + Method((A& a,B& b,C& c,D& d,E& e,F& f,G& g, H& h),(mFile,a,b,c,d,e,f,g,h)); + template + Method((A& a,B& b,C& c,D& d,E& e,F& f,G& g),(mFile,a,b,c,d,e,f,g)); + template + Method((A& a,B& b,C& c,D& d,E& e,F& f),(mFile,a,b,c,d,e,f)); + template + Method((A& a,B& b,C& c,D& d,E& e),(mFile,a,b,c,d,e)); + template + Method((A& a,B& b,C& c,D& d),(mFile,a,b,c,d)); + template + Method((A& a,B& b,C& c),(mFile,a,b,c)); + template + Method((A& a,B& b),(mFile,a,b)); + template + Method((A& a),(mFile,a)); + + #undef Method +}; + +#endif diff --git a/core/util/journal/journaledSignal.h b/core/util/journal/journaledSignal.h new file mode 100644 index 0000000..97e3b96 --- /dev/null +++ b/core/util/journal/journaledSignal.h @@ -0,0 +1,287 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _JOURNALEDSIGNAL_H_ +#define _JOURNALEDSIGNAL_H_ + +#include "core/util/journal/journal.h" +#include "core/util/tSignal.h" + +template class JournaledSignal; + +template<> class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger() + { + Journal::Call((Parent*)this, &Parent::trigger); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a) + { + Journal::Call((Parent*)this, &Parent::trigger, a); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a, B b) + { + Journal::Call((Parent*)this, &Parent::trigger, a, b); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a, B b, C c) + { + Journal::Call((Parent*)this, &Parent::trigger, a, b, c); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a, B b, C c, D d) + { + Journal::Call((Parent*)this, &Parent::trigger, a, b, c, d); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a, B b, C c, D d, E e) + { + Journal::Call((Parent*)this, &Parent::trigger, a, b, c, d, e); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a, B b, C c, D d, E e, F f) + { + Journal::Call((Parent*)this, &Parent::trigger, a, b, c, d, e, f); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a, B b, C c, D d, E e, F f, G g) + { + Journal::Call((Parent*)this, &Parent::trigger, a, b, c, d, e, f, g); + } +}; + +template class JournaledSignal : public Signal +{ + typedef Signal Parent; + +public: + JournaledSignal() + { + Journal::DeclareFunction((Parent*)this, &Parent::trigger); + } + + ~JournaledSignal() + { + Journal::RemoveFunction((Parent*)this, &Parent::trigger); + } + + void trigger(A a, B b, C c, D d, E e, F f, G g, H h) + { + Journal::Call((Parent*)this, &Parent::trigger, a, b, c, d, e, f, g, h); + } +}; + +//----------------------------------------------------------------------------- +// Common event callbacks definitions +enum InputModifier { + IM_LALT = (1 << 1), + IM_RALT = (1 << 2), + IM_LSHIFT = (1 << 3), + IM_RSHIFT = (1 << 4), + IM_LCTRL = (1 << 5), + IM_RCTRL = (1 << 6), + IM_LOPT = (1 << 7), + IM_ROPT = (1 << 8), + IM_ALT = IM_LALT | IM_RALT, + IM_SHIFT = IM_LSHIFT | IM_RSHIFT, + IM_CTRL = IM_LCTRL | IM_RCTRL, + IM_OPT = IM_LOPT | IM_ROPT, +}; + +enum InputAction { + IA_MAKE = (1 << 0), + IA_BREAK = (1 << 1), + IA_REPEAT = (1 << 2), + IA_MOVE = (1 << 3), + IA_DELTA = (1 << 4), + IA_BUTTON = (1 << 5), +}; + +enum ApplicationMessage { + Quit, + WindowOpen, ///< Window opened + WindowClose, ///< Window closed. + WindowShown, ///< Window has been shown on screen + WindowHidden, ///< Window has become hidden + WindowDestroy, ///< Window was destroyed. + GainCapture, ///< Window will capture all input + LoseCapture, ///< Window will no longer capture all input + GainFocus, ///< Application gains focus + LoseFocus, ///< Application loses focus + DisplayChange, ///< Desktop Display mode has changed + GainScreen, ///< Window will acquire lock on the full screen + LoseScreen, ///< Window has released lock on the full screen + Timer, +}; + +typedef U32 WindowId; + +/// void event() +typedef JournaledSignal IdleEvent; + +/// void event(WindowId,U32 modifier,S32 x,S32 y, bool isRelative) +typedef JournaledSignal MouseEvent; + +/// void event(WindowId,U32 modifier,S32 wheelDeltaX, S32 wheelDeltaY) +typedef JournaledSignal MouseWheelEvent; + +/// void event(WindowId,U32 modifier,U32 action,U16 key) +typedef JournaledSignal KeyEvent; + +/// void event(WindowId,U32 modifier,U16 key) +typedef JournaledSignal CharEvent; + +/// void event(WindowId,U32 modifier,U32 action,U16 button) +typedef JournaledSignal ButtonEvent; + +/// void event(WindowId,U32 modifier,U32 action,U32 axis,F32 value) +typedef JournaledSignal LinearEvent; + +/// void event(WindowId,U32 modifier,F32 value) +typedef JournaledSignal PovEvent; + +/// void event(WindowId,InputAppMessage) +typedef JournaledSignal AppEvent; + +/// void event(WindowId) +typedef JournaledSignal DisplayEvent; + +/// void event(WindowId, S32 width, S32 height) +typedef JournaledSignal ResizeEvent; + +/// void event(S32 timeDelta) +typedef JournaledSignal TimeManagerEvent; + +// void event(U32 deviceInst,F32 fValue, U16 deviceType, U16 objType, U16 ascii, U16 objInst, U8 action, U8 modifier) +typedef JournaledSignal InputEvent; + +#endif diff --git a/core/util/journal/process.cpp b/core/util/journal/process.cpp new file mode 100644 index 0000000..342b85c --- /dev/null +++ b/core/util/journal/process.cpp @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/journal/process.h" +#include "core/util/journal/journal.h" + +static Process* _theOneProcess = NULL; ///< the one instance of the Process class + +//----------------------------------------------------------------------------- + +void Process::requestShutdown() +{ + Process::get()._RequestShutdown = true; +} + +//----------------------------------------------------------------------------- + +Process::Process() +: _RequestShutdown( false ) +{ +} + +Process &Process::get() +{ + struct Cleanup + { + ~Cleanup() + { + if( _theOneProcess ) + delete _theOneProcess; + } + }; + static Cleanup cleanup; + + // NOTE that this function is not thread-safe + // To make it thread safe, use the double-checked locking mechanism for singleton objects + + if ( !_theOneProcess ) + _theOneProcess = new Process; + + return *_theOneProcess; +} + +bool Process::init() +{ + return Process::get()._signalInit.trigger(); +} + +void Process::handleCommandLine(S32 argc, const char **argv) +{ + Process::get()._signalCommandLine.trigger(argc, argv); +} + +bool Process::processEvents() +{ + // Process all the devices. We need to call these even during journal + // playback to ensure that the OS event queues are serviced. + Process::get()._signalProcess.trigger(); + + if (!Process::get()._RequestShutdown) + { + if (Journal::IsPlaying()) + return Journal::PlayNext(); + return true; + } + + // Reset the Quit flag so the function can be called again. + Process::get()._RequestShutdown = false; + return false; +} + +bool Process::shutdown() +{ + return Process::get()._signalShutdown.trigger(); +} + diff --git a/core/util/journal/process.h b/core/util/journal/process.h new file mode 100644 index 0000000..1b0d061 --- /dev/null +++ b/core/util/journal/process.h @@ -0,0 +1,203 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _UTIL_JOURNAL_PROCESS_H_ +#define _UTIL_JOURNAL_PROCESS_H_ + +#include "platform/platform.h" +#include "core/util/tVector.h" +#include "core/util/delegate.h" +#include "core/util/tSignal.h" + + +#define PROCESS_FIRST_ORDER 0.0f +#define PROCESS_NET_ORDER 0.35f +#define PROCESS_INPUT_ORDER 0.4f +#define PROCESS_DEFAULT_ORDER 0.5f +#define PROCESS_TIME_ORDER 0.75f +#define PROCESS_RENDER_ORDER 0.8f +#define PROCESS_LAST_ORDER 1.0f + +class StandardMainLoop; + + +/// Event generation signal. +/// +/// Objects that generate events need to register a callback with +/// this signal and should only generate events from within the callback. +/// +/// This signal is triggered from the ProcessEvents() method. +class Process +{ +public: + /// Trigger the ProcessSignal and replay journal events. + /// + /// The ProcessSignal is triggered during which all events are generated, + /// journaled, and delivered using the EventSignal classes. Event producers should + /// only generate events from within the function they register with ProcessSignal. + /// ProcessSignal is also triggered during event playback, though all new events are + /// thrown away so as not to interfere with journal playback. + /// This function returns false if Process::requestShutdown() has been called, otherwise it + /// returns true. + /// + /// NOTE: This should only be called from main loops - it should really be private, + /// but we need to sort out how to handle the unit test cases + static bool processEvents(); + + /// Ask the processEvents() function to shutdown. + static void requestShutdown(); + + + static void notifyInit(Delegate del, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalInit.notify(del,order); + } + + template + static void notifyInit(T func, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalInit.notify(func,order); + } + + + static void notifyCommandLine(Delegate del, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalCommandLine.notify(del,order); + } + + template + static void notifyCommandLine(T func, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalCommandLine.notify(func,order); + } + + + static void notify(Delegate del, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalProcess.notify(del,order); + } + + template + static void notify(T func, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalProcess.notify(func,order); + } + + template + static void notify(T obj,U func, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalProcess.notify(obj,func,order); + } + + + static void remove(Delegate del) + { + get()._signalProcess.remove(del); + } + + template + static void remove(T func) + { + get()._signalProcess.remove(func); + } + + template + static void remove(T obj,U func) + { + get()._signalProcess.remove(obj,func); + } + + + static void notifyShutdown(Delegate del, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalShutdown.notify(del,order); + } + + template + static void notifyShutdown(T func, F32 order = PROCESS_DEFAULT_ORDER) + { + get()._signalShutdown.notify(func,order); + } + +private: + friend class StandardMainLoop; + + /// Trigger the registered init functions + static bool init(); + + /// Trigger the registered command line handling functions + static void handleCommandLine(S32 argc, const char **argv); + + /// Trigger the registered shutdown functions + static bool shutdown(); + + /// Private constructor + Process(); + + /// Access method will construct the singleton as necessary + static Process &get(); + + Signal _signalInit; + Signal _signalCommandLine; + Signal _signalProcess; + Signal _signalShutdown; + + bool _RequestShutdown; +}; + +/// Register an initialization function +/// To use this, declare a static var like this: +/// static ProcessRegisterInit sgInit(InitFoo); +class ProcessRegisterInit +{ +public: + template + ProcessRegisterInit( T func, F32 order = PROCESS_DEFAULT_ORDER ) + { + Process::notifyInit( func, order ); + } +}; + +/// Register a command line handling function +/// To use this, declare a static var like this: +/// static ProcessRegisterCommandLine sgCommandLine(HandleCommandLine); +class ProcessRegisterCommandLine +{ +public: + template + ProcessRegisterCommandLine( T func, F32 order = PROCESS_DEFAULT_ORDER ) + { + Process::notifyCommandLine( func, order ); + } +}; + +/// Register a processing function +/// To use this, declare a static var like this: +/// static ProcessRegisterProcessing sgProcess(ProcessFoo); +class ProcessRegisterProcessing +{ +public: + template + ProcessRegisterProcessing( T func, F32 order = PROCESS_DEFAULT_ORDER ) + { + Process::notify( func, order ); + } +}; + +/// Register a shutdown function +/// To use this, declare a static var like this: +/// static ProcessRegisterShutdown sgShutdown(ShutdownFoo); +class ProcessRegisterShutdown +{ +public: + template + ProcessRegisterShutdown( T func, F32 order = PROCESS_DEFAULT_ORDER ) + { + Process::notifyShutdown( func, order ); + } +}; +///@} + +#endif diff --git a/core/util/journal/test/testJournal.cpp b/core/util/journal/test/testJournal.cpp new file mode 100644 index 0000000..43dbd6e --- /dev/null +++ b/core/util/journal/test/testJournal.cpp @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "core/util/journal/journaledSignal.h" +#include "core/util/safeDelete.h" + +using namespace UnitTesting; + +CreateUnitTest(TestsJournalRecordAndPlayback, "Journal/Basic") +{ + U32 _lastTriggerValue; + + void triggerReceiver(U16 msg) + { + _lastTriggerValue = msg; + } + + void run() + { + // Reset the last trigger value just in case... + _lastTriggerValue = 0; + + // Set up a journaled signal to test with. + JournaledSignal testEvent; + + testEvent.notify(this, &TestsJournalRecordAndPlayback::triggerReceiver); + + // Initialize journal recording and fire off some events... + Journal::Record("test.jrn"); + + testEvent.trigger(16); + testEvent.trigger(17); + testEvent.trigger(18); + + test(_lastTriggerValue == 18, "Should encounter last triggered value (18)."); + + Journal::Stop(); + + // Clear it... + _lastTriggerValue = 0; + + // and play back - should get same thing. + Journal::Play("test.jrn"); + + // Since we fired 3 events, it should take three loops. + test(Journal::PlayNext(), "Should be two more events."); + test(Journal::PlayNext(), "Should be one more event."); + test(!Journal::PlayNext(), "Should be no more events."); + + test(_lastTriggerValue == 18, "Should encounter last journaled value (18)."); + } +}; + +CreateUnitTest(TestsJournalDynamicSignals, "Journal/DynamicSignals") +{ + typedef JournaledSignal EventA; + typedef JournaledSignal EventB; + typedef JournaledSignal EventC; + + EventA *dynamicA; + EventB *dynamicB; + EventC *dynamicC; + + // Root, non-dynamic signal receiver. + void receiverRoot(U8 msg) + { + if(msg==1) + { + dynamicA = new EventA(); + dynamicA->notify(this, &TestsJournalDynamicSignals::receiverA); + } + + if(msg==2) + { + dynamicB = new EventB(); + dynamicB->notify(this, &TestsJournalDynamicSignals::receiverB); + } + + if(msg==3) + { + dynamicC = new EventC(); + dynamicC->notify(this, &TestsJournalDynamicSignals::receiverC); + } + } + + U32 recvA, recvB, recvC; + + void receiverA(U32, U16 d) + { + recvA += d; + } + + void receiverB(U8, S8 d) + { + recvB += d; + } + + void receiverC(U32, S32 d) + { + recvC += d; + } + + void run() + { + // Reset our state values. + recvA = recvB = recvC = 0; + + // Set up a signal to start with. + JournaledSignal testEvent; + testEvent.notify(this, &TestsJournalDynamicSignals::receiverRoot); + + // Initialize journal recording and fire off some events... + Journal::Record("test.jrn"); + + testEvent.trigger(1); + dynamicA->trigger(8, 100); + testEvent.trigger(2); + dynamicA->trigger(8, 8); + dynamicB->trigger(9, 'a'); + testEvent.trigger(3); + SAFE_DELETE(dynamicB); // Test a deletion. + dynamicC->trigger(8, 1); + dynamicC->trigger(8, 1); + + // Did we end up with expected values? Check before clearing. + test(recvA == 108, "recvA wasn't 108 - something broken in signals?"); + test(recvB == 'a', "recvB wasn't 'a' - something broken in signals?"); + test(recvC == 2, "recvC wasn't 2 - something broken in signals?"); + + // Reset our state values. + recvA = recvB = recvC = 0; + + // And kill the journal... + Journal::Stop(); + + // Also kill our remaining dynamic signals. + SAFE_DELETE(dynamicA); + SAFE_DELETE(dynamicB); + SAFE_DELETE(dynamicC); + + // Play back - should get same thing. + Journal::Play("test.jrn"); + + // Since we fired 8 events, it should take 7+1=8 loops. + for(S32 i=0; i<7; i++) + test(Journal::PlayNext(), "Should be more events."); + test(!Journal::PlayNext(), "Should be no more events."); + + test(recvA == 108, "recvA wasn't 108 - something broken in journal?"); + test(recvB == 'a', "recvB wasn't 'a' - something broken in journal?"); + test(recvC == 2, "recvC wasn't 2 - something broken in journal?"); + } +}; diff --git a/core/util/journal/test/testProcess.cpp b/core/util/journal/test/testProcess.cpp new file mode 100644 index 0000000..d8249be --- /dev/null +++ b/core/util/journal/test/testProcess.cpp @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "core/util/journal/process.h" +#include "core/util/safeDelete.h" + +using namespace UnitTesting; + +CreateUnitTest(TestingProcess, "Journal/Process") +{ + // How many ticks remaining? + U32 _remainingTicks; + + // Callback for process list. + void process() + { + if(_remainingTicks==0) + Process::requestShutdown(); + + _remainingTicks--; + } + + void run() + { + // We'll run 30 ticks, then quit. + _remainingTicks = 30; + + // Register with the process list. + Process::notify(this, &TestingProcess::process); + + // And do 30 notifies, making sure we end on the 30th. + for(S32 i=0; i<30; i++) + test(Process::processEvents(), "Should quit after 30 ProcessEvents() calls - not before!"); + test(!Process::processEvents(), "Should quit after the 30th ProcessEvent() call!"); + } +}; \ No newline at end of file diff --git a/core/util/namedSingleton.h b/core/util/namedSingleton.h new file mode 100644 index 0000000..2f99388 --- /dev/null +++ b/core/util/namedSingleton.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_CORE_UTIL_NAMEDSINGLETON_H_ +#define _TORQUE_CORE_UTIL_NAMEDSINGLETON_H_ + +#include "platform/platform.h" +#include "core/util/safeCast.h" +#include "console/console.h" +#include "core/stringTable.h" + +//-------------------------------------------------------------------------- +// StaticNamedSingleton. +//-------------------------------------------------------------------------- + +/// Collection of statically registered named singletons. +/// +/// This class is useful as a mix-in for classes that are supposed to +/// represent a range of named singleton instances from which a specific +/// instance is then selected at run-time. +/// +/// @param T Arbitrary type parameter; identical types will share +/// static data. + +template< class T > +struct StaticNamedSingleton +{ + typedef StaticNamedSingleton This; + + StaticNamedSingleton( const char* name ); + virtual ~StaticNamedSingleton() {} + + const char* getName(); + T* getNext(); + + static T* staticGetFirst(); + static T* staticFindSingleton( const char* name ); + static EnumTable* staticCreateEnumTable(); + static U32 staticGetNumSingletons(); + +private: + const char* mName; + This* mNext; + + static This* smSingletons; +}; + +template< class T > +StaticNamedSingleton< T >* StaticNamedSingleton< T >::smSingletons; + +template< class T > +StaticNamedSingleton< T >::StaticNamedSingleton( const char* name ) + : mName( name ) +{ + mNext = smSingletons; + smSingletons = this; +} + +template< class T > +inline const char* StaticNamedSingleton< T >::getName() +{ + return mName; +} + +template< class T > +inline T* StaticNamedSingleton< T >::getNext() +{ + return static_cast< T* >( mNext ); +} + +template< class T > +T* StaticNamedSingleton< T >::staticGetFirst() +{ + return static_cast< T* >( smSingletons ); +} + +/// Find the instance with the given name. Returns NULL if no such +/// instance exists. + +template< class T > +T* StaticNamedSingleton< T >::staticFindSingleton( const char* name ) +{ + for( This* ptr = smSingletons; ptr != 0; ptr = ptr->mNext ) + if( dStricmp( name, ptr->mName ) == 0 ) + return static_cast< T* >( ptr ); + + return 0; +} + +/// Create a TorqueScript EnumTable that contains all registered +/// instance names. + +template< class T > +EnumTable* StaticNamedSingleton< T >::staticCreateEnumTable() +{ + U32 numSingletons = staticGetNumSingletons(); + + // Create the enums. + + EnumTable::Enums* enums = new EnumTable::Enums[ numSingletons ]; + This* ptr = smSingletons; + for( U32 i = 0; i < numSingletons; ++ i ) + { + enums[ i ].index = i; + enums[ i ].label = StringTable->insert( ptr->getName() ); + + ptr = ptr->mNext; + } + + // Create the table. + + return new EnumTable( numSingletons, enums ); +} + +/// Return the number of registered named singletons. + +template< class T > +U32 StaticNamedSingleton< T >::staticGetNumSingletons() +{ + U32 numSingletons = 0; + for( This* ptr = smSingletons; ptr != 0; ptr = ptr->mNext ) + numSingletons ++; + return numSingletons; +} + +#endif // _TORQUE_CORE_UTIL_NAMEDSINGLETON_H_ diff --git a/core/util/noncopyable.h b/core/util/noncopyable.h new file mode 100644 index 0000000..5bb400f --- /dev/null +++ b/core/util/noncopyable.h @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CORE_NONCOPYABLE_H_ +#define _CORE_NONCOPYABLE_H_ + +class Noncopyable +{ +protected: + Noncopyable() {} + ~Noncopyable() {} + +private: + Noncopyable(const Noncopyable&); + const Noncopyable& operator=(const Noncopyable&); +}; + +#endif diff --git a/core/util/path.cpp b/core/util/path.cpp new file mode 100644 index 0000000..64b7213 --- /dev/null +++ b/core/util/path.cpp @@ -0,0 +1,404 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "core/util/path.h" + + +namespace Torque +{ + +//----------------------------------------------------------------------------- + +void Path::_split(String name) +{ + S32 pos = 0; + S32 idx = 0; + + // Make sure we have platform separators + name = PathToPlatform(name); + + // root: + idx = name.find(':'); + if (idx >= 0) + { + mRoot = name.substr(0,idx); + pos = idx + 1; + } + else + { + mRoot = ""; + } + + // Extract path and strip trailing '/' + idx = name.find('/', 0, String::Right); + if (idx >= pos) + { + int len = idx - pos; + mPath = name.substr(pos,len? len: 1); + mPath = Path::CleanSeparators(mPath); + pos = idx + 1; + } + else + { + mPath = ""; + } + + // file.ext + idx = name.find('.', 0, String::Right); + if (idx >= pos) + { + mFile = name.substr(pos,idx - pos); + mExt = name.substr(idx + 1,name.length() - idx - 1); + } + else + { + mFile = name.substr(pos,name.length() - pos); + mExt = ""; + } +} + +String Path::_join() const +{ + String name; + name = Path::Join(mRoot, ':', mPath); + name = Path::Join(name, '/', mFile); + name = Path::Join(name, '.', mExt); + return name; +} + +String Path::CleanSeparators(String path) +{ + return path.replace( '\\', '/' ); +} + +String Path::CompressPath(String path) +{ + // Remove "./" and "../" references. A ".." will also result in the + // removal of the proceeding directory. + // Also cleans separators as in CleanSeparators(). + // Assumes there are no trailing "/" + + // Start by cleaning separators + path = Path::CleanSeparators(path); + + U32 src = 0; + U32 dst = 0; + + while (path[src]) + { + if (path[src] == '/' && path[src + 1] == '/') + { + src += 1; + continue; + } + else if (path[src] == '.') + { + if (path[src + 1] == 0) + { + if (dst && path[dst - 1] == '/') + dst--; + src++; + break; + } + else if (path[src + 1] == '/') + { + src += 2; + continue; + } + else if (path[src + 1] == '.') + { + if (path[src + 2] == 0) + { + if (dst && path[dst - 1] == '/') + dst = path.find('/', dst - 1, String::Right); + src += 2; + break; + } + if (dst && path[dst - 1] == '/') + dst = path.find('/', dst - 1, String::Right) + 1; + else + dst += 3; + + src += 3; + continue; + } + } + + if (dst != src) + { + String end = path.substr(src, path.length() - src); + if (dst > 0 && end[(String::SizeType)0] == '/' && path[dst-1] == '/') + end = end.substr(1, end.length() - 1); + path.replace(dst, path.length() - dst, end); + src = dst; + } + else + { + src++; + dst++; + } + } + + if (src - dst) + path.erase(dst, src - dst); + + return path; +} + +Torque::Path Path::MakeRelativePath( const Path &makeRelative, const Path &relativeTo, U32 mode ) +{ + // We need to find the part of makeRelative that starts to diverge from + // relativeTo. We only need to check up to the end of makeRelative or realtiveTo + // (whichever comes first). + U32 minDirCount = getMin(makeRelative.getDirectoryCount(), relativeTo.getDirectoryCount()); + + // Store the index of the directory where we diverge. If we never diverge this + // will end up being the same as the number of directories in makeRelative + U32 divergeDirIdx = 0; + + for (divergeDirIdx = 0; divergeDirIdx < minDirCount; divergeDirIdx++) + { + // If our directories don't match then this is the diverge directory + if (!makeRelative.getDirectory(divergeDirIdx).equal(relativeTo.getDirectory(divergeDirIdx), mode)) + break; + } + + // Get the part of makeRelative's path after it diverged from relativeTo's path + String uniquePath; + + // If we never diverged then divergeDirIdx will be equal to the number of + // directories in makeRelative and this loop will immediately exit + for (U32 i = divergeDirIdx; i < makeRelative.getDirectoryCount(); i++) + uniquePath += makeRelative.getDirectory(i) + "/"; + + // Go ahead and add the full file name + uniquePath += makeRelative.getFullFileName(); + + // Now calculate the relative offset + String offsetPath; + + U32 numOffset = relativeTo.getDirectoryCount() - divergeDirIdx; + + // Push back an "up" for each directory we are offset + for (U32 i = 0; i < numOffset; i++) + offsetPath += "../"; + + return offsetPath + uniquePath; +} + +String Path::Join(const String& a,String::ValueType s,const String& b) +{ + switch (s) + { + case '/': + { + if (b.isEmpty() || (b.length() == 1 && (b.c_str()[0] == '/'))) + return a; + + if (a.isEmpty()) + return b; + + String::ValueType c = a[a.length()-1]; + + if (c == ':' || ((c == '/') ^ (b.c_str()[0] == '/'))) + return a + b; + + if (c == '/' && b.c_str()[0] == '/') + return a.substr(0,a.length() - 1) + b; + break; + } + + case ':': + { + if (a.isEmpty()) + return b; + + if (b.isEmpty()) + return a + ':'; + break; + } + + case '.': + { + if (b.isEmpty()) + return a; + + if (a.isEmpty()) + return '.' + b; + break; + } + + default: + break; + } + + return a + s + b; +} + +bool Path::appendPath( const Path &p ) +{ + mPath = CompressPath(Join(mPath,'/',p.getPath())); + mIsDirtyPath = true; + return true; +} + +const String &Path::getFullFileName() const +{ + if ( mIsDirtyFileName ) + { + mFullFileName = mFile; + + if (mExt.isNotEmpty()) + mFullFileName += '.' + mExt; + mIsDirtyFileName = false; + } + + return mFullFileName; + +} + +String Path::getFullPath(bool includeRoot) const +{ + if (!includeRoot) + { + // skip the dirty optimization for this case + return Torque::Path::Join(getPath(), '/', getFullFileName()); + } + if ( mIsDirtyPath ) + { + mFullPath = _join(); + mIsDirtyPath = false; + } + + return mFullPath; +} + +const String& Path::setRoot(const String &s) +{ + if ( mRoot != s ) + { + mIsDirtyPath = true; + mRoot = s; + } + + return mRoot; +} + +const String& Path::setPath(const String &s) +{ + String clean = CleanSeparators(s); + + if ( mPath != clean ) + { + mIsDirtyPath = true; + mPath = clean; + } + + return mPath; +} + +const String& Path::setFileName(const String &s) +{ + if ( mFile != s ) + { + mIsDirtyPath = true; + mIsDirtyFileName = true; + mFile = s; + } + + return mFile; +} + +const String& Path::setExtension(const String &s) +{ + if ( mExt != s ) + { + mIsDirtyPath = true; + mIsDirtyFileName = true; + mExt = s; + } + + return mExt; +} + +bool Path::isDirectory() const +{ + return mFile.isEmpty() && mExt.isEmpty(); +} + +bool Path::isRelative() const +{ + return (mPath.isEmpty() || mPath.c_str()[0] != '/'); +} + +bool Path::isAbsolute() const +{ + return (!mPath.isEmpty() && mPath.c_str()[0] == '/'); +} + +U32 Path::getDirectoryCount() const +{ + if (mPath.isEmpty()) + return 0; + + U32 count = 0; + U32 offset = 0; + + if (mPath.c_str()[0] == '/') + offset++; + + while (offset < mPath.length()) + { + if (mPath[offset++] == '/') + count++; + } + + return count + 1; +} + +String Path::getDirectory(U32 count) const +{ + if (mPath.isEmpty()) + return String(); + + U32 offset = 0; + + if (mPath.c_str()[0] == '/') + offset++; + + while (count && offset < mPath.length()) + { + if (mPath[offset++] == '/') + count--; + } + + U32 end = offset; + + while (mPath[end] != '/' && end < mPath.length()) + end++; + + return mPath.substr(offset,end - offset); +} + +//----------------------------------------------------------------------------- + +String PathToPlatform(String file) +{ + if (Path::OsSeparator != '/') + file.replace( Path::OsSeparator, '/' ); + + return file; +} + +String PathToOS(String file) +{ + if (Path::OsSeparator != '/') + file.replace( '/', Path::OsSeparator ); + + return file; +} + +} // Namespace + diff --git a/core/util/path.h b/core/util/path.h new file mode 100644 index 0000000..fd26a93 --- /dev/null +++ b/core/util/path.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _PATH_H_ +#define _PATH_H_ + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +namespace Torque +{ + +//----------------------------------------------------------------------------- + +/// FileSystem filename representation. +/// Filenames has the following form: "root:path/file.ext" +/// @ingroup UtilString +class Path +{ +public: + enum Separator + { +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XENON) + OsSeparator = '\\' +#else + OsSeparator = '/' +#endif + }; + + Path() + : mIsDirtyFileName( true ), + mIsDirtyPath( true ) + { + } + + Path( const char *file ) + : mIsDirtyFileName( true ), + mIsDirtyPath( true ) + { + _split(file); + } + + Path( const String &file ) + : mIsDirtyFileName( true ), + mIsDirtyPath( true ) + { + _split(file); + } + + Path& operator = ( const String &file ) { _split(file); mIsDirtyPath = mIsDirtyFileName = true; return *this; } + operator String() const { return getFullPath(); } + + bool operator == (const Path& path) const { return getFullPath().equal(path.getFullPath()); } + bool operator != (const Path& path) const { return !(*this == path); } + + bool isEmpty() const { return getFullPath().isEmpty(); } + + /// Join two path or file name components together. + static String Join(const String&,String::ValueType,const String&); + + /// Replace all '\' with '/' + static String CleanSeparators( String path ); + + /// Remove "." and ".." relative paths. + static String CompressPath( String path ); + + /// Take two paths and return the relative path between them. + static Path MakeRelativePath( const Path &makeRelative, const Path &relativeTo, U32 mode = String::NoCase ); + + const String& getRoot() const { return mRoot; } + const String& getPath() const { return mPath; } + const String& getFileName() const { return mFile; } + const String& getExtension() const { return mExt; } + + const String& getFullFileName() const; + String getFullPath(bool includeRoot=true) const; + + const String& setRoot(const String &s); + const String& setPath(const String &s); + const String& setFileName(const String &s); + const String& setExtension(const String &s); + + U32 getDirectoryCount() const; + String getDirectory(U32) const; + + bool isDirectory() const; + bool isRelative() const; + bool isAbsolute() const; + + /// Appends the argument's path component to the object's + /// path component. The object's root, filename and + /// extension are unaffected. + bool appendPath(const Path &path); + +private: + String mRoot; + String mPath; + String mFile; + String mExt; + + mutable String mFullFileName; + mutable String mFullPath; + + mutable bool mIsDirtyFileName; + mutable bool mIsDirtyPath; + + void _split(String name); + String _join() const; +}; + +/// Convert file/path name to use platform standard path separator +///@ingroup VolumeSystem +String PathToPlatform(String file); + +/// Convert file/path name to use OS standard path separator +///@ingroup VolumeSystem +String PathToOS(String file); + +} // Namespace +#endif + diff --git a/core/util/preprocessorHelpers.h b/core/util/preprocessorHelpers.h new file mode 100644 index 0000000..d5afe43 --- /dev/null +++ b/core/util/preprocessorHelpers.h @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef TORQUE_CORE_UTIL_PREPROCESSORHELPERS_H_ +#define TORQUE_CORE_UTIL_PREPROCESSORHELPERS_H_ + +/// @defgroup preprocess_helpers Preprocessor Helpers +/// These are some handy preprocessor macros to simplify certain tasks, like +/// preprocessor concatenation that works properly with __LINE__. + +#define _TORQUE_CONCAT(x, y) x ## y + +/// @ingroup preprocess_helpers +/// This command concatenates two tokens in a way that will work with things such +/// as __LINE__. +/// @hideinitializer +#define TORQUE_CONCAT(x, y) _TORQUE_CONCAT(x, y) + +#endif diff --git a/core/util/rawData.h b/core/util/rawData.h new file mode 100644 index 0000000..261269a --- /dev/null +++ b/core/util/rawData.h @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RAWDATA_H_ +#define _RAWDATA_H_ + +#ifndef _PLATFORM_H_ +# include "platform/platform.h" +#endif +#ifndef _TYPETRAITS_H_ +# include "platform/typetraits.h" +#endif + + +template< typename T > +class RawDataT +{ + public: + + typedef void Parent; + typedef RawDataT< T > ThisType; + + /// Type of elements in the buffer. + typedef T ValueType; + + /// If true, the structure own the data buffer and will + /// delete[] it on destruction. + bool ownMemory; + + /// The data buffer. + T *data; + + /// Number of elements in the buffer. + U32 size; + + RawDataT() + : ownMemory(false), data(NULL), size(0) + { + } + + RawDataT( T* data, U32 size, bool ownMemory = false ) + : data( data ), size( size ), ownMemory( ownMemory ) {} + + RawDataT(const ThisType& rd) + { + data = rd.data; + size = rd.size; + ownMemory = false; + } + + ~RawDataT() + { + reset(); + } + + void reset() + { + if (ownMemory) + delete [] data; + + data = NULL; + ownMemory = false; + size = 0; + } + + void alloc(const U32 newSize) + { + reset(); + + ownMemory = true; + size = newSize; + data = new ValueType[newSize]; + } + + void operator =(const ThisType& rd) + { + data = rd.data; + size = rd.size; + ownMemory = false; + } + + /// Allocate a RawDataT instance inline with its data elements. + /// + /// @param Self RawDataT instance type; this is a type parameter so this + /// can work with types derived from RawDataT. + template< class Self > + static Self* allocInline( U32 numElements TORQUE_TMM_ARGS_DECL ) + { + const char* file = __FILE__; + U32 line = __LINE__; +#ifndef TORQUE_DISABLE_MEMORY_MANAGER + file = fileName; + line = lineNum; +#endif + Self* inst = ( Self* ) dMalloc_r( sizeof( Self ) + numElements * sizeof( ValueType ), file, line ); + ValueType* data = ( ValueType* ) ( inst + 1 ); + constructArray< ValueType >( data, numElements ); + return constructInPlace< Self >( inst, data, numElements ); + } +}; + +template< typename T > +struct TypeTraits< RawDataT< T >* > : public TypeTraits< typename RawDataT< T >::Parent* > +{ + struct Construct + { + template< typename R > + static R* single( U32 size ) + { + typedef typename TypeTraits< R >::BaseType Type; + return Type::template allocInline< Type >( size TORQUE_TMM_LOC ); + } + }; + struct Destruct + { + template< typename R > + static void single( R* ptr ) + { + destructInPlace( ptr ); + dFree( ptr ); + } + }; +}; + +/// Raw byte buffer. +/// This isn't a typedef to allow forward declarations. +class RawData : public RawDataT< S8 > +{ + public: + + typedef RawDataT< S8 > Parent; + + RawData() {} + RawData( S8* data, U32 size, bool ownMemory = false ) + : Parent( data, size, ownMemory ) {} +}; + +#endif // _RAWDATA_H_ diff --git a/core/util/refBase.h b/core/util/refBase.h new file mode 100644 index 0000000..a60be4a --- /dev/null +++ b/core/util/refBase.h @@ -0,0 +1,444 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _REFBASE_H_ +#define _REFBASE_H_ + +#ifndef _PLATFORMASSERT_H_ +# include "platform/platformAssert.h" +#endif +#ifndef _TYPETRAITS_H_ +# include "platform/typetraits.h" +#endif + + +/// Base class for objects which can be weakly referenced +/// (i.e., reference goes away when object is destroyed). +class WeakRefBase +{ +public: + + /// Weak reference to WeakRefBase. + class WeakReference + { + public: + + WeakRefBase * get() { return mObject; } + void incRefCount() { mRefCount++; } + void decRefCount() + { + if (--mRefCount==0) + delete this; + } + + private: + + friend class WeakRefBase; + WeakReference(WeakRefBase *object) { mObject = object; mRefCount = 0; } + ~WeakReference() { AssertFatal(mObject==NULL, "Deleting weak reference which still points at an object."); } + + // Object we reference + WeakRefBase *mObject; + + // reference count for this structure (not WeakObjectRef itself) + U32 mRefCount; + }; + +public: + WeakRefBase() { mReference = NULL; } + virtual ~WeakRefBase() { clearWeakReferences(); } + + WeakReference * getWeakReference(); + +protected: + void clearWeakReferences(); + +private: + WeakReference * mReference; +}; + +/// Weak reference pointer class. +/// Instances of this template class can be used as pointers to +/// instances of WeakRefBase and its subclasses. +/// When the object referenced by a WeakRefPtr instance is deleted, +/// the pointer to the object is set to NULL in the WeakRefPtr instance. +template class WeakRefPtr +{ +public: + WeakRefPtr() { mReference = NULL; } + WeakRefPtr(T *ptr) { mReference = NULL; set(ptr); } + WeakRefPtr(const WeakRefPtr & ref) { mReference = NULL; set(ref.mReference); } + + ~WeakRefPtr() { set((WeakRefBase::WeakReference*)NULL); } + + WeakRefPtr& operator=(const WeakRefPtr& ref) + { + set(ref.mReference); + return *this; + } + WeakRefPtr& operator=(T *ptr) + { + set(ptr); + return *this; + } + + bool isNull() const { return mReference == NULL || mReference->get() == NULL; } + T* operator->() const { return getPointer(); } + T& operator*() const { return *getPointer(); } + operator T*() const { return getPointer(); } + T* getPointer() const { return mReference ? static_cast(mReference->get()) : NULL; } + +protected: + void set(WeakRefBase::WeakReference * ref) + { + if (mReference) + mReference->decRefCount(); + mReference = NULL; + if (ref) + { + mReference = ref; + mReference->incRefCount(); + } + } + + void set(T * obj) { set(obj ? obj->getWeakReference() : (WeakRefBase::WeakReference *)NULL); } +private: + WeakRefBase::WeakReference * mReference; +}; + +/// Union of an arbitrary type with a WeakRefBase. The exposed type will +/// remain accessible so long as the WeakRefBase is not cleared. Once +/// it is cleared, accessing the exposed type will result in a NULL pointer. +template +class WeakRefUnion +{ + typedef WeakRefUnion Union; + +public: + WeakRefUnion() : mPtr(NULL) {} + WeakRefUnion(const WeakRefPtr & ref, ExposedType * ptr) : mPtr(NULL) { set(ref, ptr); } + WeakRefUnion(const Union & lock) : mPtr(NULL) { *this = lock; } + WeakRefUnion(WeakRefBase * ref) : mPtr(NULL) { set(ref, dynamic_cast(ref)); } + ~WeakRefUnion() { mWeakReference = NULL; } + + Union & operator=(const Union & ptr) + { + set(ptr.mWeakReference, ptr.getPointer()); + return *this; + } + + void set(const WeakRefPtr & ref, ExposedType * ptr) + { + mWeakReference = ref; + mPtr = ptr; + } + + bool operator == (const ExposedType * t ) const { return getPointer() == t; } + bool operator != (const ExposedType * t ) const { return getPointer() != t; } + bool operator == (const Union &t ) const { return getPointer() == t.getPointer(); } + bool operator != (const Union &t ) const { return getPointer() != t.getPointer(); } + bool isNull() const { return mWeakReference.isNull() || !mPtr; } + + ExposedType* getPointer() const { return !mWeakReference.isNull() ? mPtr : NULL; } + ExposedType* operator->() const { return getPointer(); } + ExposedType& operator*() const { return *getPointer(); } + operator ExposedType*() const { return getPointer(); } + + WeakRefPtr getRef() const { return mWeakReference; } + +private: + WeakRefPtr mWeakReference; + ExposedType * mPtr; +}; + +/// Base class for objects which can be strongly referenced +/// (i.e., as long as reference exists, object will exist, +/// when all strong references go away, object is destroyed). +class StrongRefBase : public WeakRefBase +{ + friend class StrongObjectRef; + +public: + StrongRefBase() { mRefCount = 0; } + + U32 getRefCount() const { return mRefCount; } + + /// object destroy self call (from StrongRefPtr). Override if this class has specially allocated memory. + virtual void destroySelf() { delete this; } + + /// Increments the reference count. + void incRefCount() + { + mRefCount++; + } + + /// Decrements the reference count. + void decRefCount() + { + AssertFatal(mRefCount, "Decrementing a reference with refcount 0!"); + if(!--mRefCount) + destroySelf(); + } + +private: + U32 mRefCount; ///< reference counter for StrongRefPtr objects +}; + +/// Base class for StrongRefBase strong reference pointers. +class StrongObjectRef +{ + +public: + /// Constructor, assigns from the object and increments its reference count if it's not NULL + StrongObjectRef(StrongRefBase *object = NULL) : mObject( object ) + { + mObject = object; + incRef(); + } + + /// Destructor, dereferences the object, if there is one + ~StrongObjectRef() { decRef(); } + + /// Assigns this reference object from an existing StrongRefBase instance + void set(StrongRefBase *object) + { + if( mObject != object ) + { + decRef(); + mObject = object; + incRef(); + } + } + +protected: + StrongRefBase *mObject; ///< the object this RefObjectRef references + + /// increments the reference count on the referenced object + void incRef() + { + if(mObject) + mObject->incRefCount(); + } + + /// decrements the reference count on the referenced object + void decRef() + { + if(mObject) + mObject->decRefCount(); + } +}; + +/// Reference counted object template pointer class +/// Instances of this template class can be used as pointers to +/// instances of StrongRefBase and its subclasses. The object will not +/// be deleted until all of the StrongRefPtr instances pointing to it +/// have been destructed. +template class StrongRefPtr : protected StrongObjectRef +{ +public: + StrongRefPtr() : StrongObjectRef() {} + StrongRefPtr(T *ptr) : StrongObjectRef(ptr) {} + StrongRefPtr(const StrongRefPtr& ref) : StrongObjectRef(ref.mObject) {} + ~StrongRefPtr() {} + + StrongRefPtr& operator=(const StrongRefPtr& ref) + { + set(ref.mObject); + return *this; + } + StrongRefPtr& operator=(T *ptr) + { + set(ptr); + return *this; + } + + bool isNull() const { return mObject == 0; } + bool isValid() const { return mObject != 0; } + T* operator->() const { return getPointer(); } + T& operator*() const { return *getPointer(); } + operator T*() const { return getPointer(); } + T* getPointer() const { return const_cast(static_cast(mObject)); } +}; + +/// Union of an arbitrary type with a StrongRefBase. StrongRefBase will remain locked +/// until the union is destructed. Handy for when the exposed class will +/// become invalid whenever the reference becomes invalid and you want to make sure that doesn't +/// happen. +template +class StrongRefUnion +{ + typedef StrongRefUnion Union; + +public: + StrongRefUnion() : mPtr(NULL) {} + + StrongRefUnion(const StrongRefPtr & ref, ExposedType * ptr) : mPtr(NULL) { set(ref, ptr); } + StrongRefUnion(const Union & lock) : mPtr(NULL) { *this = lock; } + StrongRefUnion(StrongRefBase * ref) : mPtr(NULL) { set(ref, dynamic_cast(ref)); } + + ~StrongRefUnion() { mReference = NULL; } + + Union & operator=(const Union & ptr) + { + set(ptr.mReference, ptr.mPtr); + return *this; + } + + void set(const StrongRefPtr & ref, ExposedType * ptr) + { + mReference = ref; + mPtr = ptr; + } + + bool operator == (const ExposedType * t ) const { return mPtr == t; } + bool operator != (const ExposedType * t ) const { return mPtr != t; } + bool operator == (const Union &t ) const { return mPtr == t.mPtr; } + bool operator != (const Union &t ) const { return mPtr != t.mPtr; } + bool isNull() const { return mReference.isNull() || !mPtr; } + bool isValid() const { return mReference.isValid() && mPtr; } + + ExposedType* operator->() const { return mPtr; } + ExposedType& operator*() const { return *mPtr; } + operator ExposedType*() const { return mPtr; } + ExposedType* getPointer() const { return mPtr; } + + StrongRefPtr getRef() const { return mReference; } + +private: + StrongRefPtr mReference; + ExposedType * mPtr; +}; + +/// This oxymoron is a pointer that reference-counts the referenced +/// object but also NULLs out if the object goes away. +/// +/// This is useful for situations where an object's lifetime is ultimately +/// governed by a superior entity but where individual objects may also die +/// independently of the superior entity. All client code should use +/// StrongWeakRefs that keep object live as long as the superior entity doesn't +/// step in and kill them (in which case, the client code sees the reference +/// disappear). +template< class T > +class StrongWeakRefPtr +{ +public: + StrongWeakRefPtr() : mReference( NULL ) {} + StrongWeakRefPtr( T* ptr ) : mReference( NULL ) { _set( ptr ); } + ~StrongWeakRefPtr() + { + if( mReference ) + { + T* ptr = _get(); + if( ptr ) + ptr->decRefCount(); + + mReference->decRefCount(); + } + } + + bool isNull() const { return ( _get() == NULL ); } + bool operator ==( T* ptr ) const { return ( _get() == ptr ); } + bool operator !=( T* ptr ) const { return ( _get() != ptr ); } + bool operator !() const { return isNull(); } + T* operator ->() const { return _get(); } + T& operator *() const { return *( _get() ); } + operator T*() const { return _get(); } + T* getPointer() const { return _get(); } + + StrongWeakRefPtr& operator =( T* ptr ) + { + _set( ptr ); + return *this; + } + +private: + WeakRefBase::WeakReference* mReference; + + T* _get() const + { + if( mReference ) + return static_cast< T* >( mReference->get() ); + else + return NULL; + } + void _set( T* ptr ) + { + if( mReference ) + { + T* old = _get(); + if( old ) + old->decRefCount(); + + mReference->decRefCount(); + } + + if( ptr ) + { + ptr->incRefCount(); + mReference = ptr->getWeakReference(); + mReference->incRefCount(); + } + else + mReference = NULL; + } +}; + +//--------------------------------------------------------------- + +inline void WeakRefBase::clearWeakReferences() +{ + if (mReference) + { + mReference->mObject = NULL; + mReference->decRefCount(); + mReference = NULL; + } +} + +inline WeakRefBase::WeakReference * WeakRefBase::getWeakReference() +{ + if (!mReference) + { + mReference = new WeakReference(this); + mReference->incRefCount(); + } + return mReference; +} + +//--------------------------------------------------------------- + +template< typename T > +struct TypeTraits< WeakRefPtr< T > > : public _TypeTraits< WeakRefPtr< T > > +{ + typedef typename TypeTraits< T >::BaseType BaseType; +}; +template< typename T > +struct TypeTraits< StrongRefPtr< T > > : public _TypeTraits< StrongRefPtr< T > > +{ + typedef typename TypeTraits< T >::BaseType BaseType; +}; +template< typename T > +struct TypeTraits< StrongWeakRefPtr< T > > : public _TypeTraits< StrongWeakRefPtr< T > > +{ + typedef typename TypeTraits< T >::BaseType BaseType; +}; + +template< typename T > +inline T& Deref( WeakRefPtr< T >& ref ) +{ + return *ref; +} +template< typename T > +inline T& Deref( StrongRefPtr< T >& ref ) +{ + return *ref; +} +template< typename T > +inline T& Deref( StrongWeakRefPtr< T >& ref ) +{ + return *ref; +} + +#endif \ No newline at end of file diff --git a/core/util/rgb2luv.cpp b/core/util/rgb2luv.cpp new file mode 100644 index 0000000..a90f269 --- /dev/null +++ b/core/util/rgb2luv.cpp @@ -0,0 +1,37 @@ + +#include "platform/platform.h" +#include "core/util/rgb2luv.h" + +#include "core/util/rgb2xyz.h" +#include "math/mPoint3.h" +#include "math/mPoint2.h" + + +namespace ConvertRGB +{ + +ColorF toLUV( const ColorF &rgbColor ) +{ + static const Point3F scXYZLUVDot( 1.0f, 15.0f, 3.0f ); + static const Point2F sc49( 4.0f, 9.0f ); + + ColorF xyzColor = ConvertRGB::toXYZ( rgbColor ); + + const Point2F &xyz_xy = *((Point2F *)&xyzColor); + + Point2F uvColor = sc49; + uvColor.convolve( xyz_xy ); + uvColor /= mDot( *(Point3F *)&xyzColor, scXYZLUVDot ); + + return ColorF( uvColor.x, uvColor.y, xyzColor.green, rgbColor.alpha ); +} + +ColorF toLUVScaled( const ColorF &rgbColor ) +{ + ColorF luvColor = toLUV( rgbColor ); + luvColor.red /= 0.62f; + luvColor.green /= 0.62f; + return luvColor; +} + +} diff --git a/core/util/rgb2luv.h b/core/util/rgb2luv.h new file mode 100644 index 0000000..62c1b41 --- /dev/null +++ b/core/util/rgb2luv.h @@ -0,0 +1,15 @@ +#ifndef _RGB2LUV_H_ +#define _RGB2LUV_H_ + +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + +namespace ConvertRGB +{ + ColorF toLUV( const ColorF &rgbColor ); + ColorF toLUVScaled( const ColorF &rgbColor ); + ColorF fromLUV( const ColorF &luvColor ); +}; + +#endif \ No newline at end of file diff --git a/core/util/rgb2xyz.cpp b/core/util/rgb2xyz.cpp new file mode 100644 index 0000000..d53a746 --- /dev/null +++ b/core/util/rgb2xyz.cpp @@ -0,0 +1,46 @@ + +#include "platform/platform.h" +#include "core/util/rgb2xyz.h" + +#include "math/mMatrix.h" + + +namespace ConvertRGB +{ + +// http://www.w3.org/Graphics/Color/sRGB +static const F32 scRGB2XYZ[] = +{ + 0.4124f, 0.3576f, 0.1805f, 0.0f, + 0.2126f, 0.7152f, 0.0722f, 0.0f, + 0.0193f, 0.1192f, 0.9505f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, +}; + +static const F32 scXYZ2RGB[] = +{ + 3.2410f, -1.5374f, -0.4986f, 0.0f, + -0.9692f, 1.8760f, 0.0416f, 0.0f, + 0.0556f, -0.2040f, 1.0570f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, +}; + +ColorF toXYZ( const ColorF &rgbColor ) +{ + const MatrixF &rgb2xyz = *((MatrixF *)scRGB2XYZ); + + ColorF retColor = rgbColor; + rgb2xyz.mul( *(Point4F *)&retColor ); + return retColor; +} + +ColorF fromXYZ( const ColorF &xyzColor ) +{ + const MatrixF &xyz2rgb = *((MatrixF *)scXYZ2RGB); + + ColorF retColor = xyzColor; + xyz2rgb.mul( *(Point4F *)&retColor ); + return retColor; +} + +} diff --git a/core/util/rgb2xyz.h b/core/util/rgb2xyz.h new file mode 100644 index 0000000..3683d7d --- /dev/null +++ b/core/util/rgb2xyz.h @@ -0,0 +1,12 @@ +#ifndef _RGB2XYZ_H_ +#define _RGB2XYZ_H_ + +#include "core/color.h" + +namespace ConvertRGB +{ + ColorF toXYZ( const ColorF &rgbColor ); + ColorF fromXYZ( const ColorF &xyzColor ); +}; + +#endif \ No newline at end of file diff --git a/core/util/safeCast.h b/core/util/safeCast.h new file mode 100644 index 0000000..f3a0ece --- /dev/null +++ b/core/util/safeCast.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_CORE_UTIL_SAFECAST_H_ +#define _TORQUE_CORE_UTIL_SAFECAST_H_ + +#include "platform/platform.h" + +template< class T, typename I > +inline T* safeCast( I* inPtr ) +{ + if( !inPtr ) + return 0; + else + { + T* outPtr = dynamic_cast< T* >( inPtr ); + AssertFatal( outPtr != 0, "safeCast failed" ); + return outPtr; + } +} + +template<> +inline void* safeCast< void >( void* inPtr ) +{ + return inPtr; +} + +template< class T, typename I > +inline T* safeCastISV( I* inPtr ) +{ + if( !inPtr ) + return 0; + else + { + T* outPtr = dynamic_cast< T* >( inPtr ); + AssertISV( outPtr != 0, "safeCast failed" ); + return outPtr; + } +} + +#endif // _TORQUE_CORE_UTIL_SAFECAST_H_ diff --git a/core/util/safeDelete.h b/core/util/safeDelete.h new file mode 100644 index 0000000..6fcee5b --- /dev/null +++ b/core/util/safeDelete.h @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +/// @addtogroup utility_macros Utility Macros +// @{ + +#undef SAFE_DELETE + +//----------------------------------------------------------------------------- +/// @brief Safely delete an object and set the pointer to NULL +/// +/// @param a Object to delete +/// @see #SAFE_DELETE_ARRAY(), #SAFE_DELETE_OBJECT(), #SAFE_FREE(), #SAFE_FREE_REFERENCE() +//----------------------------------------------------------------------------- +#define SAFE_DELETE(a) {delete (a); (a) = NULL; } + +#undef SAFE_DELETE_ARRAY + +//----------------------------------------------------------------------------- +/// @brief Safely delete an array and set the pointer to NULL +/// +/// @param a Array to delete +/// @see #SAFE_DELETE(), #SAFE_DELETE_OBJECT(), #SAFE_FREE(), #SAFE_FREE_REFERENCE() +//----------------------------------------------------------------------------- +#define SAFE_DELETE_ARRAY(a) { delete [] (a); (a) = NULL; } + +#undef SAFE_DELETE_OBJECT + +//----------------------------------------------------------------------------- +/// @brief Safely delete a SimObject and set the pointer to NULL +/// +/// @param a Object to delete +/// @see #SAFE_DELETE_ARRAY(), #SAFE_DELETE(), #SAFE_FREE(), #SAFE_FREE_REFERENCE() +//----------------------------------------------------------------------------- +#define SAFE_DELETE_OBJECT(a) { if( (a) != NULL ) (a)->deleteObject(); (a) = NULL; } + +#undef SAFE_FREE + +//----------------------------------------------------------------------------- +/// @brief Safely free memory and set the pointer to NULL +/// +/// @param a Pointer to memory to free +/// @see #SAFE_DELETE_ARRAY(), #SAFE_DELETE_OBJECT(), #SAFE_DELETE(), #SAFE_FREE_REFERENCE() +//----------------------------------------------------------------------------- +#define SAFE_FREE(a) { if( (a) != NULL ) dFree ((void *)a); (a) = NULL; } + +// CodeReview: Is the NULL conditional needed? [5/14/2007 Pat] + +#undef SAFE_FREE_REFERENCE + +//----------------------------------------------------------------------------- +/// @brief Safely free a reference to a Message and set the pointer to NULL +/// +/// @param a Pointer to message to free +/// @see #SAFE_DELETE_ARRAY(), #SAFE_DELETE_OBJECT(), #SAFE_FREE(), #SAFE_DELETE() +//----------------------------------------------------------------------------- +#define SAFE_FREE_REFERENCE(a) { if((a) != NULL) (a)->freeReference(); (a) = NULL; } + +#undef SAFE_DELETE_MESSAGE + +//----------------------------------------------------------------------------- +/// @brief Synonym for SAFE_FREE_REFERENCE() +/// +/// @param a Object to delete +/// @see #SAFE_DELETE(), #SAFE_DELETE_ARRAY(), #SAFE_DELETE_OBJECT(), #SAFE_FREE(), #SAFE_FREE_REFERENCE() +//----------------------------------------------------------------------------- +#define SAFE_DELETE_MESSAGE SAFE_FREE_REFERENCE + +// @} diff --git a/core/util/safeRelease.h b/core/util/safeRelease.h new file mode 100644 index 0000000..83ea913 --- /dev/null +++ b/core/util/safeRelease.h @@ -0,0 +1,8 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef SAFE_RELEASE +# define SAFE_RELEASE(x) if( x != NULL ) { x->Release(); x = NULL; } +#endif \ No newline at end of file diff --git a/core/util/str.cpp b/core/util/str.cpp new file mode 100644 index 0000000..48c5d66 --- /dev/null +++ b/core/util/str.cpp @@ -0,0 +1,1432 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include + +#include "platform/platform.h" + +// Sigh... guess what compiler needs this... +namespace DictHash { U32 hash( String::StringData* ); } +namespace KeyCmp +{ + template< typename Key > bool equals( const Key&, const Key& ); + template<> bool equals<>( String::StringData* const&, String::StringData* const& ); +} + +#include "core/util/str.h" +#include "core/util/tDictionary.h" +#include "core/strings/stringFunctions.h" +#include "core/strings/unicode.h" +#include "core/util/hashFunction.h" +#include "core/util/autoPtr.h" +#include "core/util/tVector.h" +#include "core/dataChunker.h" +#include "console/console.h" + +#include "math/mMathFn.h" + +#include "platform/platform.h" +#include "platform/profiler.h" +#include "platform/platformIntrinsics.h" +#include "platform/threads/mutex.h" + +#ifndef TORQUE_DISABLE_MEMORY_MANAGER +# undef new +#else +# define _new new +#endif + +const String::SizeType String::NPos = U32(~0); +const String String::EmptyString; + +/// A delete policy for the AutoPtr class +struct DeleteString +{ + template + static void destroy(T *ptr) { dFree(ptr); } +}; + + +//----------------------------------------------------------------------------- + +/// Search for a character. +/// Search for the position of the needle in the haystack. +/// Default mode is StrCase | StrLeft, mode also accepts StrNoCase and StrRight. +/// If pos is non-zero, then in mode StrLeft the search starts at (hay + pos) and +/// in mode StrRight the search starts at (hay + pos - 1) +/// @return Returns a pointer to the location of the character in the haystack or 0 +static const char* StrFind(const char* hay, char needle, S32 pos, U32 mode) +{ + if (mode & String::Right) + { + // Go to the end first, then search backwards + const char *he = hay; + + if (pos) + { + he += pos - 1; + } + else + { + while (*he) + he++; + } + + if (mode & String::NoCase) + { + needle = dTolower(needle); + + for (; he >= hay; he--) + { + if (dTolower(*he) == needle) + return he; + } + } + else + { + for (; he >= hay; he--) + { + if (*he == needle) + return he; + } + } + return 0; + } + else + { + if (mode & String::NoCase) + { + needle = dTolower(needle); + for (hay += pos; *hay && dTolower(*hay) != needle;) + hay++; + } + else + { + for (hay += pos; *hay && *hay != needle;) + hay++; + } + + return *hay ? hay : 0; + } +} + +/// Search for a StringData. +/// Search for the position of the needle in the haystack. +/// Default mode is StrCase | StrLeft, mode also accepts StrNoCase and StrRight. +/// If pos is non-zero, then in mode StrLeft the search starts at (hay + pos) and +/// in mode StrRight the search starts at (hay + pos - 1) +/// @return Returns a pointer to the StringData in the haystack or 0 +static const char* StrFind(const char* hay, const char* needle, S32 pos, U32 mode) +{ + if (mode & String::Right) + { + const char *he = hay; + + if (pos) + { + he += pos - 1; + } + else + { + while (*he) + he++; + } + + if (mode & String::NoCase) + { + AutoPtr ln(dStrlwr(dStrdup(needle))); + for (; he >= hay; he--) + { + if (dTolower(*he) == *ln) + { + U32 i = 0; + while (ln[i] && ln[i] == dTolower(he[i])) + i++; + if (!ln[i]) + return he; + if (!hay[i]) + return 0; + } + } + } + else + { + for (; he >= hay; he--) + { + if (*he == *needle) + { + U32 i = 0; + while (needle[i] && needle[i] == he[i]) + i++; + if (!needle[i]) + return he; + if (!hay[i]) + return 0; + } + } + } + return 0; + } + else + { + if (mode & String::NoCase) + { + AutoPtr ln(dStrlwr(dStrdup(needle))); + for (hay += pos; *hay; hay++) + { + if (dTolower(*hay) == *ln) + { + U32 i = 0; + while (ln[i] && ln[i] == dTolower(hay[i])) + i++; + if (!ln[i]) + return hay; + if (!hay[i]) + return 0; + } + } + } + else + { + for (hay += pos; *hay; hay++) + { + if (*hay == *needle) + { + U32 i = 0; + while (needle[i] && needle[i] == hay[i]) + i++; + if (!needle[i]) + return hay; + if (!hay[i]) + return 0; + } + } + } + } + + return 0; +} + +//----------------------------------------------------------------------------- + +/// +class String::StringData +{ + protected: + +#ifdef TORQUE_DEBUG + StringChar* mString; ///< so we can inspect data in a debugger +#endif + + U32 mRefCount; ///< String reference count; string is not refcounted if this is U32_MAX (necessary for thread-safety of interned strings and the empty string). + U32 mLength; ///< String length in bytes excluding null. + mutable U32 mNumChars; ///< Character count; varies from byte count for strings with multi-bytes characters. + mutable U32 mHashCase; ///< case-sensitive hash + mutable U32 mHashNoCase; ///< case-insensitive hash + mutable UTF16* mUTF16; + bool mIsInterned; ///< If true, this string is interned in the string table. + StringChar mData[1]; ///< Start of string data + + /// This constructor is only used for constructing the empty string. + StringData() : mRefCount(U32_MAX), mLength(0), mNumChars(0), + mHashCase(U32_MAX), mHashNoCase(U32_MAX), mUTF16(new UTF16[1]), + mData(), mIsInterned( false ) + { + // operator new() will never call this constructor which is why we + // initialized mLength in the initializer list. + mData[0] = '\0'; + mUTF16[ 0 ] = '\0'; + +#ifdef TORQUE_DEBUG + mString = &mData[0]; +#endif + } + + public: + + enum { MAX_HASH_LENGTH = 64 }; + + /// + StringData( const StringChar* data, bool interned = false ) : mRefCount(1), mNumChars(U32_MAX), + mHashCase(U32_MAX), mHashNoCase(U32_MAX), mUTF16(NULL), mIsInterned( interned ) + { + // mLength is initialized by operator new() + + if( data ) + { + dMemcpy( mData, data, sizeof( StringChar ) * mLength ); + mData[ mLength ] = '\0'; + } + +#ifdef TORQUE_DEBUG + mString = &mData[0]; +#endif + if( mIsInterned ) + mRefCount = U32_MAX; + } + + ~StringData() + { + if( mUTF16 ) + delete [] mUTF16; + } + + void* operator new(size_t size, U32 len); + void* operator new( size_t size, U32 len, DataChunker& chunker ); + void operator delete(void *); + + bool isShared() const + { + return ( mRefCount > 1 ); + } + void addRef() + { + if( mRefCount != U32_MAX ) + mRefCount ++; + } + void release() + { + if( mRefCount != U32_MAX ) + { + -- mRefCount; + if( !mRefCount ) + delete this; + } + } + U32 getLength() const + { + return mLength; + } + U32 getDataSize() const + { + return ( mLength + 1 ); + } + U32 getDataSizeUTF16() const + { + return ( mLength * sizeof( UTF16 ) ); + } + UTF8 operator []( U32 index ) const + { + AssertFatal( index < mLength, "String::StringData::operator []() - index out of range" ); + return mData[ index ]; + } + UTF8* utf8() + { + return mData; + } + const UTF8* utf8() const + { + return mData; + } + UTF16* utf16() const + { + // IMPORTANT: if interned strings are added, setting mUTF16 must + // be done atomatically and the converted string freed if the CAS fails. + + if( !mUTF16 ) + mUTF16 = convertUTF8toUTF16( mData ); + return mUTF16; + } + U32 getHashCase() const + { + return mHashCase; + } + U32 getOrCreateHashCase() const + { + if( mHashCase == U32_MAX ) + mHashCase = Torque::hash((const U8 *)(mData), getMin( mLength, ( U32 ) MAX_HASH_LENGTH ), 0); + return mHashCase; + } + U32 getHashNoCase() const + { + return mHashNoCase; + } + U32 getOrCreateHashNoCase() const + { + if( mHashNoCase == U32_MAX) + { + PROFILE_SCOPE(String_getHashCaseInsensitive); + + StringChar sLowerBuffer[ MAX_HASH_LENGTH ]; + U32 len = getMin( mLength, ( U32 ) MAX_HASH_LENGTH - 1 ); + + dMemcpy(sLowerBuffer, utf8(), len ); + sLowerBuffer[ len ] = '\0'; + dStrlwr(sLowerBuffer); + + mHashNoCase = Torque::hash((const U8 *)(sLowerBuffer), len, 0); + } + + return mHashNoCase; + } + U32 getNumChars() const + { + //TODO + AssertFatal( false, "TODO" ); + + return mNumChars; + } + bool isInterned() const + { + return mIsInterned; + } + static StringData* Empty() + { + static StringData empty; + return ∅ + } +}; + +//----------------------------------------------------------------------------- + +namespace DictHash +{ + inline U32 hash( String::StringData* data ) + { + return data->getOrCreateHashCase(); + } +} +namespace KeyCmp +{ + template<> + inline bool equals<>( String::StringData* const& d1, String::StringData* const& d2 ) + { + return ( dStrcmp( d1->utf8(), d2->utf8() ) == 0 ); + } +} + +/// Type for the intern string table. We don't want String instances directly +/// on the table so that destructors don't run when the table is destroyed. This +/// is because we really shouldn't depend on dtor ordering within this file and thus +/// we can't tell whether the intern string memory is freed before or after the +/// table is destroyed. +typedef HashTable< String::StringData*, String::StringData* > StringTable; + +static Mutex sStringTableLock; +static DataChunker sStringTableChunker; +static StringTable sStringTable; + +//----------------------------------------------------------------------------- + +#ifdef TORQUE_DEBUG + +/// Tracks the number of bytes allocated for strings. +/// @bug This currently does not include UTF16 allocations. +static U32 sgStringMemBytes; + +/// Tracks the number of Strings which are currently instantiated. +static U32 sgStringInstances; + +ConsoleFunction( dumpStringMemStats, void, 1, 1, "() - Dumps information about String memory usage" ) +{ + Con::printf( "String Data: %i instances, %i bytes", sgStringInstances, sgStringMemBytes ); +} + +#endif + +//----------------------------------------------------------------------------- + +void* String::StringData::operator new( size_t size, U32 len ) +{ + AssertFatal( len != 0, "String::StringData::operator new() - string must not be empty" ); + StringData *str = reinterpret_cast( dMalloc( size + len * sizeof(StringChar) ) ); + + str->mLength = len; + +#ifdef TORQUE_DEBUG + dFetchAndAdd( sgStringMemBytes, size + len * sizeof(StringChar) ); + dFetchAndAdd( sgStringInstances, 1 ); +#endif + + return str; +} + +void String::StringData::operator delete(void *ptr) +{ + StringData* sub = static_cast(ptr); + AssertFatal( sub->mRefCount == 0, "StringData::delete() - invalid refcount" ); + +#ifdef TORQUE_DEBUG + dFetchAndAdd( sgStringMemBytes, U32( -( S32( sizeof( StringData ) + sub->mLength * sizeof(StringChar) ) ) ) ); + dFetchAndAdd( sgStringInstances, U32( -1 ) ); +#endif + + dFree( ptr ); +} + +void* String::StringData::operator new( size_t size, U32 len, DataChunker& chunker ) +{ + AssertFatal( len != 0, "String::StringData::operator new() - string must not be empty" ); + StringData *str = reinterpret_cast( chunker.alloc( size + len * sizeof(StringChar) ) ); + + str->mLength = len; + +#ifdef TORQUE_DEBUG + dFetchAndAdd( sgStringMemBytes, size + len * sizeof(StringChar) ); + dFetchAndAdd( sgStringInstances, 1 ); +#endif + + return str; +} + +//----------------------------------------------------------------------------- + +String::String() +{ + PROFILE_SCOPE(String_default_constructor); + _string = StringData::Empty(); +} + +String::String(const String &str) +{ + PROFILE_SCOPE(String_String_constructor); + _string = str._string; + _string->addRef(); +} + +String::String(const StringChar *str) +{ + PROFILE_SCOPE(String_char_constructor); + if( str && *str ) + { + U32 len = dStrlen(str); + _string = new ( len ) StringData( str ); + } + else + _string = StringData::Empty(); +} + +String::String(const StringChar *str, SizeType len) +{ + PROFILE_SCOPE(String_char_len_constructor); + if (str && *str && len!=0) + { + AssertFatal(len<=dStrlen(str), "String::String: string too short"); + _string = new ( len ) StringData( str ); + } + else + _string = StringData::Empty(); +} + +String::String(const UTF16 *str) +{ + PROFILE_SCOPE(String_UTF16_constructor); + + if( str && str[ 0 ] ) + { + UTF8* utf8 = convertUTF16toUTF8( str ); + U32 len = dStrlen( utf8 ); + _string = new ( len ) StringData( utf8 ); + delete [] utf8; + } + else + _string = StringData::Empty(); +} + +String::~String() +{ + _string->release(); +} + +//----------------------------------------------------------------------------- + +String String::intern() const +{ + if( isInterned() ) + return *this; + + // Lock the string table. + + MutexHandle mutex; + mutex.lock( &sStringTableLock ); + + // Lookup. + + StringTable::Iterator iter = sStringTable.find( _string ); + if( iter != sStringTable.end() ) + return ( *iter ).value; + + // Create new. + + StringData* data = new ( length(), sStringTableChunker ) StringData( c_str(), true ); + iter = sStringTable.insertUnique( data, data ); + + return ( *iter ).value; +} + +//----------------------------------------------------------------------------- + +const StringChar* String::c_str() const +{ + return _string->utf8(); +} + +const UTF16 *String::utf16() const +{ + return _string->utf16(); +} + +String::SizeType String::length() const +{ + return _string->getLength(); +} + +String::SizeType String::size() const +{ + return _string->getDataSize(); +} + +String::SizeType String::numChars() const +{ + return _string->getNumChars(); +} + +bool String::isEmpty() const +{ + return ( _string == StringData::Empty() ); +} + +bool String::isShared() const +{ + return _string->isShared(); +} + +bool String::isSame( const String& str ) const +{ + return ( _string == str._string ); +} + +bool String::isInterned() const +{ + return ( _string->isInterned() ); +} + +U32 String::getHashCaseSensitive() const +{ + return _string->getOrCreateHashCase(); +} + +U32 String::getHashCaseInsensitive() const +{ + return _string->getOrCreateHashNoCase(); +} + +//----------------------------------------------------------------------------- + +String::SizeType String::find(const String &str, SizeType pos, U32 mode) const +{ + return find(str._string->utf8(), pos, mode); +} + +String& String::insert(SizeType pos, const String &str) +{ + return insert(pos, str._string->utf8()); +} + +String& String::replace(SizeType pos, SizeType len, const String &str) +{ + return replace(pos, len, str._string->utf8()); +} + +//----------------------------------------------------------------------------- + +String& String::operator=(StringChar c) +{ + _string->release(); + + _string = new ( 2 ) StringData( 0 ); + _string->utf8()[ 0 ] = c; + _string->utf8()[ 1 ] = '\0'; + + return *this; +} + +String& String::operator+=(StringChar c) +{ + // Append the given string into a new string + U32 len = _string->getLength(); + StringData* sub = new ( len + 1 ) StringData( NULL ); + + copy( sub->utf8(), _string->utf8(), len ); + sub->utf8()[len] = c; + sub->utf8()[len+1] = 0; + + _string->release(); + _string = sub; + + return *this; +} + +//----------------------------------------------------------------------------- + +String& String::operator=(const StringChar *str) +{ + // Protect against self assignment which is not only a + // waste of time, but can also lead to the string being + // freed before it can be reassigned. + if ( _string->utf8() == str ) + return *this; + + _string->release(); + + if (str && *str) + { + U32 len = dStrlen(str); + _string = new ( len ) StringData( str ); + } + else + _string = StringData::Empty(); + + return *this; +} + +String& String::operator=(const String &src) +{ + // Inc src first to avoid assignment to self problems. + src._string->addRef(); + + _string->release(); + _string = src._string; + + return *this; +} + +String& String::operator+=(const StringChar *src) +{ + if( src == NULL && !*src ) + return *this; + + // Append the given string into a new string + U32 lena = _string->getLength(); + U32 lenb = dStrlen(src); + U32 newlen = lena + lenb; + + StringData* sub; + if( !newlen ) + sub = StringData::Empty(); + else + { + sub = new ( newlen ) StringData( NULL ); + + copy(sub->utf8(),_string->utf8(),lena); + copy(sub->utf8() + lena,src,lenb + 1); + } + + _string->release(); + _string = sub; + + return *this; +} + +String& String::operator+=(const String &src) +{ + if( src.isEmpty() ) + return *this; + + // Append the given string into a new string + U32 lena = _string->getLength(); + U32 lenb = src._string->getLength(); + U32 newlen = lena + lenb; + + StringData* sub; + if( !newlen ) + sub = StringData::Empty(); + else + { + sub = new ( newlen ) StringData( NULL ); + + copy(sub->utf8(),_string->utf8(),lena); + copy(sub->utf8() + lena,src._string->utf8(),lenb + 1); + } + + _string->release(); + _string = sub; + + return *this; +} + +//----------------------------------------------------------------------------- + +String operator+(const String &a, const String &b) +{ + if( a.isEmpty() ) + return b; + else if( b.isEmpty() ) + return a; + + U32 lena = a.length(); + U32 lenb = b.length(); + + String::StringData *sub = new ( lena + lenb ) String::StringData( NULL ); + + String::copy(sub->utf8(),a._string->utf8(),lena); + String::copy(sub->utf8() + lena,b._string->utf8(),lenb + 1); + + return String(sub); +} + +String operator+(const String &a, StringChar c) +{ + U32 lena = a.length(); + String::StringData *sub = new ( lena + 1 ) String::StringData( NULL ); + + String::copy(sub->utf8(),a._string->utf8(),lena); + + sub->utf8()[lena] = c; + sub->utf8()[lena+1] = 0; + + return String(sub); +} + +String operator+(StringChar c, const String &a) +{ + U32 lena = a.length(); + String::StringData *sub = new ( lena + 1 ) String::StringData( NULL ); + + String::copy(sub->utf8() + 1,a._string->utf8(),lena + 1); + sub->utf8()[0] = c; + + return String(sub); +} + +String operator+(const String &a, const StringChar *b) +{ + AssertFatal(b,"String:: Invalid null ptr argument"); + + if( a.isEmpty() ) + return String( b ); + + U32 lena = a.length(); + U32 lenb = dStrlen(b); + + if( !lenb ) + return a; + + String::StringData *sub = new ( lena + lenb ) String::StringData( NULL ); + + String::copy(sub->utf8(),a._string->utf8(),lena); + String::copy(sub->utf8() + lena,b,lenb + 1); + + return String(sub); +} + +String operator+(const StringChar *a, const String &b) +{ + AssertFatal(a,"String:: Invalid null ptr argument"); + + if( b.isEmpty() ) + return String( a ); + + U32 lena = dStrlen(a); + if( !lena ) + return b; + + U32 lenb = b.length(); + + String::StringData* sub = new ( lena + lenb ) String::StringData( NULL ); + + String::copy(sub->utf8(),a,lena); + String::copy(sub->utf8() + lena,b._string->utf8(),lenb + 1); + + return String(sub); +} + +bool String::operator==(const String &str) const +{ + if( str._string == _string ) + return true; + else if( str._string->isInterned() && _string->isInterned() ) + return false; + else if( str.length() != length() ) + return false; + else if( str._string->getHashCase() != U32_MAX + && _string->getHashCase() != U32_MAX + && str._string->getHashCase() != _string->getHashCase() ) + return false; + else + return ( dMemcmp( str._string->utf8(), _string->utf8(), _string->getLength() ) == 0 ); +} + +bool String::operator<(const String &str) const +{ + return ( dStrnatcmp( _string->utf8(), str._string->utf8() ) < 0 ); +} + +bool String::operator>(const String &str) const +{ + return ( dStrnatcmp( _string->utf8(), str._string->utf8() ) > 0 ); +} + +bool String::operator<=(const String &str) const +{ + return ( dStrnatcmp( _string->utf8(), str._string->utf8() ) <= 0 ); +} + +bool String::operator>=(const String &str) const +{ + return ( dStrnatcmp( _string->utf8(), str._string->utf8() ) >= 0 ); +} + +//----------------------------------------------------------------------------- +// Base functions for string comparison + +S32 String::compare(const StringChar *str, SizeType len, U32 mode) const +{ + AssertFatal(str,"String:: Invalid null ptr argument"); + + const StringChar *p1 = _string->utf8(); + const StringChar *p2 = str; + + if (p1 == p2) + return 0; + + if( mode & String::Right ) + { + U32 n = len; + if( n > length() ) + n = length(); + + p1 += length() - n; + p2 += dStrlen( str ) - n; + } + + if (mode & String::NoCase) + { + if (len) + { + for (;--len; p1++,p2++) + { + if (dTolower(*p1) != dTolower(*p2) || !*p1) + break; + } + } + else + { + while (dTolower(*p1) == dTolower(*p2) && *p1) + { + p1++; + p2++; + } + } + + return dTolower(*p1) - dTolower(*p2); + } + + if (len) + return dMemcmp(p1,p2,len); + + while (*p1 == *p2 && *p1) + { + p1++; + p2++; + } + + return *p1 - *p2; +} + +S32 String::compare(const String &str, SizeType len, U32 mode) const +{ + if ( str._string == _string ) + return 0; + + return compare( str.c_str(), len, mode ); +} + +bool String::equal(const String &str, U32 mode) const +{ + if( !mode ) + return ( *this == str ); + else + { + if( _string == str._string ) + return true; + else if( _string->isInterned() && str._string->isInterned() ) + return false; + else if( length() != str.length() ) + return false; + else if( _string->getHashNoCase() != U32_MAX + && str._string->getHashNoCase() != U32_MAX + && _string->getHashNoCase() != str._string->getHashNoCase() ) + return false; + else + return ( compare( str.c_str(), length(), mode ) == 0 ); + } +} + +//----------------------------------------------------------------------------- + +String::SizeType String::find(StringChar c, SizeType pos, U32 mode) const +{ + const StringChar* ptr = StrFind(_string->utf8(),c,pos,mode); + + return ptr? SizeType(ptr - _string->utf8()): NPos; +} + +String::SizeType String::find(const StringChar *str, SizeType pos, U32 mode) const +{ + AssertFatal(str,"String:: Invalid null ptr argument"); + + const StringChar* ptr = StrFind(_string->utf8(),str,pos,mode); + + return ptr? SizeType(ptr - _string->utf8()): NPos; +} + + +//----------------------------------------------------------------------------- + +String& String::insert(SizeType pos, const StringChar *str) +{ + AssertFatal(str,"String:: Invalid null ptr argument"); + + return insert(pos,str,dStrlen(str)); +} + +///@todo review for error checking +String& String::insert(SizeType pos, const StringChar *str, SizeType len) +{ + if( !len ) + return *this; + + AssertFatal( str, "String:: Invalid null ptr argument" ); + + SizeType lena = length(); + AssertFatal((pos <= lena),"Calling String::insert with position greater than length"); + U32 newlen = lena + len; + + StringData *sub; + if( !newlen ) + sub = StringData::Empty(); + else + { + sub = new ( newlen ) StringData( NULL ); + + String::copy(sub->utf8(),_string->utf8(),pos); + String::copy(sub->utf8() + pos,str,len); + String::copy(sub->utf8() + pos + len,_string->utf8() + pos,lena - pos + 1); + } + + _string->release(); + _string = sub; + + return *this; +} + +String& String::erase(SizeType pos, SizeType len) +{ + AssertFatal( len != 0, "String::erase() - Calling String::erase with 0 length" ); + AssertFatal( ( pos + len ) <= length(), "String::erase() - Invalid string region" ); + + if( !len ) + return *this; + + SizeType slen = length(); + U32 newlen = slen - len; + + StringData *sub; + if( !newlen ) + sub = StringData::Empty(); + else + { + sub = new ( newlen ) StringData( NULL ); + + if (pos > 0) + String::copy(sub->utf8(),_string->utf8(),pos); + + String::copy(sub->utf8() + pos, _string->utf8() + pos + len, slen - (pos + len) + 1); + } + + _string->release(); + _string = sub; + + return *this; +} + +///@todo review for error checking +String& String::replace(SizeType pos, SizeType len, const StringChar *str) +{ + AssertFatal( str, "String::replace() - Invalid null ptr argument" ); + AssertFatal( len != 0, "String::replace() - Zero length" ); + AssertFatal( ( pos + len ) <= length(), "String::replace() - Invalid string region" ); + + SizeType slen = length(); + SizeType rlen = dStrlen(str); + + U32 newlen = slen - len + rlen; + StringData *sub; + if( !newlen ) + sub = StringData::Empty(); + else + { + sub = new ( newlen ) StringData( NULL ); + + String::copy(sub->utf8(),_string->utf8(), pos); + String::copy(sub->utf8() + pos,str,rlen); + String::copy(sub->utf8() + pos + rlen,_string->utf8() + pos + len,slen - pos - len + 1); + } + + _string->release(); + _string = sub; + + return *this; +} + +String &String::replace( StringChar c1, StringChar c2 ) +{ + if( isEmpty() ) + return *this; + + StringData *sub = new ( length() ) StringData( _string->utf8() ); + + StringChar *c = sub->utf8(); + + while ( *c ) + { + if ( *c == c1 ) + *c = c2; + + c++; + } + + _string->release(); + _string = sub; + + return *this; +} + +String &String::replace(const String &s1, const String &s2) +{ + // Find number of occurrences of s1 and + // Calculate length of the new string... + + const U32 &s1len = s1.length(); + const U32 &s2len = s2.length(); + + U32 pos = 0; + Vector indices; + StringChar *walk = _string->utf8(); + + while ( walk ) + { + // Casting away the const... was there a better way? + walk = (StringChar*)StrFind( _string->utf8(), s1.c_str(), pos, Case|Left ); + if ( walk ) + { + pos = SizeType(walk - _string->utf8()); + indices.push_back( pos ); + pos += s1len; + } + } + + // Early-out, no StringDatas found. + if ( indices.size() == 0 ) + return *this; + + U32 newSize = size() - ( indices.size() * s1len ) + ( indices.size() * s2len ); + StringData *sub; + if( newSize == 1 ) + sub = StringData::Empty(); + else + { + sub = new (newSize - 1 ) StringData( NULL ); + + // Now assemble the new string from the pieces of the old... + + // Index into the old string + pos = 0; + // Index into the new string + U32 newPos = 0; + // Used to store a character count to be memcpy'd + U32 copyCharCount = 0; + + for ( U32 i = 0; i < indices.size(); i++ ) + { + const U32 &index = indices[i]; + + // Number of chars (if any) before the next indexed StringData + copyCharCount = index - pos; + + // Copy chars before the StringData if we have any. + if ( copyCharCount > 0 ) + { + dMemcpy( sub->utf8() + newPos, _string->utf8() + pos, copyCharCount * sizeof(StringChar) ); + newPos += copyCharCount; + } + + // Copy over the replacement string. + if ( s2len > 0 ) + dMemcpy( sub->utf8() + newPos, s2._string->utf8(), s2len * sizeof(StringChar) ); + + newPos += s2len; + pos = index + s1len; + } + + // There could be characters left in the original string after the last + // StringData occurrence, which we need to copy now - outside the loop. + copyCharCount = length() - indices.last() - s1len; + if ( copyCharCount != 0 ) + dMemcpy( sub->utf8() + newPos, _string->utf8() + pos, copyCharCount * sizeof(StringChar) ); + + // Null terminate it! + sub->utf8()[newSize-1] = 0; + } + + _string->release(); + _string = sub; + + return *this; +} + +//----------------------------------------------------------------------------- + +String String::substr(SizeType pos, SizeType len) const +{ + AssertFatal( pos <= length(), "String::substr - Invalid position!" ); + + if ( len == -1 ) + len = length() - pos; + + AssertFatal( len + pos <= length(), "String::substr - Invalid length!" ); + + StringData* sub; + if( !len ) + sub = StringData::Empty(); + else + sub = new ( len ) StringData( _string->utf8() + pos ); + + return sub; +} + +//----------------------------------------------------------------------------- + +String String::trim() const +{ + if( isEmpty() ) + return *this; + + const StringChar* start = _string->utf8(); + while( *start && dIsspace( *start ) ) + start ++; + + const StringChar* end = _string->utf8() + length() - 1; + while( end > start && dIsspace( *end ) ) + end --; + end ++; + + const U32 len = end - start; + if( len == length() ) + return *this; + + StringData* sub; + if( !len ) + sub = StringData::Empty(); + else + sub = new ( len ) StringData( start ); + + return sub; +} + +//----------------------------------------------------------------------------- + +void String::copy(StringChar* dst, const StringChar *src, U32 len) +{ + dMemcpy(dst, src, len * sizeof(StringChar)); +} + +//----------------------------------------------------------------------------- + +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) +// This standard function is not defined when compiling with VC7... +#define vsnprintf _vsnprintf +#endif + +String::StrFormat::~StrFormat() +{ + if( _dynamicBuffer ) + dFree( _dynamicBuffer ); +} + +S32 String::StrFormat::format( const char *format, void *args ) +{ + _len=0; + return formatAppend(format,args); +} + +S32 String::StrFormat::formatAppend( const char *format, void *args ) +{ + // Format into the fixed buffer first. + S32 startLen = _len; + if (_dynamicBuffer == NULL) + { + _len += vsnprintf(_fixedBuffer + _len, sizeof(_fixedBuffer) - _len, format, *(va_list*)args); + if (_len >= 0 && _len < sizeof(_fixedBuffer)) + return _len; + + // Start off the dynamic buffer at twice fixed buffer size + _len = startLen; + _dynamicSize = sizeof(_fixedBuffer) * 2; + _dynamicBuffer = (char*)dMalloc(_dynamicSize); + dMemcpy(_dynamicBuffer, _fixedBuffer, _len + 1); + } + + // Format into the dynamic buffer, if the buffer is not large enough, then + // keep doubling it's size until it is. The buffer is not reallocated + // using reallocate() to avoid unnecessary buffer copying. + _len += vsnprintf(_dynamicBuffer + _len, _dynamicSize - _len, format, *(va_list*)args); + while (_len < 0 || _len >= _dynamicSize) + { + _len = startLen; + char * oldBuffer = _dynamicBuffer; + _dynamicBuffer = (char*)dRealloc(oldBuffer, _dynamicSize *= 2); + dFree(oldBuffer); + _len += vsnprintf(_dynamicBuffer + _len, _dynamicSize - _len, format, *(va_list*)args); + } + + return _len; +} + +S32 String::StrFormat::append(const char * str, S32 len) +{ + if (_dynamicBuffer == NULL) + { + if (_len+len >= 0 && _len+len < sizeof(_fixedBuffer)) + { + dMemcpy(_fixedBuffer + _len, str, len); + _len += len; + _fixedBuffer[_len] = '\0'; + return _len; + } + + _dynamicSize = sizeof(_fixedBuffer) * 2; + _dynamicBuffer = (char*)dMalloc(_dynamicSize); + dMemcpy(_dynamicBuffer, _fixedBuffer, _len + 1); + } + + S32 newSize = _dynamicSize; + while (newSize < _len+len) + newSize *= 2; + if (newSize != _dynamicSize) + _dynamicBuffer = (char*) dRealloc(_dynamicBuffer, newSize); + _dynamicSize = newSize; + dMemcpy(_dynamicBuffer + _len, str, len); + _len += len; + _dynamicBuffer[_len] = '\0'; + return _len; +} + +S32 String::StrFormat::append(const char * str) +{ + return append(str, dStrlen(str)); +} + +char* String::StrFormat::copy( char *buffer ) +{ + dMemcpy(buffer, _dynamicBuffer? _dynamicBuffer: _fixedBuffer, _len+1); + return buffer; +} + +//----------------------------------------------------------------------------- + +String String::ToString(const char *str, ...) +{ + AssertFatal(str,"String:: Invalid null ptr argument"); + + // Use the format object + va_list args; + va_start(args, str); + String ret = VToString(str, args); + va_end(args); + return ret; +} + +String String::VToString(const char* str, void* args) +{ + StrFormat format(str,&args); + + // Copy it into a string + U32 len = format.length(); + StringData* sub; + if( !len ) + sub = StringData::Empty(); + else + { + sub = new ( len ) StringData( NULL ); + + format.copy( sub->utf8() ); + sub->utf8()[ len ] = 0; + } + + return sub; +} + +String String::SpanToString(const char *start, const char *end) +{ + if ( end == start ) + return String(); + + AssertFatal( end > start, "Invalid arguments to String::SpanToString - end is before start" ); + + U32 len = U32(end - start); + StringData* sub = new ( len ) StringData( start ); + + return sub; +} + +String String::ToLower(const String &string) +{ + if ( string.isEmpty() ) + return String(); + + StringData* sub = new ( string.length() ) StringData( string ); + dStrlwr( sub->utf8() ); + + return sub; +} + +String String::ToUpper(const String &string) +{ + if ( string.isEmpty() ) + return String(); + + StringData* sub = new ( string.length() ) StringData( string ); + dStrupr( sub->utf8() ); + + return sub; +} + +String String::GetTrailingNumber(const char* str, S32& number) +{ + // Check for trivial strings + if (!str || !str[0]) + return String::EmptyString; + + // Find the number at the end of the string + String base(str); + const char* p = base.c_str() + base.length() - 1; + + // Ignore trailing whitespace + while ((p != base.c_str()) && dIsspace(*p)) + p--; + + // Need at least one digit! + if (!isdigit(*p)) + return base; + + // Back up to the first non-digit character + while ((p != base.c_str()) && isdigit(*p)) + p--; + + // Convert number => allow negative numbers, treat '_' as '-' for Maya + if ((*p == '-') || (*p == '_')) + number = -dAtoi(p + 1); + else + number = ((p == base.c_str()) ? dAtoi(p) : dAtoi(++p)); + + // Remove space between the name and the number + while ((p > base.c_str()) && dIsspace(*(p-1))) + p--; + + return base.substr(0, p - base.c_str()); +} diff --git a/core/util/str.h b/core/util/str.h new file mode 100644 index 0000000..1df3b0f --- /dev/null +++ b/core/util/str.h @@ -0,0 +1,345 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_STRING_H_ +#define _TORQUE_STRING_H_ + +#include + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + + +typedef UTF8 StringChar; + +/// The String class represents a 0-terminated array of characters. +/// +/// @note Strings are not thread-safe. +class String +{ +public: + class StringData; + + /// Default mode is case sensitive starting from the left + enum Mode + { + Case = 0, ///< Case sensitive + NoCase = 1, ///< Case insensitive + Left = 0, ///< Start at left end of string + Right = 2, ///< Start at right end of string + }; + + typedef U32 SizeType; + typedef StringChar ValueType; + + static const SizeType NPos; ///< Indicates 'not found' when using find() functions + + /// A predefined empty string. + static const String EmptyString; + + String(); + String(const String &str); + String(const StringChar *str); + String(const StringChar *str, SizeType size); + String(const UTF16 *str); + ~String(); + + const UTF8 *c_str() const; ///< Return the string as a native type + const UTF16 *utf16() const; + const UTF8* utf8() const { return c_str(); } + + SizeType length() const; ///< Returns the length of the string in bytes. + SizeType size() const; ///< Returns the length of the string in bytes including the NULL terminator. + SizeType numChars() const; ///< Returns the length of the string in characters. + bool isEmpty() const; ///< Is this an empty string [""]? + bool isNotEmpty() const { return !isEmpty(); } ///< Is this not an empty string [""]? + + /// Erases all characters in a string. + void clear() { *this = EmptyString; } + + bool isShared() const; ///< Is this string's reference count greater than 1? + bool isSame( const String& str ) const; ///< Return true if both strings refer to the same shared data. + + U32 getHashCaseSensitive() const; ///< Get the case-sensitive hash of the string [only calculates the hash as necessary] + U32 getHashCaseInsensitive() const; ///< Get the case-insensitive hash of the string [only calculates the hash as necessary] + + String& operator=(StringChar); + String& operator+=(StringChar); + String& operator=(const StringChar*); + String& operator+=(const StringChar*); + String& operator=(const String&); + String& operator+=(const String&); + + /** + Compare this string with another. + @param str The string to compare against. + @param len If len is non-zero, then at most len characters are compared. + @param mode Comparison mode. + @return Difference between the first two characters that don't match. + */ + S32 compare(const StringChar *str, SizeType len = 0, U32 mode = Case|Left) const; + S32 compare(const String &str, SizeType len = 0, U32 mode = Case|Left) const; ///< @see compare(const StringChar *, SizeType, U32) const + + /** + Compare two strings for equality. + It will use the string hashes to determine inequality. + @param str The string to compare against. + @param mode Comparison mode - case sensitive or not. + */ + bool equal(const String &str, U32 mode = Case) const; + + SizeType find(StringChar c, SizeType pos = 0, U32 mode = Case|Left) const; + SizeType find(const StringChar *str, SizeType pos = 0, U32 mode = Case|Left) const; + SizeType find(const String &str, SizeType pos = 0, U32 mode = Case|Left) const; + + String &insert(SizeType pos, const StringChar c) { return insert(pos,&c,1); } + String &insert(SizeType pos, const StringChar *str); + String &insert(SizeType pos, const String &str); + String &insert(SizeType pos, const StringChar *str, SizeType len); + + String &erase(SizeType pos, SizeType len); + + String &replace(SizeType pos, SizeType len, const StringChar *str); + String &replace(SizeType pos, SizeType len, const String &str); + + /// Replace all occurrences of character 'c1' with 'c2' + String &replace( StringChar c1, StringChar c2 ); + + /// Replace all occurrences of StringData 's1' with StringData 's2' + String &replace(const String &s1, const String &s2); + + String substr( SizeType pos, SizeType len = -1 ) const; + + /// Remove leading and trailing whitespace. + String trim() const; + + operator const StringChar*() const { return c_str(); } + + StringChar operator []( U32 i ) const { return c_str()[i]; } + StringChar operator []( S32 i ) const { return c_str()[i]; } + + bool operator==(const String &str) const; + bool operator!=(const String &str) const { return !(*this == str); } + bool operator<(const String &str) const; + bool operator>(const String &str) const; + bool operator<=(const String &str) const; + bool operator>=(const String &str) const; + + friend String operator+(const String &a, StringChar c); + friend String operator+(StringChar c, const String &a); + friend String operator+(const String &a, const StringChar *b); + friend String operator+(const String &a, const String &b); + friend String operator+(const StringChar *a, const String &b); + +public: + /// @name String Utility routines + /// @{ + + static String ToString(const char *format, ...); + static String VToString(const char* format, void* args); + + static inline String ToString(U32 v) { return ToString("%u", v); } + static inline String ToString(S32 v) { return ToString("%d", v); } + static inline String ToString(F32 v) { return ToString("%g", v); } + + static String SpanToString(const char* start, const char* end); + + static String ToLower(const String &string); + static String ToUpper(const String &string); + + static String GetTrailingNumber(const char* str, S32& number); + + /// @} + + /// @name Interning + /// + /// Interning maps identical strings to unique instances so that equality + /// amounts to simple pointer comparisons. + /// + /// Note that using interned strings within global destructors is not safe + /// as table destruction runs within this phase as well. Uses o interned + /// strings in global destructors is thus dependent on object file ordering. + /// + /// Also, interned strings are not reference-counted. Once interned, a + /// string will persist until shutdown. This is to avoid costly concurrent + /// reference counting that would otherwise be necessary. + /// + /// @{ + + /// Return the interned version of the string. + /// @note Interning is case-sensitive. + String intern() const; + + /// Return true if this string is interned. + bool isInterned() const; + + /// @} + + /** An internal support class for ToString(). + StrFormat manages the formatting of arbitrary length strings. + The class starts with a default internal fixed size buffer and + moves to dynamic allocation from the heap when that is exceeded. + Constructing the class on the stack will result in its most + efficient use. This class is meant to be used as a helper class, + and not for the permanent storage of string data. + @code + char* indexString(U32 index) + { + StrFormat format("Index: %d",index); + char* str = new char[format.size()]; + format.copy(str); + return str; + } + @endcode + */ + class StrFormat + { + public: + StrFormat() + : _dynamicBuffer( NULL ), + _dynamicSize( 0 ), + _len( 0 ) + { + _fixedBuffer[0] = '\0'; + } + + StrFormat(const char *formatStr, void *args) + : _dynamicBuffer( NULL ), + _dynamicSize( 0 ), + _len( 0 ) + { + format(formatStr, args); + } + + ~StrFormat(); + + S32 format( const char *format, void *args ); + S32 formatAppend( const char *format, void *args ); + S32 append(const char * str, S32 len); + S32 append(const char * str); + + String getString() { return String(c_str(),_len); } + + const char * c_str() { return _dynamicBuffer ? _dynamicBuffer : _fixedBuffer; } + + void reset() + { + _len = 0; + _fixedBuffer[0] = '\0'; + } + + /// Copy the formatted string into the output buffer which must be at least size() characters. + char *copy(char* buffer); + + /// Return the length of the formated string (does not include the terminating 0) + U32 length() { return _len; }; + + public: + char _fixedBuffer[2048]; //< Fixed size buffer + char *_dynamicBuffer; //< Temporary format buffer + U32 _dynamicSize; //< Dynamic buffer size + U32 _len; //< Len of the formatted string + }; + +private: + String(StringData *str) + : _string( str ) {} + + // Generate compile error if operator bool is used. Without this we use + // operator const char *, which is always true...including operator bool + // causes an ambiguous cast compile error. Making it private is simply + // more insurance that it isn't used on different compilers. + // NOTE: disable on GCC since it causes hyper casting to U32 on gcc. +#ifndef TORQUE_COMPILER_GCC + operator const bool() const { return false; } +#endif + + + static void copy(StringChar *dst, const StringChar *src, U32 size); + + StringData *_string; +}; + +// Utility class for formatting strings. +class StringBuilder +{ + protected: + + /// + String::StrFormat mFormat; + + public: + + StringBuilder() {} + + String end() + { + return mFormat.getString(); + } + + S32 append( char ch ) + { + char str[2]; + str[0]=ch; + str[1]='\0'; + return mFormat.append(str); + } + S32 append( const char* str ) + { + return mFormat.append(str); + } + S32 append( const String& str ) + { + return mFormat.append( str.c_str(), str.length() ); + } + S32 append( const char* str, U32 length ) + { + return mFormat.append(str,length); + } + S32 format( const char* fmt, ... ) + { + va_list args; + va_start(args, fmt); + return mFormat.formatAppend(fmt, &args); + } +}; + +// For use in hash tables and the like for explicitly requesting case sensitive hashing. +// Meant to only appear in hash table definition (casting will take care of the rest). +class StringCase : public String +{ +public: + StringCase() : String() {} + StringCase(const String & s) : String(s) {} +}; + +// For use in hash tables and the like for explicitly requesting case insensitive hashing. +// Meant to only appear in hash table definition (casting will take care of the rest). +class StringNoCase : public String +{ +public: + StringNoCase() : String() {} + StringNoCase(const String & s) : String(s) {} +}; + +class FileName : public String +{ +public: + FileName() : String() {} + FileName(const String & s) : String(s) {} + FileName & operator=(const String & s) { String::operator=(s); return *this; } +}; + +//----------------------------------------------------------------------------- + +extern String operator+(const String &a, StringChar c); +extern String operator+(StringChar c, const String &a); +extern String operator+(const String &a, const StringChar *b); +extern String operator+(const String &a, const String &b); +extern String operator+(const StringChar *a, const String &b); + +#endif + diff --git a/core/util/swizzle.h b/core/util/swizzle.h new file mode 100644 index 0000000..1b7f40a --- /dev/null +++ b/core/util/swizzle.h @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SWIZZLE_H_ +#define _SWIZZLE_H_ + +#include "platform/platform.h" +#include "core/frameAllocator.h" + +/// This class will swizzle 'sizeof( T )' length chunks of memory into different +/// patterns which are user described. The pattern is described by an instance +/// of Swizzle and this swizzle can then be executed on buffers. The following +/// must be true of the buffer size: +/// size % ( sizeof( T ) * mapLength ) == 0 +template +class Swizzle +{ +private: + /// This is an array from 0..n. Each entry in the array is a dsize_t with values + /// in the range 0..n. Each value in the range 0..n can occur any number of times. + /// + /// For example: + /// This is our data set, an array of characters with 4 elements + /// { 'a', 'b', 'c', 'd' } + /// + /// If the map { 3, 2, 1, 0 } was applied to this set, the result would be: + /// { 'd', 'c', 'b', 'a' } + /// + /// If the map { 3, 0, 2, 2 } was applied to the set, the result would be: + /// { 'd', 'a', 'c', 'c' } + dsize_t mMap[mapLength]; + +public: + /// Construct a swizzle + /// @see Swizzle::mMap + Swizzle( const dsize_t *map ); + + virtual ~Swizzle(){} + + /// This method will, in the general case, use the ToBuffer method to swizzle + /// the memory specified into a temporary buffer, allocated by FrameTemp, and + /// then copy the temporary memory into the source memory. + /// + /// @param memory Pointer to the start of the buffer to swizzle + /// @param size Size of the buffer + virtual void InPlace( void *memory, const dsize_t size ) const; + + /// This method copies the data from source to destination while applying the + /// re-ordering. This method is, in the non-specalized case, O(N^2) where N + /// is sizeof( T ) / size; the number of instances of type 'T' in the buffer + /// + /// @param destination The destination of the swizzled data + /// @param source The source data to be swizzled + /// @param size Size of the source and destination buffers. + virtual void ToBuffer( void *destination, const void *source, const dsize_t size ) const; +}; + +// Null swizzle +template +class NullSwizzle : public Swizzle +{ +public: + NullSwizzle( const dsize_t *map = NULL ) : Swizzle( map ) {}; + + virtual void InPlace( void *memory, const dsize_t size ) const {} + + virtual void ToBuffer( void *destination, const void *source, const dsize_t size ) const + { + dMemcpy( destination, source, size ); + } +}; + +//------------------------------------------------------------------------------ +// Common Swizzles +namespace Swizzles +{ + extern Swizzle bgra; + extern Swizzle argb; + extern Swizzle rgba; + extern Swizzle abgr; + + extern Swizzle bgr; + extern Swizzle rgb; + + extern NullSwizzle null; +}; + +//------------------------------------------------------------------------------ + +template +Swizzle::Swizzle( const dsize_t *map ) +{ + if( map != NULL ) + dMemcpy( mMap, map, sizeof( dsize_t ) * mapLength ); +} + +//------------------------------------------------------------------------------ + +template +inline void Swizzle::ToBuffer( void *destination, const void *source, const dsize_t size ) const +{ + // TODO: OpenMP? + AssertFatal( size % ( sizeof( T ) * mapLength ) == 0, "Bad buffer size for swizzle, see docs." ); + + T *dest = reinterpret_cast( destination ); + const T *src = reinterpret_cast( source ); + + for( int i = 0; i < size / ( mapLength * sizeof( T ) ); i++ ) + { + dMemcpy( dest, src, mapLength * sizeof( T ) ); + + for( int j = 0; j < mapLength; j++ ) + *dest++ = src[mMap[j]]; + + src += mapLength; + } +} + +//------------------------------------------------------------------------------ + +template +inline void Swizzle::InPlace( void *memory, const dsize_t size ) const +{ + // Just in case the inliner messes up the FrameTemp scoping (not sure if it would) -patw + { + // FrameTemp should work because the PNG loading code uses the FrameAllocator, so + // it should only get used on an image w/ that size as max -patw + FrameTemp buffer( size ); + dMemcpy( ~buffer, memory, size ); + ToBuffer( memory, ~buffer, size ); + } +} + +//------------------------------------------------------------------------------ + +// Template specializations for certain swizzles +//#include "core/util/swizzleSpec.h" + +#ifdef TORQUE_OS_XENON +# include "platformXbox/altivecSwizzle.h" +#endif + +#endif \ No newline at end of file diff --git a/core/util/swizzleSpec.h b/core/util/swizzleSpec.h new file mode 100644 index 0000000..e6a7797 --- /dev/null +++ b/core/util/swizzleSpec.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SWIZZLESPEC_H_ +#define _SWIZZLESPEC_H_ + +//------------------------------------------------------------------------------ +// (most common) Specialization +//------------------------------------------------------------------------------ +#include "core/util/byteswap.h" + +template<> +inline void Swizzle::InPlace( void *memory, const dsize_t size ) const +{ + AssertFatal( size % 4 == 0, "Bad buffer size for swizzle, see docs." ); + + U8 *dest = reinterpret_cast( memory ); + U8 *src = reinterpret_cast( memory ); + + // Fast divide by 4 since we are assured a proper size + for( int i = 0; i < size >> 2; i++ ) + { + BYTESWAP( *dest++, src[mMap[0]] ); + BYTESWAP( *dest++, src[mMap[1]] ); + BYTESWAP( *dest++, src[mMap[2]] ); + BYTESWAP( *dest++, src[mMap[3]] ); + + src += 4; + } +} + +template<> +inline void Swizzle::ToBuffer( void *destination, const void *source, const dsize_t size ) const +{ + AssertFatal( size % 4 == 0, "Bad buffer size for swizzle, see docs." ); + + U8 *dest = reinterpret_cast( destination ); + const U8 *src = reinterpret_cast( source ); + + // Fast divide by 4 since we are assured a proper size + for( int i = 0; i < size >> 2; i++ ) + { + *dest++ = src[mMap[0]]; + *dest++ = src[mMap[1]]; + *dest++ = src[mMap[2]]; + *dest++ = src[mMap[3]]; + + src += 4; + } +} + +//------------------------------------------------------------------------------ +// Specialization +//------------------------------------------------------------------------------ + +template<> +inline void Swizzle::InPlace( void *memory, const dsize_t size ) const +{ + AssertFatal( size % 3 == 0, "Bad buffer size for swizzle, see docs." ); + + U8 *dest = reinterpret_cast( memory ); + U8 *src = reinterpret_cast( memory ); + + for( int i = 0; i < size /3; i++ ) + { + BYTESWAP( *dest++, src[mMap[0]] ); + BYTESWAP( *dest++, src[mMap[1]] ); + BYTESWAP( *dest++, src[mMap[2]] ); + + src += 3; + } +} + +template<> +inline void Swizzle::ToBuffer( void *destination, const void *source, const dsize_t size ) const +{ + AssertFatal( size % 3 == 0, "Bad buffer size for swizzle, see docs." ); + + U8 *dest = reinterpret_cast( destination ); + const U8 *src = reinterpret_cast( source ); + + for( int i = 0; i < size / 3; i++ ) + { + *dest++ = src[mMap[0]]; + *dest++ = src[mMap[1]]; + *dest++ = src[mMap[2]]; + + src += 3; + } +} + + +#endif \ No newline at end of file diff --git a/core/util/tAlignedArray.h b/core/util/tAlignedArray.h new file mode 100644 index 0000000..c8b572d --- /dev/null +++ b/core/util/tAlignedArray.h @@ -0,0 +1,186 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ALIGNEDARRAY_H_ +#define _ALIGNEDARRAY_H_ + +/// This is a fixed size class that will align its elements on configurable boundaries. +template +class AlignedArray +{ +public: + AlignedArray(); + /// Create an AlignedArray + /// @param arraySize How many items + /// @param elementSize Size of each element (including padding) + AlignedArray(const U32 arraySize, const U32 elementSize); + /// Create an AlignedArray + /// @param arraySize How many items + /// @param elementSize Size of each element (including padding) + /// @param buffer Preallocated buffer (with data and aligned on elementSize boundaries) + /// @param takeOwn If true, this class will take ownership of the buffer and free on destruct + AlignedArray(const U32 arraySize, const U32 elementSize, U8* buffer, bool takeOwn); + ~AlignedArray(); + + void setCapacity(const U32 arraySize, const U32 elementSize); + void setCapacity(const U32 arraySize, const U32 elementSize, U8* buffer, bool takeOwn); + + /// Size of the array + U32 size() const; + + /// Set a new array size (up to initial size created returned by capacity) + void setSize(U32 newsize); + + /// Capacity of the array (you can setCapacity the size this high) + U32 capacity() const; + + /// Returns the size of an element (useful for asserting, etc) + U32 getElementSize() const; + + /// Returns a pointer to the raw buffer data. + const void* getBuffer() const; + void* getBuffer(); + + // Returns the buffer size in bytes. + U32 getBufferSize() const { return mElementSize * mElementCount; } + + // Operators + T& operator[](const U32); + const T& operator[](const U32) const; +protected: + // How big an element is, this includes padding need to align + U32 mElementSize; + // How many elements do we have + U32 mElementCount; + // How many elements can we have + U32 mCapacity; + // Storage, we use placement new and reinterpret casts to deal with + // alignment + U8* mBuffer; + // Do we own this buffer? Or are we just wrapping it? + bool mOwnBuffer; +}; + +template +inline AlignedArray::AlignedArray() +{ + mElementSize = 0; + mElementCount = 0; + mCapacity = 0; + mBuffer = NULL; + mOwnBuffer = true; +} + +template +inline AlignedArray::AlignedArray(const U32 arraySize, const U32 elementSize) +{ + mElementCount = 0; // avoid debug assert + setCapacity(arraySize, elementSize); +} + +template +inline AlignedArray::AlignedArray(const U32 arraySize, const U32 elementSize, U8* buffer, bool takeOwn) +{ + mElementCount = 0; // avoid debug assert + setCapacity(arraySize, elementSize, buffer, takeOwn); +} + +template +inline AlignedArray::~AlignedArray() +{ + if (mOwnBuffer) + delete[] mBuffer; +} +template +inline void AlignedArray::setCapacity(const U32 arraySize, const U32 elementSize) +{ + AssertFatal(mElementCount == 0, "Unable to set array properties after they are init'ed"); + AssertFatal(elementSize >= sizeof(T), "Element size is too small!"); + AssertFatal(arraySize > 0, "0 length AlignedArrays are not allowed!"); + + mElementSize = elementSize; + mElementCount = arraySize; + mCapacity = arraySize; + mBuffer = new U8[mElementSize * mElementCount]; + dMemset(mBuffer, 0xFF, mElementSize * mElementCount); + U32 bufIndex = 0; + for (U32 i = 0; i < mElementCount; i++) + { + T* item = reinterpret_cast(&mBuffer[bufIndex]); + constructInPlace(item); + bufIndex += mElementSize; + } + mOwnBuffer = true; +} + +template +inline void AlignedArray::setCapacity(const U32 arraySize, const U32 elementSize, U8* buffer, bool takeOwn) +{ + AssertFatal(mElementCount == 0, "Unable to set array properties after they are init'ed"); + AssertFatal(elementSize >= sizeof(T), "Element size is too small!"); + AssertFatal(arraySize > 0, "0 length AlignedArrays are not allowed!"); + AssertFatal(buffer, "NULL buffer!"); + + mElementSize = elementSize; + mElementCount = arraySize; + mCapacity = arraySize; + mBuffer = buffer; + mOwnBuffer = takeOwn; +} + +/// Set a new array size (up to initial size created returned by capacity) +template +inline void AlignedArray::setSize(U32 newsize) +{ + AssertFatal(newsize <= mCapacity, "Unable to grow this type of array!"); + mElementCount = newsize; +} + +template +inline U32 AlignedArray::size() const +{ + return mElementCount; +} + +template +inline U32 AlignedArray::capacity() const +{ + return mCapacity; +} + +/// Returns the size of an element (useful for asserting, etc) +template +U32 AlignedArray::getElementSize() const +{ + return mElementSize; +} + +template +inline T& AlignedArray::operator[](const U32 index) +{ + AssertFatal(index < mElementCount, "AlignedArray::operator[] - out of bounds array access!"); + return reinterpret_cast(mBuffer[index*mElementSize]); +} + +template +inline const T& AlignedArray::operator[](const U32 index) const +{ + AssertFatal(index < mElementCount, "AlignedArray::operator[] - out of bounds array access!"); + return reinterpret_cast(mBuffer[index*mElementSize]); +} + +template +const void* AlignedArray::getBuffer() const +{ + return reinterpret_cast(mBuffer); +} + +template +void* AlignedArray::getBuffer() +{ + return reinterpret_cast(mBuffer); +} + +#endif // _ALIGNEDARRAY_H_ diff --git a/core/util/tDictionary.cpp b/core/util/tDictionary.cpp new file mode 100644 index 0000000..83e5f12 --- /dev/null +++ b/core/util/tDictionary.cpp @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "core/util/tDictionary.h" + +namespace DictHash +{ + +static U32 Primes[] = { + 53ul, 97ul, 193ul, 389ul, 769ul, + 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, + 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, + 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, + 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, + 1610612741ul, 3221225473ul, 4294967291ul +}; + +U32 nextPrime(U32 size) +{ + U32 len = sizeof(Primes) / sizeof(U32); + for(U32 i=0; i size) + return Primes[i]; + + return Primes[len-1]; +} + +}; diff --git a/core/util/tDictionary.h b/core/util/tDictionary.h new file mode 100644 index 0000000..afc90a6 --- /dev/null +++ b/core/util/tDictionary.h @@ -0,0 +1,827 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _TDICTIONARY_H_ +#define _TDICTIONARY_H_ + +#ifndef _STRINGFUNCTIONS_H_ +#include "core/strings/stringFunctions.h" +#endif +#ifndef _HASHFUNCTION_H_ +#include "core/util/hashFunction.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif + +template +struct CompoundKey +{ + A key1; + B key2; + + CompoundKey() {}; + CompoundKey(const A & a, const B & b) { key1 = a; key2 = b; }; + + bool operator==(const CompoundKey & compound) const { return key1==compound.key1 && key2==compound.key2; } +}; + +namespace DictHash +{ + inline U32 hash(U32 data) + { + return data; + } + + inline U32 hash(const StringCase &data) + { + return data.getHashCaseSensitive(); + } + + inline U32 hash(const StringNoCase &data) + { + return data.getHashCaseInsensitive(); + } + + inline U32 hash(const String &data) + { + return data.getHashCaseInsensitive(); + } + + inline U32 hash(const char *data) + { + return Torque::hash( (const U8 *)data, dStrlen( data ), 0 ); + } + + inline U32 hash(const void *data) + { + return (U32)data; + } + + template + inline U32 hash(const CompoundKey & compound) + { + return hash(compound.key1) + hash(compound.key2); + } + + U32 nextPrime(U32); +}; + +namespace KeyCmp +{ + template + inline bool equals( const Key &keya, const Key &keyb ) + { + return ( keya == keyb ); + } + + template<> + inline bool equals<>( const StringCase &keya, const StringCase &keyb ) + { + return ( keya.equal( keyb, String::Case ) ); + } + + template<> + inline bool equals<>( const StringNoCase &keya, const StringNoCase &keyb ) + { + return ( keya.equal( keyb, String::NoCase ) ); + } + + template<> + inline bool equals<>( const String &keya, const String &keyb ) + { + return ( keya.equal( keyb, String::NoCase ) ); + } + + template<> + inline bool equals<>( const char * const &keya, const char * const &keyb ) + { + return ( dStrcmp( keya, keyb ) == 0 ); + } +}; + +/// A HashTable template class. +/// +/// The hash table class maps between a key and an associated value. Access +/// using the key is performed using a hash table. The class provides +/// methods for both unique and equal keys. The global ::hash(Type) function +/// is used for hashing, see util/hash.h +/// @ingroup UtilContainers +template +class HashTable +{ +public: + struct Pair + { + Key key; + Value value; + Pair() {} + Pair(Key k,Value v) + : key(k), + value(v) + {} + }; + +private: + struct Node + { + Node* mNext; + Pair mPair; + Node(): mNext(0) {} + Node(Pair p,Node* n) + : mNext(n), + mPair(p) + {} + }; + + Node** mTable; ///< Hash table + S32 mTableSize; ///< Hash table size + U32 mSize; ///< Number of keys in the table + ClassChunker mNodeAllocator; + + U32 _hash(const Key& key) const; + U32 _index(const Key& key) const; + Node* _next(U32 index) const; + void _resize(U32 size); + void _destroy(); + +public: + // Iterator support + template + class _Iterator { + friend class HashTable; + E* mLink; + M* mHashTable; + operator E*(); + public: + typedef U ValueType; + typedef U* Pointer; + typedef U& Reference; + + _Iterator() + { + mHashTable = 0; + mLink = 0; + } + + _Iterator(M* table,E* ptr) + { + mHashTable = table; + mLink = ptr; + } + + _Iterator& operator++() + { + mLink = mLink->mNext? mLink->mNext : + mHashTable->_next(mHashTable->_index(mLink->mPair.key) + 1); + return *this; + } + + _Iterator operator++(int) + { + _Iterator itr(*this); + ++(*this); + return itr; + } + + bool operator==(const _Iterator& b) const + { + return mHashTable == b.mHashTable && mLink == b.mLink; + } + + bool operator!=(const _Iterator& b) const + { + return !(*this == b); + } + + U* operator->() const + { + return &mLink->mPair; + } + + U& operator*() const + { + return mLink->mPair; + } + }; + + // Types + typedef Pair ValueType; + typedef Pair& Reference; + typedef const Pair& ConstReference; + + typedef _Iterator Iterator; + typedef _Iterator ConstIterator; + typedef S32 DifferenceType; + typedef U32 SizeType; + + // Initialization + HashTable(); + ~HashTable(); + HashTable(const HashTable& p); + + // Management + U32 size() const; ///< Return the number of elements + U32 tableSize() const; ///< Return the size of the hash bucket table + void clear(); ///< Empty the HashTable + void resize(U32 size); + bool isEmpty() const; ///< Returns true if the table is empty + F32 collisions() const; ///< Returns the average number of nodes per bucket + + // Insert & erase elements + Iterator insertEqual(const Key& key, const Value&); + Iterator insertUnique(const Key& key, const Value&); + void erase(Iterator); ///< Erase the given entry + void erase(const Key& key); ///< Erase all matching keys from the table + void erase(const Key & key, const Value & value); ///< Erase entry for this key-value pair + + // HashTable lookup + Iterator findOrInsert(const Key& key); + Iterator find(const Key&); ///< Find the first entry for the given key + ConstIterator find(const Key&) const; ///< Find the first entry for the given key + bool find(const Key & key, Value & value); ///< Find the first entry for the given key + S32 count(const Key&) const; ///< Count the number of matching keys in the table + + // Forward Iterator access + Iterator begin(); ///< Iterator to first element + ConstIterator begin() const; ///< Iterator to first element + Iterator end(); ///< Iterator to last element + 1 + ConstIterator end() const; ///< Iterator to last element + 1 + + void operator=(const HashTable& p); +}; + +template HashTable::HashTable() : mNodeAllocator(512) +{ + mTableSize = 0; + mTable = 0; + mSize = 0; +} + +template HashTable::HashTable(const HashTable& p) : mNodeAllocator(512) +{ + mSize = 0; + mTableSize = 0; + mTable = 0; + *this = p; +} + +template HashTable::~HashTable() +{ + _destroy(); +} + +//----------------------------------------------------------------------------- + +template +inline U32 HashTable::_hash(const Key& key) const +{ + return DictHash::hash(key); +} + +template +inline U32 HashTable::_index(const Key& key) const +{ + return _hash(key) % mTableSize; +} + +template +typename HashTable::Node* HashTable::_next(U32 index) const +{ + for (; index < mTableSize; index++) + if (Node* node = mTable[index]) + return node; + return 0; +} + +template +void HashTable::_resize(U32 size) +{ + S32 currentSize = mTableSize; + mTableSize = DictHash::nextPrime(size); + Node** table = new Node*[mTableSize]; + dMemset(table,0,mTableSize * sizeof(Node*)); + + for (S32 i = 0; i < currentSize; i++) + for (Node* node = mTable[i]; node; ) + { + // Get groups of matching keys + Node* last = node; + while (last->mNext && last->mNext->mPair.key == node->mPair.key) + last = last->mNext; + + // Move the chain to the new table + Node** link = &table[_index(node->mPair.key)]; + Node* tmp = last->mNext; + last->mNext = *link; + *link = node; + node = tmp; + } + + delete[] mTable; + mTable = table; +} + +template +void HashTable::_destroy() +{ + // Call destructors. + for (S32 i = 0; i < mTableSize; i++) + for (Node* ptr = mTable[i]; ptr; ) + { + Node *tmp = ptr; + ptr = ptr->mNext; + destructInPlace( tmp ); + } + + mNodeAllocator.freeBlocks(); + delete[] mTable; + mTable = NULL; +} + + +//----------------------------------------------------------------------------- +// management + +template +inline U32 HashTable::size() const +{ + return mSize; +} + +template +inline U32 HashTable::tableSize() const +{ + return mTableSize; +} + +template +inline void HashTable::clear() +{ + _destroy(); + mTableSize = 0; + mSize = 0; +} + +/// Resize the bucket table for an estimated number of elements. +/// This method will optimize the hash bucket table size to hold the given +/// number of elements. The size argument is a hint, and will not be the +/// exact size of the table. If the table is sized down below it's optimal +/// size, the next insert will cause it to be resized again. Normally this +/// function is used to avoid resizes when the number of elements that will +/// be inserted is known in advance. +template +inline void HashTable::resize(U32 size) +{ + // Attempt to resize the datachunker as well. + mNodeAllocator.setChunkSize(sizeof(Node) * size); + _resize(size); +} + +template +inline bool HashTable::isEmpty() const +{ + return mSize == 0; +} + +template +inline F32 HashTable::collisions() const +{ + S32 chains = 0; + for (S32 i = 0; i < mTableSize; i++) + if (mTable[i]) + chains++; + return F32(mSize) / chains; +} + + +//----------------------------------------------------------------------------- +// add & remove elements + +/// Insert the key value pair but don't insert duplicates. +/// This insert method does not insert duplicate keys. If the key already exists in +/// the table the function will fail and end() is returned. +template +typename HashTable::Iterator HashTable::insertUnique(const Key& key, const Value& x) +{ + if (mSize >= mTableSize) + _resize(mSize + 1); + Node** table = &mTable[_index(key)]; + for (Node* itr = *table; itr; itr = itr->mNext) + if ( KeyCmp::equals( itr->mPair.key, key) ) + return end(); + + mSize++; + Node* newNode = mNodeAllocator.alloc(); + newNode->mPair = Pair(key, x); + newNode->mNext = *table; + *table = newNode; + return Iterator(this,*table); +} + +/// Insert the key value pair and allow duplicates. +/// This insert method allows duplicate keys. Keys are grouped together but +/// are not sorted. +template +typename HashTable::Iterator HashTable::insertEqual(const Key& key, const Value& x) +{ + if (mSize >= mTableSize) + _resize(mSize + 1); + // The new key is inserted at the head of any group of matching keys. + Node** prev = &mTable[_index(key)]; + for (Node* itr = *prev; itr; prev = &itr->mNext, itr = itr->mNext) + if ( KeyCmp::equals( itr->mPair.key, key ) ) + break; + mSize++; + Node* newNode = mNodeAllocator.alloc(); + newNode->mPair = Pair(key, x); + newNode->mNext = *prev; + *prev = newNode; + return Iterator(this,*prev); +} + +template +void HashTable::erase(const Key& key) +{ + if (mTable==NULL) + return; + Node** prev = &mTable[_index(key)]; + for (Node* itr = *prev; itr; prev = &itr->mNext, itr = itr->mNext) + if ( KeyCmp::equals( itr->mPair.key, key ) ) { + // Delete matching keys, which should be grouped together. + do { + Node* tmp = itr; + itr = itr->mNext; + mNodeAllocator.free(tmp); + mSize--; + } while (itr && KeyCmp::equals( itr->mPair.key, key ) ); + *prev = itr; + return; + } +} + +template +void HashTable::erase(Iterator node) +{ + if (mTable==NULL) + return; + Node** prev = &mTable[_index(node->key)]; + for (Node* itr = *prev; itr; prev = &itr->mNext, itr = itr->mNext) + { + if (itr == node.mLink) + { + *prev = itr->mNext; + mNodeAllocator.free(itr); + mSize--; + return; + } + } +} + +template +void HashTable::erase(const Key & key, const Value & value) +{ + if (mTable==NULL) + return; + Node** prev = &mTable[_index(key)]; + for (Node* itr = *prev; itr; prev = &itr->mNext, itr = itr->mNext) + { + if ( KeyCmp::equals( itr->mPair.key, key ) && itr->mPair.value == value) + { + *prev = itr->mNext; + mNodeAllocator.free(itr); + mSize--; + return; + } + } +} + +//----------------------------------------------------------------------------- + +/// Find the key, or insert a one if it doesn't exist. +/// Returns the first key in the table that matches, or inserts one if there +/// are none. +template +typename HashTable::Iterator HashTable::findOrInsert(const Key& key) +{ + if (mSize >= mTableSize) + _resize(mSize + 1); + Node** table = &mTable[_index(key)]; + for (Node* itr = *table; itr; itr = itr->mNext) + if ( KeyCmp::equals( itr->mPair.key, key ) ) + return Iterator(this,itr); + mSize++; + Node* newNode = mNodeAllocator.alloc(); + newNode->mPair = Pair(key, Value()); + newNode->mNext = *table; + *table = newNode; + return Iterator(this,*table); +} + +template +typename HashTable::Iterator HashTable::find(const Key& key) +{ + if (mTableSize) + for (Node* itr = mTable[_index(key)]; itr; itr = itr->mNext) + if ( KeyCmp::equals( itr->mPair.key, key ) ) + return Iterator(this,itr); + return Iterator(this,0); +} + +template +typename HashTable::ConstIterator HashTable::find(const Key& key) const +{ + if (mTableSize) + { + for (Node* itr = mTable[_index(key)]; itr; itr = itr->mNext) + { + if ( KeyCmp::equals( itr->mPair.key, key ) ) + return ConstIterator(this,itr); + } + } + return ConstIterator(this,0); +} + +template +bool HashTable::find(const Key & key, Value & value) +{ + if (mTableSize) + { + for (Node* itr = mTable[_index(key)]; itr; itr = itr->mNext) + if ( KeyCmp::equals( itr->mPair.key, key ) ) + { + value = itr->mPair.value; + return true; + } + } + return false; +} + +template +S32 HashTable::count(const Key& key) const +{ + S32 count = 0; + if (mTableSize) + for (Node* itr = mTable[_index(key)]; itr; itr = itr->mNext) + if ( KeyCmp::equals( itr->mPair.key, key ) ) { + // Matching keys should be grouped together. + do { + count++; + itr = itr->mNext; + } while (itr && KeyCmp::equals( itr->mPair.key, key ) ); + break; + } + return count; +} + + +//----------------------------------------------------------------------------- +// Iterator access + +template +inline typename HashTable::Iterator HashTable::begin() +{ + return Iterator(this,_next(0)); +} + +template +inline typename HashTable::ConstIterator HashTable::begin() const +{ + return ConstIterator(this,_next(0)); +} + +template +inline typename HashTable::Iterator HashTable::end() +{ + return Iterator(this,0); +} + +template +inline typename HashTable::ConstIterator HashTable::end() const +{ + return ConstIterator(this,0); +} + + +//----------------------------------------------------------------------------- +// operators + +template +void HashTable::operator=(const HashTable& p) +{ + _destroy(); + mTableSize = p.mTableSize; + mTable = new Node*[mTableSize]; + mSize = p.mSize; + for (S32 i = 0; i < mTableSize; i++) + if (Node* itr = p.mTable[i]) + { + Node** head = &mTable[i]; + do + { + *head = new Node(itr->mPair,0); + head = &(*head)->mNext; + itr = itr->mNext; + } while (itr); + } + else + mTable[i] = 0; +} + +//----------------------------------------------------------------------------- +// Iterator class + +/// A Map template class. +/// The map class maps between a key and an associated value. Keys +/// are unique. +/// The hash table class is used as the default implementation so the +/// the key must be hashable, see util/hash.h for details. +/// @ingroup UtilContainers +template > +class Map +{ +public: + // types + typedef typename Sequence::Pair Pair; + typedef Pair ValueType; + typedef Pair& Reference; + typedef const Pair& ConstReference; + + typedef typename Sequence::Iterator Iterator; + typedef typename Sequence::ConstIterator ConstIterator; + typedef S32 DifferenceType; + typedef U32 SizeType; + + // initialization + Map() {} + ~Map() {} + Map(const Map& p); + + // management + U32 size() const; ///< Return the number of elements + void resize(U32 size); + void clear(); ///< Empty the Map + bool isEmpty() const; ///< Returns true if the map is empty + + // insert & erase elements + Iterator insert(const Key& key, const Value&); // Documented below... + void erase(Iterator); ///< Erase the given entry + void erase(const Key& key); ///< Erase the key from the map + + // Map lookup + Iterator find(const Key&); ///< Find entry for the given key + ConstIterator find(const Key&) const; ///< Find entry for the given key + bool contains(const Key&a) const + { + return mMap.count(a) > 0; + } + /// Try to get the value of the specified key. Returns true and fills in the value + /// if the key exists. Returns false otherwise. + /// Unlike operator [], this function does not create the key/value if the key + /// is not found. + bool tryGetValue(const Key& key, Value& val) const + { + ConstIterator iter = find(key); + if (iter != end()) + { + val = (*iter).value; + return true; + } + return false; + } + + // forward Iterator access + Iterator begin(); ///< Iterator to first element + ConstIterator begin() const; ///< Iterator to first element + Iterator end(); ///< IIterator to last element + 1 + ConstIterator end() const; ///< Iterator to last element + 1 + + // operators + /// Index using the given key. If the key is not currently in the map it is added. + /// If you just want to try to get the value without the side effect of creating the + /// key, use tryGetValue() instead. + Value& operator[](const Key&); + +private: + Sequence mMap; +}; + +template Map::Map(const Map& p) +{ + *this = p; +} + + +//----------------------------------------------------------------------------- +// management + +template +inline U32 Map::size() const +{ + return mMap.size(); +} + +template +inline void Map::resize(U32 size) +{ + return mMap.resize(size); +} + +template +inline void Map::clear() +{ + mMap.clear(); +} + +template +inline bool Map::isEmpty() const +{ + return mMap.isEmpty(); +} + + +//----------------------------------------------------------------------------- +// add & remove elements + +/// Insert the key value pair but don't allow duplicates. +/// The map class does not allow duplicates keys. If the key already exists in +/// the map the function will fail and return end(). +template +typename Map::Iterator Map::insert(const Key& key, const Value& x) +{ + return mMap.insertUnique(key,x); +} + +template +void Map::erase(const Key& key) +{ + mMap.erase(key); +} + +template +void Map::erase(Iterator node) +{ + mMap.erase(node); +} + + +//----------------------------------------------------------------------------- +// Searching + +template +typename Map::Iterator Map::find(const Key& key) +{ + return mMap.find(key); +} + +template +typename Map::ConstIterator Map::find(const Key& key) const +{ + return mMap.find(key); +} + +//----------------------------------------------------------------------------- +// Iterator access + +template +inline typename Map::Iterator Map::begin() +{ + return mMap.begin(); +} + +template +inline typename Map::ConstIterator Map::begin() const +{ + return mMap.begin(); +} + +template +inline typename Map::Iterator Map::end() +{ + return mMap.end(); +} + +template +inline typename Map::ConstIterator Map::end() const +{ + return mMap.end(); +} + + +//----------------------------------------------------------------------------- +// operators + +template +inline Value& Map::operator[](const Key& key) +{ + return mMap.findOrInsert(key)->value; +} + +#endif + diff --git a/core/util/tFixedSizeDeque.h b/core/util/tFixedSizeDeque.h new file mode 100644 index 0000000..ccd6f86 --- /dev/null +++ b/core/util/tFixedSizeDeque.h @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TFIXEDSIZEDEQUE_H_ +#define _TFIXEDSIZEDEQUE_H_ + +#ifndef _PLATFORM_H_ +# include "platform/platform.h" +#endif + + +/// A double-ended queue with a fixed number of entries set at +/// construction time. Implemented as an array. +/// +/// @param T Element type of the queue. +template< typename T > +class FixedSizeDeque +{ + public: + + /// Type for elements stored in the deque. + typedef T ValueType; + + protected: + + /// + U32 mBufferSize; + + /// + U32 mSize; + + /// Index + U32 mStart; + + /// + U32 mEnd; + + /// + ValueType* mBuffer; + + public: + + /// + FixedSizeDeque( U32 bufferSize ) + : mBufferSize( bufferSize ), mSize( 0 ), mStart( 0 ), mEnd( 0 ) + { + // Don't use new() so we don't get a buffer full of instances. + mBuffer = ( ValueType* ) dMalloc( sizeof( ValueType ) * bufferSize ); + } + + ~FixedSizeDeque() + { + // Destruct remaining instances. + + while( mSize ) + { + destructInPlace( &mBuffer[ mStart ] ); + mStart ++; + if( mStart == mBufferSize ) + mStart = 0; + mSize --; + } + + // Free buffer. + dFree( mBuffer ); + } + + /// + bool isEmpty() const { return !size(); } + + /// + U32 size() const + { + return mSize; + } + + /// + U32 capacity() const + { + return ( mBufferSize - size() ); + } + + /// + void clear() + { + while( size() ) + popFront(); + } + + /// Return the leftmost value in the queue. + ValueType front() const + { + AssertFatal( !isEmpty(), "FixedSizeDeque::front() - queue is empty" ); + return mBuffer[ mStart ]; + } + + /// Return the rightmost value in the queue. + ValueType back() const + { + AssertFatal( !isEmpty(), "FixedSizeDeque::back() - queue is empty" ); + if( !mEnd ) + return mBuffer[ mBufferSize - 1 ]; + else + return mBuffer[ mEnd - 1 ]; + } + + /// Prepend "value" to the left end of the queue. + void pushFront( const ValueType& value ) + { + AssertFatal( capacity() != 0, "FixedSizeDeque::pushFront() - queue is full" ); + if( mStart == 0 ) + mStart = mBufferSize - 1; + else + mStart --; + mBuffer[ mStart ] = value; + mSize ++; + } + + /// Append "value" to the right end of the queue. + void pushBack( const ValueType& value ) + { + AssertFatal( capacity() != 0, "FixedSizeDeque::pushBack() - queue is full" ); + mBuffer[ mEnd ] = value; + mEnd ++; + if( mEnd == mBufferSize ) + mEnd = 0; + mSize ++; + } + + /// Remove and return the leftmost value in the queue. + ValueType popFront() + { + AssertFatal( !isEmpty(), "FixedSizeDeque::popFront() - queue is empty" ); + ValueType value = mBuffer[ mStart ]; + destructInPlace( &mBuffer[ mStart ] ); + + mStart ++; + if( mStart == mBufferSize ) + mStart = 0; + + mSize --; + return value; + } + + /// Remove and return the rightmost value in the queue. + ValueType popBack() + { + AssertFatal( !isEmpty(), "FixedSizeDeque::popBack() - queue is empty" ); + + U32 idx; + if( !mEnd ) + idx = mBufferSize - 1; + else + idx = mEnd - 1; + + ValueType value = mBuffer[ idx ]; + destructInPlace( &mBuffer[ idx ] ); + + mEnd = idx; + + mSize --; + return value; + } +}; + +#endif // _TFIXEDSIZEDEQUE_H_ diff --git a/core/util/tList.h b/core/util/tList.h new file mode 100644 index 0000000..9833da0 --- /dev/null +++ b/core/util/tList.h @@ -0,0 +1,473 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_LIST_ +#define _TORQUE_LIST_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +namespace Torque +{ + +/// A list template class. +/// A classic list class similar to the STL list class. The list +/// class supports fast insert and erase operations, but slow dynamic +/// access. The list is circular and the iterator supports iterating +/// past the end() or rend() in order to loop around. +/// @ingroup UtilContainers +template +class List +{ + struct Link; + struct PrivatePersist {}; ///< Used instead of a (void *) for type-safety of persistent iterators + +public: + /// A PersistentIter is used for the special cases where you need to store an iterator for + /// efficiency reasons. The _Iterator class handles the conversion to/from PersistentIter. + /// @note The data in the node this points to could go away, so only use these in cases where + /// you control the allocation/deallocation of the nodes in the list. + typedef PrivatePersist *PersistentIter; + + // Iterator support + template + class _Iterator + { + public: + typedef U ValueType; + typedef U* Pointer; + typedef U& Reference; + + _Iterator(); + _Iterator(PersistentIter &iter) { _link = (Link *)iter; } + + operator PersistentIter() const { return (PrivatePersist *)_link; } + + _Iterator& operator++(); + _Iterator operator++(int); + _Iterator& operator--(); + _Iterator operator--(int); + bool operator==(const _Iterator&) const; + bool operator!=(const _Iterator&) const; + U* operator->() const; + U& operator*() const; + + private: + friend class List; + + E* _link; + _Iterator(E*); + }; + + // types + typedef Type ValueType; + typedef Type& Reference; + typedef const Type& ConstReference; + + typedef _Iterator Iterator; + typedef _Iterator ConstIterator; + + typedef S32 DifferenceType; + typedef U32 SizeType; + + // initialization + List(); + ~List(); + List(const List& p); + + // management + U32 getSize() const; ///< Return the number of elements + void resize(U32); ///< Set the list size + void clear(); ///< Empty the List + bool isEmpty() const; ///< Node count == 0? + + // insert elements + Iterator insert(U32 index, const Type& = Type()); ///< Insert new element at the given index + Iterator insert(Iterator, const Type& = Type()); ///< Insert at the given iter + Iterator pushFront(const Type& = Type()); ///< Insert at first element + Iterator pushBack(const Type& = Type()); ///< Insert after the last element + + // erase elements + void erase(U32); ///< Preserves element order + void erase(Iterator); ///< Preserves element order + void popFront(); ///< Remove the first element + void popBack(); ///< Remove the last element + + // forward Iterator access + Iterator begin(); ///< _Iterator to first element + ConstIterator begin() const; ///< _Iterator to first element + Iterator end(); ///< _Iterator to last element + 1 + ConstIterator end() const; ///< _Iterator to last element + 1 + + // reverse Iterator access + Iterator rbegin(); ///< _Iterator to first element - 1 + ConstIterator rbegin() const; ///< _Iterator to first element - 1 + Iterator rend(); ///< _Iterator to last element + ConstIterator rend() const; ///< _Iterator to last element + + // type access + Type& first(); ///< First element + const Type& first() const; ///< First element + Type& last(); ///< Last element + const Type& last() const; ///< Last element + + // operators + void operator=(const List& p); + Type& operator[](U32); + const Type& operator[](U32) const; + +private: + struct Link + { + Link* next; + Link* prev; + Link() {} + Link(Link* p,Link* n): next(n),prev(p) {} + }; + + struct Node: Link + { + Type value; + Node() {} + Node(Link* p,Link* n,const Type& x): Link(p,n),value(x) {} + }; + + Link _head; + U32 _size; + + Link* _node(U32 index) const; + void _destroy(); +}; + +template List::List() +{ + _size = 0; + _head.next = &_head; + _head.prev = &_head; +} + +template List::List(const List& p) +{ + _size = 0; + _head.next = &_head; + _head.prev = &_head; + *this = p; +} + +template List::~List() +{ + _destroy(); +} + + +//----------------------------------------------------------------------------- + +template typename List::Link* List::_node(U32 index) const +{ + Iterator itr(_head.next); + while (index--) + itr++; + return itr._link; +} + +template void List::_destroy() +{ + for (Iterator itr(begin()); itr != end(); ) + { + Node* n = static_cast((itr++)._link); + delete n; + } +} + + +//----------------------------------------------------------------------------- +// management + +template inline U32 List::getSize() const +{ + return _size; +} + +template void List::resize(U32 size) +{ + if (size > _size) + { + while (_size != size) + pushBack(Type()); + } + else + { + while (_size != size) + popBack(); + } +} + +template void List::clear() +{ + _destroy(); + _head.next = &_head; + _head.prev = &_head; + _size = 0; +} + +template inline bool List::isEmpty() const +{ + return _size == 0; +} + + +//----------------------------------------------------------------------------- +// add Nodes + +template typename List::Iterator List::insert(Iterator itr, const Type& x) +{ + Link* node = itr._link; + Node* n = new Node(node->prev,node,x); + node->prev->next = n; + node->prev = n; + _size++; + return n; +} + +template inline typename List::Iterator List::insert(U32 index, const Type& x) +{ + insert(_node(index),x); +} + +template inline typename List::Iterator List::pushFront(const Type& x) +{ + return insert(_head.next,x); +} + +template inline typename List::Iterator List::pushBack(const Type& x) +{ + return insert(&_head,x); +} + + +//----------------------------------------------------------------------------- +// remove elements + +template void List::erase(Iterator itr) +{ + Node* node = static_cast(itr._link); + node->prev->next = node->next; + node->next->prev = node->prev; + delete node; + _size--; +} + +template inline void List::erase(U32 index) +{ + erase(_node(index)); +} + +template inline void List::popFront() +{ + erase(_head.next); +} + +template inline void List::popBack() +{ + erase(_head.prev); +} + + +//----------------------------------------------------------------------------- +// Iterator access + +template inline typename List::Iterator List::begin() +{ + return _head.next; +} + +template inline typename List::ConstIterator List::begin() const +{ + return _head.next; +} + +template inline typename List::Iterator List::end() +{ + return &_head; +} + +template inline typename List::ConstIterator List::end() const +{ + return &_head; +} + +template inline typename List::Iterator List::rbegin() +{ + return _head.prev; +} + +template inline typename List::ConstIterator List::rbegin() const +{ + return _head.prev; +} + +template inline typename List::Iterator List::rend() +{ + return &_head; +} + +template inline typename List::ConstIterator List::rend() const +{ + return &_head; +} + + +//----------------------------------------------------------------------------- +// type access + +template inline Type& List::first() +{ + return static_cast(_head.next)->value; +} + +template inline const Type& List::first() const +{ + return static_cast(_head.next)->value; +} + +template inline Type& List::last() +{ + return static_cast(_head.prev)->value; +} + +template inline const Type& List::last() const +{ + return static_cast(_head.prev)->value; +} + + +//----------------------------------------------------------------------------- +// operators + +template void List::operator=(const List& p) +{ + if (_size >= p._size) + { + // Destroy the elements we're not going to use and copy the rest + while (_size != p._size) + popBack(); + } + + U32 count = _size; + ConstIterator src = p.begin(); + Iterator dst = begin(); + while (count--) + *dst++ = *src++; + while (src != p.end()) + pushBack(*src++); +} + +template inline Type& List::operator[](U32 index) +{ + return static_cast(_node(index))->value; +} + +template inline const Type& List::operator[](U32 index) const +{ + return static_cast(_node(index))->value; +} + +//----------------------------------------------------------------------------- +// _Iterator class + +template template +inline List::_Iterator::_Iterator() +{ + _link = 0; +} + +template template +inline List::_Iterator::_Iterator(E* ptr) +{ + _link = ptr; +} + +// The checking for _MSC_VER on the ++/-- operators is due to a bug with the VS2008 compiler +// recheck this and remove if fixed with VS2008 SP1 + +template template +#ifdef _MSC_VER +inline typename List:: _Iterator& List::_Iterator::operator++() +#else +inline typename List::template _Iterator& List::_Iterator::operator++() +#endif +{ + _link = _link->next; + return *this; +} + +template template +#ifdef _MSC_VER +inline typename List:: _Iterator List::_Iterator::operator++(int) +#else +inline typename List::template _Iterator List::_Iterator::operator++(int) +#endif +{ + _Iterator itr(*this); + _link = _link->next; + return itr; +} + +template template +#ifdef _MSC_VER +inline typename List:: _Iterator& List::_Iterator::operator--() +#else +inline typename List::template _Iterator& List::_Iterator::operator--() +#endif +{ + _link = _link->prev; + return *this; +} + +template template +#ifdef _MSC_VER +inline typename List:: _Iterator List::_Iterator::operator--(int) +#else +inline typename List::template _Iterator List::_Iterator::operator--(int) +#endif +{ + _Iterator itr(*this); + _link = _link->prev; + return itr; +} + +template template +inline bool List::_Iterator::operator==(const _Iterator& b) const +{ + return _link == b._link; +} + +template template +inline bool List::_Iterator::operator!=(const _Iterator& b) const +{ + return !(_link == b._link); +} + +template template +inline U* List::_Iterator::operator->() const +{ + // Can't use static_cast because of const / non-const versions of Iterator. + // Could use reflection functions, but C-style cast is easier in this case. + return &((Node*)_link)->value; +} + +template template +inline U& List::_Iterator::operator*() const +{ + // Can't use static_cast because of const / non-const versions of Iterator. + // Could use reflection functions, but C-style cast is easier in this case. + return ((Node*)_link)->value; +} + +} // Namespace + +#endif // _TORQUE_LIST_ + diff --git a/core/util/tSignal.cpp b/core/util/tSignal.cpp new file mode 100644 index 0000000..3f850af --- /dev/null +++ b/core/util/tSignal.cpp @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/util/tSignal.h" + + +void SignalBase::DelegateLink::insert(DelegateLink* node, float order) +{ + // Note: can only legitimately be called on list head + DelegateLink * walk = next; + while (order >= walk->order && walk->next != this) + walk = walk->next; + if (order >= walk->order) + { + // insert after walk + node->prev = walk; + node->next = walk->next; + walk->next->prev = node; + walk->next = node; + } + else + { + // insert before walk + node->prev = walk->prev; + node->next = walk; + walk->prev->next = node; + walk->prev = node; + } + node->order = order; +} + +void SignalBase::DelegateLink::unlink() +{ + // Unlink this node + next->prev = prev; + prev->next = next; +} + +SignalBase::~SignalBase() +{ + while (mList.next != &mList) + { + DelegateLink* ptr = mList.next; + ptr->unlink(); + delete ptr; + } +} diff --git a/core/util/tSignal.h b/core/util/tSignal.h new file mode 100644 index 0000000..8342223 --- /dev/null +++ b/core/util/tSignal.h @@ -0,0 +1,426 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSIGNAL_H_ +#define _TSIGNAL_H_ + +#include "core/util/delegate.h" + +/// Signals (Multi-cast Delegates) +/// +/// Signals are used throughout this engine to allow subscribers to listen +/// for generated events for various things. +/// +/// Signals are called according to their order parameter (lower +/// numbers first). +/// +/// Signal functions can return bool or void. If bool then returning false +/// from a signal function will cause entries in the ordered signal list after +/// that one to not be called. +/// +/// This allows handlers of a given event to say to the signal generator, "I handled this message, and +/// it is no longer appropriate for other listeners to handle it" + +class SignalBase +{ +public: + + SignalBase() + { + mList.next = mList.prev = &mList; + mList.order = 0.5f; + } + + ~SignalBase(); + + /// Returns true if the delegate list is empty. + bool isEmpty() const + { + return mList.next == &mList; + } + +protected: + + struct DelegateLink + { + DelegateLink *next,*prev; + F32 order; + + void insert(DelegateLink* node, F32 order); + void unlink(); + }; + + DelegateLink mList; +}; + +template class SignalBaseT : public SignalBase +{ +public: + + /// The delegate signature for this signal. + typedef Delegate DelegateSig; + + SignalBaseT() {} + + SignalBaseT( const SignalBaseT &base ) + { + mList.next = mList.prev = &mList; + + for ( DelegateLink *ptr = base.mList.next; ptr != &base.mList; ptr = ptr->next ) + { + DelegateLinkImpl *del = static_cast( ptr ); + notify( del->mDelegate, del->order ); + } + } + + void notify( const DelegateSig &dlg, F32 order = 0.5f) + { + mList.insert(new DelegateLinkImpl(dlg), order); + } + + void remove(DelegateSig dlg) + { + for(DelegateLink *ptr = mList.next;ptr != &mList;ptr = ptr->next) + { + if(DelegateLinkImpl *del = static_cast(ptr)) + { + if(del->mDelegate == dlg) + { + del->unlink(); + delete del; + return; + } + } + } + } + + template + void notify(T obj,U func, F32 order = 0.5f) + { + DelegateSig dlg(obj, func); + notify(dlg, order); + } + + template + void notify(T func, F32 order = 0.5f) + { + DelegateSig dlg(func); + notify(dlg, order); + } + + template + void remove(T obj,U func) + { + DelegateSig compDelegate(obj, func); + remove(compDelegate); + } + + template + void remove(T func) + { + DelegateSig compDelegate(func); + remove(compDelegate); + } + + /// Returns true if the signal already contains this delegate. + bool contains( const DelegateSig &dlg ) const + { + for ( DelegateLink *ptr = mList.next; ptr != &mList; ptr = ptr->next ) + { + DelegateLinkImpl *del = static_cast( ptr ); + if ( del->mDelegate == dlg ) + return true; + } + + return false; + } + +protected: + + struct DelegateLinkImpl : public SignalBase::DelegateLink + { + DelegateSig mDelegate; + DelegateLinkImpl(DelegateSig dlg) : mDelegate(dlg) {} + }; + + DelegateSig & getDelegate(SignalBase::DelegateLink * link) + { + return ((DelegateLinkImpl*)link)->mDelegate; + } +}; + +//----------------------------------------------------------------------------- + +template class Signal; + +// Short-circuit signal implementations + +template<> +class Signal : public SignalBaseT +{ +public: + bool trigger() + { + for(SignalBase::DelegateLink *ptr = mList.next;ptr != &mList;ptr = ptr->next) + if (!getDelegate(ptr)()) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c, D d) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c,d)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c, D d, E e) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c,d,e)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c, D d, E e, F f) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c,d,e,f)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c, D d, E e, F f, G g) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c,d,e,f,g)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c, D d, E e, F f, G g, H h) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c,d,e,f,g,h)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c, D d, E e, F f, G g, H h, I i) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c,d,e,f,g,h,i)) + return false; + return true; + } +}; + +template +class Signal : public SignalBaseT +{ +public: + bool trigger(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + if (!this->getDelegate(ptr)(a,b,c,d,e,f,g,h,i,j)) + return false; + return true; + } +}; + +// Non short-circuit signal implementations + +template<> +class Signal : public SignalBaseT +{ +public: + void trigger() + { + for(SignalBase::DelegateLink *ptr = mList.next;ptr != &mList;ptr = ptr->next) + getDelegate(ptr)(); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c, D d) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c,d); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c, D d, E e) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c,d,e); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c, D d, E e, F f) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c,d,e,f); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c, D d, E e, F f, G g) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c,d,e,f,g); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c, D d, E e, F f, G g, H h) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c,d,e,f,g,h); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c, D d, E e, F f, G g, H h, I i) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c,d,e,f,g,h,i); + } +}; + +template +class Signal : public SignalBaseT +{ +public: + void trigger(A a, B b, C c, D d, E e, F f, G g, H h, I i, J j) + { + for(SignalBase::DelegateLink *ptr = this->mList.next;ptr != &this->mList;ptr = ptr->next) + this->getDelegate(ptr)(a,b,c,d,e,f,g,h,i,j); + } +}; + +#endif // _TSIGNAL_H_ \ No newline at end of file diff --git a/core/util/tSingleton.h b/core/util/tSingleton.h new file mode 100644 index 0000000..a2c7993 --- /dev/null +++ b/core/util/tSingleton.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSINGLETON_H_ +#define _TSINGLETON_H_ + + +/// This is a simple thread safe singleton class based on the +/// design of boost::singleton_default (see http://www.boost.org/). +/// +/// This singleton is guaranteed to be constructed before main() is called +/// and destroyed after main() exits. It will also be created on demand +/// if Singleton::instance() is called before main() begins. +/// +/// This thread safety only works within the execution context of main(). +/// Access to the singleton from multiple threads outside of main() is +/// is not guaranteed to be thread safe. +/// +/// To create a singleton you only need to access it once in your code: +/// +/// Singleton::instance()->myFunction(); +/// +/// You do not need to derive from this class. +/// +template +class Singleton +{ +private: + + // This is the creator object which ensures that the + // singleton is created before main() begins. + struct SingletonCreator + { + SingletonCreator() { Singleton::instance(); } + + // This dummy function is used below to force + // singleton creation at startup. + inline void forceSafeCreate() const {} + }; + + // The creator object instance. + static SingletonCreator smSingletonCreator; + + /// This is private on purpose. + Singleton(); + +public: + + /// Returns the singleton instance. + static T* instance() + { + // The singleton. + static T theSingleton; + + // This is here to force the compiler to create + // the singleton before main() is called. + smSingletonCreator.forceSafeCreate(); + + return &theSingleton; + } + +}; + +template +typename Singleton::SingletonCreator Singleton::smSingletonCreator; + + +/// This is a managed singleton class with explict creation +/// and destruction functions which must be called at startup +/// and shutdown of the engine. +/// +/// Your class to be managed must implement the following +/// function: +/// +/// static const char* getSingletonName() { return "YourClassName"; } +/// +/// This allow us to provide better asserts. +/// +template +class ManagedSingleton +{ +private: + + static T *smSingleton; + +public: + + /// + static void createSingleton() + { + AssertFatal( smSingleton == NULL, String::ToString( "%s::createSingleton() - The singleton is already created!", T::getSingletonName() ) ); + smSingleton = new T(); + } + + /// + static void deleteSingleton() + { + AssertFatal( smSingleton, String::ToString( "%s::deleteSingleton() - The singleton doest not exist!", T::getSingletonName() ) ); + delete smSingleton; + smSingleton = NULL; + } + + /// + static T* instance() + { + AssertFatal( smSingleton, String::ToString( "%s::instance() - The singleton has not been created!", T::getSingletonName() ) ); + return smSingleton; + } + +}; + +/// +template +T* ManagedSingleton::smSingleton = NULL; + + +#endif //_TSINGLETON_H_ + diff --git a/core/util/tVector.cpp b/core/util/tVector.cpp new file mode 100644 index 0000000..7206820 --- /dev/null +++ b/core/util/tVector.cpp @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/util/tVector.h" + +#ifdef TORQUE_DEBUG_GUARD +bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize, + const char* fileName, + const U32 lineNum) +{ + if (newCount > 0) { + U32 blocks = newCount / VectorBlockSize; + if (newCount % VectorBlockSize) + blocks++; + S32 mem_size = blocks * VectorBlockSize * elemSize; + + const char* pUseFileName = fileName != NULL ? fileName : __FILE__; + U32 useLineNum = fileName != NULL ? lineNum : __LINE__; + + if (*arrayPtr != NULL) + { + *arrayPtr = dRealloc_r(*arrayPtr, mem_size, pUseFileName, useLineNum); + } + else + { + *arrayPtr = dMalloc_r(mem_size, pUseFileName, useLineNum); + } + + *aCount = newCount; + *aSize = blocks * VectorBlockSize; + return true; + } + + if (*arrayPtr) { + dFree(*arrayPtr); + *arrayPtr = 0; + } + + *aSize = 0; + *aCount = 0; + return true; +} + +#else + +bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize) +{ + if (newCount > 0) + { + U32 blocks = newCount / VectorBlockSize; + if (newCount % VectorBlockSize) + blocks++; + S32 mem_size = blocks * VectorBlockSize * elemSize; + *arrayPtr = *arrayPtr ? dRealloc(*arrayPtr,mem_size) : + dMalloc(mem_size); + + *aCount = newCount; + *aSize = blocks * VectorBlockSize; + return true; + } + + if (*arrayPtr) + { + dFree(*arrayPtr); + *arrayPtr = 0; + } + + *aSize = 0; + *aCount = 0; + return true; +} + +#endif diff --git a/core/util/tVector.h b/core/util/tVector.h new file mode 100644 index 0000000..dc14cf2 --- /dev/null +++ b/core/util/tVector.h @@ -0,0 +1,916 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TVECTOR_H_ +#define _TVECTOR_H_ + +// TODO: This shouldn't be included in headers... it should +// be included by the source file before all other includes. +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +//----------------------------------------------------------------------------- +// Helper definitions for the vector class. + +/// Size of memory blocks to allocate at a time for vectors. +const static S32 VectorBlockSize = 16; +#ifdef TORQUE_DEBUG_GUARD +extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize, + const char* fileName, + const U32 lineNum); +#else +extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize); +#endif + +/// Use the following macro to bind a vector to a particular line +/// of the owning class for memory tracking purposes +#ifdef TORQUE_DEBUG_GUARD +#define VECTOR_SET_ASSOCIATION(x) x.setFileAssociation(__FILE__, __LINE__) +#else +#define VECTOR_SET_ASSOCIATION(x) +#endif + +// ============================================================================= +/// A dynamic array template class. +/// +/// The vector grows as you insert or append +/// elements. Insertion is fastest at the end of the array. Resizing +/// of the array can be avoided by pre-allocating space using the +/// reserve() method. +/// +/// @nosubgrouping +template +class Vector +{ + protected: + U32 mElementCount; ///< Number of elements currently in the Vector. + U32 mArraySize; ///< Number of elements allocated for the Vector. + T* mArray; ///< Pointer to the Vector elements. + +#ifdef TORQUE_DEBUG_GUARD + const char* mFileAssociation; + U32 mLineAssociation; +#endif + + bool resize(U32); // resizes, but does no construction/destruction + void destroy(U32 start, U32 end); ///< Destructs elements from start to end-1 + void construct(U32 start, U32 end); ///< Constructs elements from start to end-1 + void construct(U32 start, U32 end, const T* array); + public: + Vector(const U32 initialSize = 0); + Vector(const U32 initialSize, const char* fileName, const U32 lineNum); + Vector(const char* fileName, const U32 lineNum); + Vector(const Vector&); + ~Vector(); + +#ifdef TORQUE_DEBUG_GUARD + void setFileAssociation(const char* file, const U32 line); +#endif + + /// @name STL interface + /// @{ + + typedef T value_type; + typedef T& reference; + typedef const T& const_reference; + + typedef T* iterator; + typedef const T* const_iterator; + typedef S32 difference_type; + typedef U32 size_type; + + typedef difference_type (QSORT_CALLBACK *compare_func)(const T *a, const T *b); + + Vector& operator=(const Vector& p); + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + S32 size() const; + bool empty() const; + bool contains(const T&) const; + + void insert(iterator, const T&); + void erase(iterator); + + T& front(); + const T& front() const; + T& back(); + const T& back() const; + + void push_front(const T&); + void push_back(const T&); + U32 push_front_unique(const T&); + U32 push_back_unique(const T&); + S32 find_next( const T&, U32 start = 0 ) const; + void pop_front(); + void pop_back(); + + T& operator[](U32); + const T& operator[](U32) const; + + T& operator[](S32 i) { return operator[](U32(i)); } + const T& operator[](S32 i ) const { return operator[](U32(i)); } + + void reserve(U32); + U32 capacity() const; + + /// @} + + /// @name Extended interface + /// @{ + + U32 memSize() const; + T* address() const; + U32 setSize(U32); + void increment(); + void decrement(); + void increment(U32); + void decrement(U32); + void insert(U32); + void insert(U32, const T&); + void erase(U32); + void erase_fast(U32); + void erase(U32 index, U32 count); + void erase_fast(iterator); + void clear(); + void compact(); + void sort(compare_func f); + + /// Finds the first matching element and erases it. + /// @return Returns true if a match is found. + bool remove( const T& ); + + T& first(); + T& last(); + const T& first() const; + const T& last() const; + + void set(void * addr, U32 sz); + + /// Appends the content of the vector to this one. + void merge( const Vector &p ); + + /// @} +}; + +template inline Vector::~Vector() +{ + clear(); + dFree(mArray); +} + +template inline Vector::Vector(const U32 initialSize) +{ +#ifdef TORQUE_DEBUG_GUARD + mFileAssociation = NULL; + mLineAssociation = 0; +#endif + + mArray = 0; + mElementCount = 0; + mArraySize = 0; + if(initialSize) + reserve(initialSize); +} + +template inline Vector::Vector(const U32 initialSize, + const char* fileName, + const U32 lineNum) +{ +#ifdef TORQUE_DEBUG_GUARD + mFileAssociation = fileName; + mLineAssociation = lineNum; +#else +// TORQUE_UNUSED(fileName); +// TORQUE_UNUSED(lineNum); +#endif + + mArray = 0; + mElementCount = 0; + mArraySize = 0; + if(initialSize) + reserve(initialSize); +} + +template inline Vector::Vector(const char* fileName, + const U32 lineNum) +{ +#ifdef TORQUE_DEBUG_GUARD + mFileAssociation = fileName; + mLineAssociation = lineNum; +#else +// TORQUE_UNUSED(fileName); +// TORQUE_UNUSED(lineNum); +#endif + + mArray = 0; + mElementCount = 0; + mArraySize = 0; +} + +template inline Vector::Vector(const Vector& p) +{ +#ifdef TORQUE_DEBUG_GUARD + mFileAssociation = p.mFileAssociation; + mLineAssociation = p.mLineAssociation; +#endif + + mArray = 0; + resize(p.mElementCount); + construct(0, p.mElementCount, p.mArray); +} + + +#ifdef TORQUE_DEBUG_GUARD +template inline void Vector::setFileAssociation(const char* file, + const U32 line) +{ + mFileAssociation = file; + mLineAssociation = line; +} +#endif + +template inline void Vector::destroy(U32 start, U32 end) // destroys from start to end-1 +{ + // This check is a little generous as we can legitimately get (0,0) as + // our parameters... so it won't detect every invalid case but it does + // remain simple. + AssertFatal(start <= mElementCount && end <= mElementCount, "Vector::destroy - out of bounds start/end."); + + // destroys from start to end-1 + while(start < end) + destructInPlace(&mArray[start++]); +} + +template inline void Vector::construct(U32 start, U32 end) // destroys from start to end-1 +{ + AssertFatal(start <= mElementCount && end <= mElementCount, "Vector::construct - out of bounds start/end."); + while(start < end) + constructInPlace(&mArray[start++]); +} + +template inline void Vector::construct(U32 start, U32 end, const T* array) // destroys from start to end-1 +{ + AssertFatal(start <= mElementCount && end <= mElementCount, "Vector::construct - out of bounds start/end."); + while(start < end) + { + constructInPlace(&mArray[start], &array[start]); + start++; + } +} + +template inline U32 Vector::memSize() const +{ + return capacity() * sizeof(T); +} + +template inline T* Vector::address() const +{ + return mArray; +} + +template inline U32 Vector::setSize(U32 size) +{ + const U32 oldSize = mElementCount; + + if(size > mElementCount) + { + if (size > mArraySize) + resize(size); + + // Set count first so we are in a valid state for construct. + mElementCount = size; + construct(oldSize, size); + } + else if(size < mElementCount) + { + destroy(size, oldSize); + mElementCount = size; + } + + return mElementCount; +} + +template inline void Vector::increment() +{ + if(mElementCount == mArraySize) + resize(mElementCount + 1); + else + mElementCount++; + constructInPlace(&mArray[mElementCount - 1]); +} + +template inline void Vector::decrement() +{ + AssertFatal(mElementCount != 0, "Vector::decrement - cannot decrement zero-length vector."); + mElementCount--; + destructInPlace(&mArray[mElementCount]); +} + +template inline void Vector::increment(U32 delta) +{ + U32 count = mElementCount; + if ((mElementCount += delta) > mArraySize) + resize(mElementCount); + construct(count, mElementCount); +} + +template inline void Vector::decrement(U32 delta) +{ + AssertFatal(mElementCount != 0, "Vector::decrement - cannot decrement zero-length vector."); + + const U32 count = mElementCount; + + // Determine new count after decrement... + U32 newCount = mElementCount; + if (mElementCount > delta) + newCount -= delta; + else + newCount = 0; + + // Destruct removed items... + destroy(newCount, count); + + // Note new element count. + mElementCount = newCount; +} + +template inline void Vector::insert(U32 index) +{ + AssertFatal(index <= mElementCount, "Vector::insert - out of bounds index."); + + if(mElementCount == mArraySize) + resize(mElementCount + 1); + else + mElementCount++; + + dMemmove(&mArray[index + 1], + &mArray[index], + (mElementCount - index - 1) * sizeof(value_type)); + + constructInPlace(&mArray[index]); +} + +template inline void Vector::insert(U32 index,const T& x) +{ + insert(index); + mArray[index] = x; +} + +template inline void Vector::erase(U32 index) +{ + AssertFatal(index < mElementCount, "Vector::erase - out of bounds index!"); + + destructInPlace(&mArray[index]); + + if (index < (mElementCount - 1)) + { + dMemmove(&mArray[index], + &mArray[index + 1], + (mElementCount - index - 1) * sizeof(value_type)); + } + + mElementCount--; +} + +template inline bool Vector::remove( const T& x ) +{ + iterator i = begin(); + while (i != end()) + { + if (*i == x) + { + erase( i ); + return true; + } + + i++; + } + + return false; +} + +template inline void Vector::erase(U32 index, U32 count) +{ + AssertFatal(index < mElementCount, "Vector::erase - out of bounds index!"); + AssertFatal(count > 0, "Vector::erase - count must be greater than zero!"); + AssertFatal(index+count <= mElementCount, "Vector::erase - out of bounds count!"); + + destroy( index, index+count ); + + dMemmove( &mArray[index], + &mArray[index + count], + (mElementCount - index - count) * sizeof(value_type)); + + mElementCount -= count; +} + +template inline void Vector::erase_fast(U32 index) +{ + AssertFatal(index < mElementCount, "Vector::erase_fast - out of bounds index."); + + // CAUTION: this operator does NOT maintain list order + // Copy the last element into the deleted 'hole' and decrement the + // size of the vector. + destructInPlace(&mArray[index]); + if (index < (mElementCount - 1)) + dMemmove(&mArray[index], &mArray[mElementCount - 1], sizeof(value_type)); + mElementCount--; +} + +template inline bool Vector::contains(const T& x) const +{ + const_iterator i = begin(); + while (i != end()) + { + if (*i == x) + return true; + + i++; + } + + return false; +} + + +template inline T& Vector::first() +{ + AssertFatal(mElementCount != 0, "Vector::first - Error, no first element of a zero sized array!"); + return mArray[0]; +} + +template inline const T& Vector::first() const +{ + AssertFatal(mElementCount != 0, "Vector::first - Error, no first element of a zero sized array! (const)"); + return mArray[0]; +} + +template inline T& Vector::last() +{ + AssertFatal(mElementCount != 0, "Vector::last - Error, no last element of a zero sized array!"); + return mArray[mElementCount - 1]; +} + +template inline const T& Vector::last() const +{ + AssertFatal(mElementCount != 0, "Vector::last - Error, no last element of a zero sized array! (const)"); + return mArray[mElementCount - 1]; +} + +template inline void Vector::clear() +{ + destroy(0, mElementCount); + mElementCount = 0; +} + +template inline void Vector::compact() +{ + resize(mElementCount); +} + +typedef int (QSORT_CALLBACK *qsort_compare_func)(const void *, const void *); + +template inline void Vector::sort(compare_func f) +{ + qsort(address(), size(), sizeof(T), (qsort_compare_func) f); +} + +//----------------------------------------------------------------------------- + +template inline Vector& Vector::operator=(const Vector& p) +{ + if(mElementCount > p.mElementCount) + { + destroy(p.mElementCount, mElementCount); + } + + U32 count = getMin( mElementCount, p.mElementCount ); + U32 i; + for( i=0; i < count; i++ ) + { + mArray[i] = p.mArray[i]; + } + + resize( p.mElementCount ); + + if( i < p.mElementCount ) + { + construct(i, p.mElementCount, p.mArray); + } + return *this; +} + +template inline typename Vector::iterator Vector::begin() +{ + return mArray; +} + +template inline typename Vector::const_iterator Vector::begin() const +{ + return mArray; +} + +template inline typename Vector::iterator Vector::end() +{ + return mArray + mElementCount; +} + +template inline typename Vector::const_iterator Vector::end() const +{ + return mArray +mElementCount; +} + +template inline S32 Vector::size() const +{ + return (S32)mElementCount; +} + +template inline bool Vector::empty() const +{ + return (mElementCount == 0); +} + +template inline void Vector::insert(iterator p,const T& x) +{ + U32 index = (U32) (p - mArray); + insert(index); + mArray[index] = x; +} + +template inline void Vector::erase(iterator q) +{ + erase(U32(q - mArray)); +} + +template inline void Vector::erase_fast(iterator q) +{ + erase_fast(U32(q - mArray)); +} + +template inline T& Vector::front() +{ + return *begin(); +} + +template inline const T& Vector::front() const +{ + return *begin(); +} + +template inline T& Vector::back() +{ + return *end(); +} + +template inline const T& Vector::back() const +{ + return *end(); +} + +template inline void Vector::push_front(const T& x) +{ + insert(0); + mArray[0] = x; +} + +template inline void Vector::push_back(const T& x) +{ + increment(); + mArray[mElementCount - 1] = x; +} + +template inline U32 Vector::push_front_unique(const T& x) +{ + S32 index = find_next(x); + + if (index == -1) + { + index = 0; + + insert(index); + mArray[index] = x; + } + + return index; +} + +template inline U32 Vector::push_back_unique(const T& x) +{ + S32 index = find_next(x); + + if (index == -1) + { + increment(); + + index = mElementCount - 1; + mArray[index] = x; + } + + return index; +} + +template inline S32 Vector::find_next( const T& x, U32 start ) const +{ + S32 index = -1; + + if (start < mElementCount) + { + for (U32 i = start; i < mElementCount; i++) + { + if (mArray[i] == x) + { + index = i; + break; + } + } + } + + return index; +} + +template inline void Vector::pop_front() +{ + AssertFatal(mElementCount != 0, "Vector::pop_front - cannot pop the front of a zero-length vector."); + erase(U32(0)); +} + +template inline void Vector::pop_back() +{ + AssertFatal(mElementCount != 0, "Vector::pop_back - cannot pop the back of a zero-length vector."); + decrement(); +} + +template inline T& Vector::operator[](U32 index) +{ + AssertFatal(index < mElementCount, "Vector::operator[] - out of bounds array access!"); + return mArray[index]; +} + +template inline const T& Vector::operator[](U32 index) const +{ + AssertFatal(index < mElementCount, "Vector::operator[] - out of bounds array access!"); + return mArray[index]; +} + +template inline void Vector::reserve(U32 size) +{ + if (size <= mArraySize) + return; + + const U32 ec = mElementCount; + if (resize(size)) + mElementCount = ec; +} + +template inline U32 Vector::capacity() const +{ + return mArraySize; +} + +template inline void Vector::set(void * addr, U32 sz) +{ + if ( !addr ) + sz = 0; + + setSize( sz ); + + if ( addr && sz > 0 ) + dMemcpy(address(),addr,sz*sizeof(T)); +} + +//----------------------------------------------------------------------------- + +template inline bool Vector::resize(U32 ecount) +{ +#ifdef TORQUE_DEBUG_GUARD + return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T), + mFileAssociation, mLineAssociation); +#else + return VectorResize(&mArraySize, &mElementCount, (void**) &mArray, ecount, sizeof(T)); +#endif +} + +template inline void Vector::merge( const Vector &p ) +{ + if ( !p.size() ) + return; + + const U32 oldSize = mElementCount; + const U32 newSize = oldSize + p.size(); + if ( newSize > mArraySize ) + resize( newSize ); + + T *dest = mArray + oldSize; + const T *src = p.mArray; + while ( dest < mArray + newSize ) + constructInPlace( dest++, src++ ); + + mElementCount = newSize; +} + +//----------------------------------------------------------------------------- +/// Template for vectors of pointers. +template +class VectorPtr : public Vector +{ + /// @deprecated Disallowed. + VectorPtr(const VectorPtr&); // Disallowed + + public: + VectorPtr(); + VectorPtr(const char* fileName, const U32 lineNum); + + /// @name STL interface + /// @{ + + typedef T value_type; + typedef T& reference; + typedef const T& const_reference; + + typedef T* iterator; + typedef const T* const_iterator; + typedef U32 difference_type; + typedef U32 size_type; + + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + void insert(iterator,const T&); + void insert(int idx) { Parent::insert(idx); } + void erase(iterator); + + T& front(); + const T& front() const; + T& back(); + const T& back() const; + void push_front(const T&); + void push_back(const T&); + + T& operator[](U32); + const T& operator[](U32) const; + + /// @} + + /// @name Extended interface + /// @{ + + typedef Vector Parent; + T& first(); + T& last(); + const T& first() const; + const T& last() const; + void erase_fast(U32); + void erase_fast(iterator); + + /// @} +}; + + +//----------------------------------------------------------------------------- +template inline VectorPtr::VectorPtr() +{ + // +} + +template inline VectorPtr::VectorPtr(const char* fileName, + const U32 lineNum) + : Vector(fileName, lineNum) +{ + // +} + +template inline T& VectorPtr::first() +{ + return (T&)Parent::first(); +} + +template inline const T& VectorPtr::first() const +{ + return (const T)Parent::first(); +} + +template inline T& VectorPtr::last() +{ + return (T&)Parent::last(); +} + +template inline const T& VectorPtr::last() const +{ + return (const T&)Parent::last(); +} + +template inline typename VectorPtr::iterator VectorPtr::begin() +{ + return (iterator)Parent::begin(); +} + +template inline typename VectorPtr::const_iterator VectorPtr::begin() const +{ + return (const_iterator)Parent::begin(); +} + +template inline typename VectorPtr::iterator VectorPtr::end() +{ + return (iterator)Parent::end(); +} + +template inline typename VectorPtr::const_iterator VectorPtr::end() const +{ + return (const_iterator)Parent::end(); +} + +template inline void VectorPtr::insert(iterator i,const T& x) +{ + Parent::insert( (Parent::iterator)i, (Parent::reference)x ); +} + +template inline void VectorPtr::erase(iterator i) +{ + Parent::erase( (Parent::iterator)i ); +} + +template inline void VectorPtr::erase_fast(U32 index) +{ + AssertFatal(index < mElementCount, "VectorPtr::erase_fast - out of bounds index." ); + + // CAUTION: this operator does not maintain list order + // Copy the last element into the deleted 'hole' and decrement the + // size of the vector. + // Assert: index >= 0 && index < mElementCount + if (index < (mElementCount - 1)) + mArray[index] = mArray[mElementCount - 1]; + decrement(); +} + +template inline void VectorPtr::erase_fast(iterator i) +{ + erase_fast(U32(i - iterator(mArray))); +} + +template inline T& VectorPtr::front() +{ + return *begin(); +} + +template inline const T& VectorPtr::front() const +{ + return *begin(); +} + +template inline T& VectorPtr::back() +{ + return *end(); +} + +template inline const T& VectorPtr::back() const +{ + return *end(); +} + +template inline void VectorPtr::push_front(const T& x) +{ + Parent::push_front((Parent::const_reference)x); +} + +template inline void VectorPtr::push_back(const T& x) +{ + Parent::push_back((Parent::const_reference)x); +} + +template inline T& VectorPtr::operator[](U32 index) +{ + return (T&)Parent::operator[](index); +} + +template inline const T& VectorPtr::operator[](U32 index) const +{ + return (const T&)Parent::operator[](index); +} + +//------------------------------------------------------------------------------ + +template class VectorSet : public Vector +{ +public: + void insert(T dat) + { + if(find(this->begin(), this->end(), dat) == this->end()) + push_back(dat); + } +}; + +// Include vector specializations +#ifndef _VECTORSPEC_H_ +#include "core/util/tVectorSpecializations.h" +#endif + +#endif //_TVECTOR_H_ + diff --git a/core/util/tVectorSpecializations.h b/core/util/tVectorSpecializations.h new file mode 100644 index 0000000..8caca49 --- /dev/null +++ b/core/util/tVectorSpecializations.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TVECTORSPEC_H_ +#define _TVECTORSPEC_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +// Not exactly a specialization, just a vector to use when speed is a concern +template +class FastVector : public Vector +{ +public: + // This method was re-written to prevent load-hit-stores during the simple-case. + void push_back_noresize(const T &x) + { +#ifdef TORQUE_DEBUG + AssertFatal(Vector::mElementCount < Vector::mArraySize, "use of push_back_noresize requires that you reserve enough space in the FastVector"); +#endif + Vector::mArray[Vector::mElementCount++] = x; + } +}; + +#endif //_TVECTORSPEC_H_ + diff --git a/core/util/test/testFixedSizeDeque.cpp b/core/util/test/testFixedSizeDeque.cpp new file mode 100644 index 0000000..4702a49 --- /dev/null +++ b/core/util/test/testFixedSizeDeque.cpp @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "core/util/tFixedSizeDeque.h" + + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) + +CreateUnitTest( TestFixedSizeDeque, "Util/FixedSizeDeque" ) +{ + void run() + { + enum { DEQUE_SIZE = 3 }; + FixedSizeDeque< U32 > deque( DEQUE_SIZE ); + + TEST( deque.capacity() == DEQUE_SIZE ); + TEST( deque.size() == 0 ); + + deque.pushFront( 1 ); + TEST( deque.capacity() == ( DEQUE_SIZE - 1 ) ); + TEST( deque.size() == 1 ); + TEST( !deque.isEmpty() ); + + deque.pushBack( 2 ); + TEST( deque.capacity() == ( DEQUE_SIZE - 2 ) ); + TEST( deque.size() == 2 ); + + TEST( deque.popFront() == 1 ); + TEST( deque.popFront() == 2 ); + TEST( deque.isEmpty() ); + } +}; diff --git a/core/util/test/testPath.cpp b/core/util/test/testPath.cpp new file mode 100644 index 0000000..0d344e1 --- /dev/null +++ b/core/util/test/testPath.cpp @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "core/util/path.h" + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) + +CreateUnitTest(TestPathMakeRelativePath, "Core/Util/Path/MakeRelativePath") +{ + void run() + { + TEST(Torque::Path::MakeRelativePath("art/interiors/burg/file.png", "art/interiors/") == "burg/file.png"); + TEST(Torque::Path::MakeRelativePath("art/interiors/file.png", "art/interiors/burg/") == "../file.png"); + TEST(Torque::Path::MakeRelativePath("art/file.png", "art/interiors/burg/") == "../../file.png"); + TEST(Torque::Path::MakeRelativePath("file.png", "art/interiors/burg/") == "../../../file.png"); + TEST(Torque::Path::MakeRelativePath("art/interiors/burg/file.png", "art/interiors/burg/") == "file.png"); + TEST(Torque::Path::MakeRelativePath("art/interiors/camp/file.png", "art/interiors/burg/") == "../camp/file.png"); + TEST(Torque::Path::MakeRelativePath("art/interiors/burg/file.png", "art/shapes/") == "../interiors/burg/file.png"); + TEST(Torque::Path::MakeRelativePath("levels/den/file.png", "art/interiors/burg/") == "../../../levels/den/file.png"); + TEST(Torque::Path::MakeRelativePath("art/interiors/burg/file.png", "art/dts/burg/") == "../../interiors/burg/file.png"); + } +}; diff --git a/core/util/test/testString.cpp b/core/util/test/testString.cpp new file mode 100644 index 0000000..d09fb81 --- /dev/null +++ b/core/util/test/testString.cpp @@ -0,0 +1,341 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "core/util/str.h" +#include "core/util/tVector.h" +#include "core/strings/stringFunctions.h" +#include "core/strings/unicode.h" + + +#ifndef TORQUE_SHIPPING + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) +#define XTEST( t, x ) t->test( ( x ), "FAIL: " #x ) + +CreateUnitTest( TestString, "Util/String" ) +{ + struct StrTest + { + const UTF8* mData; + const UTF16* mUTF16; + U32 mLength; + + StrTest() : mData( 0 ), mUTF16( 0 ) {} + StrTest( const char* str ) + : mData( str ), mLength( str ? dStrlen( str ) : 0 ), mUTF16( NULL ) + { + if( str ) + mUTF16 = convertUTF8toUTF16( mData ); + } + ~StrTest() + { + if( mUTF16 ) + delete [] mUTF16; + } + }; + + Vector< StrTest* > mStrings; + + template< class T > + void runTestOnStrings() + { + for( U32 i = 0; i < mStrings.size(); ++ i ) + T::run( this, *mStrings[ i ] ); + } + template< class T > + void runPairwiseTestOnStrings() + { + for( U32 i = 0; i < mStrings.size(); ++ i ) + for( U32 j = 0; j < mStrings.size(); ++ j ) + T::run( this, *mStrings[ i ], *mStrings[ j ] ); + } + + struct Test1 + { + static void run( TestString* test, StrTest& data ) + { + String str( data.mData ); + String str16( data.mUTF16 ); + + XTEST( test, str.length() == data.mLength ); + XTEST( test, str.size() == data.mLength + 1 ); + XTEST( test, str.isEmpty() || str.length() > 0 ); + XTEST( test, str.length() == str16.length() ); + XTEST( test, str.size() == str16.size() ); + + XTEST( test, dMemcmp( str.utf16(), str16.utf16(), str.length() * sizeof( UTF16 ) ) == 0 ); + XTEST( test, !data.mData || dMemcmp( str.utf16(), data.mUTF16, str.length() * sizeof( UTF16 ) ) == 0 ); + XTEST( test, !data.mData || dMemcmp( str16.utf8(), data.mData, str.length() ) == 0 ); + + XTEST( test, !data.mData || dStrcmp( str.utf8(), data.mData ) == 0 ); + XTEST( test, !data.mData || dStrcmp( str.utf16(), data.mUTF16 ) == 0 ); + } + }; + + struct Test2 + { + static void run( TestString* test, StrTest& data ) + { + String str( data.mData ); + + XTEST( test, str == str ); + XTEST( test, !( str != str ) ); + XTEST( test, !( str < str ) ); + XTEST( test, !( str > str ) ); + XTEST( test, str.equal( str ) ); + XTEST( test, str.equal( str, String::NoCase ) ); + } + }; + + struct Test3 + { + static void run( TestString* test, StrTest& d1, StrTest& d2 ) + { + if( &d1 != &d2 ) + XTEST( test, String( d1.mData ) != String( d2.mData ) + || ( String( d1.mData ).isEmpty() && String( d2.mData ).isEmpty() ) ); + else + XTEST( test, String( d1.mData ) == String( d2.mData ) ); + } + }; + + void testEmpty() + { + TEST( String().length() == 0 ); + TEST( String( "" ).length() == 0 ); + TEST( String().size() == 1 ); + TEST( String( "" ).size() == 1 ); + TEST( String().isEmpty() ); + TEST( String( "" ).isEmpty() ); + } + + void testTrim() + { + TEST( String( " Foobar Barfoo \n\t " ).trim() == String( "Foobar Barfoo" ) ); + TEST( String( "Foobar" ).trim() == String( "Foobar" ) ); + TEST( String( " " ).trim().isEmpty() ); + } + + void testCompare() + { + String str( "Foobar" ); + + TEST( str.compare( "Foo", 3 ) == 0 ); + TEST( str.compare( "bar", 3, String::NoCase | String::Right ) == 0 ); + TEST( str.compare( "foo", 3, String::NoCase ) == 0 ); + TEST( str.compare( "BAR", 3, String::NoCase | String::Right ) == 0 ); + TEST( str.compare( "Foobar" ) == 0 ); + TEST( str.compare( "Foo" ) != 0 ); + TEST( str.compare( "foobar", 0, String::NoCase ) == 0 ); + TEST( str.compare( "FOOBAR", 0, String::NoCase ) == 0 ); + TEST( str.compare( "Foobar", 0, String::Right ) == 0 ); + TEST( str.compare( "foobar", 0, String::NoCase | String::Right ) == 0 ); + } + + void testOrder() + { + Vector< String > strs; + + strs.push_back( "a" ); + strs.push_back( "a0" ); + strs.push_back( "a1" ); + strs.push_back( "a1a" ); + strs.push_back( "a1b" ); + strs.push_back( "a2" ); + strs.push_back( "a10" ); + strs.push_back( "a20" ); + + for( U32 i = 0; i < strs.size(); ++ i ) + { + for( U32 j = 0; j < i; ++ j ) + { + TEST( strs[ j ] < strs[ i ] ); + TEST( strs[ i ] > strs[ j ] ); + + TEST( !( strs[ j ] > strs[ i ] ) ); + TEST( !( strs[ i ] < strs[ i ] ) ); + + TEST( strs[ j ] <= strs[ i ] ); + TEST( strs[ i ] >= strs[ j ] ); + } + + TEST( !( strs[ i ] < strs[ i ] ) ); + TEST( !( strs[ i ] > strs[ i ] ) ); + TEST( strs[ i ] <= strs[ i ] ); + TEST( strs[ i ] >= strs[ i ] ); + + for( U32 j = i + 1; j < strs.size(); ++ j ) + { + TEST( strs[ j ] > strs[ i ] ); + TEST( strs[ i ] < strs[ j ] ); + + TEST( !( strs[ j ] < strs[ i ] ) ); + TEST( !( strs[ i ] > strs[ j ] ) ); + + TEST( strs[ j ] >= strs[ i ] ); + TEST( strs[ i ] <= strs[ j ] ); + } + } + } + + void testFind() + { + //TODO + } + + void testInsert() + { + // String.insert( Pos, Char ) + TEST( String( "aa" ).insert( 1, 'c' ) == String( "aca" ) ); + + // String.insert( Pos, String ) + TEST( String( "aa" ).insert( 1, "cc" ) == String( "acca" ) ); + TEST( String( "aa" ).insert( 1, String( "cc" ) ) == String( "acca" ) ); + + // String.insert( Pos, String, Len ) + TEST( String( "aa" ).insert( 1, "ccdddd", 2 ) == String( "acca" ) ); + } + + void testErase() + { + TEST( String( "abba" ).erase( 1, 2 ) == String( "aa" ) ); + TEST( String( "abba" ).erase( 0, 4 ).isEmpty() ); + } + + void testReplace() + { + // String.replace( Pos, Len, String ) + TEST( String( "abba" ).replace( 1, 2, "ccc" ) == String( "accca" ) ); + TEST( String( "abba" ).replace( 1, 2, String( "ccc" ) ) == String( "accca" ) ); + TEST( String( "abba" ).replace( 0, 4, "" ).isEmpty() ); + TEST( String( "abba" ).replace( 2, 2, "c" ) == String( "abc" ) ); + + // String.replace( Char, Char ) + TEST( String().replace( 'a', 'b' ).isEmpty() ); + TEST( String( "ababc" ).replace( 'a', 'b' ) == String( "bbbbc" ) ); + TEST( String( "ababc" ).replace( 'd', 'e' ) == String( "ababc" ) ); + + // String.replace( String, String ) + TEST( String().replace( "foo", "bar" ).isEmpty() ); + TEST( String( "foobarfoo" ).replace( "foo", "bar" ) == String( "barbarbar" ) ); + TEST( String( "foobar" ).replace( "xx", "yy" ) == String( "foobar" ) ); + TEST( String( "foofoofoo" ).replace( "foo", "" ).isEmpty() ); + } + + void testSubstr() + { + TEST( String( "foobar" ).substr( 0, 3 ) == String( "foo" ) ); + TEST( String( "foobar" ).substr( 3 ) == String( "bar" ) ); + TEST( String( "foobar" ).substr( 2, 2 ) == String( "ob" ) ); + TEST( String( "foobar" ).substr( 2, 0 ).isEmpty() ); + TEST( String( "foobar" ).substr( 0, 6 ) == String( "foobar" ) ); + } + + void testToString() + { + TEST( String::ToString( U32( 1 ) ) == String( "1" ) ); + TEST( String::ToString( S32( -1 ) ) == String( "-1" ) ); + TEST( String::ToString( F32( 1.01 ) ) == String( "1.01" ) ); + TEST( String::ToString( "%s%i", "foo", 1 ) == String( "foo1" ) ); + } + + void testCaseConversion() + { + TEST( String::ToLower( "foobar123." ) == String( "foobar123." ) ); + TEST( String::ToLower( "FOOBAR123." ) == String( "foobar123." ) ); + TEST( String::ToUpper( "barfoo123." ) == String( "BARFOO123." ) ); + TEST( String::ToUpper( "BARFOO123." ) == String( "BARFOO123." ) ); + } + + void testConcat() + { + TEST( String( "foo" ) + String( "bar" ) == String( "foobar" ) ); + TEST( String() + String( "bar" ) == String( "bar" ) ); + TEST( String( "foo" ) + String() == String( "foo" ) ); + TEST( String() + String() == String() ); + TEST( String( "fo" ) + 'o' == String( "foo" ) ); + TEST( 'f' + String( "oo" ) == String( "foo" ) ); + TEST( String( "foo" ) + "bar" == String( "foobar" ) ); + TEST( "foo" + String( "bar" ) == String( "foobar" ) ); + } + + void testHash() + { + TEST( String( "foo" ).getHashCaseSensitive() == String( "foo" ).getHashCaseSensitive() ); + TEST( String( "foo" ).getHashCaseSensitive() != String( "bar" ).getHashCaseSensitive() ); + TEST( String( "foo" ).getHashCaseInsensitive() == String( "FOO" ).getHashCaseInsensitive() ); + } + + void testIntern() + { + TEST( String( "foo" ).intern().isSame( String( "foo" ).intern() ) ); + TEST( !String( "foo" ).intern().isSame( String( "bar" ).intern() ) ); + TEST( !String( "foo" ).intern().isSame( String( "Foo" ).intern() ) ); + TEST( String( "foo" ).intern() == String( "foo" ).intern() ); + TEST( String( "foo" ).intern() != String( "bar" ).intern() ); + TEST( String( "foo" ).intern().isInterned() ); + } + + void run() + { + mStrings.push_back( new StrTest( NULL ) ); + mStrings.push_back( new StrTest( "" ) ); + mStrings.push_back( new StrTest( "Torque" ) ); + mStrings.push_back( new StrTest( "TGEA" ) ); + mStrings.push_back( new StrTest( "GarageGames" ) ); + mStrings.push_back( new StrTest( "TGB" ) ); + mStrings.push_back( new StrTest( "games" ) ); + mStrings.push_back( new StrTest( "engine" ) ); + mStrings.push_back( new StrTest( "rocks" ) ); + mStrings.push_back( new StrTest( "technology" ) ); + mStrings.push_back( new StrTest( "Torque 3D" ) ); + mStrings.push_back( new StrTest( "Torque 2D" ) ); + + runTestOnStrings< Test1 >(); + runTestOnStrings< Test2 >(); + + runPairwiseTestOnStrings< Test3 >(); + + testEmpty(); + testTrim(); + testCompare(); + testOrder(); + testFind(); + testInsert(); + testReplace(); + testErase(); + testSubstr(); + testToString(); + testCaseConversion(); + testConcat(); + testHash(); + testIntern(); + + for( U32 i = 0; i < mStrings.size(); ++ i ) + delete mStrings[ i ]; + mStrings.clear(); + } +}; + +CreateUnitTest( TestStringBuilder, "Util/StringBuilder" ) +{ + void run() + { + StringBuilder str; + + str.append( 'f' ); + str.append( "oo" ); + str.append( String( "ba" ) ); + str.append( "rfajskfdj", 1 ); + str.format( "%s", "barfoo" ); + + TEST( str.end() == String( "foobarbarfoo" ) ); + } +}; + +#endif // !TORQUE_SHIPPING diff --git a/core/util/timeClass.cpp b/core/util/timeClass.cpp new file mode 100644 index 0000000..d7f113c --- /dev/null +++ b/core/util/timeClass.cpp @@ -0,0 +1,181 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include + +#include "core/util/timeClass.h" + +namespace Torque +{ + +//Micro 0.000001 10-6 +//Milli 0.001 10-3 + +static S8 _DaysInMonth[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static S8 _DaysInMonthLeap[13]= {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static S32 _DayNumber[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; +static S32 _DayNumberLeap[13] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}; + + +/**---------------------------------------------------------------------------- +* PRIVATE Test to see if a year is a leap year. +* +* @param year Year to test for leap year +* @return true if year is a leap year +*/ +inline bool Time::_isLeapYear(S32 year) const +{ + return ((year & 3) == 0) && ( ((year % 100) != 0) || ((year % 400) == 0) ); +} + + +/**---------------------------------------------------------------------------- +* PRIVATE Determine the number of days in any given month/year +* +* @param month Month to be tested +* @param year Year to be tested +* @return Number of days in month/year +*/ +S32 Time::_daysInMonth(S32 month, S32 year) const +{ + if (_isLeapYear(year)) + return _DaysInMonthLeap[month]; + else + return _DaysInMonth[month]; +} + + +//----------------------------------------------------------------------------- +void Time::getCurrentDateTime(DateTime &dateTime) +{ + time_t long_time; + + time( &long_time ); + + struct tm *systime = localtime( &long_time ); + + dateTime.year = systime->tm_year; + dateTime.month = systime->tm_mon; + dateTime.day = systime->tm_mday; + dateTime.hour = systime->tm_hour; + dateTime.minute = systime->tm_min; + dateTime.second = systime->tm_sec; + dateTime.microsecond = 0; +} + +Time Time::getCurrentTime() +{ + return Torque::UnixTimeToTime( time( NULL ) ); +} + +bool Time::set(S32 year, S32 month, S32 day, S32 hour, S32 minute, S32 second, S32 microsecond) +{ + second += microsecond / 100000; + microsecond %= 100000; + minute += second / 60; + second %= 60; + hour += minute / 60; + minute %= 60; + S32 carryDays = hour / 24; + hour %= 24; + + bool leapYear = _isLeapYear(year); + + year -= 1; // all the next operations need (year-1) so do it ahead of time + S32 gregorian = 365 * year // number of days since the epoch + + (year/4) // add Julian leap year days + - (year/100) // subtract century leap years + + (year/400) // add gregorian 400 year leap adjustment + + ((367*month-362)/12) // days in prior months + + day // add days + + carryDays; // add days from time overflow/underflow + + // make days in this year adjustment if leap year + if (leapYear) + { + if (month > 2) + gregorian -= 1; + } + else + { + if (month > 2) + gregorian -= 2; + } + + _time = S64(gregorian) * OneDay; + _time += S64((hour * OneHour) + + (minute * OneMinute) + + (second * OneSecond) + + microsecond); + + return true; +} + + +//----------------------------------------------------------------------------- +void Time::get(S32 *pyear, S32 *pmonth, S32 *pday, S32 *phour, S32 *pminute, S32 *psecond, S32 *pmicrosecond) const +{ + // extract date if requested + if (pyear || pmonth || pday) + { + S32 gregorian = (S32)(_time / OneDay); + + S32 prior = gregorian - 1; // prior days + S32 years400 = prior / 146097L; // number of 400 year cycles + S32 days400 = prior % 146097L; // days NOT in years400 + S32 years100 = days400 / 36524L; // number 100 year cycles not checked + S32 days100 = days400 % 36524L; // days NOT already included + S32 years4 = days100 / 1461L; // number 4 year cycles not checked + S32 days4 = days100 % 1461L; // days NOT already included + S32 year1 = days4 / 365L; // number years not already checked + S32 day1 = days4 % 365L; // days NOT already included + S32 day; + S32 year = (400 * years400) + (100 * years100) + (4 * years4) + year1; + + // December 31 of leap year + if (years100 == 4 || year1 == 4) + { + day = 366; + } + else + { + year += 1; + day = day1 + 1; + } + + const S32 *dayNumber = _isLeapYear(year) ? _DayNumberLeap : _DayNumber; + + // find month and day in month given computed year and day number, + S32 month = 1; + while(day >= dayNumber[month]) + month++; + + day -= dayNumber[month-1]; + + if(pyear) + *pyear = year; + if(pmonth) + *pmonth = month; + if(pday) + *pday = day; + } + + // extract time + if (phour) + *phour = (S32)((_time % OneDay) / OneHour); + + S32 time = (S32)(_time % OneHour); + + if (pminute) + *pminute = time / (S32)OneMinute; + time %= OneMinute; + + if (psecond) + *psecond = time / (S32)OneSecond; + if (pmicrosecond) + *pmicrosecond = time % OneSecond; +} + +} // Namespace diff --git a/core/util/timeClass.h b/core/util/timeClass.h new file mode 100644 index 0000000..d556b8c --- /dev/null +++ b/core/util/timeClass.h @@ -0,0 +1,261 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _TIMECLASS_H_ +#define _TIMECLASS_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + + +#if defined(TORQUE_COMPILER_VISUALC) + #define TORQUE_CONSTANT_S64(a) (a##I64) + #define TORQUE_CONSTANT_U64(a) (a##UI64) +#else + #define TORQUE_CONSTANT_S64(a) (a##LL) ///< Used to declare signed 64 bit constants @hideinitializer + #define TORQUE_CONSTANT_U64(a) (a##ULL) ///< Used to declare unsigned 64 bit constants @hideinitializer +#endif + +namespace Torque +{ + +//----------------------------------------------------------------------------- +/// 64 bit time representation with ten microsecond resolution. +class Time +{ + class Tester; + +public: + struct DateTime + { + S32 year; + S32 month; + S32 day; + S32 hour; + S32 minute; + S32 second; + S32 microsecond; + }; + + static void getCurrentDateTime(DateTime &dateTime); + static Time getCurrentTime(); + + static const S64 OneDay = TORQUE_CONSTANT_S64(8640000000); + static const S64 OneHour = TORQUE_CONSTANT_S64( 360000000); + static const S64 OneMinute = TORQUE_CONSTANT_S64( 6000000); + static const S64 OneSecond = TORQUE_CONSTANT_S64( 100000); + static const S64 OneMillisecond = TORQUE_CONSTANT_S64( 100); + + Time(); + explicit Time(S64 time); + Time(S32 year, S32 month, S32 day, S32 hour, S32 minute, S32 second, S32 microsecond); + Time(const DateTime &dateTime); + + bool set(S32 year, S32 month, S32 day, S32 hour, S32 minute, S32 second, S32 microsecond); + void get(S32 *year, S32 *month, S32 *day, S32 *hour, S32 *minute, S32 *second, S32 *microsecond) const; + + Time operator+() const; + Time operator-() const; + Time operator+(const Time &time) const; + Time operator-(const Time &time) const; + S64 operator/(const Time &time) const; + const Time& operator+=(const Time time); + const Time& operator-=(const Time time); + + template Time operator*(T scaler) const { return Time(_time * scaler); } + template Time operator/(T scaler) const { return Time(_time / scaler); } + template friend Time operator*(T scaler,Time t) { return t * scaler; } + + bool operator==(const Time &time) const; + bool operator!=(const Time &time) const; + bool operator<(const Time &time) const; + bool operator>(const Time &time) const; + bool operator<=(const Time &time) const; + bool operator>=(const Time &time) const; + + operator Tester*() const + { + static Tester test; + return (_time == 0)? 0: &test; + } + bool operator!() const; + + S64 getSeconds() const; + S64 getMilliseconds() const; + S64 getMicroseconds() const; + S64 getInternalRepresentation() const; + +private: + class Tester + { + void operator delete(void*) {} + }; + + S64 _time; + + bool _isLeapYear(S32 year) const; + S32 _daysInMonth(S32 month, S32 year) const; +}; + +namespace TimeConstant +{ + const Time OneDay (Time::OneDay); + const Time OneHour (Time::OneHour); + const Time OneMinute (Time::OneMinute); + const Time OneSecond (Time::OneSecond); + const Time OneMillisecond (Time::OneMillisecond); +} + +//----------------------------------------------------------------------------- + +inline Time::Time() +{ + _time = 0; +} + +inline Time::Time(S64 time) +{ + _time = time; +} + +inline Time::Time(S32 year, S32 month, S32 day, S32 hour, S32 minute, S32 second, S32 microsecond) +{ + set(year, month, day, hour, minute, second, microsecond); +} + +inline Time::Time(const Time::DateTime &dateTime) +{ + set(dateTime.year, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, dateTime.microsecond); +} + +inline Time Time::operator+() const +{ + return Time(_time); +} + +inline Time Time::operator-() const +{ + return Time(-_time); +} + +inline Time Time::operator+(const Time &time) const +{ + return Time(_time + time._time); +} + +inline Time Time::operator-(const Time &time) const +{ + return Time(_time - time._time); +} + +inline S64 Time::operator/(const Time &time) const +{ + return S64(_time / time._time); +} + +inline const Time& Time::operator+=(const Time time) +{ + _time += time._time; + return *this; +} + +inline const Time& Time::operator-=(const Time time) +{ + _time -= time._time; + return *this; +} + +inline bool Time::operator==(const Time &time) const +{ + return (_time == time._time); +} + +inline bool Time::operator!=(const Time &time) const +{ + return (_time != time._time); +} + +inline bool Time::operator<(const Time &time) const +{ + return (_time < time._time); +} + +inline bool Time::operator>(const Time &time) const +{ + return (_time > time._time); +} + +inline bool Time::operator<=(const Time &time) const +{ + return (_time <= time._time); +} + +inline bool Time::operator>=(const Time &time) const +{ + return (_time >= time._time); +} + +inline bool Time::operator !() const +{ + return _time == 0; +} + +inline S64 Time::getSeconds() const +{ + return _time / TimeConstant::OneSecond._time; +} + +inline S64 Time::getMilliseconds() const +{ + return _time / TimeConstant::OneMillisecond._time; +} + +inline S64 Time::getMicroseconds() const +{ + return _time * 10; +} + +inline S64 Time::getInternalRepresentation() const +{ + return _time; +} + +//----------------------------------------------------------------------------- +// time i/o time functions + +template inline bool read(S &stream, Time *theTime) +{ + S64 time; + bool ret = read(stream, &time); + *theTime = Time(time); + return ret; +} + +template inline bool write(S &stream, const Time &theTime) +{ + S64 time = theTime.getInternalRepresentation(); + return write(stream, time); +} + +//----------------------------------------------------------------------------- + +inline Time UnixTimeToTime(U32 t) +{ + // Converts "unix" time, seconds since 00:00:00 UTC, January 1, 1970 + return Time(((S64)(t)) * 100000 + TORQUE_CONSTANT_S64(6213568320000000)); +} + +inline Time Win32FileTimeToTime(U32 low,U32 high) +{ + // Converts Win32 "file" time, 100 nanosecond intervals since 00:00:00 UTC, January 1, 1601 + S64 t = (((S64)high) << 32) + low; + return Time(t / 100 + TORQUE_CONSTANT_S64(5049120960000000)); +} + + +} // Namespace + +#endif diff --git a/core/util/timeSource.h b/core/util/timeSource.h new file mode 100644 index 0000000..2f47b3a --- /dev/null +++ b/core/util/timeSource.h @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TIMESOURCE_H_ +#define _TIMESOURCE_H_ + +#ifndef _PLATFORM_H_ +# include "platform/platform.h" +#endif +#ifndef _TSTREAM_H_ +# include "core/stream/tStream.h" +#endif + + +/// Timer that queries the real-time ticker. +struct RealMSTimer +{ + static U32 getTick() + { + return Platform::getRealMilliseconds(); + } +}; + +/// Timer that queries the simulation-time ticker. +struct VirtualMSTimer +{ + static U32 getTick() + { + return Platform::getVirtualMilliseconds(); + } +}; + +/// +template< class Timer = RealMSTimer, typename Tick = U32 > +class GenericTimeSource : public IPositionable< Tick >, + public IProcess, + public IResettable +{ + public: + + typedef IPositionable< Tick > Parent; + typedef Tick TickType; + + protected: + + /// + TickType mStartTime; + + /// + TickType mPauseTime; + + /// + Timer mTimer; + + public: + + GenericTimeSource() + : mStartTime( TypeTraits< TickType >::MAX ), + mPauseTime( TypeTraits< TickType >::MAX ) {} + + bool isStarted() const + { + return ( mStartTime != TypeTraits< TickType >::MAX ); + } + bool isPaused() const + { + return ( mPauseTime != TypeTraits< TickType >::MAX ); + } + + /// Return the number of ticks since the time source + /// has been started. + TickType getPosition() const + { + if( !isStarted() ) + return TypeTraits< TickType >::ZERO; + else if( isPaused() ) + return ( mPauseTime - mStartTime ); + else + return ( mTimer.getTick() - mStartTime ); + } + + /// + void setPosition( TickType pos ) + { + if( !isStarted() ) + mStartTime = pos; + else + { + TickType savedStartTime = mStartTime; + + mStartTime = ( mTimer.getTick() - pos ); + if( isPaused() ) + mPauseTime = ( mStartTime + ( mPauseTime - savedStartTime ) ); + } + } + + // IResettable. + virtual void reset() + { + mStartTime = TypeTraits< TickType >::MAX; + mPauseTime = TypeTraits< TickType >::MAX; + } + + // IProcess. + virtual void start() + { + if( !isStarted() ) + { + TickType now = mTimer.getTick(); + + if( isPaused() ) + { + mStartTime += now - mPauseTime; + mPauseTime = TypeTraits< TickType >::MAX; + } + else + mStartTime = now; + } + } + virtual void stop() + { + reset(); + } + virtual void pause() + { + if( !isPaused() ) + mPauseTime = mTimer.getTick(); + } +}; + +#endif // _TIMESOURCE_H_ diff --git a/core/util/zip/centralDir.cpp b/core/util/zip/centralDir.cpp new file mode 100644 index 0000000..5502bd7 --- /dev/null +++ b/core/util/zip/centralDir.cpp @@ -0,0 +1,303 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/stream.h" +#include "core/strings/stringFunctions.h" + +#include "core/util/zip/centralDir.h" +#include "core/util/zip/compressor.h" + +#include "core/util/safeDelete.h" + +namespace Zip +{ + +//----------------------------------------------------------------------------- +// CentralDir Class +//----------------------------------------------------------------------------- + +CentralDir::CentralDir() +{ + mHeaderSig = mCentralDirSignature; + + mDiskNumStart = 0; + + mInternalFileAttr = 0; + mExternalFileAttr = 0; + + mLocalHeadOffset = 0; + + mVersionMadeBy = 0; + + mFileComment = NULL; + + mInternalFlags = 0; +} + +CentralDir::CentralDir(FileHeader &fh) : FileHeader(fh) +{ + mHeaderSig = mCentralDirSignature; + + mDiskNumStart = 0; + + mInternalFileAttr = 0; + mExternalFileAttr = 0; + + mLocalHeadOffset = 0; + + mVersionMadeBy = 0; + + mFileComment = NULL; +} + +CentralDir::~CentralDir() +{ + SAFE_DELETE_ARRAY(mFileComment); +} + +//----------------------------------------------------------------------------- + +bool CentralDir::read(Stream *stream) +{ + stream->read(&mHeaderSig); + if(mHeaderSig != mCentralDirSignature) + return false; + + stream->read(&mVersionMadeBy); + stream->read(&mExtractVer); + stream->read(&mFlags); + stream->read(&mCompressMethod); + stream->read(&mModTime); + stream->read(&mModDate); + stream->read(&mCRC32); + stream->read(&mCompressedSize); + stream->read(&mUncompressedSize); + + U16 fnLen, efLen, fcLen; + stream->read(&fnLen); + stream->read(&efLen); + stream->read(&fcLen); + + stream->read(&mDiskNumStart); + + stream->read(&mInternalFileAttr); + stream->read(&mExternalFileAttr); + + stream->read(&mLocalHeadOffset); + + char *fn = new char[fnLen + 1]; + stream->read(fnLen, fn); + fn[fnLen] = 0; + mFilename = String(fn); + SAFE_DELETE_ARRAY(fn); + + + // [tom, 10/28/2006] We currently only need the extra fields when we want to + // open the file, so we won't bother reading them here. This avoids keeping + // them in memory twice. + + //readExtraFields(stream, efLen); + stream->setPosition(stream->getPosition() + efLen); + + fn = new char[fcLen + 1]; + stream->read(fcLen, fn); + fn[fcLen] = 0; + + SAFE_DELETE_ARRAY(mFileComment); + mFileComment = fn; + + // Sanity checks to make life easier elsewhere + if(mCompressMethod != Stored && mUncompressedSize == 0 && mCompressedSize == 0) + mCompressMethod = Stored; + + return true; +} + +bool CentralDir::write(Stream *stream) +{ + mHeaderSig = mCentralDirSignature; + stream->write(mHeaderSig); + + stream->write(mVersionMadeBy); + stream->write(mExtractVer); + stream->write(mFlags); + stream->write(mCompressMethod); + stream->write(mModTime); + stream->write(mModDate); + stream->write(mCRC32); + stream->write(mCompressedSize); + stream->write(mUncompressedSize); + + U16 fnLen = mFilename.length(), + efLen = 0, + fcLen = mFileComment ? (U16)dStrlen(mFileComment) : 0; + stream->write(fnLen); + stream->write(efLen); + stream->write(fcLen); + + stream->write(mDiskNumStart); + + stream->write(mInternalFileAttr); + stream->write(mExternalFileAttr); + + stream->write(mLocalHeadOffset); + + if(fnLen) + stream->write(fnLen, mFilename); + + // FIXME [tom, 10/29/2006] Write extra fields here + + if(fcLen) + stream->write(fcLen, mFileComment); + + return true; +} + +//----------------------------------------------------------------------------- + +void CentralDir::setFileComment(const char *comment) +{ + SAFE_DELETE_ARRAY(mFileComment); + mFileComment = new char [dStrlen(comment)+1]; + dStrcpy(mFileComment, comment); +} + +//----------------------------------------------------------------------------- +// EndOfCentralDir Class +//----------------------------------------------------------------------------- + +EndOfCentralDir::EndOfCentralDir() +{ + mHeaderSig = mEOCDSignature; + + mDiskNum = 0; + mStartCDDiskNum = 0; + mNumEntriesInThisCD = 0; + mTotalEntriesInCD = 0; + mCDSize = 0; + mCDOffset = 0; + mCommentSize = 0; + mZipComment = NULL; +} + +EndOfCentralDir::~EndOfCentralDir() +{ + SAFE_DELETE_ARRAY(mZipComment); +} + +//----------------------------------------------------------------------------- + +bool EndOfCentralDir::read(Stream *stream) +{ + stream->read(&mHeaderSig); + if(mHeaderSig != mEOCDSignature) + return false; + + stream->read(&mDiskNum); + stream->read(&mStartCDDiskNum); + stream->read(&mNumEntriesInThisCD); + stream->read(&mTotalEntriesInCD); + stream->read(&mCDSize); + stream->read(&mCDOffset); + + stream->read(&mCommentSize); + + char *comment = new char[mCommentSize + 1]; + stream->read(mCommentSize, comment); + comment[mCommentSize] = 0; + + SAFE_DELETE_ARRAY(mZipComment); + mZipComment = comment; + + return true; +} + +bool EndOfCentralDir::write(Stream *stream) +{ + stream->write(mHeaderSig); + + stream->write(mDiskNum); + stream->write(mStartCDDiskNum); + stream->write(mNumEntriesInThisCD); + stream->write(mTotalEntriesInCD); + stream->write(mCDSize); + stream->write(mCDOffset); + + stream->write(mCommentSize); + if(mZipComment && mCommentSize) + stream->write(mCommentSize, mZipComment); + + return true; +} + +//----------------------------------------------------------------------------- + +// [tom, 10/19/2006] I know, i know ... this'll get rewritten. +// [tom, 1/23/2007] Maybe. + +bool EndOfCentralDir::findInStream(Stream *stream) +{ + U32 initialPos = stream->getPosition(); + U32 size = stream->getStreamSize(); + U32 pos; + if(size == 0) + return false; + + if(! stream->setPosition(size - mRecordSize)) + goto hell; + + U32 sig; + stream->read(&sig); + + if(sig == mEOCDSignature) + { + stream->setPosition(size - mRecordSize); + return true; + } + + // OK, so we couldn't find the EOCD where we expected it. The zip file + // either has comments or isn't a zip file. We need to search the last + // 64Kb of the file for the EOCD. + + pos = size > mEOCDSearchSize ? size - mEOCDSearchSize : 0; + if(! stream->setPosition(pos)) + goto hell; + + while(pos < (size - 4)) + { + stream->read(&sig); + + if(sig == mEOCDSignature) + { + stream->setPosition(pos); + return true; + } + + pos++; + if(! stream->setPosition(pos)) + goto hell; + } + +hell: + stream->setPosition(initialPos); + return false; +} + +//----------------------------------------------------------------------------- + +void EndOfCentralDir::setZipComment(U16 commentSize, const char *zipComment) +{ + SAFE_DELETE_ARRAY(mZipComment); + mZipComment = new char [commentSize]; + dMemcpy((void *)mZipComment, zipComment, commentSize); + mCommentSize = commentSize; +} + +void EndOfCentralDir::setZipComment(const char *zipComment) +{ + setZipComment(dStrlen(zipComment), zipComment); +} + +} // end namespace Zip diff --git a/core/util/zip/centralDir.h b/core/util/zip/centralDir.h new file mode 100644 index 0000000..7254eaf --- /dev/null +++ b/core/util/zip/centralDir.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/zip/fileHeader.h" + +#ifndef _CENTRALDIR_H_ +#define _CENTRALDIR_H_ + +namespace Zip +{ + +/// @addtogroup zipint_group +/// @ingroup zip_group +// @{ + +/// Internal flags used by the zip code when writing zips +enum CDIntFlags +{ + CDFileDirty = BIT(0), + CDFileAdded = BIT(1), + CDFileDeleted = BIT(2), + CDFileOpen = BIT(3) +}; + +class CentralDir : public FileHeader +{ + typedef FileHeader Parent; + + static const U32 mCentralDirSignature = 0x02014b50; + + char *mFileComment; + +public: + U16 mDiskNumStart; + + U16 mInternalFileAttr; + U32 mExternalFileAttr; + + U32 mLocalHeadOffset; + + U16 mVersionMadeBy; + + U32 mInternalFlags; + + CentralDir(); + CentralDir(FileHeader &fh); + virtual ~CentralDir(); + + virtual bool read(Stream *stream); + virtual bool write(Stream *stream); + + void setFileComment(const char *comment); +}; + +class EndOfCentralDir +{ + static const U32 mEOCDSignature = 0x06054b50; + /// The size of the EndOfCentralDir record in the zip file, used to locate it + /// at the end of the file. + static const U32 mRecordSize = 20; + /// The number of bytes from the end of the file to start searching for the EOCD + static const U32 mEOCDSearchSize = 64 * 1024; + +public: + U32 mHeaderSig; + + U16 mDiskNum; + U16 mStartCDDiskNum; + U16 mNumEntriesInThisCD; + U16 mTotalEntriesInCD; + U32 mCDSize; + U32 mCDOffset; + U16 mCommentSize; + + const char *mZipComment; + + EndOfCentralDir(); + virtual ~EndOfCentralDir(); + + virtual bool findInStream(Stream *stream); + + virtual bool read(Stream *stream); + virtual bool write(Stream *stream); + + virtual void setZipComment(const char *zipComment); + virtual void setZipComment(U16 commentSize, const char *zipComment); +}; + +// @} + +} // end namespace Zip + +#endif // _CENTRALDIR_H_ diff --git a/core/util/zip/compressor.cpp b/core/util/zip/compressor.cpp new file mode 100644 index 0000000..3074a4f --- /dev/null +++ b/core/util/zip/compressor.cpp @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "core/util/zip/compressor.h" + +namespace Zip +{ + +static Compressor *gCompressorInitList = NULL; + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +Compressor::Compressor(S32 method, const char *name) +{ + mName = name; + mMethod = method; + + mNext = gCompressorInitList; + gCompressorInitList = this; +} + +//----------------------------------------------------------------------------- +// Static Methods +//----------------------------------------------------------------------------- + +Compressor *Compressor::findCompressor(S32 method) +{ + for(Compressor *walk = gCompressorInitList;walk;walk = walk->mNext) + { + if(walk->getMethod() == method) + return walk; + } + + return NULL; +} + +Compressor *Compressor::findCompressor(const char *name) +{ + for(Compressor *walk = gCompressorInitList;walk;walk = walk->mNext) + { + if(dStricmp(walk->getName(), name) == 0) + return walk; + } + + return NULL; +} + +} // end namespace Zip diff --git a/core/util/zip/compressor.h b/core/util/zip/compressor.h new file mode 100644 index 0000000..f671d22 --- /dev/null +++ b/core/util/zip/compressor.h @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/zip/centralDir.h" + +#ifndef _COMPRESSOR_H_ +#define _COMPRESSOR_H_ + +// Forward refs +class Stream; + +namespace Zip +{ + +/// @addtogroup zipint_group +/// @ingroup zip_group +// @{ + +// [tom, 10/26/2006] Standard Zip compression methods +enum CompressionMethod +{ + Stored = 0, + Shrunk = 1, + ReducedL1 = 2, + ReducedL2 = 3, + ReducedL3 = 4, + ReducedL4 = 5, + Imploded = 6, + ReservedTokenized = 7, + Deflated = 8, + EnhDefalted = 9, + DateCompression = 10, + ReservedPKWARE = 11, + BZip2 = 12, + PPMd = 98, + AESEncrypted = 99, // WinZip's AES Encrypted zips use compression method 99 + // to indicate AES encryption. The actual compression + // method is specified in the AES extra field. +}; + +class Compressor +{ + Compressor *mNext; + +protected: + const char *mName; //!< The name of the compression method + S32 mMethod; //!< The compression method as in the Zip header + +public: + Compressor(S32 method, const char *name); + virtual ~Compressor() {} + + inline const char * getName() { return mName; } + inline S32 getMethod() { return mMethod; } + + virtual Stream *createReadStream(const CentralDir *cdir, Stream *zipStream) = 0; + virtual Stream *createWriteStream(const CentralDir *cdir, Stream *zipStream) = 0; + + // Run time lookup methods + static Compressor *findCompressor(const char *name); + static Compressor *findCompressor(S32 method); +}; + +#define ImplementCompressor(name, method) \ + class Compressor##name : public Compressor \ + { \ + public: \ + Compressor##name(S32 m, const char *n) : Compressor(m, n) {} \ + virtual Stream *createReadStream(const CentralDir *cdir, Stream *zipStream); \ + virtual Stream *createWriteStream(const CentralDir *cdir, Stream *zipStream); \ + }; \ + Compressor##name gCompressor##name##instance(method, #name); + +#define CompressorCreateReadStream(name) \ + Stream * Compressor##name::createReadStream(const CentralDir *cdir, Stream *zipStream) + +#define CompressorCreateWriteStream(name) \ + Stream * Compressor##name::createWriteStream(const CentralDir *cdir, Stream *zipStream) + +// @} + +} // end namespace Zip + +#endif // _COMPRESSOR_H_ diff --git a/core/util/zip/compressors/deflate.cpp b/core/util/zip/compressors/deflate.cpp new file mode 100644 index 0000000..8e74d1f --- /dev/null +++ b/core/util/zip/compressors/deflate.cpp @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/util/zip/compressor.h" + +#include "core/util/zip/zipSubStream.h" + +namespace Zip +{ + +ImplementCompressor(Deflate, Deflated); + +CompressorCreateReadStream(Deflate) +{ + ZipSubRStream *stream = new ZipSubRStream; + stream->attachStream(zipStream); + stream->setUncompressedSize(cdir->mUncompressedSize); + + return stream; +} + +CompressorCreateWriteStream(Deflate) +{ + ZipSubWStream *stream = new ZipSubWStream; + stream->attachStream(zipStream); + + return stream; +} + +} // end namespace Zip diff --git a/core/util/zip/compressors/stored.cpp b/core/util/zip/compressors/stored.cpp new file mode 100644 index 0000000..820c41e --- /dev/null +++ b/core/util/zip/compressors/stored.cpp @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/util/zip/compressor.h" + +#include "core/resizeStream.h" + +namespace Zip +{ + +ImplementCompressor(Stored, Stored); + +CompressorCreateReadStream(Stored) +{ + ResizeFilterStream *resStream = new ResizeFilterStream; + resStream->attachStream(zipStream); + resStream->setStreamOffset(zipStream->getPosition(), cdir->mCompressedSize); + + return resStream; +} + +CompressorCreateWriteStream(Stored) +{ + return zipStream; +} + +} // end namespace Zip diff --git a/core/util/zip/crctab.h b/core/util/zip/crctab.h new file mode 100644 index 0000000..14b1724 --- /dev/null +++ b/core/util/zip/crctab.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CRCTAB_H_ +#define _CRCTAB_H_ + +static U32 crc_32_tab[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +#define ZC_CRC32(c, b) (crc_32_tab[((int)(c) ^ (b)) & 0xff] ^ ((c) >> 8)) + +#endif // _CRCTAB_H_ + diff --git a/core/util/zip/extraField.cpp b/core/util/zip/extraField.cpp new file mode 100644 index 0000000..0600dae --- /dev/null +++ b/core/util/zip/extraField.cpp @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/util/zip/extraField.h" + +namespace Zip +{ + +static ExtraField *gExtraFieldInitList = NULL; + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +ExtraField::ExtraField(U16 id, ExtraFieldCreateFn fnCreate) +{ + mID = id; + mCreateFn = fnCreate; + + mNext = gExtraFieldInitList; + gExtraFieldInitList = this; +} + +//----------------------------------------------------------------------------- +// Static Methods +//----------------------------------------------------------------------------- + +ExtraField * ExtraField::create(U16 id) +{ + for(ExtraField *walk = gExtraFieldInitList;walk;walk = walk->mNext) + { + if(walk->getID() == id) + return walk->mCreateFn(); + } + + return NULL; +} + +} // end namespace Zip diff --git a/core/util/zip/extraField.h b/core/util/zip/extraField.h new file mode 100644 index 0000000..27327b1 --- /dev/null +++ b/core/util/zip/extraField.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EXTRAFIELD_H_ +#define _EXTRAFIELD_H_ + +class Stream; + +namespace Zip +{ + +/// @addtogroup zipint_group +/// @ingroup zip_group +// @{ + + +// Forward Refs +class ExtraField; + +// Creation Helpers +typedef ExtraField *(*ExtraFieldCreateFn)(); + +template ExtraField * createExtraField() +{ + return new T; +} + +// ExtraField base class +class ExtraField +{ + ExtraField *mNext; + +protected: + U16 mID; + ExtraFieldCreateFn mCreateFn; + +public: + ExtraField() + { + mID = 0; + mCreateFn = NULL; + } + ExtraField(U16 id, ExtraFieldCreateFn fnCreate); + + virtual ~ExtraField() {} + + inline U16 getID() { return mID; } + + virtual bool read(Stream *stream) = 0; + + // Run time creation methods + static ExtraField *create(U16 id); +}; + +#define DeclareExtraField(name) \ + name(U16 id, ExtraFieldCreateFn fnCreate) : Parent(id, fnCreate) {} + +#define ImplementExtraField(name, id) \ + name gExtraField##name##instance(id, &createExtraField); + +// @} + +} // end namespace Zip + +#endif // _EXTRAFIELD_H_ diff --git a/core/util/zip/fileHeader.cpp b/core/util/zip/fileHeader.cpp new file mode 100644 index 0000000..91200d6 --- /dev/null +++ b/core/util/zip/fileHeader.cpp @@ -0,0 +1,174 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/frameAllocator.h" +#include "core/stream/stream.h" + +#include "core/util/zip/fileHeader.h" +#include "core/util/zip/compressor.h" + +#include "core/util/safeDelete.h" + +#include "core/resizeStream.h" +#include "core/strings/stringFunctions.h" +#include "core/frameAllocator.h" + +namespace Zip +{ + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +FileHeader::FileHeader() +{ + mHeaderSig = mFileHeaderSignature; + + mExtractVer = 20; + mFlags = 0; + mCompressMethod = Stored; + + mModTime = 0; + mModDate = 0; + + mCRC32 = 0; + + mCompressedSize = 0; + mUncompressedSize = 0; + + mFilename = ""; +} + +FileHeader::~FileHeader() +{ + for(S32 i = 0;i < mExtraFields.size();i++) + { + SAFE_DELETE(mExtraFields[i]); + } +} + +//----------------------------------------------------------------------------- +// Protected Methods +//----------------------------------------------------------------------------- + +bool FileHeader::readExtraFields(Stream *stream, U16 efLen) +{ + bool ret = true; + + U32 pos = stream->getPosition(); + U32 end = pos + efLen; + + while(stream->getPosition() < end) + { + U16 fieldSig, fieldSize; + + ret = false; + + ret |= stream->read(&fieldSig); + ret |= stream->read(&fieldSize); + if(! ret) + break; + + pos = stream->getPosition(); + + ExtraField *ef = ExtraField::create(fieldSig); + if(ef) + { + ret |= ef->read(stream); + + if(! ret) + delete ef; + else + mExtraFields.push_back(ef); + } + + stream->setPosition(pos + fieldSize); + } + + return ret; +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +bool FileHeader::read(Stream *stream) +{ + stream->read(&mHeaderSig); + if(mHeaderSig != mFileHeaderSignature) + return false; + + stream->read(&mExtractVer); + stream->read(&mFlags); + stream->read(&mCompressMethod); + stream->read(&mModTime); + stream->read(&mModDate); + stream->read(&mCRC32); + stream->read(&mCompressedSize); + stream->read(&mUncompressedSize); + + U16 fnLen, efLen; + stream->read(&fnLen); + stream->read(&efLen); + + char *fn = new char[fnLen + 1]; + stream->read(fnLen, fn); + fn[fnLen] = 0; + mFilename = fn; + SAFE_DELETE_ARRAY(fn); + + + return readExtraFields(stream, efLen); +} + +bool FileHeader::write(Stream *stream) +{ + mHeaderSig = mFileHeaderSignature; + + stream->write(mHeaderSig); + + stream->write(mExtractVer); + stream->write(mFlags); + stream->write(mCompressMethod); + stream->write(mModTime); + stream->write(mModDate); + stream->write(mCRC32); + stream->write(mCompressedSize); + stream->write(mUncompressedSize); + + U16 fnLen = mFilename.length(), + efLen = 0; + stream->write(fnLen); + stream->write(efLen); + + if(fnLen) + stream->write(fnLen, mFilename); + + // FIXME [tom, 1/23/2007] Write extra fields here + + return true; +} + +//----------------------------------------------------------------------------- + +ExtraField *FileHeader::findExtraField(U16 id) +{ + for(S32 i = 0;i < mExtraFields.size();++i) + { + if(mExtraFields[i]->getID() == id) + return mExtraFields[i]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- + +void FileHeader::setFilename(String filename) +{ + mFilename = filename; +} + +} // end namespace Zip diff --git a/core/util/zip/fileHeader.h b/core/util/zip/fileHeader.h new file mode 100644 index 0000000..0d24522 --- /dev/null +++ b/core/util/zip/fileHeader.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/zip/extraField.h" +#include "core/util/tVector.h" + +#ifndef _FILEHEADER_H_ +#define _FILEHEADER_H_ + +// Forward Refs +class Stream; + +namespace Zip +{ + +/// @addtogroup zipint_group +/// @ingroup zip_group +// @{ + +enum FileFlags +{ + Encrypted = BIT(0), + + // For implode compression + Implode8KDictionary = BIT(1), + Implode3ShannonFanoTrees = BIT(2), + + // For deflate compression + DeflateTypeMask = BIT(1) | BIT(2), + + FileInfoInDirectory = BIT(3), + + // Note that much of the following flag bits are unsupported for various reasons + ReservedEnhDeflate = BIT(4), + PatchData = BIT(5), + StrongEncryption = BIT(6), + + UnusedReserved1 = BIT(7), + UnusedReserved2 = BIT(8), + UnusedReserved3 = BIT(9), + UnusedReserved4 = BIT(10), + UnusedReserved5 = BIT(11), + + ReservedPKWARE1 = BIT(12), + + EncryptedDirectory = BIT(13), + + ReservedPKWARE2 = BIT(14), + ReservedPKWARE3 = BIT(15), +}; + +class FileHeader +{ + static const U32 mFileHeaderSignature = 0x04034b50; + +protected: + bool readExtraFields(Stream *stream, U16 efLen); + +public: + U32 mHeaderSig; + + U16 mExtractVer; + U16 mFlags; + U16 mCompressMethod; + + U16 mModTime; + U16 mModDate; + + U32 mCRC32; + + U32 mCompressedSize; + U32 mUncompressedSize; + + String mFilename; + + Vector mExtraFields; + + FileHeader(); + virtual ~FileHeader(); + + virtual bool read(Stream *stream); + virtual bool write(Stream *stream); + + ExtraField *findExtraField(U16 id); + + void setFilename(String filename); +}; + +// @} + +} // end namespace Zip + +#endif diff --git a/core/util/zip/unitTests/zipTest.h b/core/util/zip/unitTests/zipTest.h new file mode 100644 index 0000000..9e6b9ee --- /dev/null +++ b/core/util/zip/unitTests/zipTest.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stringTable.h" + +#ifndef _ZIPTEST_H_ +#define _ZIPTEST_H_ + +namespace Zip +{ + +//----------------------------------------------------------------------------- +// +// Things that need to be tested: +// +// Writing Zips: +// Write to file that doesn't already exist +// Write to file that we've already written to +// Write to file that already exists, but we haven't written to +// Attempt to open a file for write that's already open for write (should fail) +// ** Attempt to open a file that's open for read (should fail) +// Writing >= 5000 files to a zip +// Writing >= 5000 files to a zip in a number of folders +// +// Reading Zips: +// Open for read from file that doesn't exist (should fail) +// Read from file that already existed in the zip +// Read from file that we have written to the zip but not yet rebuilt +// ** Read from file that is already open for read (should fail) +// Read from file that is already open for write (should fail) +// +// Miscellaneous: +// Opening a file in the zip as ReadWrite (should fail) +// Enumerating files +// Deleting files +// ** Adding files +// Opening Zips with zip comments +// Opening Zips/Files with file comments +// Opening large zips that require searching for the EOCD +// +// All tests should be run on zip files that are opened for read, write and readwrite +// All tests need to be run for both forms of openArchive() +// +// All tests that require an existing zip file should be run on a zip created with +// a standard zip application in addition to zip files created by this code. +// +// Tests involving ReadWrite mode need to be done both with and without the zip +// file existing before the test. +// +// Tests marked ** are not possible to test yet as they are not supported by the +// zip code. +// +// [tom, 2/2/2007] I'm using WinZip 11 to create the baseline zips. It is probably +// worthwhile to include zips created in additional applications. +// +//----------------------------------------------------------------------------- + +// Directory relative to executable directory to use as a working directory +#define ZIPTEST_WORKING_DIR "unitTests/zip" + +// The filename of the zip file that we create for writing to +#define ZIPTEST_WRITE_FILENAME "zipUnitTest.zip" + +// The filename of the zip file created in a standard zip application +#define ZIPTEST_BASELINE_FILENAME "zipUnitTestBaseline.zip" + +// The filename of our working copy of the above so that we don't destroy the svn copy +#define ZIPTEST_WORKING_FILENAME "zipUnitTestWorking.zip" + +//----------------------------------------------------------------------------- +// Helper Functions +//----------------------------------------------------------------------------- + +inline StringTableEntry makeTestPath(const char *path) +{ + char buffer[1024], dir[1024]; + + Platform::makeFullPathName(ZIPTEST_WORKING_DIR, dir, sizeof(dir), Platform::getMainDotCsDir()); + Platform::makeFullPathName(path, buffer, sizeof(buffer), dir); + + return StringTable->insert(buffer, true); +} + +//----------------------------------------------------------------------------- + +} // end namespace Zip + +#endif // _ZIPTEST_H_ diff --git a/core/util/zip/unitTests/zipTestMisc.cpp b/core/util/zip/unitTests/zipTestMisc.cpp new file mode 100644 index 0000000..71bcc2b --- /dev/null +++ b/core/util/zip/unitTests/zipTestMisc.cpp @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/crc.h" +#include "core/strings/stringFunctions.h" +#include "core/util/zip/zipArchive.h" +#include "core/util/zip/unitTests/zipTest.h" + +#include "unit/test.h" +#include "unit/memoryTester.h" + + +using namespace UnitTesting; +using namespace Zip; + +CreateUnitTest(ZipTestMisc, "Zip/Misc") +{ +private: + StringTableEntry mWriteFilename; + StringTableEntry mBaselineFilename; + StringTableEntry mWorkingFilename; + +public: + ZipTestMisc() + { + mWriteFilename = makeTestPath(ZIPTEST_WRITE_FILENAME); + mBaselineFilename = makeTestPath(ZIPTEST_BASELINE_FILENAME); + mWorkingFilename = makeTestPath(ZIPTEST_WORKING_FILENAME); + } + + void run() + { + MemoryTester m; + m.mark(); + + // Clean up from a previous run + cleanup(); + + test(makeTestZip(), "Failed to create test zip"); + + miscTest(mWorkingFilename); + + // [tom, 2/7/2007] extractFile() uses the resource manager so it will + // create a resource object that will be detected as a "leak" since it + // won't be freed until much later. + + test(m.check(), "Zip misc test leaked memory! (Unless it says it's from the Resource Manager above, see comments in core/zip/unitTests/zipTestMisc.cc)"); + } + +private: + + //----------------------------------------------------------------------------- + + void cleanup() + { + if(Platform::isFile(mWriteFilename)) + dFileDelete(mWriteFilename); + if(Platform::isFile(mWorkingFilename)) + dFileDelete(mWorkingFilename); + } + + bool makeTestZip() + { + FileStream source, dest; + + if(! source.open(mBaselineFilename, Torque::FS::File::Read)) + { + fail("Failed to open baseline zip for read"); + return false; + } + + // [tom, 2/7/2007] FileStream's d'tor calls close() so we don't really have to do it here + + if(! dest.open(mWorkingFilename, Torque::FS::File::Write)) + { + fail("Failed to open working zip for write"); + return false; + } + + if(! dest.copyFrom(&source)) + { + fail("Failed to copy baseline zip"); + return false; + } + + dest.close(); + source.close(); + return true; + } + + //----------------------------------------------------------------------------- + + bool miscTest(const char *zipfile) + { + ZipArchive *zip = new ZipArchive; + bool ret = true; + + if(! zip->openArchive(zipfile, ZipArchive::ReadWrite)) + { + delete zip; + fail("Unable to open zip file"); + + return false; + } + + // Opening a file in the zip as ReadWrite (should fail) + Stream *stream; + if((stream = zip->openFile("readWriteTest.txt", ZipArchive::ReadWrite)) != NULL) + { + zip->closeFile(stream); + + fail("File opened successfully as read/write"); + ret = false; + } + + // Enumerating, Extracting and Deleting files + U32 curSize = zip->mDiskStream->getStreamSize(); + + if(zip->numEntries() > 0) + { + // Find the biggest file in the zip + const CentralDir *del = NULL; + for(S32 i = 0;i < zip->numEntries();++i) + { + const CentralDir &cd = (*zip)[i]; + + if(del == NULL || (del && cd.mUncompressedSize > del->mUncompressedSize)) + del = &cd; + } + + if(del) + { + // Extract the file + const char *ptr = dStrrchr(del->mFilename, '/'); + if(ptr) + ++ptr; + else + ptr = del->mFilename; + StringTableEntry fn = makeTestPath(ptr); + + ret = test(zip->extractFile(del->mFilename, fn), "Failed to extract file."); + + // Then delete it + ret = test(zip->deleteFile(del->mFilename), "ZipArchive::deleteFile() failed?!"); + + // Close and reopen the zip to force it to rebuild + zip->closeArchive(); + + if(! zip->openArchive(zipfile, ZipArchive::ReadWrite)) + { + delete zip; + fail("Unable to re-open zip file?!"); + + return false; + } + } + else + { + fail("Couldn't find a file to delete?!"); + ret = false; + } + } + else + { + fail("Baseline zip has no files."); + ret = false; + } + + U32 newSize = zip->mDiskStream->getStreamSize(); + test(newSize != curSize, "Zip file size didn't change when deleting a file ?!"); + + zip->closeArchive(); + delete zip; + + return ret; + } +}; diff --git a/core/util/zip/unitTests/zipTestRead.cpp b/core/util/zip/unitTests/zipTestRead.cpp new file mode 100644 index 0000000..6752074 --- /dev/null +++ b/core/util/zip/unitTests/zipTestRead.cpp @@ -0,0 +1,235 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "unit/test.h" +#include "unit/memoryTester.h" + +#include "core/util/zip/zipArchive.h" +#include "core/util/zip/unitTests/zipTest.h" + +#include "core/stringTable.h" +#include "core/crc.h" + +using namespace UnitTesting; +using namespace Zip; + +CreateUnitTest(ZipTestRead, "Zip/Read") +{ +private: + StringTableEntry mWriteFilename; + StringTableEntry mBaselineFilename; + StringTableEntry mWorkingFilename; + +public: + ZipTestRead() + { + mWriteFilename = makeTestPath(ZIPTEST_WRITE_FILENAME); + mBaselineFilename = makeTestPath(ZIPTEST_BASELINE_FILENAME); + mWorkingFilename = makeTestPath(ZIPTEST_WORKING_FILENAME); + } + + void run() + { + MemoryTester m; + m.mark(); + + // Clean up from a previous run + cleanup(); + + // Read mode tests with the baseline zip + readTest(mBaselineFilename); + + // Read mode tests with a zip created by us + test(makeTestZip(), "Failed to make test zip"); + readTest(mWorkingFilename); + + // Do read/write mode tests + readWriteTest(mWriteFilename); + + test(m.check(), "Zip read test leaked memory!"); + } + +private: + + //----------------------------------------------------------------------------- + + void cleanup() + { + if(Platform::isFile(mWriteFilename)) + dFileDelete(mWriteFilename); + if(Platform::isFile(mWorkingFilename)) + dFileDelete(mWorkingFilename); + } + + //----------------------------------------------------------------------------- + + bool writeFile(ZipArchive *zip, const char *filename) + { + Stream * stream = zip->openFile(filename, ZipArchive::Write); + if(stream != NULL) + { + stream->writeLine((U8 *)"This is a test file for reading from a zip created with the zip code.\r\nIt is pointless by itself, but needs to be long enough that it gets compressed.\r\n"); + zip->closeFile(stream); + return true; + } + + return false; + } + + bool makeTestZip() + { + ZipArchive *zip = new ZipArchive; + + if(!zip->openArchive(mWorkingFilename, ZipArchive::Write)) + { + delete zip; + fail("Unable to open zip file"); + + return false; + } + + test(writeFile(zip, "test.txt"), "Failed to write file into test zip"); + test(writeFile(zip, "console.log"), "Failed to write file into test zip"); + test(writeFile(zip, "folder/OpenAL32.dll"), "Failed to write file into test zip"); + + zip->closeArchive(); + delete zip; + + return true; + } + + //----------------------------------------------------------------------------- + + bool testReadFile(ZipArchive *zip, const char *filename) + { + // [tom, 2/5/2007] Try using openFile() first for fairest real-world test + Stream *stream; + if((stream = zip->openFile(filename, ZipArchive::Read)) == NULL) + return false; + + const CentralDir *cd = zip->findFileInfo(filename); + if(cd == NULL) + { + // [tom, 2/5/2007] This shouldn't happen + zip->closeFile(stream); + + fail("testReadFile - File opened successfully, but couldn't find central directory. This is bad."); + + return false; + } + + U32 crc = CRC::INITIAL_CRC_VALUE; + + bool ret = true; + U8 buffer[1024]; + U32 numBytes = stream->getStreamSize() - stream->getPosition(); + while((stream->getStatus() != Stream::EOS) && numBytes > 0) + { + U32 numRead = numBytes > sizeof(buffer) ? sizeof(buffer) : numBytes; + if(! stream->read(numRead, buffer)) + { + ret = false; + fail("testReadFile - Couldn't read from stream"); + break; + } + + crc = CRC::calculateCRC(buffer, numRead, crc); + + numBytes -= numRead; + } + + if(ret) + { + // CRC Check + crc ^= CRC::CRC_POSTCOND_VALUE; + if(crc != cd->mCRC32) + { + fail("testReadFile - File failed CRC check"); + ret = false; + } + } + + zip->closeFile(stream); + return ret; + } + + //----------------------------------------------------------------------------- + + bool readTest(const char *zipfile) + { + ZipArchive *zip = new ZipArchive; + + if(!zip->openArchive(zipfile, ZipArchive::Read)) + { + delete zip; + fail("Unable to open zip file"); + + return false; + } + + // Try reading files that exist in various formats + testReadFile(zip, "test.txt"); + testReadFile(zip, "console.log"); + testReadFile(zip, "folder/OpenAL32.dll"); + + // Try reading a file that doesn't exist + if(testReadFile(zip, "hopefullyDoesntExist.bla.bla.foo")) + fail("Opening a file the didn't exist succeeded"); + + zip->closeArchive(); + delete zip; + + return true; + } + + bool readWriteTest(const char *zipfile) + { + ZipArchive *zip = new ZipArchive; + bool ret = true; + + if(!zip->openArchive(zipfile, ZipArchive::ReadWrite)) + { + delete zip; + fail("Unable to open zip file"); + + return false; + } + + // Test writing a file and then reading it back before the zip is rebuilt + test(writeFile(zip, "test.txt"), "Failed to write file to test zip"); + test(testReadFile(zip, "test.txt"), "Failed to read file back from test zip"); + + // Read from file that is already open for write (should fail) + Stream *wrStream = zip->openFile("writeTest.txt", ZipArchive::Write); + if(wrStream != NULL) + { + wrStream->writeLine((U8 *)"Test text to make a valid file"); + + // This next open should fail + Stream * rdStream = zip->openFile("writeTest.txt", ZipArchive::Read); + if(rdStream != NULL) + { + fail("Succeeded in opening a file for read that's already open for write"); + ret = false; + + zip->closeFile(rdStream); + } + + zip->closeFile(wrStream); + } + else + { + fail("Could not open file for write"); + ret = false; + } + + zip->closeArchive(); + delete zip; + + return ret; + } +}; diff --git a/core/util/zip/unitTests/zipTestWrite.cpp b/core/util/zip/unitTests/zipTestWrite.cpp new file mode 100644 index 0000000..e39b4e1 --- /dev/null +++ b/core/util/zip/unitTests/zipTestWrite.cpp @@ -0,0 +1,227 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "core/util/zip/zipArchive.h" +#include "core/util/zip/unitTests/zipTest.h" + +#include "unit/test.h" +#include "unit/memoryTester.h" + + +using namespace UnitTesting; +using namespace Zip; + +CreateUnitTest(ZipTestWrite, "Zip/Write") +{ +private: + StringTableEntry mWriteFilename; + StringTableEntry mBaselineFilename; + StringTableEntry mWorkingFilename; + +public: + /// Number of files to write for testing large numbers of files in a zip + static const U32 mTons = 5000; + + ZipTestWrite() + { + mWriteFilename = makeTestPath(ZIPTEST_WRITE_FILENAME); + mBaselineFilename = makeTestPath(ZIPTEST_BASELINE_FILENAME); + mWorkingFilename = makeTestPath(ZIPTEST_WORKING_FILENAME); + } + + void run() + { + MemoryTester m; + m.mark(); + + // Clean up from a previous run + cleanup(); + + // Test writing to zip files without the zip file existing + testWriting(mWriteFilename, ZipArchive::Read); + testWriting(mWriteFilename, ZipArchive::ReadWrite); + + // Cleanup to try write without existing file + cleanup(); + testWriting(mWriteFilename, ZipArchive::Write); + + // Now use previous file to test everything again with an existing file + testWriting(mWriteFilename, ZipArchive::ReadWrite); + testWriting(mWriteFilename, ZipArchive::Write); + testWriting(mWriteFilename, ZipArchive::Read); + + testWritingTons(makeTestPath("WriteTons.zip")); + + test(m.check(), "Zip write test leaked memory!"); + } + +private: + + //----------------------------------------------------------------------------- + + void cleanup() + { + if(Platform::isFile(mWriteFilename)) + dFileDelete(mWriteFilename); + if(Platform::isFile(mWorkingFilename)) + dFileDelete(mWorkingFilename); + } + + //----------------------------------------------------------------------------- + + bool writeFile(ZipArchive *zip, const char *filename, char *fileBuf, U32 bufSize, bool mustNotExist = false, const char *contents = NULL) + { + if(mustNotExist && fileBuf && bufSize > 0) + { + // Find a unique filename + U32 count = 1; + dStrcpy(fileBuf, filename); + + while(zip->findFileInfo(fileBuf)) + { + dSprintf(fileBuf, bufSize, "%s.%04x", filename, count++); + + if(count == 0) + { + fail("writeFile - got stuck in an infinite loop looking for a unique filename"); + return false; + } + } + } + else if(fileBuf && bufSize > 0) + dStrcpy(fileBuf, filename); + + // Try and write to the file + Stream * stream = zip->openFile(fileBuf ? fileBuf : filename, ZipArchive::Write); + if(stream != NULL) + { + stream->writeLine(contents ? (U8 *)contents : (U8 *)"This is a test of writing to a file."); + zip->closeFile(stream); + + return true; + } + + return false; + } + + //----------------------------------------------------------------------------- + + bool testWriting(const char *zipfile, ZipArchive::AccessMode mode) + { + ZipArchive *zip = new ZipArchive; + + if(! zip->openArchive(zipfile, mode)) + { + delete zip; + + // This is only an error if we're not trying to open as read + + if(mode != ZipArchive::Read) + fail("Unable to open zip file"); + + return mode == ZipArchive::Read; + } + + char fileBuf[1024]; + + // Write to file that doesn't already exist + if(!writeFile(zip, "testWriteNew.txt", fileBuf, sizeof(fileBuf), true)) + { + fail("Couldn't write to a file that didn't already exist"); + goto bail; + } + + // Write to file that we've already written to + if(!writeFile(zip, fileBuf, NULL, 0, false, "This is a test of overwriting the file.")) + { + if(mode != ZipArchive::Read) + fail("Couldn't write to a file that we've already written to"); + goto bail; + } + + // Write to file that already exists, but we haven't written to + // To do this, we need to close and re-open the zipfile + + zip->closeArchive(); + delete zip; + + zip = new ZipArchive; + if(! zip->openArchive(zipfile, mode)) + { + delete zip; + fail("Unable to re-open zip file. Strange!"); + return false; + } + + // Use the file we already overwrote since we are sure of it's filename + if(!writeFile(zip, fileBuf, NULL, 0, false, "This is a test of overwriting the file again.")) + { + if(mode != ZipArchive::Read) + fail("Couldn't write to a file that already existed"); + goto bail; + } + + { + // Move this into its own scope to deal with goto 'crosses initialization' errors + // Attempt to open a file for write that's already open for write (should fail) + Stream * stream1 = zip->openFile("writeLockTest.txt", ZipArchive::Write); + if(stream1 != NULL) + { + stream1->writeLine((U8 *)"Test text to make a valid file"); + + // This next open should fail + Stream * stream2 = zip->openFile("writeLockTest.txt", ZipArchive::Write); + if(stream2 != NULL) + { + if(mode != ZipArchive::Read) + fail("Opening a file for write multiple times should have failed"); + zip->closeFile(stream2); + } + zip->closeFile(stream1); + } + } + +bail: + zip->closeArchive(); + delete zip; + + return true; + } + + //----------------------------------------------------------------------------- + + bool testWritingTons(const char *zipfile) + { + ZipArchive zip; + + if(! zip.openArchive(zipfile, ZipArchive::Write)) + { + fail("Unable to open zip file"); + return false; + } + + bool ret = true; + + for(U32 i = 0;i < mTons;++i) + { + char fname[256]; + dSprintf(fname, sizeof(fname), "file%04x.txt", i); + + if(! writeFile(&zip, fname, NULL, 0)) + { + fail("Failed to write file"); + fail(fname); + + ret = false; + + break; + } + } + + zip.closeArchive(); + return ret; + } +}; diff --git a/core/util/zip/zipArchive.cpp b/core/util/zip/zipArchive.cpp new file mode 100644 index 0000000..512cf62 --- /dev/null +++ b/core/util/zip/zipArchive.cpp @@ -0,0 +1,907 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/stream.h" +#include "core/stream/fileStream.h" +#include "core/filterStream.h" +#include "core/util/zip/zipCryptStream.h" +#include "core/crc.h" +//#include "core/resManager.h" + +#include "console/console.h" + +#include "core/util/zip/zipArchive.h" +#include "core/util/zip/compressor.h" +#include "core/util/zip/zipTempStream.h" +#include "core/util/zip/zipStatFilter.h" + +#ifdef TORQUE_ZIP_AES +#include "core/zipAESCryptStream.h" +#include "core/zip/crypto/aes.h" +#endif + +#include "core/util/safeDelete.h" + +#include "app/version.h" + +namespace Zip +{ + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +ZipArchive::ZipArchive() : + mStream(NULL), + mDiskStream(NULL), + mMode(Read), + mRoot(NULL), + mFilename(NULL) +{ +} + +ZipArchive::~ZipArchive() +{ + closeArchive(); +} + +//----------------------------------------------------------------------------- +// Protected Methods +//----------------------------------------------------------------------------- + +bool ZipArchive::readCentralDirectory() +{ + mEntries.clear(); + SAFE_DELETE(mRoot); + mRoot = new ZipEntry; + mRoot->mName = ""; + mRoot->mIsDirectory = true; + mRoot->mCD.setFilename(""); + + if(! mEOCD.findInStream(mStream)) + return false; + + if(! mEOCD.read(mStream)) + return false; + + if(mEOCD.mDiskNum != mEOCD.mStartCDDiskNum || + mEOCD.mNumEntriesInThisCD != mEOCD.mTotalEntriesInCD) + { + if(isVerbose()) + Con::errorf("ZipArchive::readCentralDirectory - %s: Zips that span multiple disks are not supported.", mFilename ? mFilename : ""); + return false; + } + + if(! mStream->setPosition(mEOCD.mCDOffset)) + return false; + + for(S32 i = 0;i < mEOCD.mNumEntriesInThisCD;++i) + { + ZipEntry *ze = new ZipEntry; + if(! ze->mCD.read(mStream)) + { + delete ze; + + if(isVerbose()) + Con::errorf("ZipArchive::readCentralDirectory - %s: Error reading central directory.", mFilename ? mFilename : ""); + return false; + } + + insertEntry(ze); + } + + return true; +} + +void ZipArchive::dumpCentralDirectory(ZipEntry* entry, String* indent) +{ + // if entry is null, use root + if (entry == NULL) + entry = mRoot; + + if (!entry) + return; + + String emptyIndent; + if (indent == NULL) + indent = &emptyIndent; + + Con::printf("%s%s%s", indent->c_str(), entry->mIsDirectory ? "/" : "", entry->mName.c_str()); + for (Map::Iterator iter = entry->mChildren.begin(); + iter != entry->mChildren.end(); + ++iter) + { + String newIdent = *indent + " "; + dumpCentralDirectory((*iter).value, &(newIdent)); + } +} + +//----------------------------------------------------------------------------- + +void ZipArchive::insertEntry(ZipEntry *ze) +{ + char path[1024]; + dStrncpy(path, ze->mCD.mFilename.c_str(), sizeof(path)); + path[sizeof(path) - 1] = 0; + + for(S32 i = 0;i < dStrlen(path);++i) + { + if(path[i] == '\\') + path[i] = '/'; + } + + ZipEntry *root = mRoot; + + char *ptr = path, *slash = NULL; + do + { + slash = dStrchr(ptr, '/'); + if(slash) + { + // Add the directory + *slash = 0; + + // try to get root, create if not found + ZipEntry *newEntry = NULL; + if (!root->mChildren.tryGetValue(ptr, newEntry)) + { + newEntry = new ZipEntry; + newEntry->mParent = root; + newEntry->mName = String(ptr); + newEntry->mIsDirectory = true; + newEntry->mCD.setFilename(path); + + root->mChildren[ptr] = newEntry; + } + + root = newEntry; + + *slash = '/'; + ptr = slash + 1; + } + else + { + // Add the file. + if(*ptr) + { + ze->mIsDirectory = false; + ze->mName = ptr; + ze->mParent = root; + root->mChildren[ptr] = ze; + mEntries.push_back(ze); + } + else + { + // [tom, 2/6/2007] If ptr is empty, this was a directory entry. Since + // we created a new entry for it above, we need to delete the old + // pointer otherwise it will leak as it won't have got inserted. + + delete ze; + } + } + } while(slash); +} + +void ZipArchive::removeEntry(ZipEntry *ze) +{ + if(ze == mRoot) + { + // [tom, 2/1/2007] We don't want to remove the root as it should always + // be removed through closeArchive() + AssertFatal(0, "ZipArchive::removeEntry - Attempting to remove the root"); + return; + } + + // Can't iterate the hash table, so we can't do this safely + AssertFatal(!ze->mIsDirectory, "ZipArchive::removeEntry - Cannot remove a directory"); + + // See if we have a temporary file for this entry + Vector::iterator i; + for(i = mTempFiles.begin();i != mTempFiles.end();++i) + { + if((*i)->getCentralDir() == &ze->mCD) + { + SAFE_DELETE(*i); + mTempFiles.erase(i); + + break; + } + } + + // Remove from the tree + Vector::iterator j; + for(j = mEntries.begin();j != mEntries.end();++j) + { + if(*j == ze) + { + mEntries.erase(j); + break; + } + } + + // [tom, 2/2/2007] This must be last, as ze is no longer valid once it's + // removed from the parent. + ZipEntry *z = ze->mParent->mChildren[ze->mName]; + ze->mParent->mChildren.erase(ze->mName); + delete z; +} + +//----------------------------------------------------------------------------- + +CentralDir *ZipArchive::findFileInfo(const char *filename) +{ + ZipEntry *ze = findZipEntry(filename); + return ze ? &ze->mCD : NULL; +} + +ZipArchive::ZipEntry *ZipArchive::findZipEntry(const char *filename) +{ + char path[1024]; + dStrncpy(path, filename, sizeof(path)); + path[sizeof(path) - 1] = 0; + + for(S32 i = 0;i < dStrlen(path);++i) + { + if(path[i] == '\\') + path[i] = '/'; + } + + ZipEntry *root = mRoot; + + char *ptr = path, *slash = NULL; + do + { + slash = dStrchr(ptr, '/'); + if(slash) + { + // Find the directory + *slash = 0; + + // check child dict for new root + ZipEntry *newRoot = NULL; + if (!root->mChildren.tryGetValue(ptr, newRoot)) + return NULL; + + root = newRoot; + + ptr = slash + 1; + } + else + { + // Find the file + ZipEntry* entry = NULL; + if (root->mChildren.tryGetValue(ptr, entry)) + return entry; + } + } while(slash); + + return NULL; +} + +//----------------------------------------------------------------------------- + +Stream *ZipArchive::createNewFile(const char *filename, Compressor *method) +{ + ZipEntry *ze = new ZipEntry; + ze->mIsDirectory = false; + ze->mCD.setFilename(filename); + insertEntry(ze); + + ZipTempStream *stream = new ZipTempStream(&ze->mCD); + if(stream->open()) + { + Stream *retStream = method->createWriteStream(&ze->mCD, stream); + if(retStream == NULL) + { + delete stream; + return NULL; + } + + ZipStatFilter *filter = new ZipStatFilter(&ze->mCD); + if(! filter->attachStream(retStream)) + { + delete filter; + delete retStream; + delete stream; + return NULL; + } + + ze->mCD.mCompressMethod = method->getMethod(); + ze->mCD.mInternalFlags |= CDFileOpen; + + return filter; + } + + return NULL; +} + +void ZipArchive::updateFile(ZipTempStream *stream) +{ + CentralDir *cd = stream->getCentralDir(); + + // [tom, 1/23/2007] Uncompressed size and CRC32 are updated by ZipStatFilter + cd->mCompressedSize = stream->getStreamSize(); + cd->mInternalFlags |= CDFileDirty; + cd->mInternalFlags &= ~CDFileOpen; + + // Upper byte should be zero, lower is version as major * 10 + minor + cd->mVersionMadeBy = (getVersionNumber() / 100) & 0xff; + cd->mExtractVer = 20; + + U32 dosTime = currentTimeToDOSTime(); + cd->mModTime = dosTime & 0x0000ffff; + cd->mModDate = (dosTime & 0xffff0000) >> 16; + + mTempFiles.push_back(stream); +} + +//----------------------------------------------------------------------------- + +U32 ZipArchive::localTimeToDOSTime(const Torque::Time::DateTime &dt) +{ + // DOS time format + // http://msdn.microsoft.com/en-us/library/ms724274(VS.85).aspx + return TimeToDOSTime(Torque::Time(dt)); +} + +U32 ZipArchive::TimeToDOSTime(const Torque::Time& t) +{ + S32 year,month,day,hour,minute,second,microsecond; + t.get(&year, &month, &day, &hour, &minute, &second, µsecond); + + if(year > 1980) // De Do Do Do, De Da Da Da + year -= 1980; + + return (((day) + (32 * (month)) + (512 * year)) << 16) | ((second/2) + (32* minute) + (2048 * (U32)hour)); +} + +Torque::Time ZipArchive::DOSTimeToTime(U16 time, U16 date) +{ + Torque::Time::DateTime dt; + dt.microsecond = 0; + dt.hour = (time & 0xF800) >> 11; + dt.minute = (time & 0x07E0) >> 5; + dt.second = (time & 0x001F)*2; + + dt.year = ((date & 0xFE00) >> 9) + 1980; + dt.month = (date & 0x01E0) >> 5; + dt.day = (date & 0x001F); + + return Torque::Time(dt); +} + +Torque::Time ZipArchive::DOSTimeToTime(U32 dosTime) +{ + U16 time = dosTime & 0x0000ffff; + U16 date = (dosTime & 0xffff0000) >> 16; + + return ZipArchive::DOSTimeToTime(time, date); +} + +U32 ZipArchive::currentTimeToDOSTime() +{ + Torque::Time::DateTime dt; + Torque::Time::getCurrentDateTime(dt); + + return localTimeToDOSTime(dt); +} + +//----------------------------------------------------------------------------- + +// [tom, 1/24/2007] The general idea here is we want to create a new file, +// copy any data from the old zip file and add the new stuff. Once the new +// zip is created, delete the old one and rename the new one. + +bool ZipArchive::rebuildZip() +{ + String newZipName; + FileStream tempFile; + Stream *zipFile = mStream; + + // FIXME [tom, 1/24/2007] Temporary for expediting testing + if(mFilename == NULL) + return false; + + if(mMode == ReadWrite) + { + newZipName = String(mFilename) + ".new"; + + if(! tempFile.open(newZipName, mMode == Write ? Torque::FS::File::Write : Torque::FS::File::ReadWrite)) + return false; + + zipFile = &tempFile; + } + + // Copy any unmodified files + for(S32 i = 0;i < mEntries.size();++i) + { + ZipEntry *entry = mEntries[i]; + + // Directories are internal only for lookup purposes + if(entry->mIsDirectory || (entry->mCD.mInternalFlags & (CDFileDirty | CDFileDeleted))) + continue; + + copyFileToNewZip(&entry->mCD, zipFile); + } + + // Copy any dirty files + for(S32 i = 0;i < mTempFiles.size();++i) + { + ZipTempStream *zts = mTempFiles[i]; + + writeDirtyFileToNewZip(zts, zipFile); + zts->close(); + delete zts; + mTempFiles[i] = NULL; + } + mTempFiles.clear(); + + // Write central directory + mEOCD.mCDOffset = zipFile->getPosition(); + mEOCD.mNumEntriesInThisCD = 0; + + for(S32 i = 0;i < mEntries.size();++i) + { + ZipEntry *entry = mEntries[i]; + + // [tom, 1/24/2007] Directories are internal only for lookup purposes + if(entry->mIsDirectory || (entry->mCD.mInternalFlags & CDFileDeleted) != 0) + continue; + + ++mEOCD.mNumEntriesInThisCD; + if(! entry->mCD.write(zipFile)) + break; + } + + mEOCD.mCDSize = zipFile->getPosition() - mEOCD.mCDOffset; + mEOCD.mTotalEntriesInCD = mEOCD.mNumEntriesInThisCD; + + mEOCD.mDiskNum = 0; + mEOCD.mStartCDDiskNum = 0; + + mEOCD.write(zipFile); + + if(mMode == ReadWrite) + { + // Close file, replace old zip with it + tempFile.close(); + + // [tom, 2/1/2007] The disk stream must be closed else we can't rename + // the file. Since rebuildZip() is only called from closeArchive() this + // should be safe. + if(mDiskStream) + { + mDiskStream->close(); + + delete mDiskStream; + mDiskStream = NULL; + } + + String oldRename; + oldRename = String(mFilename) + ".old"; + + if(! Torque::FS::Rename(mFilename, oldRename)) + return false; + + if(! Torque::FS::Rename(newZipName, mFilename)) + return false; + + Torque::FS::Remove(oldRename); + } + return true; +} + +bool ZipArchive::writeDirtyFileToNewZip(ZipTempStream *fileStream, Stream *zipStream) +{ + CentralDir *cdir = fileStream->getCentralDir(); + FileHeader fh(*cdir); + fh.setFilename(cdir->mFilename); + + cdir->mLocalHeadOffset = zipStream->getPosition(); + + // Write header and file + if(! fh.write(zipStream)) + return false; + + if(! fileStream->rewind()) + return false; + + return zipStream->copyFrom(fileStream); +} + +bool ZipArchive::copyFileToNewZip(CentralDir *cdir, Stream *newZipStream) +{ + // [tom, 1/24/2007] Using the stored compressor allows us to copy the raw + // data regardless of compression method without having to re-compress it. + Compressor *comp = Compressor::findCompressor(Stored); + if(comp == NULL) + return false; + + if(! mStream->setPosition(cdir->mLocalHeadOffset)) + return false; + + // Copy file header + // FIXME [tom, 1/24/2007] This will currently not copy the extra fields + FileHeader fh; + if(! fh.read(mStream)) + return false; + + cdir->mLocalHeadOffset = newZipStream->getPosition(); + + if(! fh.write(newZipStream)) + return false; + + // Copy file data + Stream *readS = comp->createReadStream(cdir, mStream); + if(readS == NULL) + return false; + + bool ret = newZipStream->copyFrom(readS); + + // [tom, 1/24/2007] closeFile() just frees the relevant filters and + // thus it is safe to call from here. + closeFile(readS); + + return ret; +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +void ZipArchive::setFilename(const char *filename) +{ + SAFE_FREE(mFilename); + if(filename) + mFilename = dStrdup(filename); +} + +//----------------------------------------------------------------------------- + +bool ZipArchive::openArchive(const char *filename, AccessMode mode /* = Read */) +{ + if(mode != Read && mode != Write && mode != ReadWrite) + return false; + + closeArchive(); + + mDiskStream = new FileStream; + if(mDiskStream->open(filename, (Torque::FS::File::AccessMode)mode)) + { + setFilename(filename); + + if(openArchive(mDiskStream, mode)) + return true; + } + + // Cleanup just in case openArchive() failed + closeArchive(); + + return false; +} + +bool ZipArchive::openArchive(Stream *stream, AccessMode mode /* = Read */) +{ + if(mode != Read && mode != Write && mode != ReadWrite) + return false; + + mStream = stream; + mMode = mode; + + if(mode == Read || mode == ReadWrite) + { + bool ret = readCentralDirectory(); + if(mode == Read) + return ret; + + return true; + } + else + { + mEntries.clear(); + SAFE_DELETE(mRoot); + mRoot = new ZipEntry; + mRoot->mName = ""; + mRoot->mIsDirectory = true; + mRoot->mCD.setFilename(""); + } + + return true; +} + +void ZipArchive::closeArchive() +{ + if(mMode == Write || mMode == ReadWrite) + rebuildZip(); + + // Free any remaining temporary files + for(S32 i = 0;i < mTempFiles.size();++i) + { + SAFE_DELETE(mTempFiles[i]); + } + mTempFiles.clear(); + + // Close the zip file stream and clean up + if(mDiskStream) + { + mDiskStream->close(); + + delete mDiskStream; + mDiskStream = NULL; + } + + mStream = NULL; + + SAFE_FREE(mFilename); + SAFE_DELETE(mRoot); + mEntries.clear(); +} + +//----------------------------------------------------------------------------- +Stream * ZipArchive::openFile(const char *filename, AccessMode mode /* = Read */) +{ + ZipEntry* ze = findZipEntry(filename); + return openFile(filename, ze, mode); +} + +Stream * ZipArchive::openFile(const char *filename, ZipEntry* ze, AccessMode mode /* = Read */) +{ + if(mode == Read) + { + if(ze == NULL) + return NULL; + + return openFileForRead(&ze->mCD); + } + + if(mode == Write) + { + if(ze) + { + if(ze->mCD.mInternalFlags & CDFileOpen) + { + if(isVerbose()) + Con::errorf("ZipArchive::openFile - File %s is already open", filename); + return NULL; + } + + // Remove the old entry so we can create a new one + removeEntry(ze); + ze = NULL; + } + + return createNewFile(filename, Compressor::findCompressor(Deflated)); + } + + if(isVerbose()) + Con::errorf("ZipArchive::openFile - Files within zips can only be opened as read or write, but not both at the same time."); + + return NULL; +} + +void ZipArchive::closeFile(Stream *stream) +{ + FilterStream *currentStream, *nextStream; + + nextStream = dynamic_cast(stream); + while (nextStream) + { + currentStream = nextStream; + stream = currentStream->getStream(); + + currentStream->detachStream(); + + nextStream = dynamic_cast(stream); + + delete currentStream; + } + + ZipTempStream *tempStream = dynamic_cast(stream); + if(tempStream && (tempStream->getCentralDir()->mInternalFlags & CDFileOpen)) + { + // [tom, 1/23/2007] This is a temporary file we are writing to + // so we need to update the relevant information in the header. + updateFile(tempStream); + } +} + +//----------------------------------------------------------------------------- + +Stream *ZipArchive::openFileForRead(const CentralDir *fileCD) +{ + if(mMode != Read && mMode != ReadWrite) + return NULL; + + if((fileCD->mInternalFlags & (CDFileDeleted | CDFileOpen)) != 0) + return NULL; + + Stream *stream = mStream; + + if(fileCD->mInternalFlags & CDFileDirty) + { + // File is dirty, we need to read from the temporary file + for(S32 i = 0;i < mTempFiles.size();++i) + { + if(mTempFiles[i]->getCentralDir() == fileCD) + { + // Found the temporary file + if(! mTempFiles[i]->rewind()) + { + if(isVerbose()) + Con::errorf("ZipArchive::openFile - %s: %s is dirty, but could not rewind temporary file?", mFilename ? mFilename : "", fileCD->mFilename.c_str()); + return NULL; + } + + stream = mTempFiles[i]; + break; + } + } + + if(stream == mStream) + { + if(isVerbose()) + Con::errorf("ZipArchive::openFile - %s: %s is dirty, but no temporary file found?", mFilename ? mFilename : "", fileCD->mFilename.c_str()); + return NULL; + } + } + else + { + // Read from the zip file directly + if(! mStream->setPosition(fileCD->mLocalHeadOffset)) + { + if(isVerbose()) + Con::errorf("ZipArchive::openFile - %s: Could not locate local header for file %s", mFilename ? mFilename : "", fileCD->mFilename.c_str()); + return NULL; + } + + FileHeader fh; + if(! fh.read(mStream)) + { + if(isVerbose()) + Con::errorf("ZipArchive::openFile - %s: Could not read local header for file %s", mFilename ? mFilename : "", fileCD->mFilename.c_str()); + return NULL; + } + } + + Stream *attachTo = stream; + U16 compMethod = fileCD->mCompressMethod; + + if(fileCD->mFlags & Encrypted) + { + if(fileCD->mCompressMethod == AESEncrypted) + { + // [tom, 1/19/2007] Whilst AES support does exist, I'm not including it in TGB + // to avoid having to deal with crypto export legal issues. + Con::errorf("ZipArchive::openFile - %s: File %s is AES encrypted, but AES is not supported in this version.", mFilename ? mFilename : "", fileCD->mFilename.c_str()); + } + else + { + ZipCryptRStream *cryptStream = new ZipCryptRStream; + cryptStream->setPassword(DEFAULT_ZIP_PASSWORD); + cryptStream->setFileEndPos(stream->getPosition() + fileCD->mCompressedSize); + if(! cryptStream->attachStream(stream)) + { + delete cryptStream; + return NULL; + } + + attachTo = cryptStream; + } + } + + Compressor *comp = Compressor::findCompressor(compMethod); + if(comp == NULL) + { + if(isVerbose()) + Con::errorf("ZipArchive::openFile - %s: Unsupported compression method (%d) for file %s", mFilename ? mFilename : "", fileCD->mCompressMethod, fileCD->mFilename.c_str()); + return NULL; + } + + return comp->createReadStream(fileCD, attachTo); +} + +//----------------------------------------------------------------------------- + +bool ZipArchive::addFile(const char *filename, const char *pathInZip, bool replace /* = true */) +{ + FileStream f; + if (!f.open(filename, Torque::FS::File::Read)) + return false; + + const CentralDir *cd = findFileInfo(pathInZip); + if(! replace && cd && (cd->mInternalFlags & CDFileDeleted) == 0) + return false; + + Stream *dest = openFile(pathInZip, Write); + if(dest == NULL) + { + f.close(); + return false; + } + + bool ret = dest->copyFrom(&f); + + closeFile(dest); + f.close(); + + return ret; +} + +bool ZipArchive::extractFile(const char *pathInZip, const char *filename, bool *crcFail /* = NULL */) +{ + if(crcFail) + *crcFail = false; + + const CentralDir *realCD = findFileInfo(pathInZip); + if(realCD == NULL) + return false; + + FileStream dest; + if(! dest.open(filename, Torque::FS::File::Write)) + return false; + + Stream *source = openFile(pathInZip, Read); + if(source == NULL) + { + dest.close(); + return false; + } + + // [tom, 2/7/2007] CRC checking the lazy man's way + // ZipStatFilter only fails if it doesn't have a central directory, so this is safe + CentralDir fakeCD; + ZipStatFilter zsf(&fakeCD); + zsf.attachStream(source); + + bool ret = dest.copyFrom(&zsf); + + zsf.detachStream(); + + if(ret && fakeCD.mCRC32 != realCD->mCRC32) + { + if(crcFail) + *crcFail = true; + + if(isVerbose()) + Con::errorf("ZipArchive::extractFile - CRC failure extracting file %s", pathInZip); + ret = false; + } + + closeFile(source); + dest.close(); + + return ret; +} + +bool ZipArchive::deleteFile(const char *filename) +{ + if(mMode != Write && mMode != ReadWrite) + return false; + + CentralDir *cd = findFileInfo(filename); + if(cd == NULL) + return false; + + cd->mInternalFlags |= CDFileDeleted; + + // CodeReview [tom, 2/9/2007] If this is a file we have a temporary file for, + // we should probably delete it here rather then waiting til the archive is closed. + + return true; +} + +//----------------------------------------------------------------------------- + +bool ZipArchive::isVerbose() +{ + return Con::getBoolVariable("$Pref::Zip::Verbose"); +} + +void ZipArchive::setVerbose(bool verbose) +{ + Con::setBoolVariable("$Pref::Zip::Verbose", verbose); +} + +} // end namespace Zip diff --git a/core/util/zip/zipArchive.h b/core/util/zip/zipArchive.h new file mode 100644 index 0000000..610d084 --- /dev/null +++ b/core/util/zip/zipArchive.h @@ -0,0 +1,573 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/zip/fileHeader.h" +#include "core/util/zip/centralDir.h" +#include "core/util/zip/compressor.h" + +#include "core/stream/fileStream.h" + +#include "core/util/tVector.h" +#include "core/util/tDictionary.h" +#include "core/util/timeClass.h" + +#ifndef _ZIPARCHIVE_H_ +#define _ZIPARCHIVE_H_ + +/// @addtogroup zip_group Zip Code +// @{ + +/// Password to use when opening encrypted zip files. Change this to whatever the password is for your zips. +#define DEFAULT_ZIP_PASSWORD "changeme" + +class ZipTestWrite; +class ZipTestRead; +class ZipTestMisc; + +namespace Zip +{ + +/// @addtogroup zip_group Zip Code +// @{ + +// Forward Refs +class ZipTempStream; + +// [tom, 10/18/2006] This will be split up into a separate interface for allowing +// the resource manager to handle any kind of archive relatively easily. + +// [tom, 2/9/2007] At least, it was designed so that it could be. It may not +// actually happen for a very long time, if ever ;-) + +//----------------------------------------------------------------------------- +/// @brief Class for accessing Zip files +/// +/// ZipArchive provides two interfaces for reading or writing zip files. +/// The first is a stream based interface that should be familiar to anyone +/// who's ever written code, and the other is an archiver interface that +/// should be familiar to anyone who's ever used a standard Zip application. +/// +/// The two interfaces are not mutually exclusive so you can use both if +/// you wish. For example, you may want to use the stream interface to add +/// a configuration file to a zip without having to create a temporary file +/// and then add a number of existing files using the addFile() method. +/// +/// Both interfaces have their advantages and disadvantages, which will +/// be discussed below. +/// +///

Accessing a Zip file

+/// +/// Before you can access any files in the zip, you first need to open the +/// archive. This is the same regardless of which interface you use, and there +/// are two ways to accomplish it. +/// +/// Opening from a file on the file system +/// +/// The simplest method of opening a zip file is to use openArchive(const char *, AccessMode) +/// to open a file that is on the disk. +/// +/// When opening a zip file on the file system, the filename is automatically set. +/// +/// Opening a file from a stream +/// +/// A more advanced way to open the zip file is from an arbitrary stream. The +/// only requirements are that the stream supports seeking and was opened with +/// the correct access mode. Use the openArchive(Stream *, AccessMode) method to +/// do this. +/// +/// Opening zip files from arbitrary streams is a very powerful feature and +/// opens many interesting doors. For example, combined with some small changes +/// to the resource manager and startup code, it was possible to implement a +/// VFS that allows the entire game to run from a single executable with no +/// external files. +/// +/// Note that the filename is not automatically set when you open the zip file +/// from a stream. The filename is used in error reporting and by the resource +/// manager, so you may wish to set it to something meaningful. +/// +/// Regardless of which method you use to open the file, the #AccessMode controls +/// what you can do with it. If you open the archive as #ReadWrite, you can both +/// write to and read from files in the zip. However, it is not possible to open +/// files in the zip as #ReadWrite. +/// +/// Closing the zip file +/// +/// When you are done with the zip file, call closeArchive() to free any resources +/// and rebuild the zip file if it was open for #Write. +/// +/// Example +/// +/// @code +/// Zip::ZipArchive za; +/// if(za.openArchive("filename.zip", ZipArchive::Read)) +/// { +/// // ... do stuff ... +/// za.closeArchive(); +/// } +/// @endcode +/// +///

Archiver Interface

+/// +/// The archiver style interface allows you to add, extract and delete files in +/// the zip in a way similar to that of an standard archiver application. +/// +/// While the archiver interface is simple to use, it is blocking and thus +/// difficult to use asynchronously. If you require zip file support and +/// responsive UI then you should consider using the stream interface instead. +/// +/// See the following method documentation for more information: +/// +///
    +///
  • addFile() +///
  • extractFile() +///
  • deleteFile() +///
+/// +/// Example +/// +/// @code +/// Zip::ZipArchive za; +/// if(za.openArchive("filename.zip", ZipArchive::ReadWrite)) +/// { +/// // Extract a file +/// za.extractFile("test.txt", "test.txt"); +/// // Add a file +/// za.addFile("test.txt", "test.txt"); +/// // Delete a file +/// za.deleteFile("test.txt"); +/// +/// za.closeArchive(); +/// } +/// @endcode +/// +///

Stream Interface

+/// +/// The stream based interface allows you to access files within the zip +/// in a similar way to accessing the file system through the ResourceManager. +/// +/// There are a few small caveats to the stream interface: +///
    +///
  • When writing files, the whole file must be written sequentially. You +/// cannot seek in the stream. +///
  • It may or may not be possible to seek in streams opened for read. +/// Files that were not compressed in the zip file support seeking with +/// no penalty. In all cases where the file is compressed, if seeking is +/// supported by the decompression and/or decryption filter then it +/// carries with it an extreme performance penalty and should be avoided. +/// All currently available decompression filters (Deflate and BZip2) and +/// decryption filters (Zip 2.0 and AES) support seeking, but have to reset +/// their state and re-decompress/decrypt the entire file up to the point +/// you are seeking to. An extreme example would be that if you had a +/// 20MB file and were currently at the end of the file, seeking back 1 byte +/// of the file would cause the entire file to be decompressed again. This +/// would be a blocking operation that would lock Torque up for an appreciable +/// chunk of time. +///
  • Files can only be open as #Read or #Write, but not #ReadWrite +///
  • Only one file can be open for read at a time, but multiple files can +/// be open for write at a time. - [tom, 2/9/2007] Check this +///
+/// +/// See the following method documentation for more information: +/// +///
    +///
  • openFile() +///
  • closeFile() +///
+/// +/// CRC Checking +/// +/// Unlike the archiver interface, there is no automatic CRC checking when +/// reading from files using the stream interface. If you will only be +/// reading files sequentially, see the documentation for ZipStatFilter +/// for a useful trick to get easy CRC checking. +/// +/// Example +/// +/// @code +/// Zip::ZipArchive za; +/// if(za.openArchive("filename.zip", ZipArchive::Write)) +/// { +/// // Write to the file +/// Stream *stream; +/// if(stream = za.openFile("test.txt", ZipArchive::Write)) +/// { +/// stream->writeLine((U8 *)"Hello, Zipped World!"); +/// za.closeFile(stream); +/// } +/// +/// za.closeArchive(); +/// } +/// @endcode +/// +///

Compressed Files

+/// +/// The zip code included with stock Torque supports "stored" (uncompressed) files +/// and deflate compressed files. The code is easily extensible to support any +/// compression format that the Zip file format supports. +/// +/// In addition to the deflate and stored formats, BZip2 is supported but not +/// included with stock Torque. BZip2 support will be released as a resource in +/// the future. +/// +///

Encrypted Files

+/// +/// Preliminary support for Encrypted/Passworded files is included in TGB Pro only. +/// Currently, only Zip 2.0 encryption is supported by the stock code. AES support +/// exists and may be released as a resource in the future. +/// +/// To set the password used for zips, you need to modify the #DEFAULT_ZIP_PASSWORD +/// define in core/zip/zipArchive.h. This password will be used for all zips that +/// require a password. The default password is changeme. This may be used by +/// TGB Binary users to test encrypted zips with their game. Shipping with the +/// default password is not recommended for obvious reasons. +/// +/// The intended use of encrypted zips is for preventing casual copying of your +/// game's assets. Zip 2.0 encryption has known weaknesses that allow an attacker +/// to decrypt the contents of the zip. AES encryption is significantly more secure, +/// but as the password must be stored in the executable it will not stop a +/// determined attacker. +/// +/// A script accessible mechanism for setting the password does not currently exist. +/// To use encrypted mod zips, if the password was in script then the password +/// would be clearly visible to anyone that cared to poke around in your scripts. +/// +/// Encrypted zip support will be improved in a future version. For now, a more +/// secure method of storing the password is left as an exercise for the reader. +/// +///

Accessing Zip files from script

+/// +/// ZipArchive is a C++ class and thus cannot be used from script. However, +/// a wrapper is provided to allow script access to zips. See the documentation +/// on ZipObject for more information. +/// +///

More Examples

+/// +/// More in depth example code than that featured here can be found in the +/// unit tests for the zip code (in the core/zip/unitTests directory) +/// and the script code for the packaging utility. +/// +//----------------------------------------------------------------------------- +class ZipArchive : public StrongRefBase +{ + friend class ::ZipTestWrite; + friend class ::ZipTestRead; + friend class ::ZipTestMisc; + +public: + /// Access modes for zip files and files within the zip + enum AccessMode + { + Read = Torque::FS::File::Read, //!< Open a zip or file in a zip for reading + Write = Torque::FS::File::Write, //!< Open a zip or file in a zip for writing + ReadWrite = Torque::FS::File::ReadWrite //!< Open a zip file for reading and writing. Note: Not valid for files in zips. + }; + + struct ZipEntry + { + ZipEntry *mParent; + + String mName; + + bool mIsDirectory; + CentralDir mCD; + + Map mChildren; + + ZipEntry() + { + mName = ""; + mIsDirectory = false; + mParent = NULL; + } + }; +protected: + + Stream *mStream; + FileStream *mDiskStream; + AccessMode mMode; + + EndOfCentralDir mEOCD; + + // mRoot forms a tree of entries for fast queries given a file path + // mEntries allows easy iteration of the entire file list + ZipEntry *mRoot; + Vector mEntries; + + const char *mFilename; + + Vector mTempFiles; + + bool readCentralDirectory(); + + void insertEntry(ZipEntry *ze); + void removeEntry(ZipEntry *ze); + + Stream *createNewFile(const char *filename, Compressor *method); + Stream *createNewFile(const char *filename, const char *method) + { + return createNewFile(filename, Compressor::findCompressor(method)); + } + Stream *createNewFile(const char *filename, S32 method) + { + return createNewFile(filename, Compressor::findCompressor(method)); + } + + void updateFile(ZipTempStream *stream); + bool rebuildZip(); + bool copyFileToNewZip(CentralDir *cdir, Stream *newZipStream); + bool writeDirtyFileToNewZip(ZipTempStream *fileStream, Stream *zipStream); + +public: + ZipEntry* getRoot() { return mRoot; } + ZipEntry* findZipEntry(const char *filename); + static U32 localTimeToDOSTime(const Torque::Time::DateTime &dt); + static Torque::Time DOSTimeToTime(U16 time, U16 date); + static Torque::Time DOSTimeToTime(U32 datetime); + static U32 TimeToDOSTime(const Torque::Time& t); + static U32 currentTimeToDOSTime(); + void dumpCentralDirectory(ZipEntry* entry = NULL, String* indent = NULL); + +public: + ZipArchive(); + virtual ~ZipArchive(); + + /// @name Miscellaneous Methods + // @{ + + //----------------------------------------------------------------------------- + /// @brief Set the filename of the zip file. + /// + /// The zip filename is used by the resource manager and for error reporting. + /// + /// Note: The filename is set automatically when you open the file. + /// + /// @param filename Filename of the zip file + //----------------------------------------------------------------------------- + void setFilename(const char *filename); + + /// Set the disk stream pointer. The ZipArchive is then responsible for + /// deleting the stream when appropriate and the caller should not do the same. + /// This function should only be called after openArchive(Stream*) has been + /// successfully executed. + void setDiskStream(FileStream* stream) { mDiskStream = stream; } + + //----------------------------------------------------------------------------- + /// @brief Get the filename of the zip file. + /// + /// @returns Filename of the zip file, or NULL if none set + //----------------------------------------------------------------------------- + const char *getFilename() { return mFilename; } + + //----------------------------------------------------------------------------- + /// @brief Determine if the Zip code is in verbose mode + /// + /// Verbose mode causes the Zip code to provide diagnostic error messages + /// when things go wrong. It can be enabled or disabled through script by + /// setting the $Pref::Zip::Verbose variable to true to enable it or false + /// to disable it. + /// + /// Verbose mode is mostly useful when tracking down issues with opening + /// a zip file without having to resort to using a debugger. + /// + /// @returns The value of $Pref::Zip::Verbose + /// @see ZipArchive::setVerbose() + //----------------------------------------------------------------------------- + bool isVerbose(); + + //----------------------------------------------------------------------------- + /// @brief Turn verbose mode on or off. + /// + /// This sets the $Pref::Zip::Verbose variable. + /// + /// See isVerbose() for a discussion on verbose mode. + /// + /// @param verbose True to enable verbose mode, false to disable + /// @see ZipArchive::isVerbose() + //----------------------------------------------------------------------------- + void setVerbose(bool verbose); + // @} + + /// @name Archive Access Methods + // @{ + + //----------------------------------------------------------------------------- + /// @brief Open a zip archive from a file + /// + /// The archive must be closed with closeArchive() when you are done with it. + /// + /// @param filename Filename of zip file to open + /// @param mode Access mode. May be Read, Write or ReadWrite + /// @return true for success, false for failure + /// @see ZipArchive::openArchive(Stream *, AccessMode), ZipArchive::closeArchive() + //----------------------------------------------------------------------------- + virtual bool openArchive(const char *filename, AccessMode mode = Read); + + //----------------------------------------------------------------------------- + /// @brief Open a zip archive from a stream + /// + /// The stream must support seeking and must support the specified access + /// mode. For example, if the stream is opened for Read you cannot specify + /// Write to openArchive(). However, if the stream is open for ReadWrite + /// then you can specify any one of Read, Write or ReadWrite as the mode + /// argument to openArchive(). + /// + /// The archive must be closed with closeArchive() when you are done with it. + /// + /// @param stream Pointer to stream to open the zip archive from + /// @param mode Access mode. May be Read, Write or ReadWrite + /// @return true for success, false for failure + /// @see ZipArchive::openArchive(const char *, AccessMode), ZipArchive::closeArchive() + //----------------------------------------------------------------------------- + + // CodeReview [tom, 2/9/2007] I just thought, if we open a stream directly + // for write then rebuilding the zip file probably won't work. This needs to + // be checked and either fixed or worked around. + + virtual bool openArchive(Stream *stream, AccessMode mode = Read); + + //----------------------------------------------------------------------------- + /// @brief Close the zip archive and free any resources + /// + /// @see ZipArchive::openArchive(Stream *, AccessMode), ZipArchive::openArchive(const char *, AccessMode) + //----------------------------------------------------------------------------- + virtual void closeArchive(); + // @} + + /// @name Stream Based File Access Methods + // @{ + + //----------------------------------------------------------------------------- + /// @brief Open a file within the zip file + /// + /// The access mode can only be Read or Write. It is not possible to open + /// a file within the zip as ReadWrite. + /// + /// The returned stream must be freed with closeFile(). Do not delete it + /// directly. + /// + /// In verbose mode, openFile() will display additional error information + /// in the console when it fails. + /// + /// @param filename Filename of the file in the zip + /// @param mode Access mode. May be Read or Write + /// @param ze Output zip entry. May be unspecified. + /// @return Pointer to stream or NULL for failure + /// @see ZipArchive::closeFile(), ZipArchive::isVerbose() + //----------------------------------------------------------------------------- + virtual Stream *openFile(const char *filename, AccessMode mode = Read); + virtual Stream *openFile(const char *filename, ZipEntry* ze, AccessMode = Read); + + //----------------------------------------------------------------------------- + /// @brief Close a file opened through openFile() + /// + /// @param stream Stream to close + /// @see ZipArchive::openFile(const char *, AccessMode) + //----------------------------------------------------------------------------- + virtual void closeFile(Stream *stream); + + //----------------------------------------------------------------------------- + /// @brief Open a file within the zip file for read + /// + /// This method exists mainly for the integration with the resource manager. + /// Unless there is good reason to use this method, it is better to use the + /// openFile() method instead. + /// + /// @param fileCD Pointer to central directory of the file to open + /// @return Pointer to stream or NULL for failure + /// @see ZipArchive::openFile(const char *, AccessMode), ZipArchive::closeFile() + //----------------------------------------------------------------------------- + Stream *openFileForRead(const CentralDir *fileCD); + // @} + + /// @name Archiver Style File Access Methods + // @{ + + //----------------------------------------------------------------------------- + /// @brief Add a file to the zip + /// + /// If replace is false and the file already exists in the zip, this function + /// will fail and return false. If replace is true, the existing file will be + /// overwritten. + /// + /// @param filename Filename on the local file system to add + /// @param pathInZip The path and filename in the zip file to give this file + /// @param replace true to replace existing files, false otherwise + /// @return true for success, false for failure + /// @see ZipArchive::extractFile(), ZipArchive::deleteFile(), ZipArchive::isVerbose() + //----------------------------------------------------------------------------- + bool addFile(const char *filename, const char *pathInZip, bool replace = true); + + //----------------------------------------------------------------------------- + /// @brief Extract a file from the zip + /// + /// The file will be created through the resource manager and so must be + /// in a location that is writable. + /// + /// The file will be CRC checked during extraction and extractFile() will + /// return false if the CRC check failed. The CRC check is just an advisory, + /// the output file will still exist if the CRC check failed. It is up to + /// the caller to decide what to do in the event of a CRC failure. + /// + /// You can optionally pass a pointer to a bool to determine if a CRC check + /// failed. If extractFile() returns false and crcFail is false then the failure + /// was not CRC related. If crcFail is true and extractFile() returns false, + /// then the CRC check failed but the file otherwise extracted OK. You can + /// take your chances as to whether the data is valid or not, if you wish. + /// + /// In verbose mode, extractFile() will display an error in the console when + /// a file fails the CRC check. + /// + /// @param pathInZip The path and filename in the zip file to extract + /// @param filename Filename on the local file system to extract to + /// @param crcFail Pointer to a boolean that receives the result of the CRC check + /// @return true for success, false for failure + /// @see ZipArchive::addFile(), ZipArchive::deleteFile(), ZipArchive::isVerbose() + //----------------------------------------------------------------------------- + bool extractFile(const char *pathInZip, const char *filename, bool *crcFail = NULL); + + //----------------------------------------------------------------------------- + /// @brief Delete a file from the zip + /// + /// Flags a file as deleted so it is removed when the zip file is rebuilt. + /// + /// @param filename Filename in the zip to delete + /// @return true for success, false for failure + /// @see ZipArchive::addFile(), ZipArchive::extractFile(), ZipArchive::isVerbose() + //----------------------------------------------------------------------------- + bool deleteFile(const char *filename); + // @} + + /// @name Enumeration Methods + // @{ + + //----------------------------------------------------------------------------- + /// @brief Get number of entries in the central directory + /// + /// @see ZipArchive::findFileInfo(const char *) + //----------------------------------------------------------------------------- + U32 numEntries() const { return mEntries.size(); } + + //----------------------------------------------------------------------------- + /// Get a central directory entry + //----------------------------------------------------------------------------- + const CentralDir & operator[](const U32 idx) const { return mEntries[idx]->mCD; } + + //----------------------------------------------------------------------------- + /// @brief Find a file in the zip + /// + /// @param filename Path and filename to find + /// @return Pointer to the central directory entry + //----------------------------------------------------------------------------- + CentralDir *findFileInfo(const char *filename); + // @} +}; + +// @} + +} // end namespace Zip + +// @} + +#endif // _ZIPARCHIVE_H_ diff --git a/core/util/zip/zipCryptStream.cpp b/core/util/zip/zipCryptStream.cpp new file mode 100644 index 0000000..0ea6258 --- /dev/null +++ b/core/util/zip/zipCryptStream.cpp @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/zip/zipCryptStream.h" +#include "core/util/zip/crctab.h" + +#include "console/console.h" + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +ZipCryptRStream::ZipCryptRStream() : mStream(NULL), mFileEndPos(0), mPassword(NULL) +{ +} + +ZipCryptRStream::~ZipCryptRStream() +{ +} + +//----------------------------------------------------------------------------- +// Private Methods +//----------------------------------------------------------------------------- + +U32 ZipCryptRStream::fillBuffer(const U32 in_attemptSize, void *pBuffer) +{ + AssertFatal(mStream != NULL, "No stream to fill from?"); + AssertFatal(mStream->getStatus() != Stream::Closed, + "Fill from a closed stream?"); + + U32 currPos = mStream->getPosition(); + + U32 actualReadSize; + if (in_attemptSize + currPos > mFileEndPos) { + actualReadSize = mFileEndPos - currPos; + } else { + actualReadSize = in_attemptSize; + } + + if (mStream->read(actualReadSize, pBuffer) == true) { + return actualReadSize; + } else { + AssertWarn(false, "Read failed while trying to fill buffer"); + return 0; + } +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +void ZipCryptRStream::setPassword(const char *password) +{ + mKeys[0] = 305419896; + mKeys[1] = 591751049; + mKeys[2] = 878082192; + + mPassword = password; + const char *pPtr = password; + while(*pPtr) + { + updateKeys(*pPtr); + pPtr++; + } +} + +bool ZipCryptRStream::attachStream(Stream* io_pSlaveStream) +{ + mStream = io_pSlaveStream; + mStreamStartPos = mStream->getPosition(); + + // [tom, 12/20/2005] Encrypted zip files have an extra 12 bytes + // of entropy before the file data. + + U8 buffer[12]; + if(mStream->read(sizeof(buffer), &buffer)) + { + // Initialize keys + for(S32 i = 0;i < sizeof(buffer);i++) + { + updateKeys(buffer[i] ^= decryptByte()); + } + + // if(buffer[11] !) + mFileStartPos = mStream->getPosition(); + + setStatus(Ok); + return true; + } + return false; +} + +void ZipCryptRStream::detachStream() +{ + mStream = NULL; + + // Clear keys, just in case + dMemset(&mKeys, 0, sizeof(mKeys)); + + setStatus(Closed); +} + +U32 ZipCryptRStream::getPosition() const +{ + return mStream->getPosition(); +} + +bool ZipCryptRStream::setPosition(const U32 in_newPosition) +{ + if(in_newPosition > mFileEndPos) + return false; + + U32 curPos = getPosition(); + U32 readSize = in_newPosition - mFileStartPos; + bool ret = true; + + if(in_newPosition < curPos) + { + // Reposition to start of stream + Stream *stream = getStream(); + U32 startPos = mStreamStartPos; + const char *password = mPassword; + detachStream(); + setPassword(password); + stream->setPosition(startPos); + ret = attachStream(stream); + + if(in_newPosition == mFileStartPos) + return ret; + } + + // Read until we reach the new position + U8 *buffer = new U8 [1024]; + while(readSize >= 1024) + { + readSize -= 1024; + ret = _read(1024, buffer); + if(! ret) + break; + } + + if(readSize > 0 && ret) + { + ret = _read(readSize, buffer); + } + delete [] buffer; + + return ret; +} + +//----------------------------------------------------------------------------- +// Protected Methods +//----------------------------------------------------------------------------- + +void ZipCryptRStream::updateKeys(const U8 c) +{ + mKeys[0] = ZC_CRC32(mKeys[0], c); + mKeys[1] += mKeys[0] & 0x000000ff; + mKeys[1] = mKeys[1] * 134775813 + 1; + U32 k = mKeys[1] >> 24; + mKeys[2] = ZC_CRC32(mKeys[2], k); +} + +U8 ZipCryptRStream::decryptByte() +{ + U16 temp; + temp = (mKeys[2] & 0xffff) | 2; + return (temp * (temp ^ 1)) >> 8; +} + +bool ZipCryptRStream::_read(const U32 in_numBytes, void* out_pBuffer) +{ + U32 numRead = fillBuffer(in_numBytes, out_pBuffer); + if(numRead > 0) + { + // Decrypt + U8 *pBytes = (U8 *)out_pBuffer; + for(S32 i = 0;i < numRead;i++) + { + updateKeys(pBytes[i] ^= decryptByte()); + } + return true; + } + return false; +} diff --git a/core/util/zip/zipCryptStream.h b/core/util/zip/zipCryptStream.h new file mode 100644 index 0000000..dd24720 --- /dev/null +++ b/core/util/zip/zipCryptStream.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ZIPCRYPTSTREAM_H_ +#define _ZIPCRYPTSTREAM_H_ + +#ifndef _FILTERSTREAM_H_ +#include "core/filterStream.h" +#endif + +class ZipCryptRStream : public FilterStream +{ + typedef FilterStream Parent; + + Stream *mStream; + + S32 mStreamStartPos; + S32 mFileStartPos; + S32 mFileEndPos; + + U32 mKeys[3]; // mKeys and it's usage is very unclear and has a ton of magic numbers -patw + + const char *mPassword; + + U32 fillBuffer(const U32 in_attemptSize, void *pBuffer); + +public: + ZipCryptRStream(); + virtual ~ZipCryptRStream(); + + void setPassword(const char *password); + inline void setFileEndPos(S32 pos) { mFileEndPos = pos; } + + // Overrides of FilterStream + bool attachStream(Stream* io_pSlaveStream); + void detachStream(); + Stream *getStream() { return mStream; } + + U32 getPosition() const; + bool setPosition(const U32 in_newPosition); + +protected: + bool _read(const U32 in_numBytes, void* out_pBuffer); + + void updateKeys(const U8 c); + U8 decryptByte(); +}; + +#endif // _ZIPCRYPTSTREAM_H_ diff --git a/core/util/zip/zipObject.cpp b/core/util/zip/zipObject.cpp new file mode 100644 index 0000000..624da4e --- /dev/null +++ b/core/util/zip/zipObject.cpp @@ -0,0 +1,289 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/zip/zipObject.h" +#include "core/util/safeDelete.h" + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +ZipObject::ZipObject() +{ + mZipArchive = NULL; +} + +ZipObject::~ZipObject() +{ + closeArchive(); +} + +IMPLEMENT_CONOBJECT(ZipObject); + +//----------------------------------------------------------------------------- +// Protected Methods +//----------------------------------------------------------------------------- + +StreamObject *ZipObject::createStreamObject(Stream *stream) +{ + for(S32 i = 0;i < mStreamPool.size();++i) + { + StreamObject *so = mStreamPool[i]; + + if(so == NULL) + { + // Reuse any free locations in the pool + so = new StreamObject(stream); + so->registerObject(); + mStreamPool[i] = so; + return so; + } + + if(so->getStream() == NULL) + { + // Existing unused stream, update it and return it + so->setStream(stream); + return so; + } + } + + // No free object found, create a new one + StreamObject *so = new StreamObject(stream); + so->registerObject(); + mStreamPool.push_back(so); + + return so; +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +bool ZipObject::openArchive(const char *filename, Zip::ZipArchive::AccessMode mode /* = Read */) +{ + closeArchive(); + + mZipArchive = new Zip::ZipArchive; + if(mZipArchive->openArchive(filename, mode)) + return true; + + SAFE_DELETE(mZipArchive); + return false; +} + +void ZipObject::closeArchive() +{ + if(mZipArchive == NULL) + return; + + for(S32 i = 0;i < mStreamPool.size();++i) + { + StreamObject *so = mStreamPool[i]; + if(so && so->getStream() != NULL) + closeFile(so); + + SAFE_DELETE_OBJECT(mStreamPool[i]); + } + mStreamPool.clear(); + + mZipArchive->closeArchive(); + SAFE_DELETE(mZipArchive); +} + +//----------------------------------------------------------------------------- + +StreamObject * ZipObject::openFileForRead(const char *filename) +{ + if(mZipArchive == NULL) + return NULL; + + Stream * stream = mZipArchive->openFile(filename, Zip::ZipArchive::Read); + if(stream != NULL) + return createStreamObject(stream); + + return NULL; +} + +StreamObject * ZipObject::openFileForWrite(const char *filename) +{ + if(mZipArchive == NULL) + return NULL; + + Stream * stream = mZipArchive->openFile(filename, Zip::ZipArchive::Write); + if(stream != NULL) + return createStreamObject(stream); + + return NULL; +} + +void ZipObject::closeFile(StreamObject *stream) +{ + if(mZipArchive == NULL) + return; + +#ifdef TORQUE_DEBUG + bool found = false; + for(S32 i = 0;i < mStreamPool.size();++i) + { + StreamObject *so = mStreamPool[i]; + if(so && so == stream) + { + found = true; + break; + } + } + + AssertFatal(found, "ZipObject::closeFile() - Attempting to close stream not opened by this ZipObject"); +#endif + + mZipArchive->closeFile(stream->getStream()); + stream->setStream(NULL); +} + +//----------------------------------------------------------------------------- + +bool ZipObject::addFile(const char *filename, const char *pathInZip, bool replace /* = true */) +{ + return mZipArchive->addFile(filename, pathInZip, replace); +} + +bool ZipObject::extractFile(const char *pathInZip, const char *filename) +{ + return mZipArchive->extractFile(pathInZip, filename); +} + +bool ZipObject::deleteFile(const char *filename) +{ + return mZipArchive->deleteFile(filename); +} + +//----------------------------------------------------------------------------- + +S32 ZipObject::getFileEntryCount() +{ + return mZipArchive ? mZipArchive->numEntries() : 0; +} + +String ZipObject::getFileEntry(S32 idx) +{ + if(mZipArchive == NULL) + return ""; + + const Zip::CentralDir &dir = (*mZipArchive)[idx]; + char buffer[1024]; + int chars = dSprintf(buffer, sizeof(buffer), "%s\t%d\t%d\t%d\t%08x", + dir.mFilename.c_str(), dir.mUncompressedSize, dir.mCompressedSize, + dir.mCompressMethod, dir.mCRC32); + if (chars < sizeof(buffer)) + buffer[chars] = 0; + + return String(buffer); +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +static const struct +{ + const char *strMode; + Zip::ZipArchive::AccessMode mode; +} gModeMap[]= +{ + { "read", Zip::ZipArchive::Read }, + { "write", Zip::ZipArchive::Write }, + { "readwrite", Zip::ZipArchive::ReadWrite }, + { NULL, (Zip::ZipArchive::AccessMode)0 } +}; + +//----------------------------------------------------------------------------- + +ConsoleMethod(ZipObject, openArchive, bool, 3, 4, "(filename, [accessMode = Read]) Open a zip file") +{ + Zip::ZipArchive::AccessMode mode = Zip::ZipArchive::Read; + + if(argc > 3) + { + for(S32 i = 0;gModeMap[i].strMode;++i) + { + if(dStricmp(gModeMap[i].strMode, argv[3]) == 0) + { + mode = gModeMap[i].mode; + break; + } + } + } + + char buf[512]; + Con::expandScriptFilename(buf, sizeof(buf), argv[2]); + + return object->openArchive(buf, mode); +} + +ConsoleMethod(ZipObject, closeArchive, void, 2, 2, "() Close a zip file") +{ + object->closeArchive(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(ZipObject, openFileForRead, S32, 3, 3, "(filename) Open a file within the zip for reading") +{ + StreamObject *stream = object->openFileForRead(argv[2]); + return stream ? stream->getId() : 0; +} + +ConsoleMethod(ZipObject, openFileForWrite, S32, 3, 3, "(filename) Open a file within the zip for reading") +{ + StreamObject *stream = object->openFileForWrite(argv[2]); + return stream ? stream->getId() : 0; +} + +ConsoleMethod(ZipObject, closeFile, void, 3, 3, "(stream) Close a file within the zip") +{ + StreamObject *stream = dynamic_cast(Sim::findObject(argv[2])); + if(stream == NULL) + { + Con::errorf("ZipObject::closeFile - Invalid stream specified"); + return; + } + + object->closeFile(stream); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(ZipObject, addFile, bool, 4, 5, "(filename, pathInZip[, replace = true]) Add a file to the zip") +{ + // Left this line commented out as it was useful when i had a problem + // with the zip's i was creating. [2/21/2007 justind] + // [tom, 2/21/2007] To is now a warnf() for better visual separation in the console + // Con::errorf("zipAdd: %s", argv[2]); + //Con::warnf(" To: %s", argv[3]); + + return object->addFile(argv[2], argv[3], argc > 4 ? dAtob(argv[4]) : true); +} + +ConsoleMethod(ZipObject, extractFile, bool, 4, 4, "(pathInZip, filename) Extract a file from the zip") +{ + return object->extractFile(argv[2], argv[3]); +} + +ConsoleMethod(ZipObject, deleteFile, bool, 3, 3, "(pathInZip) Delete a file from the zip") +{ + return object->deleteFile(argv[2]); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(ZipObject, getFileEntryCount, S32, 2, 2, "() Get number of files in the zip") +{ + return object->getFileEntryCount(); +} + +ConsoleMethod(ZipObject, getFileEntry, const char *, 3, 3, "(index) Get file entry. Returns tab separated string containing filename, uncompressed size, compressed size, compression method and CRC32") +{ + return object->getFileEntry(dAtoi(argv[2])); +} diff --git a/core/util/zip/zipObject.h b/core/util/zip/zipObject.h new file mode 100644 index 0000000..3e18e21 --- /dev/null +++ b/core/util/zip/zipObject.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/simBase.h" +#include "core/util/zip/zipArchive.h" +#include "core/util/tVector.h" +#include "core/stream/streamObject.h" +#include "core/util/str.h" + +#ifndef _ZIPOBJECT_H_ +#define _ZIPOBJECT_H_ + +/// @addtogroup zip_group +// @{ + +//----------------------------------------------------------------------------- +/// @brief Script wrapper for Zip::ZipArchive. +//----------------------------------------------------------------------------- +class ZipObject : public SimObject +{ + typedef SimObject Parent; + +protected: + Zip::ZipArchive *mZipArchive; + + // StreamObjects are pooled and reused to avoid creating tons of SimObjects + Vector mStreamPool; + + StreamObject *createStreamObject(Stream *stream); + +public: + ZipObject(); + virtual ~ZipObject(); + DECLARE_CONOBJECT(ZipObject); + + // Methods for accessing the archive + /// @see Zip::ZipArchive::openArchive() + bool openArchive(const char *filename, Zip::ZipArchive::AccessMode mode = Zip::ZipArchive::Read); + /// @see Zip::ZipArchive::closeArchive() + void closeArchive(); + + // Stream based file system style interface + /// @see Zip::ZipArchive::openFile() + StreamObject *openFileForRead(const char *filename); + /// @see Zip::ZipArchive::openFile() + StreamObject *openFileForWrite(const char *filename); + /// @see Zip::ZipArchive::closeFile() + void closeFile(StreamObject *stream); + + // Alternative archiver style interface + /// @see Zip::ZipArchive::addFile() + bool addFile(const char *filename, const char *pathInZip, bool replace = true); + /// @see Zip::ZipArchive::extractFile() + bool extractFile(const char *pathInZip, const char *filename); + /// @see Zip::ZipArchive::deleteFile() + bool deleteFile(const char *filename); + + + // Methods for access the list of files + S32 getFileEntryCount(); + String getFileEntry(S32 idx); +}; + +// @} + +#endif // _ZIPOBJECT_H_ diff --git a/core/util/zip/zipStatFilter.h b/core/util/zip/zipStatFilter.h new file mode 100644 index 0000000..48ce36f --- /dev/null +++ b/core/util/zip/zipStatFilter.h @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/filterStream.h" +#include "core/crc.h" +#include "core/util/zip/centralDir.h" + +// [tom, 1/29/2007] ZipStatFilter allows us to track CRC and uncompressed size +// on the fly. This is necessary when dealing with compressed files as the +// CRC must be of the uncompressed data. +// +// The alternative would be to compress the files when updating the zip file, +// but this could potentially cause ZipArchive::closeArchive() to take a long +// time to complete. With compression done on the fly the time consuming code +// is pushed out to when writing to files, which is significantly easier to +// do asynchronously. + +#ifndef _ZIPSTATFILTER_H_ +#define _ZIPSTATFILTER_H_ + +//----------------------------------------------------------------------------- +/// \brief Namespace for the zip code. +/// +/// @see Zip::ZipArchive, \ref zip_group "Zip Code Module" +//----------------------------------------------------------------------------- +namespace Zip +{ + +/// @addtogroup zipint_group Zip Code Internals +/// +/// The zip code internals are mostly undocumented, but should be fairly +/// obvious to anyone who is familiar with the zip file format. + +/// @ingroup zip_group +// @{ + +//----------------------------------------------------------------------------- +/// \brief Helper class for tracking CRC and uncompressed size +/// +/// ZipStatFilter allows us to track CRC and uncompressed size +/// on the fly. This is necessary when dealing with compressed files as the +/// CRC must be of the uncompressed data. +/// +/// ZipStatFilter is mostly intended for internal use by the zip code. +/// However, it can be useful when reading zips sequentially using the +/// stream interface to provide CRC checking. +/// +/// Example +/// +/// @code +/// // It's assumed that you would use proper error checking and that +/// // zip is a valid pointer to a ZipArchive and otherStream is a pointer +/// // to a valid stream. +/// Zip::ZipArchive *zip; +/// Stream *otherStream; +/// +/// // We need the real central directory to compare the CRC32 +/// Zip::CentralDir *realCD = zip->findFileInfo("file.txt"); +/// Stream *stream = zip->openFile("file.txt", ZipArchive::Read); +/// +/// Zip::CentralDir fakeCD; +/// Zip::ZipStatFilter zsf(&fakeCD); +/// +/// zsf.attachStream(stream); +/// +/// // ... read entire file sequentially using zsf instead of stream +/// otherStream->copyFrom(&zsf); +/// +/// zsf.detachStream(); +/// +/// // fakeCD.mCRC32 now contains the CRC32 of the stream +/// if(fakeCD.mCRC32 != realCD->mCRC32) +/// { +/// // ... handle CRC failure ... +/// } +/// +/// zip->closeFile(stream); +/// @endcode +/// +/// A more complete example of this may be found in the code for the +/// ZipArchive::extractFile() method in zipArchive.cc +/// +//----------------------------------------------------------------------------- +class ZipStatFilter : public FilterStream +{ + typedef FilterStream Parent; + +protected: + Stream *mStream; + + CentralDir *mCD; + + virtual bool _write(const U32 numBytes, const void *buffer) + { + if(! mStream->write(numBytes, buffer)) + return false; + + mCD->mUncompressedSize += numBytes; + mCD->mCRC32 = CRC::calculateCRC(buffer, numBytes, mCD->mCRC32); + + return true; + } + + virtual bool _read(const U32 numBytes, void *buffer) + { + if(! mStream->read(numBytes, buffer)) + return false; + + mCD->mUncompressedSize += numBytes; + mCD->mCRC32 = CRC::calculateCRC(buffer, numBytes, mCD->mCRC32); + + return true; + } + +public: + ZipStatFilter() : mStream(NULL), mCD(NULL) {} + ZipStatFilter(CentralDir *cd) : mStream(NULL), mCD(cd) {} + virtual ~ZipStatFilter() + { + detachStream(); + } + + virtual bool attachStream(Stream *stream) + { + if(mCD == NULL) + return false; + + mStream = stream; + mCD->mUncompressedSize = 0; + mCD->mCRC32 = CRC::INITIAL_CRC_VALUE; + return true; + } + + virtual void detachStream() + { + if(mStream == NULL) + return; + + // Post condition the CRC + mCD->mCRC32 ^= CRC::CRC_POSTCOND_VALUE; + mStream = NULL; + } + + virtual Stream *getStream() { return mStream; } + + void setCentralDir(CentralDir *cd) { mCD = cd; } + CentralDir *getCentralDir() { return mCD; } +}; + +// @} + +} // end namespace Zip + +#endif // _ZIPSTATFILTER_H_ diff --git a/core/util/zip/zipSubStream.cpp b/core/util/zip/zipSubStream.cpp new file mode 100644 index 0000000..798943f --- /dev/null +++ b/core/util/zip/zipSubStream.cpp @@ -0,0 +1,462 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "zlib.h" +#include "core/util/zip/zipSubStream.h" + + +const U32 ZipSubRStream::csm_streamCaps = U32(Stream::StreamRead) | U32(Stream::StreamPosition); +const U32 ZipSubRStream::csm_inputBufferSize = 4096; + +const U32 ZipSubWStream::csm_streamCaps = U32(Stream::StreamWrite); +const U32 ZipSubWStream::csm_bufferSize = (2048 * 1024); + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +ZipSubRStream::ZipSubRStream() + : m_pStream(NULL), + m_uncompressedSize(0), + m_currentPosition(0), + m_EOS(false), + m_pZipStream(NULL), + m_pInputBuffer(NULL), + m_originalSlavePosition(0), + m_lastBytesRead(0) +{ + // +} + +//-------------------------------------- +ZipSubRStream::~ZipSubRStream() +{ + detachStream(); +} + +//-------------------------------------- +bool ZipSubRStream::attachStream(Stream* io_pSlaveStream) +{ + AssertFatal(io_pSlaveStream != NULL, "NULL Slave stream?"); + AssertFatal(m_pStream == NULL, "Already attached!"); + + m_pStream = io_pSlaveStream; + m_originalSlavePosition = io_pSlaveStream->getPosition(); + m_uncompressedSize = 0; + m_currentPosition = 0; + m_EOS = false; + + // Initialize zipStream state... + m_pZipStream = new z_stream_s; + m_pInputBuffer = new U8[csm_inputBufferSize]; + + m_pZipStream->zalloc = Z_NULL; + m_pZipStream->zfree = Z_NULL; + m_pZipStream->opaque = Z_NULL; + + U32 buffSize = fillBuffer(csm_inputBufferSize); + + m_pZipStream->next_in = m_pInputBuffer; + m_pZipStream->avail_in = buffSize; + m_pZipStream->total_in = 0; + inflateInit2(m_pZipStream, -MAX_WBITS); + + setStatus(Ok); + return true; +} + +//-------------------------------------- +void ZipSubRStream::detachStream() +{ + if (m_pZipStream != NULL) + { + // close out zip stream... + inflateEnd(m_pZipStream); + + delete [] m_pInputBuffer; + m_pInputBuffer = NULL; + delete m_pZipStream; + m_pZipStream = NULL; + } + + m_pStream = NULL; + m_originalSlavePosition = 0; + m_uncompressedSize = 0; + m_currentPosition = 0; + m_EOS = false; + setStatus(Closed); +} + +//-------------------------------------- +Stream* ZipSubRStream::getStream() +{ + return m_pStream; +} + +//-------------------------------------- +void ZipSubRStream::setUncompressedSize(const U32 in_uncSize) +{ + AssertFatal(m_pStream != NULL, "error, no stream to set unc size for"); + + m_uncompressedSize = in_uncSize; +} + +//-------------------------------------- +bool ZipSubRStream::_read(const U32 in_numBytes, void *out_pBuffer) +{ + m_lastBytesRead = 0; + if (in_numBytes == 0) + return true; + + AssertFatal(out_pBuffer != NULL, "NULL output buffer"); + if (getStatus() == Closed) { + AssertFatal(false, "Attempted read from closed stream"); + return false; + } + + + if (Ok != getStatus()) + return false; + + if (m_EOS) + { + setStatus(EOS); + return true; + }; + + // Ok, we need to call inflate() until the output buffer is full. + // first, set up the output portion of the z_stream + // + m_pZipStream->next_out = (Bytef*)out_pBuffer; + m_pZipStream->avail_out = in_numBytes; + m_pZipStream->total_out = 0; + + while (m_pZipStream->avail_out != 0) + { + S32 retVal = Z_OK; + + if(m_pZipStream->avail_in == 0) + { + // check if there is more output pending + inflate(m_pZipStream, Z_SYNC_FLUSH); + + if(m_pZipStream->total_out != in_numBytes) + { + // Need to provide more input bytes for the stream to read... + U32 buffSize = fillBuffer(csm_inputBufferSize); + //AssertFatal(buffSize != 0, "Must find a more graceful way to handle this"); + + m_pZipStream->next_in = m_pInputBuffer; + m_pZipStream->avail_in = buffSize; + m_pZipStream->total_in = 0; + } + } + + // need to get more? + if(m_pZipStream->total_out != in_numBytes) + retVal = inflate(m_pZipStream, Z_SYNC_FLUSH); + + AssertFatal(retVal != Z_BUF_ERROR, "Should never run into a buffer error"); + AssertFatal(retVal == Z_OK || retVal == Z_STREAM_END, "error in the stream"); + + m_lastBytesRead = m_pZipStream->total_out; + + if (retVal == Z_STREAM_END) + { + if (m_pZipStream->avail_out != 0) + m_EOS = true; + + setStatus(EOS); + m_currentPosition += m_pZipStream->total_out; + return true; + } + } + AssertFatal(m_pZipStream->total_out == in_numBytes, + "Error, didn't finish the decompression!"); + + // If we're here, everything went peachy... + setStatus(Ok); + m_currentPosition += m_pZipStream->total_out; + + return true; +} + +//-------------------------------------- +bool ZipSubRStream::hasCapability(const Capability in_cap) const +{ + return (csm_streamCaps & U32(in_cap)) != 0; +} + +//-------------------------------------- +U32 ZipSubRStream::getPosition() const +{ + AssertFatal(m_pStream != NULL, "Error, not attached"); + + return m_currentPosition; +} + +//-------------------------------------- +bool ZipSubRStream::setPosition(const U32 in_newPosition) +{ + AssertFatal(m_pStream != NULL, "Error, not attached"); + + if (in_newPosition == 0) + { + Stream* pStream = getStream(); + U32 resetPosition = m_originalSlavePosition; + U32 uncompressedSize = m_uncompressedSize; + detachStream(); + pStream->setPosition(resetPosition); + attachStream(pStream); + setUncompressedSize(uncompressedSize); + return true; + } + else + { + if (in_newPosition > m_uncompressedSize) + return false; + + U32 newPosition = in_newPosition; + if (newPosition < m_currentPosition) + { + Stream* pStream = getStream(); + U32 resetPosition = m_originalSlavePosition; + U32 uncompressedSize = m_uncompressedSize; + detachStream(); + pStream->setPosition(resetPosition); + attachStream(pStream); + setUncompressedSize(uncompressedSize); + } + else + { + newPosition -= m_currentPosition; + } + + bool bRet = true; + char *buffer = new char[2048]; + while (newPosition >= 2048) + { + newPosition -= 2048; + if (!_read(2048,buffer)) + { + bRet = false; + break; + } + }; + if (bRet && newPosition > 0) + { + if (!_read(newPosition,buffer)) + { + bRet = false; + }; + }; + + delete [] buffer; + + return bRet; + + } +} + +//-------------------------------------- +U32 ZipSubRStream::getStreamSize() +{ + AssertFatal(m_pStream != NULL, "No stream to size()"); + AssertFatal(m_uncompressedSize != 0, "No data? Properties probably not set..."); + + return m_uncompressedSize; +} + +//-------------------------------------- +U32 ZipSubRStream::fillBuffer(const U32 in_attemptSize) +{ + AssertFatal(m_pStream != NULL, "No stream to fill from?"); + AssertFatal(m_pStream->getStatus() != Stream::Closed, + "Fill from a closed stream?"); + + U32 streamSize = m_pStream->getStreamSize(); + U32 currPos = m_pStream->getPosition(); + + U32 actualReadSize; + if (in_attemptSize + currPos > streamSize) { + actualReadSize = streamSize - currPos; + } else { + actualReadSize = in_attemptSize; + } + + if (m_pStream->read(actualReadSize, m_pInputBuffer) == true) { + return actualReadSize; + } else { + AssertWarn(false, "Read failed while trying to fill buffer"); + return 0; + } +} + + +//-------------------------------------------------------------------------- +ZipSubWStream::ZipSubWStream() + : m_pStream(NULL), + m_pZipStream(NULL), + m_currPosition(0), + m_pOutputBuffer(NULL), + m_pInputBuffer(NULL), + m_lastBytesRead(0), + m_lastBytesWritten(0) +{ + // +} + +//-------------------------------------- +ZipSubWStream::~ZipSubWStream() +{ + detachStream(); +} + +//-------------------------------------- +bool ZipSubWStream::attachStream(Stream* io_pSlaveStream) +{ + AssertFatal(io_pSlaveStream != NULL, "NULL Slave stream?"); + AssertFatal(m_pStream == NULL, "Already attached!"); + + m_pStream = io_pSlaveStream; + m_currPosition = 0; + + m_pOutputBuffer = new U8[csm_bufferSize]; + m_pInputBuffer = new U8[csm_bufferSize]; + + // Initialize zipStream state... + m_pZipStream = new z_stream_s; + + m_pZipStream->zalloc = Z_NULL; + m_pZipStream->zfree = Z_NULL; + m_pZipStream->opaque = Z_NULL; + + m_pZipStream->next_in = m_pInputBuffer; + m_pZipStream->avail_in = csm_bufferSize; + m_pZipStream->total_in = 0; + m_pZipStream->next_out = m_pOutputBuffer; + m_pZipStream->avail_out = csm_bufferSize; + m_pZipStream->total_out = 0; + + deflateInit2(m_pZipStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + + setStatus(Ok); + return true; +} + +//-------------------------------------- +void ZipSubWStream::detachStream() +{ + // Must finish... + if (m_pZipStream != NULL) + { + m_pZipStream->avail_in = 0; + deflate(m_pZipStream, Z_FINISH); + + // write the remainder + m_pStream->write(csm_bufferSize - m_pZipStream->avail_out, m_pOutputBuffer); + + // close out zip stream... + deflateEnd(m_pZipStream); + + delete m_pZipStream; + m_pZipStream = NULL; + + delete [] m_pInputBuffer; + delete [] m_pOutputBuffer; + m_pInputBuffer = NULL; + m_pOutputBuffer = NULL; + } + + m_pStream = NULL; + m_currPosition = 0; + setStatus(Closed); +} + +//-------------------------------------- +Stream* ZipSubWStream::getStream() +{ + return m_pStream; +} + +//-------------------------------------- +bool ZipSubWStream::_read(const U32, void*) +{ + AssertFatal(false, "Cannot read from a ZipSubWStream"); + + setStatus(IllegalCall); + return false; +} + +//-------------------------------------- +bool ZipSubWStream::_write(const U32 numBytes, const void *pBuffer) +{ + m_lastBytesWritten = 0; + if (numBytes == 0) + return true; + + AssertFatal(pBuffer != NULL, "NULL input buffer"); + if (getStatus() == Closed) + { + AssertFatal(false, "Attempted write to a closed stream"); + return false; + } + + m_pZipStream->next_in = (U8*)pBuffer; + m_pZipStream->avail_in = numBytes; + + // write as many bufferSize chunks as possible + while(m_pZipStream->avail_in != 0) + { + if(m_pZipStream->avail_out == 0) + { + if(!m_pStream->write(csm_bufferSize, m_pOutputBuffer)) + return(false); + + m_pZipStream->next_out = m_pOutputBuffer; + m_pZipStream->avail_out = csm_bufferSize; + } + + S32 retVal = deflate(m_pZipStream, Z_NO_FLUSH); + AssertFatal(retVal != Z_BUF_ERROR, "ZipSubWStream::_write: invalid buffer"); + } + + setStatus(Ok); + m_currPosition += m_pZipStream->total_out; + + m_lastBytesWritten = m_pZipStream->total_out; + + return true; +} + +//-------------------------------------- +bool ZipSubWStream::hasCapability(const Capability in_cap) const +{ + return (csm_streamCaps & U32(in_cap)) != 0; +} + +//-------------------------------------- +U32 ZipSubWStream::getPosition() const +{ + AssertFatal(m_pStream != NULL, "Error, not attached"); + + return m_currPosition; +} + +//-------------------------------------- +bool ZipSubWStream::setPosition(const U32 /*in_newPosition*/) +{ + AssertFatal(m_pStream != NULL, "Error, not attached"); + AssertFatal(false, "Not implemented!"); + + // Erk. How do we do this. + return false; +} + +U32 ZipSubWStream::getStreamSize() +{ + AssertFatal(false, "Undecided how to implement this!"); + return 0; +} + diff --git a/core/util/zip/zipSubStream.h b/core/util/zip/zipSubStream.h new file mode 100644 index 0000000..74a576f --- /dev/null +++ b/core/util/zip/zipSubStream.h @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ZIPSUBSTREAM_H_ +#define _ZIPSUBSTREAM_H_ + +//Includes +#ifndef _FILTERSTREAM_H_ +#include "core/filterStream.h" +#endif + +struct z_stream_s; + +class ZipSubRStream : public FilterStream, public IStreamByteCount +{ + typedef FilterStream Parent; + static const U32 csm_streamCaps; + static const U32 csm_inputBufferSize; + + Stream* m_pStream; + U32 m_uncompressedSize; + U32 m_currentPosition; + bool m_EOS; + z_stream_s* m_pZipStream; + U8* m_pInputBuffer; + U32 m_originalSlavePosition; + U32 m_lastBytesRead; + + U32 fillBuffer(const U32 in_attemptSize); +public: + virtual U32 getLastBytesRead() { return m_lastBytesRead; } + virtual U32 getLastBytesWritten() { return 0; } + + + public: + ZipSubRStream(); + virtual ~ZipSubRStream(); + + // Overrides of NFilterStream + public: + bool attachStream(Stream* io_pSlaveStream); + void detachStream(); + Stream* getStream(); + + void setUncompressedSize(const U32); + + // Mandatory overrides. By default, these are simply passed to + // whatever is returned from getStream(); + protected: + bool _read(const U32 in_numBytes, void* out_pBuffer); + public: + bool hasCapability(const Capability) const; + + U32 getPosition() const; + bool setPosition(const U32 in_newPosition); + + U32 getStreamSize(); +}; + +class ZipSubWStream : public FilterStream, public IStreamByteCount +{ + typedef FilterStream Parent; + static const U32 csm_streamCaps; + static const U32 csm_bufferSize; + + Stream* m_pStream; + z_stream_s* m_pZipStream; + U32 m_currPosition; // Indicates number of _uncompressed_ bytes written + U8* m_pOutputBuffer; + U8* m_pInputBuffer; + U32 m_lastBytesRead; + U32 m_lastBytesWritten; + +public: + virtual U32 getLastBytesRead() { return m_lastBytesRead; } + virtual U32 getLastBytesWritten() { return m_lastBytesWritten; } + + public: + ZipSubWStream(); + virtual ~ZipSubWStream(); + + // Overrides of NFilterStream + public: + bool attachStream(Stream* io_pSlaveStream); + void detachStream(); + Stream* getStream(); + + // Mandatory overrides. By default, these are simply passed to + // whatever is returned from getStream(); + protected: + bool _read(const U32 in_numBytes, void* out_pBuffer); + bool _write(const U32 in_numBytes, const void* in_pBuffer); + public: + bool hasCapability(const Capability) const; + + U32 getPosition() const; + bool setPosition(const U32 in_newPosition); + + U32 getStreamSize(); +}; + +#endif //_ZIPSUBSTREAM_H_ diff --git a/core/util/zip/zipTempStream.cpp b/core/util/zip/zipTempStream.cpp new file mode 100644 index 0000000..b5214b4 --- /dev/null +++ b/core/util/zip/zipTempStream.cpp @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "core/util/zip/zipTempStream.h" +#include "core/crc.h" + +namespace Zip +{ + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- +static S32 tempNum = 0; + +bool ZipTempStream::open(String filename, Torque::FS::File::AccessMode mode) +{ + if(filename.isEmpty()) + { + filename = String("_ZipTempStream_tempfile") + String::ToString(tempNum++); + //filename = Platform::getTemporaryFileName(); + mDeleteOnClose = true; + } + + mFilename = filename; + + if(! Parent::open(filename, mode)) + return false; + + mStreamCaps &= ~U32(StreamPosition); + + return true; +} + +} // end namespace Zip diff --git a/core/util/zip/zipTempStream.h b/core/util/zip/zipTempStream.h new file mode 100644 index 0000000..d49f26a --- /dev/null +++ b/core/util/zip/zipTempStream.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/zip/zipArchive.h" +#include "core/util/str.h" + +#ifndef _ZIPTEMPSTREAM_H_ +#define _ZIPTEMPSTREAM_H_ + +namespace Zip +{ + +/// @addtogroup zipint_group +/// @ingroup zip_group +// @{ + +class ZipTempStream : public FileStream +{ + typedef FileStream Parent; + +protected: + CentralDir *mCD; + bool mDeleteOnClose; + String mFilename; + +public: + ZipTempStream() : mCD(NULL), mDeleteOnClose(false) {} + ZipTempStream(CentralDir *cd) : mCD(cd), mDeleteOnClose(false) {} + virtual ~ZipTempStream() { close(); } + + void setCentralDir(CentralDir *cd) { mCD = cd; } + CentralDir *getCentralDir() { return mCD; } + + void setDeleteOnClose(bool del) { mDeleteOnClose = del; } + + virtual bool open(String filename, Torque::FS::File::AccessMode mode); + + /// Open a temporary file in ReadWrite mode. The file will be deleted when the stream is closed. + virtual bool open() + { + return open(String(), Torque::FS::File::ReadWrite); + } + + virtual void close() + { + Parent::close(); + + if(mDeleteOnClose) + Torque::FS::Remove(mFilename); + + } + + /// Disallow setPosition() + virtual bool setPosition(const U32 i_newPosition) { return false; } + + /// Seek back to the start of the file. + /// This is used internally by the zip code and should never be called whilst + /// filters are attached (e.g. when reading or writing in a zip file) + bool rewind() + { + mStreamCaps |= U32(StreamPosition); + bool ret = Parent::setPosition(0); + mStreamCaps &= ~U32(StreamPosition); + + return ret; + } +}; + +// @} + +} // end namespace Zip + +#endif // _ZIPTEMPSTREAM_H_ diff --git a/core/util/zip/zipVolume.cpp b/core/util/zip/zipVolume.cpp new file mode 100644 index 0000000..b4abbce --- /dev/null +++ b/core/util/zip/zipVolume.cpp @@ -0,0 +1,441 @@ +#include "core/util/zip/zipVolume.h" +#include "core/util/zip/zipSubStream.h" +#include "core/util/noncopyable.h" +#include "console/console.h" + +namespace Torque +{ + using namespace FS; + using namespace Zip; + +class ZipFileNode : public Torque::FS::File, public Noncopyable +{ + typedef FileNode Parent; + + //-------------------------------------------------------------------------- + // ZipFileNode class (Internal) + //-------------------------------------------------------------------------- +public: + ZipFileNode(StrongRefPtr& archive, String zipFilename, Stream* zipStream, ZipArchive::ZipEntry* ze) + { + mZipStream = zipStream; + mArchive = archive; + mZipFilename = zipFilename; + mByteCount = dynamic_cast(mZipStream); + AssertFatal(mByteCount, "error, zip stream interface does not implement IStreamByteCount"); + mZipEntry = ze; + } + virtual ~ZipFileNode() + { + close(); + } + + virtual Path getName() const { return mZipFilename; } + virtual Status getStatus() const + { + if (mZipStream) + { + // great, Stream Status is different from FileNode Status... + switch (mZipStream->getStatus()) + { + case Stream::Ok: + return FileNode::Open; + case Stream::EOS: + return FileNode::EndOfFile; + case Stream::IOError: + return FileNode::UnknownError; + default: + return FileNode::UnknownError; + } + } + else + return FileNode::Closed; + } + + virtual bool getAttributes(Attributes* attr) + { + if (!attr) + return false; + + attr->flags = FileNode::File | FileNode::Compressed | FileNode::ReadOnly; + attr->name = mZipFilename; + // use the mod time for both mod and access time, since we only have mod time in the CD + attr->mtime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate); + attr->atime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate); + attr->size = mZipEntry->mCD.mUncompressedSize; + + return true; + } + + virtual U32 getPosition() + { + if (mZipStream) + return mZipStream->getPosition(); + else + return 0; + } + + virtual U32 setPosition(U32 pos, SeekMode mode) + { + if (!mZipStream || mode != Begin) + return 0; + else + return mZipStream->setPosition(pos); + } + + virtual bool open(AccessMode mode) + { + // stream is already open so just check to make sure that they are using a valid mode + if (mode == Read) + return mZipStream != NULL; + else + { + Con::errorf("ZipFileSystem: Write access denied for file %s", mZipFilename.c_str()); + return false; + } + } + + virtual bool close() + { + if (mZipStream != NULL && mArchive != NULL) + { + mArchive->closeFile(mZipStream); + mZipStream = NULL; + mByteCount = NULL; + } + return true; + } + + virtual U32 read(void* dst, U32 size) + { + if (mZipStream && mZipStream->read(size, dst) && mByteCount) + return mByteCount->getLastBytesRead(); + else + return 0; + } + + virtual U32 write(const void* src, U32 size) + { + if (mZipStream && mZipStream->write(size, src) && mByteCount) + return mByteCount->getLastBytesWritten(); + else + return 0; + } + + protected: + virtual U32 calculateChecksum() + { + // JMQ: implement + return 0; + }; + + Stream* mZipStream; + StrongRefPtr mArchive; + ZipArchive::ZipEntry* mZipEntry; + String mZipFilename; + IStreamByteCount* mByteCount; +}; + +//-------------------------------------------------------------------------- +// ZipDirectoryNode class (Internal) +//-------------------------------------------------------------------------- + +class ZipDirectoryNode : public Torque::FS::Directory, public Noncopyable +{ +public: + ZipDirectoryNode(StrongRefPtr& archive, const Torque::Path& path, ZipArchive::ZipEntry* ze) + { + mPath = path; + mArchive = archive; + mZipEntry = ze; + if (mZipEntry) + mChildIter = mZipEntry->mChildren.end(); + } + ~ZipDirectoryNode() + { + } + + Torque::Path getName() const { return mPath; } + + // getStatus() doesn't appear to be used for directories + Status getStatus() const + { + return FileNode::Open; + } + + bool getAttributes(Attributes* attr) + { + if (!attr) + return false; + + attr->flags = FileNode::Directory | FileNode::Compressed | FileNode::ReadOnly; + attr->name = mPath.getFullPath(); + // use the mod time for both mod and access time, since we only have mod time in the CD + attr->mtime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate); + attr->atime = ZipArchive::DOSTimeToTime(mZipEntry->mCD.mModTime, mZipEntry->mCD.mModDate); + attr->size = mZipEntry->mCD.mUncompressedSize; + + return true; + } + + bool open() + { + // reset iterator + if (mZipEntry) + mChildIter = mZipEntry->mChildren.begin(); + return (mZipEntry != NULL && mArchive.getPointer() != NULL); + } + bool close() + { + if (mZipEntry) + mChildIter = mZipEntry->mChildren.end(); + return true; + } + bool read(Attributes* attr) + { + if (!attr) + return false; + + if (mChildIter == mZipEntry->mChildren.end()) + return false; + + ZipArchive::ZipEntry* ze = (*mChildIter).value; + + attr->flags = FileNode::Compressed; + if (ze->mIsDirectory) + attr->flags |= FileNode::Directory; + else + attr->flags |= FileNode::File; + + attr->name = ze->mName; + attr->mtime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate); + attr->atime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate); + attr->size = 0; // we don't know the size until we open a stream, so we'll have to use size 0 + + mChildIter++; + + return true; + } + +private: + U32 calculateChecksum() + { + return 0; + } + + Torque::Path mPath; + Map::Iterator mChildIter; + StrongRefPtr mArchive; + ZipArchive::ZipEntry* mZipEntry; +}; + +//-------------------------------------------------------------------------- +// ZipFakeRootNode class (Internal) +//-------------------------------------------------------------------------- + +class ZipFakeRootNode : public Torque::FS::Directory, public Noncopyable +{ +public: + ZipFakeRootNode(StrongRefPtr& archive, const Torque::Path& path, const String &fakeRoot) + { + mPath = path; + mArchive = archive; + mRead = false; + mFakeRoot = fakeRoot; + } + ~ZipFakeRootNode() + { + } + + Torque::Path getName() const { return mPath; } + + // getStatus() doesn't appear to be used for directories + Status getStatus() const + { + return FileNode::Open; + } + + bool getAttributes(Attributes* attr) + { + if (!attr) + return false; + + attr->flags = FileNode::Directory | FileNode::Compressed | FileNode::ReadOnly; + attr->name = mPath.getFullPath(); + // use the mod time for both mod and access time, since we only have mod time in the CD + + ZipArchive::ZipEntry* zipEntry = mArchive->getRoot(); + attr->mtime = ZipArchive::DOSTimeToTime(zipEntry->mCD.mModTime, zipEntry->mCD.mModDate); + attr->atime = ZipArchive::DOSTimeToTime(zipEntry->mCD.mModTime, zipEntry->mCD.mModDate); + attr->size = zipEntry->mCD.mUncompressedSize; + + return true; + } + + bool open() + { + mRead = false; + return (mArchive.getPointer() != NULL); + } + bool close() + { + mRead = false; + return true; + } + bool read(Attributes* attr) + { + if (!attr) + return false; + + if (mRead) + return false; + + ZipArchive::ZipEntry* ze = mArchive->getRoot(); + + attr->flags = FileNode::Compressed; + if (ze->mIsDirectory) + attr->flags |= FileNode::Directory; + else + attr->flags |= FileNode::File; + + attr->name = mFakeRoot; + attr->mtime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate); + attr->atime = ZipArchive::DOSTimeToTime(ze->mCD.mModTime, ze->mCD.mModDate); + attr->size = 0; // we don't know the size until we open a stream, so we'll have to use size 0 + + mRead = true; + + return true; + } + +private: + U32 calculateChecksum() + { + return 0; + } + + Torque::Path mPath; + Map::Iterator mChildIter; + StrongRefPtr mArchive; + bool mRead; + String mFakeRoot; +}; + +//-------------------------------------------------------------------------- +// ZipFileSystem +//-------------------------------------------------------------------------- + +ZipFileSystem::ZipFileSystem(String& zipFilename, bool zipNameIsDir /* = false */) +{ + mZipFilename = zipFilename; + mInitted = false; + mZipNameIsDir = zipNameIsDir; + if(mZipNameIsDir) + { + Path path(zipFilename); + mFakeRoot = Path::Join(path.getPath(), '/', path.getFileName()); + } + + // open the file now but don't read it yet, since we want construction to be lightweight + // we open the file now so that whatever filesystems are mounted right now (which may be temporary) + // can be umounted without affecting this file system. + mZipArchiveStream = new FileStream(); + mZipArchiveStream->open(mZipFilename, Torque::FS::File::Read); + + // As far as the mount system is concerned, ZFSes are read only write now (even though + // ZipArchive technically support read-write, we don't expose this to the mount system because we + // don't want to support that as a standard run case right now.) + mReadOnly = true; +} + +ZipFileSystem::~ZipFileSystem() +{ + if (mZipArchiveStream) + { + mZipArchiveStream->close(); + delete mZipArchiveStream; + } + mZipArchive = NULL; +} + +FileNodeRef ZipFileSystem::resolve(const Path& path) +{ + if (!mInitted) + _init(); + + if (mZipArchive.isNull()) + return NULL; + + // eat leading "/" + String name = path.getFullPath(false); + if (name.find("/") == 0) + name = name.substr(1, name.length() - 1); + + if(name.isEmpty() && mZipNameIsDir) + return new ZipFakeRootNode(mZipArchive, path, mFakeRoot); + + if(mZipNameIsDir) + { + // Remove the fake root from the name so things can be found + if(name.find(mFakeRoot) == 0) + name = name.substr(mFakeRoot.length()); + + if (name.find("/") == 0) + name = name.substr(1, name.length() - 1); + } + + // first check to see if input path is a directory + // check for request of root directory + if (name.isEmpty()) + { + ZipDirectoryNode* zdn = new ZipDirectoryNode(mZipArchive, path, mZipArchive->getRoot()); + return zdn; + } + + ZipArchive::ZipEntry* ze = mZipArchive->findZipEntry(name); + if (ze == NULL) + return NULL; + + if (ze->mIsDirectory) + { + ZipDirectoryNode* zdn = new ZipDirectoryNode(mZipArchive, path, ze); + return zdn; + } + + // pass in the zip entry so that openFile() doesn't need to look it up again. + Stream* stream = mZipArchive->openFile(name, ze, ZipArchive::Read); + if (stream == NULL) + return NULL; + + ZipFileNode* zfn = new ZipFileNode(mZipArchive, name, stream, ze); + return zfn; +} + +void ZipFileSystem::_init() +{ + if (mInitted) + return; + mInitted = true; + + if (!mZipArchive.isNull()) + return; + if (mZipArchiveStream->getStatus() != Stream::Ok) + return; + + mZipArchive = new ZipArchive(); + if (!mZipArchive->openArchive(mZipArchiveStream, ZipArchive::Read)) + { + Con::errorf("ZipFileSystem: failed to open zip archive %s", mZipFilename.c_str()); + return; + } + + // tell the archive that it owns the zipStream now + mZipArchive->setDiskStream(mZipArchiveStream); + // and null it out because we don't own it anymore + mZipArchiveStream = NULL; + + // for debugging + //mZipArchive->dumpCentralDirectory(); +} + +}; \ No newline at end of file diff --git a/core/util/zip/zipVolume.h b/core/util/zip/zipVolume.h new file mode 100644 index 0000000..5a13b68 --- /dev/null +++ b/core/util/zip/zipVolume.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- +#ifndef _CORE_ZIP_VOLUME_H_ +#define _CORE_ZIP_VOLUME_H_ + +#include "core/volume.h" +#include "core/util/str.h" +#include "core/util/zip/zipArchive.h" +#include "core/util/autoPtr.h" + +namespace Torque +{ + using namespace FS; + using namespace Zip; + +class ZipFileSystem: public FileSystem +{ +public: + ZipFileSystem(String& zipFilename, bool zipNameIsDir = false); + virtual ~ZipFileSystem(); + + String getTypeStr() const { return "Zip"; } + + FileNodeRef resolve(const Path& path); + + // these are unsupported, ZipFileSystem is currently read only access + FileNodeRef create(const Path& path,FileNode::Mode) { return 0; } + bool remove(const Path& path) { return 0; } + bool rename(const Path& a,const Path& b) { return 0; } + + // these are unsupported + Path mapTo(const Path& path) { return path; } + Path mapFrom(const Path& path) { return path; } + +public: + /// Private interface for use by unit test only. + StrongRefPtr getArchive() { return mZipArchive; } + +private: + void _init(); + + bool mInitted; + bool mZipNameIsDir; + String mZipFilename; + String mFakeRoot; + FileStream* mZipArchiveStream; + StrongRefPtr mZipArchive; +}; + +} + +#endif \ No newline at end of file diff --git a/core/virtualMountSystem.cpp b/core/virtualMountSystem.cpp new file mode 100644 index 0000000..bc26727 --- /dev/null +++ b/core/virtualMountSystem.cpp @@ -0,0 +1,286 @@ +#include "core/virtualMountSystem.h" +#include "platform/platform.h" +#include "console/console.h" +#include "core/tAlgorithm.h" + +namespace Torque +{ +namespace FS +{ + +bool gVMSVerboseLog = false; + +bool VirtualMountSystem::mount(String root, FileSystemRef fs) +{ + bool ok = Parent::mount(root,fs); + if (!ok) + return false; + + root = String::ToLower(root); + + mRootMap[root].push_back(fs); + +// PathFSMap* rootDict = NULL; +// if (!mMountMap.tryGetValue(root, rootDict)) +// { +// rootDict = new PathFSMap(); +// mMountMap[root] = rootDict; +// } +// +// U32 start = Platform::getRealMilliseconds(); +// +// // get the paths from the fs and add them to the rootDict +// Vector paths; +// +// // we'll use the mount system's findByPattern function to build the path list. +// // but, we want to override its default behavior so that it searches only the desired fs. +// _setFindByPatternOverrideFS(fs); +// +// Torque::Path basePath; +// // we use an empty root so that the resulting filenames don't have the root filename in them. +// // we don't want to include the root in the dict has entries. we can omit the root because we have +// // specified an override FS; the search would fail otherwise. +// //basePath.setRoot(root); +// basePath.setRoot(""); +// basePath.setPath("/"); +// mUseParentFind = true; +// if (findByPattern(basePath, "*.*", true, paths, true) == -1) +// { +// // this is probably a problem +// _log("Unable to get paths from filesystem for virtual mount"); +// _setFindByPatternOverrideFS(NULL); +// mUseParentFind = false; +// return false; +// } +// +// _setFindByPatternOverrideFS(NULL); +// mUseParentFind = false; +// +// for (S32 i = 0; i < paths.size(); ++i) +// { +// String path = String::ToLower(paths[i]); +// +// // is it a directory? if so remove dir prefix +// String dirPrefix = "DIR:"; +// String::SizeType dIdx = path.find(dirPrefix, 0, String::NoCase); +// if (dIdx == 0) +// path = path.substr(dirPrefix.length()); +// // omit leading / +// if (path[(String::SizeType)0] == '/') +// path = path.substr(1); +// +// // warn about duplicate files (not directories) +// // JMQ: disabled this, it false alarms at startup because the mount doc always mounts the +// // root before other processing mounts. still, would be useful, maybe change the mount doc to +// // not mount root? +// if (dIdx != 0 && (*rootDict)[path].size() > 0) +// _log(String::ToString("Duplicate file path detected, first volume containing file will be used: %s", path.c_str())); +// +// (*rootDict)[path].push_back(fs); +// } +// +// if (gVMSVerboseLog) +// _log(String::ToString("Indexed virtual file system in %ums", Platform::getRealMilliseconds() - start)); + + return true; +} + +bool VirtualMountSystem::mount(String root, const Path &path) +{ + //AssertFatal(false, "This function not supported in virtual mount system"); + return Parent::mount(root, path); +} + +FileSystemRef VirtualMountSystem::unmount(String root) +{ + FileSystemRef ret = Parent::unmount(root); + + mRootMap.erase(root); + + // clear all filesystem lists for root. +// PathFSMap* rootDict = NULL; +// root = String::ToLower(root); +// if (!mMountMap.tryGetValue(root, rootDict)) +// return ret; +// +// // buh bye +// mMountMap.erase(root); +// delete rootDict; + + return ret; +} + +bool VirtualMountSystem::unmount(FileSystemRef fs) +{ + bool unmounted = Parent::unmount(fs); + if (!unmounted) + return false; + + for(PathFSMap::Iterator ritr = mRootMap.begin();ritr != mRootMap.end();++ritr) + { + RootToFSVec &vec = (*ritr).value; + for (S32 i = vec.size() - 1;i >= 0;i--) + { + if (vec[i].getPointer() == fs.getPointer()) + vec.erase(i); + } + } + + // this is a linear time operation, because we have to search every path in all roots + // to remove references to the fs. + // contant time operation can be achieved be using the unmount(string) version, which unmounts all + // filesystems for a given root and so doesn't need to do any searching. +// U32 start = Platform::getRealMilliseconds(); +// for (RootToPathFSMap::Iterator riter = mMountMap.begin(); +// riter != mMountMap.end(); +// ++riter) +// { +// PathFSMap* rootDict = (*riter).value; +// for (PathFSMap::Iterator piter = rootDict->begin(); +// piter != rootDict->end(); +// ++piter) +// { +// Vector& plist = (*piter).value; +// for (S32 i = plist.size() - 1; +// i >= 0; +// i--) +// { +// if (plist[i].getPointer() == fs.getPointer()) +// plist.erase(i); +// } +// } +// } +// +// if (gVMSVerboseLog) +// _log(String::ToString("Unmounted virtual file system in %ums", Platform::getRealMilliseconds() - start)); + + return true; +} + +S32 VirtualMountSystem::findByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool includeDirs/* =false */, bool multiMatch /* = true */ ) +{ + if (mUseParentFind) + // use parent version + return Parent::findByPattern(inBasePath, inFilePattern, inRecursive, outList, includeDirs, multiMatch); + + // don't want to re-enter this version + mUseParentFind = true; + + // kind of cheesy, just call find by pattern on each File system mounted on the root + for (Vector::const_iterator itr = mMountList.begin(); itr != mMountList.end(); itr++) + { + if (itr->root.equal( inBasePath.getRoot(), String::NoCase ) ) + { + FileSystemRef fsref = itr->fileSystem; + _setFindByPatternOverrideFS(fsref); + Parent::findByPattern(inBasePath, inFilePattern, inRecursive, outList, includeDirs, multiMatch); + _setFindByPatternOverrideFS(NULL); + } + } + + mUseParentFind = false; + + return outList.size(); +} + +bool VirtualMountSystem::createPath(const Path& path) +{ + bool ret = Parent::createPath(path); + +// if (ret) +// { +// // make sure the filesystem that owns the path has the path elements +// // in its search table (so that we can open the file, if it is new) +// String root = String::ToLower(path.getRoot()); +// FileSystemRef fsRef = getFileSystem(path); +// +// PathFSMap* rootDict = mMountMap[root]; +// if (rootDict) +// { +// // add all directories in the path +// // add the filename +// +// // Start from the top and work our way down +// Path sub,dir; +// dir.setPath(""); +// for (U32 i = 0; i < path.getDirectoryCount(); i++) +// { +// sub.setPath(path.getDirectory(i)); +// dir.appendPath(sub); +// +// Vector& fsList = (*rootDict)[String::ToLower(dir.getPath())]; +// Vector::iterator iter = ::find(fsList.begin(), fsList.end(), fsRef); +// +// if (iter == fsList.end()) +// fsList.push_back(fsRef); +// } +// +// // add full file path +// Vector& fsList = (*rootDict)[String::ToLower(path.getFullPath(false))]; +// Vector::iterator iter = ::find(fsList.begin(), fsList.end(), fsRef); +// +// if (iter == fsList.end()) +// fsList.push_back(fsRef); +// } +// } + + return ret; +} + +void VirtualMountSystem::_log(const String& msg) +{ + String newMsg = "VirtualMountSystem: " + msg; + Con::warnf("%s", newMsg.c_str()); +} + +FileSystemRef VirtualMountSystem::_removeMountFromList(String root) +{ + return Parent::_removeMountFromList(root); +} + +FileSystemRef VirtualMountSystem::_getFileSystemFromList(const Path& fullpath) const +{ + String root = String::ToLower(fullpath.getRoot()); + String path = fullpath.getFullPath(false); + // eat leading slash + if (path[(String::SizeType)0] == '/') + path = path.substr(1); + // lowercase it + path = String::ToLower(path); + + // find the dictionary for root +// PathFSMap* rootDict = NULL; +// if (!mMountMap.tryGetValue(root, rootDict)) +// return NULL; +// +// // see if we have a FS list for this path +// Vector& fsList = (*rootDict)[path]; + + RootToFSVec fsList; + if(! mRootMap.tryGetValue(root, fsList)) + return NULL; + + if (fsList.size() == 0) + { + // no exact match for path, defer to parent + return Parent::_getFileSystemFromList(fullpath); + } + else + { + // find the right file system + if(fsList.size() == 1) + return fsList[0]; + + for(S32 i = 0;i < fsList.size();++i) + { + FileNodeRef fn = fsList[i]->resolve(path); + if(fn != NULL) + return fsList[i]; + } + + return fsList[0]; + } +} + +} //namespace FS +} //namespace Torque \ No newline at end of file diff --git a/core/virtualMountSystem.h b/core/virtualMountSystem.h new file mode 100644 index 0000000..e876151 --- /dev/null +++ b/core/virtualMountSystem.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- +#ifndef _CORE_VMS_H_ +#define _CORE_VMS_H_ + +#include "core/volume.h" +#include "core/util/tDictionary.h" + +namespace Torque +{ +namespace FS +{ + + /// The VirtualMountSystem extend the mount system by allowing you to mount and access multiple filesystems + /// on a single root. This allows you to mount multiple volume files on a single root without needing to + /// change source paths that reference those volumes to use new roots. For instance, you could mount + /// "missions1.zip" and "missions2.zip", on the same root "", and you could access files from either one. + /// + /// If you need to mount a writeable filesystem on a VMS, you should mount it as the first filesystem on a root. + /// File writes are handled as follows: + /// 1) If you try to open a file for write, and it exists on one of the virtual mounts, + /// that is the mount that will be used for writing. But if the filesystem is read-only then the writes + /// will fail (and you will get appropriate error codes from your FileStream or whatever). + /// 2) If you try to open a file for write that doesn't exist, the VMS falls back to the default MountSystem + /// behavior, which is to use the first filesystem mounted on the root. If this filesystem happens to be + /// writable, then you will be able to write to the file. + /// 3) Nothing special is done for the WriteAppend case; that is, it follows the same logic as if you were + /// just trying to Write the file. + /// + /// Because of rule 1) above, you should take care that any files you need to write (such as prefs.cs), are not + /// included in read-only zip archive files. + class VirtualMountSystem : public MountSystem + { + typedef MountSystem Parent; + public: + VirtualMountSystem() : mUseParentFind(false) {} + + virtual ~VirtualMountSystem() { } + virtual bool mount(String root, FileSystemRef fs); + virtual bool mount(String root, const Path &path); + virtual FileSystemRef unmount(String root); + virtual bool unmount(FileSystemRef fs); + virtual S32 findByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool includeDirs=false, bool multiMatch = true ); + virtual bool createPath(const Path& path); + + protected: + virtual void _log(const String& msg); + virtual FileSystemRef _removeMountFromList(String root); + virtual FileSystemRef _getFileSystemFromList(const Path& path) const ; + + // Vector of file system refs + typedef Vector RootToFSVec; + // map of path to list of file systems containing path + typedef Map > PathFSMap; + // map of root to PathFSMap for that root + //typedef Map RootToPathFSMap; + + //RootToPathFSMap mMountMap; + PathFSMap mRootMap; + + bool mUseParentFind; // this is needed because findByParent calls itself recursively and in some cases we don't want it to re-enter our version + }; + +} +} + +#endif + diff --git a/core/volume.cpp b/core/volume.cpp new file mode 100644 index 0000000..a859031 --- /dev/null +++ b/core/volume.cpp @@ -0,0 +1,1097 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "core/volume.h" +#include "core/virtualMountSystem.h" +#include "core/strings/findMatch.h" +#include "core/util/journal/process.h" +#include "core/util/safeDelete.h" +#include "console/console.h" + + +namespace Torque +{ +using namespace FS; + +//----------------------------------------------------------------------------- + +bool FileSystemChangeNotifier::addNotification( const Path &path, ChangeDelegate callback ) +{ + // Notifications are for files... if the path is empty + // then there is nothing to do. + if ( path.isEmpty() ) + return false; + + // strip the filename and extension - we notify on dirs + Path dir(cleanPath(path)); + if (dir.isEmpty()) + return false; + + dir.setFileName( String() ); + dir.setExtension( String () ); + + // first lookup the dir to see if we already have an entry for it... + DirMap::Iterator itr = mDirMap.find( dir ); + + FileInfoList *fileList = NULL; + bool addedDir = false; + + // Note that GetFileAttributes can fail here if the file doesn't + // exist, but thats ok and expected. You can have notifications + // on files that don't exist yet. + FileNode::Attributes attr; + GetFileAttributes(path, &attr); + + if ( itr != mDirMap.end() ) + { + fileList = &(itr->value); + + // look for the file and return if we find it + for ( U32 i = 0; i < fileList->getSize(); i++ ) + { + FileInfo &fInfo = (*fileList)[i]; + if ( fInfo.filePath == path ) + { + // NOTE: This is bad... we should store the mod + // time for each callback seperately in the future. + // + fInfo.savedLastModTime = attr.mtime; + fInfo.signal.notify(callback); + return true; + } + } + } + else + { + // otherwise we need to add the dir to our map and let the inherited class add it + itr = mDirMap.insert( dir, FileInfoList() ); + + fileList = &(itr->value); + + addedDir = true; + } + + FileInfo newInfo; + newInfo.signal.notify(callback); + newInfo.filePath = path; + newInfo.savedLastModTime = attr.mtime; + + fileList->pushBack( newInfo ); + + return addedDir ? internalAddNotification( dir ) : true; +} + +bool FileSystemChangeNotifier::removeNotification( const Path &path, ChangeDelegate callback ) +{ + if (path.isEmpty()) + return false; + + // strip the filename and extension - we notify on dirs + Path dir(cleanPath(path)); + if (dir.isEmpty()) + return false; + + dir.setFileName( String() ); + dir.setExtension( String () ); + + DirMap::Iterator itr = mDirMap.find( dir ); + + if ( itr == mDirMap.end() ) + return false; + + FileInfoList &fileList = itr->value; + + // look for the file and return if we find it + for ( U32 i = 0; i < fileList.getSize(); i++ ) + { + FileInfo &fInfo = fileList[i]; + if ( fInfo.filePath == path ) + { + fInfo.signal.remove(callback); + if (fInfo.signal.isEmpty()) + fileList.erase( i ); + break; + } + } + + // IF we removed the last file + // THEN get rid of the dir from our map and notify inherited classes + if ( fileList.getSize() == 0 ) + { + mDirMap.erase( dir ); + + return internalRemoveNotification( dir ); + } + + return true; +} + +void FileSystemChangeNotifier::startNotifier() +{ + // update the timestamps of all the files we are managing + + DirMap::Iterator itr = mDirMap.begin(); + + for ( ; itr != mDirMap.end(); ++itr ) + { + FileInfoList &fileList = itr->value; + + for ( U32 i = 0; i < fileList.getSize(); i++ ) + { + FileInfo &fInfo = fileList[i]; + + // This may fail if the file doesn't exist... thats ok. + FileNode::Attributes attr; + GetFileAttributes(fInfo.filePath, &attr); + + fInfo.savedLastModTime = attr.mtime; + } + } + + mNotifying = true; + + Process::notify( this, &FileSystemChangeNotifier::process, PROCESS_LAST_ORDER ); +} + +void FileSystemChangeNotifier::process() +{ + internalProcessOnce(); +} + +void FileSystemChangeNotifier::stopNotifier() +{ + mNotifying = false; + + Process::remove( this, &FileSystemChangeNotifier::process ); +} + +/// Makes sure paths going in and out of the notifier will have the same format +String FileSystemChangeNotifier::cleanPath(const Path& dir) +{ + // This "cleans up" the path, if we don't do this we can get mismatches on the path + // coming from the notifier + FileSystemRef fs = Torque::FS::GetFileSystem(dir); + if (!fs) + return String::EmptyString; + return fs->mapFrom(fs->mapTo(dir)); +} + +void FileSystemChangeNotifier::internalNotifyDirChanged( const Path &dir ) +{ + Con::warnf( "[FileSystemChangeNotifier::internalNotifyDirChanged] : [%s]", dir.getFullPath().c_str() ); + + DirMap::Iterator itr = mDirMap.find( dir ); + if ( itr == mDirMap.end() ) + return; + + // Gather the changed file info. + FileInfoList changedList; + FileInfoList &fileList = itr->value; + for ( U32 i = 0; i < fileList.getSize(); i++ ) + { + FileInfo &fInfo = fileList[i]; + + FileNode::Attributes attr; + bool success = GetFileAttributes(fInfo.filePath, &attr); + + // Ignore the file if we couldn't get the attributes (it must have + // been deleted) or the last modification time isn't newer. + if ( !success || attr.mtime <= fInfo.savedLastModTime ) + continue; + + // Store the new mod time. + fInfo.savedLastModTime = attr.mtime; + + // We're taking a copy of the FileInfo struct here so that the + // callback can safely remove the notification without crashing. + changedList.pushBack( fInfo ); + } + + // Now signal all the changed files. + for ( U32 i = 0; i < changedList.getSize(); i++ ) + { + FileInfo &fInfo = changedList[i]; + + Con::warnf( " : file changed [%s]", fInfo.filePath.getFullPath().c_str() ); + fInfo.signal.trigger( fInfo.filePath ); + } +} + +//----------------------------------------------------------------------------- + +FileSystem::FileSystem() + : mChangeNotifier( NULL ), + mReadOnly(false) +{ +} + +FileSystem::~FileSystem() +{ + delete mChangeNotifier; + mChangeNotifier = NULL; +} + +File::File() {} +File::~File() {} +Directory::Directory() {} +Directory::~Directory() {} + + +FileNode::FileNode() +: mChecksum(0) +{ +} + +Time FileNode::getModifiedTime() +{ + Attributes attrs; + + bool success = getAttributes( &attrs ); + + if ( !success ) + return Time(); + + return attrs.mtime; +} + +U64 FileNode::getSize() +{ + Attributes attrs; + + bool success = getAttributes( &attrs ); + + if ( !success ) + return 0; + + return attrs.size; +} + +U32 FileNode::getChecksum() +{ + bool calculateCRC = (mLastChecksum == Torque::Time()); + + if ( !calculateCRC ) + { + Torque::Time modTime = getModifiedTime(); + + calculateCRC = (modTime > mLastChecksum); + } + + if ( calculateCRC ) + mChecksum = calculateChecksum(); + + if ( mChecksum ) + mLastChecksum = Time::getCurrentTime(); + + return mChecksum; + +} + +//----------------------------------------------------------------------------- + +class FileSystemRedirect: public FileSystem +{ + friend class FileSystemRedirectChangeNotifier; +public: + FileSystemRedirect(MountSystem* mfs,const Path& path); + + String getTypeStr() const { return "Redirect"; } + + FileNodeRef resolve(const Path& path); + FileNodeRef create(const Path& path,FileNode::Mode); + bool remove(const Path& path); + bool rename(const Path& a,const Path& b); + Path mapTo(const Path& path); + Path mapFrom(const Path& path); + +private: + Path _merge(const Path& path); + + Path mPath; + MountSystem *mMFS; +}; + +class FileSystemRedirectChangeNotifier : public FileSystemChangeNotifier +{ +public: + + FileSystemRedirectChangeNotifier( FileSystem *fs ); + + bool addNotification( const Path &path, ChangeDelegate callback ); + bool removeNotification( const Path &path, ChangeDelegate callback ); + +protected: + + virtual void internalProcessOnce() {} + virtual bool internalAddNotification( const Path &dir ) { return false; } + virtual bool internalRemoveNotification( const Path &dir ) { return false; } +}; + +FileSystemRedirectChangeNotifier::FileSystemRedirectChangeNotifier( FileSystem *fs ) +: FileSystemChangeNotifier( fs ) +{ + +} + +bool FileSystemRedirectChangeNotifier::addNotification( const Path &path, ChangeDelegate callback ) +{ + FileSystemRedirect *rfs = (FileSystemRedirect*)mFS; + Path redirectPath = rfs->_merge( path ); + + FileSystemRef fs = rfs->mMFS->getFileSystem( redirectPath ); + if ( !fs || !fs->getChangeNotifier() ) + return false; + + return fs->getChangeNotifier()->addNotification( redirectPath, callback ); +} + +bool FileSystemRedirectChangeNotifier::removeNotification( const Path &path, ChangeDelegate callback ) +{ + FileSystemRedirect *rfs = (FileSystemRedirect*)mFS; + Path redirectPath = rfs->_merge( path ); + + FileSystemRef fs = rfs->mMFS->getFileSystem( redirectPath ); + if ( !fs || !fs->getChangeNotifier() ) + return false; + + return fs->getChangeNotifier()->removeNotification( redirectPath, callback ); +} + +FileSystemRedirect::FileSystemRedirect(MountSystem* mfs,const Path& path) +{ + mMFS = mfs; + mPath.setRoot(path.getRoot()); + mPath.setPath(path.getPath()); + mChangeNotifier = new FileSystemRedirectChangeNotifier( this ); +} + +Path FileSystemRedirect::_merge(const Path& path) +{ + Path p = mPath; + p.setPath(Path::Join(p.getPath(),'/',Path::CompressPath(path.getPath()))); + p.setFileName(path.getFileName()); + p.setExtension(path.getExtension()); + return p; +} + +FileNodeRef FileSystemRedirect::resolve(const Path& path) +{ + Path p = _merge(path); + FileSystemRef fs = mMFS->getFileSystem(p); + if (fs != NULL) + return fs->resolve(p); + return NULL; +} + +FileNodeRef FileSystemRedirect::create(const Path& path,FileNode::Mode mode) +{ + Path p = _merge(path); + FileSystemRef fs = mMFS->getFileSystem(p); + if (fs != NULL) + return fs->create(p,mode); + return NULL; +} + +bool FileSystemRedirect::remove(const Path& path) +{ + Path p = _merge(path); + FileSystemRef fs = mMFS->getFileSystem(p); + if (fs != NULL) + return fs->remove(p); + return false; +} + +bool FileSystemRedirect::rename(const Path& a,const Path& b) +{ + Path na = _merge(a); + Path nb = _merge(b); + FileSystemRef fsa = mMFS->getFileSystem(na); + FileSystemRef fsb = mMFS->getFileSystem(nb); + if (fsa.getPointer() == fsb.getPointer()) + return fsa->rename(na,nb); + return false; +} + +Path FileSystemRedirect::mapTo(const Path& path) +{ + Path p = _merge(path); + FileSystemRef fs = mMFS->getFileSystem(p); + if (fs != NULL) + return fs->mapTo(p); + return NULL; +} + +Path FileSystemRedirect::mapFrom(const Path& path) +{ + Path p = _merge(path); + FileSystemRef fs = mMFS->getFileSystem(p); + if (fs != NULL) + return fs->mapFrom(p); + return NULL; +} + +//----------------------------------------------------------------------------- + +void MountSystem::_log(const String& msg) +{ + String newMsg = "MountSystem: " + msg; + Con::warnf("%s", newMsg.c_str()); +} + +FileSystemRef MountSystem::_removeMountFromList(String root) +{ + for (Vector::iterator itr = mMountList.begin(); itr != mMountList.end(); itr++) + { + if (root.equal( itr->root, String::NoCase )) + { + FileSystemRef fs = itr->fileSystem; + mMountList.erase(itr); + return fs; + } + } + return NULL; +} + +FileSystemRef MountSystem::_getFileSystemFromList(const Path& path) const +{ + for (Vector::const_iterator itr = mMountList.begin(); itr != mMountList.end(); itr++) + { + if (itr->root.equal( path.getRoot(), String::NoCase )) + return itr->fileSystem; + } + + return NULL; +} + + +Path MountSystem::_normalize(const Path& path) +{ + Path po = path; + + // Assign to cwd root if none is specified. +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XENON) //TEMPHACK + if (po.getRoot().isEmpty()) + po.setRoot(mCWD.getRoot()); +#endif + + // Merge in current working directory if the path is relative to + // the current cwd. + if (po.getRoot().equal( mCWD.getRoot(), String::NoCase ) && po.isRelative()) + { + po.setPath(Path::CompressPath( Path::Join(mCWD.getPath(),'/',po.getPath()) )); + } + return po; +} + +FileRef MountSystem::createFile(const Path& path) +{ + Path np = _normalize(path); + FileSystemRef fs = _getFileSystemFromList(np); + + if (fs && fs->isReadOnly()) + { + _log(String::ToString("Cannot create file %s, filesystem is read-only", path.getFullPath().c_str())); + return NULL; + } + + if (fs != NULL) + return static_cast(fs->create(np,FileNode::File).getPointer()); + return NULL; +} + +DirectoryRef MountSystem::createDirectory(const Path& path, FileSystemRef fs) +{ + Path np = _normalize(path); + if (fs.isNull()) + fs = _getFileSystemFromList(np); + + if (fs && fs->isReadOnly()) + { + _log(String::ToString("Cannot create directory %s, filesystem is read-only", path.getFullPath().c_str())); + return NULL; + } + + if (fs != NULL) + return static_cast(fs->create(np,FileNode::Directory).getPointer()); + return NULL; +} + +FileRef MountSystem::openFile(const Path& path,File::AccessMode mode) +{ + FileNodeRef node = getFileNode(path); + if (node != NULL) + { + FileRef file = dynamic_cast(node.getPointer()); + if (file != NULL) + { + if (file->open(mode)) + return file; + else + return NULL; + } + } + else + { + if (mode != File::Read) + { + FileRef file = createFile(path); + + if (file != NULL) + { + file->open(mode); + return file; + } + } + } + return NULL; +} + +DirectoryRef MountSystem::openDirectory(const Path& path) +{ + FileNodeRef node = getFileNode(path); + + if (node != NULL) + { + DirectoryRef dir = dynamic_cast(node.getPointer()); + if (dir != NULL) + { + dir->open(); + return dir; + } + } + return NULL; +} + +bool MountSystem::remove(const Path& path) +{ + Path np = _normalize(path); + FileSystemRef fs = _getFileSystemFromList(np); + if (fs && fs->isReadOnly()) + { + _log(String::ToString("Cannot remove path %s, filesystem is read-only", path.getFullPath().c_str())); + return false; + } + if (fs != NULL) + return fs->remove(np); + return false; +} + +bool MountSystem::rename(const Path& from,const Path& to) +{ + // Will only rename files on the same filesystem + Path pa = _normalize(from); + Path pb = _normalize(to); + FileSystemRef fsa = _getFileSystemFromList(pa); + FileSystemRef fsb = _getFileSystemFromList(pb); + if (!fsa || !fsb) + return false; + if (fsa.getPointer() != fsb.getPointer()) + { + _log(String::ToString("Cannot rename path %s to a different filesystem", from.getFullPath().c_str())); + return false; + } + if (fsa->isReadOnly() || fsb->isReadOnly()) + { + _log(String::ToString("Cannot rename path %s; source or target filesystem is read-only", from.getFullPath().c_str())); + return false; + } + + return fsa->rename(pa,pb); +} + +bool MountSystem::mount(String root,FileSystemRef fs) +{ + MountFS mount; + mount.root = root; + mount.path = "/"; + mount.fileSystem = fs; + mMountList.push_back(mount); + return true; +} + +bool MountSystem::mount(String root,const Path &path) +{ + return mount(root,new FileSystemRedirect(this,_normalize(path))); +} + +FileSystemRef MountSystem::unmount(String root) +{ + FileSystemRef first = _removeMountFromList(root); + + // remove remaining FSes on this root + while (!_removeMountFromList(root).isNull()) + ; + + return first; +} + +bool MountSystem::unmount(FileSystemRef fs) +{ + if (fs.isNull()) + return false; + + // iterate back to front in case FS is in list multiple times. + // also check that fs is not null each time since its a strong ref + // so it could be nulled during removal. + bool unmounted = false; + for (int i = mMountList.size() - 1; !fs.isNull() && i >= 0; --i) + { + if (mMountList[i].fileSystem.getPointer() == fs.getPointer()) + { + mMountList.erase(i); + unmounted = true; + } + } + return unmounted; +} + +bool MountSystem::setCwd(const Path& file) +{ + if (file.getPath().isEmpty()) + return false; + mCWD.setRoot(file.getRoot()); + mCWD.setPath(file.getPath()); + return true; +} + +const Path& MountSystem::getCwd() const +{ + return mCWD; +} + +FileSystemRef MountSystem::getFileSystem(const Path& path) +{ + return _getFileSystemFromList(_normalize(path)); +} + +bool MountSystem::getFileAttributes(const Path& path,FileNode::Attributes* attr) +{ + FileNodeRef file = getFileNode(path); + + if (file != NULL) + { + bool result = file->getAttributes(attr); + return result; + } + + return false; +} + +FileNodeRef MountSystem::getFileNode(const Path& path) +{ + Path np = _normalize(path); + FileSystemRef fs = _getFileSystemFromList(np); + if (fs != NULL) + return fs->resolve(np); + return NULL; +} + +bool MountSystem::mapFSPath( const String &inRoot, const Path &inPath, Path &outPath ) +{ + FileSystemRef fs = _getFileSystemFromList(inRoot); + + if ( fs == NULL ) + { + outPath = Path(); + return false; + } + + outPath = fs->mapFrom( inPath ); + + return outPath.getFullPath() != String(); +} + +S32 MountSystem::findByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool includeDirs/* =false */, bool multiMatch /* = true */ ) +{ + if (mFindByPatternOverrideFS.isNull() && !inBasePath.isDirectory() ) + return -1; + + DirectoryRef dir = NULL; + if (mFindByPatternOverrideFS.isNull()) + // open directory using standard mount system search + dir = openDirectory( inBasePath ); + else + { + // use specified filesystem to open directory + FileNodeRef fNode = mFindByPatternOverrideFS->resolve(inBasePath); + if (fNode && (dir = dynamic_cast(fNode.getPointer())) != NULL) + dir->open(); + } + + if ( dir == NULL ) + return -1; + + if (includeDirs) + { + // prepend cheesy "DIR:" annotation for directories + outList.push_back(String("DIR:") + inBasePath.getPath()); + } + + FileNode::Attributes attrs; + + Vector recurseDirs; + + while ( dir->read( &attrs ) ) + { + // skip hidden files + if ( attrs.name.c_str()[0] == '.' ) + continue; + + String name( attrs.name ); + + if ( (attrs.flags & FileNode::Directory) && inRecursive ) + { + name += '/'; + String path = Path::Join( inBasePath, '/', name ); + recurseDirs.push_back( path ); + } + + if ( !multiMatch && FindMatch::isMatch( inFilePattern, attrs.name, false ) ) + { + String path = Path::Join( inBasePath, '/', name ); + outList.push_back( path ); + } + + if ( multiMatch && FindMatch::isMatchMultipleExprs( inFilePattern, attrs.name, false ) ) + { + String path = Path::Join( inBasePath, '/', name ); + outList.push_back( path ); + } + } + + dir->close(); + + for ( S32 i = 0; i < recurseDirs.size(); i++ ) + findByPattern( recurseDirs[i], inFilePattern, true, outList, includeDirs, multiMatch ); + + return outList.size(); +} + +bool MountSystem::isFile(const Path& path) +{ + FileNode::Attributes attr; + if (getFileAttributes(path,&attr)) + return attr.flags & FileNode::File; + return false; +} + +bool MountSystem::isDirectory(const Path& path, FileSystemRef fsRef) +{ + FileNode::Attributes attr; + + if (fsRef.isNull()) + { + if (getFileAttributes(path,&attr)) + return attr.flags & FileNode::Directory; + return false; + } + else + { + FileNodeRef fnRef = fsRef->resolve(path); + if (fnRef.isNull()) + return false; + + FileNode::Attributes attr; + if (fnRef->getAttributes(&attr)) + return attr.flags & FileNode::Directory; + return false; + } +} + +bool MountSystem::isReadOnly(const Path& path) +{ + // first check to see if filesystem is read only + FileSystemRef fs = getFileSystem(path); + if ( fs.isNull() ) + // no filesystem owns this file...oh well, return false + return false; + if (fs->isReadOnly()) + return true; + + // check the file attributes, note that if the file does not exist, + // this function returns false. that should be ok since we know + // the file system is writable at this point. + FileNode::Attributes attr; + if (getFileAttributes(path,&attr)) + return attr.flags & FileNode::ReadOnly; + return false; +} + +void MountSystem::startFileChangeNotifications() +{ + for ( U32 i = 0; i < mMountList.size(); i++ ) + { + FileSystemChangeNotifier *notifier = mMountList[i].fileSystem->getChangeNotifier(); + + if ( notifier != NULL && !notifier->isNotifying() ) + notifier->startNotifier(); + } +} + +void MountSystem::stopFileChangeNotifications() +{ + for ( U32 i = 0; i < mMountList.size(); i++ ) + { + FileSystemChangeNotifier *notifier = mMountList[i].fileSystem->getChangeNotifier(); + + if ( notifier != NULL && notifier->isNotifying() ) + notifier->stopNotifier(); + } +} + +bool MountSystem::createPath(const Path& path) +{ + if (path.getPath().isEmpty()) + return true; + + // See if the pathectory exists + Path dir; + dir.setRoot(path.getRoot()); + dir.setPath(path.getPath()); + + // in a virtual mount system, isDirectory may return true if the directory exists in a read only FS, + // but the directory may not exist on a writeable filesystem that is also mounted. + // So get the target filesystem that will + // be used for the full writable path and and make sure the directory exists on it. + FileSystemRef fsRef = getFileSystem(path); + + if (isDirectory(dir,fsRef)) + return true; + + // Start from the top and work our way down + Path sub; + dir.setPath(path.isAbsolute()? String("/"): String()); + for (U32 i = 0; i < path.getDirectoryCount(); i++) + { + sub.setPath(path.getDirectory(i)); + dir.appendPath(sub); + if (!isDirectory(dir,fsRef)) + { + if (!createDirectory(dir,fsRef)) + return false; + } + } + return true; +} + + +//----------------------------------------------------------------------------- + +// Default global mount system +// [8/31/2009 tomb] Disabling this for T3D 1.0 due to issues with script namespace corruption when running from zips +// Note that the Platform::FS::MountZips() must be called in platformVolume.cpp for zip support to work. +//static VirtualMountSystem sgMountSystem; +static MountSystem sgMountSystem; + +namespace FS +{ + +FileRef CreateFile(const Path &path) +{ + return sgMountSystem.createFile(path); +} + +DirectoryRef CreateDirectory(const Path &path) +{ + return sgMountSystem.createDirectory(path); +} + +FileRef OpenFile(const Path &path, File::AccessMode mode) +{ + return sgMountSystem.openFile(path,mode); +} + +bool ReadFile(const Path &inPath, void *&outData, U32 &outSize, bool inNullTerminate ) +{ + FileRef fileR = OpenFile( inPath, File::Read ); + + outData = NULL; + outSize = 0; + + // We'll get a NULL file reference if + // the file failed to open. + if ( fileR == NULL ) + return false; + + outSize = fileR->getSize(); + + // Its not a failure to read an empty + // file... but we can exit early. + if ( outSize == 0 ) + return true; + + U32 sizeRead = 0; + + if ( inNullTerminate ) + { + outData = new char [outSize+1]; + sizeRead = fileR->read(outData, outSize); + static_cast(outData)[outSize] = '\0'; + } + else + { + outData = new char [outSize]; + sizeRead = fileR->read(outData, outSize); + } + + if ( sizeRead != outSize ) + { + delete static_cast(outData); + outData = NULL; + outSize = 0; + return false; + } + + return true; +} + +DirectoryRef OpenDirectory(const Path &path) +{ + return sgMountSystem.openDirectory(path); +} + +bool Remove(const Path &path) +{ + return sgMountSystem.remove(path); +} + +bool Rename(const Path &from, const Path &to) +{ + return sgMountSystem.rename(from,to); +} + +bool Mount(String root, FileSystemRef fs) +{ + return sgMountSystem.mount(root,fs); +} + +bool Mount(String root, const Path &path) +{ + return sgMountSystem.mount(root,path); +} + +FileSystemRef Unmount(String root) +{ + return sgMountSystem.unmount(root); +} + +bool Unmount(FileSystemRef fs) +{ + return sgMountSystem.unmount(fs); +} + +bool SetCwd(const Path &file) +{ + return sgMountSystem.setCwd(file); +} + +const Path& GetCwd() +{ + return sgMountSystem.getCwd(); +} + +FileSystemRef GetFileSystem(const Path &path) +{ + return sgMountSystem.getFileSystem(path); +} + +bool GetFileAttributes(const Path &path, FileNode::Attributes* attr) +{ + return sgMountSystem.getFileAttributes(path,attr); +} + +S32 CompareModifiedTimes(const Path& p1, const Path& p2) +{ + FileNode::Attributes a1, a2; + if (!Torque::FS::GetFileAttributes(p1, &a1)) + return -1; + if (!Torque::FS::GetFileAttributes(p2, &a2)) + return -1; + if (a1.mtime < a2.mtime) + return -1; + if (a1.mtime == a2.mtime) + return 0; + return 1; +} + +FileNodeRef GetFileNode(const Path &path) +{ + return sgMountSystem.getFileNode(path); +} + +bool MapFSPath( const String &inRoot, const Path &inPath, Path &outPath ) +{ + return sgMountSystem.mapFSPath( inRoot, inPath, outPath ); +} + +bool GetFSPath( const Path &inPath, Path &outPath ) +{ + FileSystemRef sys = GetFileSystem( inPath ); + if ( sys ) + { + outPath = sys->mapTo( inPath ); + return true; + } + + return false; +} + +S32 FindByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool multiMatch ) +{ + return sgMountSystem.findByPattern(inBasePath, inFilePattern, inRecursive, outList, false, multiMatch); +} + +bool IsFile(const Path &path) +{ + return sgMountSystem.isFile(path); +} + +bool IsDirectory(const Path &path) +{ + return sgMountSystem.isDirectory(path); +} + +bool IsReadOnly(const Path &path) +{ + return sgMountSystem.isReadOnly(path); +} + +String MakeUniquePath( const char *path, const char *fileName, const char *ext ) +{ + Path filePath; + + filePath.setPath( path ); + filePath.setFileName( fileName ); + filePath.setExtension( ext ); + + for ( U32 i=0; ; i++ ) + { + if ( !IsFile( filePath ) ) + break; + + filePath.setFileName( String::ToString( "%s%d", fileName, i ) ); + } + + return filePath.getFullPath(); +} + +void StartFileChangeNotifications() { sgMountSystem.startFileChangeNotifications(); } +void StopFileChangeNotifications() { sgMountSystem.stopFileChangeNotifications(); } + +S32 GetNumMounts() { return sgMountSystem.getNumMounts(); } +String GetMountRoot( S32 index ) { return sgMountSystem.getMountRoot(index); } +String GetMountPath( S32 index ) { return sgMountSystem.getMountPath(index); } +String GetMountType( S32 index ) { return sgMountSystem.getMountType(index); } + +bool CreatePath(const Path& path) +{ + return sgMountSystem.createPath(path); +} + +} // Namespace FS + +} // Namespace Torque + + diff --git a/core/volume.h b/core/volume.h new file mode 100644 index 0000000..f21d9d6 --- /dev/null +++ b/core/volume.h @@ -0,0 +1,560 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _VOLUME_H_ +#define _VOLUME_H_ + +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif + +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +#ifndef _TORQUE_LIST_ +#include "core/util/tList.h" +#endif + +#ifndef _PATH_H_ +#include "core/util/path.h" +#endif + +#ifndef _TIMECLASS_H_ +#include "core/util/timeClass.h" +#endif + +namespace Torque +{ +namespace FS +{ + +///@defgroup VolumeSystem Volume System +/// Volume access. + +//----------------------------------------------------------------------------- + +/// Base class for all FileIO objects. +/// @ingroup VolumeSystem +class FileBase : public StrongRefBase +{ +public: + virtual ~FileBase() {} +}; + + +//----------------------------------------------------------------------------- + +/// Base class for objects in a FileSystem. +/// This class provides the functionality required by all file system +/// files, which is basically name and attributes. +/// @ingroup VolumeSystem +class FileNode : public FileBase +{ +public: + enum Status + { + Open, ///< In an open state + Closed, ///< In a closed state + EndOfFile, ///< End of file reached + UnknownError, ///< An undetermined error has occurred + FileSystemFull, ///< File system full + NoSuchFile, ///< File or path does not exist + AccessDenied, ///< File access denied + IllegalCall, ///< An unsupported operation was used. + SharingViolation, ///< File being used by another process + NoDisk, ///< No disk or dvd in drive + DriveOpen, ///< Disk or DVD drive open + WrongDisk, ///< Disk or DVD has been swapped + }; + + enum Mode + { + File = 1 << 0, ///< Normal file + Directory = 1 << 1, ///< Directory + System = 1 << 2, ///< OS specific system file + Hidden = 1 << 3, ///< Hidden file or directory + ReadOnly = 1 << 4, ///< Read only + Compressed = 1 << 5, ///< Part of a compressed archive? + Encrypted = 1 << 6, ///< Part of an encrypted archive? + Library = 1 << 7, ///< Dynamic Library + Executable = 1 << 8, ///< Executable file + }; + + struct Attributes + { + U32 flags; ///< File/Directory modes + String name; ///< File/Directory name + Time mtime; ///< Last modified time + Time atime; ///< Last access time + U64 size; + }; + +public: + FileNode(); + + // Properties + virtual Path getName() const = 0; + virtual Status getStatus() const = 0; + + virtual bool getAttributes(Attributes*) = 0; + + // Convenience routines - may be overridden for optimal access + virtual Time getModifiedTime(); ///< @note This will return Time() on failure + virtual U64 getSize(); ///< @note This will return 0 on failure + virtual U32 getChecksum(); ///< @note This will return 0 on failure + +protected: + virtual U32 calculateChecksum() = 0; ///< return 0 on failure + +private: + U32 mChecksum; + Torque::Time mLastChecksum; +}; + +typedef WeakRefPtr FileNodePtr; +typedef StrongRefPtr FileNodeRef; + + +//----------------------------------------------------------------------------- + +/// File object in a FileSystem. +/// File object in a FileSystem. When a file is initially obtained from a +/// FileSystem it is in a closed state. +/// @ingroup VolumeSystem +class File : public FileNode +{ +public: + enum AccessMode + { + Read = 0, ///< Open for read only. + Write = 1, ///< Open for write only. + ReadWrite = 2, ///< Open for read-write. + WriteAppend = 3 ///< Write-only, starting at end of file. + }; + + enum SeekMode + { + Begin, ///< Relative to the start of the file + Current, ///< Relative to the current position + End, ///< Relative to the end of the file + }; + + File(); + virtual ~File(); + + // Properties + virtual U32 getPosition() = 0; + virtual U32 setPosition(U32 pos, SeekMode mode) = 0; + + // Functions + virtual bool open(AccessMode mode) = 0; + virtual bool close() = 0; + + virtual U32 read(void* dst, U32 size) = 0; + virtual U32 write(const void* src, U32 size) = 0; +}; + +typedef WeakRefPtr FilePtr; +typedef StrongRefPtr FileRef; + + +//----------------------------------------------------------------------------- + +/// Directory in a FileSystem. +/// Directory object in a FileSystem. When a directory is initially obtained from a +/// FileSystem it is in a closed state. +/// @ingroup VolumeSystem +class Directory : public FileNode +{ +public: + Directory(); + virtual ~Directory(); + + // Functions + virtual bool open() = 0; + virtual bool close() = 0; + virtual bool read(Attributes*) = 0; +}; + +typedef WeakRefPtr DirectoryPtr; +typedef StrongRefPtr DirectoryRef; + + +//----------------------------------------------------------------------------- + +class FileSystem; + +class FileSystemChangeNotifier +{ +public: + typedef Delegate ChangeDelegate; + typedef Signal ChangeSignal; + +public: + FileSystemChangeNotifier( FileSystem *fs ) + : mFS( fs ), + mNotifying( false ) + { + } + + virtual ~FileSystemChangeNotifier() {} + + /// Adds a file change notification. + /// @see FS::AddChangeNotification + virtual bool addNotification( const Path &path, ChangeDelegate callback ); + + /// Removes an existing file change notification. + /// @see FS::RemoveChangeNotification + virtual bool removeNotification( const Path &path, ChangeDelegate callback ); + + void startNotifier(); + void stopNotifier(); + + /// Returns true if the notifier is enabled and file + /// change notifications will be sent. + bool isNotifying() const { return mNotifying; } + +protected: + struct FileInfo + { + /// The full path to the file. + Path filePath; + + /// The last known modification time. + Time savedLastModTime; + + /// The notifications and reference count. + ChangeSignal signal; + }; + + typedef List FileInfoList; + typedef Map DirMap; ///< map a directory to a list of files and their mod times + + void process(); + + virtual void internalProcessOnce() = 0; + + /// This is called so the inherited class can do its own bookkeeping on addNotification() + /// @note We pass the directory here, not the file + virtual bool internalAddNotification( const Path &dir ) = 0; + + /// This is called so the inherited class can do its own bookkeeping on removeNotification() + /// @note We pass the directory here, not the file + virtual bool internalRemoveNotification( const Path &dir ) = 0; + + /// Called by the inherited class to let us know a directory has changed + /// so we can find the file which changed and notify on it + void internalNotifyDirChanged( const Path &dir ); + + /// Makes sure paths going in and out of the notifier will have the same format + String cleanPath(const Path& dir); + + FileSystem *mFS; + + DirMap mDirMap; + + bool mNotifying; +}; + +//----------------------------------------------------------------------------- + +/// Collection of FileNode objects. +/// File systems represent collections of FileNode objects. Functions are +/// provided for manipulating FileNode objects but the internal organization +/// and representation is opaque. +/// Path names must be fully specified relative to the file system root and +/// names cannot contain relative path information. +/// @ingroup VolumeSystem +class FileSystem : public FileBase +{ +public: + FileSystem(); + virtual ~FileSystem(); + + virtual String getTypeStr() const = 0; ///< Used for describing the file system type + + virtual FileNodeRef resolve(const Path& path) = 0; + virtual FileNodeRef create(const Path& path,FileNode::Mode) = 0; + virtual bool remove(const Path& path) = 0; + virtual bool rename(const Path& a,const Path& b) = 0; + virtual Path mapTo(const Path& path) = 0; + virtual Path mapFrom(const Path& path) = 0; + + /// Returns the file change notifier. + /// @see FS::AddChangeNotification + /// @see FS::RemoveChangeNotification + FileSystemChangeNotifier *getChangeNotifier() { return mChangeNotifier; } + + bool isReadOnly() { return mReadOnly; } + +protected: + FileSystemChangeNotifier *mChangeNotifier; + bool mReadOnly; +}; + +typedef WeakRefPtr FileSystemPtr; +typedef StrongRefPtr FileSystemRef; + + +//----------------------------------------------------------------------------- +///@name File System Access +/// A collection of file systems. +/// @ingroup VolumeSystem +class MountSystem +{ +public: + virtual ~MountSystem() {} + + FileRef createFile(const Path& path); + DirectoryRef createDirectory(const Path& path, FileSystemRef fs = NULL); + virtual bool createPath(const Path& path); + + FileRef openFile(const Path& path,File::AccessMode mode); + DirectoryRef openDirectory(const Path& path); + + bool remove(const Path& path); + + bool rename(const Path& from,const Path& to); + + virtual bool mount(String root, FileSystemRef fs); + virtual bool mount(String root, const Path &path); + virtual FileSystemRef unmount(String root); + virtual bool unmount(FileSystemRef fs); + + bool setCwd(const Path& file); + const Path &getCwd() const; + + FileSystemRef getFileSystem(const Path& path); + bool getFileAttributes(const Path& path,FileNode::Attributes* attr); + FileNodeRef getFileNode(const Path& path); + + bool mapFSPath( const String &inRoot, const Path &inPath, Path &outPath ); + + virtual S32 findByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool includeDirs=false, bool multiMatch = true ); + + bool isFile(const Path &path); + bool isDirectory(const Path &path, FileSystemRef fsRef = NULL); + bool isReadOnly(const Path &path); + + S32 getNumMounts() const { return mMountList.size(); } + String getMountRoot( S32 index ) const { return mMountList[index].root; } + String getMountPath( S32 index ) const { return mMountList[index].fileSystem->mapTo(mMountList[index].path); } + String getMountType( S32 index ) const { return mMountList[index].fileSystem->getTypeStr(); } + + // File system notifications + void startFileChangeNotifications(); + void stopFileChangeNotifications(); + +protected: + virtual void _log(const String& msg); + +protected: + struct MountFS + { + String root; // Root for file system + String path; // File system path + FileSystemRef fileSystem; + }; + + virtual FileSystemRef _removeMountFromList(String root); + virtual FileSystemRef _getFileSystemFromList(const Path& path) const ; + void _setFindByPatternOverrideFS(FileSystemRef fs) { mFindByPatternOverrideFS = fs; } + + Path _normalize(const Path& path); + + Vector mMountList; + Path mCWD; + FileSystemRef mFindByPatternOverrideFS; +}; + +///@name File System Access +/// Functions for mounting file systems and dealing with files and directories. +/// The kernel provides FileSystem mounting, the concept of a current working +/// directory as well as relative paths. +///@{ + +/// Mount file system +///@ingroup VolumeSystem +bool Mount(String root, FileSystemRef fs); + +/// Mount file system redirect +///@ingroup VolumeSystem +bool Mount(String root, const Path &path); + +/// Remove mounted file system. +/// The file system object associated with the given root is unmounted. +/// Open files associated with this file system are unaffected. +///@return The unmounted file system. +///@ingroup VolumeSystem +FileSystemRef Unmount(String root); + +/// Remove mounted file system. +/// Open files associated with this file system are unaffected. +///@return true if the filesystem was successfully unmounted, false otherwise (most likely, the FS was not mounted) +bool Unmount(FileSystemRef fs); + +/// Find the the file system which owns the given file. +///@ingroup VolumeSystem +FileSystemRef GetFileSystem(const Path &file); + +/// Find the file system node for the given file. +///@return Null if the file doesn't exist +///@ingroup VolumeSystem +FileNodeRef GetFileNode(const Path &path); + +/// Adds a file change notification callback. +///@ingroup VolumeSystem +template +inline bool AddChangeNotification( const Path &path, T obj, U func ) +{ + FileSystemRef fs = GetFileSystem( path ); + if ( !fs || !fs->getChangeNotifier() ) + return false; + + FileSystemChangeNotifier::ChangeDelegate dlg( obj, func ); + return fs->getChangeNotifier()->addNotification( path, dlg ); +} + +/// Removes an existing file change notification callback. +///@ingroup VolumeSystem +template +inline bool RemoveChangeNotification( const Path &path, T obj, U func ) +{ + FileSystemRef fs = GetFileSystem( path ); + if ( !fs || !fs->getChangeNotifier() ) + return false; + + FileSystemChangeNotifier::ChangeDelegate dlg( obj, func ); + return fs->getChangeNotifier()->removeNotification( path, dlg ); +} + +/// Map a real file system path to a virtual one based on a root. +/// This is useful when we get a real path back from an OS file dialog for example. +/// e.g. If we have a root "gumby" which points at "C:/foo/bar", +/// MapFSPath("gumby", "C:/foo/bar/blat/picture.png", path ); +/// will map "C:/foo/bar/blat/picture.png" to "gumby:/blat/picture.png" +///@param inRoot The root to check +///@param inPath The real file system path +///@param outPath The resulting volume system path +///@return Success or failure +bool MapFSPath( const String &inRoot, const Path &inPath, Path &outPath ); + +/// Returns a true file system path without virtual mounts. +/// +///@param inPath The path to convert. +///@param outPath The resulting real file system path. +/// +bool GetFSPath( const Path &inPath, Path &outPath ); + +/// Find files matching a pattern starting in a given dir. +///@param inBasePath path to start in +///@param inFilePattern the file pattern [it uses the FindMatch class] +///@param inRecursive do we search recursively? +///@param outList the list of files as Strings [Paths are more expensive to compute, so these may be converted on demand] +///@param multiMatch match against multiple file patterns given in inFilePattern? +///@return number of files which matched +///@ingroup VolumeSystem +S32 FindByPattern( const Path &inBasePath, const String &inFilePattern, bool inRecursive, Vector &outList, bool multiMatch = false ); + +/// Set current working directory. +///@ingroup VolumeSystem +bool SetCwd(const Path &file); + +/// Get the current working directory. +///@ingroup VolumeSystem +const Path& GetCwd(); + +/// Remove (or delete) a file from the file system. +///@ingroup VolumeSystem +bool Remove(const Path &file); + +/// Rename a file or directory. +///@ingroup VolumeSystem +bool Rename(const Path &from, const Path &to); + +/// Get the file attributes. +/// @return success +///@ingroup VolumeSystem +bool GetFileAttributes(const Path &path, FileNode::Attributes *attr); + +/// Compare modified times of p1 & p2 +/// @return -1 if p1 < p2, 0 if p1 == p2, 1 if p1 > p2 +S32 CompareModifiedTimes(const Path& p1, const Path& p2); + +/// Open a file. +/// If the file exists a file object will be returned even if the +/// open operation fails. +///@return Null if the file does not exist +///@ingroup VolumeSystem +FileRef OpenFile(const Path &file, File::AccessMode mode); + +/// Read in an entire file +/// @note Caller is responsible for freeing memory +///@param inPath the file +///@param outData the pointer to return the data +///@param outSize the size of the data returned +///@param inNullTerminate add an extra '\0' byte to the return buffer +///@return successful read? If not, outData will be NULL and outSize will be 0 +bool ReadFile(const Path &inPath, void *&outData, U32 &outSize, bool inNullTerminate = false ); + +/// Open a directory. +/// If the directory exists a directory object will be returned even if the +/// open operation fails. +///@return Null if the file does not exist +///@ingroup VolumeSystem +DirectoryRef OpenDirectory(const Path &file); + +/// Create a file. +/// The file object is returned in a closed state. +///@ingroup VolumeSystem +FileRef CreateFile(const Path &file); + +/// Create a directory. +/// The directory object is returned in a closed state. +///@ingroup VolumeSystem +DirectoryRef CreateDirectory(const Path &file); + +/// Create all the directories in the path if they don't already exist +///@ingroup VolumeSystem +bool CreatePath(const Path &path); + +bool IsReadOnly(const Path &path); +bool IsDirectory(const Path &path); +bool IsFile(const Path &path); +bool VerifyWriteAccess(const Path &path); + +/// This returns a unique file path from the components +/// by appending numbers to the end of the file name if +/// a file with the same name already exists. +/// +/// @param path The root and directory for the file. +/// @param fileName The file name without extension. +/// @param ext The dot-less extension. +String MakeUniquePath( const char *path, const char *fileName, const char *ext ); + +void StartFileChangeNotifications(); +void StopFileChangeNotifications(); + +S32 GetNumMounts(); +String GetMountRoot( S32 index ); +String GetMountPath( S32 index ); +String GetMountType( S32 index ); + +///@} + +} // Namespace FS +} // Namespace Torque + +#endif + diff --git a/environment/basicClouds.cpp b/environment/basicClouds.cpp new file mode 100644 index 0000000..3d3424d --- /dev/null +++ b/environment/basicClouds.cpp @@ -0,0 +1,382 @@ + +#include "platform/platform.h" +#include "platform/profiler.h" +#include "console/consoleTypes.h" +#include "basicClouds.h" + +#include "gfx/gfxTransformSaver.h" +#include "core/stream/fileStream.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "materials/shaderData.h" +#include "math/mathIO.h" + + +U32 BasicClouds::smVertStride = 50; +U32 BasicClouds::smStrideMinusOne = 49; +U32 BasicClouds::smVertCount = 50 * 50; +U32 BasicClouds::smTriangleCount = smStrideMinusOne * smStrideMinusOne * 2; + +BasicClouds::BasicClouds() +{ + mTypeMask |= EnvironmentObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mLayerEnabled[0] = true; + mLayerEnabled[1] = true; + mLayerEnabled[2] = true; + + // Default textures are assigned by the ObjectBuilderGui. + //mTexName[0] = "art/skies/clouds/cloud1"; + //mTexName[1] = "art/skies/clouds/cloud2"; + //mTexName[2] = "art/skies/clouds/cloud3"; + + mHeight[0] = 4.0f; + mHeight[1] = 3.0f; + mHeight[2] = 2.0f; + + mTexSpeed[0] = 0.0005f; + mTexSpeed[1] = 0.001f; + mTexSpeed[2] = 0.0003f; + + mTexScale[0] = 1.0; + mTexScale[1] = 1.0; + mTexScale[2] = 1.0; + + mTexDirection[0].set( 1.0f, 0.0f ); + mTexDirection[1].set( 1.0f, 0.0f ); + mTexDirection[2].set( 1.0f, 0.0f ); + + mTexOffset[0].set( 0.5f, 0.5f ); + mTexOffset[1].set( 0.5f, 0.5f ); + mTexOffset[2].set( 0.5f, 0.5f ); +} + +IMPLEMENT_CO_NETOBJECT_V1( BasicClouds ); + +// ConsoleObject... + + +bool BasicClouds::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + setGlobalBounds(); + resetWorldBox(); + + addToScene(); + + if ( isClientObject() ) + { + _initTexture(); + _initBuffers(); + + // Find ShaderData + ShaderData *shaderData; + mShader = Sim::findObject( "BasicCloudsShader", shaderData ) ? shaderData->getShader() : NULL; + if ( !mShader ) + { + Con::errorf( "BasicClouds::onAdd - could not find BasicCloudsShader" ); + return false; + } + + // Create ShaderConstBuffer and Handles + mShaderConsts = mShader->allocConstBuffer(); + mModelViewProjSC = mShader->getShaderConstHandle( "$modelView" ); + mTimeSC = mShader->getShaderConstHandle( "$accumTime" ); + mTexScaleSC = mShader->getShaderConstHandle( "$texScale" ); + mTexDirectionSC = mShader->getShaderConstHandle( "$texDirection" ); + mTexOffsetSC = mShader->getShaderConstHandle( "$texOffset" ); + + // Create StateBlocks + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setBlend( true ); + desc.setZReadWrite( false, false ); + desc.samplersDefined = true; + desc.samplers[0].addressModeU = GFXAddressWrap; + desc.samplers[0].addressModeV = GFXAddressWrap; + desc.samplers[0].addressModeW = GFXAddressWrap; + desc.samplers[0].magFilter = GFXTextureFilterLinear; + desc.samplers[0].minFilter = GFXTextureFilterLinear; + desc.samplers[0].mipFilter = GFXTextureFilterLinear; + desc.samplers[0].textureColorOp = GFXTOPModulate; + + mStateblock = GFX->createStateBlock( desc ); + } + + return true; +} + +void BasicClouds::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +void BasicClouds::initPersistFields() +{ + /* + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + addGroup( String::ToString( "Layer%i", i ) ); + + addField( String::ToString("enabled[%i]",i), TypeBool, Offset( mLayerEnabled[i], BasicClouds ) ); + addField( String::ToString("texture[%i]",i), TypeImageFilename, Offset( mTexName[i], BasicClouds ) ); + addField( String::ToString("texScale[%i]",i), TypeF32, Offset( mTexScale[i], BasicClouds ) ); + addField( String::ToString("texDirection[%i]",i), TypePoint2F, Offset( mTexDirection[i], BasicClouds ) ); + addField( String::ToString("texSpeed[%i]",i), TypeF32, Offset( mTexSpeed[i], BasicClouds ) ); + addField( String::ToString("texOffset[%i]",i), TypePoint2F, Offset( mTexOffset[i], BasicClouds ) ); + addField( String::ToString("height[%i]",i), TypeF32, Offset( mHeight[i], BasicClouds ) ); + + endGroup( String::ToString( "Layer%i", i ) ); + } + */ + + addGroup( "BasicClouds" ); + + addArray( "Layers", TEX_COUNT ); + addField( "layerEnabled", TypeBool, Offset( mLayerEnabled, BasicClouds ), TEX_COUNT ); + addField( "texture", TypeImageFilename, Offset( mTexName, BasicClouds ), TEX_COUNT ); + addField( "texScale", TypeF32, Offset( mTexScale, BasicClouds ), TEX_COUNT ); + addField( "texDirection", TypePoint2F, Offset( mTexDirection, BasicClouds ), TEX_COUNT ); + addField( "texSpeed", TypeF32, Offset( mTexSpeed, BasicClouds ), TEX_COUNT ); + addField( "texOffset", TypePoint2F, Offset( mTexOffset, BasicClouds ), TEX_COUNT ); + addField( "height", TypeF32, Offset( mHeight, BasicClouds ), TEX_COUNT ); + endArray( "Layers" ); + + endGroup( "BasicClouds" ); + + Parent::initPersistFields(); +} + +void BasicClouds::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits( BasicCloudsMask ); +} + + +// NetObject... + + +U32 BasicClouds::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + stream->writeFlag( mLayerEnabled[i] ); + + stream->write( mTexName[i] ); + + stream->write( mTexScale[i] ); + mathWrite( *stream, mTexDirection[i] ); + stream->write( mTexSpeed[i] ); + mathWrite( *stream, mTexOffset[i] ); + + stream->write( mHeight[i] ); + } + + return retMask; +} + +void BasicClouds::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + mLayerEnabled[i] = stream->readFlag(); + + stream->read( &mTexName[i] ); + + stream->read( &mTexScale[i] ); + mathRead( *stream, &mTexDirection[i] ); + stream->read( &mTexSpeed[i] ); + mathRead( *stream, &mTexOffset[i] ); + + stream->read( &mHeight[i] ); + } + + if ( isProperlyAdded() ) + { + // We could check if the height or texture have actually changed. + _initBuffers(); + _initTexture(); + } +} + + +// SceneObject... + + +bool BasicClouds::prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState ) +{ + PROFILE_SCOPE( BasicClouds_prepRenderImage ); + + bool isEnabled = false; + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + if ( mLayerEnabled[i] ) + { + isEnabled = true; + break; + } + } + + if ( !isEnabled ) + return false; + + if ( isLastState( state, stateKey ) ) + return false; + + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if ( state->isObjectRendered( this ) ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &BasicClouds::renderObject ); + ri->type = RenderPassManager::RIT_Sky; + ri->defaultKey = 0; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void BasicClouds::renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *mi ) +{ + GFXTransformSaver saver; + + Point3F camPos = state->getCameraPosition(); + MatrixF xfm(true); + xfm.setPosition(camPos); + GFX->multWorld(xfm); + + if ( state->isReflectPass() ) + GFX->setProjectionMatrix( gClientSceneGraph->getNonClipProjection() ); + + GFX->setShader( mShader ); + GFX->setShaderConstBuffer( mShaderConsts ); + GFX->setStateBlock( mStateblock ); + + MatrixF xform(GFX->getProjectionMatrix()); + xform *= GFX->getViewMatrix(); + xform *= GFX->getWorldMatrix(); + + mShaderConsts->set( mModelViewProjSC, xform ); + mShaderConsts->set( mTimeSC, (F32)Sim::getCurrentTime() / 1000.0f ); + GFX->setPrimitiveBuffer( mPB ); + + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + if ( !mLayerEnabled[i] ) + continue; + + mShaderConsts->set( mTexScaleSC, mTexScale[i] ); + mShaderConsts->set( mTexDirectionSC, mTexDirection[i] * mTexSpeed[i] ); + mShaderConsts->set( mTexOffsetSC, mTexOffset[i] ); + + GFX->setTexture( 0, mTexture[i] ); + GFX->setVertexBuffer( mVB[i] ); + + GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, smVertCount, 0, smTriangleCount ); + } +} + + +// BasicClouds Internal Methods.... + + +void BasicClouds::_initTexture() +{ + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + if ( !mLayerEnabled[i] ) + { + mTexture[i] = NULL; + continue; + } + + if ( mTexName[i].isNotEmpty() ) + mTexture[i].set( mTexName[i], &GFXDefaultStaticDiffuseProfile, "BasicClouds" ); + + if ( mTexture[i].isNull() ) + mTexture[i].set( "core/art/warnmat", &GFXDefaultStaticDiffuseProfile, "BasicClouds" ); + } +} + +void BasicClouds::_initBuffers() +{ + // Primitive Buffer... Is shared for all Layers. + + mPB.set( GFX, smTriangleCount * 3, smTriangleCount, GFXBufferTypeStatic ); + + U16 *pIdx = NULL; + mPB.lock(&pIdx); + U32 curIdx = 0; + + for ( U32 y = 0; y < smStrideMinusOne; y++ ) + { + for ( U32 x = 0; x < smStrideMinusOne; x++ ) + { + U32 offset = x + y * smVertStride; + + pIdx[curIdx] = offset; + curIdx++; + pIdx[curIdx] = offset + 1; + curIdx++; + pIdx[curIdx] = offset + smVertStride + 1; + curIdx++; + + pIdx[curIdx] = offset; + curIdx++; + pIdx[curIdx] = offset + smVertStride + 1; + curIdx++; + pIdx[curIdx] = offset + smVertStride; + curIdx++; + } + } + + mPB.unlock(); + + // Vertex Buffer... + // Each layer has their own so they can be at different heights. + + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + Point3F vertScale( 16.0f, 16.0f, mHeight[i] ); + F32 zOffset = -( mCos( mSqrt( 1.0f ) ) + 0.01f ); + + mVB[i].set( GFX, smVertCount, GFXBufferTypeStatic ); + GFXVertexPT *pVert = mVB[i].lock(); + + for ( U32 y = 0; y < smVertStride; y++ ) + { + F32 v = ( (F32)y / (F32)smStrideMinusOne - 0.5f ) * 2.0f; + + for ( U32 x = 0; x < smVertStride; x++ ) + { + F32 u = ( (F32)x / (F32)smStrideMinusOne - 0.5f ) * 2.0f; + + F32 sx = u; + F32 sy = v; + F32 sz = mCos( mSqrt( sx*sx + sy*sy ) ) + zOffset; + + pVert->point.set( sx, sy, sz ); + pVert->point *= vertScale; + pVert->texCoord.set( u, v ); + pVert++; + } + } + + mVB[i].unlock(); + } +} \ No newline at end of file diff --git a/environment/basicClouds.h b/environment/basicClouds.h new file mode 100644 index 0000000..663f226 --- /dev/null +++ b/environment/basicClouds.h @@ -0,0 +1,96 @@ + +#ifndef _BASICCLOUDS_H_ +#define _BASICCLOUDS_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + + +class BasicClouds : public SceneObject +{ + typedef SceneObject Parent; + + enum + { + BasicCloudsMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1, + }; + + #define TEX_COUNT 3 + +public: + + BasicClouds(); + virtual ~BasicClouds() {} + + DECLARE_CONOBJECT( BasicClouds ); + + // ConsoleObject + virtual bool onAdd(); + virtual void onRemove(); + static void initPersistFields(); + virtual void inspectPostApply(); + + // NetObject + virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + virtual void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false); + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *mi ); + +protected: + + void _initTexture(); + void _initBuffers(); + void _initBuffer( F32 height, GFXVertexBufferHandle *vb, GFXPrimitiveBufferHandle *pb ); + +protected: + + static U32 smVertStride; + static U32 smStrideMinusOne; + static U32 smVertCount; + static U32 smTriangleCount; + + GFXTexHandle mTexture[TEX_COUNT]; + + GFXStateBlockRef mStateblock; + + GFXShaderRef mShader; + + GFXShaderConstBufferRef mShaderConsts; + GFXShaderConstHandle *mTimeSC; + GFXShaderConstHandle *mModelViewProjSC; + GFXShaderConstHandle *mTexScaleSC; + GFXShaderConstHandle *mTexDirectionSC; + GFXShaderConstHandle *mTexOffsetSC; + + GFXVertexBufferHandle mVB[TEX_COUNT]; + GFXPrimitiveBufferHandle mPB; + + // Fields... + + bool mLayerEnabled[TEX_COUNT]; + String mTexName[TEX_COUNT]; + F32 mTexScale[TEX_COUNT]; + Point2F mTexDirection[TEX_COUNT]; + F32 mTexSpeed[TEX_COUNT]; + Point2F mTexOffset[TEX_COUNT]; + F32 mHeight[TEX_COUNT]; +}; + + +#endif // _BASICCLOUDS_H_ \ No newline at end of file diff --git a/environment/cloudLayer.cpp b/environment/cloudLayer.cpp new file mode 100644 index 0000000..499660c --- /dev/null +++ b/environment/cloudLayer.cpp @@ -0,0 +1,434 @@ + +#include "platform/platform.h" +#include "platform/profiler.h" +#include "console/consoleTypes.h" +#include "cloudLayer.h" + +#include "gfx/gfxTransformSaver.h" +#include "core/stream/fileStream.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/primBuilder.h" +#include "materials/materialManager.h" +#include "materials/customMaterialDefinition.h" +#include "materials/shaderData.h" +#include "T3D/sphere.h" +#include "lighting/lightInfo.h" +#include "math/mathIO.h" + + +GFXImplementVertexFormat( GFXCloudVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::BINORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TANGENT, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +} + +U32 CloudLayer::smVertStride = 50; +U32 CloudLayer::smStrideMinusOne = smVertStride - 1; +U32 CloudLayer::smVertCount = smVertStride * smVertStride; +U32 CloudLayer::smTriangleCount = smStrideMinusOne * smStrideMinusOne * 2; + +CloudLayer::CloudLayer() +: mBaseColor( 0.9f, 0.9f, 0.9f, 1.0f ), + mCoverage( 0.5f ), + mWindSpeed( 1.0f ), + mLastTime( 0 ) +{ + mTypeMask |= EnvironmentObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mTexScale[0] = 1.0; + mTexScale[1] = 1.0; + mTexScale[2] = 1.0; + + mTexDirection[0].set( 1.0f, 0.0f ); + mTexDirection[1].set( 0.0f, 1.0f ); + mTexDirection[2].set( 0.5f, 0.0f ); + + mTexSpeed[0] = 0.005f; + mTexSpeed[1] = 0.005f; + mTexSpeed[2] = 0.005f; + + mTexOffset[0] = mTexOffset[1] = mTexOffset[2] = Point2F::Zero; + + mHeight = 4.0f; +} + +IMPLEMENT_CO_NETOBJECT_V1( CloudLayer ); + +// ConsoleObject... + + +bool CloudLayer::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + setGlobalBounds(); + resetWorldBox(); + + addToScene(); + + if ( isClientObject() ) + { + _initTexture(); + _initBuffers(); + + // Find ShaderData + ShaderData *shaderData; + mShader = Sim::findObject( "CloudLayerShader", shaderData ) ? + shaderData->getShader() : NULL; + if ( !mShader ) + { + Con::errorf( "CloudLayer::onAdd - could not find CloudLayerShader" ); + return false; + } + + // Create ShaderConstBuffer and Handles + mShaderConsts = mShader->allocConstBuffer(); + mModelViewProjSC = mShader->getShaderConstHandle( "$modelView" ); + mEyePosWorldSC = mShader->getShaderConstHandle( "$eyePosWorld" ); + mSunVecSC = mShader->getShaderConstHandle( "$sunVec" ); + mTexOffsetSC[0] = mShader->getShaderConstHandle( "$texOffset0" ); + mTexOffsetSC[1] = mShader->getShaderConstHandle( "$texOffset1" ); + mTexOffsetSC[2] = mShader->getShaderConstHandle( "$texOffset2" ); + mTexScaleSC = mShader->getShaderConstHandle( "$texScale" ); + mAmbientColorSC = mShader->getShaderConstHandle( "$ambientColor" ); + mSunColorSC = mShader->getShaderConstHandle( "$sunColor" ); + mCoverageSC = mShader->getShaderConstHandle( "$cloudCoverage" ); + mBaseColorSC = mShader->getShaderConstHandle( "$cloudBaseColor" ); + + // Create StateBlocks + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setBlend( true ); + desc.setZReadWrite( false, false ); + desc.samplersDefined = true; + desc.samplers[0].addressModeU = GFXAddressWrap; + desc.samplers[0].addressModeV = GFXAddressWrap; + desc.samplers[0].addressModeW = GFXAddressWrap; + desc.samplers[0].magFilter = GFXTextureFilterLinear; + desc.samplers[0].minFilter = GFXTextureFilterLinear; + desc.samplers[0].mipFilter = GFXTextureFilterLinear; + desc.samplers[0].textureColorOp = GFXTOPModulate; + + mStateblock = GFX->createStateBlock( desc ); + } + + return true; +} + +void CloudLayer::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +void CloudLayer::initPersistFields() +{ + addGroup( "CloudLayer" ); + + addField( "texture", TypeImageFilename, Offset( mTextureName, CloudLayer ) ); + + addArray( "Textures", TEX_COUNT ); + addField( "texScale", TypeF32, Offset( mTexScale, CloudLayer ), TEX_COUNT ); + addField( "texDirection", TypePoint2F, Offset( mTexDirection, CloudLayer ), TEX_COUNT ); + addField( "texSpeed", TypeF32, Offset( mTexSpeed, CloudLayer ), TEX_COUNT ); + //addField( "texStartOffset", TypePoint2F, Offset( mTexStartOffset, CloudLayer), TEX_COUNT ); + endArray( "Textures" ); + + addField( "baseColor", TypeColorF, Offset( mBaseColor, CloudLayer ) ); + addField( "coverage", TypeF32, Offset( mCoverage, CloudLayer ) ); + addField( "windSpeed", TypeF32, Offset( mWindSpeed, CloudLayer ) ); + addField( "height", TypeF32, Offset( mHeight, CloudLayer ) ); + + endGroup( "CloudLayer" ); + + Parent::initPersistFields(); +} + +void CloudLayer::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits( CloudLayerMask ); +} + + +// NetObject... + + +U32 CloudLayer::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + stream->write( mTextureName ); + + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + stream->write( mTexScale[i] ); + stream->write( mTexSpeed[i] ); + mathWrite( *stream, mTexDirection[i] ); + } + + stream->write( mBaseColor ); + stream->write( mCoverage ); + stream->write( mWindSpeed ); + stream->write( mHeight ); + + return retMask; +} + +void CloudLayer::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + String oldTextureName = mTextureName; + stream->read( &mTextureName ); + + for ( U32 i = 0; i < TEX_COUNT; i++ ) + { + stream->read( &mTexScale[i] ); + stream->read( &mTexSpeed[i] ); + mathRead( *stream, &mTexDirection[i] ); + } + + stream->read( &mBaseColor ); + + F32 oldCoverage = mCoverage; + stream->read( &mCoverage ); + + stream->read( &mWindSpeed ); + + F32 oldHeight = mHeight; + stream->read( &mHeight ); + + if ( isProperlyAdded() ) + { + if ( ( oldTextureName != mTextureName ) || ( ( oldCoverage == 0.0f ) != ( mCoverage == 0.0f ) ) ) + _initTexture(); + if ( oldHeight != mHeight ) + _initBuffers(); + } +} + + +// SceneObject... + + +bool CloudLayer::prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState ) +{ + PROFILE_SCOPE( CloudLayer_prepRenderImage ); + + if ( mCoverage <= 0.0f ) + return false; + + if ( isLastState( state, stateKey ) ) + return false; + + setLastState(state, stateKey); + + if ( state->isDiffusePass() ) + { + // Scroll textures... + + U32 time = Sim::getCurrentTime(); + F32 delta = (F32)( time - mLastTime ) / 1000.0f; + mLastTime = time; + + for ( U32 i = 0; i < 3; i++ ) + { + mTexOffset[i] += mTexDirection[i] * mTexSpeed[i] * delta * mWindSpeed; + } + } + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if ( state->isObjectRendered( this ) ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &CloudLayer::renderObject ); + ri->type = RenderPassManager::RIT_Sky; + ri->defaultKey = 0; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void CloudLayer::renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *mi ) +{ + GFXTransformSaver saver; + + Point3F camPos = state->getCameraPosition(); + MatrixF xfm(true); + xfm.setPosition(camPos); + GFX->multWorld(xfm); + + if ( state->isReflectPass() ) + GFX->setProjectionMatrix( gClientSceneGraph->getNonClipProjection() ); + + GFX->setShader( mShader ); + GFX->setShaderConstBuffer( mShaderConsts ); + GFX->setStateBlock( mStateblock ); + + // Set all the shader consts... + + MatrixF xform(GFX->getProjectionMatrix()); + xform *= GFX->getViewMatrix(); + xform *= GFX->getWorldMatrix(); + + mShaderConsts->set( mModelViewProjSC, xform ); + + LightInfo *lightinfo = gClientSceneGraph->getLightManager()->getSpecialLight(LightManager::slSunLightType); + const ColorF &sunlight = lightinfo->getAmbient(); + + Point3F ambientColor( sunlight.red, sunlight.green, sunlight.blue ); + mShaderConsts->set( mAmbientColorSC, ambientColor ); + + const ColorF &sunColor = lightinfo->getColor(); + Point3F data( sunColor.red, sunColor.green, sunColor.blue ); + mShaderConsts->set( mSunColorSC, data ); + + mShaderConsts->set( mSunVecSC, lightinfo->getDirection() ); + + for ( U32 i = 0; i < TEX_COUNT; i++ ) + mShaderConsts->set( mTexOffsetSC[i], mTexOffset[i] ); + + Point3F scale( mTexScale[0], mTexScale[1], mTexScale[2] ); + mShaderConsts->set( mTexScaleSC, scale ); + + Point3F color; + color.set( mBaseColor.red, mBaseColor.green, mBaseColor.blue ); + mShaderConsts->set( mBaseColorSC, color ); + + mShaderConsts->set( mCoverageSC, mCoverage ); + + GFX->setTexture( 0, mTexture ); + GFX->setVertexBuffer( mVB ); + GFX->setPrimitiveBuffer( mPB ); + + GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, smVertCount, 0, smTriangleCount ); +} + + +// CloudLayer Internal Methods.... + + +void CloudLayer::_initTexture() +{ + if ( mCoverage <= 0.0f ) + { + mTexture = NULL; + return; + } + + if ( mTextureName.isNotEmpty() ) + mTexture.set( mTextureName, &GFXDefaultStaticDiffuseProfile, "CloudLayer" ); + + if ( mTexture.isNull() ) + mTexture.set( "core/art/warnmat", &GFXDefaultStaticDiffuseProfile, "CloudLayer" ); +} + +void CloudLayer::_initBuffers() +{ + // Vertex Buffer... + + Point3F vertScale( 16.0f, 16.0f, mHeight ); + F32 zOffset = -( mCos( mSqrt( 1.0f ) ) + 0.01f ); + + mVB.set( GFX, smVertCount, GFXBufferTypeStatic ); + GFXCloudVertex *pVert = mVB.lock(); + + for ( U32 y = 0; y < smVertStride; y++ ) + { + F32 v = ( (F32)y / (F32)smStrideMinusOne - 0.5f ) * 2.0f; + + for ( U32 x = 0; x < smVertStride; x++ ) + { + F32 u = ( (F32)x / (F32)smStrideMinusOne - 0.5f ) * 2.0f; + + F32 sx = u; + F32 sy = v; + F32 sz = mCos( mSqrt( sx*sx + sy*sy ) ) + zOffset; + //F32 sz = 1.0f; + pVert->point.set( sx, sy, sz ); + pVert->point *= vertScale; + + // The vert to our right. + Point3F rpnt; + + F32 ru = ( (F32)( x + 1 ) / (F32)smStrideMinusOne - 0.5f ) * 2.0f; + F32 rv = v; + + rpnt.x = ru; + rpnt.y = rv; + rpnt.z = mCos( mSqrt( rpnt.x*rpnt.x + rpnt.y*rpnt.y ) ) + zOffset; + rpnt *= vertScale; + + // The vert to our front. + Point3F fpnt; + + F32 fu = u; + F32 fv = ( (F32)( y + 1 ) / (F32)smStrideMinusOne - 0.5f ) * 2.0f; + + fpnt.x = fu; + fpnt.y = fv; + fpnt.z = mCos( mSqrt( fpnt.x*fpnt.x + fpnt.y*fpnt.y ) ) + zOffset; + fpnt *= vertScale; + + Point3F fvec = fpnt - pVert->point; + fvec.normalize(); + + Point3F rvec = rpnt - pVert->point; + rvec.normalize(); + + pVert->normal = mCross( fvec, rvec ); + pVert->normal.normalize(); + pVert->binormal = fvec; + pVert->tangent = rvec; + pVert->texCoord.set( u, v ); + pVert++; + } + } + + mVB.unlock(); + + + // Primitive Buffer... + + mPB.set( GFX, smTriangleCount * 3, smTriangleCount, GFXBufferTypeStatic ); + + U16 *pIdx = NULL; + mPB.lock(&pIdx); + U32 curIdx = 0; + + for ( U32 y = 0; y < smStrideMinusOne; y++ ) + { + for ( U32 x = 0; x < smStrideMinusOne; x++ ) + { + U32 offset = x + y * smVertStride; + + pIdx[curIdx] = offset; + curIdx++; + pIdx[curIdx] = offset + 1; + curIdx++; + pIdx[curIdx] = offset + smVertStride + 1; + curIdx++; + + pIdx[curIdx] = offset; + curIdx++; + pIdx[curIdx] = offset + smVertStride + 1; + curIdx++; + pIdx[curIdx] = offset + smVertStride; + curIdx++; + } + } + + mPB.unlock(); +} \ No newline at end of file diff --git a/environment/cloudLayer.h b/environment/cloudLayer.h new file mode 100644 index 0000000..e7f568d --- /dev/null +++ b/environment/cloudLayer.h @@ -0,0 +1,112 @@ + +#ifndef _CLOUDLAYER_H_ +#define _CLOUDLAYER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _MATINSTANCE_H_ +#include "materials/matInstance.h" +#endif + +GFXDeclareVertexFormat( GFXCloudVertex ) +{ + Point3F point; + Point3F normal; + Point3F binormal; + Point3F tangent; + Point2F texCoord; +}; + +class CloudLayer : public SceneObject +{ + typedef SceneObject Parent; + + enum + { + CloudLayerMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1, + }; + + #define TEX_COUNT 3 + +public: + + CloudLayer(); + virtual ~CloudLayer() {} + + DECLARE_CONOBJECT( CloudLayer ); + + // ConsoleObject + virtual bool onAdd(); + virtual void onRemove(); + static void initPersistFields(); + virtual void inspectPostApply(); + + // NetObject + virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + virtual void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false); + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *mi ); + +protected: + + void _initTexture(); + void _initBuffers(); + +protected: + + static U32 smVertStride; + static U32 smStrideMinusOne; + static U32 smVertCount; + static U32 smTriangleCount; + + GFXTexHandle mTexture; + + GFXShaderRef mShader; + + GFXStateBlockRef mStateblock; + + GFXShaderConstBufferRef mShaderConsts; + GFXShaderConstHandle *mModelViewProjSC; + GFXShaderConstHandle *mAmbientColorSC; + GFXShaderConstHandle *mSunColorSC; + GFXShaderConstHandle *mSunVecSC; + GFXShaderConstHandle *mTexOffsetSC[3]; + GFXShaderConstHandle *mTexScaleSC; + GFXShaderConstHandle *mBaseColorSC; + GFXShaderConstHandle *mCoverageSC; + GFXShaderConstHandle *mEyePosWorldSC; + + GFXVertexBufferHandle mVB; + GFXPrimitiveBufferHandle mPB; + + Point2F mTexOffset[3]; + U32 mLastTime; + + // Fields... + + String mTextureName; + F32 mTexScale[TEX_COUNT]; + Point2F mTexDirection[TEX_COUNT]; + F32 mTexSpeed[TEX_COUNT]; + + ColorF mBaseColor; + F32 mCoverage; + F32 mWindSpeed; + F32 mHeight; +}; + + +#endif // _CLOUDLAYER_H_ \ No newline at end of file diff --git a/environment/decalRoad.cpp b/environment/decalRoad.cpp new file mode 100644 index 0000000..70264dc --- /dev/null +++ b/environment/decalRoad.cpp @@ -0,0 +1,1409 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/decalRoad.h" + +#include "console/consoleTypes.h" +#include "util/catmullRom.h" +#include "math/util/quadTransforms.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "core/stream/bitStream.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "math/mathIO.h" +#include "math/mathUtils.h" +#include "terrain/terrData.h" +#include "materials/materialDefinition.h" +#include "materials/materialManager.h" + + +extern F32 gDecalBias; +extern bool gEditingMission; + + +void DecalRoadUpdateEvent::process( SimObject *object ) +{ + DecalRoad *road = dynamic_cast( object ); + AssertFatal( road, "DecalRoadRegenEvent::process - wasn't a DecalRoad" ); + + // Inform clients to perform the update. + road->setMaskBits( mMask ); + + if ( !road->isProperlyAdded() ) + return; + + // Perform the server side update. + if ( mMask & DecalRoad::TerrainChangedMask ) + { + road->_generateEdges(); + } + if ( mMask & DecalRoad::GenEdgesMask ) + { + // Server has already done this. + //road->_generateEdges(); + } + if ( mMask & DecalRoad::ReClipMask ) + { + // Server does not need to capture verts. + road->_captureVerts(); + } +} + + +//------------------------------------------------------------------------------ +// Class: DecalRoad +//------------------------------------------------------------------------------ + +// Init Statics + +// Static ConsoleVars for toggling debug rendering +bool DecalRoad::smEditorOpen = false; +bool DecalRoad::smWireframe = true; +bool DecalRoad::smShowBatches = false; +bool DecalRoad::smDiscardAll = false; +bool DecalRoad::smShowSpline = true; +bool DecalRoad::smShowRoad = true; +S32 DecalRoad::smUpdateDelay = 500; + +SimObjectPtr DecalRoad::smServerDecalRoadSet = NULL; + + +// Constructors + +DecalRoad::DecalRoad() + : mLoadRenderData( true ), + mBreakAngle( 3.0f ), + mSegmentsPerBatch( 10 ), + mTextureLength( 5.0f ), + mRenderPriority( 10 ), + mMaterial( NULL ), + mMatInst( NULL ), + mUpdateEventId( -1 ), + mTerrainUpdateRect( Box3F::Invalid ) +{ + // Setup NetObject. + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType; + mNetFlags.set(Ghostable); +} + +DecalRoad::~DecalRoad() +{ +} + +IMPLEMENT_CO_NETOBJECT_V1(DecalRoad); + + +// ConsoleObject + +void DecalRoad::initPersistFields() +{ + addGroup( "DecalRoad" ); + + addField( "material", TypeMaterialName, Offset( mMaterialName, DecalRoad ) ); + addProtectedField( "textureLength", TypeF32, Offset( mTextureLength, DecalRoad ), &DecalRoad::ptSetTextureLength, &defaultProtectedGetFn, "" ); + addProtectedField( "breakAngle", TypeF32, Offset( mBreakAngle, DecalRoad ), &DecalRoad::ptSetBreakAngle, &defaultProtectedGetFn, + "Angle in degrees - DecalRoad will subdivided the spline if its curve is greater than this threshold." ); + addField( "renderPriority", TypeS32, Offset( mRenderPriority, DecalRoad ), "DecalRoad(s) are rendered in descending renderPriority order" ); + + endGroup( "DecalRoad" ); + + addGroup( "Internal" ); + + addProtectedField( "node", TypeString, NULL, &addNodeFromField, &emptyStringProtectedGetFn, "" ); + + endGroup( "Internal" ); + + Parent::initPersistFields(); +} + +void DecalRoad::consoleInit() +{ + Parent::consoleInit(); + + // Vars for debug rendering while the RoadEditor is open, only used if smEditorOpen is true. + Con::addVariable( "$DecalRoad::EditorOpen", TypeBool, &DecalRoad::smEditorOpen ); + Con::addVariable( "$DecalRoad::wireframe", TypeBool, &DecalRoad::smWireframe ); + Con::addVariable( "$DecalRoad::showBatches", TypeBool, &DecalRoad::smShowBatches ); + Con::addVariable( "$DecalRoad::discardAll", TypeBool, &DecalRoad::smDiscardAll ); + Con::addVariable( "$DecalRoad::showSpline", TypeBool, &DecalRoad::smShowSpline ); + Con::addVariable( "$DecalRoad::showRoad", TypeBool, &DecalRoad::smShowRoad ); + Con::addVariable( "$DecalRoad::updateDelay", TypeS32, &DecalRoad::smUpdateDelay ); +} + + +// SimObject + +bool DecalRoad::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // DecalRoad is at position zero when created, + // it sets its own position to the first node inside + // _generateEdges but until it has at least one node + // it will be at 0,0,0. + + MatrixF mat(true); + Parent::setTransform( mat ); + + // The client side calculates bounds based on clipped geometry. It would + // be wasteful for the server to do this so the server uses global bounds. + if ( isServerObject() ) + { + setGlobalBounds(); + resetWorldBox(); + } + + // Set the Render Transform. + setRenderTransform(mObjToWorld); + + // Add to Scene. + addToScene(); + + if ( isServerObject() ) + getServerSet()->addObject( this ); + + // + TerrainBlock::smUpdateSignal.notify( this, &DecalRoad::_onTerrainChanged ); + + // + if ( isClientObject() ) + _initMaterial(); + + _generateEdges(); + _captureVerts(); + + return true; +} + +void DecalRoad::onRemove() +{ + SAFE_DELETE( mMatInst ); + + TerrainBlock::smUpdateSignal.remove( this, &DecalRoad::_onTerrainChanged ); + + removeFromScene(); + + Parent::onRemove(); +} + +void DecalRoad::inspectPostApply() +{ + Parent::inspectPostApply(); + + setMaskBits( DecalRoadMask ); +} + +void DecalRoad::onStaticModified( const char* slotName, const char*newValue ) +{ + Parent::onStaticModified( slotName, newValue ); + + /* + if ( isProperlyAdded() && + dStricmp( slotName, "material" ) == 0 ) + { + setMaskBits( DecalRoadMask ); + } + */ + + if ( dStricmp( slotName, "renderPriority" ) == 0 ) + { + mRenderPriority = getMax( dAtoi(newValue), (S32)1 ); + } +} + +SimSet* DecalRoad::getServerSet() +{ + if ( !smServerDecalRoadSet ) + { + smServerDecalRoadSet = new SimSet(); + smServerDecalRoadSet->registerObject( "ServerDecalRoadSet" ); + Sim::getRootGroup()->addObject( smServerDecalRoadSet ); + } + + return smServerDecalRoadSet; +} + +void DecalRoad::writeFields( Stream &stream, U32 tabStop ) +{ + Parent::writeFields( stream, tabStop ); + + // Now write all nodes + + stream.write(2, "\r\n"); + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + const RoadNode &node = mNodes[i]; + + stream.writeTabs(tabStop); + + char buffer[1024]; + dMemset( buffer, 0, 1024 ); + dSprintf( buffer, 1024, "Node = \"%f %f %f %f\";", node.point.x, node.point.y, node.point.z, node.width ); + stream.writeLine( (const U8*)buffer ); + } +} + +bool DecalRoad::writeField( StringTableEntry fieldname, const char *value ) +{ + if ( fieldname == StringTable->insert("node") ) + return false; + + return Parent::writeField( fieldname, value ); +} + +void DecalRoad::onEditorEnable() +{ +} + +void DecalRoad::onEditorDisable() +{ +} + + +// NetObject + +U32 DecalRoad::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + // Pack Parent. + U32 retMask = Parent::packUpdate(con, mask, stream); + + if ( stream->writeFlag( mask & NodeMask ) ) + { + stream->writeInt( mNodes.size(), 16 ); + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + mathWrite( *stream, mNodes[i].point ); + stream->write( mNodes[i].width ); + } + } + + if ( stream->writeFlag( mask & DecalRoadMask ) ) + { + // Write Texture Name. + stream->write( mMaterialName ); + + stream->write( mBreakAngle ); + + stream->write( mSegmentsPerBatch ); + + stream->write( mTextureLength ); + + stream->write( mRenderPriority ); + } + + stream->writeFlag( mask & GenEdgesMask ); + + stream->writeFlag( mask & ReClipMask ); + + stream->writeFlag( mask & TerrainChangedMask ); + + // Were done ... + return retMask; +} + +void DecalRoad::unpackUpdate( NetConnection *con, BitStream *stream ) +{ + // Unpack Parent. + Parent::unpackUpdate( con, stream ); + + // NodeMask + if ( stream->readFlag() ) + { + U32 count = stream->readInt( 16 ); + + mNodes.clear(); + + Point3F pos; + F32 width; + for ( U32 i = 0; i < count; i++ ) + { + mathRead( *stream, &pos ); + stream->read( &width ); + _addNode( pos, width ); + } + } + + // DecalRoadMask + if ( stream->readFlag() ) + { + String matName; + stream->read( &matName ); + + if ( matName != mMaterialName ) + { + mMaterialName = matName; + Material *pMat = NULL; + if ( !Sim::findObject( mMaterialName, pMat ) ) + { + Con::printf( "DecalRoad::unpackUpdate, failed to find Material of name %s!", mMaterialName.c_str() ); + } + else + { + mMaterial = pMat; + if ( isProperlyAdded() ) + _initMaterial(); + } + } + + stream->read( &mBreakAngle ); + + stream->read( &mSegmentsPerBatch ); + + stream->read( &mTextureLength ); + + stream->read( &mRenderPriority ); + } + + // GenEdgesMask + if ( stream->readFlag() && isProperlyAdded() ) + _generateEdges(); + + // ReClipMask + if ( stream->readFlag() && isProperlyAdded() ) + _captureVerts(); + + // TerrainChangedMask + if ( stream->readFlag() ) + { + if ( isProperlyAdded() ) + { + if ( mTerrainUpdateRect.isOverlapped( getWorldBox() ) ) + { + _generateEdges(); + _captureVerts(); + // Clear out the mTerrainUpdateRect since we have updated its + // region and we now need to store future terrain changes + // in it. + mTerrainUpdateRect = Box3F::Invalid; + } + } + } +} + + +// SceneObject + +bool DecalRoad::prepRenderImage( SceneState* state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseZoneState) +{ + if ( mNodes.size() <= 1 || + isLastState(state, stateKey) || + mBatches.size() == 0 || + !mMatInst || + state->isShadowPass() ) + return false; + + // Set Last State. + setLastState( state, stateKey ); + + // Is Object Rendered? + if ( !state->isObjectRendered( this ) ) + return false; + + RenderPassManager *renderPass = state->getRenderPass(); + + // Debug RenderInstance + // Only when editor is open. + if ( smEditorOpen ) + { + ObjectRenderInst *ri = renderPass->allocInst(); + ri->type = RenderPassManager::RIT_Object; + ri->renderDelegate.bind( this, &DecalRoad::_debugRender ); + state->getRenderPass()->addInst( ri ); + } + + // Normal Road RenderInstance + // Always rendered when the editor is not open + // otherwise obey the smShowRoad flag + if ( !smShowRoad && smEditorOpen ) + return false; + + const Frustum &frustum = state->getFrustum(); + + MeshRenderInst coreRI; + coreRI.clear(); + coreRI.objectToWorld = &MatrixF::Identity; + coreRI.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View); + + MatrixF *tempMat = renderPass->allocUniqueXform( MatrixF( true ) ); + MathUtils::getZBiasProjectionMatrix( gDecalBias, frustum, tempMat ); + coreRI.projection = tempMat; + + coreRI.type = RenderPassManager::RIT_Decal; + coreRI.matInst = mMatInst; + coreRI.vertBuff = &mVB; + coreRI.primBuff = &mPB; + + // Make it the sort distance the max distance so that + // it renders after all the other opaque geometry in + // the prepass bin. + coreRI.sortDistSq = F32_MAX; + + // Get the light manager and setup lights + LightManager *lm = state->getLightManager(); + if ( lm ) + { + lm->setupLights( this, getWorldSphere() ); + lm->getBestLights( coreRI.lights, 8 ); + } + + U32 startBatchIdx = -1; + U32 endBatchIdx = 0; + + for ( U32 i = 0; i < mBatches.size(); i++ ) + { + // TODO: visibility is bugged... must fix! + //const RoadBatch &batch = mBatches[i]; + //const bool isVisible = frustum.intersects( batch.bounds ); + if ( true /*isVisible*/ ) + { + // If this is the start of a set of batches. + if ( startBatchIdx == -1 ) + endBatchIdx = startBatchIdx = i; + + // Else we're extending the end batch index. + else + ++endBatchIdx; + + // If this isn't the last batch then continue. + if ( i < mBatches.size()-1 ) + continue; + } + + // We we still don't have a start batch, so skip. + if ( startBatchIdx == -1 ) + continue; + + // Render this set of batches. + const RoadBatch &startBatch = mBatches[startBatchIdx]; // mBatches[0]; + const RoadBatch &endBatch = mBatches[endBatchIdx]; // mBatches.last(); + + U32 startVert = startBatch.startVert; + U32 startIdx = startBatch.startIndex; + U32 vertCount = endBatch.endVert - startVert; + U32 idxCount = ( endBatch.endIndex - startIdx ) + 1; + U32 triangleCount = idxCount / 3; + + AssertFatal( startVert + vertCount <= mVertCount, "DecalRoad, bad draw call!" ); + AssertFatal( startIdx + triangleCount < mTriangleCount * 3, "DecalRoad, bad draw call!" ); + + MeshRenderInst *ri = renderPass->allocInst(); + + *ri = coreRI; + + ri->prim = renderPass->allocPrim(); + ri->prim->type = GFXTriangleList; + ri->prim->minIndex = 0; + ri->prim->startIndex = startIdx; + ri->prim->numPrimitives = triangleCount; + ri->prim->startVertex = startVert; + ri->prim->numVertices = vertCount; + + // For sorting we first sort by render priority + // and then by objectId. + // + // Since a road can submit more than one render instance, we want all + // draw calls for a single road to occur consecutively, since they + // could use the same vertex buffer. + ri->defaultKey = mRenderPriority << 0 | mId << 16; + ri->defaultKey2 = 0; + + renderPass->addInst( ri ); + + // Reset the batching. + startBatchIdx = -1; + } + + return false; +} + +void DecalRoad::setTransform( const MatrixF &mat ) +{ + // We ignore transform requests from the editor + // right now. +} + +void DecalRoad::setScale( const VectorF &scale ) +{ + // We ignore scale requests from the editor + // right now. +} + + +// DecalRoad Public Methods + +bool DecalRoad::getClosestNode( const Point3F &pos, U32 &idx ) +{ + F32 closestDist = F32_MAX; + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + F32 dist = ( mNodes[i].point - pos ).len(); + if ( dist < closestDist ) + { + closestDist = dist; + idx = i; + } + } + + return closestDist != F32_MAX; +} + +bool DecalRoad::containsPoint( const Point3F &worldPos, U32 *nodeIdx ) const +{ + // If point isn't in the world box, + // it's definitely not in the road. + //if ( !getWorldBox().isContained( worldPos ) ) + // return false; + + if ( mEdges.size() < 2 ) + return false; + + Point2F testPt( worldPos.x, + worldPos.y ); + Point2F poly[4]; + + // Look through all edges, does the polygon + // formed from adjacent edge's contain the worldPos? + for ( U32 i = 0; i < mEdges.size() - 1; i++ ) + { + const RoadEdge &edge0 = mEdges[i]; + const RoadEdge &edge1 = mEdges[i+1]; + + poly[0].set( edge0.p0.x, edge0.p0.y ); + poly[1].set( edge0.p2.x, edge0.p2.y ); + poly[2].set( edge1.p2.x, edge1.p2.y ); + poly[3].set( edge1.p0.x, edge1.p0.y ); + + if ( MathUtils::pointInPolygon( poly, 4, testPt ) ) + { + if ( nodeIdx ) + *nodeIdx = edge0.parentNodeIdx; + + return true; + } + + } + + return false; +} + +bool DecalRoad::castray( const Point3F &start, const Point3F &end ) const +{ + // We just cast against the object box for the editor. + return mWorldBox.collideLine( start, end ); +} + +Point3F DecalRoad::getNodePosition( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return Point3F(); + + return mNodes[idx].point; +} + +void DecalRoad::setNodePosition( U32 idx, const Point3F &pos ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].point = pos; + + _generateEdges(); + scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); +} + +U32 DecalRoad::addNode( const Point3F &pos, F32 width ) +{ + U32 idx = _addNode( pos, width ); + + _generateEdges(); + scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); + + return idx; +} + +U32 DecalRoad::insertNode(const Point3F &pos, const F32 &width, const U32 &idx) +{ + U32 ret = _insertNode( pos, width, idx ); + + _generateEdges(); + scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); + + return ret; +} + +void DecalRoad::setNodeWidth( U32 idx, F32 width ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].width = width; + + _generateEdges(); + scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); +} + +F32 DecalRoad::getNodeWidth( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return -1.0f; + + return mNodes[idx].width; +} + +void DecalRoad::deleteNode( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes.erase(idx); + + _generateEdges(); + scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); +} + +void DecalRoad::setTextureLength( F32 meters ) +{ + meters = getMax( meters, 0.1f ); + if ( mTextureLength == meters ) + return; + + mTextureLength = meters; + + _generateEdges(); + scheduleUpdate( DecalRoadMask | ReClipMask ); +} + +void DecalRoad::setBreakAngle( F32 degrees ) +{ + //meters = getMax( meters, MIN_METERS_PER_SEGMENT ); + //if ( mBreakAngle == meters ) + // return; + + mBreakAngle = degrees; + + _generateEdges(); + scheduleUpdate( DecalRoadMask | GenEdgesMask | ReClipMask ); +} + +void DecalRoad::scheduleUpdate( U32 updateMask ) +{ + scheduleUpdate( updateMask, smUpdateDelay, true ); +} + +void DecalRoad::scheduleUpdate( U32 updateMask, U32 delayMs, bool restartTimer ) +{ + if ( Sim::isEventPending( mUpdateEventId ) ) + { + if ( !restartTimer ) + { + mLastEvent->mMask |= updateMask; + return; + } + else + { + Sim::cancelEvent( mUpdateEventId ); + } + } + + mLastEvent = new DecalRoadUpdateEvent( updateMask, delayMs ); + mUpdateEventId = Sim::postEvent( this, mLastEvent, Sim::getCurrentTime() + delayMs ); +} + +void DecalRoad::regenerate() +{ + _generateEdges(); + _captureVerts(); + setMaskBits( NodeMask | GenEdgesMask | ReClipMask ); +} + +bool DecalRoad::addNodeFromField( void* obj, const char* data ) +{ + DecalRoad *pObj = static_cast(obj); + + F32 x,y,z,width; + U32 result = dSscanf( data, "%f %f %f %f", &x, &y, &z, &width ); + if ( result == 4 ) + pObj->_addNode( Point3F(x,y,z), width ); + + return false; +} + + +// Internal Helper Methods + +void DecalRoad::_initMaterial() +{ + SAFE_DELETE( mMatInst ); + + if ( mMaterial ) + mMatInst = mMaterial->createMatInstance(); + else + mMatInst = MATMGR->createMatInstance( "WarningMaterial" ); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + mMatInst->addStateBlockDesc( desc ); + + mMatInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); +} + +void DecalRoad::_debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ) +{ + //if ( mStateBlock.isNull() ) + // return; + + GFX->enterDebugEvent( ColorI( 255, 0, 0 ), "DecalRoad_debugRender" ); + GFXTransformSaver saver; + + //GFX->setStateBlock( mStateBlock ); + + Point3F size(1,1,1); + ColorI color( 255, 0, 0, 255 ); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.setBlend( true ); + desc.fillMode = GFXFillWireframe; + + if ( smShowBatches ) + { + for ( U32 i = 0; i < mBatches.size(); i++ ) + { + const Box3F &box = mBatches[i].bounds; + GFX->getDrawUtil()->drawCube( desc, box, ColorI(255,100,100,255) ); + } + } + + //GFX->leaveDebugEvent(); +} + +void DecalRoad::_generateEdges() +{ + PROFILE_SCOPE( DecalRoad_generateEdges ); + + //Con::warnf( "%s - generateEdges", isServerObject() ? "server" : "client" ); + + if ( mNodes.size() > 0 ) + { + // Set our object position to the first node. + const Point3F &nodePt = mNodes.first().point; + MatrixF mat( true ); + mat.setPosition( nodePt ); + Parent::setTransform( mat ); + + // The server object has global bounds, which Parent::setTransform + // messes up so we must reset it. + if ( isServerObject() ) + { + mObjBox.minExtents.set(-1e10, -1e10, -1e10); + mObjBox.maxExtents.set( 1e10, 1e10, 1e10); + } + } + + + if ( mNodes.size() < 2 ) + return; + + // Ensure nodes are above the terrain height at their xy position + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + _getTerrainHeight( mNodes[i].point ); + } + + // Now start generating edges... + + U32 nodeCount = mNodes.size(); + Point3F *positions = new Point3F[nodeCount]; + + for ( U32 i = 0; i < nodeCount; i++ ) + { + const RoadNode &node = mNodes[i]; + positions[i].set( node.point.x, node.point.y, node.width ); + } + + CatmullRom spline; + spline.initialize( nodeCount, positions ); + delete [] positions; + + mEdges.clear(); + + Point3F lastBreakVector(0,0,0); + RoadEdge slice; + Point3F lastBreakNode; + lastBreakNode = spline.evaluate(0.0f); + + for ( U32 i = 1; i < mNodes.size(); i++ ) + { + F32 t1 = spline.getTime(i); + F32 t0 = spline.getTime(i-1); + + F32 segLength = spline.arcLength( t0, t1 ); + + U32 numSegments = mCeil( segLength / MIN_METERS_PER_SEGMENT ); + numSegments = getMax( numSegments, (U32)1 ); + F32 tstep = ( t1 - t0 ) / numSegments; + + U32 startIdx = 0; + U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; + + for ( U32 j = startIdx; j < endIdx; j++ ) + { + F32 t = t0 + tstep * j; + Point3F splineNode = spline.evaluate(t); + F32 width = splineNode.z; + _getTerrainHeight( splineNode ); + + Point3F toNodeVec = splineNode - lastBreakNode; + toNodeVec.normalizeSafe(); + + if ( lastBreakVector.isZero() ) + lastBreakVector = toNodeVec; + + F32 angle = mRadToDeg( mAcos( mDot( toNodeVec, lastBreakVector ) ) ); + + if ( j == startIdx || + ( j == endIdx - 1 && i == mNodes.size() - 1 ) || + angle > mBreakAngle ) + { + // Push back a spline node + //slice.p1.set( splineNode.x, splineNode.y, 0.0f ); + //_getTerrainHeight( slice.p1 ); + slice.p1 = splineNode; + slice.uvec.set(0,0,1); + slice.width = width; + slice.parentNodeIdx = i-1; + mEdges.push_back( slice ); + + lastBreakVector = splineNode - lastBreakNode; + lastBreakVector.normalizeSafe(); + + lastBreakNode = splineNode; + } + } + } + + /* + for ( U32 i = 1; i < nodeCount; i++ ) + { + F32 t0 = spline.getTime( i-1 ); + F32 t1 = spline.getTime( i ); + + F32 segLength = spline.arcLength( t0, t1 ); + + U32 numSegments = mCeil( segLength / mBreakAngle ); + numSegments = getMax( numSegments, (U32)1 ); + F32 tstep = ( t1 - t0 ) / numSegments; + + AssertFatal( numSegments > 0, "DecalRoad::_generateEdges, got zero segments!" ); + + U32 startIdx = 0; + U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; + + for ( U32 j = startIdx; j < endIdx; j++ ) + { + F32 t = t0 + tstep * j; + Point3F val = spline.evaluate(t); + + RoadEdge edge; + edge.p1.set( val.x, val.y, 0.0f ); + _getTerrainHeight( val.x, val.y, edge.p1.z ); + edge.uvec.set(0,0,1); + edge.width = val.z; + edge.parentNodeIdx = i-1; + mEdges.push_back( edge ); + } + } + */ + + // + // Calculate fvec and rvec for all edges + // + RoadEdge *edge = NULL; + RoadEdge *nextEdge = NULL; + + for ( U32 i = 0; i < mEdges.size() - 1; i++ ) + { + edge = &mEdges[i]; + nextEdge = &mEdges[i+1]; + + edge->fvec = nextEdge->p1 - edge->p1; + edge->fvec.normalize(); + + edge->rvec = mCross( edge->fvec, edge->uvec ); + edge->rvec.normalize(); + } + + // Must do the last edge outside the loop + RoadEdge *lastEdge = &mEdges[mEdges.size()-1]; + RoadEdge *prevEdge = &mEdges[mEdges.size()-2]; + lastEdge->fvec = prevEdge->fvec; + lastEdge->rvec = prevEdge->rvec; + + + // + // Calculate p0/p2 for all edges + // + for ( U32 i = 0; i < mEdges.size(); i++ ) + { + RoadEdge *edge = &mEdges[i]; + edge->p0 = edge->p1 - edge->rvec * edge->width * 0.5f; + edge->p2 = edge->p1 + edge->rvec * edge->width * 0.5f; + _getTerrainHeight( edge->p0 ); + _getTerrainHeight( edge->p2 ); + } +} + +void DecalRoad::_captureVerts() +{ + PROFILE_SCOPE( DecalRoad_captureVerts ); + + //Con::warnf( "%s - captureVerts", isServerObject() ? "server" : "client" ); + + if ( isServerObject() ) + { + //Con::errorf( "DecalRoad::_captureVerts - called on the server side!" ); + return; + } + + if ( mEdges.size() == 0 ) + return; + + // + // Construct ClippedPolyList objects for each pair + // of roadEdges. + // Use them to capture Terrain verts. + // + SphereF sphere; + RoadEdge *edge = NULL; + RoadEdge *nextEdge = NULL; + + mTriangleCount = 0; + mVertCount = 0; + + Vector clipperList; + + for ( U32 i = 0; i < mEdges.size() - 1; i++ ) + { + Box3F box; + edge = &mEdges[i]; + nextEdge = &mEdges[i+1]; + + box.minExtents = edge->p1; + box.maxExtents = edge->p1; + box.extend( edge->p0 ); + box.extend( edge->p2 ); + box.extend( nextEdge->p0 ); + box.extend( nextEdge->p1 ); + box.extend( nextEdge->p2 ); + box.minExtents.z -= 5.0f; + box.maxExtents.z += 5.0f; + + sphere.center = ( nextEdge->p1 + edge->p1 ) * 0.5f; + sphere.radius = 100.0f; // NOTE: no idea how to calculate this + + ClippedPolyList clipper; + clipper.mNormal.set(0.0f, 0.0f, 0.0f); + VectorF n; + PlaneF plane0, plane1; + + // Construct Back Plane + n = edge->p2 - edge->p0; + n.normalize(); + n = mCross( n, edge->uvec ); + plane0.set( edge->p0, n ); + clipper.mPlaneList.push_back( plane0 ); + + // Construct Front Plane + n = nextEdge->p2 - nextEdge->p0; + n.normalize(); + n = -mCross( edge->uvec, n ); + plane1.set( nextEdge->p0, -n ); + //clipper.mPlaneList.push_back( plane1 ); + + // Test if / where the planes intersect. + bool discardLeft = false; + bool discardRight = false; + Point3F iPos; + VectorF iDir; + + if ( mIntersect( plane0, plane1, &iPos, &iDir ) ) + { + // Don't know why I have to negate iPos... + Point2F iPos2F( -iPos.x, -iPos.y ); + Point2F cPos2F( edge->p1.x, edge->p1.y ); + Point2F rVec2F( edge->rvec.x, edge->rvec.y ); + + Point2F iVec2F = iPos2F - cPos2F; + F32 iLen = iVec2F.len(); + iVec2F.normalize(); + + if ( iLen < edge->width * 0.5f ) + { + F32 dot = mDot( rVec2F, iVec2F ); + + // The clipping planes intersected on the right side, + // discard the right side clipping plane. + if ( dot > 0.0f ) + discardRight = true; + // The clipping planes intersected on the left side, + // discard the left side clipping plane. + else + discardLeft = true; + } + } + + // Left Plane + if ( !discardLeft ) + { + n = ( nextEdge->p0 - edge->p0 ); + n.normalize(); + n = mCross( edge->uvec, n ); + clipper.mPlaneList.push_back( PlaneF(edge->p0, n) ); + } + else + { + nextEdge->p0 = edge->p0; + } + + // Right Plane + if ( !discardRight ) + { + n = ( nextEdge->p2 - edge->p2 ); + n.normalize(); + n = -mCross( n, edge->uvec ); + clipper.mPlaneList.push_back( PlaneF(edge->p2, -n) ); + } + else + { + nextEdge->p2 = edge->p2; + } + + n = nextEdge->p2 - nextEdge->p0; + n.normalize(); + n = -mCross( edge->uvec, n ); + plane1.set( nextEdge->p0, -n ); + clipper.mPlaneList.push_back( plane1 ); + + // We have constructed the clipping planes, + // now grab/clip the terrain geometry + getContainer()->buildPolyList( box, TerrainObjectType, &clipper ); + clipper.cullUnusedVerts(); + clipper.triangulate(); + clipper.generateNormals(); + + // If we got something, add it to the ClippedPolyList Vector + if ( !clipper.isEmpty() && !( smDiscardAll && ( discardRight || discardLeft ) ) ) + { + clipperList.push_back( clipper ); + + mVertCount += clipper.mVertexList.size(); + mTriangleCount += clipper.mPolyList.size(); + } + } + + // + // Set the roadEdge height to be flush with terrain + // This is not really necessary but makes the debug spline rendering better. + // + for ( U32 i = 0; i < mEdges.size() - 1; i++ ) + { + edge = &mEdges[i]; + + _getTerrainHeight( edge->p0.x, edge->p0.y, edge->p0.z ); + + _getTerrainHeight( edge->p2.x, edge->p2.y, edge->p2.z ); + } + + // + // Allocate the RoadBatch(s) + // + + // If we captured no verts, then we can return here without + // allocating any RoadBatches or the Vert/Index Buffers. + // PreprenderImage will not allocate a render instance while + // mBatches.size() is zero. + U32 numClippers = clipperList.size(); + if ( numClippers == 0 ) + return; + + mBatches.clear(); + + // Allocate the VertexBuffer and PrimitiveBuffer + mVB.set( GFX, mVertCount, GFXBufferTypeStatic ); + mPB.set( GFX, mTriangleCount * 3, 0, GFXBufferTypeStatic ); + + // Lock the VertexBuffer + GFXVertexPNTBT *vertPtr = mVB.lock(); + U32 vertIdx = 0; + + // + // Fill the VertexBuffer and vertex data for the RoadBatches + // Loop through the ClippedPolyList Vector + // + RoadBatch *batch = NULL; + F32 texStart = 0.0f; + F32 texEnd; + + for ( U32 i = 0; i < clipperList.size(); i++ ) + { + ClippedPolyList *clipper = &clipperList[i]; + RoadEdge &edge = mEdges[i]; + RoadEdge &nextEdge = mEdges[i+1]; + + VectorF segFvec = nextEdge.p1 - edge.p1; + F32 segLen = segFvec.len(); + segFvec.normalize(); + + F32 texLen = segLen / mTextureLength; + texEnd = texStart + texLen; + + BiQuadToSqr quadToSquare( Point2F( edge.p0.x, edge.p0.y ), + Point2F( edge.p2.x, edge.p2.y ), + Point2F( nextEdge.p2.x, nextEdge.p2.y ), + Point2F( nextEdge.p0.x, nextEdge.p0.y ) ); + + // + if ( i % mSegmentsPerBatch == 0 ) + { + mBatches.increment(); + batch = &mBatches.last(); + + batch->bounds.minExtents = clipper->mVertexList[0].point; + batch->bounds.maxExtents = clipper->mVertexList[0].point; + batch->startVert = vertIdx; + batch->endVert = vertIdx + clipper->mVertexList.size(); + } + + // Loop through each ClippedPolyList + for ( U32 j = 0; j < clipper->mVertexList.size(); j++ ) + { + // Add each vert to the VertexBuffer + Point3F pos = clipper->mVertexList[j].point; + vertPtr[vertIdx].point = pos; + vertPtr[vertIdx].normal = clipper->mNormalList[j]; + + Point2F uv = quadToSquare.transform( Point2F(pos.x,pos.y) ); + vertPtr[vertIdx].texCoord.x = uv.x; + vertPtr[vertIdx].texCoord.y = -(( texEnd - texStart ) * uv.y + texStart); + + vertPtr[vertIdx].tangent = mCross( segFvec, clipper->mNormalList[j] ); + vertPtr[vertIdx].binormal = segFvec; + + vertIdx++; + + // Expand the RoadBatch bounds to contain this vertex + batch->bounds.extend( pos ); + } + + texStart = texEnd; + } + + // Unlock the VertexBuffer, we are done filling it. + mVB.unlock(); + + // Lock the PrimitiveBuffer + U16 *idxBuff; + mPB.lock(&idxBuff); + U32 curIdx = 0; + U16 vertOffset = 0; + batch = NULL; + S32 batchIdx = -1; + + // Fill the PrimitiveBuffer + // Loop through each ClippedPolyList in the Vector + for ( U32 i = 0; i < clipperList.size(); i++ ) + { + ClippedPolyList *clipper = &clipperList[i]; + + if ( i % mSegmentsPerBatch == 0 ) + { + batchIdx++; + batch = &mBatches[batchIdx]; + batch->startIndex = curIdx; + } + + for ( U32 j = 0; j < clipper->mPolyList.size(); j++ ) + { + // Write indices for each Poly + ClippedPolyList::Poly *poly = &clipper->mPolyList[j]; + + AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" ); + + idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart] + vertOffset; + curIdx++; + idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 1] + vertOffset; + curIdx++; + idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 2] + vertOffset; + curIdx++; + } + + batch->endIndex = curIdx - 1; + + vertOffset += clipper->mVertexList.size(); + } + + // Unlock the PrimitiveBuffer, we are done filling it. + mPB.unlock(); + + // Generate the object/world bounds + // Is the union of all batch bounding boxes. + + Box3F box; + for ( U32 i = 0; i < mBatches.size(); i++ ) + { + const RoadBatch &batch = mBatches[i]; + + if ( i == 0 ) + box = batch.bounds; + else + box.intersect( batch.bounds ); + } + + Point3F pos = getPosition(); + + mObjBox = box; + mObjBox.minExtents -= pos; + mObjBox.maxExtents -= pos; + resetWorldBox(); +} + +U32 DecalRoad::_addNode( const Point3F &pos, F32 width ) +{ + mNodes.increment(); + RoadNode &node = mNodes.last(); + + node.point = pos; + node.width = width; + + return mNodes.size() - 1; +} + +U32 DecalRoad::_insertNode( const Point3F &pos, const F32 &width, const U32 &idx ) +{ + U32 ret; + RoadNode *node; + + if ( idx == U32_MAX ) + { + mNodes.increment(); + node = &mNodes.last(); + ret = mNodes.size() - 1; + } + else + { + mNodes.insert( idx ); + node = &mNodes[idx]; + ret = idx; + } + + node->point = pos; + //node->t = -1.0f; + //node->rot.identity(); + node->width = width; + + return ret; +} + +bool DecalRoad::_getTerrainHeight( Point3F &pos ) +{ + return _getTerrainHeight( pos.x, pos.y, pos.z ); +} + +bool DecalRoad::_getTerrainHeight( const Point2F &pos, F32 &height ) +{ + return _getTerrainHeight( pos.x, pos.y, height ); +} + +bool DecalRoad::_getTerrainHeight( const F32 &x, const F32 &y, F32 &height ) +{ + Point3F startPnt( x, y, 10000.0f ); + Point3F endPnt( x, y, -10000.0f ); + + RayInfo ri; + bool hit; + + hit = getContainer()->castRay(startPnt, endPnt, TerrainObjectType, &ri); + + if ( hit ) + height = ri.point.z; + + return hit; +} + +void DecalRoad::_onTerrainChanged( U32 type, TerrainBlock* tblock, const Point2I &min, const Point2I &max ) +{ + // The client side object just stores the area that has changed + // and waits for the (delayed) update event from the server + // to actually perform the update. + if ( isClientObject() && tblock->isClientObject() ) + { + // Convert the min and max into world space. + const F32 size = tblock->getSquareSize(); + const Point3F pos = tblock->getPosition(); + + // TODO: I don't think this works right with tiling! + Box3F dirty( F32( min.x * size ) + pos.x, F32( min.y * size ) + pos.y, -F32_MAX, + F32( max.x * size ) + pos.x, F32( max.y * size ) + pos.y, F32_MAX ); + + if ( !mTerrainUpdateRect.isValidBox() ) + mTerrainUpdateRect = dirty; + else + mTerrainUpdateRect.intersect( dirty ); + } + // The server object only updates edges (doesn't clip to geometry) + // and schedules an update to be sent to the client. + else if ( isServerObject() && tblock->isServerObject() ) + { + //_generateEdges(); + scheduleUpdate( TerrainChangedMask ); + } +} + + +// Static protected field set methods + +bool DecalRoad::ptSetBreakAngle( void *obj, const char *data ) +{ + DecalRoad *road = static_cast( obj ); + F32 val = dAtof( data ); + + road->setBreakAngle( val ); + + // we already set the field + return false; +} + +bool DecalRoad::ptSetTextureLength( void *obj, const char *data ) +{ + DecalRoad *road = static_cast( obj ); + F32 val = dAtof( data ); + + road->setTextureLength( val ); + + // we already set the field + return false; +} + + +// ConsoleMethods + +ConsoleMethod( DecalRoad, regenerate, void, 2, 2, "setRegenFlag()" ) +{ + object->regenerate(); +} + +ConsoleMethod( DecalRoad, postApply, void, 2, 2, "") +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/environment/decalRoad.h b/environment/decalRoad.h new file mode 100644 index 0000000..7d36f28 --- /dev/null +++ b/environment/decalRoad.h @@ -0,0 +1,257 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DECALROAD_H_ +#define _DECALROAD_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif + +class Path; +class TerrainBlock; +struct ObjectRenderInst; +class Material; + + +class DecalRoadUpdateEvent : public SimEvent +{ + typedef SimEvent Parent; +public: + + DecalRoadUpdateEvent( U32 mask, U32 ms ) { mMask = mask; mMs = ms; } + virtual void process( SimObject *object ); + + U32 mMask; + U32 mMs; +}; + + +struct RoadNode +{ + /// The 3D position of the node. + Point3F point; + + /// The width of the road at this node. + F32 width; + + /// Alpha of the road at this node. + //F32 alpha; +}; +typedef Vector RoadNodeVector; + +struct RoadEdge +{ + RoadEdge() + { + p0.zero(); + p1.zero(); + p2.zero(); + + uvec.zero(); + fvec.zero(); + rvec.zero(); + + width = 0.0f; + + parentNodeIdx = -1; + }; + + Point3F p0; + Point3F p1; + Point3F p2; + + VectorF uvec; + VectorF fvec; + VectorF rvec; + + F32 width; + + U32 parentNodeIdx; +}; +typedef Vector RoadEdgeVector; + +struct RoadBatch +{ + U32 startVert; + U32 endVert; + + U32 startIndex; + U32 endIndex; + + Box3F bounds; +}; +typedef Vector RoadBatchVector; + + +//------------------------------------------------------------------------------ +// DecalRoad Class +//------------------------------------------------------------------------------ +class DecalRoad : public SceneObject +{ +private: + + friend class DecalRoadUpdateEvent; + friend class GuiRoadEditorCtrl; + friend class GuiRoadEditorUndoAction; + typedef SceneObject Parent; + +protected: + // Internal defines, enums, structs, classes + + struct Triangle + { + GFXVertexPT v0, v1, v2; + }; + + enum + { + DecalRoadMask = Parent::NextFreeMask, + NodeMask = Parent::NextFreeMask << 1, + GenEdgesMask = Parent::NextFreeMask << 2, + ReClipMask = Parent::NextFreeMask << 3, + TerrainChangedMask = Parent::NextFreeMask << 4, + NextFreeMask = Parent::NextFreeMask << 5, + }; + + #define StepSize_Normal 10.0f + #define MIN_METERS_PER_SEGMENT 1.0f + +public: + + DecalRoad(); + ~DecalRoad(); + + DECLARE_CONOBJECT(DecalRoad); + + // ConsoleObject + static void initPersistFields(); + static void consoleInit(); + + // SimObject + bool onAdd(); + void onRemove(); + void onEditorEnable(); + void onEditorDisable(); + void inspectPostApply(); + void onStaticModified(const char* slotName, const char*newValue = NULL); + void writeFields(Stream &stream, U32 tabStop); + bool writeField( StringTableEntry fieldname, const char *value ); + + // NetObject + U32 packUpdate(NetConnection *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + // SceneObject + virtual bool prepRenderImage(SceneState*, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState = false); + virtual void setTransform( const MatrixF &mat ); + virtual void setScale( const VectorF &scale ); + + // fxRoad Public Methods + void scheduleUpdate( U32 updateMask ); + void scheduleUpdate( U32 updateMask, U32 delayMs, bool restartTimer ); + void regenerate(); + void setTextureLength( F32 meters ); + void setBreakAngle( F32 degrees ); + + /// Insert a node anywhere in the road. + /// Pass idx zero to add to the front and idx U32_MAX to add to the end + U32 insertNode( const Point3F &pos, const F32 &width, const U32 &idx ); + + U32 addNode( const Point3F &pos, F32 width = 10.0f ); + void deleteNode( U32 idx ); + + bool getClosestNode( const Point3F &pos, U32 &idx ); + + bool containsPoint( const Point3F &worldPos, U32 *nodeIdx = NULL ) const; + bool castray( const Point3F &start, const Point3F &end ) const; + + Point3F getNodePosition( U32 idx ); + void setNodePosition( U32 idx, const Point3F &pos ); + + F32 getNodeWidth( U32 idx ); + void setNodeWidth( U32 idx, F32 width ); + + /// Protected 'Node' Field setter that will add a node to the list. + static bool addNodeFromField(void* obj, const char* data); + + static SimSet* getServerSet(); + +protected: + + // Internal Helper Methods + + void _initMaterial(); + void _debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *matInst ); + + U32 _insertNode( const Point3F &pos, const F32 &width, const U32 &idx ); + U32 _addNode( const Point3F &pos, F32 width ); + void _generateEdges(); + void _captureVerts(); + + bool _getTerrainHeight( Point3F &pos ); + bool _getTerrainHeight( const Point2F &pos, F32 &height ); + bool _getTerrainHeight( const F32 &x, const F32 &y, F32 &height ); + + void _onTerrainChanged( U32 type, TerrainBlock* tblock, const Point2I &min, const Point2I &max ); + + // static protected field set methods + static bool ptSetBreakAngle( void *obj, const char *data ); + static bool ptSetTextureLength( void *obj, const char *data ); + +protected: + + // Field Vars + F32 mBreakAngle; + U32 mSegmentsPerBatch; + F32 mTextureLength; + String mMaterialName; + U32 mRenderPriority; + + // Static ConsoleVars for editor + static bool smEditorOpen; + static bool smWireframe; + static bool smShowBatches; + static bool smDiscardAll; + static bool smShowSpline; + static bool smShowRoad; + static S32 smUpdateDelay; + + static SimObjectPtr smServerDecalRoadSet; + + // Other Internal Vars + + RoadEdgeVector mEdges; + RoadNodeVector mNodes; + RoadBatchVector mBatches; + + bool mLoadRenderData; + + SimObjectPtr mMaterial; + BaseMatInstance *mMatInst; + + GFXVertexBufferHandle mVB; + GFXPrimitiveBufferHandle mPB; + + U32 mTriangleCount; + U32 mVertCount; + + S32 mUpdateEventId; + DecalRoadUpdateEvent *mLastEvent; + + Box3F mTerrainUpdateRect; +}; + + +#endif // _DECALROAD_H_ diff --git a/environment/editors/guiMeshRoadEditorCtrl.cpp b/environment/editors/guiMeshRoadEditorCtrl.cpp new file mode 100644 index 0000000..03bdf61 --- /dev/null +++ b/environment/editors/guiMeshRoadEditorCtrl.cpp @@ -0,0 +1,1273 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/editors/guiMeshRoadEditorCtrl.h" + +#include "console/consoleTypes.h" +#include "environment/meshRoad.h" +#include "renderInstance/renderPassManager.h" +#include "collision/collision.h" +#include "math/util/frustum.h" +#include "math/mathUtils.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxTextureHandle.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonCtrl.h" +#include "gui/worldEditor/undoActions.h" +#include "T3D/gameConnection.h" +#include "gfx/sim/debugDraw.h" +#include "materials/materialDefinition.h" + +IMPLEMENT_CONOBJECT(GuiMeshRoadEditorCtrl); + +GuiMeshRoadEditorCtrl::GuiMeshRoadEditorCtrl() + : + // Each of the mode names directly correlates with the Mesh Road Editor's + // tool palette + mSelectMeshRoadMode("MeshRoadEditorSelectMode"), + mAddMeshRoadMode("MeshRoadEditorAddRoadMode"), + mMovePointMode("MeshRoadEditorMoveMode"), + mRotatePointMode("MeshRoadEditorRotateMode"), + mScalePointMode("MeshRoadEditorScaleMode"), + mAddNodeMode("MeshRoadEditorAddNodeMode"), + mInsertPointMode("MeshRoadEditorInsertPointMode"), + mRemovePointMode("MeshRoadEditorRemovePointMode"), + mMode(mSelectMeshRoadMode), + + mHasCopied( false ), + mIsDirty( false ), + mRoadSet( NULL ), + mSelNode( -1 ), + mSelRoad( NULL ), + mHoverRoad( NULL ), + mHoverNode( -1 ), + mDefaultWidth( 10.0f ), + mDefaultDepth( 5.0f ), + mDefaultNormal( 0,0,1 ), + mAddNodeIdx( 0 ), + mNodeHalfSize( 4,4 ), + mHoverSplineColor( 255,0,0,255 ), + mSelectedSplineColor( 0,255,0,255 ), + mHoverNodeColor( 255,255,255,255 ) +{ +} + +GuiMeshRoadEditorCtrl::~GuiMeshRoadEditorCtrl() +{ + // nothing to do +} + +void GuiMeshRoadEditorUndoAction::undo() +{ + MeshRoad *object = NULL; + if ( !Sim::findObject( mObjId, object ) ) + return; + + // Temporarily save the Roads current data. + //F32 metersPerSeg = object->mMetersPerSegment; + Vector nodes; + nodes.merge( object->mNodes ); + + // Restore the River properties saved in the UndoAction + //object->mMetersPerSegment = mMetersPerSegment; + + // Restore the Nodes saved in the UndoAction + object->mNodes.clear(); + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + object->_addNode( mNodes[i].point, mNodes[i].width, mNodes[i].depth, mNodes[i].normal ); + } + + // Regenerate the Road + object->regenerate(); + + // If applicable set the selected Road and node + mEditor->mSelRoad = object; + mEditor->mSelNode = -1; + + // Now save the previous Road data in this UndoAction + // since an undo action must become a redo action and vice-versa + //mMetersPerSegment = metersPerSeg; + mNodes.clear(); + mNodes.merge( nodes ); +} + +bool GuiMeshRoadEditorCtrl::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + mRoadSet = MeshRoad::getServerSet(); + + GFXStateBlockDesc desc; + desc.fillMode = GFXFillSolid; + desc.blendDefined = true; + desc.blendEnable = false; + desc.zDefined = true; + desc.zEnable = false; + desc.cullDefined = true; + desc.cullMode = GFXCullNone; + + mZDisableSB = GFX->createStateBlock(desc); + + desc.zEnable = true; + mZEnableSB = GFX->createStateBlock(desc); + + return true; +} + +void GuiMeshRoadEditorCtrl::initPersistFields() +{ + addField( "DefaultWidth", TypeF32, Offset( mDefaultWidth, GuiMeshRoadEditorCtrl ) ); + addField( "DefaultDepth", TypeF32, Offset( mDefaultDepth, GuiMeshRoadEditorCtrl ) ); + addField( "DefaultNormal", TypePoint3F,Offset( mDefaultNormal, GuiMeshRoadEditorCtrl ) ); + addField( "HoverSplineColor", TypeColorI, Offset( mHoverSplineColor, GuiMeshRoadEditorCtrl ) ); + addField( "SelectedSplineColor", TypeColorI, Offset( mSelectedSplineColor, GuiMeshRoadEditorCtrl ) ); + addField( "HoverNodeColor", TypeColorI, Offset( mHoverNodeColor, GuiMeshRoadEditorCtrl ) ); + addField( "isDirty", TypeBool, Offset( mIsDirty, GuiMeshRoadEditorCtrl ) ); + //addField( "MoveNodeCursor", TypeSimObjectPtr, Offset( mMoveNodeCursor, GuiMeshRoadEditorCtrl) ); + //addField( "AddNodeCursor", TypeSimObjectPtr, Offset( mAddNodeCursor, GuiMeshRoadEditorCtrl) ); + //addField( "InsertNodeCursor", TypeSimObjectPtr, Offset( mInsertNodeCursor, GuiMeshRoadEditorCtrl) ); + //addField( "ResizeNodeCursor", TypeSimObjectPtr, Offset( mResizeNodeCursor, GuiMeshRoadEditorCtrl) ); + + Parent::initPersistFields(); +} + +void GuiMeshRoadEditorCtrl::onSleep() +{ + Parent::onSleep(); + + mMode = mSelectMeshRoadMode; + mHoverNode = -1; + mHoverRoad = NULL; + setSelectedNode(-1); +} + +void GuiMeshRoadEditorCtrl::get3DCursor( GuiCursor *&cursor, + bool &visible, + const Gui3DMouseEvent &event_ ) +{ + //cursor = mAddNodeCursor; + //visible = false; + + cursor = NULL; + visible = false; + + GuiCanvas *root = getRoot(); + if ( !root ) + return; + + S32 currCursor = PlatformCursorController::curArrow; + + if ( root->mCursorChanged == currCursor ) + return; + + PlatformWindow *window = root->getPlatformWindow(); + PlatformCursorController *controller = window->getCursorController(); + + // We've already changed the cursor, + // so set it back before we change it again. + if( root->mCursorChanged != -1) + controller->popCursor(); + + // Now change the cursor shape + controller->pushCursor(currCursor); + root->mCursorChanged = currCursor; +} + +void GuiMeshRoadEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) +{ + mHasCopied = false; + + mGizmo->on3DMouseDown( event ); + + if ( !isFirstResponder() ) + setFirstResponder(); + + // Get the raycast collision position + Point3F tPos; + if ( !getStaticPos( event, tPos ) ) + return; + + // Construct a LineSegment from the camera position to 1000 meters away in + // the direction clicked. + // If that segment hits the terrain, truncate the ray to only be that length. + + // We will use a LineSegment/Sphere intersection test to determine if a MeshRoadNode + // was clicked. + + MeshRoad *pRoad = NULL; + MeshRoad *pClickedRoad = NULL; + U32 insertNodeIdx = -1; + Point3F collisionPnt; + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 2000.0f; + RayInfo ri; + + if ( gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri) ) + endPnt = ri.point; + + DebugDrawer *ddraw = DebugDrawer::get(); + if ( false && ddraw ) + { + ddraw->drawLine( startPnt, endPnt, ColorI(255,0,0,255) ); + ddraw->setLastTTL(DebugDrawer::DD_INFINITE); + } + + // Check for collision with nodes of the currently selected or highlighted MeshRoad + + // Did we click on a MeshRoad? check currently selected road first + if ( mSelRoad != NULL && mSelRoad->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) ) + { + pClickedRoad = mSelRoad; + } + else + { + for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) + { + pRoad = static_cast( *iter ); + if ( pRoad->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) ) + { + pClickedRoad = pRoad; + break; + } + } + } + + /* + else if ( gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri) ) + { + MeshRoad *pRoad = NULL; + pRoad = dynamic_cast(ri.object); + + if ( pRoad && pRoad->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) ) + pClickedRoad = pRoad; + } + */ + + // Did we click on a node? + bool nodeClicked = false; + S32 clickedNodeIdx = -1; + + // If we clicked on the currently selected road, only scan its nodes + if ( mSelRoad != NULL && pClickedRoad == mSelRoad ) + { + clickedNodeIdx = _getNodeAtScreenPos( mSelRoad, event.mousePoint ); + nodeClicked = clickedNodeIdx != -1; + } + else + { + for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) + { + pRoad = static_cast( *iter ); + + clickedNodeIdx = _getNodeAtScreenPos( pRoad, event.mousePoint ); + + if ( clickedNodeIdx != -1 ) + { + nodeClicked = true; + pClickedRoad = pRoad; + break; + } + } + } + + // shortcuts + bool dblClick = ( event.mouseClickCount > 1 ); + if( dblClick ) + { + if( mMode == mSelectMeshRoadMode ) + { + setMode( mAddMeshRoadMode, true ); + return; + } + if( mMode == mAddNodeMode ) + { + // Delete the node attached to the cursor. + deleteSelectedNode(); + mMode = mAddMeshRoadMode; + return; + } + } + + //this check is here in order to bounce back from deleting a whole road with ctrl+z + //this check places the editor back into addrivermode + if ( mMode == mAddNodeMode ) + { + if ( !mSelRoad ) + mMode = mAddMeshRoadMode; + } + + if ( mMode == mSelectMeshRoadMode ) + { + // Did not click on a MeshRoad or a node. + if ( !pClickedRoad ) + { + setSelectedRoad( NULL ); + setSelectedNode( -1 ); + + return; + } + + // Clicked on a MeshRoad that wasn't the currently selected River. + if ( pClickedRoad != mSelRoad ) + { + setSelectedRoad( pClickedRoad ); + setSelectedNode( clickedNodeIdx ); + return; + } + + // Clicked on a node in the currently selected River that wasn't + // the currently selected node. + if ( nodeClicked ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } + else if ( mMode == mAddMeshRoadMode ) + { + if ( nodeClicked ) + { + // A double-click on a node in Normal mode means set AddNode mode. + if ( clickedNodeIdx == 0 ) + { + setSelectedRoad( pClickedRoad ); + setSelectedNode( clickedNodeIdx ); + + mAddNodeIdx = clickedNodeIdx; + mMode = mAddNodeMode; + + mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx ); + mIsDirty = true; + + return; + } + else if ( clickedNodeIdx == pClickedRoad->mNodes.size() - 1 ) + { + setSelectedRoad( pClickedRoad ); + setSelectedNode( clickedNodeIdx ); + + mAddNodeIdx = U32_MAX; + mMode = mAddNodeMode; + + mSelNode = mSelRoad->addNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal); + mIsDirty = true; + setSelectedNode( mSelNode ); + + return; + } + } + + MeshRoad *newRoad = new MeshRoad; + + Material * defaultMat = dynamic_cast( Sim::findObject("DefaultRoadMaterialTop") ); + if( defaultMat ) + newRoad->mMaterialName[newRoad->Top] = "DefaultRoadMaterialTop"; + + Material * defaultMatOther = dynamic_cast( Sim::findObject("DefaultRoadMaterialOther") ); + if( defaultMatOther ) + { + newRoad->mMaterialName[newRoad->Bottom] = "DefaultRoadMaterialOther"; + newRoad->mMaterialName[newRoad->Side] = "DefaultRoadMaterialOther"; + } + + newRoad->registerObject(); + + // Add to MissionGroup + SimGroup *missionGroup; + if ( !Sim::findObject( "MissionGroup", missionGroup ) ) + Con::errorf( "GuiMeshRoadEditorCtrl - could not find MissionGroup to add new MeshRoad" ); + else + missionGroup->addObject( newRoad ); + + Point3F pos( endPnt ); + pos.z += mDefaultDepth * 0.5f; + + newRoad->insertNode( pos, mDefaultWidth, mDefaultDepth, mDefaultNormal, 0 ); + U32 newNode = newRoad->insertNode( pos, mDefaultWidth, mDefaultDepth, mDefaultNormal, 1 ); + + // Always add to the end of the road, the first node is the start. + mAddNodeIdx = U32_MAX; + + setSelectedRoad( newRoad ); + setSelectedNode( newNode ); + + mMode = mAddNodeMode; + + // Disable the hover node while in addNodeMode, we + // don't want some random node enlarged. + mHoverNode = -1; + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + + // Create the UndoAction. + MECreateUndoAction *action = new MECreateUndoAction("Create MeshRoad"); + action->addObject( newRoad ); + + // Submit it. + undoMan->addAction( action ); + + return; + } + else if ( mMode == mAddNodeMode ) + { + // Oops the road got deleted, maybe from an undo action? + // Back to NormalMode. + if ( mSelRoad ) + { + // A double-click on a node in Normal mode means set AddNode mode. + if ( clickedNodeIdx == 0 ) + { + submitUndo( "Add Node" ); + mAddNodeIdx = clickedNodeIdx; + mMode = mAddNodeMode; + mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx ); + mIsDirty = true; + setSelectedNode( mSelNode ); + + return; + } + else + { + if( pClickedRoad && clickedNodeIdx == pClickedRoad->mNodes.size() - 1 ) + { + submitUndo( "Add Node" ); + mAddNodeIdx = U32_MAX; + mMode = mAddNodeMode; + U32 newNode = mSelRoad->addNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal); + mIsDirty = true; + setSelectedNode( newNode ); + return; + } + else + { + submitUndo( "Insert Node" ); + // A single-click on empty space while in + // AddNode mode means insert / add a node. + //submitUndo( "Add Node" ); + U32 newNode = mSelRoad->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx); + mIsDirty = true; + setSelectedNode( newNode ); + return; + } + } + } + + } + else if ( mMode == mInsertPointMode && mSelRoad != NULL ) + { + if ( pClickedRoad == mSelRoad ) + { + // NOTE: I guess we have to determine the if the clicked ray intersects a road but not a specific node... + // in order to handle inserting nodes in the same way as for fxRoad + + U32 prevNodeIdx = insertNodeIdx; + U32 nextNodeIdx = ( prevNodeIdx + 1 > mSelRoad->mNodes.size() - 1 ) ? prevNodeIdx : prevNodeIdx + 1; + + const MeshRoadNode &prevNode = mSelRoad->mNodes[prevNodeIdx]; + const MeshRoadNode &nextNode = mSelRoad->mNodes[nextNodeIdx]; + + F32 width = ( prevNode.width + nextNode.width ) * 0.5f; + F32 depth = ( prevNode.depth + nextNode.depth ) * 0.5f; + Point3F normal = ( prevNode.normal + nextNode.normal ) * 0.5f; + normal.normalize(); + + submitUndo( "Insert Node" ); + U32 newNode = mSelRoad->insertNode( collisionPnt, width, depth, normal, insertNodeIdx + 1 ); + mIsDirty = true; + setSelectedNode( newNode ); + return; + } + } + else if ( mMode == mRemovePointMode && mSelRoad != NULL ) + { + if ( nodeClicked && pClickedRoad == mSelRoad ) + { + setSelectedNode( clickedNodeIdx ); + deleteSelectedNode(); + return; + } + } + else if ( mMode == mMovePointMode ) + { + if ( nodeClicked && pClickedRoad == mSelRoad ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } + else if ( mMode == mScalePointMode ) + { + if ( nodeClicked && pClickedRoad == mSelRoad ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } + else if ( mMode == mRotatePointMode ) + { + if ( nodeClicked && pClickedRoad == mSelRoad ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } +} + +void GuiMeshRoadEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event) +{ + //mIsPanning = true; +} + +void GuiMeshRoadEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event) +{ + //mIsPanning = false; +} + +void GuiMeshRoadEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) +{ + mGizmo->on3DMouseUp(event); + + mSavedDrag = false; +} + +void GuiMeshRoadEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) +{ + if ( mSelRoad != NULL && mMode == mAddNodeMode ) + { + Point3F pos; + + mSelRoad->disableCollision(); + if ( getStaticPos( event, pos ) ) + { + pos.z += mSelRoad->getNodeDepth( mSelNode ) * 0.5f; + mSelRoad->setNodePosition( mSelNode, pos ); + mIsDirty = true; + } + mSelRoad->enableCollision(); + + return; + } + + if ( mSelRoad != NULL && mSelNode != -1 ) + { + mGizmo->on3DMouseMove( event ); + //mGizmo.collideAxisGizmo( event ); + //Con::printf( "SelectedAxis: %i", mGizmo.getSelectedAxis() ); + } + + // Is cursor hovering over a river? + if ( mMode == mSelectMeshRoadMode ) + { + mHoverRoad = NULL; + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + + if ( gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri) ) + { + MeshRoad *pRoad = NULL; + pRoad = dynamic_cast(ri.object); + + if ( pRoad ) + mHoverRoad = pRoad; + } + } + + // Is cursor over a node? + if ( mHoverRoad ) + { + MeshRoad *pRoad = NULL; + S32 nodeIdx = -1; + for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) + { + pRoad = static_cast( *iter ); + + nodeIdx = _getNodeAtScreenPos( pRoad, event.mousePoint ); + + if ( nodeIdx != -1 ) + { + mHoverRoad = pRoad; + break; + } + } + + mHoverNode = nodeIdx; + } +} + +void GuiMeshRoadEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + // If a node is not selected, we don't care about drags, + // drags are only used for modifying nodes. + if ( !mSelRoad || mSelNode == -1 || mMode == mAddNodeMode ) + return; + + // If we haven't already saved, + // save an undo action to get back to this state, + // before we make any modifications to the selected node. + if ( !mSavedDrag ) + { + submitUndo( "Modify Node" ); + mSavedDrag = true; + } + + // If shift is held and we haven't already copied the node, + // make a copy of the selected node and select it. + if ( event.modifier & SI_SHIFT && !mHasCopied && mSelRoad->isEndNode( mSelNode ) ) + { + const MeshRoadNode &data = mSelRoad->getNode( mSelNode ); + + U32 insertIdx = ( mSelNode == 0 ) ? 0 : U32_MAX; + U32 newNodeIdx = mSelRoad->insertNode( data.point, data.width, data.depth, data.normal, insertIdx ); + mIsDirty = true; + + mSelNode = -1; + setSelectedNode( newNodeIdx ); + + mHasCopied = true; + } + + // Let the Gizmo handle the drag, eg, modify its transforms + mGizmo->on3DMouseDragged( event ); + if ( mGizmo->isDirty() ) + { + Point3F pos = mGizmo->getPosition(); + Point3F scale = mGizmo->getScale(); + const MatrixF &mat = mGizmo->getTransform(); + VectorF normal; + mat.getColumn( 2, &normal ); + + mSelRoad->setNode( pos, scale.x, scale.z, normal, mSelNode ); + mIsDirty = true; + mGizmo->markClean(); + + + } + + Con::executef( this, "onNodeModified", Con::getIntArg(mSelNode) ); +} + +void GuiMeshRoadEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +void GuiMeshRoadEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +bool GuiMeshRoadEditorCtrl::onKeyDown(const GuiEvent& event) +{ + if( event.keyCode == KEY_RETURN && mMode == mAddNodeMode ) + { + // Delete the node attached to the cursor. + deleteSelectedNode(); + mMode = mAddMeshRoadMode; + return true; + } + + return false; +} + +void GuiMeshRoadEditorCtrl::updateGuiInfo() +{ + // nothing to do +} + +void GuiMeshRoadEditorCtrl::renderScene(const RectI & updateRect) +{ + //GFXDrawUtil *drawer = GFX->getDrawUtil(); + + GFX->setStateBlock( mZDisableSB ); + + // get the projected size... + GameConnection* connection = GameConnection::getConnectionToServer(); + if(!connection) + return; + + // Grab the camera's transform + MatrixF mat; + connection->getControlCameraTransform(0, &mat); + + // Get the camera position + Point3F camPos; + mat.getColumn(3,&camPos); + + if ( mHoverRoad && mHoverRoad != mSelRoad ) + { + _drawSpline( mHoverRoad, mHoverSplineColor ); + } + + if ( mSelRoad ) + { + _drawSpline( mSelRoad, mSelectedSplineColor ); + + // Render Gizmo for selected node if were in either of the three transform modes + if ( mSelNode != -1 && ( mMode == mMovePointMode || mMode == mScalePointMode || mMode == mRotatePointMode ) ) + { + if( mMode == mMovePointMode ) + { + mGizmo->getProfile()->mode = MoveMode; + } + else if( mMode == mScalePointMode ) + { + mGizmo->getProfile()->mode = ScaleMode; + } + else if( mMode == mRotatePointMode ) + { + mGizmo->getProfile()->mode = RotateMode; + } + + const MeshRoadNode &node = mSelRoad->mNodes[mSelNode]; + + MatrixF objMat = mSelRoad->getNodeTransform(mSelNode); + Point3F objScale( node.width, 1.0f, node.depth ); + Point3F worldPos = node.point; + + mGizmo->set( objMat, worldPos, objScale ); + + mGizmo->renderGizmo(mLastCameraQuery.cameraMatrix); + } + } + + DebugDrawer::get()->render(); + + // Now draw all the 2d stuff! + GFX->setClipRect(updateRect); + + // Render Gizmo text + if ( mSelRoad && mSelNode != -1 ) + { + //mGizmo.setPosition(mSelRoad->mNodes[mSelNode].point); + mGizmo->renderText( mSaveViewport, mSaveModelview, mSaveProjection ); + } + + // Draw Control nodes for selecting and highlighted rivers + if ( mHoverRoad ) + _drawControlNodes( mHoverRoad, mHoverSplineColor ); + if ( mSelRoad ) + _drawControlNodes( mSelRoad, mSelectedSplineColor ); +} + +S32 GuiMeshRoadEditorCtrl::_getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi ) +{ + for ( U32 i = 0; i < pRoad->mNodes.size(); i++ ) + { + const Point3F &nodePos = pRoad->mNodes[i].point; + + Point3F screenPos; + project( nodePos, &screenPos ); + + if ( screenPos.z < 0.0f ) + continue; + + Point2I screenPosI( (S32)screenPos.x, (S32)screenPos.y ); + + RectI nodeScreenRect( screenPosI - mNodeHalfSize, mNodeHalfSize * 2 ); + + if ( nodeScreenRect.pointInRect(posi) ) + { + // we found a hit! + return i; + } + } + + return -1; +} + +void GuiMeshRoadEditorCtrl::_drawSpline( MeshRoad *river, const ColorI &color ) +{ + if ( river->mSlices.size() <= 1 ) + return; + + if ( MeshRoad::smShowSpline ) + { + // Render the River center-line + PrimBuild::color( color ); + PrimBuild::begin( GFXLineStrip, river->mSlices.size() ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p1 ); + } + PrimBuild::end(); + } + + if ( MeshRoad::smWireframe ) + { + // Left-side line + PrimBuild::color3i( 100, 100, 100 ); + PrimBuild::begin( GFXLineStrip, river->mSlices.size() ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p0 ); + } + PrimBuild::end(); + + // Right-side line + PrimBuild::begin( GFXLineStrip, river->mSlices.size() ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p2 ); + } + PrimBuild::end(); + + // Cross-sections + PrimBuild::begin( GFXLineList, river->mSlices.size() * 2 ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p0 ); + PrimBuild::vertex3fv( river->mSlices[i].p2 ); + } + PrimBuild::end(); + } + + // Segment +} + +void GuiMeshRoadEditorCtrl::_drawControlNodes( MeshRoad *river, const ColorI &color ) +{ + if ( !MeshRoad::smShowSpline ) + return; + + RectI bounds = getBounds(); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + bool isSelected = ( river == mSelRoad ); + bool isHighlighted = ( river == mHoverRoad ); + + for ( U32 i = 0; i < river->mNodes.size(); i++ ) + { + if ( false && isSelected && mSelNode == i ) + continue; + + const Point3F &wpos = river->mNodes[i].point; + + Point3F spos; + project( wpos, &spos ); + + if ( spos.z > 1.0f ) + continue; + + Point2I posi; + posi.x = spos.x; + posi.y = spos.y; + + if ( !bounds.pointInRect( posi ) ) + continue; + + ColorI theColor = color; + Point2I nodeHalfSize = mNodeHalfSize; + + if ( isHighlighted && mHoverNode == i ) + { + //theColor = mHoverNodeColor; + nodeHalfSize += Point2I(2,2); + } + + if ( isSelected ) + { + if ( mSelNode == i ) + { + theColor.set(0,0,255); + } + else if ( i == 0 ) + { + theColor.set(0,255,0); + } + else if ( i == river->mNodes.size() - 1 ) + { + theColor.set(255,0,0); + } + } + + drawer->drawRectFill( posi - nodeHalfSize, posi + nodeHalfSize, theColor ); + } +} + +bool GuiMeshRoadEditorCtrl::getStaticPos( const Gui3DMouseEvent & event, Point3F &tpos ) +{ + // Find clicked point on the terrain + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + bool hit; + + hit = gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri); + tpos = ri.point; + + return hit; +} + +void GuiMeshRoadEditorCtrl::deleteSelectedNode() +{ + if ( !mSelRoad || mSelNode == -1 ) + return; + + // If the Road has only two nodes remaining, + // delete the whole Road. + if ( mSelRoad->mNodes.size() <= 2 ) + { + deleteSelectedRoad( mMode != mAddNodeMode ); + } + else + { + if ( mMode != mAddNodeMode ) + submitUndo( "Delete Node" ); + + // Delete the SelectedNode of the SelectedRoad + mSelRoad->deleteNode( mSelNode ); + + // We deleted the Node but not the Road (it has nodes left) + // so decrement the currently selected node. + if ( mSelRoad->mNodes.size() <= mSelNode ) + setSelectedNode( mSelNode - 1 ); + else + { + // force gizmo to update to the selected nodes position + // the index didn't change but the node it refers to did. + U32 i = mSelNode; + mSelNode = -1; + setSelectedNode( i ); + } + } + + // If you were in addNodeMode, + // deleting a node should ends it. + //mMode = smNormalMode; +} + +void GuiMeshRoadEditorCtrl::deleteSelectedRoad( bool undoAble ) +{ + AssertFatal( mSelRoad != NULL, "GuiMeshRoadEditorCtrl::deleteSelectedRoad() - No Road is selected" ); + + // Not undoAble? Just delete it. + if ( !undoAble ) + { + mSelRoad->deleteObject(); + mIsDirty = true; + Con::executef( this, "onRoadSelected" ); + mSelNode = -1; + + return; + } + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + // Couldn't find it? Well just delete the Road. + Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + else + { + // Create the UndoAction. + MEDeleteUndoAction *action = new MEDeleteUndoAction("Deleted Road"); + action->deleteObject( mSelRoad ); + mIsDirty = true; + + // Submit it. + undoMan->addAction( action ); + } + + // ScriptCallback with 'NULL' parameter for no Road currently selected. + Con::executef( this, "onRoadSelected" ); + + // Clear the SelectedNode (it has been deleted along with the River). + setSelectedNode( -1 ); + mSelNode = -1; +} + +void GuiMeshRoadEditorCtrl::setMode( String mode, bool sourceShortcut = false ) +{ + mMode = mode; + + if( sourceShortcut ) + Con::executef( this, "paletteSync", mode ); +} + +void GuiMeshRoadEditorCtrl::setSelectedRoad( MeshRoad *road ) +{ + mSelRoad = road; + + if ( mSelRoad != NULL ) + Con::executef( this, "onRoadSelected", road->scriptThis() ); + else + Con::executef( this, "onRoadSelected" ); + + if ( mSelRoad != road ) + setSelectedNode(-1); +} + +void GuiMeshRoadEditorCtrl::setNodeWidth( F32 width ) +{ + if ( mSelRoad && mSelNode != -1 ) + { + mSelRoad->setNodeWidth( mSelNode, width ); + mIsDirty = true; + } +} + +F32 GuiMeshRoadEditorCtrl::getNodeWidth() +{ + if ( mSelRoad && mSelNode != -1 ) + return mSelRoad->getNodeWidth( mSelNode ); + + return 0.0f; +} + +void GuiMeshRoadEditorCtrl::setNodeDepth(F32 depth) +{ + if ( mSelRoad && mSelNode != -1 ) + { + mSelRoad->setNodeDepth( mSelNode, depth ); + mIsDirty = true; + } +} + +F32 GuiMeshRoadEditorCtrl::getNodeDepth() +{ + if ( mSelRoad && mSelNode != -1 ) + return mSelRoad->getNodeDepth( mSelNode ); + + return 0.0f; +} + +void GuiMeshRoadEditorCtrl::setNodePosition( Point3F pos ) +{ + if ( mSelRoad && mSelNode != -1 ) + { + mSelRoad->setNodePosition( mSelNode, pos ); + mIsDirty = true; + } +} + +Point3F GuiMeshRoadEditorCtrl::getNodePosition() +{ + if ( mSelRoad && mSelNode != -1 ) + return mSelRoad->getNodePosition( mSelNode ); + + return Point3F( 0, 0, 0 ); +} + +void GuiMeshRoadEditorCtrl::setNodeNormal( const VectorF &normal ) +{ + if ( mSelRoad && mSelNode != -1 ) + { + mSelRoad->setNodeNormal( mSelNode, normal ); + mIsDirty = true; + } +} + +VectorF GuiMeshRoadEditorCtrl::getNodeNormal() +{ + if ( mSelRoad && mSelNode != -1 ) + return mSelRoad->getNodeNormal( mSelNode ); + + return VectorF::Zero; +} + +void GuiMeshRoadEditorCtrl::setSelectedNode( S32 node ) +{ + if ( mSelNode == node ) + return; + + mSelNode = node; + if ( mSelNode != -1 ) + { + const MeshRoadNode &node = mSelRoad->mNodes[mSelNode]; + + MatrixF objMat = mSelRoad->getNodeTransform(mSelNode); + Point3F objScale( node.width, 1.0f, node.depth ); + Point3F worldPos = node.point; + + mGizmo->set( objMat, worldPos, objScale ); + } + + if ( mSelNode != -1 ) + Con::executef( this, "onNodeSelected", Con::getIntArg(mSelNode) ); + else + Con::executef( this, "onNodeSelected", Con::getIntArg(-1) ); +} + +void GuiMeshRoadEditorCtrl::submitUndo( const UTF8 *name ) +{ + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiMeshRoadEditorCtrl::submitUndo() - EUndoManager not found!" ); + return; + } + + // Setup the action. + GuiMeshRoadEditorUndoAction *action = new GuiMeshRoadEditorUndoAction( name ); + + action->mObjId = mSelRoad->getId(); + //action->mMetersPerSegment = mSelRoad->mMetersPerSegment; + action->mEditor = this; + + for( U32 i = 0; i < mSelRoad->mNodes.size(); i++ ) + { + action->mNodes.push_back( mSelRoad->mNodes[i] ); + } + + undoMan->addAction( action ); +} + +void GuiMeshRoadEditorCtrl::matchTerrainToRoad() +{ + if ( !mSelRoad ) + return; + + // Not implemented, but a potentially useful idea. + // Move manipulate the terrain so that the MeshRoad appears to be sitting + // on top of it. + + // The opposite could also be useful, manipulate the MeshRoad to line up + // with the terrain underneath it. +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, deleteNode, void, 2, 2, "deleteNode()" ) +{ + object->deleteSelectedNode(); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, getMode, const char*, 2, 2, "" ) +{ + return object->getMode(); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, setMode, void, 3, 3, "setMode( String mode )" ) +{ + String newMode = ( argv[2] ); + object->setMode( newMode ); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, getNodeWidth, F32, 2, 2, "" ) +{ + return object->getNodeWidth(); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, setNodeWidth, void, 3, 3, "" ) +{ + object->setNodeWidth( dAtof(argv[2]) ); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, getNodeDepth, F32, 2, 2, "" ) +{ + return object->getNodeDepth(); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, setNodeDepth, void, 3, 3, "" ) +{ + object->setNodeDepth( dAtof(argv[2]) ); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, getNodePosition, const char*, 2, 2, "" ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + + dSprintf(returnBuffer, 256, "%f %f %f", + object->getNodePosition().x, object->getNodePosition().y, object->getNodePosition().z); + + return returnBuffer; +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, setNodePosition, void, 3, 3, "" ) +{ + Point3F pos; + + S32 count = dSscanf( argv[2], "%f %f %f", + &pos.x, &pos.y, &pos.z); + + if ( (count != 3) ) + { + Con::printf("Failed to parse node information \"px py pz\" from '%s'", argv[3]); + return; + } + + object->setNodePosition( pos ); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, getNodeNormal, const char*, 2, 2, "" ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + + dSprintf(returnBuffer, 256, "%f %f %f", + object->getNodeNormal().x, object->getNodeNormal().y, object->getNodeNormal().z); + + return returnBuffer; +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, setNodeNormal, void, 3, 3, "" ) +{ + VectorF normal; + + S32 count = dSscanf( argv[2], "%f %f %f", + &normal.x, &normal.y, &normal.z); + + if ( (count != 3) ) + { + Con::printf("Failed to parse node information \"px py pz\" from '%s'", argv[3]); + return; + } + + object->setNodeNormal( normal ); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, setSelectedRoad, void, 2, 3, "" ) +{ + if ( argc == 2 ) + object->setSelectedRoad(NULL); + else + { + MeshRoad *road = NULL; + if ( Sim::findObject( argv[2], road ) ) + object->setSelectedRoad(road); + } +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, getSelectedRoad, const char*, 2, 2, "" ) +{ + MeshRoad *road = object->getSelectedRoad(); + if ( !road ) + return NULL; + + return road->scriptThis(); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, regenerate, void, 2, 2, "" ) +{ + MeshRoad *road = object->getSelectedRoad(); + if ( road ) + road->regenerate(); +} + +ConsoleMethod( GuiMeshRoadEditorCtrl, matchTerrainToRoad, void, 2, 2, "" ) +{ + object->matchTerrainToRoad(); +} \ No newline at end of file diff --git a/environment/editors/guiMeshRoadEditorCtrl.h b/environment/editors/guiMeshRoadEditorCtrl.h new file mode 100644 index 0000000..7e1d7b1 --- /dev/null +++ b/environment/editors/guiMeshRoadEditorCtrl.h @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMESHROADEDITORCTRL_H_ +#define _GUIMESHROADEDITORCTRL_H_ + +#ifndef _EDITTSCTRL_H_ +#include "gui/worldEditor/editTSCtrl.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _GIZMO_H_ +#include "gui/worldEditor/gizmo.h" +#endif +#ifndef _MESHROAD_H_ +#include "environment/meshRoad.h" +#endif + +class GameBase; + +class GuiMeshRoadEditorCtrl : public EditTSCtrl +{ + typedef EditTSCtrl Parent; + + public: + + friend class GuiMeshRoadEditorUndoAction; + + //static StringTableEntry smNormalMode; + //static StringTableEntry smAddNodeMode; + + String mSelectMeshRoadMode; + String mAddMeshRoadMode; + String mAddNodeMode; + String mInsertPointMode; + String mRemovePointMode; + String mMovePointMode; + String mScalePointMode; + String mRotatePointMode; + + GuiMeshRoadEditorCtrl(); + ~GuiMeshRoadEditorCtrl(); + + DECLARE_CONOBJECT(GuiMeshRoadEditorCtrl); + + // SimObject + bool onAdd(); + static void initPersistFields(); + + // GuiControl + virtual void onSleep(); + + // EditTSCtrl + bool onKeyDown(const GuiEvent& event); + void get3DCursor( GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_ ); + void on3DMouseDown(const Gui3DMouseEvent & event); + void on3DMouseUp(const Gui3DMouseEvent & event); + void on3DMouseMove(const Gui3DMouseEvent & event); + void on3DMouseDragged(const Gui3DMouseEvent & event); + void on3DMouseEnter(const Gui3DMouseEvent & event); + void on3DMouseLeave(const Gui3DMouseEvent & event); + void on3DRightMouseDown(const Gui3DMouseEvent & event); + void on3DRightMouseUp(const Gui3DMouseEvent & event); + void updateGuiInfo(); + void renderScene(const RectI & updateRect); + + // GuiRiverEditorCtrl + bool getStaticPos( const Gui3DMouseEvent & event, Point3F &tpos ); + void deleteSelectedNode(); + void deleteSelectedRoad( bool undoAble = true ); + + void setMode( String mode, bool sourceShortcut ); + String getMode() { return mMode; } + + //void setGizmoMode( Gizmo::Mode mode ) { mGizmo.setMode( mode ); } + + void setSelectedRoad( MeshRoad *road ); + MeshRoad* getSelectedRoad() { return mSelRoad; }; + void setSelectedNode( S32 node ); + + F32 getNodeWidth(); + void setNodeWidth( F32 width ); + + F32 getNodeDepth(); + void setNodeDepth( F32 depth ); + + Point3F getNodePosition(); + void setNodePosition( Point3F pos ); + + VectorF getNodeNormal(); + void setNodeNormal( const VectorF &normal ); + + void matchTerrainToRoad(); + + protected: + + S32 _getNodeAtScreenPos( const MeshRoad *pRoad, const Point2I &posi ); + void _drawSpline( MeshRoad *road, const ColorI &color ); + void _drawControlNodes( MeshRoad *road, const ColorI &color ); + + void submitUndo( const UTF8 *name = "Action" ); + + GFXStateBlockRef mZDisableSB; + GFXStateBlockRef mZEnableSB; + + bool mSavedDrag; + bool mIsDirty; + + SimSet *mRoadSet; + S32 mSelNode; + S32 mHoverNode; + U32 mAddNodeIdx; + SimObjectPtr mSelRoad; + SimObjectPtr mHoverRoad; + + String mMode; + + F32 mDefaultWidth; + F32 mDefaultDepth; + VectorF mDefaultNormal; + + Point2I mNodeHalfSize; + + ColorI mHoverSplineColor; + ColorI mSelectedSplineColor; + ColorI mHoverNodeColor; + + bool mHasCopied; +}; + +class GuiMeshRoadEditorUndoAction : public UndoAction +{ + public: + + GuiMeshRoadEditorUndoAction( const UTF8* actionName ) : UndoAction( actionName ) + { + } + + GuiMeshRoadEditorCtrl *mEditor; + + Vector mNodes; + + SimObjectId mObjId; + F32 mMetersPerSegment; + + virtual void undo(); + virtual void redo() { undo(); } +}; + +#endif // _GUIMESHROADEDITORCTRL_H_ + + + diff --git a/environment/editors/guiRiverEditorCtrl.cpp b/environment/editors/guiRiverEditorCtrl.cpp new file mode 100644 index 0000000..0bc3800 --- /dev/null +++ b/environment/editors/guiRiverEditorCtrl.cpp @@ -0,0 +1,1466 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/editors/guiRiverEditorCtrl.h" + +#include "console/consoleTypes.h" +#include "environment/river.h" +#include "renderInstance/renderPassManager.h" +#include "collision/collision.h" +#include "math/util/frustum.h" +#include "math/mathUtils.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxTextureHandle.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonCtrl.h" +#include "gui/worldEditor/undoActions.h" +#include "T3D/gameConnection.h" + +IMPLEMENT_CONOBJECT(GuiRiverEditorCtrl); + +GuiRiverEditorCtrl::GuiRiverEditorCtrl() + : mDefaultNormal( 0, 0, 1 ), + mDefaultWidth( 10.0f ), + mDefaultDepth( 5.0f ) +{ + // Each of the mode names directly correlates with the River Editor's + // tool palette + mSelectRiverMode = "RiverEditorSelectMode"; + mAddRiverMode = "RiverEditorAddRiverMode"; + mMovePointMode = "RiverEditorMoveMode"; + mRotatePointMode = "RiverEditorRotateMode"; + mScalePointMode = "RiverEditorScaleMode"; + mAddNodeMode = "RiverEditorAddNodeMode"; + mInsertPointMode = "RiverEditorInsertPointMode"; + mRemovePointMode = "RiverEditorRemovePointMode"; + + mMode = mSelectRiverMode; + + mRiverSet = NULL; + mSelNode = -1; + mSelRiver = NULL; + mHoverRiver = NULL; + mAddNodeIdx = 0; + mHoverNode = -1; + + mInsertIdx = -1; + + mStartWidth = -1.0f; + mStartHeight = -1.0f; + mStartX = 0; + + mIsDirty = false; + + mNodeHalfSize.set(4,4); + + mNodeSphereRadius = 15.0f; + mNodeSphereFillColor.set( 15,15,100,145 ); + mNodeSphereLineColor.set( 25,25,25,0 ); + mHoverSplineColor.set( 255,0,0,255 ); + mSelectedSplineColor.set( 0,255,0,255 ); + mHoverNodeColor.set( 255,255,255,255 ); + + mStartDragMousePoint = InvalidMousePoint; + //mMoveNodeCursor = NULL; + //mAddNodeCursor = NULL; + //mInsertNodeCursor = NULL; + //mResizeNodeCursor = NULL; +} + +GuiRiverEditorCtrl::~GuiRiverEditorCtrl() +{ + // nothing to do +} + +void GuiRiverEditorUndoAction::undo() +{ + River *river = NULL; + if ( !Sim::findObject( mObjId, river ) ) + return; + + // Temporarily save the Rivers current data. + F32 metersPerSeg = river->mMetersPerSegment; + Vector nodes; + nodes.merge( river->mNodes ); + + // Restore the River properties saved in the UndoAction + river->mMetersPerSegment = mMetersPerSegment; + + // Restore the Nodes saved in the UndoAction + river->mNodes.clear(); + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + river->_addNode( mNodes[i].point, mNodes[i].width, mNodes[i].depth, mNodes[i].normal ); + } + + // Regenerate the River + river->regenerate(); + + // If applicable set the selected River and node + mRiverEditor->mSelRiver = river; + mRiverEditor->mSelNode = -1; + + // Now save the previous River data in this UndoAction + // since an undo action must become a redo action and vice-versa + mMetersPerSegment = metersPerSeg; + mNodes.clear(); + mNodes.merge( nodes ); +} + +bool GuiRiverEditorCtrl::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + mRiverSet = River::getServerSet(); + + GFXStateBlockDesc desc; + desc.fillMode = GFXFillSolid; + desc.setBlend( false ); + desc.setZReadWrite( false, false ); + desc.setCullMode( GFXCullNone ); + + mZDisableSB = GFX->createStateBlock(desc); + + desc.setZReadWrite( true, true ); + mZEnableSB = GFX->createStateBlock(desc); + + SceneGraph::getPreRenderSignal().notify( this, &GuiRiverEditorCtrl::_prepRenderImage ); + + return true; +} + +void GuiRiverEditorCtrl::initPersistFields() +{ + addField( "DefaultWidth", TypeF32, Offset( mDefaultWidth, GuiRiverEditorCtrl ) ); + addField( "DefaultDepth", TypeF32, Offset( mDefaultDepth, GuiRiverEditorCtrl ) ); + addField( "DefaultNormal", TypePoint3F,Offset( mDefaultNormal, GuiRiverEditorCtrl ) ); + addField( "HoverSplineColor", TypeColorI, Offset( mHoverSplineColor, GuiRiverEditorCtrl ) ); + addField( "SelectedSplineColor", TypeColorI, Offset( mSelectedSplineColor, GuiRiverEditorCtrl ) ); + addField( "HoverNodeColor", TypeColorI, Offset( mHoverNodeColor, GuiRiverEditorCtrl ) ); + addField( "isDirty", TypeBool, Offset( mIsDirty, GuiRiverEditorCtrl ) ); + //addField( "MoveNodeCursor", TypeSimObjectPtr, Offset( mMoveNodeCursor, GuiRiverEditorCtrl) ); + //addField( "AddNodeCursor", TypeSimObjectPtr, Offset( mAddNodeCursor, GuiRiverEditorCtrl) ); + //addField( "InsertNodeCursor", TypeSimObjectPtr, Offset( mInsertNodeCursor, GuiRiverEditorCtrl) ); + //addField( "ResizeNodeCursor", TypeSimObjectPtr, Offset( mResizeNodeCursor, GuiRiverEditorCtrl) ); + + Parent::initPersistFields(); +} + +void GuiRiverEditorCtrl::onSleep() +{ + Parent::onSleep(); + + mMode = mSelectRiverMode; + mHoverNode = -1; + mHoverRiver = NULL; + setSelectedNode(-1); + //mSelRiver = NULL; + //mSelNode = -1; +} + +void GuiRiverEditorCtrl::get3DCursor( GuiCursor *&cursor, + bool &visible, + const Gui3DMouseEvent &event_ ) +{ + //cursor = mAddNodeCursor; + //visible = false; + + cursor = NULL; + visible = false; + + GuiCanvas *root = getRoot(); + if ( !root ) + return; + + S32 currCursor = PlatformCursorController::curArrow; + + if ( root->mCursorChanged == currCursor ) + return; + + PlatformWindow *window = root->getPlatformWindow(); + PlatformCursorController *controller = window->getCursorController(); + + // We've already changed the cursor, + // so set it back before we change it again. + if( root->mCursorChanged != -1) + controller->popCursor(); + + // Now change the cursor shape + controller->pushCursor(currCursor); + root->mCursorChanged = currCursor; +} + +void GuiRiverEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) +{ + mGizmo->on3DMouseDown( event ); + + if ( !isFirstResponder() ) + setFirstResponder(); + + // Get the raycast collision position + Point3F tPos; + if ( !getStaticPos( event, tPos ) ) + return; + + // Construct a LineSegment from the camera position to 1000 meters away in + // the direction clicked. + // If that segment hits the terrain, truncate the ray to only be that length. + + // We will use a LineSegment/Sphere intersection test to determine if a RiverNode + // was clicked. + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + + if ( gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri) ) + endPnt = ri.point; + + River *riverPtr = NULL; + River *clickedRiverPtr = NULL; + + // Did we click on a river? check current selection first + U32 insertNodeIdx = -1; + Point3F collisionPnt; + if ( mSelRiver != NULL && mSelRiver->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) ) + { + clickedRiverPtr = mSelRiver; + } + else + { + for ( SimSetIterator iter(mRiverSet); *iter; ++iter ) + { + riverPtr = static_cast( *iter ); + if ( riverPtr->collideRay( event.pos, event.vec, &insertNodeIdx, &collisionPnt ) ) + { + clickedRiverPtr = riverPtr; + break; + } + } + } + + // Did we click on a riverNode? + bool nodeClicked = false; + S32 clickedNodeIdx = -1; + F32 clickedNodeDist = mNodeSphereRadius; + + // If we clicked on the currently selected river, only scan its nodes + if ( mSelRiver != NULL && clickedRiverPtr == mSelRiver ) + { + for ( U32 i = 0; i < mSelRiver->mNodes.size(); i++ ) + { + const Point3F &nodePos = mSelRiver->mNodes[i].point; + + Point3F screenPos; + project( nodePos, &screenPos ); + + F32 dist = ( event.mousePoint - Point2I(screenPos.x, screenPos.y) ).len(); + if ( dist < clickedNodeDist ) + { + clickedNodeDist = dist; + clickedNodeIdx = i; + nodeClicked = true; + } + } + } + else + { + for ( SimSetIterator iter(mRiverSet); *iter; ++iter ) + { + riverPtr = static_cast( *iter ); + + for ( U32 i = 0; i < riverPtr->mNodes.size(); i++ ) + { + const Point3F &nodePos = riverPtr->mNodes[i].point; + + Point3F screenPos; + project( nodePos, &screenPos ); + + F32 dist = ( event.mousePoint - Point2I(screenPos.x, screenPos.y) ).len(); + if ( dist < clickedNodeDist ) + { + // we found a hit! + clickedNodeDist = dist; + clickedNodeIdx = i; + nodeClicked = true; + clickedRiverPtr = riverPtr; + } + } + } + } + + // shortcuts + bool dblClick = ( event.mouseClickCount > 1 ); + if( dblClick ) + { + if( mMode == mSelectRiverMode ) + { + setMode( mAddRiverMode, true ); + return; + } + if( mMode == mAddNodeMode ) + { + // Delete the node attached to the cursor. + deleteSelectedNode(); + mMode = mAddRiverMode; + return; + } + } + + //this check is here in order to bounce back from deleting a whole road with ctrl+z + //this check places the editor back into addrivermode + if ( mMode == mAddNodeMode ) + { + if ( !mSelRiver ) + mMode = mAddRiverMode; + } + + if ( mMode == mSelectRiverMode ) + { + // Did not click on a River or a node. + if ( !clickedRiverPtr ) + { + setSelectedRiver( NULL ); + setSelectedNode( -1 ); + + return; + } + + // Clicked on a River that wasn't the currently selected River. + if ( clickedRiverPtr != mSelRiver ) + { + setSelectedRiver( clickedRiverPtr ); + setSelectedNode( clickedNodeIdx ); + return; + } + + // Clicked on a node in the currently selected River that wasn't + // the currently selected node. + if ( nodeClicked ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } + else if ( mMode == mAddRiverMode ) + { + if ( nodeClicked ) + { + // A double-click on a node in Normal mode means set AddNode mode. + if ( clickedNodeIdx == 0 ) + { + setSelectedRiver( clickedRiverPtr ); + setSelectedNode( clickedNodeIdx ); + + mAddNodeIdx = clickedNodeIdx; + mMode = mAddNodeMode; + + mSelNode = mSelRiver->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx ); + mIsDirty = true; + + return; + } + else if ( clickedNodeIdx == clickedRiverPtr->mNodes.size() - 1 ) + { + setSelectedRiver( clickedRiverPtr ); + setSelectedNode( clickedNodeIdx ); + + mAddNodeIdx = U32_MAX; + mMode = mAddNodeMode; + + mSelNode = mSelRiver->addNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal); + mIsDirty = true; + setSelectedNode( mSelNode ); + + return; + } + } + + if ( !isMethod( "createRiver" ) ) + { + Con::errorf( "GuiRiverEditorCtrl::on3DMouseDown - createRiver method does not exist." ); + return; + } + + const char *res = Con::executef( this, "createRiver" ); + + River *newRiver; + if ( !Sim::findObject( res, newRiver ) ) + { + Con::errorf( "GuiRiverEditorCtrl::on3DMouseDown - createRiver method did not return a river object." ); + return; + } + + // Add to MissionGroup + SimGroup *missionGroup; + if ( !Sim::findObject( "MissionGroup", missionGroup ) ) + Con::errorf( "GuiRiverEditorCtrl - could not find MissionGroup to add new River" ); + else + missionGroup->addObject( newRiver ); + + Point3F pos( endPnt ); + pos.z += mDefaultDepth * 0.5f; + + newRiver->insertNode( pos, mDefaultWidth, mDefaultDepth, mDefaultNormal, 0 ); + U32 newNode = newRiver->insertNode( pos, mDefaultWidth, mDefaultDepth, mDefaultNormal, 1 ); + + // Always add to the end of the road, the first node is the start. + mAddNodeIdx = U32_MAX; + + setSelectedRiver( newRiver ); + setSelectedNode( newNode ); + + mMode = mAddNodeMode; + + // Disable the hover node while in addNodeMode, we + // don't want some random node enlarged. + mHoverNode = -1; + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + + // Create the UndoAction. + MECreateUndoAction *action = new MECreateUndoAction("Create MeshRoad"); + action->addObject( newRiver ); + + // Submit it. + undoMan->addAction( action ); + + return; + } + else if ( mMode == mAddNodeMode ) + { + // Oops the road got deleted, maybe from an undo action? + // Back to NormalMode. + if ( mSelRiver ) + { + // A double-click on a node in Normal mode means set AddNode mode. + if ( clickedNodeIdx == 0 ) + { + submitUndo( "Add Node" ); + mAddNodeIdx = clickedNodeIdx; + mMode = mAddNodeMode; + mSelNode = mSelRiver->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx ); + mIsDirty = true; + setSelectedNode( mSelNode ); + + return; + } + else + { + if( clickedRiverPtr && clickedNodeIdx == clickedRiverPtr->mNodes.size() - 1 ) + { + submitUndo( "Add Node" ); + mAddNodeIdx = U32_MAX; + mMode = mAddNodeMode; + U32 newNode = mSelRiver->addNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal); + mIsDirty = true; + setSelectedNode( newNode ); + + return; + } + else + { + submitUndo( "Insert Node" ); + // A single-click on empty space while in + // AddNode mode means insert / add a node. + //submitUndo( "Add Node" ); + //F32 width = mSelRiver->getNodeWidth( mSelNode ); + U32 newNode = mSelRiver->insertNode( tPos, mDefaultWidth, mDefaultDepth, mDefaultNormal, mAddNodeIdx); + mIsDirty = true; + setSelectedNode( newNode ); + + return; + } + } + } + } + else if ( mMode == mInsertPointMode && mSelRiver != NULL ) + { + if ( clickedRiverPtr == mSelRiver ) + { + // NOTE: I guess we have to determine the if the clicked ray intersects a road but not a specific node... + // in order to handle inserting nodes in the same way as for DecalRoad + + U32 prevNodeIdx = insertNodeIdx; + U32 nextNodeIdx = ( prevNodeIdx + 1 > mSelRiver->mNodes.size() - 1 ) ? prevNodeIdx : prevNodeIdx + 1; + + const RiverNode &prevNode = mSelRiver->mNodes[prevNodeIdx]; + const RiverNode &nextNode = mSelRiver->mNodes[nextNodeIdx]; + + F32 width = ( prevNode.width + nextNode.width ) * 0.5f; + F32 depth = ( prevNode.depth + nextNode.depth ) * 0.5f; + Point3F normal = ( prevNode.normal + nextNode.normal ) * 0.5f; + normal.normalize(); + + submitUndo( "Insert Node" ); + U32 newNode = mSelRiver->insertNode( collisionPnt, width, depth, normal, insertNodeIdx + 1 ); + mIsDirty = true; + setSelectedNode( newNode ); + + return; + } + } + else if ( mMode == mRemovePointMode && mSelRiver != NULL ) + { + if ( nodeClicked && clickedRiverPtr == mSelRiver ) + { + setSelectedNode( clickedNodeIdx ); + deleteSelectedNode(); + return; + } + } + else if ( mMode == mMovePointMode ) + { + if ( nodeClicked && clickedRiverPtr == mSelRiver ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } + else if ( mMode == mScalePointMode ) + { + if ( nodeClicked && clickedRiverPtr == mSelRiver ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } + else if ( mMode == mRotatePointMode ) + { + if ( nodeClicked && clickedRiverPtr == mSelRiver ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } +} + +void GuiRiverEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event) +{ + //mIsPanning = true; +} + +void GuiRiverEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event) +{ + //mIsPanning = false; +} + +void GuiRiverEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) +{ + // Keep the Gizmo up to date. + mGizmo->on3DMouseUp( event ); + + mStartWidth = -1.0f; + mStartHeight = -1.0f; + mSavedDrag = false; +} + +void GuiRiverEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) +{ + if ( mSelRiver != NULL && mMode == mAddNodeMode ) + { + Point3F pos; + if ( getStaticPos( event, pos ) ) + { + pos.z += mSelRiver->getNodeDepth(mSelNode) * 0.5f; + mSelRiver->setNodePosition( mSelNode, pos ); + mIsDirty = true; + } + + return; + } + + if ( mSelRiver != NULL && mSelNode != -1 ) + mGizmo->on3DMouseMove( event ); + + // Is cursor hovering over a river? + if ( mMode == mSelectRiverMode ) + { + mHoverRiver = NULL; + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + + if ( gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri) ) + endPnt = ri.point; + + River *pRiver = NULL; + + for ( SimSetIterator iter(mRiverSet); *iter; ++iter ) + { + pRiver = static_cast( *iter ); + if ( pRiver->collideRay( event.pos, event.vec ) ) + { + mHoverRiver = pRiver; + break; + } + } + } + + // Is cursor hovering over a RiverNode? + if ( mHoverRiver ) + { + River *pRiver = mHoverRiver; + + S32 hoverNodeIdx = -1; + F32 hoverNodeDist = mNodeSphereRadius; + + //for ( SimSetIterator iter(mRiverSet); *iter; ++iter ) + //{ + // River *pRiver = static_cast( *iter ); + + for ( U32 i = 0; i < pRiver->mNodes.size(); i++ ) + { + const Point3F &nodePos = pRiver->mNodes[i].point; + + Point3F screenPos; + project( nodePos, &screenPos ); + + F32 dist = ( event.mousePoint - Point2I(screenPos.x, screenPos.y) ).len(); + if ( dist < hoverNodeDist ) + { + // we found a hit! + hoverNodeDist = dist; + hoverNodeIdx = i; + } + } + //} + + mHoverNode = hoverNodeIdx; + } +} + +void GuiRiverEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + // If a node is not selected, we don't care about drags, + // drags are only used for modifying nodes. + if ( !mSelRiver || mSelNode == -1 || mMode == mAddNodeMode) + return; + + // If we haven't already saved, + // save an undo action to get back to this state, + // before we make any modifications to the selected node. + if ( !mSavedDrag ) + { + submitUndo( "Modify Node" ); + mSavedDrag = true; + } + + // Let the gizmo handle the drag, eg, modify its transforms + mGizmo->on3DMouseDragged( event ); + if ( mGizmo->isDirty() ) + { + Point3F pos = mGizmo->getPosition(); + Point3F scale = mGizmo->getScale(); + const MatrixF &mat = mGizmo->getTransform(); + VectorF normal; + mat.getColumn( 2, &normal ); + + mSelRiver->setNode( pos, scale.x, scale.z, normal, mSelNode ); + mIsDirty = true; + + + } + Con::executef( this, "onNodeModified", Con::getIntArg(mSelNode) ); + /* + // If we are just starting a new drag, + // we need to save the starting screen position of the mouse, + // and the starting position of the selected node. + if ( mStartDragMousePoint == InvalidMousePoint ) + { + mStartDragMousePoint = event.mousePoint; + mStartDragNodePos = mSelRiver->getNodePosition( mSelNode ); + } + + MathUtils::Line clickLine; + clickLine.p = event.pos; + clickLine.d = event.vec; + + MathUtils::Line axisLine; + axisLine.p = mStartDragNodePos; + axisLine.d = mGizmo.selectionToAxisVector( mGizmoSelection ); + + MathUtils::LineSegment segment; + + MathUtils::mShortestSegmentBetweenLines( clickLine, axisLine, segment ); + + // Segment.p1 is the closest point on the axis line, + // We want to put the selected gizmo handle at that point, + // So calculate the offset from the handle to the centerPoint to + // determine the gizmo's position. + mSelRiver->setNodePosition( mSelNode, segment.p1 ); + */ + + /* + // Convert the delta (dragged mouse distance) from screen space + // into world space. + Point2I deltaScreen = event.mousePoint - mStartDragMousePoint; + + F32 worldDist = ( event.pos - mStartDragNodePos ).len(); + + Point2F deltaWorld; + deltaWorld.x = GFX->unprojectRadius( worldDist, deltaScreen.x ); + deltaWorld.y = GFX->unprojectRadius( worldDist, deltaScreen.y ); + + // Now modify the selected node depending on the kind of operation we are doing. + if ( mGizmoSelection == Gizmo::Axis_X ) + { + Point3F newPos = mStartDragNodePos; + newPos.x += deltaWorld.x; + mSelRiver->setNodePosition( mSelNode, newPos ); + } + else if ( mGizmoSelection == Gizmo::Axis_Y ) + { + Point3F newPos = mStartDragNodePos; + newPos.y += deltaWorld.x; + mSelRiver->setNodePosition( mSelNode, newPos ); + } + else if ( mGizmoSelection == Gizmo::Axis_Z ) + { + Point3F newPos = mStartDragNodePos; + newPos.z += deltaWorld.y; + mSelRiver->setNodePosition( mSelNode, newPos ); + } + */ + + /* + F32 height = mStartHeight + deltaWorldX; + Con::printf( "height = %g", height ); + + mSelRiver->setNodeHeight( mSelNode, height ); + + Con::executef( this, "onNodeHeightModified", Con::getFloatArg(height) ); + + + if ( event.modifier & SI_PRIMARY_CTRL ) + { + //Point3F tPos; + //if ( !getStaticPos( event, tPos ) ) + // return; + + if ( mStartHeight == -1.0f ) + { + mStartHeight = mSelRiver->mNodes[mSelNode].point.z; + + mStartX = event.mousePoint.x; + mStartWorld = mSelRiver->mNodes[mSelNode].point; + } + + S32 deltaScreenX = event.mousePoint.x - mStartX; + + F32 worldDist = ( event.pos - mStartWorld ).len(); + + F32 deltaWorldX = GFX->unprojectRadius( worldDist, deltaScreenX ); + + F32 height = mStartHeight + deltaWorldX; + Con::printf( "height = %g", height ); + + mSelRiver->setNodeHeight( mSelNode, height ); + + Con::executef( this, "onNodeHeightModified", Con::getFloatArg(height) ); + } + else if ( event.modifier & SI_SHIFT ) + { + Point3F tPos; + if ( !getStaticPos( event, tPos ) ) + return; + + if ( mStartWidth == -1.0f ) + { + mStartWidth = mSelRiver->mNodes[mSelNode].width; + + mStartX = event.mousePoint.x; + mStartWorld = tPos; + } + + S32 deltaScreenX = event.mousePoint.x - mStartX; + + F32 worldDist = ( event.pos - mStartWorld ).len(); + + F32 deltaWorldX = GFX->unprojectRadius( worldDist, deltaScreenX ); + + F32 width = mStartWidth + deltaWorldX; + + mSelRiver->setNodeWidth( mSelNode, width ); + + Con::executef( this, "onNodeWidthModified", Con::getFloatArg(width) ); + } + else + { + Point3F tPos; + if ( !getStaticPos( event, tPos ) ) + return; + else if ( mGizmoSelection == Gizmo::Axis_Y ) + { + Point3F newPos = mStartDragNodePos; + newPos.y += deltaWorld.x; + mSelRiver->setNodePosition( mSelNode, newPos ); + } + mSelRiver->setNodePosition( mSelNode, tPos ); + } + */ +} + +void GuiRiverEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +void GuiRiverEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +bool GuiRiverEditorCtrl::onKeyDown(const GuiEvent& event) +{ + if( event.keyCode == KEY_RETURN && mMode == mAddNodeMode ) + { + // Delete the node attached to the cursor. + deleteSelectedNode(); + mMode = mAddRiverMode; + return true; + } + + return false; +} + +void GuiRiverEditorCtrl::updateGuiInfo() +{ + // nothing to do +} + +void GuiRiverEditorCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + PROFILE_SCOPE( GuiRiverEditorCtrl_OnRender ); + + Parent::onRender( offset, updateRect ); + return; +} + +void GuiRiverEditorCtrl::renderScene(const RectI & updateRect) +{ + //GFXDrawUtil *drawer = GFX->getDrawUtil(); + + GFX->setStateBlock( mZDisableSB ); + + // get the projected size... + GameConnection* connection = GameConnection::getConnectionToServer(); + if(!connection) + return; + + // Grab the camera's transform + MatrixF mat; + connection->getControlCameraTransform(0, &mat); + + // Get the camera position + Point3F camPos; + mat.getColumn(3,&camPos); + + if ( mHoverRiver && mHoverRiver != mSelRiver ) + { + _drawRiverSpline( mHoverRiver, mHoverSplineColor ); + } + + if ( mSelRiver ) + { + _drawRiverSpline( mSelRiver, mSelectedSplineColor ); + + // Render Gizmo for selected node if were in either of the three transform modes + if ( mSelNode != -1 && ( mMode == mMovePointMode || mMode == mScalePointMode || mMode == mRotatePointMode ) ) + { + if( mMode == mMovePointMode ) + { + mGizmo->getProfile()->mode = MoveMode; + } + else if( mMode == mScalePointMode ) + { + mGizmo->getProfile()->mode = ScaleMode; + } + else if( mMode == mRotatePointMode ) + { + mGizmo->getProfile()->mode = RotateMode; + } + + const RiverNode &node = mSelRiver->mNodes[mSelNode]; + + MatrixF objMat = mSelRiver->getNodeTransform(mSelNode); + Point3F objScale( node.width, 1.0f, node.depth ); + Point3F worldPos = node.point; + + mGizmo->set( objMat, worldPos, objScale ); + + mGizmo->renderGizmo(mLastCameraQuery.cameraMatrix); + } + } + + // Now draw all the 2d stuff! + GFX->setClipRect(updateRect); + + // Render Gizmo text + if ( mSelRiver && mSelNode != -1 ) + { + // mGizmo->setPosition(mSelRiver->mNodes[mSelNode].point); + // mGizmo->renderText( mSaveViewport, mSaveModelview, mSaveProjection ); + } + + // Draw Control nodes for selecting and highlighted rivers + if ( mHoverRiver ) + _drawRiverControlNodes( mHoverRiver, mHoverSplineColor ); + if ( mSelRiver ) + _drawRiverControlNodes( mSelRiver, mSelectedSplineColor ); +} + +void GuiRiverEditorCtrl::_drawRiverSpline( River *river, const ColorI &color ) +{ + if ( river->mSlices.size() <= 1 ) + return; + + if ( River::smShowSpline ) + { + // Render the River center-line + PrimBuild::color( color ); + PrimBuild::begin( GFXLineStrip, river->mSlices.size() ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p1 ); + } + PrimBuild::end(); + } + + if ( River::smWireframe ) + { + // Left-side line + PrimBuild::color3i( 100, 100, 100 ); + PrimBuild::begin( GFXLineStrip, river->mSlices.size() ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p0 ); + } + PrimBuild::end(); + + // Right-side line + PrimBuild::begin( GFXLineStrip, river->mSlices.size() ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p2 ); + } + PrimBuild::end(); + + // Cross-sections + PrimBuild::begin( GFXLineList, river->mSlices.size() * 2 ); + for ( U32 i = 0; i < river->mSlices.size(); i++ ) + { + PrimBuild::vertex3fv( river->mSlices[i].p0 ); + PrimBuild::vertex3fv( river->mSlices[i].p2 ); + } + PrimBuild::end(); + } + // Segment +} + +void GuiRiverEditorCtrl::_drawRiverControlNodes( River *river, const ColorI &color ) +{ + if ( !River::smShowSpline ) + return; + + RectI bounds = getBounds(); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + bool isSelected = ( river == mSelRiver ); + bool isHighlighted = ( river == mHoverRiver ); + + for ( U32 i = 0; i < river->mNodes.size(); i++ ) + { + if ( false && isSelected && mSelNode == i ) + continue; + + const Point3F &wpos = river->mNodes[i].point; + + Point3F spos; + project( wpos, &spos ); + + if ( spos.z > 1.0f ) + continue; + + Point2I posi; + posi.x = spos.x; + posi.y = spos.y; + + if ( !bounds.pointInRect( posi ) ) + continue; + + ColorI theColor = color; + Point2I nodeHalfSize = mNodeHalfSize; + + if ( isHighlighted && mHoverNode == i ) + { + //theColor = mHoverNodeColor; + nodeHalfSize += Point2I(2,2); + } + + if ( isSelected ) + { + if ( mSelNode == i ) + { + theColor.set(0,0,255); + } + else if ( i == 0 ) + { + theColor.set(0,255,0); + } + else if ( i == river->mNodes.size() - 1 ) + { + theColor.set(255,0,0); + } + } + + drawer->drawRectFill( posi - nodeHalfSize, posi + nodeHalfSize, theColor ); + } +} + +bool GuiRiverEditorCtrl::getStaticPos( const Gui3DMouseEvent & event, Point3F &tpos ) +{ + // Find clicked point on the terrain + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + bool hit; + + hit = gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri); + tpos = ri.point; + + return hit; +} + +void GuiRiverEditorCtrl::deleteSelectedNode() +{ + if ( !mSelRiver || mSelNode == -1 ) + return; + + // If the River has only two nodes remaining, + // delete the whole River. + if ( mSelRiver->mNodes.size() <= 2 ) + { + deleteSelectedRiver( mMode != mAddNodeMode ); + } + else + { + if ( mMode != mAddNodeMode ) + submitUndo( "Delete Node" ); + + // Delete the SelectedNode of the SelectedRiver + mSelRiver->deleteNode(mSelNode); + mIsDirty = true; + + // We deleted the Node but not the River (it has nodes left) + // so decrement the currently selected node. + if ( mSelRiver->mNodes.size() <= mSelNode ) + setSelectedNode( mSelNode - 1 ); + else + { + // force gizmo to update to the selected nodes position + // the index didn't change but the node it refers to did. + U32 i = mSelNode; + mSelNode = -1; + setSelectedNode( i ); + } + } + + // If you were in addNodeMode, + // deleting a node should ends it. + //mMode = smNormalMode; +} + +void GuiRiverEditorCtrl::deleteSelectedRiver( bool undoAble ) +{ + AssertFatal( mSelRiver != NULL, "GuiRiverEditorCtrl::deleteSelectedRiver() - No River IS selected" ); + + // Not undoAble? Just delete it. + if ( !undoAble ) + { + mSelRiver->deleteObject(); + mIsDirty = true; + Con::executef( this, "onRiverSelected" ); + mSelNode = -1; + + return; + } + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + // Couldn't find it? Well just delete the River. + Con::errorf( "GuiRiverEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + else + { + // Create the UndoAction. + MEDeleteUndoAction *action = new MEDeleteUndoAction("Deleted River"); + action->deleteObject( mSelRiver ); + mIsDirty = true; + + // Submit it. + undoMan->addAction( action ); + } + + // ScriptCallback with 'NULL' parameter for no River currently selected. + Con::executef( this, "onRiverSelected" ); + + // Clear the SelectedNode (it has been deleted along with the River). + setSelectedNode( -1 ); + mSelNode = -1; + + // SelectedRiver is a SimObjectPtr and will be NULL automatically. +} + +void GuiRiverEditorCtrl::setMode( String mode, bool sourceShortcut = false ) +{ + mMode = mode; + + if( sourceShortcut ) + Con::executef( this, "paletteSync", mode ); +} + +void GuiRiverEditorCtrl::setSelectedRiver( River *river ) +{ + mSelRiver = river; + + if ( mSelRiver != NULL ) + Con::executef( this, "onRiverSelected", river->scriptThis() ); + else + Con::executef( this, "onRiverSelected" ); + + if ( mSelRiver != river ) + setSelectedNode(-1); +} + +void GuiRiverEditorCtrl::setNodeWidth( F32 width ) +{ + if ( mSelRiver && mSelNode != -1 ) + { + mSelRiver->setNodeWidth( mSelNode, width ); + mIsDirty = true; + } +} + +F32 GuiRiverEditorCtrl::getNodeWidth() +{ + if ( mSelRiver && mSelNode != -1 ) + return mSelRiver->getNodeWidth( mSelNode ); + + return 0.0f; +} + +void GuiRiverEditorCtrl::setNodeDepth(F32 depth) +{ + if ( mSelRiver && mSelNode != -1 ) + { + mSelRiver->setNodeDepth( mSelNode, depth ); + mIsDirty = true; + } +} + +F32 GuiRiverEditorCtrl::getNodeDepth() +{ + if ( mSelRiver && mSelNode != -1 ) + return mSelRiver->getNodeDepth( mSelNode ); + + return 0.0f; +} + +void GuiRiverEditorCtrl::setNodePosition( Point3F pos ) +{ + if ( mSelRiver && mSelNode != -1 ) + { + mSelRiver->setNodePosition( mSelNode, pos ); + mIsDirty = true; + } +} + +Point3F GuiRiverEditorCtrl::getNodePosition() +{ + if ( mSelRiver && mSelNode != -1 ) + return mSelRiver->getNodePosition( mSelNode ); + + return Point3F( 0, 0, 0 ); +} + +void GuiRiverEditorCtrl::setNodeNormal( const VectorF &normal ) +{ + if ( mSelRiver && mSelNode != -1 ) + { + mSelRiver->setNodeNormal( mSelNode, normal ); + mIsDirty = true; + } +} + +VectorF GuiRiverEditorCtrl::getNodeNormal() +{ + if ( mSelRiver && mSelNode != -1 ) + return mSelRiver->getNodeNormal( mSelNode ); + + return VectorF::Zero; +} + +void GuiRiverEditorCtrl::setSelectedNode( S32 node ) +{ + //if ( mSelNode == node ) + // return; + + mSelNode = node; + if ( mSelNode != -1 ) + { + const RiverNode &node = mSelRiver->mNodes[mSelNode]; + + MatrixF objMat = mSelRiver->getNodeTransform(mSelNode); + Point3F objScale( node.width, 1.0f, node.depth ); + Point3F worldPos = node.point; + + mGizmo->set( objMat, worldPos, objScale ); + } + + if ( mSelNode != -1 ) + Con::executef( this, "onNodeSelected", Con::getIntArg(mSelNode) ); + else + Con::executef( this, "onNodeSelected", Con::getIntArg(-1) ); +} + +void GuiRiverEditorCtrl::submitUndo( const UTF8 *name ) +{ + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiRiverEditorCtrl::submitUndo() - EUndoManager not found!" ); + return; + } + + // Setup the action. + GuiRiverEditorUndoAction *action = new GuiRiverEditorUndoAction( name ); + + action->mObjId = mSelRiver->getId(); + action->mMetersPerSegment = mSelRiver->mMetersPerSegment; + action->mSegmentsPerBatch = mSelRiver->mSegmentsPerBatch; + action->mRiverEditor = this; + + for( U32 i = 0; i < mSelRiver->mNodes.size(); i++ ) + { + action->mNodes.push_back( mSelRiver->mNodes[i] ); + } + + undoMan->addAction( action ); +} + +void GuiRiverEditorCtrl::_prepRenderImage( SceneGraph* sceneGraph, const SceneState* state ) +{ + if ( isAwake() && River::smEditorOpen && mSelRiver ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->type = RenderPassManager::RIT_Water; + ri->renderDelegate.bind( this, &GuiRiverEditorCtrl::_renderSelectedRiver ); + ri->defaultKey = 100; + state->getRenderPass()->addInst( ri ); + } +} + +void GuiRiverEditorCtrl::_renderSelectedRiver( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *matInst ) +{ + if ( !mSelRiver || !River::smEditorOpen) + return; + + GFXTransformSaver saver; + + GFX->setStateBlock( mZEnableSB ); + + if ( River::smShowWalls && mSelRiver->mSlices.size() > 1 ) + { + Point3F offset(0,0,1); + + // Render the River volume + PrimBuild::begin( GFXTriangleList, 18 * mSelRiver->mSlices.size() - 1 ); + + for ( U32 i = 0; i < mSelRiver->mSlices.size() - 1; i++ ) + { + const RiverSlice &slice = mSelRiver->mSlices[i]; + const RiverSlice &nextSlice = mSelRiver->mSlices[i+1]; + + // Top face + //drawer->drawQuad( slice.p0, nextSlice.p0, nextSlice.p2, slice.p2, colorRed, true ); + //PrimBuild::color3i( 0, 0, 255 ); + //PrimBuild::vertex3fv( slice.p0 ); + //PrimBuild::vertex3fv( nextSlice.p0 ); + //PrimBuild::vertex3fv( nextSlice.p2 ); + //PrimBuild::vertex3fv( slice.p0 ); + //PrimBuild::vertex3fv( nextSlice.p2 ); + //PrimBuild::vertex3fv( slice.p2 ); + + // Bottom face + PrimBuild::color3i( 0, 255, 0 ); + PrimBuild::vertex3fv( slice.pb0 ); + PrimBuild::vertex3fv( nextSlice.pb0 ); + PrimBuild::vertex3fv( nextSlice.pb2 ); + PrimBuild::vertex3fv( slice.pb0 ); + PrimBuild::vertex3fv( nextSlice.pb2 ); + PrimBuild::vertex3fv( slice.pb2 ); + + // Left face + PrimBuild::color3i( 255, 0, 0 ); + PrimBuild::vertex3fv( slice.pb0 ); + PrimBuild::vertex3fv( nextSlice.pb0 ); + PrimBuild::vertex3fv( nextSlice.p0 ); + PrimBuild::vertex3fv( slice.pb0 ); + PrimBuild::vertex3fv( nextSlice.p0 ); + PrimBuild::vertex3fv( slice.p0 ); + + // Right face + PrimBuild::color3i( 255, 0, 0 ); + PrimBuild::vertex3fv( slice.p2 ); + PrimBuild::vertex3fv( nextSlice.p2 ); + PrimBuild::vertex3fv( nextSlice.pb2 ); + PrimBuild::vertex3fv( slice.p2 ); + PrimBuild::vertex3fv( nextSlice.pb2 ); + PrimBuild::vertex3fv( slice.pb2 ); + } + + PrimBuild::end(); + } +} + +ConsoleMethod( GuiRiverEditorCtrl, deleteNode, void, 2, 2, "deleteNode()" ) +{ + object->deleteSelectedNode(); +} + +ConsoleMethod( GuiRiverEditorCtrl, getMode, const char*, 2, 2, "" ) +{ + return object->getMode(); +} + +ConsoleMethod( GuiRiverEditorCtrl, setMode, void, 3, 3, "setMode( String mode )" ) +{ + String newMode = ( argv[2] ); + object->setMode( newMode ); +} + +ConsoleMethod( GuiRiverEditorCtrl, getNodeWidth, F32, 2, 2, "" ) +{ + return object->getNodeWidth(); +} + +ConsoleMethod( GuiRiverEditorCtrl, setNodeWidth, void, 3, 3, "" ) +{ + object->setNodeWidth( dAtof(argv[2]) ); +} + +ConsoleMethod( GuiRiverEditorCtrl, getNodeDepth, F32, 2, 2, "" ) +{ + return object->getNodeDepth(); +} + +ConsoleMethod( GuiRiverEditorCtrl, setNodeDepth, void, 3, 3, "" ) +{ + object->setNodeDepth( dAtof(argv[2]) ); +} + +ConsoleMethod( GuiRiverEditorCtrl, getNodePosition, const char*, 2, 2, "" ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + + dSprintf(returnBuffer, 256, "%f %f %f", + object->getNodePosition().x, object->getNodePosition().y, object->getNodePosition().z); + + return returnBuffer; +} + +ConsoleMethod( GuiRiverEditorCtrl, setNodePosition, void, 3, 3, "" ) +{ + Point3F pos; + + S32 count = dSscanf( argv[2], "%f %f %f", + &pos.x, &pos.y, &pos.z); + + if ( (count != 3) ) + { + Con::printf("Failed to parse node information \"px py pz\" from '%s'", argv[3]); + return; + } + + object->setNodePosition( pos ); +} + +ConsoleMethod( GuiRiverEditorCtrl, getNodeNormal, const char*, 2, 2, "" ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + + dSprintf(returnBuffer, 256, "%f %f %f", + object->getNodeNormal().x, object->getNodeNormal().y, object->getNodeNormal().z); + + return returnBuffer; +} + +ConsoleMethod( GuiRiverEditorCtrl, setNodeNormal, void, 3, 3, "" ) +{ + VectorF normal; + + S32 count = dSscanf( argv[2], "%f %f %f", + &normal.x, &normal.y, &normal.z); + + if ( (count != 3) ) + { + Con::printf("Failed to parse node information \"px py pz\" from '%s'", argv[3]); + return; + } + + object->setNodeNormal( normal ); +} + +ConsoleMethod( GuiRiverEditorCtrl, setSelectedRiver, void, 2, 3, "" ) +{ + if ( argc == 2 ) + object->setSelectedRiver(NULL); + else + { + River *river = NULL; + if ( Sim::findObject( argv[2], river ) ) + object->setSelectedRiver(river); + } +} + +ConsoleMethod( GuiRiverEditorCtrl, getSelectedRiver, const char*, 2, 2, "" ) +{ + River *river = object->getSelectedRiver(); + if ( !river ) + return NULL; + + return river->scriptThis(); +} + +ConsoleMethod( GuiRiverEditorCtrl, regenerate, void, 2, 2, "" ) +{ + River *river = object->getSelectedRiver(); + if ( river ) + river->regenerate(); +} \ No newline at end of file diff --git a/environment/editors/guiRiverEditorCtrl.h b/environment/editors/guiRiverEditorCtrl.h new file mode 100644 index 0000000..397b3b8 --- /dev/null +++ b/environment/editors/guiRiverEditorCtrl.h @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIRIVEREDITORCTRL_H_ +#define _GUIRIVEREDITORCTRL_H_ + +#ifndef _EDITTSCTRL_H_ +#include "gui/worldEditor/editTSCtrl.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _RIVER_H_ +#include "environment/river.h" +#endif +#ifndef _GIZMO_H_ +#include "gui/worldEditor/gizmo.h" +#endif + +struct ObjectRenderInst; +class SceneGraph; +class SceneState; +class BaseMatInstance; + + +class GuiRiverEditorCtrl : public EditTSCtrl +{ + typedef EditTSCtrl Parent; + + public: + + friend class GuiRiverEditorUndoAction; + + //static StringTableEntry smNormalMode; + //static StringTableEntry smAddNodeMode; + + String mSelectRiverMode; + String mAddRiverMode; + String mAddNodeMode; + String mInsertPointMode; + String mRemovePointMode; + String mMovePointMode; + String mScalePointMode; + String mRotatePointMode; + + GuiRiverEditorCtrl(); + ~GuiRiverEditorCtrl(); + + DECLARE_CONOBJECT(GuiRiverEditorCtrl); + + // SimObject + bool onAdd(); + static void initPersistFields(); + + // GuiControl + virtual void onSleep(); + virtual void onRender(Point2I offset, const RectI &updateRect); + + // EditTSCtrl + bool onKeyDown(const GuiEvent& event); + void get3DCursor( GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_ ); + void on3DMouseDown(const Gui3DMouseEvent & event); + void on3DMouseUp(const Gui3DMouseEvent & event); + void on3DMouseMove(const Gui3DMouseEvent & event); + void on3DMouseDragged(const Gui3DMouseEvent & event); + void on3DMouseEnter(const Gui3DMouseEvent & event); + void on3DMouseLeave(const Gui3DMouseEvent & event); + void on3DRightMouseDown(const Gui3DMouseEvent & event); + void on3DRightMouseUp(const Gui3DMouseEvent & event); + void updateGuiInfo(); + void renderScene(const RectI & updateRect); + + // GuiRiverEditorCtrl + bool getStaticPos( const Gui3DMouseEvent & event, Point3F &tpos ); + void deleteSelectedNode(); + void deleteSelectedRiver( bool undoAble = true ); + + void setMode( String mode, bool sourceShortcut ); + String getMode() { return mMode; } + + //void setGizmoMode( Gizmo::Mode mode ) { mGizmo->setMode( mode ); } + + void setSelectedRiver( River *river ); + River* getSelectedRiver() { return mSelRiver; }; + void setSelectedNode( S32 node ); + + F32 getNodeWidth(); + void setNodeWidth( F32 width ); + + F32 getNodeDepth(); + void setNodeDepth( F32 depth ); + + Point3F getNodePosition(); + void setNodePosition( Point3F pos ); + + VectorF getNodeNormal(); + void setNodeNormal( const VectorF &normal ); + + protected: + + void _renderSelectedRiver( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *matInst ); + void _prepRenderImage( SceneGraph* sceneGraph, const SceneState* sceneState ); + void _drawRiverSpline( River *river, const ColorI &color ); + void _drawRiverControlNodes( River *river, const ColorI &color ); + + void submitUndo( const UTF8 *name = "Action" ); + + GFXStateBlockRef mZDisableSB; + GFXStateBlockRef mZEnableSB; + + bool mSavedDrag; + bool mIsDirty; + + SimSet *mRiverSet; + S32 mSelNode; + S32 mHoverNode; + U32 mAddNodeIdx; + SimObjectPtr mSelRiver; + SimObjectPtr mHoverRiver; + + String mMode; + + F32 mDefaultWidth; + F32 mDefaultDepth; + VectorF mDefaultNormal; + S32 mInsertIdx; + + F32 mStartHeight; + F32 mStartWidth; + S32 mStartX; + Point3F mStartWorld; + + Point2I mNodeHalfSize; + + //Gizmo mGizmo; + + Gui3DMouseEvent mLastMouseEvent; + + F32 mNodeSphereRadius; + ColorI mNodeSphereFillColor; + ColorI mNodeSphereLineColor; + + ColorI mHoverSplineColor; + ColorI mSelectedSplineColor; + ColorI mHoverNodeColor; + + #define InvalidMousePoint Point2I(-100,-100) + Point2I mStartDragMousePoint; + + Point3F mStartDragNodePos; + + //GuiCursor *mMoveNodeCursor; + //GuiCursor *mAddNodeCursor; + //GuiCursor *mInsertNodeCursor; + //GuiCursor *mResizeNodeCursor; +}; + +class GuiRiverEditorUndoAction : public UndoAction +{ + public: + + GuiRiverEditorUndoAction( const UTF8* actionName ) : UndoAction( actionName ) + { + } + + GuiRiverEditorCtrl *mRiverEditor; + + Vector mNodes; + + SimObjectId mObjId; + F32 mMetersPerSegment; + U32 mSegmentsPerBatch; + + virtual void undo(); + virtual void redo() { undo(); } +}; + +#endif + + + diff --git a/environment/editors/guiRoadEditorCtrl.cpp b/environment/editors/guiRoadEditorCtrl.cpp new file mode 100644 index 0000000..5c55bf5 --- /dev/null +++ b/environment/editors/guiRoadEditorCtrl.cpp @@ -0,0 +1,1105 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/editors/guiRoadEditorCtrl.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "collision/collision.h" +#include "math/util/frustum.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxTextureHandle.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/primBuilder.h" +#include "T3D/gameConnection.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonCtrl.h" +#include "gui/worldEditor/undoActions.h" +#include "materials/materialDefinition.h" + +IMPLEMENT_CONOBJECT(GuiRoadEditorCtrl); + +GuiRoadEditorCtrl::GuiRoadEditorCtrl() +{ + // Each of the mode names directly correlates with the River Editor's + // tool palette + mSelectRoadMode = "RoadEditorSelectMode"; + mAddRoadMode = "RoadEditorAddRoadMode"; + mMovePointMode = "RoadEditorMoveMode"; + mScalePointMode = "RoadEditorScaleMode"; + mAddNodeMode = "RoadEditorAddNodeMode"; + mInsertPointMode = "RoadEditorInsertPointMode"; + mRemovePointMode = "RoadEditorRemovePointMode"; + + mMode = mSelectRoadMode; + + mRoadSet = NULL; + mSelNode = -1; + mHoverNode = -1; + mSelRoad = NULL; + mHoverRoad = NULL; + mAddNodeIdx = 0; + + mDefaultWidth = 10.0f; + mInsertIdx = -1; + + mStartWidth = -1.0f; + mStartX = 0; + + mNodeHalfSize.set(4,4); + + mHoverSplineColor.set( 255,0,0,255 ); + mSelectedSplineColor.set( 0,255,0,255 ); + mHoverNodeColor.set( 255,255,255,255 ); + + mIsDirty = false; +} + +GuiRoadEditorCtrl::~GuiRoadEditorCtrl() +{ + // nothing to do +} + +void GuiRoadEditorUndoAction::undo() +{ + DecalRoad *road = NULL; + if ( !Sim::findObject( mObjId, road ) ) + return; + + // Temporarily save the roads current data. + String materialName = road->mMaterialName; + F32 textureLength = road->mTextureLength; + F32 breakAngle = road->mBreakAngle; + F32 segmentsPerBatch = road->mSegmentsPerBatch; + Vector nodes; + nodes.merge( road->mNodes ); + + // Restore the Road properties saved in the UndoAction + road->mMaterialName = materialName; + road->mBreakAngle = breakAngle; + road->mSegmentsPerBatch = segmentsPerBatch; + road->mTextureLength = textureLength; + road->inspectPostApply(); + + // Restore the Nodes saved in the UndoAction + road->mNodes.clear(); + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + road->_addNode( mNodes[i].point, mNodes[i].width ); + } + + // Regenerate the road + road->regenerate(); + + // If applicable set the selected road and node + mRoadEditor->mSelRoad = road; + mRoadEditor->mSelNode = -1; + + // Now save the previous Road data in this UndoAction + // since an undo action must become a redo action and vice-versa + mMaterialName = materialName; + mBreakAngle = breakAngle; + mSegmentsPerBatch = segmentsPerBatch; + mTextureLength = textureLength; + + mNodes.clear(); + mNodes.merge( nodes ); +} + +bool GuiRoadEditorCtrl::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + mRoadSet = DecalRoad::getServerSet(); + + GFXStateBlockDesc desc; + desc.fillMode = GFXFillSolid; + desc.blendDefined = true; + desc.blendEnable = false; + desc.zDefined = true; + desc.zEnable = false; + desc.cullDefined = true; + desc.cullMode = GFXCullNone; + + mZDisableSB = GFX->createStateBlock(desc); + + desc.zEnable = true; + mZEnableSB = GFX->createStateBlock(desc); + + return true; +} + +void GuiRoadEditorCtrl::initPersistFields() +{ + addField( "DefaultWidth", TypeF32, Offset( mDefaultWidth, GuiRoadEditorCtrl ) ); + addField( "HoverSplineColor", TypeColorI, Offset( mHoverSplineColor, GuiRoadEditorCtrl ) ); + addField( "SelectedSplineColor", TypeColorI, Offset( mSelectedSplineColor, GuiRoadEditorCtrl ) ); + addField( "HoverNodeColor", TypeColorI, Offset( mHoverNodeColor, GuiRoadEditorCtrl ) ); + addField( "isDirty", TypeBool, Offset( mIsDirty, GuiRoadEditorCtrl ) ); + + //addField( "MoveNodeCursor", TypeSimObjectPtr, Offset( mMoveNodeCursor, GuiRoadEditorCtrl) ); + //addField( "AddNodeCursor", TypeSimObjectPtr, Offset( mAddNodeCursor, GuiRoadEditorCtrl) ); + //addField( "InsertNodeCursor", TypeSimObjectPtr, Offset( mInsertNodeCursor, GuiRoadEditorCtrl) ); + //addField( "ResizeNodeCursor", TypeSimObjectPtr, Offset( mResizeNodeCursor, GuiRoadEditorCtrl) ); + + Parent::initPersistFields(); +} + +void GuiRoadEditorCtrl::onSleep() +{ + Parent::onSleep(); + + mMode = mSelectRoadMode; + mHoverNode = -1; + mHoverRoad = NULL; + setSelectedNode(-1); +} + +void GuiRoadEditorCtrl::get3DCursor( GuiCursor *&cursor, + bool &visible, + const Gui3DMouseEvent &event_ ) +{ + //cursor = mAddNodeCursor; + //visible = false; + + cursor = NULL; + visible = false; + + GuiCanvas *root = getRoot(); + if ( !root ) + return; + + S32 currCursor = PlatformCursorController::curArrow; + + if ( root->mCursorChanged == currCursor ) + return; + + PlatformWindow *window = root->getPlatformWindow(); + PlatformCursorController *controller = window->getCursorController(); + + // We've already changed the cursor, + // so set it back before we change it again. + if( root->mCursorChanged != -1) + controller->popCursor(); + + // Now change the cursor shape + controller->pushCursor(currCursor); + root->mCursorChanged = currCursor; +} + +void GuiRoadEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) +{ + if ( !isFirstResponder() ) + setFirstResponder(); + + // Get the clicked terrain position. + Point3F tPos; + if ( !getTerrainPos( event, tPos ) ) + return; + + // Find any road / node at the clicked position. + // TODO: handle overlapping roads/nodes somehow, cycle through them. + + DecalRoad *roadPtr = NULL; + S32 closestNodeIdx = -1; + F32 closestDist = F32_MAX; + DecalRoad *closestNodeRoad = NULL; + + // First, find the closest node in any road to the clicked position. + for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) + { + roadPtr = static_cast( *iter ); + U32 idx; + if ( roadPtr->getClosestNode( tPos, idx ) ) + { + Point3F nodePos = roadPtr->getNodePosition(idx); + F32 dist = ( nodePos - tPos ).len(); + if ( dist < closestDist ) + { + closestNodeIdx = idx; + closestDist = dist; + closestNodeRoad = roadPtr; + } + } + } + + // + // Second, determine if the screen-space node rectangle + // contains the clicked position. + + bool nodeClicked = false; + S32 clickedNodeIdx = -1; + + if ( closestNodeIdx != -1 ) + { + Point3F nodePos = closestNodeRoad->getNodePosition( closestNodeIdx ); + + Point3F temp; + project( nodePos, &temp ); + Point2I screenPos( temp.x, temp.y ); + + RectI nodeRect( screenPos - mNodeHalfSize, mNodeHalfSize * 2 ); + + nodeClicked = nodeRect.pointInRect( event.mousePoint ); + if ( nodeClicked ) + clickedNodeIdx = closestNodeIdx; + } + + // + // Determine the clickedRoad + // + DecalRoad *clickedRoadPtr = NULL; + U32 insertNodeIdx = 0; + + if ( nodeClicked && (mSelRoad == NULL || closestNodeRoad == mSelRoad) ) + { + // If a node was clicked, the owning road is always + // considered the clicked road. + clickedRoadPtr = closestNodeRoad; + } + else + { + // check the selected road first + if ( mSelRoad != NULL && mSelRoad->containsPoint( tPos, &insertNodeIdx ) ) + { + clickedRoadPtr = mSelRoad; + nodeClicked = false; + clickedNodeIdx = -1; + } + else + { + // Otherwise, we must ask each road if it contains + // the clicked pos. + for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) + { + roadPtr = static_cast( *iter ); + if ( roadPtr->containsPoint( tPos, &insertNodeIdx ) ) + { + clickedRoadPtr = roadPtr; + break; + } + } + } + } + + // shortcuts + bool dblClick = ( event.mouseClickCount > 1 ); + if( dblClick ) + { + if( mMode == mSelectRoadMode ) + { + setMode( mAddRoadMode, true ); + return; + } + if( mMode == mAddNodeMode ) + { + // Delete the node attached to the cursor. + deleteSelectedNode(); + mMode = mAddRoadMode; + return; + } + } + + //this check is here in order to bounce back from deleting a whole road with ctrl+z + //this check places the editor back into addroadmode + if ( mMode == mAddNodeMode ) + { + if ( !mSelRoad ) + mMode = mAddRoadMode; + } + + if ( mMode == mSelectRoadMode ) + { + // Did not click on a road or a node. + if ( !clickedRoadPtr ) + { + setSelectedRoad( NULL ); + setSelectedNode( -1 ); + + return; + } + + // Clicked on a road that wasn't the currently selected road. + if ( clickedRoadPtr != mSelRoad ) + { + setSelectedRoad( clickedRoadPtr ); + setSelectedNode( -1 ); + return; + } + + // Clicked on a node in the currently selected road that wasn't + // the currently selected node. + if ( nodeClicked ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + + + // Clicked a position on the currently selected road + // that did not contain a node. + //U32 newNode = clickedRoadPtr->insertNode( tPos, mDefaultWidth, insertNodeIdx ); + //setSelectedNode( newNode ); + } + else if ( mMode == mAddRoadMode ) + { + if ( nodeClicked && clickedRoadPtr ) + { + // A double-click on a node in Normal mode means set AddNode mode. + if ( clickedNodeIdx == 0 ) + { + setSelectedRoad( clickedRoadPtr ); + setSelectedNode( clickedNodeIdx ); + + mAddNodeIdx = clickedNodeIdx; + mMode = mAddNodeMode; + mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mAddNodeIdx ); + mIsDirty = true; + + return; + } + else if ( clickedNodeIdx == clickedRoadPtr->mNodes.size() - 1 ) + { + setSelectedRoad( clickedRoadPtr ); + setSelectedNode( clickedNodeIdx ); + + mAddNodeIdx = U32_MAX; + mMode = mAddNodeMode; + mSelNode = mSelRoad->addNode( tPos, mDefaultWidth ); + mIsDirty = true; + setSelectedNode( mSelNode ); + + return; + } + } + + DecalRoad *newRoad = new DecalRoad; + + Material * defaultMat = dynamic_cast( Sim::findObject("DefaultDecalRoadMaterial") ); + if( defaultMat ) + newRoad->mMaterialName = "DefaultDecalRoadMaterial"; + + newRoad->registerObject(); + + // Add to MissionGroup + SimGroup *missionGroup; + if ( !Sim::findObject( "MissionGroup", missionGroup ) ) + Con::errorf( "GuiDecalRoadEditorCtrl - could not find MissionGroup to add new DecalRoad" ); + else + missionGroup->addObject( newRoad ); + + newRoad->insertNode( tPos, mDefaultWidth, 0 ); + U32 newNode = newRoad->insertNode( tPos, mDefaultWidth, 1 ); + + // Always add to the end of the road, the first node is the start. + mAddNodeIdx = U32_MAX; + + setSelectedRoad( newRoad ); + setSelectedNode( newNode ); + + mMode = mAddNodeMode; + + // Disable the hover node while in addNodeMode, we + // don't want some random node enlarged. + mHoverNode = -1; + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + + // Create the UndoAction. + MECreateUndoAction *action = new MECreateUndoAction("Create Road"); + action->addObject( newRoad ); + + // Submit it. + undoMan->addAction( action ); + + //send a callback to script after were done here if one exists + if ( isMethod( "onRoadCreation" ) ) + Con::executef( this, "onRoadCreation" ); + + return; + } + else if ( mMode == mAddNodeMode ) + { + // Oops the road got deleted, maybe from an undo action? + // Back to NormalMode. + if ( mSelRoad ) + { + // A double-click on a node in Normal mode means set AddNode mode. + if ( clickedNodeIdx == 0 ) + { + submitUndo( "Add Node" ); + mAddNodeIdx = clickedNodeIdx; + mMode = mAddNodeMode; + mSelNode = mSelRoad->insertNode( tPos, mDefaultWidth, mAddNodeIdx ); + mIsDirty = true; + setSelectedNode( mSelNode ); + + return; + } + else + { + if( clickedRoadPtr && clickedNodeIdx == clickedRoadPtr->mNodes.size() - 1 ) + { + submitUndo( "Add Node" ); + mAddNodeIdx = U32_MAX; + mMode = mAddNodeMode; + mSelNode = mSelRoad->addNode( tPos, mDefaultWidth ); + mIsDirty = true; + setSelectedNode( mSelNode ); + + return; + } + else + { + submitUndo( "Insert Node" ); + // A single-click on empty space while in + // AddNode mode means insert / add a node. + //submitUndo( "Add Node" ); + //F32 width = mSelRoad->getNodeWidth( mSelNode ); + U32 newNode = mSelRoad->insertNode( tPos, mDefaultWidth, mAddNodeIdx); + mIsDirty = true; + setSelectedNode( newNode ); + + return; + } + } + } + } + else if ( mMode == mInsertPointMode && mSelRoad != NULL) + { + if ( clickedRoadPtr == mSelRoad ) + { + F32 w0 = mSelRoad->getNodeWidth( insertNodeIdx ); + F32 w1 = mSelRoad->getNodeWidth( insertNodeIdx + 1 ); + F32 width = ( w0 + w1 ) * 0.5f; + + submitUndo( "Insert Node" ); + U32 newNode = mSelRoad->insertNode( tPos, width, insertNodeIdx + 1); + mIsDirty = true; + setSelectedNode( newNode ); + + return; + } + } + else if ( mMode == mRemovePointMode && mSelRoad != NULL) + { + if ( nodeClicked && clickedRoadPtr == mSelRoad ) + { + setSelectedNode( clickedNodeIdx ); + deleteSelectedNode(); + return; + } + } + else if ( mMode == mMovePointMode ) + { + if ( nodeClicked && clickedRoadPtr == mSelRoad ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } + else if ( mMode == mScalePointMode ) + { + if ( nodeClicked && clickedRoadPtr == mSelRoad ) + { + setSelectedNode( clickedNodeIdx ); + return; + } + } +} + +void GuiRoadEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event) +{ + //mIsPanning = true; +} + +void GuiRoadEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event) +{ + //mIsPanning = false; +} + +void GuiRoadEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) +{ + mStartWidth = -1.0f; + mSavedDrag = false; +} + +void GuiRoadEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) +{ + if ( mSelRoad != NULL && mMode == mAddNodeMode ) + { + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + RayInfo ri; + if ( gServerContainer.castRay(startPnt, endPnt, TerrainObjectType, &ri) ) + { + mSelRoad->setNodePosition( mSelNode, ri.point ); + mIsDirty = true; + } + + return; + } + + // Is cursor hovering over a road? + if ( mMode == mSelectRoadMode ) + { + mHoverRoad = NULL; + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + + if ( gServerContainer.castRay(startPnt, endPnt, TerrainObjectType, &ri) ) + { + DecalRoad *pRoad = NULL; + + for ( SimSetIterator iter(mRoadSet); *iter; ++iter ) + { + pRoad = static_cast( *iter ); + + if ( pRoad->containsPoint( ri.point ) ) + { + mHoverRoad = pRoad; + break; + } + } + } + } + + // Is cursor hovering over a RoadNode? + if ( mHoverRoad ) + { + DecalRoad *pRoad = mHoverRoad; + + S32 hoverNodeIdx = -1; + F32 hoverNodeDist = F32_MAX; + + for ( U32 i = 0; i < pRoad->mNodes.size(); i++ ) + { + const Point3F &nodePos = pRoad->mNodes[i].point; + + Point3F screenPos; + project( nodePos, &screenPos ); + + RectI rect( Point2I((S32)screenPos.x,(S32)screenPos.y) - mNodeHalfSize, mNodeHalfSize * 2 ); + + if ( rect.pointInRect( event.mousePoint ) && screenPos.z < hoverNodeDist ) + { + hoverNodeDist = screenPos.z; + hoverNodeIdx = i; + } + } + + mHoverNode = hoverNodeIdx; + } +} + +void GuiRoadEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + if ( !mSelRoad || mSelNode == -1 || mMode == mAddNodeMode) + return; + + if ( !mSavedDrag ) + { + submitUndo( "Modify Node" ); + mSavedDrag = true; + } + + if ( mMode == mScalePointMode ) + { + Point3F tPos; + if ( !getTerrainPos( event, tPos ) ) + return; + + if ( mStartWidth == -1.0f ) + { + mStartWidth = mSelRoad->mNodes[mSelNode].width; + + mStartX = event.mousePoint.x; + mStartWorld = tPos; + } + + S32 deltaScreenX = event.mousePoint.x - mStartX; + + F32 worldDist = ( event.pos - mStartWorld ).len(); + + F32 deltaWorldX = ( deltaScreenX * worldDist ) / getWorldToScreenScale().y; + + F32 width = mStartWidth + deltaWorldX; + + mSelRoad->setNodeWidth( mSelNode, width ); + mIsDirty = true; + } + else if( mMode == mMovePointMode ) + { + Point3F tPos; + if ( !getTerrainPos( event, tPos ) ) + return; + + mSelRoad->setNodePosition( mSelNode, tPos ); + mIsDirty = true; + } + + Con::executef( this, "onNodeModified", Con::getIntArg(mSelNode) ); +} + +void GuiRoadEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +void GuiRoadEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +bool GuiRoadEditorCtrl::onKeyDown(const GuiEvent& event) +{ + if( event.keyCode == KEY_RETURN && mMode == mAddNodeMode ) + { + // Delete the node attached to the cursor. + deleteSelectedNode(); + mMode = mAddRoadMode; + return true; + } + + return false; +} + +void GuiRoadEditorCtrl::updateGuiInfo() +{ + // nothing to do +} + +void GuiRoadEditorCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + PROFILE_SCOPE( GuiRoadEditorCtrl_OnRender ); + + Parent::onRender( offset, updateRect ); + return; +} + +void GuiRoadEditorCtrl::renderScene(const RectI & updateRect) +{ + //GFXDrawUtil *drawer = GFX->getDrawUtil(); + + GFX->setStateBlock( mZDisableSB ); + + // get the projected size... + GameConnection* connection = GameConnection::getConnectionToServer(); + if(!connection) + return; + + // Grab the camera's transform + MatrixF mat; + connection->getControlCameraTransform(0, &mat); + + // Get the camera position + Point3F camPos; + mat.getColumn(3,&camPos); + + if ( mHoverRoad && mHoverRoad != mSelRoad ) + { + _drawRoadSpline( mHoverRoad, mHoverSplineColor ); + } + + if ( mSelRoad ) + { + _drawRoadSpline( mSelRoad, mSelectedSplineColor ); + } + + // Now draw all the 2d stuff! + GFX->setClipRect(updateRect); + + // Draw Control nodes for selected and highlighted roads + if ( mHoverRoad ) + _drawRoadControlNodes( mHoverRoad, mHoverSplineColor ); + if ( mSelRoad ) + _drawRoadControlNodes( mSelRoad, mSelectedSplineColor ); + +} + +void GuiRoadEditorCtrl::_drawRoadSpline( DecalRoad *road, const ColorI &color ) +{ + if ( road->mEdges.size() <= 1 ) + return; + + if ( DecalRoad::smShowSpline ) + { + // Render the center-line + PrimBuild::color( color ); + PrimBuild::begin( GFXLineStrip, road->mEdges.size() ); + for ( U32 i = 0; i < road->mEdges.size(); i++ ) + { + PrimBuild::vertex3fv( road->mEdges[i].p1 ); + } + PrimBuild::end(); + } + + if ( DecalRoad::smWireframe ) + { + // Left-side line + PrimBuild::color3i( 100, 100, 100 ); + PrimBuild::begin( GFXLineStrip, road->mEdges.size() ); + for ( U32 i = 0; i < road->mEdges.size(); i++ ) + { + PrimBuild::vertex3fv( road->mEdges[i].p0 ); + } + PrimBuild::end(); + + // Right-side line + PrimBuild::begin( GFXLineStrip, road->mEdges.size() ); + for ( U32 i = 0; i < road->mEdges.size(); i++ ) + { + PrimBuild::vertex3fv( road->mEdges[i].p2 ); + } + PrimBuild::end(); + + // Cross-sections + PrimBuild::begin( GFXLineList, road->mEdges.size() * 2 ); + for ( U32 i = 0; i < road->mEdges.size(); i++ ) + { + PrimBuild::vertex3fv( road->mEdges[i].p0 ); + PrimBuild::vertex3fv( road->mEdges[i].p2 ); + } + PrimBuild::end(); + } + + // Segment +} + +void GuiRoadEditorCtrl::_drawRoadControlNodes( DecalRoad *road, const ColorI &color ) +{ + if ( !DecalRoad::smShowSpline ) + return; + + RectI bounds = getBounds(); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + bool isSelected = ( road == mSelRoad ); + bool isHighlighted = ( road == mHoverRoad ); + + for ( U32 i = 0; i < road->mNodes.size(); i++ ) + { + if ( false && isSelected && mSelNode == i ) + continue; + + const Point3F &wpos = road->mNodes[i].point; + + Point3F spos; + project( wpos, &spos ); + + if ( spos.z > 1.0f ) + continue; + + Point2I posi; + posi.x = spos.x; + posi.y = spos.y; + + if ( !bounds.pointInRect( posi ) ) + continue; + + ColorI theColor = color; + Point2I nodeHalfSize = mNodeHalfSize; + + if ( isHighlighted && mHoverNode == i ) + { + //theColor = mHoverNodeColor; + nodeHalfSize += Point2I(2,2); + } + + if ( isSelected ) + { + if ( mSelNode == i ) + { + theColor.set(0,0,255); + } + else if ( i == 0 ) + { + theColor.set(0,255,0); + } + else if ( i == road->mNodes.size() - 1 ) + { + theColor.set(255,0,0); + } + } + + drawer->drawRectFill( posi - nodeHalfSize, posi + nodeHalfSize, theColor ); + } +} + +bool GuiRoadEditorCtrl::getTerrainPos( const Gui3DMouseEvent & event, Point3F &tpos ) +{ + // Find clicked point on the terrain + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + bool hit; + + hit = gServerContainer.castRay(startPnt, endPnt, TerrainObjectType, &ri); + tpos = ri.point; + + return hit; +} + +void GuiRoadEditorCtrl::deleteSelectedNode() +{ + if ( !mSelRoad || mSelNode == -1 ) + return; + + // If the road has only two nodes remaining, + // delete the whole road. + if ( mSelRoad->mNodes.size() <= 2 ) + { + deleteSelectedRoad(); + } + else + { + // Only submit undo if we weren't in AddMode + if ( mMode != mAddNodeMode ) + submitUndo( "Delete Node" ); + + // Delete the SelectedNode of the SelectedRoad + mSelRoad->deleteNode(mSelNode); + mIsDirty = true; + + // We deleted the Node but not the Road (it has nodes left) + // so decrement the currently selected node. + if ( mSelRoad->mNodes.size() <= mSelNode ) + mSelNode--; + } +} + +void GuiRoadEditorCtrl::deleteSelectedRoad( bool undoAble ) +{ + AssertFatal( mSelRoad != NULL, "GuiRoadEditorCtrl::deleteSelectedRoad() - No road IS selected" ); + + // Not undo-able? Just delete it. + if ( !undoAble ) + { + DecalRoad *lastRoad = mSelRoad; + + setSelectedRoad(NULL); + + lastRoad->deleteObject(); + mIsDirty = true; + + return; + } + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + // Couldn't find it? Well just delete the road. + Con::errorf( "GuiRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + else + { + DecalRoad *lastRoad = mSelRoad; + setSelectedRoad(NULL); + + // Create the UndoAction. + MEDeleteUndoAction *action = new MEDeleteUndoAction("Deleted Road"); + action->deleteObject( lastRoad ); + mIsDirty = true; + + // Submit it. + undoMan->addAction( action ); + } +} + +void GuiRoadEditorCtrl::setMode( String mode, bool sourceShortcut = false ) +{ + mMode = mode; + + if( sourceShortcut ) + Con::executef( this, "paletteSync", mode ); +} + +void GuiRoadEditorCtrl::setSelectedRoad( DecalRoad *road ) +{ + mSelRoad = road; + + if ( road != NULL ) + Con::executef( this, "onRoadSelected", road->scriptThis() ); + else + Con::executef( this, "onRoadSelected" ); + + if ( mSelRoad != road ) + setSelectedNode(-1); +} + +void GuiRoadEditorCtrl::setNodeWidth( F32 width ) +{ + if ( mSelRoad && mSelNode != -1 ) + { + mSelRoad->setNodeWidth( mSelNode, width ); + mIsDirty = true; + } +} + +F32 GuiRoadEditorCtrl::getNodeWidth() +{ + if ( mSelRoad && mSelNode != -1 ) + return mSelRoad->getNodeWidth( mSelNode ); + + return 0.0f; +} + +void GuiRoadEditorCtrl::setNodePosition( Point3F pos ) +{ + if ( mSelRoad && mSelNode != -1 ) + { + mSelRoad->setNodePosition( mSelNode, pos ); + mIsDirty = true; + } +} + +Point3F GuiRoadEditorCtrl::getNodePosition() +{ + if ( mSelRoad && mSelNode != -1 ) + return mSelRoad->getNodePosition( mSelNode ); + + return Point3F( 0, 0, 0 ); +} + +void GuiRoadEditorCtrl::setSelectedNode( S32 node ) +{ + //if ( mSelNode == node ) + // return; + + mSelNode = node; + + if ( mSelNode != -1 && mSelRoad != NULL ) + Con::executef( this, "onNodeSelected", Con::getIntArg(mSelNode), Con::getFloatArg(mSelRoad->mNodes[mSelNode].width) ); + else + Con::executef( this, "onNodeSelected", Con::getIntArg(-1) ); +} + +void GuiRoadEditorCtrl::submitUndo( const UTF8 *name ) +{ + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiRoadEditorCtrl::submitUndo() - EUndoManager not found!" ); + return; + } + + // Setup the action. + GuiRoadEditorUndoAction *action = new GuiRoadEditorUndoAction( name ); + + action->mObjId = mSelRoad->getId(); + action->mBreakAngle = mSelRoad->mBreakAngle; + action->mMaterialName = mSelRoad->mMaterialName; + action->mSegmentsPerBatch = mSelRoad->mSegmentsPerBatch; + action->mTextureLength = mSelRoad->mTextureLength; + action->mRoadEditor = this; + + for( U32 i = 0; i < mSelRoad->mNodes.size(); i++ ) + { + action->mNodes.push_back( mSelRoad->mNodes[i] ); + } + + undoMan->addAction( action ); +} + +ConsoleMethod( GuiRoadEditorCtrl, deleteNode, void, 2, 2, "deleteNode()" ) +{ + object->deleteSelectedNode(); +} + +ConsoleMethod( GuiRoadEditorCtrl, getMode, const char*, 2, 2, "" ) +{ + return object->getMode(); +} + +ConsoleMethod( GuiRoadEditorCtrl, setMode, void, 3, 3, "setMode( String mode )" ) +{ + String newMode = ( argv[2] ); + object->setMode( newMode ); +} + +ConsoleMethod( GuiRoadEditorCtrl, getNodeWidth, F32, 2, 2, "" ) +{ + return object->getNodeWidth(); +} + +ConsoleMethod( GuiRoadEditorCtrl, setNodeWidth, void, 3, 3, "" ) +{ + object->setNodeWidth( dAtof(argv[2]) ); +} + +ConsoleMethod( GuiRoadEditorCtrl, getNodePosition, const char*, 2, 2, "" ) +{ + char* returnBuffer = Con::getReturnBuffer(256); + + dSprintf(returnBuffer, 256, "%f %f %f", + object->getNodePosition().x, object->getNodePosition().y, object->getNodePosition().z); + + return returnBuffer; +} + +ConsoleMethod( GuiRoadEditorCtrl, setNodePosition, void, 3, 3, "" ) +{ + Point3F pos; + + S32 count = dSscanf( argv[2], "%f %f %f", + &pos.x, &pos.y, &pos.z); + + if ( (count != 3) ) + { + Con::printf("Failed to parse node information \"px py pz\" from '%s'", argv[3]); + return; + } + + object->setNodePosition( pos ); +} + +ConsoleMethod( GuiRoadEditorCtrl, setSelectedRoad, void, 2, 3, "" ) +{ + if ( argc == 2 ) + object->setSelectedRoad(NULL); + else + { + DecalRoad *road = NULL; + if ( Sim::findObject( argv[2], road ) ) + object->setSelectedRoad(road); + } +} + +ConsoleMethod( GuiRoadEditorCtrl, getSelectedRoad, const char*, 2, 2, "" ) +{ + DecalRoad *road = object->getSelectedRoad(); + if ( road ) + return road->scriptThis(); + + return NULL; +} + +ConsoleMethod( GuiRoadEditorCtrl, getSelectedNode, S32, 2, 2, "" ) +{ + return object->getSelectedNode(); +} + +ConsoleMethod( GuiRoadEditorCtrl, deleteRoad, void, 2, 2, "" ) +{ + object->deleteSelectedRoad(); +} diff --git a/environment/editors/guiRoadEditorCtrl.h b/environment/editors/guiRoadEditorCtrl.h new file mode 100644 index 0000000..bc24ef0 --- /dev/null +++ b/environment/editors/guiRoadEditorCtrl.h @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIROADEDITORCTRL_H_ +#define _GUIROADEDITORCTRL_H_ + +#ifndef _EDITTSCTRL_H_ +#include "gui/worldEditor/editTSCtrl.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _DECALROAD_H_ +#include "environment/decalRoad.h" +#endif + +class GameBase; +class DecalRoad; + + +class GuiRoadEditorCtrl : public EditTSCtrl +{ + typedef EditTSCtrl Parent; + + public: + + friend class GuiRoadEditorUndoAction; + + String mSelectRoadMode; + String mAddRoadMode; + String mAddNodeMode; + String mInsertPointMode; + String mRemovePointMode; + String mMovePointMode; + String mScalePointMode; + + GuiRoadEditorCtrl(); + ~GuiRoadEditorCtrl(); + + DECLARE_CONOBJECT(GuiRoadEditorCtrl); + + // SimObject + bool onAdd(); + static void initPersistFields(); + + // GuiControl + virtual void onSleep(); + virtual void onRender(Point2I offset, const RectI &updateRect); + + // EditTSCtrl + bool onKeyDown(const GuiEvent& event); + void get3DCursor( GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_ ); + void on3DMouseDown(const Gui3DMouseEvent & event); + void on3DMouseUp(const Gui3DMouseEvent & event); + void on3DMouseMove(const Gui3DMouseEvent & event); + void on3DMouseDragged(const Gui3DMouseEvent & event); + void on3DMouseEnter(const Gui3DMouseEvent & event); + void on3DMouseLeave(const Gui3DMouseEvent & event); + void on3DRightMouseDown(const Gui3DMouseEvent & event); + void on3DRightMouseUp(const Gui3DMouseEvent & event); + void updateGuiInfo(); + void renderScene(const RectI & updateRect); + + bool getTerrainPos( const Gui3DMouseEvent & event, Point3F &tpos ); + void deleteSelectedNode(); + void deleteSelectedRoad( bool undoAble = true ); + + void setMode( String mode, bool sourceShortcut ); + String getMode() { return mMode; } + + void setSelectedRoad( DecalRoad *road ); + DecalRoad* getSelectedRoad() { return mSelRoad; }; + void setSelectedNode( S32 node ); + S32 getSelectedNode() { return mSelNode; }; + + F32 getNodeWidth(); + void setNodeWidth( F32 width ); + + Point3F getNodePosition(); + void setNodePosition( Point3F pos ); + + void setTextureFile( StringTableEntry file ); + + protected: + + void _drawRoadSpline( DecalRoad *road, const ColorI &color ); + void _drawRoadControlNodes( DecalRoad *road, const ColorI &color ); + + void submitUndo( const UTF8 *name ); + + bool mSavedDrag; + bool mIsDirty; + + SimSet *mRoadSet; + S32 mSelNode; + S32 mHoverNode; + U32 mAddNodeIdx; + SimObjectPtr mSelRoad; + SimObjectPtr mHoverRoad; + + ColorI mHoverSplineColor; + ColorI mSelectedSplineColor; + ColorI mHoverNodeColor; + + String mMode; + + F32 mDefaultWidth; + S32 mInsertIdx; + + F32 mStartWidth; + S32 mStartX; + Point3F mStartWorld; + + Point2I mNodeHalfSize; + + GFXStateBlockRef mZDisableSB; + GFXStateBlockRef mZEnableSB; +}; + +class GuiRoadEditorUndoAction : public UndoAction +{ + public: + + GuiRoadEditorUndoAction( const UTF8* actionName ) : UndoAction( actionName ) + { + } + + GuiRoadEditorCtrl *mRoadEditor; + + Vector mNodes; + + SimObjectId mObjId; + String mMaterialName; + F32 mBreakAngle; + U32 mSegmentsPerBatch; + F32 mTextureLength; + + virtual void undo(); + virtual void redo() { undo(); } +}; + +#endif + + + diff --git a/environment/meshRoad.cpp b/environment/meshRoad.cpp new file mode 100644 index 0000000..77ff1fd --- /dev/null +++ b/environment/meshRoad.cpp @@ -0,0 +1,2145 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/meshRoad.h" + +#include "console/consoleTypes.h" +#include "util/catmullRom.h" +#include "math/util/quadTransforms.h" +#include "sceneGraph/simPath.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sgUtil.h" +#include "renderInstance/renderPassManager.h" +#include "T3D/gameConnection.h" +#include "core/stream/bitStream.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDebugEvent.h" +#include "materials/materialManager.h" +#include "math/mathIO.h" +#include "math/mathUtils.h" +#include "math/util/frustum.h" +#include "util/triBoxCheck.h" +#include "util/triRayCheck.h" +#include "gui/3d/guiTSControl.h" +#include "materials/shaderData.h" +#include "gfx/sim/gfxStateBlockData.h" +#include "gfx/sim/debugDraw.h" +#include "collision/concretePolyList.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsStatic.h" + + +#define MIN_METERS_PER_SEGMENT 1.0f +#define MIN_NODE_DEPTH 0.25f +#define MAX_NODE_DEPTH 50.0f +#define MIN_NODE_WIDTH 0.25f +#define MAX_NODE_WIDTH 50.0f + + +static U32 gIdxArray[6][2][3] = { + { { 0, 4, 5 }, { 0, 5, 1 }, }, // Top Face + { { 2, 6, 4 }, { 2, 4, 0 }, }, // Left Face + { { 1, 5, 7 }, { 1, 7, 3 }, }, // Right Face + { { 2, 3, 7 }, { 2, 7, 6 }, }, // Bottom Face + { { 0, 1, 3 }, { 0, 3, 2 }, }, // Front Face + { { 4, 6, 7 }, { 4, 7, 5 }, }, // Back Face +}; + +static S32 QSORT_CALLBACK compareHitSegments(const void* a,const void* b) +{ + const MeshRoadHitSegment *fa = (MeshRoadHitSegment*)a; + const MeshRoadHitSegment *fb = (MeshRoadHitSegment*)b; + + return fb->t - fa->t; +} + + +//------------------------------------------------------------------------------ +// MeshRoadConvex Class +//------------------------------------------------------------------------------ + +const MatrixF& MeshRoadConvex::getTransform() const +{ + return mObject->getTransform(); +} + +Box3F MeshRoadConvex::getBoundingBox() const +{ + return box; +} + +Box3F MeshRoadConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const +{ + Box3F newBox = box; + newBox.minExtents.convolve(scale); + newBox.maxExtents.convolve(scale); + mat.mul(newBox); + return newBox; +} + +Point3F MeshRoadConvex::support(const VectorF& vec) const +{ + F32 bestDot = mDot( verts[0], vec ); + + const Point3F *bestP = &verts[0]; + for(S32 i=1; i<4; i++) + { + F32 newD = mDot(verts[i], vec); + if(newD > bestDot) + { + bestDot = newD; + bestP = &verts[i]; + } + } + + return *bestP; +} + +void MeshRoadConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf) +{ + cf->material = 0; + cf->object = mObject; + + // For a tetrahedron this is pretty easy... first + // convert everything into world space. + Point3F tverts[4]; + mat.mulP(verts[0], &tverts[0]); + mat.mulP(verts[1], &tverts[1]); + mat.mulP(verts[2], &tverts[2]); + mat.mulP(verts[3], &tverts[3]); + + // Points... + S32 firstVert = cf->mVertexList.size(); + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[0]; + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[1]; + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[2]; + cf->mVertexList.increment(); cf->mVertexList.last() = tverts[3]; + + // Edges... + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+0; + cf->mEdgeList.last().vertex[1] = firstVert+1; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+1; + cf->mEdgeList.last().vertex[1] = firstVert+2; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+2; + cf->mEdgeList.last().vertex[1] = firstVert+0; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+3; + cf->mEdgeList.last().vertex[1] = firstVert+0; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+3; + cf->mEdgeList.last().vertex[1] = firstVert+1; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = firstVert+3; + cf->mEdgeList.last().vertex[1] = firstVert+2; + + // Triangles... + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[0]); + cf->mFaceList.last().vertex[0] = firstVert+2; + cf->mFaceList.last().vertex[1] = firstVert+1; + cf->mFaceList.last().vertex[2] = firstVert+0; + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[1], tverts[0], tverts[3]); + cf->mFaceList.last().vertex[0] = firstVert+1; + cf->mFaceList.last().vertex[1] = firstVert+0; + cf->mFaceList.last().vertex[2] = firstVert+3; + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[3]); + cf->mFaceList.last().vertex[0] = firstVert+2; + cf->mFaceList.last().vertex[1] = firstVert+1; + cf->mFaceList.last().vertex[2] = firstVert+3; + + cf->mFaceList.increment(); + cf->mFaceList.last().normal = PlaneF(tverts[0], tverts[2], tverts[3]); + cf->mFaceList.last().vertex[0] = firstVert+0; + cf->mFaceList.last().vertex[1] = firstVert+2; + cf->mFaceList.last().vertex[2] = firstVert+3; +} + + +void MeshRoadConvex::getPolyList( AbstractPolyList* list ) +{ + // Transform the list into object space and set the pointer to the object + //MatrixF i( mObject->getTransform() ); + //Point3F iS( mObject->getScale() ); + //list->setTransform(&i, iS); + + list->setTransform( &MatrixF::Identity, Point3F::One ); + list->setObject(mObject); + + // Points... + S32 base = list->addPoint(verts[0]); + list->addPoint(verts[2]); + list->addPoint(verts[1]); + list->addPoint(verts[3]); + + // Planes... + list->begin(0,0); + list->vertex(base + 2); + list->vertex(base + 1); + list->vertex(base + 0); + list->plane(base + 2, base + 1, base + 0); + list->end(); + list->begin(0,0); + list->vertex(base + 2); + list->vertex(base + 1); + list->vertex(base + 3); + list->plane(base + 2, base + 1, base + 3); + list->end(); + list->begin(0,0); + list->vertex(base + 3); + list->vertex(base + 1); + list->vertex(base + 0); + list->plane(base + 3, base + 1, base + 0); + list->end(); + list->begin(0,0); + list->vertex(base + 2); + list->vertex(base + 3); + list->vertex(base + 0); + list->plane(base + 2, base + 3, base + 0); + list->end(); +} + + +//------------------------------------------------------------------------------ +// MeshRoadSegment Class +//------------------------------------------------------------------------------ + +MeshRoadSegment::MeshRoadSegment() +{ + mPlaneCount = 0; + columns = 0; + rows = 0; + numVerts = 0; + numTriangles = 0; + + startVert = 0; + endVert = 0; + startIndex = 0; + endIndex = 0; + + slice0 = NULL; + slice1 = NULL; +} + +MeshRoadSegment::MeshRoadSegment( MeshRoadSlice *rs0, MeshRoadSlice *rs1, const MatrixF &roadMat ) +{ + columns = 0; + rows = 0; + numVerts = 0; + numTriangles = 0; + + startVert = 0; + endVert = 0; + startIndex = 0; + endIndex = 0; + + slice0 = rs0; + slice1 = rs1; + + // Calculate the bounding box(s) + worldbounds.minExtents = worldbounds.maxExtents = rs0->p0; + worldbounds.extend( rs0->p2 ); + worldbounds.extend( rs0->pb0 ); + worldbounds.extend( rs0->pb2 ); + worldbounds.extend( rs1->p0 ); + worldbounds.extend( rs1->p2 ); + worldbounds.extend( rs1->pb0 ); + worldbounds.extend( rs1->pb2 ); + + objectbounds = worldbounds; + roadMat.mul( objectbounds ); + + // Calculate the planes for this segment + // Will be used for intersection/buoyancy tests + + mPlaneCount = 6; + mPlanes[0].set( slice0->pb0, slice0->p0, slice1->p0 ); // left + mPlanes[1].set( slice1->pb2, slice1->p2, slice0->p2 ); // right + mPlanes[2].set( slice0->pb2, slice0->p2, slice0->p0 ); // near + mPlanes[3].set( slice1->p0, slice1->p2, slice1->pb2 ); // far + mPlanes[4].set( slice1->p2, slice1->p0, slice0->p0 ); // top + mPlanes[5].set( slice0->pb0, slice1->pb0, slice1->pb2 ); // bottom +} + +void MeshRoadSegment::set( MeshRoadSlice *rs0, MeshRoadSlice *rs1 ) +{ + columns = 0; + rows = 0; + numVerts = 0; + numTriangles = 0; + + startVert = 0; + endVert = 0; + startIndex = 0; + endIndex = 0; + + slice0 = rs0; + slice1 = rs1; +} + +bool MeshRoadSegment::intersectBox( const Box3F &bounds ) const +{ + // This code copied from Frustum class. + + Point3F maxPoint; + F32 maxDot; + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + for ( S32 i = 0; i < mPlaneCount; i++ ) + { + // This is pretty much as optimal as you can + // get for a plane vs AABB test... + // + // 4 comparisons + // 3 multiplies + // 2 adds + // 1 negation + // + // It will early out as soon as it detects the + // bounds is outside one of the planes. + + if ( mPlanes[i].x > 0 ) + maxPoint.x = bounds.maxExtents.x; + else + maxPoint.x = bounds.minExtents.x; + + if ( mPlanes[i].y > 0 ) + maxPoint.y = bounds.maxExtents.y; + else + maxPoint.y = bounds.minExtents.y; + + if ( mPlanes[i].z > 0 ) + maxPoint.z = bounds.maxExtents.z; + else + maxPoint.z = bounds.minExtents.z; + + maxDot = mDot( maxPoint, mPlanes[ i ] ); + + if ( maxDot <= -mPlanes[ i ].d ) + return false; + } + + return true; +} + +bool MeshRoadSegment::containsPoint( const Point3F &pnt ) const +{ + // This code from Frustum class. + + F32 maxDot; + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + for ( S32 i = 0; i < mPlaneCount; i++ ) + { + const PlaneF &plane = mPlanes[ i ]; + + // This is pretty much as optimal as you can + // get for a plane vs point test... + // + // 1 comparison + // 2 multiplies + // 1 adds + // + // It will early out as soon as it detects the + // point is outside one of the planes. + + maxDot = mDot( pnt, plane ) + plane.d; + if ( maxDot < 0.0f ) + return false; + } + + return true; +} + +F32 MeshRoadSegment::distanceToSurface(const Point3F &pnt) const +{ + return mPlanes[4].distToPlane( pnt ); +} + +//------------------------------------------------------------------------------ +// MeshRoad Class +//------------------------------------------------------------------------------ + +bool MeshRoad::smEditorOpen = false; +bool MeshRoad::smShowBatches = false; +bool MeshRoad::smShowSpline = true; +bool MeshRoad::smShowRoad = true; +bool MeshRoad::smWireframe = true; +SimObjectPtr MeshRoad::smServerMeshRoadSet = NULL; + +GFXStateBlockRef MeshRoad::smWireframeSB; + +IMPLEMENT_CO_NETOBJECT_V1(MeshRoad); + +MeshRoad::MeshRoad() +: mTextureLength( 5.0f ), + mBreakAngle( 3.0f ), + mPhysicsRep( NULL ), + mWidthSubdivisions( 0 ) +{ + mConvexList = new Convex; + + // Setup NetObject. + mTypeMask |= StaticObjectType | StaticTSObjectType | StaticRenderedObjectType; + mNetFlags.set(Ghostable); + + mMatInst[Top] = NULL; + mMatInst[Bottom] = NULL; + mMatInst[Side] = NULL; +} + +MeshRoad::~MeshRoad() +{ + delete mConvexList; + mConvexList = NULL; +} + +void MeshRoad::initPersistFields() +{ + addGroup( "MeshRoad" ); + + addField( "topMaterial", TypeMaterialName, Offset( mMaterialName[Top], MeshRoad ) ); + addField( "bottomMaterial", TypeMaterialName, Offset( mMaterialName[Bottom], MeshRoad ) ); + addField( "sideMaterial", TypeMaterialName, Offset( mMaterialName[Side], MeshRoad ) ); + addField( "textureLength", TypeF32, Offset( mTextureLength, MeshRoad ), + "The length in meters of textures mapped to the MeshRoad." ); + addField( "breakAngle", TypeF32, Offset( mBreakAngle, MeshRoad ), + "Angle in degrees - MeshRoad will subdivide the spline if its curve is greater than this threshold." ); + addField( "widthSubdivisions", TypeS32, Offset( mWidthSubdivisions, MeshRoad ), "Subdivide segments widthwise this many times when generating vertices." ); + + endGroup( "MeshRoad" ); + + addGroup( "Internal" ); + + addProtectedField( "Node", TypeString, NULL, &addNodeFromField, &emptyStringProtectedGetFn, + "Do not modify, for internal use." ); + + endGroup( "Internal" ); + + Parent::initPersistFields(); +} + +void MeshRoad::consoleInit() +{ + Parent::consoleInit(); + + Con::addVariable( "$MeshRoad::EditorOpen", TypeBool, &MeshRoad::smEditorOpen ); + Con::addVariable( "$MeshRoad::wireframe", TypeBool, &MeshRoad::smWireframe ); + Con::addVariable( "$MeshRoad::showBatches", TypeBool, &MeshRoad::smShowBatches ); + Con::addVariable( "$MeshRoad::showSpline", TypeBool, &MeshRoad::smShowSpline ); + Con::addVariable( "$MeshRoad::showRoad", TypeBool, &MeshRoad::smShowRoad ); +} + +bool MeshRoad::addNodeFromField( void* obj, const char* data ) +{ + MeshRoad *pObj = static_cast(obj); + + //if ( !pObj->isProperlyAdded() ) + //{ + F32 width, depth; + Point3F pos, normal; + U32 result = dSscanf( data, "%g %g %g %g %g %g %g %g", &pos.x, &pos.y, &pos.z, &width, &depth, &normal.x, &normal.y, &normal.z ); + if ( result == 8 ) + pObj->_addNode( pos, width, depth, normal ); + //} + + return false; +} + +bool MeshRoad::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Reset the World Box. + setGlobalBounds(); + resetWorldBox(); + + // Set the Render Transform. + setRenderTransform(mObjToWorld); + + // Add to Scene. + addToScene(); + + // Add to ServerMeshRoadSet + if ( isServerObject() ) + { + getServerSet()->addObject( this ); + } + + if ( isClientObject() ) + _initMaterial(); + + // Generate the Vert/Index buffers and everything else. + _regenerate(); + + return true; +} + +void MeshRoad::onRemove() +{ + SAFE_DELETE( mPhysicsRep ); + + mConvexList->nukeList(); + + for ( U32 i = 0; i < SurfaceCount; i++ ) + { + SAFE_DELETE( mMatInst[i] ); + } + + removeFromScene(); + Parent::onRemove(); +} + +void MeshRoad::inspectPostApply() +{ + // Set Parent. + Parent::inspectPostApply(); + + //if ( mMetersPerSegment < MIN_METERS_PER_SEGMENT ) + // mMetersPerSegment = MIN_METERS_PER_SEGMENT; + + setMaskBits(MeshRoadMask); +} + +void MeshRoad::onStaticModified( const char* slotName, const char*newValue ) +{ + Parent::onStaticModified( slotName, newValue ); + + if ( dStricmp( slotName, "breakAngle" ) == 0 ) + { + setMaskBits( RegenMask ); + } +} + +void MeshRoad::writeFields( Stream &stream, U32 tabStop ) +{ + Parent::writeFields( stream, tabStop ); + + // Now write all nodes + + stream.write(2, "\r\n"); + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + const MeshRoadNode &node = mNodes[i]; + + stream.writeTabs(tabStop); + + char buffer[1024]; + dMemset( buffer, 0, 1024 ); + dSprintf( buffer, 1024, "Node = \"%g %g %g %g %g %g %g %g\";", node.point.x, node.point.y, node.point.z, node.width, node.depth, node.normal.x, node.normal.y, node.normal.z ); + stream.writeLine( (const U8*)buffer ); + } +} + +bool MeshRoad::writeField( StringTableEntry fieldname, const char *value ) +{ + if ( fieldname == StringTable->insert("Node") ) + return false; + + return Parent::writeField( fieldname, value ); +} + +void MeshRoad::onEditorEnable() +{ +} + +void MeshRoad::onEditorDisable() +{ +} + +SimSet* MeshRoad::getServerSet() +{ + if ( !smServerMeshRoadSet ) + { + smServerMeshRoadSet = new SimSet(); + smServerMeshRoadSet->registerObject( "ServerMeshRoadSet" ); + Sim::getRootGroup()->addObject( smServerMeshRoadSet ); + } + + return smServerMeshRoadSet; +} + +bool MeshRoad::prepRenderImage( SceneState* state, + const U32 stateKey, + const U32, + const bool ) +{ + if ( mNodes.size() <= 1 || isLastState(state, stateKey) ) + return false; + + // Set Last State. + setLastState(state, stateKey); + + // Is Object Rendered? + if ( !state->isObjectRendered(this) ) + return false; + + RenderPassManager *renderPass = state->getRenderPass(); + + // Normal Road RenderInstance + // Always rendered when the editor is not open + // otherwise obey the smShowRoad flag + if ( smShowRoad || !smEditorOpen ) + { + MeshRenderInst coreRI; + coreRI.clear(); + coreRI.objectToWorld = &MatrixF::Identity; + coreRI.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View); + coreRI.projection = renderPass->allocSharedXform(RenderPassManager::Projection); + coreRI.type = RenderPassManager::RIT_Mesh; + + // Get the light manager and setup lights + LightManager *lm = state->getLightManager(); + if ( lm ) + { + lm->setupLights( this, getWorldSphere() ); + lm->getBestLights( coreRI.lights, 8 ); + lm->resetLights(); + } + + for ( U32 i = 0; i < SurfaceCount; i++ ) + { + if ( !mMatInst[i] ) + continue; + + MeshRenderInst *ri = renderPass->allocInst(); + + *ri = coreRI; + + // Currently rendering whole road, fix to cull and batch + // per segment. + + ri->matInst = mMatInst[i]; + ri->vertBuff = &mVB[i]; + ri->primBuff = &mPB[i]; + + ri->prim = renderPass->allocPrim(); + ri->prim->type = GFXTriangleList; + ri->prim->minIndex = 0; + ri->prim->startIndex = 0; + ri->prim->numPrimitives = mTriangleCount[i]; + ri->prim->startVertex = 0; + ri->prim->numVertices = mVertCount[i]; + + // We sort by VB. + ri->defaultKey = (U32)ri->vertBuff; // Not 64bit safe! + + renderPass->addInst( ri ); + } + } + + // Debug RenderInstance + // Only when editor is open. + if ( smEditorOpen ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &MeshRoad::_debugRender ); + ri->type = RenderPassManager::RIT_ObjectTranslucent; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void MeshRoad::_initMaterial() +{ + for ( U32 i = 0; i < SurfaceCount; i++ ) + { + if ( mMatInst[i] ) + SAFE_DELETE( mMatInst[i] ); + + if ( mMaterial[i] ) + mMatInst[i] = mMaterial[i]->createMatInstance(); + else + mMatInst[i] = MATMGR->createMatInstance( "WarningMaterial" ); + + mMatInst[i]->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); + } +} + +void MeshRoad::_debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ) +{ + //MeshRoadConvex convex; + //buildConvex( Box3F(true), convex ); + //convex.render(); + //GFXDrawUtil *drawer = GFX->getDrawUtil(); + + //GFX->setStateBlock( smStateBlock ); + return; + /* + U32 convexCount = mDebugConvex.size(); + + PrimBuild::begin( GFXTriangleList, convexCount * 12 ); + PrimBuild::color4i( 0, 0, 255, 155 ); + + for ( U32 i = 0; i < convexCount; i++ ) + { + MeshRoadConvex *convex = mDebugConvex[i]; + + Point3F a = convex->verts[0]; + Point3F b = convex->verts[1]; + Point3F c = convex->verts[2]; + Point3F p = convex->verts[3]; + + //mObjToWorld.mulP(a); + //mObjToWorld.mulP(b); + //mObjToWorld.mulP(c); + //mObjToWorld.mulP(p); + + PrimBuild::vertex3fv( c ); + PrimBuild::vertex3fv( b ); + PrimBuild::vertex3fv( a ); + + PrimBuild::vertex3fv( b ); + PrimBuild::vertex3fv( a ); + PrimBuild::vertex3fv( p ); + + PrimBuild::vertex3fv( c ); + PrimBuild::vertex3fv( b ); + PrimBuild::vertex3fv( p ); + + PrimBuild::vertex3fv( a ); + PrimBuild::vertex3fv( c ); + PrimBuild::vertex3fv( p ); + } + + PrimBuild::end(); + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + ///GFX->getDrawUtil()->drawWireBox( mSegments[i].worldbounds, ColorI(255,0,0,255) ); + } + + GFX->enterDebugEvent( ColorI( 255, 0, 0 ), "DecalRoad_debugRender" ); + GFXTransformSaver saver; + + GFX->setStateBlock( smStateBlock ); + + Point3F size(1,1,1); + ColorI color( 255, 0, 0, 255 ); + + if ( smShowBatches ) + { + for ( U32 i = 0; i < mBatches.size(); i++ ) + { + const Box3F &box = mBatches[i].bounds; + Point3F center; + box.getCenter( ¢er ); + + GFX->getDrawUtil()->drawWireCube( ( box.maxExtents - box.minExtents ) * 0.5f, center, ColorI(255,100,100,255) ); + } + } + + GFX->leaveDebugEvent(); + */ +} + +U32 MeshRoad::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if ( stream->writeFlag( mask & NodeMask ) ) + { + stream->writeInt( mNodes.size(), 16 ); + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + mathWrite( *stream, mNodes[i].point ); + stream->write( mNodes[i].width ); + stream->write( mNodes[i].depth ); + mathWrite( *stream, mNodes[i].normal ); + } + } + + if ( stream->writeFlag( mask & MeshRoadMask ) ) + { + // Write Object Transform. + stream->writeAffineTransform( mObjToWorld ); + + // Write Materials + stream->write( mMaterialName[0] ); + stream->write( mMaterialName[1] ); + stream->write( mMaterialName[2] ); + + stream->write( mTextureLength ); + stream->write( mBreakAngle ); + stream->write( mWidthSubdivisions ); + } + + stream->writeFlag( mask & RegenMask ); + + // Were done ... + return retMask; +} + +void MeshRoad::unpackUpdate(NetConnection * con, BitStream * stream) +{ + // Unpack Parent. + Parent::unpackUpdate(con, stream); + + // NodeMask + if ( stream->readFlag() ) + { + U32 count = stream->readInt( 16 ); + + mNodes.clear(); + + Point3F pos, normal; + F32 width, depth; + for ( U32 i = 0; i < count; i++ ) + { + mathRead( *stream, &pos ); + stream->read( &width ); + stream->read( &depth ); + mathRead( *stream, &normal ); + _addNode( pos, width, depth, normal ); + } + } + + // MeshRoadMask + if(stream->readFlag()) + { + MatrixF ObjectMatrix; + stream->readAffineTransform(&ObjectMatrix); + Parent::setTransform(ObjectMatrix); + + // Read Materials... + Material *pMat = NULL; + + for ( U32 i = 0; i < SurfaceCount; i++ ) + { + stream->read( &mMaterialName[i] ); + + if ( !Sim::findObject( mMaterialName[i], pMat ) ) + Con::printf( "DecalRoad::unpackUpdate, failed to find Material of name &s!", mMaterialName[i].c_str() ); + else + mMaterial[i] = pMat; + } + + if ( isProperlyAdded() ) + _initMaterial(); + + stream->read( &mTextureLength ); + + stream->read( &mBreakAngle ); + + stream->read( &mWidthSubdivisions ); + } + + if ( stream->readFlag() && isProperlyAdded() ) + _regenerate(); +} + +void MeshRoad::setTransform( const MatrixF &mat ) +{ + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + mWorldToObj.mulP( mNodes[i].point ); + mat.mulP( mNodes[i].point ); + } + + Parent::setTransform( mat ); + + if ( mPhysicsRep ) + mPhysicsRep->setTransform( mat ); + + // Regenerate and update the client + _regenerate(); + setMaskBits( NodeMask | RegenMask ); +} + +void MeshRoad::setScale( const VectorF &scale ) +{ + // We ignore scale requests from the editor + // right now. + + //Parent::setScale( scale ); +} + +void MeshRoad::buildConvex(const Box3F& box, Convex* convex) +{ + if ( mSlices.size() < 2 ) + return; + + mConvexList->collectGarbage(); + mDebugConvex.clear(); + + Box3F realBox = box; + mWorldToObj.mul(realBox); + realBox.minExtents.convolveInverse(mObjScale); + realBox.maxExtents.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + U32 segmentCount = mSegments.size(); + + // Create convex(s) for each segment + for ( U32 i = 0; i < segmentCount; i++ ) + { + const MeshRoadSegment &segment = mSegments[i]; + + // Is this segment overlapped? + if ( !segment.getWorldBounds().isOverlapped( box ) ) + continue; + + // Each segment has 6 faces + for ( U32 j = 0; j < 6; j++ ) + { + // Only first segment has front face + if ( j == 4 && i != 0 ) + continue; + // Only last segment has back face + if ( j == 5 && i != segmentCount-1 ) + continue; + + // Each face has 2 convex(s) + for ( U32 k = 0; k < 2; k++ ) + { + // See if this convex exists in the working set already... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for ( CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext ) + { + if ( itr->mConvex->getType() == MeshRoadConvexType ) + { + MeshRoadConvex *pConvex = static_cast(itr->mConvex); + + if ( pConvex->pRoad == this && + pConvex->segmentId == i && + pConvex->faceId == j && + pConvex->triangleId == k ) + { + cc = itr->mConvex; + break; + } + } + } + if (cc) + continue; + + // Get the triangle... + U32 idx0 = gIdxArray[j][k][0]; + U32 idx1 = gIdxArray[j][k][1]; + U32 idx2 = gIdxArray[j][k][2]; + + Point3F a = segment[idx0]; + Point3F b = segment[idx1]; + Point3F c = segment[idx2]; + + // Transform the result into object space! + //mWorldToObj.mulP( a ); + //mWorldToObj.mulP( b ); + //mWorldToObj.mulP( c ); + + PlaneF p( c, b, a ); + Point3F peak = ((a + b + c) / 3.0f) - (p * 0.15f); + + // Set up the convex... + MeshRoadConvex *cp = new MeshRoadConvex(); + + mConvexList->registerObject( cp ); + convex->addToWorkingList( cp ); + + cp->mObject = this; + cp->pRoad = this; + cp->segmentId = i; + cp->faceId = j; + cp->triangleId = k; + + cp->normal = p; + cp->verts[0] = a; + cp->verts[1] = b; + cp->verts[2] = c; + cp->verts[3] = peak; + + // Update the bounding box. + Box3F &bounds = cp->box; + bounds.minExtents.set( F32_MAX, F32_MAX, F32_MAX ); + bounds.maxExtents.set( -F32_MAX, -F32_MAX, -F32_MAX ); + + bounds.minExtents.setMin( a ); + bounds.minExtents.setMin( b ); + bounds.minExtents.setMin( c ); + bounds.minExtents.setMin( peak ); + + bounds.maxExtents.setMax( a ); + bounds.maxExtents.setMax( b ); + bounds.maxExtents.setMax( c ); + bounds.maxExtents.setMax( peak ); + + mDebugConvex.push_back(cp); + } + } + } +} + +bool MeshRoad::buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &) +{ + if ( mSlices.size() < 2 ) + return false; + + polyList->setTransform( &MatrixF::Identity, Point3F::One ); + polyList->setObject(this); + + // JCF: optimize this to not always add everything. + + return buildSegmentPolyList( polyList, 0, mSegments.size() - 1, true, true ); +} + +bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx, U32 endSegIdx, bool capFront, bool capEnd ) +{ + if ( mSlices.size() < 2 ) + return false; + + // Add verts + for ( U32 i = startSegIdx; i <= endSegIdx; i++ ) + { + const MeshRoadSegment &seg = mSegments[startSegIdx]; + + if ( i == startSegIdx ) + { + polyList->addPoint( seg.slice0->p0 ); + polyList->addPoint( seg.slice0->p2 ); + polyList->addPoint( seg.slice0->pb0 ); + polyList->addPoint( seg.slice0->pb2 ); + } + + polyList->addPoint( seg.slice1->p0 ); + polyList->addPoint( seg.slice1->p2 ); + polyList->addPoint( seg.slice1->pb0 ); + polyList->addPoint( seg.slice1->pb2 ); + } + + // Temporaries to hold indices for the corner points of a quad. + S32 p00, p01, p11, p10; + S32 pb00, pb01, pb11, pb10; + U32 offset = 0; + + DebugDrawer *ddraw = NULL;//DebugDrawer::get(); + ClippedPolyList *cpolyList = dynamic_cast(polyList); + MatrixF mat; + Point3F scale; + if ( cpolyList ) + cpolyList->getTransform( &mat, &scale ); + + + for ( U32 i = startSegIdx; i <= endSegIdx; i++ ) + { + p00 = offset; + p10 = offset + 1; + pb00 = offset + 2; + pb10 = offset + 3; + p01 = offset + 4; + p11 = offset + 5; + pb01 = offset + 6; + pb11 = offset + 7; + + // Top Face + + polyList->begin( 0,0 ); + polyList->vertex( p00 ); + polyList->vertex( p01 ); + polyList->vertex( p11 ); + polyList->plane( p00, p01, p11 ); + polyList->end(); + if ( ddraw && cpolyList ) + { + Point3F v0 = cpolyList->mVertexList[p00].point; + mat.mulP( v0 ); + Point3F v1 = cpolyList->mVertexList[p01].point; + mat.mulP( v1 ); + Point3F v2 = cpolyList->mVertexList[p11].point; + mat.mulP( v2 ); + ddraw->drawTri( v0, v1, v2 ); + ddraw->setLastZTest( false ); + ddraw->setLastTTL( 0 ); + } + + polyList->begin( 0,0 ); + polyList->vertex( p00 ); + polyList->vertex( p11 ); + polyList->vertex( p10 ); + polyList->plane( p00, p11, p10 ); + polyList->end(); + if ( ddraw && cpolyList ) + { + ddraw->drawTri( cpolyList->mVertexList[p00].point, cpolyList->mVertexList[p11].point, cpolyList->mVertexList[p10].point ); + ddraw->setLastTTL( 0 ); + } + + // Left Face + + polyList->begin( 0,0 ); + polyList->vertex( pb00 ); + polyList->vertex( pb01 ); + polyList->vertex( p01 ); + polyList->plane( pb00, pb01, p01 ); + polyList->end(); + + polyList->begin( 0,0 ); + polyList->vertex( pb00 ); + polyList->vertex( p01 ); + polyList->vertex( p00 ); + polyList->plane( pb00, p01, p00 ); + polyList->end(); + + // Right Face + + polyList->begin( 0,0 ); + polyList->vertex( p10 ); + polyList->vertex( p11 ); + polyList->vertex( pb11 ); + polyList->plane( p10, p11, pb11 ); + polyList->end(); + + polyList->begin( 0,0 ); + polyList->vertex( p10 ); + polyList->vertex( pb11 ); + polyList->vertex( pb10 ); + polyList->plane( p10, pb11, pb10 ); + polyList->end(); + + // Bottom Face + + polyList->begin( 0,0 ); + polyList->vertex( pb00 ); + polyList->vertex( pb10 ); + polyList->vertex( pb11 ); + polyList->plane( pb00, pb10, pb11 ); + polyList->end(); + + polyList->begin( 0,0 ); + polyList->vertex( pb00 ); + polyList->vertex( pb11 ); + polyList->vertex( pb01 ); + polyList->plane( pb00, pb11, pb01 ); + polyList->end(); + + // Front Face + + if ( i == startSegIdx && capFront ) + { + polyList->begin( 0,0 ); + polyList->vertex( p00 ); + polyList->vertex( p10 ); + polyList->vertex( pb10 ); + polyList->plane( p00, p10, pb10 ); + polyList->end(); + + polyList->begin( 0,0 ); + polyList->vertex( p00 ); + polyList->vertex( pb10 ); + polyList->vertex( pb00 ); + polyList->plane( p00, pb10, pb00 ); + polyList->end(); + } + + // Back Face + if ( i == endSegIdx && capEnd ) + { + polyList->begin( 0,0 ); + polyList->vertex( p01 ); + polyList->vertex( pb01 ); + polyList->vertex( pb11 ); + polyList->plane( p01, pb01, pb11 ); + polyList->end(); + + polyList->begin( 0,0 ); + polyList->vertex( p01 ); + polyList->vertex( pb11 ); + polyList->vertex( p11 ); + polyList->plane( p01, pb11, p11 ); + polyList->end(); + } + + offset += 4; + } + + return true; +} + +bool MeshRoad::castRay( const Point3F &s, const Point3F &e, RayInfo *info ) +{ + Point3F start = s; + Point3F end = e; + mObjToWorld.mulP(start); + mObjToWorld.mulP(end); + + F32 out = 1.0f; // The output fraction/percentage along the line defined by s and e + VectorF norm(0.0f, 0.0f, 0.0f); // The normal of the face intersected + + Vector hitSegments; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const MeshRoadSegment &segment = mSegments[i]; + + F32 t; + VectorF n; + + if ( segment.getWorldBounds().collideLine( start, end, &t, &n ) ) + { + hitSegments.increment(); + hitSegments.last().t = t; + hitSegments.last().idx = i; + } + } + + dQsort( hitSegments.address(), hitSegments.size(), sizeof(MeshRoadHitSegment), compareHitSegments ); + + U32 idx0, idx1, idx2; + F32 t; + + for ( U32 i = 0; i < hitSegments.size(); i++ ) + { + U32 segIdx = hitSegments[i].idx; + const MeshRoadSegment &segment = mSegments[segIdx]; + + // Each segment has 6 faces + for ( U32 j = 0; j < 6; j++ ) + { + if ( j == 4 && segIdx != 0 ) + continue; + + if ( j == 5 && segIdx != mSegments.size() - 1 ) + continue; + + // Each face has 2 triangles + for ( U32 k = 0; k < 2; k++ ) + { + idx0 = gIdxArray[j][k][0]; + idx1 = gIdxArray[j][k][1]; + idx2 = gIdxArray[j][k][2]; + + const Point3F &v0 = segment[idx0]; + const Point3F &v1 = segment[idx1]; + const Point3F &v2 = segment[idx2]; + + if ( !MathUtils::mLineTriangleCollide( start, end, + v2, v1, v0, + NULL, + &t ) ) + continue; + + if ( t >= 0.0f && t < 1.0f && t < out ) + { + out = t; + norm = PlaneF( v0, v1, v2 ); + } + } + } + + if (out >= 0.0f && out < 1.0f) + break; + } + + if (out >= 0.0f && out < 1.0f) + { + info->t = out; + info->normal = norm; + info->point.interpolate(start, end, out); + info->face = -1; + info->object = this; + + return true; + } + + return false; +} + +bool MeshRoad::collideBox(const Point3F &start, const Point3F &end, RayInfo* info) +{ + Con::warnf( "MeshRoad::collideBox() - not yet implemented!" ); + return Parent::collideBox( start, end, info ); +} + +void MeshRoad::_regenerate() +{ + if ( mNodes.size() == 0 ) + return; + + const Point3F &nodePt = mNodes.first().point; + + MatrixF mat( true ); + mat.setPosition( nodePt ); + Parent::setTransform( mat ); + + _generateSlices(); +} + +void MeshRoad::_generateSlices() +{ + if ( mNodes.size() < 2 ) + return; + + // Create the spline, initialized with the MeshRoadNode(s) + U32 nodeCount = mNodes.size(); + MeshRoadSplineNode *splineNodes = new MeshRoadSplineNode[nodeCount]; + + for ( U32 i = 0; i < nodeCount; i++ ) + { + MeshRoadSplineNode &splineNode = splineNodes[i]; + const MeshRoadNode &node = mNodes[i]; + + splineNode.x = node.point.x; + splineNode.y = node.point.y; + splineNode.z = node.point.z; + splineNode.width = node.width; + splineNode.depth = node.depth; + splineNode.normal = node.normal; + } + + CatmullRom spline; + spline.initialize( nodeCount, splineNodes ); + delete [] splineNodes; + + mSlices.clear(); + + VectorF lastBreakVector(0,0,0); + MeshRoadSlice slice; + MeshRoadSplineNode lastBreakNode; + lastBreakNode = spline.evaluate(0.0f); + + for ( U32 i = 1; i < mNodes.size(); i++ ) + { + F32 t1 = spline.getTime(i); + F32 t0 = spline.getTime(i-1); + + F32 segLength = spline.arcLength( t0, t1 ); + + U32 numSegments = mCeil( segLength / MIN_METERS_PER_SEGMENT ); + numSegments = getMax( numSegments, (U32)1 ); + F32 tstep = ( t1 - t0 ) / numSegments; + + U32 startIdx = 0; + U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; + + for ( U32 j = startIdx; j < endIdx; j++ ) + { + F32 t = t0 + tstep * j; + MeshRoadSplineNode splineNode = spline.evaluate(t); + + VectorF toNodeVec = splineNode.getPosition() - lastBreakNode.getPosition(); + toNodeVec.normalizeSafe(); + + if ( lastBreakVector.isZero() ) + lastBreakVector = toNodeVec; + + F32 angle = mRadToDeg( mAcos( mDot( toNodeVec, lastBreakVector ) ) ); + + if ( j == startIdx || + ( j == endIdx - 1 && i == mNodes.size() - 1 ) || + angle > mBreakAngle ) + { + // Push back a spline node + slice.p1.set( splineNode.x, splineNode.y, splineNode.z ); + slice.width = splineNode.width; + slice.depth = splineNode.depth; + slice.normal = splineNode.normal; + slice.normal.normalize(); + slice.parentNodeIdx = i-1; + slice.t = t; + mSlices.push_back( slice ); + + lastBreakVector = splineNode.getPosition() - lastBreakNode.getPosition(); + lastBreakVector.normalizeSafe(); + + lastBreakNode = splineNode; + } + } + } + + // + // Calculate uvec, fvec, and rvec for all slices + // + + MatrixF mat(true); + + for ( U32 i = 0; i < mSlices.size(); i++ ) + { + calcSliceTransform( i, mat ); + mat.getColumn( 0, &mSlices[i].rvec ); + mat.getColumn( 1, &mSlices[i].fvec ); + mat.getColumn( 2, &mSlices[i].uvec ); + } + + // + // Calculate p0/p2/pb0/pb2 for all slices + // + for ( U32 i = 0; i < mSlices.size(); i++ ) + { + MeshRoadSlice *slice = &mSlices[i]; + slice->p0 = slice->p1 - slice->rvec * slice->width * 0.5f; + slice->p2 = slice->p1 + slice->rvec * slice->width * 0.5f; + slice->pb0 = slice->p0 - slice->uvec * slice->depth; + slice->pb2 = slice->p2 - slice->uvec * slice->depth; + } + + // Generate the object/world bounds + Box3F box; + for ( U32 i = 0; i < mSlices.size(); i++ ) + { + const MeshRoadSlice &slice = mSlices[i]; + + if ( i == 0 ) + { + box.minExtents = slice.p0; + box.maxExtents = slice.p2; + box.extend( slice.pb0 ); + box.extend( slice.pb2 ); + } + else + { + box.extend( slice.p0 ); + box.extend( slice.p2 ); + box.extend( slice.pb0 ); + box.extend( slice.pb2 ); + } + } + + Point3F pos = getPosition(); + + mWorldBox = box; + resetObjectBox(); + + _generateSegments(); +} + +void MeshRoad::_generateSegments() +{ + mSegments.clear(); + + for ( U32 i = 0; i < mSlices.size() - 1; i++ ) + { + MeshRoadSegment seg( &mSlices[i], &mSlices[i+1], getWorldTransform() ); + + mSegments.push_back( seg ); + } + + if ( isClientObject() ) + _generateVerts(); + + if ( gPhysicsPlugin ) + { + SAFE_DELETE( mPhysicsRep ); + mPhysicsRep = gPhysicsPlugin->createStatic( this ); + } +} + +void MeshRoad::_generateVerts() +{ + const U32 widthDivisions = getMax( 0, mWidthSubdivisions ); + const F32 divisionStep = 1.0f / (F32)( widthDivisions + 1 ); + const U32 sliceCount = mSlices.size(); + const U32 segmentCount = mSegments.size(); + + mVertCount[Top] = ( 2 + widthDivisions ) * sliceCount; + mTriangleCount[Top] = segmentCount * 2 * ( widthDivisions + 1 ); + + mVertCount[Bottom] = sliceCount * 2; + mTriangleCount[Bottom] = segmentCount * 2; + + mVertCount[Side] = sliceCount * 4; + mTriangleCount[Side] = segmentCount * 4 + 4; + + // Calculate TexCoords for Slices + + F32 texCoordV = 0.0f; + mSlices[0].texCoordV = 0.0f; + + for ( U32 i = 1; i < sliceCount; i++ ) + { + MeshRoadSlice &slice = mSlices[i]; + MeshRoadSlice &prevSlice = mSlices[i-1]; + + // Increment the textCoordV for the next slice. + F32 len = ( slice.p1 - prevSlice.p1 ).len(); + texCoordV += len / mTextureLength; + + slice.texCoordV = texCoordV; + } + + // Make Vertex Buffers + GFXVertexPNTT *pVert = NULL; + U32 vertCounter = 0; + + // Top Buffers... + + mVB[Top].set( GFX, mVertCount[Top], GFXBufferTypeStatic ); + pVert = mVB[Top].lock(); + vertCounter = 0; + + for ( U32 i = 0; i < sliceCount; i++ ) + { + MeshRoadSlice &slice = mSlices[i]; + + pVert->point = slice.p0; + pVert->normal = slice.uvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set(1,slice.texCoordV); + pVert++; + vertCounter++; + + for ( U32 j = 0; j < widthDivisions; j++ ) + { + const F32 t = divisionStep * (F32)( j + 1 ); + + pVert->point.interpolate( slice.p0, slice.p2, t ); + pVert->normal = slice.uvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set( 1.0f - t, slice.texCoordV ); + pVert++; + vertCounter++; + } + + pVert->point = slice.p2; + pVert->normal = slice.uvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set( 0, slice.texCoordV ); + pVert++; + vertCounter++; + } + + AssertFatal( vertCounter == mVertCount[Top], "MeshRoad, wrote incorrect number of verts in mVB[Top]!" ); + + mVB[Top].unlock(); + + // Bottom Buffer... + + mVB[Bottom].set( GFX, mVertCount[Bottom], GFXBufferTypeStatic ); + pVert = mVB[Bottom].lock(); + vertCounter = 0; + + for ( U32 i = 0; i < sliceCount; i++ ) + { + MeshRoadSlice &slice = mSlices[i]; + + pVert->point = slice.pb2; + pVert->normal = -slice.uvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set(0,slice.texCoordV); + pVert++; + vertCounter++; + + pVert->point = slice.pb0; + pVert->normal = -slice.uvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set(1,slice.texCoordV); + pVert++; + vertCounter++; + } + + AssertFatal( vertCounter == mVertCount[Bottom], "MeshRoad, wrote incorrect number of verts in mVB[Bottom]!" ); + + mVB[Bottom].unlock(); + + // Side Buffers... + + mVB[Side].set( GFX, mVertCount[Side], GFXBufferTypeStatic ); + pVert = mVB[Side].lock(); + vertCounter = 0; + + for ( U32 i = 0; i < sliceCount; i++ ) + { + MeshRoadSlice &slice = mSlices[i]; + + pVert->point = slice.p0; + pVert->normal = -slice.rvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set(1,slice.texCoordV); + pVert++; + vertCounter++; + + pVert->point = slice.p2; + pVert->normal = slice.rvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set(1,slice.texCoordV); + pVert++; + vertCounter++; + + pVert->point = slice.pb0; + pVert->normal = -slice.rvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set(0,slice.texCoordV); + pVert++; + vertCounter++; + + pVert->point = slice.pb2; + pVert->normal = slice.rvec; + pVert->tangent = slice.fvec; + pVert->texCoord.set(0,slice.texCoordV); + pVert++; + vertCounter++; + } + + AssertFatal( vertCounter == mVertCount[Side], "MeshRoad, wrote incorrect number of verts in mVB[Side]!" ); + + mVB[Side].unlock(); + + // Make Primitive Buffers + U32 p00, p01, p11, p10; + U32 pb00, pb01, pb11, pb10; + U32 offset = 0; + U16 *pIdx = NULL; + U32 curIdx = 0; + + // Top Primitive Buffer + + mPB[Top].set( GFX, mTriangleCount[Top] * 3, mTriangleCount[Top], GFXBufferTypeStatic ); + + mPB[Top].lock(&pIdx); + curIdx = 0; + offset = 0; + + const U32 rowStride = 2 + widthDivisions; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + for ( U32 j = 0; j < widthDivisions + 1; j++ ) + { + p00 = offset; + p10 = offset + 1; + p01 = offset + rowStride; + p11 = offset + rowStride + 1; + + pIdx[curIdx] = p00; + curIdx++; + pIdx[curIdx] = p01; + curIdx++; + pIdx[curIdx] = p11; + curIdx++; + + pIdx[curIdx] = p00; + curIdx++; + pIdx[curIdx] = p11; + curIdx++; + pIdx[curIdx] = p10; + curIdx++; + + offset += 1; + } + + offset += 1; + } + + + AssertFatal( curIdx == mTriangleCount[Top] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Top]!" ); + + mPB[Top].unlock(); + + // Bottom Primitive Buffer + + mPB[Bottom].set( GFX, mTriangleCount[Bottom] * 3, mTriangleCount[Bottom], GFXBufferTypeStatic ); + + mPB[Bottom].lock(&pIdx); + curIdx = 0; + offset = 0; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + p00 = offset; + p10 = offset + 1; + p01 = offset + 2; + p11 = offset + 3; + + pIdx[curIdx] = p00; + curIdx++; + pIdx[curIdx] = p01; + curIdx++; + pIdx[curIdx] = p11; + curIdx++; + + pIdx[curIdx] = p00; + curIdx++; + pIdx[curIdx] = p11; + curIdx++; + pIdx[curIdx] = p10; + curIdx++; + + offset += 2; + } + + AssertFatal( curIdx == mTriangleCount[Bottom] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Bottom]!" ); + + mPB[Bottom].unlock(); + + // Side Primitive Buffer + + mPB[Side].set( GFX, mTriangleCount[Side] * 3, mTriangleCount[Side], GFXBufferTypeStatic ); + + mPB[Side].lock(&pIdx); + curIdx = 0; + offset = 0; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + p00 = offset; + p10 = offset + 1; + pb00 = offset + 2; + pb10 = offset + 3; + p01 = offset + 4; + p11 = offset + 5; + pb01 = offset + 6; + pb11 = offset + 7; + + // Left Side + + pIdx[curIdx] = pb00; + curIdx++; + pIdx[curIdx] = pb01; + curIdx++; + pIdx[curIdx] = p01; + curIdx++; + + pIdx[curIdx] = pb00; + curIdx++; + pIdx[curIdx] = p01; + curIdx++; + pIdx[curIdx] = p00; + curIdx++; + + // Right Side + + pIdx[curIdx] = p10; + curIdx++; + pIdx[curIdx] = p11; + curIdx++; + pIdx[curIdx] = pb11; + curIdx++; + + pIdx[curIdx] = p10; + curIdx++; + pIdx[curIdx] = pb11; + curIdx++; + pIdx[curIdx] = pb10; + curIdx++; + + offset += 4; + } + + // Cap the front and back ends + pIdx[curIdx++] = 0; + pIdx[curIdx++] = 1; + pIdx[curIdx++] = 3; + pIdx[curIdx++] = 0; + pIdx[curIdx++] = 3; + pIdx[curIdx++] = 2; + + pIdx[curIdx++] = offset + 0; + pIdx[curIdx++] = offset + 3; + pIdx[curIdx++] = offset + 1; + pIdx[curIdx++] = offset + 0; + pIdx[curIdx++] = offset + 2; + pIdx[curIdx++] = offset + 3; + + + AssertFatal( curIdx == mTriangleCount[Side] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Side]!" ); + + mPB[Side].unlock(); +} + +const MeshRoadNode& MeshRoad::getNode( U32 idx ) +{ + return mNodes[idx]; +} + +VectorF MeshRoad::getNodeNormal( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return VectorF::Zero; + + return mNodes[idx].normal; +} + +void MeshRoad::setNodeNormal( U32 idx, const VectorF &normal ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].normal = normal; + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); +} + +Point3F MeshRoad::getNodePosition( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return Point3F::Zero; + + return mNodes[idx].point; +} + +void MeshRoad::setNodePosition( U32 idx, const Point3F &pos ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].point = pos; + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); +} + +U32 MeshRoad::addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal ) +{ + U32 idx = _addNode( pos, width, depth, normal ); + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); + + return idx; +} + +U32 MeshRoad::insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx ) +{ + U32 ret = _insertNode( pos, width, depth, normal, idx ); + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); + + return ret; +} + +void MeshRoad::setNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + MeshRoadNode &node = mNodes[idx]; + + node.point = pos; + node.width = width; + node.depth = depth; + node.normal = normal; + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); +} + +void MeshRoad::setNodeWidth( U32 idx, F32 meters ) +{ + meters = mClampF( meters, MIN_NODE_WIDTH, MAX_NODE_WIDTH ); + + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].width = meters; + _regenerate(); + + setMaskBits( RegenMask | NodeMask ); +} + +F32 MeshRoad::getNodeWidth( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return -1.0f; + + return mNodes[idx].width; +} + +void MeshRoad::setNodeDepth( U32 idx, F32 meters ) +{ + meters = mClampF( meters, MIN_NODE_DEPTH, MAX_NODE_DEPTH ); + + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].depth = meters; + + _regenerate(); + + setMaskBits( MeshRoadMask | RegenMask | NodeMask ); +} + +F32 MeshRoad::getNodeDepth( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return -1.0f; + + return mNodes[idx].depth; +} + +MatrixF MeshRoad::getNodeTransform( U32 idx ) +{ + MatrixF mat(true); + + if ( mNodes.size() - 1 < idx ) + return mat; + + bool hasNext = idx + 1 < mNodes.size(); + bool hasPrev = (S32)idx - 1 > 0; + + const MeshRoadNode &node = mNodes[idx]; + + VectorF fvec( 0, 1, 0 ); + + if ( hasNext ) + { + fvec = mNodes[idx+1].point - node.point; + fvec.normalizeSafe(); + } + else if ( hasPrev ) + { + fvec = node.point - mNodes[idx-1].point; + fvec.normalizeSafe(); + } + else + fvec = mPerp( node.normal ); + + if ( fvec.isZero() ) + fvec = mPerp( node.normal ); + + F32 dot = mDot( fvec, node.normal ); + if ( dot < -0.9f || dot > 0.9f ) + fvec = mPerp( node.normal ); + + VectorF rvec = mCross( fvec, node.normal ); + if ( rvec.isZero() ) + rvec = mPerp( fvec ); + rvec.normalize(); + + fvec = mCross( node.normal, rvec ); + fvec.normalize(); + + mat.setColumn( 0, rvec ); + mat.setColumn( 1, fvec ); + mat.setColumn( 2, node.normal ); + mat.setColumn( 3, node.point ); + + AssertFatal( m_matF_determinant( mat ) != 0.0f, "no inverse!"); + + return mat; +} + +void MeshRoad::calcSliceTransform( U32 idx, MatrixF &mat ) +{ + if ( mSlices.size() - 1 < idx ) + return; + + bool hasNext = idx + 1 < mSlices.size(); + bool hasPrev = (S32)idx - 1 >= 0; + + const MeshRoadSlice &slice = mSlices[idx]; + + VectorF fvec( 0, 1, 0 ); + + if ( hasNext ) + { + fvec = mSlices[idx+1].p1 - slice.p1; + fvec.normalizeSafe(); + } + else if ( hasPrev ) + { + fvec = slice.p1 - mSlices[idx-1].p1; + fvec.normalizeSafe(); + } + else + fvec = mPerp( slice.normal ); + + if ( fvec.isZero() ) + fvec = mPerp( slice.normal ); + + F32 dot = mDot( fvec, slice.normal ); + if ( dot < -0.9f || dot > 0.9f ) + fvec = mPerp( slice.normal ); + + VectorF rvec = mCross( fvec, slice.normal ); + if ( rvec.isZero() ) + rvec = mPerp( fvec ); + rvec.normalize(); + + fvec = mCross( slice.normal, rvec ); + fvec.normalize(); + + mat.setColumn( 0, rvec ); + mat.setColumn( 1, fvec ); + mat.setColumn( 2, slice.normal ); + mat.setColumn( 3, slice.p1 ); + + AssertFatal( m_matF_determinant( mat ) != 0.0f, "no inverse!"); +} + +F32 MeshRoad::getRoadLength() const +{ + F32 length = 0.0f; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + length += mSegments[i].length(); + } + + return length; +} + +void MeshRoad::deleteNode( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes.erase(idx); + _regenerate(); + + setMaskBits( RegenMask | NodeMask ); +} + +U32 MeshRoad::_addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal ) +{ + mNodes.increment(); + MeshRoadNode &node = mNodes.last(); + + node.point = pos; + node.width = width; + node.depth = depth; + node.normal = normal; + + setMaskBits( NodeMask | RegenMask ); + + return mNodes.size() - 1; +} + +U32 MeshRoad::_insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx ) +{ + U32 ret; + MeshRoadNode *node; + + if ( idx == U32_MAX ) + { + mNodes.increment(); + node = &mNodes.last(); + ret = mNodes.size() - 1; + } + else + { + mNodes.insert( idx ); + node = &mNodes[idx]; + ret = idx; + } + + node->point = pos; + node->depth = depth; + node->width = width; + node->normal = normal; + + return ret; +} + +void MeshRoad::setMetersPerSegment( F32 meters ) +{ + if ( meters < MIN_METERS_PER_SEGMENT ) + { + Con::warnf( "MeshRoad::setMetersPerSegment, specified meters (%g) is below the min meters (%g), NOT SET!", meters, MIN_METERS_PER_SEGMENT ); + return; + } + + //mMetersPerSegment = meters; + _regenerate(); + setMaskBits( MeshRoadMask | RegenMask ); +} + +bool MeshRoad::collideRay( const Point3F &origin, const Point3F &direction, U32 *nodeIdx, Point3F *collisionPnt ) +{ + Point3F p0 = origin; + Point3F p1 = origin + direction * 2000.0f; + + // If the line segment does not collide with the river's world box, + // it definitely does not collide with any part of the river. + if ( !getWorldBox().collideLine( p0, p1 ) ) + return false; + + if ( mSlices.size() < 2 ) + return false; + + MathUtils::Quad quad; + MathUtils::Ray ray; + F32 t; + + // Check each road segment (formed by a pair of slices) for collision + // with the line segment. + for ( U32 i = 0; i < mSlices.size() - 1; i++ ) + { + const MeshRoadSlice &slice0 = mSlices[i]; + const MeshRoadSlice &slice1 = mSlices[i+1]; + + // For simplicities sake we will only test for collision between the + // line segment and the Top face of the river segment. + + // Clockwise starting with the leftmost/closest point. + quad.p00 = slice0.p0; + quad.p01 = slice1.p0; + quad.p11 = slice1.p2; + quad.p10 = slice0.p2; + + ray.origin = origin; + ray.direction = direction; + + if ( MathUtils::mRayQuadCollide( quad, ray, NULL, &t ) ) + { + if ( nodeIdx ) + *nodeIdx = slice0.parentNodeIdx; + if ( collisionPnt ) + *collisionPnt = ray.origin + ray.direction * t; + return true; + } + } + + return false; +} + +void MeshRoad::regenerate() +{ + _regenerate(); + setMaskBits( RegenMask ); +} +//------------------------------------------------------------------------- +// Console Methods +//------------------------------------------------------------------------- + +ConsoleMethod( MeshRoad, regenerate, void, 2, 2, "setRegenFlag()" ) +{ + object->regenerate(); +} + +ConsoleMethod( MeshRoad, setMetersPerSegment, void, 3, 3, "setMetersPerSegment( F32 meters )" ) +{ + object->setMetersPerSegment( dAtoi(argv[2]) ); +} + +ConsoleMethod( MeshRoad, setNodeDepth, void, 4, 4, "setNodeDepth( U32 idx, F32 meters )" ) +{ + object->setNodeDepth( dAtoi(argv[2]), dAtof(argv[3]) ); +} + +ConsoleMethod( MeshRoad, postApply, void, 2, 2, "") +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/environment/meshRoad.h b/environment/meshRoad.h new file mode 100644 index 0000000..9f11d1c --- /dev/null +++ b/environment/meshRoad.h @@ -0,0 +1,547 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MESHROAD_H_ +#define _MESHROAD_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif +#ifndef _MATINSTANCE_H_ +#include "materials/matInstance.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif + + +//extern U32 gIdxArray[6][2][3]; + +struct MeshRoadHitSegment +{ + U32 idx; + F32 t; +}; + +class MeshRoad; + +//------------------------------------------------------------------------- +// MeshRoadConvex Class +//------------------------------------------------------------------------- + +class MeshRoadConvex : public Convex +{ + typedef Convex Parent; + friend class MeshRoad; + +protected: + + MeshRoad *pRoad; + +public: + + U32 faceId; + U32 triangleId; + U32 segmentId; + Point3F verts[4]; + PlaneF normal; + Box3F box; + +public: + + MeshRoadConvex() { mType = MeshRoadConvexType; } + + MeshRoadConvex( const MeshRoadConvex& cv ) { + mType = MeshRoadConvexType; + mObject = cv.mObject; + pRoad = cv.pRoad; + faceId = cv.faceId; + triangleId = cv.triangleId; + segmentId = cv.segmentId; + verts[0] = cv.verts[0]; + verts[1] = cv.verts[1]; + verts[2] = cv.verts[2]; + verts[3] = cv.verts[3]; + normal = cv.normal; + box = cv.box; + } + + const MatrixF& getTransform() const; + Box3F getBoundingBox() const; + Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const; + Point3F support(const VectorF& vec) const; + void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + void getPolyList(AbstractPolyList* list); +}; + + +//------------------------------------------------------------------------- +// MeshRoadSplineNode Class +//------------------------------------------------------------------------- + +class Path; +class TerrainBlock; +struct ObjectRenderInst; + +class MeshRoadSplineNode +{ +public: + MeshRoadSplineNode() {} + + F32 x; + F32 y; + F32 z; + F32 width; + F32 depth; + VectorF normal; + + MeshRoadSplineNode& operator=(const MeshRoadSplineNode&); + MeshRoadSplineNode operator+(const MeshRoadSplineNode&) const; + MeshRoadSplineNode operator-(const MeshRoadSplineNode&) const; + MeshRoadSplineNode operator*(const F32) const; + + F32 len(); + Point3F getPosition() const { return Point3F(x,y,z); }; +}; + +inline F32 MeshRoadSplineNode::len() +{ + return mSqrt(F32(x*x + y*y + z*z)); +} + +inline MeshRoadSplineNode& MeshRoadSplineNode::operator=(const MeshRoadSplineNode &_node) +{ + x = _node.x; + y = _node.y; + z = _node.z; + width = _node.width; + depth = _node.depth; + normal = _node.normal; + + return *this; +} + +inline MeshRoadSplineNode MeshRoadSplineNode::operator+(const MeshRoadSplineNode& _add) const +{ + MeshRoadSplineNode result; + result.x = x + _add.x; + result.y = y + _add.y; + result.z = z + _add.z; + result.width = width + _add.width; + result.depth = depth + _add.depth; + result.normal = normal + _add.normal; + + return result; +} + + +inline MeshRoadSplineNode MeshRoadSplineNode::operator-(const MeshRoadSplineNode& _rSub) const +{ + MeshRoadSplineNode result; + result.x = x - _rSub.x; + result.y = y - _rSub.y; + result.z = z - _rSub.z; + result.width = width - _rSub.width; + result.depth = depth - _rSub.depth; + result.normal = normal - _rSub.normal; + + return result; +} + +inline MeshRoadSplineNode operator*(const F32 mul, const MeshRoadSplineNode& multiplicand) +{ + return multiplicand * mul; +} + +inline MeshRoadSplineNode MeshRoadSplineNode::operator*(const F32 _mul) const +{ + MeshRoadSplineNode result; + result.x = x * _mul; + result.y = y * _mul; + result.z = z * _mul; + result.width = width * _mul; + result.depth = depth * _mul; + result.normal = normal * _mul; + + return result; +} + +//------------------------------------------------------------------------- +// Structures +//------------------------------------------------------------------------- + +struct MeshRoadRenderBatch +{ + U32 startSegmentIdx; + U32 endSegmentIdx; + U32 startVert; + U32 endVert; + U32 startIndex; + U32 endIndex; + U32 totalRows; + U32 indexCount; + + U32 vertCount; + U32 triangleCount; +}; + +typedef Vector MeshRoadBatchVector; + +struct MeshRoadNode +{ + // The 3D position of the node + Point3F point; + + // The width of the River at this node (meters) + F32 width; + + // The depth of the River at this node (meters) + F32 depth; + + VectorF normal; +}; + +typedef Vector MeshRoadNodeVector; + +struct MeshRoadSlice +{ + MeshRoadSlice() + { + p0.zero(); + p1.zero(); + p2.zero(); + pb0.zero(); + pb2.zero(); + + uvec.zero(); + fvec.zero(); + rvec.zero(); + + width = 0.0f; + depth = 0.0f; + normal.set(0,0,1); + texCoordV = 0.0f; + + parentNodeIdx = -1; + }; + + Point3F p0; // upper left + Point3F p1; // upper center + Point3F p2; // upper right + + Point3F pb0; // bottom left + Point3F pb2; // bottom right + + VectorF uvec; + VectorF fvec; + VectorF rvec; + + F32 width; + F32 depth; + Point3F normal; + + F32 t; + + F32 texCoordV; + + U32 parentNodeIdx; +}; +typedef Vector MeshRoadSliceVector; + + +//------------------------------------------------------------------------- +// MeshRoadSegment Class +//------------------------------------------------------------------------- + +class MeshRoadSegment +{ +public: + + MeshRoadSegment(); + MeshRoadSegment( MeshRoadSlice *rs0, MeshRoadSlice *rs1, const MatrixF &roadMat ); + + void set( MeshRoadSlice *rs0, MeshRoadSlice *rs1 ); + + F32 TexCoordStart() const { return slice0->texCoordV; } + F32 TexCoordEnd() const { return slice1->texCoordV; } + + const Point3F& getP00() const { return slice0->p0; } + const Point3F& getP01() const { return slice1->p0; } + const Point3F& getP11() const { return slice1->p2; } + const Point3F& getP10() const { return slice0->p2; } + + Point3F getSurfaceCenter() const { return ( slice0->p1 + slice1->p1 ) / 2.0f; } + Point3F getSurfaceNormal() const { return -mPlanes[4].getNormal(); } + + bool intersectBox( const Box3F &bounds ) const; + bool containsPoint( const Point3F &pnt ) const; + F32 distanceToSurface( const Point3F &pnt ) const; + F32 length() const { return ( slice1->p1 - slice0->p1 ).len(); } + + // Quick access to the segment's points + Point3F& operator[](U32); + const Point3F& operator[](U32) const; + Point3F& operator[](S32 i) { return operator[](U32(i)); } + const Point3F& operator[](S32 i ) const { return operator[](U32(i)); } + + const Box3F& getWorldBounds() const { return worldbounds; } + + MeshRoadSlice *slice0; + MeshRoadSlice *slice1; + +protected: + + PlaneF mPlanes[6]; + U32 mPlaneCount; + + U32 columns; + U32 rows; + + U32 startVert; + U32 endVert; + U32 startIndex; + U32 endIndex; + + U32 numVerts; + U32 numTriangles; + + Box3F objectbounds; + Box3F worldbounds; +}; + +typedef Vector MeshRoadSegmentVector; + + +inline Point3F& MeshRoadSegment::operator[](U32 index) +{ + AssertFatal(index < 8, "MeshRoadSegment::operator[] - out of bounds array access!"); + + MeshRoadSlice *slice = NULL; + if ( index > 3 ) + { + slice = slice1; + index -= 4; + } + else + { + slice = slice0; + } + + if ( index == 0 ) + return slice->p0; + + if ( index == 1 ) + return slice->p2; + + if ( index == 2 ) + return slice->pb0; + + else //( index == 3 ) + return slice->pb2; +} + +inline const Point3F& MeshRoadSegment::operator[](U32 index) const +{ + AssertFatal(index < 8, "MeshRoadSegment::operator[] - out of bounds array access!"); + + MeshRoadSlice *slice = NULL; + if ( index > 3 ) + { + slice = slice1; + index -= 4; + } + else + { + slice = slice0; + } + + if ( index == 0 ) + return slice->p0; + + if ( index == 1 ) + return slice->p2; + + if ( index == 2 ) + return slice->pb0; + + else// ( index == 3 ) + return slice->pb2; +} + + +//------------------------------------------------------------------------------ +// MeshRoad Class +//------------------------------------------------------------------------------ + +class PhysicsStatic; + +class MeshRoad : public SceneObject +{ +private: + + friend class GuiMeshRoadEditorCtrl; + friend class GuiMeshRoadEditorUndoAction; + friend class MeshRoadConvex; + + typedef SceneObject Parent; + + enum + { + MeshRoadMask = Parent::NextFreeMask, + NodeMask = Parent::NextFreeMask << 1, + RegenMask = Parent::NextFreeMask << 2, + InitialUpdateMask = Parent::NextFreeMask << 3, + SelectedMask = Parent::NextFreeMask << 4, + MaterialMask = Parent::NextFreeMask << 5, + NextFreeMask = Parent::NextFreeMask << 6, + }; + +public: + + MeshRoad(); + ~MeshRoad(); + + DECLARE_CONOBJECT(MeshRoad); + + // ConObject. + static void initPersistFields(); + static void consoleInit(); + + // SimObject + bool onAdd(); + void onRemove(); + void onEditorEnable(); + void onEditorDisable(); + void inspectPostApply(); + void onStaticModified(const char* slotName, const char*newValue = NULL); + void writeFields(Stream &stream, U32 tabStop); + bool writeField( StringTableEntry fieldname, const char *value ); + + // NetObject + U32 packUpdate(NetConnection *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + // SceneObject + virtual bool prepRenderImage(SceneState*, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState = false); + virtual void setTransform( const MatrixF &mat ); + virtual void setScale( const VectorF &scale ); + + // SceneObject - Collision + virtual void buildConvex(const Box3F& box,Convex* convex); + virtual bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + virtual bool collideBox(const Point3F &start, const Point3F &end, RayInfo* info); + + // MeshRoad + void regenerate(); + void setBatchSize( U32 level ); + void setTextureFile( StringTableEntry file ); + void setTextureRepeat( F32 meters ); + void setMetersPerSegment( F32 meters ); + + bool collideRay( const Point3F &origin, const Point3F &direction, U32 *nodeIdx = NULL, Point3F *collisionPnt = NULL ); + bool buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx, U32 endSegIdx, bool capFront, bool capEnd ); + + U32 insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const Point3F &normal, const U32 &idx ); + U32 addNode( const Point3F &pos, const F32 &width, const F32 &depth, const Point3F &normal ); + void deleteNode( U32 idx ); + + void setNode( const Point3F &pos, const F32 &width, const F32 &depth, const Point3F &normal, const U32 &idx ); + + const MeshRoadNode& getNode( U32 idx ); + VectorF getNodeNormal( U32 idx ); + void setNodeNormal( U32 idx, const VectorF &normal ); + Point3F getNodePosition( U32 idx ); + void setNodePosition( U32 idx, const Point3F &pos ); + F32 getNodeWidth( U32 idx ); + void setNodeWidth( U32 idx, F32 width ); + F32 getNodeDepth( U32 idx ); + void setNodeDepth( U32 idx, F32 depth ); + MatrixF getNodeTransform( U32 idx ); + void calcSliceTransform( U32 idx, MatrixF &mat ); + bool isEndNode( U32 idx ) { return ( mNodes.size() > 0 && ( idx == 0 || idx == mNodes.size() - 1 ) ); } + + U32 getSegmentCount() { return mSegments.size(); } + const MeshRoadSegment& getSegment( U32 idx ) { return mSegments[idx]; } + + F32 getRoadLength() const; + + static SimSet* getServerSet(); + + /// Protected 'Component' Field setter that will add a component to the list. + static bool addNodeFromField(void* obj, const char* data); + + static bool smEditorOpen; + static bool smWireframe; + static bool smShowBatches; + static bool smShowSpline; + static bool smShowRoad; + static SimObjectPtr smServerMeshRoadSet; + +protected: + + void _initMaterial(); + + void _debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ); + + U32 _insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const Point3F &normal, const U32 &idx ); + U32 _addNode( const Point3F &pos, const F32 &width, const F32 &depth, const Point3F &normal ); + + void _regenerate(); + void _generateSlices(); + void _generateSegments(); + void _generateVerts(); + +protected: + + MeshRoadSliceVector mSlices; + MeshRoadNodeVector mNodes; + MeshRoadSegmentVector mSegments; + MeshRoadBatchVector mBatches; + + static GFXStateBlockRef smWireframeSB; + + enum { + Top = 0, + Bottom = 1, + Side = 2, + SurfaceCount = 3 + }; + + GFXVertexBufferHandle mVB[SurfaceCount]; + GFXPrimitiveBufferHandle mPB[SurfaceCount]; + + String mMaterialName[SurfaceCount]; + SimObjectPtr mMaterial[SurfaceCount]; + BaseMatInstance *mMatInst[SurfaceCount]; + + U32 mVertCount[SurfaceCount]; + U32 mTriangleCount[SurfaceCount]; + + // Fields. + F32 mTextureLength; + F32 mBreakAngle; + S32 mWidthSubdivisions; + + // Collision and Physics. + Convex* mConvexList; + Vector mDebugConvex; + PhysicsStatic *mPhysicsRep; +}; + + +#endif // _MESHROAD_H_ \ No newline at end of file diff --git a/environment/river.cpp b/environment/river.cpp new file mode 100644 index 0000000..ce8ec11 --- /dev/null +++ b/environment/river.cpp @@ -0,0 +1,2162 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/river.h" + +#include "console/consoleTypes.h" +#include "util/catmullRom.h" +#include "math/util/quadTransforms.h" +#include "sceneGraph/simPath.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "materials/sceneData.h" +#include "sceneGraph/sgUtil.h" +#include "T3D/gameConnection.h" +#include "core/stream/bitStream.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxOcclusionQuery.h" +#include "math/mathIO.h" +#include "math/mathUtils.h" +#include "math/util/frustum.h" +#include "math/util/quadTransforms.h" +#include "util/triBoxCheck.h" +#include "util/triRayCheck.h" +#include "gui/3d/guiTSControl.h" +#include "gfx/sim/debugDraw.h" +#include "T3D/fx/particleEmitter.h" +#include "sceneGraph/reflectionManager.h" +#include "ts/tsShapeInstance.h" +#include "postFx/postEffect.h" +#include "math/util/matrixSet.h" + + +#define MIN_METERS_PER_SEGMENT 1.0f +#define MIN_NODE_DEPTH 0.25f +#define MAX_NODE_DEPTH 500.0f +#define MIN_NODE_WIDTH 0.25f +#define MAX_NODE_WIDTH 1000.0f +#define NODE_RADIUS 15.0f + +static U32 gIdxArray[6][2][3] = { + { { 0, 4, 5 }, { 0, 5, 1 }, }, // Top Face + { { 2, 6, 4 }, { 2, 4, 0 }, }, // Left Face + { { 1, 5, 7 }, { 1, 7, 3 }, }, // Right Face + { { 2, 3, 7 }, { 2, 7, 6 }, }, // Bottom Face + { { 0, 1, 3 }, { 0, 3, 2 }, }, // Front Face + { { 4, 6, 7 }, { 4, 7, 5 }, }, // Back Face +}; + +struct RiverHitSegment +{ + U32 idx; + F32 t; +}; + +static S32 QSORT_CALLBACK compareHitSegments(const void* a,const void* b) +{ + const RiverHitSegment *fa = (RiverHitSegment*)a; + const RiverHitSegment *fb = (RiverHitSegment*)b; + + return fb->t - fa->t; +} + +static Point3F sSegmentPointComparePoints[4]; + +//------------------------------------------------------------------------------ +// Class: RiverSegment +//------------------------------------------------------------------------------ + +RiverSegment::RiverSegment() +{ + mPlaneCount = 0; + columns = 0; + rows = 0; + numVerts = 0; + numTriangles = 0; + + startVert = 0; + endVert = 0; + startIndex = 0; + endIndex = 0; + + slice0 = NULL; + slice1 = NULL; +} + +RiverSegment::RiverSegment( RiverSlice *rs0, RiverSlice *rs1 ) +{ + columns = 0; + rows = 0; + numVerts = 0; + numTriangles = 0; + + startVert = 0; + endVert = 0; + startIndex = 0; + endIndex = 0; + + slice0 = rs0; + slice1 = rs1; + + // Calculate the planes for this segment + // Will be used for intersection/buoyancy tests + VectorF normal; + mPlaneCount = 6; + + sSegmentPointCompareReference = getFaceCenter(6); + + // left + mPlanes[0] = _getBestPlane( &slice1->p0, &slice1->pb0, &slice0->pb0, &slice0->p0 ); + + // right + mPlanes[1] = _getBestPlane( &slice0->pb2, &slice1->pb2, &slice1->p2, &slice0->p2 ); + + // near + mPlanes[2] = _getBestPlane( &slice0->pb0, &slice0->pb2, &slice0->p2, &slice0->p0 ); + + // far + mPlanes[3] = _getBestPlane( &slice1->pb2, &slice1->pb0, &slice1->p0, &slice1->p2 ); + + // top + mPlanes[4] = _getBestPlane( &slice0->p2, &slice1->p2, &slice1->p0, &slice0->p0 ); + + // bottom + mPlanes[5] = _getBestPlane( &slice0->pb2, &slice0->pb0, &slice1->pb0, &slice1->pb2 ); + + // Calculate the bounding box(s) + worldbounds.minExtents = worldbounds.maxExtents = rs0->p0; + worldbounds.extend( rs0->p2 ); + worldbounds.extend( rs0->pb0 ); + worldbounds.extend( rs0->pb2 ); + worldbounds.extend( rs1->p0 ); + worldbounds.extend( rs1->p2 ); + worldbounds.extend( rs1->pb0 ); + worldbounds.extend( rs1->pb2 ); + + /* + // Calculate tetrahedrons (for collision and buoyancy testing) + // This is 0 in the diagram. + mCubePoints[0] = cornerPoint; + mCubePoints[1] = cornerPoint + (VectorF( 1.0f, 0.0f, 0.0f ) * size ); + mCubePoints[2] = cornerPoint + (VectorF( 0.0f, 1.0f, 0.0f ) * size ); + mCubePoints[3] = cornerPoint + (VectorF( 1.0f, 1.0f, 0.0f ) * size ); + + mCubePoints[4] = cornerPoint + (VectorF( 0.0f, 0.0f, 1.0f ); + mCubePoints[5] = cornerPoint + (VectorF( 1.0f, 0.0f, 1.0f ); + mCubePoints[6] = cornerPoint + (VectorF( 0.0f, 1.0f, 1.0f ); + mCubePoints[7] = cornerPoint + (VectorF( 1.0f, 1.0f, 1.0f ); + + // Center tetra. + mTetras[0].p0 = &mCubePoints[1]; + mTetras[0].p1 = &mCubePoints[2]; + mTetras[0].p2 = &mCubePoints[4]; + mTetras[0].p3 = &mCubePoints[7]; + + + + mTetras[1].p0 = &mCubePoints[0]; // this is the tip + mTetras[1].p1 = &mCubePoints[1]; + mTetras[1].p2 = &mCubePoints[2]; + mTetras[1].p3 = &mCubePoints[4]; + + mTetras[2].p0 = &mCubePoints[3]; // tip + mTetras[2].p1 = &mCubePoints[2]; + mTetras[2].p2 = &mCubePoints[1]; + mTetras[2].p3 = &mCubePoints[7]; + + mTetras[3].p0 = &mCubePoints[6]; // tip + mTetras[3].p1 = &mCubePoints[7]; + mTetras[3].p2 = &mCubePoints[4]; + mTetras[3].p3 = &mCubePoints[2]; + + mTetras[4].p0 = &mCubePoints[5]; // tip + mTetras[4].p1 = &mCubePoints[7]; + mTetras[4].p2 = &mCubePoints[4]; + mTetras[4].p3 = &mCubePoints[3];*/ + +} + +void RiverSegment::set( RiverSlice *rs0, RiverSlice *rs1 ) +{ + columns = 0; + rows = 0; + numVerts = 0; + numTriangles = 0; + + startVert = 0; + endVert = 0; + startIndex = 0; + endIndex = 0; + + slice0 = rs0; + slice1 = rs1; +} + +static S32 QSORT_CALLBACK SegmentPointCompare(const void *aptr, const void *bptr) +{ + const U32 a = *(const U32*)aptr; + const U32 b = *(const U32*)bptr; + + F32 lenA = ( sSegmentPointCompareReference - sSegmentPointComparePoints[a] ).lenSquared(); + F32 lenB = ( sSegmentPointCompareReference - sSegmentPointComparePoints[b] ).lenSquared(); + return ( lenB - lenA ); +} + +PlaneF RiverSegment::_getBestPlane( const Point3F *p0, const Point3F *p1, const Point3F *p2, const Point3F *p3 ) +{ + sSegmentPointComparePoints[0] = *p0; + sSegmentPointComparePoints[1] = *p1; + sSegmentPointComparePoints[2] = *p2; + sSegmentPointComparePoints[3] = *p3; + + Point3F points[4] = { + *p0, *p1, *p2, *p3 + }; + + U32 indices[4] = { + 0,1,2,3 + }; + + dQsort(indices, 4, sizeof(U32), SegmentPointCompare); + + // Collect the best three points (in correct winding order) + // To generate the plane's normal + Vector normalPnts; + + for ( U32 i = 0; i < 4; i++ ) + { + if ( i == indices[3] ) + continue; + + normalPnts.push_back(points[i]); + } + + PlaneF plane( normalPnts[0], normalPnts[1], normalPnts[2] ); + return plane; +} + +Point3F RiverSegment::getFaceCenter( U32 faceIdx ) const +{ + Point3F center(0,0,0); + + switch ( faceIdx ) + { + case 0: // left + center = slice1->p0 + slice0->p0 + slice0->pb0 + slice1->pb0; + center *= 0.25f; + break; + + case 1: // right + center = slice0->p2 + slice1->p2 + slice1->pb2 + slice0->pb2; + center *= 0.25f; + break; + + case 2: // near + center = slice0->p0 + slice0->p2 + slice0->pb2 + slice0->pb0; + center *= 0.25f; + break; + + case 3: // far + center = slice1->pb0 + slice1->p0 + slice1->pb0 + slice1->pb2; + center *= 0.25f; + break; + + case 4: // top + center = slice0->p0 + slice1->p0 + slice1->p2 + slice0->p2; + center *= 0.25f; + break; + + case 5: // bottom + center = slice1->pb2 + slice1->pb0 + slice0->pb0 + slice0->pb2; + center *= 0.25f; + break; + + case 6: // segment center + center = slice0->p0 + slice0->p2 + slice1->p0 + slice1->p2 + slice0->pb0 + slice0->pb2 + slice1->pb0 + slice1->pb2; + center /= 8; + break; + } + + return center; +} + +bool RiverSegment::intersectBox( const Box3F &bounds ) const +{ + // This code copied from Frustum class. + + Point3F maxPoint; + F32 maxDot; + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + for ( S32 i = 0; i < mPlaneCount; i++ ) + { + // This is pretty much as optimal as you can + // get for a plane vs AABB test... + // + // 4 comparisons + // 3 multiplies + // 2 adds + // 1 negation + // + // It will early out as soon as it detects the + // bounds is outside one of the planes. + + if ( mPlanes[i].x > 0 ) + maxPoint.x = bounds.maxExtents.x; + else + maxPoint.x = bounds.minExtents.x; + + if ( mPlanes[i].y > 0 ) + maxPoint.y = bounds.maxExtents.y; + else + maxPoint.y = bounds.minExtents.y; + + if ( mPlanes[i].z > 0 ) + maxPoint.z = bounds.maxExtents.z; + else + maxPoint.z = bounds.minExtents.z; + + maxDot = mDot( maxPoint, mPlanes[ i ] ); + + if ( maxDot <= -mPlanes[ i ].d ) + return false; + } + + return true; +} + +bool RiverSegment::containsPoint( const Point3F &pnt ) const +{ + // NOTE: this code from Frustum class. + + F32 maxDot; + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + for ( S32 i = 0; i < mPlaneCount; i++ ) + { + const PlaneF &plane = mPlanes[ i ]; + + // This is pretty much as optimal as you can + // get for a plane vs point test... + // + // 1 comparison + // 2 multiplies + // 1 adds + // + // It will early out as soon as it detects the + // point is outside one of the planes. + + maxDot = mDot( pnt, plane ) + plane.d; + if ( maxDot < -0.1f ) + return false; + } + + return true; +} + +F32 RiverSegment::distanceToSurface(const Point3F &pnt) const +{ + return mPlanes[4].distToPlane( pnt ); +} + +bool River::smEditorOpen = false; +bool River::smWireframe = false; +bool River::smShowWalls = false; +bool River::smShowNodes = false; +bool River::smShowSpline = true; +bool River::smShowRiver = true; +SimObjectPtr River::smServerRiverSet = NULL; + +IMPLEMENT_CO_NETOBJECT_V1(River); + + +River::River() + : mMetersPerSegment(10.0f), + mSegmentsPerBatch(10), + mDepthScale(1.0f), + mMaxDivisionSize(2.5f), + mMinDivisionSize(0.25f), + mColumnCount(5), + mFlowMagnitude(1.0f), + mLodDistance( 50.0f ) +{ + mNetFlags.set( Ghostable | ScopeAlways ); + + mObjScale.set( 1, 1, 1 ); + + mObjBox.minExtents.set( -0.5, -0.5, -0.5 ); + mObjBox.maxExtents.set( 0.5, 0.5, 0.5 ); + + mReflectNormalUp = false; + + // We use the shader const miscParams.w to signify + // that this object is a River. + mMiscParamW = 1.0f; +} + +River::~River() +{ +} + +void River::initPersistFields() +{ + addGroup( "River" ); + addField( "SegmentLength", TypeF32, Offset( mMetersPerSegment, River ) ); + addField( "SubdivideLength", TypeF32, Offset( mMaxDivisionSize, River ) ); + addField( "FlowMagnitude", TypeF32, Offset( mFlowMagnitude, River ) ); + addField( "LowLODDistance", TypeF32, Offset( mLodDistance, River ) ); + endGroup( "River" ); + + addGroup( "Internal" ); + addProtectedField( "Node", TypeString, NULL, &addNodeFromField, &emptyStringProtectedGetFn, "For internal use, do not modify." ); + endGroup( "Internal" ); + + Parent::initPersistFields(); +} + +void River::consoleInit() +{ + Parent::consoleInit(); + + Con::addVariable( "$River::EditorOpen", TypeBool, &River::smEditorOpen ); + Con::addVariable( "$River::showWalls", TypeBool, &River::smShowWalls ); + Con::addVariable( "$River::showNodes", TypeBool, &River::smShowNodes ); + Con::addVariable( "$River::showSpline", TypeBool, &River::smShowSpline ); + Con::addVariable( "$River::showRiver", TypeBool, &River::smShowRiver ); + Con::addVariable( "$River::showWireframe", TypeBool, &River::smWireframe ); +} + +bool River::addNodeFromField( void* obj, const char* data ) +{ + River *pObj = static_cast(obj); + + //if ( !pObj->isProperlyAdded() ) + //{ + F32 x,y,z,width,depth; + VectorF normal; + U32 result = dSscanf( data, "%f %f %f %f %f %f %f %f", &x, &y, &z, &width, &depth, &normal.x, &normal.y, &normal.z ); + if ( result == 8 ) + pObj->_addNode( Point3F(x,y,z), width, depth, normal ); + //} + + return false; +} + +bool River::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Reset the World Box. + //setGlobalBounds(); + resetWorldBox(); + + // Set the Render Transform. + setRenderTransform(mObjToWorld); + + // Add to Scene. + addToScene(); + + if ( isServerObject() ) + getServerSet()->addObject( this ); + + _regenerate(); + + return true; +} + +void River::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +void River::inspectPostApply() +{ + // Set Parent. + Parent::inspectPostApply(); + + if ( mMetersPerSegment < MIN_METERS_PER_SEGMENT ) + mMetersPerSegment = MIN_METERS_PER_SEGMENT; + + mMaxDivisionSize = getMax( mMaxDivisionSize, mMinDivisionSize ); + + // Set fxPortal Mask. + setMaskBits(RiverMask|RegenMask); +} + +void River::onStaticModified( const char* slotName, const char*newValue ) +{ + Parent::onStaticModified( slotName, newValue ); + + if ( dStricmp( slotName, "surfMaterial" ) == 0 ) + setMaskBits( MaterialMask ); +} + +SimSet* River::getServerSet() +{ + if ( !smServerRiverSet ) + { + smServerRiverSet = new SimSet(); + smServerRiverSet->registerObject( "ServerRiverSet" ); + Sim::getRootGroup()->addObject( smServerRiverSet ); + } + + return smServerRiverSet; +} + +void River::writeFields( Stream &stream, U32 tabStop ) +{ + Parent::writeFields( stream, tabStop ); + + // Now write all nodes + + stream.write(2, "\r\n"); + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + const RiverNode &node = mNodes[i]; + + stream.writeTabs(tabStop); + + char buffer[1024]; + dMemset( buffer, 0, 1024 ); + dSprintf( buffer, 1024, "Node = \"%f %f %f %f %f %f %f %f\";", node.point.x, node.point.y, node.point.z, + node.width, + node.depth, + node.normal.x, node.normal.y, node.normal.z ); + stream.writeLine( (const U8*)buffer ); + } +} + +bool River::writeField( StringTableEntry fieldname, const char *value ) +{ + if ( fieldname == StringTable->insert("node") ) + return false; + + return Parent::writeField( fieldname, value ); +} + +void River::innerRender( SceneState *state ) +{ + GFXDEBUGEVENT_SCOPE( River_innerRender, ColorI( 255, 0, 0 ) ); + + // Setup SceneGraphData + SceneGraphData sgData = setupSceneGraphInfo( state ); + const Point3F &camPosition = state->getCameraPosition(); + + // set the material + + S32 matIdx = getMaterialIndex( camPosition ); + + if ( !initMaterial( matIdx ) ) + return; + + BaseMatInstance *mat = mMatInstances[matIdx]; + WaterMatParams matParams = mMatParamHandles[matIdx]; + + if ( !mat ) + return; + + // setup proj/world transform + GFXTransformSaver saver; + + setShaderParams( state, mat, matParams ); + + _makeRenderBatches( camPosition ); + + if ( !River::smShowRiver ) + return; + + // If no material... we're done. + if ( mLowLODBatches.empty() && mHighLODBatches.empty() ) + return; + + if ( !mHighLODBatches.empty() ) + _makeHighLODBuffers(); + + mMatrixSet->restoreSceneViewProjection(); + mMatrixSet->setWorld( MatrixF::Identity ); + + while( mat->setupPass( state, sgData ) ) + { + mat->setSceneInfo(state, sgData); + mat->setTransforms(*mMatrixSet, state); + + setCustomTextures( matIdx, mat->getCurPass(), matParams ); + + GFX->setVertexBuffer( mVB_low ); + GFX->setPrimitiveBuffer( mPB_low ); + + for ( U32 i = 0; i < mLowLODBatches.size(); i++ ) + { + const RiverRenderBatch &batch = mLowLODBatches[i]; + + U32 startVert = batch.startSegmentIdx * 2; + U32 endVert = ( batch.endSegmentIdx + 1 ) * 2 + 1; + U32 startIdx = batch.startSegmentIdx * 6; + U32 endIdx = batch.endSegmentIdx * 6 + 5; + + U32 vertCount = ( endVert - startVert ) + 1; + U32 idxCount = ( endIdx - startIdx ) + 1; + U32 triangleCount = idxCount / 3; + + AssertFatal( startVert < mLowVertCount, "River, bad draw call!" ); + AssertFatal( startVert + vertCount <= mLowVertCount, "River, bad draw call!" ); + AssertFatal( triangleCount <= mLowTriangleCount, "River, bad draw call!" ); + + GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, vertCount, batch.startSegmentIdx * 6, triangleCount ); + } + + // Render all high detail batches. + // + // It is possible that the buffers could not be allocated because + // the max number of verts/indices was exceeded. We don't want to + // crash because that would be unhelpful for working in the editor. + if ( mVB_high.isValid() && mVB_low.isValid() ) + { + GFX->setVertexBuffer( mVB_high ); + GFX->setPrimitiveBuffer( mPB_high ); + + for ( U32 i = 0; i < mHighLODBatches.size(); i++ ) + { + const RiverRenderBatch &batch = mHighLODBatches[i]; + + AssertFatal( batch.startVert < mHighVertCount, "River, bad draw call!" ); + AssertFatal( batch.startVert + batch.vertCount <= mHighVertCount, "River, bad draw call!" ); + AssertFatal( batch.triangleCount <= mHighTriangleCount, "River, bad draw call!" ); + AssertFatal( batch.startIndex < mHighTriangleCount * 3, "River, bad draw call!" ); + AssertFatal( batch.startIndex + batch.triangleCount * 3 <= mHighTriangleCount * 3, "River, bad draw call!" ); + + GFX->drawIndexedPrimitive( GFXTriangleList, + 0, + 0, + batch.vertCount, + batch.startIndex, + batch.triangleCount ); + } + } + + } // while( mat->setupPass( sgData ) ) +} + +SceneGraphData River::setupSceneGraphInfo( SceneState *state ) +{ + SceneGraphData sgData; + + LightManager* lm = gClientSceneGraph->getLightManager(); + sgData.lights[0] = lm->getSpecialLight( LightManager::slSunLightType ); + + // The river is in world space already... no transform! + sgData.objTrans.identity(); + + // fog + sgData.setFogParams( gClientSceneGraph->getFogData() ); + + // misc + sgData.backBuffTex = REFLECTMGR->getRefractTex(); + sgData.reflectTex = mPlaneReflector.reflectTex; + sgData.wireframe = GFXDevice::getWireframe() || smWireframe; + + return sgData; +} + +void River::updateUnderwaterEffect( SceneState *state ) +{ + // Calculate mWaterPlane before calling updateUnderwaterEffect. + Point3F dummy; + _getWaterPlane( state->getCameraPosition(), mWaterFogData.plane, dummy ); + + Parent::updateUnderwaterEffect( state ); +} + +void River::setShaderParams( SceneState *state, BaseMatInstance* mat, const WaterMatParams& paramHandles ) +{ + // Set variables that will be assigned to shader consts within WaterCommon + // before calling Parent::setShaderParams + + mUndulateMaxDist = mLodDistance; + + Parent::setShaderParams( state, mat, paramHandles ); + + // Now set the rest of the shader consts that are either unique to this + // class or that WaterObject leaves to us to handle... + + MaterialParameters* matParams = mat->getMaterialParameters(); + + // set vertex shader constants + //----------------------------------- + + matParams->set(paramHandles.mGridElementSizeSC, 1.0f); + matParams->set(paramHandles.mModelMatSC, MatrixF::Identity, GFXSCT_Float3x3); + + // set pixel shader constants + //----------------------------------- + + ColorF c( mWaterFogData.color ); + matParams->set(paramHandles.mBaseColorSC, c); + + //Point4F reflectParams( mReflectPlaneWorldPosition.z, mReflectMinDist, mReflectMaxDist, mFullReflect ? 0.0f : 1.0f ); + Point4F reflectParams( mWaterPos.z, 0.0f, 1000.0f, mPlaneReflector.isEnabled() ? 0.0f : 1.0f ); + matParams->set(paramHandles.mReflectParamsSC, reflectParams ); + + matParams->set(paramHandles.mReflectNormalSC, mPlaneReflector.refplane ); +} + +bool River::isUnderwater( const Point3F &pnt ) +{ + return containsPoint( pnt ); +} + +U32 River::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + // Pack Parent. + U32 retMask = Parent::packUpdate(con, mask, stream); + + if ( stream->writeFlag( mask & NodeMask ) ) + { + stream->writeInt( mNodes.size(), 16 ); + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + mathWrite( *stream, mNodes[i].point ); + stream->write( mNodes[i].width ); + stream->write( mNodes[i].depth ); + mathWrite( *stream, mNodes[i].normal ); + } + } + + if ( stream->writeFlag( mask & RiverMask ) ) + { + // Write Object Transform. + stream->writeAffineTransform(mObjToWorld); + + stream->write( mMetersPerSegment ); + stream->write( mSegmentsPerBatch ); + stream->write( mDepthScale ); + stream->write( mMaxDivisionSize ); + stream->write( mColumnCount ); + + stream->write( mFlowMagnitude ); + stream->write( mLodDistance ); + } + + stream->writeFlag( mask & RegenMask ); + + if( stream->writeFlag( mask & ( RiverMask | InitialUpdateMask ) ) ) + { + // This is set to allow the user to modify the size of the water dynamically + // in the editor + mathWrite( *stream, mObjScale ); + stream->writeAffineTransform( mObjToWorld ); + } + + return retMask; +} + +void River::unpackUpdate(NetConnection * con, BitStream * stream) +{ + // Unpack Parent. + Parent::unpackUpdate(con, stream); + + // NodeMask + if ( stream->readFlag() ) + { + U32 count = stream->readInt( 16 ); + + mNodes.clear(); + + Point3F pos; + VectorF normal; + F32 width,depth; + + for ( U32 i = 0; i < count; i++ ) + { + mathRead( *stream, &pos ); + stream->read( &width ); + stream->read( &depth ); + mathRead( *stream, &normal ); + _addNode( pos, width, depth, normal ); + } + } + + // RiverMask + if(stream->readFlag()) + { + MatrixF ObjectMatrix; + stream->readAffineTransform(&ObjectMatrix); + Parent::setTransform(ObjectMatrix); + + stream->read( &mMetersPerSegment ); + stream->read( &mSegmentsPerBatch ); + stream->read( &mDepthScale ); + stream->read( &mMaxDivisionSize ); + stream->read( &mColumnCount ); + + stream->read( &mFlowMagnitude ); + stream->read( &mLodDistance ); + } + + // RegenMask + if ( stream->readFlag() && isProperlyAdded() ) + regenerate(); + + // RiverMask | InitialUpdateMask + if( stream->readFlag() ) + { + mathRead( *stream, &mObjScale ); + stream->readAffineTransform( &mObjToWorld ); + } +} + +void River::_getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos ) +{ + // Find the RiverSegment closest to the camera. + F32 closestDist = F32_MAX; + S32 closestSegment = 0; + Point3F projPnt(0.0f, 0.0f, 0.0f); + + VectorF normal(0,0,0); + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + + const Point3F pos = MathUtils::mClosestPointOnSegment( segment.slice0->p1, segment.slice1->p1, camPos ); + + F32 dist = ( camPos - pos ).len(); + + if ( dist < closestDist ) + { + closestDist = dist; + closestSegment = i; + projPnt = pos; + } + + normal += segment.getSurfaceNormal(); + } + + if ( mReflectNormalUp ) + normal.set(0,0,1); + else + normal.normalizeSafe(); + + outPos = projPnt; + outPlane.set( projPnt, normal ); +} + +void River::setTransform( const MatrixF &mat ) +{ + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + mWorldToObj.mulP( mNodes[i].point ); + mat.mulP( mNodes[i].point ); + } + + /* + // Get the amount of change in position. + MatrixF oldMat = getTransform(); + Point3F oldPos = oldMat.getPosition(); + Point3F newPos = mat.getPosition(); + Point3F delta = newPos - oldPos; + + // Offset all nodes by that amount + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + mNodes[i].point += delta; + } + + // Assign the new position ( we ignore rotation ) + MatrixF newMat( oldMat ); + newMat.setPosition( newPos ); + */ + + Parent::setTransform( mat ); + + // Regenerate and update the client + _regenerate(); + setMaskBits( NodeMask | RegenMask ); +} + +void River::setScale( const VectorF &scale ) +{ + // We ignore scale requests from the editor + // right now. +} + +bool River::castRay(const Point3F &s, const Point3F &e, RayInfo* info) +{ + Point3F start = s; + Point3F end = e; + mObjToWorld.mulP(start); + mObjToWorld.mulP(end); + + F32 out = 1.0f; // The output fraction/percentage along the line defined by s and e + VectorF norm(0.0f, 0.0f, 0.0f); // The normal of the face intersected + + Vector hitSegments; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + + F32 t; + VectorF n; + + if ( segment.worldbounds.collideLine( start, end, &t, &n ) ) + { + hitSegments.increment(); + hitSegments.last().t = t; + hitSegments.last().idx = i; + } + } + + dQsort( hitSegments.address(), hitSegments.size(), sizeof(RiverHitSegment), compareHitSegments ); + + U32 idx0, idx1, idx2; + F32 t; + + for ( U32 i = 0; i < hitSegments.size(); i++ ) + { + U32 segIdx = hitSegments[i].idx; + const RiverSegment &segment = mSegments[segIdx]; + + // Each segment has 6 faces + for ( U32 j = 0; j < 6; j++ ) + { + if ( j == 4 && segIdx != 0 ) + continue; + + if ( j == 5 && segIdx != mSegments.size() - 1 ) + continue; + + // Each face has 2 triangles + for ( U32 k = 0; k < 2; k++ ) + { + idx0 = gIdxArray[j][k][0]; + idx1 = gIdxArray[j][k][1]; + idx2 = gIdxArray[j][k][2]; + + const Point3F &v0 = segment[idx0]; + const Point3F &v1 = segment[idx1]; + const Point3F &v2 = segment[idx2]; + + if ( !MathUtils::mLineTriangleCollide( start, end, + v2, v1, v0, + NULL, + &t ) ) + continue; + + if ( t >= 0.0f && t < 1.0f && t < out ) + { + out = t; + + // optimize this, can be calculated easily within + // the collision test + norm = PlaneF( v0, v1, v2 ); + } + } + } + + if (out >= 0.0f && out < 1.0f) + break; + } + + if (out >= 0.0f && out < 1.0f) + { + info->t = out; + info->normal = norm; + info->point.interpolate(start, end, out); + info->face = -1; + info->object = this; + + return true; + } + + return false; +} + +bool River::collideBox(const Point3F &start, const Point3F &end, RayInfo* info) +{ + return false; +} + +F32 River::getWaterCoverage( const Box3F &worldBox ) const +{ + PROFILE_SCOPE( River_GetWaterCoverage ); + + if ( !mWorldBox.isOverlapped(worldBox) ) + return 0.0f; + + Point3F bottomPnt = worldBox.getCenter(); + bottomPnt.z = worldBox.minExtents.z; + + F32 farthest = 0.0f; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + + if ( !segment.worldbounds.isOverlapped(worldBox) ) + continue; + + if ( !segment.intersectBox( worldBox ) ) + continue; + + F32 distance = segment.distanceToSurface( bottomPnt ); + + if ( distance > farthest ) + farthest = distance; + } + + F32 height = worldBox.maxExtents.z - worldBox.minExtents.z; + F32 distance = mClampF( farthest, 0.0f, height ); + F32 coverage = distance / height; + + return coverage; +} + +F32 River::getSurfaceHeight( const Point2F &pos ) const +{ + PROFILE_SCOPE( River_GetSurfaceHeight ); + + Point3F origin( pos.x, pos.y, mWorldBox.maxExtents.z ); + Point3F direction(0,0,-1); + U32 nodeIdx; + Point3F collisionPnt; + + if ( !collideRay( origin, direction, &nodeIdx, &collisionPnt ) ) + return -1.0f; + + return collisionPnt.z; +} + +VectorF River::getFlow( const Point3F &pos ) const +{ + PROFILE_SCOPE( River_GetFlow ); + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + + if ( !segment.containsPoint(pos) ) + continue; + + VectorF flow = segment.slice0->p1 - segment.slice1->p1; + flow.normalize(); + flow *= mFlowMagnitude; + + return flow; + } + + return VectorF::Zero; +} + +void River::onReflectionInfoChanged() +{ + /* + if ( isClientObject() && GFX->getPixelShaderVersion() >= 1.4 ) + { + if ( mFullReflect ) + REFLECTMGR->registerObject( this, ReflectDelegate( this, &River::updateReflection ), mReflectPriority, mReflectMaxRateMs, mReflectMaxDist ); + else + { + REFLECTMGR->unregisterObject( this ); + mReflectTex = NULL; + } + } + */ +} + +void River::_regenerate() +{ + if ( mNodes.size() == 0 ) + return; + + const Point3F &nodePt = mNodes.first().point; + + MatrixF mat( true ); + mat.setPosition( nodePt ); + Parent::setTransform( mat ); + + _generateSlices(); +} + +void River::_generateSlices() +{ + if ( mNodes.size() < 2 ) + return; + + U32 nodeCount = mNodes.size(); + RiverSplineNode *splineNodes = new RiverSplineNode[nodeCount]; + + for ( U32 i = 0; i < nodeCount; i++ ) + { + const RiverNode &node = mNodes[i]; + splineNodes[i].x = node.point.x; + splineNodes[i].y = node.point.y; + splineNodes[i].z = node.point.z; + splineNodes[i].width = node.width; + splineNodes[i].depth = node.depth; + splineNodes[i].normal = node.normal; + } + + CatmullRom spline; + spline.initialize( nodeCount, splineNodes ); + delete [] splineNodes; + + mSlices.clear(); + + for ( U32 i = 1; i < nodeCount; i++ ) + { + F32 t0 = spline.getTime( i-1 ); + F32 t1 = spline.getTime( i ); + + F32 segLength = spline.arcLength( t0, t1 ); + + U32 numSegments = mCeil( segLength / mMetersPerSegment ); + numSegments = getMax( numSegments, (U32)1 ); + F32 tstep = ( t1 - t0 ) / numSegments; + + //AssertFatal( numSegments > 0, "River::_generateSlices, got zero segments!" ); + + U32 startIdx = 0; + U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; + + for ( U32 j = startIdx; j < endIdx; j++ ) + { + F32 t = t0 + tstep * j; //spline.findParameterByDistance( 0.0f, i * segLen ); + RiverSplineNode val = spline.evaluate(t); + + RiverSlice slice; + slice.p1.set( val.x, val.y, val.z ); + slice.uvec.set( 0,0,1 ); + slice.width = val.width; + slice.depth = val.depth; + slice.parentNodeIdx = i-1; + slice.normal = val.normal; + slice.normal.normalize(); + mSlices.push_back( slice ); + } + } + + // + // Calculate fvec and rvec for all slices + // + RiverSlice *pSlice = NULL; + RiverSlice *pNextSlice = NULL; + + // Must do the first slice outside the loop + { + pSlice = &mSlices[0]; + pNextSlice = &mSlices[1]; + pSlice->fvec = pNextSlice->p1 - pSlice->p1; + pSlice->fvec.normalize(); + pSlice->rvec = mCross( pSlice->fvec, pSlice->normal ); + pSlice->rvec.normalize(); + pSlice->uvec = mCross( pSlice->rvec, pSlice->fvec ); + pSlice->uvec.normalize(); + pSlice->rvec = mCross( pSlice->fvec, pSlice->uvec ); + pSlice->rvec.normalize(); + } + + for ( U32 i = 1; i < mSlices.size() - 1; i++ ) + { + pSlice = &mSlices[i]; + pNextSlice = &mSlices[i+1]; + + pSlice->fvec = pNextSlice->p1 - pSlice->p1; + pSlice->fvec.normalize(); + + pSlice->rvec = mCross( pSlice->fvec, pSlice->normal ); + pSlice->rvec.normalize(); + + pSlice->uvec = mCross( pSlice->rvec, pSlice->fvec ); + pSlice->uvec.normalize(); + + pSlice->rvec = mCross( pSlice->fvec, pSlice->uvec ); + pSlice->rvec.normalize(); + } + + // Must do the last slice outside the loop + { + RiverSlice *lastSlice = &mSlices[mSlices.size()-1]; + RiverSlice *prevSlice = &mSlices[mSlices.size()-2]; + + lastSlice->fvec = prevSlice->fvec; + + lastSlice->rvec = mCross( lastSlice->fvec, lastSlice->normal ); + lastSlice->rvec.normalize(); + + lastSlice->uvec = mCross( lastSlice->rvec, lastSlice->fvec ); + lastSlice->uvec.normalize(); + + lastSlice->rvec = mCross( lastSlice->fvec, lastSlice->uvec ); + lastSlice->rvec.normalize(); + } + + + // + // Calculate p0/p2/pb0/pb2 for all slices + // + for ( U32 i = 0; i < mSlices.size(); i++ ) + { + RiverSlice *slice = &mSlices[i]; + slice->p0 = slice->p1 - slice->rvec * slice->width * 0.5f; + slice->p2 = slice->p1 + slice->rvec * slice->width * 0.5f; + slice->pb0 = slice->p0 - slice->uvec * slice->depth; + slice->pb2 = slice->p2 - slice->uvec * slice->depth; + } + + // Generate the object/world bounds + Box3F box; + for ( U32 i = 0; i < mSlices.size(); i++ ) + { + const RiverSlice &slice = mSlices[i]; + + if ( i == 0 ) + { + box.minExtents = slice.p0; + box.maxExtents = slice.p2; + box.extend( slice.pb0 ); + box.extend( slice.pb2 ); + } + else + { + box.extend( slice.p0 ); + box.extend( slice.p2 ); + box.extend( slice.pb0 ); + box.extend( slice.pb2 ); + } + } + + Point3F pos = getPosition(); + + mWorldBox = box; + //mObjBox.minExtents -= pos; + //mObjBox.maxExtents -= pos; + resetObjectBox(); + + _generateSegments(); +} + +void River::_generateSegments() +{ + mSegments.clear(); + + for ( U32 i = 0; i < mSlices.size() - 1; i++ ) + { + RiverSegment seg( &mSlices[i], &mSlices[i+1] ); + + mSegments.push_back( seg ); + } + + /* + #ifdef TORQUE_DEBUG + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + PlaneF normal0 = MathUtils::mTriangleNormal( segment.slice0->p0, segment.slice1->p0, segment.slice1->p2 ); + PlaneF normal1 = MathUtils::mTriangleNormal( segment.slice0->p0, segment.slice1->p2, segment.slice0->p2 ); + AssertFatal( true || normal0 != normal1, "River::generateSegments, segment is not coplanar!" ); + } + + #endif // TORQUE_DEBUG + */ + + // We have to go back and generate normals for each slice + // to be used in calculation of the reflect plane. + // The slice-normal we calculate are relative to the surface normal + // of the segments adjacent to the slice. + /* + if ( mSlices.size() >= 2 ) + { + mSlices[0].normal = mSegments[0].getSurfaceNormal(); + for ( U32 i = 1; i < mSlices.size() - 1; i++ ) + { + mSlices[i].normal = ( mSegments[i-1].getSurfaceNormal() + mSegments[i].getSurfaceNormal() ) / 2; + } + mSlices.last().normal = mSegments.last().getSurfaceNormal(); + } + */ + + _generateVerts(); +} + +void River::_generateVerts() +{ + // These will depend on the level of subdivision per segment + // calculated below. + mHighVertCount = 0; + mHighTriangleCount = 0; + + // Calculate the number of row/column subdivisions per each + // RiverSegment. + + F32 greatestWidth = 0.1f; + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + RiverNode &node = mNodes[i]; + if ( node.width > greatestWidth ) + greatestWidth = node.width; + } + + mColumnCount = mCeil( greatestWidth / mMaxDivisionSize ); + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + RiverSegment &segment = mSegments[i]; + const RiverSlice *slice = segment.slice0; + const RiverSlice *nextSlice = segment.slice1; + + // Calculate the size of divisions in the forward direction ( p00 -> p01 ) + F32 segLength = (nextSlice->p1 - slice->p1).len(); + + // A division count of one is actually NO subdivision, + // the segment corners are the only verts in this segment. + U32 numRows = 1; + F32 rowSize = segLength / numRows; + + while ( rowSize > mMaxDivisionSize ) + { + numRows++; + rowSize = segLength / numRows; + } + + // The problem with calculating num columns per segment is + // two adjacent - high lod segments of different width can have + // verts that don't line up! So even though RiverSegment HAS a + // column data member we initialize all segments in the river to + // the same (River::mColumnCount) + + // Calculate the size of divisions in the right direction ( p00 -> p10 ) + // F32 segWidth = ( ( p11 - p01 ).len() + ( p10 - p00 ).len() ) * 0.5f; + + // U32 numColumns = 5; + //F32 columnSize = segWidth / numColumns; + + //while ( columnSize > mMaxDivisionSize ) + //{ + // numColumns++; + // columnSize = segWidth / numColumns; + //} + + // Save the calculated numb of columns / rows for this segment. + segment.columns = mColumnCount; + segment.rows = numRows; + + // Save the corresponding number of verts/prims + segment.numVerts = ( 1 + mColumnCount ) * ( 1 + numRows ); + segment.numTriangles = mColumnCount * numRows * 2; + + mHighVertCount += segment.numVerts; + mHighTriangleCount += segment.numTriangles; + } + + // Number of low detail verts/prims. + mLowVertCount = mSlices.size() * 2; + mLowTriangleCount = mSegments.size() * 2; + + // Allocate the low detail VertexBuffer, + // this will stay in memory and will never need to change. + mVB_low.set( GFX, mLowVertCount, GFXBufferTypeStatic ); + + GFXWaterVertex *lowVertPtr = mVB_low.lock(); + U32 vertCounter = 0; + + // The texCoord.y value start/end for a segment + // as we loop through them. + F32 textCoordV = 0; + + // + // Fill the low-detail VertexBuffer + // + for ( U32 i = 0; i < mSlices.size(); i++ ) + { + RiverSlice &slice = mSlices[i]; + + lowVertPtr->point = slice.p0; + lowVertPtr->normal = slice.normal; + lowVertPtr->undulateData.set( -slice.width*0.5f, textCoordV ); + lowVertPtr->horizonFactor.set( 0, 0, 0, 0 ); + lowVertPtr++; + vertCounter++; + + lowVertPtr->point = slice.p2; + lowVertPtr->normal = slice.normal; + lowVertPtr->undulateData.set( slice.width*0.5f, textCoordV ); + lowVertPtr->horizonFactor.set( 0, 0, 0, 0 ); + lowVertPtr++; + vertCounter++; + + // Save this so we can get it later. + slice.texCoordV = textCoordV; + + if ( i < mSlices.size() - 1 ) + { + // Increment the textCoordV for the next slice. + F32 segLen = ( mSlices[i+1].p1 - slice.p1 ).len(); + textCoordV += segLen; + } + } + + AssertFatal( vertCounter == mLowVertCount, "River, wrote incorrect number of verts in mBV_low!" ); + + // Unlock the low-detail VertexBuffer, we are done filling it. + mVB_low.unlock(); + + // + // Create the low-detail prim buffer(s) + // + mPB_low.set( GFX, mLowTriangleCount * 3, mLowTriangleCount, GFXBufferTypeStatic ); + + U16 *lowIdxBuff; + mPB_low.lock(&lowIdxBuff); + U32 curLowIdx = 0; + + // Temporaries to hold indices for the corner points of a quad. + U32 p00, p01, p11, p10; + + U32 offset = 0; + + // Fill the low-detail PrimitiveBuffer + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + //const RiverSegment &segment = mSegments[i]; + + // Two triangles formed by the corner points of this segment + // into the the low detail primitive buffer. + p00 = offset; + p01 = p00 + 2; + p11 = p01 + 1; + p10 = p00 + 1; + + // Upper-Left triangle + lowIdxBuff[curLowIdx] = p00; + curLowIdx++; + lowIdxBuff[curLowIdx] = p01; + curLowIdx++; + lowIdxBuff[curLowIdx] = p11; + curLowIdx++; + + // Lower-Right Triangle + lowIdxBuff[curLowIdx] = p00; + curLowIdx++; + lowIdxBuff[curLowIdx] = p11; + curLowIdx++; + lowIdxBuff[curLowIdx] = p10; + curLowIdx++; + + offset += 2; + } + + AssertFatal( curLowIdx == mLowTriangleCount * 3, "River, wrote incorrect number of indices in mPB_low!" ); + + // Unlock the low-detail PrimitiveBuffer, we are done filling it. + mPB_low.unlock(); +} + +bool River::getClosestNode( const Point3F &pos, U32 &idx ) const +{ + F32 closestDist = F32_MAX; + + for ( U32 i = 0; i < mNodes.size(); i++ ) + { + F32 dist = ( mNodes[i].point - pos ).len(); + if ( dist < closestDist ) + { + closestDist = dist; + idx = i; + } + } + + return closestDist != F32_MAX; +} + +bool River::containsPoint( const Point3F &worldPos, U32 *nodeIdx ) +{ + // If point isn't in the world box, + // it's definitely not in the River. + //if ( !getWorldBox().isContained( worldPos ) ) + // return false; + + // Look through all edges, does the polygon + // formed from adjacent edge's contain the worldPos? + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + + if ( segment.containsPoint( worldPos ) ) + { + if ( nodeIdx ) + *nodeIdx = i; + return true; + } + } + + return false; +} + +F32 River::distanceToSurface( const Point3F &pnt, U32 segmentIdx ) +{ + return mSegments[segmentIdx].distanceToSurface( pnt ); +} + +bool River::collideRay( const Point3F &origin, const Point3F &direction, U32 *nodeIdx, Point3F *collisionPnt ) const +{ + Point3F p0 = origin; + Point3F p1 = origin + direction * 2000.0f; + + // If the line segment does not collide with the river's world box, + // it definitely does not collide with any part of the river. + if ( !getWorldBox().collideLine( p0, p1 ) ) + return false; + + if ( mSlices.size() < 2 ) + return false; + + MathUtils::Quad quad; + MathUtils::Ray ray; + F32 t; + + // Check each river segment (formed by a pair of slices) for collision + // with the line segment. + for ( U32 i = 0; i < mSlices.size() - 1; i++ ) + { + const RiverSlice &slice0 = mSlices[i]; + const RiverSlice &slice1 = mSlices[i+1]; + + // For simplicities sake we will only test for collision between the + // line segment and the Top face of the river segment. + + // Clockwise starting with the leftmost/closest point. + quad.p00 = slice0.p0; + quad.p01 = slice1.p0; + quad.p11 = slice1.p2; + quad.p10 = slice0.p2; + + ray.origin = origin; + ray.direction = direction; + + // NOTE: + // mRayQuadCollide is designed for a "real" quad in which all four points + // are coplanar which is actually not the case here. The more twist + // and turn in-between two neighboring river slices the more incorrect + // this calculation will be. + + if ( MathUtils::mRayQuadCollide( quad, ray, NULL, &t ) ) + { + if ( nodeIdx ) + *nodeIdx = slice0.parentNodeIdx; + if ( collisionPnt ) + *collisionPnt = ray.origin + ray.direction * t; + return true; + } + } + + return false; +} + +Point3F River::getNodePosition( U32 idx ) const +{ + if ( mNodes.size() - 1 < idx ) + return Point3F(); + + return mNodes[idx].point; +} + +void River::setNodePosition( U32 idx, const Point3F &pos ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].point = pos; + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); +} + +U32 River::addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal ) +{ + U32 idx = _addNode( pos, width, depth, normal ); + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); + + return idx; +} + +U32 River::insertNode(const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx) +{ + U32 ret = _insertNode( pos, width, depth, normal, idx ); + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); + + return ret; +} + +void River::setNode(const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx) +{ + if ( mNodes.size() - 1 < idx ) + return; + + RiverNode &node = mNodes[idx]; + node.point = pos; + node.width = width; + node.depth = depth; + node.normal = normal; + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); +} + +void River::setNodeWidth( U32 idx, F32 meters ) +{ + meters = mClampF( meters, MIN_NODE_WIDTH, MAX_NODE_WIDTH ); + + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].width = meters; + _regenerate(); + + setMaskBits( RegenMask | NodeMask ); +} + +void River::setNodeHeight( U32 idx, F32 height ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].point.z = height; + _regenerate(); + + setMaskBits( RegenMask | NodeMask ); +} + +F32 River::getNodeWidth( U32 idx ) const +{ + if ( mNodes.size() - 1 < idx ) + return -1.0f; + + return mNodes[idx].width; +} + +void River::setNodeDepth( U32 idx, F32 meters ) +{ + meters = mClampF( meters, MIN_NODE_DEPTH, MAX_NODE_DEPTH ); + + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].depth = meters; + _regenerate(); + setMaskBits( RiverMask | RegenMask | NodeMask ); +} + +void River::setNodeNormal( U32 idx, const VectorF &normal ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes[idx].normal = normal; + + regenerate(); + + setMaskBits( NodeMask | RegenMask ); +} + +F32 River::getNodeDepth( U32 idx ) const +{ + if ( mNodes.size() - 1 < idx ) + return -1.0f; + + return mNodes[idx].depth; +} + +VectorF River::getNodeNormal( U32 idx ) const +{ + if ( mNodes.size() - 1 < idx ) + return VectorF::Zero; + + return mNodes[idx].normal; +} + +MatrixF River::getNodeTransform( U32 idx ) const +{ + MatrixF mat(true); + + if ( mNodes.size() - 1 < idx ) + return mat; + + bool hasNext = idx + 1 < mNodes.size(); + bool hasPrev = (S32)idx - 1 >= 0; + + const RiverNode &node = mNodes[idx]; + + VectorF fvec( 0, 1, 0 ); + + if ( hasNext ) + { + fvec = mNodes[idx+1].point - node.point; + fvec.normalizeSafe(); + } + else if ( hasPrev ) + { + fvec = node.point - mNodes[idx-1].point; + fvec.normalizeSafe(); + } + else + fvec = mPerp( node.normal ); + + if ( fvec.isZero() ) + fvec = mPerp( node.normal ); + + F32 dot = mDot( fvec, node.normal ); + if ( dot < -0.9f || dot > 0.9f ) + fvec = mPerp( node.normal ); + + VectorF rvec = mCross( fvec, node.normal ); + if ( rvec.isZero() ) + rvec = mPerp( fvec ); + rvec.normalize(); + + fvec = mCross( node.normal, rvec ); + fvec.normalize(); + + mat.setColumn( 0, rvec ); + mat.setColumn( 1, fvec ); + mat.setColumn( 2, node.normal ); + mat.setColumn( 3, node.point ); + + AssertFatal( m_matF_determinant( mat ) != 0.0f, "no inverse!"); + + return mat; +} + +void River::deleteNode( U32 idx ) +{ + if ( mNodes.size() - 1 < idx ) + return; + + mNodes.erase(idx); + _regenerate(); + + setMaskBits( RegenMask | NodeMask ); +} + +void River::_makeRenderBatches( const Point3F &cameraPos ) +{ + // Loop through each segment to determine if it is either 1 [not visible], 2 [high LOD], 3 [low LOD] + + mHighLODBatches.clear(); + mLowLODBatches.clear(); + + // Keeps track of what we batch type we are currently collecting. + // -1 is uninitialized, 0 is low detail, 1 is high detail + S32 lastDetail = -1; + bool highDetail; + + U32 startSegmentIdx = -1; + U32 endSegmentIdx = 0; + + F32 lodDistSquared = mLodDistance * mLodDistance; + + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + const RiverSlice *slice = segment.slice0; + const RiverSlice *nextSlice = segment.slice1; + + // TODO: add bounds BoxF to RiverSegment + const bool isVisible = true; //frustum.intersects( segment.bounds ); + if ( isVisible ) + { + F32 dist0 = MathUtils::mTriangleDistance( slice->p0, nextSlice->p0, nextSlice->p2, cameraPos ); + F32 dist1 = MathUtils::mTriangleDistance( slice->p0, nextSlice->p2, slice->p2, cameraPos ); + + F32 dist = getMin( dist0, dist1 ); + highDetail = ( dist < lodDistSquared ); + if ( highDetail && lastDetail == 0 || + !highDetail && lastDetail == 1 ) + { + // We hit a segment with a different lod than the previous. + // Save what we have so far... + + RiverRenderBatch batch; + + batch.startSegmentIdx = startSegmentIdx; + batch.endSegmentIdx = endSegmentIdx; + + if ( lastDetail == 0 ) + { + mLowLODBatches.push_back( batch ); + } + else + { + mHighLODBatches.push_back( batch ); + } + + // Reset the batching + startSegmentIdx = -1; + lastDetail = -1; + i--; + + continue; + } + + // If this is the start of a set of batches. + if ( startSegmentIdx == -1 ) + { + endSegmentIdx = startSegmentIdx = i; + lastDetail = ( highDetail ) ? 1 : 0; + } + + // Else we're extending the end batch index. + else + ++endSegmentIdx; + + // If this isn't the last batch then continue. + if ( i < mSegments.size()-1 ) + continue; + } + + // If we still don't have a start batch skip. + if ( startSegmentIdx == -1 ) + continue; + + // Save what we have so far... + + RiverRenderBatch batch; + + batch.startSegmentIdx = startSegmentIdx; + batch.endSegmentIdx = endSegmentIdx; + + if ( lastDetail == 0 ) + { + mLowLODBatches.push_back( batch ); + } + else + { + mHighLODBatches.push_back( batch ); + } + + // Reset the batching. + startSegmentIdx = -1; + lastDetail = -1; + } +} + +void River::_makeHighLODBuffers() +{ + // This is the number of verts/triangles for ALL high lod batches combined. + // eg. the size for the buffers. + U32 numVerts = 0; + U32 numTriangles = 0; + + for ( U32 i = 0; i < mHighLODBatches.size(); i++ ) + { + RiverRenderBatch &batch = mHighLODBatches[i]; + + for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ ) + { + const RiverSegment &segment = mSegments[j]; + + numTriangles += segment.numTriangles; + numVerts += segment.numVerts; + } + } + + if ( numVerts > MAX_DYNAMIC_VERTS || numTriangles * 3 > MAX_DYNAMIC_INDICES ) + { + mVB_high = NULL; + mPB_high = NULL; + return; + } + + mHighTriangleCount = numTriangles; + mHighVertCount = numVerts; + + mVB_high.set( GFX, numVerts, GFXBufferTypeDynamic ); + GFXWaterVertex *vertPtr = mVB_high.lock(); + U32 vertCounter = 0; + + // NOTE: this will break if different segments have different number + // of columns, but that will also cause T-junction triangles so just don't + // do that. + + // For each batch, loop through the segments contained by + // that batch, and add their verts to the buffer. + for ( U32 i = 0; i < mHighLODBatches.size(); i++ ) + { + RiverRenderBatch &batch = mHighLODBatches[i]; + + batch.startVert = vertCounter; + batch.vertCount = 0; + + VectorF lastNormal(0,0,1); + + for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ ) + { + // Add the verts for this segment to the buffer. + RiverSegment &segment = mSegments[j]; + + BiSqrToQuad3D squareToQuad( segment.getP00(), + segment.getP10(), + segment.getP11(), + segment.getP01() ); + + // We are duplicating the last row of verts in a segment on + // the first row of the next segment. This could be optimized but + // shouldn't cause any problems. + + VectorF normal = segment.getSurfaceNormal(); + + for ( U32 k = 0; k <= segment.rows; k++ ) + { + VectorF vertNormal = ( k == 0 && j != batch.startSegmentIdx ) ? lastNormal : normal; + + F32 rowLen = mLerp( segment.slice0->width, segment.slice1->width, (F32)k / (F32)segment.rows ); + + for ( U32 l = 0; l <= segment.columns; l++ ) + { + // We are generating a "row" of verts along the forwardDivision + // Each l iteration is a step to the right along with row. + + Point2F uv( (F32)l / (F32)segment.columns, (F32)k / (F32)segment.rows ); + + Point3F pnt = squareToQuad.transform( uv ); + + // Assign the Vert + vertPtr->point = pnt; + vertPtr->normal = vertNormal; + vertPtr->undulateData.x = ( uv.x - 0.5f ) * rowLen; + vertPtr->undulateData.y = ( segment.TexCoordEnd() - segment.TexCoordStart() ) * uv.y + segment.TexCoordStart(); + vertPtr->horizonFactor.set( 0, 0, 0, 0 ); + + vertPtr++; + vertCounter++; + batch.vertCount++; + } + } + + lastNormal = normal; + } + } + + AssertFatal( vertCounter == mHighVertCount, "River, wrote incorrect number of verts in mVB_high" ); + + mVB_high.unlock(); + + // + // Do the high lod primitive buffer. + // + + mPB_high.set( GFX, numTriangles * 3, numTriangles, GFXBufferTypeDynamic ); + U16 *idxBuff; + mPB_high.lock(&idxBuff); + U32 curIdx = 0; + + U32 batchOffset = 0; + + // For each high lod batch, we must add indices to the buffer + // for each segment it contains ( and the count will depend on + // the division level columns/rows for each segment ). + + // Temporaries for holding the indices of a quad + U32 p00, p01, p11, p10; + + for ( U32 i = 0; i < mHighLODBatches.size(); i++ ) + { + RiverRenderBatch &batch = mHighLODBatches[i]; + + batch.indexCount = 0; + batch.triangleCount = 0; + batch.startIndex = curIdx; + + U32 temp = 0; + U32 segmentOffset = 0; + + for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ ) + { + const RiverSegment &segment = mSegments[j]; + + // Loop through all divisions adding the indices to the + // high detail primitive buffer. + for ( U32 k = 0; k < segment.rows; k++ ) + { + for ( U32 l = 0; l < segment.columns; l++ ) + { + // The indices for this quad. + p00 = batchOffset + segmentOffset + l + k * ( segment.columns + 1 ); + p01 = p00 + segment.columns + 1; + p11 = p01 + 1; + p10 = p00 + 1; + + AssertFatal( p00 <= mHighTriangleCount * 3, "River, bad draw call!" ); + AssertFatal( p01 <= mHighTriangleCount * 3, "River, bad draw call!" ); + AssertFatal( p11 <= mHighTriangleCount * 3, "River, bad draw call!" ); + AssertFatal( p10 <= mHighTriangleCount * 3, "River, bad draw call!" ); + + // Upper-Left triangle + idxBuff[curIdx] = p00; + curIdx++; + idxBuff[curIdx] = p01; + curIdx++; + idxBuff[curIdx] = p11; + curIdx++; + + // Lower-Right Triangle + idxBuff[curIdx] = p00; + curIdx++; + idxBuff[curIdx] = p11; + curIdx++; + idxBuff[curIdx] = p10; + curIdx++; + + batch.indexCount += 6; + batch.triangleCount += 2; + } + } + + // Increment the sliceOffset by the number of verts + // used by this segment. So the next segment will index + // into new verts. + segmentOffset += ( segment.columns + 1 ) * ( segment.rows + 1 ); + temp += ( segment.columns + 1 ) * ( segment.rows + 1 ); + } + + batchOffset += temp; + } + + AssertFatal( curIdx == numTriangles * 3, "River, wrote incorrect number of indicies to mPV_high" ); + + // Unlock the PrimitiveBuffer, we are done filling it. + mPB_high.unlock(); +} + +U32 River::_addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal ) +{ + mNodes.increment(); + RiverNode &node = mNodes.last(); + + node.point = pos; + node.width = width; + node.depth = depth; + node.normal = normal; + + setMaskBits( NodeMask | RegenMask ); + + return mNodes.size() - 1; +} + +U32 River::_insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx ) +{ + U32 ret; + RiverNode *node; + + if ( idx == U32_MAX ) + { + mNodes.increment(); + node = &mNodes.last(); + ret = mNodes.size() - 1; + } + else + { + mNodes.insert( idx ); + node = &mNodes[idx]; + ret = idx; + } + + node->point = pos; + node->depth = depth; + node->width = width; + node->normal = normal; + + return ret; +} + +void River::setMetersPerSegment( F32 meters ) +{ + if ( meters < MIN_METERS_PER_SEGMENT ) + { + Con::warnf( "River::setMetersPerSegment, specified meters (%g) is below the min meters (%g), NOT SET!", meters, MIN_METERS_PER_SEGMENT ); + return; + } + + mMetersPerSegment = meters; + _regenerate(); + setMaskBits( RiverMask | RegenMask ); +} + +void River::setBatchSize( U32 size ) +{ + // Not functional + //mSegmentsPerBatch = size; + //_regenerate(); + //setMaskBits( RiverMask | RegenMask ); +} + +void River::regenerate() +{ + _regenerate(); + setMaskBits( RegenMask ); +} + +void River::setMaxDivisionSize( F32 meters ) +{ + if ( meters < mMinDivisionSize ) + mMaxDivisionSize = mMinDivisionSize; + else + mMaxDivisionSize = meters; + + _regenerate(); + setMaskBits( RiverMask | RegenMask ); +} + +//------------------------------------------------------------------------- +// Console Methods +//------------------------------------------------------------------------- + +ConsoleMethod( River, regenerate, void, 2, 2, "setRegenFlag()" ) +{ + object->regenerate(); +} + +ConsoleMethod( River, setMetersPerSegment, void, 3, 3, "setMetersPerSegment( F32 meters )" ) +{ + object->setMetersPerSegment( dAtoi(argv[2]) ); +} + +ConsoleMethod( River, setBatchSize, void, 3, 3, "setBatchSize( U32 size )" ) +{ + object->setBatchSize( dAtoi(argv[2]) ); +} + +ConsoleMethod( River, setNodeDepth, void, 4, 4, "setNodeDepth( U32 idx, F32 meters )" ) +{ + object->setNodeDepth( dAtoi(argv[2]), dAtof(argv[3]) ); +} + +ConsoleMethod( River, setMaxDivisionSize, void, 3, 3, "setMaxDivisionSize( F32 meters )" ) +{ + object->setMaxDivisionSize( dAtof(argv[2]) ); +} diff --git a/environment/river.h b/environment/river.h new file mode 100644 index 0000000..1169fdf --- /dev/null +++ b/environment/river.h @@ -0,0 +1,512 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RIVER_H_ +#define _RIVER_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _CLIPPEDPOLYLIST_H_ +#include "collision/clippedPolyList.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _WATEROBJECT_H_ +#include "waterObject.h" +#endif + + + +//------------------------------------------------------------------------- +// RiverSplineNode Class +//------------------------------------------------------------------------- + +class Path; +class TerrainBlock; +struct ObjectRenderInst; + +class RiverSplineNode +{ +public: + RiverSplineNode() {} + + F32 x; + F32 y; + F32 z; + F32 width; + F32 depth; + VectorF normal; + + RiverSplineNode& operator=(const RiverSplineNode&); + RiverSplineNode operator+(const RiverSplineNode&) const; + RiverSplineNode operator-(const RiverSplineNode&) const; + RiverSplineNode operator*(const F32) const; + + F32 len(); +}; + +inline F32 RiverSplineNode::len() +{ + return mSqrt(F32(x*x + y*y + z*z)); +} + +inline RiverSplineNode& RiverSplineNode::operator=(const RiverSplineNode &_node) +{ + x = _node.x; + y = _node.y; + z = _node.z; + width = _node.width; + depth = _node.depth; + normal = _node.normal; + + return *this; +} + +inline RiverSplineNode RiverSplineNode::operator+(const RiverSplineNode& _add) const +{ + RiverSplineNode result; + result.x = x + _add.x; + result.y = y + _add.y; + result.z = z + _add.z; + result.width = width + _add.width; + result.depth = depth + _add.depth; + result.normal = normal + _add.normal; + + return result; +} + + +inline RiverSplineNode RiverSplineNode::operator-(const RiverSplineNode& _rSub) const +{ + RiverSplineNode result; + result.x = x - _rSub.x; + result.y = y - _rSub.y; + result.z = z - _rSub.z; + result.width = width - _rSub.width; + result.depth = depth - _rSub.depth; + result.normal = normal - _rSub.normal; + + return result; +} + +inline RiverSplineNode operator*(const F32 mul, const RiverSplineNode& multiplicand) +{ + return multiplicand * mul; +} + +inline RiverSplineNode RiverSplineNode::operator*(const F32 _mul) const +{ + RiverSplineNode result; + result.x = x * _mul; + result.y = y * _mul; + result.z = z * _mul; + result.width = width * _mul; + result.depth = depth * _mul; + result.normal = normal * _mul; + + return result; +} + +//------------------------------------------------------------------------- +// Structures +//------------------------------------------------------------------------- + +struct RiverRenderBatch +{ + U32 startSegmentIdx; + U32 endSegmentIdx; + U32 startVert; + U32 endVert; + U32 startIndex; + U32 endIndex; + U32 totalRows; + U32 indexCount; + + U32 vertCount; + U32 triangleCount; +}; + +typedef Vector RiverBatchVector; + +struct RiverNode +{ + // The 3D position of the node + Point3F point; + + // The width of the River at this node (meters) + F32 width; + + // The depth of the River at this node (meters) + F32 depth; + + VectorF normal; +}; + +typedef Vector RiverNodeVector; + +struct RiverSlice +{ + RiverSlice() + { + p0.zero(); + p1.zero(); + p2.zero(); + pb0.zero(); + pb2.zero(); + + uvec.zero(); + fvec.zero(); + rvec.zero(); + + normal.zero(); + + width = 0.0f; + depth = 0.0f; + texCoordV = 0.0f; + + parentNodeIdx = -1; + }; + + Point3F p0; // upper left + Point3F p1; // upper center + Point3F p2; // upper right + + Point3F pb0; // bottom left + Point3F pb2; // bottom right + + VectorF uvec; + VectorF fvec; + VectorF rvec; + + VectorF normal; + + F32 width; + F32 depth; + + F32 texCoordV; + + U32 parentNodeIdx; +}; +typedef Vector RiverSliceVector; + +//------------------------------------------------------------------------- +// RiverSegment Class +//------------------------------------------------------------------------- + +static Point3F sSegmentPointCompareReference; + +class RiverSegment +{ +public: + + RiverSegment(); + RiverSegment( RiverSlice *rs0, RiverSlice *rs1 ); + + void set( RiverSlice *rs0, RiverSlice *rs1 ); + + F32 TexCoordStart() { return slice0->texCoordV; } + F32 TexCoordEnd() { return slice1->texCoordV; } + + Point3F getP00() const { return slice0->p0; } + Point3F getP01() const { return slice1->p0; } + Point3F getP11() const { return slice1->p2; } + Point3F getP10() const { return slice0->p2; } + + // NOTE: + // For purposes of collision testing against a RiverSegment we represent + // it as a set of 6 planes (one for each face) much like a frustum. + // This is actually a flawed representation that will be more incorrect + // the more twist/bend exists between the two RiverSlices making up + // the segment. Basically we treat the four points that make up a "face" + // as if they were coplanar. + + Point3F getSurfaceCenter() const { return (slice0->p1 + slice1->p1) / 2; } + Point3F getSurfaceNormal() const { return ( -mPlanes[4].getNormal() ); } + Point3F getFaceCenter( U32 faceIdx ) const; + + bool intersectBox( const Box3F &bounds ) const; + bool containsPoint( const Point3F &pnt ) const; + F32 distanceToSurface( const Point3F &pnt ) const; + + // Quick access to the segment's points + Point3F& operator[](U32); + const Point3F& operator[](U32) const; + Point3F& operator[](S32 i) { return operator[](U32(i)); } + const Point3F& operator[](S32 i ) const { return operator[](U32(i)); } + + RiverSlice *slice0; + RiverSlice *slice1; + + PlaneF mPlanes[6]; + U32 mPlaneCount; + + U32 columns; + U32 rows; + + U32 startVert; + U32 endVert; + U32 startIndex; + U32 endIndex; + + U32 numVerts; + U32 numTriangles; + + Box3F worldbounds; + +protected: + + PlaneF _getBestPlane( const Point3F *p0, const Point3F *p1, const Point3F *p2, const Point3F *p3 ); +}; +typedef Vector RiverSegmentVector; + +inline Point3F& RiverSegment::operator[](U32 index) +{ + AssertFatal(index < 8, "RiverSegment::operator[] - out of bounds array access!"); + + RiverSlice *slice = NULL; + if ( index > 3 ) + { + slice = slice1; + index -= 4; + } + else + { + slice = slice0; + } + + if ( index == 0 ) + return slice->p0; + + if ( index == 1 ) + return slice->p2; + + if ( index == 2 ) + return slice->pb0; + + else //( index == 3 ) + return slice->pb2; +} + +inline const Point3F& RiverSegment::operator[](U32 index) const +{ + AssertFatal(index < 8, "RiverSegment::operator[] - out of bounds array access!"); + + RiverSlice *slice = NULL; + if ( index > 3 ) + { + slice = slice1; + index -= 4; + } + else + { + slice = slice0; + } + + if ( index == 0 ) + return slice->p0; + + if ( index == 1 ) + return slice->p2; + + if ( index == 2 ) + return slice->pb0; + + else// ( index == 3 ) + return slice->pb2; +} + +//------------------------------------------------------------------------------ +// River Class +//------------------------------------------------------------------------------ + +class ParticleEmitter; +class ParticleEmitterData; + +class River : public WaterObject +{ +private: + + friend class GuiRiverEditorCtrl; + friend class GuiRiverEditorUndoAction; + + typedef WaterObject Parent; + +protected: + + enum + { + RiverMask = Parent::NextFreeMask, + NodeMask = Parent::NextFreeMask << 1, + RegenMask = Parent::NextFreeMask << 2, + InitialUpdateMask = Parent::NextFreeMask << 3, + SelectedMask = Parent::NextFreeMask << 4, + MaterialMask = Parent::NextFreeMask << 5, + NextFreeMask = Parent::NextFreeMask << 6, + }; + +public: + + River(); + ~River(); + + DECLARE_CONOBJECT(River); + + // ConObject. + static void initPersistFields(); + static void consoleInit(); + + // SimObject + bool onAdd(); + void onRemove(); + void inspectPostApply(); + void onStaticModified(const char* slotName, const char*newValue = NULL); + void writeFields(Stream &stream, U32 tabStop); + bool writeField( StringTableEntry fieldname, const char *value ); + + // NetObject + U32 packUpdate(NetConnection *, U32, BitStream *); + void unpackUpdate(NetConnection *, BitStream *); + + // SceneObject + virtual void setTransform( const MatrixF &mat ); + virtual void setScale( const VectorF &scale ); + virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + virtual bool collideBox(const Point3F &start, const Point3F &end, RayInfo* info); + + // WaterObject + virtual F32 getWaterCoverage( const Box3F &worldBox ) const; + virtual F32 getSurfaceHeight( const Point2F &pos ) const; + virtual VectorF getFlow( const Point3F &pos ) const; + virtual void onReflectionInfoChanged(); + virtual void updateUnderwaterEffect( SceneState *state ); + + virtual bool isUnderwater( const Point3F &pnt ); + F32 distanceToSurface( const Point3F &pnt, U32 nodeIdx ); + bool containsPoint( const Point3F &worldPos, U32 *nodeIdx = NULL ); + + // Cast a ray against the river -- THE TOP SURFACE ONLY + bool collideRay( const Point3F &origin, const Point3F &direction, U32 *nodeIdx = NULL, Point3F *collisionPnt = NULL ) const; + + void regenerate(); + void setMetersPerSegment( F32 meters ); + void setBatchSize( U32 level ); + void setShowNodes( bool enabled ); + void setMaxDivisionSize( F32 meters ); + void setColumnCount( S32 count ); + + U32 insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx ); + U32 addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal ); + void setNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx ); + void deleteNode( U32 idx ); + + bool getClosestNode( const Point3F &pos, U32 &idx ) const; + + Point3F getNodePosition( U32 idx ) const; + void setNodePosition( U32 idx, const Point3F &pos ); + + MatrixF getNodeTransform( U32 idx ) const; + + F32 getNodeWidth( U32 idx ) const; + void setNodeWidth( U32 idx, F32 width ); + + F32 getNodeDepth( U32 idx ) const; + void setNodeDepth( U32 idx, F32 depth ); + + void setNodeHeight( U32 idx, F32 height ); + + void setNodeNormal( U32 idx, const VectorF &normal ); + VectorF getNodeNormal( U32 idx ) const; + + /// Protected 'Component' Field setter that will add a component to the list. + static bool addNodeFromField(void* obj, const char* data); + + static SimSet* getServerSet(); + + static bool smEditorOpen; + static bool smWireframe; + static bool smShowWalls; + static bool smShowNodes; + static bool smShowSpline; + static bool smShowRiver; + static SimObjectPtr smServerRiverSet; + +protected: + + void _loadRenderData(); + bool _setRenderData(); + + void _render( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *matInst ); + + void _makeRenderBatches( const Point3F &cameraPos ); + void _makeHighLODBuffers(); + + U32 _insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx ); + U32 _addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal ); + + void _regenerate(); + void _generateSlices(); + void _generateSegments(); + void _generateVerts(); + + bool _getTerrainHeight( const Point2F &pos, F32 &height ); + bool _getTerrainHeight( F32 x, F32 y, F32 &height ); + + // WaterObject + virtual void setShaderParams( SceneState *state, BaseMatInstance *mat, const WaterMatParams ¶mHandles ); + virtual SceneGraphData setupSceneGraphInfo( SceneState *state ); + virtual void innerRender( SceneState *state ); + + virtual void _getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos ); + +protected: + + RiverSliceVector mSlices; + + RiverNodeVector mNodes; + + RiverSegmentVector mSegments; + + GFXVertexBufferHandle mVB_high; // the high detail vertex buffer + GFXVertexBufferHandle mVB_low; // the low detail vertex buffer + + GFXPrimitiveBufferHandle mPB_high; // the high detail prim buffer + GFXPrimitiveBufferHandle mPB_low; // the low detail prim buffer + + RiverBatchVector mHighLODBatches; + RiverBatchVector mLowLODBatches; + + U32 mLowVertCount; + U32 mHighVertCount; + + U32 mLowTriangleCount; + U32 mHighTriangleCount; + + // Fields. + U32 mSegmentsPerBatch; + F32 mMetersPerSegment; + F32 mDepthScale; + F32 mFlowMagnitude; + F32 mLodDistance; + + // Divide segments that are greater than this length + // into sections of this size, or as close as possible + // while maintaining an equal division size. + F32 mMaxDivisionSize; + F32 mMinDivisionSize; + U32 mColumnCount; +}; + +#endif // _RIVER_H_ \ No newline at end of file diff --git a/environment/scatterSky.cpp b/environment/scatterSky.cpp new file mode 100644 index 0000000..0057003 --- /dev/null +++ b/environment/scatterSky.cpp @@ -0,0 +1,1207 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "scatterSky.h" + +#include "sceneGraph/sceneState.h" +#include "lighting/lightInfo.h" +#include "math/util/sphereMesh.h" + +#include "gfx/sim/gfxStateBlockData.h" +#include "materials/shaderData.h" +#include "gfx/gfxTransformSaver.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" +#include "environment/timeOfDay.h" +#include "sim/netConnection.h" +#include "math/mathUtils.h" +#include "gfx/sim/cubemapData.h" + +IMPLEMENT_CO_NETOBJECT_V1(ScatterSky); + +const F32 ScatterSky::smEarthRadius = (6378.0f * 1000.0f); +const F32 ScatterSky::smAtmosphereRadius = 200000.0f; +const F32 ScatterSky::smViewerHeight = 1.0f; + +GFXImplementVertexFormat( ScatterSkyVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); +} + +ScatterSky::ScatterSky() +{ + mPrimCount = 0; + mVertCount = 0; + + // Rayleigh scattering constant. + mRayleighScattering = 0.0035f; + mRayleighScattering4PI = mRayleighScattering * 4.0f * M_PI_F; + + // Mie scattering constant. + mMieScattering = 0.0045f; + mMieScattering4PI = mMieScattering * 4.0f * M_PI_F; + + // Overall scatter scalar. + mSkyBrightness = 25.0f; + + // The Mie phase asymmetry factor. + mMiePhaseAssymetry = -0.75f; + + mSphereInnerRadius = 1.0f; + mSphereOuterRadius = 1.0f * 1.025f; + mScale = 1.0f / (mSphereOuterRadius - mSphereInnerRadius); + + // 650 nm for red + // 570 nm for green + // 475 nm for blue + mWavelength.set( 0.650f, 0.570f, 0.475f, 0 ); + + mWavelength4[0] = mPow(mWavelength[0], 4.0f); + mWavelength4[1] = mPow(mWavelength[1], 4.0f); + mWavelength4[2] = mPow(mWavelength[2], 4.0f); + + mRayleighScaleDepth = 0.25f; + mMieScaleDepth = 0.1f; + + mAmbientColor.set( 0, 0, 0, 1.0f ); + mAmbientScale.set( 1.0f, 1.0f, 1.0f, 1.0f ); + + mSunColor.set( 0, 0, 0, 1.0f ); + mSunScale.set( 1.0f, 1.0f, 1.0f, 1.0f ); + + mFogColor.set( 0, 0, 0, 1.0f ); + + mExposure = 1.0f; + mNightInterpolant = 0; + + mShader = NULL; + + mTimeOfDay = 0; + + mSunAzimuth = 0.0f; + mSunElevation = 35.0f; + + mBrightness = 1.0f; + + mCastShadows = true; + mDirty = true; + + mLight = LightManager::createLightInfo(); + mLight->setType( LightInfo::Vector ); + + mFlareData = NULL; + mFlareState.clear(); + mFlareScale = 1.0f; + + mMoonEnabled = true; + mMoonScale = 0.3f; + mMoonTint.set( 0.192157f, 0.192157f, 0.192157f, 1.0f ); + MathUtils::getVectorFromAngles( mMoonLightDir, 0.0f, 45.0f ); + mMoonLightDir.normalize(); + mMoonLightDir = -mMoonLightDir; + mNightCubemap = NULL; + mNightColor.set( 0.0196078f, 0.0117647f, 0.109804f, 1.0f ); + mUseNightCubemap = false; + + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask |= EnvironmentObjectType | LightObjectType; + + _generateSkyPoints(); +} + +ScatterSky::~ScatterSky() +{ + SAFE_DELETE( mLight ); +} + +bool ScatterSky::onAdd() +{ + PROFILE_SCOPE(ScatterSky_onAdd); + + // onNewDatablock for the server is called here + // for the client it is called in unpackUpdate + if ( !Parent::onAdd() ) + return false; + + if ( isClientObject() ) + TimeOfDay::getTimeOfDayUpdateSignal().notify( this, &ScatterSky::_updateTimeOfDay ); + + setGlobalBounds(); + resetWorldBox(); + + addToScene(); + + if ( isClientObject() ) + { + _initMoon(); + Sim::findObject( mNightCubemapName, mNightCubemap ); + } + + return true; +} + +void ScatterSky::onRemove() +{ + removeFromScene(); + + if ( isClientObject() ) + TimeOfDay::getTimeOfDayUpdateSignal().remove( this, &ScatterSky::_updateTimeOfDay ); + + Parent::onRemove(); +} + +void ScatterSky::_conformLights() +{ + _initCurves(); + F32 val = mCurves[0].getVal( mTimeOfDay ); + mNightInterpolant = 1.0f - val; + + VectorF lightDirection; + F32 brightness; + + if ( mNightInterpolant == 1.0f ) + { + lightDirection = -mMoonLightDir; + brightness = mCurves[1].getVal( mTimeOfDay ); + } + else + { + // Build the light direction from the azimuth and elevation. + F32 yaw = mDegToRad(mClampF(mSunAzimuth,0,359)); + F32 pitch = mDegToRad(mClampF(mSunElevation,-360,+360)); + MathUtils::getVectorFromAngles(lightDirection, yaw, pitch); + lightDirection.normalize(); + + brightness = val; + } + + mLight->setDirection( -lightDirection ); + mLight->setBrightness( brightness * mBrightness ); + mLightDir = lightDirection; + + // Have to do interpolation + // after the light direction is set + // otherwise the sun color will be invalid. + _interpolateColors(); + + if ( mNightInterpolant == 1.0f ) + mAmbientColor += mAmbientColor * 2.0f * ( 1.0f - brightness ); + + mLight->setAmbient( mAmbientColor ); + mLight->setColor( mSunColor ); + + + bool castShadows = mCastShadows && + mSunColor != mAmbientColor && + brightness > 0.1f; + + //bool castShadows = false; + mLight->setCastShadows( castShadows ); + + FogData fog = gClientSceneGraph->getFogData(); + fog.color = mFogColor; + gClientSceneGraph->setFogData( fog ); +} + +void ScatterSky::submitLights( LightManager *lm, bool staticLighting ) +{ + if ( mDirty ) + { + _conformLights(); + mDirty = false; + } + + // The sun is a special light and needs special registration. + lm->setSpecialLight( LightManager::slSunLightType, mLight ); +} + +void ScatterSky::setAzimuth( F32 azimuth ) +{ + mSunAzimuth = azimuth; + mDirty = true; + setMaskBits( TimeMask ); +} + +void ScatterSky::setElevation( F32 elevation ) +{ + mSunElevation = elevation; + + while( elevation < 0 ) + elevation += 360.0f; + + while( elevation >= 360.0f ) + elevation -= 360.0f; + + mTimeOfDay = elevation / 180.0f; + mDirty = true; + setMaskBits( TimeMask ); +} + +void ScatterSky::inspectPostApply() +{ + mDirty = true; + setMaskBits( 0xFFFFFFFF ); +} + +void ScatterSky::initPersistFields() +{ + addGroup( "ScatterSky", + "Only azimuth and elevation are networked fields. To trigger a full update of all other fields use the applyChanges ConsoleMethod." ); + + addField( "skyBrightness", TypeF32, Offset( mSkyBrightness, ScatterSky ) ); + addField( "mieScattering", TypeF32, Offset( mMieScattering, ScatterSky ) ); + addField( "rayleighScattering", TypeF32, Offset( mRayleighScattering, ScatterSky ) ); + addField( "sunScale", TypeColorF, Offset( mSunScale, ScatterSky ) ); + addField( "ambientScale", TypeColorF, Offset( mAmbientScale, ScatterSky ) ); + addField( "exposure", TypeF32, Offset( mExposure, ScatterSky ) ); + + endGroup( "ScatterSky" ); + + addGroup( "Orbit" ); + + addProtectedField( "azimuth", TypeF32, Offset( mSunAzimuth, ScatterSky ), &ScatterSky::ptSetAzimuth, &defaultProtectedGetFn, + "The horizontal angle of the sun measured clockwise from the positive Y world axis. This field is networked." ); + + addProtectedField( "elevation", TypeF32, Offset( mSunElevation, ScatterSky ), &ScatterSky::ptSetElevation, &defaultProtectedGetFn, + "The elevation angle of the sun above or below the horizon. This field is networked." ); + + endGroup( "Orbit" ); + + // We only add the basic lighting options that all lighting + // systems would use... the specific lighting system options + // are injected at runtime by the lighting system itself. + + addGroup( "Lighting" ); + + addField( "castShadows", TypeBool, Offset( mCastShadows, ScatterSky ) ); + addField( "brightness", TypeF32, Offset( mBrightness, ScatterSky ), + "The brightness of the ScatterSky's light object." ); + + endGroup( "Lighting" ); + + addGroup( "Misc" ); + + addField( "flareType", TypeLightFlareDataPtr, Offset( mFlareData, ScatterSky ) ); + addField( "flareScale", TypeF32, Offset( mFlareScale, ScatterSky ) ); + + endGroup( "Misc" ); + + addGroup( "Night" ); + + addField( "nightColor", TypeColorF, Offset( mNightColor, ScatterSky ) ); + addField( "moonEnabled", TypeBool, Offset( mMoonEnabled, ScatterSky ) ); + addField( "moonTexture", TypeImageFilename, Offset( mMoonTextureName, ScatterSky ) ); + addField( "moonScale", TypeF32, Offset( mMoonScale, ScatterSky ) ); + addField( "moonTint", TypeColorF, Offset( mMoonTint, ScatterSky ) ); + addField( "useNightCubemap", TypeBool, Offset( mUseNightCubemap, ScatterSky ) ); + addField( "nightCubemap", TypeCubemapName, Offset( mNightCubemapName, ScatterSky ) ); + + endGroup( "Night" ); + + // Now inject any light manager specific fields. + LightManager::initLightFields(); + + Parent::initPersistFields(); +} + +U32 ScatterSky::packUpdate(NetConnection *con, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if ( stream->writeFlag( mask & TimeMask ) ) + { + stream->write( mSunAzimuth ); + stream->write( mSunElevation ); + } + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + stream->write( mRayleighScattering ); + mRayleighScattering4PI = mRayleighScattering * 4.0f * M_PI_F; + + stream->write( mRayleighScattering4PI ); + + stream->write( mMieScattering ); + mMieScattering4PI = mMieScattering * 4.0f * M_PI_F; + + stream->write( mMieScattering4PI ); + + stream->write( mSkyBrightness ); + + stream->write( mMiePhaseAssymetry ); + + stream->write( mSphereInnerRadius ); + stream->write( mSphereOuterRadius ); + + stream->write( mScale ); + + stream->write( mWavelength ); + + stream->write( mWavelength4[0] ); + stream->write( mWavelength4[1] ); + stream->write( mWavelength4[2] ); + + stream->write( mRayleighScaleDepth ); + stream->write( mMieScaleDepth ); + + stream->write( mNightColor ); + stream->write( mAmbientScale ); + stream->write( mSunScale ); + + stream->write( mExposure ); + + stream->write( mBrightness ); + + stream->writeFlag( mCastShadows ); + + stream->write( mFlareScale ); + + if ( stream->writeFlag( mFlareData ) ) + { + stream->writeRangedU32( mFlareData->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } + + stream->writeFlag( mMoonEnabled ); + stream->write( mMoonTextureName ); + stream->write( mMoonScale ); + stream->write( mMoonTint ); + stream->writeFlag( mUseNightCubemap ); + stream->write( mNightCubemapName ); + + + mLight->packExtended( stream ); + } + + return retMask; +} + +void ScatterSky::unpackUpdate(NetConnection *con, BitStream *stream) +{ + Parent::unpackUpdate(con, stream); + + if ( stream->readFlag() ) // TimeMask + { + F32 temp = 0; + stream->read( &temp ); + setAzimuth( temp ); + + stream->read( &temp ); + setElevation( temp ); + } + + if ( stream->readFlag() ) // UpdateMask + { + stream->read( &mRayleighScattering ); + stream->read( &mRayleighScattering4PI ); + + stream->read( &mMieScattering ); + stream->read( &mMieScattering4PI ); + + stream->read( &mSkyBrightness ); + + stream->read( &mMiePhaseAssymetry ); + + stream->read( &mSphereInnerRadius ); + stream->read( &mSphereOuterRadius ); + + stream->read( &mScale ); + + ColorF tmpColor( 0, 0, 0 ); + + stream->read( &tmpColor ); + + stream->read( &mWavelength4[0] ); + stream->read( &mWavelength4[1] ); + stream->read( &mWavelength4[2] ); + + stream->read( &mRayleighScaleDepth ); + stream->read( &mMieScaleDepth ); + + stream->read( &mNightColor ); + stream->read( &mAmbientScale ); + stream->read( &mSunScale ); + + if ( tmpColor != mWavelength ) + { + mWavelength = tmpColor; + mWavelength4[0] = mPow(mWavelength[0], 4.0f); + mWavelength4[1] = mPow(mWavelength[1], 4.0f); + mWavelength4[2] = mPow(mWavelength[2], 4.0f); + } + + stream->read( &mExposure ); + + stream->read( &mBrightness ); + + mCastShadows = stream->readFlag(); + + stream->read( &mFlareScale ); + + if ( stream->readFlag() ) + { + SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + LightFlareData *datablock = NULL; + + if ( Sim::findObject( id, datablock ) ) + mFlareData = datablock; + else + { + con->setLastError( "ScatterSky::unpackUpdate() - invalid LightFlareData!" ); + mFlareData = NULL; + } + } + else + mFlareData = NULL; + + mMoonEnabled = stream->readFlag(); + stream->read( &mMoonTextureName ); + stream->read( &mMoonScale ); + stream->read( &mMoonTint ); + mUseNightCubemap = stream->readFlag(); + stream->read( &mNightCubemapName ); + + mLight->unpackExtended( stream ); + + if ( isProperlyAdded() ) + { + mDirty = true; + _initMoon(); + Sim::findObject( mNightCubemapName, mNightCubemap ); + } + } +} + +bool ScatterSky::prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseState ) +{ + if ( !(state->isDiffusePass() || state->isReflectPass()) ) + return false; + + if (isLastState(state, stateKey)) + return false; + + setLastState(state, stateKey); + + // Test portal visibility. + if ( !state->isObjectRendered(this) ) + return false; + + // Regular sky render instance. + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &ScatterSky::_render ); + ri->type = RenderPassManager::RIT_Sky; + ri->defaultKey = 10; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + + // Debug render instance. + if ( Con::getBoolVariable( "$ScatterSky::debug", false ) ) + { + ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &ScatterSky::_debugRender ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + } + + // Light flare effect render instance. + if ( mFlareData && mNightInterpolant != 1.0f ) + { + mFlareState.fullBrightness = mBrightness; + mFlareState.scale = mFlareScale; + mFlareState.lightInfo = mLight; + + Point3F lightPos = state->getCameraPosition() - state->getFarPlane() * mLight->getDirection() * 0.9f; + mFlareState.lightMat.identity(); + mFlareState.lightMat.setPosition( lightPos ); + + mFlareData->prepRender( state, &mFlareState ); + } + + // Render instances for Night effects. + if ( mNightInterpolant <= 0.0f ) + return false; + + // Render instance for Moon sprite. + if ( mMoonEnabled && mMoonTexture.isValid() ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &ScatterSky::_renderMoon ); + ri->type = RenderPassManager::RIT_Sky; + // Render after sky objects and before CloudLayer! + ri->defaultKey = 5; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +bool ScatterSky::_initShader() +{ + ShaderData *shaderData; + if ( !Sim::findObject( "ScatterSkyShaderData", shaderData ) ) + { + Con::warnf( "ScatterSky::_initShader - failed to locate shader ScatterSkyShaderData!" ); + return false; + } + + mShader = shaderData->getShader(); + if ( !mShader ) + return false; + + if ( mStateBlock.isNull() ) + { + GFXStateBlockData *data = NULL; + if ( !Sim::findObject( "ScatterSkySBData", data ) ) + Con::warnf( "ScatterSky::_initShader - failed to locate ScatterSkySBData!" ); + else + mStateBlock = GFX->createStateBlock( data->getState() ); + } + + if ( !mStateBlock ) + return false; + + mShaderConsts = mShader->allocConstBuffer(); + mModelViewProjSC = mShader->getShaderConstHandle( "$modelView" ); + + // Camera height, cam height squared, scale and scale over depth. + mMiscSC = mShader->getShaderConstHandle( "$misc" ); + + // Inner and out radius, and inner and outer radius squared. + mSphereRadiiSC = mShader->getShaderConstHandle( "$sphereRadii" ); + + // Rayleigh sun brightness, mie sun brightness and 4 * PI * coefficients. + mScatteringCoefficientsSC = mShader->getShaderConstHandle( "$scatteringCoeffs" ); + mCamPosSC = mShader->getShaderConstHandle( "$camPos" ); + mLightDirSC = mShader->getShaderConstHandle( "$lightDir" ); + mPixLightDirSC = mShader->getShaderConstHandle( "$pLightDir" ); + mNightColorSC = mShader->getShaderConstHandle( "$nightColor" ); + mInverseWavelengthSC = mShader->getShaderConstHandle( "$invWaveLength" ); + mNightInterpolantAndExposureSC = mShader->getShaderConstHandle( "$nightInterpAndExposure" ); + mUseCubemapSC = mShader->getShaderConstHandle( "$useCubemap" ); + + return true; +} + +void ScatterSky::_initVBIB() +{ + U32 rings=60, segments=20;//rings=160, segments=20; + + // Set vertex count and index count. + U32 vertCount = ( rings + 1 ) * ( segments + 1 ) ; + U32 idxCount = 2 * rings * ( segments + 1 ) ; + + mVertCount = vertCount; + mPrimCount = idxCount; + + // If the VB or PB haven't been created then create them. + if ( mPrimBuffer.isNull() ) + mPrimBuffer.set( GFX, idxCount, 0, GFXBufferTypeStatic ); + + if ( mVB.isNull() ) + mVB.set( GFX, vertCount, GFXBufferTypeStatic ); + + Point3F tmpPoint( 0, 0, 0 ); + Point3F horizPoint( 0, 0, 0 ); + + ScatterSkyVertex *verts = mVB.lock(); + U16 *idxBuff; + mPrimBuffer.lock( &idxBuff ); + + // Establish constants used in sphere generation. + F32 deltaRingAngle = ( M_PI_F / (F32)(rings * 2) ); + F32 deltaSegAngle = ( 2.0f * M_PI_F / (F32)segments ); + + U32 vertIdx = 0; + // Generate the group of rings for the sphere. + for( int ring = 0; ring < rings + 1 ; ring++ ) + { + F32 r0 = mSin( ring * deltaRingAngle ); + F32 y0 = mCos( ring * deltaRingAngle ); + + // Generate the group of segments for the current ring. + for( int seg = 0; seg < segments + 1 ; seg++ ) + { + F32 x0 = r0 * sinf( seg * deltaSegAngle ); + F32 z0 = r0 * cosf( seg * deltaSegAngle ); + + tmpPoint.set( x0, z0, y0 ); + tmpPoint.normalizeSafe(); + + tmpPoint.x *= (6378.0f * 1000.0f) + 200000.0f; + tmpPoint.y *= (6378.0f * 1000.0f) + 200000.0f; + tmpPoint.z *= (6378.0f * 1000.0f) + 200000.0f; + tmpPoint.z -= (6378.0f * 1000.0f); + + // Add one vertices to the strip which makes up the sphere. + verts->point = tmpPoint; + verts++; + + // Add two indices except for last ring. + if ( ring != rings ) + { + *idxBuff = vertIdx; + idxBuff++; + *idxBuff = vertIdx + (U32)( segments + 1 ) ; + idxBuff++; + vertIdx++; + }; + }; // End for seg. + } // End for ring. + + mPrimCount = vertIdx / 2; + + mVB.unlock(); + mPrimBuffer.unlock(); +} + +void ScatterSky::_initMoon() +{ + if ( isServerObject() ) + return; + + // Load texture... + + if ( mMoonTextureName.isNotEmpty() ) + mMoonTexture.set( mMoonTextureName, &GFXDefaultStaticDiffuseProfile, "MoonTexture" ); + + // Make StateBlock... + + if ( mMoonSB.isNull() ) + { + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setAlphaTest( true, GFXCmpGreaterEqual, 1 ); + desc.setZReadWrite( false, false ); + desc.setBlend( true ); + desc.samplersDefined = true; + desc.samplers[0].textureColorOp = GFXTOPModulate; + desc.samplers[0].colorArg1 = GFXTATexture; + desc.samplers[0].colorArg2 = GFXTADiffuse; + desc.samplers[0].alphaOp = GFXTOPModulate; + desc.samplers[0].alphaArg1 = GFXTATexture; + desc.samplers[0].alphaArg2 = GFXTADiffuse; + + mMoonSB = GFX->createStateBlock(desc); + + desc.setFillModeWireframe(); + mMoonWireframeSB = GFX->createStateBlock(desc); + } +} + +void ScatterSky::_initCurves() +{ + if ( mCurves->getSampleCount() > 0 ) + return; + + // Takes time of day (0-2) and returns + // the night interpolant (0-1) day/night factor. + mCurves[0].clear(); + mCurves[0].addPoint( 0.0f, 0.5f ); + mCurves[0].addPoint( 0.1f, 1.0f ); + mCurves[0].addPoint( 0.9f, 1.0f ); + mCurves[0].addPoint( 1.0f, 0.5f ); + mCurves[0].addPoint( 1.1f, 0.0f ); + mCurves[0].addPoint( 1.9f, 0.0f ); + mCurves[0].addPoint( 2.0f, 0.5f ); + + // Takes time of day (0-2) and returns + // the moon light brightness. + mCurves[1].clear(); + mCurves[1].addPoint( 0.0f, 0.0f ); + mCurves[1].addPoint( 1.0f, 0.0f ); + mCurves[1].addPoint( 1.1f, 0.0f ); + mCurves[1].addPoint( 1.2f, 0.5f ); + mCurves[1].addPoint( 1.3f, 1.0f ); + mCurves[1].addPoint( 1.8f, 0.5f ); + mCurves[1].addPoint( 1.9f, 0.0f ); + mCurves[1].addPoint( 2.0f, 0.0f ); +} + +void ScatterSky::_updateTimeOfDay( TimeOfDay *timeOfDay, F32 time ) +{ + setElevation( timeOfDay->getElevationDegrees() ); + setAzimuth( timeOfDay->getAzimuthDegrees() ); +} + +void ScatterSky::_render( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + if ( overrideMat || (!mShader && !_initShader()) ) + return; + + GFXTransformSaver saver; + + //mLightDir = -mLight->getDirection(); + //mLightDir.normalize(); + + if ( mVB.isNull() || mPrimBuffer.isNull() ) + _initVBIB(); + + GFX->setShader( mShader ); + GFX->setShaderConstBuffer( mShaderConsts ); + + Point4F sphereRadii( mSphereOuterRadius, mSphereOuterRadius * mSphereOuterRadius, + mSphereInnerRadius, mSphereInnerRadius * mSphereInnerRadius ); + + Point4F scatteringCoeffs( mRayleighScattering * mSkyBrightness, mRayleighScattering4PI, + mMieScattering * mSkyBrightness, mMieScattering4PI ); + + Point4F invWavelength( 1.0f / mWavelength4[0], + 1.0f / mWavelength4[1], + 1.0f / mWavelength4[2], 1.0f ); + + Point3F camPos( 0, 0, smViewerHeight ); + Point4F miscParams( camPos.z, camPos.z * camPos.z, mScale, mScale / mRayleighScaleDepth ); + + Frustum frust = state->getFrustum(); + frust.setFarDist( smEarthRadius + smAtmosphereRadius ); + MatrixF proj( true ); + frust.getProjectionMatrix( &proj ); + + MatrixF camMat = state->getCameraTransform(); + camMat.inverse(); + MatrixF tmp( true ); + tmp = camMat; + tmp.setPosition( Point3F( 0, 0, 0 ) ); + + proj.mul( tmp ); + + mShaderConsts->set( mModelViewProjSC, proj ); + mShaderConsts->set( mMiscSC, miscParams ); + mShaderConsts->set( mSphereRadiiSC, sphereRadii ); + mShaderConsts->set( mScatteringCoefficientsSC, scatteringCoeffs ); + mShaderConsts->set( mCamPosSC, camPos ); + mShaderConsts->set( mLightDirSC, mLightDir ); + mShaderConsts->set( mPixLightDirSC, mLightDir ); + mShaderConsts->set( mNightColorSC, mNightColor ); + mShaderConsts->set( mInverseWavelengthSC, invWavelength ); + mShaderConsts->set( mNightInterpolantAndExposureSC, Point2F( mExposure, mNightInterpolant ) ); + + if ( GFXDevice::getWireframe() ) + { + GFXStateBlockDesc desc( mStateBlock->getDesc() ); + desc.setFillModeWireframe(); + GFX->setStateBlockByDesc( desc ); + } + else + GFX->setStateBlock( mStateBlock ); + + if ( mUseNightCubemap && mNightCubemap ) + { + mShaderConsts->set( mUseCubemapSC, 1.0f ); + + if ( !mNightCubemap->mCubemap ) + mNightCubemap->createMap(); + + GFX->setCubeTexture( 0, mNightCubemap->mCubemap ); + } + else + { + GFX->setCubeTexture( 0, NULL ); + mShaderConsts->set( mUseCubemapSC, 0.0f ); + } + + GFX->setPrimitiveBuffer( mPrimBuffer ); + GFX->setVertexBuffer( mVB ); + + GFX->drawIndexedPrimitive( GFXTriangleStrip, 0, 0, mVertCount, 0, mPrimCount ); +} + +void ScatterSky::_debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + GFXStateBlockDesc desc; + desc.fillMode = GFXFillSolid; + desc.setBlend( false, GFXBlendOne, GFXBlendZero ); + desc.setZReadWrite( false, false ); + GFXStateBlockRef sb = GFX->GFX->createStateBlock( desc ); + + GFX->setStateBlock( sb ); + + PrimBuild::begin( GFXLineStrip, mSkyPoints.size() ); + PrimBuild::color3i( 255, 0, 255 ); + + for ( U32 i = 0; i < mSkyPoints.size(); i++ ) + { + Point3F pnt = mSkyPoints[i]; + pnt.normalize(); + pnt *= 500; + pnt += state->getCameraPosition(); + PrimBuild::vertex3fv( pnt ); + } + + PrimBuild::end(); +} + +void ScatterSky::_renderMoon( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + Point3F moonlightPosition = state->getCameraPosition() - /*mLight->getDirection()*/ mMoonLightDir * state->getFarPlane() * 0.5f; + + // Calculate Billboard Radius (in world units) to be constant, independent of distance. + // Takes into account distance, viewport size, and specified size in editor + F32 BBRadius = (((moonlightPosition - state->getCameraPosition()).len()) / (GFX->getViewport().extent.x / 640.0)) / 2; + BBRadius *= mMoonScale; + + GFXTransformSaver saver; + + if ( state->isReflectPass() ) + GFX->setProjectionMatrix( gClientSceneGraph->getNonClipProjection() ); + + GFX->setStateBlock(mMoonSB); + + // Initialize points with basic info + Point3F points[4]; + points[0] = Point3F(-BBRadius, 0.0, -BBRadius); + points[1] = Point3F( BBRadius, 0.0, -BBRadius); + points[2] = Point3F( BBRadius, 0.0, BBRadius); + points[3] = Point3F(-BBRadius, 0.0, BBRadius); + + // Get info we need to adjust points + MatrixF camView = GFX->getWorldMatrix(); + camView.inverse(); + + // Finalize points + for(int i = 0; i < 4; i++) + { + // align with camera + camView.mulV(points[i]); + // offset + points[i] += moonlightPosition; + } + + // Draw it + + ColorF moonVertColor; + moonVertColor.set( 1.0f, 1.0f, 1.0f, mNightInterpolant ); + PrimBuild::color( moonVertColor ); + + GFX->setTexture(0, mMoonTexture); + + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::texCoord2f(0, 0); + PrimBuild::vertex3fv(points[0]); + PrimBuild::texCoord2f(1, 0); + PrimBuild::vertex3fv(points[1]); + PrimBuild::texCoord2f(1, 1); + PrimBuild::vertex3fv(points[2]); + PrimBuild::texCoord2f(0, 1); + PrimBuild::vertex3fv(points[3]); + PrimBuild::end(); +} + +void ScatterSky::_generateSkyPoints() +{ + U32 rings=60, segments=20;//rings=160, segments=20; + + Point3F tmpPoint( 0, 0, 0 ); + + // Establish constants used in sphere generation. + F32 deltaRingAngle = ( M_PI_F / (F32)(rings * 2) ); + F32 deltaSegAngle = ( 2.0f * M_PI_F / (F32)segments ); + + // Generate the group of rings for the sphere. + for( int ring = 0; ring < 2; ring++ ) + { + F32 r0 = mSin( ring * deltaRingAngle ); + F32 y0 = mCos( ring * deltaRingAngle ); + + // Generate the group of segments for the current ring. + for( int seg = 0; seg < segments + 1 ; seg++ ) + { + F32 x0 = r0 * sinf( seg * deltaSegAngle ); + F32 z0 = r0 * cosf( seg * deltaSegAngle ); + + tmpPoint.set( x0, z0, y0 ); + tmpPoint.normalizeSafe(); + + tmpPoint.x *= smEarthRadius + smAtmosphereRadius; + tmpPoint.y *= smEarthRadius + smAtmosphereRadius; + tmpPoint.z *= smEarthRadius + smAtmosphereRadius; + tmpPoint.z -= smEarthRadius; + + if ( ring == 1 ) + mSkyPoints.push_back( tmpPoint ); + } + } +} + +void ScatterSky::_interpolateColors() +{ + mFogColor.set( 0, 0, 0, 0 ); + mAmbientColor.set( 0, 0, 0, 0 ); + mSunColor.set( 0, 0, 0, 0 ); + + _getFogColor( &mFogColor ); + _getAmbientColor( &mAmbientColor ); + _getSunColor( &mSunColor ); + + mAmbientColor *= mAmbientScale; + mSunColor *= mSunScale; + + mFogColor.interpolate( mFogColor, mNightColor, mNightInterpolant ); + mFogColor.alpha = 1.0f; + + mAmbientColor.interpolate( mAmbientColor, mNightColor, mNightInterpolant ); + mSunColor.interpolate( mSunColor, mMoonTint, mNightInterpolant ); +} + +void ScatterSky::_getSunColor( ColorF *outColor ) +{ + PROFILE_SCOPE( ScatterSky_GetSunColor ); + + U32 count = 0; + ColorF tmpColor( 0, 0, 0 ); + VectorF tmpVec( 0, 0, 0 ); + + tmpVec = mLightDir; + tmpVec.x *= smEarthRadius + smAtmosphereRadius; + tmpVec.y *= smEarthRadius + smAtmosphereRadius; + tmpVec.z *= smEarthRadius + smAtmosphereRadius; + tmpVec.z -= smAtmosphereRadius; + + for ( U32 i = 0; i < 10; i++ ) + { + _getColor( tmpVec, &tmpColor ); + (*outColor) += tmpColor; + tmpVec.x += (smEarthRadius * 0.5f) + (smAtmosphereRadius * 0.5f); + count++; + } + + if ( count > 0 ) + (*outColor) /= count; +} + +void ScatterSky::_getAmbientColor( ColorF *outColor ) +{ + PROFILE_SCOPE( ScatterSky_GetAmbientColor ); + + ColorF tmpColor( 0, 0, 0, 0 ); + U32 count = 0; + + // Disable mieScattering for purposes of calculating the ambient color. + F32 oldMieScattering = mMieScattering; + mMieScattering = 0.0f; + + for ( U32 i = 0; i < mSkyPoints.size(); i++ ) + { + Point3F pnt( mSkyPoints[i] ); + + _getColor( pnt, &tmpColor ); + (*outColor) += tmpColor; + count++; + } + + if ( count > 0 ) + (*outColor) /= count; + //Point3F pColor( outColor->red, outColor->green, outColor->blue ); + //F32 len = pColor.len(); + //if ( len > 0 ) + // (*outColor) /= len; + + mMieScattering = oldMieScattering; +} + +void ScatterSky::_getFogColor( ColorF *outColor ) +{ + PROFILE_SCOPE( ScatterSky_GetFogColor ); + + VectorF scatterPos( 0, 0, 0 ); + + F32 sunBrightness = mSkyBrightness; + mSkyBrightness *= 0.25f; + + F32 yaw = 0, pitch = 0, originalYaw = 0; + VectorF fwd( 0, 1.0f, 0 ); + MathUtils::getAnglesFromVector( fwd, yaw, pitch ); + originalYaw = yaw; + pitch = mDegToRad( 10.0f ); + + ColorF tmpColor( 0, 0, 0 ); + + U32 i = 0; + for ( i = 0; i < 10; i++ ) + { + MathUtils::getVectorFromAngles( scatterPos, yaw, pitch ); + + scatterPos.x *= smEarthRadius + smAtmosphereRadius; + scatterPos.y *= smEarthRadius + smAtmosphereRadius; + scatterPos.z *= smEarthRadius + smAtmosphereRadius; + scatterPos.y -= smEarthRadius; + + _getColor( scatterPos, &tmpColor ); + (*outColor) += tmpColor; + + if ( i <= 5 ) + yaw += mDegToRad( 5.0f ); + else + { + originalYaw += mDegToRad( -5.0f ); + yaw = originalYaw; + } + + yaw = mFmod( yaw, M_2PI_F ); + } + + if ( i > 0 ) + (*outColor) /= i; + + mSkyBrightness = sunBrightness; +} + +F32 ScatterSky::_vernierScale( F32 fCos ) +{ + /* + F32 x5 = x * 5.25; + F32 x5p6 = (-6.80 + x5); + F32 xnew = (3.83 + x * x5p6); + F32 xfinal = (0.459 + x * xnew); + F32 xfinal2 = -0.00287 + x * xfinal; + F32 outx = mExp( xfinal2 ); + return 0.25 * outx;*/ + F32 x = 1.0 - fCos; + return 0.25f * exp( -0.00287f + x * (0.459f + x * (3.83f + x * ((-6.80f + (x * 5.25f))))) ); +} + +F32 ScatterSky::_getMiePhase( F32 fCos, F32 fCos2, F32 g, F32 g2) +{ + return 1.5f * ((1.0f - g2) / (2.0f + g2)) * (1.0f + fCos2) / mPow(mFabs(1.0f + g2 - 2.0f*g*fCos), 1.5f); +} + +F32 ScatterSky::_getRayleighPhase( F32 fCos2 ) +{ + return 0.75 + 0.75 * fCos2; +} + +void ScatterSky::_getColor( const Point3F &pos, ColorF *outColor ) +{ + PROFILE_SCOPE( ScatterSky_GetColor ); + + F32 scaleOverScaleDepth = mScale / mRayleighScaleDepth; + F32 rayleighBrightness = mRayleighScattering * mSkyBrightness; + F32 mieBrightness = mMieScattering * mSkyBrightness; + + Point3F invWaveLength( 1.0f / mWavelength4[0], + 1.0f / mWavelength4[1], + 1.0f / mWavelength4[2] ); + + Point3F v3Pos = pos / 6378000.0f; + v3Pos.z += mSphereInnerRadius; + + Point3F newCamPos( 0, 0, smViewerHeight ); + + VectorF v3Ray = v3Pos - newCamPos; + F32 fFar = v3Ray.len(); + v3Ray / fFar; + v3Ray.normalizeSafe(); + + Point3F v3Start = newCamPos; + F32 fDepth = mExp( scaleOverScaleDepth * (mSphereInnerRadius - smViewerHeight ) ); + F32 fStartAngle = mDot( v3Ray, v3Start ); + + F32 fStartOffset = fDepth * _vernierScale( fStartAngle ); + + F32 fSampleLength = fFar / 2.0f; + F32 fScaledLength = fSampleLength * mScale; + VectorF v3SampleRay = v3Ray * fSampleLength; + Point3F v3SamplePoint = v3Start + v3SampleRay * 0.5f; + + Point3F v3FrontColor( 0, 0, 0 ); + for ( U32 i = 0; i < 2; i++ ) + { + F32 fHeight = v3SamplePoint.len(); + F32 fDepth = mExp( scaleOverScaleDepth * (mSphereInnerRadius - smViewerHeight) ); + F32 fLightAngle = mDot( mLightDir, v3SamplePoint ) / fHeight; + F32 fCameraAngle = mDot( v3Ray, v3SamplePoint ) / fHeight; + + F32 fScatter = (fStartOffset + fDepth * ( _vernierScale( fLightAngle ) - _vernierScale( fCameraAngle ) )); + Point3F v3Attenuate( 0, 0, 0 ); + + F32 tmp = mExp( -fScatter * (invWaveLength[0] * mRayleighScattering4PI + mMieScattering4PI) ); + v3Attenuate.x = tmp; + + tmp = mExp( -fScatter * (invWaveLength[1] * mRayleighScattering4PI + mMieScattering4PI) ); + v3Attenuate.y = tmp; + + tmp = mExp( -fScatter * (invWaveLength[2] * mRayleighScattering4PI + mMieScattering4PI) ); + v3Attenuate.z = tmp; + + v3FrontColor += v3Attenuate * (fDepth * fScaledLength); + v3SamplePoint += v3SampleRay; + } + + Point3F mieColor = v3FrontColor * mieBrightness; + Point3F rayleighColor = v3FrontColor * (invWaveLength * rayleighBrightness); + Point3F v3Direction = newCamPos - v3Pos; + v3Direction.normalize(); + + F32 fCos = mDot( mLightDir, v3Direction ) / v3Direction.len(); + F32 fCos2 = fCos * fCos; + + F32 g = -0.991f; + F32 g2 = g * g; + F32 miePhase = _getMiePhase( fCos, fCos2, g, g2 ); + //F32 rayleighPhase = _getRayleighPhase( fCos2 ); + + Point3F color = rayleighColor + (miePhase * mieColor); + ColorF tmp( color.x, color.y, color.z, color.y ); + + //if ( !tmp.isValidColor() ) + //{ + // F32 len = color.len(); + // if ( len > 0 ) + // color /= len; + //} + + Point3F expColor( 0, 0, 0 ); + expColor.x = 1.0f - exp(-mExposure * color.x); + expColor.y = 1.0f - exp(-mExposure * color.y); + expColor.z = 1.0f - exp(-mExposure * color.z); + + tmp.set( expColor.x, expColor.y, expColor.z, 1.0f ); + + if ( !tmp.isValidColor() ) + { + F32 len = expColor.len(); + if ( len > 0 ) + expColor /= len; + } + + outColor->set( expColor.x, expColor.y, expColor.z, 1.0f ); +} + +// Static protected field set methods + +bool ScatterSky::ptSetElevation( void *obj, const char *data ) +{ + ScatterSky *sky = static_cast( obj ); + F32 val = dAtof( data ); + + sky->setElevation( val ); + + // we already set the field + return false; +} + +bool ScatterSky::ptSetAzimuth( void *obj, const char *data ) +{ + ScatterSky *sky = static_cast( obj ); + F32 val = dAtof( data ); + + sky->setAzimuth( val ); + + // we already set the field + return false; +} + +// ConsoleMethods + +ConsoleMethod( ScatterSky, applyChanges, void, 2, 2, "Apply a full network update of all fields to all clients." ) +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/environment/scatterSky.h b/environment/scatterSky.h new file mode 100644 index 0000000..69917db --- /dev/null +++ b/environment/scatterSky.h @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCATTERSKY_H_ +#define _SCATTERSKY_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _PRIMBUILDER_H_ +#include "gfx/primBuilder.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _LIGHTFLAREDATA_H_ +#include "T3D/lightFlareData.h" +#endif +#ifndef _TRESPONSECURVE_H_ +#include "math/util/tResponseCurve.h" +#endif + +class LightInfo; +class SphereMesh; +class TimeOfDay; +class CubemapData; + + +GFXDeclareVertexFormat( ScatterSkyVertex ) +{ + // .xyz = coords + Point3F point; + VectorF normal; + ColorF color; +}; + +class ScatterSky : public SceneObject, public ISceneLight +{ + typedef SceneObject Parent; + +public: + + enum MaskBits + { + UpdateMask = Parent::NextFreeMask, + TimeMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + ScatterSky(); + ~ScatterSky(); + + // SimObject + bool onAdd(); + void onRemove(); + + // ISceneLight + virtual void submitLights( LightManager *lm, bool staticLighting ); + virtual LightInfo* getLight() { return mLight; } + + // ConsoleObject + DECLARE_CONOBJECT(ScatterSky); + void inspectPostApply(); + static void initPersistFields(); + + // Network + U32 packUpdate ( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseState ); + + /// + void setAzimuth( F32 azimuth ); + /// + void setElevation( F32 elevation ); + + /// + F32 getAzimuth() const { return mSunAzimuth; } + /// + F32 getElevation() const { return mSunElevation; } + +protected: + + void _render( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + void _debugRender( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + void _renderMoon( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + void _initVBIB(); + bool _initShader(); + void _initMoon(); + void _initCurves(); + + F32 _getRayleighPhase( F32 fCos2 ); + F32 _getMiePhase( F32 fCos, F32 fCos2, F32 g, F32 g2 ); + F32 _vernierScale( F32 fCos ); + + void _generateSkyPoints(); + + void _getColor( const Point3F &pos, ColorF *outColor ); + void _getFogColor( ColorF *outColor ); + void _getAmbientColor( ColorF *outColor ); + void _getSunColor( ColorF *outColor ); + void _interpolateColors(); + + void _conformLights(); + + void _updateTimeOfDay( TimeOfDay *timeofDay, F32 time ); + + // static protected field set methods + static bool ptSetElevation( void *obj, const char *data ); + static bool ptSetAzimuth( void *obj, const char *data ); + +protected: + + static const F32 smEarthRadius; + static const F32 smAtmosphereRadius; + static const F32 smViewerHeight; + +#define CURVE_COUNT 2 + + FloatCurve mCurves[CURVE_COUNT]; + + U32 mVertCount; + U32 mPrimCount; + + F32 mRayleighScattering; + F32 mRayleighScattering4PI; + + F32 mMieScattering; + F32 mMieScattering4PI; + + F32 mSkyBrightness; + F32 mMiePhaseAssymetry; + + F32 mOuterRadius; + F32 mScale; + ColorF mWavelength; + F32 mWavelength4[3]; + F32 mRayleighScaleDepth; + F32 mMieScaleDepth; + + F32 mSphereInnerRadius; + F32 mSphereOuterRadius; + + F32 mExposure; + F32 mNightInterpolant; + + VectorF mLightDir; + + F32 mSunAzimuth; + F32 mSunElevation; + F32 mTimeOfDay; + + F32 mBrightness; + + ColorF mNightColor; + ColorF mAmbientColor; ///< Not a field + ColorF mSunColor; ///< Not a field + ColorF mFogColor; ///< Not a field + + ColorF mAmbientScale; + ColorF mSunScale; + + LightInfo *mLight; + + bool mCastShadows; + bool mDirty; + + LightFlareData *mFlareData; + LightFlareState mFlareState; + F32 mFlareScale; + + bool mMoonEnabled; + String mMoonTextureName; + GFXTexHandle mMoonTexture; + F32 mMoonScale; + ColorF mMoonTint; + GFXStateBlockRef mMoonSB; + GFXStateBlockRef mMoonWireframeSB; + VectorF mMoonLightDir; + CubemapData *mNightCubemap; + String mNightCubemapName; + bool mUseNightCubemap; + + Vector mSkyPoints; + + // Prim buffer, vertex buffer and shader for rendering. + GFXPrimitiveBufferHandle mPrimBuffer; + GFXVertexBufferHandle mVB; + GFXShaderRef mShader; + + GFXStateBlockRef mStateBlock; + + // Shared shader constant blocks + GFXShaderConstBufferRef mShaderConsts; + GFXShaderConstHandle *mModelViewProjSC; + GFXShaderConstHandle *mMiscSC; // Camera height, cam height squared, scale and scale over depth. + GFXShaderConstHandle *mSphereRadiiSC; // Inner and out radius, and inner and outer radius squared. + GFXShaderConstHandle *mScatteringCoefficientsSC; // Rayleigh sun brightness, mie sun brightness and 4 * PI * coefficients. + GFXShaderConstHandle *mCamPosSC; + GFXShaderConstHandle *mLightDirSC; + GFXShaderConstHandle *mPixLightDirSC; + GFXShaderConstHandle *mNightColorSC; + GFXShaderConstHandle *mInverseWavelengthSC; + GFXShaderConstHandle *mNightInterpolantAndExposureSC; + GFXShaderConstHandle *mUseCubemapSC; +}; + +#endif // _SCATTERSKY_H_ diff --git a/environment/sky.cpp b/environment/sky.cpp new file mode 100644 index 0000000..dda8992 --- /dev/null +++ b/environment/sky.cpp @@ -0,0 +1,1526 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "environment/sky.h" +#include "math/mMath.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneObject.h" +#include "math/mathIO.h" +#include "sceneGraph/windingClipper.h" +#include "platform/profiler.h" +#include "gfx/primBuilder.h" +#include "T3D/fx/particleEmitter.h" +#include "renderInstance/renderPassManager.h" +#include "core/stream/fileStream.h" + +// +#define HORIZON 0.0f +#define FOG_BAN_DETAIL 8 +#define RAD (2.0f * M_PI_F) + + +IMPLEMENT_CO_NETOBJECT_V1(Sky); + +//Static Sky variables +bool Sky::smCloudsOn = true; +bool Sky::smCloudOutlineOn = false; +bool Sky::smSkyOn = true; +S32 Sky::smNumCloudsOn = MAX_NUM_LAYERS; + +//Static Cloud variables +StormInfo Cloud::mGStormData; +F32 Cloud::mRadius; + + +//--------------------------------------------------------------------------- +Sky::Sky() +{ + mNumCloudLayers = 0; + mTypeMask |= EnvironmentObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mSkyTexturesOn = true; + mRenderBoxBottom = false; + mSolidFillColor.set(0.0f, 1.0f, 0.0f, 0.0f); + mWindVelocity.set(1.0f, 0.0f, 0.0f); + mWindDir.set(0.f, 0.f); + mNoRenderBans = false; + + mSkyGlow = false; + + mLastVisDisMod = -1; + + for(S32 i = 0; i < MAX_NUM_LAYERS; ++i) + { + mCloudSpeed[i] = 0.0001f * (1.0f + (i * 1.0f)); + mCloudHeight[i] = 0.0f; + } + + mStormCloudData.state = isDone; + mStormCloudData.speed = 0.0f; + mStormCloudData.time = 0.0f; + + mSkyVB = NULL; + mBanOffsetHeight = 50.0f; +} + +//------------------------------------------------------------------------------ +Sky::~Sky() +{ +} + +//--------------------------------------------------------------------------- +bool Sky::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.minExtents.set(-1e9f, -1e9f, -1e9f); + mObjBox.maxExtents.set( 1e9f, 1e9f, 1e9f); + resetWorldBox(); + + if(isClientObject()) + { + if(!loadDml()) + return false; + + loadVBPoints(); + + initSkyData(); + setupStateBlocks(); + } + else + { + setWindVelocity(mWindVelocity); + } + + addToScene(); + setSkyColor(); + return true; +} + +//--------------------------------------------------------------------------- +void Sky::initSkyData() +{ + calcPoints(); + mWindDir = Point2F(mWindVelocity.x, -mWindVelocity.y); + mWindDir.normalize(); + for(S32 i = 0; i < MAX_NUM_LAYERS; ++i) + { + mCloudLayer[i].setHeights(mCloudHeight[i], mCloudHeight[i]-0.05f, 0.00f); + mCloudLayer[i].setSpeed(mWindDir * mCloudSpeed[i]); + mCloudLayer[i].setPoints(); + } + setSkyColor(); +} + +//--------------------------------------------------------------------------- +void Sky::setSkyColor() +{ + if(mSceneManager) + { + mRealSkyColor.red = S32(mSolidFillColor.red * 255.0f); + mRealSkyColor.green = S32(mSolidFillColor.green * 255.0f); + mRealSkyColor.blue = S32(mSolidFillColor.blue * 255.0f); + } +} + +//--------------------------------------------------------------------------- +void Sky::setWindVelocity(const Point3F & vel) +{ + mWindVelocity = vel; + ParticleEmitter::setWindVelocity(vel); + if(isServerObject()) + setMaskBits(WindMask); +} + +const Point3F &Sky::getWindVelocity() const +{ + return(mWindVelocity); +} + +//--------------------------------------------------------------------------- +void Sky::onRemove() +{ + mSkyVB = NULL; + + removeFromScene(); + Parent::onRemove(); +} + +//--------------------------------------------------------------------------- +ConsoleMethod( Sky, stormClouds, void, 4, 4, "(bool show, float duration)") +{ + Sky *ctrl = static_cast(object); + ctrl->stormCloudsOn(dAtoi(argv[2]), dAtof(argv[3])); +} + +ConsoleMethod( Sky, getWindVelocity, const char *, 2, 2, "()") +{ + Sky * sky = static_cast(object); + char * retBuf = Con::getReturnBuffer(128); + + Point3F vel = sky->getWindVelocity(); + dSprintf(retBuf, 128, "%f %f %f", vel.x, vel.y, vel.z); + return(retBuf); +} + +ConsoleMethod( Sky, applySkyChanges, void, 2, 2, "() - Apply any changes.") +{ + object->applySkyChanges(); +} + +//--------------------------------------------------------------------------- + +ConsoleMethod( Sky, setWindVelocity, void, 5, 5, "(float x, float y, float z)") +{ + Sky * sky = static_cast(object); + if(sky->isClientObject()) + return; + + Point3F vel(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4])); + sky->setWindVelocity(vel); +} + +ConsoleMethod( Sky, stormCloudsShow, void, 3, 3, "(bool showClouds)") +{ + Sky *ctrl = static_cast(object); + ctrl->stormCloudsShow(dAtob(argv[2])); +} + +//--------------------------------------------------------------------------- +void Sky::initPersistFields() +{ + addGroup("Media"); + addField("materialList", TypeStringFilename, Offset(mMaterialListName,Sky)); + endGroup("Media"); + + addGroup("Clouds"); + // This is set from the DML. + addField("cloudHeightPer", TypeF32, Offset(mCloudHeight,Sky),MAX_NUM_LAYERS); + addField("cloudSpeed1", TypeF32, Offset(mCloudSpeed[0],Sky)); + addField("cloudSpeed2", TypeF32, Offset(mCloudSpeed[1],Sky)); + addField("cloudSpeed3", TypeF32, Offset(mCloudSpeed[2],Sky)); + endGroup("Clouds"); + + addGroup("Wind"); + addField("windVelocity", TypePoint3F, Offset(mWindVelocity, Sky)); + endGroup("Wind"); + + addGroup("Misc"); + addField("SkySolidColor", TypeColorF, Offset(mSolidFillColor, Sky)); + addField("useSkyTextures", TypeBool, Offset(mSkyTexturesOn, Sky)); + addField("renderBottomTexture", TypeBool, Offset(mRenderBoxBottom, Sky)); + addField("noRenderBans", TypeBool, Offset(mNoRenderBans, Sky)); + addField("renderBanOffsetHeight", TypeF32, Offset(mBanOffsetHeight, Sky)); + + addField("skyGlow", TypeBool, Offset(mSkyGlow, Sky)); + addField("skyGlowColor", TypeColorF, Offset(mSkyGlowColor, Sky)); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +void Sky::consoleInit() +{ +#if defined(TORQUE_DEBUG) + Con::addVariable("pref::CloudOutline", TypeBool, &smCloudOutlineOn); +#endif + Con::addVariable("pref::CloudsOn", TypeBool, &smCloudsOn); + + Con::addVariable("pref::NumCloudLayers", TypeS32, &smNumCloudsOn); + Con::addVariable("pref::SkyOn", TypeBool, &smSkyOn); +} +//--------------------------------------------------------------------------- +void Sky::unpackUpdate(NetConnection *, BitStream *stream) +{ + if(stream->readFlag()) // InitMask + { + stream->read(&mMaterialListName); + loadDml(); + + stream->read(&mSkyTexturesOn); + stream->read(&mRenderBoxBottom); + stream->read(&mSolidFillColor.red); + stream->read(&mSolidFillColor.green); + stream->read(&mSolidFillColor.blue); + mNoRenderBans = stream->readFlag(); + stream->read(&mBanOffsetHeight); + + for (U32 i = 0; i < MAX_NUM_LAYERS; i++) + { + stream->read(&mCloudHeight[i]); + stream->read(&mCloudSpeed[i]); + } + + initSkyData(); + Point3F vel; + if(mathRead(*stream, &vel)) + setWindVelocity(vel); + + } // InitMask + + if(stream->readFlag()) // WindMask + { + Point3F vel; + if(mathRead(*stream, &vel)) + setWindVelocity(vel); + } + + if(stream->readFlag()) // SkyGlowMask + { + mSkyGlow = stream->readFlag(); + if(mSkyGlow) + { + stream->read(&mSkyGlowColor.red); + stream->read(&mSkyGlowColor.green); + stream->read(&mSkyGlowColor.blue); + } + } + +} + +//--------------------------------------------------------------------------- +U32 Sky::packUpdate(NetConnection *, U32 mask, BitStream *stream) +{ + if(stream->writeFlag(mask & InitMask)) + { + stream->write(mMaterialListName); + stream->write(mSkyTexturesOn); + stream->write(mRenderBoxBottom); + stream->write(mSolidFillColor.red); + stream->write(mSolidFillColor.green); + stream->write(mSolidFillColor.blue); + stream->writeFlag(mNoRenderBans); + stream->write(mBanOffsetHeight); + + for (U32 i = 0; i < MAX_NUM_LAYERS; i++) + { + stream->write(mCloudHeight[i]); + stream->write(mCloudSpeed[i]); + } + mathWrite(*stream, mWindVelocity); + } + + if(stream->writeFlag(mask & WindMask)) + mathWrite(*stream, mWindVelocity); + + if(stream->writeFlag(mask & SkyGlowMask)) + { + if(stream->writeFlag(mSkyGlow)) + { + stream->write(mSkyGlowColor.red); + stream->write(mSkyGlowColor.green); + stream->write(mSkyGlowColor.blue); + } + } + + return 0; +} + +//--------------------------------------------------------------------------- +void Sky::inspectPostApply() +{ + setMaskBits(InitMask | SkyGlowMask); +} + +void Sky::setupStateBlocks() +{ + GFXStateBlockDesc clear; + clear.cullDefined = true; + clear.cullMode = GFXCullNone; + clear.zDefined = true; + clear.zWriteEnable = false; + mClearSB = GFX->createStateBlock(clear); + + GFXStateBlockDesc skybox; + skybox.cullDefined = true; + skybox.cullMode = GFXCullNone; + skybox.zDefined = true; + skybox.zEnable = false; + skybox.zWriteEnable = false; + skybox.samplersDefined = true; + skybox.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + mSkyBoxSB = GFX->createStateBlock(skybox); + + GFXStateBlockDesc renderbans; + renderbans.cullDefined = true; + renderbans.cullMode = GFXCullNone; + renderbans.zDefined = true; + renderbans.zEnable = false; + renderbans.zWriteEnable = false; + renderbans.blendDefined = true; + renderbans.blendEnable = true; + renderbans.blendSrc = GFXBlendSrcAlpha; + renderbans.blendDest = GFXBlendInvSrcAlpha; + mRenderBansSB = GFX->createStateBlock(renderbans); +} + +//--------------------------------------------------------------------------- +void Sky::renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + GFX->disableShaders(); + + for(U32 i = 0; i < GFX->getNumSamplers(); i++) + GFX->setTexture(i, NULL); + + RectI viewport = GFX->getViewport(); + + // Clear the objects viewport to the fog color. This is something of a dirty trick, + // since we want an identity projection matrix here... + MatrixF proj = GFX->getProjectionMatrix(); + GFX->setProjectionMatrix( MatrixF( true ) ); + + GFX->pushWorldMatrix(); + GFX->setWorldMatrix( MatrixF( true ) ); + + GFX->setStateBlock(mClearSB); + + ColorI fogColor(200, 200, 200, 255); + + if (state->getSceneManager()) + fogColor = state->getSceneManager()->getFogData().color; + + PrimBuild::color3i(U8(fogColor.red),U8(fogColor.green),U8(fogColor.blue)); + + GFX->setupGenericShaders( GFXDevice::GSColor ); + + PrimBuild::begin(GFXTriangleFan, 4); + PrimBuild::vertex3f(-1, -1, 1); + PrimBuild::vertex3f(-1, 1, 1); + PrimBuild::vertex3f( 1, 1, 1); + PrimBuild::vertex3f( 1, -1, 1); + PrimBuild::end(); + + // this fixes oblique frustum clip prob on planar reflections + if( state->isInvertedCull() ) + GFX->setProjectionMatrix( gClientSceneGraph->getNonClipProjection() ); + else + GFX->setProjectionMatrix( proj ); + + GFX->popWorldMatrix(); + GFX->pushWorldMatrix(); + + Point3F camPos = state->getCameraPosition(); + + MatrixF tMat(1); + tMat.setPosition(camPos); + + GFX->multWorld(tMat); + + render(state); + + GFX->setProjectionMatrix(proj); + + GFX->popWorldMatrix(); + + GFX->setViewport(viewport); +} + +//--------------------------------------------------------------------------- +bool Sky::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 startZone, const bool modifyBaseState) +{ + TORQUE_UNUSED(startZone); TORQUE_UNUSED(modifyBaseState); + AssertFatal(modifyBaseState == false, "Error, should never be called with this parameter set"); + AssertFatal(startZone == 0xFFFFFFFF, "Error, startZone should indicate -1"); + + PROFILE_START(Sky_prepRenderImage); + + if (isLastState(state, stateKey)) + { + PROFILE_END(); + return false; + } + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Sky::renderObject ); + ri->type = RenderPassManager::RIT_Sky; + ri->defaultKey = 10; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + PROFILE_END(); + return false; +} + +//--------------------------------------------------------------------------- +void Sky::render(SceneState *state) +{ + PROFILE_START(SkyRender); + + F32 banHeights[2] = {-(mSpherePt.z-1),-(mSpherePt.z-1)}; + F32 alphaBan[2] = {0.0f, 0.0f}; + Point3F camPos; + + if(gClientSceneGraph) + { + F32 currentVisDis = gClientSceneGraph->getVisibleDistance(); + if(mLastVisDisMod != currentVisDis) + { + calcPoints(); + for(S32 i = 0; i < MAX_NUM_LAYERS; ++i) + mCloudLayer[i].setPoints(); + + mLastVisDisMod = currentVisDis; + } + } + + ColorI fogColor(200, 200, 200, 255); + + if (state->getSceneManager()) + fogColor = state->getSceneManager()->getFogData().color; + + // Setup default values + alphaBan[0] = 0.0f; + alphaBan[1] = 0.0f; + banHeights[0] = HORIZON; + banHeights[1] = banHeights[0] + mBanOffsetHeight; + + // if lower ban is at top of box then no clipping plane is needed + if(banHeights[0] >= mSpherePt.z) + banHeights[0] = banHeights[1] = mSpherePt.z; + + //Renders the 6 sides of the sky box + if(alphaBan[1] < 1.0f) + renderSkyBox(banHeights[0], alphaBan[1]); + + // if completely fogged out then no need to render + if(alphaBan[1] < 1.0f) + { + if(smCloudsOn && mStormCloudsOn && smSkyOn) + { + F32 ang = mAtan2(banHeights[0],mSkyBoxPt.x); + F32 xyval = mSin(ang); + F32 zval = mCos(ang); + PlaneF planes[4]; + planes[0] = PlaneF(xyval, 0.0f, zval, 0.0f); + planes[1] = PlaneF(-xyval, 0.0f, zval, 0.0f); + planes[2] = PlaneF(0.0f, xyval, zval, 0.0f); + planes[3] = PlaneF(0.0f, -xyval, zval, 0.0f); + + S32 numRender = (smNumCloudsOn > mNumCloudLayers) ? mNumCloudLayers : smNumCloudsOn; + for(S32 x = 0; x < numRender; ++x) + mCloudLayer[x].render( + Sim::getCurrentTime(), x, + smCloudOutlineOn, mNumCloudLayers, + planes); + } + + if(!mNoRenderBans) + { + Point3F banPoints[2][MAX_BAN_POINTS]; + Point3F cornerPoints[MAX_BAN_POINTS]; + + // Calculate upper, lower, and corner ban points + calcBans(banHeights, banPoints, cornerPoints); + + GFX->setTexture(0, NULL); + + // Renders the side, top, and corner bans + renderBans(alphaBan, banHeights, banPoints, cornerPoints, fogColor); + } + } + + PROFILE_END(); +} + +void Sky::setRenderPoints(Point3F* renderPoints, S32 index) +{ + renderPoints[0].set(mPoints[index ].x, mPoints[index ].y, mPoints[index ].z); + renderPoints[1].set(mPoints[index+1].x, mPoints[index+1].y, mPoints[index+1].z); + renderPoints[2].set(mPoints[index+6].x, mPoints[index+6].y, mPoints[index+6].z); + renderPoints[3].set(mPoints[index+5].x, mPoints[index+5].y, mPoints[index+5].z); +} + +void Sky::calcTexCoords(Point2F* texCoords, Point3F* renderPoints, S32 index, F32 lowerBanHeight) +{ + + for(S32 x = 0; x < 4; ++x) + texCoords[x].set(mTexCoord[x].x, mTexCoord[x].y); + S32 length = (S32)(mFabs(mPoints[index].z) + mFabs(mPoints[index + 5].z)); + F32 per = mPoints[index].z - renderPoints[3].z; + + texCoords[3].y = texCoords[2].y = (per / length); +} + +//--------------------------------------------------------------------------- +void Sky::renderSkyBox(F32 lowerBanHeight, F32 alphaBanUpper) +{ + S32 side, index=0, val; + U32 numPoints; + Point3F renderPoints[8]; + Point2F texCoords[8]; + + GFX->setStateBlock(mSkyBoxSB); + + PrimBuild::color4f( 1, 1, 1, 1 ); + + if(!mSkyTexturesOn || !smSkyOn) + { + GFX->setTexture(0, NULL); + PrimBuild::color3i(U8(mRealSkyColor.red), U8(mRealSkyColor.green), U8(mRealSkyColor.blue)); + } + + for(side = 0; side < ((mRenderBoxBottom) ? 6 : 5); ++side) + { + if((lowerBanHeight != mSpherePt.z || (side == 4 && alphaBanUpper < 1.0f)) && mSkyHandle[side]) + { + if(!mSkyTexturesOn || !smSkyOn) + { + GFX->setTexture(0, NULL); + PrimBuild::color3i(U8(mRealSkyColor.red), U8(mRealSkyColor.green), U8(mRealSkyColor.blue)); + } + else + { + GFX->setTexture(0, mSkyHandle[side]); + } + + // If it's one of the sides... + if(side < 4) + { + numPoints = 4; + setRenderPoints(renderPoints, index); + + if(!mNoRenderBans) + sgUtil_clipToPlane(renderPoints, numPoints, PlaneF(0.0f, 0.0f, 1.0f, -lowerBanHeight)); + // Need this assert since above method can change numPoints + AssertFatal(sizeof(Point3F) * numPoints <= sizeof(renderPoints), "Exceeding size of renderPoints array"); + + if(numPoints) + { + calcTexCoords(texCoords, renderPoints, index, lowerBanHeight); + + GFX->setupGenericShaders( GFXDevice::GSModColorTexture ); + + PrimBuild::begin(GFXTriangleFan, numPoints); + for (S32 p = 0; p < numPoints; p++) + { + PrimBuild::texCoord2f(texCoords[p].x, texCoords[p].y); + PrimBuild::vertex3f( renderPoints[p].x, renderPoints[p].y, renderPoints[p].z); + } + PrimBuild::end(); + + } + index++; + } + else + { + index = 3; + val = -1; + if(side == 5) + { + index = 5; + val = 1; + } + + GFX->setupGenericShaders( GFXDevice::GSModColorTexture ); + + PrimBuild::begin(GFXTriangleFan, 4); + PrimBuild::texCoord2f(mTexCoord[0].x, mTexCoord[0].y); + PrimBuild::vertex3f(mPoints[index].x, mPoints[index].y, mPoints[index].z); + PrimBuild::texCoord2f(mTexCoord[1].x, mTexCoord[1].y); + PrimBuild::vertex3f(mPoints[index+(1*val)].x, mPoints[index+(1*val)].y, mPoints[index+(1*val)].z); + PrimBuild::texCoord2f(mTexCoord[2].x, mTexCoord[2].y); + PrimBuild::vertex3f(mPoints[index+(2*val)].x, mPoints[index+(2*val)].y, mPoints[index+(2*val)].z); + PrimBuild::texCoord2f(mTexCoord[3].x, mTexCoord[3].y); + PrimBuild::vertex3f(mPoints[index+(3*val)].x, mPoints[index+(3*val)].y, mPoints[index+(3*val)].z); + PrimBuild::end(); + } + + } + } +} + +//--------------------------------------------------------------------------- +void Sky::calcBans(F32 *banHeights, Point3F banPoints[][MAX_BAN_POINTS], Point3F *cornerPoints) +{ + F32 incRad = RAD / F32(FOG_BAN_DETAIL*2); + MatrixF ban; + Point4F point; + S32 index, x; + F32 height = banHeights[0]; + + F32 value = banHeights[0] / mSkyBoxPt.z; + F32 mulVal = -(mSqrt(1-(value*value))); // lowerBan Multiple + index=0; + + // Calculates the upper and lower bans + for(x=0; x < 2; ++x) + { + for(F32 angle=0.0f; angle <= RAD+incRad ; angle+=incRad) + { + ban.set(Point3F(0.0f, 0.0f, angle)); + point.set(mulVal*mSkyBoxPt.x,0.0f,0.0f,1.0f); + ban.mul(point); + banPoints[x][index].set(point.x,point.y,height); + index++; + } + height = banHeights[1]; + value = banHeights[1] / mSkyBoxPt.x; + value = mClampF( value, 0.0, 1.0 ); + mulVal = -(mSqrt(1.0-(value*value))); // upperBan Multiple + index = 0; + } + + // Calculates the filler points needed between the lower ban and the clipping plane + index = 2; + cornerPoints[0].set(mPoints[3].x, mPoints[3].y, banHeights[0]-1); + cornerPoints[1].set(mPoints[3].x, 0.0f, banHeights[0]-1); + + for(x = 0; x < (FOG_BAN_DETAIL/2.0f) + 1.0f; ++x) + cornerPoints[index++].set(banPoints[0][x].x, banPoints[0][x].y, banPoints[0][x].z); + cornerPoints[index].set(0.0f, mPoints[3].y, banHeights[0]-1 ); +} + +//--------------------------------------------------------------------------- +void Sky::renderBans(const F32 *alphaBan, const F32 *banHeights, const Point3F banPoints[][MAX_BAN_POINTS], const Point3F *cornerPoints, const ColorI& fogColor) +{ + S32 side, x, index = 0; + F32 angle; + U8 UalphaIn = U8(alphaBan[1]*255); + U8 UalphaOut = U8(alphaBan[0]*255); + + GFX->setStateBlock(mRenderBansSB); + + //Renders the side bans + if(banHeights[0] < mSpherePt.z) + { + GFX->setupGenericShaders( GFXDevice::GSColor ); + PrimBuild::begin(GFXTriangleStrip, 2*(FOG_BAN_DETAIL*2+1)); + for(x=0;xsetupGenericShaders( GFXDevice::GSColor ); + PrimBuild::begin(GFXTriangleFan, 2*(FOG_BAN_DETAIL*2+1)); + + PrimBuild::color4i(U8(fogColor.red), U8(fogColor.green), U8(fogColor.blue), UalphaIn); + PrimBuild::vertex3f(mTopCenterPt.x, mTopCenterPt.y, mTopCenterPt.z); + + for(x=0;xpushWorldMatrix(); + + angle = 0.0f; + + //Renders the filler + for(side=0;side<4;++side) + { + // Rotate stuff + AngAxisF rotAAF( Point3F(0,0,1), angle); + MatrixF m; + rotAAF.setMatrix(&m); + GFX->multWorld(m); + + GFX->setupGenericShaders( GFXDevice::GSColor ); + + PrimBuild::begin(GFXTriangleFan, FOG_BAN_DETAIL); + for(x=0;xpopWorldMatrix(); +} + +//--------------------------------------------------------------------------- +void Sky::startStorm() +{ + mStormCloudsOn = true; + Cloud::startStorm(mStormCloudData.state); + for(int i = 0; i < mNumCloudLayers; ++i) + mCloudLayer[i].calcStorm(mStormCloudData.speed, mStormCloudData.fadeSpeed); +} + +void Sky::stormCloudsShow(bool show) +{ + mStormCloudsOn = show; + setMaskBits(StormCloudsOnMask); +} + +//--------------------------------------------------------------------------- +// Load vertex buffer points +//--------------------------------------------------------------------------- +void Sky::loadVBPoints() +{ + mSkyVB.set(GFX, 24, GFXBufferTypeStatic ); + mSkyVB.lock(); + + Point3F points[8]; + + #define fillPoints( PointIndex, x, y, z ){\ + points[PointIndex].set( x, y, z ); } + + #define fillVerts( PointIndex, SkyIndex, TU, TV ){\ + dMemcpy( &mSkyVB[SkyIndex], points[PointIndex], sizeof(Point3F) );\ + mSkyVB[SkyIndex].color.set( 255, 255, 255, 255 );\ + mSkyVB[SkyIndex].texCoord.x = TU;\ + mSkyVB[SkyIndex].texCoord.y = TV;} + + fillPoints( 0, -1.0, -1.0, 1.0 ); + fillPoints( 1, 1.0, -1.0, 1.0 ); + fillPoints( 2, 1.0, 1.0, 1.0 ); + fillPoints( 3, -1.0, 1.0, 1.0 ); + fillPoints( 4, -1.0, -1.0, -1.0 ); + fillPoints( 5, 1.0, -1.0, -1.0 ); + fillPoints( 6, 1.0, 1.0, -1.0 ); + fillPoints( 7, -1.0, 1.0, -1.0 ); + + + fillVerts( 0, 0, 0.0, 1.0 ); + fillVerts( 1, 1, 1.0, 1.0 ); + fillVerts( 2, 2, 1.0, 0.0 ); + fillVerts( 3, 3, 0.0, 0.0 ); + + fillVerts( 4, 4, 0.0, 0.0 ); + fillVerts( 5, 5, 1.0, 0.0 ); + fillVerts( 6, 6, 1.0, 1.0 ); + fillVerts( 7, 7, 0.0, 1.0 ); + + fillVerts( 0, 8, 0.0, 0.0 ); + fillVerts( 1, 9, 1.0, 0.0 ); + fillVerts( 5, 10, 1.0, 1.0 ); + fillVerts( 4, 11, 0.0, 1.0 ); + + fillVerts( 2, 12, 0.0, 0.0 ); + fillVerts( 3, 13, 1.0, 0.0 ); + fillVerts( 7, 14, 1.0, 1.0 ); + fillVerts( 6, 15, 0.0, 1.0 ); + + fillVerts( 3, 16, 0.0, 0.0 ); + fillVerts( 0, 17, 1.0, 0.0 ); + fillVerts( 4, 18, 1.0, 1.0 ); + fillVerts( 7, 19, 0.0, 1.0 ); + + fillVerts( 1, 20, 0.0, 0.0 ); + fillVerts( 2, 21, 1.0, 0.0 ); + fillVerts( 6, 22, 1.0, 1.0 ); + fillVerts( 5, 23, 0.0, 1.0 ); + + mSkyVB.unlock(); +} + +//--------------------------------------------------------------------------- +void Sky::calcPoints() +{ + S32 x, xval = 1, yval = -1; + F32 textureDim; + + F32 visDisMod = 1000.0f; + if(gClientSceneGraph) + visDisMod = gClientSceneGraph->getVisibleDistance(); + mRadius = visDisMod * 0.20f; + + Cloud::setRadius(mRadius); + + Point3F tpt(1,1,1); + tpt.normalize(mRadius); + + mPoints[0] = mPoints[4] = Point3F(-tpt.x, -tpt.y, tpt.z); + mPoints[5] = mPoints[9] = Point3F(-tpt.x, -tpt.y, -tpt.z); + + for(x = 1; x < 4; ++x) + { + mPoints[x] = Point3F(tpt.x * xval, tpt.y * yval, tpt.z); + mPoints[x+5] = Point3F(tpt.x * xval, tpt.y * yval, -tpt.z); + + if(yval > 0 && xval > 0) + xval *= -1; + if(yval < 0) + yval *= -1; + } + + textureDim = 512.0f; + if(mSkyHandle[0]) + textureDim = (F32)mSkyHandle[0].getWidth(); + + mTexCoord[0].set( 0.f, 0.f ); + mTexCoord[1].set( 1.f, 0.f ); + mTexCoord[2].set( 1.f, 1.f ); + mTexCoord[3].set( 0.f, 1.f ); + + for(U32 i = 0; i < 4 ; i++) + { + mTexCoord[i] *= (textureDim-1.0f)/textureDim; + mTexCoord[i] += Point2F(0.5 / textureDim, 0.5 / textureDim); + } + + mSpherePt = mSkyBoxPt = mPoints[1]; + mSpherePt.set(mSpherePt.x,0.0f,mSpherePt.z); + mSpherePt.normalize(mSkyBoxPt.x); + mTopCenterPt.set(0.0f,0.0f,mSkyBoxPt.z); +} + +//--------------------------------------------------------------------------- + +bool Sky::loadDml() +{ + // Reset cloud layers. + mNumCloudLayers = 0; + + FileStream *stream = FileStream::createAndOpen( mMaterialListName, Torque::FS::File::Read ); + + if (stream == NULL) + { + Con::errorf("Sky material list is missing: %s", mMaterialListName.c_str()); + return false; + } + + mMaterialList.read(*stream); + stream->close(); + + delete stream; + + const Torque::Path thePath( mMaterialListName ); + + if(!mMaterialList.load(thePath.getPath())) + { + Con::errorf("Sky material list failed to load properly: %s", mMaterialListName.c_str()); + return false; + } + + // Finally, assign our various texture handles. + for(S32 x = 0; x < 6; ++x) + mSkyHandle[x] = mMaterialList.getMaterial(x); + + for(S32 x = 0; x < mMaterialList.size() - CloudMaterialOffset; ++x, ++mNumCloudLayers) + mCloudLayer[x].setTexture(mMaterialList.getMaterial(x + CloudMaterialOffset)); + + if(mNumCloudLayers>3) + Con::warnf("Sky::loadDml - got more than 3 cloud layers, may not be able to control all the layers properly!"); + + return true; +} + +//--------------------------------------------------------------------------- +void Sky::stormCloudsOn(S32 state, F32 time) +{ + mStormCloudData.state = (state) ? comingIn : goingOut; + mStormCloudData.time = time; + + setMaskBits(StormCloudMask); +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Cloud Code +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +Cloud::Cloud() +{ + mDown=5; + mOver=1; + mBaseOffset.set(0, 0); + mTextureScale.set(1, 1); + mCenterHeight=0.5f; + mInnerHeight=0.45f; + mEdgeHeight=0.4f; + mLastTime = 0; + mOffset=0; + mSpeed.set(1,1); + mGStormData.currentCloud = MAX_NUM_LAYERS; + mGStormData.fadeSpeed = 0.0f; + mGStormData.StormOn = false; + mGStormData.stormState = isDone; + for(int i = 0; i < 25; ++i) + stormAlpha[i] = 1.0f; + mRadius = 1.0f; +} + +//--------------------------------------------------------------------------- +Cloud::~Cloud() +{ +} + +//--------------------------------------------------------------------------- +void Cloud::updateCoord() +{ + mBaseOffset += mSpeed*mOffset; + if(mSpeed.x < 0) + mBaseOffset.x -= mCeil(mBaseOffset.x); + else + mBaseOffset.x -= mFloor(mBaseOffset.x); + if(mSpeed.y < 0) + mBaseOffset.y -= mCeil(mBaseOffset.y); + else + mBaseOffset.y -= mFloor(mBaseOffset.y); +} + +//--------------------------------------------------------------------------- +void Cloud::setHeights(F32 cHeight, F32 iHeight, F32 eHeight) +{ + mCenterHeight = cHeight; + mInnerHeight = iHeight; + mEdgeHeight = eHeight; +} + +//--------------------------------------------------------------------------- + +void Cloud::setTexture(GFXTexHandle textHand) +{ + mCloudHandle = textHand; +} + +//--------------------------------------------------------------------------- +void Cloud::setSpeed(const Point2F &speed) +{ + mSpeed = speed; +} + +//--------------------------------------------------------------------------- +void Cloud::setPoints() +{ + S32 x, y; + F32 xyDiff = mRadius/2; + F32 cDis = mRadius*mCenterHeight, + upDis = mRadius*mInnerHeight, + edgeZ = mRadius*mEdgeHeight; + + // We're dealing with a hemisphere so calculate some heights. + F32 zValue[25] = { + edgeZ, edgeZ, edgeZ, edgeZ, edgeZ, + edgeZ, upDis, upDis, upDis, edgeZ, + edgeZ, upDis, cDis, upDis, edgeZ, + edgeZ, upDis, upDis, upDis, edgeZ, + edgeZ, edgeZ, edgeZ, edgeZ, edgeZ + }; + + for(y = 0; y < 5; ++y) + for(x = 0; x < 5; ++x) + mPoints[y*5+x].set(-mRadius+(xyDiff*x),mRadius - (xyDiff*y),zValue[y*5+x]); + + // 0, 4, 20, 24 are the four corners of the grid... + // the goal here is to make the cloud layer more "spherical"? + + /*Point3F vec = (mPoints[5] + ((mPoints[1] - mPoints[5]) * 0.5f)) - mPoints[6]; + mPoints[0] = mPoints[6] + (vec * 2.0f); + + vec = (mPoints[9] + ((mPoints[3] - mPoints[9]) * 0.5f)) - mPoints[8]; + mPoints[4] = mPoints[8] + (vec * 2.0f); + + vec = (mPoints[21] + ((mPoints[15] - mPoints[21]) * 0.5f)) - mPoints[16]; + mPoints[20] = mPoints[16] + (vec * 2.0f); + + vec = (mPoints[23] + ((mPoints[19] - mPoints[23]) * 0.5f)) - mPoints[18]; + mPoints[24] = mPoints[18] + (vec * 2.0f); */ + + calcAlpha(); +} + +//--------------------------------------------------------------------------- +void Cloud::calcAlpha() +{ + for(S32 i = 0; i < 25; ++i) + { + mAlpha[i] = 1.3f - ((mPoints[i] - Point3F(0, 0, mPoints[i].z)).len())/mRadius; + if(mAlpha[i] < 0.4f) + mAlpha[i]=0.0f; + else if(mAlpha[i] > 0.8f) + mAlpha[i] = 1.0f; + } +} + +//--------------------------------------------------------------------------- +void Cloud::render(U32 currentTime, U32 cloudLayer, bool outlineOn, S32 numLayers, PlaneF* planes) +{ +// if(cloudLayer != Con::getIntVariable("onlyCloudLayer", 2)) +// return; + + mGStormData.numCloudLayers = numLayers; + mOffset = 1.0f; + U32 numPoints; + Point3F renderPoints[128]; + Point2F renderTexPoints[128]; + F32 renderAlpha[128]; + F32 renderSAlpha[128]; + + if(mLastTime != 0) + mOffset = (currentTime - mLastTime)/32.0f; + mLastTime=currentTime; + + if(!mCloudHandle || (mGStormData.StormOn && mGStormData.currentCloud < cloudLayer)) + return; + + S32 start=0, i, j, k; + updateCoord(); + for(S32 x = 0; x < 5; x++) + for(S32 y = 0; y < 5; y++) + mTexCoords[y * 5 + x].set ( x * mTextureScale.x + mBaseOffset.x, + y * mTextureScale.y + mBaseOffset.y); + + if(mGStormData.StormOn && mGStormData.currentCloud == cloudLayer) + updateStorm(); + + if(!outlineOn) + { + if ( mCloudSB.isNull() ) + { + GFXStateBlockDesc clouddesc; + clouddesc.samplersDefined = true; + clouddesc.samplers[0] = GFXSamplerStateDesc::getWrapLinear(); + clouddesc.zDefined = true; + clouddesc.zEnable = false; + clouddesc.zWriteEnable = false; + clouddesc.blendDefined = true; + clouddesc.blendEnable = true; + clouddesc.blendSrc = GFXBlendSrcAlpha; + clouddesc.blendDest = GFXBlendInvSrcAlpha; + mCloudSB = GFX->createStateBlock(clouddesc); + } + GFX->setStateBlock(mCloudSB); + GFX->setTexture(0,mCloudHandle); + } + + for(i = 0; i < 4; ++i) + { + start = i * 5; + for(j = 0; j < 4; ++j ) + { + numPoints = 4; + setRenderPoints(renderPoints, renderTexPoints, renderAlpha, renderSAlpha, start); + + for(S32 i = 0; i < 4; ++i) + clipToPlane(renderPoints, renderTexPoints, renderAlpha, renderSAlpha, + numPoints, planes[i]); + + if(numPoints) + { + GFX->setupGenericShaders( GFXDevice::GSModColorTexture ); + PrimBuild::begin(GFXTriangleFan, numPoints); + + for(k = 0; k < numPoints; ++k) + { + PrimBuild::color4f (1.0,1.0,1.0, renderAlpha[k]*renderSAlpha[k]); + + PrimBuild::texCoord2f(renderTexPoints[k].x, renderTexPoints[k].y); + PrimBuild::vertex3f (renderPoints[k].x, renderPoints[k].y, renderPoints[k].z); + } + + PrimBuild::end(); + } + + ++start; + } + } +} + +void Cloud::setRenderPoints(Point3F* renderPoints, Point2F* renderTexPoints, + F32* renderAlpha, F32* renderSAlpha, S32 index) +{ + S32 offset[4] = {0,5,6,1}; + for(S32 x = 0; x < 4; ++x) + { + renderPoints[x].set( + mPoints[index+offset[x]].x, + mPoints[index+offset[x]].y, + mPoints[index+offset[x]].z + ); + renderTexPoints[x].set( + mTexCoords[index+offset[x]].x, + mTexCoords[index+offset[x]].y + ); + + renderAlpha[x] = mAlpha [index+offset[x]]; + renderSAlpha[x] = stormAlpha[index+offset[x]]; + } +} + + +//--------------------------------------------------------------------------- +void Cloud::setTextPer(F32 cloudTextPer) +{ + mTextureScale.set(cloudTextPer / 4.0, cloudTextPer / 4.0); +} + +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +// Storm Code +//--------------------------------------------------------------------------- +//--------------------------------------------------------------------------- +void Cloud::updateStorm() +{ + if(!mGStormData.FadeOut && !mGStormData.FadeIn) { + alphaCenter += (stormUpdate * mOffset); + F32 update, center; + if(mGStormData.stormDir == 'x') { + update = stormUpdate.x; + center = alphaCenter.x; + } + else { + update = stormUpdate.y; + center = alphaCenter.y; + } + + if(mGStormData.stormState == comingIn) { + if((update > 0 && center > 0) || (update < 0 && center < 0)) + mGStormData.FadeIn = true; + } + else + if((update > 0 && center > mRadius*2) || (update < 0 && center < -mRadius*2)) { +// Con::printf("Cloud %d is done.", mGStormData.currentCloud); + mGStormData.StormOn = --mGStormData.currentCloud >= 0; + if(mGStormData.StormOn) { + mGStormData.FadeOut = true; + return; + } + } + } + calcStormAlpha(); +} + +//--------------------------------------------------------------------------- +void Cloud::calcStormAlpha() +{ + if(mGStormData.FadeIn) + { + bool done = true; + for(int i = 0; i < 25; ++i) + { + stormAlpha[i] += (mGStormData.fadeSpeed * mOffset); + if(stormAlpha[i] >= 1.0f) + stormAlpha[i] = 1.0f; + else + done = false; + } + if(done) + { +// Con::printf("Cloud %d is done.", mGStormData.currentCloud); + mGStormData.StormOn = ++mGStormData.currentCloud < mGStormData.numCloudLayers; + mGStormData.FadeIn = false; + } + } + else if(mGStormData.FadeOut) + { + bool done = true; + for(int i = 0; i < 25; ++i) + { + stormAlpha[i] -= (mGStormData.fadeSpeed * mOffset); + if(stormAlpha[i] <= mAlphaSave[i]) + stormAlpha[i] = mAlphaSave[i]; + else + done = false; + } + if(done) + mGStormData.FadeOut = false; + } + else + for(int i = 0; i < 25; ++i) + { + stormAlpha[i] = 1.0f -((Point3F(mPoints[i].x-alphaCenter.x, mPoints[i].y-alphaCenter.y, mPoints[i].z).len())/mRadius); + if(stormAlpha[i] < 0.0f) + stormAlpha[i]=0.0f; + else if(stormAlpha[i] > 1.0f) + stormAlpha[i] = 1.0f; + } +} + +//--------------------------------------------------------------------------- +void Cloud::calcStorm(F32 speed, F32 fadeSpeed) +{ + F32 tempX, tempY; + F32 windSlop = 0.0f; + + if(mSpeed.x != 0) + windSlop = mSpeed.y/mSpeed.x; + + tempX = (mSpeed.x < 0) ? -mSpeed.x : mSpeed.x; + tempY = (mSpeed.y < 0) ? -mSpeed.y : mSpeed.y; + + if(tempX >= tempY) + { + alphaCenter.x =(mSpeed.x < 0) ? mRadius * -2 : mRadius * 2; + alphaCenter.y = windSlop*alphaCenter.x; + + stormUpdate.x = alphaCenter.x > 0.0f ? -speed : speed; + stormUpdate.y = alphaCenter.y > 0.0f ? -speed * windSlop : speed * windSlop; + mGStormData.stormDir = 'x'; + } + else + { + alphaCenter.y = (mSpeed.y < 0) ? mRadius * 2 : mRadius * -2; + alphaCenter.x = windSlop * alphaCenter.y; + +/* if(windSlop != 0) + alphaCenter.x = (1/windSlop)*alphaCenter.y; + else + alphaCenter.x = 0.0f; +*/ + stormUpdate.y = alphaCenter.y > 0.0f ? -speed : speed; + stormUpdate.x = alphaCenter.x > 0.0f ? -speed * (1/windSlop) : speed * (1/windSlop); + + mGStormData.stormDir = 'y'; + } + + mGStormData.fadeSpeed = fadeSpeed; + + for(int i = 0; i < 25; ++i) + { + mAlphaSave[i] = 1.0f - (mPoints[i].len()/mRadius); + if(mAlphaSave[i] < 0.0f) + mAlphaSave[i]=0.0f; + else if(mAlphaSave[i] > 1.0f) + mAlphaSave[i] = 1.0f; + } + if(mGStormData.stormState == goingOut) + alphaCenter.set(0.0f, 0.0f); +} + +//--------------------------------------------------------------------------- +void Cloud::startStorm(SkyState state) +{ + mGStormData.StormOn = true; + mGStormData.stormState = state; + if(state == goingOut) + { + mGStormData.FadeOut= true; + mGStormData.FadeIn = false; + mGStormData.currentCloud = mGStormData.numCloudLayers - 1; + } + else + { + mGStormData.FadeIn = false; + mGStormData.FadeOut= false; + mGStormData.currentCloud = 0; + } +} + +void Cloud::clipToPlane(Point3F* points, Point2F* texPoints, F32* alphaPoints, + F32* sAlphaPoints, U32& rNumPoints, const PlaneF& rPlane) +{ + S32 start = -1; + for (U32 i = 0; i < rNumPoints; i++) + { + if (rPlane.whichSide(points[i]) == PlaneF::Front) + { + start = i; + break; + } + } + + // Nothing was in front of the plane... + if (start == -1) + { + rNumPoints = 0; + return; + } + + U32 numFinalPoints = 0; + Point3F finalPoints[128]; + Point2F finalTexPoints[128]; + F32 finalAlpha[128]; + F32 finalSAlpha[128]; + + U32 baseStart = start; + U32 end = (start + 1) % rNumPoints; + + while (end != baseStart) + { + const Point3F& rStartPoint = points[start]; + const Point3F& rEndPoint = points[end]; + + const Point2F& rStartTexPoint = texPoints[start]; + const Point2F& rEndTexPoint = texPoints[end]; + + PlaneF::Side fSide = rPlane.whichSide(rStartPoint); + PlaneF::Side eSide = rPlane.whichSide(rEndPoint); + + S32 code = fSide * 3 + eSide; + switch (code) { + case 4: // f f + case 3: // f o + case 1: // o f + case 0: // o o + // No Clipping required + + //Alpha + finalAlpha[numFinalPoints] = alphaPoints[start]; + finalSAlpha[numFinalPoints] = sAlphaPoints[start]; + + //Points + finalPoints[numFinalPoints] = points[start]; + finalTexPoints[numFinalPoints++] = texPoints[start]; + + start = end; + end = (end + 1) % rNumPoints; + break; + + + case 2: { // f b + // In this case, we emit the front point, Insert the intersection, + // and advancing to point to first point that is in front or on... + + //Alpha + finalAlpha[numFinalPoints] = alphaPoints[start]; + finalSAlpha[numFinalPoints] = sAlphaPoints[start]; + + //Points + finalPoints[numFinalPoints] = points[start]; + finalTexPoints[numFinalPoints++] = texPoints[start]; + + Point3F vector = rEndPoint - rStartPoint; + F32 t = -(rPlane.distToPlane(rStartPoint) / mDot(rPlane, vector)); + + //Alpha + finalAlpha[numFinalPoints] = alphaPoints[start]+ ((alphaPoints[end] - alphaPoints[start]) * t); + finalSAlpha[numFinalPoints] = sAlphaPoints[start]+ ((sAlphaPoints[end] - sAlphaPoints[start]) * t); + + //Polygon Points + Point3F intersection = rStartPoint + (vector * t); + finalPoints[numFinalPoints] = intersection; + + //Texture Points + Point2F texVec = rEndTexPoint - rStartTexPoint; + + Point2F texIntersection = rStartTexPoint + (texVec * t); + finalTexPoints[numFinalPoints++] = texIntersection; + + U32 endSeek = (end + 1) % rNumPoints; + while (rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + const Point2F& rNewStartTexPoint = texPoints[start]; + const Point2F& rNewEndTexPoint = texPoints[end]; + + vector = rNewEndPoint - rNewStartPoint; + t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + //Alpha + alphaPoints[start] = alphaPoints[start]+ ((alphaPoints[end] - alphaPoints[start]) * t); + sAlphaPoints[start] = sAlphaPoints[start]+ ((sAlphaPoints[end] - sAlphaPoints[start]) * t); + + //Polygon Points + intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + + //Texture Points + texVec = rNewEndTexPoint - rNewStartTexPoint; + + texIntersection = rNewStartTexPoint + (texVec * t); + texPoints[start] = texIntersection; + } + break; + + case -1: {// o b + // In this case, we emit the front point, and advance to point to first + // point that is in front or on... + // + + //Alpha + finalAlpha[numFinalPoints] = alphaPoints[start]; + finalSAlpha[numFinalPoints] = sAlphaPoints[start]; + + //Points + finalPoints[numFinalPoints] = points[start]; + finalTexPoints[numFinalPoints++] = texPoints[start]; + + U32 endSeek = (end + 1) % rNumPoints; + while (rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + const Point2F& rNewStartTexPoint = texPoints[start]; + const Point2F& rNewEndTexPoint = texPoints[end]; + + Point3F vector = rNewEndPoint - rNewStartPoint; + F32 t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + //Alpha + alphaPoints[start] = alphaPoints[start] + ((alphaPoints[end] - alphaPoints[start]) * t); + sAlphaPoints[start] = sAlphaPoints[start] + ((sAlphaPoints[end] - sAlphaPoints[start]) * t); + + //Polygon Points + Point3F intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + + //Texture Points + Point2F texVec = rNewEndTexPoint - rNewStartTexPoint; + + Point2F texIntersection = rNewStartTexPoint + (texVec * t); + texPoints[start] = texIntersection; + } + break; + + case -2: // b f + case -3: // b o + case -4: // b b + // In the algorithm used here, this should never happen... + AssertISV(false, "SGUtil::clipToPlane: error in polygon clipper"); + break; + + default: + AssertFatal(false, "SGUtil::clipToPlane: bad outcode"); + break; + } + } + + // Emit the last point. + + //Alpha + finalAlpha[numFinalPoints] = alphaPoints[start]; + finalSAlpha[numFinalPoints] = sAlphaPoints[start]; + + //Points + finalPoints[numFinalPoints] = points[start]; + finalTexPoints[numFinalPoints++] = texPoints[start]; + AssertFatal(numFinalPoints >= 3, avar("Error, this shouldn't happen! Invalid winding in clipToPlane: %d", numFinalPoints)); + + // Copy the new rWinding, and we're set! + + //Alpha + dMemcpy(alphaPoints, finalAlpha, numFinalPoints * sizeof(F32)); + dMemcpy(sAlphaPoints, finalSAlpha, numFinalPoints * sizeof(F32)); + + //Points + dMemcpy(points, finalPoints, numFinalPoints * sizeof(Point3F)); + dMemcpy(texPoints, finalTexPoints, numFinalPoints * sizeof(Point2F)); + + rNumPoints = numFinalPoints; + AssertISV(rNumPoints <= 128, "MaxWindingPoints exceeded in scenegraph. Fatal error."); +} diff --git a/environment/sky.h b/environment/sky.h new file mode 100644 index 0000000..a3cf198 --- /dev/null +++ b/environment/sky.h @@ -0,0 +1,235 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SKY_H_ +#define _SKY_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _SCENESTATE_H_ +#include "sceneGraph/sceneState.h" +#endif +#ifndef _SCENEGRAPH_H_ +#include "sceneGraph/sceneGraph.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MATERIALLIST_H_ +#include "materials/materialList.h" +#endif +#ifndef _GAMEBASE_H_ +//#include "T3D/gameBase.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif + +#include "gfx/gfxDevice.h" + +#define MAX_NUM_LAYERS 3 +#define MAX_BAN_POINTS 20 + +class SceneGraph; +class SceneState; + +enum SkyState +{ + isDone = 0, + comingIn = 1, + goingOut = 2 +}; + +typedef struct +{ + bool StormOn; + bool FadeIn; + bool FadeOut; + S32 currentCloud; + F32 stormSpeed; + F32 stormDir; + S32 numCloudLayers; + F32 fadeSpeed; + SkyState stormState; +} StormInfo; + +typedef struct +{ + SkyState state; + F32 speed; + F32 time; + F32 fadeSpeed; +} StormCloudData; + +//--------------------------------------------------------------------------- +class Cloud +{ + private: + Point3F mPoints[25]; + Point2F mSpeed; + F32 mCenterHeight, mInnerHeight, mEdgeHeight; + F32 mAlpha[25]; + S32 mDown, mOver; + static F32 mRadius; + U32 mLastTime; + F32 mOffset; + Point2F mBaseOffset, mTexCoords[25], mTextureScale; + GFXTexHandle mCloudHandle; + + Point2F alphaCenter; + Point2F stormUpdate; + F32 stormAlpha[25]; + F32 mAlphaSave[25]; + + GFXStateBlockRef mCloudSB; + + static StormInfo mGStormData; + public: + Cloud(); + ~Cloud(); + void setPoints(); + void setHeights(F32 cHeight, F32 iHeight, F32 eHeight); + void setTexture(GFXTexHandle); + void setSpeed(const Point2F &speed); + void setTextPer(F32 cloudTextPer); + void updateCoord(); + void calcAlpha(); + void render(U32, U32, bool, S32, PlaneF*); + void updateStorm(); + void calcStorm(F32 speed, F32 fadeSpeed); + void calcStormAlpha(); + static void startStorm(SkyState); + static void setRadius(F32 rad) {mRadius = rad;} + void setRenderPoints(Point3F* renderPoints, Point2F* renderTexPoints, F32* renderAlpha, F32* renderSAlpha, S32 index); + void clipToPlane(Point3F* points, Point2F* texPoints, F32* alphaPoints, F32* sAlphaPoints, U32& rNumPoints, const PlaneF& rPlane); +}; + +//-------------------------------------------------------------------------- +class Sky : public SceneObject +{ + typedef SceneObject Parent; + private: + + StormCloudData mStormCloudData; + GFXTexHandle mSkyHandle[6]; + F32 mCloudHeight[MAX_NUM_LAYERS]; + F32 mCloudSpeed[MAX_NUM_LAYERS]; + Cloud mCloudLayer[MAX_NUM_LAYERS]; + F32 mRadius; + Point3F mPoints[10]; + Point2F mTexCoord[4]; + FileName mMaterialListName; + Point3F mSkyBoxPt; + Point3F mTopCenterPt; + Point3F mSpherePt; + ColorI mRealSkyColor; + + MaterialList mMaterialList; + bool mSkyTexturesOn; + bool mRenderBoxBottom; + ColorF mSolidFillColor; + + bool mNoRenderBans; + F32 mBanOffsetHeight; + + S32 mNumCloudLayers; + Point3F mWindVelocity; + + F32 mLastVisDisMod; + + GFXVertexBufferHandle mSkyVB; + + static bool smCloudsOn; + static bool smCloudOutlineOn; + static bool smSkyOn; + static S32 smNumCloudsOn; + + bool mStormCloudsOn; + + bool mSkyGlow; + ColorF mSkyGlowColor; + + GFXStateBlockRef mClearSB; + GFXStateBlockRef mSkyBoxSB; + GFXStateBlockRef mRenderBansSB; + + void calcPoints(); + void loadVBPoints(); + void setupStateBlocks(); + protected: + bool onAdd(); + void onRemove(); + + bool prepRenderImage ( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false); + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ); + void render(SceneState *state); + void renderSkyBox(F32 lowerBanHeight, F32 alphaIn); + void calcBans(F32 *banHeights, Point3F banPoints[][MAX_BAN_POINTS], Point3F *cornerPoints); + void renderBans(const F32 *alphaBan, const F32 *banHeights, const Point3F banPoints[][MAX_BAN_POINTS], const Point3F *cornerPoints, const ColorI& fogColor); + void inspectPostApply(); + void startStorm(); + void setSkyColor(); + void initSkyData(); + bool loadDml(); + void setRenderPoints(Point3F* renderPoints, S32 index); + void calcTexCoords(Point2F* texCoords, Point3F* renderPoints, S32 index, F32 lowerBanHeight); + public: + Point2F mWindDir; + enum NetMaskBits { + InitMask = BIT(0), + StormCloudMask = BIT(1), + WindMask = BIT(2), + StormCloudsOnMask = BIT(3), + SkyGlowMask = BIT(4) + }; + enum Constants { + EnvMapMaterialOffset = 6, + CloudMaterialOffset = 7 + }; + + Sky(); + ~Sky(); + + /// @name Storm management. + /// @{ + void stormCloudsShow(bool); + void stormCloudsOn(S32 state, F32 time); + /// @} + + /// @name Wind velocity + /// @{ + + void setWindVelocity(const Point3F &); + const Point3F &getWindVelocity() const; + /// @} + + /// @name Environment mapping + /// @{ + +// TextureHandle getEnvironmentMap() { return mMaterialList.getMaterial(EnvMapMaterialOffset); } + /// @} + + /// Torque infrastructure + DECLARE_CONOBJECT(Sky); + static void initPersistFields(); + static void consoleInit(); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + void applySkyChanges() + { + inspectPostApply(); + } +}; + + + + +#endif diff --git a/environment/skyBox.cpp b/environment/skyBox.cpp new file mode 100644 index 0000000..89ceaf4 --- /dev/null +++ b/environment/skyBox.cpp @@ -0,0 +1,592 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/skyBox.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxTransformSaver.h" +#include "core/stream/fileStream.h" +#include "core/stream/bitStream.h" +#include "materials/materialManager.h" +#include "materials/materialFeatureTypes.h" +#include "materials/sceneData.h" +#include "T3D/gameFunctions.h" +#include "renderInstance/renderBinManager.h" +#include "materials/processedMaterial.h" +#include "gfx/gfxDebugEvent.h" +#include "math/util/matrixSet.h" + + +IMPLEMENT_CO_NETOBJECT_V1( SkyBox ); + +SkyBox::SkyBox() +{ + mTypeMask |= EnvironmentObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mMatName = ""; + mMatInstance = NULL; + + mIsVBDirty = false; + mDrawBottom = true; + mPrimCount = 0; + mFogBandHeight = 0; + + mMatrixSet = reinterpret_cast(dAligned_malloc(sizeof(MatrixSet), 16)); + constructInPlace(mMatrixSet); +} + +SkyBox::~SkyBox() +{ + dAligned_free(mMatrixSet); + + if( mMatInstance ) + SAFE_DELETE( mMatInstance ); +} + +bool SkyBox::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + setGlobalBounds(); + resetWorldBox(); + + addToScene(); + + if ( isClientObject() ) + { + GFXStateBlockDesc transparent; + transparent.setCullMode( GFXCullNone ); + transparent.setZReadWrite( true, false ); + transparent.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha ); + mFogBandSB = GFX->createStateBlock( transparent ); + + _initRender(); + _updateMaterial(); + } + + return true; +} + +void SkyBox::onRemove() +{ + removeFromScene(); + Parent::onRemove(); +} + +void SkyBox::initPersistFields() +{ + addGroup( "Sky Box" ); + + addField( "material", TypeMaterialName, Offset( mMatName, SkyBox ), + "The name of a cubemap material for the sky box." ); + + addField( "drawBottom", TypeBool, Offset( mDrawBottom, SkyBox ), + "If false the bottom of the skybox is not rendered." ); + + addField( "fogBandHeight", TypeF32, Offset( mFogBandHeight, SkyBox ), + "The height (0-1) of the fog band from the horizon to the top of the SkyBox." ); + + endGroup( "Sky Box" ); + + Parent::initPersistFields(); +} + +void SkyBox::inspectPostApply() +{ + Parent::inspectPostApply(); + _updateMaterial(); +} + +U32 SkyBox::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + stream->write( mMatName ); + stream->writeFlag( mDrawBottom ); + stream->write( mFogBandHeight ); + + return retMask; +} + +void SkyBox::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + String tmpString( "" ); + stream->read( &tmpString ); + if ( !tmpString.equal( mMatName, String::NoCase ) ) + { + mMatName = tmpString; + _updateMaterial(); + } + + bool drawBottom = stream->readFlag(); + F32 bandHeight = 0; + stream->read( &bandHeight ); + + // If this flag has changed + // we need to update the vertex buffer. + if ( drawBottom != mDrawBottom || + bandHeight != mFogBandHeight ) + { + mDrawBottom = drawBottom; + mFogBandHeight = bandHeight; + mIsVBDirty = true; + _initRender(); + } +} + +bool SkyBox::prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState ) +{ + PROFILE_SCOPE( SkyBox_prepRenderImage ); + + if ( state->isShadowPass() || + mVB.isNull() || + mFogBandVB.isNull() || + !mMatInstance ) + return false; + + if ( isLastState( state, stateKey ) ) + return false; + + setLastState(state, stateKey); + + if ( state->isObjectRendered( this ) ) + { + mMatrixSet->setSceneView(GFX->getWorldMatrix()); + mMatrixSet->setSceneProjection(GFX->getProjectionMatrix()); + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &SkyBox::_renderObject ); + ri->type = RenderPassManager::RIT_Sky; + ri->defaultKey = 10; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + return false; +} + +void SkyBox::_renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *mi ) +{ + GFXDEBUGEVENT_SCOPE( SkyBox_RenderObject, ColorF::WHITE ); + + GFXTransformSaver saver; + GFX->setVertexBuffer( mVB ); + + MatrixF worldMat = state->getCameraTransform(); + + SceneGraphData sgData; + sgData.objTrans = worldMat; + + mMatrixSet->restoreSceneViewProjection(); + mMatrixSet->setWorld( worldMat ); + if ( state->isReflectPass() ) + mMatrixSet->setProjection( gClientSceneGraph->getNonClipProjection() ); + + while ( mMatInstance->setupPass( state, sgData ) ) + { + mMatInstance->setTransforms( *mMatrixSet, state ); + mMatInstance->setSceneInfo( state, sgData ); + + GFX->drawPrimitive( GFXTriangleList, 0, mPrimCount ); + + // Draw render band. + if ( mFogBandHeight > 0 ) + { + const FogData &fog = gClientSceneGraph->getFogData(); + if ( mLastFogColor != fog.color ) + { + mLastFogColor = fog.color; + _initRender(); + } + + MatrixF camMat = sgData.objTrans; + camMat.setPosition( Point3F::Zero ); + camMat.inverse(); + + GFX->setStateBlock( mFogBandSB ); + GFX->setWorldMatrix( camMat ); + GFX->setVertexBuffer( mFogBandVB ); + + GFX->setupGenericShaders(); + GFX->drawPrimitive( GFXTriangleList, 0, 16 ); + } + } +} + +void SkyBox::_initRender() +{ + GFXVertexPNTT *tmpVerts = NULL; + + U32 vertCount = 36; + + if ( !mDrawBottom ) + vertCount = 30; + + mPrimCount = vertCount / 3; + + // Create temp vertex pointer + // so we can read from it + // for generating the normals below. + tmpVerts = new GFXVertexPNTT[vertCount]; + + // We don't bother sharing + // vertices here, in order to + // avoid using a primitive buffer. + tmpVerts[0].point.set( -1, -1, 1 ); + tmpVerts[1].point.set( 1, -1, 1 ); + tmpVerts[2].point.set( 1, -1, -1 ); + + tmpVerts[0].texCoord.set( 0, 0 ); + tmpVerts[1].texCoord.set( 1.0f, 0 ); + tmpVerts[2].texCoord.set( 1.0f, 1.0f ); + + tmpVerts[3].point.set( -1, -1, 1 ); + tmpVerts[4].point.set( 1, -1, -1 ); + tmpVerts[5].point.set( -1, -1, -1 ); + + tmpVerts[3].texCoord.set( 0, 0 ); + tmpVerts[4].texCoord.set( 1.0f, 1.0f ); + tmpVerts[5].texCoord.set( 0, 1.0f ); + + tmpVerts[6].point.set( 1, -1, 1 ); + tmpVerts[7].point.set( 1, 1, 1 ); + tmpVerts[8].point.set( 1, 1, -1 ); + + tmpVerts[6].texCoord.set( 0, 0 ); + tmpVerts[7].texCoord.set( 1.0f, 0 ); + tmpVerts[8].texCoord.set( 1.0f, 1.0f ); + + tmpVerts[9].point.set( 1, -1, 1 ); + tmpVerts[10].point.set( 1, 1, -1 ); + tmpVerts[11].point.set( 1, -1, -1 ); + + tmpVerts[9].texCoord.set( 0, 0 ); + tmpVerts[10].texCoord.set( 1.0f, 1.0f ); + tmpVerts[11].texCoord.set( 0, 1.0f ); + + tmpVerts[12].point.set( -1, 1, 1 ); + tmpVerts[13].point.set( -1, -1, 1 ); + tmpVerts[14].point.set( -1, -1, -1 ); + + tmpVerts[12].texCoord.set( 0, 0 ); + tmpVerts[13].texCoord.set( 1.0f, 0 ); + tmpVerts[14].texCoord.set( 1.0f, 1.0f ); + + tmpVerts[15].point.set( -1, 1, 1 ); + tmpVerts[16].point.set( -1, -1, -1 ); + tmpVerts[17].point.set( -1, 1, -1 ); + + tmpVerts[15].texCoord.set( 0, 0 ); + tmpVerts[16].texCoord.set( 1.0f, 1.0f ); + tmpVerts[17].texCoord.set( 1.0f, 0 ); + + tmpVerts[18].point.set( 1, 1, 1 ); + tmpVerts[19].point.set( -1, 1, 1 ); + tmpVerts[20].point.set( -1, 1, -1 ); + + tmpVerts[18].texCoord.set( 0, 0 ); + tmpVerts[19].texCoord.set( 1.0f, 0 ); + tmpVerts[20].texCoord.set( 1.0f, 1.0f ); + + tmpVerts[21].point.set( 1, 1, 1 ); + tmpVerts[22].point.set( -1, 1, -1 ); + tmpVerts[23].point.set( 1, 1, -1 ); + + tmpVerts[21].texCoord.set( 0, 0 ); + tmpVerts[22].texCoord.set( 1.0f, 1.0f ); + tmpVerts[23].texCoord.set( 0, 1.0f ); + + tmpVerts[24].point.set( -1, -1, 1 ); + tmpVerts[25].point.set( -1, 1, 1 ); + tmpVerts[26].point.set( 1, 1, 1 ); + + tmpVerts[24].texCoord.set( 0, 0 ); + tmpVerts[25].texCoord.set( 1.0f, 0 ); + tmpVerts[26].texCoord.set( 1.0f, 1.0f ); + + tmpVerts[27].point.set( -1, -1, 1 ); + tmpVerts[28].point.set( 1, 1, 1 ); + tmpVerts[29].point.set( 1, -1, 1 ); + + tmpVerts[27].texCoord.set( 0, 0 ); + tmpVerts[28].texCoord.set( 1.0f, 1.0f ); + tmpVerts[29].texCoord.set( 0, 1.0f ); + + // Only set up these + // vertices if the SkyBox + // is set to render the bottom face. + if ( mDrawBottom ) + { + tmpVerts[30].point.set( -1, -1, -1 ); + tmpVerts[31].point.set( -1, 1, -1 ); + tmpVerts[32].point.set( 1, 1, -1 ); + + tmpVerts[30].texCoord.set( 0, 0 ); + tmpVerts[31].texCoord.set( 1.0f, 0 ); + tmpVerts[32].texCoord.set( 1.0f, 1.0f ); + + tmpVerts[33].point.set( -1, -1, -1 ); + tmpVerts[34].point.set( 1, 1, -1 ); + tmpVerts[35].point.set( 1, -1, -1 ); + + tmpVerts[33].texCoord.set( 0, 0 ); + tmpVerts[34].texCoord.set( 1.0f, 1.0f ); + tmpVerts[35].texCoord.set( 0, 1.0f ); + } + + VectorF tmp( 0, 0, 0 ); + + for ( U32 i = 0; i < vertCount; i++ ) + { + //tmp = tmpVerts[i].point; + //tmp.normalize(); + //tmpVerts[i].normal.set( tmp ); + + // Note: SkyBox renders with a regular material, which uses the "Reflect Cube" + // feature. + // + // This feature is really designed a cubemap representing a reflection + // on an objects surface and therefore looks up into the cubemap with the + // cubemap-space view vector reflected by the vert normal. + // + // Since we are actually viewing the skybox from "inside" not from + // "outside" this reflection ends up making the cubemap appear upsidown. + // Therefore we set the vert-normals to "zero" so that the reflection + // operation returns the input, unreflected, vector. + + tmpVerts[i].normal.set( Point3F::Zero ); + } + + if ( mVB.isNull() || mIsVBDirty ) + { + mVB.set( GFX, vertCount, GFXBufferTypeStatic ); + mIsVBDirty = false; + } + + GFXVertexPNTT *vertPtr = mVB.lock(); + + dMemcpy( vertPtr, tmpVerts, sizeof ( GFXVertexPNTT ) * vertCount ); + + mVB.unlock(); + + // Clean up temp verts. + delete [] tmpVerts; + + if ( mFogBandVB.isNull() ) + mFogBandVB.set( GFX, 48, GFXBufferTypeStatic ); + + GFXVertexPC *bandVertPtr = mFogBandVB.lock(); + + // Grab the fog color. + ColorI fogColor( mLastFogColor.red * 255, mLastFogColor.green * 255, mLastFogColor.blue * 255 ); + ColorI fogColorAlpha( mLastFogColor.red * 255, mLastFogColor.green * 255, mLastFogColor.blue * 255, 0 ); + + // Upper portion of band geometry. + { + bandVertPtr[0].point.set( -1, -1, mFogBandHeight ); + bandVertPtr[1].point.set( 1, -1, mFogBandHeight ); + bandVertPtr[2].point.set( 1, -1, 0 ); + + bandVertPtr[0].color.set( fogColorAlpha ); + bandVertPtr[1].color.set( fogColorAlpha ); + bandVertPtr[2].color.set( fogColor ); + + bandVertPtr[3].point.set( -1, -1, mFogBandHeight ); + bandVertPtr[4].point.set( 1, -1, 0 ); + bandVertPtr[5].point.set( -1, -1, 0 ); + + bandVertPtr[3].color.set( fogColorAlpha ); + bandVertPtr[4].color.set( fogColor ); + bandVertPtr[5].color.set( fogColor ); + + bandVertPtr[6].point.set( 1, -1, mFogBandHeight ); + bandVertPtr[7].point.set( 1, 1, mFogBandHeight ); + bandVertPtr[8].point.set( 1, 1, 0 ); + + bandVertPtr[6].color.set( fogColorAlpha ); + bandVertPtr[7].color.set( fogColorAlpha ); + bandVertPtr[8].color.set( fogColor ); + + bandVertPtr[9].point.set( 1, -1, mFogBandHeight ); + bandVertPtr[10].point.set( 1, 1, 0 ); + bandVertPtr[11].point.set( 1, -1, 0 ); + + bandVertPtr[9].color.set( fogColorAlpha ); + bandVertPtr[10].color.set( fogColor ); + bandVertPtr[11].color.set( fogColor ); + + bandVertPtr[12].point.set( -1, 1, mFogBandHeight ); + bandVertPtr[13].point.set( -1, -1, mFogBandHeight ); + bandVertPtr[14].point.set( -1, -1, 0 ); + + bandVertPtr[12].color.set( fogColorAlpha ); + bandVertPtr[13].color.set( fogColorAlpha ); + bandVertPtr[14].color.set( fogColor ); + + bandVertPtr[15].point.set( -1, 1, mFogBandHeight ); + bandVertPtr[16].point.set( -1, -1, 0 ); + bandVertPtr[17].point.set( -1, 1, 0 ); + + bandVertPtr[15].color.set( fogColorAlpha ); + bandVertPtr[16].color.set( fogColor ); + bandVertPtr[17].color.set( fogColor ); + + bandVertPtr[18].point.set( 1, 1, mFogBandHeight ); + bandVertPtr[19].point.set( -1, 1, mFogBandHeight ); + bandVertPtr[20].point.set( -1, 1, 0 ); + + bandVertPtr[18].color.set( fogColorAlpha ); + bandVertPtr[19].color.set( fogColorAlpha ); + bandVertPtr[20].color.set( fogColor ); + + bandVertPtr[21].point.set( 1, 1, mFogBandHeight ); + bandVertPtr[22].point.set( -1, 1, 0 ); + bandVertPtr[23].point.set( 1, 1, 0 ); + + bandVertPtr[21].color.set( fogColorAlpha ); + bandVertPtr[22].color.set( fogColor ); + bandVertPtr[23].color.set( fogColor ); + } + + // Lower portion of band geometry. + { + bandVertPtr[24].point.set( -1, -1, 0 ); + bandVertPtr[25].point.set( 1, -1, 0 ); + bandVertPtr[26].point.set( 1, -1, -1 ); + + bandVertPtr[24].color.set( fogColor ); + bandVertPtr[25].color.set( fogColor ); + bandVertPtr[26].color.set( fogColor ); + + bandVertPtr[27].point.set( -1, -1, 0 ); + bandVertPtr[28].point.set( 1, -1, -1 ); + bandVertPtr[29].point.set( -1, -1, -1 ); + + bandVertPtr[27].color.set( fogColor ); + bandVertPtr[28].color.set( fogColor ); + bandVertPtr[29].color.set( fogColor ); + + bandVertPtr[30].point.set( 1, -1, 0 ); + bandVertPtr[31].point.set( 1, 1, 0 ); + bandVertPtr[32].point.set( 1, 1, -1 ); + + bandVertPtr[30].color.set( fogColor ); + bandVertPtr[31].color.set( fogColor ); + bandVertPtr[32].color.set( fogColor ); + + bandVertPtr[33].point.set( 1, -1, 0 ); + bandVertPtr[34].point.set( 1, 1, -1 ); + bandVertPtr[35].point.set( 1, -1, -1 ); + + bandVertPtr[33].color.set( fogColor ); + bandVertPtr[34].color.set( fogColor ); + bandVertPtr[35].color.set( fogColor ); + + bandVertPtr[36].point.set( -1, 1, 0 ); + bandVertPtr[37].point.set( -1, -1, 0 ); + bandVertPtr[38].point.set( -1, -1, -1 ); + + bandVertPtr[36].color.set( fogColor ); + bandVertPtr[37].color.set( fogColor ); + bandVertPtr[38].color.set( fogColor ); + + bandVertPtr[39].point.set( -1, 1, 0 ); + bandVertPtr[40].point.set( -1, -1, -1 ); + bandVertPtr[41].point.set( -1, 1, -1 ); + + bandVertPtr[39].color.set( fogColor ); + bandVertPtr[40].color.set( fogColor ); + bandVertPtr[41].color.set( fogColor ); + + bandVertPtr[42].point.set( 1, 1, 0 ); + bandVertPtr[43].point.set( -1, 1, 0 ); + bandVertPtr[44].point.set( -1, 1, -1 ); + + bandVertPtr[42].color.set( fogColor ); + bandVertPtr[43].color.set( fogColor ); + bandVertPtr[44].color.set( fogColor ); + + bandVertPtr[45].point.set( 1, 1, 0 ); + bandVertPtr[46].point.set( -1, 1, -1 ); + bandVertPtr[47].point.set( 1, 1, -1 ); + + bandVertPtr[45].color.set( fogColor ); + bandVertPtr[46].color.set( fogColor ); + bandVertPtr[47].color.set( fogColor ); + } + + mFogBandVB.unlock(); +} + +void SkyBox::onStaticModified( const char *slotName, const char *newValue ) +{ + Parent::onStaticModified( slotName, newValue ); + + if ( dStricmp( slotName, "material" ) == 0 ) + setMaskBits( 0xFFFFFFFF ); +} + +void SkyBox::_initMaterial() +{ + if ( mMatInstance ) + SAFE_DELETE( mMatInstance ); + + if ( mMaterial ) + mMatInstance = mMaterial->createMatInstance(); + else + mMatInstance = MATMGR->createMatInstance( "WarningMaterial" ); + + // We want to disable culling and z write. + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setZReadWrite( true, false ); + mMatInstance->addStateBlockDesc( desc ); + + // Also disable lighting on the skybox material by default. + FeatureSet features = MATMGR->getDefaultFeatures(); + features.removeFeature( MFT_RTLighting ); + + // Now initialize the material. + mMatInstance->init( features, getGFXVertexFormat() ); +} + +void SkyBox::_updateMaterial() +{ + if ( mMatName.isEmpty() ) + return; + + Material *pMat = NULL; + if ( !Sim::findObject( mMatName, pMat ) ) + Con::printf( "SkyBox::_updateMaterial, failed to find Material of name %s!", mMatName.c_str() ); + else if ( isProperlyAdded() ) + { + mMaterial = pMat; + _initMaterial(); + } +} + +BaseMatInstance* SkyBox::_getMaterialInstance() +{ + if ( !mMaterial || !mMatInstance || mMatInstance->getMaterial() != mMaterial ) + _initMaterial(); + + if ( !mMatInstance ) + return NULL; + + return mMatInstance; +} + +ConsoleMethod( SkyBox, postApply, void, 2, 2, "") +{ + object->inspectPostApply(); +} \ No newline at end of file diff --git a/environment/skyBox.h b/environment/skyBox.h new file mode 100644 index 0000000..77348cf --- /dev/null +++ b/environment/skyBox.h @@ -0,0 +1,107 @@ + +#ifndef _SKYBOX_H_ +#define _SKYBOX_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif + +#ifndef _CUBEMAPDATA_H_ +#include "gfx/sim/cubemapData.h" +#endif + +#ifndef _MATERIALLIST_H_ +#include "materials/materialList.h" +#endif + +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + + +GFXDeclareVertexFormat( GFXSkyVertex ) +{ + Point3F point; + Point3F normal; + GFXVertexColor color; +}; + + +struct SkyMatParams +{ + void init( BaseMatInstance *matInst ) {}; +}; + +class MatrixSet; + +class SkyBox : public SceneObject +{ + typedef SceneObject Parent; + +public: + + SkyBox(); + virtual ~SkyBox(); + + DECLARE_CONOBJECT( SkyBox ); + + // SimObject + void onStaticModified( const char *slotName, const char *newValue ); + + // ConsoleObject + virtual bool onAdd(); + virtual void onRemove(); + static void initPersistFields(); + virtual void inspectPostApply(); + + // NetObject + virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + virtual void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // SceneObject + bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false); + + /// Our render delegate. + void _renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *mi ); + + /// Prepares rendering structures and geometry. + void _initRender(); + +protected: + + // Material + String mMatName; + BaseMatInstance *mMatInstance; + SkyMatParams mMatParamHandle; + + SimObjectPtr mMaterial; + + GFXVertexBufferHandle mVB; + + GFXVertexBufferHandle mFogBandVB; + GFXStateBlockRef mFogBandSB; + ColorF mLastFogColor; + + bool mDrawBottom; + bool mIsVBDirty; + U32 mPrimCount; + + MatrixSet *mMatrixSet; + + F32 mFogBandHeight; + + void _updateMaterial(); + void _initMaterial(); + + BaseMatInstance* _getMaterialInstance(); +}; + +#endif // _SKYBOX_H_ \ No newline at end of file diff --git a/environment/sun.cpp b/environment/sun.cpp new file mode 100644 index 0000000..ce2d03c --- /dev/null +++ b/environment/sun.cpp @@ -0,0 +1,489 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/sun.h" + +#include "gfx/bitmap/gBitmap.h" +#include "math/mathIO.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "math/mathUtils.h" +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "sim/netConnection.h" +#include "environment/timeOfDay.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" + +IMPLEMENT_CO_NETOBJECT_V1(Sun); + +//----------------------------------------------------------------------------- + +Sun::Sun() +{ + mNetFlags.set(Ghostable | ScopeAlways); + mTypeMask = EnvironmentObjectType | LightObjectType; + + mLightColor.set(0.7f, 0.7f, 0.7f); + mLightAmbient.set(0.3f, 0.3f, 0.3f); + mBrightness = 1.0f; + mSunAzimuth = 0.0f; + mSunElevation = 35.0f; + mCastShadows = true; + + mAnimateSun = false; + mTotalTime = 0.0f; + mCurrTime = 0.0f; + mStartAzimuth = 0.0f; + mEndAzimuth = 0.0f; + mStartElevation = 0.0f; + mEndElevation = 0.0f; + + mLight = LightManager::createLightInfo(); + mLight->setType( LightInfo::Vector ); + + mFlareData = NULL; + mFlareState.clear(); + mFlareScale = 1.0f; + + mCoronaEnabled = true; + mCoronaScale = 1.0f; + mCoronaTint.set( 1.0f, 1.0f, 1.0f, 1.0f ); + mCoronaUseLightColor = true; +} + +Sun::~Sun() +{ + SAFE_DELETE( mLight ); +} + +bool Sun::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Register as listener to TimeOfDay update events + TimeOfDay::getTimeOfDayUpdateSignal().notify( this, &Sun::_updateTimeOfDay ); + + // Make this thing have a global bounds so that its + // always returned from spatial light queries. + setGlobalBounds(); + resetWorldBox(); + setRenderTransform( mObjToWorld ); + addToScene(); + + _initCorona(); + + // Update the light parameters. + _conformLights(); + + return true; +} + +void Sun::onRemove() +{ + TimeOfDay::getTimeOfDayUpdateSignal().remove( this, &Sun::_updateTimeOfDay ); + + removeFromScene(); + Parent::onRemove(); +} + +void Sun::initPersistFields() +{ + addGroup( "Orbit" ); + + addField( "azimuth", TypeF32, Offset( mSunAzimuth, Sun ), + "The horizontal angle of the sun measured clockwise from the positive Y world axis." ); + + addField( "elevation", TypeF32, Offset( mSunElevation, Sun ), + "The elevation angle of the sun above or below the horizon." ); + + endGroup( "Orbit" ); + + // We only add the basic lighting options that all lighting + // systems would use... the specific lighting system options + // are injected at runtime by the lighting system itself. + + addGroup( "Lighting" ); + + addField( "color", TypeColorF, Offset( mLightColor, Sun ), "Color shading applied to surfaces in " + "direct contact with light source."); + addField( "ambient", TypeColorF, Offset( mLightAmbient, Sun ), "Color shading applied to surfaces not " + "in direct contact with light source, such as in the shadows or interiors."); + addField( "brightness", TypeF32, Offset( mBrightness, Sun ), "Adjust the Sun's global contrast/intensity"); + addField( "castShadows", TypeBool, Offset( mCastShadows, Sun ), "Enables/disables shadows cast by " + "objects due to Sun light"); + + endGroup( "Lighting" ); + + addGroup( "Corona" ); + + addField( "coronaEnabled", TypeBool, Offset( mCoronaEnabled, Sun ) ); + addField( "coronaTexture", TypeImageFilename, Offset( mCoronaTextureName, Sun ) ); + addField( "coronaScale", TypeF32, Offset( mCoronaScale, Sun ) ); + addField( "coronaTint", TypeColorF, Offset( mCoronaTint, Sun ) ); + addField( "coronaUseLightColor", TypeBool, Offset( mCoronaUseLightColor, Sun ) ); + + endGroup( "Corona" ); + + + addGroup( "Misc" ); + + addField( "flareType", TypeLightFlareDataPtr, Offset( mFlareData, Sun ), "Datablock for the flare and corona produced " + "by the Sun"); + addField( "flareScale", TypeF32, Offset( mFlareScale, Sun ), "Changes the size and intensity of the flare" ); + + endGroup( "Misc" ); + + // Now inject any light manager specific fields. + LightManager::initLightFields(); + + Parent::initPersistFields(); +} + +void Sun::inspectPostApply() +{ + _conformLights(); + setMaskBits(UpdateMask); +} + +U32 Sun::packUpdate(NetConnection *conn, U32 mask, BitStream *stream ) +{ + Parent::packUpdate( conn, mask, stream ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + stream->write( mSunAzimuth ); + stream->write( mSunElevation ); + stream->write( mLightColor ); + stream->write( mLightAmbient ); + stream->write( mBrightness ); + stream->writeFlag( mCastShadows ); + stream->write( mFlareScale ); + + if ( stream->writeFlag( mFlareData ) ) + { + stream->writeRangedU32( mFlareData->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } + + stream->writeFlag( mCoronaEnabled ); + stream->write( mCoronaTextureName ); + stream->write( mCoronaScale ); + stream->write( mCoronaTint ); + stream->writeFlag( mCoronaUseLightColor ); + + mLight->packExtended( stream ); + } + + return 0; +} + +void Sun::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + if ( stream->readFlag() ) // UpdateMask + { + stream->read( &mSunAzimuth ); + stream->read( &mSunElevation ); + stream->read( &mLightColor ); + stream->read( &mLightAmbient ); + stream->read( &mBrightness ); + mCastShadows = stream->readFlag(); + stream->read( &mFlareScale ); + + if ( stream->readFlag() ) + { + SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + LightFlareData *datablock = NULL; + + if ( Sim::findObject( id, datablock ) ) + mFlareData = datablock; + else + { + conn->setLastError( "Sun::unpackUpdate() - invalid LightFlareData!" ); + mFlareData = NULL; + } + } + else + mFlareData = NULL; + + mCoronaEnabled = stream->readFlag(); + stream->read( &mCoronaTextureName ); + stream->read( &mCoronaScale ); + stream->read( &mCoronaTint ); + mCoronaUseLightColor = stream->readFlag(); + + mLight->unpackExtended( stream ); + } + + if ( isProperlyAdded() ) + { + _initCorona(); + _conformLights(); + } +} + +void Sun::submitLights( LightManager *lm, bool staticLighting ) +{ + // The sun is a special light and needs special registration. + lm->setSpecialLight( LightManager::slSunLightType, mLight ); +} + + +void Sun::advanceTime( F32 timeDelta ) +{ + if (mAnimateSun) + { + if (mCurrTime >= mTotalTime) + { + mAnimateSun = false; + mCurrTime = 0.0f; + } + else + { + mCurrTime += timeDelta; + + F32 fract = mCurrTime / mTotalTime; + F32 inverse = 1.0f - fract; + + F32 newAzimuth = mStartAzimuth * inverse + mEndAzimuth * fract; + F32 newElevation = mStartElevation * inverse + mEndElevation * fract; + + if (newAzimuth > 360.0f) + newAzimuth -= 360.0f; + if (newElevation > 360.0f) + newElevation -= 360.0f; + + setAzimuth(newAzimuth); + setElevation(newElevation); + } + } +} + + +bool Sun::prepRenderImage( SceneState *state, const U32 stateKey, const U32, const bool ) +{ + if ( isLastState( state, stateKey ) ) + return false; + + setLastState( state, stateKey ); + + if ( !state->isObjectRendered( this ) || + !(state->isDiffusePass() || state->isReflectPass()) ) + return false; + + // Render instance for Corona effect. + if ( mCoronaEnabled && mCoronaTexture.isValid() ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Sun::_renderCorona ); + ri->type = RenderPassManager::RIT_Sky; + // Render after sky objects and before CloudLayer! + ri->defaultKey = 5; + ri->defaultKey2 = 0; + state->getRenderPass()->addInst( ri ); + } + + // LightFlareData handles rendering flare effects. + if ( mFlareData ) + { + mFlareState.fullBrightness = mBrightness; + mFlareState.scale = mFlareScale; + mFlareState.lightInfo = mLight; + + Point3F lightPos = state->getCameraPosition() - state->getFarPlane() * mLight->getDirection() * 0.9f; + mFlareState.lightMat.identity(); + mFlareState.lightMat.setPosition( lightPos ); + + F32 dist = ( lightPos - state->getCameraPosition() ).len(); + F32 radius = ( dist / ( GFX->getViewport().extent.x / 640.0 ) ) / 2; + radius *= mCoronaScale; + radius = ( radius / dist ) * state->getWorldToScreenScale().y; + + mFlareState.worldRadius = radius; + + mFlareData->prepRender( state, &mFlareState ); + } + + return false; +} + +void Sun::setAzimuth( F32 azimuth ) +{ + mSunAzimuth = azimuth; + _conformLights(); + setMaskBits( UpdateMask ); // TODO: Break out the masks to save bandwidth! +} + +void Sun::setElevation( F32 elevation ) +{ + mSunElevation = elevation; + _conformLights(); + setMaskBits( UpdateMask ); // TODO: Break out the masks to save some space! +} + +void Sun::setColor( const ColorF &color ) +{ + mLightColor = color; + _conformLights(); + setMaskBits( UpdateMask ); // TODO: Break out the masks to save some space! +} + +void Sun::animate( F32 duration, F32 startAzimuth, F32 endAzimuth, F32 startElevation, F32 endElevation ) +{ + mAnimateSun = true; + mCurrTime = 0.0f; + + mTotalTime = duration; + + mStartAzimuth = startAzimuth; + mEndAzimuth = endAzimuth; + mStartElevation = startElevation; + mEndElevation = endElevation; +} + +void Sun::_conformLights() +{ + // Build the light direction from the azimuth and elevation. + F32 yaw = mDegToRad(mClampF(mSunAzimuth,0,359)); + F32 pitch = mDegToRad(mClampF(mSunElevation,-360,+360)); + VectorF lightDirection; + MathUtils::getVectorFromAngles(lightDirection, yaw, pitch); + lightDirection.normalize(); + mLight->setDirection( -lightDirection ); + mLight->setBrightness( mBrightness ); + + // Now make sure the colors are within range. + mLightColor.clamp(); + mLight->setColor( mLightColor ); + mLightAmbient.clamp(); + mLight->setAmbient( mLightAmbient ); + + // Optimization... disable shadows if the ambient and + // directional color are the same. + bool castShadows = mLightColor != mLightAmbient && mCastShadows; + mLight->setCastShadows( castShadows ); +} + +void Sun::_initCorona() +{ + if ( isServerObject() ) + return; + + // Load texture... + + if ( mCoronaTextureName.isNotEmpty() ) + mCoronaTexture.set( mCoronaTextureName, &GFXDefaultStaticDiffuseProfile, "CoronaTexture" ); + + // Make stateblock... + + if ( mCoronaSB.isNull() ) + { + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setAlphaTest( true, GFXCmpGreaterEqual, 1 ); + desc.setZReadWrite( false, false ); + desc.setBlend( true, GFXBlendSrcColor, GFXBlendOne ); + desc.samplersDefined = true; + desc.samplers[0].textureColorOp = GFXTOPModulate; + desc.samplers[0].colorArg1 = GFXTATexture; + desc.samplers[0].colorArg2 = GFXTADiffuse; + desc.samplers[0].alphaOp = GFXTOPModulate; + desc.samplers[0].alphaArg1 = GFXTATexture; + desc.samplers[0].alphaArg2 = GFXTADiffuse; + + mCoronaSB = GFX->createStateBlock(desc); + + desc.setFillModeWireframe(); + mCoronaWireframeSB = GFX->createStateBlock(desc); + } +} + +void Sun::_renderCorona( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + Point3F sunlightPosition = state->getCameraPosition() - mLight->getDirection() * state->getFarPlane() * 0.9f; + + // Calculate Billboard Radius (in world units) to be constant, independent of distance. + // Takes into account distance, viewport size, and specified size in editor + F32 BBRadius = (((sunlightPosition - state->getCameraPosition()).len()) / (GFX->getViewport().extent.x / 640.0)) / 2; + BBRadius *= mCoronaScale; + + GFXTransformSaver saver; + + if ( state->isReflectPass() ) + GFX->setProjectionMatrix( gClientSceneGraph->getNonClipProjection() ); + + GFX->setStateBlock(mCoronaSB); + + // Initialize points with basic info + Point3F points[4]; + points[0] = Point3F(-BBRadius, 0.0, -BBRadius); + points[1] = Point3F( BBRadius, 0.0, -BBRadius); + points[2] = Point3F( BBRadius, 0.0, BBRadius); + points[3] = Point3F(-BBRadius, 0.0, BBRadius); + + // Get info we need to adjust points + MatrixF camView = GFX->getWorldMatrix(); + camView.inverse(); + + // Finalize points + for(int i = 0; i < 4; i++) + { + // align with camera + camView.mulV(points[i]); + // offset + points[i] += sunlightPosition; + } + + // Draw it + + if ( mCoronaUseLightColor ) + PrimBuild::color(mLightColor); + else + PrimBuild::color(mCoronaTint); + + GFX->setTexture(0, mCoronaTexture); + + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::texCoord2f(0, 0); + PrimBuild::vertex3fv(points[0]); + PrimBuild::texCoord2f(1, 0); + PrimBuild::vertex3fv(points[1]); + PrimBuild::texCoord2f(1, 1); + PrimBuild::vertex3fv(points[2]); + PrimBuild::texCoord2f(0, 1); + PrimBuild::vertex3fv(points[3]); + PrimBuild::end(); +} + +void Sun::_updateTimeOfDay( TimeOfDay *timeOfDay, F32 time ) +{ + setElevation( timeOfDay->getElevationDegrees() ); + setAzimuth( timeOfDay->getAzimuthDegrees() ); +} + +ConsoleMethod(Sun, apply, void, 2, 2, "") +{ + object->inspectPostApply(); +} + +ConsoleMethod(Sun, animate, void, 7, 7, "animate( F32 duration, F32 startAzimuth, F32 endAzimuth, F32 startElevation, F32 endElevation )") +{ + F32 duration = dAtof(argv[2]); + F32 startAzimuth = dAtof(argv[3]); + F32 endAzimuth = dAtof(argv[4]); + F32 startElevation = dAtof(argv[5]); + F32 endElevation = dAtof(argv[6]); + + object->animate(duration, startAzimuth, endAzimuth, startElevation, endElevation); +} + diff --git a/environment/sun.h b/environment/sun.h new file mode 100644 index 0000000..46cd41c --- /dev/null +++ b/environment/sun.h @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SUN_H_ +#define _SUN_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _ITICKABLE_H_ +#include "core/iTickable.h" +#endif +#ifndef _LIGHTFLAREDATA_H_ +#include "T3D/lightFlareData.h" +#endif + +class TimeOfDay; + +/// +class Sun : public SceneObject, public ISceneLight, public virtual ITickable +{ + typedef SceneObject Parent; + +protected: + + F32 mSunAzimuth; + + F32 mSunElevation; + + ColorF mLightColor; + + ColorF mLightAmbient; + + F32 mBrightness; + + bool mAnimateSun; + F32 mTotalTime; + F32 mCurrTime; + F32 mStartAzimuth; + F32 mEndAzimuth; + F32 mStartElevation; + F32 mEndElevation; + + bool mCastShadows; + + LightInfo *mLight; + + LightFlareData *mFlareData; + LightFlareState mFlareState; + F32 mFlareScale; + + bool mCoronaEnabled; + String mCoronaTextureName; + GFXTexHandle mCoronaTexture; + F32 mCoronaScale; + ColorF mCoronaTint; + bool mCoronaUseLightColor; + + GFXStateBlockRef mCoronaSB; + GFXStateBlockRef mCoronaWireframeSB; + + void _conformLights(); + void _initCorona(); + void _renderCorona( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + void _updateTimeOfDay( TimeOfDay *timeOfDay, F32 time ); + + enum NetMaskBits + { + UpdateMask = BIT(0) + }; + +public: + + Sun(); + virtual ~Sun(); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + + // ConsoleObject + DECLARE_CONOBJECT(Sun); + static void initPersistFields(); + void inspectPostApply(); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // ISceneLight + virtual void submitLights( LightManager *lm, bool staticLighting ); + virtual LightInfo* getLight() { return mLight; } + + // ITickable + virtual void interpolateTick( F32 delta ) {}; + virtual void processTick() {}; + virtual void advanceTime( F32 timeDelta ); + + // SceneObject + virtual bool prepRenderImage( SceneState *state, const U32 stateKey, const U32, const bool ); + + /// + void setAzimuth( F32 azimuth ); + + /// + void setElevation( F32 elevation ); + + /// + void setColor( const ColorF &color ); + + /// + void animate( F32 duration, F32 startAzimuth, F32 endAzimuth, F32 startElevation, F32 endElevation ); +}; + +#endif // _SUN_H_ diff --git a/environment/timeOfDay.cpp b/environment/timeOfDay.cpp new file mode 100644 index 0000000..49041df --- /dev/null +++ b/environment/timeOfDay.cpp @@ -0,0 +1,522 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/timeOfDay.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "T3D/gameConnection.h" +#include "environment/sun.h" + +//#include "sceneGraph/sceneGraph.h" +//#include "math/mathUtils.h" + +TimeOfDayUpdateSignal TimeOfDay::smTimeOfDayUpdateSignal; + +IMPLEMENT_CO_NETOBJECT_V1(TimeOfDay); + +TimeOfDay::TimeOfDay() + : mElevation( 0.0f ), + mAzimuth( 0.0f ), + mAxisTilt( 23.44f ), // 35 degree tilt + mDayLen( 120.0f ), // 2 minutes + mStartTimeOfDay( 0.5f ), // High noon + mTimeOfDay( 0.0f ), // initialized to StartTimeOfDay in onAdd + mPlay( true ), + mDayScale( 1.0f ), + mNightScale( 1.5f ) +{ + mNetFlags.set( Ghostable | ScopeAlways ); + mTypeMask = EnvironmentObjectType; + + // Sets the sun vector directly overhead for lightmap generation + // The value of mSunVector is grabbed by the terrain lighting stuff. + /* + F32 ele, azi; + ele = azi = TORADIANS(90); + MathUtils::getVectorFromAngles(mSunVector, azi, ele); + */ + mPrevElevation = 0; + mNextElevation = 0; + mAzimuthOverride = 1.0f; + + _initColors(); +} + +TimeOfDay::~TimeOfDay() +{ +} + +bool TimeOfDay::setTimeOfDay( void *obj, const char *data ) +{ + TimeOfDay *tod = static_cast(obj); + tod->setTimeOfDay( dAtof( data ) ); + + return false; +} + +bool TimeOfDay::setPlay( void *obj, const char *data ) +{ + TimeOfDay *tod = static_cast(obj); + tod->setPlay( dAtob( data ) ); + + return false; +} + +bool TimeOfDay::setDayLength( void *obj, const char *data ) +{ + TimeOfDay *tod = static_cast(obj); + F32 length = dAtof( data ); + if( length != 0 ) + tod->setDayLength( length ); + + return false; + +} + +void TimeOfDay::initPersistFields() +{ + addGroup( "TimeOfDay" ); + + addField( "axisTilt", TypeF32, Offset( mAxisTilt, TimeOfDay ), + "The angle in degrees between global equator and tropic." ); + + addProtectedField( "dayLength", TypeF32, Offset( mDayLen, TimeOfDay ), &setDayLength, &defaultProtectedGetFn, + "The length of a virtual day in real world seconds." ); + + addField( "startTime", TypeF32, Offset( mStartTimeOfDay, TimeOfDay ), + "" ); + + addProtectedField( "time", TypeF32, Offset( mTimeOfDay, TimeOfDay ), &setTimeOfDay, &defaultProtectedGetFn, "Current time of day." ); + + addProtectedField( "play", TypeBool, Offset( mPlay, TimeOfDay ), &setPlay, &defaultProtectedGetFn, "True when the TimeOfDay object is operating." ); + + addField( "azimuthOverride", TypeF32, Offset( mAzimuthOverride, TimeOfDay ), "" ); + + addField( "dayScale", TypeF32, Offset( mDayScale, TimeOfDay ), "Scalar applied to time that elapses while the sun is up." ); + + addField( "nightScale", TypeF32, Offset( mNightScale, TimeOfDay ), "Scalar applied to time that elapses while the sun is down." ); + + endGroup( "TimeOfDay" ); + + Parent::initPersistFields(); +} + +void TimeOfDay::consoleInit() +{ + Parent::consoleInit(); + + //addVariable( "$TimeOfDay::currentTime", &TimeOfDay::smCurrentTime ); + //addVariable( "$TimeOfDay::timeScale", TypeF32, &TimeOfDay::smTimeScale ); +} + +void TimeOfDay::inspectPostApply() +{ + _updatePosition(); + setMaskBits( OrbitMask ); +} + +void TimeOfDay::_onGhostAlwaysDone() +{ +} + +bool TimeOfDay::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // The server initializes to the specified starting values. + // The client initializes itself to the server time from + // unpackUpdate. + if ( isServerObject() ) + { + mTimeOfDay = mStartTimeOfDay; + } + + // We don't use a bounds. + setGlobalBounds(); + resetWorldBox(); + addToScene(); + + // Lets receive ghost events so we can resolve + // the sun object. + if ( isClientObject() ) + NetConnection::smGhostAlwaysDone.notify( this, &TimeOfDay::_onGhostAlwaysDone ); + + if ( isServerObject() ) + Con::executef( this, "onAdd" ); + + return true; +} + +void TimeOfDay::onRemove() +{ + if ( isClientObject() ) + NetConnection::smGhostAlwaysDone.remove( this, &TimeOfDay::_onGhostAlwaysDone ); + + removeFromScene(); + Parent::onRemove(); +} + +U32 TimeOfDay::packUpdate(NetConnection *conn, U32 mask, BitStream *stream ) +{ + Parent::packUpdate( conn, mask, stream ); + + if ( stream->writeFlag( mask & OrbitMask ) ) + { + stream->write( mStartTimeOfDay ); + stream->write( mDayLen ); + stream->write( mTimeOfDay ); + stream->write( mAxisTilt ); + stream->write( mAzimuthOverride ); + + stream->write( mDayScale ); + stream->write( mNightScale ); + + stream->writeFlag( mPlay ); + } + + return 0; +} + +void TimeOfDay::unpackUpdate( NetConnection *conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + if ( stream->readFlag() ) // OrbitMask + { + stream->read( &mStartTimeOfDay ); + stream->read( &mDayLen ); + stream->read( &mTimeOfDay ); + stream->read( &mAxisTilt ); + stream->read( &mAzimuthOverride ); + + stream->read( &mDayScale ); + stream->read( &mNightScale ); + + mPlay = stream->readFlag(); + + _updatePosition(); + } +} + +void TimeOfDay::advanceTime( F32 timeDelta ) +{ + if ( !mPlay ) + return; + + F32 elevation = mRadToDeg( mElevation ); + bool daytime = false; + + if ( elevation > 350.0f || ( 0.0f <= elevation && elevation < 190.0f ) ) + { + timeDelta *= mDayScale; + daytime = true; + } + else + { + timeDelta *= mNightScale; + daytime = false; + } + + //Con::printf( "elevation ( %f ), ( %s )", elevation, ( daytime ) ? "day" : "night" ); + + // do time updates + mTimeOfDay += timeDelta / mDayLen; + + // It could be possible for more than a full day to + // pass in a single advance time, so I put this inside a loop + // but timeEvents will not actually be called for the + // skipped day. + while ( mTimeOfDay > 1.0f ) + mTimeOfDay -= 1.0f; + + _updatePosition(); + + if ( isServerObject() ) + _updateTimeEvents(); +} + +void TimeOfDay::_updatePosition() +{ + //// Full azimuth/elevation calculation. + //// calculate sun decline and meridian angle (in radians) + //F32 sunDecline = mSin( M_2PI * mTimeOfYear ) * mDegToRad( mAxisTilt ); + //F32 meridianAngle = mTimeOfDay * M_2PI - mDegToRad( mLongitude ); + + //// calculate the elevation and azimuth (in radians) + //mElevation = _calcElevation( mDegToRad( mLatitude ), sunDecline, meridianAngle ); + //mAzimuth = _calcAzimuth( mDegToRad( mLatitude ), sunDecline, meridianAngle ); + + // Simplified azimuth/elevation calculation. + // calculate sun decline and meridian angle (in radians) + F32 sunDecline = mDegToRad( mAxisTilt ); + F32 meridianAngle = mTimeOfDay * M_2PI; + + mPrevElevation = mNextElevation; + + // calculate the elevation and azimuth (in radians) + mElevation = _calcElevation( 0.0f, sunDecline, meridianAngle ); + mAzimuth = _calcAzimuth( 0.0f, sunDecline, meridianAngle ); + + if ( mFabs( mAzimuthOverride ) ) + { + mElevation = mDegToRad( mTimeOfDay * 360.0f ); + mAzimuth = mAzimuthOverride; + } + + mNextElevation = mElevation; + + // Only the client updates the sun position! + if ( isClientObject() ) + smTimeOfDayUpdateSignal.trigger( this, mTimeOfDay ); +} + +F32 TimeOfDay::_calcElevation( F32 lat, F32 dec, F32 mer ) +{ + return mAsin( mSin(lat) * mSin(dec) + mCos(lat) * mCos(dec) * mCos(mer) ); +} + +F32 TimeOfDay::_calcAzimuth( F32 lat, F32 dec, F32 mer ) +{ + // Add PI to normalize this from the range of -PI/2 to PI/2 to 0 to 2 * PI; + return mAtan2( mSin(mer), mCos(mer) * mSin(lat) - mTan(dec) * mCos(lat) ) + M_PI_F; +} + +void TimeOfDay::_getSunColor( ColorF *outColor ) const +{ + const COLOR_TARGET *ct = NULL; + + F32 ele = mClampF( M_2PI_F - mElevation, 0.0f, M_PI_F ); + F32 phase = -1.0f; + F32 div; + + if (!mColorTargets.size()) + { + outColor->set(1.0f,1.0f,1.0f); + return; + } + + if (mColorTargets.size() == 1) + { + ct = &mColorTargets[0]; + outColor->set(ct->color.red, ct->color.green, ct->color.blue); + return; + } + + //simple check + if ( mColorTargets[0].elevation != 0.0f ) + { + AssertFatal(0, "TimeOfDay::GetColor() - First elevation must be 0.0 radians") + outColor->set(1.0f, 1.0f, 1.0f); + //mBandMod = 1.0f; + //mCurrentBandColor = color; + return; + } + + if ( mColorTargets[mColorTargets.size()-1].elevation != M_PI_F ) + { + AssertFatal(0, "Celestails::GetColor() - Last elevation must be PI") + outColor->set(1.0f, 1.0f, 1.0f); + //mBandMod = 1.0f; + //mCurrentBandColor = color; + return; + } + + //we need to find the phase and interp... also loop back around + U32 count=0; + for (;count < mColorTargets.size() - 1; count++) + { + const COLOR_TARGET *one = &mColorTargets[count]; + const COLOR_TARGET *two = &mColorTargets[count+1]; + + if (ele >= one->elevation && ele <= two->elevation) + { + div = two->elevation - one->elevation; + + //catch bad input divide by zero + if ( mFabs( div ) < 0.01f ) + div = 0.01f; + + phase = (ele - one->elevation) / div; + outColor->interpolate( one->color, two->color, phase ); + + //mCurrentBandColor.interpolate(one->bandColor, two->bandColor, phase); + //mBandMod = one->bandMod * (1.0f - phase) + two->bandMod * phase; + + return; + } + } + + AssertFatal(0,"This isn't supposed to happen"); +} + +void TimeOfDay::_initColors() +{ + // NOTE: The elevation targets represent distances + // from PI/2 radians (strait up). + + ColorF c; + ColorF bc; + + // e is for elevation + F32 e = M_PI_F / 13.0f; // (semicircle in radians)/(number of color target entries); + + // Day + c.set(1.0f,1.0f,1.0f); + _addColorTarget(0, c, 1.0f, c); // High noon at equanox + c.set(.9f,.9f,.9f); + _addColorTarget(e * 1.0f, c, 1.0f, c); + c.set(.9f,.9f,.9f); + _addColorTarget(e * 2.0f, c, 1.0f, c); + c.set(.8f,.75f,.75f); + _addColorTarget(e * 3.0f, c, 1.0f, c); + c.set(.7f,.65f,.65f); + _addColorTarget(e * 4.0f, c, 1.0f, c); + + //Dawn and Dusk (3 entries) + c.set(.7f,.65f,.65f); + bc.set(.8f,.6f,.3f); + _addColorTarget(e * 5.0f, c, 3.0f, bc); + c.set(.65f,.54f,.4f); + bc.set(.75f,.5f,.4f); + _addColorTarget(e * 6.0f, c, 2.75f, bc); + c.set(.55f,.45f,.25f); + bc.set(.65f,.3f,.3f); + _addColorTarget(e * 7.0f, c, 2.5f, bc); + + //NIGHT + c.set(.3f,.3f,.3f); + bc.set(.7f,.4f,.2f); + _addColorTarget(e * 8.0f, c, 1.25f, bc); + c.set(.25f,.25f,.3f); + bc.set(.8f,.3f,.2f); + _addColorTarget(e * 9.0f, c, 1.00f, bc); + c.set(.25f,.25f,.4f); + _addColorTarget(e * 10.0f, c, 1.0f, c); + c.set(.2f,.2f,.35f); + _addColorTarget(e * 11.0f, c, 1.0f, c); + c.set(.15f,.15f,.2f); + _addColorTarget(M_PI_F, c, 1.0f, c); // Midnight at equanox. +} + +void TimeOfDay::_addColorTarget( F32 ele, const ColorF &color, F32 bandMod, const ColorF &bandColor ) +{ + COLOR_TARGET newTarget; + + newTarget.elevation = ele; + newTarget.color = color; + newTarget.bandMod = bandMod; + newTarget.bandColor = bandColor; + + mColorTargets.push_back(newTarget); +} + +int QSORT_CALLBACK cmpTriggerElevation( const void *p1, const void *p2 ) +{ + const TimeOfDayEvent *evnt1 = (const TimeOfDayEvent*)p1; + const TimeOfDayEvent *evnt2 = (const TimeOfDayEvent*)p2; + + if ( evnt1->triggerElevation < evnt2->triggerElevation ) + return -1; + else if ( evnt1->triggerElevation > evnt2->triggerElevation ) + return 1; + else + return 0; +} + +void TimeOfDay::_updateTimeEvents() +{ + // Sort by trigger times here. + //dQsort( mTimeEvents.address(), mTimeEvents.size(), sizeof( TimeOfDayEvent ), cmpTriggerElevation ); + + // Get the prev, next elevation within 0-360 range. + F32 prevElevation = mRadToDeg( mPrevElevation ); + F32 nextElevation = mRadToDeg( mNextElevation ); + + // Walk the list, and fire any + // events whose trigger times + // are equal to the current time + // or lie between the previous and + // current times. + for ( U32 i = 0; i < mTimeEvents.size(); i++ ) + { + const TimeOfDayEvent &timeEvent = mTimeEvents[i]; + + bool fire = false; + + // Elevation just rolled over form 360 to 0 + if ( nextElevation < prevElevation ) + { + if ( nextElevation >= timeEvent.triggerElevation || + prevElevation < timeEvent.triggerElevation ) + { + fire = true; + } + } + // Normal progression, nextElevation is greater than previous + else + { + if ( nextElevation >= timeEvent.triggerElevation && + prevElevation < timeEvent.triggerElevation ) + { + fire = true; + } + } + + if ( fire ) + { + // Call the time event callback. + _onTimeEvent( timeEvent.identifier ); + } + } +} + +void TimeOfDay::addTimeEvent( F32 triggerElevation, const UTF8 *identifier ) +{ + mTimeEvents.increment(); + mTimeEvents.last().triggerElevation = triggerElevation; + mTimeEvents.last().identifier = identifier; +} + +void TimeOfDay::_onTimeEvent( const String &identifier ) +{ + String strCurrentTime = String::ToString( "%g", mTimeOfDay ); + + F32 elevation = mRadToDeg( mElevation ); + while( elevation < 0 ) + elevation += 360.0f; + while( elevation > 360.0f ) + elevation -= 360.0f; + + String strCurrentElevation = String::ToString( "%g", elevation ); + + Con::executef( this, "onTimeEvent", identifier.c_str(), strCurrentTime.c_str(), strCurrentElevation.c_str() ); +} + +ConsoleMethod( TimeOfDay, addTimeOfDayEvent, void, 4, 4, "addTimeOfDayEvent( triggerElevation, identifierString )" ) +{ + object->addTimeEvent( dAtof( argv[2] ), argv[3] ); +} + +ConsoleMethod( TimeOfDay, setTimeOfDay, void, 3, 3, "setTimeOfDay( time )" ) +{ + object->setTimeOfDay( dAtof( argv[2] ) ); +} + +ConsoleMethod( TimeOfDay, setPlay, void, 3, 3, "setPlay( bool )" ) +{ + object->setPlay( dAtob( argv[2] ) ); +} + +ConsoleMethod( TimeOfDay, setDayLength, void, 3, 3, "setDayLength( time )" ) +{ + F32 length = dAtof( argv[2] ); + if( length != 0 ) + object->setDayLength( length ); + else + Con::warnf( "setDayLength( time ): time must not equal zero." ); +} diff --git a/environment/timeOfDay.h b/environment/timeOfDay.h new file mode 100644 index 0000000..e6e474d --- /dev/null +++ b/environment/timeOfDay.h @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + +#ifndef _TIMEOFDAY_H_ +#define _TIMEOFDAY_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _ITICKABLE_H_ +#include "core/iTickable.h" +#endif + +class Sun; +class TimeOfDay; + +struct COLOR_TARGET +{ + F32 elevation; // maximum target elevation + ColorF color; //normalized 0 = 1.0 ... + F32 bandMod; //6 is max + ColorF bandColor; +}; + +typedef Vector COLOR_TARGETS; + +typedef Signal TimeOfDayUpdateSignal; + + +struct TimeOfDayEvent +{ + // The elevation at which + // this event will fire. + F32 triggerElevation; + + // User identifier for the event. + String identifier; +}; + +class TimeOfDay : public SceneObject, public virtual ITickable +{ + typedef SceneObject Parent; + +public: + + static S32 smCurrentTime; + static F32 smTimeScale; // To pause or resume time flow from outside this object, like in the editor. + + TimeOfDay(); + virtual ~TimeOfDay(); + + // ConsoleObject + static void initPersistFields(); + static void consoleInit(); + DECLARE_CONOBJECT( TimeOfDay ); + void inspectPostApply(); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + + // NetObject + U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream ); + void unpackUpdate( NetConnection *conn, BitStream *stream ); + + // ITickable + virtual void interpolateTick( F32 delta ) {} + virtual void processTick() {} + virtual void advanceTime( F32 timeDelta ); + + F32 getAzimuthRads() { return mAzimuth; } + F32 getElevationRads() { return mElevation; } + F32 getAzimuthDegrees() { return mRadToDeg(mAzimuth); } + F32 getElevationDegrees() { return mRadToDeg(mElevation); } + + /* + // Sun position stuff + void UpdateSunPosition(void); + // void UpdateSunPosition(fxSunLight *sunLight); + + + // Scene lighting (Adapted from Joshua Ritter's Day/Night cycle code) + // I changed references to pointers on the basis of principle. ;-) + void EnableLighting(F32 emissiveScale = 1.0); + void DisableLighting(); + F32 GetIntensity() + { return (mCurrentColor.blue + mCurrentColor.green + mCurrentColor.red) / 3; } + */ + static TimeOfDayUpdateSignal& getTimeOfDayUpdateSignal() { return smTimeOfDayUpdateSignal; } + void getSunColor( ColorF *outColor ) const { _getSunColor( outColor ); } + + void addTimeEvent( F32 triggerElevation, const UTF8 *identifier ); + + void setTimeOfDay( F32 time ) { mTimeOfDay = time; setMaskBits( OrbitMask ); } + void setPlay( bool play ) { mPlay = play; setMaskBits( OrbitMask ); } + void setDayLength( F32 length ) { mDayLen = length; setMaskBits( OrbitMask ); } + +protected: + + Vector mTimeEvents; + + void _updateTimeEvents(); + void _onTimeEvent( const String &identifier ); + + static TimeOfDayUpdateSignal smTimeOfDayUpdateSignal; + + enum NetMaskBits + { + OrbitMask = BIT(0), + }; + + void _updatePosition(); + + void _onGhostAlwaysDone(); + + F32 _calcElevation( F32 lat, F32 dec, F32 mer ); + + F32 _calcAzimuth( F32 lat, F32 dec, F32 mer ); + + /// Adds all of our target colors to our COLOR_TARGETS. + void _initColors(); + + /// Adds a color target to our set of targets. + /// + /// @param ele [in] target sun elevation. + /// @param color [in] target color. + /// @param bandMod [in] + /// @param bandColor [in] + void _addColorTarget( F32 ele, const ColorF &color, F32 bandMod, const ColorF &bandColor ); + + // Grab our sun and sky colors based upon sun elevation. + void _getSunColor( ColorF *outColor ) const; + + static bool setTimeOfDay( void *obj, const char *data ); + static bool setPlay( void *obj, const char *data ); + static bool setDayLength( void *obj, const char *data ); + + /* + // Get a pointer to the sun's light object + Sun* GetSunObject(); + // return number between 0 and 1 representing color variance + F32 getColorVariance(); + */ + + // Date tracking stuff + F32 mStartTimeOfDay; ///< The time of day this object begins at. + F32 mDayLen; ///< length of day in real world seconds. + F32 mPrevElevation; ///< The 0-360 elevation for the previous update. + F32 mNextElevation; ///< The 0-360 elevation for the next update. + F32 mTimeOfDay; ///< The zero to one time of day where zero is the start of a day and one is the end. + + F32 mAzimuthOverride; ///< Used to specify an azimuth that will stay constant throughout the day cycle. + + // Global positioning stuff + F32 mAxisTilt; // angle between global equator and tropic + + F32 mAzimuth; // Angle from true north of celestial object in radians + F32 mElevation; // Angle from horizon of celestial object in radians + + VectorF mZenithDirection; // The direction of celestial object at the zenith of its orbit. + + // Scalar applied to time that elapses while the sun is up. + F32 mDayScale; + // Scalar applied to time that elapses while the sun is down. + F32 mNightScale; + + // color management + COLOR_TARGETS mColorTargets; + + /* + ColorF mCurrentColor; + F32 mBandMod; + ColorF mCurrentBandColor; + + // PersistFields preparation + bool mConvertedToRads; + */ + + // Debugging stuff that probably needs to be removed eventaully + bool mPlay; +}; + + +#endif // _TIMEOFDAY_H_ \ No newline at end of file diff --git a/environment/waterBlock.cpp b/environment/waterBlock.cpp new file mode 100644 index 0000000..b6f81f5 --- /dev/null +++ b/environment/waterBlock.cpp @@ -0,0 +1,678 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/waterBlock.h" + +#include "core/util/safeDelete.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightInfo.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" +#include "console/consoleTypes.h" +#include "gui/3d/guiTSControl.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxOcclusionQuery.h" +#include "renderInstance/renderPassManager.h" +#include "sim/netConnection.h" +#include "sceneGraph/reflectionManager.h" +#include "ts/tsShapeInstance.h" +#include "postFx/postEffect.h" +#include "math/util/matrixSet.h" + +IMPLEMENT_CO_NETOBJECT_V1(WaterBlock); + +WaterBlock::WaterBlock() +{ + mGridElementSize = 5.0f; + mObjScale.set( 100.0f, 100.0f, 10.0f ); + + mNetFlags.set(Ghostable | ScopeAlways); + + mObjBox.minExtents.set( -0.5f, -0.5f, -0.5f ); + mObjBox.maxExtents.set( 0.5f, 0.5f, 0.5f ); + + mElapsedTime = 0.0f; + mGenerateVB = true; +} + +WaterBlock::~WaterBlock() +{ +} + +bool WaterBlock::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + resetWorldBox(); + addToScene(); + + return true; +} + +void WaterBlock::onRemove() +{ + clearVertBuffers(); + + removeFromScene(); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- +// packUpdate +//----------------------------------------------------------------------------- +U32 WaterBlock::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + stream->write( mGridElementSize ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + // This is set to allow the user to modify the size of the water dynamically + // in the editor + mathWrite( *stream, mObjScale ); + stream->writeAffineTransform( mObjToWorld ); + } + + return retMask; +} + +//----------------------------------------------------------------------------- +// unpackUpdate +//----------------------------------------------------------------------------- +void WaterBlock::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + F32 gridSize = mGridElementSize; + stream->read( &mGridElementSize ); + if ( gridSize != mGridElementSize ) + mGenerateVB = true; + + if( stream->readFlag() ) // UpdateMask + { + Point3F scale; + mathRead( *stream, &scale ); + if ( isProperlyAdded() && scale != mObjScale ) + mGenerateVB = true; + mObjScale = scale; + + stream->readAffineTransform( &mObjToWorld ); + } +} + +//----------------------------------------------------------------------------- +// Setup vertex and index buffers +//----------------------------------------------------------------------------- +void WaterBlock::setupVBIB() +{ + clearVertBuffers(); + + const U32 maxIndexedVerts = 65536; // max number of indexed verts with U16 size indices + + if( mObjScale.x < mGridElementSize || + mObjScale.y < mGridElementSize ) + { + F32 oldGridSize = mGridElementSize; + mGridElementSize = getMin(mObjScale.x, mObjScale.y); + logWarning("gridElementSize %g is larger than scale (%g, %g), clamping gridElementSize to %g", + oldGridSize, mObjScale.x, mObjScale.y, mGridElementSize); + } + + Point3F div = getScale() / mGridElementSize; + // Add one to width and height for the edge. + mWidth = (U32)mCeil(div.x) + 1; + mHeight = (U32)mCeil(div.y) + 1; + + // figure out how many blocks are needed and their size + U32 maxBlockRows = maxIndexedVerts / mWidth; + U32 rowOffset = 0; + + while( (rowOffset+1) < mHeight ) + { + U32 numRows = mHeight - rowOffset; + if( numRows == 1 ) numRows++; + if( numRows > maxBlockRows ) + { + numRows = maxBlockRows; + } + + setupVertexBlock( mWidth, numRows, rowOffset ); + setupPrimitiveBlock( mWidth, numRows ); + + rowOffset += numRows - 1; + } + +} + +//----------------------------------------------------------------------------- +// Set up a block of vertices - the width is always the width of the entire +// waterBlock, so this is a block of full rows. +//----------------------------------------------------------------------------- +void WaterBlock::setupVertexBlock( U32 width, U32 height, U32 rowOffset ) +{ + Point3F pos = getPosition(); + RayInfo rInfo; + VectorF sunVector(-0.61f, 0.354f, 0.707f); + + LightManager* lm = gClientSceneGraph->getLightManager(); + if ( lm ) + { + LightInfo* linfo = lm->getSpecialLight( LightManager::slSunLightType ); + if ( linfo ) + sunVector = linfo->getDirection(); + } + + sunVector.normalize(); + + U32 numVerts = width * height; + + GFXWaterVertex *verts = new GFXWaterVertex[ numVerts ]; + ColorI waterColor(31, 56, 64, 127); + GFXVertexColor vertCol(waterColor); + + U32 index = 0; + for( U32 i=0; ipoint.x = vertX; + vert->point.y = vertY; + vert->point.z = 0.0; + vert->color = vertCol; + vert->normal.set(0,0,1); + vert->undulateData.set( vertX, vertY ); + vert->horizonFactor.set( 0, 0, 0, 0 ); + + // Calculate the water depth + + /* + vert->depthData.set( 0.0f, 0.0f ); + + Point3F start, end; + Point3F worldPoint = vert->point + pos; + + start.x = end.x = worldPoint.x; + start.y = end.y = worldPoint.y; + start.z = -2000; // Really high, might be over kill + end.z = 2000; // really low, might be overkill + + // Cast a ray to see how deep the water is. We are + // currently just testing for terrain and atlas + // objects, but potentially any object that responds + // to a ray cast could detected. + if(gClientContainer.castRay(start, end, + //StaticObjectType | + InteriorObjectType | + //ShapeBaseObjectType | + //StaticShapeObjectType | + //ItemObjectType | + //StaticTSObjectType | + TerrainObjectType + , &rInfo)) + { + F32 depth = -(rInfo.point.z - pos.z); + + if(depth <= 0.0f) + { + depth = 1.0f; + } + else + { + depth = depth / mVisibilityDepth; + if(depth > 1.0f) + { + depth = 1.0f; + } + + depth = 1.0f - depth; + } + + vert->depthData.x = depth; + } + else + { + vert->depthData.x = 0.0f; + } + + // Cast a ray to do some AO-style shadowing. + F32 &shadow = vert->depthData.y; + + if(gClientContainer.castRay(worldPoint, worldPoint + sunVector * 9000.f, + //StaticObjectType | + InteriorObjectType | + //ShapeBaseObjectType | + //StaticShapeObjectType | + //ItemObjectType | + //StaticTSObjectType | + TerrainObjectType + , &rInfo)) + { + shadow = 0.f; + } + else + { + shadow = 1.f; + } + */ + } + } + + // copy to vertex buffer + GFXVertexBufferHandle * vertBuff = new GFXVertexBufferHandle ; + + vertBuff->set( GFX, numVerts, GFXBufferTypeStatic ); + GFXWaterVertex *vbVerts = vertBuff->lock(); + dMemcpy( vbVerts, verts, sizeof(GFXWaterVertex) * numVerts ); + vertBuff->unlock(); + mVertBuffList.push_back( vertBuff ); + + + delete [] verts; + +} + +//----------------------------------------------------------------------------- +// Set up a block of indices to match the block of vertices. The width is +// always the width of the entire waterBlock, so this is a block of full rows. +//----------------------------------------------------------------------------- +void WaterBlock::setupPrimitiveBlock( U32 width, U32 height ) +{ + // setup vertex / primitive buffers + U32 numIndices = (width-1) * (height-1) * 6; + U16 *indices = new U16[ numIndices ]; + U32 numVerts = width * height; + + // This uses indexed triangle lists instead of strips, but it shouldn't be + // significantly slower if the indices cache well. + + // Rough diagram of the index order + // 0----2----+ ... + // | / | | + // |/ | | + // 1----3----+ ... + // | | | + // | | | + // +----+----+ ... + + U32 index = 0; + for( U32 i=0; i<(height-1); i++ ) + { + for( U32 j=0; j<(width-1); j++, index+=6 ) + { + // Process one quad at a time. Note it will re-use the same indices from + // previous quad, thus optimizing vert cache. Cache will run out at + // end of each row with this implementation however. + indices[index+0] = (i) * mWidth + j; // 0 + indices[index+1] = (i+1) * mWidth + j; // 1 + indices[index+2] = i * mWidth + j+1; // 2 + indices[index+3] = (i+1) * mWidth + j; // 1 + indices[index+4] = (i+1) * mWidth + j+1; // 3 + indices[index+5] = i * mWidth + j+1; // 2 + } + + } + + GFXPrimitiveBufferHandle *indexBuff = new GFXPrimitiveBufferHandle; + + GFXPrimitive pInfo; + pInfo.type = GFXTriangleList; + pInfo.numPrimitives = numIndices / 3; + pInfo.startIndex = 0; + pInfo.minIndex = 0; + pInfo.numVertices = numVerts; + + U16 *ibIndices; + GFXPrimitive *piInput; + indexBuff->set( GFX, numIndices, 1, GFXBufferTypeStatic ); + indexBuff->lock( &ibIndices, &piInput ); + dMemcpy( ibIndices, indices, numIndices * sizeof(U16) ); + dMemcpy( piInput, &pInfo, sizeof(GFXPrimitive) ); + indexBuff->unlock(); + mPrimBuffList.push_back( indexBuff ); + + + delete [] indices; +} + +//------------------------------------------------------------------------------ +// Setup scenegraph data structure for materials +//------------------------------------------------------------------------------ +SceneGraphData WaterBlock::setupSceneGraphInfo( SceneState *state ) +{ + SceneGraphData sgData; + + LightManager* lm = gClientSceneGraph->getLightManager(); + sgData.lights[0] = lm->getSpecialLight( LightManager::slSunLightType ); + + // fill in water's transform + sgData.objTrans = getRenderTransform(); + + // fog + sgData.setFogParams( gClientSceneGraph->getFogData() ); + + // misc + sgData.backBuffTex = REFLECTMGR->getRefractTex(); + sgData.reflectTex = mPlaneReflector.reflectTex; + sgData.wireframe = GFXDevice::getWireframe() || smWireframe; + + return sgData; +} + +//----------------------------------------------------------------------------- +// set shader parameters +//----------------------------------------------------------------------------- +void WaterBlock::setShaderParams( SceneState *state, BaseMatInstance *mat, const WaterMatParams ¶mHandles) +{ + // Set variables that will be assigned to shader consts within WaterCommon + // before calling Parent::setShaderParams + + mUndulateMaxDist = F32_MAX; + + Parent::setShaderParams( state, mat, paramHandles ); + + // Now set the rest of the shader consts that are either unique to this + // class or that WaterObject leaves to us to handle... + + MaterialParameters* matParams = mat->getMaterialParameters(); + + // set vertex shader constants + //----------------------------------- + + MatrixF modelMat( getRenderTransform() ); + matParams->set(paramHandles.mModelMatSC, modelMat, GFXSCT_Float3x3); + matParams->set(paramHandles.mGridElementSizeSC, (F32)mGridElementSize); + + // set pixel shader constants + //----------------------------------- + + ColorF c( mWaterFogData.color ); + matParams->set( paramHandles.mBaseColorSC, c ); + + //Point4F reflectParams( mPlaneReflector.position.z, mReflectMinDist, mReflectMaxDist, mPlaneReflector.enabled ? 0.0f : 1.0f ); + Point4F reflectParams( mWaterPos.z, 0.0f, 1000.0f, mPlaneReflector.isEnabled() ? 0.0f : 1.0f ); + matParams->set( paramHandles.mReflectParamsSC, reflectParams ); + + VectorF reflectNorm = mReflectNormalUp ? VectorF(0,0,1) : static_cast(mPlaneReflector.refplane); + matParams->set(paramHandles.mReflectNormalSC, reflectNorm ); +} + +void WaterBlock::innerRender( SceneState *state ) +{ + GFXDEBUGEVENT_SCOPE( WaterBlock_innerRender, ColorI( 255, 0, 0 ) ); + + if ( mGenerateVB ) + { + setupVBIB(); + mGenerateVB = false; + } + + // Setup SceneGraphData + SceneGraphData sgData = setupSceneGraphInfo( state ); + const Point3F &camPosition = state->getCameraPosition(); + + // set the material + + S32 matIdx = getMaterialIndex( camPosition ); + + if ( !initMaterial( matIdx ) ) + return; + + BaseMatInstance *mat = mMatInstances[matIdx]; + WaterMatParams matParams = mMatParamHandles[matIdx]; + + // render the geometry + if ( mat ) + { + // setup proj/world transform + mMatrixSet->setWorld(getRenderTransform()); + mMatrixSet->restoreSceneViewProjection(); + + setShaderParams( state, mat, matParams ); + + while ( mat->setupPass( state, sgData ) ) + { + mat->setSceneInfo(state, sgData); + mat->setTransforms(*mMatrixSet, state); + setCustomTextures( matIdx, mat->getCurPass(), matParams ); + + for ( U32 i = 0; i < mVertBuffList.size(); i++ ) + { + GFX->setVertexBuffer( *mVertBuffList[i] ); + GFXPrimitiveBuffer *primBuff = *mPrimBuffList[i]; + GFX->setPrimitiveBuffer( primBuff ); + GFX->drawPrimitives(); + } + } + } +} + +bool WaterBlock::setGridSizeProperty(void* obj, const char* data) +{ + WaterBlock* object = static_cast(obj); + F32 gridSize = dAtof(data); + + Point3F scale = object->getScale(); + + if(gridSize < 0.001f) + { + object->logWarning("gridSize cannot be <= 0, clamping to scale"); + gridSize = getMin(scale.x, scale.y); + } + + if(gridSize > scale.x || gridSize > scale.y) + { + object->logWarning("gridSize cannot be > scale. Your scale is (%g, %g) and your gridsize is %g", + scale.x, scale.y, gridSize); + gridSize = getMin(scale.x, scale.y); + } + + object->mGridElementSize = gridSize; + + // This is a hack so the console system doesn't go in and set our variable + // again, after we've already set it (possibly with a different value...) + return false; +} + +//----------------------------------------------------------------------------- +// initPersistFields +//----------------------------------------------------------------------------- +void WaterBlock::initPersistFields() +{ + addGroup( "WaterBlock" ); + addProtectedField( "gridElementSize", TypeF32, Offset( mGridElementSize, WaterBlock ), + &setGridSizeProperty, &defaultProtectedGetFn, "Spacing between vertices in the WaterBlock mesh" ); + addProtectedField( "gridSize", TypeF32, Offset( mGridElementSize, WaterBlock ), + &setGridSizeProperty, &defaultProtectedGetFn, "Duplicate of gridElementSize for backwards compatility" ); + endGroup( "WaterBlock" ); + + Parent::initPersistFields(); +} + +bool WaterBlock::isUnderwater( const Point3F &pnt ) +{ + // Transform point into object space so we can test if it is within + // the WaterBlock's object box, include rotation/scale. + + Point3F objPnt = pnt; + mWorldToObj.mulP( pnt, &objPnt ); + + objPnt.z -= 0.1f; + + Box3F testBox = mObjBox; + testBox.scale( mObjScale ); + + // We already tested if below the surface plane, + // so clamping the z height of the box is not really necessary. + testBox.maxExtents.z = testBox.getCenter().z; + + if ( testBox.isContained( objPnt ) ) + return true; + + return false; +} + +void WaterBlock::clearVertBuffers() +{ + for( U32 i=0; igetFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &ortho ); + + F32 FOV = GameGetCameraFov(); + Point2I size = GFX->getVideoMode().resolution; + +// GFX->setFrustum( FOV, size.x/F32(size.y), nearPlane + 0.010, farPlane + 10.0 ); + +// note - will have to re-calc left, right, top, bottom if the above technique +// doesn't work through portals +// GFX->setFrustum( left, right, bottom, top, nearPlane + 0.001, farPlane ); + + +} +*/ + +bool WaterBlock::castRay( const Point3F &start, const Point3F &end, RayInfo *info ) +{ + // Simply look for the hit on the water plane + // and ignore any future issues with waves, etc. + const Point3F norm(0,0,1); + PlaneF plane( Point3F::Zero, norm ); + + F32 hit = plane.intersect( start, end ); + if ( hit < 0.0f || hit > 1.0f ) + return false; + + info->t = hit; + info->object = this; + info->point = start + ( ( end - start ) * hit ); + info->normal = norm; + info->material = mMatInstances[WaterMat]; + + return true; +} + +F32 WaterBlock::getWaterCoverage( const Box3F &testBox ) const +{ + Box3F wbox = getWorldBox(); + wbox.maxExtents.z = wbox.getCenter().z; + + F32 coverage = 0.0f; + + if ( wbox.isOverlapped(testBox) ) + { + if (wbox.maxExtents.z < testBox.maxExtents.z) + coverage = (wbox.maxExtents.z - testBox.minExtents.z) / (testBox.maxExtents.z - testBox.minExtents.z); + else + coverage = 1.0f; + } + + return coverage; +} + +F32 WaterBlock::getSurfaceHeight( const Point2F &pos ) const +{ + if ( !mWorldBox.isContained( pos ) ) + return -1.0f; + + return getPosition().z; +} + +void WaterBlock::_getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos ) +{ + outPos = getPosition(); + if ( mReflectNormalUp ) + outPlane.set( outPos, Point3F(0,0,1) ); + else + { + Point3F normal; + getRenderTransform().getColumn( 2, &normal ); + outPlane.set( outPos, normal ); + } +} \ No newline at end of file diff --git a/environment/waterBlock.h b/environment/waterBlock.h new file mode 100644 index 0000000..d5e3753 --- /dev/null +++ b/environment/waterBlock.h @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WATERBLOCK_H_ +#define _WATERBLOCK_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _SCENEDATA_H_ +#include "materials/sceneData.h" +#endif +#ifndef _MATINSTANCE_H_ +#include "materials/matInstance.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _WATEROBJECT_H_ +#include "waterObject.h" +#endif + +class AudioEnvironment; + +//***************************************************************************** +// WaterBlock +//***************************************************************************** +class WaterBlock : public WaterObject +{ + typedef WaterObject Parent; + +public: + + // LEGACY support + enum EWaterType + { + eWater = 0, + eOceanWater = 1, + eRiverWater = 2, + eStagnantWater = 3, + eLava = 4, + eHotLava = 5, + eCrustyLava = 6, + eQuicksand = 7, + }; + +private: + + enum MaskBits { + UpdateMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + // vertex / index buffers + Vector< GFXVertexBufferHandle* > mVertBuffList; + Vector mPrimBuffList; + GFXVertexBufferHandle mRadialVertBuff; + GFXPrimitiveBufferHandle mRadialPrimBuff; + + // misc + F32 mGridElementSize; + U32 mWidth; + U32 mHeight; + F32 mElapsedTime; + GFXTexHandle mBumpTex; + bool mGenerateVB; + + // reflect plane + //ReflectPlane mReflectPlane; + //Point3F mReflectPlaneWorldPos; + + GFXTexHandle mReflectTex; + + // Stateblocks + GFXStateBlockRef mUnderwaterSB; + + void setupVertexBlock( U32 width, U32 height, U32 rowOffset ); + void setupPrimitiveBlock( U32 width, U32 height ); + void setMultiPassProjection(); + void clearVertBuffers(); + + static bool setGridSizeProperty(void* obj, const char* data); +protected: + + //------------------------------------------------------- + // Standard engine functions + //------------------------------------------------------- + bool onAdd(); + void onRemove(); + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + +public: + WaterBlock(); + virtual ~WaterBlock(); + + DECLARE_CONOBJECT(WaterBlock); + + static void initPersistFields(); + void onStaticModified( const char* slotName, const char*newValue = NULL ); + virtual void inspectPostApply(); + virtual void setTransform( const MatrixF & mat ); + virtual void setScale( const Point3F &scale ); + + // WaterObject + virtual F32 getWaterCoverage( const Box3F &worldBox ) const; + virtual F32 getSurfaceHeight( const Point2F &pos ) const; + virtual bool isUnderwater( const Point3F &pnt ); + + // WaterBlock + bool updateReflection( bool enabled ); + bool isPointSubmerged ( const Point3F &pos, bool worldSpace = true ) const{ return true; } + AudioEnvironment * getAudioEnvironment(){ return NULL; } + +protected: + + // WaterObject + virtual void setShaderParams( SceneState *state, BaseMatInstance *mat, const WaterMatParams ¶mHandles ); + virtual SceneGraphData setupSceneGraphInfo( SceneState *state ); + virtual void setupVBIB(); + virtual void innerRender( SceneState *state ); + virtual void _getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos ); +}; + +#endif // _WATERBLOCK_H_ diff --git a/environment/waterObject.cpp b/environment/waterObject.cpp new file mode 100644 index 0000000..72228ab --- /dev/null +++ b/environment/waterObject.cpp @@ -0,0 +1,926 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/waterObject.h" + +#include "console/consoleTypes.h" +#include "materials/materialParameters.h" +#include "materials/baseMatInstance.h" +#include "materials/materialManager.h" +#include "materials/customMaterialDefinition.h" +#include "materials/sceneData.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/reflectionManager.h" +#include "sceneGraph/sceneState.h" +#include "lighting/lightInfo.h" +#include "math/mathIO.h" +#include "postFx/postEffect.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" +#include "gfx/gfxOcclusionQuery.h" +#include "gfx/sim/cubemapData.h" +#include "math/util/matrixSet.h" + +GFXImplementVertexFormat( GFXWaterVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float4, 1 ); +} + +void WaterMatParams::clear() +{ + mRippleDirSC = NULL; + mRippleTexScaleSC = NULL; + mRippleSpeedSC = NULL; + mRippleMagnitudeSC = NULL; + mWaveDirSC = NULL; + mWaveDataSC = NULL; + mReflectTexSizeSC = NULL; + mBaseColorSC = NULL; + mMiscParamsSC = NULL; + mReflectParamsSC = NULL; + mReflectNormalSC = NULL; + mHorizonPositionSC = NULL; + mFogParamsSC = NULL; + mMoreFogParamsSC = NULL; + mFarPlaneDistSC = NULL; + mWetnessParamsSC = NULL; + mDistortionParamsSC = NULL; + mUndulateMaxDistSC = NULL; + mAmbientColorSC = NULL; + mFoamParamsSC = NULL; + mFoamColorModulateSC = NULL; + mGridElementSizeSC = NULL; + mElapsedTimeSC = NULL; + mFoamSamplerSC = NULL; + mRippleSamplerSC = NULL; + mCubemapSamplerSC = NULL; +} + +void WaterMatParams::init( BaseMatInstance* matInst ) +{ + clear(); + + mRippleDirSC = matInst->getMaterialParameterHandle( "$rippleDir" ); + mRippleTexScaleSC = matInst->getMaterialParameterHandle( "$rippleTexScale" ); + mRippleSpeedSC = matInst->getMaterialParameterHandle( "$rippleSpeed" ); + mRippleMagnitudeSC = matInst->getMaterialParameterHandle( "$rippleMagnitude" ); + mWaveDirSC = matInst->getMaterialParameterHandle( "$waveDir" ); + mWaveDataSC = matInst->getMaterialParameterHandle( "$waveData" ); + mReflectTexSizeSC = matInst->getMaterialParameterHandle( "$reflectTexSize" ); + mBaseColorSC = matInst->getMaterialParameterHandle( "$baseColor" ); + mMiscParamsSC = matInst->getMaterialParameterHandle( "$miscParams" ); + mReflectParamsSC = matInst->getMaterialParameterHandle( "$reflectParams" ); + mReflectNormalSC = matInst->getMaterialParameterHandle( "$reflectNormal" ); + mHorizonPositionSC = matInst->getMaterialParameterHandle( "$horizonPos" ); + mFogParamsSC = matInst->getMaterialParameterHandle( "$fogParams" ); + mMoreFogParamsSC = matInst->getMaterialParameterHandle( "$moreFogParams" ); + mFarPlaneDistSC = matInst->getMaterialParameterHandle( "$farPlaneDist" ); + mWetnessParamsSC = matInst->getMaterialParameterHandle( "$wetnessParams" ); + mDistortionParamsSC = matInst->getMaterialParameterHandle( "$distortionParams" ); + mUndulateMaxDistSC = matInst->getMaterialParameterHandle( "$undulateMaxDist" ); + mAmbientColorSC = matInst->getMaterialParameterHandle( "$ambientColor" ); + mFoamParamsSC = matInst->getMaterialParameterHandle( "$foamParams" ); + mFoamColorModulateSC = matInst->getMaterialParameterHandle( "$foamColorMod" ); + mGridElementSizeSC = matInst->getMaterialParameterHandle( "$gridElementSize" ); + mElapsedTimeSC = matInst->getMaterialParameterHandle( "$elapsedTime" ); + mModelMatSC = matInst->getMaterialParameterHandle( "$modelMat" ); + mFoamSamplerSC = matInst->getMaterialParameterHandle( "$foamMap" ); + mRippleSamplerSC = matInst->getMaterialParameterHandle( "$bumpMap" ); + mCubemapSamplerSC = matInst->getMaterialParameterHandle( "$skyMap" ); +} + + +bool WaterObject::smWireframe = false; + +//------------------------------------------------------------------------- +// WaterObject Class +//------------------------------------------------------------------------- + +WaterObject::WaterObject() + : mViscosity( 1.0f ), + mDensity( 1.0f ), + mReflectNormalUp( true ), + mDistortStartDist( 0.1f ), + mDistortEndDist( 20.0f ), + mDistortFullDepth( 3.5f ), + mUndulateMaxDist(50.0f), + mFoamScale( 1.0f ), + mFoamMaxDepth( 2.0f ), + mFoamColorModulate( 0.5f, 0.5f, 0.5f ), + mUnderwaterPostFx( NULL ), + mLiquidType( "Water" ), + mFresnelBias( 0.3f ), + mFresnelPower( 6.0f ), + mClarity( 0.5f ), + mBasicLighting( false ), + mMiscParamW( 0.0f ), + mOverallWaveMagnitude( 1.0f ), + mOverallRippleMagnitude( 1.0f ), + mCubemap( NULL ) +{ + mTypeMask = WaterObjectType; + + for( U32 i=0; i < MAX_WAVES; i++ ) + { + mRippleDir[i].set( 0.0f, 0.0f ); + mRippleSpeed[i] = 0.0f; + mRippleTexScale[i].set( 0.0f, 0.0f ); + + mWaveDir[i].set( 0.0f, 0.0f ); + mWaveSpeed[i] = 0.0f; + mWaveMagnitude[i] = 0.0f; + } + + mRippleMagnitude[0] = 1.0f; + mRippleMagnitude[1] = 1.0f; + mRippleMagnitude[2] = 0.3f; + + mWaterFogData.density = 0.1f; + mWaterFogData.densityOffset = 1.0f; + mWaterFogData.wetDepth = 1.5f; + mWaterFogData.wetDarkening = 0.2f; + mWaterFogData.color = ColorI::BLUE; + + mSurfMatName[WaterMat] = "Water"; + mSurfMatName[UnderWaterMat] = "UnderWater"; + mSurfMatName[BasicWaterMat] = "WaterBasic"; + mSurfMatName[BasicUnderWaterMat] = "UnderWaterBasic"; + + dMemset( mMatInstances, 0, sizeof(mMatInstances) ); + + mWaterPos.set( 0,0,0 ); + mWaterPlane.set( mWaterPos, Point3F(0,0,1) ); + + mGenerateVB = true; + + mMatrixSet = reinterpret_cast(dAligned_malloc(sizeof(MatrixSet), 16)); + constructInPlace(mMatrixSet); +} + +WaterObject::~WaterObject() +{ + dAligned_free(mMatrixSet); +} + + +void WaterObject::initPersistFields() +{ + addGroup( "WaterObject" ); + + addField( "density", TypeF32, Offset( mDensity, WaterObject ), "Affects buoyancy of an object, thus affecting the Z velocity" + " of a player (jumping, falling, etc."); + addField( "viscosity", TypeF32, Offset( mViscosity, WaterObject ), "Affects drag force applied to an object submerged in this container." ); + addField( "liquidType", TypeRealString, Offset( mLiquidType, WaterObject ), "Liquid type of WaterBlock, such as water, ocean, lava" + " Currently only Water is defined and used."); + addField( "baseColor", TypeColorI, Offset( mWaterFogData.color, WaterObject ), "Changes color of water fog." ); + addField( "fresnelBias", TypeF32, Offset( mFresnelBias, WaterObject ), "Extent of fresnel affecting reflection fogging." ); + addField( "fresnelPower", TypeF32, Offset( mFresnelPower, WaterObject ), "Measures intensity of affect on reflection based on fogging." ); + + addArray( "Waves (vertex undulation)", MAX_WAVES ); + + addField( "waveDir", TypePoint2F, Offset( mWaveDir, WaterObject ), MAX_WAVES, 0, "Direction waves flow toward shores." ); + addField( "waveSpeed", TypeF32, Offset( mWaveSpeed, WaterObject ), MAX_WAVES, 0, "Speed of water undulation." ); + addField( "waveMagnitude", TypeF32, Offset( mWaveMagnitude, WaterObject ), MAX_WAVES, 0, "Height of water undulation." ); + + endArray( "Waves (vertex undulation)" ); + + addField( "overallWaveMagnitude", TypeF32, Offset( mOverallWaveMagnitude, WaterObject ), "Master variable affecting entire body" + " of water's undulation" ); + + addField( "rippleTex", TypeImageFilename, Offset( mRippleTexName, WaterObject ), "Normal map used to simulate small surface ripples" ); + + addArray( "Ripples (texture animation)", MAX_WAVES ); + + addField( "rippleDir", TypePoint2F, Offset( mRippleDir, WaterObject ), MAX_WAVES, 0, "Modifies the direction of ripples on the surface." ); + addField( "rippleSpeed", TypeF32, Offset( mRippleSpeed, WaterObject ), MAX_WAVES, 0, "Modifies speed of surface ripples."); + addField( "rippleTexScale", TypePoint2F, Offset( mRippleTexScale, WaterObject ), MAX_WAVES, 0, "Intensifies the affect of the normal map " + "applied to the surface."); + addField( "rippleMagnitude", TypeF32, Offset( mRippleMagnitude, WaterObject ), MAX_WAVES, 0, "Intensifies the vertext modification of the surface." ); + + endArray( "Ripples (texture animation)" ); + + addField( "overallRippleMagnitude", TypeF32, Offset( mOverallRippleMagnitude, WaterObject ), "Master variable affecting entire surface"); + + endGroup( "WaterObject" ); + + addGroup( "Reflect" ); + + addField( "cubemap", TypeCubemapName, Offset( mCubemapName, WaterObject ), "Cubemap used instead of reflection texture if fullReflect is off." ); + + addProtectedField( "fullReflect", TypeBool, Offset( mFullReflect, WaterObject ), + &WaterObject::_setFullReflect, + &defaultProtectedGetFn, + "Enables dynamic reflection rendering." ); + + addField( "reflectPriority", TypeF32, Offset( mReflectorDesc.priority, WaterObject ), "Affects the sort order of reflected objects." ); + addField( "reflectMaxRateMs", TypeS32, Offset( mReflectorDesc.maxRateMs, WaterObject ), "Affects the sort time of reflected objects." ); + //addField( "reflectMaxDist", TypeF32, Offset( mReflectMaxDist, WaterObject ), "vert distance at which only cubemap color is used" ); + //addField( "reflectMinDist", TypeF32, Offset( mReflectMinDist, WaterObject ), "vert distance at which only reflection color is used" ); + addField( "reflectDetailAdjust", TypeF32, Offset( mReflectorDesc.detailAdjust, WaterObject ), "scale up or down the detail level for objects rendered in a reflection" ); + addField( "reflectNormalUp", TypeBool, Offset( mReflectNormalUp, WaterObject ), "always use z up as the reflection normal" ); + addField( "useOcclusionQuery", TypeBool, Offset( mReflectorDesc.useOcclusionQuery, WaterObject ), "turn off reflection rendering when occluded (delayed)." ); + addField( "reflectTexSize", TypeS32, Offset( mReflectorDesc.texSize, WaterObject ), "The texture size used for reflections (square)" ); + + endGroup( "Reflect" ); + + addGroup( "Underwater Fogging" ); + + addField( "waterFogDensity", TypeF32, Offset( mWaterFogData.density, WaterObject ), "Intensity of underwater fogging." ); + addField( "waterFogDensityOffset", TypeF32, Offset( mWaterFogData.densityOffset, WaterObject ), "Delta, or limit, applied to waterFogDensity." ); + addField( "wetDepth", TypeF32, Offset( mWaterFogData.wetDepth, WaterObject ), "The depth in world units at which full darkening will be received," + " giving a wet look to objects underwater." ); + addField( "wetDarkening", TypeF32, Offset( mWaterFogData.wetDarkening, WaterObject ), "The refract color intensity scaled at wetDepth." ); + + endGroup( "Underwater Fogging" ); + + addGroup( "Misc" ); + + addField( "foamTex", TypeImageFilename, Offset( mFoamTexName, WaterObject ), "Diffuse texture for foam in shallow water (advanced lighting only)" ); + addField( "foamScale", TypeF32, Offset( mFoamScale, WaterObject ), "Size of the foam generated by WaterBlock hitting shore." ); + addField( "foamMaxDepth", TypeF32, Offset( mFoamMaxDepth, WaterObject ), "Controls how deep foam will be visible from underwater." ); + addField( "foamColorModulate", TypePoint3F, Offset( mFoamColorModulate, WaterObject ), "An RGB value taht linearly interpolates " + "between the base foam color and ambient color so there are not bright white" + " white colors during inappropriate situations, such as night."); + + endGroup( "Misc" ); + + addGroup( "Distortion" ); + + addField( "distortStartDist", TypeF32, Offset( mDistortStartDist, WaterObject ), "Determines start of distortion effect where water" + " surface intersects the camera near plane."); + addField( "distortEndDist", TypeF32, Offset( mDistortEndDist, WaterObject ), "Max distance that distortion algorithm is performed. " + "The lower, the more distorted the effect."); + addField( "distortFullDepth", TypeF32, Offset( mDistortFullDepth, WaterObject ), "Determines the scaling down of distortion " + "in shallow water."); + + endGroup( "Distortion" ); + + addGroup( "Basic Lighting" ); + + addField( "clarity", TypeF32, Offset( mClarity, WaterObject ), "Relative opacity or transparency of the water surface." ); + addField( "underwaterColor", TypeColorI, Offset( mUnderwaterColor, WaterObject ), "Changes the color shading of objects beneath" + " the water surface."); + + endGroup( "Basic Lighting" ); + + Parent::initPersistFields(); + + Con::addVariable( "$WaterObject::wireframe", TypeBool, &smWireframe ); +} + +void WaterObject::inspectPostApply() +{ + Parent::inspectPostApply(); + + setMaskBits( UpdateMask | WaveMask | TextureMask ); +} + +bool WaterObject::_setFullReflect( void *obj, const char *data ) +{ + WaterObject *water = static_cast( obj ); + water->mFullReflect = dAtob( data ); + + if ( water->isProperlyAdded() && water->isClientObject() ) + { + bool isEnabled = water->mPlaneReflector.isEnabled(); + if ( water->mFullReflect && !isEnabled ) + water->mPlaneReflector.registerReflector( water, &water->mReflectorDesc ); + else if ( !water->mFullReflect && isEnabled ) + water->mPlaneReflector.unregisterReflector(); + } + + return false; +} + +U32 WaterObject::packUpdate( NetConnection * conn, U32 mask, BitStream *stream ) +{ + U32 retMask = Parent::packUpdate( conn, mask, stream ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + stream->write( mDensity ); + stream->write( mViscosity ); + stream->write( mLiquidType ); + + if ( stream->writeFlag( mFullReflect ) ) + { + stream->write( mReflectorDesc.priority ); + stream->writeInt( mReflectorDesc.maxRateMs, 32 ); + //stream->write( mReflectMaxDist ); + //stream->write( mReflectMinDist ); + stream->write( mReflectorDesc.detailAdjust ); + stream->writeFlag( mReflectNormalUp ); + stream->writeFlag( mReflectorDesc.useOcclusionQuery ); + stream->writeInt( mReflectorDesc.texSize, 32 ); + } + + stream->write( mWaterFogData.density ); + stream->write( mWaterFogData.densityOffset ); + stream->write( mWaterFogData.wetDepth ); + stream->write( mWaterFogData.wetDarkening ); + + stream->write( mDistortStartDist ); + stream->write( mDistortEndDist ); + stream->write( mDistortFullDepth ); + + stream->write( mFoamScale ); + stream->write( mFoamMaxDepth ); + mathWrite( *stream, mFoamColorModulate ); + + stream->write( mWaterFogData.color ); + + stream->write( mFresnelBias ); + stream->write( mFresnelPower ); + + stream->write( mClarity ); + stream->write( mUnderwaterColor ); + + stream->write( mOverallRippleMagnitude ); + stream->write( mOverallWaveMagnitude ); + } + + if ( stream->writeFlag( mask & WaveMask ) ) + { + for( U32 i=0; iwrite( mRippleSpeed[i] ); + mathWrite( *stream, mRippleDir[i] ); + mathWrite( *stream, mRippleTexScale[i] ); + stream->write( mRippleMagnitude[i] ); + + stream->write( mWaveSpeed[i] ); + mathWrite( *stream, mWaveDir[i] ); + stream->write( mWaveMagnitude[i] ); + } + } + + if ( stream->writeFlag( mask & MaterialMask ) ) + { + for ( U32 i = 0; i < NumMatTypes; i++ ) + stream->write( mSurfMatName[i] ); + } + + if ( stream->writeFlag( mask & TextureMask ) ) + { + stream->write( mRippleTexName ); + stream->write( mFoamTexName ); + stream->write( mCubemapName ); + } + + return retMask; +} + +void WaterObject::unpackUpdate( NetConnection * conn, BitStream *stream ) +{ + Parent::unpackUpdate( conn, stream ); + + // UpdateMask + if ( stream->readFlag() ) + { + stream->read( &mDensity ); + stream->read( &mViscosity ); + stream->read( &mLiquidType ); + + if ( stream->readFlag() ) + { + mFullReflect = true; + stream->read( &mReflectorDesc.priority ); + mReflectorDesc.maxRateMs = stream->readInt( 32 ); + //stream->read( &mReflectMaxDist ); + //stream->read( &mReflectMinDist ); + stream->read( &mReflectorDesc.detailAdjust ); + mReflectNormalUp = stream->readFlag(); + mReflectorDesc.useOcclusionQuery = stream->readFlag(); + mReflectorDesc.texSize = stream->readInt( 32 ); + + if ( isProperlyAdded() && !mPlaneReflector.isEnabled() ) + mPlaneReflector.registerReflector( this, &mReflectorDesc ); + } + else + { + mFullReflect = false; + if ( isProperlyAdded() && mPlaneReflector.isEnabled() ) + mPlaneReflector.unregisterReflector(); + } + + stream->read( &mWaterFogData.density ); + stream->read( &mWaterFogData.densityOffset ); + stream->read( &mWaterFogData.wetDepth ); + stream->read( &mWaterFogData.wetDarkening ); + + stream->read( &mDistortStartDist ); + stream->read( &mDistortEndDist ); + stream->read( &mDistortFullDepth ); + + stream->read( &mFoamScale ); + stream->read( &mFoamMaxDepth ); + mathRead( *stream, &mFoamColorModulate ); + + stream->read( &mWaterFogData.color ); + + stream->read( &mFresnelBias ); + stream->read( &mFresnelPower ); + + stream->read( &mClarity ); + stream->read( &mUnderwaterColor ); + + stream->read( &mOverallRippleMagnitude ); + stream->read( &mOverallWaveMagnitude ); + } + + // WaveMask + if ( stream->readFlag() ) + { + for( U32 i=0; iread( &mRippleSpeed[i] ); + mathRead( *stream, &mRippleDir[i] ); + mathRead( *stream, &mRippleTexScale[i] ); + stream->read( &mRippleMagnitude[i] ); + + stream->read( &mWaveSpeed[i] ); + mathRead( *stream, &mWaveDir[i] ); + stream->read( &mWaveMagnitude[i] ); + } + } + + // MaterialMask + if ( stream->readFlag() ) + { + for ( U32 i = 0; i < NumMatTypes; i++ ) + stream->read( &mSurfMatName[i] ); + + if ( isProperlyAdded() ) + { + // So they will be reloaded on next use. + cleanupMaterials(); + } + } + + // TextureMask + if ( stream->readFlag() ) + { + stream->read( &mRippleTexName ); + stream->read( &mFoamTexName ); + stream->read( &mCubemapName ); + + if ( isProperlyAdded() ) + initTextures(); + } +} + +bool WaterObject::prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState ) +{ + PROFILE_SCOPE(WaterObject_prepRenderImage); + + // Are we in Basic Lighting? + mBasicLighting = dStricmp( gClientSceneGraph->getLightManager()->getId(), "BLM" ) == 0; + mUnderwater = isUnderwater( state->getCameraPosition() ); + + // We only render during the normal diffuse render pass. + if ( state->isShadowPass() || state->isReflectPass() ) //mPlaneReflector.isRendering() ) + return false; + + if ( isLastState( state, stateKey ) ) + return false; + + setLastState(state, stateKey); + + if ( state->isObjectRendered( this ) ) + { + // Setup scene transforms + mMatrixSet->setSceneView(GFX->getWorldMatrix()); + mMatrixSet->setSceneProjection(GFX->getProjectionMatrix()); + + _getWaterPlane( state->getCameraPosition(), mWaterPlane, mWaterPos ); + mWaterFogData.plane = mWaterPlane; + mPlaneReflector.refplane = mWaterPlane; + + updateUnderwaterEffect( state ); + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &WaterObject::renderObject ); + ri->type = RenderPassManager::RIT_Water; + ri->defaultKey = 1; + state->getRenderPass()->addInst( ri ); + + //mRenderUpdateCount++; + } + + return false; +} + +void WaterObject::renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ) +{ + if ( overrideMat ) + return; + + // TODO: Revive projection z-bias at some point. + // The current issue with this method of fixing z-fighting + // in the WaterBlock is that a constant bias does not alleviate + // the issue at the extreme end of the view range. + //GFXTransformSaver saver; + + //MatrixF projMat( true ); + //const Frustum &frustum = ri->state->getFrustum(); + // + //F32 bias = Con::getFloatVariable( "$waterBlockBias", 0.0002418f ); + + //MathUtils::getZBiasProjectionMatrix( bias, frustum, &projMat ); + //GFX->setProjectionMatrix( projMat ); + + + GFXOcclusionQuery *query = mPlaneReflector.getOcclusionQuery(); + if ( query && mReflectorDesc.useOcclusionQuery ) + query->begin(); + + + // Real render call, done by derived class. + innerRender( state ); + + if ( query && mReflectorDesc.useOcclusionQuery ) + query->end(); + + if ( mUnderwater && mBasicLighting ) + drawUnderwaterFilter( state ); +} + +void WaterObject::setCustomTextures( S32 matIdx, U32 pass, const WaterMatParams ¶mHandles ) +{ + // TODO: Retrieve sampler numbers from parameter handles, see r22631. + // Always use the ripple texture. + GFX->setTexture( 0, mRippleTex ); + + // Only above-water in advanced-lighting uses the foam texture. + if ( matIdx == WaterMat ) + GFX->setTexture( 5, mFoamTex ); + + // Only use the cubemap if fullReflect is off. + if ( !mPlaneReflector.isEnabled() ) + { + if ( mCubemap ) + GFX->setCubeTexture( 4, mCubemap->mCubemap ); + else + GFX->setCubeTexture( 4, NULL ); + } + else + GFX->setCubeTexture( 4, NULL ); +} + +void WaterObject::drawUnderwaterFilter( SceneState *state ) +{ + // set up camera transforms + MatrixF proj = GFX->getProjectionMatrix(); + MatrixF newMat(true); + GFX->setProjectionMatrix( newMat ); + GFX->pushWorldMatrix(); + GFX->setWorldMatrix( newMat ); + + // set up render states + GFX->disableShaders(); + GFX->setStateBlock( mUnderwaterSB ); + + /* + const Frustum &frustum = state->getFrustum(); + const MatrixF &camXfm = state->getCameraTransform(); + F32 nearDist = frustum.getNearDist(); + F32 nearLeft = frustum.getNearLeft(); + F32 nearRight = frustum.getNearRight(); + F32 nearTop = frustum.getNearTop(); + F32 nearBottom = frustum.getNearBottom(); + Point3F centerPnt; + frustum.getCenterPoint( ¢erPnt ); + + MatrixF.mul + Point3F linePnt, lineDir; + if ( mIntersect( nearPlane, mWaterPlane, &linePnt, &lineDir ) ) + { + Point3F leftPnt( centerPnt ); + leftPnt.x = near + } + */ + + Point2I resolution = GFX->getActiveRenderTarget()->getSize(); + F32 copyOffsetX = 1.0 / resolution.x; + F32 copyOffsetY = 1.0 / resolution.y; + + /* + ClippedPolyList polylist; + polylist.addPoint( Point3F( -1.0f - copyOffsetX, -1.0f + copyOffsetY, 0.0f ) ); + polylist.addPoint( Point3F( -1.0f - copyOffsetX, 1.0f + copyOffsetY, 0.0f ) ); + polylist.addPoint( Point3F( 1.0f - copyOffsetX, 1.0f + copyOffsetY, 0.0f ) ); + polylist.addPoint( Point3F( 1.0f - copyOffsetX, -1.0f + copyOffsetY, 0.0f ) ); + polylist.addPlane( clipPlane ); + + polylist.begin( NULL, 0 ); + polylist.vertex( 0 ); + polylist.vertex( 1 ); + polylist.vertex( 2 ); + polylist.vertex( 0 ); + polylist.vertex( 2 ); + polylist.vertex( 3 ); + */ + + // draw quad + + + GFXVertexBufferHandle verts( GFX, 4, GFXBufferTypeVolatile ); + verts.lock(); + + verts[0].point.set( -1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 ); + verts[0].color = mUnderwaterColor; + + verts[1].point.set( -1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 ); + verts[1].color = mUnderwaterColor; + + verts[2].point.set( 1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 ); + verts[2].color = mUnderwaterColor; + + verts[3].point.set( 1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 ); + verts[3].color = mUnderwaterColor; + + verts.unlock(); + + GFX->setVertexBuffer( verts ); + GFX->drawPrimitive( GFXTriangleFan, 0, 2 ); + + // reset states / transforms + GFX->setProjectionMatrix( proj ); + GFX->popWorldMatrix(); +} + +bool WaterObject::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + if ( isClientObject() ) + { + GFXStateBlockDesc desc; + desc.blendDefined = true; + desc.blendEnable = true; + desc.blendSrc = GFXBlendSrcAlpha; + desc.blendDest = GFXBlendInvSrcAlpha; + desc.zDefined = true; + desc.zEnable = false; + desc.cullDefined = true; + desc.cullMode = GFXCullNone; + mUnderwaterSB = GFX->createStateBlock( desc ); + + initTextures(); + + if ( mFullReflect ) + mPlaneReflector.registerReflector( this, &mReflectorDesc ); + } + + return true; +} + +void WaterObject::onRemove() +{ + if ( isClientObject() ) + { + mPlaneReflector.unregisterReflector(); + cleanupMaterials(); + } + + Parent::onRemove(); +} + +void WaterObject::setShaderParams( SceneState *state, BaseMatInstance *mat, const WaterMatParams ¶mHandles ) +{ + MaterialParameters* matParams = mat->getMaterialParameters(); + + matParams->set( paramHandles.mElapsedTimeSC, (F32)Sim::getCurrentTime() / 1000.0f ); + + // set vertex shader constants + //----------------------------------- + + //Point2F reflectTexSize( mReflectTexSize.x, mReflectTexSize.y ); + Point2F reflectTexSize( mPlaneReflector.reflectTex.getWidth(), mPlaneReflector.reflectTex.getHeight() ); + matParams->set( paramHandles.mReflectTexSizeSC, reflectTexSize ); + + if ( mConstArray.getElementSize() == 0 ) + mConstArray.setCapacity( MAX_WAVES, sizeof( Point4F ) ); + + // Ripples... + + for ( U32 i = 0; i < MAX_WAVES; i++ ) + mConstArray[i].set( mRippleDir[i].x, mRippleDir[i].y ); + matParams->set( paramHandles.mRippleDirSC, mConstArray ); + + Point3F rippleSpeed( mRippleSpeed[0], mRippleSpeed[1], mRippleSpeed[2] ); + matParams->set( paramHandles.mRippleSpeedSC, rippleSpeed ); + + Point3F rippleMagnitude( mRippleMagnitude[0] * mOverallRippleMagnitude, + mRippleMagnitude[1] * mOverallRippleMagnitude, + mRippleMagnitude[2] * mOverallRippleMagnitude ); + matParams->set( paramHandles.mRippleMagnitudeSC, rippleMagnitude ); + + for ( U32 i = 0; i < MAX_WAVES; i++ ) + { + Point2F texScale = mRippleTexScale[i]; + if ( texScale.x > 0.0 ) + texScale.x = 1.0 / texScale.x; + if ( texScale.y > 0.0 ) + texScale.y = 1.0 / texScale.y; + + mConstArray[i].set( texScale.x, texScale.y ); + } + matParams->set(paramHandles.mRippleTexScaleSC, mConstArray); + + // Waves... + + for ( U32 i = 0; i < MAX_WAVES; i++ ) + mConstArray[i].set( mWaveDir[i].x, mWaveDir[i].y ); + matParams->set( paramHandles.mWaveDirSC, mConstArray ); + + for ( U32 i = 0; i < MAX_WAVES; i++ ) + mConstArray[i].set( mWaveSpeed[i], mWaveMagnitude[i] * mOverallWaveMagnitude ); + matParams->set( paramHandles.mWaveDataSC, mConstArray ); + + // Other vert params... + + matParams->set( paramHandles.mUndulateMaxDistSC, mUndulateMaxDist ); + + // set pixel shader constants + //----------------------------------- + + Point2F fogParams( mWaterFogData.density, mWaterFogData.densityOffset ); + matParams->set(paramHandles.mFogParamsSC, fogParams ); + + matParams->set(paramHandles.mFarPlaneDistSC, (F32)state->getFarPlane() ); + + Point2F wetnessParams( mWaterFogData.wetDepth, mWaterFogData.wetDarkening ); + matParams->set(paramHandles.mWetnessParamsSC, wetnessParams ); + + Point3F distortionParams( mDistortStartDist, mDistortEndDist, mDistortFullDepth ); + matParams->set(paramHandles.mDistortionParamsSC, distortionParams ); + + const ColorF &sunlight = gClientSceneGraph->getLightManager()->getSpecialLight(LightManager::slSunLightType)->getAmbient(); + Point3F ambientColor( sunlight.red, sunlight.green, sunlight.blue ); + matParams->set(paramHandles.mAmbientColorSC, ambientColor ); + + Point2F foamParams( mFoamScale, mFoamMaxDepth ); + matParams->set(paramHandles.mFoamParamsSC, foamParams ); + matParams->set(paramHandles.mFoamColorModulateSC, mFoamColorModulate ); + + Point4F miscParams( mFresnelBias, mFresnelPower, mClarity, mMiscParamW ); + matParams->set( paramHandles.mMiscParamsSC, miscParams ); +} + +PostEffect* WaterObject::getUnderwaterEffect() +{ + if ( mUnderwaterPostFx.isValid() ) + return mUnderwaterPostFx; + + PostEffect *effect; + if ( Sim::findObject( "UnderwaterFogPostFx", effect ) ) + mUnderwaterPostFx = effect; + + return mUnderwaterPostFx; +} + +void WaterObject::updateUnderwaterEffect( SceneState *state ) +{ + AssertFatal( isClientObject(), "uWaterObject::updateUnderwaterEffect() called on the server" ); + + PostEffect *effect = getUnderwaterEffect(); + if ( !effect ) + return; + + // Never use underwater postFx with Basic Lighting, we don't have depth. + if ( mBasicLighting ) + { + effect->disable(); + return; + } + + GameConnection *conn = GameConnection::getConnectionToServer(); + if ( !conn ) + return; + + GameBase *control = conn->getControlObject(); + if ( !control ) + return; + + WaterObject *water = control->getCurrentWaterObject(); + if ( water == NULL ) + effect->disable(); + + else if ( water == this ) + { + MatrixF mat; + conn->getControlCameraTransform( 0, &mat ); + + if ( mUnderwater ) + { + effect->enable(); + effect->setOnThisFrame( true ); + + gClientSceneGraph->setWaterFogData( mWaterFogData ); + } + else + effect->disable(); + } +} + +bool WaterObject::initMaterial( S32 idx ) +{ + // We must return false for any case which it is NOT safe for the caller + // to use the indexed material. + + if ( idx < 0 || idx > NumMatTypes ) + return false; + + BaseMatInstance *mat = mMatInstances[idx]; + WaterMatParams &matParams = mMatParamHandles[idx]; + + // Is it already initialized? + + if ( mat && mat->isValid() ) + return true; + + // Do we need to allocate anything? + + if ( mSurfMatName[idx].isNotEmpty() ) + { + if ( mat ) + SAFE_DELETE( mat ); + + CustomMaterial *custMat; + if ( Sim::findObject( mSurfMatName[idx], custMat ) && custMat->mShaderData ) + mat = custMat->createMatInstance(); + else + mat = MATMGR->createMatInstance( mSurfMatName[idx] ); + + const GFXVertexFormat *flags = getGFXVertexFormat(); + + if ( mat && mat->init( MATMGR->getDefaultFeatures(), flags ) ) + { + mMatInstances[idx] = mat; + matParams.init( mat ); + return true; + } + + SAFE_DELETE( mat ); + } + + return false; +} + +void WaterObject::initTextures() +{ + if ( mRippleTexName.isNotEmpty() ) + mRippleTex.set( mRippleTexName, &GFXDefaultStaticDiffuseProfile, "WaterObject::mRippleTex" ); + if ( mRippleTex.isNull() ) + mRippleTex.set( "core/art/warnmat", &GFXDefaultStaticDiffuseProfile, "WaterObject::mRippleTex" ); + + if ( mFoamTexName.isNotEmpty() ) + mFoamTex.set( mFoamTexName, &GFXDefaultStaticDiffuseProfile, "WaterObject::mFoamTex" ); + if ( mFoamTex.isNull() ) + mFoamTex.set( "core/art/warnmat", &GFXDefaultStaticDiffuseProfile, "WaterObject::mFoamTex" ); + + if ( mCubemapName.isNotEmpty() ) + Sim::findObject( mCubemapName, mCubemap ); + if ( mCubemap ) + mCubemap->createMap(); +} + +void WaterObject::cleanupMaterials() +{ + for (U32 i = 0; i < NumMatTypes; i++) + SAFE_DELETE(mMatInstances[i]); +} + +S32 WaterObject::getMaterialIndex( const Point3F &camPos ) +{ + bool underwater = isUnderwater( camPos ); + bool basicLighting = dStricmp( gClientSceneGraph->getLightManager()->getId(), "BLM" ) == 0; + + // set the material + S32 matIdx = -1; + if ( underwater ) + { + if ( basicLighting ) + matIdx = BasicUnderWaterMat; + else + matIdx = UnderWaterMat; + } + else + { + if ( basicLighting ) + matIdx = BasicWaterMat; + else + matIdx = WaterMat; + } + + return matIdx; +} \ No newline at end of file diff --git a/environment/waterObject.h b/environment/waterObject.h new file mode 100644 index 0000000..b9e7fc1 --- /dev/null +++ b/environment/waterObject.h @@ -0,0 +1,253 @@ + +#ifndef _WATEROBJECT_H_ +#define _WATEROBJECT_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXSTRUCTS_H_ +#include "gfx/gfxStructs.h" +#endif +#ifndef _FOGSTRUCTS_H_ +#include "sceneGraph/fogStructs.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _REFLECTOR_H_ +#include "sceneGraph/reflector.h" +#endif + + +GFXDeclareVertexFormat( GFXWaterVertex ) +{ + Point3F point; + Point3F normal; + GFXVertexColor color; + Point2F undulateData; + Point4F horizonFactor; +}; + + +class MaterialParameterHandle; +class BaseMatInstance; +class ShaderData; + +struct WaterMatParams +{ + MaterialParameterHandle* mRippleDirSC; + MaterialParameterHandle* mRippleTexScaleSC; + MaterialParameterHandle* mRippleSpeedSC; + MaterialParameterHandle* mRippleMagnitudeSC; + MaterialParameterHandle* mWaveDirSC; + MaterialParameterHandle* mWaveDataSC; + MaterialParameterHandle* mReflectTexSizeSC; + MaterialParameterHandle* mBaseColorSC; + MaterialParameterHandle* mMiscParamsSC; + MaterialParameterHandle* mReflectParamsSC; + MaterialParameterHandle* mReflectNormalSC; + MaterialParameterHandle* mHorizonPositionSC; + MaterialParameterHandle* mFogParamsSC; + MaterialParameterHandle* mMoreFogParamsSC; + MaterialParameterHandle* mFarPlaneDistSC; + MaterialParameterHandle* mWetnessParamsSC; + MaterialParameterHandle* mDistortionParamsSC; + MaterialParameterHandle* mUndulateMaxDistSC; + MaterialParameterHandle* mAmbientColorSC; + MaterialParameterHandle* mFoamParamsSC; + MaterialParameterHandle* mFoamColorModulateSC; + MaterialParameterHandle* mGridElementSizeSC; + MaterialParameterHandle* mElapsedTimeSC; + MaterialParameterHandle* mModelMatSC; + MaterialParameterHandle* mFoamSamplerSC; + MaterialParameterHandle* mRippleSamplerSC; + MaterialParameterHandle* mCubemapSamplerSC; + + void clear(); + void init(BaseMatInstance* matInst); +}; + + +class GFXOcclusionQuery; +class PostEffect; +class CubemapData; + +//------------------------------------------------------------------------- +// WaterObject Class +//------------------------------------------------------------------------- + +class WaterObject : public SceneObject +{ + typedef SceneObject Parent; + +protected: + + enum MaskBits { + UpdateMask = Parent::NextFreeMask << 0, + WaveMask = Parent::NextFreeMask << 1, + MaterialMask = Parent::NextFreeMask << 2, + TextureMask = Parent::NextFreeMask << 3, + NextFreeMask = Parent::NextFreeMask << 4 + }; + + enum consts { + MAX_WAVES = 3, + NUM_ANIM_FRAMES = 32, + }; + + enum MaterialType + { + WaterMat = 0, + UnderWaterMat, + BasicWaterMat, + BasicUnderWaterMat, + NumMatTypes + }; + +public: + + WaterObject(); + virtual ~WaterObject(); + + + // ConsoleObject + static void initPersistFields(); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + virtual void inspectPostApply(); + + // NetObject + virtual U32 packUpdate( NetConnection * conn, U32 mask, BitStream *stream ); + virtual void unpackUpdate( NetConnection * conn, BitStream *stream ); + + // SceneObject + virtual bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState = false ); + virtual void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + // WaterObject + virtual F32 getViscosity() const { return mViscosity; } + virtual F32 getDensity() const { return mDensity; } + virtual F32 getSurfaceHeight( const Point2F &pos ) const { return 0.0f; }; + virtual const char* getLiquidType() const { return mLiquidType; } + virtual F32 getWaterCoverage( const Box3F &worldBox ) const { return 0.0f; } + virtual VectorF getFlow( const Point3F &pos ) const { return Point3F::Zero; } + virtual void updateUnderwaterEffect( SceneState *state ); + virtual bool isUnderwater( const Point3F &pnt ) { return false; } + +protected: + + virtual void setShaderXForms( BaseMatInstance *mat ) {}; + virtual void setupVBIB() {}; + virtual void innerRender( SceneState *state ) {}; + virtual void setCustomTextures( S32 matIdx, U32 pass, const WaterMatParams ¶mHandles ); + void drawUnderwaterFilter( SceneState *state ); + + virtual void setShaderParams( SceneState *state, BaseMatInstance *mat, const WaterMatParams ¶mHandles ); + PostEffect* getUnderwaterEffect(); + + bool initMaterial( S32 idx ); + void cleanupMaterials(); + S32 getMaterialIndex( const Point3F &camPos ); + + void initTextures(); + + virtual void _getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos ) {} + +protected: + + static bool _setFullReflect( void *obj, const char *data ); + + // WaterObject + F32 mViscosity; + F32 mDensity; + String mLiquidType; + F32 mFresnelBias; + F32 mFresnelPower; + + // Reflection + bool mFullReflect; + PlaneReflector mPlaneReflector; + ReflectorDesc mReflectorDesc; + PlaneF mWaterPlane; + Point3F mWaterPos; + bool mReflectNormalUp; + + // Water Fogging + WaterFogData mWaterFogData; + + // Distortion + F32 mDistortStartDist; + F32 mDistortEndDist; + F32 mDistortFullDepth; + + // Ripples + Point2F mRippleDir[ MAX_WAVES ]; + F32 mRippleSpeed[ MAX_WAVES ]; + Point2F mRippleTexScale[ MAX_WAVES ]; + F32 mRippleMagnitude[ MAX_WAVES ]; + + F32 mOverallRippleMagnitude; + + // Waves + Point2F mWaveDir[ MAX_WAVES ]; + F32 mWaveSpeed[ MAX_WAVES ]; + F32 mWaveMagnitude[ MAX_WAVES ]; + + F32 mOverallWaveMagnitude; + + // Foam + F32 mFoamScale; + F32 mFoamMaxDepth; + Point3F mFoamColorModulate; + + // Basic Lighting + F32 mClarity; + ColorI mUnderwaterColor; + + // Other textures + String mRippleTexName; + String mFoamTexName; + String mCubemapName; + + // Not fields... + + /// Defined here and sent to the shader in WaterCommon::setShaderParams + /// but needs to be initialized in child classes prior to that call. + F32 mUndulateMaxDist; + + /// Defined in WaterCommon but set and used by child classes. + /// If true will refuse to render a reflection even if called from + /// the ReflectionManager, is set true if occlusion query is enabled and + /// it determines it is occluded. + //bool mSkipReflectUpdate; + + /// Derived classes can set this value prior to calling Parent::setShaderConst + /// to pass it into the shader miscParam.w + F32 mMiscParamW; + + SimObjectPtr mUnderwaterPostFx; + + /// A global for enabling wireframe rendering + /// on all water objects. + static bool smWireframe; + + // Rendering + bool mBasicLighting; + //U32 mRenderUpdateCount; + //U32 mReflectUpdateCount; + bool mGenerateVB; + String mSurfMatName[NumMatTypes]; + BaseMatInstance* mMatInstances[NumMatTypes]; + WaterMatParams mMatParamHandles[NumMatTypes]; + AlignedArray mConstArray; + bool mUnderwater; + GFXStateBlockRef mUnderwaterSB; + GFXTexHandle mRippleTex; + GFXTexHandle mFoamTex; + CubemapData *mCubemap; + MatrixSet *mMatrixSet; +}; + +#endif // _WATEROBJECT_H_ diff --git a/environment/waterPlane.cpp b/environment/waterPlane.cpp new file mode 100644 index 0000000..293f5de --- /dev/null +++ b/environment/waterPlane.cpp @@ -0,0 +1,757 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "environment/waterPlane.h" + +#include "core/util/safeDelete.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightInfo.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" +#include "math/mathUtils.h" +#include "console/consoleTypes.h" +#include "gui/3d/guiTSControl.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxOcclusionQuery.h" +#include "renderInstance/renderPassManager.h" +#include "sim/netConnection.h" +#include "sceneGraph/reflectionManager.h" +#include "ts/tsShapeInstance.h" +#include "T3D/gameFunctions.h" +#include "postFx/postEffect.h" +#include "math/util/matrixSet.h" + +extern ColorI gCanvasClearColor; + +#define BLEND_TEX_SIZE 256 +#define V_SHADER_PARAM_OFFSET 50 + +IMPLEMENT_CO_NETOBJECT_V1(WaterPlane); + +//***************************************************************************** +// WaterPlane +//***************************************************************************** +WaterPlane::WaterPlane() +{ + mGridElementSize = 1.0f; + mGridSize = 101; + mGridSizeMinusOne = mGridSize - 1; + + mNetFlags.set(Ghostable | ScopeAlways); + + mVertCount = 0; + mIndxCount = 0; + mPrimCount = 0; +} + +WaterPlane::~WaterPlane() +{ +} + +//----------------------------------------------------------------------------- +// onAdd +//----------------------------------------------------------------------------- +bool WaterPlane::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + setGlobalBounds(); + resetWorldBox(); + addToScene(); + + mWaterFogData.plane.set( 0, 0, 1, -getPosition().z ); + + return true; +} + +//----------------------------------------------------------------------------- +// onRemove +//----------------------------------------------------------------------------- +void WaterPlane::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- +// packUpdate +//----------------------------------------------------------------------------- +U32 WaterPlane::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + stream->write( mGridSize ); + stream->write( mGridElementSize ); + + if ( stream->writeFlag( mask & UpdateMask ) ) + { + stream->write( getPosition().z ); + } + + return retMask; +} + +//----------------------------------------------------------------------------- +// unpackUpdate +//----------------------------------------------------------------------------- +void WaterPlane::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + U32 inGridSize; + stream->read( &inGridSize ); + setGridSize( inGridSize ); + + F32 inGridElementSize; + stream->read( &inGridElementSize ); + setGridElementSize( inGridElementSize ); + + if( stream->readFlag() ) // UpdateMask + { + float posZ; + stream->read( &posZ ); + Point3F newPos = getPosition(); + newPos.z = posZ; + setPosition( newPos ); + } +} + +//----------------------------------------------------------------------------- +// Setup vertex and index buffers +//----------------------------------------------------------------------------- +void WaterPlane::setupVBIB( const Point3F &camPos ) +{ + F32 squareSize = mGridElementSize; + + // Position of the first vertex + // x and y components of which will + // be the camera position x and y offset + // from the center of the grid to the corner + // dependent on the dimensions of the grid squares. + + ColorI waterColor(31, 56, 64, 127); + GFXVertexColor vertCol(waterColor); + + F32 offsetAmt = (F32)(mGridSize - 1) / 2.0f; + + Point3F cornerPosition(0, 0, 0); + cornerPosition.x -= squareSize * offsetAmt; + cornerPosition.y -= squareSize * offsetAmt; + + mIndxCount = (mGridSizeMinusOne) * (mGridSizeMinusOne); + mPrimCount = mIndxCount * 2; + + mPrimBuff.set( GFX, mIndxCount * 6, 1, GFXBufferTypeStatic ); + + mVertCount = (mGridSize * mGridSize); + + mVertBuff.set( GFX, mVertCount, GFXBufferTypeStatic ); + + GFXWaterVertex *vertPtr = mVertBuff.lock(); + + F32 xVal = 0, yVal = 0; + + U32 innerEdgeIdx = mGridSizeMinusOne - 1; + + F32 frac = (mFrustum.getFarDist() - 130.0f) - (squareSize * offsetAmt); + + F32 cornerOffset = 0.5f; + F32 edgeOffset = 0.98f; + + // Build the full grid first. + for ( U32 i = 0; i < mGridSize; i++ ) + { + for ( U32 j = 0; j < mGridSize; j++ ) + { + yVal = cornerPosition.y + (F32)(i * squareSize); + xVal = cornerPosition.x + (F32)(j * squareSize); + + U32 index = (i * mGridSize) + j; + + vertPtr[index].horizonFactor.set( 0, 0, 1.0f, 0 ); + vertPtr[index].point.set( xVal, yVal, 0 ); + vertPtr[index].normal.set( 0, 0, 1.0f ); + vertPtr[index].undulateData.set(xVal,yVal); + vertPtr[index].color = waterColor; + } + } + + bool front, back, left, right; + bool upperLeft, lowerLeft, upperRight, lowerRight; + + front = back = left = right = false; + upperLeft = lowerLeft = upperRight = lowerRight = false; + + for ( U32 i = 0; i < mGridSize; i++ ) + { + for ( U32 j = 0; j < mGridSize; j++ ) + { + U32 index = (i * mGridSize) + j; + + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + + if ( i == 0 || + j == 0 || + i == mGridSizeMinusOne || + j == mGridSizeMinusOne ) + { + front = index >= (mGridSizeMinusOne * mGridSize) && index <= (mGridSizeMinusOne * mGridSize) + mGridSizeMinusOne; + back = index >= 0 && index <= mGridSizeMinusOne; + left = (index % mGridSize) == 0 && index != 0; + right = ((index+1) % mGridSize) == 0 && index != 0; + + upperLeft = index == mGridSize * mGridSizeMinusOne; + lowerLeft = index == 0; + + upperRight = index == (mGridSize * mGridSize) - 1; + lowerRight = index == mGridSizeMinusOne; + + if ( front ) + yVal += frac * edgeOffset; + else if ( back ) + yVal -= frac * edgeOffset; + else if ( left ) + xVal -= frac * edgeOffset; + else if ( right ) + xVal += frac * edgeOffset; + + if ( upperLeft ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal += frac; + xVal -= frac; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + else if ( lowerLeft ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal -= frac; + xVal -= frac; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + else if ( upperRight ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal += frac; + xVal += frac; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + else if ( lowerRight ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal -= frac; + xVal += frac; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + + // Set edge vert factor to 1.0f here. + vertPtr[index].horizonFactor.x = 1.0f; + vertPtr[index].horizonFactor.y = 1.0f; + vertPtr[index].point.set( xVal, yVal, 0 ); + } + else if ( (i >= 1 && i <= innerEdgeIdx) && (j == 1 || j == innerEdgeIdx) || + (i == 1 && (j >= 1 && j <= innerEdgeIdx ) ) || + (i == innerEdgeIdx && (j >= 1 && j <= innerEdgeIdx ) ) ) + { + front = index >= (mGridSizeMinusOne * mGridSizeMinusOne) && index <= ((mGridSizeMinusOne * mGridSize) - 2); + back = index >= (mGridSize + 1) && index <= (mGridSize + (mGridSizeMinusOne)); + left = ((index-1) % mGridSize) == 0 && index != 0; + right = ((index + 2) % mGridSize) == 0 && index != 0; + + upperLeft = index == (mGridSizeMinusOne * mGridSizeMinusOne); + lowerLeft = index == mGridSize + 1; + + upperRight = index == (mGridSize * mGridSizeMinusOne) - 2; + lowerRight = index == (mGridSizeMinusOne - 1) + mGridSize; + + if ( front ) + yVal += (frac * edgeOffset) + squareSize; + else if ( back ) + yVal -= (frac * edgeOffset) + squareSize; + else if ( left ) + xVal -= (frac * edgeOffset) + squareSize; + else if ( right ) + xVal += (frac * edgeOffset) + squareSize; + + if ( upperLeft ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal += frac + squareSize; + xVal -= frac + squareSize; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + else if ( lowerLeft ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal -= frac + squareSize; + xVal -= frac + squareSize; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + else if ( upperRight ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal += frac + squareSize; + xVal += frac + squareSize; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + else if ( lowerRight ) + { + yVal = cornerPosition.y + (i * squareSize); + xVal = cornerPosition.x + (j * squareSize); + yVal -= frac + squareSize; + xVal += frac + squareSize; + + xVal *= cornerOffset; + yVal *= cornerOffset; + } + + vertPtr[index].horizonFactor.x = 0.0f; + vertPtr[index].horizonFactor.y = 1.0f; + vertPtr[index].point.set( xVal, yVal, 0 ); + } + + vertPtr[index].normal.set( 0, 0, 1.0f ); + vertPtr[index].undulateData.set(xVal,yVal); + vertPtr[index].color = waterColor; + } + } + mVertBuff.unlock(); + + U16 *idxPtr; + mPrimBuff.lock(&idxPtr); + U32 curIdx = 0; + + // Temporaries to hold indices for the corner points of a quad. + U32 p00, p01, p11, p10; + U32 offset = 0; + + for ( U32 i = 1; i < mGridSize; i++ ) + { + offset = ((i-1) * mGridSize); + + for ( U32 j = 0; j < mGridSizeMinusOne; j++ ) + { + p00 = offset; + p01 = offset + 1; + p11 = p01 + mGridSize; + p10 = p00 + mGridSize; + + // Upper-Left triangle + idxPtr[curIdx] = p00; + curIdx++; + idxPtr[curIdx] = p01; + curIdx++; + idxPtr[curIdx] = p11; + curIdx++; + + // Lower-Right Triangle + idxPtr[curIdx] = p00; + curIdx++; + idxPtr[curIdx] = p11; + curIdx++; + idxPtr[curIdx] = p10; + curIdx++; + + offset += 1; + } + } + + mPrimBuff.unlock(); +} + +SceneGraphData WaterPlane::setupSceneGraphInfo( SceneState *state ) +{ + SceneGraphData sgData; + + LightManager* lm = gClientSceneGraph->getLightManager(); + sgData.lights[0] = lm->getSpecialLight( LightManager::slSunLightType ); + + // fill in water's transform + sgData.objTrans = getRenderTransform(); + + // fog + sgData.setFogParams( gClientSceneGraph->getFogData() ); + + // misc + sgData.backBuffTex = REFLECTMGR->getRefractTex(); + sgData.reflectTex = mPlaneReflector.reflectTex; + sgData.wireframe = GFXDevice::getWireframe() || smWireframe; + + return sgData; +} + +//----------------------------------------------------------------------------- +// set shader parameters +//----------------------------------------------------------------------------- +void WaterPlane::setShaderParams( SceneState *state, BaseMatInstance* mat, const WaterMatParams& paramHandles) +{ + // Set variables that will be assigned to shader consts within WaterCommon + // before calling Parent::setShaderParams + + mUndulateMaxDist = mGridElementSize * mGridSizeMinusOne * 0.5f; + + Parent::setShaderParams( state, mat, paramHandles ); + + // Now set the rest of the shader consts that are either unique to this + // class or that WaterObject leaves to us to handle... + + MaterialParameters* matParams = mat->getMaterialParameters(); + + // set vertex shader constants + //----------------------------------- + matParams->set(paramHandles.mGridElementSizeSC, (F32)mGridElementSize); + //matParams->set( paramHandles.mReflectTexSizeSC, mReflectTexSize ); + matParams->set(paramHandles.mModelMatSC, getRenderTransform(), GFXSCT_Float3x3); + + // set pixel shader constants + //----------------------------------- + + ColorF c( mWaterFogData.color ); + matParams->set( paramHandles.mBaseColorSC, c ); + + F32 reflect = mPlaneReflector.isEnabled() && !isUnderwater( state->getCameraPosition() ) ? 0.0f : 1.0f; + //Point4F reflectParams( getRenderPosition().z, mReflectMinDist, mReflectMaxDist, reflect ); + Point4F reflectParams( getRenderPosition().z, 0.0f, 1000.0f, reflect ); + + // TODO: This is a hack... why is this broken... check after + // we merge advanced lighting with trunk! + // + reflectParams.z = 0.0f; + matParams->set( paramHandles.mReflectParamsSC, reflectParams ); + + VectorF reflectNorm( 0, 0, 1 ); + matParams->set(paramHandles.mReflectNormalSC, reflectNorm ); +} + +bool WaterPlane::prepRenderImage( SceneState* state, + const U32 stateKey, + const U32, + const bool ) +{ + PROFILE_SCOPE(WaterPlane_prepRenderImage); + + if ( !state->isDiffusePass() || mPlaneReflector.isRendering() ) + return false; + + if ( isLastState( state, stateKey ) ) + return false; + + setLastState(state, stateKey); + + if ( !state->isObjectRendered( this ) ) + return false; + + mBasicLighting = dStricmp( gClientSceneGraph->getLightManager()->getId(), "BLM" ) == 0; + mUnderwater = isUnderwater( state->getCameraPosition() ); + + mMatrixSet->setSceneView(GFX->getWorldMatrix()); + + const Frustum &frustum = state->getFrustum(); + + if ( mPrimBuff.isNull() || + mGenerateVB || + frustum != mFrustum ) + { + mFrustum = frustum; + setupVBIB( state->getCameraPosition() ); + mGenerateVB = false; + + MatrixF proj( true ); + MathUtils::getZBiasProjectionMatrix( 0.0001f, mFrustum, &proj ); + mMatrixSet->setSceneProjection(proj); + } + + _getWaterPlane( state->getCameraPosition(), mWaterPlane, mWaterPos ); + mWaterFogData.plane = mWaterPlane; + mPlaneReflector.refplane = mWaterPlane; + updateUnderwaterEffect( state ); + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &WaterObject::renderObject ); + ri->type = RenderPassManager::RIT_Water; + state->getRenderPass()->addInst( ri ); + + //mRenderUpdateCount++; + + return false; +} + +void WaterPlane::innerRender( SceneState *state ) +{ + GFXDEBUGEVENT_SCOPE( WaterPlane_innerRender, ColorI( 255, 0, 0 ) ); + + // Setup SceneGraphData + SceneGraphData sgData = setupSceneGraphInfo( state ); + const Point3F &camPosition = state->getCameraPosition(); + + // set the material + + S32 matIdx = getMaterialIndex( camPosition ); + + if ( !initMaterial( matIdx ) ) + return; + + BaseMatInstance *mat = mMatInstances[matIdx]; + WaterMatParams matParams = mMatParamHandles[matIdx]; + + // render the geometry + if ( mat ) + { + // setup proj/world transform + mMatrixSet->restoreSceneViewProjection(); + mMatrixSet->setWorld(getRenderTransform()); + + setShaderParams( state, mat, matParams ); + + while( mat->setupPass( state, sgData ) ) + { + mat->setSceneInfo(state, sgData); + mat->setTransforms(*mMatrixSet, state); + setCustomTextures( matIdx, mat->getCurPass(), matParams ); + + // set vert/prim buffer + GFX->setVertexBuffer( mVertBuff ); + GFX->setPrimitiveBuffer( mPrimBuff ); + GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, mVertCount, 0, mPrimCount ); + } + } +} + +//----------------------------------------------------------------------------- +// initPersistFields +//----------------------------------------------------------------------------- +void WaterPlane::initPersistFields() +{ + addGroup( "WaterPlane" ); + addProtectedField( "gridSize", TypeS32, Offset( mGridSize, WaterPlane ), &protectedSetGridSize, &defaultProtectedGetFn, 1, 0, + "Spacing between vertices in the WaterBlock mesh" ); + addProtectedField( "gridElementSize", TypeF32, Offset( mGridElementSize, WaterPlane ), &protectedSetGridElementSize, &defaultProtectedGetFn, 1, 0, + "Duplicate of gridElementSize for backwards compatility"); + endGroup( "WaterPlane" ); + + Parent::initPersistFields(); + + removeField( "rotation" ); + removeField( "scale" ); +} + +bool WaterPlane::isUnderwater( const Point3F &pnt ) +{ + F32 height = getPosition().z; + + F32 diff = pnt.z - height; + + return ( diff < 0.1 ); +} + +void WaterPlane::inspectPostApply() +{ + Parent::inspectPostApply(); + + setMaskBits( UpdateMask ); +} + +void WaterPlane::setTransform( const MatrixF &mat ) +{ + // We only accept the z value from the new transform. + + MatrixF newMat( true ); + + Point3F newPos = getPosition(); + newPos.z = mat.getPosition().z; + newMat.setPosition( newPos ); + + Parent::setTransform( newMat ); + + // Parent::setTransforms ends up setting our worldBox to something other than + // global, so we have to set it back... but we can't actually call setGlobalBounds + // again because it does extra work adding and removing us from the container. + + mGlobalBounds = true; + mObjBox.minExtents.set(-1e10, -1e10, -1e10); + mObjBox.maxExtents.set( 1e10, 1e10, 1e10); + + // Keep mWaterPlane up to date. + mWaterFogData.plane.set( 0, 0, 1, -getPosition().z ); +} + +void WaterPlane::onStaticModified( const char* slotName, const char*newValue ) +{ + Parent::onStaticModified( slotName, newValue ); + + if ( dStricmp( slotName, "surfMaterial" ) == 0 ) + setMaskBits( MaterialMask ); +} + +//----------------------------------------------------------------------------- +// Set up projection matrix for multipass technique with different geometry. +// It basically just pushes the near plane out. This should work across +// fixed-function and shader geometry. +//----------------------------------------------------------------------------- +/* +void WaterBlock::setMultiPassProjection() +{ + F32 nearPlane, farPlane; + F32 left, right, bottom, top; + bool ortho = false; + GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &ortho ); + + F32 FOV = GameGetCameraFov(); + Point2I size = GFX->getVideoMode().resolution; + +// GFX->setFrustum( FOV, size.x/F32(size.y), nearPlane + 0.010, farPlane + 10.0 ); + +// note - will have to re-calc left, right, top, bottom if the above technique +// doesn't work through portals +// GFX->setFrustum( left, right, bottom, top, nearPlane + 0.001, farPlane ); + + +} +*/ + +bool WaterPlane::castRay(const Point3F& start, const Point3F& end, RayInfo* info ) +{ + // Simply look for the hit on the water plane + // and ignore any future issues with waves, etc. + const Point3F norm(0,0,1); + PlaneF plane( Point3F::Zero, norm ); + + F32 hit = plane.intersect( start, end ); + if ( hit < 0.0f || hit > 1.0f ) + return false; + + info->t = hit; + info->object = this; + info->point = start + ( ( end - start ) * hit ); + info->normal = norm; + info->material = mMatInstances[ WaterMat ]; + + return true; +} + +F32 WaterPlane::getWaterCoverage( const Box3F &testBox ) const +{ + F32 posZ = getPosition().z; + + F32 coverage = 0.0f; + + if ( posZ > testBox.minExtents.z ) + { + if ( posZ < testBox.maxExtents.z ) + coverage = (posZ - testBox.minExtents.z) / (testBox.maxExtents.z - testBox.minExtents.z); + else + coverage = 1.0f; + } + + return coverage; +} + +F32 WaterPlane::getSurfaceHeight( const Point2F &pos ) const +{ + return getPosition().z; +} + +void WaterPlane::onReflectionInfoChanged() +{ + /* + if ( isClientObject() && GFX->getPixelShaderVersion() >= 1.4 ) + { + if ( mFullReflect ) + REFLECTMGR->registerObject( this, ReflectDelegate( this, &WaterPlane::updateReflection ), mReflectPriority, mReflectMaxRateMs, mReflectMaxDist ); + else + { + REFLECTMGR->unregisterObject( this ); + mReflectTex = NULL; + } + } + */ +} + +void WaterPlane::setGridSize( U32 inSize ) +{ + if ( inSize == mGridSize ) + return; + + // GridSize must be an odd number. + if ( inSize % 2 == 0 ) + inSize++; + + // GridSize must be at least 7 + inSize = getMax( inSize, (U32)7 ); + + mGridSize = inSize; + mGridSizeMinusOne = mGridSize - 1; + mGenerateVB = true; + setMaskBits( UpdateMask ); +} + +void WaterPlane::setGridElementSize( F32 inSize ) +{ + if ( inSize == mGridElementSize ) + return; + + // GridElementSize must be greater than 0 + inSize = getMax( inSize, 0.0001f ); + + mGridElementSize = inSize; + mGenerateVB = true; + setMaskBits( UpdateMask ); +} + +bool WaterPlane::protectedSetGridSize( void *obj, const char *data ) +{ + WaterPlane *object = static_cast(obj); + S32 size = dAtoi( data ); + + object->setGridSize( size ); + + // We already set the field. + return false; +} + +bool WaterPlane::protectedSetGridElementSize( void *obj, const char *data ) +{ + WaterPlane *object = static_cast(obj); + F32 size = dAtof( data ); + + object->setGridElementSize( size ); + + // We already set the field. + return false; +} + +void WaterPlane::_getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos ) +{ + outPos = getPosition(); + outPlane.set( outPos, Point3F(0,0,1) ); +} \ No newline at end of file diff --git a/environment/waterPlane.h b/environment/waterPlane.h new file mode 100644 index 0000000..c9c644c --- /dev/null +++ b/environment/waterPlane.h @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WATERPLANE_H_ +#define _WATERPLANE_H_ + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase.h" +#endif +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _SCENEDATA_H_ +#include "materials/sceneData.h" +#endif +#ifndef _MATINSTANCE_H_ +#include "materials/matInstance.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif +#ifndef _WATEROBJECT_H_ +#include "environment/waterObject.h" +#endif + +class AudioEnvironment; + + +//***************************************************************************** +// WaterBlock +//***************************************************************************** +class WaterPlane : public WaterObject +{ + typedef WaterObject Parent; + +public: + + // LEGACY support + enum EWaterType + { + eWater = 0, + eOceanWater = 1, + eRiverWater = 2, + eStagnantWater = 3, + eLava = 4, + eHotLava = 5, + eCrustyLava = 6, + eQuicksand = 7, + }; + +private: + + enum MaskBits { + UpdateMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + + // vertex / index buffers + GFXVertexBufferHandle mVertBuff; + GFXPrimitiveBufferHandle mPrimBuff; + + // misc + U32 mGridSize; + U32 mGridSizeMinusOne; + F32 mGridElementSize; + U32 mVertCount; + U32 mIndxCount; + U32 mPrimCount; + Frustum mFrustum; + + SceneGraphData setupSceneGraphInfo( SceneState *state ); + void setShaderParams( SceneState *state, BaseMatInstance* mat, const WaterMatParams& paramHandles ); + void setupVBIB( const Point3F &camPos ); + virtual bool prepRenderImage( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState = false ); + virtual void innerRender( SceneState *state ); + void setMultiPassProjection(); + +protected: + + //------------------------------------------------------- + // Standard engine functions + //------------------------------------------------------- + bool onAdd(); + void onRemove(); + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + +public: + WaterPlane(); + virtual ~WaterPlane(); + + DECLARE_CONOBJECT(WaterPlane); + + static void initPersistFields(); + void onStaticModified( const char* slotName, const char*newValue = NULL ); + virtual void inspectPostApply(); + virtual void setTransform( const MatrixF & mat ); + + // WaterObject + virtual F32 getWaterCoverage( const Box3F &worldBox ) const; + virtual F32 getSurfaceHeight( const Point2F &pos ) const; + virtual void onReflectionInfoChanged(); + virtual bool isUnderwater( const Point3F &pnt ); + + // WaterBlock + bool isPointSubmerged ( const Point3F &pos, bool worldSpace = true ) const{ return true; } + AudioEnvironment * getAudioEnvironment(){ return NULL; } + + // WaterPlane + void setGridSize( U32 inSize ); + void setGridElementSize( F32 inSize ); + + // Protected Set'ers + static bool protectedSetGridSize( void *obj, const char *data ); + static bool protectedSetGridElementSize( void *obj, const char *data ); + +protected: + + // WaterObject + virtual void _getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos ); +}; + +#endif // _WATERPLANE_H_ diff --git a/gfx/D3D/screenshotD3D.cpp b/gfx/D3D/screenshotD3D.cpp new file mode 100644 index 0000000..71e967c --- /dev/null +++ b/gfx/D3D/screenshotD3D.cpp @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "d3d9.h" +#include "d3dx9core.h" +#include "d3dx9tex.h" +#include "gfx/D3D9/gfxD3D9Device.h" +#include "screenshotD3D.h" +#include "core/stream/fileStream.h" + +//----------------------------------------------------------------------------- +// Capture standard screenshot - read it from the back buffer +// This function jumps through some hoops copying surfaces around so that +// the screenshot will work when using a multisample back buffer. +//----------------------------------------------------------------------------- +void ScreenShotD3D::captureStandard() +{ + LPDIRECT3DDEVICE9 D3DDevice = dynamic_cast(GFX)->getDevice(); + + // CodeReview - We should probably just be doing this on Canvas and getting + // the data from the GFXWindowTarget - [bjg 5/1/07] + // grab the back buffer + IDirect3DSurface9 * backBuffer; + D3DDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer ); + + // Figure the size we're snagging. + D3DSURFACE_DESC desc; + backBuffer->GetDesc(&desc); + + Point2I size; + size.x = desc.Width; + size.y = desc.Height; + + // set up the 2 copy surfaces + GFXTexHandle tex[2]; + IDirect3DSurface9 *surface[2]; + + tex[0].set( size.x, size.y, GFXFormatR8G8B8X8, &GFXDefaultRenderTargetProfile, avar("%s() - tex[0] (line %d)", __FUNCTION__, __LINE__) ); + tex[1].set( size.x, size.y, GFXFormatR8G8B8X8, &GFXSystemMemProfile, avar("%s() - tex[1] (line %d)", __FUNCTION__, __LINE__) ); + + // grab the top level surface of tex 0 + GFXD3D9TextureObject *to = (GFXD3D9TextureObject *) &(*tex[0]); + D3D9Assert( to->get2DTex()->GetSurfaceLevel( 0, &surface[0] ), NULL ); + + // use StretchRect because it allows a copy from a multisample surface + // to a normal rendertarget surface + D3DDevice->StretchRect( backBuffer, NULL, surface[0], NULL, D3DTEXF_NONE ); + + // grab the top level surface of tex 1 + to = (GFXD3D9TextureObject *) &(*tex[1]); + D3D9Assert( to->get2DTex()->GetSurfaceLevel( 0, &surface[1] ), NULL ); + + // copy the data from the render target to the system memory texture + D3DDevice->GetRenderTargetData( surface[0], surface[1] ); + + // save it off + D3DXIMAGE_FILEFORMAT format; + + if( dStrstr( (const char*)mFilename, ".jpg" ) ) + { + format = D3DXIFF_JPG; + } + else + { + format = D3DXIFF_PNG; + } + + GFXD3DX.D3DXSaveSurfaceToFile( mFilename.utf16(), format, surface[1], NULL, NULL ); + + // release the COM pointers + surface[0]->Release(); + surface[1]->Release(); + backBuffer->Release(); + + mPending = false; +} + +void saveRT_to_bitmap(GFXTexHandle &texToSave, const char *filename) +{ + LPDIRECT3DDEVICE9 D3DDevice = dynamic_cast(GFX)->getDevice(); + + Point2I size(texToSave.getWidth(), texToSave.getHeight()); + + GFXTexHandle sysTex; + sysTex.set( size.x, size.y, GFXFormatR8G8B8X8, &GFXSystemMemProfile, avar("%s() - sysTex (line %d)", __FUNCTION__, __LINE__) ); + + IDirect3DSurface9 *surface[2]; + + // grab the top level surface of tex to save + GFXD3D9TextureObject *to = (GFXD3D9TextureObject *) &(*texToSave); + D3D9Assert( to->get2DTex()->GetSurfaceLevel( 0, &surface[0] ), NULL ); + + // grab the top level surface of tex 1 + to = (GFXD3D9TextureObject *) &(*sysTex); + D3D9Assert( to->get2DTex()->GetSurfaceLevel( 0, &surface[1] ), NULL ); + + // copy the data from the render target to the system memory texture + D3DDevice->GetRenderTargetData( surface[0], surface[1] ); + + // save it off + D3DXIMAGE_FILEFORMAT format; + + if( dStrstr( (const char*)filename, ".jpg" ) ) + { + format = D3DXIFF_JPG; + } + else + { + format = D3DXIFF_PNG; + } + + GFXD3DX.D3DXSaveSurfaceToFileA( filename, format, surface[1], NULL, NULL ); + + // release the COM pointers + surface[0]->Release(); + surface[1]->Release(); +} + diff --git a/gfx/D3D/screenshotD3D.h b/gfx/D3D/screenshotD3D.h new file mode 100644 index 0000000..e612f6e --- /dev/null +++ b/gfx/D3D/screenshotD3D.h @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SCREENSHOTD3D_H_ +#define _SCREENSHOTD3D_H_ + +#include "gfx/screenshot.h" + +//************************************************************************** +// D3D implementation of screenshot +//************************************************************************** +class ScreenShotD3D : public ScreenShot +{ + +public: + + /// captures the back buffer + virtual void captureStandard(); + +}; + + +#endif // _SCREENSHOTD3D_H_ diff --git a/gfx/D3D9/d3dx9Functions.h b/gfx/D3D9/d3dx9Functions.h new file mode 100644 index 0000000..20ed584 --- /dev/null +++ b/gfx/D3D9/d3dx9Functions.h @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +D3DX_FUNCTION( D3DXCreateBuffer, HRESULT, + (DWORD NumBytes, + LPD3DXBUFFER * ppBuffer) ) + +D3DX_FUNCTION( D3DXSaveSurfaceToFileA, HRESULT, + (LPCSTR pDestFile, + D3DXIMAGE_FILEFORMAT DestFormat, + LPDIRECT3DSURFACE9 pSrcSurface, + CONST PALETTEENTRY* pSrcPalette, + CONST RECT* pSrcRect) ) + +D3DX_FUNCTION( D3DXSaveSurfaceToFileW, HRESULT, + (LPCWSTR pDestFile, + D3DXIMAGE_FILEFORMAT DestFormat, + LPDIRECT3DSURFACE9 pSrcSurface, + CONST PALETTEENTRY* pSrcPalette, + CONST RECT* pSrcRect) ) + +D3DX_FUNCTION( D3DXCompileShader, HRESULT, + (LPCSTR pSrcData, + UINT srcDataLen, + CONST D3DXMACRO* pDefines, + LPD3DXINCLUDE pInclude, + LPCSTR pFunctionName, + LPCSTR pProfile, + DWORD Flags, + LPD3DXBUFFER* ppShader, + LPD3DXBUFFER* ppErrorMsgs, + LPD3DXCONSTANTTABLE * ppConstantTable) ) + +D3DX_FUNCTION( D3DXGetShaderConstantTable, HRESULT, + (CONST DWORD* pFunction, + LPD3DXCONSTANTTABLE* ppConstantTable) ) + + +D3DX_FUNCTION( D3DXLoadSurfaceFromSurface, HRESULT, + (LPDIRECT3DSURFACE9 pDestSurface, + CONST PALETTEENTRY* pDestPalette, + CONST RECT* pDestRect, + LPDIRECT3DSURFACE9 pSrcSurface, + CONST PALETTEENTRY* pSrcPalette, + CONST RECT* pSrcRect, + DWORD Filter, + D3DCOLOR ColorKey) ) + +D3DX_FUNCTION( D3DXCreateVolumeTexture, HRESULT, + (LPDIRECT3DDEVICE9 pDevice, + UINT Width, + UINT Height, + UINT Depth, + UINT MipLevels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + LPDIRECT3DVOLUMETEXTURE9* ppVolumeTexture) ) + +D3DX_FUNCTION( D3DXCreateTexture, HRESULT, + (LPDIRECT3DDEVICE9 pDevice, + UINT Width, + UINT Height, + UINT MipLevels, + DWORD Usage, + D3DFORMAT Format, + D3DPOOL Pool, + LPDIRECT3DTEXTURE9* ppTexture) ) + +#ifdef TORQUE_OS_XENON +D3DX_FUNCTION( D3DXLoadVolumeFromMemory, HRESULT, + (LPDIRECT3DVOLUME9 pDestVolume, + CONST PALETTEENTRY* pDestPalette, + CONST D3DBOX* pDestBox, + LPCVOID pSrcMemory, + D3DFORMAT SrcFormat, + UINT SrcRowPitch, + UINT SrcSlicePitch, + CONST PALETTEENTRY* pSrcPalette, + CONST D3DBOX* pSrcBox, + BOOL SrcParentPacked, + UINT SrcParentWidth, + UINT SrcParentHeight, + UINT SrcParentDepth, + DWORD Filter, + D3DCOLOR ColorKey) ) +#else +D3DX_FUNCTION( D3DXLoadVolumeFromMemory, HRESULT, + (LPDIRECT3DVOLUME9 pDestVolume, + CONST PALETTEENTRY* pDestPalette, + CONST D3DBOX* pDestBox, + LPCVOID pSrcMemory, + D3DFORMAT SrcFormat, + UINT SrcRowPitch, + UINT SrcSlicePitch, + CONST PALETTEENTRY* pSrcPalette, + CONST D3DBOX* pSrcBox, + DWORD Filter, + D3DCOLOR ColorKey) ) +#endif + +D3DX_FUNCTION( D3DXSaveTextureToFileInMemory, HRESULT, + (LPD3DXBUFFER *ppDestBuf, + D3DXIMAGE_FILEFORMAT DestFormat, + LPDIRECT3DBASETEXTURE9 pSrcTexture, + const PALETTEENTRY *pSrcPalette) ) + +D3DX_FUNCTION( D3DXGetImageInfoFromFileInMemory, HRESULT, + (LPCVOID pSrcData, + UINT SrcDataSize, + D3DXIMAGE_INFO* pSrcInfo) ) + +D3DX_FUNCTION( D3DXLoadSurfaceFromFileInMemory, HRESULT, + (LPDIRECT3DSURFACE9 pDestSurface, + CONST PALETTEENTRY * pDestPalette, + CONST RECT * pDestRect, + LPCVOID pSrcData, + UINT SrcData, + CONST RECT * pSrcRect, + DWORD Filter, + D3DCOLOR ColorKey, + D3DXIMAGE_INFO * pSrcInfo) ) + +D3DX_FUNCTION( D3DXSaveSurfaceToFileInMemory, HRESULT, + (LPD3DXBUFFER* ppDestBuf, + D3DXIMAGE_FILEFORMAT DestFormat, + LPDIRECT3DSURFACE9 pSrcSurface, + CONST PALETTEENTRY* pSrcPalette, + CONST RECT* pSrcRect) ) diff --git a/gfx/D3D9/gfxD3D9CardProfiler.cpp b/gfx/D3D9/gfxD3D9CardProfiler.cpp new file mode 100644 index 0000000..c9a23c6 --- /dev/null +++ b/gfx/D3D9/gfxD3D9CardProfiler.cpp @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/gfxD3D9Device.h" +#include "console/console.h" +#include "gfx/primBuilder.h" +#include "gfx/D3D9/gfxD3D9CardProfiler.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#ifdef TORQUE_OS_WIN32 +#include "platformWin32/videoInfo/wmiVideoInfo.h" +#endif + + +GFXD3D9CardProfiler::GFXD3D9CardProfiler() : GFXCardProfiler() +{ + +} + +GFXD3D9CardProfiler::~GFXD3D9CardProfiler() +{ + +} + +void GFXD3D9CardProfiler::init() +{ + mD3DDevice = dynamic_cast(GFX)->getDevice(); + AssertISV( mD3DDevice, "GFXD3D9CardProfiler::init() - No D3D9 Device found!"); + + // Grab the caps so we can get our adapter ordinal and look up our name. + D3DCAPS9 caps; + D3D9Assert(mD3DDevice->GetDeviceCaps(&caps), "GFXD3D9CardProfiler::init - failed to get device caps!"); + +#ifdef TORQUE_OS_XENON + mCardDescription = "Xbox360 GPU"; + mChipSet = "ATI"; + mVersionString = String::ToString(_XDK_VER); + mVideoMemory = 512 * 1024 * 1024; +#else + WMIVideoInfo wmiVidInfo; + if( wmiVidInfo.profileAdapters() ) + { + const PlatformVideoInfo::PVIAdapter &adapter = wmiVidInfo.getAdapterInformation( caps.AdapterOrdinal ); + + mCardDescription = adapter.description; + mChipSet = adapter.chipSet; + mVersionString = adapter.driverVersion; + mVideoMemory = adapter.vram; + } +#endif + + Parent::init(); +} + +void GFXD3D9CardProfiler::setupCardCapabilities() +{ + // Get the D3D device caps + D3DCAPS9 caps; + mD3DDevice->GetDeviceCaps(&caps); + + setCapability( "autoMipMapLevel", ( caps.Caps2 & D3DCAPS2_CANAUTOGENMIPMAP ? 1 : 0 ) ); + + setCapability( "maxTextureWidth", caps.MaxTextureWidth ); + setCapability( "maxTextureHeight", caps.MaxTextureHeight ); + setCapability( "maxTextureSize", getMin( (U32)caps.MaxTextureWidth, (U32)caps.MaxTextureHeight) ); + + bool canDoLERPDetailBlend = ( caps.TextureOpCaps & D3DTEXOPCAPS_LERP ) && ( caps.MaxTextureBlendStages > 1 ); + + bool canDoFourStageDetailBlend = ( caps.TextureOpCaps & D3DTEXOPCAPS_SUBTRACT ) && + ( caps.PrimitiveMiscCaps & D3DPMISCCAPS_TSSARGTEMP ) && + ( caps.MaxTextureBlendStages > 3 ); + + setCapability( "lerpDetailBlend", canDoLERPDetailBlend ); + setCapability( "fourStageDetailBlend", canDoFourStageDetailBlend ); +} + +bool GFXD3D9CardProfiler::_queryCardCap(const String &query, U32 &foundResult) +{ + return 0; +} + +bool GFXD3D9CardProfiler::_queryFormat( const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips ) +{ + LPDIRECT3D9 pD3D = dynamic_cast(GFX)->getD3D(); + D3DDISPLAYMODE displayMode; + mD3DDevice->GetDisplayMode( 0, &displayMode ); + + DWORD usage = 0; + D3DRESOURCETYPE rType = D3DRTYPE_TEXTURE; + + D3DFORMAT texFormat = GFXD3D9TextureFormat[fmt]; + +#if defined(TORQUE_OS_XENON) + inOutAutogenMips = false; + + if(profile->isRenderTarget()) + { + texFormat = (D3DFORMAT)MAKELEFMT(texFormat); + } +#else + if( profile->isRenderTarget() ) + usage |= D3DUSAGE_RENDERTARGET; + else if( profile->isZTarget() ) + { + usage |= D3DUSAGE_DEPTHSTENCIL; + rType = D3DRTYPE_SURFACE; + } + + if( inOutAutogenMips ) + usage |= D3DUSAGE_AUTOGENMIPMAP; +#endif + + // Early-check to see if the enum translation table has an unsupported value + if(texFormat == (_D3DFORMAT)GFX_UNSUPPORTED_VAL) + return false; + + HRESULT hr = pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, + displayMode.Format, usage, rType, texFormat ); + + bool retVal = SUCCEEDED( hr ); + +#if !defined(TORQUE_OS_XENON) + // If check device format failed, and auto gen mips were requested, try again + // without autogen mips. + if( !retVal && inOutAutogenMips ) + { + usage ^= D3DUSAGE_AUTOGENMIPMAP; + + hr = pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, + displayMode.Format, usage, D3DRTYPE_TEXTURE, GFXD3D9TextureFormat[fmt] ); + + retVal = SUCCEEDED( hr ); + + // If this one passed, auto gen mips are not supported with this format, + // so set the variable properly + if( retVal ) + inOutAutogenMips = false; + } +#endif + + return retVal; +} \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9CardProfiler.h b/gfx/D3D9/gfxD3D9CardProfiler.h new file mode 100644 index 0000000..fce456d --- /dev/null +++ b/gfx/D3D9/gfxD3D9CardProfiler.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXD3D9CARDPROFILER_H_ +#define _GFXD3D9CARDPROFILER_H_ + +#ifndef _D3D9_H_ +#include +#endif + +#include "gfx/gfxCardProfile.h" + + +class GFXD3D9CardProfiler : public GFXCardProfiler +{ +private: + typedef GFXCardProfiler Parent; + + LPDIRECT3DDEVICE9 mD3DDevice; + UINT mAdapterOrdinal; + +public: + GFXD3D9CardProfiler(); + ~GFXD3D9CardProfiler(); + void init(); + +protected: + const String &getRendererString() const { static String sRS("D3D9"); return sRS; } + + void setupCardCapabilities(); + bool _queryCardCap(const String &query, U32 &foundResult); + bool _queryFormat(const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips); +}; + +#endif diff --git a/gfx/D3D9/gfxD3D9Cubemap.cpp b/gfx/D3D9/gfxD3D9Cubemap.cpp new file mode 100644 index 0000000..2195cc1 --- /dev/null +++ b/gfx/D3D9/gfxD3D9Cubemap.cpp @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/gfxD3D9Cubemap.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" + +_D3DCUBEMAP_FACES GFXD3D9Cubemap::faceList[6] = +{ + D3DCUBEMAP_FACE_POSITIVE_X, D3DCUBEMAP_FACE_NEGATIVE_X, + D3DCUBEMAP_FACE_POSITIVE_Y, D3DCUBEMAP_FACE_NEGATIVE_Y, + D3DCUBEMAP_FACE_POSITIVE_Z, D3DCUBEMAP_FACE_NEGATIVE_Z +}; + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +GFXD3D9Cubemap::GFXD3D9Cubemap() +{ + mCubeTex = NULL; + mDynamic = false; + mFaceFormat = GFXFormatR8G8B8A8; +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +GFXD3D9Cubemap::~GFXD3D9Cubemap() +{ + releaseSurfaces(); + + if ( mDynamic ) + GFXTextureManager::removeEventDelegate( this, &GFXD3D9Cubemap::_onTextureEvent ); +} + +//----------------------------------------------------------------------------- +// Release D3D surfaces +//----------------------------------------------------------------------------- +void GFXD3D9Cubemap::releaseSurfaces() +{ + if ( !mCubeTex ) + return; + + mCubeTex->Release(); + mCubeTex = NULL; +} + +void GFXD3D9Cubemap::_onTextureEvent( GFXTexCallbackCode code ) +{ + // Can this happen? + if ( !mDynamic ) + return; + + if ( code == GFXZombify ) + releaseSurfaces(); + else if ( code == GFXResurrect ) + initDynamic( mTexSize ); +} + +//----------------------------------------------------------------------------- +// Init Static +//----------------------------------------------------------------------------- +void GFXD3D9Cubemap::initStatic( GFXTexHandle *faces ) +{ + //if( mCubeTex ) + // return; + + if( faces ) + { + AssertFatal( faces[0], "empty texture passed to CubeMap::create" ); + + + LPDIRECT3DDEVICE9 D3D9Device = static_cast(GFX)->getDevice(); + + // NOTE - check tex sizes on all faces - they MUST be all same size + mTexSize = faces[0].getWidth(); + mFaceFormat = faces[0].getFormat(); + + D3D9Assert( D3D9Device->CreateCubeTexture( mTexSize, 1, 0, GFXD3D9TextureFormat[mFaceFormat], + D3DPOOL_MANAGED, &mCubeTex, NULL ), NULL ); + + fillCubeTextures( faces, D3D9Device ); +// mCubeTex->GenerateMipSubLevels(); + } +} + + +//----------------------------------------------------------------------------- +// Init Dynamic +//----------------------------------------------------------------------------- +void GFXD3D9Cubemap::initDynamic( U32 texSize, GFXFormat faceFormat ) +{ + if ( mCubeTex ) + return; + + if ( !mDynamic ) + GFXTextureManager::addEventDelegate( this, &GFXD3D9Cubemap::_onTextureEvent ); + + mDynamic = true; + mTexSize = texSize; + mFaceFormat = faceFormat; + + LPDIRECT3DDEVICE9 D3D9Device = reinterpret_cast(GFX)->getDevice(); + + // might want to try this as a 16 bit texture... + D3D9Assert( D3D9Device->CreateCubeTexture( texSize, + 1, +#ifdef TORQUE_OS_XENON + 0, +#else + D3DUSAGE_RENDERTARGET, +#endif + GFXD3D9TextureFormat[faceFormat], + D3DPOOL_DEFAULT, + &mCubeTex, + NULL ), NULL ); +} + +//----------------------------------------------------------------------------- +// Fills in face textures of cube map from existing textures +//----------------------------------------------------------------------------- +void GFXD3D9Cubemap::fillCubeTextures( GFXTexHandle *faces, LPDIRECT3DDEVICE9 D3DDevice ) +{ + for( U32 i=0; i<6; i++ ) + { + // get cube face surface + IDirect3DSurface9 *cubeSurf = NULL; + D3D9Assert( mCubeTex->GetCubeMapSurface( faceList[i], 0, &cubeSurf ), NULL ); + + // get incoming texture surface + GFXD3D9TextureObject *texObj = dynamic_cast( (GFXTextureObject*)faces[i] ); + IDirect3DSurface9 *inSurf; + D3D9Assert( texObj->get2DTex()->GetSurfaceLevel( 0, &inSurf ), NULL ); + + // copy incoming texture into cube face + D3D9Assert( GFXD3DX.D3DXLoadSurfaceFromSurface( cubeSurf, NULL, NULL, inSurf, NULL, + NULL, D3DX_FILTER_NONE, 0 ), NULL ); + cubeSurf->Release(); + inSurf->Release(); + } +} + +//----------------------------------------------------------------------------- +// Set the cubemap to the specified texture unit num +//----------------------------------------------------------------------------- +void GFXD3D9Cubemap::setToTexUnit( U32 tuNum ) +{ + static_cast(GFX)->getDevice()->SetTexture( tuNum, mCubeTex ); +} + +void GFXD3D9Cubemap::zombify() +{ + // Static cubemaps are handled by D3D + if( mDynamic ) + releaseSurfaces(); +} + +void GFXD3D9Cubemap::resurrect() +{ + // Static cubemaps are handled by D3D + if( mDynamic ) + initDynamic( mTexSize, mFaceFormat ); +} diff --git a/gfx/D3D9/gfxD3D9Cubemap.h b/gfx/D3D9/gfxD3D9Cubemap.h new file mode 100644 index 0000000..326a2ef --- /dev/null +++ b/gfx/D3D9/gfxD3D9Cubemap.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GFXD3D9CUBEMAP_H_ +#define _GFXD3D9CUBEMAP_H_ + +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/gfxCubemap.h" +#include "gfx/gfxResource.h" +#include "gfx/gfxTarget.h" + +//************************************************************************** +// Cube map +//************************************************************************** +class GFXD3D9Cubemap : public GFXCubemap +{ +public: + virtual void initStatic( GFXTexHandle *faces ); + virtual void initDynamic( U32 texSize, GFXFormat faceFormat = GFXFormatR8G8B8A8 ); + virtual void setToTexUnit( U32 tuNum ); + virtual U32 getSize() const { return mTexSize; } + virtual GFXFormat getFormat() const { return mFaceFormat; } + + GFXD3D9Cubemap(); + virtual ~GFXD3D9Cubemap(); + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + +protected: + + friend class GFXPCD3D9TextureTarget; + friend class GFX360TextureTarget; + friend class GFXD3D9Device; + + LPDIRECT3DCUBETEXTURE9 mCubeTex; + + static _D3DCUBEMAP_FACES faceList[6]; + bool mDynamic; + U32 mTexSize; + GFXFormat mFaceFormat; + + void fillCubeTextures( GFXTexHandle *faces, LPDIRECT3DDEVICE9 D3DDevice ); + void releaseSurfaces(); + + /// The callback used to get texture events. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ); +}; + +#endif // GFXD3D9CUBEMAP diff --git a/gfx/D3D9/gfxD3D9Device.cpp b/gfx/D3D9/gfxD3D9Device.cpp new file mode 100644 index 0000000..86c5c7a --- /dev/null +++ b/gfx/D3D9/gfxD3D9Device.cpp @@ -0,0 +1,1054 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/gfxD3D9Device.h" + +#include "console/console.h" +#include "core/stream/fileStream.h" +#include "core/strings/unicode.h" +#include "gfx/primBuilder.h" +#include "gfx/D3D9/gfxD3D9CardProfiler.h" +#include "gfx/D3D9/gfxD3D9VertexBuffer.h" +#include "gfx/D3D/screenShotD3D.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#include "gfx/D3D9/gfxD3D9QueryFence.h" +#include "gfx/D3D9/gfxD3D9OcclusionQuery.h" +#include "gfx/D3D9/gfxD3D9Shader.h" +#include "windowManager/platformWindow.h" +#ifndef TORQUE_OS_XENON +# include "windowManager/win32/win32Window.h" +#endif + +D3DXFNTable GFXD3D9Device::smD3DX; + +GFXD3D9Device::GFXD3D9Device( LPDIRECT3D9 d3d, U32 index ) +{ + mDeviceSwizzle32 = &Swizzles::bgra; + GFXVertexColor::setSwizzle( mDeviceSwizzle32 ); + + mDeviceSwizzle24 = &Swizzles::bgr; + + mD3D = d3d; + mAdapterIndex = index; + mD3DDevice = NULL; + mCurrentOpenAllocVB = NULL; + mCurrentVB = NULL; + + mCurrentOpenAllocPB = NULL; + mCurrentPB = NULL; + mDynamicPB = NULL; + + mLastVertShader = NULL; + mLastPixShader = NULL; + + mCanCurrentlyRender = false; + mTextureManager = NULL; + mCurrentStateBlock = NULL; + mResourceListHead = NULL; + +#ifdef TORQUE_DEBUG + mVBListHead = NULL; + mNumAllocatedVertexBuffers = 0; +#endif + + mCurrentOpenAllocVertexData = NULL; + mPixVersion = 0.0; + mNumSamplers = 0; + mNumRenderTargets = 0; + + mCardProfiler = NULL; + + mDeviceDepthStencil = NULL; + mDeviceBackbuffer = NULL; + mDeviceColor = NULL; + + mCreateFenceType = -1; // Unknown, test on first allocate + + mCurrentConstBuffer = NULL; + + mOcclusionQuerySupported = false; + + // Set up the Enum translation tables + GFXD3D9EnumTranslate::init(); +} + +//----------------------------------------------------------------------------- + +GFXD3D9Device::~GFXD3D9Device() +{ + // Release our refcount on the current stateblock object + mCurrentStateBlock = NULL; + + releaseDefaultPoolResources(); + + // Free the vertex declarations. + VertexDeclMap::Iterator iter = mVertexDecls.begin(); + for ( ; iter != mVertexDecls.end(); iter++ ) + SAFE_RELEASE( iter->value ); + + // Check up on things + Con::printf("Cur. D3DDevice ref count=%d", mD3DDevice->AddRef() - 1); + mD3DDevice->Release(); + + // Forcibly clean up the pools + mVolatileVBList.setSize(0); + mDynamicPB = NULL; + + // And release our D3D resources. + SAFE_RELEASE( mDeviceDepthStencil ); + SAFE_RELEASE( mDeviceBackbuffer ) + SAFE_RELEASE( mDeviceColor ); + SAFE_RELEASE( mD3D ); + SAFE_RELEASE( mD3DDevice ); + +#ifdef TORQUE_DEBUG + logVertexBuffers(); +#endif + + if( mCardProfiler ) + { + delete mCardProfiler; + mCardProfiler = NULL; + } + + if( gScreenShot ) + { + delete gScreenShot; + gScreenShot = NULL; + } +} + +//------------------------------------------------------------------------------ +// setupGenericShaders - This function is totally not needed on PC because there +// is fixed-function support in D3D9 +//------------------------------------------------------------------------------ +inline void GFXD3D9Device::setupGenericShaders( GenericShaderType type /* = GSColor */ ) +{ +#ifdef WANT_TO_SIMULATE_UI_ON_360 + if( mGenericShader[GSColor] == NULL ) + { + mGenericShader[GSColor] = createShader( "shaders/common/genericColorV.hlsl", + "shaders/common/genericColorP.hlsl", + 2.f ); + + mGenericShader[GSModColorTexture] = createShader( "shaders/common/genericModColorTextureV.hlsl", + "shaders/common/genericModColorTextureP.hlsl", + 2.f ); + + mGenericShader[GSAddColorTexture] = createShader( "shaders/common/genericAddColorTextureV.hlsl", + "shaders/common/genericAddColorTextureP.hlsl", + 2.f ); + } + + mGenericShader[type]->process(); + + MatrixF world, view, proj; + mWorldMatrix[mWorldStackSize].transposeTo( world ); + mViewMatrix.transposeTo( view ); + mProjectionMatrix.transposeTo( proj ); + + mTempMatrix = world * view * proj; + + setVertexShaderConstF( VC_WORLD_PROJ, (F32 *)&mTempMatrix, 4 ); +#else + disableShaders(); +#endif +} + +//----------------------------------------------------------------------------- +/// Creates a state block object based on the desc passed in. This object +/// represents an immutable state. +GFXStateBlockRef GFXD3D9Device::createStateBlockInternal(const GFXStateBlockDesc& desc) +{ + return GFXStateBlockRef(new GFXD3D9StateBlock(desc, mD3DDevice)); +} + +/// Activates a stateblock +void GFXD3D9Device::setStateBlockInternal(GFXStateBlock* block, bool force) +{ + AssertFatal(dynamic_cast(block), "Incorrect stateblock type for this device!"); + GFXD3D9StateBlock* d3dBlock = static_cast(block); + GFXD3D9StateBlock* d3dCurrent = static_cast(mCurrentStateBlock.getPointer()); + if (force) + d3dCurrent = NULL; + d3dBlock->activate(d3dCurrent); +} + +/// Called by base GFXDevice to actually set a const buffer +void GFXD3D9Device::setShaderConstBufferInternal(GFXShaderConstBuffer* buffer) +{ + if (buffer) + { + PROFILE_SCOPE(GFXD3D9Device_setShaderConstBufferInternal); + AssertFatal(dynamic_cast(buffer), "Incorrect shader const buffer type for this device!"); + GFXD3D9ShaderConstBuffer* d3dBuffer = static_cast(buffer); + + d3dBuffer->activate(mCurrentConstBuffer); + mCurrentConstBuffer = d3dBuffer; + } else { + mCurrentConstBuffer = NULL; + } +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::clear( U32 flags, ColorI color, F32 z, U32 stencil ) +{ + // Make sure we have flushed our render target state. + _updateRenderTargets(); + + // Kind of a bummer we have to do this, there should be a better way made + DWORD realflags = 0; + + if( flags & GFXClearTarget ) + realflags |= D3DCLEAR_TARGET; + + if( flags & GFXClearZBuffer ) + realflags |= D3DCLEAR_ZBUFFER; + + if( flags & GFXClearStencil ) + realflags |= D3DCLEAR_STENCIL; + + mD3DDevice->Clear( 0, NULL, realflags, + D3DCOLOR_ARGB( color.alpha, color.red, color.green, color.blue ), + z, stencil ); +} + +//----------------------------------------------------------------------------- + +bool GFXD3D9Device::beginSceneInternal() +{ + HRESULT hr = mD3DDevice->BeginScene(); + D3D9Assert(hr, "GFXD3D9Device::beginSceneInternal - failed to BeginScene"); + mCanCurrentlyRender = SUCCEEDED(hr); + return mCanCurrentlyRender; +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::endSceneInternal() +{ + mD3DDevice->EndScene(); + mCanCurrentlyRender = false; +} + +void GFXD3D9Device::_updateRenderTargets() +{ + if ( mRTDirty || ( mCurrentRT && mCurrentRT->isPendingState() ) ) + { + if ( mRTDeactivate ) + { + mRTDeactivate->deactivate(); + mRTDeactivate = NULL; + } + + // NOTE: The render target changes are not really accurate + // as the GFXTextureTarget supports MRT internally. So when + // we activate a GFXTarget it could result in multiple calls + // to SetRenderTarget on the actual device. + mDeviceStatistics.mRenderTargetChanges++; + + mCurrentRT->activate(); + + mRTDirty = false; + } + + if ( mViewportDirty ) + { + D3DVIEWPORT9 viewport; + viewport.X = mViewport.point.x; + viewport.Y = mViewport.point.y; + viewport.Width = mViewport.extent.x; + viewport.Height = mViewport.extent.y; + viewport.MinZ = 0.0; + viewport.MaxZ = 1.0; + + D3D9Assert( mD3DDevice->SetViewport( &viewport ), + "GFXD3D9Device::_updateRenderTargets() - Error setting viewport!" ); + + mViewportDirty = false; + } +} + + +#ifdef TORQUE_DEBUG + +void GFXD3D9Device::logVertexBuffers() +{ + + // NOTE: This function should be called on the destructor of this class and ONLY then + // otherwise it'll produce the wrong output + if( mNumAllocatedVertexBuffers == 0 ) + return; + + FileStream fs; + + fs.open( "vertexbuffer.log", Torque::FS::File::Write ); + + char buff[256]; + + fs.writeLine( (U8 *)avar("-- Vertex buffer memory leak report -- time = %d", Platform::getRealMilliseconds()) ); + dSprintf( (char *)&buff, sizeof( buff ), "%d un-freed vertex buffers", mNumAllocatedVertexBuffers ); + fs.writeLine( (U8 *)buff ); + + GFXD3D9VertexBuffer *walk = mVBListHead; + + while( walk != NULL ) + { + dSprintf( (char *)&buff, sizeof( buff ), "[Name: %s] Size: %d", walk->name, walk->mNumVerts ); + fs.writeLine( (U8 *)buff ); + + walk = walk->next; + } + + fs.writeLine( (U8 *)"-- End report --" ); + + fs.close(); +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::addVertexBuffer( GFXD3D9VertexBuffer *buffer ) +{ + mNumAllocatedVertexBuffers++; + + if( mVBListHead == NULL ) + { + mVBListHead = buffer; + } + else + { + GFXD3D9VertexBuffer *walk = mVBListHead; + + while( walk->next != NULL ) + { + walk = walk->next; + } + + walk->next = buffer; + } + + buffer->next = NULL; +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::removeVertexBuffer( GFXD3D9VertexBuffer *buffer ) +{ + mNumAllocatedVertexBuffers--; + + // Quick check to see if this is head of list + if( mVBListHead == buffer ) + { + mVBListHead = mVBListHead->next; + return; + } + + GFXD3D9VertexBuffer *walk = mVBListHead; + + while( walk->next != NULL ) + { + if( walk->next == buffer ) + { + walk->next = walk->next->next; + return; + } + + walk = walk->next; + } + + AssertFatal( false, "Vertex buffer not found in list." ); +} + +#endif + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::releaseDefaultPoolResources() +{ + // Release all the dynamic vertex buffer arrays + // Forcibly clean up the pools + for( U32 i=0; ivb->AddRef() - 1, mVolatileVBList[i]->mRefCount); + // mVolatileVBList[i]->vb->Release(); + + mVolatileVBList[i]->vb->Release(); + mVolatileVBList[i]->vb = NULL; + mVolatileVBList[i] = NULL; + } + mVolatileVBList.setSize(0); + + // Set current VB to NULL and set state dirty + mCurrentVertexBuffer = NULL; + mVertexBufferDirty = true; + + // Release dynamic index buffer + if( mDynamicPB != NULL ) + { + SAFE_RELEASE( mDynamicPB->ib ); + } + + // Set current PB/IB to NULL and set state dirty + mCurrentPrimitiveBuffer = NULL; + mCurrentPB = NULL; + mPrimitiveBufferDirty = true; + + // Zombify texture manager (for D3D this only modifies default pool textures) + if( mTextureManager ) + mTextureManager->zombify(); + + // Kill off other potentially dangling references... + SAFE_RELEASE( mDeviceDepthStencil ); + SAFE_RELEASE( mDeviceBackbuffer ); + mD3DDevice->SetDepthStencilSurface(NULL); + + // Set global dirty state so the IB/PB and VB get reset + mStateDirty = true; + + // Walk the resource list and zombify everything. + GFXResource *walk = mResourceListHead; + while(walk) + { + walk->zombify(); + walk = walk->getNextResource(); + } +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::reacquireDefaultPoolResources() +{ + // Now do the dynamic index buffers + if( mDynamicPB == NULL ) + mDynamicPB = new GFXD3D9PrimitiveBuffer(this, 0, 0, GFXBufferTypeDynamic); + + D3D9Assert( mD3DDevice->CreateIndexBuffer( sizeof( U16 ) * MAX_DYNAMIC_INDICES, +#ifdef TORQUE_OS_XENON + D3DUSAGE_WRITEONLY, +#else + D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, +#endif + GFXD3D9IndexFormat[GFXIndexFormat16], D3DPOOL_DEFAULT, &mDynamicPB->ib, NULL ), "Failed to allocate dynamic IB" ); + + // Grab the depth-stencil... + SAFE_RELEASE(mDeviceDepthStencil); + D3D9Assert(mD3DDevice->GetDepthStencilSurface(&mDeviceDepthStencil), + "GFXD3D9Device::reacquireDefaultPoolResources - couldn't grab reference to device's depth-stencil surface."); + + SAFE_RELEASE(mDeviceBackbuffer); + mD3DDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &mDeviceBackbuffer ); + + // Walk the resource list and zombify everything. + GFXResource *walk = mResourceListHead; + while(walk) + { + walk->resurrect(); + walk = walk->getNextResource(); + } + + if(mTextureManager) + mTextureManager->resurrect(); +} + +//----------------------------------------------------------------------------- +GFXD3D9VertexBuffer * GFXD3D9Device::findVBPool( const GFXVertexFormat *vertexFormat, U32 vertsNeeded ) +{ + // Verts needed is ignored on the base device, 360 is different + for( U32 i=0; imVertexFormat->isEqual( *vertexFormat ) ) + return mVolatileVBList[i]; + + return NULL; +} + +//----------------------------------------------------------------------------- +GFXD3D9VertexBuffer * GFXD3D9Device::createVBPool( const GFXVertexFormat *vertexFormat, U32 vertSize ) +{ + // this is a bit funky, but it will avoid problems with (lack of) copy constructors + // with a push_back() situation + mVolatileVBList.increment(); + StrongRefPtr newBuff; + mVolatileVBList.last() = new GFXD3D9VertexBuffer(); + newBuff = mVolatileVBList.last(); + + newBuff->mNumVerts = 0; + newBuff->mBufferType = GFXBufferTypeVolatile; + newBuff->mVertexFormat = vertexFormat; + newBuff->mVertexSize = vertSize; + newBuff->mDevice = this; + + allocVertexDecl( newBuff ); + + // Con::printf("Created buff with type %x", vertFlags); + + D3D9Assert( mD3DDevice->CreateVertexBuffer( vertSize * MAX_DYNAMIC_VERTS, +#ifdef TORQUE_OS_XENON + D3DUSAGE_WRITEONLY, +#else + D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, +#endif + 0, + D3DPOOL_DEFAULT, + &newBuff->vb, + NULL ), + "Failed to allocate dynamic VB" ); + return newBuff; +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::setClipRect( const RectI &inRect ) +{ + // Clip the rect against the renderable size. + Point2I size = mCurrentRT->getSize(); + + RectI maxRect(Point2I(0,0), size); + RectI rect = inRect; + rect.intersect(maxRect); + + mClipRect = rect; + + F32 l = F32( mClipRect.point.x ); + F32 r = F32( mClipRect.point.x + mClipRect.extent.x ); + F32 b = F32( mClipRect.point.y + mClipRect.extent.y ); + F32 t = F32( mClipRect.point.y ); + + // Set up projection matrix, + static Point4F pt; + pt.set(2.0f / (r - l), 0.0f, 0.0f, 0.0f); + mTempMatrix.setColumn(0, pt); + + pt.set(0.0f, 2.0f/(t - b), 0.0f, 0.0f); + mTempMatrix.setColumn(1, pt); + + pt.set(0.0f, 0.0f, 1.0f, 0.0f); + mTempMatrix.setColumn(2, pt); + + pt.set((l+r)/(l-r), (t+b)/(b-t), 1.0f, 1.0f); + mTempMatrix.setColumn(3, pt); + + setProjectionMatrix( mTempMatrix ); + + // Set up world/view matrix + mTempMatrix.identity(); + setViewMatrix( mTempMatrix ); + setWorldMatrix( mTempMatrix ); + + setViewport( mClipRect ); +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::setVB( GFXVertexBuffer *buffer ) +{ + AssertFatal( mCurrentOpenAllocVB == NULL, "Calling setVertexBuffer() when a vertex buffer is still open for editing" ); + + mCurrentVB = static_cast( buffer ); + + D3D9Assert( mD3DDevice->SetVertexDeclaration( mCurrentVB->decl ), "Failed to set vertex declaration" ); + D3D9Assert( mD3DDevice->SetStreamSource( 0, mCurrentVB->vb, 0, mCurrentVB->mVertexSize ), "Failed to set stream source" ); +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::_setPrimitiveBuffer( GFXPrimitiveBuffer *buffer ) +{ + AssertFatal( mCurrentOpenAllocPB == NULL, "Calling setIndexBuffer() when a index buffer is still open for editing" ); + + mCurrentPB = static_cast( buffer ); + + D3D9Assert( mD3DDevice->SetIndices( mCurrentPB->ib ), "Failed to set indices" ); +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ) +{ + // This is done to avoid the function call overhead if possible + if( mStateDirty ) + updateStates(); + if (mCurrentShaderConstBuffer) + setShaderConstBufferInternal(mCurrentShaderConstBuffer); + + AssertFatal( mCurrentOpenAllocVB == NULL, "Calling drawPrimitive() when a vertex buffer is still open for editing" ); + AssertFatal( mCurrentVB != NULL, "Trying to call draw primitive with no current vertex buffer, call setVertexBuffer()" ); + + D3D9Assert( mD3DDevice->DrawPrimitive( GFXD3D9PrimType[primType], mCurrentVB->mVolatileStart + vertexStart, primitiveCount ), "Failed to draw primitives" ); + mDeviceStatistics.mDrawCalls++; + mDeviceStatistics.mPolyCount += primitiveCount; +} + +//----------------------------------------------------------------------------- + +void GFXD3D9Device::drawIndexedPrimitive( GFXPrimitiveType primType, + U32 startVertex, + U32 minIndex, + U32 numVerts, + U32 startIndex, + U32 primitiveCount ) +{ + // This is done to avoid the function call overhead if possible + if( mStateDirty ) + updateStates(); + if (mCurrentShaderConstBuffer) + setShaderConstBufferInternal(mCurrentShaderConstBuffer); + + AssertFatal( mCurrentOpenAllocVB == NULL, "Calling drawIndexedPrimitive() when a vertex buffer is still open for editing" ); + AssertFatal( mCurrentVB != NULL, "Trying to call drawIndexedPrimitive with no current vertex buffer, call setVertexBuffer()" ); + + AssertFatal( mCurrentOpenAllocPB == NULL, "Calling drawIndexedPrimitive() when a index buffer is still open for editing" ); + AssertFatal( mCurrentPB != NULL, "Trying to call drawIndexedPrimitive with no current index buffer, call setIndexBuffer()" ); + + D3D9Assert( mD3DDevice->DrawIndexedPrimitive( GFXD3D9PrimType[primType], + mCurrentVB->mVolatileStart + startVertex, + /* mCurrentPB->mVolatileStart + */ minIndex, + numVerts, + mCurrentPB->mVolatileStart + startIndex, + primitiveCount ), "Failed to draw indexed primitive" ); + + mDeviceStatistics.mDrawCalls++; + mDeviceStatistics.mPolyCount += primitiveCount; +} + +GFXShader* GFXD3D9Device::createShader() +{ + GFXD3D9Shader* shader = new GFXD3D9Shader(); + shader->registerResourceWithDevice( this ); + return shader; +} + +void GFXD3D9Device::disableShaders() +{ + setShader( NULL ); + setShaderConstBuffer( NULL ); +} + +//----------------------------------------------------------------------------- +// Set shader - this function exists to make sure this is done in one place, +// and to make sure redundant shader states are not being +// sent to the card. +//----------------------------------------------------------------------------- +void GFXD3D9Device::setShader( GFXShader *shader ) +{ + GFXD3D9Shader *d3dShader = static_cast( shader ); + + IDirect3DPixelShader9 *pixShader = ( d3dShader != NULL ? d3dShader->mPixShader : NULL ); + IDirect3DVertexShader9 *vertShader = ( d3dShader ? d3dShader->mVertShader : NULL ); + + if( pixShader != mLastPixShader ) + { + mD3DDevice->SetPixelShader( pixShader ); + mLastPixShader = pixShader; + } + + if( vertShader != mLastVertShader ) + { + mD3DDevice->SetVertexShader( vertShader ); + mLastVertShader = vertShader; + } + +} + +//----------------------------------------------------------------------------- +// allocPrimitiveBuffer +//----------------------------------------------------------------------------- +GFXPrimitiveBuffer * GFXD3D9Device::allocPrimitiveBuffer(U32 numIndices, U32 numPrimitives, GFXBufferType bufferType) +{ + // Allocate a buffer to return + GFXD3D9PrimitiveBuffer * res = new GFXD3D9PrimitiveBuffer(this, numIndices, numPrimitives, bufferType); + + // Determine usage flags + U32 usage = 0; + D3DPOOL pool = D3DPOOL_DEFAULT; + + // Assumptions: + // - static buffers are write once, use many + // - dynamic buffers are write many, use many + // - volatile buffers are write once, use once + // You may never read from a buffer. + switch(bufferType) + { + case GFXBufferTypeStatic: + pool = D3DPOOL_MANAGED; + break; + + case GFXBufferTypeDynamic: + case GFXBufferTypeVolatile: +#ifndef TORQUE_OS_XENON + usage |= D3DUSAGE_DYNAMIC; +#endif + break; + } + + // Register resource + res->registerResourceWithDevice(this); + + // We never allow reading from a primitive buffer. + usage |= D3DUSAGE_WRITEONLY; + + // Create d3d index buffer + if(bufferType == GFXBufferTypeVolatile) + { + // Get it from the pool if it's a volatile... + AssertFatal( numIndices < MAX_DYNAMIC_INDICES, "Cannot allocate that many indices in a volatile buffer, increase MAX_DYNAMIC_INDICES." ); + + res->ib = mDynamicPB->ib; + // mDynamicPB->ib->AddRef(); + res->mVolatileBuffer = mDynamicPB; + } + else + { + // Otherwise, get it as a seperate buffer... + D3D9Assert(mD3DDevice->CreateIndexBuffer( sizeof(U16) * numIndices , usage, GFXD3D9IndexFormat[GFXIndexFormat16], pool, &res->ib, 0), + "Failed to allocate an index buffer."); + } + + // Return buffer + return res; +} + +//----------------------------------------------------------------------------- +// allocVertexBuffer +//----------------------------------------------------------------------------- +GFXVertexBuffer * GFXD3D9Device::allocVertexBuffer( U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertSize, + GFXBufferType bufferType ) +{ + GFXD3D9VertexBuffer *res = new GFXD3D9VertexBuffer( this, numVerts, vertexFormat, vertSize, bufferType ); + + // Determine usage flags + U32 usage = 0; + D3DPOOL pool = D3DPOOL_DEFAULT; + + res->mNumVerts = 0; + + // Assumptions: + // - static buffers are write once, use many + // - dynamic buffers are write many, use many + // - volatile buffers are write once, use once + // You may never read from a buffer. + switch(bufferType) + { + case GFXBufferTypeStatic: + pool = D3DPOOL_MANAGED; + res->registerResourceWithDevice(this); + break; + + case GFXBufferTypeDynamic: + case GFXBufferTypeVolatile: + pool = D3DPOOL_DEFAULT; + res->registerResourceWithDevice(this); + usage |= D3DUSAGE_WRITEONLY; +#ifndef TORQUE_OS_XENON + usage |= D3DUSAGE_DYNAMIC; +#endif + break; + } + + // Create vertex buffer + if(bufferType == GFXBufferTypeVolatile) + { + // Get volatile stuff from a pool... + AssertFatal( numVerts <= MAX_DYNAMIC_VERTS, "Cannot allocate that many verts in a volatile vertex buffer, increase MAX_DYNAMIC_VERTS! -- BJG" ); + + // This is all we need here, everything else lives in the lock method on the + // buffer... -- BJG + + } + else + { + allocVertexDecl( res ); + + // Get a new buffer... + D3D9Assert( mD3DDevice->CreateVertexBuffer( vertSize * numVerts, usage, 0, pool, &res->vb, NULL ), + "Failed to allocate VB" ); + } + + res->mNumVerts = numVerts; + return res; +} + +//----------------------------------------------------------------------------- +// deallocate vertex buffer +//----------------------------------------------------------------------------- +void GFXD3D9Device::deallocVertexBuffer( GFXD3D9VertexBuffer *vertBuff ) +{ + SAFE_RELEASE(vertBuff->vb); +} + +void GFXD3D9Device::allocVertexDecl( GFXD3D9VertexBuffer *vertBuff ) +{ + PROFILE_SCOPE( GFXD3D9Device_AllocVertexDecl ); + + if ( vertBuff->decl ) + return; + + const GFXVertexFormat *vertexFormat = vertBuff->mVertexFormat; + + // First check the map... you shouldn't allocate VBs very often + // if you want performance. The map lookup should never become + // a performance bottleneck. + vertBuff->decl = mVertexDecls[vertexFormat->getDescription()]; + if ( vertBuff->decl ) + return; + + // Setup the declaration struct. + U32 elemCount = vertexFormat->getElementCount(); + U32 offset = 0; + D3DVERTEXELEMENT9 *vd = new D3DVERTEXELEMENT9[ elemCount + 1 ]; + for ( U32 i=0; i < elemCount; i++ ) + { + const GFXVertexElement &element = vertexFormat->getElement( i ); + + vd[i].Stream = 0; + vd[i].Offset = offset; + vd[i].Type = GFXD3D9DeclType[element.getType()]; + vd[i].Method = D3DDECLMETHOD_DEFAULT; + + // We force the usage index of 0 for everything but + // texture coords for now... this may change later. + vd[i].UsageIndex = 0; + + if ( element.isSemantic( GFXSemantic::POSITION ) ) + vd[i].Usage = D3DDECLUSAGE_POSITION; + else if ( element.isSemantic( GFXSemantic::NORMAL ) ) + vd[i].Usage = D3DDECLUSAGE_NORMAL; + else if ( element.isSemantic( GFXSemantic::COLOR ) ) + vd[i].Usage = D3DDECLUSAGE_COLOR; + else if ( element.isSemantic( GFXSemantic::TANGENT ) ) + vd[i].Usage = D3DDECLUSAGE_TANGENT; + else if ( element.isSemantic( GFXSemantic::BINORMAL ) ) + vd[i].Usage = D3DDECLUSAGE_BINORMAL; + else + { + // Anything that falls thru to here will be a texture coord. + vd[i].Usage = D3DDECLUSAGE_TEXCOORD; + vd[i].UsageIndex = element.getSemanticIndex(); + } + + offset += element.getSizeInBytes(); + } + + D3DVERTEXELEMENT9 declEnd = D3DDECL_END(); + vd[elemCount] = declEnd; + + D3D9Assert( mD3DDevice->CreateVertexDeclaration( vd, &vertBuff->decl ), + "GFXD3D9Device::allocVertexDecl - Failed to create vertex declaration!" ); + + delete[] vd; + + // Store it in the cache. + mVertexDecls[vertexFormat->getDescription()] = vertBuff->decl; +} + +//----------------------------------------------------------------------------- +// This function should ONLY be called from GFXDevice::updateStates() !!! +//----------------------------------------------------------------------------- +void GFXD3D9Device::setTextureInternal( U32 textureUnit, const GFXTextureObject *texture) +{ + if( texture == NULL ) + { + D3D9Assert(mD3DDevice->SetTexture( textureUnit, NULL ), "Failed to set texture to null!"); + return; + } + + GFXD3D9TextureObject *tex = (GFXD3D9TextureObject *) texture; + D3D9Assert(mD3DDevice->SetTexture( textureUnit, tex->getTex()), "Failed to set texture to valid value!"); +} + +//----------------------------------------------------------------------------- +// This function should ONLY be called from GFXDevice::updateStates() !!! +//----------------------------------------------------------------------------- +void GFXD3D9Device::setLightInternal(U32 lightStage, const GFXLightInfo light, bool lightEnable) +{ +#ifndef TORQUE_OS_XENON + if(!lightEnable) + { + mD3DDevice->LightEnable(lightStage, false); + return; + } + D3DLIGHT9 d3dLight; + switch (light.mType) + { + case GFXLightInfo::Ambient: + AssertFatal(false, "Instead of setting an ambient light you should set the global ambient color."); + return; + case GFXLightInfo::Vector: + d3dLight.Type = D3DLIGHT_DIRECTIONAL; + break; + + case GFXLightInfo::Point: + d3dLight.Type = D3DLIGHT_POINT; + break; + + case GFXLightInfo::Spot: + d3dLight.Type = D3DLIGHT_SPOT; + break; + + default : + AssertFatal(false, "Unknown light type!"); + }; + + dMemcpy(&d3dLight.Diffuse, &light.mColor, sizeof(light.mColor)); + dMemcpy(&d3dLight.Ambient, &light.mAmbient, sizeof(light.mAmbient)); + dMemcpy(&d3dLight.Specular, &light.mColor, sizeof(light.mColor)); + dMemcpy(&d3dLight.Position, &light.mPos, sizeof(light.mPos)); + dMemcpy(&d3dLight.Direction, &light.mDirection, sizeof(light.mDirection)); + + d3dLight.Range = light.mRadius; + + d3dLight.Falloff = 1.0; + + d3dLight.Attenuation0 = 1.0f; + d3dLight.Attenuation1 = 0.1f; + d3dLight.Attenuation2 = 0.0f; + + d3dLight.Theta = light.mInnerConeAngle; + d3dLight.Phi = light.mOuterConeAngle; + + mD3DDevice->SetLight(lightStage, &d3dLight); + mD3DDevice->LightEnable(lightStage, true); +#endif +} + +void GFXD3D9Device::setLightMaterialInternal(const GFXLightMaterial mat) +{ +#ifndef TORQUE_OS_XENON + D3DMATERIAL9 d3dmat; + dMemset(&d3dmat, 0, sizeof(D3DMATERIAL9)); + D3DCOLORVALUE col; + + col.r = mat.ambient.red; + col.g = mat.ambient.green; + col.b = mat.ambient.blue; + col.a = mat.ambient.alpha; + d3dmat.Ambient = col; + + col.r = mat.diffuse.red; + col.g = mat.diffuse.green; + col.b = mat.diffuse.blue; + col.a = mat.diffuse.alpha; + d3dmat.Diffuse = col; + + col.r = mat.specular.red; + col.g = mat.specular.green; + col.b = mat.specular.blue; + col.a = mat.specular.alpha; + d3dmat.Specular = col; + + col.r = mat.emissive.red; + col.g = mat.emissive.green; + col.b = mat.emissive.blue; + col.a = mat.emissive.alpha; + d3dmat.Emissive = col; + + d3dmat.Power = mat.shininess; + mD3DDevice->SetMaterial(&d3dmat); +#endif +} + +void GFXD3D9Device::setGlobalAmbientInternal(ColorF color) +{ +#ifndef TORQUE_OS_XENON + mD3DDevice->SetRenderState(D3DRS_AMBIENT, + D3DCOLOR_COLORVALUE(color.red, color.green, color.blue, color.alpha)); +#endif +} + +//------------------------------------------------------------------------------ +// Check for texture mis-match between GFX internal state and what is on the card +// This function is expensive because of the readbacks from DX, and additionally +// won't work unless it's a non-pure device. +// +// This function can crash or give false positives when the game +// is shutting down or returning to the main menu as some of the textures +// present in the mCurrentTexture array will have been freed. +// +// This function is best used as a quick check for mismatched state when it is +// suspected. +//------------------------------------------------------------------------------ +void GFXD3D9Device::doParanoidStateCheck() +{ +#ifdef TORQUE_DEBUG + // Read back all states and make sure they match what we think they should be. + + // For now just do texture binds. + for(U32 i = 0; i < getNumSamplers(); i++) + { + IDirect3DBaseTexture9 *b=NULL; + getDevice()->GetTexture(i, &b); + if ((mCurrentTexture[i].isNull()) && (mCurrentCubemap[i].isNull())) + { + AssertFatal(b == NULL, "GFXD3D9Device::doParanoidStateCheck - got non-null texture in expected NULL slot!"); + getDevice()->SetTexture(i, NULL); + } + else + { + AssertFatal(mCurrentTexture[i] || mCurrentCubemap[i], "GFXD3D9Device::doParanoidStateCheck - got null texture in expected non-null slot!"); + if (mCurrentCubemap[i]) + { + IDirect3DCubeTexture9 *cur= static_cast(mCurrentCubemap[i].getPointer())->mCubeTex; + AssertFatal(cur == b, "GFXD3D9Device::doParanoidStateCheck - mismatched cubemap!"); + } + else + { + IDirect3DBaseTexture9 *cur= static_cast(mCurrentTexture[i].getPointer())->getTex(); + AssertFatal(cur == b, "GFXD3D9Device::doParanoidStateCheck - mismatched 2d texture!"); + } + } + + SAFE_RELEASE(b); + } +#endif +} + +GFXFence *GFXD3D9Device::createFence() +{ + // Figure out what fence type we should be making if we don't know + if( mCreateFenceType == -1 ) + { + IDirect3DQuery9 *testQuery = NULL; + mCreateFenceType = ( mD3DDevice->CreateQuery( D3DQUERYTYPE_EVENT, &testQuery ) == D3DERR_NOTAVAILABLE ); + SAFE_RELEASE( testQuery ); + } + + // Cool, use queries + if( !mCreateFenceType ) + { + GFXFence* fence = new GFXD3D9QueryFence( this ); + fence->registerResourceWithDevice(this); + return fence; + } + + // CodeReview: At some point I would like a specialized D3D9 implementation of + // the method used by the general fence, only without the overhead incurred + // by using the GFX constructs. Primarily the lock() method on texture handles + // will do a data copy, and this method doesn't require a copy, just a lock + // [5/10/2007 Pat] + GFXFence* fence = new GFXGeneralFence( this ); + fence->registerResourceWithDevice(this); + return fence; +} + +GFXOcclusionQuery* GFXD3D9Device::createOcclusionQuery() +{ + GFXOcclusionQuery *query; + if (mOcclusionQuerySupported) + query = new GFXD3D9OcclusionQuery( this ); + else + return NULL; + + query->registerResourceWithDevice(this); + return query; +} + +GFXCubemap * GFXD3D9Device::createCubemap() +{ + GFXD3D9Cubemap* cube = new GFXD3D9Cubemap(); + cube->registerResourceWithDevice(this); + return cube; +} diff --git a/gfx/D3D9/gfxD3D9Device.h b/gfx/D3D9/gfxD3D9Device.h new file mode 100644 index 0000000..0b540f0 --- /dev/null +++ b/gfx/D3D9/gfxD3D9Device.h @@ -0,0 +1,296 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXD3D9DEVICE_H_ +#define _GFXD3D9DEVICE_H_ + +#ifdef TORQUE_OS_XENON +# include "platformXbox/platformXbox.h" +#else +# include +# include "platformWin32/platformWin32.h" +#endif +#ifndef _GFXD3D9STATEBLOCK_H_ +#include "gfx/D3D9/gfxD3D9StateBlock.h" +#endif +#ifndef _GFXD3DTEXTUREMANAGER_H_ +#include "gfx/D3D9/gfxD3D9TextureManager.h" +#endif +#ifndef _GFXD3D9CUBEMAP_H_ +#include "gfx/D3D9/gfxD3D9Cubemap.h" +#endif +#ifndef _GFXD3D9PRIMITIVEBUFFER_H_ +#include "gfx/D3D9/gfxD3D9PrimitiveBuffer.h" +#endif +#ifndef _GFXINIT_H_ +#include "gfx/gfxInit.h" +#endif +#ifndef _PLATFORMDLIBRARY_H +#include "platform/platformDlibrary.h" +#endif + +#include "DxErr.h" + +inline void D3D9Assert( HRESULT hr, const char *info ) +{ +#if defined( TORQUE_DEBUG ) + if( FAILED( hr ) ) + { + char buf[256]; + dSprintf( buf, 256, "%s\n%s\n%s", DXGetErrorStringA( hr ), DXGetErrorDescriptionA( hr ), info ); + AssertFatal( false, buf ); + // DXTrace( __FILE__, __LINE__, hr, info, true ); + } +#endif +} + + +// Typedefs +#define D3DX_FUNCTION(fn_name, fn_return, fn_args) \ + typedef fn_return (WINAPI *D3DXFNPTR##fn_name##)##fn_args##; +#include "gfx/D3D9/d3dx9Functions.h" +#undef D3DX_FUNCTION + +// Function table +struct D3DXFNTable +{ + D3DXFNTable() : isLoaded( false ){}; + bool isLoaded; + DLibraryRef dllRef; +#define D3DX_FUNCTION(fn_name, fn_return, fn_args) \ + D3DXFNPTR##fn_name fn_name; +#include "gfx/D3D9/d3dx9Functions.h" +#undef D3DX_FUNCTION +}; + +#define GFXD3DX static_cast(GFX)->smD3DX + +class GFXResource; +class GFXD3D9ShaderConstBuffer; + +//------------------------------------------------------------------------------ + +class GFXD3D9Device : public GFXDevice +{ + friend class GFXResource; + friend class GFXD3D9PrimitiveBuffer; + friend class GFXD3D9VertexBuffer; + friend class GFXD3D9TextureObject; + friend class GFXPCD3D9TextureTarget; + friend class GFXPCD3D9WindowTarget; + + typedef GFXDevice Parent; + +protected: + + MatrixF mTempMatrix; ///< Temporary matrix, no assurances on value at all + RectI mClipRect; + + typedef StrongRefPtr RPGDVB; + Vector mVolatileVBList; + + /// Used to lookup a vertex declaration for the vertex format. + /// @see allocVertexDecl + typedef Map VertexDeclMap; + VertexDeclMap mVertexDecls; + + IDirect3DSurface9 *mDeviceBackbuffer; + IDirect3DSurface9 *mDeviceDepthStencil; + IDirect3DSurface9 *mDeviceColor; + + GFXD3D9VertexBuffer *mCurrentOpenAllocVB; + GFXD3D9VertexBuffer *mCurrentVB; + void *mCurrentOpenAllocVertexData; + + static void initD3DXFnTable(); + //----------------------------------------------------------------------- + StrongRefPtr mDynamicPB; ///< Dynamic index buffer + GFXD3D9PrimitiveBuffer *mCurrentOpenAllocPB; + GFXD3D9PrimitiveBuffer *mCurrentPB; + + IDirect3DVertexShader9 *mLastVertShader; + IDirect3DPixelShader9 *mLastPixShader; + + S32 mCreateFenceType; + + LPDIRECT3D9 mD3D; ///< D3D Handle + LPDIRECT3DDEVICE9 mD3DDevice; ///< Handle for D3DDevice + + U32 mAdapterIndex; ///< Adapter index because D3D supports multiple adapters + + F32 mPixVersion; + U32 mNumSamplers; ///< Profiled (via caps) + U32 mNumRenderTargets; ///< Profiled (via caps) + + D3DMULTISAMPLE_TYPE mMultisampleType; + DWORD mMultisampleLevel; + + bool mOcclusionQuerySupported; + + /// To manage creating and re-creating of these when device is aquired + void reacquireDefaultPoolResources(); + + /// To release all resources we control from D3DPOOL_DEFAULT + void releaseDefaultPoolResources(); + + /// This you will probably never, ever use, but it is used to generate the code for + /// the initStates() function + void regenStates(); + + virtual GFXD3D9VertexBuffer* findVBPool( const GFXVertexFormat *vertexFormat, U32 numVertsNeeded ); + virtual GFXD3D9VertexBuffer* createVBPool( const GFXVertexFormat *vertexFormat, U32 vertSize ); + +#ifdef TORQUE_DEBUG + /// @name Debug Vertex Buffer information/management + /// @{ + + /// + U32 mNumAllocatedVertexBuffers; ///< To keep track of how many are allocated and freed + GFXD3D9VertexBuffer *mVBListHead; + void addVertexBuffer( GFXD3D9VertexBuffer *buffer ); + void removeVertexBuffer( GFXD3D9VertexBuffer *buffer ); + void logVertexBuffers(); + /// @} +#endif + + // State overrides + // { + + /// + virtual void setTextureInternal(U32 textureUnit, const GFXTextureObject* texture); + + /// Called by GFXDevice to create a device specific stateblock + virtual GFXStateBlockRef createStateBlockInternal(const GFXStateBlockDesc& desc); + /// Called by GFXDevice to actually set a stateblock. + virtual void setStateBlockInternal(GFXStateBlock* block, bool force); + + /// Track the last const buffer we've used. Used to notify new constant buffers that + /// they should send all of their constants up + StrongRefPtr mCurrentConstBuffer; + /// Called by base GFXDevice to actually set a const buffer + virtual void setShaderConstBufferInternal(GFXShaderConstBuffer* buffer); + + // CodeReview - How exactly do we want to deal with this on the Xenon? + // Right now it's just in an #ifndef in gfxD3D9Device.cpp - AlexS 4/11/07 + virtual void setLightInternal(U32 lightStage, const GFXLightInfo light, bool lightEnable); + virtual void setLightMaterialInternal(const GFXLightMaterial mat); + virtual void setGlobalAmbientInternal(ColorF color); + + virtual void initStates()=0; + // } + + // Index buffer management + // { + virtual void _setPrimitiveBuffer( GFXPrimitiveBuffer *buffer ); + virtual void drawIndexedPrimitive( GFXPrimitiveType primType, + U32 startVertex, + U32 minIndex, + U32 numVerts, + U32 startIndex, + U32 primitiveCount ); + // } + + virtual GFXShader* createShader(); + + /// Device helper function + virtual D3DPRESENT_PARAMETERS setupPresentParams( const GFXVideoMode &mode, const HWND &hwnd ) const = 0; + +public: + static D3DXFNTable smD3DX; + + static GFXDevice *createInstance( U32 adapterIndex ); + + GFXTextureObject* createRenderSurface( U32 width, U32 height, GFXFormat format, U32 mipLevel ); + + /// Constructor + /// @param d3d Direct3D object to instantiate this device with + /// @param index Adapter index since D3D can use multiple graphics adapters + GFXD3D9Device( LPDIRECT3D9 d3d, U32 index ); + virtual ~GFXD3D9Device(); + + // Activate/deactivate + // { + virtual void init( const GFXVideoMode &mode, PlatformWindow *window = NULL ) = 0; + + virtual void preDestroy() { Parent::preDestroy(); if(mTextureManager) mTextureManager->kill(); } + + GFXAdapterType getAdapterType(){ return Direct3D9; } + + virtual GFXCubemap *createCubemap(); + + virtual F32 getPixelShaderVersion() const { return mPixVersion; } + virtual void setPixelShaderVersion( F32 version ){ mPixVersion = version; } + virtual void disableShaders(); + virtual void setShader( GFXShader *shader ); + virtual U32 getNumSamplers() const { return mNumSamplers; } + virtual U32 getNumRenderTargets() const { return mNumRenderTargets; } + // } + + // Misc rendering control + // { + virtual void clear( U32 flags, ColorI color, F32 z, U32 stencil ); + virtual bool beginSceneInternal(); + virtual void endSceneInternal(); + + virtual void setClipRect( const RectI &rect ); + virtual const RectI& getClipRect() const { return mClipRect; } + + // } + + /// @name Render Targets + /// @{ + virtual void _updateRenderTargets(); + /// @} + + // Vertex/Index buffer management + // { + void setVB( GFXVertexBuffer *buffer ); + + virtual GFXVertexBuffer* allocVertexBuffer( U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertSize, + GFXBufferType bufferType ); + virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ); + virtual void deallocVertexBuffer( GFXD3D9VertexBuffer *vertBuff ); + + void allocVertexDecl( GFXD3D9VertexBuffer *vertBuff ); + // } + + virtual U32 getMaxDynamicVerts() { return MAX_DYNAMIC_VERTS; } + virtual U32 getMaxDynamicIndices() { return MAX_DYNAMIC_INDICES; } + + // Rendering + // { + virtual void drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ); + // } + + virtual LPDIRECT3DDEVICE9 getDevice(){ return mD3DDevice; } + virtual LPDIRECT3D9 getD3D() { return mD3D; } + + /// Reset + virtual void reset( D3DPRESENT_PARAMETERS &d3dpp ) = 0; + + GFXShaderRef mGenericShader[GS_COUNT]; + + virtual void setupGenericShaders( GenericShaderType type = GSColor ); + + // Function only really used on the, however a centralized function for + // destroying resources is probably a good thing -patw + virtual void destroyD3DResource( IDirect3DResource9 *d3dResource ) { SAFE_RELEASE( d3dResource ); }; + + inline virtual F32 getFillConventionOffset() const { return 0.5f; } + virtual void doParanoidStateCheck(); + + GFXFence *createFence(); + + GFXOcclusionQuery* createOcclusionQuery(); + + // Default multisample parameters + D3DMULTISAMPLE_TYPE getMultisampleType() const { return mMultisampleType; } + DWORD getMultisampleLevel() const { return mMultisampleLevel; } +}; + + +#endif // _GFXD3D9DEVICE_H_ diff --git a/gfx/D3D9/gfxD3D9Device.regen-states.cpp b/gfx/D3D9/gfxD3D9Device.regen-states.cpp new file mode 100644 index 0000000..b131263 --- /dev/null +++ b/gfx/D3D9/gfxD3D9Device.regen-states.cpp @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/gfxD3D9Device.h" +#include "console/console.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" + +// Cut and paste from console.log into GFXD3D9Device::initStates() +void GFXD3D9Device::regenStates() +{ + DWORD temp; + Con::printf( " //-------------------------------------" ); + Con::printf( " // Auto-generated default states, see regenStates() for details" ); + Con::printf( " //" ); + Con::printf( "" ); + Con::printf( " // Render states" ); + + for( U32 state = GFXRenderState_FIRST; state < GFXRenderState_COUNT; state++ ) + { + if( GFXD3D9RenderState[state] == GFX_UNSUPPORTED_VAL ) + continue; + + temp = 0; + mD3DDevice->GetRenderState( GFXD3D9RenderState[state], &temp ); + Con::printf( " mD3DDevice->SetRenderState( GFXD3D9RenderState[%d], %d );", state, temp ); + } + +#ifndef TORQUE_OS_XENON + Con::printf( "" ); + Con::printf( " // Texture Stage states" ); + + for( U32 stage = 0; stage < TEXTURE_STAGE_COUNT; stage++ ) + { + if( stage >= GFX->getNumSamplers() ) + { + Con::errorf( "Sampler %d out of range for this device, ignoring.", stage ); + break; + } + + for( U32 state = GFXTSS_FIRST; state < GFXTSS_COUNT; state++ ) + { + if( GFXD3D9TextureStageState[state] == GFX_UNSUPPORTED_VAL ) + continue; + + temp = 0; + mD3DDevice->GetTextureStageState( stage, GFXD3D9TextureStageState[state], &temp ); + Con::printf( " mD3DDevice->SetTextureStageState( %d, GFXD3D9TextureStageState[%d], %d );", stage, state, temp ); + } + } +#endif + + Con::printf( "" ); + Con::printf( " // Sampler states" ); + for( U32 stage = 0; stage < TEXTURE_STAGE_COUNT; stage++ ) + { + if( stage >= GFX->getNumSamplers() ) + { + Con::errorf( "Sampler %d out of range for this device, ignoring.", stage ); + break; + } + + for( U32 state = GFXSAMP_FIRST; state < GFXSAMP_COUNT; state++ ) + { + if( GFXD3D9SamplerState[state] == GFX_UNSUPPORTED_VAL ) + continue; + + temp = 0; + + mD3DDevice->GetSamplerState( stage, GFXD3D9SamplerState[state], &temp ); + Con::printf( " mD3DDevice->SetSamplerState( %d, GFXD3D9SamplerState[%d], %d );", stage, state, temp ); + } + } +} diff --git a/gfx/D3D9/gfxD3D9EnumTranslate.h b/gfx/D3D9/gfxD3D9EnumTranslate.h new file mode 100644 index 0000000..a652b2f --- /dev/null +++ b/gfx/D3D9/gfxD3D9EnumTranslate.h @@ -0,0 +1,46 @@ +//------------------------------------------------------------------------------ +// Torque Shader Engine +// Copyright (c) GarageGames.Com +//------------------------------------------------------------------------------ + +#include "gfx/D3D9/gfxD3D9Shader.h" + +#include "gfx/gfxEnums.h" + +//------------------------------------------------------------------------------ + +namespace GFXD3D9EnumTranslate +{ + void init(); +}; + +//------------------------------------------------------------------------------ + +extern _D3DFORMAT GFXD3D9IndexFormat[GFXIndexFormat_COUNT]; +extern _D3DSAMPLERSTATETYPE GFXD3D9SamplerState[GFXSAMP_COUNT]; +extern _D3DFORMAT GFXD3D9TextureFormat[GFXFormat_COUNT]; +#ifdef TORQUE_OS_XENON +extern _D3DFORMAT GFXD3D9RenderTargetFormat[GFXFormat_COUNT]; +#endif +extern _D3DRENDERSTATETYPE GFXD3D9RenderState[GFXRenderState_COUNT]; +extern _D3DTEXTUREFILTERTYPE GFXD3D9TextureFilter[GFXTextureFilter_COUNT]; +extern _D3DBLEND GFXD3D9Blend[GFXBlend_COUNT]; +extern _D3DBLENDOP GFXD3D9BlendOp[GFXBlendOp_COUNT]; +extern _D3DSTENCILOP GFXD3D9StencilOp[GFXStencilOp_COUNT]; +extern _D3DCMPFUNC GFXD3D9CmpFunc[GFXCmp_COUNT]; +extern _D3DCULL GFXD3D9CullMode[GFXCull_COUNT]; +extern _D3DFILLMODE GFXD3D9FillMode[GFXFill_COUNT]; +extern _D3DPRIMITIVETYPE GFXD3D9PrimType[GFXPT_COUNT]; +extern _D3DTEXTURESTAGESTATETYPE GFXD3D9TextureStageState[GFXTSS_COUNT]; +extern _D3DTEXTUREADDRESS GFXD3D9TextureAddress[GFXAddress_COUNT]; +extern _D3DTEXTUREOP GFXD3D9TextureOp[GFXTOP_COUNT]; +extern _D3DDECLTYPE GFXD3D9DeclType[GFXDeclType_COUNT]; + +#define GFXREVERSE_LOOKUP( tablearray, enumprefix, val ) \ + for( int i = enumprefix##_FIRST; i < enumprefix##_COUNT; i++ ) \ + if( (int)tablearray##[i] == val ) \ + { \ + val = i; \ + break; \ + } \ + diff --git a/gfx/D3D9/gfxD3D9OcclusionQuery.cpp b/gfx/D3D9/gfxD3D9OcclusionQuery.cpp new file mode 100644 index 0000000..35c005c --- /dev/null +++ b/gfx/D3D9/gfxD3D9OcclusionQuery.cpp @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/D3D9/gfxD3D9OcclusionQuery.h" + + +GFXD3D9OcclusionQuery::GFXD3D9OcclusionQuery( GFXDevice *device ) + : GFXOcclusionQuery( device ), + mQuery( NULL ) +{ +} + +GFXD3D9OcclusionQuery::~GFXD3D9OcclusionQuery() +{ + SAFE_RELEASE( mQuery ); +} + +bool GFXD3D9OcclusionQuery::begin() +{ + if ( mQuery == NULL ) + { +#ifdef TORQUE_OS_XENON + HRESULT hRes = static_cast( mDevice )->getDevice()->CreateQueryTiled( D3DQUERYTYPE_OCCLUSION, 2, &mQuery ); +#else + HRESULT hRes = static_cast( mDevice )->getDevice()->CreateQuery( D3DQUERYTYPE_OCCLUSION, &mQuery ); +#endif + + AssertFatal( hRes != D3DERR_NOTAVAILABLE, "GFXD3D9OcclusionQuery::resurrect - Hardware does not support D3D9 Occlusion-Queries, this should be caught before this type is created" ); + AssertISV( hRes != E_OUTOFMEMORY, "GFXD3D9OcclusionQuery::resurrect - Out of memory" ); + } + + // Add a begin marker to the command buffer queue. + mQuery->Issue( D3DISSUE_BEGIN ); + + return true; +} + +void GFXD3D9OcclusionQuery::end() +{ + // Add an end marker to the command buffer queue. + mQuery->Issue( D3DISSUE_END ); +} + +GFXD3D9OcclusionQuery::OcclusionQueryStatus GFXD3D9OcclusionQuery::getStatus( bool block, U32 *data ) +{ + // If this ever shows up near the top of a profile + // then your system is GPU bound. + PROFILE_SCOPE(GFXD3D9OcclusionQuery_getStatus); + + if ( mQuery == NULL ) + return Unset; + + HRESULT hRes; + DWORD dwOccluded = 0; + + if ( block ) + { + while( ( hRes = mQuery->GetData( &dwOccluded, sizeof(DWORD), D3DGETDATA_FLUSH ) ) == S_FALSE ) + ; + } + else + { + hRes = mQuery->GetData( &dwOccluded, sizeof(DWORD), 0 ); + } + + if ( hRes == S_OK ) + { + if ( data != NULL ) + *data = dwOccluded; + + return dwOccluded > 0 ? NotOccluded : Occluded; + } + + if ( hRes == S_FALSE ) + return Waiting; + + return Error; +} + +void GFXD3D9OcclusionQuery::zombify() +{ + // Release our query + SAFE_RELEASE( mQuery ); +} + +void GFXD3D9OcclusionQuery::resurrect() +{ + // Recreate the query + if ( mQuery == NULL ) + { + HRESULT hRes = static_cast( mDevice )->getDevice()->CreateQuery( D3DQUERYTYPE_OCCLUSION, &mQuery ); + + AssertFatal( hRes != D3DERR_NOTAVAILABLE, "GFXD3D9OcclusionQuery::resurrect - Hardware does not support D3D9 Occlusion-Queries, this should be caught before this type is created" ); + AssertISV( hRes != E_OUTOFMEMORY, "GFXD3D9OcclusionQuery::resurrect - Out of memory" ); + } +} + +const String GFXD3D9OcclusionQuery::describeSelf() const +{ + // We've got nothing + return String(); +} \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9OcclusionQuery.h b/gfx/D3D9/gfxD3D9OcclusionQuery.h new file mode 100644 index 0000000..58eec7f --- /dev/null +++ b/gfx/D3D9/gfxD3D9OcclusionQuery.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFX_D3D9_OCCLUSIONQUERY_H_ +#define _GFX_D3D9_OCCLUSIONQUERY_H_ + +#ifndef _GFXOCCLUSIONQUERY_H_ +#include "gfx/gfxOcclusionQuery.h" +#endif + +struct IDirect3DQuery9; + + +class GFXD3D9OcclusionQuery : public GFXOcclusionQuery +{ +private: + mutable IDirect3DQuery9 *mQuery; + +public: + GFXD3D9OcclusionQuery( GFXDevice *device ); + virtual ~GFXD3D9OcclusionQuery(); + + virtual bool begin(); + virtual void end(); + virtual OcclusionQueryStatus getStatus( bool block, U32 *data = NULL ); + + // GFXResource + virtual void zombify(); + virtual void resurrect(); + virtual const String GFXD3D9OcclusionQuery::describeSelf() const; +}; + +#endif // _GFX_D3D9_OCCLUSIONQUERY_H_ \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9PrimitiveBuffer.cpp b/gfx/D3D9/gfxD3D9PrimitiveBuffer.cpp new file mode 100644 index 0000000..33cd7e8 --- /dev/null +++ b/gfx/D3D9/gfxD3D9PrimitiveBuffer.cpp @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#include "gfx/D3D9/gfxD3D9PrimitiveBuffer.h" +#include "core/util/safeRelease.h" + +void GFXD3D9PrimitiveBuffer::prepare() +{ + static_cast( mDevice )->_setPrimitiveBuffer(this); +} + +void GFXD3D9PrimitiveBuffer::unlock() +{ + ib->Unlock(); + mLocked = false; + mIsFirstLock = false; +} + +GFXD3D9PrimitiveBuffer::~GFXD3D9PrimitiveBuffer() +{ + if( mBufferType != GFXBufferTypeVolatile ) + SAFE_RELEASE( ib ); +} + +void GFXD3D9PrimitiveBuffer::zombify() +{ + if(mBufferType != GFXBufferTypeDynamic) + return; + AssertFatal(!mLocked, "GFXD3D9PrimitiveBuffer::zombify - Cannot zombify a locked buffer!"); + SAFE_RELEASE(ib); +} + +void GFXD3D9PrimitiveBuffer::resurrect() +{ + if ( mBufferType != GFXBufferTypeDynamic ) + return; + + U32 usage = D3DUSAGE_WRITEONLY; + +#ifndef TORQUE_OS_XENON + usage |= D3DUSAGE_DYNAMIC; +#endif + + D3DPOOL pool = D3DPOOL_DEFAULT; + + D3D9Assert(static_cast(mDevice)->mD3DDevice->CreateIndexBuffer( sizeof(U16) * mIndexCount , + usage , GFXD3D9IndexFormat[GFXIndexFormat16], pool, &ib, 0), + "GFXD3D9PrimitiveBuffer::resurrect - Failed to allocate an index buffer."); +} diff --git a/gfx/D3D9/gfxD3D9PrimitiveBuffer.h b/gfx/D3D9/gfxD3D9PrimitiveBuffer.h new file mode 100644 index 0000000..2167eb4 --- /dev/null +++ b/gfx/D3D9/gfxD3D9PrimitiveBuffer.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXD3D9PRIMITIVEBUFFER_H_ +#define _GFXD3D9PRIMITIVEBUFFER_H_ + +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + + +struct IDirect3DIndexBuffer9; + +class GFXD3D9PrimitiveBuffer : public GFXPrimitiveBuffer +{ + public: + IDirect3DIndexBuffer9 *ib; + StrongRefPtr mVolatileBuffer; + U32 mVolatileStart; + + bool mLocked; + bool mIsFirstLock; + + GFXD3D9PrimitiveBuffer(GFXDevice *device, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType); + ~GFXD3D9PrimitiveBuffer(); + + virtual void lock(U16 indexStart, U16 indexEnd, U16 **indexPtr); + virtual void unlock(); + + virtual void prepare(); + +#ifdef TORQUE_DEBUG + //GFXD3D9PrimitiveBuffer *next; +#endif + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); +}; + +inline GFXD3D9PrimitiveBuffer::GFXD3D9PrimitiveBuffer(GFXDevice *device, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType) + : GFXPrimitiveBuffer(device, indexCount, primitiveCount, bufferType) +{ + mVolatileStart = 0; + ib = NULL; + mIsFirstLock = true; + mLocked = false; +} + +#endif diff --git a/gfx/D3D9/gfxD3D9QueryFence.cpp b/gfx/D3D9/gfxD3D9QueryFence.cpp new file mode 100644 index 0000000..f868a77 --- /dev/null +++ b/gfx/D3D9/gfxD3D9QueryFence.cpp @@ -0,0 +1,84 @@ +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/D3D9/gfxD3D9QueryFence.h" + +GFXD3D9QueryFence::~GFXD3D9QueryFence() +{ + SAFE_RELEASE( mQuery ); +} + +//------------------------------------------------------------------------------ + +void GFXD3D9QueryFence::issue() +{ + PROFILE_START( GFXD3D9QueryFence_issue ); + + // Create the query if we need to + if( mQuery == NULL ) + { + HRESULT hRes = static_cast( mDevice )->getDevice()->CreateQuery( D3DQUERYTYPE_EVENT, &mQuery ); + + AssertFatal( hRes != D3DERR_NOTAVAILABLE, "Hardware does not support D3D9 Queries, this should be caught before this fence type is created" ); + AssertISV( hRes != E_OUTOFMEMORY, "Out of memory" ); + } + + // Issue the query + mQuery->Issue( D3DISSUE_END ); + + PROFILE_END(); +} + +//------------------------------------------------------------------------------ + +GFXFence::FenceStatus GFXD3D9QueryFence::getStatus() const +{ + if( mQuery == NULL ) + return GFXFence::Unset; + + HRESULT hRes = mQuery->GetData( NULL, 0, 0 ); + + return ( hRes == S_OK ? GFXFence::Processed : GFXFence::Pending ); +} + +//------------------------------------------------------------------------------ + +void GFXD3D9QueryFence::block() +{ + PROFILE_SCOPE(GFXD3D9QueryFence_block); + + // Calling block() before issue() is valid, catch this case + if( mQuery == NULL ) + return; + + HRESULT hRes; + while( ( hRes = mQuery->GetData( NULL, 0, D3DGETDATA_FLUSH ) ) == S_FALSE ) + ; + + // Check for D3DERR_DEVICELOST, if we lost the device, the fence will get + // re-created next issue() + if( hRes == D3DERR_DEVICELOST ) + SAFE_RELEASE( mQuery ); +} + +void GFXD3D9QueryFence::zombify() +{ + // Release our query + SAFE_RELEASE( mQuery ); +} + +void GFXD3D9QueryFence::resurrect() +{ + // Recreate the query + if( mQuery == NULL ) + { + HRESULT hRes = static_cast( mDevice )->getDevice()->CreateQuery( D3DQUERYTYPE_EVENT, &mQuery ); + + AssertFatal( hRes != D3DERR_NOTAVAILABLE, "GFXD3D9QueryFence::resurrect - Hardware does not support D3D9 Queries, this should be caught before this fence type is created" ); + AssertISV( hRes != E_OUTOFMEMORY, "GFXD3D9QueryFence::resurrect - Out of memory" ); + } +} + +const String GFXD3D9QueryFence::describeSelf() const +{ + // We've got nothing + return String(); +} \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9QueryFence.h b/gfx/D3D9/gfxD3D9QueryFence.h new file mode 100644 index 0000000..3b703d8 --- /dev/null +++ b/gfx/D3D9/gfxD3D9QueryFence.h @@ -0,0 +1,30 @@ +#ifndef _GFX_D3D9_QUERYFENCE_H_ +#define _GFX_D3D9_QUERYFENCE_H_ + +#include "gfx/gfxFence.h" +#include "gfx/gfxResource.h" + +struct IDirect3DQuery9; + +class GFXD3D9QueryFence : public GFXFence +{ +private: + mutable IDirect3DQuery9 *mQuery; + +public: + GFXD3D9QueryFence( GFXDevice *device ) : GFXFence( device ), mQuery( NULL ) {}; + virtual ~GFXD3D9QueryFence(); + + virtual void issue(); + virtual FenceStatus getStatus() const; + virtual void block(); + + + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + virtual const String describeSelf() const; +}; + +#endif \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9Shader.cpp b/gfx/D3D9/gfxD3D9Shader.cpp new file mode 100644 index 0000000..5fb307c --- /dev/null +++ b/gfx/D3D9/gfxD3D9Shader.cpp @@ -0,0 +1,1300 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/gfxD3D9Shader.h" +#include "gfx/D3D9/gfxD3D9Device.h" + +#include "core/frameAllocator.h" +#include "core/stream/fileStream.h" +#include "core/util/safeDelete.h" +#include "console/console.h" + +extern bool gDisassembleAllShaders; + +/// D3DXInclude plugin +class _gfxD3DXInclude : public ID3DXInclude, public StrongRefBase +{ +private: + U8 *mIncludeData; + dsize_t mIncludeDataSize; + Torque::Path mIncludeFileName; + +public: + Torque::Path _pShaderFile; + + _gfxD3DXInclude() : mIncludeData( NULL ), mIncludeDataSize( 0 ) {} + ~_gfxD3DXInclude() { /*SAFE_DELETE_ARRAY( mIncludeData );*/ } + + STDMETHOD(Close)(THIS_ LPCVOID pData); + + // 360 + STDMETHOD(Open)(THIS_ D3DXINCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes, /* OUT */ LPSTR pFullPath, DWORD cbFullPath); + + // PC + STDMETHOD(Open)(THIS_ D3DXINCLUDE_TYPE IncludeType, LPCSTR pFileName, LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes) + { + return Open( IncludeType, pFileName, pParentData, ppData, pBytes, NULL, 0 ); + } +}; + +_gfxD3DXIncludeRef GFXD3D9Shader::smD3DXInclude = NULL; +Vector gIncludeAllocs( __FILE__, __LINE__ ); + +HRESULT _gfxD3DXInclude::Open(THIS_ D3DXINCLUDE_TYPE IncludeType, LPCSTR pFileName, + LPCVOID pParentData, LPCVOID *ppData, UINT *pBytes, + LPSTR pFullPath, DWORD cbFullPath) +{ + // First try making the path relative to the parent. + Torque::Path path = Torque::Path::Join( _pShaderFile.getPath(), '/', pFileName ); + path = Torque::Path::CompressPath( path ); + + if ( !Torque::FS::ReadFile( path, (void *&)mIncludeData, mIncludeDataSize, true ) ) + { + // Ok... now try using the path as is. + path = String( pFileName ); + path = Torque::Path::CompressPath( path ); + + if ( !Torque::FS::ReadFile( path, (void *&)mIncludeData, mIncludeDataSize, true ) ) + { + AssertISV(false, avar( "Failed to open include '%s'.", pFileName)); + return E_FAIL; + } + } + + mIncludeFileName = path; + + gIncludeAllocs.push_back(mIncludeData); + + *pBytes = mIncludeDataSize; + *ppData = mIncludeData; + + return S_OK; +} + +HRESULT _gfxD3DXInclude::Close( THIS_ LPCVOID pData ) +{ + return S_OK; +} + +GFXD3D9ShaderConstHandle::GFXD3D9ShaderConstHandle() +{ + mShader = NULL; + mVertexConstant = false; + mPixelConstant = false; + mValid = false; +} + +const String& GFXD3D9ShaderConstHandle::getName() const +{ + if ( mVertexConstant ) + return mVertexHandle.name; + else + return mPixelHandle.name; +} + +GFXShaderConstType GFXD3D9ShaderConstHandle::getType() const +{ + if ( mVertexConstant ) + return mVertexHandle.constType; + else + return mPixelHandle.constType; +} + +U32 GFXD3D9ShaderConstHandle::getArraySize() const +{ + if ( mVertexConstant ) + return mVertexHandle.arraySize; + else + return mPixelHandle.arraySize; +} + +S32 GFXD3D9ShaderConstHandle::getSamplerRegister() const +{ + if ( !mValid ) + return -1; + + // We always store sampler type and register index in the pixelHandle, + // sampler registers are shared between vertex and pixel shaders anyway. + + if ( mPixelHandle.constType != GFXSCT_Sampler && mPixelHandle.constType != GFXSCT_SamplerCube ) + return -1; + + return mPixelHandle.offset; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + +bool GFXD3D9ShaderBufferLayout::setMatrix(const ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data, U8* basePointer) +{ + if (pd.constType == GFXSCT_Float4x4) + { + // Special case, we can just blast this guy. + AssertFatal(pd.size >= size, "Not enough room in the buffer for this data!"); + if (dMemcmp(basePointer+pd.offset, data, size) != 0) + { + dMemcpy(basePointer+pd.offset, data, size); + return true; + } else { + return false; + } + } else { + // Figure out how big of a chunk we are copying. We're going to copy 4 columns by N rows of data + U32 csize; + switch (pd.constType) + { + case GFXSCT_Float2x2 : + csize = 32; + break; + case GFXSCT_Float3x3 : + csize = 48; + break; + default: + AssertFatal(false, "Unhandled case!"); + return false; + break; + } + // Loop through and copy + bool ret = false; + U8* currDestPointer = basePointer+pd.offset; + const U8* currSourcePointer = static_cast(data); + const U8* endData = currSourcePointer + size; + while (currSourcePointer < endData) + { + if (dMemcmp(currDestPointer, currSourcePointer, csize) != 0) + { + dMemcpy(currDestPointer, currSourcePointer, csize); + ret = true; + } + currDestPointer += csize; + currSourcePointer += sizeof(MatrixF); + } + return ret; + } +} + +//------------------------------------------------------------------------------ +GFXD3D9ShaderConstBuffer::GFXD3D9ShaderConstBuffer( GFXD3D9Shader* shader, + GFXD3D9ShaderBufferLayout* vertexLayoutF, + GFXD3D9ShaderBufferLayout* vertexLayoutI, + GFXD3D9ShaderBufferLayout* pixelLayoutF, + GFXD3D9ShaderBufferLayout* pixelLayoutI ) +{ + AssertFatal(shader, "NULL shader not allowed."); + mShader = shader; + mVertexConstBufferLayoutF = vertexLayoutF; + mVertexConstBufferF = new GenericConstBuffer(vertexLayoutF); + mVertexConstBufferLayoutI = vertexLayoutI; + mVertexConstBufferI = new GenericConstBuffer(vertexLayoutI); + mPixelConstBufferLayoutF = pixelLayoutF; + mPixelConstBufferF = new GenericConstBuffer(pixelLayoutF); + mPixelConstBufferLayoutI = pixelLayoutI; + mPixelConstBufferI = new GenericConstBuffer(pixelLayoutI); +} + +GFXD3D9ShaderConstBuffer::~GFXD3D9ShaderConstBuffer() +{ + SAFE_DELETE(mVertexConstBufferF); + SAFE_DELETE(mPixelConstBufferF); + SAFE_DELETE(mVertexConstBufferI); + SAFE_DELETE(mPixelConstBufferI); + + if ( mShader ) + mShader->_unlinkBuffer( this ); +} + +GFXShader* GFXD3D9ShaderConstBuffer::getShader() +{ + return mShader; +} + +// This is kind of cheesy, but I don't think templates would work well here because +// these functions potentially need to be handled differently by other derived types +#define SET_CONSTANT(handle, fv, floatOrInt) \ + if (!handle||!handle->isValid()) \ + return; \ + AssertFatal(dynamic_cast(handle), "Incorrect const buffer type!"); \ + const GFXD3D9ShaderConstHandle* h = static_cast(handle); \ + if (h->isSampler()) \ + return; \ + AssertFatal(!mShader.isNull(), "Buffer's shader is null!" ); \ + AssertFatal(!h->mShader.isNull(), "Handle's shader is null!" ); \ + AssertFatal(h->mShader.getPointer() == mShader.getPointer(), "Mismatched shaders!"); \ + if (h->mVertexConstant) \ + mVertexConstBuffer##floatOrInt->set(h->mVertexHandle, fv); \ + if (h->mPixelConstant) \ + mPixelConstBuffer##floatOrInt->set(h->mPixelHandle, fv); + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const F32 fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point2F& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point3F& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point4F& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const PlaneF& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const ColorF& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const S32 f) +{ + SET_CONSTANT(handle, f, I); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point2I& fv) +{ + SET_CONSTANT(handle, fv, I); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point3I& fv) +{ + SET_CONSTANT(handle, fv, I); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point4I& fv) +{ + SET_CONSTANT(handle, fv, I); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, F); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, I); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, I); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, I); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + SET_CONSTANT(handle, fv, I); +} +#undef SET_CONSTANT + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType) +{ + if (!handle||!handle->isValid()) + return; + MatrixF transposed; + mat.transposeTo(transposed); + + AssertFatal(dynamic_cast(handle), "Incorrect const buffer type!"); + const GFXD3D9ShaderConstHandle* h = static_cast(handle); + if ( h->isSampler() ) + return; + AssertFatal(h->mShader == mShader, "Mismatched shaders!"); + if (h->mVertexConstant) + mVertexConstBufferF->set(h->mVertexHandle, transposed, matrixType); + if (h->mPixelConstant) + mPixelConstBufferF->set(h->mPixelHandle, transposed, matrixType); +} + +void GFXD3D9ShaderConstBuffer::set(GFXShaderConstHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType) +{ + if (!handle||!handle->isValid()) + return; + static Vector transposed; + if (arraySize > transposed.size()) + transposed.setSize(arraySize); + + for (U32 i = 0; i < arraySize; i++) + { + mat[i].transposeTo(transposed[i]); + } + AssertFatal(dynamic_cast(handle), "Incorrect const buffer type!"); + const GFXD3D9ShaderConstHandle* h = static_cast(handle); + if ( h->isSampler() ) + return; + AssertFatal(h->mShader == mShader, "Mismatched shaders!"); + if (h->mVertexConstant) + mVertexConstBufferF->set(h->mVertexHandle, transposed.begin(), arraySize, matrixType); + if (h->mPixelConstant) + mPixelConstBufferF->set(h->mPixelHandle, transposed.begin(), arraySize, matrixType); +} + +/// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer +const String GFXD3D9ShaderConstBuffer::describeSelf() const +{ + String ret; + ret = String(" GFXD3D9ShaderConstBuffer\n"); + + for (U32 i = 0; i < mVertexConstBufferLayoutF->getParameterCount(); i++) + { + GenericConstBufferLayout::ParamDesc pd; + mVertexConstBufferLayoutF->getDesc(i, pd); + + ret += String::ToString(" Constant name: %s", pd.name); + } + + return ret; +} + +void GFXD3D9ShaderConstBuffer::zombify() +{ + +} + +/// When called the resource should restore all device sensitive information destroyed by zombify() +void GFXD3D9ShaderConstBuffer::resurrect() +{ + +} + + +/// Used internally by GXD3D9ShaderConstBuffer to determine if it's dirty. +bool GFXD3D9ShaderConstBuffer::isDirty() +{ + bool ret = false; + if (mVertexConstBufferF) + ret |= mVertexConstBufferF->isDirty(); + if (mVertexConstBufferI) + ret |= mVertexConstBufferF->isDirty(); + if (mPixelConstBufferF) + ret |= mPixelConstBufferF->isDirty(); + if (mPixelConstBufferI) + ret |= mPixelConstBufferF->isDirty(); + return ret; +} + +void GFXD3D9ShaderConstBuffer::activate(GFXD3D9ShaderConstBuffer* mPrevShaderBuffer) +{ + PROFILE_SCOPE(GFXD3D9ShaderConstBuffer_activate); + // Ok, check for equal buffers, this can happen with materials that differ only by base texture + // and other fun cases. + if (mPrevShaderBuffer != this) + { + // If the previous buffer is dirty, than we can't compare against it, because it hasn't + // sent its contents to the card yet + if (mPrevShaderBuffer && (!mPrevShaderBuffer->isDirty())) + { + PROFILE_SCOPE(GFXD3D9ShaderConstBuffer_activate_dirty_check_1); + mVertexConstBufferF->setDirty(!mPrevShaderBuffer->mVertexConstBufferF->isEqual(mVertexConstBufferF)); + mPixelConstBufferF->setDirty(!mPrevShaderBuffer->mPixelConstBufferF->isEqual(mPixelConstBufferF)); + mVertexConstBufferI->setDirty(!mPrevShaderBuffer->mVertexConstBufferF->isEqual(mVertexConstBufferI)); + mPixelConstBufferI->setDirty(!mPrevShaderBuffer->mPixelConstBufferF->isEqual(mPixelConstBufferI)); + } else { + PROFILE_SCOPE(GFXD3D9ShaderConstBuffer_activate_dirty_check_2); + mVertexConstBufferF->setDirty(true); + mPixelConstBufferF->setDirty(true); + mVertexConstBufferI->setDirty(true); + mPixelConstBufferI->setDirty(true); + } + } + + LPDIRECT3DDEVICE9 d = static_cast(GFX)->getDevice(); + // /16 is the number of float4 vectors + const U32 bytesToFloat4 = 16; + U32 start, bufferSize; + const U8* buf; + + if (mVertexConstBufferF->isDirty()) + { + buf = mVertexConstBufferF->getDirtyBuffer(start, bufferSize); + if (buf && bufferSize) + d->SetVertexShaderConstantF(start / bytesToFloat4, (float*) buf, bufferSize / bytesToFloat4); + mVertexConstBufferF->setDirty(false); + } + + if (mPixelConstBufferF->isDirty()) + { + buf = mPixelConstBufferF->getDirtyBuffer(start, bufferSize); + if (buf && bufferSize) + d->SetPixelShaderConstantF(start / bytesToFloat4, (float*) buf, bufferSize / bytesToFloat4); + mPixelConstBufferF->setDirty(false); + } + + const U32 bytesToInt4 = 16; + if (mVertexConstBufferI->isDirty()) + { + buf = mVertexConstBufferI->getDirtyBuffer(start, bufferSize); + if (buf && bufferSize) + d->SetVertexShaderConstantI(start / bytesToInt4, (int*) buf, bufferSize / bytesToInt4); + mVertexConstBufferI->setDirty(false); + } + + if (mPixelConstBufferI->isDirty()) + { + buf = mPixelConstBufferI->getDirtyBuffer(start, bufferSize); + if (buf && bufferSize) + d->SetPixelShaderConstantI(start / bytesToInt4, (int*) buf, bufferSize / bytesToInt4); + mPixelConstBufferI->setDirty(false); + } +} + +void GFXD3D9ShaderConstBuffer::onShaderReload( GFXShader *shader ) +{ + AssertFatal( shader == mShader, "GFXD3D9ShaderConstBuffer::onShaderReload is hosed!" ); + + SAFE_DELETE( mVertexConstBufferF ); + SAFE_DELETE( mPixelConstBufferF ); + SAFE_DELETE( mVertexConstBufferI ); + SAFE_DELETE( mPixelConstBufferI ); + + GFXD3D9Shader *d3d9Shader = dynamic_cast( shader ); + + AssertFatal( mVertexConstBufferLayoutF == d3d9Shader->mVertexConstBufferLayoutF, "GFXD3D9ShaderConstBuffer::onShaderReload is hosed!" ); + AssertFatal( mPixelConstBufferLayoutF == d3d9Shader->mPixelConstBufferLayoutF, "GFXD3D9ShaderConstBuffer::onShaderReload is hosed!" ); + AssertFatal( mVertexConstBufferLayoutI == d3d9Shader->mVertexConstBufferLayoutI, "GFXD3D9ShaderConstBuffer::onShaderReload is hosed!" ); + AssertFatal( mPixelConstBufferLayoutI == d3d9Shader->mPixelConstBufferLayoutI, "GFXD3D9ShaderConstBuffer::onShaderReload is hosed!" ); + + mVertexConstBufferF = new GenericConstBuffer( mVertexConstBufferLayoutF ); + mVertexConstBufferI = new GenericConstBuffer( mVertexConstBufferLayoutI ); + mPixelConstBufferF = new GenericConstBuffer( mPixelConstBufferLayoutF ); + mPixelConstBufferI = new GenericConstBuffer( mPixelConstBufferLayoutI ); +} + +//------------------------------------------------------------------------------ + +GFXD3D9Shader::GFXD3D9Shader() +{ + VECTOR_SET_ASSOCIATION( mShaderConsts ); + + mD3D9Device = dynamic_cast(GFX)->getDevice(); + AssertFatal(mD3D9Device, "Invalid device for shader."); + mVertShader = NULL; + mPixShader = NULL; + mVertexConstBufferLayoutF = NULL; + mPixelConstBufferLayoutF = NULL; + mVertexConstBufferLayoutI = NULL; + mPixelConstBufferLayoutI = NULL; + + if( smD3DXInclude == NULL ) + smD3DXInclude = new _gfxD3DXInclude; +} + +//------------------------------------------------------------------------------ + +GFXD3D9Shader::~GFXD3D9Shader() +{ + for (HandleMap::Iterator i = mHandles.begin(); i != mHandles.end(); i++) + delete i->value; + SAFE_DELETE(mVertexConstBufferLayoutF); + SAFE_DELETE(mPixelConstBufferLayoutF); + SAFE_DELETE(mVertexConstBufferLayoutI); + SAFE_DELETE(mPixelConstBufferLayoutI); + SAFE_RELEASE(mVertShader); + SAFE_RELEASE(mPixShader); +} + +bool GFXD3D9Shader::_init() +{ + PROFILE_SCOPE( GFXD3D9Shader_Init ); + + if ( mPixVersion > GFX->getPixelShaderVersion() ) + { + if ( smLogErrors ) + Con::errorf( "GFXD3D9Shader::init - Bad pixel shader version!" ); + + return false; + } + + if ( mPixVersion < 1.0f && mPixelFile.getFileName().isNotEmpty() ) + { + if ( smLogErrors ) + Con::errorf( "GFXD3D9Shader::init - Pixel shaders not supported on SM %.1f!", mPixVersion ); + + return false; + } + + SAFE_RELEASE(mVertShader); + SAFE_RELEASE(mPixShader); + + U32 mjVer = (U32)mFloor( mPixVersion ); + U32 mnVer = (U32)( ( mPixVersion - F32( mjVer ) ) * 10.01f ); // 10.01 instead of 10.0 because of floating point issues + + String vertTarget = String::ToString("vs_%d_%d", mjVer, mnVer); + String pixTarget = String::ToString("ps_%d_%d", mjVer, mnVer); + + // Adjust version for vertex shaders + if ( ( mPixVersion < 2.0f ) && ( mPixVersion > 1.101f ) ) + vertTarget = "vs_1_1"; + + // Create the macro array including the system wide macros. + const U32 macroCount = smGlobalMacros.size() + mMacros.size() + 3; + FrameTemp d3dXMacros( macroCount ); + for ( U32 i=0; i < smGlobalMacros.size(); i++ ) + { + d3dXMacros[i].Name = smGlobalMacros[i].name.c_str(); + d3dXMacros[i].Definition = smGlobalMacros[i].value.c_str(); + } + for ( U32 i=0; i < mMacros.size(); i++ ) + { + d3dXMacros[i+smGlobalMacros.size()].Name = mMacros[i].name.c_str(); + d3dXMacros[i+smGlobalMacros.size()].Definition = mMacros[i].value.c_str(); + } + String smVersion = String::ToString( mjVer * 10 + mnVer ); + d3dXMacros[macroCount - 2].Name = "TORQUE_SM"; + d3dXMacros[macroCount - 2].Definition = smVersion.c_str(); + d3dXMacros[macroCount - 1].Name = NULL; + d3dXMacros[macroCount - 1].Definition = NULL; + + // Provide HLSL shaders with an OS flag + d3dXMacros[macroCount - 3].Name = "TORQUE_OS_XENON"; +#ifdef TORQUE_OS_XENON + d3dXMacros[macroCount - 3].Definition = "1"; +#else + d3dXMacros[macroCount - 3].Definition = "0"; +#endif + + if ( !mVertexConstBufferLayoutF ) + mVertexConstBufferLayoutF = new GFXD3D9ShaderBufferLayout(); + else + mVertexConstBufferLayoutF->clear(); + + if ( !mVertexConstBufferLayoutI ) + mVertexConstBufferLayoutI = new GFXD3D9ShaderBufferLayout(); + else + mVertexConstBufferLayoutI->clear(); + + if ( !mPixelConstBufferLayoutF ) + mPixelConstBufferLayoutF = new GFXD3D9ShaderBufferLayout(); + else + mPixelConstBufferLayoutF->clear(); + + if ( !mPixelConstBufferLayoutI ) + mPixelConstBufferLayoutI = new GFXD3D9ShaderBufferLayout(); + else + mPixelConstBufferLayoutI->clear(); + + mSamplerDescriptions.clear(); + mShaderConsts.clear(); + + if ( GFXD3DX.isLoaded && !Con::getBoolVariable( "$shaders::forceLoadCSF", false ) ) + { + if ( !mVertexFile.isEmpty() && + !_compileShader( mVertexFile, vertTarget, d3dXMacros, mVertexConstBufferLayoutF, mVertexConstBufferLayoutI, mSamplerDescriptions ) ) + return false; + + if ( !mPixelFile.isEmpty() && + !_compileShader( mPixelFile, pixTarget, d3dXMacros, mPixelConstBufferLayoutF, mPixelConstBufferLayoutI, mSamplerDescriptions ) ) + return false; + } + else + { + if ( !_loadCompiledOutput( mVertexFile, vertTarget, mVertexConstBufferLayoutF, mVertexConstBufferLayoutI, mSamplerDescriptions ) ) + { + if ( smLogErrors ) + Con::errorf( "GFXD3D9Shader::init - Unable to load precompiled vertex shader for '%s'.", + mVertexFile.getFullPath().c_str() ); + + return false; + } + + if ( !_loadCompiledOutput( mPixelFile, pixTarget, mPixelConstBufferLayoutF, mPixelConstBufferLayoutI, mSamplerDescriptions ) ) + { + if ( smLogErrors ) + Con::errorf( "GFXD3D9Shader::init - Unable to load precompiled pixel shader for '%s'.", + mPixelFile.getFullPath().c_str() ); + + return false; + } + } + + // Mark all existing handles as invalid. + // Those that are found when parsing the layout parameters + // will then be marked valid again. + HandleMap::Iterator iter = mHandles.begin(); + for ( ; iter != mHandles.end(); iter++ ) + (iter->value)->setValid( false ); + + _buildShaderConstantHandles(mVertexConstBufferLayoutF, true); + _buildShaderConstantHandles(mVertexConstBufferLayoutI, true); + _buildShaderConstantHandles(mPixelConstBufferLayoutF, false); + _buildShaderConstantHandles(mPixelConstBufferLayoutI, false); + _buildSamplerShaderConstantHandles( mSamplerDescriptions ); + + // Notify Buffers we might have changed in size. + // If this was our first init then we won't have any activeBuffers + // to worry about unnecessarily calling. + Vector::iterator biter = mActiveBuffers.begin(); + for ( ; biter != mActiveBuffers.end(); biter++ ) + (*biter)->onShaderReload( this ); + + return true; +} + +bool GFXD3D9Shader::_compileShader( const Torque::Path &filePath, + const String& target, + const D3DXMACRO *defines, + GenericConstBufferLayout* bufferLayoutF, + GenericConstBufferLayout* bufferLayoutI, + Vector &samplerDescriptions ) +{ + PROFILE_SCOPE( GFXD3D9Shader_CompileShader ); + + HRESULT res = D3DERR_INVALIDCALL; + LPD3DXBUFFER code = NULL; + LPD3DXBUFFER errorBuff = NULL; + +#ifdef TORQUE_DEBUG + U32 flags = D3DXSHADER_DEBUG; +#else + U32 flags = 0; +#endif + +#ifdef TORQUE_OS_XENON + flags |= D3DXSHADER_PREFER_FLOW_CONTROL; +#endif + +#ifdef D3DXSHADER_USE_LEGACY_D3DX9_31_DLL + if( D3DX_SDK_VERSION >= 32 ) + { + // will need to use old compiler for 1_1 shaders - check for pixel + // or vertex shader with appropriate version. + if ((target.compare("vs1", 3) == 0) || (target.compare("vs_1", 4) == 0)) + flags |= D3DXSHADER_USE_LEGACY_D3DX9_31_DLL; + + if ((target.compare("ps1", 3) == 0) || (target.compare("ps_1", 4) == 0)) + flags |= D3DXSHADER_USE_LEGACY_D3DX9_31_DLL; + } +#endif + +#if !defined(TORQUE_OS_XENON) && (D3DX_SDK_VERSION <= 40) +#error This version of the DirectX SDK is too old. Please install a newer version of the DirectX SDK: http://msdn.microsoft.com/en-us/directx/default.aspx +#endif + + ID3DXConstantTable* table = NULL; + + static String sHLSLStr( "hlsl" ); + static String sOBJStr( "obj" ); + + // Is it an HLSL shader? + if ( filePath.getExtension().equal(sHLSLStr, String::NoCase) ) + { + FrameAllocatorMarker fam; + char *buffer = NULL; + + // Set this so that the D3DXInclude::Open will have this + // information for relative paths. + smD3DXInclude->_pShaderFile = filePath; + + FileStream s; + if ( !s.open( filePath, Torque::FS::File::Read ) ) + { + AssertISV(false, avar("GFXD3D9Shader::initShader - failed to open shader '%s'.", filePath.getFullPath().c_str())); + + if ( smLogErrors ) + Con::errorf( "GFXD3D9Shader::_compileShader - Failed to open shader file '%s'.", + filePath.getFullPath().c_str() ); + + return false; + } + + // Convert the path which might have virtualized + // mount paths to a real file system path. + Torque::Path realPath; + if ( !FS::GetFSPath( filePath, realPath ) ) + realPath = filePath; + + // Add a #line pragma so that error and warning messages + // returned by the HLSL compiler report the right file. + String linePragma = String::ToString( "#line 1 \"%s\"\r\n", realPath.getFullPath().c_str() ); + U32 linePragmaLen = linePragma.length(); + + U32 bufSize = s.getStreamSize(); + buffer = (char *)fam.alloc( bufSize + linePragmaLen ); + dStrncpy( buffer, linePragma.c_str(), linePragmaLen ); + s.read( bufSize, buffer + linePragmaLen ); + + res = GFXD3DX.D3DXCompileShader( buffer, bufSize + linePragmaLen, defines, smD3DXInclude, "main", + target, flags, &code, &errorBuff, &table ); + } + + // Is it a precompiled obj shader? + else if ( filePath.getExtension().equal( sOBJStr, String::NoCase ) ) + { + FileStream s; + if(!s.open(filePath, Torque::FS::File::Read)) + { + AssertISV(false, avar("GFXD3D9Shader::initShader - failed to open shader '%s'.", filePath.getFullPath().c_str())); + + if ( smLogErrors ) + Con::errorf( "GFXD3D9Shader::_compileShader - Failed to open shader file '%s'.", + filePath.getFullPath().c_str() ); + + return false; + } + + res = GFXD3DX.D3DXCreateBuffer(s.getStreamSize(), &code); + AssertISV(res == D3D_OK, "Unable to create buffer!"); + s.read(s.getStreamSize(), code->GetBufferPointer()); + + if (res == D3D_OK) + { + DWORD* data = (DWORD*) code->GetBufferPointer(); + res = GFXD3DX.D3DXGetShaderConstantTable(data, &table); + } + } + else + { + if ( smLogErrors ) + Con::errorf( "GFXD3D9Shader::_compileShader - Unsupported shader file type '%s'.", + filePath.getFullPath().c_str() ); + + return false; + } + + // The terrain code can generate failed shaders + // and recover trying a simpler shader.... so we + // cannot just assert here. + /* + if ( Con::getBoolVariable("$pref::assertOnBadShader", false ) ) + { + AssertISV( res == D3D_OK, avar("GFXD3D9Shader::initShader - Unable to compile shader '%s'", + filePath.getFullPath().c_str()) ); + } + */ + + // Wipe our allocations from this compile. + while (gIncludeAllocs.size()) + { + delete [] gIncludeAllocs.last(); + gIncludeAllocs.pop_back(); + } + + if ( res != D3D_OK && smLogErrors ) + Con::errorf( "GFXD3D9Shader::_compileShader - Error compiling shader: %s: %s (%x)", + DXGetErrorStringA(res), DXGetErrorDescriptionA(res), res ); + + if ( errorBuff ) + { + // remove \n at end of buffer + U8 *buffPtr = (U8*) errorBuff->GetBufferPointer(); + U32 len = dStrlen( (const char*) buffPtr ); + buffPtr[len-1] = '\0'; + + if( res != D3D_OK ) + { + if ( smLogErrors ) + Con::errorf( " %s", (const char*) errorBuff->GetBufferPointer() ); + } + else + { + if ( smLogWarnings ) + Con::warnf( "%s", (const char*) errorBuff->GetBufferPointer() ); + } + } + else if ( code == NULL && smLogErrors ) + Con::errorf( "GFXD3D9Shader::_compileShader - no compiled code produced; possibly missing file '%s'.", + filePath.getFullPath().c_str() ); + + // Create the proper shader if we have code + if( code != NULL ) + { + #ifndef TORQUE_SHIPPING + + LPD3DXBUFFER disassem = NULL; + D3DXDisassembleShader( (DWORD*)code->GetBufferPointer(), false, NULL, &disassem ); + mDissasembly = (const char*)disassem->GetBufferPointer(); + SAFE_RELEASE( disassem ); + + if ( gDisassembleAllShaders ) + { + String filename = filePath.getFullPath(); + filename.replace( ".hlsl", "_dis.txt" ); + + FileStream *fstream = FileStream::createAndOpen( filename, Torque::FS::File::Write ); + if ( fstream ) + { + fstream->write( mDissasembly ); + fstream->close(); + delete fstream; + } + } + + #endif + + if (target.compare("ps_", 3) == 0) + res = mD3D9Device->CreatePixelShader( (DWORD*)code->GetBufferPointer(), &mPixShader ); + else + res = mD3D9Device->CreateVertexShader( (DWORD*)code->GetBufferPointer(), &mVertShader ); + + if (res == S_OK) + _getShaderConstants(table, bufferLayoutF, bufferLayoutI, samplerDescriptions); + +#ifdef TORQUE_ENABLE_CSF_GENERATION + + // Ok, we've got a valid shader and constants, let's write them all out. + if ( !_saveCompiledOutput(filePath, code, bufferLayoutF, bufferLayoutI) && smLogErrors ) + Con::errorf( "GFXD3D9Shader::_compileShader - Unable to save shader compile output for: %s", + filePath.getFullPath().c_str() ); + +#endif + + SAFE_RELEASE(table); + + if ( res != S_OK && smLogErrors ) + Con::errorf( "GFXD3D9Shader::_compileShader - Unable to create shader for '%s'.", + filePath.getFullPath().c_str() ); + } + + bool result = code != NULL && res == S_OK; + + SAFE_RELEASE( code ); + SAFE_RELEASE( errorBuff ); + + return result; +} + +void GFXD3D9Shader::_getShaderConstants( ID3DXConstantTable *table, + GenericConstBufferLayout *bufferLayoutF, + GenericConstBufferLayout* bufferLayoutI, + Vector &samplerDescriptions ) +{ + PROFILE_SCOPE( GFXD3D9Shader_GetShaderConstants ); + + AssertFatal(table, "NULL constant table not allowed, is this an assembly shader?"); + + D3DXCONSTANTTABLE_DESC tableDesc; + D3D9Assert(table->GetDesc(&tableDesc), "Unable to get constant table info."); + + for (U32 i = 0; i < tableDesc.Constants; i++) + { + D3DXHANDLE handle = table->GetConstant(0, i); + const U32 descSize=16; + D3DXCONSTANT_DESC constantDescArray[descSize]; + U32 size = descSize; + if (table->GetConstantDesc(handle, constantDescArray, &size) == S_OK) + { + D3DXCONSTANT_DESC& constantDesc = constantDescArray[0]; + GFXShaderConstDesc desc; + + desc.name = String(constantDesc.Name); + // Prepend a "$" if it doesn't exist. Just to make things consistent. + if (desc.name.find("$") != 0) + desc.name = String::ToString("$%s", desc.name.c_str()); + //Con::printf("name %s: , offset: %d, size: %d, constantDesc.Elements: %d", desc.name.c_str(), constantDesc.RegisterIndex, constantDesc.Bytes, constantDesc.Elements); + desc.arraySize = constantDesc.Elements; + + GenericConstBufferLayout* bufferLayout = NULL; + switch (constantDesc.RegisterSet) + { + case D3DXRS_INT4 : + { + bufferLayout = bufferLayoutI; + switch (constantDesc.Class) + { + case D3DXPC_SCALAR : + desc.constType = GFXSCT_Int; + break; + case D3DXPC_VECTOR : + { + switch (constantDesc.Columns) + { + case 1 : + desc.constType = GFXSCT_Int; + break; + case 2 : + desc.constType = GFXSCT_Int2; + break; + case 3 : + desc.constType = GFXSCT_Int3; + break; + case 4 : + desc.constType = GFXSCT_Int4; + break; + default: + AssertFatal(false, "Unknown int vector type!"); + break; + } + } + break; + } + desc.constType = GFXSCT_Int4; + break; + } + case D3DXRS_FLOAT4 : + { + bufferLayout = bufferLayoutF; + switch (constantDesc.Class) + { + case D3DXPC_SCALAR: + desc.constType = GFXSCT_Float; + break; + case D3DXPC_VECTOR : + { + switch (constantDesc.Columns) + { + case 1 : + desc.constType = GFXSCT_Float; + break; + case 2 : + desc.constType = GFXSCT_Float2; + break; + case 3 : + desc.constType = GFXSCT_Float3; + break; + case 4 : + desc.constType = GFXSCT_Float4; + break; + default: + AssertFatal(false, "Unknown float vector type!"); + break; + } + } + break; + case D3DXPC_MATRIX_ROWS : + case D3DXPC_MATRIX_COLUMNS : + { + switch (constantDesc.Columns) + { + case 3 : + desc.constType = GFXSCT_Float3x3; + break; + case 4 : + desc.constType = GFXSCT_Float4x4; + break; + } + } + break; + case D3DXPC_OBJECT : + case D3DXPC_STRUCT : + bufferLayout = NULL; + break; + } + } + break; + case D3DXRS_SAMPLER : + { + AssertFatal( constantDesc.Elements == 1, "Sampler Arrays not yet supported!" ); + + switch (constantDesc.Type) + { + case D3DXPT_SAMPLER : + case D3DXPT_SAMPLER1D : + case D3DXPT_SAMPLER2D : + case D3DXPT_SAMPLER3D : + // Hi-jack the desc's arraySize to store the registerIndex. + desc.constType = GFXSCT_Sampler; + desc.arraySize = constantDesc.RegisterIndex; + samplerDescriptions.push_back( desc ); + break; + case D3DXPT_SAMPLERCUBE : + desc.constType = GFXSCT_SamplerCube; + desc.arraySize = constantDesc.RegisterIndex; + samplerDescriptions.push_back( desc ); + break; + } + } + break; + default: + AssertFatal(false, "Unknown shader constant class enum"); + break; + } + + if (bufferLayout) + { + mShaderConsts.push_back(desc); + + U32 alignBytes = getAlignmentValue(desc.constType); + U32 paramSize = alignBytes * desc.arraySize; + bufferLayout->addParameter( desc.name, + desc.constType, + constantDesc.RegisterIndex * sizeof(Point4F), + paramSize, + desc.arraySize, + alignBytes ); + } + } + else + AssertFatal(false, "Unable to get shader constant description! (may need more elements of constantDesc"); + } +} + +const U32 GFXD3D9Shader::smCompiledShaderTag = MakeFourCC('t','c','s','f'); + +bool GFXD3D9Shader::_saveCompiledOutput( const Torque::Path &filePath, + LPD3DXBUFFER buffer, + GenericConstBufferLayout *bufferLayoutF, + GenericConstBufferLayout *bufferLayoutI, + Vector &samplerDescriptions ) +{ + Torque::Path outputPath(filePath); + outputPath.setExtension("csf"); // "C"ompiled "S"hader "F"ile (fancy!) + + FileStream f; + if (!f.open(outputPath, Torque::FS::File::Write)) + return false; + if (!f.write(smCompiledShaderTag)) + return false; + // We could reverse engineer the structure in the compiled output, but this + // is a bit easier because we can just read it into the struct that we want. + if (!bufferLayoutF->write(&f)) + return false; + if (!bufferLayoutI->write(&f)) + return false; + U32 bufferSize = buffer->GetBufferSize(); + if (!f.write(bufferSize)) + return false; + if (!f.write(bufferSize, buffer->GetBufferPointer())) + return false; + + // Write out sampler descriptions. + + f.write( samplerDescriptions.size() ); + + for ( U32 i = 0; i < samplerDescriptions.size(); i++ ) + { + f.write( samplerDescriptions[i].name ); + f.write( (U32)(samplerDescriptions[i].constType) ); + f.write( samplerDescriptions[i].arraySize ); + } + + f.close(); + + return true; +} + +bool GFXD3D9Shader::_loadCompiledOutput( const Torque::Path &filePath, + const String &target, + GenericConstBufferLayout *bufferLayoutF, + GenericConstBufferLayout *bufferLayoutI, + Vector &samplerDescriptions ) +{ + Torque::Path outputPath(filePath); + outputPath.setExtension("csf"); // "C"ompiled "S"hader "F"ile (fancy!) + + FileStream f; + if (!f.open(outputPath, Torque::FS::File::Read)) + return false; + U32 fileTag; + if (!f.read(&fileTag)) + return false; + if (fileTag != smCompiledShaderTag) + return false; + if (!bufferLayoutF->read(&f)) + return false; + if (!bufferLayoutI->read(&f)) + return false; + U32 bufferSize; + if (!f.read(&bufferSize)) + return false; + U32 waterMark = FrameAllocator::getWaterMark(); + DWORD* buffer = static_cast(FrameAllocator::alloc(bufferSize)); + if (!f.read(bufferSize, buffer)) + return false; + + // Read sampler descriptions. + + U32 samplerCount; + f.read( &samplerCount ); + + for ( U32 i = 0; i < samplerCount; i++ ) + { + GFXShaderConstDesc samplerDesc; + f.read( &(samplerDesc.name) ); + f.read( (U32*)&(samplerDesc.constType) ); + f.read( &(samplerDesc.arraySize) ); + + samplerDescriptions.push_back( samplerDesc ); + } + + f.close(); + + HRESULT res; + if (target.compare("ps_", 3) == 0) + res = mD3D9Device->CreatePixelShader(buffer, &mPixShader ); + else + res = mD3D9Device->CreateVertexShader(buffer, &mVertShader ); + AssertFatal(SUCCEEDED(res), "Unable to load shader!"); + + FrameAllocator::setWaterMark(waterMark); + return SUCCEEDED(res); +} + +void GFXD3D9Shader::_buildShaderConstantHandles(GenericConstBufferLayout* layout, bool vertexConst) +{ + for (U32 i = 0; i < layout->getParameterCount(); i++) + { + GenericConstBufferLayout::ParamDesc pd; + layout->getDesc(i, pd); + + GFXD3D9ShaderConstHandle* handle; + HandleMap::Iterator j = mHandles.find(pd.name); + if (j != mHandles.end()) + { + handle = j->value; + handle->setValid( true ); + } else { + handle = new GFXD3D9ShaderConstHandle(); + handle->mShader = this; + mHandles[pd.name] = handle; + handle->setValid( true ); + } + if (vertexConst) + { + handle->mVertexConstant = true; + handle->mVertexHandle = pd; + } else { + handle->mPixelConstant = true; + handle->mPixelHandle = pd; + } + } +} + +void GFXD3D9Shader::_buildSamplerShaderConstantHandles( Vector &samplerDescriptions ) +{ + Vector::iterator iter = samplerDescriptions.begin(); + for ( ; iter != samplerDescriptions.end(); iter++ ) + { + const GFXShaderConstDesc &desc = *iter; + + AssertFatal( desc.constType == GFXSCT_Sampler || desc.constType == GFXSCT_SamplerCube, "Invalid samplerDescription type!" ); + + GFXD3D9ShaderConstHandle *handle; + HandleMap::Iterator j = mHandles.find(desc.name); + + if ( j != mHandles.end() ) + { + handle = j->value; + handle->mShader = this; + handle->setValid( true ); + handle->mPixelConstant = true; + handle->mPixelHandle.constType = desc.constType; + handle->mPixelHandle.offset = desc.arraySize; + mHandles[desc.name] = handle; + } + else + { + handle = new GFXD3D9ShaderConstHandle(); + handle->mShader = this; + handle->setValid( true ); + handle->mPixelConstant = true; + handle->mPixelHandle.constType = desc.constType; + handle->mPixelHandle.offset = desc.arraySize; + mHandles[desc.name] = handle; + } + } +} + +GFXShaderConstBufferRef GFXD3D9Shader::allocConstBuffer() +{ + if (mVertexConstBufferLayoutF && mPixelConstBufferLayoutF) + { + GFXD3D9ShaderConstBuffer* buffer = new GFXD3D9ShaderConstBuffer(this, mVertexConstBufferLayoutF, mVertexConstBufferLayoutI, mPixelConstBufferLayoutF, mPixelConstBufferLayoutI); + mActiveBuffers.push_back( buffer ); + buffer->registerResourceWithDevice(getOwningDevice()); + return buffer; + } else { + return NULL; + } +} + +/// Returns a shader constant handle for name, if the variable doesn't exist NULL is returned. +GFXShaderConstHandle* GFXD3D9Shader::getShaderConstHandle(const String& name) +{ + HandleMap::Iterator i = mHandles.find(name); + if ( i != mHandles.end() ) + { + return i->value; + } + else + { + GFXD3D9ShaderConstHandle *handle = new GFXD3D9ShaderConstHandle(); + handle->setValid( false ); + handle->mShader = this; + mHandles[name] = handle; + + return handle; + } +} + +const Vector& GFXD3D9Shader::getShaderConstDesc() const +{ + return mShaderConsts; +} + +U32 GFXD3D9Shader::getAlignmentValue(const GFXShaderConstType constType) const +{ + const U32 mRowSizeF = 16; + const U32 mRowSizeI = 16; + + switch (constType) + { + case GFXSCT_Float : + case GFXSCT_Float2 : + case GFXSCT_Float3 : + case GFXSCT_Float4 : + return mRowSizeF; + break; + // Matrices + case GFXSCT_Float2x2 : + return mRowSizeF * 2; + break; + case GFXSCT_Float3x3 : + return mRowSizeF * 3; + break; + case GFXSCT_Float4x4 : + return mRowSizeF * 4; + break; + //// Scalar + case GFXSCT_Int : + case GFXSCT_Int2 : + case GFXSCT_Int3 : + case GFXSCT_Int4 : + return mRowSizeI; + break; + default: + AssertFatal(false, "Unsupported type!"); + return 0; + break; + } +} + +void GFXD3D9Shader::zombify() +{ + // Shaders don't need zombification +} + +void GFXD3D9Shader::resurrect() +{ + // Shaders are never zombies, and therefore don't have to be brought back. +} diff --git a/gfx/D3D9/gfxD3D9Shader.h b/gfx/D3D9/gfxD3D9Shader.h new file mode 100644 index 0000000..df172bd --- /dev/null +++ b/gfx/D3D9/gfxD3D9Shader.h @@ -0,0 +1,253 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXD3D9SHADER_H_ +#define _GFXD3D9SHADER_H_ + +#include "gfx/D3D9/platformD3D.h" +#ifndef _PATH_H_ +#include "core/util/path.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _GFXRESOURCE_H_ +#include "gfx/gfxResource.h" +#endif +#ifndef _GENERICCONSTBUFFER_H_ +#include "gfx/genericConstBuffer.h" +#endif + + +class GFXD3D9Shader; + +class GFXD3D9ShaderBufferLayout : public GenericConstBufferLayout +{ +protected: + /// Set a matrix, given a base pointer + virtual bool setMatrix(const ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data, U8* basePointer); +}; + +class GFXD3D9ShaderConstHandle : public GFXShaderConstHandle +{ +public: + + // GFXShaderConstHandle + const String& getName() const; + GFXShaderConstType getType() const; + U32 getArraySize() const; + + WeakRefPtr mShader; + + bool mVertexConstant; + GenericConstBufferLayout::ParamDesc mVertexHandle; + bool mPixelConstant; + GenericConstBufferLayout::ParamDesc mPixelHandle; + + void setValid( bool valid ) { mValid = valid; } + S32 getSamplerRegister() const; + + // Returns true if this is a handle to a sampler register. + bool isSampler() const + { + return ( mPixelConstant && mPixelHandle.constType >= GFXSCT_Sampler ) || + ( mVertexConstant && mVertexHandle.constType >= GFXSCT_Sampler ); + } + + GFXD3D9ShaderConstHandle(); +}; + +class GFXD3D9ShaderConstBuffer : public GFXShaderConstBuffer +{ + friend class GFXD3D9Shader; + +public: + + GFXD3D9ShaderConstBuffer( GFXD3D9Shader* shader, + GFXD3D9ShaderBufferLayout* vertexLayoutF, + GFXD3D9ShaderBufferLayout* vertexLayoutI, + GFXD3D9ShaderBufferLayout* pixelLayoutF, + GFXD3D9ShaderBufferLayout* pixelLayoutI ); + ~GFXD3D9ShaderConstBuffer(); + + /// Called by GFXD3D9Device to activate this buffer. + /// @param mPrevShaderBuffer The previously active buffer + void activate(GFXD3D9ShaderConstBuffer* mPrevShaderBuffer); + + /// Used internally by GXD3D9ShaderConstBuffer to determine if it's dirty. + bool isDirty(); + + /// + /// @name GFXShaderConstBuffer interface + /// @{ + + virtual GFXShader* getShader(); + + /// @name Set shader constant values + /// @{ + /// Actually set shader constant values + /// @param name Name of the constant, this should be a name contained in the array returned in getShaderConstDesc, + /// if an invalid name is used, its ignored. + virtual void set(GFXShaderConstHandle* handle, const F32 fv); + virtual void set(GFXShaderConstHandle* handle, const Point2F& fv); + virtual void set(GFXShaderConstHandle* handle, const Point3F& fv); + virtual void set(GFXShaderConstHandle* handle, const Point4F& fv); + virtual void set(GFXShaderConstHandle* handle, const PlaneF& fv); + virtual void set(GFXShaderConstHandle* handle, const ColorF& fv); + virtual void set(GFXShaderConstHandle* handle, const S32 f); + virtual void set(GFXShaderConstHandle* handle, const Point2I& fv); + virtual void set(GFXShaderConstHandle* handle, const Point3I& fv); + virtual void set(GFXShaderConstHandle* handle, const Point4I& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const MatrixF& mat, const GFXShaderConstType matType = GFXSCT_Float4x4); + virtual void set(GFXShaderConstHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType = GFXSCT_Float4x4); + /// @} + + /// @} + + /// + /// @name GFXResource interface + /// @{ + + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const; + + /// When called the resource should destroy all device sensitive information (e.g. D3D resources in D3DPOOL_DEFAULT + virtual void zombify(); + + /// When called the resource should restore all device sensitive information destroyed by zombify() + virtual void resurrect(); + /// @} + + /// Called when the shader this buffer references is reloaded. + virtual void onShaderReload( GFXShader *shader ); + +protected: + // Empty constructor for sub-classing + GFXD3D9ShaderConstBuffer() {} + + /// We keep a weak reference to the shader + /// because it will often be deleted. + WeakRefPtr mShader; + + GFXD3D9ShaderBufferLayout* mVertexConstBufferLayoutF; + GenericConstBuffer* mVertexConstBufferF; + GFXD3D9ShaderBufferLayout* mPixelConstBufferLayoutF; + GenericConstBuffer* mPixelConstBufferF; + GFXD3D9ShaderBufferLayout* mVertexConstBufferLayoutI; + GenericConstBuffer* mVertexConstBufferI; + GFXD3D9ShaderBufferLayout* mPixelConstBufferLayoutI; + GenericConstBuffer* mPixelConstBufferI; +}; + +//------------------------------------------------------------------------------ + +class _gfxD3DXInclude; +typedef StrongRefPtr<_gfxD3DXInclude> _gfxD3DXIncludeRef; + +//------------------------------------------------------------------------------ + +class GFXD3D9Shader : public GFXShader +{ + friend class GFXD3D9Device; + friend class GFX360Device; + friend class GFXD3D9ShaderConstBuffer; + friend class GFX360ShaderConstBuffer; +public: + typedef Map HandleMap; + + GFXD3D9Shader(); + virtual ~GFXD3D9Shader(); + + // GFXShader + virtual GFXShaderConstBufferRef allocConstBuffer(); + virtual const Vector& getShaderConstDesc() const; + virtual GFXShaderConstHandle* getShaderConstHandle(const String& name); + virtual U32 getAlignmentValue(const GFXShaderConstType constType) const; + virtual bool getDisassembly( String &outStr ) const; + + // GFXResource + virtual void zombify(); + virtual void resurrect(); + +protected: + + virtual bool _init(); + + static const U32 smCompiledShaderTag; + + LPDIRECT3DDEVICE9 mD3D9Device; + + IDirect3DVertexShader9 *mVertShader; + IDirect3DPixelShader9 *mPixShader; + + GFXD3D9ShaderBufferLayout* mVertexConstBufferLayoutF; + GFXD3D9ShaderBufferLayout* mPixelConstBufferLayoutF; + GFXD3D9ShaderBufferLayout* mVertexConstBufferLayoutI; + GFXD3D9ShaderBufferLayout* mPixelConstBufferLayoutI; + + static _gfxD3DXIncludeRef smD3DXInclude; + + HandleMap mHandles; + + /// The shader disassembly from DX when this shader is compiled. + /// We only store this data in non-release builds. + String mDissasembly; + + /// Vector of sampler type descriptions consolidated from _compileShader. + Vector mSamplerDescriptions; + + /// Vector of descriptions (consolidated for the getShaderConstDesc call) + Vector mShaderConsts; + + // These two functions are used when compiling shaders from hlsl + virtual bool _compileShader( const Torque::Path &filePath, + const String &target, + const D3DXMACRO *defines, + GenericConstBufferLayout *bufferLayoutF, + GenericConstBufferLayout *bufferLayoutI, + Vector &samplerDescriptions ); + + void _getShaderConstants( ID3DXConstantTable* table, + GenericConstBufferLayout *bufferLayoutF, + GenericConstBufferLayout *bufferLayoutI, + Vector &samplerDescriptions ); + + bool _saveCompiledOutput( const Torque::Path &filePath, + LPD3DXBUFFER buffer, + GenericConstBufferLayout *bufferLayoutF, + GenericConstBufferLayout *bufferLayoutI, + Vector &samplerDescriptions ); + + // Loads precompiled shaders + bool _loadCompiledOutput( const Torque::Path &filePath, + const String &target, + GenericConstBufferLayout *bufferLayoutF, + GenericConstBufferLayout *bufferLayoutI, + Vector &samplerDescriptions ); + + // This is used in both cases + virtual void _buildShaderConstantHandles( GenericConstBufferLayout *layout, bool vertexConst ); + + virtual void _buildSamplerShaderConstantHandles( Vector &samplerDescriptions ); +}; + +inline bool GFXD3D9Shader::getDisassembly( String &outStr ) const +{ + outStr = mDissasembly; + return ( outStr.isNotEmpty() ); +} + +#endif // _GFXD3D9SHADER_H_ \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9StateBlock.cpp b/gfx/D3D9/gfxD3D9StateBlock.cpp new file mode 100644 index 0000000..1f32444 --- /dev/null +++ b/gfx/D3D9/gfxD3D9StateBlock.cpp @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/gfxD3D9StateBlock.h" +#include "gfx/gfxDevice.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" + +GFXD3D9StateBlock::GFXD3D9StateBlock(const GFXStateBlockDesc& desc, LPDIRECT3DDEVICE9 d3dDevice) +{ + AssertFatal(d3dDevice, "Invalid mD3DDevice!"); + + mDesc = desc; + mCachedHashValue = desc.getHashValue(); + mD3DDevice = d3dDevice; + + // Color writes + mColorMask = 0; + mColorMask |= ( mDesc.colorWriteRed ? GFXCOLORWRITEENABLE_RED : 0 ); + mColorMask |= ( mDesc.colorWriteGreen ? GFXCOLORWRITEENABLE_GREEN : 0 ); + mColorMask |= ( mDesc.colorWriteBlue ? GFXCOLORWRITEENABLE_BLUE : 0 ); + mColorMask |= ( mDesc.colorWriteAlpha ? GFXCOLORWRITEENABLE_ALPHA : 0 ); + + // Z*bias + mZBias = *((U32*)&mDesc.zBias); + mZSlopeBias = *((U32*)&mDesc.zSlopeBias); +} + +GFXD3D9StateBlock::~GFXD3D9StateBlock() +{ + +} + +/// Returns the hash value of the desc that created this block +U32 GFXD3D9StateBlock::getHashValue() const +{ + return mCachedHashValue; +} + +/// Returns a GFXStateBlockDesc that this block represents +const GFXStateBlockDesc& GFXD3D9StateBlock::getDesc() const +{ + return mDesc; +} + +/// Called by D3D9 device to active this state block. +/// @param oldState The current state, used to make sure we don't set redundant states on the device. Pass NULL to reset all states. +void GFXD3D9StateBlock::activate(GFXD3D9StateBlock* oldState) +{ + PROFILE_SCOPE( GFXD3D9StateBlock_Activate ); + + // Little macro to save some typing, SD = state diff, checks for null source state block, then + // checks to see if the states differ +#if defined(TORQUE_OS_XENON) + #define SD(x, y) if (!oldState || oldState->mDesc.x != mDesc.x) \ + mD3DDevice->SetRenderState_Inline(y, mDesc.x) + + // Same as above, but allows you to set the data + #define SDD(x, y, z) if (!oldState || oldState->mDesc.x != mDesc.x) \ + mD3DDevice->SetRenderState_Inline(y, z) +#else + #define SD(x, y) if (!oldState || oldState->mDesc.x != mDesc.x) \ + mD3DDevice->SetRenderState(y, mDesc.x) + + // Same as above, but allows you to set the data + #define SDD(x, y, z) if (!oldState || oldState->mDesc.x != mDesc.x) \ + mD3DDevice->SetRenderState(y, z) +#endif + + // Blending + SD(blendEnable, D3DRS_ALPHABLENDENABLE); + SDD(blendSrc, D3DRS_SRCBLEND, GFXD3D9Blend[mDesc.blendSrc]); + SDD(blendDest, D3DRS_DESTBLEND, GFXD3D9Blend[mDesc.blendDest]); + SDD(blendOp, D3DRS_BLENDOP, GFXD3D9BlendOp[mDesc.blendOp]); + + // Separate alpha blending + SD(separateAlphaBlendEnable, D3DRS_SEPARATEALPHABLENDENABLE); + SDD(separateAlphaBlendSrc, D3DRS_SRCBLENDALPHA, GFXD3D9Blend[mDesc.separateAlphaBlendSrc]); + SDD(separateAlphaBlendDest, D3DRS_DESTBLENDALPHA, GFXD3D9Blend[mDesc.separateAlphaBlendDest]); + SDD(separateAlphaBlendOp, D3DRS_BLENDOPALPHA, GFXD3D9BlendOp[mDesc.separateAlphaBlendOp]); + + // Alpha test + SD(alphaTestEnable, D3DRS_ALPHATESTENABLE); + SDD(alphaTestFunc, D3DRS_ALPHAFUNC, GFXD3D9CmpFunc[mDesc.alphaTestFunc]); + SD(alphaTestRef, D3DRS_ALPHAREF); + + // Color writes + if ((oldState == NULL) || (mColorMask != oldState->mColorMask)) + mD3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, mColorMask); + + // Culling + SDD(cullMode, D3DRS_CULLMODE, GFXD3D9CullMode[mDesc.cullMode]); + + // Depth + SD(zEnable, D3DRS_ZENABLE); + SD(zWriteEnable, D3DRS_ZWRITEENABLE); + SDD(zFunc, D3DRS_ZFUNC, GFXD3D9CmpFunc[mDesc.zFunc]); + if ((!oldState) || (mZBias != oldState->mZBias)) + mD3DDevice->SetRenderState(D3DRS_DEPTHBIAS, mZBias); + if ((!oldState) || (mZSlopeBias != oldState->mZSlopeBias)) + mD3DDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, mZSlopeBias); + + // Stencil + SD(stencilEnable, D3DRS_STENCILENABLE); + SDD(stencilFailOp, D3DRS_STENCILFAIL, GFXD3D9StencilOp[mDesc.stencilFailOp]); + SDD(stencilZFailOp, D3DRS_STENCILZFAIL, GFXD3D9StencilOp[mDesc.stencilZFailOp]); + SDD(stencilPassOp, D3DRS_STENCILPASS, GFXD3D9StencilOp[mDesc.stencilPassOp]); + SDD(stencilFunc, D3DRS_STENCILFUNC, GFXD3D9CmpFunc[mDesc.stencilFunc]); + SD(stencilRef, D3DRS_STENCILREF); + SD(stencilMask, D3DRS_STENCILMASK); + SD(stencilWriteMask, D3DRS_STENCILWRITEMASK); + SDD(fillMode, D3DRS_FILLMODE, GFXD3D9FillMode[mDesc.fillMode]); +#if !defined(TORQUE_OS_XENON) + SD(ffLighting, D3DRS_LIGHTING); + SD(vertexColorEnable, D3DRS_COLORVERTEX); + + static DWORD swzTemp; + getOwningDevice()->getDeviceSwizzle32()->ToBuffer( &swzTemp, &mDesc.textureFactor, sizeof(ColorI) ); + SDD(textureFactor, D3DRS_TEXTUREFACTOR, swzTemp); +#endif +#undef SD +#undef SDD + + + // NOTE: Samplers and Stages are different things. + // + // The Stages were for fixed function blending. When using shaders + // calling SetTextureStageState() is a complete waste of time. In + // fact if this function rises to the top of profiles we should + // refactor stateblocks to seperate the two. + // + // Samplers are used by both fixed function and shaders, but the + // number of samplers is limited by shader model. +#if !defined(TORQUE_OS_XENON) + + #define TSS(x, y, z) if (!oldState || oldState->mDesc.samplers[i].x != mDesc.samplers[i].x) \ + mD3DDevice->SetTextureStageState(i, y, z) + for ( U32 i = 0; i < 8; i++ ) + { + TSS(textureColorOp, D3DTSS_COLOROP, GFXD3D9TextureOp[mDesc.samplers[i].textureColorOp]); + TSS(colorArg1, D3DTSS_COLORARG1, mDesc.samplers[i].colorArg1); + TSS(colorArg2, D3DTSS_COLORARG2, mDesc.samplers[i].colorArg2); + TSS(colorArg3, D3DTSS_COLORARG0, mDesc.samplers[i].colorArg3); + TSS(alphaOp, D3DTSS_ALPHAOP, GFXD3D9TextureOp[mDesc.samplers[i].alphaOp]); + TSS(alphaArg1, D3DTSS_ALPHAARG1, mDesc.samplers[i].alphaArg1); + TSS(alphaArg2, D3DTSS_ALPHAARG2, mDesc.samplers[i].alphaArg2); + TSS(alphaArg3, D3DTSS_ALPHAARG0, mDesc.samplers[i].alphaArg3); + TSS(textureTransform, D3DTSS_TEXTURETRANSFORMFLAGS, mDesc.samplers[i].textureTransform); + TSS(resultArg, D3DTSS_RESULTARG, mDesc.samplers[i].resultArg); + } + #undef TSS +#endif + +#if defined(TORQUE_OS_XENON) + #define SS(x, y, z) if (!oldState || oldState->mDesc.samplers[i].x != mDesc.samplers[i].x) \ + mD3DDevice->SetSamplerState_Inline(i, y, z) +#else + #define SS(x, y, z) if (!oldState || oldState->mDesc.samplers[i].x != mDesc.samplers[i].x) \ + mD3DDevice->SetSamplerState(i, y, z) +#endif + for ( U32 i = 0; i < getOwningDevice()->getNumSamplers(); i++ ) + { + SS(minFilter, D3DSAMP_MINFILTER, GFXD3D9TextureFilter[mDesc.samplers[i].minFilter]); + SS(magFilter, D3DSAMP_MAGFILTER, GFXD3D9TextureFilter[mDesc.samplers[i].magFilter]); + SS(mipFilter, D3DSAMP_MIPFILTER, GFXD3D9TextureFilter[mDesc.samplers[i].mipFilter]); + + F32 bias = mDesc.samplers[i].mipLODBias; + DWORD dwBias = *( (LPDWORD)(&bias) ); + SS(mipLODBias, D3DSAMP_MIPMAPLODBIAS, dwBias); + + SS(maxAnisotropy, D3DSAMP_MAXANISOTROPY, mDesc.samplers[i].maxAnisotropy); + + SS(addressModeU, D3DSAMP_ADDRESSU, GFXD3D9TextureAddress[mDesc.samplers[i].addressModeU]); + SS(addressModeV, D3DSAMP_ADDRESSV, GFXD3D9TextureAddress[mDesc.samplers[i].addressModeV]); + SS(addressModeW, D3DSAMP_ADDRESSW, GFXD3D9TextureAddress[mDesc.samplers[i].addressModeW]); + } + #undef SS +} diff --git a/gfx/D3D9/gfxD3D9StateBlock.h b/gfx/D3D9/gfxD3D9StateBlock.h new file mode 100644 index 0000000..29c3fb7 --- /dev/null +++ b/gfx/D3D9/gfxD3D9StateBlock.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GFXD3D9STATEBLOCK_H_ +#define _GFXD3D9STATEBLOCK_H_ + +#include "gfx/D3D9/platformD3D.h" + +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + +class GFXD3D9StateBlock : public GFXStateBlock +{ +public: + // + // GFXD3D9StateBlock interface + // + + GFXD3D9StateBlock(const GFXStateBlockDesc& desc, LPDIRECT3DDEVICE9 d3dDevice); + virtual ~GFXD3D9StateBlock(); + + /// Called by D3D9 device to active this state block. + /// @param oldState The current state, used to make sure we don't set redundant states on the device. Pass NULL to reset all states. + void activate(GFXD3D9StateBlock* oldState); + + + // + // GFXStateBlock interface + // + + /// Returns the hash value of the desc that created this block + virtual U32 getHashValue() const; + + /// Returns a GFXStateBlockDesc that this block represents + virtual const GFXStateBlockDesc& getDesc() const; + + // + // GFXResource + // + virtual void zombify() { } + /// When called the resource should restore all device sensitive information destroyed by zombify() + virtual void resurrect() { } +private: + GFXStateBlockDesc mDesc; + U32 mCachedHashValue; + LPDIRECT3DDEVICE9 mD3DDevice; ///< Handle for D3DDevice + // Cached D3D specific things, these are "calculated" from GFXStateBlock + U32 mColorMask; + U32 mZBias; + U32 mZSlopeBias; +}; + +typedef StrongRefPtr GFXD3D9StateBlockRef; + +#endif \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9TextureManager.cpp b/gfx/D3D9/gfxD3D9TextureManager.cpp new file mode 100644 index 0000000..d110b4f --- /dev/null +++ b/gfx/D3D9/gfxD3D9TextureManager.cpp @@ -0,0 +1,572 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifdef _MSC_VER +#pragma warning(disable: 4996) +#endif + +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#include "gfx/bitmap/bitmapUtils.h" +#include "gfx/gfxCardProfile.h" +#include "core/strings/unicode.h" +#include "core/util/swizzle.h" +#include "core/util/safeDelete.h" +#include "console/console.h" +#include "core/resourceManager.h" + +//----------------------------------------------------------------------------- +// Utility function, valid only in this file +#ifdef D3D_TEXTURE_SPEW +U32 GFXD3D9TextureObject::mTexCount = 0; +#endif + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +GFXD3D9TextureManager::GFXD3D9TextureManager( LPDIRECT3DDEVICE9 d3ddevice ) +{ + mD3DDevice = d3ddevice; + dMemset( mCurTexSet, 0, sizeof( mCurTexSet ) ); + mD3DDevice->GetDeviceCaps(&mDeviceCaps); +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +GFXD3D9TextureManager::~GFXD3D9TextureManager() +{ + // Destroy texture table now so just in case some texture objects + // are still left, we don't crash on a pure virtual method call. + SAFE_DELETE_ARRAY( mHashTable ); +} + +//----------------------------------------------------------------------------- +// _innerCreateTexture +//----------------------------------------------------------------------------- +void GFXD3D9TextureManager::_innerCreateTexture( GFXD3D9TextureObject *retTex, + U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips, + S32 antialiasLevel) +{ + GFXD3D9Device* d3d = static_cast(GFX); + + // Some relevant helper information... + bool supportsAutoMips = GFX->getCardProfiler()->queryProfile("autoMipMapLevel", true); + + DWORD usage = 0; // 0, D3DUSAGE_RENDERTARGET, or D3DUSAGE_DYNAMIC + D3DPOOL pool = D3DPOOL_DEFAULT; + + retTex->mProfile = profile; + + D3DFORMAT d3dTextureFormat = GFXD3D9TextureFormat[format]; + +#ifndef TORQUE_OS_XENON + if( retTex->mProfile->isDynamic() ) + { + usage = D3DUSAGE_DYNAMIC; + } + else + { + usage = 0; + pool = D3DPOOL_MANAGED; + } + + if( retTex->mProfile->isRenderTarget() ) + { + pool = D3DPOOL_DEFAULT; + usage |= D3DUSAGE_RENDERTARGET; + } + + if(retTex->mProfile->isZTarget()) + { + usage |= D3DUSAGE_DEPTHSTENCIL; + pool = D3DPOOL_DEFAULT; + } + + if( retTex->mProfile->isSystemMemory() ) + { + pool = D3DPOOL_SYSTEMMEM; + } + + if( supportsAutoMips && + !forceMips && + !retTex->mProfile->isSystemMemory() && + numMipLevels == 0 && + !(depth > 0) ) + { + usage |= D3DUSAGE_AUTOGENMIPMAP; + } +#else + if(retTex->mProfile->isRenderTarget()) + { + d3dTextureFormat = (D3DFORMAT)MAKELEFMT(d3dTextureFormat); + } +#endif + + // Set the managed flag... + retTex->isManaged = (pool == D3DPOOL_MANAGED); + + if( depth > 0 ) + { +#ifdef TORQUE_OS_XENON + D3D9Assert( mD3DDevice->CreateVolumeTexture( width, height, depth, numMipLevels, 0 /* usage ignored on the 360 */, + d3dTextureFormat, pool, retTex->get3DTexPtr(), NULL), "Failed to create volume texture" ); +#else + D3D9Assert( + GFXD3DX.D3DXCreateVolumeTexture( + mD3DDevice, + width, + height, + depth, + numMipLevels, + usage, + d3dTextureFormat, + pool, + retTex->get3DTexPtr() + ), "GFXD3D9TextureManager::_createTexture - failed to create volume texture!" + ); +#endif + + retTex->mTextureSize.set( width, height, depth ); + retTex->mMipLevels = retTex->get3DTex()->GetLevelCount(); + // required for 3D texture support - John Kabus + retTex->mFormat = format; + } + else + { +#ifdef TORQUE_OS_XENON + D3D9Assert( mD3DDevice->CreateTexture(width, height, numMipLevels, usage, d3dTextureFormat, pool, retTex->get2DTexPtr(), NULL), "Failed to create texture" ); + retTex->mMipLevels = retTex->get2DTex()->GetLevelCount(); +#else + // Figure out AA settings for depth and render targets + D3DMULTISAMPLE_TYPE mstype; + DWORD mslevel; + + switch (antialiasLevel) + { + case 0 : + mstype = D3DMULTISAMPLE_NONE; + mslevel = 0; + break; + case AA_MATCH_BACKBUFFER : + mstype = d3d->getMultisampleType(); + mslevel = d3d->getMultisampleLevel(); + break; + default : + { + mstype = D3DMULTISAMPLE_NONMASKABLE; + mslevel = antialiasLevel; +#ifdef TORQUE_DEBUG + DWORD MaxSampleQualities; + d3d->getD3D()->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dTextureFormat, FALSE, D3DMULTISAMPLE_NONMASKABLE, &MaxSampleQualities); + AssertFatal(mslevel < MaxSampleQualities, "Invalid AA level!"); +#endif + } + break; + } + + bool fastCreate = true; + // Check for power of 2 textures - this is a problem with FX 5xxx cards + // with current drivers - 3/2/05 + if( !isPow2(width) || !isPow2(height) ) + { + fastCreate = false; + } + + if(retTex->mProfile->isZTarget()) + { + D3D9Assert(mD3DDevice->CreateDepthStencilSurface(width, height, d3dTextureFormat, + mstype, mslevel, FALSE, retTex->getSurfacePtr(), NULL), "Failed to create Z surface" ); + + retTex->mFormat = format; // Assigning format like this should be fine. + } + else + { + // Try to create the texture directly - should gain us a bit in high + // performance cases where we know we're creating good stuff and we + // don't want to bother with D3DX - slow function. + HRESULT res = D3DERR_INVALIDCALL; + if( fastCreate ) + { + res = mD3DDevice->CreateTexture(width, height, numMipLevels, usage, d3dTextureFormat, pool, retTex->get2DTexPtr(), NULL); + } + + if( !fastCreate || (res != D3D_OK) ) + { + D3D9Assert( + GFXD3DX.D3DXCreateTexture( + mD3DDevice, + width, + height, + numMipLevels, + usage, + d3dTextureFormat, + pool, + retTex->get2DTexPtr() + ), "GFXD3D9TextureManager::_createTexture - failed to create texture!" + ); + } + + // If this is a render target, and it wants AA or wants to match the backbuffer (for example, to share the z) + // Check the caps though, if we can't stretchrect between textures, use the old RT method. (Which hopefully means + // that they can't force AA on us as well.) + if (retTex->mProfile->isRenderTarget() && mslevel != 0 && (mDeviceCaps.Caps2 && D3DDEVCAPS2_CAN_STRETCHRECT_FROM_TEXTURES)) + { + D3D9Assert(mD3DDevice->CreateRenderTarget(width, height, d3dTextureFormat, + mstype, mslevel, false, retTex->getSurfacePtr(), NULL), + "GFXD3D9TextureManager::_createTexture - unable to create render target"); + } + + // All done! + retTex->mMipLevels = retTex->get2DTex()->GetLevelCount(); + } +#endif + + // Get the actual size of the texture... + D3DSURFACE_DESC probeDesc; + ZeroMemory(&probeDesc, sizeof probeDesc); + + if( retTex->get2DTex() != NULL ) + D3D9Assert( retTex->get2DTex()->GetLevelDesc( 0, &probeDesc ), "Failed to get surface description"); + else if( retTex->getSurface() != NULL ) + D3D9Assert( retTex->getSurface()->GetDesc( &probeDesc ), "Failed to get surface description"); + + retTex->mTextureSize.set(probeDesc.Width, probeDesc.Height, 0); + + int fmt = probeDesc.Format; + +#if !defined(TORQUE_OS_XENON) + GFXREVERSE_LOOKUP( GFXD3D9TextureFormat, GFXFormat, fmt ); + retTex->mFormat = (GFXFormat)fmt; +#else + retTex->mFormat = format; +#endif + } +} + +//----------------------------------------------------------------------------- +// createTexture +//----------------------------------------------------------------------------- +GFXTextureObject *GFXD3D9TextureManager::_createTextureObject( U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips, + S32 antialiasLevel, + GFXTextureObject *inTex ) +{ + GFXD3D9TextureObject *retTex; + if ( inTex ) + { + AssertFatal( dynamic_cast( inTex ), "GFXD3D9TextureManager::_createTexture() - Bad inTex type!" ); + retTex = static_cast( inTex ); + retTex->release(); + } + else + { + retTex = new GFXD3D9TextureObject( GFX, profile ); + retTex->registerResourceWithDevice( GFX ); + } + + _innerCreateTexture(retTex, height, width, depth, format, profile, numMipLevels, forceMips, antialiasLevel); + + return retTex; +} + +//----------------------------------------------------------------------------- +// loadTexture - GBitmap +//----------------------------------------------------------------------------- +bool GFXD3D9TextureManager::_loadTexture(GFXTextureObject *aTexture, GBitmap *pDL) +{ + GFXD3D9TextureObject *texture = static_cast(aTexture); + +#ifdef TORQUE_OS_XENON + // If the texture is currently bound, it needs to be unbound before modifying it + if( texture->getTex() && texture->getTex()->IsSet( mD3DDevice ) ) + { + mD3DDevice->SetTexture( 0, NULL ); + mD3DDevice->SetTexture( 1, NULL ); + mD3DDevice->SetTexture( 2, NULL ); + mD3DDevice->SetTexture( 3, NULL ); + mD3DDevice->SetTexture( 4, NULL ); + mD3DDevice->SetTexture( 5, NULL ); + mD3DDevice->SetTexture( 6, NULL ); + mD3DDevice->SetTexture( 7, NULL ); + } +#endif + + // Check with profiler to see if we can do automatic mipmap generation. + const bool supportsAutoMips = GFX->getCardProfiler()->queryProfile("autoMipMapLevel", true); + + // Helper bool + const bool isCompressedTexFmt = aTexture->mFormat >= GFXFormatDXT1 && aTexture->mFormat <= GFXFormatDXT5; + + // Settings for mipmap generation + U32 maxDownloadMip = pDL->getNumMipLevels(); + U32 nbMipMapLevel = pDL->getNumMipLevels(); + + if( supportsAutoMips && !isCompressedTexFmt ) + { + maxDownloadMip = 1; + nbMipMapLevel = aTexture->mMipLevels; + } + + // Fill the texture... + for( int i = 0; i < maxDownloadMip; i++ ) + { + LPDIRECT3DSURFACE9 surf = NULL; + D3D9Assert(texture->get2DTex()->GetSurfaceLevel( i, &surf ), "Failed to get surface"); + + D3DLOCKED_RECT lockedRect; + +#ifdef TORQUE_OS_XENON + // On the 360, doing a LockRect doesn't work like it does with untiled memory + // so instead swizzle into some temporary memory, and then later use D3DX + // to do the upload properly. + FrameTemp swizzleMem(pDL->getWidth(i) * pDL->getHeight(i) * pDL->getBytesPerPixel()); + lockedRect.pBits = (void*)~swizzleMem; +#else + surf->LockRect( &lockedRect, NULL, 0 ); +#endif + + PROFILE_START(SWIZZLE_UPLOAD); + switch( texture->mFormat ) + { + case GFXFormatR8G8B8: + AssertFatal( pDL->getFormat() == GFXFormatR8G8B8, "Assumption failed" ); + GFX->getDeviceSwizzle24()->ToBuffer( lockedRect.pBits, pDL->getBits(i), + pDL->getWidth(i) * pDL->getHeight(i) * pDL->getBytesPerPixel() ); + break; + + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + { + PROFILE_START(Swizzle32_Upload); + GFX->getDeviceSwizzle32()->ToBuffer( lockedRect.pBits, pDL->getBits(i), + pDL->getWidth(i) * pDL->getHeight(i) * pDL->getBytesPerPixel() ); + PROFILE_END(); + } + break; + + default: + // Just copy the bits in no swizzle or padding + AssertFatal( pDL->getFormat() == texture->mFormat, "Format mismatch" ); + dMemcpy( lockedRect.pBits, pDL->getBits(i), + pDL->getWidth(i) * pDL->getHeight(i) * pDL->getBytesPerPixel() ); + } + PROFILE_END(); + +#ifdef TORQUE_OS_XENON + RECT srcRect; + srcRect.bottom = pDL->getHeight(i); + srcRect.top = 0; + srcRect.left = 0; + srcRect.right = pDL->getWidth(i); + + D3DXLoadSurfaceFromMemory(surf, NULL, NULL, ~swizzleMem, (D3DFORMAT)MAKELINFMT(GFXD3D9TextureFormat[pDL->getFormat()]), + pDL->getWidth(i) * pDL->getBytesPerPixel(), NULL, &srcRect, false, 0, 0, D3DX_FILTER_NONE, 0); +#else + surf->UnlockRect(); +#endif + + surf->Release(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// loadTexture - raw +//----------------------------------------------------------------------------- +bool GFXD3D9TextureManager::_loadTexture( GFXTextureObject *inTex, void *raw ) +{ + GFXD3D9TextureObject *texture = (GFXD3D9TextureObject *) inTex; + + // currently only for volume textures... + if( texture->getDepth() < 1 ) return false; + + + U32 bytesPerPix = 1; + + switch( texture->mFormat ) + { + case GFXFormatR8G8B8: + bytesPerPix = 3; + break; + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + bytesPerPix = 4; + break; + } + + U32 rowPitch = texture->getWidth() * bytesPerPix; + U32 slicePitch = texture->getWidth() * texture->getHeight() * bytesPerPix; + + D3DBOX box; + box.Left = 0; + box.Right = texture->getWidth(); + box.Front = 0; + box.Back = texture->getDepth(); + box.Top = 0; + box.Bottom = texture->getHeight(); + + + LPDIRECT3DVOLUME9 volume = NULL; + D3D9Assert( texture->get3DTex()->GetVolumeLevel( 0, &volume ), "Failed to load volume" ); + +#ifdef TORQUE_OS_XENON + D3DLOCKED_BOX lockedBox; + volume->LockBox( &lockedBox, &box, 0 ); + + dMemcpy( lockedBox.pBits, raw, slicePitch * texture->getDepth() ); + + volume->UnlockBox(); +#else + D3D9Assert( + GFXD3DX.D3DXLoadVolumeFromMemory( + volume, + NULL, + NULL, + raw, + GFXD3D9TextureFormat[texture->mFormat], + rowPitch, + slicePitch, + NULL, + &box, +#ifdef TORQUE_OS_XENON + false, 0, 0, 0, // Unique to Xenon -pw +#endif + D3DX_FILTER_NONE, + 0 + ), + "Failed to load volume texture" + ); +#endif + + volume->Release(); + + + return true; +} + +//----------------------------------------------------------------------------- +// refreshTexture +//----------------------------------------------------------------------------- +bool GFXD3D9TextureManager::_refreshTexture(GFXTextureObject *texture) +{ + U32 usedStrategies = 0; + GFXD3D9TextureObject *realTex = static_cast( texture ); + + if(texture->mProfile->doStoreBitmap()) + { +// SAFE_RELEASE(realTex->mD3DTexture); +// _innerCreateTexture(realTex, texture->mTextureSize.x, texture->mTextureSize.y, texture->mFormat, texture->mProfile, texture->mMipLevels); + + if(texture->mBitmap) + _loadTexture(texture, texture->mBitmap); + + if(texture->mDDS) + _loadTexture(texture, texture->mDDS); + + usedStrategies++; + } + + if(texture->mProfile->isRenderTarget() || texture->mProfile->isDynamic() || + texture->mProfile->isZTarget()) + { + realTex->release(); + _innerCreateTexture(realTex, texture->getHeight(), texture->getWidth(), texture->getDepth(), texture->mFormat, + + texture->mProfile, texture->mMipLevels, false, texture->mAntialiasLevel); + usedStrategies++; + } + + AssertFatal(usedStrategies < 2, "GFXD3D9TextureManager::_refreshTexture - Inconsistent profile flags!"); + + return true; +} + + +//----------------------------------------------------------------------------- +// freeTexture +//----------------------------------------------------------------------------- +bool GFXD3D9TextureManager::_freeTexture(GFXTextureObject *texture, bool zombify) +{ + AssertFatal(dynamic_cast(texture),"Not an actual d3d texture object!"); + GFXD3D9TextureObject *tex = static_cast( texture ); + + // If it's a managed texture and we're zombifying, don't blast it, D3D allows + // us to keep it. + if(zombify && tex->isManaged) + return true; + + tex->release(); + + return true; +} + +/// Load a texture from a proper DDSFile instance. +bool GFXD3D9TextureManager::_loadTexture(GFXTextureObject *aTexture, DDSFile *dds) +{ + PROFILE_SCOPE(GFXD3DTexMan_loadTexture); + + GFXD3D9TextureObject *texture = static_cast(aTexture); + + // Fill the texture... + for( int i = 0; i < aTexture->mMipLevels; i++ ) + { + PROFILE_SCOPE(GFXD3DTexMan_loadSurface); + + LPDIRECT3DSURFACE9 surf = NULL; + D3D9Assert(texture->get2DTex()->GetSurfaceLevel( i, &surf ), "Failed to get surface"); + +#if defined(TORQUE_OS_XENON) + XGTEXTURE_DESC surfDesc; + dMemset(&surfDesc, 0, sizeof(XGTEXTURE_DESC)); + XGGetSurfaceDesc(surf, &surfDesc); + + RECT srcRect; + srcRect.top = srcRect.left = 0; + srcRect.bottom = dds->getHeight(i); + srcRect.right = dds->getWidth(i); + + D3DXLoadSurfaceFromMemory(surf, NULL, NULL, dds->mSurfaces[0]->mMips[i], + (D3DFORMAT)MAKELINFMT(GFXD3D9TextureFormat[dds->mFormat]), dds->getPitch(i), + NULL, &srcRect, false, 0, 0, D3DX_FILTER_NONE, 0); +#else + + D3DLOCKED_RECT lockedRect; + D3D9Assert( surf->LockRect( &lockedRect, NULL, 0 ), "Failed to lock surface level for load" ); + + if( dds->getPitch( i ) != lockedRect.Pitch ) + { + Con::errorf( "GFXD3D9TextureManager - pitch mismatch on DDS load" ); + + surf->UnlockRect(); + surf->Release(); + + return false; + } + + AssertFatal( dds->mSurfaces.size() > 0, "Assumption failed. DDSFile has no surfaces." ); + + dMemcpy( lockedRect.pBits, dds->mSurfaces[0]->mMips[i], dds->getSurfaceSize(i) ); + + surf->UnlockRect(); +#endif + + surf->Release(); + } + + return true; +} \ No newline at end of file diff --git a/gfx/D3D9/gfxD3D9TextureManager.h b/gfx/D3D9/gfxD3D9TextureManager.h new file mode 100644 index 0000000..dad6e58 --- /dev/null +++ b/gfx/D3D9/gfxD3D9TextureManager.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GFXD3DTEXTUREMANAGER_H_ +#define _GFXD3DTEXTUREMANAGER_H_ + +#include "gfx/D3D9/gfxD3D9TextureObject.h" +#include "core/util/safeRelease.h" + +// #define D3D_TEXTURE_SPEW + + +//***************************************************************************** +// GFX D3D Texture Manager +//***************************************************************************** +class GFXD3D9TextureManager : public GFXTextureManager +{ + friend class GFXD3D9TextureObject; + +public: + GFXD3D9TextureManager( LPDIRECT3DDEVICE9 d3ddevice ); + virtual ~GFXD3D9TextureManager(); + +protected: + + // GFXTextureManager + GFXTextureObject *_createTextureObject( U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips = false, + S32 antialiasLevel = 0, + GFXTextureObject *inTex = NULL ); + bool _loadTexture(GFXTextureObject *texture, DDSFile *dds); + bool _loadTexture(GFXTextureObject *texture, GBitmap *bmp); + bool _loadTexture(GFXTextureObject *texture, void *raw); + bool _refreshTexture(GFXTextureObject *texture); + bool _freeTexture(GFXTextureObject *texture, bool zombify = false); + +private: + U32 mCurTexSet[TEXTURE_STAGE_COUNT]; + + LPDIRECT3DDEVICE9 mD3DDevice; + D3DCAPS9 mDeviceCaps; + + void _innerCreateTexture(GFXD3D9TextureObject *obj, U32 height, U32 width, + U32 depth, GFXFormat format, GFXTextureProfile *profile, U32 numMipLevels, + bool forceMips = false, S32 antialiasLevel = 0); +}; + +#endif diff --git a/gfx/D3D9/gfxD3D9TextureObject.cpp b/gfx/D3D9/gfxD3D9TextureObject.cpp new file mode 100644 index 0000000..eb04a0a --- /dev/null +++ b/gfx/D3D9/gfxD3D9TextureObject.cpp @@ -0,0 +1,260 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/D3D9/gfxD3D9TextureObject.h" +#include "platform/profiler.h" + +#ifdef TORQUE_OS_XENON +#include "gfx/D3D9/360/gfx360Device.h" +#include "gfx/D3D9/360/gfx360Target.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#endif + +U32 GFXD3D9TextureObject::mTexCount = 0; + +//***************************************************************************** +// GFX D3D Texture Object +//***************************************************************************** +GFXD3D9TextureObject::GFXD3D9TextureObject( GFXDevice * d, GFXTextureProfile *profile) + : GFXTextureObject( d, profile ) +{ +#ifdef D3D_TEXTURE_SPEW + mTexCount++; + Con::printf("+ texMake %d %x", mTexCount, this); +#endif + + isManaged = false; + mD3DTexture = NULL; + mLocked = false; + + mD3DSurface = NULL; +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +GFXD3D9TextureObject::~GFXD3D9TextureObject() +{ + kill(); +#ifdef D3D_TEXTURE_SPEW + mTexCount--; + Con::printf("+ texkill %d %x", mTexCount, this); +#endif +} + +//----------------------------------------------------------------------------- +// lock +//----------------------------------------------------------------------------- +GFXLockedRect *GFXD3D9TextureObject::lock(U32 mipLevel /*= 0*/, RectI *inRect /*= NULL*/) +{ + AssertFatal( !mLocked, "GFXD3D9TextureObject::lock - The texture is already locked!" ); + + if( mProfile->isRenderTarget() ) + { + if( !mLockTex || + mLockTex->getWidth() != getWidth() || + mLockTex->getHeight() != getHeight() ) + { + mLockTex.set( getWidth(), getHeight(), mFormat, &GFXSystemMemProfile, avar("%s() - mLockTex (line %d)", __FUNCTION__, __LINE__) ); + } + + PROFILE_START(GFXD3D9TextureObject_lockRT); + + IDirect3DSurface9 *source; + D3D9Assert( get2DTex()->GetSurfaceLevel( 0, &source ), "GFXD3D9TextureObject::lock - failed to get our own texture's surface." ); + + IDirect3DSurface9 *dest; + GFXD3D9TextureObject *to = (GFXD3D9TextureObject *) &(*mLockTex); + D3D9Assert( to->get2DTex()->GetSurfaceLevel( 0, &dest ), "GFXD3D9TextureObject::lock - failed to get dest texture's surface." ); + +#ifndef TORQUE_OS_XENON + LPDIRECT3DDEVICE9 D3DDevice = dynamic_cast(GFX)->getDevice(); + HRESULT rtLockRes = D3DDevice->GetRenderTargetData( source, dest ); +#else + AssertFatal(false, "Use different functionality on the Xbox 360 to perform this task."); + HRESULT rtLockRes = E_FAIL; +#endif + source->Release(); + + if(!SUCCEEDED(rtLockRes)) + { + // This case generally occurs if the device is lost. The lock failed + // so clean up and return NULL. + dest->Release(); + PROFILE_END(); + return NULL; + } + + D3D9Assert( dest->LockRect( &mLockRect, NULL, D3DLOCK_READONLY ), NULL ); + dest->Release(); + mLocked = true; + + PROFILE_END(); + } + else + { + RECT r; + + if(inRect) + { + r.top = inRect->point.y; + r.left = inRect->point.x; + r.bottom = inRect->point.y + inRect->extent.y; + r.right = inRect->point.x + inRect->extent.x; + } + + D3D9Assert( get2DTex()->LockRect(mipLevel, &mLockRect, inRect ? &r : NULL, 0), + "GFXD3D9TextureObject::lock - could not lock non-RT texture!" ); + mLocked = true; + + } + + // GFXLockedRect is set up to correspond to D3DLOCKED_RECT, so this is ok. + return (GFXLockedRect*)&mLockRect; +} + +//----------------------------------------------------------------------------- +// unLock +//----------------------------------------------------------------------------- +void GFXD3D9TextureObject::unlock(U32 mipLevel) +{ + AssertFatal( mLocked, "GFXD3D9TextureObject::unlock - Attempting to unlock a surface that has not been locked" ); + +#ifndef TORQUE_OS_XENON + if( mProfile->isRenderTarget() ) + { + IDirect3DSurface9 *dest; + GFXD3D9TextureObject *to = (GFXD3D9TextureObject *) &(*mLockTex); + D3D9Assert( to->get2DTex()->GetSurfaceLevel( 0, &dest ), NULL ); + + dest->UnlockRect(); + dest->Release(); + + mLocked = false; + } + else +#endif + { + D3D9Assert( get2DTex()->UnlockRect(mipLevel), + "GFXD3D9TextureObject::unlock - could not unlock non-RT texture." ); + + mLocked = false; + } +} + +//------------------------------------------------------------------------------ + +void GFXD3D9TextureObject::release() +{ + static_cast( GFX )->destroyD3DResource( mD3DTexture ); + static_cast( GFX )->destroyD3DResource( mD3DSurface ); + mD3DTexture = NULL; + mD3DSurface = NULL; +} + +void GFXD3D9TextureObject::zombify() +{ + // Managed textures are managed by D3D + AssertFatal(!mLocked, "GFXD3D9TextureObject::zombify - Cannot zombify a locked texture!"); + if(isManaged) + return; + + release(); +} + +void GFXD3D9TextureObject::resurrect() +{ + // Managed textures are managed by D3D + if(isManaged) + return; + + static_cast(TEXMGR)->refreshTexture(this); +} + +//------------------------------------------------------------------------------ + +bool GFXD3D9TextureObject::copyToBmp(GBitmap* bmp) +{ +#ifdef TORQUE_OS_XENON + // TODO: Implement Xenon version -patw + return false; +#else + if (!bmp) + return false; + + // check format limitations + // at the moment we only support RGBA for the source (other 4 byte formats should + // be easy to add though) + AssertFatal(mFormat == GFXFormatR8G8B8A8, "copyToBmp: invalid format"); + if (mFormat != GFXFormatR8G8B8A8) + return false; + + PROFILE_START(GFXD3D9TextureObject_copyToBmp); + + AssertFatal(bmp->getWidth() == getWidth(), "doh"); + AssertFatal(bmp->getHeight() == getHeight(), "doh"); + U32 width = getWidth(); + U32 height = getHeight(); + + bmp->setHasTransparency(mHasTransparency); + + // set some constants + const U32 sourceBytesPerPixel = 4; + U32 destBytesPerPixel = 0; + if (bmp->getFormat() == GFXFormatR8G8B8A8) + destBytesPerPixel = 4; + else if (bmp->getFormat() == GFXFormatR8G8B8) + destBytesPerPixel = 3; + else + // unsupported + AssertFatal(false, "unsupported bitmap format"); + + // lock the texture + D3DLOCKED_RECT* lockRect = (D3DLOCKED_RECT*) lock(); + + // set pointers + U8* srcPtr = (U8*)lockRect->pBits; + U8* destPtr = bmp->getWritableBits(); + + // we will want to skip over any D3D cache data in the source texture + const S32 sourceCacheSize = lockRect->Pitch - width * sourceBytesPerPixel; + AssertFatal(sourceCacheSize >= 0, "copyToBmp: cache size is less than zero?"); + + PROFILE_START(GFXD3D9TextureObject_copyToBmp_pixCopy); + // copy data into bitmap + for (int row = 0; row < height; ++row) + { + for (int col = 0; col < width; ++col) + { + destPtr[0] = srcPtr[2]; // red + destPtr[1] = srcPtr[1]; // green + destPtr[2] = srcPtr[0]; // blue + if (destBytesPerPixel == 4) + destPtr[3] = srcPtr[3]; // alpha + + // go to next pixel in src + srcPtr += sourceBytesPerPixel; + + // go to next pixel in dest + destPtr += destBytesPerPixel; + } + // skip past the cache data for this row (if any) + srcPtr += sourceCacheSize; + } + PROFILE_END(); + + // assert if we stomped or underran memory + AssertFatal(U32(destPtr - bmp->getWritableBits()) == width * height * destBytesPerPixel, "copyToBmp: doh, memory error"); + AssertFatal(U32(srcPtr - (U8*)lockRect->pBits) == height * lockRect->Pitch, "copyToBmp: doh, memory error"); + + // unlock + unlock(); + + PROFILE_END(); + + return true; +#endif +} diff --git a/gfx/D3D9/gfxD3D9TextureObject.h b/gfx/D3D9/gfxD3D9TextureObject.h new file mode 100644 index 0000000..848209a --- /dev/null +++ b/gfx/D3D9/gfxD3D9TextureObject.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- +#ifndef _GFXD3D9TEXTUREOBJECT_H_ +#define _GFXD3D9TEXTUREOBJECT_H_ + +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif + +#ifndef _GFXTEXTUREMANAGER_H_ +#include "gfx/gfxTextureManager.h" +#endif + + +class GFXD3D9TextureObject : public GFXTextureObject +{ +protected: + static U32 mTexCount; + GFXTexHandle mLockTex; + D3DLOCKED_RECT mLockRect; + bool mLocked; + + IDirect3DBaseTexture9 *mD3DTexture; + + // used for z buffers... + IDirect3DSurface9 *mD3DSurface; + +public: + + GFXD3D9TextureObject( GFXDevice * d, GFXTextureProfile *profile); + ~GFXD3D9TextureObject(); + + IDirect3DBaseTexture9 * getTex(){ return mD3DTexture; } + IDirect3DTexture9 * get2DTex(){ return (LPDIRECT3DTEXTURE9) mD3DTexture; } + IDirect3DTexture9 ** get2DTexPtr(){ return (LPDIRECT3DTEXTURE9*) &mD3DTexture; } + IDirect3DVolumeTexture9 * get3DTex(){ return (LPDIRECT3DVOLUMETEXTURE9) mD3DTexture; } + IDirect3DVolumeTexture9 ** get3DTexPtr(){ return (LPDIRECT3DVOLUMETEXTURE9*) &mD3DTexture; } + + void release(); + + bool isManaged; + + virtual GFXLockedRect * lock(U32 mipLevel = 0, RectI *inRect = NULL); + virtual void unlock(U32 mipLevel = 0 ); + + virtual bool copyToBmp(GBitmap* bmp); + IDirect3DSurface9 *getSurface() {return mD3DSurface;} + IDirect3DSurface9 **getSurfacePtr() {return &mD3DSurface;} + + // GFXResource + void zombify(); + void resurrect(); + +#ifdef TORQUE_DEBUG + virtual void pureVirtualCrash() {}; +#endif +}; + + +#endif diff --git a/gfx/D3D9/gfxD3D9VertexBuffer.cpp b/gfx/D3D9/gfxD3D9VertexBuffer.cpp new file mode 100644 index 0000000..a9ea590 --- /dev/null +++ b/gfx/D3D9/gfxD3D9VertexBuffer.cpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/D3D9/platformD3D.h" +#include "platform/platform.h" +#include "gfx/D3D9/gfxD3D9VertexBuffer.h" + +GFXD3D9VertexBuffer::~GFXD3D9VertexBuffer() +{ +#ifdef TORQUE_DEBUG + SAFE_DELETE( name ); +#endif + + if (getOwningDevice() != NULL) + { + if (mBufferType == GFXBufferTypeDynamic) + static_cast(getOwningDevice())->deallocVertexBuffer( this ); + else if (mBufferType != GFXBufferTypeVolatile) + { + static_cast(getOwningDevice())->destroyD3DResource( vb ); + } + } +} + +//----------------------------------------------------------------------------- +// Lock +//----------------------------------------------------------------------------- +void GFXD3D9VertexBuffer::lock(U32 vertexStart, U32 vertexEnd, void **vertexPtr) +{ + AssertFatal(lockedVertexStart == 0 && lockedVertexEnd == 0, "Cannot lock a buffer more than once!"); + + U32 flags = 0; + + GFXD3D9Device *d = static_cast( mDevice ); + + switch( mBufferType ) + { + case GFXBufferTypeStatic: + break; + + case GFXBufferTypeDynamic: +#ifndef TORQUE_OS_XENON + flags |= D3DLOCK_DISCARD; +#endif + break; + + case GFXBufferTypeVolatile: + + // Get or create the volatile buffer... + mVolatileBuffer = d->findVBPool( mVertexFormat, vertexEnd ); + + if( !mVolatileBuffer ) + mVolatileBuffer = d->createVBPool( mVertexFormat, mVertexSize ); + + vb = mVolatileBuffer->vb; + decl = mVolatileBuffer->decl; + + // Get our range now... + AssertFatal(vertexStart == 0, "Cannot get a subrange on a volatile buffer."); + AssertFatal(vertexEnd <= MAX_DYNAMIC_VERTS, "Cannot get more than MAX_DYNAMIC_VERTS in a volatile buffer. Up the constant!"); + AssertFatal(mVolatileBuffer->lockedVertexStart == 0 && mVolatileBuffer->lockedVertexEnd == 0, "Got more than one lock on the volatile pool."); + + // We created the pool when we requested this volatile buffer, so assume it exists... + if( mVolatileBuffer->mNumVerts + vertexEnd > MAX_DYNAMIC_VERTS ) + { +#ifdef TORQUE_OS_XENON + AssertFatal( false, "This should never, ever happen. findVBPool should have returned NULL" ); +#else + flags |= D3DLOCK_DISCARD; +#endif + mVolatileStart = vertexStart = 0; + vertexEnd = vertexEnd; + } + else + { + flags |= D3DLOCK_NOOVERWRITE; + mVolatileStart = vertexStart = mVolatileBuffer->mNumVerts; + vertexEnd += mVolatileBuffer->mNumVerts; + } + + mVolatileBuffer->mNumVerts = vertexEnd+1; + + mVolatileBuffer->lockedVertexStart = vertexStart; + mVolatileBuffer->lockedVertexEnd = vertexEnd; + break; + } + + lockedVertexStart = vertexStart; + lockedVertexEnd = vertexEnd; + + // Con::printf("%x: Locking %s range (%d, %d)", this, (mBufferType == GFXBufferTypeVolatile ? "volatile" : "static"), lockedVertexStart, lockedVertexEnd); + +#ifdef TORQUE_OS_XENON + // If the vertex buffer which we are trying to lock is held by the D3D device + // on Xenon it will bomb. So if that is the case, then SetStreamSource to NULL + // and also call setVertexBuffer because otherwise the state-cache will be hosed + if( d->mCurrentVB != NULL && d->mCurrentVB->vb == vb ) + { + d->setVertexBuffer( NULL ); + d->mD3DDevice->SetStreamSource( 0, NULL, 0, 0 ); + } + + // As of October 2006 XDK, range locking is no longer supported. Lock the whole buffer + // and then manually offset the pointer to simulate the subrange. -patw + D3D9Assert( vb->Lock( 0, 0, vertexPtr, flags), + "Unable to lock vertex buffer."); + + U8 *tmp = (U8 *)(*vertexPtr); + tmp += ( vertexStart * mVertexSize ); + *vertexPtr = tmp; +#else + D3D9Assert( vb->Lock(vertexStart * mVertexSize, (vertexEnd - vertexStart) * mVertexSize, vertexPtr, flags), + "Unable to lock vertex buffer."); +#endif +} + +//----------------------------------------------------------------------------- +// Unlock +//----------------------------------------------------------------------------- +void GFXD3D9VertexBuffer::unlock() +{ + D3D9Assert( vb->Unlock(), + "Unable to unlock vertex buffer."); + mIsFirstLock = false; + + // Con::printf("%x: Unlocking %s range (%d, %d)", this, (mBufferType == GFXBufferTypeVolatile ? "volatile" : "static"), lockedVertexStart, lockedVertexEnd); + + lockedVertexEnd = lockedVertexStart = 0; + + if(mVolatileBuffer.isValid()) + { + mVolatileBuffer->lockedVertexStart = 0; + mVolatileBuffer->lockedVertexEnd = 0; + mVolatileBuffer = NULL; + //vb->Release(); + //vb = NULL; + } +} + +//----------------------------------------------------------------------------- +// Prepare +//----------------------------------------------------------------------------- +void GFXD3D9VertexBuffer::prepare() +{ + ((GFXD3D9Device*)mDevice)->setVB(this); +} + +void GFXD3D9VertexBuffer::zombify() +{ + AssertFatal(lockedVertexStart == 0 && lockedVertexEnd == 0, "GFXD3D9VertexBuffer::zombify - Cannot zombify a locked buffer!"); + // Static buffers are managed by D3D9 so we don't deal with them. + if(mBufferType == GFXBufferTypeDynamic) + { + SAFE_RELEASE(vb); + } +} + +void GFXD3D9VertexBuffer::resurrect() +{ + // Static buffers are managed by D3D9 so we don't deal with them. + if(mBufferType == GFXBufferTypeDynamic) + { + D3D9Assert(static_cast(mDevice)->mD3DDevice->CreateVertexBuffer( mVertexSize * mNumVerts, +#ifdef TORQUE_OS_XENON + D3DUSAGE_WRITEONLY, +#else + D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, +#endif + 0, + D3DPOOL_DEFAULT, + &vb, + NULL ), + "GFXD3D9VertexBuffer::resurrect - Failed to allocate VB" ); + } +} + diff --git a/gfx/D3D9/gfxD3D9VertexBuffer.h b/gfx/D3D9/gfxD3D9VertexBuffer.h new file mode 100644 index 0000000..c591d57 --- /dev/null +++ b/gfx/D3D9/gfxD3D9VertexBuffer.h @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXD3D_VERTEXBUFFER_H_ +#define _GFXD3D_VERTEXBUFFER_H_ + +#ifndef _GFXD3D9DEVICE_H_ +#include "gfx/D3D9/gfxD3D9Device.h" +#endif +#ifndef _SAFEDELETE_H_ +#include "core/util/safeDelete.h" +#endif + +//***************************************************************************** +// GFXD3D9VertexBuffer +//***************************************************************************** +class GFXD3D9VertexBuffer : public GFXVertexBuffer +{ +public: + IDirect3DVertexBuffer9 *vb; + IDirect3DVertexDeclaration9 *decl; + StrongRefPtr mVolatileBuffer; + + bool mIsFirstLock; + bool mClearAtFrameEnd; + + GFXD3D9VertexBuffer(); + GFXD3D9VertexBuffer( GFXDevice *device, + U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertexSize, + GFXBufferType bufferType ); + virtual ~GFXD3D9VertexBuffer(); + + void lock(U32 vertexStart, U32 vertexEnd, void **vertexPtr); + void unlock(); + void prepare(); + +#ifdef TORQUE_DEBUG + char *name; + + /// In debug compile, the verts will be chained together and the device + /// will examine the chain when it's destructor is called, this will + /// allow developers to see which vertex buffers are not destroyed + GFXD3D9VertexBuffer *next; +#endif + void setName( const char *n ); + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); +}; + +//----------------------------------------------------------------------------- +// This is for debugging vertex buffers and trying to track down which vbs +// aren't getting free'd + +inline GFXD3D9VertexBuffer::GFXD3D9VertexBuffer() +: GFXVertexBuffer(0,0,0,0,(GFXBufferType)0) +{ +#ifdef TORQUE_DEBUG + name = NULL; +#endif + vb = NULL; + decl = NULL; + mIsFirstLock = true; + lockedVertexEnd = lockedVertexStart = 0; + mClearAtFrameEnd = false; +} + +inline GFXD3D9VertexBuffer::GFXD3D9VertexBuffer( GFXDevice *device, + U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertexSize, + GFXBufferType bufferType ) + : GFXVertexBuffer( device, numVerts, vertexFormat, vertexSize, bufferType ) +{ +#ifdef TORQUE_DEBUG + name = NULL; +#endif + vb = NULL; + decl = NULL; + mIsFirstLock = true; + mClearAtFrameEnd = false; + lockedVertexEnd = lockedVertexStart = 0; +} + +#ifdef TORQUE_DEBUG + +inline void GFXD3D9VertexBuffer::setName( const char *n ) +{ + SAFE_DELETE( name ); + name = new char[dStrlen( n )]; + dStrcpy( name, n ); +} + +#else + +inline void GFXD3D9VertexBuffer::setName( const char *n ) { } + +#endif + +#endif // _GFXD3D_VERTEXBUFFER_H_ + diff --git a/gfx/D3D9/pc/gfxD3D9Device.pc.cpp b/gfx/D3D9/pc/gfxD3D9Device.pc.cpp new file mode 100644 index 0000000..e853fc1 --- /dev/null +++ b/gfx/D3D9/pc/gfxD3D9Device.pc.cpp @@ -0,0 +1,54 @@ +#include "gfx/D3D9/pc/gfxPCD3D9Device.h" +#include "gfx/D3D9/gfxD3D9CardProfiler.h" +#include "gfx/D3D9/gfxD3D9Shader.h" +#include "gfx/D3D9/gfxD3D9VertexBuffer.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#include "core/strings/unicode.h" +#include "console/console.h" + +//------------------------------------------------------------------------------ +// D3DX Function binding +//------------------------------------------------------------------------------ + +bool d3dxBindFunction( DLibrary *dll, void *&fnAddress, const char *name ) +{ + fnAddress = dll->bind( name ); + + if (!fnAddress) + Con::warnf( "D3DX Loader: DLL bind failed for %s", name ); + + return fnAddress != 0; +} + +void GFXD3D9Device::initD3DXFnTable() +{ + if ( smD3DX.isLoaded ) + return; + + // We only load the d3dx version that we compiled + // and linked against which should keep unexpected + // problems from newer or older SDKs to a minimum. + String d3dxVersion = String::ToString( "d3dx9_%d.dll", (S32)D3DX_SDK_VERSION ); + smD3DX.dllRef = OsLoadLibrary( d3dxVersion ); + + // If the d3dx version we requested didn't load then we have + // a corrupt or old install of DirectX.... prompt them to update. + if ( !smD3DX.dllRef ) + { + Con::errorf( "Unsupported DirectX version!" ); + Platform::messageBox( Con::getVariable( "$appName" ), + "DirectX could not be started!\r\n" + "Please be sure you have the latest version of DirectX installed.", + MBOk, MIStop ); + Platform::forceShutdown( -1 ); + } + + smD3DX.isLoaded = true; + + #define D3DX_FUNCTION(fn_name, fn_return, fn_args) \ + smD3DX.isLoaded &= d3dxBindFunction(smD3DX.dllRef, *(void**)&smD3DX.fn_name, #fn_name); + # include "gfx/D3D9/d3dx9Functions.h" + #undef D3DX_FUNCTION + + AssertISV( smD3DX.isLoaded, "D3DX Failed to load all functions." ); +} diff --git a/gfx/D3D9/pc/gfxD3D9EnumTranslate.pc.cpp b/gfx/D3D9/pc/gfxD3D9EnumTranslate.pc.cpp new file mode 100644 index 0000000..b0cd1b3 --- /dev/null +++ b/gfx/D3D9/pc/gfxD3D9EnumTranslate.pc.cpp @@ -0,0 +1,356 @@ +//------------------------------------------------------------------------------ +// Torque Shader Engine +// Copyright (c) GarageGames.Com +//------------------------------------------------------------------------------ + +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#include "console/console.h" + +//------------------------------------------------------------------------------ + +_D3DFORMAT GFXD3D9IndexFormat[GFXIndexFormat_COUNT]; +_D3DSAMPLERSTATETYPE GFXD3D9SamplerState[GFXSAMP_COUNT]; +_D3DFORMAT GFXD3D9TextureFormat[GFXFormat_COUNT]; +_D3DRENDERSTATETYPE GFXD3D9RenderState[GFXRenderState_COUNT]; +_D3DTEXTUREFILTERTYPE GFXD3D9TextureFilter[GFXTextureFilter_COUNT]; +_D3DBLEND GFXD3D9Blend[GFXBlend_COUNT]; +_D3DBLENDOP GFXD3D9BlendOp[GFXBlendOp_COUNT]; +_D3DSTENCILOP GFXD3D9StencilOp[GFXStencilOp_COUNT]; +_D3DCMPFUNC GFXD3D9CmpFunc[GFXCmp_COUNT]; +_D3DCULL GFXD3D9CullMode[GFXCull_COUNT]; +_D3DFILLMODE GFXD3D9FillMode[GFXFill_COUNT]; +_D3DPRIMITIVETYPE GFXD3D9PrimType[GFXPT_COUNT]; +_D3DTEXTURESTAGESTATETYPE GFXD3D9TextureStageState[GFXTSS_COUNT]; +_D3DTEXTUREADDRESS GFXD3D9TextureAddress[GFXAddress_COUNT]; +_D3DTEXTUREOP GFXD3D9TextureOp[GFXTOP_COUNT]; +_D3DDECLTYPE GFXD3D9DeclType[GFXDeclType_COUNT]; + +//------------------------------------------------------------------------------ + +#define INIT_LOOKUPTABLE( tablearray, enumprefix, type ) \ + for( int i = enumprefix##_FIRST; i < enumprefix##_COUNT; i++ ) \ + tablearray##[i] = (##type##)GFX_UNINIT_VAL; + +#define VALIDATE_LOOKUPTABLE( tablearray, enumprefix ) \ + for( int i = enumprefix##_FIRST; i < enumprefix##_COUNT; i++ ) \ + if( (int)tablearray##[i] == GFX_UNINIT_VAL ) \ + Con::warnf( "GFXD3D9EnumTranslate: Unassigned value in " #tablearray ": %i", i ); \ + else if( (int)tablearray##[i] == GFX_UNSUPPORTED_VAL ) \ + Con::warnf( "GFXD3D9EnumTranslate: Unsupported value in " #tablearray ": %i", i ); + +//------------------------------------------------------------------------------ + +void GFXD3D9EnumTranslate::init() +{ + INIT_LOOKUPTABLE( GFXD3D9IndexFormat, GFXIndexFormat, _D3DFORMAT ); + GFXD3D9IndexFormat[GFXIndexFormat16] = D3DFMT_INDEX16; + GFXD3D9IndexFormat[GFXIndexFormat32] = D3DFMT_INDEX32; + VALIDATE_LOOKUPTABLE( GFXD3D9IndexFormat, GFXIndexFormat ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9SamplerState, GFXSAMP, _D3DSAMPLERSTATETYPE ); + GFXD3D9SamplerState[GFXSAMPAddressU] = D3DSAMP_ADDRESSU; + GFXD3D9SamplerState[GFXSAMPAddressV] = D3DSAMP_ADDRESSV; + GFXD3D9SamplerState[GFXSAMPAddressW] = D3DSAMP_ADDRESSW; + GFXD3D9SamplerState[GFXSAMPBorderColor] = D3DSAMP_BORDERCOLOR; + GFXD3D9SamplerState[GFXSAMPMagFilter] = D3DSAMP_MAGFILTER; + GFXD3D9SamplerState[GFXSAMPMinFilter] = D3DSAMP_MINFILTER; + GFXD3D9SamplerState[GFXSAMPMipFilter] = D3DSAMP_MIPFILTER; + GFXD3D9SamplerState[GFXSAMPMipMapLODBias] = D3DSAMP_MIPMAPLODBIAS; + GFXD3D9SamplerState[GFXSAMPMaxMipLevel] = D3DSAMP_MAXMIPLEVEL; + GFXD3D9SamplerState[GFXSAMPMaxAnisotropy] = D3DSAMP_MAXANISOTROPY; + GFXD3D9SamplerState[GFXSAMPSRGBTexture] = D3DSAMP_SRGBTEXTURE; + GFXD3D9SamplerState[GFXSAMPElementIndex] = D3DSAMP_ELEMENTINDEX; + GFXD3D9SamplerState[GFXSAMPDMapOffset] = D3DSAMP_DMAPOFFSET; + VALIDATE_LOOKUPTABLE( GFXD3D9SamplerState, GFXSAMP ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9TextureFormat, GFXFormat, _D3DFORMAT ); + GFXD3D9TextureFormat[GFXFormatR8G8B8] = D3DFMT_R8G8B8; + GFXD3D9TextureFormat[GFXFormatR8G8B8A8] = D3DFMT_A8R8G8B8; + GFXD3D9TextureFormat[GFXFormatR8G8B8X8] = D3DFMT_X8R8G8B8; + GFXD3D9TextureFormat[GFXFormatR5G6B5] = D3DFMT_R5G6B5; + GFXD3D9TextureFormat[GFXFormatR5G5B5A1] = D3DFMT_A1R5G5B5; + GFXD3D9TextureFormat[GFXFormatR5G5B5X1] = D3DFMT_X1R5G5B5; + GFXD3D9TextureFormat[GFXFormatR32F] = D3DFMT_R32F; + GFXD3D9TextureFormat[GFXFormatA8] = D3DFMT_A8; + GFXD3D9TextureFormat[GFXFormatL8] = D3DFMT_L8; + GFXD3D9TextureFormat[GFXFormatDXT1] = D3DFMT_DXT1; + GFXD3D9TextureFormat[GFXFormatDXT2] = D3DFMT_DXT2; + GFXD3D9TextureFormat[GFXFormatDXT3] = D3DFMT_DXT3; + GFXD3D9TextureFormat[GFXFormatDXT4] = D3DFMT_DXT4; + GFXD3D9TextureFormat[GFXFormatDXT5] = D3DFMT_DXT5; + GFXD3D9TextureFormat[GFXFormatR32G32B32A32F] = D3DFMT_A32B32G32R32F; + GFXD3D9TextureFormat[GFXFormatR16G16B16A16F] = D3DFMT_A16B16G16R16F; + GFXD3D9TextureFormat[GFXFormatL16] = D3DFMT_L16; + GFXD3D9TextureFormat[GFXFormatR16G16B16A16] = D3DFMT_A16B16G16R16; + GFXD3D9TextureFormat[GFXFormatR16G16] = D3DFMT_G16R16; + GFXD3D9TextureFormat[GFXFormatR16F] = D3DFMT_R16F; + GFXD3D9TextureFormat[GFXFormatR16G16F] = D3DFMT_G16R16F; + GFXD3D9TextureFormat[GFXFormatR10G10B10A2] = D3DFMT_A2R10G10B10; + GFXD3D9TextureFormat[GFXFormatD32] = D3DFMT_D32; + GFXD3D9TextureFormat[GFXFormatD24X8] = D3DFMT_D24X8; + GFXD3D9TextureFormat[GFXFormatD24S8] = D3DFMT_D24S8; + GFXD3D9TextureFormat[GFXFormatD24FS8] = D3DFMT_D24FS8; + GFXD3D9TextureFormat[GFXFormatD16] = D3DFMT_D16; + VALIDATE_LOOKUPTABLE( GFXD3D9TextureFormat, GFXFormat); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9RenderState, GFXRenderState, _D3DRENDERSTATETYPE ); + GFXD3D9RenderState[GFXRSZEnable] = D3DRS_ZENABLE; + GFXD3D9RenderState[GFXRSFillMode] = D3DRS_FILLMODE; + GFXD3D9RenderState[GFXRSZWriteEnable] = D3DRS_ZWRITEENABLE; + GFXD3D9RenderState[GFXRSAlphaTestEnable] = D3DRS_ALPHATESTENABLE; + GFXD3D9RenderState[GFXRSSrcBlend] = D3DRS_SRCBLEND; + GFXD3D9RenderState[GFXRSDestBlend] = D3DRS_DESTBLEND; + GFXD3D9RenderState[GFXRSCullMode] = D3DRS_CULLMODE; + GFXD3D9RenderState[GFXRSZFunc] = D3DRS_ZFUNC; + GFXD3D9RenderState[GFXRSAlphaRef] = D3DRS_ALPHAREF; + GFXD3D9RenderState[GFXRSAlphaFunc] = D3DRS_ALPHAFUNC; + GFXD3D9RenderState[GFXRSAlphaBlendEnable] = D3DRS_ALPHABLENDENABLE; + GFXD3D9RenderState[GFXRSStencilEnable] = D3DRS_STENCILENABLE; + GFXD3D9RenderState[GFXRSStencilFail] = D3DRS_STENCILFAIL; + GFXD3D9RenderState[GFXRSStencilZFail] = D3DRS_STENCILZFAIL; + GFXD3D9RenderState[GFXRSStencilPass] = D3DRS_STENCILPASS; + GFXD3D9RenderState[GFXRSStencilFunc] = D3DRS_STENCILFUNC; + GFXD3D9RenderState[GFXRSStencilRef] = D3DRS_STENCILREF; + GFXD3D9RenderState[GFXRSStencilMask] = D3DRS_STENCILMASK; + GFXD3D9RenderState[GFXRSStencilWriteMask] = D3DRS_STENCILWRITEMASK; + GFXD3D9RenderState[GFXRSWrap0] = D3DRS_WRAP0; + GFXD3D9RenderState[GFXRSWrap1] = D3DRS_WRAP1; + GFXD3D9RenderState[GFXRSWrap2] = D3DRS_WRAP2; + GFXD3D9RenderState[GFXRSWrap3] = D3DRS_WRAP3; + GFXD3D9RenderState[GFXRSWrap4] = D3DRS_WRAP4; + GFXD3D9RenderState[GFXRSWrap5] = D3DRS_WRAP5; + GFXD3D9RenderState[GFXRSWrap6] = D3DRS_WRAP6; + GFXD3D9RenderState[GFXRSWrap7] = D3DRS_WRAP7; + GFXD3D9RenderState[GFXRSClipPlaneEnable] = D3DRS_CLIPPLANEENABLE; + GFXD3D9RenderState[GFXRSPointSize] = D3DRS_POINTSIZE; + GFXD3D9RenderState[GFXRSPointSizeMin] = D3DRS_POINTSIZE_MIN; + GFXD3D9RenderState[GFXRSPointSize_Max] = D3DRS_POINTSIZE_MAX; + GFXD3D9RenderState[GFXRSPointSpriteEnable] = D3DRS_POINTSPRITEENABLE; + GFXD3D9RenderState[GFXRSMultiSampleantiAlias] = D3DRS_MULTISAMPLEANTIALIAS; + GFXD3D9RenderState[GFXRSMultiSampleMask] = D3DRS_MULTISAMPLEMASK; + GFXD3D9RenderState[GFXRSShadeMode] = D3DRS_SHADEMODE; + GFXD3D9RenderState[GFXRSLastPixel] = D3DRS_LASTPIXEL; + GFXD3D9RenderState[GFXRSClipping] = D3DRS_CLIPPING; + GFXD3D9RenderState[GFXRSPointScaleEnable] = D3DRS_POINTSCALEENABLE; + GFXD3D9RenderState[GFXRSPointScale_A] = D3DRS_POINTSCALE_A; + GFXD3D9RenderState[GFXRSPointScale_B] = D3DRS_POINTSCALE_B; + GFXD3D9RenderState[GFXRSPointScale_C] = D3DRS_POINTSCALE_C; + GFXD3D9RenderState[GFXRSLighting] = D3DRS_LIGHTING; + GFXD3D9RenderState[GFXRSAmbient] = D3DRS_AMBIENT; + GFXD3D9RenderState[GFXRSFogVertexMode] = D3DRS_FOGVERTEXMODE; + GFXD3D9RenderState[GFXRSColorVertex] = D3DRS_COLORVERTEX; + GFXD3D9RenderState[GFXRSLocalViewer] = D3DRS_LOCALVIEWER; + GFXD3D9RenderState[GFXRSNormalizeNormals] = D3DRS_NORMALIZENORMALS; + GFXD3D9RenderState[GFXRSDiffuseMaterialSource] = D3DRS_DIFFUSEMATERIALSOURCE; + GFXD3D9RenderState[GFXRSSpecularMaterialSource] = D3DRS_SPECULARMATERIALSOURCE; + GFXD3D9RenderState[GFXRSAmbientMaterialSource] = D3DRS_AMBIENTMATERIALSOURCE; + GFXD3D9RenderState[GFXRSEmissiveMaterialSource] = D3DRS_EMISSIVEMATERIALSOURCE; + GFXD3D9RenderState[GFXRSVertexBlend] = D3DRS_VERTEXBLEND; + GFXD3D9RenderState[GFXRSFogEnable] = D3DRS_FOGENABLE; + GFXD3D9RenderState[GFXRSSpecularEnable] = D3DRS_SPECULARENABLE; + GFXD3D9RenderState[GFXRSFogColor] = D3DRS_FOGCOLOR; + GFXD3D9RenderState[GFXRSFogTableMode] = D3DRS_FOGTABLEMODE; + GFXD3D9RenderState[GFXRSFogStart] = D3DRS_FOGSTART; + GFXD3D9RenderState[GFXRSFogEnd] = D3DRS_FOGEND; + GFXD3D9RenderState[GFXRSFogDensity] = D3DRS_FOGDENSITY; + GFXD3D9RenderState[GFXRSRangeFogEnable] = D3DRS_RANGEFOGENABLE; + GFXD3D9RenderState[GFXRSDebugMonitorToken] = D3DRS_DEBUGMONITORTOKEN; + GFXD3D9RenderState[GFXRSIndexedVertexBlendEnable] = D3DRS_INDEXEDVERTEXBLENDENABLE; + GFXD3D9RenderState[GFXRSTweenFactor] = D3DRS_TWEENFACTOR; + GFXD3D9RenderState[GFXRSTextureFactor] = D3DRS_TEXTUREFACTOR; + GFXD3D9RenderState[GFXRSPatchEdgeStyle] = D3DRS_PATCHEDGESTYLE; + GFXD3D9RenderState[GFXRSPositionDegree] = D3DRS_POSITIONDEGREE; + GFXD3D9RenderState[GFXRSNormalDegree] = D3DRS_NORMALDEGREE; + GFXD3D9RenderState[GFXRSAntiAliasedLineEnable] = D3DRS_ANTIALIASEDLINEENABLE; + GFXD3D9RenderState[GFXRSAdaptiveTess_X] = D3DRS_ADAPTIVETESS_X; + GFXD3D9RenderState[GFXRSAdaptiveTess_Y] = D3DRS_ADAPTIVETESS_Y; + GFXD3D9RenderState[GFXRSdaptiveTess_Z] = D3DRS_ADAPTIVETESS_Z; + GFXD3D9RenderState[GFXRSAdaptiveTess_W] = D3DRS_ADAPTIVETESS_W; + GFXD3D9RenderState[GFXRSEnableAdaptiveTesselation] = D3DRS_ENABLEADAPTIVETESSELLATION; + GFXD3D9RenderState[GFXRSDitherEnable] = D3DRS_DITHERENABLE; + GFXD3D9RenderState[GFXRSColorWriteEnable] = D3DRS_COLORWRITEENABLE; + GFXD3D9RenderState[GFXRSBlendOp] = D3DRS_BLENDOP; + GFXD3D9RenderState[GFXRSScissorTestEnable] = D3DRS_SCISSORTESTENABLE; + GFXD3D9RenderState[GFXRSSlopeScaleDepthBias] = D3DRS_SLOPESCALEDEPTHBIAS; + GFXD3D9RenderState[GFXRSMinTessellationLevel] = D3DRS_MINTESSELLATIONLEVEL; + GFXD3D9RenderState[GFXRSMaxTessellationLevel] = D3DRS_MAXTESSELLATIONLEVEL; + GFXD3D9RenderState[GFXRSTwoSidedStencilMode] = D3DRS_TWOSIDEDSTENCILMODE; + GFXD3D9RenderState[GFXRSCCWStencilFail] = D3DRS_CCW_STENCILFAIL; + GFXD3D9RenderState[GFXRSCCWStencilZFail] = D3DRS_CCW_STENCILZFAIL; + GFXD3D9RenderState[GFXRSCCWStencilPass] = D3DRS_CCW_STENCILPASS; + GFXD3D9RenderState[GFXRSCCWStencilFunc] = D3DRS_CCW_STENCILFUNC; + GFXD3D9RenderState[GFXRSColorWriteEnable1] = D3DRS_COLORWRITEENABLE1; + GFXD3D9RenderState[GFXRSColorWriteEnable2] = D3DRS_COLORWRITEENABLE2; + GFXD3D9RenderState[GFXRSolorWriteEnable3] = D3DRS_COLORWRITEENABLE3; + GFXD3D9RenderState[GFXRSBlendFactor] = D3DRS_BLENDFACTOR; + GFXD3D9RenderState[GFXRSSRGBWriteEnable] = D3DRS_SRGBWRITEENABLE; + GFXD3D9RenderState[GFXRSDepthBias] = D3DRS_DEPTHBIAS; + GFXD3D9RenderState[GFXRSWrap8] = D3DRS_WRAP8; + GFXD3D9RenderState[GFXRSWrap9] = D3DRS_WRAP9; + GFXD3D9RenderState[GFXRSWrap10] = D3DRS_WRAP10; + GFXD3D9RenderState[GFXRSWrap11] = D3DRS_WRAP11; + GFXD3D9RenderState[GFXRSWrap12] = D3DRS_WRAP12; + GFXD3D9RenderState[GFXRSWrap13] = D3DRS_WRAP13; + GFXD3D9RenderState[GFXRSWrap14] = D3DRS_WRAP14; + GFXD3D9RenderState[GFXRSWrap15] = D3DRS_WRAP15; + GFXD3D9RenderState[GFXRSSeparateAlphaBlendEnable] = D3DRS_SEPARATEALPHABLENDENABLE; + GFXD3D9RenderState[GFXRSSrcBlendAlpha] = D3DRS_SRCBLENDALPHA; + GFXD3D9RenderState[GFXRSDestBlendAlpha] = D3DRS_DESTBLENDALPHA; + GFXD3D9RenderState[GFXRSBlendOpAlpha] = D3DRS_BLENDOPALPHA; + VALIDATE_LOOKUPTABLE( GFXD3D9RenderState, GFXRenderState ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9TextureFilter, GFXTextureFilter, _D3DTEXTUREFILTERTYPE ); + GFXD3D9TextureFilter[GFXTextureFilterNone] = D3DTEXF_NONE; + GFXD3D9TextureFilter[GFXTextureFilterPoint] = D3DTEXF_POINT; + GFXD3D9TextureFilter[GFXTextureFilterLinear] = D3DTEXF_LINEAR; + GFXD3D9TextureFilter[GFXTextureFilterAnisotropic] = D3DTEXF_ANISOTROPIC; + GFXD3D9TextureFilter[GFXTextureFilterPyramidalQuad] = D3DTEXF_PYRAMIDALQUAD; + GFXD3D9TextureFilter[GFXTextureFilterGaussianQuad] = D3DTEXF_GAUSSIANQUAD; + VALIDATE_LOOKUPTABLE( GFXD3D9TextureFilter, GFXTextureFilter ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9Blend, GFXBlend, _D3DBLEND ); + GFXD3D9Blend[GFXBlendZero] = D3DBLEND_ZERO; + GFXD3D9Blend[GFXBlendOne] = D3DBLEND_ONE; + GFXD3D9Blend[GFXBlendSrcColor] = D3DBLEND_SRCCOLOR; + GFXD3D9Blend[GFXBlendInvSrcColor] = D3DBLEND_INVSRCCOLOR; + GFXD3D9Blend[GFXBlendSrcAlpha] = D3DBLEND_SRCALPHA; + GFXD3D9Blend[GFXBlendInvSrcAlpha] = D3DBLEND_INVSRCALPHA; + GFXD3D9Blend[GFXBlendDestAlpha] = D3DBLEND_DESTALPHA; + GFXD3D9Blend[GFXBlendInvDestAlpha] = D3DBLEND_INVDESTALPHA; + GFXD3D9Blend[GFXBlendDestColor] = D3DBLEND_DESTCOLOR; + GFXD3D9Blend[GFXBlendInvDestColor] = D3DBLEND_INVDESTCOLOR; + GFXD3D9Blend[GFXBlendSrcAlphaSat] = D3DBLEND_SRCALPHASAT; + VALIDATE_LOOKUPTABLE( GFXD3D9Blend, GFXBlend ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9BlendOp, GFXBlendOp, _D3DBLENDOP ); + GFXD3D9BlendOp[GFXBlendOpAdd] = D3DBLENDOP_ADD; + GFXD3D9BlendOp[GFXBlendOpSubtract] = D3DBLENDOP_SUBTRACT; + GFXD3D9BlendOp[GFXBlendOpRevSubtract] = D3DBLENDOP_REVSUBTRACT; + GFXD3D9BlendOp[GFXBlendOpMin] = D3DBLENDOP_MIN; + GFXD3D9BlendOp[GFXBlendOpMax] = D3DBLENDOP_MAX; + VALIDATE_LOOKUPTABLE( GFXD3D9BlendOp, GFXBlendOp ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9StencilOp, GFXStencilOp, _D3DSTENCILOP ); + GFXD3D9StencilOp[GFXStencilOpKeep] = D3DSTENCILOP_KEEP; + GFXD3D9StencilOp[GFXStencilOpZero] = D3DSTENCILOP_ZERO; + GFXD3D9StencilOp[GFXStencilOpReplace] = D3DSTENCILOP_REPLACE; + GFXD3D9StencilOp[GFXStencilOpIncrSat] = D3DSTENCILOP_INCRSAT; + GFXD3D9StencilOp[GFXStencilOpDecrSat] = D3DSTENCILOP_DECRSAT; + GFXD3D9StencilOp[GFXStencilOpInvert] = D3DSTENCILOP_INVERT; + GFXD3D9StencilOp[GFXStencilOpIncr] = D3DSTENCILOP_INCR; + GFXD3D9StencilOp[GFXStencilOpDecr] = D3DSTENCILOP_DECR; + VALIDATE_LOOKUPTABLE( GFXD3D9StencilOp, GFXStencilOp ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9CmpFunc, GFXCmp, _D3DCMPFUNC ); + GFXD3D9CmpFunc[GFXCmpNever] = D3DCMP_NEVER; + GFXD3D9CmpFunc[GFXCmpLess] = D3DCMP_LESS; + GFXD3D9CmpFunc[GFXCmpEqual] = D3DCMP_EQUAL; + GFXD3D9CmpFunc[GFXCmpLessEqual] = D3DCMP_LESSEQUAL; + GFXD3D9CmpFunc[GFXCmpGreater] = D3DCMP_GREATER; + GFXD3D9CmpFunc[GFXCmpNotEqual] = D3DCMP_NOTEQUAL; + GFXD3D9CmpFunc[GFXCmpGreaterEqual] = D3DCMP_GREATEREQUAL; + GFXD3D9CmpFunc[GFXCmpAlways] = D3DCMP_ALWAYS; + VALIDATE_LOOKUPTABLE( GFXD3D9CmpFunc, GFXCmp ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9CullMode, GFXCull, _D3DCULL ); + GFXD3D9CullMode[GFXCullNone] = D3DCULL_NONE; + GFXD3D9CullMode[GFXCullCW] = D3DCULL_CW; + GFXD3D9CullMode[GFXCullCCW] = D3DCULL_CCW; + VALIDATE_LOOKUPTABLE( GFXD3D9CullMode, GFXCull ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9FillMode, GFXFill, _D3DFILLMODE ); + GFXD3D9FillMode[GFXFillPoint] = D3DFILL_POINT; + GFXD3D9FillMode[GFXFillWireframe] = D3DFILL_WIREFRAME; + GFXD3D9FillMode[GFXFillSolid] = D3DFILL_SOLID; + VALIDATE_LOOKUPTABLE( GFXD3D9FillMode, GFXFill ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9PrimType, GFXPT, _D3DPRIMITIVETYPE ); + GFXD3D9PrimType[GFXPointList] = D3DPT_POINTLIST; + GFXD3D9PrimType[GFXLineList] = D3DPT_LINELIST; + GFXD3D9PrimType[GFXLineStrip] = D3DPT_LINESTRIP; + GFXD3D9PrimType[GFXTriangleList] = D3DPT_TRIANGLELIST; + GFXD3D9PrimType[GFXTriangleStrip] = D3DPT_TRIANGLESTRIP; + GFXD3D9PrimType[GFXTriangleFan] = D3DPT_TRIANGLEFAN; + VALIDATE_LOOKUPTABLE( GFXD3D9PrimType, GFXPT ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9TextureStageState, GFXTSS, _D3DTEXTURESTAGESTATETYPE ); + GFXD3D9TextureStageState[GFXTSSColorOp] = D3DTSS_COLOROP; + GFXD3D9TextureStageState[GFXTSSColorArg1] = D3DTSS_COLORARG1; + GFXD3D9TextureStageState[GFXTSSColorArg2] = D3DTSS_COLORARG2; + GFXD3D9TextureStageState[GFXTSSAlphaOp] = D3DTSS_ALPHAOP; + GFXD3D9TextureStageState[GFXTSSAlphaArg1] = D3DTSS_ALPHAARG1; + GFXD3D9TextureStageState[GFXTSSAlphaArg2] = D3DTSS_ALPHAARG2; + GFXD3D9TextureStageState[GFXTSSBumpEnvMat00] = D3DTSS_BUMPENVMAT00; + GFXD3D9TextureStageState[GFXTSSBumpEnvMat01] = D3DTSS_BUMPENVMAT01; + GFXD3D9TextureStageState[GFXTSSBumpEnvMat10] = D3DTSS_BUMPENVMAT10; + GFXD3D9TextureStageState[GFXTSSBumpEnvMat11] = D3DTSS_BUMPENVMAT11; + GFXD3D9TextureStageState[GFXTSSTexCoordIndex] = D3DTSS_TEXCOORDINDEX; + GFXD3D9TextureStageState[GFXTSSBumpEnvlScale] = D3DTSS_BUMPENVLSCALE; + GFXD3D9TextureStageState[GFXTSSBumpEnvlOffset] = D3DTSS_BUMPENVLOFFSET; + GFXD3D9TextureStageState[GFXTSSTextureTransformFlags] = D3DTSS_TEXTURETRANSFORMFLAGS; + GFXD3D9TextureStageState[GFXTSSColorArg0] = D3DTSS_COLORARG0; + GFXD3D9TextureStageState[GFXTSSAlphaArg0] = D3DTSS_ALPHAARG0; + GFXD3D9TextureStageState[GFXTSSResultArg] = D3DTSS_RESULTARG; + GFXD3D9TextureStageState[GFXTSSConstant] = D3DTSS_CONSTANT; + VALIDATE_LOOKUPTABLE( GFXD3D9TextureStageState, GFXTSS ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9TextureAddress, GFXAddress, _D3DTEXTUREADDRESS ); + GFXD3D9TextureAddress[GFXAddressWrap] = D3DTADDRESS_WRAP ; + GFXD3D9TextureAddress[GFXAddressMirror] = D3DTADDRESS_MIRROR; + GFXD3D9TextureAddress[GFXAddressClamp] = D3DTADDRESS_CLAMP; + GFXD3D9TextureAddress[GFXAddressBorder] = D3DTADDRESS_BORDER; + GFXD3D9TextureAddress[GFXAddressMirrorOnce] = D3DTADDRESS_MIRRORONCE; + VALIDATE_LOOKUPTABLE(GFXD3D9TextureAddress, GFXAddress ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9TextureOp, GFXTOP, _D3DTEXTUREOP ); + GFXD3D9TextureOp[GFXTOPDisable] = D3DTOP_DISABLE; + GFXD3D9TextureOp[GFXTOPSelectARG1] = D3DTOP_SELECTARG1; + GFXD3D9TextureOp[GFXTOPSelectARG2] = D3DTOP_SELECTARG2; + GFXD3D9TextureOp[GFXTOPModulate] = D3DTOP_MODULATE; + GFXD3D9TextureOp[GFXTOPModulate2X] = D3DTOP_MODULATE2X; + GFXD3D9TextureOp[GFXTOPModulate4X] = D3DTOP_MODULATE4X; + GFXD3D9TextureOp[GFXTOPAdd] = D3DTOP_ADD; + GFXD3D9TextureOp[GFXTOPAddSigned] = D3DTOP_ADDSIGNED; + GFXD3D9TextureOp[GFXTOPAddSigned2X] = D3DTOP_ADDSIGNED2X; + GFXD3D9TextureOp[GFXTOPSubtract] = D3DTOP_SUBTRACT; + GFXD3D9TextureOp[GFXTOPAddSmooth] = D3DTOP_ADDSMOOTH; + GFXD3D9TextureOp[GFXTOPBlendDiffuseAlpha] = D3DTOP_BLENDDIFFUSEALPHA; + GFXD3D9TextureOp[GFXTOPBlendTextureAlpha] = D3DTOP_BLENDTEXTUREALPHA; + GFXD3D9TextureOp[GFXTOPBlendFactorAlpha] = D3DTOP_BLENDFACTORALPHA; + GFXD3D9TextureOp[GFXTOPBlendTextureAlphaPM] = D3DTOP_BLENDTEXTUREALPHAPM; + GFXD3D9TextureOp[GFXTOPBlendCURRENTALPHA] = D3DTOP_BLENDCURRENTALPHA; + GFXD3D9TextureOp[GFXTOPPreModulate] = D3DTOP_PREMODULATE; + GFXD3D9TextureOp[GFXTOPModulateAlphaAddColor] = D3DTOP_MODULATEALPHA_ADDCOLOR; + GFXD3D9TextureOp[GFXTOPModulateColorAddAlpha] = D3DTOP_MODULATECOLOR_ADDALPHA; + GFXD3D9TextureOp[GFXTOPModulateInvAlphaAddColor] = D3DTOP_MODULATEINVALPHA_ADDCOLOR; + GFXD3D9TextureOp[GFXTOPModulateInvColorAddAlpha] = D3DTOP_MODULATEINVCOLOR_ADDALPHA; + GFXD3D9TextureOp[GFXTOPBumpEnvMap] = D3DTOP_BUMPENVMAP; + GFXD3D9TextureOp[GFXTOPBumpEnvMapLuminance] = D3DTOP_BUMPENVMAPLUMINANCE; + GFXD3D9TextureOp[GFXTOPDotProduct3] = D3DTOP_DOTPRODUCT3; + GFXD3D9TextureOp[GFXTOPLERP] = D3DTOP_LERP; + VALIDATE_LOOKUPTABLE( GFXD3D9TextureOp, GFXTOP ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXD3D9DeclType, GFXDeclType, _D3DDECLTYPE ); + GFXD3D9DeclType[GFXDeclType_Float] = D3DDECLTYPE_FLOAT1; + GFXD3D9DeclType[GFXDeclType_Float2] = D3DDECLTYPE_FLOAT2; + GFXD3D9DeclType[GFXDeclType_Float3] = D3DDECLTYPE_FLOAT3; + GFXD3D9DeclType[GFXDeclType_Float4] = D3DDECLTYPE_FLOAT4; + GFXD3D9DeclType[GFXDeclType_Color] = D3DDECLTYPE_D3DCOLOR; + VALIDATE_LOOKUPTABLE( GFXD3D9DeclType, GFXDeclType ); +} + diff --git a/gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp b/gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp new file mode 100644 index 0000000..6bb5b3d --- /dev/null +++ b/gfx/D3D9/pc/gfxD3D9PrimitiveBuffer.pc.cpp @@ -0,0 +1,55 @@ +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/D3D9/gfxD3D9PrimitiveBuffer.h" +#include "core/util/safeRelease.h" + +void GFXD3D9PrimitiveBuffer::lock(U16 indexStart, U16 indexEnd, U16 **indexPtr) +{ + AssertFatal(!mLocked, "GFXD3D9PrimitiveBuffer::lock - Can't lock a primitive buffer more than once!"); + mLocked = true; + U32 flags=0; + switch(mBufferType) + { + case GFXBufferTypeStatic: + // flags |= D3DLOCK_DISCARD; + break; + + case GFXBufferTypeDynamic: + // Always discard the content within a locked region. + flags |= D3DLOCK_DISCARD; + break; + + case GFXBufferTypeVolatile: + // Get our range now... + AssertFatal(indexStart == 0, "Cannot get a subrange on a volatile buffer."); + AssertFatal(indexEnd < MAX_DYNAMIC_INDICES, "Cannot get more than MAX_DYNAMIC_INDICES in a volatile buffer. Up the constant!"); + + // Get the primtive buffer + mVolatileBuffer = ((GFXD3D9Device*)mDevice)->mDynamicPB; + + AssertFatal( mVolatileBuffer, "GFXD3D9PrimitiveBuffer::lock - No dynamic primitive buffer was available!"); + + // We created the pool when we requested this volatile buffer, so assume it exists... + if( mVolatileBuffer->mIndexCount + indexEnd > MAX_DYNAMIC_INDICES ) + { + flags |= D3DLOCK_DISCARD; + mVolatileStart = indexStart = 0; + indexEnd = indexEnd; + } + else + { + flags |= D3DLOCK_NOOVERWRITE; + mVolatileStart = indexStart = mVolatileBuffer->mIndexCount; + indexEnd += mVolatileBuffer->mIndexCount; + } + + mVolatileBuffer->mIndexCount = indexEnd + 1; + ib = mVolatileBuffer->ib; + + break; + } + + D3D9Assert( ib->Lock(indexStart * sizeof(U16), (indexEnd - indexStart) * sizeof(U16), (void**)indexPtr, flags), + "GFXD3D9PrimitiveBuffer::lock - Could not lock primitive buffer."); + +} + diff --git a/gfx/D3D9/pc/gfxPCD3D9Device.cpp b/gfx/D3D9/pc/gfxPCD3D9Device.cpp new file mode 100644 index 0000000..afdd06c --- /dev/null +++ b/gfx/D3D9/pc/gfxPCD3D9Device.cpp @@ -0,0 +1,1034 @@ +#include "gfx/D3D9/pc/gfxPCD3D9Device.h" +#include "gfx/D3D9/pc/gfxPCD3D9Target.h" +#include "gfx/D3D9/gfxD3D9CardProfiler.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#include "platformWin32/platformWin32.h" +#include "windowManager/win32/win32Window.h" +#include "gfx/screenshot.h" +#include "gfx/D3D/screenshotD3D.h" +#include "core/util/journal/process.h" + + +bool GFXPCD3D9Device::mEnableNVPerfHUD = false; + +GFXAdapter::CreateDeviceInstanceDelegate GFXPCD3D9Device::mCreateDeviceInstance(GFXPCD3D9Device::createInstance); + +GFXDevice *GFXPCD3D9Device::createInstance( U32 adapterIndex ) +{ + return new GFXPCD3D9Device( Direct3DCreate9( D3D_SDK_VERSION ), adapterIndex ); +} + +//----------------------------------------------------------------------------- + +GFXFormat GFXPCD3D9Device::selectSupportedFormat(GFXTextureProfile *profile, + const Vector &formats, bool texture, bool mustblend, bool mustfilter) +{ + DWORD usage = 0; + + if(profile->isDynamic()) + usage |= D3DUSAGE_DYNAMIC; + + if(profile->isRenderTarget()) + usage |= D3DUSAGE_RENDERTARGET; + + if(profile->isZTarget()) + usage |= D3DUSAGE_DEPTHSTENCIL; + + if(mustblend) + usage |= D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING; + + if(mustfilter) + usage |= D3DUSAGE_QUERY_FILTER; + + D3DDISPLAYMODE mode; + D3D9Assert(mD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode), "Unable to get adapter mode."); + + D3DRESOURCETYPE type; + if(texture) + type = D3DRTYPE_TEXTURE; + else + type = D3DRTYPE_SURFACE; + + for(U32 i=0; iCheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, mode.Format, + usage, type, GFXD3D9TextureFormat[formats[i]]) == D3D_OK) + return formats[i]; + } + + return GFXFormatR8G8B8A8; +} + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Setup D3D present parameters - init helper function +//----------------------------------------------------------------------------- +D3DPRESENT_PARAMETERS GFXPCD3D9Device::setupPresentParams( const GFXVideoMode &mode, const HWND &hwnd ) const +{ + // Create D3D Presentation params + D3DPRESENT_PARAMETERS d3dpp; + dMemset( &d3dpp, 0, sizeof( d3dpp ) ); + + D3DFORMAT fmt = D3DFMT_X8R8G8B8; // 32 bit + + if( mode.bitDepth == 16 ) + { + fmt = D3DFMT_R5G6B5; + } + + D3DMULTISAMPLE_TYPE aatype; + DWORD aalevel; + + if (mode.antialiasLevel == 0) + { + aatype = D3DMULTISAMPLE_NONE; + aalevel = 0; + } else { + aatype = D3DMULTISAMPLE_NONMASKABLE; + aalevel = mode.antialiasLevel-1; + } + + _validateMultisampleParams(fmt, aatype, aalevel); + + d3dpp.BackBufferWidth = mode.resolution.x; + d3dpp.BackBufferHeight = mode.resolution.y; + d3dpp.BackBufferFormat = fmt; + d3dpp.BackBufferCount = 1; + d3dpp.MultiSampleType = aatype; + d3dpp.MultiSampleQuality = aalevel; + d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; + d3dpp.hDeviceWindow = hwnd; + d3dpp.Windowed = !mode.fullScreen; + d3dpp.EnableAutoDepthStencil = TRUE; + d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; + d3dpp.Flags = 0; + d3dpp.FullScreen_RefreshRateInHz = (mode.refreshRate == 0 || !mode.fullScreen) ? + D3DPRESENT_RATE_DEFAULT : mode.refreshRate; + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + + if( Con::getBoolVariable( "$pref::Video::disableVerticalSync", true ) ) + { + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // This does NOT wait for vsync + } + + return d3dpp; +} + +//----------------------------------------------------------------------------- +// Enumerate D3D adapters +//----------------------------------------------------------------------------- +void GFXPCD3D9Device::enumerateAdapters( Vector &adapterList ) +{ + // Grab a D3D9 handle here to first get the D3D9 devices + LPDIRECT3D9 d3d9 = Direct3DCreate9( D3D_SDK_VERSION ); + + // If we could not create the d3d9 object then either the system + // is corrupt or they need to update the directx runtime. + if ( !d3d9 ) + { + Con::errorf( "Unsupported DirectX version!" ); + Platform::messageBox( Con::getVariable( "$appName" ), + "DirectX could not be started!\r\n" + "Please be sure you have the latest version of DirectX installed.", + MBOk, MIStop ); + Platform::forceShutdown( -1 ); + } + + for( U32 adapterIndex = 0; adapterIndex < d3d9->GetAdapterCount(); adapterIndex++ ) + { + GFXAdapter *toAdd = new GFXAdapter; + toAdd->mType = Direct3D9; + toAdd->mIndex = adapterIndex; + toAdd->mCreateDeviceInstanceDelegate = mCreateDeviceInstance; + + // Grab the shader model. + D3DCAPS9 caps; + d3d9->GetDeviceCaps(adapterIndex, D3DDEVTYPE_HAL, &caps); + U8 *pxPtr = (U8*) &caps.PixelShaderVersion; + toAdd->mShaderModel = pxPtr[1] + pxPtr[0] * 0.1; + + // Get the device description string. + D3DADAPTER_IDENTIFIER9 temp; + d3d9->GetAdapterIdentifier( adapterIndex, NULL, &temp ); // The NULL is the flags which deal with WHQL + + dStrcpy( toAdd->mName, temp.Description ); + dStrncat(toAdd->mName, " (D3D9)", GFXAdapter::MaxAdapterNameLen); + + // Video mode enumeration. + Vector formats( __FILE__, __LINE__ ); + formats.push_back( D3DFMT_R5G6B5 ); // D3DFMT_R5G6B5 - 16bit format + formats.push_back( D3DFMT_X8R8G8B8 ); // D3DFMT_X8R8G8B8 - 32bit format + + for( S32 i = 0; i < formats.size(); i++ ) + { + DWORD MaxSampleQualities; + d3d9->CheckDeviceMultiSampleType(adapterIndex, D3DDEVTYPE_HAL, formats[i], FALSE, D3DMULTISAMPLE_NONMASKABLE, &MaxSampleQualities); + + for( U32 j = 0; j < d3d9->GetAdapterModeCount( adapterIndex, formats[i] ); j++ ) + { + D3DDISPLAYMODE mode; + d3d9->EnumAdapterModes( adapterIndex, formats[i], j, &mode ); + + GFXVideoMode vmAdd; + + vmAdd.bitDepth = ( i == 0 ? 16 : 32 ); // This will need to be changed later + vmAdd.fullScreen = true; + vmAdd.refreshRate = mode.RefreshRate; + vmAdd.resolution = Point2I( mode.Width, mode.Height ); + vmAdd.antialiasLevel = MaxSampleQualities; + + toAdd->mAvailableModes.push_back( vmAdd ); + } + } + + adapterList.push_back( toAdd ); + } + + d3d9->Release(); +} + +void GFXPCD3D9Device::enumerateVideoModes() +{ + Vector formats( __FILE__, __LINE__ ); + formats.push_back( D3DFMT_R5G6B5 ); // D3DFMT_R5G6B5 - 16bit format + formats.push_back( D3DFMT_X8R8G8B8 ); // D3DFMT_X8R8G8B8 - 32bit format + + for( S32 i = 0; i < formats.size(); i++ ) + { + for( U32 j = 0; j < mD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT, formats[i] ); j++ ) + { + D3DDISPLAYMODE mode; + mD3D->EnumAdapterModes( D3DADAPTER_DEFAULT, formats[i], j, &mode ); + + GFXVideoMode toAdd; + + toAdd.bitDepth = ( i == 0 ? 16 : 32 ); // This will need to be changed later + toAdd.fullScreen = false; + toAdd.refreshRate = mode.RefreshRate; + toAdd.resolution = Point2I( mode.Width, mode.Height ); + + mVideoModes.push_back( toAdd ); + } + } +} + +//----------------------------------------------------------------------------- +// Initialize - create window, device, etc +//----------------------------------------------------------------------------- +void GFXPCD3D9Device::init( const GFXVideoMode &mode, PlatformWindow *window /* = NULL */ ) +{ + AssertFatal(window, "GFXPCD3D9Device::init - must specify a window!"); + + initD3DXFnTable(); + + Win32Window *win = dynamic_cast( window ); + AssertISV( win, "GFXD3D9Device::init - got a non Win32Window window passed in! Did DX go crossplatform?" ); + + HWND winHwnd = win->getHWND(); + + // Create D3D Presentation params + D3DPRESENT_PARAMETERS d3dpp = setupPresentParams( mode, winHwnd ); + mMultisampleType = d3dpp.MultiSampleType; + mMultisampleLevel = d3dpp.MultiSampleQuality; + +#ifndef TORQUE_SHIPPING + bool usePerfHud = GFXPCD3D9Device::mEnableNVPerfHUD || Con::getBoolVariable("$Video::useNVPerfHud", false); +#else + bool usePerfHud = false; +#endif + + HRESULT hres = E_FAIL; + if ( usePerfHud ) + { + hres = mD3D->CreateDevice( mD3D->GetAdapterCount() - 1, D3DDEVTYPE_REF, + winHwnd, + D3DCREATE_MIXED_VERTEXPROCESSING, // | D3DCREATE_MULTITHREADED, + &d3dpp, &mD3DDevice ); + } + else + { + // Vertex processing was changed from MIXED to HARDWARE because of the switch to a pure D3D device. + + // Set up device flags from our compile flags. + U32 deviceFlags = 0; + deviceFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING; + + // NOTE: We do not enabled D3D multithreading by default as it + // adds a considerable amount of CPU cost. In all cases you can + // do better by locking/creating resources in the primary thread + // and passing them to worker threads. + // + //#ifdef TORQUE_MULTITHREAD + // deviceFlags |= D3DCREATE_MULTITHREADED; + //#endif + + // This needs to be set for firefox on windows + if ( Platform::getWebDeployment() ) + deviceFlags |= D3DCREATE_FPU_PRESERVE; + + // Try to do pure, unless we're doing debug (and thus doing more paranoid checking). +#ifndef TORQUE_DEBUG_RENDER + deviceFlags |= D3DCREATE_PUREDEVICE; +#endif + + hres = mD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, winHwnd, deviceFlags, &d3dpp, &mD3DDevice ); + + if (FAILED(hres) && hres != D3DERR_OUTOFVIDEOMEMORY) + { + Con::errorf(" Failed to create hardware device, trying mixed device"); + // turn off pure + deviceFlags &= (~D3DCREATE_PUREDEVICE); + + // try mixed mode + deviceFlags &= (~D3DCREATE_HARDWARE_VERTEXPROCESSING); + deviceFlags |= D3DCREATE_MIXED_VERTEXPROCESSING; + hres = mD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, + winHwnd, deviceFlags, + &d3dpp, &mD3DDevice ); + + // try software + if (FAILED(hres) && hres != D3DERR_OUTOFVIDEOMEMORY) + { + Con::errorf(" Failed to create mixed mode device, trying software device"); + deviceFlags &= (~D3DCREATE_MIXED_VERTEXPROCESSING); + deviceFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING; + hres = mD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, + winHwnd, deviceFlags, + &d3dpp, &mD3DDevice ); + + if (FAILED(hres) && hres != D3DERR_OUTOFVIDEOMEMORY) + Con::errorf(" Failed to create software device, giving up"); + D3D9Assert(hres, "GFXPCD3D9Device::init - CreateDevice failed!"); + } + } + } + + // Gracefully die if they can't give us a device. + if(!mD3DDevice) + { + if (hres == D3DERR_OUTOFVIDEOMEMORY) + { + char errorMsg[4096]; + dSprintf(errorMsg, sizeof(errorMsg), + "Out of video memory. Close other windows, reboot, and/or upgrade your video card drivers. Your video card is: %s", getAdapter().getName()); + Platform::AlertOK("DirectX Error", errorMsg); + } + else + { + Platform::AlertOK("DirectX Error!", "Failed to initialize Direct3D! Make sure you have DirectX 9 installed, and " + "are running a graphics card that supports Pixel Shader 1.1."); + } + Platform::forceShutdown(1); + } + + // Check up on things + Con::printf(" Cur. D3DDevice ref count=%d", mD3DDevice->AddRef() - 1); + mD3DDevice->Release(); + + mTextureManager = new GFXD3D9TextureManager( mD3DDevice ); + + // Now reacquire all the resources we trashed earlier + reacquireDefaultPoolResources(); + + // Setup default states + initStates(); + + //-------- Output init info --------- + D3DCAPS9 caps; + mD3DDevice->GetDeviceCaps( &caps ); + + U8 *pxPtr = (U8*) &caps.PixelShaderVersion; + mPixVersion = pxPtr[1] + pxPtr[0] * 0.1; + Con::printf( " Pix version detected: %f", mPixVersion ); + + bool forcePixVersion = Con::getBoolVariable( "$pref::Video::forcePixVersion", false ); + if( forcePixVersion ) + { + float forcedPixVersion = Con::getFloatVariable( "$pref::Video::forcedPixVersion", mPixVersion ); + if( forcedPixVersion < mPixVersion ) + { + mPixVersion = forcedPixVersion; + Con::errorf( " Forced pix version: %f", mPixVersion ); + } + } + + U8 *vertPtr = (U8*) &caps.VertexShaderVersion; + F32 vertVersion = vertPtr[1] + vertPtr[0] * 0.1; + Con::printf( " Vert version detected: %f", vertVersion ); + + // The sampler count is based on the shader model and + // not found in the caps. + // + // MaxSimultaneousTextures is only valid for fixed + // function rendering. + // + if ( mPixVersion >= 2.0f ) + mNumSamplers = 16; + else if ( mPixVersion >= 1.4f ) + mNumSamplers = 6; + else if ( mPixVersion > 0.0f ) + mNumSamplers = 4; + else + mNumSamplers = caps.MaxSimultaneousTextures; + + // This shouldn't happen until SM5 or some other + // radical change in GPU hardware occurs. + AssertFatal( mNumSamplers <= TEXTURE_STAGE_COUNT, + "GFXPCD3D9Device::init - Sampler count greater than TEXTURE_STAGE_COUNT!" ); + + Con::printf( " Maximum number of simultaneous samplers: %d", mNumSamplers ); + + // detect max number of simultaneous render targets + mNumRenderTargets = caps.NumSimultaneousRTs; + Con::printf( " Number of simultaneous render targets: %d", mNumRenderTargets ); + + // detect occlusion query support + if (SUCCEEDED(mD3DDevice->CreateQuery( D3DQUERYTYPE_OCCLUSION, NULL ))) + mOcclusionQuerySupported = true; + + Con::printf( " Hardware occlusion query detected: %s", mOcclusionQuerySupported ? "Yes" : "No" ); + + mCardProfiler = new GFXD3D9CardProfiler(); + mCardProfiler->init(); + + gScreenShot = new ScreenShotD3D; + + // Grab the depth-stencil... + SAFE_RELEASE(mDeviceDepthStencil); + D3D9Assert(mD3DDevice->GetDepthStencilSurface(&mDeviceDepthStencil), "GFXD3D9Device::init - couldn't grab reference to device's depth-stencil surface."); + + mInitialized = true; + + deviceInited(); + + // Uncomment to dump out code needed in initStates, you may also need to enable the reference device (get rid of code in initStates first as well) + // regenStates(); +} + +//------------------------------------------------------------------------------ +void GFXPCD3D9Device::enterDebugEvent(ColorI color, const char *name) +{ + // BJGFIX + WCHAR eventName[260]; + MultiByteToWideChar( CP_ACP, 0, name, -1, eventName, 260 ); + + D3DPERF_BeginEvent(D3DCOLOR_ARGB(color.alpha, color.red, color.green, color.blue), + (LPCWSTR)&eventName); +} + +//------------------------------------------------------------------------------ +void GFXPCD3D9Device::leaveDebugEvent() +{ + D3DPERF_EndEvent(); +} + +//------------------------------------------------------------------------------ +void GFXPCD3D9Device::setDebugMarker(ColorI color, const char *name) +{ + // BJGFIX + WCHAR eventName[260]; + MultiByteToWideChar( CP_ACP, 0, name, -1, eventName, 260 ); + + D3DPERF_SetMarker(D3DCOLOR_ARGB(color.alpha, color.red, color.green, color.blue), + (LPCWSTR)&eventName); +} + +//----------------------------------------------------------------------------- + +void GFXPCD3D9Device::setMatrix( GFXMatrixType mtype, const MatrixF &mat ) +{ + mat.transposeTo( mTempMatrix ); + + mD3DDevice->SetTransform( (_D3DTRANSFORMSTATETYPE)mtype, (D3DMATRIX *)&mTempMatrix ); +} + +//----------------------------------------------------------------------------- + +void GFXPCD3D9Device::_setTextureStageState( U32 stage, U32 state, U32 value ) +{ + switch( state ) + { + case GFXTSSColorOp: + case GFXTSSAlphaOp: + mD3DDevice->SetTextureStageState( stage, GFXD3D9TextureStageState[state], GFXD3D9TextureOp[value] ); + break; + + default: + mD3DDevice->SetTextureStageState( stage, GFXD3D9TextureStageState[state], value ); + break; + } +} + +//------------------------------------------------------------------------------ + +void GFXPCD3D9Device::initStates() +{ + //------------------------------------- + // Auto-generated default states, see regenStates() for details + // + + // Render states + mD3DDevice->SetRenderState( GFXD3D9RenderState[0], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[1], 3 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[2], 2 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[3], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[4], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[5], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[6], 2 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[7], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[8], 3 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[9], 4 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[10], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[11], 8 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[12], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[13], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[14], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[15], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[16], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[17], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[18], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[19], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[20], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[21], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[22], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[23], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[24], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[25], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[26], 8 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[27], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[28], -1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[29], -1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[30], -1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[31], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[32], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[33], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[34], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[35], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[36], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[37], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[38], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[39], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[40], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[41], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[42], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[43], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[44], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[45], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[46], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[47], 2 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[48], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[49], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[50], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[51], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[52], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[53], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[54], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[55], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[56], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[57], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[58], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[59], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[60], -1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[61], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[62], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[63], 1115684864 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[64], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[65], 15 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[66], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[67], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[68], 3 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[69], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[70], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[71], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[72], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[73], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[74], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[75], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[76], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[77], 1065353216 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[78], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[79], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[80], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[81], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[82], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[83], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[84], 8 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[85], 15 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[86], 15 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[87], 15 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[88], -1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[89], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[90], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[91], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[92], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[93], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[94], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[95], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[96], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[97], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[98], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[99], 0 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[100], 2 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[101], 1 ); + mD3DDevice->SetRenderState( GFXD3D9RenderState[102], 1 ); + + // Texture Stage states + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[0], 4 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[3], 2 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[10], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 0, GFXD3D9TextureStageState[17], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[0], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[3], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[10], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 1, GFXD3D9TextureStageState[17], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[0], 1 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[3], 1 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[10], 2 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 2, GFXD3D9TextureStageState[17], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[0], 1 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[3], 1 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[10], 3 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 3, GFXD3D9TextureStageState[17], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[0], 1 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[3], 1 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[10], 4 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 4, GFXD3D9TextureStageState[17], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[0], 1 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[3], 1 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[10], 5 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 5, GFXD3D9TextureStageState[17], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[0], 1 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[3], 1 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[10], 6 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 6, GFXD3D9TextureStageState[17], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[0], 1 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[1], 2 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[2], 1 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[3], 1 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[4], 2 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[5], 1 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[6], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[7], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[8], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[9], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[10], 7 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[11], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[12], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[13], 0 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[14], 1 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[15], 1 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[16], 1 ); + mD3DDevice->SetTextureStageState( 7, GFXD3D9TextureStageState[17], 0 ); + + // Sampler states + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 0, GFXD3D9SamplerState[12], 0 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 1, GFXD3D9SamplerState[12], 0 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 2, GFXD3D9SamplerState[12], 0 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 3, GFXD3D9SamplerState[12], 0 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 4, GFXD3D9SamplerState[12], 0 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 5, GFXD3D9SamplerState[12], 0 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 6, GFXD3D9SamplerState[12], 0 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[0], 1 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[1], 1 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[2], 1 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[3], 0 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[4], 1 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[5], 1 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[6], 0 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[7], 0 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[8], 0 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[9], 1 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[10], 0 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[11], 0 ); + mD3DDevice->SetSamplerState( 7, GFXD3D9SamplerState[12], 0 ); +} + +void GFXPCD3D9Device::_validateMultisampleParams(D3DFORMAT format, D3DMULTISAMPLE_TYPE & aatype, DWORD & aalevel) const +{ + if (aatype != D3DMULTISAMPLE_NONE) + { + DWORD MaxSampleQualities; + mD3D->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, format, FALSE, D3DMULTISAMPLE_NONMASKABLE, &MaxSampleQualities); + aatype = D3DMULTISAMPLE_NONMASKABLE; + aalevel = getMin((U32)aalevel, (U32)MaxSampleQualities-1); + } +} + +bool GFXPCD3D9Device::beginSceneInternal() +{ + // Make sure we have a device + HRESULT res = mD3DDevice->TestCooperativeLevel(); + + S32 attempts = 0; + const S32 MaxAttempts = 40; + const S32 SleepMsPerAttempt = 50; + while(res == D3DERR_DEVICELOST && attempts < MaxAttempts) + { + // Lost device! Just keep querying + res = mD3DDevice->TestCooperativeLevel(); + + Con::warnf("GFXPCD3D9Device::beginScene - Device needs to be reset, waiting on device..."); + + Sleep(SleepMsPerAttempt); + attempts++; + } + + if (attempts >= MaxAttempts && res == D3DERR_DEVICELOST) + { + Con::errorf("GFXPCD3D9Device::beginScene - Device lost and reset wait time exceeded, skipping reset (will retry later)"); + mCanCurrentlyRender = false; + return false; + } + + // Trigger a reset if we can't get a good result from TestCooperativeLevel. + if(res == D3DERR_DEVICENOTRESET) + { + Con::warnf("GFXPCD3D9Device::beginScene - Device needs to be reset, resetting device..."); + + // Reset the device! + GFXResource *walk = mResourceListHead; + while(walk) + { + // Find the window target with implicit flag set and reset the device with its presentation params. + if(GFXPCD3D9WindowTarget *gdwt = dynamic_cast(walk)) + { + if(gdwt->mImplicit) + { + reset(gdwt->mPresentationParams); + break; + } + } + + walk = walk->getNextResource(); + } + } + + // Call parent + return Parent::beginSceneInternal(); +} + +GFXWindowTarget * GFXPCD3D9Device::allocWindowTarget( PlatformWindow *window ) +{ + AssertFatal(window,"GFXD3D9Device::allocWindowTarget - no window provided!"); +#ifndef TORQUE_OS_XENON + AssertFatal(dynamic_cast(window), + "GFXD3D9Device::allocWindowTarget - only works with Win32Windows!"); +#endif + + // Set up a new window target... + GFXPCD3D9WindowTarget *gdwt = new GFXPCD3D9WindowTarget(); + gdwt->mWindow = window; + gdwt->mSize = window->getClientExtent(); + gdwt->mDevice = this; + gdwt->initPresentationParams(); + + // Now, we have to init & bind our device... we have basically two scenarios + // of which the first is: + if(mD3DDevice == NULL) + { + // Allocate the device. + init(window->getVideoMode(), window); + + // Cool, we have the device, grab back the depthstencil buffer as well + // as the swap chain. + gdwt->mImplicit = true; + gdwt->setImplicitSwapChain(); + } + else + { + // And the second case: + // Initialized device, create an additional swap chain. + gdwt->mImplicit = false; + gdwt->createAdditionalSwapChain(); + } + + gdwt->registerResourceWithDevice(this); + + return gdwt; +} + +GFXTextureTarget * GFXPCD3D9Device::allocRenderToTextureTarget() +{ + GFXPCD3D9TextureTarget *targ = new GFXPCD3D9TextureTarget(); + targ->mDevice = this; + + targ->registerResourceWithDevice(this); + + return targ; +} + +//----------------------------------------------------------------------------- +// Reset D3D device +//----------------------------------------------------------------------------- +void GFXPCD3D9Device::reset( D3DPRESENT_PARAMETERS &d3dpp ) +{ + if(!mD3DDevice) + return; + + mInitialized = false; + + mMultisampleType = d3dpp.MultiSampleType; + mMultisampleLevel = d3dpp.MultiSampleQuality; + _validateMultisampleParams(d3dpp.BackBufferFormat, mMultisampleType, mMultisampleLevel); + + // Clean up some commonly dangling state. This helps prevents issues with + // items that are destroyed by the texture manager callbacks and recreated + // later, but still left bound. + setVertexBuffer(NULL); + setPrimitiveBuffer(NULL); + for(S32 i=0; iAddRef()-1); + mDeviceDepthStencil->Release(); + } + + // First release all the stuff we allocated from D3DPOOL_DEFAULT + releaseDefaultPoolResources(); + + // reset device + Con::printf( "--- Resetting D3D Device ---" ); + HRESULT hres = S_OK; + hres = mD3DDevice->Reset( &d3dpp ); + + if( FAILED( hres ) ) + { + while( mD3DDevice->TestCooperativeLevel() == D3DERR_DEVICELOST ) + { + Sleep( 100 ); + } + + hres = mD3DDevice->Reset( &d3dpp ); + } + + D3D9Assert( hres, "GFXD3D9Device::reset - Failed to create D3D Device!" ); + mInitialized = true; + + // Setup default states + initStates(); + + // Now re aquire all the resources we trashed earlier + reacquireDefaultPoolResources(); + + // Mark everything dirty and flush to card, for sanity. + updateStates(true); +} + +// +// Register this device with GFXInit +// +class GFXPCD3D9RegisterDevice +{ +public: + GFXPCD3D9RegisterDevice() + { + GFXInit::getRegisterDeviceSignal().notify(&GFXPCD3D9Device::enumerateAdapters); + } +}; + +static GFXPCD3D9RegisterDevice pPCD3D9RegisterDevice; + +//----------------------------------------------------------------------------- +/// Parse command line arguments for window creation +//----------------------------------------------------------------------------- +static void sgPCD3D9DeviceHandleCommandLine( S32 argc, const char **argv ) +{ + for (U32 i = 1; i < argc; i++) + { + String s(argv[i]); + if (s.equal("-nvperfhud", String::NoCase)) + { + GFXPCD3D9Device::mEnableNVPerfHUD = true; + break; + } + } +} + +// Register the command line parsing hook +static ProcessRegisterCommandLine sgCommandLine( sgPCD3D9DeviceHandleCommandLine ); diff --git a/gfx/D3D9/pc/gfxPCD3D9Device.h b/gfx/D3D9/pc/gfxPCD3D9Device.h new file mode 100644 index 0000000..9ad08a5 --- /dev/null +++ b/gfx/D3D9/pc/gfxPCD3D9Device.h @@ -0,0 +1,50 @@ + +#ifndef _GFX_PC_D3D9DEVICE_H_ +#define _GFX_PC_D3D9DEVICE_H_ + +#include "gfx/D3D9/gfxD3D9Device.h" + +class PlatformWindow; + +class GFXPCD3D9Device : public GFXD3D9Device +{ + typedef GFXD3D9Device Parent; + +public: + // Set to true to force nvperfhud device creation + static bool mEnableNVPerfHUD; + + GFXPCD3D9Device( LPDIRECT3D9 d3d, U32 index ) : GFXD3D9Device( d3d, index ) {}; + + static GFXDevice *createInstance( U32 adapterIndex ); + + virtual GFXFormat selectSupportedFormat(GFXTextureProfile *profile, + const Vector &formats, bool texture, bool mustblend, bool mustfilter); + + static void enumerateAdapters( Vector &adapterList ); + + virtual void enumerateVideoModes(); + + virtual GFXWindowTarget *allocWindowTarget(PlatformWindow *window); + virtual GFXTextureTarget *allocRenderToTextureTarget(); + virtual bool beginSceneInternal(); + + virtual void init( const GFXVideoMode &mode, PlatformWindow *window = NULL ); + + virtual void enterDebugEvent(ColorI color, const char *name); + virtual void leaveDebugEvent(); + virtual void setDebugMarker(ColorI color, const char *name); + + virtual void setMatrix( GFXMatrixType mtype, const MatrixF &mat ); + + virtual void initStates(); + virtual void reset( D3DPRESENT_PARAMETERS &d3dpp ); + virtual D3DPRESENT_PARAMETERS setupPresentParams( const GFXVideoMode &mode, const HWND &hwnd ) const; +protected: + static GFXAdapter::CreateDeviceInstanceDelegate mCreateDeviceInstance; + + virtual void _setTextureStageState( U32 stage, U32 state, U32 value ); + void _validateMultisampleParams(D3DFORMAT format, D3DMULTISAMPLE_TYPE & aatype, DWORD & aalevel) const; +}; + +#endif \ No newline at end of file diff --git a/gfx/D3D9/pc/gfxPCD3D9Target.cpp b/gfx/D3D9/pc/gfxPCD3D9Target.cpp new file mode 100644 index 0000000..252a87d --- /dev/null +++ b/gfx/D3D9/pc/gfxPCD3D9Target.cpp @@ -0,0 +1,468 @@ +#ifdef _MSC_VER +#pragma warning(disable: 4996) // turn off "deprecation" warnings +#endif + +#include "gfx/D3D9/gfxD3D9Device.h" +#include "gfx/D3D9/gfxD3D9TextureObject.h" +#include "gfx/D3D9/gfxD3D9Cubemap.h" +#include "gfx/D3D9/gfxD3D9EnumTranslate.h" +#include "gfx/D3D9/pc/gfxPCD3D9Device.h" +#include "gfx/D3D9/pc/gfxPCD3D9Target.h" +#include "windowManager/win32/win32Window.h" + +GFXPCD3D9TextureTarget::GFXPCD3D9TextureTarget() + : mTargetSize( Point2I::Zero ), + mTargetFormat( GFXFormatR8G8B8A8 ) +{ + for(S32 i=0; i( GFX )->destroyD3DResource( mTargets[i] ); + mTargets[i] = NULL; + } + else + SAFE_RELEASE( mTargets[i] ); + } + + zombify(); +} + +void GFXPCD3D9TextureTarget::attachTexture( RenderSlot slot, GFXTextureObject *tex, U32 mipLevel/*=0*/, U32 zOffset /*= 0*/ ) +{ + AssertFatal(slot < MaxRenderSlotId, "GFXPCD3D9TextureTarget::attachTexture - out of range slot."); + + // Mark state as dirty so device can know to update. + invalidateState(); + + // Release what we had, it's definitely going to change. + static_cast( GFX )->destroyD3DResource( mTargets[slot] ); + mTargets[slot] = NULL; + mResolveTargets[slot] = NULL; + + if(slot == Color0) + { + mTargetSize = Point2I::Zero; + mTargetFormat = GFXFormatR8G8B8A8; + } + + // Are we clearing? + if(!tex) + { + // Yup - just exit, it'll stay NULL. + return; + } + + + // Take care of default targets + if( tex == GFXTextureTarget::sDefaultDepthStencil ) + { + mTargets[slot] = static_cast(mDevice)->mDeviceDepthStencil; + mTargets[slot]->AddRef(); + } + else + { + // Cast the texture object to D3D... + AssertFatal(dynamic_cast(tex), + "GFXPCD3D9TextureTarget::attachTexture - invalid texture object."); + + GFXD3D9TextureObject *d3dto = static_cast(tex); + + // Grab the surface level. + if( slot == DepthStencil ) + { + mTargets[slot] = d3dto->getSurface(); + if ( mTargets[slot] ) + mTargets[slot]->AddRef(); + } + else + { + // getSurface will almost always return NULL. It will only return non-NULL + // if the surface that it needs to render to is different than the mip level + // in the actual texture. This will happen with MSAA. + if( d3dto->getSurface() == NULL ) + { + D3D9Assert(d3dto->get2DTex()->GetSurfaceLevel(mipLevel, &mTargets[slot]), + "GFXPCD3D9TextureTarget::attachTexture - could not get surface level for the passed texture!"); + } + else + { + mTargets[slot] = d3dto->getSurface(); + mTargets[slot]->AddRef(); + + // Only assign resolve target if d3dto has a surface to give us. + // + // That usually means there is an MSAA target involved, which is why + // the resolve is needed to get the data out of the target. + mResolveTargets[slot] = d3dto; + + if ( tex && slot == Color0 ) + { + mTargetSize.set( tex->getSize().x, tex->getSize().y ); + mTargetFormat = tex->getFormat(); + } + } + } + + // Update surface size + if(slot == Color0) + { + IDirect3DSurface9 *surface = mTargets[Color0]; + if ( surface ) + { + D3DSURFACE_DESC sd; + surface->GetDesc(&sd); + mTargetSize = Point2I(sd.Width, sd.Height); + + S32 format = sd.Format; + GFXREVERSE_LOOKUP( GFXD3D9TextureFormat, GFXFormat, format ); + mTargetFormat = (GFXFormat)format; + } + } + } +} + +void GFXPCD3D9TextureTarget::attachTexture( RenderSlot slot, GFXCubemap *tex, U32 face, U32 mipLevel/*=0*/ ) +{ + AssertFatal(slot < MaxRenderSlotId, "GFXPCD3D9TextureTarget::attachTexture - out of range slot."); + + // Mark state as dirty so device can know to update. + invalidateState(); + + // Release what we had, it's definitely going to change. + static_cast( GFX )->destroyD3DResource( mTargets[slot] ); + mTargets[slot] = NULL; + mResolveTargets[slot] = NULL; + + // Cast the texture object to D3D... + AssertFatal(!tex || dynamic_cast(tex), + "GFXD3DTextureTarget::attachTexture - invalid cubemap object."); + + GFXD3D9Cubemap *cube = static_cast(tex); + + if(slot == Color0) + { + mTargetSize = Point2I::Zero; + mTargetFormat = GFXFormatR8G8B8A8; + } + + // Are we clearing? + if(!tex) + { + // Yup - just exit, it'll stay NULL. + return; + } + + D3D9Assert(cube->mCubeTex->GetCubeMapSurface( (D3DCUBEMAP_FACES)face, mipLevel, &mTargets[slot] ), + "GFXD3DTextureTarget::attachTexture - could not get surface level for the passed texture!"); + + // Update surface size + if(slot == Color0) + { + IDirect3DSurface9 *surface = mTargets[Color0]; + if ( surface ) + { + D3DSURFACE_DESC sd; + surface->GetDesc(&sd); + mTargetSize = Point2I(sd.Width, sd.Height); + + S32 format = sd.Format; + GFXREVERSE_LOOKUP( GFXD3D9TextureFormat, GFXFormat, format ); + mTargetFormat = (GFXFormat)format; + } + } +} + +void GFXPCD3D9TextureTarget::activate() +{ + const U32 &numSimultaneousRTs = mDevice->getNumRenderTargets(); + + LPDIRECT3DDEVICE9 d3dDevice = mDevice->getDevice(); + + // Clear the state indicator. + stateApplied(); + + // Set all the surfaces into the appropriate slots. + for(U32 i = 0; i < (Color4 - Color0); i++) + { + if ( i + GFXTextureTarget::Color0 <= numSimultaneousRTs ) + { + D3D9Assert(d3dDevice->SetRenderTarget(i, mTargets[GFXTextureTarget::Color0 + i]), + avar("GFXPCD3D9TextureTarget::activate() - failed to set slot %d for texture target!", i) ); + } + } + + // TODO: This is often the same shared depth buffer used by most + // render targets. Are we getting performance hit from setting it + // multiple times... aside from the function call? + + D3D9Assert(d3dDevice->SetDepthStencilSurface(mTargets[GFXTextureTarget::DepthStencil]), + "GFXPCD3D9TextureTarget::activate() - failed to set depthstencil target!" ); +} + +void GFXPCD3D9TextureTarget::deactivate() +{ + LPDIRECT3DDEVICE9 d3dDevice = mDevice->getDevice(); + + const U32 &numSimultaneousRTs = mDevice->getNumRenderTargets(); + + // Set NULL to all slots but Color0, start with 'i = 1' + for(U32 i = 1; i < (Color4 - Color0); i++) + { + if ( i < numSimultaneousRTs ) + { + D3D9Assert(d3dDevice->SetRenderTarget(i, NULL), + avar("GFXPCD3D9TextureTarget::activate() - failed to set slot %d for texture target!", i) ); + } + } +} + +void GFXPCD3D9TextureTarget::resolve() +{ + for (U32 i = 0; i < MaxRenderSlotId; i++) + { + // We use existance @ mResolveTargets as a flag that we need to copy + // data from the rendertarget into the texture. + if (mResolveTargets[i]) + { + IDirect3DSurface9 *surf; + mResolveTargets[i]->get2DTex()->GetSurfaceLevel( 0, &surf ); + AssertFatal(dynamic_cast(getOwningDevice()), "Incorrect device type!"); + GFXD3D9Device* d = static_cast(getOwningDevice()); + d->getDevice()->StretchRect(mTargets[i], NULL, surf, NULL, D3DTEXF_NONE ); + surf->Release(); + } + } +} + +void GFXPCD3D9TextureTarget::resolveTo( GFXTextureObject *tex ) +{ + if ( mTargets[Color0] == NULL ) + return; + + IDirect3DSurface9 *surf; + ((GFXD3D9TextureObject*)(tex))->get2DTex()->GetSurfaceLevel( 0, &surf ); + mDevice->getDevice()->StretchRect( mTargets[Color0], NULL, surf, NULL, D3DTEXF_NONE ); + + surf->Release(); +} + +void GFXPCD3D9TextureTarget::zombify() +{ + for(int i = 0; i < MaxRenderSlotId; i++) + attachTexture(RenderSlot(i), NULL); +} + +void GFXPCD3D9TextureTarget::resurrect() +{ + +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + +GFXPCD3D9WindowTarget::GFXPCD3D9WindowTarget() +{ + mSwapChain = NULL; + mDepthStencil = NULL; + mWindow = NULL; + mDevice = NULL; + mBackbuffer = NULL; + mImplicit = true; +} + +GFXPCD3D9WindowTarget::~GFXPCD3D9WindowTarget() +{ + SAFE_RELEASE(mSwapChain); + SAFE_RELEASE(mDepthStencil); + SAFE_RELEASE(mBackbuffer); +} + +void GFXPCD3D9WindowTarget::initPresentationParams() +{ + // Get some video mode related info. + GFXVideoMode vm = mWindow->getVideoMode(); + + // Do some validation... + if(vm.fullScreen == true && mImplicit == false) + { + AssertISV(false, + "GFXPCD3D9WindowTarget::initPresentationParams - Cannot go fullscreen with secondary window!"); + } + + Win32Window *win = dynamic_cast(mWindow); + AssertISV(win, "GFXPCD3D9WindowTarget::initPresentationParams() - got a non Win32Window window passed in! Did DX go crossplatform?"); + + HWND hwnd = win->getHWND(); + + // At some point, this will become GFXPCD3D9WindowTarget like trunk has, + // so this cast isn't as bad as it looks. ;) BTR + GFXPCD3D9Device* pcdevice = dynamic_cast(mDevice); + mPresentationParams = pcdevice->setupPresentParams(vm, hwnd); + + if (mImplicit) + { + pcdevice->mMultisampleType = mPresentationParams.MultiSampleType; + pcdevice->mMultisampleLevel = mPresentationParams.MultiSampleQuality; + } +} + +const Point2I GFXPCD3D9WindowTarget::getSize() +{ + return mWindow->getVideoMode().resolution; +} + +GFXFormat GFXPCD3D9WindowTarget::getFormat() +{ + S32 format = mPresentationParams.BackBufferFormat; + GFXREVERSE_LOOKUP( GFXD3D9TextureFormat, GFXFormat, format ); + return (GFXFormat)format; +} + +bool GFXPCD3D9WindowTarget::present() +{ + AssertFatal(mSwapChain, "GFXPCD3D9WindowTarget::present - no swap chain present to present!"); + HRESULT res = mSwapChain->Present(NULL, NULL, NULL, NULL, NULL); + + return (res == S_OK); +} + +void GFXPCD3D9WindowTarget::setImplicitSwapChain() +{ + AssertFatal(mImplicit, "Invalid swap chain type! Additional swap chains are created as needed"); + // Reacquire our swapchain & DS + if(!mSwapChain) + mDevice->getDevice()->GetSwapChain(0, &mSwapChain); + if(!mDepthStencil) + mDevice->getDevice()->GetDepthStencilSurface(&mDepthStencil); + if (!mBackbuffer) + mSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &mBackbuffer); +} + +void GFXPCD3D9WindowTarget::createAdditionalSwapChain() +{ + AssertFatal(!mImplicit, "Invalid swap chain type! Implicit swap chains use the device"); + + // Since we're not going to do a device reset for an additional swap + // chain, we can just release our resources and regrab them. + SAFE_RELEASE(mSwapChain); + SAFE_RELEASE(mDepthStencil); + SAFE_RELEASE(mBackbuffer); + + // If there's a fullscreen window active, don't try to create these additional swap chains. + // CodeReview, we need to store the window target with the implicit swap chain better, this line below + // could fail if the current render target isn't what we expect. + GFXPCD3D9WindowTarget* currTarget = dynamic_cast(mDevice->getActiveRenderTarget()); + if (currTarget && currTarget->getWindow()->getVideoMode().fullScreen) + return; + + // Setup our presentation params. + initPresentationParams(); + + // Create our resources! + D3D9Assert(mDevice->getDevice()->CreateAdditionalSwapChain(&mPresentationParams, &mSwapChain), + "GFXPCD3D9WindowTarget::createAdditionalSwapChain - couldn't reallocate additional swap chain!"); + D3D9Assert(mDevice->getDevice()->CreateDepthStencilSurface(mPresentationParams.BackBufferWidth, mPresentationParams.BackBufferHeight, + D3DFMT_D24S8, mPresentationParams.MultiSampleType, mPresentationParams.MultiSampleQuality, false, &mDepthStencil, NULL), + "GFXPCD3D9WindowTarget::createAdditionalSwapChain: Unable to create stencil/depth surface"); + D3D9Assert(mSwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &mBackbuffer), + "GFXPCD3D9WindowTarget::createAdditionalSwapChain: Unable to get backbuffer!"); +} + +void GFXPCD3D9WindowTarget::resetMode() +{ + mWindow->setSuppressReset(true); + + if (mSwapChain) + { + D3DPRESENT_PARAMETERS pp; + mSwapChain->GetPresentParameters(&pp); + bool ppFullscreen = !pp.Windowed; + Point2I backbufferSize(pp.BackBufferWidth, pp.BackBufferHeight); + + if ((backbufferSize == getSize()) && (ppFullscreen == mWindow->getVideoMode().fullScreen)) + return; + } + + // So, the video mode has changed - if we're an additional swap chain + // just kill the swapchain and reallocate to match new vid mode. + if(mImplicit == false) + { + createAdditionalSwapChain(); + } + else + { + // Setup our presentation params. + initPresentationParams(); + + // Otherwise, we have to reset the device, if we're the implicit swapchain. + mDevice->reset(mPresentationParams); + } + + // Update our size, too. + mSize = Point2I(mPresentationParams.BackBufferWidth, mPresentationParams.BackBufferHeight); + + mWindow->setSuppressReset(false); +} + +void GFXPCD3D9WindowTarget::zombify() +{ + // Release our resources + SAFE_RELEASE(mSwapChain); + SAFE_RELEASE(mDepthStencil); + SAFE_RELEASE(mBackbuffer); +} + +void GFXPCD3D9WindowTarget::resurrect() +{ + if(mImplicit) + { + setImplicitSwapChain(); + } + else if(!mSwapChain) + { + createAdditionalSwapChain(); + } +} + +void GFXPCD3D9WindowTarget::activate() +{ + LPDIRECT3DDEVICE9 d3dDevice = mDevice->getDevice(); + d3dDevice->SetRenderTarget(0, mBackbuffer); + d3dDevice->SetDepthStencilSurface(mDepthStencil); + + D3DPRESENT_PARAMETERS pp; + + mSwapChain->GetPresentParameters(&pp); + + // Update our video mode here, too. + GFXVideoMode vm; + vm = mWindow->getVideoMode(); + vm.resolution.x = pp.BackBufferWidth; + vm.resolution.y = pp.BackBufferHeight; + vm.fullScreen = !pp.Windowed; + + mSize = vm.resolution; +} + +void GFXPCD3D9WindowTarget::resolveTo( GFXTextureObject *tex ) +{ + IDirect3DSurface9 *surf; + ((GFXD3D9TextureObject*)(tex))->get2DTex()->GetSurfaceLevel( 0, &surf ); + mDevice->getDevice()->StretchRect( mBackbuffer, NULL, surf, NULL, D3DTEXF_NONE ); + + surf->Release(); +} \ No newline at end of file diff --git a/gfx/D3D9/pc/gfxPCD3D9Target.h b/gfx/D3D9/pc/gfxPCD3D9Target.h new file mode 100644 index 0000000..b2cd11c --- /dev/null +++ b/gfx/D3D9/pc/gfxPCD3D9Target.h @@ -0,0 +1,98 @@ +#ifndef _GFX_D3D_GFXD3D9TARGET_H_ +#define _GFX_D3D_GFXD3D9TARGET_H_ + +#include "gfx/gfxTarget.h" + +struct IDirect3DSurface9; +struct IDirect3DSwapChain9; + +class GFXPCD3D9TextureTarget : public GFXTextureTarget +{ + friend class GFXPCD3D9Device; + + // Array of target surfaces, this is given to us by attachTexture + IDirect3DSurface9 * mTargets[MaxRenderSlotId]; + + // Array of texture objects which correspond to the target surfaces above, + // needed for copy from RenderTarget to texture situations. Current only valid in those situations + GFXD3D9TextureObject* mResolveTargets[MaxRenderSlotId]; + + /// Owning d3d device. + GFXD3D9Device *mDevice; + + Point2I mTargetSize; + + GFXFormat mTargetFormat; + +public: + + GFXPCD3D9TextureTarget(); + ~GFXPCD3D9TextureTarget(); + + // Public interface. + virtual const Point2I getSize() { return mTargetSize; } + virtual GFXFormat getFormat() { return mTargetFormat; } + virtual void attachTexture(RenderSlot slot, GFXTextureObject *tex, U32 mipLevel=0, U32 zOffset = 0); + virtual void attachTexture(RenderSlot slot, GFXCubemap *tex, U32 face, U32 mipLevel=0); + virtual void resolve(); + + /// Note we always copy the Color0 RenderSlot. + virtual void resolveTo( GFXTextureObject *tex ); + + virtual void activate(); + virtual void deactivate(); + + void zombify(); + void resurrect(); +}; + +class GFXPCD3D9WindowTarget : public GFXWindowTarget +{ + friend class GFXPCD3D9Device; + + /// Our depth stencil buffer, if any. + IDirect3DSurface9 *mDepthStencil; + + /// Our backbuffer + IDirect3DSurface9 *mBackbuffer; + + /// Maximum size we can render to. + Point2I mSize; + + /// Our swap chain, potentially the implicit device swap chain. + IDirect3DSwapChain9 *mSwapChain; + + /// D3D presentation info. + D3DPRESENT_PARAMETERS mPresentationParams; + + /// Owning d3d device. + GFXD3D9Device *mDevice; + + /// Is this the implicit swap chain? + bool mImplicit; + + /// Internal interface that notifies us we need to reset our video mode. + void resetMode(); + +public: + + GFXPCD3D9WindowTarget(); + ~GFXPCD3D9WindowTarget(); + + virtual const Point2I getSize(); + virtual GFXFormat getFormat(); + virtual bool present(); + + void initPresentationParams(); + void setImplicitSwapChain(); + void createAdditionalSwapChain(); + + virtual void activate(); + + void zombify(); + void resurrect(); + + virtual void resolveTo( GFXTextureObject *tex ); +}; + +#endif diff --git a/gfx/D3D9/platformD3D.h b/gfx/D3D9/platformD3D.h new file mode 100644 index 0000000..ef0f336 --- /dev/null +++ b/gfx/D3D9/platformD3D.h @@ -0,0 +1,6 @@ +#ifdef TORQUE_OS_XENON +# include "platformXbox/platformXbox.h" +#else +# include +# include +#endif \ No newline at end of file diff --git a/gfx/Null/gfxNullDevice.cpp b/gfx/Null/gfxNullDevice.cpp new file mode 100644 index 0000000..9aa3be5 --- /dev/null +++ b/gfx/Null/gfxNullDevice.cpp @@ -0,0 +1,315 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/Null/gfxNullDevice.h" + +#include "core/strings/stringFunctions.h" +#include "gfx/gfxCubemap.h" +#include "gfx/screenshot.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxCardProfile.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/bitmap/gBitmap.h" +#include "core/util/safeDelete.h" + + +GFXAdapter::CreateDeviceInstanceDelegate GFXNullDevice::mCreateDeviceInstance(GFXNullDevice::createInstance); + +class GFXNullCardProfiler: public GFXCardProfiler +{ +private: + typedef GFXCardProfiler Parent; +public: + + /// + virtual const String &getRendererString() const { static String sRS("GFX Null Device Renderer"); return sRS; } + +protected: + + virtual void setupCardCapabilities() { }; + + virtual bool _queryCardCap(const String &query, U32 &foundResult){ return false; } + virtual bool _queryFormat(const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips) { inOutAutogenMips = false; return false; } + +public: + virtual void init() + { + mCardDescription = "GFX Null Device Card"; + mChipSet = "NULL Device"; + mVersionString = "0"; + + Parent::init(); // other code notes that not calling this is "BAD". + }; +}; + +class GFXNullTextureObject : public GFXTextureObject +{ +public: + GFXNullTextureObject(GFXDevice * aDevice, GFXTextureProfile *profile); + ~GFXNullTextureObject() { kill(); }; + + virtual void pureVirtualCrash() { }; + + virtual GFXLockedRect * lock( U32 mipLevel = 0, RectI *inRect = NULL ) { return NULL; }; + virtual void unlock( U32 mipLevel = 0) {}; + virtual bool copyToBmp(GBitmap *) { return false; }; + + virtual void zombify() {} + virtual void resurrect() {} +}; + +GFXNullTextureObject::GFXNullTextureObject(GFXDevice * aDevice, GFXTextureProfile *profile) : + GFXTextureObject(aDevice, profile) +{ + mProfile = profile; + mTextureSize.set( 0, 0, 0 ); +} + +class GFXNullTextureManager : public GFXTextureManager +{ +protected: + virtual GFXTextureObject *_createTextureObject( U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips = false, + S32 antialiasLevel = 0, + GFXTextureObject *inTex = NULL ) + { + GFXNullTextureObject *retTex; + if ( inTex ) + { + AssertFatal( dynamic_cast( inTex ), "GFXNullTextureManager::_createTexture() - Bad inTex type!" ); + retTex = static_cast( inTex ); + } + else + { + retTex = new GFXNullTextureObject( GFX, profile ); + retTex->registerResourceWithDevice( GFX ); + } + + SAFE_DELETE( retTex->mBitmap ); + retTex->mBitmap = new GBitmap(width, height); + return retTex; + }; + + /// Load a texture from a proper DDSFile instance. + virtual bool _loadTexture(GFXTextureObject *texture, DDSFile *dds){ return true; }; + + /// Load data into a texture from a GBitmap using the internal API. + virtual bool _loadTexture(GFXTextureObject *texture, GBitmap *bmp){ return true; }; + + /// Load data into a texture from a raw buffer using the internal API. + /// + /// Note that the size of the buffer is assumed from the parameters used + /// for this GFXTextureObject's _createTexture call. + virtual bool _loadTexture(GFXTextureObject *texture, void *raw){ return true; }; + + /// Refresh a texture using the internal API. + virtual bool _refreshTexture(GFXTextureObject *texture){ return true; }; + + /// Free a texture (but do not delete the GFXTextureObject) using the internal + /// API. + /// + /// This is only called during zombification for textures which need it, so you + /// don't need to do any internal safety checks. + virtual bool _freeTexture(GFXTextureObject *texture, bool zombify=false) { return true; }; + + virtual U32 _getTotalVideoMemory() { return 0; }; + virtual U32 _getFreeVideoMemory() { return 0; }; +}; + +class GFXNullCubemap : public GFXCubemap +{ + friend class GFXDevice; +private: + // should only be called by GFXDevice + virtual void setToTexUnit( U32 tuNum ) { }; + +public: + virtual void initStatic( GFXTexHandle *faces ) { }; + virtual void initDynamic( U32 texSize, GFXFormat faceFormat = GFXFormatR8G8B8A8 ) { }; + virtual U32 getSize() const { return 0; } + virtual GFXFormat getFormat() const { return GFXFormatR8G8B8A8; } + + virtual ~GFXNullCubemap(){}; + + virtual void zombify() {} + virtual void resurrect() {} +}; + +class GFXNullVertexBuffer : public GFXVertexBuffer +{ + unsigned char* tempBuf; +public: + GFXNullVertexBuffer(GFXDevice *device, U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertexSize, GFXBufferType bufferType) : + GFXVertexBuffer(device, numVerts, vertexFormat, vertexSize, bufferType) { }; + virtual void lock(U32 vertexStart, U32 vertexEnd, void **vertexPtr); + virtual void unlock(); + virtual void prepare(); + + virtual void zombify() {} + virtual void resurrect() {} +}; + +void GFXNullVertexBuffer::lock(U32 vertexStart, U32 vertexEnd, void **vertexPtr) +{ + tempBuf = new unsigned char[(vertexEnd - vertexStart) * mVertexSize]; + *vertexPtr = (void*) tempBuf; + lockedVertexStart = vertexStart; + lockedVertexEnd = vertexEnd; +} + +void GFXNullVertexBuffer::unlock() +{ + delete[] tempBuf; + tempBuf = NULL; +} + +void GFXNullVertexBuffer::prepare() +{ +} + +class GFXNullPrimitiveBuffer : public GFXPrimitiveBuffer +{ +private: + U16* temp; +public: + GFXNullPrimitiveBuffer(GFXDevice *device, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType) : + GFXPrimitiveBuffer(device, indexCount, primitiveCount, bufferType) {}; + + virtual void lock(U16 indexStart, U16 indexEnd, U16 **indexPtr); ///< locks this primitive buffer for writing into + virtual void unlock(); ///< unlocks this primitive buffer. + virtual void prepare() { }; ///< prepares this primitive buffer for use on the device it was allocated on + + virtual void zombify() {} + virtual void resurrect() {} +}; + +void GFXNullPrimitiveBuffer::lock(U16 indexStart, U16 indexEnd, U16 **indexPtr) +{ + temp = new U16[indexEnd - indexStart]; + *indexPtr = temp; +} + +void GFXNullPrimitiveBuffer::unlock() +{ + delete[] temp; + temp = NULL; +} + +// +// GFXNullStateBlock +// +class GFXNullStateBlock : public GFXStateBlock +{ +public: + /// Returns the hash value of the desc that created this block + virtual U32 getHashValue() const { return 0; }; + + /// Returns a GFXStateBlockDesc that this block represents + virtual const GFXStateBlockDesc& getDesc() const { return mDefaultDesc; } + + // + // GFXResource + // + virtual void zombify() { } + /// When called the resource should restore all device sensitive information destroyed by zombify() + virtual void resurrect() { } +private: + GFXStateBlockDesc mDefaultDesc; +}; + +// +// GFXNullDevice +// + +GFXDevice *GFXNullDevice::createInstance( U32 adapterIndex ) +{ + return new GFXNullDevice(); +} + +GFXNullDevice::GFXNullDevice() +{ + clip.set(0, 0, 800, 800); + + mTextureManager = new GFXNullTextureManager(); + gScreenShot = new ScreenShot(); + mCardProfiler = new GFXNullCardProfiler(); + mCardProfiler->init(); +} + +GFXNullDevice::~GFXNullDevice() +{ + delete mTextureManager; + mTextureManager = NULL; +} + +GFXVertexBuffer *GFXNullDevice::allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, GFXBufferType bufferType ) +{ + return new GFXNullVertexBuffer(GFX, numVerts, vertexFormat, vertSize, bufferType); +} + +GFXPrimitiveBuffer *GFXNullDevice::allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ) +{ + return new GFXNullPrimitiveBuffer(GFX, numIndices, numPrimitives, bufferType); +} + +GFXCubemap* GFXNullDevice::createCubemap() +{ + return new GFXNullCubemap(); +}; + +void GFXNullDevice::enumerateAdapters( Vector &adapterList ) +{ + // Add the NULL renderer + GFXAdapter *toAdd = new GFXAdapter(); + + toAdd->mIndex = 0; + toAdd->mType = NullDevice; + toAdd->mCreateDeviceInstanceDelegate = mCreateDeviceInstance; + + GFXVideoMode vm; + vm.bitDepth = 32; + vm.resolution.set(800,600); + toAdd->mAvailableModes.push_back(vm); + + dStrcpy(toAdd->mName, "GFX Null Device"); + + adapterList.push_back(toAdd); +} + +void GFXNullDevice::setLightInternal(U32 lightStage, const GFXLightInfo light, bool lightEnable) +{ + +} + +void GFXNullDevice::init( const GFXVideoMode &mode, PlatformWindow *window ) +{ + mCardProfiler = new GFXNullCardProfiler(); + mCardProfiler->init(); +} + +GFXStateBlockRef GFXNullDevice::createStateBlockInternal(const GFXStateBlockDesc& desc) +{ + return new GFXNullStateBlock(); +} + +// +// Register this device with GFXInit +// +class GFXNullRegisterDevice +{ +public: + GFXNullRegisterDevice() + { + GFXInit::getRegisterDeviceSignal().notify(&GFXNullDevice::enumerateAdapters); + } +}; + +static GFXNullRegisterDevice pNullRegisterDevice; \ No newline at end of file diff --git a/gfx/Null/gfxNullDevice.h b/gfx/Null/gfxNullDevice.h new file mode 100644 index 0000000..e81d784 --- /dev/null +++ b/gfx/Null/gfxNullDevice.h @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXNullDevice_H_ +#define _GFXNullDevice_H_ + +#include "platform/platform.h" + +//----------------------------------------------------------------------------- + +#include "gfx/gfxDevice.h" +#include "gfx/gfxInit.h" +#include "gfx/gfxFence.h" + +class GFXNullWindowTarget : public GFXWindowTarget +{ +public: + virtual bool present() + { + return true; + } + + virtual const Point2I getSize() + { + // Return something stupid. + return Point2I(1,1); + } + + virtual GFXFormat getFormat() { return GFXFormatR8G8B8A8; } + + virtual void resetMode() + { + + } + + virtual void zombify() {}; + virtual void resurrect() {}; + +}; + +class GFXNullDevice : public GFXDevice +{ +public: + GFXNullDevice(); + virtual ~GFXNullDevice(); + + static GFXDevice *createInstance( U32 adapterIndex ); + + static void enumerateAdapters( Vector &adapterList ); + + void init( const GFXVideoMode &mode, PlatformWindow *window = NULL ); + + virtual void activate() { }; + virtual void deactivate() { }; + virtual GFXAdapterType getAdapterType() { return NullDevice; }; + + /// @name Debug Methods + /// @{ + virtual void enterDebugEvent(ColorI color, const char *name) { }; + virtual void leaveDebugEvent() { }; + virtual void setDebugMarker(ColorI color, const char *name) { }; + /// @} + + /// Enumerates the supported video modes of the device + virtual void enumerateVideoModes() { }; + + /// Sets the video mode for the device + virtual void setVideoMode( const GFXVideoMode &mode ) { }; +protected: + static GFXAdapter::CreateDeviceInstanceDelegate mCreateDeviceInstance; + + /// Called by GFXDevice to create a device specific stateblock + virtual GFXStateBlockRef createStateBlockInternal(const GFXStateBlockDesc& desc); + /// Called by GFXDevice to actually set a stateblock. + virtual void setStateBlockInternal(GFXStateBlock* block, bool force) { }; + /// @} + + /// Called by base GFXDevice to actually set a const buffer + virtual void setShaderConstBufferInternal(GFXShaderConstBuffer* buffer) { }; + + virtual void setTextureInternal(U32 textureUnit, const GFXTextureObject*texture) { }; + + virtual void setLightInternal(U32 lightStage, const GFXLightInfo light, bool lightEnable); + virtual void setLightMaterialInternal(const GFXLightMaterial mat) { }; + virtual void setGlobalAmbientInternal(ColorF color) { }; + + /// @name State Initalization. + /// @{ + + /// State initalization. This MUST BE CALLED in setVideoMode after the device + /// is created. + virtual void initStates() { }; + + virtual void setMatrix( GFXMatrixType mtype, const MatrixF &mat ) { }; + + virtual GFXVertexBuffer *allocVertexBuffer( U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize, GFXBufferType bufferType ); + virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ); + +public: + virtual GFXCubemap * createCubemap(); + + virtual F32 getFillConventionOffset() const { return 0.0f; }; + + ///@} + + virtual GFXTextureTarget *allocRenderToTextureTarget(){return NULL;}; + virtual GFXWindowTarget *allocWindowTarget(PlatformWindow *window) + { + return new GFXNullWindowTarget(); + }; + + virtual void _updateRenderTargets(){}; + + virtual F32 getPixelShaderVersion() const { return 0.0f; }; + virtual void setPixelShaderVersion( F32 version ) { }; + virtual U32 getNumSamplers() const { return 0; }; + virtual U32 getNumRenderTargets() const { return 0; }; + + virtual GFXShader* createShader() { return NULL; }; + + + virtual void clear( U32 flags, ColorI color, F32 z, U32 stencil ) { }; + virtual bool beginSceneInternal() { return true; }; + virtual void endSceneInternal() { }; + + virtual void drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ) { }; + virtual void drawIndexedPrimitive( GFXPrimitiveType primType, + U32 startVertex, + U32 minIndex, + U32 numVerts, + U32 startIndex, + U32 primitiveCount ) { }; + + virtual void setClipRect( const RectI &rect ) { }; + virtual const RectI &getClipRect() const { return clip; }; + + virtual void preDestroy() { Parent::preDestroy(); }; + + virtual U32 getMaxDynamicVerts() { return 16384; }; + virtual U32 getMaxDynamicIndices() { return 16384; }; + + virtual GFXFormat selectSupportedFormat( GFXTextureProfile *profile, + const Vector &formats, + bool texture, + bool mustblend, + bool mustfilter ) { return GFXFormatR8G8B8A8; }; + + GFXFence *createFence() { return new GFXGeneralFence( this ); } + GFXOcclusionQuery* createOcclusionQuery() { return NULL; } + +private: + typedef GFXDevice Parent; + RectI clip; +}; + +#endif diff --git a/gfx/bitmap/bitmapUtils.cpp b/gfx/bitmap/bitmapUtils.cpp new file mode 100644 index 0000000..d04a31c --- /dev/null +++ b/gfx/bitmap/bitmapUtils.cpp @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/bitmap/bitmapUtils.h" + +#include "platform/platform.h" + + +void bitmapExtrude5551_c(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) +{ + const U16 *src = (const U16 *) srcMip; + U16 *dst = (U16 *) mip; + U32 stride = srcHeight != 1 ? srcWidth : 0; + + U32 width = srcWidth >> 1; + U32 height = srcHeight >> 1; + if (width == 0) width = 1; + if (height == 0) height = 1; + + if (srcWidth != 1) + { + for(U32 y = 0; y < height; y++) + { + for(U32 x = 0; x < width; x++) + { + U32 a = src[0]; + U32 b = src[1]; + U32 c = src[stride]; + U32 d = src[stride+1]; +#if defined(TORQUE_BIG_ENDIAN) + dst[x] = ((( (a >> 10) + (b >> 10) + (c >> 10) + (d >> 10)) >> 2) << 10) | + ((( ((a >> 5) & 0x1F) + ((b >> 5) & 0x1F) + ((c >> 5) & 0x1F) + ((d >> 5) & 0x1F)) >> 2) << 5) | + ((( ((a >> 0) & 0x1F) + ((b >> 0) & 0x1F) + ((c >> 0) & 0x1F) + ((d >> 0) & 0x1F)) >> 2) << 0); +#else + dst[x] = ((( (a >> 11) + (b >> 11) + (c >> 11) + (d >> 11)) >> 2) << 11) | + ((( ((a >> 6) & 0x1F) + ((b >> 6) & 0x1F) + ((c >> 6) & 0x1F) + ((d >> 6) & 0x1F)) >> 2) << 6) | + ((( ((a >> 1) & 0x1F) + ((b >> 1) & 0x1F) + ((c >> 1) & 0x1F) + ((d >> 1) & 0x1F)) >> 2) << 1); +#endif + src += 2; + } + src += stride; + dst += width; + } + } + else + { + for(U32 y = 0; y < height; y++) + { + U32 a = src[0]; + U32 c = src[stride]; +#if defined(TORQUE_OS_MAC) + dst[y] = ((( (a >> 10) + (c >> 10)) >> 1) << 10) | + ((( ((a >> 5) & 0x1F) + ((c >> 5) & 0x1f)) >> 1) << 5) | + ((( ((a >> 0) & 0x1F) + ((c >> 0) & 0x1f)) >> 1) << 0); +#else + dst[y] = ((( (a >> 11) + (c >> 11)) >> 1) << 11) | + ((( ((a >> 6) & 0x1f) + ((c >> 6) & 0x1f)) >> 1) << 6) | + ((( ((a >> 1) & 0x1F) + ((c >> 1) & 0x1f)) >> 1) << 1); +#endif + src += 1 + stride; + } + } +} + + +//-------------------------------------------------------------------------- +void bitmapExtrudeRGB_c(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) +{ + const U8 *src = (const U8 *) srcMip; + U8 *dst = (U8 *) mip; + U32 stride = srcHeight != 1 ? (srcWidth) * 3 : 0; + + U32 width = srcWidth >> 1; + U32 height = srcHeight >> 1; + if (width == 0) width = 1; + if (height == 0) height = 1; + + if (srcWidth != 1) + { + for(U32 y = 0; y < height; y++) + { + for(U32 x = 0; x < width; x++) + { + *dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3]) + 2) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3]) + 2) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3]) + 2) >> 2; + src += 4; + } + src += stride; // skip + } + } + else + { + for(U32 y = 0; y < height; y++) + { + *dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1; + src += 4; + + src += stride; // skip + } + } +} + +//-------------------------------------------------------------------------- +void bitmapExtrudeRGBA_c(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) +{ + const U8 *src = (const U8 *) srcMip; + U8 *dst = (U8 *) mip; + U32 stride = srcHeight != 1 ? (srcWidth) * 4 : 0; + + U32 width = srcWidth >> 1; + U32 height = srcHeight >> 1; + if (width == 0) width = 1; + if (height == 0) height = 1; + + if (srcWidth != 1) + { + for(U32 y = 0; y < height; y++) + { + for(U32 x = 0; x < width; x++) + { + *dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2; + src++; + *dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2; + src += 5; + } + src += stride; // skip + } + } + else + { + for(U32 y = 0; y < height; y++) + { + *dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1; + src++; + *dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1; + src += 5; + + src += stride; // skip + } + } +} + +void (*bitmapExtrude5551)(const void *srcMip, void *mip, U32 height, U32 width) = bitmapExtrude5551_c; +void (*bitmapExtrudeRGB)(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) = bitmapExtrudeRGB_c; +void (*bitmapExtrudeRGBA)(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) = bitmapExtrudeRGBA_c; + + +//-------------------------------------------------------------------------- + +void bitmapConvertRGB_to_1555_c(U8 *src, U32 pixels) +{ + U16 *dst = (U16 *)src; + for(U32 j = 0; j < pixels; j++) + { + U32 r = src[0] >> 3; + U32 g = src[1] >> 3; + U32 b = src[2] >> 3; + +#if defined(TORQUE_OS_MAC) + *dst++ = 0x8000 | (b << 10) | (g << 5) | (r << 0); +#else + *dst++ = b | (g << 5) | (r << 10) | 0x8000; +#endif + src += 3; + } +} + +void (*bitmapConvertRGB_to_1555)(U8 *src, U32 pixels) = bitmapConvertRGB_to_1555_c; + +//------------------------------------------------------------------------------ + +void bitmapConvertRGB_to_5551_c(U8 *src, U32 pixels) +{ + U16 *dst = (U16 *)src; + for(U32 j = 0; j < pixels; j++) + { + U32 r = src[0] >> 3; + U32 g = src[1] >> 3; + U32 b = src[2] >> 3; + +#if defined(TORQUE_OS_MAC) + *dst++ = (1 << 15) | (b << 10) | (g << 5) | (r << 0); +#else + *dst++ = (b << 1) | (g << 6) | (r << 11) | 1; +#endif + src += 3; + } +} + + + +void (*bitmapConvertRGB_to_5551)(U8 *src, U32 pixels) = bitmapConvertRGB_to_5551_c; + +//------------------------------------------------------------------------------ + +void bitmapConvertRGB_to_RGBX_c( U8 **src, U32 pixels ) +{ + const U8 *oldBits = *src; + U8 *newBits = new U8[pixels * 4]; + dMemset( newBits, 0xFF, pixels * 4 ); // This is done to set alpha values -patw + + // Copy the bits over to the new memory + for( U32 i = 0; i < pixels; i++ ) + dMemcpy( &newBits[i * 4], &oldBits[i * 3], sizeof(U8) * 3 ); + + // Now hose the old bits + delete [] *src; + *src = newBits; +} + +void (*bitmapConvertRGB_to_RGBX)( U8 **src, U32 pixels ) = bitmapConvertRGB_to_RGBX_c; + +//------------------------------------------------------------------------------ + +void bitmapConvertRGBX_to_RGB_c( U8 **src, U32 pixels ) +{ + const U8 *oldBits = *src; + U8 *newBits = new U8[pixels * 3]; + + // Copy the bits over to the new memory + for( U32 i = 0; i < pixels; i++ ) + dMemcpy( &newBits[i * 3], &oldBits[i * 4], sizeof(U8) * 3 ); + + // Now hose the old bits + delete [] *src; + *src = newBits; +} + +void (*bitmapConvertRGBX_to_RGB)( U8 **src, U32 pixels ) = bitmapConvertRGBX_to_RGB_c; + +//------------------------------------------------------------------------------ + +void bitmapConvertA8_to_RGBA_c( U8 **src, U32 pixels ) +{ + const U8 *oldBits = *src; + U8 *newBits = new U8[pixels * 4]; + + // Zero new bits + dMemset( newBits, 0, pixels * 4 ); + + // Copy Alpha values + for( U32 i = 0; i < pixels; i++ ) + newBits[i * 4 + 3] = oldBits[i]; + + // Now hose the old bits + delete [] *src; + *src = newBits; +} + +void (*bitmapConvertA8_to_RGBA)( U8 **src, U32 pixels ) = bitmapConvertA8_to_RGBA_c; diff --git a/gfx/bitmap/bitmapUtils.h b/gfx/bitmap/bitmapUtils.h new file mode 100644 index 0000000..1c61373 --- /dev/null +++ b/gfx/bitmap/bitmapUtils.h @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BITMAPUTILS_H_ +#define _BITMAPUTILS_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +extern void (*bitmapExtrude5551)(const void *srcMip, void *mip, U32 height, U32 width); +extern void (*bitmapExtrudeRGB)(const void *srcMip, void *mip, U32 height, U32 width); +extern void (*bitmapExtrudeRGBA)(const void *srcMip, void *mip, U32 height, U32 width); +extern void (*bitmapConvertRGB_to_5551)(U8 *src, U32 pixels); +extern void (*bitmapConvertRGB_to_1555)(U8 *src, U32 pixels); +extern void (*bitmapConvertRGB_to_RGBX)( U8 **src, U32 pixels ); +extern void (*bitmapConvertRGBX_to_RGB)( U8 **src, U32 pixels ); +extern void (*bitmapConvertA8_to_RGBA)( U8 **src, U32 pixels ); + +void bitmapExtrudeRGB_c(const void *srcMip, void *mip, U32 height, U32 width); + +#endif //_BITMAPUTILS_H_ diff --git a/gfx/bitmap/ddsFile.h b/gfx/bitmap/ddsFile.h new file mode 100644 index 0000000..a10ba7b --- /dev/null +++ b/gfx/bitmap/ddsFile.h @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DDSFILE_H_ +#define _DDSFILE_H_ + +#ifndef _GFXSTRUCTS_H_ +#include "gfx/gfxStructs.h" +#endif +#ifndef _BITSET_H_ +#include "core/bitSet.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif + +class Stream; +class GBitmap; + + +struct DDSFile +{ + enum DDSFlags + { + ComplexFlag = BIT(0), ///< Indicates this includes a mipchain, cubemap, or + /// volume texture, ie, isn't a plain old bitmap. + MipMapsFlag = BIT(1), ///< Indicates we have a mipmap chain in the file. + CubeMapFlag = BIT(2), ///< Indicates we are a cubemap. Requires all six faces. + VolumeFlag = BIT(3), ///< Indicates we are a volume texture. + + PitchSizeFlag = BIT(4), ///< Cue as to how to interpret our pitchlinear value. + LinearSizeFlag = BIT(5), ///< Cue as to how to interpret our pitchlinear value. + + RGBData = BIT(6), ///< Indicates that this is straight out RGBA data. + CompressedData = BIT(7), ///< Indicates that this is compressed or otherwise + /// exotic data. + }; + + BitSet32 mFlags; + U32 mHeight; + U32 mWidth; + U32 mDepth; + U32 mPitchOrLinearSize; + U32 mMipMapCount; + + GFXFormat mFormat; + U32 mBytesPerPixel; ///< Ignored if we're a compressed texture. + U32 mFourCC; + String mCacheString; + Torque::Path mSourcePath; + + bool mHasTransparency; + + struct SurfaceData + { + SurfaceData() + { + VECTOR_SET_ASSOCIATION( mMips ); + } + + ~SurfaceData() + { + // Free our mips! + for(S32 i=0; i mMips; + + // Helper function to read in a mipchain. + bool readMipChain(); + + void dumpImage(DDSFile *dds, U32 mip, const char *file); + + /// Helper for reading a mip level. + void readNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel); + + /// Helper for writing a mip level. + void writeNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel); + }; + + Vector mSurfaces; + + /// Clear all our information; used before reading. + void clear(); + + /// Reads a DDS file from the stream. + bool read(Stream &s); + + /// Called from read() to read in the DDS header. + bool readHeader(Stream &s); + + /// Writes this DDS file to the stream. + bool write(Stream &s); + + /// Called from write() to write the DDS header. + bool writeHeader(Stream &s); + + /// For our current format etc., what is the size of a surface with the + /// given dimensions? + U32 getSurfaceSize( U32 mipLevel = 0 ) const { return getSurfaceSize( mHeight, mWidth, mipLevel ); } + U32 getSurfaceSize( U32 height, U32 width, U32 mipLevel = 0 ) const; + + /// Returns the total video memory size of the texture + /// including all mipmaps and compression settings. + U32 getSizeInBytes() const; + + U32 getWidth( U32 mipLevel = 0 ) const { return getMax( U32(1), mWidth >> mipLevel ); } + U32 getHeight( U32 mipLevel = 0 ) const { return getMax(U32(1), mHeight >> mipLevel); } + U32 getDepth( U32 mipLevel = 0 ) const { return getMax(U32(1), mDepth >> mipLevel); } + + bool getHasTransparency() const { return mHasTransparency; } + + U32 getPitch( U32 mipLevel = 0 ) const; + + const Torque::Path &getSourcePath() const { return mSourcePath; } + const String &getTextureCacheString() const { return mCacheString; } + + static Resource load( const Torque::Path &path ); + + // For debugging fun! + static S32 smActiveCopies; + + DDSFile() + { + VECTOR_SET_ASSOCIATION( mSurfaces ); + smActiveCopies++; + + mHasTransparency = false; + } + + DDSFile( const DDSFile &dds ); + + ~DDSFile() + { + smActiveCopies--; + + // Free our surfaces! + for(S32 i=0; imMips.size(); m++ ) + { + U32 size = dds.getSurfaceSize( m ); + surface->mMips.push_back(new U8[size]); + dMemcpy( surface->mMips.last(), dds.mSurfaces[i]->mMips[m], size ); + } + } +} + +void DDSFile::clear() +{ + mFlags = 0; + mHeight = mWidth = mDepth = mPitchOrLinearSize = mMipMapCount = 0; + mFormat = GFXFormatR8G8B8; +} + +U32 DDSFile::getPitch( U32 mipLevel ) const +{ + if(mFlags.test(CompressedData)) + { + U32 sizeMultiple = 0; + + switch(mFormat) + { + case GFXFormatDXT1: + sizeMultiple = 8; + break; + case GFXFormatDXT2: + case GFXFormatDXT3: + case GFXFormatDXT4: + case GFXFormatDXT5: + sizeMultiple = 16; + break; + default: + AssertISV(false, "DDSFile::getPitch - invalid compressed texture format, we only support DXT1-5 right now."); + break; + } + + // Maybe need to be DWORD aligned? + U32 align = getMax(U32(1), getWidth(mipLevel)/4) * sizeMultiple;; + align += 3; align >>=2; align <<=2; + return align; + + } + else + return getWidth(mipLevel) * mBytesPerPixel; +} + +U32 DDSFile::getSurfaceSize( U32 height, U32 width, U32 mipLevel ) const +{ + // Bump by the mip level. + height = getMax(U32(1), height >> mipLevel); + width = getMax(U32(1), width >> mipLevel); + + if(mFlags.test(CompressedData)) + { + // From the directX docs: + // max(1, width ÷ 4) x max(1, height ÷ 4) x 8(DXT1) or 16(DXT2-5) + + U32 sizeMultiple = 0; + + switch(mFormat) + { + case GFXFormatDXT1: + sizeMultiple = 8; + break; + case GFXFormatDXT2: + case GFXFormatDXT3: + case GFXFormatDXT4: + case GFXFormatDXT5: + sizeMultiple = 16; + break; + default: + AssertISV(false, "DDSFile::getSurfaceSize - invalid compressed texture format, we only support DXT1-5 right now."); + break; + } + + return getMax(U32(1), width/4) * getMax(U32(1), height/4) * sizeMultiple; + } + else + { + return height * width* mBytesPerPixel; + } +} + +U32 DDSFile::getSizeInBytes() const +{ + // TODO: This doesn't take mDepth into account, so + // it doesn't work right for volume textures! + + U32 bytes = 0; + for ( U32 i=0; i < mMipMapCount; i++ ) + bytes += getSurfaceSize( mHeight, mWidth, i ); + + return bytes; +} + +bool DDSFile::readHeader(Stream &s) +{ + U32 tmp; + + // Read the FOURCC + s.read(&tmp); + + if(tmp != MakeFourCC('D', 'D', 'S', ' ')) + { + Con::errorf("DDSFile::readHeader - unexpected magic number, wanted 'DDS '!"); + return false; + } + + // Read the size of the header. + s.read(&tmp); + + if(tmp != 124) + { + Con::errorf("DDSFile::readHeader - incorrect header size. Expected 124 bytes."); + return false; + } + + // Read some flags... + U32 ddsdFlags; + s.read(&ddsdFlags); + + // "Always include DDSD_CAPS, DDSD_PIXELFORMAT, DDSD_WIDTH, DDSD_HEIGHT." + if(!(ddsdFlags & (DDSDCaps | DDSDPixelFormat | DDSDWidth | DDSDHeight))) + { + Con::errorf("DDSFile::readHeader - incorrect surface description flags."); + return false; + } + + // Read height and width (always present) + s.read(&mHeight); + s.read(&mWidth); + + // Read pitch or linear size. + + // First make sure we have valid flags (either linear size or pitch). + if((ddsdFlags & (DDSDLinearSize | DDSDPitch)) == (DDSDLinearSize | DDSDPitch)) + { + // Both are invalid! + Con::errorf("DDSFile::readHeader - encountered both DDSD_LINEARSIZE and DDSD_PITCH!"); + return false; + } + + // Ok, some flags are set, so let's do some reading. + s.read(&mPitchOrLinearSize); + + if(ddsdFlags & DDSDLinearSize) + { + mFlags.set(LinearSizeFlag); + } + else if (ddsdFlags & DDSDPitch) + { + mFlags.set(PitchSizeFlag); + } + else + { + // Neither set! This appears to be depressingly common. + // Con::warnf("DDSFile::readHeader - encountered neither DDSD_LINEARSIZE nor DDSD_PITCH!"); + } + + // Do we need to read depth? If so, we are a volume texture! + s.read(&mDepth); + + if(ddsdFlags & DDSDDepth) + { + mFlags.set(VolumeFlag); + } + else + { + // Wipe it if the flag wasn't set! + mDepth = 0; + } + + // Deal with mips! + s.read(&mMipMapCount); + + if(ddsdFlags & DDSDMipMapCount) + { + mFlags.set(MipMapsFlag); + } + else + { + // Wipe it if the flag wasn't set! + mMipMapCount = 1; + } + + // Deal with 11 DWORDS of reserved space (this reserved space brought to + // you by DirectDraw and the letters F and U). + for(U32 i=0; i<11; i++) + s.read(&tmp); + + // Now we're onto the pixel format! + s.read(&tmp); + + if(tmp != 32) + { + Con::errorf("DDSFile::readHeader - pixel format chunk has unexpected size!"); + return false; + } + + U32 ddpfFlags; + + s.read(&ddpfFlags); + + // Read the next few values so we can deal with them all in one go. + U32 pfFourCC, pfBitCount, pfRMask, pfGMask, pfBMask, pfAlphaMask; + + s.read(&pfFourCC); + s.read(&pfBitCount); + s.read(&pfRMask); + s.read(&pfGMask); + s.read(&pfBMask); + s.read(&pfAlphaMask); + + // Sanity check flags... + if(!(ddpfFlags & (DDPFRGB | DDPFFourCC))) + { + Con::errorf("DDSFile::readHeader - incoherent pixel flags, neither RGB nor FourCC!"); + return false; + } + + // For now let's just dump the header info. + if(ddpfFlags & DDPFRGB) + { + mFlags.set(RGBData); + + //Con::printf("RGB Pixel format of DDS:"); + //Con::printf(" bitcount = %d (16, 24, 32)", pfBitCount); + mBytesPerPixel = pfBitCount / 8; + //Con::printf(" red mask = %x", pfRMask); + //Con::printf(" green mask = %x", pfGMask); + //Con::printf(" blue mask = %x", pfBMask); + + bool hasAlpha = false; + + if(ddpfFlags & DDPFAlphaPixels) + { + hasAlpha = true; + //Con::printf(" alpha mask = %x", pfAlphaMask); + } + else + { + //Con::printf(" no alpha."); + } + + mHasTransparency = hasAlpha; + + // Try to match a format. + if(hasAlpha) + { + // If it has alpha it is one of... + // GFXFormatR8G8B8A8 + // GFXFormatR5G5B5A1 + // GFXFormatA8 + + if(pfBitCount == 32) + mFormat = GFXFormatR8G8B8A8; + else if(pfBitCount == 16) + mFormat = GFXFormatR5G5B5A1; + else if(pfBitCount == 8) + mFormat = GFXFormatA8; + else + { + Con::errorf("DDSFile::readHeader - unable to match alpha RGB format!"); + return false; + } + } + else + { + // Otherwise it is one of... + // GFXFormatR8G8B8 + // GFXFormatR8G8B8X8 + // GFXFormatR5G6B5 + // GFXFormatL8 + + if(pfBitCount == 24) + mFormat = GFXFormatR8G8B8; + else if(pfBitCount == 32) + mFormat = GFXFormatR8G8B8X8; + else if(pfBitCount == 16) + mFormat = GFXFormatR5G6B5; + else if(pfBitCount == 8) + { + // luminance + mFormat = GFXFormatL8; + } + else + { + Con::errorf("DDSFile::readHeader - unable to match non-alpha RGB format!"); + return false; + } + } + + + // Sweet, all done. + } + else if (ddpfFlags & DDPFFourCC) + { + mFlags.set(CompressedData); + +/* Con::printf("FourCC Pixel format of DDS:"); + Con::printf(" fourcc = '%c%c%c%c'", ((U8*)&pfFourCC)[0], ((U8*)&pfFourCC)[1], ((U8*)&pfFourCC)[2], ((U8*)&pfFourCC)[3]); */ + + // Ok, make a format determination. + switch(pfFourCC) + { + case FOURCC_DXT1: + mFormat = GFXFormatDXT1; + break; + case FOURCC_DXT2: + mFormat = GFXFormatDXT2; + break; + case FOURCC_DXT3: + mFormat = GFXFormatDXT3; + break; + case FOURCC_DXT4: + mFormat = GFXFormatDXT4; + break; + case FOURCC_DXT5: + mFormat = GFXFormatDXT5; + break; + default: + Con::errorf("DDSFile::readHeader - unknown fourcc = '%c%c%c%c'", ((U8*)&pfFourCC)[0], ((U8*)&pfFourCC)[1], ((U8*)&pfFourCC)[2], ((U8*)&pfFourCC)[3]); + break; + } + + } + + // Deal with final caps bits... Is this really necessary? + + U32 caps1, caps2; + s.read(&caps1); + s.read(&caps2); + s.read(&tmp); + s.read(&tmp); // More icky reserved space. + + // Screw caps1. + // if(!(caps1 & DDSCAPS_TEXTURE))) + // { + // } + + // Caps2 has cubemap/volume info. Care about that. + if(caps2 & DDSCAPS2Cubemap) + { + mFlags.set(CubeMapFlag); + } + + // MS has ANOTHER reserved word here. This one particularly sucks. + s.read(&tmp); + + return true; +} + +bool DDSFile::read(Stream &s) +{ + if(!readHeader(s)) + { + Con::errorf("DDSFile::read - error reading header!"); + return false; + } + + // At this point we know what sort of image we contain. So we should + // allocate some buffers, and read it in. + + // How many surfaces are we talking about? + if(mFlags.test(CubeMapFlag)) + { + // Do something with cubemaps. + } + else if (mFlags.test(VolumeFlag)) + { + // Do something with volume + } + else + { + // It's a plain old texture. + + // First allocate a SurfaceData to stick this in. + mSurfaces.push_back(new SurfaceData()); + + // Load the main image. + mSurfaces.last()->readNextMip(this, s, mHeight, mWidth, 0); + + // Load however many mips there are. + for(S32 i=1; ireadNextMip(this, s, mHeight, mWidth, i); + + // Ok, we're done. + } + + return true; +} + +bool DDSFile::writeHeader( Stream &s ) +{ + // Read the FOURCC + s.write( 4, "DDS " ); + + U32 tmp = 0; + + // Read the size of the header. + s.write( 124 ); + + // Read some flags... + U32 ddsdFlags = DDSDCaps | DDSDPixelFormat | DDSDWidth | DDSDHeight; + + if ( mFlags.test( CompressedData ) ) + ddsdFlags |= DDSDLinearSize; + else + ddsdFlags |= DDSDPitch; + + if ( mMipMapCount > 0 ) + ddsdFlags |= DDSDMipMapCount; + + s.write( ddsdFlags ); + + // Read height and width (always present) + s.write( mHeight ); + s.write( mWidth ); + + // Read pitch or linear size. + + // Ok, some flags are set, so let's do some reading. + s.write( mPitchOrLinearSize ); + + // Do we need to read depth? If so, we are a volume texture! + s.write( mDepth ); + + // Deal with mips! + s.write( mMipMapCount ); + + // Deal with 11 DWORDS of reserved space (this reserved space brought to + // you by DirectDraw and the letters F and U). + for(U32 i=0; i<11; i++) + s.write( tmp ); // is this right? + + // Now we're onto the pixel format! + + // This is the size, in bits, + // of the pixel format data. + tmp = 32; + s.write( tmp ); + + U32 ddpfFlags; + + U32 fourCC = 0; + + if ( mFlags.test( CompressedData ) ) + { + ddpfFlags = DDPFFourCC; + if (mFormat == GFXFormatDXT1) + fourCC = FOURCC_DXT1; + if (mFormat == GFXFormatDXT3) + fourCC = FOURCC_DXT3; + if (mFormat == GFXFormatDXT5) + fourCC = FOURCC_DXT5; + } + else + ddpfFlags = mBytesPerPixel == 4 ? DDPFRGB | DDPFAlphaPixels : DDPFRGB; + + s.write( ddpfFlags ); + + // Read the next few values so we can deal with them all in one go. + //U32 pfFourCC, pfBitCount, pfRMask, pfGMask, pfBMask, pfAlphaMask; + + s.write( fourCC ); + s.write( mBytesPerPixel * 8 ); + s.write( 0x000000FF ); + s.write( 0x00FF0000 ); + s.write( 0x0000FF00 ); + s.write( 0xFF000000 ); + + // Deal with final caps bits... Is this really necessary? + + U32 caps1 = DDSCAPSTexture; + if ( mMipMapCount > 0 ) + caps1 |= DDSCAPSComplex | DDSCAPSMipMap; + + tmp = 0; + + s.write( caps1 ); + s.write( tmp ); + s.write( tmp ); + s.write( tmp );// More icky reserved space. + + // MS has ANOTHER reserved word here. This one particularly sucks. + s.write( tmp ); + + return true; +} + +bool DDSFile::write( Stream &s ) +{ + if(!writeHeader(s)) + { + Con::errorf("DDSFile::write - error writing header!"); + return false; + } + + // At this point we know what sort of image we contain. So we should + // allocate some buffers, and read it in. + + // How many surfaces are we talking about? + if(mFlags.test(CubeMapFlag)) + { + // Do something with cubemaps. + } + else if (mFlags.test(VolumeFlag)) + { + // Do something with volume + } + else + { + // It's a plain old texture. + + // Load however many mips there are. + for ( S32 i = 0; i < mMipMapCount; i++ ) + mSurfaces.last()->writeNextMip(this, s, mHeight, mWidth, i); + + // Ok, we're done. + } + + return true; +} + +void DDSFile::SurfaceData::dumpImage(DDSFile *dds, U32 mip, const char *file) +{ + GBitmap *foo = new GBitmap(dds->mWidth >> mip, dds->mHeight >> mip, false, dds->mFormat); + + // Copy our data in. + dMemcpy(foo->getWritableBits(), mMips[mip], dds->getSurfaceSize(dds->mHeight, dds->mWidth, mip) ); + + FileStream stream; + + stream.open( file, Torque::FS::File::Write ); + + if ( stream.getStatus() == Stream::Ok ) + { + // Write it out. + foo->writeBitmap("png", stream); + } + + // Clean up. + delete foo; +} + +void DDSFile::SurfaceData::readNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel) +{ + // First, advance the stream to the next DWORD (ie, 4 byte aligned) + /*U32 align = s.getPosition(); + align += 3; align >>=2; align <<=2; + s.setPosition(align); */ + + U32 size = dds->getSurfaceSize(height, width, mipLevel); + mMips.push_back(new U8[size]); + if(!s.read(size, mMips.last())) + Con::errorf("DDSFile::SurfaceData::addNextMip - failed to read mip!"); + + // If it's not compressed, let's dump this mip... + //if(!dds->mFlags.test(CompressedData)) + // dumpImage(dds, mipLevel, avar("%xmip%d", this, mipLevel)); +} + +void DDSFile::SurfaceData::writeNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel) +{ + U32 size = dds->getSurfaceSize(height, width, mipLevel); + if(!s.write(size, mMips[mipLevel])) + Con::errorf("DDSFile::SurfaceData::writeNextMip - failed to write mip!"); +} + +//------------------------------------------------------------------------------ + +template<> void *Resource::create( const Torque::Path &path ) +{ +#ifdef TORQUE_DEBUG_RES_MANAGER + Con::printf( "Resource::create - [%s]", path.getFullPath().c_str() ); +#endif + + FileStream stream; + + stream.open( path.getFullPath(), Torque::FS::File::Read ); + + if ( stream.getStatus() != Stream::Ok ) + return NULL; + + DDSFile *retDDS = new DDSFile; + + if( !retDDS->read( stream ) ) + { + delete retDDS; + return NULL; + } + else + { + // Set source file name + retDDS->mSourcePath = path; + + retDDS->mCacheString = Torque::Path::Join( path.getRoot(), ':', path.getPath() ); + retDDS->mCacheString = Torque::Path::Join( retDDS->mCacheString, '/', path.getFileName() ); + } + + return retDDS; +} + +template<> ResourceBase::Signature Resource::signature() +{ + return MakeFourCC('D','D','S',' '); // Direct Draw Surface +} + +Resource DDSFile::load( const Torque::Path &path ) +{ + Resource ret = ResourceManager::get().load( path ); + + // Any kind of error checking or path stepping can happen here + + return ret; +} + +//------------------------------------------------------------------------------ + +DDSFile *DDSFile::createDDSFileFromGBitmap( const GBitmap *gbmp ) +{ + if( gbmp == NULL ) + return NULL; + + DDSFile *ret = new DDSFile; + + // Set up the DDSFile properties that matter. Since this is a GBitmap, there + // are assumptions that can be made + ret->mHeight = gbmp->getHeight(); + ret->mWidth = gbmp->getWidth(); + ret->mDepth = 0; + ret->mFormat = gbmp->getFormat(); + ret->mFlags.set(RGBData); + ret->mBytesPerPixel = gbmp->getBytesPerPixel(); + ret->mMipMapCount = gbmp->getNumMipLevels(); + ret->mHasTransparency = gbmp->getHasTransparency(); + + // ASSUMPTION!!! + // This _most likely_ does not belong here, but it is safe to assume that if + // a GBitmap is 24-bit, and it's being converted to a DDS, it is most likely + // going to be either: + // a) Uploaded as a 32-bit texture, and just needs to be padded to RGBX + // b) Uploaded as a compressed format, and needs to be padded to 32-bits anyway + if( ret->mFormat == GFXFormatR8G8B8 ) + { + ret->mFormat = GFXFormatR8G8B8X8; + ret->mBytesPerPixel = 4; + } + + if( ret->mMipMapCount > 1 ) + ret->mFlags.set(MipMapsFlag); + + // One surface per GBitmap + ret->mSurfaces.push_back( new SurfaceData() ); + + // Load the mips + for( int i = 0; i < ret->mMipMapCount; i++ ) + { + const U32 mipSz = ret->getSurfaceSize(i); + ret->mSurfaces.last()->mMips.push_back( new U8[mipSz] ); + + U8 *mipMem = ret->mSurfaces.last()->mMips.last(); + + // If this is a straight copy, just do it, otherwise (ugh) + if( ret->mFormat == gbmp->getFormat() ) + dMemcpy( mipMem, gbmp->getBits(i), mipSz ); + else + { + // Assumption: + AssertFatal( gbmp->getBytesPerPixel() + 1 == ret->mBytesPerPixel, "Assumption failed, not 24->32 bit straight convert." ); + + for( int pxl = 0; pxl < gbmp->getWidth(i) * gbmp->getHeight(i); pxl++ ) + { + U8 *dst = &mipMem[pxl * ret->mBytesPerPixel]; + const U8 *src = &gbmp->getBits(i)[pxl * gbmp->getBytesPerPixel()]; + dMemcpy( dst, src, gbmp->getBytesPerPixel() * sizeof(U8) ); + dst[ret->mBytesPerPixel - 1] = 255; + } + } + + // Uncomment to debug-dump each mip level + //ret->mSurfaces.last()->dumpImage( ret, i, avar( "%d_Gbmp_xmip%d", ret, i ) ); + } + + return ret; +} + +//------------------------------------------------------------------------------ + +ConsoleFunction(getActiveDDSFiles, S32, 1, 1, "() - Returns active DDSes in memory!") +{ + return DDSFile::smActiveCopies; +} \ No newline at end of file diff --git a/gfx/bitmap/ddsUtils.cpp b/gfx/bitmap/ddsUtils.cpp new file mode 100644 index 0000000..52dcbf0 --- /dev/null +++ b/gfx/bitmap/ddsUtils.cpp @@ -0,0 +1,94 @@ +#include "squish/squish.h" +#include "gfx/bitmap/ddsFile.h" +#include "gfx/bitmap/ddsUtils.h" + +//------------------------------------------------------------------------------ + +// If false is returned, from this method, the source DDS is not modified +bool DDSUtil::squishDDS( DDSFile *srcDDS, const GFXFormat dxtFormat ) +{ + // Sanity check + if( srcDDS->mBytesPerPixel != 4 ) + { + AssertFatal( false, "Squish wants 32-bit source data" ); + return false; + } + + // Build flags, start with fast compress + U32 squishFlags = squish::kColourRangeFit; + + // Flag which format we are using + switch( dxtFormat ) + { + case GFXFormatDXT1: + squishFlags |= squish::kDxt1; + break; + + case GFXFormatDXT2: + case GFXFormatDXT3: + squishFlags |= squish::kDxt3; + break; + + case GFXFormatDXT4: + case GFXFormatDXT5: + squishFlags |= squish::kDxt5; + break; + + default: + AssertFatal( false, "Assumption failed" ); + return false; + break; + } + + // We got this far, so assume we can finish (gosh I hope so) + srcDDS->mFormat = dxtFormat; + srcDDS->mFlags.set( DDSFile::CompressedData ); + + // If this has alpha, set the flag + if( srcDDS->mFormat == GFXFormatR8G8B8A8 ) + squishFlags |= squish::kWeightColourByAlpha; + + // The source surface is the original surface of the file + DDSFile::SurfaceData *srcSurface = srcDDS->mSurfaces.last(); + + // Create a new surface, this will be the DXT compressed surface. Once we + // are done, we can discard the old surface, and replace it with this one. + DDSFile::SurfaceData *newSurface = new DDSFile::SurfaceData(); + + for( int i = 0; i < srcDDS->mMipMapCount; i++ ) + { + const U8 *srcBits = srcSurface->mMips[i]; + + const U32 mipSz = srcDDS->getSurfaceSize(i); + U8 *dstBits = new U8[mipSz]; + newSurface->mMips.push_back( dstBits ); + + PROFILE_START(SQUISH_DXT_COMPRESS); + + // Compress with Squish + // + // squish::CompressImageOMP will call squish::CompressImage if OpenMP is + // not enabled. + squish::CompressImageOMP( srcBits, srcDDS->getWidth(i), srcDDS->getHeight(i), + dstBits, squishFlags ); + + PROFILE_END(); + } + + // Now delete the source surface, and return. + srcDDS->mSurfaces.pop_back(); + delete srcSurface; + srcDDS->mSurfaces.push_back( newSurface ); + + return true; +} + +//------------------------------------------------------------------------------ + +void DDSUtil::swizzleDDS( DDSFile *srcDDS, const Swizzle &swizzle ) +{ + for( int i = 0; i < srcDDS->mMipMapCount; i++ ) + { + swizzle.InPlace( srcDDS->mSurfaces.last()->mMips[i], srcDDS->getSurfaceSize( i ) ); + } +} \ No newline at end of file diff --git a/gfx/bitmap/ddsUtils.h b/gfx/bitmap/ddsUtils.h new file mode 100644 index 0000000..2d7fdcd --- /dev/null +++ b/gfx/bitmap/ddsUtils.h @@ -0,0 +1,12 @@ +#ifndef _DDS_UTILS_H_ +#define _DDS_UTILS_H_ + +struct DDSFile; + +namespace DDSUtil +{ + bool squishDDS( DDSFile *srcDDS, const GFXFormat dxtFormat ); + void swizzleDDS( DDSFile *srcDDS, const Swizzle &swizzle ); +}; + +#endif \ No newline at end of file diff --git a/gfx/bitmap/gBitmap.cpp b/gfx/bitmap/gBitmap.cpp new file mode 100644 index 0000000..a9ec039 --- /dev/null +++ b/gfx/bitmap/gBitmap.cpp @@ -0,0 +1,1126 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/resourceManager.h" +#include "core/stream/fileStream.h" +#include "core/strings/stringFunctions.h" +#include "core/color.h" + +#include "gfx/bitmap/gBitmap.h" +#include "gfx/bitmap/bitmapUtils.h" + +#include "math/mRect.h" + +#include "console/console.h" + +const U32 GBitmap::csFileVersion = 3; + +Vector GBitmap::sRegistrations( __FILE__, __LINE__ ); + + +GBitmap::GBitmap() + : mInternalFormat(GFXFormatR8G8B8), + mBits(NULL), + mByteSize(0), + mWidth(0), + mHeight(0), + mBytesPerPixel(0), + mNumMipLevels(0), + mHasTransparency(false) +{ + for (U32 i = 0; i < c_maxMipLevels; i++) + mMipLevelOffsets[i] = 0xffffffff; +} + +GBitmap::GBitmap(const GBitmap& rCopy) +{ + mInternalFormat = rCopy.mInternalFormat; + + mByteSize = rCopy.mByteSize; + mBits = new U8[mByteSize]; + dMemcpy(mBits, rCopy.mBits, mByteSize); + + mWidth = rCopy.mWidth; + mHeight = rCopy.mHeight; + mBytesPerPixel = rCopy.mBytesPerPixel; + mNumMipLevels = rCopy.mNumMipLevels; + dMemcpy(mMipLevelOffsets, rCopy.mMipLevelOffsets, sizeof(mMipLevelOffsets)); + + mHasTransparency = rCopy.mHasTransparency; +} + + +GBitmap::GBitmap(const U32 in_width, + const U32 in_height, + const bool in_extrudeMipLevels, + const GFXFormat in_format) + : mBits(NULL), + mByteSize(0) +{ + for (U32 i = 0; i < c_maxMipLevels; i++) + mMipLevelOffsets[i] = 0xffffffff; + + allocateBitmap(in_width, in_height, in_extrudeMipLevels, in_format); + + mHasTransparency = false; +} + +GBitmap::GBitmap(const U32 in_width, + const U32 in_height, + const U8* data ) + : mBits(NULL), + mByteSize(0) +{ + allocateBitmap(in_width, in_height, false, GFXFormatR8G8B8A8); + + mHasTransparency = false; + + for (U32 x = 0; x < in_width; x++) + { + for (U32 y = 0; y < in_height; y++) + { + U32 offset = (x + y * in_width) * 4; + + ColorI color(data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3]); + + if (color.alpha < 255) + mHasTransparency = true; + + setColor(x, y, color); + } + } +} + + +//-------------------------------------------------------------------------- + +GBitmap::~GBitmap() +{ + deleteImage(); +} + +//-------------------------------------------------------------------------- + +void GBitmap::sRegisterFormat( const GBitmap::Registration ® ) +{ + sRegistrations.push_back( reg ); +} + +const GBitmap::Registration *GBitmap::sFindRegInfo( const String &extension ) +{ + for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ ) + { + const GBitmap::Registration ® = GBitmap::sRegistrations[i]; + const Vector &extensions = reg.extensions; + + for ( U32 j = 0; j < extensions.size(); ++j ) + { + if ( extensions[j].equal( extension, String::NoCase ) ) + return ® + } + } + + return NULL; +} + +bool GBitmap::sFindFiles( const Path &path, Vector *outFoundPaths ) +{ + Path tryPath( path ); + + for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ ) + { + const GBitmap::Registration ® = GBitmap::sRegistrations[i]; + const Vector &extensions = reg.extensions; + + for ( U32 j = 0; j < extensions.size(); ++j ) + { + tryPath.setExtension( extensions[j] ); + + if ( Torque::FS::IsFile( tryPath ) ) + { + if ( outFoundPaths ) + outFoundPaths->push_back( tryPath ); + else + return true; + } + } + } + + return outFoundPaths ? outFoundPaths->size() > 0 : false; +} + +String GBitmap::sGetExtensionList() +{ + String list; + + for ( U32 i = 0; i < sRegistrations.size(); i++ ) + { + const Registration ® = sRegistrations[i]; + for ( U32 j = 0; j < reg.extensions.size(); j++ ) + { + list += reg.extensions[j]; + list += " "; + } + } + + return list; +} + +//-------------------------------------------------------------------------- +void GBitmap::deleteImage() +{ + delete [] mBits; + mBits = NULL; + mByteSize = 0; + + mWidth = 0; + mHeight = 0; + mNumMipLevels = 0; +} + + +//-------------------------------------------------------------------------- + +void GBitmap::copyRect(const GBitmap *src, const RectI &srcRect, const Point2I &dstPt) +{ + if(src->getFormat() != getFormat()) + return; + if(srcRect.extent.x + srcRect.point.x > src->getWidth() || srcRect.extent.y + srcRect.point.y > src->getHeight()) + return; + if(srcRect.extent.x + dstPt.x > getWidth() || srcRect.extent.y + dstPt.y > getHeight()) + return; + + for(U32 i = 0; i < srcRect.extent.y; i++) + { + dMemcpy(getAddress(dstPt.x, dstPt.y + i), + src->getAddress(srcRect.point.x, srcRect.point.y + i), + mBytesPerPixel * srcRect.extent.x); + } +} + +//-------------------------------------------------------------------------- +void GBitmap::allocateBitmap(const U32 in_width, const U32 in_height, const bool in_extrudeMipLevels, const GFXFormat in_format ) +{ + //-------------------------------------- Some debug checks... + U32 svByteSize = mByteSize; + U8 *svBits = mBits; + + AssertFatal(in_width != 0 && in_height != 0, "GBitmap::allocateBitmap: width or height is 0"); + + if (in_extrudeMipLevels == true) + { + AssertFatal(isPow2(in_width) == true && isPow2(in_height) == true, "GBitmap::GBitmap: in order to extrude mip levels, bitmap w/h must be pow2"); + } + + mInternalFormat = in_format; + mWidth = in_width; + mHeight = in_height; + + mBytesPerPixel = 1; + switch (mInternalFormat) + { + case GFXFormatA8: + case GFXFormatL8: mBytesPerPixel = 1; + break; + case GFXFormatR8G8B8: mBytesPerPixel = 3; + break; + case GFXFormatR8G8B8X8: + case GFXFormatR8G8B8A8: mBytesPerPixel = 4; + break; + case GFXFormatR5G6B5: + case GFXFormatR5G5B5A1: mBytesPerPixel = 2; + break; + default: + AssertFatal(false, "GBitmap::GBitmap: misunderstood format specifier"); + break; + } + + // Set up the mip levels, if necessary... + mNumMipLevels = 1; + U32 allocPixels = in_width * in_height * mBytesPerPixel; + mMipLevelOffsets[0] = 0; + + + if (in_extrudeMipLevels == true) + { + U32 currWidth = in_width; + U32 currHeight = in_height; + + do + { + mMipLevelOffsets[mNumMipLevels] = mMipLevelOffsets[mNumMipLevels - 1] + + (currWidth * currHeight * mBytesPerPixel); + currWidth >>= 1; + currHeight >>= 1; + if (currWidth == 0) currWidth = 1; + if (currHeight == 0) currHeight = 1; + + mNumMipLevels++; + allocPixels += currWidth * currHeight * mBytesPerPixel; + } while (currWidth != 1 || currHeight != 1); + } + AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); + + // Set up the memory... + mByteSize = allocPixels; + mBits = new U8[mByteSize]; + + dMemset(mBits, 0xFF, mByteSize); + + if(svBits != NULL) + { + dMemcpy(mBits, svBits, getMin(mByteSize, svByteSize)); + delete[] svBits; + } +} + +//-------------------------------------------------------------------------- +void GBitmap::extrudeMipLevels(bool clearBorders) +{ + if(mNumMipLevels == 1) + allocateBitmap(getWidth(), getHeight(), true, getFormat()); + + switch (getFormat()) + { + case GFXFormatR5G5B5A1: + { + for(U32 i = 1; i < mNumMipLevels; i++) + bitmapExtrude5551(getBits(i - 1), getWritableBits(i), getHeight(i), getWidth(i)); + break; + } + + case GFXFormatR8G8B8: + { + for(U32 i = 1; i < mNumMipLevels; i++) + bitmapExtrudeRGB(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); + break; + } + + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + { + for(U32 i = 1; i < mNumMipLevels; i++) + bitmapExtrudeRGBA(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); + break; + } + + default: + break; + } + if (clearBorders) + { + for (U32 i = 1; i> shift; + AssertFatal(newVal <= 255, "Error, oob"); + pMipBits[j] = U8(newVal); + } + } + AssertFatal(getWidth(mNumMipLevels - 1) == 1 && getHeight(mNumMipLevels - 1) == 1, + "Error, last miplevel should be 1x1!"); + ((U8*)getWritableBits(mNumMipLevels - 1))[0] = 0x80; + ((U8*)getWritableBits(mNumMipLevels - 1))[1] = 0x80; + ((U8*)getWritableBits(mNumMipLevels - 1))[2] = 0x80; +} + +//-------------------------------------------------------------------------- +bool GBitmap::setFormat(GFXFormat fmt) +{ + if (getFormat() == fmt) + return true; + + // this is a nasty pointer math hack + // is there a quick way to calc pixels of a fully mipped bitmap? + U32 pixels = 0; + for (U32 i=0; i < mNumMipLevels; i++) + pixels += getHeight(i) * getWidth(i); + + switch( getFormat() ) + { + case GFXFormatR8G8B8: + switch ( fmt ) + { + case GFXFormatR5G5B5A1: +#ifdef _XBOX + bitmapConvertRGB_to_1555(mBits, pixels); +#else + bitmapConvertRGB_to_5551(mBits, pixels); +#endif + mInternalFormat = GFXFormatR5G5B5A1; + mBytesPerPixel = 2; + break; + + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + // Took this out, it may crash -patw + //AssertFatal( mNumMipLevels == 1, "Do the mip-mapping in hardware." ); + + bitmapConvertRGB_to_RGBX( &mBits, pixels ); + mInternalFormat = fmt; + mBytesPerPixel = 4; + mByteSize = pixels * 4; + break; + + default: + AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); + return false; + } + break; + + case GFXFormatR8G8B8X8: + switch( fmt ) + { + // No change needed for this + case GFXFormatR8G8B8A8: + mInternalFormat = GFXFormatR8G8B8A8; + break; + + case GFXFormatR8G8B8: + bitmapConvertRGBX_to_RGB( &mBits, pixels ); + mInternalFormat = GFXFormatR8G8B8; + mBytesPerPixel = 3; + mByteSize = pixels * 3; + break; + + default: + AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); + return false; + } + break; + + case GFXFormatR8G8B8A8: + switch( fmt ) + { + // No change needed for this + case GFXFormatR8G8B8X8: + mInternalFormat = GFXFormatR8G8B8X8; + break; + + case GFXFormatR8G8B8: + bitmapConvertRGBX_to_RGB( &mBits, pixels ); + mInternalFormat = GFXFormatR8G8B8; + mBytesPerPixel = 3; + mByteSize = pixels * 3; + break; + + default: + AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); + return false; + } + break; + + case GFXFormatA8: + switch( fmt ) + { + case GFXFormatR8G8B8A8: + mInternalFormat = GFXFormatR8G8B8A8; + bitmapConvertA8_to_RGBA( &mBits, pixels ); + mBytesPerPixel = 4; + mByteSize = pixels * 4; + break; + + default: + AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); + return false; + } + break; + + default: + AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); + return false; + } + + U32 offset = 0; + for (U32 j=0; j < mNumMipLevels; j++) + { + mMipLevelOffsets[j] = offset; + offset += getHeight(j) * getWidth(j) * mBytesPerPixel; + } + + return true; +} + +//------------------------------------------------------------------------------ + +bool GBitmap::checkForTransparency() +{ + mHasTransparency = false; + + ColorI pixel(255, 255, 255, 255); + + switch (mInternalFormat) + { + // Non-transparent formats + case GFXFormatL8: + case GFXFormatR8G8B8: + case GFXFormatR5G6B5: + break; + // Transparent formats + case GFXFormatA8: + case GFXFormatR8G8B8A8: + case GFXFormatR5G5B5A1: + // Let getColor() do the heavy lifting + for (U32 x = 0; x < mWidth; x++) + { + for (U32 y = 0; y < mHeight; y++) + { + if (getColor(x, y, pixel)) + { + if (pixel.alpha < 255) + { + mHasTransparency = true; + break; + } + } + } + } + + break; + default: + AssertFatal(false, "GBitmap::checkForTransparency: misunderstood format specifier"); + break; + } + + return mHasTransparency; +} + +//------------------------------------------------------------------------------ +ColorF GBitmap::sampleTexel(F32 u, F32 v) const +{ + ColorF col(0.5f, 0.5f, 0.5f); + // normally sampling wraps all the way around at 1.0, + // but locking doesn't support this, and we seem to calc + // the uv based on a clamped 0 - 1... + Point2F max((F32)(getWidth()-1), (F32)(getHeight()-1)); + Point2F posf; + posf.x = mClampF(((u) * max.x), 0.0f, max.x); + posf.y = mClampF(((v) * max.y), 0.0f, max.y); + Point2I posi((S32)posf.x, (S32)posf.y); + + const U8 *buffer = getBits(); + U32 lexelindex = ((posi.y * getWidth()) + posi.x) * mBytesPerPixel; + + if(mBytesPerPixel == 2) + { + //U16 *buffer = (U16 *)lockrect->pBits; + } + else if(mBytesPerPixel > 2) + { + col.red = F32(buffer[lexelindex + 0]) / 255.0f; + col.green = F32(buffer[lexelindex + 1]) / 255.0f; + col.blue = F32(buffer[lexelindex + 2]) / 255.0f; + } + + return col; +} + +//-------------------------------------------------------------------------- +bool GBitmap::getColor(const U32 x, const U32 y, ColorI& rColor) const +{ + if (x >= mWidth || y >= mHeight) + return false; + + const U8* pLoc = getAddress(x, y); + + switch (mInternalFormat) { + case GFXFormatA8: + case GFXFormatL8: + rColor.set( *pLoc, *pLoc, *pLoc, *pLoc ); + break; + + case GFXFormatR8G8B8: + case GFXFormatR8G8B8X8: + rColor.set( pLoc[0], pLoc[1], pLoc[2], 255 ); + break; + + case GFXFormatR8G8B8A8: + rColor.set( pLoc[0], pLoc[1], pLoc[2], pLoc[3] ); + break; + + case GFXFormatR5G5B5A1: +#if defined(TORQUE_OS_MAC) + rColor.set( (*((U16*)pLoc) >> 0) & 0x1F, + (*((U16*)pLoc) >> 5) & 0x1F, + (*((U16*)pLoc) >> 10) & 0x1F, + ((*((U16*)pLoc) >> 15) & 0x01) ? 255 : 0 ); +#else + rColor.set( *((U16*)pLoc) >> 11, + (*((U16*)pLoc) >> 6) & 0x1f, + (*((U16*)pLoc) >> 1) & 0x1f, + (*((U16*)pLoc) & 1) ? 255 : 0 ); +#endif + break; + + default: + AssertFatal(false, "Bad internal format"); + return false; + } + + return true; +} + + +//-------------------------------------------------------------------------- + + +bool GBitmap::setColor(const U32 x, const U32 y, const ColorI& rColor) +{ + if (x >= mWidth || y >= mHeight) + return false; + + U8* pLoc = getAddress(x, y); + + switch (mInternalFormat) { + case GFXFormatA8: + case GFXFormatL8: + *pLoc = rColor.alpha; + break; + + case GFXFormatR8G8B8: + dMemcpy( pLoc, &rColor, 3 * sizeof( U8 ) ); + break; + + case GFXFormatR8G8B8A8: + case GFXFormatR8G8B8X8: + dMemcpy( pLoc, &rColor, 4 * sizeof( U8 ) ); + break; + + case GFXFormatR5G6B5: + #ifdef TORQUE_OS_MAC + *((U16*)pLoc) = (rColor.red << 11) | (rColor.green << 5) | (rColor.blue << 0) ; + #else + *((U16*)pLoc) = (rColor.blue << 0) | (rColor.green << 5) | (rColor.red << 11); + #endif + break; + + case GFXFormatR5G5B5A1: + #ifdef TORQUE_OS_MAC + *((U16*)pLoc) = (((rColor.alpha>0) ? 1 : 0)<<15) | (rColor.blue << 10) | (rColor.green << 5) | (rColor.red << 0); + #else + *((U16*)pLoc) = (rColor.blue << 1) | (rColor.green << 6) | (rColor.red << 11) | ((rColor.alpha>0) ? 1 : 0); + #endif + break; + + default: + AssertFatal(false, "Bad internal format"); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- + +bool GBitmap::combine( const GBitmap *bitmapA, const GBitmap *bitmapB, const GFXTextureOp combineOp ) +{ + // Check valid texture ops + switch( combineOp ) + { + case GFXTOPAdd: + case GFXTOPSubtract: + break; + + default: + Con::errorf( "GBitmap::combine - Invalid op type" ); + return false; + } + + // Check bitmapA format + switch( bitmapA->getFormat() ) + { + case GFXFormatR8G8B8: + case GFXFormatR8G8B8X8: + case GFXFormatR8G8B8A8: + break; + + default: + Con::errorf( "GBitmap::combine - invalid format for bitmapA" ); + return false; + } + + // Check bitmapB format + switch( bitmapB->getFormat() ) + { + case GFXFormatR8G8B8: + case GFXFormatR8G8B8X8: + case GFXFormatR8G8B8A8: + break; + + default: + Con::errorf( "GBitmap::combine - invalid format for bitmapB" ); + return false; + } + + // Determine format of result texture + // CodeReview: This is dependent on the order of the GFXFormat enum. [5/11/2007 Pat] + GFXFormat resFmt = static_cast( getMax( bitmapA->getFormat(), bitmapB->getFormat() ) ); + U32 resWidth = getMax( bitmapA->getWidth(), bitmapB->getWidth() ); + U32 resHeight = getMax( bitmapA->getHeight(), bitmapB->getHeight() ); + + // Adjust size OF bitmap based on the biggest one + if( bitmapA->getWidth() != bitmapB->getWidth() || + bitmapA->getHeight() != bitmapB->getHeight() ) + { + // Delete old bitmap + deleteImage(); + + // Allocate new one + allocateBitmap( resWidth, resHeight, false, resFmt ); + } + + // Adjust format of result bitmap (if resFmt == getFormat() it will not perform the format convert) + setFormat( resFmt ); + + // Perform combine + U8 *destBits = getWritableBits(); + const U8 *aBits = bitmapA->getBits(); + const U8 *bBits = bitmapB->getBits(); + + for( int y = 0; y < getHeight(); y++ ) + { + for( int x = 0; x < getWidth(); x++ ) + { + for( int _byte = 0; _byte < mBytesPerPixel; _byte++ ) + { + U8 pxA = 0; + U8 pxB = 0; + + // Get contributions from A and B + if( y < bitmapA->getHeight() && + x < bitmapA->getWidth() && + _byte < bitmapA->mBytesPerPixel ) + pxA = *aBits++; + + if( y < bitmapB->getHeight() && + x < bitmapB->getWidth() && + _byte < bitmapB->mBytesPerPixel ) + pxB = *bBits++; + + // Combine them (clamp values 0-U8_MAX) + switch( combineOp ) + { + case GFXTOPAdd: + *destBits++ = getMin( U8( pxA + pxB ), U8_MAX ); + break; + + case GFXTOPSubtract: + *destBits++ = getMax( U8( pxA - pxB ), U8( 0 ) ); + break; + default: + AssertFatal(false, "GBitmap::combine - Invalid combineOp"); + break; + } + } + } + } + + return true; +} + +void GBitmap::fill( const ColorI &rColor ) +{ + // Set the first pixel using the slow + // but proper method. + setColor( 0, 0, rColor ); + mHasTransparency = rColor.alpha < 255; + + // Now fill the first row of the bitmap by + // copying the first pixel across the row. + const U32 stride = getWidth() * mBytesPerPixel; + const U8 *src = getBits(); + U8 *dest = getWritableBits() + mBytesPerPixel; + const U8 *end = src + stride; + for ( ; dest != end; dest += mBytesPerPixel ) + dMemcpy( dest, src, mBytesPerPixel ); + + // Now copy the first row to all the others. + // + // TODO: This could adaptively size the copy + // amount to copy more rows from the source + // and reduce the total number of memcpy calls. + // + dest = getWritableBits() + stride; + end = src + ( stride * getHeight() ); + for ( ; dest != end; dest += stride ) + dMemcpy( dest, src, stride ); +} + +void GBitmap::fillWhite() +{ + dMemset( getWritableBits(), 255, mByteSize ); + mHasTransparency = false; +} + +GBitmap* GBitmap::createPaddedBitmap() const +{ + if (isPow2(getWidth()) && isPow2(getHeight())) + return NULL; + + AssertFatal(getNumMipLevels() == 1, + "Cannot have non-pow2 bitmap with miplevels"); + + U32 width = getWidth(); + U32 height = getHeight(); + + U32 newWidth = getNextPow2(getWidth()); + U32 newHeight = getNextPow2(getHeight()); + + GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, getFormat()); + + for (U32 i = 0; i < height; i++) + { + U8* pDest = (U8*)pReturn->getAddress(0, i); + const U8* pSrc = (const U8*)getAddress(0, i); + + dMemcpy(pDest, pSrc, width * mBytesPerPixel); + + pDest += width * mBytesPerPixel; + // set the src pixel to the last pixel in the row + const U8 *pSrcPixel = pDest - mBytesPerPixel; + + for(U32 j = width; j < newWidth; j++) + for(U32 k = 0; k < mBytesPerPixel; k++) + *pDest++ = pSrcPixel[k]; + } + + for(U32 i = height; i < newHeight; i++) + { + U8* pDest = (U8*)pReturn->getAddress(0, i); + U8* pSrc = (U8*)pReturn->getAddress(0, height-1); + dMemcpy(pDest, pSrc, newWidth * mBytesPerPixel); + } + + return pReturn; +} + +GBitmap* GBitmap::createPow2Bitmap() const +{ + if (isPow2(getWidth()) && isPow2(getHeight())) + return NULL; + + AssertFatal(getNumMipLevels() == 1, + "Cannot have non-pow2 bitmap with miplevels"); + + U32 width = getWidth(); + U32 height = getHeight(); + + U32 newWidth = getNextPow2(getWidth()); + U32 newHeight = getNextPow2(getHeight()); + + GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, getFormat()); + + U8* pDest = (U8*)pReturn->getAddress(0, 0); + const U8* pSrc = (const U8*)getAddress(0, 0); + + F32 yCoeff = (F32) height / (F32) newHeight; + F32 xCoeff = (F32) width / (F32) newWidth; + + F32 currY = 0.0f; + for (U32 y = 0; y < newHeight; y++) + { + F32 currX = 0.0f; + //U32 yDestOffset = (pReturn->mWidth * pReturn->mBytesPerPixel) * y; + //U32 xDestOffset = 0; + //U32 ySourceOffset = (U32)((mWidth * mBytesPerPixel) * currY); + //F32 xSourceOffset = 0.0f; + for (U32 x = 0; x < newWidth; x++) + { + pDest = (U8*) pReturn->getAddress(x, y); + pSrc = (U8*) getAddress((S32)currX, (S32)currY); + for (U32 p = 0; p < pReturn->mBytesPerPixel; p++) + { + pDest[p] = pSrc[p]; + } + currX += xCoeff; + } + currY += yCoeff; + } + + return pReturn; +} + +void GBitmap::copyChannel( U32 index, GBitmap *outBitmap ) const +{ + AssertFatal( index < mBytesPerPixel, "GBitmap::copyChannel() - Bad channel offset!" ); + AssertFatal( outBitmap, "GBitmap::copyChannel() - Null output bitmap!" ); + AssertFatal( outBitmap->getWidth() == getWidth(), "GBitmap::copyChannel() - Width mismatch!" ); + AssertFatal( outBitmap->getHeight() == getHeight(), "GBitmap::copyChannel() - Height mismatch!" ); + + U8 *outBits = outBitmap->getWritableBits(); + const U32 outBytesPerPixel = outBitmap->getBytesPerPixel(); + const U8 *srcBits = getBits() + index; + const U8 *endBits = getBits() + mByteSize; + + for ( ; srcBits < endBits; ) + { + *outBits = *srcBits; + outBits += outBytesPerPixel; + srcBits += mBytesPerPixel; + } +} + +//------------------------------------------------------------------------------ + +bool GBitmap::read(Stream& io_rStream) +{ + // Handle versioning + U32 version; + io_rStream.read(&version); + AssertFatal(version == csFileVersion, "Bitmap::read: incorrect file version"); + + //-------------------------------------- Read the object + U32 fmt; + io_rStream.read(&fmt); + mInternalFormat = GFXFormat(fmt); + mBytesPerPixel = 1; + switch (mInternalFormat) { + case GFXFormatA8: + case GFXFormatL8: mBytesPerPixel = 1; + break; + case GFXFormatR8G8B8: mBytesPerPixel = 3; + break; + case GFXFormatR8G8B8A8: mBytesPerPixel = 4; + break; + case GFXFormatR5G6B5: + case GFXFormatR5G5B5A1: mBytesPerPixel = 2; + break; + default: + AssertFatal(false, "GBitmap::read: misunderstood format specifier"); + break; + } + + io_rStream.read(&mByteSize); + + mBits = new U8[mByteSize]; + io_rStream.read(mByteSize, mBits); + + io_rStream.read(&mWidth); + io_rStream.read(&mHeight); + + io_rStream.read(&mNumMipLevels); + for (U32 i = 0; i < c_maxMipLevels; i++) + io_rStream.read(&mMipLevelOffsets[i]); + + checkForTransparency(); + + return (io_rStream.getStatus() == Stream::Ok); +} + +bool GBitmap::write(Stream& io_rStream) const +{ + // Handle versioning + io_rStream.write(csFileVersion); + + //-------------------------------------- Write the object + io_rStream.write(U32(mInternalFormat)); + + io_rStream.write(mByteSize); + io_rStream.write(mByteSize, mBits); + + io_rStream.write(mWidth); + io_rStream.write(mHeight); + + io_rStream.write(mNumMipLevels); + for (U32 i = 0; i < c_maxMipLevels; i++) + io_rStream.write(mMipLevelOffsets[i]); + + return (io_rStream.getStatus() == Stream::Ok); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Persistent I/O +// + +bool GBitmap::readBitmap( const String &bmType, Stream &ioStream ) +{ + const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType ); + + if ( regInfo == NULL ) + { + Con::errorf( "[GBitmap::readBitmap] unable to find registration for extension [%s]", bmType.c_str() ); + return NULL; + } + + return regInfo->readFunc( ioStream, this ); +} + +bool GBitmap::writeBitmap( const String &bmType, Stream &ioStream, U32 compressionLevel ) +{ + const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType ); + + if ( regInfo == NULL ) + { + Con::errorf( "[GBitmap::writeBitmap] unable to find registration for extension [%s]", bmType.c_str() ); + return NULL; + } + + return regInfo->writeFunc( this, ioStream, (compressionLevel == U32_MAX) ? regInfo->defaultCompression : compressionLevel ); +} + +template<> void *Resource::create(const Torque::Path &path) +{ +#ifdef TORQUE_DEBUG_RES_MANAGER + Con::printf( "Resource::create - [%s]", path.getFullPath().c_str() ); +#endif + + FileStream stream; + + stream.open( path.getFullPath(), Torque::FS::File::Read ); + + if ( stream.getStatus() != Stream::Ok ) + { + Con::errorf( "Resource::create - failed to open '%s'", path.getFullPath().c_str() ); + return NULL; + } + + GBitmap *bmp = new GBitmap; + const String extension = path.getExtension(); + if( !bmp->readBitmap( extension, stream ) ) + { + Con::errorf( "Resource::create - error reading '%s'", path.getFullPath().c_str() ); + delete bmp; + bmp = NULL; + } + + return bmp; +} + +template<> ResourceBase::Signature Resource::signature() +{ + return MakeFourCC('b','i','t','m'); +} + +/// Load the given bitmap file. +/// +/// Important: Don't do something like this +/// +/// @code +/// GBitmap* bitmap = GBitmap::load( filename ); // DON'T DO!!! +/// @endcode +/// +/// Resources are reference-counted and the smart pointer conversion will +/// release the bitmap and thus render the resulting bitmap pointer invalid! +Resource GBitmap::load(const Torque::Path &path) +{ + if ( Torque::FS::IsFile( path ) ) + return ResourceManager::get().load( path ); + + // Try some different possible filenames. + const String origExtension( String::ToLower( path.getExtension() ) ); + + Resource ret; + Vector foundPaths( __FILE__, __LINE__ ); + + bool found = GBitmap::sFindFiles( path, &foundPaths ); + + if ( found ) + { + for( U32 i = 0; i < foundPaths.size(); i++ ) + { + if ( foundPaths[i].getExtension() == origExtension ) // we've already tried this one + continue; + + ret = ResourceManager::get().load( foundPaths[i] ); + + if ( ret != NULL ) + return ret; + } + } + + // If unable to load texture in current directory + // look in the parent directory. But never look in the root. + Path newPath( path ); + + String filePath = newPath.getPath(); + + String::SizeType slash = filePath.find( '/', filePath.length(), String::Right ); + + if ( slash != String::NPos ) + { + slash = filePath.find( '/', filePath.length(), String::Right ); + + if ( slash != String::NPos ) + { + String truncPath = filePath.substr( 0, slash ); + newPath.setPath( truncPath ); + + return load( newPath ); + } + } + + return ret; +} + +ConsoleFunction( getBitmapInfo, const char *, 2, 2, "( filename )\n" + "Returns image info in the following format: \n" + "\t\t" ) +{ + // Pull this out. + const UTF8 *fileName = argv[1]; + + Resource image = GBitmap::load( fileName ); + if ( !image ) + return ""; + + char *returnBuffer = Con::getReturnBuffer( 256 ); + dSprintf( returnBuffer, 256, "%d\t%d\t%d", image->getWidth(), + image->getHeight(), + image->getBytesPerPixel() ); + + return returnBuffer; +} + diff --git a/gfx/bitmap/gBitmap.h b/gfx/bitmap/gBitmap.h new file mode 100644 index 0000000..4495249 --- /dev/null +++ b/gfx/bitmap/gBitmap.h @@ -0,0 +1,288 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GBITMAP_H_ +#define _GBITMAP_H_ + +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif + +#ifndef _SWIZZLE_H_ +#include "core/util/swizzle.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" // For the format +#endif + +//-------------------------------------- Forward decls. +class Stream; +class RectI; +class Point2I; +class ColorI; +class ColorF; + +//------------------------------------------------------------------------------ +//-------------------------------------- GBitmap + +class GBitmap +{ +public: + enum Constants + { + /// The maximum mipmap levels we support. The current + /// value lets us support up to 4096 x 4096 images. + c_maxMipLevels = 13 + }; + + struct Registration + { + /// The read function prototype. + typedef bool(*ReadFunc)(Stream &stream, GBitmap *bitmap); + + /// The write function prototype. Compression levels are image-specific - see their registration declaration for details. + typedef bool(*WriteFunc)(GBitmap *bitmap, Stream &stream, U32 compressionLevel); + + Vector extensions; ///< the list of file extensions for this bitmap type [these should be lower case] + + ReadFunc readFunc; ///< the read function to call for this bitmap type + WriteFunc writeFunc; ///< the write function to call for this bitmap type + U32 defaultCompression; ///< the default compression level [levels are image-specific - see their registration declaration for details] + + Registration() + { + VECTOR_SET_ASSOCIATION( extensions ); + } + }; + + static Resource load(const Torque::Path &path); + +public: + GBitmap(); + GBitmap(const GBitmap&); + + GBitmap(const U32 in_width, + const U32 in_height, + const bool in_extrudeMipLevels = false, + const GFXFormat in_format = GFXFormatR8G8B8 ); + + // This builds a GBitmap with the R8G8B8A8 format using the passed in + // data (assumes that there is width * height * 4 U8's in data) + GBitmap(const U32 in_width, + const U32 in_height, + const U8* data ); + + virtual ~GBitmap(); + + + static void sRegisterFormat( const Registration ® ); + static const Registration* sFindRegInfo( const String &extension ); + + /// Given a path to a file, try all known extensions. If the file exists on disk, fill in path + /// with the correct extension and return true. Otherwise, return false. + static bool sFindFiles( const Torque::Path &path, Vector *outFoundPaths ); + + /// Returns a space separated string of all registered extensions. + static String sGetExtensionList(); + + void allocateBitmap(const U32 in_width, + const U32 in_height, + const bool in_extrudeMipLevels = false, + const GFXFormat in_format = GFXFormatR8G8B8 ); + + void extrudeMipLevels(bool clearBorders = false); + void extrudeMipLevelsDetail(); + + U32 getNumMipLevels() const { return mNumMipLevels; } + + GBitmap *createPaddedBitmap() const; + GBitmap *createPow2Bitmap() const; + + /// Copies a color channel by index into the first channel + /// of the output bitmap. The output bitmap must be the same + /// dimensions as the source. + void copyChannel( U32 index, GBitmap *outBitmap ) const; + + void copyRect(const GBitmap *in, const RectI &srcRect, const Point2I &dstPoint); + + GFXFormat getFormat() const { return mInternalFormat; } + bool setFormat(GFXFormat fmt); + + U32 getWidth(const U32 in_mipLevel = 0) const; + U32 getHeight(const U32 in_mipLevel = 0) const; + U32 getDepth(const U32 in_mipLevel = 0) const; + + U8* getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel = 0); + const U8* getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel = 0) const; + + const U8* getBits(const U32 in_mipLevel = 0) const; + U8* getWritableBits(const U32 in_mipLevel = 0); + + U32 getByteSize() const { return mByteSize; } + U32 getBytesPerPixel() const { return mBytesPerPixel; } + + /// Use these functions to set and get the mHasTransparency value + /// This is used to indicate that this bitmap has pixels that have + /// an alpha value less than 255 (used by the auto-Material mapper) + bool getHasTransparency() const { return mHasTransparency; } + void setHasTransparency(bool hasTransparency) { mHasTransparency = hasTransparency; } + + /// In general you will want to use this function if there is not a + /// good spot in the bitmap loader(s) to check the alpha value of + /// the pixels. This function uses the texture format to loop over + /// the bitmap bits and to check for alpha values less than 255 + bool checkForTransparency(); + + ColorF sampleTexel(F32 u, F32 v) const; + bool getColor(const U32 x, const U32 y, ColorI& rColor) const; + bool setColor(const U32 x, const U32 y, const ColorI& rColor); + + /// This method will combine bitmapA and bitmapB using the operation specified + /// by combineOp. The result will be stored in the bitmap that this method is + /// called on. The size of the resulting bitmap will be the larger of A and B. + /// The format of the resulting bitmap will be the format of A or B, whichever + /// has a larger byte size. + /// + /// @note There are some restrictions on ops and formats that will probably change + /// based on how we use this function. + bool combine( const GBitmap *bitmapA, const GBitmap *bitmapB, const GFXTextureOp combineOp ); + + /// Fills the first mip level of the bitmap with the specified color. + void fill( const ColorI &rColor ); + + /// An optimized version of fill(). + void fillWhite(); + + //-------------------------------------- Internal data/operators + + void deleteImage(); + + //-------------------------------------- Input/Output interface + + /// Read a bitmap from a stream + /// @param bmType This is a file extension to describe the type of the data [i.e. "png" for PNG file, etc] + /// @param ioStream The stream to read from + bool readBitmap( const String &bmType, Stream &ioStream ); + + /// Write a bitmap to a stream + /// @param bmType This is a file extension to describe the type of the data [i.e. "png" for PNG file, etc] + /// @param ioStream The stream to read from + /// @param compressionLevel Image format-specific compression level. If set to U32_MAX, we use the default compression defined when the format was registered. + bool writeBitmap( const String &bmType, Stream &ioStream, U32 compressionLevel = U32_MAX ); + + bool readMNG(Stream& io_rStream); // located in bitmapMng.cc + bool writeMNG(Stream& io_rStream) const; + + bool read(Stream& io_rStream); + bool write(Stream& io_rStream) const; + + template + void swizzle(const Swizzle *s); + + static Vector sRegistrations; + +private: + GFXFormat mInternalFormat; + + U8* mBits; // Master bytes + U32 mByteSize; + U32 mWidth; + U32 mHeight; + U32 mDepth; + U32 mBytesPerPixel; + + U32 mNumMipLevels; + U32 mMipLevelOffsets[c_maxMipLevels]; + + bool mHasTransparency; + + static const U32 csFileVersion; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- Inlines +// + +inline U32 GBitmap::getWidth(const U32 in_mipLevel) const +{ + AssertFatal(in_mipLevel < mNumMipLevels, + avar("GBitmap::getWidth: mip level out of range: (%d, %d)", + in_mipLevel, mNumMipLevels)); + + U32 retVal = mWidth >> in_mipLevel; + + return (retVal != 0) ? retVal : 1; +} + +inline U32 GBitmap::getHeight(const U32 in_mipLevel) const +{ + AssertFatal(in_mipLevel < mNumMipLevels, + avar("Bitmap::getHeight: mip level out of range: (%d, %d)", + in_mipLevel, mNumMipLevels)); + + U32 retVal = mHeight >> in_mipLevel; + + return (retVal != 0) ? retVal : 1; +} + +inline U32 GBitmap::getDepth(const U32 in_mipLevel) const +{ + AssertFatal(in_mipLevel < mNumMipLevels, + avar("Bitmap::getDepth: mip level out of range: (%d, %d)", + in_mipLevel, mNumMipLevels)); + + U32 retVal = mDepth >> in_mipLevel; + + return (retVal != 0) ? retVal : 1; +} + +inline const U8* GBitmap::getBits(const U32 in_mipLevel) const +{ + AssertFatal(in_mipLevel < mNumMipLevels, + avar("GBitmap::getBits: mip level out of range: (%d, %d)", + in_mipLevel, mNumMipLevels)); + + return &mBits[mMipLevelOffsets[in_mipLevel]]; +} + +inline U8* GBitmap::getWritableBits(const U32 in_mipLevel) +{ + AssertFatal(in_mipLevel < mNumMipLevels, + avar("GBitmap::getWritableBits: mip level out of range: (%d, %d)", + in_mipLevel, mNumMipLevels)); + + return &mBits[mMipLevelOffsets[in_mipLevel]]; +} + +inline U8* GBitmap::getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel) +{ + return (getWritableBits(mipLevel) + ((in_y * getWidth(mipLevel)) + in_x) * mBytesPerPixel); +} + +inline const U8* GBitmap::getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel) const +{ + return (getBits(mipLevel) + ((in_y * getWidth(mipLevel)) + in_x) * mBytesPerPixel); +} + +template +void GBitmap::swizzle(const Swizzle *s ) +{ + const U32 memSize = getWidth() * getHeight() * mBytesPerPixel; + + void *b = dMalloc(memSize); + + s->ToBuffer(b, getWritableBits(), memSize); + + dMemcpy(getWritableBits(), b, memSize); + + dFree(b); +} + +#endif //_GBITMAP_H_ diff --git a/gfx/bitmap/loaders/bitmapBmp.cpp b/gfx/bitmap/loaders/bitmapBmp.cpp new file mode 100644 index 0000000..b7f0a20 --- /dev/null +++ b/gfx/bitmap/loaders/bitmapBmp.cpp @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/stream.h" + +#include "gfx/bitmap/gBitmap.h" + + +static bool sReadBMP(Stream &stream, GBitmap *bitmap); +static bool sWriteBMP(GBitmap *bitmap, Stream &stream, U32 compressionLevel); + +static struct _privateRegisterBMP +{ + _privateRegisterBMP() + { + GBitmap::Registration reg; + + reg.extensions.push_back( "bmp" ); + + reg.readFunc = sReadBMP; + reg.writeFunc = sWriteBMP; + + GBitmap::sRegisterFormat( reg ); + } +} sStaticRegisterBMP; + + +// structures mirror those defined by the win32 API + +struct RGBQUAD +{ + U8 rgbBlue; + U8 rgbGreen; + U8 rgbRed; + U8 rgbReserved; +}; + +struct BITMAPFILEHEADER +{ + U16 bfType; + U32 bfSize; + U16 bfReserved1; + U16 bfReserved2; + U32 bfOffBits; +}; + +struct BITMAPINFOHEADER +{ + U32 biSize; + S32 biWidth; + S32 biHeight; + U16 biPlanes; + U16 biBitCount; + U32 biCompression; + U32 biSizeImage; + S32 biXPelsPerMeter; + S32 biYPelsPerMeter; + U32 biClrUsed; + U32 biClrImportant; +}; + +// constants for the biCompression field +#define BI_RGB 0L +#define BI_RLE8 1L +#define BI_RLE4 2L +#define BI_BITFIELDS 3L + + +//------------------------------------------------------------------------------ +//-------------------------------------- Supplementary I/O (Partially located in +// bitmapPng.cc) +// + +static bool sReadBMP(Stream &stream, GBitmap *bitmap) +{ + BITMAPINFOHEADER bi; + BITMAPFILEHEADER bf; + RGBQUAD rgb[256]; + + stream.read(&bf.bfType); + stream.read(&bf.bfSize); + stream.read(&bf.bfReserved1); + stream.read(&bf.bfReserved2); + stream.read(&bf.bfOffBits); + + stream.read(&bi.biSize); + stream.read(&bi.biWidth); + stream.read(&bi.biHeight); + stream.read(&bi.biPlanes); + stream.read(&bi.biBitCount); + stream.read(&bi.biCompression); + stream.read(&bi.biSizeImage); + stream.read(&bi.biXPelsPerMeter); + stream.read(&bi.biYPelsPerMeter); + stream.read(&bi.biClrUsed); + stream.read(&bi.biClrImportant); + + GFXFormat fmt = GFXFormatR8G8B8; + if(bi.biBitCount == 8) + { + // read in texture palette + if(!bi.biClrUsed) + bi.biClrUsed = 256; + stream.read(sizeof(RGBQUAD) * bi.biClrUsed, rgb); + } + bitmap->allocateBitmap(bi.biWidth, bi.biHeight, false, fmt); + U32 width = bitmap->getWidth(); + U32 height = bitmap->getHeight(); + U32 bytesPerPixel = bitmap->getBytesPerPixel(); + + for(U32 i = 0; i < bi.biHeight; i++) + { + U8 *rowDest = bitmap->getAddress(0, height - i - 1); + if (bi.biBitCount == 8) + { + // use palette...don't worry about being slow + for (S32 j=0; jgetAddress(0,0); + for(int i = 0; i < width * height; i++) + { + U8 tmp = ptr[0]; + ptr[0] = ptr[2]; + ptr[2] = tmp; + ptr += 3; + } + } + + // We know BMP's don't have any transparency + bitmap->setHasTransparency(false); + + return true; +} + +static bool sWriteBMP(GBitmap *bitmap, Stream &stream, U32 compressionLevel) +{ + TORQUE_UNUSED( compressionLevel ); // BMP does not use compression + + BITMAPINFOHEADER bi; + BITMAPFILEHEADER bf; + + bi.biSize = sizeof(BITMAPINFOHEADER); + bi.biWidth = bitmap->getWidth(); + bi.biHeight = bitmap->getHeight(); //our data is top-down + bi.biPlanes = 1; + + if(bitmap->getFormat() == GFXFormatR8G8B8) + { + bi.biBitCount = 24; + bi.biCompression = BI_RGB; + bi.biClrUsed = 0; + } + else + { + bi.biBitCount = 0; + bi.biCompression = BI_RGB; // Removes warning C4701 on line + AssertISV(false, "GBitmap::writeMSBmp - only support R8G8B8 formats!"); + } + + U32 width = bitmap->getWidth(); + U32 height = bitmap->getHeight(); + + U32 bytesPP = bi.biBitCount >> 3; + bi.biSizeImage = width * height * bytesPP; + bi.biXPelsPerMeter = 0; + bi.biYPelsPerMeter = 0; + bi.biClrUsed = 0; + bi.biClrImportant = 0; + + bf.bfType = makeFourCCTag('B','M',0,0); //Type of file 'BM' + bf.bfOffBits= sizeof(BITMAPINFOHEADER) + + sizeof(BITMAPFILEHEADER) + + (sizeof(RGBQUAD)*bi.biClrUsed); + bf.bfSize = bf.bfOffBits + bi.biSizeImage; + bf.bfReserved1 = 0; + bf.bfReserved2 = 0; + + stream.write(bf.bfType); + stream.write(bf.bfSize); + stream.write(bf.bfReserved1); + stream.write(bf.bfReserved2); + stream.write(bf.bfOffBits); + + stream.write(bi.biSize); + stream.write(bi.biWidth); + stream.write(bi.biHeight); + stream.write(bi.biPlanes); + stream.write(bi.biBitCount); + stream.write(bi.biCompression); + stream.write(bi.biSizeImage); + stream.write(bi.biXPelsPerMeter); + stream.write(bi.biYPelsPerMeter); + stream.write(bi.biClrUsed); + stream.write(bi.biClrImportant); + + //write the bitmap bits + U8* pMSUpsideDownBits = new U8[bi.biSizeImage]; + for (U32 i = 0; i < height; i++) + { + const U8* pSrc = bitmap->getAddress(0, i); + U8* pDst = pMSUpsideDownBits + (height - i - 1) * width * bytesPP; + + dMemcpy(pDst, pSrc, width * bytesPP); + } + + stream.write(bi.biSizeImage, pMSUpsideDownBits); + delete [] pMSUpsideDownBits; + + return stream.getStatus() == Stream::Ok; +} diff --git a/gfx/bitmap/loaders/bitmapGif.cpp b/gfx/bitmap/loaders/bitmapGif.cpp new file mode 100644 index 0000000..0db548a --- /dev/null +++ b/gfx/bitmap/loaders/bitmapGif.cpp @@ -0,0 +1,212 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/fileStream.h" +#include "core/util/path.h" + +#include "gfx/bitmap/gBitmap.h" + +// This must come after our headers due to a conflicting definition of VoidPtr +#include "lungif/gif_lib.h" + + +using namespace Torque; + +static bool sReadGIF(Stream &stream, GBitmap *bitmap); +static bool sWriteGIF(GBitmap *bitmap, Stream &stream, U32 compressionLevel); + +static struct _privateRegisterGIF +{ + _privateRegisterGIF() + { + GBitmap::Registration reg; + + reg.extensions.push_back( "gif" ); + + reg.readFunc = sReadGIF; + reg.writeFunc = sWriteGIF; + + GBitmap::sRegisterFormat( reg ); + } +} sStaticRegisterGIF; + +//-------------------------------------- Replacement I/O for standard LIBjpeg +// functions. we don't wanna use +// FILE*'s... +static int gifReadDataFn(GifFileType *gifinfo, GifByteType *data, int length) +{ + Stream *stream = (Stream*)gifinfo->UserData; + AssertFatal(stream != NULL, "gifReadDataFn::No stream."); + int pos = stream->getPosition(); + if (stream->read(length, data)) + return length; + + if (stream->getStatus() == Stream::EOS) + return (stream->getPosition()-pos); + else + return 0; +} + + +//-------------------------------------- +#if 0 +// CodeReview - until we can write these, get rid of warning by disabling method. +static int gifWriteDataFn(GifFileType *gifinfo, GifByteType *data, int length) +{ + Stream *stream = (Stream*)gifinfo->UserData; + AssertFatal(stream != NULL, "gifWriteDataFn::No stream."); + if (stream->write(length, data)) + return length; + else + return 0; +} +#endif + +//-------------------------------------- +static bool sReadGIF( Stream &stream, GBitmap *bitmap ) +{ + GifFileType *gifinfo = DGifOpen( (void*)&stream, gifReadDataFn); + if (!gifinfo) + return false; + + GifRecordType recordType; + do + { + if (DGifGetRecordType(gifinfo, &recordType) == GIF_ERROR) + break; + + if (recordType == IMAGE_DESC_RECORD_TYPE) + { + if (DGifGetImageDesc(gifinfo) == GIF_ERROR) + break; + + GFXFormat format = (gifinfo->SBackGroundColor == 0 ) ? GFXFormatR8G8B8 : GFXFormatR8G8B8A8; + bitmap->allocateBitmap(gifinfo->SWidth, gifinfo->SHeight, false, format); + + // Assume no transparency until proven otherwise + bitmap->setHasTransparency(false); + + U32 gwidth = gifinfo->Image.Width ? gifinfo->Image.Width : bitmap->getWidth(); + U32 gheight= gifinfo->Image.Height ? gifinfo->Image.Height : bitmap->getHeight(); + U32 gifSize = gwidth * gheight; + U8 *data = new U8[gifSize]; + + if (DGifGetLine(gifinfo, data, gifSize) != GIF_ERROR) + { + // use the global or local color table ? + GifColorType *color = NULL; + if (gifinfo->Image.ColorMap) + color = gifinfo->Image.ColorMap->Colors; + else if (gifinfo->SColorMap) + color = gifinfo->SColorMap->Colors; + + if (color) + { + U8 *dst = bitmap->getAddress(gifinfo->Image.Left, gifinfo->Image.Top); + U8 *src = data; + U32 right = gifinfo->Image.Left + gwidth; + U32 bottom = gifinfo->Image.Top + gheight; + U32 next = (bitmap->getWidth() - gwidth) * bitmap->getBytesPerPixel(); + + if (format == GFXFormatR8G8B8A8) + { + for (U32 y=gifinfo->Image.Top; yImage.Left; xSBackGroundColor) + { + // this is a transparent pixel + dst[0] = 0; // red + dst[1] = 0; // green + dst[2] = 0; // blue + dst[3] = 0; // alpha + + bitmap->setHasTransparency(true); + } + else + { + dst[0] = color[*src].Red; + dst[1] = color[*src].Green; + dst[2] = color[*src].Blue; + dst[3] = 0; // alpha + } + dst += bitmap->getBytesPerPixel(); + } + dst += next; + } + } + else + { + for (U32 y=gifinfo->Image.Top; yImage.Left; xgetBytesPerPixel(); + } + dst += next; + } + } + delete [] data; + DGifCloseFile(gifinfo); + return true; + } + } + // failure + delete [] data; + break; + } + else if (recordType == EXTENSION_RECORD_TYPE) + { + GifByteType *extension; + S32 extCode; + + // Skip any extension blocks in file + if (DGifGetExtension(gifinfo, &extCode, &extension) != GIF_ERROR) + { + while (extension != NULL) + { + if (DGifGetExtensionNext(gifinfo, &extension) == GIF_ERROR) + { + return false; + } + } + } + else + { + return false; + } + } + + // There used to be a break right here. This caused the while condition to + // never get processed, and so it never looped through all the records in + // the GIF. I took a quick peek back at TGB and TGE histories and I am not + // sure where this change got made, but I can't figure out why the loading + // worked at all, ever, with that break in there. The only case I can think + // of is if the first record in the GIF was the bitmap data. + // [6/6/2007 Pat] + + }while (recordType != TERMINATE_RECORD_TYPE); + + + DGifCloseFile(gifinfo); + return true; +} + + +//-------------------------------------------------------------------------- +static bool sWriteGIF(GBitmap *bitmap, Stream &stream, U32 compressionLevel) +{ + TORQUE_UNUSED( bitmap ); + TORQUE_UNUSED( stream ); + TORQUE_UNUSED( compressionLevel ); + + return false; +} + + diff --git a/gfx/bitmap/loaders/bitmapJpeg.cpp b/gfx/bitmap/loaders/bitmapJpeg.cpp new file mode 100644 index 0000000..6a7c9ef --- /dev/null +++ b/gfx/bitmap/loaders/bitmapJpeg.cpp @@ -0,0 +1,232 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "ljpeg/jpeglib.h" + +#include "core/stream/stream.h" + +#include "gfx/bitmap/gBitmap.h" + + +static bool sReadJPG(Stream &stream, GBitmap *bitmap); +static bool sWriteJPG(GBitmap *bitmap, Stream &stream, U32 compressionLevel); + +static struct _privateRegisterJPG +{ + _privateRegisterJPG() + { + GBitmap::Registration reg; + + reg.extensions.push_back( "jpeg" ); + reg.extensions.push_back( "jpg" ); + + reg.readFunc = sReadJPG; + reg.writeFunc = sWriteJPG; + + GBitmap::sRegisterFormat( reg ); + } +} sStaticRegisterJPG; + +//-------------------------------------- Replacement I/O for standard LIBjpeg +// functions. we don't wanna use +// FILE*'s... +static int jpegReadDataFn(void *client_data, unsigned char *data, int length) +{ + Stream *stream = (Stream*)client_data; + AssertFatal(stream != NULL, "jpegReadDataFn::No stream."); + int pos = stream->getPosition(); + if (stream->read(length, data)) + return length; + + if (stream->getStatus() == Stream::EOS) + return (stream->getPosition()-pos); + else + return 0; +} + + +//-------------------------------------- +static int jpegWriteDataFn(void *client_data, unsigned char *data, int length) +{ + Stream *stream = (Stream*)client_data; + AssertFatal(stream != NULL, "jpegWriteDataFn::No stream."); + if (stream->write(length, data)) + return length; + else + return 0; +} + + +//-------------------------------------- +static int jpegFlushDataFn(void *) +{ + // do nothing since we can't flush the stream object + return 0; +} + + +//-------------------------------------- +static int jpegErrorFn(void *client_data) +{ + Stream *stream = (Stream*)client_data; + AssertFatal(stream != NULL, "jpegErrorFn::No stream."); + return (stream->getStatus() != Stream::Ok); +} + + +//-------------------------------------- +static bool sReadJPG(Stream &stream, GBitmap *bitmap) +{ + JFREAD = jpegReadDataFn; + JFERROR = jpegErrorFn; + + jpeg_decompress_struct cinfo; + jpeg_error_mgr jerr; + + // We set up the normal JPEG error routines, then override error_exit. + //cinfo.err = jpeg_std_error(&jerr.pub); + //jerr.pub.error_exit = my_error_exit; + + // if (setjmp(jerr.setjmp_buffer)) + // { + // // If we get here, the JPEG code has signaled an error. + // // We need to clean up the JPEG object, close the input file, and return. + // jpeg_destroy_decompress(&cinfo); + // return false; + // } + + + cinfo.err = jpeg_std_error(&jerr); // set up the normal JPEG error routines. + cinfo.client_data = (void*)&stream; // set the stream into the client_data + + // Now we can initialize the JPEG decompression object. + jpeg_create_decompress(&cinfo); + + jpeg_stdio_src(&cinfo); + + // Read file header, set default decompression parameters + jpeg_read_header(&cinfo, true); + + GFXFormat format; + switch (cinfo.out_color_space) + { + case JCS_GRAYSCALE: format = GFXFormatA8; break; + case JCS_RGB: format = GFXFormatR8G8B8; break; + default: + jpeg_destroy_decompress(&cinfo); + return false; + } + + // Start decompressor + jpeg_start_decompress(&cinfo); + + // allocate the bitmap space and init internal variables... + bitmap->allocateBitmap(cinfo.output_width, cinfo.output_height, false, format); + + // Set up the row pointers... + U32 rowBytes = cinfo.output_width * cinfo.output_components; + + U8* pBase = (U8*)bitmap->getBits(); + for (U32 i = 0; i < bitmap->getHeight(); i++) + { + JSAMPROW rowPointer = pBase + (i * rowBytes); + jpeg_read_scanlines(&cinfo, &rowPointer, 1); + } + + // Finish decompression + jpeg_finish_decompress(&cinfo); + + // Release JPEG decompression object + // This is an important step since it will release a good deal of memory. + jpeg_destroy_decompress(&cinfo); + + // We know JPEG's don't have any transparency + bitmap->setHasTransparency(false); + + return true; +} + + +//-------------------------------------------------------------------------- +static bool sWriteJPG(GBitmap *bitmap, Stream &stream, U32 compressionLevel) +{ + TORQUE_UNUSED(compressionLevel); // compression level not currently hooked up + + GFXFormat format = bitmap->getFormat(); + + // JPEG format does not support transparency so any image + // in Alpha format should be saved as a grayscale which coincides + // with how the readJPEG function will read-in a JPEG. So the + // only formats supported are RGB and Alpha, not RGBA. + AssertFatal(format == GFXFormatR8G8B8 || format == GFXFormatA8, + "GBitmap::writeJPEG: ONLY RGB bitmap writing supported at this time."); + if (format != GFXFormatR8G8B8 && format != GFXFormatA8) + return false; + + // maximum image size allowed + #define MAX_HEIGHT 4096 + if (bitmap->getHeight() > MAX_HEIGHT) + return false; + + // Bind our own stream writing, error, and memory flush functions + // to the jpeg library interface + JFWRITE = jpegWriteDataFn; + JFFLUSH = jpegFlushDataFn; + JFERROR = jpegErrorFn; + + // Allocate and initialize our jpeg compression structure and error manager + jpeg_compress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); // set up the normal JPEG error routines. + cinfo.client_data = (void*)&stream; // set the stream into the client_data + jpeg_create_compress(&cinfo); // allocates a small amount of memory + + // specify the destination for the compressed data(our stream) + jpeg_stdio_dest(&cinfo); + + // set the image properties + cinfo.image_width = bitmap->getWidth(); // image width + cinfo.image_height = bitmap->getHeight(); // image height + cinfo.input_components = bitmap->getBytesPerPixel(); // samples per pixel(RGB:3, Alpha:1) + + switch (format) + { + case GFXFormatA8: // no alpha support in JPEG format, so turn it into a grayscale + cinfo.in_color_space = JCS_GRAYSCALE; + break; + case GFXFormatR8G8B8: // otherwise we are writing in RGB format + cinfo.in_color_space = JCS_RGB; + break; + default: + AssertFatal( false, "Format not handled in GBitmap::writeJPEG() switch" ); + break; + } + // use default compression params(75% compression) + jpeg_set_defaults(&cinfo); + + // begin JPEG compression cycle + jpeg_start_compress(&cinfo, true); + + // Set up the row pointers... + U32 rowBytes = cinfo.image_width * cinfo.input_components; + + U8* pBase = (U8*)bitmap->getBits(); + for (U32 i = 0; i < bitmap->getHeight(); i++) + { + // write the image data + JSAMPROW rowPointer = pBase + (i * rowBytes); + jpeg_write_scanlines(&cinfo, &rowPointer, 1); + } + + // complete the compression cycle + jpeg_finish_compress(&cinfo); + + // release the JPEG compression object + jpeg_destroy_compress(&cinfo); + + // return success + return true; +} diff --git a/gfx/bitmap/loaders/bitmapMng.cpp b/gfx/bitmap/loaders/bitmapMng.cpp new file mode 100644 index 0000000..08e6610 --- /dev/null +++ b/gfx/bitmap/loaders/bitmapMng.cpp @@ -0,0 +1,404 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/stream.h" + +#include "gfx/bitmap/gBitmap.h" + +#include "core/color.h" + +#define MNG_NO_CMS +#define MNG_SUPPORT_READ +#define MNG_SUPPORT_WRITE +#define MNG_SUPPORT_DISPLAY +#define MNG_STORE_CHUNKS +#define MNG_ACCESS_CHUNKS + +#include "lmng/libmng.h" + + + +static bool sReadMNG(Stream &stream, GBitmap *bitmap); +static bool sWriteMNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel); + +static struct _privateRegisterMNG +{ + _privateRegisterMNG() + { + GBitmap::Registration reg; + + reg.extensions.push_back( "jng" ); + reg.extensions.push_back( "mng" ); + + reg.readFunc = sReadMNG; + reg.writeFunc = sWriteMNG; + + GBitmap::sRegisterFormat( reg ); + } +} sStaticRegisterMNG; + + +typedef struct +{ + GBitmap* image; + Stream* stream; +} mngstuff; + +static mng_ptr mngMallocFn(mng_size_t size) +{ + mng_ptr data = dMalloc(size); + return dMemset(data, 0, size); +} + +static void mngFreeFn(mng_ptr p, mng_size_t size) +{ + dFree(p); +} + +static mng_bool mngOpenDataFn(mng_handle mng) +{ + return MNG_TRUE; +} + +static mng_bool mngCloseDataFn(mng_handle mng) +{ + return MNG_TRUE; +} + +static mng_bool mngReadDataFn(mng_handle mng, mng_ptr data, mng_uint32 length, mng_uint32 *bytesread) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + AssertFatal(mymng->stream != NULL, "No stream?"); + + bool success = mymng->stream->read(length, data); + *bytesread = length; // stupid hack + + AssertFatal(success, "MNG read catastrophic error!"); + if(success) + return MNG_TRUE; + else + return MNG_FALSE; +} + +#if 0 +// CodeReview - until we can write these, get rid of warning by disabling method. +static mng_bool mngWriteDataFn(mng_handle mng, mng_ptr data, mng_uint32 length, mng_uint32 *iWritten) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + AssertFatal(mymng->stream != NULL, "No stream?"); + + bool success = mymng->stream->write(length, data); + *iWritten = length; // stupid hack + + AssertFatal(success, "MNG write catastrophic error!"); + if(success) + return MNG_TRUE; + else + return MNG_FALSE; +} +#endif + +static mng_bool mngProcessHeaderFn(mng_handle mng, mng_uint32 width, mng_uint32 height) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + + GFXFormat format; + mng_uint8 colorType = mng_get_colortype(mng); + mng_uint8 alphaDepth = mng_get_alphadepth(mng); + switch(colorType) + { + case MNG_COLORTYPE_GRAY: + case MNG_COLORTYPE_JPEGGRAY: + format = GFXFormatR8G8B8; + mng_set_canvasstyle(mng, MNG_CANVAS_RGB8); + break; + + case MNG_COLORTYPE_INDEXED: + if(alphaDepth >= 1) + { + format = GFXFormatR8G8B8A8; + mng_set_canvasstyle(mng, MNG_CANVAS_RGBA8); + } + else + { + format = GFXFormatR8G8B8; + mng_set_canvasstyle(mng, MNG_CANVAS_RGB8); + } + + case MNG_COLORTYPE_RGB: + case MNG_COLORTYPE_JPEGCOLOR: + if(alphaDepth >= 1) + { + format = GFXFormatR8G8B8A8; + mng_set_canvasstyle(mng, MNG_CANVAS_RGBA8); + } + else + { + format = GFXFormatR8G8B8; + mng_set_canvasstyle(mng, MNG_CANVAS_RGB8); + } + break; + + case MNG_COLORTYPE_RGBA: + case MNG_COLORTYPE_JPEGCOLORA: + format = GFXFormatR8G8B8A8; + mng_set_canvasstyle(mng, MNG_CANVAS_RGBA8); + break; + + default: + // This case should never get hit, however it resolves a compiler + // warning + format = GFXFormat_FIRST; + AssertISV( false, "Unknown color format in bitmap MNG Loading" ); + } + + mymng->image->allocateBitmap(width, height, false, format); + return MNG_TRUE; +} + +static mng_ptr mngCanvasLineFn(mng_handle mng, mng_uint32 line) +{ + mngstuff *mymng = (mngstuff *)mng_get_userdata(mng); + return (mng_ptr) mymng->image->getAddress(0, line); +} + +static mng_bool mngRefreshFn(mng_handle mng, mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h) +{ + return MNG_TRUE; +} + +static mng_uint32 mngGetTicksFn(mng_handle mng) +{ + return 0; +} + +static mng_bool mngSetTimerFn(mng_handle mng, mng_uint32 msecs) +{ + return MNG_TRUE; +} + +static mng_bool mngFatalErrorFn(mng_handle mng, mng_int32 code, mng_int8 severity, mng_chunkid chunktype, mng_uint32 chunkseq, mng_int32 extra1, mng_int32 extra2, mng_pchar text) +{ + mng_cleanup(&mng); + + AssertISV(false, avar("Error reading MNG file:\n %s", (const char*)text)); + return MNG_FALSE; +} + +static bool sReadMNG(Stream &stream, GBitmap *bitmap) +{ + mngstuff mnginfo; + dMemset(&mnginfo, 0, sizeof(mngstuff)); + + mng_handle mng = mng_initialize(&mnginfo, mngMallocFn, mngFreeFn, MNG_NULL); + if(mng == NULL) + return false; + + // setup the callbacks + mng_setcb_errorproc(mng, mngFatalErrorFn); + mng_setcb_openstream(mng, mngOpenDataFn); + mng_setcb_closestream(mng, mngCloseDataFn); + mng_setcb_readdata(mng, mngReadDataFn); + mng_setcb_processheader(mng, mngProcessHeaderFn); + mng_setcb_getcanvasline(mng, mngCanvasLineFn); + mng_setcb_refresh(mng, mngRefreshFn); + mng_setcb_gettickcount(mng, mngGetTicksFn); + mng_setcb_settimer(mng, mngSetTimerFn); + + mnginfo.image = bitmap; + mnginfo.stream = &stream; + + mng_read(mng); + mng_display(mng); + + // hacks :( + // libmng doesn't support returning data in gray/gray alpha format, + // so we grab as RGB/RGBA and just cut off the g and b + mng_uint8 colorType = mng_get_colortype(mng); + switch(colorType) + { + case MNG_COLORTYPE_GRAY: + case MNG_COLORTYPE_JPEGGRAY: + { + GBitmap temp(*bitmap); + bitmap->deleteImage(); + bitmap->allocateBitmap(temp.getWidth(), temp.getHeight(), false, GFXFormatA8); + + // force getColor to read in in the same color value for each channel + // since the gray colortype has the real alpha in the first channel + temp.setFormat( GFXFormatA8 ); + + ColorI color; + for(U32 row = 0; row < bitmap->getHeight(); row++) + { + for(U32 col = 0; col < bitmap->getWidth(); col++) + { + temp.getColor(col, row, color); + bitmap->setColor(col, row, color); + } + } + } + + break; + } + + mng_cleanup(&mng); + + // Check this bitmap for transparency + bitmap->checkForTransparency(); + + return true; +} + +static bool sWriteMNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel) +{ + TORQUE_UNUSED( bitmap ); + TORQUE_UNUSED( stream ); + TORQUE_UNUSED( compressionLevel ); + + return false; +#if 0 + // ONLY RGB bitmap writing supported at this time! + AssertFatal(getFormat() == GFXFormatR8G8B8 || getFormat() == GFXFormatR8G8B8A8 || getFormat() == GFXFormatA8, "GBitmap::writeMNG: ONLY RGB bitmap writing supported at this time."); + if(getFormat() != GFXFormatR8G8B8 && getFormat() != GFXFormatR8G8B8A8 && getFormat() != GFXFormatA8) + return (false); + + // maximum image size allowed + #define MAX_HEIGHT 4096 + if(getHeight() >= MAX_HEIGHT) + return false; + + mngstuff mnginfo; + dMemset(&mnginfo, 0, sizeof(mngstuff)); + mng_handle mng = mng_initialize(&mnginfo, mngMallocFn, mngFreeFn, MNG_NULL); + if(mng == NULL) { + return false; + } + + // setup the callbacks + mng_setcb_openstream(mng, mngOpenDataFn); + mng_setcb_closestream(mng, mngCloseDataFn); + mng_setcb_writedata(mng, mngWriteDataFn); + + // create the file in memory + mng_create(mng); + + mng_putchunk_defi(mng, 0, 0, 0, MNG_FALSE, 0, 0, MNG_FALSE, 0, getWidth(), 0, getHeight()); + + mnginfo.image = (GBitmap*)this; + mnginfo.stream = &stream; + + switch(getFormat()) { + case GFXFormatA8: + mng_putchunk_ihdr(mng, getWidth(), getHeight(), + MNG_BITDEPTH_8, + MNG_COLORTYPE_GRAY, + MNG_COMPRESSION_DEFLATE, + MNG_FILTER_ADAPTIVE, + MNG_INTERLACE_NONE); + + // not implemented in lib yet + //mng_putimgdata_ihdr(mng, getWidth(), getHeight(), + // MNG_COLORTYPE_GRAY, + // MNG_BITDEPTH_8, + // MNG_COMPRESSION_DEFLATE, + // MNG_FILTER_ADAPTIVE, + // MNG_INTERLACE_NONE, + // MNG_CANVAS_GRAY8, mngCanvasLineFn); + break; + case GFXFormatR8G8B8: + mng_putchunk_ihdr(mng, getWidth(), getHeight(), + MNG_BITDEPTH_8, + MNG_COLORTYPE_RGB, + MNG_COMPRESSION_DEFLATE, + MNG_FILTER_ADAPTIVE, + MNG_INTERLACE_NONE); + + // not implemented in lib yet + //mng_putimgdata_ihdr(mng, getWidth(), getHeight(), + // MNG_COLORTYPE_RGB, + // MNG_BITDEPTH_8, + // MNG_COMPRESSION_DEFLATE, + // MNG_FILTER_ADAPTIVE, + // MNG_INTERLACE_NONE, + // MNG_CANVAS_RGB8, mngCanvasLineFn); + break; + case GFXFormatR8G8B8A8: + mng_putchunk_ihdr(mng, getWidth(), getHeight(), + MNG_BITDEPTH_8, + MNG_COLORTYPE_RGBA, + MNG_COMPRESSION_DEFLATE, + MNG_FILTER_ADAPTIVE, + MNG_INTERLACE_NONE); + + // not implemented in lib yet + //mng_putimgdata_ihdr(mng, getWidth(), getHeight(), + // MNG_COLORTYPE_RGBA, + // MNG_BITDEPTH_8, + // MNG_COMPRESSION_DEFLATE, + // MNG_FILTER_ADAPTIVE, + // MNG_INTERLACE_NONE, + // MNG_CANVAS_RGBA8, mngCanvasLineFn); + break; + } + + + // below is a hack until libmng is mature enough to handle this itself + //----------------------------------------------------------------------------- + + + U8 *tmpbuffer = new U8[this->byteSize + getHeight()]; + if(tmpbuffer == 0) + { + mng_cleanup(&mng); + return false; + } + + // transfer data, add filterbyte + U32 effwdt = getWidth() * this->bytesPerPixel; + for(U32 Row = 0; Row < getHeight(); Row++) + { + // first Byte in each scanline is filterbyte: currently 0 -> no filter + tmpbuffer[Row * (effwdt + 1)] = 0; + + // copy the scanline + dMemcpy(tmpbuffer + Row * (effwdt + 1) + 1, getAddress(0, Row), effwdt); + } + + // compress data with zlib + U8 *dstbuffer = new U8[this->byteSize + getHeight()]; + if(dstbuffer == 0) + { + delete [] tmpbuffer; + mng_cleanup(&mng); + return false; + } + + U32 dstbufferSize = this->byteSize + getHeight(); + if(Z_OK != compress2((Bytef*)dstbuffer,(uLongf*)&dstbufferSize, (const Bytef*)tmpbuffer, dstbufferSize, 9)) + { + delete [] tmpbuffer; + delete [] dstbuffer; + mng_cleanup(&mng); + return false; + } + + mng_putchunk_idat(mng, dstbufferSize, (mng_ptr*)dstbuffer); + + + //----------------------------------------------------------------------------- + + + mng_putchunk_iend(mng); + + delete [] tmpbuffer; + delete [] dstbuffer; + + mng_write(mng); + mng_cleanup(&mng); + + return true; +#endif +} diff --git a/gfx/bitmap/loaders/bitmapPng.cpp b/gfx/bitmap/loaders/bitmapPng.cpp new file mode 100644 index 0000000..351c55e --- /dev/null +++ b/gfx/bitmap/loaders/bitmapPng.cpp @@ -0,0 +1,486 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/fileStream.h" +#include "core/stream/memStream.h" +#include "core/strings/stringFunctions.h" + +#include "gfx/bitmap/gBitmap.h" + +#define PNG_INTERNAL 1 +#include +#include "lpng/png.h" +#include "zlib/zlib.h" + +#ifdef NULL +#undef NULL +#define NULL 0 +#endif + + +static bool sReadPNG(Stream &stream, GBitmap *bitmap); + +/// Compression levels for PNGs range from 0-9. +/// A value outside that range will cause the write routine to look for the best compression for a given PNG. This can be slow. +static bool sWritePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel); +static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter); + +static struct _privateRegisterPNG +{ + _privateRegisterPNG() + { + GBitmap::Registration reg; + + reg.extensions.push_back( "png" ); + + reg.readFunc = sReadPNG; + reg.writeFunc = sWritePNG; + reg.defaultCompression = 6; + + GBitmap::sRegisterFormat( reg ); + } +} sStaticRegisterPNG; + + +//-------------------------------------- Replacement I/O for standard LIBPng +// functions. we don't wanna use +// FILE*'s... +static void pngReadDataFn(png_structp png_ptr, + png_bytep data, + png_size_t length) +{ + AssertFatal(png_ptr->io_ptr != NULL, "No stream?"); + + Stream *strm = (Stream*)png_ptr->io_ptr; + bool success = strm->read(length, data); + AssertFatal(success, "pngReadDataFn - failed to read from stream!"); +} + + +//-------------------------------------- +static void pngWriteDataFn(png_structp png_ptr, + png_bytep data, + png_size_t length) +{ + AssertFatal(png_ptr->io_ptr != NULL, "No stream?"); + + Stream *strm = (Stream*)png_ptr->io_ptr; + bool success = strm->write(length, data); + AssertFatal(success, "pngWriteDataFn - failed to write to stream!"); +} + + +//-------------------------------------- +static void pngFlushDataFn(png_structp /*png_ptr*/) +{ + // +} + +static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size) +{ + return FrameAllocator::alloc(size); +} + +static void pngFreeFn(png_structp /*png_ptr*/, png_voidp /*mem*/) +{ +} + +static png_voidp pngRealMallocFn(png_structp /*png_ptr*/, png_size_t size) +{ + return (png_voidp)dMalloc(size); +} + +static void pngRealFreeFn(png_structp /*png_ptr*/, png_voidp mem) +{ + dFree(mem); +} + +//-------------------------------------- +static void pngFatalErrorFn(png_structp /*png_ptr*/, + png_const_charp pMessage) +{ + AssertISV(false, avar("Error reading PNG file:\n %s", pMessage)); +} + + +//-------------------------------------- +static void pngWarningFn(png_structp, png_const_charp /*pMessage*/) +{ + // AssertWarn(false, avar("Warning reading PNG file:\n %s", pMessage)); +} + + +//-------------------------------------- +static bool sReadPNG(Stream &stream, GBitmap *bitmap) +{ + static const U32 cs_headerBytesChecked = 8; + + U8 header[cs_headerBytesChecked]; + stream.read(cs_headerBytesChecked, header); + + bool isPng = png_check_sig(header, cs_headerBytesChecked) != 0; + if (isPng == false) + { + AssertWarn(false, "GBitmap::readPNG: stream doesn't contain a PNG"); + return false; + } + + U32 prevWaterMark = FrameAllocator::getWaterMark(); + png_structp png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, + NULL, + pngFatalErrorFn, + pngWarningFn, + NULL, + pngRealMallocFn, + pngRealFreeFn); + + if (png_ptr == NULL) + { + FrameAllocator::setWaterMark(prevWaterMark); + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + png_destroy_read_struct(&png_ptr, + (png_infopp)NULL, + (png_infopp)NULL); + + FrameAllocator::setWaterMark(prevWaterMark); + return false; + } + + png_infop end_info = png_create_info_struct(png_ptr); + if (end_info == NULL) + { + png_destroy_read_struct(&png_ptr, + &info_ptr, + (png_infopp)NULL); + + FrameAllocator::setWaterMark(prevWaterMark); + return false; + } + + png_set_read_fn(png_ptr, &stream, pngReadDataFn); + + // Read off the info on the image. + png_set_sig_bytes(png_ptr, cs_headerBytesChecked); + png_read_info(png_ptr, info_ptr); + + // OK, at this point, if we have reached it ok, then we can reset the + // image to accept the new data... + // + bitmap->deleteImage(); + + png_uint_32 width; + png_uint_32 height; + S32 bit_depth; + S32 color_type; + + png_get_IHDR(png_ptr, info_ptr, + &width, &height, // obv. + &bit_depth, &color_type, // obv. + NULL, // interlace + NULL, // compression_type + NULL); // filter_type + + // First, handle the color transformations. We need this to read in the + // data as RGB or RGBA, _always_, with a maximal channel width of 8 bits. + // + bool transAlpha = false; + GFXFormat format = GFXFormatR8G8B8; + + // Strip off any 16 bit info + // + if (bit_depth == 16 && color_type != PNG_COLOR_TYPE_GRAY) + { + png_set_strip_16(png_ptr); + } + + // Expand a transparency channel into a full alpha channel... + // + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + { + png_set_expand(png_ptr); + transAlpha = true; + } + + if (color_type == PNG_COLOR_TYPE_PALETTE) + { + png_set_expand(png_ptr); + format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; + } + else if (color_type == PNG_COLOR_TYPE_GRAY) + { + png_set_expand(png_ptr); + + if (bit_depth == 16) + format = GFXFormatR5G6B5; + else + format = GFXFormatA8; + } + else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + png_set_expand(png_ptr); + png_set_gray_to_rgb(png_ptr); + format = GFXFormatR8G8B8A8; + } + else if (color_type == PNG_COLOR_TYPE_RGB) + { + format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8; + png_set_expand(png_ptr); + } + else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) + { + png_set_expand(png_ptr); + format = GFXFormatR8G8B8A8; + } + + // Update the info pointer with the result of the transformations + // above... + png_read_update_info(png_ptr, info_ptr); + + png_uint_32 rowBytes = png_get_rowbytes(png_ptr, info_ptr); + if (format == GFXFormatR8G8B8) + { + AssertFatal(rowBytes == width * 3, + "Error, our rowbytes are incorrect for this transform... (3)"); + } + else if (format == GFXFormatR8G8B8A8) + { + AssertFatal(rowBytes == width * 4, + "Error, our rowbytes are incorrect for this transform... (4)"); + } + else if (format == GFXFormatR5G6B5) + { + AssertFatal(rowBytes == width * 2, + "Error, our rowbytes are incorrect for this transform... (2)"); + } + + // actually allocate the bitmap space... + bitmap->allocateBitmap(width, height, + false, // don't extrude miplevels... + format); // use determined format... + + // Set up the row pointers... + png_bytep* rowPointers = new png_bytep[ height ]; + U8* pBase = (U8*)bitmap->getBits(); + + for (U32 i = 0; i < height; i++) + rowPointers[i] = pBase + (i * rowBytes); + + // And actually read the image! + png_read_image(png_ptr, rowPointers); + + // We're outta here, destroy the png structs, and release the lock + // as quickly as possible... + //png_read_end(png_ptr, end_info); + delete [] rowPointers; + png_read_end(png_ptr, NULL); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + + // Ok, the image is read in, now we need to finish up the initialization, + // which means: setting up the detailing members, init'ing the palette + // key, etc... + // + // actually, all of that was handled by allocateBitmap, so we're outta here + // + + // Check this bitmap for transparency + bitmap->checkForTransparency(); + + FrameAllocator::setWaterMark(prevWaterMark); + + return true; +} + +//-------------------------------------------------------------------------- +static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter) +{ + GFXFormat format = bitmap->getFormat(); + + // ONLY RGB bitmap writing supported at this time! + AssertFatal( format == GFXFormatR8G8B8 || + format == GFXFormatR8G8B8A8 || + format == GFXFormatR8G8B8X8 || + format == GFXFormatA8 || + format == GFXFormatR5G6B5, "_writePNG: ONLY RGB bitmap writing supported at this time."); + + if ( format != GFXFormatR8G8B8 && + format != GFXFormatR8G8B8A8 && + format != GFXFormatR8G8B8X8 && + format != GFXFormatA8 && + format != GFXFormatR5G6B5 ) + return false; + + png_structp png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, + NULL, + pngFatalErrorFn, + pngWarningFn, + NULL, + pngMallocFn, + pngFreeFn); + if (png_ptr == NULL) + return (false); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + return false; + } + + png_set_write_fn(png_ptr, &stream, pngWriteDataFn, pngFlushDataFn); + + // Set the compression level, image filters, and compression strategy... + png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_STRATEGY; + png_ptr->zlib_strategy = strategy; + png_set_compression_window_bits(png_ptr, 15); + png_set_compression_level(png_ptr, compressionLevel); + png_set_filter(png_ptr, 0, filter); + + // Set the image information here. Width and height are up to 2^31, + // bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on + // the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY, + // PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB, + // or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or + // PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST + // currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED + + U32 width = bitmap->getWidth(); + U32 height = bitmap->getHeight(); + + if (format == GFXFormatR8G8B8) + { + png_set_IHDR(png_ptr, info_ptr, + width, height, // the width & height + 8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type, + NULL, // no interlace + NULL, // compression type + NULL); // filter type + } + else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8) + { + png_set_IHDR(png_ptr, info_ptr, + width, height, // the width & height + 8, PNG_COLOR_TYPE_RGB_ALPHA, // bit_depth, color_type, + NULL, // no interlace + NULL, // compression type + NULL); // filter type + } + else if (format == GFXFormatA8) + { + png_set_IHDR(png_ptr, info_ptr, + width, height, // the width & height + 8, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type, + NULL, // no interlace + NULL, // compression type + NULL); // filter type + } + else if (format == GFXFormatR5G6B5) + { + png_set_IHDR(png_ptr, info_ptr, + width, height, // the width & height + 16, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type, + PNG_INTERLACE_NONE, // no interlace + PNG_COMPRESSION_TYPE_DEFAULT, // compression type + PNG_FILTER_TYPE_DEFAULT); // filter type + + png_color_8_struct sigBit = { 0 }; + sigBit.gray = 16; + png_set_sBIT(png_ptr, info_ptr, &sigBit ); + + png_set_swap( png_ptr ); + } + + png_write_info(png_ptr, info_ptr); + FrameAllocatorMarker marker; + png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) ); + for (U32 i=0; i(bitmap->getAddress(0, i)); + + png_write_image(png_ptr, row_pointers); + + // Write S3TC data if present... + // Write FXT1 data if present... + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + + return true; +} + + +//-------------------------------------------------------------------------- +static bool sWritePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel) +{ + U32 waterMark = FrameAllocator::getWaterMark(); + + if ( compressionLevel < 10 ) + { + bool retVal = _writePNG(bitmap, stream, compressionLevel, 0, PNG_ALL_FILTERS); + FrameAllocator::setWaterMark(waterMark); + return retVal; + } + + // check all our methods of compression to find the best one and use it + U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough... + MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true); + + const U32 zStrategies[] = { Z_DEFAULT_STRATEGY, + Z_FILTERED }; + const U32 pngFilters[] = { PNG_FILTER_NONE, + PNG_FILTER_SUB, + PNG_FILTER_UP, + PNG_FILTER_AVG, + PNG_FILTER_PAETH, + PNG_ALL_FILTERS }; + + U32 minSize = 0xFFFFFFFF; + U32 bestStrategy = 0xFFFFFFFF; + U32 bestFilter = 0xFFFFFFFF; + U32 bestCLevel = 0xFFFFFFFF; + + for (U32 cl = 0; cl <=9; cl++) + { + for (U32 zs = 0; zs < 2; zs++) + { + for (U32 pf = 0; pf < 6; pf++) + { + pMemStream->setPosition(0); + + U32 waterMarkInner = FrameAllocator::getWaterMark(); + + if (_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf]) == false) + AssertFatal(false, "Handle this error!"); + + FrameAllocator::setWaterMark(waterMarkInner); + + if (pMemStream->getPosition() < minSize) + { + minSize = pMemStream->getPosition(); + bestStrategy = zs; + bestFilter = pf; + bestCLevel = cl; + } + } + } + } + AssertFatal(minSize != 0xFFFFFFFF, "Error, no best found?"); + + delete pMemStream; + delete [] buffer; + + + bool retVal = _writePNG(bitmap, stream, + bestCLevel, + zStrategies[bestStrategy], + pngFilters[bestFilter]); + FrameAllocator::setWaterMark(waterMark); + + return retVal; +} + diff --git a/gfx/bitmap/loaders/bitmapTga.cpp b/gfx/bitmap/loaders/bitmapTga.cpp new file mode 100644 index 0000000..db4dd45 --- /dev/null +++ b/gfx/bitmap/loaders/bitmapTga.cpp @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// TGA Loader +// Copyright 2009 3D Interactive Inc. All Rights Reserved +// +// Only known to work with Purelight's lightmap tga files. +//----------------------------------------------------------------------------- + +#include "core/stream/stream.h" + +#include "gfx/bitmap/gBitmap.h" + + +static bool sReadTGA(Stream &stream, GBitmap *bitmap); +static bool sWriteTGA(GBitmap *bitmap, Stream &stream, U32 compressionLevel); + +static struct _privateRegisterTGA +{ + _privateRegisterTGA() + { + GBitmap::Registration reg; + + reg.extensions.push_back( "tga" ); + + reg.readFunc = sReadTGA; + reg.writeFunc = sWriteTGA; + + GBitmap::sRegisterFormat( reg ); + } +} sStaticRegisterTGA; + + +//------------------------------------------------------------------------------ +//-------------------------------------- Supplementary I/O (Partially located in +// bitmapPng.cc) +// + +static bool sReadTGA(Stream &stream, GBitmap *bitmap) +{ + U8 idLength; + stream.read(&idLength); + + U8 colormapType; + stream.read(&colormapType); + + U8 imageType; //2 = rgb, not sure what the other types are + stream.read(&imageType); + + //Not sure what these are, none of our tgas use them + U16 colormapStart; + stream.read(&colormapStart); + U16 colormapLength; + stream.read(&colormapLength); + U8 colormapDepth; + stream.read(&colormapDepth); + + //Never seen these actually used + U16 xOrigin; + stream.read(&xOrigin); + U16 yOrigin; + stream.read(&yOrigin); + + U16 width; + stream.read(&width); + U16 height; + stream.read(&height); + + U8 colorDepth; + stream.read(&colorDepth); + + U8 imageDescriptor; //always 0 for us + stream.read(&imageDescriptor); + + + GFXFormat fmt; + if(colorDepth == 24) + { + fmt = GFXFormatR8G8B8; + } + else if(colorDepth == 32) + { + fmt = GFXFormatR8G8B8A8; + } + else + { + //not sure how to handle other color depths + fmt = GFXFormatR8G8B8; + } + + bitmap->allocateBitmap(width, height, false, fmt); + U32 bmpWidth = bitmap->getWidth(); + U32 bmpHeight = bitmap->getHeight(); + U32 bmpBytesPerPixel = bitmap->getBytesPerPixel(); + + for(U32 row = 0; row < bmpHeight; row++) + { + U8* rowDest = bitmap->getAddress(0, bmpHeight - row - 1); + stream.read(bmpBytesPerPixel * bmpWidth, rowDest); + } + + if(bmpBytesPerPixel == 3 || bmpBytesPerPixel == 4) // do BGR swap + { + U8* ptr = bitmap->getAddress(0, 0); + for(int i = 0; i < bmpWidth * bmpHeight; i++) + { + U8 tmp = ptr[0]; + ptr[0] = ptr[2]; + ptr[2] = tmp; + ptr += bmpBytesPerPixel; + } + } + + //32 bit tgas have an alpha channel + bitmap->setHasTransparency(colorDepth == 32); + + return true; +} + +static bool sWriteTGA(GBitmap *bitmap, Stream &stream, U32 compressionLevel) +{ + AssertISV(false, "GBitmap::writeTGA - doesn't support writing tga files!") + + return false; +} diff --git a/gfx/gFont.cpp b/gfx/gFont.cpp new file mode 100644 index 0000000..abbf1db --- /dev/null +++ b/gfx/gFont.cpp @@ -0,0 +1,1269 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/resourceManager.h" +#include "core/stream/fileStream.h" +#include "core/strings/unicode.h" +#include "core/strings/findMatch.h" +#include "core/strings/stringFunctions.h" +#include "core/util/endian.h" +#include "core/util/safeDelete.h" + +#include "console/console.h" + +#include "gfx/gFont.h" + +#include "platform/threads/mutex.h" + +#include "zlib/zlib.h" + + +GFX_ImplementTextureProfile(GFXFontTextureProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::Static | + GFXTextureProfile::KeepBitmap | + GFXTextureProfile::NoMipmap, + GFXTextureProfile::None); + +template<> void *Resource::create(const Torque::Path &path) +{ +#ifdef TORQUE_DEBUG_RES_MANAGER + Con::printf( "Resource::create - [%s]", path.getFullPath().c_str() ); +#endif + + return GFont::load( path ); +} + +template<> ResourceBase::Signature Resource::signature() +{ + return MakeFourCC('f','o','n','t'); +} + +/// Used for repacking in GFont::importStrip. +struct GlyphMap +{ + U32 charId; + GBitmap *bitmap; +}; + +static S32 QSORT_CALLBACK GlyphMapCompare(const void *a, const void *b) +{ + S32 ha = ((GlyphMap *) a)->bitmap->getHeight(); + S32 hb = ((GlyphMap *) b)->bitmap->getHeight(); + + return hb - ha; +} + + +const U32 GFont::csm_fileVersion = 3; + +String GFont::getFontCacheFilename(const String &faceName, U32 size) +{ + return String::ToString("%s/%s %d (%s).uft", + Con::getVariable("$GUI::fontCacheDirectory"), faceName.c_str(), size, getCharSetName(0)); +} + +GFont* GFont::load( const Torque::Path& path ) +{ + FileStream stream; + + stream.open( path.getFullPath(), Torque::FS::File::Read ); + if ( stream.getStatus() != Stream::Ok ) + return NULL; + + GFont *ret = new GFont; + ret->mGFTFile = path; + + if(!ret->read(stream)) + { + Con::errorf( "GFont::load - error reading '%s'", path.getFullPath().c_str() ); + SAFE_DELETE(ret); + } + else + { +#ifndef TORQUE_OS_XENON + PlatformFont *platFont = createPlatformFont(ret->getFontFaceName(), ret->getFontSize(), ret->getFontCharSet()); + + if ( platFont == NULL ) + { + Con::errorf( "GFont::load - error creating platform font for '%s'", path.getFullPath().c_str() ); + SAFE_DELETE(ret); + } + else + ret->setPlatformFont(platFont); +#endif + } + + return ret; +} + +Resource GFont::create(const String &faceName, U32 size, const char *cacheDirectory, U32 charset /* = TGE_ANSI_CHARSET */) +{ + if( !cacheDirectory ) + cacheDirectory = Con::getVariable( "$GUI::fontCacheDirectory" ); + + const Torque::Path path( String::ToString("%s/%s %d (%s).uft", + cacheDirectory, faceName.c_str(), size, getCharSetName(charset)) ); + + Resource ret; + + // If the file already exists attempt to load it + if (Platform::isFile(path.getFullPath().c_str())) + { + ret = ResourceManager::get().load(path); + + if (ret != NULL) + { + ret->mGFTFile = path; + return ret; + } + } + + // Otherwise attempt to have the platform generate a new font + PlatformFont *platFont = createPlatformFont(faceName, size, charset); + + if (platFont == NULL) + { + String fontName; + +#ifdef _XBOX + //AssertFatal( false, "Font creation is not supported on the Xbox platform. Create the font files (*.uft) using the Windows/MacOS build of the project." ); + if(!faceName.equal("arial", String::NoCase) || size != 14) + { + return create("Arial", 14, cacheDirectory, charset); + } + return ret; +#endif + + // Couldn't load the requested font. This probably will be common + // since many unix boxes don't have arial or lucida console installed. + // Attempt to map the font name into a font we're pretty sure exist + // Lucida Console is a common code & console font on windows, and + // Monaco is the recommended code & console font on mac. + if (faceName.equal("arial", String::NoCase)) + fontName = "Helvetica"; + else if (faceName.equal("lucida console", String::NoCase)) + fontName = "Monaco"; + else if (faceName.equal("monaco", String::NoCase)) + fontName = "Courier"; + else + return ret; + + return create(fontName, size, cacheDirectory, charset); + } + + // Create the actual GFont and set some initial properties + GFont *font = new GFont; + font->mPlatformFont = platFont; + font->mGFTFile = path; + font->mFaceName = faceName; + font->mSize = size; + font->mCharSet = charset; + + font->mHeight = platFont->getFontHeight(); + font->mBaseline = platFont->getFontBaseLine(); + font->mAscent = platFont->getFontBaseLine(); + font->mDescent = platFont->getFontHeight() - platFont->getFontBaseLine(); + + // Flag it to save when we exit + font->mNeedSave = true; + + // Load the newly created font into the ResourceManager + ret.setResource(ResourceManager::get().load(path), font); + + return ret; +} + +//------------------------------------------------------------------------- + +GFont::GFont() +{ + VECTOR_SET_ASSOCIATION(mCharInfoList); + VECTOR_SET_ASSOCIATION(mTextureSheets); + + for (U32 i = 0; i < (sizeof(mRemapTable) / sizeof(S32)); i++) + mRemapTable[i] = -1; + + mCurX = mCurY = mCurSheet = -1; + + mPlatformFont = NULL; + mSize = 0; + mCharSet = 0; + mNeedSave = false; + + mMutex = Mutex::createMutex(); +} + +GFont::~GFont() +{ + if(mNeedSave) + { + AssertFatal( mGFTFile.getFullPath().isNotEmpty(), "GFont::~GFont - path not set" ); + + FileStream stream; + stream.open(mGFTFile, Torque::FS::File::Write); + + if(stream.getStatus() == Stream::Ok) + write(stream); + + stream.close(); + } + + S32 i; + + for(i = 0;i < mCharInfoList.size();i++) + { + SAFE_DELETE_ARRAY(mCharInfoList[i].bitmapData); + } + + for(i=0; imapEnd) mapEnd = i; + } + } + + + // Let's write out all the info we can on this font. + Con::printf(" '%s' %dpt", mFaceName.c_str(), mSize); + Con::printf(" - %d texture sheets, %d mapped characters.", mTextureSheets.size(), mapCount); + + if(mapCount) + Con::printf(" - Codepoints range from 0x%x to 0x%x.", mapBegin, mapEnd); + else + Con::printf(" - No mapped codepoints.", mapBegin, mapEnd); + Con::printf(" - Platform font is %s.", (mPlatformFont ? "present" : "not present") ); +} + +//----------------------------------------------------------------------------- + +bool GFont::loadCharInfo(const UTF16 ch) +{ + PROFILE_SCOPE(GFont_loadCharInfo); + + if(mRemapTable[ch] != -1) + return true; // Not really an error + + if(mPlatformFont && mPlatformFont->isValidChar(ch)) + { + Mutex::lockMutex(mMutex); // the CharInfo returned by mPlatformFont is static data, must protect from changes. + PlatformFont::CharInfo &ci = mPlatformFont->getCharInfo(ch); + if(ci.bitmapData) + addBitmap(ci); + + mCharInfoList.push_back(ci); + mRemapTable[ch] = mCharInfoList.size() - 1; + + mNeedSave = true; + + Mutex::unlockMutex(mMutex); + return true; + } + + return false; +} + +void GFont::addBitmap(PlatformFont::CharInfo &charInfo) +{ + U32 nextCurX = U32(mCurX + charInfo.width ); /*7) & ~0x3;*/ + U32 nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + + // These are here for postmortem debugging. + bool routeA = false, routeB = false; + + if(mCurSheet == -1 || nextCurY >= TextureSheetSize) + { + routeA = true; + addSheet(); + + // Recalc our nexts. + nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; + nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + } + + if( nextCurX >= TextureSheetSize) + { + routeB = true; + mCurX = 0; + mCurY = nextCurY; + + // Recalc our nexts. + nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; + nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + } + + // Check the Y once more - sometimes we advance to a new row and run off + // the end. + if(nextCurY >= TextureSheetSize) + { + routeA = true; + addSheet(); + + // Recalc our nexts. + nextCurX = U32(mCurX + charInfo.width); // + 7) & ~0x3; + nextCurY = U32(mCurY + mPlatformFont->getFontHeight()); // + 7) & ~0x3; + } + + charInfo.bitmapIndex = mCurSheet; + charInfo.xOffset = mCurX; + charInfo.yOffset = mCurY; + + mCurX = nextCurX; + + S32 x, y; + GBitmap *bmp = mTextureSheets[mCurSheet].getBitmap(); + + AssertFatal(bmp->getFormat() == GFXFormatA8, "GFont::addBitmap - cannot added characters to non-greyscale textures!"); + + for(y = 0;y < charInfo.height;y++) + for(x = 0;x < charInfo.width;x++) + *bmp->getAddress(x + charInfo.xOffset, y + charInfo.yOffset) = charInfo.bitmapData[y * charInfo.width + x]; + + mTextureSheets[mCurSheet].refresh(); +} + +void GFont::addSheet() +{ + GBitmap *bitmap = new GBitmap(TextureSheetSize, TextureSheetSize, false, GFXFormatA8 ); + + // Set everything to transparent. + U8 *bits = bitmap->getWritableBits(); + dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize); + + GFXTexHandle handle = GFXTexHandle( bitmap, &GFXFontTextureProfile, true, avar("%s() - (line %d)", __FUNCTION__, __LINE__) ); + mTextureSheets.increment(); + constructInPlace(&mTextureSheets.last()); + mTextureSheets.last() = handle; + + mCurX = 0; + mCurY = 0; + mCurSheet = mTextureSheets.size() - 1; +} + +//----------------------------------------------------------------------------- + +const PlatformFont::CharInfo &GFont::getCharInfo(const UTF16 in_charIndex) +{ + PROFILE_SCOPE(GFont_getCharInfo); + + AssertFatal(in_charIndex, "GFont::getCharInfo - can't get info for char 0!"); + + if(mRemapTable[in_charIndex] == -1) + loadCharInfo(in_charIndex); + + AssertFatal(mRemapTable[in_charIndex] != -1, "No remap info for this character"); + + return mCharInfoList[mRemapTable[in_charIndex]]; +} + +const PlatformFont::CharInfo &GFont::getDefaultCharInfo() +{ + static PlatformFont::CharInfo c; + // c is initialized by the CharInfo default constructor. + return c; +} + +//----------------------------------------------------------------------------- + +U32 GFont::getStrWidth(const UTF8* in_pString) +{ + AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined"); + // If we ain't running debug... + if (in_pString == NULL || *in_pString == '\0') + return 0; + + return getStrNWidth(in_pString, dStrlen(in_pString)); +} + +U32 GFont::getStrWidthPrecise(const UTF8* in_pString) +{ + AssertFatal(in_pString != NULL, "GFont::getStrWidth: String is NULL, height is undefined"); + // If we ain't running debug... + if (in_pString == NULL) + return 0; + + return getStrNWidthPrecise(in_pString, dStrlen(in_pString)); +} + +//----------------------------------------------------------------------------- +U32 GFont::getStrNWidth(const UTF8 *str, U32 n) +{ + // UTF8 conversion is expensive. Avoid converting in a tight loop. + FrameTemp str16(n + 1); + convertUTF8toUTF16(str, str16, n + 1); + return getStrNWidth(str16, dStrlen(str16)); +} + +U32 GFont::getStrNWidth(const UTF16 *str, U32 n) +{ + AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL"); + + if (str == NULL || str[0] == '\0' || n == 0) + return 0; + + U32 totWidth = 0; + UTF16 curChar; + U32 charCount; + + for(charCount = 0; charCount < n; charCount++) + { + curChar = str[charCount]; + if(curChar == '\0') + break; + + if(isValidChar(curChar)) + { + const PlatformFont::CharInfo& rChar = getCharInfo(curChar); + totWidth += rChar.xIncrement; + } + else if (curChar == dT('\t')) + { + const PlatformFont::CharInfo& rChar = getCharInfo(dT(' ')); + totWidth += rChar.xIncrement * TabWidthInSpaces; + } + } + + return(totWidth); +} + +U32 GFont::getStrNWidthPrecise(const UTF8 *str, U32 n) +{ + FrameTemp str16(n + 1); + convertUTF8toUTF16(str, str16, n + 1); + return getStrNWidthPrecise(str16, dStrlen(str16)); +} + +U32 GFont::getStrNWidthPrecise(const UTF16 *str, U32 n) +{ + AssertFatal(str != NULL, "GFont::getStrNWidth: String is NULL"); + + if (str == NULL || str[0] == '\0' || n == 0) + return(0); + + U32 totWidth = 0; + UTF16 curChar; + U32 charCount = 0; + + for(charCount = 0; charCount < n; charCount++) + { + curChar = str[charCount]; + if(curChar == '\0') + break; + + if(isValidChar(curChar)) + { + const PlatformFont::CharInfo& rChar = getCharInfo(curChar); + totWidth += rChar.xIncrement; + } + else if (curChar == dT('\t')) + { + const PlatformFont::CharInfo& rChar = getCharInfo(dT(' ')); + totWidth += rChar.xIncrement * TabWidthInSpaces; + } + } + + UTF16 endChar = str[getMin(charCount,n-1)]; + + if (isValidChar(endChar)) + { + const PlatformFont::CharInfo& rChar = getCharInfo(endChar); + if (rChar.width != rChar.xIncrement) + totWidth += (rChar.width - rChar.xIncrement); + } + + return(totWidth); +} + +U32 GFont::getBreakPos(const UTF16 *str16, U32 slen, U32 width, bool breakOnWhitespace) +{ + // Some early out cases. + if(slen==0) + return 0; + + U32 ret = 0; + U32 lastws = 0; + UTF16 c; + U32 charCount = 0; + + for( charCount=0; charCount < slen; charCount++) + { + c = str16[charCount]; + if(c == '\0') + break; + + if(c == dT('\t')) + c = dT(' '); + + if(!isValidChar(c)) + { + ret++; + continue; + } + + if(c == dT(' ')) + lastws = ret+1; + + const PlatformFont::CharInfo& rChar = getCharInfo(c); + if(rChar.width > width || rChar.xIncrement > width) + { + if(lastws && breakOnWhitespace) + return lastws; + return ret; + } + + width -= rChar.xIncrement; + + ret++; + } + return ret; +} + +void GFont::wrapString(const UTF8 *txt, U32 lineWidth, Vector &startLineOffset, Vector &lineLen) +{ + // TODO: Is this error still true? + //Con::errorf("GFont::wrapString(): Not yet converted to be UTF-8 safe"); + + startLineOffset.clear(); + lineLen.clear(); + + if (!txt || !txt[0] || lineWidth < getCharWidth('W')) //make sure the line width is greater then a single character + return; + + U32 len = dStrlen(txt); + + U32 startLine; + + for (U32 i = 0; i < len;) + { + startLine = i; + startLineOffset.push_back(startLine); + + // loop until the string is too large + bool needsNewLine = false; + U32 lineStrWidth = 0; + for (; i < len; i++) + { + if( txt[ i ] == '\n' ) + { + needsNewLine = true; + break; + } + else if(isValidChar(txt[i])) + { + lineStrWidth += getCharInfo(txt[i]).xIncrement; + if( lineStrWidth > lineWidth ) + { + needsNewLine = true; + break; + } + } + } + + if (!needsNewLine) + { + // we are done! + lineLen.push_back(i - startLine); + return; + } + + S32 j; + + // Did we hit a hardwrap (newline character) in the string. + bool hardwrap = ( txt[i] == '\n' ); + + if ( hardwrap ) + { + j = i; + } + // determine where to put the newline + // we need to backtrack until we find a space character + // we don't do this for hardwrap(s) + else + { + for (j = i - 1; j >= startLine; j--) + { + if ( dIsspace(txt[j]) || txt[i] == '\n' ) + break; + } + + if (j < startLine) + { + // the line consists of a single word! + // So, just break up the word + + j = i - 1; + } + } + + lineLen.push_back(j - startLine); + i = j; + + // Now we need to increment through any space characters at the + // beginning of the next line. + // We don't skip spaces after a hardwrap because they were obviously intended. + for (i++; i < len; i++) + { + if ( txt[i] == '\n' ) + continue; + + if ( dIsspace( txt[i] ) && !hardwrap ) + continue; + + break; + } + } +} + +//----------------------------------------------------------------------------- + +bool GFont::read(Stream& io_rStream) +{ + // Handle versioning + U32 version; + io_rStream.read(&version); + if(version != csm_fileVersion) + return false; + + char buf[256]; + io_rStream.readString(buf); + mFaceName = buf; + + io_rStream.read(&mSize); + io_rStream.read(&mCharSet); + + io_rStream.read(&mHeight); + io_rStream.read(&mBaseline); + io_rStream.read(&mAscent); + io_rStream.read(&mDescent); + + U32 size = 0; + io_rStream.read(&size); + mCharInfoList.setSize(size); + U32 i; + for(i = 0; i < size; i++) + { + PlatformFont::CharInfo *ci = &mCharInfoList[i]; + io_rStream.read(&ci->bitmapIndex); + io_rStream.read(&ci->xOffset); + io_rStream.read(&ci->yOffset); + io_rStream.read(&ci->width); + io_rStream.read(&ci->height); + io_rStream.read(&ci->xOrigin); + io_rStream.read(&ci->yOrigin); + io_rStream.read(&ci->xIncrement); + ci->bitmapData = NULL; + } + + U32 numSheets = 0; + io_rStream.read(&numSheets); + + for(i = 0; i < numSheets; i++) + { + GBitmap *bmp = new GBitmap; + if(!bmp->readBitmap("png", io_rStream)) + { + delete bmp; + return false; + } + GFXTexHandle handle = GFXTexHandle(bmp, &GFXFontTextureProfile, true, avar("%s() - Read Font Sheet for %s %d (line %d)", __FUNCTION__, mFaceName.c_str(), mSize, __LINE__)); + //handle.setFilterNearest(); + mTextureSheets.push_back(handle); + } + + // Read last position info + io_rStream.read(&mCurX); + io_rStream.read(&mCurY); + io_rStream.read(&mCurSheet); + + // Read the remap table. + U32 minGlyph, maxGlyph; + io_rStream.read(&minGlyph); + io_rStream.read(&maxGlyph); + + if(maxGlyph >= minGlyph) + { + // Length of buffer.. + U32 buffLen; + io_rStream.read(&buffLen); + + // Read the buffer. + FrameTemp inBuff(buffLen); + io_rStream.read(buffLen, inBuff); + + // Decompress. + uLongf destLen = (maxGlyph-minGlyph+1)*sizeof(S32); + uncompress((Bytef*)&mRemapTable[minGlyph], &destLen, (Bytef*)(S32*)inBuff, buffLen); + + AssertISV(destLen == (maxGlyph-minGlyph+1)*sizeof(S32), "GFont::read - invalid remap table data!"); + + // Make sure we've got the right endianness. + for(i = minGlyph; i <= maxGlyph; i++) + mRemapTable[i] = convertBEndianToHost(mRemapTable[i]); + } + + return (io_rStream.getStatus() == Stream::Ok); +} + +bool GFont::write(Stream& stream) +{ + // Handle versioning + stream.write(csm_fileVersion); + + // Write font info + stream.write(mFaceName); + stream.write(mSize); + stream.write(mCharSet); + + stream.write(mHeight); + stream.write(mBaseline); + stream.write(mAscent); + stream.write(mDescent); + + // Write char info list + stream.write(U32(mCharInfoList.size())); + U32 i; + for(i = 0; i < mCharInfoList.size(); i++) + { + const PlatformFont::CharInfo *ci = &mCharInfoList[i]; + stream.write(ci->bitmapIndex); + stream.write(ci->xOffset); + stream.write(ci->yOffset); + stream.write(ci->width); + stream.write(ci->height); + stream.write(ci->xOrigin); + stream.write(ci->yOrigin); + stream.write(ci->xIncrement); + } + + stream.write(mTextureSheets.size()); + for(i = 0; i < mTextureSheets.size(); i++) + mTextureSheets[i].getBitmap()->writeBitmap("png", stream); + + stream.write(mCurX); + stream.write(mCurY); + stream.write(mCurSheet); + + // Get the min/max we have values for, and only write that range out. + S32 minGlyph = S32_MAX, maxGlyph = 0; + + for(i = 0; i < 65536; i++) + { + if(mRemapTable[i] != -1) + { + if(i>maxGlyph) maxGlyph = i; + if(i= minGlyph) + { + // Put everything big endian, to be consistent. Do this inplace. + for(i = minGlyph; i <= maxGlyph; i++) + mRemapTable[i] = convertHostToBEndian(mRemapTable[i]); + + { + // Compress. + const U32 buffSize = 128 * 1024; + FrameTemp outBuff(buffSize); + uLongf destLen = buffSize * sizeof(S32); + compress2((Bytef*)(S32*)outBuff, &destLen, (Bytef*)(S32*)&mRemapTable[minGlyph], (maxGlyph-minGlyph+1)*sizeof(S32), 9); + + // Write out. + stream.write((U32)destLen); + stream.write(destLen, outBuff); + } + + // Put us back to normal. + for(i = minGlyph; i <= maxGlyph; i++) + mRemapTable[i] = convertBEndianToHost(mRemapTable[i]); + } + + return (stream.getStatus() == Stream::Ok); +} + +void GFont::exportStrip(const char *fileName, U32 padding, U32 kerning) +{ + // Figure dimensions of our strip by iterating over all the char infos. + U32 totalHeight = 0; + U32 totalWidth = 0; + + S32 heightMin=0, heightMax=0; + + for(S32 i=0; igetFormat()); + + dMemset(gb.getWritableBits(), 0, sizeof(U8) * totalHeight * totalWidth ); + + // Ok, copy some rects, taking into account padding, kerning, offset. + U32 curWidth = kerning + padding; + + for(S32 i=0; i strip = GBitmap::load(fileName); + + if(!strip) + { + Con::errorf("GFont::importStrip - could not load file '%s'!", fileName); + return; + } + + // And get parsing and copying - load up all the characters as separate + // GBitmaps, sort, then pack. Not terribly efficient but this is basically + // on offline task anyway. + + // Ok, snag some glyphs. + Vector glyphList; + glyphList.reserve(mCharInfoList.size()); + + U32 curWidth = 0; + for(S32 i=0; igetFormat()); + glyphList.last().charId = i; + + // Copy the rect. + RectI ri(curWidth, getBaseline() - mCharInfoList[i].yOrigin, glyphList.last().bitmap->getWidth(), glyphList.last().bitmap->getHeight()); + Point2I outRi(0,0); + glyphList.last().bitmap->copyRect(strip, ri, outRi); + + // Update glyph attributes. + mCharInfoList[i].width = glyphList.last().bitmap->getWidth(); + mCharInfoList[i].height = glyphList.last().bitmap->getHeight(); + mCharInfoList[i].xOffset -= kerning + padding; + mCharInfoList[i].xIncrement += kerning; + mCharInfoList[i].yOffset -= padding; + + // Advance. + curWidth += ri.extent.x; + } + + // Ok, we have a big list of glyphmaps now. So let's sort them, then pack them. + dQsort(glyphList.address(), glyphList.size(), sizeof(GlyphMap), GlyphMapCompare); + + // They're sorted by height, so now we can do some sort of awesome packing. + Point2I curSheetSize(256, 256); + Vector sheetSizes; + + S32 curY = 0; + S32 curX = 0; + S32 curLnHeight = 0; + S32 maxHeight = 0; + for(U32 i = 0; i < glyphList.size(); i++) + { + PlatformFont::CharInfo *ci = &mCharInfoList[glyphList[i].charId]; + + if(ci->height > maxHeight) + maxHeight = ci->height; + + if(curX + ci->width > curSheetSize.x) + { + curY += curLnHeight; + curX = 0; + curLnHeight = 0; + } + + if(curY + ci->height > curSheetSize.y) + { + sheetSizes.push_back(curSheetSize.y); + curX = 0; + curY = 0; + curLnHeight = 0; + } + + if(ci->height > curLnHeight) + curLnHeight = ci->height; + + ci->bitmapIndex = sheetSizes.size(); + ci->xOffset = curX; + ci->yOffset = curY; + curX += ci->width; + } + + // Terminate the packing loop calculations. + curY += curLnHeight; + + if(curY < 64) + curSheetSize.y = 64; + else if(curY < 128) + curSheetSize.y = 128; + + sheetSizes.push_back(curSheetSize.y); + + if(getHeight() + padding * 2 > maxHeight) + maxHeight = getHeight() + padding * 2; + + // Allocate texture pages. + for(S32 i=0; igetFormat()); + + // Set everything to transparent. + U8 *bits = bitmap->getWritableBits(); + dMemset(bits, 0, sizeof(U8) *TextureSheetSize*TextureSheetSize * strip->getBytesPerPixel()); + + GFXTexHandle handle = GFXTexHandle( bitmap, &GFXFontTextureProfile, true, avar("%s() - Font Sheet for %s (line %d)", __FUNCTION__, fileName, __LINE__) ); + mTextureSheets.increment(); + constructInPlace(&mTextureSheets.last()); + mTextureSheets.last() = handle; + } + + // Alright, we're ready to copy bits! + for(S32 i=0; ibitmapIndex; + mTextureSheets[bi]->getBitmap()->copyRect(glyphList[i].bitmap, RectI(0,0, glyphList[i].bitmap->getWidth(),glyphList[i].bitmap->getHeight()), Point2I(ci->xOffset, ci->yOffset)); + } + + // Ok, all done! Just refresh some textures and we're set. + for(S32 i=0; i f = GFont::create(fontName, fontSize, Con::getVariable("$GUI::fontCacheDirectory")); + + if(f == NULL) + { + Con::errorf("populateFontCacheString - could not load font '%s %d'", fontName.c_str(), fontSize); + return; + } + + if(!f->hasPlatformFont()) + { + Con::errorf("populateFontCacheString - font '%s %d' has no platform font. Cannot generate more characters.", fontName.c_str(), fontSize); + return; + } + + // This has the side effect of generating character info, including the bitmaps. + f->getStrWidthPrecise(argv[3]); +} + +ConsoleFunction(populateFontCacheRange, void, 5, 5, "(faceName, size, rangeStart, rangeEnd) - " + "Populate the font cache for the specified font with Unicode code points in the specified range. " + "Note we only support BMP-0, so code points range from 0 to 65535.") +{ + const String fontName(argv[1]); + S32 fontSize = dAtoi(argv[2]); + + Resource f = GFont::create(fontName, fontSize, Con::getVariable("$GUI::fontCacheDirectory")); + + if(f == NULL) + { + Con::errorf("populateFontCacheRange - could not load font '%s %d'", fontName.c_str(), fontSize); + return; + } + + U32 rangeStart = dAtoi(argv[3]); + U32 rangeEnd = dAtoi(argv[4]); + + if(rangeStart > rangeEnd) + { + Con::errorf("populateFontCacheRange - range start is after end"); + return; + } + + if(!f->hasPlatformFont()) + { + Con::errorf("populateFontCacheRange - font '%s %d' has no platform font Cannot generate more characters.", fontName.c_str(), fontSize); + return; + } + + // This has the side effect of generating character info, including the bitmaps. + for(U32 i=rangeStart; iisValidChar(i)) + f->getCharWidth(i); + else + Con::warnf("populateFontCacheRange - skipping invalid char 0x%x", i); + } + + // All done! +} + +ConsoleFunction(dumpFontCacheStatus, void, 1, 1, "() - Return a full description " + "of all cached fonts, along with info on the codepoints each contains.") +{ + Resource theFont = ResourceManager::get().startResourceList( Resource::signature() ); + + Con::printf("--------------------------------------------------------------------------"); + Con::printf(" Font Cache Usage Report"); + + while( theFont != NULL ) + { + theFont->dumpInfo(); + + theFont = ResourceManager::get().nextResource(); + } +} + +ConsoleFunction(writeFontCache, void, 1, 1, "() - force all cached fonts to" + "serialize themselves to the cache.") +{ + Resource theFont = ResourceManager::get().startResourceList( Resource::signature() ); + + Con::printf("--------------------------------------------------------------------------"); + Con::printf(" Writing font cache to disk"); + + while( theFont != NULL ) + { + const String fileName( theFont.getPath() ); + + FileStream stream; + stream.open(fileName, Torque::FS::File::Write); + + if(stream.getStatus() == Stream::Ok) + { + Con::printf(" o Writing '%s' to disk...", fileName.c_str()); + theFont->write(stream); + } + else + { + Con::errorf(" o Could not open '%s' for write", fileName.c_str()); + } + + theFont = ResourceManager::get().nextResource(); + } +} + +ConsoleFunction(populateAllFontCacheString, void, 2, 2, "(string) - " + "Populate the font cache for all fonts with characters from the specified string.") +{ + Resource theFont = ResourceManager::get().startResourceList( Resource::signature() ); + + Con::printf("Populating font cache with string '%s'", argv[1]); + + while( theFont != NULL ) + { + if(theFont->hasPlatformFont()) + { + // This has the side effect of generating character info, including the bitmaps. + theFont->getStrWidthPrecise(argv[1]); + } + else + { + const String fileName( theFont.getPath() ); + Con::errorf("populateAllFontCacheString - font '%s' has no platform font. Cannot generate more characters.", fileName.c_str()); + } + + theFont = ResourceManager::get().nextResource(); + } +} + +ConsoleFunction(populateAllFontCacheRange, void, 3, 3, "(rangeStart, rangeEnd) - " + "Populate the font cache for all fonts with Unicode code points in the specified range. " + "Note we only support BMP-0, so code points range from 0 to 65535.") +{ + U32 rangeStart = dAtoi(argv[1]); + U32 rangeEnd = dAtoi(argv[2]); + + if(rangeStart > rangeEnd) + { + Con::errorf("populateAllFontCacheRange - range start is after end!"); + return; + } + + Resource theFont = ResourceManager::get().startResourceList( Resource::signature() ); + + Con::printf("Populating font cache with range 0x%x to 0x%x", rangeStart, rangeEnd); + + while( theFont != NULL ) + { + const String fileName( theFont.getPath() ); + + if(theFont->hasPlatformFont()) + { + // This has the side effect of generating character info, including the bitmaps. + Con::printf(" o Populating font '%s'", fileName.c_str()); + for(U32 i=rangeStart; iisValidChar(i)) + theFont->getCharWidth(i); + else + Con::warnf("populateAllFontCacheRange - skipping invalid char 0x%x", i); + } + } + else + { + Con::errorf("populateAllFontCacheRange - font '%s' has no platform font. Cannot generate more characters.", fileName.c_str()); + } + + theFont = ResourceManager::get().nextResource(); + } +} + +ConsoleFunction(exportCachedFont, void, 6, 6, "(fontName, size, fileName, padding, kerning) - " + "Export specified font to the specified filename as a PNG. The " + "image can then be processed in Photoshop or another tool and " + "reimported using importCachedFont. Characters in the font are" + "exported as one long strip.") +{ + // Read in some params. + const String fontName(argv[1]); + S32 fontSize = dAtoi(argv[2]); + const char *fileName = argv[3]; + S32 padding = dAtoi(argv[4]); + S32 kerning = dAtoi(argv[5]); + + // Tell the font to export itself. + Resource f = GFont::create(fontName, fontSize, Con::getVariable("$GUI::fontCacheDirectory")); + + if(f == NULL) + { + Con::errorf("exportCachedFont - could not load font '%s %d'", fontName.c_str(), fontSize); + return; + } + + f->exportStrip(fileName, padding, kerning); +} + +ConsoleFunction(importCachedFont, void, 6, 6, "(fontName, size, fileName, padding, kerning) - " + "Import an image strip from exportCachedFont. Call with the " + "same parameters you called exportCachedFont.") +{ + // Read in some params. + const String fontName(argv[1]); + S32 fontSize = dAtoi(argv[2]); + const char *fileName = argv[3]; + S32 padding = dAtoi(argv[4]); + S32 kerning = dAtoi(argv[5]); + + // Tell the font to import itself. + Resource f = GFont::create(fontName, fontSize, Con::getVariable("$GUI::fontCacheDirectory")); + + if(f == NULL) + { + Con::errorf("importCachedFont - could not load font '%s %d'", fontName.c_str(), fontSize); + return; + } + + f->importStrip(fileName, padding, kerning); +} + +ConsoleFunction(duplicateCachedFont, void, 4, 4, "(oldFontName, oldFontSize, newFontName) -" + "Copy the specified old font to a new name. The new copy will not have a " + "platform font backing it, and so will never have characters added to it. " + "But this is useful for making copies of fonts to add postprocessing effects " + "to via exportCachedFont.") +{ + const String oldFontName(argv[1]); + U32 fontSize = dAtoi(argv[2]); + + String newFontFile = GFont::getFontCacheFilename(argv[3], fontSize); + + // Load the original font. + Resource font = GFont::create(oldFontName, fontSize, Con::getVariable("$GUI::fontCacheDirectory")); + + // Deal with inexplicably missing or failed to load fonts. + if (font == NULL) + { + Con::errorf(" o Couldn't find font : %s", oldFontName.c_str()); + return; + } + + // Ok, dump info! + FileStream stream; + stream.open( newFontFile, Torque::FS::File::Write ); + if(stream.getStatus() == Stream::Ok) + { + Con::printf(" o Writing duplicate font '%s' to disk...", newFontFile.c_str()); + font->write(stream); + stream.close(); + } + else + { + Con::errorf(" o Could not open '%s' for write", newFontFile.c_str()); + } +} + diff --git a/gfx/gFont.h b/gfx/gFont.h new file mode 100644 index 0000000..7075889 --- /dev/null +++ b/gfx/gFont.h @@ -0,0 +1,177 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFONT_H_ +#define _GFONT_H_ + +//Includes +#ifndef _RESOURCE_H_ +#include "core/resource.h" +#endif +#ifndef _PLATFORMFONT_H_ +#include "platform/platformFont.h" +#endif +#ifndef _GBITMAP_H_ +#include "gfx/bitmap/gBitmap.h" +#endif +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif + + +GFX_DeclareTextureProfile(GFXFontTextureProfile); + +class GFont +{ +public: + enum Constants + { + TabWidthInSpaces = 3, + TextureSheetSize = 256, + }; + +public: + GFont(); + virtual ~GFont(); + + static Resource create(const String &faceName, U32 size, const char *cacheDirectory = 0, U32 charset = TGE_ANSI_CHARSET); + + GFXTexHandle getTextureHandle(S32 index) const { return mTextureSheets[index]; } + + const PlatformFont::CharInfo& getCharInfo(const UTF16 in_charIndex); + static const PlatformFont::CharInfo& getDefaultCharInfo(); + + U32 getCharHeight(const UTF16 in_charIndex); + U32 getCharWidth(const UTF16 in_charIndex); + U32 getCharXIncrement(const UTF16 in_charIndex); + + bool isValidChar(const UTF16 in_charIndex) const; + + const U32 getHeight() const { return mHeight; } + const U32 getBaseline() const { return mBaseline; } + const U32 getAscent() const { return mAscent; } + const U32 getDescent() const { return mDescent; } + + U32 getBreakPos(const UTF16 *string, U32 strlen, U32 width, bool breakOnWhitespace); + + /// These are the preferred width functions. + U32 getStrNWidth(const UTF16*, U32 n); + U32 getStrNWidthPrecise(const UTF16*, U32 n); + + /// These UTF8 versions of the width functions will be deprecated, please avoid them. + U32 getStrWidth(const UTF8*); // Note: ignores c/r + U32 getStrNWidth(const UTF8*, U32 n); + U32 getStrWidthPrecise(const UTF8*); // Note: ignores c/r + U32 getStrNWidthPrecise(const UTF8*, U32 n); + void wrapString(const UTF8 *string, U32 width, Vector &startLineOffset, Vector &lineLen); + + /// Dump information about this font to the console. + void dumpInfo() const; + + /// Export to an image strip for image processing. + void exportStrip(const char *fileName, U32 padding, U32 kerning); + + /// Import an image strip generated with exportStrip, make sure parameters match! + void importStrip(const char *fileName, U32 padding, U32 kerning); + + void setPlatformFont(PlatformFont *inPlatformFont); + + /// Query as to presence of platform font. If absent, we cannot generate more + /// chars! + const bool hasPlatformFont() const + { + return mPlatformFont != NULL; + } + + /// Query to determine if we should use add or modulate (as A8 textures + /// are treated as having 0 for RGB). + bool isAlphaOnly() const + { + return mTextureSheets[0]->getBitmap()->getFormat() == GFXFormatA8; + } + + /// Get the filename for a cached font. + static String getFontCacheFilename(const String &faceName, U32 faceSize); + + /// Get the face name of the font. + String getFontFaceName() const { return mFaceName; }; + U32 getFontSize() const { return mSize; } + U32 getFontCharSet() const { return mCharSet; } + + bool read(Stream& io_rStream); + bool write(Stream& io_rStream); + + static GFont* load( const Torque::Path& path ); + +protected: + bool loadCharInfo(const UTF16 ch); + void addBitmap(PlatformFont::CharInfo &charInfo); + void addSheet(void); + void assignSheet(S32 sheetNum, GBitmap *bmp); + + void *mMutex; + +private: + static const U32 csm_fileVersion; + + PlatformFont *mPlatformFont; + VectormTextureSheets; + + S32 mCurX; + S32 mCurY; + S32 mCurSheet; + + bool mNeedSave; + Torque::Path mGFTFile; + String mFaceName; + U32 mSize; + U32 mCharSet; + + U32 mHeight; + U32 mBaseline; + U32 mAscent; + U32 mDescent; + + /// List of character info structures, must be accessed through the + /// getCharInfo(U32) function to account for remapping. + Vector mCharInfoList; + + /// Index remapping + S32 mRemapTable[65536]; +}; + +inline U32 GFont::getCharXIncrement(const UTF16 in_charIndex) +{ + const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex); + return rChar.xIncrement; +} + +inline U32 GFont::getCharWidth(const UTF16 in_charIndex) +{ + const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex); + return rChar.width; +} + +inline U32 GFont::getCharHeight(const UTF16 in_charIndex) +{ + const PlatformFont::CharInfo& rChar = getCharInfo(in_charIndex); + return rChar.height; +} + +inline bool GFont::isValidChar(const UTF16 in_charIndex) const +{ + if(mRemapTable[in_charIndex] != -1) + return true; + + if(mPlatformFont) + return mPlatformFont->isValidChar(in_charIndex); + + return false; +} + +#endif //_GFONT_H_ diff --git a/gfx/genericConstBuffer.cpp b/gfx/genericConstBuffer.cpp new file mode 100644 index 0000000..49e5562 --- /dev/null +++ b/gfx/genericConstBuffer.cpp @@ -0,0 +1,310 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/genericConstBuffer.h" +#include "platform/profiler.h" + +/// +/// GenericConstBufferLayout +/// +GenericConstBufferLayout::GenericConstBufferLayout() +{ + VECTOR_SET_ASSOCIATION( mParams ); + + mBufferSize = 0; + mCurrentIndex = 0; + mTimesCleared = 0; +} + +/// Add a parameter to the buffer +void GenericConstBufferLayout::addParameter(const String& name, const GFXShaderConstType constType, const U32 offset, const U32 size, const U32 arraySize, const U32 alignValue) +{ +#ifdef TORQUE_DEBUG + // Make sure we don't have overlapping parameters + S32 start = offset; + S32 end = offset + size; + for (Params::iterator i = mParams.begin(); i != mParams.end(); i++) + { + const ParamDesc& dp = *i; + S32 pstart = dp.offset; + S32 pend = pstart + dp.size; + pstart -= start; + pend -= end; + // This is like a minkowski sum for two line segments, if the newly formed line contains + // the origin, then they intersect + bool intersect = ((pstart >= 0 && 0 >= pend) || ((pend >= 0 && 0 >= pstart))); + AssertFatal(!intersect, "Overlapping shader parameter!"); + } +#endif + ParamDesc desc; + desc.name = name; + desc.constType = constType; + desc.offset = offset; + desc.size = size; + desc.arraySize = arraySize; + desc.alignValue = alignValue; + desc.index = mCurrentIndex++; + mParams.push_back(desc); + mBufferSize = getMax(desc.offset + desc.size, mBufferSize); + AssertFatal(mBufferSize, "Empty constant buffer!"); +} + +/// Set a parameter, given a base pointer +/// Set a parameter, given a base pointer +bool GenericConstBufferLayout::set(const ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data, U8* basePointer) +{ + PROFILE_SCOPE(GenericConstBufferLayout_set); + AssertFatal(pd.constType == constType, "Mismatched const type!"); + + // This "cute" bit of code allows us to support 2x3 and 3x3 matrices in shader constants but use our MatrixF class. Yes, a hack. -BTR + switch (pd.constType) + { + case GFXSCT_Float2x2 : + case GFXSCT_Float3x3 : + case GFXSCT_Float4x4 : + return setMatrix(pd, constType, size, data, basePointer); + break; + default : + break; + } + + AssertFatal(pd.size >= size, "Not enough room in the buffer for this data!"); + + // Ok, we only set data if it's different than the data we already have, this maybe more expensive than just setting the data, but + // we'll have to do some timings to see. For example, the lighting shader constants rarely change, but we can't assume that at the + // renderInstMgr level, but we can check down here. -BTR + if (dMemcmp(basePointer+pd.offset, data, size) != 0) + { + dMemcpy(basePointer+pd.offset, data, size); + return true; + } + return false; +} + +// Matrices are an annoying case because of the alignment issues. There are alignment issues in the matrix itself, and then potential inter matrices alignment issues. +// So GL and DX will need to derive their own GenericConstBufferLayout classes and override this method to deal with that stuff. For GenericConstBuffer, copy the whole +// 4x4 matrix regardless of the target case. +bool GenericConstBufferLayout::setMatrix(const ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data, U8* basePointer) +{ + // We're generic, so just copy the full MatrixF in + AssertFatal(pd.size >= size, "Not enough room in the buffer for this data!"); + + if (dMemcmp(basePointer+pd.offset, data, size) != 0) + { + dMemcpy(basePointer+pd.offset, data, size); + return true; + } + + return false; +} + +/// Returns the description of a parameter +bool GenericConstBufferLayout::getDesc(const String& name, ParamDesc& param) const +{ + for (U32 i = 0; i < mParams.size(); i++) + { + if (mParams[i].name.equal(name)) + { + param = mParams[i]; + return true; + } + } + return false; +} + +/// Returns the ParamDesc of a parameter +bool GenericConstBufferLayout::getDesc(const U32 index, ParamDesc& param) const +{ + if ( index < mParams.size() ) + { + param = mParams[index]; + return true; + } + + return false; +} + +/// Save this layout to a stream +bool GenericConstBufferLayout::write(Stream* s) +{ + // Write out the size of the ParamDesc structure as a sanity check. + if (!s->write((U32) sizeof(ParamDesc))) + return false; + // Next, write out the number of elements we've got. + if (!s->write(mParams.size())) + return false; + for (U32 i = 0; i < mParams.size(); i++) + { + s->write(mParams[i].name); + + if (!s->write(mParams[i].offset)) + return false; + if (!s->write(mParams[i].size)) + return false; + U32 t = (U32) mParams[i].constType; + if (!s->write(t)) + return false; + if (!s->write(mParams[i].arraySize)) + return false; + if (!s->write(mParams[i].alignValue)) + return false; + if (!s->write(mParams[i].index)) + return false; + } + return true; +} + +/// Load this layout from a stream +bool GenericConstBufferLayout::read(Stream* s) +{ + U32 structSize; + if (!s->read(&structSize)) + return false; + if (structSize != sizeof(ParamDesc)) + { + AssertFatal(false, "Invalid shader layout structure size!"); + return false; + } + U32 numParams; + if (!s->read(&numParams)) + return false; + mParams.setSize(numParams); + mBufferSize = 0; + mCurrentIndex = 0; + for (U32 i = 0; i < mParams.size(); i++) + { + s->read(&mParams[i].name); + if (!s->read(&mParams[i].offset)) + return false; + if (!s->read(&mParams[i].size)) + return false; + U32 t; + if (!s->read(&t)) + return false; + mParams[i].constType = (GFXShaderConstType) t; + if (!s->read(&mParams[i].arraySize)) + return false; + if (!s->read(&mParams[i].alignValue)) + return false; + if (!s->read(&mParams[i].index)) + return false; + mBufferSize = getMax(mParams[i].offset + mParams[i].size, mBufferSize); + mCurrentIndex = getMax(mParams[i].index, mCurrentIndex); + } + mCurrentIndex++; + return true; +} + +void GenericConstBufferLayout::clear() +{ + mParams.clear(); + mBufferSize = 0; + mCurrentIndex = 0; + mTimesCleared++; +} + +/// +/// GenericConstBuffer +/// +GenericConstBuffer::GenericConstBuffer(GenericConstBufferLayout* layout) +{ + VECTOR_SET_ASSOCIATION( mDirtyFields ); + VECTOR_SET_ASSOCIATION( mHasData ); + + if (layout) + { + mLayout = layout; + mBuffer = new U8[mLayout->getBufferSize()]; + mDirtyFields.setSize(mLayout->getParameterCount()); + mHasData.setSize(mLayout->getParameterCount()); + for (U32 i = 0; i < mHasData.size(); i++) + mHasData[i] = false; + setDirty(false); + // Always set a default value, that way our isEqual checks will work in release as well. + dMemset(mBuffer, 0xFFFF, mLayout->getBufferSize()); + } + else + { + mBuffer = NULL; + } +} + +GenericConstBuffer::~GenericConstBuffer() +{ + delete[] mBuffer; +} + +void GenericConstBuffer::internalSet(const GenericConstBufferLayout::ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data) +{ + if (!mBuffer) + return; + + //AssertFatal( mDirtyFields.size() == pd.arraySize, "Buffer size does not equal ParamDesc!" ); + + if (mLayout->set(pd, constType, size, data, mBuffer)) + { + mDirty = true; + mDirtyFields[pd.index] = true; + mHasData[pd.index] = true; + } +} + +void GenericConstBuffer::setDirty(bool dirty) +{ + mDirty = dirty; + for (Vector::iterator i = mDirtyFields.begin(); i != mDirtyFields.end(); i++) + { + *i = dirty; + } +} + +/// This scans the fields and returns a pointer to the first dirty field +/// and the length of the dirty bytes +const U8* GenericConstBuffer::getDirtyBuffer(U32& start, U32& size) +{ + PROFILE_SCOPE(GenericConstBuffer_getDirtyBuffer); + + U32 dirtyStart = mLayout->getBufferSize(); + U32 dirtyEnd = 0; + + static GenericConstBufferLayout::ParamDesc pd; + + for (U32 i = 0; i < mDirtyFields.size(); i++) + { + if ( isFieldDirty(i) && mLayout->getDesc(i, pd) ) + { + dirtyStart = getMin(pd.offset, dirtyStart); + dirtyEnd = getMax(pd.offset + pd.size, dirtyEnd); + } + } + + if (dirtyEnd > dirtyStart) + { + size = dirtyEnd - dirtyStart; + start = dirtyStart; + return mBuffer + dirtyStart; + } + else + { + // Nothing has changed. + start = 0; + size = 0; + return NULL; + } +} + +/// Returns true if we hold the same data as buffer and have the same layout +bool GenericConstBuffer::isEqual(GenericConstBuffer* buffer) const +{ + PROFILE_SCOPE(GenericConstBuffer_isEqual); + + U32 bsize = mLayout->getBufferSize(); + if (bsize == buffer->mLayout->getBufferSize()) + { + return dMemcmp(mBuffer, buffer->getBuffer(), bsize) == 0; + } + + return false; +} \ No newline at end of file diff --git a/gfx/genericConstBuffer.h b/gfx/genericConstBuffer.h new file mode 100644 index 0000000..3f88220 --- /dev/null +++ b/gfx/genericConstBuffer.h @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GENERICCONSTBUFFER_H_ +#define _GENERICCONSTBUFFER_H_ + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _ALIGNEDARRAY_H_ +#include "core/util/tAlignedArray.h" +#endif + +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif + +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif + +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif + +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif + +/// +/// This class describes a memory layout for a GenericConstBuffer +/// +class GenericConstBufferLayout +{ +public: + /// Describes the parameters we contain + struct ParamDesc + { + /// Parameter name + String name; + /// Offset into the memory block + U32 offset; + /// Size of the block + U32 size; + /// Type of data + GFXShaderConstType constType; + // For arrays, how many elements + U32 arraySize; + // Array element alignment value + U32 alignValue; + /// 0 based index of this param, in order of addParameter calls. + U32 index; + }; + + GenericConstBufferLayout(); + virtual ~GenericConstBufferLayout() {} + + /// Add a parameter to the buffer + void addParameter(const String& name, const GFXShaderConstType constType, const U32 offset, const U32 size, const U32 arraySize, const U32 alignValue); + + /// Get the size of the buffer + U32 getBufferSize() const { return mBufferSize; } + + /// Get the number of parameters + U32 getParameterCount() const { return mParams.size(); } + + /// Returns the ParamDesc of a parameter + bool getDesc(const String& name, ParamDesc& param) const; + + /// Returns the ParamDesc of a parameter + bool getDesc(const U32 index, ParamDesc& param) const; + + /// Set a parameter, given a base pointer + virtual bool set(const ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data, U8* basePointer); + + /// Save this layout to a stream + bool write(Stream* s); + + /// Load this layout from a stream + bool read(Stream* s); + + /// Restore to initial state. + void clear(); + +protected: + /// Set a matrix, given a base pointer + virtual bool setMatrix(const ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data, U8* basePointer); + + /// Vector of parameter descriptions + typedef Vector Params; + + /// Vector of parameter descriptions + Params mParams; + U32 mBufferSize; + U32 mCurrentIndex; + + // This if for debugging shader reloading and can be removed later. + U32 mTimesCleared; +}; + +/// This class will be used by other const buffers and the material system. Takes a set of variable names and maps them to +/// a section of memory. Should this descend from GFXConstBuffer? I don't know, we need two of them (one for vert, one for +/// pixel shaders for D3D9, maybe it'd be useful in OpenGL?) +class GenericConstBuffer +{ +public: + GenericConstBuffer(GenericConstBufferLayout* layout); + ~GenericConstBuffer(); + + /// @name Set shader constant values + /// @{ + /// Actually set shader constant values + /// @param name Name of the constant, this should be a name contained in the array returned in getShaderConstDesc, + /// if an invalid name is used, its ignored, but it's not an error. + void set(const GenericConstBufferLayout::ParamDesc& pd, const F32 f) { internalSet(pd, GFXSCT_Float, sizeof(F32), &f); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const Point2F& fv) { internalSet(pd, GFXSCT_Float2, sizeof(Point2F), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const Point3F& fv) { internalSet(pd, GFXSCT_Float3, sizeof(Point3F), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const Point4F& fv) { internalSet(pd, GFXSCT_Float4, sizeof(Point4F), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const PlaneF& fv) { internalSet(pd, GFXSCT_Float4, sizeof(PlaneF), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const ColorF& fv) { internalSet(pd, GFXSCT_Float4, sizeof(Point4F), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const S32 f) { internalSet(pd, GFXSCT_Int, sizeof(S32), &f); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const Point2I& fv) { internalSet(pd, GFXSCT_Int2, sizeof(Point2I), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const Point3I& fv) { internalSet(pd, GFXSCT_Int3, sizeof(Point3I), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const Point4I& fv) { internalSet(pd, GFXSCT_Int4, sizeof(Point4I), &fv); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Float, fv.getElementSize() * fv.size(), fv.getBuffer()); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Float2, fv.getElementSize() * fv.size(), fv.getBuffer()); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Float3, fv.getElementSize() * fv.size(), fv.getBuffer()); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Float4, fv.getElementSize() * fv.size(), fv.getBuffer()); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Int, fv.getElementSize() * fv.size(), fv.getBuffer()); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Int2, fv.getElementSize() * fv.size(), fv.getBuffer()); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Int3, fv.getElementSize() * fv.size(), fv.getBuffer()); } + void set(const GenericConstBufferLayout::ParamDesc& pd, const AlignedArray& fv) { internalSet(pd, GFXSCT_Int4, fv.getElementSize() * fv.size(), fv.getBuffer()); } + + void set(const GenericConstBufferLayout::ParamDesc& pd, const MatrixF& mat, const GFXShaderConstType matrixType) + { + AssertFatal(matrixType == GFXSCT_Float2x2 || matrixType == GFXSCT_Float3x3 || matrixType == GFXSCT_Float4x4, "Invalid matrix type!"); + internalSet(pd, matrixType, sizeof(MatrixF), &mat); + } + + void set(const GenericConstBufferLayout::ParamDesc& pd, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType) + { + AssertFatal(matrixType == GFXSCT_Float2x2 || matrixType == GFXSCT_Float3x3 || matrixType == GFXSCT_Float4x4, "Invalid matrix type!"); + internalSet(pd, matrixType, sizeof(MatrixF)*arraySize, mat); + } + + /// This scans the fields and returns a pointer to the first dirty field + /// and the length of the dirty bytes + const U8* getDirtyBuffer(U32& start, U32& size); + + void setDirty(bool dirty); + bool isDirty() const { return mDirty; } + + /// Returns true if we hold the same data as buffer and have the same layout + bool isEqual(GenericConstBuffer* buffer) const; + + /// Returns our layout + GenericConstBufferLayout* getLayout() { return mLayout; } +private: + /// Returns a pointer to the raw buffer + const U8* getBuffer() const { return mBuffer; } + + /// Called by the set functions above. + void internalSet(const GenericConstBufferLayout::ParamDesc& pd, const GFXShaderConstType constType, const U32 size, const void* data); + + /// Returns true if the field is marked dirty + /// @param index is the ParamDesc.Index in GenericConstBufferLayout + bool isFieldDirty(const U32 index) const { return mDirtyFields[index] && mHasData[index]; } + + /// Buffer layout + GenericConstBufferLayout* mLayout; + + /// Buffer + U8* mBuffer; + + Vector mDirtyFields; + Vector mHasData; + bool mDirty; +}; +#endif diff --git a/gfx/gfxAdapter.h b/gfx/gfxAdapter.h new file mode 100644 index 0000000..c665214 --- /dev/null +++ b/gfx/gfxAdapter.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GFXADAPTER_H_ +#define _GFXADAPTER_H_ + +#ifndef _GFXSTRUCTS_H_ +#include "gfx/gfxStructs.h" +#endif + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _UTIL_DELEGATE_H_ +#include "core/util/delegate.h" +#endif + +struct GFXAdapter +{ +public: + typedef Delegate CreateDeviceInstanceDelegate; + + enum + { + MaxAdapterNameLen = 512, + }; + + char mName[MaxAdapterNameLen]; + + /// List of available full-screen modes. Windows can be any size, + /// so we do not enumerate them here. + Vector mAvailableModes; + + /// Supported shader model. 0.f means none supported. + F32 mShaderModel; + + const char * getName() const { return mName; } + GFXAdapterType mType; + U32 mIndex; + CreateDeviceInstanceDelegate mCreateDeviceInstanceDelegate; + + GFXAdapter() + { + VECTOR_SET_ASSOCIATION( mAvailableModes ); + + mName[0] = 0; + mShaderModel = 0.f; + mIndex = 0; + } + + ~GFXAdapter() + { + mAvailableModes.clear(); + } +private: + // Disallow copying to prevent mucking with our data above. + GFXAdapter(const GFXAdapter&); +}; + +#endif // _GFXADAPTER_H_ diff --git a/gfx/gfxCardProfile.cpp b/gfx/gfxCardProfile.cpp new file mode 100644 index 0000000..9e2f977 --- /dev/null +++ b/gfx/gfxCardProfile.cpp @@ -0,0 +1,180 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/volume.h" + +#include "gfx/gfxCardProfile.h" +#include "console/console.h" + + +void GFXCardProfiler::loadProfileScript(const char* aScriptName) +{ + const String profilePath = Con::getVariable( "$Pref::Video::ProfilePath" ); + String scriptName = !profilePath.isEmpty() ? profilePath.c_str() : "profile"; + scriptName += "/"; + scriptName += aScriptName; + + void *data = NULL; + U32 dataSize = 0; + + Torque::FS::ReadFile( scriptName.c_str(), data, dataSize, true ); + + if(data == NULL) + { + Con::warnf(" - No card profile %s exists", scriptName.c_str()); + return; + } + + const char *script = static_cast(data); + + Con::printf(" - Loaded card profile %s", scriptName.c_str()); + + Con::executef("eval", script); + delete[] script; +} + +void GFXCardProfiler::loadProfileScripts(const String& render, const String& vendor, const String& card, const String& version) +{ + String script = render + ".cs"; + loadProfileScript(script); + + script = render + "." + vendor + ".cs"; + loadProfileScript(script); + + script = render + "." + vendor + "." + card + ".cs"; + loadProfileScript(script); + + script = render + "." + vendor + "." + card + "." + version + ".cs"; + loadProfileScript(script); +} + +GFXCardProfiler::GFXCardProfiler() : mVideoMemory( 0 ) +{ +} + +GFXCardProfiler::~GFXCardProfiler() +{ + mCapDictionary.clear(); +} + +String GFXCardProfiler::strippedString(const char *string) +{ + String res = ""; + + // And fill it with the stripped string... + const char *a=string; + while(*a) + { + if(isalnum(*a)) + { + res += *a; + } + a++; + } + + return res; +} + +void GFXCardProfiler::init() +{ + // Spew a bit... + Con::printf("Initializing GFXCardProfiler (%s)", getRendererString().c_str()); + Con::printf(" o Chipset : '%s'", getChipString().c_str()); + Con::printf(" o Card : '%s'", getCardString().c_str()); + Con::printf(" o Version : '%s'", getVersionString().c_str()); + + // Do card-specific setup... + Con::printf(" - Scanning card capabilities..."); + + setupCardCapabilities(); + + // And finally, load stuff up... + String render = strippedString(getRendererString()); + String chipset = strippedString(getChipString()); + String card = strippedString(getCardString()); + String version = strippedString(getVersionString()); + + Con::printf(" - Loading card profiles..."); + loadProfileScripts(render, chipset, card, version); +} + +U32 GFXCardProfiler::queryProfile(const String &cap) +{ + U32 res; + if( _queryCardCap( cap, res ) ) + return res; + + if(mCapDictionary.contains(cap)) + return mCapDictionary[cap]; + + Con::errorf( "GFXCardProfiler (%s) - Unknown capability '%s'.", getRendererString().c_str(), cap.c_str() ); + return 0; +} + +U32 GFXCardProfiler::queryProfile(const String &cap, U32 defaultValue) +{ + U32 res; + if( _queryCardCap( cap, res ) ) + return res; + + if( mCapDictionary.contains( cap ) ) + return mCapDictionary[cap]; + else + return defaultValue; +} + +void GFXCardProfiler::setCapability(const String &cap, U32 value) +{ + // Check for dups. + if( mCapDictionary.contains( cap ) ) + { + Con::warnf( "GFXCardProfiler (%s) - Setting capability '%s' multiple times.", getRendererString().c_str(), cap.c_str() ); + mCapDictionary[cap] = value; + return; + } + + // Insert value as necessary. + Con::printf( "GFXCardProfiler (%s) - Setting capability '%s' to %d.", getRendererString().c_str(), cap.c_str(), value ); + mCapDictionary.insert( cap, value ); +} + +bool GFXCardProfiler::checkFormat( const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips ) +{ + return _queryFormat( fmt, profile, inOutAutogenMips ); +} + +ConsoleMethodGroupBegin(GFXCardProfiler, Core, "Functions relating to the card profiler functionality."); + +ConsoleStaticMethod( GFXCardProfiler, getVersion, const char*, 1, 1, "() - Returns the driver version (59.72).") +{ + return GFX->getCardProfiler()->getVersionString(); +} + +ConsoleStaticMethod( GFXCardProfiler, getCard, const char*, 1, 1, "() - Returns the card name (GeforceFX 5950 Ultra).") +{ + return GFX->getCardProfiler()->getCardString(); +} + +ConsoleStaticMethod( GFXCardProfiler, getVendor, const char*, 1, 1, "() - Returns the vendor name (nVidia, ATI).") +{ + // TODO: Fix all of this vendor crap, it's not consistent + return GFX->getCardProfiler()->getChipString(); +} + +ConsoleStaticMethod( GFXCardProfiler, getRenderer, const char*, 1, 1, "() - Returns the renderer name (D3D9, for instance).") +{ + return GFX->getCardProfiler()->getRendererString(); +} + +ConsoleStaticMethod( GFXCardProfiler, setCapability, void, 3, 3, "setCapability(name, true/false) - Set a specific card capability.") +{ + TORQUE_UNUSED(argc); + + bool val = dAtob(argv[2]); + GFX->getCardProfiler()->setCapability(argv[1], val); + return; +} + +ConsoleMethodGroupEnd(GFXCardProfiler, Core); diff --git a/gfx/gfxCardProfile.h b/gfx/gfxCardProfile.h new file mode 100644 index 0000000..070e75f --- /dev/null +++ b/gfx/gfxCardProfile.h @@ -0,0 +1,216 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXCARDPROFILE_H_ +#define _GFXCARDPROFILE_H_ + +#include "core/util/tDictionary.h" +#include "gfx/gfxDevice.h" +#include "core/util/str.h" + +/// Graphics Card Profile +/// +/// GFXCardProfiler provides a device independent wrapper around both the +/// capabilities reported by the card/drivers and the exceptions recorded +/// in various scripts. +/// +/// The materials system keeps track of most caps-related rendering +/// optimizations and/or workarounds, but it is occasionally necessary to +/// expose capability information to higher level code (for instance, if some +/// feature depends on a specific subset of render functionality) or to keep +/// track of exceptions. +/// +/// The proper way to fix this is to get the IHV to release fixed drivers +/// and/or move to a single consistent rendering path that works. Of course, +/// when you're releasing a game, especially on a timeline (or with a less than +/// infinite budget) this isn't always a valid solution. +/// +/// It's also often convenient to be able to tweak performance/detail settings +/// based on the identified card type. +/// +/// GFXCardProfiler addresses both these needs by providing two data retrieval +/// methods and a generic interface for querying capability strings. +/// +/// @note The GFXCardProfiler is at heart a system for implementing WORKAROUNDS. +/// It is not guaranteed to work in all cases. The capability strings it +/// responds to are specific to each implementation. You should be +/// EXTREMELY careful when working with this functionality. When used in +/// moderation it can be a project-saver, but if used to excess or without +/// forethought it can lead to complex, hard-to-maintain code. +/// +/// The first data retrieval method that the GFXCardProfiler supports is a +/// card-specific capability query. This is implemented by each subclass. In the +/// case of DirectX, this means using the built-in capability query. For OpenGL +/// or other APIs, more exotic methods may be necessary. The goal of this method +/// is to retrieve some reasonable defaults that can be overridden later if +/// necessary. +/// +/// The second data retrieval method is script based. In ./profile a collection of +/// script files are stored. They are named in one of the forms: +/// +/// @code +/// Renderer.cs +/// Renderer.VendorString.CardString.cs +/// Renderer.VendorString.CardString.cs +/// Renderer.VendorString.CardString.VersionString.cs +/// @endcode +/// +/// These files are found and executed from most general to most specific. For instance, +/// say we're working in the D3D renderer with an nVidia GeForce FX 5950, running driver +/// version 53.36. The following files would be found and executed: +/// +/// @code +/// D3D.cs +/// D3D.nVidia.cs +/// D3D.nVidia.GeForceFX5950.cs +/// D3D.nVidia.GeForceFX5950.5336.cs +/// @endcode +/// +/// The general rule for turning strings into filename parts is to strip all spaces and +/// punctuation. If a file is not found, no error is reported; it is assumed that the +/// absence of a file means all is well. +/// +/// Several functions are made available to allow simple logic in the script functions (for +/// instance, to enable a workaround for a given range of driver versions). They are: +/// - GFXCardProfiler::getRenderer() +/// - GFXCardProfiler::getVendor() +/// - GFXCardProfiler::getCard() +/// - GFXCardProfiler::getVersion() +/// +/// In addition, specific subclasses may expose other values (for instance, chipset IDs). +/// These are made available as static members of the specific subclass. For instance, +/// a D3D-specific chipset query may be made available as GFXD3DCardProfiler::getChipset(). +/// +/// Finally, once a script file has reached a determination they may indicate their settings +/// to the GFXCardProfiler by calling GFXCardProfiler::setCapability(). For instance, +/// +/// @code +/// // Indicate we can show the color red. +/// GFXCardProfiler::setCapability("supportsRed", true); +/// @endcode +/// +/// GFXCardProfiler may be queried from script by calling +/// GFXCardProfiler::queryProfile() - for instance: +/// +/// @code +/// GFXCardProfiler::queryProfile("supportsRed"); // Query without default. +/// GFXCardProfiler::queryProfile("supportsRed", false"); // Query with default. +/// @endcode +/// +/// As in the C++ code, if a capability string isn't found and no default is found, +/// a console error will be reported. +/// +class GFXCardProfiler +{ + /// @name icpi Internal Card Profile Interface + /// + /// This is the interface implemented by subclasses of this class in order + /// to provide implementation-specific information about the current + /// card/drivers. + /// + /// Basically, the implementation needs to provide some unique strings: + /// - mVersionString indicating the current driver version of the + /// card in question. (For instance, "53.36") + /// - mCardDescription indicating the name of the card ("Radeon 8500") + /// - getRendererString() indicating the name of the renderer ("DX9", "GL1.2"). + /// Each card profiler subclass must return a unique constant so we can keep + /// data separate. Bear in mind that punctuation is stripped from filenames. + /// + /// The profiler also needs to implement setupCardCapabilities(), which is responsible + /// for querying the active device and setting defaults based on the reported capabilities, + /// and _queryCardCap, which is responsible for recognizing and responding to + /// device-specific capability queries. + /// + /// @{ + +public: + + /// + const String &getVersionString() const { return mVersionString; } + const String &getCardString() const { return mCardDescription; } + const String &getChipString() const { return mChipSet; } + U32 getVideoMemoryInMB() const { return mVideoMemory; } + + virtual const String &getRendererString() const = 0; + +protected: + + String mVersionString; + String mCardDescription; + String mChipSet; + U32 mVideoMemory; + + virtual void setupCardCapabilities()=0; + + /// Implementation specific query code. + /// + /// This function is meant to be overridden by the specific implementation class. + /// + /// Some query strings are handled by the external implementation while others must + /// be done by the specific implementation. This is given first chance to return + /// a result, then the generic rules are applied. + /// + /// @param query Capability being queried. + /// @param foundResult Result to return to the caller. If the function returns true + /// then this value is returned as the result of the query. + virtual bool _queryCardCap(const String &query, U32 &foundResult)=0; + + virtual bool _queryFormat( const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips ) = 0; + /// @} + + /// @name helpergroup Helper Functions + /// + /// Various helper functions. + + /// Load a specified script file from the profiles directory, if it exists. + void loadProfileScript(const char* scriptName); + + /// Load the script files in order for the specified card profile tuple. + void loadProfileScripts(const String& render, const String& vendor, const String& card, const String& version); + + String strippedString(const char*string); + + /// @} + + /// Capability dictionary. + Map mCapDictionary; + +public: + + /// @name ecpi External Card Profile Interface + /// + /// @{ + + /// Called for a profile for a given device. + GFXCardProfiler(); + virtual ~GFXCardProfiler(); + + + /// Set load script files and generally initialize things. + virtual void init()=0; + + /// Called to query a capability. Given a query string it returns a + /// bool indicating whether or not the capability holds. If you call + /// this and cap isn't recognized then it returns false and prints + /// a console error. + U32 queryProfile(const String &cap); + + /// Same as queryProfile(), but a default can be specified to indicate + /// what value should be returned if the profiler doesn't know anything + /// about it. If cap is not recognized, defaultValue is returned and + /// no error is reported. + U32 queryProfile(const String &cap, U32 defaultValue); + + /// Set the specified capability to the specified value. + void setCapability(const String &cap, U32 value); + + /// Queries support for the specified texture format, and texture profile + bool checkFormat( const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips ); + + /// @} +}; + +#endif + diff --git a/gfx/gfxCubemap.cpp b/gfx/gfxCubemap.cpp new file mode 100644 index 0000000..99cb7b9 --- /dev/null +++ b/gfx/gfxCubemap.cpp @@ -0,0 +1,68 @@ +#include "gfx/gfxCubemap.h" +#include "gfx/gfxDevice.h" +#include "gfx/bitmap/gBitmap.h" + +GFXCubemap::~GFXCubemap() +{ +} + +void GFXCubemap::initNormalize( U32 size ) +{ + Point3F axis[6] = + {Point3F(1.0, 0.0, 0.0), Point3F(-1.0, 0.0, 0.0), + Point3F(0.0, 1.0, 0.0), Point3F( 0.0, -1.0, 0.0), + Point3F(0.0, 0.0, 1.0), Point3F( 0.0, 0.0, -1.0),}; + Point3F s[6] = + {Point3F(0.0, 0.0, -1.0), Point3F( 0.0, 0.0, 1.0), + Point3F(1.0, 0.0, 0.0), Point3F( 1.0, 0.0, 0.0), + Point3F(1.0, 0.0, 0.0), Point3F(-1.0, 0.0, 0.0),}; + Point3F t[6] = + {Point3F(0.0, -1.0, 0.0), Point3F(0.0, -1.0, 0.0), + Point3F(0.0, 0.0, 1.0), Point3F(0.0, 0.0, -1.0), + Point3F(0.0, -1.0, 0.0), Point3F(0.0, -1.0, 0.0),}; + + F32 span = 2.0; + F32 start = -1.0; + + F32 stride = span / F32(size - 1); + GFXTexHandle faces[6]; + + for(U32 i=0; i<6; i++) + { + GFXTexHandle &tex = faces[i]; + GBitmap *bitmap = new GBitmap(size, size); + + // fill in... + for(U32 v=0; vgetAddress(u, v); + bits[0] = U8(vector.x); + bits[1] = U8(vector.y); + bits[2] = U8(vector.z); + } + } + + tex.set(bitmap, &GFXDefaultStaticDiffuseProfile, true, "Cubemap"); + } + + initStatic(faces); +} + +/// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer +const String GFXCubemap::describeSelf() const +{ + // We've got nothing + return String(); +} \ No newline at end of file diff --git a/gfx/gfxCubemap.h b/gfx/gfxCubemap.h new file mode 100644 index 0000000..cd633bd --- /dev/null +++ b/gfx/gfxCubemap.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXCUBEMAP_H_ +#define _GFXCUBEMAP_H_ + +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif + +class GFXDevice; + + +/// +class GFXCubemap : public StrongRefBase, public GFXResource +{ + friend class GFXDevice; +private: + // should only be called by GFXDevice + virtual void setToTexUnit( U32 tuNum ) = 0; + +public: + virtual void initStatic( GFXTexHandle *faces ) = 0; + virtual void initDynamic( U32 texSize, GFXFormat faceFormat = GFXFormatR8G8B8A8 ) = 0; + + void initNormalize(U32 size); + + virtual ~GFXCubemap(); + + /// Returns the size of the faces. + virtual U32 getSize() const = 0; + + /// Returns the face texture format. + virtual GFXFormat getFormat() const = 0; + + // GFXResource interface + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const; +}; + +typedef StrongRefPtr GFXCubemapHandle; + +#endif // GFXCUBEMAP diff --git a/gfx/gfxDebugEvent.h b/gfx/gfxDebugEvent.h new file mode 100644 index 0000000..5f434cb --- /dev/null +++ b/gfx/gfxDebugEvent.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXDEBUGEVENT_H_ +#define _GFXDEBUGEVENT_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif + +/// See TorqueConfig.h to enable this. +#ifdef TORQUE_ENABLE_GFXDEBUGEVENTS + + +/// You shouldn't use this class directly... use the +/// following macros: +/// +/// GFXDEBUGEVENT_START / GFXDEBUGEVENT_END +/// GFXDEBUGEVENT_SCOPE +/// +class GFXDebugEventScope +{ +public: + GFXDebugEventScope( const char* name, const ColorI &color ) + { + GFX->enterDebugEvent( color, name ); + } + + ~GFXDebugEventScope() + { + GFX->leaveDebugEvent(); + } +}; + +#define GFXDEBUGEVENT_START( name, color ) GFX->enterDebugEvent( color, #name ) + +#define GFXDEBUGEVENT_END() GFX->leaveDebugEvent() + +#define GFXDEBUGEVENT_MARKER( name, color ) GFX->setDebugMarker( color, #name ) + + +/// +/// Will add start/end GFX debug events around the +/// current scope. +/// +/// @param name The unquoted name for the event. +/// @param color A ColorI to associate with the event. +/// +#define GFXDEBUGEVENT_SCOPE( name, color ) GFXDebugEventScope GFXDebugEventScope##name##Obj( #name, color ) +#define GFXDEBUGEVENT_SCOPE_EX( name, color, desc ) GFXDebugEventScope GFXDebugEventScope##name##Obj( desc, color ) + +#else // !TORQUE_ENABLE_GFXDEBUGEVENTS + + /// These are disabled in shipping builds or maybe you + /// forgot to include "platform/platform.h" first? + #define GFXDEBUGEVENT_START(n,c) + #define GFXDEBUGEVENT_END() + #define GFXDEBUGEVENT_MARKER(n,c) + #define GFXDEBUGEVENT_SCOPE(n,c) + #define GFXDEBUGEVENT_SCOPE_EX(n,c,d) + +#endif + +#endif // _GFXDEBUGEVENT_H_ diff --git a/gfx/gfxDevice.cpp b/gfx/gfxDevice.cpp new file mode 100644 index 0000000..93aefc5 --- /dev/null +++ b/gfx/gfxDevice.cpp @@ -0,0 +1,1321 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxDevice.h" + +#include "gfx/gfxInit.h" +#include "gfx/gfxCubemap.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxFence.h" +#include "gfx/gfxFontRenderBatcher.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxShader.h" +#include "gfx/gfxStateBlock.h" +#include "gfx/screenshot.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "gfx/gfxTextureManager.h" + +#include "core/frameAllocator.h" +#include "core/stream/fileStream.h" +#include "core/strings/unicode.h" +#include "core/util/journal/process.h" +#include "core/util/safeDelete.h" +#include "console/consoleTypes.h" + +GFXDevice * GFXDevice::smGFXDevice = NULL; +bool GFXDevice::smWireframe = false; +bool gDisassembleAllShaders = false; + +void GFXDevice::initConsole() +{ + GFXStringEnumTranslate::init(); + + Con::addVariable( "$gfx::wireframe", TypeBool, &GFXDevice::smWireframe ); + Con::addVariable( "$gfx::disassembleAllShaders", TypeBool, &gDisassembleAllShaders ); +} +//----------------------------------------------------------------------------- + +// Static method +GFXDevice::DeviceEventSignal& GFXDevice::getDeviceEventSignal() +{ + static DeviceEventSignal theSignal; + return theSignal; +} + +//----------------------------------------------------------------------------- + +GFXDevice::GFXDevice() +{ + VECTOR_SET_ASSOCIATION( mVideoModes ); + VECTOR_SET_ASSOCIATION( mRTStack ); + + mWorldMatrixDirty = false; + mWorldStackSize = 0; + mProjectionMatrixDirty = false; + mViewMatrixDirty = false; + mTextureMatrixCheckDirty = false; + + mViewMatrix.identity(); + mProjectionMatrix.identity(); + + for( int i = 0; i < WORLD_STACK_MAX; i++ ) + mWorldMatrix[i].identity(); + + AssertFatal(smGFXDevice == NULL, "Already a GFXDevice created! Bad!"); + smGFXDevice = this; + + // Vertex buffer cache + mVertexBufferDirty = false; + + // Primitive buffer cache + mPrimitiveBufferDirty = false; + mTexturesDirty = false; + + // Use of TEXTURE_STAGE_COUNT in initialization is okay [7/2/2007 Pat] + for(U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) + { + mTextureDirty[i] = false; + mCurrentTexture[i] = NULL; + mNewTexture[i] = NULL; + mCurrentCubemap[i] = NULL; + mNewCubemap[i] = NULL; + mTexType[i] = GFXTDT_Normal; + + mTextureMatrix[i].identity(); + mTextureMatrixDirty[i] = false; + } + + mLightsDirty = false; + for(U32 i = 0; i < LIGHT_STAGE_COUNT; i++) + { + mLightDirty[i] = false; + mCurrentLightEnable[i] = false; + } + + mGlobalAmbientColorDirty = false; + mGlobalAmbientColor = ColorF(0.0f, 0.0f, 0.0f, 1.0f); + + mLightMaterialDirty = false; + dMemset(&mCurrentLightMaterial, NULL, sizeof(GFXLightMaterial)); + + // State block + mStateBlockDirty = false; + mCurrentStateBlock = NULL; + mNewStateBlock = NULL; + + mCurrentShaderConstBuffer = NULL; + + // misc + mAllowRender = true; + mCanCurrentlyRender = false; + mInitialized = false; + + mRTDirty = false; + mViewport = RectI::Zero; + mViewportDirty = false; + + mCurrentFrontBufferIdx = 0; + + mDeviceSwizzle32 = NULL; + mDeviceSwizzle24 = NULL; + + mResourceListHead = NULL; + + mCardProfiler = NULL; + + // Initialize our drawing utility. + mDrawer = NULL; + + // Add a few system wide shader macros. + GFXShader::addGlobalMacro( "TORQUE", "1" ); + GFXShader::addGlobalMacro( "TORQUE_VERSION", String::ToString(getVersionNumber()) ); +} + +GFXDrawUtil* GFXDevice::getDrawUtil() +{ + if (!mDrawer) + { + mDrawer = new GFXDrawUtil(this); + } + return mDrawer; +} + + +//----------------------------------------------------------------------------- + +void GFXDevice::deviceInited() +{ + getDeviceEventSignal().trigger(deInit); + mDeviceStatistics.setPrefix("$GFXDeviceStatistics::"); + + // Initialize the static helper textures. + GBitmap temp( 2, 2, false, GFXFormatR8G8B8A8 ); + temp.fill( ColorI::ONE ); + GFXTexHandle::ONE.set( &temp, &GFXDefaultStaticDiffuseProfile, false, "GFXTexHandle::ONE" ); + temp.fill( ColorI::ZERO ); + GFXTexHandle::ZERO.set( &temp, &GFXDefaultStaticDiffuseProfile, false, "GFXTexHandle::ZERO" ); + temp.fill( ColorI( 128, 128, 255 ) ); + GFXTexHandle::ZUP.set( &temp, &GFXDefaultStaticNormalMapProfile, false, "GFXTexHandle::ZUP" ); +} + +//----------------------------------------------------------------------------- + +// Static method +bool GFXDevice::destroy() +{ + // Cleanup the static helper textures. + GFXTexHandle::ONE.free(); + GFXTexHandle::ZERO.free(); + GFXTexHandle::ZUP.free(); + + // Make this release its buffer. + PrimBuild::shutdown(); + + // Let people know we are shutting down + getDeviceEventSignal().trigger(deDestroy); + + if(smGFXDevice) + smGFXDevice->preDestroy(); + SAFE_DELETE(smGFXDevice); + + return true; +} + +//----------------------------------------------------------------------------- +void GFXDevice::preDestroy() +{ + // Delete draw util + SAFE_DELETE( mDrawer ); +} + +//----------------------------------------------------------------------------- + +GFXDevice::~GFXDevice() +{ + smGFXDevice = NULL; + + // Clean up our current PB, if any. + mCurrentPrimitiveBuffer = NULL; + mCurrentVertexBuffer = NULL; + + // Clear out our current texture references + for (U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) + { + mCurrentTexture[i] = NULL; + mNewTexture[i] = NULL; + mCurrentCubemap[i] = NULL; + mNewCubemap[i] = NULL; + } + + // Check for resource leaks +#ifdef TORQUE_DEBUG + AssertFatal( GFXTextureObject::dumpActiveTOs() == 0, "There is a texture object leak, check the log for more details." ); + GFXPrimitiveBuffer::dumpActivePBs(); +#endif + + SAFE_DELETE( mTextureManager ); + + // Clear out our state block references + mCurrentStateBlocks.clear(); + mNewStateBlock = NULL; + mCurrentStateBlock = NULL; + + mCurrentShaderConstBuffer = NULL; + /// End Block above BTR + + // -- Clear out resource list + // Note: our derived class destructor will have already released resources. + // Clearing this list saves us from having our resources (which are not deleted + // just released) turn around and try to remove themselves from this list. + while (mResourceListHead) + { + GFXResource * head = mResourceListHead; + mResourceListHead = head->mNextResource; + + head->mPrevResource = NULL; + head->mNextResource = NULL; + head->mOwningDevice = NULL; + } +} + +//----------------------------------------------------------------------------- + +F32 GFXDevice::formatByteSize(GFXFormat format) +{ + if(format < GFXFormat_16BIT) + return 1.0f;// 8 bit... + else if(format < GFXFormat_24BIT) + return 2.0f;// 16 bit... + else if(format < GFXFormat_32BIT) + return 3.0f;// 24 bit... + else if(format < GFXFormat_64BIT) + return 4.0f;// 32 bit... + else if(format < GFXFormat_128BIT) + return 8.0f;// 64 bit... + else if(format < GFXFormat_UNKNOWNSIZE) + return 16.0f;// 128 bit... + return 4.0f;// default... +} + +GFXStateBlockRef GFXDevice::createStateBlock(const GFXStateBlockDesc& desc) +{ + PROFILE_SCOPE( GFXDevice_CreateStateBlock ); + + U32 hashValue = desc.getHashValue(); + if (mCurrentStateBlocks[hashValue]) + return mCurrentStateBlocks[hashValue]; + + GFXStateBlockRef result = createStateBlockInternal(desc); + result->registerResourceWithDevice(this); + mCurrentStateBlocks[hashValue] = result; + return result; +} + +void GFXDevice::setStateBlock(GFXStateBlock* block) +{ + AssertFatal(block, "NULL state block!"); + AssertFatal(block->getOwningDevice() == this, "This state doesn't apply to this device!"); + + if (block != mCurrentStateBlock) + { + mStateDirty = true; + mStateBlockDirty = true; + mNewStateBlock = block; + } else { + mStateBlockDirty = false; + mNewStateBlock = mCurrentStateBlock; + } +} + +void GFXDevice::setStateBlockByDesc( const GFXStateBlockDesc &desc ) +{ + PROFILE_SCOPE( GFXDevice_SetStateBlockByDesc ); + GFXStateBlock *block = createStateBlock( desc ); + setStateBlock( block ); +} + +void GFXDevice::setShaderConstBuffer(GFXShaderConstBuffer* buffer) +{ + mCurrentShaderConstBuffer = buffer; +} + + +//----------------------------------------------------------------------------- + +void GFXDevice::updateStates(bool forceSetAll /*=false*/) +{ + PROFILE_SCOPE(GFXDevice_updateStates); + + if(forceSetAll) + { + bool rememberToEndScene = false; + if(!canCurrentlyRender()) + { + if (!beginScene()) + { + AssertFatal(false, "GFXDevice::updateStates: Unable to beginScene!"); + } + rememberToEndScene = true; + } + + setMatrix( GFXMatrixProjection, mProjectionMatrix ); + setMatrix( GFXMatrixWorld, mWorldMatrix[mWorldStackSize] ); + setMatrix( GFXMatrixView, mViewMatrix ); + + if(mCurrentVertexBuffer.isValid()) + mCurrentVertexBuffer->prepare(); + + if( mCurrentPrimitiveBuffer.isValid() ) // This could be NULL when the device is initalizing + mCurrentPrimitiveBuffer->prepare(); + + /// Stateblocks + if ( mNewStateBlock ) + setStateBlockInternal(mNewStateBlock, true); + mCurrentStateBlock = mNewStateBlock; + + for(U32 i = 0; i < getNumSamplers(); i++) + { + switch (mTexType[i]) + { + case GFXTDT_Normal : + { + mCurrentTexture[i] = mNewTexture[i]; + setTextureInternal(i, mCurrentTexture[i]); + } + break; + case GFXTDT_Cube : + { + mCurrentCubemap[i] = mNewCubemap[i]; + if (mCurrentCubemap[i]) + mCurrentCubemap[i]->setToTexUnit(i); + else + setTextureInternal(i, NULL); + } + break; + default: + AssertFatal(false, "Unknown texture type!"); + break; + } + } + + // Set our material + setLightMaterialInternal(mCurrentLightMaterial); + + // Set our lights + for(U32 i = 0; i < LIGHT_STAGE_COUNT; i++) + { + setLightInternal(i, mCurrentLight[i], mCurrentLightEnable[i]); + } + + _updateRenderTargets(); + + if(rememberToEndScene) + endScene(); + + return; + } + + if (!mStateDirty) + return; + + // Normal update logic begins here. + mStateDirty = false; + + // Update Projection Matrix + if( mProjectionMatrixDirty ) + { + setMatrix( GFXMatrixProjection, mProjectionMatrix ); + mProjectionMatrixDirty = false; + } + + // Update World Matrix + if( mWorldMatrixDirty ) + { + setMatrix( GFXMatrixWorld, mWorldMatrix[mWorldStackSize] ); + mWorldMatrixDirty = false; + } + + // Update View Matrix + if( mViewMatrixDirty ) + { + setMatrix( GFXMatrixView, mViewMatrix ); + mViewMatrixDirty = false; + } + + + if( mTextureMatrixCheckDirty ) + { + for( int i = 0; i < getNumSamplers(); i++ ) + { + if( mTextureMatrixDirty[i] ) + { + mTextureMatrixDirty[i] = false; + setMatrix( (GFXMatrixType)(GFXMatrixTexture + i), mTextureMatrix[i] ); + } + } + + mTextureMatrixCheckDirty = false; + } + + // Update vertex buffer + if( mVertexBufferDirty ) + { + if(mCurrentVertexBuffer.isValid()) + mCurrentVertexBuffer->prepare(); + mVertexBufferDirty = false; + } + + // Update primitive buffer + // + // NOTE: It is very important to set the primitive buffer AFTER the vertex buffer + // because in order to draw indexed primitives in DX8, the call to SetIndicies + // needs to include the base vertex offset, and the DX8 GFXDevice relies on + // having mCurrentVB properly assigned before the call to setIndices -patw + if( mPrimitiveBufferDirty ) + { + if( mCurrentPrimitiveBuffer.isValid() ) // This could be NULL when the device is initalizing + mCurrentPrimitiveBuffer->prepare(); + mPrimitiveBufferDirty = false; + } + + // NOTE: With state blocks, it's now important to update state before setting textures + // some devices (e.g. OpenGL) set states on the texture and we need that information before + // the texture is activated. + if (mStateBlockDirty) + { + setStateBlockInternal(mNewStateBlock, false); + mCurrentStateBlock = mNewStateBlock; + mStateBlockDirty = false; + } + + if( mTexturesDirty ) + { + mTexturesDirty = false; + for(U32 i = 0; i < getNumSamplers(); i++) + { + if(!mTextureDirty[i]) + continue; + mTextureDirty[i] = false; + + switch (mTexType[i]) + { + case GFXTDT_Normal : + { + mCurrentTexture[i] = mNewTexture[i]; + setTextureInternal(i, mCurrentTexture[i]); + } + break; + case GFXTDT_Cube : + { + mCurrentCubemap[i] = mNewCubemap[i]; + if (mCurrentCubemap[i]) + mCurrentCubemap[i]->setToTexUnit(i); + else + setTextureInternal(i, NULL); + } + break; + default: + AssertFatal(false, "Unknown texture type!"); + break; + } + } + } + + // Set light material + if(mLightMaterialDirty) + { + setLightMaterialInternal(mCurrentLightMaterial); + mLightMaterialDirty = false; + } + + // Set our lights + if(mLightsDirty) + { + mLightsDirty = false; + for(U32 i = 0; i < LIGHT_STAGE_COUNT; i++) + { + if(!mLightDirty[i]) + continue; + + mLightDirty[i] = false; + setLightInternal(i, mCurrentLight[i], mCurrentLightEnable[i]); + } + } + + _updateRenderTargets(); + +#ifdef TORQUE_DEBUG_RENDER + doParanoidStateCheck(); +#endif +} + +//----------------------------------------------------------------------------- + +void GFXDevice::setPrimitiveBuffer( GFXPrimitiveBuffer *buffer ) +{ + if( buffer == mCurrentPrimitiveBuffer ) + return; + + mCurrentPrimitiveBuffer = buffer; + mPrimitiveBufferDirty = true; + mStateDirty = true; +} + +//----------------------------------------------------------------------------- + +void GFXDevice::drawPrimitive( U32 primitiveIndex ) +{ + if( mStateDirty ) + updateStates(); + + if (mCurrentShaderConstBuffer) + setShaderConstBufferInternal(mCurrentShaderConstBuffer); + + AssertFatal( mCurrentPrimitiveBuffer.isValid(), "Trying to call drawPrimitive with no current primitive buffer, call setPrimitiveBuffer()" ); + AssertFatal( primitiveIndex < mCurrentPrimitiveBuffer->mPrimitiveCount, "Out of range primitive index."); + drawPrimitive( mCurrentPrimitiveBuffer->mPrimitiveArray[primitiveIndex] ); +} + +void GFXDevice::drawPrimitive( const GFXPrimitive &prim ) +{ + // Do NOT add index buffer offset to this call, it will be added by drawIndexedPrimitive + drawIndexedPrimitive( prim.type, + prim.startVertex, + prim.minIndex, + prim.numVertices, + prim.startIndex, + prim.numPrimitives ); +} + +//----------------------------------------------------------------------------- + +void GFXDevice::drawPrimitives() +{ + if( mStateDirty ) + updateStates(); + + if (mCurrentShaderConstBuffer) + setShaderConstBufferInternal(mCurrentShaderConstBuffer); + + AssertFatal( mCurrentPrimitiveBuffer.isValid(), "Trying to call drawPrimitive with no current primitive buffer, call setPrimitiveBuffer()" ); + + GFXPrimitive *info = NULL; + + for( U32 i = 0; i < mCurrentPrimitiveBuffer->mPrimitiveCount; i++ ) { + info = &mCurrentPrimitiveBuffer->mPrimitiveArray[i]; + + // Do NOT add index buffer offset to this call, it will be added by drawIndexedPrimitive + drawIndexedPrimitive( info->type, + info->startVertex, + info->minIndex, + info->numVertices, + info->startIndex, + info->numPrimitives ); + } +} + +//------------------------------------------------------------- +// Console functions +//------------------------------------------------------------- +ConsoleFunction( getDisplayDeviceList, const char*, 1, 1, "Returns a tab-seperated string of the detected devices.") +{ + Vector adapters; + GFXInit::getAdapters(&adapters); + + StringBuilder str; + for (S32 i=0; imName); + } + String temp = str.end(); + U32 tempSize = temp.size(); + + char* retBuffer = Con::getReturnBuffer( tempSize ); + dMemcpy( retBuffer, temp, tempSize ); + return retBuffer; +} + +//----------------------------------------------------------------------------- +// Set projection frustum +//----------------------------------------------------------------------------- +void GFXDevice::setFrustum(F32 left, + F32 right, + F32 bottom, + F32 top, + F32 nearPlane, + F32 farPlane, + bool bRotate) +{ + // store values + mFrustLeft = left; + mFrustRight = right; + mFrustBottom = bottom; + mFrustTop = top; + mFrustNear = nearPlane; + mFrustFar = farPlane; + mFrustOrtho = false; + + // compute matrix + MatrixF projection; + + Point4F row; + row.x = 2.0*nearPlane / (right-left); + row.y = 0.0; + row.z = 0.0; + row.w = 0.0; + projection.setRow( 0, row ); + + row.x = 0.0; + row.y = 2.0 * nearPlane / (top-bottom); + row.z = 0.0; + row.w = 0.0; + projection.setRow( 1, row ); + + row.x = (left+right) / (right-left); + row.y = (top+bottom) / (top-bottom); + row.z = farPlane / (nearPlane-farPlane); + row.w = -1.0; + projection.setRow( 2, row ); + + row.x = 0.0; + row.y = 0.0; + row.z = nearPlane * farPlane / (nearPlane-farPlane); + row.w = 0.0; + projection.setRow( 3, row ); + + projection.transpose(); + + if (bRotate) + { + static MatrixF rotMat(EulerF( (M_PI_F / 2.0f), 0.0f, 0.0f)); + projection.mul( rotMat ); + } + + setProjectionMatrix( projection ); +} + + +//----------------------------------------------------------------------------- +// Get projection frustum +//----------------------------------------------------------------------------- +void GFXDevice::getFrustum(F32 *left, F32 *right, F32 *bottom, F32 *top, F32 *nearPlane, F32 *farPlane, bool *isOrtho ) +{ + if ( left ) *left = mFrustLeft; + if ( right ) *right = mFrustRight; + if ( bottom ) *bottom = mFrustBottom; + if ( top ) *top = mFrustTop; + if ( nearPlane ) *nearPlane = mFrustNear; + if ( farPlane ) *farPlane = mFrustFar; + if ( isOrtho ) *isOrtho = mFrustOrtho; +} + +//----------------------------------------------------------------------------- +// Set frustum using FOV (Field of view) in degrees along the horizontal axis +//----------------------------------------------------------------------------- +void GFXDevice::setFrustum( F32 FOVx, F32 aspectRatio, F32 nearPlane, F32 farPlane ) +{ + // Figure our planes and pass it up. + + //b = a tan D + F32 left = -nearPlane * mTan( mDegToRad(FOVx) / 2.0 ); + F32 right = -left; + F32 bottom = left / aspectRatio; + F32 top = -bottom; + + setFrustum(left, right, bottom, top, nearPlane, farPlane); + + return; +} + +//----------------------------------------------------------------------------- +// Set projection matrix to ortho transform +//----------------------------------------------------------------------------- +void GFXDevice::setOrtho(F32 left, + F32 right, + F32 bottom, + F32 top, + F32 nearPlane, + F32 farPlane, + bool doRotate) +{ + // store values + mFrustLeft = left; + mFrustRight = right; + mFrustBottom = bottom; + mFrustTop = top; + mFrustNear = nearPlane; + mFrustFar = farPlane; + mFrustOrtho = true; + + // compute matrix + MatrixF projection; + + Point4F row; + + row.x = 2.0f / (right - left); + row.y = 0.0f; + row.z = 0.0f; + row.w = 0.0f; + projection.setRow( 0, row ); + + row.x = 0.0f; + row.y = 2.0f / (top - bottom); + row.z = 0.0f; + row.w = 0.0f; + projection.setRow( 1, row ); + + row.x = 0.0f; + row.y = 0.0f; + // This may need be modified to work with OpenGL (d3d has 0..1 projection for z, vs -1..1 in OpenGL) + row.z = 1.0f / (nearPlane - farPlane); + row.w = 0.0f; + projection.setRow( 2, row ); + + row.x = (left + right) / (left - right); + row.y = (top + bottom) / (bottom - top); + row.z = nearPlane / (nearPlane - farPlane); + row.w = 1.0f; + projection.setRow( 3, row ); + + projection.transpose(); + + static MatrixF sRotMat(EulerF( (M_PI_F / 2.0f), 0.0f, 0.0f)); + + if( doRotate ) + projection.mul( sRotMat ); + + setProjectionMatrix( projection ); +} + +Point2F GFXDevice::getWorldToScreenScale() const +{ + Point2F scale; + + const RectI &viewport = getViewport(); + + if ( mFrustOrtho ) + scale.set( viewport.extent.x / ( mFrustRight - mFrustLeft ), + viewport.extent.y / ( mFrustTop - mFrustBottom ) ); + else + scale.set( ( mFrustNear * viewport.extent.x ) / ( mFrustRight - mFrustLeft ), + ( mFrustNear * viewport.extent.y ) / ( mFrustTop - mFrustBottom ) ); + + return scale; +} + +//----------------------------------------------------------------------------- +// Set Light +//----------------------------------------------------------------------------- +void GFXDevice::setLight(U32 stage, GFXLightInfo* light) +{ + AssertFatal(stage < LIGHT_STAGE_COUNT, "GFXDevice::setLight - out of range stage!"); + + if(!mLightDirty[stage]) + { + mStateDirty = true; + mLightsDirty = true; + mLightDirty[stage] = true; + } + mCurrentLightEnable[stage] = (light != NULL); + if(mCurrentLightEnable[stage]) + mCurrentLight[stage] = *light; +} + +//----------------------------------------------------------------------------- +// Set Light Material +//----------------------------------------------------------------------------- +void GFXDevice::setLightMaterial(GFXLightMaterial mat) +{ + mCurrentLightMaterial = mat; + mLightMaterialDirty = true; + mStateDirty = true; +} + +void GFXDevice::setGlobalAmbientColor(ColorF color) +{ + if(mGlobalAmbientColor != color) + { + mGlobalAmbientColor = color; + mGlobalAmbientColorDirty = true; + } +} + +//----------------------------------------------------------------------------- +// Set texture +//----------------------------------------------------------------------------- +void GFXDevice::setTexture( U32 stage, GFXTextureObject *texture ) +{ + AssertFatal(stage < getNumSamplers(), "GFXDevice::setTexture - out of range stage!"); + + if ( mTexType[stage] == GFXTDT_Normal && + ( ( mTextureDirty[stage] && mNewTexture[stage].getPointer() == texture ) || + ( !mTextureDirty[stage] && mCurrentTexture[stage].getPointer() == texture ) ) ) + return; + + mStateDirty = true; + mTexturesDirty = true; + mTextureDirty[stage] = true; + + mNewTexture[stage] = texture; + mTexType[stage] = GFXTDT_Normal; + + // Clear out the cubemaps + mNewCubemap[stage] = NULL; + mCurrentCubemap[stage] = NULL; +} + +//----------------------------------------------------------------------------- +// Set cube texture +//----------------------------------------------------------------------------- +void GFXDevice::setCubeTexture( U32 stage, GFXCubemap *texture ) +{ + AssertFatal(stage < getNumSamplers(), "GFXDevice::setTexture - out of range stage!"); + + if ( mTexType[stage] == GFXTDT_Cube && + ( ( mTextureDirty[stage] && mNewCubemap[stage].getPointer() == texture ) || + ( !mTextureDirty[stage] && mCurrentCubemap[stage].getPointer() == texture ) ) ) + return; + + mStateDirty = true; + mTexturesDirty = true; + mTextureDirty[stage] = true; + + mNewCubemap[stage] = texture; + mTexType[stage] = GFXTDT_Cube; + + // Clear out the normal textures + mNewTexture[stage] = NULL; + mCurrentTexture[stage] = NULL; +} + +inline bool GFXDevice::beginScene() +{ + AssertFatal( mCanCurrentlyRender == false, "GFXDevice::beginScene() - The scene has already begun!" ); + + mDeviceStatistics.clear(); + + // Send the start of frame signal. + getDeviceEventSignal().trigger( GFXDevice::deStartOfFrame ); + + return beginSceneInternal(); +} + +//------------------------------------------------------------------------------ + +inline void GFXDevice::endScene() +{ + AssertFatal( mCanCurrentlyRender == true, "GFXDevice::endScene() - The scene has already ended!" ); + + if( gScreenShot != NULL && gScreenShot->mPending ) + gScreenShot->captureStandard(); + + // End frame signal + getDeviceEventSignal().trigger( GFXDevice::deEndOfFrame ); + + endSceneInternal(); + mDeviceStatistics.exportToConsole(); +} + +void GFXDevice::setViewport( const RectI &inRect ) +{ + // Clip the rect against the renderable size. + Point2I size = mCurrentRT->getSize(); + RectI maxRect(Point2I(0,0), size); + RectI rect = inRect; + rect.intersect(maxRect); + + if ( mViewport != rect ) + { + mViewport = rect; + mViewportDirty = true; + } +} + +void GFXDevice::pushActiveRenderTarget() +{ + // Push the current target on to the stack. + mRTStack.push_back( mCurrentRT ); +} + +void GFXDevice::popActiveRenderTarget() +{ + AssertFatal( mRTStack.size() > 0, "GFXDevice::popActiveRenderTarget() - stack is empty!" ); + + // Restore the last item on the stack and pop. + setActiveRenderTarget( mRTStack.last() ); + mRTStack.pop_back(); +} + +void GFXDevice::setActiveRenderTarget( GFXTarget *target ) +{ + AssertFatal( target, + "GFXDevice::setActiveRenderTarget - must specify a render target!" ); + + if ( target == mCurrentRT ) + return; + + // If we're not dirty then store the + // current RT for deactivation later. + if ( !mRTDirty ) + { + // Deactivate the target queued for deactivation + if(mRTDeactivate) + mRTDeactivate->deactivate(); + + mRTDeactivate = mCurrentRT; + } + + mRTDirty = true; + mCurrentRT = target; + + // When a target changes we also change the viewport + // to match it. This causes problems when the viewport + // has been modified for clipping to a GUI bounds. + // + // We should consider removing this and making it the + // responsibility of the caller to set a proper viewport + // when the target is changed. + setViewport( RectI( Point2I::Zero, mCurrentRT->getSize() ) ); +} + +/// Helper class for GFXDevice::describeResources. +class DescriptionOutputter +{ + /// Are we writing to a file? + bool mWriteToFile; + + /// File if we are writing to a file + FileStream mFile; +public: + DescriptionOutputter(const char* file) + { + mWriteToFile = false; + // If we've been given what could be a valid file path, open it. + if(file && file[0] != '\0') + { + mWriteToFile = mFile.open(file, Torque::FS::File::Write); + + // Note that it is safe to retry. If this is hit, we'll just write to the console instead of to the file. + AssertFatal(mWriteToFile, avar("DescriptionOutputter::DescriptionOutputter - could not open file %s", file)); + } + } + + ~DescriptionOutputter() + { + // Close the file + if(mWriteToFile) + mFile.close(); + } + + /// Writes line to the file or to the console, depending on what we want. + void write(const char* line) + { + if(mWriteToFile) + mFile.writeLine((const U8*)line); + else + Con::printf(line); + } +}; + +#ifndef TORQUE_SHIPPING +void GFXDevice::dumpStates( const char *fileName ) const +{ + DescriptionOutputter output(fileName); + + output.write("Current state"); + if (!mCurrentStateBlock.isNull()) + output.write(mCurrentStateBlock->getDesc().describeSelf().c_str()); + else + output.write("No state!"); + + output.write("\nAll states:\n"); + GFXResource* walk = mResourceListHead; + while(walk) + { + const GFXStateBlock* sb = dynamic_cast(walk); + if (sb) + { + output.write(sb->getDesc().describeSelf().c_str()); + } + walk = walk->getNextResource(); + } +} +#endif + +void GFXDevice::listResources(bool unflaggedOnly) +{ + U32 numTextures = 0, numShaders = 0, numRenderToTextureTargs = 0, numWindowTargs = 0; + U32 numCubemaps = 0, numVertexBuffers = 0, numPrimitiveBuffers = 0, numFences = 0; + U32 numStateBlocks = 0; + + GFXResource* walk = mResourceListHead; + while(walk) + { + if(unflaggedOnly && walk->isFlagged()) + { + walk = walk->getNextResource(); + continue; + } + + if(dynamic_cast(walk)) + numTextures++; + else if(dynamic_cast(walk)) + numShaders++; + else if(dynamic_cast(walk)) + numRenderToTextureTargs++; + else if(dynamic_cast(walk)) + numWindowTargs++; + else if(dynamic_cast(walk)) + numCubemaps++; + else if(dynamic_cast(walk)) + numVertexBuffers++; + else if(dynamic_cast(walk)) + numPrimitiveBuffers++; + else if(dynamic_cast(walk)) + numFences++; + else if (dynamic_cast(walk)) + numStateBlocks++; + else + Con::warnf("Unknown resource: %x", walk); + + walk = walk->getNextResource(); + } + const char* flag = unflaggedOnly ? "unflagged" : "allocated"; + + Con::printf("GFX currently has:"); + Con::printf(" %i %s textures", numTextures, flag); + Con::printf(" %i %s shaders", numShaders, flag); + Con::printf(" %i %s texture targets", numRenderToTextureTargs, flag); + Con::printf(" %i %s window targets", numWindowTargs, flag); + Con::printf(" %i %s cubemaps", numCubemaps, flag); + Con::printf(" %i %s vertex buffers", numVertexBuffers, flag); + Con::printf(" %i %s primitive buffers", numPrimitiveBuffers, flag); + Con::printf(" %i %s fences", numFences, flag); + Con::printf(" %i %s state blocks", numStateBlocks, flag); +} + +void GFXDevice::fillResourceVectors(const char* resNames, bool unflaggedOnly, Vector &textureObjects, + Vector &textureTargets, Vector &windowTargets, Vector &vertexBuffers, + Vector &primitiveBuffers, Vector &fences, Vector &cubemaps, + Vector &shaders, Vector &stateblocks) +{ + bool describeTexture = true, describeTextureTarget = true, describeWindowTarget = true, describeVertexBuffer = true, + describePrimitiveBuffer = true, describeFence = true, describeCubemap = true, describeShader = true, + describeStateBlock = true; + + // If we didn't specify a string of names, we'll print all of them + if(resNames && resNames[0] != '\0') + { + // If we did specify a string of names, determine which names + describeTexture = (dStrstr(resNames, "GFXTextureObject") != NULL); + describeTextureTarget = (dStrstr(resNames, "GFXTextureTarget") != NULL); + describeWindowTarget = (dStrstr(resNames, "GFXWindowTarget") != NULL); + describeVertexBuffer = (dStrstr(resNames, "GFXVertexBuffer") != NULL); + describePrimitiveBuffer = (dStrstr(resNames, "GFXPrimitiveBuffer") != NULL); + describeFence = (dStrstr(resNames, "GFXFence") != NULL); + describeCubemap = (dStrstr(resNames, "GFXCubemap") != NULL); + describeShader = (dStrstr(resNames, "GFXShader") != NULL); + describeStateBlock = (dStrstr(resNames, "GFXStateBlock") != NULL); + } + + // Start going through the list + GFXResource* walk = mResourceListHead; + while(walk) + { + // If we only want unflagged resources, skip all flagged resources + if(unflaggedOnly && walk->isFlagged()) + { + walk = walk->getNextResource(); + continue; + } + + // All of the following checks go through the same logic. + // if(describingThisResource) + // { + // ResourceType* type = dynamic_cast(walk) + // if(type) + // { + // typeVector.push_back(type); + // walk = walk->getNextResource(); + // continue; + // } + // } + + if(describeTexture) + { + GFXTextureObject* tex = dynamic_cast(walk); + { + if(tex) + { + textureObjects.push_back(tex); + walk = walk->getNextResource(); + continue; + } + } + } + if(describeShader) + { + GFXShader* shd = dynamic_cast(walk); + if(shd) + { + shaders.push_back(shd); + walk = walk->getNextResource(); + continue; + } + } + if(describeVertexBuffer) + { + GFXVertexBuffer* buf = dynamic_cast(walk); + if(buf) + { + vertexBuffers.push_back(buf); + walk = walk->getNextResource(); + continue; + } + } + if(describePrimitiveBuffer) + { + GFXPrimitiveBuffer* buf = dynamic_cast(walk); + if(buf) + { + primitiveBuffers.push_back(buf); + walk = walk->getNextResource(); + continue; + } + } + if(describeTextureTarget) + { + GFXTextureTarget* targ = dynamic_cast(walk); + if(targ) + { + textureTargets.push_back(targ); + walk = walk->getNextResource(); + continue; + } + } + if(describeWindowTarget) + { + GFXWindowTarget* targ = dynamic_cast(walk); + if(targ) + { + windowTargets.push_back(targ); + walk = walk->getNextResource(); + continue; + } + } + if(describeCubemap) + { + GFXCubemap* cube = dynamic_cast(walk); + if(cube) + { + cubemaps.push_back(cube); + walk = walk->getNextResource(); + continue; + } + } + if(describeFence) + { + GFXFence* fence = dynamic_cast(walk); + if(fence) + { + fences.push_back(fence); + walk = walk->getNextResource(); + continue; + } + } + if (describeStateBlock) + { + GFXStateBlock* sb = dynamic_cast(walk); + if (sb) + { + stateblocks.push_back(sb); + walk = walk->getNextResource(); + continue; + } + } + // Wasn't something we were looking for + walk = walk->getNextResource(); + } +} + +void GFXDevice::describeResources(const char* resNames, const char* filePath, bool unflaggedOnly) +{ + const U32 numResourceTypes = 9; + Vector resVectors[numResourceTypes]; + const char* reslabels[numResourceTypes] = { "texture", "texture target", "window target", "vertex buffers", "primitive buffers", "fences", "cubemaps", "shaders", "stateblocks" }; + + // Fill the vectors with the right resources + fillResourceVectors(resNames, unflaggedOnly, resVectors[0], resVectors[1], resVectors[2], resVectors[3], + resVectors[4], resVectors[5], resVectors[6], resVectors[7], resVectors[8]); + + // Helper object + DescriptionOutputter output(filePath); + + // Print the info to the file + // Note that we check if we have any objects of that type. + for (U32 i = 0; i < numResourceTypes; i++) + { + if (resVectors[i].size()) + { + // Header + String header = String::ToString("--------Dumping GFX %s descriptions...----------", reslabels[i]); + output.write(header); + // Data + for (U32 j = 0; j < resVectors[i].size(); j++) + { + GFXResource* resource = resVectors[i][j]; + String dataline = String::ToString("Addr: %x %s", resource, resource->describeSelf().c_str()); + output.write(dataline.c_str()); + } + // Footer + output.write("--------------------Done---------------------"); + output.write(""); + } + } +} + +void GFXDevice::flagCurrentResources() +{ + GFXResource* walk = mResourceListHead; + while(walk) + { + walk->setFlag(); + walk = walk->getNextResource(); + } +} + +void GFXDevice::clearResourceFlags() +{ + GFXResource* walk = mResourceListHead; + while(walk) + { + walk->clearFlag(); + walk = walk->getNextResource(); + } +} + +ConsoleFunction(listGFXResources, void, 1, 2, "(bool unflaggedOnly = false)") +{ + bool unflaggedOnly = false; + if(argc == 2) + unflaggedOnly = dAtob(argv[1]); + GFX->listResources(unflaggedOnly); +} + +ConsoleFunction(flagCurrentGFXResources, void, 1, 1, "") +{ + GFX->flagCurrentResources(); +} + +ConsoleFunction(clearGFXResourceFlags, void, 1, 1, "") +{ + GFX->clearResourceFlags(); +} + +ConsoleFunction(describeGFXResources, void, 3, 4, "(string resourceNames, string filePath, bool unflaggedOnly = false)\n" + " If resourceNames is "", this function describes all resources.\n" + " If filePath is "", this function writes the resource descriptions to the console") +{ + bool unflaggedOnly = false; + if(argc == 4) + unflaggedOnly = dAtob(argv[3]); + GFX->describeResources(argv[1], argv[2], unflaggedOnly); +} + +ConsoleFunction(describeGFXStateBlocks, void, 2, 2, "(string filePath)\n" + " If filePath is "", this function writes the resource descriptions to the console") +{ + GFX->dumpStates(argv[1]); +} + +//----------------------------------------------------------------------------- +// Get pixel shader version - for script +//----------------------------------------------------------------------------- +ConsoleFunction( getPixelShaderVersion, F32, 1, 1, "Get pixel shader version.\n\n" ) +{ + return GFX->getPixelShaderVersion(); +} + +//----------------------------------------------------------------------------- +// Set pixel shader version - for script +//----------------------------------------------------------------------------- +ConsoleFunction( setPixelShaderVersion, void, 2, 2, "Set pixel shader version.\n\n" ) +{ + GFX->setPixelShaderVersion( dAtof(argv[1]) ); +} + +ConsoleFunction( getDisplayDeviceInformation, const char*, 1, 1, "Get a string describing the current GFX device") +{ + if (!GFXDevice::devicePresent()) + return "(no device)"; + + const GFXAdapter& adapter = GFX->getAdapter(); + return adapter.getName(); +} diff --git a/gfx/gfxDevice.h b/gfx/gfxDevice.h new file mode 100644 index 0000000..907cd98 --- /dev/null +++ b/gfx/gfxDevice.h @@ -0,0 +1,968 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXDEVICE_H_ +#define _GFXDEVICE_H_ + +#ifndef _GFXADAPTER_H_ +#include "gfx/gfxAdapter.h" +#endif + +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif + +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif + +#ifndef _GFXCUBEMAP_H_ +#include "gfx/gfxCubemap.h" +#endif + +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif + +#ifndef _GFXDEVICESTATISTICS_H_ +#include "gfx/gfxDeviceStatistics.h" +#endif + +class FontRenderBatcher; +class GFont; +class GFXCardProfiler; +class GFXDrawUtil; +class GFXFence; +class GFXOcclusionQuery; +class GFXPrimitiveBuffer; +class GFXShader; +class GFXStateBlock; +class GFXShaderConstBuffer; +class GFXTextureManager; + +// Global macro +#define GFX GFXDevice::get() + +#define MAX_MRT_TARGETS 4 + +//----------------------------------------------------------------------------- + +/// GFXDevice is the TSE graphics interface layer. This allows the TSE to +/// do many things, such as use multiple render devices for multi-head systems, +/// and allow a game to render in DirectX 9, OpenGL or any other API which has +/// a GFX implementation seamlessly. There are many concepts in GFX device which +/// may not be familiar to you, especially if you have not used DirectX. +/// @n +/// Buffers +/// There are three types of buffers in GFX: vertex, index and primitive. Please +/// note that index buffers are not accessable outside the GFX layer, they are wrapped +/// by primitive buffers. Primitive buffers will be explained in detail later. +/// Buffers are allocated and deallocated using their associated allocXBuffer and +/// freeXBuffer methods on the device. When a buffer is allocated you pass in a +/// pointer to, depending on the buffer, a vertex type pointer or a U16 pointer. +/// During allocation, this pointer is set to the address of where you should +/// copy in the information for this buffer. You must the tell the GFXDevice +/// that the information is in, and it should prepare the buffer for use by calling +/// the prepare method on it. Dynamic vertex buffer example: +/// @code +/// GFXVertexP *verts; // Making a buffer containing verticies with only position +/// +/// // Allocate a dynamic vertex buffer to hold 3 vertices and use *verts as the location to copy information into +/// GFXVertexBufferHandle vb = GFX->allocVertexBuffer( 3, &verts, true ); +/// +/// // Now set the information, we're making a triangle +/// verts[0].point = Point3F( 200.f, 200.f, 0.f ); +/// verts[1].point = Point3F( 200.f, 400.f, 0.f ); +/// verts[2].point = Point3F( 400.f, 200.f, 0.f ); +/// +/// // Tell GFX that the information is in and it should be made ready for use +/// // Note that nothing is done with verts, this should not and MUST NOT be deleted +/// // stored, or otherwise used after prepare is called. +/// GFX->prepare( vb ); +/// +/// // Because this is a dynamic vertex buffer, it is only assured to be valid until someone +/// // else allocates a dynamic vertex buffer, so we will render it now +/// GFX->setVertexBuffer( vb ); +/// GFX->drawPrimitive( GFXTriangleStrip, 0, 1 ); +/// +/// // Now because this is a dynamic vertex buffer it MUST NOT BE FREED you are only +/// // given a handle to a vertex buffer which belongs to the device +/// @endcode +/// +/// To use a static vertex buffer, it is very similar, this is an example using a +/// static primitive buffer: +/// @n +/// This takes place inside a constructor for a class which has a member variable +/// called mPB which is the primitive buffer for the class instance. +/// @code +/// U16 *idx; // This is going to be where to write indices +/// GFXPrimitiveInfo *primitiveInfo; // This will be where to write primitive information +/// +/// // Allocate a primitive buffer with 4 indices, and 1 primitive described for use +/// mPB = GFX->allocPrimitiveBuffer( 4, &idx, 1, &primitiveInfo ); +/// +/// // Write the index information, this is going to be for the outline of a triangle using +/// // a line strip +/// idx[0] = 0; +/// idx[1] = 1; +/// idx[2] = 2; +/// idx[3] = 0; +/// +/// // Write the information for the primitive +/// primitiveInfo->indexStart = 0; // Starting with index 0 +/// primitiveInfo->minVertex = 0; // The minimum vertex index is 0 +/// primitiveInfo->maxVertex = 3; // The maximum vertex index is 3 +/// primitiveInfo->primitiveCount = 3; // There are 3 lines we are drawing +/// primitiveInfo->type = GFXLineStrip; // This primitive info describes a line strip +/// @endcode +/// The following code takes place in the destructor for the same class +/// @code +/// // Because this is a static buffer it's our responsibility to free it when we are done +/// GFX->freePrimitiveBuffer( mPB ); +/// @endcode +/// This last bit takes place in the rendering function for the class +/// @code +/// // You need to set a vertex buffer as well, primitive buffers contain indexing +/// // information, not vertex information. This is so you could have, say, a static +/// // vertex buffer, and a dynamic primitive buffer. +/// +/// // This sets the primitive buffer to the static buffer we allocated in the constructor +/// GFX->setPrimitiveBuffer( mPB ); +/// +/// // Draw the first primitive contained in the set primitive buffer, our primitive buffer +/// // has only one primitive, so we could also technically call GFX->drawPrimitives(); and +/// // get the same result. +/// GFX->drawPrimitive( 0 ); +/// @endcode +/// If you need any more examples on how to use these buffers please see the rest of the engine. +/// @n +/// Primitive Buffers +/// @n +/// Primitive buffers wrap and extend the concept of index buffers. The purpose of a primitive +/// buffer is to let objects store all information they have to render their primitives in +/// a central place. Say that a shape is made up of triangle strips and triangle fans, it would +/// still have only one primitive buffer which contained primitive information for each strip +/// and fan. It could then draw itself with one call. +/// +/// TO BE FINISHED LATER +class GFXDevice +{ +private: + friend class GFXInit; + friend class GFXPrimitiveBufferHandle; + friend class GFXVertexBufferHandleBase; + friend class GFXTextureObject; + friend class GFXTexHandle; + friend class GFXTestFullscreenToggle; + friend class TestGFXTextureCube; + friend class TestGFXRenderTargetCube; + friend class TestGFXRenderTargetStack; + friend class GFXResource; + friend class LightMatInstance; // For stencil interface + + //-------------------------------------------------------------------------- + // Static GFX interface + //-------------------------------------------------------------------------- +public: + + enum GFXDeviceEventType + { + /// The device has been created, but not initialized + deCreate, + + /// The device has been initialized + deInit, + + /// The device is about to be destroyed. + deDestroy, + + /// The device has started rendering a frame + deStartOfFrame, + + /// The device is about to finish rendering a frame + deEndOfFrame, + }; + + typedef Signal DeviceEventSignal; + static DeviceEventSignal& getDeviceEventSignal(); + + static GFXDevice *get() { return smGFXDevice; } + + static void initConsole(); + static bool destroy(); + + static bool devicePresent() { return smGFXDevice != NULL; } + +private: + /// @name Device management variables + /// @{ + static GFXDevice * smGFXDevice; ///< Global GFXDevice + + /// @} + + //-------------------------------------------------------------------------- + // Core GFX interface + //-------------------------------------------------------------------------- +private: + + /// Adapter for this device. + GFXAdapter mAdapter; + +protected: + /// List of valid video modes for this device. + Vector mVideoModes; + + /// The CardProfiler for this device. + GFXCardProfiler *mCardProfiler; + + /// Head of the resource list. + /// + /// @see GFXResource + GFXResource *mResourceListHead; + + /// Set once the device is active. + bool mCanCurrentlyRender; + + /// Set if we're in a mode where we want rendering to occur. + bool mAllowRender; + + /// This will allow querying to see if a device is initialized and ready to + /// have operations performed on it. + bool mInitialized; + + /// This is called before this, or any other device, is deleted in the global destroy() + /// method. It allows the device to clean up anything while everything is still valid. + virtual void preDestroy(); + + /// Set the adapter that this device is using. For use by GFXInit::createDevice only. + virtual void setAdapter(const GFXAdapter& adapter) { mAdapter = adapter; } + + /// Notify GFXDevice that we are initialized + virtual void deviceInited(); +public: + GFXDevice(); + virtual ~GFXDevice(); + + /// Initialize this GFXDevice, optionally specifying a platform window to + /// bind to. + virtual void init( const GFXVideoMode &mode, PlatformWindow *window = NULL ) = 0; + + /// Returns true if the scene has begun and its + /// safe to make rendering calls. + /// @see beginScene + /// @see endScene + bool canCurrentlyRender() const { return mCanCurrentlyRender; } + + void setAllowRender( bool render ) { mAllowRender = render; } + + inline bool allowRender() const { return mAllowRender; } + + GFXCardProfiler* getCardProfiler() const { return mCardProfiler; } + + /// Returns active graphics adapter type. + virtual GFXAdapterType getAdapterType()=0; + + /// Returns the Adapter that was used to create this device + virtual const GFXAdapter& getAdapter() { return mAdapter; } + + /// @} + + /// @name Debug Methods + /// @{ + + virtual void enterDebugEvent(ColorI color, const char *name) = 0; + virtual void leaveDebugEvent() = 0; + virtual void setDebugMarker(ColorI color, const char *name) = 0; + + /// @} + + /// @name Resource debug methods + /// @{ + + /// Lists how many of each GFX resource (e.g. textures, texture targets, shaders, etc.) GFX is aware of + /// @param unflaggedOnly If true, this method only counts unflagged resources + virtual void listResources(bool unflaggedOnly); + + /// Flags all resources GFX is currently aware of + virtual void flagCurrentResources(); + + /// Clears the flag on all resources GFX is currently aware of + virtual void clearResourceFlags(); + + /// Dumps a description of the specified resource types to the console + /// @param resNames A string of space separated class names (e.g. "GFXTextureObject GFXTextureTarget GFXShader") + /// to describe to the console + /// @param file A path to the file to write the descriptions to. If it is NULL or "", descriptions are + /// written to the console. + /// @param unflaggedOnly If true, this method only counts unflagged resources + /// @note resNames is case sensitive because there is no dStristr function. + virtual void describeResources(const char* resName, const char* file, bool unflaggedOnly); + + /// Returns the current GFXDeviceStatistics, stats are cleared every ::beginScene call. + GFXDeviceStatistics* getDeviceStatistics() { return &mDeviceStatistics; } +protected: + GFXDeviceStatistics mDeviceStatistics; + + /// This is a helper method for describeResourcesToFile. It walks through the + /// GFXResource list and sorts it by item type, putting the resources into the proper vector. + /// @see describeResources + virtual void fillResourceVectors(const char* resNames, bool unflaggedOnly, Vector &textureObjects, + Vector &textureTargets, Vector &windowTargets, Vector &vertexBuffers, + Vector &primitiveBuffers, Vector &fences, Vector &cubemaps, + Vector &shaders, Vector &stateblocks); +public: + + /// @} + + /// @name Video Mode Functions + /// @{ + /// Enumerates the supported video modes of the device + virtual void enumerateVideoModes() = 0; + + /// Returns the video mode list. + /// @see GFXVideoMode + const Vector* const getVideoModeList() const { return &mVideoModes; } + + static F32 formatByteSize( GFXFormat format ); + + /// Returns the first format from the list which meets all + /// the criteria of the texture profile and query options. + virtual GFXFormat selectSupportedFormat(GFXTextureProfile *profile, + const Vector &formats, bool texture, bool mustblend, bool mustfilter) = 0; + + /// @} + + //----------------------------------------------------------------------------- +protected: + + /// @name State tracking variables + /// @{ + + /// Set if ANY state is dirty, including matrices or primitive buffers. + bool mStateDirty; + + enum TexDirtyType + { + GFXTDT_Normal, + GFXTDT_Cube + }; + + GFXTexHandle mCurrentTexture[TEXTURE_STAGE_COUNT]; + GFXTexHandle mNewTexture[TEXTURE_STAGE_COUNT]; + GFXCubemapHandle mCurrentCubemap[TEXTURE_STAGE_COUNT]; + GFXCubemapHandle mNewCubemap[TEXTURE_STAGE_COUNT]; + + TexDirtyType mTexType[TEXTURE_STAGE_COUNT]; + bool mTextureDirty[TEXTURE_STAGE_COUNT]; + bool mTexturesDirty; + + // This maps a GFXStateBlockDesc hash value to a GFXStateBlockRef + typedef Map StateBlockMap; + StateBlockMap mCurrentStateBlocks; + + // This tracks whether or not our state block is dirty. + bool mStateBlockDirty; + GFXStateBlockRef mCurrentStateBlock; + GFXStateBlockRef mNewStateBlock; + + GFXShaderConstBuffer *mCurrentShaderConstBuffer; + + /// A global forced wireframe mode. + static bool smWireframe; + + /// @} + + /// @name Light Tracking + /// @{ + + GFXLightInfo mCurrentLight[LIGHT_STAGE_COUNT]; + bool mCurrentLightEnable[LIGHT_STAGE_COUNT]; + bool mLightDirty[LIGHT_STAGE_COUNT]; + bool mLightsDirty; + + ColorF mGlobalAmbientColor; + bool mGlobalAmbientColorDirty; + + /// @} + + /// @name Fixed function material tracking + /// @{ + + GFXLightMaterial mCurrentLightMaterial; + bool mLightMaterialDirty; + + /// @} + + /// @name Bitmap modulation and color stack + /// @{ + + /// + + /// @} + + /// @see getDeviceSwizzle32 + Swizzle *mDeviceSwizzle32; + + /// @see getDeviceSwizzle24 + Swizzle *mDeviceSwizzle24; + + + //----------------------------------------------------------------------------- + + /// @name Matrix managing variables + /// @{ + + /// + MatrixF mWorldMatrix[WORLD_STACK_MAX]; + bool mWorldMatrixDirty; + S32 mWorldStackSize; + + MatrixF mProjectionMatrix; + bool mProjectionMatrixDirty; + + MatrixF mViewMatrix; + bool mViewMatrixDirty; + + MatrixF mTextureMatrix[TEXTURE_STAGE_COUNT]; + bool mTextureMatrixDirty[TEXTURE_STAGE_COUNT]; + bool mTextureMatrixCheckDirty; + /// @} + + /// @name Current frustum planes + /// @{ + + /// + F32 mFrustLeft, mFrustRight; + F32 mFrustBottom, mFrustTop; + F32 mFrustNear, mFrustFar; + bool mFrustOrtho; + + //----------------------------------------------------------------------------- + + /// @name Stateblock functions + /// @{ + + /// Called by GFXDevice to create a device specific stateblock + virtual GFXStateBlockRef createStateBlockInternal(const GFXStateBlockDesc& desc) = 0; + /// Called by GFXDevice to actually set a stateblock. + /// @param force If true, set all states + virtual void setStateBlockInternal(GFXStateBlock* block, bool force) = 0; + /// @} + + /// Called by base GFXDevice to actually set a const buffer + virtual void setShaderConstBufferInternal(GFXShaderConstBuffer* buffer) = 0; + + virtual void setTextureInternal(U32 textureUnit, const GFXTextureObject*texture) = 0; + + virtual void setLightInternal(U32 lightStage, const GFXLightInfo light, bool lightEnable) = 0; + virtual void setGlobalAmbientInternal(ColorF color) = 0; + virtual void setLightMaterialInternal(const GFXLightMaterial mat) = 0; + + virtual bool beginSceneInternal() = 0; + virtual void endSceneInternal() = 0; + + /// @name State Initialization. + /// @{ + + /// State initialization. This MUST BE CALLED in setVideoMode after the device + /// is created. + virtual void initStates() = 0; + /// @} + + //----------------------------------------------------------------------------- + + /// This function must be implemented differently per + /// API and it should set ONLY the current matrix. + /// For example, in OpenGL, there should be NO matrix stack + /// activity, all the stack stuff is managed in the GFX layer. + /// + /// OpenGL does not have separate world and + /// view matrices. It has ModelView which is world * view. + /// You must take this into consideration. + /// + /// @param mtype Which matrix to set, world/view/projection + /// @param mat Matrix to assign + virtual void setMatrix( GFXMatrixType mtype, const MatrixF &mat ) = 0; + + //----------------------------------------------------------------------------- +protected: + + + /// @name Buffer Allocation + /// These methods are implemented per-device and are called by the GFX layer + /// when a user calls an alloc + /// + /// @note Primitive Buffers are NOT implemented per device, they wrap index buffers + /// @{ + + /// This allocates a vertex buffer and returns a pointer to the allocated buffer. + /// This function should not be called directly - rather it should be used by + /// the GFXVertexBufferHandle class. + virtual GFXVertexBuffer *allocVertexBuffer( U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertSize, + GFXBufferType bufferType ) = 0; + + StrongRefPtr mCurrentVertexBuffer; + bool mVertexBufferDirty; + + StrongRefPtr mCurrentPrimitiveBuffer; + bool mPrimitiveBufferDirty; + + /// This allocates a primitive buffer and returns a pointer to the allocated buffer. + /// A primitive buffer's type argument refers to the index data - the primitive data will + /// always be preserved from call to call. + /// + /// @note All index buffers use unsigned 16-bit indices. + virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ) = 0; + /// @} + + //--------------------------------------- + // SFX buffer + //--------------------------------------- +protected: + + GFXTexHandle mFrontBuffer[2]; + U32 mCurrentFrontBufferIdx; + + //--------------------------------------- + // Render target related + //--------------------------------------- + + /// A stack of previously active render targets. + Vector mRTStack; + + /// The current render target which may or may not + /// not be yet activated. + /// @see mRTDirty + GFXTargetRef mCurrentRT; + + /// This tracks a previously activated render target + /// which need to be deactivated. + GFXTargetRef mRTDeactivate; + + /// This is set when the current and/or deactivate render + /// targets have changed and the device need to update + /// its state on the next draw/clear. + bool mRTDirty; + + /// Updates the render targets and viewport in a device + /// specific manner when they are dirty. + virtual void _updateRenderTargets() = 0; + + /// The current viewport rect. + RectI mViewport; + + /// If true the viewport has been changed and + /// it must be updated on the next draw/clear. + bool mViewportDirty; + +public: + + /// @name Texture functions + /// @{ +protected: + GFXTextureManager * mTextureManager; + +public: + virtual GFXCubemap * createCubemap() = 0; + + inline GFXTextureManager *getTextureManager() + { + return mTextureManager; + } + + ///@} + + /// Swizzle to convert 32bpp bitmaps from RGBA to the native device format. + const Swizzle *getDeviceSwizzle32() const + { + return mDeviceSwizzle32; + } + + /// Swizzle to convert 24bpp bitmaps from RGB to the native device format. + const Swizzle *getDeviceSwizzle24() const + { + return mDeviceSwizzle24; + } + + /// @name Render Target functions + /// @{ + + /// Allocate a target for doing render to texture operations, with no + /// depth/stencil buffer. + virtual GFXTextureTarget *allocRenderToTextureTarget()=0; + + /// Allocate a target for a given window. + virtual GFXWindowTarget *allocWindowTarget(PlatformWindow *window)=0; + + /// Store the current render target to restore later. + void pushActiveRenderTarget(); + + /// Restore the previous render target. + void popActiveRenderTarget(); + + /// Assign a new active render target. + void setActiveRenderTarget( GFXTarget *target ); + + /// Returns the current active render target. + inline GFXTarget* getActiveRenderTarget() { return mCurrentRT; } + + ///@} + + /// @name Shader functions + /// @{ + virtual F32 getPixelShaderVersion() const = 0; + virtual void setPixelShaderVersion( F32 version ) = 0; + + /// Returns the number of texture samplers that can be used in a shader rendering pass + virtual U32 getNumSamplers() const = 0; + + /// Returns the number of simultaneous render targets supported by the device. + virtual U32 getNumRenderTargets() const = 0; + + virtual void setShader( GFXShader *shader ) {} + virtual void disableShaders() {} + + /// Set the buffer! (Actual set happens on the next draw call, just like textures, state blocks, etc) + void setShaderConstBuffer(GFXShaderConstBuffer* buffer); + + /// Creates a new empty shader which must be initialized + /// and deleted by the caller. + /// @see GFXShader::init + virtual GFXShader* createShader() = 0; + + /// @} + + //----------------------------------------------------------------------------- + + /// @name Rendering methods + /// @{ + + /// + virtual void clear( U32 flags, ColorI color, F32 z, U32 stencil ) = 0; + virtual bool beginScene(); + virtual void endScene(); + + virtual GFXTexHandle & getFrontBuffer(){ return mFrontBuffer[mCurrentFrontBufferIdx]; } + + void setPrimitiveBuffer( GFXPrimitiveBuffer *buffer ); + void setVertexBuffer( GFXVertexBuffer *buffer ); + + virtual void drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ) = 0; + + /// The parameters to drawIndexedPrimitive are somewhat complicated. From a raw-data stand point + /// they evaluate to something like the following: + /// @code + /// U16 indicies[] = { 0, 1, 2, 1, 0, 0, 2 }; + /// Point3F verts[] = { Point3F( 0.0f, 0.0f, 0.0f ), Point3F( 0.0f, 1.0f, 0.0f ), Point3F( 0.0f, 0.0f, 1.0f ) }; + /// + /// GFX->drawIndexedPrimitive( GFXLineList, // Drawing a list of lines, each line is two verts + /// 0, // vertex 0 will be referenced so minIndex = 0 + /// 3, // 3 verticies will be used for this draw call + /// 1, // We want index 1 to be the first index used, so indicies 1-6 will be used + /// 3 // Drawing 3 LineList primitives, meaning 6 verts will be drawn + /// ); + /// + /// U16 *idxPtr = &indicies[1]; // 1 = startIndex, so the pointer is offset such that: + /// // idxPtr[0] is the same as indicies[1] + /// + /// U32 numVertsToDrawFromBuffer = primitiveCount * 2; // 2 verts define a line in the GFXLineList primitive type (6) + /// @endcode + /// + /// @param primType Type of primitive to draw + /// + /// @param startVertex This defines index zero. Its the offset from the start of + /// the vertex buffer to the first vertex. + /// + /// @param minIndex The smallest index into the vertex stream which will be used for this draw call. + /// This is a zero based index relative to startVertex. It is strictly a performance + /// hint for implementations. No vertex below minIndex will be referenced by this draw + /// call. For device implementors, this should _not_ be used to offset the vertex buffer, + /// or index buffer. + /// + /// @param numVerts The number of verticies which will be referenced in this draw call. This is not + /// the number of verticies which will be drawn. That is a function of 'primType' and + /// 'primitiveCount'. + /// + /// @param startIndex An offset from the start of the index buffer to specify where to start. If + /// 'idxBuffer' is a pointer to an array of integers, this could be written as + /// int *offsetIdx = idxBuffer + startIndex; + /// + /// @param primitiveCount The number of primitives of type 'primType' to draw. + /// + virtual void drawIndexedPrimitive( GFXPrimitiveType primType, + U32 startVertex, + U32 minIndex, + U32 numVerts, + U32 startIndex, + U32 primitiveCount ) = 0; + + void drawPrimitive( const GFXPrimitive &prim ); + void drawPrimitive( U32 primitiveIndex ); + void drawPrimitives(); + void drawPrimitiveBuffer( GFXPrimitiveBuffer *buffer ); + /// @} + + //----------------------------------------------------------------------------- + + /// Allocate a fence. The API specific implementation of GFXDevice is responsible + /// to make sure that the proper type is used. GFXGeneralFence should work in + /// all cases. + virtual GFXFence *createFence() = 0; + + /// Returns a hardware occlusion query object or NULL + /// if this device does not support them. + virtual GFXOcclusionQuery* createOcclusionQuery() { return NULL; } + + /// @name Light Settings + /// NONE of these should be overridden by API implementations + /// because of the state caching stuff. + /// @{ + void setLight(U32 stage, GFXLightInfo* light); + void setLightMaterial(GFXLightMaterial mat); + void setGlobalAmbientColor(ColorF color); + + /// @} + + /// @name Texture State Settings + /// NONE of these should be overridden by API implementations + /// because of the state caching stuff. + /// @{ + + /// + void setTexture(U32 stage, GFXTextureObject*texture); + void setCubeTexture( U32 stage, GFXCubemap *cubemap ); + inline GFXTextureObject* getCurrentTexture( U32 stage ) { return mCurrentTexture[stage]; } + + /// @} + + /// @name State Block Interface + /// @{ + + /// Creates a state block object based on the desc passed in. This object + /// represents an immutable state. + virtual GFXStateBlockRef createStateBlock( const GFXStateBlockDesc &desc ); + + /// Sets the current stateblock (actually activated in ::updateStates) + virtual void setStateBlock( GFXStateBlock *block ); + + /// This sets a stateblock directly from the description + /// structure. Its acceptable to use this for debug rendering + /// and other low frequency rendering tasks. + virtual void setStateBlockByDesc( const GFXStateBlockDesc &desc ); + + /// @} + + /// @name General state interface + /// @{ + /// Sets the dirty Render/Texture/Sampler states from the caching system + void updateStates(bool forceSetAll = false); + + /// Returns the forced global wireframe state. + static bool getWireframe() { return smWireframe; } + /// @} + + //----------------------------------------------------------------------------- + + /// @name Matrix interface + /// @{ + + /// Sets the top of the world matrix stack + /// @param newWorld New world matrix to set + void setWorldMatrix( const MatrixF &newWorld ); + + /// Gets the matrix on the top of the world matrix stack + inline const MatrixF &getWorldMatrix() const { return mWorldMatrix[mWorldStackSize]; } + + /// Pushes the world matrix stack and copies the current top + /// matrix to the new top of the stack + void pushWorldMatrix(); + + /// Pops the world matrix stack + void popWorldMatrix(); + + /// Sets the projection matrix + /// @param newProj New projection matrix to set + void setProjectionMatrix( const MatrixF &newProj ); + + /// Gets the projection matrix + inline const MatrixF &getProjectionMatrix() const { return mProjectionMatrix; } + + /// Sets the view matrix + /// @param newView New view matrix to set + void setViewMatrix( const MatrixF &newView ); + + /// Gets the view matrix + inline const MatrixF &getViewMatrix() const { return mViewMatrix; } + + /// Multiplies the matrix at the top of the world matrix stack by a matrix + /// and replaces the top of the matrix stack with the result + /// @param mat Matrix to multiply + void multWorld( const MatrixF &mat ); + + /// Set texture matrix for a sampler + void setTextureMatrix( const U32 stage, const MatrixF &texMat ); + + /// Set an area of the target to render to. + void setViewport( const RectI &rect ); + + /// Get the current area of the target we will render to. + const RectI &getViewport() const { return mViewport; } + + virtual void setClipRect( const RectI &rect ) = 0; + virtual const RectI &getClipRect() const = 0; + + virtual void setFrustum( F32 left, F32 right, + F32 bottom, F32 top, + F32 nearPlane, F32 farPlane, + bool bRotate = true); + void getFrustum(F32 *left, F32 *right, F32 *bottom, F32 *top, F32 *nearPlane, F32 *farPlane, bool *isOrtho ); + + virtual void setFrustum( F32 FOV, F32 aspectRatio, F32 nearPlane, F32 farPlane ); + + /// This will construct and apply an orthographic projection matrix with the provided parameters + /// @param doRotate If set to true, the resulting matrix will be rotated PI/2 around the X axis + // for support in tsShapeInstance. You probably want to leave this as 'false'. + void setOrtho(F32 left, F32 right, F32 bottom, F32 top, F32 nearPlane, F32 farPlane, bool doRotate = false); + + /// @} + + /// Returns the scale for converting world space + /// units to screen space units... aka pixels. + /// + /// This is the true scale which is best used for GUI + /// drawing. For doing lod calculations you should be + /// using the functions in SceneState which is adjusted + /// for special cases like shadows and reflections. + /// + /// @see SceneState::getWorldToScreenScale() + /// @see SceneState::projectRadius() + /// + Point2F getWorldToScreenScale() const; + +public: + enum GenericShaderType + { + GSColor = 0, + GSTexture, + GSModColorTexture, + GSAddColorTexture, + GSTargetRestore, + GS_COUNT + }; + + /// This is a helper function to set a default shader for rendering GUI elements + /// on systems which do not support fixed-function operations as well as for + /// things which need just generic position/texture/color shaders + /// + /// @param type Type of generic shader, add your own if you need + virtual void setupGenericShaders( GenericShaderType type = GSColor ) {}; + + /// Get the fill convention for this device + virtual F32 getFillConventionOffset() const = 0; + + virtual U32 getMaxDynamicVerts() = 0; + virtual U32 getMaxDynamicIndices() = 0; + + virtual void doParanoidStateCheck(){}; + + /// Get access to this device's drawing utility class. + GFXDrawUtil *getDrawUtil(); + +#ifndef TORQUE_SHIPPING + /// This is a method designed for debugging. It will allow you to dump the states + /// in the render manager out to a file so that it can be diffed and examined. + void dumpStates( const char *fileName ) const; +#else + void dumpStates( const char *fileName ) const {}; +#endif + protected: + GFXDrawUtil *mDrawer; +}; + +//----------------------------------------------------------------------------- +// Matrix interface + +inline void GFXDevice::setWorldMatrix( const MatrixF &newWorld ) +{ + mWorldMatrixDirty = true; + mStateDirty = true; + mWorldMatrix[mWorldStackSize] = newWorld; +} + +inline void GFXDevice::pushWorldMatrix() +{ + mWorldMatrixDirty = true; + mStateDirty = true; + mWorldStackSize++; + AssertFatal( mWorldStackSize < WORLD_STACK_MAX, "GFX: Exceeded world matrix stack size" ); + mWorldMatrix[mWorldStackSize] = mWorldMatrix[mWorldStackSize - 1]; +} + +inline void GFXDevice::popWorldMatrix() +{ + mWorldMatrixDirty = true; + mStateDirty = true; + mWorldStackSize--; + AssertFatal( mWorldStackSize >= 0, "GFX: Negative WorldStackSize!" ); +} + +inline void GFXDevice::multWorld( const MatrixF &mat ) +{ + mWorldMatrixDirty = true; + mStateDirty = true; + mWorldMatrix[mWorldStackSize].mul(mat); +} + +inline void GFXDevice::setProjectionMatrix( const MatrixF &newProj ) +{ + mProjectionMatrixDirty = true; + mStateDirty = true; + mProjectionMatrix = newProj; +} + +inline void GFXDevice::setViewMatrix( const MatrixF &newView ) +{ + mStateDirty = true; + mViewMatrixDirty = true; + mViewMatrix = newView; +} + +inline void GFXDevice::setTextureMatrix( const U32 stage, const MatrixF &texMat ) +{ + AssertFatal( stage < TEXTURE_STAGE_COUNT, "Out of range texture sampler" ); + mStateDirty = true; + mTextureMatrixDirty[stage] = true; + mTextureMatrix[stage] = texMat; + mTextureMatrixCheckDirty = true; +} + +//----------------------------------------------------------------------------- +// Buffer management + +inline void GFXDevice::setVertexBuffer( GFXVertexBuffer *buffer ) +{ + if( buffer == mCurrentVertexBuffer ) + return; + + mCurrentVertexBuffer = buffer; + mVertexBufferDirty = true; + mStateDirty = true; +} + +#endif // _GFXDEVICE_H_ diff --git a/gfx/gfxDeviceStatistics.cpp b/gfx/gfxDeviceStatistics.cpp new file mode 100644 index 0000000..8ba4876 --- /dev/null +++ b/gfx/gfxDeviceStatistics.cpp @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gfxDeviceStatistics.h" +#include "platform/platform.h" +#include "console/console.h" + +GFXDeviceStatistics::GFXDeviceStatistics() +{ + clear(); +} + +void GFXDeviceStatistics::setPrefix(const String& prefix) +{ + // This is a bit silly, but we don't want to construct these + // strings every frame. + vnPolyCount = prefix + "polyCount"; + vnDrawCalls = prefix + "drawCalls"; + vnRenderTargetChanges = prefix + "renderTargetChanges"; +} + +/// Clear stats +void GFXDeviceStatistics::clear() +{ + mPolyCount = 0; + mDrawCalls = 0; + mRenderTargetChanges = 0; +} + +/// Copy from source (should just be a memcpy, but that may change later) used in +/// conjunction with end to get a subset of statistics. For example, statistics +/// for a particular render bin. +void GFXDeviceStatistics::start(GFXDeviceStatistics * source) +{ + mPolyCount = source->mPolyCount; + mDrawCalls = source->mDrawCalls; + mRenderTargetChanges = source->mRenderTargetChanges; +} + +/// Used with start to get a subset of stats on a device. Basically will do +/// this->mPolyCount = source->mPolyCount - this->mPolyCount. (Fancy!) +void GFXDeviceStatistics::end(GFXDeviceStatistics * source) +{ + mPolyCount = source->mPolyCount - mPolyCount; + mDrawCalls = source->mDrawCalls - mDrawCalls; + mRenderTargetChanges = source->mRenderTargetChanges - mRenderTargetChanges; +} + +/// Exports the stats to the console +void GFXDeviceStatistics::exportToConsole() +{ + Con::setIntVariable(vnPolyCount, mPolyCount); + Con::setIntVariable(vnDrawCalls, mDrawCalls); + Con::setIntVariable(vnRenderTargetChanges, mRenderTargetChanges); +} \ No newline at end of file diff --git a/gfx/gfxDeviceStatistics.h b/gfx/gfxDeviceStatistics.h new file mode 100644 index 0000000..0cbe632 --- /dev/null +++ b/gfx/gfxDeviceStatistics.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GFXDEVICESTATISTICS_H_ +#define _GFXDEVICESTATISTICS_H_ + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +// A class that hold a simple set of device stats. +class GFXDeviceStatistics +{ +public: + // Actual stats + S32 mPolyCount; + S32 mDrawCalls; + S32 mRenderTargetChanges; + + GFXDeviceStatistics(); + + void setPrefix(const String& prefix); + + /// Clear stats + void clear(); + + /// Copy from source (should just be a memcpy, but that may change later) used in + /// conjunction with end to get a subset of statistics. For example, statistics + /// for a particular render bin. + void start(GFXDeviceStatistics * source); + + /// Used with start to get a subset of stats on a device. Basically will do + /// this->mPolyCount = source->mPolyCount - this->mPolyCount. (Fancy!) + void end(GFXDeviceStatistics * source); + + /// Exports the stats to the console + void exportToConsole(); +private: + String vnPolyCount; + String vnDrawCalls; + String vnRenderTargetChanges; +}; + +#endif \ No newline at end of file diff --git a/gfx/gfxDrawUtil.cpp b/gfx/gfxDrawUtil.cpp new file mode 100644 index 0000000..044d771 --- /dev/null +++ b/gfx/gfxDrawUtil.cpp @@ -0,0 +1,1716 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxDrawUtil.h" + +#include "core/strings/stringFunctions.h" +#include "core/strings/unicode.h" +#include "math/util/frustum.h" +#include "math/util/sphereMesh.h" +#include "collision/polyhedron.h" +#include "math/mathUtils.h" +#include "gfx/gfxFontRenderBatcher.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" + + +GFXDrawUtil::GFXDrawUtil( GFXDevice * d) +{ + mDevice = d; + mBitmapModulation.set(0xFF, 0xFF, 0xFF, 0xFF); + mTextAnchorColor.set(0xFF, 0xFF, 0xFF, 0xFF); + mFontRenderBatcher = new FontRenderBatcher(); + + _setupStateBlocks(); +} + +GFXDrawUtil::~GFXDrawUtil() +{ + delete mFontRenderBatcher; +} + +void GFXDrawUtil::_setupStateBlocks() +{ + // DrawBitmapStretchSR + GFXStateBlockDesc bitmapStretchSR; + bitmapStretchSR.setCullMode(GFXCullNone); + bitmapStretchSR.setZReadWrite(false); + bitmapStretchSR.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + bitmapStretchSR.samplersDefined = true; + + // Linear: Create wrap SB + bitmapStretchSR.samplers[0] = GFXSamplerStateDesc::getWrapLinear(); + mBitmapStretchWrapLinearSB = mDevice->createStateBlock(bitmapStretchSR); + + // Linear: Create clamp SB + bitmapStretchSR.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + mBitmapStretchLinearSB = mDevice->createStateBlock(bitmapStretchSR); + + // Point: + bitmapStretchSR.samplers[0].minFilter = GFXTextureFilterPoint; + bitmapStretchSR.samplers[0].mipFilter = GFXTextureFilterPoint; + bitmapStretchSR.samplers[0].magFilter = GFXTextureFilterPoint; + + // Point: Create clamp SB, last created clamped so no work required here + mBitmapStretchSB = mDevice->createStateBlock(bitmapStretchSR); + + // Point: Create wrap SB, have to do this manually because getWrapLinear doesn't + bitmapStretchSR.samplers[0].addressModeU = GFXAddressWrap; + bitmapStretchSR.samplers[0].addressModeV = GFXAddressWrap; + bitmapStretchSR.samplers[0].addressModeW = GFXAddressWrap; + mBitmapStretchWrapSB = mDevice->createStateBlock(bitmapStretchSR); + + GFXStateBlockDesc rectFill; + rectFill.setCullMode(GFXCullNone); + rectFill.setZReadWrite(false); + rectFill.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + mRectFillSB = mDevice->createStateBlock(rectFill); +} + +//----------------------------------------------------------------------------- +// Color Modulation +//----------------------------------------------------------------------------- +void GFXDrawUtil::setBitmapModulation( const ColorI &modColor ) +{ + mBitmapModulation = modColor; +} + +void GFXDrawUtil::clearBitmapModulation() +{ + mBitmapModulation.set( 255, 255, 255, 255 ); +} + +void GFXDrawUtil::getBitmapModulation( ColorI *color ) +{ + mBitmapModulation.getColor( color ); +} + +void GFXDrawUtil::setTextAnchorColor( const ColorI &ancColor ) +{ + mTextAnchorColor = ancColor; +} + +//----------------------------------------------------------------------------- +// Draw Text +//----------------------------------------------------------------------------- +U32 GFXDrawUtil::drawText( GFont *font, const Point2I &ptDraw, const UTF16 *in_string, + const ColorI *colorTable, const U32 maxColorIndex, F32 rot ) +{ + return drawTextN( font, ptDraw, in_string, dStrlen(in_string), colorTable, maxColorIndex, rot ); +} + +U32 GFXDrawUtil::drawText( GFont *font, const Point2I &ptDraw, const UTF8 *in_string, + const ColorI *colorTable, const U32 maxColorIndex, F32 rot ) +{ + return drawTextN( font, ptDraw, in_string, dStrlen(in_string), colorTable, maxColorIndex, rot ); +} + +U32 GFXDrawUtil::drawText( GFont *font, const Point2F &ptDraw, const UTF8 *in_string, const ColorI *colorTable /*= NULL*/, const U32 maxColorIndex /*= 9*/, F32 rot /*= 0.f */ ) +{ + return drawText(font,Point2I((S32)ptDraw.x,(S32)ptDraw.y),in_string,colorTable,maxColorIndex,rot); +} + +U32 GFXDrawUtil::drawText( GFont *font, const Point2F &ptDraw, const UTF16 *in_string, const ColorI *colorTable /*= NULL*/, const U32 maxColorIndex /*= 9*/, F32 rot /*= 0.f */ ) +{ + return drawText(font,Point2I((S32)ptDraw.x,(S32)ptDraw.y),in_string,colorTable,maxColorIndex,rot); +} + +U32 GFXDrawUtil::drawTextN( GFont *font, const Point2I &ptDraw, const UTF8 *in_string, U32 n, + const ColorI *colorTable, const U32 maxColorIndex, F32 rot ) +{ + // return on zero length strings + if( n == 0 ) + return ptDraw.x; + + // Convert to UTF16 temporarily. + n++; // space for null terminator + FrameTemp ubuf( n * sizeof(UTF16) ); + convertUTF8toUTF16(in_string, ubuf, n); + + return drawTextN( font, ptDraw, ubuf, n, colorTable, maxColorIndex, rot ); +} + +U32 GFXDrawUtil::drawTextN( GFont *font, const Point2I &ptDraw, const UTF16 *in_string, + U32 n, const ColorI *colorTable, const U32 maxColorIndex, F32 rot ) +{ + // return on zero length strings + if( n == 0 ) + return ptDraw.x; + + // If it's over about 4000 verts we want to break it up + if( n > 666 ) + { + U32 left = drawTextN(font, ptDraw, in_string, 666, colorTable, maxColorIndex, rot); + + Point2I newDrawPt(left, ptDraw.y); + const UTF16* str = (const UTF16*)in_string; + + return drawTextN(font, newDrawPt, &(str[666]), n - 666, colorTable, maxColorIndex, rot); + } + + PROFILE_START(GFXDevice_drawTextN); + + const PlatformFont::CharInfo *tabci = NULL; + + S32 ptX = 0; + + // Queue everything for render. + mFontRenderBatcher->init(font, n); + + U32 i; + UTF16 c; + for(i = 0, c = in_string[i]; in_string[i] && i < n; i++, c = in_string[i]) + { + switch(c) + { + // We have to do a little dance here since \t = 0x9, \n = 0xa, and \r = 0xd + case 1: case 2: case 3: case 4: case 5: case 6: case 7: + case 11: case 12: + case 14: + { + // Color code + if (colorTable) + { + static U8 remap[15] = + { + 0x0, // 0 special null terminator + 0x0, // 1 ascii start-of-heading?? + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x0, // 8 special backspace + 0x0, // 9 special tab + 0x0, // a special \n + 0x7, + 0x8, + 0x0, // a special \r + 0x9 + }; + + U8 remapped = remap[c]; + + // Ignore if the color is greater than the specified max index: + if ( remapped <= maxColorIndex ) + { + const ColorI &clr = colorTable[remapped]; + mBitmapModulation = clr; + } + } + + // And skip rendering this character. + continue; + } + + // reset color? + case 15: + { + mBitmapModulation = mTextAnchorColor; + + // And skip rendering this character. + continue; + } + + // push color: + case 16: + { + mTextAnchorColor = mBitmapModulation; + + // And skip rendering this character. + continue; + } + + // pop color: + case 17: + { + mBitmapModulation = mTextAnchorColor; + + // And skip rendering this character. + continue; + } + + // Tab character + case dT('\t'): + { + if ( tabci == NULL ) + tabci = &(font->getCharInfo( dT(' ') )); + + const U32 fontTabIncrement = tabci->xIncrement * GFont::TabWidthInSpaces; + + ptX += fontTabIncrement; + + // And skip rendering this character. + continue; + } + + // Don't draw invalid characters. + default: + { + if( !font->isValidChar( c ) ) + continue; + } + } + + // Queue char for rendering.. + mFontRenderBatcher->queueChar(c, ptX, mBitmapModulation); + } + + + mFontRenderBatcher->render(rot, Point2F((F32)ptDraw.x, (F32)ptDraw.y)); + + PROFILE_END(); + + return ptX - ptDraw.x; +} + +U32 GFXDrawUtil::drawTextN( GFont *font, const Point2F &ptDraw, const UTF8 *in_string, U32 n, const ColorI *colorTable /*= NULL*/, const U32 maxColorIndex /*= 9*/, F32 rot /*= 0.f */ ) +{ + return drawTextN(font,Point2I((S32)ptDraw.x,(S32)ptDraw.y),in_string,n,colorTable,maxColorIndex,rot); +} + +U32 GFXDrawUtil::drawTextN( GFont *font, const Point2F &ptDraw, const UTF16 *in_string, U32 n, const ColorI *colorTable /*= NULL*/, const U32 maxColorIndex /*= 9*/, F32 rot /*= 0.f */ ) +{ + return drawTextN(font,Point2I((S32)ptDraw.x,(S32)ptDraw.y),in_string,n,colorTable,maxColorIndex,rot); +} + +//----------------------------------------------------------------------------- +// Draw Bitmaps +//----------------------------------------------------------------------------- +void GFXDrawUtil::drawBitmap( GFXTextureObject* texture, const Point2I &in_rAt, const GFXBitmapFlip in_flip, const GFXTextureFilterType filter , bool in_wrap /*= true*/ ) +{ + drawBitmap(texture,Point2F((F32)in_rAt.x,(F32)in_rAt.y),in_flip,filter,in_wrap); +} + +void GFXDrawUtil::drawBitmapStretch( GFXTextureObject* texture, const RectI &dstRect, const GFXBitmapFlip in_flip, const GFXTextureFilterType filter , bool in_wrap /*= true*/ ) +{ + drawBitmapStretch(texture,RectF((F32)dstRect.point.x,(F32)dstRect.point.y,(F32)dstRect.extent.x,(F32)dstRect.extent.y),in_flip,filter,in_wrap); +} + +void GFXDrawUtil::drawBitmapSR( GFXTextureObject* texture, const Point2I &in_rAt, const RectI &srcRect, const GFXBitmapFlip in_flip, const GFXTextureFilterType filter , bool in_wrap /*= true*/ ) +{ + drawBitmapSR(texture,Point2F((F32)in_rAt.x,(F32)in_rAt.y),RectF((F32)srcRect.point.x,(F32)srcRect.point.y,(F32)srcRect.extent.x,(F32)srcRect.extent.y),in_flip,filter,in_wrap); +} + +void GFXDrawUtil::drawBitmapStretchSR( GFXTextureObject *texture, const RectI &dstRect, const RectI &srcRect, const GFXBitmapFlip in_flip, const GFXTextureFilterType filter , bool in_wrap /*= true*/ ) +{ + RectF dstRectF = RectF((F32)dstRect.point.x,(F32)dstRect.point.y,(F32)dstRect.extent.x,(F32)dstRect.extent.y); + RectF srcRectF = RectF((F32)srcRect.point.x,(F32)srcRect.point.y,(F32)srcRect.extent.x,(F32)srcRect.extent.y); + drawBitmapStretchSR(texture,dstRectF,srcRectF,in_flip,filter,in_wrap); +} + +void GFXDrawUtil::drawBitmap( GFXTextureObject*texture, const Point2F &in_rAt, const GFXBitmapFlip in_flip /*= GFXBitmapFlip_None*/, const GFXTextureFilterType filter /*= GFXTextureFilterPoint */ , bool in_wrap /*= true*/ ) +{ + AssertFatal( texture != 0, "No texture specified for drawBitmap()" ); + + RectI subRegion( 0, 0, texture->mBitmapSize.x, texture->mBitmapSize.y ); + RectI stretch( in_rAt.x, in_rAt.y, texture->mBitmapSize.x, texture->mBitmapSize.y ); + drawBitmapStretchSR( texture, stretch, subRegion, in_flip, filter, in_wrap ); +} + +void GFXDrawUtil::drawBitmapStretch( GFXTextureObject*texture, const RectF &dstRect, const GFXBitmapFlip in_flip /*= GFXBitmapFlip_None*/, const GFXTextureFilterType filter /*= GFXTextureFilterPoint */ , bool in_wrap /*= true*/ ) +{ + AssertFatal( texture != 0, "No texture specified for drawBitmapStretch()" ); + + RectF subRegion( 0.f, 0.f, (F32)texture->mBitmapSize.x, (F32)texture->mBitmapSize.y ); + drawBitmapStretchSR( texture, dstRect, subRegion, in_flip, filter, in_wrap ); +} + +void GFXDrawUtil::drawBitmapSR( GFXTextureObject*texture, const Point2F &in_rAt, const RectF &srcRect, const GFXBitmapFlip in_flip /*= GFXBitmapFlip_None*/, const GFXTextureFilterType filter /*= GFXTextureFilterPoint */ , bool in_wrap /*= true*/ ) +{ + AssertFatal( texture != 0, "No texture specified for drawBitmapSR()" ); + + RectF stretch( in_rAt.x, in_rAt.y, srcRect.len_x(), srcRect.len_y() ); + drawBitmapStretchSR( texture, stretch, srcRect, in_flip, filter, in_wrap ); +} + +void GFXDrawUtil::drawBitmapStretchSR( GFXTextureObject* texture, const RectF &dstRect, const RectF &srcRect, const GFXBitmapFlip in_flip /*= GFXBitmapFlip_None*/, const GFXTextureFilterType filter /*= GFXTextureFilterPoint */ , bool in_wrap /*= true*/ ) +{ + // Sanity if no texture is specified. + if(!texture) + return; + + GFXVertexBufferHandle verts(mDevice, 4, GFXBufferTypeVolatile ); + verts.lock(); + + F32 texLeft = (srcRect.point.x) / (texture->mTextureSize.x); + F32 texRight = (srcRect.point.x + srcRect.extent.x) / (texture->mTextureSize.x); + F32 texTop = (srcRect.point.y) / (texture->mTextureSize.y); + F32 texBottom = (srcRect.point.y + srcRect.extent.y) / (texture->mTextureSize.y); + + F32 screenLeft = dstRect.point.x; + F32 screenRight = (dstRect.point.x + dstRect.extent.x); + F32 screenTop = dstRect.point.y; + F32 screenBottom = (dstRect.point.y + dstRect.extent.y); + + if( in_flip & GFXBitmapFlip_X ) + { + F32 temp = texLeft; + texLeft = texRight; + texRight = temp; + } + if( in_flip & GFXBitmapFlip_Y ) + { + F32 temp = texTop; + texTop = texBottom; + texBottom = temp; + } + + const F32 fillConv = mDevice->getFillConventionOffset(); + verts[0].point.set( screenLeft - fillConv, screenTop - fillConv, 0.f ); + verts[1].point.set( screenRight - fillConv, screenTop - fillConv, 0.f ); + verts[2].point.set( screenLeft - fillConv, screenBottom - fillConv, 0.f ); + verts[3].point.set( screenRight - fillConv, screenBottom - fillConv, 0.f ); + + verts[0].color = verts[1].color = verts[2].color = verts[3].color = mBitmapModulation; + + verts[0].texCoord.set( texLeft, texTop ); + verts[1].texCoord.set( texRight, texTop ); + verts[2].texCoord.set( texLeft, texBottom ); + verts[3].texCoord.set( texRight, texBottom ); + + verts.unlock(); + + mDevice->setVertexBuffer( verts ); + + switch (filter) + { + case GFXTextureFilterPoint : + mDevice->setStateBlock(in_wrap ? mBitmapStretchWrapSB : mBitmapStretchSB); + break; + case GFXTextureFilterLinear : + mDevice->setStateBlock(in_wrap ? mBitmapStretchWrapLinearSB : mBitmapStretchLinearSB); + break; + default: + AssertFatal(false, "No GFXDrawUtil state block defined for this filter type!"); + mDevice->setStateBlock(mBitmapStretchSB); + break; + } + mDevice->setTexture( 0, texture ); + mDevice->setupGenericShaders( GFXDevice::GSModColorTexture ); + + mDevice->drawPrimitive( GFXTriangleStrip, 0, 2 ); +} +void GFXDrawUtil::drawBitmapStretchSRCircle( GFXTextureObject*texture, const RectI &dstRectConst, const RectI &srcRect,const GFXBitmapFlip in_flip /* = GFXBitmapFlip_None */ , const GFXTextureFilterType filter/* = GFXTextureFilterPoint */, bool in_wrap/* = true */) +{ + //Ä¿±ê¾ØÐÎÈç¹û²»ÊÇÕý·½ÐΣ¬½«È¡½ÏСµÄ±ß + RectI dstRect = dstRectConst; + if (dstRect.extent.x != dstRect.extent.y) + { + int i = dstRect.extent.x > dstRect.extent.y ? dstRect.extent.y : dstRect.extent.x; + dstRect.extent.x = i; + dstRect.extent.y = i; + } + //»­Èý½ÇÉÈ + //mDevice->setBaseRenderState(); + + int nDivided = 24; + F32 angelStep = M_2PI_F / nDivided; + GFXVertexBufferHandle verts(mDevice, nDivided + 2, GFXBufferTypeVolatile ); + verts.lock(); + + F32 texLeft = F32(srcRect.point.x) / F32(texture->mTextureSize.x); + F32 texRight = F32(srcRect.point.x + srcRect.extent.x) / F32(texture->mTextureSize.x); + F32 texTop = F32(srcRect.point.y) / F32(texture->mTextureSize.y); + F32 texBottom = F32(srcRect.point.y + srcRect.extent.y) / F32(texture->mTextureSize.y); + F32 texHalfWidth = (texRight - texLeft) / 2; + F32 texHalfHeight = (texBottom - texTop) / 2; + + //ÉèÖö¥µãºÍÎÆÀí + F32 radius = (F32)(dstRect.extent.x) / 2; + F32 angle,cos,sin; + Point3F pt(0,0,0); + Point2F pt2(0,0); + verts[0].point.set(dstRect.point.x + radius,dstRect.point.y + radius,0.f); + verts[0].color = mBitmapModulation; + verts[0].texCoord.set(texLeft / 2 + texRight / 2 , texTop / 2 + texBottom / 2); + for (int i = 0 ; i < nDivided ; i++) + { + angle = -angelStep * i; + cos = mCos(angle); + sin = mSin(angle); + pt.x = verts[0].point.x + radius * cos; + pt.y = verts[0].point.y - radius * sin; + pt2.x = verts[0].texCoord.x + texHalfWidth * cos; + pt2.y = verts[0].texCoord.y - texHalfHeight * sin; + verts[i + 1].point.set(pt); + verts[i + 1].color = mBitmapModulation; + verts[i + 1].texCoord.set(pt2.x,pt2.y); + } + verts[nDivided + 1] = verts[1]; + + verts.unlock(); + + mDevice->setVertexBuffer( verts ); + switch (filter) + { + case GFXTextureFilterPoint : + mDevice->setStateBlock(in_wrap ? mBitmapStretchWrapSB : mBitmapStretchSB); + break; + case GFXTextureFilterLinear : + mDevice->setStateBlock(in_wrap ? mBitmapStretchWrapLinearSB : mBitmapStretchLinearSB); + break; + default: + AssertFatal(false, "No GFXDrawUtil state block defined for this filter type!"); + mDevice->setStateBlock(mBitmapStretchSB); + break; + } + mDevice->setTexture( 0, texture ); + mDevice->setupGenericShaders( GFXDevice::GSModColorTexture ); + + mDevice->drawPrimitive( GFXTriangleFan, 0, nDivided); +} +void GFXDrawUtil::drawCDRectFill(const Point2I ¢er , const Point2I &size , const F32 angle, const ColorI &color) +{ + if (angle < 0.01 || angle > M_2PI_F - 0.01) + { + drawRectFill(Point2I(center.x - size.x/2,center.y - size.y/2),Point2I(center.x + size.x/2,center.y + size.y/2),color); + return; + } + +// mDevice->setBaseRenderState(); + + int nVerts = 0; + //45¶ÈÒÔÏÂ7¸öµã + if (angle < M_PI_F/4) + { + nVerts = 7; + } + //45 to 135 need 6 point + else if (angle >= M_PI_F/4 && angle < 3*M_PI_F/4) + { + nVerts = 6; + } + //135 to 225 need 5 point + else if (angle >= 3*M_PI_F/4 && angle < 5*M_PI_F/4) + { + nVerts = 5; + } + //225 to 315 need 4 point + else if (angle >= 5*M_PI_F/4 && angle < 7*M_PI_F/4) + { + nVerts = 4; + } + // + else if (angle >= 7*M_PI_F/4) + { + nVerts = 3; + } + + if (nVerts == 0) + return; + + GFXVertexBufferHandle verts(mDevice, nVerts, GFXBufferTypeVolatile); + verts.lock(); + + Point2I ptConers[4]; + Point2I ptMiddles[4]; + Point2I ptHalfSize = size; + ptHalfSize.x = ptHalfSize.x/2; + ptHalfSize.y = ptHalfSize.y/2; + + ptConers[0].x = center.x + ptHalfSize.x; + ptConers[0].y = center.y - ptHalfSize.y; + + ptConers[1].x = center.x + ptHalfSize.x; + ptConers[1].y = center.y + ptHalfSize.y; + + ptConers[2].x = center.x - ptHalfSize.x; + ptConers[2].y = center.y + ptHalfSize.y; + + ptConers[3].x = center.x - ptHalfSize.x; + ptConers[3].y = center.y - ptHalfSize.y; + + ptMiddles[0].x = center.x; + ptMiddles[0].y = center.y - ptHalfSize.y; + + ptMiddles[1].x = center.x + ptHalfSize.x; + ptMiddles[1].y = center.y; + + ptMiddles[2].x = center.x; + ptMiddles[2].y = center.y + ptHalfSize.y; + + ptMiddles[3].x = center.x - ptHalfSize.x; + ptMiddles[3].y = center.y; + + verts[0].point.set(center.x,center.y,0.0f); + switch (nVerts) + { + case 7: + verts[1].point.set(center.x + ptHalfSize.y * mTan(angle),ptMiddles[0].y,0.0f); + verts[2].point.set(ptConers[0].x,ptConers[0].y,0.0f); + verts[3].point.set(ptConers[1].x,ptConers[1].y,0.0f); + verts[4].point.set(ptConers[2].x,ptConers[2].y,0.0f); + verts[5].point.set(ptConers[3].x,ptConers[3].y,0.0f); + verts[6].point.set(ptMiddles[0].x,ptMiddles[0].y,0.0f); + break; + case 6: + verts[1].point.set(ptMiddles[1].x,(angle < M_PI_F/2) ? center.y - ptHalfSize.x * (M_PI_F/2 - angle): center.y + ptHalfSize.x * mTan(angle - M_PI_F/2),0.0f); + verts[2].point.set(ptConers[1].x,ptConers[1].y,0.0f); + verts[3].point.set(ptConers[2].x,ptConers[2].y,0.0f); + verts[4].point.set(ptConers[3].x,ptConers[3].y,0.0f); + verts[5].point.set(ptMiddles[0].x,ptMiddles[0].y,0.0f); + break; + case 5: + verts[1].point.set( (angle - 3*M_PI_F/4) < M_PI_F/4 ? center.x + ptHalfSize.y * mTan(M_PI_F/4 - (angle - 3*M_PI_F/4)) : center.x - ptHalfSize.y * mTan((angle - 3*M_PI_F/4) - M_PI_F/4), + ptMiddles[2].y,0.0f); + verts[2].point.set(ptConers[2].x,ptConers[2].y,0.0f); + verts[3].point.set(ptConers[3].x,ptConers[3].y,0.0f); + verts[4].point.set(ptMiddles[0].x,ptMiddles[0].y,0.0f); + break; + case 4: + verts[1].point.set(ptMiddles[3].x, + (angle < 3*M_PI_F/2) ? center.y + ptHalfSize.x * mTan(M_PI_F/4 - (angle - 5*M_PI_F/4)) : center.y - ptHalfSize.x * ((angle - 5*M_PI_F/4) - M_PI_F/4) , + 0.0f); + verts[2].point.set(ptConers[3].x,ptConers[3].y,0.0f); + verts[3].point.set(ptMiddles[0].x,ptMiddles[0].y,0.0f); + break; + case 3: + verts[1].point.set(center.x - ptHalfSize.y * mTan(M_PI_F/4 - (angle - 7*M_PI_F/4)),ptMiddles[0].y,0.0f); + verts[2].point.set(ptMiddles[0].x,ptMiddles[0].y,0.0f); + break; + } + + + for (int i=0; isetStateBlock(mRectFillSB); + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleFan, 0, nVerts - 2 ); +} +void GFXDrawUtil::drawBitmapStretchRotate( GFXTextureObject*texture, const RectI &dstRect,F32 spinAngle, const GFXBitmapFlip in_flip /* = GFXBitmapFlip_None */ , const GFXTextureFilterType filter/* = GFXTextureFilterPoint */, bool in_wrap/* = true */) +{ + AssertFatal( texture != 0, "No texture specified for drawBitmapStretch()" ); + + RectI subRegion( 0, 0, texture->mBitmapSize.x, texture->mBitmapSize.y ); + drawBitmapStretchSRRotate(texture,dstRect,subRegion,spinAngle,in_flip,filter,in_wrap); +} +void GFXDrawUtil::drawBitmapStretchSRRotate( GFXTextureObject*texture, const RectI &dstRect, const RectI &srcRect,F32 spinAngle, const GFXBitmapFlip in_flip /* = GFXBitmapFlip_None */ , const GFXTextureFilterType filter/* = GFXTextureFilterPoint */, bool in_wrap/* = true */) +{ + // Sanity if no texture is specified. + if(!texture) + return; + +// mDevice->setBaseRenderState(); + + GFXVertexBufferHandle verts(mDevice, 4, GFXBufferTypeVolatile ); + verts.lock(); + + F32 texLeft = F32(srcRect.point.x) / F32(texture->mTextureSize.x); + F32 texRight = F32(srcRect.point.x + srcRect.extent.x) / F32(texture->mTextureSize.x); + F32 texTop = F32(srcRect.point.y) / F32(texture->mTextureSize.y); + F32 texBottom = F32(srcRect.point.y + srcRect.extent.y) / F32(texture->mTextureSize.y); + + + const F32 fillConv = mDevice->getFillConventionOffset(); + Point3F offsetCenter(0,0,0); + offsetCenter.x = dstRect.point.x + dstRect.extent.x/2 - fillConv; + offsetCenter.y = dstRect.point.y + dstRect.extent.y/2 - fillConv; + + + if( in_flip & GFXBitmapFlip_X ) + { + F32 temp = texLeft; + texLeft = texRight; + texRight = temp; + } + if( in_flip & GFXBitmapFlip_Y ) + { + F32 temp = texTop; + texTop = texBottom; + texBottom = temp; + } + + + verts[0].point.set(-dstRect.extent.x/2,-dstRect.extent.y/2,0.f); + verts[1].point.set(-dstRect.extent.x/2,dstRect.extent.y/2,0.f); + verts[2].point.set(dstRect.extent.x/2,-dstRect.extent.y/2,0.f); + verts[3].point.set(dstRect.extent.x/2,dstRect.extent.y/2,0.f); + // if(spinAngle != 0.f) + // { + MatrixF rotMatrix( EulerF( 0.0, 0.0, -spinAngle - M_PI/2 )); + + for( S32 i = 0; i < 4; i++ ) + { + rotMatrix.mulP( verts[i].point ); + verts[i].point += offsetCenter; + } + /* }*/ + + + + verts[0].color = verts[1].color = verts[2].color = verts[3].color = mBitmapModulation; + + verts[0].texCoord.set( texLeft, texTop ); + verts[1].texCoord.set( texRight, texTop ); + verts[2].texCoord.set( texLeft, texBottom ); + verts[3].texCoord.set( texRight, texBottom ); + + + verts.unlock(); + + mDevice->setVertexBuffer( verts ); + switch (filter) + { + case GFXTextureFilterPoint : + mDevice->setStateBlock(in_wrap ? mBitmapStretchWrapSB : mBitmapStretchSB); + break; + case GFXTextureFilterLinear : + mDevice->setStateBlock(in_wrap ? mBitmapStretchWrapLinearSB : mBitmapStretchLinearSB); + break; + default: + AssertFatal(false, "No GFXDrawUtil state block defined for this filter type!"); + mDevice->setStateBlock(mBitmapStretchSB); + break; + } + mDevice->setTexture( 0, texture ); + mDevice->setupGenericShaders( GFXDevice::GSModColorTexture ); + + mDevice->drawPrimitive( GFXTriangleStrip, 0, 2 ); +} + + +//----------------------------------------------------------------------------- +// Draw Rectangle +//----------------------------------------------------------------------------- +void GFXDrawUtil::drawRect( const Point2I &upperLeft, const Point2I &lowerRight, const ColorI &color ) +{ + drawRect( Point2F((F32)upperLeft.x,(F32)upperLeft.y),Point2F((F32)lowerRight.x,(F32)lowerRight.y),color); +} + +void GFXDrawUtil::drawRect( const RectI &rect, const ColorI &color ) +{ + drawRect( rect.point, Point2I(rect.point.x + rect.extent.x - 1, rect.point.y + rect.extent.y - 1), color ); +} + +void GFXDrawUtil::drawRect( const RectF &rect, const ColorI &color ) +{ + drawRect( rect.point, Point2F(rect.point.x + rect.extent.x - 1, rect.point.y + rect.extent.y - 1), color ); +} + +void GFXDrawUtil::drawRect( const Point2F &upperLeft, const Point2F &lowerRight, const ColorI &color ) +{ + // + // Convert Box a----------x + // | | + // x----------b + // + // Into Triangle-Strip Outline + // v0-----------v2 + // | a x | + // | v1-----v3 | + // | | | | + // | v7-----v5 | + // | x b | + // v6-----------v4 + // + + // NorthWest and NorthEast facing offset vectors + // These adjust the thickness of the line, it'd be neat if one day + // they were passed in as arguments. + Point2F nw(-0.5f,-0.5f); /* \ */ + Point2F ne(0.5f,-0.5f); /* / */ + + GFXVertexBufferHandle verts (mDevice, 10, GFXBufferTypeVolatile ); + verts.lock(); + + F32 ulOffset = 0.5f - mDevice->getFillConventionOffset(); + + verts[0].point.set( upperLeft.x + ulOffset + nw.x, upperLeft.y + ulOffset + nw.y, 0.0f ); + verts[1].point.set( upperLeft.x + ulOffset - nw.x, upperLeft.y + ulOffset - nw.y, 0.0f ); + verts[2].point.set( lowerRight.x + ne.x, upperLeft.y + ulOffset + ne.y, 0.0f ); + verts[3].point.set( lowerRight.x - ne.x, upperLeft.y + ulOffset - ne.y, 0.0f ); + verts[4].point.set( lowerRight.x - nw.x, lowerRight.y - nw.y, 0.0f ); + verts[5].point.set( lowerRight.x + nw.x, lowerRight.y + nw.y, 0.0f ); + verts[6].point.set( upperLeft.x + ulOffset - ne.x, lowerRight.y - ne.y, 0.0f ); + verts[7].point.set( upperLeft.x + ulOffset + ne.x, lowerRight.y + ne.y, 0.0f ); + verts[8].point.set( upperLeft.x + ulOffset + nw.x, upperLeft.y + ulOffset + nw.y, 0.0f ); // same as 0 + verts[9].point.set( upperLeft.x + ulOffset - nw.x, upperLeft.y + ulOffset - nw.y, 0.0f ); // same as 1 + + for (int i=0; i<10; i++) + verts[i].color = color; + + verts.unlock(); + mDevice->setVertexBuffer( verts ); + + mDevice->setStateBlock(mRectFillSB); + mDevice->setupGenericShaders(); + mDevice->drawPrimitive( GFXTriangleStrip, 0, 8 ); +} + +//----------------------------------------------------------------------------- +// Draw Rectangle Fill +//----------------------------------------------------------------------------- +void GFXDrawUtil::drawRectFill( const RectF &rect, const ColorI &color ) +{ + drawRectFill(rect.point, Point2F(rect.extent.x + rect.point.x - 1, rect.extent.y + rect.point.y - 1), color ); +} + +void GFXDrawUtil::drawRectFill( const Point2I &upperLeft, const Point2I &lowerRight, const ColorI &color ) +{ + drawRectFill(Point2F((F32)upperLeft.x, (F32)upperLeft.y), Point2F((F32)lowerRight.x, (F32)lowerRight.y), color); +} + +void GFXDrawUtil::drawRectFill( const RectI &rect, const ColorI &color ) +{ + drawRectFill(rect.point, Point2I(rect.extent.x + rect.point.x - 1, rect.extent.y + rect.point.y - 1), color ); +} + +void GFXDrawUtil::drawRectFill( const Point2F &upperLeft, const Point2F &lowerRight, const ColorI &color ) +{ + // + // Convert Box a----------x + // | | + // x----------b + // Into Quad + // v0---------v1 + // | a x | + // | | + // | x b | + // v2---------v3 + // + + // NorthWest and NorthEast facing offset vectors + Point2F nw(-0.5,-0.5); /* \ */ + Point2F ne(0.5,-0.5); /* / */ + + GFXVertexBufferHandle verts(mDevice, 4, GFXBufferTypeVolatile); + verts.lock(); + + F32 ulOffset = 0.5f - mDevice->getFillConventionOffset(); + + verts[0].point.set( upperLeft.x+nw.x+ulOffset, upperLeft.y+nw.y+ulOffset, 0.0f ); + verts[1].point.set( lowerRight.x+ne.x, upperLeft.y+ne.y+ulOffset, 0.0f ); + verts[2].point.set( upperLeft.x-ne.x+ulOffset, lowerRight.y-ne.y, 0.0f ); + verts[3].point.set( lowerRight.x-nw.x, lowerRight.y-nw.y, 0.0f ); + + for (int i=0; i<4; i++) + verts[i].color = color; + + verts.unlock(); + + mDevice->setStateBlock(mRectFillSB); + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + mDevice->drawPrimitive( GFXTriangleStrip, 0, 2 ); +} + +void GFXDrawUtil::draw2DSquare( const Point2F &screenPoint, F32 width, F32 spinAngle ) +{ + width *= 0.5; + + Point3F offset( screenPoint.x, screenPoint.y, 0.0 ); + + GFXVertexBufferHandle verts( mDevice, 4, GFXBufferTypeVolatile ); + verts.lock(); + + verts[0].point.set( -width, -width, 0.0f ); + verts[1].point.set( -width, width, 0.0f ); + verts[2].point.set( width, -width, 0.0f ); + verts[3].point.set( width, width, 0.0f ); + + verts[0].color = verts[1].color = verts[2].color = verts[3].color = mBitmapModulation; + + if(spinAngle != 0.f) + { + MatrixF rotMatrix( EulerF( 0.0, 0.0, spinAngle ) ); + + for( S32 i = 0; i < 4; i++ ) + { + rotMatrix.mulP( verts[i].point ); + verts[i].point += offset; + } + } + + verts.unlock(); + mDevice->setVertexBuffer( verts ); + + mDevice->setStateBlock(mRectFillSB); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleStrip, 0, 2 ); +} + +//----------------------------------------------------------------------------- +// Draw Line +//----------------------------------------------------------------------------- +void GFXDrawUtil::drawLine( const Point3F &startPt, const Point3F &endPt, const ColorI &color ) +{ + drawLine( startPt.x, startPt.y, startPt.z, endPt.x, endPt.y, endPt.z, color ); +} + +void GFXDrawUtil::drawLine( const Point2F &startPt, const Point2F &endPt, const ColorI &color ) +{ + drawLine( startPt.x, startPt.y, 0.0f, endPt.x, endPt.y, 0.0f, color ); +} + +void GFXDrawUtil::drawLine( const Point2I &startPt, const Point2I &endPt, const ColorI &color ) +{ + drawLine( startPt.x, startPt.y, 0.0f, endPt.x, endPt.y, 0.0f, color ); +} + +void GFXDrawUtil::drawLine( F32 x1, F32 y1, F32 x2, F32 y2, const ColorI &color ) +{ + drawLine( x1, y1, 0.0f, x2, y2, 0.0f, color ); +} + +void GFXDrawUtil::drawLine( F32 x1, F32 y1, F32 z1, F32 x2, F32 y2, F32 z2, const ColorI &color ) +{ + // + // Convert Line a----------b + // + // Into Quad v0---------v1 + // a b + // v2---------v3 + // + + Point2F start(x1, y1); + Point2F end(x2, y2); + Point2F perp, lineVec; + + // handle degenerate case where point a = b + if(x1 == x2 && y1 == y2) + { + perp.set(0.0f, 0.5f); + lineVec.set(0.1f, 0.0f); + } + else + { + perp.set(start.y - end.y, end.x - start.x); + lineVec.set(end.x - start.x, end.y - start.y); + perp.normalize(0.5f); + lineVec.normalize(0.1f); + } + start -= lineVec; + end += lineVec; + + GFXVertexBufferHandle verts(mDevice, 4, GFXBufferTypeVolatile); + verts.lock(); + + verts[0].point.set( start.x+perp.x, start.y+perp.y, z1 ); + verts[1].point.set( end.x+perp.x, end.y+perp.y, z2 ); + verts[2].point.set( start.x-perp.x, start.y-perp.y, z1 ); + verts[3].point.set( end.x-perp.x, end.y-perp.y, z2 ); + + verts[0].color = color; + verts[1].color = color; + verts[2].color = color; + verts[3].color = color; + + verts.unlock(); + mDevice->setVertexBuffer( verts ); + mDevice->setStateBlock(mRectFillSB); + mDevice->drawPrimitive( GFXTriangleStrip, 0, 2 ); +} + +//----------------------------------------------------------------------------- +// 3D World Draw Misc +//----------------------------------------------------------------------------- + +static SphereMesh gSphere; + +void GFXDrawUtil::drawSphere( const GFXStateBlockDesc &desc, F32 radius, const Point3F &pos, const ColorI &color, bool drawTop, bool drawBottom, const MatrixF *xfm ) +{ + MatrixF mat; + if ( xfm ) + mat = *xfm; + else + mat = MatrixF::Identity; + + mat.scale(Point3F(radius,radius,radius)); + mat.setPosition(pos); + GFX->pushWorldMatrix(); + GFX->multWorld(mat); + + const SphereMesh::TriangleMesh * sphereMesh = gSphere.getMesh(2); + S32 numPoly = sphereMesh->numPoly; + S32 totalPoly = 0; + GFXVertexBufferHandle verts(mDevice, numPoly*3, GFXBufferTypeVolatile); + verts.lock(); + S32 vertexIndex = 0; + for (S32 i=0; ipoly[i].pnt[0].z < -0.01f || sphereMesh->poly[i].pnt[1].z < -0.01f || sphereMesh->poly[i].pnt[2].z < -0.01f) + continue; + } + if (!drawTop) + { + if (sphereMesh->poly[i].pnt[0].z > 0.01f || sphereMesh->poly[i].pnt[1].z > 0.01f || sphereMesh->poly[i].pnt[2].z > 0.01f) + continue; + } + totalPoly++; + + verts[vertexIndex].point = sphereMesh->poly[i].pnt[0]; + verts[vertexIndex].color = color; + vertexIndex++; + + verts[vertexIndex].point = sphereMesh->poly[i].pnt[1]; + verts[vertexIndex].color = color; + vertexIndex++; + + verts[vertexIndex].point = sphereMesh->poly[i].pnt[2]; + verts[vertexIndex].color = color; + vertexIndex++; + } + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleList, 0, totalPoly ); + + GFX->popWorldMatrix(); +} + +//----------------------------------------------------------------------------- + +static const Point3F cubePoints[8] = +{ + Point3F(-1, -1, -1), Point3F(-1, -1, 1), Point3F(-1, 1, -1), Point3F(-1, 1, 1), + Point3F( 1, -1, -1), Point3F( 1, -1, 1), Point3F( 1, 1, -1), Point3F( 1, 1, 1) +}; + +static const U32 cubeFaces[6][4] = +{ + { 0, 4, 6, 2 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 }, + { 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 } +}; + +void GFXDrawUtil::drawTriangle( const GFXStateBlockDesc &desc, const Point3F &p0, const Point3F &p1, const Point3F &p2, const ColorI &color, const MatrixF *xfm ) +{ + if ( desc.fillMode == GFXFillWireframe ) + _drawWireTriangle( desc, p0, p1, p2, color, xfm ); + else + _drawSolidTriangle( desc, p0, p1, p2, color, xfm ); +} + +void GFXDrawUtil::_drawWireTriangle( const GFXStateBlockDesc &desc, const Point3F &p0, const Point3F &p1, const Point3F &p2, const ColorI &color, const MatrixF *xfm ) +{ + GFXVertexBufferHandle verts(mDevice, 4, GFXBufferTypeVolatile); + verts.lock(); + + // Set up the line strip + verts[0].point = p0; + verts[0].color = color; + verts[1].point = p1; + verts[1].color = color; + verts[2].point = p2; + verts[2].color = color; + verts[3].point = p0; + verts[3].color = color; + + // Apply xfm if we were passed one. + if ( xfm != NULL ) + { + for ( U32 i = 0; i < 4; i++ ) + xfm->mulP( verts[i].point ); + } + + verts.unlock(); + + GFXStateBlockRef sb = mDevice->createStateBlock( desc ); + mDevice->setStateBlock( sb ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXLineStrip, 0, 3 ); +} + +void GFXDrawUtil::_drawSolidTriangle( const GFXStateBlockDesc &desc, const Point3F &p0, const Point3F &p1, const Point3F &p2, const ColorI &color, const MatrixF *xfm ) +{ + GFXVertexBufferHandle verts(mDevice, 3, GFXBufferTypeVolatile); + verts.lock(); + + // Set up the line strip + verts[0].point = p0; + verts[0].color = color; + verts[1].point = p1; + verts[1].color = color; + verts[2].point = p2; + verts[2].color = color; + + // Apply xfm if we were passed one. + if ( xfm != NULL ) + { + for ( U32 i = 0; i < 3; i++ ) + xfm->mulP( verts[i].point ); + } + + verts.unlock(); + + GFXStateBlockRef sb = mDevice->createStateBlock( desc ); + mDevice->setStateBlock( sb ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleList, 0, 1 ); +} + +void GFXDrawUtil::drawCube( const GFXStateBlockDesc &desc, const Box3F &box, const ColorI &color, const MatrixF *xfm ) +{ + drawCube( desc, box.getExtents(), box.getCenter(), color, xfm ); +} + +void GFXDrawUtil::drawCube( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const ColorI &color, const MatrixF *xfm ) +{ + if ( desc.fillMode == GFXFillWireframe ) + _drawWireCube( desc, size, pos, color, xfm ); + else + _drawSolidCube( desc, size, pos, color, xfm ); +} + +void GFXDrawUtil::_drawWireCube( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const ColorI &color, const MatrixF *xfm ) +{ + GFXVertexBufferHandle verts(mDevice, 30, GFXBufferTypeVolatile); + verts.lock(); + + Point3F halfSize = size * 0.5f; + + // setup 6 line loops + U32 vertexIndex = 0; + for(int i = 0; i < 6; i++) + { + for(int j = 0; j < 5; j++) + { + int idx = cubeFaces[i][j%4]; + + verts[vertexIndex].point = cubePoints[idx] * halfSize; + verts[vertexIndex].color = color; + vertexIndex++; + } + } + + // Apply xfm if we were passed one. + if ( xfm != NULL ) + { + for ( U32 i = 0; i < 30; i++ ) + xfm->mulP( verts[i].point ); + } + + // Apply position offset + for ( U32 i = 0; i < 30; i++ ) + verts[i].point += pos; + + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + for( U32 i=0; i<6; i++ ) + mDevice->drawPrimitive( GFXLineStrip, i*5, 4 ); +} + +void GFXDrawUtil::_drawSolidCube( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const ColorI &color, const MatrixF *xfm ) +{ + GFXVertexBufferHandle verts(mDevice, 36, GFXBufferTypeVolatile); + verts.lock(); + + Point3F halfSize = size * 0.5f; + + // setup 6 line loops + U32 vertexIndex = 0; + U32 idx; + for(int i = 0; i < 6; i++) + { + idx = cubeFaces[i][0]; + verts[vertexIndex].point = cubePoints[idx] * halfSize; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][1]; + verts[vertexIndex].point = cubePoints[idx] * halfSize; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][3]; + verts[vertexIndex].point = cubePoints[idx] * halfSize; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][1]; + verts[vertexIndex].point = cubePoints[idx] * halfSize; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][2]; + verts[vertexIndex].point = cubePoints[idx] * halfSize; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][3]; + verts[vertexIndex].point = cubePoints[idx] * halfSize; + verts[vertexIndex].color = color; + vertexIndex++; + } + + // Apply xfm if we were passed one. + if ( xfm != NULL ) + { + for ( U32 i = 0; i < 36; i++ ) + xfm->mulV( verts[i].point ); + } + + // Apply position offset + for ( U32 i = 0; i < 36; i++ ) + verts[i].point += pos; + + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleList, 0, 12 ); +} + +void GFXDrawUtil::drawPolyhedron( const GFXStateBlockDesc &desc, const Polyhedron &poly, const ColorI &color, const MatrixF *xfm ) +{ + if ( desc.fillMode == GFXFillWireframe ) + _drawWirePolyhedron( desc, poly, color, xfm ); + else + _drawSolidPolyhedron( desc, poly, color, xfm ); +} + +// maps vertices from the cubeFaces array to a polyhedron object +static const U32 polyFaceMap[] = { 2, 6, 0, 3, 4, 7, 1, 5 }; + +void GFXDrawUtil::_drawWirePolyhedron( const GFXStateBlockDesc &desc, const Polyhedron &poly, const ColorI &color, const MatrixF *xfm ) +{ + GFXVertexBufferHandle verts(mDevice, 30, GFXBufferTypeVolatile); + verts.lock(); + + // setup 6 line loops + U32 vertexIndex = 0; + for(int i = 0; i < 6; i++) + { + for(int j = 0; j < 5; j++) + { + int idx = cubeFaces[i][j%4]; + + verts[vertexIndex].point = poly.pointList[polyFaceMap[idx]]; + verts[vertexIndex].color = color; + vertexIndex++; + } + } + + // Apply xfm if we were passed one. + if ( xfm != NULL ) + { + for ( U32 i = 0; i < 30; i++ ) + xfm->mulP( verts[i].point ); + } + + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + for( U32 i=0; i<6; i++ ) + mDevice->drawPrimitive( GFXLineStrip, i*5, 4 ); +} + +void GFXDrawUtil::_drawSolidPolyhedron( const GFXStateBlockDesc &desc, const Polyhedron &poly, const ColorI &color, const MatrixF *xfm ) +{ + GFXVertexBufferHandle verts(mDevice, 36, GFXBufferTypeVolatile); + verts.lock(); + + // setup 6 line loops + U32 vertexIndex = 0; + U32 idx; + for(int i = 0; i < 6; i++) + { + idx = cubeFaces[i][0]; + verts[vertexIndex].point = poly.pointList[polyFaceMap[idx]]; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][1]; + verts[vertexIndex].point = poly.pointList[polyFaceMap[idx]]; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][3]; + verts[vertexIndex].point = poly.pointList[polyFaceMap[idx]]; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][1]; + verts[vertexIndex].point = poly.pointList[polyFaceMap[idx]]; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][2]; + verts[vertexIndex].point = poly.pointList[polyFaceMap[idx]]; + verts[vertexIndex].color = color; + vertexIndex++; + + idx = cubeFaces[i][3]; + verts[vertexIndex].point = poly.pointList[polyFaceMap[idx]]; + verts[vertexIndex].color = color; + vertexIndex++; + } + + // Apply xfm if we were passed one. + if ( xfm != NULL ) + { + for ( U32 i = 0; i < 36; i++ ) + xfm->mulV( verts[i].point ); + } + + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleList, 0, 12 ); +} + +void GFXDrawUtil::drawObjectBox( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const MatrixF &objMat, const ColorI &color ) +{ + GFXTransformSaver saver; + + mDevice->setStateBlockByDesc( desc ); + + MatrixF scaledObjMat( true ); + scaledObjMat = objMat; + + scaledObjMat.scale( size ); + scaledObjMat.setPosition( pos ); + + PrimBuild::color( color ); + PrimBuild::begin( GFXLineList, 48 ); + + static const Point3F cubePoints[8] = + { + Point3F(-0.5, -0.5, -0.5), Point3F(-0.5, -0.5, 0.5), Point3F(-0.5, 0.5, -0.5), Point3F(-0.5, 0.5, 0.5), + Point3F( 0.5, -0.5, -0.5), Point3F( 0.5, -0.5, 0.5), Point3F( 0.5, 0.5, -0.5), Point3F( 0.5, 0.5, 0.5) + }; + + // 8 corner points of the box + for ( U32 i = 0; i < 8; i++ ) + { + //const Point3F &start = cubePoints[i]; + + // 3 lines per corner point + for ( U32 j = 0; j < 3; j++ ) + { + Point3F start = cubePoints[i]; + Point3F end = start; + end[j] *= 0.8f; + + scaledObjMat.mulP(start); + PrimBuild::vertex3fv(start); + scaledObjMat.mulP(end); + PrimBuild::vertex3fv(end); + } + } + + PrimBuild::end(); +} + +static const Point2F circlePoints[] = +{ + Point2F(0.707107f, 0.707107f), + Point2F(0.923880f, 0.382683f), + Point2F(1.000000f, 0.000000f), + Point2F(0.923880f, -0.382684f), + Point2F(0.707107f, -0.707107f), + Point2F(0.382683f, -0.923880f), + Point2F(0.000000f, -1.000000f), + Point2F(-0.382683f, -0.923880f), + Point2F(-0.707107f, -0.707107f), + Point2F(-0.923880f, -0.382684f), + Point2F(-1.000000f, 0.000000f), + Point2F(-0.923879f, 0.382684f), + Point2F(-0.707107f, 0.707107f), + Point2F(-0.382683f, 0.923880f), + Point2F(0.000000f, 1.000000f), + Point2F(0.382684f, 0.923879f) +}; + +void GFXDrawUtil::drawCapsule( const GFXStateBlockDesc &desc, const Point3F ¢er, F32 radius, F32 height, const ColorI &color, const MatrixF *xfm ) +{ + if ( desc.fillMode == GFXFillWireframe ) + _drawWireCapsule( desc, center, radius, height, color, xfm ); + else + _drawSolidCapsule( desc, center, radius, height, color, xfm ); +} + +void GFXDrawUtil::_drawSolidCapsule( const GFXStateBlockDesc &desc, const Point3F ¢er, F32 radius, F32 height, const ColorI &color, const MatrixF *xfm ) +{ + MatrixF mat; + if ( xfm ) + mat = *xfm; + else + mat = MatrixF::Identity; + + S32 numPoints = sizeof(circlePoints)/sizeof(Point2F); + GFXVertexBufferHandle verts(mDevice, numPoints * 2 + 2, GFXBufferTypeVolatile); + verts.lock(); + + for (S32 i=0; isetStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleStrip, 0, 2 * numPoints ); + + Point3F sphereCenter; + MatrixF sphereMat; + + if ( xfm ) + sphereMat = *xfm; + else + sphereMat = MatrixF::Identity; + + sphereCenter.set( 0, 0, 0.5f * height ); + mat.mulV( sphereCenter ); + sphereCenter += center; + + drawSphere( desc, radius, sphereCenter, color, true, false, &sphereMat ); + + sphereCenter.set( 0, 0, -0.5f * height ); + mat.mulV( sphereCenter ); + sphereCenter += center; + + drawSphere( desc, radius, sphereCenter, color, false, true, &sphereMat ); +} + +void GFXDrawUtil::_drawWireCapsule( const GFXStateBlockDesc &desc, const Point3F ¢er, F32 radius, F32 height, const ColorI &color, const MatrixF *xfm ) +{ + MatrixF mat; + if ( xfm ) + mat = *xfm; + else + mat = MatrixF::Identity; + + mat.scale( Point3F(radius,radius,height*0.5f) ); + mat.setPosition(center); + mDevice->pushWorldMatrix(); + mDevice->multWorld(mat); + + S32 numPoints = sizeof(circlePoints)/sizeof(Point2F); + GFXVertexBufferHandle verts(mDevice, numPoints, GFXBufferTypeVolatile); + verts.lock(); + for (S32 i=0; i< numPoints; i++) + { + S32 idx = i & (~1); // just draw the even ones + F32 z = i & 1 ? 1.0f : -1.0f; + verts[i].point = Point3F(circlePoints[idx].x,circlePoints[idx].y, z); + verts[i].color = color; + } + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + for (S32 i=0; idrawPrimitive(GFXLineStrip, i, 1); + + mDevice->popWorldMatrix(); + + Point3F sphereCenter; + sphereCenter.z = center.z + 0.5f * height; + drawSphere( desc, radius,sphereCenter,color,true,false); + sphereCenter.z = center.z - 0.5f * height; + drawSphere( desc, radius,sphereCenter,color,false,true); +} + +void GFXDrawUtil::drawCone( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 baseRadius, const ColorI &color ) +{ + VectorF uvec = tipPnt - basePnt; + F32 height = uvec.len(); + uvec.normalize(); + MatrixF mat( true ); + MathUtils::getMatrixFromUpVector( uvec, &mat ); + mat.setPosition(basePnt); + + Point3F scale( baseRadius, baseRadius, height ); + mat.scale(scale); + + GFXTransformSaver saver; + + mDevice->pushWorldMatrix(); + mDevice->multWorld(mat); + + S32 numPoints = sizeof(circlePoints)/sizeof(Point2F); + GFXVertexBufferHandle verts(mDevice, numPoints + 2, GFXBufferTypeVolatile); + verts.lock(); + verts[0].point = Point3F(0.0f,0.0f,1.0f); + verts[0].color = color; + for (S32 i=0; isetStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders( GFXDevice::GSModColorTexture ); + + mDevice->drawPrimitive( GFXTriangleFan, 0, numPoints ); + mDevice->drawPrimitive( GFXTriangleFan, 1, numPoints-1 ); + + mDevice->popWorldMatrix(); +} + +void GFXDrawUtil::drawCylinder( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 radius, const ColorI &color ) +{ + VectorF uvec = tipPnt - basePnt; + F32 height = uvec.len(); + uvec.normalize(); + MatrixF mat( true ); + MathUtils::getMatrixFromUpVector( uvec, &mat ); + mat.setPosition(basePnt); + + Point3F scale( radius/2, radius/2, height * 2 ); + mat.scale(scale); + GFXTransformSaver saver; + + mDevice->pushWorldMatrix(); + mDevice->multWorld(mat); + + S32 numPoints = sizeof(circlePoints)/sizeof(Point2F); + GFXVertexBufferHandle verts(mDevice, numPoints * 4 + 4, GFXBufferTypeVolatile); + verts.lock(); + for (S32 i=0; isetStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders( GFXDevice::GSModColorTexture ); + + mDevice->drawPrimitive( GFXTriangleFan, 0, numPoints ); + mDevice->drawPrimitive( GFXTriangleFan, numPoints + 1, numPoints ); + mDevice->drawPrimitive( GFXTriangleStrip, 2 * numPoints + 2, 2 * numPoints); + + mDevice->popWorldMatrix(); +} + +void GFXDrawUtil::drawArrow( const GFXStateBlockDesc &desc, const Point3F &start, const Point3F &end, const ColorI &color ) +{ + GFXTransformSaver saver; + + // Direction and length of the arrow. + VectorF dir = end - start; + F32 len = dir.len(); + dir.normalize(); + len *= 0.2f; + + // Base of the cone will be a distance back from the end of the arrow + // proportional to the total distance of the arrow... 0.3f looks about right. + Point3F coneBase = end - dir * len * 0.3f; + + // Calculate the radius of the cone given that we want the cone to have + // an angle of 25 degrees (just because it looks good). + F32 coneLen = ( end - coneBase ).len(); + F32 coneDiameter = mTan( mDegToRad(25.0f) ) * coneLen; + + // Draw the cone on at the arrow's tip. + drawCone( desc, coneBase, end, coneDiameter / 2.0f, color ); + + // Get the difference in length from + // the start of the cone to the end + // of the cylinder so we can put the + // end of the cylinder right against where + // the cone starts. + Point3F coneDiff = end - coneBase; + + // Draw the cylinder. + F32 stickRadius = len * 0.025f; + drawCylinder( desc, start, end - coneDiff, stickRadius, color ); +} + +void GFXDrawUtil::drawFrustum( const Frustum &f, const ColorI &color ) +{ + const Point3F *points = f.getPoints(); + + // Draw near and far planes. + for (U32 offset = 0; offset < 8; offset+=4) + { + drawLine(points[offset+0], points[offset+1], color); + drawLine(points[offset+2], points[offset+3], color); + drawLine(points[offset+0], points[offset+2], color); + drawLine(points[offset+1], points[offset+3], color); + } + + // connect the near and far planes + drawLine(points[Frustum::NearTopLeft], points[Frustum::FarTopLeft], color); + drawLine(points[Frustum::NearTopRight], points[Frustum::FarTopRight], color); + drawLine(points[Frustum::NearBottomLeft], points[Frustum::FarBottomLeft], color); + drawLine(points[Frustum::NearBottomRight], points[Frustum::FarBottomRight], color); +} + +void GFXDrawUtil::drawSolidPlane( const GFXStateBlockDesc &desc, const Point3F &pos, const Point2F &size, const ColorI &color ) +{ + GFXVertexBufferHandle verts(mDevice, 4, GFXBufferTypeVolatile); + verts.lock(); + + verts[0].point = pos + Point3F( -size.x / 2.0f, -size.y / 2.0f, 0 ); + verts[0].color = color; + verts[1].point = pos + Point3F( -size.x / 2.0f, size.y / 2.0f, 0 ); + verts[1].color = color; + verts[2].point = pos + Point3F( size.x / 2.0f, size.y / 2.0f, 0 ); + verts[2].color = color; + verts[3].point = pos + Point3F( size.x / 2.0f, -size.y / 2.0f, 0 ); + verts[3].color = color; + + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXTriangleFan, 0, 2 ); +} + +void GFXDrawUtil::drawPlaneGrid( const GFXStateBlockDesc &desc, const Point3F &pos, const Point2F &size, const Point2F &step, const ColorI &color ) +{ + // Note that when calculating the number of steps, we +0.5 to round up, + // and +1 for the last line (ie. 4 steps needs 5 lines to be rendered) + U32 xSteps = 0; + if ( step.x > 0 ) + xSteps = size.x / step.x + 0.5 + 1; + + U32 ySteps = 0; + if ( step.y > 0 ) + ySteps = size.y / step.y + 0.5 + 1; + + if ( xSteps <= 1 || ySteps <= 1 ) + return; + + GFXVertexBufferHandle verts( mDevice, ( xSteps * 2 ) + ( ySteps * 2 ), GFXBufferTypeVolatile ); + verts.lock(); + + U32 vertCount = 0; + + Point3F origin( pos.x - ( size.x / 2.0f ), pos.y - ( size.y / 2.0f ), pos.z ); + + F32 start = mFloor( origin.x / step.x + 0.5f ) * step.x; + + for ( U32 i = 0; i < xSteps; i++ ) + { + verts[vertCount].point = Point3F( start + step.x * i, origin.y, origin.z ); + verts[vertCount].color = color; + ++vertCount; + + verts[vertCount].point = Point3F( start + step.x * i, origin.y + size.y, origin.z ); + verts[vertCount].color = color; + ++vertCount; + } + + start = mFloor( origin.y / step.y + 0.5f ) * step.y; + + for ( U32 i = 0; i < ySteps; i++ ) + { + verts[vertCount].point = Point3F( origin.x, start + step.y * i, origin.z ); + verts[vertCount].color = color; + ++vertCount; + + verts[vertCount].point = Point3F( origin.x + size.x, start + step.y * i, origin.z ); + verts[vertCount].color = color; + ++vertCount; + } + + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + + mDevice->drawPrimitive( GFXLineList, 0, xSteps + ySteps ); +} + +void GFXDrawUtil::drawTransform( const GFXStateBlockDesc &desc, const MatrixF &mat, const Point3F &scale, const ColorI &color ) +{ + GFXTransformSaver saver; + + GFX->multWorld( mat ); + + GFXVertexBufferHandle verts( mDevice, 6, GFXBufferTypeVolatile ); + verts.lock(); + + verts[0].point = Point3F::Zero; + verts[0].color = color; + verts[1].point = Point3F( 1, 0, 0 ); + verts[1].color = color; + verts[2].point = Point3F::Zero; + verts[2].color = color; + verts[3].point = Point3F( 0, 1, 0 ); + verts[3].color = color; + verts[4].point = Point3F::Zero; + verts[4].color = color; + verts[5].point = Point3F( 0, 0, 1 ); + verts[5].color = color; + + verts.unlock(); + + mDevice->setStateBlockByDesc( desc ); + + mDevice->setVertexBuffer( verts ); + mDevice->setupGenericShaders(); + mDevice->drawPrimitive( GFXLineList, 0, 6 ); +} \ No newline at end of file diff --git a/gfx/gfxDrawUtil.h b/gfx/gfxDrawUtil.h new file mode 100644 index 0000000..13c636f --- /dev/null +++ b/gfx/gfxDrawUtil.h @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFX_GFXDRAWER_H_ +#define _GFX_GFXDRAWER_H_ + +#include "gfx/gfxDevice.h" + +class FontRenderBatcher; +class Frustum; +struct Polyhedron; + +/// Helper class containing utility functions for useful drawing routines +/// (line, box, rect, billboard, text). +class GFXDrawUtil +{ +public: + GFXDrawUtil(GFXDevice *); + ~GFXDrawUtil(); + + //----------------------------------------------------------------------------- + // Draw Rectangles + //----------------------------------------------------------------------------- + void drawRect( const Point2F &upperLeft, const Point2F &lowerRight, const ColorI &color ); + void drawRect( const RectF &rect, const ColorI &color ); + void drawRect( const Point2I &upperLeft, const Point2I &lowerRight, const ColorI &color ); + void drawRect( const RectI &rect, const ColorI &color ); + + void drawRectFill( const Point2F &upperL, const Point2F &lowerR, const ColorI &color ); + void drawRectFill( const RectF &rect, const ColorI &color ); + void drawRectFill( const Point2I &upperLeft, const Point2I &lowerRight, const ColorI &color ); + void drawRectFill( const RectI &rect, const ColorI &color ); + + void draw2DSquare( const Point2F &screenPoint, F32 width, F32 spinAngle ); + + //----------------------------------------------------------------------------- + // Draw Lines + //----------------------------------------------------------------------------- + void drawLine( const Point3F &startPt, const Point3F &endPt, const ColorI &color ); + void drawLine( const Point2F &startPt, const Point2F &endPt, const ColorI &color ); + void drawLine( const Point2I &startPt, const Point2I &endPt, const ColorI &color ); + void drawLine( F32 x1, F32 y1, F32 x2, F32 y2, const ColorI &color ); + void drawLine( F32 x1, F32 y1, F32 z1, F32 x2, F32 y2, F32 z2, const ColorI &color ); + + //----------------------------------------------------------------------------- + // Draw Text + //----------------------------------------------------------------------------- + U32 drawText( GFont *font, const Point2I &ptDraw, const UTF8 *in_string, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + U32 drawTextN( GFont *font, const Point2I &ptDraw, const UTF8 *in_string, U32 n, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + U32 drawText( GFont *font, const Point2I &ptDraw, const UTF16 *in_string, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + U32 drawTextN( GFont *font, const Point2I &ptDraw, const UTF16 *in_string, U32 n, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + + U32 drawText( GFont *font, const Point2F &ptDraw, const UTF8 *in_string, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + U32 drawTextN( GFont *font, const Point2F &ptDraw, const UTF8 *in_string, U32 n, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + U32 drawText( GFont *font, const Point2F &ptDraw, const UTF16 *in_string, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + U32 drawTextN( GFont *font, const Point2F &ptDraw, const UTF16 *in_string, U32 n, const ColorI *colorTable = NULL, const U32 maxColorIndex = 9, F32 rot = 0.f ); + + //----------------------------------------------------------------------------- + // Color Modulation + //----------------------------------------------------------------------------- + void setBitmapModulation( const ColorI &modColor ); + void setTextAnchorColor( const ColorI &ancColor ); + void clearBitmapModulation(); + void getBitmapModulation( ColorI *color ); + + //----------------------------------------------------------------------------- + // Draw Bitmaps + //----------------------------------------------------------------------------- + void drawBitmap( GFXTextureObject*texture, const Point2F &in_rAt, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapSR( GFXTextureObject*texture, const Point2F &in_rAt, const RectF &srcRect, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapStretch( GFXTextureObject*texture, const RectF &dstRect, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapStretchSR( GFXTextureObject*texture, const RectF &dstRect, const RectF &srcRect, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + + void drawBitmap( GFXTextureObject*texture, const Point2I &in_rAt, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapSR( GFXTextureObject*texture, const Point2I &in_rAt, const RectI &srcRect, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapStretch( GFXTextureObject*texture, const RectI &dstRect, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapStretchSR( GFXTextureObject*texture, const RectI &dstRect, const RectI &srcRect, const GFXBitmapFlip in_flip = GFXBitmapFlip_None, const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + + void drawBitmapStretchSRCircle( GFXTextureObject*texture, const RectI &dstRect, const RectI &srcRect,const GFXBitmapFlip in_flip = GFXBitmapFlip_None , const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapStretchRotate( GFXTextureObject*texture, const RectI &dstRect,F32 spinAngle, const GFXBitmapFlip in_flip = GFXBitmapFlip_None , const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawBitmapStretchSRRotate( GFXTextureObject*texture, const RectI &dstRect, const RectI &srcRect,F32 spinAngle, const GFXBitmapFlip in_flip = GFXBitmapFlip_None , const GFXTextureFilterType filter = GFXTextureFilterPoint , bool in_wrap = true ); + void drawCDRectFill (const Point2I ¢er , const Point2I &size , const F32 angle,const ColorI &color); + //----------------------------------------------------------------------------- + // Draw 3D Shapes + //----------------------------------------------------------------------------- + void drawTriangle( const GFXStateBlockDesc &desc, const Point3F &p0, const Point3F &p1, const Point3F &p2, const ColorI &color, const MatrixF *xfm = NULL ); + void drawCube( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const ColorI &color, const MatrixF *xfm = NULL ); + void drawCube( const GFXStateBlockDesc &desc, const Box3F &box, const ColorI &color, const MatrixF *xfm = NULL ); + void drawPolyhedron( const GFXStateBlockDesc &desc, const Polyhedron &poly, const ColorI &color, const MatrixF *xfm = NULL ); + void drawObjectBox( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const MatrixF &objMat, const ColorI &color ); + void drawSphere( const GFXStateBlockDesc &desc, F32 radius, const Point3F &pos, const ColorI &color, bool drawTop = true, bool drawBottom = true, const MatrixF *xfm = NULL ); + void drawCapsule( const GFXStateBlockDesc &desc, const Point3F ¢er, F32 radius, F32 height, const ColorI &color, const MatrixF *xfm = NULL ); + void drawCone( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 baseRadius, const ColorI &color ); + void drawCylinder( const GFXStateBlockDesc &desc, const Point3F &basePnt, const Point3F &tipPnt, F32 baseRadius, const ColorI &color ); + void drawArrow( const GFXStateBlockDesc &desc, const Point3F &start, const Point3F &end, const ColorI &color ); + void drawFrustum( const Frustum& f, const ColorI &color ); + + /// Draws a solid XY plane centered on the point with the specified dimensions. + void drawSolidPlane( const GFXStateBlockDesc &desc, const Point3F &pos, const Point2F &size, const ColorI &color ); + + /// Draws a grid on XY plane centered on the point with the specified size and step size. + void drawPlaneGrid( const GFXStateBlockDesc &desc, const Point3F &pos, const Point2F &size, const Point2F &step, const ColorI &color ); + + /// Draws an xyz axes representing the passed matrix. + void drawTransform( const GFXStateBlockDesc &desc, const MatrixF &mat, const Point3F &scale, const ColorI &color ); + +protected: + + void _setupStateBlocks(); + void _drawWireTriangle( const GFXStateBlockDesc &desc, const Point3F &p0, const Point3F &p1, const Point3F &p2, const ColorI &color, const MatrixF *xfm = NULL ); + void _drawSolidTriangle( const GFXStateBlockDesc &desc, const Point3F &p0, const Point3F &p1, const Point3F &p2, const ColorI &color, const MatrixF *xfm = NULL ); + void _drawWireCube( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const ColorI &color, const MatrixF *xfm = NULL ); + void _drawSolidCube( const GFXStateBlockDesc &desc, const Point3F &size, const Point3F &pos, const ColorI &color, const MatrixF *xfm = NULL ); + void _drawWireCapsule( const GFXStateBlockDesc &desc, const Point3F ¢er, F32 radius, F32 height, const ColorI &color, const MatrixF *xfm = NULL ); + void _drawSolidCapsule( const GFXStateBlockDesc &desc, const Point3F ¢er, F32 radius, F32 height, const ColorI &color, const MatrixF *xfm = NULL ); + void _drawWirePolyhedron( const GFXStateBlockDesc &desc, const Polyhedron &poly, const ColorI &color, const MatrixF *xfm = NULL ); + void _drawSolidPolyhedron( const GFXStateBlockDesc &desc, const Polyhedron &poly, const ColorI &color, const MatrixF *xfm = NULL ); + +protected: + + /// The device we're rendering to. + GFXDevice *mDevice; + + /// Bitmap modulation color; bitmaps are multiplied by this color when + /// drawn. + GFXVertexColor mBitmapModulation; + + /// Base text color; what color text is drawn at when no other color is + /// specified. + GFXVertexColor mTextAnchorColor; + + GFXStateBlockRef mBitmapStretchSB; + GFXStateBlockRef mBitmapStretchLinearSB; + GFXStateBlockRef mBitmapStretchWrapSB; + GFXStateBlockRef mBitmapStretchWrapLinearSB; + GFXStateBlockRef mRectFillSB; + + FontRenderBatcher* mFontRenderBatcher; +}; + +#endif // _GFX_GFXDRAWER_H_ diff --git a/gfx/gfxEnums.h b/gfx/gfxEnums.h new file mode 100644 index 0000000..5004664 --- /dev/null +++ b/gfx/gfxEnums.h @@ -0,0 +1,582 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GFXENUMS_H_ +#define _GFXENUMS_H_ + +#include "core/util/fourcc.h" + +// These are for the enum translation. It will help with porting to other platforms +// and API's. +#define GFX_UNSUPPORTED_VAL 0xDEADBEEF +#define GFX_UNINIT_VAL 0xDECAFBAD + +// Adjust these pools to your app's needs. Be aware dynamic vertices are much more +// expensive than static vertices. These are in gfxEnums because they should be +// consistant across all APIs/platforms so that the dynamic buffer performance +// and behavior is also consistant. -patw +#define MAX_DYNAMIC_VERTS (8192*2) +#define MAX_DYNAMIC_INDICES (8192*4) + +enum GFXBufferType +{ + GFXBufferTypeStatic, ///< Static vertex buffers are created and filled one time. + ///< incur a performance penalty. Resizing a static vertex buffer is not + ///< allowed. + GFXBufferTypeDynamic, ///< Dynamic vertex buffers are meant for vertices that can be changed + ///< often. Vertices written into dynamic vertex buffers will remain valid + ///< until the dynamic vertex buffer is released. Resizing a dynamic vertex buffer is not + ///< allowed. + GFXBufferTypeVolatile, ///< Volatile vertex or index buffers are meant for vertices or indices that are essentially + ///< only used once. They can be resized without any performance penalty. + + GFXBufferType_COUNT ///< Number of buffer types. +}; + +enum GFXTexCallbackCode +{ + GFXZombify, + GFXResurrect, +}; + + +enum GFXPrimitiveType +{ + GFXPT_FIRST = 0, + GFXPointList = 0, + GFXLineList, + GFXLineStrip, + GFXTriangleList, + GFXTriangleStrip, + GFXTriangleFan, + GFXPT_COUNT +}; + +enum GFXTextureType +{ + GFXTextureType_Normal, + GFXTextureType_KeepBitmap, + GFXTextureType_Dynamic, + GFXTextureType_RenderTarget, + GFXTextureType_Count +}; + +enum GFXBitmapFlip +{ + GFXBitmapFlip_None = 0, + GFXBitmapFlip_X = 1 << 0, + GFXBitmapFlip_Y = 1 << 1, + GFXBitmapFlip_XY = GFXBitmapFlip_X | GFXBitmapFlip_Y +}; + +enum GFXTextureOp +{ + GFXTOP_FIRST = 0, + GFXTOPDisable = 0, + GFXTOPSelectARG1, + GFXTOPSelectARG2, + GFXTOPModulate, + GFXTOPModulate2X, + GFXTOPModulate4X, + GFXTOPAdd, + GFXTOPAddSigned, + GFXTOPAddSigned2X, + GFXTOPSubtract, + GFXTOPAddSmooth, + GFXTOPBlendDiffuseAlpha, + GFXTOPBlendTextureAlpha, + GFXTOPBlendFactorAlpha, + GFXTOPBlendTextureAlphaPM, + GFXTOPBlendCURRENTALPHA, + GFXTOPPreModulate, + GFXTOPModulateAlphaAddColor, + GFXTOPModulateColorAddAlpha, + GFXTOPModulateInvAlphaAddColor, + GFXTOPModulateInvColorAddAlpha, + GFXTOPBumpEnvMap, + GFXTOPBumpEnvMapLuminance, + GFXTOPDotProduct3, + GFXTOPLERP, + GFXTOP_COUNT +}; + +enum GFXTextureAddressMode +{ + GFXAddress_FIRST = 0, + GFXAddressWrap = 0, + GFXAddressMirror, + GFXAddressClamp, + GFXAddressBorder, + GFXAddressMirrorOnce, + GFXAddress_COUNT +}; + +enum GFXTextureFilterType +{ + GFXTextureFilter_FIRST = 0, + GFXTextureFilterNone = 0, + GFXTextureFilterPoint, + GFXTextureFilterLinear, + GFXTextureFilterAnisotropic, + GFXTextureFilterPyramidalQuad, + GFXTextureFilterGaussianQuad, + GFXTextureFilter_COUNT +}; + +enum GFXFillMode +{ + GFXFill_FIRST = 1, + GFXFillPoint = 1, + GFXFillWireframe, + GFXFillSolid, + GFXFill_COUNT +}; + +enum GFXFormat +{ + // when adding formats make sure to place + // them in the correct group! + // + // if displacing the first entry in the group + // make sure to update the GFXFormat_xBIT entries! + // + GFXFormat_FIRST = 0, + + // 8 bit texture formats... + GFXFormatA8 = 0,// first in group... + GFXFormatL8, + + // 16 bit texture formats... + GFXFormatR5G6B5,// first in group... + GFXFormatR5G5B5A1, + GFXFormatR5G5B5X1, + GFXFormatL16, + GFXFormatR16F, + GFXFormatD16, + + // 24 bit texture formats... + GFXFormatR8G8B8,// first in group... + + // 32 bit texture formats... + GFXFormatR8G8B8A8,// first in group... + GFXFormatR8G8B8X8, + GFXFormatR32F, + GFXFormatR16G16, + GFXFormatR16G16F, + GFXFormatR10G10B10A2, + GFXFormatD32, + GFXFormatD24X8, + GFXFormatD24S8, + GFXFormatD24FS8, + + // 64 bit texture formats... + GFXFormatR16G16B16A16,// first in group... + GFXFormatR16G16B16A16F, + + // 128 bit texture formats... + GFXFormatR32G32B32A32F,// first in group... + + // unknown size... + GFXFormatDXT1,// first in group... + GFXFormatDXT2, + GFXFormatDXT3, + GFXFormatDXT4, + GFXFormatDXT5, + + GFXFormat_COUNT, + + GFXFormat_8BIT = GFXFormatA8, + GFXFormat_16BIT = GFXFormatR5G6B5, + GFXFormat_24BIT = GFXFormatR8G8B8, + GFXFormat_32BIT = GFXFormatR8G8B8A8, + GFXFormat_64BIT = GFXFormatR16G16B16A16, + GFXFormat_128BIT = GFXFormatR32G32B32A32F, + GFXFormat_UNKNOWNSIZE = GFXFormatDXT1, +}; + +enum GFXShadeMode +{ + GFXShadeFlat = 1, + GFXShadeGouraud, + GFXShadePhong, +}; + +enum GFXClearFlags +{ + GFXClearTarget = 1 << 0, + GFXClearZBuffer = 1 << 1, + GFXClearStencil = 1 << 2, +}; + +enum GFXBlend +{ + GFXBlend_FIRST = 0, + GFXBlendZero = 0, + GFXBlendOne, + GFXBlendSrcColor, + GFXBlendInvSrcColor, + GFXBlendSrcAlpha, + GFXBlendInvSrcAlpha, + GFXBlendDestAlpha, + GFXBlendInvDestAlpha, + GFXBlendDestColor, + GFXBlendInvDestColor, + GFXBlendSrcAlphaSat, + GFXBlend_COUNT +}; + +/// Constants that name each GFXDevice type. Any new GFXDevice subclass must be +/// added to this enum. A string representing its name must also be added to +/// GFXInit::getAdapterNameFromType(). +enum GFXAdapterType +{ + OpenGL = 0, + Direct3D9, + Direct3D8, + NullDevice, + Direct3D9_360, + GFXAdapterType_Count +}; + +enum GFXCullMode +{ + GFXCull_FIRST = 0, + GFXCullNone = 0, + GFXCullCW, + GFXCullCCW, + GFXCull_COUNT +}; + +enum GFXCmpFunc +{ + GFXCmp_FIRST = 0, + GFXCmpNever = 0, + GFXCmpLess, + GFXCmpEqual, + GFXCmpLessEqual, + GFXCmpGreater, + GFXCmpNotEqual, + GFXCmpGreaterEqual, + GFXCmpAlways, + GFXCmp_COUNT +}; + +enum GFXStencilOp +{ + GFXStencilOp_FIRST = 0, + GFXStencilOpKeep = 0, + GFXStencilOpZero, + GFXStencilOpReplace, + GFXStencilOpIncrSat, + GFXStencilOpDecrSat, + GFXStencilOpInvert, + GFXStencilOpIncr, + GFXStencilOpDecr, + GFXStencilOp_COUNT +}; + +enum GFXMaterialColorSource +{ + GFXMCSMaterial = 0, + GFXMCSColor1, + GFXMCSColor2, +}; + +enum GFXBlendOp +{ + GFXBlendOp_FIRST = 0, + GFXBlendOpAdd = 0, + GFXBlendOpSubtract, + GFXBlendOpRevSubtract, + GFXBlendOpMin, + GFXBlendOpMax, + GFXBlendOp_COUNT +}; + +enum GFXRenderState +{ + GFXRenderState_FIRST = 0, + GFXRSZEnable = 0, + GFXRSFillMode, + GFXRSShadeMode, + GFXRSZWriteEnable, + GFXRSAlphaTestEnable, + GFXRSLastPixel, + GFXRSSrcBlend, + GFXRSDestBlend, + GFXRSCullMode, + GFXRSZFunc, + GFXRSAlphaRef, + GFXRSAlphaFunc, + GFXRSDitherEnable, + GFXRSAlphaBlendEnable, + GFXRSFogEnable, + GFXRSSpecularEnable, + GFXRSFogColor, + GFXRSFogTableMode, + GFXRSFogStart, + GFXRSFogEnd, + GFXRSFogDensity, + GFXRSRangeFogEnable, + GFXRSStencilEnable, + GFXRSStencilFail, + GFXRSStencilZFail, + GFXRSStencilPass, + GFXRSStencilFunc, + GFXRSStencilRef, + GFXRSStencilMask, + GFXRSStencilWriteMask, + GFXRSTextureFactor, + GFXRSWrap0, + GFXRSWrap1, + GFXRSWrap2, + GFXRSWrap3, + GFXRSWrap4, + GFXRSWrap5, + GFXRSWrap6, + GFXRSWrap7, + GFXRSClipping, + GFXRSLighting, + GFXRSAmbient, + GFXRSFogVertexMode, + GFXRSColorVertex, + GFXRSLocalViewer, + GFXRSNormalizeNormals, + GFXRSDiffuseMaterialSource, + GFXRSSpecularMaterialSource, + GFXRSAmbientMaterialSource, + GFXRSEmissiveMaterialSource, + GFXRSVertexBlend, + GFXRSClipPlaneEnable, + GFXRSPointSize, + GFXRSPointSizeMin, + GFXRSPointSpriteEnable, + GFXRSPointScaleEnable, + GFXRSPointScale_A, + GFXRSPointScale_B, + GFXRSPointScale_C, + GFXRSMultiSampleantiAlias, + GFXRSMultiSampleMask, + GFXRSPatchEdgeStyle, + GFXRSDebugMonitorToken, + GFXRSPointSize_Max, + GFXRSIndexedVertexBlendEnable, + GFXRSColorWriteEnable, + GFXRSTweenFactor, + GFXRSBlendOp, + GFXRSPositionDegree, + GFXRSNormalDegree, + GFXRSScissorTestEnable, + GFXRSSlopeScaleDepthBias, + GFXRSAntiAliasedLineEnable, + GFXRSMinTessellationLevel, + GFXRSMaxTessellationLevel, + GFXRSAdaptiveTess_X, + GFXRSAdaptiveTess_Y, + GFXRSdaptiveTess_Z, + GFXRSAdaptiveTess_W, + GFXRSEnableAdaptiveTesselation, + GFXRSTwoSidedStencilMode, + GFXRSCCWStencilFail, + GFXRSCCWStencilZFail, + GFXRSCCWStencilPass, + GFXRSCCWStencilFunc, + GFXRSColorWriteEnable1, + GFXRSColorWriteEnable2, + GFXRSolorWriteEnable3, + GFXRSBlendFactor, + GFXRSSRGBWriteEnable, + GFXRSDepthBias, + GFXRSWrap8, + GFXRSWrap9, + GFXRSWrap10, + GFXRSWrap11, + GFXRSWrap12, + GFXRSWrap13, + GFXRSWrap14, + GFXRSWrap15, + GFXRSSeparateAlphaBlendEnable, + GFXRSSrcBlendAlpha, + GFXRSDestBlendAlpha, + GFXRSBlendOpAlpha, + GFXRenderState_COUNT ///< Don't use this one, this is a counter +}; + +#define GFXCOLORWRITEENABLE_RED 1 +#define GFXCOLORWRITEENABLE_GREEN 2 +#define GFXCOLORWRITEENABLE_BLUE 4 +#define GFXCOLORWRITEENABLE_ALPHA 8 + +enum GFXTextureStageState +{ + GFXTSS_FIRST = 0, + GFXTSSColorOp = 0, + GFXTSSColorArg1, + GFXTSSColorArg2, + GFXTSSAlphaOp, + GFXTSSAlphaArg1, + GFXTSSAlphaArg2, + GFXTSSBumpEnvMat00, + GFXTSSBumpEnvMat01, + GFXTSSBumpEnvMat10, + GFXTSSBumpEnvMat11, + GFXTSSTexCoordIndex, + GFXTSSBumpEnvlScale, + GFXTSSBumpEnvlOffset, + GFXTSSTextureTransformFlags, + GFXTSSColorArg0, + GFXTSSAlphaArg0, + GFXTSSResultArg, + GFXTSSConstant, + GFXTSS_COUNT ///< Don't use this one, this is a counter +}; + +enum GFXTextureTransformFlags +{ + GFXTTFFDisable = 0, + GFXTTFFCoord1D = 1, + GFXTTFFCoord2D = 2, + GFXTTFFCoord3D = 3, + GFXTTFFCoord4D = 4, + GFXTTFFProjected = 256, +}; + +// CodeReview: This number is used for the declaration of variables, but it +// should *not* be used for any run-time purposes [7/2/2007 Pat] +#define TEXTURE_STAGE_COUNT 16 + +enum GFXSamplerState +{ + GFXSAMP_FIRST = 0, + GFXSAMPAddressU = 0, + GFXSAMPAddressV, + GFXSAMPAddressW, + GFXSAMPBorderColor, + GFXSAMPMagFilter, + GFXSAMPMinFilter, + GFXSAMPMipFilter, + GFXSAMPMipMapLODBias, + GFXSAMPMaxMipLevel, + GFXSAMPMaxAnisotropy, + GFXSAMPSRGBTexture, + GFXSAMPElementIndex, + GFXSAMPDMapOffset, + GFXSAMP_COUNT ///< Don't use this one, this is a counter +}; + +enum GFXTextureArgument +{ + GFXTA_FIRST = 0, + GFXTADiffuse = 0, + GFXTACurrent, + GFXTATexture, + GFXTATFactor, + GFXTASpecular, + GFXTATemp, + GFXTAConstant, + GFXTA_COUNT, + GFXTAComplement = 0x00000010, // take 1.0 - x (read modifier) + GFXTAAlphaReplicate = 0x00000020, // replicate alpha to color components (read modifier) +}; + +// Matrix stuff +#define WORLD_STACK_MAX 24 + +enum GFXMatrixType +{ + GFXMatrixWorld = 256, + GFXMatrixView = 2, + GFXMatrixProjection = 3, + GFXMatrixTexture = 16, // This value is texture matrix for sampler 0, can use this for offset + GFXMatrixTexture0 = 16, + GFXMatrixTexture1 = 17, + GFXMatrixTexture2 = 18, + GFXMatrixTexture3 = 19, + GFXMatrixTexture4 = 20, + GFXMatrixTexture5 = 21, + GFXMatrixTexture6 = 22, + GFXMatrixTexture7 = 23, +}; + +// Light define +#define LIGHT_STAGE_COUNT 8 + +#define GFXVERTEXFLAG_F32 3 +#define GFXVERTEXFLAG_POINT2F 0 +#define GFXVERTEXFLAG_POINT3F 1 +#define GFXVERTEXFLAG_POINT4F 2 + +#define GFXVERTEXFLAG_TEXCOORD_F32(CoordIndex) ( GFXVERTEXFLAG_F32 << ( CoordIndex * 2 + 16 ) ) +#define GFXVERTEXFLAG_TEXCOORD_POINT2F(CoordIndex) ( GFXVERTEXFLAG_POINT2F ) +#define GFXVERTEXFLAG_TEXCOORD_POINT3F(CoordIndex) ( GFXVERTEXFLAG_POINT3F << ( CoordIndex * 2 + 16 ) ) +#define GFXVERTEXFLAG_TEXCOORD_POINT4F(CoordIndex) ( GFXVERTEXFLAG_POINT4F << ( CoordIndex * 2 + 16 ) ) + +#define STATE_STACK_SIZE 32 + +// Index Formats +enum GFXIndexFormat +{ + GFXIndexFormat_FIRST = 0, + GFXIndexFormat16 = 0, + GFXIndexFormat32, + GFXIndexFormat_COUNT +}; + +enum GFXShaderConstType +{ + /// GFX"S"hader"C"onstant"T"ype + // Scalar + GFXSCT_Float, + // Vectors + GFXSCT_Float2, + GFXSCT_Float3, + GFXSCT_Float4, + // Matrices + GFXSCT_Float2x2, + GFXSCT_Float3x3, + GFXSCT_Float4x4, + // Scalar + GFXSCT_Int, + // Vectors + GFXSCT_Int2, + GFXSCT_Int3, + GFXSCT_Int4, + // Samplers + GFXSCT_Sampler, + GFXSCT_SamplerCube +}; + + +/// Defines a vertex declaration type. +/// @see GFXVertexElement +/// @see GFXVertexFormat +enum GFXDeclType +{ + GFXDeclType_FIRST = 0, + + /// A single component F32. + GFXDeclType_Float = 0, + + /// A two-component F32. + /// @see Point2F + GFXDeclType_Float2, + + /// A three-component F32. + /// @see Point3F + GFXDeclType_Float3, + + /// A four-component F32. + /// @see Point4F + GFXDeclType_Float4, + + /// A four-component, packed, unsigned bytes mapped to 0 to 1 range. + /// @see GFXVertexColor + GFXDeclType_Color, + + /// The count of total GFXDeclTypes. + GFXDeclType_COUNT, +}; + +#endif // _GFXENUMS_H_ diff --git a/gfx/gfxFence.cpp b/gfx/gfxFence.cpp new file mode 100644 index 0000000..10d5e30 --- /dev/null +++ b/gfx/gfxFence.cpp @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxFence.h" + +#include "gfx/primBuilder.h" +#include "gfx/gfxTextureManager.h" + + +GFXGeneralFence::~GFXGeneralFence() +{ + // Release the ref pointers + mRenderTarget = NULL; + mRTTexHandle = NULL; + + // Unregister texture callback + GFXTextureManager::removeEventDelegate( this, &GFXGeneralFence::_onTextureEvent ); +} + +//------------------------------------------------------------------------------ + +void GFXGeneralFence::_init() +{ + // Register texture callback to re-create resources + if ( !mInitialized ) + GFXTextureManager::addEventDelegate( this, &GFXGeneralFence::_onTextureEvent ); + + // Set this to true for error checking + mInitialized = true; + + // Allocate resources + mInitialized &= mRTTexHandle.set( 2, 2, GFXFormatR8G8B8X8, &GFXDefaultRenderTargetProfile, avar("%s() - mInitialized (line %d)", __FUNCTION__, __LINE__) ); + mRenderTarget = GFX->allocRenderToTextureTarget(); + mInitialized &= ( mRenderTarget != NULL ); + + GFXStateBlockDesc d; + mRenderSB = GFX->createStateBlock(d); +} + +//------------------------------------------------------------------------------ + +void GFXGeneralFence::issue() +{ + PROFILE_SCOPE(GFXGeneralFence_Issue); + + // Resource creation will be done on first call to issue() + if( !mInitialized ) + _init(); + + // If we can't init, return. + if(!mInitialized) + return; + + AssertFatal( mInitialized, "Error occured during GFXGeneralFence::_init, sorry I can't be more specific, break and debug." ); + + RectI viewport = GFX->getViewport(); + + GFX->pushActiveRenderTarget(); + mRenderTarget->attachTexture( GFXTextureTarget::Color0, mRTTexHandle ); + GFX->setActiveRenderTarget( mRenderTarget ); + + // Set-up states + GFX->setStateBlock(mRenderSB); + GFX->pushWorldMatrix(); + GFX->setWorldMatrix( MatrixF::Identity ); + + // CodeReview: We can re-do this with a static vertex buffer at some point. [5/9/2007 Pat] + PrimBuild::begin( GFXTriangleList, 3 ); + PrimBuild::vertex2f( 0.f, 0.f ); + PrimBuild::vertex2f( 0.f, 1.f ); + PrimBuild::vertex2f( 1.f, 0.f ); + PrimBuild::end(); + + GFX->popWorldMatrix(); + GFX->popActiveRenderTarget(); + + GFX->setViewport(viewport); +} + +//------------------------------------------------------------------------------ + +void GFXGeneralFence::block() +{ + PROFILE_SCOPE(GFXGeneralFence_block); + if( !mInitialized ) + return; + + // We have to deal with the case where the lock fails (usually due to + // a device reset). + if(mRTTexHandle.lock()) + mRTTexHandle.unlock(); +} + +void GFXGeneralFence::_onTextureEvent( GFXTexCallbackCode code ) +{ + switch( code ) + { + case GFXZombify: + mRTTexHandle = NULL; + break; + + case GFXResurrect: + mRTTexHandle.set( 2, 2, GFXFormatR8G8B8X8, &GFXDefaultRenderTargetProfile, avar("%s() - GFXGeneralFence->mRTTexHandle (line %d)", __FUNCTION__, __LINE__) ); + break; + } +} + +void GFXGeneralFence::zombify() +{ + mRTTexHandle = NULL; +} + +void GFXGeneralFence::resurrect() +{ + mRTTexHandle.set( 2, 2, GFXFormatR8G8B8X8, &GFXDefaultRenderTargetProfile, avar("%s() - mRTTexHandle (line %d)", __FUNCTION__, __LINE__) ); +} + +const String GFXGeneralFence::describeSelf() const +{ + // We've got nothing + return String(); +} diff --git a/gfx/gfxFence.h b/gfx/gfxFence.h new file mode 100644 index 0000000..3a2ca25 --- /dev/null +++ b/gfx/gfxFence.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXFENCE_H_ +#define _GFXFENCE_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif + + +/// +class GFXFence : public GFXResource +{ +protected: + + GFXDevice *mDevice; + +public: + + /// The states returned by getStatus() + enum FenceStatus + { + Unset, ///< The fence has not been set + Pending, ///< The fence has been set and has not been hit + Processed, ///< The fence has been processed + Unsupported ///< A non-blocking query of fence status is not supported by the implementation + }; + +public: + + GFXFence( GFXDevice *device ) : mDevice( device ) {}; + virtual ~GFXFence(){}; + + /// This method inserts the fence into the command buffer + virtual void issue() = 0; + + // CodeReview: Do we need a remove() [5/10/2007 Pat] + + /// This is a non-blocking call to get the status of the fence + /// @see GFXFence::FenceStatus + virtual FenceStatus getStatus() const = 0; + + /// This method will not return until the fence has been processed by the GPU + virtual void block() = 0; +}; + + +/// +class GFXGeneralFence : public GFXFence +{ +protected: + + bool mInitialized; + GFXTextureTargetRef mRenderTarget; + GFXTexHandle mRTTexHandle; + GFXStateBlockRef mRenderSB; + + void _init(); + + /// The callback used to get texture events. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ); + +public: + + GFXGeneralFence( GFXDevice *device ) + : GFXFence( device ), + mInitialized( false ) + { + } + + virtual ~GFXGeneralFence(); + + virtual void issue(); + virtual FenceStatus getStatus() const { return GFXFence::Unsupported; }; + virtual void block(); + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const; +}; + +#endif // _GFXFENCE_H_ \ No newline at end of file diff --git a/gfx/gfxFontRenderBatcher.cpp b/gfx/gfxFontRenderBatcher.cpp new file mode 100644 index 0000000..1d32b2e --- /dev/null +++ b/gfx/gfxFontRenderBatcher.cpp @@ -0,0 +1,220 @@ +#include "gfx/gfxFontRenderBatcher.h" +#include "gfx/gFont.h" + +FontRenderBatcher::FontRenderBatcher() : mStorage(8096) +{ + if (!mFontSB) + { + GFXStateBlockDesc f; + f.zDefined = true; + f.zEnable = false; + f.zWriteEnable = false; + f.cullDefined = true; + f.cullMode = GFXCullNone; + f.blendDefined = true; + f.blendEnable = true; + f.blendSrc = GFXBlendSrcAlpha; + f.blendDest = GFXBlendInvSrcAlpha; + f.samplersDefined = true; + f.samplers[0].alphaOp = GFXTOPModulate; + f.samplers[0].magFilter = GFXTextureFilterPoint; + f.samplers[0].minFilter = GFXTextureFilterPoint; + f.samplers[0].addressModeU = GFXAddressClamp; + f.samplers[0].addressModeV = GFXAddressClamp; + f.samplers[0].alphaArg1 = GFXTATexture; + f.samplers[0].alphaArg2 = GFXTADiffuse; + // This is an add operation because in D3D, when a texture of format D3DFMT_A8 + // is used, the RGB channels are all set to 0. Therefore a modulate would + // result in the text always being black. This may not be the case in OpenGL + // so it may have to change. -bramage + f.samplers[0].textureColorOp = GFXTOPAdd; + mFontSB = GFX->createStateBlock(f); + } +} + +void FontRenderBatcher::render( F32 rot, const Point2F &offset ) +{ + if( mLength == 0 ) + return; + + GFX->setStateBlock(mFontSB); + GFX->disableShaders(); + for(U32 i = 0; i < GFX->getNumSamplers(); i++) + GFX->setTexture(i, NULL); + + MatrixF rotMatrix; + + bool doRotation = rot != 0.f; + if(doRotation) + rotMatrix.set( EulerF( 0.0, 0.0, mDegToRad( rot ) ) ); + + // Write verts out. + U32 currentPt = 0; + GFXVertexBufferHandle verts(GFX, mLength * 6, GFXBufferTypeVolatile); + verts.lock(); + + for( S32 i = 0; i < mSheets.size(); i++ ) + { + // Do some early outs... + if(!mSheets[i]) + continue; + + if(!mSheets[i]->numChars) + continue; + + mSheets[i]->startVertex = currentPt; + const GFXTextureObject *tex = mFont->getTextureHandle(i); + + for( S32 j = 0; j < mSheets[i]->numChars; j++ ) + { + // Get some general info to proceed with... + const CharMarker &m = mSheets[i]->charIndex[j]; + const PlatformFont::CharInfo &ci = mFont->getCharInfo( m.c ); + + // Where are we drawing it? + F32 drawY = offset.y + mFont->getBaseline() - ci.yOrigin * TEXT_MAG; + F32 drawX = offset.x + m.x + ci.xOrigin; + + // Figure some values. + const F32 texWidth = (F32)tex->getWidth(); + const F32 texHeight = (F32)tex->getHeight(); + const F32 texLeft = (F32)(ci.xOffset) / texWidth; + const F32 texRight = (F32)(ci.xOffset + ci.width) / texWidth; + const F32 texTop = (F32)(ci.yOffset) / texHeight; + const F32 texBottom = (F32)(ci.yOffset + ci.height) / texHeight; + + const F32 fillConventionOffset = GFX->getFillConventionOffset(); + const F32 screenLeft = drawX - fillConventionOffset; + const F32 screenRight = drawX - fillConventionOffset + ci.width * TEXT_MAG; + const F32 screenTop = drawY - fillConventionOffset; + const F32 screenBottom = drawY - fillConventionOffset + ci.height * TEXT_MAG; + + // Build our vertices. We NEVER read back from the buffer, that's + // incredibly slow, so for rotation do it into tmp. This code is + // ugly as sin. + Point3F tmp; + + tmp.set( screenLeft, screenTop, 0.f ); + if(doRotation) + rotMatrix.mulP( tmp, &verts[currentPt].point); + else + verts[currentPt].point = tmp; + verts[currentPt].color = m.color; + verts[currentPt].texCoord.set( texLeft, texTop ); + currentPt++; + + tmp.set( screenLeft, screenBottom, 0.f ); + if(doRotation) + rotMatrix.mulP( tmp, &verts[currentPt].point); + else + verts[currentPt].point = tmp; + verts[currentPt].color = m.color; + verts[currentPt].texCoord.set( texLeft, texBottom ); + currentPt++; + + tmp.set( screenRight, screenBottom, 0.f ); + if(doRotation) + rotMatrix.mulP( tmp, &verts[currentPt].point); + else + verts[currentPt].point = tmp; + verts[currentPt].color = m.color; + verts[currentPt].texCoord.set( texRight, texBottom ); + currentPt++; + + tmp.set( screenRight, screenBottom, 0.f ); + if(doRotation) + rotMatrix.mulP( tmp, &verts[currentPt].point); + else + verts[currentPt].point = tmp; + verts[currentPt].color = m.color; + verts[currentPt].texCoord.set( texRight, texBottom ); + currentPt++; + + tmp.set( screenRight, screenTop, 0.f ); + if(doRotation) + rotMatrix.mulP( tmp, &verts[currentPt].point); + else + verts[currentPt].point = tmp; + verts[currentPt].color = m.color; + verts[currentPt].texCoord.set( texRight, texTop ); + currentPt++; + + tmp.set( screenLeft, screenTop, 0.f ); + if(doRotation) + rotMatrix.mulP( tmp, &verts[currentPt].point); + else + verts[currentPt].point = tmp; + verts[currentPt].color = m.color; + verts[currentPt].texCoord.set( texLeft, texTop ); + currentPt++; + } + } + + verts->unlock(); + + AssertFatal(currentPt <= mLength * 6, "FontRenderBatcher::render - too many verts for length of string!"); + + GFX->setVertexBuffer(verts); + + // Now do an optimal render! + for( S32 i = 0; i < mSheets.size(); i++ ) + { + if(!mSheets[i]) + continue; + + if(!mSheets[i]->numChars ) + continue; + + GFX->setupGenericShaders( GFXDevice::GSAddColorTexture ); + GFX->setTexture( 0, mFont->getTextureHandle(i) ); + GFX->drawPrimitive(GFXTriangleList, mSheets[i]->startVertex, mSheets[i]->numChars * 2); + } +} + +void FontRenderBatcher::queueChar( UTF16 c, S32 ¤tX, GFXVertexColor ¤tColor ) +{ + const PlatformFont::CharInfo &ci = mFont->getCharInfo( c ); + U32 sidx = ci.bitmapIndex; + + if( ci.width != 0 && ci.height != 0 ) + { + SheetMarker &sm = getSheetMarker(sidx); + + CharMarker &m = sm.charIndex[sm.numChars]; + sm.numChars++; + + m.c = c; + m.x = (F32)currentX; + m.color = currentColor; + } + + currentX += ci.xIncrement; +} + +FontRenderBatcher::SheetMarker & FontRenderBatcher::getSheetMarker( U32 sheetID ) +{ + // Allocate if it doesn't exist... + if(mSheets.size() <= sheetID || !mSheets[sheetID]) + { + if(sheetID >= mSheets.size()) + mSheets.setSize(sheetID+1); + + S32 size = sizeof( SheetMarker) + mLength * sizeof( CharMarker ); + mSheets[sheetID] = (SheetMarker *)mStorage.alloc(size); + mSheets[sheetID]->numChars = 0; + mSheets[sheetID]->startVertex = 0; // cosmetic initialization + } + + return *mSheets[sheetID]; +} + +void FontRenderBatcher::init( GFont *font, U32 n ) +{ + // Clear out batched results + dMemset(mSheets.address(), 0, mSheets.memSize()); + mSheets.clear(); + mStorage.freeBlocks(true); + + mFont = font; + mLength = n; +} \ No newline at end of file diff --git a/gfx/gfxFontRenderBatcher.h b/gfx/gfxFontRenderBatcher.h new file mode 100644 index 0000000..4d34149 --- /dev/null +++ b/gfx/gfxFontRenderBatcher.h @@ -0,0 +1,45 @@ +#ifndef _GFXFONTBATCHER_H_ +#define _GFXFONTBATCHER_H_ + +#include "core/dataChunker.h" +#include "gfx/gfxDevice.h" +#include "gfx/gFont.h" + +#define TEXT_MAG 1 + +class FontRenderBatcher +{ + struct CharMarker + { + S32 c; + F32 x; + GFXVertexColor color; + PlatformFont::CharInfo *ci; + }; + + struct SheetMarker + { + S32 numChars; + S32 startVertex; + CharMarker charIndex[1]; + }; + + DataChunker mStorage; + Vector mSheets; + GFont *mFont; + U32 mLength; + GFXStateBlockRef mFontSB; + + SheetMarker &getSheetMarker(U32 sheetID); + +public: + FontRenderBatcher(); + + void init(GFont *font, U32 n); + + void queueChar(UTF16 c, S32 ¤tX, GFXVertexColor ¤tColor); + + void render(F32 rot, const Point2F &offset ); +}; + +#endif \ No newline at end of file diff --git a/gfx/gfxFormatUtils.cpp b/gfx/gfxFormatUtils.cpp new file mode 100644 index 0000000..7428809 --- /dev/null +++ b/gfx/gfxFormatUtils.cpp @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gfxFormatUtils.h" +#include "gfx/gfxDevice.h" + + +//RDTODO: complete format infos + + +//----------------------------------------------------------------------------- + +GFXFormatInfo::Data GFXFormatInfo::smFormatInfos[ GFXFormat_COUNT ] = +{ + // 8 bit texture formats... + GFXFormatInfo::Data( 1, false, false, false ), // GFXFormatA8 + GFXFormatInfo::Data( 1, false, false, false ), // GFXFormatL8 + + // 16 bit texture formats... + GFXFormatInfo::Data( 2, false, false, false ), // GFXFormatR5G6B5 + GFXFormatInfo::Data( 2, true, false, false ), // GFXFormatR5G5B5A1 + GFXFormatInfo::Data( 2, false, false, false ), // GFXFormatR5G5B5X1 + GFXFormatInfo::Data( 2, false, false, false ), // GFXFormatL16 + GFXFormatInfo::Data( 2, false, false, false ), // GFXFormatR16F + GFXFormatInfo::Data( 2, false, false, false ), // GFXFormatD16 + + // 24 bit texture formats... + GFXFormatInfo::Data( 3, false, false, false ), // GFXFormatR8G8B8 + + // 32 bit texture formats... + GFXFormatInfo::Data( 4, true, false, false ), // GFXFormatR8G8B8A8 + GFXFormatInfo::Data( 4, false, false, false ), // GFXFormatR8G8B8X8 + GFXFormatInfo::Data( 4, false, false, false ), // GFXFormatR32F + GFXFormatInfo::Data( 4, false, false, false ), // GFXFormatR16G16 + GFXFormatInfo::Data( 4, false, false, true ), // GFXFormatR16G16F + GFXFormatInfo::Data( 4, true, false, false ), // GFXFormatR10G10B10A2 + GFXFormatInfo::Data( 4, false, false, false ), // GFXFormatD32 + GFXFormatInfo::Data( 4, false, false, false ), // GFXFormatD24X8 + GFXFormatInfo::Data( 4, false, false, false ), // GFXFormatD24S8 + GFXFormatInfo::Data( 4, false, false, false ), // GFXFormatD24FS8 + + // 64 bit texture formats... + GFXFormatInfo::Data( 8, true, false, false ), // GFXFormatR16G16B16A16 + GFXFormatInfo::Data( 8, true, false, true ), // GFXFormatR16G16B16A16F + + // 128 bit texture formats... + GFXFormatInfo::Data( 16, true, false, true ), // GFXFormatR32G32B32A32F + + // Compressed formats... + GFXFormatInfo::Data( 0, false, true, false ), // GFXFormatDXT1 + GFXFormatInfo::Data( 0, true, true, false ), // GFXFormatDXT2 + GFXFormatInfo::Data( 0, true, true, false ), // GFXFormatDXT3 + GFXFormatInfo::Data( 0, true, true, false ), // GFXFormatDXT4 + GFXFormatInfo::Data( 0, true, true, false ), // GFXFormatDXT5 +}; + +//----------------------------------------------------------------------------- + +void GFXCopyPixels( GFXFormat fromFormat, U32 fromWidth, U32 fromHeight, U8* fromData, + GFXFormat toFormat, U32 toWidth, U32 toHeight, U8* toData ) +{ + if( fromFormat == toFormat + && fromWidth == toWidth + && fromHeight == fromHeight ) + dMemcpy( toData, fromData, fromWidth * fromHeight * GFXFormatInfo( fromFormat ).getBytesPerPixel() ); + else + { + AssertFatal( false, "Not implemented" ); + } +} diff --git a/gfx/gfxFormatUtils.h b/gfx/gfxFormatUtils.h new file mode 100644 index 0000000..0b3b233 --- /dev/null +++ b/gfx/gfxFormatUtils.h @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXFORMATUTILS_H_ +#define _GFXFORMATUTILS_H_ + +#ifndef _PLATFORM_H_ + #include "platform/platform.h" +#endif +#ifndef _GFXENUMS_H_ + #include "gfx/gfxEnums.h" +#endif +#ifndef _COLOR_H_ + #include "core/color.h" +#endif + + +//WIP: still in early stages + + + +/// Some information about a GFXFormat. +struct GFXFormatInfo +{ + protected: + + struct Data + { + /// Bytes per single pixel. + U32 mBytesPerPixel; + + /// If true, format has alpha channel. + bool mHasAlpha; + + /// If true, format uses compression. + bool mIsCompressed; + + /// If true, channels are in floating-point. + bool mIsFloatingPoint; + + Data() {} + Data( U32 bpp, bool hasAlpha = false, bool isCompressed = false, bool isFP = false ) + : mBytesPerPixel( bpp ), + mHasAlpha( hasAlpha ), + mIsCompressed( isCompressed ), + mIsFloatingPoint( isFP ) {} + }; + + GFXFormat mFormat; + + static Data smFormatInfos[ GFXFormat_COUNT ]; + + public: + + GFXFormatInfo( GFXFormat format ) + : mFormat( format ) {} + + /// @return the number of bytes per pixel in this format. + /// @note For compressed formats that can't give a fixed value per pixel, + /// this will be zero. + U32 getBytesPerPixel() const { return smFormatInfos[ mFormat ].mBytesPerPixel; } + + /// @return true if the format has an alpha channel. + bool hasAlpha() const { return smFormatInfos[ mFormat ].mHasAlpha; } + + /// @return true if format uses compression. + bool isCompressed() const { return smFormatInfos[ mFormat ].mIsCompressed; } + + /// @return true if channels are stored in floating-point format. + bool isFloatingPoint() const { return smFormatInfos[ mFormat ].mIsFloatingPoint; } +}; + + +#if 0 +/// +extern void GFXCopyPixels( GFXFormat fromFormat, U32 fromWidth, U32 fromHeight, U8* fromData, + GFXFormat toFormat, U32 toWidth, U32 toHeight, U8* toData ); +#endif + + +inline void GFXPackPixel( GFXFormat format, U8*& ptr, U8 red, U8 green, U8 blue, U8 alpha, bool leastSignficantFirst = true ) +{ + switch( format ) + { + case GFXFormatR8G8B8A8: + if( leastSignficantFirst ) + { + ptr[ 0 ] = blue; + ptr[ 1 ] = green; + ptr[ 2 ] = red; + ptr[ 3 ] = alpha; + } + else + { + ptr[ 0 ] = red; + ptr[ 1 ] = green; + ptr[ 2 ] = blue; + ptr[ 3 ] = alpha; + } + ptr += 4; + break; + + case GFXFormatR8G8B8: + if( leastSignficantFirst ) + { + ptr[ 0 ] = blue; + ptr[ 1 ] = green; + ptr[ 2 ] = red; + } + else + { + ptr[ 0 ] = red; + ptr[ 1 ] = green; + ptr[ 2 ] = blue; + } + ptr += 3; + break; + + default: + AssertISV( false, "GFXPackPixel() - pixel format not implemented." ); + } +} + +#endif // _GFXFORMATUTILS_H_ diff --git a/gfx/gfxInit.cpp b/gfx/gfxInit.cpp new file mode 100644 index 0000000..8c32205 --- /dev/null +++ b/gfx/gfxInit.cpp @@ -0,0 +1,460 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "gfx/gfxInit.h" +#include "console/console.h" +#include "windowManager/platformWindowMgr.h" + +Vector GFXInit::smAdapters( __FILE__, __LINE__ ); +GFXInit::RegisterDeviceSignal* GFXInit::smRegisterDeviceSignal; + +//----------------------------------------------------------------------------- + +inline static void _GFXInitReportAdapters(Vector &adapters) +{ + for (U32 i = 0; i < adapters.size(); i++) + { + switch (adapters[i]->mType) + { + case Direct3D9: + Con::printf("Direct 3D (version 9.x) device found"); + break; + case OpenGL: + Con::printf("OpenGL device found"); + break; + case NullDevice: + Con::printf("Null device found"); + break; + case Direct3D8: + Con::printf("Direct 3D (version 8.1) device found"); + break; + default : + Con::printf("Unknown device found"); + break; + } + } +} + +inline static void _GFXInitGetInitialRes(GFXVideoMode &vm, const Point2I &initialSize) +{ + const U32 kDefaultWindowSizeX = 800; + const U32 kDefaultWindowSizeY = 600; + const bool kDefaultFullscreen = false; + const U32 kDefaultBitDepth = 32; + const U32 kDefaultRefreshRate = 60; + + // cache the desktop size of the main screen + GFXVideoMode desktopVm = GFXInit::getDesktopResolution(); + + // load pref variables, properly choose windowed / fullscreen + const String resString = Con::getVariable("$pref::Video::mode"); + + // Set defaults into the video mode, then have it parse the user string. + vm.resolution.x = kDefaultWindowSizeX; + vm.resolution.y = kDefaultWindowSizeY; + vm.fullScreen = kDefaultFullscreen; + vm.bitDepth = kDefaultBitDepth; + vm.refreshRate = kDefaultRefreshRate; + vm.wideScreen = false; + + vm.parseFromString(resString); +} + +GFXInit::RegisterDeviceSignal& GFXInit::getRegisterDeviceSignal() +{ + if (smRegisterDeviceSignal) + return *smRegisterDeviceSignal; + smRegisterDeviceSignal = new RegisterDeviceSignal(); + return *smRegisterDeviceSignal; +} + +void GFXInit::init() +{ + // init only once. + static bool doneOnce = false; + if(doneOnce) + return; + doneOnce = true; + + Con::printf( "GFX Init:" ); + + //find our adapters + Vector adapters( __FILE__, __LINE__ ); + GFXInit::enumerateAdapters(); + GFXInit::getAdapters(&adapters); + + if(!adapters.size()) + Con::errorf("Could not find a display adapter"); + + //loop through and tell the user what kind of adapters we found + _GFXInitReportAdapters(adapters); + Con::printf( "" ); +} + +void GFXInit::cleanup() +{ + while( smAdapters.size() ) + { + GFXAdapter* adapter = smAdapters.last(); + smAdapters.decrement(); + delete adapter; + } + + if( smRegisterDeviceSignal ) + SAFE_DELETE( smRegisterDeviceSignal ); +} + +GFXAdapter* GFXInit::getAdapterOfType( GFXAdapterType type ) +{ + GFXAdapter* adapter = NULL; + for( U32 i = 0; i < smAdapters.size(); i++ ) + { + if( smAdapters[i]->mType == type ) + { + adapter = smAdapters[i]; + break; + } + } + return adapter; +} + +GFXAdapter* GFXInit::chooseAdapter( GFXAdapterType type) +{ + GFXAdapter* adapter = GFXInit::getAdapterOfType(type); + + if(!adapter && type != OpenGL) + { + Con::errorf("The requested renderer, %s, doesn't seem to be available." + " Trying the default, OpenGL.", getAdapterNameFromType(type)); + adapter = GFXInit::getAdapterOfType(OpenGL); + } + + if(!adapter) + { + Con::errorf("The OpenGL renderer doesn't seem to be available. Trying the GFXNulDevice."); + adapter = GFXInit::getAdapterOfType(NullDevice); + } + + AssertFatal( adapter, "There is no rendering device available whatsoever."); + return adapter; +} + +const char* GFXInit::getAdapterNameFromType(GFXAdapterType type) +{ + static const char* _names[] = { "OpenGL", "D3D9", "D3D8", "NullDevice", "Xenon" }; + + if( type < 0 || type >= GFXAdapterType_Count ) + { + Con::errorf( "GFXInit::getAdapterNameFromType - Invalid renderer type, defaulting to OpenGL" ); + return _names[OpenGL]; + } + + return _names[type]; +} + +GFXAdapterType GFXInit::getAdapterTypeFromName(const char* name) +{ + S32 ret = -1; + for(S32 i = 0; i < GFXAdapterType_Count; i++) + { + if( !dStricmp( getAdapterNameFromType((GFXAdapterType)i), name ) ) + ret = i; + } + + if( ret == -1 ) + { + Con::errorf( "GFXInit::getAdapterTypeFromName - Invalid renderer name, defaulting to D3D9" ); + ret = Direct3D9; + } + + return (GFXAdapterType)ret; +} + +GFXAdapter *GFXInit::getBestAdapterChoice() +{ + // Get the user's preference for device... + const String renderer = Con::getVariable("$pref::Video::displayDevice"); + GFXAdapterType adapterType = getAdapterTypeFromName(renderer); + GFXAdapter *adapter = chooseAdapter(adapterType); + + // Did they have one? Return it. + if(adapter) + return adapter; + + // Didn't have one. So make it up. Find the highest SM available. Prefer + // D3D to GL because if we have a D3D device at all we're on windows, + // and in an unknown situation on Windows D3D is probably the safest bet. + // + // If D3D is unavailable, we're not on windows, so GL is de facto the + // best choice! + F32 highestSM9 = 0.f, highestSMGL = 0.f; + GFXAdapter *foundAdapter8 = NULL, *foundAdapter9 = NULL, + *foundAdapterGL = NULL; + + for(S32 i=0; imType) + { + case Direct3D9: + if(currAdapter->mShaderModel > highestSM9) + { + highestSM9 = currAdapter->mShaderModel; + foundAdapter9 = currAdapter; + } + break; + + case OpenGL: + if(currAdapter->mShaderModel > highestSMGL) + { + highestSMGL = currAdapter->mShaderModel; + foundAdapterGL = currAdapter; + } + break; + + case Direct3D8: + if(!foundAdapter8) + foundAdapter8 = currAdapter; + break; + + default: + break; + } + } + + // Return best found in order DX9, GL, DX8. + if(foundAdapter9) + return foundAdapter9; + + if(foundAdapterGL) + return foundAdapterGL; + + if(foundAdapter8) + return foundAdapter8; + + // Uh oh - we didn't find anything. Grab whatever we can that's not Null... + for(S32 i=0; imType != NullDevice) + return smAdapters[i]; + + // Dare we return a null device? No. Just return NULL. + return NULL; +} + +GFXVideoMode GFXInit::getInitialVideoMode() +{ + GFXVideoMode vm; + _GFXInitGetInitialRes(vm, Point2I(800,600)); + return vm; +} + +S32 GFXInit::getAdapterCount() +{ + return smAdapters.size(); +} + +void GFXInit::getAdapters(Vector *adapters) +{ + adapters->clear(); + for (U32 k = 0; k < smAdapters.size(); k++) + adapters->push_back(smAdapters[k]); +} + +GFXVideoMode GFXInit::getDesktopResolution() +{ + GFXVideoMode resVm; + + // Retrieve Resolution Information. + resVm.bitDepth = WindowManager->getDesktopBitDepth(); + resVm.resolution = WindowManager->getDesktopResolution(); + resVm.fullScreen = false; + resVm.refreshRate = 60; + + // Return results + return resVm; +} + +void GFXInit::enumerateAdapters() +{ + // Call each device class and have it report any adapters it supports. + if(smAdapters.size()) + { + // CodeReview Seems like this is ok to just ignore? [bjg, 5/19/07] + //Con::warnf("GFXInit::enumerateAdapters - already have a populated adapter list, aborting re-analysis."); + return; + } + + getRegisterDeviceSignal().trigger(GFXInit::smAdapters); +} + +GFXDevice *GFXInit::createDevice( GFXAdapter *adapter ) +{ + Con::printf("Attempting to create GFX device: %s", adapter->getName()); + + GFXDevice* temp = adapter->mCreateDeviceInstanceDelegate(adapter->mIndex); + if (temp) + { + Con::printf("Device created, setting adapter and enumerating modes"); + temp->setAdapter(*adapter); + temp->enumerateVideoModes(); + temp->getVideoModeList(); + } + else + Con::errorf("Failed to create GFX device"); + + GFXDevice::getDeviceEventSignal().trigger(GFXDevice::deCreate); + + return temp; +} + +//----------------------------------------------------------------------------- +ConsoleFunction( getDesktopResolution, const char*, 1, 1, "Get the width, height, and bitdepth of the screen.") +{ + GFXVideoMode res = GFXInit::getDesktopResolution(); + + String temp = String::ToString("%d %d %d", res.resolution.x, res.resolution.y, res.bitDepth ); + U32 tempSize = temp.size(); + + char* retBuffer = Con::getReturnBuffer( tempSize ); + dMemcpy( retBuffer, temp, tempSize ); + return retBuffer; +} + +ConsoleStaticMethod( GFXInit, getAdapterCount, S32, 1, 1, "() Return the number of adapters available.") +{ + return GFXInit::getAdapterCount(); +} + +ConsoleStaticMethod( GFXInit, getAdapterName, const char*, 2, 2, "(int id) Returns the name of a given adapter.") +{ + Vector adapters( __FILE__, __LINE__ ); + GFXInit::getAdapters(&adapters); + + S32 idx = dAtoi(argv[1]); + if(idx >= 0 && idx < adapters.size()) + return adapters[idx]->mName; + + Con::errorf("GFXInit::getAdapterName - out of range adapter index."); + return NULL; +} + +ConsoleStaticMethod( GFXInit, getAdapterType, const char*, 2, 2, "(int id) Returns the type (D3D9, D3D8, GL, Null) of a given adapter.") +{ + Vector adapters( __FILE__, __LINE__ ); + GFXInit::getAdapters(&adapters); + + S32 idx = dAtoi(argv[1]); + if(idx >= 0 && idx < adapters.size()) + return GFXInit::getAdapterNameFromType(adapters[idx]->mType); + + Con::errorf("GFXInit::getAdapterType - out of range adapter index."); + return NULL; +} + +ConsoleStaticMethod( GFXInit, getAdapterShaderModel, F32, 2, 2, "(int id) Returns the SM supported by a given adapter.") +{ + Vector adapters( __FILE__, __LINE__ ); + GFXInit::getAdapters(&adapters); + + S32 idx = dAtoi(argv[1]); + + if(idx < 0 || idx >= adapters.size()) + { + Con::errorf("GFXInit::getAdapterShaderModel - out of range adapter index."); + return -1.f; + } + + return adapters[idx]->mShaderModel; +} + +ConsoleStaticMethod( GFXInit, getDefaultAdapterIndex, S32, 1, 1, "() Returns the index of the adapter we'll be starting up with.") +{ + // Get the chosen adapter, and locate it in the list. (Kind of silly.) + GFXAdapter *a = GFXInit::getBestAdapterChoice(); + + Vector adapters( __FILE__, __LINE__ ); + for(S32 i=0; imIndex == a->mIndex && adapters[i]->mType == a->mType) + return i; + + Con::warnf("GFXInit::getDefaultAdapterIndex - didn't find the chosen adapter in the adapter list!"); + return -1; +} + +ConsoleStaticMethod(GFXInit, getAdapterModeCount, S32, 2, 2, + "(int id)\n" + "Gets the number of modes available on the specified adapter.\n\n" + "\\param id Index of the adapter to get data from.\n" + "\\return (int) The number of video modes supported by the adapter, or -1 if the given adapter was not found.") +{ + Vector adapters( __FILE__, __LINE__ ); + GFXInit::getAdapters(&adapters); + + S32 idx = dAtoi(argv[1]); + + if(idx < 0 || idx >= adapters.size()) + { + Con::errorf("GFXInit::getAdapterModeCount - You specified an out of range adapter index of %d. Please specify an index in the range [0, %d).", idx, idx >= adapters.size()); + return -1; + } + + return adapters[idx]->mAvailableModes.size(); +} + +ConsoleStaticMethod(GFXInit, getAdapterMode, const char*, 3, 3, + "(int id, int modeId)\n" + "Gets information on the specified adapter and mode.\n\n" + "\\param id Index of the adapter to get data from.\n" + "\\param modeId Index of the mode to get data from.\n" + "\\return (string) A video mode string given an adapter and mode index. See GuiCanvas.getVideoMode()") +{ + Vector adapters( __FILE__, __LINE__ ); + GFXInit::getAdapters(&adapters); + + S32 adapIdx = dAtoi(argv[1]); + if((adapIdx < 0) || (adapIdx >= adapters.size())) + { + Con::errorf("GFXInit::getAdapterMode - You specified an out of range adapter index of %d. Please specify an index in the range [0, %d).", adapIdx, adapIdx >= adapters.size()); + return NULL; + } + + S32 modeIdx = dAtoi(argv[2]); + if((modeIdx < 0) || (modeIdx >= adapters[adapIdx]->mAvailableModes.size())) + { + Con::errorf("GFXInit::getAdapterMode - You requested an out of range mode index of %d. Please specify an index in the range [0, %d).", modeIdx, adapters[adapIdx]->mAvailableModes.size()); + return NULL; + } + + GFXVideoMode vm = adapters[adapIdx]->mAvailableModes[modeIdx]; + + // Format and return to console. + + String vmString = vm.toString(); + U32 bufferSize = vmString.size(); + char* buffer = Con::getReturnBuffer( bufferSize ); + dMemcpy( buffer, vmString, bufferSize ); + + return buffer; +} + +ConsoleStaticMethod( GFXInit, createNullDevice, void, 1, 1, "() Create a NULL device") +{ + // Enumerate things for GFX before we have an active device. + GFXInit::enumerateAdapters(); + + // Create a device. + GFXAdapter *a = GFXInit::chooseAdapter(NullDevice); + + GFXDevice *newDevice = GFX; + + // Do we have a global device already? (This is the site if you want + // to start rendering to multiple devices simultaneously) + if(newDevice == NULL) + newDevice = GFXInit::createDevice(a); + + newDevice->setAllowRender( false ); + + return; +} diff --git a/gfx/gfxInit.h b/gfx/gfxInit.h new file mode 100644 index 0000000..ffffbdc --- /dev/null +++ b/gfx/gfxInit.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GFXINIT_H_ +#define _GFXINIT_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif + +/// Interface for tracking GFX adapters and initializing them into devices. +/// @note Implement this class per platform. +/// @note This is just a class so it can be friends with GFXDevice) +class GFXInit +{ +public: + /// Allows device to register themselves as available + typedef Signal&)> RegisterDeviceSignal; + static RegisterDeviceSignal& getRegisterDeviceSignal(); + + /// Prepares the adapter list. + static void init(); + + /// Cleans out the adapter list. + static void cleanup(); + + /// Creates a GFXDevice based on an adapter from the + /// enumerateAdapters method. + /// + /// @param adapter Graphics adapter to create device + static GFXDevice *createDevice( GFXAdapter *adapter ); + + /// Enumerate all the graphics adapters on the system + static void enumerateAdapters(); + + /// Get the enumerated adapters. Should only call this after + /// a call to enumerateAdapters. + static void getAdapters( Vector *adapters ); + + /// Get the number of available adapters. + static S32 getAdapterCount(); + + /// Chooses a suitable GFXAdapter, based on type, preferences, and fallbacks. + /// If the requested type is omitted, we use the prefs value. + /// If the requested type isn't found, we use fallbacks: OpenGL, NullDevice + /// This method never returns NULL. + static GFXAdapter *chooseAdapter( GFXAdapterType type); + + /// Gets the first adapter of the requested type from the list of enumerated + /// adapters. Should only call this after a call to enumerateAdapters. + static GFXAdapter *getAdapterOfType( GFXAdapterType type ); + + /// Converts a GFXAdapterType to a string name. Useful for writing out prefs + static const char *getAdapterNameFromType( GFXAdapterType type ); + + /// Converts a string to a GFXAdapterType. Useful for reading in prefs. + static GFXAdapterType getAdapterTypeFromName( const char* name ); + + /// Returns a GFXVideoMode that describes the current state of the main monitor. + /// This should probably move to the abstract window manager + static GFXVideoMode getDesktopResolution(); + + /// Based on user preferences (or in the absence of a valid user selection, + /// a heuristic), return a "best" adapter. + static GFXAdapter *getBestAdapterChoice(); + + /// Get the initial video mode based on user preferences (or a heuristic). + static GFXVideoMode getInitialVideoMode(); +private: + /// List of known adapters. + static Vector smAdapters; + + static RegisterDeviceSignal* smRegisterDeviceSignal; +}; + +#endif diff --git a/gfx/gfxOcclusionQuery.cpp b/gfx/gfxOcclusionQuery.cpp new file mode 100644 index 0000000..d7ca1d8 --- /dev/null +++ b/gfx/gfxOcclusionQuery.cpp @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxOcclusionQuery.h" + +String GFXOcclusionQuery::statusToString( OcclusionQueryStatus status ) +{ + String outStr; + if ( status == GFXOcclusionQuery::Unset ) + outStr = "Unset"; + else if ( status == GFXOcclusionQuery::Waiting ) + outStr = "Waiting"; + else if ( status == GFXOcclusionQuery::Occluded ) + outStr = "Occluded"; + else if ( status == GFXOcclusionQuery::NotOccluded ) + outStr = "Visible"; + else if ( status == GFXOcclusionQuery::Error ) + outStr = "Error"; + else + outStr = "Unknown"; + + return outStr; +} diff --git a/gfx/gfxOcclusionQuery.h b/gfx/gfxOcclusionQuery.h new file mode 100644 index 0000000..c0b13cb --- /dev/null +++ b/gfx/gfxOcclusionQuery.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXOCCLUSIONQUERY_H_ +#define _GFXOCCLUSIONQUERY_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif + + +/// A geometry visibility query object. +/// @see GFXDevice::createOcclusionQuery +class GFXOcclusionQuery : public GFXResource +{ +protected: + + GFXDevice *mDevice; + + GFXOcclusionQuery( GFXDevice *device ) + : mDevice( device ) + { + } + +public: + + /// The states returned by getStatus() + /// If you modify this enum you should also modify statusToString() + enum OcclusionQueryStatus + { + Unset, ///< + Waiting, ///< + Error, ///< + Occluded, + NotOccluded + }; + + virtual ~GFXOcclusionQuery() {} + + /// Prepares the query returning true if the last query + /// has been processed and more geometry can be issued. + /// @see getStatus + virtual bool begin() = 0; + + /// Called after your geometry is drawn to submit + /// the query for processing. + virtual void end() = 0; + + /// Returns the status of the last submitted query. In general + /// you should avoid blocking for the result until the frame + /// following your query to keep from stalling the CPU. + /// @return Status + /// @param block If true CPU will block until the query finishes. + /// @param data Number of pixels rendered, valid only if status returned is NotOccluded. + virtual OcclusionQueryStatus getStatus( bool block, U32 *data = NULL ) = 0; + + /// Returns a status string. + static String statusToString( OcclusionQueryStatus status ); + + // GFXResource + virtual void zombify() = 0; + virtual void resurrect() = 0; + virtual const String describeSelf() const = 0; +}; + +#endif // _GFXOCCLUSIONQUERY_H_ \ No newline at end of file diff --git a/gfx/gfxPrimitiveBuffer.cpp b/gfx/gfxPrimitiveBuffer.cpp new file mode 100644 index 0000000..f989716 --- /dev/null +++ b/gfx/gfxPrimitiveBuffer.cpp @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "console/console.h" + +#include "gfx/gfxDevice.h" +#include "gfx/gfxPrimitiveBuffer.h" + +//----------------------------------------------------------------------------- +#ifdef TORQUE_DEBUG +GFXPrimitiveBuffer *GFXPrimitiveBuffer::smHead = NULL; +U32 GFXPrimitiveBuffer::smActivePBCount = 0; + +void GFXPrimitiveBuffer::dumpActivePBs() +{ + if(!smActivePBCount) + { + Con::printf("GFXPrimitiveBuffer::dumpActivePBs - no currently active PBs to dump. You are A-OK!"); + return; + } + + Con::printf("GFXPrimitiveBuffer Usage Report - %d active PBs", smActivePBCount); + Con::printf("---------------------------------------------------------------"); + Con::printf(" Addr #idx #prims Profiler Path RefCount"); + for(GFXPrimitiveBuffer *walk = smHead; walk; walk=walk->mDebugNext) + { +#if defined(TORQUE_ENABLE_PROFILER) + Con::printf(" %x %6d %6d %s %d", walk, walk->mIndexCount, walk->mPrimitiveCount, walk->mDebugCreationPath.c_str(), walk->getRefCount()); +#else + Con::printf(" %x %6d %6d %s %d", walk, walk->mIndexCount, walk->mPrimitiveCount, "", walk->getRefCount()); +#endif + } + Con::printf("----- dump complete -------------------------------------------"); + AssertFatal(false, "There is a primitive buffer leak, check the log for more details."); +} + +#endif + +/// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer +const String GFXPrimitiveBuffer::describeSelf() const +{ +#if defined(TORQUE_DEBUG) && defined(TORQUE_ENABLE_PROFILER) + return String::ToString("indexCount: %6d primCount: %6d refCount: %d path: %s", + mIndexCount, mPrimitiveCount, getRefCount(), mDebugCreationPath.c_str()); +#else + return String::ToString("indexCount: %6d primCount: %6d refCount: %d path: %s", + mIndexCount, mPrimitiveCount, getRefCount(), ""); +#endif +} + +//----------------------------------------------------------------------------- +// Set +//----------------------------------------------------------------------------- +void GFXPrimitiveBufferHandle::set(GFXDevice *theDevice, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType, String desc) +{ + StrongRefPtr::operator=( theDevice->allocPrimitiveBuffer(indexCount, primitiveCount, bufferType) ); + +#ifdef TORQUE_DEBUG + if( desc.isNotEmpty() ) + getPointer()->mDebugCreationPath = desc; +#endif +} diff --git a/gfx/gfxPrimitiveBuffer.h b/gfx/gfxPrimitiveBuffer.h new file mode 100644 index 0000000..90a12d0 --- /dev/null +++ b/gfx/gfxPrimitiveBuffer.h @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXPRIMITIVEBUFFER_H_ +#define _GFXPRIMITIVEBUFFER_H_ + +#ifndef _GFXSTRUCTS_H_ +#include "gfx/gfxStructs.h" +#endif + +#ifdef TORQUE_ENABLE_PROFILER +#include "platform/profiler.h" +#endif + + +class GFXPrimitiveBuffer : public StrongRefBase, public GFXResource +{ + friend class GFXPrimitiveBufferHandle; + friend class GFXDevice; +public: //protected: + U32 mIndexCount; + U32 mPrimitiveCount; + GFXBufferType mBufferType; + GFXPrimitive *mPrimitiveArray; + GFXDevice *mDevice; + +#ifdef TORQUE_DEBUG + // In debug builds we provide a TOC leak tracking system. + static U32 smActivePBCount; + static GFXPrimitiveBuffer *smHead; + static void dumpActivePBs(); + + String mDebugCreationPath; + GFXPrimitiveBuffer *mDebugNext; + GFXPrimitiveBuffer *mDebugPrev; +#endif + + GFXPrimitiveBuffer(GFXDevice *device, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType) + { + mDevice = device; + mIndexCount = indexCount; + mPrimitiveCount = primitiveCount; + mBufferType = bufferType; + if(primitiveCount) + { + mPrimitiveArray = new GFXPrimitive[primitiveCount]; + dMemset((void *) mPrimitiveArray, 0, primitiveCount * sizeof(GFXPrimitive)); + } + else + mPrimitiveArray = NULL; + +#if defined(TORQUE_DEBUG) + // Active copy tracking. + smActivePBCount++; +#if defined(TORQUE_ENABLE_PROFILER) + mDebugCreationPath = gProfiler->getProfilePath(); +#endif + mDebugNext = smHead; + mDebugPrev = NULL; + + if(smHead) + { + AssertFatal(smHead->mDebugPrev == NULL, "GFXPrimitiveBuffer::GFXPrimitiveBuffer - found unexpected previous in current head!"); + smHead->mDebugPrev = this; + } + + smHead = this; +#endif + } + + virtual ~GFXPrimitiveBuffer() + { + if( mPrimitiveArray != NULL ) + { + delete [] mPrimitiveArray; + mPrimitiveArray = NULL; + } + +#ifdef TORQUE_DEBUG + if(smHead == this) + smHead = this->mDebugNext; + + if(mDebugNext) + mDebugNext->mDebugPrev = mDebugPrev; + + if(mDebugPrev) + mDebugPrev->mDebugNext = mDebugNext; + + mDebugPrev = mDebugNext = NULL; + + smActivePBCount--; +#endif + } + + virtual void lock(U16 indexStart, U16 indexEnd, U16 **indexPtr)=0; ///< locks this primitive buffer for writing into + virtual void unlock()=0; ///< unlocks this primitive buffer. + virtual void prepare()=0; ///< prepares this primitive buffer for use on the device it was allocated on + + // GFXResource interface + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const; +}; + +class GFXPrimitiveBufferHandle : public StrongRefPtr +{ + typedef StrongRefPtr Parent; +public: + enum Constants { + MaxIndexCount = 65535, + }; + GFXPrimitiveBufferHandle() {}; + GFXPrimitiveBufferHandle(GFXDevice *theDevice, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType, String desc = String()) + { + set(theDevice, indexCount, primitiveCount, bufferType, desc); + } + void set(GFXDevice *theDevice, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType, String desc = String()); + void lock(U16 **indexBuffer, GFXPrimitive **primitiveBuffer = NULL, U32 indexStart = 0, U32 indexEnd = 0) + { + if(indexEnd == 0) + indexEnd = getPointer()->mIndexCount; + AssertFatal(indexStart < indexEnd && indexEnd <= getPointer()->mIndexCount, "Out of range index lock!"); + getPointer()->lock(indexStart, indexEnd, indexBuffer); + if(primitiveBuffer) + *primitiveBuffer = getPointer()->mPrimitiveArray; + } + void unlock() + { + getPointer()->unlock(); + } + void prepare() + { + getPointer()->prepare(); + } + bool operator==(const GFXPrimitiveBufferHandle &buffer) const { + return getPointer() == buffer.getPointer(); + } + GFXPrimitiveBufferHandle& operator=(GFXPrimitiveBuffer *ptr) + { + StrongObjectRef::set(ptr); + return *this; + } +}; + +#endif diff --git a/gfx/gfxResource.cpp b/gfx/gfxResource.cpp new file mode 100644 index 0000000..1b135c8 --- /dev/null +++ b/gfx/gfxResource.cpp @@ -0,0 +1,38 @@ +#include "gfx/gfxResource.h" +#include "gfx/gfxDevice.h" + +GFXResource::GFXResource() +{ + mPrevResource = mNextResource = NULL; + mOwningDevice = NULL; + mFlagged = false; +} + +GFXResource::~GFXResource() +{ + // Make sure we're not the head of the list and referencd on the device. + if(mOwningDevice && mOwningDevice->mResourceListHead == this) + { + AssertFatal(mPrevResource == NULL, + "GFXResource::~GFXResource - head of list but have a previous item!"); + mOwningDevice->mResourceListHead = mNextResource; + } + + // Unlink ourselves from the list. + if(mPrevResource) + mPrevResource->mNextResource = mNextResource; + if(mNextResource) + mNextResource->mPrevResource = mPrevResource; + + mPrevResource = mNextResource = NULL; +} + +void GFXResource::registerResourceWithDevice( GFXDevice *device ) +{ + mOwningDevice = device; + mNextResource = device->mResourceListHead; + device->mResourceListHead = this; + + if(mNextResource) + mNextResource->mPrevResource = this; +} \ No newline at end of file diff --git a/gfx/gfxResource.h b/gfx/gfxResource.h new file mode 100644 index 0000000..657dbd6 --- /dev/null +++ b/gfx/gfxResource.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXRESOURCE_H_ +#define _GFXRESOURCE_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +class GFXDevice; + +/// Mixin for the purpose of tracking GFX resources owned by a GFXDevice. +/// +/// There are many types of resource that are allocated from a GFXDevice that +/// must be participatory in device resets. For instance, all default pool +/// DirectX resources have to be involved when the device resets. Render +/// targets in all APIs need to unbind themselves when resets happen. +/// +/// This system is also handy for accounting purposes. For instance, we may +/// want to traverse all registered VBs, IBs, Textures, or RTs in order to +/// determine what, if any, items are still allocated. This can be used in +/// leak reports, memory usage reports, etc. +class GFXResource +{ +private: + friend class GFXDevice; + + GFXResource *mPrevResource; + GFXResource *mNextResource; + GFXDevice *mOwningDevice; + + /// Helper flag to check new resource allocations + bool mFlagged; + +public: + GFXResource(); + virtual ~GFXResource(); + + /// Registers this resource with the given device + void registerResourceWithDevice(GFXDevice *device); + + /// When called the resource should destroy all device sensitive information (e.g. D3D resources in D3DPOOL_DEFAULT + virtual void zombify()=0; + + /// When called the resource should restore all device sensitive information destroyed by zombify() + virtual void resurrect()=0; + + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const = 0; + + inline GFXResource *getNextResource() const + { + return mNextResource; + } + + inline GFXResource *getPrevResource() const + { + return mPrevResource; + } + + inline GFXDevice *getOwningDevice() const + { + return mOwningDevice; + } + + inline bool isFlagged() + { + return mFlagged; + } + + inline void setFlag() + { + mFlagged = true; + } + + inline void clearFlag() + { + mFlagged = false; + } +}; + +#endif \ No newline at end of file diff --git a/gfx/gfxShader.cpp b/gfx/gfxShader.cpp new file mode 100644 index 0000000..e9a34d2 --- /dev/null +++ b/gfx/gfxShader.cpp @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxShader.h" + +#include "shaderGen/conditionerFeature.h" +#include "core/volume.h" + + +Vector GFXShader::smGlobalMacros; +bool GFXShader::smLogErrors = true; +bool GFXShader::smLogWarnings = true; + + +GFXShader::GFXShader() + : mPixVersion( 0.0f ), + mVertexFormat( NULL ), + mReloadKey( 0 ) +{ +} + +GFXShader::~GFXShader() +{ + Torque::FS::RemoveChangeNotification( mVertexFile, this, &GFXShader::_onFileChanged ); + Torque::FS::RemoveChangeNotification( mPixelFile, this, &GFXShader::_onFileChanged ); +} + +bool GFXShader::init( const Torque::Path &vertFile, + const Torque::Path &pixFile, + F32 pixVersion, + const Vector ¯os ) +{ + // Store the inputs for use in reloading. + mVertexFile = vertFile; + mPixelFile = pixFile; + mPixVersion = pixVersion; + mMacros = macros; + + // Before we compile the shader make sure the + // conditioner features have been updated. + ConditionerFeature::updateConditioners(); + + // Now do the real initialization. + if ( !_init() ) + return false; + + // Add file change notifications for reloads. + Torque::FS::AddChangeNotification( mVertexFile, this, &GFXShader::_onFileChanged ); + Torque::FS::AddChangeNotification( mPixelFile, this, &GFXShader::_onFileChanged ); + + return true; +} + +bool GFXShader::reload() +{ + // Before we compile the shader make sure the + // conditioner features have been updated. + ConditionerFeature::updateConditioners(); + + mReloadKey++; + + // Init does the work. + bool success = _init(); + + // Let anything that cares know that + // this shader has reloaded + mReloadSignal.trigger(); + + return success; +} + +void GFXShader::addGlobalMacro( const String &name, const String &value ) +{ + // Check to see if we already have this macro. + Vector::iterator iter = smGlobalMacros.begin(); + for ( ; iter != smGlobalMacros.end(); iter++ ) + { + if ( iter->name == name ) + { + if ( iter->value != value ) + iter->value = value; + return; + } + } + + // Add a new macro. + smGlobalMacros.increment(); + smGlobalMacros.last().name = name; + smGlobalMacros.last().value = value; +} + +bool GFXShader::removeGlobalMacro( const String &name ) +{ + Vector::iterator iter = smGlobalMacros.begin(); + for ( ; iter != smGlobalMacros.end(); iter++ ) + { + if ( iter->name == name ) + { + smGlobalMacros.erase( iter ); + return true; + } + } + + return false; +} + +void GFXShader::_unlinkBuffer( GFXShaderConstBuffer *buf ) +{ + Vector::iterator iter = mActiveBuffers.begin(); + for ( ; iter != mActiveBuffers.end(); iter++ ) + { + if ( *iter == buf ) + { + mActiveBuffers.erase_fast( iter ); + return; + } + } + + AssertFatal( false, "GFXShader::_unlinkBuffer - buffer was not found?" ); +} + +ConsoleFunction( addGlobalShaderMacro, void, 2, 3, + "Adds a global shader macro which will be merged with the script defined " + "macros on every shader reload. The macro will replace the value of an " + "existing macro of the same name. For the new macro to take effect all " + "the shaders/materials in the system need to be reloaded." ) +{ + String value = String::EmptyString; + if ( argc > 2 ) + value = argv[2]; + + GFXShader::addGlobalMacro( argv[1], value ); +} + +ConsoleFunction( removeGlobalShaderMacro, void, 2, 2, + "Removes an existing global macro by name." ) +{ + GFXShader::removeGlobalMacro( argv[1] ); +} diff --git a/gfx/gfxShader.h b/gfx/gfxShader.h new file mode 100644 index 0000000..deef9ca --- /dev/null +++ b/gfx/gfxShader.h @@ -0,0 +1,290 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXSHADER_H_ +#define _GFXSHADER_H_ + +#ifndef _GFXRESOURCE_H_ +#include "gfx/gfxResource.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _ALIGNEDARRAY_H_ +#include "core/util/tAlignedArray.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif +#ifndef _GFXSTRUCTS_H_ +#include "gfx/gfxStructs.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif +#ifndef _PATH_H_ +#include "core/util/path.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif + +class Point2I; +class Point2F; +class ColorF; +class MatrixF; +class GFXShader; +class GFXVertexFormat; + + +/// Instances of this struct are returned GFXShaderConstBuffer +struct GFXShaderConstDesc +{ +public: + String name; + GFXShaderConstType constType; + U32 arraySize; // > 1 means it is an array! +}; + +/// This is an opaque handle used by GFXShaderConstBuffer clients to set individual shader constants. +/// Derived classes can put whatever info they need into here, these handles are owned by the shader constant buffer +/// (or shader). Client code should not free these. +class GFXShaderConstHandle +{ +public: + + GFXShaderConstHandle() { mValid = false; } + virtual ~GFXShaderConstHandle() {} + + bool isValid() { return mValid; } + + /// Returns the name of the constant handle. + virtual const String& getName() const = 0; + + /// Returns the type of the constant handle. + virtual GFXShaderConstType getType() const = 0; + + virtual U32 getArraySize() const = 0; + + /// Returns -1 if this handle does not point to a Sampler. + virtual S32 getSamplerRegister() const = 0; + +protected: + + bool mValid; +}; + +/// GFXShaderConstBuffer is a collection of string/value pairs that are sent to a shader. +/// Under the hood, the string value pair is mapped to a block of memory that can +/// be blasted to a shader with one call (ideally) +class GFXShaderConstBuffer : public GFXResource, public StrongRefBase +{ +public: + /// Return the shader that created this buffer + virtual GFXShader* getShader() = 0; + + /// @name Set shader constant values + /// @{ + /// Actually set shader constant values + /// @param name Name of the constant, this should be a name contained in the array returned in getShaderConstDesc, + /// if an invalid name is used, its ignored. + virtual void set(GFXShaderConstHandle* handle, const F32 f) = 0; + virtual void set(GFXShaderConstHandle* handle, const Point2F& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const Point3F& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const Point4F& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const PlaneF& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const ColorF& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const S32 f) = 0; + virtual void set(GFXShaderConstHandle* handle, const Point2I& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const Point3I& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const Point4I& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv) = 0; + /// Specify the type of the matrix, only the GFXSCT types ending in NxN are valid + virtual void set(GFXShaderConstHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType = GFXSCT_Float4x4) = 0; + /// Same as above, but in array form. We don't use an AlignedArray here because the packing of non 4x4 arrays will differ more + /// than we can express with an AlignedArray. So the API is responsible for marshaling the data into the format it needs. In practice, + /// that means that 4x4 matrices are going to be quickest (straight memory copy on D3D and GL). Other dimensions will require "interesting" + /// code to handle marshaling. + virtual void set(GFXShaderConstHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType = GFXSCT_Float4x4) = 0; + + /// + /// @name GFXResource interface + /// @{ + + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const = 0; + + /// @} + + /// Called when the shader this buffer references is reloaded. + virtual void onShaderReload( GFXShader *shader ) = 0; +}; + +typedef StrongRefPtr GFXShaderConstBufferRef; + +//************************************************************************** +// Shader +//************************************************************************** +class GFXShader : public StrongRefBase, public GFXResource +{ + friend class GFXShaderConstBuffer; + +protected: + + /// These are system wide shader macros which are + /// merged with shader specific macros at creation. + static Vector smGlobalMacros; + + /// If true the shader errors are spewed to the console. + static bool smLogErrors; + + /// If true the shader warnings are spewed to the console. + static bool smLogWarnings; + + /// The vertex shader file. + Torque::Path mVertexFile; + + /// The pixel shader file. + Torque::Path mPixelFile; + + /// The macros to be passed to the shader. + Vector mMacros; + + /// The pixel version this is compiled for. + F32 mPixVersion; + + /// + String mDescription; + + /// Counter that is incremented each time this shader is reloaded. + U32 mReloadKey; + + Signal mReloadSignal; + + /// Vector of buffers that reference this shader. + /// It is the responsibility of the derived shader class to populate this + /// vector and to notify them when this shader is reloaded. Classes + /// derived from GFXShaderConstBuffer should call _unlinkBuffer from + /// their destructor. + Vector mActiveBuffers; + + /// A protected constructor so it cannot be instantiated. + GFXShader(); + +public: + + // TODO: Make these protected! + const GFXVertexFormat *mVertexFormat; + + /// Adds a global shader macro which will be merged with + /// the script defined macros on every shader reload. + /// + /// The macro will replace the value of an existing macro + /// of the same name. + /// + /// For the new macro to take effect all the shaders/materials + /// in the system need to be reloaded. + /// + /// @see MaterialManager::flushAndReInitInstances + /// @see ShaderData::reloadAll + static void addGlobalMacro( const String &name, const String &value = String::EmptyString ); + + /// Removes an existing global macro by name. + /// @see addGlobalMacro + static bool removeGlobalMacro( const String &name ); + + /// Toggle logging for shader errors. + static void setLogging( bool logErrors, + bool logWarning ) + { + smLogErrors = logErrors; + smLogWarnings = logWarning; + } + + /// The destructor. + virtual ~GFXShader(); + + /// + bool init( const Torque::Path &vertFile, + const Torque::Path &pixFile, + F32 pixVersion, + const Vector ¯os ); + + /// Reloads the shader from disk. + bool reload(); + + Signal getReloadSignal() { return mReloadSignal; } + + /// Allocate a constant buffer + virtual GFXShaderConstBufferRef allocConstBuffer() = 0; + + /// Returns our list of shader constants, the material can get this and just set the constants it knows about + virtual const Vector& getShaderConstDesc() const = 0; + + /// Returns a shader constant handle for the name constant. + /// + /// Since shaders can reload and later have handles that didn't + /// exist originally this will return a handle in an invalid state + /// if the constant doesn't exist at this time. + virtual GFXShaderConstHandle* getShaderConstHandle( const String& name ) = 0; + + /// Returns the alignment value for constType + virtual U32 getAlignmentValue(const GFXShaderConstType constType) const = 0; + + /// Used to store filename and other info used for info dumps + void setDescription( const String &desc ) { mDescription = desc; } + + const GFXVertexFormat* getVertexFormat() const { return mVertexFormat; } + + F32 getPixVersion() const { return mPixVersion; } + + /// Returns a counter which is incremented each time this shader is reloaded. + U32 getReloadKey() const { return mReloadKey; } + + /// Device specific shaders can override this method to return + /// the shader disassembly. + virtual bool getDisassembly( String &outStr ) const { return false; } + + // GFXResource + const String describeSelf() const { return mDescription; } + +protected: + + /// Called when the shader files change on disk. + void _onFileChanged( const Torque::Path &path ) { reload(); } + + /// Internal initialization function overloaded for + /// each GFX device type. + virtual bool _init() = 0; + + /// Buffers call this from their destructor (so we don't have to ref count them). + void _unlinkBuffer( GFXShaderConstBuffer *buf ); +}; + +/// A strong pointer to a reference counted GFXShader. +typedef StrongRefPtr GFXShaderRef; + + +/// A weak pointer to a reference counted GFXShader. +typedef WeakRefPtr GFXShaderWeakRef; + + +#endif // GFXSHADER diff --git a/gfx/gfxStateBlock.cpp b/gfx/gfxStateBlock.cpp new file mode 100644 index 0000000..194f969 --- /dev/null +++ b/gfx/gfxStateBlock.cpp @@ -0,0 +1,320 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (c) GarageGames.Com +//----------------------------------------------------------------------------- +#include "gfx/gfxStateBlock.h" +#include "core/crc.h" +#include "gfx/gfxDevice.h" +#include "core/strings/stringFunctions.h" +#include "gfx/gfxStringEnumTranslate.h" + +/// +/// GFXStateBlock +/// +const String GFXStateBlock::describeSelf() const +{ + return String::ToString("hashvalue: 0x%x", getDesc().getHashValue()); +} + +/// +/// GFXStateBlockDesc +/// +GFXStateBlockDesc::GFXStateBlockDesc() +{ + // Alpha blending + blendDefined = false; + blendEnable = false; + blendSrc = GFXBlendOne; + blendDest = GFXBlendOne; + blendOp = GFXBlendOpAdd; + + // Separate alpha blending + separateAlphaBlendDefined = false; + separateAlphaBlendEnable = false; + separateAlphaBlendSrc = GFXBlendOne; + separateAlphaBlendDest = GFXBlendZero; + separateAlphaBlendOp = GFXBlendOpAdd; + + // Alpha test + alphaDefined = false; + alphaTestEnable = false; + alphaTestRef = 0; + alphaTestFunc = GFXCmpGreaterEqual; + + // Color Writes + colorWriteDefined = false; + colorWriteRed = true; + colorWriteBlue = true; + colorWriteGreen = true; + colorWriteAlpha = true; + + // Rasterizer + cullDefined = false; + cullMode = GFXCullCCW; + + // Depth + zDefined = false; + zEnable = true; + zWriteEnable = true; + zFunc = GFXCmpLessEqual; + zBias = 0; + zSlopeBias = 0; + + // Stencil + stencilDefined = false; + stencilEnable = false; + stencilFailOp = GFXStencilOpKeep; + stencilZFailOp = GFXStencilOpKeep; + stencilPassOp = GFXStencilOpKeep; + stencilFunc = GFXCmpNever; + stencilRef = 0; + stencilMask = 0xFFFFFFFF; + stencilWriteMask = 0xFFFFFFFF; + + // FF lighting + ffLighting = false; + + vertexColorEnable = false; + + fillMode = GFXFillSolid; + + samplersDefined = false; + textureFactor.set( 255, 255, 255, 255 ); +} + +// This method just needs to return a unique value based on its contents. +U32 GFXStateBlockDesc::getHashValue() const +{ + return CRC::calculateCRC(this, sizeof(GFXStateBlockDesc)); +} + +/// Adds data from desc to this description, uses *defined parameters in desc to figure out +/// what blocks of state to actually copy from desc. +void GFXStateBlockDesc::addDesc(const GFXStateBlockDesc& desc) +{ + // Alpha blending + if (desc.blendDefined) + { + blendDefined = true; + blendEnable = desc.blendEnable; + blendSrc = desc.blendSrc; + blendDest = desc.blendDest; + blendOp = desc.blendOp; + } + + // Separate alpha blending + if ( desc.separateAlphaBlendDefined ) + { + separateAlphaBlendDefined = true; + separateAlphaBlendEnable = desc.separateAlphaBlendEnable; + separateAlphaBlendSrc = desc.separateAlphaBlendSrc; + separateAlphaBlendDest = desc.separateAlphaBlendDest; + separateAlphaBlendOp = desc.separateAlphaBlendOp; + } + + // Alpha test + if (desc.alphaDefined) + { + alphaDefined = true; + alphaTestEnable = desc.alphaTestEnable; + alphaTestRef = desc.alphaTestRef; + alphaTestFunc = desc.alphaTestFunc; + } + + // Color Writes + if (desc.colorWriteDefined) + { + colorWriteDefined = true; + colorWriteRed = desc.colorWriteRed; + colorWriteBlue = desc.colorWriteBlue; + colorWriteGreen = desc.colorWriteGreen; + colorWriteAlpha = desc.colorWriteAlpha; + } + + // Rasterizer + if (desc.cullDefined) + { + cullDefined = true; + cullMode = desc.cullMode; + } + + // Depth + if (desc.zDefined) + { + zDefined = true; + zEnable = desc.zEnable; + zWriteEnable = desc.zWriteEnable; + zFunc = desc.zFunc; + zBias = desc.zBias; + zSlopeBias = desc.zSlopeBias; + } + + // Stencil + if (desc.stencilDefined) + { + stencilDefined = true; + stencilEnable = desc.stencilEnable; + stencilFailOp = desc.stencilFailOp; + stencilZFailOp = desc.stencilZFailOp; + stencilPassOp = desc.stencilPassOp; + stencilFunc = desc.stencilFunc; + stencilRef = desc.stencilRef; + stencilMask = desc.stencilMask; + stencilWriteMask = desc.stencilWriteMask; + } + + if (desc.samplersDefined) + { + samplersDefined = true; + for (U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) + { + samplers[i] = desc.samplers[i]; + } + textureFactor = desc.textureFactor; + } + + vertexColorEnable = desc.vertexColorEnable; + fillMode = desc.fillMode; +} + +/// Returns a string that describes the options set (used by GFXStateBlock::describeSelf) +const String GFXStateBlockDesc::describeSelf() const +{ + GFXStringEnumTranslate::init(); + + String ret; + ret = String::ToString("GFXStateBlockDesc hash value: 0x%x\n", getHashValue()); + ret += String::ToString(" AlphaBlend: %d, BlendSrc: %s, BlendDest: %s, BlendOp: %s\n", + blendEnable, GFXStringBlend[blendSrc], GFXStringBlend[blendDest], GFXStringBlendOp[blendOp]); + ret += String::ToString(" SeparateAlphaBlend: %d, SeparateAlphaBlendSrc: %s, SeparateAlphaBlendDest: %s, SeparateAlphaBlendOp: %s\n", + separateAlphaBlendEnable, GFXStringBlend[separateAlphaBlendSrc], GFXStringBlend[separateAlphaBlendDest], GFXStringBlendOp[separateAlphaBlendOp]); + ret += String::ToString(" AlphaTest: %d, AlphaTestFunc: %s, AlphaTestRef: %d\n", + alphaTestEnable, GFXStringCmpFunc[alphaTestFunc], alphaTestRef); + ret += String::ToString(" ColorWrites: r: %d g: %d b: %d a: %d", + colorWriteRed, colorWriteGreen, colorWriteBlue, colorWriteAlpha); + ret += String::ToString(" CullMode: %s\n", GFXStringCullMode[cullMode]); + ret += String::ToString(" ZEnable: %d, ZWriteEnable: %d, ZFunc: %s, ZBias: %f, ZSlopeBias: %f\n", + zEnable, zWriteEnable, GFXStringCmpFunc[zFunc], zBias, zSlopeBias); + ret += String::ToString(" Stencil: %d, StencilFailOp: %s, StencilZFailOp: %s, StencilPassOp: %s, \n stencilFunc: %s, stencilRef: %d, stencilMask: 0x%x, stencilWriteMask: 0x%x\n", + stencilEnable, GFXStringCmpFunc[stencilFailOp], GFXStringCmpFunc[stencilZFailOp], GFXStringCmpFunc[stencilPassOp], + GFXStringCmpFunc[stencilFunc], stencilRef, stencilMask, stencilWriteMask); + ret += String::ToString(" FF Lighting: %d, VertexColors: %d, fillMode: %s", + ffLighting, vertexColorEnable, GFXStringFillMode[fillMode]); + + return ret; +} + +// +// Utility functions +// + +void GFXStateBlockDesc::setCullMode( GFXCullMode m ) +{ + cullDefined = true; + cullMode = m; +} + +void GFXStateBlockDesc::setZReadWrite( bool read, bool write ) +{ + zDefined = true; + zEnable = read; + zWriteEnable = write; +} + +void GFXStateBlockDesc::setAlphaTest( bool enable, GFXCmpFunc func, S32 alphaRef ) +{ + alphaDefined = true; + alphaTestEnable = enable; + alphaTestFunc = func; + alphaTestRef = alphaRef; +} + +void GFXStateBlockDesc::setBlend( bool enable, GFXBlend src, GFXBlend dest, GFXBlendOp op ) +{ + blendDefined = true; + blendEnable = enable; + blendSrc = src; + blendDest = dest; + blendOp = op; +} + +void GFXStateBlockDesc::setSeparateAlphaBlend( bool enable, GFXBlend src, GFXBlend dest, GFXBlendOp op ) +{ + separateAlphaBlendDefined = true; + separateAlphaBlendEnable = enable; + separateAlphaBlendSrc = src; + separateAlphaBlendDest = dest; + separateAlphaBlendOp = op; +} + +void GFXStateBlockDesc::setColorWrites( bool red, bool green, bool blue, bool alpha ) +{ + colorWriteDefined = true; + colorWriteRed = red; + colorWriteGreen = green; + colorWriteBlue = blue; + colorWriteAlpha = alpha; +} + +GFXSamplerStateDesc::GFXSamplerStateDesc() +{ + textureColorOp = GFXTOPDisable; + addressModeU = GFXAddressWrap; + addressModeV = GFXAddressWrap; + addressModeW = GFXAddressWrap; + magFilter = GFXTextureFilterLinear; + minFilter = GFXTextureFilterLinear; + mipFilter = GFXTextureFilterLinear; + maxAnisotropy = 1; + alphaArg1 = GFXTATexture; + alphaArg2 = GFXTADiffuse; + alphaArg3 = GFXTACurrent; + colorArg1 = GFXTACurrent; + colorArg2 = GFXTATexture; + colorArg3 = GFXTACurrent; + alphaOp = GFXTOPModulate; + textureTransform = GFXTTFFDisable; + resultArg = GFXTACurrent; + mipLODBias = 0.0f; +} + +GFXSamplerStateDesc GFXSamplerStateDesc::getWrapLinear() +{ + // Linear with wrapping is already the default + GFXSamplerStateDesc ssd; + ssd.textureColorOp = GFXTOPModulate; + return ssd; +} + +GFXSamplerStateDesc GFXSamplerStateDesc::getWrapPoint() +{ + GFXSamplerStateDesc ssd; + ssd.textureColorOp = GFXTOPModulate; + ssd.magFilter = GFXTextureFilterPoint; + ssd.minFilter = GFXTextureFilterPoint; + ssd.mipFilter = GFXTextureFilterPoint; + return ssd; +} + +GFXSamplerStateDesc GFXSamplerStateDesc::getClampLinear() +{ + GFXSamplerStateDesc ssd; + ssd.textureColorOp = GFXTOPModulate; + ssd.addressModeU = GFXAddressClamp; + ssd.addressModeV = GFXAddressClamp; + ssd.addressModeW = GFXAddressClamp; + return ssd; +} + +GFXSamplerStateDesc GFXSamplerStateDesc::getClampPoint() +{ + GFXSamplerStateDesc ssd; + ssd.textureColorOp = GFXTOPModulate; + ssd.addressModeU = GFXAddressClamp; + ssd.addressModeV = GFXAddressClamp; + ssd.addressModeW = GFXAddressClamp; + ssd.magFilter = GFXTextureFilterPoint; + ssd.minFilter = GFXTextureFilterPoint; + ssd.mipFilter = GFXTextureFilterPoint; + return ssd; +} \ No newline at end of file diff --git a/gfx/gfxStateBlock.h b/gfx/gfxStateBlock.h new file mode 100644 index 0000000..040bbef --- /dev/null +++ b/gfx/gfxStateBlock.h @@ -0,0 +1,203 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GFXSTATEBLOCK_H_ +#define _GFXSTATEBLOCK_H_ + +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif +#ifndef _GFXRESOURCE_H_ +#include "gfx/gfxResource.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + + +struct GFXSamplerStateDesc +{ + GFXTextureAddressMode addressModeU; + GFXTextureAddressMode addressModeV; + GFXTextureAddressMode addressModeW; + + GFXTextureFilterType magFilter; + GFXTextureFilterType minFilter; + GFXTextureFilterType mipFilter; + + /// The maximum anisotropy used when one of the filter types + /// is set to anisotropic. + /// + /// Defaults to 1. + /// + /// @see GFXTextureFilterType + U32 maxAnisotropy; + + /// Used to offset the mipmap selection by whole or + /// fractional amounts either postively or negatively. + /// + /// Defaults to zero. + F32 mipLODBias; + + GFXTextureOp textureColorOp; + + GFXTextureOp alphaOp; + GFXTextureArgument alphaArg1; + GFXTextureArgument alphaArg2; + GFXTextureArgument alphaArg3; + + GFXTextureArgument colorArg1; + GFXTextureArgument colorArg2; + GFXTextureArgument colorArg3; + + GFXTextureArgument resultArg; + + GFXTextureTransformFlags textureTransform; + + GFXSamplerStateDesc(); + + /// Returns an modulate, wrap, and linear sampled state. + static GFXSamplerStateDesc getWrapLinear(); + + /// Returns an modulate, wrap, and point sampled state. + static GFXSamplerStateDesc getWrapPoint(); + + /// Returns an modulate, clamp, and linear sampled state. + static GFXSamplerStateDesc getClampLinear(); + + /// Returns an modulate, clamp, and point sampled state. + static GFXSamplerStateDesc getClampPoint(); +}; + +/// GFXStateBlockDesc defines a render state, which is then used to create a GFXStateBlock instance. +struct GFXStateBlockDesc +{ + // Blending + bool blendDefined; + bool blendEnable; + GFXBlend blendSrc; + GFXBlend blendDest; + GFXBlendOp blendOp; + + /// @name Separate Alpha Blending + /// @{ + bool separateAlphaBlendDefined; + bool separateAlphaBlendEnable; + GFXBlend separateAlphaBlendSrc; + GFXBlend separateAlphaBlendDest; + GFXBlendOp separateAlphaBlendOp; + /// @} + + // Alpha test + bool alphaDefined; + bool alphaTestEnable; + S32 alphaTestRef; + GFXCmpFunc alphaTestFunc; + + // Color Writes + bool colorWriteDefined; + bool colorWriteRed; + bool colorWriteBlue; + bool colorWriteGreen; + bool colorWriteAlpha; + + // Rasterizer + bool cullDefined; + GFXCullMode cullMode; + + // Depth + bool zDefined; + bool zEnable; + bool zWriteEnable; + GFXCmpFunc zFunc; + F32 zBias; + F32 zSlopeBias; + + // Stencil + bool stencilDefined; + bool stencilEnable; + GFXStencilOp stencilFailOp; + GFXStencilOp stencilZFailOp; + GFXStencilOp stencilPassOp; + GFXCmpFunc stencilFunc; + U32 stencilRef; + U32 stencilMask; + U32 stencilWriteMask; + + // FF lighting + bool ffLighting; + + bool vertexColorEnable; + + GFXFillMode fillMode; + + // Sampler states + bool samplersDefined; + GFXSamplerStateDesc samplers[TEXTURE_STAGE_COUNT]; + ColorI textureFactor; + + GFXStateBlockDesc(); + + /// Returns the hash value of this state description + U32 getHashValue() const; + + /// Adds data from desc to this description, uses *defined parameters in desc to figure out + /// what blocks of state to actually copy from desc. + void addDesc( const GFXStateBlockDesc& desc ); + + /// Returns a string that describes the options set (used by GFXStateBlock::describeSelf) + const String describeSelf() const; + + /// Utility functions to make setting up stateblock descriptions less wordy. + void setCullMode( GFXCullMode m ); + + /// Helpers for setting the fill modes. + void setFillModePoint() { fillMode = GFXFillPoint; } + void setFillModeWireframe() { fillMode = GFXFillWireframe; } + void setFillModeSolid() { fillMode = GFXFillSolid; } + + void setZReadWrite( bool read, bool write = true ); + + void setAlphaTest( bool enable, + GFXCmpFunc func = GFXCmpGreaterEqual, + S32 alphaRef = 0 ); + + void setBlend( bool enable, + GFXBlend src = GFXBlendSrcAlpha, + GFXBlend dest = GFXBlendInvSrcAlpha, + GFXBlendOp op = GFXBlendOpAdd ); + + void setSeparateAlphaBlend( bool enable, + GFXBlend src = GFXBlendOne, + GFXBlend dest = GFXBlendZero, + GFXBlendOp op = GFXBlendOpAdd ); + + + /// + void setColorWrites( bool red, bool green, bool blue, bool alpha ); +}; + +class GFXStateBlock : public StrongRefBase, public GFXResource +{ +public: + virtual ~GFXStateBlock() { } + + /// Returns the hash value of the desc that created this block + virtual U32 getHashValue() const = 0; + + /// Returns a GFXStateBlockDesc that this block represents + virtual const GFXStateBlockDesc& getDesc() const = 0; + + /// Default implementation for GFXResource::describeSelf + virtual const String describeSelf() const; +}; + +typedef StrongRefPtr GFXStateBlockRef; + +#endif diff --git a/gfx/gfxStringEnumTranslate.cpp b/gfx/gfxStringEnumTranslate.cpp new file mode 100644 index 0000000..759ad61 --- /dev/null +++ b/gfx/gfxStringEnumTranslate.cpp @@ -0,0 +1,596 @@ +//------------------------------------------------------------------------------ +// Torque Shader Engine +// Copyright (c) GarageGames.Com +//------------------------------------------------------------------------------ + +#include "core/strings/stringFunctions.h" + +#include "gfx/gfxStringEnumTranslate.h" +#include "console/console.h" + +//------------------------------------------------------------------------------ + +const char *GFXStringIndexFormat[GFXIndexFormat_COUNT]; +const char *GFXStringSamplerState[GFXSAMP_COUNT]; +const char *GFXStringTextureFormat[GFXFormat_COUNT]; +const char *GFXStringTiledTextureFormat[GFXFormat_COUNT]; +const char *GFXStringRenderTargetFormat[GFXFormat_COUNT]; +const char *GFXStringRenderState[GFXRenderState_COUNT]; +const char *GFXStringTextureFilter[GFXTextureFilter_COUNT]; +const char *GFXStringBlend[GFXBlend_COUNT]; +const char *GFXStringBlendOp[GFXBlendOp_COUNT]; +const char *GFXStringStencilOp[GFXStencilOp_COUNT]; +const char *GFXStringCmpFunc[GFXCmp_COUNT]; +const char *GFXStringCullMode[GFXCull_COUNT]; +const char *GFXStringPrimType[GFXPT_COUNT]; +const char *GFXStringTextureStageState[GFXTSS_COUNT]; +const char *GFXStringTextureAddress[GFXAddress_COUNT]; +const char *GFXStringTextureOp[GFXTOP_COUNT]; +const char *GFXStringFillMode[GFXFill_COUNT]; + +StringValueLookupFn GFXStringRenderStateValueLookup[GFXRenderState_COUNT]; +StringValueLookupFn GFXStringSamplerStateValueLookup[GFXSAMP_COUNT]; +StringValueLookupFn GFXStringTextureStageStateValueLookup[GFXTSS_COUNT]; + +//------------------------------------------------------------------------------ + +const char *defaultStringValueLookup( const U32 &value ) +{ + static char retbuffer[256]; + + dSprintf( retbuffer, sizeof( retbuffer ), "%d", value ); + + return retbuffer; +} + +#define _STRING_VALUE_LOOKUP_FXN( table ) \ + const char * table##_lookup( const U32 &value ) { return table[value]; } + +_STRING_VALUE_LOOKUP_FXN(GFXStringTextureAddress); +_STRING_VALUE_LOOKUP_FXN(GFXStringTextureFilter); +_STRING_VALUE_LOOKUP_FXN(GFXStringBlend); +_STRING_VALUE_LOOKUP_FXN(GFXStringTextureOp); +_STRING_VALUE_LOOKUP_FXN(GFXStringCmpFunc); +_STRING_VALUE_LOOKUP_FXN(GFXStringStencilOp); +_STRING_VALUE_LOOKUP_FXN(GFXStringCullMode); +_STRING_VALUE_LOOKUP_FXN(GFXStringBlendOp); + +//------------------------------------------------------------------------------ + +#define INIT_LOOKUPTABLE( tablearray, enumprefix, type ) \ + for( int i = enumprefix##_FIRST; i < enumprefix##_COUNT; i++ ) \ + tablearray[i] = (type)GFX_UNINIT_VAL; +#define INIT_LOOKUPTABLE_EX( tablearray, enumprefix, type, typeTable ) \ + for( int i = enumprefix##_FIRST; i < enumprefix##_COUNT; i++ ) \ + {\ + tablearray[i] = (type)GFX_UNINIT_VAL;\ + typeTable[i] = &defaultStringValueLookup;\ + } + +#define VALIDATE_LOOKUPTABLE( tablearray, enumprefix ) \ + for( int i = enumprefix##_FIRST; i < enumprefix##_COUNT; i++ ) \ + if( (int)tablearray[i] == GFX_UNINIT_VAL ) \ + Con::warnf( "GFXStringEnumTranslate: Unassigned value in " #tablearray ": %i", i ); \ + else if( (int)tablearray[i] == GFX_UNSUPPORTED_VAL ) \ + Con::warnf( "GFXStringEnumTranslate: Unsupported value in " #tablearray ": %i", i ); + +//------------------------------------------------------------------------------ + +#define GFX_STRING_ASSIGN_MACRO( table, indexEnum ) table[indexEnum] = #indexEnum; +#define GFX_STRING_ASSIGN_MACRO_EX( table, indexEnum, typeTable ) table[indexEnum] = #indexEnum; table##ValueLookup[indexEnum] = &typeTable##_lookup; + +void GFXStringEnumTranslate::init() +{ + static bool sInitCalled = false; + + if( sInitCalled ) + return; + + sInitCalled = true; + + INIT_LOOKUPTABLE( GFXStringIndexFormat, GFXIndexFormat, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringIndexFormat, GFXIndexFormat16 ); + GFX_STRING_ASSIGN_MACRO( GFXStringIndexFormat, GFXIndexFormat32 ); + VALIDATE_LOOKUPTABLE( GFXStringIndexFormat, GFXIndexFormat ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE_EX( GFXStringSamplerState, GFXSAMP, const char *, GFXStringSamplerStateValueLookup ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringSamplerState, GFXSAMPAddressU, GFXStringTextureAddress ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringSamplerState, GFXSAMPAddressV, GFXStringTextureAddress ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringSamplerState, GFXSAMPAddressW, GFXStringTextureAddress ); + GFX_STRING_ASSIGN_MACRO( GFXStringSamplerState, GFXSAMPBorderColor ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringSamplerState, GFXSAMPMagFilter, GFXStringTextureFilter ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringSamplerState, GFXSAMPMinFilter, GFXStringTextureFilter ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringSamplerState, GFXSAMPMipFilter, GFXStringTextureFilter ); + GFX_STRING_ASSIGN_MACRO( GFXStringSamplerState, GFXSAMPMipMapLODBias ); + GFX_STRING_ASSIGN_MACRO( GFXStringSamplerState, GFXSAMPMaxMipLevel ); + GFX_STRING_ASSIGN_MACRO( GFXStringSamplerState, GFXSAMPMaxAnisotropy ); + + GFX_STRING_ASSIGN_MACRO( GFXStringSamplerState, GFXSAMPSRGBTexture ); + GFX_STRING_ASSIGN_MACRO( GFXStringSamplerState, GFXSAMPElementIndex ); + GFX_STRING_ASSIGN_MACRO( GFXStringSamplerState, GFXSAMPDMapOffset ); + + VALIDATE_LOOKUPTABLE( GFXStringSamplerState, GFXSAMP ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringTextureFormat, GFXFormat, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR8G8B8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR8G8B8A8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR8G8B8X8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR32F ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR5G6B5 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR5G5B5A1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR5G5B5X1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatA8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatL8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatDXT1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatDXT2 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatDXT3 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatDXT4 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatDXT5 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatD32 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatD24X8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatD24S8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatD24FS8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatD16 ); + + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR32G32B32A32F ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR16G16B16A16F ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatL16 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR16G16B16A16 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR16G16 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR16F ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR16G16F ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFormat, GFXFormatR10G10B10A2 ); + VALIDATE_LOOKUPTABLE( GFXStringTextureFormat, GFXFormat); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE_EX( GFXStringRenderState, GFXRenderState, const char *, GFXStringRenderStateValueLookup ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSZEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFillMode ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSZWriteEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAlphaTestEnable ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSSrcBlend, GFXStringBlend ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSDestBlend, GFXStringBlend ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSCullMode, GFXStringCullMode ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSZFunc, GFXStringCmpFunc ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAlphaRef ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSAlphaFunc, GFXStringCmpFunc ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAlphaBlendEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSStencilEnable ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSStencilFail, GFXStringStencilOp ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSStencilZFail, GFXStringStencilOp ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSStencilPass, GFXStringStencilOp ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSStencilFunc, GFXStringCmpFunc ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSStencilRef ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSStencilMask ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSStencilWriteMask ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap0 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap2 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap3 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap4 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap5 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap6 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap7 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSClipPlaneEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointSize ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointSizeMin ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointSize_Max ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointSpriteEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSMultiSampleantiAlias ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSMultiSampleMask ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSShadeMode ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSLastPixel ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSClipping ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointScaleEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointScale_A ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointScale_B ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPointScale_C ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSLighting ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAmbient ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFogVertexMode ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSColorVertex ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSLocalViewer ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSNormalizeNormals ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSDiffuseMaterialSource ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSSpecularMaterialSource ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAmbientMaterialSource ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSEmissiveMaterialSource ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSVertexBlend ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFogEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSSpecularEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFogColor ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFogTableMode ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFogStart ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFogEnd ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSFogDensity ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSRangeFogEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSDebugMonitorToken ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSIndexedVertexBlendEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSTweenFactor ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSTextureFactor ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPatchEdgeStyle ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSDitherEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSColorWriteEnable ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSBlendOp, GFXStringBlendOp ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSPositionDegree ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSNormalDegree ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAntiAliasedLineEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAdaptiveTess_X ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAdaptiveTess_Y ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSdaptiveTess_Z ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSAdaptiveTess_W ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSEnableAdaptiveTesselation ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSScissorTestEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSSlopeScaleDepthBias ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSMinTessellationLevel ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSMaxTessellationLevel ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSTwoSidedStencilMode ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSCCWStencilFail ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSCCWStencilZFail ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSCCWStencilPass ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSCCWStencilFunc ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSColorWriteEnable1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSColorWriteEnable2 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSolorWriteEnable3 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSBlendFactor ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSSRGBWriteEnable ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSDepthBias ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap8 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap9 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap10 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap11 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap12 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap13 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap14 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSWrap15 ); + GFX_STRING_ASSIGN_MACRO( GFXStringRenderState, GFXRSSeparateAlphaBlendEnable ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSSrcBlendAlpha, GFXStringBlend ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSDestBlendAlpha, GFXStringBlend ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringRenderState, GFXRSBlendOpAlpha, GFXStringBlendOp ); + + VALIDATE_LOOKUPTABLE( GFXStringRenderState, GFXRenderState ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringTextureFilter, GFXTextureFilter, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFilter, GFXTextureFilterNone ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFilter, GFXTextureFilterPoint ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFilter, GFXTextureFilterLinear ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFilter, GFXTextureFilterAnisotropic ); + + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFilter, GFXTextureFilterPyramidalQuad ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureFilter, GFXTextureFilterGaussianQuad ); + + VALIDATE_LOOKUPTABLE( GFXStringTextureFilter, GFXTextureFilter ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringBlend, GFXBlend, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendZero ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendOne ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendSrcColor ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendInvSrcColor ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendSrcAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendInvSrcAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendDestAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendInvDestAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendDestColor ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendInvDestColor ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlend, GFXBlendSrcAlphaSat ); + VALIDATE_LOOKUPTABLE( GFXStringBlend, GFXBlend ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringBlendOp, GFXBlendOp, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlendOp, GFXBlendOpAdd ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlendOp, GFXBlendOpSubtract ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlendOp, GFXBlendOpRevSubtract ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlendOp, GFXBlendOpMin ); + GFX_STRING_ASSIGN_MACRO( GFXStringBlendOp, GFXBlendOpMax ); + VALIDATE_LOOKUPTABLE( GFXStringBlendOp, GFXBlendOp ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringStencilOp, GFXStencilOp, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpKeep ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpZero ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpReplace ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpIncrSat ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpDecrSat ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpInvert ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpIncr ); + GFX_STRING_ASSIGN_MACRO( GFXStringStencilOp, GFXStencilOpDecr ); + VALIDATE_LOOKUPTABLE( GFXStringStencilOp, GFXStencilOp ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringCmpFunc, GFXCmp, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpNever ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpLess ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpEqual ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpLessEqual ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpGreater ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpNotEqual ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpGreaterEqual ); + GFX_STRING_ASSIGN_MACRO( GFXStringCmpFunc, GFXCmpAlways ); + VALIDATE_LOOKUPTABLE( GFXStringCmpFunc, GFXCmp ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringCullMode, GFXCull, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringCullMode, GFXCullNone ); + GFX_STRING_ASSIGN_MACRO( GFXStringCullMode, GFXCullCW ); + GFX_STRING_ASSIGN_MACRO( GFXStringCullMode, GFXCullCCW ); + VALIDATE_LOOKUPTABLE( GFXStringCullMode, GFXCull ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringPrimType, GFXPT, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringPrimType, GFXPointList ); + GFX_STRING_ASSIGN_MACRO( GFXStringPrimType, GFXLineList ); + GFX_STRING_ASSIGN_MACRO( GFXStringPrimType, GFXLineStrip ); + GFX_STRING_ASSIGN_MACRO( GFXStringPrimType, GFXTriangleList ); + GFX_STRING_ASSIGN_MACRO( GFXStringPrimType, GFXTriangleStrip ); + GFX_STRING_ASSIGN_MACRO( GFXStringPrimType, GFXTriangleFan ); + VALIDATE_LOOKUPTABLE( GFXStringPrimType, GFXPT ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE_EX( GFXStringTextureStageState, GFXTSS, const char *, GFXStringTextureStageStateValueLookup ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringTextureStageState, GFXTSSColorOp, GFXStringTextureOp ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSColorArg1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSColorArg2 ); + GFX_STRING_ASSIGN_MACRO_EX( GFXStringTextureStageState, GFXTSSAlphaOp, GFXStringTextureOp ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSAlphaArg1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSAlphaArg2 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSBumpEnvMat00 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSBumpEnvMat01 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSBumpEnvMat10 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSBumpEnvMat11 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSTexCoordIndex ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSBumpEnvlScale ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSBumpEnvlOffset ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSTextureTransformFlags ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSColorArg0 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSAlphaArg0 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSResultArg ); + + GFX_STRING_ASSIGN_MACRO( GFXStringTextureStageState, GFXTSSConstant ); + VALIDATE_LOOKUPTABLE( GFXStringTextureStageState, GFXTSS ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringTextureAddress, GFXAddress, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureAddress, GFXAddressWrap ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureAddress, GFXAddressMirror ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureAddress, GFXAddressClamp ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureAddress, GFXAddressBorder ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureAddress, GFXAddressMirrorOnce ); + VALIDATE_LOOKUPTABLE(GFXStringTextureAddress, GFXAddress ); +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + INIT_LOOKUPTABLE( GFXStringTextureOp, GFXTOP, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPDisable ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPSelectARG1 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPSelectARG2 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPModulate ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPModulate2X ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPModulate4X ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPAdd ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPAddSigned ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPAddSigned2X ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPSubtract ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPAddSmooth ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPBlendDiffuseAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPBlendTextureAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPBlendFactorAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPBlendTextureAlphaPM ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPBlendCURRENTALPHA ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPPreModulate ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPModulateAlphaAddColor ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPModulateColorAddAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPModulateInvAlphaAddColor ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPModulateInvColorAddAlpha ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPBumpEnvMap ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPBumpEnvMapLuminance ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPDotProduct3 ); + GFX_STRING_ASSIGN_MACRO( GFXStringTextureOp, GFXTOPLERP ); + VALIDATE_LOOKUPTABLE( GFXStringTextureOp, GFXTOP ); + + INIT_LOOKUPTABLE( GFXStringFillMode, GFXFill, const char * ); + GFX_STRING_ASSIGN_MACRO( GFXStringFillMode, GFXFillPoint ); + GFX_STRING_ASSIGN_MACRO( GFXStringFillMode, GFXFillWireframe ); + GFX_STRING_ASSIGN_MACRO( GFXStringFillMode, GFXFillSolid ); + VALIDATE_LOOKUPTABLE( GFXStringFillMode, GFXFill ); +} + +// ----- +static EnumTable::Enums gBlendEnums[] = +{ + { GFXBlendZero, "GFXBlendZero" }, + { GFXBlendOne, "GFXBlendOne" }, + { GFXBlendSrcColor, "GFXBlendSrcColor" }, + { GFXBlendInvSrcColor, "GFXBlendInvSrcColor" }, + { GFXBlendSrcAlpha, "GFXBlendSrcAlpha" }, + { GFXBlendInvSrcAlpha, "GFXBlendInvSrcAlpha" }, + { GFXBlendDestAlpha, "GFXBlendDestAlpha" }, + { GFXBlendInvDestAlpha, "GFXBlendInvDestAlpha" }, + { GFXBlendDestColor, "GFXBlendDestColor" }, + { GFXBlendInvDestColor, "GFXBlendInvDestColor" }, + { GFXBlendSrcAlphaSat, "GFXBlendSrcAlphaSat" } +}; +EnumTable gBlendEnumTable( GFXBlend_COUNT, gBlendEnums ); + +static EnumTable::Enums gCmpFuncEnums[] = +{ + { GFXCmpNever, "GFXCmpNever" }, + { GFXCmpLess, "GFXCmpLess" }, + { GFXCmpEqual, "GFXCmpEqual" }, + { GFXCmpLessEqual, "GFXCmpLessEqual" }, + { GFXCmpGreater, "GFXCmpGreater" }, + { GFXCmpNotEqual, "GFXCmpNotEqual" }, + { GFXCmpGreaterEqual, "GFXCmpGreaterEqual" }, + { GFXCmpAlways, "GFXCmpAlways" }, +}; +EnumTable gCmpFuncEnumTable( GFXCmp_COUNT, gCmpFuncEnums ); + +static EnumTable::Enums gSamplerAddressModeEnums[] = +{ + { GFXAddressWrap, "GFXAddressWrap" }, + { GFXAddressMirror, "GFXAddressMirror" }, + { GFXAddressClamp, "GFXAddressClamp" }, + { GFXAddressBorder, "GFXAddressBorder" }, + { GFXAddressMirrorOnce, "GFXAddressMirrorOnce" } +}; +EnumTable gSamplerAddressModeEnumTable( GFXAddress_COUNT, gSamplerAddressModeEnums ); + +static EnumTable::Enums gTextureFilterModeEnums[] = +{ + { GFXTextureFilterNone, "GFXTextureFilterNone" }, + { GFXTextureFilterPoint, "GFXTextureFilterPoint" }, + { GFXTextureFilterLinear, "GFXTextureFilterLinear" }, + { GFXTextureFilterAnisotropic, "GFXTextureFilterAnisotropic" }, + { GFXTextureFilterPyramidalQuad, "GFXTextureFilterPyramidalQuad" }, + { GFXTextureFilterGaussianQuad, "GFXTextureFilterGaussianQuad" } +}; +EnumTable gTextureFilterModeEnumTable( GFXTextureFilter_COUNT, gTextureFilterModeEnums ); + +static EnumTable::Enums gTextureColorOpEnums[] = +{ + { GFXTOPDisable, "GFXTOPDisable" }, + { GFXTOPSelectARG1, "GFXTOPSelectARG1" }, + { GFXTOPSelectARG2, "GFXTOPSelectARG2" }, + { GFXTOPModulate, "GFXTOPModulate" }, + { GFXTOPModulate2X, "GFXTOPModulate2X" }, + { GFXTOPModulate4X, "GFXTOPModulate4X" }, + { GFXTOPAdd, "GFXTOPAdd" }, + { GFXTOPAddSigned, "GFXTOPAddSigned" }, + { GFXTOPAddSigned2X, "GFXTOPAddSigned2X" }, + { GFXTOPSubtract, "GFXTOPSubtract" }, + { GFXTOPAddSmooth, "GFXTOPAddSmooth" }, + { GFXTOPBlendDiffuseAlpha, "GFXTOPBlendDiffuseAlpha" }, + { GFXTOPBlendTextureAlpha, "GFXTOPBlendTextureAlpha" }, + { GFXTOPBlendFactorAlpha, "GFXTOPBlendFactorAlpha" }, + { GFXTOPBlendTextureAlphaPM, "GFXTOPBlendTextureAlphaPM" }, + { GFXTOPBlendCURRENTALPHA, "GFXTOPBlendCURRENTALPHA" }, + { GFXTOPPreModulate, "GFXTOPPreModulate" }, + { GFXTOPModulateAlphaAddColor, "GFXTOPModulateAlphaAddColor" }, + { GFXTOPModulateColorAddAlpha, "GFXTOPModulateColorAddAlpha" }, + { GFXTOPModulateInvAlphaAddColor, "GFXTOPModulateInvAlphaAddColor" }, + { GFXTOPModulateInvColorAddAlpha, "GFXTOPModulateInvColorAddAlpha" }, + { GFXTOPBumpEnvMap, "GFXTOPBumpEnvMap" }, + { GFXTOPBumpEnvMapLuminance, "GFXTOPBumpEnvMapLuminance" }, + { GFXTOPDotProduct3, "GFXTOPDotProduct3" }, + { GFXTOPLERP, "GFXTOPLERP" } +}; +EnumTable gTextureColorOpEnumTable( GFXTOP_COUNT, gTextureColorOpEnums ); + +static EnumTable::Enums gTextureArgumentEnums[] = +{ + { GFXTADiffuse, "GFXTADiffuse" }, + { GFXTACurrent, "GFXTACurrent" }, + { GFXTATexture, "GFXTATexture" }, + { GFXTATFactor, "GFXTATFactor" }, + { GFXTASpecular, "GFXTASpecular" }, + { GFXTATemp, "GFXTATemp" }, + { GFXTAConstant, "GFXTAConstant" }, + { GFXTAComplement, "OneMinus" }, // first flag + { GFXTAAlphaReplicate, "AlphaReplicate" } +}; +EnumTable gTextureArgumentEnumTable_M(GFXTA_COUNT + 2, gTextureArgumentEnums,GFXTAComplement); +EnumTable gTextureArgumentEnumTable(GFXTA_COUNT, gTextureArgumentEnums); + +static EnumTable::Enums gTextureTransformEnums[] = +{ + { GFXTTFFDisable, "GFXTTFDisable" }, + { GFXTTFFCoord1D, "GFXTTFFCoord1D" }, + { GFXTTFFCoord2D, "GFXTTFFCoord2D" }, + { GFXTTFFCoord3D, "GFXTTFFCoord3D" }, + { GFXTTFFCoord4D, "GFXTTFFCoord4D" }, + { GFXTTFFProjected, "GFXTTFProjected" } +}; +EnumTable gTextureTransformEnumTable(6, gTextureTransformEnums); + +static EnumTable::Enums gTextureFormatEnums[] = +{ + { GFXFormatR8G8B8, "GFXFormatR8G8B8" }, + { GFXFormatR8G8B8A8, "GFXFormatR8G8B8A8" }, + { GFXFormatR8G8B8X8, "GFXFormatR8G8B8X8" }, + { GFXFormatR32F, "GFXFormatR32F" }, + { GFXFormatR5G6B5, "GFXFormatR5G6B5" }, + { GFXFormatR5G5B5A1, "GFXFormatR5G5B5A1" }, + { GFXFormatR5G5B5X1, "GFXFormatR5G5B5X1" }, + { GFXFormatA8, "GFXFormatA8" }, + { GFXFormatL8, "GFXFormatL8" }, + { GFXFormatDXT1, "GFXFormatDXT1" }, + { GFXFormatDXT2, "GFXFormatDXT2" }, + { GFXFormatDXT3, "GFXFormatDXT3" }, + { GFXFormatDXT4, "GFXFormatDXT4" }, + { GFXFormatDXT5, "GFXFormatDXT5" }, + { GFXFormatD32, "GFXFormatD32" }, + { GFXFormatD24X8, "GFXFormatD24X8" }, + { GFXFormatD24S8, "GFXFormatD24S8" }, + { GFXFormatD24FS8, "GFXFormatD24FS8" }, + { GFXFormatD16, "GFXFormatD16" }, + + { GFXFormatR32G32B32A32F, "GFXFormatR32G32B32A32F" }, + { GFXFormatR16G16B16A16F, "GFXFormatR16G16B16A16F" }, + { GFXFormatL16, "GFXFormatL16" }, + { GFXFormatR16G16B16A16, "GFXFormatR16G16B16A16" }, + { GFXFormatR16G16, "GFXFormatR16G16" }, + { GFXFormatR16F, "GFXFormatR16F" }, + { GFXFormatR16G16F, "GFXFormatR16G16F" }, + { GFXFormatR10G10B10A2, "GFXFormatR10G10B10A2" } +}; +EnumTable gTextureFormatEnumTable(27, gTextureFormatEnums); + +static EnumTable::Enums gCullModeEnums[] = +{ + { GFXCullNone, "GFXCullNone" }, + { GFXCullCW, "GFXCullCW" }, + { GFXCullCCW, "GFXCullCCW" } +}; +EnumTable gCullModeEnumTable(GFXCull_COUNT, gCullModeEnums); + +static EnumTable::Enums gStencilModeEnums[] = +{ + { GFXStencilOpKeep, "GFXStencilOpKeep" }, + { GFXStencilOpZero, "GFXStencilOpZero" }, + { GFXStencilOpReplace, "GFXStencilOpReplace" }, + { GFXStencilOpIncrSat, "GFXStencilOpIncrSat" }, + { GFXStencilOpDecrSat, "GFXStencilOpDecrSat" }, + { GFXStencilOpInvert, "GFXStencilOpInvert" }, + { GFXStencilOpIncr, "GFXStencilOpIncr" }, + { GFXStencilOpDecr, "GFXStencilOpDecr" }, +}; +EnumTable gStencilModeEnumTable(GFXStencilOp_COUNT, gStencilModeEnums); + +static EnumTable::Enums gBlendOpEnums[] = +{ + { GFXBlendOpAdd, "GFXBlendOpAdd" }, + { GFXBlendOpSubtract, "GFXBlendOpSubtract" }, + { GFXBlendOpRevSubtract, "GFXBlendOpRevSubtract" }, + { GFXBlendOpMin, "GFXBlendOpMin" }, + { GFXBlendOpMax, "GFXBlendOpMax" } +}; +EnumTable gBlendOpEnumTable(GFXBlendOp_COUNT, gBlendOpEnums); + +EnumTable::Enums srcBlendFactorLookup[] = +{ + { GFXBlendZero, "ZERO" }, + { GFXBlendOne, "ONE" }, + { GFXBlendDestColor, "DST_COLOR" }, + { GFXBlendInvDestColor, "ONE_MINUS_DST_COLOR" }, + { GFXBlendSrcAlpha, "SRC_ALPHA" }, + { GFXBlendInvSrcAlpha, "ONE_MINUS_SRC_ALPHA" }, + { GFXBlendDestAlpha, "DST_ALPHA" }, + { GFXBlendInvDestAlpha, "ONE_MINUS_DST_ALPHA" }, + { GFXBlendSrcAlphaSat, "SRC_ALPHA_SATURATE" }, +}; +EnumTable srcBlendFactorTable(sizeof(srcBlendFactorLookup) / sizeof(EnumTable::Enums), &srcBlendFactorLookup[0]); + +EnumTable::Enums dstBlendFactorLookup[] = +{ + { GFXBlendZero, "ZERO" }, + { GFXBlendOne, "ONE" }, + { GFXBlendSrcColor, "SRC_COLOR" }, + { GFXBlendInvSrcColor, "ONE_MINUS_SRC_COLOR" }, + { GFXBlendSrcAlpha, "SRC_ALPHA" }, + { GFXBlendInvSrcAlpha, "ONE_MINUS_SRC_ALPHA" }, + { GFXBlendDestAlpha, "DST_ALPHA" }, + { GFXBlendInvDestAlpha, "ONE_MINUS_DST_ALPHA" }, +}; +EnumTable dstBlendFactorTable(sizeof(dstBlendFactorLookup) / sizeof(EnumTable::Enums), &dstBlendFactorLookup[0]); diff --git a/gfx/gfxStringEnumTranslate.h b/gfx/gfxStringEnumTranslate.h new file mode 100644 index 0000000..335e0e9 --- /dev/null +++ b/gfx/gfxStringEnumTranslate.h @@ -0,0 +1,75 @@ +//------------------------------------------------------------------------------ +// Torque Shader Engine +// Copyright (c) GarageGames.Com +//------------------------------------------------------------------------------ +#ifndef _GFXSTRINGENUMTRANSLATE_H_ +#define _GFXSTRINGENUMTRANSLATE_H_ + +#include "platform/platform.h" +#include "gfx/gfxEnums.h" + +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif + +//------------------------------------------------------------------------------ + +namespace GFXStringEnumTranslate +{ + void init(); +}; + +//------------------------------------------------------------------------------ + +extern const char *GFXStringIndexFormat[GFXIndexFormat_COUNT]; +extern const char *GFXStringSamplerState[GFXSAMP_COUNT]; +extern const char *GFXStringTextureFormat[GFXFormat_COUNT]; +extern const char *GFXStringRenderState[GFXRenderState_COUNT]; +extern const char *GFXStringTextureFilter[GFXTextureFilter_COUNT]; +extern const char *GFXStringBlend[GFXBlend_COUNT]; +extern const char *GFXStringBlendOp[GFXBlendOp_COUNT]; +extern const char *GFXStringStencilOp[GFXStencilOp_COUNT]; +extern const char *GFXStringCmpFunc[GFXCmp_COUNT]; +extern const char *GFXStringCullMode[GFXCull_COUNT]; +extern const char *GFXStringPrimType[GFXPT_COUNT]; +extern const char *GFXStringTextureStageState[GFXTSS_COUNT]; +extern const char *GFXStringTextureAddress[GFXAddress_COUNT]; +extern const char *GFXStringTextureOp[GFXTOP_COUNT]; +extern const char *GFXStringFillMode[GFXFill_COUNT]; + +typedef const char *(*StringValueLookupFn)( const U32 &value ); + +extern StringValueLookupFn GFXStringRenderStateValueLookup[GFXRenderState_COUNT]; +extern StringValueLookupFn GFXStringSamplerStateValueLookup[GFXSAMP_COUNT]; +extern StringValueLookupFn GFXStringTextureStageStateValueLookup[GFXTSS_COUNT]; + +#define GFXREVERSE_LOOKUP( tablearray, enumprefix, val ) \ + for( int i = enumprefix##_FIRST; i < enumprefix##_COUNT; i++ ) \ + if( (int)tablearray##[i] == val ) \ + { \ + val = i; \ + break; \ + } \ + +// +// EnumTables +// +extern EnumTable gBlendEnumTable; +extern EnumTable gCmpFuncEnumTable; +extern EnumTable gSamplerAddressModeEnumTable; +extern EnumTable gTextureFormatEnumTable; +extern EnumTable gTextureTransformEnumTable; +extern EnumTable gTextureFilterModeEnumTable; +extern EnumTable gTextureColorOpEnumTable; +extern EnumTable gTextureArgumentEnumTable; +extern EnumTable gTextureArgumentEnumTable_M; +extern EnumTable gCullModeEnumTable; +extern EnumTable gStencilModeEnumTable; +extern EnumTable gBlendOpEnumTable; + +extern EnumTable::Enums srcBlendFactorLookup[9]; +extern EnumTable::Enums dstBlendFactorLookup[9]; +extern EnumTable srcBlendFactorTable; +extern EnumTable dstBlendFactorTable; + +#endif \ No newline at end of file diff --git a/gfx/gfxStructs.cpp b/gfx/gfxStructs.cpp new file mode 100644 index 0000000..e371302 --- /dev/null +++ b/gfx/gfxStructs.cpp @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gfxDevice.h" + + +GFXVideoMode::GFXVideoMode() +{ + bitDepth = 32; + fullScreen = false; + refreshRate = 60; + wideScreen = false; + resolution.set(800,600); + antialiasLevel = 0; +} + +void GFXVideoMode::parseFromString( const char *str ) +{ + if(!str) + return; + + // Copy the string, as dStrtok is destructive + char *tempBuf = new char[dStrlen( str ) + 1]; + dStrcpy( tempBuf, str ); + +#define PARSE_ELEM(type, var, func, tokParam, sep) \ + if(const char *ptr = dStrtok( tokParam, sep)) \ + { type tmp = func(ptr); if(tmp > 0) var = tmp; } + + PARSE_ELEM(S32, resolution.x, dAtoi, tempBuf, " x\0") + PARSE_ELEM(S32, resolution.y, dAtoi, NULL, " x\0") + PARSE_ELEM(S32, fullScreen, dAtob, NULL, " \0") + PARSE_ELEM(S32, bitDepth, dAtoi, NULL, " \0") + PARSE_ELEM(S32, refreshRate, dAtoi, NULL, " \0") + PARSE_ELEM(S32, antialiasLevel, dAtoi, NULL, " \0") + +#undef PARSE_ELEM + + delete [] tempBuf; +} + +const String GFXVideoMode::toString() const +{ + return String::ToString("%d %d %s %d %d %d", resolution.x, resolution.y, (fullScreen ? "true" : "false"), bitDepth, refreshRate, antialiasLevel); +} + +void GFXShaderMacro::stringize( const Vector ¯os, String *outString ) +{ + Vector::const_iterator itr = macros.begin(); + for ( ; itr != macros.end(); itr++ ) + { + (*outString) += itr->name; + if ( itr->value.isNotEmpty() ) + { + (*outString) += "="; + (*outString) += itr->value; + } + (*outString) += ";"; + } +} \ No newline at end of file diff --git a/gfx/gfxStructs.h b/gfx/gfxStructs.h new file mode 100644 index 0000000..cd39fbd --- /dev/null +++ b/gfx/gfxStructs.h @@ -0,0 +1,177 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXSTRUCTS_H_ +#define _GFXSTRUCTS_H_ + +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _GFXVERTEXCOLOR_H_ +#include "gfx/gfxVertexColor.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _PROFILER_H_ +#include "platform/profiler.h" +#endif +#ifndef _GFXRESOURCE_H_ +#include "gfx/gfxResource.h" +#endif +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif +#ifndef _GFXVERTEXTYPES_H_ +#include "gfx/gfxVertexTypes.h" +#endif + + +//----------------------------------------------------------------------------- +// This class is used to interact with an API's fixed function lights. See GFX->setLight +class GFXLightInfo +{ +public: + enum Type { + Point = 0, + Spot = 1, + Vector = 2, + Ambient = 3, + }; + Type mType; + + Point3F mPos; + VectorF mDirection; + ColorF mColor; + ColorF mAmbient; + F32 mRadius; + F32 mInnerConeAngle; + F32 mOuterConeAngle; + + /// @todo Revisit below (currently unused by fixed function lights) + Point3F position; + ColorF ambient; + ColorF diffuse; + ColorF specular; + VectorF spotDirection; + F32 spotExponent; + F32 spotCutoff; + F32 constantAttenuation; + F32 linearAttenuation; + F32 quadraticAttenuation; +}; + +//----------------------------------------------------------------------------- + +// Material definition for FF lighting +struct GFXLightMaterial +{ + ColorF ambient; + ColorF diffuse; + ColorF specular; + ColorF emissive; + F32 shininess; +}; + +//----------------------------------------------------------------------------- + +struct GFXVideoMode +{ + GFXVideoMode(); + + Point2I resolution; + U32 bitDepth; + U32 refreshRate; + bool fullScreen; + bool wideScreen; + // When this is returned from GFX, it's the max, otherwise it's the desired AA level. + U32 antialiasLevel; + + inline bool operator == ( const GFXVideoMode &otherMode ) const + { + if( otherMode.fullScreen != fullScreen ) + return false; + if( otherMode.resolution != resolution ) + return false; + if( otherMode.bitDepth != bitDepth ) + return false; + if( otherMode.refreshRate != refreshRate ) + return false; + if( otherMode.wideScreen != wideScreen ) + return false; + if( otherMode.antialiasLevel != antialiasLevel) + return false; + + return true; + } + + inline bool operator !=( const GFXVideoMode& otherMode ) const + { + return !( *this == otherMode ); + } + + /// Fill whatever fields we can from the passed string, which should be + /// of form "width height [bitDepth [refreshRate] [antialiasLevel]]" Unspecified fields + /// aren't modified, so you may want to set defaults before parsing. + void parseFromString( const char *str ); + + /// Gets a string representation of the object as + /// "resolution.x resolution.y fullScreen bitDepth refreshRate antialiasLevel" + /// + /// \return (string) A string representation of the object. + const String toString() const; +}; + + +//----------------------------------------------------------------------------- + +struct GFXPrimitive +{ + GFXPrimitiveType type; + + U32 startVertex; /// offset into vertex buffer to change where vertex[0] is + U32 minIndex; /// minimal value we will see in the indices + U32 startIndex; /// start of indices in buffer + U32 numPrimitives; /// how many prims to render + U32 numVertices; /// how many vertices... (used for locking, we lock from minIndex to minIndex + numVertices) + + GFXPrimitive() + { + dMemset( this, 0, sizeof( GFXPrimitive ) ); + } +}; + +/// Passed to GFX for shader defines. +struct GFXShaderMacro +{ + GFXShaderMacro() {} + + GFXShaderMacro( const GFXShaderMacro ¯o ) + : name( macro.name ), + value( macro.value ) + {} + + GFXShaderMacro( const String &name_, + const String &value_ = String::EmptyString ) + : name( name_ ), + value( value_ ) + {} + + ~GFXShaderMacro() {} + + /// The macro name. + String name; + + /// The optional macro value. + String value; + + static void stringize( const Vector ¯os, String *outString ); +}; + + +#endif // _GFXSTRUCTS_H_ diff --git a/gfx/gfxTarget.cpp b/gfx/gfxTarget.cpp new file mode 100644 index 0000000..7583456 --- /dev/null +++ b/gfx/gfxTarget.cpp @@ -0,0 +1,9 @@ +#include "gfx/gfxTarget.h" +#include "console/console.h" + +GFXTextureObject *GFXTextureTarget::sDefaultDepthStencil = reinterpret_cast( 0x1 ); + +const String GFXTarget::describeSelf() const +{ + return String(); +} \ No newline at end of file diff --git a/gfx/gfxTarget.h b/gfx/gfxTarget.h new file mode 100644 index 0000000..7d54fd9 --- /dev/null +++ b/gfx/gfxTarget.h @@ -0,0 +1,190 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXTARGET_H_ +#define _GFXTARGET_H_ + +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif +#ifndef _GFXRESOURCE_H_ +#include "gfx/gfxResource.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + + +class Point2I; +class PlatformWindow; +class GFXCubemap; +class GFXTextureObject; + +/// Base class for a target to which GFX can render. +/// +/// Most modern graphics hardware supports selecting render targets. However, +/// there may be multiple types of render target, with wildly varying +/// device-level implementations, resource requirements, and so forth. +/// +/// This base class is used to represent a render target; it might be a context +/// tied to a window, or a set of surfaces or textures. +class GFXTarget : public StrongRefBase, public GFXResource +{ + friend class GFXD3D9Device; + friend class GFX360Device; + +private: + S32 mChangeToken; + S32 mLastAppliedChange; + +protected: + + /// Called whenever a change is made to this target. + inline void invalidateState() + { + mChangeToken++; + } + + /// Called when the device has applied pending state. + inline void stateApplied() + { + mLastAppliedChange = mChangeToken; + } +public: + + /// Constructor to initialize the state tracking logic. + GFXTarget() : mChangeToken( 0 ), + mLastAppliedChange( 0 ) + { + } + virtual ~GFXTarget() {} + + /// Called to check if we have pending state for the device to apply. + inline const bool isPendingState() const + { + return (mChangeToken != mLastAppliedChange); + } + + /// Returns the size in pixels of the render target. + virtual const Point2I getSize()=0; + + /// Returns the texture format of the render target. + virtual GFXFormat getFormat()=0; + + // GFXResourceInterface + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const; + + /// This is called to set the render target. + virtual void activate() { } + + /// This is called when the target is not being used anymore. + virtual void deactivate() { } + + /// This tells the target that the contents of this target should be restored + /// when activate() is next called. + virtual void preserve() { } + + /// Copy this surface to the passed GFXTextureObject. + /// @param tex The GFXTextureObject to copy to. + virtual void resolveTo( GFXTextureObject *tex ) { } +}; + +/// A render target associated with an OS window. +/// +/// Various API/OS combinations will implement their own GFXTargets for +/// rendering to a window. However, they are all subclasses of GFXWindowTarget. +/// +/// This allows platform-neutral code to safely distinguish between various +/// types of render targets (using dynamic_cast<>), as well as letting it +/// gain access to useful things like the corresponding PlatformWindow. +class GFXWindowTarget : public GFXTarget +{ +protected: + PlatformWindow *mWindow; +public: + GFXWindowTarget() : mWindow(NULL){}; + GFXWindowTarget( PlatformWindow *windowObject ) + { + mWindow = windowObject; + } + virtual ~GFXWindowTarget() {} + + /// Returns a pointer to the window this target is bound to. + inline PlatformWindow *getWindow() { return mWindow; }; + + /// Present latest buffer, if buffer swapping is in effect. + virtual bool present()=0; + + /// Notify the target that the video mode on the window has changed. + virtual void resetMode()=0; +}; + +/// A render target associated with one or more textures. +/// +/// Although some APIs allow directly selecting any texture or surfaces, in +/// some cases it is necessary to allocate helper resources to enable RTT +/// operations. +/// +/// @note A GFXTextureTarget will retain references to textures that are +/// attached to it, so be sure to clear them out when you're done! +/// +/// @note Different APIs have different restrictions on what they can support +/// here. Be aware when mixing cubemaps vs. non-cubemaps, or targets of +/// different resolutions. The devices will attempt to limit behavior +/// to things that are safely portable, but they cannot catch every +/// possible situation for all drivers and API - so make sure to +/// actually test things! +class GFXTextureTarget : public GFXTarget +{ +public: + enum RenderSlot + { + DepthStencil, + Color0, Color1, Color2, Color3, Color4, + MaxRenderSlotId, + }; + + static GFXTextureObject *sDefaultDepthStencil; + + virtual ~GFXTextureTarget() {} + + /// Attach a surface to a given slot as part of this render target. + /// + /// @param slot What slot is used for multiple render target (MRT) effects. + /// Most of the time you'll use Color0. + /// @param tex A texture and miplevel to bind for rendering, or else NULL/0 + /// to clear a slot. + /// @param mipLevel What level of this texture are we rendering to? + /// @param zOffset If this is a depth texture, what z level are we + /// rendering to? + virtual void attachTexture(RenderSlot slot, GFXTextureObject *tex, U32 mipLevel=0, U32 zOffset = 0) = 0; + + /// Support binding to cubemaps. + /// + /// @param slot What slot is used for multiple render target (MRT) effects. + /// Most of the time you'll use Color0. + /// @param tex What cubemap will we be rendering to? + /// @param face A face identifier. + /// @param mipLevel What level of this texture are we rendering to? + virtual void attachTexture(RenderSlot slot, GFXCubemap *tex, U32 face, U32 mipLevel=0) = 0; + + /// Resolve the current render target data to the associated textures. This method + /// will get called automatically when a rendertarget is changed, before new geometry + /// is drawn to a different rendertarget. This method can also be called to + /// gather render target data without switching targets. + /// + /// By default, this method will resolve all color targets. + virtual void resolve()=0; +}; + +typedef StrongRefPtr GFXTargetRef; +typedef StrongRefPtr GFXWindowTargetRef; +typedef StrongRefPtr GFXTextureTargetRef; + +#endif // _GFXTARGET_H_ diff --git a/gfx/gfxTextureHandle.cpp b/gfx/gfxTextureHandle.cpp new file mode 100644 index 0000000..409552b --- /dev/null +++ b/gfx/gfxTextureHandle.cpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxTextureHandle.h" + +#include "gfx/gfxDevice.h" +#include "gfx/gfxTextureManager.h" + + +GFXTexHandle GFXTexHandle::ZERO; +GFXTexHandle GFXTexHandle::ONE; +GFXTexHandle GFXTexHandle::ZUP; + + +GFXTexHandle::GFXTexHandle( GFXTextureObject *obj ) +{ + StrongObjectRef::set( obj ); +} + +GFXTexHandle::GFXTexHandle( const GFXTexHandle &handle, const String &desc ) +{ + StrongObjectRef::set( handle.getPointer() ); + + #ifdef TORQUE_DEBUG + if ( getPointer() ) + getPointer()->mDebugDescription = desc; + #endif +} + +GFXTexHandle::GFXTexHandle( const String &texName, GFXTextureProfile *profile, const String &desc ) +{ + set( texName, profile, desc ); +} + +bool GFXTexHandle::set( const String &texName, GFXTextureProfile *profile, const String &desc ) +{ + // Clear the existing texture first, so that + // its memory is free for the new allocation. + free(); + + // Create and set the new texture. + AssertFatal( texName.isNotEmpty(), "Texture name is empty" ); + StrongObjectRef::set( GFX->mTextureManager->createTexture( texName, profile ) ); + + #ifdef TORQUE_DEBUG + if ( getPointer() ) + getPointer()->mDebugDescription = desc; + #endif + + return isValid(); +} + +GFXTexHandle::GFXTexHandle( GBitmap *bmp, GFXTextureProfile *profile, bool deleteBmp, const String &desc ) +{ + set( bmp, profile, deleteBmp, desc ); +} + +bool GFXTexHandle::set( GBitmap *bmp, GFXTextureProfile *profile, bool deleteBmp, const String &desc ) +{ + // Clear the existing texture first, so that + // its memory is free for the new allocation. + free(); + + // Create and set the new texture. + AssertFatal( bmp, "Bitmap is NULL" ); + StrongObjectRef::set( GFX->mTextureManager->createTexture( bmp, String(), profile, deleteBmp ) ); + + #ifdef TORQUE_DEBUG + if ( getPointer() ) + getPointer()->mDebugDescription = desc; + #endif + + return isValid(); +} + +GFXTexHandle::GFXTexHandle( DDSFile *dds, GFXTextureProfile *profile, bool deleteDDS, const String &desc ) +{ + set( dds, profile, deleteDDS, desc ); +} + +bool GFXTexHandle::set( DDSFile *dds, GFXTextureProfile *profile, bool deleteDDS, const String &desc ) +{ + // Clear the existing texture first, so that + // its memory is free for the new allocation. + free(); + + // Create and set the new texture. + AssertFatal( dds, "Bitmap is NULL" ); + StrongObjectRef::set( GFX->mTextureManager->createTexture( dds, profile, deleteDDS ) ); + + #ifdef TORQUE_DEBUG + if ( getPointer() ) + getPointer()->mDebugDescription = desc; + #endif + + return isValid(); +} + +GFXTexHandle::GFXTexHandle( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, const String &desc, U32 numMipLevels, S32 antialiasLevel) +{ + set( width, height, format, profile, desc, numMipLevels, antialiasLevel ); +} + +bool GFXTexHandle::set( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, const String &desc, U32 numMipLevels, S32 antialiasLevel) +{ + // Clear the existing texture first, so that + // its memory is free for the new allocation. + free(); + + // Create and set the new texture. + StrongObjectRef::set( GFX->mTextureManager->createTexture( width, height, format, profile, numMipLevels, antialiasLevel ) ); + + #ifdef TORQUE_DEBUG + if ( getPointer() ) + getPointer()->mDebugDescription = desc; + #endif + + return isValid(); +} + +bool GFXTexHandle::set( U32 width, U32 height, U32 depth, void *pixels, GFXFormat format, GFXTextureProfile *profile, const String &desc, U32 numMipLevels ) +{ + // Clear the existing texture first, so that + // its memory is free for the new allocation. + free(); + + // Create and set the new texture. + StrongObjectRef::set( GFX->mTextureManager->createTexture( width, height, depth, pixels, format, profile ) ); + + #ifdef TORQUE_DEBUG + if ( getPointer() ) + getPointer()->mDebugDescription = desc; + #endif + + return isValid(); +} + +void GFXTexHandle::refresh() +{ + GFX->mTextureManager->reloadTexture( getPointer() ); +} diff --git a/gfx/gfxTextureHandle.h b/gfx/gfxTextureHandle.h new file mode 100644 index 0000000..06543cd --- /dev/null +++ b/gfx/gfxTextureHandle.h @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXTEXTUREHANDLE_H_ +#define _GFXTEXTUREHANDLE_H_ + +#ifndef _GFXTEXTUREOBJECT_H_ +#include "gfx/gfxTextureObject.h" +#endif +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif + + +class GFXTextureProfile; + + +/// A reference counted handle to a texture resource. +class GFXTexHandle : public StrongRefPtr +{ +public: + GFXTexHandle() {} + GFXTexHandle( GFXTextureObject* obj ); + GFXTexHandle( const GFXTexHandle &handle, const String &desc ); + + // load texture + GFXTexHandle( const String &texName, GFXTextureProfile *profile, const String &desc ); + bool set( const String &texName, GFXTextureProfile *profile, const String &desc ); + + // register texture + GFXTexHandle( GBitmap *bmp, GFXTextureProfile *profile, bool deleteBmp, const String &desc ); + bool set( GBitmap *bmp, GFXTextureProfile *profile, bool deleteBmp, const String &desc ); + + GFXTexHandle( DDSFile *bmp, GFXTextureProfile *profile, bool deleteDDS, const String &desc ); + bool set( DDSFile *bmp, GFXTextureProfile *profile, bool deleteDDS, const String &desc ); + + // Sized bitmap + GFXTexHandle( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, const String &desc, U32 numMipLevels = 1, S32 antialiasLevel = 0); + bool set( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, const String &desc, U32 numMipLevels = 1, S32 antialiasLevel = 0); + bool set( U32 width, U32 height, U32 depth, void *pixels, GFXFormat format, GFXTextureProfile *profile, const String &desc, U32 numMipLevels = 1 ); + + /// Returns the width and height as a point. + Point2I getWidthHeight() const { return getPointer() ? Point2I( getPointer()->getWidth(), getPointer()->getHeight() ) : Point2I::Zero; } + + U32 getWidth() const { return getPointer() ? getPointer()->getWidth() : 0; } + U32 getHeight() const { return getPointer() ? getPointer()->getHeight() : 0; } + U32 getDepth() const { return getPointer() ? getPointer()->getDepth() : 0; } + GFXFormat getFormat() const { return getPointer() ? getPointer()->getFormat() : GFXFormat_COUNT; } + + /// Reloads the texture. + /// @see GFXTextureManager::reloadTexture + void refresh(); + + /// Releases the texture handle. + void free() { StrongObjectRef::set( NULL ); } + + GFXLockedRect *lock( U32 mipLevel = 0, RectI *inRect = NULL ) + { + return getPointer()->lock(mipLevel, inRect); + } + + void unlock( U32 mipLevel = 0) + { + getPointer()->unlock(mipLevel); + } + + // copy to bitmap. see gfxTetureObject.h for description of what types of textures + // can be copied into bitmaps. returns true if successful, false otherwise + bool copyToBmp(GBitmap* bmp) { return getPointer() ? getPointer()->copyToBmp(bmp) : false; } + + //--------------------------------------------------------------------------- + // Operator overloads + //--------------------------------------------------------------------------- + GFXTexHandle& operator=(const GFXTexHandle &t) + { + StrongObjectRef::set(t.getPointer()); + return *this; + } + + GFXTexHandle& operator=( GFXTextureObject *to) + { + StrongObjectRef::set(to); + return *this; + } + + bool operator==(const GFXTexHandle &t) const { return t.getPointer() == getPointer(); } + bool operator!=(const GFXTexHandle &t) const { return t.getPointer() != getPointer(); } + + operator GFXTextureObject*() + { + return (GFXTextureObject*)getPointer(); + } + + /// Returns the backing bitmap for this texture. + GBitmap* getBitmap() { return getPointer() ? getPointer()->getBitmap() : NULL; } + const GBitmap* getBitmap() const { return getPointer() ? getPointer()->getBitmap() : NULL; } + + + /// Helper 2x2 R8G8B8A8 texture filled with 0. + static GFXTexHandle ZERO; + + /// Helper 2x2 R8G8B8A8 texture filled with 255. + static GFXTexHandle ONE; + + /// Helper 2x2 R8G8B8A8 normal map texture filled + /// with 128, 128, 255. + static GFXTexHandle ZUP; + +}; + + +#endif // _GFXTEXTUREHANDLE_H_ diff --git a/gfx/gfxTextureManager.cpp b/gfx/gfxTextureManager.cpp new file mode 100644 index 0000000..34e6265 --- /dev/null +++ b/gfx/gfxTextureManager.cpp @@ -0,0 +1,1174 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- +#include "gfx/gfxTextureManager.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxCardProfile.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "gfx/bitmap/ddsUtils.h" +#include "core/strings/stringFunctions.h" +#include "core/util/safeDelete.h" +#include "core/resourceManager.h" +#include "core/volume.h" +#include "core/util/dxt5nmSwizzle.h" +#include "console/consoleTypes.h" + +/// Threshold of total VRAM under which we start scaling textures down... +/// +/// Textures are scaled by powers of 2, so each integer increase in the scale values +/// results in textures of half the width and half the height, or a quarter of the +/// source art's size, to be allocated. +/// +/// @note We set this a little bit above the actual value we want to run at low quality +/// mode in, so that we are sure of actually being in low-quality mode on that hardware. +static S32 gTextureScaleThreshold = 64; // 64 MB + +// 0 == auto, 1 == low, 2 == high +static S32 gTextureQualityMode = 0; + +// 0 == none, 1 == 1/(4^1), 2 == 1/(4^2), 3 = 1/(4^3) +static S32 gTextureReductionLevel = 1; + + +GFXTextureManager::EventSignal GFXTextureManager::smEventSignal; + +//----------------------------------------------------------------------------- + +void GFXTextureManager::init() +{ + Con::addVariable("pref::TextureManager::scaleThreshold", TypeS32, &gTextureScaleThreshold); + Con::addVariable("pref::TextureManager::qualityMode", TypeS32, &gTextureQualityMode); + Con::addVariable("pref::TextureManager::reductionLevel", TypeS32, &gTextureReductionLevel); +} + +GFXTextureManager::GFXTextureManager() +{ + mListHead = mListTail = NULL; + mTextureManagerState = GFXTextureManager::Living; + + // Set up the hash table + mHashCount = 1023; + mHashTable = new GFXTextureObject *[mHashCount]; + for(U32 i = 0; i < mHashCount; i++) + mHashTable[i] = NULL; + + mValidTextureQualityInfo = false; +} + +//----------------------------------------------------------------------------- + +GFXTextureManager::~GFXTextureManager() +{ + if( mHashTable ) + SAFE_DELETE_ARRAY( mHashTable ); +} + +//----------------------------------------------------------------------------- +void GFXTextureManager::_validateTextureMemory() +{ + if(mValidTextureQualityInfo) + return; + + if(GFX->getCardProfiler()->getVideoMemoryInMB() == 0) + { + // Cue off of quality mode, assuming the best... + switch(gTextureQualityMode) + { + case 0: + case 2: + mAboveTextureThreshold = true; + break; + + case 1: + mAboveTextureThreshold = false; + break; + } + + // And skip the rest. + return; + } + + const F32 totalVideoMem = GFX->getCardProfiler()->getVideoMemoryInMB(); + + // Let the user know what texture strategy we're using... + Con::printf( "Texture Manager" ); + Con::printf( " - Approx. Available VRAM: %.2f MB", totalVideoMem ); + Con::printf( " - Threshold VRAM: %d MB", gTextureScaleThreshold ); + + const char *qualityMode; + + // Use different heuristics based on the global... + bool force = false; + + switch(gTextureQualityMode) + { + case 0: + mAboveTextureThreshold = (totalVideoMem > gTextureScaleThreshold); + break; + + case 1: + force = true; + mAboveTextureThreshold = false; + break; + + case 2: + force = true; + mAboveTextureThreshold = true; + break; + } + + if(mAboveTextureThreshold) + qualityMode = "high"; + else + qualityMode = "low"; + + Con::printf(" - Quality mode: %s%s", qualityMode, (force ? " (forced)" : "" )); + + mValidTextureQualityInfo = true; +} + +U32 GFXTextureManager::getBitmapScalePower(GFXTextureProfile *profile) +{ + _validateTextureMemory(); + + if( mAboveTextureThreshold ) + { + return 0; + } + + if( profile->canDownscale() ) + { + return gTextureReductionLevel; + } + + return 0; +} + +bool GFXTextureManager::validateTextureQuality(GFXTextureProfile *profile, U32 &width, U32 &height) +{ + U32 scaleFactor; + if((scaleFactor = getBitmapScalePower(profile)) == 0) + return true; + + // Otherwise apply the appropriate scale... + width >>= scaleFactor; + height >>= scaleFactor; + + return true; + +} +//----------------------------------------------------------------------------- +void GFXTextureManager::kill() +{ + AssertFatal( mTextureManagerState != GFXTextureManager::Dead, "Texture Manager already killed!" ); + + GFXTextureObject *curr = mListHead; + GFXTextureObject *temp; + + // Actually delete all the textures we know about. + while( curr != NULL ) + { + temp = curr->mNext; + curr->kill(); + curr = temp; + } + + mTextureManagerState = GFXTextureManager::Dead; +} + +//----------------------------------------------------------------------------- +void GFXTextureManager::zombify() +{ + AssertFatal( mTextureManagerState != GFXTextureManager::Zombie, "Texture Manager already a zombie!" ); + + // Notify everyone that cares about the zombification! + smEventSignal.trigger( GFXZombify ); + + // Release unused pool textures. + cleanupPool(); + + // Free all the device copies of the textures. + GFXTextureObject *temp = mListHead; + while( temp != NULL ) + { + freeTexture( temp, true ); + temp = temp->mNext; + } + + // Finally, note our state. + mTextureManagerState = GFXTextureManager::Zombie; +} + +//----------------------------------------------------------------------------- +void GFXTextureManager::resurrect() +{ + // Reupload all the device copies of the textures. + GFXTextureObject *temp = mListHead; + + while( temp != NULL ) + { + refreshTexture( temp ); + + temp = temp->mNext; + } + + // Notify callback registries. + smEventSignal.trigger( GFXResurrect ); + + // Update our state. + mTextureManagerState = GFXTextureManager::Living; +} + +void GFXTextureManager::cleanupPool() +{ + PROFILE_SCOPE( GFXTextureManager_CleanupPool ); + + TexturePoolMap::Iterator iter = mTexturePool.begin(); + for ( ; iter != mTexturePool.end(); ) + { + if ( iter->value->getRefCount() == 1 ) + { + // This texture is unreferenced, so take the time + // now to completely remove it from the pool. + TexturePoolMap::Iterator unref = iter; + iter++; + unref->value = NULL; + mTexturePool.erase( unref ); + continue; + } + + iter++; + } +} + +//------------------------------------------------------------------------------ + +GFXTextureObject *GFXTextureManager::_lookupTexture( const char *hashName, const GFXTextureProfile *profile ) +{ + GFXTextureObject *ret = hashFind( hashName ); + + // TODO: Profile checking HERE + + return ret; +} + +//------------------------------------------------------------------------------ + +GFXTextureObject *GFXTextureManager::_lookupTexture( const DDSFile *ddsFile, const GFXTextureProfile *profile ) +{ + if( ddsFile->getTextureCacheString().isNotEmpty() ) + { + // Call _lookupTexture() + return _lookupTexture( ddsFile->getTextureCacheString(), profile ); + } + + return NULL; +} + +//----------------------------------------------------------------------------- + +GFXTextureObject *GFXTextureManager::createTexture( GBitmap *bmp, const String &resourceName, GFXTextureProfile *profile, bool deleteBmp ) +{ + AssertFatal(bmp, "GFXTextureManager::createTexture() - Got NULL bitmap!"); + + GFXTextureObject *cacheHit = _lookupTexture( resourceName, profile ); + if( cacheHit != NULL) + { + // Con::errorf("Cached texture '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); + if (deleteBmp) + delete bmp; + return cacheHit; + } + + return _createTexture( bmp, resourceName, profile, deleteBmp, NULL ); +} + +GFXTextureObject *GFXTextureManager::_createTexture( GBitmap *bmp, + const String &resourceName, + GFXTextureProfile *profile, + bool deleteBmp, + GFXTextureObject *inObj ) +{ + PROFILE_SCOPE( GFXTextureManager_CreateTexture_Bitmap ); + + // Massage the bitmap based on any resize rules. + U32 scalePower = getBitmapScalePower(profile); + + GBitmap *realBmp = bmp; + U32 realWidth = bmp->getWidth(); + U32 realHeight = bmp->getHeight(); + + if(scalePower && isPow2(bmp->getWidth()) && isPow2(bmp->getHeight()) && profile->canDownscale()) + { + // We only work with power of 2 textures for now, so we don't have + // to worry about padding... + + // If createPaddedBitmap is added back in, be sure to comment back in the + // call to delete padBmp below. + GBitmap * padBmp = bmp; //->createPaddedBitmap(); // createPaddedBitmap(bmp); + realWidth = padBmp->getWidth() >> scalePower; + realHeight = padBmp->getHeight() >> scalePower; + + if( realHeight == 0 ) + realHeight = 1; + + if( realWidth == 0 ) + realWidth = 1; + + realBmp = new GBitmap(realWidth, realHeight, false, bmp->getFormat()); + + padBmp->extrudeMipLevels(); + + // Copy to the new bitmap... + dMemcpy( + realBmp->getWritableBits(), padBmp->getBits(scalePower), + padBmp->getBytesPerPixel() * realWidth * realHeight + ); + + // This line is commented out because createPaddedBitmap is commented out. + // If that line is added back in, this line should be added back in. + // delete padBmp; + } + + // Call the internal create... (use the real* variables now, as they + // reflect the reality of the texture we are creating.) + U32 numMips = 0; + GFXFormat realFmt = realBmp->getFormat(); + _validateTexParams( realWidth, realHeight, profile, numMips, realFmt ); + + GFXTextureObject *ret; + if ( inObj ) + { + // If the texture has changed in dimensions + // then we need to recreate it. + if ( inObj->getWidth() != realWidth || + inObj->getHeight() != realHeight || + inObj->getFormat() != realFmt ) + ret = _createTextureObject( realHeight, realWidth, 0, realFmt, profile, numMips, false, 0, inObj ); + else + ret = inObj; + } + else + ret = _createTextureObject(realHeight, realWidth, 0, realFmt, profile, numMips ); + + if(!ret) + { + Con::errorf("GFXTextureManager - failed to create texture (1) for '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); + return NULL; + } + + // Extrude mip levels + // Don't do this for fonts! + if( ret->mMipLevels > 1 && ( realBmp->getNumMipLevels() == 1 ) && ( realBmp->getFormat() != GFXFormatA8 ) && + isPow2( realBmp->getHeight() ) && isPow2( realBmp->getWidth() ) && !profile->noMip() ) + { + // NOTE: This should really be done by extruding mips INTO a DDS file instead + // of modifying the gbitmap + realBmp->extrudeMipLevels(false); + } + + // If _validateTexParams kicked back a different format, than there needs to be + // a conversion + DDSFile *bmpDDS = NULL; + if( realBmp->getFormat() != realFmt ) + { + const GFXFormat oldFmt = realBmp->getFormat(); + + // TODO: Set it up so that ALL format conversions use DDSFile. Rip format + // switching out of GBitmap entirely. + if( !realBmp->setFormat( realFmt ) ) + { + // This is not the ideal implementation... + bmpDDS = DDSFile::createDDSFileFromGBitmap( realBmp ); + + bool convSuccess = false; + + if( bmpDDS != NULL ) + { + // This shouldn't live here, I don't think + switch( realFmt ) + { + case GFXFormatDXT1: + case GFXFormatDXT2: + case GFXFormatDXT3: + case GFXFormatDXT4: + case GFXFormatDXT5: + // If this is a Normal Map profile, than the data needs to be conditioned + // to use the swizzle trick + if( ret->mProfile->getType() == GFXTextureProfile::NormalMap ) + { + PROFILE_START(DXT_DXTNMSwizzle); + static DXT5nmSwizzle sDXT5nmSwizzle; + DDSUtil::swizzleDDS( bmpDDS, sDXT5nmSwizzle ); + PROFILE_END(); + } + + convSuccess = DDSUtil::squishDDS( bmpDDS, realFmt ); + break; + default: + AssertFatal(false, "Attempting to convert to a non-DXT format"); + break; + } + } + + if( !convSuccess ) + { + Con::errorf( "[GFXTextureManager]: Failed to change source format from %s to %s. Cannot create texture.", + GFXStringTextureFormat[oldFmt], GFXStringTextureFormat[realFmt] ); + delete bmpDDS; + + return NULL; + } + } +#ifdef TORQUE_DEBUG + else + { + //Con::warnf( "[GFXTextureManager]: Changed bitmap format from %s to %s.", + // GFXStringTextureFormat[oldFmt], GFXStringTextureFormat[realFmt] ); + } +#endif + } + + // Call the internal load... + if( ( bmpDDS == NULL && !_loadTexture( ret, realBmp ) ) || // If we aren't doing a DDS format change, use bitmap load + ( bmpDDS != NULL && !_loadTexture( ret, bmpDDS ) ) ) // If there is a DDS, than load that instead. A format change took place. + { + Con::errorf("GFXTextureManager - failed to load GBitmap for '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); + return NULL; + } + + // Do statistics and book-keeping... + + // - info for the texture... + ret->mTextureLookupName = resourceName; + ret->mBitmapSize.set(realWidth, realHeight,0); + +#ifdef TORQUE_DEBUG + if (resourceName.isNotEmpty()) + ret->mDebugDescription = resourceName; + else + ret->mDebugDescription = "Anonymous Texture Object"; + +#endif + + if(profile->doStoreBitmap()) + { + // NOTE: may store a downscaled copy! + SAFE_DELETE( ret->mBitmap ); + SAFE_DELETE( ret->mDDS ); + + if( bmpDDS == NULL ) + ret->mBitmap = new GBitmap( *realBmp ); + else + ret->mDDS = bmpDDS; + } + else + { + // Delete the DDS if we made one + SAFE_DELETE( bmpDDS ); + } + + if ( !inObj ) + _linkTexture( ret ); + + // - output debug info? + // Save texture for debug purpose + // static int texId = 0; + // char buff[256]; + // dSprintf(buff, sizeof(buff), "tex_%d", texId++); + // bmp->writePNGDebug(buff); + // texId++; + + // Before we delete the bitmap save our transparency flag + ret->mHasTransparency = realBmp->getHasTransparency(); + + // Some final cleanup... + if(realBmp != bmp) + SAFE_DELETE(realBmp); + if (deleteBmp) + SAFE_DELETE(bmp); + + // Return the new texture! + return ret; +} + + +GFXTextureObject *GFXTextureManager::createTexture( DDSFile *dds, GFXTextureProfile *profile, bool deleteDDS ) +{ + AssertFatal(dds, "GFXTextureManager::createTexture() - Got NULL dds!"); + + // Check the cache first... + GFXTextureObject *cacheHit = _lookupTexture( dds, profile ); + if ( cacheHit ) + { + // Con::errorf("Cached texture '%s'", (fileName.isNotEmpty() ? fileName.c_str() : "unknown")); + if( deleteDDS ) + delete dds; + + return cacheHit; + } + + return _createTexture( dds, profile, deleteDDS, NULL ); +} + +GFXTextureObject *GFXTextureManager::_createTexture( DDSFile *dds, + GFXTextureProfile *profile, + bool deleteDDS, + GFXTextureObject *inObj ) +{ + PROFILE_SCOPE( GFXTextureManager_CreateTexture_DDS ); + + const char *fileName = dds->getTextureCacheString(); + if( !fileName ) + fileName = "unknown"; + + // Ignore padding from the profile. + U32 numMips = dds->mMipMapCount; + GFXFormat fmt = dds->mFormat; + _validateTexParams( dds->getHeight(), dds->getWidth(), profile, numMips, fmt ); + + if( fmt != dds->mFormat ) + { + Con::errorf( "GFXTextureManager - failed to validate texture parameters for DDS file '%s'", fileName ); + return NULL; + } + + // Call the internal create... (use the real* variables now, as they + // reflect the reality of the texture we are creating.) + + GFXTextureObject *ret; + if ( inObj ) + { + // If the texture has changed in dimensions + // then we need to recreate it. + if ( inObj->getWidth() != dds->getWidth() || + inObj->getHeight() != dds->getHeight() || + inObj->getFormat() != fmt || + inObj->getMipLevels() != numMips ) + ret = _createTextureObject( dds->getHeight(), dds->getWidth(), 0, + fmt, profile, numMips, + true, 0, inObj ); + else + ret = inObj; + } + else + ret = _createTextureObject( dds->getHeight(), dds->getWidth(), 0, + fmt, profile, numMips, true ); + + + if(!ret) + { + Con::errorf("GFXTextureManager - failed to create texture (1) for '%s' DDSFile.", fileName); + return NULL; + } + + // Call the internal load... + if(!_loadTexture(ret, dds)) + { + Con::errorf("GFXTextureManager - failed to load DDS for '%s'", fileName); + return NULL; + } + + // Do statistics and book-keeping... + + // - info for the texture... + ret->mTextureLookupName = dds->getTextureCacheString(); + ret->mBitmapSize.set( dds->mHeight, dds->mWidth, 0 ); + +#ifdef TORQUE_DEBUG + ret->mDebugDescription = fileName; +#endif + + if(profile->doStoreBitmap()) + { + // NOTE: may store a downscaled copy! + SAFE_DELETE( ret->mBitmap ); + SAFE_DELETE( ret->mDDS ); + + ret->mDDS = new DDSFile( *dds ); + } + + if ( !inObj ) + _linkTexture( ret ); + + // - output debug info? + // Save texture for debug purpose + // static int texId = 0; + // char buff[256]; + // dSprintf(buff, sizeof(buff), "tex_%d", texId++); + // bmp->writePNGDebug(buff); + // texId++; + + // Save our transparency flag + ret->mHasTransparency = dds->getHasTransparency(); + + if( deleteDDS ) + delete dds; + + // Return the new texture! + return ret; +} + +GFXTextureObject *GFXTextureManager::createTexture( const Torque::Path &path, GFXTextureProfile *profile ) +{ + // We need to handle path's that have had "incorrect" + // extensions parsed out of the file name + Torque::Path correctPath = path; + + bool textureExt = false; + static const String sDDSExt( "dds" ); + + // Easiest case to handle is when there isn't an extension + if (path.getExtension().isEmpty()) + textureExt = true; + + // Since "dds" isn't registered with GBitmap currently we + // have to test it separately + if (sDDSExt.equal( path.getExtension(), String::NoCase ) ) + textureExt = true; + + // Now loop through the rest of the GBitmap extensions + // to see if we have any matches + for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ ) + { + // If we have gotten a match (either in this loop or before) + // then we can exit + if (textureExt) + break; + + const GBitmap::Registration ® = GBitmap::sRegistrations[i]; + const Vector &extensions = reg.extensions; + + for ( U32 j = 0; j < extensions.size(); ++j ) + { + if ( extensions[j].equal( path.getExtension(), String::NoCase ) ) + { + // Found a valid texture extension + textureExt = true; + break; + } + } + } + + // If we didn't find a valid texture extension then assume that + // the parsed out "extension" was actually intended to be part of + // the texture name so add it back + if (!textureExt) + { + correctPath.setFileName( Torque::Path::Join( path.getFileName(), '.', path.getExtension() ) ); + correctPath.setExtension(""); + } + + // Check the cache first... + String pathNoExt = Torque::Path::Join( correctPath.getRoot(), ':', correctPath.getPath() ); + pathNoExt = Torque::Path::Join( pathNoExt, '/', correctPath.getFileName() ); + + GFXTextureObject *retTexObj = _lookupTexture( pathNoExt, profile ); + if( retTexObj ) + return retTexObj; + + // If this is a valid file (has an extension) than load it + Path realPath; + if( Torque::FS::IsFile( correctPath ) ) + { + // Check for DDS + if( correctPath.getExtension() == sDDSExt ) + { + Resource dds = DDSFile::load( correctPath ); + if( dds != NULL ) + { + realPath = dds.getPath(); + retTexObj = createTexture( dds, profile, false ); + } + } + else // Let GBitmap take care of it + { + Resource bitmap = GBitmap::load( correctPath ); + if( bitmap != NULL ) + { + realPath = bitmap.getPath(); + retTexObj = createTexture( bitmap, pathNoExt, profile, false ); + } + } + } + else + { + // NOTE -- We should probably remove the code from GBitmap that tries different + // extensions for things GBitmap loads, and move it here. I think it should + // be a bit more involved than just a list of extensions. Some kind of + // extension registration thing, maybe. + + // Check to see if there is a .DDS file with this name (if no extension is provided) + Torque::Path tryDDSPath = pathNoExt; + if( tryDDSPath.getExtension().isNotEmpty() ) + tryDDSPath.setFileName( tryDDSPath.getFullFileName() ); + tryDDSPath.setExtension( "dds" ); + + if( Torque::FS::IsFile( tryDDSPath ) ) + { + Resource dds = DDSFile::load( tryDDSPath ); + if( dds != NULL ) + { + realPath = dds.getPath(); + retTexObj = createTexture( dds, profile, false ); + } + } + + // Otherwise, retTexObj stays NULL, and fall through to the generic GBitmap + // load. + } + + // If we still don't have a texture object yet, feed the correctPath to GBitmap and + // it will try a bunch of extensions + if( retTexObj == NULL ) + { + // Find and load the texture. + Resource bitmap = GBitmap::load( correctPath ); + + if ( bitmap != NULL ) + { + realPath = bitmap.getPath(); + retTexObj = createTexture( bitmap, pathNoExt, profile, false ); + } + } + + if ( retTexObj ) + { + // Store the path for later use. + retTexObj->mPath = realPath; + + // Register the texture file for change notifications. + FS::AddChangeNotification( retTexObj->getPath(), this, &GFXTextureManager::_onFileChanged ); + } + + // Could put in a final check for 'retTexObj == NULL' here as an error message. + + return retTexObj; +} + +GFXTextureObject *GFXTextureManager::createTexture( U32 width, U32 height, void *pixels, GFXFormat format, GFXTextureProfile *profile ) +{ + // For now, stuff everything into a GBitmap and pass it off... This may need to be revisited -- BJG + GBitmap *bmp = new GBitmap(width, height, 0, format); + dMemcpy(bmp->getWritableBits(), pixels, width * height * bmp->getBytesPerPixel()); + + return createTexture( bmp, String(), profile, true ); // TODO: Replace String() with a proper source string -patw +} + +GFXTextureObject *GFXTextureManager::createTexture( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, U32 numMipLevels, S32 antialiasLevel ) +{ + // Deal with sizing issues... + U32 localWidth = width; + U32 localHeight = height; + + // TODO: Format check HERE! -patw + + validateTextureQuality(profile, localWidth, localHeight); + + U32 numMips = numMipLevels; + GFXFormat checkFmt = format; + _validateTexParams( localWidth, localHeight, profile, numMips, checkFmt ); + + AssertFatal( checkFmt == format, "Anonymous texture didn't get the format it wanted." ); + + GFXTextureObject *outTex = NULL; + + // If this is a pooled profile then look there first. + if ( profile->isPooled() ) + { + outTex = _findPooledTexure( localWidth, localHeight, checkFmt, + profile, numMips, antialiasLevel ); + + // If we got a pooled texture then its + // already setup... just return it. + if ( outTex ) + return outTex; + } + + // Create the texture if we didn't get one from the pool. + if ( !outTex ) + { + outTex = _createTextureObject( localHeight, localWidth, 0, format, profile, numMips, false, antialiasLevel ); + + // Make sure we add it to the pool. + if ( outTex && profile->isPooled() ) + mTexturePool.insertEqual( profile, outTex ); + } + + if ( !outTex ) + { + Con::errorf("GFXTextureManager - failed to create anonymous texture."); + return NULL; + } + + // And do book-keeping... + // - texture info + outTex->mBitmapSize.set(localWidth, localHeight, 0); + outTex->mAntialiasLevel = antialiasLevel; + + // PWTODO: Need to assign this a lookup name before _linkTexture() is called + // otherwise it won't get a hash insert call + + _linkTexture( outTex ); + + return outTex; +} + +//----------------------------------------------------------------------------- +// createTexture - 3D volume +//----------------------------------------------------------------------------- +GFXTextureObject *GFXTextureManager::createTexture( U32 width, + U32 height, + U32 depth, + void *pixels, + GFXFormat format, + GFXTextureProfile *profile ) +{ + PROFILE_SCOPE( GFXTextureManager_CreateTexture_3D ); + + // Create texture... + GFXTextureObject *ret = _createTextureObject( height, width, depth, format, profile, 1 ); + + if(!ret) + { + Con::errorf("GFXTextureManager - failed to create anonymous texture."); + return NULL; + } + + // Call the internal load... + if( !_loadTexture( ret, pixels ) ) + { + Con::errorf("GFXTextureManager - failed to load volume texture" ); + return NULL; + } + + + // And do book-keeping... + // - texture info + ret->mBitmapSize.set( width, height, depth ); + + _linkTexture( ret ); + + + // Return the new texture! + return ret; +} + +GFXTextureObject* GFXTextureManager::_findPooledTexure( U32 width, + U32 height, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + S32 antialiasLevel ) +{ + PROFILE_SCOPE( GFXTextureManager_FindPooledTexure ); + + GFXTextureObject *outTex; + + // First see if we have a free one in the pool. + TexturePoolMap::Iterator iter = mTexturePool.find( profile ); + for ( ; iter != mTexturePool.end() && iter->key == profile; iter++ ) + { + outTex = iter->value; + + // If the reference count is 1 then we're the only + // ones holding on to this texture and we can hand + // it out if the size matches... else its in use. + if ( outTex->getRefCount() != 1 ) + continue; + + // Check for a match... if so return it. The assignment + // to a GFXTexHandle will take care of incrementing the + // reference count and keeping it from being handed out + // to anyone else. + if ( outTex->getFormat() == format && + outTex->getWidth() == width && + outTex->getHeight() == height && + outTex->getMipLevels() == numMipLevels && + outTex->mAntialiasLevel == antialiasLevel ) + return outTex; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- + +void GFXTextureManager::hashInsert(GFXTextureObject *object) +{ + if(object->mTextureLookupName.isNotEmpty()) + { + U32 key = object->mTextureLookupName.getHashCaseInsensitive() % mHashCount; + + object->mHashNext = mHashTable[key]; + mHashTable[key] = object; + } +} + +void GFXTextureManager::hashRemove(GFXTextureObject *object) +{ + // Don't hash stuff with no name. + if(object->mTextureLookupName.isEmpty()) + return; + + U32 key = object->mTextureLookupName.getHashCaseInsensitive() % mHashCount; + GFXTextureObject **walk = &mHashTable[key]; + while(*walk) + { + if(*walk == object) + { + *walk = object->mHashNext; + break; + } + walk = &((*walk)->mHashNext); + } +} + +GFXTextureObject* GFXTextureManager::hashFind( const String &name ) +{ + if( name.isEmpty() ) + return NULL; + + U32 key = name.getHashCaseInsensitive() % mHashCount; + GFXTextureObject *walk = mHashTable[key]; + for(; walk; walk = walk->mHashNext) + { + if( walk->mTextureLookupName.equal( name, String::NoCase ) ) + break; + } + + return walk; +} + +//----------------------------------------------------------------------------- +void GFXTextureManager::freeTexture(GFXTextureObject *texture, bool zombify) +{ + // Ok, let the backend deal with it. + _freeTexture(texture, zombify); +} + +void GFXTextureManager::refreshTexture(GFXTextureObject *texture) +{ + _refreshTexture(texture); +} + +//----------------------------------------------------------------------------- +// store texture (in Texture Manager) - add to linked list +//----------------------------------------------------------------------------- +void GFXTextureManager::_linkTexture( GFXTextureObject *obj ) +{ + // - info for the profile... + GFXTextureProfile::updateStatsForCreation(obj); + + // - info for the cache... + hashInsert(obj); + + // - info for the master list... + if( mListHead == NULL ) + mListHead = obj; + + if( mListTail != NULL ) + { + mListTail->mNext = obj; + } + + obj->mPrev = mListTail; + mListTail = obj; + +} + +void GFXTextureManager::deleteTexture( GFXTextureObject *texture ) +{ + if ( mTextureManagerState == GFXTextureManager::Dead ) + return; + + if( mListHead == texture ) + mListHead = texture->mNext; + if( mListTail == texture ) + mListTail = texture->mPrev; + + hashRemove( texture ); + + // If we have a path for the texture then + // remove change notifications for it. + Path texPath = texture->getPath(); + if ( !texPath.isEmpty() ) + FS::RemoveChangeNotification( texPath, this, &GFXTextureManager::_onFileChanged ); + + GFXTextureProfile::updateStatsForDeletion(texture); + + freeTexture( texture ); +} + +//----------------------------------------------------------------------------- +// Validate the parameters for creating a texture +//----------------------------------------------------------------------------- +void GFXTextureManager::_validateTexParams( const U32 width, const U32 height, + const GFXTextureProfile *profile, + U32 &inOutNumMips, GFXFormat &inOutFormat ) +{ + // Validate mipmap parameter. If this profile requests no mips, set mips to 1. + if( profile->noMip() ) + { + inOutNumMips = 1; + } + else if( !isPow2( width ) || !isPow2( height ) ) + { + // If a texture is not power-of-2 in size for both dimensions, it must + // have only 1 mip level. + inOutNumMips = 1; + } + + // Check format, and compatibility with texture profile requirements + bool autoGenSupp = ( inOutNumMips == 0 ); + + // If the format is non-compressed, and the profile requests a compressed format + // than change the format. + GFXFormat testingFormat = inOutFormat; + if( profile->getCompression() != GFXTextureProfile::None ) + { + const int offset = profile->getCompression() - GFXTextureProfile::DXT1; + testingFormat = GFXFormat( GFXFormatDXT1 + offset ); + + // No auto-gen mips on compressed textures + autoGenSupp = false; + } + + // inOutFormat is not modified by this method + bool chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); + + if( !chekFmt ) + { + // It tested for a compressed format, and didn't like it + if( testingFormat != inOutFormat && profile->getCompression() ) + testingFormat = inOutFormat; // Reset to requested format, and try again + + // Trying again here, so reset autogen mip + autoGenSupp = ( inOutNumMips == 0 ); + + // Wow more weak sauce. There should be a better way to do this. + switch( inOutFormat ) + { + case GFXFormatR8G8B8: + testingFormat = GFXFormatR8G8B8X8; + chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); + break; + + case GFXFormatA8: + testingFormat = GFXFormatR8G8B8A8; + chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); + break; + + default: + chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); + break; + } + } + + // Write back num mips that need to be generated by GBitmap + if( !chekFmt ) + Con::errorf( "Format %s not supported with specified profile.", GFXStringTextureFormat[inOutFormat] ); + else + { + inOutFormat = testingFormat; + + // If auto gen mipmaps were requested, and they aren't supported for whatever + // reason, than write out the number of mips that need to be generated. + // + // NOTE: Does this belong here? + if( inOutNumMips == 0 && !autoGenSupp ) + { + U32 currWidth = width; + U32 currHeight = height; + + inOutNumMips = 1; + do + { + currWidth >>= 1; + currHeight >>= 1; + if( currWidth == 0 ) + currWidth = 1; + if( currHeight == 0 ) + currHeight = 1; + + inOutNumMips++; + } while ( currWidth != 1 || currHeight != 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Reloads texture resource from disk +//----------------------------------------------------------------------------- +void GFXTextureManager::reloadTextureResource( const char *filename ) +{ + // PWNOTE: This should interact with the resource manager, not directly access + // texture structures like GBitmap. It also ignores the possibility of texture + // objects created with the KeepBitmap profile, as well as texture objects which + // were not created from a file. + + // Find and load the texture. + Resource bmp = GBitmap::load( filename ); + + if( bmp ) + { + GFXTextureObject *obj = hashFind( filename ); + if( obj ) + { + _loadTexture( obj, bmp ); + } + } +} + +void GFXTextureManager::_onFileChanged( const Torque::Path &path ) +{ + String pathNoExt = Torque::Path::Join( path.getRoot(), ':', path.getPath() ); + pathNoExt = Torque::Path::Join( pathNoExt, '/', path.getFileName() ); + + // See if we've got it loaded. + GFXTextureObject *obj = hashFind( pathNoExt ); + if ( !obj || path != obj->getPath() ) + return; + + Con::errorf( "[GFXTextureManager::_onFileChanged] : File changed [%s]", path.getFullPath().c_str() ); + + static const String sDDSExt( "dds" ); + if ( path.getExtension() == sDDSExt ) + { + Resource dds = DDSFile::load( path ); + if ( dds ) + _createTexture( dds, obj->mProfile, false, obj ); + } + else + { + Resource bmp = GBitmap::load( path ); + if( bmp ) + _createTexture( bmp, obj->mTextureLookupName, obj->mProfile, false, obj ); + } +} + +ConsoleFunctionGroupBegin( TextureManagment , "Texture mananagement functions."); + +ConsoleFunction( flushTextureCache, void, 1, 1, + "Releases all textures and resurrects the texture manager.") +{ + if ( !GFX || !TEXMGR ) + return; + + TEXMGR->zombify(); + TEXMGR->resurrect(); +} + +ConsoleFunction( cleanupTexturePool, void, 1, 1, + "Release the unused pooled textures in texture manager freeing up video memory.") +{ + if ( !GFX || !TEXMGR ) + return; + + TEXMGR->cleanupPool(); +} + +ConsoleFunctionGroupEnd( TextureManagment ); diff --git a/gfx/gfxTextureManager.h b/gfx/gfxTextureManager.h new file mode 100644 index 0000000..6a1a3db --- /dev/null +++ b/gfx/gfxTextureManager.h @@ -0,0 +1,301 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXTEXTUREMANAGER_H_ +#define _GFXTEXTUREMANAGER_H_ + +#ifndef _GFXTEXTUREOBJECT_H_ +#include "gfx/gfxTextureObject.h" +#endif +#ifndef _GBITMAP_H_ +#include "gfx/bitmap/gBitmap.h" +#endif +#ifndef _DDSFILE_H_ +#include "gfx/bitmap/ddsFile.h" +#endif +#ifndef _RESOURCEMANAGER_H_ +#include "core/resourceManager.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif + + +namespace Torque +{ + class Path; +} + + +class GFXTextureManager +{ +public: + enum + { + AA_MATCH_BACKBUFFER = -1 + }; + + GFXTextureManager(); + virtual ~GFXTextureManager(); + + /// Set up some global script interface stuff. + static void init(); + + /// Update width and height based on available resources. + /// + /// We provide a simple interface for managing texture memory usage. Specifically, + /// if the total video memory is below a certain threshold, we scale all texture + /// resolutions down by a specific factor (you can specify different scale factors + /// for different types of textures). + /// + /// @note The base GFXTextureManager class provides all the logic to do this scaling. + /// Subclasses need only implement getTotalVideoMemory(). + /// + /// @param type Type of the requested texture. This is used to determine scaling factors. + /// @param width Requested width - is changed to the actual width that should be used. + /// @param height Requested height - is changed to the actual height that should be used. + /// @return True if the texture request should be granted, false otherwise. + virtual bool validateTextureQuality(GFXTextureProfile *profile, U32 &width, U32 &height); + + U32 getBitmapScalePower(GFXTextureProfile *profile); + + virtual GFXTextureObject *createTexture( GBitmap *bmp, + const String &resourceName, + GFXTextureProfile *profile, + bool deleteBmp); + + virtual GFXTextureObject *createTexture( DDSFile *dds, + GFXTextureProfile *profile, + bool deleteDDS); + + virtual GFXTextureObject *createTexture( const Torque::Path &path, + GFXTextureProfile *profile ); + + virtual GFXTextureObject *createTexture( U32 width, + U32 height, + void *pixels, + GFXFormat format, + GFXTextureProfile *profile); + + virtual GFXTextureObject *createTexture( U32 width, + U32 height, + U32 depth, + void *pixels, + GFXFormat format, + GFXTextureProfile *profile ); + + virtual GFXTextureObject *createTexture( U32 width, + U32 height, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + S32 antialiasLevel); + + void deleteTexture( GFXTextureObject *texture ); + void reloadTexture( GFXTextureObject *texture ); + void reloadTextureResource( const char *filename ); + + /// @name Texture Necromancy + /// + /// Texture necromancy in three easy steps: + /// - If you want to destroy the texture manager, call kill(). + /// - If you want to switch resolutions, or otherwise reset the device, call zombify(). + /// - When you want to bring the manager back from zombie state, call resurrect(). + /// @{ + + /// + void kill(); + void zombify(); + void resurrect(); + + /// This releases any pooled textures which are + /// currently unused freeing up video memory. + void cleanupPool(); + + /// Registers a callback for texture zombify and resurrect events. + /// @see GFXTexCallbackCode + /// @see removeEventDelegate + template + static void addEventDelegate( T obj, U func ); + + /// Unregisteres a texture event callback. + /// @see addEventDelegate + template + static void removeEventDelegate( T obj, U func ) { smEventSignal.remove( obj, func ); } + + /// @} + +protected: + + //----------------------------------------------------------------------- + // General texture management data + //----------------------------------------------------------------------- + bool mAboveTextureThreshold; + bool mValidTextureQualityInfo; + + GFXTextureObject *mListHead; + GFXTextureObject *mListTail; + + // We have a hash table for fast texture lookups + GFXTextureObject **mHashTable; + U32 mHashCount; + GFXTextureObject *hashFind( const String &name ); + void hashInsert(GFXTextureObject *object); + void hashRemove(GFXTextureObject *object); + + enum TextureManagerState + { + Living, + Zombie, + Dead + + } mTextureManagerState; + + /// The texture pool collection type. + typedef HashTable > TexturePoolMap; + + /// All the allocated texture pool textures. + TexturePoolMap mTexturePool; + + //----------------------------------------------------------------------- + // Protected methods + //----------------------------------------------------------------------- + + /// Returns a free texture of the requested attributes from + /// from the shared texture pool. It returns NULL if no match + /// is found. + GFXTextureObject* _findPooledTexure( U32 width, + U32 height, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + S32 antialiasLevel ); + + GFXTextureObject *_createTexture( GBitmap *bmp, + const String &resourceName, + GFXTextureProfile *profile, + bool deleteBmp, + GFXTextureObject *inObj ); + + GFXTextureObject *_createTexture( DDSFile *dds, + GFXTextureProfile *profile, + bool deleteDDS, + GFXTextureObject *inObj ); + + /// Frees the API handles to the texture, for D3D this is a release call + /// + /// @note freeTexture MUST NOT DELETE THE TEXTURE OBJECT + virtual void freeTexture( GFXTextureObject *texture, bool zombify = false ); + + virtual void refreshTexture( GFXTextureObject *texture ); + + /// @group Internal Texture Manager Interface + /// + /// These pure virtual functions are overloaded by each API-specific + /// subclass. + /// + /// The order of calls is: + /// @code + /// _createTexture() + /// _loadTexture + /// _refreshTexture() + /// _refreshTexture() + /// _refreshTexture() + /// ... + /// _freeTexture() + /// @endcode + /// + /// @{ + + /// Allocate a texture with the internal API. + /// + /// @param height Height of the texture. + /// @param width Width of the texture. + /// @param depth Depth of the texture. (Will normally be 1 unless + /// we are doing a cubemap or volumetexture.) + /// @param format Pixel format of the texture. + /// @param profile Profile for the texture. + /// @param numMipLevels If not-NULL, then use that many mips. + /// If NULL create the full mip chain + /// @param antialiasLevel, Use GFXTextureManager::AA_MATCH_BACKBUFFER to match the backbuffer settings (for render targets that want to share + /// the backbuffer z buffer. 0 for no antialiasing, > 0 for levels that match the GFXVideoMode struct. + virtual GFXTextureObject *_createTextureObject( U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips = false, + S32 antialiasLevel = 0, + GFXTextureObject *inTex = NULL ) = 0; + + /// Load a texture from a proper DDSFile instance. + virtual bool _loadTexture(GFXTextureObject *texture, DDSFile *dds)=0; + + /// Load data into a texture from a GBitmap using the internal API. + virtual bool _loadTexture(GFXTextureObject *texture, GBitmap *bmp)=0; + + /// Load data into a texture from a raw buffer using the internal API. + /// + /// Note that the size of the buffer is assumed from the parameters used + /// for this GFXTextureObject's _createTexture call. + virtual bool _loadTexture(GFXTextureObject *texture, void *raw)=0; + + /// Refresh a texture using the internal API. + virtual bool _refreshTexture(GFXTextureObject *texture)=0; + + /// Free a texture (but do not delete the GFXTextureObject) using the internal + /// API. + /// + /// This is only called during zombification for textures which need it, so you + /// don't need to do any internal safety checks. + virtual bool _freeTexture(GFXTextureObject *texture, bool zombify=false)=0; + + /// @} + + void _validateTextureMemory(); + void _linkTexture( GFXTextureObject *obj ); + + void _validateTexParams( const U32 width, const U32 height, const GFXTextureProfile *profile, + U32 &inOutNumMips, GFXFormat &inOutFormat ); + + // New texture manager methods for the cleanup work: + GFXTextureObject *_lookupTexture( const char *filename, const GFXTextureProfile *profile ); + GFXTextureObject *_lookupTexture( const DDSFile *ddsFile, const GFXTextureProfile *profile ); + + void _onFileChanged( const Torque::Path &path ); + + /// The texture event signal type. + typedef Signal EventSignal; + + /// The texture event signal. + static EventSignal smEventSignal; +}; + + +template +inline void GFXTextureManager::addEventDelegate( T obj, U func ) +{ + EventSignal::DelegateSig d( obj, func ); + + AssertFatal( !smEventSignal.contains( d ), + "GFXTextureManager::addEventDelegate() - This is already registered!" ); + + smEventSignal.notify( d ); +} + +inline void GFXTextureManager::reloadTexture( GFXTextureObject *texture ) +{ + refreshTexture( texture ); +} + +/// Returns the GFXTextureManager singleton. Should only be +/// called after the GFX device has been initialized. +#define TEXMGR GFXDevice::get()->getTextureManager() + +#endif // _GFXTEXTUREMANAGER_H_ diff --git a/gfx/gfxTextureObject.cpp b/gfx/gfxTextureObject.cpp new file mode 100644 index 0000000..875f478 --- /dev/null +++ b/gfx/gfxTextureObject.cpp @@ -0,0 +1,231 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxTextureObject.h" + +#include "gfx/gfxDevice.h" +#include "gfx/gfxTextureManager.h" +#include "core/util/safeDelete.h" +#include "core/strings/stringFunctions.h" +#include "core/stream/fileStream.h" +#include "console/console.h" + +#ifdef TORQUE_DEBUG + +GFXTextureObject *GFXTextureObject::smHead = NULL; +U32 GFXTextureObject::smActiveTOCount = 0; + +U32 GFXTextureObject::dumpActiveTOs() +{ + if(!smActiveTOCount) + { + Con::printf( "GFXTextureObject::dumpActiveTOs - no active TOs to dump." ); + return 0; + } + + Con::printf("GFXTextureObject Usage Report - %d active TOs", smActiveTOCount); + Con::printf("---------------------------------------------------------------"); + Con::printf(" Addr Dim. GFXTextureProfile ProfilerPath DebugDescription"); + + for(GFXTextureObject *walk = smHead; walk; walk=walk->mDebugNext) + Con::printf(" %x (%4d, %4d) %s %s %s", walk, walk->getWidth(), + walk->getHeight(), walk->mProfile->getName().c_str(), walk->mDebugCreationPath.c_str(), walk->mDebugDescription.c_str()); + + Con::printf("----- dump complete -------------------------------------------"); + return smActiveTOCount; +} + +ConsoleFunction( dumpTextureObjects, void, 1, 1, "" ) +{ + GFXTextureObject::dumpActiveTOs(); +} + +#endif // TORQUE_DEBUG + +//----------------------------------------------------------------------------- +// GFXTextureObject +//----------------------------------------------------------------------------- +GFXTextureObject::GFXTextureObject(GFXDevice *aDevice, GFXTextureProfile *aProfile) +{ + mHashNext = mNext = mPrev = NULL; + + mDevice = aDevice; + mProfile = aProfile; + + mBitmap = NULL; + mMipLevels = 1; + mAntialiasLevel = 0; + + mTextureSize.set( 0, 0, 0 ); + + mDead = false; + + cacheId = 0; + cacheTime = 0; + + mBitmap = NULL; + mDDS = NULL; + + mFormat = GFXFormatR8G8B8; + + mHasTransparency = false; + +#if defined(TORQUE_DEBUG) + // Active object tracking. + smActiveTOCount++; + mDebugDescription = "Anonymous Texture Object"; +#if defined(TORQUE_ENABLE_PROFILER) + mDebugCreationPath = gProfiler->getProfilePath(); +#endif + mDebugNext = smHead; + mDebugPrev = NULL; + + if(smHead) + { + AssertFatal(smHead->mDebugPrev == NULL, "GFXTextureObject::GFXTextureObject - found unexpected previous in current head!"); + smHead->mDebugPrev = this; + } + + smHead = this; +#endif +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +GFXTextureObject::~GFXTextureObject() +{ + kill(); + +#ifdef TORQUE_DEBUG + if(smHead == this) + smHead = this->mDebugNext; + + if(mDebugNext) + mDebugNext->mDebugPrev = mDebugPrev; + + if(mDebugPrev) + mDebugPrev->mDebugNext = mDebugNext; + + mDebugPrev = mDebugNext = NULL; + + smActiveTOCount--; +#endif +} + +//----------------------------------------------------------------------------- +// kill - this function clears out the data in texture object. It's done like +// this because the texture object needs to release its pointers to textures +// before the GFXDevice is shut down. The texture objects themselves get +// deleted by the refcount structure - which may be after the GFXDevice has +// been destroyed. +//----------------------------------------------------------------------------- +void GFXTextureObject::kill() +{ + if( mDead ) + return; + +#ifdef TORQUE_DEBUG + // This makes sure that nobody is forgetting to call kill from the derived + // destructor. If they are, then we should get a pure virtual function + // call here. + pureVirtualCrash(); +#endif + + // If we're a dummy, don't do anything... + if( !mDevice || !mDevice->mTextureManager ) + { + mDead = true; + return; + } + + // Remove ourselves from the texture list and hash + mDevice->mTextureManager->deleteTexture(this); + + // Delete the stored bitmap. + SAFE_DELETE(mBitmap) + SAFE_DELETE(mDDS); + + // Clean up linked list + if(mNext) + mNext->mPrev = mPrev; + if(mPrev) + mPrev->mNext = mNext; + + mDead = true; +} + +const String GFXTextureObject::describeSelf() const +{ + return String::ToString(" (width: %4d, height: %4d) profile: %s creation path: %s", getWidth(), +#if defined(TORQUE_DEBUG) && defined(TORQUE_ENABLE_PROFILER) + getHeight(), mProfile->getName().c_str(), mDebugCreationPath.c_str()); +#else + getHeight(), mProfile->getName().c_str(), ""); +#endif +} + +F32 GFXTextureObject::getMaxUCoord() const +{ + return 1.0f; +} + +F32 GFXTextureObject::getMaxVCoord() const +{ + return 1.0f; +} + +U32 GFXTextureObject::getEstimatedSizeInBytes() const +{ + // If we have a DDS file ask it for its size. + if ( mDDS ) + return mDDS->getSizeInBytes(); + + // Else we need to calculate the size ourselves. + S32 texSizeX = mTextureSize.x; + S32 texSizeY = mTextureSize.y; + S32 volDepth = getMax( 1, mTextureSize.z ); + U32 byteSize = (U32)GFXDevice::formatByteSize( mFormat ); + U32 totalBytes = texSizeX * texSizeY * volDepth * byteSize; + + // Without mips we're done. + if ( mProfile->noMip() ) + return totalBytes; + + // NOTE: While we have mMipLevels, at the time of this + // comment it only stores the accessable mip levels and + // not the count of the autogen mips. + // + // So we figure out the mip count ourselves assuming its + // a complete mip chain. + while ( texSizeX > 1 || texSizeY > 1 ) + { + texSizeX = getMax( texSizeX >> 1, 1 ); + texSizeY = getMax( texSizeY >> 1, 1 ); + volDepth = getMax( volDepth >> 1, 1 ); + + totalBytes += texSizeX * texSizeY * volDepth * byteSize; + } + + return totalBytes; +} + +bool GFXTextureObject::dumpToDisk( const String &bmType, const String &path ) +{ + FileStream stream; + if ( !stream.open( path, Torque::FS::File::Write ) ) + return false; + + if ( mBitmap ) + return mBitmap->writeBitmap( bmType, stream ); + + GBitmap bitmap( getWidth(), getHeight(), false, getFormat() ); + copyToBmp( &bitmap ); + return bitmap.writeBitmap( bmType, stream ); +} + + diff --git a/gfx/gfxTextureObject.h b/gfx/gfxTextureObject.h new file mode 100644 index 0000000..05f5a9f --- /dev/null +++ b/gfx/gfxTextureObject.h @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- +#ifndef _GFXTEXTUREOBJECT_H_ +#define _GFXTEXTUREOBJECT_H_ + +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif +#ifndef _GFXTEXTUREPROFILE_H_ +#include "gfx/gfxTextureProfile.h" +#endif +#ifndef _GFXRESOURCE_H_ +#include "gfx/gfxResource.h" +#endif + +class Point2I; +class GFXDevice; +class GFXTextureProfile; +class GBitmap; +struct DDSFile; +class RectI; + +/// Contains information on a locked region of a texture. +/// +/// In general, to access a given pixel in a locked rectangle, use this +/// equation: +/// +/// @code +/// U8 *pixelAtXY = bits + x * pitch + y * pixelSizeInBytes; +/// @endcode +/// +/// @note D3DLOCKED_RECT and this structure match up. If you change this +/// assumption, be sure to revisit the D3D GFX implementation. +/// +/// @see GFXTextureObject::lock() +struct GFXLockedRect +{ + /// Pitch of the lock. This is the spacing in bytes of the start + /// of each row of the locked region. + int pitch; + + /// Pointer to the start of locked rectangle. + U8* bits; +}; + + +class GFXTextureObject : public StrongRefBase, public GFXResource +{ + public: + +#ifdef TORQUE_DEBUG + // In debug builds we provide a TOC leak tracking system. + static U32 smActiveTOCount; + static GFXTextureObject *smHead; + static U32 dumpActiveTOs(); + + String mDebugCreationPath; + String mDebugDescription; + GFXTextureObject *mDebugNext; + GFXTextureObject *mDebugPrev; +#endif + + /// The path to the texture file. + String mPath; + + bool mDead; + + U32 cacheId; + U32 cacheTime; + + // Linked List management + GFXDevice *mDevice; ///< Device this texture belongs to. + GFXTextureObject *mNext; ///< Next texture in the linked list + GFXTextureObject *mPrev; ///< Previous texture in the linked list + GFXTextureObject *mHashNext; ///< Used for hash table lookups. + + String mTextureLookupName; ///< This is the file name or other unique string used to hash this texture object + + + Point3I mBitmapSize; + Point3I mTextureSize; + U32 mMipLevels; + + // TODO: This looks unused in the engine... not even sure + // what it means. We should investigate and remove it. + S32 mAntialiasLevel; + + bool mHasTransparency; + + // These two should be removed, and replaced by a reference to a resource + // object, or data buffer. Something more generic. -patw + GBitmap *mBitmap; ///< GBitmap we are backed by. + DDSFile *mDDS; ///< DDSFile we're backed by. + + GFXTextureProfile *mProfile; + GFXFormat mFormat; + + + GFXTextureObject(GFXDevice * aDevice, GFXTextureProfile *profile); + virtual ~GFXTextureObject(); + + GBitmap *getBitmap(); + DDSFile *getDDS(); + U32 getWidth() const { return mTextureSize.x; } + U32 getHeight() const { return mTextureSize.y; } + const Point3I& getSize() const { return mTextureSize; } + U32 getDepth() const { return mTextureSize.z; } + U32 getMipLevels() const { return mMipLevels; } + U32 getBitmapWidth() const { return mBitmapSize.x; } + U32 getBitmapHeight() const { return mBitmapSize.y; } + U32 getBitmapDepth() const { return mBitmapSize.z; } + GFXFormat getFormat() const { return mFormat; } + + /// Returns true if this texture is a render target. + bool isRenderTarget() const { return mProfile->isRenderTarget(); } + + /// Returns the file path to the texture if + /// it was loaded from disk. + const String& getPath() const { return mPath; } + + virtual F32 getMaxUCoord() const; + virtual F32 getMaxVCoord() const; + + /// Returns the estimated video memory usage + /// in bytes including mipmaps. + U32 getEstimatedSizeInBytes() const; + + /// Acquire a lock on part of the texture. The GFXLockedRect returned + /// is managed by the GFXTextureObject and does not need to be freed. + virtual GFXLockedRect * lock( U32 mipLevel = 0, RectI *inRect = NULL ) = 0; + + /// Releases a lock previously acquired. Note that the mipLevel parameter + /// must match the corresponding lock! + virtual void unlock( U32 mipLevel = 0) = 0; + + // copy the texture data into the specified bitmap. + // - this texture object must be a render target. the function will assert if this is not the case. + // - you must have called allocateBitmap() on the input bitmap first. the bitmap should have the + // same dimensions as this texture. the bitmap format can be RGB or RGBA (in the latter case + // the alpha values from the texture are copied too) + // - returns true if successful, false otherwise + // - this process is not fast. + virtual bool copyToBmp(GBitmap* bmp) = 0; + +#ifdef TORQUE_DEBUG + + // It is important for any derived objects to define this method + // and also call 'kill' from their destructors. If you fail to + // do either, you will get a pure virtual function call crash + // in debug mode. This is a precaution to make sure you don't + // forget to add 'kill' to your destructor. + virtual void pureVirtualCrash() = 0; + +#endif + + virtual void kill(); + + /// Debug helper function for writing the texture to disk. + bool dumpToDisk( const String &bmType, const String &path ); + + // GFXResource interface + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const; +}; + +//----------------------------------------------------------------------------- + +inline GBitmap *GFXTextureObject::getBitmap() +{ + AssertFatal( mProfile->doStoreBitmap(), avar("GFXTextureObject::getBitmap - Cannot access bitmap for a '%s' texture.", mProfile->getName().c_str()) ); + + return mBitmap; +} + +inline DDSFile *GFXTextureObject::getDDS() +{ + AssertFatal( mProfile->doStoreBitmap(), avar("GFXTextureObject::getDDS - Cannot access bitmap for a '%s' texture.", mProfile->getName().c_str()) ); + + return mDDS; +} + +#endif // _GFXTEXTUREOBJECT_H_ diff --git a/gfx/gfxTextureProfile.cpp b/gfx/gfxTextureProfile.cpp new file mode 100644 index 0000000..2d57f42 --- /dev/null +++ b/gfx/gfxTextureProfile.cpp @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxTextureProfile.h" + +#include "gfx/gfxTextureObject.h" +#include "gfx/bitmap/gBitmap.h" +#include "core/strings/stringFunctions.h" +#include "console/console.h" + + +// Set up defaults... +GFX_ImplementTextureProfile(GFXDefaultRenderTargetProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | GFXTextureProfile::NoMipmap | GFXTextureProfile::RenderTarget, + GFXTextureProfile::None); +GFX_ImplementTextureProfile(GFXDefaultStaticDiffuseProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::Static, + GFXTextureProfile::None); +GFX_ImplementTextureProfile(GFXDefaultStaticNormalMapProfile, + GFXTextureProfile::NormalMap, + GFXTextureProfile::Static, + GFXTextureProfile::None); +GFX_ImplementTextureProfile(GFXDefaultStaticDXT5nmProfile, + GFXTextureProfile::NormalMap, + GFXTextureProfile::Static, + GFXTextureProfile::DXT5); +GFX_ImplementTextureProfile(GFXDefaultPersistentProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | GFXTextureProfile::Static | GFXTextureProfile::KeepBitmap, + GFXTextureProfile::None); +GFX_ImplementTextureProfile(GFXSystemMemProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | GFXTextureProfile::NoMipmap | GFXTextureProfile::SystemMemory, + GFXTextureProfile::None); +GFX_ImplementTextureProfile(GFXDefaultZTargetProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | GFXTextureProfile::NoMipmap | GFXTextureProfile::ZTarget, + GFXTextureProfile::None); + +//----------------------------------------------------------------------------- + +GFXTextureProfile *GFXTextureProfile::smHead = NULL; +U32 GFXTextureProfile::smProfileCount = 0; + +GFXTextureProfile::GFXTextureProfile(const String &name, Types type, U32 flag, Compression compression) +: mName( name ) +{ + // Take type, flag, and compression and produce a munged profile word. + mProfile = (type & (BIT(TypeBits + 1) - 1)) | + ((flag & (BIT(FlagBits + 1) - 1)) << TypeBits) | + ((compression & (BIT(CompressionBits + 1) - 1)) << (FlagBits + TypeBits)); + + // Stick us on the linked list. + mNext = smHead; + smHead = this; + ++smProfileCount; + + // Now do some sanity checking. (Ben is not proud of this code.) + AssertFatal( (testFlag(Dynamic) && !testFlag(Static)) + || (!testFlag(Dynamic) && !testFlag(Static)) + || (!testFlag(Dynamic) && testFlag(Static)), + "GFXTextureProfile::GFXTextureProfile - Cannot have a texture profile be both static and dynamic!"); + mDownscale = 0; +} + +void GFXTextureProfile::init() +{ + // Do something, anything? +} + +GFXTextureProfile * GFXTextureProfile::find(const String &name) +{ + // Not really necessary at this time. + return NULL; +} + +void GFXTextureProfile::collectStats( Flags flags, GFXTextureProfileStats *stats ) +{ + // Walk the profile list. + GFXTextureProfile *curr = smHead; + while ( curr ) + { + if ( curr->testFlag( flags ) ) + (*stats) += curr->getStats(); + + curr = curr->mNext; + } +} + +void GFXTextureProfile::updateStatsForCreation(GFXTextureObject *t) +{ + if(t->mProfile) + { + t->mProfile->incActiveCopies(); + t->mProfile->mStats.allocatedTextures++; + + U32 texSize = t->getHeight() * t->getWidth(); + U32 byteSize = t->getEstimatedSizeInBytes(); + + t->mProfile->mStats.allocatedTexels += texSize; + t->mProfile->mStats.allocatedBytes += byteSize; + + t->mProfile->mStats.activeTexels += texSize; + t->mProfile->mStats.activeBytes += byteSize; + } +} + +void GFXTextureProfile::updateStatsForDeletion(GFXTextureObject *t) +{ + if(t->mProfile) + { + t->mProfile->decActiveCopies(); + + U32 texSize = t->getHeight() * t->getWidth(); + U32 byteSize = t->getEstimatedSizeInBytes(); + + t->mProfile->mStats.activeTexels -= texSize; + t->mProfile->mStats.activeBytes -= byteSize; + } +} + + +ConsoleFunction( getTextureProfileStats, const char*, 1, 1, + "()\n" + "Returns a list of texture profiles in the format: \n" + " \n" ) +{ + // Grab a fairly greedy buffer. + char* result = Con::getReturnBuffer( GFXTextureProfile::getProfileCount() * 256 ); + result[0] = 0; + + char temp[256]; + + GFXTextureProfile *profile = GFXTextureProfile::getHead(); + while ( profile ) + { + const GFXTextureProfileStats &stats = profile->getStats(); + + F32 mb = ( stats.activeBytes / 1024.0f ) / 1024.0f; + + dSprintf( temp, 256, "%s %d %0.2f\n", + profile->getName().c_str(), + stats.activeCount, + mb ); + + dStrcat( result, temp ); + + profile = profile->getNext(); + } + + return result; +} + diff --git a/gfx/gfxTextureProfile.h b/gfx/gfxTextureProfile.h new file mode 100644 index 0000000..39d9f62 --- /dev/null +++ b/gfx/gfxTextureProfile.h @@ -0,0 +1,194 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXTEXTUREPROFILE_H_ +#define _GFXTEXTUREPROFILE_H_ + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +class GFXTextureObject; + +/// Helper struct for gathering profile stats. +class GFXTextureProfileStats +{ +public: + + /// Constructs and clears the stats. + GFXTextureProfileStats() { clear(); } + + /// Zeros all the stats. + void clear() + { + dMemset( this, 0, sizeof( GFXTextureProfileStats ) ); + } + + /// Adds stats together. + GFXTextureProfileStats& operator += ( const GFXTextureProfileStats &stats ) + { + activeCount += stats.activeCount; + activeTexels += stats.activeTexels; + activeBytes += stats.activeBytes; + allocatedTextures += stats.allocatedTextures; + allocatedTexels += stats.allocatedTexels; + allocatedBytes += stats.allocatedBytes; + return *this; + } + + U32 activeCount; ///< Count of textures of this profile type allocated. + U32 activeTexels; ///< Amount of texelspace currently allocated under this profile. + U32 activeBytes; ///< Amount of storage currently allocated under this profile. + U32 allocatedTextures; ///< Total number of textures allocated under this profile. + U32 allocatedTexels; ///< Total number of texels allocated under this profile. + U32 allocatedBytes; ///< Total number of bytes allocated under this profile. +}; + + +class GFXTextureProfile +{ +public: + enum Types + { + DiffuseMap, + NormalMap, + AlphaMap, + LuminanceMap + }; + + enum Flags + { + PreserveSize = BIT(0), ///< Never shrink this bitmap in low VRAM situations. + NoMipmap = BIT(1), ///< Do not generate mipmap chain for this texture. + SystemMemory = BIT(2), ///< System memory texture - isn't uploaded to card - useful as target for copying surface data out of video ram + RenderTarget = BIT(3), ///< This texture will be used as a render target. + Dynamic = BIT(4), ///< This texture may be refreshed. (Precludes Static) + Static = BIT(5), ///< This texture will never be modified once loaded. (Precludes Dynamic) + NoPadding = BIT(6), ///< Do not pad this texture if it's non pow2. + KeepBitmap = BIT(7), ///< Always keep a copy of this texture's bitmap. (Potentially in addition to the API managed copy?) + ZTarget = BIT(8), ///< This texture will be used as a Z target. + + /// Track and pool textures of this type for reuse. + /// + /// You should use this profile flag sparingly. Odd + /// sized textures and spikes in allocation can cause + /// the pool to contain unused textures which will remain + /// in memory until a flush occurs. + /// + Pooled = BIT(9), + }; + + enum Compression + { + None, + DXT1, + DXT2, + DXT3, + DXT4, + DXT5, + }; + + GFXTextureProfile(const String &name, Types type, U32 flags, Compression compression = None); + + // Accessors + String getName() const { return mName; }; + Types getType() const { return (Types)(mProfile & (BIT(TypeBits + 1) - 1)); } + const Compression getCompression() const { return (Compression)((mProfile >> (FlagBits + TypeBits)) & (BIT(CompressionBits + 1) - 1)); }; + + bool testFlag(Flags flag) const + { + return (mProfile & (flag << TypeBits)) != 0; + } + + // Mutators + const U32 getDownscale() const { return mDownscale; } + void setDownscale(const U32 shift) { mDownscale = shift; } + void incActiveCopies() { mStats.activeCount++; } + void decActiveCopies() { AssertFatal( mStats.activeCount != 0, "Ran out of extant copies!"); mStats.activeCount--; } + + // And static interface... + static void init(); + static GFXTextureProfile *find(const String &name); + static void updateStatsForCreation(GFXTextureObject *t); + static void updateStatsForDeletion(GFXTextureObject *t); + + /// Collects the total stats for all the profiles which + /// include any of the flag bits. + static void collectStats( Flags flags, GFXTextureProfileStats *stats ); + + /// Returns the total profile count in the list. + static U32 getProfileCount() { return smProfileCount; } + + /// Returns the head of the profile list. + static GFXTextureProfile* getHead() { return smHead; } + + /// Returns the next profile in the list. + GFXTextureProfile* getNext() const { return mNext; } + + /// Returns the allocation stats for this texture profile. + inline const GFXTextureProfileStats& getStats() const { return mStats; } + + // Helper functions... + inline bool doStoreBitmap() const { return testFlag(KeepBitmap); } + inline bool canDownscale() const { return !testFlag(PreserveSize); } + inline bool isDynamic() const { return testFlag(Dynamic); } + inline bool isRenderTarget() const { return testFlag(RenderTarget); } + inline bool isZTarget() const { return testFlag(ZTarget); } + inline bool isSystemMemory() const { return testFlag(SystemMemory); } + inline bool noMip() const { return testFlag(NoMipmap); } + inline bool isPooled() const { return testFlag(Pooled); } + +private: + /// These constants control the packing for the profile; if you add flags, types, or + /// compression info then make sure these are giving enough bits! + enum Constants + { + TypeBits = 2, + FlagBits = 10, + CompressionBits = 3, + }; + + String mName; ///< Name of this profile... + U32 mDownscale; ///< Amount to shift textures of this type down, if any. + U32 mProfile; ///< Stores a munged version of the profile data. + U32 mActiveCount; ///< Count of textures of this profile type allocated. + U32 mActiveTexels; ///< Amount of texelspace currently allocated under this profile. + U32 mActiveBytes; ///< Amount of storage currently allocated under this profile. + U32 mAllocatedTextures; ///< Total number of textures allocated under this profile. + U32 mAllocatedTexels; ///< Total number of texels allocated under this profile. + U32 mAllocatedBytes; ///< Total number of bytes allocated under this profile. + + /// The texture profile stats. + GFXTextureProfileStats mStats; + + /// The number of profiles in the system. + static U32 smProfileCount; + + /// Keep a list of all the profiles. + GFXTextureProfile *mNext; + static GFXTextureProfile *smHead; +}; + +#define GFX_DeclareTextureProfile(name) extern GFXTextureProfile name +#define GFX_ImplementTextureProfile(name, type, flags, compression) GFXTextureProfile name(#name, type, flags, compression) + +// Set up some defaults.. + +// Texture we can render to. +GFX_DeclareTextureProfile(GFXDefaultRenderTargetProfile); +// Standard diffuse texture that stays in system memory. +GFX_DeclareTextureProfile(GFXDefaultPersistentProfile); +// Generic diffusemap. This works in most cases. +GFX_DeclareTextureProfile(GFXDefaultStaticDiffuseProfile); +// Generic normal map. +GFX_DeclareTextureProfile(GFXDefaultStaticNormalMapProfile); +// DXT5 swizzled normal map +GFX_DeclareTextureProfile(GFXDefaultStaticDXT5nmProfile); +// Texture that resides in system memory - used to copy data to +GFX_DeclareTextureProfile(GFXSystemMemProfile); +// Depth buffer texture +GFX_DeclareTextureProfile(GFXDefaultZTargetProfile); + +#endif diff --git a/gfx/gfxTransformSaver.h b/gfx/gfxTransformSaver.h new file mode 100644 index 0000000..6d9842d --- /dev/null +++ b/gfx/gfxTransformSaver.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFX_GFXTRANSFORMSAVER_H_ +#define _GFX_GFXTRANSFORMSAVER_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif + + +/// Helper class to store viewport and matrix stack state, and restore it +/// later. +/// +/// When doing complex out-of-scene rendering, for instance, doing a +/// render to texture operation that needs its own transform state, it +/// is very easy to nuke important rendering state, like the viewport +/// or the projection matrix stored in vertex shader constant zero. +/// +/// This class simplifies save and cleanup of those properties. You can +/// either treat it as a stack helper, e.g. +/// +/// @code +/// void myFunc() +/// { +/// GFXTransformSaver saver; +/// +/// // Lots of nasty render state changes... +/// +/// // Everything is magically cleaned up when saver is destructed! +/// } +/// @endcode +/// +/// Or you can manually control when you do saves or restores: +/// +/// @code +/// void myFunc() +/// { +/// GFXTransformSaver saver(false, false); +/// +/// if(!somePrecondition) +/// return false; // Note early out. +/// +/// saver.save(); +/// +/// // Lots of nasty render state changes... +/// +/// // If we had passed (false, true) to the constructor then it would +/// // clean up automagically for us; but we want to do it manually. +/// saver.restore(); +/// } +/// @endcode +/// +class GFXTransformSaver +{ +protected: + + RectI mSavedViewport; + MatrixF mSavedProjectionMatrix, mSavedViewMatrix; + bool mHaveSavedData, mRestoreSavedDataOnDestruct; + +public: + + /// Constructor - controls how data is saved. + /// + /// @param saveDataNow If true, indicates that saveData() should be called + /// immediately. Otherwise, you can do it manually. + /// + /// @param restoreDataOnDestruct If true, indicates that restoreData() should + /// be called on destruct. Otherwise, you'll + /// have to do it manually. + GFXTransformSaver(bool saveDataNow = true, bool restoreDataOnDestruct = true) + { + mHaveSavedData = false; + + if(saveDataNow) + save(); + + mRestoreSavedDataOnDestruct = restoreDataOnDestruct; + } + + ~GFXTransformSaver() + { + if(mRestoreSavedDataOnDestruct) + restore(); + } + + void save() + { + AssertFatal(mHaveSavedData==false, "GFXTransformSaver::saveData - can't save twice!"); + mSavedViewport = GFX->getViewport(); + mSavedProjectionMatrix = GFX->getProjectionMatrix(); + mSavedViewMatrix = GFX->getViewMatrix(); + GFX->pushWorldMatrix(); + + // Note we have saved data! + mHaveSavedData = true; + } + + void restore() + { + AssertFatal(mHaveSavedData==true, "GFXTransformSaver::restoreData - no saved data to restore!"); + + GFX->popWorldMatrix(); + GFX->setViewMatrix(mSavedViewMatrix); + GFX->setProjectionMatrix(mSavedProjectionMatrix); + GFX->setViewport(mSavedViewport); + + // Once we've restored we do not want to be able to restore again... + mHaveSavedData = false; + + // And we don't want to restore on destruct! + mRestoreSavedDataOnDestruct = false; + } + + /// Returns the saved viewport. + const RectI& getViewport() const { return mSavedViewport; } + + /// Returns the saved projection matrix. + const MatrixF& getProjectionMatrix() const { return mSavedProjectionMatrix; } + + /// Returns the saved projection matrix. + const MatrixF& getViewMatrix() const { return mSavedViewMatrix; } +}; + +#endif // _GFX_GFXTRANSFORMSAVER_H_ \ No newline at end of file diff --git a/gfx/gfxVertexBuffer.cpp b/gfx/gfxVertexBuffer.cpp new file mode 100644 index 0000000..30eb410 --- /dev/null +++ b/gfx/gfxVertexBuffer.cpp @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxVertexBuffer.h" + +#include "core/strings/stringFunctions.h" +#include "gfx/gfxDevice.h" + + +void GFXVertexBufferHandleBase::set( GFXDevice *theDevice, + U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertexSize, + GFXBufferType type ) +{ + StrongRefPtr::operator=( theDevice->allocVertexBuffer( numVerts, vertexFormat, vertexSize, type ) ); +} + +const String GFXVertexBuffer::describeSelf() const +{ + const char *bufType; + + switch(mBufferType) + { + case GFXBufferTypeStatic: + bufType = "Static"; + break; + case GFXBufferTypeDynamic: + bufType = "Dynamic"; + break; + case GFXBufferTypeVolatile: + bufType = "Volatile"; + break; + default: + bufType = "Unknown"; + break; + } + + return String::ToString("numVerts: %i vertSize: %i bufferType: %s", mNumVerts, mVertexSize, bufType); +} \ No newline at end of file diff --git a/gfx/gfxVertexBuffer.h b/gfx/gfxVertexBuffer.h new file mode 100644 index 0000000..ed37b07 --- /dev/null +++ b/gfx/gfxVertexBuffer.h @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXVERTEXBUFFER_H_ +#define _GFXVERTEXBUFFER_H_ + +#ifndef _GFXSTRUCTS_H_ +#include "gfx/gfxStructs.h" +#endif + + +//***************************************************************************** +// GFXVertexBuffer - base vertex buffer class +//***************************************************************************** +class GFXVertexBuffer : public StrongRefBase, public GFXResource +{ + friend class GFXVertexBufferHandleBase; + friend class GFXDevice; + +public: + + /// Number of vertices in this buffer. + U32 mNumVerts; + + /// The vertex format for this buffer. + const GFXVertexFormat *mVertexFormat; + + /// Vertex size in bytes. + U32 mVertexSize; + + /// GFX buffer type (static, dynamic or volatile). + GFXBufferType mBufferType; + + /// Device this vertex buffer was allocated on. + GFXDevice *mDevice; + + bool isLocked; + U32 lockedVertexStart; + U32 lockedVertexEnd; + void* lockedVertexPtr; + U32 mVolatileStart; + + GFXVertexBuffer( GFXDevice *device, + U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertexSize, + GFXBufferType bufferType ) + : mDevice( device ), + mVolatileStart( 0 ), + mNumVerts( numVerts ), + mVertexFormat( vertexFormat ), + mVertexSize( vertexSize ), + mBufferType( bufferType ) + { + } + + virtual void lock(U32 vertexStart, U32 vertexEnd, void **vertexPtr) = 0; + virtual void unlock() = 0; + virtual void prepare() = 0; + + // GFXResource + virtual const String describeSelf() const; +}; + + +//***************************************************************************** +// GFXVertexBufferHandleBase +//***************************************************************************** +class GFXVertexBufferHandleBase : public StrongRefPtr +{ + friend class GFXDevice; + +protected: + + void set( GFXDevice *theDevice, + U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertexSize, + GFXBufferType type ); + + void* lock(U32 vertexStart, U32 vertexEnd) + { + if(vertexEnd == 0) + vertexEnd = getPointer()->mNumVerts; + AssertFatal(vertexEnd > vertexStart, "Can't get a lock with the end before the start."); + AssertFatal(vertexEnd <= getPointer()->mNumVerts || getPointer()->mBufferType == GFXBufferTypeVolatile, "Tried to get vertices beyond the end of the buffer!"); + getPointer()->lock(vertexStart, vertexEnd, &getPointer()->lockedVertexPtr); + return getPointer()->lockedVertexPtr; + } + void unlock() ///< unlocks the vertex data, making changes illegal. + { + getPointer()->unlock(); + } +}; + +//***************************************************************************** +// GFXVertexBufferHandle +//***************************************************************************** +template class GFXVertexBufferHandle : public GFXVertexBufferHandleBase +{ + void prepare() ///< sets this vertex buffer as the current vertex buffer for the device it was allocated on + { + getPointer()->prepare(); + } +public: + GFXVertexBufferHandle() { } + GFXVertexBufferHandle(GFXDevice *theDevice, U32 numVerts = 0, GFXBufferType bufferType = GFXBufferTypeVolatile) + { + set(theDevice, numVerts, bufferType); + } + void set(GFXDevice *theDevice, U32 numVerts = 0, GFXBufferType t = GFXBufferTypeVolatile) + { + GFXVertexBufferHandleBase::set(theDevice, numVerts, getGFXVertexFormat(), sizeof(T), t); + } + T *lock(U32 vertexStart = 0, U32 vertexEnd = 0) ///< locks the vertex buffer range, and returns a pointer to the beginning of the vertex array + ///< also allows the array operators to work on this vertex buffer. + { + return (T*)GFXVertexBufferHandleBase::lock(vertexStart, vertexEnd); + } + void unlock() + { + GFXVertexBufferHandleBase::unlock(); + } + + T& operator[](U32 index) ///< Array operator allows indexing into a locked vertex buffer. The debug version of the code + ///< will range check the array access as well as validate the locked vertex buffer pointer. + { + return ((T*)getPointer()->lockedVertexPtr)[index]; + } + const T& operator[](U32 index) const ///< Array operator allows indexing into a locked vertex buffer. The debug version of the code + ///< will range check the array access as well as validate the locked vertex buffer pointer. + { + index += getPointer()->mVolatileStart; + AssertFatal(getPointer()->lockedVertexPtr != NULL, "Cannot access verts from an unlocked vertex buffer!!!"); + AssertFatal(index >= getPointer()->lockedVertexStart && index < getPointer()->lockedVertexEnd, "Out of range vertex access!"); + index -= getPointer()->mVolatileStart; + return ((T*)getPointer()->lockedVertexPtr)[index]; + } + T& operator[](S32 index) ///< Array operator allows indexing into a locked vertex buffer. The debug version of the code + ///< will range check the array access as well as validate the locked vertex buffer pointer. + { + index += getPointer()->mVolatileStart; + AssertFatal(getPointer()->lockedVertexPtr != NULL, "Cannot access verts from an unlocked vertex buffer!!!"); + AssertFatal(index >= getPointer()->lockedVertexStart && index < getPointer()->lockedVertexEnd, "Out of range vertex access!"); + index -= getPointer()->mVolatileStart; + return ((T*)getPointer()->lockedVertexPtr)[index]; + } + const T& operator[](S32 index) const ///< Array operator allows indexing into a locked vertex buffer. The debug version of the code + ///< will range check the array access as well as validate the locked vertex buffer pointer. + { + index += getPointer()->mVolatileStart; + AssertFatal(getPointer()->lockedVertexPtr != NULL, "Cannot access verts from an unlocked vertex buffer!!!"); + AssertFatal(index >= getPointer()->lockedVertexStart && index < getPointer()->lockedVertexEnd, "Out of range vertex access!"); + index -= getPointer()->mVolatileStart; + return ((T*)getPointer()->lockedVertexPtr)[index]; + } + GFXVertexBufferHandle& operator=(GFXVertexBuffer *ptr) + { + StrongObjectRef::set(ptr); + return *this; + } + +}; + +/// This is a non-typed vertex buffer handle which can be +/// used when your vertex type is undefined until runtime. +class GFXVertexBufferDataHandle : public GFXVertexBufferHandleBase +{ + typedef GFXVertexBufferHandleBase Parent; + +protected: + + void prepare() { getPointer()->prepare(); } + + U32 mVertSize; + + const GFXVertexFormat *mVertexFormat; + +public: + + GFXVertexBufferDataHandle() + : mVertSize( 0 ), + mVertexFormat( NULL ) + { + } + + void set( GFXDevice *theDevice, + U32 vertSize, + const GFXVertexFormat *vertexFormat, + U32 numVerts, + GFXBufferType t ) + { + mVertSize = vertSize; + mVertexFormat = vertexFormat; + Parent::set( theDevice, numVerts, mVertexFormat, mVertSize, t); + } + + U8* lock( U32 vertexStart = 0, U32 vertexEnd = 0 ) + { + return (U8*)Parent::lock( vertexStart, vertexEnd ); + } + + void unlock() { Parent::unlock(); } + + GFXVertexBufferDataHandle& operator=( GFXVertexBuffer *ptr ) + { + StrongObjectRef::set(ptr); + return *this; + } +}; + + +#endif // _GFXVERTEXBUFFER_H_ + + diff --git a/gfx/gfxVertexColor.cpp b/gfx/gfxVertexColor.cpp new file mode 100644 index 0000000..230b96c --- /dev/null +++ b/gfx/gfxVertexColor.cpp @@ -0,0 +1,3 @@ +#include "gfx/gfxVertexColor.h" + +Swizzle *GFXVertexColor::mDeviceSwizzle = &Swizzles::null; diff --git a/gfx/gfxVertexColor.h b/gfx/gfxVertexColor.h new file mode 100644 index 0000000..a5130a3 --- /dev/null +++ b/gfx/gfxVertexColor.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2003 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _GFXVERTEXCOLOR_H_ +#define _GFXVERTEXCOLOR_H_ + +#ifndef _SWIZZLE_H_ +#include "core/util/swizzle.h" +#endif + + +class ColorI; + + +class GFXVertexColor +{ + +private: + U32 packedColorData; + static Swizzle *mDeviceSwizzle; + +public: + static void setSwizzle( Swizzle *val ) { mDeviceSwizzle = val; } + + GFXVertexColor() : packedColorData( 0xFFFFFFFF ) {} // White with full alpha + GFXVertexColor( const ColorI &color ) { set( color ); } + + void set( U8 red, U8 green, U8 blue, U8 alpha = 255 ) + { + packedColorData = red << 0 | green << 8 | blue << 16 | alpha << 24; + mDeviceSwizzle->InPlace( &packedColorData, sizeof( packedColorData ) ); + } + + void set( const ColorI &color ) + { + mDeviceSwizzle->ToBuffer( &packedColorData, (U8 *)&color, sizeof( packedColorData ) ); + } + + GFXVertexColor &operator=( const ColorI &color ) { set( color ); return *this; } + operator const U32 *() const { return &packedColorData; } + const U32& getPackedColorData() const { return packedColorData; } + + void getColor( ColorI *color ) const + { + mDeviceSwizzle->ToBuffer( color, &packedColorData, sizeof( packedColorData ) ); + } +}; + +#endif diff --git a/gfx/gfxVertexFormat.cpp b/gfx/gfxVertexFormat.cpp new file mode 100644 index 0000000..e50cc27 --- /dev/null +++ b/gfx/gfxVertexFormat.cpp @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxVertexFormat.h" + +#include "platform/profiler.h" +#include "core/util/hashFunction.h" + + +const String GFXSemantic::POSITION = String( "POSITION" ).intern(); +const String GFXSemantic::NORMAL = String( "NORMAL" ).intern(); +const String GFXSemantic::BINORMAL = String( "BINORMAL" ).intern(); +const String GFXSemantic::TANGENT = String( "TANGENT" ).intern(); +const String GFXSemantic::TANGENTW = String( "TANGENTW" ).intern(); +const String GFXSemantic::COLOR = String( "COLOR" ).intern(); +const String GFXSemantic::TEXCOORD = String( "TEXCOORD" ).intern(); + + + +U32 GFXVertexElement::getSizeInBytes() const +{ + switch ( mType ) + { + case GFXDeclType_Float: + return 4; + + case GFXDeclType_Float2: + return 8; + + case GFXDeclType_Float3: + return 12; + + case GFXDeclType_Float4: + return 16; + + case GFXDeclType_Color: + return 4; + + default: + return 0; + }; +} + + +GFXVertexFormat::GFXVertexFormat() + : mDirty( true ), + mHasColor( false ), + mHasNormalAndTangent( false ), + mTexCoordCount( 0 ) +{ + VECTOR_SET_ASSOCIATION( mElements ); +} + +void GFXVertexFormat::clear() +{ + mDirty = true; + mElements.clear(); +} + +void GFXVertexFormat::addElement( const String& semantic, GFXDeclType type, U32 index ) +{ + mDirty = true; + mElements.increment(); + mElements.last().mSemantic = semantic.intern(); + mElements.last().mSemanticIndex = index; + mElements.last().mType = type; +} + +const String& GFXVertexFormat::getDescription() const +{ + if ( mDirty ) + const_cast(this)->_updateDirty(); + + return mDescription; +} + +bool GFXVertexFormat::hasNormalAndTangent() const +{ + if ( mDirty ) + const_cast(this)->_updateDirty(); + + return mHasNormalAndTangent; +} + +bool GFXVertexFormat::hasColor() const +{ + if ( mDirty ) + const_cast(this)->_updateDirty(); + + return mHasColor; +} + +U32 GFXVertexFormat::getTexCoordCount() const +{ + if ( mDirty ) + const_cast(this)->_updateDirty(); + + return mTexCoordCount; +} + +bool GFXVertexFormat::isEqual( const GFXVertexFormat &format ) const +{ + return getDescription().equal( format.getDescription(), String::NoCase ); +} + +void GFXVertexFormat::_updateDirty() +{ + PROFILE_SCOPE( FeatureSet_UpdateDirty ); + + mTexCoordCount = 0; + + mHasColor = false; + + bool hasNormal = false; + bool hasTangent = false; + + mDescription.clear(); + + for ( U32 i=0; i < mElements.size(); i++ ) + { + const GFXVertexElement &element = mElements[i]; + + mDescription += String::ToString( "%s,%d,%d\n", element.mSemantic.c_str(), + element.mSemanticIndex, + element.mType ); + + if ( element.isSemantic( GFXSemantic::NORMAL ) ) + hasNormal = true; + else if ( element.isSemantic( GFXSemantic::TANGENT ) ) + hasTangent = true; + else if ( element.isSemantic( GFXSemantic::COLOR ) ) + mHasColor = true; + else if ( element.isSemantic( GFXSemantic::TEXCOORD ) ) + ++mTexCoordCount; + } + + mHasNormalAndTangent = hasNormal && hasTangent; + + // Make sure the hash is created here once + // so that it can be used in comparisions later. + mDescription.getHashCaseInsensitive(); + + mDirty = false; +} diff --git a/gfx/gfxVertexFormat.h b/gfx/gfxVertexFormat.h new file mode 100644 index 0000000..c8dba52 --- /dev/null +++ b/gfx/gfxVertexFormat.h @@ -0,0 +1,268 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXVERTEXFORMAT_H_ +#define _GFXVERTEXFORMAT_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif + + +/// The known Torque vertex element semantics. You can use +/// other semantic strings, but they will be interpreted as +/// a TEXCOORD. +/// @see GFXVertexElement +/// @see GFXVertexFormat +struct GFXSemantic +{ + static const String POSITION; + static const String NORMAL; + static const String BINORMAL; + static const String TANGENT; + static const String TANGENTW; + static const String COLOR; + static const String TEXCOORD; +}; + + +/// The element structure helps define the data layout +/// for GFXVertexFormat. +/// +/// @see GFXVertexFormat +/// +class GFXVertexElement +{ + friend class GFXVertexFormat; + +protected: + + /// A valid Torque shader symantic. + /// @see GFXSemantic + String mSemantic; + + /// The semantic index is used where there are + /// multiple semantics of the same type. For + /// instance with texcoords. + U32 mSemanticIndex; + + /// The element type. + GFXDeclType mType; + +public: + + /// Default constructor. + GFXVertexElement() + : mSemanticIndex( 0 ), + mType( GFXDeclType_Float4 ) + { + } + + /// Copy constructor. + GFXVertexElement( const GFXVertexElement &elem ) + : mSemantic( elem.mSemantic ), + mSemanticIndex( elem.mSemanticIndex ), + mType( elem.mType ) + { + } + + /// Returns the semantic name which is usually a + /// valid Torque semantic. + /// @see GFXSemantic + const String& getSemantic() const { return mSemantic; } + + /// Returns the semantic index which is used where there + /// are multiple semantics of the same type. For instance + /// with texcoords. + U32 getSemanticIndex() const { return mSemanticIndex; } + + /// Returns the type for the semantic. + GFXDeclType getType() const { return mType; } + + /// Returns true of the semantic matches. + bool isSemantic( const String& str ) const { return ( mSemantic == str ); } + + /// Returns the size in bytes of the semantic type. + U32 getSizeInBytes() const; + +}; + + +/// The vertex format structure usually created via the declare and +/// implement macros. +/// +/// You can use this class directly to create a vertex format, but +/// note that it is expected to live as long as the VB that uses it +/// exists. +/// +/// @see GFXDeclareVertexFormat +/// @see GFXImplementVertexFormat +/// @see GFXVertexElement +/// +class GFXVertexFormat +{ +public: + + /// Default constructor for an empty format. + GFXVertexFormat(); + + /// Returns a 64bit hash string which uniquely identifies + /// this vertex format. + //const String& getCacheString() const; + + /// Returns a unique description string for this vertex format. + const String& getDescription() const; + + /// Clears all the vertex elements. + void clear(); + + /// Adds a vertex element to the format. + /// + /// @param semantic A valid Torque semantic string. + /// @param type The element type. + /// @param index The semantic index which is typically only used for texcoords. + /// + void addElement( const String& semantic, GFXDeclType type, U32 index = 0 ); + + /// Returns true if the format has a normal + /// and a tangent at each vertex. + bool hasNormalAndTangent() const; + + /// Returns true if there is at least one color + /// symantic in the vertex format. + bool hasColor() const; + + /// Returns the texture coordinate count by + /// counting the number of "TEXCOORD" semantics. + U32 getTexCoordCount() const; + + /// Returns true if these two formats are equal. + bool isEqual( const GFXVertexFormat &format ) const; + + /// Returns the total elements in this format. + U32 getElementCount() const { return mElements.size(); } + + /// Returns the vertex element by index. + const GFXVertexElement& getElement( U32 index ) const { return mElements[index]; } + +protected: + + /// Recreates the description and state when + /// the format has been modified. + void _updateDirty(); + + /// Set when the element list is changed. + bool mDirty; + + /// Is set to true if there is a normal and + /// a tanget and/or binormal in this format. + bool mHasNormalAndTangent; + + /// Is true if there is at least one color + /// symantic in the vertex format. + bool mHasColor; + + /// The texture coordinate count by counting the + /// number of "TEXCOORD" semantics. + U32 mTexCoordCount; + + /// The a string which uniquely identifies + /// this vertex format. + String mDescription; + + /// The elements of the vertex format. + Vector mElements; + +}; + + +/// This template class is usused to initialize the format in +/// the GFXImplement/DeclareVertexFormat macros. You shouldn't +/// need to use it directly in your code. +/// +/// @see GFXVertexFormat +/// @see GFXImplementVertexFormat +/// +template +class _GFXVertexFormatConstructor : public GFXVertexFormat +{ +protected: + + void _construct(); + +public: + + _GFXVertexFormatConstructor() { _construct(); } +}; + + +/// Helper template function which returns the correct +/// GFXVertexFormat object for a vertex structure. +/// @see GFXVertexFormat +template inline const GFXVertexFormat* getGFXVertexFormat(); + +#ifdef TORQUE_OS_XENON + + /// On the Xbox360 we want we want to be sure that verts + /// are on aligned boundariess. + #define GFX_VERTEX_STRUCT __declspec(align(16)) struct + +#else + #define GFX_VERTEX_STRUCT struct +#endif + + +/// The vertex format declaration which is usally placed in your header +/// file. It should be used in conjunction with the implementation macro. +/// +/// @param name The name for the vertex structure. +/// +/// @code +/// +/// // A simple vertex format declaration. +/// GFXDeclareVertexFormat( GFXVertexPCT ) +/// { +/// Point3F pos; +/// GFXVertexColor color; +/// Point2F texCoord; +/// } +/// +/// @endcode +/// +/// @see GFXImplementVertexFormat +/// +#define GFXDeclareVertexFormat( name ) \ + GFX_VERTEX_STRUCT name; \ + extern const GFXVertexFormat _gfxVertexFormat##name; \ + template<> inline const GFXVertexFormat* getGFXVertexFormat() { static _GFXVertexFormatConstructor vertexFormat; return &vertexFormat; } \ + GFX_VERTEX_STRUCT name \ + + +/// The vertex format implementation which is usally placed in your source +/// file. It should be used in conjunction with the declaration macro. +/// +/// @param name The name of the vertex structure. +/// +/// @code +/// +/// // A simple vertex format implementation. +/// GFXImplementVertexFormat( GFXVertexPCT ) +/// { +/// addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); +/// addElement( GFXSemantic::COLOR, GFXDeclType_Color ); +/// addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +/// } +/// +/// @endcode +/// +/// @see GFXDeclareVertexFormat +/// +#define GFXImplementVertexFormat( name ) \ + template<> void _GFXVertexFormatConstructor::_construct() \ + +#endif // _GFXVERTEXFORMAT_H_ diff --git a/gfx/gfxVertexTypes.cpp b/gfx/gfxVertexTypes.cpp new file mode 100644 index 0000000..5f55741 --- /dev/null +++ b/gfx/gfxVertexTypes.cpp @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gfxVertexTypes.h" + + +GFXImplementVertexFormat( GFXVertexP ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); +} + +GFXImplementVertexFormat( GFXVertexPT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +} + +GFXImplementVertexFormat( GFXVertexPTT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 1 ); +} + +GFXImplementVertexFormat( GFXVertexPTTT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 1 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 2 ); +} + +GFXImplementVertexFormat( GFXVertexPC ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); +} + +GFXImplementVertexFormat( GFXVertexPCN ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); +} + +GFXImplementVertexFormat( GFXVertexPCT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +} + +GFXImplementVertexFormat( GFXVertexPCTT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 1 ); +} + +GFXImplementVertexFormat( GFXVertexPN ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); +} + +GFXImplementVertexFormat( GFXVertexPNT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +} + +GFXImplementVertexFormat( GFXVertexPNTT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TANGENT, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +} + +GFXImplementVertexFormat( GFXVertexPNTBT ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TANGENT, GFXDeclType_Float3 ); + addElement( GFXSemantic::BINORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); +} + +GFXImplementVertexFormat( GFXVertexPNTTB ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TANGENT, GFXDeclType_Float3 ); + addElement( GFXSemantic::BINORMAL, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 1 ); +} diff --git a/gfx/gfxVertexTypes.h b/gfx/gfxVertexTypes.h new file mode 100644 index 0000000..6e799de --- /dev/null +++ b/gfx/gfxVertexTypes.h @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXVERTEXTYPES_H_ +#define _GFXVERTEXTYPES_H_ + +#ifndef _GFXVERTEXFORMAT_H_ +#include "gfx/gfxVertexFormat.h" +#endif +#ifndef _GFXVERTEXCOLOR_H_ +#include "gfx/gfxVertexColor.h" +#endif +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +// Disable warning 'structure was padded due to __declspec(align())' +// It's worth noting, though, that GPUs are heavily cache dependent and using +// 4-byte aligned vertex sizes is best. +#pragma warning(disable: 4324) + +GFXDeclareVertexFormat( GFXVertexP ) +{ + Point3F point; +}; + +GFXDeclareVertexFormat( GFXVertexPT ) +{ + Point3F point; + Point2F texCoord; +}; + +GFXDeclareVertexFormat( GFXVertexPTT ) +{ + Point3F point; + Point2F texCoord1; + Point2F texCoord2; +}; + +GFXDeclareVertexFormat( GFXVertexPTTT ) +{ + Point3F point; + Point2F texCoord1; + Point2F texCoord2; + Point2F texCoord3; +}; + +GFXDeclareVertexFormat( GFXVertexPC ) +{ + Point3F point; + GFXVertexColor color; +}; + +GFXDeclareVertexFormat( GFXVertexPCN ) +{ + Point3F point; + Point3F normal; + GFXVertexColor color; +}; + +GFXDeclareVertexFormat( GFXVertexPCT ) +{ + Point3F point; + GFXVertexColor color; + Point2F texCoord; +}; + +GFXDeclareVertexFormat( GFXVertexPCTT ) +{ + Point3F point; + GFXVertexColor color; + Point2F texCoord; + Point2F texCoord2; +}; + +GFXDeclareVertexFormat( GFXVertexPN ) +{ + Point3F point; + Point3F normal; +}; + +GFXDeclareVertexFormat( GFXVertexPNT ) +{ + Point3F point; + Point3F normal; + Point2F texCoord; +}; + +GFXDeclareVertexFormat( GFXVertexPNTT ) +{ + Point3F point; + Point3F normal; + Point3F tangent; + Point2F texCoord; +}; + +GFXDeclareVertexFormat( GFXVertexPNTBT ) +{ + Point3F point; + Point3F normal; + Point3F tangent; + Point3F binormal; + Point2F texCoord; +}; + +/* + +DEFINE_VERT( GFXVertexPCNT, + GFXVertexFlagXYZ | GFXVertexFlagNormal | GFXVertexFlagDiffuse | GFXVertexFlagTextureCount1 | GFXVertexFlagUV0) +{ + Point3F point; + Point3F normal; + GFXVertexColor color; + Point2F texCoord; +}; + +DEFINE_VERT( GFXVertexPCNTT, + GFXVertexFlagXYZ | GFXVertexFlagNormal | GFXVertexFlagDiffuse | GFXVertexFlagTextureCount2 | GFXVertexFlagUV0 | GFXVertexFlagUV1) +{ + Point3F point; + Point3F normal; + GFXVertexColor color; + Point2F texCoord[2]; +}; +*/ + +GFXDeclareVertexFormat( GFXVertexPNTTB ) +{ + Point3F point; + Point3F normal; + Point3F T; + Point3F B; + Point2F texCoord; + Point2F texCoord2; +}; + +/* +DEFINE_VERT( GFXVertexPNTB, + GFXVertexFlagXYZ | GFXVertexFlagNormal | GFXVertexFlagTextureCount2 | + GFXVertexFlagUV0 | GFXVertexFlagUVW1 ) +{ + Point3F point; + Point3F normal; + Point2F texCoord; + Point3F binormal; +}; +*/ + +#endif // _GFXVERTEXTYPES_H_ diff --git a/gfx/gl/gfxGLAppleFence.cpp b/gfx/gl/gfxGLAppleFence.cpp new file mode 100644 index 0000000..dad8921 --- /dev/null +++ b/gfx/gl/gfxGLAppleFence.cpp @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gl/gfxGLAppleFence.h" + +GFXGLAppleFence::GFXGLAppleFence(GFXDevice* device) : GFXFence(device), mIssued(false) +{ + glGenFencesAPPLE(1, &mHandle); +} + +GFXGLAppleFence::~GFXGLAppleFence() +{ + glDeleteFencesAPPLE(1, &mHandle); +} + +void GFXGLAppleFence::issue() +{ + glSetFenceAPPLE(mHandle); + mIssued = true; +} + +GFXFence::FenceStatus GFXGLAppleFence::getStatus() const +{ + if(!mIssued) + return GFXFence::Unset; + + GLboolean res = glTestFenceAPPLE(mHandle); + return res ? GFXFence::Processed : GFXFence::Pending; +} + +void GFXGLAppleFence::block() +{ + if(!mIssued) + return; + + glFinishFenceAPPLE(mHandle); +} + +void GFXGLAppleFence::zombify() +{ + glDeleteFencesAPPLE(1, &mHandle); +} + +void GFXGLAppleFence::resurrect() +{ + glGenFencesAPPLE(1, &mHandle); +} + +const String GFXGLAppleFence::describeSelf() const +{ + return String::ToString(" GL Handle: %i", mHandle); +} diff --git a/gfx/gl/gfxGLAppleFence.h b/gfx/gl/gfxGLAppleFence.h new file mode 100644 index 0000000..414e20e --- /dev/null +++ b/gfx/gl/gfxGLAppleFence.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLAPPLEFENCE_H_ +#define _GFXGLAPPLEFENCE_H_ + +#include "gfx/gfxFence.h" +#include "gfx/gl/ggl/ggl.h" + +class GFXGLAppleFence : public GFXFence +{ +public: + GFXGLAppleFence(GFXDevice* device); + virtual ~GFXGLAppleFence(); + + // GFXFence interface + virtual void issue(); + virtual FenceStatus getStatus() const; + virtual void block(); + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + virtual const String describeSelf() const; + +private: + GLuint mHandle; + bool mIssued; +}; + +#endif \ No newline at end of file diff --git a/gfx/gl/gfxGLCardProfiler.cpp b/gfx/gl/gfxGLCardProfiler.cpp new file mode 100644 index 0000000..c8284c8 --- /dev/null +++ b/gfx/gl/gfxGLCardProfiler.cpp @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gl/gfxGLCardProfiler.h" +#include "gfx/gl/gfxGLDevice.h" +#include "gfx/gl/gfxGLEnumTranslate.h" + +void GFXGLCardProfiler::init() +{ + mChipSet = reinterpret_cast(glGetString(GL_VENDOR)); + + // get the major and minor parts of the GL version. These are defined to be + // in the order "[major].[minor] [other]|[major].[minor].[release] [other] in the spec + const char *versionStart = reinterpret_cast(glGetString(GL_VERSION)); + const char *versionEnd = versionStart; + // get the text for the version "x.x.xxxx " + for( S32 tok = 0; tok < 2; ++tok ) + { + char *text = dStrdup( versionEnd ); + dStrtok(text, ". "); + versionEnd += dStrlen( text ) + 1; + dFree( text ); + } + + mRendererString = "GL"; + mRendererString += String::SpanToString(versionStart, versionEnd - 1); + + mCardDescription = reinterpret_cast(glGetString(GL_RENDERER)); + mVersionString = reinterpret_cast(glGetString(GL_VERSION)); + + mVideoMemory = static_cast(GFX)->getTotalVideoMemory(); + + Parent::init(); + + // Set new enums here so if our profile script forces this to be false we keep the GL_ZEROs. + if(queryProfile("GL::suppFloatTexture")) + { + GFXGLTextureInternalFormat[GFXFormatR16G16F] = GL_RGBA_FLOAT16_ATI; + GFXGLTextureFormat[GFXFormatR16G16F] = GL_RGBA; + GFXGLTextureInternalFormat[GFXFormatR16G16B16A16F] = GL_RGBA_FLOAT16_ATI; + GFXGLTextureInternalFormat[GFXFormatR32G32B32A32F] = GL_RGBA_FLOAT32_ATI; + GFXGLTextureInternalFormat[GFXFormatR32F] = GL_RGBA_FLOAT32_ATI; + } + + if(queryProfile("GL::suppMipLodBias")) + { + GFXGLSamplerState[GFXSAMPMipMapLODBias] = GL_TEXTURE_LOD_BIAS_EXT; + } +} + +void GFXGLCardProfiler::setupCardCapabilities() +{ + GLint maxTexSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); + + const char* versionString = reinterpret_cast(glGetString(GL_VERSION)); + F32 glVersion = dAtof(versionString); + + // OpenGL doesn't have separate maximum width/height. + setCapability("maxTextureWidth", maxTexSize); + setCapability("maxTextureHeight", maxTexSize); + setCapability("maxTextureSize", maxTexSize); + + // If extensions haven't been inited, we're in trouble here. + bool suppVBO = (gglHasExtension(GL_ARB_vertex_buffer_object) || glVersion >= 1.499f); + setCapability("GL::suppVertexBufferObject", suppVBO); + + // check if render to texture supported is available + bool suppRTT = gglHasExtension(GL_EXT_framebuffer_object); + setCapability("GL::suppRenderTexture", suppRTT); + + bool suppBlit = gglHasExtension(GL_EXT_framebuffer_blit); + setCapability("GL::suppRTBlit", suppBlit); + + bool suppFloatTex = gglHasExtension(GL_ATI_texture_float); + setCapability("GL::suppFloatTexture", suppFloatTex); + + // Check for anisotropic filtering support. + bool suppAnisotropic = gglHasExtension( GL_EXT_texture_filter_anisotropic ); + setCapability( "GL::suppAnisotropic", suppAnisotropic ); + + // Check to see if mipmap lod bias is supported + bool suppMipLodBias = gglHasExtension(GL_EXT_texture_lod_bias); + setCapability("GL::suppMipLodBias", suppMipLodBias); + + // check to see if we have the fragment shader extension or the gl version is high enough for glsl to be core + // also check to see if the language version is high enough + F32 glslVersion = dAtof(reinterpret_cast(glGetString( GL_SHADING_LANGUAGE_VERSION))); + bool suppSPU = (gglHasExtension(GL_ARB_fragment_shader) || glVersion >= 1.999f) && glslVersion >= 1.0999; + setCapability("GL::suppFragmentShader", suppSPU); + + bool suppAppleFence = gglHasExtension(GL_APPLE_fence); + setCapability("GL::APPLE::suppFence", suppAppleFence); + + // When enabled, call glGenerateMipmapEXT() to generate mipmaps instead of relying on GL_GENERATE_MIPMAP + setCapability("GL::Workaround::needsExplicitGenerateMipmap", false); + // When enabled, binds and unbinds a texture target before doing the depth buffer copy. Failure to do + // so will cause a hard freeze on Mac OS 10.4 with a Radeon X1600 + setCapability("GL::Workaround::X1600DepthBufferCopy", false); + // When enabled, does not copy the last column and row of the depth buffer in a depth buffer copy. Failure + // to do so will cause a kernel panic on Mac OS 10.5(.1) with a Radeon HD 2600 (fixed in 10.5.2) + setCapability("GL::Workaround::HD2600DepthBufferCopy", false); + + // Certain Intel drivers have a divide by 0 crash if mipmaps are specified with + // glTexSubImage2D. + setCapability("GL::Workaround::noManualMips", false); +} + +bool GFXGLCardProfiler::_queryCardCap(const String& query, U32& foundResult) +{ + // Just doing what the D3D9 layer does + return 0; +} + +bool GFXGLCardProfiler::_queryFormat(const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips) +{ + // Alex I am sure this isn't proper, but it seems like you set up the enums + // like this. I am not sure how to query for using them as render targets, so + // I just set it to Apple Rules(tm) -Pat + + return GFXGLTextureInternalFormat[fmt] != GL_ZERO; +} diff --git a/gfx/gl/gfxGLCardProfiler.h b/gfx/gl/gfxGLCardProfiler.h new file mode 100644 index 0000000..302989e --- /dev/null +++ b/gfx/gl/gfxGLCardProfiler.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLCARDPROFILE_H +#define _GFXGLCARDPROFILE_H + +#include "gfx/gfxCardProfile.h" + +/// A note on workarounds - The following settings are used exclusively to work around driver bugs: +/// GL::Workaround::needsExplicitGenerateMipmap +/// GL::Workaround::X1600DepthBufferCopy +/// GL::Workaround::HD2600DepthBufferCopy +/// If you find that you need to work around additional driver bugs, add a new setting +/// of the form GL::Workaround:: and set it to false in GFXGLCardProfiler::setupCardCapabilities() +/// Enclose your workaround code in if(GFX->getCardProfiler()->queryProfile("GL::Workaround::")) { +/// } +/// Remember to set the work around to true in the card profile script! + +class GFXGLCardProfiler : public GFXCardProfiler +{ +public: + void init(); + +protected: + virtual const String& getRendererString() const { return mRendererString; } + virtual void setupCardCapabilities(); + virtual bool _queryCardCap(const String& query, U32& foundResult); + virtual bool _queryFormat(const GFXFormat fmt, const GFXTextureProfile *profile, bool &inOutAutogenMips); + +private: + String mRendererString; + typedef GFXCardProfiler Parent; +}; + +#endif diff --git a/gfx/gl/gfxGLCubemap.cpp b/gfx/gl/gfxGLCubemap.cpp new file mode 100644 index 0000000..0998ccb --- /dev/null +++ b/gfx/gl/gfxGLCubemap.cpp @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gl/gfxGLDevice.h" +#include "gfx/gl/gfxGLTextureObject.h" +#include "gfx/gl/gfxGLEnumTranslate.h" +#include "gfx/gl/gfxGLUtils.h" +#include "gfx/gl/gfxGLCubemap.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gfxCardProfile.h" + +GLenum GFXGLCubemap::faceList[6] = +{ + GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z +}; + +GFXGLCubemap::GFXGLCubemap() : + mCubemap(0), + mDynamicTexSize(0), + mFaceFormat( GFXFormatR8G8B8A8 ) +{ + for(U32 i = 0; i < 6; i++) + mTextures[i] = NULL; + + GFXTextureManager::addEventDelegate( this, &GFXGLCubemap::_onTextureEvent ); +} + +GFXGLCubemap::~GFXGLCubemap() +{ + glDeleteTextures(1, &mCubemap); + GFXTextureManager::removeEventDelegate( this, &GFXGLCubemap::_onTextureEvent ); +} + +void GFXGLCubemap::fillCubeTextures(GFXTexHandle* faces) +{ + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, mCubemap); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_TRUE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + U32 reqWidth = faces[0]->getWidth(); + U32 reqHeight = faces[0]->getHeight(); + GFXFormat regFaceFormat = faces[0]->getFormat(); + mWidth = reqWidth; + mHeight = reqHeight; + mFaceFormat = regFaceFormat; + mMipLevels = 1; // Lie for now + AssertFatal(reqWidth == reqHeight, "GFXGLCubemap::fillCubeTextures - Width and height must be equal!"); + + for(U32 i = 0; i < 6; i++) + { + AssertFatal(faces[i], avar("GFXGLCubemap::fillCubeFaces - texture %i is NULL!", i)); + AssertFatal((faces[i]->getWidth() == reqWidth) && (faces[i]->getHeight() == reqHeight), "GFXGLCubemap::fillCubeFaces - All textures must have identical dimensions!"); + AssertFatal(faces[i]->getFormat() == regFaceFormat, "GFXGLCubemap::fillCubeFaces - All textures must have identical formats!"); + + mTextures[i] = faces[i]; + GFXFormat faceFormat = faces[i]->getFormat(); + + GFXGLTextureObject* glTex = static_cast(faces[i].getPointer()); + U8* buf = glTex->getTextureData(); + glTexImage2D(faceList[i], 0, GFXGLTextureInternalFormat[faceFormat], faces[i]->getWidth(), faces[i]->getHeight(), + 0, GFXGLTextureFormat[faceFormat], GFXGLTextureType[faceFormat], buf); + delete[] buf; + } + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); +} + +void GFXGLCubemap::initStatic(GFXTexHandle* faces) +{ + if(mCubemap) + return; + + if(faces) + { + AssertFatal(faces[0], "GFXGLCubemap::initStatic - empty texture passed"); + glGenTextures(1, &mCubemap); + fillCubeTextures(faces); + } +} + +void GFXGLCubemap::initDynamic(U32 texSize, GFXFormat faceFormat) +{ + mDynamicTexSize = texSize; + mFaceFormat = faceFormat; + + glGenTextures(1, &mCubemap); + glBindTexture(GL_TEXTURE_CUBE_MAP, mCubemap); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + mWidth = texSize; + mHeight = texSize; + mMipLevels = 1; + for(U32 i = 0; i < 6; i++) + { + glTexImage2D( faceList[i], 0, GFXGLTextureInternalFormat[faceFormat], texSize, texSize, + 0, GFXGLTextureFormat[faceFormat], GFXGLTextureType[faceFormat], NULL); + } + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +} + +void GFXGLCubemap::zombify() +{ + glDeleteTextures(1, &mCubemap); + mCubemap = 0; +} + +void GFXGLCubemap::resurrect() +{ + // Handled in tmResurrect +} + +void GFXGLCubemap::tmResurrect() +{ + if(mDynamicTexSize) + initDynamic(mDynamicTexSize,mFaceFormat); + else + initStatic(mTextures); +} + +void GFXGLCubemap::setToTexUnit(U32 tuNum) +{ + static_cast(getOwningDevice())->setCubemapInternal(tuNum, this); +} + +void GFXGLCubemap::bind(U32 textureUnit) const +{ + glActiveTexture(GL_TEXTURE0 + textureUnit); + glBindTexture(GL_TEXTURE_CUBE_MAP, mCubemap); + + GFXGLStateBlockRef sb = static_cast(GFX)->getCurrentStateBlock(); + AssertFatal(sb, "GFXGLCubemap::bind - No active stateblock!"); + if (!sb) + return; + + const GFXSamplerStateDesc ssd = sb->getDesc().samplers[textureUnit]; + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, minificationFilter(ssd.minFilter, ssd.mipFilter, 0)); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GFXGLTextureFilter[ssd.magFilter]); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GFXGLTextureAddress[ssd.addressModeU]); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GFXGLTextureAddress[ssd.addressModeV]); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GFXGLTextureAddress[ssd.addressModeW]); + + if (GFX->getCardProfiler()->queryProfile("GL::suppMipLodBias")) + glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, ssd.mipLODBias); +} + +void GFXGLCubemap::_onTextureEvent( GFXTexCallbackCode code ) +{ + if ( code == GFXZombify ) + zombify(); + else + tmResurrect(); +} diff --git a/gfx/gl/gfxGLCubemap.h b/gfx/gl/gfxGLCubemap.h new file mode 100644 index 0000000..ad22132 --- /dev/null +++ b/gfx/gl/gfxGLCubemap.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLCUBEMAP_H_ +#define _GFXGLCUBEMAP_H_ + +class GFXGLCubemap : public GFXCubemap +{ +public: + GFXGLCubemap(); + virtual ~GFXGLCubemap(); + + virtual void initStatic( GFXTexHandle *faces ); + virtual void initDynamic( U32 texSize, GFXFormat faceFormat = GFXFormatR8G8B8A8 ); + virtual U32 getSize() const { return mWidth; } + virtual GFXFormat getFormat() const { return mFaceFormat; } + + // Convenience methods for GFXGLTextureTarget + U32 getWidth() { return mWidth; } + U32 getHeight() { return mHeight; } + U32 getNumMipLevels() { return mMipLevels; } + U32 getHandle() { return mCubemap; } + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + + /// Called by texCB; this is to ensure that all textures have been resurrected before we attempt to res the cubemap. + void tmResurrect(); + + static GLenum getEnumForFaceNumber(U32 face) { return faceList[face]; } ///< Performs lookup to get a GLenum for the given face number + +protected: + + friend class GFXDevice; + friend class GFXGLDevice; + + /// The callback used to get texture events. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ); + + GLuint mCubemap; ///< Internal GL handle + U32 mDynamicTexSize; ///< Size of faces for a dynamic texture (used in resurrect) + + // Self explanatory + U32 mWidth; + U32 mHeight; + U32 mMipLevels; + GFXFormat mFaceFormat; + + GFXTexHandle mTextures[6]; ///< Keep refs to our textures for resurrection of static cubemaps + + // should only be called by GFXDevice + virtual void setToTexUnit( U32 tuNum ); ///< Binds the cubemap to the given texture unit + virtual void bind(U32 textureUnit) const; ///< Notifies our owning device that we want to be set to the given texture unit (used for GL internal state tracking) + void fillCubeTextures(GFXTexHandle* faces); ///< Copies the textures in faces into the cubemap + + static GLenum faceList[6]; ///< Lookup table +}; + +#endif diff --git a/gfx/gl/gfxGLDevice.cpp b/gfx/gl/gfxGLDevice.cpp new file mode 100644 index 0000000..5900d32 --- /dev/null +++ b/gfx/gl/gfxGLDevice.cpp @@ -0,0 +1,747 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gl/gfxGLDevice.h" + +#include "gfx/gfxCubemap.h" +#include "gfx/screenshot.h" +#include "gfx/gfxDrawUtil.h" + +#include "gfx/gl/gfxGLEnumTranslate.h" +#include "gfx/gl/gfxGLVertexBuffer.h" +#include "gfx/gl/gfxGLPrimitiveBuffer.h" +#include "gfx/gl/gfxGLTextureTarget.h" +#include "gfx/gl/gfxGLTextureManager.h" +#include "gfx/gl/gfxGLTextureObject.h" +#include "gfx/gl/gfxGLCubemap.h" +#include "gfx/gl/gfxGLCardProfiler.h" +#include "gfx/gl/gfxGLWindowTarget.h" +#include "gfx/gl/ggl/ggl.h" +#include "platform/platformDlibrary.h" +#include "gfx/gl/gfxGLShader.h" +#include "gfx/primBuilder.h" +#include "console/console.h" +#include "gfx/gl/gfxGLOcclusionQuery.h" + +GFXAdapter::CreateDeviceInstanceDelegate GFXGLDevice::mCreateDeviceInstance(GFXGLDevice::createInstance); + +GFXDevice *GFXGLDevice::createInstance( U32 adapterIndex ) +{ + return new GFXGLDevice(adapterIndex); +} + +namespace GL +{ + extern void gglPerformBinds(); + extern void gglPerformExtensionBinds(void *context); +} + +void loadGLCore() +{ + static bool coreLoaded = false; // Guess what this is for. + if(coreLoaded) + return; + coreLoaded = true; + + // Make sure we've got our GL bindings. + GL::gglPerformBinds(); +} + +void loadGLExtensions(void *context) +{ + static bool extensionsLoaded = false; + if(extensionsLoaded) + return; + extensionsLoaded = true; + + GL::gglPerformExtensionBinds(context); +} + +void GFXGLDevice::initGLState() +{ + // We don't currently need to sync device state with a known good place because we are + // going to set everything in GFXGLStateBlock, but if we change our GFXGLStateBlock strategy, this may + // need to happen. + + // Deal with the card profiler here when we know we have a valid context. + mCardProfiler = new GFXGLCardProfiler(); + mCardProfiler->init(); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (GLint*)&mMaxShaderTextures); + glGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&mMaxFFTextures); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + // Apple's drivers lie and claim that everything supports fragment shaders. Conveniently they don't lie about the number + // of supported image units. Checking for 16 or more image units ensures that we don't try and use pixel shaders on + // cards which don't support them. + if(mCardProfiler->queryProfile("GL::suppFragmentShader") && mMaxShaderTextures >= 16) + mPixelShaderVersion = 2.0f; + else + mPixelShaderVersion = 0.0f; + + // MACHAX - Setting mPixelShaderVersion to 3.0 will allow Advanced Lighting + // to run. At the time of writing (6/18) it doesn't quite work yet. + if(Con::getBoolVariable("$pref::machax::enableAdvancedLighting", false)) + mPixelShaderVersion = 3.0f; + + mSupportsAnisotropic = mCardProfiler->queryProfile( "GL::suppAnisotropic" ); + mSupportsMipLodBias = mCardProfiler->queryProfile( "GL::suppMipLodBias" ); +} + +GFXGLDevice::GFXGLDevice(U32 adapterIndex) : + mAdapterIndex(adapterIndex), + mCurrentVB(NULL), + mCurrentPB(NULL), + m_mCurrentWorld(true), + m_mCurrentView(true), + mContext(NULL), + mPixelFormat(NULL), + mPixelShaderVersion(0.0f), + mMaxShaderTextures(2), + mMaxFFTextures(2), + mClip(0, 0, 0, 0) +{ + loadGLCore(); + + GFXGLEnumTranslate::init(); + + GFXVertexColor::setSwizzle( &Swizzles::rgba ); + mDeviceSwizzle32 = &Swizzles::bgra; + mDeviceSwizzle24 = &Swizzles::bgr; + + mTextureManager = new GFXGLTextureManager(); + gScreenShot = new ScreenShot(); + + for(U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) + mActiveTextureType[i] = GL_ZERO; +} + +GFXGLDevice::~GFXGLDevice() +{ + mCurrentStateBlock = NULL; + mCurrentPB = NULL; + mCurrentVB = NULL; + for(U32 i = 0; i < mVolatileVBs.size(); i++) + mVolatileVBs[i] = NULL; + for(U32 i = 0; i < mVolatilePBs.size(); i++) + mVolatilePBs[i] = NULL; + + GFXResource* walk = mResourceListHead; + while(walk) + { + walk->zombify(); + walk = walk->getNextResource(); + } + mTextureManager->kill(); + delete mTextureManager; + mTextureManager = NULL; + + if( mCardProfiler ) + SAFE_DELETE( mCardProfiler ); + + SAFE_DELETE( gScreenShot ); +} + +void GFXGLDevice::zombify() +{ + mTextureManager->zombify(); + if(mCurrentVB) + mCurrentVB->finish(); + if(mCurrentPB) + mCurrentPB->finish(); + //mVolatileVBs.clear(); + //mVolatilePBs.clear(); + GFXResource* walk = mResourceListHead; + while(walk) + { + walk->zombify(); + walk = walk->getNextResource(); + } +} + +void GFXGLDevice::resurrect() +{ + GFXResource* walk = mResourceListHead; + while(walk) + { + walk->resurrect(); + walk = walk->getNextResource(); + } + if(mCurrentVB) + mCurrentVB->prepare(); + if(mCurrentPB) + mCurrentPB->prepare(); + mTextureManager->resurrect(); +} + +GFXVertexBuffer* GFXGLDevice::findVolatileVBO(U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize) +{ + for(U32 i = 0; i < mVolatileVBs.size(); i++) + if ( mVolatileVBs[i]->mNumVerts >= numVerts && + mVolatileVBs[i]->mVertexFormat->isEqual( *vertexFormat ) && + mVolatileVBs[i]->mVertexSize == vertSize && + mVolatileVBs[i]->getRefCount() == 1 ) + return mVolatileVBs[i]; + + // No existing VB, so create one + StrongRefPtr buf(new GFXGLVertexBuffer(GFX, numVerts, vertexFormat, vertSize, GFXBufferTypeVolatile)); + buf->registerResourceWithDevice(this); + mVolatileVBs.push_back(buf); + return buf.getPointer(); +} + +GFXPrimitiveBuffer* GFXGLDevice::findVolatilePBO(U32 numIndices, U32 numPrimitives) +{ + for(U32 i = 0; i < mVolatilePBs.size(); i++) + if((mVolatilePBs[i]->mIndexCount >= numIndices) && (mVolatilePBs[i]->getRefCount() == 1)) + return mVolatilePBs[i]; + + // No existing PB, so create one + StrongRefPtr buf(new GFXGLPrimitiveBuffer(GFX, numIndices, numPrimitives, GFXBufferTypeVolatile)); + buf->registerResourceWithDevice(this); + mVolatilePBs.push_back(buf); + return buf.getPointer(); +} + +GFXVertexBuffer *GFXGLDevice::allocVertexBuffer( U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertSize, + GFXBufferType bufferType ) +{ + if(bufferType == GFXBufferTypeVolatile) + return findVolatileVBO(numVerts, vertexFormat, vertSize); + + GFXGLVertexBuffer* buf = new GFXGLVertexBuffer( GFX, numVerts, vertexFormat, vertSize, bufferType ); + buf->registerResourceWithDevice(this); + return buf; +} + +GFXPrimitiveBuffer *GFXGLDevice::allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ) +{ + if(bufferType == GFXBufferTypeVolatile) + return findVolatilePBO(numIndices, numPrimitives); + + GFXGLPrimitiveBuffer* buf = new GFXGLPrimitiveBuffer(GFX, numIndices, numPrimitives, bufferType); + buf->registerResourceWithDevice(this); + return buf; +} + +GFXCubemap* GFXGLDevice::createCubemap() +{ + GFXGLCubemap* cube = new GFXGLCubemap(); + cube->registerResourceWithDevice(this); + return cube; +}; + +void GFXGLDevice::endSceneInternal() +{ + // nothing to do for opengl + mCanCurrentlyRender = false; +} + +void GFXGLDevice::clear(U32 flags, ColorI color, F32 z, U32 stencil) +{ + // Make sure we have flushed our render target state. + _updateRenderTargets(); + + bool zwrite = true; + if (mCurrentGLStateBlock) + { + zwrite = mCurrentGLStateBlock->getDesc().zWriteEnable; + } + + glDepthMask(true); + ColorF c = color; + glClearColor(c.red, c.green, c.blue, c.alpha); + glClearDepth(z); + glClearStencil(stencil); + + GLbitfield clearflags = 0; + clearflags |= (flags & GFXClearTarget) ? GL_COLOR_BUFFER_BIT : 0; + clearflags |= (flags & GFXClearZBuffer) ? GL_DEPTH_BUFFER_BIT : 0; + clearflags |= (flags & GFXClearStencil) ? GL_STENCIL_BUFFER_BIT : 0; + + glClear(clearflags); + + if(!zwrite) + glDepthMask(false); +} + +// Given a primitive type and a number of primitives, return the number of indexes/vertexes used. +GLsizei GFXGLDevice::primCountToIndexCount(GFXPrimitiveType primType, U32 primitiveCount) +{ + switch (primType) + { + case GFXPointList : + return primitiveCount; + break; + case GFXLineList : + return primitiveCount * 2; + break; + case GFXLineStrip : + return primitiveCount + 1; + break; + case GFXTriangleList : + return primitiveCount * 3; + break; + case GFXTriangleStrip : + return 2 + primitiveCount; + break; + case GFXTriangleFan : + return 2 + primitiveCount; + break; + default: + AssertFatal(false, "GFXGLDevice::primCountToIndexCount - unrecognized prim type"); + break; + } + + return 0; +} + +inline void GFXGLDevice::preDrawPrimitive() +{ + if( mStateDirty ) + { + updateStates(); + } + + if(mCurrentShaderConstBuffer) + setShaderConstBufferInternal(mCurrentShaderConstBuffer); +} + +inline void GFXGLDevice::postDrawPrimitive(U32 primitiveCount) +{ + mDeviceStatistics.mDrawCalls++; + mDeviceStatistics.mPolyCount += primitiveCount; +} + +void GFXGLDevice::drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ) +{ + preDrawPrimitive(); + + // There are some odd performance issues if a buffer is bound to GL_ELEMENT_ARRAY_BUFFER when glDrawArrays is called. Unbinding the buffer + // improves performance by 10%. + if(mCurrentPB) + mCurrentPB->finish(); + + glDrawArrays(GFXGLPrimType[primType], vertexStart, primCountToIndexCount(primType, primitiveCount)); + + if(mCurrentPB) + mCurrentPB->prepare(); + + postDrawPrimitive(primitiveCount); +} + +void GFXGLDevice::drawIndexedPrimitive( GFXPrimitiveType primType, + U32 startVertex, + U32 minIndex, + U32 numVerts, + U32 startIndex, + U32 primitiveCount ) +{ + AssertFatal( startVertex == 0, "GFXGLDevice::drawIndexedPrimitive() - Non-zero startVertex unsupported!" ); + + preDrawPrimitive(); + + U16* buf = (U16*)static_cast(mCurrentPrimitiveBuffer.getPointer())->getBuffer() + startIndex; + + glDrawElements(GFXGLPrimType[primType], primCountToIndexCount(primType, primitiveCount), GL_UNSIGNED_SHORT, buf); + + postDrawPrimitive(primitiveCount); +} + + +void GFXGLDevice::setVB(GFXGLVertexBuffer* vb) +{ + // Reset the state the old VB required, then set the state the new VB requires. + if(mCurrentVB) + mCurrentVB->finish(); + mCurrentVB = vb; +} + +void GFXGLDevice::setPB(GFXGLPrimitiveBuffer* pb) +{ + if(mCurrentPB) + mCurrentPB->finish(); + mCurrentPB = pb; +} + +void GFXGLDevice::setLightInternal(U32 lightStage, const GFXLightInfo light, bool lightEnable) +{ + if(!lightEnable) + { + glDisable(GL_LIGHT0 + lightStage); + return; + } + + if(light.mType == GFXLightInfo::Ambient) + { + AssertFatal(false, "Instead of setting an ambient light you should set the global ambient color."); + return; + } + + GLenum lightEnum = GL_LIGHT0 + lightStage; + glLightfv(lightEnum, GL_AMBIENT, (GLfloat*)&light.mAmbient); + glLightfv(lightEnum, GL_DIFFUSE, (GLfloat*)&light.mColor); + glLightfv(lightEnum, GL_SPECULAR, (GLfloat*)&light.mColor); + + F32 pos[4]; + + if(light.mType != GFXLightInfo::Vector) + { + dMemcpy(pos, &light.mPos, sizeof(light.mPos)); + pos[3] = 1.0; + } + else + { + dMemcpy(pos, &light.mDirection, sizeof(light.mDirection)); + pos[3] = 0.0; + } + // Harcoded attenuation + glLightf(lightEnum, GL_CONSTANT_ATTENUATION, 1.0f); + glLightf(lightEnum, GL_LINEAR_ATTENUATION, 0.1f); + glLightf(lightEnum, GL_QUADRATIC_ATTENUATION, 0.0f); + + glLightfv(lightEnum, GL_POSITION, (GLfloat*)&pos); + glEnable(lightEnum); +} + +void GFXGLDevice::setLightMaterialInternal(const GFXLightMaterial mat) +{ + // CodeReview - Setting these for front and back is unnecessary. We should consider + // checking what faces we're culling and setting this only for the unculled faces. + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, (GLfloat*)&mat.ambient); + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, (GLfloat*)&mat.diffuse); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat*)&mat.specular); + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, (GLfloat*)&mat.emissive); + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, mat.shininess); +} + +void GFXGLDevice::setGlobalAmbientInternal(ColorF color) +{ + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (GLfloat*)&color); +} + +void GFXGLDevice::setTextureInternal(U32 textureUnit, const GFXTextureObject*texture) +{ + const GFXGLTextureObject *tex = static_cast(texture); + glActiveTexture(GL_TEXTURE0 + textureUnit); + if (tex) + { + // GFXGLTextureObject::bind also handles applying the current sampler state. + if(mActiveTextureType[textureUnit] != tex->getBinding() && mActiveTextureType[textureUnit] != GL_ZERO) + { + glBindTexture(mActiveTextureType[textureUnit], 0); + glDisable(mActiveTextureType[textureUnit]); + } + mActiveTextureType[textureUnit] = tex->getBinding(); + tex->bind(textureUnit); + } + else if(mActiveTextureType[textureUnit] != GL_ZERO) + { + glBindTexture(mActiveTextureType[textureUnit], 0); + glDisable(mActiveTextureType[textureUnit]); + mActiveTextureType[textureUnit] = GL_ZERO; + } + + glActiveTexture(GL_TEXTURE0); +} + +void GFXGLDevice::setCubemapInternal(U32 textureUnit, const GFXGLCubemap* texture) +{ + glActiveTexture(GL_TEXTURE0 + textureUnit); + if(texture) + { + if(mActiveTextureType[textureUnit] != GL_TEXTURE_CUBE_MAP && mActiveTextureType[textureUnit] != GL_ZERO) + { + glBindTexture(mActiveTextureType[textureUnit], 0); + glDisable(mActiveTextureType[textureUnit]); + } + mActiveTextureType[textureUnit] = GL_TEXTURE_CUBE_MAP; + texture->bind(textureUnit); + } + else if(mActiveTextureType[textureUnit] != GL_ZERO) + { + glBindTexture(mActiveTextureType[textureUnit], 0); + glDisable(mActiveTextureType[textureUnit]); + mActiveTextureType[textureUnit] = GL_ZERO; + } + + glActiveTexture(GL_TEXTURE0); +} + +void GFXGLDevice::setMatrix( GFXMatrixType mtype, const MatrixF &mat ) +{ + MatrixF modelview; + switch (mtype) + { + case GFXMatrixWorld : + { + glMatrixMode(GL_MODELVIEW); + m_mCurrentWorld = mat; + modelview = m_mCurrentWorld; + modelview *= m_mCurrentView; + modelview.transpose(); + glLoadMatrixf((F32*) modelview); + } + break; + case GFXMatrixView : + { + glMatrixMode(GL_MODELVIEW); + m_mCurrentView = mat; + modelview = m_mCurrentView; + modelview *= m_mCurrentWorld; + modelview.transpose(); + glLoadMatrixf((F32*) modelview); + } + break; + case GFXMatrixProjection : + { + glMatrixMode(GL_PROJECTION); + MatrixF t(mat); + t.transpose(); + glLoadMatrixf((F32*) t); + glMatrixMode(GL_MODELVIEW); + } + break; + // CodeReview - Add support for texture transform matrix types + default: + AssertFatal(false, "GFXGLDevice::setMatrix - Unknown matrix mode!"); + return; + } +} + +void GFXGLDevice::setClipRect( const RectI &inRect ) +{ + AssertFatal(mCurrentRT.isValid(), "GFXGLDevice::setClipRect - must have a render target set to do any rendering operations!"); + + // Clip the rect against the renderable size. + Point2I size = mCurrentRT->getSize(); + RectI maxRect(Point2I(0,0), size); + mClip = inRect; + mClip.intersect(maxRect); + + // Create projection matrix. See http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/ortho.html + const F32 left = mClip.point.x; + const F32 right = mClip.point.x + mClip.extent.x; + const F32 bottom = mClip.extent.y; + const F32 top = 0.0f; + const F32 near = 0.0f; + const F32 far = 1.0f; + + const F32 tx = -(right + left)/(right - left); + const F32 ty = -(top + bottom)/(top - bottom); + const F32 tz = -(far + near)/(far - near); + + static Point4F pt; + pt.set(2.0f / (right - left), 0.0f, 0.0f, 0.0f); + mProjectionMatrix.setColumn(0, pt); + + pt.set(0.0f, 2.0f/(top - bottom), 0.0f, 0.0f); + mProjectionMatrix.setColumn(1, pt); + + pt.set(0.0f, 0.0f, -2.0f/(far - near), 0.0f); + mProjectionMatrix.setColumn(2, pt); + + pt.set(tx, ty, tz, 1.0f); + mProjectionMatrix.setColumn(3, pt); + + // Translate projection matrix. + static MatrixF translate(true); + pt.set(0.0f, -mClip.point.y, 0.0f, 1.0f); + translate.setColumn(3, pt); + + mProjectionMatrix *= translate; + + setMatrix(GFXMatrixProjection, mProjectionMatrix); + + MatrixF mTempMatrix(true); + setViewMatrix( mTempMatrix ); + setWorldMatrix( mTempMatrix ); + + // Set the viewport to the clip rect (with y flip) + RectI viewport(mClip.point.x, size.y - (mClip.point.y + mClip.extent.y), mClip.extent.x, mClip.extent.y); + setViewport(viewport); +} + +/// Creates a state block object based on the desc passed in. This object +/// represents an immutable state. +GFXStateBlockRef GFXGLDevice::createStateBlockInternal(const GFXStateBlockDesc& desc) +{ + return GFXStateBlockRef(new GFXGLStateBlock(desc)); +} + +/// Activates a stateblock +void GFXGLDevice::setStateBlockInternal(GFXStateBlock* block, bool force) +{ + AssertFatal(dynamic_cast(block), "GFXGLDevice::setStateBlockInternal - Incorrect stateblock type for this device!"); + GFXGLStateBlock* glBlock = static_cast(block); + GFXGLStateBlock* glCurrent = static_cast(mCurrentStateBlock.getPointer()); + if (force) + glCurrent = NULL; + + glBlock->activate(glCurrent); // Doesn't use current yet. + mCurrentGLStateBlock = glBlock; +} + +//------------------------------------------------------------------------------ + +GFXTextureTarget * GFXGLDevice::allocRenderToTextureTarget() +{ + GFXGLTextureTarget *targ = new GFXGLTextureTarget(); + targ->registerResourceWithDevice(this); + return targ; +} + +GFXFence * GFXGLDevice::createFence() +{ + GFXFence* fence = _createPlatformSpecificFence(); + if(!fence) + fence = new GFXGeneralFence( this ); + + fence->registerResourceWithDevice(this); + return fence; +} + +GFXOcclusionQuery* GFXGLDevice::createOcclusionQuery() +{ + GFXOcclusionQuery *query = new GFXGLOcclusionQuery( this ); + query->registerResourceWithDevice(this); + return query; +} + +void GFXGLDevice::setupGenericShaders( GenericShaderType type ) +{ + TORQUE_UNUSED(type); + // We have FF support, use that. + disableShaders(); +} + +GFXShader* GFXGLDevice::createShader() +{ + GFXGLShader* shader = new GFXGLShader(); + shader->registerResourceWithDevice( this ); + return shader; +} + +void GFXGLDevice::setShader( GFXShader *shader ) +{ + if ( shader ) + { + GFXGLShader *glShader = static_cast( shader ); + glShader->useProgram(); + } + else + glUseProgram(0); +} + +void GFXGLDevice::disableShaders() +{ + setShader(NULL); + setShaderConstBuffer( NULL ); +} + +void GFXGLDevice::setShaderConstBufferInternal(GFXShaderConstBuffer* buffer) +{ + static_cast(buffer)->activate(); +} + +U32 GFXGLDevice::getNumSamplers() const +{ + return mPixelShaderVersion > 0.001f ? mMaxShaderTextures : mMaxFFTextures; +} + +U32 GFXGLDevice::getNumRenderTargets() const +{ + return 1; +} + +void GFXGLDevice::_updateRenderTargets() +{ + if ( mRTDirty || mCurrentRT->isPendingState() ) + { + if ( mRTDeactivate ) + { + mRTDeactivate->deactivate(); + mRTDeactivate = NULL; + } + + // NOTE: The render target changes is not really accurate + // as the GFXTextureTarget supports MRT internally. So when + // we activate a GFXTarget it could result in multiple calls + // to SetRenderTarget on the actual device. + mDeviceStatistics.mRenderTargetChanges++; + + GFXGLTextureTarget *tex = dynamic_cast( mCurrentRT.getPointer() ); + if ( tex ) + { + tex->applyState(); + tex->makeActive(); + } + else + { + GFXGLWindowTarget *win = dynamic_cast( mCurrentRT.getPointer() ); + AssertFatal( win != NULL, + "GFXGLDevice::_updateRenderTargets() - invalid target subclass passed!" ); + + win->makeActive(); + + if( win->mContext != static_cast(GFX)->mContext ) + { + mRTDirty = false; + GFX->updateStates(true); + } + } + + mRTDirty = false; + } + + if ( mViewportDirty ) + { + glViewport( mViewport.point.x, mViewport.point.y, mViewport.extent.x, mViewport.extent.y ); + mViewportDirty = false; + } +} + +GFXFormat GFXGLDevice::selectSupportedFormat( GFXTextureProfile* profile, + const Vector& formats, + bool texture, + bool mustblend, + bool mustfilter ) +{ + for(U32 i = 0; i < formats.size(); i++) + { + // Single channel textures are not supported by FBOs. + if(profile->testFlag(GFXTextureProfile::RenderTarget) && (formats[i] == GFXFormatA8 || formats[i] == GFXFormatL8 || formats[i] == GFXFormatL16)) + continue; + if(GFXGLTextureInternalFormat[formats[i]] == GL_ZERO) + continue; + + return formats[i]; + } + + return GFXFormatR8G8B8A8; +} + +// +// Register this device with GFXInit +// +class GFXGLRegisterDevice +{ +public: + GFXGLRegisterDevice() + { + GFXInit::getRegisterDeviceSignal().notify(&GFXGLDevice::enumerateAdapters); + } +}; + +static GFXGLRegisterDevice pGLRegisterDevice; + +ConsoleFunction(cycleResources, void, 1, 1, "") +{ + static_cast(GFX)->zombify(); + static_cast(GFX)->resurrect(); +} diff --git a/gfx/gl/gfxGLDevice.h b/gfx/gl/gfxGLDevice.h new file mode 100644 index 0000000..4ec7f2a --- /dev/null +++ b/gfx/gl/gfxGLDevice.h @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLDEVICE_H_ +#define _GFXGLDEVICE_H_ + +#include "platform/platform.h" + +#include "gfx/gfxDevice.h" +#include "gfx/gfxInit.h" + +#ifndef GL_GGL_H +#include "gfx/gl/ggl/ggl.h" +#endif + +#include "windowManager/platformWindow.h" +#include "gfx/gfxFence.h" +#include "gfx/gfxResource.h" +#include "gfx/gl/gfxGLStateBlock.h" + +class GFXGLVertexBuffer; +class GFXGLPrimitiveBuffer; +class GFXGLTextureTarget; +class GFXGLCubemap; + +class GFXGLDevice : public GFXDevice +{ +public: + void zombify(); + void resurrect(); + GFXGLDevice(U32 adapterIndex); + virtual ~GFXGLDevice(); + + static void enumerateAdapters( Vector &adapterList ); + static GFXDevice *createInstance( U32 adapterIndex ); + + virtual void init( const GFXVideoMode &mode, PlatformWindow *window = NULL ); + + virtual void activate() { } + virtual void deactivate() { } + virtual GFXAdapterType getAdapterType() { return OpenGL; } + + virtual void enterDebugEvent(ColorI color, const char *name) { } + virtual void leaveDebugEvent() { } + virtual void setDebugMarker(ColorI color, const char *name) { } + + virtual void enumerateVideoModes(); + + virtual U32 getTotalVideoMemory(); + + virtual GFXCubemap * createCubemap(); + + virtual F32 getFillConventionOffset() const { return 0.0f; } + + + ///@} + + /// @name Render Target functions + /// @{ + + /// + virtual GFXTextureTarget *allocRenderToTextureTarget(); + virtual GFXWindowTarget *allocWindowTarget(PlatformWindow *window); + virtual void _updateRenderTargets(); + + ///@} + + /// @name Shader functions + /// @{ + virtual F32 getPixelShaderVersion() const { return mPixelShaderVersion; } + virtual void setPixelShaderVersion( F32 version ) { mPixelShaderVersion = version; } + + virtual void setShader(GFXShader* shd); + virtual void disableShaders(); ///< Equivalent to setShader(NULL) + + /// @attention GL cannot check if the given format supports blending or filtering! + virtual GFXFormat selectSupportedFormat(GFXTextureProfile *profile, + const Vector &formats, bool texture, bool mustblend, bool mustfilter); + + /// Returns the number of texture samplers that can be used in a shader rendering pass + virtual U32 getNumSamplers() const; + + /// Returns the number of simultaneous render targets supported by the device. + virtual U32 getNumRenderTargets() const; + + virtual GFXShader* createShader(); + + virtual void clear( U32 flags, ColorI color, F32 z, U32 stencil ); + virtual bool beginSceneInternal(); + virtual void endSceneInternal(); + + virtual void drawPrimitive( GFXPrimitiveType primType, U32 vertexStart, U32 primitiveCount ); + + virtual void drawIndexedPrimitive( GFXPrimitiveType primType, + U32 startVertex, + U32 minIndex, + U32 numVerts, + U32 startIndex, + U32 primitiveCount ); + + virtual void setClipRect( const RectI &rect ); + virtual const RectI &getClipRect() const { return mClip; } + + virtual void preDestroy() { Parent::preDestroy(); } + + virtual U32 getMaxDynamicVerts() { return MAX_DYNAMIC_VERTS; } + virtual U32 getMaxDynamicIndices() { return MAX_DYNAMIC_INDICES; } + + GFXFence *createFence(); + + GFXOcclusionQuery* createOcclusionQuery(); + + GFXGLStateBlockRef getCurrentStateBlock() { return mCurrentGLStateBlock; } + + virtual void setupGenericShaders( GenericShaderType type = GSColor ); + + /// + bool supportsAnisotropic() const { return mSupportsAnisotropic; } + + /// + bool supportsMipLodBias() const { return mSupportsMipLodBias; } + +protected: + /// Called by GFXDevice to create a device specific stateblock + virtual GFXStateBlockRef createStateBlockInternal(const GFXStateBlockDesc& desc); + /// Called by GFXDevice to actually set a stateblock. + virtual void setStateBlockInternal(GFXStateBlock* block, bool force); + + /// Called by base GFXDevice to actually set a const buffer + virtual void setShaderConstBufferInternal(GFXShaderConstBuffer* buffer); + + virtual void setTextureInternal(U32 textureUnit, const GFXTextureObject*texture); + virtual void setCubemapInternal(U32 cubemap, const GFXGLCubemap* texture); + + virtual void setLightInternal(U32 lightStage, const GFXLightInfo light, bool lightEnable); + virtual void setLightMaterialInternal(const GFXLightMaterial mat); + virtual void setGlobalAmbientInternal(ColorF color); + + /// @name State Initalization. + /// @{ + + /// State initalization. This MUST BE CALLED in setVideoMode after the device + /// is created. + virtual void initStates() { } + + virtual void setMatrix( GFXMatrixType mtype, const MatrixF &mat ); + + virtual GFXVertexBuffer *allocVertexBuffer( U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertSize, + GFXBufferType bufferType ); + virtual GFXPrimitiveBuffer *allocPrimitiveBuffer( U32 numIndices, U32 numPrimitives, GFXBufferType bufferType ); + +private: + typedef GFXDevice Parent; + + friend class GFXGLTextureObject; + friend class GFXGLCubemap; + friend class GFXGLWindowTarget; + friend class GFXGLPrimitiveBuffer; + friend class GFXGLVertexBuffer; + + static GFXAdapter::CreateDeviceInstanceDelegate mCreateDeviceInstance; + + U32 mAdapterIndex; + + StrongRefPtr mCurrentVB; + StrongRefPtr mCurrentPB; + + /// Since GL does not have separate world and view matrices we need to track them + MatrixF m_mCurrentWorld; + MatrixF m_mCurrentView; + + void* mContext; + void* mPixelFormat; + + F32 mPixelShaderVersion; + + bool mSupportsAnisotropic; + bool mSupportsMipLodBias; + + U32 mMaxShaderTextures; + U32 mMaxFFTextures; + + RectI mClip; + + GFXGLStateBlockRef mCurrentGLStateBlock; + + GLenum mActiveTextureType[TEXTURE_STAGE_COUNT]; + + Vector< StrongRefPtr > mVolatileVBs; ///< Pool of existing volatile VBs so we can reuse previously created ones + Vector< StrongRefPtr > mVolatilePBs; ///< Pool of existing volatile PBs so we can reuse previously created ones + + GLsizei primCountToIndexCount(GFXPrimitiveType primType, U32 primitiveCount); + void preDrawPrimitive(); + void postDrawPrimitive(U32 primitiveCount); + + GFXVertexBuffer* findVolatileVBO(U32 numVerts, const GFXVertexFormat *vertexFormat, U32 vertSize); ///< Returns an existing volatile VB which has >= numVerts and the same vert flags/size, or creates a new VB if necessary + GFXPrimitiveBuffer* findVolatilePBO(U32 numIndices, U32 numPrimitives); ///< Returns an existing volatile PB which has >= numIndices, or creates a new PB if necessary + + void initGLState(); ///< Guaranteed to be called after all extensions have been loaded, use to init card profiler, shader version, max samplers, etc. + + GFXFence* _createPlatformSpecificFence(); ///< If our platform (e.g. OS X) supports a fence extenstion (e.g. GL_APPLE_fence) this will create one, otherwise returns NULL + + void setVB(GFXGLVertexBuffer* vb); ///< Sets mCurrentVB + void setPB(GFXGLPrimitiveBuffer* pb); ///< Sets mCurrentPB +}; + +#endif diff --git a/gfx/gl/gfxGLDevice.mac.mm b/gfx/gl/gfxGLDevice.mac.mm new file mode 100644 index 0000000..c922ce7 --- /dev/null +++ b/gfx/gl/gfxGLDevice.mac.mm @@ -0,0 +1,340 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// Don't include Apple's GL header +#define __gl_h_ +// Include our GL header before Apple headers. +#include "gfx/gl/ggl/ggl.h" + +#include "platform/tmm_off.h" +#include +#include +#include "gfx/gl/gfxGLDevice.h" +#include "platform/tmm_on.h" + +#include "gfx/gl/gfxGLTextureTarget.h" +#include "gfx/gl/gfxGLCardProfiler.h" +#include "gfx/gl/gfxGLAppleFence.h" +#include "gfx/gl/gfxGLWindowTarget.h" +#include "platformMac/macGLUtils.h" +#include "windowManager/mac/macWindow.h" + +extern void loadGLCore(); +extern void loadGLExtensions(void* context); + +static String _getRendererForDisplay(CGDirectDisplayID display) +{ + Vector attributes = _createStandardPixelFormatAttributesForDisplay(display); + + NSOpenGLPixelFormat* fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes.address()]; + AssertFatal(fmt, "_getRendererForDisplay - Unable to create a pixel format object"); + + NSOpenGLContext* ctx = [[NSOpenGLContext alloc] initWithFormat:fmt shareContext:nil]; + AssertFatal(ctx, "_getRendererForDisplay - Unable to create an OpenGL context"); + + // Save the current context, just in case + NSOpenGLContext* currCtx = [NSOpenGLContext currentContext]; + [ctx makeCurrentContext]; + + // CodeReview [ags 12/19/07] This is a hack. We should call loadGLCore somewhere else, before we reach here. + // On Macs we can safely assume access to the OpenGL framework at anytime, perhaps this should go in main()? + loadGLCore(); + + // get the renderer string + String ret((const char*)glGetString(GL_RENDERER)); + + // Restore our old context, release the context and pixel format. + [currCtx makeCurrentContext]; + [ctx release]; + [fmt release]; + return ret; +} + +static void _createInitialContextAndFormat(void* &ctx, void* &fmt) +{ + AssertFatal(!fmt && !ctx, "_createInitialContextAndFormat - Already created initial context and format"); + + fmt = _createStandardPixelFormat(); + AssertFatal(fmt, "_createInitialContextAndFormat - Unable to create an OpenGL pixel format"); + + ctx = [[NSOpenGLContext alloc] initWithFormat: (NSOpenGLPixelFormat*)fmt shareContext: nil]; + AssertFatal(ctx, "_createInitialContextAndFormat - Unable to create an OpenGL context"); +} + +static NSOpenGLContext* _createContextForWindow(PlatformWindow *window, void* &context, void* &pixelFormat) +{ + NSOpenGLView* view = (NSOpenGLView*)window->getPlatformDrawable(); + AssertFatal([view isKindOfClass:[NSOpenGLView class]], avar("_createContextForWindow - Supplied a %s instead of a NSOpenGLView", [[view className] UTF8String])); + + NSOpenGLContext* ctx = NULL; + if(!context || !pixelFormat) + { + // Create the initial opengl context that the device and the first window will hold. + _createInitialContextAndFormat(context, pixelFormat); + ctx = (NSOpenGLContext*)context; + } + else + { + // Create a context which shares its resources with the device's initial context + ctx = [[NSOpenGLContext alloc] initWithFormat: (NSOpenGLPixelFormat*)pixelFormat shareContext: (NSOpenGLContext*)context]; + AssertFatal(ctx, "Unable to create a shared OpenGL context"); + } + + [view setPixelFormat: (NSOpenGLPixelFormat*)pixelFormat]; + [view setOpenGLContext: ctx]; + + return ctx; +} + +void GFXGLDevice::init( const GFXVideoMode &mode, PlatformWindow *window ) +{ + if(mInitialized) + return; + + NSOpenGLContext* ctx = _createContextForWindow(window, mContext, mPixelFormat); + [ctx makeCurrentContext]; + + loadGLCore(); + loadGLExtensions(ctx); + + initGLState(); + + mInitialized = true; + deviceInited(); +} + +void GFXGLDevice::enumerateAdapters( Vector &adapterList ) +{ + GFXAdapter *toAdd; + + Vector videoModes; + + CGDirectDisplayID display = CGMainDisplayID(); + + // Enumerate all available resolutions: + NSArray* modeArray = (NSArray*)CGDisplayAvailableModes(display); + + GFXVideoMode vmAdd; + NSEnumerator* enumerator = [modeArray objectEnumerator]; + NSDictionary* mode; + while((mode = [enumerator nextObject])) + { + vmAdd.resolution.x = [[mode valueForKey:@"Width"] intValue]; + vmAdd.resolution.y = [[mode valueForKey:@"Height"] intValue]; + vmAdd.bitDepth = [[mode valueForKey:@"BitsPerPixel"] intValue]; + vmAdd.refreshRate = [[mode valueForKey:@"RefreshRate"] intValue]; + + vmAdd.fullScreen = false; + + // skip if mode claims to be 8bpp + if( vmAdd.bitDepth == 8 ) + continue; + + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for(Vector::iterator i = videoModes.begin(); i != videoModes.end(); i++) + { + if(vmAdd == *i) + { + alreadyInList = true; + break; + } + } + if( !alreadyInList ) + { + videoModes.push_back( vmAdd ); + } + } + + + // Get number of displays + CGDisplayCount dispCnt; + CGGetActiveDisplayList(0, NULL, &dispCnt); + + // Take advantage of GNU-C + CGDirectDisplayID displays[dispCnt]; + + CGGetActiveDisplayList(dispCnt, displays, &dispCnt); + for(U32 i = 0; i < dispCnt; i++) + { + toAdd = new GFXAdapter(); + toAdd->mType = OpenGL; + toAdd->mIndex = (U32)displays[i]; + toAdd->mCreateDeviceInstanceDelegate = mCreateDeviceInstance; + String renderer = _getRendererForDisplay(displays[i]); + AssertFatal(dStrlen(renderer.c_str()) < GFXAdapter::MaxAdapterNameLen, "GFXGLDevice::enumerateAdapter - renderer name too long, increae the size of GFXAdapter::MaxAdapterNameLen (or use String!)"); + dStrncpy(toAdd->mName, renderer.c_str(), GFXAdapter::MaxAdapterNameLen); + adapterList.push_back(toAdd); + + for (S32 j = videoModes.size() - 1 ; j >= 0 ; j--) + toAdd->mAvailableModes.push_back(videoModes[j]); + } +} + +void GFXGLDevice::enumerateVideoModes() +{ + mVideoModes.clear(); + + CGDirectDisplayID display = CGMainDisplayID(); + + // Enumerate all available resolutions: + NSArray* modeArray = (NSArray*)CGDisplayAvailableModes(display); + + GFXVideoMode toAdd; + NSEnumerator* enumerator = [modeArray objectEnumerator]; + NSDictionary* mode; + while((mode = [enumerator nextObject])) + { + toAdd.resolution.x = [[mode valueForKey:@"Width"] intValue]; + toAdd.resolution.y = [[mode valueForKey:@"Height"] intValue]; + toAdd.bitDepth = [[mode valueForKey:@"BitsPerPixel"] intValue]; + toAdd.refreshRate = [[mode valueForKey:@"RefreshRate"] intValue]; + + toAdd.fullScreen = false; + + // skip if mode claims to be 8bpp + if( toAdd.bitDepth == 8 ) + continue; + + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for(Vector::iterator i = mVideoModes.begin(); i != mVideoModes.end(); i++) + { + if(toAdd == *i) + { + alreadyInList = true; + break; + } + } + if( !alreadyInList ) + { + mVideoModes.push_back( toAdd ); + } + } +} + +bool GFXGLDevice::beginSceneInternal() +{ + // Nothing to do here for GL. + mCanCurrentlyRender = true; + return true; +} + +U32 GFXGLDevice::getTotalVideoMemory() +{ + // Convert our adapterIndex (i.e. our CGDirectDisplayID) into an OpenGL display mask + GLuint display = CGDisplayIDToOpenGLDisplayMask((CGDirectDisplayID)mAdapterIndex); + CGLRendererInfoObj rend; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + GLint nrend; +#else + long int nrend; +#endif + CGLQueryRendererInfo(display, &rend, &nrend); + if(!nrend) + return 0; + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 + GLint vidMem; +#else + long int vidMem; +#endif + CGLDescribeRenderer(rend, 0, kCGLRPVideoMemory, &vidMem); + CGLDestroyRendererInfo(rend); + + // convert bytes to MB + vidMem /= (1024 * 1024); + + return vidMem; +} + +//----------------------------------------------------------------------------- +GFXWindowTarget *GFXGLDevice::allocWindowTarget(PlatformWindow *window) +{ + void *ctx = NULL; + + // Init if needed, or create a new context + if(!mInitialized) + init(window->getVideoMode(), window); + else + ctx = _createContextForWindow(window, mContext, mPixelFormat); + + // Allocate the wintarget and create a new context. + GFXGLWindowTarget *gwt = new GFXGLWindowTarget(window, this); + gwt->mContext = ctx ? ctx : mContext; + + // And return... + return gwt; +} + +GFXFence* GFXGLDevice::_createPlatformSpecificFence() +{ + if(!mCardProfiler->queryProfile("GL::APPLE::suppFence")) + return NULL; + + return new GFXGLAppleFence(this); +} + +void GFXGLWindowTarget::makeActive() +{ + // If we're supposed to be running fullscreen, but haven't yet set up for it, + // do it now. + + if( !mFullscreenContext && mWindow->getVideoMode().fullScreen ) + { + static_cast< GFXGLDevice* >( mDevice )->zombify(); + _setupNewMode(); + } + + mFullscreenContext ? [(NSOpenGLContext*)mFullscreenContext makeCurrentContext] : [(NSOpenGLContext*)mContext makeCurrentContext]; +} + +bool GFXGLWindowTarget::present() +{ + GFX->updateStates(); + mFullscreenContext ? [(NSOpenGLContext*)mFullscreenContext flushBuffer] : [(NSOpenGLContext*)mContext flushBuffer]; + return true; +} + +void GFXGLWindowTarget::_teardownCurrentMode() +{ + GFX->setActiveRenderTarget(this); + static_cast(mDevice)->zombify(); + if(mFullscreenContext) + { + [NSOpenGLContext clearCurrentContext]; + [(NSOpenGLContext*)mFullscreenContext clearDrawable]; + } +} + +void GFXGLWindowTarget::_setupNewMode() +{ + if(mWindow->getVideoMode().fullScreen && !mFullscreenContext) + { + // We have to create a fullscreen context. + Vector attributes = _beginPixelFormatAttributesForDisplay(static_cast(mWindow)->getDisplay()); + _addColorAlphaDepthStencilAttributes(attributes, 24, 8, 24, 8); + _addFullscreenAttributes(attributes); + _endAttributeList(attributes); + + NSOpenGLPixelFormat* fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes.address()]; + mFullscreenContext = [[NSOpenGLContext alloc] initWithFormat:fmt shareContext:nil]; + [fmt release]; + [(NSOpenGLContext*)mFullscreenContext setFullScreen]; + [(NSOpenGLContext*)mFullscreenContext makeCurrentContext]; + // Restore resources in new context + static_cast(mDevice)->resurrect(); + GFX->updateStates(true); + } + else if(!mWindow->getVideoMode().fullScreen && mFullscreenContext) + { + [(NSOpenGLContext*)mFullscreenContext release]; + mFullscreenContext = NULL; + [(NSOpenGLContext*)mContext makeCurrentContext]; + GFX->clear(GFXClearTarget | GFXClearZBuffer | GFXClearStencil, ColorI(0, 0, 0), 1.0f, 0); + static_cast(mDevice)->resurrect(); + GFX->updateStates(true); + } +} diff --git a/gfx/gl/gfxGLDevice.win.cpp b/gfx/gl/gfxGLDevice.win.cpp new file mode 100644 index 0000000..9e62046 --- /dev/null +++ b/gfx/gl/gfxGLDevice.win.cpp @@ -0,0 +1,386 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "gfx/gfxCubemap.h" +#include "gfx/screenshot.h" + +#include "gfx/GL/gfxGLDevice.h" +#include "gfx/GL/gfxGLEnumTranslate.h" +#include "gfx/GL/gfxGLVertexBuffer.h" +#include "gfx/GL/gfxGLPrimitiveBuffer.h" +#include "gfx/gl/gfxGLTextureTarget.h" +#include "gfx/GL/gfxGLWindowTarget.h" +#include "gfx/GL/gfxGLTextureManager.h" +#include "gfx/GL/gfxGLTextureObject.h" +#include "gfx/GL/gfxGLCubemap.h" +#include "gfx/GL/gfxGLCardProfiler.h" +#include "windowManager/win32/win32Window.h" +#include "ggl/Win32/wgl.h" + +#define GETHWND(x) static_cast(x)->getHWND() + +// yonked from winWindow.cc +void CreatePixelFormat( PIXELFORMATDESCRIPTOR *pPFD, S32 colorBits, S32 depthBits, S32 stencilBits, bool stereo ) +{ + PIXELFORMATDESCRIPTOR src = + { + sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd + 1, // version number + PFD_DRAW_TO_WINDOW | // support window + PFD_SUPPORT_OPENGL | // support OpenGL + PFD_DOUBLEBUFFER, // double buffered + PFD_TYPE_RGBA, // RGBA type + colorBits, // color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 0, // no alpha buffer + 0, // shift bit ignored + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits ignored + depthBits, // z-buffer + stencilBits, // stencil buffer + 0, // no auxiliary buffer + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0 // layer masks ignored + }; + + if ( stereo ) + { + //ri.Printf( PRINT_ALL, "...attempting to use stereo\n" ); + src.dwFlags |= PFD_STEREO; + //glConfig.stereoEnabled = true; + } + else + { + //glConfig.stereoEnabled = qfalse; + } + *pPFD = src; +} + +extern void loadGLCore(); +extern void loadGLExtensions(void* context); + +void GFXGLDevice::enumerateAdapters( Vector &adapterList ) +{ +// GL_ERROR_CHECK(); + WNDCLASS windowclass; + dMemset( &windowclass, 0, sizeof( WNDCLASS ) ); + + windowclass.lpszClassName = L"GFX-OpenGL"; + windowclass.style = CS_OWNDC; + windowclass.lpfnWndProc = DefWindowProc; + windowclass.hInstance = winState.appInstance; + + if( !RegisterClass( &windowclass ) ) + AssertFatal( false, "Failed to register the window class for the GL test window." ); + + // Now create a window + HWND hwnd = CreateWindow( L"GFX-OpenGL", L"", WS_POPUP, 0, 0, 640, 480, + NULL, NULL, winState.appInstance, NULL ); + AssertFatal( hwnd != NULL, "Failed to create the window for the GL test window." ); + + // Create a device context + HDC tempDC = GetDC( hwnd ); + AssertFatal( tempDC != NULL, "Failed to create device context" ); + + // Create pixel format descriptor... + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, 16, 16, 8, false ); // 16 bit color, 16 bit depth, 8 bit stencil...everyone can do this + if( !SetPixelFormat( tempDC, ChoosePixelFormat( tempDC, &pfd ), &pfd ) ) + AssertFatal( false, "I don't know who's responcible for this, but I want caught..." ); + + // Create a rendering context! + HGLRC tempGLRC = wglCreateContext( tempDC ); + if( !wglMakeCurrent( tempDC, tempGLRC ) ) + AssertFatal( false, "I want them caught and killed." ); + + // Add the GL renderer + loadGLCore(); + loadGLExtensions(tempDC); + + GFXAdapter *toAdd = new GFXAdapter; + toAdd->mIndex = 0; + + const char* renderer = (const char*) glGetString( GL_RENDERER ); + AssertFatal( renderer != NULL, "GL_RENDERER returned NULL!" ); + + if (renderer) + { + dStrcpy(toAdd->mName, renderer); + dStrncat(toAdd->mName, " OpenGL", GFXAdapter::MaxAdapterNameLen); + } + else + dStrcpy(toAdd->mName, "OpenGL"); + + toAdd->mType = OpenGL; + toAdd->mShaderModel = 0.f; + toAdd->mCreateDeviceInstanceDelegate = mCreateDeviceInstance; + + // Enumerate all available resolutions: + DEVMODE devMode; + U32 modeNum = 0; + U32 stillGoing = true; + while ( stillGoing ) + { + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + + stillGoing = EnumDisplaySettings( NULL, modeNum++, &devMode ); + + if (( devMode.dmPelsWidth >= 480) && (devMode.dmPelsHeight >= 360 ) + && ( devMode.dmBitsPerPel == 16 || devMode.dmBitsPerPel == 32 )) + { + GFXVideoMode vmAdd; + + vmAdd.bitDepth = devMode.dmBitsPerPel; + vmAdd.fullScreen = true; + vmAdd.refreshRate = devMode.dmDisplayFrequency; + vmAdd.resolution.x = devMode.dmPelsWidth; + vmAdd.resolution.y = devMode.dmPelsHeight; + + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for (Vector::iterator i = toAdd->mAvailableModes.begin(); i != toAdd->mAvailableModes.end(); i++) + { + if (vmAdd == *i) + { + alreadyInList = true; + break; + } + } + + if(alreadyInList) + continue; + + toAdd->mAvailableModes.push_back( vmAdd ); + } + } + + // Add to the list of available adapters. + adapterList.push_back(toAdd); + + // Cleanup our window + wglMakeCurrent(NULL, NULL); + wglDeleteContext(tempGLRC); + ReleaseDC(hwnd, tempDC); + DestroyWindow(hwnd); + UnregisterClass(L"GFX-OpenGL", winState.appInstance); +} + +void GFXGLDevice::enumerateVideoModes() +{ + mVideoModes.clear(); + + // Enumerate all available resolutions: + DEVMODE devMode; + U32 modeNum = 0; + U32 stillGoing = true; + while ( stillGoing ) + { + dMemset( &devMode, 0, sizeof( devMode ) ); + devMode.dmSize = sizeof( devMode ); + + stillGoing = EnumDisplaySettings( NULL, modeNum++, &devMode ); + + if (( devMode.dmPelsWidth >= 480) && (devMode.dmPelsHeight >= 360 ) + && ( devMode.dmBitsPerPel == 16 || devMode.dmBitsPerPel == 32 )) + //( smCanSwitchBitDepth || devMode.dmBitsPerPel == winState.desktopBitsPixel ) ) + { + GFXVideoMode toAdd; + + toAdd.bitDepth = devMode.dmBitsPerPel; + toAdd.fullScreen = false; + toAdd.refreshRate = devMode.dmDisplayFrequency; + toAdd.resolution.x = devMode.dmPelsWidth; + toAdd.resolution.y = devMode.dmPelsHeight; + + // Only add this resolution if it is not already in the list: + bool alreadyInList = false; + for (Vector::iterator i = mVideoModes.begin(); i != mVideoModes.end(); i++) + { + if (toAdd == *i) + { + alreadyInList = true; + break; + } + } + if ( !alreadyInList ) + { + //Con::printf("Resolution: %dx%d %d bpp %d Refresh rate: %d", toAdd.resolution.x, toAdd.resolution.y, toAdd.bitDepth, toAdd.refreshRate); + mVideoModes.push_back( toAdd ); + } + } + } +} + +void GFXGLDevice::init( const GFXVideoMode &mode, PlatformWindow *window ) +{ + AssertFatal(window, "GFXGLDevice::init - no window specified, can't init device without a window!"); + AssertFatal(dynamic_cast(window), "Invalid window class type!"); + HWND hwnd = GETHWND(window); + + RECT rect; + GetClientRect(hwnd, &rect); + + Point2I resolution; + resolution.x = rect.right - rect.left; + resolution.y = rect.bottom - rect.top; + + // Create a device context + HDC hdcGL = GetDC( hwnd ); + AssertFatal( hdcGL != NULL, "Failed to create device context" ); + + // Create pixel format descriptor... + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, 16, 16, 8, false ); // 16 bit color, 16 bit depth, 8 bit stencil...everyone can do this + if( !SetPixelFormat( hdcGL, ChoosePixelFormat( hdcGL, &pfd ), &pfd ) ) + { + AssertFatal( false, "GFXGLDevice::init - cannot get the one and only pixel format we check for." ); + } + + // Create a rendering context! + mContext = wglCreateContext( hdcGL ); + if( !wglMakeCurrent( hdcGL, (HGLRC)mContext ) ) + AssertFatal( false , "GFXGLDevice::init - cannot make our context current. Or maybe we can't create it." ); + + loadGLCore(); + loadGLExtensions(hdcGL); + + wglSwapIntervalEXT(0); + + // It is very important that extensions be loaded + // before we call initGLState() + initGLState(); + + mProjectionMatrix.identity(); + + mInitialized = true; + deviceInited(); +} + +bool GFXGLDevice::beginSceneInternal() +{ + glGetError(); + return true; +} + +U32 GFXGLDevice::getTotalVideoMemory() +{ + // CodeReview [ags 12/21/07] Figure out how to do this. + return 0; +} + +//------------------------------------------------------------------------------ + +GFXWindowTarget *GFXGLDevice::allocWindowTarget( PlatformWindow *window ) +{ + HDC hdcGL = GetDC(GETHWND(window)); + + if(!mContext) + { + init(window->getVideoMode(), window); + GFXGLWindowTarget *ggwt = new GFXGLWindowTarget(window, this); + ggwt->registerResourceWithDevice(this); + ggwt->mContext = wglCreateContext(hdcGL); + AssertFatal(ggwt->mContext, "GFXGLDevice::allocWindowTarget - failed to allocate window target!"); + + return ggwt; + } + + GFXGLWindowTarget *ggwt = new GFXGLWindowTarget(window, this); + ggwt->registerResourceWithDevice(this); + + // Create pixel format descriptor... + PIXELFORMATDESCRIPTOR pfd; + CreatePixelFormat( &pfd, 16, 16, 8, false ); // 16 bit color, 16 bit depth, 8 bit stencil...everyone can do this + if( !SetPixelFormat( hdcGL, ChoosePixelFormat( hdcGL, &pfd ), &pfd ) ) + { + AssertFatal( false, "GFXGLDevice::allocWindowTarget - cannot get the one and only pixel format we check for." ); + } + + ggwt->mContext = wglCreateContext(hdcGL); + DWORD w = GetLastError(); + AssertFatal(ggwt->mContext, "GFXGLDevice::allocWindowTarget - failed to allocate window target!"); + + wglMakeCurrent(NULL, NULL); + bool res = wglShareLists((HGLRC)mContext, (HGLRC)ggwt->mContext); + w = GetLastError(); + + wglMakeCurrent(hdcGL, (HGLRC)ggwt->mContext); + AssertFatal(res, "GFXGLDevice::allocWindowTarget - wasn't able to share contexts!"); + + return ggwt; +} + +void GFXGLDevice::_updateRenderTargets() +{ + if ( mRTDirty || mCurrentRT->isPendingState() ) + { + // GL doesn't need to deactivate targets. + mRTDeactivate = NULL; + + // NOTE: The render target changes is not really accurate + // as the GFXTextureTarget supports MRT internally. So when + // we activate a GFXTarget it could result in multiple calls + // to SetRenderTarget on the actual device. + mDeviceStatistics.mRenderTargetChanges++; + + GFXGLTextureTarget *tex = dynamic_cast( mCurrentRT.getPointer() ); + if ( tex ) + { + tex->applyState(); + tex->makeActive(); + } + else + { + GFXGLWindowTarget *win = dynamic_cast( mCurrentRT.getPointer() ); + AssertFatal( win != NULL, + "GFXGLDevice::_updateRenderTargets() - invalid target subclass passed!" ); + + //DWORD w1 = GetLastError(); + HWND hwnd = GETHWND(win->getWindow()); + HDC winDc = GetDC(hwnd); + bool res = wglMakeCurrent(winDc,(HGLRC)win->mContext); + //DWORD w2 = GetLastError(); + AssertFatal(res==true,"GFXGLDevice::setActiveRenderTarget - failed"); + } + + mRTDirty = false; + } + + if ( mViewportDirty ) + { + glViewport( mViewport.point.x, mViewport.point.y, mViewport.extent.x, mViewport.extent.y ); + mViewportDirty = false; + } +} + +GFXFence* GFXGLDevice::_createPlatformSpecificFence() +{ + return NULL; +} + + +//----------------------------------------------------------------------------- + +void GFXGLWindowTarget::makeActive() +{ +} + +bool GFXGLWindowTarget::present() +{ + HWND hwnd = GETHWND(getWindow()); + SwapBuffers(GetDC(hwnd)); + return true; +} + +void GFXGLWindowTarget::_teardownCurrentMode() +{ +} + +void GFXGLWindowTarget::_setupNewMode() +{ +} diff --git a/gfx/gl/gfxGLEnumTranslate.cpp b/gfx/gl/gfxGLEnumTranslate.cpp new file mode 100644 index 0000000..280021e --- /dev/null +++ b/gfx/gl/gfxGLEnumTranslate.cpp @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gl/gfxGLEnumTranslate.h" + +GLenum GFXGLPrimType[GFXPT_COUNT]; +GLenum GFXGLBlend[GFXBlend_COUNT]; +GLenum GFXGLBlendOp[GFXBlendOp_COUNT]; +GLenum GFXGLSamplerState[GFXSAMP_COUNT]; +GLenum GFXGLTextureFilter[GFXTextureFilter_COUNT]; +GLenum GFXGLTextureAddress[GFXAddress_COUNT]; +GLenum GFXGLCmpFunc[GFXCmp_COUNT]; +GLenum GFXGLStencilOp[GFXStencilOp_COUNT]; +GLenum GFXGLTextureInternalFormat[GFXFormat_COUNT]; +GLenum GFXGLTextureFormat[GFXFormat_COUNT]; +GLenum GFXGLTextureType[GFXFormat_COUNT]; +GLenum GFXGLBufferType[GFXBufferType_COUNT]; +GLenum GFXGLCullMode[GFXCull_COUNT]; +GLenum GFXGLFillMode[GFXFill_COUNT]; + +void GFXGLEnumTranslate::init() +{ + // Buffer types + GFXGLBufferType[GFXBufferTypeStatic] = GL_STATIC_DRAW; + GFXGLBufferType[GFXBufferTypeDynamic] = GL_DYNAMIC_DRAW; + GFXGLBufferType[GFXBufferTypeVolatile] = GL_STREAM_DRAW; + + // Primitives + GFXGLPrimType[GFXPointList] = GL_POINTS; + GFXGLPrimType[GFXLineList] = GL_LINES; + GFXGLPrimType[GFXLineStrip] = GL_LINE_STRIP; + GFXGLPrimType[GFXTriangleList] = GL_TRIANGLES; + GFXGLPrimType[GFXTriangleStrip] = GL_TRIANGLE_STRIP; + GFXGLPrimType[GFXTriangleFan] = GL_TRIANGLE_FAN; + + // Blend + GFXGLBlend[GFXBlendZero] = GL_ZERO; + GFXGLBlend[GFXBlendOne] = GL_ONE; + GFXGLBlend[GFXBlendSrcColor] = GL_SRC_COLOR; + GFXGLBlend[GFXBlendInvSrcColor] = GL_ONE_MINUS_SRC_COLOR; + GFXGLBlend[GFXBlendSrcAlpha] = GL_SRC_ALPHA; + GFXGLBlend[GFXBlendInvSrcAlpha] = GL_ONE_MINUS_SRC_ALPHA; + GFXGLBlend[GFXBlendDestAlpha] = GL_DST_ALPHA; + GFXGLBlend[GFXBlendInvDestAlpha] = GL_ONE_MINUS_DST_ALPHA; + GFXGLBlend[GFXBlendDestColor] = GL_DST_COLOR; + GFXGLBlend[GFXBlendInvDestColor] = GL_ONE_MINUS_DST_COLOR; + GFXGLBlend[GFXBlendSrcAlphaSat] = GL_SRC_ALPHA_SATURATE; + + // Blend op + GFXGLBlendOp[GFXBlendOpAdd] = GL_FUNC_ADD; + GFXGLBlendOp[GFXBlendOpSubtract] = GL_FUNC_SUBTRACT; + GFXGLBlendOp[GFXBlendOpRevSubtract] = GL_FUNC_REVERSE_SUBTRACT; + GFXGLBlendOp[GFXBlendOpMin] = GL_MIN; + GFXGLBlendOp[GFXBlendOpMax] = GL_MAX; + + // Sampler + GFXGLSamplerState[GFXSAMPMagFilter] = GL_TEXTURE_MAG_FILTER; + GFXGLSamplerState[GFXSAMPMinFilter] = GL_TEXTURE_MIN_FILTER; + GFXGLSamplerState[GFXSAMPAddressU] = GL_TEXTURE_WRAP_S; + GFXGLSamplerState[GFXSAMPAddressV] = GL_TEXTURE_WRAP_T; + GFXGLSamplerState[GFXSAMPAddressW] = GL_TEXTURE_WRAP_R; + + // Comparison + GFXGLCmpFunc[GFXCmpNever] = GL_NEVER; + GFXGLCmpFunc[GFXCmpLess] = GL_LESS; + GFXGLCmpFunc[GFXCmpEqual] = GL_EQUAL; + GFXGLCmpFunc[GFXCmpLessEqual] = GL_LEQUAL; + GFXGLCmpFunc[GFXCmpGreater] = GL_GREATER; + GFXGLCmpFunc[GFXCmpNotEqual] = GL_NOTEQUAL; + GFXGLCmpFunc[GFXCmpGreaterEqual] = GL_GEQUAL; + GFXGLCmpFunc[GFXCmpAlways] = GL_ALWAYS; + + GFXGLTextureFilter[GFXTextureFilterNone] = GL_NEAREST; + GFXGLTextureFilter[GFXTextureFilterPoint] = GL_NEAREST; + GFXGLTextureFilter[GFXTextureFilterLinear] = GL_LINEAR; + + GFXGLTextureFilter[GFXTextureFilterAnisotropic] = GL_LINEAR; + GFXGLTextureFilter[GFXTextureFilterPyramidalQuad] = GL_LINEAR; + GFXGLTextureFilter[GFXTextureFilterGaussianQuad] = GL_LINEAR; + + GFXGLTextureAddress[GFXAddressWrap] = GL_REPEAT; + GFXGLTextureAddress[GFXAddressMirror] = GL_REPEAT; + GFXGLTextureAddress[GFXAddressClamp] = GL_CLAMP_TO_EDGE; + GFXGLTextureAddress[GFXAddressBorder] = GL_REPEAT; + GFXGLTextureAddress[GFXAddressMirrorOnce] = GL_REPEAT; + + // Stencil ops + GFXGLStencilOp[GFXStencilOpKeep] = GL_KEEP; + GFXGLStencilOp[GFXStencilOpZero] = GL_ZERO; + GFXGLStencilOp[GFXStencilOpReplace] = GL_REPLACE; + GFXGLStencilOp[GFXStencilOpIncrSat] = GL_INCR; + GFXGLStencilOp[GFXStencilOpDecrSat] = GL_DECR; + GFXGLStencilOp[GFXStencilOpInvert] = GL_INVERT; + + GFXGLStencilOp[GFXStencilOpIncr] = GL_INCR_WRAP; + GFXGLStencilOp[GFXStencilOpDecr] = GL_DECR_WRAP; + + + // Texture formats + GFXGLTextureInternalFormat[GFXFormatA8] = GL_ALPHA8; + GFXGLTextureInternalFormat[GFXFormatL8] = GL_LUMINANCE8; + GFXGLTextureInternalFormat[GFXFormatR5G6B5] = GL_RGB5_A1; // OpenGL has no R5G6B5 format. + GFXGLTextureInternalFormat[GFXFormatR5G5B5A1] = GL_RGB5_A1; + GFXGLTextureInternalFormat[GFXFormatR5G5B5X1] = GL_RGB5_A1; + GFXGLTextureInternalFormat[GFXFormatL16] = GL_LUMINANCE16; + GFXGLTextureInternalFormat[GFXFormatR16F] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatD16] = GL_DEPTH_COMPONENT; + GFXGLTextureInternalFormat[GFXFormatR8G8B8] = GL_RGB8; + GFXGLTextureInternalFormat[GFXFormatR8G8B8A8] = GL_RGBA8; + GFXGLTextureInternalFormat[GFXFormatR8G8B8X8] = GL_RGBA8; + GFXGLTextureInternalFormat[GFXFormatR32F] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatR16G16] = GL_RGBA16; + GFXGLTextureInternalFormat[GFXFormatR16G16F] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatR10G10B10A2] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatD32] = GL_DEPTH_COMPONENT32; + GFXGLTextureInternalFormat[GFXFormatD24X8] = GL_DEPTH_COMPONENT24; + GFXGLTextureInternalFormat[GFXFormatD24S8] = GL_DEPTH_COMPONENT24; + GFXGLTextureInternalFormat[GFXFormatR16G16B16A16] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatR16G16B16A16F] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatR32G32B32A32F] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatDXT1] = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + GFXGLTextureInternalFormat[GFXFormatDXT2] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatDXT3] = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + GFXGLTextureInternalFormat[GFXFormatDXT4] = GL_ZERO; + GFXGLTextureInternalFormat[GFXFormatDXT5] = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + + GFXGLTextureFormat[GFXFormatA8] = GL_ALPHA; + GFXGLTextureFormat[GFXFormatL8] = GL_LUMINANCE; + GFXGLTextureFormat[GFXFormatR5G6B5] = GL_RGBA; + GFXGLTextureFormat[GFXFormatR5G5B5A1] = GL_RGBA; + GFXGLTextureFormat[GFXFormatR5G5B5X1] = GL_RGBA; + GFXGLTextureFormat[GFXFormatL16] = GL_LUMINANCE; + GFXGLTextureFormat[GFXFormatR16F] = GL_ZERO; + GFXGLTextureFormat[GFXFormatD16] = GL_DEPTH_COMPONENT; + GFXGLTextureFormat[GFXFormatR8G8B8] = GL_RGB; + GFXGLTextureFormat[GFXFormatR8G8B8A8] = GL_BGRA; + GFXGLTextureFormat[GFXFormatR8G8B8X8] = GL_BGRA; + GFXGLTextureFormat[GFXFormatR32F] = GL_RGBA; + GFXGLTextureFormat[GFXFormatR16G16] = GL_RGBA; + GFXGLTextureFormat[GFXFormatR16G16F] = GL_ZERO; + GFXGLTextureFormat[GFXFormatR10G10B10A2] = GL_RGBA; + GFXGLTextureFormat[GFXFormatD32] = GL_DEPTH_COMPONENT; + GFXGLTextureFormat[GFXFormatD24X8] = GL_DEPTH_COMPONENT; + GFXGLTextureFormat[GFXFormatD24S8] = GL_DEPTH_COMPONENT; + GFXGLTextureFormat[GFXFormatR16G16B16A16] = GL_RGBA; + GFXGLTextureFormat[GFXFormatR16G16B16A16F] = GL_RGBA; + GFXGLTextureFormat[GFXFormatR32G32B32A32F] = GL_RGBA; + GFXGLTextureFormat[GFXFormatDXT1] = GL_RGBA; + GFXGLTextureFormat[GFXFormatDXT2] = GL_ZERO; + GFXGLTextureFormat[GFXFormatDXT3] = GL_RGBA; + GFXGLTextureFormat[GFXFormatDXT4] = GL_ZERO; + GFXGLTextureFormat[GFXFormatDXT5] = GL_RGBA; + + GFXGLTextureType[GFXFormatA8] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatL8] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatR5G6B5] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatR5G5B5A1] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatR5G5B5X1] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatL16] = GL_UNSIGNED_SHORT; + GFXGLTextureType[GFXFormatR16F] = GL_ZERO; + GFXGLTextureType[GFXFormatD16] = GL_UNSIGNED_SHORT; + GFXGLTextureType[GFXFormatR8G8B8] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatR8G8B8A8] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatR8G8B8X8] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatR32F] = GL_FLOAT; + GFXGLTextureType[GFXFormatR16G16] = GL_UNSIGNED_SHORT; + GFXGLTextureType[GFXFormatR16G16F] = GL_FLOAT; + GFXGLTextureType[GFXFormatR10G10B10A2] = GL_UNSIGNED_SHORT; + GFXGLTextureType[GFXFormatD32] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatD24X8] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatD24S8] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatR16G16B16A16] = GL_UNSIGNED_SHORT; + GFXGLTextureType[GFXFormatR16G16B16A16F] = GL_FLOAT; + GFXGLTextureType[GFXFormatR32G32B32A32F] = GL_FLOAT; + GFXGLTextureType[GFXFormatDXT1] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatDXT2] = GL_ZERO; + GFXGLTextureType[GFXFormatDXT3] = GL_UNSIGNED_BYTE; + GFXGLTextureType[GFXFormatDXT4] = GL_ZERO; + GFXGLTextureType[GFXFormatDXT5] = GL_UNSIGNED_BYTE; + + // Cull + GFXGLCullMode[GFXCullNone] = GL_BACK; + GFXGLCullMode[GFXCullCW] = GL_BACK; + GFXGLCullMode[GFXCullCCW] = GL_FRONT; + + // Fill + GFXGLFillMode[GFXFillPoint] = GL_POINT; + GFXGLFillMode[GFXFillWireframe] = GL_LINE; + GFXGLFillMode[GFXFillSolid] = GL_FILL; +} diff --git a/gfx/gl/gfxGLEnumTranslate.h b/gfx/gl/gfxGLEnumTranslate.h new file mode 100644 index 0000000..b474737 --- /dev/null +++ b/gfx/gl/gfxGLEnumTranslate.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLENUMTRANSLATE_H_ +#define _GFXGLENUMTRANSLATE_H_ + +#include "gfx/gfxEnums.h" +#include "gfx/gl/gfxGLDevice.h" + +namespace GFXGLEnumTranslate +{ + void init(); +}; + +extern GLenum GFXGLPrimType[GFXPT_COUNT]; +extern GLenum GFXGLBlend[GFXBlend_COUNT]; +extern GLenum GFXGLBlendOp[GFXBlendOp_COUNT]; +extern GLenum GFXGLSamplerState[GFXSAMP_COUNT]; +extern GLenum GFXGLTextureFilter[GFXTextureFilter_COUNT]; +extern GLenum GFXGLTextureAddress[GFXAddress_COUNT]; +extern GLenum GFXGLCmpFunc[GFXCmp_COUNT]; +extern GLenum GFXGLStencilOp[GFXStencilOp_COUNT]; + +extern GLenum GFXGLTextureInternalFormat[GFXFormat_COUNT]; +extern GLenum GFXGLTextureFormat[GFXFormat_COUNT]; +extern GLenum GFXGLTextureType[GFXFormat_COUNT]; + +extern GLenum GFXGLBufferType[GFXBufferType_COUNT]; +extern GLenum GFXGLCullMode[GFXCull_COUNT]; + +extern GLenum GFXGLFillMode[GFXFill_COUNT]; + +#endif diff --git a/gfx/gl/gfxGLOcclusionQuery.cpp b/gfx/gl/gfxGLOcclusionQuery.cpp new file mode 100644 index 0000000..3bac9c6 --- /dev/null +++ b/gfx/gl/gfxGLOcclusionQuery.cpp @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gl/gfxGLOcclusionQuery.h" +#include "gfx/gl/ggl/ggl.h" + +GFXGLOcclusionQuery::GFXGLOcclusionQuery(GFXDevice* device) : + GFXOcclusionQuery(device), mQuery(0) +{ + glGenQueries(1, &mQuery); +} + +GFXGLOcclusionQuery::~GFXGLOcclusionQuery() +{ + glDeleteQueries(1, &mQuery); +} + +bool GFXGLOcclusionQuery::begin() +{ + glBeginQuery(GL_SAMPLES_PASSED, mQuery); + return true; +} + +void GFXGLOcclusionQuery::end() +{ + glEndQuery(GL_SAMPLES_PASSED); +} + +GFXOcclusionQuery::OcclusionQueryStatus GFXGLOcclusionQuery::getStatus(bool block, U32* data) +{ + // If this ever shows up near the top of a profile + // then your system is GPU bound. + PROFILE_SCOPE(GFXGLOcclusionQuery_getStatus); + + GLint numPixels = 0; + GLint queryDone = false; + + if (block) + queryDone = true; + else + glGetQueryObjectiv(mQuery, GL_QUERY_RESULT_AVAILABLE, &queryDone); + + if (queryDone) + glGetQueryObjectiv(mQuery, GL_QUERY_RESULT, &numPixels); + else + return Waiting; + + if (data) + *data = numPixels; + + return numPixels > 0 ? NotOccluded : Occluded; +} + +void GFXGLOcclusionQuery::zombify() +{ + glDeleteQueries(1, &mQuery); + mQuery = 0; +} + +void GFXGLOcclusionQuery::resurrect() +{ + glGenQueries(1, &mQuery); +} + +const String GFXGLOcclusionQuery::describeSelf() const +{ + // We've got nothing + return String(); +} diff --git a/gfx/gl/gfxGLOcclusionQuery.h b/gfx/gl/gfxGLOcclusionQuery.h new file mode 100644 index 0000000..a2a7af5 --- /dev/null +++ b/gfx/gl/gfxGLOcclusionQuery.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFX_GL_OCCLUSIONQUERY_H_ +#define _GFX_GL_OCCLUSIONQUERY_H_ + +#ifndef _GFXOCCLUSIONQUERY_H_ +#include "gfx/gfxOcclusionQuery.h" +#endif + +class GFXGLOcclusionQuery : public GFXOcclusionQuery +{ +public: + GFXGLOcclusionQuery( GFXDevice *device ); + virtual ~GFXGLOcclusionQuery(); + + virtual bool begin(); + virtual void end(); + virtual OcclusionQueryStatus getStatus( bool block, U32 *data = NULL ); + + // GFXResource + virtual void zombify(); + virtual void resurrect(); + virtual const String describeSelf() const; + +private: + U32 mQuery; +}; + +#endif // _GFX_GL_OCCLUSIONQUERY_H_ diff --git a/gfx/gl/gfxGLPrimitiveBuffer.cpp b/gfx/gl/gfxGLPrimitiveBuffer.cpp new file mode 100644 index 0000000..400e36a --- /dev/null +++ b/gfx/gl/gfxGLPrimitiveBuffer.cpp @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gl/gfxGLDevice.h" +#include "gfx/gl/gfxGLPrimitiveBuffer.h" +#include "gfx/gl/gfxGLEnumTranslate.h" + +#include "gfx/gl/ggl/ggl.h" +#include "gfx/gl/gfxGLUtils.h" + +GFXGLPrimitiveBuffer::GFXGLPrimitiveBuffer(GFXDevice *device, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType) : +GFXPrimitiveBuffer(device, indexCount, primitiveCount, bufferType), mZombieCache(NULL) +{ + PRESERVE_INDEX_BUFFER(); + // Generate a buffer and allocate the needed memory + glGenBuffers(1, &mBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * sizeof(U16), NULL, GFXGLBufferType[bufferType]); +} + +GFXGLPrimitiveBuffer::~GFXGLPrimitiveBuffer() +{ + // This is heavy handed, but it frees the buffer memory + glDeleteBuffersARB(1, &mBuffer); + + if( mZombieCache ) + delete [] mZombieCache; +} + +void GFXGLPrimitiveBuffer::lock(U16 indexStart, U16 indexEnd, U16 **indexPtr) +{ + // Preserve previous binding + PRESERVE_INDEX_BUFFER(); + + // Bind ourselves and map + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndexCount * sizeof(U16), NULL, GFXGLBufferType[mBufferType]); + + // Offset the buffer to indexStart + *indexPtr = (U16*)((U8*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY) + (indexStart * sizeof(U16))); +} + +void GFXGLPrimitiveBuffer::unlock() +{ + // Preserve previous binding + PRESERVE_INDEX_BUFFER(); + + // Bind ourselves and unmap + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBuffer); + bool res = glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); + AssertFatal(res, "GFXGLPrimitiveBuffer::unlock - shouldn't fail!"); +} + +void GFXGLPrimitiveBuffer::prepare() +{ + // Bind + static_cast(mDevice)->setPB(this); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBuffer); +} + +void GFXGLPrimitiveBuffer::finish() +{ + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +} + +GLvoid* GFXGLPrimitiveBuffer::getBuffer() +{ + // NULL specifies no offset into the hardware buffer + return (GLvoid*)NULL; +} + +void GFXGLPrimitiveBuffer::zombify() +{ + if(mZombieCache) + return; + + mZombieCache = new U8[mIndexCount * sizeof(U16)]; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBuffer); + glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, mIndexCount * sizeof(U16), mZombieCache); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &mBuffer); + mBuffer = 0; +} + +void GFXGLPrimitiveBuffer::resurrect() +{ + if(!mZombieCache) + return; + + glGenBuffers(1, &mBuffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndexCount * sizeof(U16), mZombieCache, GFXGLBufferType[mBufferType]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + delete[] mZombieCache; + mZombieCache = NULL; +} diff --git a/gfx/gl/gfxGLPrimitiveBuffer.h b/gfx/gl/gfxGLPrimitiveBuffer.h new file mode 100644 index 0000000..e62ba97 --- /dev/null +++ b/gfx/gl/gfxGLPrimitiveBuffer.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLPRIMITIVEBUFFER_H_ +#define _GFXGLPRIMITIVEBUFFER_H_ + +#include "gfx/gfxPrimitiveBuffer.h" + +/// This is a primitive buffer (index buffer to GL users) which uses VBOs. +class GFXGLPrimitiveBuffer : public GFXPrimitiveBuffer +{ +public: + GFXGLPrimitiveBuffer(GFXDevice *device, U32 indexCount, U32 primitiveCount, GFXBufferType bufferType); + ~GFXGLPrimitiveBuffer(); + + virtual void lock(U16 indexStart, U16 indexEnd, U16 **indexPtr); ///< calls glMapBuffer, offets pointer by indexStart + virtual void unlock(); ///< calls glUnmapBuffer, unbinds the buffer + virtual void prepare(); ///< binds the buffer + virtual void finish(); ///< We're done with this buffer + + virtual void* getBuffer(); ///< returns NULL + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + +private: + /// Handle to our GL buffer object + GLuint mBuffer; + + U8* mZombieCache; +}; + +#endif \ No newline at end of file diff --git a/gfx/gl/gfxGLShader.cpp b/gfx/gl/gfxGLShader.cpp new file mode 100644 index 0000000..d9fc9bc --- /dev/null +++ b/gfx/gl/gfxGLShader.cpp @@ -0,0 +1,908 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gl/gfxGLShader.h" + +#include "core/frameAllocator.h" +#include "core/stream/fileStream.h" +#include "core/strings/stringFunctions.h" +#include "math/mPoint2.h" +#include "gfx/gfxStructs.h" +#include "console/console.h" + + +class GFXGLShaderConstHandle : public GFXShaderConstHandle +{ + friend class GFXGLShader; + +public: + + GFXGLShaderConstHandle( GFXGLShader *shader ); + GFXGLShaderConstHandle( GFXGLShader *shader, const GFXShaderConstDesc &desc, GLuint loc, S32 samplerNum ); + virtual ~GFXGLShaderConstHandle(); + + void reinit( const GFXShaderConstDesc &desc, GLuint loc, S32 samplerNum ); + + const String& getName() const { return mDesc.name; } + GFXShaderConstType getType() const { return mDesc.constType; } + U32 getArraySize() const { return mDesc.arraySize; } + + U32 getSize() const; + void setValid( bool valid ) { mValid = valid; } + /// @warning This will always return the value assigned when the shader was + /// initialized. If the value is later changed this method won't reflect that. + S32 getSamplerRegister() const { return mSamplerNum; } + + GFXShaderConstDesc mDesc; + GFXGLShader* mShader; + GLuint mLocation; + U32 mOffset; + U32 mSize; + S32 mSamplerNum; +}; + +GFXGLShaderConstHandle::GFXGLShaderConstHandle( GFXGLShader *shader ) + : mShader( shader ), mSamplerNum(-1) +{ + mValid = false; +} + +static U32 shaderConstTypeSize(GFXShaderConstType type) +{ + switch(type) + { + case GFXSCT_Float: + case GFXSCT_Int: + case GFXSCT_Sampler: + case GFXSCT_SamplerCube: + return 4; + case GFXSCT_Float2: + case GFXSCT_Int2: + return 8; + case GFXSCT_Float3: + case GFXSCT_Int3: + return 12; + case GFXSCT_Float4: + case GFXSCT_Int4: + return 16; + case GFXSCT_Float2x2: + return 16; + case GFXSCT_Float3x3: + return 36; + case GFXSCT_Float4x4: + return 64; + default: + AssertFatal(false,"shaderConstTypeSize - Unrecognized constant type"); + return 0; + } +} + +GFXGLShaderConstHandle::GFXGLShaderConstHandle( GFXGLShader *shader, const GFXShaderConstDesc &desc, GLuint loc, S32 samplerNum ) + : mShader(shader) +{ + reinit(desc, loc, samplerNum); +} + +void GFXGLShaderConstHandle::reinit( const GFXShaderConstDesc& desc, GLuint loc, S32 samplerNum ) +{ + mDesc = desc; + mLocation = loc; + mSamplerNum = samplerNum; + mOffset = 0; + + U32 elemSize = shaderConstTypeSize(mDesc.constType); + AssertFatal(elemSize, "GFXGLShaderConst::GFXGLShaderConst - elemSize is 0"); + mSize = mDesc.arraySize * elemSize; + mValid = true; +} + + +U32 GFXGLShaderConstHandle::getSize() const +{ + return mSize; +} + +GFXGLShaderConstHandle::~GFXGLShaderConstHandle() +{ +} + +GFXGLShaderConstBuffer::GFXGLShaderConstBuffer(GFXGLShader* shader, U32 bufSize, U8* existingConstants) +{ + mShader = shader; + mBuffer = new U8[bufSize]; + // Copy the existing constant buffer to preserve sampler numbers + /// @warning This preserves a lot more than sampler numbers, obviously. If there + /// is any code that assumes a new constant buffer will have everything set to + /// 0, it will break. + dMemcpy(mBuffer, existingConstants, bufSize); +} + +GFXGLShaderConstBuffer::~GFXGLShaderConstBuffer() +{ + delete[] mBuffer; + + if ( mShader ) + mShader->_unlinkBuffer( this ); +} + +template +void GFXGLShaderConstBuffer::internalSet(GFXShaderConstHandle* handle, const ConstType& param) +{ + if( !handle || !handle->isValid() ) + return; + + AssertFatal(dynamic_cast(handle), "GFXGLShaderConstBuffer::set - Incorrect const buffer type"); + GFXGLShaderConstHandle* _glHandle = static_cast(handle); + AssertFatal(mShader == _glHandle->mShader, "GFXGLShaderConstBuffer::set - Should only set handles which are owned by our shader"); + + dMemcpy(mBuffer + _glHandle->mOffset, ¶m, sizeof(ConstType)); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const F32 fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point2F& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point3F& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point4F& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const PlaneF& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const ColorF& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const S32 fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point2I& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point3I& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const Point4I& fv) +{ + internalSet(handle, fv); +} + +template +void GFXGLShaderConstBuffer::internalSet(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + if( !handle || !handle->isValid() ) + return; + + AssertFatal(dynamic_cast(handle), "GFXGLShaderConstBuffer::set - Incorrect const buffer type"); + GFXGLShaderConstHandle* _glHandle = static_cast(handle); + AssertFatal(mShader == _glHandle->mShader, "GFXGLShaderConstBuffer::set - Should only set handles which are owned by our shader"); + const U8* fvBuffer = static_cast(fv.getBuffer()); + for(U32 i = 0; i < fv.size(); ++i) + { + dMemcpy(mBuffer + _glHandle->mOffset + i * sizeof(ConstType), fvBuffer, sizeof(ConstType)); + fvBuffer += fv.getElementSize(); + } +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const AlignedArray& fv) +{ + internalSet(handle, fv); +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const MatrixF& mat, const GFXShaderConstType matType) +{ + if( !handle || !handle->isValid() ) + return; + + AssertFatal(dynamic_cast(handle), "GFXGLShaderConstBuffer::set - Incorrect const buffer type"); + GFXGLShaderConstHandle* _glHandle = static_cast(handle); + AssertFatal(mShader == _glHandle->mShader, "GFXGLShaderConstBuffer::set - Should only set handles which are owned by our shader"); + + switch(matType) + { + case GFXSCT_Float2x2: + reinterpret_cast(mBuffer + _glHandle->mOffset)[0] = mat[0]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[1] = mat[1]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[2] = mat[4]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[3] = mat[5]; + break; + case GFXSCT_Float3x3: + reinterpret_cast(mBuffer + _glHandle->mOffset)[0] = mat[0]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[1] = mat[1]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[2] = mat[2]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[3] = mat[4]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[4] = mat[5]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[5] = mat[6]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[6] = mat[8]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[7] = mat[9]; + reinterpret_cast(mBuffer + _glHandle->mOffset)[8] = mat[10]; + break; + case GFXSCT_Float4x4: + dMemcpy(mBuffer + _glHandle->mOffset, (const F32*)mat, sizeof(MatrixF)); + break; + default: + AssertFatal(false, "GFXGLShaderConstBuffer::set - Invalid matrix type"); + break; + } +} + +void GFXGLShaderConstBuffer::set(GFXShaderConstHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType) +{ + if( !handle || !handle->isValid() ) + return; + + GFXGLShaderConstHandle* _glHandle = static_cast(handle); + AssertFatal(mShader == _glHandle->mShader, "GFXGLShaderConstBuffer::set - Should only set handles which are owned by our shader"); + + switch (matrixType) { + case GFXSCT_Float4x4: + dMemcpy(mBuffer + _glHandle->mOffset, (F32*)mat, _glHandle->getSize()); + break; + default: + AssertFatal(false, "GFXGLShaderConstBuffer::set - setting array of non 4x4 matrices!"); + break; + } +} + +void GFXGLShaderConstBuffer::activate() +{ + mShader->setConstantsFromBuffer(this); +} + +const String GFXGLShaderConstBuffer::describeSelf() const +{ + return String(); +} + +void GFXGLShaderConstBuffer::onShaderReload( GFXShader *shader ) +{ + AssertFatal( shader == mShader, "GFXGLShaderConstBuffer::onShaderReload, mismatched shaders!" ); + + delete[] mBuffer; + mBuffer = new U8[mShader->mConstBufferSize]; + dMemset(mBuffer, 0, mShader->mConstBufferSize); +} + +GFXGLShader::GFXGLShader() : + mVertexShader(0), + mPixelShader(0), + mProgram(0), + mConstBufferSize(0), + mConstBuffer(NULL) +{ +} + +GFXGLShader::~GFXGLShader() +{ + clearShaders(); + for(HandleMap::Iterator i = mHandles.begin(); i != mHandles.end(); i++) + delete i->value; + + delete[] mConstBuffer; +} + +void GFXGLShader::clearShaders() +{ + glDeleteProgram(mProgram); + glDeleteShader(mVertexShader); + glDeleteShader(mPixelShader); + + mProgram = 0; + mVertexShader = 0; + mPixelShader = 0; +} + +bool GFXGLShader::_init() +{ + // Don't initialize empty shaders. + if ( mVertexFile.isEmpty() && mPixelFile.isEmpty() ) + return false; + + clearShaders(); + + mProgram = glCreateProgram(); + + // Set the macros and add the global ones. + Vector macros; + macros.merge( mMacros ); + macros.merge( smGlobalMacros ); + + // Add the shader version to the macros. + const U32 mjVer = (U32)mFloor( mPixVersion ); + const U32 mnVer = (U32)( ( mPixVersion - F32( mjVer ) ) * 10.01f ); + macros.increment(); + macros.last().name = "TORQUE_SM"; + macros.last().value = String::ToString( mjVer * 10 + mnVer ); + + // Default to true so we're "successful" if a vertex/pixel shader wasn't specified. + bool compiledVertexShader = true; + bool compiledPixelShader = true; + + // Compile the vertex and pixel shaders if specified. + if(!mVertexFile.isEmpty()) + compiledVertexShader = initShader(mVertexFile, true, macros); + if(!mPixelFile.isEmpty()) + compiledPixelShader = initShader(mPixelFile, false, macros); + + // If either shader was present and failed to compile, bail. + if(!compiledVertexShader || !compiledPixelShader) + return false; + + // Link it! + glLinkProgram( mProgram ); + + GLint linkStatus; + glGetProgramiv( mProgram, GL_LINK_STATUS, &linkStatus ); + + // Dump the info log to the console + U32 logLength = 0; + glGetProgramiv(mProgram, GL_INFO_LOG_LENGTH, (GLint*)&logLength); + if ( logLength ) + { + FrameAllocatorMarker fam; + char* log = (char*)fam.alloc( logLength ); + glGetProgramInfoLog( mProgram, logLength, NULL, log ); + + if ( linkStatus == GL_FALSE ) + { + if ( smLogErrors ) + { + Con::errorf( "GFXGLShader::init - Error linking shader!" ); + Con::errorf( "Program %s / %s: %s", + mVertexFile.getFullPath().c_str(), mPixelFile.getFullPath().c_str(), log); + } + } + else if ( smLogWarnings ) + { + Con::warnf( "Program %s / %s: %s", + mVertexFile.getFullPath().c_str(), mPixelFile.getFullPath().c_str(), log); + } + } + + // If we failed to link, bail. + if ( linkStatus == GL_FALSE ) + return false; + + initConstantDescs(); + initHandles(); + + // Notify Buffers we might have changed in size. + // If this was our first init then we won't have any activeBuffers + // to worry about unnecessarily calling. + Vector::iterator biter = mActiveBuffers.begin(); + for ( ; biter != mActiveBuffers.end(); biter++ ) + (*biter)->onShaderReload( this ); + + return true; +} + +void GFXGLShader::initConstantDescs() +{ + mConstants.clear(); + GLint numUniforms; + glGetProgramiv(mProgram, GL_ACTIVE_UNIFORMS, &numUniforms); + GLint maxNameLength; + glGetProgramiv(mProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxNameLength); + FrameTemp uniformName(maxNameLength); + + for(U32 i = 0; i < numUniforms; i++) + { + GLint size; + GLenum type; + glGetActiveUniform(mProgram, i, maxNameLength, NULL, &size, &type, uniformName); + GFXShaderConstDesc desc; + + desc.name = String((char*)uniformName); + + // Remove array brackets from the name + desc.name = desc.name.substr(0, desc.name.find('[')); + + // Insert $ to match D3D behavior of having a $ prepended to parameters to main. + desc.name.insert(0, '$'); + desc.arraySize = size; + + switch(type) + { + case GL_FLOAT: + desc.constType = GFXSCT_Float; + break; + case GL_FLOAT_VEC2: + desc.constType = GFXSCT_Float2; + break; + case GL_FLOAT_VEC3: + desc.constType = GFXSCT_Float3; + break; + case GL_FLOAT_VEC4: + desc.constType = GFXSCT_Float4; + break; + case GL_INT: + desc.constType = GFXSCT_Int; + break; + case GL_INT_VEC2: + desc.constType = GFXSCT_Int2; + break; + case GL_INT_VEC3: + desc.constType = GFXSCT_Int3; + break; + case GL_INT_VEC4: + desc.constType = GFXSCT_Int4; + break; + case GL_FLOAT_MAT2: + desc.constType = GFXSCT_Float2x2; + break; + case GL_FLOAT_MAT3: + desc.constType = GFXSCT_Float3x3; + break; + case GL_FLOAT_MAT4: + desc.constType = GFXSCT_Float4x4; + break; + case GL_SAMPLER_1D: + case GL_SAMPLER_2D: + case GL_SAMPLER_3D: + case GL_SAMPLER_1D_SHADOW: + case GL_SAMPLER_2D_SHADOW: + desc.constType = GFXSCT_Sampler; + break; + case GL_SAMPLER_CUBE: + desc.constType = GFXSCT_SamplerCube; + break; + default: + AssertFatal(false, "GFXGLShader::initConstantDescs - unrecognized uniform type"); + // If we don't recognize the constant don't add its description. + continue; + } + + mConstants.push_back(desc); + } +} + +void GFXGLShader::initHandles() +{ + // Mark all existing handles as invalid. + // Those that are found when parsing the descriptions will then be marked valid again. + for ( HandleMap::Iterator iter = mHandles.begin(); iter != mHandles.end(); ++iter ) + (iter->value)->setValid( false ); + + // Loop through all ConstantDescriptions, + // if they aren't in the HandleMap add them, if they are reinitialize them. + S32 assignedSamplerNum = 0; + for ( U32 i = 0; i < mConstants.size(); i++ ) + { + GFXShaderConstDesc &desc = mConstants[i]; + + // Index element 1 of the name to skip the '$' we inserted earier. + U32 loc = glGetUniformLocation(mProgram, &desc.name.c_str()[1]); + + HandleMap::Iterator handle = mHandles.find(desc.name); + S32 sampler = (desc.constType == GFXSCT_Sampler || desc.constType == GFXSCT_SamplerCube) ? + assignedSamplerNum++ : -1; + if ( handle != mHandles.end() ) + { + handle->value->reinit( desc, loc, sampler ); + } + else + { + mHandles[desc.name] = new GFXGLShaderConstHandle( this, desc, loc, sampler ); + } + } + + // Loop through handles once more to set their offset and calculate our + // constBuffer size. + + if ( mConstBuffer ) + delete[] mConstBuffer; + mConstBufferSize = 0; + + for ( HandleMap::Iterator iter = mHandles.begin(); iter != mHandles.end(); ++iter ) + { + GFXGLShaderConstHandle* handle = iter->value; + if ( handle->isValid() ) + { + handle->mOffset = mConstBufferSize; + mConstBufferSize += handle->getSize(); + } + } + + mConstBuffer = new U8[mConstBufferSize]; + dMemset(mConstBuffer, 0, mConstBufferSize); + + // Set our program so uniforms are assigned properly. + glUseProgram(mProgram); + // Iterate through uniforms to set sampler numbers. + for (HandleMap::Iterator iter = mHandles.begin(); iter != mHandles.end(); ++iter) + { + GFXGLShaderConstHandle* handle = iter->value; + if(handle->isValid() && (handle->getType() == GFXSCT_Sampler || handle->getType() == GFXSCT_SamplerCube)) + { + // Set sampler number on our program. + glUniform1i(handle->mLocation, handle->mSamplerNum); + // Set sampler in constant buffer so it does not get unset later. + dMemcpy(mConstBuffer + handle->mOffset, &handle->mLocation, handle->getSize()); + } + } + glUseProgram(0); +} + +GFXShaderConstHandle* GFXGLShader::getShaderConstHandle(const String& name) +{ + HandleMap::Iterator i = mHandles.find(name); + if(i != mHandles.end()) + return i->value; + else + { + GFXGLShaderConstHandle* handle = new GFXGLShaderConstHandle( this ); + mHandles[ name ] = handle; + + return handle; + } +} + +void GFXGLShader::setConstantsFromBuffer(GFXGLShaderConstBuffer* buffer) +{ + for(HandleMap::Iterator i = mHandles.begin(); i != mHandles.end(); i++) + { + GFXGLShaderConstHandle* handle = i->value; + AssertFatal(handle, "GFXGLShader::setConstantsFromBuffer - Null handle"); + if(!handle || !handle->isValid()) + continue; + + // Don't set if the value has not be changed. + if(dMemcmp(mConstBuffer + handle->mOffset, buffer->mBuffer + handle->mOffset, handle->getSize()) == 0) + continue; + + // Copy new value into our const buffer and set in GL. + dMemcpy(mConstBuffer + handle->mOffset, buffer->mBuffer + handle->mOffset, handle->getSize()); + switch(handle->mDesc.constType) + { + case GFXSCT_Float: + glUniform1fv(handle->mLocation, handle->mDesc.arraySize, (GLfloat*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Float2: + glUniform2fv(handle->mLocation, handle->mDesc.arraySize, (GLfloat*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Float3: + glUniform3fv(handle->mLocation, handle->mDesc.arraySize, (GLfloat*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Float4: + glUniform4fv(handle->mLocation, handle->mDesc.arraySize, (GLfloat*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Int: + case GFXSCT_Sampler: + case GFXSCT_SamplerCube: + glUniform1iv(handle->mLocation, handle->mDesc.arraySize, (GLint*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Int2: + glUniform2iv(handle->mLocation, handle->mDesc.arraySize, (GLint*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Int3: + glUniform3iv(handle->mLocation, handle->mDesc.arraySize, (GLint*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Int4: + glUniform4iv(handle->mLocation, handle->mDesc.arraySize, (GLint*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Float2x2: + glUniformMatrix2fv(handle->mLocation, handle->mDesc.arraySize, true, (GLfloat*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Float3x3: + glUniformMatrix3fv(handle->mLocation, handle->mDesc.arraySize, true, (GLfloat*)(mConstBuffer + handle->mOffset)); + break; + case GFXSCT_Float4x4: + glUniformMatrix4fv(handle->mLocation, handle->mDesc.arraySize, true, (GLfloat*)(mConstBuffer + handle->mOffset)); + break; + } + } +} + +GFXShaderConstBufferRef GFXGLShader::allocConstBuffer() +{ + GFXGLShaderConstBuffer* buffer = new GFXGLShaderConstBuffer(this, mConstBufferSize, mConstBuffer); + buffer->registerResourceWithDevice(getOwningDevice()); + mActiveBuffers.push_back( buffer ); + return buffer; +} + +void GFXGLShader::useProgram() +{ + glUseProgram(mProgram); +} + +void GFXGLShader::zombify() +{ + clearShaders(); + dMemset(mConstBuffer, 0, mConstBufferSize); +} + +char* GFXGLShader::_handleIncludes( const Torque::Path& path, FileStream *s ) +{ + // TODO: The #line pragma on GLSL takes something called a + // "source-string-number" which it then never explains. + // + // Until i resolve this mystery i disabled this. + // + //String linePragma = String::ToString( "#line 1 \r\n"); + //U32 linePragmaLen = linePragma.length(); + + U32 shaderLen = s->getStreamSize(); + char* buffer = (char*)dMalloc(shaderLen + 1); + //dStrncpy( buffer, linePragma.c_str(), linePragmaLen ); + s->read(shaderLen, buffer); + buffer[shaderLen] = 0; + + char* p = dStrstr(buffer, "#include"); + while(p) + { + char* q = p; + p += 8; + if(dIsspace(*p)) + { + U32 n = 0; + while(dIsspace(*p)) ++p; + AssertFatal(*p == '"', "Bad #include directive"); + ++p; + static char includeFile[256]; + while(*p != '"') + { + AssertFatal(*p != 0, "Bad #include directive"); + includeFile[n++] = *p++; + AssertFatal(n < sizeof(includeFile), "#include directive too long"); + } + ++p; + includeFile[n] = 0; + + // First try it as a local file. + Torque::Path includePath = Torque::Path::Join(path.getPath(), '/', includeFile); + includePath = Torque::Path::CompressPath(includePath); + + FileStream includeStream; + + if ( !includeStream.open( includePath, Torque::FS::File::Read ) ) + { + // Try again assuming the path is absolute + // and/or relative. + includePath = String( includeFile ); + includePath = Torque::Path::CompressPath(includePath); + if ( !includeStream.open( includePath, Torque::FS::File::Read ) ) + { + AssertISV(false, avar("failed to open include '%s'.", includePath.getFullPath().c_str())); + + if ( smLogErrors ) + Con::errorf( "GFXGLShader::_handleIncludes - Failed to open include '%s'.", + includePath.getFullPath().c_str() ); + + // Fail... don't return the buffer. + dFree(buffer); + return NULL; + } + } + + char* includedText = _handleIncludes(includePath, &includeStream); + + // If a sub-include fails... cleanup and return. + if ( !includedText ) + { + dFree(buffer); + return NULL; + } + + // TODO: Disabled till this is fixed correctly. + // + // Count the number of lines in the file + // before the include. + /* + U32 includeLine = 0; + { + char* nl = dStrstr( buffer, "\n" ); + while ( nl ) + { + includeLine++; + nl = dStrstr( nl, "\n" ); + if(nl) ++nl; + } + } + */ + + String manip(buffer); + manip.erase(q-buffer, p-q); + String sItx(includedText); + + // TODO: Disabled till this is fixed correctly. + // + // Add a new line pragma to restore the proper + // file and line number after the include. + //sItx += String::ToString( "\r\n#line %d \r\n", includeLine ); + + dFree(includedText); + manip.insert(q-buffer, sItx); + char* manipBuf = dStrdup(manip.c_str()); + p = manipBuf + (p - buffer); + dFree(buffer); + buffer = manipBuf; + } + p = dStrstr(p, "#include"); + } + + return buffer; +} + +bool GFXGLShader::_loadShaderFromStream( GLuint shader, + const Torque::Path &path, + FileStream *s, + const Vector ¯os ) +{ + Vector buffers; + Vector lengths; + + // The GLSL version declaration must go first! + const char *versionDecl = "#version 120\r\n\r\n"; + buffers.push_back( dStrdup( versionDecl ) ); + lengths.push_back( dStrlen( versionDecl ) ); + + // Now add all the macros. + for( U32 i = 0; i < macros.size(); i++ ) + { + String define = String::ToString( "#define %s %s\n", macros[i].name.c_str(), macros[i].value.c_str() ); + buffers.push_back( dStrdup( define.c_str() ) ); + lengths.push_back( define.length() ); + } + + // Now finally add the shader source. + U32 shaderLen = s->getStreamSize(); + char *buffer = _handleIncludes(path, s); + if ( !buffer ) + return false; + + buffers.push_back(buffer); + lengths.push_back(shaderLen); + + glShaderSource(shader, buffers.size(), (const GLchar**)const_cast(buffers.address()), NULL); + + // Cleanup the shader source buffer. + for ( U32 i=0; i < buffers.size(); i++ ) + dFree( buffers[i] ); + + glCompileShader(shader); + + return true; +} + +bool GFXGLShader::initShader( const Torque::Path &file, + bool isVertex, + const Vector ¯os ) +{ + GLuint activeShader = glCreateShader(isVertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER); + if(isVertex) + mVertexShader = activeShader; + else + mPixelShader = activeShader; + glAttachShader(mProgram, activeShader); + + + // Ok it's not in the shader gen manager, so ask Torque for it + FileStream stream; + if ( !stream.open( file, Torque::FS::File::Read ) ) + { + AssertISV(false, avar("GFXGLShader::initShader - failed to open shader '%s'.", file.getFullPath().c_str())); + + if ( smLogErrors ) + Con::errorf( "GFXGLShader::initShader - Failed to open shader file '%s'.", + file.getFullPath().c_str() ); + + return false; + } + + if ( !_loadShaderFromStream( activeShader, file, &stream, macros ) ) + return false; + + GLint compile; + glGetShaderiv(activeShader, GL_COMPILE_STATUS, &compile); + + // Dump the info log to the console + U32 logLength = 0; + glGetShaderiv(activeShader, GL_INFO_LOG_LENGTH, (GLint*)&logLength); + + GLint compileStatus = GL_TRUE; + if ( logLength ) + { + FrameAllocatorMarker fam; + char* log = (char*)fam.alloc(logLength); + glGetShaderInfoLog(activeShader, logLength, NULL, log); + + // Always print errors + glGetShaderiv( activeShader, GL_COMPILE_STATUS, &compileStatus ); + + if ( compileStatus == GL_FALSE ) + { + if ( smLogErrors ) + { + Con::errorf( "GFXGLShader::initShader - Error compiling shader!" ); + Con::errorf( "Program %s: %s", file.getFullPath().c_str(), log ); + } + } + else if ( smLogWarnings ) + Con::warnf( "Program %s: %s", file.getFullPath().c_str(), log ); + } + + return compileStatus != GL_FALSE; +} + +/// Returns our list of shader constants, the material can get this and just set the constants it knows about +const Vector& GFXGLShader::getShaderConstDesc() const +{ + return mConstants; +} + +/// Returns the alignment value for constType +U32 GFXGLShader::getAlignmentValue(const GFXShaderConstType constType) const +{ + // Alignment is the same thing as size for us. + return shaderConstTypeSize(constType); +} + +const String GFXGLShader::describeSelf() const +{ + String ret; + ret = String::ToString(" Program: %i", mProgram); + ret += String::ToString(" Vertex Path: %s", mVertexFile.getFullPath().c_str()); + ret += String::ToString(" Pixel Path: %s", mPixelFile.getFullPath().c_str()); + + return ret; +} diff --git a/gfx/gl/gfxGLShader.h b/gfx/gl/gfxGLShader.h new file mode 100644 index 0000000..e175edf --- /dev/null +++ b/gfx/gl/gfxGLShader.h @@ -0,0 +1,161 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLSHADER_H_ +#define _GFXGLSHADER_H_ + +#include "core/util/refBase.h" +#include "gfx/gfxShader.h" +#include "gfx/gl/ggl/ggl.h" +#include "core/util/tSignal.h" +#include "core/util/tDictionary.h" + +class GFXGLShaderConstHandle; +class FileStream; +class GFXGLShaderConstBuffer; + +class GFXGLShader : public GFXShader +{ + typedef Map HandleMap; +public: + GFXGLShader(); + virtual ~GFXGLShader(); + + /// @name GFXShader interface + /// @{ + virtual GFXShaderConstHandle* getShaderConstHandle(const String& name); + + /// Returns our list of shader constants, the material can get this and just set the constants it knows about + virtual const Vector& getShaderConstDesc() const; + + /// Returns the alignment value for constType + virtual U32 getAlignmentValue(const GFXShaderConstType constType) const; + + virtual GFXShaderConstBufferRef allocConstBuffer(); + + /// @} + + /// @name GFXResource interface + /// @{ + virtual void zombify(); + virtual void resurrect() { reload(); } + virtual const String describeSelf() const; + /// @} + + /// Activates this shader in the GL context. + void useProgram(); + +protected: + + friend class GFXGLShaderConstBuffer; + friend class GFXGLShaderConstHandle; + + virtual bool _init(); + + bool initShader( const Torque::Path &file, + bool isVertex, + const Vector ¯os ); + + void clearShaders(); + void initConstantDescs(); + void initHandles(); + void setConstantsFromBuffer(GFXGLShaderConstBuffer* buffer); + + static char* _handleIncludes( const Torque::Path &path, FileStream *s ); + + static bool _loadShaderFromStream( GLuint shader, + const Torque::Path& path, + FileStream* s, + const Vector& macros ); + + /// @name Internal GL handles + /// @{ + GLuint mVertexShader; + GLuint mPixelShader; + GLuint mProgram; + /// @} + + Vector mConstants; + U32 mConstBufferSize; + U8* mConstBuffer; + HandleMap mHandles; +}; + +class GFXGLShaderConstBuffer : public GFXShaderConstBuffer +{ +public: + GFXGLShaderConstBuffer(GFXGLShader* shader, U32 bufSize, U8* existingConstants); + ~GFXGLShaderConstBuffer(); + + /// Called by GFXGLDevice to activate this buffer. + void activate(); + + /// + /// @name GFXShaderConstBuffer interface + /// @{ + + /// Return the shader that created this buffer + virtual GFXShader* getShader() { return mShader; } + + /// @name Set shader constant values + /// @{ + /// Actually set shader constant values + /// @param name Name of the constant, this should be a name contained in the array returned in getShaderConstDesc, + /// if an invalid name is used, its ignored. + virtual void set(GFXShaderConstHandle* handle, const F32 fv); + virtual void set(GFXShaderConstHandle* handle, const Point2F& fv); + virtual void set(GFXShaderConstHandle* handle, const Point3F& fv); + virtual void set(GFXShaderConstHandle* handle, const Point4F& fv); + virtual void set(GFXShaderConstHandle* handle, const PlaneF& fv); + virtual void set(GFXShaderConstHandle* handle, const ColorF& fv); + virtual void set(GFXShaderConstHandle* handle, const S32 f); + virtual void set(GFXShaderConstHandle* handle, const Point2I& fv); + virtual void set(GFXShaderConstHandle* handle, const Point3I& fv); + virtual void set(GFXShaderConstHandle* handle, const Point4I& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const AlignedArray& fv); + virtual void set(GFXShaderConstHandle* handle, const MatrixF& mat, const GFXShaderConstType matType = GFXSCT_Float4x4); + virtual void set(GFXShaderConstHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType = GFXSCT_Float4x4); + /// @} + + /// @} + + /// + /// @name GFXResource interface + /// @{ + + /// The resource should put a description of itself (number of vertices, size/width of texture, etc.) in buffer + virtual const String describeSelf() const; + + /// When called the resource should destroy all device sensitive information (e.g. D3D resources in D3DPOOL_DEFAULT + virtual void zombify() {} + + /// When called the resource should restore all device sensitive information destroyed by zombify() + virtual void resurrect() {} + + /// @} + + /// Called when the shader this buffer references is reloaded. + virtual void onShaderReload( GFXShader *shader ); + +private: + friend class GFXGLShader; + U8* mBuffer; + WeakRefPtr mShader; + + template + void internalSet(GFXShaderConstHandle* handle, const ConstType& param); + + template + void internalSet(GFXShaderConstHandle* handle, const AlignedArray& fv); +}; + +#endif \ No newline at end of file diff --git a/gfx/gl/gfxGLStateBlock.cpp b/gfx/gl/gfxGLStateBlock.cpp new file mode 100644 index 0000000..f88cecd --- /dev/null +++ b/gfx/gl/gfxGLStateBlock.cpp @@ -0,0 +1,171 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gl/gfxGLStateBlock.h" +#include "gfx/gl/gfxGLDevice.h" +#include "gfx/gl/gfxGLEnumTranslate.h" +#include "gfx/gl/gfxGLUtils.h" +#include "gfx/gl/gfxGLTextureObject.h" + + +GFXGLStateBlock::GFXGLStateBlock(const GFXStateBlockDesc& desc) : + mDesc(desc), + mCachedHashValue(desc.getHashValue()) +{ +} + +GFXGLStateBlock::~GFXGLStateBlock() +{ +} + +/// Returns the hash value of the desc that created this block +U32 GFXGLStateBlock::getHashValue() const +{ + return mCachedHashValue; +} + +/// Returns a GFXStateBlockDesc that this block represents +const GFXStateBlockDesc& GFXGLStateBlock::getDesc() const +{ + return mDesc; +} + +/// Called by OpenGL device to active this state block. +/// @param oldState The current state, used to make sure we don't set redundant states on the device. Pass NULL to reset all states. +void GFXGLStateBlock::activate(const GFXGLStateBlock* oldState) +{ + // Big scary warning copied from Apple docs + // http://developer.apple.com/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_performance/chapter_13_section_2.html#//apple_ref/doc/uid/TP40001987-CH213-SW12 + // Don't set a state that's already set. Once a feature is enabled, it does not need to be enabled again. + // Calling an enable function more than once does nothing except waste time because OpenGL does not check + // the state of a feature when you call glEnable or glDisable. For instance, if you call glEnable(GL_LIGHTING) + // more than once, OpenGL does not check to see if the lighting state is already enabled. It simply updates + // the state value even if that value is identical to the current value. + +#define STATE_CHANGE(state) (!oldState || oldState->mDesc.state != mDesc.state) +#define TOGGLE_STATE(state, enum) if(mDesc.state) glEnable(enum); else glDisable(enum) +#define CHECK_TOGGLE_STATE(state, enum) if(!oldState || oldState->mDesc.state != mDesc.state) if(mDesc.state) glEnable(enum); else glDisable(enum) + + // Blending + CHECK_TOGGLE_STATE(blendEnable, GL_BLEND); + if(STATE_CHANGE(blendSrc) || STATE_CHANGE(blendDest)) + glBlendFunc(GFXGLBlend[mDesc.blendSrc], GFXGLBlend[mDesc.blendDest]); + if(STATE_CHANGE(blendOp)) + glBlendEquation(GFXGLBlendOp[mDesc.blendOp]); + + // Alpha testing + CHECK_TOGGLE_STATE(alphaTestEnable, GL_ALPHA_TEST); + if(STATE_CHANGE(alphaTestFunc) || STATE_CHANGE(alphaTestRef)) + glAlphaFunc(GFXGLCmpFunc[mDesc.alphaTestFunc], (F32) mDesc.alphaTestRef * 1.0f/255.0f); + + // Color write masks + if(STATE_CHANGE(colorWriteRed) || STATE_CHANGE(colorWriteBlue) || STATE_CHANGE(colorWriteGreen) || STATE_CHANGE(colorWriteAlpha)) + glColorMask(mDesc.colorWriteRed, mDesc.colorWriteBlue, mDesc.colorWriteGreen, mDesc.colorWriteAlpha); + + // Culling + if(STATE_CHANGE(cullMode)) + { + TOGGLE_STATE(cullMode, GL_CULL_FACE); + glCullFace(GFXGLCullMode[mDesc.cullMode]); + } + + // Depth + CHECK_TOGGLE_STATE(zEnable, GL_DEPTH_TEST); + + if(STATE_CHANGE(zFunc)) + glDepthFunc(GFXGLCmpFunc[mDesc.zFunc]); + + if(STATE_CHANGE(zBias)) + { + if (mDesc.zBias == 0) + { + glDisable(GL_POLYGON_OFFSET_FILL); + } else { + F32 bias = mDesc.zBias * 10000.0f; + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(bias, bias); + } + } + + if(STATE_CHANGE(zWriteEnable)) + glDepthMask(mDesc.zWriteEnable); + + // Stencil + CHECK_TOGGLE_STATE(stencilEnable, GL_STENCIL_TEST); + if(STATE_CHANGE(stencilFunc) || STATE_CHANGE(stencilRef) || STATE_CHANGE(stencilMask)) + glStencilFunc(GFXGLCmpFunc[mDesc.stencilFunc], mDesc.stencilRef, mDesc.stencilMask); + if(STATE_CHANGE(stencilFailOp) || STATE_CHANGE(stencilZFailOp) || STATE_CHANGE(stencilPassOp)) + glStencilOp(GFXGLStencilOp[mDesc.stencilFailOp], GFXGLStencilOp[mDesc.stencilZFailOp], GFXGLStencilOp[mDesc.stencilPassOp]); + if(STATE_CHANGE(stencilWriteMask)) + glStencilMask(mDesc.stencilWriteMask); + + // "Misc" + CHECK_TOGGLE_STATE(ffLighting, GL_LIGHTING); + + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + CHECK_TOGGLE_STATE(vertexColorEnable, GL_COLOR_MATERIAL); + + if(STATE_CHANGE(fillMode)) + glPolygonMode(GL_FRONT_AND_BACK, GFXGLFillMode[mDesc.fillMode]); + +#undef CHECK_STATE +#undef TOGGLE_STATE +#undef CHECK_TOGGLE_STATE + + // TODO: states added for detail blend + + // Non per object texture mode states + for (U32 i = 0; i < getMin(getOwningDevice()->getNumSamplers(), (U32) TEXTURE_STAGE_COUNT); i++) + { + GFXGLTextureObject* tex = static_cast(getOwningDevice()->getCurrentTexture(i)); + const GFXSamplerStateDesc &ssd = mDesc.samplers[i]; + bool updateTexParam = true; + glActiveTexture(GL_TEXTURE0 + i); + switch (ssd.textureColorOp) + { + case GFXTOPDisable : + if(!tex) + break; + glDisable(GL_TEXTURE_2D); + updateTexParam = false; + break; + case GFXTOPModulate : + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + break; + case GFXTOPAdd : + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); + break; + default : + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + break; + } + +#define SSF(state, enum, value, tex) if(!oldState || oldState->mDesc.samplers[i].state != mDesc.samplers[i].state) glTexParameteri(tex->getBinding(), enum, value) +#define SSW(state, enum, value, tex) if(!oldState || oldState->mDesc.samplers[i].state != mDesc.samplers[i].state) glTexParameteri(tex->getBinding(), enum, !tex->mIsNPoT2 ? value : GL_CLAMP_TO_EDGE) + // Per object texture mode states. + // TODO: Check dirty flag of samplers[i] and don't do this if it's dirty (it'll happen in the texture bind) + if (updateTexParam && tex) + { + SSF(minFilter, GL_TEXTURE_MIN_FILTER, minificationFilter(ssd.minFilter, ssd.mipFilter, tex->mMipLevels), tex); + SSF(mipFilter, GL_TEXTURE_MIN_FILTER, minificationFilter(ssd.minFilter, ssd.mipFilter, tex->mMipLevels), tex); + SSF(magFilter, GL_TEXTURE_MAG_FILTER, GFXGLTextureFilter[ssd.magFilter], tex); + SSW(addressModeU, GL_TEXTURE_WRAP_S, GFXGLTextureAddress[ssd.addressModeU], tex); + SSW(addressModeV, GL_TEXTURE_WRAP_T, GFXGLTextureAddress[ssd.addressModeV], tex); + + if( ( !oldState || oldState->mDesc.samplers[i].maxAnisotropy != ssd.maxAnisotropy ) && + static_cast< GFXGLDevice* >( GFX )->supportsAnisotropic() ) + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, ssd.maxAnisotropy); + + if( ( !oldState || oldState->mDesc.samplers[i].mipLODBias != ssd.mipLODBias ) && + static_cast< GFXGLDevice* >( GFX )->supportsMipLodBias() ) + glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, ssd.mipLODBias); + } + } +#undef SSF +#undef SSW +} diff --git a/gfx/gl/gfxGLStateBlock.h b/gfx/gl/gfxGLStateBlock.h new file mode 100644 index 0000000..84cf154 --- /dev/null +++ b/gfx/gl/gfxGLStateBlock.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLSTATEBLOCK_H_ +#define _GFXGLSTATEBLOCK_H_ + +#include "gfx/gfxStateBlock.h" + +class GFXGLStateBlock : public GFXStateBlock +{ +public: + // + // GFXGLStateBlock interface + // + GFXGLStateBlock(const GFXStateBlockDesc& desc); + virtual ~GFXGLStateBlock(); + + /// Called by OpenGL device to active this state block. + /// @param oldState The current state, used to make sure we don't set redundant states on the device. Pass NULL to reset all states. + void activate(const GFXGLStateBlock* oldState); + + + // + // GFXStateBlock interface + // + + /// Returns the hash value of the desc that created this block + virtual U32 getHashValue() const; + + /// Returns a GFXStateBlockDesc that this block represents + virtual const GFXStateBlockDesc& getDesc() const; + + // + // GFXResource + // + virtual void zombify() { } + /// When called the resource should restore all device sensitive information destroyed by zombify() + virtual void resurrect() { } +private: + GFXStateBlockDesc mDesc; + U32 mCachedHashValue; +}; + +typedef StrongRefPtr GFXGLStateBlockRef; + +#endif \ No newline at end of file diff --git a/gfx/gl/gfxGLTextureManager.cpp b/gfx/gl/gfxGLTextureManager.cpp new file mode 100644 index 0000000..07d07a6 --- /dev/null +++ b/gfx/gl/gfxGLTextureManager.cpp @@ -0,0 +1,316 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gl/gfxGLTextureManager.h" +#include "gfx/gl/gfxGLEnumTranslate.h" +#include "gfx/gfxCardProfile.h" +#include "core/util/safeDelete.h" +#include "gfx/gl/gfxGLUtils.h" + +#include + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +GFXGLTextureManager::GFXGLTextureManager() +{ +} + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +GFXGLTextureManager::~GFXGLTextureManager() +{ + SAFE_DELETE_ARRAY( mHashTable ); +} + +//----------------------------------------------------------------------------- +// createTexture +//----------------------------------------------------------------------------- +GFXTextureObject *GFXGLTextureManager::_createTextureObject( U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips, + S32 antialiasLevel, + GFXTextureObject *inTex ) +{ + AssertFatal(format >= 0 && format < GFXFormat_COUNT, "GFXGLTextureManager::_createTexture - invalid format!"); + + GFXGLTextureObject *retTex; + if ( inTex ) + { + AssertFatal( dynamic_cast( inTex ), "GFXGLTextureManager::_createTexture() - Bad inTex type!" ); + retTex = static_cast( inTex ); + retTex->release(); + } + else + { + retTex = new GFXGLTextureObject( GFX, profile ); + retTex->registerResourceWithDevice( GFX ); + } + + innerCreateTexture(retTex, height, width, depth, format, profile, numMipLevels, forceMips); + + return retTex; +} + +//----------------------------------------------------------------------------- +// innerCreateTexture +//----------------------------------------------------------------------------- +// This just creates the texture, no info is actually loaded to it. We do that later. +void GFXGLTextureManager::innerCreateTexture( GFXGLTextureObject *retTex, + U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips) +{ + // No 24 bit formats. They trigger various oddities because hardware (and Apple's drivers apparently...) don't natively support them. + if(format == GFXFormatR8G8B8) + format = GFXFormatR8G8B8A8; + + retTex->mFormat = format; + retTex->mIsZombie = false; + retTex->mIsNPoT2 = false; + + GLenum binding = (depth == 0) ? GL_TEXTURE_2D : GL_TEXTURE_3D; + if((profile->testFlag(GFXTextureProfile::RenderTarget) || profile->testFlag(GFXTextureProfile::ZTarget)) && (!isPow2(width) || !isPow2(height)) && !depth) + retTex->mIsNPoT2 = true; + retTex->mBinding = binding; + + // Bind it + glActiveTexture(GL_TEXTURE0); + PRESERVE_2D_TEXTURE(); + PRESERVE_3D_TEXTURE(); + glBindTexture(binding, retTex->getHandle()); + + // Create it + // TODO: Reenable mipmaps on render targets when Apple fixes their drivers + if(forceMips && !retTex->mIsNPoT2) + { + glTexParameteri(binding, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); + retTex->mMipLevels = 0; + } + else if(profile->testFlag(GFXTextureProfile::NoMipmap) || profile->testFlag(GFXTextureProfile::RenderTarget) || numMipLevels == 1 || retTex->mIsNPoT2) + { + retTex->mMipLevels = 1; + } + else + { + glTexParameteri(binding, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); + retTex->mMipLevels = 0; + } + + if(!retTex->mIsNPoT2) + { + if(!isPow2(width)) + width = getNextPow2(width); + if(!isPow2(height)) + height = getNextPow2(height); + if(depth && !isPow2(depth)) + depth = getNextPow2(depth); + } + + AssertFatal(GFXGLTextureInternalFormat[format] != GL_ZERO, "GFXGLTextureManager::innerCreateTexture - invalid internal format"); + AssertFatal(GFXGLTextureFormat[format] != GL_ZERO, "GFXGLTextureManager::innerCreateTexture - invalid format"); + AssertFatal(GFXGLTextureType[format] != GL_ZERO, "GFXGLTextureManager::innerCreateTexture - invalid type"); + + if(binding != GL_TEXTURE_3D) + glTexImage2D(binding, 0, GFXGLTextureInternalFormat[format], width, height, 0, GFXGLTextureFormat[format], GFXGLTextureType[format], NULL); + else + glTexImage3D(GL_TEXTURE_3D, 0, GFXGLTextureInternalFormat[format], width, height, depth, 0, GFXGLTextureFormat[format], GFXGLTextureType[format], NULL); + + // Complete the texture + glTexParameteri(binding, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(binding, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(binding, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(binding, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if(binding == GL_TEXTURE_3D) + glTexParameteri(binding, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // Get the size from GL (you never know...) + GLint texHeight, texWidth, texDepth = 0; + + glGetTexLevelParameteriv(binding, 0, GL_TEXTURE_WIDTH, &texWidth); + glGetTexLevelParameteriv(binding, 0, GL_TEXTURE_HEIGHT, &texHeight); + if(binding == GL_TEXTURE_3D) + glGetTexLevelParameteriv(binding, 0, GL_TEXTURE_DEPTH, &texDepth); + + retTex->mTextureSize.set(texWidth, texHeight, texDepth); +} + +//----------------------------------------------------------------------------- +// loadTexture - GBitmap +//----------------------------------------------------------------------------- + +static void _fastTextureLoad(GFXGLTextureObject* texture, GBitmap* pDL) +{ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, texture->getBuffer()); + U32 bufSize = pDL->getWidth(0) * pDL->getHeight(0) * pDL->getBytesPerPixel(); + glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, bufSize, NULL, GL_STREAM_DRAW); + U8* pboMemory = (U8*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY); + + if(pDL->getFormat() == GFXFormatR8G8B8A8 || pDL->getFormat() == GFXFormatR8G8B8X8) + GFX->getDeviceSwizzle32()->ToBuffer(pboMemory, pDL->getBits(0), bufSize); + else + dMemcpy(pboMemory, pDL->getBits(0), bufSize); + + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); + + glTexSubImage2D(texture->getBinding(), 0, 0, 0, pDL->getWidth(0), pDL->getHeight(0), GFXGLTextureFormat[pDL->getFormat()], GFXGLTextureType[pDL->getFormat()], NULL); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); +} + +static void _slowTextureLoad(GFXGLTextureObject* texture, GBitmap* pDL) +{ + glTexSubImage2D(texture->getBinding(), 0, 0, 0, pDL->getWidth(0), pDL->getHeight(0), GFXGLTextureFormat[pDL->getFormat()], GFXGLTextureType[pDL->getFormat()], pDL->getBits(0)); +} + +bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, GBitmap *pDL) +{ + GFXGLTextureObject *texture = static_cast(aTexture); + + AssertFatal(texture->getBinding() == GL_TEXTURE_2D, + "GFXGLTextureManager::_loadTexture(GBitmap) - This method can only be used with 2D textures"); + + if(texture->getBinding() != GL_TEXTURE_2D) + return false; + + // No 24bit formats. + if(pDL->getFormat() == GFXFormatR8G8B8) + pDL->setFormat(GFXFormatR8G8B8A8); + // Bind to edit + glActiveTexture(GL_TEXTURE0); + PRESERVE_2D_TEXTURE(); + glBindTexture(texture->getBinding(), texture->getHandle()); + + if(pDL->getFormat() == GFXFormatR8G8B8A8 || pDL->getFormat() == GFXFormatR8G8B8X8) + _fastTextureLoad(texture, pDL); + else + _slowTextureLoad(texture, pDL); + + glBindTexture(texture->getBinding(), 0); + + return true; +} + +bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, DDSFile *dds) +{ + AssertFatal(!(dds->mFormat == GFXFormatDXT2 || dds->mFormat == GFXFormatDXT4), "GFXGLTextureManager::_loadTexture - OpenGL does not support DXT2 or DXT4 compressed textures"); + GFXGLTextureObject* texture = static_cast(aTexture); + + AssertFatal(texture->getBinding() == GL_TEXTURE_2D, + "GFXGLTextureManager::_loadTexture(DDSFile) - This method can only be used with 2D textures"); + + if(texture->getBinding() != GL_TEXTURE_2D) + return false; + + glActiveTexture(GL_TEXTURE0); + PRESERVE_2D_TEXTURE(); + glBindTexture(texture->getBinding(), texture->getHandle()); + U32 numMips = dds->mSurfaces[0]->mMips.size(); + if(GFX->getCardProfiler()->queryProfile("GL::Workaround::noManualMips")) + numMips = 1; + for(U32 i = 0; i < numMips; i++) + { + if(dds->mFormat == GFXFormatDXT1 || dds->mFormat == GFXFormatDXT3 || dds->mFormat == GFXFormatDXT5) + { + if((!isPow2(dds->getWidth()) || !isPow2(dds->getHeight())) && GFX->getCardProfiler()->queryProfile("GL::Workaround::noCompressedNPoTTextures")) + { + U32 squishFlag = squish::kDxt1; + switch (dds->mFormat) + { + case GFXFormatDXT3: + squishFlag = squish::kDxt3; + break; + case GFXFormatDXT5: + squishFlag = squish::kDxt5; + break; + default: + break; + } + U8* uncompressedTex = new U8[dds->getWidth(i) * dds->getHeight(i) * 4]; + squish::DecompressImage(uncompressedTex, dds->getWidth(i), dds->getHeight(i), dds->mSurfaces[0]->mMips[i], squishFlag); + glTexSubImage2D(texture->getBinding(), i, 0, 0, dds->getWidth(i), dds->getHeight(i), GL_RGBA, GL_UNSIGNED_BYTE, uncompressedTex); + delete[] uncompressedTex; + } + else + glCompressedTexSubImage2D(texture->getBinding(), i, 0, 0, dds->getWidth(i), dds->getHeight(i), GFXGLTextureInternalFormat[dds->mFormat], dds->getSurfaceSize(dds->getHeight(), dds->getWidth(), i), dds->mSurfaces[0]->mMips[i]); + } + else + glTexSubImage2D(texture->getBinding(), i, 0, 0, dds->getWidth(i), dds->getHeight(i), GFXGLTextureFormat[dds->mFormat], GFXGLTextureType[dds->mFormat], dds->mSurfaces[0]->mMips[i]); + } + glBindTexture(texture->getBinding(), 0); + + return true; +} + +bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, void *raw) +{ + if(aTexture->getDepth() < 1) + return false; + + GFXGLTextureObject* texture = static_cast(aTexture); + + glActiveTexture(GL_TEXTURE0); + PRESERVE_3D_TEXTURE(); + glBindTexture(GL_TEXTURE_3D, texture->getHandle()); + glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, texture->getWidth(), texture->getHeight(), texture->getDepth(), GFXGLTextureFormat[texture->mFormat], GFXGLTextureType[texture->mFormat], raw); + glBindTexture(GL_TEXTURE_3D, 0); + + return true; +} + +bool GFXGLTextureManager::_freeTexture(GFXTextureObject *texture, bool zombify /*= false*/) +{ + if(zombify) + static_cast(texture)->zombify(); + else + static_cast(texture)->release(); + + return true; +} + +bool GFXGLTextureManager::_refreshTexture(GFXTextureObject *texture) +{ + U32 usedStrategies = 0; + GFXGLTextureObject* realTex = static_cast(texture); + + if(texture->mProfile->doStoreBitmap()) + { + if(realTex->isZombie()) + { + realTex->resurrect(); + innerCreateTexture(realTex, texture->getHeight(), texture->getWidth(), texture->getDepth(), texture->mFormat, texture->mProfile, texture->mMipLevels); + } + if(texture->mBitmap) + _loadTexture(texture, texture->mBitmap); + + if(texture->mDDS) + return false; + + usedStrategies++; + } + + if(texture->mProfile->isRenderTarget() || texture->mProfile->isDynamic() || texture->mProfile->isZTarget() || !usedStrategies) + { + realTex->release(); + realTex->resurrect(); + innerCreateTexture(realTex, texture->getHeight(), texture->getWidth(), texture->getDepth(), texture->mFormat, texture->mProfile, texture->mMipLevels); + realTex->reloadFromCache(); + usedStrategies++; + } + + AssertFatal(usedStrategies < 2, "GFXGLTextureManager::_refreshTexture - Inconsistent profile flags (store bitmap and dynamic/target"); + + return true; +} diff --git a/gfx/gl/gfxGLTextureManager.h b/gfx/gl/gfxGLTextureManager.h new file mode 100644 index 0000000..c1066cb --- /dev/null +++ b/gfx/gl/gfxGLTextureManager.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLTEXTUREMANAGER_H +#define _GFXGLTEXTUREMANAGER_H + +#include "gfx/gfxDevice.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gl/gfxGLTextureObject.h" +#include "gfx/gl/ggl/ggl.h" + +class GFXGLTextureManager : public GFXTextureManager +{ +public: + GFXGLTextureManager(); + ~GFXGLTextureManager(); + +protected: + + // GFXTextureManager + GFXTextureObject *_createTextureObject( U32 height, + U32 width, + U32 depth, + GFXFormat format, + GFXTextureProfile *profile, + U32 numMipLevels, + bool forceMips = false, + S32 antialiasLevel = 0, + GFXTextureObject *inTex = NULL ); + bool _loadTexture(GFXTextureObject *texture, DDSFile *dds); + bool _loadTexture(GFXTextureObject *texture, GBitmap *bmp); + bool _loadTexture(GFXTextureObject *texture, void *raw); + bool _refreshTexture(GFXTextureObject *texture); + bool _freeTexture(GFXTextureObject *texture, bool zombify = false); + +private: + friend class GFXGLTextureObject; + + /// Creates internal GL texture + void innerCreateTexture(GFXGLTextureObject *obj, U32 height, U32 width, U32 depth, GFXFormat format, GFXTextureProfile *profile, U32 numMipLevels, bool forceMips = false); +}; + +#endif \ No newline at end of file diff --git a/gfx/gl/gfxGLTextureObject.cpp b/gfx/gl/gfxGLTextureObject.cpp new file mode 100644 index 0000000..854e1bc --- /dev/null +++ b/gfx/gl/gfxGLTextureObject.cpp @@ -0,0 +1,222 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "gfx/gl/ggl/ggl.h" +#include "math/mRect.h" +#include "gfx/gl/gfxGLTextureObject.h" +#include "gfx/gfxDevice.h" +#include "gfx/gl/gfxGLEnumTranslate.h" +#include "gfx/gl/gfxGLTextureManager.h" +#include "gfx/gl/gfxGLUtils.h" +#include "gfx/gfxCardProfile.h" + + +GFXGLTextureObject::GFXGLTextureObject(GFXDevice * aDevice, GFXTextureProfile *profile) : + GFXTextureObject(aDevice, profile), + mBinding(GL_TEXTURE_2D), + mBytesPerTexel(4), + mLockedRectRect(0, 0, 0, 0), + mGLDevice(static_cast(mDevice)), + mZombieCache(NULL) +{ + AssertFatal(dynamic_cast(mDevice), "GFXGLTextureObject::GFXGLTextureObject - Invalid device type, expected GFXGLDevice!"); + glGenTextures(1, &mHandle); + glGenBuffers(1, &mBuffer); +} + +GFXGLTextureObject::~GFXGLTextureObject() +{ + glDeleteBuffers(1, &mBuffer); + delete[] mZombieCache; + kill(); +} + +GFXLockedRect* GFXGLTextureObject::lock(U32 mipLevel, RectI *inRect) +{ + AssertFatal(mBinding != GL_TEXTURE_3D, "GFXGLTextureObject::lock - We don't support locking 3D textures yet"); + U32 width = mTextureSize.x >> mipLevel; + U32 height = mTextureSize.y >> mipLevel; + + if(inRect) + { + if((inRect->point.x + inRect->extent.x > width) || (inRect->point.y + inRect->extent.y > height)) + AssertFatal(false, "GFXGLTextureObject::lock - Rectangle too big!"); + + mLockedRectRect = *inRect; + } + else + { + mLockedRectRect = RectI(0, 0, width, height); + } + + mLockedRect.pitch = mLockedRectRect.extent.x * mBytesPerTexel; + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, mBuffer); + // CodeReview [ags 12/19/07] This one texel boundary is necessary to keep the clipmap code from crashing. Figure out why. + glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, (mLockedRectRect.extent.x + 1) * (mLockedRectRect.extent.y + 1) * mBytesPerTexel, NULL, GL_STREAM_DRAW); + mLockedRect.bits = (U8*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + + if( !mLockedRect.bits ) + return NULL; + + return &mLockedRect; +} + +void GFXGLTextureObject::unlock(U32 mipLevel) +{ + if(!mLockedRect.bits) + return; + + glActiveTexture(GL_TEXTURE0); + U32 boundTexture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*)&boundTexture); + + glBindTexture(GL_TEXTURE_2D, mHandle); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, mBuffer); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); + glTexSubImage2D(GL_TEXTURE_2D, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, + mLockedRectRect.extent.x, mLockedRectRect.extent.y, GL_BGRA, GL_UNSIGNED_BYTE, NULL); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + + mLockedRect.bits = NULL; + + glBindTexture(GL_TEXTURE_2D, boundTexture); +} + +void GFXGLTextureObject::release() +{ + glDeleteTextures(1, &mHandle); + glDeleteBuffers(1, &mBuffer); + + mHandle = 0; + mBuffer = 0; +} + +bool GFXGLTextureObject::copyToBmp(GBitmap * bmp) +{ + GLint oldTex; + glGetIntegerv(0x8069, &oldTex); + glBindTexture(GL_TEXTURE_2D, mHandle); + + GLint textureFormat = GFXGLTextureFormat[bmp->getFormat()]; + // Don't swizzle outgoing textures. + if(textureFormat == GL_BGRA) + textureFormat = GL_RGBA; + + glGetTexImage(GL_TEXTURE_2D, 0, textureFormat, GL_UNSIGNED_BYTE, bmp->getWritableBits()); + + glBindTexture(GL_TEXTURE_2D, oldTex); + return true; +} + +void GFXGLTextureObject::bind(U32 textureUnit) const +{ + glActiveTexture(GL_TEXTURE0 + textureUnit); + glBindTexture(mBinding, mHandle); + glEnable(mBinding); + + GFXGLStateBlockRef sb = mGLDevice->getCurrentStateBlock(); + AssertFatal(sb, "GFXGLTextureObject::bind - No active stateblock!"); + if (!sb) + return; + + const GFXSamplerStateDesc ssd = sb->getDesc().samplers[textureUnit]; + glTexParameteri(mBinding, GL_TEXTURE_MIN_FILTER, minificationFilter(ssd.minFilter, ssd.mipFilter, mMipLevels)); + glTexParameteri(mBinding, GL_TEXTURE_MAG_FILTER, GFXGLTextureFilter[ssd.magFilter]); + glTexParameteri(mBinding, GL_TEXTURE_WRAP_S, !mIsNPoT2 ? GFXGLTextureAddress[ssd.addressModeU] : GL_CLAMP_TO_EDGE); + glTexParameteri(mBinding, GL_TEXTURE_WRAP_T, !mIsNPoT2 ? GFXGLTextureAddress[ssd.addressModeV] : GL_CLAMP_TO_EDGE); + if(mBinding == GL_TEXTURE_3D) + glTexParameteri(mBinding, GL_TEXTURE_WRAP_R, GFXGLTextureAddress[ssd.addressModeW]); + + if (GFX->getCardProfiler()->queryProfile("GL::suppMipLodBias")) + glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, ssd.mipLODBias); +} + +U8* GFXGLTextureObject::getTextureData() +{ + U8* data = new U8[mTextureSize.x * mTextureSize.y * mBytesPerTexel]; + glBindTexture(GL_TEXTURE_2D, mHandle); + glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data); + return data; +} + +void GFXGLTextureObject::copyIntoCache() +{ + glBindTexture(mBinding, mHandle); + U32 cacheSize = mTextureSize.x * mTextureSize.y; + if(mBinding == GL_TEXTURE_3D) + cacheSize *= mTextureSize.z; + + cacheSize *= mBytesPerTexel; + mZombieCache = new U8[cacheSize]; + + glGetTexImage(mBinding, 0, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], mZombieCache); + glBindTexture(mBinding, 0); +} + +void GFXGLTextureObject::reloadFromCache() +{ + if(!mZombieCache) + return; + + if(mBinding == GL_TEXTURE_3D) + { + static_cast(TEXMGR)->_loadTexture(this, mZombieCache); + delete[] mZombieCache; + mZombieCache = NULL; + return; + } + + glBindTexture(mBinding, mHandle); + glTexSubImage2D(mBinding, 0, 0, 0, mTextureSize.x, mTextureSize.y, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], mZombieCache); + + if(GFX->getCardProfiler()->queryProfile("GL::Workaround::needsExplicitGenerateMipmap") && mMipLevels != 1) + glGenerateMipmapEXT(mBinding); + + delete[] mZombieCache; + mZombieCache = NULL; + mIsZombie = false; +} + +void GFXGLTextureObject::zombify() +{ + if(mIsZombie) + return; + + mIsZombie = true; + if(!mProfile->doStoreBitmap() && !mProfile->isRenderTarget() && !mProfile->isDynamic() && !mProfile->isZTarget()) + copyIntoCache(); + + release(); +} + +void GFXGLTextureObject::resurrect() +{ + if(!mIsZombie) + return; + + glGenTextures(1, &mHandle); + glGenBuffers(1, &mBuffer); +} + +F32 GFXGLTextureObject::getMaxUCoord() const +{ + return mBinding == GL_TEXTURE_2D ? 1.0f : (F32)getWidth(); +} + +F32 GFXGLTextureObject::getMaxVCoord() const +{ + return mBinding == GL_TEXTURE_2D ? 1.0f : (F32)getHeight(); +} + +const String GFXGLTextureObject::describeSelf() const +{ + String ret = Parent::describeSelf(); + ret += String::ToString(" GL Handle: %i", mHandle); + + return ret; +} diff --git a/gfx/gl/gfxGLTextureObject.h b/gfx/gl/gfxGLTextureObject.h new file mode 100644 index 0000000..674075d --- /dev/null +++ b/gfx/gl/gfxGLTextureObject.h @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLTEXTUREOBJECT_H +#define _GFXGLTEXTUREOBJECT_H + +#include "gfx/gfxTextureObject.h" +#include "gfx/gl/ggl/ggl.h" + +class GFXGLDevice; + +class GFXGLTextureObject : public GFXTextureObject +{ +public: + GFXGLTextureObject(GFXDevice * aDevice, GFXTextureProfile *profile); + virtual ~GFXGLTextureObject(); + + void release(); + + inline GLuint getHandle() const { return mHandle; } + inline GLenum getBinding() const { return mBinding; } + inline GLuint getBuffer() const { return mBuffer; } + + inline bool isZombie() const { return mIsZombie; } + + /// Binds the texture to the given texture unit + /// and applies the current sampler state because GL tracks + /// filtering and wrapper per object, while GFX tracks per sampler. + void bind(U32 textureUnit) const; + + /// @return An array containing the texture data + /// @note You are responsible for deleting the returned data! (Use delete[]) + U8* getTextureData(); + + virtual F32 getMaxUCoord() const; + virtual F32 getMaxVCoord() const; + + void reloadFromCache(); ///< Reloads texture from zombie cache, used by GFXGLTextureManager to resurrect the texture. + +#ifdef TORQUE_DEBUG + virtual void pureVirtualCrash() {} +#endif + + /// Get/set data from texture (for dynamic textures and render targets) + /// @attention DO NOT READ FROM THE RETURNED RECT! It is not guaranteed to work and may incur significant performance penalties. + virtual GFXLockedRect* lock(U32 mipLevel = 0, RectI *inRect = NULL); + virtual void unlock(U32 mipLevel = 0 ); + + virtual bool copyToBmp(GBitmap *); ///< Not implemented + + bool mIsNPoT2; + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + virtual const String describeSelf() const; + +private: + friend class GFXGLTextureManager; + typedef GFXTextureObject Parent; + /// Internal GL object + GLuint mHandle; + GLuint mBuffer; + + GLenum mBinding; + + U32 mBytesPerTexel; + GFXLockedRect mLockedRect; + RectI mLockedRectRect; + + /// Pointer to owner device + GFXGLDevice* mGLDevice; + + bool mIsZombie; + U8* mZombieCache; + + void copyIntoCache(); +}; + +#endif \ No newline at end of file diff --git a/gfx/gl/gfxGLTextureTarget.cpp b/gfx/gl/gfxGLTextureTarget.cpp new file mode 100644 index 0000000..026d226 --- /dev/null +++ b/gfx/gl/gfxGLTextureTarget.cpp @@ -0,0 +1,421 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "gfx/gl/gfxGLDevice.h" +#include "gfx/gl/gfxGLTextureTarget.h" +#include "gfx/gl/gfxGLTextureObject.h" +#include "gfx/gl/gfxGLCubemap.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gl/gfxGLUtils.h" + +/// Internal struct used to track texture information for FBO attachments +/// This serves as an abstract base so we can deal with cubemaps and standard +/// 2D/Rect textures through the same interface +class _GFXGLTargetDesc +{ +public: + _GFXGLTargetDesc(U32 _mipLevel, U32 _zOffset) : + mipLevel(_mipLevel), zOffset(_zOffset) + { + } + + virtual ~_GFXGLTargetDesc() {} + + virtual U32 getHandle() = 0; + virtual U32 getWidth() = 0; + virtual U32 getHeight() = 0; + virtual U32 getDepth() = 0; + virtual bool hasMips() = 0; + virtual GLenum getBinding() = 0; + + U32 getMipLevel() { return mipLevel; } + U32 getZOffset() { return zOffset; } + +private: + U32 mipLevel; + U32 zOffset; +}; + +/// Internal struct used to track 2D/Rect texture information for FBO attachment +class _GFXGLTextureTargetDesc : public _GFXGLTargetDesc +{ +public: + _GFXGLTextureTargetDesc(GFXGLTextureObject* tex, U32 _mipLevel, U32 _zOffset) + : _GFXGLTargetDesc(_mipLevel, _zOffset), mTex(tex) + { + } + + virtual ~_GFXGLTextureTargetDesc() {} + + virtual U32 getHandle() { return mTex->getHandle(); } + virtual U32 getWidth() { return mTex->getWidth(); } + virtual U32 getHeight() { return mTex->getHeight(); } + virtual U32 getDepth() { return mTex->getDepth(); } + virtual bool hasMips() { return mTex->mMipLevels != 1; } + virtual GLenum getBinding() { return mTex->getBinding(); } + +private: + StrongRefPtr mTex; +}; + +/// Internal struct used to track Cubemap texture information for FBO attachment +class _GFXGLCubemapTargetDesc : public _GFXGLTargetDesc +{ +public: + _GFXGLCubemapTargetDesc(GFXGLCubemap* tex, U32 _face, U32 _mipLevel, U32 _zOffset) + : _GFXGLTargetDesc(_mipLevel, _zOffset), mTex(tex), mFace(_face) + { + } + + virtual ~_GFXGLCubemapTargetDesc() {} + + virtual U32 getHandle() { return mTex->getHandle(); } + virtual U32 getWidth() { return mTex->getWidth(); } + virtual U32 getHeight() { return mTex->getHeight(); } + virtual U32 getDepth() { return 0; } + virtual bool hasMips() { return mTex->getNumMipLevels() != 1; } + virtual GLenum getBinding() { return GFXGLCubemap::getEnumForFaceNumber(mFace); } + +private: + StrongRefPtr mTex; + U32 mFace; +}; + +// Internal implementations +class _GFXGLTextureTargetImpl +{ +public: + GFXGLTextureTarget* mTarget; + + virtual ~_GFXGLTextureTargetImpl() {} + + virtual void applyState() = 0; + virtual void makeActive() = 0; + virtual void finish() = 0; +}; + +// Use FBOs to render to texture. This is the preferred implementation and is almost always used. +class _GFXGLTextureTargetFBOImpl : public _GFXGLTextureTargetImpl +{ +public: + GLuint mFramebuffer; + + _GFXGLTextureTargetFBOImpl(GFXGLTextureTarget* target); + virtual ~_GFXGLTextureTargetFBOImpl(); + + virtual void applyState(); + virtual void makeActive(); + virtual void finish(); +}; + +// Handy macro for checking the status of a framebuffer. Framebuffers can fail in +// all sorts of interesting ways, these are just the most common. Further, no existing GL profiling +// tool catches framebuffer errors when the framebuffer is created, so we actually need this. +#define CHECK_FRAMEBUFFER_STATUS()\ +{\ +GLenum status;\ +status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);\ +switch(status) {\ +case GL_FRAMEBUFFER_COMPLETE_EXT:\ +break;\ +case GL_FRAMEBUFFER_UNSUPPORTED_EXT:\ +AssertFatal(false, "Unsupported FBO");\ +break;\ +case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:\ +AssertFatal(false, "Incomplete FBO Attachment");\ +break;\ +case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:\ +AssertFatal(false, "Incomplete FBO dimensions");\ +break;\ +case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:\ +AssertFatal(false, "Incomplete FBO formats");\ +default:\ +/* programming error; will fail on all hardware */\ +AssertFatal(false, "Something really bad happened with an FBO");\ +}\ +} + +_GFXGLTextureTargetFBOImpl::_GFXGLTextureTargetFBOImpl(GFXGLTextureTarget* target) +{ + mTarget = target; + glGenFramebuffersEXT(1, &mFramebuffer); +} + +_GFXGLTextureTargetFBOImpl::~_GFXGLTextureTargetFBOImpl() +{ + glDeleteFramebuffersEXT(1, &mFramebuffer); +} + +void _GFXGLTextureTargetFBOImpl::applyState() +{ + // REMINDER: When we implement MRT support, check against GFXGLDevice::getNumRenderTargets() + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFramebuffer); + + _GFXGLTargetDesc* color0 = mTarget->getTargetDesc(GFXTextureTarget::Color0); + if(color0) + { + if(color0->getDepth() == 0) + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, color0->getBinding(), color0->getHandle(), color0->getMipLevel()); + else + glFramebufferTexture3DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, color0->getBinding(), color0->getHandle(), color0->getMipLevel(), color0->getZOffset()); + } + else + { + // Clears the texture (note that the binding is irrelevent) + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0); + } + + _GFXGLTargetDesc* depthStecil = mTarget->getTargetDesc(GFXTextureTarget::DepthStencil); + if(depthStecil) + { + // Certain drivers have issues with depth only FBOs. That and the next two asserts assume we have a color target. + AssertFatal(color0, "GFXGLTextureTarget::applyState() - Cannot set DepthStencil target without Color0 target!"); + AssertFatal(depthStecil->getWidth() == color0->getWidth(), "GFXGLTextureTarget::applyState() - DepthStencil and Color0 targets MUST have the same width!"); + AssertFatal(depthStecil->getHeight() == color0->getHeight(), "GFXGLTextureTarget::applyState() - DepthStencil and Color0 targets MUST have the same height!"); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, depthStecil->getBinding(), depthStecil->getHandle(), depthStecil->getMipLevel()); + } + else + { + // Clears the texture (note that the binding is irrelevent) + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, 0, 0); + } + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); +} + +void _GFXGLTextureTargetFBOImpl::makeActive() +{ + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, mFramebuffer); + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, mFramebuffer); +} + +void _GFXGLTextureTargetFBOImpl::finish() +{ + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); + + _GFXGLTargetDesc* color0 = mTarget->getTargetDesc(GFXTextureTarget::Color0); + if(!color0 || !(color0->hasMips())) + return; + + // Generate mips if necessary + // Assumes a 2D texture. + glActiveTexture(GL_TEXTURE0); + PRESERVE_2D_TEXTURE(); + glBindTexture(GL_TEXTURE_2D, color0->getHandle()); + glGenerateMipmapEXT(GL_TEXTURE_2D); +} + +// This implementations uses AUX buffers (we should always have at least one) to do render to texture. It is currently only used when we need access to the windows depth buffer. +class _GFXGLTextureTargetAUXBufferImpl : public _GFXGLTextureTargetImpl +{ +public: + _GFXGLTextureTargetAUXBufferImpl(GFXGLTextureTarget* target); + + virtual void applyState(); + virtual void makeActive(); + virtual void finish(); +}; + +_GFXGLTextureTargetAUXBufferImpl::_GFXGLTextureTargetAUXBufferImpl(GFXGLTextureTarget* target) +{ + mTarget = target; +} + +void _GFXGLTextureTargetAUXBufferImpl::applyState() +{ + +} + +void _GFXGLTextureTargetAUXBufferImpl::makeActive() +{ + glDrawBuffer(GL_AUX0); + glReadBuffer(GL_AUX0); +} + +void _GFXGLTextureTargetAUXBufferImpl::finish() +{ + // Bind the Color0 texture + _GFXGLTargetDesc* color0 = mTarget->getTargetDesc(GFXTextureTarget::Color0); + + glActiveTexture(GL_TEXTURE0); + // Assume we're a 2D texture for now. + PRESERVE_2D_TEXTURE(); + glBindTexture(color0->getBinding(), color0->getHandle()); + glCopyTexSubImage2D(color0->getBinding(), 0, 0, 0, 0, 0, color0->getWidth(), color0->getHeight()); + + glDrawBuffer(GL_BACK); + glReadBuffer(GL_BACK); +} + +// Actual GFXGLTextureTarget interface +GFXGLTextureTarget::GFXGLTextureTarget() +{ + for(U32 i=0; igetWidth(), mTargets[Color0]->getHeight()); + + return Point2I(0, 0); +} + +GFXFormat GFXGLTextureTarget::getFormat() +{ + // TODO: Fix me! + return GFXFormatR8G8B8A8; +} + +void GFXGLTextureTarget::attachTexture( RenderSlot slot, GFXTextureObject *tex, U32 mipLevel/*=0*/, U32 zOffset /*= 0*/ ) +{ + // GFXTextureTarget::sDefaultDepthStencil is a hint that we want the window's depth buffer. + if(tex == GFXTextureTarget::sDefaultDepthStencil) + _needsAux = true; + + if(slot == DepthStencil && tex != GFXTextureTarget::sDefaultDepthStencil) + _needsAux = false; + + // Triggers an update when we next render + invalidateState(); + + // We stash the texture and info into an internal struct. + GFXGLTextureObject* glTexture = static_cast(tex); + if(tex && tex != GFXTextureTarget::sDefaultDepthStencil) + mTargets[slot] = new _GFXGLTextureTargetDesc(glTexture, mipLevel, zOffset); + else + mTargets[slot] = NULL; +} + +void GFXGLTextureTarget::attachTexture( RenderSlot slot, GFXCubemap *tex, U32 face, U32 mipLevel/*=0*/ ) +{ + // No depth cubemaps, sorry + AssertFatal(slot != DepthStencil, "GFXGLTextureTarget::attachTexture (cube) - Cube depth textures not supported!"); + if(slot == DepthStencil) + return; + + // Triggers an update when we next render + invalidateState(); + + // We stash the texture and info into an internal struct. + GFXGLCubemap* glTexture = static_cast(tex); + if(tex) + mTargets[slot] = new _GFXGLCubemapTargetDesc(glTexture, face, mipLevel, 0); + else + mTargets[slot] = NULL; +} + +void GFXGLTextureTarget::clearAttachments() +{ + deactivate(); + for(S32 i=1; imakeActive(); +} + +void GFXGLTextureTarget::deactivate() +{ + _impl->finish(); +} + +void GFXGLTextureTarget::applyState() +{ + if(!isPendingState()) + return; + + // So we don't do this over and over again + stateApplied(); + + // Ensure we have the proper implementation (consider changing to an enum?) + if(_needsAux && dynamic_cast<_GFXGLTextureTargetAUXBufferImpl*>(_impl.ptr()) == NULL) + _impl = new _GFXGLTextureTargetAUXBufferImpl(this); + else if(!_needsAux && dynamic_cast<_GFXGLTextureTargetFBOImpl*>(_impl.ptr()) == NULL) + _impl = new _GFXGLTextureTargetFBOImpl(this); + + _impl->applyState(); +} + +_GFXGLTargetDesc* GFXGLTextureTarget::getTargetDesc(RenderSlot slot) const +{ + // This can only be called by our implementations, and then will not actually store the pointer so this is (almost) safe + return mTargets[slot].ptr(); +} + +void GFXGLTextureTarget::_onTextureEvent( GFXTexCallbackCode code ) +{ + invalidateState(); +} + +const String GFXGLTextureTarget::describeSelf() const +{ + String ret = String::ToString(" Color0 Attachment: %i", mTargets[Color0].isValid() ? mTargets[Color0]->getHandle() : 0); + ret += String::ToString(" Depth Attachment: %i", mTargets[DepthStencil].isValid() ? mTargets[DepthStencil]->getHandle() : 0); + + return ret; +} + +void GFXGLTextureTarget::resolve() +{ +} + +void GFXGLTextureTarget::resolveTo(GFXTextureObject* obj) +{ + AssertFatal(dynamic_cast(obj), "GFXGLTextureTarget::resolveTo - Incorrect type of texture, expected a GFXGLTextureObject"); + GFXGLTextureObject* glTexture = static_cast(obj); + + PRESERVE_FRAMEBUFFER(); + + GLuint dest; + GLuint src; + + glGenFramebuffersEXT(1, &dest); + glGenFramebuffersEXT(1, &src); + + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dest); + glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, glTexture->getHandle(), 0); + + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, src); + glFramebufferTexture2DEXT(GL_READ_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,mTargets[Color0]->getHandle(), 0); + + glBlitFramebufferEXT(0, 0, mTargets[Color0]->getWidth(), mTargets[Color0]->getHeight(), + 0, 0, glTexture->getWidth(), glTexture->getHeight(), GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); + + glDeleteFramebuffersEXT(1, &dest); + glDeleteFramebuffersEXT(1, &src); +} diff --git a/gfx/gl/gfxGLTextureTarget.h b/gfx/gl/gfxGLTextureTarget.h new file mode 100644 index 0000000..65137f5 --- /dev/null +++ b/gfx/gl/gfxGLTextureTarget.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLTEXTURETARGET_H_ +#define _GFXGLTEXTURETARGET_H_ + +#include "gfx/gfxTarget.h" +#include "core/util/autoPtr.h" + +class GFXGLTextureObject; +class _GFXGLTargetDesc; +class _GFXGLTextureTargetImpl; + +/// Render to texture support for OpenGL. +/// This class needs to make a number of assumptions due to the requirements +/// and complexity of render to texture in OpenGL. +/// 1) This class is only guaranteed to work with 2D textures or cubemaps. 3D textures +/// may or may not work. +/// 2) This class does not currently support multiple texture targets. Regardless +/// of how many targets you bind, only Color0 will be used. +/// 3) This class requires that the DepthStencil and Color0 targets have identical +/// dimensions. +/// 4) If the DepthStencil target is GFXTextureTarget::sDefaultStencil, then the +/// Color0 target should be the same size as the current backbuffer and should also +/// be the same format (typically R8G8B8A8) +class GFXGLTextureTarget : public GFXTextureTarget +{ +public: + GFXGLTextureTarget(); + virtual ~GFXGLTextureTarget(); + + virtual const Point2I getSize(); + virtual GFXFormat getFormat(); + virtual void attachTexture(RenderSlot slot, GFXTextureObject *tex, U32 mipLevel=0, U32 zOffset = 0); + virtual void attachTexture(RenderSlot slot, GFXCubemap *tex, U32 face, U32 mipLevel=0); + virtual void clearAttachments(); + + /// Functions to query internal state + /// @{ + + /// Returns the internal structure for the given slot. This should only be called by our internal implementations. + _GFXGLTargetDesc* getTargetDesc(RenderSlot slot) const; + + /// @} + + void deactivate(); + void zombify(); + void resurrect(); + virtual const String describeSelf() const; + + virtual void resolve(); + + virtual void resolveTo(GFXTextureObject* obj); + +protected: + + friend class GFXGLDevice; + + /// The callback used to get texture events. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ); + + /// If true our implementation should use AUX buffers + bool _needsAux; + + /// Pointer to our internal implementation + AutoPtr<_GFXGLTextureTargetImpl> _impl; + + /// Array of _GFXGLTargetDesc's, an internal struct used to keep track of texture data. + AutoPtr<_GFXGLTargetDesc> mTargets[MaxRenderSlotId]; + + /// These redirect to our internal implementation + /// @{ + + void applyState(); + void makeActive(); + + /// @} + +}; + +#endif diff --git a/gfx/gl/gfxGLUtils.h b/gfx/gl/gfxGLUtils.h new file mode 100644 index 0000000..dab5a90 --- /dev/null +++ b/gfx/gl/gfxGLUtils.h @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef TORQUE_GFX_GL_GFXGLUTILS_H_ +#define TORQUE_GFX_GL_GFXGLUTILS_H_ + +#include "core/util/preprocessorHelpers.h" +#include "gfx/gl/gfxGLEnumTranslate.h" + +static inline GLenum minificationFilter(U32 minFilter, U32 mipFilter, U32 mipLevels) +{ + if(mipLevels == 1) + return GFXGLTextureFilter[minFilter]; + + // the compiler should interpret this as array lookups + switch( minFilter ) + { + case GFXTextureFilterLinear: + switch( mipFilter ) + { + case GFXTextureFilterLinear: + return GL_LINEAR_MIPMAP_LINEAR; + case GFXTextureFilterPoint: + return GL_LINEAR_MIPMAP_NEAREST; + default: + return GL_LINEAR; + } + default: + switch( mipFilter ) { + case GFXTextureFilterLinear: + return GL_NEAREST_MIPMAP_LINEAR; + case GFXTextureFilterPoint: + return GL_NEAREST_MIPMAP_NEAREST; + default: + return GL_NEAREST; + } + } +} + +/// Simple class which preserves a given GL integer. +/// This class determines the integer to preserve on construction and restores +/// it on destruction. +class GFXGLPreserveInteger +{ +public: + typedef void(*BindFn)(GLenum, GLuint); + + /// Preserve the integer. + /// @param binding The binding which should be set on destruction. + /// @param getBinding The parameter to be passed to glGetIntegerv to determine + /// the integer to be preserved. + /// @param binder The gl function to call to restore the integer. + GFXGLPreserveInteger(GLenum binding, GLint getBinding, BindFn binder) : + mBinding(binding), mPreserved(0), mBinder(binder) + { + AssertFatal(mBinder, "GFXGLPreserveInteger - Need a valid binder function"); + glGetIntegerv(getBinding, &mPreserved); + } + + /// Restores the integer. + ~GFXGLPreserveInteger() + { + mBinder(mBinding, mPreserved); + } + +private: + GLenum mBinding; + GLint mPreserved; + BindFn mBinder; +}; + +/// Helper macro to preserve the current VBO binding. +#define PRESERVE_VERTEX_BUFFER() \ +GFXGLPreserveInteger TORQUE_CONCAT(preserve_, __LINE__) (GL_ARRAY_BUFFER, GL_ARRAY_BUFFER_BINDING, glBindBuffer) + +/// Helper macro to preserve the current element array binding. +#define PRESERVE_INDEX_BUFFER() \ +GFXGLPreserveInteger TORQUE_CONCAT(preserve_, __LINE__) (GL_ELEMENT_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER_BINDING, glBindBuffer) + +/// Helper macro to preserve the current 2D texture binding. +#define PRESERVE_2D_TEXTURE() \ +GFXGLPreserveInteger TORQUE_CONCAT(preserve_, __LINE__) (GL_TEXTURE_2D, GL_TEXTURE_BINDING_2D, glBindTexture) + +/// Helper macro to preserve the current 3D texture binding. +#define PRESERVE_3D_TEXTURE() \ +GFXGLPreserveInteger TORQUE_CONCAT(preserve_, __LINE__) (GL_TEXTURE_3D, GL_TEXTURE_BINDING_3D, glBindTexture) + +#define PRESERVE_FRAMEBUFFER() \ +GFXGLPreserveInteger TORQUE_CONCAT(preserve_, __LINE__) (GL_READ_FRAMEBUFFER_EXT, GL_READ_FRAMEBUFFER_BINDING_EXT, glBindFramebufferEXT);\ +GFXGLPreserveInteger TORQUE_CONCAT(preserve2_, __LINE__) (GL_DRAW_FRAMEBUFFER_EXT, GL_DRAW_FRAMEBUFFER_BINDING_EXT, glBindFramebufferEXT) + +#endif diff --git a/gfx/gl/gfxGLVertexBuffer.cpp b/gfx/gl/gfxGLVertexBuffer.cpp new file mode 100644 index 0000000..a4f07a0 --- /dev/null +++ b/gfx/gl/gfxGLVertexBuffer.cpp @@ -0,0 +1,161 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/gl/gfxGLVertexBuffer.h" + +#include "gfx/gl/gfxGLDevice.h" +#include "gfx/gl/gfxGLEnumTranslate.h" +#include "gfx/gl/gfxGLUtils.h" + + +GFXGLVertexBuffer::GFXGLVertexBuffer( GFXDevice *device, + U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertexSize, + GFXBufferType bufferType ) + : GFXVertexBuffer( device, numVerts, vertexFormat, vertexSize, bufferType ), + mZombieCache(NULL) +{ + PRESERVE_VERTEX_BUFFER(); + // Generate a buffer and allocate the needed memory. + glGenBuffers(1, &mBuffer); + glBindBuffer(GL_ARRAY_BUFFER, mBuffer); + glBufferData(GL_ARRAY_BUFFER, numVerts * vertexSize, NULL, GFXGLBufferType[bufferType]); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +GFXGLVertexBuffer::~GFXGLVertexBuffer() +{ + // While heavy handed, this does delete the buffer and frees the associated memory. + glDeleteBuffers(1, &mBuffer); + + if( mZombieCache ) + delete [] mZombieCache; +} + +void GFXGLVertexBuffer::lock( U32 vertexStart, U32 vertexEnd, void **vertexPtr ) +{ + PRESERVE_VERTEX_BUFFER(); + // Bind us, get a pointer into the buffer, then + // offset it by vertexStart so we act like the D3D layer. + glBindBuffer(GL_ARRAY_BUFFER, mBuffer); + glBufferData(GL_ARRAY_BUFFER, mNumVerts * mVertexSize, NULL, GFXGLBufferType[mBufferType]); + *vertexPtr = (void*)((U8*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY) + (vertexStart * mVertexSize)); + lockedVertexStart = vertexStart; + lockedVertexEnd = vertexEnd; +} + +void GFXGLVertexBuffer::unlock() +{ + PRESERVE_VERTEX_BUFFER(); + // Unmap the buffer and bind 0 to GL_ARRAY_BUFFER + glBindBuffer(GL_ARRAY_BUFFER, mBuffer); + bool res = glUnmapBuffer(GL_ARRAY_BUFFER); + AssertFatal(res, "GFXGLVertexBuffer::unlock - shouldn't fail!"); + + lockedVertexStart = 0; + lockedVertexEnd = 0; +} + +void GFXGLVertexBuffer::prepare() +{ + // Bind the buffer... + static_cast(mDevice)->setVB(this); + glBindBuffer(GL_ARRAY_BUFFER, mBuffer); + U8* buffer = (U8*)getBuffer(); + + // Loop thru the vertex format elements adding the array state... + U32 texCoordIndex = 0; + for ( U32 i=0; i < mVertexFormat->getElementCount(); i++ ) + { + const GFXVertexElement &element = mVertexFormat->getElement( i ); + + if ( element.isSemantic( GFXSemantic::POSITION ) ) + { + glEnableClientState( GL_VERTEX_ARRAY ); + glVertexPointer( element.getSizeInBytes() / 4, GL_FLOAT, mVertexSize, buffer ); + buffer += element.getSizeInBytes(); + } + else if ( element.isSemantic( GFXSemantic::NORMAL ) ) + { + glEnableClientState( GL_NORMAL_ARRAY ); + glNormalPointer( GL_FLOAT, mVertexSize, buffer ); + buffer += element.getSizeInBytes(); + } + else if ( element.isSemantic( GFXSemantic::COLOR ) ) + { + glEnableClientState( GL_COLOR_ARRAY ); + glColorPointer( element.getSizeInBytes(), GL_UNSIGNED_BYTE, mVertexSize, buffer ); + buffer += element.getSizeInBytes(); + } + else // Everything else is a texture coordinate. + { + glClientActiveTexture( GL_TEXTURE0 + texCoordIndex ); + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + glTexCoordPointer( element.getSizeInBytes() / 4, GL_FLOAT, mVertexSize, buffer ); + buffer += element.getSizeInBytes(); + ++texCoordIndex; + } + + } +} + +void GFXGLVertexBuffer::finish() +{ + glBindBuffer(GL_ARRAY_BUFFER, 0); + + U32 texCoordIndex = 0; + for ( U32 i=0; i < mVertexFormat->getElementCount(); i++ ) + { + const GFXVertexElement &element = mVertexFormat->getElement( i ); + + if ( element.isSemantic( GFXSemantic::POSITION ) ) + glDisableClientState( GL_VERTEX_ARRAY ); + else if ( element.isSemantic( GFXSemantic::NORMAL ) ) + glDisableClientState( GL_NORMAL_ARRAY ); + else if ( element.isSemantic( GFXSemantic::COLOR ) ) + glDisableClientState( GL_COLOR_ARRAY ); + else + { + glClientActiveTexture( GL_TEXTURE0 + texCoordIndex ); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + ++texCoordIndex; + } + } +} + +GLvoid* GFXGLVertexBuffer::getBuffer() +{ + // NULL specifies no offset into the hardware buffer + return (GLvoid*)NULL; +} + +void GFXGLVertexBuffer::zombify() +{ + if(mZombieCache || !mBuffer) + return; + + mZombieCache = new U8[mNumVerts * mVertexSize]; + glBindBuffer(GL_ARRAY_BUFFER, mBuffer); + glGetBufferSubData(GL_ARRAY_BUFFER, 0, mNumVerts * mVertexSize, mZombieCache); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &mBuffer); + mBuffer = 0; +} + +void GFXGLVertexBuffer::resurrect() +{ + if(!mZombieCache) + return; + + glGenBuffers(1, &mBuffer); + glBindBuffer(GL_ARRAY_BUFFER, mBuffer); + glBufferData(GL_ARRAY_BUFFER, mNumVerts * mVertexSize, mZombieCache, GFXGLBufferType[mBufferType]); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + delete[] mZombieCache; + mZombieCache = NULL; +} diff --git a/gfx/gl/gfxGLVertexBuffer.h b/gfx/gl/gfxGLVertexBuffer.h new file mode 100644 index 0000000..b60fa13 --- /dev/null +++ b/gfx/gl/gfxGLVertexBuffer.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLVERTEXBUFFER_H_ +#define _GFXGLVERTEXBUFFER_H_ + +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef GL_GGL_H +#include "gfx/gl/ggl/ggl.h" +#endif + +/// This is a vertex buffer which uses GL_ARB_vertex_buffer_object. +class GFXGLVertexBuffer : public GFXVertexBuffer +{ +public: + GFXGLVertexBuffer( GFXDevice *device, + U32 numVerts, + const GFXVertexFormat *vertexFormat, + U32 vertexSize, + GFXBufferType bufferType ); + + ~GFXGLVertexBuffer(); + + virtual void lock(U32 vertexStart, U32 vertexEnd, void **vertexPtr); ///< calls glMapBuffer and offsets the pointer by vertex start + virtual void unlock(); ///< calls glUnmapBuffer, unbinds the buffer + virtual void prepare(); ///< Binds the buffer + virtual void finish(); ///< We're done here + + GLvoid* getBuffer(); ///< returns NULL + + // GFXResource interface + virtual void zombify(); + virtual void resurrect(); + +private: + friend class GFXGLDevice; + /// GL buffer handle + GLuint mBuffer; + + U8* mZombieCache; +}; + +#endif diff --git a/gfx/gl/gfxGLWindowTarget.cpp b/gfx/gl/gfxGLWindowTarget.cpp new file mode 100644 index 0000000..e41b19e --- /dev/null +++ b/gfx/gl/gfxGLWindowTarget.cpp @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "windowManager/platformWindow.h" +#include "gfx/gl/gfxGLDevice.h" +#include "gfx/gl/gfxGLWindowTarget.h" +#include "gfx/gl/gfxGLTextureObject.h" +#include "gfx/gl/gfxGLUtils.h" + +GFXGLWindowTarget::GFXGLWindowTarget(PlatformWindow *win, GFXDevice *d) + : GFXWindowTarget(win), mDevice(d), mContext(NULL), mFullscreenContext(NULL) +{ + win->appEvent.notify(this, &GFXGLWindowTarget::_onAppSignal); +} + +void GFXGLWindowTarget::resetMode() +{ + if(mWindow->getVideoMode().fullScreen != mWindow->isFullscreen()) + { + _teardownCurrentMode(); + _setupNewMode(); + } +} + +void GFXGLWindowTarget::_onAppSignal(WindowId wnd, S32 event) +{ + if(event != WindowHidden) + return; + + // TODO: Investigate this further. + // Opening and then closing the console results in framerate dropping at an alarming rate down to 3-4 FPS and then + // rebounding to it's usual level. Clearing all the volatile VBs prevents this behavior, but I can't explain why. + // My fear is there is something fundamentally wrong with how we share objects between contexts and this is simply + // masking the issue for the most common case. + static_cast(mDevice)->mVolatileVBs.clear(); +} + +void GFXGLWindowTarget::resolveTo(GFXTextureObject* obj) +{ + AssertFatal(dynamic_cast(obj), "GFXGLTextureTarget::resolveTo - Incorrect type of texture, expected a GFXGLTextureObject"); + GFXGLTextureObject* glTexture = static_cast(obj); + + PRESERVE_FRAMEBUFFER(); + + GLuint dest; + + glGenFramebuffersEXT(1, &dest); + + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dest); + glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, glTexture->getHandle(), 0); + + glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0); + + glBlitFramebufferEXT(0, 0, getSize().x, getSize().y, + 0, 0, glTexture->getWidth(), glTexture->getHeight(), GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0); + + glDeleteFramebuffersEXT(1, &dest); +} diff --git a/gfx/gl/gfxGLWindowTarget.h b/gfx/gl/gfxGLWindowTarget.h new file mode 100644 index 0000000..d6414d1 --- /dev/null +++ b/gfx/gl/gfxGLWindowTarget.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GFXGLWINDOWTARGET_H_ +#define _GFXGLWINDOWTARGET_H_ + +#include "gfx/gfxTarget.h" + +class GFXGLWindowTarget : public GFXWindowTarget +{ +public: + + GFXGLWindowTarget(PlatformWindow *win, GFXDevice *d); + const Point2I getSize() + { + return mWindow->getClientExtent(); + } + virtual GFXFormat getFormat() + { + // TODO: Fix me! + return GFXFormatR8G8B8A8; + } + void makeActive(); + virtual bool present(); + virtual void resetMode(); + virtual void zombify() { } + virtual void resurrect() { } + + virtual void resolveTo(GFXTextureObject* obj); + + void _onAppSignal(WindowId wnd, S32 event); + +private: + friend class GFXGLDevice; + Point2I size; + GFXDevice* mDevice; + void* mContext; + void* mFullscreenContext; + void _teardownCurrentMode(); + void _setupNewMode(); +}; + +#endif \ No newline at end of file diff --git a/gfx/gl/ggl/generated/glc.h b/gfx/gl/ggl/generated/glc.h new file mode 100644 index 0000000..8ace4e6 --- /dev/null +++ b/gfx/gl/ggl/generated/glc.h @@ -0,0 +1,649 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +// OpenGL 1.0 core definitions +//----------------------------------------------------------------------------- +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Private, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2002 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Private published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; /* 1-byte signed */ +typedef short GLshort; /* 2-byte signed */ +typedef int GLint; /* 4-byte signed */ +typedef unsigned char GLubyte; /* 1-byte unsigned */ +typedef unsigned short GLushort; /* 2-byte unsigned */ +typedef unsigned int GLuint; /* 4-byte unsigned */ +typedef int GLsizei; /* 4-byte signed */ +typedef float GLfloat; /* single precision float */ +typedef float GLclampf; /* single precision float in [0,1] */ +typedef double GLdouble; /* double precision float */ +typedef double GLclampd; /* double precision float in [0,1] */ + +/* Boolean values */ +#define GL_FALSE 0x0 +#define GL_TRUE 0x1 + +/* Data types */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 + +/* Primitives */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +/* Vertex Arrays */ +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D + +/* Matrix Mode */ +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 + +/* Points */ +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_POINT_SIZE_RANGE 0x0B12 + +/* Lines */ +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_WIDTH_RANGE 0x0B22 + +/* Polygons */ +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +/* Display Lists */ +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_LIST_MODE 0x0B30 + +/* Depth buffer */ +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_BITS 0x0D56 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_COMPONENT 0x1902 + +/* Lighting */ +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_SHININESS 0x1601 +#define GL_EMISSION 0x1600 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_SHADE_MODEL 0x0B54 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_NORMALIZE 0x0BA1 + +/* User clipping planes */ +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 + +/* Accumulation buffer */ +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_ACCUM 0x0100 +#define GL_ADD 0x0104 +#define GL_LOAD 0x0101 +#define GL_MULT 0x0103 +#define GL_RETURN 0x0102 + +/* Alpha testing */ +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_ALPHA_TEST_FUNC 0x0BC1 + +/* Blending */ +#define GL_BLEND 0x0BE2 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND_DST 0x0BE0 +#define GL_ZERO 0x0 +#define GL_ONE 0x1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA_SATURATE 0x0308 + +/* Render Mode */ +#define GL_FEEDBACK 0x1C01 +#define GL_RENDER 0x1C00 +#define GL_SELECT 0x1C02 + +/* Feedback */ +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 + +/* Selection */ +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 + +/* Fog */ +#define GL_FOG 0x0B60 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_COLOR 0x0B66 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_LINEAR 0x2601 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +/* Logic Ops */ +#define GL_LOGIC_OP 0x0BF1 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_CLEAR 0x1500 +#define GL_SET 0x150F +#define GL_COPY 0x1503 +#define GL_COPY_INVERTED 0x150C +#define GL_NOOP 0x1505 +#define GL_INVERT 0x150A +#define GL_AND 0x1501 +#define GL_NAND 0x150E +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_XOR 0x1506 +#define GL_EQUIV 0x1509 +#define GL_AND_REVERSE 0x1502 +#define GL_AND_INVERTED 0x1504 +#define GL_OR_REVERSE 0x150B +#define GL_OR_INVERTED 0x150D + +/* Stencil */ +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_STENCIL_BITS 0x0D57 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_INDEX 0x1901 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +/* Buffers, Pixel Drawing/Reading */ +#define GL_NONE 0x0 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +/*GL_FRONT 0x0404 */ +/*GL_BACK 0x0405 */ +/*GL_FRONT_AND_BACK 0x0408 */ +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_COLOR_INDEX 0x1900 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_ALPHA_BITS 0x0D55 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_INDEX_BITS 0x0D51 +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_READ_BUFFER 0x0C02 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_BITMAP 0x1A00 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_DITHER 0x0BD0 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_RGB2 0x804E +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +/*#define GL_REPLACE 0x8062*/ +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 + +/* Private limits */ +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B + +/* Gets */ +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_MODE 0x0C30 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_RENDER_MODE 0x0C40 +#define GL_RGBA_MODE 0x0C31 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_VIEWPORT 0x0BA2 + +/* Evaluators */ +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_COEFF 0x0A00 +#define GL_DOMAIN 0x0A02 +#define GL_ORDER 0x0A01 + +/* Hints */ +#define GL_FOG_HINT 0x0C54 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* Scissor box */ +#define GL_SCISSOR_TEST 0x0C11 +#define GL_SCISSOR_BOX 0x0C10 + +/* Pixel Mode / Transfer */ +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 + +/* Texture mapping */ +#define GL_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_TEXTURE_COMPONENTS 0x1003 +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_LINEAR 0x2400 +#define GL_EYE_PLANE 0x2502 +#define GL_SPHERE_MAP 0x2402 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 +#define GL_NEAREST 0x2600 +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_BINDING_3D 0x806A + +/* Utility */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* Errors */ +#define GL_NO_ERROR 0x0 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +/* glPush/PopAttrib bits */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000FFFFF + diff --git a/gfx/gl/ggl/generated/glcfn.h b/gfx/gl/ggl/generated/glcfn.h new file mode 100644 index 0000000..c6c6f98 --- /dev/null +++ b/gfx/gl/ggl/generated/glcfn.h @@ -0,0 +1,330 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +// OpenGL 1.1 core functions +GL_GROUP_BEGIN(GL_VERSION_1_1) +GL_FUNCTION(glAccum, void, (GLenum op, GLfloat value)) +GL_FUNCTION(glAlphaFunc, void, (GLenum func, GLclampf ref)) +GL_FUNCTION(glAreTexturesResident, void, (GLsizei n, const GLuint *textures, GLboolean *residences)) +GL_FUNCTION(glArrayElement, void, (GLint i)) +GL_FUNCTION(glBegin, void, (GLenum mode)) +GL_FUNCTION(glBindTexture, void, (GLenum target, GLuint texture)) +GL_FUNCTION(glBitmap, void, (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap)) +GL_FUNCTION(glBlendFunc, void, (GLenum sfactor, GLenum dfactor)) +GL_FUNCTION(glCallList, void, (GLuint list)) +GL_FUNCTION(glCallLists, void, (GLsizei n, GLenum type, const GLvoid *lists)) +GL_FUNCTION(glClear, void, (GLbitfield mask)) +GL_FUNCTION(glClearAccum, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) +GL_FUNCTION(glClearColor, void, (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)) +GL_FUNCTION(glClearDepth, void, (GLclampd depth)) +GL_FUNCTION(glClearIndex, void, (GLfloat c)) +GL_FUNCTION(glClearStencil, void, (GLint s)) +GL_FUNCTION(glClipPlane, void, (GLenum plane, const GLdouble *equation)) +GL_FUNCTION(glColor3b, void, (GLbyte red, GLbyte green, GLbyte blue)) +GL_FUNCTION(glColor3bv, void, (const GLbyte *v)) +GL_FUNCTION(glColor3d, void, (GLdouble red, GLdouble green, GLdouble blue)) +GL_FUNCTION(glColor3dv, void, (const GLdouble *v)) +GL_FUNCTION(glColor3f, void, (GLfloat red, GLfloat green, GLfloat blue)) +GL_FUNCTION(glColor3fv, void, (const GLfloat *v)) +GL_FUNCTION(glColor3i, void, (GLint red, GLint green, GLint blue)) +GL_FUNCTION(glColor3iv, void, (const GLint *v)) +GL_FUNCTION(glColor3s, void, (GLshort red, GLshort green, GLshort blue)) +GL_FUNCTION(glColor3sv, void, (const GLshort *v)) +GL_FUNCTION(glColor3ub, void, (GLubyte red, GLubyte green, GLubyte blue)) +GL_FUNCTION(glColor3ubv, void, (const GLubyte *v)) +GL_FUNCTION(glColor3ui, void, (GLuint red, GLuint green, GLuint blue)) +GL_FUNCTION(glColor3uiv, void, (const GLuint *v)) +GL_FUNCTION(glColor3us, void, (GLushort red, GLushort green, GLushort blue)) +GL_FUNCTION(glColor3usv, void, (const GLushort *v)) +GL_FUNCTION(glColor4b, void, (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha)) +GL_FUNCTION(glColor4bv, void, (const GLbyte *v)) +GL_FUNCTION(glColor4d, void, (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha)) +GL_FUNCTION(glColor4dv, void, (const GLdouble *v)) +GL_FUNCTION(glColor4f, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) +GL_FUNCTION(glColor4fv, void, (const GLfloat *v)) +GL_FUNCTION(glColor4i, void, (GLint red, GLint green, GLint blue, GLint alpha)) +GL_FUNCTION(glColor4iv, void, (const GLint *v)) +GL_FUNCTION(glColor4s, void, (GLshort red, GLshort green, GLshort blue, GLshort alpha)) +GL_FUNCTION(glColor4sv, void, (const GLshort *v)) +GL_FUNCTION(glColor4ub, void, (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha)) +GL_FUNCTION(glColor4ubv, void, (const GLubyte *v)) +GL_FUNCTION(glColor4ui, void, (GLuint red, GLuint green, GLuint blue, GLuint alpha)) +GL_FUNCTION(glColor4uiv, void, (const GLuint *v)) +GL_FUNCTION(glColor4us, void, (GLushort red, GLushort green, GLushort blue, GLushort alpha)) +GL_FUNCTION(glColor4usv, void, (const GLushort *v)) +GL_FUNCTION(glColorMask, void, (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)) +GL_FUNCTION(glColorMaterial, void, (GLenum face, GLenum mode)) +GL_FUNCTION(glCopyPixels, void, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type)) +GL_FUNCTION(glCopyTexImage1D, void, (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border)) +GL_FUNCTION(glCopyTexImage2D, void, (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)) +GL_FUNCTION(glCopyTexSubImage1D, void, (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width)) +GL_FUNCTION(glCopyTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)) +GL_FUNCTION(glCullFace, void, (GLenum mode)) +GL_FUNCTION(glDeleteLists, void, (GLuint list, GLsizei range)) +GL_FUNCTION(glDeleteTextures, void, (GLsizei n, const GLuint *textures)) +GL_FUNCTION(glDepthFunc, void, (GLenum func)) +GL_FUNCTION(glDepthMask, void, (GLboolean flag)) +GL_FUNCTION(glDepthRange, void, (GLclampd zNear, GLclampd zFar)) +GL_FUNCTION(glDisable, void, (GLenum cap)) +GL_FUNCTION(glDisableClientState, void, (GLenum array)) +GL_FUNCTION(glDrawArrays, void, (GLenum mode, GLint first, GLsizei count)) +GL_FUNCTION(glDrawBuffer, void, (GLenum mode)) +GL_FUNCTION(glDrawElements, void, (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices)) +GL_FUNCTION(glDrawPixels, void, (GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)) +GL_FUNCTION(glEdgeFlag, void, (GLboolean flag)) +GL_FUNCTION(glEdgeFlagPointer, void, (GLsizei stride, const GLboolean *pointer)) +GL_FUNCTION(glEnable, void, (GLenum cap)) +GL_FUNCTION(glEndList, void, (void)) +GL_FUNCTION(glEnableClientState, void, (GLenum array)) +GL_FUNCTION(glEnd, void, (void)) +GL_FUNCTION(glEvalCoord1d, void, (GLdouble u)) +GL_FUNCTION(glEvalCoord1f, void, (GLfloat u)) +GL_FUNCTION(glEvalCoord2d, void, (GLdouble u, GLdouble v)) +GL_FUNCTION(glEvalCoord2f, void, (GLfloat u, GLfloat v)) +GL_FUNCTION(glEvalMesh1, void, (GLenum mode, GLint i1, GLint i2)) +GL_FUNCTION(glEvalMesh2, void, (GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2)) +GL_FUNCTION(glEvalPoint1, void, (GLint i)) +GL_FUNCTION(glEvalPoint2, void, (GLint i, GLint j)) +GL_FUNCTION(glFeedbackBuffer, void, (GLsizei size, GLenum type, GLfloat *buffer)) +GL_FUNCTION(glFinish, void, (void)) +GL_FUNCTION(glFlush, void, (void)) +GL_FUNCTION(glFogf, void, (GLenum pname, GLfloat param)) +GL_FUNCTION(glFogfv, void, (GLenum pname, const GLfloat *params)) +GL_FUNCTION(glFogi, void, (GLenum pname, GLint param)) +GL_FUNCTION(glFogiv, void, (GLenum pname, const GLint *params)) +GL_FUNCTION(glFrontFace, void, (GLenum mode)) +GL_FUNCTION(glFrustum, void, (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar)) +GL_FUNCTION(glGenLists, GLuint, (GLsizei range)) +GL_FUNCTION(glGenTextures, void, (GLsizei n, GLuint *textures)) +GL_FUNCTION(glGetBooleanv, void, (GLenum pname, GLboolean *params)) +GL_FUNCTION(glGetClipPlane, void, (GLenum plane, GLdouble *equation)) +GL_FUNCTION(glColorPointer, void, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)) +GL_FUNCTION(glGetDoublev, void, (GLenum pname, GLdouble *params)) +GL_FUNCTION(glGetFloatv, void, (GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetError, GLuint, (void)) +GL_FUNCTION(glGetIntegerv, void, (GLenum pname, GLint *params)) +GL_FUNCTION(glGetLightfv, void, (GLenum light, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetLightiv, void, (GLenum light, GLenum pname, GLint *params)) +GL_FUNCTION(glGetMapdv, void, (GLenum target, GLenum query, GLdouble *v)) +GL_FUNCTION(glGetMapfv, void, (GLenum target, GLenum query, GLfloat *v)) +GL_FUNCTION(glGetMapiv, void, (GLenum target, GLenum query, GLint *v)) +GL_FUNCTION(glGetMaterialfv, void, (GLenum face, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetMaterialiv, void, (GLenum face, GLenum pname, GLint *params)) +GL_FUNCTION(glGetPixelMapfv, void, (GLenum map, GLfloat *values)) +GL_FUNCTION(glGetPixelMapuiv, void, (GLenum map, GLuint *values)) +GL_FUNCTION(glGetPixelMapusv, void, (GLenum map, GLushort *values)) +GL_FUNCTION(glGetPointerv, void, (GLenum pname, GLvoid* *params)) +GL_FUNCTION(glGetPolygonStipple, void, (GLubyte *mask)) +GL_FUNCTION(glGetString, const GLubyte*, (GLenum name)) +GL_FUNCTION(glGetTexEnvfv, void, (GLenum target, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetTexEnviv, void, (GLenum target, GLenum pname, GLint *params)) +GL_FUNCTION(glGetTexGendv, void, (GLenum coord, GLenum pname, GLdouble *params)) +GL_FUNCTION(glGetTexGenfv, void, (GLenum coord, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetTexGeniv, void, (GLenum coord, GLenum pname, GLint *params)) +GL_FUNCTION(glGetTexImage, void, (GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels)) +GL_FUNCTION(glGetTexLevelParameterfv, void, (GLenum target, GLint level, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetTexLevelParameteriv, void, (GLenum target, GLint level, GLenum pname, GLint *params)) +GL_FUNCTION(glGetTexParameterfv, void, (GLenum target, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetTexParameteriv, void, (GLenum target, GLenum pname, GLint *params)) +GL_FUNCTION(glHint, void, (GLenum target, GLenum mode)) +GL_FUNCTION(glIndexd, void, (GLdouble c)) +GL_FUNCTION(glIndexf, void, (GLfloat c)) +GL_FUNCTION(glIndexi, void, (GLint c)) +GL_FUNCTION(glIndexs, void, (GLshort c)) +GL_FUNCTION(glIndexub, void, (GLubyte c)) +GL_FUNCTION(glIndexMask, void, (GLuint mask)) +GL_FUNCTION(glIndexPointer, void, (GLenum type, GLsizei stride, const GLvoid *pointer)) +GL_FUNCTION(glInitNames, void, (void)) +GL_FUNCTION(glInterleavedArrays, void, (GLenum format, GLsizei stride, const GLvoid *pointer)) +GL_FUNCTION(glIsEnabled, GLboolean, (GLenum cap)) +GL_FUNCTION(glIsList, GLboolean, (GLuint list)) +GL_FUNCTION(glIsTexture, GLboolean, (GLuint texture)) +GL_FUNCTION(glLightf, void, (GLenum light, GLenum pname, GLfloat param)) +GL_FUNCTION(glLightfv, void, (GLenum light, GLenum pname, const GLfloat *params)) +GL_FUNCTION(glLighti, void, (GLenum light, GLenum pname, GLint param)) +GL_FUNCTION(glLightiv, void, (GLenum light, GLenum pname, const GLint *params)) +GL_FUNCTION(glLightModelf, void, (GLenum pname, GLfloat param)) +GL_FUNCTION(glLightModelfv, void, (GLenum pname, const GLfloat *params)) +GL_FUNCTION(glLightModeli, void, (GLenum pname, GLint param)) +GL_FUNCTION(glLightModeliv, void, (GLenum pname, const GLint *params)) +GL_FUNCTION(glLineStipple, void, (GLint factor, GLushort pattern)) +GL_FUNCTION(glLineWidth, void, (GLfloat width)) +GL_FUNCTION(glListBase, void, (GLuint base)) +GL_FUNCTION(glLoadIdentity, void, (void)) +GL_FUNCTION(glLoadMatrixd, void, (const GLdouble *m)) +GL_FUNCTION(glLoadMatrixf, void, (const GLfloat *m)) +GL_FUNCTION(glLoadName, void, (GLuint name)) +GL_FUNCTION(glLogicOp, void, (GLenum opcode)) +GL_FUNCTION(glMap1d, void, (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points)) +GL_FUNCTION(glMap1f, void, (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLdouble *points)) +GL_FUNCTION(glMap2d, void, (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points)) +GL_FUNCTION(glMap2f, void, (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points)) +GL_FUNCTION(glMapGrid1d, void, (GLint un, GLdouble u1, GLdouble u2)) +GL_FUNCTION(glMapGrid1f, void, (GLint un, GLfloat u1, GLfloat u2)) +GL_FUNCTION(glMapGrid2d, void, (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2)) +GL_FUNCTION(glMapGrid2f, void, (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2)) +GL_FUNCTION(glMaterialf, void, (GLenum face, GLenum pname, GLfloat param)) +GL_FUNCTION(glMaterialfv, void, (GLenum face, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glMateriali, void, (GLenum face, GLenum pname, GLint param)) +GL_FUNCTION(glMaterialiv, void, (GLenum face, GLenum pname, const GLint* params)) +GL_FUNCTION(glMatrixMode, void, (GLenum mode)) +GL_FUNCTION(glMultMatrixd, void, (const GLdouble *m)) +GL_FUNCTION(glMultMatrixf, void, (const GLfloat *m)) +GL_FUNCTION(glNewList, void, (GLuint list, GLenum mode)) +GL_FUNCTION(glNormal3b, void, (GLbyte nx, GLbyte ny, GLbyte nz)) +GL_FUNCTION(glNormal3bv, void, (const GLbyte *v)) +GL_FUNCTION(glNormal3d, void, (GLdouble nx, GLdouble ny, GLdouble nz)) +GL_FUNCTION(glNormal3dv, void, (const GLdouble *v)) +GL_FUNCTION(glNormal3f, void, (GLfloat nx, GLfloat ny, GLfloat nz)) +GL_FUNCTION(glNormal3fv, void, (const GLfloat *v)) +GL_FUNCTION(glNormal3i, void, (GLint nx, GLint ny, GLint nz)) +GL_FUNCTION(glNormal3iv, void, (const GLint *v)) +GL_FUNCTION(glNormal3s, void, (GLshort nx, GLshort ny, GLshort nz)) +GL_FUNCTION(glNormal3sv, void, (const GLshort *v)) +GL_FUNCTION(glNormalPointer, void, (GLenum type, GLsizei stride, const GLvoid *pointer)) +GL_FUNCTION(glOrtho, void, (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar)) +GL_FUNCTION(glPassThrough, void, (GLfloat token)) +GL_FUNCTION(glPixelMapfv, void, (GLenum map, GLint mapSize, const GLfloat *values)) +GL_FUNCTION(glPixelMapuiv, void, (GLenum map, GLint mapSize, const GLuint *values)) +GL_FUNCTION(glPixelMapusv, void, (GLenum map, GLint mapSize, const GLushort *values)) +GL_FUNCTION(glPixelStoref, void, (GLenum pname, GLfloat param)) +GL_FUNCTION(glPixelStorei, void, (GLenum pname, GLint param)) +GL_FUNCTION(glPixelTransferf, void, (GLenum pname, GLfloat param)) +GL_FUNCTION(glPixelTransferi, void, (GLenum pname, GLint param)) +GL_FUNCTION(glPixelZoom, void, (GLfloat xfactor, GLfloat yfactor)) +GL_FUNCTION(glPointSize, void, (GLfloat size)) +GL_FUNCTION(glPolygonMode, void, (GLenum face, GLenum mode)) +GL_FUNCTION(glPolygonOffset, void, (GLfloat factor, GLfloat units)) +GL_FUNCTION(glPopAttrib, void, (void)) +GL_FUNCTION(glPopMatrix, void, (void)) +GL_FUNCTION(glPopName, void, (void)) +GL_FUNCTION(glPrioritizeTextures, void, (GLsizei n, const GLuint *textures, const GLclampf *priorities)) +GL_FUNCTION(glPushAttrib, void, (GLbitfield mask)) +GL_FUNCTION(glPushMatrix, void, (void)) +GL_FUNCTION(glPushName, void, (GLuint name)) +GL_FUNCTION(glRasterPos2d, void, (GLdouble x, GLdouble y)) +GL_FUNCTION(glRasterPos2dv, void, (const GLdouble *v)) +GL_FUNCTION(glRasterPos2f, void, (GLfloat x, GLfloat y)) +GL_FUNCTION(glRasterPos2fv, void, (const GLfloat *v)) +GL_FUNCTION(glRasterPos2i, void, (GLint x, GLint y)) +GL_FUNCTION(glRasterPos2iv, void, (const GLint *v)) +GL_FUNCTION(glRasterPos2s, void, (GLshort x, GLshort y)) +GL_FUNCTION(glRasterPos2sv, void, (const GLshort *v)) +GL_FUNCTION(glRasterPos3d, void, (GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glRasterPos3dv, void, (const GLdouble *v)) +GL_FUNCTION(glRasterPos3f, void, (GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glRasterPos3fv, void, (const GLfloat *v)) +GL_FUNCTION(glRasterPos3i, void, (GLint x, GLint y, GLint z)) +GL_FUNCTION(glRasterPos3iv, void, (const GLint *v)) +GL_FUNCTION(glRasterPos3s, void, (GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glRasterPos3sv, void, (const GLshort *v)) +GL_FUNCTION(glRasterPos4d, void, (GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glRasterPos4dv, void, (const GLdouble *v)) +GL_FUNCTION(glRasterPos4f, void, (GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glRasterPos4fv, void, (const GLfloat *v)) +GL_FUNCTION(glRasterPos4i, void, (GLint x, GLint y, GLint z, GLint w)) +GL_FUNCTION(glRasterPos4iv, void, (const GLint *v)) +GL_FUNCTION(glRasterPos4s, void, (GLshort x, GLshort y, GLshort z, GLshort w)) +GL_FUNCTION(glRasterPos4sv, void, (const GLshort *v)) +GL_FUNCTION(glReadBuffer, void, (GLenum mode)) +GL_FUNCTION(glReadPixels, void, (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels)) +GL_FUNCTION(glRectd, void, (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2)) +GL_FUNCTION(glRectdv, void, (const GLdouble *v)) +GL_FUNCTION(glRectf, void, (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)) +GL_FUNCTION(glRectfv, void, (const GLfloat *v)) +GL_FUNCTION(glRecti, void, (GLint x1, GLint y1, GLint x2, GLint y2)) +GL_FUNCTION(glRectiv, void, (const GLint *v)) +GL_FUNCTION(glRects, void, (GLshort x1, GLshort y1, GLshort x2, GLshort y2)) +GL_FUNCTION(glRectsv, void, (const GLshort *v)) +GL_FUNCTION(glRenderMode, void, (GLenum mode)) +GL_FUNCTION(glRotated, void, (GLdouble angle, GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glRotatef, void, (GLfloat angle, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glScaled, void, (GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glScalef, void, (GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glScissor, void, (GLint x, GLint y, GLsizei width, GLsizei height)) +GL_FUNCTION(glSelectBuffer, void, (GLsizei size, GLuint *buffer)) +GL_FUNCTION(glShadeModel, void, (GLenum mode)) +GL_FUNCTION(glStencilFunc, void, (GLenum func, GLint ref, GLuint mask)) +GL_FUNCTION(glStencilMask, void, (GLuint mask)) +GL_FUNCTION(glStencilOp, void, (GLenum fail, GLenum zfail, GLenum zpass)) +GL_FUNCTION(glTexCoord1d, void, (GLdouble s)) +GL_FUNCTION(glTexCoord1dv, void, (const GLdouble *v)) +GL_FUNCTION(glTexCoord1f, void, (GLfloat s)) +GL_FUNCTION(glTexCoord1fv, void, (const GLfloat *v)) +GL_FUNCTION(glTexCoord1i, void, (GLint s)) +GL_FUNCTION(glTexCoord1iv, void, (const GLint *v)) +GL_FUNCTION(glTexCoord1s, void, (GLshort s)) +GL_FUNCTION(glTexCoord1sv, void, (const GLshort *v)) +GL_FUNCTION(glTexCoord2d, void, (GLdouble s, GLdouble t)) +GL_FUNCTION(glTexCoord2dv, void, (const GLdouble *v)) +GL_FUNCTION(glTexCoord2f, void, (GLfloat s, GLfloat t)) +GL_FUNCTION(glTexCoord2fv, void, (const GLfloat *v)) +GL_FUNCTION(glTexCoord2i, void, (GLint s, GLint t)) +GL_FUNCTION(glTexCoord2iv, void, (const GLint *v)) +GL_FUNCTION(glTexCoord2s, void, (GLshort s, GLshort t)) +GL_FUNCTION(glTexCoord2sv, void, (const GLshort *v)) +GL_FUNCTION(glTexCoord3d, void, (GLdouble s, GLdouble t, GLdouble r)) +GL_FUNCTION(glTexCoord3dv, void, (const GLdouble *v)) +GL_FUNCTION(glTexCoord3f, void, (GLfloat s, GLfloat t, GLfloat r)) +GL_FUNCTION(glTexCoord3fv, void, (const GLfloat *v)) +GL_FUNCTION(glTexCoord3i, void, (GLint s, GLint t, GLint r)) +GL_FUNCTION(glTexCoord3iv, void, (const GLint *v)) +GL_FUNCTION(glTexCoord3s, void, (GLshort s, GLshort t, GLshort r)) +GL_FUNCTION(glTexCoord3sv, void, (const GLshort *v)) +GL_FUNCTION(glTexCoord4d, void, (GLdouble s, GLdouble t, GLdouble r, GLdouble q)) +GL_FUNCTION(glTexCoord4dv, void, (const GLdouble *v)) +GL_FUNCTION(glTexCoord4f, void, (GLfloat s, GLfloat t, GLfloat r, GLfloat q)) +GL_FUNCTION(glTexCoord4fv, void, (const GLfloat *v)) +GL_FUNCTION(glTexCoord4i, void, (GLint s, GLint t, GLint r, GLint q)) +GL_FUNCTION(glTexCoord4iv, void, (const GLint *v)) +GL_FUNCTION(glTexCoord4s, void, (GLshort s, GLshort t, GLshort r, GLshort q)) +GL_FUNCTION(glTexCoord4sv, void, (const GLshort *v)) +GL_FUNCTION(glTexCoordPointer, void, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)) +GL_FUNCTION(glTexEnvf, void, (GLenum target, GLenum pname, GLfloat param)) +GL_FUNCTION(glTexEnvfv, void, (GLenum target, GLenum pname, const GLfloat *params)) +GL_FUNCTION(glTexEnvi, void, (GLenum target, GLenum pname, GLint param)) +GL_FUNCTION(glTexEnviv, void, (GLenum target, GLenum pname, const GLint *params)) +GL_FUNCTION(glTexGend, void, (GLenum coord, GLenum pname, GLdouble param)) +GL_FUNCTION(glTexGendv, void, (GLenum coord, GLenum pname, const GLdouble *param)) +GL_FUNCTION(glTexGenf, void, (GLenum coord, GLenum pname, GLfloat param)) +GL_FUNCTION(glTexGenfv, void, (GLenum coord, GLenum pname, const GLfloat *param)) +GL_FUNCTION(glTexGeni, void, (GLenum coord, GLenum pname, GLint param)) +GL_FUNCTION(glTexGeniv, void, (GLenum coord, GLenum pname, const GLint *param)) +GL_FUNCTION(glTexImage1D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels)) +GL_FUNCTION(glTexImage2D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)) +GL_FUNCTION(glTexParameterf, void, (GLenum target, GLenum pname, GLfloat param)) +GL_FUNCTION(glTexParameteri, void, (GLenum target, GLenum pname, GLint param)) +GL_FUNCTION(glTexSubImage1D, void, (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels)) +GL_FUNCTION(glTexSubImage2D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)) +GL_FUNCTION(glTranslated, void, (GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glTranslatef, void, (GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glVertex2d, void, (GLdouble x, GLdouble y)) +GL_FUNCTION(glVertex2dv, void, (const GLdouble *v)) +GL_FUNCTION(glVertex2f, void, (GLfloat x, GLfloat y)) +GL_FUNCTION(glVertex2fv, void, (const GLfloat *v)) +GL_FUNCTION(glVertex2i, void, (GLint x, GLint y)) +GL_FUNCTION(glVertex2iv, void, (const GLint *v)) +GL_FUNCTION(glVertex2s, void, (GLshort x, GLshort y)) +GL_FUNCTION(glVertex2sv, void, (const GLshort *v)) +GL_FUNCTION(glVertex3d, void, (GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glVertex3dv, void, (const GLdouble *v)) +GL_FUNCTION(glVertex3f, void, (GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glVertex3fv, void, (const GLfloat *v)) +GL_FUNCTION(glVertex3i, void, (GLint x, GLint y, GLint z)) +GL_FUNCTION(glVertex3iv, void, (const GLint *v)) +GL_FUNCTION(glVertex3s, void, (GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glVertex3sv, void, (const GLshort *v)) +GL_FUNCTION(glVertex4d, void, (GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glVertex4dv, void, (const GLdouble *v)) +GL_FUNCTION(glVertex4f, void, (GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glVertex4fv, void, (const GLfloat *v)) +GL_FUNCTION(glVertex4i, void, (GLint x, GLint y, GLint z, GLint w)) +GL_FUNCTION(glVertex4iv, void, (const GLint *v)) +GL_FUNCTION(glVertex4s, void, (GLshort x, GLshort y, GLshort z, GLshort w)) +GL_FUNCTION(glVertex4sv, void, (const GLshort *v)) +GL_FUNCTION(glVertexPointer, void, (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer)) +GL_FUNCTION(glViewport, void, (GLint x, GLint y, GLsizei width, GLsizei height)) +GL_GROUP_END() + diff --git a/gfx/gl/ggl/generated/gle.h b/gfx/gl/ggl/generated/gle.h new file mode 100644 index 0000000..fe2c6f2 --- /dev/null +++ b/gfx/gl/ggl/generated/gle.h @@ -0,0 +1,3980 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +typedef int ptrdiff_t; +typedef void (*GLFunction)(); + +#ifdef GL_VERSION_1_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_TEXTURE_BINDING_3D 0x806A +#define glDrawRangeElements XGL_FUNCPTR(glDrawRangeElements) +#define glTexImage3D XGL_FUNCPTR(glTexImage3D) +#define glTexSubImage3D XGL_FUNCPTR(glTexSubImage3D) +#define glCopyTexSubImage3D XGL_FUNCPTR(glCopyTexSubImage3D) +#endif + +#ifdef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#define GL_CLAMP_TO_BORDER 0x812D +#define glActiveTexture XGL_FUNCPTR(glActiveTexture) +#define glClientActiveTexture XGL_FUNCPTR(glClientActiveTexture) +#define glCompressedTexImage1D XGL_FUNCPTR(glCompressedTexImage1D) +#define glCompressedTexImage2D XGL_FUNCPTR(glCompressedTexImage2D) +#define glCompressedTexImage3D XGL_FUNCPTR(glCompressedTexImage3D) +#define glCompressedTexSubImage1D XGL_FUNCPTR(glCompressedTexSubImage1D) +#define glCompressedTexSubImage2D XGL_FUNCPTR(glCompressedTexSubImage2D) +#define glCompressedTexSubImage3D XGL_FUNCPTR(glCompressedTexSubImage3D) +#define glGetCompressedTexImage XGL_FUNCPTR(glGetCompressedTexImage) +#define glLoadTransposeMatrixd XGL_FUNCPTR(glLoadTransposeMatrixd) +#define glLoadTransposeMatrixf XGL_FUNCPTR(glLoadTransposeMatrixf) +#define glMultTransposeMatrixd XGL_FUNCPTR(glMultTransposeMatrixd) +#define glMultTransposeMatrixf XGL_FUNCPTR(glMultTransposeMatrixf) +#define glMultiTexCoord1d XGL_FUNCPTR(glMultiTexCoord1d) +#define glMultiTexCoord1dv XGL_FUNCPTR(glMultiTexCoord1dv) +#define glMultiTexCoord1f XGL_FUNCPTR(glMultiTexCoord1f) +#define glMultiTexCoord1fv XGL_FUNCPTR(glMultiTexCoord1fv) +#define glMultiTexCoord1i XGL_FUNCPTR(glMultiTexCoord1i) +#define glMultiTexCoord1iv XGL_FUNCPTR(glMultiTexCoord1iv) +#define glMultiTexCoord1s XGL_FUNCPTR(glMultiTexCoord1s) +#define glMultiTexCoord1sv XGL_FUNCPTR(glMultiTexCoord1sv) +#define glMultiTexCoord2d XGL_FUNCPTR(glMultiTexCoord2d) +#define glMultiTexCoord2dv XGL_FUNCPTR(glMultiTexCoord2dv) +#define glMultiTexCoord2f XGL_FUNCPTR(glMultiTexCoord2f) +#define glMultiTexCoord2fv XGL_FUNCPTR(glMultiTexCoord2fv) +#define glMultiTexCoord2i XGL_FUNCPTR(glMultiTexCoord2i) +#define glMultiTexCoord2iv XGL_FUNCPTR(glMultiTexCoord2iv) +#define glMultiTexCoord2s XGL_FUNCPTR(glMultiTexCoord2s) +#define glMultiTexCoord2sv XGL_FUNCPTR(glMultiTexCoord2sv) +#define glMultiTexCoord3d XGL_FUNCPTR(glMultiTexCoord3d) +#define glMultiTexCoord3dv XGL_FUNCPTR(glMultiTexCoord3dv) +#define glMultiTexCoord3f XGL_FUNCPTR(glMultiTexCoord3f) +#define glMultiTexCoord3fv XGL_FUNCPTR(glMultiTexCoord3fv) +#define glMultiTexCoord3i XGL_FUNCPTR(glMultiTexCoord3i) +#define glMultiTexCoord3iv XGL_FUNCPTR(glMultiTexCoord3iv) +#define glMultiTexCoord3s XGL_FUNCPTR(glMultiTexCoord3s) +#define glMultiTexCoord3sv XGL_FUNCPTR(glMultiTexCoord3sv) +#define glMultiTexCoord4d XGL_FUNCPTR(glMultiTexCoord4d) +#define glMultiTexCoord4dv XGL_FUNCPTR(glMultiTexCoord4dv) +#define glMultiTexCoord4f XGL_FUNCPTR(glMultiTexCoord4f) +#define glMultiTexCoord4fv XGL_FUNCPTR(glMultiTexCoord4fv) +#define glMultiTexCoord4i XGL_FUNCPTR(glMultiTexCoord4i) +#define glMultiTexCoord4iv XGL_FUNCPTR(glMultiTexCoord4iv) +#define glMultiTexCoord4s XGL_FUNCPTR(glMultiTexCoord4s) +#define glMultiTexCoord4sv XGL_FUNCPTR(glMultiTexCoord4sv) +#define glSampleCoverage XGL_FUNCPTR(glSampleCoverage) +#endif + +#ifdef GL_VERSION_1_4 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_MIRRORED_REPEAT 0x8370 +#define glBlendEquation XGL_FUNCPTR(glBlendEquation) +#define glBlendColor XGL_FUNCPTR(glBlendColor) +#define glFogCoordf XGL_FUNCPTR(glFogCoordf) +#define glFogCoordfv XGL_FUNCPTR(glFogCoordfv) +#define glFogCoordd XGL_FUNCPTR(glFogCoordd) +#define glFogCoorddv XGL_FUNCPTR(glFogCoorddv) +#define glFogCoordPointer XGL_FUNCPTR(glFogCoordPointer) +#define glMultiDrawArrays XGL_FUNCPTR(glMultiDrawArrays) +#define glMultiDrawElements XGL_FUNCPTR(glMultiDrawElements) +#define glPointParameterf XGL_FUNCPTR(glPointParameterf) +#define glPointParameterfv XGL_FUNCPTR(glPointParameterfv) +#define glSecondaryColor3b XGL_FUNCPTR(glSecondaryColor3b) +#define glSecondaryColor3bv XGL_FUNCPTR(glSecondaryColor3bv) +#define glSecondaryColor3d XGL_FUNCPTR(glSecondaryColor3d) +#define glSecondaryColor3dv XGL_FUNCPTR(glSecondaryColor3dv) +#define glSecondaryColor3f XGL_FUNCPTR(glSecondaryColor3f) +#define glSecondaryColor3fv XGL_FUNCPTR(glSecondaryColor3fv) +#define glSecondaryColor3i XGL_FUNCPTR(glSecondaryColor3i) +#define glSecondaryColor3iv XGL_FUNCPTR(glSecondaryColor3iv) +#define glSecondaryColor3s XGL_FUNCPTR(glSecondaryColor3s) +#define glSecondaryColor3sv XGL_FUNCPTR(glSecondaryColor3sv) +#define glSecondaryColor3ub XGL_FUNCPTR(glSecondaryColor3ub) +#define glSecondaryColor3ubv XGL_FUNCPTR(glSecondaryColor3ubv) +#define glSecondaryColor3ui XGL_FUNCPTR(glSecondaryColor3ui) +#define glSecondaryColor3uiv XGL_FUNCPTR(glSecondaryColor3uiv) +#define glSecondaryColor3us XGL_FUNCPTR(glSecondaryColor3us) +#define glSecondaryColor3usv XGL_FUNCPTR(glSecondaryColor3usv) +#define glSecondaryColorPointer XGL_FUNCPTR(glSecondaryColorPointer) +#define glBlendFuncSeparate XGL_FUNCPTR(glBlendFuncSeparate) +#define glWindowPos2d XGL_FUNCPTR(glWindowPos2d) +#define glWindowPos2f XGL_FUNCPTR(glWindowPos2f) +#define glWindowPos2i XGL_FUNCPTR(glWindowPos2i) +#define glWindowPos2s XGL_FUNCPTR(glWindowPos2s) +#define glWindowPos2dv XGL_FUNCPTR(glWindowPos2dv) +#define glWindowPos2fv XGL_FUNCPTR(glWindowPos2fv) +#define glWindowPos2iv XGL_FUNCPTR(glWindowPos2iv) +#define glWindowPos2sv XGL_FUNCPTR(glWindowPos2sv) +#define glWindowPos3d XGL_FUNCPTR(glWindowPos3d) +#define glWindowPos3f XGL_FUNCPTR(glWindowPos3f) +#define glWindowPos3i XGL_FUNCPTR(glWindowPos3i) +#define glWindowPos3s XGL_FUNCPTR(glWindowPos3s) +#define glWindowPos3dv XGL_FUNCPTR(glWindowPos3dv) +#define glWindowPos3fv XGL_FUNCPTR(glWindowPos3fv) +#define glWindowPos3iv XGL_FUNCPTR(glWindowPos3iv) +#define glWindowPos3sv XGL_FUNCPTR(glWindowPos3sv) +#endif + +#ifdef GL_VERSION_1_5 +typedef ptrdiff_t GLsizeiptr; +typedef ptrdiff_t GLintptr; +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_FOG_COORD_SRC GL_FOG_COORDINATE_SOURCE +#define GL_FOG_COORD GL_FOG_COORDINATE +#define GL_CURRENT_FOG_COORD GL_CURRENT_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY_TYPE GL_FOG_COORDINATE_ARRAY_TYPE +#define GL_FOG_COORD_ARRAY_STRIDE GL_FOG_COORDINATE_ARRAY_STRIDE +#define GL_FOG_COORD_ARRAY_POINTER GL_FOG_COORDINATE_ARRAY_POINTER +#define GL_FOG_COORD_ARRAY GL_FOG_COORDINATE_ARRAY +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING +#define GL_SRC0_RGB GL_SOURCE0_RGB +#define GL_SRC1_RGB GL_SOURCE1_RGB +#define GL_SRC2_RGB GL_SOURCE2_RGB +#define GL_SRC0_ALPHA GL_SOURCE0_ALPHA +#define GL_SRC1_ALPHA GL_SOURCE1_ALPHA +#define GL_SRC2_ALPHA GL_SOURCE2_ALPHA +#define glGenQueries XGL_FUNCPTR(glGenQueries) +#define glDeleteQueries XGL_FUNCPTR(glDeleteQueries) +#define glIsQuery XGL_FUNCPTR(glIsQuery) +#define glBeginQuery XGL_FUNCPTR(glBeginQuery) +#define glEndQuery XGL_FUNCPTR(glEndQuery) +#define glGetQueryiv XGL_FUNCPTR(glGetQueryiv) +#define glGetQueryObjectiv XGL_FUNCPTR(glGetQueryObjectiv) +#define glGetQueryObjectuiv XGL_FUNCPTR(glGetQueryObjectuiv) +#define glBindBuffer XGL_FUNCPTR(glBindBuffer) +#define glDeleteBuffers XGL_FUNCPTR(glDeleteBuffers) +#define glGenBuffers XGL_FUNCPTR(glGenBuffers) +#define glIsBuffer XGL_FUNCPTR(glIsBuffer) +#define glBufferData XGL_FUNCPTR(glBufferData) +#define glBufferSubData XGL_FUNCPTR(glBufferSubData) +#define glGetBufferSubData XGL_FUNCPTR(glGetBufferSubData) +#define glMapBuffer XGL_FUNCPTR(glMapBuffer) +#define glUnmapBuffer XGL_FUNCPTR(glUnmapBuffer) +#define glGetBufferParameteriv XGL_FUNCPTR(glGetBufferParameteriv) +#define glGetBufferPointerv XGL_FUNCPTR(glGetBufferPointerv) +#endif + +#ifdef GL_VERSION_2_0 +typedef char GLchar; +#define GL_BLEND_EQUATION_RGB GL_BLEND_EQUATION +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#define glBlendEquationSeparate XGL_FUNCPTR(glBlendEquationSeparate) +#define glDrawBuffers XGL_FUNCPTR(glDrawBuffers) +#define glStencilOpSeparate XGL_FUNCPTR(glStencilOpSeparate) +#define glStencilFuncSeparate XGL_FUNCPTR(glStencilFuncSeparate) +#define glStencilMaskSeparate XGL_FUNCPTR(glStencilMaskSeparate) +#define glAttachShader XGL_FUNCPTR(glAttachShader) +#define glBindAttribLocation XGL_FUNCPTR(glBindAttribLocation) +#define glCompileShader XGL_FUNCPTR(glCompileShader) +#define glCreateProgram XGL_FUNCPTR(glCreateProgram) +#define glCreateShader XGL_FUNCPTR(glCreateShader) +#define glDeleteProgram XGL_FUNCPTR(glDeleteProgram) +#define glDeleteShader XGL_FUNCPTR(glDeleteShader) +#define glDetachShader XGL_FUNCPTR(glDetachShader) +#define glDisableVertexAttribArray XGL_FUNCPTR(glDisableVertexAttribArray) +#define glEnableVertexAttribArray XGL_FUNCPTR(glEnableVertexAttribArray) +#define glGetActiveAttrib XGL_FUNCPTR(glGetActiveAttrib) +#define glGetActiveUniform XGL_FUNCPTR(glGetActiveUniform) +#define glGetAttachedShaders XGL_FUNCPTR(glGetAttachedShaders) +#define glGetAttribLocation XGL_FUNCPTR(glGetAttribLocation) +#define glGetProgramiv XGL_FUNCPTR(glGetProgramiv) +#define glGetProgramInfoLog XGL_FUNCPTR(glGetProgramInfoLog) +#define glGetShaderiv XGL_FUNCPTR(glGetShaderiv) +#define glGetShaderInfoLog XGL_FUNCPTR(glGetShaderInfoLog) +#define glShaderSource XGL_FUNCPTR(glShaderSource) +#define glGetUniformLocation XGL_FUNCPTR(glGetUniformLocation) +#define glGetUniformfv XGL_FUNCPTR(glGetUniformfv) +#define glGetUniformiv XGL_FUNCPTR(glGetUniformiv) +#define glGetVertexAttribdv XGL_FUNCPTR(glGetVertexAttribdv) +#define glGetVertexAttribfv XGL_FUNCPTR(glGetVertexAttribfv) +#define glGetVertexAttribiv XGL_FUNCPTR(glGetVertexAttribiv) +#define glGetVertexAttribPointerv XGL_FUNCPTR(glGetVertexAttribPointerv) +#define glIsProgram XGL_FUNCPTR(glIsProgram) +#define glIsShader XGL_FUNCPTR(glIsShader) +#define glLinkProgram XGL_FUNCPTR(glLinkProgram) +#define glGetShaderSource XGL_FUNCPTR(glGetShaderSource) +#define glUseProgram XGL_FUNCPTR(glUseProgram) +#define glUniform1f XGL_FUNCPTR(glUniform1f) +#define glUniform1fv XGL_FUNCPTR(glUniform1fv) +#define glUniform1i XGL_FUNCPTR(glUniform1i) +#define glUniform1iv XGL_FUNCPTR(glUniform1iv) +#define glUniform2f XGL_FUNCPTR(glUniform2f) +#define glUniform2fv XGL_FUNCPTR(glUniform2fv) +#define glUniform2i XGL_FUNCPTR(glUniform2i) +#define glUniform2iv XGL_FUNCPTR(glUniform2iv) +#define glUniform3f XGL_FUNCPTR(glUniform3f) +#define glUniform3fv XGL_FUNCPTR(glUniform3fv) +#define glUniform3i XGL_FUNCPTR(glUniform3i) +#define glUniform3iv XGL_FUNCPTR(glUniform3iv) +#define glUniform4f XGL_FUNCPTR(glUniform4f) +#define glUniform4fv XGL_FUNCPTR(glUniform4fv) +#define glUniform4i XGL_FUNCPTR(glUniform4i) +#define glUniform4iv XGL_FUNCPTR(glUniform4iv) +#define glUniformMatrix2fv XGL_FUNCPTR(glUniformMatrix2fv) +#define glUniformMatrix3fv XGL_FUNCPTR(glUniformMatrix3fv) +#define glUniformMatrix4fv XGL_FUNCPTR(glUniformMatrix4fv) +#define glValidateProgram XGL_FUNCPTR(glValidateProgram) +#define glVertexAttrib1d XGL_FUNCPTR(glVertexAttrib1d) +#define glVertexAttrib1dv XGL_FUNCPTR(glVertexAttrib1dv) +#define glVertexAttrib1f XGL_FUNCPTR(glVertexAttrib1f) +#define glVertexAttrib1fv XGL_FUNCPTR(glVertexAttrib1fv) +#define glVertexAttrib1s XGL_FUNCPTR(glVertexAttrib1s) +#define glVertexAttrib1sv XGL_FUNCPTR(glVertexAttrib1sv) +#define glVertexAttrib2d XGL_FUNCPTR(glVertexAttrib2d) +#define glVertexAttrib2dv XGL_FUNCPTR(glVertexAttrib2dv) +#define glVertexAttrib2f XGL_FUNCPTR(glVertexAttrib2f) +#define glVertexAttrib2fv XGL_FUNCPTR(glVertexAttrib2fv) +#define glVertexAttrib2s XGL_FUNCPTR(glVertexAttrib2s) +#define glVertexAttrib2sv XGL_FUNCPTR(glVertexAttrib2sv) +#define glVertexAttrib3d XGL_FUNCPTR(glVertexAttrib3d) +#define glVertexAttrib3dv XGL_FUNCPTR(glVertexAttrib3dv) +#define glVertexAttrib3f XGL_FUNCPTR(glVertexAttrib3f) +#define glVertexAttrib3fv XGL_FUNCPTR(glVertexAttrib3fv) +#define glVertexAttrib3s XGL_FUNCPTR(glVertexAttrib3s) +#define glVertexAttrib3sv XGL_FUNCPTR(glVertexAttrib3sv) +#define glVertexAttrib4Nbv XGL_FUNCPTR(glVertexAttrib4Nbv) +#define glVertexAttrib4Niv XGL_FUNCPTR(glVertexAttrib4Niv) +#define glVertexAttrib4Nsv XGL_FUNCPTR(glVertexAttrib4Nsv) +#define glVertexAttrib4Nub XGL_FUNCPTR(glVertexAttrib4Nub) +#define glVertexAttrib4Nubv XGL_FUNCPTR(glVertexAttrib4Nubv) +#define glVertexAttrib4Nuiv XGL_FUNCPTR(glVertexAttrib4Nuiv) +#define glVertexAttrib4Nusv XGL_FUNCPTR(glVertexAttrib4Nusv) +#define glVertexAttrib4bv XGL_FUNCPTR(glVertexAttrib4bv) +#define glVertexAttrib4d XGL_FUNCPTR(glVertexAttrib4d) +#define glVertexAttrib4dv XGL_FUNCPTR(glVertexAttrib4dv) +#define glVertexAttrib4f XGL_FUNCPTR(glVertexAttrib4f) +#define glVertexAttrib4fv XGL_FUNCPTR(glVertexAttrib4fv) +#define glVertexAttrib4iv XGL_FUNCPTR(glVertexAttrib4iv) +#define glVertexAttrib4s XGL_FUNCPTR(glVertexAttrib4s) +#define glVertexAttrib4sv XGL_FUNCPTR(glVertexAttrib4sv) +#define glVertexAttrib4ubv XGL_FUNCPTR(glVertexAttrib4ubv) +#define glVertexAttrib4uiv XGL_FUNCPTR(glVertexAttrib4uiv) +#define glVertexAttrib4usv XGL_FUNCPTR(glVertexAttrib4usv) +#define glVertexAttribPointer XGL_FUNCPTR(glVertexAttribPointer) +#endif + +#ifdef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifdef GL_3DFX_tbuffer +#define glTbufferMask3DFX XGL_FUNCPTR(glTbufferMask3DFX) +#endif + +#ifdef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifdef GL_APPLE_client_storage +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif + +#ifdef GL_APPLE_element_array +#define GL_ELEMENT_ARRAY_APPLE 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x876A +#define glDrawElementArrayAPPLE XGL_FUNCPTR(glDrawElementArrayAPPLE) +#define glDrawRangeElementArrayAPPLE XGL_FUNCPTR(glDrawRangeElementArrayAPPLE) +#define glElementPointerAPPLE XGL_FUNCPTR(glElementPointerAPPLE) +#define glMultiDrawElementArrayAPPLE XGL_FUNCPTR(glMultiDrawElementArrayAPPLE) +#define glMultiDrawRangeElementArrayAPPLE XGL_FUNCPTR(glMultiDrawRangeElementArrayAPPLE) +#endif + +#ifdef GL_APPLE_fence +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +#define glDeleteFencesAPPLE XGL_FUNCPTR(glDeleteFencesAPPLE) +#define glFinishFenceAPPLE XGL_FUNCPTR(glFinishFenceAPPLE) +#define glFinishObjectAPPLE XGL_FUNCPTR(glFinishObjectAPPLE) +#define glGenFencesAPPLE XGL_FUNCPTR(glGenFencesAPPLE) +#define glIsFenceAPPLE XGL_FUNCPTR(glIsFenceAPPLE) +#define glSetFenceAPPLE XGL_FUNCPTR(glSetFenceAPPLE) +#define glTestFenceAPPLE XGL_FUNCPTR(glTestFenceAPPLE) +#define glTestObjectAPPLE XGL_FUNCPTR(glTestObjectAPPLE) +#endif + +#ifdef GL_APPLE_float_pixels +#define GL_HALF_APPLE 0x140B +#define GL_COLOR_FLOAT_APPLE 0x8A0F +#define GL_RGBA_FLOAT32_APPLE 0x8814 +#define GL_RGB_FLOAT32_APPLE 0x8815 +#define GL_ALPHA_FLOAT32_APPLE 0x8816 +#define GL_INTENSITY_FLOAT32_APPLE 0x8817 +#define GL_LUMINANCE_FLOAT32_APPLE 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_APPLE 0x8819 +#define GL_RGBA_FLOAT16_APPLE 0x881A +#define GL_RGB_FLOAT16_APPLE 0x881B +#define GL_ALPHA_FLOAT16_APPLE 0x881C +#define GL_INTENSITY_FLOAT16_APPLE 0x881D +#define GL_LUMINANCE_FLOAT16_APPLE 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_APPLE 0x881F +#endif + +#ifdef GL_APPLE_pixel_buffer +#define GL_MIN_PBUFFER_VIEWPORT_DIMS_APPLE 0x8A10 +#endif + +#ifdef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifdef GL_APPLE_texture_range +#define GL_TEXTURE_STORAGE_HINT_APPLE 0x85BC +#define GL_STORAGE_PRIVATE_APPLE 0x85BD +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#define GL_TEXTURE_RANGE_LENGTH_APPLE 0x85B7 +#define GL_TEXTURE_RANGE_POINTER_APPLE 0x85B8 +#define glTextureRangeAPPLE XGL_FUNCPTR(glTextureRangeAPPLE) +#define glGetTexParameterPointervAPPLE XGL_FUNCPTR(glGetTexParameterPointervAPPLE) +#endif + +#ifdef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifdef GL_APPLE_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +#define glBindVertexArrayAPPLE XGL_FUNCPTR(glBindVertexArrayAPPLE) +#define glDeleteVertexArraysAPPLE XGL_FUNCPTR(glDeleteVertexArraysAPPLE) +#define glGenVertexArraysAPPLE XGL_FUNCPTR(glGenVertexArraysAPPLE) +#define glIsVertexArrayAPPLE XGL_FUNCPTR(glIsVertexArrayAPPLE) +#endif + +#ifdef GL_APPLE_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_APPLE 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#define glFlushVertexArrayRangeAPPLE XGL_FUNCPTR(glFlushVertexArrayRangeAPPLE) +#define glVertexArrayParameteriAPPLE XGL_FUNCPTR(glVertexArrayParameteriAPPLE) +#define glVertexArrayRangeAPPLE XGL_FUNCPTR(glVertexArrayRangeAPPLE) +#endif + +#ifdef GL_APPLE_ycbcr_422 +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#endif + +#ifdef GL_ARB_color_buffer_float +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +#define glClampColorARB XGL_FUNCPTR(glClampColorARB) +#endif + +#ifdef GL_ARB_depth_texture +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifdef GL_ARB_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +#define glDrawBuffersARB XGL_FUNCPTR(glDrawBuffersARB) +#endif + +#ifdef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +#ifdef GL_ARB_fragment_program_shadow +#endif + +#ifdef GL_ARB_fragment_shader +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif + +#ifdef GL_ARB_half_float_pixel +#define GL_HALF_FLOAT_ARB 0x140B +#endif + +#ifdef GL_ARB_imaging +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_IGNORE_BORDER 0x8150 +#define GL_CONSTANT_BORDER 0x8151 +#define GL_WRAP_BORDER 0x8152 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#define glColorTable XGL_FUNCPTR(glColorTable) +#define glColorSubTable XGL_FUNCPTR(glColorSubTable) +#define glColorTableParameteriv XGL_FUNCPTR(glColorTableParameteriv) +#define glColorTableParameterfv XGL_FUNCPTR(glColorTableParameterfv) +#define glCopyColorSubTable XGL_FUNCPTR(glCopyColorSubTable) +#define glCopyColorTable XGL_FUNCPTR(glCopyColorTable) +#define glGetColorTable XGL_FUNCPTR(glGetColorTable) +#define glGetColorTableParameterfv XGL_FUNCPTR(glGetColorTableParameterfv) +#define glGetColorTableParameteriv XGL_FUNCPTR(glGetColorTableParameteriv) +#define glHistogram XGL_FUNCPTR(glHistogram) +#define glResetHistogram XGL_FUNCPTR(glResetHistogram) +#define glGetHistogram XGL_FUNCPTR(glGetHistogram) +#define glGetHistogramParameterfv XGL_FUNCPTR(glGetHistogramParameterfv) +#define glGetHistogramParameteriv XGL_FUNCPTR(glGetHistogramParameteriv) +#define glMinmax XGL_FUNCPTR(glMinmax) +#define glResetMinmax XGL_FUNCPTR(glResetMinmax) +#define glGetMinmaxParameterfv XGL_FUNCPTR(glGetMinmaxParameterfv) +#define glGetMinmaxParameteriv XGL_FUNCPTR(glGetMinmaxParameteriv) +#define glConvolutionFilter1D XGL_FUNCPTR(glConvolutionFilter1D) +#define glConvolutionFilter2D XGL_FUNCPTR(glConvolutionFilter2D) +#define glConvolutionParameterf XGL_FUNCPTR(glConvolutionParameterf) +#define glConvolutionParameterfv XGL_FUNCPTR(glConvolutionParameterfv) +#define glConvolutionParameteri XGL_FUNCPTR(glConvolutionParameteri) +#define glConvolutionParameteriv XGL_FUNCPTR(glConvolutionParameteriv) +#define glCopyConvolutionFilter1D XGL_FUNCPTR(glCopyConvolutionFilter1D) +#define glCopyConvolutionFilter2D XGL_FUNCPTR(glCopyConvolutionFilter2D) +#define glGetConvolutionFilter XGL_FUNCPTR(glGetConvolutionFilter) +#define glGetConvolutionParameterfv XGL_FUNCPTR(glGetConvolutionParameterfv) +#define glGetConvolutionParameteriv XGL_FUNCPTR(glGetConvolutionParameteriv) +#define glSeparableFilter2D XGL_FUNCPTR(glSeparableFilter2D) +#define glGetSeparableFilter XGL_FUNCPTR(glGetSeparableFilter) +#define glGetMinmax XGL_FUNCPTR(glGetMinmax) +#endif + +#ifdef GL_ARB_matrix_palette +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +#define glCurrentPaletteMatrixARB XGL_FUNCPTR(glCurrentPaletteMatrixARB) +#define glMatrixIndexPointerARB XGL_FUNCPTR(glMatrixIndexPointerARB) +#define glMatrixIndexubvARB XGL_FUNCPTR(glMatrixIndexubvARB) +#define glMatrixIndexusvARB XGL_FUNCPTR(glMatrixIndexusvARB) +#define glMatrixIndexuivARB XGL_FUNCPTR(glMatrixIndexuivARB) +#endif + +#ifdef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#define glSampleCoverageARB XGL_FUNCPTR(glSampleCoverageARB) +#endif + +#ifdef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#define glActiveTextureARB XGL_FUNCPTR(glActiveTextureARB) +#define glClientActiveTextureARB XGL_FUNCPTR(glClientActiveTextureARB) +#define glMultiTexCoord1dARB XGL_FUNCPTR(glMultiTexCoord1dARB) +#define glMultiTexCoord1dvARB XGL_FUNCPTR(glMultiTexCoord1dvARB) +#define glMultiTexCoord1fARB XGL_FUNCPTR(glMultiTexCoord1fARB) +#define glMultiTexCoord1fvARB XGL_FUNCPTR(glMultiTexCoord1fvARB) +#define glMultiTexCoord1iARB XGL_FUNCPTR(glMultiTexCoord1iARB) +#define glMultiTexCoord1ivARB XGL_FUNCPTR(glMultiTexCoord1ivARB) +#define glMultiTexCoord1sARB XGL_FUNCPTR(glMultiTexCoord1sARB) +#define glMultiTexCoord1svARB XGL_FUNCPTR(glMultiTexCoord1svARB) +#define glMultiTexCoord2dARB XGL_FUNCPTR(glMultiTexCoord2dARB) +#define glMultiTexCoord2dvARB XGL_FUNCPTR(glMultiTexCoord2dvARB) +#define glMultiTexCoord2fARB XGL_FUNCPTR(glMultiTexCoord2fARB) +#define glMultiTexCoord2fvARB XGL_FUNCPTR(glMultiTexCoord2fvARB) +#define glMultiTexCoord2iARB XGL_FUNCPTR(glMultiTexCoord2iARB) +#define glMultiTexCoord2ivARB XGL_FUNCPTR(glMultiTexCoord2ivARB) +#define glMultiTexCoord2sARB XGL_FUNCPTR(glMultiTexCoord2sARB) +#define glMultiTexCoord2svARB XGL_FUNCPTR(glMultiTexCoord2svARB) +#define glMultiTexCoord3dARB XGL_FUNCPTR(glMultiTexCoord3dARB) +#define glMultiTexCoord3dvARB XGL_FUNCPTR(glMultiTexCoord3dvARB) +#define glMultiTexCoord3fARB XGL_FUNCPTR(glMultiTexCoord3fARB) +#define glMultiTexCoord3fvARB XGL_FUNCPTR(glMultiTexCoord3fvARB) +#define glMultiTexCoord3iARB XGL_FUNCPTR(glMultiTexCoord3iARB) +#define glMultiTexCoord3ivARB XGL_FUNCPTR(glMultiTexCoord3ivARB) +#define glMultiTexCoord3sARB XGL_FUNCPTR(glMultiTexCoord3sARB) +#define glMultiTexCoord3svARB XGL_FUNCPTR(glMultiTexCoord3svARB) +#define glMultiTexCoord4dARB XGL_FUNCPTR(glMultiTexCoord4dARB) +#define glMultiTexCoord4dvARB XGL_FUNCPTR(glMultiTexCoord4dvARB) +#define glMultiTexCoord4fARB XGL_FUNCPTR(glMultiTexCoord4fARB) +#define glMultiTexCoord4fvARB XGL_FUNCPTR(glMultiTexCoord4fvARB) +#define glMultiTexCoord4iARB XGL_FUNCPTR(glMultiTexCoord4iARB) +#define glMultiTexCoord4ivARB XGL_FUNCPTR(glMultiTexCoord4ivARB) +#define glMultiTexCoord4sARB XGL_FUNCPTR(glMultiTexCoord4sARB) +#define glMultiTexCoord4svARB XGL_FUNCPTR(glMultiTexCoord4svARB) +#endif + +#ifdef GL_ARB_occlusion_query +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +#define glBeginQueryARB XGL_FUNCPTR(glBeginQueryARB) +#define glDeleteQueriesARB XGL_FUNCPTR(glDeleteQueriesARB) +#define glEndQueryARB XGL_FUNCPTR(glEndQueryARB) +#define glGenQueriesARB XGL_FUNCPTR(glGenQueriesARB) +#define glGetQueryObjectivARB XGL_FUNCPTR(glGetQueryObjectivARB) +#define glGetQueryObjectuivARB XGL_FUNCPTR(glGetQueryObjectuivARB) +#define glGetQueryivARB XGL_FUNCPTR(glGetQueryivARB) +#define glIsQueryARB XGL_FUNCPTR(glIsQueryARB) +#endif + +#ifdef GL_ARB_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif + +#ifdef GL_ARB_point_parameters +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +#define glPointParameterfARB XGL_FUNCPTR(glPointParameterfARB) +#define glPointParameterfvARB XGL_FUNCPTR(glPointParameterfvARB) +#endif + +#ifdef GL_ARB_point_sprite +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif + +#ifdef GL_ARB_shader_objects +typedef char GLcharARB; +typedef unsigned int GLhandleARB; +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#define glAttachObjectARB XGL_FUNCPTR(glAttachObjectARB) +#define glCompileShaderARB XGL_FUNCPTR(glCompileShaderARB) +#define glCreateProgramObjectARB XGL_FUNCPTR(glCreateProgramObjectARB) +#define glCreateShaderObjectARB XGL_FUNCPTR(glCreateShaderObjectARB) +#define glDeleteObjectARB XGL_FUNCPTR(glDeleteObjectARB) +#define glDetachObjectARB XGL_FUNCPTR(glDetachObjectARB) +#define glGetActiveUniformARB XGL_FUNCPTR(glGetActiveUniformARB) +#define glGetAttachedObjectsARB XGL_FUNCPTR(glGetAttachedObjectsARB) +#define glGetHandleARB XGL_FUNCPTR(glGetHandleARB) +#define glGetInfoLogARB XGL_FUNCPTR(glGetInfoLogARB) +#define glGetObjectParameterfvARB XGL_FUNCPTR(glGetObjectParameterfvARB) +#define glGetObjectParameterivARB XGL_FUNCPTR(glGetObjectParameterivARB) +#define glGetShaderSourceARB XGL_FUNCPTR(glGetShaderSourceARB) +#define glGetUniformLocationARB XGL_FUNCPTR(glGetUniformLocationARB) +#define glGetUniformfvARB XGL_FUNCPTR(glGetUniformfvARB) +#define glGetUniformivARB XGL_FUNCPTR(glGetUniformivARB) +#define glLinkProgramARB XGL_FUNCPTR(glLinkProgramARB) +#define glShaderSourceARB XGL_FUNCPTR(glShaderSourceARB) +#define glUniform1fARB XGL_FUNCPTR(glUniform1fARB) +#define glUniform1fvARB XGL_FUNCPTR(glUniform1fvARB) +#define glUniform1iARB XGL_FUNCPTR(glUniform1iARB) +#define glUniform1ivARB XGL_FUNCPTR(glUniform1ivARB) +#define glUniform2fARB XGL_FUNCPTR(glUniform2fARB) +#define glUniform2fvARB XGL_FUNCPTR(glUniform2fvARB) +#define glUniform2iARB XGL_FUNCPTR(glUniform2iARB) +#define glUniform2ivARB XGL_FUNCPTR(glUniform2ivARB) +#define glUniform3fARB XGL_FUNCPTR(glUniform3fARB) +#define glUniform3fvARB XGL_FUNCPTR(glUniform3fvARB) +#define glUniform3iARB XGL_FUNCPTR(glUniform3iARB) +#define glUniform3ivARB XGL_FUNCPTR(glUniform3ivARB) +#define glUniform4fARB XGL_FUNCPTR(glUniform4fARB) +#define glUniform4fvARB XGL_FUNCPTR(glUniform4fvARB) +#define glUniform4iARB XGL_FUNCPTR(glUniform4iARB) +#define glUniform4ivARB XGL_FUNCPTR(glUniform4ivARB) +#define glUniformMatrix2fvARB XGL_FUNCPTR(glUniformMatrix2fvARB) +#define glUniformMatrix3fvARB XGL_FUNCPTR(glUniformMatrix3fvARB) +#define glUniformMatrix4fvARB XGL_FUNCPTR(glUniformMatrix4fvARB) +#define glUseProgramObjectARB XGL_FUNCPTR(glUseProgramObjectARB) +#define glValidateProgramARB XGL_FUNCPTR(glValidateProgramARB) +#endif + +#ifdef GL_ARB_shading_language_100 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif + +#ifdef GL_ARB_shadow +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +#ifdef GL_ARB_shadow_ambient +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif + +#ifdef GL_ARB_texture_border_clamp +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif + +#ifdef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#define glCompressedTexImage1DARB XGL_FUNCPTR(glCompressedTexImage1DARB) +#define glCompressedTexImage2DARB XGL_FUNCPTR(glCompressedTexImage2DARB) +#define glCompressedTexImage3DARB XGL_FUNCPTR(glCompressedTexImage3DARB) +#define glCompressedTexSubImage1DARB XGL_FUNCPTR(glCompressedTexSubImage1DARB) +#define glCompressedTexSubImage2DARB XGL_FUNCPTR(glCompressedTexSubImage2DARB) +#define glCompressedTexSubImage3DARB XGL_FUNCPTR(glCompressedTexSubImage3DARB) +#define glGetCompressedTexImageARB XGL_FUNCPTR(glGetCompressedTexImageARB) +#endif + +#ifdef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifdef GL_ARB_texture_env_add +#endif + +#ifdef GL_ARB_texture_env_combine +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#endif + +#ifdef GL_ARB_texture_env_crossbar +#endif + +#ifdef GL_ARB_texture_env_dot3 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif + +#ifdef GL_ARB_texture_float +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#endif + +#ifdef GL_ARB_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif + +#ifdef GL_ARB_texture_non_power_of_two +#endif + +#ifdef GL_ARB_texture_rectangle +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif + +#ifdef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#define glLoadTransposeMatrixfARB XGL_FUNCPTR(glLoadTransposeMatrixfARB) +#define glLoadTransposeMatrixdARB XGL_FUNCPTR(glLoadTransposeMatrixdARB) +#define glMultTransposeMatrixfARB XGL_FUNCPTR(glMultTransposeMatrixfARB) +#define glMultTransposeMatrixdARB XGL_FUNCPTR(glMultTransposeMatrixdARB) +#endif + +#ifdef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#define glWeightbvARB XGL_FUNCPTR(glWeightbvARB) +#define glWeightsvARB XGL_FUNCPTR(glWeightsvARB) +#define glWeightivARB XGL_FUNCPTR(glWeightivARB) +#define glWeightfvARB XGL_FUNCPTR(glWeightfvARB) +#define glWeightdvARB XGL_FUNCPTR(glWeightdvARB) +#define glWeightubvARB XGL_FUNCPTR(glWeightubvARB) +#define glWeightusvARB XGL_FUNCPTR(glWeightusvARB) +#define glWeightuivARB XGL_FUNCPTR(glWeightuivARB) +#define glWeightPointerARB XGL_FUNCPTR(glWeightPointerARB) +#define glVertexBlendARB XGL_FUNCPTR(glVertexBlendARB) +#endif + +#ifdef GL_ARB_vertex_buffer_object +typedef ptrdiff_t GLsizeiptrARB; +typedef ptrdiff_t GLintptrARB; +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#define glBindBufferARB XGL_FUNCPTR(glBindBufferARB) +#define glBufferDataARB XGL_FUNCPTR(glBufferDataARB) +#define glBufferSubDataARB XGL_FUNCPTR(glBufferSubDataARB) +#define glDeleteBuffersARB XGL_FUNCPTR(glDeleteBuffersARB) +#define glGenBuffersARB XGL_FUNCPTR(glGenBuffersARB) +#define glGetBufferParameterivARB XGL_FUNCPTR(glGetBufferParameterivARB) +#define glGetBufferPointervARB XGL_FUNCPTR(glGetBufferPointervARB) +#define glGetBufferSubDataARB XGL_FUNCPTR(glGetBufferSubDataARB) +#define glIsBufferARB XGL_FUNCPTR(glIsBufferARB) +#define glMapBufferARB XGL_FUNCPTR(glMapBufferARB) +#define glUnmapBufferARB XGL_FUNCPTR(glUnmapBufferARB) +#endif + +#ifdef GL_ARB_vertex_program +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#define glBindProgramARB XGL_FUNCPTR(glBindProgramARB) +#define glDeleteProgramsARB XGL_FUNCPTR(glDeleteProgramsARB) +#define glDisableVertexAttribArrayARB XGL_FUNCPTR(glDisableVertexAttribArrayARB) +#define glEnableVertexAttribArrayARB XGL_FUNCPTR(glEnableVertexAttribArrayARB) +#define glGenProgramsARB XGL_FUNCPTR(glGenProgramsARB) +#define glGetProgramEnvParameterdvARB XGL_FUNCPTR(glGetProgramEnvParameterdvARB) +#define glGetProgramEnvParameterfvARB XGL_FUNCPTR(glGetProgramEnvParameterfvARB) +#define glGetProgramLocalParameterdvARB XGL_FUNCPTR(glGetProgramLocalParameterdvARB) +#define glGetProgramLocalParameterfvARB XGL_FUNCPTR(glGetProgramLocalParameterfvARB) +#define glGetProgramStringARB XGL_FUNCPTR(glGetProgramStringARB) +#define glGetProgramivARB XGL_FUNCPTR(glGetProgramivARB) +#define glGetVertexAttribPointervARB XGL_FUNCPTR(glGetVertexAttribPointervARB) +#define glGetVertexAttribdvARB XGL_FUNCPTR(glGetVertexAttribdvARB) +#define glGetVertexAttribfvARB XGL_FUNCPTR(glGetVertexAttribfvARB) +#define glGetVertexAttribivARB XGL_FUNCPTR(glGetVertexAttribivARB) +#define glIsProgramARB XGL_FUNCPTR(glIsProgramARB) +#define glProgramEnvParameter4dARB XGL_FUNCPTR(glProgramEnvParameter4dARB) +#define glProgramEnvParameter4dvARB XGL_FUNCPTR(glProgramEnvParameter4dvARB) +#define glProgramEnvParameter4fARB XGL_FUNCPTR(glProgramEnvParameter4fARB) +#define glProgramEnvParameter4fvARB XGL_FUNCPTR(glProgramEnvParameter4fvARB) +#define glProgramLocalParameter4dARB XGL_FUNCPTR(glProgramLocalParameter4dARB) +#define glProgramLocalParameter4dvARB XGL_FUNCPTR(glProgramLocalParameter4dvARB) +#define glProgramLocalParameter4fARB XGL_FUNCPTR(glProgramLocalParameter4fARB) +#define glProgramLocalParameter4fvARB XGL_FUNCPTR(glProgramLocalParameter4fvARB) +#define glProgramStringARB XGL_FUNCPTR(glProgramStringARB) +#define glVertexAttrib1dARB XGL_FUNCPTR(glVertexAttrib1dARB) +#define glVertexAttrib1dvARB XGL_FUNCPTR(glVertexAttrib1dvARB) +#define glVertexAttrib1fARB XGL_FUNCPTR(glVertexAttrib1fARB) +#define glVertexAttrib1fvARB XGL_FUNCPTR(glVertexAttrib1fvARB) +#define glVertexAttrib1sARB XGL_FUNCPTR(glVertexAttrib1sARB) +#define glVertexAttrib1svARB XGL_FUNCPTR(glVertexAttrib1svARB) +#define glVertexAttrib2dARB XGL_FUNCPTR(glVertexAttrib2dARB) +#define glVertexAttrib2dvARB XGL_FUNCPTR(glVertexAttrib2dvARB) +#define glVertexAttrib2fARB XGL_FUNCPTR(glVertexAttrib2fARB) +#define glVertexAttrib2fvARB XGL_FUNCPTR(glVertexAttrib2fvARB) +#define glVertexAttrib2sARB XGL_FUNCPTR(glVertexAttrib2sARB) +#define glVertexAttrib2svARB XGL_FUNCPTR(glVertexAttrib2svARB) +#define glVertexAttrib3dARB XGL_FUNCPTR(glVertexAttrib3dARB) +#define glVertexAttrib3dvARB XGL_FUNCPTR(glVertexAttrib3dvARB) +#define glVertexAttrib3fARB XGL_FUNCPTR(glVertexAttrib3fARB) +#define glVertexAttrib3fvARB XGL_FUNCPTR(glVertexAttrib3fvARB) +#define glVertexAttrib3sARB XGL_FUNCPTR(glVertexAttrib3sARB) +#define glVertexAttrib3svARB XGL_FUNCPTR(glVertexAttrib3svARB) +#define glVertexAttrib4NbvARB XGL_FUNCPTR(glVertexAttrib4NbvARB) +#define glVertexAttrib4NivARB XGL_FUNCPTR(glVertexAttrib4NivARB) +#define glVertexAttrib4NsvARB XGL_FUNCPTR(glVertexAttrib4NsvARB) +#define glVertexAttrib4NubARB XGL_FUNCPTR(glVertexAttrib4NubARB) +#define glVertexAttrib4NubvARB XGL_FUNCPTR(glVertexAttrib4NubvARB) +#define glVertexAttrib4NuivARB XGL_FUNCPTR(glVertexAttrib4NuivARB) +#define glVertexAttrib4NusvARB XGL_FUNCPTR(glVertexAttrib4NusvARB) +#define glVertexAttrib4bvARB XGL_FUNCPTR(glVertexAttrib4bvARB) +#define glVertexAttrib4dARB XGL_FUNCPTR(glVertexAttrib4dARB) +#define glVertexAttrib4dvARB XGL_FUNCPTR(glVertexAttrib4dvARB) +#define glVertexAttrib4fARB XGL_FUNCPTR(glVertexAttrib4fARB) +#define glVertexAttrib4fvARB XGL_FUNCPTR(glVertexAttrib4fvARB) +#define glVertexAttrib4ivARB XGL_FUNCPTR(glVertexAttrib4ivARB) +#define glVertexAttrib4sARB XGL_FUNCPTR(glVertexAttrib4sARB) +#define glVertexAttrib4svARB XGL_FUNCPTR(glVertexAttrib4svARB) +#define glVertexAttrib4ubvARB XGL_FUNCPTR(glVertexAttrib4ubvARB) +#define glVertexAttrib4uivARB XGL_FUNCPTR(glVertexAttrib4uivARB) +#define glVertexAttrib4usvARB XGL_FUNCPTR(glVertexAttrib4usvARB) +#define glVertexAttribPointerARB XGL_FUNCPTR(glVertexAttribPointerARB) +#endif + +#ifdef GL_ARB_vertex_shader +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#define glBindAttribLocationARB XGL_FUNCPTR(glBindAttribLocationARB) +#define glGetActiveAttribARB XGL_FUNCPTR(glGetActiveAttribARB) +#define glGetAttribLocationARB XGL_FUNCPTR(glGetAttribLocationARB) +#endif + +#ifdef GL_ARB_window_pos +#define glWindowPos2dARB XGL_FUNCPTR(glWindowPos2dARB) +#define glWindowPos2dvARB XGL_FUNCPTR(glWindowPos2dvARB) +#define glWindowPos2fARB XGL_FUNCPTR(glWindowPos2fARB) +#define glWindowPos2fvARB XGL_FUNCPTR(glWindowPos2fvARB) +#define glWindowPos2iARB XGL_FUNCPTR(glWindowPos2iARB) +#define glWindowPos2ivARB XGL_FUNCPTR(glWindowPos2ivARB) +#define glWindowPos2sARB XGL_FUNCPTR(glWindowPos2sARB) +#define glWindowPos2svARB XGL_FUNCPTR(glWindowPos2svARB) +#define glWindowPos3dARB XGL_FUNCPTR(glWindowPos3dARB) +#define glWindowPos3dvARB XGL_FUNCPTR(glWindowPos3dvARB) +#define glWindowPos3fARB XGL_FUNCPTR(glWindowPos3fARB) +#define glWindowPos3fvARB XGL_FUNCPTR(glWindowPos3fvARB) +#define glWindowPos3iARB XGL_FUNCPTR(glWindowPos3iARB) +#define glWindowPos3ivARB XGL_FUNCPTR(glWindowPos3ivARB) +#define glWindowPos3sARB XGL_FUNCPTR(glWindowPos3sARB) +#define glWindowPos3svARB XGL_FUNCPTR(glWindowPos3svARB) +#endif + +#ifdef GL_ATIX_point_sprites +#define GL_TEXTURE_POINT_MODE_ATIX 0x60b0 +#define GL_TEXTURE_POINT_ONE_COORD_ATIX 0x60b1 +#define GL_TEXTURE_POINT_SPRITE_ATIX 0x60b2 +#define GL_POINT_SPRITE_CULL_MODE_ATIX 0x60b3 +#define GL_POINT_SPRITE_CULL_CENTER_ATIX 0x60b4 +#define GL_POINT_SPRITE_CULL_CLIP_ATIX 0x60b5 +#endif + +#ifdef GL_ATIX_texture_env_combine3 +#define GL_MODULATE_ADD_ATIX 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATIX 0x8745 +#define GL_MODULATE_SUBTRACT_ATIX 0x8746 +#endif + +#ifdef GL_ATIX_texture_env_route +#define GL_SECONDARY_COLOR_ATIX 0x8747 +#define GL_TEXTURE_OUTPUT_RGB_ATIX 0x8748 +#define GL_TEXTURE_OUTPUT_ALPHA_ATIX 0x8749 +#endif + +#ifdef GL_ATIX_vertex_shader_output_point_size +#define GL_OUTPUT_POINT_SIZE_ATIX 0x610E +#endif + +#ifdef GL_ATI_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +#define glDrawBuffersATI XGL_FUNCPTR(glDrawBuffersATI) +#endif + +#ifdef GL_ATI_element_array +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +#define glDrawElementArrayATI XGL_FUNCPTR(glDrawElementArrayATI) +#define glDrawRangeElementArrayATI XGL_FUNCPTR(glDrawRangeElementArrayATI) +#define glElementPointerATI XGL_FUNCPTR(glElementPointerATI) +#endif + +#ifdef GL_ATI_envmap_bumpmap +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +#define glTexBumpParameterivATI XGL_FUNCPTR(glTexBumpParameterivATI) +#define glTexBumpParameterfvATI XGL_FUNCPTR(glTexBumpParameterfvATI) +#define glGetTexBumpParameterivATI XGL_FUNCPTR(glGetTexBumpParameterivATI) +#define glGetTexBumpParameterfvATI XGL_FUNCPTR(glGetTexBumpParameterfvATI) +#endif + +#ifdef GL_ATI_fragment_shader +#define GL_RED_BIT_ATI 0x00000001 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define glAlphaFragmentOp1ATI XGL_FUNCPTR(glAlphaFragmentOp1ATI) +#define glAlphaFragmentOp2ATI XGL_FUNCPTR(glAlphaFragmentOp2ATI) +#define glAlphaFragmentOp3ATI XGL_FUNCPTR(glAlphaFragmentOp3ATI) +#define glBeginFragmentShaderATI XGL_FUNCPTR(glBeginFragmentShaderATI) +#define glBindFragmentShaderATI XGL_FUNCPTR(glBindFragmentShaderATI) +#define glColorFragmentOp1ATI XGL_FUNCPTR(glColorFragmentOp1ATI) +#define glColorFragmentOp2ATI XGL_FUNCPTR(glColorFragmentOp2ATI) +#define glColorFragmentOp3ATI XGL_FUNCPTR(glColorFragmentOp3ATI) +#define glDeleteFragmentShaderATI XGL_FUNCPTR(glDeleteFragmentShaderATI) +#define glEndFragmentShaderATI XGL_FUNCPTR(glEndFragmentShaderATI) +#define glGenFragmentShadersATI XGL_FUNCPTR(glGenFragmentShadersATI) +#define glPassTexCoordATI XGL_FUNCPTR(glPassTexCoordATI) +#define glSampleMapATI XGL_FUNCPTR(glSampleMapATI) +#define glSetFragmentShaderConstantATI XGL_FUNCPTR(glSetFragmentShaderConstantATI) +#endif + +#ifdef GL_ATI_map_object_buffer +#define glMapObjectBufferATI XGL_FUNCPTR(glMapObjectBufferATI) +#define glUnmapObjectBufferATI XGL_FUNCPTR(glUnmapObjectBufferATI) +#endif + +#ifdef GL_ATI_pn_triangles +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#define glPNTrianglesiATI XGL_FUNCPTR(glPNTrianglesiATI) +#define glPNTrianglesfATI XGL_FUNCPTR(glPNTrianglesfATI) +#endif + +#ifdef GL_ATI_separate_stencil +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +#define glStencilOpSeparateATI XGL_FUNCPTR(glStencilOpSeparateATI) +#define glStencilFuncSeparateATI XGL_FUNCPTR(glStencilFuncSeparateATI) +#endif + +#ifdef GL_ATI_text_fragment_shader +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif + +#ifdef GL_ATI_texture_compression_3dc +#define GL_COMPRESSED_RGB_3DC_ATI 0x8837 +#endif + +#ifdef GL_ATI_texture_env_combine3 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif + +#ifdef GL_ATI_texture_float +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif + +#ifdef GL_ATI_texture_mirror_once +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif + +#ifdef GL_ATI_vertex_array_object +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +#define glArrayObjectATI XGL_FUNCPTR(glArrayObjectATI) +#define glFreeObjectBufferATI XGL_FUNCPTR(glFreeObjectBufferATI) +#define glGetArrayObjectfvATI XGL_FUNCPTR(glGetArrayObjectfvATI) +#define glGetArrayObjectivATI XGL_FUNCPTR(glGetArrayObjectivATI) +#define glGetObjectBufferfvATI XGL_FUNCPTR(glGetObjectBufferfvATI) +#define glGetObjectBufferivATI XGL_FUNCPTR(glGetObjectBufferivATI) +#define glGetVariantArrayObjectfvATI XGL_FUNCPTR(glGetVariantArrayObjectfvATI) +#define glGetVariantArrayObjectivATI XGL_FUNCPTR(glGetVariantArrayObjectivATI) +#define glIsObjectBufferATI XGL_FUNCPTR(glIsObjectBufferATI) +#define glNewObjectBufferATI XGL_FUNCPTR(glNewObjectBufferATI) +#define glUpdateObjectBufferATI XGL_FUNCPTR(glUpdateObjectBufferATI) +#define glVariantArrayObjectATI XGL_FUNCPTR(glVariantArrayObjectATI) +#endif + +#ifdef GL_ATI_vertex_attrib_array_object +#define glGetVertexAttribArrayObjectfvATI XGL_FUNCPTR(glGetVertexAttribArrayObjectfvATI) +#define glGetVertexAttribArrayObjectivATI XGL_FUNCPTR(glGetVertexAttribArrayObjectivATI) +#define glVertexAttribArrayObjectATI XGL_FUNCPTR(glVertexAttribArrayObjectATI) +#endif + +#ifdef GL_ATI_vertex_streams +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_SOURCE_ATI 0x876C +#define GL_VERTEX_STREAM0_ATI 0x876D +#define GL_VERTEX_STREAM1_ATI 0x876E +#define GL_VERTEX_STREAM2_ATI 0x876F +#define GL_VERTEX_STREAM3_ATI 0x8770 +#define GL_VERTEX_STREAM4_ATI 0x8771 +#define GL_VERTEX_STREAM5_ATI 0x8772 +#define GL_VERTEX_STREAM6_ATI 0x8773 +#define GL_VERTEX_STREAM7_ATI 0x8774 +#define glClientActiveVertexStreamATI XGL_FUNCPTR(glClientActiveVertexStreamATI) +#define glVertexBlendEnviATI XGL_FUNCPTR(glVertexBlendEnviATI) +#define glVertexBlendEnvfATI XGL_FUNCPTR(glVertexBlendEnvfATI) +#define glVertexStream2sATI XGL_FUNCPTR(glVertexStream2sATI) +#define glVertexStream2svATI XGL_FUNCPTR(glVertexStream2svATI) +#define glVertexStream2iATI XGL_FUNCPTR(glVertexStream2iATI) +#define glVertexStream2ivATI XGL_FUNCPTR(glVertexStream2ivATI) +#define glVertexStream2fATI XGL_FUNCPTR(glVertexStream2fATI) +#define glVertexStream2fvATI XGL_FUNCPTR(glVertexStream2fvATI) +#define glVertexStream2dATI XGL_FUNCPTR(glVertexStream2dATI) +#define glVertexStream2dvATI XGL_FUNCPTR(glVertexStream2dvATI) +#define glVertexStream3sATI XGL_FUNCPTR(glVertexStream3sATI) +#define glVertexStream3svATI XGL_FUNCPTR(glVertexStream3svATI) +#define glVertexStream3iATI XGL_FUNCPTR(glVertexStream3iATI) +#define glVertexStream3ivATI XGL_FUNCPTR(glVertexStream3ivATI) +#define glVertexStream3fATI XGL_FUNCPTR(glVertexStream3fATI) +#define glVertexStream3fvATI XGL_FUNCPTR(glVertexStream3fvATI) +#define glVertexStream3dATI XGL_FUNCPTR(glVertexStream3dATI) +#define glVertexStream3dvATI XGL_FUNCPTR(glVertexStream3dvATI) +#define glVertexStream4sATI XGL_FUNCPTR(glVertexStream4sATI) +#define glVertexStream4svATI XGL_FUNCPTR(glVertexStream4svATI) +#define glVertexStream4iATI XGL_FUNCPTR(glVertexStream4iATI) +#define glVertexStream4ivATI XGL_FUNCPTR(glVertexStream4ivATI) +#define glVertexStream4fATI XGL_FUNCPTR(glVertexStream4fATI) +#define glVertexStream4fvATI XGL_FUNCPTR(glVertexStream4fvATI) +#define glVertexStream4dATI XGL_FUNCPTR(glVertexStream4dATI) +#define glVertexStream4dvATI XGL_FUNCPTR(glVertexStream4dvATI) +#define glNormalStream3bATI XGL_FUNCPTR(glNormalStream3bATI) +#define glNormalStream3bvATI XGL_FUNCPTR(glNormalStream3bvATI) +#define glNormalStream3sATI XGL_FUNCPTR(glNormalStream3sATI) +#define glNormalStream3svATI XGL_FUNCPTR(glNormalStream3svATI) +#define glNormalStream3iATI XGL_FUNCPTR(glNormalStream3iATI) +#define glNormalStream3ivATI XGL_FUNCPTR(glNormalStream3ivATI) +#define glNormalStream3fATI XGL_FUNCPTR(glNormalStream3fATI) +#define glNormalStream3fvATI XGL_FUNCPTR(glNormalStream3fvATI) +#define glNormalStream3dATI XGL_FUNCPTR(glNormalStream3dATI) +#define glNormalStream3dvATI XGL_FUNCPTR(glNormalStream3dvATI) +#endif + +#ifdef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifdef GL_EXT_Cg_shader +#define GL_CG_VERTEX_SHADER_EXT 0x890E +#define GL_CG_FRAGMENT_SHADER_EXT 0x890F +#endif + +#ifdef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifdef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifdef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#define glBlendColorEXT XGL_FUNCPTR(glBlendColorEXT) +#endif + +#ifdef GL_EXT_blend_equation_separate +#define GL_BLEND_EQUATION_RGB_EXT 0x8009 +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +#define glBlendEquationSeparateEXT XGL_FUNCPTR(glBlendEquationSeparateEXT) +#endif + +#ifdef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#define glBlendFuncSeparateEXT XGL_FUNCPTR(glBlendFuncSeparateEXT) +#endif + +#ifdef GL_EXT_blend_logic_op +#endif + +#ifdef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#define glBlendEquationEXT XGL_FUNCPTR(glBlendEquationEXT) +#endif + +#ifdef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifdef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifdef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifdef GL_EXT_color_subtable +#define glColorSubTableEXT XGL_FUNCPTR(glColorSubTableEXT) +#define glCopyColorSubTableEXT XGL_FUNCPTR(glCopyColorSubTableEXT) +#endif + +#ifdef GL_EXT_compiled_vertex_array +#define glLockArraysEXT XGL_FUNCPTR(glLockArraysEXT) +#define glUnlockArraysEXT XGL_FUNCPTR(glUnlockArraysEXT) +#endif + +#ifdef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#define glConvolutionFilter1DEXT XGL_FUNCPTR(glConvolutionFilter1DEXT) +#define glConvolutionFilter2DEXT XGL_FUNCPTR(glConvolutionFilter2DEXT) +#define glConvolutionParameterfEXT XGL_FUNCPTR(glConvolutionParameterfEXT) +#define glConvolutionParameterfvEXT XGL_FUNCPTR(glConvolutionParameterfvEXT) +#define glConvolutionParameteriEXT XGL_FUNCPTR(glConvolutionParameteriEXT) +#define glConvolutionParameterivEXT XGL_FUNCPTR(glConvolutionParameterivEXT) +#define glCopyConvolutionFilter1DEXT XGL_FUNCPTR(glCopyConvolutionFilter1DEXT) +#define glCopyConvolutionFilter2DEXT XGL_FUNCPTR(glCopyConvolutionFilter2DEXT) +#define glGetConvolutionFilterEXT XGL_FUNCPTR(glGetConvolutionFilterEXT) +#define glGetConvolutionParameterfvEXT XGL_FUNCPTR(glGetConvolutionParameterfvEXT) +#define glGetConvolutionParameterivEXT XGL_FUNCPTR(glGetConvolutionParameterivEXT) +#define glGetSeparableFilterEXT XGL_FUNCPTR(glGetSeparableFilterEXT) +#define glSeparableFilter2DEXT XGL_FUNCPTR(glSeparableFilter2DEXT) +#endif + +#ifdef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#define glBinormalPointerEXT XGL_FUNCPTR(glBinormalPointerEXT) +#define glTangentPointerEXT XGL_FUNCPTR(glTangentPointerEXT) +#endif + +#ifdef GL_EXT_copy_texture +#define glCopyTexImage1DEXT XGL_FUNCPTR(glCopyTexImage1DEXT) +#define glCopyTexImage2DEXT XGL_FUNCPTR(glCopyTexImage2DEXT) +#define glCopyTexSubImage1DEXT XGL_FUNCPTR(glCopyTexSubImage1DEXT) +#define glCopyTexSubImage2DEXT XGL_FUNCPTR(glCopyTexSubImage2DEXT) +#define glCopyTexSubImage3DEXT XGL_FUNCPTR(glCopyTexSubImage3DEXT) +#endif + +#ifdef GL_EXT_cull_vertex +#define glCullParameterdvEXT XGL_FUNCPTR(glCullParameterdvEXT) +#define glCullParameterfvEXT XGL_FUNCPTR(glCullParameterfvEXT) +#endif + +#ifdef GL_EXT_depth_bounds_test +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +#define glDepthBoundsEXT XGL_FUNCPTR(glDepthBoundsEXT) +#endif + +#ifdef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define glDrawRangeElementsEXT XGL_FUNCPTR(glDrawRangeElementsEXT) +#endif + +#ifdef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#define glFogCoordfEXT XGL_FUNCPTR(glFogCoordfEXT) +#define glFogCoordfvEXT XGL_FUNCPTR(glFogCoordfvEXT) +#define glFogCoorddEXT XGL_FUNCPTR(glFogCoorddEXT) +#define glFogCoorddvEXT XGL_FUNCPTR(glFogCoorddvEXT) +#define glFogCoordPointerEXT XGL_FUNCPTR(glFogCoordPointerEXT) +#endif + +#ifdef GL_EXT_fragment_lighting +#define GL_FRAGMENT_LIGHTING_EXT 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_EXT 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_EXT 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_EXT 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_EXT 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_EXT 0x8405 +#define GL_CURRENT_RASTER_NORMAL_EXT 0x8406 +#define GL_LIGHT_ENV_MODE_EXT 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_EXT 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_EXT 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_EXT 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_EXT 0x840B +#define GL_FRAGMENT_LIGHT0_EXT 0x840C +#define GL_FRAGMENT_LIGHT7_EXT 0x8413 +#define glFragmentColorMaterialEXT XGL_FUNCPTR(glFragmentColorMaterialEXT) +#define glFragmentLightModelfEXT XGL_FUNCPTR(glFragmentLightModelfEXT) +#define glFragmentLightModelfvEXT XGL_FUNCPTR(glFragmentLightModelfvEXT) +#define glFragmentLightModeliEXT XGL_FUNCPTR(glFragmentLightModeliEXT) +#define glFragmentLightModelivEXT XGL_FUNCPTR(glFragmentLightModelivEXT) +#define glFragmentLightfEXT XGL_FUNCPTR(glFragmentLightfEXT) +#define glFragmentLightfvEXT XGL_FUNCPTR(glFragmentLightfvEXT) +#define glFragmentLightiEXT XGL_FUNCPTR(glFragmentLightiEXT) +#define glFragmentLightivEXT XGL_FUNCPTR(glFragmentLightivEXT) +#define glFragmentMaterialfEXT XGL_FUNCPTR(glFragmentMaterialfEXT) +#define glFragmentMaterialfvEXT XGL_FUNCPTR(glFragmentMaterialfvEXT) +#define glFragmentMaterialiEXT XGL_FUNCPTR(glFragmentMaterialiEXT) +#define glFragmentMaterialivEXT XGL_FUNCPTR(glFragmentMaterialivEXT) +#define glGetFragmentLightfvEXT XGL_FUNCPTR(glGetFragmentLightfvEXT) +#define glGetFragmentLightivEXT XGL_FUNCPTR(glGetFragmentLightivEXT) +#define glGetFragmentMaterialfvEXT XGL_FUNCPTR(glGetFragmentMaterialfvEXT) +#define glGetFragmentMaterialivEXT XGL_FUNCPTR(glGetFragmentMaterialivEXT) +#define glLightEnviEXT XGL_FUNCPTR(glLightEnviEXT) +#endif + +#ifdef GL_EXT_framebuffer_blit +#define GL_READ_FRAMEBUFFER_EXT 0x8CA8 +#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9 +#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA +#define glBlitFramebufferEXT XGL_FUNCPTR(glBlitFramebufferEXT) +#endif + +#ifdef GL_EXT_framebuffer_object +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT 0x8CD8 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_FRAMEBUFFER_STATUS_ERROR_EXT 0x8CDE +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX_EXT 0x8D45 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define glBindFramebufferEXT XGL_FUNCPTR(glBindFramebufferEXT) +#define glBindRenderbufferEXT XGL_FUNCPTR(glBindRenderbufferEXT) +#define glCheckFramebufferStatusEXT XGL_FUNCPTR(glCheckFramebufferStatusEXT) +#define glDeleteFramebuffersEXT XGL_FUNCPTR(glDeleteFramebuffersEXT) +#define glDeleteRenderbuffersEXT XGL_FUNCPTR(glDeleteRenderbuffersEXT) +#define glFramebufferRenderbufferEXT XGL_FUNCPTR(glFramebufferRenderbufferEXT) +#define glFramebufferTexture1DEXT XGL_FUNCPTR(glFramebufferTexture1DEXT) +#define glFramebufferTexture2DEXT XGL_FUNCPTR(glFramebufferTexture2DEXT) +#define glFramebufferTexture3DEXT XGL_FUNCPTR(glFramebufferTexture3DEXT) +#define glGenFramebuffersEXT XGL_FUNCPTR(glGenFramebuffersEXT) +#define glGenRenderbuffersEXT XGL_FUNCPTR(glGenRenderbuffersEXT) +#define glGenerateMipmapEXT XGL_FUNCPTR(glGenerateMipmapEXT) +#define glGetFramebufferAttachmentParameterivEXT XGL_FUNCPTR(glGetFramebufferAttachmentParameterivEXT) +#define glGetRenderbufferParameterivEXT XGL_FUNCPTR(glGetRenderbufferParameterivEXT) +#define glIsFramebufferEXT XGL_FUNCPTR(glIsFramebufferEXT) +#define glIsRenderbufferEXT XGL_FUNCPTR(glIsRenderbufferEXT) +#define glRenderbufferStorageEXT XGL_FUNCPTR(glRenderbufferStorageEXT) +#endif + +#ifdef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define glGetHistogramEXT XGL_FUNCPTR(glGetHistogramEXT) +#define glGetHistogramParameterfvEXT XGL_FUNCPTR(glGetHistogramParameterfvEXT) +#define glGetHistogramParameterivEXT XGL_FUNCPTR(glGetHistogramParameterivEXT) +#define glGetMinmaxEXT XGL_FUNCPTR(glGetMinmaxEXT) +#define glGetMinmaxParameterfvEXT XGL_FUNCPTR(glGetMinmaxParameterfvEXT) +#define glGetMinmaxParameterivEXT XGL_FUNCPTR(glGetMinmaxParameterivEXT) +#define glHistogramEXT XGL_FUNCPTR(glHistogramEXT) +#define glMinmaxEXT XGL_FUNCPTR(glMinmaxEXT) +#define glResetHistogramEXT XGL_FUNCPTR(glResetHistogramEXT) +#define glResetMinmaxEXT XGL_FUNCPTR(glResetMinmaxEXT) +#endif + +#ifdef GL_EXT_index_array_formats +#endif + +#ifdef GL_EXT_index_func +#define glIndexFuncEXT XGL_FUNCPTR(glIndexFuncEXT) +#endif + +#ifdef GL_EXT_index_material +#define glIndexMaterialEXT XGL_FUNCPTR(glIndexMaterialEXT) +#endif + +#ifdef GL_EXT_index_texture +#endif + +#ifdef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define glApplyTextureEXT XGL_FUNCPTR(glApplyTextureEXT) +#define glTextureLightEXT XGL_FUNCPTR(glTextureLightEXT) +#define glTextureMaterialEXT XGL_FUNCPTR(glTextureMaterialEXT) +#endif + +#ifdef GL_EXT_misc_attribute +#endif + +#ifdef GL_EXT_multi_draw_arrays +#define glMultiDrawArraysEXT XGL_FUNCPTR(glMultiDrawArraysEXT) +#define glMultiDrawElementsEXT XGL_FUNCPTR(glMultiDrawElementsEXT) +#endif + +#ifdef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#define glSampleMaskEXT XGL_FUNCPTR(glSampleMaskEXT) +#define glSamplePatternEXT XGL_FUNCPTR(glSamplePatternEXT) +#endif + +#ifdef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifdef GL_EXT_paletted_texture +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_COLOR_TABLE_FORMAT_EXT 0x80D8 +#define GL_COLOR_TABLE_WIDTH_EXT 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_EXT 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_EXT 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_EXT 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_EXT 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_EXT 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_EXT 0x80DF +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define glColorTableEXT XGL_FUNCPTR(glColorTableEXT) +#define glGetColorTableEXT XGL_FUNCPTR(glGetColorTableEXT) +#define glGetColorTableParameterfvEXT XGL_FUNCPTR(glGetColorTableParameterfvEXT) +#define glGetColorTableParameterivEXT XGL_FUNCPTR(glGetColorTableParameterivEXT) +#endif + +#ifdef GL_EXT_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif + +#ifdef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#define glGetPixelTransformParameterfvEXT XGL_FUNCPTR(glGetPixelTransformParameterfvEXT) +#define glGetPixelTransformParameterivEXT XGL_FUNCPTR(glGetPixelTransformParameterivEXT) +#define glPixelTransformParameterfEXT XGL_FUNCPTR(glPixelTransformParameterfEXT) +#define glPixelTransformParameterfvEXT XGL_FUNCPTR(glPixelTransformParameterfvEXT) +#define glPixelTransformParameteriEXT XGL_FUNCPTR(glPixelTransformParameteriEXT) +#define glPixelTransformParameterivEXT XGL_FUNCPTR(glPixelTransformParameterivEXT) +#endif + +#ifdef GL_EXT_pixel_transform_color_table +#endif + +#ifdef GL_EXT_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define glPointParameterfEXT XGL_FUNCPTR(glPointParameterfEXT) +#define glPointParameterfvEXT XGL_FUNCPTR(glPointParameterfvEXT) +#endif + +#ifdef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#define glPolygonOffsetEXT XGL_FUNCPTR(glPolygonOffsetEXT) +#endif + +#ifdef GL_EXT_rescale_normal +#endif + +#ifdef GL_EXT_scene_marker +#define glBeginSceneEXT XGL_FUNCPTR(glBeginSceneEXT) +#define glEndSceneEXT XGL_FUNCPTR(glEndSceneEXT) +#endif + +#ifdef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#define glSecondaryColor3bEXT XGL_FUNCPTR(glSecondaryColor3bEXT) +#define glSecondaryColor3bvEXT XGL_FUNCPTR(glSecondaryColor3bvEXT) +#define glSecondaryColor3dEXT XGL_FUNCPTR(glSecondaryColor3dEXT) +#define glSecondaryColor3dvEXT XGL_FUNCPTR(glSecondaryColor3dvEXT) +#define glSecondaryColor3fEXT XGL_FUNCPTR(glSecondaryColor3fEXT) +#define glSecondaryColor3fvEXT XGL_FUNCPTR(glSecondaryColor3fvEXT) +#define glSecondaryColor3iEXT XGL_FUNCPTR(glSecondaryColor3iEXT) +#define glSecondaryColor3ivEXT XGL_FUNCPTR(glSecondaryColor3ivEXT) +#define glSecondaryColor3sEXT XGL_FUNCPTR(glSecondaryColor3sEXT) +#define glSecondaryColor3svEXT XGL_FUNCPTR(glSecondaryColor3svEXT) +#define glSecondaryColor3ubEXT XGL_FUNCPTR(glSecondaryColor3ubEXT) +#define glSecondaryColor3ubvEXT XGL_FUNCPTR(glSecondaryColor3ubvEXT) +#define glSecondaryColor3uiEXT XGL_FUNCPTR(glSecondaryColor3uiEXT) +#define glSecondaryColor3uivEXT XGL_FUNCPTR(glSecondaryColor3uivEXT) +#define glSecondaryColor3usEXT XGL_FUNCPTR(glSecondaryColor3usEXT) +#define glSecondaryColor3usvEXT XGL_FUNCPTR(glSecondaryColor3usvEXT) +#define glSecondaryColorPointerEXT XGL_FUNCPTR(glSecondaryColorPointerEXT) +#endif + +#ifdef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifdef GL_EXT_shadow_funcs +#endif + +#ifdef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifdef GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#define glActiveStencilFaceEXT XGL_FUNCPTR(glActiveStencilFaceEXT) +#endif + +#ifdef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifdef GL_EXT_subtexture +#define glTexSubImage1DEXT XGL_FUNCPTR(glTexSubImage1DEXT) +#define glTexSubImage2DEXT XGL_FUNCPTR(glTexSubImage2DEXT) +#define glTexSubImage3DEXT XGL_FUNCPTR(glTexSubImage3DEXT) +#endif + +#ifdef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#endif + +#ifdef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#define glTexImage3DEXT XGL_FUNCPTR(glTexImage3DEXT) +#endif + +#ifdef GL_EXT_texture_compression_dxt1 +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif + +#ifdef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifdef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifdef GL_EXT_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_EXT 0x812F +#endif + +#ifdef GL_EXT_texture_env +#define GL_TEXTURE_ENV0_EXT 0 +#define GL_TEXTURE_ENV_SHIFT_EXT 0 +#define GL_ENV_BLEND_EXT 0 +#define GL_ENV_ADD_EXT 0 +#define GL_ENV_REPLACE_EXT 0 +#define GL_ENV_SUBTRACT_EXT 0 +#define GL_TEXTURE_ENV_MODE_ALPHA_EXT 0 +#define GL_ENV_REVERSE_BLEND_EXT 0 +#define GL_ENV_REVERSE_SUBTRACT_EXT 0 +#define GL_ENV_COPY_EXT 0 +#define GL_ENV_MODULATE_EXT 0 +#endif + +#ifdef GL_EXT_texture_env_add +#endif + +#ifdef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif + +#ifdef GL_EXT_texture_env_dot3 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif + +#ifdef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifdef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifdef GL_EXT_texture_mirror_clamp +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif + +#ifdef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#define glAreTexturesResidentEXT XGL_FUNCPTR(glAreTexturesResidentEXT) +#define glBindTextureEXT XGL_FUNCPTR(glBindTextureEXT) +#define glDeleteTexturesEXT XGL_FUNCPTR(glDeleteTexturesEXT) +#define glGenTexturesEXT XGL_FUNCPTR(glGenTexturesEXT) +#define glIsTextureEXT XGL_FUNCPTR(glIsTextureEXT) +#define glPrioritizeTexturesEXT XGL_FUNCPTR(glPrioritizeTexturesEXT) +#endif + +#ifdef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#define glTextureNormalEXT XGL_FUNCPTR(glTextureNormalEXT) +#endif + +#ifdef GL_EXT_texture_rectangle +#define GL_TEXTURE_RECTANGLE_EXT 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_EXT 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_EXT 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT 0x84F8 +#endif + +#ifdef GL_EXT_vertex_array +#define GL_DOUBLE_EXT 0x140A +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#define glArrayElementEXT XGL_FUNCPTR(glArrayElementEXT) +#define glColorPointerEXT XGL_FUNCPTR(glColorPointerEXT) +#define glDrawArraysEXT XGL_FUNCPTR(glDrawArraysEXT) +#define glEdgeFlagPointerEXT XGL_FUNCPTR(glEdgeFlagPointerEXT) +#define glGetPointervEXT XGL_FUNCPTR(glGetPointervEXT) +#define glIndexPointerEXT XGL_FUNCPTR(glIndexPointerEXT) +#define glNormalPointerEXT XGL_FUNCPTR(glNormalPointerEXT) +#define glTexCoordPointerEXT XGL_FUNCPTR(glTexCoordPointerEXT) +#define glVertexPointerEXT XGL_FUNCPTR(glVertexPointerEXT) +#endif + +#ifdef GL_EXT_vertex_shader +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +#define glBeginVertexShaderEXT XGL_FUNCPTR(glBeginVertexShaderEXT) +#define glEndVertexShaderEXT XGL_FUNCPTR(glEndVertexShaderEXT) +#define glBindVertexShaderEXT XGL_FUNCPTR(glBindVertexShaderEXT) +#define glGenVertexShadersEXT XGL_FUNCPTR(glGenVertexShadersEXT) +#define glDeleteVertexShaderEXT XGL_FUNCPTR(glDeleteVertexShaderEXT) +#define glShaderOp1EXT XGL_FUNCPTR(glShaderOp1EXT) +#define glShaderOp2EXT XGL_FUNCPTR(glShaderOp2EXT) +#define glShaderOp3EXT XGL_FUNCPTR(glShaderOp3EXT) +#define glSwizzleEXT XGL_FUNCPTR(glSwizzleEXT) +#define glWriteMaskEXT XGL_FUNCPTR(glWriteMaskEXT) +#define glInsertComponentEXT XGL_FUNCPTR(glInsertComponentEXT) +#define glExtractComponentEXT XGL_FUNCPTR(glExtractComponentEXT) +#define glGenSymbolsEXT XGL_FUNCPTR(glGenSymbolsEXT) +#define glSetInvariantEXT XGL_FUNCPTR(glSetInvariantEXT) +#define glSetLocalConstantEXT XGL_FUNCPTR(glSetLocalConstantEXT) +#define glVariantbvEXT XGL_FUNCPTR(glVariantbvEXT) +#define glVariantsvEXT XGL_FUNCPTR(glVariantsvEXT) +#define glVariantivEXT XGL_FUNCPTR(glVariantivEXT) +#define glVariantfvEXT XGL_FUNCPTR(glVariantfvEXT) +#define glVariantdvEXT XGL_FUNCPTR(glVariantdvEXT) +#define glVariantubvEXT XGL_FUNCPTR(glVariantubvEXT) +#define glVariantusvEXT XGL_FUNCPTR(glVariantusvEXT) +#define glVariantuivEXT XGL_FUNCPTR(glVariantuivEXT) +#define glVariantPointerEXT XGL_FUNCPTR(glVariantPointerEXT) +#define glEnableVariantClientStateEXT XGL_FUNCPTR(glEnableVariantClientStateEXT) +#define glDisableVariantClientStateEXT XGL_FUNCPTR(glDisableVariantClientStateEXT) +#define glBindLightParameterEXT XGL_FUNCPTR(glBindLightParameterEXT) +#define glBindMaterialParameterEXT XGL_FUNCPTR(glBindMaterialParameterEXT) +#define glBindTexGenParameterEXT XGL_FUNCPTR(glBindTexGenParameterEXT) +#define glBindTextureUnitParameterEXT XGL_FUNCPTR(glBindTextureUnitParameterEXT) +#define glBindParameterEXT XGL_FUNCPTR(glBindParameterEXT) +#define glIsVariantEnabledEXT XGL_FUNCPTR(glIsVariantEnabledEXT) +#define glGetVariantBooleanvEXT XGL_FUNCPTR(glGetVariantBooleanvEXT) +#define glGetVariantIntegervEXT XGL_FUNCPTR(glGetVariantIntegervEXT) +#define glGetVariantFloatvEXT XGL_FUNCPTR(glGetVariantFloatvEXT) +#define glGetVariantPointervEXT XGL_FUNCPTR(glGetVariantPointervEXT) +#define glGetInvariantBooleanvEXT XGL_FUNCPTR(glGetInvariantBooleanvEXT) +#define glGetInvariantIntegervEXT XGL_FUNCPTR(glGetInvariantIntegervEXT) +#define glGetInvariantFloatvEXT XGL_FUNCPTR(glGetInvariantFloatvEXT) +#define glGetLocalConstantBooleanvEXT XGL_FUNCPTR(glGetLocalConstantBooleanvEXT) +#define glGetLocalConstantIntegervEXT XGL_FUNCPTR(glGetLocalConstantIntegervEXT) +#define glGetLocalConstantFloatvEXT XGL_FUNCPTR(glGetLocalConstantFloatvEXT) +#endif + +#ifdef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT 0x0BA3 +#define GL_MODELVIEW0_MATRIX_EXT 0x0BA6 +#define GL_MODELVIEW0_EXT 0x1700 +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#define glVertexWeightPointerEXT XGL_FUNCPTR(glVertexWeightPointerEXT) +#define glVertexWeightfEXT XGL_FUNCPTR(glVertexWeightfEXT) +#define glVertexWeightfvEXT XGL_FUNCPTR(glVertexWeightfvEXT) +#endif + +#ifdef GL_GREMEDY_string_marker +#define glStringMarkerGREMEDY XGL_FUNCPTR(glStringMarkerGREMEDY) +#endif + +#ifdef GL_HP_convolution_border_modes +#endif + +#ifdef GL_HP_image_transform +#define glGetImageTransformParameterfvHP XGL_FUNCPTR(glGetImageTransformParameterfvHP) +#define glGetImageTransformParameterivHP XGL_FUNCPTR(glGetImageTransformParameterivHP) +#define glImageTransformParameterfHP XGL_FUNCPTR(glImageTransformParameterfHP) +#define glImageTransformParameterfvHP XGL_FUNCPTR(glImageTransformParameterfvHP) +#define glImageTransformParameteriHP XGL_FUNCPTR(glImageTransformParameteriHP) +#define glImageTransformParameterivHP XGL_FUNCPTR(glImageTransformParameterivHP) +#endif + +#ifdef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#define GL_OCCLUSION_TEST_HP 0x8165 +#endif + +#ifdef GL_HP_texture_lighting +#endif + +#ifdef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifdef GL_IBM_multimode_draw_arrays +#define glMultiModeDrawArraysIBM XGL_FUNCPTR(glMultiModeDrawArraysIBM) +#define glMultiModeDrawElementsIBM XGL_FUNCPTR(glMultiModeDrawElementsIBM) +#endif + +#ifdef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 103010 +#endif + +#ifdef GL_IBM_static_data +#define GL_ALL_STATIC_DATA_IBM 103060 +#define GL_STATIC_VERTEX_ARRAY_IBM 103061 +#endif + +#ifdef GL_IBM_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif + +#ifdef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#define glColorPointerListIBM XGL_FUNCPTR(glColorPointerListIBM) +#define glEdgeFlagPointerListIBM XGL_FUNCPTR(glEdgeFlagPointerListIBM) +#define glFogCoordPointerListIBM XGL_FUNCPTR(glFogCoordPointerListIBM) +#define glIndexPointerListIBM XGL_FUNCPTR(glIndexPointerListIBM) +#define glNormalPointerListIBM XGL_FUNCPTR(glNormalPointerListIBM) +#define glSecondaryColorPointerListIBM XGL_FUNCPTR(glSecondaryColorPointerListIBM) +#define glTexCoordPointerListIBM XGL_FUNCPTR(glTexCoordPointerListIBM) +#define glVertexPointerListIBM XGL_FUNCPTR(glVertexPointerListIBM) +#endif + +#ifdef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifdef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifdef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#define glColorPointervINTEL XGL_FUNCPTR(glColorPointervINTEL) +#define glNormalPointervINTEL XGL_FUNCPTR(glNormalPointervINTEL) +#define glTexCoordPointervINTEL XGL_FUNCPTR(glTexCoordPointervINTEL) +#define glVertexPointervINTEL XGL_FUNCPTR(glVertexPointervINTEL) +#endif + +#ifdef GL_INTEL_texture_scissor +#define glTexScissorFuncINTEL XGL_FUNCPTR(glTexScissorFuncINTEL) +#define glTexScissorINTEL XGL_FUNCPTR(glTexScissorINTEL) +#endif + +#ifdef GL_KTX_buffer_region +#define GL_KTX_FRONT_REGION 0x0 +#define GL_KTX_BACK_REGION 0x1 +#define GL_KTX_Z_REGION 0x2 +#define GL_KTX_STENCIL_REGION 0x3 +#define glBufferRegionEnabledEXT XGL_FUNCPTR(glBufferRegionEnabledEXT) +#define glNewBufferRegionEXT XGL_FUNCPTR(glNewBufferRegionEXT) +#define glDeleteBufferRegionEXT XGL_FUNCPTR(glDeleteBufferRegionEXT) +#define glReadBufferRegionEXT XGL_FUNCPTR(glReadBufferRegionEXT) +#define glDrawBufferRegionEXT XGL_FUNCPTR(glDrawBufferRegionEXT) +#endif + +#ifdef GL_MESA_pack_invert +#define GL_PACK_INVERT_MESA 0x8758 +#endif + +#ifdef GL_MESA_resize_buffers +#define glResizeBuffersMESA XGL_FUNCPTR(glResizeBuffersMESA) +#endif + +#ifdef GL_MESA_window_pos +#define glWindowPos2dMESA XGL_FUNCPTR(glWindowPos2dMESA) +#define glWindowPos2dvMESA XGL_FUNCPTR(glWindowPos2dvMESA) +#define glWindowPos2fMESA XGL_FUNCPTR(glWindowPos2fMESA) +#define glWindowPos2fvMESA XGL_FUNCPTR(glWindowPos2fvMESA) +#define glWindowPos2iMESA XGL_FUNCPTR(glWindowPos2iMESA) +#define glWindowPos2ivMESA XGL_FUNCPTR(glWindowPos2ivMESA) +#define glWindowPos2sMESA XGL_FUNCPTR(glWindowPos2sMESA) +#define glWindowPos2svMESA XGL_FUNCPTR(glWindowPos2svMESA) +#define glWindowPos3dMESA XGL_FUNCPTR(glWindowPos3dMESA) +#define glWindowPos3dvMESA XGL_FUNCPTR(glWindowPos3dvMESA) +#define glWindowPos3fMESA XGL_FUNCPTR(glWindowPos3fMESA) +#define glWindowPos3fvMESA XGL_FUNCPTR(glWindowPos3fvMESA) +#define glWindowPos3iMESA XGL_FUNCPTR(glWindowPos3iMESA) +#define glWindowPos3ivMESA XGL_FUNCPTR(glWindowPos3ivMESA) +#define glWindowPos3sMESA XGL_FUNCPTR(glWindowPos3sMESA) +#define glWindowPos3svMESA XGL_FUNCPTR(glWindowPos3svMESA) +#define glWindowPos4dMESA XGL_FUNCPTR(glWindowPos4dMESA) +#define glWindowPos4dvMESA XGL_FUNCPTR(glWindowPos4dvMESA) +#define glWindowPos4fMESA XGL_FUNCPTR(glWindowPos4fMESA) +#define glWindowPos4fvMESA XGL_FUNCPTR(glWindowPos4fvMESA) +#define glWindowPos4iMESA XGL_FUNCPTR(glWindowPos4iMESA) +#define glWindowPos4ivMESA XGL_FUNCPTR(glWindowPos4ivMESA) +#define glWindowPos4sMESA XGL_FUNCPTR(glWindowPos4sMESA) +#define glWindowPos4svMESA XGL_FUNCPTR(glWindowPos4svMESA) +#endif + +#ifdef GL_MESA_ycbcr_texture +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif + +#ifdef GL_NV_blend_square +#endif + +#ifdef GL_NV_copy_depth_to_color +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif + +#ifdef GL_NV_depth_clamp +#define GL_DEPTH_CLAMP_NV 0x864F +#endif + +#ifdef GL_NV_evaluators +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +#define glEvalMapsNV XGL_FUNCPTR(glEvalMapsNV) +#define glGetMapAttribParameterfvNV XGL_FUNCPTR(glGetMapAttribParameterfvNV) +#define glGetMapAttribParameterivNV XGL_FUNCPTR(glGetMapAttribParameterivNV) +#define glGetMapControlPointsNV XGL_FUNCPTR(glGetMapControlPointsNV) +#define glGetMapParameterfvNV XGL_FUNCPTR(glGetMapParameterfvNV) +#define glGetMapParameterivNV XGL_FUNCPTR(glGetMapParameterivNV) +#define glMapControlPointsNV XGL_FUNCPTR(glMapControlPointsNV) +#define glMapParameterfvNV XGL_FUNCPTR(glMapParameterfvNV) +#define glMapParameterivNV XGL_FUNCPTR(glMapParameterivNV) +#endif + +#ifdef GL_NV_fence +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +#define glDeleteFencesNV XGL_FUNCPTR(glDeleteFencesNV) +#define glFinishFenceNV XGL_FUNCPTR(glFinishFenceNV) +#define glGenFencesNV XGL_FUNCPTR(glGenFencesNV) +#define glGetFenceivNV XGL_FUNCPTR(glGetFenceivNV) +#define glIsFenceNV XGL_FUNCPTR(glIsFenceNV) +#define glSetFenceNV XGL_FUNCPTR(glSetFenceNV) +#define glTestFenceNV XGL_FUNCPTR(glTestFenceNV) +#endif + +#ifdef GL_NV_float_buffer +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif + +#ifdef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +#endif + +#ifdef GL_NV_fragment_program +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +#define glGetProgramNamedParameterdvNV XGL_FUNCPTR(glGetProgramNamedParameterdvNV) +#define glGetProgramNamedParameterfvNV XGL_FUNCPTR(glGetProgramNamedParameterfvNV) +#define glProgramNamedParameter4dNV XGL_FUNCPTR(glProgramNamedParameter4dNV) +#define glProgramNamedParameter4dvNV XGL_FUNCPTR(glProgramNamedParameter4dvNV) +#define glProgramNamedParameter4fNV XGL_FUNCPTR(glProgramNamedParameter4fNV) +#define glProgramNamedParameter4fvNV XGL_FUNCPTR(glProgramNamedParameter4fvNV) +#endif + +#ifdef GL_NV_fragment_program2 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif + +#ifdef GL_NV_fragment_program_option +#endif + +#ifdef GL_NV_half_float +typedef unsigned short GLhalf; +#define GL_HALF_FLOAT_NV 0x140B +#define glColor3hNV XGL_FUNCPTR(glColor3hNV) +#define glColor3hvNV XGL_FUNCPTR(glColor3hvNV) +#define glColor4hNV XGL_FUNCPTR(glColor4hNV) +#define glColor4hvNV XGL_FUNCPTR(glColor4hvNV) +#define glFogCoordhNV XGL_FUNCPTR(glFogCoordhNV) +#define glFogCoordhvNV XGL_FUNCPTR(glFogCoordhvNV) +#define glMultiTexCoord1hNV XGL_FUNCPTR(glMultiTexCoord1hNV) +#define glMultiTexCoord1hvNV XGL_FUNCPTR(glMultiTexCoord1hvNV) +#define glMultiTexCoord2hNV XGL_FUNCPTR(glMultiTexCoord2hNV) +#define glMultiTexCoord2hvNV XGL_FUNCPTR(glMultiTexCoord2hvNV) +#define glMultiTexCoord3hNV XGL_FUNCPTR(glMultiTexCoord3hNV) +#define glMultiTexCoord3hvNV XGL_FUNCPTR(glMultiTexCoord3hvNV) +#define glMultiTexCoord4hNV XGL_FUNCPTR(glMultiTexCoord4hNV) +#define glMultiTexCoord4hvNV XGL_FUNCPTR(glMultiTexCoord4hvNV) +#define glNormal3hNV XGL_FUNCPTR(glNormal3hNV) +#define glNormal3hvNV XGL_FUNCPTR(glNormal3hvNV) +#define glSecondaryColor3hNV XGL_FUNCPTR(glSecondaryColor3hNV) +#define glSecondaryColor3hvNV XGL_FUNCPTR(glSecondaryColor3hvNV) +#define glTexCoord1hNV XGL_FUNCPTR(glTexCoord1hNV) +#define glTexCoord1hvNV XGL_FUNCPTR(glTexCoord1hvNV) +#define glTexCoord2hNV XGL_FUNCPTR(glTexCoord2hNV) +#define glTexCoord2hvNV XGL_FUNCPTR(glTexCoord2hvNV) +#define glTexCoord3hNV XGL_FUNCPTR(glTexCoord3hNV) +#define glTexCoord3hvNV XGL_FUNCPTR(glTexCoord3hvNV) +#define glTexCoord4hNV XGL_FUNCPTR(glTexCoord4hNV) +#define glTexCoord4hvNV XGL_FUNCPTR(glTexCoord4hvNV) +#define glVertex2hNV XGL_FUNCPTR(glVertex2hNV) +#define glVertex2hvNV XGL_FUNCPTR(glVertex2hvNV) +#define glVertex3hNV XGL_FUNCPTR(glVertex3hNV) +#define glVertex3hvNV XGL_FUNCPTR(glVertex3hvNV) +#define glVertex4hNV XGL_FUNCPTR(glVertex4hNV) +#define glVertex4hvNV XGL_FUNCPTR(glVertex4hvNV) +#define glVertexAttrib1hNV XGL_FUNCPTR(glVertexAttrib1hNV) +#define glVertexAttrib1hvNV XGL_FUNCPTR(glVertexAttrib1hvNV) +#define glVertexAttrib2hNV XGL_FUNCPTR(glVertexAttrib2hNV) +#define glVertexAttrib2hvNV XGL_FUNCPTR(glVertexAttrib2hvNV) +#define glVertexAttrib3hNV XGL_FUNCPTR(glVertexAttrib3hNV) +#define glVertexAttrib3hvNV XGL_FUNCPTR(glVertexAttrib3hvNV) +#define glVertexAttrib4hNV XGL_FUNCPTR(glVertexAttrib4hNV) +#define glVertexAttrib4hvNV XGL_FUNCPTR(glVertexAttrib4hvNV) +#define glVertexAttribs1hvNV XGL_FUNCPTR(glVertexAttribs1hvNV) +#define glVertexAttribs2hvNV XGL_FUNCPTR(glVertexAttribs2hvNV) +#define glVertexAttribs3hvNV XGL_FUNCPTR(glVertexAttribs3hvNV) +#define glVertexAttribs4hvNV XGL_FUNCPTR(glVertexAttribs4hvNV) +#define glVertexWeighthNV XGL_FUNCPTR(glVertexWeighthNV) +#define glVertexWeighthvNV XGL_FUNCPTR(glVertexWeighthvNV) +#endif + +#ifdef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifdef GL_NV_multisample_filter_hint +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif + +#ifdef GL_NV_occlusion_query +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +#define glBeginOcclusionQueryNV XGL_FUNCPTR(glBeginOcclusionQueryNV) +#define glDeleteOcclusionQueriesNV XGL_FUNCPTR(glDeleteOcclusionQueriesNV) +#define glEndOcclusionQueryNV XGL_FUNCPTR(glEndOcclusionQueryNV) +#define glGenOcclusionQueriesNV XGL_FUNCPTR(glGenOcclusionQueriesNV) +#define glGetOcclusionQueryivNV XGL_FUNCPTR(glGetOcclusionQueryivNV) +#define glGetOcclusionQueryuivNV XGL_FUNCPTR(glGetOcclusionQueryuivNV) +#define glIsOcclusionQueryNV XGL_FUNCPTR(glIsOcclusionQueryNV) +#endif + +#ifdef GL_NV_packed_depth_stencil +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif + +#ifdef GL_NV_pixel_data_range +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +#define glFlushPixelDataRangeNV XGL_FUNCPTR(glFlushPixelDataRangeNV) +#define glPixelDataRangeNV XGL_FUNCPTR(glPixelDataRangeNV) +#endif + +#ifdef GL_NV_point_sprite +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#define glPointParameteriNV XGL_FUNCPTR(glPointParameteriNV) +#define glPointParameterivNV XGL_FUNCPTR(glPointParameterivNV) +#endif + +#ifdef GL_NV_primitive_restart +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +#define glPrimitiveRestartIndexNV XGL_FUNCPTR(glPrimitiveRestartIndexNV) +#define glPrimitiveRestartNV XGL_FUNCPTR(glPrimitiveRestartNV) +#endif + +#ifdef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +#define glCombinerInputNV XGL_FUNCPTR(glCombinerInputNV) +#define glCombinerOutputNV XGL_FUNCPTR(glCombinerOutputNV) +#define glCombinerParameterfNV XGL_FUNCPTR(glCombinerParameterfNV) +#define glCombinerParameterfvNV XGL_FUNCPTR(glCombinerParameterfvNV) +#define glCombinerParameteriNV XGL_FUNCPTR(glCombinerParameteriNV) +#define glCombinerParameterivNV XGL_FUNCPTR(glCombinerParameterivNV) +#define glFinalCombinerInputNV XGL_FUNCPTR(glFinalCombinerInputNV) +#define glGetCombinerInputParameterfvNV XGL_FUNCPTR(glGetCombinerInputParameterfvNV) +#define glGetCombinerInputParameterivNV XGL_FUNCPTR(glGetCombinerInputParameterivNV) +#define glGetCombinerOutputParameterfvNV XGL_FUNCPTR(glGetCombinerOutputParameterfvNV) +#define glGetCombinerOutputParameterivNV XGL_FUNCPTR(glGetCombinerOutputParameterivNV) +#define glGetFinalCombinerInputParameterfvNV XGL_FUNCPTR(glGetFinalCombinerInputParameterfvNV) +#define glGetFinalCombinerInputParameterivNV XGL_FUNCPTR(glGetFinalCombinerInputParameterivNV) +#endif + +#ifdef GL_NV_register_combiners2 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +#define glCombinerStageParameterfvNV XGL_FUNCPTR(glCombinerStageParameterfvNV) +#define glGetCombinerStageParameterfvNV XGL_FUNCPTR(glGetCombinerStageParameterfvNV) +#endif + +#ifdef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifdef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifdef GL_NV_texture_compression_vtc +#endif + +#ifdef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifdef GL_NV_texture_expand_normal +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif + +#ifdef GL_NV_texture_rectangle +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif + +#ifdef GL_NV_texture_shader +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif + +#ifdef GL_NV_texture_shader2 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#endif + +#ifdef GL_NV_texture_shader3 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif + +#ifdef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#define glFlushVertexArrayRangeNV XGL_FUNCPTR(glFlushVertexArrayRangeNV) +#define glVertexArrayRangeNV XGL_FUNCPTR(glVertexArrayRangeNV) +#endif + +#ifdef GL_NV_vertex_array_range2 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif + +#ifdef GL_NV_vertex_program +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +#define glAreProgramsResidentNV XGL_FUNCPTR(glAreProgramsResidentNV) +#define glBindProgramNV XGL_FUNCPTR(glBindProgramNV) +#define glDeleteProgramsNV XGL_FUNCPTR(glDeleteProgramsNV) +#define glExecuteProgramNV XGL_FUNCPTR(glExecuteProgramNV) +#define glGenProgramsNV XGL_FUNCPTR(glGenProgramsNV) +#define glGetProgramParameterdvNV XGL_FUNCPTR(glGetProgramParameterdvNV) +#define glGetProgramParameterfvNV XGL_FUNCPTR(glGetProgramParameterfvNV) +#define glGetProgramStringNV XGL_FUNCPTR(glGetProgramStringNV) +#define glGetProgramivNV XGL_FUNCPTR(glGetProgramivNV) +#define glGetTrackMatrixivNV XGL_FUNCPTR(glGetTrackMatrixivNV) +#define glGetVertexAttribPointervNV XGL_FUNCPTR(glGetVertexAttribPointervNV) +#define glGetVertexAttribdvNV XGL_FUNCPTR(glGetVertexAttribdvNV) +#define glGetVertexAttribfvNV XGL_FUNCPTR(glGetVertexAttribfvNV) +#define glGetVertexAttribivNV XGL_FUNCPTR(glGetVertexAttribivNV) +#define glIsProgramNV XGL_FUNCPTR(glIsProgramNV) +#define glLoadProgramNV XGL_FUNCPTR(glLoadProgramNV) +#define glProgramParameter4dNV XGL_FUNCPTR(glProgramParameter4dNV) +#define glProgramParameter4dvNV XGL_FUNCPTR(glProgramParameter4dvNV) +#define glProgramParameter4fNV XGL_FUNCPTR(glProgramParameter4fNV) +#define glProgramParameter4fvNV XGL_FUNCPTR(glProgramParameter4fvNV) +#define glProgramParameters4dvNV XGL_FUNCPTR(glProgramParameters4dvNV) +#define glProgramParameters4fvNV XGL_FUNCPTR(glProgramParameters4fvNV) +#define glRequestResidentProgramsNV XGL_FUNCPTR(glRequestResidentProgramsNV) +#define glTrackMatrixNV XGL_FUNCPTR(glTrackMatrixNV) +#define glVertexAttrib1dNV XGL_FUNCPTR(glVertexAttrib1dNV) +#define glVertexAttrib1dvNV XGL_FUNCPTR(glVertexAttrib1dvNV) +#define glVertexAttrib1fNV XGL_FUNCPTR(glVertexAttrib1fNV) +#define glVertexAttrib1fvNV XGL_FUNCPTR(glVertexAttrib1fvNV) +#define glVertexAttrib1sNV XGL_FUNCPTR(glVertexAttrib1sNV) +#define glVertexAttrib1svNV XGL_FUNCPTR(glVertexAttrib1svNV) +#define glVertexAttrib2dNV XGL_FUNCPTR(glVertexAttrib2dNV) +#define glVertexAttrib2dvNV XGL_FUNCPTR(glVertexAttrib2dvNV) +#define glVertexAttrib2fNV XGL_FUNCPTR(glVertexAttrib2fNV) +#define glVertexAttrib2fvNV XGL_FUNCPTR(glVertexAttrib2fvNV) +#define glVertexAttrib2sNV XGL_FUNCPTR(glVertexAttrib2sNV) +#define glVertexAttrib2svNV XGL_FUNCPTR(glVertexAttrib2svNV) +#define glVertexAttrib3dNV XGL_FUNCPTR(glVertexAttrib3dNV) +#define glVertexAttrib3dvNV XGL_FUNCPTR(glVertexAttrib3dvNV) +#define glVertexAttrib3fNV XGL_FUNCPTR(glVertexAttrib3fNV) +#define glVertexAttrib3fvNV XGL_FUNCPTR(glVertexAttrib3fvNV) +#define glVertexAttrib3sNV XGL_FUNCPTR(glVertexAttrib3sNV) +#define glVertexAttrib3svNV XGL_FUNCPTR(glVertexAttrib3svNV) +#define glVertexAttrib4dNV XGL_FUNCPTR(glVertexAttrib4dNV) +#define glVertexAttrib4dvNV XGL_FUNCPTR(glVertexAttrib4dvNV) +#define glVertexAttrib4fNV XGL_FUNCPTR(glVertexAttrib4fNV) +#define glVertexAttrib4fvNV XGL_FUNCPTR(glVertexAttrib4fvNV) +#define glVertexAttrib4sNV XGL_FUNCPTR(glVertexAttrib4sNV) +#define glVertexAttrib4svNV XGL_FUNCPTR(glVertexAttrib4svNV) +#define glVertexAttrib4ubNV XGL_FUNCPTR(glVertexAttrib4ubNV) +#define glVertexAttrib4ubvNV XGL_FUNCPTR(glVertexAttrib4ubvNV) +#define glVertexAttribPointerNV XGL_FUNCPTR(glVertexAttribPointerNV) +#define glVertexAttribs1dvNV XGL_FUNCPTR(glVertexAttribs1dvNV) +#define glVertexAttribs1fvNV XGL_FUNCPTR(glVertexAttribs1fvNV) +#define glVertexAttribs1svNV XGL_FUNCPTR(glVertexAttribs1svNV) +#define glVertexAttribs2dvNV XGL_FUNCPTR(glVertexAttribs2dvNV) +#define glVertexAttribs2fvNV XGL_FUNCPTR(glVertexAttribs2fvNV) +#define glVertexAttribs2svNV XGL_FUNCPTR(glVertexAttribs2svNV) +#define glVertexAttribs3dvNV XGL_FUNCPTR(glVertexAttribs3dvNV) +#define glVertexAttribs3fvNV XGL_FUNCPTR(glVertexAttribs3fvNV) +#define glVertexAttribs3svNV XGL_FUNCPTR(glVertexAttribs3svNV) +#define glVertexAttribs4dvNV XGL_FUNCPTR(glVertexAttribs4dvNV) +#define glVertexAttribs4fvNV XGL_FUNCPTR(glVertexAttribs4fvNV) +#define glVertexAttribs4svNV XGL_FUNCPTR(glVertexAttribs4svNV) +#define glVertexAttribs4ubvNV XGL_FUNCPTR(glVertexAttribs4ubvNV) +#endif + +#ifdef GL_NV_vertex_program1_1 +#endif + +#ifdef GL_NV_vertex_program2 +#endif + +#ifdef GL_NV_vertex_program2_option +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#endif + +#ifdef GL_NV_vertex_program3 +#define MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#endif + +#ifdef GL_OML_interlace +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif + +#ifdef GL_OML_resample +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif + +#ifdef GL_OML_subsample +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif + +#ifdef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 107000 +#define GL_CONSERVE_MEMORY_HINT_PGI 107005 +#define GL_RECLAIM_MEMORY_HINT_PGI 107006 +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 107010 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 107011 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 107012 +#define GL_ALWAYS_FAST_HINT_PGI 107020 +#define GL_ALWAYS_SOFT_HINT_PGI 107021 +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 107022 +#define GL_ALLOW_DRAW_WIN_HINT_PGI 107023 +#define GL_ALLOW_DRAW_FRG_HINT_PGI 107024 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 107025 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 107030 +#define GL_STRICT_LIGHTING_HINT_PGI 107031 +#define GL_STRICT_SCISSOR_HINT_PGI 107032 +#define GL_FULL_STIPPLE_HINT_PGI 107033 +#define GL_CLIP_NEAR_HINT_PGI 107040 +#define GL_CLIP_FAR_HINT_PGI 107041 +#define GL_WIDE_LINE_HINT_PGI 107042 +#define GL_BACK_NORMALS_HINT_PGI 107043 +#endif + +#ifdef GL_PGI_vertex_hints +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_VERTEX_DATA_HINT_PGI 107050 +#define GL_VERTEX_CONSISTENT_HINT_PGI 107051 +#define GL_MATERIAL_SIDE_HINT_PGI 107052 +#define GL_MAX_VERTEX_HINT_PGI 107053 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#endif + +#ifdef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifdef GL_S3_s3tc +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#define GL_RGBA_DXT5_S3TC 0x83A4 +#define GL_RGBA4_DXT5_S3TC 0x83A5 +#endif + +#ifdef GL_SGIS_color_range +#define GL_EXTENDED_RANGE_SGIS 0x85A5 +#define GL_MIN_RED_SGIS 0x85A6 +#define GL_MAX_RED_SGIS 0x85A7 +#define GL_MIN_GREEN_SGIS 0x85A8 +#define GL_MAX_GREEN_SGIS 0x85A9 +#define GL_MIN_BLUE_SGIS 0x85AA +#define GL_MAX_BLUE_SGIS 0x85AB +#define GL_MIN_ALPHA_SGIS 0x85AC +#define GL_MAX_ALPHA_SGIS 0x85AD +#endif + +#ifdef GL_SGIS_detail_texture +#define glDetailTexFuncSGIS XGL_FUNCPTR(glDetailTexFuncSGIS) +#define glGetDetailTexFuncSGIS XGL_FUNCPTR(glGetDetailTexFuncSGIS) +#endif + +#ifdef GL_SGIS_fog_function +#define glFogFuncSGIS XGL_FUNCPTR(glFogFuncSGIS) +#define glGetFogFuncSGIS XGL_FUNCPTR(glGetFogFuncSGIS) +#endif + +#ifdef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifdef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#define glSampleMaskSGIS XGL_FUNCPTR(glSampleMaskSGIS) +#define glSamplePatternSGIS XGL_FUNCPTR(glSamplePatternSGIS) +#endif + +#ifdef GL_SGIS_pixel_texture +#endif + +#ifdef GL_SGIS_sharpen_texture +#define glGetSharpenTexFuncSGIS XGL_FUNCPTR(glGetSharpenTexFuncSGIS) +#define glSharpenTexFuncSGIS XGL_FUNCPTR(glSharpenTexFuncSGIS) +#endif + +#ifdef GL_SGIS_texture4D +#define glTexImage4DSGIS XGL_FUNCPTR(glTexImage4DSGIS) +#define glTexSubImage4DSGIS XGL_FUNCPTR(glTexSubImage4DSGIS) +#endif + +#ifdef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifdef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifdef GL_SGIS_texture_filter4 +#define glGetTexFilterFuncSGIS XGL_FUNCPTR(glGetTexFilterFuncSGIS) +#define glTexFilterFuncSGIS XGL_FUNCPTR(glTexFilterFuncSGIS) +#endif + +#ifdef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifdef GL_SGIS_texture_select +#endif + +#ifdef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#define glAsyncMarkerSGIX XGL_FUNCPTR(glAsyncMarkerSGIX) +#define glDeleteAsyncMarkersSGIX XGL_FUNCPTR(glDeleteAsyncMarkersSGIX) +#define glFinishAsyncSGIX XGL_FUNCPTR(glFinishAsyncSGIX) +#define glGenAsyncMarkersSGIX XGL_FUNCPTR(glGenAsyncMarkersSGIX) +#define glIsAsyncMarkerSGIX XGL_FUNCPTR(glIsAsyncMarkerSGIX) +#define glPollAsyncSGIX XGL_FUNCPTR(glPollAsyncSGIX) +#endif + +#ifdef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif + +#ifdef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifdef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifdef GL_SGIX_clipmap +#endif + +#ifdef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifdef GL_SGIX_flush_raster +#define glFlushRasterSGIX XGL_FUNCPTR(glFlushRasterSGIX) +#endif + +#ifdef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifdef GL_SGIX_fog_texture +#define GL_TEXTURE_FOG_SGIX 0 +#define GL_FOG_PATCHY_FACTOR_SGIX 0 +#define GL_FRAGMENT_FOG_SGIX 0 +#define glTextureFogSGIX XGL_FUNCPTR(glTextureFogSGIX) +#endif + +#ifdef GL_SGIX_fragment_specular_lighting +#define glFragmentColorMaterialSGIX XGL_FUNCPTR(glFragmentColorMaterialSGIX) +#define glFragmentLightModelfSGIX XGL_FUNCPTR(glFragmentLightModelfSGIX) +#define glFragmentLightModelfvSGIX XGL_FUNCPTR(glFragmentLightModelfvSGIX) +#define glFragmentLightModeliSGIX XGL_FUNCPTR(glFragmentLightModeliSGIX) +#define glFragmentLightModelivSGIX XGL_FUNCPTR(glFragmentLightModelivSGIX) +#define glFragmentLightfSGIX XGL_FUNCPTR(glFragmentLightfSGIX) +#define glFragmentLightfvSGIX XGL_FUNCPTR(glFragmentLightfvSGIX) +#define glFragmentLightiSGIX XGL_FUNCPTR(glFragmentLightiSGIX) +#define glFragmentLightivSGIX XGL_FUNCPTR(glFragmentLightivSGIX) +#define glFragmentMaterialfSGIX XGL_FUNCPTR(glFragmentMaterialfSGIX) +#define glFragmentMaterialfvSGIX XGL_FUNCPTR(glFragmentMaterialfvSGIX) +#define glFragmentMaterialiSGIX XGL_FUNCPTR(glFragmentMaterialiSGIX) +#define glFragmentMaterialivSGIX XGL_FUNCPTR(glFragmentMaterialivSGIX) +#define glGetFragmentLightfvSGIX XGL_FUNCPTR(glGetFragmentLightfvSGIX) +#define glGetFragmentLightivSGIX XGL_FUNCPTR(glGetFragmentLightivSGIX) +#define glGetFragmentMaterialfvSGIX XGL_FUNCPTR(glGetFragmentMaterialfvSGIX) +#define glGetFragmentMaterialivSGIX XGL_FUNCPTR(glGetFragmentMaterialivSGIX) +#endif + +#ifdef GL_SGIX_framezoom +#define glFrameZoomSGIX XGL_FUNCPTR(glFrameZoomSGIX) +#endif + +#ifdef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifdef GL_SGIX_ir_instrument1 +#endif + +#ifdef GL_SGIX_list_priority +#endif + +#ifdef GL_SGIX_pixel_texture +#define glPixelTexGenSGIX XGL_FUNCPTR(glPixelTexGenSGIX) +#endif + +#ifdef GL_SGIX_pixel_texture_bits +#endif + +#ifdef GL_SGIX_reference_plane +#define glReferencePlaneSGIX XGL_FUNCPTR(glReferencePlaneSGIX) +#endif + +#ifdef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842E +#define GL_UNPACK_RESAMPLE_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#define GL_RESAMPLE_REPLICATE_SGIX 0x8433 +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x8434 +#endif + +#ifdef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifdef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifdef GL_SGIX_sprite +#define glSpriteParameterfSGIX XGL_FUNCPTR(glSpriteParameterfSGIX) +#define glSpriteParameterfvSGIX XGL_FUNCPTR(glSpriteParameterfvSGIX) +#define glSpriteParameteriSGIX XGL_FUNCPTR(glSpriteParameteriSGIX) +#define glSpriteParameterivSGIX XGL_FUNCPTR(glSpriteParameterivSGIX) +#endif + +#ifdef GL_SGIX_tag_sample_buffer +#define glTagSampleBufferSGIX XGL_FUNCPTR(glTagSampleBufferSGIX) +#endif + +#ifdef GL_SGIX_texture_add_env +#endif + +#ifdef GL_SGIX_texture_coordinate_clamp +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif + +#ifdef GL_SGIX_texture_lod_bias +#endif + +#ifdef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifdef GL_SGIX_texture_range +#define GL_RGB_SIGNED_SGIX 0x85E0 +#define GL_RGBA_SIGNED_SGIX 0x85E1 +#define GL_ALPHA_SIGNED_SGIX 0x85E2 +#define GL_LUMINANCE_SIGNED_SGIX 0x85E3 +#define GL_INTENSITY_SIGNED_SGIX 0x85E4 +#define GL_LUMINANCE_ALPHA_SIGNED_SGIX 0x85E5 +#define GL_RGB16_SIGNED_SGIX 0x85E6 +#define GL_RGBA16_SIGNED_SGIX 0x85E7 +#define GL_ALPHA16_SIGNED_SGIX 0x85E8 +#define GL_LUMINANCE16_SIGNED_SGIX 0x85E9 +#define GL_INTENSITY16_SIGNED_SGIX 0x85EA +#define GL_LUMINANCE16_ALPHA16_SIGNED_SGIX 0x85EB +#define GL_RGB_EXTENDED_RANGE_SGIX 0x85EC +#define GL_RGBA_EXTENDED_RANGE_SGIX 0x85ED +#define GL_ALPHA_EXTENDED_RANGE_SGIX 0x85EE +#define GL_LUMINANCE_EXTENDED_RANGE_SGIX 0x85EF +#define GL_INTENSITY_EXTENDED_RANGE_SGIX 0x85F0 +#define GL_LUMINANCE_ALPHA_EXTENDED_RANGE_SGIX 0x85F1 +#define GL_RGB16_EXTENDED_RANGE_SGIX 0x85F2 +#define GL_RGBA16_EXTENDED_RANGE_SGIX 0x85F3 +#define GL_ALPHA16_EXTENDED_RANGE_SGIX 0x85F4 +#define GL_LUMINANCE16_EXTENDED_RANGE_SGIX 0x85F5 +#define GL_INTENSITY16_EXTENDED_RANGE_SGIX 0x85F6 +#define GL_LUMINANCE16_ALPHA16_EXTENDED_RANGE_SGIX 0x85F7 +#define GL_MIN_LUMINANCE_SGIS 0x85F8 +#define GL_MAX_LUMINANCE_SGIS 0x85F9 +#define GL_MIN_INTENSITY_SGIS 0x85FA +#define GL_MAX_INTENSITY_SGIS 0x85FB +#endif + +#ifdef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifdef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifdef GL_SGIX_vertex_preclip_hint +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifdef GL_SGIX_ycrcb +#endif + +#ifdef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifdef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#define glColorTableParameterfvSGI XGL_FUNCPTR(glColorTableParameterfvSGI) +#define glColorTableParameterivSGI XGL_FUNCPTR(glColorTableParameterivSGI) +#define glColorTableSGI XGL_FUNCPTR(glColorTableSGI) +#define glCopyColorTableSGI XGL_FUNCPTR(glCopyColorTableSGI) +#define glGetColorTableParameterfvSGI XGL_FUNCPTR(glGetColorTableParameterfvSGI) +#define glGetColorTableParameterivSGI XGL_FUNCPTR(glGetColorTableParameterivSGI) +#define glGetColorTableSGI XGL_FUNCPTR(glGetColorTableSGI) +#endif + +#ifdef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifdef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#define glFinishTextureSUNX XGL_FUNCPTR(glFinishTextureSUNX) +#endif + +#ifdef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifdef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#define glGlobalAlphaFactorbSUN XGL_FUNCPTR(glGlobalAlphaFactorbSUN) +#define glGlobalAlphaFactordSUN XGL_FUNCPTR(glGlobalAlphaFactordSUN) +#define glGlobalAlphaFactorfSUN XGL_FUNCPTR(glGlobalAlphaFactorfSUN) +#define glGlobalAlphaFactoriSUN XGL_FUNCPTR(glGlobalAlphaFactoriSUN) +#define glGlobalAlphaFactorsSUN XGL_FUNCPTR(glGlobalAlphaFactorsSUN) +#define glGlobalAlphaFactorubSUN XGL_FUNCPTR(glGlobalAlphaFactorubSUN) +#define glGlobalAlphaFactoruiSUN XGL_FUNCPTR(glGlobalAlphaFactoruiSUN) +#define glGlobalAlphaFactorusSUN XGL_FUNCPTR(glGlobalAlphaFactorusSUN) +#endif + +#ifdef GL_SUN_mesh_array +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +#endif + +#ifdef GL_SUN_read_video_pixels +#define glReadVideoPixelsSUN XGL_FUNCPTR(glReadVideoPixelsSUN) +#endif + +#ifdef GL_SUN_slice_accum +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif + +#ifdef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x01 +#define GL_REPLACE_MIDDLE_SUN 0x02 +#define GL_REPLACE_OLDEST_SUN 0x03 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#define glReplacementCodePointerSUN XGL_FUNCPTR(glReplacementCodePointerSUN) +#define glReplacementCodeubSUN XGL_FUNCPTR(glReplacementCodeubSUN) +#define glReplacementCodeubvSUN XGL_FUNCPTR(glReplacementCodeubvSUN) +#define glReplacementCodeuiSUN XGL_FUNCPTR(glReplacementCodeuiSUN) +#define glReplacementCodeuivSUN XGL_FUNCPTR(glReplacementCodeuivSUN) +#define glReplacementCodeusSUN XGL_FUNCPTR(glReplacementCodeusSUN) +#define glReplacementCodeusvSUN XGL_FUNCPTR(glReplacementCodeusvSUN) +#endif + +#ifdef GL_SUN_vertex +#define glColor3fVertex3fSUN XGL_FUNCPTR(glColor3fVertex3fSUN) +#define glColor3fVertex3fvSUN XGL_FUNCPTR(glColor3fVertex3fvSUN) +#define glColor4fNormal3fVertex3fSUN XGL_FUNCPTR(glColor4fNormal3fVertex3fSUN) +#define glColor4fNormal3fVertex3fvSUN XGL_FUNCPTR(glColor4fNormal3fVertex3fvSUN) +#define glColor4ubVertex2fSUN XGL_FUNCPTR(glColor4ubVertex2fSUN) +#define glColor4ubVertex2fvSUN XGL_FUNCPTR(glColor4ubVertex2fvSUN) +#define glColor4ubVertex3fSUN XGL_FUNCPTR(glColor4ubVertex3fSUN) +#define glColor4ubVertex3fvSUN XGL_FUNCPTR(glColor4ubVertex3fvSUN) +#define glNormal3fVertex3fSUN XGL_FUNCPTR(glNormal3fVertex3fSUN) +#define glNormal3fVertex3fvSUN XGL_FUNCPTR(glNormal3fVertex3fvSUN) +#define glReplacementCodeuiColor3fVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiColor3fVertex3fSUN) +#define glReplacementCodeuiColor3fVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiColor3fVertex3fvSUN) +#define glReplacementCodeuiColor4fNormal3fVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiColor4fNormal3fVertex3fSUN) +#define glReplacementCodeuiColor4fNormal3fVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiColor4fNormal3fVertex3fvSUN) +#define glReplacementCodeuiColor4ubVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiColor4ubVertex3fSUN) +#define glReplacementCodeuiColor4ubVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiColor4ubVertex3fvSUN) +#define glReplacementCodeuiNormal3fVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiNormal3fVertex3fSUN) +#define glReplacementCodeuiNormal3fVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiNormal3fVertex3fvSUN) +#define glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN) +#define glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN) +#define glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN) +#define glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN) +#define glReplacementCodeuiTexCoord2fVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiTexCoord2fVertex3fSUN) +#define glReplacementCodeuiTexCoord2fVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiTexCoord2fVertex3fvSUN) +#define glReplacementCodeuiVertex3fSUN XGL_FUNCPTR(glReplacementCodeuiVertex3fSUN) +#define glReplacementCodeuiVertex3fvSUN XGL_FUNCPTR(glReplacementCodeuiVertex3fvSUN) +#define glTexCoord2fColor3fVertex3fSUN XGL_FUNCPTR(glTexCoord2fColor3fVertex3fSUN) +#define glTexCoord2fColor3fVertex3fvSUN XGL_FUNCPTR(glTexCoord2fColor3fVertex3fvSUN) +#define glTexCoord2fColor4fNormal3fVertex3fSUN XGL_FUNCPTR(glTexCoord2fColor4fNormal3fVertex3fSUN) +#define glTexCoord2fColor4fNormal3fVertex3fvSUN XGL_FUNCPTR(glTexCoord2fColor4fNormal3fVertex3fvSUN) +#define glTexCoord2fColor4ubVertex3fSUN XGL_FUNCPTR(glTexCoord2fColor4ubVertex3fSUN) +#define glTexCoord2fColor4ubVertex3fvSUN XGL_FUNCPTR(glTexCoord2fColor4ubVertex3fvSUN) +#define glTexCoord2fNormal3fVertex3fSUN XGL_FUNCPTR(glTexCoord2fNormal3fVertex3fSUN) +#define glTexCoord2fNormal3fVertex3fvSUN XGL_FUNCPTR(glTexCoord2fNormal3fVertex3fvSUN) +#define glTexCoord2fVertex3fSUN XGL_FUNCPTR(glTexCoord2fVertex3fSUN) +#define glTexCoord2fVertex3fvSUN XGL_FUNCPTR(glTexCoord2fVertex3fvSUN) +#define glTexCoord4fColor4fNormal3fVertex4fSUN XGL_FUNCPTR(glTexCoord4fColor4fNormal3fVertex4fSUN) +#define glTexCoord4fColor4fNormal3fVertex4fvSUN XGL_FUNCPTR(glTexCoord4fColor4fNormal3fVertex4fvSUN) +#define glTexCoord4fVertex4fSUN XGL_FUNCPTR(glTexCoord4fVertex4fSUN) +#define glTexCoord4fVertex4fvSUN XGL_FUNCPTR(glTexCoord4fVertex4fvSUN) +#endif + +#ifdef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifdef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifdef GL_WIN_swap_hint +#define glAddSwapHintRectWIN XGL_FUNCPTR(glAddSwapHintRectWIN) +#endif + + diff --git a/gfx/gl/ggl/generated/glefn.h b/gfx/gl/ggl/generated/glefn.h new file mode 100644 index 0000000..a7279a4 --- /dev/null +++ b/gfx/gl/ggl/generated/glefn.h @@ -0,0 +1,2382 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifdef GL_VERSION_1_2 +GL_GROUP_BEGIN(GL_VERSION_1_2) +GL_FUNCTION(glDrawRangeElements ,void, (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices)) +GL_FUNCTION(glTexImage3D, void, (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels)) +GL_FUNCTION(glTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels)) +GL_FUNCTION(glCopyTexSubImage3D, void, (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height)) +GL_GROUP_END() +#endif + +#ifdef GL_VERSION_1_3 +GL_GROUP_BEGIN(GL_VERSION_1_3) +GL_FUNCTION(glActiveTexture,void,(GLenum texture)) +GL_FUNCTION(glClientActiveTexture,void,(GLenum texture)) +GL_FUNCTION(glCompressedTexImage1D,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data)) +GL_FUNCTION(glCompressedTexImage2D,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data)) +GL_FUNCTION(glCompressedTexImage3D,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data)) +GL_FUNCTION(glCompressedTexSubImage1D,void,(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data)) +GL_FUNCTION(glCompressedTexSubImage2D,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data)) +GL_FUNCTION(glCompressedTexSubImage3D,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data)) +GL_FUNCTION(glGetCompressedTexImage,void,(GLenum target, GLint lod, GLvoid *img)) +GL_FUNCTION(glLoadTransposeMatrixd,void,(const GLdouble m[16])) +GL_FUNCTION(glLoadTransposeMatrixf,void,(const GLfloat m[16])) +GL_FUNCTION(glMultTransposeMatrixd,void,(const GLdouble m[16])) +GL_FUNCTION(glMultTransposeMatrixf,void,(const GLfloat m[16])) +GL_FUNCTION(glMultiTexCoord1d,void,(GLenum target, GLdouble s)) +GL_FUNCTION(glMultiTexCoord1dv,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord1f,void,(GLenum target, GLfloat s)) +GL_FUNCTION(glMultiTexCoord1fv,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord1i,void,(GLenum target, GLint s)) +GL_FUNCTION(glMultiTexCoord1iv,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord1s,void,(GLenum target, GLshort s)) +GL_FUNCTION(glMultiTexCoord1sv,void,(GLenum target, const GLshort *v)) +GL_FUNCTION(glMultiTexCoord2d,void,(GLenum target, GLdouble s, GLdouble t)) +GL_FUNCTION(glMultiTexCoord2dv,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord2f,void,(GLenum target, GLfloat s, GLfloat t)) +GL_FUNCTION(glMultiTexCoord2fv,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord2i,void,(GLenum target, GLint s, GLint t)) +GL_FUNCTION(glMultiTexCoord2iv,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord2s,void,(GLenum target, GLshort s, GLshort t)) +GL_FUNCTION(glMultiTexCoord2sv,void,(GLenum target, const GLshort *v)) +GL_FUNCTION(glMultiTexCoord3d,void,(GLenum target, GLdouble s, GLdouble t, GLdouble r)) +GL_FUNCTION(glMultiTexCoord3dv,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord3f,void,(GLenum target, GLfloat s, GLfloat t, GLfloat r)) +GL_FUNCTION(glMultiTexCoord3fv,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord3i,void,(GLenum target, GLint s, GLint t, GLint r)) +GL_FUNCTION(glMultiTexCoord3iv,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord3s,void,(GLenum target, GLshort s, GLshort t, GLshort r)) +GL_FUNCTION(glMultiTexCoord3sv,void,(GLenum target, const GLshort *v)) +GL_FUNCTION(glMultiTexCoord4d,void,(GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q)) +GL_FUNCTION(glMultiTexCoord4dv,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord4f,void,(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q)) +GL_FUNCTION(glMultiTexCoord4fv,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord4i,void,(GLenum target, GLint s, GLint t, GLint r, GLint q)) +GL_FUNCTION(glMultiTexCoord4iv,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord4s,void,(GLenum target, GLshort s, GLshort t, GLshort r, GLshort q)) +GL_FUNCTION(glMultiTexCoord4sv,void,(GLenum target, const GLshort *v)) +GL_FUNCTION(glSampleCoverage,void,(GLclampf value, GLboolean invert)) +GL_GROUP_END() +#endif + +#ifdef GL_VERSION_1_4 +GL_GROUP_BEGIN(GL_VERSION_1_4) +GL_FUNCTION(glBlendEquation,void,(GLenum mode)) +GL_FUNCTION(glBlendColor,void,(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)) +GL_FUNCTION(glFogCoordf,void,(GLfloat coord)) +GL_FUNCTION(glFogCoordfv,void,(const GLfloat *coord)) +GL_FUNCTION(glFogCoordd,void,(GLdouble coord)) +GL_FUNCTION(glFogCoorddv,void,(const GLdouble *coord)) +GL_FUNCTION(glFogCoordPointer,void,(GLenum type, GLsizei stride, const GLvoid *pointer)) +GL_FUNCTION(glMultiDrawArrays,void,(GLenum mode, GLint *first, GLsizei *count, GLsizei primcount)) +GL_FUNCTION(glMultiDrawElements,void,(GLenum mode, GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount)) +GL_FUNCTION(glPointParameterf,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glPointParameterfv,void,(GLenum pname, GLfloat *params)) +GL_FUNCTION(glSecondaryColor3b,void,(GLbyte red, GLbyte green, GLbyte blue)) +GL_FUNCTION(glSecondaryColor3bv,void,(const GLbyte *v)) +GL_FUNCTION(glSecondaryColor3d,void,(GLdouble red, GLdouble green, GLdouble blue)) +GL_FUNCTION(glSecondaryColor3dv,void,(const GLdouble *v)) +GL_FUNCTION(glSecondaryColor3f,void,(GLfloat red, GLfloat green, GLfloat blue)) +GL_FUNCTION(glSecondaryColor3fv,void,(const GLfloat *v)) +GL_FUNCTION(glSecondaryColor3i,void,(GLint red, GLint green, GLint blue)) +GL_FUNCTION(glSecondaryColor3iv,void,(const GLint *v)) +GL_FUNCTION(glSecondaryColor3s,void,(GLshort red, GLshort green, GLshort blue)) +GL_FUNCTION(glSecondaryColor3sv,void,(const GLshort *v)) +GL_FUNCTION(glSecondaryColor3ub,void,(GLubyte red, GLubyte green, GLubyte blue)) +GL_FUNCTION(glSecondaryColor3ubv,void,(const GLubyte *v)) +GL_FUNCTION(glSecondaryColor3ui,void,(GLuint red, GLuint green, GLuint blue)) +GL_FUNCTION(glSecondaryColor3uiv,void,(const GLuint *v)) +GL_FUNCTION(glSecondaryColor3us,void,(GLushort red, GLushort green, GLushort blue)) +GL_FUNCTION(glSecondaryColor3usv,void,(const GLushort *v)) +GL_FUNCTION(glSecondaryColorPointer,void,(GLint size, GLenum type, GLsizei stride, GLvoid *pointer)) +GL_FUNCTION(glBlendFuncSeparate,void,(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) +GL_FUNCTION(glWindowPos2d,void,(GLdouble x, GLdouble y)) +GL_FUNCTION(glWindowPos2f,void,(GLfloat x, GLfloat y)) +GL_FUNCTION(glWindowPos2i,void,(GLint x, GLint y)) +GL_FUNCTION(glWindowPos2s,void,(GLshort x, GLshort y)) +GL_FUNCTION(glWindowPos2dv,void,(const GLdouble *p)) +GL_FUNCTION(glWindowPos2fv,void,(const GLfloat *p)) +GL_FUNCTION(glWindowPos2iv,void,(const GLint *p)) +GL_FUNCTION(glWindowPos2sv,void,(const GLshort *p)) +GL_FUNCTION(glWindowPos3d,void,(GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glWindowPos3f,void,(GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glWindowPos3i,void,(GLint x, GLint y, GLint z)) +GL_FUNCTION(glWindowPos3s,void,(GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glWindowPos3dv,void,(const GLdouble *p)) +GL_FUNCTION(glWindowPos3fv,void,(const GLfloat *p)) +GL_FUNCTION(glWindowPos3iv,void,(const GLint *p)) +GL_FUNCTION(glWindowPos3sv,void,(const GLshort *p)) +GL_GROUP_END() +#endif + +#ifdef GL_VERSION_1_5 +GL_GROUP_BEGIN(GL_VERSION_1_5) +GL_FUNCTION(glGenQueries,void,(GLsizei n, GLuint* ids)) +GL_FUNCTION(glDeleteQueries,void,(GLsizei n, const GLuint* ids)) +GL_FUNCTION(glIsQuery,GLboolean,(GLuint id)) +GL_FUNCTION(glBeginQuery,void,(GLenum target, GLuint id)) +GL_FUNCTION(glEndQuery,void,(GLenum target)) +GL_FUNCTION(glGetQueryiv,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glGetQueryObjectiv,void,(GLuint id, GLenum pname, GLint* params)) +GL_FUNCTION(glGetQueryObjectuiv,void,(GLuint id, GLenum pname, GLuint* params)) +GL_FUNCTION(glBindBuffer,void,(GLenum target, GLuint buffer)) +GL_FUNCTION(glDeleteBuffers,void,(GLsizei n, const GLuint* buffers)) +GL_FUNCTION(glGenBuffers,void,(GLsizei n, GLuint* buffers)) +GL_FUNCTION(glIsBuffer,GLboolean,(GLuint buffer)) +GL_FUNCTION(glBufferData,void,(GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)) +GL_FUNCTION(glBufferSubData,void,(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data)) +GL_FUNCTION(glGetBufferSubData,void,(GLenum target, GLintptr offset, GLsizeiptr size, GLvoid* data)) +GL_FUNCTION(glMapBuffer,GLvoid*,(GLenum target, GLenum access)) +GL_FUNCTION(glUnmapBuffer,GLboolean,(GLenum target)) +GL_FUNCTION(glGetBufferParameteriv,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glGetBufferPointerv,void,(GLenum target, GLenum pname, GLvoid** params)) +GL_GROUP_END() +#endif + +#ifdef GL_VERSION_2_0 +GL_GROUP_BEGIN(GL_VERSION_2_0) +GL_FUNCTION(glBlendEquationSeparate,void,(GLenum, GLenum)) +GL_FUNCTION(glDrawBuffers,void,(GLsizei n, const GLenum* bufs)) +GL_FUNCTION(glStencilOpSeparate,void,(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) +GL_FUNCTION(glStencilFuncSeparate,void,(GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask)) +GL_FUNCTION(glStencilMaskSeparate,void,(GLenum, GLuint)) +GL_FUNCTION(glAttachShader,void,(GLuint program, GLuint shader)) +GL_FUNCTION(glBindAttribLocation,void,(GLuint program, GLuint index, const GLchar* name)) +GL_FUNCTION(glCompileShader,void,(GLuint shader)) +GL_FUNCTION(glCreateProgram,GLuint,(void)) +GL_FUNCTION(glCreateShader,GLuint,(GLenum type)) +GL_FUNCTION(glDeleteProgram,void,(GLuint program)) +GL_FUNCTION(glDeleteShader,void,(GLuint shader)) +GL_FUNCTION(glDetachShader,void,(GLuint program, GLuint shader)) +GL_FUNCTION(glDisableVertexAttribArray,void,(GLuint)) +GL_FUNCTION(glEnableVertexAttribArray,void,(GLuint)) +GL_FUNCTION(glGetActiveAttrib,void,(GLuint program, GLuint index, GLsizei maxLength, GLsizei* length, GLint* size, GLenum* type, GLchar* name)) +GL_FUNCTION(glGetActiveUniform,void,(GLuint program, GLuint index, GLsizei maxLength, GLsizei* length, GLint* size, GLenum* type, GLchar* name)) +GL_FUNCTION(glGetAttachedShaders,void,(GLuint program, GLsizei maxCount, GLsizei* count, GLuint* shaders)) +GL_FUNCTION(glGetAttribLocation,GLint,(GLuint program, const GLchar* name)) +GL_FUNCTION(glGetProgramiv,void,(GLuint program, GLenum pname, GLint* param)) +GL_FUNCTION(glGetProgramInfoLog,void,(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog)) +GL_FUNCTION(glGetShaderiv,void,(GLuint shader, GLenum pname, GLint* param)) +GL_FUNCTION(glGetShaderInfoLog,void,(GLuint shader, GLsizei bufSize, GLsizei* length, GLchar* infoLog)) +GL_FUNCTION(glShaderSource,void,(GLuint shader, GLsizei count, const GLchar** strings, const GLint* lengths)) +GL_FUNCTION(glGetUniformLocation,GLint,(GLint programObj, const GLchar* name)) +GL_FUNCTION(glGetUniformfv,void,(GLuint program, GLint location, GLfloat* params)) +GL_FUNCTION(glGetUniformiv,void,(GLuint program, GLint location, GLint* params)) +GL_FUNCTION(glGetVertexAttribdv,void,(GLuint, GLenum, GLdouble*)) +GL_FUNCTION(glGetVertexAttribfv,void,(GLuint, GLenum, GLfloat*)) +GL_FUNCTION(glGetVertexAttribiv,void,(GLuint, GLenum, GLint*)) +GL_FUNCTION(glGetVertexAttribPointerv,void,(GLuint, GLenum, GLvoid*)) +GL_FUNCTION(glIsProgram,GLboolean,(GLuint program)) +GL_FUNCTION(glIsShader,GLboolean,(GLuint shader)) +GL_FUNCTION(glLinkProgram,void,(GLuint program)) +GL_FUNCTION(glGetShaderSource,void,(GLint obj, GLsizei maxLength, GLsizei* length, GLchar* source)) +GL_FUNCTION(glUseProgram,void,(GLuint program)) +GL_FUNCTION(glUniform1f,void,(GLint location, GLfloat v0)) +GL_FUNCTION(glUniform1fv,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform1i,void,(GLint location, GLint v0)) +GL_FUNCTION(glUniform1iv,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniform2f,void,(GLint location, GLfloat v0, GLfloat v1)) +GL_FUNCTION(glUniform2fv,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform2i,void,(GLint location, GLint v0, GLint v1)) +GL_FUNCTION(glUniform2iv,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniform3f,void,(GLint location, GLfloat v0, GLfloat v1, GLfloat v2)) +GL_FUNCTION(glUniform3fv,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform3i,void,(GLint location, GLint v0, GLint v1, GLint v2)) +GL_FUNCTION(glUniform3iv,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniform4f,void,(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)) +GL_FUNCTION(glUniform4fv,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform4i,void,(GLint location, GLint v0, GLint v1, GLint v2, GLint v3)) +GL_FUNCTION(glUniform4iv,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniformMatrix2fv,void,(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)) +GL_FUNCTION(glUniformMatrix3fv,void,(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)) +GL_FUNCTION(glUniformMatrix4fv,void,(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)) +GL_FUNCTION(glValidateProgram,void,(GLuint program)) +GL_FUNCTION(glVertexAttrib1d,void,(GLuint index, GLdouble x)) +GL_FUNCTION(glVertexAttrib1dv,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib1f,void,(GLuint index, GLfloat x)) +GL_FUNCTION(glVertexAttrib1fv,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib1s,void,(GLuint index, GLshort x)) +GL_FUNCTION(glVertexAttrib1sv,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib2d,void,(GLuint index, GLdouble x, GLdouble y)) +GL_FUNCTION(glVertexAttrib2dv,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib2f,void,(GLuint index, GLfloat x, GLfloat y)) +GL_FUNCTION(glVertexAttrib2fv,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib2s,void,(GLuint index, GLshort x, GLshort y)) +GL_FUNCTION(glVertexAttrib2sv,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib3d,void,(GLuint index, GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glVertexAttrib3dv,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib3f,void,(GLuint index, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glVertexAttrib3fv,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib3s,void,(GLuint index, GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glVertexAttrib3sv,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4Nbv,void,(GLuint index, const GLbyte* v)) +GL_FUNCTION(glVertexAttrib4Niv,void,(GLuint index, const GLint* v)) +GL_FUNCTION(glVertexAttrib4Nsv,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4Nub,void,(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w)) +GL_FUNCTION(glVertexAttrib4Nubv,void,(GLuint index, const GLubyte* v)) +GL_FUNCTION(glVertexAttrib4Nuiv,void,(GLuint index, const GLuint* v)) +GL_FUNCTION(glVertexAttrib4Nusv,void,(GLuint index, const GLushort* v)) +GL_FUNCTION(glVertexAttrib4bv,void,(GLuint index, const GLbyte* v)) +GL_FUNCTION(glVertexAttrib4d,void,(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glVertexAttrib4dv,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib4f,void,(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glVertexAttrib4fv,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib4iv,void,(GLuint index, const GLint* v)) +GL_FUNCTION(glVertexAttrib4s,void,(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w)) +GL_FUNCTION(glVertexAttrib4sv,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4ubv,void,(GLuint index, const GLubyte* v)) +GL_FUNCTION(glVertexAttrib4uiv,void,(GLuint index, const GLuint* v)) +GL_FUNCTION(glVertexAttrib4usv,void,(GLuint index, const GLushort* v)) +GL_FUNCTION(glVertexAttribPointer,void,(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_3DFX_multisample +GL_GROUP_BEGIN(GL_3DFX_multisample) +GL_GROUP_END() +#endif + +#ifdef GL_3DFX_tbuffer +GL_GROUP_BEGIN(GL_3DFX_tbuffer) +GL_FUNCTION(glTbufferMask3DFX,void,(GLuint mask)) +GL_GROUP_END() +#endif + +#ifdef GL_3DFX_texture_compression_FXT1 +GL_GROUP_BEGIN(GL_3DFX_texture_compression_FXT1) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_client_storage +GL_GROUP_BEGIN(GL_APPLE_client_storage) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_element_array +GL_GROUP_BEGIN(GL_APPLE_element_array) +GL_FUNCTION(glDrawElementArrayAPPLE,void,(GLenum mode, GLint first, GLsizei count)) +GL_FUNCTION(glDrawRangeElementArrayAPPLE,void,(GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count)) +GL_FUNCTION(glElementPointerAPPLE,void,(GLenum type, const void* pointer)) +GL_FUNCTION(glMultiDrawElementArrayAPPLE,void,(GLenum mode, const GLint* first, const GLsizei *count, GLsizei primcount)) +GL_FUNCTION(glMultiDrawRangeElementArrayAPPLE,void,(GLenum mode, GLuint start, GLuint end, const GLint* first, const GLsizei *count, GLsizei primcount)) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_fence +GL_GROUP_BEGIN(GL_APPLE_fence) +GL_FUNCTION(glDeleteFencesAPPLE,void,(GLsizei n, const GLuint* fences)) +GL_FUNCTION(glFinishFenceAPPLE,void,(GLuint fence)) +GL_FUNCTION(glFinishObjectAPPLE,void,(GLenum object, GLint name)) +GL_FUNCTION(glGenFencesAPPLE,void,(GLsizei n, GLuint* fences)) +GL_FUNCTION(glIsFenceAPPLE,GLboolean,(GLuint fence)) +GL_FUNCTION(glSetFenceAPPLE,void,(GLuint fence)) +GL_FUNCTION(glTestFenceAPPLE,GLboolean,(GLuint fence)) +GL_FUNCTION(glTestObjectAPPLE,GLboolean,(GLenum object, GLuint name)) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_float_pixels +GL_GROUP_BEGIN(GL_APPLE_float_pixels) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_pixel_buffer +GL_GROUP_BEGIN(GL_APPLE_pixel_buffer) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_specular_vector +GL_GROUP_BEGIN(GL_APPLE_specular_vector) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_texture_range +GL_GROUP_BEGIN(GL_APPLE_texture_range) +GL_FUNCTION(glTextureRangeAPPLE,void,(GLenum target, GLsizei length, GLvoid *pointer)) +GL_FUNCTION(glGetTexParameterPointervAPPLE,void,(GLenum target, GLenum pname, GLvoid **params)) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_transform_hint +GL_GROUP_BEGIN(GL_APPLE_transform_hint) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_vertex_array_object +GL_GROUP_BEGIN(GL_APPLE_vertex_array_object) +GL_FUNCTION(glBindVertexArrayAPPLE,void,(GLuint array)) +GL_FUNCTION(glDeleteVertexArraysAPPLE,void,(GLsizei n, const GLuint* arrays)) +GL_FUNCTION(glGenVertexArraysAPPLE,void,(GLsizei n, const GLuint* arrays)) +GL_FUNCTION(glIsVertexArrayAPPLE,GLboolean,(GLuint array)) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_vertex_array_range +GL_GROUP_BEGIN(GL_APPLE_vertex_array_range) +GL_FUNCTION(glFlushVertexArrayRangeAPPLE,void,(GLsizei length, void* pointer)) +GL_FUNCTION(glVertexArrayParameteriAPPLE,void,(GLenum pname, GLint param)) +GL_FUNCTION(glVertexArrayRangeAPPLE,void,(GLsizei length, void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_APPLE_ycbcr_422 +GL_GROUP_BEGIN(GL_APPLE_ycbcr_422) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_color_buffer_float +GL_GROUP_BEGIN(GL_ARB_color_buffer_float) +GL_FUNCTION(glClampColorARB,void,(GLenum target, GLenum clamp)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_depth_texture +GL_GROUP_BEGIN(GL_ARB_depth_texture) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_draw_buffers +GL_GROUP_BEGIN(GL_ARB_draw_buffers) +GL_FUNCTION(glDrawBuffersARB,void,(GLsizei n, const GLenum* bufs)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_fragment_program +GL_GROUP_BEGIN(GL_ARB_fragment_program) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_fragment_program_shadow +GL_GROUP_BEGIN(GL_ARB_fragment_program_shadow) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_fragment_shader +GL_GROUP_BEGIN(GL_ARB_fragment_shader) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_half_float_pixel +GL_GROUP_BEGIN(GL_ARB_half_float_pixel) +GL_GROUP_END() +#endif + +#if defined(GL_ARB_imaging) && !defined(GL_VERSION_1_4) +GL_GROUP_BEGIN(GL_ARB_imaging) +GL_FUNCTION(glColorTable,void,(GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table)) +GL_FUNCTION(glColorSubTable,void,(GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data)) +GL_FUNCTION(glColorTableParameteriv,void,(GLenum target, GLenum pname, const GLint *params)) +GL_FUNCTION(glColorTableParameterfv,void,(GLenum target, GLenum pname, const GLfloat *params)) +GL_FUNCTION(glCopyColorSubTable,void,(GLenum target, GLsizei start, GLint x, GLint y, GLsizei width)) +GL_FUNCTION(glCopyColorTable,void,(GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width)) +GL_FUNCTION(glGetColorTable,void,(GLenum target, GLenum format, GLenum type, GLvoid *table)) +GL_FUNCTION(glGetColorTableParameterfv,void,(GLenum target, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetColorTableParameteriv,void,(GLenum target, GLenum pname, GLint *params)) +GL_FUNCTION(glHistogram,void,(GLenum target, GLsizei width, GLenum internalformat, GLboolean sink)) +GL_FUNCTION(glResetHistogram,void,(GLenum target)) +GL_FUNCTION(glGetHistogram,void,(GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values)) +GL_FUNCTION(glGetHistogramParameterfv,void,(GLenum target, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetHistogramParameteriv,void,(GLenum target, GLenum pname, GLint *params)) +GL_FUNCTION(glMinmax,void,(GLenum target, GLenum internalformat, GLboolean sink)) +GL_FUNCTION(glResetMinmax,void,(GLenum target)) +GL_FUNCTION(glGetMinmaxParameterfv,void,(GLenum target, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetMinmaxParameteriv,void,(GLenum target, GLenum pname, GLint *params)) +GL_FUNCTION(glConvolutionFilter1D,void,(GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image)) +GL_FUNCTION(glConvolutionFilter2D,void,(GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image)) +GL_FUNCTION(glConvolutionParameterf,void,(GLenum target, GLenum pname, GLfloat params)) +GL_FUNCTION(glConvolutionParameterfv,void,(GLenum target, GLenum pname, const GLfloat *params)) +GL_FUNCTION(glConvolutionParameteri,void,(GLenum target, GLenum pname, GLint params)) +GL_FUNCTION(glConvolutionParameteriv,void,(GLenum target, GLenum pname, const GLint *params)) +GL_FUNCTION(glCopyConvolutionFilter1D,void,(GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width)) +GL_FUNCTION(glCopyConvolutionFilter2D,void,(GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height)) +GL_FUNCTION(glGetConvolutionFilter,void,(GLenum target, GLenum format, GLenum type, GLvoid *image)) +GL_FUNCTION(glGetConvolutionParameterfv,void,(GLenum target, GLenum pname, GLfloat *params)) +GL_FUNCTION(glGetConvolutionParameteriv,void,(GLenum target, GLenum pname, GLint *params)) +GL_FUNCTION(glSeparableFilter2D,void,(GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column)) +GL_FUNCTION(glGetSeparableFilter,void,(GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span)) +GL_FUNCTION(glGetMinmax,void,(GLenum target, GLboolean reset, GLenum format, GLenum types, GLvoid *values)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_matrix_palette +GL_GROUP_BEGIN(GL_ARB_matrix_palette) +GL_FUNCTION(glCurrentPaletteMatrixARB,void,(GLint index)) +GL_FUNCTION(glMatrixIndexPointerARB,void,(GLint size, GLenum type, GLsizei stride, GLvoid *pointer)) +GL_FUNCTION(glMatrixIndexubvARB,void,(GLint size, GLubyte *indices)) +GL_FUNCTION(glMatrixIndexusvARB,void,(GLint size, GLushort *indices)) +GL_FUNCTION(glMatrixIndexuivARB,void,(GLint size, GLuint *indices)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_multisample +GL_GROUP_BEGIN(GL_ARB_multisample) +GL_FUNCTION(glSampleCoverageARB,void,(GLclampf value, GLboolean invert)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_multitexture +GL_GROUP_BEGIN(GL_ARB_multitexture) +GL_FUNCTION(glActiveTextureARB,void,(GLenum texture)) +GL_FUNCTION(glClientActiveTextureARB,void,(GLenum texture)) +GL_FUNCTION(glMultiTexCoord1dARB,void,(GLenum target, GLdouble s)) +GL_FUNCTION(glMultiTexCoord1dvARB,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord1fARB,void,(GLenum target, GLfloat s)) +GL_FUNCTION(glMultiTexCoord1fvARB,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord1iARB,void,(GLenum target, GLint s)) +GL_FUNCTION(glMultiTexCoord1ivARB,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord1sARB,void,(GLenum target, GLshort s)) +GL_FUNCTION(glMultiTexCoord1svARB,void,(GLenum target, const GLshort *v)) +GL_FUNCTION(glMultiTexCoord2dARB,void,(GLenum target, GLdouble s, GLdouble t)) +GL_FUNCTION(glMultiTexCoord2dvARB,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord2fARB,void,(GLenum target, GLfloat s, GLfloat t)) +GL_FUNCTION(glMultiTexCoord2fvARB,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord2iARB,void,(GLenum target, GLint s, GLint t)) +GL_FUNCTION(glMultiTexCoord2ivARB,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord2sARB,void,(GLenum target, GLshort s, GLshort t)) +GL_FUNCTION(glMultiTexCoord2svARB,void,(GLenum target, const GLshort *v)) +GL_FUNCTION(glMultiTexCoord3dARB,void,(GLenum target, GLdouble s, GLdouble t, GLdouble r)) +GL_FUNCTION(glMultiTexCoord3dvARB,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord3fARB,void,(GLenum target, GLfloat s, GLfloat t, GLfloat r)) +GL_FUNCTION(glMultiTexCoord3fvARB,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord3iARB,void,(GLenum target, GLint s, GLint t, GLint r)) +GL_FUNCTION(glMultiTexCoord3ivARB,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord3sARB,void,(GLenum target, GLshort s, GLshort t, GLshort r)) +GL_FUNCTION(glMultiTexCoord3svARB,void,(GLenum target, const GLshort *v)) +GL_FUNCTION(glMultiTexCoord4dARB,void,(GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q)) +GL_FUNCTION(glMultiTexCoord4dvARB,void,(GLenum target, const GLdouble *v)) +GL_FUNCTION(glMultiTexCoord4fARB,void,(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q)) +GL_FUNCTION(glMultiTexCoord4fvARB,void,(GLenum target, const GLfloat *v)) +GL_FUNCTION(glMultiTexCoord4iARB,void,(GLenum target, GLint s, GLint t, GLint r, GLint q)) +GL_FUNCTION(glMultiTexCoord4ivARB,void,(GLenum target, const GLint *v)) +GL_FUNCTION(glMultiTexCoord4sARB,void,(GLenum target, GLshort s, GLshort t, GLshort r, GLshort q)) +GL_FUNCTION(glMultiTexCoord4svARB,void,(GLenum target, const GLshort *v)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_occlusion_query +GL_GROUP_BEGIN(GL_ARB_occlusion_query) +GL_FUNCTION(glBeginQueryARB,void,(GLenum target, GLuint id)) +GL_FUNCTION(glDeleteQueriesARB,void,(GLsizei n, const GLuint* ids)) +GL_FUNCTION(glEndQueryARB,void,(GLenum target)) +GL_FUNCTION(glGenQueriesARB,void,(GLsizei n, GLuint* ids)) +GL_FUNCTION(glGetQueryObjectivARB,void,(GLuint id, GLenum pname, GLint* params)) +GL_FUNCTION(glGetQueryObjectuivARB,void,(GLuint id, GLenum pname, GLuint* params)) +GL_FUNCTION(glGetQueryivARB,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glIsQueryARB,GLboolean,(GLuint id)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_pixel_buffer_object +GL_GROUP_BEGIN(GL_ARB_pixel_buffer_object) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_point_parameters +GL_GROUP_BEGIN(GL_ARB_point_parameters) +GL_FUNCTION(glPointParameterfARB,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glPointParameterfvARB,void,(GLenum pname, GLfloat* params)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_point_sprite +GL_GROUP_BEGIN(GL_ARB_point_sprite) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_shader_objects +GL_GROUP_BEGIN(GL_ARB_shader_objects) +GL_FUNCTION(glAttachObjectARB,void,(GLhandleARB containerObj, GLhandleARB obj)) +GL_FUNCTION(glCompileShaderARB,void,(GLhandleARB shaderObj)) +GL_FUNCTION(glCreateProgramObjectARB,GLhandleARB,(void)) +GL_FUNCTION(glCreateShaderObjectARB,GLhandleARB,(GLenum shaderType)) +GL_FUNCTION(glDeleteObjectARB,void,(GLhandleARB obj)) +GL_FUNCTION(glDetachObjectARB,void,(GLhandleARB containerObj, GLhandleARB attachedObj)) +GL_FUNCTION(glGetActiveUniformARB,void,(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei* length, GLint *size, GLenum *type, GLcharARB *name)) +GL_FUNCTION(glGetAttachedObjectsARB,void,(GLhandleARB containerObj, GLsizei maxCount, GLsizei* count, GLhandleARB *obj)) +GL_FUNCTION(glGetHandleARB,GLhandleARB,(GLenum pname)) +GL_FUNCTION(glGetInfoLogARB,void,(GLhandleARB obj, GLsizei maxLength, GLsizei* length, GLcharARB *infoLog)) +GL_FUNCTION(glGetObjectParameterfvARB,void,(GLhandleARB obj, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetObjectParameterivARB,void,(GLhandleARB obj, GLenum pname, GLint* params)) +GL_FUNCTION(glGetShaderSourceARB,void,(GLhandleARB obj, GLsizei maxLength, GLsizei* length, GLcharARB *source)) +GL_FUNCTION(glGetUniformLocationARB,GLint,(GLhandleARB programObj, const GLcharARB* name)) +GL_FUNCTION(glGetUniformfvARB,void,(GLhandleARB programObj, GLint location, GLfloat* params)) +GL_FUNCTION(glGetUniformivARB,void,(GLhandleARB programObj, GLint location, GLint* params)) +GL_FUNCTION(glLinkProgramARB,void,(GLhandleARB programObj)) +GL_FUNCTION(glShaderSourceARB,void,(GLhandleARB shaderObj, GLsizei count, const GLcharARB ** string, const GLint *length)) +GL_FUNCTION(glUniform1fARB,void,(GLint location, GLfloat v0)) +GL_FUNCTION(glUniform1fvARB,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform1iARB,void,(GLint location, GLint v0)) +GL_FUNCTION(glUniform1ivARB,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniform2fARB,void,(GLint location, GLfloat v0, GLfloat v1)) +GL_FUNCTION(glUniform2fvARB,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform2iARB,void,(GLint location, GLint v0, GLint v1)) +GL_FUNCTION(glUniform2ivARB,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniform3fARB,void,(GLint location, GLfloat v0, GLfloat v1, GLfloat v2)) +GL_FUNCTION(glUniform3fvARB,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform3iARB,void,(GLint location, GLint v0, GLint v1, GLint v2)) +GL_FUNCTION(glUniform3ivARB,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniform4fARB,void,(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)) +GL_FUNCTION(glUniform4fvARB,void,(GLint location, GLsizei count, const GLfloat* value)) +GL_FUNCTION(glUniform4iARB,void,(GLint location, GLint v0, GLint v1, GLint v2, GLint v3)) +GL_FUNCTION(glUniform4ivARB,void,(GLint location, GLsizei count, const GLint* value)) +GL_FUNCTION(glUniformMatrix2fvARB,void,(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)) +GL_FUNCTION(glUniformMatrix3fvARB,void,(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)) +GL_FUNCTION(glUniformMatrix4fvARB,void,(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)) +GL_FUNCTION(glUseProgramObjectARB,void,(GLhandleARB programObj)) +GL_FUNCTION(glValidateProgramARB,void,(GLhandleARB programObj)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_shading_language_100 +GL_GROUP_BEGIN(GL_ARB_shading_language_100) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_shadow +GL_GROUP_BEGIN(GL_ARB_shadow) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_shadow_ambient +GL_GROUP_BEGIN(GL_ARB_shadow_ambient) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_border_clamp +GL_GROUP_BEGIN(GL_ARB_texture_border_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_compression +GL_GROUP_BEGIN(GL_ARB_texture_compression) +GL_FUNCTION(glCompressedTexImage1DARB,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data)) +GL_FUNCTION(glCompressedTexImage2DARB,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data)) +GL_FUNCTION(glCompressedTexImage3DARB,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data)) +GL_FUNCTION(glCompressedTexSubImage1DARB,void,(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data)) +GL_FUNCTION(glCompressedTexSubImage2DARB,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data)) +GL_FUNCTION(glCompressedTexSubImage3DARB,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data)) +GL_FUNCTION(glGetCompressedTexImageARB,void,(GLenum target, GLint lod, void* img)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_cube_map +GL_GROUP_BEGIN(GL_ARB_texture_cube_map) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_env_add +GL_GROUP_BEGIN(GL_ARB_texture_env_add) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_env_combine +GL_GROUP_BEGIN(GL_ARB_texture_env_combine) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_env_crossbar +GL_GROUP_BEGIN(GL_ARB_texture_env_crossbar) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_env_dot3 +GL_GROUP_BEGIN(GL_ARB_texture_env_dot3) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_float +GL_GROUP_BEGIN(GL_ARB_texture_float) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_mirrored_repeat +GL_GROUP_BEGIN(GL_ARB_texture_mirrored_repeat) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_non_power_of_two +GL_GROUP_BEGIN(GL_ARB_texture_non_power_of_two) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_texture_rectangle +GL_GROUP_BEGIN(GL_ARB_texture_rectangle) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_transpose_matrix +GL_GROUP_BEGIN(GL_ARB_transpose_matrix) +GL_FUNCTION(glLoadTransposeMatrixfARB,void,(GLfloat m[16])) +GL_FUNCTION(glLoadTransposeMatrixdARB,void,(GLdouble m[16])) +GL_FUNCTION(glMultTransposeMatrixfARB,void,(GLfloat m[16])) +GL_FUNCTION(glMultTransposeMatrixdARB,void,(GLdouble m[16])) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_vertex_blend +GL_GROUP_BEGIN(GL_ARB_vertex_blend) +GL_FUNCTION(glWeightbvARB,void,(GLint size, GLbyte *weights)) +GL_FUNCTION(glWeightsvARB,void,(GLint size, GLshort *weights)) +GL_FUNCTION(glWeightivARB,void,(GLint size, GLint *weights)) +GL_FUNCTION(glWeightfvARB,void,(GLint size, GLfloat *weights)) +GL_FUNCTION(glWeightdvARB,void,(GLint size, GLdouble *weights)) +GL_FUNCTION(glWeightubvARB,void,(GLint size, GLubyte *weights)) +GL_FUNCTION(glWeightusvARB,void,(GLint size, GLushort *weights)) +GL_FUNCTION(glWeightuivARB,void,(GLint size, GLuint *weights)) +GL_FUNCTION(glWeightPointerARB,void,(GLint size, GLenum type, GLsizei stride, GLvoid *pointer)) +GL_FUNCTION(glVertexBlendARB,void,(GLint count)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_vertex_buffer_object +GL_GROUP_BEGIN(GL_ARB_vertex_buffer_object) +GL_FUNCTION(glBindBufferARB,void,(GLenum target, GLuint buffer)) +GL_FUNCTION(glBufferDataARB,void,(GLenum target, GLsizeiptrARB size, const GLvoid* data, GLenum usage)) +GL_FUNCTION(glBufferSubDataARB,void,(GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid* data)) +GL_FUNCTION(glDeleteBuffersARB,void,(GLsizei n, const GLuint* buffers)) +GL_FUNCTION(glGenBuffersARB,void,(GLsizei n, GLuint* buffers)) +GL_FUNCTION(glGetBufferParameterivARB,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glGetBufferPointervARB,void,(GLenum target, GLenum pname, GLvoid** params)) +GL_FUNCTION(glGetBufferSubDataARB,void,(GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid* data)) +GL_FUNCTION(glIsBufferARB,GLboolean,(GLuint buffer)) +GL_FUNCTION(glMapBufferARB,GLvoid *,(GLenum target, GLenum access)) +GL_FUNCTION(glUnmapBufferARB,GLboolean,(GLenum target)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_vertex_program +GL_GROUP_BEGIN(GL_ARB_vertex_program) +GL_FUNCTION(glBindProgramARB,void,(GLenum target, GLuint program)) +GL_FUNCTION(glDeleteProgramsARB,void,(GLsizei n, const GLuint* programs)) +GL_FUNCTION(glDisableVertexAttribArrayARB,void,(GLuint index)) +GL_FUNCTION(glEnableVertexAttribArrayARB,void,(GLuint index)) +GL_FUNCTION(glGenProgramsARB,void,(GLsizei n, GLuint* programs)) +GL_FUNCTION(glGetProgramEnvParameterdvARB,void,(GLenum target, GLuint index, GLdouble* params)) +GL_FUNCTION(glGetProgramEnvParameterfvARB,void,(GLenum target, GLuint index, GLfloat* params)) +GL_FUNCTION(glGetProgramLocalParameterdvARB,void,(GLenum target, GLuint index, GLdouble* params)) +GL_FUNCTION(glGetProgramLocalParameterfvARB,void,(GLenum target, GLuint index, GLfloat* params)) +GL_FUNCTION(glGetProgramStringARB,void,(GLenum target, GLenum pname, void* string)) +GL_FUNCTION(glGetProgramivARB,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glGetVertexAttribPointervARB,void,(GLuint index, GLenum pname, GLvoid** pointer)) +GL_FUNCTION(glGetVertexAttribdvARB,void,(GLuint index, GLenum pname, GLdouble* params)) +GL_FUNCTION(glGetVertexAttribfvARB,void,(GLuint index, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetVertexAttribivARB,void,(GLuint index, GLenum pname, GLint* params)) +GL_FUNCTION(glIsProgramARB,GLboolean,(GLuint program)) +GL_FUNCTION(glProgramEnvParameter4dARB,void,(GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glProgramEnvParameter4dvARB,void,(GLenum target, GLuint index, const GLdouble* params)) +GL_FUNCTION(glProgramEnvParameter4fARB,void,(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glProgramEnvParameter4fvARB,void,(GLenum target, GLuint index, const GLfloat* params)) +GL_FUNCTION(glProgramLocalParameter4dARB,void,(GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glProgramLocalParameter4dvARB,void,(GLenum target, GLuint index, const GLdouble* params)) +GL_FUNCTION(glProgramLocalParameter4fARB,void,(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glProgramLocalParameter4fvARB,void,(GLenum target, GLuint index, const GLfloat* params)) +GL_FUNCTION(glProgramStringARB,void,(GLenum target, GLenum format, GLsizei len, const void* string)) +GL_FUNCTION(glVertexAttrib1dARB,void,(GLuint index, GLdouble x)) +GL_FUNCTION(glVertexAttrib1dvARB,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib1fARB,void,(GLuint index, GLfloat x)) +GL_FUNCTION(glVertexAttrib1fvARB,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib1sARB,void,(GLuint index, GLshort x)) +GL_FUNCTION(glVertexAttrib1svARB,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib2dARB,void,(GLuint index, GLdouble x, GLdouble y)) +GL_FUNCTION(glVertexAttrib2dvARB,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib2fARB,void,(GLuint index, GLfloat x, GLfloat y)) +GL_FUNCTION(glVertexAttrib2fvARB,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib2sARB,void,(GLuint index, GLshort x, GLshort y)) +GL_FUNCTION(glVertexAttrib2svARB,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib3dARB,void,(GLuint index, GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glVertexAttrib3dvARB,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib3fARB,void,(GLuint index, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glVertexAttrib3fvARB,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib3sARB,void,(GLuint index, GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glVertexAttrib3svARB,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4NbvARB,void,(GLuint index, const GLbyte* v)) +GL_FUNCTION(glVertexAttrib4NivARB,void,(GLuint index, const GLint* v)) +GL_FUNCTION(glVertexAttrib4NsvARB,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4NubARB,void,(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w)) +GL_FUNCTION(glVertexAttrib4NubvARB,void,(GLuint index, const GLubyte* v)) +GL_FUNCTION(glVertexAttrib4NuivARB,void,(GLuint index, const GLuint* v)) +GL_FUNCTION(glVertexAttrib4NusvARB,void,(GLuint index, const GLushort* v)) +GL_FUNCTION(glVertexAttrib4bvARB,void,(GLuint index, const GLbyte* v)) +GL_FUNCTION(glVertexAttrib4dARB,void,(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glVertexAttrib4dvARB,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib4fARB,void,(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glVertexAttrib4fvARB,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib4ivARB,void,(GLuint index, const GLint* v)) +GL_FUNCTION(glVertexAttrib4sARB,void,(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w)) +GL_FUNCTION(glVertexAttrib4svARB,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4ubvARB,void,(GLuint index, const GLubyte* v)) +GL_FUNCTION(glVertexAttrib4uivARB,void,(GLuint index, const GLuint* v)) +GL_FUNCTION(glVertexAttrib4usvARB,void,(GLuint index, const GLushort* v)) +GL_FUNCTION(glVertexAttribPointerARB,void,(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_vertex_shader +GL_GROUP_BEGIN(GL_ARB_vertex_shader) +GL_FUNCTION(glBindAttribLocationARB,void,(GLhandleARB programObj, GLuint index, const GLcharARB* name)) +GL_FUNCTION(glGetActiveAttribARB,void,(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei* length, GLint *size, GLenum *type, GLcharARB *name)) +GL_FUNCTION(glGetAttribLocationARB,GLint,(GLhandleARB programObj, const GLcharARB* name)) +GL_GROUP_END() +#endif + +#ifdef GL_ARB_window_pos +GL_GROUP_BEGIN(GL_ARB_window_pos) +GL_FUNCTION(glWindowPos2dARB,void,(GLdouble x, GLdouble y)) +GL_FUNCTION(glWindowPos2dvARB,void,(const GLdouble* p)) +GL_FUNCTION(glWindowPos2fARB,void,(GLfloat x, GLfloat y)) +GL_FUNCTION(glWindowPos2fvARB,void,(const GLfloat* p)) +GL_FUNCTION(glWindowPos2iARB,void,(GLint x, GLint y)) +GL_FUNCTION(glWindowPos2ivARB,void,(const GLint* p)) +GL_FUNCTION(glWindowPos2sARB,void,(GLshort x, GLshort y)) +GL_FUNCTION(glWindowPos2svARB,void,(const GLshort* p)) +GL_FUNCTION(glWindowPos3dARB,void,(GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glWindowPos3dvARB,void,(const GLdouble* p)) +GL_FUNCTION(glWindowPos3fARB,void,(GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glWindowPos3fvARB,void,(const GLfloat* p)) +GL_FUNCTION(glWindowPos3iARB,void,(GLint x, GLint y, GLint z)) +GL_FUNCTION(glWindowPos3ivARB,void,(const GLint* p)) +GL_FUNCTION(glWindowPos3sARB,void,(GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glWindowPos3svARB,void,(const GLshort* p)) +GL_GROUP_END() +#endif + +#ifdef GL_ATIX_point_sprites +GL_GROUP_BEGIN(GL_ATIX_point_sprites) +GL_GROUP_END() +#endif + +#ifdef GL_ATIX_texture_env_combine3 +GL_GROUP_BEGIN(GL_ATIX_texture_env_combine3) +GL_GROUP_END() +#endif + +#ifdef GL_ATIX_texture_env_route +GL_GROUP_BEGIN(GL_ATIX_texture_env_route) +GL_GROUP_END() +#endif + +#ifdef GL_ATIX_vertex_shader_output_point_size +GL_GROUP_BEGIN(GL_ATIX_vertex_shader_output_point_size) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_draw_buffers +GL_GROUP_BEGIN(GL_ATI_draw_buffers) +GL_FUNCTION(glDrawBuffersATI,void,(GLsizei n, const GLenum* bufs)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_element_array +GL_GROUP_BEGIN(GL_ATI_element_array) +GL_FUNCTION(glDrawElementArrayATI,void,(GLenum mode, GLsizei count)) +GL_FUNCTION(glDrawRangeElementArrayATI,void,(GLenum mode, GLuint start, GLuint end, GLsizei count)) +GL_FUNCTION(glElementPointerATI,void,(GLenum type, const void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_envmap_bumpmap +GL_GROUP_BEGIN(GL_ATI_envmap_bumpmap) +GL_FUNCTION(glTexBumpParameterivATI,void,(GLenum pname, GLint *param)) +GL_FUNCTION(glTexBumpParameterfvATI,void,(GLenum pname, GLfloat *param)) +GL_FUNCTION(glGetTexBumpParameterivATI,void,(GLenum pname, GLint *param)) +GL_FUNCTION(glGetTexBumpParameterfvATI,void,(GLenum pname, GLfloat *param)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_fragment_shader +GL_GROUP_BEGIN(GL_ATI_fragment_shader) +GL_FUNCTION(glAlphaFragmentOp1ATI,void,(GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod)) +GL_FUNCTION(glAlphaFragmentOp2ATI,void,(GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod)) +GL_FUNCTION(glAlphaFragmentOp3ATI,void,(GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod)) +GL_FUNCTION(glBeginFragmentShaderATI,void,(void)) +GL_FUNCTION(glBindFragmentShaderATI,void,(GLuint id)) +GL_FUNCTION(glColorFragmentOp1ATI,void,(GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod)) +GL_FUNCTION(glColorFragmentOp2ATI,void,(GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod)) +GL_FUNCTION(glColorFragmentOp3ATI,void,(GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod)) +GL_FUNCTION(glDeleteFragmentShaderATI,void,(GLuint id)) +GL_FUNCTION(glEndFragmentShaderATI,void,(void)) +GL_FUNCTION(glGenFragmentShadersATI,GLuint,(GLuint range)) +GL_FUNCTION(glPassTexCoordATI,void,(GLuint dst, GLuint coord, GLenum swizzle)) +GL_FUNCTION(glSampleMapATI,void,(GLuint dst, GLuint interp, GLenum swizzle)) +GL_FUNCTION(glSetFragmentShaderConstantATI,void,(GLuint dst, const GLfloat* value)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_map_object_buffer +GL_GROUP_BEGIN(GL_ATI_map_object_buffer) +GL_FUNCTION(glMapObjectBufferATI,void*,(GLuint buffer)) +GL_FUNCTION(glUnmapObjectBufferATI,void,(GLuint buffer)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_pn_triangles +GL_GROUP_BEGIN(GL_ATI_pn_triangles) +GL_FUNCTION(glPNTrianglesiATI,void,(GLenum pname, GLint param)) +GL_FUNCTION(glPNTrianglesfATI,void,(GLenum pname, GLfloat param)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_separate_stencil +GL_GROUP_BEGIN(GL_ATI_separate_stencil) +GL_FUNCTION(glStencilOpSeparateATI,void,(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass)) +GL_FUNCTION(glStencilFuncSeparateATI,void,(GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_text_fragment_shader +GL_GROUP_BEGIN(GL_ATI_text_fragment_shader) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_texture_compression_3dc +GL_GROUP_BEGIN(GL_ATI_texture_compression_3dc) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_texture_env_combine3 +GL_GROUP_BEGIN(GL_ATI_texture_env_combine3) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_texture_float +GL_GROUP_BEGIN(GL_ATI_texture_float) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_texture_mirror_once +GL_GROUP_BEGIN(GL_ATI_texture_mirror_once) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_vertex_array_object +GL_GROUP_BEGIN(GL_ATI_vertex_array_object) +GL_FUNCTION(glArrayObjectATI,void,(GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset)) +GL_FUNCTION(glFreeObjectBufferATI,void,(GLuint buffer)) +GL_FUNCTION(glGetArrayObjectfvATI,void,(GLenum array, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetArrayObjectivATI,void,(GLenum array, GLenum pname, GLint* params)) +GL_FUNCTION(glGetObjectBufferfvATI,void,(GLuint buffer, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetObjectBufferivATI,void,(GLuint buffer, GLenum pname, GLint* params)) +GL_FUNCTION(glGetVariantArrayObjectfvATI,void,(GLuint id, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetVariantArrayObjectivATI,void,(GLuint id, GLenum pname, GLint* params)) +GL_FUNCTION(glIsObjectBufferATI,GLboolean,(GLuint buffer)) +GL_FUNCTION(glNewObjectBufferATI,GLuint,(GLsizei size, const void* pointer, GLenum usage)) +GL_FUNCTION(glUpdateObjectBufferATI,void,(GLuint buffer, GLuint offset, GLsizei size, const void* pointer, GLenum preserve)) +GL_FUNCTION(glVariantArrayObjectATI,void,(GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_vertex_attrib_array_object +GL_GROUP_BEGIN(GL_ATI_vertex_attrib_array_object) +GL_FUNCTION(glGetVertexAttribArrayObjectfvATI,void,(GLuint index, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetVertexAttribArrayObjectivATI,void,(GLuint index, GLenum pname, GLint* params)) +GL_FUNCTION(glVertexAttribArrayObjectATI,void,(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset)) +GL_GROUP_END() +#endif + +#ifdef GL_ATI_vertex_streams +GL_GROUP_BEGIN(GL_ATI_vertex_streams) +GL_FUNCTION(glClientActiveVertexStreamATI,void,(GLenum stream)) +GL_FUNCTION(glVertexBlendEnviATI,void,(GLenum pname, GLint param)) +GL_FUNCTION(glVertexBlendEnvfATI,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glVertexStream2sATI,void,(GLenum stream, GLshort x, GLshort y)) +GL_FUNCTION(glVertexStream2svATI,void,(GLenum stream, const GLshort *v)) +GL_FUNCTION(glVertexStream2iATI,void,(GLenum stream, GLint x, GLint y)) +GL_FUNCTION(glVertexStream2ivATI,void,(GLenum stream, const GLint *v)) +GL_FUNCTION(glVertexStream2fATI,void,(GLenum stream, GLfloat x, GLfloat y)) +GL_FUNCTION(glVertexStream2fvATI,void,(GLenum stream, const GLfloat *v)) +GL_FUNCTION(glVertexStream2dATI,void,(GLenum stream, GLdouble x, GLdouble y)) +GL_FUNCTION(glVertexStream2dvATI,void,(GLenum stream, const GLdouble *v)) +GL_FUNCTION(glVertexStream3sATI,void,(GLenum stream, GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glVertexStream3svATI,void,(GLenum stream, const GLshort *v)) +GL_FUNCTION(glVertexStream3iATI,void,(GLenum stream, GLint x, GLint y, GLint z)) +GL_FUNCTION(glVertexStream3ivATI,void,(GLenum stream, const GLint *v)) +GL_FUNCTION(glVertexStream3fATI,void,(GLenum stream, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glVertexStream3fvATI,void,(GLenum stream, const GLfloat *v)) +GL_FUNCTION(glVertexStream3dATI,void,(GLenum stream, GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glVertexStream3dvATI,void,(GLenum stream, const GLdouble *v)) +GL_FUNCTION(glVertexStream4sATI,void,(GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w)) +GL_FUNCTION(glVertexStream4svATI,void,(GLenum stream, const GLshort *v)) +GL_FUNCTION(glVertexStream4iATI,void,(GLenum stream, GLint x, GLint y, GLint z, GLint w)) +GL_FUNCTION(glVertexStream4ivATI,void,(GLenum stream, const GLint *v)) +GL_FUNCTION(glVertexStream4fATI,void,(GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glVertexStream4fvATI,void,(GLenum stream, const GLfloat *v)) +GL_FUNCTION(glVertexStream4dATI,void,(GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glVertexStream4dvATI,void,(GLenum stream, const GLdouble *v)) +GL_FUNCTION(glNormalStream3bATI,void,(GLenum stream, GLbyte x, GLbyte y, GLbyte z)) +GL_FUNCTION(glNormalStream3bvATI,void,(GLenum stream, const GLbyte *v)) +GL_FUNCTION(glNormalStream3sATI,void,(GLenum stream, GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glNormalStream3svATI,void,(GLenum stream, const GLshort *v)) +GL_FUNCTION(glNormalStream3iATI,void,(GLenum stream, GLint x, GLint y, GLint z)) +GL_FUNCTION(glNormalStream3ivATI,void,(GLenum stream, const GLint *v)) +GL_FUNCTION(glNormalStream3fATI,void,(GLenum stream, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glNormalStream3fvATI,void,(GLenum stream, const GLfloat *v)) +GL_FUNCTION(glNormalStream3dATI,void,(GLenum stream, GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glNormalStream3dvATI,void,(GLenum stream, const GLdouble *v)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_422_pixels +GL_GROUP_BEGIN(GL_EXT_422_pixels) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_Cg_shader +GL_GROUP_BEGIN(GL_EXT_Cg_shader) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_abgr +GL_GROUP_BEGIN(GL_EXT_abgr) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_bgra +GL_GROUP_BEGIN(GL_EXT_bgra) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_blend_color +GL_GROUP_BEGIN(GL_EXT_blend_color) +GL_FUNCTION(glBlendColorEXT,void,(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_blend_equation_separate +GL_GROUP_BEGIN(GL_EXT_blend_equation_separate) +GL_FUNCTION(glBlendEquationSeparateEXT,void,(GLenum modeRGB, GLenum modeAlpha)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_blend_func_separate +GL_GROUP_BEGIN(GL_EXT_blend_func_separate) +GL_FUNCTION(glBlendFuncSeparateEXT,void,(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_blend_logic_op +GL_GROUP_BEGIN(GL_EXT_blend_logic_op) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_blend_minmax +GL_GROUP_BEGIN(GL_EXT_blend_minmax) +GL_FUNCTION(glBlendEquationEXT,void,(GLenum mode)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_blend_subtract +GL_GROUP_BEGIN(GL_EXT_blend_subtract) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_clip_volume_hint +GL_GROUP_BEGIN(GL_EXT_clip_volume_hint) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_cmyka +GL_GROUP_BEGIN(GL_EXT_cmyka) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_color_subtable +GL_GROUP_BEGIN(GL_EXT_color_subtable) +GL_FUNCTION(glColorSubTableEXT,void,(GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const void* data)) +GL_FUNCTION(glCopyColorSubTableEXT,void,(GLenum target, GLsizei start, GLint x, GLint y, GLsizei width)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_compiled_vertex_array +GL_GROUP_BEGIN(GL_EXT_compiled_vertex_array) +GL_FUNCTION(glLockArraysEXT,void,(GLint first, GLsizei count)) +GL_FUNCTION(glUnlockArraysEXT,void,(void)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_convolution +GL_GROUP_BEGIN(GL_EXT_convolution) +GL_FUNCTION(glConvolutionFilter1DEXT,void,(GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void* image)) +GL_FUNCTION(glConvolutionFilter2DEXT,void,(GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* image)) +GL_FUNCTION(glConvolutionParameterfEXT,void,(GLenum target, GLenum pname, GLfloat param)) +GL_FUNCTION(glConvolutionParameterfvEXT,void,(GLenum target, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glConvolutionParameteriEXT,void,(GLenum target, GLenum pname, GLint param)) +GL_FUNCTION(glConvolutionParameterivEXT,void,(GLenum target, GLenum pname, const GLint* params)) +GL_FUNCTION(glCopyConvolutionFilter1DEXT,void,(GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width)) +GL_FUNCTION(glCopyConvolutionFilter2DEXT,void,(GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height)) +GL_FUNCTION(glGetConvolutionFilterEXT,void,(GLenum target, GLenum format, GLenum type, void* image)) +GL_FUNCTION(glGetConvolutionParameterfvEXT,void,(GLenum target, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetConvolutionParameterivEXT,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glGetSeparableFilterEXT,void,(GLenum target, GLenum format, GLenum type, void* row, void* column, void* span)) +GL_FUNCTION(glSeparableFilter2DEXT,void,(GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* row, const void* column)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_coordinate_frame +GL_GROUP_BEGIN(GL_EXT_coordinate_frame) +GL_FUNCTION(glBinormalPointerEXT,void,(GLenum type, GLsizei stride, void* pointer)) +GL_FUNCTION(glTangentPointerEXT,void,(GLenum type, GLsizei stride, void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_copy_texture +GL_GROUP_BEGIN(GL_EXT_copy_texture) +GL_FUNCTION(glCopyTexImage1DEXT,void,(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border)) +GL_FUNCTION(glCopyTexImage2DEXT,void,(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)) +GL_FUNCTION(glCopyTexSubImage1DEXT,void,(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width)) +GL_FUNCTION(glCopyTexSubImage2DEXT,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)) +GL_FUNCTION(glCopyTexSubImage3DEXT,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_cull_vertex +GL_GROUP_BEGIN(GL_EXT_cull_vertex) +GL_FUNCTION(glCullParameterdvEXT,void,(GLenum pname, GLdouble* params)) +GL_FUNCTION(glCullParameterfvEXT,void,(GLenum pname, GLfloat* params)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_depth_bounds_test +GL_GROUP_BEGIN(GL_EXT_depth_bounds_test) +GL_FUNCTION(glDepthBoundsEXT,void,(GLclampd zmin, GLclampd zmax)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_draw_range_elements +GL_GROUP_BEGIN(GL_EXT_draw_range_elements) +GL_FUNCTION(glDrawRangeElementsEXT,void,(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_fog_coord +GL_GROUP_BEGIN(GL_EXT_fog_coord) +GL_FUNCTION(glFogCoordfEXT,void,(GLfloat coord)) +GL_FUNCTION(glFogCoordfvEXT,void,(const GLfloat *coord)) +GL_FUNCTION(glFogCoorddEXT,void,(GLdouble coord)) +GL_FUNCTION(glFogCoorddvEXT,void,(const GLdouble *coord)) +GL_FUNCTION(glFogCoordPointerEXT,void,(GLenum type, GLsizei stride, const GLvoid *pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_fragment_lighting +GL_GROUP_BEGIN(GL_EXT_fragment_lighting) +GL_FUNCTION(glFragmentColorMaterialEXT,void,(GLenum face, GLenum mode)) +GL_FUNCTION(glFragmentLightModelfEXT,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glFragmentLightModelfvEXT,void,(GLenum pname, GLfloat* params)) +GL_FUNCTION(glFragmentLightModeliEXT,void,(GLenum pname, GLint param)) +GL_FUNCTION(glFragmentLightModelivEXT,void,(GLenum pname, GLint* params)) +GL_FUNCTION(glFragmentLightfEXT,void,(GLenum light, GLenum pname, GLfloat param)) +GL_FUNCTION(glFragmentLightfvEXT,void,(GLenum light, GLenum pname, GLfloat* params)) +GL_FUNCTION(glFragmentLightiEXT,void,(GLenum light, GLenum pname, GLint param)) +GL_FUNCTION(glFragmentLightivEXT,void,(GLenum light, GLenum pname, GLint* params)) +GL_FUNCTION(glFragmentMaterialfEXT,void,(GLenum face, GLenum pname, const GLfloat param)) +GL_FUNCTION(glFragmentMaterialfvEXT,void,(GLenum face, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glFragmentMaterialiEXT,void,(GLenum face, GLenum pname, const GLint param)) +GL_FUNCTION(glFragmentMaterialivEXT,void,(GLenum face, GLenum pname, const GLint* params)) +GL_FUNCTION(glGetFragmentLightfvEXT,void,(GLenum light, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetFragmentLightivEXT,void,(GLenum light, GLenum pname, GLint* params)) +GL_FUNCTION(glGetFragmentMaterialfvEXT,void,(GLenum face, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glGetFragmentMaterialivEXT,void,(GLenum face, GLenum pname, const GLint* params)) +GL_FUNCTION(glLightEnviEXT,void,(GLenum pname, GLint param)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_framebuffer_blit +GL_GROUP_BEGIN(GL_EXT_framebuffer_blit) +GL_FUNCTION(glBlitFramebufferEXT,void,(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum fliter)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_framebuffer_object +GL_GROUP_BEGIN(GL_EXT_framebuffer_object) +GL_FUNCTION(glBindFramebufferEXT,void,(GLenum target, GLuint framebuffer)) +GL_FUNCTION(glBindRenderbufferEXT,void,(GLenum target, GLuint renderbuffer)) +GL_FUNCTION(glCheckFramebufferStatusEXT,GLenum,(GLenum target)) +GL_FUNCTION(glDeleteFramebuffersEXT,void,(GLsizei n, const GLuint* framebuffers)) +GL_FUNCTION(glDeleteRenderbuffersEXT,void,(GLsizei n, const GLuint* renderbuffers)) +GL_FUNCTION(glFramebufferRenderbufferEXT,void,(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)) +GL_FUNCTION(glFramebufferTexture1DEXT,void,(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) +GL_FUNCTION(glFramebufferTexture2DEXT,void,(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) +GL_FUNCTION(glFramebufferTexture3DEXT,void,(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset)) +GL_FUNCTION(glGenFramebuffersEXT,void,(GLsizei n, GLuint* framebuffers)) +GL_FUNCTION(glGenRenderbuffersEXT,void,(GLsizei n, GLuint* renderbuffers)) +GL_FUNCTION(glGenerateMipmapEXT,void,(GLenum target)) +GL_FUNCTION(glGetFramebufferAttachmentParameterivEXT,void,(GLenum target, GLenum attachment, GLenum pname, GLint* params)) +GL_FUNCTION(glGetRenderbufferParameterivEXT,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glIsFramebufferEXT,GLboolean,(GLuint framebuffer)) +GL_FUNCTION(glIsRenderbufferEXT,GLboolean,(GLuint renderbuffer)) +GL_FUNCTION(glRenderbufferStorageEXT,void,(GLenum target, GLenum internalformat, GLsizei width, GLsizei height)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_histogram +GL_GROUP_BEGIN(GL_EXT_histogram) +GL_FUNCTION(glGetHistogramEXT,void,(GLenum target, GLboolean reset, GLenum format, GLenum type, void* values)) +GL_FUNCTION(glGetHistogramParameterfvEXT,void,(GLenum target, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetHistogramParameterivEXT,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glGetMinmaxEXT,void,(GLenum target, GLboolean reset, GLenum format, GLenum type, void* values)) +GL_FUNCTION(glGetMinmaxParameterfvEXT,void,(GLenum target, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetMinmaxParameterivEXT,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glHistogramEXT,void,(GLenum target, GLsizei width, GLenum internalformat, GLboolean sink)) +GL_FUNCTION(glMinmaxEXT,void,(GLenum target, GLenum internalformat, GLboolean sink)) +GL_FUNCTION(glResetHistogramEXT,void,(GLenum target)) +GL_FUNCTION(glResetMinmaxEXT,void,(GLenum target)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_index_array_formats +GL_GROUP_BEGIN(GL_EXT_index_array_formats) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_index_func +GL_GROUP_BEGIN(GL_EXT_index_func) +GL_FUNCTION(glIndexFuncEXT,void,(GLenum func, GLfloat ref)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_index_material +GL_GROUP_BEGIN(GL_EXT_index_material) +GL_FUNCTION(glIndexMaterialEXT,void,(GLenum face, GLenum mode)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_index_texture +GL_GROUP_BEGIN(GL_EXT_index_texture) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_light_texture +GL_GROUP_BEGIN(GL_EXT_light_texture) +GL_FUNCTION(glApplyTextureEXT,void,(GLenum mode)) +GL_FUNCTION(glTextureLightEXT,void,(GLenum pname)) +GL_FUNCTION(glTextureMaterialEXT,void,(GLenum face, GLenum mode)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_misc_attribute +GL_GROUP_BEGIN(GL_EXT_misc_attribute) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_multi_draw_arrays +GL_GROUP_BEGIN(GL_EXT_multi_draw_arrays) +GL_FUNCTION(glMultiDrawArraysEXT,void,(GLenum mode, GLint* first, GLsizei *count, GLsizei primcount)) +GL_FUNCTION(glMultiDrawElementsEXT,void,(GLenum mode, GLsizei* count, GLenum type, const GLvoid **indices, GLsizei primcount)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_multisample +GL_GROUP_BEGIN(GL_EXT_multisample) +GL_FUNCTION(glSampleMaskEXT,void,(GLclampf value, GLboolean invert)) +GL_FUNCTION(glSamplePatternEXT,void,(GLenum pattern)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_packed_pixels +GL_GROUP_BEGIN(GL_EXT_packed_pixels) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_paletted_texture +GL_GROUP_BEGIN(GL_EXT_paletted_texture) +GL_FUNCTION(glColorTableEXT,void,(GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const void* data)) +GL_FUNCTION(glGetColorTableEXT,void,(GLenum target, GLenum format, GLenum type, void* data)) +GL_FUNCTION(glGetColorTableParameterfvEXT,void,(GLenum target, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetColorTableParameterivEXT,void,(GLenum target, GLenum pname, GLint* params)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_pixel_buffer_object +GL_GROUP_BEGIN(GL_EXT_pixel_buffer_object) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_pixel_transform +GL_GROUP_BEGIN(GL_EXT_pixel_transform) +GL_FUNCTION(glGetPixelTransformParameterfvEXT,void,(GLenum target, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glGetPixelTransformParameterivEXT,void,(GLenum target, GLenum pname, const GLint* params)) +GL_FUNCTION(glPixelTransformParameterfEXT,void,(GLenum target, GLenum pname, const GLfloat param)) +GL_FUNCTION(glPixelTransformParameterfvEXT,void,(GLenum target, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glPixelTransformParameteriEXT,void,(GLenum target, GLenum pname, const GLint param)) +GL_FUNCTION(glPixelTransformParameterivEXT,void,(GLenum target, GLenum pname, const GLint* params)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_pixel_transform_color_table +GL_GROUP_BEGIN(GL_EXT_pixel_transform_color_table) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_point_parameters +GL_GROUP_BEGIN(GL_EXT_point_parameters) +GL_FUNCTION(glPointParameterfEXT,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glPointParameterfvEXT,void,(GLenum pname, GLfloat* params)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_polygon_offset +GL_GROUP_BEGIN(GL_EXT_polygon_offset) +GL_FUNCTION(glPolygonOffsetEXT,void,(GLfloat factor, GLfloat bias)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_rescale_normal +GL_GROUP_BEGIN(GL_EXT_rescale_normal) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_scene_marker +GL_GROUP_BEGIN(GL_EXT_scene_marker) +GL_FUNCTION(glBeginSceneEXT,void,(void)) +GL_FUNCTION(glEndSceneEXT,void,(void)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_secondary_color +GL_GROUP_BEGIN(GL_EXT_secondary_color) +GL_FUNCTION(glSecondaryColor3bEXT,void,(GLbyte red, GLbyte green, GLbyte blue)) +GL_FUNCTION(glSecondaryColor3bvEXT,void,(const GLbyte *v)) +GL_FUNCTION(glSecondaryColor3dEXT,void,(GLdouble red, GLdouble green, GLdouble blue)) +GL_FUNCTION(glSecondaryColor3dvEXT,void,(const GLdouble *v)) +GL_FUNCTION(glSecondaryColor3fEXT,void,(GLfloat red, GLfloat green, GLfloat blue)) +GL_FUNCTION(glSecondaryColor3fvEXT,void,(const GLfloat *v)) +GL_FUNCTION(glSecondaryColor3iEXT,void,(GLint red, GLint green, GLint blue)) +GL_FUNCTION(glSecondaryColor3ivEXT,void,(const GLint *v)) +GL_FUNCTION(glSecondaryColor3sEXT,void,(GLshort red, GLshort green, GLshort blue)) +GL_FUNCTION(glSecondaryColor3svEXT,void,(const GLshort *v)) +GL_FUNCTION(glSecondaryColor3ubEXT,void,(GLubyte red, GLubyte green, GLubyte blue)) +GL_FUNCTION(glSecondaryColor3ubvEXT,void,(const GLubyte *v)) +GL_FUNCTION(glSecondaryColor3uiEXT,void,(GLuint red, GLuint green, GLuint blue)) +GL_FUNCTION(glSecondaryColor3uivEXT,void,(const GLuint *v)) +GL_FUNCTION(glSecondaryColor3usEXT,void,(GLushort red, GLushort green, GLushort blue)) +GL_FUNCTION(glSecondaryColor3usvEXT,void,(const GLushort *v)) +GL_FUNCTION(glSecondaryColorPointerEXT,void,(GLint size, GLenum type, GLsizei stride, GLvoid *pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_separate_specular_color +GL_GROUP_BEGIN(GL_EXT_separate_specular_color) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_shadow_funcs +GL_GROUP_BEGIN(GL_EXT_shadow_funcs) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_shared_texture_palette +GL_GROUP_BEGIN(GL_EXT_shared_texture_palette) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_stencil_two_side +GL_GROUP_BEGIN(GL_EXT_stencil_two_side) +GL_FUNCTION(glActiveStencilFaceEXT,void,(GLenum face)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_stencil_wrap +GL_GROUP_BEGIN(GL_EXT_stencil_wrap) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_subtexture +GL_GROUP_BEGIN(GL_EXT_subtexture) +GL_FUNCTION(glTexSubImage1DEXT,void,(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels)) +GL_FUNCTION(glTexSubImage2DEXT,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels)) +GL_FUNCTION(glTexSubImage3DEXT,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture +GL_GROUP_BEGIN(GL_EXT_texture) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture3D +GL_GROUP_BEGIN(GL_EXT_texture3D) +GL_FUNCTION(glTexImage3DEXT,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_compression_dxt1 +GL_GROUP_BEGIN(GL_EXT_texture_compression_dxt1) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_compression_s3tc +GL_GROUP_BEGIN(GL_EXT_texture_compression_s3tc) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_cube_map +GL_GROUP_BEGIN(GL_EXT_texture_cube_map) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_edge_clamp +GL_GROUP_BEGIN(GL_EXT_texture_edge_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_env +GL_GROUP_BEGIN(GL_EXT_texture_env) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_env_add +GL_GROUP_BEGIN(GL_EXT_texture_env_add) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_env_combine +GL_GROUP_BEGIN(GL_EXT_texture_env_combine) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_env_dot3 +GL_GROUP_BEGIN(GL_EXT_texture_env_dot3) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_filter_anisotropic +GL_GROUP_BEGIN(GL_EXT_texture_filter_anisotropic) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_lod_bias +GL_GROUP_BEGIN(GL_EXT_texture_lod_bias) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_mirror_clamp +GL_GROUP_BEGIN(GL_EXT_texture_mirror_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_object +GL_GROUP_BEGIN(GL_EXT_texture_object) +GL_FUNCTION(glAreTexturesResidentEXT,GLboolean,(GLsizei n, const GLuint* textures, GLboolean* residences)) +GL_FUNCTION(glBindTextureEXT,void,(GLenum target, GLuint texture)) +GL_FUNCTION(glDeleteTexturesEXT,void,(GLsizei n, const GLuint* textures)) +GL_FUNCTION(glGenTexturesEXT,void,(GLsizei n, GLuint* textures)) +GL_FUNCTION(glIsTextureEXT,GLboolean,(GLuint texture)) +GL_FUNCTION(glPrioritizeTexturesEXT,void,(GLsizei n, const GLuint* textures, const GLclampf* priorities)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_perturb_normal +GL_GROUP_BEGIN(GL_EXT_texture_perturb_normal) +GL_FUNCTION(glTextureNormalEXT,void,(GLenum mode)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_texture_rectangle +GL_GROUP_BEGIN(GL_EXT_texture_rectangle) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_vertex_array +GL_GROUP_BEGIN(GL_EXT_vertex_array) +GL_FUNCTION(glArrayElementEXT,void,(GLint i)) +GL_FUNCTION(glColorPointerEXT,void,(GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer)) +GL_FUNCTION(glDrawArraysEXT,void,(GLenum mode, GLint first, GLsizei count)) +GL_FUNCTION(glEdgeFlagPointerEXT,void,(GLsizei stride, GLsizei count, const GLboolean* pointer)) +GL_FUNCTION(glGetPointervEXT,void,(GLenum pname, void** params)) +GL_FUNCTION(glIndexPointerEXT,void,(GLenum type, GLsizei stride, GLsizei count, const void* pointer)) +GL_FUNCTION(glNormalPointerEXT,void,(GLenum type, GLsizei stride, GLsizei count, const void* pointer)) +GL_FUNCTION(glTexCoordPointerEXT,void,(GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer)) +GL_FUNCTION(glVertexPointerEXT,void,(GLint size, GLenum type, GLsizei stride, GLsizei count, const void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_vertex_shader +GL_GROUP_BEGIN(GL_EXT_vertex_shader) +GL_FUNCTION(glBeginVertexShaderEXT,void,(void)) +GL_FUNCTION(glEndVertexShaderEXT,void,(void)) +GL_FUNCTION(glBindVertexShaderEXT,void,(GLuint id)) +GL_FUNCTION(glGenVertexShadersEXT,GLuint,(GLuint range)) +GL_FUNCTION(glDeleteVertexShaderEXT,void,(GLuint id)) +GL_FUNCTION(glShaderOp1EXT,void,(GLenum op, GLuint res, GLuint arg1)) +GL_FUNCTION(glShaderOp2EXT,void,(GLenum op, GLuint res, GLuint arg1, GLuint arg2)) +GL_FUNCTION(glShaderOp3EXT,void,(GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3)) +GL_FUNCTION(glSwizzleEXT,void,(GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW)) +GL_FUNCTION(glWriteMaskEXT,void,(GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW)) +GL_FUNCTION(glInsertComponentEXT,void,(GLuint res, GLuint src, GLuint num)) +GL_FUNCTION(glExtractComponentEXT,void,(GLuint res, GLuint src, GLuint num)) +GL_FUNCTION(glGenSymbolsEXT,GLuint,(GLenum dataType, GLenum storageType, GLenum range, GLuint components)) +GL_FUNCTION(glSetInvariantEXT,void,(GLuint id, GLenum type, GLvoid *addr)) +GL_FUNCTION(glSetLocalConstantEXT,void,(GLuint id, GLenum type, GLvoid *addr)) +GL_FUNCTION(glVariantbvEXT,void,(GLuint id, GLbyte *addr)) +GL_FUNCTION(glVariantsvEXT,void,(GLuint id, GLshort *addr)) +GL_FUNCTION(glVariantivEXT,void,(GLuint id, GLint *addr)) +GL_FUNCTION(glVariantfvEXT,void,(GLuint id, GLfloat *addr)) +GL_FUNCTION(glVariantdvEXT,void,(GLuint id, GLdouble *addr)) +GL_FUNCTION(glVariantubvEXT,void,(GLuint id, GLubyte *addr)) +GL_FUNCTION(glVariantusvEXT,void,(GLuint id, GLushort *addr)) +GL_FUNCTION(glVariantuivEXT,void,(GLuint id, GLuint *addr)) +GL_FUNCTION(glVariantPointerEXT,void,(GLuint id, GLenum type, GLuint stride, GLvoid *addr)) +GL_FUNCTION(glEnableVariantClientStateEXT,void,(GLuint id)) +GL_FUNCTION(glDisableVariantClientStateEXT,void,(GLuint id)) +GL_FUNCTION(glBindLightParameterEXT,GLuint,(GLenum light, GLenum value)) +GL_FUNCTION(glBindMaterialParameterEXT,GLuint,(GLenum face, GLenum value)) +GL_FUNCTION(glBindTexGenParameterEXT,GLuint,(GLenum unit, GLenum coord, GLenum value)) +GL_FUNCTION(glBindTextureUnitParameterEXT,GLuint,(GLenum unit, GLenum value)) +GL_FUNCTION(glBindParameterEXT,GLuint,(GLenum value)) +GL_FUNCTION(glIsVariantEnabledEXT,GLboolean,(GLuint id, GLenum cap)) +GL_FUNCTION(glGetVariantBooleanvEXT,void,(GLuint id, GLenum value, GLboolean *data)) +GL_FUNCTION(glGetVariantIntegervEXT,void,(GLuint id, GLenum value, GLint *data)) +GL_FUNCTION(glGetVariantFloatvEXT,void,(GLuint id, GLenum value, GLfloat *data)) +GL_FUNCTION(glGetVariantPointervEXT,void,(GLuint id, GLenum value, GLvoid **data)) +GL_FUNCTION(glGetInvariantBooleanvEXT,void,(GLuint id, GLenum value, GLboolean *data)) +GL_FUNCTION(glGetInvariantIntegervEXT,void,(GLuint id, GLenum value, GLint *data)) +GL_FUNCTION(glGetInvariantFloatvEXT,void,(GLuint id, GLenum value, GLfloat *data)) +GL_FUNCTION(glGetLocalConstantBooleanvEXT,void,(GLuint id, GLenum value, GLboolean *data)) +GL_FUNCTION(glGetLocalConstantIntegervEXT,void,(GLuint id, GLenum value, GLint *data)) +GL_FUNCTION(glGetLocalConstantFloatvEXT,void,(GLuint id, GLenum value, GLfloat *data)) +GL_GROUP_END() +#endif + +#ifdef GL_EXT_vertex_weighting +GL_GROUP_BEGIN(GL_EXT_vertex_weighting) +GL_FUNCTION(glVertexWeightPointerEXT,void,(GLint size, GLenum type, GLsizei stride, void* pointer)) +GL_FUNCTION(glVertexWeightfEXT,void,(GLfloat weight)) +GL_FUNCTION(glVertexWeightfvEXT,void,(GLfloat* weight)) +GL_GROUP_END() +#endif + +#ifdef GL_GREMEDY_string_marker +GL_GROUP_BEGIN(GL_GREMEDY_string_marker) +GL_FUNCTION(glStringMarkerGREMEDY,void,(GLsizei len, const void* string)) +GL_GROUP_END() +#endif + +#ifdef GL_HP_convolution_border_modes +GL_GROUP_BEGIN(GL_HP_convolution_border_modes) +GL_GROUP_END() +#endif + +#ifdef GL_HP_image_transform +GL_GROUP_BEGIN(GL_HP_image_transform) +GL_FUNCTION(glGetImageTransformParameterfvHP,void,(GLenum target, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glGetImageTransformParameterivHP,void,(GLenum target, GLenum pname, const GLint* params)) +GL_FUNCTION(glImageTransformParameterfHP,void,(GLenum target, GLenum pname, const GLfloat param)) +GL_FUNCTION(glImageTransformParameterfvHP,void,(GLenum target, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glImageTransformParameteriHP,void,(GLenum target, GLenum pname, const GLint param)) +GL_FUNCTION(glImageTransformParameterivHP,void,(GLenum target, GLenum pname, const GLint* params)) +GL_GROUP_END() +#endif + +#ifdef GL_HP_occlusion_test +GL_GROUP_BEGIN(GL_HP_occlusion_test) +GL_GROUP_END() +#endif + +#ifdef GL_HP_texture_lighting +GL_GROUP_BEGIN(GL_HP_texture_lighting) +GL_GROUP_END() +#endif + +#ifdef GL_IBM_cull_vertex +GL_GROUP_BEGIN(GL_IBM_cull_vertex) +GL_GROUP_END() +#endif + +#ifdef GL_IBM_multimode_draw_arrays +GL_GROUP_BEGIN(GL_IBM_multimode_draw_arrays) +GL_FUNCTION(glMultiModeDrawArraysIBM,void,(const GLenum* mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride)) +GL_FUNCTION(glMultiModeDrawElementsIBM,void,(const GLenum* mode, const GLsizei *count, GLenum type, const GLvoid * const *indices, GLsizei primcount, GLint modestride)) +GL_GROUP_END() +#endif + +#ifdef GL_IBM_rasterpos_clip +GL_GROUP_BEGIN(GL_IBM_rasterpos_clip) +GL_GROUP_END() +#endif + +#ifdef GL_IBM_static_data +GL_GROUP_BEGIN(GL_IBM_static_data) +GL_GROUP_END() +#endif + +#ifdef GL_IBM_texture_mirrored_repeat +GL_GROUP_BEGIN(GL_IBM_texture_mirrored_repeat) +GL_GROUP_END() +#endif + +#ifdef GL_IBM_vertex_array_lists +GL_GROUP_BEGIN(GL_IBM_vertex_array_lists) +GL_FUNCTION(glColorPointerListIBM,void,(GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride)) +GL_FUNCTION(glEdgeFlagPointerListIBM,void,(GLint stride, const GLboolean ** pointer, GLint ptrstride)) +GL_FUNCTION(glFogCoordPointerListIBM,void,(GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride)) +GL_FUNCTION(glIndexPointerListIBM,void,(GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride)) +GL_FUNCTION(glNormalPointerListIBM,void,(GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride)) +GL_FUNCTION(glSecondaryColorPointerListIBM,void,(GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride)) +GL_FUNCTION(glTexCoordPointerListIBM,void,(GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride)) +GL_FUNCTION(glVertexPointerListIBM,void,(GLint size, GLenum type, GLint stride, const GLvoid ** pointer, GLint ptrstride)) +GL_GROUP_END() +#endif + +#ifdef GL_INGR_color_clamp +GL_GROUP_BEGIN(GL_INGR_color_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_INGR_interlace_read +GL_GROUP_BEGIN(GL_INGR_interlace_read) +GL_GROUP_END() +#endif + +#ifdef GL_INTEL_parallel_arrays +GL_GROUP_BEGIN(GL_INTEL_parallel_arrays) +GL_FUNCTION(glColorPointervINTEL,void,(GLint size, GLenum type, const void** pointer)) +GL_FUNCTION(glNormalPointervINTEL,void,(GLenum type, const void** pointer)) +GL_FUNCTION(glTexCoordPointervINTEL,void,(GLint size, GLenum type, const void** pointer)) +GL_FUNCTION(glVertexPointervINTEL,void,(GLint size, GLenum type, const void** pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_INTEL_texture_scissor +GL_GROUP_BEGIN(GL_INTEL_texture_scissor) +GL_FUNCTION(glTexScissorFuncINTEL,void,(GLenum target, GLenum lfunc, GLenum hfunc)) +GL_FUNCTION(glTexScissorINTEL,void,(GLenum target, GLclampf tlow, GLclampf thigh)) +GL_GROUP_END() +#endif + +#ifdef GL_KTX_buffer_region +GL_GROUP_BEGIN(GL_KTX_buffer_region) +GL_FUNCTION(glBufferRegionEnabledEXT,GLuint,(void)) +GL_FUNCTION(glNewBufferRegionEXT,GLuint,(GLenum region)) +GL_FUNCTION(glDeleteBufferRegionEXT,void,(GLenum region)) +GL_FUNCTION(glReadBufferRegionEXT,void,(GLuint region, GLint x, GLint y, GLsizei width, GLsizei height)) +GL_FUNCTION(glDrawBufferRegionEXT,void,(GLuint region, GLint x, GLint y, GLsizei width, GLsizei height, GLint xDest, GLint yDest)) +GL_GROUP_END() +#endif + +#ifdef GL_MESA_pack_invert +GL_GROUP_BEGIN(GL_MESA_pack_invert) +GL_GROUP_END() +#endif + +#ifdef GL_MESA_resize_buffers +GL_GROUP_BEGIN(GL_MESA_resize_buffers) +GL_FUNCTION(glResizeBuffersMESA,void,(void)) +GL_GROUP_END() +#endif + +#ifdef GL_MESA_window_pos +GL_GROUP_BEGIN(GL_MESA_window_pos) +GL_FUNCTION(glWindowPos2dMESA,void,(GLdouble x, GLdouble y)) +GL_FUNCTION(glWindowPos2dvMESA,void,(const GLdouble* p)) +GL_FUNCTION(glWindowPos2fMESA,void,(GLfloat x, GLfloat y)) +GL_FUNCTION(glWindowPos2fvMESA,void,(const GLfloat* p)) +GL_FUNCTION(glWindowPos2iMESA,void,(GLint x, GLint y)) +GL_FUNCTION(glWindowPos2ivMESA,void,(const GLint* p)) +GL_FUNCTION(glWindowPos2sMESA,void,(GLshort x, GLshort y)) +GL_FUNCTION(glWindowPos2svMESA,void,(const GLshort* p)) +GL_FUNCTION(glWindowPos3dMESA,void,(GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glWindowPos3dvMESA,void,(const GLdouble* p)) +GL_FUNCTION(glWindowPos3fMESA,void,(GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glWindowPos3fvMESA,void,(const GLfloat* p)) +GL_FUNCTION(glWindowPos3iMESA,void,(GLint x, GLint y, GLint z)) +GL_FUNCTION(glWindowPos3ivMESA,void,(const GLint* p)) +GL_FUNCTION(glWindowPos3sMESA,void,(GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glWindowPos3svMESA,void,(const GLshort* p)) +GL_FUNCTION(glWindowPos4dMESA,void,(GLdouble x, GLdouble y, GLdouble z, GLdouble)) +GL_FUNCTION(glWindowPos4dvMESA,void,(const GLdouble* p)) +GL_FUNCTION(glWindowPos4fMESA,void,(GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glWindowPos4fvMESA,void,(const GLfloat* p)) +GL_FUNCTION(glWindowPos4iMESA,void,(GLint x, GLint y, GLint z, GLint w)) +GL_FUNCTION(glWindowPos4ivMESA,void,(const GLint* p)) +GL_FUNCTION(glWindowPos4sMESA,void,(GLshort x, GLshort y, GLshort z, GLshort w)) +GL_FUNCTION(glWindowPos4svMESA,void,(const GLshort* p)) +GL_GROUP_END() +#endif + +#ifdef GL_MESA_ycbcr_texture +GL_GROUP_BEGIN(GL_MESA_ycbcr_texture) +GL_GROUP_END() +#endif + +#ifdef GL_NV_blend_square +GL_GROUP_BEGIN(GL_NV_blend_square) +GL_GROUP_END() +#endif + +#ifdef GL_NV_copy_depth_to_color +GL_GROUP_BEGIN(GL_NV_copy_depth_to_color) +GL_GROUP_END() +#endif + +#ifdef GL_NV_depth_clamp +GL_GROUP_BEGIN(GL_NV_depth_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_NV_evaluators +GL_GROUP_BEGIN(GL_NV_evaluators) +GL_FUNCTION(glEvalMapsNV,void,(GLenum target, GLenum mode)) +GL_FUNCTION(glGetMapAttribParameterfvNV,void,(GLenum target, GLuint index, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetMapAttribParameterivNV,void,(GLenum target, GLuint index, GLenum pname, GLint* params)) +GL_FUNCTION(glGetMapControlPointsNV,void,(GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, void* points)) +GL_FUNCTION(glGetMapParameterfvNV,void,(GLenum target, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetMapParameterivNV,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glMapControlPointsNV,void,(GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const void* points)) +GL_FUNCTION(glMapParameterfvNV,void,(GLenum target, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glMapParameterivNV,void,(GLenum target, GLenum pname, const GLint* params)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_fence +GL_GROUP_BEGIN(GL_NV_fence) +GL_FUNCTION(glDeleteFencesNV,void,(GLsizei n, const GLuint* fences)) +GL_FUNCTION(glFinishFenceNV,void,(GLuint fence)) +GL_FUNCTION(glGenFencesNV,void,(GLsizei n, GLuint* fences)) +GL_FUNCTION(glGetFenceivNV,void,(GLuint fence, GLenum pname, GLint* params)) +GL_FUNCTION(glIsFenceNV,GLboolean,(GLuint fence)) +GL_FUNCTION(glSetFenceNV,void,(GLuint fence, GLenum condition)) +GL_FUNCTION(glTestFenceNV,GLboolean,(GLuint fence)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_float_buffer +GL_GROUP_BEGIN(GL_NV_float_buffer) +GL_GROUP_END() +#endif + +#ifdef GL_NV_fog_distance +GL_GROUP_BEGIN(GL_NV_fog_distance) +GL_GROUP_END() +#endif + +#ifdef GL_NV_fragment_program +GL_GROUP_BEGIN(GL_NV_fragment_program) +GL_FUNCTION(glGetProgramNamedParameterdvNV,void,(GLuint id, GLsizei len, const GLubyte* name, GLdouble *params)) +GL_FUNCTION(glGetProgramNamedParameterfvNV,void,(GLuint id, GLsizei len, const GLubyte* name, GLfloat *params)) +GL_FUNCTION(glProgramNamedParameter4dNV,void,(GLuint id, GLsizei len, const GLubyte* name, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glProgramNamedParameter4dvNV,void,(GLuint id, GLsizei len, const GLubyte* name, const GLdouble v[])) +GL_FUNCTION(glProgramNamedParameter4fNV,void,(GLuint id, GLsizei len, const GLubyte* name, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glProgramNamedParameter4fvNV,void,(GLuint id, GLsizei len, const GLubyte* name, const GLfloat v[])) +GL_GROUP_END() +#endif + +#ifdef GL_NV_fragment_program2 +GL_GROUP_BEGIN(GL_NV_fragment_program2) +GL_GROUP_END() +#endif + +#ifdef GL_NV_fragment_program_option +GL_GROUP_BEGIN(GL_NV_fragment_program_option) +GL_GROUP_END() +#endif + +#ifdef GL_NV_half_float +GL_GROUP_BEGIN(GL_NV_half_float) +GL_FUNCTION(glColor3hNV,void,(GLhalf red, GLhalf green, GLhalf blue)) +GL_FUNCTION(glColor3hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glColor4hNV,void,(GLhalf red, GLhalf green, GLhalf blue, GLhalf alpha)) +GL_FUNCTION(glColor4hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glFogCoordhNV,void,(GLhalf fog)) +GL_FUNCTION(glFogCoordhvNV,void,(const GLhalf* fog)) +GL_FUNCTION(glMultiTexCoord1hNV,void,(GLenum target, GLhalf s)) +GL_FUNCTION(glMultiTexCoord1hvNV,void,(GLenum target, const GLhalf* v)) +GL_FUNCTION(glMultiTexCoord2hNV,void,(GLenum target, GLhalf s, GLhalf t)) +GL_FUNCTION(glMultiTexCoord2hvNV,void,(GLenum target, const GLhalf* v)) +GL_FUNCTION(glMultiTexCoord3hNV,void,(GLenum target, GLhalf s, GLhalf t, GLhalf r)) +GL_FUNCTION(glMultiTexCoord3hvNV,void,(GLenum target, const GLhalf* v)) +GL_FUNCTION(glMultiTexCoord4hNV,void,(GLenum target, GLhalf s, GLhalf t, GLhalf r, GLhalf q)) +GL_FUNCTION(glMultiTexCoord4hvNV,void,(GLenum target, const GLhalf* v)) +GL_FUNCTION(glNormal3hNV,void,(GLhalf nx, GLhalf ny, GLhalf nz)) +GL_FUNCTION(glNormal3hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glSecondaryColor3hNV,void,(GLhalf red, GLhalf green, GLhalf blue)) +GL_FUNCTION(glSecondaryColor3hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glTexCoord1hNV,void,(GLhalf s)) +GL_FUNCTION(glTexCoord1hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glTexCoord2hNV,void,(GLhalf s, GLhalf t)) +GL_FUNCTION(glTexCoord2hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glTexCoord3hNV,void,(GLhalf s, GLhalf t, GLhalf r)) +GL_FUNCTION(glTexCoord3hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glTexCoord4hNV,void,(GLhalf s, GLhalf t, GLhalf r, GLhalf q)) +GL_FUNCTION(glTexCoord4hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glVertex2hNV,void,(GLhalf x, GLhalf y)) +GL_FUNCTION(glVertex2hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glVertex3hNV,void,(GLhalf x, GLhalf y, GLhalf z)) +GL_FUNCTION(glVertex3hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glVertex4hNV,void,(GLhalf x, GLhalf y, GLhalf z, GLhalf w)) +GL_FUNCTION(glVertex4hvNV,void,(const GLhalf* v)) +GL_FUNCTION(glVertexAttrib1hNV,void,(GLuint index, GLhalf x)) +GL_FUNCTION(glVertexAttrib1hvNV,void,(GLuint index, const GLhalf* v)) +GL_FUNCTION(glVertexAttrib2hNV,void,(GLuint index, GLhalf x, GLhalf y)) +GL_FUNCTION(glVertexAttrib2hvNV,void,(GLuint index, const GLhalf* v)) +GL_FUNCTION(glVertexAttrib3hNV,void,(GLuint index, GLhalf x, GLhalf y, GLhalf z)) +GL_FUNCTION(glVertexAttrib3hvNV,void,(GLuint index, const GLhalf* v)) +GL_FUNCTION(glVertexAttrib4hNV,void,(GLuint index, GLhalf x, GLhalf y, GLhalf z, GLhalf w)) +GL_FUNCTION(glVertexAttrib4hvNV,void,(GLuint index, const GLhalf* v)) +GL_FUNCTION(glVertexAttribs1hvNV,void,(GLuint index, GLsizei n, const GLhalf* v)) +GL_FUNCTION(glVertexAttribs2hvNV,void,(GLuint index, GLsizei n, const GLhalf* v)) +GL_FUNCTION(glVertexAttribs3hvNV,void,(GLuint index, GLsizei n, const GLhalf* v)) +GL_FUNCTION(glVertexAttribs4hvNV,void,(GLuint index, GLsizei n, const GLhalf* v)) +GL_FUNCTION(glVertexWeighthNV,void,(GLhalf weight)) +GL_FUNCTION(glVertexWeighthvNV,void,(const GLhalf* weight)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_light_max_exponent +GL_GROUP_BEGIN(GL_NV_light_max_exponent) +GL_GROUP_END() +#endif + +#ifdef GL_NV_multisample_filter_hint +GL_GROUP_BEGIN(GL_NV_multisample_filter_hint) +GL_GROUP_END() +#endif + +#ifdef GL_NV_occlusion_query +GL_GROUP_BEGIN(GL_NV_occlusion_query) +GL_FUNCTION(glBeginOcclusionQueryNV,void,(GLuint id)) +GL_FUNCTION(glDeleteOcclusionQueriesNV,void,(GLsizei n, const GLuint* ids)) +GL_FUNCTION(glEndOcclusionQueryNV,void,(void)) +GL_FUNCTION(glGenOcclusionQueriesNV,void,(GLsizei n, GLuint* ids)) +GL_FUNCTION(glGetOcclusionQueryivNV,void,(GLuint id, GLenum pname, GLint* params)) +GL_FUNCTION(glGetOcclusionQueryuivNV,void,(GLuint id, GLenum pname, GLuint* params)) +GL_FUNCTION(glIsOcclusionQueryNV,GLboolean,(GLuint id)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_packed_depth_stencil +GL_GROUP_BEGIN(GL_NV_packed_depth_stencil) +GL_GROUP_END() +#endif + +#ifdef GL_NV_pixel_data_range +GL_GROUP_BEGIN(GL_NV_pixel_data_range) +GL_FUNCTION(glFlushPixelDataRangeNV,void,(GLenum target)) +GL_FUNCTION(glPixelDataRangeNV,void,(GLenum target, GLsizei length, void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_point_sprite +GL_GROUP_BEGIN(GL_NV_point_sprite) +GL_FUNCTION(glPointParameteriNV,void,(GLenum pname, GLint param)) +GL_FUNCTION(glPointParameterivNV,void,(GLenum pname, const GLint* params)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_primitive_restart +GL_GROUP_BEGIN(GL_NV_primitive_restart) +GL_FUNCTION(glPrimitiveRestartIndexNV,void,(GLuint index)) +GL_FUNCTION(glPrimitiveRestartNV,void,(void)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_register_combiners +GL_GROUP_BEGIN(GL_NV_register_combiners) +GL_FUNCTION(glCombinerInputNV,void,(GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage)) +GL_FUNCTION(glCombinerOutputNV,void,(GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum)) +GL_FUNCTION(glCombinerParameterfNV,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glCombinerParameterfvNV,void,(GLenum pname, const GLfloat* params)) +GL_FUNCTION(glCombinerParameteriNV,void,(GLenum pname, GLint param)) +GL_FUNCTION(glCombinerParameterivNV,void,(GLenum pname, const GLint* params)) +GL_FUNCTION(glFinalCombinerInputNV,void,(GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage)) +GL_FUNCTION(glGetCombinerInputParameterfvNV,void,(GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetCombinerInputParameterivNV,void,(GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint* params)) +GL_FUNCTION(glGetCombinerOutputParameterfvNV,void,(GLenum stage, GLenum portion, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetCombinerOutputParameterivNV,void,(GLenum stage, GLenum portion, GLenum pname, GLint* params)) +GL_FUNCTION(glGetFinalCombinerInputParameterfvNV,void,(GLenum variable, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetFinalCombinerInputParameterivNV,void,(GLenum variable, GLenum pname, GLint* params)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_register_combiners2 +GL_GROUP_BEGIN(GL_NV_register_combiners2) +GL_FUNCTION(glCombinerStageParameterfvNV,void,(GLenum stage, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glGetCombinerStageParameterfvNV,void,(GLenum stage, GLenum pname, GLfloat* params)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texgen_emboss +GL_GROUP_BEGIN(GL_NV_texgen_emboss) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texgen_reflection +GL_GROUP_BEGIN(GL_NV_texgen_reflection) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texture_compression_vtc +GL_GROUP_BEGIN(GL_NV_texture_compression_vtc) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texture_env_combine4 +GL_GROUP_BEGIN(GL_NV_texture_env_combine4) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texture_expand_normal +GL_GROUP_BEGIN(GL_NV_texture_expand_normal) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texture_rectangle +GL_GROUP_BEGIN(GL_NV_texture_rectangle) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texture_shader +GL_GROUP_BEGIN(GL_NV_texture_shader) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texture_shader2 +GL_GROUP_BEGIN(GL_NV_texture_shader2) +GL_GROUP_END() +#endif + +#ifdef GL_NV_texture_shader3 +GL_GROUP_BEGIN(GL_NV_texture_shader3) +GL_GROUP_END() +#endif + +#ifdef GL_NV_vertex_array_range +GL_GROUP_BEGIN(GL_NV_vertex_array_range) +GL_FUNCTION(glFlushVertexArrayRangeNV,void,(void)) +GL_FUNCTION(glVertexArrayRangeNV,void,(GLsizei length, void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_vertex_array_range2 +GL_GROUP_BEGIN(GL_NV_vertex_array_range2) +GL_GROUP_END() +#endif + +#ifdef GL_NV_vertex_program +GL_GROUP_BEGIN(GL_NV_vertex_program) +GL_FUNCTION(glAreProgramsResidentNV,GLboolean,(GLsizei n, const GLuint* ids, GLboolean *residences)) +GL_FUNCTION(glBindProgramNV,void,(GLenum target, GLuint id)) +GL_FUNCTION(glDeleteProgramsNV,void,(GLsizei n, const GLuint* ids)) +GL_FUNCTION(glExecuteProgramNV,void,(GLenum target, GLuint id, const GLfloat* params)) +GL_FUNCTION(glGenProgramsNV,void,(GLsizei n, GLuint* ids)) +GL_FUNCTION(glGetProgramParameterdvNV,void,(GLenum target, GLuint index, GLenum pname, GLdouble* params)) +GL_FUNCTION(glGetProgramParameterfvNV,void,(GLenum target, GLuint index, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetProgramStringNV,void,(GLuint id, GLenum pname, GLubyte* program)) +GL_FUNCTION(glGetProgramivNV,void,(GLuint id, GLenum pname, GLint* params)) +GL_FUNCTION(glGetTrackMatrixivNV,void,(GLenum target, GLuint address, GLenum pname, GLint* params)) +GL_FUNCTION(glGetVertexAttribPointervNV,void,(GLuint index, GLenum pname, GLvoid** pointer)) +GL_FUNCTION(glGetVertexAttribdvNV,void,(GLuint index, GLenum pname, GLdouble* params)) +GL_FUNCTION(glGetVertexAttribfvNV,void,(GLuint index, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetVertexAttribivNV,void,(GLuint index, GLenum pname, GLint* params)) +GL_FUNCTION(glIsProgramNV,GLboolean,(GLuint id)) +GL_FUNCTION(glLoadProgramNV,void,(GLenum target, GLuint id, GLsizei len, const GLubyte* program)) +GL_FUNCTION(glProgramParameter4dNV,void,(GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glProgramParameter4dvNV,void,(GLenum target, GLuint index, const GLdouble* params)) +GL_FUNCTION(glProgramParameter4fNV,void,(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glProgramParameter4fvNV,void,(GLenum target, GLuint index, const GLfloat* params)) +GL_FUNCTION(glProgramParameters4dvNV,void,(GLenum target, GLuint index, GLuint num, const GLdouble* params)) +GL_FUNCTION(glProgramParameters4fvNV,void,(GLenum target, GLuint index, GLuint num, const GLfloat* params)) +GL_FUNCTION(glRequestResidentProgramsNV,void,(GLsizei n, GLuint* ids)) +GL_FUNCTION(glTrackMatrixNV,void,(GLenum target, GLuint address, GLenum matrix, GLenum transform)) +GL_FUNCTION(glVertexAttrib1dNV,void,(GLuint index, GLdouble x)) +GL_FUNCTION(glVertexAttrib1dvNV,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib1fNV,void,(GLuint index, GLfloat x)) +GL_FUNCTION(glVertexAttrib1fvNV,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib1sNV,void,(GLuint index, GLshort x)) +GL_FUNCTION(glVertexAttrib1svNV,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib2dNV,void,(GLuint index, GLdouble x, GLdouble y)) +GL_FUNCTION(glVertexAttrib2dvNV,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib2fNV,void,(GLuint index, GLfloat x, GLfloat y)) +GL_FUNCTION(glVertexAttrib2fvNV,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib2sNV,void,(GLuint index, GLshort x, GLshort y)) +GL_FUNCTION(glVertexAttrib2svNV,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib3dNV,void,(GLuint index, GLdouble x, GLdouble y, GLdouble z)) +GL_FUNCTION(glVertexAttrib3dvNV,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib3fNV,void,(GLuint index, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glVertexAttrib3fvNV,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib3sNV,void,(GLuint index, GLshort x, GLshort y, GLshort z)) +GL_FUNCTION(glVertexAttrib3svNV,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4dNV,void,(GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w)) +GL_FUNCTION(glVertexAttrib4dvNV,void,(GLuint index, const GLdouble* v)) +GL_FUNCTION(glVertexAttrib4fNV,void,(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glVertexAttrib4fvNV,void,(GLuint index, const GLfloat* v)) +GL_FUNCTION(glVertexAttrib4sNV,void,(GLuint index, GLshort x, GLshort y, GLshort z, GLshort w)) +GL_FUNCTION(glVertexAttrib4svNV,void,(GLuint index, const GLshort* v)) +GL_FUNCTION(glVertexAttrib4ubNV,void,(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w)) +GL_FUNCTION(glVertexAttrib4ubvNV,void,(GLuint index, const GLubyte* v)) +GL_FUNCTION(glVertexAttribPointerNV,void,(GLuint index, GLint size, GLenum type, GLsizei stride, const void* pointer)) +GL_FUNCTION(glVertexAttribs1dvNV,void,(GLuint index, GLsizei n, const GLdouble* v)) +GL_FUNCTION(glVertexAttribs1fvNV,void,(GLuint index, GLsizei n, const GLfloat* v)) +GL_FUNCTION(glVertexAttribs1svNV,void,(GLuint index, GLsizei n, const GLshort* v)) +GL_FUNCTION(glVertexAttribs2dvNV,void,(GLuint index, GLsizei n, const GLdouble* v)) +GL_FUNCTION(glVertexAttribs2fvNV,void,(GLuint index, GLsizei n, const GLfloat* v)) +GL_FUNCTION(glVertexAttribs2svNV,void,(GLuint index, GLsizei n, const GLshort* v)) +GL_FUNCTION(glVertexAttribs3dvNV,void,(GLuint index, GLsizei n, const GLdouble* v)) +GL_FUNCTION(glVertexAttribs3fvNV,void,(GLuint index, GLsizei n, const GLfloat* v)) +GL_FUNCTION(glVertexAttribs3svNV,void,(GLuint index, GLsizei n, const GLshort* v)) +GL_FUNCTION(glVertexAttribs4dvNV,void,(GLuint index, GLsizei n, const GLdouble* v)) +GL_FUNCTION(glVertexAttribs4fvNV,void,(GLuint index, GLsizei n, const GLfloat* v)) +GL_FUNCTION(glVertexAttribs4svNV,void,(GLuint index, GLsizei n, const GLshort* v)) +GL_FUNCTION(glVertexAttribs4ubvNV,void,(GLuint index, GLsizei n, const GLubyte* v)) +GL_GROUP_END() +#endif + +#ifdef GL_NV_vertex_program1_1 +GL_GROUP_BEGIN(GL_NV_vertex_program1_1) +GL_GROUP_END() +#endif + +#ifdef GL_NV_vertex_program2 +GL_GROUP_BEGIN(GL_NV_vertex_program2) +GL_GROUP_END() +#endif + +#ifdef GL_NV_vertex_program2_option +GL_GROUP_BEGIN(GL_NV_vertex_program2_option) +GL_GROUP_END() +#endif + +#ifdef GL_NV_vertex_program3 +GL_GROUP_BEGIN(GL_NV_vertex_program3) +GL_GROUP_END() +#endif + +#ifdef GL_OML_interlace +GL_GROUP_BEGIN(GL_OML_interlace) +GL_GROUP_END() +#endif + +#ifdef GL_OML_resample +GL_GROUP_BEGIN(GL_OML_resample) +GL_GROUP_END() +#endif + +#ifdef GL_OML_subsample +GL_GROUP_BEGIN(GL_OML_subsample) +GL_GROUP_END() +#endif + +#ifdef GL_PGI_misc_hints +GL_GROUP_BEGIN(GL_PGI_misc_hints) +GL_GROUP_END() +#endif + +#ifdef GL_PGI_vertex_hints +GL_GROUP_BEGIN(GL_PGI_vertex_hints) +GL_GROUP_END() +#endif + +#ifdef GL_REND_screen_coordinates +GL_GROUP_BEGIN(GL_REND_screen_coordinates) +GL_GROUP_END() +#endif + +#ifdef GL_S3_s3tc +GL_GROUP_BEGIN(GL_S3_s3tc) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_color_range +GL_GROUP_BEGIN(GL_SGIS_color_range) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_detail_texture +GL_GROUP_BEGIN(GL_SGIS_detail_texture) +GL_FUNCTION(glDetailTexFuncSGIS,void,(GLenum target, GLsizei n, const GLfloat* points)) +GL_FUNCTION(glGetDetailTexFuncSGIS,void,(GLenum target, GLfloat* points)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_fog_function +GL_GROUP_BEGIN(GL_SGIS_fog_function) +GL_FUNCTION(glFogFuncSGIS,void,(GLsizei n, const GLfloat* points)) +GL_FUNCTION(glGetFogFuncSGIS,void,(GLfloat* points)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_generate_mipmap +GL_GROUP_BEGIN(GL_SGIS_generate_mipmap) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_multisample +GL_GROUP_BEGIN(GL_SGIS_multisample) +GL_FUNCTION(glSampleMaskSGIS,void,(GLclampf value, GLboolean invert)) +GL_FUNCTION(glSamplePatternSGIS,void,(GLenum pattern)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_pixel_texture +GL_GROUP_BEGIN(GL_SGIS_pixel_texture) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_sharpen_texture +GL_GROUP_BEGIN(GL_SGIS_sharpen_texture) +GL_FUNCTION(glGetSharpenTexFuncSGIS,void,(GLenum target, GLfloat* points)) +GL_FUNCTION(glSharpenTexFuncSGIS,void,(GLenum target, GLsizei n, const GLfloat* points)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_texture4D +GL_GROUP_BEGIN(GL_SGIS_texture4D) +GL_FUNCTION(glTexImage4DSGIS,void,(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei extent, GLint border, GLenum format, GLenum type, const void* pixels)) +GL_FUNCTION(glTexSubImage4DSGIS,void,(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei extent, GLenum format, GLenum type, const void* pixels)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_texture_border_clamp +GL_GROUP_BEGIN(GL_SGIS_texture_border_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_texture_edge_clamp +GL_GROUP_BEGIN(GL_SGIS_texture_edge_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_texture_filter4 +GL_GROUP_BEGIN(GL_SGIS_texture_filter4) +GL_FUNCTION(glGetTexFilterFuncSGIS,void,(GLenum target, GLenum filter, GLfloat* weights)) +GL_FUNCTION(glTexFilterFuncSGIS,void,(GLenum target, GLenum filter, GLsizei n, const GLfloat* weights)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_texture_lod +GL_GROUP_BEGIN(GL_SGIS_texture_lod) +GL_GROUP_END() +#endif + +#ifdef GL_SGIS_texture_select +GL_GROUP_BEGIN(GL_SGIS_texture_select) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_async +GL_GROUP_BEGIN(GL_SGIX_async) +GL_FUNCTION(glAsyncMarkerSGIX,void,(GLuint marker)) +GL_FUNCTION(glDeleteAsyncMarkersSGIX,void,(GLuint marker, GLsizei range)) +GL_FUNCTION(glFinishAsyncSGIX,GLint,(GLuint* markerp)) +GL_FUNCTION(glGenAsyncMarkersSGIX,GLuint,(GLsizei range)) +GL_FUNCTION(glIsAsyncMarkerSGIX,GLboolean,(GLuint marker)) +GL_FUNCTION(glPollAsyncSGIX,GLint,(GLuint* markerp)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_async_histogram +GL_GROUP_BEGIN(GL_SGIX_async_histogram) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_async_pixel +GL_GROUP_BEGIN(GL_SGIX_async_pixel) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_blend_alpha_minmax +GL_GROUP_BEGIN(GL_SGIX_blend_alpha_minmax) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_clipmap +GL_GROUP_BEGIN(GL_SGIX_clipmap) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_depth_texture +GL_GROUP_BEGIN(GL_SGIX_depth_texture) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_flush_raster +GL_GROUP_BEGIN(GL_SGIX_flush_raster) +GL_FUNCTION(glFlushRasterSGIX,void,(void)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_fog_offset +GL_GROUP_BEGIN(GL_SGIX_fog_offset) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_fog_texture +GL_GROUP_BEGIN(GL_SGIX_fog_texture) +GL_FUNCTION(glTextureFogSGIX,void,(GLenum pname)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_fragment_specular_lighting +GL_GROUP_BEGIN(GL_SGIX_fragment_specular_lighting) +GL_FUNCTION(glFragmentColorMaterialSGIX,void,(GLenum face, GLenum mode)) +GL_FUNCTION(glFragmentLightModelfSGIX,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glFragmentLightModelfvSGIX,void,(GLenum pname, GLfloat* params)) +GL_FUNCTION(glFragmentLightModeliSGIX,void,(GLenum pname, GLint param)) +GL_FUNCTION(glFragmentLightModelivSGIX,void,(GLenum pname, GLint* params)) +GL_FUNCTION(glFragmentLightfSGIX,void,(GLenum light, GLenum pname, GLfloat param)) +GL_FUNCTION(glFragmentLightfvSGIX,void,(GLenum light, GLenum pname, GLfloat* params)) +GL_FUNCTION(glFragmentLightiSGIX,void,(GLenum light, GLenum pname, GLint param)) +GL_FUNCTION(glFragmentLightivSGIX,void,(GLenum light, GLenum pname, GLint* params)) +GL_FUNCTION(glFragmentMaterialfSGIX,void,(GLenum face, GLenum pname, const GLfloat param)) +GL_FUNCTION(glFragmentMaterialfvSGIX,void,(GLenum face, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glFragmentMaterialiSGIX,void,(GLenum face, GLenum pname, const GLint param)) +GL_FUNCTION(glFragmentMaterialivSGIX,void,(GLenum face, GLenum pname, const GLint* params)) +GL_FUNCTION(glGetFragmentLightfvSGIX,void,(GLenum light, GLenum value, GLfloat* data)) +GL_FUNCTION(glGetFragmentLightivSGIX,void,(GLenum light, GLenum value, GLint* data)) +GL_FUNCTION(glGetFragmentMaterialfvSGIX,void,(GLenum face, GLenum pname, const GLfloat* data)) +GL_FUNCTION(glGetFragmentMaterialivSGIX,void,(GLenum face, GLenum pname, const GLint* data)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_framezoom +GL_GROUP_BEGIN(GL_SGIX_framezoom) +GL_FUNCTION(glFrameZoomSGIX,void,(GLint factor)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_interlace +GL_GROUP_BEGIN(GL_SGIX_interlace) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_ir_instrument1 +GL_GROUP_BEGIN(GL_SGIX_ir_instrument1) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_list_priority +GL_GROUP_BEGIN(GL_SGIX_list_priority) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_pixel_texture +GL_GROUP_BEGIN(GL_SGIX_pixel_texture) +GL_FUNCTION(glPixelTexGenSGIX,void,(GLenum mode)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_pixel_texture_bits +GL_GROUP_BEGIN(GL_SGIX_pixel_texture_bits) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_reference_plane +GL_GROUP_BEGIN(GL_SGIX_reference_plane) +GL_FUNCTION(glReferencePlaneSGIX,void,(const GLdouble* equation)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_resample +GL_GROUP_BEGIN(GL_SGIX_resample) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_shadow +GL_GROUP_BEGIN(GL_SGIX_shadow) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_shadow_ambient +GL_GROUP_BEGIN(GL_SGIX_shadow_ambient) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_sprite +GL_GROUP_BEGIN(GL_SGIX_sprite) +GL_FUNCTION(glSpriteParameterfSGIX,void,(GLenum pname, GLfloat param)) +GL_FUNCTION(glSpriteParameterfvSGIX,void,(GLenum pname, GLfloat* params)) +GL_FUNCTION(glSpriteParameteriSGIX,void,(GLenum pname, GLint param)) +GL_FUNCTION(glSpriteParameterivSGIX,void,(GLenum pname, GLint* params)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_tag_sample_buffer +GL_GROUP_BEGIN(GL_SGIX_tag_sample_buffer) +GL_FUNCTION(glTagSampleBufferSGIX,void,(void)) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_texture_add_env +GL_GROUP_BEGIN(GL_SGIX_texture_add_env) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_texture_coordinate_clamp +GL_GROUP_BEGIN(GL_SGIX_texture_coordinate_clamp) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_texture_lod_bias +GL_GROUP_BEGIN(GL_SGIX_texture_lod_bias) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_texture_multi_buffer +GL_GROUP_BEGIN(GL_SGIX_texture_multi_buffer) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_texture_range +GL_GROUP_BEGIN(GL_SGIX_texture_range) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_texture_scale_bias +GL_GROUP_BEGIN(GL_SGIX_texture_scale_bias) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_vertex_preclip +GL_GROUP_BEGIN(GL_SGIX_vertex_preclip) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_vertex_preclip_hint +GL_GROUP_BEGIN(GL_SGIX_vertex_preclip_hint) +GL_GROUP_END() +#endif + +#ifdef GL_SGIX_ycrcb +GL_GROUP_BEGIN(GL_SGIX_ycrcb) +GL_GROUP_END() +#endif + +#ifdef GL_SGI_color_matrix +GL_GROUP_BEGIN(GL_SGI_color_matrix) +GL_GROUP_END() +#endif + +#ifdef GL_SGI_color_table +GL_GROUP_BEGIN(GL_SGI_color_table) +GL_FUNCTION(glColorTableParameterfvSGI,void,(GLenum target, GLenum pname, const GLfloat* params)) +GL_FUNCTION(glColorTableParameterivSGI,void,(GLenum target, GLenum pname, const GLint* params)) +GL_FUNCTION(glColorTableSGI,void,(GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const void* table)) +GL_FUNCTION(glCopyColorTableSGI,void,(GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width)) +GL_FUNCTION(glGetColorTableParameterfvSGI,void,(GLenum target, GLenum pname, GLfloat* params)) +GL_FUNCTION(glGetColorTableParameterivSGI,void,(GLenum target, GLenum pname, GLint* params)) +GL_FUNCTION(glGetColorTableSGI,void,(GLenum target, GLenum format, GLenum type, void* table)) +GL_GROUP_END() +#endif + +#ifdef GL_SGI_texture_color_table +GL_GROUP_BEGIN(GL_SGI_texture_color_table) +GL_GROUP_END() +#endif + +#ifdef GL_SUNX_constant_data +GL_GROUP_BEGIN(GL_SUNX_constant_data) +GL_FUNCTION(glFinishTextureSUNX,void,(void)) +GL_GROUP_END() +#endif + +#ifdef GL_SUN_convolution_border_modes +GL_GROUP_BEGIN(GL_SUN_convolution_border_modes) +GL_GROUP_END() +#endif + +#ifdef GL_SUN_global_alpha +GL_GROUP_BEGIN(GL_SUN_global_alpha) +GL_FUNCTION(glGlobalAlphaFactorbSUN,void,(GLbyte factor)) +GL_FUNCTION(glGlobalAlphaFactordSUN,void,(GLdouble factor)) +GL_FUNCTION(glGlobalAlphaFactorfSUN,void,(GLfloat factor)) +GL_FUNCTION(glGlobalAlphaFactoriSUN,void,(GLint factor)) +GL_FUNCTION(glGlobalAlphaFactorsSUN,void,(GLshort factor)) +GL_FUNCTION(glGlobalAlphaFactorubSUN,void,(GLubyte factor)) +GL_FUNCTION(glGlobalAlphaFactoruiSUN,void,(GLuint factor)) +GL_FUNCTION(glGlobalAlphaFactorusSUN,void,(GLushort factor)) +GL_GROUP_END() +#endif + +#ifdef GL_SUN_mesh_array +GL_GROUP_BEGIN(GL_SUN_mesh_array) +GL_GROUP_END() +#endif + +#ifdef GL_SUN_read_video_pixels +GL_GROUP_BEGIN(GL_SUN_read_video_pixels) +GL_FUNCTION(glReadVideoPixelsSUN,void,(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)) +GL_GROUP_END() +#endif + +#ifdef GL_SUN_slice_accum +GL_GROUP_BEGIN(GL_SUN_slice_accum) +GL_GROUP_END() +#endif + +#ifdef GL_SUN_triangle_list +GL_GROUP_BEGIN(GL_SUN_triangle_list) +GL_FUNCTION(glReplacementCodePointerSUN,void,(GLenum type, GLsizei stride, const void* pointer)) +GL_FUNCTION(glReplacementCodeubSUN,void,(GLubyte code)) +GL_FUNCTION(glReplacementCodeubvSUN,void,(const GLubyte* code)) +GL_FUNCTION(glReplacementCodeuiSUN,void,(GLuint code)) +GL_FUNCTION(glReplacementCodeuivSUN,void,(const GLuint* code)) +GL_FUNCTION(glReplacementCodeusSUN,void,(GLushort code)) +GL_FUNCTION(glReplacementCodeusvSUN,void,(const GLushort* code)) +GL_GROUP_END() +#endif + +#ifdef GL_SUN_vertex +GL_GROUP_BEGIN(GL_SUN_vertex) +GL_FUNCTION(glColor3fVertex3fSUN,void,(GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glColor3fVertex3fvSUN,void,(const GLfloat* c, const GLfloat *v)) +GL_FUNCTION(glColor4fNormal3fVertex3fSUN,void,(GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glColor4fNormal3fVertex3fvSUN,void,(const GLfloat* c, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glColor4ubVertex2fSUN,void,(GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y)) +GL_FUNCTION(glColor4ubVertex2fvSUN,void,(const GLubyte* c, const GLfloat *v)) +GL_FUNCTION(glColor4ubVertex3fSUN,void,(GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glColor4ubVertex3fvSUN,void,(const GLubyte* c, const GLfloat *v)) +GL_FUNCTION(glNormal3fVertex3fSUN,void,(GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glNormal3fVertex3fvSUN,void,(const GLfloat* n, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiColor3fVertex3fSUN,void,(GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiColor3fVertex3fvSUN,void,(const GLuint* rc, const GLfloat *c, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiColor4fNormal3fVertex3fSUN,void,(GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiColor4fNormal3fVertex3fvSUN,void,(const GLuint* rc, const GLfloat *c, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiColor4ubVertex3fSUN,void,(GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiColor4ubVertex3fvSUN,void,(const GLuint* rc, const GLubyte *c, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiNormal3fVertex3fSUN,void,(GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiNormal3fVertex3fvSUN,void,(const GLuint* rc, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN,void,(GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN,void,(const GLuint* rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN,void,(GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN,void,(const GLuint* rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiTexCoord2fVertex3fSUN,void,(GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiTexCoord2fVertex3fvSUN,void,(const GLuint* rc, const GLfloat *tc, const GLfloat *v)) +GL_FUNCTION(glReplacementCodeuiVertex3fSUN,void,(GLuint rc, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glReplacementCodeuiVertex3fvSUN,void,(const GLuint* rc, const GLfloat *v)) +GL_FUNCTION(glTexCoord2fColor3fVertex3fSUN,void,(GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glTexCoord2fColor3fVertex3fvSUN,void,(const GLfloat* tc, const GLfloat *c, const GLfloat *v)) +GL_FUNCTION(glTexCoord2fColor4fNormal3fVertex3fSUN,void,(GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glTexCoord2fColor4fNormal3fVertex3fvSUN,void,(const GLfloat* tc, const GLfloat *c, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glTexCoord2fColor4ubVertex3fSUN,void,(GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glTexCoord2fColor4ubVertex3fvSUN,void,(const GLfloat* tc, const GLubyte *c, const GLfloat *v)) +GL_FUNCTION(glTexCoord2fNormal3fVertex3fSUN,void,(GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glTexCoord2fNormal3fVertex3fvSUN,void,(const GLfloat* tc, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glTexCoord2fVertex3fSUN,void,(GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z)) +GL_FUNCTION(glTexCoord2fVertex3fvSUN,void,(const GLfloat* tc, const GLfloat *v)) +GL_FUNCTION(glTexCoord4fColor4fNormal3fVertex4fSUN,void,(GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glTexCoord4fColor4fNormal3fVertex4fvSUN,void,(const GLfloat* tc, const GLfloat *c, const GLfloat *n, const GLfloat *v)) +GL_FUNCTION(glTexCoord4fVertex4fSUN,void,(GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w)) +GL_FUNCTION(glTexCoord4fVertex4fvSUN,void,(const GLfloat* tc, const GLfloat *v)) +GL_GROUP_END() +#endif + +#ifdef GL_WIN_phong_shading +GL_GROUP_BEGIN(GL_WIN_phong_shading) +GL_GROUP_END() +#endif + +#ifdef GL_WIN_specular_fog +GL_GROUP_BEGIN(GL_WIN_specular_fog) +GL_GROUP_END() +#endif + +#ifdef GL_WIN_swap_hint +GL_GROUP_BEGIN(GL_WIN_swap_hint) +GL_FUNCTION(glAddSwapHintRectWIN,void,(GLint x, GLint y, GLsizei width, GLsizei height)) +GL_GROUP_END() +#endif + + diff --git a/gfx/gl/ggl/generated/glxe.h b/gfx/gl/ggl/generated/glxe.h new file mode 100644 index 0000000..17bb268 --- /dev/null +++ b/gfx/gl/ggl/generated/glxe.h @@ -0,0 +1,388 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifdef GLX_3DFX_multisample +#define GLX_SAMPLE_BUFFERS_3DFX 0x8050 +#define GLX_SAMPLES_3DFX 0x8051 +#endif + +#ifdef GLX_ARB_fbconfig_float +#define GLX_RGBA_FLOAT_BIT 0x00000004 +#define GLX_RGBA_FLOAT_TYPE 0x20B9 +#endif + +#ifdef GLX_ARB_get_proc_address +#define glXGetProcAddressARB XGL_FUNCPTR(glXGetProcAddressARB) +#endif + +#ifdef GLX_ARB_multisample +#define GLX_SAMPLE_BUFFERS_ARB 100000 +#define GLX_SAMPLES_ARB 100001 +#endif + +#ifdef GLX_ATI_pixel_format_float +#define GLX_RGBA_FLOAT_ATI_BIT 0x00000100 +#endif + +#ifdef GLX_ATI_render_texture +#define GLX_BIND_TO_TEXTURE_RGB_ATI 0x9800 +#define GLX_BIND_TO_TEXTURE_RGBA_ATI 0x9801 +#define GLX_TEXTURE_FORMAT_ATI 0x9802 +#define GLX_TEXTURE_TARGET_ATI 0x9803 +#define GLX_MIPMAP_TEXTURE_ATI 0x9804 +#define GLX_TEXTURE_RGB_ATI 0x9805 +#define GLX_TEXTURE_RGBA_ATI 0x9806 +#define GLX_NO_TEXTURE_ATI 0x9807 +#define GLX_TEXTURE_CUBE_MAP_ATI 0x9808 +#define GLX_TEXTURE_1D_ATI 0x9809 +#define GLX_TEXTURE_2D_ATI 0x980A +#define GLX_MIPMAP_LEVEL_ATI 0x980B +#define GLX_CUBE_MAP_FACE_ATI 0x980C +#define GLX_TEXTURE_CUBE_MAP_POSITIVE_X_ATI 0x980D +#define GLX_TEXTURE_CUBE_MAP_NEGATIVE_X_ATI 0x980E +#define GLX_TEXTURE_CUBE_MAP_POSITIVE_Y_ATI 0x980F +#define GLX_TEXTURE_CUBE_MAP_NEGATIVE_Y_ATI 0x9810 +#define GLX_TEXTURE_CUBE_MAP_POSITIVE_Z_ATI 0x9811 +#define GLX_TEXTURE_CUBE_MAP_NEGATIVE_Z_ATI 0x9812 +#define GLX_FRONT_LEFT_ATI 0x9813 +#define GLX_FRONT_RIGHT_ATI 0x9814 +#define GLX_BACK_LEFT_ATI 0x9815 +#define GLX_BACK_RIGHT_ATI 0x9816 +#define GLX_AUX0_ATI 0x9817 +#define GLX_AUX1_ATI 0x9818 +#define GLX_AUX2_ATI 0x9819 +#define GLX_AUX3_ATI 0x981A +#define GLX_AUX4_ATI 0x981B +#define GLX_AUX5_ATI 0x981C +#define GLX_AUX6_ATI 0x981D +#define GLX_AUX7_ATI 0x981E +#define GLX_AUX8_ATI 0x981F +#define GLX_AUX9_ATI 0x9820 +#define GLX_BIND_TO_TEXTURE_LUMINANCE_ATI 0x9821 +#define GLX_BIND_TO_TEXTURE_INTENSITY_ATI 0x9822 +#define glXBindTexImageATI XGL_FUNCPTR(glXBindTexImageATI) +#define glXReleaseTexImageATI XGL_FUNCPTR(glXReleaseTexImageATI) +#define glXDrawableAttribATI XGL_FUNCPTR(glXDrawableAttribATI) +#endif + +#ifdef GLX_EXT_import_context +typedef XID GLXContextID; +#define GLX_SHARE_CONTEXT_EXT 0x800A +#define GLX_VISUAL_ID_EXT 0x800B +#define GLX_SCREEN_EXT 0x800C +#define glXFreeContextEXT XGL_FUNCPTR(glXFreeContextEXT) +#define glXGetContextIDEXT XGL_FUNCPTR(glXGetContextIDEXT) +#define glXImportContextEXT XGL_FUNCPTR(glXImportContextEXT) +#define glXQueryContextInfoEXT XGL_FUNCPTR(glXQueryContextInfoEXT) +#endif + +#ifdef GLX_EXT_scene_marker +#endif + +#ifdef GLX_EXT_visual_info +#define GLX_X_VISUAL_TYPE_EXT 0x22 +#define GLX_TRANSPARENT_TYPE_EXT 0x23 +#define GLX_TRANSPARENT_INDEX_VALUE_EXT 0x24 +#define GLX_TRANSPARENT_RED_VALUE_EXT 0x25 +#define GLX_TRANSPARENT_GREEN_VALUE_EXT 0x26 +#define GLX_TRANSPARENT_BLUE_VALUE_EXT 0x27 +#define GLX_TRANSPARENT_ALPHA_VALUE_EXT 0x28 +#define GLX_NONE_EXT 0x8000 +#define GLX_TRUE_COLOR_EXT 0x8002 +#define GLX_DIRECT_COLOR_EXT 0x8003 +#define GLX_PSEUDO_COLOR_EXT 0x8004 +#define GLX_STATIC_COLOR_EXT 0x8005 +#define GLX_GRAY_SCALE_EXT 0x8006 +#define GLX_STATIC_GRAY_EXT 0x8007 +#define GLX_TRANSPARENT_RGB_EXT 0x8008 +#define GLX_TRANSPARENT_INDEX_EXT 0x8009 +#endif + +#ifdef GLX_EXT_visual_rating +#define GLX_VISUAL_CAVEAT_EXT 0x20 +#define GLX_SLOW_VISUAL_EXT 0x8001 +#define GLX_NON_CONFORMANT_VISUAL_EXT 0x800D +#endif + +#ifdef GLX_MESA_agp_offset +#define glXGetAGPOffsetMESA XGL_FUNCPTR(glXGetAGPOffsetMESA) +#endif + +#ifdef GLX_MESA_copy_sub_buffer +#define glXCopySubBufferMESA XGL_FUNCPTR(glXCopySubBufferMESA) +#endif + +#ifdef GLX_MESA_pixmap_colormap +#define glXCreateGLXPixmapMESA XGL_FUNCPTR(glXCreateGLXPixmapMESA) +#endif + +#ifdef GLX_MESA_release_buffers +#define glXReleaseBuffersMESA XGL_FUNCPTR(glXReleaseBuffersMESA) +#endif + +#ifdef GLX_MESA_set_3dfx_mode +#define GLX_3DFX_WINDOW_MODE_MESA 0x1 +#define GLX_3DFX_FULLSCREEN_MODE_MESA 0x2 +#define glXSet3DfxModeMESA XGL_FUNCPTR(glXSet3DfxModeMESA) +#endif + +#ifdef GLX_NV_float_buffer +#define GLX_FLOAT_COMPONENTS_NV 0x20B0 +#endif + +#ifdef GLX_NV_vertex_array_range +#define glXAllocateMemoryNV XGL_FUNCPTR(glXAllocateMemoryNV) +#define glXFreeMemoryNV XGL_FUNCPTR(glXFreeMemoryNV) +#endif + +#ifdef GLX_OML_swap_method +#define GLX_SWAP_METHOD_OML 0x8060 +#define GLX_SWAP_EXCHANGE_OML 0x8061 +#define GLX_SWAP_COPY_OML 0x8062 +#define GLX_SWAP_UNDEFINED_OML 0x8063 +#endif + +#ifdef GLX_OML_sync_control +#define glXGetMscRateOML XGL_FUNCPTR(glXGetMscRateOML) +#define glXGetSyncValuesOML XGL_FUNCPTR(glXGetSyncValuesOML) +#define glXSwapBuffersMscOML XGL_FUNCPTR(glXSwapBuffersMscOML) +#define glXWaitForMscOML XGL_FUNCPTR(glXWaitForMscOML) +#define glXWaitForSbcOML XGL_FUNCPTR(glXWaitForSbcOML) +#endif + +#ifdef GLX_SGIS_blended_overlay +#define GLX_BLENDED_RGBA_SGIS 0x8025 +#endif + +#ifdef GLX_SGIS_color_range +#define GLX_MAX_GREEN_SGIS 0 +#define GLX_MIN_RED_SGIS 0 +#define GLX_MIN_BLUE_SGIS 0 +#define GLX_MAX_RED_SGIS 0 +#define GLX_MAX_ALPHA_SGIS 0 +#define GLX_MIN_GREEN_SGIS 0 +#define GLX_MIN_ALPHA_SGIS 0 +#define GLX_EXTENDED_RANGE_SGIS 0 +#define GLX_MAX_BLUE_SGIS 0 +#endif + +#ifdef GLX_SGIS_multisample +#define GLX_SAMPLE_BUFFERS_SGIS 100000 +#define GLX_SAMPLES_SGIS 100001 +#endif + +#ifdef GLX_SGIS_shared_multisample +#define GLX_MULTISAMPLE_SUB_RECT_WIDTH_SGIS 0x8026 +#define GLX_MULTISAMPLE_SUB_RECT_HEIGHT_SGIS 0x8027 +#endif + +#ifdef GLX_SGIX_fbconfig +typedef XID GLXFBConfigIDSGIX; +typedef struct __GLXFBConfigRec *GLXFBConfigSGIX; +#define GLX_WINDOW_BIT_SGIX 0x00000001 +#define GLX_RGBA_BIT_SGIX 0x00000001 +#define GLX_PIXMAP_BIT_SGIX 0x00000002 +#define GLX_COLOR_INDEX_BIT_SGIX 0x00000002 +#define GLX_SCREEN_EXT 0x800C +#define GLX_DRAWABLE_TYPE_SGIX 0x8010 +#define GLX_RENDER_TYPE_SGIX 0x8011 +#define GLX_X_RENDERABLE_SGIX 0x8012 +#define GLX_FBCONFIG_ID_SGIX 0x8013 +#define GLX_RGBA_TYPE_SGIX 0x8014 +#define GLX_COLOR_INDEX_TYPE_SGIX 0x8015 +#define glXChooseFBConfigSGIX XGL_FUNCPTR(glXChooseFBConfigSGIX) +#define glXCreateContextWithConfigSGIX XGL_FUNCPTR(glXCreateContextWithConfigSGIX) +#define glXCreateGLXPixmapWithConfigSGIX XGL_FUNCPTR(glXCreateGLXPixmapWithConfigSGIX) +#define glXGetFBConfigAttribSGIX XGL_FUNCPTR(glXGetFBConfigAttribSGIX) +#define glXGetFBConfigFromVisualSGIX XGL_FUNCPTR(glXGetFBConfigFromVisualSGIX) +#define glXGetVisualFromFBConfigSGIX XGL_FUNCPTR(glXGetVisualFromFBConfigSGIX) +#endif + +#ifdef GLX_SGIX_pbuffer +typedef XID GLXPbufferSGIX; +typedef struct { int type; unsigned long serial; Bool send_event; Display *display; GLXDrawable drawable; int event_type; int draw_type; unsigned int mask; int x, y; int width, height; int count; } GLXBufferClobberEventSGIX; +#define GLX_FRONT_LEFT_BUFFER_BIT_SGIX 0x00000001 +#define GLX_FRONT_RIGHT_BUFFER_BIT_SGIX 0x00000002 +#define GLX_PBUFFER_BIT_SGIX 0x00000004 +#define GLX_BACK_LEFT_BUFFER_BIT_SGIX 0x00000004 +#define GLX_BACK_RIGHT_BUFFER_BIT_SGIX 0x00000008 +#define GLX_AUX_BUFFERS_BIT_SGIX 0x00000010 +#define GLX_DEPTH_BUFFER_BIT_SGIX 0x00000020 +#define GLX_STENCIL_BUFFER_BIT_SGIX 0x00000040 +#define GLX_ACCUM_BUFFER_BIT_SGIX 0x00000080 +#define GLX_SAMPLE_BUFFERS_BIT_SGIX 0x00000100 +#define GLX_MAX_PBUFFER_WIDTH_SGIX 0x8016 +#define GLX_MAX_PBUFFER_HEIGHT_SGIX 0x8017 +#define GLX_MAX_PBUFFER_PIXELS_SGIX 0x8018 +#define GLX_OPTIMAL_PBUFFER_WIDTH_SGIX 0x8019 +#define GLX_OPTIMAL_PBUFFER_HEIGHT_SGIX 0x801A +#define GLX_PRESERVED_CONTENTS_SGIX 0x801B +#define GLX_LARGEST_PBUFFER_SGIX 0x801C +#define GLX_WIDTH_SGIX 0x801D +#define GLX_HEIGHT_SGIX 0x801E +#define GLX_EVENT_MASK_SGIX 0x801F +#define GLX_DAMAGED_SGIX 0x8020 +#define GLX_SAVED_SGIX 0x8021 +#define GLX_WINDOW_SGIX 0x8022 +#define GLX_PBUFFER_SGIX 0x8023 +#define GLX_BUFFER_CLOBBER_MASK_SGIX 0x08000000 +#define glXCreateGLXPbufferSGIX XGL_FUNCPTR(glXCreateGLXPbufferSGIX) +#define glXDestroyGLXPbufferSGIX XGL_FUNCPTR(glXDestroyGLXPbufferSGIX) +#define glXGetSelectedEventSGIX XGL_FUNCPTR(glXGetSelectedEventSGIX) +#define glXQueryGLXPbufferSGIX XGL_FUNCPTR(glXQueryGLXPbufferSGIX) +#define glXSelectEventSGIX XGL_FUNCPTR(glXSelectEventSGIX) +#endif + +#ifdef GLX_SGIX_swap_barrier +#define glXBindSwapBarrierSGIX XGL_FUNCPTR(glXBindSwapBarrierSGIX) +#define glXQueryMaxSwapBarriersSGIX XGL_FUNCPTR(glXQueryMaxSwapBarriersSGIX) +#endif + +#ifdef GLX_SGIX_swap_group +#define glXJoinSwapGroupSGIX XGL_FUNCPTR(glXJoinSwapGroupSGIX) +#endif + +#ifdef GLX_SGIX_video_resize +#define GLX_SYNC_FRAME_SGIX 0x00000000 +#define GLX_SYNC_SWAP_SGIX 0x00000001 +#define glXBindChannelToWindowSGIX XGL_FUNCPTR(glXBindChannelToWindowSGIX) +#define glXChannelRectSGIX XGL_FUNCPTR(glXChannelRectSGIX) +#define glXChannelRectSyncSGIX XGL_FUNCPTR(glXChannelRectSyncSGIX) +#define glXQueryChannelDeltasSGIX XGL_FUNCPTR(glXQueryChannelDeltasSGIX) +#define glXQueryChannelRectSGIX XGL_FUNCPTR(glXQueryChannelRectSGIX) +#endif + +#ifdef GLX_SGIX_visual_select_group +#define GLX_VISUAL_SELECT_GROUP_SGIX 0x8028 +#endif + +#ifdef GLX_SGI_cushion +#define glXCushionSGI XGL_FUNCPTR(glXCushionSGI) +#endif + +#ifdef GLX_SGI_make_current_read +#define glXGetCurrentReadDrawableSGI XGL_FUNCPTR(glXGetCurrentReadDrawableSGI) +#define glXMakeCurrentReadSGI XGL_FUNCPTR(glXMakeCurrentReadSGI) +#endif + +#ifdef GLX_SGI_swap_control +#define glXSwapIntervalSGI XGL_FUNCPTR(glXSwapIntervalSGI) +#endif + +#ifdef GLX_SGI_video_sync +#define glXGetVideoSyncSGI XGL_FUNCPTR(glXGetVideoSyncSGI) +#define glXWaitVideoSyncSGI XGL_FUNCPTR(glXWaitVideoSyncSGI) +#endif + +#ifdef GLX_SUN_get_transparent_index +#define glXGetTransparentIndexSUN XGL_FUNCPTR(glXGetTransparentIndexSUN) +#endif + +#ifdef GLX_SUN_video_resize +#define GL_VIDEO_RESIZE_COMPENSATION_SUN 0x85CD +#define GLX_VIDEO_RESIZE_SUN 0x8171 +#define glXVideoResizeSUN XGL_FUNCPTR(glXVideoResizeSUN) +#define glXGetVideoResizeSUN XGL_FUNCPTR(glXGetVideoResizeSUN) +#endif + +#ifdef GLX_VERSION_1_1 +#define glXQueryExtensionsString XGL_FUNCPTR(glXQueryExtensionsString) +#define glXGetClientString XGL_FUNCPTR(glXGetClientString) +#define glXQueryServerString XGL_FUNCPTR(glXQueryServerString) +#endif + +#ifdef GLX_VERSION_1_2 +#define glXGetCurrentDisplay XGL_FUNCPTR(glXGetCurrentDisplay) +#endif + +#ifdef GLX_VERSION_1_3 +typedef XID GLXWindow; +typedef XID GLXPbuffer; +typedef XID GLXFBConfigID; +typedef struct __GLXFBConfigRec *GLXFBConfig; +typedef struct { int event_type; int draw_type; unsigned long serial; Bool send_event; Display *display; GLXDrawable drawable; unsigned int buffer_mask; unsigned int aux_buffer; int x, y; int width, height; int count; } GLXPbufferClobberEvent; +typedef union __GLXEvent { GLXPbufferClobberEvent glxpbufferclobber; long pad[24]; } GLXEvent; +#define GLX_WINDOW_BIT 0x00000001 +#define GLX_PIXMAP_BIT 0x00000002 +#define GLX_PBUFFER_BIT 0x00000004 +#define GLX_RGBA_BIT 0x00000001 +#define GLX_COLOR_INDEX_BIT 0x00000002 +#define GLX_PBUFFER_CLOBBER_MASK 0x08000000 +#define GLX_FRONT_LEFT_BUFFER_BIT 0x00000001 +#define GLX_FRONT_RIGHT_BUFFER_BIT 0x00000002 +#define GLX_BACK_LEFT_BUFFER_BIT 0x00000004 +#define GLX_BACK_RIGHT_BUFFER_BIT 0x00000008 +#define GLX_AUX_BUFFERS_BIT 0x00000010 +#define GLX_DEPTH_BUFFER_BIT 0x00000020 +#define GLX_STENCIL_BUFFER_BIT 0x00000040 +#define GLX_ACCUM_BUFFER_BIT 0x00000080 +#define GLX_CONFIG_CAVEAT 0x20 +#define GLX_X_VISUAL_TYPE 0x22 +#define GLX_TRANSPARENT_TYPE 0x23 +#define GLX_TRANSPARENT_INDEX_VALUE 0x24 +#define GLX_TRANSPARENT_RED_VALUE 0x25 +#define GLX_TRANSPARENT_GREEN_VALUE 0x26 +#define GLX_TRANSPARENT_BLUE_VALUE 0x27 +#define GLX_TRANSPARENT_ALPHA_VALUE 0x28 +#define GLX_DONT_CARE 0xFFFFFFFF +#define GLX_NONE 0x8000 +#define GLX_SLOW_CONFIG 0x8001 +#define GLX_TRUE_COLOR 0x8002 +#define GLX_DIRECT_COLOR 0x8003 +#define GLX_PSEUDO_COLOR 0x8004 +#define GLX_STATIC_COLOR 0x8005 +#define GLX_GRAY_SCALE 0x8006 +#define GLX_STATIC_GRAY 0x8007 +#define GLX_TRANSPARENT_RGB 0x8008 +#define GLX_TRANSPARENT_INDEX 0x8009 +#define GLX_VISUAL_ID 0x800B +#define GLX_SCREEN 0x800C +#define GLX_NON_CONFORMANT_CONFIG 0x800D +#define GLX_DRAWABLE_TYPE 0x8010 +#define GLX_RENDER_TYPE 0x8011 +#define GLX_X_RENDERABLE 0x8012 +#define GLX_FBCONFIG_ID 0x8013 +#define GLX_RGBA_TYPE 0x8014 +#define GLX_COLOR_INDEX_TYPE 0x8015 +#define GLX_MAX_PBUFFER_WIDTH 0x8016 +#define GLX_MAX_PBUFFER_HEIGHT 0x8017 +#define GLX_MAX_PBUFFER_PIXELS 0x8018 +#define GLX_PRESERVED_CONTENTS 0x801B +#define GLX_LARGEST_PBUFFER 0x801C +#define GLX_WIDTH 0x801D +#define GLX_HEIGHT 0x801E +#define GLX_EVENT_MASK 0x801F +#define GLX_DAMAGED 0x8020 +#define GLX_SAVED 0x8021 +#define GLX_WINDOW 0x8022 +#define GLX_PBUFFER 0x8023 +#define GLX_PBUFFER_HEIGHT 0x8040 +#define GLX_PBUFFER_WIDTH 0x8041 +#define glXChooseFBConfig XGL_FUNCPTR(glXChooseFBConfig) +#define glXGetFBConfigs XGL_FUNCPTR(glXGetFBConfigs) +#define glXGetVisualFromFBConfig XGL_FUNCPTR(glXGetVisualFromFBConfig) +#define glXGetFBConfigAttrib XGL_FUNCPTR(glXGetFBConfigAttrib) +#define glXCreateWindow XGL_FUNCPTR(glXCreateWindow) +#define glXDestroyWindow XGL_FUNCPTR(glXDestroyWindow) +#define glXCreatePixmap XGL_FUNCPTR(glXCreatePixmap) +#define glXDestroyPixmap XGL_FUNCPTR(glXDestroyPixmap) +#define glXCreatePbuffer XGL_FUNCPTR(glXCreatePbuffer) +#define glXDestroyPbuffer XGL_FUNCPTR(glXDestroyPbuffer) +#define glXQueryDrawable XGL_FUNCPTR(glXQueryDrawable) +#define glXCreateNewContext XGL_FUNCPTR(glXCreateNewContext) +#define glXMakeContextCurrent XGL_FUNCPTR(glXMakeContextCurrent) +#define glXGetCurrentReadDrawable XGL_FUNCPTR(glXGetCurrentReadDrawable) +#define glXQueryContext XGL_FUNCPTR(glXQueryContext) +#define glXSelectEvent XGL_FUNCPTR(glXSelectEvent) +#define glXGetSelectedEvent XGL_FUNCPTR(glXGetSelectedEvent) +#endif + +#ifdef GLX_VERSION_1_4 +#define GLX_SAMPLE_BUFFERS 100000 +#define GLX_SAMPLES 100001 +#define glXGetProcAddress XGL_FUNCPTR(glXGetProcAddress) +#endif + + diff --git a/gfx/gl/ggl/generated/glxefn.h b/gfx/gl/ggl/generated/glxefn.h new file mode 100644 index 0000000..fd678f0 --- /dev/null +++ b/gfx/gl/ggl/generated/glxefn.h @@ -0,0 +1,271 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifdef GLX_3DFX_multisample +GL_GROUP_BEGIN(GLX_3DFX_multisample) +GL_GROUP_END() +#endif + +#ifdef GLX_ARB_fbconfig_float +GL_GROUP_BEGIN(GLX_ARB_fbconfig_float) +GL_GROUP_END() +#endif + +#ifdef GLX_ARB_get_proc_address +GL_GROUP_BEGIN(GLX_ARB_get_proc_address) +GL_FUNCTION(glXGetProcAddressARB, GLFunction, (const GLubyte*)) +GL_GROUP_END() +#endif + +#ifdef GLX_ARB_multisample +GL_GROUP_BEGIN(GLX_ARB_multisample) +GL_GROUP_END() +#endif + +#ifdef GLX_ATI_pixel_format_float +GL_GROUP_BEGIN(GLX_ATI_pixel_format_float) +GL_GROUP_END() +#endif + +#ifdef GLX_ATI_render_texture +GL_GROUP_BEGIN(GLX_ATI_render_texture) +GL_FUNCTION(glXBindTexImageATI,void,(Display *dpy, GLXPbuffer pbuf, int buffer)) +GL_FUNCTION(glXReleaseTexImageATI,void,(Display *dpy, GLXPbuffer pbuf, int buffer)) +GL_FUNCTION(glXDrawableAttribATI,void,(Display *dpy, GLXDrawable draw, const int *attrib_list)) +GL_GROUP_END() +#endif + +#ifdef GLX_EXT_import_context +GL_GROUP_BEGIN(GLX_EXT_import_context) +GL_FUNCTION(glXFreeContextEXT,void,(Display* dpy, GLXContext context)) +GL_FUNCTION(glXGetContextIDEXT,GLXContextID,(const GLXContext context)) +GL_FUNCTION(glXImportContextEXT,GLXContext,(Display* dpy, GLXContextID contextID)) +GL_FUNCTION(glXQueryContextInfoEXT,int,(Display* dpy, GLXContext context, int attribute,int *value)) +GL_GROUP_END() +#endif + +#ifdef GLX_EXT_scene_marker +GL_GROUP_BEGIN(GLX_EXT_scene_marker) +GL_GROUP_END() +#endif + +#ifdef GLX_EXT_visual_info +GL_GROUP_BEGIN(GLX_EXT_visual_info) +GL_GROUP_END() +#endif + +#ifdef GLX_EXT_visual_rating +GL_GROUP_BEGIN(GLX_EXT_visual_rating) +GL_GROUP_END() +#endif + +#ifdef GLX_MESA_agp_offset +GL_GROUP_BEGIN(GLX_MESA_agp_offset) +GL_FUNCTION(glXGetAGPOffsetMESA,unsigned int,(const void* pointer)) +GL_GROUP_END() +#endif + +#ifdef GLX_MESA_copy_sub_buffer +GL_GROUP_BEGIN(GLX_MESA_copy_sub_buffer) +GL_FUNCTION(glXCopySubBufferMESA,void,(Display* dpy, GLXDrawable drawable, int x, int y, int width, int height)) +GL_GROUP_END() +#endif + +#ifdef GLX_MESA_pixmap_colormap +GL_GROUP_BEGIN(GLX_MESA_pixmap_colormap) +GL_FUNCTION(glXCreateGLXPixmapMESA,GLXPixmap,(Display* dpy, XVisualInfo *visual, Pixmap pixmap, Colormap cmap)) +GL_GROUP_END() +#endif + +#ifdef GLX_MESA_release_buffers +GL_GROUP_BEGIN(GLX_MESA_release_buffers) +GL_FUNCTION(glXReleaseBuffersMESA,Bool,(Display* dpy, GLXDrawable d)) +GL_GROUP_END() +#endif + +#ifdef GLX_MESA_set_3dfx_mode +GL_GROUP_BEGIN(GLX_MESA_set_3dfx_mode) +GL_FUNCTION(glXSet3DfxModeMESA,GLboolean,(GLint mode)) +GL_GROUP_END() +#endif + +#ifdef GLX_NV_float_buffer +GL_GROUP_BEGIN(GLX_NV_float_buffer) +GL_GROUP_END() +#endif + +#ifdef GLX_NV_vertex_array_range +GL_GROUP_BEGIN(GLX_NV_vertex_array_range) +GL_FUNCTION(glXAllocateMemoryNV,void *,(GLsizei size, GLfloat readFrequency, GLfloat writeFrequency, GLfloat priority)) +GL_FUNCTION(glXFreeMemoryNV,void,(void *pointer)) +GL_GROUP_END() +#endif + +#ifdef GLX_OML_swap_method +GL_GROUP_BEGIN(GLX_OML_swap_method) +GL_GROUP_END() +#endif + +#ifdef GLX_OML_sync_control +GL_GROUP_BEGIN(GLX_OML_sync_control) +GL_FUNCTION(glXGetMscRateOML,Bool,(Display* dpy, GLXDrawable drawable, int32_t* numerator, int32_t* denominator)) +GL_FUNCTION(glXGetSyncValuesOML,Bool,(Display* dpy, GLXDrawable drawable, int64_t* ust, int64_t* msc, int64_t* sbc)) +GL_FUNCTION(glXSwapBuffersMscOML,int64_t,(Display* dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder)) +GL_FUNCTION(glXWaitForMscOML,Bool,(Display* dpy, GLXDrawable drawable, int64_t target_msc, int64_t divisor, int64_t remainder, int64_t* ust, int64_t* msc, int64_t* sbc)) +GL_FUNCTION(glXWaitForSbcOML,Bool,(Display* dpy, GLXDrawable drawable, int64_t target_sbc, int64_t* ust, int64_t* msc, int64_t* sbc)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIS_blended_overlay +GL_GROUP_BEGIN(GLX_SGIS_blended_overlay) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIS_color_range +GL_GROUP_BEGIN(GLX_SGIS_color_range) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIS_multisample +GL_GROUP_BEGIN(GLX_SGIS_multisample) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIS_shared_multisample +GL_GROUP_BEGIN(GLX_SGIS_shared_multisample) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIX_fbconfig +GL_GROUP_BEGIN(GLX_SGIX_fbconfig) +GL_FUNCTION(glXChooseFBConfigSGIX,GLXFBConfigSGIX*,(Display *dpy, int screen, const int *attrib_list, int *nelements)) +GL_FUNCTION(glXCreateContextWithConfigSGIX,GLXContext,(Display* dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct)) +GL_FUNCTION(glXCreateGLXPixmapWithConfigSGIX,GLXPixmap,(Display* dpy, GLXFBConfig config, Pixmap pixmap)) +GL_FUNCTION(glXGetFBConfigAttribSGIX,int,(Display* dpy, GLXFBConfigSGIX config, int attribute, int *value)) +GL_FUNCTION(glXGetFBConfigFromVisualSGIX,GLXFBConfigSGIX,(Display* dpy, XVisualInfo *vis)) +GL_FUNCTION(glXGetVisualFromFBConfigSGIX,XVisualInfo*,(Display *dpy, GLXFBConfig config)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIX_pbuffer +GL_GROUP_BEGIN(GLX_SGIX_pbuffer) +GL_FUNCTION(glXCreateGLXPbufferSGIX,GLXPbuffer,(Display* dpy, GLXFBConfig config, unsigned int width, unsigned int height, int *attrib_list)) +GL_FUNCTION(glXDestroyGLXPbufferSGIX,void,(Display* dpy, GLXPbuffer pbuf)) +GL_FUNCTION(glXGetSelectedEventSGIX,void,(Display* dpy, GLXDrawable drawable, unsigned long *mask)) +GL_FUNCTION(glXQueryGLXPbufferSGIX,void,(Display* dpy, GLXPbuffer pbuf, int attribute, unsigned int *value)) +GL_FUNCTION(glXSelectEventSGIX,void,(Display* dpy, GLXDrawable drawable, unsigned long mask)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIX_swap_barrier +GL_GROUP_BEGIN(GLX_SGIX_swap_barrier) +GL_FUNCTION(glXBindSwapBarrierSGIX,void,(Display *dpy, GLXDrawable drawable, int barrier)) +GL_FUNCTION(glXQueryMaxSwapBarriersSGIX,Bool,(Display *dpy, int screen, int *max)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIX_swap_group +GL_GROUP_BEGIN(GLX_SGIX_swap_group) +GL_FUNCTION(glXJoinSwapGroupSGIX,void,(Display *dpy, GLXDrawable drawable, GLXDrawable member)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIX_video_resize +GL_GROUP_BEGIN(GLX_SGIX_video_resize) +GL_FUNCTION(glXBindChannelToWindowSGIX,int,(Display* display, int screen, int channel, Window window)) +GL_FUNCTION(glXChannelRectSGIX,int,(Display* display, int screen, int channel, int x, int y, int w, int h)) +GL_FUNCTION(glXChannelRectSyncSGIX,int,(Display* display, int screen, int channel, GLenum synctype)) +GL_FUNCTION(glXQueryChannelDeltasSGIX,int,(Display* display, int screen, int channel, int *x, int *y, int *w, int *h)) +GL_FUNCTION(glXQueryChannelRectSGIX,int,(Display* display, int screen, int channel, int *dx, int *dy, int *dw, int *dh)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGIX_visual_select_group +GL_GROUP_BEGIN(GLX_SGIX_visual_select_group) +GL_GROUP_END() +#endif + +#ifdef GLX_SGI_cushion +GL_GROUP_BEGIN(GLX_SGI_cushion) +GL_FUNCTION(glXCushionSGI,void,(Display* dpy, Window window, float cushion)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGI_make_current_read +GL_GROUP_BEGIN(GLX_SGI_make_current_read) +GL_FUNCTION(glXGetCurrentReadDrawableSGI,GLXDrawable,(void)) +GL_FUNCTION(glXMakeCurrentReadSGI,Bool,(Display* dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGI_swap_control +GL_GROUP_BEGIN(GLX_SGI_swap_control) +GL_FUNCTION(glXSwapIntervalSGI,int,(int interval)) +GL_GROUP_END() +#endif + +#ifdef GLX_SGI_video_sync +GL_GROUP_BEGIN(GLX_SGI_video_sync) +GL_FUNCTION(glXGetVideoSyncSGI,int,(uint* count)) +GL_FUNCTION(glXWaitVideoSyncSGI,int,(int divisor, int remainder, unsigned int* count)) +GL_GROUP_END() +#endif + +#ifdef GLX_SUN_get_transparent_index +GL_GROUP_BEGIN(GLX_SUN_get_transparent_index) +GL_FUNCTION(glXGetTransparentIndexSUN,Status,(Display* dpy, Window overlay, Window underlay, unsigned long *pTransparentIndex)) +GL_GROUP_END() +#endif + +#ifdef GLX_SUN_video_resize +GL_GROUP_BEGIN(GLX_SUN_video_resize) +GL_FUNCTION(glXVideoResizeSUN,int,(Display* display, GLXDrawable window, float factor)) +GL_FUNCTION(glXGetVideoResizeSUN,int,(Display* display, GLXDrawable window, float* factor)) +GL_GROUP_END() +#endif + +#ifdef GLX_VERSION_1_1 +GL_GROUP_BEGIN(GLX_VERSION_1_1) +GL_FUNCTION(glXQueryExtensionsString, const char*, (Display *dpy, int screen)) +GL_FUNCTION(glXGetClientString, const char*, (Display *dpy, int name)) +GL_FUNCTION(glXQueryServerString, const char*, (Display *dpy, int screen, int name)) +GL_GROUP_END() +#endif + +#ifdef GLX_VERSION_1_2 +GL_GROUP_BEGIN(GLX_VERSION_1_2) +GL_FUNCTION(glXGetCurrentDisplay, Display*, (void)) +GL_GROUP_END() +#endif + +#ifdef GLX_VERSION_1_3 +GL_GROUP_BEGIN(GLX_VERSION_1_3) +GL_FUNCTION(glXChooseFBConfig,GLXFBConfig*,(Display *dpy, int screen, const int *attrib_list, int *nelements)) +GL_FUNCTION(glXGetFBConfigs,GLXFBConfig*,(Display *dpy, int screen, int *nelements)) +GL_FUNCTION(glXGetVisualFromFBConfig,XVisualInfo*,(Display *dpy, GLXFBConfig config)) +GL_FUNCTION(glXGetFBConfigAttrib,int,(Display *dpy, GLXFBConfig config, int attribute, int *value)) +GL_FUNCTION(glXCreateWindow,GLXWindow,(Display *dpy, GLXFBConfig config, Window win, const int *attrib_list)) +GL_FUNCTION(glXDestroyWindow,void,(Display *dpy, GLXWindow win)) +GL_FUNCTION(glXCreatePixmap,GLXPixmap,(Display *dpy, GLXFBConfig config, Pixmap pixmap, const int *attrib_list)) +GL_FUNCTION(glXDestroyPixmap,void,(Display *dpy, GLXPixmap pixmap)) +GL_FUNCTION(glXCreatePbuffer,GLXPbuffer,(Display *dpy, GLXFBConfig config, const int *attrib_list)) +GL_FUNCTION(glXDestroyPbuffer,void,(Display *dpy, GLXPbuffer pbuf)) +GL_FUNCTION(glXQueryDrawable,void,(Display *dpy, GLXDrawable draw, int attribute, unsigned int *value)) +GL_FUNCTION(glXCreateNewContext,GLXContext,(Display *dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct)) +GL_FUNCTION(glXMakeContextCurrent,Bool,(Display *display, GLXDrawable draw, GLXDrawable read, GLXContext ctx)) +GL_FUNCTION(glXGetCurrentReadDrawable,GLXDrawable,(void)) +GL_FUNCTION(glXQueryContext,int,(Display *dpy, GLXContext ctx, int attribute, int *value)) +GL_FUNCTION(glXSelectEvent,void,(Display *dpy, GLXDrawable draw, unsigned long event_mask)) +GL_FUNCTION(glXGetSelectedEvent,void,(Display *dpy, GLXDrawable draw, unsigned long *event_mask)) +GL_GROUP_END() +#endif + +#ifdef GLX_VERSION_1_4 +GL_GROUP_BEGIN(GLX_VERSION_1_4) +GL_FUNCTION(glXGetProcAddress, GLFunction, (const GLubyte *procName)) +GL_GROUP_END() +#endif + + diff --git a/gfx/gl/ggl/generated/glxfn.h b/gfx/gl/ggl/generated/glxfn.h new file mode 100644 index 0000000..4d8757d --- /dev/null +++ b/gfx/gl/ggl/generated/glxfn.h @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +// OpenGL 1.0 core definitions +//----------------------------------------------------------------------------- + +// X11 gl functions +GL_GROUP_BEGIN(ARB_glx) +GL_FUNCTION(glXQueryExtension, Bool, (Display *dpy, int *errorBase, int *eventBase)) +GL_FUNCTION(glXQueryVersion, Bool, (Display *dpy, int *major, int *minor)) +GL_FUNCTION(glXGetConfig, int, (Display *dpy, XVisualInfo *vis, int attrib, int *value)) +GL_FUNCTION(glXChooseVisual, XVisualInfo*, (Display *dpy, int screen, int *attribList)) +GL_FUNCTION(glXCreateGLXPixmap, GLXPixmap, (Display *dpy, XVisualInfo *vis, Pixmap pixmap)) +GL_FUNCTION(glXDestroyGLXPixmap, void, (Display *dpy, GLXPixmap pix)) +GL_FUNCTION(glXCreateContext, GLXContext, (Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct)) +GL_FUNCTION(glXDestroyContext, void, (Display *dpy, GLXContext ctx)) +GL_FUNCTION(glXIsDirect, Bool, (Display *dpy, GLXContext ctx)) +GL_FUNCTION(glXCopyContext, void, (Display *dpy, GLXContext src, GLXContext dst, GLuint mask)) +GL_FUNCTION(glXMakeCurrent, Bool, (Display *dpy, GLXDrawable drawable, GLXContext ctx)) +GL_FUNCTION(glXGetCurrentContext, GLXContext, (void)) +GL_FUNCTION(glXGetCurrentDrawable, GLXDrawable, (void)) +GL_FUNCTION(glXWaitGL, void, (void)) +GL_FUNCTION(glXWaitX, void, (void)) +GL_FUNCTION(glXSwapBuffers, void, (Display *dpy, GLXDrawable drawable)) +GL_FUNCTION(glXUseXFont, void, (Font font, int first, int count, int listBase)) +GL_GROUP_END() + diff --git a/gfx/gl/ggl/generated/wgle.h b/gfx/gl/ggl/generated/wgle.h new file mode 100644 index 0000000..68eecbb --- /dev/null +++ b/gfx/gl/ggl/generated/wgle.h @@ -0,0 +1,366 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +// OpenGL 1.0 core definitions +//----------------------------------------------------------------------------- + +#ifdef WGL_3DFX_multisample +#define WGL_SAMPLE_BUFFERS_3DFX 0x2060 +#define WGL_SAMPLES_3DFX 0x2061 +#endif + +#ifdef WGL_ARB_buffer_region +#define WGL_FRONT_COLOR_BUFFER_BIT_ARB 0x00000001 +#define WGL_BACK_COLOR_BUFFER_BIT_ARB 0x00000002 +#define WGL_DEPTH_BUFFER_BIT_ARB 0x00000004 +#define WGL_STENCIL_BUFFER_BIT_ARB 0x00000008 +#define wglCreateBufferRegionARB XGL_FUNCPTR(wglCreateBufferRegionARB) +#define wglDeleteBufferRegionARB XGL_FUNCPTR(wglDeleteBufferRegionARB) +#define wglRestoreBufferRegionARB XGL_FUNCPTR(wglRestoreBufferRegionARB) +#define wglSaveBufferRegionARB XGL_FUNCPTR(wglSaveBufferRegionARB) +#endif + +#ifdef WGL_ARB_extensions_string +#define wglGetExtensionsStringARB XGL_FUNCPTR(wglGetExtensionsStringARB) +#endif + +#ifdef WGL_ARB_make_current_read +#define wglGetCurrentReadDCARB XGL_FUNCPTR(wglGetCurrentReadDCARB) +#define wglMakeContextCurrentARB XGL_FUNCPTR(wglMakeContextCurrentARB) +#endif + +#ifdef WGL_ARB_multisample +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#endif + +#ifdef WGL_ARB_pbuffer +DECLARE_HANDLE(HPBUFFERARB); +#define WGL_DRAW_TO_PBUFFER_ARB 0x202D +#define WGL_MAX_PBUFFER_PIXELS_ARB 0x202E +#define WGL_MAX_PBUFFER_WIDTH_ARB 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_ARB 0x2030 +#define WGL_PBUFFER_LARGEST_ARB 0x2033 +#define WGL_PBUFFER_WIDTH_ARB 0x2034 +#define WGL_PBUFFER_HEIGHT_ARB 0x2035 +#define WGL_PBUFFER_LOST_ARB 0x2036 +#define wglCreatePbufferARB XGL_FUNCPTR(wglCreatePbufferARB) +#define wglDestroyPbufferARB XGL_FUNCPTR(wglDestroyPbufferARB) +#define wglGetPbufferDCARB XGL_FUNCPTR(wglGetPbufferDCARB) +#define wglQueryPbufferARB XGL_FUNCPTR(wglQueryPbufferARB) +#define wglReleasePbufferDCARB XGL_FUNCPTR(wglReleasePbufferDCARB) +#endif + +#ifdef WGL_ARB_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define wglChoosePixelFormatARB XGL_FUNCPTR(wglChoosePixelFormatARB) +#define wglGetPixelFormatAttribfvARB XGL_FUNCPTR(wglGetPixelFormatAttribfvARB) +#define wglGetPixelFormatAttribivARB XGL_FUNCPTR(wglGetPixelFormatAttribivARB) +#endif + +#ifdef WGL_ARB_pixel_format_float +#define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0 +#endif + +#ifdef WGL_ARB_render_texture +#define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 +#define WGL_BIND_TO_TEXTURE_RGBA_ARB 0x2071 +#define WGL_TEXTURE_FORMAT_ARB 0x2072 +#define WGL_TEXTURE_TARGET_ARB 0x2073 +#define WGL_MIPMAP_TEXTURE_ARB 0x2074 +#define WGL_TEXTURE_RGB_ARB 0x2075 +#define WGL_TEXTURE_RGBA_ARB 0x2076 +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_TEXTURE_CUBE_MAP_ARB 0x2078 +#define WGL_TEXTURE_1D_ARB 0x2079 +#define WGL_TEXTURE_2D_ARB 0x207A +#define WGL_MIPMAP_LEVEL_ARB 0x207B +#define WGL_CUBE_MAP_FACE_ARB 0x207C +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x207D +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x207E +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x207F +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x2080 +#define WGL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x2081 +#define WGL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x2082 +#define WGL_FRONT_LEFT_ARB 0x2083 +#define WGL_FRONT_RIGHT_ARB 0x2084 +#define WGL_BACK_LEFT_ARB 0x2085 +#define WGL_BACK_RIGHT_ARB 0x2086 +#define WGL_AUX0_ARB 0x2087 +#define WGL_AUX1_ARB 0x2088 +#define WGL_AUX2_ARB 0x2089 +#define WGL_AUX3_ARB 0x208A +#define WGL_AUX4_ARB 0x208B +#define WGL_AUX5_ARB 0x208C +#define WGL_AUX6_ARB 0x208D +#define WGL_AUX7_ARB 0x208E +#define WGL_AUX8_ARB 0x208F +#define WGL_AUX9_ARB 0x2090 +#define wglBindTexImageARB XGL_FUNCPTR(wglBindTexImageARB) +#define wglReleaseTexImageARB XGL_FUNCPTR(wglReleaseTexImageARB) +#define wglSetPbufferAttribARB XGL_FUNCPTR(wglSetPbufferAttribARB) +#endif + +#ifdef WGL_ATI_pixel_format_float +#define WGL_TYPE_RGBA_FLOAT_ATI 0x21A0 +#define GL_RGBA_FLOAT_MODE_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif + +#ifdef WGL_ATI_render_texture_rectangle +#define WGL_TEXTURE_RECTANGLE_ATI 0x21A5 +#endif + +#ifdef WGL_EXT_depth_float +#define WGL_DEPTH_FLOAT_EXT 0x2040 +#endif + +#ifdef WGL_EXT_display_color_table +#define wglBindDisplayColorTableEXT XGL_FUNCPTR(wglBindDisplayColorTableEXT) +#define wglCreateDisplayColorTableEXT XGL_FUNCPTR(wglCreateDisplayColorTableEXT) +#define wglDestroyDisplayColorTableEXT XGL_FUNCPTR(wglDestroyDisplayColorTableEXT) +#define wglLoadDisplayColorTableEXT XGL_FUNCPTR(wglLoadDisplayColorTableEXT) +#endif + +#ifdef WGL_EXT_extensions_string +#define wglGetExtensionsStringEXT XGL_FUNCPTR(wglGetExtensionsStringEXT) +#endif + +#ifdef WGL_EXT_make_current_read +#define wglGetCurrentReadDCEXT XGL_FUNCPTR(wglGetCurrentReadDCEXT) +#define wglMakeContextCurrentEXT XGL_FUNCPTR(wglMakeContextCurrentEXT) +#endif + +#ifdef WGL_EXT_multisample +#define WGL_SAMPLE_BUFFERS_EXT 0x2041 +#define WGL_SAMPLES_EXT 0x2042 +#endif + +#ifdef WGL_EXT_pbuffer +DECLARE_HANDLE(HPBUFFEREXT);; +#define WGL_DRAW_TO_PBUFFER_EXT 0x202D +#define WGL_MAX_PBUFFER_PIXELS_EXT 0x202E +#define WGL_MAX_PBUFFER_WIDTH_EXT 0x202F +#define WGL_MAX_PBUFFER_HEIGHT_EXT 0x2030 +#define WGL_OPTIMAL_PBUFFER_WIDTH_EXT 0x2031 +#define WGL_OPTIMAL_PBUFFER_HEIGHT_EXT 0x2032 +#define WGL_PBUFFER_LARGEST_EXT 0x2033 +#define WGL_PBUFFER_WIDTH_EXT 0x2034 +#define WGL_PBUFFER_HEIGHT_EXT 0x2035 +#define wglCreatePbufferEXT XGL_FUNCPTR(wglCreatePbufferEXT) +#define wglDestroyPbufferEXT XGL_FUNCPTR(wglDestroyPbufferEXT) +#define wglGetPbufferDCEXT XGL_FUNCPTR(wglGetPbufferDCEXT) +#define wglQueryPbufferEXT XGL_FUNCPTR(wglQueryPbufferEXT) +#define wglReleasePbufferDCEXT XGL_FUNCPTR(wglReleasePbufferDCEXT) +#endif + +#ifdef WGL_EXT_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_EXT 0x2000 +#define WGL_DRAW_TO_WINDOW_EXT 0x2001 +#define WGL_DRAW_TO_BITMAP_EXT 0x2002 +#define WGL_ACCELERATION_EXT 0x2003 +#define WGL_NEED_PALETTE_EXT 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_EXT 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_EXT 0x2006 +#define WGL_SWAP_METHOD_EXT 0x2007 +#define WGL_NUMBER_OVERLAYS_EXT 0x2008 +#define WGL_NUMBER_UNDERLAYS_EXT 0x2009 +#define WGL_TRANSPARENT_EXT 0x200A +#define WGL_TRANSPARENT_VALUE_EXT 0x200B +#define WGL_SHARE_DEPTH_EXT 0x200C +#define WGL_SHARE_STENCIL_EXT 0x200D +#define WGL_SHARE_ACCUM_EXT 0x200E +#define WGL_SUPPORT_GDI_EXT 0x200F +#define WGL_SUPPORT_OPENGL_EXT 0x2010 +#define WGL_DOUBLE_BUFFER_EXT 0x2011 +#define WGL_STEREO_EXT 0x2012 +#define WGL_PIXEL_TYPE_EXT 0x2013 +#define WGL_COLOR_BITS_EXT 0x2014 +#define WGL_RED_BITS_EXT 0x2015 +#define WGL_RED_SHIFT_EXT 0x2016 +#define WGL_GREEN_BITS_EXT 0x2017 +#define WGL_GREEN_SHIFT_EXT 0x2018 +#define WGL_BLUE_BITS_EXT 0x2019 +#define WGL_BLUE_SHIFT_EXT 0x201A +#define WGL_ALPHA_BITS_EXT 0x201B +#define WGL_ALPHA_SHIFT_EXT 0x201C +#define WGL_ACCUM_BITS_EXT 0x201D +#define WGL_ACCUM_RED_BITS_EXT 0x201E +#define WGL_ACCUM_GREEN_BITS_EXT 0x201F +#define WGL_ACCUM_BLUE_BITS_EXT 0x2020 +#define WGL_ACCUM_ALPHA_BITS_EXT 0x2021 +#define WGL_DEPTH_BITS_EXT 0x2022 +#define WGL_STENCIL_BITS_EXT 0x2023 +#define WGL_AUX_BUFFERS_EXT 0x2024 +#define WGL_NO_ACCELERATION_EXT 0x2025 +#define WGL_GENERIC_ACCELERATION_EXT 0x2026 +#define WGL_FULL_ACCELERATION_EXT 0x2027 +#define WGL_SWAP_EXCHANGE_EXT 0x2028 +#define WGL_SWAP_COPY_EXT 0x2029 +#define WGL_SWAP_UNDEFINED_EXT 0x202A +#define WGL_TYPE_RGBA_EXT 0x202B +#define WGL_TYPE_COLORINDEX_EXT 0x202C +#define wglChoosePixelFormatEXT XGL_FUNCPTR(wglChoosePixelFormatEXT) +#define wglGetPixelFormatAttribfvEXT XGL_FUNCPTR(wglGetPixelFormatAttribfvEXT) +#define wglGetPixelFormatAttribivEXT XGL_FUNCPTR(wglGetPixelFormatAttribivEXT) +#endif + +#ifdef WGL_EXT_swap_control +#define wglGetSwapIntervalEXT XGL_FUNCPTR(wglGetSwapIntervalEXT) +#define wglSwapIntervalEXT XGL_FUNCPTR(wglSwapIntervalEXT) +#endif + +#ifdef WGL_I3D_digital_video_control +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_FRAMEBUFFER_I3D 0x2050 +#define WGL_DIGITAL_VIDEO_CURSOR_ALPHA_VALUE_I3D 0x2051 +#define WGL_DIGITAL_VIDEO_CURSOR_INCLUDED_I3D 0x2052 +#define WGL_DIGITAL_VIDEO_GAMMA_CORRECTED_I3D 0x2053 +#define wglGetDigitalVideoParametersI3D XGL_FUNCPTR(wglGetDigitalVideoParametersI3D) +#define wglSetDigitalVideoParametersI3D XGL_FUNCPTR(wglSetDigitalVideoParametersI3D) +#endif + +#ifdef WGL_I3D_gamma +#define WGL_GAMMA_TABLE_SIZE_I3D 0x204E +#define WGL_GAMMA_EXCLUDE_DESKTOP_I3D 0x204F +#define wglGetGammaTableI3D XGL_FUNCPTR(wglGetGammaTableI3D) +#define wglGetGammaTableParametersI3D XGL_FUNCPTR(wglGetGammaTableParametersI3D) +#define wglSetGammaTableI3D XGL_FUNCPTR(wglSetGammaTableI3D) +#define wglSetGammaTableParametersI3D XGL_FUNCPTR(wglSetGammaTableParametersI3D) +#endif + +#ifdef WGL_I3D_genlock +#define WGL_GENLOCK_SOURCE_MULTIVIEW_I3D 0x2044 +#define WGL_GENLOCK_SOURCE_EXTERNAL_SYNC_I3D 0x2045 +#define WGL_GENLOCK_SOURCE_EXTERNAL_FIELD_I3D 0x2046 +#define WGL_GENLOCK_SOURCE_EXTERNAL_TTL_I3D 0x2047 +#define WGL_GENLOCK_SOURCE_DIGITAL_SYNC_I3D 0x2048 +#define WGL_GENLOCK_SOURCE_DIGITAL_FIELD_I3D 0x2049 +#define WGL_GENLOCK_SOURCE_EDGE_FALLING_I3D 0x204A +#define WGL_GENLOCK_SOURCE_EDGE_RISING_I3D 0x204B +#define WGL_GENLOCK_SOURCE_EDGE_BOTH_I3D 0x204C +#define wglDisableGenlockI3D XGL_FUNCPTR(wglDisableGenlockI3D) +#define wglEnableGenlockI3D XGL_FUNCPTR(wglEnableGenlockI3D) +#define wglGenlockSampleRateI3D XGL_FUNCPTR(wglGenlockSampleRateI3D) +#define wglGenlockSourceDelayI3D XGL_FUNCPTR(wglGenlockSourceDelayI3D) +#define wglGenlockSourceEdgeI3D XGL_FUNCPTR(wglGenlockSourceEdgeI3D) +#define wglGenlockSourceI3D XGL_FUNCPTR(wglGenlockSourceI3D) +#define wglGetGenlockSampleRateI3D XGL_FUNCPTR(wglGetGenlockSampleRateI3D) +#define wglGetGenlockSourceDelayI3D XGL_FUNCPTR(wglGetGenlockSourceDelayI3D) +#define wglGetGenlockSourceEdgeI3D XGL_FUNCPTR(wglGetGenlockSourceEdgeI3D) +#define wglGetGenlockSourceI3D XGL_FUNCPTR(wglGetGenlockSourceI3D) +#define wglIsEnabledGenlockI3D XGL_FUNCPTR(wglIsEnabledGenlockI3D) +#define wglQueryGenlockMaxSourceDelayI3D XGL_FUNCPTR(wglQueryGenlockMaxSourceDelayI3D) +#endif + +#ifdef WGL_I3D_image_buffer +#define WGL_IMAGE_BUFFER_MIN_ACCESS_I3D 0x00000001 +#define WGL_IMAGE_BUFFER_LOCK_I3D 0x00000002 +#define wglAssociateImageBufferEventsI3D XGL_FUNCPTR(wglAssociateImageBufferEventsI3D) +#define wglCreateImageBufferI3D XGL_FUNCPTR(wglCreateImageBufferI3D) +#define wglDestroyImageBufferI3D XGL_FUNCPTR(wglDestroyImageBufferI3D) +#define wglReleaseImageBufferEventsI3D XGL_FUNCPTR(wglReleaseImageBufferEventsI3D) +#endif + +#ifdef WGL_I3D_swap_frame_lock +#define wglDisableFrameLockI3D XGL_FUNCPTR(wglDisableFrameLockI3D) +#define wglEnableFrameLockI3D XGL_FUNCPTR(wglEnableFrameLockI3D) +#define wglIsEnabledFrameLockI3D XGL_FUNCPTR(wglIsEnabledFrameLockI3D) +#define wglQueryFrameLockMasterI3D XGL_FUNCPTR(wglQueryFrameLockMasterI3D) +#endif + +#ifdef WGL_I3D_swap_frame_usage +#define wglBeginFrameTrackingI3D XGL_FUNCPTR(wglBeginFrameTrackingI3D) +#define wglEndFrameTrackingI3D XGL_FUNCPTR(wglEndFrameTrackingI3D) +#define wglGetFrameUsageI3D XGL_FUNCPTR(wglGetFrameUsageI3D) +#define wglQueryFrameTrackingI3D XGL_FUNCPTR(wglQueryFrameTrackingI3D) +#endif + +#ifdef WGL_NV_float_buffer +#define WGL_FLOAT_COMPONENTS_NV 0x20B0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_R_NV 0x20B1 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RG_NV 0x20B2 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGB_NV 0x20B3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_FLOAT_RGBA_NV 0x20B4 +#define WGL_TEXTURE_FLOAT_R_NV 0x20B5 +#define WGL_TEXTURE_FLOAT_RG_NV 0x20B6 +#define WGL_TEXTURE_FLOAT_RGB_NV 0x20B7 +#define WGL_TEXTURE_FLOAT_RGBA_NV 0x20B8 +#endif + +#ifdef WGL_NV_render_depth_texture +#define WGL_NO_TEXTURE_ARB 0x2077 +#define WGL_BIND_TO_TEXTURE_DEPTH_NV 0x20A3 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_DEPTH_NV 0x20A4 +#define WGL_DEPTH_TEXTURE_FORMAT_NV 0x20A5 +#define WGL_TEXTURE_DEPTH_COMPONENT_NV 0x20A6 +#define WGL_DEPTH_COMPONENT_NV 0x20A7 +#endif + +#ifdef WGL_NV_render_texture_rectangle +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGB_NV 0x20A0 +#define WGL_BIND_TO_TEXTURE_RECTANGLE_RGBA_NV 0x20A1 +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 +#endif + +#ifdef WGL_NV_vertex_array_range +#define wglAllocateMemoryNV XGL_FUNCPTR(wglAllocateMemoryNV) +#define wglFreeMemoryNV XGL_FUNCPTR(wglFreeMemoryNV) +#endif + +#ifdef WGL_OML_sync_control +#define wglGetMscRateOML XGL_FUNCPTR(wglGetMscRateOML) +#define wglGetSyncValuesOML XGL_FUNCPTR(wglGetSyncValuesOML) +#define wglSwapBuffersMscOML XGL_FUNCPTR(wglSwapBuffersMscOML) +#define wglSwapLayerBuffersMscOML XGL_FUNCPTR(wglSwapLayerBuffersMscOML) +#define wglWaitForMscOML XGL_FUNCPTR(wglWaitForMscOML) +#define wglWaitForSbcOML XGL_FUNCPTR(wglWaitForSbcOML) +#endif + diff --git a/gfx/gl/ggl/generated/wglefn.h b/gfx/gl/ggl/generated/wglefn.h new file mode 100644 index 0000000..d10f982 --- /dev/null +++ b/gfx/gl/ggl/generated/wglefn.h @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifdef WGL_3DFX_multisample +GL_GROUP_BEGIN(WGL_3DFX_multisample) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_buffer_region +GL_GROUP_BEGIN(WGL_ARB_buffer_region) +GL_FUNCTION(wglCreateBufferRegionARB,HANDLE,(HDC hDC, int iLayerPlane, UINT uType)) +GL_FUNCTION(wglDeleteBufferRegionARB,VOID,(HANDLE hRegion)) +GL_FUNCTION(wglRestoreBufferRegionARB,BOOL,(HANDLE hRegion, int x, int y, int width, int height, int xSrc, int ySrc)) +GL_FUNCTION(wglSaveBufferRegionARB,BOOL,(HANDLE hRegion, int x, int y, int width, int height)) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_extensions_string +GL_GROUP_BEGIN(WGL_ARB_extensions_string) +GL_FUNCTION(wglGetExtensionsStringARB,const char*,(HDC hdc)) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_make_current_read +GL_GROUP_BEGIN(WGL_ARB_make_current_read) +GL_FUNCTION(wglGetCurrentReadDCARB,HDC,(VOID)) +GL_FUNCTION(wglMakeContextCurrentARB,BOOL,(HDC hDrawDC, HDC hReadDC, HGLRC hglrc)) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_multisample +GL_GROUP_BEGIN(WGL_ARB_multisample) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_pbuffer +GL_GROUP_BEGIN(WGL_ARB_pbuffer) +GL_FUNCTION(wglCreatePbufferARB,HPBUFFERARB,(HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int* piAttribList)) +GL_FUNCTION(wglDestroyPbufferARB,BOOL,(HPBUFFERARB hPbuffer)) +GL_FUNCTION(wglGetPbufferDCARB,HDC,(HPBUFFERARB hPbuffer)) +GL_FUNCTION(wglQueryPbufferARB,BOOL,(HPBUFFERARB hPbuffer, int iAttribute, int* piValue)) +GL_FUNCTION(wglReleasePbufferDCARB,int,(HPBUFFERARB hPbuffer, HDC hDC)) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_pixel_format +GL_GROUP_BEGIN(WGL_ARB_pixel_format) +GL_FUNCTION(wglChoosePixelFormatARB,BOOL,(HDC hdc, const int* piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats)) +GL_FUNCTION(wglGetPixelFormatAttribfvARB,BOOL,(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int* piAttributes, FLOAT *pfValues)) +GL_FUNCTION(wglGetPixelFormatAttribivARB,BOOL,(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int* piAttributes, int *piValues)) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_pixel_format_float +GL_GROUP_BEGIN(WGL_ARB_pixel_format_float) +GL_GROUP_END() +#endif + +#ifdef WGL_ARB_render_texture +GL_GROUP_BEGIN(WGL_ARB_render_texture) +GL_FUNCTION(wglBindTexImageARB,BOOL,(HPBUFFERARB hPbuffer, int iBuffer)) +GL_FUNCTION(wglReleaseTexImageARB,BOOL,(HPBUFFERARB hPbuffer, int iBuffer)) +GL_FUNCTION(wglSetPbufferAttribARB,BOOL,(HPBUFFERARB hPbuffer, const int* piAttribList)) +GL_GROUP_END() +#endif + +#ifdef WGL_ATI_pixel_format_float +GL_GROUP_BEGIN(WGL_ATI_pixel_format_float) +GL_GROUP_END() +#endif + +#ifdef WGL_ATI_render_texture_rectangle +GL_GROUP_BEGIN(WGL_ATI_render_texture_rectangle) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_depth_float +GL_GROUP_BEGIN(WGL_EXT_depth_float) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_display_color_table +GL_GROUP_BEGIN(WGL_EXT_display_color_table) +GL_FUNCTION(wglBindDisplayColorTableEXT,GLboolean,(GLushort id)) +GL_FUNCTION(wglCreateDisplayColorTableEXT,GLboolean,(GLushort id)) +GL_FUNCTION(wglDestroyDisplayColorTableEXT,void,(GLushort id)) +GL_FUNCTION(wglLoadDisplayColorTableEXT,GLboolean,(GLushort* table, GLuint length)) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_extensions_string +GL_GROUP_BEGIN(WGL_EXT_extensions_string) +GL_FUNCTION(wglGetExtensionsStringEXT,const char*,(void)) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_make_current_read +GL_GROUP_BEGIN(WGL_EXT_make_current_read) +GL_FUNCTION(wglGetCurrentReadDCEXT,HDC,(VOID)) +GL_FUNCTION(wglMakeContextCurrentEXT,BOOL,(HDC hDrawDC, HDC hReadDC, HGLRC hglrc)) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_multisample +GL_GROUP_BEGIN(WGL_EXT_multisample) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_pbuffer +GL_GROUP_BEGIN(WGL_EXT_pbuffer) +GL_FUNCTION(wglCreatePbufferEXT,HPBUFFEREXT,(HDC hDC, int iPixelFormat, int iWidth, int iHeight, const int* piAttribList)) +GL_FUNCTION(wglDestroyPbufferEXT,BOOL,(HPBUFFEREXT hPbuffer)) +GL_FUNCTION(wglGetPbufferDCEXT,HDC,(HPBUFFEREXT hPbuffer)) +GL_FUNCTION(wglQueryPbufferEXT,BOOL,(HPBUFFEREXT hPbuffer, int iAttribute, int* piValue)) +GL_FUNCTION(wglReleasePbufferDCEXT,int,(HPBUFFEREXT hPbuffer, HDC hDC)) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_pixel_format +GL_GROUP_BEGIN(WGL_EXT_pixel_format) +GL_FUNCTION(wglChoosePixelFormatEXT,BOOL,(HDC hdc, const int* piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats)) +GL_FUNCTION(wglGetPixelFormatAttribfvEXT,BOOL,(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int* piAttributes, FLOAT *pfValues)) +GL_FUNCTION(wglGetPixelFormatAttribivEXT,BOOL,(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, int* piAttributes, int *piValues)) +GL_GROUP_END() +#endif + +#ifdef WGL_EXT_swap_control +GL_GROUP_BEGIN(WGL_EXT_swap_control) +GL_FUNCTION(wglGetSwapIntervalEXT,int,(void)) +GL_FUNCTION(wglSwapIntervalEXT,BOOL,(int interval)) +GL_GROUP_END() +#endif + +#ifdef WGL_I3D_digital_video_control +GL_GROUP_BEGIN(WGL_I3D_digital_video_control) +GL_FUNCTION(wglGetDigitalVideoParametersI3D,BOOL,(HDC hDC, int iAttribute, int* piValue)) +GL_FUNCTION(wglSetDigitalVideoParametersI3D,BOOL,(HDC hDC, int iAttribute, const int* piValue)) +GL_GROUP_END() +#endif + +#ifdef WGL_I3D_gamma +GL_GROUP_BEGIN(WGL_I3D_gamma) +GL_FUNCTION(wglGetGammaTableI3D,BOOL,(HDC hDC, int iEntries, USHORT* puRed, USHORT *puGreen, USHORT *puBlue)) +GL_FUNCTION(wglGetGammaTableParametersI3D,BOOL,(HDC hDC, int iAttribute, int* piValue)) +GL_FUNCTION(wglSetGammaTableI3D,BOOL,(HDC hDC, int iEntries, const USHORT* puRed, const USHORT *puGreen, const USHORT *puBlue)) +GL_FUNCTION(wglSetGammaTableParametersI3D,BOOL,(HDC hDC, int iAttribute, const int* piValue)) +GL_GROUP_END() +#endif + +#ifdef WGL_I3D_genlock +GL_GROUP_BEGIN(WGL_I3D_genlock) +GL_FUNCTION(wglDisableGenlockI3D,BOOL,(HDC hDC)) +GL_FUNCTION(wglEnableGenlockI3D,BOOL,(HDC hDC)) +GL_FUNCTION(wglGenlockSampleRateI3D,BOOL,(HDC hDC, UINT uRate)) +GL_FUNCTION(wglGenlockSourceDelayI3D,BOOL,(HDC hDC, UINT uDelay)) +GL_FUNCTION(wglGenlockSourceEdgeI3D,BOOL,(HDC hDC, UINT uEdge)) +GL_FUNCTION(wglGenlockSourceI3D,BOOL,(HDC hDC, UINT uSource)) +GL_FUNCTION(wglGetGenlockSampleRateI3D,BOOL,(HDC hDC, UINT* uRate)) +GL_FUNCTION(wglGetGenlockSourceDelayI3D,BOOL,(HDC hDC, UINT* uDelay)) +GL_FUNCTION(wglGetGenlockSourceEdgeI3D,BOOL,(HDC hDC, UINT* uEdge)) +GL_FUNCTION(wglGetGenlockSourceI3D,BOOL,(HDC hDC, UINT* uSource)) +GL_FUNCTION(wglIsEnabledGenlockI3D,BOOL,(HDC hDC, BOOL* pFlag)) +GL_FUNCTION(wglQueryGenlockMaxSourceDelayI3D,BOOL,(HDC hDC, UINT* uMaxLineDelay, UINT *uMaxPixelDelay)) +GL_GROUP_END() +#endif + +#ifdef WGL_I3D_image_buffer +GL_GROUP_BEGIN(WGL_I3D_image_buffer) +GL_FUNCTION(wglAssociateImageBufferEventsI3D,BOOL,(HDC hdc, HANDLE* pEvent, LPVOID *pAddress, DWORD *pSize, UINT count)) +GL_FUNCTION(wglCreateImageBufferI3D,LPVOID,(HDC hDC, DWORD dwSize, UINT uFlags)) +GL_FUNCTION(wglDestroyImageBufferI3D,BOOL,(HDC hDC, LPVOID pAddress)) +GL_FUNCTION(wglReleaseImageBufferEventsI3D,BOOL,(HDC hdc, LPVOID* pAddress, UINT count)) +GL_GROUP_END() +#endif + +#ifdef WGL_I3D_swap_frame_lock +GL_GROUP_BEGIN(WGL_I3D_swap_frame_lock) +GL_FUNCTION(wglDisableFrameLockI3D,BOOL,(VOID)) +GL_FUNCTION(wglEnableFrameLockI3D,BOOL,(VOID)) +GL_FUNCTION(wglIsEnabledFrameLockI3D,BOOL,(BOOL* pFlag)) +GL_FUNCTION(wglQueryFrameLockMasterI3D,BOOL,(BOOL* pFlag)) +GL_GROUP_END() +#endif + +#ifdef WGL_I3D_swap_frame_usage +GL_GROUP_BEGIN(WGL_I3D_swap_frame_usage) +GL_FUNCTION(wglBeginFrameTrackingI3D,BOOL,(void)) +GL_FUNCTION(wglEndFrameTrackingI3D,BOOL,(void)) +GL_FUNCTION(wglGetFrameUsageI3D,BOOL,(float* pUsage)) +GL_FUNCTION(wglQueryFrameTrackingI3D,BOOL,(DWORD* pFrameCount, DWORD *pMissedFrames, float *pLastMissedUsage)) +GL_GROUP_END() +#endif + +#ifdef WGL_NV_float_buffer +GL_GROUP_BEGIN(WGL_NV_float_buffer) +GL_GROUP_END() +#endif + +#ifdef WGL_NV_render_depth_texture +GL_GROUP_BEGIN(WGL_NV_render_depth_texture) +GL_GROUP_END() +#endif + +#ifdef WGL_NV_render_texture_rectangle +GL_GROUP_BEGIN(WGL_NV_render_texture_rectangle) +GL_GROUP_END() +#endif + +#ifdef WGL_NV_vertex_array_range +GL_GROUP_BEGIN(WGL_NV_vertex_array_range) +GL_FUNCTION(wglAllocateMemoryNV,void *,(GLsizei size, GLfloat readFrequency, GLfloat writeFrequency, GLfloat priority)) +GL_FUNCTION(wglFreeMemoryNV,void,(void *pointer)) +GL_GROUP_END() +#endif + +#ifdef WGL_OML_sync_control +GL_GROUP_BEGIN(WGL_OML_sync_control) +GL_FUNCTION(wglGetMscRateOML,BOOL,(HDC hdc, INT32* numerator, INT32 *denominator)) +GL_FUNCTION(wglGetSyncValuesOML,BOOL,(HDC hdc, INT64* ust, INT64 *msc, INT64 *sbc)) +GL_FUNCTION(wglSwapBuffersMscOML,INT64,(HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder)) +GL_FUNCTION(wglSwapLayerBuffersMscOML,INT64,(HDC hdc, INT fuPlanes, INT64 target_msc, INT64 divisor, INT64 remainder)) +GL_FUNCTION(wglWaitForMscOML,BOOL,(HDC hdc, INT64 target_msc, INT64 divisor, INT64 remainder, INT64* ust, INT64 *msc, INT64 *sbc)) +GL_FUNCTION(wglWaitForSbcOML,BOOL,(HDC hdc, INT64 target_sbc, INT64* ust, INT64 *msc, INT64 *sbc)) +GL_GROUP_END() +#endif + diff --git a/gfx/gl/ggl/generated/wglfn.h b/gfx/gl/ggl/generated/wglfn.h new file mode 100644 index 0000000..f23a6c0 --- /dev/null +++ b/gfx/gl/ggl/generated/wglfn.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +// Windows WGL functions +GL_GROUP_BEGIN(ARB_win32) +GL_FUNCTION(wglCopyContext, BOOL, (HGLRC, HGLRC, UINT)) +GL_FUNCTION(wglCreateContext, HGLRC, (HDC)) +GL_FUNCTION(wglCreateLayerContext, HGLRC, (HDC, GLint)) +GL_FUNCTION(wglDeleteContext, BOOL, (HGLRC)) +GL_FUNCTION(wglGetCurrentContext, HGLRC, (VOID)) +GL_FUNCTION(wglGetCurrentDC, HDC, (VOID)) +GL_FUNCTION(wglGetProcAddress, PROC, (LPCSTR)) +GL_FUNCTION(wglMakeCurrent, BOOL, (HDC, HGLRC)) +GL_FUNCTION(wglShareLists, BOOL, (HGLRC, HGLRC)) +GL_FUNCTION(wglDescribeLayerPlane, BOOL, (HDC, GLint, GLint, UINT, LPLAYERPLANEDESCRIPTOR)) +GL_FUNCTION(wglSetLayerPaletteEntries, GLint, (HDC, GLint, GLint, GLint, CONST COLORREF *)) +GL_FUNCTION(wglGetLayerPaletteEntries, GLint, (HDC, GLint, GLint, GLint, COLORREF *)) +GL_FUNCTION(wglRealizeLayerPalette, BOOL, (HDC, GLint, BOOL)) +GL_FUNCTION(wglSwapLayerBuffers, BOOL, (HDC, UINT)) + +// Ascii and Unicode versions +GL_FUNCTION(wglUseFontBitmapsA, BOOL, (HDC, DWORD, DWORD, DWORD)) +GL_FUNCTION(wglUseFontOutlinesA, BOOL, (HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, GLint, LPGLYPHMETRICSFLOAT)) +GL_FUNCTION(wglUseFontBitmapsW, BOOL, (HDC, DWORD, DWORD, DWORD)) +GL_FUNCTION(wglUseFontOutlinesW, BOOL, (HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, GLint, LPGLYPHMETRICSFLOAT)) + +GL_GROUP_END() + diff --git a/gfx/gl/ggl/generator/parse.cpp b/gfx/gl/ggl/generator/parse.cpp new file mode 100644 index 0000000..3ca39f9 --- /dev/null +++ b/gfx/gl/ggl/generator/parse.cpp @@ -0,0 +1,606 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#include "os/osFile.h" +#include "os/osPrint.h" +#include "util/utilStr.h" +#include "util/utilString.h" +#include "util/utilArray.h" +#include "util/utilMap.h" +#include "util/utilSort.h" + +using namespace Torque; + +#include +#include + +//----------------------------------------------------------------------------- + +const char* OrderTable[] = +{ + // Extension list as of 8.25.2005 + // http://oss.sgi.com/projects/ogl-sample/registry/ + + // Core + "GL_VERSION_1_1", + "GL_VERSION_1_2", + "GL_VERSION_1_3", + "GL_VERSION_1_4", + "GL_VERSION_1_5", + + // X Windows + "GLX_VERSION_1_1", + "GLX_VERSION_1_2", + "GLX_VERSION_1_3", + "GLX_VERSION_1_4", + "GLX_VERSION_1_5", + + // Arb extension + "GL_ARB_multitexture", + "GLX_ARB_get_proc_address", + "GL_ARB_transpose_matrix", + "WGL_ARB_buffer_region", + "GL_ARB_multisample", + "GL_ARB_texture_env_add", + "GL_ARB_texture_cube_map", + "WGL_ARB_extensions_string", + "WGL_ARB_pixel_format", + "WGL_ARB_make_current_read", + "WGL_ARB_pbuffer", + "GL_ARB_texture_compression", + "GL_ARB_texture_border_clamp", + "GL_ARB_point_parameters", + "GL_ARB_vertex_blend", + "GL_ARB_matrix_palette", + "GL_ARB_texture_env_combine", + "GL_ARB_texture_env_crossbar", + "GL_ARB_texture_env_dot3", + "WGL_ARB_render_texture", + "GL_ARB_texture_mirrored_repeat", + "GL_ARB_depth_texture", + "GL_ARB_shadow", + "GL_ARB_shadow_ambient", + "GL_ARB_window_pos", + "GL_ARB_vertex_program", + "GL_ARB_fragment_program", + "GL_ARB_vertex_buffer_object", + "GL_ARB_occlusion_query", + "GL_ARB_shader_objects", + "GL_ARB_vertex_shader", + "GL_ARB_fragment_shader", + "GL_ARB_shading_language_100", + "GL_ARB_texture_non_power_of_two", + "GL_ARB_point_sprite", + "GL_ARB_fragment_program_shadow", + "GL_ARB_draw_buffers", + "GL_ARB_texture_rectangle", + "GL_ARB_color_buffer_float", + "GL_ARB_half_float_pixel", + "GL_ARB_texture_float", + "GL_ARB_pixel_buffer_object", + + // Misc extensions + "GL_EXT_abgr", + "GL_EXT_blend_color", + "GL_EXT_polygon_offset", + "GL_EXT_texture", + "GL_EXT_texture3D", + "GL_SGIS_texture_filter4", + "GL_EXT_subtexture", + "GL_EXT_copy_texture", + "GL_EXT_histogram", + "GL_EXT_convolution", + "GL_SGI_color_matrix", + "GL_SGI_color_table", + "GL_SGIS_pixel_texture", + "GL_SGIX_pixel_texture", + "GL_SGIS_texture4D", + "GL_SGI_texture_color_table", + "GL_EXT_cmyka", + "GL_EXT_texture_object", + "GL_SGIS_detail_texture", + "GL_SGIS_sharpen_texture", + "GL_EXT_packed_pixels", + "GL_SGIS_texture_lod", + "GL_SGIS_multisample", + "GL_EXT_rescale_normal", + "GLX_EXT_visual_info", + "GL_EXT_vertex_array", + "GL_EXT_misc_attribute", + "GL_SGIS_generate_mipmap", + "GL_SGIX_clipmap", + "GL_SGIX_shadow", + "GL_SGIS_texture_edge_clamp", + "GL_SGIS_texture_border_clamp", + "GL_EXT_blend_minmax", + "GL_EXT_blend_subtract", + "GL_EXT_blend_logic_op", + "GLX_SGI_swap_control", + "GLX_SGI_video_sync", + "GLX_SGI_make_current_read", + "GLX_SGIX_video_source", + "GLX_EXT_visual_rating", + "GL_SGIX_interlace", + "GLX_EXT_import_context", + "GLX_SGIX_fbconfig", + "GLX_SGIX_pbuffer", + "GL_SGIS_texture_select", + "GL_SGIX_sprite", + "GL_SGIX_texture_multi_buffer", + "GL_EXT_point_parameters", + "GL_SGIX_instruments", + "GL_SGIX_texture_scale_bias", + "GL_SGIX_framezoom", + "GL_SGIX_tag_sample_buffer", + "GL_SGIX_reference_plane", + "GL_SGIX_flush_raster", + "GLX_SGI_cushion", + "GL_SGIX_depth_texture", + "GL_SGIS_fog_function", + "GL_SGIX_fog_offset", + "GL_HP_image_transform", + "GL_HP_convolution_border_modes", + "GL_SGIX_texture_add_env", + "GL_EXT_color_subtable", + "GLU_EXT_object_space_tess", + "GL_PGI_vertex_hints", + "GL_PGI_misc_hints", + "GL_EXT_paletted_texture", + "GL_EXT_clip_volume_hint", + "GL_SGIX_list_priority", + "GL_SGIX_ir_instrument1", + "GLX_SGIX_video_resize", + "GL_SGIX_texture_lod_bias", + "GLU_SGI_filter4_parameters", + "GLX_SGIX_dm_buffer", + "GL_SGIX_shadow_ambient", + "GLX_SGIX_swap_group", + "GLX_SGIX_swap_barrier", + "GL_EXT_index_texture", + "GL_EXT_index_material", + "GL_EXT_index_func", + "GL_EXT_index_array_formats", + "GL_EXT_compiled_vertex_array", + "GL_EXT_cull_vertex", + "GLU_EXT_nurbs_tessellator", + "GL_SGIX_ycrcb", + "GL_EXT_fragment_lighting", + "GL_IBM_rasterpos_clip", + "GL_HP_texture_lighting", + "GL_EXT_draw_range_elements", + "GL_WIN_phong_shading", + "GL_WIN_specular_fog", + "GLX_SGIS_color_range", + "GL_EXT_light_texture", + "GL_SGIX_blend_alpha_minmax", + "GL_EXT_scene_marker", + "GL_SGIX_pixel_texture_bits", + "GL_EXT_bgra", + "GL_SGIX_async", + "GL_SGIX_async_pixel", + "GL_SGIX_async_histogram", + "GL_INTEL_texture_scissor", + "GL_INTEL_parallel_arrays", + "GL_HP_occlusion_test", + "GL_EXT_pixel_transform", + "GL_EXT_pixel_transform_color_table", + "GL_EXT_shared_texture_palette", + "GLX_SGIS_blended_overlay", + "GL_EXT_separate_specular_color", + "GL_EXT_secondary_color", + "GL_EXT_texture_env", + "GL_EXT_texture_perturb_normal", + "GL_EXT_multi_draw_arrays", + "GL_EXT_fog_coord", + "GL_REND_screen_coordinates", + "GL_EXT_coordinate_frame", + "GL_EXT_texture_env_combine", + "GL_APPLE_specular_vector", + "GL_APPLE_transform_hint", + "GL_SUNX_constant_data", + "GL_SUN_global_alpha", + "GL_SUN_triangle_list", + "GL_SUN_vertex", + "WGL_EXT_display_color_table", + "WGL_EXT_extensions_string", + "WGL_EXT_make_current_read", + "WGL_EXT_pixel_format", + "WGL_EXT_pbuffer", + "WGL_EXT_swap_control", + "GL_EXT_blend_func_separate", + "GL_INGR_color_clamp", + "GL_INGR_interlace_read", + "GL_EXT_stencil_wrap", + "WGL_EXT_depth_float", + "GL_EXT_422_pixels", + "GL_NV_texgen_reflection", + "GL_SGIX_texture_range", + "GL_SUN_convolution_border_modes", + "GLX_SUN_get_transparent_index", + "GL_EXT_texture_env_add", + "GL_EXT_texture_lod_bias", + "GL_EXT_texture_filter_anisotropic", + "GL_EXT_vertex_weighting", + "GL_NV_light_max_exponent", + "GL_NV_vertex_array_range", + "GL_NV_register_combiners", + "GL_NV_fog_distance", + "GL_NV_texgen_emboss", + "GL_NV_blend_square", + "GL_NV_texture_env_combine4", + "GL_MESA_resize_buffers", + "GL_MESA_window_pos", + "GL_EXT_texture_compression_s3tc", + "GL_IBM_cull_vertex", + "GL_IBM_multimode_draw_arrays", + "GL_IBM_vertex_array_lists", + "GL_3DFX_texture_compression_FXT1", + "GL_3DFX_multisample", + "GL_3DFX_tbuffer", + "WGL_EXT_multisample", + "GL_SGIX_vertex_preclip", + "GL_SGIX_resample", + "GL_SGIS_texture_color_mask", + "GLX_MESA_copy_sub_buffer", + "GLX_MESA_pixmap_colormap", + "GLX_MESA_release_buffers", + "GLX_MESA_set_3dfx_mode", + "GL_EXT_texture_env_dot3", + "GL_ATI_texture_mirror_once", + "GL_NV_fence", + "GL_IBM_static_data", + "GL_IBM_texture_mirrored_repeat", + "GL_NV_evaluators", + "GL_NV_packed_depth_stencil", + "GL_NV_register_combiners2", + "GL_NV_texture_compression_vtc", + "GL_NV_texture_rectangle", + "GL_NV_texture_shader", + "GL_NV_texture_shader2", + "GL_NV_vertex_array_range2", + "GL_NV_vertex_program", + "GLX_SGIX_visual_select_group", + "GL_SGIX_texture_coordinate_clamp", + "GLX_OML_swap_method", + "GLX_OML_sync_control", + "GL_OML_interlace", + "GL_OML_subsample", + "GL_OML_resample", + "WGL_OML_sync_control", + "GL_NV_copy_depth_to_color", + "GL_ATI_envmap_bumpmap", + "GL_ATI_fragment_shader", + "GL_ATI_pn_triangles", + "GL_ATI_vertex_array_object", + "GL_EXT_vertex_shader", + "GL_ATI_vertex_streams", + "WGL_I3D_digital_video_control", + "WGL_I3D_gamma", + "WGL_I3D_genlock", + "WGL_I3D_image_buffer", + "WGL_I3D_swap_frame_lock", + "WGL_I3D_swap_frame_usage", + "GL_ATI_element_array", + "GL_SUN_mesh_array", + "GL_SUN_slice_accum", + "GL_NV_multisample_filter_hint", + "GL_NV_depth_clamp", + "GL_NV_occlusion_query", + "GL_NV_point_sprite", + "WGL_NV_render_depth_texture", + "WGL_NV_render_texture_rectangle", + "GL_NV_texture_shader3", + "GL_NV_vertex_program1_1", + "GL_EXT_shadow_funcs", + "GL_EXT_stencil_two_side", + "GL_ATI_text_fragment_shader", + "GL_APPLE_client_storage", + "GL_APPLE_element_array", + "GL_APPLE_fence", + "GL_APPLE_vertex_array_object", + "GL_APPLE_vertex_array_range", + "GL_APPLE_ycbcr_422", + "GL_S3_s3tc", + "GL_ATI_draw_buffers", + "WGL_ATI_pixel_format_float", + "GL_ATI_texture_env_combine3", + "GL_ATI_texture_float", + "GL_NV_float_buffer", + "GL_NV_fragment_program", + "GL_NV_half_float", + "GL_NV_pixel_data_range", + "GL_NV_primitive_restart", + "GL_NV_texture_expand_normal", + "GL_NV_vertex_program2", + "GL_ATI_map_object_buffer", + "GL_ATI_separate_stencil", + "GL_ATI_vertex_attrib_array_object", + "GL_OES_byte_coordinates", + "GL_OES_fixed_point", + "GL_OES_single_precision", + "GL_OES_compressed_paletted_texture", + "GL_OES_read_format", + "GL_OES_query_matrix", + "GL_EXT_depth_bounds_test", + "GL_EXT_texture_mirror_clamp", + "GL_EXT_blend_equation_separate", + "GL_MESA_pack_invert", + "GL_MESA_ycbcr_texture", + "GL_EXT_pixel_buffer_object", + "GL_NV_fragment_program_option", + "GL_NV_fragment_program2", + "GL_NV_vertex_program2_option", + "GL_NV_vertex_program3", + "GLX_SGIX_hyperpipe", + "GLX_MESA_agp_offset", + "GL_EXT_texture_compression_dxt1", + "GL_EXT_framebuffer_object", + "GL_GREMEDY_string_marker", +}; + +Map OutputOrder; + + +//----------------------------------------------------------------------------- + +String trim(const char* str) +{ + String ts = ""; + if (str) { + int s = 0, e = strLength(str); + for (; s != e; s++) { + C8 c = str[s]; + if (c != ' ' && c != '\t' && c != '\n') + break; + } + while (s != e) { + C8 c = str[--e]; + if (c != ' ' && c != '\t' && c != '\n') + break; + } + if (s != e) + ts.insert(0,str + s,e-s+1); + } + return ts; +} + + +//----------------------------------------------------------------------------- + +struct Group +{ + String name; + String link; + Array defines; + Array functions; + Array types; + int order; +}; +typedef Array GroupList; + +static inline int weight(const String& str) +{ + if (str.find("GL_VERSION") != String::NPos) + return 0; + + String prefix = str.substr(0,3); + if (prefix == "GL_") + return 1; + if (prefix == "GLU") + return 2; + if (prefix == "GLX") + return 3; + if (prefix == "WGL") + return 4; + return 5; +} + +bool operator<(const Group& a,const Group& b) +{ + int wa = weight(a.name); + int wb = weight(b.name); + return (wa == wb)? a.name < b.name: wa < wb; + // return a.order < b.order; +} + +bool loadFile(Group& group,String name) +{ + FILE* file = fopen(name.c_str(),"r"); + if (!file) { + Print("Could not open file " + name); + return false; + } + + char buf[512]; + group.name = trim(fgets(buf,sizeof(buf),file)); + + Map::Iterator entry = OutputOrder.find(group.name); + if (entry == OutputOrder.end()) { + Print ("[" + group.name + "]"); + } + group.order = (entry != OutputOrder.end())? entry->value: 2000; + + while (!feof(file)) { + String str = trim(fgets(buf,sizeof(buf),file)); + if (!str.length()) + continue; + + if (str.find("http:") != String::NPos) { + group.link = trim(str); + continue; + } + if (str.find("typedef") != String::NPos) { + group.types.pushBack(str); + continue; + } + if (str.find("DECLARE") != String::NPos) { + group.types.pushBack(str); + continue; + } + if (str.find("(") != String::NPos) { + group.functions.pushBack(str); + continue; + } + group.defines.pushBack(str); + } + return true; +} + +void loadDir(GroupList& groups,String name,const char* filter) +{ + DIR *dir = opendir(name); + if (!dir) { + Print("Could not open file " + name); + return; + } + + struct dirent *fEntry; + while ((fEntry = readdir(dir)) != 0) { + if (fEntry->d_name[0] == '.') + continue; + String file = name + "/" + String(fEntry->d_name); + if (filter[0] != '*' && file.find(filter) == String::NPos) + continue; + //Print("Loading " + file); + groups.pushBack(Group()); + if (!loadFile(groups.last(),file)) + groups.popBack(); + } + + quickSort(groups.begin(),groups.end()); +} + + +//----------------------------------------------------------------------------- + +void write(FILE* file,String line) +{ + fwrite(line,1,line.length(),file); +} + +void outputHeader(GroupList& groups, String name) +{ + FILE* file = fopen(name.c_str(),"w"); + if (!file) + Print("Could not open file " + name); + + // Output all the group name together at the top + for (GroupList::Iterator grp = groups.begin(); grp != groups.end(); grp++) + write(file,"#define " + grp->name + "\n"); + +#if 0 // Types now with the group + // Output all the types for all the extensions + for (GroupList::Iterator grp = groups.begin(); grp != groups.end(); grp++) + for (Array::Iterator itr = grp->types.begin(); + itr != grp->types.end(); itr++) + write(file,*itr + ";\n"); +#endif + + // Output the defines for each group + for (GroupList::Iterator grp = groups.begin(); grp != groups.end(); grp++) { + if (!grp->name) + continue; + write(file,"\n#ifdef " + grp->name + "\n"); + for (Array::Iterator itr = grp->types.begin(); + itr != grp->types.end(); itr++) + write(file,*itr + ";\n"); + for (Array::Iterator itr = grp->defines.begin(); + itr != grp->defines.end(); itr++) { + write(file,"#define " + *itr + "\n"); + } + for (Array::Iterator itr = grp->functions.begin(); + itr != grp->functions.end(); itr++) { + String& str = *itr; + + // Parse function "return name (args)". Start at the back because + // args is enclosed in (), the name has no spaces, and the return type + // can be several tokens, such as "void *" or "const char *" + int b = str.length(); + int a = b - 1; + while (str[a] != '(') + a--; + while (str[--a] == ' ') + ; + b = a; + while (str[a] != ' ') + a--; + String name = str.substr(a+1,b - a); + + // + write(file,"#define "+name+" XGL_FUNCPTR("+name+")\n"); + } + write(file,"#endif\n"); + } +} + +void outputFunctions(GroupList& groups, String name) +{ + FILE* file = fopen(name.c_str(),"w"); + if (!file) + Print("Could not open file " + name); + + // Output the functions for each group + for (GroupList::Iterator grp = groups.begin(); grp != groups.end(); grp++) { + if (!grp->name) + continue; + if (grp->name == "GL_ARB_imaging") + // Imaging is include as part of 1.4... + write(file,"\n#if defined(GL_ARB_imaging) && !defined(GL_VERSION_1_4)\n"); + else + write(file,"\n#ifdef " + grp->name + "\n"); + write(file,"GL_GROUP_BEGIN(" + grp->name + ")\n"); + for (Array::Iterator itr = grp->functions.begin(); + itr != grp->functions.end(); itr++) { + String& str = *itr; + + // Parse function "return name (args)". Start at the back because + // args is enclosed in (), the name has no spaces, and the return type + // can be several tokens, such as "void *" or "const char *" + int b = str.length(); + int a = b - 1; + while (str[a] != '(') + a--; + String args = str.substr(a,b - a); + + while (str[--a] == ' ') + ; + b = a; + while (str[a] != ' ') + a--; + String name = str.substr(a+1,b - a); + + while (str[a] == ' ') + a--; + String rtype = str.substr(0,a+1); + // + write(file,"GL_FUNCTION("+name+","+rtype+","+args+")\n"); + } + write(file,"GL_GROUP_END()\n#endif\n"); + } +} + + +//----------------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + Kernel::installDefaultPrint(); + + // Build a name -> order map for faster lookups. + for (int i = 0; i < sizeof(OrderTable) / sizeof(const char*); i++) + OutputOrder.insert(String(OrderTable[i]),i+1); + + GroupList extensions; + extensions.reserve(800); + loadDir(extensions,"core","VERSION"); + loadDir(extensions,"extensions","*"); + outputHeader(extensions,"glext.h"); + outputFunctions(extensions,"glfnext.h"); + + return 0; +} + diff --git a/gfx/gl/ggl/ggl.cpp b/gfx/gl/ggl/ggl.cpp new file mode 100644 index 0000000..9d9ca93 --- /dev/null +++ b/gfx/gl/ggl/ggl.cpp @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" + +#include "platform/platformDlibrary.h" +#include "console/console.h" + +namespace GL +{ + +//----------------------------------------------------------------------------- +// Current active extensions +struct GLExtensionPtrs; +GLExtensionPtrs* _GGLptr; + +struct GLExtensionFlags; +GLExtensionFlags* _GGLflag; + + +//----------------------------------------------------------------------------- + +static inline int versionCombine( int major, int minor ) +{ + return major * 100 + minor; +} + +bool bindFunction( DLibrary *dll, void *&fnAddress, const char *name ) +{ + fnAddress = dll->bind( name ); + + if (!fnAddress) + Con::warnf( "GLExtensions: DLL bind failed for %s", name ); + + return fnAddress != 0; +} + +bool hasExtension( const char *name, const char *extensions ) +{ + // Extensions are compared against the extension strings + if (extensions && *extensions) { + const char* ptr = dStrstr(extensions,name); + if (ptr) { + char end = ptr[dStrlen(name)]; + if (end == ' ' || end == 0) + return true; + } + } + return false; +} + +bool hasVersion( const char *name, const char* prefix, int major, int minor ) +{ + // Extension group names are compared against the version number. The prefix + // string should be "GL_VERSION" or "GLX_VERSION".. group names are + // "GL_VERSION_1_1", "GL_VERSION_1_2", "GLX_VERSION_1_2", etc. + while (*name && *prefix && *name == *prefix) + name++, prefix++; + if (*name == '_' && *prefix == '\0') { + int maj = dAtoi(++name); + while (dIsdigit(*name)) + *name++; + int min = dAtoi(++name); + return versionCombine(maj,min) <= versionCombine(major,minor); + } + return false; +} + +} // Namespace + diff --git a/gfx/gl/ggl/ggl.h b/gfx/gl/ggl/ggl.h new file mode 100644 index 0000000..36026f9 --- /dev/null +++ b/gfx/gl/ggl/ggl.h @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef GL_GGL_H +#define GL_GGL_H + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +//----------------------------------------------------------------------------- +// Configuration file which defines which parts of GL to include + +#ifndef DOXYGEN +#include "gfx/gl/ggl/gglConfig.h" + + +//----------------------------------------------------------------------------- + +#if defined(__CYGWIN__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32) + #define XGL_DLL __stdcall +#else + #define XGL_DLL +#endif + + +//----------------------------------------------------------------------------- +// Include core (OpenGL 1.1) definitions + +#include "gfx/gl/ggl/generated/glc.h" +#include "gfx/gl/ggl/generated/gle.h" + + +//----------------------------------------------------------------------------- +// All core functionality is implemented as function pointers. + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) extern type (XGL_DLL *name) args; +#define GL_GROUP_END() +#include "gfx/gl/ggl/generated/glcfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + +/// OpenGL interface. +namespace GL +{ + +//----------------------------------------------------------------------------- +// Extensions use indirection in order to support multiple contexts + +struct GLExtensionPtrs { + bool bound; + + // Include all OpenGL extensions for all platform + #define GL_GROUP_BEGIN(name) + #define GL_FUNCTION(name, type, args) type (XGL_DLL *_##name) args; + #define GL_GROUP_END() + #include "gfx/gl/ggl/generated/glefn.h" + #undef GL_GROUP_BEGIN + #undef GL_FUNCTION + #undef GL_GROUP_END + + GLExtensionPtrs() { bound = false; } +}; + +struct GLExtensionFlags { + bool bound; + + // Define extension "has" variables + #define GL_GROUP_BEGIN(name) bool has_##name; + #define GL_FUNCTION(name, type, args) + #define GL_GROUP_END() + #include "gfx/gl/ggl/generated/glefn.h" + #undef GL_GROUP_BEGIN + #undef GL_FUNCTION + #undef GL_GROUP_END + + GLExtensionFlags() { bound = false; } +}; + +// Extension loading has been reimplemented on each platform, and each platform +// has a different version of GLExtensionPtrs and GLExtensionFlags. When binding +// extensions for that platform, you MUST use these pointers. From there the XGL_FUNCPTR +// define will handle converting a gl*EXT call into the proper member function call. +extern GLExtensionPtrs* _GGLptr; +#define XGL_FUNCPTR(name) (GL::_GGLptr->_##name) +#endif // Doxygen + +extern GLExtensionFlags* _GGLflag; +#define gglHasExtension(name) (GL::_GGLflag->has_##name) + + + +} // Namespace + +#endif + diff --git a/gfx/gl/ggl/gglConfig.h b/gfx/gl/ggl/gglConfig.h new file mode 100644 index 0000000..bf79030 --- /dev/null +++ b/gfx/gl/ggl/gglConfig.h @@ -0,0 +1,362 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +// OpenGL 1.0 core definitions +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Undefine extensions not required + +#define GL_VERSION_1_2 +#define GL_VERSION_1_3 +#define GL_VERSION_1_4 +#define GL_VERSION_1_5 +#define GL_VERSION_2_0 + +#define GL_3DFX_multisample +#define GL_3DFX_tbuffer +#define GL_3DFX_texture_compression_FXT1 + +#define GL_APPLE_client_storage +#define GL_APPLE_element_array +#define GL_APPLE_fence +#define GL_APPLE_specular_vector +#define GL_APPLE_transform_hint +#define GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_range +#define GL_APPLE_ycbcr_422 + +#define GL_ARB_depth_texture +#define GL_ARB_fragment_program +#define GL_ARB_fragment_shader +#define GL_ARB_imaging +#define GL_ARB_matrix_palette +#define GL_ARB_multisample +#define GL_ARB_multitexture +#define GL_ARB_occlusion_query +#define GL_ARB_pixel_buffer_object +#define GL_ARB_point_parameters +#define GL_ARB_point_sprite +#define GL_ARB_shader_objects +#define GL_ARB_shading_language_100 +#define GL_ARB_shadow +#define GL_ARB_shadow_ambient +#define GL_ARB_texture_border_clamp +#define GL_ARB_texture_compression +#define GL_ARB_texture_cube_map +#define GL_ARB_texture_env_add +#define GL_ARB_texture_env_combine +#define GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_dot3 +#define GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_rectangle +#define GL_ARB_transpose_matrix +#define GL_ARB_vertex_blend +#define GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_program +#define GL_ARB_vertex_shader +#define GL_ARB_window_pos + +#define GL_ATIX_point_sprites +#define GL_ATIX_texture_env_combine3 +#define GL_ATIX_texture_env_route +#define GL_ATIX_vertex_shader_output_point_size + +#define GL_ATI_draw_buffers +#define GL_ATI_element_array +#define GL_ATI_envmap_bumpmap +#define GL_ATI_fragment_shader +#define GL_ATI_map_object_buffer +#define GL_ATI_pn_triangles +#define GL_ATI_separate_stencil +#define GL_ATI_text_fragment_shader +#define GL_ATI_texture_env_combine3 +#define GL_ATI_texture_float +#define GL_ATI_texture_mirror_once +#define GL_ATI_vertex_array_object +#define GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_streams + +#define GL_EXT_422_pixels +#define GL_EXT_Cg_shader +#define GL_EXT_abgr +#define GL_EXT_bgra +#define GL_EXT_blend_color +#define GL_EXT_blend_equation_separate +#define GL_EXT_blend_func_separate +#define GL_EXT_blend_logic_op +#define GL_EXT_blend_minmax +#define GL_EXT_blend_subtract +#define GL_EXT_clip_volume_hint +#define GL_EXT_cmyka +#define GL_EXT_color_subtable +#define GL_EXT_compiled_vertex_array +#define GL_EXT_convolution +#define GL_EXT_coordinate_frame +#define GL_EXT_copy_texture +#define GL_EXT_cull_vertex +#define GL_EXT_depth_bounds_test +#define GL_EXT_draw_range_elements +#define GL_EXT_fog_coord +#define GL_EXT_fragment_lighting +#define GL_EXT_framebuffer_blit +#define GL_EXT_framebuffer_object +#define GL_EXT_histogram +#define GL_EXT_index_array_formats +#define GL_EXT_index_func +#define GL_EXT_index_material +#define GL_EXT_index_texture +#define GL_EXT_light_texture +#define GL_EXT_misc_attribute +#define GL_EXT_multi_draw_arrays +#define GL_EXT_multisample +#define GL_EXT_packed_pixels +#define GL_EXT_paletted_texture +#define GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_transform +#define GL_EXT_pixel_transform_color_table +#define GL_EXT_point_parameters +#define GL_EXT_polygon_offset +#define GL_EXT_rescale_normal +#define GL_EXT_scene_marker +#define GL_EXT_secondary_color +#define GL_EXT_separate_specular_color +#define GL_EXT_shadow_funcs +#define GL_EXT_shared_texture_palette +#define GL_EXT_stencil_two_side +#define GL_EXT_stencil_wrap +#define GL_EXT_subtexture +#define GL_EXT_texture +#define GL_EXT_texture3D +#define GL_EXT_texture_compression_dxt1 +#define GL_EXT_texture_compression_s3tc +#define GL_EXT_texture_cube_map +#define GL_EXT_texture_edge_clamp +#define GL_EXT_texture_env +#define GL_EXT_texture_env_add +#define GL_EXT_texture_env_combine +#define GL_EXT_texture_env_dot3 +#define GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_lod_bias +#define GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_object +#define GL_EXT_texture_perturb_normal +#define GL_EXT_texture_rectangle +#define GL_EXT_vertex_array +#define GL_EXT_vertex_shader +#define GL_EXT_vertex_weighting +#define GL_GREMEDY_string_marker + +#define GL_HP_convolution_border_modes +#define GL_HP_image_transform +#define GL_HP_occlusion_test +#define GL_HP_texture_lighting + +#define GL_IBM_cull_vertex +#define GL_IBM_multimode_draw_arrays +#define GL_IBM_rasterpos_clip +#define GL_IBM_static_data +#define GL_IBM_texture_mirrored_repeat +#define GL_IBM_vertex_array_lists + +#define GL_INGR_color_clamp +#define GL_INGR_interlace_read + +#define GL_INTEL_parallel_arrays +#define GL_INTEL_texture_scissor + +#define GL_KTX_buffer_region + +#define GL_MESA_pack_invert +#define GL_MESA_resize_buffers +#define GL_MESA_window_pos +#define GL_MESA_ycbcr_texture + +#define GL_NV_blend_square +#define GL_NV_copy_depth_to_color +#define GL_NV_depth_clamp +#define GL_NV_evaluators +#define GL_NV_fence +#define GL_NV_float_buffer +#define GL_NV_fog_distance +#define GL_NV_fragment_program +#define GL_NV_fragment_program2 +#define GL_NV_fragment_program_option +#define GL_NV_half_float +#define GL_NV_light_max_exponent +#define GL_NV_multisample_filter_hint +#define GL_NV_occlusion_query +#define GL_NV_packed_depth_stencil +#define GL_NV_pixel_data_range +#define GL_NV_point_sprite +#define GL_NV_primitive_restart +#define GL_NV_register_combiners +#define GL_NV_register_combiners2 +#define GL_NV_texgen_emboss +#define GL_NV_texgen_reflection +#define GL_NV_texture_compression_vtc +#define GL_NV_texture_env_combine4 +#define GL_NV_texture_expand_normal +#define GL_NV_texture_rectangle +#define GL_NV_texture_shader +#define GL_NV_texture_shader2 +#define GL_NV_texture_shader3 +#define GL_NV_vertex_array_range +#define GL_NV_vertex_array_range2 +#define GL_NV_vertex_program +#define GL_NV_vertex_program1_1 +#define GL_NV_vertex_program2 +#define GL_NV_vertex_program2_option +#define GL_NV_vertex_program3 + +#define GL_OML_interlace +#define GL_OML_resample +#define GL_OML_subsample + +#define GL_PGI_misc_hints +#define GL_PGI_vertex_hints + +#define GL_REND_screen_coordinates + +#define GL_S3_s3tc + +#define GL_SGIS_color_range +#define GL_SGIS_detail_texture +#define GL_SGIS_fog_function +#define GL_SGIS_generate_mipmap +#define GL_SGIS_multisample +#define GL_SGIS_pixel_texture +#define GL_SGIS_sharpen_texture +#define GL_SGIS_texture4D +#define GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_filter4 +#define GL_SGIS_texture_lod +#define GL_SGIS_texture_select + +#define GL_SGIX_async +#define GL_SGIX_async_histogram +#define GL_SGIX_async_pixel +#define GL_SGIX_blend_alpha_minmax +#define GL_SGIX_clipmap +#define GL_SGIX_depth_texture +#define GL_SGIX_flush_raster +#define GL_SGIX_fog_offset +#define GL_SGIX_fog_texture +#define GL_SGIX_fragment_specular_lighting +#define GL_SGIX_framezoom +#define GL_SGIX_interlace +#define GL_SGIX_ir_instrument1 +#define GL_SGIX_list_priority +#define GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture_bits +#define GL_SGIX_reference_plane +#define GL_SGIX_resample +#define GL_SGIX_shadow +#define GL_SGIX_shadow_ambient +#define GL_SGIX_sprite +#define GL_SGIX_tag_sample_buffer +#define GL_SGIX_texture_add_env +#define GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_range +#define GL_SGIX_texture_scale_bias +#define GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip_hint +#define GL_SGIX_ycrcb + +#define GL_SGI_color_matrix +#define GL_SGI_color_table +#define GL_SGI_texture_color_table + +#define GL_SUNX_constant_data + +#define GL_SUN_convolution_border_modes +#define GL_SUN_global_alpha +#define GL_SUN_mesh_array +#define GL_SUN_slice_accum +#define GL_SUN_triangle_list +#define GL_SUN_vertex + +#define GL_WIN_phong_shading +#define GL_WIN_specular_fog +#define GL_WIN_swap_hint + +#define GLU_EXT_nurbs_tessellator +#define GLU_EXT_object_space_tess +#define GLU_SGI_filter4_parameters + +#define GLX_VERSION_1_1 +#define GLX_VERSION_1_2 +#define GLX_VERSION_1_3 +#define GLX_VERSION_1_4 +#define GLX_3DFX_multisample +#define GLX_ARB_fbconfig_float +#define GLX_ARB_get_proc_address +#define GLX_ARB_multisample +#define GLX_ATI_pixel_format_float +#define GLX_ATI_render_texture +#define GLX_EXT_import_context +#define GLX_EXT_scene_marker +#define GLX_EXT_visual_info +#define GLX_EXT_visual_rating +#define GLX_MESA_agp_offset +#define GLX_MESA_copy_sub_buffer +#define GLX_MESA_pixmap_colormap +#define GLX_MESA_release_buffers +#define GLX_MESA_set_3dfx_mode +#define GLX_NV_float_buffer +#define GLX_NV_vertex_array_range +#define GLX_OML_swap_method +#define GLX_OML_sync_control +#define GLX_SGIS_blended_overlay +#define GLX_SGIS_color_range +#define GLX_SGIS_multisample +#define GLX_SGIS_shared_multisample +#define GLX_SGIX_fbconfig +#define GLX_SGIX_pbuffer +#define GLX_SGIX_swap_barrier +#define GLX_SGIX_swap_group +#define GLX_SGIX_video_resize +#define GLX_SGIX_visual_select_group +#define GLX_SGI_cushion +#define GLX_SGI_make_current_read +#define GLX_SGI_swap_control +#define GLX_SGI_video_sync +#define GLX_SUN_get_transparent_index +#define GLX_SUN_video_resize + +#define WGL_3DFX_multisample +#define WGL_ARB_buffer_region +#define WGL_ARB_extensions_string +#define WGL_ARB_make_current_read +#define WGL_ARB_multisample +#define WGL_ARB_pbuffer +#define WGL_ARB_pixel_format +#define WGL_ARB_pixel_format_float +#define WGL_ARB_render_texture +#define WGL_ATI_pixel_format_float +#define WGL_ATI_render_texture_rectangle +#define WGL_EXT_depth_float +#define WGL_EXT_display_color_table +#define WGL_EXT_extensions_string +#define WGL_EXT_make_current_read +#define WGL_EXT_multisample +#define WGL_EXT_pbuffer +#define WGL_EXT_pixel_format +#define WGL_EXT_swap_control +#define WGL_I3D_digital_video_control +#define WGL_I3D_gamma +#define WGL_I3D_genlock +#define WGL_I3D_image_buffer +#define WGL_I3D_swap_frame_lock +#define WGL_I3D_swap_frame_usage +#define WGL_NV_float_buffer +#define WGL_NV_render_depth_texture +#define WGL_NV_render_texture_rectangle +#define WGL_NV_vertex_array_range +#define WGL_OML_sync_control + + diff --git a/gfx/gl/ggl/mac/agl.h b/gfx/gl/ggl/mac/agl.h new file mode 100644 index 0000000..c909f66 --- /dev/null +++ b/gfx/gl/ggl/mac/agl.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +// OpenGL 1.0 core definitions +//----------------------------------------------------------------------------- +/* +** The contents of this file are subject to the GLX Public License Version 1.0 +** (the "License"). You may not use this file except in compliance with the +** License. You may obtain a copy of the License at Silicon Graphics, Inc., +** attn: Legal Services, 2011 N. Shoreline Blvd., Mountain View, CA 94043 +** or at http://www.sgi.com/software/opensource/glx/license.html. +** +** Software distributed under the License is distributed on an "AS IS" +** basis. ALL WARRANTIES ARE DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY +** IMPLIED WARRANTIES OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR +** PURPOSE OR OF NON- INFRINGEMENT. See the License for the specific +** language governing rights and limitations under the License. +** +** The Original Software is GLX version 1.2 source code, released February, +** 1999. The developer of the Original Software is Silicon Graphics, Inc. +** Those portions of the Subject Software created by Silicon Graphics, Inc. +** are Copyright (c) 1991-9 Silicon Graphics, Inc. All Rights Reserved. +*/ + +#ifndef GFX_GLX_H +#define GFX_GLX_H + +#ifndef GFX_GGL_H + #include "../ggl.h" +#endif + +#include "platform/tmm_off.h" + +// AGL includes the standard gl defs, which we already provide in ggl.h +// Defining __gl_h_ here prevents osx's gl defs from being included. +#define __gl_h_ +#include + +#include "platform/tmm_on.h" + +namespace GL +{ + +//----------------------------------------------------------------------------- +#ifndef DOXYGEN + +#define GLX_VERSION_1_0 1 + +#define GLX_USE_GL 1 +#define GLX_BUFFER_SIZE 2 +#define GLX_LEVEL 3 +#define GLX_RGBA 4 +#define GLX_DOUBLEBUFFER 5 +#define GLX_STEREO 6 +#define GLX_AUX_BUFFERS 7 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_ACCUM_RED_SIZE 14 +#define GLX_ACCUM_GREEN_SIZE 15 +#define GLX_ACCUM_BLUE_SIZE 16 +#define GLX_ACCUM_ALPHA_SIZE 17 +#define GLX_BAD_SCREEN 1 +#define GLX_BAD_ATTRIBUTE 2 +#define GLX_NO_EXTENSION 3 +#define GLX_BAD_VISUAL 4 +#define GLX_BAD_CONTEXT 5 +#define GLX_BAD_VALUE 6 +#define GLX_BAD_ENUM 7 + + +//----------------------------------------------------------------------------- +// Extensions use indirection in order to support multiple contexts + +struct AGLExtensionPtrs: public GLExtensionPtrs { +}; + +struct AGLExtensionFlags: public GLExtensionFlags { +}; + +#endif // Doxygen + +//----------------------------------------------------------------------------- + +#undef XGL_FUNCPTR +#define XGL_FUNCPTR(name) (((AGLExtensionPtrs*)GL::_GGLptr)->_##name) + +} // Namespace +#endif + diff --git a/gfx/gl/ggl/mac/aglBind.cpp b/gfx/gl/ggl/mac/aglBind.cpp new file mode 100644 index 0000000..08a1a90 --- /dev/null +++ b/gfx/gl/ggl/mac/aglBind.cpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include +#include "agl.h" + +#include "platform/platform.h" + +//----------------------------------------------------------------------------- +// Instantiation of function pointers. + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) type (XGL_DLL *name) args; +#define GL_GROUP_END() +#include "../generated/glcfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + + +#define EXECUTE_ONLY_ONCE() \ + static bool _doneOnce = false; \ + if(_doneOnce) return; \ + _doneOnce = true; + +namespace GL +{ + +//----------------------------------------------------------------------------- +// The OpenGL Bundle is shared by all the devices. +static CFBundleRef _openglFrameworkRef; + +// Unlike WGL, AGL does not require a set of function pointers per +// context pixel format. All functions in the DLL are bound in a single +// table and shared by all contexts. +static AGLExtensionPtrs _LibraryFunctions; +static AGLExtensionFlags _ExtensionFlags; + +bool hasExtension(const char *name,const char* extensions); +bool hasVersion(const char *name,const char* prefix,int major,int minor); + + +//----------------------------------------------------------------------------- +void* MacGetProcAddress(CFBundleRef bundle,char* name) +{ + CFStringRef cfName = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringGetSystemEncoding()); + void* ret = CFBundleGetFunctionPointerForName(bundle, cfName); + CFRelease(cfName); + return ret; +} + + +//----------------------------------------------------------------------------- +bool bindFunction(CFBundleRef bundle,void *&fnAddress, const char *name) +{ + CFStringRef cfName = CFStringCreateWithCString(0,name,CFStringGetSystemEncoding()); + fnAddress = CFBundleGetFunctionPointerForName(bundle, cfName); + CFRelease(cfName); + return fnAddress != 0; +} + + +//----------------------------------------------------------------------------- +bool gglBindCoreFunctions(CFBundleRef bundle,AGLExtensionPtrs* glp) +{ + bool bound = true; + + // Bind static functions which are quarenteed to be part of the + // OpenGL library. In this case, OpenGL 1.0 and GLX 1.0 functions + #define GL_GROUP_BEGIN(name) + #define GL_GROUP_END() + #define GL_FUNCTION(fn_name, fn_return, fn_args) \ + bound &= bindFunction(bundle,*(void**)&fn_name, #fn_name); + #include "../generated/glcfn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + // Try and bind all known extension functions. We'll check later to + // see which ones are actually valid for a context. + #define GL_GROUP_BEGIN(name) + #define GL_FUNCTION(fn_name, fn_return, fn_args) \ + bindFunction(bundle,*(void**)&glp->_##fn_name, #fn_name); + #define GL_GROUP_END() + #include "../generated/glefn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + return bound; +} + + +//----------------------------------------------------------------------------- +bool gglBindExtensions(GLExtensionFlags* gl) +{ + dMemset(gl,0,sizeof(GLExtensionFlags)); + + // Get GL version and extensions + const char* glExtensions = (const char*)glGetString(GL_EXTENSIONS); + const char* glVersion = (const char*) glGetString(GL_VERSION); + if (!glExtensions || !glVersion) + return false; + + // Parse the GL version string "major.minor" + const char *itr = glVersion; + int glMajor = atoi(itr); + while (isdigit(*itr)) + *itr++; + int glMinor = atoi(++itr); + + // Check which extensions are available on the active context. + // GL and GLX versions ubove 1.0 are also tested here. + #define GL_GROUP_BEGIN(name) \ + gl->has_##name = hasVersion(#name,"GL_VERSION",glMajor,glMinor) || \ + hasExtension(#name,glExtensions); + #define GL_FUNCTION(fn_name, fn_return, fn_args) + #define GL_GROUP_END() + #include "../generated/glefn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + gl->bound = true; + return true; +} + +#define _hasGLXExtension(display,name) (display->glx.has_##name) + +//----------------------------------------------------------------------------- +void gglPerformBinds() +{ + // Some of the following code is copied from the Apple Opengl Documentation. + + // Load and bind OpenGL bundle functions + if (!_openglFrameworkRef) { + + // Load OpenGL.framework + _openglFrameworkRef = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + if (!_openglFrameworkRef) { + warn("Could not create OpenGL Framework bundle"); + return; + } + if (!CFBundleLoadExecutable(_openglFrameworkRef)) { + warn("Could not load MachO executable"); + return; + } + + // Bind our functions. + if (!gglBindCoreFunctions(_openglFrameworkRef, &_LibraryFunctions)) { + warn("GLDevice: Failed to bind all core functions"); + return; + } + + // Save the pointer to the set of opengl functions + _GGLptr = &_LibraryFunctions; + } +} + +//----------------------------------------------------------------------------- +void gglPerformExtensionBinds(void *context) +{ + // we don't care about the passed context when binding the opengl functions, + // we only care about the current opengl context. + if( !_openglFrameworkRef || !_GGLptr ) + { + gglPerformBinds(); + } + gglBindExtensions( &_ExtensionFlags ); + _GGLflag = &_ExtensionFlags; +} + + + +} // Namespace + diff --git a/gfx/gl/ggl/win32/wgl.h b/gfx/gl/ggl/win32/wgl.h new file mode 100644 index 0000000..72764a8 --- /dev/null +++ b/gfx/gl/ggl/win32/wgl.h @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +// OpenGL 1.0 core definitions +//----------------------------------------------------------------------------- + +#ifndef GFX_WGL_H +#define GFX_WGL_H + +#ifndef GFX_GGL_H + #include "../ggl.h" +#endif +#ifndef _PLATFORMDLIBRARY_H + #include "platform/platformDlibrary.h" +#endif + +#ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0500 +#endif +#include + + +namespace GL +{ + +//----------------------------------------------------------------------------- +// Include WGL 1.0 definitions + +#ifndef DOXYGEN +#include "../generated/wgle.h" + + +//----------------------------------------------------------------------------- +// All core functionality is implemented as function pointers. + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) extern type (XGL_DLL *name) args; +#define GL_GROUP_END() +#include "../generated/wglfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + + +//----------------------------------------------------------------------------- +// Extensions use indirection in order to support multiple contexts + +struct WGLExtensionPtrs: public GLExtensionPtrs { + // Include all OpenGL extensions for all platform + #define GL_GROUP_BEGIN(name) + #define GL_FUNCTION(name, type, args) type (XGL_DLL *_##name) args; + #define GL_GROUP_END() + #include "../generated/wglefn.h" + #undef GL_GROUP_BEGIN + #undef GL_FUNCTION + #undef GL_GROUP_END +}; + +struct WGLExtensionFlags: public GLExtensionFlags { + // Define extension "has" variables + #define GL_GROUP_BEGIN(name) bool has_##name; + #define GL_FUNCTION(name, type, args) + #define GL_GROUP_END() + #include "../generated/wglefn.h" + #undef GL_GROUP_BEGIN + #undef GL_FUNCTION + #undef GL_GROUP_END +}; +#endif // Doyxygen + + +//----------------------------------------------------------------------------- + +bool gglBindExtensions(DLibrary*,WGLExtensionPtrs*,WGLExtensionFlags*,HDC); + +#undef XGL_FUNCPTR +#define XGL_FUNCPTR(name) (((GL::WGLExtensionPtrs*)GL::_GGLptr)->_##name) + +#undef gglHasExtension +#define gglHasExtension(name) (((GL::WGLExtensionFlags*)GL::_GGLflag)->has_##name) + + +} // Namespace + +#endif + diff --git a/gfx/gl/ggl/win32/wglBind.cpp b/gfx/gl/ggl/win32/wglBind.cpp new file mode 100644 index 0000000..f2416c4 --- /dev/null +++ b/gfx/gl/ggl/win32/wglBind.cpp @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "wgl.h" + +#include "core/strings/stringFunctions.h" + +#include "console/console.h" + +//----------------------------------------------------------------------------- +// Instantiation of GL function pointers in global namespace. + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) type (__stdcall *name) args; +#define GL_GROUP_END() +#include "../generated/glcfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + + +namespace GL +{ + +bool bindFunction(DLibrary* dll,void *&fnAddress, const char *name); +bool hasExtension(const char *name,const char* extensions); +bool hasVersion(const char *name,const char* prefix,int major,int minor); + + +//----------------------------------------------------------------------------- +// Instantiation of WGL function pointers. These are in the GL namespace to +// avoid problems with definitions in windows.h + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) type (__stdcall *name) args; +#define GL_GROUP_END() +#include "../generated/wglfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + + +//----------------------------------------------------------------------------- + +static bool bindExtension(void *&fnAddress, const char *name) +{ + fnAddress = (void*)wglGetProcAddress(name); + if (!fnAddress) + Con::printf("GLExtensions: Extension bind failed for %s",name); + + return fnAddress != 0; +} + + +//----------------------------------------------------------------------------- + +bool gglBindCoreFunctions(DLibrary* dll) +{ + bool bound = true; + + // Bind static functions which are quarenteed to be part of the + // OpenGL library. In this case, OpenGL 1.0 and WGL functions + #define GL_GROUP_BEGIN(name) + #define GL_GROUP_END() + #define GL_FUNCTION(fn_name, fn_return, fn_args) \ + bound &= bindFunction(dll, *(void**)&fn_name, #fn_name); + #include "../generated/glcfn.h" + #include "../generated/wglfn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + return bound; +} + +bool gglBindExtensions(DLibrary* dll,WGLExtensionPtrs* glp,WGLExtensionFlags* glf,HDC hDC) +{ + dMemset(glp,0,sizeof(WGLExtensionPtrs)); + dMemset(glf,0,sizeof(WGLExtensionFlags)); + + // Get WGL extensions, there is no version + const char* wgl = 0; + if (bindExtension(*(void**)&glp->_wglGetExtensionsStringARB,"wglGetExtensionsStringARB")) + wgl = glp->_wglGetExtensionsStringARB(hDC); + else + if (bindExtension(*(void**)&glp->_wglGetExtensionsStringEXT,"wglGetExtensionsStringEXT")) + wgl = glp->_wglGetExtensionsStringEXT(); + + // Get OpenGL version and extensions + const char* glExtensions = (const char*)glGetString(GL_EXTENSIONS); + const char* glVersion = (const char*) glGetString(GL_VERSION); + if (!glExtensions || !glVersion) + return false; + + // Parse the version string major.minor + const char *itr = glVersion; + int glMajor = dAtoi(itr); + while (dIsdigit(*itr)) + *itr++; + int glMinor = dAtoi(++itr); + + // Test for, and bind, all known extensions. GL and GLX versions ubove 1.0 + // are also treated as extensions and bound here. + #define GL_GROUP_BEGIN(name) \ + if (hasVersion(#name,"GL_VERSION",glMajor,glMinor) || \ + hasExtension(#name,glExtensions) || \ + hasExtension(#name,wgl)) \ + { \ + glf->has_##name = true; + #define GL_FUNCTION(fn_name, fn_return, fn_args) \ + bindExtension(*(void**)&glp->_##fn_name, #fn_name); + #define GL_GROUP_END() \ + } + #include "../generated/glefn.h" + #include "../generated/wglefn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + glf->bound = glp->bound = true; + return true; +} + +static DLibraryRef _hGL = NULL; + +void gglPerformBinds() +{ + if(!_hGL) + { + _hGL = OsLoadLibrary("opengl32.dll"); + gglBindCoreFunctions(_hGL); + } +} + +void gglPerformExtensionBinds(void *context) +{ + if(!_hGL) + { + _hGL = OsLoadLibrary("opengl32.dll"); + } + if(!_GGLptr) + { + static WGLExtensionPtrs ptrs; + static WGLExtensionFlags flags; + + _GGLptr = &ptrs; + _GGLflag = &flags; + } + + gglBindExtensions(_hGL, (WGLExtensionPtrs*)_GGLptr, (WGLExtensionFlags*)_GGLflag, (HDC)context); +} + +} // Namespace + diff --git a/gfx/gl/ggl/x11/glx.cpp b/gfx/gl/ggl/x11/glx.cpp new file mode 100644 index 0000000..4668776 --- /dev/null +++ b/gfx/gl/ggl/x11/glx.cpp @@ -0,0 +1,396 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "os/osDlibrary.h" +#include "os/osLog.h" +#include "util/utilArray.h" + +#include "glx.h" + +namespace GL +{ +using namespace Torque; +using namespace GL; + +extern bool gglBindCoreFunctions(DLibrary* dll,GLXExtensionPtrs*); +void gglBindGLX(::Display* display,int screen,GLXExtensionFlags* glx); +bool gglBindExtensions(GLExtensionFlags* gl); + +struct GGLContext; + +//----------------------------------------------------------------------------- +// The OpenGL DLL is shared by all the devices. +static DLibraryRef _hGL; + +// Unlike WGL, GLX does not require a set of function pointers per +// context pixel format. All functions in the DLL are bound in a single +// table and shared by all contexts. +static GLXExtensionPtrs _LibraryFunctions; + + +//----------------------------------------------------------------------------- + +struct GGLDisplay: public _GLDisplay +{ + ::Display* display; + int screen; + GLXExtensionFlags glx; // GLX extension flags + Array contextList; +}; + +#define _hasGLXExtension(display,name) (display->glx.has_##name) + +GLDisplay gglCreateDisplay(::Display* display,int screen) +{ + // Load and bind DLL functions + if (!_hGL) { + static LogCategory log("/Gfx/Device/GL"); + _hGL = LoadLibrary("libGL.so"); + if (!_hGL) { + log.print("GLDevice: OpenGL dll failed to load"); + return false; + } + if (!gglBindCoreFunctions(_hGL.ptr(),&_LibraryFunctions)) { + log.print("GLDevice: Failed to bind all core functions"); + return false; + } + _GGLptr = &_LibraryFunctions; + log.print("OpenGL library loaded"); + } + + // + GGLDisplay* dp = new GGLDisplay; + dp->display = display; + dp->screen = screen; + gglBindGLX(display,screen,&dp->glx); + return dp; +} + +void gglDeleteDisplay(GLDisplay dp) +{ + GGLDisplay* display = (GGLDisplay*)dp; + Assert(!display->contextList.size(),"gglDeleteDisplay: Not all context destroyed"); + delete display; +} + + +//----------------------------------------------------------------------------- + +struct GGLFormat: public _GLFormat { + GLFormatInfo mode; + U32 pformat; +}; + +static bool _getFBConfig(GGLDisplay* display,U32 pformat,GLXFBConfig& config) +{ + // Get config info from format ID + int attributes[] = { GLX_FBCONFIG_ID, pformat, None }; + int count = 0; + GLXFBConfig* configList = glXChooseFBConfig(display->display, + display->screen,attributes,&count); + if (!count) + return false; + config = *configList; + XFree(configList); + return true; +} + +GLFormat gglSelectFormat(GLDisplay dp,GLFormatInfo& mode) +{ + GGLDisplay* display = (GGLDisplay*)dp; + U32 pformat; + + if (!_hasGLXExtension(display,GLX_VERSION_1_3)) { + // Find GL compatible X visual using 1.2 interface. + // 1.2 or earlier does not include pbuffer support. + if (mode.target != GLFormatInfo::Window) + return 0; + + int attributes[] = { + GLX_RGBA, + GLX_RED_SIZE, mode.red, + GLX_BLUE_SIZE, mode.blue, + GLX_GREEN_SIZE, mode.green, + GLX_DEPTH_SIZE, mode.z, + GLX_ALPHA_SIZE, mode.alpha, + GLX_STENCIL_SIZE, mode.stencil, + GLX_DOUBLEBUFFER, true, + GLX_DOUBLEBUFFER, + None + }; + XVisualInfo* visual = glXChooseVisual(display->display, display->screen, attributes); + if (!visual) { + static LogCategory log("/Gfx/Device/GL"); + log.print("GGLContext: Could not get an accelerated visual"); + return 0; + } + pformat = visual->visualid; + XFree(visual); + } + else { + // Find GL compatible X visual using 1.3 interface. + int attributes[] = { + GLX_DRAWABLE_TYPE, (mode.target == GLFormatInfo::Buffer)? + GLX_PBUFFER_BIT: GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_RED_SIZE, mode.red, + GLX_BLUE_SIZE, mode.blue, + GLX_GREEN_SIZE, mode.green, + GLX_DEPTH_SIZE, mode.z, + GLX_ALPHA_SIZE, mode.alpha, + GLX_STENCIL_SIZE, mode.stencil, + GLX_DOUBLEBUFFER, true, + None + }; + int count = 0; + GLXFBConfig* configList = glXChooseFBConfig(display->display, + display->screen,attributes,&count); + if (!count) + return 0; + + // Return the config ID + int xid; + glXGetFBConfigAttrib(display->display,configList[0],GLX_FBCONFIG_ID,&xid); + XFree(configList); + pformat = xid; + } + + if (pformat) { + GGLFormat* format = new GGLFormat; + format->pformat = pformat; + format->mode = mode; + return format; + } + return 0; +} + +bool gglFormatInfo(GLDisplay display,GLFormat pformat,GLFormatInfo& mode) +{ + return 0; +} + +bool gglAreCompatible(GLFormat,GLFormat) +{ + return false; +} + +void gglDeleteFormat(GLFormat fp) +{ + GGLFormat* format = (GGLFormat*)fp; + delete format; +} + +XVisualInfo* gglGetFormatVisual(GLDisplay dp,GLFormat fp) +{ + GGLDisplay* display = (GGLDisplay*)dp; + GGLFormat* format = (GGLFormat*)fp; + + if (!_hasGLXExtension(display,GLX_VERSION_1_3)) { + // Format is a visual id + int count; + XVisualInfo match; + match.visualid = format->pformat; + return XGetVisualInfo(display->display,VisualIDMask,&match,&count); + } + else { + // Format is an FBConfig id + GLXFBConfig config; + if (!_getFBConfig(display,format->pformat,config)) + return 0; + return glXGetVisualFromFBConfig(display->display,config); + } +} + + +//----------------------------------------------------------------------------- + +struct GGLSurface: public _GLSurface { + enum Type { + Window, PBuffer, + } type; + GGLDisplay* display; + GLXDrawable drawable; // Used with GLX 1.3 interface + ::Window window; // Used with GLX 1.2 interface +}; + +GLSurface gglCreateSurface(GLDisplay dp,::Window hwin,GLFormat fp) +{ + GGLDisplay* display = (GGLDisplay*)dp; + GGLFormat* format = (GGLFormat*)fp; + GGLSurface* surface = 0; + + if (!_hasGLXExtension(display,GLX_VERSION_1_3)) { + // Return our own GGL surface rep. + surface = new GGLSurface; + surface->type = GGLSurface::Window; + surface->display = display; + surface->drawable = 0; + surface->window = hwin; + } + else { + // Get config info from format ID + GLXFBConfig config; + if (!_getFBConfig(display,format->pformat,config)) + return 0; + + // Create GLX window + GLXWindow window = glXCreateWindow(display->display,config,hwin,0); + if (!window) { + GLenum error = glGetError(); + static LogCategory log("/Gfx/Device/GL"); + log.print(format("Create window error: %d",error)); + return 0; + } + + // Return our own GGL surface rep. + surface = new GGLSurface; + surface->type = GGLSurface::Window; + surface->display = display; + surface->drawable = window; + surface->window = 0; + } + return surface; +} + +bool gglGetSurfaceSize(GLSurface sp,Vector2I& size) +{ + GGLSurface* surface = (GGLSurface*)sp; + if (surface->type != GGLSurface::Window) + return false; + if (!_hasGLXExtension(surface->display,GLX_VERSION_1_3)) { + size.x = 300; size.y = 300; + } + else { + glXQueryDrawable(surface->display->display,surface->drawable, + GLX_WIDTH,(U32*)&size.x); + glXQueryDrawable(surface->display->display,surface->drawable, + GLX_HEIGHT,(U32*)&size.y); + } + return true; +} + +void gglDeleteSurface(GLSurface sp) +{ + GGLSurface* surface = (GGLSurface*)sp; + if (surface->type = GGLSurface::Window) { + glXDestroyWindow(surface->display->display,surface->drawable); + delete surface; + } +} + + +//----------------------------------------------------------------------------- + +struct GGLContext: public _GLContext +{ + GGLDisplay* display; + GLXContext context; + GLExtensionFlags glf; // GL has extension flags +}; + +GLContext gglCreateContext(GLDisplay dp,GLFormat fp) +{ + GGLDisplay* display = (GGLDisplay*)dp; + GGLFormat* format = (GGLFormat*)fp; + GGLContext* context = 0; + + GLXContext ctx,share = display->contextList.size()? + display->contextList[0]->context: 0; + + if (!_hasGLXExtension(display,GLX_VERSION_1_3)) { + // Get visual from format id. + int count; + XVisualInfo match; + match.visualid = format->pformat; + XVisualInfo* visual = XGetVisualInfo(display->display,VisualIDMask,&match,&count); + + ctx = glXCreateContext(display->display,visual,share,true); + XFree(visual); + } + else { + // Get FBConfig from format id + GLXFBConfig config; + if (!_getFBConfig(display,format->pformat,config)) + return 0; + + // Need to share contexts... + ctx = glXCreateNewContext(display->display,config, + GLX_RGBA_TYPE,share,true); + } + + if (ctx) { + context = new GGLContext; + context->context = ctx; + context->display = display; + display->contextList.pushBack(context); + return context; + } + return 0; +} + +void gglDeleteContext(GLContext ctx) +{ + GGLContext* context = (GGLContext*)ctx; + glXDestroyContext(context->display->display,context->context); + erase(context->display->contextList,context); + delete context; +} + +bool gglMakeCurrent(GLSurface sp,GLContext ctx) +{ + GGLSurface* surface = (GGLSurface*)sp; + GGLContext* context = (GGLContext*)ctx; + if (!_hasGLXExtension(surface->display,GLX_VERSION_1_3)) { + if (!glXMakeCurrent(surface->display->display, + surface->window,context->context)) + return false; + } + else + if (!glXMakeContextCurrent(surface->display->display, + surface->drawable,surface->drawable,context->context)) + return false; + + // The first time a context is made current we need to + // check which extensions are valid. + if (!context->glf.bound) + gglBindExtensions(&context->glf); + _GGLflag = &context->glf; + return true; +} + +void gglSwapBuffers(GLSurface sp) +{ + GGLSurface* surface = (GGLSurface*)sp; + if (!_hasGLXExtension(surface->display,GLX_VERSION_1_3)) + glXSwapBuffers(surface->display->display,surface->window); + else + glXSwapBuffers(surface->display->display,surface->drawable); +} + + +//----------------------------------------------------------------------------- + +GLSurface gglCreatePBufferSurface(GLDisplay display,U32 width,U32 height,bool cubeMap,GLFormat pformat) +{ + return 0; +} + +bool gglBindPBufferSurface(GLSurface sp,U32 target) +{ + return 0; +} + +bool gglReleasePBufferSurface(GLSurface sp,U32 target) +{ + return 0; +} + +bool gglSetPbufferTarget(GLSurface sp,U32 mip,U32 face) +{ + return 0; +} + +} // Namespace + diff --git a/gfx/gl/ggl/x11/glx.h b/gfx/gl/ggl/x11/glx.h new file mode 100644 index 0000000..b74aede --- /dev/null +++ b/gfx/gl/ggl/x11/glx.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +// OpenGL 1.0 core definitions +//----------------------------------------------------------------------------- +/* +** The contents of this file are subject to the GLX Public License Version 1.0 +** (the "License"). You may not use this file except in compliance with the +** License. You may obtain a copy of the License at Silicon Graphics, Inc., +** attn: Legal Services, 2011 N. Shoreline Blvd., Mountain View, CA 94043 +** or at http://www.sgi.com/software/opensource/glx/license.html. +** +** Software distributed under the License is distributed on an "AS IS" +** basis. ALL WARRANTIES ARE DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY +** IMPLIED WARRANTIES OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR +** PURPOSE OR OF NON- INFRINGEMENT. See the License for the specific +** language governing rights and limitations under the License. +** +** The Original Software is GLX version 1.2 source code, released February, +** 1999. The developer of the Original Software is Silicon Graphics, Inc. +** Those portions of the Subject Software created by Silicon Graphics, Inc. +** are Copyright (c) 1991-9 Silicon Graphics, Inc. All Rights Reserved. +*/ + +#ifndef GFX_GLX_H +#define GFX_GLX_H + +#ifndef GFX_GGL_H + #include "../ggl.h" +#endif + +#include +#include + +namespace GL +{ + +//----------------------------------------------------------------------------- +#ifndef DOXYGEN + +#define GLX_VERSION_1_0 1 + +#define GLX_USE_GL 1 +#define GLX_BUFFER_SIZE 2 +#define GLX_LEVEL 3 +#define GLX_RGBA 4 +#define GLX_DOUBLEBUFFER 5 +#define GLX_STEREO 6 +#define GLX_AUX_BUFFERS 7 +#define GLX_RED_SIZE 8 +#define GLX_GREEN_SIZE 9 +#define GLX_BLUE_SIZE 10 +#define GLX_ALPHA_SIZE 11 +#define GLX_DEPTH_SIZE 12 +#define GLX_STENCIL_SIZE 13 +#define GLX_ACCUM_RED_SIZE 14 +#define GLX_ACCUM_GREEN_SIZE 15 +#define GLX_ACCUM_BLUE_SIZE 16 +#define GLX_ACCUM_ALPHA_SIZE 17 +#define GLX_BAD_SCREEN 1 +#define GLX_BAD_ATTRIBUTE 2 +#define GLX_NO_EXTENSION 3 +#define GLX_BAD_VISUAL 4 +#define GLX_BAD_CONTEXT 5 +#define GLX_BAD_VALUE 6 +#define GLX_BAD_ENUM 7 + +typedef XID GLXDrawable; +typedef XID GLXPixmap; +typedef struct __GLXcontextRec *GLXContext; + +#include "../generated/glxe.h" + +//----------------------------------------------------------------------------- +// All core functionality is implemented as function pointers. + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) extern type (XGL_DLL *name) args; +#define GL_GROUP_END() +#include "../generated/glxfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + + +//----------------------------------------------------------------------------- +// Extensions use indirection in order to support multiple contexts + +struct GLXExtensionPtrs: public GLExtensionPtrs { + // Include all GLX extentions in global function table + #define GL_GROUP_BEGIN(name) + #define GL_FUNCTION(name, type, args) type (XGL_DLL *_##name) args; + #define GL_GROUP_END() + #include "../generated/glxefn.h" + #undef GL_GROUP_BEGIN + #undef GL_FUNCTION + #undef GL_GROUP_END +}; + +struct GLXExtensionFlags { + // Define extension "has" variables + #define GL_GROUP_BEGIN(name) bool has_##name; + #define GL_FUNCTION(name, type, args) + #define GL_GROUP_END() + #include "../generated/glxefn.h" + #undef GL_GROUP_BEGIN + #undef GL_FUNCTION + #undef GL_GROUP_END +}; + +#endif // Doxygen + +//----------------------------------------------------------------------------- + +GLDisplay gglCreateDisplay(::Display*,int screen); +GLSurface gglCreateSurface(GLDisplay,Window,GLFormat); +XVisualInfo* gglGetFormatVisual(GLDisplay dp,GLFormat format); + +#undef XGL_FUNCPTR +#define XGL_FUNCPTR(name) (((GLXExtensionPtrs*)GL::_GGLptr)->_##name) + +} // Namespace +#endif + diff --git a/gfx/gl/ggl/x11/glxBind.cpp b/gfx/gl/ggl/x11/glxBind.cpp new file mode 100644 index 0000000..d75ae4c --- /dev/null +++ b/gfx/gl/ggl/x11/glxBind.cpp @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "glx.h" + +#include "util/utilAssert.h" +#include "util/utilStr.h" +#include "util/utilString.h" +#include "util/utilMemory.h" +#include "os/osLog.h" +#include "os/osDlibrary.h" + +//----------------------------------------------------------------------------- +// Instantiation of GL function pointers in global namespace. + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) type (XGL_DLL *name) args; +#define GL_GROUP_END() +#include "../generated/glcfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + + +namespace GL +{ +using namespace Torque; + +bool bindFunction(DLibrary* dll,void *&fnAddress, const char *name); +bool hasExtension(const char *name,const char* extensions); +bool hasVersion(const char *name,const char* prefix,int major,int minor); + + +//----------------------------------------------------------------------------- +// Instantiation of GLX function pointers. + +#define GL_GROUP_BEGIN(name) +#define GL_FUNCTION(name, type, args) type (XGL_DLL *name) args; +#define GL_GROUP_END() +#include "../generated/glxfn.h" +#undef GL_GROUP_BEGIN +#undef GL_FUNCTION +#undef GL_GROUP_END + + +//----------------------------------------------------------------------------- + +bool bindFunction(DLibrary* dll,GLFunction (*gproc)(const GLubyte*), + void *&fnAddress, const char *name) +{ + // Use the getProcAddress if we have it. + fnAddress = gproc? (void*)(*gproc)((const GLubyte*)name): dll->bind(name); + return fnAddress != 0; +} + + +//----------------------------------------------------------------------------- + +bool gglBindCoreFunctions(DLibrary* dll,GLXExtensionPtrs* glp) +{ + bool bound = true; + + // Bind static functions which are quarenteed to be part of the + // OpenGL library. In this case, OpenGL 1.0 and GLX 1.0 functions + #define GL_GROUP_BEGIN(name) + #define GL_GROUP_END() + #define GL_FUNCTION(fn_name, fn_return, fn_args) \ + bound &= bindFunction(dll,*(void**)&fn_name, #fn_name); + #include "../generated/glcfn.h" + #include "../generated/glxfn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + // Check for the getProcAddress first, otherwise we'll expect everything + // to be in the DLL. + memset(glp,0,sizeof(GLXExtensionPtrs)); + glp->_glXGetProcAddressARB = (GLFunction (*)(const GLubyte*))dll->bind("glXGetProcAddressARB"); + + // Try and bind all known extension functions. We'll check later to + // see which ones are actually valid for a context. + #define GL_GROUP_BEGIN(name) + #define GL_FUNCTION(fn_name, fn_return, fn_args) \ + bindFunction(dll,glp->_glXGetProcAddressARB,*(void**)&glp->_##fn_name, #fn_name); + #define GL_GROUP_END() + #include "../generated/glefn.h" + #include "../generated/glxefn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + return bound; +} + + +//----------------------------------------------------------------------------- + +void gglBindGLX(::Display* display,int screen,GLXExtensionFlags* glx) +{ + // Check GLX version and glx extensions + int glxMajor,glxMinor; + glXQueryVersion(display,&glxMajor,&glxMinor); + const char* glxExtensions = glXQueryExtensionsString(display,screen); + + static LogCategory log("/Gfx/Device/GL"); + log.print(format("GLX Version: %d.%d",glxMajor,glxMinor)); + Assert(glxMajor == 1 && glxMinor >= 1,"GLXBind: Need GLX version 1.1 or greater"); + + #define GL_GROUP_BEGIN(name) \ + glx->has_##name = hasVersion(#name,"GLX_VERSION",glxMajor,glxMinor) || \ + hasExtension(#name,glxExtensions); + #define GL_FUNCTION(fn_name, fn_return, fn_args) + #define GL_GROUP_END() + #include "../generated/glxefn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END +} + + +//----------------------------------------------------------------------------- + +bool gglBindExtensions(GLExtensionFlags* gl) +{ + memset(gl,0,sizeof(GLExtensionFlags)); + + // Get GL version and extensions + const char* glExtensions = (const char*)glGetString(GL_EXTENSIONS); + const char* glVersion = (const char*) glGetString(GL_VERSION); + if (!glExtensions || !glVersion) + return false; + + // Parse the GL version string "major.minor" + const char *itr = glVersion; + int glMajor = atoi(itr); + while (isDigit(*itr)) + *itr++; + int glMinor = atoi(++itr); + + // Check which extensions are available on the active context. + // GL and GLX versions ubove 1.0 are also tested here. + #define GL_GROUP_BEGIN(name) \ + gl->has_##name = hasVersion(#name,"GL_VERSION",glMajor,glMinor) || \ + hasExtension(#name,glExtensions); + #define GL_FUNCTION(fn_name, fn_return, fn_args) + #define GL_GROUP_END() + #include "../generated/glefn.h" + #undef GL_FUNCTION + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + gl->bound = true; + return true; +} + +} // Namespace + diff --git a/gfx/primBuilder.cpp b/gfx/primBuilder.cpp new file mode 100644 index 0000000..0939d5d --- /dev/null +++ b/gfx/primBuilder.cpp @@ -0,0 +1,317 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "primBuilder.h" +#include "gfxDevice.h" +#include "console/console.h" + + +//***************************************************************************** +// Primitive Builder +//***************************************************************************** +namespace PrimBuild +{ +Vector mTempVertBuff; +GFXVertexBufferHandle mVertBuff; +GFXPrimitiveType mType; +U32 mCurVertIndex; +ColorI mCurColor( 255, 255, 255 ); +Point2F mCurTexCoord; +const ColorI _colWhite( 255, 255, 255, 255 ); + +#ifdef TORQUE_DEBUG +U32 mMaxVerts; + +#define INIT_VERTEX_SIZE(x) mMaxVerts = x; +#define VERTEX_BOUNDS_CHECK() AssertFatal( mCurVertIndex < mMaxVerts, "PrimBuilder encountered an out of bounds vertex! Break and debug!" ); + +// This next check shouldn't really be used a lot unless you are tracking down +// a specific bug. -pw +#define VERTEX_SIZE_CHECK() AssertFatal( mCurVertIndex <= mMaxVerts, "PrimBuilder allocated more verts than you used! Break and debug or rendering artifacts could occur." ); + +#else + +#define INIT_VERTEX_SIZE(x) +#define VERTEX_BOUNDS_CHECK() +#define VERTEX_SIZE_CHECK() + +#endif + +//----------------------------------------------------------------------------- +// begin +//----------------------------------------------------------------------------- +void begin( GFXPrimitiveType type, U32 maxVerts ) +{ + AssertFatal( type >= GFXPT_FIRST && type < GFXPT_COUNT, "PrimBuilder::end() - Bad primitive type!" ); + + mType = type; + mCurVertIndex = 0; + INIT_VERTEX_SIZE( maxVerts ); + mTempVertBuff.setSize( maxVerts ); +} + +void beginToBuffer( GFXPrimitiveType type, U32 maxVerts ) +{ + AssertFatal( type >= GFXPT_FIRST && type < GFXPT_COUNT, "PrimBuilder::end() - Bad primitive type!" ); + + mType = type; + mCurVertIndex = 0; + INIT_VERTEX_SIZE( maxVerts ); + mTempVertBuff.setSize( maxVerts ); +} + +//----------------------------------------------------------------------------- +// end +//----------------------------------------------------------------------------- +GFXVertexBuffer * endToBuffer( U32 &numPrims ) +{ + mVertBuff.set(GFX, mTempVertBuff.size(), GFXBufferTypeVolatile); + GFXVertexPCT *verts = mVertBuff.lock(); + dMemcpy( verts, mTempVertBuff.address(), mTempVertBuff.size() * sizeof(GFXVertexPCT) ); + mVertBuff.unlock(); + + VERTEX_SIZE_CHECK(); + + switch( mType ) + { + case GFXPointList: + { + numPrims = mCurVertIndex; + break; + } + + case GFXLineList: + { + numPrims = mCurVertIndex / 2; + break; + } + + case GFXLineStrip: + { + numPrims = mCurVertIndex - 1; + break; + } + + case GFXTriangleList: + { + numPrims = mCurVertIndex / 3; + break; + } + + case GFXTriangleStrip: + case GFXTriangleFan: + { + numPrims = mCurVertIndex - 2; + break; + } + case GFXPT_COUNT: + // handle warning + break; + } + + return mVertBuff; +} + +void end( bool useGenericShaders ) +{ + if ( mCurVertIndex == 0 ) + return; + + VERTEX_SIZE_CHECK(); + + U32 vertStride = 1; + U32 stripStart = 0; + + AssertFatal( mType >= GFXPT_FIRST && mType < GFXPT_COUNT, "PrimBuilder::end() - Bad primitive type!" ); + + switch( mType ) + { + default: + case GFXPointList: + { + vertStride = 1; + break; + } + + case GFXLineList: + { + vertStride = 2; + break; + } + + case GFXTriangleList: + { + vertStride = 3; + break; + } + + case GFXLineStrip: + { + stripStart = 1; + vertStride = 1; + break; + } + + case GFXTriangleStrip: + case GFXTriangleFan: + { + stripStart = 2; + vertStride = 1; + break; + } + } + + if ( useGenericShaders ) + GFX->setupGenericShaders( GFXDevice::GSModColorTexture ); + + const GFXVertexPCT *srcVerts = mTempVertBuff.address(); + U32 numVerts = mCurVertIndex; + + // Make sure we don't have a dirty prim buffer left. + GFX->setPrimitiveBuffer( NULL ); + + if ( stripStart > 0 ) + { + // TODO: Fix this to allow > MAX_DYNAMIC_VERTS! + + U32 copyVerts = getMin( (U32)MAX_DYNAMIC_VERTS, numVerts ); + mVertBuff.set( GFX, copyVerts, GFXBufferTypeVolatile ); + + GFXVertexPCT *verts = mVertBuff.lock(); + dMemcpy( verts, srcVerts, copyVerts * sizeof( GFXVertexPCT ) ); + mVertBuff.unlock(); + + U32 numPrims = ( copyVerts / vertStride ) - stripStart; + GFX->setVertexBuffer( mVertBuff ); + GFX->drawPrimitive( mType, 0, numPrims ); + } + else + { + while ( numVerts > 0 ) + { + U32 copyVerts = getMin( (U32)MAX_DYNAMIC_VERTS, numVerts ); + copyVerts -= copyVerts % vertStride; + + mVertBuff.set( GFX, copyVerts, GFXBufferTypeVolatile ); + + GFXVertexPCT *verts = mVertBuff.lock(); + dMemcpy( verts, srcVerts, copyVerts * sizeof( GFXVertexPCT ) ); + mVertBuff.unlock(); + + U32 numPrims = copyVerts / vertStride; + GFX->setVertexBuffer( mVertBuff ); + GFX->drawPrimitive( mType, 0, numPrims ); + + srcVerts += copyVerts; + numVerts -= copyVerts; + } + } +} + +//----------------------------------------------------------------------------- +// vertex2f +//----------------------------------------------------------------------------- +void vertex2f( F32 x, F32 y ) +{ + VERTEX_BOUNDS_CHECK(); + GFXVertexPCT *vert = &mTempVertBuff[mCurVertIndex++]; + + vert->point.x = x; + vert->point.y = y; + vert->point.z = 0.0; + vert->color = mCurColor; + vert->texCoord = mCurTexCoord; +} + +//----------------------------------------------------------------------------- +// vertex3f +//----------------------------------------------------------------------------- +void vertex3f( F32 x, F32 y, F32 z ) +{ + VERTEX_BOUNDS_CHECK(); + GFXVertexPCT *vert = &mTempVertBuff[mCurVertIndex++]; + + vert->point.x = x; + vert->point.y = y; + vert->point.z = z; + vert->color = mCurColor; + vert->texCoord = mCurTexCoord; +} + +//----------------------------------------------------------------------------- +// vertex3fv +//----------------------------------------------------------------------------- +void vertex3fv( const F32 *data ) +{ + VERTEX_BOUNDS_CHECK(); + GFXVertexPCT *vert = &mTempVertBuff[mCurVertIndex++]; + + vert->point.set( data[0], data[1], data[2] ); + vert->color = mCurColor; + vert->texCoord = mCurTexCoord; +} + +//----------------------------------------------------------------------------- +// vertex2fv +//----------------------------------------------------------------------------- +void vertex2fv( const F32 *data ) +{ + VERTEX_BOUNDS_CHECK(); + GFXVertexPCT *vert = &mTempVertBuff[mCurVertIndex++]; + + vert->point.set( data[0], data[1], 0.f ); + vert->color = mCurColor; + vert->texCoord = mCurTexCoord; +} + + + +//----------------------------------------------------------------------------- +// color +//----------------------------------------------------------------------------- +void color( const ColorI &inColor ) +{ + mCurColor = inColor; +} + +void color( const ColorF &inColor ) +{ + mCurColor = inColor; +} + +void color3i( U8 red, U8 green, U8 blue ) +{ + mCurColor.set( red, green, blue ); +} + +void color4i( U8 red, U8 green, U8 blue, U8 alpha ) +{ + mCurColor.set( red, green, blue, alpha ); +} + +void color3f( F32 red, F32 green, F32 blue ) +{ + mCurColor.set( U8( red * 255 ), U8( green * 255 ), U8( blue * 255 ) ); +} + +void color4f( F32 red, F32 green, F32 blue, F32 alpha ) +{ + mCurColor.set( U8( red * 255 ), U8( green * 255 ), U8( blue * 255 ), U8( alpha * 255 ) ); +} + + +//----------------------------------------------------------------------------- +// texCoord +//----------------------------------------------------------------------------- +void texCoord2f( F32 x, F32 y ) +{ + mCurTexCoord.set( x, y ); +} + +void shutdown() +{ + mVertBuff = NULL; +} + +} // namespace PrimBuild diff --git a/gfx/primBuilder.h b/gfx/primBuilder.h new file mode 100644 index 0000000..0344dfb --- /dev/null +++ b/gfx/primBuilder.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PRIMBUILDER_H_ +#define _PRIMBUILDER_H_ + +#include "gfx/gfxVertexBuffer.h" + +//************************************************************************** +// +//************************************************************************** + +/// Primitive Builder. +/// +/// A simple interface to put together lines and polygons +/// quickly and easily - OpenGL style. This is basically +/// a convenient way to fill a vertex buffer, then draw it. +/// +/// There are two ways to use it. You can use the begin() +/// and end() calls to have it draw immediately after calling +/// end(). This is the "OpenGL" or "immediate" style of usage. +/// +/// The other way to use this is to use the beginToBuffer() +/// and endToBuffer() calls, which let you store the +/// results of your intermediate calls for later use. +/// This is much more efficient than using the immediate style. +/// +namespace PrimBuild +{ + extern const ColorI _colWhite; + + void beginToBuffer( GFXPrimitiveType type, U32 maxVerts ); + GFXVertexBuffer *endToBuffer( U32 &outNumPrims ); + + void begin( GFXPrimitiveType type, U32 maxVerts ); + void end( bool useGenericShaders = true ); + + void vertex2f( F32 x, F32 y ); + void vertex3f( F32 x, F32 y, F32 z ); + + void vertex2fv( const F32 *data ); + inline void vertex2fv( const Point2F &pnt ) { vertex2fv( (F32 *) &pnt ); }; + inline void vertex2fv( const Point2F *pnt ) { vertex2fv( (F32 *) pnt ); }; + + void vertex3fv( const F32 *data ); + inline void vertex3fv( const Point3F &pnt ) { vertex3fv( (F32 *) &pnt ); }; + inline void vertex3fv( const Point3F *pnt ) { vertex3fv( (F32 *) pnt ); }; + + inline void vertex2i( S32 x, S32 y ) { vertex2f((F32)x, (F32)y); } + inline void vertex3i( S32 x, S32 y, S32 z ) { vertex3f((F32)x, (F32)y, (F32)z); } + + void color( const ColorI & ); + void color( const ColorF & ); + void color3i( U8 red, U8 green, U8 blue ); + void color4i( U8 red, U8 green, U8 blue, U8 alpha ); + void color3f( F32 red, F32 green, F32 blue ); + void color4f( F32 red, F32 green, F32 blue, F32 alpha ); + + inline void colorWhite() { color( _colWhite ); } + + void texCoord2f( F32 x, F32 y ); + + void shutdown(); +} + +#endif \ No newline at end of file diff --git a/gfx/screenshot.cpp b/gfx/screenshot.cpp new file mode 100644 index 0000000..bf398c6 --- /dev/null +++ b/gfx/screenshot.cpp @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/fileStream.h" +#include "core/strings/unicode.h" +#include "console/console.h" +#include "screenshot.h" +#include "core/volume.h" + + +// This must be initialized by the device +ScreenShot * gScreenShot = NULL; + + +//************************************************************************** +// Console function +//************************************************************************** +ConsoleFunction(screenShot, void, 3, 3, "(string file, string format)" + "Take a screenshot.\n\n" + "@param format One of JPEG or PNG.") +{ + if( !gScreenShot ) + { + Con::errorf( "Screenshot module not initialized by device" ); + return; + } + + + Torque::Path ssPath(argv[1]); + Torque::FS::CreatePath(ssPath); + Torque::FS::FileSystemRef fs = Torque::FS::GetFileSystem(ssPath); + Torque::Path newPath = fs->mapTo(ssPath); + + gScreenShot->mPending = true; + gScreenShot->mFilename = newPath.getFullPath(); +} + +//************************************************************************** +// ScreenShot class +//************************************************************************** +ScreenShot::ScreenShot() +{ + mSurfWidth = 0; + mSurfHeight = 0; + mPending = false; +} + + + diff --git a/gfx/screenshot.h b/gfx/screenshot.h new file mode 100644 index 0000000..535532b --- /dev/null +++ b/gfx/screenshot.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SCREENSHOT_H_ +#define _SCREENSHOT_H_ + +#include "gfx/gfxDevice.h" + +//************************************************************************** +/*! + This class will eventually support various capabilities such as panoramics, + high rez captures, and cubemap captures. + + Right now it just captures standard screenshots, but it does support + captures from multisample back buffers, so antialiased captures will work. +*/ +//************************************************************************** + +class ScreenShot +{ + GFXTexHandle mRenderSurface; + U32 mSurfWidth; + U32 mSurfHeight; + + static void texManagerCallback( GFXTexCallbackCode code, void *userData ); + + void setupSurface( U32 width, U32 height ); + +public: + + bool mPending; // necessary to synch capture before backbuffer flips - see GuiCanvas + + String mFilename; + + ScreenShot(); + virtual ~ScreenShot() {} + + /// captures the back buffer + virtual void captureStandard(){} + +}; + +extern ScreenShot *gScreenShot; + +#endif // _SCREENSHOT_H_ diff --git a/gfx/sim/cubemapData.cpp b/gfx/sim/cubemapData.cpp new file mode 100644 index 0000000..22c6be5 --- /dev/null +++ b/gfx/sim/cubemapData.cpp @@ -0,0 +1,249 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/bitStream.h" + +#include "cubemapData.h" +#include "console/consoleTypes.h" +#include "gfx/gfxCubemap.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "sceneGraph/sceneGraph.h" + +IMPLEMENT_CONOBJECT( CubemapData ); + +//**************************************************************************** +// Cubemap Data +//**************************************************************************** + + +//---------------------------------------------------------------------------- +// Constructor +//---------------------------------------------------------------------------- +CubemapData::CubemapData() +{ + mCubemap = NULL; + mDynamic = false; + mDynamicSize = 512; + mDynamicNearDist = 0.1f; + mDynamicFarDist = 100.0f; + mDynamicObjectTypeMask = 0; +#ifdef INIT_HACK + mInit = false; +#endif +} + +CubemapData::~CubemapData() +{ + mCubemap = NULL; +} + +//-------------------------------------------------------------------------- +// Init fields +//-------------------------------------------------------------------------- +void CubemapData::initPersistFields() +{ + addField("cubeFace", TypeStringFilename, Offset(mCubeFaceFile, CubemapData), 6); + addField("dynamic", TypeBool, Offset(mDynamic, CubemapData)); + addField("dynamicSize", TypeS32, Offset(mDynamicSize, CubemapData)); + addField("dynamicNearDist", TypeF32, Offset(mDynamicNearDist, CubemapData)); + addField("dynamicFarDist", TypeF32, Offset(mDynamicFarDist, CubemapData)); + addField("dynamicObjectTypeMask", TypeS32, Offset(mDynamicObjectTypeMask, CubemapData)); + + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +// onAdd +//-------------------------------------------------------------------------- +bool CubemapData::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + // Do NOT call this here as it forces every single cubemap defined to load its images, immediately, without mercy + // createMap(); + + return true; +} + +//-------------------------------------------------------------------------- +// Create map - this is public so cubemaps can be used with materials +// in the show tool +//-------------------------------------------------------------------------- +void CubemapData::createMap() +{ + if( !mCubemap ) + { + if( mDynamic ) + { + mCubemap = GFX->createCubemap(); + mCubemap->initDynamic( mDynamicSize ); + mDepthBuff = GFXTexHandle( mDynamicSize, mDynamicSize, GFXFormatD24S8, + &GFXDefaultZTargetProfile, avar("%s() - mDepthBuff (line %d)", __FUNCTION__, __LINE__)); + mRenderTarget = GFX->allocRenderToTextureTarget(); + } + else + { + bool initSuccess = true; + + for( U32 i=0; i<6; i++ ) + { + if( !mCubeFaceFile[i].isEmpty() ) + { + if(!mCubeFace[i].set(mCubeFaceFile[i], &GFXDefaultStaticDiffuseProfile, avar("%s() - mCubeFace[%d] (line %d)", __FUNCTION__, i, __LINE__) )) + { + Con::errorf("CubemapData::createMap - Failed to load texture '%s'", mCubeFaceFile[i].c_str()); + initSuccess = false; + } + } + } + + if( initSuccess ) + { + mCubemap = GFX->createCubemap(); + mCubemap->initStatic( mCubeFace ); + } + } + } +} + +// Should we just pass the world into here? +void CubemapData::updateDynamic(SceneGraph* sm, const Point3F& pos) +{ + AssertFatal(mDynamic, "This is not a dynamic cubemap!"); + + GFXDEBUGEVENT_SCOPE( CubemapData_updateDynamic, ColorI::WHITE ); + +#ifdef INIT_HACK + if( mInit ) return; + mInit = true; +#endif + + GFX->pushActiveRenderTarget(); + mRenderTarget->attachTexture(GFXTextureTarget::DepthStencil, mDepthBuff ); + + // store current matrices + GFXTransformSaver saver; + F32 oldVisibleDist = sm->getVisibleDistance(); + + F32 l, r, b, t, n, f; + bool ortho; + GFX->getFrustum( &l, &r, &b, &t, &n, &f, &ortho ); + + // set projection to 90 degrees vertical and horizontal + GFX->setFrustum(90.0f, 1.0f, mDynamicNearDist, mDynamicFarDist); + sm->setVisibleDistance(mDynamicFarDist); + + // We don't use a special clipping projection, but still need to initialize + // this for objects like SkyBox which will use it during a reflect pass. + gClientSceneGraph->setNonClipProjection( (MatrixF&) GFX->getProjectionMatrix() ); + + // Loop through the six faces of the cube map. + for(U32 i=0; i<6; i++) + { + // Standard view that will be overridden below. + VectorF vLookatPt(0.0f, 0.0f, 0.0f), vUpVec(0.0f, 0.0f, 0.0f), vRight(0.0f, 0.0f, 0.0f); + + switch( i ) + { + case 0 : // D3DCUBEMAP_FACE_POSITIVE_X: + vLookatPt = VectorF( 1.0f, 0.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + case 1 : // D3DCUBEMAP_FACE_NEGATIVE_X: + vLookatPt = VectorF( -1.0f, 0.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + case 2 : // D3DCUBEMAP_FACE_POSITIVE_Y: + vLookatPt = VectorF( 0.0f, 1.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 0.0f,-1.0f ); + break; + case 3 : // D3DCUBEMAP_FACE_NEGATIVE_Y: + vLookatPt = VectorF( 0.0f, -1.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 0.0f, 1.0f ); + break; + case 4 : // D3DCUBEMAP_FACE_POSITIVE_Z: + vLookatPt = VectorF( 0.0f, 0.0f, 1.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + case 5: // D3DCUBEMAP_FACE_NEGATIVE_Z: + vLookatPt = VectorF( 0.0f, 0.0f, -1.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + } + + // create camera matrix + VectorF cross = mCross( vUpVec, vLookatPt ); + cross.normalizeSafe(); + + MatrixF matView(true); + matView.setColumn( 0, cross ); + matView.setColumn( 1, vLookatPt ); + matView.setColumn( 2, vUpVec ); + matView.setPosition( pos ); + matView.inverse(); + + GFX->pushWorldMatrix(); + GFX->setWorldMatrix(matView); + + mRenderTarget->attachTexture( GFXTextureTarget::Color0, mCubemap, i ); + GFX->setActiveRenderTarget( mRenderTarget ); + GFX->clear( GFXClearStencil | GFXClearTarget | GFXClearZBuffer, ColorI( 64, 64, 64 ), 1.f, 0 ); + + // render scene + sm->renderScene( SPT_Reflect, mDynamicObjectTypeMask ); + + // Resolve render target for each face + mRenderTarget->resolve(); + GFX->popWorldMatrix(); + } + + // restore render surface and depth buffer + GFX->popActiveRenderTarget(); + + mRenderTarget->attachTexture(GFXTextureTarget::Color0, NULL); + sm->setVisibleDistance(oldVisibleDist); + + GFX->setFrustum( l, r, b, t, n, f ); +} + +void CubemapData::updateFaces() +{ + bool initSuccess = true; + + for( U32 i=0; i<6; i++ ) + { + if( !mCubeFaceFile[i].isEmpty() ) + { + if(!mCubeFace[i].set(mCubeFaceFile[i], &GFXDefaultStaticDiffuseProfile, avar("%s() - mCubeFace[%d] (line %d)", __FUNCTION__, i, __LINE__) )) + { + initSuccess = false; + Con::errorf("CubemapData::createMap - Failed to load texture '%s'", mCubeFaceFile[i].c_str()); + } + } + } + + if( initSuccess ) + { + mCubemap = NULL; + mCubemap = GFX->createCubemap(); + + mCubemap->initStatic( mCubeFace ); + } +} + +ConsoleMethod( CubemapData, updateFaces, void, 2, 2, + "updateFaces(): Update the newly assigned cubemaps faces." ) +{ + object->updateFaces(); +} + +ConsoleMethod(CubemapData, getFilename, const char*, 2, 2, "Get filename of CubemapData") +{ + SimObject *cubemap = static_cast(object); + return cubemap->getFilename(); +} \ No newline at end of file diff --git a/gfx/sim/cubemapData.h b/gfx/sim/cubemapData.h new file mode 100644 index 0000000..a5e132f --- /dev/null +++ b/gfx/sim/cubemapData.h @@ -0,0 +1,68 @@ +#ifndef _CUBEMAPDATA_H_ +#define _CUBEMAPDATA_H_ + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + +#ifndef _GFXCUBEMAP_H_ +#include "gfx/gfxCubemap.h" +#endif + +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif + +#ifndef _SCENEGRAPH_H_ +#include "sceneGraph/sceneGraph.h" +#endif + +class SceneGraph; + +//************************************************************************** +// Cubemap data +//************************************************************************** +class CubemapData : public SimObject +{ + typedef SimObject Parent; + +public: + + GFXCubemapHandle mCubemap; + + CubemapData(); + ~CubemapData(); + + bool onAdd(); + static void initPersistFields(); + + DECLARE_CONOBJECT(CubemapData); + + // Force creation of cubemap + void createMap(); + + // Update a dynamic cubemap @ pos + void updateDynamic(SceneGraph* sm, const Point3F& pos); + void updateFaces(); + + // Dynamic cube map support + bool mDynamic; + U32 mDynamicSize; + F32 mDynamicNearDist; + F32 mDynamicFarDist; + U32 mDynamicObjectTypeMask; + +protected: + + FileName mCubeFaceFile[6]; + GFXTexHandle mCubeFace[6]; + + GFXTexHandle mDepthBuff; + GFXTextureTargetRef mRenderTarget; +#ifdef INIT_HACK + bool mInit; +#endif +}; + +#endif // CUBEMAPDATA + diff --git a/gfx/sim/debugDraw.cpp b/gfx/sim/debugDraw.cpp new file mode 100644 index 0000000..777b813 --- /dev/null +++ b/gfx/sim/debugDraw.cpp @@ -0,0 +1,313 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gfx/sim/debugDraw.h" + +#include "gfx/gFont.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "math/mathUtils.h" +#include "math/util/frustum.h" +#include "console/console.h" +#include "sceneGraph/sceneGraph.h" + + +DebugDrawer* DebugDrawer::sgDebugDrawer = NULL; + +IMPLEMENT_CONOBJECT(DebugDrawer); + +DebugDrawer::DebugDrawer() +{ + mHead = NULL; + isFrozen = false; + shouldToggleFreeze = false; + +#ifdef ENABLE_DEBUGDRAW + isDrawing = true; +#else + isDrawing = false; +#endif +} + +DebugDrawer::~DebugDrawer() +{ +} + +DebugDrawer* DebugDrawer::get() +{ + if (sgDebugDrawer) + { + return sgDebugDrawer; + } else { + DebugDrawer::init(); + return sgDebugDrawer; + } +} + +void DebugDrawer::init() +{ +#ifdef ENABLE_DEBUGDRAW + sgDebugDrawer = new DebugDrawer(); + sgDebugDrawer->registerObject("DebugDraw"); + + #ifndef TORQUE_DEBUG + Con::errorf("==============================================================="); + Con::errorf("===== WARNING! DEBUG DRAWER ENABLED! ==="); + Con::errorf("===== Turn me off in final build, thanks. ==="); + Con::errorf("===== I will draw a gross line to get your attention.==="); + Con::errorf("===== -- BJG ==="); + Con::errorf("==============================================================="); + + // You can disable this code if you know what you're doing. Just be sure not to ship with + // DebugDraw enabled! + + // DebugDraw can be used for all sorts of __cheats__ and __bad things__. + sgDebugDrawer->drawLine(Point3F(-10000, -10000, -10000), Point3F(10000, 10000, 10000), ColorF(1, 0, 0)); + sgDebugDrawer->setLastTTL(15 * 60 * 1000); + #else + sgDebugDrawer->drawLine(Point3F(-10000, -10000, -10000), Point3F(10000, 10000, 10000), ColorF(0, 1, 0)); + sgDebugDrawer->setLastTTL(30*1000); + #endif +#endif + +} + +void DebugDrawer::setupStateBlocks() +{ + GFXStateBlockDesc d; + + d.setCullMode(GFXCullNone); + mRenderZOnSB = GFX->createStateBlock(d); + + d.setZReadWrite(false); + mRenderZOffSB = GFX->createStateBlock(d); +} + +void DebugDrawer::render() +{ +#ifdef ENABLE_DEBUGDRAW + if(!isDrawing) + return; + + if (!mRenderZOnSB) + { + setupStateBlocks(); + String fontCacheDir = Con::getVariable("$GUI::fontCacheDirectory"); + mFont = GFont::create("Arial", 12, fontCacheDir); + } + + SimTime curTime = Sim::getCurrentTime(); + + GFX->disableShaders(); + + for(DebugPrim **walk = &mHead; *walk; ) + { + DebugPrim *p = *walk; + + // Set up Z testing... + if(p->useZ) + GFX->setStateBlock(mRenderZOnSB); + else + GFX->setStateBlock(mRenderZOffSB); + + Point3F d; + + switch(p->type) + { + case DebugPrim::Tri: + PrimBuild::begin( GFXLineStrip, 4); + + PrimBuild::color(p->color); + + PrimBuild::vertex3fv(p->a); + PrimBuild::vertex3fv(p->b); + PrimBuild::vertex3fv(p->c); + PrimBuild::vertex3fv(p->a); + + PrimBuild::end(); + break; + case DebugPrim::Box: + d = p->a - p->b; + GFX->getDrawUtil()->drawWireCube(d * 0.5, (p->a + p->b) * 0.5, p->color); + break; + case DebugPrim::Line: + PrimBuild::begin( GFXLineStrip, 2); + + PrimBuild::color(p->color); + + PrimBuild::vertex3fv(p->a); + PrimBuild::vertex3fv(p->b); + + PrimBuild::end(); + break; + case DebugPrim::Text: + { + GFXTransformSaver saver; + Point3F result; + if (MathUtils::mProjectWorldToScreen(p->a, &result, GFX->getViewport(), GFX->getWorldMatrix(), GFX->getProjectionMatrix())) + { + GFX->setClipRect(GFX->getViewport()); + ColorI primColor(p->color.red * 255.0f, p->color.blue * 255.0f, p->color.green * 255.0f, p->color.alpha * 255.0f); + GFX->getDrawUtil()->drawText(mFont, Point2I(result.x, result.y), p->mText, &primColor); + } + } + break; + } + + // Ok, we've got data, now freeze here if needed. + if (shouldToggleFreeze) + { + isFrozen = !isFrozen; + shouldToggleFreeze = false; + } + + if(p->dieTime <= curTime && !isFrozen && p->dieTime != U32_MAX) + { + *walk = p->next; + mPrimChunker.free(p); + } + else + walk = &((*walk)->next); + } +#endif +} + +void DebugDrawer::drawBox(const Point3F &a, const Point3F &b, const ColorF &color) +{ + if(isFrozen || !isDrawing) + return; + + DebugPrim *n = mPrimChunker.alloc(); + + n->useZ = true; + n->dieTime = 0; + n->a = a; + n->b = b; + n->color = color; + n->type = DebugPrim::Box; + + n->next = mHead; + mHead = n; +} + +void DebugDrawer::drawLine(const Point3F &a, const Point3F &b, const ColorF &color) +{ + if(isFrozen || !isDrawing) + return; + + DebugPrim *n = mPrimChunker.alloc(); + + n->useZ = true; + n->dieTime = 0; + n->a = a; + n->b = b; + n->color = color; + n->type = DebugPrim::Line; + + n->next = mHead; + mHead = n; +} + +void DebugDrawer::drawTri(const Point3F &a, const Point3F &b, const Point3F &c, const ColorF &color) +{ + if(isFrozen || !isDrawing) + return; + + DebugPrim *n = mPrimChunker.alloc(); + + n->useZ = true; + n->dieTime = 0; + n->a = a; + n->b = b; + n->c = c; + n->color = color; + n->type = DebugPrim::Tri; + + n->next = mHead; + mHead = n; +} + +void DebugDrawer::drawFrustum(const Frustum& f, const ColorF &color) +{ + // Draw near and far planes. + for (U32 offset = 0; offset < 8; offset+=4) + { + drawLine(f.getPoints()[offset+0], f.getPoints()[offset+1]); + drawLine(f.getPoints()[offset+2], f.getPoints()[offset+3]); + drawLine(f.getPoints()[offset+0], f.getPoints()[offset+2]); + drawLine(f.getPoints()[offset+1], f.getPoints()[offset+3]); + } + drawLine(f.getPoints()[Frustum::NearTopLeft], f.getPoints()[Frustum::FarTopLeft]); + drawLine(f.getPoints()[Frustum::NearTopRight], f.getPoints()[Frustum::FarTopRight]); + drawLine(f.getPoints()[Frustum::NearBottomLeft], f.getPoints()[Frustum::FarBottomLeft]); + drawLine(f.getPoints()[Frustum::NearBottomRight], f.getPoints()[Frustum::FarBottomRight]); +} + +void DebugDrawer::drawText(const Point3F& pos, const String& text, const ColorF &color) +{ + if(isFrozen || !isDrawing) + return; + + DebugPrim *n = mPrimChunker.alloc(); + + n->useZ = false; + n->dieTime = 0; + n->a = pos; + n->color = color; + dStrncpy(n->mText, text.c_str(), 256); + n->type = DebugPrim::Text; + + n->next = mHead; + mHead = n; +} + +void DebugDrawer::setLastTTL(U32 ms) +{ + AssertFatal(mHead, "Tried to set last with nothing in the list!"); + if (ms != U32_MAX) + mHead->dieTime = Sim::getCurrentTime() + ms; + else + mHead->dieTime = U32_MAX; +} + +void DebugDrawer::setLastZTest(bool enabled) +{ + AssertFatal(mHead, "Tried to set last with nothing in the list!"); + mHead->useZ = enabled; +} + +// +// Script interface +// +ConsoleMethod(DebugDrawer, drawLine, void, 4, 4, "(Point3F a, Point3F b)") +{ + Point3F a, b; + + dSscanf(argv[2], "%f %f %f", &a.x, &a.y, &a.z); + dSscanf(argv[3], "%f %f %f", &b.x, &b.y, &b.z); + + object->drawLine(a, b); +} + +ConsoleMethod(DebugDrawer, setLastTTL, void, 3, 3, "(U32 ms)") +{ + object->setLastTTL(dAtoi(argv[2])); +} + +ConsoleMethod(DebugDrawer, setLastZTest, void, 3, 3, "(bool enabled)") +{ + object->setLastZTest(dAtob(argv[2])); +} + +ConsoleMethod(DebugDrawer, toggleFreeze, void, 2, 2, "() - Toggle freeze mode.") +{ + object->toggleFreeze(); +} + +ConsoleMethod(DebugDrawer, toggleDrawing, void, 2, 2, "() - Enabled/disable drawing.") +{ + object->toggleDrawing(); +} diff --git a/gfx/sim/debugDraw.h b/gfx/sim/debugDraw.h new file mode 100644 index 0000000..26f3369 --- /dev/null +++ b/gfx/sim/debugDraw.h @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DEBUGDRAW_H_ +#define _DEBUGDRAW_H_ + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _PRIMBUILDER_H_ +#include "gfx/primBuilder.h" +#endif +#ifndef _GFONT_H_ +#include "gfx/gFont.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif + + +class GFont; +class Frustum; + +#ifdef TORQUE_DEBUG +//#define ENABLE_DEBUGDRAW +#endif + +/// Debug output class. +/// +/// This class provides you with a flexible means of drawing debug output. It is +/// often useful when debugging collision code or complex 3d algorithms to have +/// them draw debug information, like culling hulls or bounding volumes, normals, +/// simple lines, and so forth. In TGE1.2, which was based directly on a simple +/// OpenGL rendering layer, it was a simple matter to do debug rendering directly +/// inline. +/// +/// Unfortunately, this doesn't hold true with more complex rendering scenarios, +/// where render modes and targets may be in abritrary states. In addition, it is +/// often useful to be able to freeze frame debug information for closer inspection. +/// +/// Therefore, TSE provides a global DebugDrawer instance, called gDebugDraw, which +/// you can use to draw debug information. It exposes a number of methods for drawing +/// a variety of debug primitives, including lines, triangles and boxes. +/// Internally, DebugDrawer maintains a list of active debug primitives, and draws the +/// contents of the list after each frame is done rendering. This way, you can be +/// assured that your debug rendering won't interfere with TSE's various effect +/// rendering passes or render-to-target calls. +/// +/// The DebugDrawer can also be used for more interesting uses, like freezing its +/// primitive list so you can look at a situation more closely, or dumping the +/// primitive list to disk for closer analysis. +/// +/// DebugDrawer is accessible by script under the name DebugDrawer, and by C++ under +/// the symbol gDebugDraw. There are a variety of methods available for drawing +/// different sorts of output; see the class reference for more information. +/// +/// DebugDrawer works solely in worldspace. Primitives are rendered with cull mode of +/// none. +/// +/// @warning Be careful! This tool is useful for debug but it can also + + +class DebugDrawer : public SimObject +{ +public: + DECLARE_CONOBJECT(DebugDrawer); + + DebugDrawer(); + ~DebugDrawer(); + + static DebugDrawer* get(); + + /// Called at engine init to set up the global debug draw object. + static void init(); + + /// Called globally to render debug draw state. Also does state updates. + void render(); + + void toggleFreeze() { shouldToggleFreeze = true; }; + void toggleDrawing() + { +#ifdef ENABLE_DEBUGDRAW + isDrawing = !isDrawing; +#endif + }; + + + /// @name ddrawmeth Debug Draw Methods + /// + /// @{ + + void drawBox (const Point3F &a, const Point3F &b, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); + void drawLine (const Point3F &a, const Point3F &b, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); + void drawTri (const Point3F &a, const Point3F &b, const Point3F &c, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); + void drawFrustum(const Frustum& f, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); + void drawText(const Point3F& pos, const String& text, const ColorF &color = ColorF(1.0f,1.0f,1.0f)); + + /// Set the TTL for the last item we entered... + /// + /// Primitives default to lasting one frame (ie, ttl=0) + enum { + DD_INFINITE = U32_MAX + }; + // How long should this primitive be draw for, 0 = one frame, DD_INFINITE = draw forever + void setLastTTL(U32 ms); + + /// Disable/enable z testing on the last primitive. + /// + /// Primitives default to z testing on. + void setLastZTest(bool enabled); + + /// @} +private: + typedef SimObject Parent; + + static DebugDrawer* sgDebugDrawer; + + struct DebugPrim + { + /// Color used for this primitive. + ColorF color; + + /// Points used to store positional data. Exact semantics determined by type. + Point3F a, b, c; + enum { + Tri, + Box, + Line, + Text + } type; ///< Type of the primitive. The meanings of a,b,c are determined by this. + + SimTime dieTime; ///< Time at which we should remove this from the list. + bool useZ; ///< If true, do z-checks for this primitive. + char mText[256]; // Text to display + + DebugPrim *next; + }; + + + FreeListChunker mPrimChunker; + DebugPrim *mHead; + + bool isFrozen; + bool shouldToggleFreeze; + bool isDrawing; + + GFXStateBlockRef mRenderZOffSB; + GFXStateBlockRef mRenderZOnSB; + + Resource mFont; + + void setupStateBlocks(); +}; + +#endif diff --git a/gfx/sim/gfxStateBlockData.cpp b/gfx/sim/gfxStateBlockData.cpp new file mode 100644 index 0000000..6e32b7c --- /dev/null +++ b/gfx/sim/gfxStateBlockData.cpp @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/sim/gfxStateBlockData.h" +#include "console/consoleTypes.h" +#include "gfx/gfxStringEnumTranslate.h" + +// +// GFXStateBlockData +// + +IMPLEMENT_CONOBJECT( GFXStateBlockData ); + +GFXStateBlockData::GFXStateBlockData() +{ + for (U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) + { + mSamplerStates[i] = NULL; + } +} + +void GFXStateBlockData::initPersistFields() +{ + // Alpha blending + addField("blendDefined", TypeBool, Offset(mState.blendDefined, GFXStateBlockData)); + addField("blendEnable", TypeBool, Offset(mState.blendEnable, GFXStateBlockData)); + addField("blendSrc", TypeEnum, Offset(mState.blendSrc, GFXStateBlockData), 1, &gBlendEnumTable); + addField("blendDest", TypeEnum, Offset(mState.blendDest, GFXStateBlockData), 1, &gBlendEnumTable); + addField("blendOp", TypeEnum, Offset(mState.blendOp, GFXStateBlockData), 1, &gBlendOpEnumTable); + + // Separate alpha blending + addField("separateAlphaBlendDefined", TypeBool, Offset(mState.separateAlphaBlendDefined, GFXStateBlockData)); + addField("separateAlphaBlendEnable", TypeBool, Offset(mState.separateAlphaBlendEnable, GFXStateBlockData)); + addField("separateAlphaBlendSrc", TypeEnum, Offset(mState.separateAlphaBlendSrc, GFXStateBlockData), 1, &gBlendEnumTable); + addField("separateAlphaBlendDest", TypeEnum, Offset(mState.separateAlphaBlendDest, GFXStateBlockData), 1, &gBlendEnumTable); + addField("separateAlphaBlendOp", TypeEnum, Offset(mState.separateAlphaBlendOp, GFXStateBlockData), 1, &gBlendOpEnumTable); + + // Alpha test + addField("alphaDefined", TypeBool, Offset(mState.alphaDefined, GFXStateBlockData)); + addField("alphaTestEnable", TypeBool, Offset(mState.alphaTestEnable, GFXStateBlockData)); + addField("alphaTestFunc", TypeEnum, Offset(mState.alphaTestFunc, GFXStateBlockData), 1, &gCmpFuncEnumTable); + addField("alphaTestRef", TypeS32, Offset(mState.alphaTestRef, GFXStateBlockData)); + + // Color Writes + addField("colorWriteDefined", TypeBool, Offset(mState.colorWriteDefined, GFXStateBlockData)); + addField("colorWriteRed", TypeBool, Offset(mState.colorWriteRed, GFXStateBlockData)); + addField("colorWriteBlue", TypeBool, Offset(mState.colorWriteBlue, GFXStateBlockData)); + addField("colorWriteGreen", TypeBool, Offset(mState.colorWriteGreen, GFXStateBlockData)); + addField("colorWriteAlpha", TypeBool, Offset(mState.colorWriteAlpha, GFXStateBlockData)); + + // Rasterizer + addField("cullDefined", TypeBool, Offset(mState.cullDefined, GFXStateBlockData)); + addField("cullMode", TypeEnum, Offset(mState.cullMode, GFXStateBlockData), 1, &gCullModeEnumTable); + + // Depth + addField("zDefined", TypeBool, Offset(mState.zDefined, GFXStateBlockData)); + addField("zEnable", TypeBool, Offset(mState.zEnable, GFXStateBlockData)); + addField("zWriteEnable", TypeBool, Offset(mState.zWriteEnable, GFXStateBlockData)); + addField("zFunc", TypeEnum, Offset(mState.zFunc, GFXStateBlockData), 1, &gCmpFuncEnumTable); + addField("zBias", TypeS32, Offset(mState.zBias, GFXStateBlockData)); + addField("zSlopeBias", TypeS32, Offset(mState.zSlopeBias, GFXStateBlockData)); + + // Stencil + addField("stencilDefined", TypeBool, Offset(mState.stencilDefined, GFXStateBlockData)); + addField("stencilEnable", TypeBool, Offset(mState.stencilEnable, GFXStateBlockData)); + addField("stencilFailOp", TypeEnum, Offset(mState.stencilFailOp, GFXStateBlockData), 1, &gStencilModeEnumTable); + addField("stencilZFailOp", TypeEnum, Offset(mState.stencilZFailOp, GFXStateBlockData), 1, &gStencilModeEnumTable); + addField("stencilPassOp", TypeEnum, Offset(mState.stencilPassOp, GFXStateBlockData), 1, &gStencilModeEnumTable); + addField("stencilFunc", TypeEnum, Offset(mState.stencilFunc, GFXStateBlockData), 1, &gCmpFuncEnumTable); + addField("stencilRef", TypeS32, Offset(mState.stencilRef, GFXStateBlockData)); + addField("stencilMask", TypeS32, Offset(mState.stencilMask, GFXStateBlockData)); + addField("stencilWriteMask", TypeS32, Offset(mState.stencilWriteMask, GFXStateBlockData)); + + // FF lighting + addField("ffLighting", TypeBool, Offset(mState.ffLighting, GFXStateBlockData)); + addField("vertexColorEnable", TypeBool, Offset(mState.vertexColorEnable, GFXStateBlockData)); + + // Sampler states + addField("samplersDefined", TypeBool, Offset(mState.samplersDefined, GFXStateBlockData)); + addField("samplerStates", TypeSimObjectPtr, Offset(mSamplerStates, GFXStateBlockData), TEXTURE_STAGE_COUNT); + addField("textureFactor", TypeColorI, Offset(mState.textureFactor, GFXStateBlockData)); + + Parent::initPersistFields(); +} + +bool GFXStateBlockData::onAdd() +{ + if (!Parent::onAdd()) + return false; + + for (U32 i = 0; i < TEXTURE_STAGE_COUNT; i++) + { + if (mSamplerStates[i]) + mSamplerStates[i]->setSamplerState(mState.samplers[i]); + } + return true; +} + +// +// GFXSamplerStateData +// +IMPLEMENT_CONOBJECT( GFXSamplerStateData ); + +void GFXSamplerStateData::initPersistFields() +{ + Parent::initPersistFields(); + + addField("textureTransform", TypeEnum, Offset(mState.textureTransform, GFXSamplerStateData), 1, &gTextureTransformEnumTable); + addField("addressModeU", TypeEnum, Offset(mState.addressModeU, GFXSamplerStateData), 1, &gSamplerAddressModeEnumTable); + addField("addressModeV", TypeEnum, Offset(mState.addressModeV, GFXSamplerStateData), 1, &gSamplerAddressModeEnumTable); + addField("addressModeW", TypeEnum, Offset(mState.addressModeW, GFXSamplerStateData), 1, &gSamplerAddressModeEnumTable); + + addField("magFilter", TypeEnum, Offset(mState.magFilter, GFXSamplerStateData), 1, &gTextureFilterModeEnumTable); + addField("minFilter", TypeEnum, Offset(mState.minFilter, GFXSamplerStateData), 1, &gTextureFilterModeEnumTable); + addField("mipFilter", TypeEnum, Offset(mState.mipFilter, GFXSamplerStateData), 1, &gTextureFilterModeEnumTable); + + addField("maxAnisotropy", TypeS32, Offset(mState.maxAnisotropy, GFXSamplerStateData)); + + addField("mipLODBias", TypeF32, Offset(mState.mipLODBias, GFXSamplerStateData)); + + addField("textureColorOp", TypeEnum, Offset(mState.textureColorOp, GFXSamplerStateData), 1, &gTextureColorOpEnumTable); + addField("colorArg1", TypeModifiedEnum, Offset(mState.colorArg1, GFXSamplerStateData), 1, &gTextureArgumentEnumTable_M); + addField("colorArg2", TypeModifiedEnum, Offset(mState.colorArg2, GFXSamplerStateData), 1, &gTextureArgumentEnumTable_M); + addField("colorArg3", TypeModifiedEnum, Offset(mState.colorArg3, GFXSamplerStateData), 1, &gTextureArgumentEnumTable_M); + + addField("alphaOp", TypeEnum, Offset(mState.alphaOp, GFXSamplerStateData), 1, &gTextureColorOpEnumTable); + addField("alphaArg1", TypeModifiedEnum, Offset(mState.alphaArg1, GFXSamplerStateData), 1, &gTextureArgumentEnumTable_M); + addField("alphaArg2", TypeModifiedEnum, Offset(mState.alphaArg2, GFXSamplerStateData), 1, &gTextureArgumentEnumTable_M); + addField("alphaArg3", TypeModifiedEnum, Offset(mState.alphaArg3, GFXSamplerStateData), 1, &gTextureArgumentEnumTable_M); + + addField("resultArg", TypeEnum, Offset(mState.resultArg, GFXSamplerStateData), 1, &gTextureArgumentEnumTable); +} + +/// Copies the data of this object into desc +void GFXSamplerStateData::setSamplerState(GFXSamplerStateDesc& desc) +{ + desc = mState; +} diff --git a/gfx/sim/gfxStateBlockData.h b/gfx/sim/gfxStateBlockData.h new file mode 100644 index 0000000..7b7fe16 --- /dev/null +++ b/gfx/sim/gfxStateBlockData.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef __GFXSTATEBLOCKDATA_H_ +#define __GFXSTATEBLOCKDATA_H_ + +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + +class GFXSamplerStateData; + +/// Allows definition of render state via script, basically wraps a GFXStateBlockDesc +class GFXStateBlockData : public SimObject +{ + typedef SimObject Parent; + + GFXStateBlockDesc mState; + GFXSamplerStateData* mSamplerStates[TEXTURE_STAGE_COUNT]; +public: + GFXStateBlockData(); + + // SimObject + virtual bool onAdd(); + static void initPersistFields(); + + // GFXStateBlockData + const GFXStateBlockDesc getState() const { return mState; } + + DECLARE_CONOBJECT(GFXStateBlockData); +}; + +/// Allows definition of sampler state via script, basically wraps a GFXSamplerStateDesc +class GFXSamplerStateData : public SimObject +{ + typedef SimObject Parent; + GFXSamplerStateDesc mState; +public: + // SimObject + static void initPersistFields(); + + /// Copies the data of this object into desc + void setSamplerState(GFXSamplerStateDesc& desc); + + DECLARE_CONOBJECT(GFXSamplerStateData); +}; + +#endif diff --git a/gfx/test/stanfordBunny.cpp b/gfx/test/stanfordBunny.cpp new file mode 100644 index 0000000..f1e6023 --- /dev/null +++ b/gfx/test/stanfordBunny.cpp @@ -0,0 +1,13623 @@ +/* + + File: stanfordbunny.c + + Abstract: See + + Version: 1.0 + + Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple + Computer, Inc. ("Apple") in consideration of your agreement to the + following terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, + install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Computer, + Inc. may be used to endorse or promote products derived from the Apple + Software without specific prior written permission from Apple. Except + as expressly stated in this notice, no other rights or licenses, express + or implied, are granted by Apple herein, including but not limited to + any patent rights that may be infringed by your derivative works or by + other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright © 2006 Apple Computer, Inc., All Rights Reserved + +*/ + +/* + stanfordbunny.c + + This file contains the data and associated APIs to create a display list + of a complex model, the "Stanford Bunny" a reconstructed 3D scan of a Bunny. + + It provides GenStanfordBunnyWireList() and GenStanfordBunnySolidList() + to construct a display list and return it's ID using 3 arrays of data: + face_indicies, vertices and normals that you see below. +*/ + + + +/* + Stanford Bunny Data (culled) + + See for original data +*/ + +#include "gfx/gfxDevice.h" +#include "gfx/gfxVertexBuffer.h" +#include "gfx/gfxStructs.h" + +// 8146 Verticies +// 8127 Normals +// 16301 Triangles + +short face_indicies[16301][6] = { +// surface + {1538,2410,1101 ,0,1,2 }, {713,6196,101 ,3,4,5 }, {696,704,101 ,6,7,5 }, + {1192,117,1113 ,8,9,10 }, {1260,173,1857 ,11,12,13 }, {540,1192,1113 ,14,8,10 }, + {704,696,398 ,7,6,15 }, {398,696,458 ,15,6,16 }, {1605,772,4540 ,17,18,19 }, + {1192,540,1857 ,8,14,13 }, {772,713,809 ,18,3,20 }, {713,173,1260 ,3,12,11 }, + {173,1192,1857 ,12,8,13 }, {809,713,1260 ,20,3,11 }, {804,4128,4120 ,21,22,23 }, + {1133,804,4120 ,24,21,23 }, {4128,4140,4531 ,22,25,26 }, {4140,1534,4136 ,25,27,28 }, + {4531,4140,4136 ,26,25,28 }, {4136,1534,1254 ,28,27,29 }, {4515,1609,4485 ,30,31,32 }, + {710,1396,889 ,33,34,35 }, {1245,3618,4204 ,36,37,38 }, {2046,1905,768 ,39,40,41 }, + {2453,5699,5698 ,42,43,44 }, {1254,2046,768 ,29,39,41 }, {768,1905,1393 ,41,40,45 }, + {1393,1490,4112 ,45,46,47 }, {5657,5658,5680 ,48,49,50 }, {1490,4281,839 ,46,51,52 }, + {4112,1490,839 ,47,46,52 }, {839,4281,1330 ,52,51,53 }, {6897,8079,3286 ,54,55,56 }, + {920,4291,1937 ,57,58,59 }, {321,1132,1222 ,60,61,62 }, {5338,4915,4914 ,63,64,65 }, + {1093,4537,840 ,66,67,68 }, {4537,1697,840 ,67,69,68 }, {705,659,4158 ,70,71,72 }, + {7248,7323,7322 ,73,74,75 }, {5413,5103,798 ,76,77,78 }, {5244,5289,5266 ,79,80,81 }, + {4197,4217,4258 ,82,83,84 }, {277,360,214 ,85,86,87 }, {4498,4536,4535 ,88,89,90 }, + {4497,4498,4535 ,91,88,90 }, {231,654,775 ,92,93,94 }, {4535,4536,792 ,90,89,95 }, + {8065,6577,8094 ,96,97,98 }, {1595,703,4145 ,99,100,101 }, {5203,1089,4369 ,102,103,104 }, + {2698,5203,4369 ,105,102,104 }, {1653,4491,226 ,106,107,108 }, + {2941,2940,2907 ,109,110,111 }, {6413,6414,3340 ,112,113,114 }, + {5231,3276,922 ,115,116,117 }, {1628,792,883 ,118,95,119 }, {4273,2042,542 ,120,121,122 }, + {4091,4126,4125 ,123,124,125 }, {4077,4091,4125 ,126,123,125 }, + {5618,103,2011 ,127,128,129 }, {2026,934,4251 ,130,131,132 }, + {4123,4173,4172 ,133,134,135 }, {3397,4123,4172 ,136,133,135 }, + {4122,4123,3397 ,137,133,136 }, {82,284,80 ,138,139,140 }, {5113,6120,5845 ,141,142,143 }, + {803,696,101 ,144,6,5 }, {139,1420,2137 ,145,146,147 }, {1348,1310,737 ,148,149,150 }, + {1423,196,396 ,151,152,153 }, {4403,4356,4432 ,154,155,156 }, + {143,781,142 ,157,158,159 }, {1148,1925,4132 ,160,161,162 }, + {1031,804,1133 ,163,21,24 }, {458,1031,1133 ,16,163,24 }, {1318,2137,1420 ,164,147,146 }, + {4255,4328,4301 ,165,166,167 }, {715,174,1652 ,168,169,170 }, + {5483,8116,5261 ,171,172,173 }, {1468,1451,542 ,174,175,122 }, + {2206,2219,4978 ,176,177,178 }, {804,4140,4128 ,21,25,22 }, {1222,1132,4521 ,62,61,179 }, + {4521,1318,606 ,179,164,180 }, {5162,5618,1443 ,181,127,182 }, + {3221,5261,8119 ,183,173,184 }, {4067,4077,4071 ,185,126,186 }, + {4526,1651,4515 ,187,188,30 }, {4526,891,1651 ,187,189,188 }, + {6320,6345,3533 ,190,191,192 }, {4140,779,1534 ,25,193,27 }, + {839,1330,1481 ,52,53,194 }, {1534,544,1254 ,27,195,29 }, {139,1318,1420 ,145,164,146 }, + {1842,940,1840 ,196,197,198 }, {5730,5729,3615 ,199,200,201 }, + {174,715,1147 ,169,168,202 }, {1345,1846,1093 ,203,204,66 }, + {4084,1178,4113 ,205,206,207 }, {3397,4172,1257 ,136,135,208 }, + {2042,1468,542 ,121,174,122 }, {787,4130,175 ,209,210,211 }, + {1461,1311,1310 ,212,213,149 }, {1311,457,1310 ,213,214,149 }, + {4211,321,885 ,215,60,216 }, {116,4211,885 ,217,215,216 }, {3761,4326,1463 ,218,219,220 }, + {544,1905,2046 ,195,40,39 }, {1254,544,2046 ,29,195,39 }, {1310,457,229 ,149,214,221 }, + {57,4160,735 ,222,223,224 }, {4029,4214,4019 ,225,226,227 }, + {4367,4421,4390 ,228,229,230 }, {4255,4301,4300 ,165,167,231 }, + {4411,962,1006 ,232,233,234 }, {2707,1335,4446 ,235,236,237 }, + {888,757,120 ,238,239,240 }, {4980,4547,2163 ,241,242,243 }, + {891,1461,200 ,189,212,244 }, {891,200,1651 ,189,244,188 }, {4838,311,24 ,245,246,247 }, + {169,105,1880 ,248,249,250 }, {1393,4179,1490 ,45,251,46 }, {1490,4179,4281 ,46,251,51 }, + {3263,8102,8089 ,252,253,254 }, {700,757,896 ,255,239,256 }, + {4465,4484,4514 ,257,258,259 }, {4202,700,464 ,260,255,261 }, + {3283,2968,1693 ,262,263,264 }, {1609,4514,4484 ,31,259,258 }, + {4485,1609,4484 ,32,31,258 }, {1609,714,4514 ,31,265,259 }, {606,1318,139 ,180,164,145 }, + {1747,979,970 ,266,267,268 }, {4136,1254,399 ,28,29,269 }, {4473,4451,4497 ,270,271,91 }, + {4057,2420,4263 ,272,273,274 }, {1396,770,889 ,34,275,35 }, {4421,4420,4390 ,229,276,230 }, + {1069,4138,1209 ,277,278,279 }, {3948,3670,1877 ,280,281,282 }, + {2707,5162,1335 ,235,181,236 }, {3484,863,1300 ,283,284,285 }, + {4978,5000,6414 ,178,286,113 }, {190,1161,1021 ,287,288,289 }, + {896,757,888 ,256,239,238 }, {1348,737,497 ,148,150,290 }, {639,1348,497 ,291,148,290 }, + {1112,4202,4194 ,292,260,293 }, {3958,3957,3903 ,294,295,296 }, + {64,613,11 ,297,298,299 }, {1344,456,795 ,300,301,302 }, {27,43,545 ,303,304,305 }, + {1033,888,30 ,306,238,307 }, {1023,4224,1033 ,308,309,306 }, + {4194,4202,464 ,293,260,261 }, {497,737,190 ,290,150,287 }, {737,356,190 ,150,310,287 }, + {2872,1245,4204 ,311,36,38 }, {4375,4531,4136 ,312,26,28 }, {1241,241,1389 ,313,314,315 }, + {4128,4531,4375 ,22,26,312 }, {4120,4128,4375 ,23,22,312 }, {709,1833,653 ,316,317,318 }, + {1793,4150,69 ,319,320,321 }, {69,1319,70 ,321,322,323 }, {1792,1375,24 ,324,325,247 }, + {6320,7500,6345 ,190,326,191 }, {1225,57,1245 ,327,222,36 }, + {1257,4172,4214 ,208,135,226 }, {997,190,1021 ,328,287,289 }, + {464,4224,1023 ,261,309,308 }, {3,464,1023 ,329,261,308 }, {714,639,950 ,265,291,330 }, + {714,950,4514 ,265,330,259 }, {7747,7746,7698 ,331,332,333 }, + {652,1296,1383 ,334,335,336 }, {1203,1204,1202 ,337,338,339 }, + {970,6018,1747 ,268,340,266 }, {4430,4451,4473 ,341,271,270 }, + {120,4362,4411 ,240,342,232 }, {7848,7874,7823 ,343,344,345 }, + {467,466,360 ,346,347,86 }, {1259,4095,4179 ,348,349,251 }, {5,413,315 ,350,351,352 }, + {4098,4057,4263 ,353,272,274 }, {1057,1596,1069 ,354,355,277 }, + {971,1057,1069 ,356,354,277 }, {44,412,1793 ,357,358,319 }, {4019,4255,4300 ,227,165,231 }, + {5378,5412,5377 ,359,360,361 }, {1259,4114,4059 ,348,362,363 }, + {4982,5000,4978 ,364,286,178 }, {4334,965,1419 ,365,366,367 }, + {1564,1085,1595 ,368,369,99 }, {4172,4215,4214 ,135,370,226 }, + {1225,2872,2428 ,327,311,371 }, {4110,1633,503 ,372,373,374 }, + {4328,4378,4354 ,166,375,376 }, {513,1343,376 ,377,378,379 }, + {3997,3998,4039 ,380,381,382 }, {8101,8122,8087 ,383,384,385 }, + {5000,6415,6414 ,286,386,113 }, {660,703,1625 ,387,100,388 }, + {805,2352,24 ,389,390,247 }, {1599,799,1194 ,391,392,393 }, {5850,4011,5784 ,394,395,396 }, + {3871,5850,5784 ,397,394,396 }, {2872,1225,1245 ,311,327,36 }, + {1633,513,376 ,373,377,379 }, {3871,5784,878 ,397,396,398 }, + {659,4171,802 ,71,399,400 }, {708,1222,365 ,401,62,402 }, {117,458,1133 ,9,16,24 }, + {4497,4535,4534 ,91,90,403 }, {1967,1966,1903 ,404,405,406 }, + {3259,4091,4068 ,407,123,408 }, {4185,3113,52 ,409,410,411 }, + {8090,8132,3006 ,412,413,414 }, {211,965,4334 ,415,366,365 }, + {4214,4254,4019 ,226,416,227 }, {1023,2045,211 ,308,417,415 }, + {1923,1023,211 ,418,308,415 }, {4043,2798,2956 ,419,420,399 }, + {4535,792,1628 ,90,95,118 }, {398,458,117 ,15,16,9 }, {139,2137,777 ,145,147,421 }, + {4534,4535,1628 ,403,90,118 }, {4411,4362,962 ,232,342,233 }, + {5199,5198,5177 ,422,423,424 }, {1838,4427,363 ,425,426,427 }, + {5213,5233,5212 ,428,429,430 }, {1419,799,1599 ,367,392,391 }, + {211,30,965 ,415,307,366 }, {347,102,2423 ,431,432,433 }, {138,3871,878 ,434,397,398 }, + {4182,4201,4051 ,435,436,437 }, {2313,1222,708 ,438,62,401 }, + {885,2313,708 ,216,438,401 }, {3327,3977,3513 ,439,440,441 }, + {3535,1345,840 ,442,203,68 }, {915,913,916 ,443,444,445 }, {678,634,682 ,446,447,448 }, + {634,346,682 ,447,449,448 }, {7254,6347,7860 ,450,451,452 }, + {8061,8082,8102 ,453,454,253 }, {1595,1085,703 ,99,369,100 }, + {2660,2753,2349 ,455,456,457 }, {2338,2680,4039 ,458,459,382 }, + {4040,2338,4039 ,460,458,382 }, {5307,5306,4393 ,461,462,463 }, + {4537,705,1697 ,67,70,69 }, {6563,8061,8063 ,464,453,465 }, {1305,4446,1300 ,466,237,285 }, + {4312,4311,490 ,467,468,469 }, {4301,4328,4354 ,167,166,376 }, + {467,503,466 ,346,374,347 }, {4051,1933,1695 ,437,470,471 }, + {6859,6860,6911 ,472,473,474 }, {2455,2212,5001 ,475,476,477 }, + {1197,6341,4983 ,478,479,480 }, {5240,5239,5218 ,481,482,483 }, + {705,1346,1697 ,70,484,69 }, {1731,1747,604 ,485,266,486 }, {491,586,678 ,487,488,446 }, + {833,491,678 ,489,487,446 }, {586,679,678 ,488,490,446 }, {679,86,634 ,490,491,447 }, + {678,679,634 ,446,490,447 }, {1485,346,634 ,492,449,447 }, {86,1485,634 ,491,492,447 }, + {1485,496,346 ,492,493,449 }, {591,638,346 ,494,495,449 }, {496,591,346 ,493,494,449 }, + {29,15,638 ,496,497,495 }, {591,29,638 ,494,496,495 }, {29,438,15 ,496,498,497 }, + {745,961,438 ,499,500,498 }, {745,446,961 ,499,501,500 }, {4187,1023,1923 ,502,308,418 }, + {189,1021,4187 ,503,289,502 }, {365,606,139 ,402,180,145 }, {883,792,146 ,119,95,504 }, + {174,1597,892 ,169,505,506 }, {826,1021,189 ,507,289,503 }, {5570,1350,5641 ,508,509,510 }, + {8072,530,3668 ,511,512,513 }, {5699,5727,5726 ,43,514,515 }, + {2927,2926,2882 ,516,517,518 }, {6912,6943,6942 ,519,520,521 }, + {6341,1197,2808 ,479,478,522 }, {536,1305,1300 ,523,466,285 }, + {5178,5199,5177 ,524,422,424 }, {4311,4383,490 ,468,399,469 }, + {174,1422,1597 ,169,525,505 }, {5642,5658,5657 ,526,49,48 }, + {586,86,679 ,488,491,490 }, {1921,446,745 ,527,501,499 }, {1199,39,446 ,528,529,501 }, + {1625,4222,4251 ,388,530,132 }, {1487,304,2680 ,531,532,459 }, + {304,1487,488 ,532,531,533 }, {2150,964,3084 ,534,535,536 }, + {5726,5727,5764 ,515,514,537 }, {803,4,696 ,144,538,6 }, {3511,4029,4019 ,539,225,227 }, + {4,1031,458 ,538,163,16 }, {696,4,458 ,6,538,16 }, {5727,5765,5764 ,514,540,537 }, + {4369,1089,4393 ,104,103,463 }, {4326,3341,3258 ,219,541,542 }, + {4158,659,802 ,72,71,400 }, {5834,5833,5816 ,543,544,545 }, {3008,7051,7029 ,546,547,548 }, + {965,888,799 ,366,238,392 }, {950,1652,4483 ,330,170,549 }, {4514,950,4483 ,259,330,549 }, + {1112,116,885 ,292,217,216 }, {3862,1973,7810 ,550,551,552 }, + {2093,2257,1749 ,553,554,555 }, {466,416,143 ,347,556,157 }, + {811,4954,812 ,557,558,559 }, {92,1921,745 ,560,527,499 }, {92,446,1921 ,560,501,527 }, + {317,344,93 ,561,562,563 }, {93,344,39 ,563,562,529 }, {1626,3253,349 ,564,565,566 }, + {4379,4431,4430 ,567,568,341 }, {4378,4379,4430 ,375,567,341 }, + {4503,779,4140 ,569,193,25 }, {5283,5284,5337 ,570,571,572 }, + {804,4503,4140 ,21,569,25 }, {2070,1367,1244 ,573,574,575 }, + {360,466,214 ,86,347,87 }, {229,116,1112 ,221,217,292 }, {4077,4109,4076 ,126,576,577 }, + {1023,1033,2045 ,308,306,417 }, {314,1419,1599 ,578,367,391 }, + {875,2155,1920 ,579,580,581 }, {4444,4482,4464 ,582,583,584 }, + {30,888,965 ,307,238,366 }, {4900,4545,5578 ,585,586,587 }, {1260,1461,891 ,11,212,189 }, + {809,1260,891 ,20,11,189 }, {1271,4548,2252 ,588,589,590 }, {2246,351,352 ,591,592,593 }, + {6946,6968,6916 ,594,595,596 }, {6555,2093,1749 ,597,553,555 }, + {235,1292,1517 ,598,599,600 }, {1292,1850,1517 ,599,601,600 }, + {1520,586,491 ,602,488,487 }, {251,1520,491 ,603,602,487 }, {1520,2225,586 ,602,604,488 }, + {63,86,586 ,605,491,488 }, {2225,63,586 ,604,605,488 }, {63,244,1485 ,605,606,492 }, + {86,63,1485 ,491,605,492 }, {1485,244,496 ,492,606,493 }, {244,590,591 ,606,607,494 }, + {496,244,591 ,493,606,494 }, {590,1198,29 ,607,608,496 }, {591,590,29 ,494,607,496 }, + {1198,441,438 ,608,609,498 }, {29,1198,438 ,496,608,498 }, {441,956,745 ,609,610,499 }, + {438,441,745 ,498,609,499 }, {745,956,92 ,499,610,560 }, {92,868,446 ,560,611,501 }, + {1247,1199,446 ,612,528,501 }, {868,1247,446 ,611,612,501 }, + {93,39,1199 ,563,529,528 }, {1247,93,1199 ,612,563,528 }, {2150,3084,832 ,534,536,613 }, + {488,2150,832 ,533,534,613 }, {6044,5933,5934 ,614,615,616 }, + {8076,1295,3040 ,617,618,619 }, {779,544,1534 ,193,195,27 }, + {2338,1487,2680 ,458,531,459 }, {1487,2150,488 ,531,534,533 }, + {1905,4179,1393 ,40,251,45 }, {681,683,302 ,620,621,622 }, {1260,1311,1461 ,11,213,212 }, + {5162,1443,1335 ,181,182,236 }, {4482,4525,4513 ,583,623,624 }, + {4334,1419,314 ,365,367,578 }, {950,715,1652 ,330,168,170 }, + {1305,2707,4446 ,466,235,237 }, {120,4411,1006 ,240,232,234 }, + {4545,3621,5578 ,586,625,587 }, {7264,7263,7222 ,626,627,628 }, + {6698,6699,6750 ,629,630,631 }, {1520,534,63 ,602,632,605 }, + {2225,1520,63 ,604,602,605 }, {63,534,244 ,605,632,606 }, {317,93,4539 ,561,563,633 }, + {3084,964,3094 ,536,535,634 }, {1294,874,169 ,635,636,248 }, + {2789,2832,2831 ,637,638,639 }, {4165,87,2477 ,640,641,642 }, + {832,3084,3094 ,613,536,634 }, {4430,4431,4451 ,341,568,271 }, + {348,249,268 ,643,644,645 }, {5618,5616,1443 ,127,646,182 }, + {715,497,1147 ,168,290,202 }, {4468,3673,3473 ,647,648,649 }, + {7746,7745,7697 ,332,650,651 }, {3749,5380,5304 ,652,653,654 }, + {888,120,1006 ,238,240,234 }, {2132,2131,2085 ,655,656,657 }, + {8107,3006,2731 ,658,414,659 }, {5812,5823,7073 ,660,661,662 }, + {153,1243,1292 ,663,664,599 }, {235,153,1292 ,598,663,599 }, + {1292,1243,1850 ,599,664,601 }, {112,251,1850 ,665,603,601 }, + {1243,112,1850 ,664,665,601 }, {1593,1520,251 ,666,602,603 }, + {112,1593,251 ,665,666,603 }, {1593,172,534 ,666,667,632 }, {1520,1593,534 ,602,666,632 }, + {727,726,627 ,668,669,670 }, {590,589,1198 ,607,671,608 }, {1198,357,441 ,608,672,609 }, + {442,310,956 ,673,674,610 }, {441,442,956 ,609,673,610 }, {310,60,92 ,674,675,560 }, + {956,310,92 ,610,674,560 }, {92,60,868 ,560,675,611 }, {60,1788,1247 ,675,676,612 }, + {868,60,1247 ,611,675,612 }, {1788,113,93 ,676,677,563 }, {1247,1788,93 ,612,676,563 }, + {1059,1294,169 ,678,635,248 }, {105,783,1066 ,249,679,680 }, + {141,4206,4186 ,681,682,683 }, {6860,6912,6911 ,473,519,474 }, + {5013,6484,5455 ,684,685,686 }, {964,1294,1059 ,535,635,678 }, + {3094,964,1059 ,634,535,678 }, {128,883,4594 ,687,119,688 }, + {883,128,76 ,119,687,689 }, {3750,1463,3681 ,690,220,691 }, {6094,3373,3300 ,692,693,694 }, + {4179,1490,4281 ,251,46,51 }, {1490,4310,4281 ,46,695,51 }, {1461,1310,1348 ,212,149,148 }, + {700,896,4224 ,255,256,309 }, {1021,1161,3 ,289,288,329 }, {1350,5642,5641 ,509,526,510 }, + {459,211,4334 ,696,415,365 }, {4237,4257,4275 ,697,698,699 }, + {5801,5817,5800 ,700,701,702 }, {4444,4464,4443 ,582,584,703 }, + {6350,6349,6321 ,704,705,706 }, {2045,1033,30 ,417,306,307 }, + {1742,7051,3008 ,707,547,546 }, {7052,3660,3454 ,708,709,710 }, + {6790,6809,6754 ,711,712,713 }, {1593,16,172 ,666,714,667 }, + {3399,4550,4447 ,715,716,717 }, {526,582,625 ,718,719,720 }, + {725,724,625 ,721,722,720 }, {8129,7693,8116 ,723,724,172 }, + {1195,4395,5194 ,725,726,727 }, {3008,3975,3944 ,546,728,729 }, + {129,128,695 ,730,687,731 }, {129,76,128 ,730,689,687 }, {6149,3701,3648 ,732,733,734 }, + {3495,3494,3434 ,735,736,737 }, {8116,7693,8071 ,172,724,738 }, + {195,4350,4098 ,739,740,353 }, {1150,38,110 ,741,742,743 }, {1311,540,457 ,213,14,214 }, + {497,190,997 ,290,287,328 }, {459,4334,4412 ,696,365,744 }, {1750,4894,1698 ,745,746,747 }, + {4412,4334,314 ,744,365,578 }, {4544,6353,6686 ,748,749,750 }, + {5521,8071,8125 ,751,738,752 }, {314,1599,4244 ,578,391,753 }, + {5536,5522,5557 ,754,755,756 }, {5850,3749,4011 ,394,652,395 }, + {1995,2049,2015 ,757,758,759 }, {6299,404,6417 ,760,761,762 }, + {172,16,1743 ,667,714,763 }, {582,583,625 ,719,764,720 }, {783,688,1066 ,679,765,680 }, + {169,874,105 ,248,636,249 }, {874,783,105 ,636,679,249 }, {615,4281,4310 ,766,51,695 }, + {226,1150,110 ,108,741,743 }, {774,1634,683 ,767,768,621 }, {4174,4217,4197 ,769,83,82 }, + {540,4211,116 ,14,215,217 }, {200,1461,1348 ,244,212,148 }, {464,700,4224 ,261,255,309 }, + {1147,497,997 ,202,290,328 }, {103,2030,2011 ,128,770,129 }, + {4429,1923,459 ,771,418,696 }, {1699,1751,1750 ,772,773,745 }, + {1698,1699,1750 ,747,772,745 }, {4587,4588,4589 ,774,775,776 }, + {1811,1885,1810 ,777,778,779 }, {6553,3252,3283 ,780,781,262 }, + {4482,4513,4464 ,583,624,584 }, {976,4964,4965 ,782,783,784 }, + {2050,2049,1995 ,785,758,757 }, {1950,2050,1995 ,786,785,757 }, + {2050,2075,2049 ,785,787,758 }, {2075,2096,2049 ,787,788,758 }, + {3661,2212,2455 ,789,476,475 }, {1331,1243,153 ,790,664,663 }, + {597,1331,153 ,791,790,663 }, {553,112,1243 ,792,665,664 }, {1331,553,1243 ,790,792,664 }, + {553,1727,1593 ,792,793,666 }, {112,553,1593 ,665,792,666 }, + {1727,1743,16 ,793,763,714 }, {1593,1727,16 ,666,793,714 }, {170,1290,1240 ,794,795,796 }, + {1184,999,1188 ,797,798,799 }, {526,527,582 ,718,800,719 }, {583,626,625 ,764,801,720 }, + {3598,3547,6598 ,802,803,804 }, {2680,304,2662 ,459,532,805 }, + {2853,3190,2416 ,806,807,808 }, {3300,5977,6094 ,694,809,692 }, + {60,145,1788 ,675,810,676 }, {310,2694,60 ,674,811,675 }, {8126,6891,3148 ,812,813,814 }, + {7924,7923,7873 ,815,816,817 }, {3007,4486,6070 ,818,819,820 }, + {1065,1340,1450 ,821,822,823 }, {688,1065,1066 ,765,821,680 }, + {3359,3325,3326 ,824,825,826 }, {5178,5177,4630 ,524,424,827 }, + {1980,2007,1979 ,828,829,830 }, {1984,1916,1917 ,831,832,833 }, + {277,214,1992 ,85,87,834 }, {1539,96,1587 ,835,836,837 }, {4335,4151,1833 ,838,839,317 }, + {768,1393,864 ,41,45,840 }, {457,540,116 ,214,14,217 }, {1652,892,4525 ,170,506,623 }, + {2030,3913,2011 ,770,841,129 }, {2014,1923,736 ,842,418,843 }, + {4392,2341,7051 ,844,845,547 }, {2097,2096,2075 ,846,788,787 }, + {2007,1978,1979 ,829,847,830 }, {1936,1333,2341 ,848,849,845 }, + {1411,1331,597 ,850,790,791 }, {5353,6836,144 ,851,852,853 }, + {1721,1778,1777 ,854,855,856 }, {392,170,1240 ,857,794,796 }, + {1475,1476,1547 ,858,859,860 }, {433,434,485 ,861,862,863 }, + {527,583,582 ,800,764,719 }, {385,386,433 ,864,865,861 }, {434,527,526 ,862,800,718 }, + {337,338,385 ,866,867,864 }, {238,338,237 ,868,867,869 }, {386,434,433 ,865,862,861 }, + {338,386,385 ,867,865,864 }, {338,337,237 ,867,866,869 }, {3382,3381,3356 ,870,871,872 }, + {2582,2474,2585 ,873,874,875 }, {5077,4549,1809 ,876,877,878 }, + {3305,3358,3324 ,879,880,881 }, {3383,3382,3357 ,882,870,883 }, + {3900,3478,3455 ,884,885,886 }, {1066,1065,1450 ,680,821,823 }, + {2036,2081,2035 ,887,888,889 }, {2081,2126,2068 ,888,890,891 }, + {2081,2068,2035 ,888,891,889 }, {2126,2125,2068 ,890,892,891 }, + {2274,1882,4980 ,893,894,241 }, {864,4112,839 ,840,47,52 }, {3341,863,3258 ,541,284,542 }, + {863,536,1300 ,284,523,285 }, {1393,4112,864 ,45,47,840 }, {4211,4212,321 ,215,895,60 }, + {1597,1422,18 ,505,525,896 }, {2014,4187,1923 ,842,502,418 }, + {1923,4429,736 ,418,771,843 }, {736,4429,459 ,843,771,696 }, + {358,189,4184 ,897,503,898 }, {1950,1929,1885 ,786,899,778 }, + {2050,2097,2075 ,785,846,787 }, {1780,1843,1842 ,900,901,196 }, + {1548,1549,1617 ,902,903,904 }, {8130,8120,8098 ,905,906,907 }, + {3080,1302,13 ,908,909,910 }, {1844,1845,1873 ,911,912,913 }, + {2035,2068,1978 ,889,891,847 }, {1059,169,652 ,678,248,334 }, + {485,434,526 ,863,862,718 }, {2650,3981,3054 ,914,915,916 }, + {3453,2650,3054 ,917,914,916 }, {2892,1305,536 ,918,466,523 }, + {7693,8129,3976 ,724,723,919 }, {443,171,444 ,920,921,922 }, + {2743,3687,1378 ,923,924,925 }, {3545,3595,3574 ,926,927,928 }, + {913,2892,536 ,444,918,523 }, {3432,3492,3462 ,929,930,931 }, + {3358,3383,3357 ,880,882,883 }, {5769,5768,5731 ,932,933,934 }, + {6045,6080,5453 ,935,936,937 }, {548,595,645 ,938,939,940 }, + {8070,3534,3668 ,941,942,513 }, {1586,1195,3315 ,943,725,944 }, + {399,1254,768 ,269,29,41 }, {4375,4136,399 ,312,28,269 }, {4212,294,321 ,895,945,60 }, + {356,4194,1161 ,310,293,288 }, {190,356,1161 ,287,310,288 }, + {1422,1147,997 ,525,202,328 }, {1652,174,892 ,170,169,506 }, + {1422,826,18 ,525,507,896 }, {4184,4187,2014 ,898,502,842 }, + {5977,3300,1936 ,809,694,848 }, {1752,1812,1811 ,946,947,777 }, + {1751,1752,1811 ,773,946,777 }, {1886,1885,1811 ,948,778,777 }, + {1812,1886,1811 ,947,948,777 }, {1951,1950,1885 ,949,786,778 }, + {1886,1951,1885 ,948,949,778 }, {1951,1996,1950 ,949,950,786 }, + {2051,2050,1950 ,951,785,786 }, {1996,2051,1950 ,950,951,786 }, + {2098,2097,2050 ,952,846,785 }, {2051,2098,2050 ,951,952,785 }, + {2098,2143,2097 ,952,953,846 }, {595,646,645 ,939,954,940 }, + {3492,3545,3491 ,930,926,955 }, {1045,1144,992 ,956,957,958 }, + {8128,8066,3277 ,959,960,961 }, {1874,1913,1912 ,962,963,964 }, + {386,435,434 ,865,965,862 }, {395,171,59 ,966,921,967 }, {59,171,443 ,967,921,920 }, + {3917,3916,3868 ,968,969,970 }, {3148,6891,3008 ,814,813,546 }, + {743,742,647 ,971,972,973 }, {1403,1404,1475 ,974,975,858 }, + {788,2558,600 ,976,977,978 }, {3542,3573,3541 ,979,980,981 }, + {3958,3959,3998 ,294,982,381 }, {938,4,803 ,983,538,144 }, {4,264,1031 ,538,984,163 }, + {264,4235,804 ,984,985,21 }, {4194,464,3 ,293,261,329 }, {505,5309,469 ,986,987,988 }, + {1635,1699,1442 ,989,772,990 }, {4443,4442,4391 ,703,991,992 }, + {4443,4464,4442 ,703,584,991 }, {4464,4463,4442 ,584,993,991 }, + {1328,1405,1327 ,994,995,996 }, {1838,363,1837 ,425,427,997 }, + {724,816,815 ,722,998,999 }, {1914,1983,1982 ,1000,1001,1002 }, + {1619,1686,1685 ,1003,1004,1005 }, {2124,2193,2123 ,1006,1007,1008 }, + {1681,1682,1739 ,1009,1010,1011 }, {291,336,335 ,1012,1013,1014 }, + {1663,1630,3081 ,1015,1016,1017 }, {726,725,626 ,669,721,801 }, + {627,726,626 ,670,669,801 }, {726,818,725 ,669,1018,721 }, {1476,1548,1547 ,859,902,860 }, + {906,817,818 ,1019,1020,1018 }, {3960,3959,3916 ,1021,982,969 }, + {3917,3960,3916 ,968,1021,969 }, {3960,4000,3999 ,1021,1022,1023 }, + {3959,3960,3999 ,982,1021,1023 }, {7115,7139,7129 ,1024,1025,1026 }, + {3189,3016,2150 ,1027,1028,534 }, {1031,264,804 ,163,984,21 }, + {804,4235,4503 ,21,985,569 }, {4503,4235,779 ,569,985,193 }, + {779,709,544 ,193,316,195 }, {173,704,398 ,12,7,15 }, {1161,4194,3 ,288,293,329 }, + {2352,1792,24 ,390,324,247 }, {4715,4716,4714 ,1029,1030,1031 }, + {6432,3265,1941 ,1032,1033,1034 }, {1603,1636,1635 ,1035,1036,989 }, + {1566,1603,1635 ,1037,1035,989 }, {1703,1752,1702 ,1038,946,1039 }, + {2143,2166,2165 ,953,1040,1041 }, {290,333,289 ,1042,1043,1044 }, + {1405,1404,1327 ,995,975,996 }, {1405,1477,1404 ,995,1045,975 }, + {7003,7002,6963 ,1046,1047,1048 }, {3303,3304,3323 ,1049,1050,1051 }, + {722,723,814 ,1052,1053,1054 }, {627,626,583 ,670,801,764 }, + {528,627,583 ,1055,670,764 }, {4426,6577,6837 ,1056,97,1057 }, + {1908,28,3636 ,1058,1059,1060 }, {3358,3357,3324 ,880,883,881 }, + {4042,2946,4041 ,1061,1062,1063 }, {2946,2571,3194 ,1062,1064,1065 }, + {3190,2555,2416 ,807,1066,808 }, {2558,19,600 ,977,1067,978 }, + {544,4454,1905 ,195,1068,40 }, {4454,4135,1905 ,1068,1069,40 }, + {117,1133,4213 ,9,24,1070 }, {1905,4135,4179 ,40,1069,251 }, + {1805,1490,4179 ,1071,46,251 }, {1805,4310,1490 ,1071,695,46 }, + {1805,615,4310 ,1071,766,695 }, {4521,1591,1318 ,179,1072,164 }, + {106,1207,5146 ,1073,1074,1075 }, {4421,4462,4420 ,229,1076,276 }, + {1338,4214,4029 ,1077,226,225 }, {7457,7515,7539 ,1078,1079,1080 }, + {1495,1567,1566 ,1081,1082,1037 }, {1494,1495,1566 ,1083,1081,1037 }, + {1567,1568,1603 ,1082,1084,1035 }, {1566,1567,1603 ,1037,1082,1035 }, + {1568,1637,1636 ,1084,1085,1036 }, {1603,1568,1636 ,1035,1084,1036 }, + {1637,1704,1703 ,1085,1086,1038 }, {1636,1637,1703 ,1036,1085,1038 }, + {1753,1752,1703 ,1087,946,1038 }, {1704,1753,1703 ,1086,1087,1038 }, + {1753,1795,1752 ,1087,1088,946 }, {1813,1812,1752 ,1089,947,946 }, + {1795,1813,1752 ,1088,1089,946 }, {1887,1886,1812 ,1090,948,947 }, + {1813,1887,1812 ,1089,1090,947 }, {1887,1952,1951 ,1090,1091,949 }, + {1886,1887,1951 ,948,1090,949 }, {1952,1996,1951 ,1091,950,949 }, + {2052,2051,1996 ,1092,951,950 }, {1952,2052,1996 ,1091,1092,950 }, + {2099,2098,2051 ,1093,952,951 }, {2052,2099,2051 ,1092,1093,951 }, + {2099,2143,2098 ,1093,953,952 }, {8080,8094,8069 ,1094,98,1095 }, + {3932,1059,652 ,1096,678,334 }, {6311,8122,1168 ,1097,384,1098 }, + {624,723,623 ,1099,1053,1100 }, {1979,1978,1911 ,830,847,1101 }, + {387,386,338 ,1102,865,867 }, {238,239,338 ,868,1103,867 }, {1687,1782,1781 ,1104,1105,1106 }, + {340,593,909 ,1107,1108,1109 }, {1918,908,1190 ,1110,1111,1112 }, + {5117,350,2291 ,1113,1114,1115 }, {2433,2400,2434 ,1116,1117,1118 }, + {865,776,867 ,1119,1120,1121 }, {4213,1133,4120 ,1070,24,23 }, + {321,294,1132 ,60,945,61 }, {101,173,713 ,5,12,3 }, {655,195,4056 ,1122,739,1123 }, + {195,4098,4056 ,739,353,1123 }, {660,118,67 ,387,1124,1125 }, + {1609,200,714 ,31,244,265 }, {1428,1495,1494 ,1126,1081,1083 }, + {1495,1568,1567 ,1081,1084,1082 }, {1753,1813,1795 ,1087,1089,1088 }, + {3194,2571,3016 ,1065,1064,1028 }, {2032,2123,2122 ,1127,1008,1128 }, + {647,549,2609 ,973,1129,1130 }, {2259,6314,6646 ,1131,1132,1133 }, + {1328,1329,1405 ,994,1134,995 }, {1406,1447,1446 ,1135,1136,1137 }, + {1777,1778,1839 ,856,855,1138 }, {8084,8110,8124 ,1139,1140,1141 }, + {436,435,386 ,1142,965,865 }, {339,387,338 ,1143,1102,867 }, + {486,528,434 ,1144,1055,862 }, {528,583,527 ,1055,764,800 }, + {3456,3566,3861 ,1145,1146,1147 }, {529,528,486 ,1148,1055,1144 }, + {437,486,436 ,1149,1144,1142 }, {1873,1874,1912 ,913,962,964 }, + {1616,1617,1683 ,1150,904,1151 }, {3462,3492,3461 ,931,930,1152 }, + {1880,105,2558 ,250,249,977 }, {3545,3544,3491 ,926,1153,955 }, + {2433,111,2400 ,1116,1154,1117 }, {101,704,173 ,5,7,12 }, {1132,1591,4521 ,61,1072,179 }, + {5765,5801,5764 ,540,700,537 }, {277,781,11 ,85,158,299 }, {613,277,11 ,298,85,299 }, + {879,4123,4122 ,1155,133,137 }, {3421,8113,3278 ,1156,1157,1158 }, + {911,2158,6684 ,1159,1160,1161 }, {1351,1428,8008 ,1162,1126,1163 }, + {1351,1395,1428 ,1162,1164,1126 }, {1567,1568,1495 ,1082,1084,1081 }, + {1814,1813,1753 ,1165,1089,1087 }, {1656,3227,3198 ,1166,1167,1168 }, + {449,547,546 ,1169,1170,1171 }, {3959,3999,3998 ,982,1023,381 }, + {448,449,546 ,1172,1169,1171 }, {647,691,743 ,973,1173,971 }, + {2191,2192,392 ,1174,1175,857 }, {547,644,546 ,1170,1176,1171 }, + {627,628,727 ,670,1177,668 }, {1234,1235,1328 ,1178,1179,994 }, + {1976,2032,2006 ,1180,1127,1181 }, {3323,3322,3303 ,1051,1182,1049 }, + {486,434,435 ,1144,862,965 }, {387,436,386 ,1102,1142,865 }, + {528,527,434 ,1055,800,862 }, {1405,1406,1446 ,995,1135,1137 }, + {304,488,3080 ,532,533,908 }, {3331,3364,3330 ,1183,1184,1185 }, + {1296,1880,2558 ,335,250,977 }, {7397,7453,7424 ,1186,1187,1188 }, + {1318,867,2137 ,164,1121,147 }, {3817,4756,4682 ,1189,1190,1191 }, + {5240,5264,5239 ,481,1192,482 }, {5616,5618,2011 ,646,127,129 }, + {5258,5282,5281 ,1193,1194,1195 }, {2026,1927,4234 ,130,1196,1197 }, + {2258,1263,5455 ,1198,1199,686 }, {3362,6350,6321 ,1200,704,706 }, + {2158,2272,6684 ,1160,1201,1161 }, {7151,3291,3290 ,1202,1203,1204 }, + {1353,1429,1428 ,1205,1206,1126 }, {1395,1353,1428 ,1164,1205,1126 }, + {1496,1495,1428 ,1207,1081,1126 }, {1429,1496,1428 ,1206,1207,1126 }, + {1569,1567,1495 ,1208,1082,1081 }, {1496,1569,1495 ,1207,1208,1081 }, + {1604,1568,1567 ,1209,1084,1082 }, {1569,1604,1567 ,1208,1209,1082 }, + {1638,1637,1568 ,1210,1085,1084 }, {1604,1638,1568 ,1209,1210,1084 }, + {1638,1705,1704 ,1210,1211,1086 }, {1637,1638,1704 ,1085,1210,1086 }, + {1754,1753,1704 ,1212,1087,1086 }, {1705,1754,1704 ,1211,1212,1086 }, + {1815,1814,1753 ,1213,1165,1087 }, {1754,1815,1753 ,1212,1213,1087 }, + {1862,1813,1814 ,1214,1089,1165 }, {1815,1862,1814 ,1213,1214,1165 }, + {1888,1887,1813 ,1215,1090,1089 }, {1862,1888,1813 ,1214,1215,1089 }, + {1953,1952,1887 ,1216,1091,1090 }, {1888,1953,1887 ,1215,1216,1090 }, + {2053,2052,1952 ,1217,1092,1091 }, {1953,2053,1952 ,1216,1217,1091 }, + {2100,2099,2052 ,1218,1093,1092 }, {2053,2100,2052 ,1217,1218,1092 }, + {828,827,741 ,1219,1220,1221 }, {385,433,432 ,864,861,1222 }, + {1842,1843,1910 ,196,901,1223 }, {2009,1188,862 ,1224,799,1225 }, + {1780,1803,1843 ,900,1226,901 }, {436,486,435 ,1142,1144,965 }, + {432,484,483 ,1222,1227,1228 }, {3987,3946,21 ,1229,1230,1231 }, + {1780,1781,1803 ,900,1106,1226 }, {1062,1014,929 ,1232,1233,1234 }, + {1182,1048,953 ,1235,1236,1237 }, {1183,1182,953 ,1238,1235,1237 }, + {2945,3900,3455 ,1239,884,886 }, {7115,7129,7102 ,1024,1026,1240 }, + {4173,4216,4215 ,134,1241,370 }, {4335,4280,642 ,838,1242,1243 }, + {1112,885,4202 ,292,216,260 }, {2026,4234,934 ,130,1197,131 }, + {3818,1907,2785 ,1244,1245,1246 }, {4328,4329,4378 ,166,1247,375 }, + {1207,106,3878 ,1074,1073,1248 }, {6203,322,803 ,1249,1250,144 }, + {4329,4379,4378 ,1247,567,375 }, {2376,6570,1263 ,1251,1252,1199 }, + {5059,1516,3177 ,1253,1254,1255 }, {5502,2208,5081 ,1256,1257,1258 }, + {3319,2245,2336 ,1259,1260,1261 }, {1352,1395,1351 ,1262,1164,1162 }, + {1569,1638,1604 ,1208,1210,1209 }, {2054,2053,1953 ,1263,1217,1216 }, + {3916,3959,3904 ,969,982,1264 }, {908,909,1190 ,1111,1109,1112 }, + {5117,2291,186 ,1113,1115,1265 }, {628,728,727 ,1177,1266,668 }, + {334,335,431 ,1267,1014,1268 }, {2123,2192,2191 ,1008,1175,1174 }, + {1290,1184,2009 ,795,797,1224 }, {1447,1407,1479 ,1136,1269,1270 }, + {3463,3494,3432 ,1271,736,929 }, {1857,540,1311 ,13,14,213 }, + {863,3484,3258 ,284,283,542 }, {4077,4076,4071 ,126,577,186 }, + {4255,4275,4328 ,165,699,166 }, {322,5,4 ,1250,350,538 }, {938,322,4 ,983,1250,538 }, + {5,315,264 ,350,352,984 }, {4,5,264 ,538,350,984 }, {4275,4329,4328 ,699,1247,166 }, + {6619,2245,1265 ,1272,1260,1273 }, {625,624,581 ,720,1099,1274 }, + {582,625,581 ,719,720,1274 }, {1781,1782,1803 ,1106,1105,1226 }, + {1779,1841,1840 ,1275,1276,198 }, {1918,2009,908 ,1110,1224,1111 }, + {2192,2193,170 ,1175,1007,794 }, {1145,1144,1045 ,1277,957,956 }, + {2122,2123,2191 ,1128,1008,1174 }, {524,525,580 ,1278,1279,1280 }, + {4518,1928,1879 ,1281,1282,1283 }, {926,1011,1010 ,1284,1285,1286 }, + {926,925,827 ,1284,1287,1220 }, {2068,2033,2034 ,891,1288,1289 }, + {6295,3283,8103 ,1290,262,1291 }, {3355,3354,3322 ,1292,1293,1182 }, + {1978,2068,2034 ,847,891,1289 }, {1233,1327,1232 ,1294,996,1295 }, + {221,2768,2541 ,1296,1297,1298 }, {264,315,4235 ,984,352,985 }, + {4235,4159,779 ,985,1299,193 }, {779,4159,709 ,193,1299,316 }, + {5056,5005,4950 ,1300,1301,1302 }, {3702,5520,1990 ,1303,1304,1305 }, + {6562,6299,4554 ,1306,760,1307 }, {1354,1430,1429 ,1308,1309,1206 }, + {1353,1354,1429 ,1205,1308,1206 }, {1430,1497,1496 ,1309,1310,1207 }, + {1429,1430,1496 ,1206,1309,1207 }, {1497,1570,1569 ,1310,1311,1208 }, + {1496,1497,1569 ,1207,1310,1208 }, {1570,1639,1638 ,1311,1312,1210 }, + {1569,1570,1638 ,1208,1311,1210 }, {1706,1705,1638 ,1313,1211,1210 }, + {1639,1706,1638 ,1312,1313,1210 }, {1706,1755,1754 ,1313,1314,1212 }, + {1705,1706,1754 ,1211,1313,1212 }, {1816,1815,1754 ,1315,1213,1212 }, + {1755,1816,1754 ,1314,1315,1212 }, {1863,1862,1815 ,1316,1214,1213 }, + {1816,1863,1815 ,1315,1316,1213 }, {1863,1889,1888 ,1316,1317,1215 }, + {1862,1863,1888 ,1214,1316,1215 }, {1889,1954,1953 ,1317,1318,1216 }, + {1888,1889,1953 ,1215,1317,1216 }, {2055,2054,1953 ,1319,1263,1216 }, + {1954,2055,1953 ,1318,1319,1216 }, {2076,2053,2054 ,1320,1217,1263 }, + {2055,2076,2054 ,1319,1320,1263 }, {2101,2100,2053 ,1321,1218,1217 }, + {2076,2101,2053 ,1320,1321,1217 }, {2168,2167,2100 ,1322,1323,1218 }, + {2101,2168,2100 ,1321,1322,1218 }, {2168,2090,2089 ,1322,1324,1325 }, + {2167,2168,2089 ,1323,1322,1325 }, {862,960,340 ,1225,1326,1107 }, + {908,340,909 ,1111,1107,1109 }, {1617,1684,1683 ,904,1327,1151 }, + {1047,906,2844 ,1328,1019,1329 }, {1326,1325,1231 ,1330,1331,1332 }, + {1143,1142,991 ,1333,1334,1335 }, {1232,1326,1231 ,1295,1330,1332 }, + {1188,999,862 ,799,798,1225 }, {524,580,623 ,1278,1280,1100 }, + {2193,2192,2123 ,1007,1175,1008 }, {433,485,484 ,861,863,1227 }, + {432,433,484 ,1222,861,1227 }, {339,387,239 ,1143,1102,1103 }, + {1234,1233,1142 ,1178,1294,1334 }, {108,2134,35 ,1336,1337,1338 }, + {790,742,743 ,1339,972,971 }, {1113,4213,4212 ,10,1070,895 }, + {540,1113,4211 ,14,10,215 }, {364,365,4362 ,1340,402,342 }, {365,139,4362 ,402,145,342 }, + {139,963,962 ,145,1341,233 }, {703,660,4145 ,100,387,101 }, {1168,4571,6311 ,1098,1342,1097 }, + {8079,3421,8104 ,55,1156,1343 }, {544,653,4454 ,195,318,1068 }, + {4569,2454,2272 ,1344,1345,1201 }, {6071,6377,6082 ,1346,1347,1348 }, + {6950,4981,5006 ,1349,1350,1351 }, {6562,5967,404 ,1306,1352,761 }, + {6570,6562,4554 ,1252,1306,1307 }, {1353,1267,1354 ,1205,1353,1308 }, + {1498,1571,1570 ,1354,1355,1311 }, {1497,1498,1570 ,1310,1354,1311 }, + {1570,1571,1639 ,1311,1355,1312 }, {1144,1145,1234 ,957,1277,1178 }, + {2033,2124,2123 ,1288,1006,1008 }, {3719,3515,959 ,1356,1357,1358 }, + {1547,1616,1615 ,860,1150,1359 }, {1546,1547,1615 ,1360,860,1359 }, + {2084,1048,1049 ,1361,1236,1362 }, {742,828,741 ,972,1219,1221 }, + {689,3315,3828 ,1363,944,1364 }, {526,525,484 ,718,1279,1227 }, + {485,526,484 ,863,718,1227 }, {1240,1290,1918 ,796,795,1110 }, + {828,926,827 ,1219,1284,1220 }, {35,751,6228 ,1338,1365,1366 }, + {744,790,743 ,1367,1339,971 }, {829,828,742 ,1368,1219,972 }, + {5801,5800,5764 ,700,702,537 }, {6577,4426,6837 ,97,1056,1057 }, + {469,468,367 ,988,1369,1370 }, {653,4053,4135 ,318,1371,1069 }, + {4454,653,4135 ,1068,318,1069 }, {376,1343,1564 ,379,378,368 }, + {2259,6646,8103 ,1131,1133,1291 }, {4179,4095,1805 ,251,349,1071 }, + {5843,1028,2958 ,1372,1373,1374 }, {5497,4530,2239 ,1375,1376,1377 }, + {2158,4569,2272 ,1160,1344,1201 }, {3582,3454,3660 ,1378,710,709 }, + {6299,6562,404 ,760,1306,761 }, {1465,1498,1497 ,1379,1354,1310 }, + {1430,1465,1497 ,1309,1379,1310 }, {632,1184,1290 ,1380,797,795 }, + {506,632,690 ,1381,1380,1382 }, {170,506,690 ,794,1381,1382 }, + {3903,3957,3956 ,296,295,1383 }, {1000,860,1876 ,1384,1385,1386 }, + {1782,1844,1843 ,1105,911,901 }, {1777,1839,1838 ,856,1138,425 }, + {1803,1782,1843 ,1226,1105,901 }, {401,448,2445 ,1387,1172,1388 }, + {6612,6648,6611 ,1389,1390,1391 }, {790,829,742 ,1339,1368,972 }, + {198,197,126 ,1392,1393,1394 }, {127,198,126 ,1395,1392,1394 }, + {368,367,197 ,1396,1370,1393 }, {198,368,197 ,1392,1396,1393 }, + {368,469,367 ,1396,988,1370 }, {614,615,1805 ,1397,766,1071 }, + {6646,6295,8103 ,1133,1290,1291 }, {1564,1595,4145 ,368,99,101 }, + {1094,839,1481 ,1398,52,194 }, {2408,5231,2219 ,1399,115,177 }, + {6562,3898,5967 ,1306,1400,1352 }, {1116,5497,2239 ,1401,1375,1377 }, + {7478,7477,7422 ,1402,1403,1404 }, {8020,2320,8019 ,1405,1406,1407 }, + {1355,1354,1267 ,1408,1308,1353 }, {1314,1355,1267 ,1409,1408,1353 }, + {1431,1430,1354 ,1410,1309,1308 }, {1355,1431,1354 ,1408,1410,1308 }, + {1431,1466,1465 ,1410,1411,1379 }, {1430,1431,1465 ,1309,1410,1379 }, + {1466,1499,1498 ,1411,1412,1354 }, {1465,1466,1498 ,1379,1411,1354 }, + {1499,1572,1571 ,1412,1413,1355 }, {1498,1499,1571 ,1354,1412,1355 }, + {1640,1639,1571 ,1414,1312,1355 }, {1572,1640,1571 ,1413,1414,1355 }, + {1640,1707,1706 ,1414,1415,1313 }, {1639,1640,1706 ,1312,1414,1313 }, + {1756,1755,1706 ,1416,1314,1313 }, {1707,1756,1706 ,1415,1416,1313 }, + {1756,1817,1816 ,1416,1417,1315 }, {1755,1756,1816 ,1314,1416,1315 }, + {1817,1818,1863 ,1417,1418,1316 }, {1816,1817,1863 ,1315,1417,1316 }, + {1818,1890,1889 ,1418,1419,1317 }, {1863,1818,1889 ,1316,1418,1317 }, + {1890,1955,1954 ,1419,1420,1318 }, {1889,1890,1954 ,1317,1419,1318 }, + {1955,2056,2055 ,1420,1421,1319 }, {1954,1955,2055 ,1318,1420,1319 }, + {2056,2057,2076 ,1421,1422,1320 }, {2055,2056,2076 ,1319,1421,1320 }, + {2102,2101,2076 ,1423,1321,1320 }, {2057,2102,2076 ,1422,1423,1320 }, + {2102,2169,2168 ,1423,1424,1322 }, {2101,2102,2168 ,1321,1423,1322 }, + {2169,2135,2090 ,1424,1425,1324 }, {2168,2169,2090 ,1322,1424,1324 }, + {631,2090,2135 ,1426,1324,1425 }, {992,906,1045 ,958,1019,956 }, + {902,901,814 ,1427,1428,1054 }, {1845,1874,1873 ,912,962,913 }, + {692,293,1182 ,1429,1430,1235 }, {1683,1777,1776 ,1151,856,1431 }, + {1910,940,1842 ,1223,197,196 }, {1739,1775,1801 ,1011,1432,1433 }, + {1236,1235,1145 ,1434,1179,1277 }, {1980,1979,1911 ,828,830,1101 }, + {991,1142,990 ,1335,1334,1435 }, {1848,1916,1915 ,1436,832,1437 }, + {3654,3712,3653 ,1438,1439,1440 }, {927,926,828 ,1441,1284,1219 }, + {3378,3377,3351 ,1442,1443,1444 }, {3534,8081,8072 ,942,1445,511 }, + {829,927,828 ,1368,1441,1219 }, {4329,4276,4355 ,1247,1446,1447 }, + {864,839,1094 ,840,52,1398 }, {193,768,864 ,1448,41,840 }, {4717,367,468 ,1449,1370,1369 }, + {2245,6344,1265 ,1260,1450,1273 }, {1263,6323,6509 ,1199,1451,1452 }, + {6163,6029,6066 ,1453,1454,1455 }, {1431,1499,1466 ,1410,1412,1411 }, + {1756,1818,1817 ,1416,1418,1417 }, {1981,2008,2037 ,1456,1457,1458 }, + {5587,5626,5586 ,1459,1460,1461 }, {595,596,646 ,939,1462,954 }, + {728,726,727 ,1266,669,668 }, {305,440,243 ,1463,1464,1465 }, + {692,1182,1183 ,1429,1235,1238 }, {154,692,1183 ,1466,1429,1238 }, + {1235,1236,1237 ,1179,1434,1467 }, {2128,2129,2149 ,1468,1469,1470 }, + {1012,1011,926 ,1471,1285,1284 }, {927,1012,926 ,1441,1471,1284 }, + {1012,1060,1011 ,1471,1472,1285 }, {1104,1103,1011 ,1473,1474,1285 }, + {1060,1104,1011 ,1472,1473,1285 }, {1104,1157,1103 ,1473,1475,1474 }, + {1157,1040,1201 ,1475,1476,1477 }, {874,36,3866 ,636,1478,1479 }, + {1103,1157,1201 ,1474,1475,1477 }, {409,150,89 ,1480,1481,1482 }, + {880,4093,1441 ,1483,1484,1485 }, {4109,4149,4090 ,576,1486,1487 }, + {4613,278,4590 ,1488,1489,1490 }, {3898,4987,5967 ,1400,1491,1352 }, + {4568,185,3247 ,1492,1493,1494 }, {1955,2057,2056 ,1420,1422,1421 }, + {484,525,524 ,1227,1279,1278 }, {431,432,483 ,1268,1222,1228 }, + {483,484,524 ,1228,1227,1278 }, {596,647,646 ,1462,973,954 }, + {1687,1781,1780 ,1104,1106,900 }, {1327,1404,1403 ,996,975,974 }, + {1190,909,1189 ,1112,1109,1495 }, {2585,2650,3453 ,875,914,917 }, + {933,692,154 ,1496,1429,1466 }, {155,933,154 ,1497,1496,1466 }, + {990,1141,1140 ,1435,1498,1499 }, {1235,1370,1329 ,1179,1500,1134 }, + {1000,1373,1515 ,1384,1501,1502 }, {1978,1977,1867 ,847,1503,1504 }, + {2130,2198,2197 ,1505,1506,1507 }, {1224,1040,1906 ,1508,1476,1509 }, + {2036,2035,1978 ,887,889,847 }, {3286,8104,79 ,56,1343,1510 }, + {2007,2036,1978 ,829,887,847 }, {3855,3854,3812 ,1511,1512,1513 }, + {3381,3380,3354 ,871,1514,1293 }, {3813,3855,3812 ,1515,1511,1513 }, + {2651,2746,3768 ,1516,1517,1518 }, {1463,3258,3681 ,220,542,691 }, + {1874,1847,1913 ,962,1519,963 }, {2082,2081,2036 ,1520,888,887 }, + {6432,1941,3252 ,1032,1034,781 }, {369,470,469 ,1521,1522,988 }, + {368,369,469 ,1396,1521,988 }, {507,505,469 ,1523,986,988 }, + {470,507,469 ,1522,1523,988 }, {4213,4120,294 ,1070,23,945 }, + {277,1992,781 ,85,834,158 }, {1494,3862,1428 ,1083,550,1126 }, + {4569,2808,2454 ,1344,522,1345 }, {2245,6619,2336 ,1260,1272,1261 }, + {6480,5276,5350 ,1524,1525,1526 }, {7239,7261,7219 ,1527,1528,1529 }, + {1356,1432,1431 ,1530,1531,1410 }, {1355,1356,1431 ,1408,1530,1410 }, + {1500,1499,1431 ,1532,1412,1410 }, {1432,1500,1431 ,1531,1532,1410 }, + {1573,1572,1499 ,1533,1413,1412 }, {1500,1573,1499 ,1532,1533,1412 }, + {1573,1641,1640 ,1533,1534,1414 }, {1572,1573,1640 ,1413,1533,1414 }, + {1708,1707,1640 ,1535,1415,1414 }, {1641,1708,1640 ,1534,1535,1414 }, + {1708,1757,1756 ,1535,1536,1416 }, {1707,1708,1756 ,1415,1535,1416 }, + {1757,1796,1756 ,1536,1537,1416 }, {1796,1819,1818 ,1537,1538,1418 }, + {1756,1796,1818 ,1416,1537,1418 }, {1891,1890,1818 ,1539,1419,1418 }, + {1819,1891,1818 ,1538,1539,1418 }, {1956,1955,1890 ,1540,1420,1419 }, + {1891,1956,1890 ,1539,1540,1419 }, {1956,2058,2057 ,1540,1541,1422 }, + {1955,1956,2057 ,1420,1540,1422 }, {2058,2103,2102 ,1541,1542,1423 }, + {2057,2058,2102 ,1422,1541,1423 }, {2103,2170,2169 ,1542,1543,1424 }, + {2102,2103,2169 ,1423,1542,1424 }, {2226,2135,2169 ,1544,1425,1424 }, + {2170,2226,2169 ,1543,1544,1424 }, {2226,2152,2136 ,1544,1545,1546 }, + {2135,2226,2136 ,1425,1544,1546 }, {1184,1188,2009 ,797,799,1224 }, + {1980,1981,2036 ,828,1456,887 }, {1985,729,730 ,1547,1548,1549 }, + {2197,933,155 ,1507,1496,1497 }, {2196,2197,155 ,1550,1507,1497 }, + {2037,2038,2082 ,1458,1551,1520 }, {2083,2128,2127 ,1552,1468,1553 }, + {2038,2083,2082 ,1551,1552,1520 }, {2198,2199,2216 ,1506,1554,1555 }, + {992,1144,991 ,958,957,1335 }, {6393,3358,3305 ,1556,880,879 }, + {1911,1978,1867 ,1101,847,1504 }, {1263,4554,6323 ,1199,1307,1451 }, + {337,385,336 ,866,864,1013 }, {1549,1548,1476 ,903,902,859 }, + {1477,1476,1404 ,1045,859,975 }, {3664,296,3475 ,1557,1558,1559 }, + {2746,3566,3768 ,1517,1146,1518 }, {526,625,582 ,718,720,719 }, + {2122,2031,2032 ,1128,1560,1127 }, {2148,2147,2126 ,1561,1562,890 }, + {1618,1685,1684 ,1563,1005,1327 }, {525,581,580 ,1279,1274,1280 }, + {3987,4227,3946 ,1229,1564,1230 }, {294,4120,4375 ,945,23,312 }, + {4548,4568,3247 ,589,1492,1494 }, {5113,6098,5914 ,141,1565,1566 }, + {1501,1500,1432 ,1567,1532,1531 }, {2259,8103,6351 ,1131,1291,1568 }, + {860,1238,1183 ,1385,1569,1238 }, {2082,2083,2127 ,1520,1552,1553 }, + {2128,2149,2148 ,1468,1470,1561 }, {2149,2197,2196 ,1470,1507,1550 }, + {2127,2128,2148 ,1553,1468,1561 }, {2148,2149,2196 ,1561,1470,1550 }, + {2130,2131,2198 ,1505,656,1506 }, {724,723,624 ,722,1053,1099 }, + {2199,2200,2216 ,1554,1570,1555 }, {1684,1685,1721 ,1327,1005,854 }, + {1683,1721,1777 ,1151,854,856 }, {1618,1619,1685 ,1563,1003,1005 }, + {240,4183,260 ,1571,1572,1573 }, {3263,2746,2651 ,252,1517,1516 }, + {392,305,2190 ,857,1463,1574 }, {3915,3903,3956 ,1575,296,1383 }, + {2869,2832,2868 ,1576,638,1577 }, {1913,1981,1980 ,963,1456,828 }, + {1885,4698,1810 ,778,1578,779 }, {649,444,492 ,1579,922,1580 }, + {399,768,193 ,269,41,1448 }, {1591,399,1032 ,1072,269,1581 }, + {1032,399,193 ,1581,269,1448 }, {4375,399,1591 ,312,269,1072 }, + {3777,4261,5733 ,1582,1583,1584 }, {1492,1749,5075 ,1585,555,1586 }, + {5889,6163,6066 ,1587,1453,1455 }, {5511,5512,5548 ,1588,1589,1590 }, + {3252,6553,6432 ,781,780,1032 }, {1757,1820,1819 ,1536,1591,1538 }, + {1796,1757,1819 ,1537,1536,1538 }, {1820,1891,1819 ,1591,1539,1538 }, + {2129,2130,2149 ,1469,1505,1470 }, {1913,1914,1981 ,963,1000,1456 }, + {1982,1983,2008 ,1002,1001,1457 }, {1983,2038,2037 ,1001,1551,1458 }, + {1981,1982,2008 ,1456,1002,1457 }, {2008,1983,2037 ,1457,1001,1458 }, + {2067,2005,2006 ,1592,1593,1181 }, {1867,1977,2390 ,1504,1503,1594 }, + {2131,2199,2198 ,656,1554,1506 }, {625,724,624 ,720,722,1099 }, + {741,740,644 ,1221,1595,1176 }, {1239,1240,1190 ,1596,796,1112 }, + {645,741,644 ,940,1221,1176 }, {547,548,644 ,1170,938,1176 }, + {690,632,1290 ,1382,1380,795 }, {2034,2033,1977 ,1289,1288,1503 }, + {2120,2121,2189 ,1597,1598,1599 }, {440,394,243 ,1464,1600,1465 }, + {2609,744,691 ,1130,1367,1173 }, {1779,1780,1842 ,1275,900,196 }, + {4035,4036,2151 ,1601,1602,1603 }, {1088,1186,1452 ,1604,1605,1606 }, + {6347,7254,6348 ,451,450,1607 }, {271,370,369 ,1608,1609,1521 }, + {370,418,369 ,1609,1610,1521 }, {418,471,470 ,1610,1611,1522 }, + {508,507,470 ,1612,1523,1522 }, {471,508,470 ,1611,1612,1522 }, + {566,565,507 ,1613,1614,1523 }, {508,566,507 ,1612,1613,1523 }, + {755,4084,4113 ,1615,205,207 }, {2030,5203,2698 ,770,102,105 }, + {229,1112,356 ,221,292,310 }, {7826,7853,7801 ,1616,1617,1618 }, + {1030,978,4991 ,1619,1620,1621 }, {6736,6790,6754 ,1622,711,713 }, + {6092,6176,5940 ,1623,1624,1625 }, {1357,1356,1315 ,1626,1530,1627 }, + {4576,4596,7524 ,1628,1629,1630 }, {1357,1433,1432 ,1626,1631,1531 }, + {1356,1357,1432 ,1530,1626,1531 }, {1433,1502,1501 ,1631,1632,1567 }, + {1432,1433,1501 ,1531,1631,1567 }, {1535,1500,1501 ,1633,1532,1567 }, + {1502,1535,1501 ,1632,1633,1567 }, {1574,1573,1500 ,1634,1533,1532 }, + {1535,1574,1500 ,1633,1634,1532 }, {1574,1642,1641 ,1634,1635,1534 }, + {1573,1574,1641 ,1533,1634,1534 }, {1642,1709,1708 ,1635,1636,1535 }, + {1641,1642,1708 ,1534,1635,1535 }, {1709,1758,1757 ,1636,1637,1536 }, + {1708,1709,1757 ,1535,1636,1536 }, {1758,1821,1820 ,1637,1638,1591 }, + {1757,1758,1820 ,1536,1637,1591 }, {1892,1891,1820 ,1639,1539,1591 }, + {1821,1892,1820 ,1638,1639,1591 }, {1957,1956,1891 ,1640,1540,1539 }, + {1892,1957,1891 ,1639,1640,1539 }, {1957,2016,1956 ,1640,1641,1540 }, + {2016,2059,2058 ,1641,1642,1541 }, {1956,2016,2058 ,1540,1641,1541 }, + {2104,2103,2058 ,1643,1542,1541 }, {2059,2104,2058 ,1642,1643,1541 }, + {2171,2170,2103 ,1644,1543,1542 }, {2104,2171,2103 ,1643,1644,1542 }, + {2171,2227,2226 ,1644,1645,1544 }, {2170,2171,2226 ,1543,1644,1544 }, + {2227,2161,2152 ,1645,1646,1545 }, {2226,2227,2152 ,1544,1645,1545 }, + {2161,391,2152 ,1646,1647,1545 }, {776,782,777 ,1120,1648,421 }, + {1914,1982,1981 ,1000,1002,1456 }, {1142,1141,990 ,1334,1498,1435 }, + {1403,1475,1474 ,974,858,1649 }, {3645,3644,3591 ,1650,1651,1652 }, + {450,548,547 ,1653,938,1170 }, {548,645,644 ,938,940,1176 }, + {1240,1239,305 ,796,1596,1463 }, {1617,1618,1684 ,904,1563,1327 }, + {221,241,240 ,1296,314,1571 }, {102,221,240 ,432,1296,1571 }, + {109,2039,349 ,1654,1655,566 }, {7747,7745,7746 ,331,650,332 }, + {1104,1040,1157 ,1473,1476,1475 }, {3913,2030,2698 ,841,770,105 }, + {5641,5642,5657 ,510,526,48 }, {1463,4326,3258 ,220,219,542 }, + {5337,5338,4914 ,572,63,65 }, {639,715,950 ,291,168,330 }, {4222,4131,4251 ,530,1656,132 }, + {1357,1315,1316 ,1626,1627,1657 }, {1502,1574,1535 ,1632,1634,1633 }, + {1709,1759,1758 ,1636,1658,1637 }, {6686,3534,8070 ,750,942,941 }, + {1785,1849,1848 ,1659,1660,1436 }, {2216,968,293 ,1555,1661,1430 }, + {1784,1785,1848 ,1662,1659,1436 }, {1875,1917,1916 ,1663,833,832 }, + {1848,1875,1916 ,1436,1663,832 }, {449,450,547 ,1169,1653,1170 }, + {1106,1203,1202 ,1664,337,339 }, {1326,1327,1403 ,1330,996,974 }, + {687,1744,2088 ,1665,1666,1667 }, {596,595,548 ,1462,939,938 }, + {1685,1722,1721 ,1005,1668,854 }, {2216,2200,968 ,1555,1570,1661 }, + {102,1553,221 ,432,1669,1296 }, {7194,7208,7225 ,1670,1671,1672 }, + {1144,1234,1142 ,957,1178,1334 }, {471,566,508 ,1611,1613,1612 }, + {200,1348,714 ,244,148,265 }, {1348,639,714 ,148,291,265 }, {737,229,356 ,150,221,310 }, + {1033,896,888 ,306,256,238 }, {4483,4525,4482 ,549,623,583 }, + {4473,4497,4520 ,270,91,1673 }, {826,189,738 ,507,503,1674 }, + {1822,1821,1758 ,1675,1638,1637 }, {1759,1822,1758 ,1658,1675,1637 }, + {1821,1822,1892 ,1638,1675,1639 }, {1957,2059,2016 ,1640,1642,1641 }, + {2172,2171,2104 ,1676,1644,1643 }, {1849,1875,1848 ,1660,1663,1436 }, + {2033,2032,1976 ,1288,1127,1180 }, {1233,1234,1327 ,1294,1178,996 }, + {339,436,387 ,1143,1142,1102 }, {221,748,241 ,1296,1677,314 }, + {284,4283,1241 ,139,1678,313 }, {2893,5307,4393 ,1679,461,463 }, + {272,371,370 ,1680,1681,1609 }, {271,272,370 ,1608,1680,1609 }, + {371,419,418 ,1681,1682,1610 }, {370,371,418 ,1609,1681,1610 }, + {419,472,471 ,1682,1683,1611 }, {418,419,471 ,1610,1682,1611 }, + {567,566,471 ,1684,1613,1611 }, {472,567,471 ,1683,1684,1611 }, + {715,639,497 ,168,291,290 }, {4483,1652,4525 ,549,170,623 }, + {1021,3,4187 ,289,329,502 }, {4497,4534,136 ,91,403,1685 }, {4525,1673,4513 ,623,1686,624 }, + {1274,1275,1316 ,1687,1688,1657 }, {1275,1358,1357 ,1688,1689,1626 }, + {1316,1275,1357 ,1657,1688,1626 }, {1434,1433,1357 ,1690,1631,1626 }, + {1358,1434,1357 ,1689,1690,1626 }, {1503,1502,1433 ,1691,1632,1631 }, + {1434,1503,1433 ,1690,1691,1631 }, {1575,1574,1502 ,1692,1634,1632 }, + {1503,1575,1502 ,1691,1692,1632 }, {1643,1642,1574 ,1693,1635,1634 }, + {1575,1643,1574 ,1692,1693,1634 }, {1643,1710,1709 ,1693,1694,1636 }, + {1642,1643,1709 ,1635,1693,1636 }, {1710,1711,1709 ,1694,1695,1636 }, + {1711,1760,1759 ,1695,1696,1658 }, {1709,1711,1759 ,1636,1695,1658 }, + {1823,1822,1759 ,1697,1675,1658 }, {1760,1823,1759 ,1696,1697,1658 }, + {1823,1893,1892 ,1697,1698,1639 }, {1822,1823,1892 ,1675,1697,1639 }, + {1893,1958,1957 ,1698,1699,1640 }, {1892,1893,1957 ,1639,1698,1640 }, + {2060,2059,1957 ,1700,1642,1640 }, {1958,2060,1957 ,1699,1700,1640 }, + {2060,2077,2059 ,1700,1701,1642 }, {2105,2104,2059 ,1702,1643,1642 }, + {2077,2105,2059 ,1701,1702,1642 }, {2173,2172,2104 ,1703,1676,1643 }, + {2105,2173,2104 ,1702,1703,1643 }, {2173,2174,2171 ,1703,1704,1644 }, + {2172,2173,2171 ,1676,1703,1644 }, {2174,2228,2227 ,1704,1705,1645 }, + {2171,2174,2227 ,1644,1704,1645 }, {2228,2214,2161 ,1705,1706,1646 }, + {2227,2228,2161 ,1645,1705,1646 }, {680,391,2161 ,1707,1647,1646 }, + {2214,680,2161 ,1706,1707,1646 }, {680,1624,1623 ,1707,1708,1709 }, + {391,680,1623 ,1647,1707,1709 }, {8061,8097,8082 ,453,1710,454 }, + {2038,2069,2083 ,1551,1711,1552 }, {1778,1779,1840 ,855,1275,198 }, + {4426,4021,8100 ,1056,1712,1713 }, {2121,2122,2190 ,1598,1128,1574 }, + {170,690,1290 ,794,1382,795 }, {7977,7976,7927 ,1714,1715,1716 }, + {1847,1914,1913 ,1519,1000,963 }, {242,1688,1389 ,1717,1718,315 }, + {241,242,1389 ,314,1717,315 }, {1688,133,1241 ,1718,1719,313 }, + {1389,1688,1241 ,315,1718,313 }, {3451,3516,2582 ,1720,1721,873 }, + {1912,1980,1911 ,964,828,1101 }, {204,272,271 ,1722,1680,1608 }, + {568,567,472 ,1723,1684,1683 }, {4224,896,1033 ,309,256,306 }, + {888,1006,799 ,238,234,392 }, {4520,4497,136 ,1673,91,1685 }, + {7928,7927,7876 ,1724,1716,1725 }, {1576,1575,1503 ,1726,1692,1691 }, + {1576,1643,1575 ,1726,1693,1692 }, {1643,1711,1710 ,1693,1695,1694 }, + {8124,3271,2810 ,1141,1727,1728 }, {905,904,858 ,1729,1730,1731 }, + {2126,2147,2146 ,890,1562,1732 }, {859,905,858 ,1733,1729,1731 }, + {1841,1842,1840 ,1276,196,198 }, {1619,1618,1549 ,1003,1563,903 }, + {815,903,902 ,999,1734,1427 }, {2541,242,241 ,1298,1717,314 }, + {883,76,146 ,119,689,504 }, {3516,3537,2582 ,1721,1735,873 }, + {3477,5785,4162 ,1736,1737,1738 }, {5480,4527,4162 ,1739,1740,1738 }, + {999,1985,960 ,798,1547,1326 }, {272,204,205 ,1680,1722,1741 }, + {4106,8142,8078 ,1742,1743,1744 }, {4483,4482,4444 ,549,583,582 }, + {174,1147,1422 ,169,202,525 }, {136,4534,1628 ,1685,403,118 }, + {189,4187,4184 ,503,502,898 }, {1644,1643,1576 ,1745,1693,1726 }, + {1643,1644,1711 ,1693,1745,1695 }, {2105,2174,2173 ,1702,1704,1703 }, + {1726,1785,1784 ,1746,1659,1662 }, {1725,1726,1784 ,1747,1746,1662 }, + {1838,1839,4427 ,425,1138,426 }, {2031,2067,2029 ,1560,1592,1748 }, + {273,372,371 ,1749,1750,1681 }, {372,420,419 ,1750,1751,1682 }, + {371,372,419 ,1681,1750,1682 }, {420,473,472 ,1751,1752,1683 }, + {419,420,472 ,1682,1751,1683 }, {569,568,472 ,1753,1723,1683 }, + {473,569,472 ,1752,1753,1683 }, {569,665,664 ,1753,1754,1755 }, + {568,569,664 ,1723,1753,1755 }, {1422,997,826 ,525,328,507 }, + {826,997,1021 ,507,328,289 }, {1034,1127,1172 ,1756,1757,1758 }, + {1172,1127,1221 ,1758,1757,1759 }, {1127,1276,1275 ,1757,1760,1688 }, + {1221,1127,1275 ,1759,1757,1688 }, {1276,1359,1358 ,1760,1761,1689 }, + {1275,1276,1358 ,1688,1760,1689 }, {1359,1435,1434 ,1761,1762,1690 }, + {1358,1359,1434 ,1689,1761,1690 }, {1504,1503,1434 ,1763,1691,1690 }, + {1435,1504,1434 ,1762,1763,1690 }, {1577,1576,1503 ,1764,1726,1691 }, + {1504,1577,1503 ,1763,1764,1691 }, {1645,1644,1576 ,1765,1745,1726 }, + {1577,1645,1576 ,1764,1765,1726 }, {1645,1712,1711 ,1765,1766,1695 }, + {1644,1645,1711 ,1745,1765,1695 }, {1761,1760,1711 ,1767,1696,1695 }, + {1712,1761,1711 ,1766,1767,1695 }, {1761,1824,1823 ,1767,1768,1697 }, + {1760,1761,1823 ,1696,1767,1697 }, {1894,1893,1823 ,1769,1698,1697 }, + {1824,1894,1823 ,1768,1769,1697 }, {1894,1959,1958 ,1769,1770,1699 }, + {1893,1894,1958 ,1698,1769,1699 }, {1959,2061,2060 ,1770,1771,1700 }, + {1958,1959,2060 ,1699,1770,1700 }, {2061,2062,2077 ,1771,1772,1701 }, + {2060,2061,2077 ,1700,1771,1701 }, {2062,2106,2105 ,1772,1773,1702 }, + {2077,2062,2105 ,1701,1772,1702 }, {2106,2175,2174 ,1773,1774,1704 }, + {2105,2106,2174 ,1702,1773,1704 }, {2175,2229,2228 ,1774,1775,1705 }, + {2174,2175,2228 ,1704,1774,1705 }, {2229,1559,2214 ,1775,1776,1706 }, + {2228,2229,2214 ,1705,1775,1706 }, {633,680,2214 ,1777,1707,1706 }, + {1559,633,2214 ,1776,1777,1706 }, {633,1624,680 ,1777,1708,1707 }, + {1187,1186,1624 ,1778,1605,1708 }, {633,1187,1624 ,1777,1778,1708 }, + {1187,1452,1186 ,1778,1606,1605 }, {1187,1919,1452 ,1778,1779,1606 }, + {8110,5521,8125 ,1140,751,752 }, {1326,1403,1402 ,1330,974,1780 }, + {1657,1658,1725 ,1781,1782,1747 }, {1688,134,133 ,1718,1783,1719 }, + {134,80,284 ,1783,140,139 }, {133,134,284 ,1719,1783,139 }, {8125,8071,8068 ,752,738,1784 }, + {272,273,371 ,1680,1749,1681 }, {493,175,492 ,1785,211,1580 }, + {6384,3362,3387 ,1786,1200,1787 }, {1034,1035,1127 ,1756,1788,1757 }, + {1959,2062,2061 ,1770,1772,1771 }, {3712,3758,3711 ,1439,1789,1790 }, + {3445,3648,3596 ,1791,734,1792 }, {1017,3513,3452 ,1793,441,1794 }, + {108,35,36 ,1336,1338,1478 }, {3148,3008,2743 ,814,546,923 }, + {1658,1726,1725 ,1782,1746,1747 }, {5057,8096,6122 ,1795,1796,1797 }, + {3456,3405,2920 ,1145,1798,1799 }, {5237,5600,2086 ,1800,1801,1802 }, + {5151,5171,6114 ,1803,1804,1805 }, {1549,1618,1617 ,903,1563,904 }, + {982,1035,1034 ,1806,1788,1756 }, {943,982,1034 ,1807,1806,1756 }, + {1959,2017,2062 ,1770,1808,1772 }, {3687,2743,3873 ,924,923,1809 }, + {3597,3445,3596 ,1810,1791,1792 }, {2121,2120,2028 ,1598,1597,1811 }, + {3537,2072,2474 ,1735,1812,874 }, {581,624,623 ,1274,1099,1100 }, + {903,990,989 ,1734,1435,1813 }, {2148,2196,2147 ,1561,1550,1562 }, + {453,82,80 ,1814,138,140 }, {5137,4631,5136 ,1815,1816,1817 }, + {1477,1549,1476 ,1045,903,859 }, {4367,4390,4343 ,228,230,1818 }, + {412,4150,1793 ,358,320,319 }, {274,373,372 ,1819,1820,1750 }, + {273,274,372 ,1749,1819,1750 }, {373,374,420 ,1820,1821,1751 }, + {372,373,420 ,1750,1820,1751 }, {374,474,473 ,1821,1822,1752 }, + {420,374,473 ,1751,1821,1752 }, {474,570,569 ,1822,1823,1753 }, + {473,474,569 ,1752,1822,1753 }, {570,666,665 ,1823,1824,1754 }, + {569,570,665 ,1753,1823,1754 }, {666,762,761 ,1824,1825,1826 }, + {665,666,761 ,1754,1824,1826 }, {762,846,845 ,1825,1827,1828 }, + {761,762,845 ,1826,1825,1828 }, {846,944,943 ,1827,1829,1807 }, + {845,846,943 ,1828,1827,1807 }, {943,944,982 ,1807,1829,1806 }, + {944,1036,1035 ,1829,1830,1788 }, {982,944,1035 ,1806,1829,1788 }, + {1036,1128,1127 ,1830,1831,1757 }, {1035,1036,1127 ,1788,1830,1757 }, + {1277,1276,1127 ,1832,1760,1757 }, {1128,1277,1127 ,1831,1832,1757 }, + {1360,1359,1276 ,1833,1761,1760 }, {1277,1360,1276 ,1832,1833,1760 }, + {1360,1436,1435 ,1833,1834,1762 }, {1359,1360,1435 ,1761,1833,1762 }, + {1505,1504,1435 ,1835,1763,1762 }, {1436,1505,1435 ,1834,1835,1762 }, + {1505,1578,1577 ,1835,1836,1764 }, {1504,1505,1577 ,1763,1835,1764 }, + {1578,1646,1645 ,1836,1837,1765 }, {1577,1578,1645 ,1764,1836,1765 }, + {1646,1713,1712 ,1837,1838,1766 }, {1645,1646,1712 ,1765,1837,1766 }, + {1713,1762,1761 ,1838,1839,1767 }, {1712,1713,1761 ,1766,1838,1767 }, + {1825,1824,1761 ,1840,1768,1767 }, {1762,1825,1761 ,1839,1840,1767 }, + {1825,1895,1894 ,1840,1841,1769 }, {1824,1825,1894 ,1768,1840,1769 }, + {1895,1960,1959 ,1841,1842,1770 }, {1894,1895,1959 ,1769,1841,1770 }, + {1960,1997,1959 ,1842,1843,1770 }, {1959,1997,2017 ,1770,1843,1808 }, + {1997,2063,2062 ,1843,1844,1772 }, {2017,1997,2062 ,1808,1843,1772 }, + {2063,2107,2106 ,1844,1845,1773 }, {2062,2063,2106 ,1772,1844,1773 }, + {2107,2108,2106 ,1845,1846,1773 }, {2108,2176,2175 ,1846,1847,1774 }, + {2106,2108,2175 ,1773,1846,1774 }, {2176,2230,2229 ,1847,1848,1775 }, + {2175,2176,2229 ,1774,1847,1775 }, {2230,866,1559 ,1848,1849,1776 }, + {2229,2230,1559 ,1775,1848,1776 }, {866,550,633 ,1849,1850,1777 }, + {1559,866,633 ,1776,1849,1777 }, {550,1244,1187 ,1850,575,1778 }, + {633,550,1187 ,1777,1850,1778 }, {1244,1367,1187 ,575,574,1778 }, + {2195,341,2194 ,1851,1852,1853 }, {7275,7254,7253 ,1854,450,1855 }, + {1977,2033,1976 ,1503,1288,1180 }, {2582,3537,2474 ,873,1735,874 }, + {902,903,901 ,1427,1734,1428 }, {1232,1231,1140 ,1295,1332,1499 }, + {1141,1232,1140 ,1498,1295,1499 }, {2082,2127,2081 ,1520,1553,888 }, + {51,301,453 ,1856,1857,1814 }, {80,51,453 ,140,1856,1814 }, {94,82,301 ,1858,138,1857 }, + {1693,3425,3283 ,264,1859,262 }, {5219,5240,5218 ,1860,481,483 }, + {3344,4221,592 ,1861,1862,1863 }, {772,891,4540 ,18,189,19 }, + {274,374,373 ,1819,1821,1820 }, {4314,1561,1525 ,1864,1865,1866 }, + {3591,3644,3590 ,1652,1651,1867 }, {2743,3931,3873 ,923,1868,1809 }, + {2125,2126,2146 ,892,890,1732 }, {723,815,814 ,1053,999,1054 }, + {2196,155,165 ,1550,1497,1869 }, {2195,2196,165 ,1851,1550,1869 }, + {2939,80,134 ,1870,140,1783 }, {5680,2453,5698 ,50,42,44 }, {274,320,374 ,1819,1871,1821 }, + {2063,2108,2107 ,1844,1846,1845 }, {550,1408,1244 ,1850,1872,575 }, + {1367,2070,614 ,574,573,1397 }, {7510,7509,7452 ,1873,1874,1875 }, + {859,858,816 ,1733,1731,998 }, {305,243,2189 ,1463,1465,1599 }, + {51,355,301 ,1856,1876,1857 }, {94,157,196 ,1858,1877,152 }, + {2743,3008,3944 ,923,546,729 }, {5180,5179,5136 ,1878,1879,1817 }, + {5658,2453,5680 ,49,42,50 }, {770,2352,805 ,275,390,389 }, {4064,4069,3317 ,1880,1881,1882 }, + {375,475,474 ,1883,1884,1822 }, {374,375,474 ,1821,1883,1822 }, + {571,570,474 ,1885,1823,1822 }, {475,571,474 ,1884,1885,1822 }, + {667,666,570 ,1886,1824,1823 }, {571,667,570 ,1885,1886,1823 }, + {763,762,666 ,1887,1825,1824 }, {667,763,666 ,1886,1887,1824 }, + {847,846,762 ,1888,1827,1825 }, {763,847,762 ,1887,1888,1825 }, + {945,944,846 ,1889,1829,1827 }, {847,945,846 ,1888,1889,1827 }, + {1037,1036,944 ,1890,1830,1829 }, {945,1037,944 ,1889,1890,1829 }, + {1129,1128,1036 ,1891,1831,1830 }, {1037,1129,1036 ,1890,1891,1830 }, + {1129,1278,1277 ,1891,1892,1832 }, {1128,1129,1277 ,1831,1891,1832 }, + {1361,1360,1277 ,1893,1833,1832 }, {1278,1361,1277 ,1892,1893,1832 }, + {1437,1436,1360 ,1894,1834,1833 }, {1361,1437,1360 ,1893,1894,1833 }, + {1506,1505,1436 ,1895,1835,1834 }, {1437,1506,1436 ,1894,1895,1834 }, + {1506,1579,1578 ,1895,1896,1836 }, {1505,1506,1578 ,1835,1895,1836 }, + {1579,1647,1646 ,1896,1897,1837 }, {1578,1579,1646 ,1836,1896,1837 }, + {1647,1714,1713 ,1897,1898,1838 }, {1646,1647,1713 ,1837,1897,1838 }, + {1714,1763,1762 ,1898,1899,1839 }, {1713,1714,1762 ,1838,1898,1839 }, + {1826,1825,1762 ,1900,1840,1839 }, {1763,1826,1762 ,1899,1900,1839 }, + {1896,1895,1825 ,1901,1841,1840 }, {1826,1896,1825 ,1900,1901,1840 }, + {1961,1960,1895 ,1902,1842,1841 }, {1896,1961,1895 ,1901,1902,1841 }, + {1961,1997,1960 ,1902,1843,1842 }, {1961,2018,1997 ,1902,1903,1843 }, + {2018,2064,2063 ,1903,1904,1844 }, {1997,2018,2063 ,1843,1903,1844 }, + {2109,2108,2063 ,1905,1846,1844 }, {2064,2109,2063 ,1904,1905,1844 }, + {2109,2177,2176 ,1905,1906,1847 }, {2108,2109,2176 ,1846,1905,1847 }, + {2177,780,2230 ,1906,1907,1848 }, {2176,2177,2230 ,1847,1906,1848 }, + {2230,780,866 ,1848,1907,1849 }, {780,389,550 ,1907,1908,1850 }, + {866,780,550 ,1849,1907,1850 }, {389,1409,1408 ,1908,1909,1872 }, + {550,389,1408 ,1850,1908,1872 }, {1409,1095,1244 ,1909,1910,575 }, + {1408,1409,1244 ,1872,1909,575 }, {1095,2070,1244 ,1910,573,575 }, + {3364,3363,3330 ,1184,1911,1185 }, {5817,5816,5800 ,701,545,702 }, + {3869,3890,3854 ,1912,1913,1512 }, {3975,2812,3684 ,728,1914,1915 }, + {1547,1548,1616 ,860,902,1150 }, {723,722,623 ,1053,1052,1100 }, + {905,992,904 ,1729,958,1730 }, {250,157,94 ,1916,1877,1858 }, + {301,250,94 ,1857,1916,1858 }, {3944,3975,3684 ,729,728,1915 }, + {1455,297,140 ,1917,1918,1919 }, {1192,398,117 ,8,15,9 }, {946,945,847 ,1920,1889,1888 }, + {1764,1826,1763 ,1921,1900,1899 }, {1065,921,1340 ,821,1922,822 }, + {2192,170,392 ,1175,794,857 }, {991,990,903 ,1335,1435,1734 }, + {2149,2130,2197 ,1470,1505,1507 }, {2746,3263,8139 ,1517,252,1923 }, + {173,398,1192 ,12,15,8 }, {772,809,891 ,18,20,189 }, {4514,4483,4465 ,259,549,257 }, + {1038,1037,945 ,1924,1890,1889 }, {946,1038,945 ,1920,1924,1889 }, + {1038,1129,1037 ,1924,1891,1890 }, {1714,1764,1763 ,1898,1921,1899 }, + {1827,1826,1764 ,1925,1900,1921 }, {2110,2109,2064 ,1926,1905,1904 }, + {3997,4039,4038 ,380,382,1927 }, {3996,3997,4038 ,1928,380,1927 }, + {3999,4041,4040 ,1023,1063,460 }, {1106,1204,1203 ,1664,338,337 }, + {906,1046,1045 ,1019,1929,956 }, {909,910,1189 ,1109,1930,1495 }, + {3572,3590,3589 ,1931,1867,1932 }, {1302,1303,13 ,909,1933,910 }, + {1844,1873,1912 ,911,913,964 }, {340,4223,593 ,1107,1934,1108 }, + {1850,251,1517 ,601,603,600 }, {1334,3871,138 ,1935,397,434 }, + {4202,562,700 ,260,1936,255 }, {4682,1376,3899 ,1191,1937,1938 }, + {106,5139,5138 ,1073,1939,1940 }, {1245,57,735 ,36,222,224 }, + {463,467,360 ,1941,346,86 }, {3749,3662,4011 ,652,1942,395 }, + {1135,1285,5067 ,1943,1944,1945 }, {6898,6563,8063 ,1946,464,465 }, + {572,611,571 ,1947,1948,1885 }, {668,667,571 ,1949,1886,1885 }, + {611,668,571 ,1948,1949,1885 }, {764,763,667 ,1950,1887,1886 }, + {668,764,667 ,1949,1950,1886 }, {848,847,763 ,1951,1888,1887 }, + {764,848,763 ,1950,1951,1887 }, {947,946,847 ,1952,1920,1888 }, + {848,947,847 ,1951,1952,1888 }, {1039,1038,946 ,1953,1924,1920 }, + {947,1039,946 ,1952,1953,1920 }, {1039,1129,1038 ,1953,1891,1924 }, + {1039,1173,1129 ,1953,1954,1891 }, {1279,1278,1129 ,1955,1892,1891 }, + {1173,1279,1129 ,1954,1955,1891 }, {1362,1361,1278 ,1956,1893,1892 }, + {1279,1362,1278 ,1955,1956,1892 }, {1438,1437,1361 ,1957,1894,1893 }, + {1362,1438,1361 ,1956,1957,1893 }, {1507,1506,1437 ,1958,1895,1894 }, + {1438,1507,1437 ,1957,1958,1894 }, {1580,1579,1506 ,1959,1896,1895 }, + {1507,1580,1506 ,1958,1959,1895 }, {1580,1648,1647 ,1959,1960,1897 }, + {1579,1580,1647 ,1896,1959,1897 }, {1648,1715,1714 ,1960,1961,1898 }, + {1647,1648,1714 ,1897,1960,1898 }, {1715,1735,1714 ,1961,1962,1898 }, + {1735,1765,1764 ,1962,1963,1921 }, {1714,1735,1764 ,1898,1962,1921 }, + {1828,1827,1764 ,1964,1925,1921 }, {1765,1828,1764 ,1963,1964,1921 }, + {1864,1826,1827 ,1965,1900,1925 }, {1828,1864,1827 ,1964,1965,1925 }, + {1897,1896,1826 ,1966,1901,1900 }, {1864,1897,1826 ,1965,1966,1900 }, + {1962,1961,1896 ,1967,1902,1901 }, {1897,1962,1896 ,1966,1967,1901 }, + {2019,2018,1961 ,1968,1903,1902 }, {1962,2019,1961 ,1967,1968,1902 }, + {2065,2064,2018 ,1969,1904,1903 }, {2019,2065,2018 ,1968,1969,1903 }, + {2111,2110,2064 ,1970,1926,1904 }, {2065,2111,2064 ,1969,1970,1904 }, + {2144,2109,2110 ,1971,1905,1926 }, {2111,2144,2110 ,1970,1971,1926 }, + {2178,2177,2109 ,1972,1906,1905 }, {2144,2178,2109 ,1971,1972,1905 }, + {2178,734,780 ,1972,1973,1907 }, {2177,2178,780 ,1906,1972,1907 }, + {390,389,780 ,1974,1908,1907 }, {734,390,780 ,1973,1974,1907 }, + {1372,1409,389 ,1975,1909,1908 }, {390,1372,389 ,1974,1975,1908 }, + {615,1095,1409 ,766,1910,1909 }, {1372,615,1409 ,1975,766,1909 }, + {3430,3489,3429 ,1976,1977,1978 }, {3380,3430,3379 ,1514,1976,1979 }, + {4038,4039,2662 ,1927,382,805 }, {3489,3488,3429 ,1977,1980,1978 }, + {580,581,623 ,1280,1274,1100 }, {293,1181,1182 ,1430,1981,1235 }, + {1616,1683,1682 ,1150,1151,1010 }, {2037,2082,2036 ,1458,1520,887 }, + {239,2290,2292 ,1103,1982,1983 }, {906,992,905 ,1019,958,1729 }, + {157,91,223 ,1877,1984,1985 }, {681,302,14 ,620,622,1986 }, {694,4165,2477 ,1987,640,642 }, + {562,708,700 ,1936,401,255 }, {467,4110,503 ,346,372,374 }, {3293,7133,7110 ,1988,1989,1990 }, + {765,764,668 ,1991,1950,1949 }, {1174,1173,1039 ,1992,1954,1953 }, + {1174,1279,1173 ,1992,1955,1954 }, {1828,1897,1864 ,1964,1966,1965 }, + {1962,2065,2019 ,1967,1969,1968 }, {2111,2178,2144 ,1970,1972,1971 }, + {2243,2325,2324 ,1993,1994,1995 }, {3323,3355,3322 ,1051,1292,1182 }, + {1142,1233,1141 ,1334,1294,1498 }, {830,928,927 ,1996,1997,1441 }, + {829,830,927 ,1368,1996,1441 }, {526,582,525 ,718,719,1279 }, + {6577,8088,8094 ,97,1998,98 }, {2127,2126,2081 ,1553,890,888 }, + {1980,2036,2007 ,828,887,829 }, {1876,460,822 ,1386,1999,2000 }, + {4244,1599,4223 ,753,391,1934 }, {14,38,681 ,1986,742,620 }, + {3662,3749,5304 ,1942,652,654 }, {4631,5180,5136 ,1816,1878,1817 }, + {959,535,6686 ,1358,2001,750 }, {700,708,757 ,255,401,239 }, + {4187,3,1023 ,502,329,308 }, {765,848,764 ,1991,1951,1950 }, + {1280,1279,1174 ,2002,1955,1992 }, {1363,1362,1279 ,2003,1956,1955 }, + {1280,1363,1279 ,2002,2003,1955 }, {1898,1897,1828 ,2004,1966,1964 }, + {2231,734,2178 ,2005,1973,1972 }, {2697,3327,1211 ,2006,439,2007 }, + {254,2662,2472 ,2008,805,2009 }, {1013,1012,927 ,2010,1471,1441 }, + {928,1013,927 ,1997,2010,1441 }, {1686,1687,1780 ,1004,1104,900 }, + {1089,2893,4393 ,103,1679,463 }, {1260,1857,1311 ,11,13,213 }, + {1319,1521,729 ,322,2011,1548 }, {139,777,963 ,145,421,1341 }, + {2785,3878,3419 ,1246,1248,2012 }, {7515,7573,7538 ,1079,2013,2014 }, + {2141,8085,973 ,2015,2016,2017 }, {3534,8138,8081 ,942,2018,1445 }, + {766,765,668 ,2019,1991,1949 }, {669,766,668 ,2020,2019,1949 }, + {849,848,765 ,2021,1951,1991 }, {766,849,765 ,2019,2021,1991 }, + {948,947,848 ,2022,1952,1951 }, {849,948,848 ,2021,2022,1951 }, + {948,983,947 ,2022,2023,1952 }, {1082,1039,947 ,2024,1953,1952 }, + {983,1082,947 ,2023,2024,1952 }, {1175,1174,1039 ,2025,1992,1953 }, + {1082,1175,1039 ,2024,2025,1953 }, {1281,1280,1174 ,2026,2002,1992 }, + {1175,1281,1174 ,2025,2026,1992 }, {1364,1363,1280 ,2027,2003,2002 }, + {1281,1364,1280 ,2026,2027,2002 }, {1364,1362,1363 ,2027,1956,2003 }, + {1439,1438,1362 ,2028,1957,1956 }, {1364,1439,1362 ,2027,2028,1956 }, + {1508,1507,1438 ,2029,1958,1957 }, {1439,1508,1438 ,2028,2029,1957 }, + {1581,1580,1507 ,2030,1959,1958 }, {1508,1581,1507 ,2029,2030,1958 }, + {1581,1649,1648 ,2030,2031,1960 }, {1580,1581,1648 ,1959,2030,1960 }, + {1649,1716,1715 ,2031,2032,1961 }, {1648,1649,1715 ,1960,2031,1961 }, + {1717,1735,1715 ,2033,1962,1961 }, {1716,1717,1715 ,2032,2033,1961 }, + {1766,1765,1735 ,2034,1963,1962 }, {1717,1766,1735 ,2033,2034,1962 }, + {1766,1829,1828 ,2034,2035,1964 }, {1765,1766,1828 ,1963,2034,1964 }, + {1899,1898,1828 ,2036,2004,1964 }, {1829,1899,1828 ,2035,2036,1964 }, + {1930,1897,1898 ,2037,1966,2004 }, {1899,1930,1898 ,2036,2037,2004 }, + {1963,1962,1897 ,2038,1967,1966 }, {1930,1963,1897 ,2037,2038,1966 }, + {2066,2065,1962 ,2039,1969,1967 }, {1963,2066,1962 ,2038,2039,1967 }, + {2066,2078,2065 ,2039,2040,1969 }, {2112,2111,2065 ,2041,1970,1969 }, + {2078,2112,2065 ,2040,2041,1969 }, {2179,2178,2111 ,2042,1972,1970 }, + {2112,2179,2111 ,2041,2042,1970 }, {2232,2231,2178 ,2043,2005,1972 }, + {2179,2232,2178 ,2042,2043,1972 }, {1448,734,2231 ,2044,1973,2005 }, + {2232,1448,2231 ,2043,2044,2005 }, {775,390,734 ,94,1974,1973 }, + {1448,775,734 ,2044,94,1973 }, {654,1372,390 ,93,1975,1974 }, + {775,654,390 ,94,93,1974 }, {1372,654,615 ,1975,93,766 }, {3080,13,2472 ,908,910,2009 }, + {2922,2923,2972 ,2045,2046,2047 }, {1061,1060,1012 ,2048,1472,1471 }, + {1013,1061,1012 ,2010,2048,1471 }, {1105,1104,1060 ,2049,1473,1472 }, + {1061,1105,1060 ,2048,2049,1472 }, {1521,730,729 ,2011,1549,1548 }, + {5180,5199,5179 ,1878,422,1879 }, {1683,1776,1775 ,1151,1431,1432 }, + {6647,5471,6611 ,2050,2051,1391 }, {1776,1777,1802 ,1431,856,2052 }, + {14,74,38 ,1986,2053,742 }, {3349,3422,3684 ,2054,2055,1915 }, + {3289,3307,3288 ,2056,2057,2058 }, {6139,4240,3797 ,2059,2060,2061 }, + {2045,30,211 ,417,307,415 }, {18,826,738 ,896,507,1674 }, {984,983,948 ,2062,2023,2022 }, + {849,984,948 ,2021,2062,2022 }, {1439,1440,1508 ,2028,2063,2029 }, + {1767,1766,1717 ,2064,2034,2033 }, {1716,1767,1717 ,2032,2064,2033 }, + {1330,654,1481 ,53,93,194 }, {2833,3054,3076 ,2065,916,2066 }, + {593,539,910 ,1108,2067,1930 }, {172,1743,3122 ,667,763,2068 }, + {394,440,1449 ,1600,1464,2069 }, {1615,1682,1614 ,1359,1010,2070 }, + {1239,1190,1449 ,1596,1112,2069 }, {1778,1840,1839 ,855,198,1138 }, + {786,963,777 ,2071,1341,421 }, {2588,74,14 ,2072,2053,1986 }, + {5453,5923,6118 ,937,2073,2074 }, {708,365,757 ,401,402,239 }, + {120,364,4362 ,240,1340,342 }, {365,364,120 ,402,1340,240 }, + {4362,139,962 ,342,145,233 }, {757,365,120 ,239,402,240 }, {5569,3121,5570 ,2075,2076,508 }, + {478,277,613 ,2077,85,298 }, {984,1082,983 ,2062,2024,2023 }, + {8079,8077,3421 ,55,2078,1156 }, {2093,4921,2257 ,553,2079,554 }, + {1536,1582,1581 ,2080,2081,2030 }, {1582,1650,1649 ,2081,2082,2031 }, + {1717,1716,1649 ,2033,2032,2031 }, {1672,1717,1649 ,2083,2033,2031 }, + {1327,1326,1232 ,996,1330,1295 }, {3592,3645,3591 ,2084,1650,1652 }, + {908,862,340 ,1111,1225,1107 }, {2029,2121,2028 ,1748,1598,1811 }, + {2127,2148,2126 ,1553,1561,890 }, {1615,1616,1682 ,1359,1150,1010 }, + {2196,2195,2147 ,1550,1851,1562 }, {1365,1440,1439 ,2085,2063,2028 }, + {5199,5219,5198 ,422,1860,423 }, {423,383,325 ,2086,2087,2088 }, + {4691,324,383 ,2089,2090,2087 }, {669,707,766 ,2020,2091,2019 }, + {850,849,766 ,2092,2021,2019 }, {767,850,766 ,2093,2092,2019 }, + {985,984,849 ,2094,2062,2021 }, {850,985,849 ,2092,2094,2021 }, + {985,1083,1082 ,2094,2095,2024 }, {984,985,1082 ,2062,2094,2024 }, + {1083,1176,1175 ,2095,2096,2025 }, {1082,1083,1175 ,2024,2095,2025 }, + {1176,1282,1281 ,2096,2097,2026 }, {1175,1176,1281 ,2025,2096,2026 }, + {1282,1365,1364 ,2097,2085,2027 }, {1281,1282,1364 ,2026,2097,2027 }, + {3347,4060,3317 ,2098,2099,1882 }, {1521,4244,730 ,2011,753,1549 }, + {4481,4480,4421 ,2100,2101,229 }, {2451,549,450 ,2102,1129,1653 }, + {3948,4326,3670 ,280,219,281 }, {3861,8097,8083 ,1147,1710,2103 }, + {732,283,871 ,2104,2105,2106 }, {4481,1674,4462 ,2100,2107,1076 }, + {1718,1716,1717 ,2108,2032,2033 }, {1768,1767,1716 ,2109,2064,2032 }, + {1718,1768,1716 ,2108,2109,2032 }, {1767,1768,1766 ,2064,2109,2034 }, + {1768,1830,1829 ,2109,2110,2035 }, {1766,1768,1829 ,2034,2109,2035 }, + {1830,1900,1899 ,2110,2111,2036 }, {1829,1830,1899 ,2035,2110,2036 }, + {1899,1900,1930 ,2036,2111,2037 }, {1900,1964,1963 ,2111,2112,2038 }, + {1930,1900,1963 ,2037,2111,2038 }, {2020,2066,1963 ,2113,2039,2038 }, + {1964,2020,1963 ,2112,2113,2038 }, {2079,2078,2066 ,2114,2040,2039 }, + {2020,2079,2066 ,2113,2114,2039 }, {2113,2112,2078 ,2115,2041,2040 }, + {2079,2113,2078 ,2114,2115,2040 }, {2180,2179,2112 ,2116,2042,2041 }, + {2113,2180,2112 ,2115,2116,2041 }, {2233,2232,2179 ,2117,2043,2042 }, + {2180,2233,2179 ,2116,2117,2042 }, {1371,1448,2232 ,2118,2044,2043 }, + {2233,1371,2232 ,2117,2118,2043 }, {231,775,1448 ,92,94,2044 }, + {1371,231,1448 ,2118,92,2044 }, {5180,5220,5199 ,1878,2119,422 }, + {5241,5240,5199 ,2120,481,422 }, {3699,3698,3645 ,2121,2122,1650 }, + {3646,3699,3645 ,2123,2121,1650 }, {7232,3288,7233 ,2124,2058,2125 }, + {26,70,460 ,2126,323,1999 }, {290,335,334 ,1042,1014,1267 }, + {2190,305,2189 ,1574,1463,1599 }, {1914,1943,1983 ,1000,2127,1001 }, + {2198,293,692 ,1506,1430,1429 }, {2197,2198,692 ,1507,1506,1429 }, + {1132,981,1591 ,61,2128,1072 }, {8118,8126,4835 ,2129,812,2130 }, + {8133,8107,2301 ,2131,658,2132 }, {965,799,1419 ,366,392,367 }, + {4131,2026,4251 ,1656,130,132 }, {767,766,707 ,2093,2019,2091 }, + {985,949,1083 ,2094,2133,2095 }, {5030,5061,1130 ,2134,2135,2136 }, + {324,4691,325 ,2090,2089,2088 }, {810,732,716 ,2137,2104,2138 }, + {4441,4481,4421 ,2139,2100,229 }, {412,736,1080 ,358,843,2140 }, + {770,1932,2352 ,275,2141,390 }, {2181,2180,2113 ,2142,2116,2115 }, + {5220,5241,5199 ,2119,2120,422 }, {2582,2585,3372 ,873,875,2143 }, + {3355,3381,3354 ,1292,871,1293 }, {3572,3573,3590 ,1931,980,1867 }, + {6230,3436,5876 ,2144,2145,2146 }, {815,857,903 ,999,2147,1734 }, + {1402,1403,1474 ,1780,974,1649 }, {1682,1681,1614 ,1010,1009,2070 }, + {1337,1845,1844 ,2148,912,911 }, {817,816,724 ,1020,998,722 }, + {1182,1181,1049 ,1235,1981,1362 }, {3316,1295,2810 ,2149,618,1728 }, + {440,1239,1449 ,1464,1596,2069 }, {741,2730,740 ,1221,2150,1595 }, + {2029,2080,2121 ,1748,2151,1598 }, {4202,885,562 ,260,216,1936 }, + {326,423,325 ,2152,2086,2088 }, {479,514,423 ,2153,2154,2086 }, + {4378,4430,4354 ,375,341,376 }, {4214,4215,4254 ,226,370,416 }, + {892,1597,810 ,506,505,2137 }, {767,851,850 ,2093,2155,2092 }, + {58,8128,227 ,2156,959,2157 }, {4972,949,851 ,2158,2133,2155 }, + {850,949,985 ,2092,2133,2094 }, {5029,1130,1083 ,2159,2136,2095 }, + {1083,1130,1176 ,2095,2136,2096 }, {5029,5030,1130 ,2159,2134,2136 }, + {884,614,4095 ,2160,1397,349 }, {1080,459,980 ,2140,696,2161 }, + {732,738,283 ,2104,1674,2105 }, {4180,1258,412 ,2162,2163,358 }, + {1319,980,1521 ,322,2161,2011 }, {732,871,716 ,2104,2106,2138 }, + {1224,1904,3048 ,1508,2164,2165 }, {4426,8100,6837 ,1056,1713,1057 }, + {283,1057,871 ,2105,354,2106 }, {1830,1865,1900 ,2110,2166,2111 }, + {8085,8091,8067 ,2016,2167,2168 }, {5285,5284,5240 ,2169,571,481 }, + {3404,2582,3372 ,2170,873,2143 }, {862,999,960 ,1225,798,1326 }, + {2193,506,170 ,1007,1381,794 }, {989,990,1140 ,1813,1435,1499 }, + {1912,1913,1980 ,964,963,828 }, {1722,1686,1779 ,1668,1004,1275 }, + {1404,1476,1475 ,975,859,858 }, {1290,2009,1918 ,795,1224,1110 }, + {5370,5328,6091 ,2171,2172,2173 }, {8107,8137,3006 ,658,2174,414 }, + {280,279,4692 ,2175,2176,2177 }, {5241,5285,5240 ,2120,2169,481 }, + {424,423,326 ,2178,2086,2152 }, {424,479,423 ,2178,2153,2086 }, + {515,514,479 ,2179,2154,2153 }, {575,574,514 ,2180,2181,2154 }, + {515,575,514 ,2179,2180,2154 }, {1223,4160,57 ,2182,223,222 }, + {635,1854,2657 ,2183,2184,2185 }, {1258,2014,736 ,2163,842,843 }, + {1080,980,25 ,2140,2161,2186 }, {1258,736,412 ,2163,843,358 }, + {4471,5295,4472 ,2187,2188,2189 }, {4354,4430,4402 ,376,341,2190 }, + {1866,1865,1830 ,2191,2166,2110 }, {1866,1901,1900 ,2191,2192,2111 }, + {1865,1866,1900 ,2166,2191,2111 }, {1901,1902,1900 ,2192,2193,2111 }, + {1965,1964,1900 ,2194,2112,2111 }, {1902,1965,1900 ,2193,2194,2111 }, + {1965,1998,1964 ,2194,2195,2112 }, {2021,2020,1964 ,2196,2113,2112 }, + {1998,2021,1964 ,2195,2196,2112 }, {2021,2079,2020 ,2196,2114,2113 }, + {2114,2113,2079 ,2197,2115,2114 }, {2021,2114,2079 ,2196,2197,2114 }, + {2182,2181,2113 ,2198,2142,2115 }, {2114,2182,2113 ,2197,2198,2115 }, + {2215,2180,2181 ,2199,2116,2142 }, {2182,2215,2181 ,2198,2199,2142 }, + {9,2233,2180 ,2200,2117,2116 }, {2215,9,2180 ,2199,2200,2116 }, + {9,1371,2233 ,2200,2118,2117 }, {232,231,1371 ,2201,92,2118 }, + {9,232,1371 ,2200,2201,2118 }, {1094,1481,231 ,1398,194,92 }, + {232,1094,231 ,2201,1398,92 }, {2147,2195,2194 ,1562,1851,1853 }, + {2198,2216,293 ,1506,1555,1430 }, {1744,1059,3932 ,1666,678,1096 }, + {2009,862,908 ,1224,1225,1111 }, {388,437,339 ,2202,1149,1143 }, + {388,339,239 ,2202,1143,1103 }, {1144,1143,991 ,957,1333,1335 }, + {1329,1406,1405 ,1134,1135,995 }, {259,410,455 ,2203,2204,2205 }, + {817,859,816 ,1020,1733,998 }, {1345,1093,840 ,203,66,68 }, {196,124,607 ,152,2206,2207 }, + {576,4907,673 ,2208,2209,2210 }, {4525,892,1673 ,623,506,1686 }, + {4160,1163,2012 ,223,2211,2212 }, {810,1597,18 ,2137,505,896 }, + {5599,5640,5639 ,2213,2214,2215 }, {736,459,1080 ,843,696,2140 }, + {5598,5599,5639 ,2216,2213,2215 }, {5237,5573,5307 ,1800,2217,461 }, + {1206,138,1734 ,2218,434,2219 }, {4520,136,4496 ,1673,1685,2220 }, + {4473,4520,4496 ,270,1673,2220 }, {1831,1866,1830 ,2221,2191,2110 }, + {2182,9,2215 ,2198,2200,2199 }, {1052,1094,232 ,2222,1398,2201 }, + {5339,5338,5284 ,2223,63,571 }, {5285,5339,5284 ,2169,2223,571 }, + {2033,2068,2124 ,1288,891,1006 }, {2068,2125,2124 ,891,892,1006 }, + {1144,1142,1143 ,957,1334,1333 }, {336,385,432 ,1013,864,1222 }, + {1722,1779,1778 ,1668,1275,855 }, {2080,2067,2121 ,2151,1592,1598 }, + {2029,2067,2080 ,1748,1592,2151 }, {239,339,338 ,1103,1143,867 }, + {628,627,528 ,1177,670,1055 }, {3697,3698,3743 ,2224,2122,2225 }, + {582,581,525 ,719,1274,1279 }, {327,326,281 ,2226,2152,2227 }, + {327,384,326 ,2226,2228,2152 }, {425,424,326 ,2229,2178,2152 }, + {384,425,326 ,2228,2229,2152 }, {480,479,424 ,2230,2153,2178 }, + {425,480,424 ,2229,2230,2178 }, {516,515,479 ,2231,2179,2153 }, + {480,516,479 ,2230,2231,2153 }, {576,575,515 ,2208,2180,2179 }, + {516,576,515 ,2231,2208,2179 }, {575,576,673 ,2180,2208,2210 }, + {5817,5834,5816 ,701,543,545 }, {892,810,1673 ,506,2137,1686 }, + {4442,4441,4391 ,991,2139,992 }, {283,738,358 ,2105,1674,897 }, + {459,4412,980 ,696,744,2161 }, {46,739,5 ,2232,2233,350 }, {883,146,76 ,119,504,689 }, + {1057,283,188 ,354,2105,2234 }, {1903,1966,1965 ,406,405,2194 }, + {1902,1903,1965 ,2193,406,2194 }, {1999,1998,1965 ,2235,2195,2194 }, + {1966,1999,1965 ,405,2235,2194 }, {2022,2021,1998 ,2236,2196,2195 }, + {1999,2022,1998 ,2235,2236,2195 }, {2115,2114,2021 ,2237,2197,2196 }, + {2022,2115,2021 ,2236,2237,2196 }, {2115,2145,2114 ,2237,2238,2197 }, + {2183,2182,2114 ,2239,2198,2197 }, {2145,2183,2114 ,2238,2239,2197 }, + {2183,9,2182 ,2239,2200,2198 }, {1513,232,9 ,2240,2201,2200 }, + {1632,76,129 ,2241,689,730 }, {2013,58,877 ,2242,2156,2243 }, + {728,819,726 ,1266,2244,669 }, {1153,6797,8123 ,2245,2246,2247 }, + {1182,1049,1048 ,1235,1362,1236 }, {335,336,431 ,1014,1013,1268 }, + {901,903,989 ,1428,1734,1813 }, {5339,4916,4915 ,2223,2248,64 }, + {1868,3760,1783 ,2249,2250,2251 }, {3784,4884,4239 ,2252,2253,2254 }, + {5338,5339,4915 ,63,2223,64 }, {4513,1673,4481 ,624,1686,2100 }, + {1295,3316,3348 ,618,2149,2255 }, {4244,4223,340 ,753,1934,1107 }, + {730,4244,340 ,1549,753,1107 }, {1114,1115,2047 ,2256,2257,2258 }, + {4480,4462,4421 ,2101,1076,229 }, {798,5103,5378 ,78,77,359 }, + {40,9,2183 ,2259,2200,2239 }, {40,163,9 ,2259,2260,2200 }, {233,1513,9 ,2261,2240,2200 }, + {163,233,9 ,2260,2261,2200 }, {1514,232,1513 ,2262,2201,2240 }, + {233,1514,1513 ,2261,2262,2240 }, {1055,1052,232 ,2263,2222,2201 }, + {1514,1055,232 ,2262,2263,2201 }, {1054,1094,1052 ,2264,1398,2222 }, + {1055,1054,1052 ,2263,2264,2222 }, {1054,865,1094 ,2264,1119,1398 }, + {2893,3946,5307 ,1679,1230,461 }, {227,1482,58 ,2157,2265,2156 }, + {1238,860,1000 ,1569,1385,1384 }, {305,1239,440 ,1463,1596,1464 }, + {1657,1656,1552 ,1781,1166,2266 }, {814,815,902 ,1054,999,1427 }, + {8092,8120,8130 ,2267,906,905 }, {2131,2200,2199 ,656,1570,1554 }, + {817,905,859 ,1020,1729,1733 }, {2032,2033,2123 ,1127,1288,1008 }, + {3946,4227,5307 ,1230,1564,461 }, {249,1664,268 ,644,2268,645 }, + {1234,1328,1327 ,1178,994,996 }, {864,865,867 ,840,1119,1121 }, + {8140,8063,8102 ,2269,465,253 }, {577,576,516 ,2270,2208,2231 }, + {576,577,674 ,2208,2270,2271 }, {358,4184,2014 ,897,898,842 }, + {6553,6295,6646 ,780,1290,1133 }, {1441,23,4181 ,1485,2272,2273 }, + {1927,870,710 ,1196,2274,33 }, {810,716,1674 ,2137,2138,2107 }, + {593,1599,539 ,1108,391,2067 }, {4152,4180,4448 ,2275,2162,2276 }, + {358,4180,4152 ,897,2162,2275 }, {188,358,4152 ,2234,897,2275 }, + {4480,4481,4462 ,2101,2100,1076 }, {941,119,1049 ,2277,2278,1362 }, + {188,4152,4138 ,2234,2275,278 }, {4152,188,4138 ,2275,2234,278 }, + {1674,716,4512 ,2107,2138,2279 }, {6509,6531,8092 ,1452,2280,2267 }, + {4060,4064,3317 ,2099,1880,1882 }, {1999,1966,2022 ,2235,405,2236 }, + {233,1055,1514 ,2261,2263,2262 }, {1055,865,1054 ,2263,1119,2264 }, + {2137,867,776 ,147,1121,1120 }, {3758,3757,3711 ,1789,2281,1790 }, + {3194,3016,3189 ,1065,1028,1027 }, {442,441,357 ,673,609,672 }, + {1910,1911,1867 ,1223,1101,1504 }, {1049,1181,1050 ,1362,1981,2282 }, + {1802,1777,1838 ,2052,856,425 }, {336,432,431 ,1013,1222,1268 }, + {5305,5572,3147 ,2283,2284,2285 }, {904,991,903 ,1730,1335,1734 }, + {1406,1407,1447 ,1135,1269,1136 }, {8114,8108,6797 ,2286,2287,2246 }, + {437,436,339 ,1149,1142,1143 }, {1190,1189,1449 ,1112,1495,2069 }, + {8081,8093,8072 ,1445,2288,511 }, {940,1910,1867 ,197,1223,1504 }, + {328,426,425 ,2289,2290,2229 }, {384,328,425 ,2228,2289,2229 }, + {426,481,480 ,2290,2291,2230 }, {425,426,480 ,2229,2290,2230 }, + {481,517,516 ,2291,2292,2231 }, {480,481,516 ,2230,2291,2231 }, + {578,577,516 ,2293,2270,2231 }, {517,578,516 ,2292,2293,2231 }, + {675,676,677 ,2294,2295,2296 }, {577,578,675 ,2270,2293,2294 }, + {738,189,358 ,1674,503,897 }, {661,1441,4181 ,2297,1485,2273 }, + {4513,4481,4463 ,624,2100,993 }, {5,739,413 ,350,2233,351 }, + {980,4244,1521 ,2161,753,2011 }, {1424,379,608 ,2298,2299,2300 }, + {416,604,502 ,556,486,2301 }, {25,980,1319 ,2186,2161,322 }, + {4448,4180,412 ,2276,2162,358 }, {138,878,1734 ,434,398,2219 }, + {5558,3121,5569 ,2302,2076,2075 }, {4430,4473,4402 ,341,270,2190 }, + {879,3397,3370 ,1155,136,2303 }, {4152,4448,4138 ,2275,2276,278 }, + {4108,4148,4124 ,2304,2305,2306 }, {8131,8075,8107 ,2307,2308,658 }, + {1967,1968,1966 ,404,2309,405 }, {1968,2023,2022 ,2309,2310,2236 }, + {1966,1968,2022 ,405,2309,2236 }, {2116,2115,2022 ,2311,2237,2236 }, + {2023,2116,2022 ,2310,2311,2236 }, {2116,2145,2115 ,2311,2238,2237 }, + {2184,2183,2145 ,2312,2239,2238 }, {2116,2184,2145 ,2311,2312,2238 }, + {83,40,2183 ,2313,2259,2239 }, {2184,83,2183 ,2312,2313,2239 }, + {1001,163,40 ,2314,2260,2259 }, {83,1001,40 ,2313,2314,2259 }, + {234,233,163 ,2315,2261,2260 }, {1001,234,163 ,2314,2315,2260 }, + {1053,1055,233 ,2316,2263,2261 }, {234,1053,233 ,2315,2316,2261 }, + {776,865,1055 ,1120,1119,2263 }, {1053,776,1055 ,2316,1120,2263 }, + {5194,1102,1454 ,727,2317,2318 }, {1744,3932,2088 ,1666,1096,1667 }, + {1721,1722,1778 ,854,1668,855 }, {2197,692,933 ,1507,1429,1496 }, + {1775,1776,1802 ,1432,1431,2052 }, {1240,1918,1190 ,796,1110,1112 }, + {1446,1447,1478 ,1137,1136,2319 }, {3880,3868,3831 ,2320,970,2321 }, + {3832,3880,3831 ,2322,2320,2321 }, {2191,392,2190 ,1174,857,1574 }, + {4587,2320,8020 ,774,1406,1405 }, {1843,1911,1910 ,901,1101,1223 }, + {5013,2256,6484 ,684,2323,685 }, {1867,2390,940 ,1504,1594,197 }, + {578,579,676 ,2293,2324,2295 }, {1338,1257,4214 ,1077,208,226 }, + {4215,4255,4254 ,370,165,416 }, {4412,314,980 ,744,578,2161 }, + {810,18,732 ,2137,896,2104 }, {980,314,4244 ,2161,578,753 }, + {4464,4513,4463 ,584,624,993 }, {1673,810,1674 ,1686,2137,2107 }, + {4463,4481,4441 ,993,2100,2139 }, {8066,8092,8098 ,960,2267,907 }, + {1225,490,1223 ,327,469,2182 }, {412,1080,69 ,358,2140,321 }, + {4150,412,69 ,320,358,321 }, {880,1441,661 ,1483,1485,2297 }, + {3644,3697,3696 ,1651,2224,2325 }, {188,4152,4138 ,2234,2275,278 }, + {83,234,1001 ,2313,2315,2314 }, {782,776,1053 ,1648,1120,2316 }, + {1691,1670,1733 ,2326,2327,2328 }, {392,1240,305 ,857,796,1463 }, + {2122,2191,2190 ,1128,1174,1574 }, {2067,2122,2121 ,1592,1128,1598 }, + {1479,1477,1478 ,1270,1045,2319 }, {1602,1610,749 ,2329,2330,2331 }, + {1478,1477,1405 ,2319,1045,995 }, {3704,6140,6169 ,2332,2333,2334 }, + {1365,1439,1364 ,2085,2028,2027 }, {3918,3917,3868 ,2335,968,970 }, + {517,579,578 ,2292,2324,2293 }, {2223,6484,2256 ,2336,685,2323 }, + {5834,1942,141 ,543,2337,681 }, {1462,4177,4116 ,2338,2339,2340 }, + {130,89,150 ,2341,1482,1481 }, {136,1628,883 ,1685,118,119 }, + {4441,4421,4367 ,2139,229,228 }, {1677,1676,1610 ,2342,2343,2330 }, + {69,1080,25 ,321,2140,2186 }, {3643,3644,3696 ,2344,1651,2325 }, + {1069,188,4138 ,277,2234,278 }, {1660,1053,234 ,2345,2316,2315 }, + {1378,3148,2743 ,925,814,923 }, {3880,3918,3868 ,2320,2335,970 }, + {1094,865,864 ,1398,1119,840 }, {1802,1838,1837 ,2052,425,997 }, + {1634,1423,396 ,768,151,153 }, {742,741,645 ,972,1221,940 }, + {1915,1916,1943 ,1437,832,2127 }, {1914,1915,1943 ,1000,1437,2127 }, + {1682,1683,1775 ,1010,1151,1432 }, {1446,1478,1405 ,1137,2319,995 }, + {1235,1329,1328 ,1179,1134,994 }, {3961,3960,3917 ,2346,1021,968 }, + {3918,3961,3917 ,2335,2346,968 }, {1145,1235,1234 ,1277,1179,1178 }, + {7151,3290,7175 ,1202,1204,2347 }, {329,427,426 ,2348,2349,2290 }, + {328,329,426 ,2289,2348,2290 }, {426,427,481 ,2290,2349,2291 }, + {427,518,517 ,2349,2350,2292 }, {481,427,517 ,2291,2349,2292 }, + {518,519,579 ,2350,2351,2324 }, {517,518,579 ,2292,2350,2324 }, + {519,617,579 ,2351,2352,2324 }, {579,617,677 ,2324,2352,2296 }, + {2026,870,1927 ,130,2274,1196 }, {732,18,738 ,2104,896,1674 }, + {4442,4463,4441 ,991,993,2139 }, {4068,4067,1253 ,408,185,2353 }, + {358,2014,4180 ,897,842,2162 }, {4391,4441,4367 ,992,2139,228 }, + {1226,1321,1320 ,2354,2355,2356 }, {1321,1398,1397 ,2355,2357,2358 }, + {1320,1321,1397 ,2356,2355,2358 }, {1397,1398,1445 ,2358,2357,2359 }, + {1398,1470,1469 ,2357,2360,2361 }, {1445,1398,1469 ,2359,2357,2361 }, + {1470,1542,1541 ,2360,2362,2363 }, {1469,1470,1541 ,2361,2360,2363 }, + {1611,1610,1541 ,2364,2330,2363 }, {1542,1611,1541 ,2362,2364,2363 }, + {1678,1677,1610 ,2365,2342,2330 }, {1611,1678,1610 ,2364,2365,2330 }, + {1720,1676,1677 ,2366,2343,2342 }, {1678,1720,1677 ,2365,2366,2342 }, + {1720,1771,4817 ,2366,2367,2368 }, {4138,4448,412 ,278,2276,358 }, + {1674,4512,4462 ,2107,2279,1076 }, {6837,8088,6577 ,1057,1998,97 }, + {4036,109,2151 ,1602,1654,1603 }, {1969,2024,2023 ,2369,2370,2310 }, + {1968,1969,2023 ,2309,2369,2310 }, {2024,2117,2116 ,2370,2371,2311 }, + {2023,2024,2116 ,2310,2370,2311 }, {2185,2184,2116 ,2372,2312,2311 }, + {2117,2185,2116 ,2371,2372,2311 }, {152,83,2184 ,2373,2313,2312 }, + {2185,152,2184 ,2372,2373,2312 }, {1288,234,83 ,2374,2315,2313 }, + {152,1288,83 ,2373,2374,2313 }, {861,1660,234 ,2375,2345,2315 }, + {1288,861,234 ,2374,2375,2315 }, {998,1053,1660 ,2376,2316,2345 }, + {861,998,1660 ,2375,2376,2345 }, {786,782,1053 ,2071,1648,2316 }, + {998,786,1053 ,2376,2071,2316 }, {4327,1427,561 ,2377,2378,2379 }, + {169,1880,1296 ,248,250,335 }, {652,169,1296 ,334,248,335 }, + {4000,4042,4041 ,1022,1061,1063 }, {3999,4000,4041 ,1023,1022,1063 }, + {3961,4001,4000 ,2346,2380,1022 }, {2129,2069,2130 ,1469,1711,1505 }, + {1981,2037,2036 ,1456,1458,887 }, {858,904,903 ,1731,1730,1734 }, + {647,742,645 ,973,972,940 }, {1943,1916,1983 ,2127,832,1001 }, + {1916,1984,1983 ,832,831,1001 }, {3372,2585,3453 ,2143,875,917 }, + {1046,1145,1045 ,1929,1277,956 }, {6098,5113,6174 ,1565,141,2381 }, + {518,427,519 ,2350,2349,2351 }, {870,1396,710 ,2274,34,33 }, + {1673,1674,4481 ,1686,2107,2100 }, {716,1675,4512 ,2138,2382,2279 }, + {4138,412,44 ,278,358,357 }, {2474,2072,8096 ,874,1812,1796 }, + {5021,1969,5019 ,2383,2369,2384 }, {5019,1969,1968 ,2384,2369,2309 }, + {2186,2185,2117 ,2385,2372,2371 }, {861,786,998 ,2375,2071,2376 }, + {3712,3711,3653 ,1439,1790,1440 }, {6169,6141,6154 ,2334,2386,2387 }, + {3356,3355,3323 ,872,1292,1051 }, {3960,3961,4000 ,1021,2346,1022 }, + {1801,1802,1837 ,1433,2052,997 }, {725,817,724 ,721,1020,722 }, + {2518,448,546 ,2388,1172,1171 }, {2069,2038,2530 ,1711,1551,2389 }, + {1784,1848,1847 ,1662,1436,1519 }, {647,645,646 ,973,940,954 }, + {723,724,815 ,1053,722,999 }, {815,816,857 ,999,998,2147 }, {3698,3744,3743 ,2122,2390,2225 }, + {237,337,336 ,869,866,1013 }, {519,618,617 ,2351,2391,2352 }, + {855,897,951 ,2392,2393,2394 }, {897,952,951 ,2393,2395,2394 }, + {4180,2014,1258 ,2162,842,2163 }, {952,5034,951 ,2395,2396,2394 }, + {1321,1369,1398 ,2355,2397,2357 }, {1678,1771,1720 ,2365,2367,2366 }, + {1596,188,1069 ,355,2234,277 }, {4536,4452,4820 ,89,2398,2399 }, + {393,4042,4000 ,2400,1061,1022 }, {4001,393,4000 ,2380,2400,1022 }, + {632,341,1184 ,1380,1852,797 }, {341,1373,1184 ,1852,1501,797 }, + {1682,1775,1739 ,1010,1432,1011 }, {1475,1547,1546 ,858,860,1360 }, + {2083,2069,2128 ,1552,1711,1468 }, {2069,2129,2128 ,1711,1469,1468 }, + {7175,3290,7196 ,2347,1204,2401 }, {857,858,903 ,2147,1731,1734 }, + {816,858,857 ,998,1731,2147 }, {1325,1326,1402 ,1331,1330,1780 }, + {1303,1302,687 ,1933,909,1665 }, {330,331,329 ,2402,2403,2348 }, + {331,428,427 ,2403,2404,2349 }, {329,331,427 ,2348,2403,2349 }, + {428,520,519 ,2404,2405,2351 }, {427,428,519 ,2349,2404,2351 }, + {520,619,618 ,2405,2406,2391 }, {519,520,618 ,2351,2405,2391 }, + {718,717,618 ,2407,2408,2391 }, {619,718,618 ,2406,2407,2391 }, + {717,718,855 ,2408,2407,2392 }, {1042,1041,952 ,2409,2410,2395 }, + {898,1042,952 ,2411,2409,2395 }, {1136,1135,1041 ,2412,1943,2410 }, + {1042,1136,1041 ,2409,2412,2410 }, {1227,1226,1135 ,2413,2354,1943 }, + {1136,1227,1135 ,2412,2413,1943 }, {1227,1322,1321 ,2413,2414,2355 }, + {1226,1227,1321 ,2354,2413,2355 }, {1321,1322,1369 ,2355,2414,2397 }, + {1322,1399,1398 ,2414,2415,2357 }, {1369,1322,1398 ,2397,2414,2357 }, + {1399,1471,1470 ,2415,2416,2360 }, {1398,1399,1470 ,2357,2415,2360 }, + {1471,1543,1542 ,2416,2417,2362 }, {1470,1471,1542 ,2360,2416,2362 }, + {1543,1612,1611 ,2417,2418,2364 }, {1542,1543,1611 ,2362,2417,2364 }, + {1679,1678,1611 ,2419,2365,2364 }, {1612,1679,1611 ,2418,2419,2364 }, + {1772,1771,1678 ,2420,2367,2365 }, {1679,1772,1678 ,2419,2420,2365 }, + {1835,1834,1771 ,2421,2422,2367 }, {1772,1835,1771 ,2420,2421,2367 }, + {322,6203,843 ,1250,1249,2423 }, {3380,3379,3353 ,1514,1979,2424 }, + {3354,3380,3353 ,1293,1514,2424 }, {4644,4740,1970 ,2425,2426,2427 }, + {1970,2000,1969 ,2427,2428,2369 }, {1969,2000,2024 ,2369,2428,2370 }, + {8090,8065,8080 ,412,96,1094 }, {2118,2117,2024 ,2429,2371,2370 }, + {2025,2118,2024 ,2430,2429,2370 }, {2187,2186,2117 ,2431,2385,2371 }, + {2118,2187,2117 ,2429,2431,2371 }, {2187,2185,2186 ,2431,2372,2385 }, + {161,152,2185 ,2432,2373,2372 }, {2187,161,2185 ,2431,2432,2372 }, + {1193,1288,152 ,2433,2374,2373 }, {161,1193,152 ,2432,2433,2373 }, + {1193,1287,1288 ,2433,2434,2374 }, {907,861,1288 ,2435,2375,2374 }, + {1287,907,1288 ,2434,2435,2374 }, {629,786,861 ,2436,2071,2375 }, + {907,629,861 ,2435,2436,2375 }, {629,962,786 ,2436,233,2071 }, + {3356,3381,3355 ,872,871,1292 }, {3004,2946,4042 ,2437,1062,1061 }, + {8106,8135,3449 ,2438,2439,2440 }, {529,628,528 ,1148,1177,1055 }, + {1848,1915,1914 ,1436,1437,1000 }, {1847,1848,1914 ,1519,1436,1000 }, + {2476,2310,3844 ,2441,2442,2443 }, {1686,1780,1779 ,1004,900,1275 }, + {236,237,336 ,2444,869,1013 }, {330,2393,286 ,2402,2445,2446 }, + {520,521,619 ,2405,2447,2406 }, {8115,8136,8140 ,2448,2449,2269 }, + {1519,79,2544 ,2450,1510,2451 }, {4021,4835,8106 ,1712,2130,2438 }, + {4446,1335,2607 ,237,236,2452 }, {5833,5834,141 ,544,543,681 }, + {3430,3429,3379 ,1976,1978,1979 }, {488,832,1302 ,533,613,909 }, + {3080,488,1302 ,908,533,909 }, {1057,188,1596 ,354,2234,355 }, + {871,594,893 ,2106,2453,2454 }, {716,871,893 ,2138,2106,2454 }, + {393,3004,4042 ,2400,2437,1061 }, {8139,3263,8089 ,1923,252,254 }, + {1876,822,1985 ,1386,2000,1547 }, {822,729,1985 ,2000,1548,1547 }, + {1685,1686,1722 ,1005,1004,1668 }, {2193,632,506 ,1007,1380,1381 }, + {1684,1721,1683 ,1327,854,1151 }, {2131,2201,2200 ,656,2455,1570 }, + {2193,2194,341 ,1007,1853,1852 }, {1184,1373,999 ,797,1501,798 }, + {1779,1842,1841 ,1275,196,1276 }, {286,331,330 ,2446,2403,2402 }, + {428,521,520 ,2404,2447,2405 }, {916,913,863 ,445,444,284 }, + {283,358,188 ,2105,897,2234 }, {1227,1286,1322 ,2413,2456,2414 }, + {1773,1772,1679 ,2457,2420,2419 }, {1871,1835,1872 ,2458,2421,2459 }, + {8074,8092,8066 ,2460,2267,960 }, {3354,3353,3321 ,1293,2424,2461 }, + {2796,304,3080 ,2462,532,908 }, {1675,716,893 ,2382,2138,2454 }, + {1193,907,1287 ,2433,2435,2434 }, {820,629,907 ,2463,2436,2435 }, + {6102,3831,3868 ,2464,2321,970 }, {3004,2087,2571 ,2437,2465,1064 }, + {1006,1100,799 ,234,2466,392 }, {729,822,1319 ,1548,2000,322 }, + {1146,1145,1046 ,2467,1277,1929 }, {1047,1146,1046 ,1328,2467,1929 }, + {1474,1475,1546 ,1649,858,1360 }, {1373,1000,999 ,1501,1384,798 }, + {3264,8144,2301 ,2468,2469,2132 }, {1000,1876,999 ,1384,1386,798 }, + {287,332,331 ,2470,2471,2403 }, {332,429,428 ,2471,2472,2404 }, + {331,332,428 ,2403,2471,2404 }, {429,522,521 ,2472,2473,2447 }, + {428,429,521 ,2404,2472,2447 }, {522,620,619 ,2473,2474,2406 }, + {521,522,619 ,2447,2473,2406 }, {720,718,719 ,2475,2407,2476 }, + {619,620,718 ,2406,2474,2407 }, {812,899,898 ,559,2477,2411 }, + {811,812,898 ,557,559,2411 }, {1043,898,899 ,2478,2411,2477 }, + {1043,1137,1136 ,2478,2479,2412 }, {1137,1228,1227 ,2479,2480,2413 }, + {1136,1137,1227 ,2412,2479,2413 }, {1228,1229,1286 ,2480,2481,2456 }, + {1227,1228,1286 ,2413,2480,2456 }, {1229,1323,1322 ,2481,2482,2414 }, + {1286,1229,1322 ,2456,2481,2414 }, {1323,1400,1399 ,2482,2483,2415 }, + {1322,1323,1399 ,2414,2482,2415 }, {1400,1472,1471 ,2483,2484,2416 }, + {1399,1400,1471 ,2415,2483,2416 }, {1472,1544,1543 ,2484,2485,2417 }, + {1471,1472,1543 ,2416,2484,2417 }, {1613,1612,1543 ,2486,2418,2417 }, + {1544,1613,1543 ,2485,2486,2417 }, {1613,1680,1679 ,2486,2487,2419 }, + {1612,1613,1679 ,2418,2486,2419 }, {1680,1774,1773 ,2487,2488,2457 }, + {1679,1680,1773 ,2419,2487,2457 }, {1800,1772,1773 ,2489,2420,2457 }, + {1774,1800,1773 ,2488,2489,2457 }, {1836,1835,1772 ,2490,2421,2420 }, + {1800,1836,1772 ,2489,2490,2420 }, {1836,1872,1835 ,2490,2459,2421 }, + {296,630,3475 ,1558,2491,1559 }, {2027,2000,2002 ,2492,2428,2493 }, + {1675,893,4512 ,2382,2454,2279 }, {8069,8100,3482 ,1095,1713,2494 }, + {2119,2118,2025 ,2495,2429,2430 }, {2027,2119,2025 ,2492,2495,2430 }, + {2188,2187,2118 ,2496,2431,2429 }, {2119,2188,2118 ,2495,2496,2429 }, + {179,161,2187 ,2497,2432,2431 }, {2188,179,2187 ,2496,2497,2431 }, + {1374,1193,161 ,2498,2433,2432 }, {179,1374,161 ,2497,2498,2432 }, + {954,907,1193 ,2499,2435,2433 }, {1374,954,1193 ,2498,2499,2433 }, + {914,820,907 ,2500,2463,2435 }, {954,914,907 ,2499,2500,2435 }, + {487,629,820 ,2501,2436,2463 }, {914,487,820 ,2500,2501,2463 }, + {1100,962,629 ,2466,233,2436 }, {487,1100,629 ,2501,2466,2436 }, + {3491,3490,3461 ,955,2502,1152 }, {2946,3004,2571 ,1062,2437,1064 }, + {3931,3684,3664 ,1868,1915,1557 }, {1843,1844,1911 ,901,911,1101 }, + {8068,8071,3264 ,1784,738,2468 }, {2087,132,3016 ,2465,2503,1028 }, + {909,593,910 ,1109,1108,1930 }, {1233,1232,1141 ,1294,1295,1498 }, + {1447,1479,1478 ,1136,1270,2319 }, {1844,1912,1911 ,911,964,1101 }, + {3434,3494,3463 ,737,736,1271 }, {2571,2087,3016 ,1064,2465,1028 }, + {1775,1802,1801 ,1432,2052,1433 }, {1876,1985,999 ,1386,1547,798 }, + {718,720,4954 ,2407,2475,558 }, {4258,4217,85 ,84,83,2504 }, + {6853,6905,6892 ,2505,2506,2507 }, {1137,1229,1228 ,2479,2481,2480 }, + {1302,832,1744 ,909,613,1666 }, {832,1059,1744 ,613,678,1666 }, + {222,179,2188 ,2508,2497,2496 }, {2222,2210,6563 ,2509,2510,464 }, + {132,2837,584 ,2503,2511,2512 }, {2364,3342,3343 ,2513,2514,2515 }, + {1548,1617,1616 ,902,904,1150 }, {239,238,2290 ,1103,868,1982 }, + {822,460,70 ,2000,1999,323 }, {3698,3697,3644 ,2122,2224,1651 }, + {5988,5972,4147 ,2516,399,2517 }, {620,720,719 ,2474,2475,2476 }, + {5309,505,469 ,987,986,988 }, {1137,1180,1229 ,2479,2518,2481 }, + {1349,2002,1971 ,2519,2493,2520 }, {3647,3646,3594 ,2521,2123,2522 }, + {2698,4369,5335 ,105,104,2523 }, {3016,132,584 ,1028,2503,2512 }, + {626,725,625 ,801,721,720 }, {290,291,335 ,1042,1012,1014 }, + {992,991,904 ,958,1335,1730 }, {1302,1744,687 ,909,1666,1665 }, + {1978,2034,1977 ,847,1289,1503 }, {289,333,332 ,1044,1043,2471 }, + {333,430,429 ,1043,2524,2472 }, {332,333,429 ,2471,1043,2472 }, + {430,482,429 ,2524,2525,2472 }, {482,523,522 ,2525,2526,2473 }, + {429,482,522 ,2472,2525,2473 }, {523,621,620 ,2526,2527,2474 }, + {522,523,620 ,2473,2526,2474 }, {621,721,720 ,2527,2528,2475 }, + {620,621,720 ,2474,2527,2475 }, {721,813,812 ,2528,2529,559 }, + {720,721,812 ,2475,2528,559 }, {813,900,899 ,2529,2530,2477 }, + {812,813,899 ,559,2529,2477 }, {900,988,899 ,2530,2531,2477 }, + {988,1044,1043 ,2531,2532,2478 }, {899,988,1043 ,2477,2531,2478 }, + {1044,1138,1137 ,2532,2533,2479 }, {1043,1044,1137 ,2478,2532,2479 }, + {1138,1139,1180 ,2533,2534,2518 }, {1137,1138,1180 ,2479,2533,2518 }, + {1139,1230,1229 ,2534,2535,2481 }, {1180,1139,1229 ,2518,2534,2481 }, + {1230,1324,1323 ,2535,2536,2482 }, {1229,1230,1323 ,2481,2535,2482 }, + {1324,1401,1400 ,2536,2537,2483 }, {1323,1324,1400 ,2482,2536,2483 }, + {1473,1472,1400 ,2538,2484,2483 }, {1401,1473,1400 ,2537,2538,2483 }, + {1473,1545,1544 ,2538,2539,2485 }, {1472,1473,1544 ,2484,2538,2485 }, + {1614,1613,1544 ,2070,2486,2485 }, {1545,1614,1544 ,2539,2070,2485 }, + {1614,1681,1680 ,2070,1009,2487 }, {1613,1614,1680 ,2486,2070,2487 }, + {1681,1739,1680 ,1009,1011,2487 }, {1739,1774,1680 ,1011,2488,2487 }, + {1801,1800,1774 ,1433,2489,2488 }, {1739,1801,1774 ,1011,1433,2488 }, + {1801,1837,1836 ,1433,997,2490 }, {1800,1801,1836 ,2489,1433,2490 }, + {1837,380,1836 ,997,2540,2490 }, {5497,3350,4530 ,1375,2541,1376 }, + {2003,2002,1349 ,2542,2493,2519 }, {1349,1971,1948 ,2519,2520,2543 }, + {2028,2027,2002 ,1811,2492,2493 }, {2003,2028,2002 ,2542,1811,2493 }, + {2028,2120,2119 ,1811,1597,2495 }, {2027,2028,2119 ,2492,1811,2495 }, + {2189,2188,2119 ,1599,2496,2495 }, {2120,2189,2119 ,1597,1599,2495 }, + {243,222,2188 ,1465,2508,2496 }, {2189,243,2188 ,1599,1465,2496 }, + {394,179,222 ,1600,2497,2508 }, {243,394,222 ,1465,1600,2508 }, + {1449,1374,179 ,2069,2498,2497 }, {394,1449,179 ,1600,2069,2497 }, + {1449,910,954 ,2069,1930,2499 }, {1374,1449,954 ,2498,2069,2499 }, + {539,914,954 ,2067,2500,2499 }, {910,539,954 ,1930,2067,2499 }, + {77,487,914 ,2544,2501,2500 }, {539,77,914 ,2067,2544,2500 }, + {1194,1100,487 ,393,2466,2501 }, {77,1194,487 ,2544,393,2501 }, + {1194,799,1100 ,393,392,2466 }, {3699,3700,3745 ,2121,2545,2546 }, + {2837,2501,108 ,2511,2547,1336 }, {584,2837,108 ,2512,2511,1336 }, + {2501,107,108 ,2547,2548,1336 }, {929,1014,1013 ,1234,1233,2010 }, + {928,929,1013 ,1997,1234,2010 }, {1014,1062,1061 ,1233,1232,2048 }, + {1013,1014,1061 ,2010,1233,2048 }, {1062,1106,1105 ,1232,1664,2049 }, + {1061,1062,1105 ,2048,1232,2049 }, {1105,1106,1202 ,2049,1664,339 }, + {304,2796,2662 ,532,2462,805 }, {3868,3916,3879 ,970,969,2549 }, + {239,387,339 ,1103,1102,1143 }, {621,622,721 ,2527,2550,2528 }, + {1044,1139,1138 ,2532,2534,2533 }, {391,1623,1088 ,1647,1709,1604 }, + {108,107,2134 ,1336,2548,1337 }, {2243,2324,2242 ,1993,1995,2551 }, + {2121,2190,2189 ,1598,1574,1599 }, {1189,910,1449 ,1495,1930,2069 }, + {107,751,35 ,2548,1365,1338 }, {2134,107,35 ,1337,2548,1338 }, + {5698,5697,5679 ,44,2552,2553 }, {523,622,621 ,2526,2550,2527 }, + {1044,988,1139 ,2532,2531,2534 }, {7271,7270,7229 ,2554,2555,2556 }, + {3547,3546,3494 ,803,2557,736 }, {2742,2029,2003 ,2558,1748,2542 }, + {539,1194,77 ,2067,393,2544 }, {7139,7161,7138 ,1025,2559,2560 }, + {2005,2031,2742 ,1593,1560,2558 }, {290,334,333 ,1042,1267,1043 }, + {334,431,430 ,1267,1268,2524 }, {333,334,430 ,1043,1267,2524 }, + {431,483,482 ,1268,1228,2525 }, {430,431,482 ,2524,1268,2525 }, + {483,524,523 ,1228,1278,2526 }, {482,483,523 ,2525,1228,2526 }, + {524,623,622 ,1278,1100,2550 }, {523,524,622 ,2526,1278,2550 }, + {623,722,721 ,1100,1052,2528 }, {622,623,721 ,2550,1100,2528 }, + {814,813,721 ,1054,2529,2528 }, {722,814,721 ,1052,1054,2528 }, + {901,900,813 ,1428,2530,2529 }, {814,901,813 ,1054,1428,2529 }, + {901,989,988 ,1428,1813,2531 }, {900,901,988 ,2530,1428,2531 }, + {989,1140,1139 ,1813,1499,2534 }, {988,989,1139 ,2531,1813,2534 }, + {1140,1231,1230 ,1499,1332,2535 }, {1139,1140,1230 ,2534,1499,2535 }, + {1231,1325,1324 ,1332,1331,2536 }, {1230,1231,1324 ,2535,1332,2536 }, + {1402,1401,1324 ,1780,2537,2536 }, {1325,1402,1324 ,1331,1780,2536 }, + {1474,1473,1401 ,1649,2538,2537 }, {1402,1474,1401 ,1780,1649,2537 }, + {1474,1546,1545 ,1649,1360,2539 }, {1473,1474,1545 ,2538,1649,2539 }, + {1546,1615,1614 ,1360,1359,2070 }, {1545,1546,1614 ,2539,1360,2070 }, + {2293,2358,292 ,2561,2562,2563 }, {2292,2293,292 ,1983,2561,2563 }, + {253,966,921 ,2564,2565,1922 }, {7129,7139,7138 ,1026,1025,2560 }, + {406,2408,2219 ,2566,1399,177 }, {8085,8073,973 ,2016,2567,2017 }, + {2421,4056,2420 ,2568,1123,273 }, {3315,1195,5194 ,944,725,727 }, + {247,253,921 ,2569,2564,1922 }, {8143,8123,8128 ,2570,2247,959 }, + {2363,2364,2440 ,2571,2513,2572 }, {2914,2895,2857 ,2573,2574,2575 }, + {2437,2436,2358 ,2576,2577,2562 }, {2928,2927,2883 ,2578,516,2579 }, + {3152,3196,1619 ,2580,2581,1003 }, {2626,2732,2625 ,2582,2583,2584 }, + {3151,3152,1550 ,2585,2580,2586 }, {1479,3151,1550 ,1270,2585,2586 }, + {1550,3152,1619 ,2586,2580,1003 }, {2942,554,2366 ,2587,2588,2589 }, + {2857,2765,2914 ,2575,2590,2573 }, {2880,2881,2924 ,2591,2592,2593 }, + {2840,3250,2875 ,2594,2595,2596 }, {2895,2380,2581 ,2574,2597,2598 }, + {3197,3196,3152 ,2599,2581,2580 }, {2515,2599,2598 ,2600,2601,2602 }, + {2890,2889,2848 ,2603,2604,2605 }, {1010,2890,2848 ,1286,2603,2605 }, + {1630,56,3081 ,1016,2606,1017 }, {2635,2388,2389 ,2607,2608,2609 }, + {2952,2489,2570 ,2610,2611,2612 }, {2430,91,2133 ,2613,1984,2614 }, + {6951,6870,6796 ,2615,2616,2617 }, {2804,2786,1062 ,2618,2619,1232 }, + {2786,2829,1062 ,2619,2620,1232 }, {2879,2878,2846 ,2621,2622,2623 }, + {2847,2879,2846 ,2624,2621,2623 }, {2736,2737,2786 ,2625,2626,2619 }, + {2544,2744,3137 ,2451,2627,2628 }, {1201,1200,1103 ,1477,2629,1474 }, + {2508,2507,388 ,2630,2631,2202 }, {6945,6944,6913 ,2632,2633,2634 }, + {2566,2526,2527 ,2635,2636,2637 }, {2566,2614,2526 ,2635,2638,2636 }, + {2476,1304,2300 ,2441,2639,2640 }, {3357,3356,3323 ,883,872,1051 }, + {1010,2848,925 ,1286,2605,1287 }, {2738,2737,2685 ,2641,2626,2642 }, + {2787,2786,2737 ,2643,2619,2626 }, {2738,2787,2737 ,2641,2643,2626 }, + {2684,2683,2612 ,2644,2645,2646 }, {437,2559,2590 ,1149,2647,2648 }, + {2900,2633,2300 ,2649,2650,2640 }, {3596,3648,3595 ,1792,734,927 }, + {2507,2508,2559 ,2631,2630,2647 }, {2508,2590,2559 ,2630,2648,2647 }, + {630,3317,1377 ,2491,1882,2651 }, {2787,2788,2786 ,2643,2652,2619 }, + {4587,135,2320 ,774,2653,1406 }, {2830,2829,2786 ,2654,2620,2619 }, + {2788,2830,2786 ,2652,2654,2619 }, {2830,2866,1107 ,2654,2655,2656 }, + {2829,2830,1107 ,2620,2654,2656 }, {2684,2737,2683 ,2644,2626,2645 }, + {2737,2736,2683 ,2626,2625,2645 }, {2633,2552,2300 ,2650,2657,2640 }, + {2507,437,388 ,2631,1149,2202 }, {2507,2559,437 ,2631,2647,1149 }, + {6435,6480,6479 ,2658,1524,2659 }, {406,2224,746 ,2566,2660,2661 }, + {2265,2297,2296 ,2662,2663,2664 }, {2613,2612,2526 ,2665,2646,2636 }, + {172,244,534 ,667,606,632 }, {2725,2726,2724 ,2666,2667,2668 }, + {172,2543,244 ,667,2669,606 }, {2833,3981,3075 ,2065,915,2670 }, + {6321,7251,3288 ,706,2671,2058 }, {2583,2629,685 ,2672,2673,2674 }, + {2718,2717,2665 ,2675,2676,2677 }, {549,2522,2609 ,1129,2678,1130 }, + {2769,2847,2846 ,2679,2624,2623 }, {6479,6480,6507 ,2659,1524,2680 }, + {2718,2770,2769 ,2675,2681,2679 }, {6809,6860,6859 ,712,473,472 }, + {4672,5892,5878 ,2682,2683,2684 }, {2343,8097,8061 ,2685,1710,453 }, + {966,3039,1090 ,2565,2686,2687 }, {819,818,726 ,2244,1018,669 }, + {3468,3439,3440 ,2688,2689,2690 }, {2445,448,2518 ,1388,1172,2388 }, + {2717,2718,2769 ,2676,2675,2679 }, {691,647,2609 ,1173,973,1130 }, + {921,966,1090 ,1922,2565,2687 }, {1047,2921,1146 ,1328,2691,2467 }, + {3154,3197,3153 ,2692,2599,2693 }, {978,1030,1077 ,1620,1619,2694 }, + {1479,1407,1480 ,1270,1269,2695 }, {7196,3288,7232 ,2401,2058,2124 }, + {2770,2815,2769 ,2681,2696,2679 }, {5328,2238,5301 ,2172,2697,2698 }, + {589,590,244 ,671,607,606 }, {2543,589,244 ,2669,671,606 }, {649,175,702 ,1579,211,2699 }, + {3180,2967,2385 ,2700,2701,2702 }, {2141,4985,6707 ,2015,2703,2704 }, + {2671,2724,2723 ,2705,2668,2706 }, {2193,2125,2563 ,1007,892,2707 }, + {3700,3746,3745 ,2545,2708,2546 }, {8140,8102,3263 ,2269,253,252 }, + {3019,3058,1407 ,2709,2710,1269 }, {204,271,203 ,1722,1608,2711 }, + {2769,2815,2847 ,2679,2696,2624 }, {2636,5650,5452 ,2712,2713,2714 }, + {2343,1074,8073 ,2685,2715,2567 }, {744,743,691 ,1367,971,1173 }, + {1370,3019,1407 ,1500,2709,1269 }, {2553,171,395 ,2716,921,966 }, + {3236,2792,3269 ,2717,2718,2719 }, {2084,860,953 ,1361,1385,1237 }, + {8076,2810,1295 ,617,1728,618 }, {2637,165,1238 ,2720,1869,1569 }, + {2664,2717,628 ,2721,2676,1177 }, {584,108,1294 ,2512,1336,635 }, + {3122,2543,172 ,2068,2669,667 }, {2543,2897,589 ,2669,2722,671 }, + {2897,442,589 ,2722,673,671 }, {289,332,288 ,1044,2471,2723 }, + {3352,3353,3378 ,2724,2424,1442 }, {1098,2760,1096 ,2725,2726,2727 }, + {292,388,239 ,2563,2202,1103 }, {2129,2131,2130 ,1469,656,1505 }, + {6039,6134,5488 ,2728,2729,2730 }, {4881,4293,4957 ,2731,2732,2733 }, + {1477,1550,1549 ,1045,2586,903 }, {4827,4887,5334 ,2734,2735,2736 }, + {2041,1743,553 ,2737,763,792 }, {3273,2543,3122 ,2738,2669,2068 }, + {3273,2623,2543 ,2738,2739,2669 }, {2623,2404,2897 ,2739,2740,2722 }, + {2543,2623,2897 ,2669,2739,2722 }, {2404,442,2897 ,2740,673,2722 }, + {2515,2514,2442 ,2600,2741,2742 }, {748,2541,241 ,1677,1298,314 }, + {252,966,1158 ,2743,2565,2744 }, {3461,3490,3430 ,1152,2502,1976 }, + {4924,1166,7073 ,2745,2746,662 }, {7074,4924,7073 ,2747,2745,662 }, + {3247,6430,144 ,1494,2748,853 }, {7344,7343,7285 ,2749,2750,2751 }, + {2903,2425,1411 ,2752,2753,850 }, {597,2903,1411 ,791,2752,850 }, + {2425,2759,2041 ,2753,2754,2737 }, {1411,2425,2041 ,850,2753,2737 }, + {2759,2458,1743 ,2754,2755,763 }, {2041,2759,1743 ,2737,2754,763 }, + {3122,2623,3273 ,2068,2739,2738 }, {2404,2479,442 ,2740,2756,673 }, + {2842,310,442 ,2757,674,673 }, {2479,2842,442 ,2756,2757,673 }, + {2842,2694,310 ,2757,811,674 }, {4887,5377,5376 ,2735,361,2758 }, + {3353,3379,3378 ,2424,1979,1442 }, {2728,2727,2676 ,2759,2760,2761 }, + {2870,2528,2367 ,2762,2763,2764 }, {2666,2719,2718 ,2765,2766,2675 }, + {2687,2635,2761 ,2767,2607,2768 }, {2718,2719,2770 ,2675,2766,2681 }, + {1407,1406,1370 ,1269,1135,1500 }, {8092,6531,8120 ,2267,2280,906 }, + {2614,2612,2613 ,2638,2646,2665 }, {3153,3197,3152 ,2693,2599,2580 }, + {2597,2640,2596 ,2769,2770,2771 }, {2372,597,2654 ,2772,791,2773 }, + {2372,2903,597 ,2772,2752,791 }, {2458,2546,3122 ,2755,2774,2068 }, + {1743,2458,3122 ,763,2755,2068 }, {2546,2699,3122 ,2774,2775,2068 }, + {3122,2699,2623 ,2068,2775,2739 }, {2479,2627,2842 ,2756,2776,2757 }, + {2627,2694,2842 ,2776,811,2757 }, {2411,2528,2870 ,2777,2763,2762 }, + {2424,2411,2870 ,2778,2777,2762 }, {2635,3250,2388 ,2607,2595,2608 }, + {2556,2430,2388 ,2779,2613,2608 }, {2931,2979,2978 ,2780,2781,2782 }, + {3136,2372,2654 ,2783,2772,2773 }, {2372,2425,2903 ,2772,2753,2752 }, + {2458,2457,2546 ,2755,2784,2774 }, {2546,2828,2623 ,2774,2785,2739 }, + {2699,2546,2623 ,2775,2774,2739 }, {2854,2703,2479 ,2786,2787,2756 }, + {2404,2854,2479 ,2740,2786,2756 }, {2479,2703,2627 ,2756,2787,2776 }, + {2615,2614,2566 ,2788,2638,2635 }, {198,127,199 ,1392,1395,2789 }, + {3236,3269,2456 ,2717,2719,2790 }, {2649,3114,2536 ,2791,2792,2793 }, + {2687,2761,2857 ,2767,2768,2575 }, {2840,2388,3250 ,2594,2608,2595 }, + {3089,3112,3132 ,2794,2795,2796 }, {3181,2425,2372 ,2797,2753,2772 }, + {2758,2759,2425 ,2798,2754,2753 }, {3181,2758,2425 ,2797,2798,2753 }, + {2758,2457,2458 ,2798,2784,2755 }, {2759,2758,2458 ,2754,2798,2755 }, + {2828,2992,2623 ,2785,2799,2739 }, {2992,2348,2404 ,2799,2800,2740 }, + {2623,2992,2404 ,2739,2799,2740 }, {2404,2348,2854 ,2740,2800,2786 }, + {3513,2582,3404 ,441,873,2170 }, {2739,2738,2685 ,2801,2641,2642 }, + {2705,2739,2685 ,2802,2801,2642 }, {3048,1904,3049 ,2165,2164,2803 }, + {2728,2756,2727 ,2759,2804,2760 }, {3090,3994,3993 ,2805,2806,2807 }, + {3112,3090,3993 ,2795,2805,2807 }, {1840,940,1869 ,198,197,2808 }, + {3128,3169,3127 ,2809,2810,2811 }, {2616,3136,2548 ,2812,2783,2813 }, + {2486,2485,7615 ,2814,2815,2816 }, {2616,2372,3136 ,2812,2772,2783 }, + {2642,2758,3181 ,2817,2798,2797 }, {2288,2642,3181 ,2818,2817,2797 }, + {2758,2642,2457 ,2798,2817,2784 }, {2546,2992,2828 ,2774,2799,2785 }, + {2348,2757,2703 ,2800,2819,2787 }, {2854,2348,2703 ,2786,2800,2787 }, + {2757,2632,2627 ,2819,2820,2776 }, {2703,2757,2627 ,2787,2819,2776 }, + {2627,2689,145 ,2776,2821,810 }, {162,238,100 ,2822,868,2823 }, + {2777,2823,2822 ,2824,2825,2826 }, {2788,2787,2738 ,2652,2643,2641 }, + {2739,2788,2738 ,2801,2652,2641 }, {3069,3070,3111 ,2827,2828,2829 }, + {2780,2779,2727 ,2830,2831,2760 }, {2756,2780,2727 ,2804,2830,2760 }, + {2528,3183,3135 ,2763,2832,2833 }, {2367,2528,3135 ,2764,2763,2833 }, + {2838,2372,2616 ,2834,2772,2812 }, {2838,2996,2372 ,2834,2835,2772 }, + {2288,3181,2372 ,2818,2797,2772 }, {2996,2288,2372 ,2835,2818,2772 }, + {2642,2605,2457 ,2817,2836,2784 }, {2605,3225,2546 ,2836,2837,2774 }, + {2457,2605,2546 ,2784,2836,2774 }, {2546,3225,2992 ,2774,2837,2799 }, + {2348,2574,2757 ,2800,2838,2819 }, {2574,2448,2632 ,2838,2839,2820 }, + {2757,2574,2632 ,2819,2838,2820 }, {2632,2448,2627 ,2820,2839,2776 }, + {2448,2553,2689 ,2839,2716,2821 }, {2627,2448,2689 ,2776,2839,2821 }, + {5698,5699,5726 ,44,43,515 }, {2622,2549,2567 ,2840,2841,2842 }, + {2890,2936,2889 ,2603,2843,2604 }, {2830,2867,2866 ,2654,2844,2655 }, + {2692,3081,56 ,2845,1017,2606 }, {2534,198,199 ,2846,1392,2789 }, + {2764,2839,3142 ,2847,2848,2849 }, {1907,1248,3878 ,1245,2850,1248 }, + {3142,2839,90 ,2849,2848,2851 }, {2344,2554,2702 ,2852,2853,2854 }, + {2401,2995,2702 ,2855,2856,2854 }, {2554,2401,2702 ,2853,2855,2854 }, + {3070,3069,3031 ,2828,2827,2857 }, {3032,3070,3031 ,2858,2828,2857 }, + {2804,1062,929 ,2618,1232,1234 }, {3097,2838,2616 ,2859,2834,2812 }, + {2834,3097,2616 ,2860,2859,2812 }, {3097,2996,2838 ,2859,2835,2834 }, + {2904,2288,2996 ,2861,2818,2835 }, {3097,2904,2996 ,2859,2861,2835 }, + {2606,2642,2288 ,2862,2817,2818 }, {2904,2606,2288 ,2861,2862,2818 }, + {2606,2783,2605 ,2862,2863,2836 }, {2642,2606,2605 ,2817,2862,2836 }, + {2783,2519,3225 ,2863,2864,2837 }, {2605,2783,3225 ,2836,2863,2837 }, + {3225,2519,2992 ,2837,2864,2799 }, {2767,299,2348 ,2865,2866,2800 }, + {2992,2767,2348 ,2799,2865,2800 }, {2348,299,2574 ,2800,2866,2838 }, + {8083,8099,5771 ,2103,2867,2868 }, {2820,2819,2773 ,2869,2870,2871 }, + {369,199,271 ,1521,2789,1608 }, {2152,821,2136 ,1545,2872,1546 }, + {5013,5950,8108 ,684,2873,2287 }, {1285,1135,1226 ,1944,1943,2354 }, + {2205,2222,6563 ,2874,2509,464 }, {2839,2202,90 ,2848,2875,2851 }, + {1249,2488,2617 ,2876,2877,2878 }, {2488,2542,2371 ,2877,2879,2880 }, + {2617,2488,2371 ,2878,2877,2880 }, {2269,2506,2505 ,2881,2882,2883 }, + {3207,3237,3206 ,2884,2885,2886 }, {2736,2786,929 ,2625,2619,1234 }, + {2735,2736,929 ,2887,2625,1234 }, {2855,3097,2834 ,2888,2859,2860 }, + {2519,2766,2992 ,2864,2889,2799 }, {2992,2766,2767 ,2799,2889,2865 }, + {3020,3021,3059 ,2890,2891,2892 }, {2665,2717,2664 ,2677,2676,2721 }, + {2006,2005,2742 ,1181,1593,2558 }, {2633,2656,2584 ,2650,2893,2894 }, + {2656,2331,2584 ,2893,2895,2894 }, {3054,2763,1787 ,916,2896,2897 }, + {2504,2331,2461 ,2898,2895,2899 }, {5914,6098,5191 ,1566,1565,2900 }, + {3236,3237,2792 ,2717,2885,2718 }, {1663,3081,115 ,1015,1017,2901 }, + {2786,2804,929 ,2619,2618,1234 }, {3317,1804,3347 ,1882,2902,2098 }, + {3191,2403,4961 ,2903,2904,2905 }, {2403,2970,2834 ,2904,2906,2860 }, + {2970,2855,2834 ,2906,2888,2860 }, {2855,2904,3097 ,2888,2861,2859 }, + {2783,2520,2519 ,2863,2907,2864 }, {2519,2520,2766 ,2864,2907,2889 }, + {2766,3015,299 ,2889,2908,2866 }, {2767,2766,299 ,2865,2889,2866 }, + {3015,3078,299 ,2908,2909,2866 }, {3078,2752,2574 ,2909,2910,2838 }, + {299,3078,2574 ,2866,2909,2838 }, {2752,2951,2448 ,2910,2911,2839 }, + {2574,2752,2448 ,2838,2910,2839 }, {2951,2378,2553 ,2911,2912,2716 }, + {2448,2951,2553 ,2839,2911,2716 }, {2553,2378,171 ,2716,2912,921 }, + {1687,1686,3196 ,1104,1004,2581 }, {2886,2885,2821 ,2913,2914,2915 }, + {2774,2773,2721 ,2916,2871,2917 }, {2526,2612,2565 ,2636,2646,2918 }, + {2626,2839,2764 ,2582,2848,2847 }, {411,2495,2899 ,2919,2920,2921 }, + {3223,2495,2911 ,2922,2920,2923 }, {2899,2495,3223 ,2921,2920,2922 }, + {2495,2366,2911 ,2920,2589,2923 }, {2911,2366,1336 ,2923,2589,2924 }, + {2371,2656,2633 ,2880,2893,2650 }, {3747,3746,3700 ,2925,2708,2545 }, + {2946,2338,4040 ,1062,458,460 }, {2696,2970,2403 ,2926,2906,2904 }, + {2696,2855,2970 ,2926,2888,2906 }, {2603,2904,2855 ,2927,2861,2888 }, + {2898,2606,2904 ,2928,2862,2861 }, {2603,2898,2904 ,2927,2928,2861 }, + {2898,2784,2783 ,2928,2929,2863 }, {2606,2898,2783 ,2862,2928,2863 }, + {2783,2784,2520 ,2863,2929,2907 }, {2766,3043,3015 ,2889,2930,2908 }, + {3015,3043,3078 ,2908,2930,2909 }, {3043,2712,2752 ,2930,2931,2910 }, + {3078,3043,2752 ,2909,2930,2910 }, {2641,2951,2752 ,2932,2911,2910 }, + {2712,2641,2752 ,2931,2932,2910 }, {2641,2498,2378 ,2932,2933,2912 }, + {2951,2641,2378 ,2911,2932,2912 }, {5681,5699,2453 ,2934,43,42 }, + {2638,2592,2666 ,2935,2936,2765 }, {2526,2565,2525 ,2636,2918,2937 }, + {8116,8109,8119 ,172,2938,184 }, {1088,1452,1592 ,1604,1606,2939 }, + {2820,2884,2883 ,2869,2940,2579 }, {2444,2443,2368 ,2941,2942,2943 }, + {2715,3095,3013 ,2944,2945,2946 }, {3179,2617,2503 ,2947,2878,2948 }, + {2666,2667,2719 ,2765,2949,2766 }, {987,3452,3977 ,2950,1794,440 }, + {3327,1387,3636 ,439,2951,1060 }, {1336,785,2085 ,2924,2952,657 }, + {3061,3101,3100 ,2953,2954,2955 }, {2868,2869,2907 ,1577,1576,111 }, + {3237,3236,3206 ,2885,2717,2886 }, {2165,2696,2403 ,1041,2926,2904 }, + {2969,2855,2696 ,2956,2888,2926 }, {2483,2898,2603 ,2957,2928,2927 }, + {3186,2483,2603 ,2958,2957,2927 }, {3220,2784,2898 ,2959,2929,2928 }, + {2483,3220,2898 ,2957,2959,2928 }, {2784,3220,2520 ,2929,2959,2907 }, + {2520,2917,2766 ,2907,2960,2889 }, {2766,2917,3043 ,2889,2960,2930 }, + {2498,2496,171 ,2933,2961,921 }, {2378,2498,171 ,2912,2933,921 }, + {3264,2301,872 ,2468,2132,2962 }, {2492,493,2496 ,2963,1785,2961 }, + {1390,131,151 ,2964,2965,2966 }, {3215,3013,2354 ,2967,2946,2968 }, + {4240,3796,3797 ,2060,2969,2061 }, {8084,8127,8110 ,1139,2970,1140 }, + {2631,2840,2586 ,2971,2594,2972 }, {2380,2586,2581 ,2597,2972,2598 }, + {2599,2600,2673 ,2601,2973,2974 }, {2600,2674,2673 ,2973,2975,2974 }, + {3596,3595,3545 ,1792,927,926 }, {3595,3594,3544 ,927,2522,1153 }, + {1002,2431,1283 ,2976,2977,2978 }, {2363,2362,2297 ,2571,2979,2663 }, + {2362,2363,2439 ,2979,2571,2980 }, {2363,2440,2439 ,2571,2572,2980 }, + {2440,2512,2439 ,2572,2981,2980 }, {2671,2670,2595 ,2705,2982,2983 }, + {3138,2537,1050 ,2984,2985,2282 }, {2515,2600,2599 ,2600,2973,2601 }, + {3504,3468,3441 ,2986,2688,2987 }, {2166,2947,2696 ,1040,2988,2926 }, + {2165,2166,2696 ,1041,1040,2926 }, {2253,2603,2855 ,2989,2927,2888 }, + {2969,2253,2855 ,2956,2989,2888 }, {2253,3186,2603 ,2989,2958,2927 }, + {3220,2561,2520 ,2959,2990,2907 }, {2561,2562,2520 ,2990,2991,2907 }, + {2520,2562,2917 ,2907,2991,2960 }, {3043,2539,2712 ,2930,2992,2931 }, + {2539,2716,2712 ,2992,2993,2931 }, {2490,2641,2712 ,2994,2932,2931 }, + {2716,2490,2712 ,2993,2994,2931 }, {2490,2498,2641 ,2994,2933,2932 }, + {2845,2844,2800 ,2995,1329,2996 }, {2977,2999,2976 ,2997,2998,2999 }, + {2364,2441,2440 ,2513,3000,2572 }, {111,258,1653 ,1154,3001,106 }, + {4543,4547,4980 ,3002,242,241 }, {2929,2930,2928 ,3003,3004,2578 }, + {2493,2467,1789 ,3005,3006,3007 }, {2434,2400,2545 ,1118,1117,3008 }, + {3492,3491,3461 ,930,955,1152 }, {2673,2674,2700 ,2974,2975,3009 }, + {2653,2964,2131 ,3010,3011,656 }, {3382,3432,3407 ,870,929,3012 }, + {1049,119,2084 ,1362,2278,1361 }, {2570,2489,2298 ,2612,2611,3013 }, + {2723,2722,2669 ,2706,3014,3015 }, {2671,2723,2669 ,2705,2706,3015 }, + {2671,2669,2670 ,2705,3015,2982 }, {4554,6299,1989 ,1307,760,3016 }, + {355,250,301 ,1876,1916,1857 }, {2947,2969,2696 ,2988,2956,2926 }, + {2919,2253,2969 ,3017,2989,2956 }, {2253,2483,3186 ,2989,2957,2958 }, + {3220,2562,2561 ,2959,2991,2990 }, {2562,114,2917 ,2991,3018,2960 }, + {2989,3043,2917 ,3019,2930,2960 }, {114,2989,2917 ,3018,3019,2960 }, + {2989,2539,3043 ,3019,2992,2930 }, {2751,2498,2490 ,3020,2933,2994 }, + {2751,2492,2496 ,3020,2963,2961 }, {2498,2751,2496 ,2933,3020,2961 }, + {3431,3461,3430 ,3021,1152,1976 }, {2133,157,250 ,2614,1877,1916 }, + {925,2848,2802 ,1287,2605,3022 }, {3123,3153,3152 ,3023,2693,2580 }, + {4563,2382,126 ,3024,3025,1394 }, {1047,2877,2921 ,1328,3026,2691 }, + {3212,2942,2366 ,3027,2587,2589 }, {3154,1590,3198 ,2692,3028,1168 }, + {1480,3123,3152 ,2695,3023,2580 }, {2625,2732,2839 ,2584,2583,2848 }, + {2732,3116,2839 ,2583,3029,2848 }, {3462,3461,3431 ,931,1152,3021 }, + {835,8127,4106 ,3030,2970,1742 }, {2862,1984,1917 ,3031,831,833 }, + {2069,2530,1984 ,1711,2389,831 }, {2862,2069,1984 ,3031,1711,831 }, + {1238,1000,1515 ,1569,1384,1502 }, {2570,2298,2963 ,2612,3013,3032 }, + {2537,2677,1050 ,2985,3033,2282 }, {1972,2003,1349 ,3034,2542,2519 }, + {8109,8110,8127 ,2938,1140,2970 }, {2619,2969,2947 ,3035,2956,2988 }, + {2089,2619,2947 ,1325,3035,2988 }, {2619,2919,2969 ,3035,3017,2956 }, + {2253,2491,2483 ,2989,3036,2957 }, {2491,3214,3220 ,3036,3037,2959 }, + {2483,2491,3220 ,2957,3036,2959 }, {3214,2562,3220 ,3037,2991,2959 }, + {2482,2539,2989 ,3038,2992,3019 }, {2858,2873,2492 ,3039,3040,2963 }, + {2751,2858,2492 ,3020,3039,2963 }, {2873,494,2492 ,3040,3041,2963 }, + {6430,3777,5733 ,2748,1582,1584 }, {3228,1725,178 ,3042,1747,3043 }, + {3221,8127,835 ,183,2970,3030 }, {3116,3219,2839 ,3029,3044,2848 }, + {2988,1336,2653 ,3045,2924,3010 }, {2604,2964,2653 ,3046,3011,3010 }, + {2768,221,1553 ,1297,1296,1669 }, {3698,3699,3744 ,2122,2121,2390 }, + {2475,3215,2793 ,3047,2967,3048 }, {3433,3432,3382 ,3049,929,870 }, + {3957,3997,3996 ,295,380,1928 }, {3956,3957,3996 ,1383,295,1928 }, + {2343,8085,8067 ,2685,2016,2168 }, {3432,3462,3431 ,929,931,3021 }, + {1337,1844,1782 ,2148,911,1105 }, {2537,2572,968 ,2985,3050,1661 }, + {2851,2908,2503 ,3051,3052,2948 }, {2908,3179,2503 ,3052,2947,2948 }, + {5681,5659,5660 ,2934,3053,3054 }, {2943,2851,2503 ,3055,3051,2948 }, + {2919,2679,2253 ,3017,3056,2989 }, {2679,2463,2491 ,3056,3057,3036 }, + {2253,2679,2491 ,2989,3056,3036 }, {2463,2398,3214 ,3057,3058,3037 }, + {2491,2463,3214 ,3036,3057,3037 }, {2575,2562,3214 ,3059,2991,3037 }, + {2398,2575,3214 ,3058,3059,3037 }, {2575,114,2562 ,3059,3018,2991 }, + {2482,2989,114 ,3038,3019,3018 }, {2762,2716,2539 ,3060,2993,2992 }, + {2482,2762,2539 ,3038,3060,2992 }, {2762,3280,2716 ,3060,3061,2993 }, + {3092,2490,2716 ,3062,2994,2993 }, {3280,3092,2716 ,3061,3062,2993 }, + {3092,2858,2751 ,3062,3039,3020 }, {2490,3092,2751 ,2994,3062,3020 }, + {3192,3285,6689 ,3063,3064,3065 }, {2383,199,127 ,3066,2789,1395 }, + {4254,4255,4019 ,416,165,227 }, {2316,2383,2382 ,3067,3066,3025 }, + {2315,2316,2382 ,3068,3067,3025 }, {2383,127,2382 ,3066,1395,3025 }, + {4031,2650,2585 ,3069,914,875 }, {2646,2614,2615 ,3070,2638,2788 }, + {2686,2685,2614 ,3071,2642,2638 }, {925,2782,2730 ,1287,3072,2150 }, + {845,943,4970 ,1828,1807,3073 }, {2839,3219,2202 ,2848,3044,2875 }, + {3219,650,2202 ,3044,3074,2875 }, {3432,3431,3407 ,929,3021,3012 }, + {3543,3542,3488 ,3075,979,1980 }, {2576,1665,2967 ,3076,3077,2701 }, + {3745,3746,3744 ,2546,2708,2390 }, {2628,3180,2835 ,3078,2700,3079 }, + {2293,2263,2294 ,2561,3080,3081 }, {3592,3591,3542 ,2084,1652,979 }, + {3543,3592,3542 ,3075,2084,979 }, {1183,155,154 ,1238,1497,1466 }, + {2537,3138,2572 ,2985,2984,3050 }, {2619,2902,2919 ,3035,3082,3017 }, + {2919,2902,2679 ,3017,3082,3056 }, {3044,114,2575 ,3083,3018,3059 }, + {3044,2402,114 ,3083,3084,3018 }, {2402,2482,114 ,3084,3038,3018 }, + {2540,2762,2482 ,3085,3060,3038 }, {2762,2540,3280 ,3060,3085,3061 }, + {2794,3092,3280 ,3086,3062,3061 }, {2540,2794,3280 ,3085,3086,3061 }, + {2492,494,493 ,2963,3041,1785 }, {3904,3958,3903 ,1264,294,296 }, + {3183,219,1855 ,2832,3087,3088 }, {2646,2686,2614 ,3070,3071,2638 }, + {3671,978,1077 ,3089,1620,2694 }, {1304,2431,4377 ,2639,2977,3090 }, + {2686,2705,2685 ,3071,2802,2642 }, {2544,3137,2966 ,2451,2628,3091 }, + {2298,2793,2744 ,3013,3048,2627 }, {925,2802,2782 ,1287,3022,3072 }, + {6348,7253,6349 ,1607,1855,705 }, {3064,3063,3025 ,3092,3093,3094 }, + {1336,2604,2653 ,2924,3046,3010 }, {3026,3064,3025 ,3095,3092,3094 }, + {2628,2576,2967 ,3078,3076,2701 }, {3061,3062,3101 ,2953,3096,2954 }, + {3062,3102,3101 ,3096,3097,2954 }, {3024,3062,3023 ,3098,3096,3099 }, + {3878,106,1156 ,1248,1073,3100 }, {3431,3430,3380 ,3021,1976,1514 }, + {460,1876,1853 ,1999,1386,3101 }, {2619,631,2902 ,3035,1426,3082 }, + {2902,631,2679 ,3082,1426,3056 }, {2402,3044,2575 ,3084,3083,3059 }, + {2916,2482,2402 ,3102,3038,3084 }, {2714,2858,3092 ,3103,3039,3062 }, + {2794,2714,3092 ,3086,3103,3062 }, {3082,2873,2858 ,3104,3040,3039 }, + {2714,3082,2858 ,3103,3104,3039 }, {3495,3434,3496 ,735,737,3105 }, + {2619,2090,631 ,3035,1324,1426 }, {845,4970,760 ,1828,3073,3106 }, + {2131,2865,2201 ,656,3107,2455 }, {2973,3021,2972 ,3108,2891,2047 }, + {4614,4613,4590 ,3109,1488,1490 }, {2594,2593,2511 ,3110,3111,3112 }, + {3062,3061,3023 ,3096,2953,3099 }, {3215,2354,2426 ,2967,2968,3113 }, + {2354,3013,3173 ,2968,2946,3114 }, {2793,3215,2426 ,3048,2967,3113 }, + {2924,2925,2974 ,2593,3115,3116 }, {2744,2793,3193 ,2627,3048,3117 }, + {631,2944,2679 ,1426,3118,3056 }, {2395,2463,2679 ,3119,3057,3056 }, + {2480,2398,2463 ,3120,3058,3057 }, {2395,2480,2463 ,3119,3120,3057 }, + {2398,2480,2575 ,3058,3120,3059 }, {2575,3174,2402 ,3059,3121,3084 }, + {2289,2482,2916 ,3122,3038,3102 }, {2482,2289,2540 ,3038,3122,3085 }, + {2836,2873,3082 ,3123,3040,3104 }, {1806,494,2873 ,3124,3041,3040 }, + {2836,1806,2873 ,3123,3124,3040 }, {494,1806,802 ,3041,3124,400 }, + {3890,3926,3889 ,1913,3125,3126 }, {6434,6435,6479 ,3127,2658,2659 }, + {4576,7524,4575 ,1628,1630,3128 }, {203,2383,2316 ,2711,3066,3067 }, + {2317,203,2316 ,3129,2711,3067 }, {270,199,2383 ,3130,2789,3066 }, + {203,270,2383 ,2711,3130,3066 }, {2609,790,744 ,1130,1339,1367 }, + {3011,2624,2914 ,3131,3132,2573 }, {6707,6643,8101 ,2704,3133,383 }, + {3149,2537,3143 ,3134,2985,3135 }, {3064,3104,3063 ,3092,3136,3093 }, + {3104,3103,3063 ,3136,3137,3093 }, {2152,391,821 ,1545,1647,2872 }, + {2669,2668,2593 ,3015,3138,3111 }, {2576,795,1665 ,3076,302,3077 }, + {3193,2630,2894 ,3117,3139,3140 }, {2628,2967,3180 ,3078,2701,2700 }, + {3193,2793,2630 ,3117,3048,3139 }, {2570,2963,2544 ,2612,3032,2451 }, + {2341,8064,3008 ,845,3141,546 }, {4212,4213,294 ,895,1070,945 }, + {928,830,929 ,1997,1996,1234 }, {2644,2944,631 ,3142,3118,1426 }, + {2478,2644,631 ,3143,3142,1426 }, {2644,2679,2944 ,3142,3056,3118 }, + {2396,2395,2679 ,3144,3119,3056 }, {2644,2396,2679 ,3142,3144,3056 }, + {2312,2339,2575 ,3145,3146,3059 }, {2480,2312,2575 ,3120,3145,3059 }, + {2575,2339,3174 ,3059,3146,3121 }, {2339,2648,2402 ,3146,3147,3084 }, + {3174,2339,2402 ,3121,3146,3084 }, {2916,3115,2289 ,3102,3148,3122 }, + {3077,2794,2540 ,3149,3086,3085 }, {2289,3077,2540 ,3122,3149,3085 }, + {2347,2714,2794 ,3150,3103,3086 }, {3077,2347,2794 ,3149,3150,3086 }, + {2836,3082,2714 ,3123,3104,3103 }, {2347,2836,2714 ,3150,3123,3103 }, + {3496,3434,3464 ,3105,737,3151 }, {2316,2275,2317 ,3067,3152,3129 }, + {1165,4831,3472 ,3153,3154,3155 }, {2293,2294,2358 ,2561,3081,2562 }, + {3489,3543,3488 ,1977,3075,1980 }, {2544,2966,2622 ,2451,3091,2840 }, + {6796,8063,8136 ,2617,465,2449 }, {2669,2667,2668 ,3015,2949,3138 }, + {2818,2882,2881 ,3156,518,2592 }, {2882,2926,2925 ,518,517,3115 }, + {2881,2882,2925 ,2592,518,3115 }, {1550,1477,1479 ,2586,1045,1270 }, + {953,1048,2084 ,1237,1236,1361 }, {2926,2975,2974 ,517,3157,3116 }, + {2975,2997,2974 ,3157,3158,3116 }, {2925,2926,2974 ,3115,517,3116 }, + {3155,1552,3100 ,3159,2266,2955 }, {3101,3155,3100 ,2954,3159,2955 }, + {2295,2296,2360 ,3160,2664,3161 }, {3024,3023,2997 ,3098,3099,3158 }, + {2511,2510,2438 ,3112,3162,3163 }, {2975,3024,2997 ,3157,3098,3158 }, + {3156,3155,3101 ,3164,3159,2954 }, {3137,3193,2894 ,2628,3117,3140 }, + {2312,2480,2395 ,3145,3120,3119 }, {2396,2312,2395 ,3144,3145,3119 }, + {2648,2806,2916 ,3147,3165,3102 }, {2402,2648,2916 ,3084,3147,3102 }, + {2916,2806,3115 ,3102,3165,3148 }, {2377,2347,3077 ,3166,3150,3149 }, + {3042,2377,3077 ,3167,3166,3149 }, {2355,1806,2836 ,3168,3124,3123 }, + {3490,3489,3430 ,2502,1977,1976 }, {1923,211,459 ,418,415,696 }, + {1145,1146,1236 ,1277,2467,1434 }, {1187,1367,1919 ,1778,574,1779 }, + {2597,2596,2512 ,2769,2771,2981 }, {2691,2353,2570 ,3169,3170,2612 }, + {3149,3143,968 ,3134,3135,1661 }, {5616,5615,3274 ,646,3171,3172 }, + {2721,2720,2667 ,2917,3173,2949 }, {2669,2721,2667 ,3015,2917,2949 }, + {2817,2818,2881 ,3174,3156,2592 }, {6968,7027,7008 ,595,3175,3176 }, + {3277,8066,8098 ,961,960,907 }, {3155,3156,1552 ,3159,3164,2266 }, + {2438,2437,2360 ,3163,2576,3161 }, {2361,2438,2360 ,3177,3163,3161 }, + {2667,2720,2719 ,2949,3173,2766 }, {3137,2744,3193 ,2628,2627,3117 }, + {119,460,2084 ,2278,1999,1361 }, {1443,5616,3274 ,182,646,3172 }, + {2135,2136,2478 ,1425,1546,3143 }, {2648,298,2806 ,3147,3178,3165 }, + {2806,2860,2289 ,3165,3179,3122 }, {3115,2806,2289 ,3148,3165,3122 }, + {2466,3042,3077 ,3180,3167,3149 }, {2289,2466,3077 ,3122,3180,3149 }, + {2377,2355,2836 ,3166,3168,3123 }, {2347,2377,2836 ,3150,3166,3123 }, + {2355,2465,1806 ,3168,3181,3124 }, {3041,1860,3618 ,3182,3183,37 }, + {553,1743,1727 ,792,763,793 }, {6388,6435,6434 ,3184,2658,3127 }, + {204,203,2317 ,1722,2711,3129 }, {2318,204,2317 ,3185,1722,3129 }, + {5191,6388,6434 ,2900,3184,3127 }, {3228,178,2532 ,3042,3043,3186 }, + {1107,2866,1205 ,2656,2655,3187 }, {3260,3149,968 ,3188,3134,1661 }, + {2909,2911,2678 ,3189,2923,3190 }, {2777,2801,2776 ,2824,3191,3192 }, + {3104,3158,3157 ,3136,3193,3194 }, {2963,2744,2544 ,3032,2627,2451 }, + {1657,1725,3228 ,1781,1747,3042 }, {2874,2628,2386 ,3195,3078,3196 }, + {2818,2817,2771 ,3156,3174,3197 }, {2772,2818,2771 ,3198,3156,3197 }, + {2136,1512,2478 ,1546,3199,3143 }, {3246,2644,2478 ,3200,3142,3143 }, + {1512,3246,2478 ,3199,3200,3143 }, {2399,2396,2644 ,3201,3144,3142 }, + {2399,2529,2312 ,3201,3202,3145 }, {2396,2399,2312 ,3144,3201,3145 }, + {2312,2529,2339 ,3145,3202,3146 }, {3245,298,2648 ,3203,3178,3147 }, + {2339,3245,2648 ,3146,3203,3147 }, {2860,2753,2289 ,3179,456,3122 }, + {2289,2753,2466 ,3122,456,3180 }, {110,111,226 ,743,1154,108 }, + {2590,2589,437 ,2648,3204,1149 }, {5390,5037,6317 ,3205,3206,3207 }, + {2521,2533,2506 ,3208,3209,2882 }, {2867,2940,1205 ,2844,110,3187 }, + {2011,3001,5615 ,129,3210,3171 }, {1208,1668,1942 ,3211,3212,2337 }, + {1687,1740,1782 ,1104,3213,1105 }, {2966,3137,2894 ,3091,2628,3140 }, + {3060,3061,3100 ,3214,2953,2955 }, {2779,2778,2727 ,2831,3215,2760 }, + {165,1373,341 ,1869,1501,1852 }, {787,802,4130 ,209,400,210 }, + {3173,2385,2434 ,3114,2702,1118 }, {2385,2433,2434 ,2702,1116,1118 }, + {2011,3913,3001 ,129,841,3210 }, {2296,2361,2360 ,2664,3177,3161 }, + {2720,2772,2771 ,3173,3198,3197 }, {2719,2720,2771 ,2766,3173,3197 }, + {2883,2882,2818 ,2579,518,3156 }, {2386,2628,2835 ,3196,3078,3079 }, + {2549,2894,2370 ,2841,3140,3216 }, {3102,3156,3101 ,3097,3164,2954 }, + {2266,3246,1512 ,3217,3200,3199 }, {821,2266,1512 ,2872,3217,3199 }, + {2266,2644,3246 ,3217,3142,3200 }, {2414,2399,2644 ,3218,3201,3142 }, + {2266,2414,2644 ,3217,3218,3142 }, {2414,2299,2529 ,3218,3219,3202 }, + {2399,2414,2529 ,3201,3218,3202 }, {2299,2447,2339 ,3219,3220,3146 }, + {2529,2299,2339 ,3202,3219,3146 }, {2339,2447,3245 ,3146,3220,3203 }, + {2447,2446,298 ,3220,3221,3178 }, {3245,2447,298 ,3203,3220,3178 }, + {2806,2753,2860 ,3165,456,3179 }, {2753,2538,3042 ,456,3222,3167 }, + {2466,2753,3042 ,3180,456,3167 }, {3119,2377,3042 ,3223,3166,3167 }, + {2538,3119,3042 ,3222,3223,3167 }, {3119,2747,2355 ,3223,3224,3168 }, + {2377,3119,2355 ,3166,3223,3168 }, {2355,2747,2465 ,3168,3224,3181 }, + {3970,3969,3925 ,3225,3226,3227 }, {2319,204,2318 ,3228,1722,3185 }, + {1211,2255,2697 ,2007,3229,2006 }, {2489,2715,2475 ,2611,2944,3047 }, + {2506,2533,2805 ,2882,3209,3230 }, {3990,3299,3254 ,3231,3232,3233 }, + {2602,2675,2600 ,3234,3235,2973 }, {2417,2874,2386 ,3236,3195,3196 }, + {2427,2379,2677 ,3237,3238,3033 }, {286,287,331 ,2446,2470,2403 }, + {2624,2949,3405 ,3132,3239,1798 }, {3429,3460,3459 ,1978,3240,3241 }, + {2886,2863,2931 ,2913,3242,2780 }, {2863,2932,2931 ,3242,3243,2780 }, + {2512,2511,2438 ,2981,3112,3163 }, {593,4223,1599 ,1108,1934,391 }, + {3199,1726,1658 ,3244,1746,1782 }, {2511,2512,2595 ,3112,2981,2983 }, + {2510,2593,2592 ,3162,3111,2936 }, {2532,1874,1337 ,3186,962,2148 }, + {2549,2370,2567 ,2841,3216,2842 }, {2038,1983,1984 ,1551,1001,831 }, + {2963,2298,2744 ,3032,3013,2627 }, {2819,2883,2818 ,2870,2579,3156 }, + {2475,3013,3215 ,3047,2946,2967 }, {2462,2413,2417 ,3245,3246,3236 }, + {2413,2900,2417 ,3246,2649,3236 }, {2715,2462,2417 ,2944,3245,3236 }, + {2900,2551,2874 ,2649,3247,3195 }, {1304,1255,2576 ,2639,3248,3076 }, + {2551,2576,3175 ,3247,3076,3249 }, {2551,1304,2576 ,3247,2639,3076 }, + {5616,2011,5615 ,646,129,3171 }, {2446,2849,2806 ,3221,3250,3165 }, + {298,2446,2806 ,3178,3221,3165 }, {2849,3144,2806 ,3250,3251,3165 }, + {2806,3144,2753 ,3165,3251,456 }, {2346,3119,2538 ,3252,3223,3222 }, + {2573,2346,2538 ,3253,3252,3222 }, {2747,2681,2465 ,3224,3254,3181 }, + {8118,1218,8126 ,2129,3255,812 }, {135,204,2319 ,2653,1722,3228 }, + {2320,135,2319 ,1406,2653,3228 }, {135,205,204 ,2653,1741,1722 }, + {8138,4106,8078 ,2018,1742,1744 }, {135,273,205 ,2653,1749,1741 }, + {3913,4248,3001 ,841,3256,3210 }, {2369,2445,2368 ,3257,1388,2943 }, + {2351,3149,2201 ,3258,3134,2455 }, {2588,2449,2556 ,2072,3259,2779 }, + {6911,6912,6942 ,474,519,521 }, {6943,6963,6962 ,520,1048,3260 }, + {3428,3429,3459 ,3261,1978,3241 }, {788,1390,3091 ,976,2964,3262 }, + {3260,968,2201 ,3188,1661,2455 }, {3325,3359,3305 ,825,824,879 }, + {2417,2386,2715 ,3236,3196,2944 }, {3199,1658,3156 ,3244,1782,3164 }, + {2439,2512,2438 ,2980,2981,3163 }, {6531,6897,8134 ,2280,54,3263 }, + {2354,3173,2434 ,2968,3114,1118 }, {3103,3104,3157 ,3137,3136,3194 }, + {2622,2966,2894 ,2840,3091,3140 }, {795,456,1665 ,302,301,3077 }, + {336,291,236 ,1013,1012,2444 }, {2356,2909,1785 ,3264,3189,1659 }, + {3267,2356,1785 ,3265,3264,1659 }, {3267,1785,1726 ,3265,1659,1746 }, + {3229,3267,1726 ,3266,3265,1746 }, {5346,5364,5363 ,3267,3268,3269 }, + {2754,2349,2849 ,3270,457,3250 }, {2446,2754,2849 ,3221,3270,3250 }, + {2849,2349,3144 ,3250,457,3251 }, {3144,2349,2753 ,3251,457,456 }, + {2811,2573,2538 ,3271,3253,3222 }, {2753,2811,2538 ,456,3271,3222 }, + {2792,2732,2626 ,2718,2583,2582 }, {8062,8065,8090 ,3272,96,412 }, + {2445,2481,2444 ,1388,3273,2941 }, {2822,2886,2821 ,2826,2913,2915 }, + {3212,2587,2942 ,3027,3274,2587 }, {1383,788,3091 ,336,976,3262 }, + {2932,2979,2931 ,3243,2781,2780 }, {2943,2503,2691 ,3055,2948,3169 }, + {2617,2852,2647 ,2878,3275,3276 }, {4571,972,2162 ,1342,3277,3278 }, + {3158,3200,3199 ,3193,3279,3244 }, {3157,3158,3199 ,3194,3193,3244 }, + {2653,2129,2069 ,3010,1469,1711 }, {2900,2874,2417 ,2649,3195,3236 }, + {2678,2911,1917 ,3190,2923,833 }, {2911,1336,2988 ,2923,2924,3045 }, + {821,3185,2266 ,2872,3280,3217 }, {3005,2414,2266 ,3281,3218,3217 }, + {3272,2754,2446 ,3282,3270,3221 }, {2447,3272,2446 ,3220,3282,3221 }, + {2349,890,2660 ,457,3283,455 }, {2753,2660,2811 ,456,455,3271 }, + {2380,2895,2914 ,2597,2574,2573 }, {2370,1482,227 ,3216,2265,2157 }, + {270,203,271 ,3130,2711,1608 }, {3134,3135,1855 ,3284,2833,3088 }, + {2367,3135,3134 ,2764,2833,3284 }, {643,2676,2602 ,3285,2761,3234 }, + {2760,2749,1096 ,2726,3286,2727 }, {4395,4347,4373 ,726,3287,3288 }, + {2807,2555,2545 ,3289,1066,3008 }, {329,2393,330 ,2348,2445,2402 }, + {3293,3291,7151 ,1988,1203,1202 }, {3089,3088,3048 ,2794,3290,2165 }, + {3049,3089,3048 ,2803,2794,2165 }, {554,2569,2366 ,2588,3291,2589 }, + {554,2268,2085 ,2588,3292,657 }, {3013,3095,3173 ,2946,2945,3114 }, + {2894,2630,2370 ,3140,3139,3216 }, {3231,411,2356 ,3293,2919,3264 }, + {3230,3231,2356 ,3294,3293,3264 }, {3199,3200,3229 ,3244,3279,3266 }, + {3200,3230,3229 ,3279,3294,3266 }, {2653,2069,2862 ,3010,1711,3031 }, + {2462,2715,2489 ,3245,2944,2611 }, {3157,3199,3156 ,3194,3244,3164 }, + {1725,1784,178 ,1747,1662,3043 }, {2031,2067,2006 ,1560,1592,1181 }, + {2551,3175,2874 ,3247,3249,3195 }, {8080,8069,8141 ,1094,1095,3295 }, + {1917,2911,2988 ,833,2923,3045 }, {3159,3158,3104 ,3296,3193,3136 }, + {3105,3159,3104 ,3297,3296,3136 }, {3065,3064,3026 ,3298,3092,3095 }, + {2464,840,1697 ,3299,68,69 }, {1088,3185,821 ,1604,3280,2872 }, + {1088,821,391 ,1604,2872,1647 }, {1088,2266,3185 ,1604,3217,3280 }, + {2309,3005,2266 ,3300,3281,3217 }, {1088,2309,2266 ,1604,3300,3217 }, + {3005,2309,2414 ,3281,3300,3218 }, {2309,2734,2299 ,3300,3301,3219 }, + {2414,2309,2299 ,3218,3300,3219 }, {2734,2311,2447 ,3301,3302,3220 }, + {2299,2734,2447 ,3219,3301,3220 }, {2447,2311,3272 ,3220,3302,3282 }, + {3272,2311,2754 ,3282,3302,3270 }, {2811,2660,2573 ,3271,455,3253 }, + {2591,2592,2638 ,3303,2936,2935 }, {2380,2914,2624 ,2597,2573,3132 }, + {2895,2581,2857 ,2574,2598,2575 }, {2930,2931,2977 ,3004,2780,2997 }, + {3000,3031,2980 ,3304,2857,3305 }, {3165,3207,3164 ,3306,2884,3307 }, + {2587,3256,554 ,3274,3308,2588 }, {2821,2885,2820 ,2915,2914,2869 }, + {3673,4185,52 ,648,409,411 }, {2949,2624,3011 ,3239,3132,3131 }, + {3429,3488,3460 ,1978,1980,3240 }, {2726,2777,2776 ,2667,2824,3192 }, + {5284,5338,5337 ,571,63,572 }, {4904,5377,4887 ,3309,361,2735 }, + {368,2534,369 ,1396,2846,1521 }, {3102,3103,3156 ,3097,3137,3164 }, + {2411,3081,2692 ,2777,1017,2845 }, {2936,2935,2889 ,2843,3310,2604 }, + {5284,5283,5239 ,571,570,482 }, {2268,2132,2085 ,3292,655,657 }, + {2979,3029,2978 ,2781,3311,2782 }, {2617,2647,2353 ,2878,3276,3170 }, + {78,2302,2495 ,3312,3313,2920 }, {2503,2617,2353 ,2948,2878,3170 }, + {3204,3234,3233 ,3314,3315,3316 }, {2302,2741,2495 ,3313,3317,2920 }, + {3162,3204,3203 ,3318,3314,3319 }, {3161,3162,3203 ,3320,3318,3319 }, + {3268,2302,3233 ,3321,3313,3316 }, {3234,3268,3233 ,3315,3321,3316 }, + {2302,2332,2741 ,3313,3322,3317 }, {3160,3161,3202 ,3323,3320,3324 }, + {3203,3233,3232 ,3319,3316,3325 }, {3107,3161,3140 ,3326,3320,3327 }, + {3161,3203,3202 ,3320,3319,3324 }, {3200,3201,3230 ,3279,3328,3294 }, + {2988,2653,2862 ,3045,3010,3031 }, {860,460,1853 ,1385,1999,3101 }, + {2508,2509,2592 ,2630,3329,2936 }, {840,2464,3450 ,68,3299,3330 }, + {2597,2673,2672 ,2769,2974,3331 }, {2311,2750,2754 ,3302,3332,3270 }, + {2750,890,2349 ,3332,3283,457 }, {2754,2750,2349 ,3270,3332,457 }, + {254,109,4036 ,2008,1654,1602 }, {687,1155,831 ,1665,3333,3334 }, + {546,2602,2560 ,1171,3234,3335 }, {2497,2132,2268 ,3336,655,3292 }, + {5264,5284,5239 ,1192,571,482 }, {8094,8088,8100 ,98,1998,1713 }, + {4041,2946,4040 ,1063,1062,460 }, {2456,2792,2626 ,2790,2718,2582 }, + {3207,3206,3164 ,2884,2886,3307 }, {3111,3165,3110 ,2829,3306,3337 }, + {486,437,529 ,1144,1149,1148 }, {2971,3019,1370 ,3338,2709,1500 }, + {2683,2736,2735 ,2645,2625,2887 }, {2704,2683,2735 ,3339,2645,2887 }, + {2730,741,827 ,2150,1221,1220 }, {220,131,8 ,3340,2965,3341 }, + {8093,8078,8076 ,2288,1744,617 }, {2801,2777,2822 ,3191,2824,2826 }, + {2675,2727,2674 ,3235,2760,2975 }, {3232,3233,78 ,3325,3316,3312 }, + {3233,2302,78 ,3316,3313,3312 }, {3203,3204,3233 ,3319,3314,3316 }, + {2445,2444,2368 ,1388,2941,2943 }, {6804,6805,6854 ,3342,3343,3344 }, + {5276,5330,5350 ,1525,3345,1526 }, {1294,108,874 ,635,1336,636 }, + {3684,3422,3664 ,1915,2055,1557 }, {554,90,2987 ,2588,2851,3346 }, + {90,2269,2987 ,2851,2881,3346 }, {3161,3160,3140 ,3320,3323,3327 }, + {3201,3200,3158 ,3328,3279,3193 }, {3159,3201,3158 ,3296,3328,3193 }, + {2630,2948,1482 ,3139,3347,2265 }, {2630,2577,2948 ,3139,3348,3347 }, + {2630,2853,2577 ,3139,806,3348 }, {2977,3028,3027 ,2997,3349,3350 }, + {2817,2881,2880 ,3174,2592,2591 }, {3105,3104,3064 ,3297,3136,3092 }, + {3065,3105,3064 ,3298,3297,3092 }, {2365,2441,2364 ,3351,3000,2513 }, + {3029,3066,3065 ,3311,3352,3298 }, {3028,3029,3065 ,3349,3311,3298 }, + {2978,3029,3028 ,2782,3311,3349 }, {2977,2978,3028 ,2997,2782,3349 }, + {2311,2419,2750 ,3302,3353,3332 }, {787,494,802 ,209,3041,400 }, + {2750,2419,890 ,3332,3353,3283 }, {1211,3636,939 ,2007,1060,3354 }, + {3636,1453,939 ,1060,3355,3354 }, {3487,3541,3540 ,3356,981,3357 }, + {221,2541,748 ,1296,1298,1677 }, {2532,1337,3226 ,3186,2148,3358 }, + {2263,2293,2262 ,3080,2561,3359 }, {3206,3236,3205 ,2886,2717,3360 }, + {3206,3205,3163 ,2886,3360,3361 }, {3165,3164,3110 ,3306,3307,3337 }, + {3036,3074,3035 ,3362,3363,3364 }, {2665,2664,2590 ,2677,2721,2648 }, + {1146,2921,1237 ,2467,2691,1467 }, {1237,2971,1370 ,1467,3338,1500 }, + {2930,2977,2928 ,3004,2997,2578 }, {3994,4036,4035 ,2806,1602,1601 }, + {3283,1556,2968 ,262,3365,263 }, {3182,2536,49 ,3366,2793,3367 }, + {2536,1015,3017 ,2793,3368,3369 }, {2533,2536,3182 ,3209,2793,3366 }, + {2535,2536,2533 ,3370,2793,3209 }, {2649,2536,2535 ,2791,2793,3370 }, + {2597,2672,2671 ,2769,3331,2705 }, {3227,3226,3198 ,1167,3358,1168 }, + {3067,3066,3029 ,3371,3352,3311 }, {3046,3067,3029 ,3372,3371,3311 }, + {3202,3203,3232 ,3324,3319,3325 }, {2781,2780,2756 ,3373,2830,2804 }, + {2729,2728,2676 ,3374,2759,2761 }, {2701,2729,2676 ,3375,3374,2761 }, + {2729,2756,2728 ,3374,2804,2759 }, {8087,8145,6797 ,385,3376,2246 }, + {2879,2923,2922 ,2621,2046,2045 }, {2878,2879,2922 ,2622,2621,2045 }, + {3209,3239,3238 ,3377,3378,3379 }, {199,270,271 ,2789,3130,1608 }, + {3066,3107,3106 ,3352,3326,3380 }, {3085,3066,3106 ,3381,3352,3380 }, + {1071,3661,2455 ,3382,789,475 }, {2822,2821,2776 ,2826,2915,3192 }, + {3027,3065,3026 ,3350,3298,3095 }, {3605,3604,3556 ,3383,3384,3385 }, + {1011,2864,1010 ,1285,3386,1286 }, {1688,451,134 ,1718,3387,1783 }, + {1592,1056,2309 ,2939,3388,3300 }, {1088,1592,2309 ,1604,2939,3300 }, + {2918,2734,2309 ,3389,3301,3300 }, {1056,2918,2309 ,3388,3389,3300 }, + {2918,2412,2311 ,3389,3390,3302 }, {2734,2918,2311 ,3301,3389,3302 }, + {2311,2412,2419 ,3302,3390,3353 }, {2840,2875,2687 ,2594,2596,2767 }, + {2629,2494,794 ,2673,3391,3392 }, {2637,1373,165 ,2720,1501,1869 }, + {3061,3060,3022 ,2953,3214,3393 }, {3236,3235,3205 ,2717,3394,3360 }, + {2565,2612,2524 ,2918,2646,3395 }, {2424,2870,2367 ,2778,2762,2764 }, + {3744,3746,3796 ,2390,2708,2969 }, {2905,2424,2367 ,399,2778,2764 }, + {2931,2930,2885 ,2780,3004,2914 }, {3169,3168,3127 ,2810,3396,2811 }, + {5363,6260,5346 ,3269,3397,3267 }, {2601,2600,2517 ,3398,2973,3399 }, + {2727,2778,2755 ,2760,3215,3400 }, {2597,2598,2673 ,2769,2602,2974 }, + {819,2769,2846 ,2244,2679,2623 }, {2436,2435,2358 ,2577,3401,2562 }, + {2729,2781,2756 ,3374,3373,2804 }, {2698,3187,4248 ,105,3402,3256 }, + {3065,3066,3085 ,3298,3352,3381 }, {2297,2362,2361 ,2663,2979,3177 }, + {3072,3125,3071 ,3403,3404,3405 }, {2999,2977,3027 ,2998,2997,3350 }, + {2359,2437,2358 ,3406,2576,2562 }, {2427,3117,2379 ,3237,3407,3238 }, + {6748,6757,1990 ,3408,3409,1305 }, {2581,2687,2857 ,2598,2767,2575 }, + {455,410,1665 ,2205,2204,3077 }, {2155,5444,2157 ,580,3410,3411 }, + {2443,2515,2442 ,2942,2600,2742 }, {2987,2497,2268 ,3346,3336,3292 }, + {2125,2193,2124 ,892,1007,1006 }, {3129,3086,3130 ,3412,3413,3414 }, + {1346,1806,2465 ,484,3124,3181 }, {2525,2565,2524 ,2937,2918,3395 }, + {2801,2822,2776 ,3191,2826,3192 }, {1740,1337,1782 ,3213,2148,1105 }, + {3405,2949,2920 ,1798,3239,1799 }, {2629,2583,2461 ,2673,2672,2899 }, + {2675,2674,2600 ,3235,2975,2973 }, {3108,3107,3066 ,3415,3326,3352 }, + {3067,3108,3066 ,3371,3415,3352 }, {2488,2693,2542 ,2877,3416,2879 }, + {2673,2725,2724 ,2974,2666,2668 }, {3256,90,554 ,3308,2851,2588 }, + {3011,2914,2965 ,3131,2573,3417 }, {7250,7233,3288 ,3418,2125,2058 }, + {1479,1480,3151 ,1270,2695,2585 }, {1480,3152,3151 ,2695,2580,2585 }, + {2884,2929,2928 ,2940,3003,2578 }, {2296,2297,2361 ,2664,2663,3177 }, + {2672,2673,2671 ,3331,2974,2705 }, {3742,3793,3792 ,3419,3420,3421 }, + {6742,4784,3192 ,3422,3423,3063 }, {3228,3227,1656 ,3042,1167,1166 }, + {2694,145,60 ,811,810,675 }, {2652,2918,1056 ,3424,3389,3388 }, + {2652,2745,2412 ,3424,3425,3390 }, {2918,2652,2412 ,3389,3424,3390 }, + {2412,2420,2419 ,3390,273,3353 }, {2513,2512,2440 ,3426,2981,2572 }, + {5166,6257,5473 ,3427,3428,3429 }, {219,3195,1855 ,3087,3430,3088 }, + {199,369,2534 ,2789,1521,2846 }, {2823,2863,2822 ,2825,3242,2826 }, + {2825,2824,2778 ,3431,3432,3215 }, {4256,4275,4255 ,3433,699,165 }, + {2555,2807,2416 ,1066,3289,808 }, {2915,2631,2711 ,3434,2971,3435 }, + {2525,2524,2485 ,2937,3395,2815 }, {4216,4256,4255 ,1241,3433,165 }, + {4215,4216,4255 ,370,1241,165 }, {2612,2611,2524 ,2646,3436,3395 }, + {4548,3247,2252 ,589,1494,590 }, {2779,2825,2778 ,2831,3431,3215 }, + {2294,2359,2358 ,3081,3406,2562 }, {2925,2924,2881 ,3115,2593,2592 }, + {3162,3161,3107 ,3318,3320,3326 }, {3108,3162,3107 ,3415,3318,3326 }, + {2693,2494,2542 ,3416,3391,2879 }, {6349,7252,6321 ,705,3437,706 }, + {2577,2416,2710 ,3348,808,3438 }, {4190,1265,1271 ,3439,1273,588 }, + {6416,6418,5089 ,3440,3441,3442 }, {2841,2521,2506 ,3443,3208,2882 }, + {2597,2671,2640 ,2769,2705,2770 }, {818,819,2800 ,1018,2244,2996 }, + {2450,3192,6689 ,3444,3063,3065 }, {2802,2827,2781 ,3022,3445,3373 }, + {2802,2848,2827 ,3022,2605,3445 }, {2497,2506,2344 ,3336,2882,2852 }, + {2702,2427,3149 ,2854,3237,3134 }, {2998,3025,2975 ,3446,3094,3157 }, + {3741,3742,3792 ,3447,3419,3421 }, {3744,3795,3794 ,2390,3448,3449 }, + {2782,2802,2781 ,3072,3022,3373 }, {31,2859,2652 ,3450,3451,3424 }, + {1056,1592,1004 ,3388,2939,3452 }, {2859,2695,2745 ,3451,3453,3425 }, + {2652,2859,2745 ,3424,3451,3425 }, {2745,2695,2412 ,3425,3453,3390 }, + {2695,2421,2420 ,3453,2568,273 }, {2412,2695,2420 ,3390,3453,273 }, + {3742,3743,3793 ,3419,2225,3420 }, {3324,3357,3323 ,881,883,1051 }, + {3743,3794,3793 ,2225,3449,3420 }, {1549,1550,1619 ,903,2586,1003 }, + {287,288,332 ,2470,2723,2471 }, {3269,2792,2456 ,2719,2718,2790 }, + {8114,5950,8108 ,2286,2873,2287 }, {2710,2416,2631 ,3438,808,2971 }, + {3166,3209,3208 ,3454,3377,3455 }, {2740,2739,2705 ,3456,2801,2802 }, + {2686,2740,2705 ,3071,3456,2802 }, {503,1633,466 ,374,373,347 }, + {1032,193,867 ,1581,1448,1121 }, {2985,3036,2984 ,3457,3362,3458 }, + {2740,2788,2739 ,3456,2652,2801 }, {7287,7286,7239 ,3459,3460,1527 }, + {2506,2805,2554 ,2882,3230,2853 }, {2437,2510,2509 ,2576,3162,3329 }, + {2436,2437,2509 ,2577,2576,3329 }, {2567,2370,227 ,2842,3216,2157 }, + {2413,2387,2900 ,3246,3461,2649 }, {2367,3134,3116 ,2764,3284,3029 }, + {2387,2633,2900 ,3461,2650,2649 }, {3824,8131,8133 ,3462,2307,2131 }, + {3954,8062,4957 ,3463,3272,2733 }, {2677,2379,886 ,3033,3238,3464 }, + {2445,2517,2481 ,1388,3399,3273 }, {2681,2464,2465 ,3254,3299,3181 }, + {2505,2506,2497 ,2883,2882,3336 }, {2202,650,2841 ,2875,3074,3443 }, + {6809,6808,6754 ,712,3465,713 }, {2514,2515,2513 ,2741,2600,3426 }, + {3743,3744,3794 ,2225,2390,3449 }, {2560,2518,546 ,3335,2388,1171 }, + {2782,2781,2729 ,3072,3373,3374 }, {2730,2782,2729 ,2150,3072,3374 }, + {6353,8121,6686 ,749,3466,750 }, {3460,3488,3487 ,3240,1980,3356 }, + {3478,3900,8141 ,885,884,3295 }, {4377,1255,1304 ,3090,3248,2639 }, + {3697,3743,3742 ,2224,2225,3419 }, {2774,2820,2773 ,2916,2869,2871 }, + {3025,3063,3062 ,3094,3093,3096 }, {3135,3183,1855 ,2833,2832,3088 }, + {7273,7251,7252 ,3467,2671,3437 }, {2567,3277,2901 ,2842,961,3468 }, + {2867,2868,2940 ,2844,1577,110 }, {1387,3372,1908 ,2951,2143,1058 }, + {147,4027,146 ,3469,3470,504 }, {5103,5418,5412 ,77,3471,360 }, + {1318,1032,867 ,164,1581,1121 }, {1787,1098,1908 ,2897,2725,1058 }, + {6735,6711,6308 ,3472,3473,3474 }, {8123,8108,8128 ,2247,2287,959 }, + {3959,3958,3904 ,982,294,1264 }, {2141,6707,8101 ,2015,2704,383 }, + {2816,2880,2879 ,3475,2591,2621 }, {3345,4106,8138 ,3476,1742,2018 }, + {2774,2776,2820 ,2916,3192,2869 }, {2775,2776,2774 ,3477,3192,2916 }, + {2776,2821,2820 ,3192,2915,2869 }, {3216,1657,3228 ,3478,1781,3042 }, + {2610,2682,790 ,3479,3480,1339 }, {2577,2710,2915 ,3348,3438,3434 }, + {220,4522,95 ,3340,3481,3482 }, {2545,2791,1789 ,3008,3483,3007 }, + {3227,2532,3226 ,1167,3186,3358 }, {2923,2973,2972 ,2046,3108,2047 }, + {2885,2930,2884 ,2914,3004,2940 }, {1590,1656,3198 ,3028,1166,1168 }, + {3212,2366,2495 ,3027,2589,2920 }, {3408,3383,3357 ,3484,882,883 }, + {3129,3128,3074 ,3412,2809,3363 }, {3086,3129,3074 ,3413,3412,3363 }, + {2506,2554,2344 ,2882,2853,2852 }, {2901,8098,2659 ,3468,907,3485 }, + {1917,2988,2862 ,833,3045,3031 }, {2353,2952,2570 ,3170,2610,2612 }, + {1235,1237,1370 ,1179,1467,1500 }, {1249,2693,2488 ,2876,3416,2877 }, + {2841,3213,2521 ,3443,3486,3208 }, {8096,8115,8140 ,1796,2448,2269 }, + {1847,178,1784 ,1519,3043,1662 }, {355,51,2939 ,1876,1856,1870 }, + {4248,3187,3972 ,3256,3402,3487 }, {4236,1334,4371 ,3488,1935,3489 }, + {3031,3030,2980 ,2857,3490,3305 }, {2724,2776,2723 ,2668,3192,2706 }, + {3031,3069,3030 ,2857,2827,3490 }, {2416,2807,1789 ,808,3289,3007 }, + {3890,3889,3854 ,1913,3126,1512 }, {3012,1332,2380 ,3491,3492,2597 }, + {1218,6891,8126 ,3255,813,812 }, {3275,2962,74 ,3493,3494,2053 }, + {2791,3275,2588 ,3483,3493,2072 }, {2278,2323,2322 ,3495,3496,3497 }, + {2323,282,281 ,3496,3498,2227 }, {2322,2323,281 ,3497,3496,2227 }, + {2484,2522,2451 ,3499,2678,2102 }, {3111,3110,3069 ,2829,3337,2827 }, + {3275,74,2588 ,3493,2053,2072 }, {2880,2924,2923 ,2591,2593,2046 }, + {2508,2592,2591 ,2630,2936,3303 }, {2493,2388,2840 ,3005,2608,2594 }, + {2986,3037,2985 ,3500,3501,3457 }, {5353,5352,4022 ,851,3502,3503 }, + {3037,3036,2985 ,3501,3362,3457 }, {2631,2467,2840 ,2971,3006,2594 }, + {3239,3241,2411 ,3378,3504,2777 }, {100,7960,162 ,2823,3505,2822 }, + {3213,2533,2521 ,3486,3209,3208 }, {2587,3142,90 ,3274,2849,2851 }, + {2515,2598,2597 ,2600,2602,2769 }, {2513,2515,2597 ,3426,2600,2769 }, + {7747,7797,7745 ,331,3506,650 }, {55,300,1155 ,3507,3508,3333 }, + {3855,3869,3854 ,1511,1912,1512 }, {2656,2461,2331 ,2893,2899,2895 }, + {3364,3390,3363 ,1184,3509,1911 }, {3129,3170,3128 ,3412,3510,2809 }, + {5556,5535,5536 ,3511,3512,754 }, {7702,7752,7701 ,3513,3514,3515 }, + {2835,2385,3173 ,3079,2702,3114 }, {3074,3128,3073 ,3363,2809,3516 }, + {2586,2840,2687 ,2972,2594,2767 }, {4371,1334,3864 ,3489,1935,3517 }, + {5970,2504,4104 ,3518,2898,3519 }, {5445,6577,4293 ,3520,97,2732 }, + {7874,7873,7847 ,344,817,3521 }, {2322,2277,2278 ,3497,3522,3495 }, + {1168,6643,4985 ,1098,3133,2703 }, {2948,2915,1482 ,3347,3434,2265 }, + {2915,2710,2631 ,3434,3438,2971 }, {1201,2986,1200 ,1477,3500,2629 }, + {2986,2985,1200 ,3500,3457,2629 }, {2449,2588,91 ,3259,2072,1984 }, + {1553,102,347 ,1669,432,431 }, {2879,2880,2923 ,2621,2591,2046 }, + {3103,3157,3156 ,3137,3194,3164 }, {2649,2535,2533 ,2791,3370,3209 }, + {2949,3011,2965 ,3239,3131,3417 }, {3213,2649,2533 ,3486,2791,3209 }, + {2875,3250,2635 ,2596,2595,2607 }, {817,906,905 ,1020,1019,1729 }, + {2789,2788,2740 ,637,2652,3456 }, {2948,2577,2915 ,3347,3348,3434 }, + {2435,2436,2508 ,3401,2577,2630 }, {2831,2830,2788 ,639,2654,2652 }, + {2789,2831,2788 ,637,639,2652 }, {6796,8136,8096 ,2617,2449,1796 }, + {2683,2682,2611 ,2645,3480,3436 }, {2807,2545,1789 ,3289,3008,3007 }, + {2533,2709,2805 ,3209,3523,3230 }, {2542,2494,2656 ,2879,3391,2893 }, + {3232,78,411 ,3325,3312,2919 }, {3231,3232,411 ,3293,3325,2919 }, + {411,78,2495 ,2919,3312,2920 }, {3027,3028,3065 ,3350,3349,3298 }, + {3485,3539,2686 ,3524,3525,3071 }, {1787,2620,1098 ,2897,3526,2725 }, + {2831,2868,2867 ,639,1577,2844 }, {2830,2831,2867 ,2654,639,2844 }, + {4240,6139,4260 ,2060,2059,3527 }, {3024,3025,3062 ,3098,3094,3096 }, + {2875,2635,2687 ,2596,2607,2767 }, {2923,2924,2973 ,2046,2593,3108 }, + {1204,1106,1205 ,338,1664,3187 }, {347,2423,2428 ,431,433,371 }, + {5308,5309,564 ,3528,987,3529 }, {2812,3349,3684 ,1914,2054,1915 }, + {3349,296,3422 ,2054,1558,2055 }, {2835,3180,2385 ,3079,2700,2702 }, + {821,1512,2136 ,2872,3199,1546 }, {6707,4985,6643 ,2704,2703,3133 }, + {2650,8096,8140 ,914,1796,2269 }, {8078,8142,8084 ,1744,1743,1139 }, + {2426,3190,2853 ,3113,807,806 }, {6351,8112,6322 ,1568,3530,3531 }, + {6914,6913,6861 ,3532,2634,3533 }, {1804,3317,296 ,2902,1882,1558 }, + {2436,2509,2508 ,2577,3329,2630 }, {2846,2877,1047 ,2623,3026,1328 }, + {2354,2555,3190 ,2968,1066,807 }, {1194,539,1599 ,393,2067,391 }, + {2426,2354,3190 ,3113,2968,807 }, {2741,2332,3212 ,3317,3322,3027 }, + {2609,2610,790 ,1130,3479,1339 }, {2443,2442,2365 ,2942,2742,3351 }, + {2612,2683,2611 ,2646,2645,3436 }, {1255,4377,795 ,3248,3090,302 }, + {2141,8091,8085 ,2015,2167,2016 }, {2400,111,2962 ,1117,1154,3494 }, + {3238,2424,2792 ,3379,2778,2718 }, {2691,2851,2943 ,3169,3051,3055 }, + {2555,2354,2434 ,1066,2968,1118 }, {3278,2851,2691 ,1158,3051,3169 }, + {382,1005,3732 ,3534,3535,3536 }, {2369,2368,2333 ,3257,2943,3537 }, + {3182,49,2709 ,3366,3367,3523 }, {4039,2680,2662 ,382,459,805 }, + {3746,3797,3796 ,2708,2061,2969 }, {2400,3275,2791 ,1117,3493,3483 }, + {2545,2400,2791 ,3008,1117,3483 }, {3915,3956,3955 ,1575,1383,3538 }, + {3939,3915,3955 ,3539,1575,3538 }, {3956,3996,3995 ,1383,1928,3540 }, + {3955,3956,3995 ,3538,1383,3540 }, {4038,4037,3995 ,1927,3541,3540 }, + {3996,4038,3995 ,1928,1927,3540 }, {238,237,100 ,868,869,2823 }, + {1853,1876,860 ,3101,1386,1385 }, {2760,2906,2428 ,2726,3542,371 }, + {2749,2760,2428 ,3286,2726,371 }, {2906,3270,2428 ,3542,3543,371 }, + {3270,347,2428 ,3543,431,371 }, {964,584,1294 ,535,2512,635 }, + {2719,2771,2770 ,2766,3197,2681 }, {2400,2962,3275 ,1117,3494,3493 }, + {4211,1113,4212 ,215,10,895 }, {8141,8069,3478 ,3295,1095,885 }, + {2242,2324,2323 ,2551,1995,3496 }, {2278,2242,2323 ,3495,2551,3496 }, + {2324,2391,282 ,1995,3544,3498 }, {2323,2324,282 ,3496,1995,3498 }, + {3226,1337,1740 ,3358,2148,3213 }, {2742,2031,2029 ,2558,1560,1748 }, + {2523,2564,2522 ,3545,3546,2678 }, {2610,2609,2522 ,3479,1130,2678 }, + {2564,2610,2522 ,3546,3479,2678 }, {5383,5396,6506 ,3547,3548,3549 }, + {2981,3032,3000 ,3550,2858,3304 }, {1103,1200,2890 ,1474,2629,2603 }, + {2353,2462,2489 ,3170,3245,2611 }, {872,836,3264 ,2962,3551,2468 }, + {3086,3074,3036 ,3413,3363,3362 }, {3037,3086,3036 ,3501,3413,3362 }, + {2682,2683,2704 ,3480,2645,3339 }, {5570,5641,5599 ,508,510,2213 }, + {2578,2635,2389 ,3552,2607,2609 }, {3238,3237,3207 ,3379,2885,2884 }, + {3208,3238,3207 ,3455,3379,2884 }, {3109,3108,3067 ,3553,3415,3371 }, + {2516,2600,2515 ,3554,2973,2600 }, {3068,3109,3067 ,3555,3553,3371 }, + {2933,2980,2979 ,3556,3305,2781 }, {2932,2933,2979 ,3243,3556,2781 }, + {3030,3029,2979 ,3490,3311,2781 }, {3030,3046,3029 ,3490,3372,3311 }, + {2980,3030,2979 ,3305,3490,2781 }, {3068,3067,3046 ,3555,3371,3372 }, + {3030,3068,3046 ,3490,3555,3372 }, {3235,2456,3268 ,3394,2790,3321 }, + {2533,3182,2709 ,3209,3366,3523 }, {2388,2430,2389 ,2608,2613,2609 }, + {2497,2865,2132 ,3336,3107,655 }, {8102,8082,8089 ,253,454,254 }, + {3594,3646,3592 ,2522,2123,2084 }, {3544,3594,3543 ,1153,2522,3075 }, + {3646,3645,3592 ,2123,1650,2084 }, {3594,3592,3593 ,2522,2084,3557 }, + {3594,3593,3543 ,2522,3557,3075 }, {4038,2662,4037 ,1927,805,3541 }, + {2662,254,4037 ,805,2008,3541 }, {5371,5404,5350 ,3558,3559,1526 }, + {562,885,708 ,1936,216,401 }, {80,2939,51 ,140,1870,1856 }, {2013,2711,2586 ,2242,3435,2972 }, + {2380,2013,2586 ,2597,2242,2972 }, {1011,1103,2864 ,1285,1474,3386 }, + {1103,2891,2864 ,1474,3560,3386 }, {2620,2760,1098 ,3526,2726,2725 }, + {2620,2305,2906 ,3526,3561,3542 }, {2760,2620,2906 ,2726,3526,3542 }, + {2305,3270,2906 ,3561,3543,3542 }, {1553,347,3270 ,1669,431,3543 }, + {2305,1553,3270 ,3561,1669,3543 }, {7704,7727,7682 ,3562,3563,3564 }, + {124,1423,5482 ,2206,151,3565 }, {2325,2392,2391 ,1994,3566,3544 }, + {2324,2325,2391 ,1995,1994,3544 }, {1929,1950,1884 ,899,786,3567 }, + {2392,328,2391 ,3566,2289,3544 }, {3166,3208,3207 ,3454,3455,2884 }, + {3165,3166,3207 ,3306,3454,2884 }, {3239,2411,2424 ,3378,2777,2778 }, + {2523,2522,2484 ,3545,2678,3499 }, {2967,111,2433 ,2701,1154,1116 }, + {2385,2967,2433 ,2702,2701,1116 }, {2967,3014,111 ,2701,3568,1154 }, + {511,2003,1972 ,3569,2542,3034 }, {1200,2985,2937 ,2629,3457,3570 }, + {2555,2434,2545 ,1066,1118,3008 }, {3237,3238,2792 ,2885,3379,2718 }, + {3048,3088,3047 ,2165,3290,3571 }, {2592,2667,2666 ,2936,2949,2765 }, + {2967,259,3014 ,2701,2203,3568 }, {2517,2516,2444 ,3399,3554,2941 }, + {2481,2517,2444 ,3273,3399,2941 }, {3234,3235,3268 ,3315,3394,3321 }, + {3205,3235,3204 ,3360,3394,3314 }, {3938,3939,3090 ,3572,3539,2805 }, + {3166,3165,3111 ,3454,3306,2829 }, {3088,3087,3047 ,3290,3573,3571 }, + {2586,2687,2581 ,2972,2767,2598 }, {70,1319,822 ,323,322,2000 }, + {3595,3648,3594 ,927,734,2522 }, {3544,3543,3489 ,1153,3075,1977 }, + {3490,3544,3489 ,2502,1153,1977 }, {2846,2844,2845 ,2623,1329,2995 }, + {2846,2845,819 ,2623,2995,2244 }, {3134,1855,2267 ,3284,3088,3574 }, + {3314,2333,2368 ,3575,3537,2943 }, {2150,3016,964 ,534,1028,535 }, + {3048,3047,1224 ,2165,3571,1508 }, {2790,2789,2740 ,3576,637,3456 }, + {2790,2832,2789 ,3576,638,637 }, {2883,2927,2882 ,2579,516,518 }, + {3399,6659,4550 ,715,3577,716 }, {6353,4544,5444 ,749,748,3410 }, + {1908,3453,1787 ,1058,917,2897 }, {2977,2976,2928 ,2997,2999,2578 }, + {3998,3999,4040 ,381,1023,460 }, {113,697,4539 ,677,3578,633 }, + {3134,2267,650 ,3284,3574,3074 }, {2732,2367,3116 ,2583,2764,3029 }, + {3116,3134,3219 ,3029,3284,3044 }, {3219,3134,650 ,3044,3284,3074 }, + {3033,3032,2981 ,3579,2858,3550 }, {2852,2387,2647 ,3275,3461,3276 }, + {906,1047,1046 ,1019,1328,1929 }, {3058,3123,1407 ,2710,3023,1269 }, + {3069,3110,3068 ,2827,3337,3555 }, {4298,141,4186 ,3580,681,683 }, + {1688,242,451 ,1718,1717,3387 }, {3125,3166,3111 ,3404,3454,2829 }, + {3644,3643,3590 ,1651,2344,1867 }, {28,88,3636 ,1059,3581,1060 }, + {4037,254,4036 ,3541,2008,1602 }, {3381,3407,3380 ,871,3012,1514 }, + {3407,3431,3380 ,3012,3021,1514 }, {1200,2937,2890 ,2629,3570,2603 }, + {2614,2684,2612 ,2638,2644,2646 }, {7910,2164,6483 ,3582,3583,3584 }, + {2298,2475,2793 ,3013,3047,3048 }, {7252,7251,6321 ,3437,2671,706 }, + {3016,584,964 ,1028,2512,535 }, {3227,3228,2532 ,1167,3042,3186 }, + {3241,3081,2411 ,3504,1017,2777 }, {2496,492,171 ,2961,1580,921 }, + {3349,1804,296 ,2054,2902,1558 }, {7067,7066,7028 ,3585,3586,3587 }, + {2326,2393,2392 ,3588,2445,3566 }, {2325,2326,2392 ,1994,3588,3566 }, + {2393,329,2392 ,2445,2348,3566 }, {329,328,2392 ,2348,2289,3566 }, + {3434,3382,3408 ,737,870,3484 }, {2936,2983,2935 ,2843,3589,3310 }, + {3240,3241,3239 ,3590,3504,3378 }, {3198,3226,3197 ,1168,3358,2599 }, + {2441,2513,2440 ,3000,3426,2572 }, {2853,2416,2577 ,806,808,3348 }, + {2805,2709,651 ,3230,3523,3591 }, {2764,3142,2587 ,2847,2849,3274 }, + {2702,3149,2351 ,2854,3134,3258 }, {2344,2702,2351 ,2852,2854,3258 }, + {2848,2826,2827 ,2605,3592,3445 }, {2682,830,829 ,3480,1996,1368 }, + {493,492,2496 ,1785,1580,2961 }, {2663,921,688 ,3593,1922,765 }, + {3198,3197,3154 ,1168,2599,2692 }, {3226,1740,3197 ,3358,3213,2599 }, + {2837,930,107 ,2511,3594,2548 }, {2726,2755,2777 ,2667,3400,2824 }, + {2517,2600,2516 ,3399,2973,3554 }, {2602,2600,2601 ,3234,2973,3398 }, + {3072,3071,3033 ,3403,3405,3579 }, {3034,3072,3033 ,3595,3403,3579 }, + {3087,3086,3037 ,3573,3413,3501 }, {2704,2735,830 ,3339,2887,1996 }, + {2682,2704,830 ,3480,3339,1996 }, {1168,8122,6643 ,1098,384,3133 }, + {4901,5591,6162 ,3596,3597,3598 }, {688,921,1065 ,765,1922,821 }, + {921,1090,1340 ,1922,2687,822 }, {2590,529,2589 ,2648,1148,3204 }, + {2097,7910,2096 ,846,3582,788 }, {2942,2587,554 ,2587,3274,2588 }, + {2590,2664,529 ,2648,2721,1148 }, {2763,2620,1787 ,2896,3526,2897 }, + {2763,3120,2620 ,2896,3599,3526 }, {2500,2305,2620 ,3600,3561,3526 }, + {3120,2500,2620 ,3599,3600,3526 }, {2500,2768,1553 ,3600,1297,1669 }, + {2305,2500,1553 ,3561,3600,1669 }, {2982,2981,2934 ,3601,3550,3602 }, + {2935,2982,2934 ,3310,3601,3602 }, {3047,3087,3037 ,3571,3573,3501 }, + {2610,2611,2682 ,3479,3436,3480 }, {2039,1626,349 ,1655,564,566 }, + {2730,2729,2701 ,2150,3374,3375 }, {2770,2816,2847 ,2681,3475,2624 }, + {3059,3099,3058 ,2892,3603,2710 }, {3099,3123,3058 ,3603,3023,2710 }, + {3019,3020,3058 ,2709,2890,2710 }, {2135,2478,631 ,1425,3143,1426 }, + {2770,2771,2816 ,2681,3197,3475 }, {2424,2905,2367 ,2778,399,2764 }, + {2267,1855,2649 ,3574,3088,2791 }, {3648,3647,3594 ,734,2521,2522 }, + {3574,3595,3544 ,928,927,1153 }, {3107,3105,3106 ,3326,3297,3380 }, + {3107,3140,3105 ,3326,3327,3297 }, {2933,2932,2863 ,3556,3243,3242 }, + {1590,3154,3123 ,3028,2692,3023 }, {3124,1590,3123 ,3604,3028,3023 }, + {2084,460,860 ,1361,1999,1385 }, {2674,2726,2700 ,2975,2667,3009 }, + {2727,2755,2726 ,2760,3400,2667 }, {3169,3211,3168 ,2810,3605,3396 }, + {2677,213,123 ,3033,3606,3607 }, {3211,3210,3168 ,3605,3608,3396 }, + {2796,3080,2472 ,2462,908,2009 }, {2255,1211,939 ,3229,2007,3354 }, + {3866,35,783 ,1479,1338,679 }, {2846,2878,2877 ,2623,2622,3026 }, + {2435,388,292 ,3401,2202,2563 }, {2358,2435,292 ,2562,3401,2563 }, + {2833,2763,3054 ,2065,2896,916 }, {2833,3120,2763 ,2065,3599,2896 }, + {2550,2500,3120 ,3609,3600,3599 }, {2833,2550,3120 ,2065,3609,3599 }, + {2550,2768,2500 ,3609,1297,3600 }, {2167,2089,2947 ,1323,1325,2988 }, + {2167,2947,2166 ,1323,2988,1040 }, {388,2435,2508 ,2202,3401,2630 }, + {3069,3068,3030 ,2827,3555,3490 }, {2591,2590,2508 ,3303,2648,2630 }, + {7514,7513,7484 ,3610,3611,3612 }, {2280,2325,2243 ,3613,1994,1993 }, + {2662,2796,2472 ,805,2462,2009 }, {2997,3023,3022 ,3158,3099,3393 }, + {2591,2638,2590 ,3303,2935,2648 }, {2666,2665,2590 ,2765,2677,2648 }, + {2563,2194,2193 ,2707,1853,1007 }, {2983,2982,2935 ,3589,3601,3310 }, + {1098,1096,28 ,2725,2727,1059 }, {1908,1098,28 ,1058,2725,1059 }, + {1197,1987,2808 ,478,3614,522 }, {401,2445,2369 ,1387,1388,3257 }, + {3322,3321,7387 ,1182,2461,3615 }, {2792,2424,2367 ,2718,2778,2764 }, + {3593,3592,3543 ,3557,2084,3075 }, {3090,3955,3994 ,2805,3538,2806 }, + {600,131,1390 ,978,2965,2964 }, {3163,3205,3162 ,3361,3360,3318 }, + {788,600,1390 ,976,978,2964 }, {2626,2764,2587 ,2582,2847,3274 }, + {2332,2626,2587 ,3322,2582,3274 }, {2442,2441,2365 ,2742,3000,3351 }, + {2441,2442,2513 ,3000,2742,3426 }, {2638,2666,2590 ,2935,2765,2648 }, + {2617,2371,2387 ,2878,2880,3461 }, {2887,2933,2863 ,3616,3556,3242 }, + {2827,2826,2780 ,3445,3592,2830 }, {2791,2588,2429 ,3483,2072,3617 }, + {818,2800,2844 ,1018,2996,1329 }, {2593,2668,2667 ,3111,3138,2949 }, + {740,2730,2701 ,1595,2150,3375 }, {2755,2778,2777 ,3400,3215,2824 }, + {2727,2726,2674 ,2760,2667,2975 }, {2885,2884,2820 ,2914,2940,2869 }, + {2332,2587,3212 ,3322,3274,3027 }, {3123,1480,1407 ,3023,2695,1269 }, + {2364,2363,177 ,2513,2571,3618 }, {3357,3382,3356 ,883,870,872 }, + {2768,2460,2541 ,1297,3619,1298 }, {3654,3653,3604 ,1438,1440,3384 }, + {492,444,171 ,1580,922,921 }, {2557,3566,3456 ,3620,1146,1145 }, + {5837,4771,5804 ,3621,3622,3623 }, {2665,2666,2718 ,2677,2765,2675 }, + {2878,2922,2921 ,2622,2045,2691 }, {2550,2460,2768 ,3609,3619,1297 }, + {108,36,874 ,1336,1478,636 }, {2857,2761,2765 ,2575,2768,2590 }, + {7923,7973,7948 ,816,3624,3625 }, {2281,2327,2326 ,3626,3627,3588 }, + {2327,286,2393 ,3627,2446,2445 }, {2326,2327,2393 ,3588,3627,2445 }, + {5038,4743,5702 ,3628,3629,3630 }, {2156,6783,6464 ,3631,3632,3633 }, + {2528,924,3183 ,2763,3634,2832 }, {3195,3114,2649 ,3430,2792,2791 }, + {3494,3546,3492 ,736,2557,930 }, {2143,2099,2166 ,953,1093,1040 }, + {2611,2610,2524 ,3436,3479,3395 }, {3845,6215,1949 ,3635,3636,3637 }, + {3130,4032,3129 ,3414,3638,3412 }, {2982,3033,2981 ,3601,3579,3550 }, + {3071,3125,3111 ,3405,3404,2829 }, {489,3761,2955 ,3639,218,3640 }, + {3163,3162,3108 ,3361,3318,3415 }, {3109,3163,3108 ,3553,3361,3415 }, + {3205,3204,3162 ,3360,3314,3318 }, {3235,3234,3204 ,3394,3315,3314 }, + {5337,4866,4830 ,572,3641,3642 }, {2673,2724,2671 ,2974,2668,2705 }, + {3201,3231,3230 ,3328,3293,3294 }, {7704,7753,7727 ,3562,3643,3563 }, + {2815,2770,2847 ,2696,2681,2624 }, {2781,2827,2780 ,3373,3445,2830 }, + {1984,2530,2038 ,831,2389,1551 }, {1004,2652,1056 ,3452,3424,3388 }, + {7910,2097,2164 ,3582,846,3583 }, {2974,2997,3022 ,3116,3158,3393 }, + {2494,2629,2656 ,3391,2673,2893 }, {2973,2974,3022 ,3108,3116,3393 }, + {2869,2868,2832 ,1576,1577,638 }, {2194,2563,2146 ,1853,2707,1732 }, + {8135,4835,3148 ,2439,2130,814 }, {2952,2353,2489 ,2610,3170,2611 }, + {3216,1656,1657 ,3478,1166,1781 }, {2869,2832,2907 ,1576,638,111 }, + {5653,350,6115 ,3644,1114,3645 }, {2877,2878,2921 ,3026,2622,2691 }, + {2793,2426,2853 ,3048,3113,806 }, {1845,1337,1874 ,912,2148,962 }, + {1519,2990,79 ,2450,3646,1510 }, {6554,6619,4190 ,3647,1272,3439 }, + {777,2137,776 ,421,147,1120 }, {4445,2871,712 ,3648,3649,3650 }, + {2572,3138,1181 ,3050,2984,1981 }, {2868,2907,2940 ,1577,111,110 }, + {3331,3330,3291 ,1183,1185,1203 }, {2497,2201,2865 ,3336,2455,3107 }, + {3032,3031,3000 ,2858,2857,3304 }, {2889,2888,2826 ,2604,3651,3592 }, + {2626,2625,2839 ,2582,2584,2848 }, {2848,2889,2826 ,2605,2604,3592 }, + {3071,3111,3070 ,3405,2829,2828 }, {3071,3070,3032 ,3405,2828,2858 }, + {3033,3071,3032 ,3579,3405,2858 }, {2351,2201,2497 ,3258,2455,3336 }, + {2516,2515,2443 ,3554,2600,2942 }, {2444,2516,2443 ,2941,3554,2942 }, + {2442,2514,2513 ,2742,2741,3426 }, {3545,3574,3544 ,926,928,1153 }, + {3931,3944,3684 ,1868,729,1915 }, {3541,3572,3571 ,981,1931,3652 }, + {3195,219,3114 ,3430,3087,2792 }, {2711,2013,877 ,3435,2242,2243 }, + {689,3828,4206 ,1363,1364,682 }, {5488,6257,6039 ,2730,3428,2728 }, + {3114,2471,2536 ,2792,3653,2793 }, {1238,165,155 ,1569,1869,1497 }, + {2973,3022,3021 ,3108,3393,2891 }, {2930,2929,2884 ,3004,3003,2940 }, + {2387,2531,2647 ,3461,3654,3276 }, {3194,1487,2338 ,1065,531,458 }, + {3022,3060,3059 ,3393,3214,2892 }, {3021,3022,3059 ,2891,3393,2892 }, + {3075,2803,2833 ,2670,3655,2065 }, {2608,2550,2833 ,3656,3609,2065 }, + {2850,2608,2833 ,3657,3656,2065 }, {2608,2470,2460 ,3656,3658,3619 }, + {2550,2608,2460 ,3609,3656,3619 }, {2470,2541,2460 ,3658,1298,3619 }, + {3422,296,3664 ,2055,1558,1557 }, {3060,3100,3099 ,3214,2955,3603 }, + {3194,3189,2150 ,1065,1027,534 }, {3400,2410,3402 ,3659,1,3660 }, + {1218,1742,3008 ,3255,707,546 }, {3170,4032,1160 ,3510,3638,3661 }, + {2099,2100,2166 ,1093,1218,1040 }, {3059,3060,3099 ,2892,3214,3603 }, + {3100,3124,3099 ,2955,3604,3603 }, {2972,3020,3019 ,2047,2890,2709 }, + {3183,924,219 ,2832,3634,3087 }, {2889,2935,2888 ,2604,3310,3651 }, + {2935,2934,2888 ,3310,3602,3651 }, {740,2676,643 ,1595,2761,3285 }, + {2006,2032,2031 ,1181,1127,1560 }, {740,2701,2676 ,1595,3375,2761 }, + {2852,2617,2387 ,3275,2878,3461 }, {2360,2437,2359 ,3161,2576,3406 }, + {2294,2295,2359 ,3081,3160,3406 }, {2971,2972,3019 ,3338,2047,2709 }, + {1224,3047,3037 ,1508,3571,3501 }, {2922,2972,2971 ,2045,2047,3338 }, + {2793,2853,2630 ,3048,806,3139 }, {2592,2593,2667 ,2936,3111,2949 }, + {628,2717,728 ,1177,2676,1266 }, {2921,2922,2971 ,2691,2045,3338 }, + {3100,1590,3124 ,2955,3028,3604 }, {3211,3241,3240 ,3605,3504,3590 }, + {3210,3211,3240 ,3608,3605,3590 }, {2983,3034,2982 ,3589,3595,3601 }, + {3463,3432,3433 ,1271,929,3049 }, {2651,2803,3075 ,1516,3655,2670 }, + {2651,2850,2803 ,1516,3657,3655 }, {2651,2608,2850 ,1516,3656,3657 }, + {2418,2541,2470 ,3662,1298,3658 }, {451,242,2541 ,3387,1717,1298 }, + {2418,451,2541 ,3662,3387,1298 }, {2931,2978,2977 ,2780,2782,2997 }, + {2841,2649,3213 ,3443,2791,3486 }, {596,549,647 ,1462,1129,973 }, + {3034,3033,2982 ,3595,3579,3601 }, {875,6836,6353 ,579,852,749 }, + {2282,2328,2327 ,3663,3664,3627 }, {2328,287,286 ,3664,2470,2446 }, + {2327,2328,286 ,3627,3664,2446 }, {4246,1729,3837 ,3665,3666,3667 }, + {4377,2431,967 ,3090,2977,3668 }, {3020,3059,3058 ,2890,2892,2710 }, + {650,2267,2841 ,3074,3574,3443 }, {219,2471,3114 ,3087,3653,2792 }, + {2823,2824,2863 ,2825,3432,3242 }, {3065,3085,3105 ,3298,3381,3297 }, + {3085,3106,3105 ,3381,3380,3297 }, {2596,2640,2595 ,2771,2770,2983 }, + {2723,2775,2774 ,2706,3477,2916 }, {2722,2723,2774 ,3014,2706,2916 }, + {2509,2510,2592 ,3329,3162,2936 }, {2295,2360,2359 ,3160,3161,3406 }, + {3229,1726,3199 ,3266,1746,3244 }, {2300,1304,2551 ,2640,2639,3247 }, + {1336,2085,2604 ,2924,657,3046 }, {3566,2397,3768 ,1146,3669,1518 }, + {2267,2649,2841 ,3574,2791,3443 }, {1920,2155,2157 ,581,580,3411 }, + {3459,3460,3487 ,3241,3240,3356 }, {1789,2791,2429 ,3007,3483,3617 }, + {2656,2629,2461 ,2893,2673,2899 }, {3110,3164,3109 ,3337,3307,3553 }, + {1224,3037,2986 ,1508,3501,3500 }, {6071,6082,6088 ,1346,1348,3670 }, + {155,1183,1238 ,1497,1238,1569 }, {4978,5592,2206 ,178,3671,176 }, + {3546,3545,3492 ,2557,926,930 }, {2381,2608,2651 ,3672,3656,1516 }, + {2381,2470,2608 ,3672,3658,3656 }, {2468,2418,2470 ,3673,3662,3658 }, + {1313,5085,2251 ,3674,3675,3676 }, {1606,4055,4054 ,3677,3678,3679 }, + {689,1586,3315 ,1363,943,944 }, {2741,3212,2495 ,3317,3027,2920 }, + {2329,288,287 ,3680,2723,2470 }, {2328,2329,287 ,3664,3680,2470 }, + {3345,8138,5352 ,3476,2018,3502 }, {2647,2531,2413 ,3276,3654,3246 }, + {2778,2824,2823 ,3215,3432,2825 }, {2777,2778,2823 ,2824,3215,2825 }, + {2824,2887,2863 ,3432,3616,3242 }, {2387,2371,2633 ,3461,2880,2650 }, + {3202,3201,3159 ,3324,3328,3296 }, {3202,3232,3201 ,3324,3325,3328 }, + {2640,2671,2595 ,2770,2705,2983 }, {2900,2300,2551 ,2649,2640,3247 }, + {2604,2085,2964 ,3046,657,3011 }, {2964,2085,2131 ,3011,657,656 }, + {2677,886,213 ,3033,3464,3606 }, {3268,2456,2302 ,3321,2790,3313 }, + {6436,5255,6480 ,3681,3682,1524 }, {3916,3904,3879 ,969,1264,2549 }, + {2524,2610,2564 ,3395,3479,3546 }, {282,327,281 ,3498,2226,2227 }, + {3421,3278,2851 ,1156,1158,3051 }, {2792,2367,2732 ,2718,2764,2583 }, + {5191,5170,5914 ,2900,3683,1566 }, {2547,2470,2381 ,3684,3658,3672 }, + {2397,2547,2381 ,3669,3684,3672 }, {2547,3053,2470 ,3684,3685,3658 }, + {2655,2468,2470 ,3686,3673,3658 }, {3053,2655,2470 ,3685,3686,3658 }, + {2655,2418,2468 ,3686,3662,3673 }, {2467,2493,2840 ,3006,3005,2594 }, + {2524,2564,2523 ,3395,3546,3545 }, {2946,3194,2338 ,1062,1065,458 }, + {178,1847,1874 ,3043,1519,962 }, {2924,2974,2973 ,2593,3116,3108 }, + {2328,2283,2329 ,3664,3687,3680 }, {2909,1849,1785 ,3189,1660,1659 }, + {3148,1378,8135 ,814,925,2439 }, {6098,6388,5191 ,1565,3184,2900 }, + {2524,2523,2484 ,3395,3545,3499 }, {6970,3340,4831 ,3688,114,3154 }, + {3126,3125,3072 ,3689,3404,3403 }, {2664,628,529 ,2721,1177,1148 }, + {2584,2310,2476 ,2894,2442,2441 }, {2552,2584,2476 ,2657,2894,2441 }, + {2633,2584,2552 ,2650,2894,2657 }, {3160,3202,3159 ,3323,3324,3296 }, + {3647,3700,3646 ,2521,2545,2123 }, {2928,2926,2927 ,2578,517,516 }, + {2976,2998,2975 ,2999,3446,3157 }, {2362,2439,2438 ,2979,2980,3163 }, + {2361,2362,2438 ,3177,2979,3163 }, {2456,2626,2332 ,2790,2582,3322 }, + {2302,2456,2332 ,3313,2790,3322 }, {3164,3163,3109 ,3307,3361,3553 }, + {2532,178,1874 ,3186,3043,962 }, {2670,2669,2595 ,2982,3015,2983 }, + {3873,3931,3475 ,1809,1868,1559 }, {6309,6735,6308 ,3690,3472,3474 }, + {6088,5255,6436 ,3670,3682,3681 }, {1265,4548,1271 ,1273,589,588 }, + {2195,165,341 ,1851,1869,1852 }, {2547,2469,2655 ,3684,3691,3686 }, + {3053,2547,2655 ,3685,3684,3686 }, {2469,2418,2655 ,3691,3662,3686 }, + {2469,451,2418 ,3691,3387,3662 }, {2939,134,451 ,1870,1783,3387 }, + {3167,3209,3166 ,3692,3377,3454 }, {8078,8084,8124 ,1744,1139,1141 }, + {3216,3228,1656 ,3478,3042,1166 }, {2330,289,288 ,3693,1044,2723 }, + {2329,2330,288 ,3680,3693,2723 }, {925,2730,827 ,1287,2150,1220 }, + {1010,925,926 ,1286,1287,1284 }, {1146,1237,1236 ,2467,1467,1434 }, + {2844,2846,1047 ,1329,2623,1328 }, {3022,3023,3061 ,3393,3099,2953 }, + {2937,2936,2890 ,3570,2843,2603 }, {3105,3140,3159 ,3297,3327,3296 }, + {3140,3160,3159 ,3327,3323,3296 }, {3232,3231,3201 ,3325,3293,3328 }, + {3021,3020,2972 ,2891,2890,2047 }, {2976,2975,2926 ,2999,3157,517 }, + {2928,2976,2926 ,2578,2999,517 }, {2595,2639,2594 ,2983,3694,3110 }, + {2595,2594,2511 ,2983,3110,3112 }, {2356,3267,3229 ,3264,3265,3266 }, + {2909,3223,2911 ,3189,2922,2923 }, {2891,2890,1010 ,3560,2603,1286 }, + {3238,3239,2424 ,3379,3378,2778 }, {1103,2890,2891 ,1474,2603,3560 }, + {3167,3166,3125 ,3692,3454,3404 }, {3306,7276,3326 ,3695,3696,826 }, + {2497,2344,2351 ,3336,2852,3258 }, {1482,2915,2711 ,2265,3434,3435 }, + {3434,3433,3382 ,737,3049,870 }, {5219,5218,5198 ,1860,483,423 }, + {8101,8087,8099 ,383,385,2867 }, {3208,3209,3238 ,3455,3377,3379 }, + {2397,2651,3768 ,3669,1516,1518 }, {2688,451,2469 ,3697,3387,3691 }, + {2658,2939,451 ,3698,1870,3387 }, {2688,2658,451 ,3697,3698,3387 }, + {3645,3698,3644 ,1650,2122,1651 }, {6639,1213,4983 ,3699,3700,480 }, + {2985,2984,2937 ,3457,3458,3570 }, {3546,3597,3545 ,2557,1810,926 }, + {6257,5166,6039 ,3428,3427,2728 }, {2283,2285,2329 ,3687,3701,3680 }, + {2129,2130,2069 ,1469,1505,1711 }, {1855,3195,2649 ,3088,3430,2791 }, + {5327,6525,6524 ,3702,3703,3704 }, {2702,2254,2427 ,2854,3705,3237 }, + {2475,2715,3013 ,3047,2944,2946 }, {3095,2715,2835 ,2945,2944,3079 }, + {2512,2596,2595 ,2981,2771,2983 }, {2563,2125,2146 ,2707,892,1732 }, + {2691,2570,2544 ,3169,2612,2451 }, {2639,2669,2593 ,3694,3015,3111 }, + {2595,2669,2639 ,2983,3015,3694 }, {3026,3025,2998 ,3095,3094,3446 }, + {2899,3223,2909 ,2921,2922,3189 }, {3700,3699,3646 ,2545,2121,2123 }, + {3073,3127,3072 ,3516,2811,3403 }, {2429,2588,2556 ,3617,2072,2779 }, + {1789,2429,2556 ,3007,3617,2779 }, {2864,2891,1010 ,3386,3560,1286 }, + {3126,3167,3125 ,3689,3692,3404 }, {1482,2711,877 ,2265,3435,2243 }, + {2518,2517,2445 ,2388,3399,1388 }, {2984,2983,2936 ,3458,3589,2843 }, + {1740,1687,3196 ,3213,1104,2581 }, {3197,1740,3196 ,2599,3213,2581 }, + {2493,1789,2556 ,3005,3007,2779 }, {554,2987,2268 ,2588,3346,3292 }, + {2876,2706,2469 ,3706,3707,3691 }, {2547,2876,2469 ,3684,3706,3691 }, + {2706,2688,2469 ,3707,3697,3691 }, {2658,355,2939 ,3698,1876,1870 }, + {3035,3034,2983 ,3364,3595,3589 }, {2388,2493,2556 ,2608,3005,2779 }, + {685,3371,1309 ,2674,3708,3709 }, {3187,4236,4371 ,3402,3488,3489 }, + {157,2133,91 ,1877,2614,1984 }, {5455,1263,6509 ,686,1199,1452 }, + {4452,4795,4820 ,2398,3710,2399 }, {3073,3072,3034 ,3516,3403,3595 }, + {3035,3073,3034 ,3364,3516,3595 }, {6173,6088,6388 ,3711,3670,3184 }, + {110,74,2962 ,743,2053,3494 }, {2715,2386,2835 ,2944,3196,3079 }, + {369,418,470 ,1521,1610,1522 }, {2549,2622,2894 ,2841,2840,3140 }, + {2639,2593,2594 ,3694,3111,3110 }, {2773,2772,2720 ,2871,3198,3173 }, + {2721,2773,2720 ,2917,2871,3173 }, {2676,2675,2602 ,2761,3235,3234 }, + {3168,3167,3126 ,3396,3692,3689 }, {1452,1004,1592 ,1606,3452,2939 }, + {1936,5140,5359 ,848,3712,3713 }, {5326,5327,6524 ,3714,3702,3704 }, + {2201,968,2200 ,2455,1661,1570 }, {2401,651,2995 ,2855,3591,2856 }, + {2531,2387,2413 ,3654,3461,3246 }, {2937,2984,2936 ,3570,3458,2843 }, + {3168,3210,3167 ,3396,3608,3692 }, {2194,2146,2147 ,1853,1732,1562 }, + {2837,107,2501 ,2511,2548,2547 }, {2652,1004,31 ,3424,3452,3450 }, + {3054,3981,3076 ,916,915,2066 }, {2557,2397,3052 ,3620,3669,3715 }, + {2876,2547,2397 ,3706,3684,3669 }, {2557,2876,2397 ,3620,3706,3669 }, + {2984,3035,2983 ,3458,3364,3589 }, {2250,2286,2330 ,3716,3717,3693 }, + {2285,2250,2330 ,3701,3716,3693 }, {2286,290,289 ,3717,1042,1044 }, + {2330,2286,289 ,3693,3717,1044 }, {8067,8083,8097 ,2168,2103,1710 }, + {2000,2025,2024 ,2428,2430,2370 }, {3127,3126,3072 ,2811,3689,3403 }, + {3179,1249,2617 ,2947,2876,2878 }, {3472,3446,1165 ,3155,3718,3153 }, + {2647,2413,2462 ,3276,3246,3245 }, {3230,2356,3229 ,3294,3264,3266 }, + {2370,2630,1482 ,3216,3139,2265 }, {2637,1238,1373 ,2720,1569,1501 }, + {2131,2129,2653 ,656,1469,3010 }, {411,2899,2909 ,2919,2921,3189 }, + {2356,411,2909 ,3264,2919,3189 }, {3534,8072,3668 ,942,511,513 }, + {3127,3168,3126 ,2811,3396,3689 }, {2513,2597,2512 ,3426,2769,2981 }, + {6170,3537,3451 ,3719,1735,1720 }, {3210,3209,3167 ,3608,3377,3692 }, + {2587,90,3256 ,3274,2851,3308 }, {2717,2769,819 ,2676,2679,2244 }, + {2805,651,2401 ,3230,3591,2855 }, {2528,56,924 ,2763,2606,3634 }, + {2416,1789,2467 ,808,3007,3006 }, {2711,2631,2586 ,3435,2971,2972 }, + {2748,2876,2557 ,3720,3706,3620 }, {2580,2688,2706 ,3721,3697,3707 }, + {2688,2580,2658 ,3697,3721,3698 }, {2459,355,2658 ,3722,1876,3698 }, + {355,2459,250 ,1876,3722,1916 }, {3210,3240,3239 ,3608,3590,3378 }, + {3209,3210,3239 ,3377,3608,3378 }, {437,2589,529 ,1149,3204,1148 }, + {2151,109,349 ,1603,1654,566 }, {790,2682,829 ,1339,3480,1368 }, + {2003,2029,2028 ,2542,1748,1811 }, {2518,2560,2517 ,2388,3335,3399 }, + {2269,2505,2497 ,2881,2883,3336 }, {2987,2269,2497 ,3346,2881,3336 }, + {2819,2818,2772 ,2870,3156,3198 }, {3095,2835,3173 ,2945,3079,3114 }, + {162,2290,238 ,2822,1982,868 }, {2724,2726,2776 ,2668,2667,3192 }, + {2723,2776,2775 ,2706,3192,3477 }, {4086,284,557 ,3723,139,3724 }, + {1255,795,2576 ,3248,302,3076 }, {3025,3024,2975 ,3094,3098,3157 }, + {2884,2928,2883 ,2940,2578,2579 }, {2537,2427,2677 ,2985,3237,3033 }, + {2598,2599,2673 ,2602,2601,2974 }, {2269,2841,2506 ,2881,3443,2882 }, + {2552,2476,2300 ,2657,2441,2640 }, {8065,8094,8080 ,96,98,1094 }, + {1238,1515,1373 ,1569,1502,1501 }, {2489,2475,2298 ,2611,3047,3013 }, + {3429,3428,3378 ,1978,3261,1442 }, {3143,2537,968 ,3135,2985,1661 }, + {3699,3745,3744 ,2121,2546,2390 }, {2554,2805,2401 ,2853,3230,2855 }, + {1969,4644,1970 ,2369,2425,2427 }, {1482,877,58 ,2265,2243,2156 }, + {2748,2579,2706 ,3720,3725,3707 }, {2876,2748,2706 ,3706,3720,3707 }, + {2579,2580,2706 ,3725,3721,3707 }, {2580,2578,2658 ,3721,3552,3698 }, + {2578,2459,2658 ,3552,3722,3698 }, {2614,2613,2526 ,2638,2665,2636 }, + {3124,3123,3099 ,3604,3023,3603 }, {3597,3596,3545 ,1810,1792,926 }, + {2544,79,2691 ,2451,1510,3169 }, {6155,6413,6970 ,3726,112,3688 }, + {4302,4303,166 ,3727,3728,3729 }, {2685,2737,2684 ,2642,2626,2644 }, + {2692,56,2528 ,2845,2606,2763 }, {2411,2692,2528 ,2777,2845,2763 }, + {2602,2601,2517 ,3234,3398,3399 }, {2569,554,1336 ,3291,2588,2924 }, + {2647,2462,2353 ,3276,3245,3170 }, {2773,2819,2772 ,2871,2870,3198 }, + {2438,2510,2437 ,3163,3162,2576 }, {2771,2817,2816 ,3197,3174,3475 }, + {2816,2817,2880 ,3475,3174,2591 }, {3359,6393,3305 ,824,1556,879 }, + {4146,4170,3844 ,3730,3731,2443 }, {2819,2820,2883 ,2870,2869,2579 }, + {2202,2841,2269 ,2875,3443,2881 }, {2888,2887,2824 ,3651,3616,3432 }, + {2826,2888,2824 ,3592,3651,3432 }, {2676,2727,2675 ,2761,2760,3235 }, + {3379,3429,3378 ,1979,1978,1442 }, {554,785,1336 ,2588,2952,2924 }, + {2569,1336,2366 ,3291,2924,2589 }, {3087,3088,3131 ,3573,3290,3732 }, + {4068,4091,4077 ,408,123,126 }, {2826,2825,2779 ,3592,3431,2831 }, + {2780,2826,2779 ,2830,3592,2831 }, {1849,2909,1875 ,1660,3189,1663 }, + {2685,2684,2614 ,2642,2644,2638 }, {1205,1106,1107 ,3187,1664,2656 }, + {2949,2748,2557 ,3239,3720,3620 }, {2949,2965,2579 ,3239,3417,3725 }, + {2748,2949,2579 ,3720,3239,3725 }, {2765,2580,2579 ,2590,3721,3725 }, + {2389,2459,2578 ,2609,3722,3552 }, {2389,250,2459 ,2609,1916,3722 }, + {3164,3206,3163 ,3307,2886,3361 }, {725,818,817 ,721,1018,1020 }, + {4172,4173,4215 ,135,134,370 }, {6525,6573,6572 ,3703,3733,3734 }, + {291,290,2286 ,1012,1042,3717 }, {2287,291,2286 ,3735,1012,3717 }, + {6524,6525,6572 ,3704,3703,3734 }, {2371,2542,2656 ,2880,2879,2893 }, + {554,2085,785 ,2588,657,2952 }, {2572,1181,293 ,3050,1981,1430 }, + {2678,1917,1875 ,3190,833,1663 }, {2909,2678,1875 ,3189,3190,1663 }, + {1665,410,2967 ,3077,2204,2701 }, {2847,2816,2879 ,2624,3475,2621 }, + {149,2471,219 ,3736,3653,3087 }, {3175,2576,2628 ,3249,3076,3078 }, + {2427,2537,3149 ,3237,2985,3134 }, {3074,3073,3035 ,3363,3516,3364 }, + {3154,3153,3123 ,2692,2693,3023 }, {2742,2003,1565 ,2558,2542,3737 }, + {511,1565,2003 ,3569,3737,2542 }, {3494,3492,3493 ,736,930,3738 }, + {13,1303,1626 ,910,1933,564 }, {3792,2941,3791 ,3421,109,3739 }, + {2504,2310,2502 ,2898,2442,3740 }, {2331,2502,2310 ,2895,3740,2442 }, + {2584,2331,2310 ,2894,2895,2442 }, {90,2202,2269 ,2851,2875,2881 }, + {2826,2824,2825 ,3592,3432,3431 }, {3110,3109,3068 ,3337,3553,3555 }, + {2965,2765,2579 ,3417,2590,3725 }, {2761,2580,2765 ,2768,3721,2590 }, + {2761,2578,2580 ,2768,3552,3721 }, {2133,250,2389 ,2614,1916,2609 }, + {253,247,2663 ,2564,2569,3593 }, {3494,3493,3432 ,736,3738,929 }, + {2921,2971,1237 ,2691,3338,1467 }, {2886,2931,2885 ,2913,2780,2914 }, + {3434,3463,3433 ,737,1271,3049 }, {972,1073,2314 ,3277,3741,3742 }, + {2286,2250,2287 ,3717,3716,3735 }, {236,291,2287 ,2444,1012,3735 }, + {99,236,2287 ,3743,2444,3735 }, {1172,2993,1081 ,1758,3744,3745 }, + {819,2845,2800 ,2244,2995,2996 }, {2560,2602,2517 ,3335,3234,3399 }, + {3993,3994,4035 ,2807,2806,1601 }, {2999,3027,3026 ,2998,3350,3095 }, + {968,2572,293 ,1661,3050,1430 }, {2976,3026,2998 ,2999,3095,3446 }, + {2510,2511,2593 ,3162,3112,3111 }, {2166,2100,2167 ,1040,1218,1323 }, + {292,239,2292 ,2563,1103,1983 }, {2874,3175,2628 ,3195,3249,3078 }, + {3063,3103,3102 ,3093,3137,3097 }, {3062,3063,3102 ,3096,3093,3097 }, + {2722,2774,2721 ,3014,2916,2917 }, {2503,2353,2691 ,2948,3170,3169 }, + {3149,3260,2201 ,3134,3188,2455 }, {2981,2980,2933 ,3550,3305,3556 }, + {2981,3000,2980 ,3550,3304,3305 }, {6388,6088,6436 ,3184,3670,3681 }, + {2416,2467,2631 ,808,3006,2971 }, {3128,3127,3073 ,2809,2811,3516 }, + {4426,6837,6577 ,1056,1057,97 }, {2908,1249,3179 ,3052,2876,2947 }, + {3138,1050,1181 ,2984,2282,1981 }, {1406,1329,1370 ,1135,1134,1500 }, + {2700,2726,2725 ,3009,2667,2666 }, {2673,2700,2725 ,2974,3009,2666 }, + {2888,2934,2933 ,3651,3602,3556 }, {2976,2999,3026 ,2999,2998,3095 }, + {2887,2888,2933 ,3616,3651,3556 }, {6573,6613,6612 ,3733,3746,1389 }, + {2722,2721,2669 ,3014,2917,3015 }, {2949,2557,2920 ,3239,3620,1799 }, + {2914,2765,2965 ,2573,2590,3417 }, {2635,2578,2761 ,2607,3552,2768 }, + {2430,2133,2389 ,2613,2614,2609 }, {2131,2132,2865 ,656,655,3107 }, + {3036,3035,2984 ,3362,3364,3458 }, {6572,6573,6612 ,3734,3733,1389 }, + {2735,929,830 ,2887,1234,1996 }, {5702,3009,5038 ,3630,3747,3628 }, + {3482,3478,8069 ,2494,885,1095 }, {2556,2449,2430 ,2779,3259,2613 }, + {2449,91,2430 ,3259,1984,2613 }, {2934,2981,2933 ,3602,3550,3556 }, + {3235,3236,2456 ,3394,2717,2790 }, {2863,2886,2822 ,3242,2913,2826 }, + {7371,7423,7370 ,3748,3749,3750 }, {2249,6897,6531 ,3751,54,2280 }, + {3258,3484,1009 ,542,283,3752 }, {7797,7848,7823 ,3506,343,345 }, + {3763,2707,1305 ,3753,235,466 }, {4083,4157,5458 ,3754,3755,3756 }, + {1376,4610,4788 ,1937,3757,3758 }, {7826,7876,7853 ,1616,1725,1617 }, + {5352,8138,4022 ,3502,2018,3503 }, {6649,6648,6612 ,3759,1390,1389 }, + {1156,106,5138 ,3100,1073,1940 }, {2622,2567,2901 ,2840,2842,3468 }, + {3648,3701,3647 ,734,733,2521 }, {4248,3972,1159 ,3256,3487,3760 }, + {836,1068,1385 ,3551,3761,3762 }, {3819,1159,5414 ,3763,3760,3764 }, + {7412,7410,7411 ,3765,3766,3767 }, {3306,3326,3325 ,3695,826,825 }, + {1668,689,4206 ,3212,1363,682 }, {1668,4206,141 ,3212,682,681 }, + {7825,7850,7824 ,3768,3769,3770 }, {1009,2607,1250 ,3752,2452,3771 }, + {7246,7268,7291 ,3772,3773,3774 }, {7979,8003,8002 ,3775,3776,3777 }, + {3690,958,872 ,3778,3779,2962 }, {5414,1159,3673 ,3764,3760,648 }, + {2731,3690,872 ,659,3778,2962 }, {3690,2394,958 ,3778,3780,3779 }, + {1594,3312,958 ,3781,3782,3779 }, {2394,1594,958 ,3780,3781,3779 }, + {5731,3615,5682 ,934,201,3783 }, {1594,3098,3420 ,3781,3784,3785 }, + {3312,1594,3420 ,3782,3781,3785 }, {3098,3254,3299 ,3784,3233,3232 }, + {3420,3098,3299 ,3785,3784,3232 }, {6355,6356,5151 ,3786,3787,1803 }, + {6311,8114,8122 ,1097,2286,384 }, {3169,3128,3170 ,2810,2809,3510 }, + {1510,3893,5862 ,3788,3789,3790 }, {8075,8105,8107 ,2308,3791,658 }, + {3454,3340,4424 ,710,114,3792 }, {4424,3340,6414 ,3792,114,113 }, + {5327,5300,6525 ,3702,3793,3703 }, {1942,1668,141 ,2337,3212,681 }, + {6415,4424,6414 ,386,3792,113 }, {8089,8097,3861 ,254,1710,1147 }, + {5259,4827,5282 ,3794,2734,1194 }, {5302,5706,6008 ,3795,3796,3797 }, + {2892,1669,771 ,918,3798,3799 }, {2582,3513,1017 ,873,441,1793 }, + {5299,6041,5251 ,3800,3801,3802 }, {7796,7823,7795 ,3803,345,3804 }, + {3371,685,794 ,3708,2674,3392 }, {3306,3325,3305 ,3695,825,879 }, + {3932,652,55 ,1096,334,3507 }, {3998,4040,4039 ,381,460,382 }, + {637,3585,3736 ,3805,3806,3807 }, {3474,637,3736 ,3808,3805,3807 }, + {1068,3685,3057 ,3761,3809,3810 }, {3685,3474,3736 ,3809,3808,3807 }, + {1385,1068,3057 ,3762,3761,3810 }, {3685,3736,3057 ,3809,3807,3810 }, + {541,1385,3057 ,3811,3762,3810 }, {3147,3477,4162 ,2285,1736,1738 }, + {8096,8136,8115 ,1796,2449,2448 }, {3477,4129,5785 ,1736,3812,1737 }, + {5680,5698,5679 ,50,44,2553 }, {836,1385,541 ,3551,3762,3811 }, + {2746,8089,3861 ,1517,254,1147 }, {3113,1734,269 ,410,2219,3813 }, + {3413,3441,3391 ,3814,2987,3815 }, {3701,3700,3647 ,733,2545,2521 }, + {296,3317,630 ,1558,1882,2491 }, {3493,3492,3432 ,3738,930,929 }, + {8124,8110,3271 ,1141,1140,1727 }, {3939,3955,3090 ,3539,3538,2805 }, + {4027,4712,1020 ,3470,3816,3817 }, {5231,922,5000 ,115,117,286 }, + {6041,5298,5273 ,3801,3818,3819 }, {3891,3890,3869 ,3820,1913,1912 }, + {7823,7847,7795 ,345,3521,3804 }, {2856,588,3549 ,3821,3822,3823 }, + {3690,1594,2394 ,3778,3781,3780 }, {5698,5726,5725 ,44,515,3824 }, + {4178,5297,4023 ,3825,3826,3827 }, {1694,836,541 ,3828,3551,3811 }, + {3453,3054,1787 ,917,916,2897 }, {531,34,62 ,3829,3830,3831 }, + {3441,3468,3440 ,2987,2688,2690 }, {3187,4371,3972 ,3402,3489,3487 }, + {3491,3544,3490 ,955,1153,2502 }, {7074,7073,7038 ,2747,662,3832 }, + {6613,6649,6612 ,3746,3759,1389 }, {6650,6680,6648 ,3833,3834,1390 }, + {3855,3891,3869 ,1511,3820,1912 }, {3504,3503,3468 ,2986,3835,2688 }, + {3441,3440,3391 ,2987,2690,3815 }, {3549,3369,62 ,3823,3836,3831 }, + {3529,3371,794 ,3837,3708,3392 }, {3369,531,62 ,3836,3829,3831 }, + {3529,794,3476 ,3837,3392,3838 }, {3568,3529,3476 ,3839,3837,3838 }, + {1783,543,1341 ,2251,3840,3841 }, {3891,3926,3890 ,3820,3125,1913 }, + {3612,3819,3473 ,3842,3763,649 }, {5785,4129,4162 ,1737,3812,1738 }, + {5242,5241,5220 ,3843,2120,2119 }, {7253,7252,6349 ,1855,3437,705 }, + {3764,3718,3316 ,3844,3845,2149 }, {3948,915,4326 ,280,443,219 }, + {3264,836,1694 ,2468,3551,3828 }, {882,3264,1694 ,3846,2468,3828 }, + {5258,5259,5282 ,1193,3794,1194 }, {3924,3968,3923 ,3847,3848,3849 }, + {5224,5244,5223 ,3850,79,3851 }, {5224,5223,4679 ,3850,3851,3852 }, + {7982,8007,7958 ,3853,3854,3855 }, {3339,3690,2731 ,3856,3778,659 }, + {3006,3339,2731 ,414,3856,659 }, {3279,1594,3690 ,3857,3781,3778 }, + {3339,3279,3690 ,3856,3857,3778 }, {1415,3098,1594 ,3858,3784,3781 }, + {3279,1415,1594 ,3857,3858,3781 }, {5286,5285,5241 ,3859,2169,2120 }, + {3392,3413,3391 ,3860,3814,3815 }, {5640,5679,5678 ,2214,2553,3861 }, + {1974,3549,62 ,3862,3823,3831 }, {4157,5481,5304 ,3755,3863,654 }, + {5639,5640,5656 ,2215,2214,3864 }, {5656,5640,5678 ,3864,2214,3861 }, + {5380,5385,5304 ,653,3865,654 }, {6649,6650,6648 ,3759,3833,1390 }, + {3511,3254,3098 ,539,3233,3784 }, {1415,3511,3098 ,3858,539,3784 }, + {3511,4019,3254 ,539,227,3233 }, {5013,8074,5950 ,684,2460,2873 }, + {6698,6697,6680 ,629,3866,3834 }, {3396,3764,3316 ,3867,3844,2149 }, + {2256,6390,2162 ,2323,3868,3278 }, {3828,4199,4186 ,1364,3869,683 }, + {4206,3828,4186 ,682,1364,683 }, {3864,138,1206 ,3517,434,2218 }, + {3972,3864,1206 ,3487,3517,2218 }, {6650,6698,6680 ,3833,629,3834 }, + {4009,4008,3967 ,3870,3871,3872 }, {6073,6055,3659 ,3873,3874,3875 }, + {834,96,1539 ,3876,836,835 }, {4861,4896,4669 ,3877,3878,3879 }, + {1724,3425,1249 ,3880,1859,2876 }, {834,1588,2856 ,3876,3881,3821 }, + {5354,4866,3907 ,3882,3641,3883 }, {3266,543,1783 ,3884,3840,2251 }, + {7796,7795,7745 ,3803,3804,650 }, {2785,1907,3878 ,1246,1245,1248 }, + {6574,6614,6613 ,3885,3886,3746 }, {3652,3651,3602 ,3887,3888,3889 }, + {3603,3652,3602 ,3890,3887,3889 }, {3392,3391,3332 ,3860,3815,3891 }, + {3309,3333,3308 ,3892,3893,3894 }, {3333,3392,3332 ,3893,3860,3891 }, + {3309,3308,3294 ,3892,3894,3895 }, {3333,3332,3308 ,3893,3891,3894 }, + {1415,3279,3339 ,3858,3857,3856 }, {3476,794,3252 ,3838,3392,781 }, + {1207,5153,5146 ,1074,3896,1075 }, {4291,920,4926 ,58,57,3897 }, + {3681,3258,1250 ,691,542,3771 }, {2813,3911,3738 ,3898,3899,3900 }, + {3294,3308,7110 ,3895,3894,1990 }, {3810,3853,3809 ,3901,3902,3903 }, + {5816,5833,5832 ,545,544,3904 }, {3968,4009,3967 ,3848,3870,3872 }, + {2472,13,1626 ,2009,910,564 }, {2659,3286,601 ,3485,56,3905 }, + {4005,2422,4004 ,3906,3907,3908 }, {4007,2798,4005 ,3909,420,3906 }, + {789,3878,1156 ,3910,1248,3100 }, {1510,1410,3893 ,3788,3911,3789 }, + {28,1096,88 ,1059,2727,3581 }, {3439,3438,3389 ,2689,3912,3913 }, + {3604,3603,3554 ,3384,3890,3914 }, {3861,8083,3456 ,1147,2103,1145 }, + {109,254,2039 ,1654,2008,1655 }, {3286,79,2990 ,56,1510,3646 }, + {2832,3694,2907 ,638,3915,111 }, {1882,4543,4980 ,894,3002,241 }, + {4714,4778,4777 ,1031,3916,3917 }, {6310,6742,6735 ,3918,3422,3472 }, + {3243,1111,4845 ,3919,3920,3921 }, {3336,6285,7095 ,3922,3923,3924 }, + {3911,1940,3738 ,3899,3925,3900 }, {3764,1412,3841 ,3844,3926,3927 }, + {3718,3764,3841 ,3845,3844,3927 }, {1412,2813,3738 ,3926,3898,3900 }, + {3841,1412,3738 ,3927,3926,3900 }, {3911,3447,1940 ,3899,3928,3925 }, + {552,3900,2945 ,3929,884,1239 }, {978,1029,977 ,1620,3930,3931 }, + {3373,206,1804 ,693,3932,2902 }, {3425,1693,1249 ,1859,264,2876 }, + {2357,2912,2422 ,3933,3934,3907 }, {3981,8140,3263 ,915,2269,252 }, + {3713,3712,3654 ,3935,1439,1438 }, {3655,3713,3654 ,3936,3935,1438 }, + {1215,6950,5006 ,3937,1349,1351 }, {5701,5682,5683 ,3938,3783,3939 }, + {8112,6351,8079 ,3530,1568,55 }, {5732,5731,5682 ,3940,934,3783 }, + {5701,5732,5682 ,3938,3940,3783 }, {4186,4199,1607 ,683,3869,3941 }, + {5832,3727,5831 ,3904,3942,3943 }, {931,1256,2910 ,3944,3945,3946 }, + {3224,931,2910 ,3947,3944,3946 }, {3653,3678,3652 ,1440,3948,3887 }, + {5732,5770,5769 ,3940,3949,932 }, {5731,5732,5769 ,934,3940,932 }, + {5770,5768,5769 ,3949,933,932 }, {7649,7699,7648 ,3950,3951,3952 }, + {5092,3766,4585 ,3953,3954,3955 }, {3619,2790,2740 ,3956,3576,3456 }, + {3554,3553,3502 ,3914,3957,3958 }, {3503,3554,3502 ,3835,3914,3958 }, + {592,1018,3368 ,1863,3959,3960 }, {20,348,75 ,3961,643,3962 }, + {7452,7451,7424 ,1875,3963,1188 }, {4005,4004,3964 ,3906,3908,3964 }, + {2422,731,4004 ,3907,3965,3908 }, {2798,2357,2422 ,420,3933,3907 }, + {6279,6278,7092 ,3966,3967,3968 }, {6870,6898,6796 ,2616,1946,2617 }, + {5308,564,4868 ,3528,3529,3969 }, {3712,3713,3714 ,1439,3935,3970 }, + {8138,3534,4022 ,2018,942,3503 }, {3537,3725,2072 ,1735,3971,1812 }, + {1556,3283,3252 ,3365,262,781 }, {406,746,2958 ,2566,2661,1374 }, + {3329,3362,3328 ,3972,1200,3973 }, {3616,4186,2795 ,3974,683,3975 }, + {3218,5480,4162 ,3976,1739,1738 }, {3962,3961,3918 ,3977,2346,2335 }, + {1256,931,1518 ,3945,3944,3978 }, {3652,3677,3651 ,3887,3979,3888 }, + {3708,3707,3651 ,3980,3981,3888 }, {4764,4765,4797 ,3982,3983,3984 }, + {6603,6744,1116 ,3985,3986,1401 }, {5838,6015,6083 ,3987,3988,3989 }, + {4837,1375,955 ,3990,325,3991 }, {3899,3760,5146 ,1938,2250,1075 }, + {3503,3502,3467 ,3835,3958,3992 }, {3440,3439,3390 ,2690,2689,3509 }, + {3391,3440,3390 ,3815,2690,3509 }, {635,20,75 ,2183,3961,3962 }, + {8134,6897,8120 ,3263,54,906 }, {5266,5288,5243 ,81,3993,3994 }, + {5306,5305,5335 ,462,2283,2523 }, {7425,7454,7397 ,3995,3996,1186 }, + {4868,4888,5379 ,3969,3997,3998 }, {5805,4129,5385 ,3999,3812,3865 }, + {6467,2407,6484 ,4000,4001,685 }, {4868,5340,5287 ,3969,4002,4003 }, + {6847,6899,3891 ,4004,4005,3820 }, {4129,5458,5385 ,3812,3756,3865 }, + {4199,694,4487 ,3869,1987,4006 }, {1333,2812,8064 ,849,1914,3141 }, + {3601,3650,3624 ,4007,4008,4009 }, {3257,5855,1974 ,4010,4011,3862 }, + {1584,694,4199 ,4012,1987,3869 }, {3809,3808,3753 ,3903,4013,4014 }, + {3252,1941,3283 ,781,1034,262 }, {4713,5768,5770 ,4015,933,3949 }, + {3907,4914,917 ,3883,65,4016 }, {1561,1459,407 ,1865,4017,4018 }, + {7595,7649,7594 ,4019,3950,4020 }, {4252,4157,4083 ,4021,3755,3754 }, + {2945,3339,3006 ,1239,3856,414 }, {7874,7924,7873 ,344,815,817 }, + {5392,5717,5122 ,4022,4023,4024 }, {3755,3809,3753 ,4025,3903,4014 }, + {5217,4797,5258 ,4026,3984,1193 }, {1018,3681,1486 ,3959,691,4027 }, + {3750,3681,1018 ,690,691,3959 }, {3681,1250,1486 ,691,3771,4027 }, + {4221,2955,592 ,1862,3640,1863 }, {592,3750,1018 ,1863,690,3959 }, + {4516,1152,1692 ,4028,4029,4030 }, {588,592,3368 ,3822,1863,3960 }, + {3274,3819,3612 ,3172,3763,3842 }, {834,1670,1588 ,3876,2327,3881 }, + {2314,6467,2223 ,3742,4000,2336 }, {530,2044,959 ,512,4031,1358 }, + {3747,3701,6249 ,2925,733,4032 }, {2203,1415,3339 ,4033,3858,3856 }, + {2945,2203,3339 ,1239,4033,3856 }, {3534,8121,6353 ,942,3466,749 }, + {3603,3602,3553 ,3890,3889,3957 }, {3554,3603,3553 ,3914,3890,3957 }, + {3748,3218,5385 ,4034,3976,3865 }, {1444,382,3318 ,4035,3534,4036 }, + {6030,5475,5576 ,4037,4038,4039 }, {3663,3511,1415 ,4040,539,3858 }, + {2203,3663,1415 ,4033,4040,3858 }, {5858,5254,3436 ,4041,4042,2145 }, + {556,5295,4471 ,4043,2188,2187 }, {4394,4323,4273 ,4044,4045,120 }, + {4043,2798,4007 ,419,420,3909 }, {6750,6749,6697 ,631,4046,3866 }, + {530,8072,3040 ,512,511,619 }, {4199,4487,1607 ,3869,4006,3941 }, + {2246,7067,5755 ,591,3585,4047 }, {4323,1297,2042 ,4045,4048,121 }, + {3809,3833,3808 ,3903,4049,4013 }, {4273,4323,2042 ,120,4045,121 }, + {803,6204,6203 ,144,4050,1249 }, {7797,7796,7745 ,3506,3803,650 }, + {3663,4029,3511 ,4040,225,539 }, {8092,8130,8098 ,2267,905,907 }, + {733,184,5006 ,4051,4052,1351 }, {4105,5392,5122 ,4053,4022,4024 }, + {1152,3274,3612 ,4029,3172,3842 }, {3819,4468,3473 ,3763,647,649 }, + {3001,1159,3819 ,3210,3760,3763 }, {3819,5414,4468 ,3763,3764,647 }, + {5957,4105,5122 ,4054,4053,4024 }, {3886,3887,3885 ,4055,4056,4057 }, + {8105,3006,8137 ,3791,414,2174 }, {3612,3473,2843 ,3842,649,4058 }, + {3577,2843,4469 ,4059,4058,4060 }, {1102,1733,4165 ,2317,2328,640 }, + {1692,2843,3577 ,4030,4058,4059 }, {1692,3612,2843 ,4030,3842,4058 }, + {3368,1018,3577 ,3960,3959,4059 }, {1486,1692,3577 ,4027,4030,4059 }, + {3344,592,588 ,1861,1863,3822 }, {1211,3327,3636 ,2007,439,1060 }, + {3844,1283,2476 ,2443,2978,2441 }, {3668,530,959 ,513,512,1358 }, + {3807,3806,3752 ,4061,4062,4063 }, {5799,5800,5815 ,4064,702,4065 }, + {3828,1454,4199 ,1364,2318,3869 }, {22,1468,2042 ,4066,174,121 }, + {2013,2380,1332 ,2242,2597,3492 }, {3292,3331,3291 ,4067,1183,1203 }, + {2607,4516,1250 ,2452,4028,3771 }, {3922,3965,3921 ,4068,4069,4070 }, + {3755,3754,3707 ,4025,4071,3981 }, {3677,3708,3651 ,3979,3980,3888 }, + {3708,3755,3707 ,3980,4025,3981 }, {3755,3753,3754 ,4025,4014,4071 }, + {1297,22,2042 ,4048,4066,121 }, {22,1451,1468 ,4066,175,174 }, + {3289,3288,7196 ,2056,2058,2401 }, {5928,3903,3915 ,4072,296,1575 }, + {7102,7129,7101 ,1240,1026,4073 }, {22,3786,1451 ,4066,4074,175 }, + {5693,5715,5714 ,4075,4076,4077 }, {3484,1300,1009 ,283,285,3752 }, + {4009,4007,4008 ,3870,3909,3871 }, {1018,1486,3577 ,3959,4027,4059 }, + {6281,6280,7090 ,4078,4079,4080 }, {6698,6750,6697 ,629,631,3866 }, + {3981,2833,3076 ,915,2065,2066 }, {2155,6353,5444 ,580,749,3410 }, + {2681,3119,2346 ,3254,3223,3252 }, {4856,4570,3834 ,4081,4082,4083 }, + {3006,552,2945 ,414,3929,1239 }, {2265,177,2297 ,2662,3618,2663 }, + {1483,834,1539 ,4084,3876,835 }, {3887,3923,3922 ,4056,3849,4068 }, + {3885,3887,3922 ,4057,4056,4068 }, {6386,4856,3834 ,4085,4081,4083 }, + {303,915,3948 ,4086,443,280 }, {68,4813,1655 ,4087,4088,4089 }, + {8100,4021,3482 ,1713,1712,2494 }, {1335,1443,1152 ,236,182,4029 }, + {3753,3807,3752 ,4014,4061,4063 }, {3871,1334,3748 ,397,1935,4034 }, + {1250,4516,1486 ,3771,4028,4027 }, {3555,3554,3503 ,4090,3914,3835 }, + {3972,1206,5389 ,3487,2218,4091 }, {6096,3765,6050 ,4092,4093,4094 }, + {3258,1009,1250 ,542,3752,3771 }, {4527,5305,3147 ,1740,2283,2285 }, + {4915,917,4914 ,64,4016,65 }, {3965,3964,3921 ,4069,3964,4070 }, + {5228,5247,5227 ,4095,4096,4097 }, {1935,913,915 ,4098,444,443 }, + {4866,5337,5354 ,3641,572,3882 }, {3968,3967,3923 ,3848,3872,3849 }, + {303,1935,915 ,4086,4098,443 }, {3263,2651,3075 ,252,1516,2670 }, + {3747,3700,3701 ,2925,2545,733 }, {3278,2691,79 ,1158,3169,1510 }, + {177,2363,2297 ,3618,2571,2663 }, {106,5146,5139 ,1073,1075,1939 }, + {3457,3348,2043 ,4099,2255,4100 }, {3348,684,2043 ,2255,4101,4100 }, + {2910,1256,3004 ,3946,3945,2437 }, {1219,1170,3471 ,4102,4103,4104 }, + {2795,4186,1607 ,3975,683,3941 }, {202,5957,5122 ,4105,4054,4024 }, + {1935,3622,913 ,4098,4106,444 }, {3850,3851,3885 ,4107,4108,4057 }, + {3851,3886,3885 ,4108,4055,4057 }, {3851,3850,3808 ,4108,4107,4013 }, + {3833,3851,3808 ,4049,4108,4013 }, {3966,4007,3965 ,4109,3909,4069 }, + {4008,4007,3967 ,3871,3909,3872 }, {4007,4005,4006 ,3909,3906,4110 }, + {7108,7095,7109 ,4111,3924,4112 }, {2607,1335,4516 ,2452,236,4028 }, + {5806,248,3350 ,4113,4114,2541 }, {3330,3329,3290 ,1185,3972,1204 }, + {3291,3330,3290 ,1203,1185,1204 }, {3330,3363,3329 ,1185,1911,3972 }, + {3389,3388,3329 ,3913,4115,3972 }, {3363,3389,3329 ,1911,3913,3972 }, + {7899,7898,7875 ,4116,4117,4118 }, {3931,3664,3475 ,1868,1557,1559 }, + {8125,8068,8117 ,752,1784,4119 }, {4516,1692,1486 ,4028,4030,4027 }, + {3972,5389,4185 ,3487,4091,409 }, {3001,4248,1159 ,3210,3256,3760 }, + {3709,3708,3652 ,4120,3980,3887 }, {8038,8052,8022 ,4121,4122,4123 }, + {5871,3337,5116 ,4124,4125,4126 }, {3294,7110,3309 ,3895,1990,3892 }, + {2380,2624,3012 ,2597,3132,3491 }, {3732,3818,2785 ,3536,1244,1246 }, + {5592,5705,1608 ,3671,4127,4128 }, {2813,3447,3911 ,3898,3928,3899 }, + {684,11,2043 ,4101,299,4100 }, {3926,3925,3889 ,3125,3227,3126 }, + {2968,2693,1693 ,263,3416,264 }, {1295,3348,3040 ,618,2255,619 }, + {2039,2472,1626 ,1655,2009,564 }, {3727,3616,1794 ,3942,3974,4129 }, + {3808,3850,3806 ,4013,4107,4062 }, {3849,3850,3884 ,4130,4107,4131 }, + {3724,5644,5495 ,4132,4133,4134 }, {3348,3457,3040 ,2255,4099,619 }, + {3556,3554,3555 ,3385,3914,4090 }, {7995,8020,7973 ,4135,1405,3624 }, + {273,272,205 ,1749,1680,1741 }, {5198,4710,5177 ,423,4136,424 }, + {2912,1379,931 ,3934,4137,3944 }, {4007,4006,3965 ,3909,4110,4069 }, + {3966,3965,3922 ,4109,4069,4068 }, {3923,3966,3922 ,3849,4109,4068 }, + {2912,3178,731 ,3934,4138,3965 }, {2798,2422,4005 ,420,3907,3906 }, + {6786,6785,6749 ,4139,4140,4046 }, {6750,6786,6749 ,631,4139,4046 }, + {3412,3387,3388 ,4141,1787,4115 }, {3389,3412,3388 ,3913,4141,4115 }, + {3438,3437,3387 ,3912,4142,1787 }, {1670,834,1483 ,2327,3876,4084 }, + {2698,5335,4236 ,105,2523,3488 }, {3706,3752,3705 ,4143,4063,4144 }, + {3624,3650,3601 ,4009,4008,4007 }, {6611,5401,6571 ,1391,4145,4146 }, + {5800,5816,5815 ,702,545,4065 }, {1588,2856,5855 ,3881,3821,4011 }, + {1005,3338,4368 ,3535,4147,4148 }, {3883,3922,3921 ,4149,4068,4070 }, + {3849,3884,3883 ,4130,4131,4149 }, {918,558,2044 ,4150,4151,4031 }, + {530,918,2044 ,512,4150,4031 }, {2868,2832,2869 ,1577,638,1576 }, + {3447,3736,3585 ,3928,3807,3806 }, {915,3341,4326 ,443,541,219 }, + {3622,4289,2892 ,4106,4152,918 }, {7090,6280,7091 ,4080,4079,4153 }, + {3283,3476,3252 ,262,3838,781 }, {913,3622,2892 ,444,4106,918 }, + {3880,3881,3918 ,2320,4154,2335 }, {4289,3172,2892 ,4152,4155,918 }, + {3850,3885,3884 ,4107,4057,4131 }, {694,2477,1607 ,1987,642,3941 }, + {5438,5439,6705 ,4156,4157,4158 }, {3296,3313,177 ,4159,4160,3618 }, + {3313,3343,3342 ,4160,2515,2514 }, {177,3313,3342 ,3618,4160,2514 }, + {166,85,4250 ,3729,2504,4161 }, {3178,2912,931 ,4138,3934,3944 }, + {7847,7846,7795 ,3521,4162,3804 }, {5831,1794,5830 ,3943,4129,4163 }, + {3257,1588,5855 ,4010,3881,4011 }, {2856,3549,1974 ,3821,3823,3862 }, + {5458,4157,5385 ,3756,3755,3865 }, {1152,3612,1692 ,4029,3842,4030 }, + {531,3577,4469 ,3829,4059,4060 }, {96,834,3909 ,836,3876,4164 }, + {3412,3438,3387 ,4141,3912,1787 }, {1733,1483,4165 ,2328,4084,640 }, + {1733,1670,1483 ,2328,2327,4084 }, {5305,4527,4236 ,2283,1740,3488 }, + {2405,4900,5578 ,4165,585,587 }, {2296,2264,2265 ,2664,4166,2662 }, + {3553,3601,3552 ,3957,4007,4167 }, {3849,3848,3806 ,4130,4168,4062 }, + {3848,3849,3883 ,4168,4130,4149 }, {1335,1152,4516 ,236,4029,4028 }, + {4162,5805,5385 ,1738,3999,3865 }, {3696,3697,3742 ,2325,2224,3419 }, + {3540,3541,3571 ,3357,981,3652 }, {6804,6803,6785 ,3342,4169,4140 }, + {5354,3907,4866 ,3882,3883,3641 }, {3285,4900,2405 ,3064,585,4165 }, + {6116,3336,7095 ,4170,3922,3924 }, {7974,7995,7973 ,4171,4135,3624 }, + {2088,3932,55 ,1667,1096,3507 }, {5764,5800,5799 ,537,702,4064 }, + {36,35,3866 ,1478,1338,1479 }, {3967,4007,3966 ,3872,3909,4109 }, + {3172,3093,1669 ,4155,4172,3798 }, {1538,3402,2410 ,0,3660,1 }, + {882,3271,8117 ,3846,1727,4119 }, {3613,2203,2945 ,4173,4033,1239 }, + {3455,3613,2945 ,886,4173,1239 }, {2892,3172,1669 ,918,4155,3798 }, + {3652,3708,3677 ,3887,3980,3979 }, {3468,3503,3467 ,2688,3835,3992 }, + {1248,1207,3878 ,2850,1074,1248 }, {5138,5139,5260 ,1940,1939,4174 }, + {834,1588,3257 ,3876,3881,4010 }, {635,2657,20 ,2183,2185,3961 }, + {3438,3466,3437 ,3912,4175,4142 }, {3501,3500,3437 ,4176,4177,4142 }, + {3466,3501,3437 ,4175,4176,4142 }, {3579,3550,3551 ,4178,4179,4180 }, + {3552,3579,3551 ,4167,4178,4180 }, {1454,1102,694 ,2318,2317,1987 }, + {1102,4165,694 ,2317,640,1987 }, {3473,3673,2153 ,649,648,4181 }, + {5109,5107,5108 ,4182,4183,4184 }, {874,3866,783 ,636,1479,679 }, + {4913,4866,3907 ,4185,3641,3883 }, {2368,2443,2365 ,2943,2942,3351 }, + {4003,1003,4002 ,4186,4187,4188 }, {1909,3663,2203 ,4189,4040,4033 }, + {3613,1909,2203 ,4173,4189,4033 }, {1338,4029,3663 ,1077,225,4040 }, + {1909,1338,3663 ,4189,1077,4040 }, {3093,5119,4942 ,4172,4190,4191 }, + {1669,3093,4942 ,3798,4172,4191 }, {6226,5562,3905 ,4192,4193,4194 }, + {3656,3655,3626 ,4195,3936,4196 }, {5705,5992,1608 ,4127,4197,4128 }, + {1022,969,793 ,4198,4199,4200 }, {4855,4809,1469 ,4201,4202,2361 }, + {3297,3314,3313 ,4203,3575,4160 }, {3313,3314,3343 ,4160,3575,2515 }, + {6786,6804,6785 ,4139,3342,4140 }, {3653,3652,3603 ,1440,3887,3890 }, + {5573,1525,407 ,2217,1866,4018 }, {3888,3924,3887 ,4204,3847,4056 }, + {3391,3390,3331 ,3815,3509,1183 }, {3332,3391,3331 ,3891,3815,1183 }, + {6803,6853,5587 ,4169,2505,1459 }, {3579,3601,3550 ,4178,4007,4179 }, + {3601,3578,3550 ,4007,4205,4179 }, {3624,3623,3578 ,4009,4206,4205 }, + {3601,3624,3578 ,4007,4009,4205 }, {3727,1794,5831 ,3942,4129,3943 }, + {3341,915,863 ,541,443,284 }, {3389,3438,3412 ,3913,3912,4141 }, + {931,1629,1518 ,3944,4207,3978 }, {1588,3344,2856 ,3881,1861,3821 }, + {3552,3601,3579 ,4167,4007,4178 }, {4003,4002,3963 ,4186,4188,4208 }, + {4393,5306,5335 ,463,462,2523 }, {3573,3591,3590 ,980,1652,1867 }, + {5572,407,3147 ,2284,4018,2285 }, {3290,3329,3289 ,1204,3972,2056 }, + {3218,4162,5385 ,3976,1738,3865 }, {8103,3283,3425 ,1291,262,1859 }, + {4558,4599,4631 ,4209,4210,1816 }, {1454,1584,4199 ,2318,4012,3869 }, + {5335,5305,4236 ,2523,2283,3488 }, {3314,2368,2365 ,3575,2943,3351 }, + {3343,3314,2365 ,2515,3575,3351 }, {2428,2872,784 ,371,311,4211 }, + {687,2088,1155 ,1665,1667,3333 }, {5697,5698,5725 ,2552,44,3824 }, + {5725,5726,5763 ,3824,515,4212 }, {3628,5045,6235 ,4213,4214,4215 }, + {5726,5764,5763 ,515,537,4212 }, {3248,6076,5982 ,4216,4217,4218 }, + {5045,6165,6235 ,4214,4219,4215 }, {6103,5988,6123 ,4220,2516,4221 }, + {4713,5837,5804 ,4015,3621,3623 }, {1333,3300,2812 ,849,694,1914 }, + {3300,3349,2812 ,694,2054,1914 }, {3336,3335,3309 ,3922,4222,3892 }, + {5471,5435,6640 ,2051,4223,4224 }, {5026,5165,4881 ,4225,4226,2731 }, + {8106,4835,8135 ,2438,2130,2439 }, {3998,3997,3957 ,381,380,295 }, + {3224,3178,931 ,3947,4138,3944 }, {3624,3650,3623 ,4009,4008,4206 }, + {75,348,268 ,3962,643,645 }, {5119,5424,5420 ,4190,4227,4228 }, + {1300,2607,1009 ,285,2452,3752 }, {4866,4913,4865 ,3641,4185,4229 }, + {4942,5119,5420 ,4191,4190,4228 }, {5424,201,84 ,4227,4230,4231 }, + {5420,5424,84 ,4228,4227,4231 }, {201,2643,3759 ,4230,4232,4233 }, + {84,201,3759 ,4231,4230,4233 }, {1160,3096,3241 ,3661,4234,3504 }, + {2316,8059,2275 ,3067,4235,3152 }, {3573,3572,3541 ,980,1931,981 }, + {3678,3709,3652 ,3948,4120,3887 }, {3710,3708,3709 ,4236,3980,4120 }, + {4846,1622,1517 ,4237,4238,600 }, {6324,3365,3335 ,4239,4240,4222 }, + {1851,1733,1102 ,4241,2328,2317 }, {3744,3796,3795 ,2390,2969,3448 }, + {4373,1851,1102 ,3288,4241,2317 }, {5763,5764,5799 ,4212,537,4064 }, + {2955,3750,592 ,3640,690,1863 }, {3850,3849,3806 ,4107,4130,4062 }, + {5877,5474,5489 ,4242,4243,4244 }, {5414,3673,4468 ,3764,648,647 }, + {7436,7464,7412 ,4245,4246,3765 }, {3338,1005,4207 ,4147,3535,4247 }, + {3852,3887,3886 ,4248,4056,4055 }, {3729,3772,3728 ,4249,4250,4251 }, + {3718,684,3348 ,3845,4101,2255 }, {1940,11,684 ,3925,299,4101 }, + {6257,5488,5473 ,3428,2730,3429 }, {7981,7980,7955 ,4252,4253,4254 }, + {6804,6854,6853 ,3342,3344,2505 }, {3756,3755,3708 ,4255,4025,3980 }, + {3656,3627,6534 ,4195,4256,4257 }, {3627,3656,3626 ,4256,4195,4196 }, + {5356,5663,5909 ,4258,4259,4260 }, {4928,3907,917 ,4261,3883,4016 }, + {1334,4236,5480 ,1935,3488,1739 }, {1851,1691,1733 ,4241,2326,2328 }, + {3373,3897,3349 ,693,4262,2054 }, {3751,3806,3729 ,4263,4062,4249 }, + {3806,3805,3729 ,4062,4264,4249 }, {3473,2153,4469 ,649,4181,4060 }, + {2843,3473,4469 ,4058,649,4060 }, {3324,3323,3304 ,881,1051,1050 }, + {976,7335,975 ,782,4265,4266 }, {3881,3920,3919 ,4154,4267,4268 }, + {3920,3963,3962 ,4267,4208,3977 }, {3919,3920,3962 ,4268,4267,3977 }, + {3963,4002,3962 ,4208,4188,3977 }, {2634,21,3759 ,4269,1231,4233 }, + {2643,2634,3759 ,4232,4269,4233 }, {3752,3751,3730 ,4063,4263,4270 }, + {3705,3752,3730 ,4144,4063,4270 }, {3919,3962,3918 ,4268,3977,2335 }, + {7720,7770,7747 ,4271,4272,331 }, {3547,6556,6598 ,803,4273,804 }, + {3372,3453,1908 ,2143,917,1058 }, {5923,6162,5591 ,2073,3598,3597 }, + {3837,1729,4286 ,3667,3666,4274 }, {3883,3885,3922 ,4149,4057,4068 }, + {3332,3331,3292 ,3891,1183,4067 }, {3714,3713,3655 ,3970,3935,3936 }, + {5870,3248,5939 ,4275,4216,4276 }, {3851,3852,3886 ,4108,4248,4055 }, + {3884,3885,3883 ,4131,4057,4149 }, {5283,5337,4830 ,570,572,3642 }, + {3316,3718,3348 ,2149,3845,2255 }, {3718,3841,684 ,3845,3927,4101 }, + {3841,1940,684 ,3927,3925,4101 }, {2634,3672,3987 ,4269,4277,1229 }, + {7930,7981,7955 ,4278,4252,4254 }, {6803,6804,6853 ,4169,3342,2505 }, + {3757,3758,3773 ,2281,1789,4279 }, {4105,3411,5392 ,4053,4280,4022 }, + {3398,3613,3455 ,4281,4173,886 }, {3478,3398,3455 ,885,4281,886 }, + {21,2634,3987 ,1231,4269,1229 }, {3300,3373,3349 ,694,693,2054 }, + {3848,3847,3805 ,4168,4282,4264 }, {3806,3848,3805 ,4062,4168,4264 }, + {7310,7344,7286 ,4283,2749,3460 }, {7698,7697,7648 ,333,651,3952 }, + {5411,5410,5376 ,4284,4285,2758 }, {3729,3728,3674 ,4249,4251,4286 }, + {3847,3846,3805 ,4282,4287,4264 }, {5307,5573,5572 ,461,2217,2284 }, + {6154,6141,3725 ,2387,2386,3971 }, {5306,5307,5572 ,462,461,2284 }, + {3881,3882,3920 ,4154,4288,4267 }, {3672,4423,3987 ,4277,4289,1229 }, + {3808,3806,3807 ,4013,4062,4061 }, {5239,5283,4830 ,482,570,3642 }, + {3252,794,2693 ,781,3392,3416 }, {3146,1909,3613 ,4290,4189,4173 }, + {3398,3146,3613 ,4281,4290,4173 }, {3735,1338,1909 ,4291,1077,4189 }, + {3146,3735,1909 ,4290,4291,4189 }, {3397,1257,1338 ,136,208,1077 }, + {3735,3397,1338 ,4291,136,1077 }, {3308,3332,3292 ,3894,3891,4067 }, + {3809,3852,3833 ,3903,4248,4049 }, {3852,3851,3833 ,4248,4108,4049 }, + {6279,7091,6280 ,3966,4153,4079 }, {5922,3445,5908 ,4292,1791,4293 }, + {3327,3404,1387 ,439,2170,2951 }, {6906,6905,6853 ,4294,2506,2505 }, + {6854,6906,6853 ,3344,4294,2505 }, {5194,4373,1102 ,727,3288,2317 }, + {1412,541,2813 ,3926,3811,3898 }, {3736,3447,2813 ,3807,3928,3898 }, + {3925,3924,3889 ,3227,3847,3126 }, {4423,160,4227 ,4289,4295,1564 }, + {3808,3807,3753 ,4013,4061,4014 }, {3883,3882,3847 ,4149,4288,4282 }, + {132,1518,2837 ,2503,3978,2511 }, {5296,5322,4023 ,4296,4297,3827 }, + {6283,7102,7088 ,4298,1240,4299 }, {3315,5194,1454 ,944,727,2318 }, + {3388,3387,3329 ,4115,1787,3972 }, {3675,3729,3674 ,4300,4249,4286 }, + {3729,3805,3772 ,4249,4264,4250 }, {3315,1454,3828 ,944,2318,1364 }, + {3987,4423,4227 ,1229,4289,1564 }, {7091,6279,7092 ,4153,3966,3968 }, + {160,5236,5237 ,4295,4301,1800 }, {4227,160,5237 ,1564,4295,1800 }, + {5263,5239,4830 ,4302,482,3642 }, {3711,3710,3653 ,1790,4236,1440 }, + {1670,3344,1588 ,2327,1861,3881 }, {541,3057,2813 ,3811,3810,3898 }, + {794,2494,2693 ,3392,3391,3416 }, {3764,1694,1412 ,3844,3828,3926 }, + {3309,3333,3334 ,3892,3893,399 }, {1606,1253,4055 ,3677,2353,3678 }, + {3178,3224,731 ,4138,3947,3965 }, {3967,3966,3923 ,3872,4109,3849 }, + {3344,2473,4221 ,1861,4303,1862 }, {5236,4834,5600 ,4301,4304,1801 }, + {3252,2693,2968 ,781,3416,263 }, {1556,3252,2968 ,3365,781,263 }, + {3864,1334,138 ,3517,1935,434 }, {1694,541,1412 ,3828,3811,3926 }, + {3404,3372,1387 ,2170,2143,2951 }, {882,1694,3396 ,3846,3828,3867 }, + {5237,5236,5600 ,1800,4301,1801 }, {4834,5787,2086 ,4304,4305,1802 }, + {7171,7208,7194 ,4306,1671,1670 }, {3556,3604,3554 ,3385,3384,3914 }, + {3521,3556,3503 ,4307,3385,3835 }, {3504,3521,3503 ,2986,4307,3835 }, + {3556,3555,3503 ,3385,4090,3835 }, {3542,3541,3487 ,979,981,3356 }, + {7495,7494,7462 ,4308,4309,4310 }, {4293,8065,3954 ,2732,96,3463 }, + {3969,3968,3924 ,3226,3848,3847 }, {3889,3924,3888 ,3126,3847,4204 }, + {3329,3328,3289 ,3972,3973,2056 }, {3848,3883,3847 ,4168,4149,4282 }, + {253,1158,966 ,2564,2744,2565 }, {5600,4834,2086 ,1801,4304,1802 }, + {2086,5787,377 ,1802,4305,4311 }, {7240,7287,7239 ,4312,3459,1527 }, + {3846,3881,3832 ,4287,4154,2322 }, {8120,2659,8098 ,906,3485,907 }, + {959,2044,501 ,1358,4031,4313 }, {3719,959,501 ,1356,1358,4313 }, + {6958,6957,6905 ,4314,4315,2506 }, {6276,7094,7093 ,4316,4317,4318 }, + {3882,3881,3846 ,4288,4154,4287 }, {3752,3806,3751 ,4063,4062,4263 }, + {141,4298,5832 ,681,3580,3904 }, {3925,3969,3924 ,3227,3226,3847 }, + {5181,5180,4631 ,4319,1878,1816 }, {2473,2955,4221 ,4303,3640,1862 }, + {3965,4005,3964 ,4069,3906,3964 }, {2422,2912,731 ,3907,3934,3965 }, + {3457,918,530 ,4099,4150,512 }, {75,2938,635 ,3962,4320,2183 }, + {3040,3457,530 ,619,4099,512 }, {3711,3757,3710 ,1790,2281,4236 }, + {6261,4044,4570 ,4321,4322,4082 }, {3057,3736,2813 ,3810,3807,3898 }, + {1694,3764,3396 ,3828,3844,3867 }, {1159,3972,4185 ,3760,3487,409 }, + {3274,3001,3819 ,3172,3210,3763 }, {5289,5288,5266 ,80,3993,81 }, + {3810,3811,3853 ,3901,4323,3902 }, {3889,3888,3853 ,3126,4204,3902 }, + {75,268,107 ,3962,645,2548 }, {6906,6958,6905 ,4294,4314,2506 }, + {1256,1518,132 ,3945,3978,2503 }, {2087,1256,132 ,2465,3945,2503 }, + {3846,3832,3804 ,4287,2322,4324 }, {3897,1804,3349 ,4262,2902,2054 }, + {5180,5242,5220 ,1878,3843,2119 }, {3800,1524,4697 ,4325,4326,4327 }, + {3676,3675,3623 ,4328,4300,4206 }, {6680,5471,6647 ,3834,2051,2050 }, + {3847,3882,3846 ,4282,4288,4287 }, {3650,3676,3623 ,4008,4328,4206 }, + {3964,3963,3920 ,3964,4208,4267 }, {3651,3706,3650 ,3888,4143,4008 }, + {3748,5385,5380 ,4034,3865,653 }, {5022,4669,1196 ,4329,3879,4330 }, + {3854,3889,3853 ,1512,3126,3902 }, {6116,7095,7094 ,4170,3924,4317 }, + {1159,4185,3673 ,3760,409,648 }, {7225,7246,7267 ,1672,3772,4331 }, + {7752,7751,7701 ,3514,4332,3515 }, {4797,5259,5258 ,3984,3794,1193 }, + {834,2856,1588 ,3876,3821,3881 }, {3909,834,3257 ,4164,3876,4010 }, + {5855,2856,1974 ,4011,3821,3862 }, {7312,7311,7262 ,4333,4334,4335 }, + {7797,7823,7796 ,3506,345,3803 }, {7825,7824,7771 ,3768,3770,4336 }, + {5286,5339,5285 ,3859,2223,2169 }, {107,268,751 ,2548,645,1365 }, + {7096,5473,3518 ,4337,3429,4338 }, {4369,4393,5335 ,104,463,2523 }, + {1339,3398,3478 ,4339,4281,885 }, {3449,1339,3478 ,2440,4339,885 }, + {3811,3810,3756 ,4323,3901,4255 }, {3757,3811,3756 ,2281,4323,4255 }, + {3757,3773,3811 ,2281,4279,4323 }, {3812,3854,3811 ,1513,1512,4323 }, + {407,3477,3147 ,4018,1736,2285 }, {1339,3564,3146 ,4339,4340,4290 }, + {3398,1339,3146 ,4281,4339,4290 }, {1334,5480,3218 ,1935,1739,3976 }, + {1670,1691,3344 ,2327,2326,1861 }, {1691,2473,3344 ,2326,4303,1861 }, + {3710,3709,3678 ,4236,4120,3948 }, {3653,3710,3678 ,1440,4236,3948 }, + {3962,4002,3961 ,3977,4188,2346 }, {3602,3601,3553 ,3889,4007,3957 }, + {4002,4001,3961 ,4188,2380,2346 }, {731,1003,4003 ,3965,4187,4186 }, + {3805,3846,3804 ,4264,4287,4324 }, {2219,5231,5000 ,177,115,286 }, + {8064,3975,3008 ,3141,728,546 }, {4078,1004,1919 ,4341,3452,1779 }, + {3368,3577,3369 ,3960,4059,3836 }, {3542,3591,3573 ,979,1652,980 }, + {7424,7423,7371 ,1188,3749,3748 }, {3038,1783,1341 ,4342,2251,3841 }, + {5288,4868,5287 ,3993,3969,4003 }, {4487,694,1607 ,4006,1987,3941 }, + {3969,4010,3968 ,3226,4343,3848 }, {3564,3782,3735 ,4340,4344,4291 }, + {3146,3564,3735 ,4290,4340,4291 }, {3370,3397,3735 ,2303,136,4291 }, + {3782,3370,3735 ,4344,2303,4291 }, {1454,694,1584 ,2318,1987,4012 }, + {4710,5218,4770 ,4136,483,4345 }, {1443,3274,1152 ,182,3172,4029 }, + {4236,4527,5480 ,3488,1740,1739 }, {4527,3147,4162 ,1740,2285,1738 }, + {4006,4005,3965 ,4110,3906,4069 }, {5572,5573,407 ,2284,2217,4018 }, + {1256,2087,3004 ,3945,2465,2437 }, {1309,3935,685 ,3709,4346,2674 }, + {1483,1539,87 ,4084,835,641 }, {3871,3748,5850 ,397,4034,394 }, + {3329,3387,3362 ,3972,1787,1200 }, {3566,2557,3052 ,1146,3620,3715 }, + {4653,4654,4679 ,4347,4348,3852 }, {6116,6276,6286 ,4170,4316,4349 }, + {6853,6892,5626 ,2505,2507,1460 }, {147,4712,4027 ,3469,3816,3470 }, + {3243,1099,1111 ,3919,4350,3920 }, {3187,2698,4236 ,3402,105,3488 }, + {5218,5238,4770 ,483,4351,4345 }, {3748,3749,5850 ,4034,652,394 }, + {2713,4091,3259 ,4352,123,407 }, {3304,3303,7333 ,1050,1049,4353 }, + {7948,7947,7896 ,3625,4354,4355 }, {3446,3472,5608 ,3718,3155,4356 }, + {6276,6116,7094 ,4316,4170,4317 }, {3309,7110,7109 ,3892,1990,4112 }, + {3707,3706,3651 ,3981,4143,3888 }, {3553,3552,3519 ,3957,4167,4357 }, + {3520,3553,3519 ,4358,3957,4357 }, {4004,731,4003 ,3908,3965,4186 }, + {3773,3758,3812 ,4279,1789,1513 }, {1003,2910,393 ,4187,3946,2400 }, + {3881,3880,3832 ,4154,2320,2322 }, {3271,3396,3316 ,1727,3867,2149 }, + {3289,3328,3307 ,2056,3973,2057 }, {4022,8111,5353 ,3503,4359,851 }, + {1630,924,56 ,1016,3634,2606 }, {3451,3537,3516 ,1720,1735,1721 }, + {4600,4653,4599 ,4360,4347,4210 }, {3566,3052,2397 ,1146,3715,3669 }, + {5385,4157,5304 ,3865,3755,654 }, {3883,3921,3920 ,4149,4070,4267 }, + {3753,3752,3706 ,4014,4063,4143 }, {7983,8095,8105 ,4361,4362,3791 }, + {4002,1003,4001 ,4188,4187,2380 }, {2544,2622,601 ,2451,2840,3905 }, + {149,454,2471 ,3736,4363,3653 }, {3456,2920,2557 ,1145,1799,3620 }, + {4165,1483,87 ,640,4084,641 }, {3502,3553,3520 ,3958,3957,4358 }, + {3331,3390,3364 ,1183,3509,1184 }, {2043,143,918 ,4100,157,4150 }, + {4056,2421,4161 ,1123,2568,4364 }, {3457,2043,918 ,4099,4100,4150 }, + {5833,141,5832 ,544,681,3904 }, {3757,3756,3710 ,2281,4255,4236 }, + {6186,3518,5488 ,4365,4338,2730 }, {7203,7240,7220 ,4366,4312,4367 }, + {3130,3086,3087 ,3414,3413,3573 }, {6997,6996,6957 ,4368,4369,4315 }, + {507,5309,505 ,1523,987,986 }, {3812,3758,3813 ,1513,1789,1515 }, + {115,3081,3241 ,2901,1017,3504 }, {3096,115,3241 ,4234,2901,3504 }, + {1657,1552,3156 ,1781,2266,3164 }, {923,924,1630 ,4370,3634,1016 }, + {1663,923,1630 ,1015,4370,1016 }, {923,3640,924 ,4370,4371,3634 }, + {149,219,924 ,3736,3087,3634 }, {3640,149,924 ,4371,3736,3634 }, + {1383,1296,788 ,336,335,976 }, {53,2346,2573 ,4372,3252,3253 }, + {3882,3883,3920 ,4288,4149,4267 }, {6958,6997,6957 ,4314,4368,4315 }, + {7397,7371,7372 ,1186,3748,4373 }, {3513,3404,3327 ,441,2170,439 }, + {268,1664,751 ,645,2268,1365 }, {3811,3854,3853 ,4323,1512,3902 }, + {2651,2397,2381 ,1516,3669,3672 }, {6997,5752,6996 ,4368,4374,4369 }, + {3112,3993,3132 ,2795,2807,2796 }, {6285,7109,7095 ,3923,4112,3924 }, + {3170,1160,3211 ,3510,3661,3605 }, {869,2681,2346 ,4375,3254,3252 }, + {53,869,2346 ,4372,4375,3252 }, {3450,2464,2681 ,3330,3299,3254 }, + {869,3450,2681 ,4375,3330,3254 }, {7039,7038,5752 ,4376,3832,4374 }, + {7109,6285,3309 ,4112,3923,3892 }, {3651,3650,3624 ,3888,4008,4009 }, + {3749,3748,5380 ,652,4034,653 }, {3373,1804,3897 ,693,2902,4262 }, + {6276,6287,6286 ,4316,4377,4349 }, {4770,5238,5263 ,4345,4351,4302 }, + {3009,5701,5683 ,3747,3938,3939 }, {2624,3405,1153 ,3132,1798,2245 }, + {1794,3616,2795 ,4129,3974,3975 }, {3377,3378,3427 ,1443,1442,4378 }, + {6509,8092,8074 ,1452,2267,2460 }, {4032,3170,3129 ,3638,3510,3412 }, + {2829,1107,1106 ,2620,2656,1664 }, {6997,7039,5752 ,4368,4376,4374 }, + {1066,2558,105 ,680,977,249 }, {2850,2833,2803 ,3657,2065,3655 }, + {2910,3004,393 ,3946,2437,2400 }, {5238,5239,5263 ,4351,482,4302 }, + {5198,5218,4710 ,423,483,4136 }, {3486,3487,3540 ,4379,3356,3357 }, + {6390,2256,5950 ,3868,2323,2873 }, {538,53,3569 ,4380,4372,4381 }, + {3468,3467,3439 ,2688,3992,2689 }, {1387,1908,3636 ,2951,1058,1060 }, + {1378,1339,3449 ,925,4339,2440 }, {3334,3333,3309 ,399,3893,3892 }, + {3502,3520,3501 ,3958,4358,4176 }, {3921,3964,3920 ,4070,3964,4267 }, + {3706,3705,3650 ,4143,4144,4008 }, {3501,3519,3500 ,4176,4357,4177 }, + {3519,3552,3500 ,4357,4167,4177 }, {3552,3551,3500 ,4167,4180,4177 }, + {3602,3625,3601 ,3889,4382,4007 }, {3625,3651,3601 ,4382,3888,4007 }, + {1693,2693,1249 ,264,3416,2876 }, {1524,1907,3818 ,4326,1245,1244 }, + {1109,1160,4032 ,4383,3661,3638 }, {1109,3096,1160 ,4383,4234,3661 }, + {149,3640,923 ,3736,4371,4370 }, {2218,409,454 ,4384,1480,4363 }, + {1303,687,831 ,1933,1665,3334 }, {3428,3459,3458 ,3261,3241,4385 }, + {3378,3428,3427 ,1442,3261,4378 }, {88,3637,1453 ,3581,4386,3355 }, + {3636,88,1453 ,1060,3581,3355 }, {3378,3428,3406 ,1442,3261,399 }, + {6277,6287,6276 ,4387,4377,4316 }, {3266,4775,495 ,3884,4388,4389 }, + {3913,2698,4248 ,841,105,3256 }, {6133,3518,7084 ,4390,4338,4391 }, + {1487,3194,2150 ,531,1065,534 }, {3571,3572,3589 ,3652,1931,1932 }, + {3531,53,538 ,4392,4372,4380 }, {5936,6143,6125 ,4393,4394,4395 }, + {382,3732,3318 ,3534,3536,4036 }, {4630,4710,4709 ,827,4136,4396 }, + {5815,5816,5832 ,4065,545,3904 }, {3439,3467,3438 ,2689,3992,3912 }, + {3502,3501,3466 ,3958,4176,4175 }, {3650,3705,3676 ,4008,4144,4328 }, + {3705,3730,3676 ,4144,4270,4328 }, {3730,3675,3676 ,4270,4300,4328 }, + {3730,3751,3675 ,4270,4263,4300 }, {3751,3729,3675 ,4263,4249,4300 }, + {4004,4003,3963 ,3908,4186,4208 }, {3964,4004,3963 ,3964,3908,4208 }, + {3651,3624,3601 ,3888,4009,4007 }, {3602,3651,3625 ,3889,3888,4382 }, + {6186,7084,3518 ,4365,4391,4338 }, {1334,3218,3748 ,1935,3976,4034 }, + {1003,393,4001 ,4187,2400,2380 }, {3321,3322,3354 ,2461,1182,1293 }, + {3853,3888,3852 ,3902,4204,4248 }, {3756,3810,3755 ,4255,3901,4025 }, + {3772,5882,3728 ,4250,4397,4251 }, {3131,4033,4032 ,3732,4398,3638 }, + {3130,3131,4032 ,3414,3732,3638 }, {225,1109,4032 ,4399,4383,3638 }, + {4033,225,4032 ,4398,4399,3638 }, {3901,3096,1109 ,4400,4234,4383 }, + {225,3901,1109 ,4399,4400,4383 }, {3901,3949,3096 ,4400,4401,4234 }, + {2010,115,3096 ,4402,2901,4234 }, {3949,2010,3096 ,4401,4402,4234 }, + {2415,1663,115 ,4403,1015,2901 }, {2010,2415,115 ,4402,4403,2901 }, + {1301,923,1663 ,4404,4370,1015 }, {2415,1301,1663 ,4403,4404,1015 }, + {182,149,923 ,4405,3736,4370 }, {1301,182,923 ,4404,4405,4370 }, + {182,230,149 ,4405,4406,3736 }, {12,454,149 ,4407,4363,3736 }, + {230,12,149 ,4406,4407,3736 }, {130,2218,454 ,2341,4384,4363 }, + {12,130,454 ,4407,2341,4363 }, {150,409,2218 ,1481,1480,4384 }, + {6509,8074,5013 ,1452,2460,684 }, {2218,130,150 ,4384,2341,1481 }, + {3427,3428,3458 ,4378,3261,4385 }, {3459,3487,3486 ,3241,3356,4379 }, + {3958,3998,3957 ,294,381,295 }, {1946,131,4384 ,4408,2965,4409 }, + {2522,549,2451 ,2678,1129,2102 }, {495,3788,1341 ,4389,4410,3841 }, + {7777,7776,7730 ,4411,4412,4413 }, {1378,3687,3564 ,925,924,4340 }, + {1339,1378,3564 ,4339,925,4340 }, {3382,3407,3381 ,870,3012,871 }, + {3050,538,3532 ,4414,4380,4415 }, {3479,3531,538 ,4416,4392,4380 }, + {3050,3479,538 ,4414,4416,4380 }, {3479,53,3531 ,4416,4372,4392 }, + {295,869,53 ,4417,4375,4372 }, {3479,295,53 ,4416,4417,4372 }, + {295,2645,3450 ,4417,4418,3330 }, {869,295,3450 ,4375,4417,3330 }, + {3535,840,3450 ,442,68,3330 }, {2645,3535,3450 ,4418,442,3330 }, + {3687,3873,3782 ,924,1809,4344 }, {3488,3542,3487 ,1980,979,3356 }, + {3888,3887,3852 ,4204,4056,4248 }, {3853,3852,3809 ,3902,4248,3903 }, + {3707,3754,3706 ,3981,4071,4143 }, {3754,3753,3706 ,4071,4014,4143 }, + {3520,3519,3501 ,4358,4357,4176 }, {3467,3502,3466 ,3992,3958,4175 }, + {731,3224,1003 ,3965,3947,4187 }, {3224,2910,1003 ,3947,3946,4187 }, + {7691,450,449 ,4419,1653,1169 }, {3390,3439,3389 ,3509,2689,3913 }, + {3467,3466,3438 ,3992,4175,3912 }, {3738,1940,3841 ,3900,3925,3927 }, + {3710,3756,3708 ,4236,4255,3980 }, {3564,3687,3782 ,4340,924,4344 }, + {3873,3475,3370 ,1809,1559,2303 }, {3782,3873,3370 ,4344,1809,2303 }, + {4162,4129,5805 ,1738,3812,3999 }, {7075,7074,7038 ,4420,2747,3832 }, + {498,2010,3949 ,4421,4402,4401 }, {3901,498,3949 ,4400,4421,4401 }, + {2088,55,1155 ,1667,3507,3333 }, {3458,3459,3486 ,4385,3241,4379 }, + {1096,1975,3637 ,2727,4422,4386 }, {88,1096,3637 ,3581,2727,4386 }, + {7039,7075,7038 ,4376,4420,3832 }, {4600,4653,4679 ,4360,4347,3852 }, + {7451,7479,7423 ,3963,4423,3749 }, {630,879,3370 ,2491,1155,2303 }, + {3475,630,3370 ,1559,2491,2303 }, {543,495,1341 ,3840,4389,3841 }, + {7983,4744,5026 ,4361,4424,4225 }, {3617,3532,3375 ,4425,4415,4426 }, + {1208,1586,689 ,3211,943,1363 }, {2527,2486,2487 ,2637,2814,4427 }, + {2831,2832,2868 ,639,638,1577 }, {5845,6174,5113 ,143,2381,141 }, + {3604,3653,3603 ,3384,1440,3890 }, {3810,3809,3755 ,3901,3903,4025 }, + {1975,1096,784 ,4422,2727,4211 }, {498,2415,2010 ,4421,4403,4402 }, + {3696,3742,3741 ,2325,3419,3447 }, {3695,3696,3741 ,4428,2325,3447 }, + {300,183,499 ,3508,4429,4430 }, {3091,1390,300 ,3262,2964,3508 }, + {4617,4924,7074 ,4431,2745,2747 }, {7075,4617,7074 ,4420,4431,2747 }, + {5337,4866,5354 ,572,3641,3882 }, {1922,3244,4397 ,4432,4433,4434 }, + {2407,2258,6484 ,4001,1198,685 }, {3924,3923,3887 ,3847,3849,4056 }, + {6316,2162,6390 ,4435,3278,3868 }, {1299,295,3479 ,4436,4417,4416 }, + {295,3041,2645 ,4417,3182,4418 }, {3902,5970,4104 ,4437,3518,3519 }, + {2314,2223,2256 ,3742,2336,2323 }, {3426,3485,2615 ,4438,3524,2788 }, + {2566,3426,2615 ,2635,4438,2788 }, {2615,3485,2646 ,2788,3524,3070 }, + {2646,3485,2686 ,3070,3524,3071 }, {3539,3588,2740 ,3525,4439,3456 }, + {2686,3539,2740 ,3071,3525,3456 }, {2740,3588,3619 ,3456,4439,3956 }, + {3588,3641,2790 ,4439,4440,3576 }, {3619,3588,2790 ,3956,4439,3576 }, + {3641,3694,2832 ,4440,3915,638 }, {2790,3641,2832 ,3576,4440,638 }, + {3393,3392,3333 ,4441,3860,3893 }, {6467,6484,2223 ,4000,685,2336 }, + {3365,3333,3309 ,4240,3893,3892 }, {3132,4034,4033 ,2796,4442,4398 }, + {3131,3132,4033 ,3732,2796,4398 }, {4034,97,225 ,4442,4443,4399 }, + {4033,4034,225 ,4398,4442,4399 }, {1110,3901,225 ,4444,4400,4399 }, + {97,1110,225 ,4443,4444,4399 }, {585,498,3901 ,4445,4421,4400 }, + {1110,585,3901 ,4444,4445,4400 }, {919,2415,498 ,4446,4403,4421 }, + {585,919,498 ,4445,4446,4421 }, {54,1301,2415 ,4447,4404,4403 }, + {919,54,2415 ,4446,4447,4403 }, {224,182,1301 ,4448,4405,4404 }, + {54,224,1301 ,4447,4448,4404 }, {3056,230,182 ,4449,4406,4405 }, + {224,3056,182 ,4448,4449,4405 }, {400,12,230 ,4450,4407,4406 }, + {3056,400,230 ,4449,4450,4406 }, {95,130,12 ,3482,2341,4407 }, + {400,95,12 ,4450,3482,4407 }, {1798,89,130 ,4451,1482,2341 }, + {1155,300,499 ,3333,3508,4430 }, {1096,2749,784 ,2727,3286,4211 }, + {784,2749,2428 ,4211,3286,371 }, {1155,499,957 ,3333,4430,4452 }, + {1626,831,3253 ,564,3334,565 }, {3184,2618,3895 ,4453,4454,4455 }, + {831,1155,957 ,3334,3333,4452 }, {1004,4078,31 ,3452,4341,3450 }, + {8108,8066,8128 ,2287,960,959 }, {3770,3050,3617 ,4456,4414,4425 }, + {3346,3770,3617 ,4457,4456,4425 }, {3424,3479,3050 ,4458,4416,4414 }, + {3770,3424,3050 ,4456,4458,4414 }, {3635,1299,3479 ,4459,4436,4416 }, + {3424,3635,3479 ,4458,4459,4416 }, {3635,295,1299 ,4459,4417,4436 }, + {3635,1860,3041 ,4459,3183,3182 }, {295,3635,3041 ,4417,4459,3182 }, + {3618,1860,4204 ,37,3183,38 }, {832,3094,1059 ,613,634,678 }, + {3955,3995,3994 ,3538,3540,2806 }, {4617,1166,4924 ,4431,2746,2745 }, + {3365,3393,3333 ,4240,4441,3893 }, {2471,454,2536 ,3653,4363,2793 }, + {55,652,1383 ,3507,334,336 }, {1296,2558,788 ,335,977,976 }, + {3442,3441,3413 ,4460,2987,3814 }, {3393,3413,3392 ,4441,3814,3860 }, + {3342,2364,177 ,2514,2513,3618 }, {1558,919,585 ,4461,4446,4445 }, + {3643,3696,3695 ,2344,2325,4428 }, {3642,3643,3695 ,4462,2344,4428 }, + {3378,3406,3428 ,1442,399,3261 }, {6260,6053,5320 ,3397,4463,4464 }, + {7241,7240,7203 ,4465,4312,4366 }, {6996,5752,6957 ,4369,4374,4315 }, + {3335,3365,3309 ,4222,4240,3892 }, {2536,454,409 ,2793,4363,1480 }, + {3346,3375,4098 ,4457,4426,353 }, {1303,831,1626 ,1933,3334,564 }, + {2743,3944,3931 ,923,729,1868 }, {53,2573,3569 ,4372,3253,4381 }, + {3895,2618,3376 ,4455,4454,4466 }, {3539,3570,3588 ,3525,4467,4439 }, + {730,960,1985 ,1549,1326,1547 }, {1378,3449,8135 ,925,2440,2439 }, + {3420,3299,3474 ,3785,3232,3808 }, {58,2013,1332 ,2156,2242,3492 }, + {224,400,3056 ,4448,4450,4449 }, {8,4522,220 ,3341,3481,3340 }, + {1390,151,183 ,2964,2966,4429 }, {300,1390,183 ,3508,2964,4429 }, + {8131,7714,7983 ,2307,4468,4361 }, {1383,3091,300 ,336,3262,3508 }, + {55,1383,300 ,3507,336,3508 }, {3390,3389,3363 ,3509,3913,1911 }, + {3119,2681,2747 ,3223,3254,3224 }, {3805,3804,3772 ,4264,4324,4250 }, + {831,957,3253 ,3334,4452,565 }, {3299,637,3474 ,3232,3805,3808 }, + {366,1202,1204 ,4469,339,338 }, {3826,3770,3346 ,4470,4456,4457 }, + {784,4204,1860 ,4211,38,3183 }, {912,309,1166 ,4471,4472,2746 }, + {2162,2314,2256 ,3278,3742,2323 }, {3211,1160,3241 ,3605,3661,3504 }, + {4617,912,1166 ,4431,4471,2746 }, {79,3421,3278 ,1510,1156,1158 }, + {1537,4191,3802 ,4473,4474,4475 }, {3377,3427,3426 ,1443,4378,4438 }, + {2487,3377,3426 ,4427,1443,4438 }, {3427,3486,3485 ,4378,4379,3524 }, + {3426,3427,3485 ,4438,4378,3524 }, {3486,3540,3539 ,4379,3357,3525 }, + {3485,3486,3539 ,3524,4379,3525 }, {3540,3571,3570 ,3357,3652,4467 }, + {3539,3540,3570 ,3525,3357,4467 }, {3571,3589,3588 ,3652,1932,4439 }, + {3570,3571,3588 ,4467,3652,4439 }, {3589,3642,3641 ,1932,4462,4440 }, + {3588,3589,3641 ,4439,1932,4440 }, {3642,3695,3694 ,4462,4428,3915 }, + {3641,3642,3694 ,4440,4462,3915 }, {3695,3741,2907 ,4428,3447,111 }, + {3694,3695,2907 ,3915,4428,111 }, {3792,3791,2907 ,3421,3739,111 }, + {3741,3792,2907 ,3447,3421,111 }, {3881,3919,3918 ,4154,4268,2335 }, + {3420,3474,3685 ,3785,3808,3809 }, {3312,3420,3685 ,3782,3785,3809 }, + {4035,4034,3132 ,1601,4442,2796 }, {3993,4035,3132 ,2807,1601,2796 }, + {4035,2151,97 ,1601,1603,4443 }, {4034,4035,97 ,4442,1601,4443 }, + {2151,349,1110 ,1603,566,4444 }, {97,2151,1110 ,4443,1603,4444 }, + {3253,585,1110 ,565,4445,4444 }, {349,3253,1110 ,566,565,4444 }, + {957,1558,585 ,4452,4461,4445 }, {3253,957,585 ,565,4452,4445 }, + {2071,919,1558 ,4476,4446,4461 }, {957,2071,1558 ,4452,4476,4461 }, + {499,54,919 ,4430,4447,4446 }, {2071,499,919 ,4476,4430,4446 }, + {183,224,54 ,4429,4448,4447 }, {499,183,54 ,4430,4429,4447 }, + {151,400,224 ,2966,4450,4448 }, {183,151,224 ,4429,2966,4448 }, + {220,95,400 ,3340,3482,4450 }, {151,220,400 ,2966,3340,4450 }, + {3590,3643,3642 ,1867,2344,4462 }, {3589,3590,3642 ,1932,1867,4462 }, + {4037,4036,3994 ,3541,1602,2806 }, {958,3312,3685 ,3779,3782,3809 }, + {3393,3442,3413 ,4441,4460,3814 }, {1068,958,3685 ,3761,3779,3809 }, + {939,3346,4350 ,3354,4457,740 }, {1453,3826,3346 ,3355,4470,4457 }, + {939,1453,3346 ,3354,3355,4457 }, {3992,3770,3826 ,4477,4456,4470 }, + {1453,3992,3826 ,3355,4477,4470 }, {3637,3424,3770 ,4386,4458,4456 }, + {3992,3637,3770 ,4477,4386,4456 }, {1975,3635,3424 ,4422,4459,4458 }, + {3637,1975,3424 ,4386,4422,4458 }, {1975,784,1860 ,4422,4211,3183 }, + {3635,1975,1860 ,4459,4422,3183 }, {2829,1106,1062 ,2620,1664,1232 }, + {4031,2585,2474 ,3069,875,874 }, {8007,2287,2250 ,3854,3735,3716 }, + {8077,8113,3421 ,2078,1157,1156 }, {8096,5057,6796 ,1796,1795,2617 }, + {957,499,2071 ,4452,4430,4476 }, {131,220,151 ,2965,3340,2966 }, + {912,2240,2375 ,4471,4478,4479 }, {254,2472,2039 ,2008,2009,1655 }, + {3995,4037,3994 ,3540,3541,2806 }, {3278,1724,2851 ,1158,3880,3051 }, + {1668,1208,689 ,3212,3211,1363 }, {872,958,1068 ,2962,3779,3761 }, + {836,872,1068 ,3551,2962,3761 }, {1453,3637,3992 ,3355,4386,4477 }, + {3211,3169,3170 ,3605,2810,3510 }, {309,912,2375 ,4472,4471,4479 }, + {3427,3458,3486 ,4378,4385,4379 }, {1564,1922,1085 ,368,4432,369 }, + {1085,1859,703 ,369,4480,100 }, {1859,1625,703 ,4480,388,100 }, + {1859,4222,1625 ,4480,530,388 }, {4181,23,4100 ,2273,2272,4481 }, + {1933,4181,1695 ,470,2273,471 }, {4131,1087,2026 ,1656,4482,130 }, + {2026,1087,870 ,130,4482,2274 }, {1063,1092,1366 ,4483,4484,4485 }, + {1092,1424,1366 ,4484,2298,4485 }, {5728,5766,5727 ,4486,4487,514 }, + {4181,4100,1695 ,2273,4481,471 }, {245,1458,1148 ,4488,4489,160 }, + {1148,1458,1925 ,160,4489,161 }, {4061,4064,4060 ,4490,1880,2099 }, + {5766,5765,5727 ,4487,540,514 }, {4107,1932,770 ,4491,2141,275 }, + {1396,4107,770 ,34,4491,275 }, {4262,2,702 ,4492,4493,2699 }, + {4090,4148,4108 ,1487,2305,2304 }, {416,2091,604 ,556,4494,486 }, + {4090,4108,4089 ,1487,2304,4495 }, {2,265,443 ,4493,4496,920 }, + {1932,1792,2352 ,2141,324,390 }, {4090,4089,4070 ,1487,4495,4497 }, + {1600,311,209 ,4498,246,4499 }, {4257,4276,4275 ,698,1446,699 }, + {461,2,443 ,399,4493,920 }, {1792,955,1375 ,324,3991,325 }, {4109,4126,4175 ,576,124,4500 }, + {5700,5728,5727 ,4501,4486,514 }, {4061,4060,3347 ,4490,2099,2098 }, + {494,787,175 ,3041,209,211 }, {1872,1836,1808 ,2459,2490,4502 }, + {443,697,59 ,920,3578,967 }, {493,494,175 ,1785,3041,211 }, {8119,5261,8116 ,184,173,172 }, + {4175,3784,4218 ,4500,2252,4503 }, {5699,5700,5727 ,43,4501,514 }, + {6311,6390,8114 ,1097,3868,2286 }, {121,4155,1730 ,4504,4505,4506 }, + {665,761,760 ,1754,1826,3106 }, {1310,229,737 ,149,221,150 }, + {3867,1101,1851 ,4507,2,4241 }, {251,491,1517 ,603,487,600 }, + {3794,3795,3796 ,3449,3448,2969 }, {2423,1225,2428 ,433,327,371 }, + {2427,2254,3117 ,3237,3705,3407 }, {4357,4405,2941 ,4508,4509,109 }, + {3792,4357,2941 ,3421,4508,109 }, {4405,2940,2941 ,4509,110,109 }, + {4499,1205,2940 ,4510,3187,110 }, {5282,4827,5334 ,1194,2734,2736 }, + {2310,2504,4146 ,2442,2898,3730 }, {3794,3796,4259 ,3449,2969,4511 }, + {3794,4305,3793 ,3449,4512,3420 }, {3793,4357,3792 ,3420,4508,3421 }, + {4474,2940,4405 ,4513,110,4509 }, {4474,4499,2940 ,4513,4510,110 }, + {4474,1205,4499 ,4513,3187,4510 }, {596,548,549 ,1462,938,1129 }, + {23,1441,1063 ,2272,1485,4483 }, {4070,4069,4064 ,4497,1881,1880 }, + {4146,3844,2310 ,3730,2443,2442 }, {3585,4493,3447 ,3806,4514,3928 }, + {4533,64,3447 ,4515,297,3928 }, {4493,4533,3447 ,4514,4515,3928 }, + {4533,207,64 ,4515,4516,297 }, {4472,4491,1653 ,2189,107,106 }, + {237,236,99 ,869,2444,3743 }, {3794,4259,4305 ,3449,4511,4512 }, + {4434,4405,4357 ,4517,4509,4508 }, {4434,4474,4405 ,4517,4513,4509 }, + {4499,1205,4474 ,4510,3187,4513 }, {549,548,450 ,1129,938,1653 }, + {1425,121,1730 ,4518,4504,4506 }, {477,478,64 ,4519,2077,297 }, + {207,477,64 ,4516,4519,297 }, {1212,1178,4075 ,4520,206,4521 }, + {8109,5521,8110 ,2938,751,1140 }, {4357,4381,4434 ,4508,4522,4517 }, + {4474,4500,4499 ,4513,4523,4510 }, {1529,277,478 ,4524,85,2077 }, + {477,1529,478 ,4519,4524,2077 }, {1529,276,277 ,4524,4525,85 }, + {5641,5657,5640 ,510,48,2214 }, {4381,4357,4305 ,4522,4508,4512 }, + {4332,4381,4305 ,4526,4522,4512 }, {4434,4453,4474 ,4517,4527,4513 }, + {4453,4500,4474 ,4527,4523,4513 }, {4371,3864,3972 ,3489,3517,3487 }, + {276,463,277 ,4525,1941,85 }, {38,74,110 ,742,2053,743 }, {5680,5679,5640 ,50,2553,2214 }, + {8037,8050,4589 ,4528,4529,776 }, {3950,5352,3799 ,4530,3502,4531 }, + {4277,4259,4240 ,4532,4511,2060 }, {4332,4305,4259 ,4526,4512,4511 }, + {4277,4332,4259 ,4532,4526,4511 }, {4406,4381,4434 ,399,4522,4517 }, + {4381,4406,4434 ,4522,399,4517 }, {4381,4453,4434 ,4522,4527,4517 }, + {4500,366,1204 ,4523,4469,338 }, {4099,4327,561 ,4533,2377,2379 }, + {122,1467,755 ,4534,4535,1615 }, {4113,1178,1212 ,207,206,4520 }, + {504,4330,513 ,4536,399,377 }, {467,504,513 ,346,4536,377 }, + {4161,2421,2695 ,4364,2568,3453 }, {4259,3796,4240 ,4511,2969,2060 }, + {5641,5640,5599 ,510,2214,2213 }, {4260,4277,4240 ,3527,4532,2060 }, + {4358,4381,4332 ,4537,4522,4526 }, {4407,4406,4381 ,4538,399,4522 }, + {4358,4407,4381 ,4537,4538,4522 }, {4407,4435,4381 ,4538,4539,4522 }, + {4406,4407,4381 ,399,4538,4522 }, {4381,4435,4453 ,4522,4539,4527 }, + {4453,4501,4500 ,4527,4540,4523 }, {4501,4538,366 ,4540,4541,4469 }, + {4500,4501,366 ,4523,4540,4469 }, {342,343,1993 ,4542,4543,4544 }, + {4471,4472,1653 ,2187,2189,106 }, {513,1154,1343 ,377,4545,378 }, + {1343,1154,4399 ,378,4545,4546 }, {8111,4022,6836 ,4359,3503,852 }, + {2697,3977,3327 ,2006,440,439 }, {6892,6905,5626 ,2507,2506,1460 }, + {4359,4358,4332 ,4547,4537,4526 }, {4277,4359,4332 ,4532,4547,4526 }, + {4359,4407,4358 ,4547,4538,4537 }, {4453,4475,4501 ,4527,4548,4540 }, + {4538,1040,366 ,4541,1476,4469 }, {2240,3978,3945 ,4478,4549,4550 }, + {2313,321,1222 ,438,60,62 }, {378,1467,122 ,4551,4535,4534 }, + {4397,1859,1085 ,4434,4480,369 }, {8095,8090,8105 ,4362,412,3791 }, + {8131,7983,8105 ,2307,4361,3791 }, {2955,3761,3750 ,3640,218,690 }, + {5569,5570,5599 ,2075,508,2213 }, {4306,4359,4277 ,4552,4547,4532 }, + {4407,4408,4435 ,4538,4553,4539 }, {4435,4408,4453 ,4539,4553,4527 }, + {4453,4408,4475 ,4527,4553,4548 }, {4475,1904,4501 ,4548,2164,4540 }, + {1904,1906,4538 ,2164,1509,4541 }, {4501,1904,4538 ,4540,2164,4541 }, + {1906,1040,4538 ,1509,1476,4541 }, {1224,1201,1040 ,1508,1477,1476 }, + {887,4131,4222 ,4554,1656,530 }, {4156,887,4222 ,4555,4554,530 }, + {887,1087,4131 ,4554,4482,1656 }, {8113,8077,1724 ,1157,2078,3880 }, + {4360,4359,4306 ,4556,4547,4552 }, {4360,4382,4359 ,4556,4557,4547 }, + {4408,4407,4359 ,4553,4538,4547 }, {4382,4408,4359 ,4557,4553,4547 }, + {4476,1904,4475 ,4558,2164,4548 }, {1904,1224,1906 ,2164,1508,1509 }, + {649,492,175 ,1579,1580,211 }, {365,4521,606 ,402,179,180 }, + {1424,1528,1807 ,2298,4559,4560 }, {1807,1528,608 ,4560,4559,2300 }, + {1366,1424,1807 ,4485,2298,4560 }, {1087,1531,870 ,4482,4561,2274 }, + {1531,4107,1396 ,4561,4491,34 }, {3761,1463,3750 ,218,220,690 }, + {4307,4360,4306 ,4562,4556,4552 }, {4307,4382,4360 ,4562,4557,4556 }, + {4476,4475,4408 ,4558,4548,4553 }, {3977,2697,66 ,440,2006,4563 }, + {1222,4521,365 ,62,179,402 }, {4515,1651,1609 ,30,188,31 }, {260,4311,4312 ,1573,468,467 }, + {870,1531,1396 ,2274,4561,34 }, {1284,23,1063 ,4564,2272,4483 }, + {1509,377,1792 ,4565,4311,324 }, {1932,1509,1792 ,2141,4565,324 }, + {5223,5222,5182 ,3851,4566,4567 }, {1268,5301,2238 ,4568,2698,2697 }, + {1691,1101,2473 ,2326,2,4303 }, {6735,6742,3192 ,3472,3422,3063 }, + {489,2473,1101 ,3639,4303,2 }, {1868,1783,2217 ,2249,2251,4569 }, + {1783,3038,2217 ,2251,4342,4569 }, {4409,4436,4408 ,4570,4571,4553 }, + {4382,4409,4408 ,4557,4570,4553 }, {4436,4437,4408 ,4571,4572,4553 }, + {4437,3049,4476 ,4572,2803,4558 }, {4408,4437,4476 ,4553,4572,4558 }, + {4476,3049,1904 ,4558,2803,2164 }, {5834,5835,1942 ,543,4573,2337 }, + {4280,4159,413 ,1242,1299,351 }, {1837,363,380 ,997,427,2540 }, + {102,4312,4313 ,432,467,4574 }, {1079,987,1832 ,4575,2950,4576 }, + {4103,1731,604 ,4577,485,486 }, {4104,2504,3902 ,3519,2898,4437 }, + {23,1284,4100 ,2272,4564,4481 }, {2588,14,91 ,2072,1986,1984 }, + {1792,377,955 ,324,4311,3991 }, {3784,4239,4217 ,2252,2254,83 }, + {465,604,4304 ,4578,486,4579 }, {4888,4932,5379 ,3997,4580,3998 }, + {4653,4679,5182 ,4347,3852,4567 }, {8000,8023,7999 ,4581,4582,4583 }, + {4361,3938,4382 ,4584,3572,4557 }, {4307,4361,4382 ,4562,4584,4557 }, + {4382,3938,4409 ,4557,3572,4570 }, {3938,4437,4436 ,3572,4572,4571 }, + {4409,3938,4436 ,4570,3572,4571 }, {659,1730,802 ,71,4506,400 }, + {6949,6989,6947 ,4585,4586,4587 }, {2834,4961,2403 ,2860,2905,2904 }, + {1051,6,1858 ,4588,4589,4590 }, {1305,2892,3763 ,466,918,3753 }, + {1940,64,11 ,3925,297,299 }, {4356,4433,4432 ,155,4591,156 }, + {2504,2583,3902 ,2898,2672,4437 }, {4383,4311,490 ,399,468,469 }, + {210,1695,194 ,4592,471,4593 }, {4218,3784,4217 ,4503,2252,83 }, + {1695,4100,1933 ,471,4481,470 }, {4890,4918,4933 ,4594,4595,4596 }, + {3088,3132,3131 ,3290,2796,3732 }, {1091,5236,160 ,4597,4301,4295 }, + {1101,1691,1851 ,2,2326,4241 }, {4361,3939,3938 ,4584,3539,3572 }, + {3938,3112,4437 ,3572,2795,4572 }, {4437,3112,3049 ,4572,2795,2803 }, + {4253,4398,4048 ,4598,4599,4600 }, {1625,4251,4192 ,388,132,4601 }, + {4380,4452,4451 ,4602,2398,271 }, {2375,2240,3945 ,4479,4478,4550 }, + {771,1669,2707 ,3799,3798,235 }, {5986,1251,5563 ,4603,4604,4605 }, + {1976,4351,2390 ,1180,4606,1594 }, {4074,890,2959 ,4607,3283,4608 }, + {1421,1223,490 ,4609,2182,469 }, {207,4533,4493 ,4516,4515,4514 }, + {4494,207,4493 ,4610,4516,4514 }, {50,651,2709 ,4611,3591,3523 }, + {2856,3344,588 ,3821,1861,3822 }, {3938,3090,3112 ,3572,2805,2795 }, + {3112,3089,3049 ,2795,2794,2803 }, {0,4253,4048 ,4612,4598,4600 }, + {376,1564,842 ,379,368,4613 }, {5835,212,1942 ,4573,4614,2337 }, + {1633,4103,466 ,373,4577,347 }, {1051,793,6 ,4588,4200,4589 }, + {4889,4890,4933 ,4615,4594,4596 }, {5571,5643,1350 ,4616,4617,509 }, + {302,683,223 ,622,621,1985 }, {987,66,1832 ,2950,4563,4576 }, + {1748,1421,490 ,4618,4609,469 }, {1225,1223,57 ,327,2182,222 }, + {3786,1418,1934 ,4074,4619,4620 }, {275,1529,477 ,4621,4524,4519 }, + {1,4062,969 ,4622,4623,4199 }, {4080,1883,4253 ,4624,4625,4598 }, + {735,2012,1345 ,224,2212,203 }, {1462,1927,710 ,2338,1196,33 }, + {4829,4799,4770 ,4626,4627,4345 }, {5194,4395,4373 ,727,726,3288 }, + {2892,771,3763 ,918,3799,3753 }, {4192,4182,118 ,4601,435,1124 }, + {7008,7007,6967 ,3176,4628,4629 }, {275,276,1529 ,4621,4525,4524 }, + {1162,463,276 ,4630,1941,4525 }, {275,1162,276 ,4621,4630,4525 }, + {1631,504,467 ,4631,4536,346 }, {2044,558,465 ,4031,4151,4578 }, + {463,1631,467 ,1941,4631,346 }, {504,513,4330 ,4536,377,399 }, + {1070,1560,245 ,4632,4633,4488 }, {7804,7856,7803 ,4634,4635,4636 }, + {4062,500,969 ,4623,4637,4199 }, {802,439,1346 ,400,4638,484 }, + {4080,4253,316 ,4624,4598,4639 }, {5817,5818,5834 ,701,4640,543 }, + {4398,759,4242 ,4599,4641,4642 }, {1949,217,216 ,3637,4643,4644 }, + {1976,2390,1977 ,1180,1594,1503 }, {1747,1731,979 ,266,485,267 }, + {1154,513,4331 ,4545,377,4645 }, {4075,1993,344 ,4521,4544,562 }, + {4933,4918,4966 ,4596,4595,4646 }, {4239,85,4217 ,2254,2504,83 }, + {4714,4777,5267 ,1031,3917,4647 }, {1426,4062,500 ,4648,4623,4637 }, + {1377,4069,630 ,2651,1881,2491 }, {4329,4355,4380 ,1247,1447,4602 }, + {1742,1562,7051 ,707,4649,547 }, {1153,8087,6797 ,2245,385,2246 }, + {4149,4174,4148 ,1486,769,2305 }, {379,1114,1467 ,2299,2256,4535 }, + {1070,245,1163 ,4632,4488,2211 }, {4156,4222,1859 ,4555,530,4480 }, + {4502,4156,1859 ,4650,4555,4480 }, {1212,4075,447 ,4520,4521,4651 }, + {122,755,4113 ,4534,1615,207 }, {245,1560,1458 ,4488,4633,4489 }, + {1441,1092,1063 ,1485,4484,4483 }, {1308,1306,4245 ,4652,4653,4654 }, + {510,2092,191 ,4655,4656,4657 }, {759,4265,4242 ,4641,4658,4642 }, + {1858,6,217 ,4590,4589,4643 }, {4088,4124,4087 ,4659,2306,4660 }, + {4355,4356,4380 ,1447,155,4602 }, {196,607,94 ,152,2207,1858 }, + {2261,608,672 ,4661,2300,4662 }, {887,4050,1087 ,4554,4663,4482 }, + {1087,4050,1531 ,4482,4663,4561 }, {4107,4396,1932 ,4491,4664,2141 }, + {4145,660,67 ,101,387,1125 }, {4175,4218,4217 ,4500,4503,83 }, + {2660,890,4074 ,455,3283,4607 }, {512,1308,4245 ,4665,4652,4654 }, + {4279,1489,4139 ,4666,4667,4668 }, {362,4048,192 ,4669,4600,4670 }, + {215,71,81 ,4671,4672,4673 }, {5818,5835,5834 ,4640,4573,543 }, + {4124,4123,4087 ,2306,133,4660 }, {4109,4175,4149 ,576,4500,1486 }, + {1925,698,1425 ,161,4674,4518 }, {4396,377,1509 ,4664,4311,4565 }, + {1932,4396,1509 ,2141,4664,4565 }, {608,378,672 ,2300,4551,4662 }, + {754,1063,800 ,4675,4483,4676 }, {1063,1366,800 ,4483,4485,4676 }, + {1807,608,2261 ,4560,2300,4661 }, {4262,699,2 ,4492,4677,4493 }, + {4197,4258,4238 ,82,84,4678 }, {3532,4074,4057 ,4415,4607,272 }, + {209,311,648 ,4499,246,4679 }, {4141,1489,4279 ,4680,4667,4666 }, + {4398,4242,192 ,4599,4642,4670 }, {5802,5801,5765 ,4681,700,540 }, + {783,2663,688 ,679,3593,765 }, {4253,4049,4398 ,4598,4682,4599 }, + {4088,4087,630 ,4659,4660,2491 }, {4069,4088,630 ,1881,4659,2491 }, + {1308,512,1883 ,4652,4665,4625 }, {5684,4106,3345 ,4683,1742,3476 }, + {1595,4145,67 ,99,101,1125 }, {4356,4403,4380 ,155,154,4602 }, + {376,979,1731 ,379,267,485 }, {3532,2660,4074 ,4415,455,4607 }, + {4137,4095,1259 ,399,349,348 }, {124,5482,2661 ,2206,3565,4684 }, + {1284,1063,754 ,4564,4483,4675 }, {229,457,116 ,221,214,217 }, + {1695,1933,701 ,471,470,4685 }, {1933,4100,4274 ,470,4481,4686 }, + {1933,4274,701 ,470,4686,4685 }, {4257,4238,4276 ,698,4678,1446 }, + {4111,1871,1872 ,4687,2458,2459 }, {889,770,844 ,35,275,4688 }, + {4048,4398,192 ,4600,4599,4670 }, {215,81,422 ,4671,4673,4689 }, + {216,215,422 ,4644,4671,4689 }, {5766,5802,5765 ,4487,4681,540 }, + {4087,879,630 ,4660,1155,2491 }, {65,71,215 ,4690,4672,4671 }, + {1926,1308,1883 ,4691,4652,4625 }, {1462,710,4177 ,2338,33,2339 }, + {4160,2012,735 ,223,2212,224 }, {3375,3532,4057 ,4426,4415,272 }, + {1425,698,121 ,4518,4674,4504 }, {4350,3346,4098 ,740,4457,353 }, + {3585,4494,4493 ,3806,4610,4514 }, {5199,5240,5219 ,422,481,1860 }, + {476,275,477 ,4692,4621,4519 }, {2,658,265 ,4493,4693,4496 }, + {4137,1259,4095 ,399,348,349 }, {4251,4470,4182 ,132,4694,435 }, + {4352,3299,3990 ,4695,3232,3231 }, {345,1489,4119 ,4696,4667,4697 }, + {362,192,71 ,4669,4670,4672 }, {1306,655,345 ,4653,1122,4696 }, + {3324,3304,3305 ,881,1050,879 }, {4098,3375,4057 ,353,4426,272 }, + {1935,3172,4289 ,4098,4155,4152 }, {275,318,463 ,4621,4698,1941 }, + {1162,275,463 ,4630,4621,1941 }, {1695,701,194 ,471,4685,4593 }, + {463,318,1631 ,1941,4698,4631 }, {504,4118,513 ,4536,4699,377 }, + {1113,117,4213 ,10,9,1070 }, {4117,4156,4502 ,4700,4555,4650 }, + {4117,4157,4156 ,4700,3755,4555 }, {4252,887,4156 ,4021,4554,4555 }, + {3141,4127,4176 ,4701,4702,4703 }, {4227,5237,5307 ,1564,1800,461 }, + {443,444,702 ,920,922,2699 }, {2255,939,4350 ,3229,3354,740 }, + {683,196,223 ,621,152,1985 }, {345,4161,1489 ,4696,4364,4667 }, + {4161,4210,1489 ,4364,4704,4667 }, {5801,5802,5817 ,700,4681,701 }, + {65,362,71 ,4690,4669,4672 }, {2859,31,4139 ,3451,3450,4668 }, + {824,4245,2092 ,4705,4654,4656 }, {4245,1306,345 ,4654,4653,4696 }, + {4177,889,379 ,2339,35,2299 }, {265,4539,697 ,4496,633,3578 }, + {4157,4252,4156 ,3755,4021,4555 }, {4252,4102,887 ,4021,4706,4554 }, + {4155,699,4262 ,4505,4677,4492 }, {5237,2086,1525 ,1800,1802,1866 }, + {4102,4050,887 ,4706,4663,4554 }, {1531,1530,4107 ,4561,4707,4491 }, + {1530,4314,4396 ,4707,1864,4664 }, {4107,1530,4396 ,4491,4707,4664 }, + {4058,4061,3347 ,4708,4490,2098 }, {4081,1079,1926 ,4709,4575,4691 }, + {759,824,2092 ,4641,4705,4656 }, {4279,4139,563 ,4666,4668,4710 }, + {0,4048,362 ,4612,4600,4669 }, {4062,1426,500 ,4623,4648,4637 }, + {3317,4069,1377 ,1882,1881,2651 }, {3452,987,1079 ,1794,2950,4575 }, + {416,466,2091 ,556,347,4494 }, {466,604,2091 ,347,486,4494 }, + {3622,1935,4289 ,4106,4098,4152 }, {4201,4181,4051 ,436,2273,437 }, + {4314,2086,377 ,1864,1802,4311 }, {4396,4314,377 ,4664,1864,4311 }, + {447,4075,344 ,4651,4521,562 }, {4977,4999,4998 ,4711,4712,4713 }, + {4276,4302,4356 ,1446,3727,155 }, {6563,2343,8061 ,464,2685,453 }, + {5573,5237,1525 ,2217,1800,1866 }, {558,416,465 ,4151,556,4578 }, + {4109,4090,4076 ,576,1487,577 }, {1467,4084,755 ,4535,205,1615 }, + {66,2697,45 ,4563,2006,4714 }, {3532,3617,3050 ,4415,4425,4414 }, + {21,3946,1089 ,1231,1230,103 }, {133,284,1241 ,1719,139,313 }, + {2695,2859,4210 ,3453,3451,4704 }, {4161,2695,4210 ,4364,3453,4704 }, + {759,2092,510 ,4641,4656,4655 }, {5802,5818,5817 ,4681,4640,701 }, + {510,191,263 ,4655,4657,4715 }, {71,27,81 ,4672,303,4673 }, {4087,4123,879 ,4660,133,1155 }, + {4062,1426,4082 ,4623,4648,4716 }, {502,604,465 ,2301,486,4578 }, + {4058,4065,4061 ,4708,4717,4490 }, {279,325,4691 ,2176,2088,2089 }, + {1163,4160,1223 ,2211,223,2182 }, {4151,510,263 ,839,4655,4715 }, + {175,4262,702 ,211,4492,2699 }, {1619,3196,1686 ,1003,2581,1004 }, + {192,4242,43 ,4670,4642,304 }, {4312,490,1225 ,467,469,327 }, + {413,4159,4235 ,351,1299,985 }, {4082,3452,1079 ,4716,1794,4575 }, + {4238,4258,4302 ,4678,84,3727 }, {193,864,867 ,1448,840,1121 }, + {4079,4113,1212 ,4718,207,4520 }, {672,378,122 ,4662,4551,4534 }, + {4824,1602,4886 ,4719,2329,4720 }, {84,3759,2030 ,4231,4233,770 }, + {2995,2254,2702 ,2856,3705,2854 }, {4400,4450,3585 ,4721,4722,3806 }, + {637,4400,3585 ,3805,4721,3806 }, {4495,4494,3585 ,4723,4610,3806 }, + {4450,4495,3585 ,4722,4723,3806 }, {4238,4302,4276 ,4678,3727,1446 }, + {4257,4238,4276 ,698,4678,1446 }, {4258,4303,4302 ,84,3728,3727 }, + {1150,683,681 ,741,621,620 }, {2697,2255,45 ,2006,3229,4714 }, + {1006,962,1100 ,234,233,2466 }, {4101,1600,209 ,4724,4498,4499 }, + {192,43,27 ,4670,304,303 }, {4059,4193,609 ,363,4725,4726 }, + {1195,4347,4395 ,725,3287,726 }, {1150,774,683 ,741,767,621 }, + {4139,31,4078 ,4668,3450,4341 }, {7799,7825,7798 ,4727,3768,4728 }, + {4779,4778,4716 ,4729,3916,1030 }, {122,4113,4079 ,4534,207,4718 }, + {841,122,4079 ,4730,4534,4718 }, {4065,1253,4067 ,4717,2353,185 }, + {756,1748,490 ,4731,4618,469 }, {4311,756,490 ,468,4731,469 }, + {4494,4495,207 ,4610,4723,4516 }, {1366,1807,4133 ,4485,4560,4732 }, + {4263,2420,4056 ,274,273,1123 }, {38,1150,681 ,742,741,620 }, + {4062,1,4063 ,4623,4622,4733 }, {808,4472,5295 ,4734,2189,2188 }, + {71,192,27 ,4672,4670,303 }, {4051,1695,210 ,437,471,4592 }, + {5681,5700,5699 ,2934,4501,43 }, {4071,4070,4064 ,186,4497,1880 }, + {4335,4265,4151 ,838,4658,839 }, {4242,642,43 ,4642,1243,304 }, + {3347,206,4058 ,2098,3932,4708 }, {4121,4139,4078 ,4735,4668,4341 }, + {0,362,65 ,4612,4669,4690 }, {3271,8125,8117 ,1727,752,4119 }, + {216,65,215 ,4644,4690,4671 }, {5618,5420,103 ,127,4228,128 }, + {1931,4644,5019 ,4736,2425,2384 }, {4067,4071,4065 ,185,186,4717 }, + {240,4724,1790 ,1571,4737,4738 }, {260,1210,4311 ,1573,4739,468 }, + {4183,1210,260 ,1572,4739,1573 }, {1070,4073,1560 ,4632,4740,4633 }, + {1730,4262,175 ,4506,4492,211 }, {476,477,207 ,4692,4519,4516 }, + {1134,476,207 ,4741,4692,4516 }, {800,1366,4133 ,4676,4485,4732 }, + {1441,4093,1092 ,1485,1484,4484 }, {4182,661,4201 ,435,2297,436 }, + {4470,880,661 ,4694,1483,2297 }, {4242,4265,642 ,4642,4658,1243 }, + {770,805,844 ,275,389,4688 }, {208,4114,1259 ,4742,362,348 }, + {143,2043,781 ,157,4100,158 }, {1421,1163,1223 ,4609,2211,2182 }, + {4313,2423,102 ,4574,433,432 }, {4265,4335,642 ,4658,838,1243 }, + {1481,654,231 ,194,93,92 }, {759,510,4151 ,4641,4655,839 }, {5986,5563,4550 ,4603,4605,716 }, + {4148,4123,4124 ,2305,133,2306 }, {322,46,5 ,1250,2232,350 }, + {4942,5420,5618 ,4191,4228,127 }, {1092,1462,4116 ,4484,2338,2340 }, + {4376,4073,1070 ,4743,4740,4632 }, {1560,2001,1458 ,4633,4744,4489 }, + {4116,4177,379 ,2340,2339,2299 }, {4376,1421,1748 ,4743,4609,4618 }, + {275,129,318 ,4621,730,4698 }, {4274,4100,72 ,4686,4481,4745 }, + {4376,1163,1421 ,4743,2211,4609 }, {4093,1462,1092 ,1484,2338,4484 }, + {5420,84,103 ,4228,4231,128 }, {5162,4942,5618 ,181,4191,127 }, + {4491,4519,226 ,107,4746,108 }, {1926,1832,1308 ,4691,4576,4652 }, + {4049,512,824 ,4682,4665,4705 }, {1992,214,781 ,834,87,158 }, + {3132,3088,3089 ,2796,3290,2794 }, {21,1089,5203 ,1231,103,102 }, + {614,609,1367 ,1397,4726,574 }, {4059,609,614 ,363,4726,1397 }, + {4265,759,4151 ,4658,4641,839 }, {802,1730,4130 ,400,4506,210 }, + {608,379,378 ,2300,2299,4551 }, {824,512,4245 ,4705,4665,4654 }, + {3577,531,3369 ,4059,3829,3836 }, {4403,4432,4452 ,154,156,2398 }, + {4148,4173,4123 ,2305,134,133 }, {4196,4195,4173 ,4747,4748,134 }, + {4540,891,4526 ,19,189,187 }, {1163,4376,1070 ,2211,4743,4632 }, + {4274,72,701 ,4686,4745,4685 }, {1631,753,504 ,4631,4749,4536 }, + {504,753,4118 ,4536,4749,4699 }, {4134,658,2 ,4750,4693,4493 }, + {4100,1284,1368 ,4481,4564,4751 }, {1458,825,1925 ,4489,4752,161 }, + {3562,5944,3943 ,4753,4754,4755 }, {4394,4273,4796 ,4044,120,4756 }, + {1445,1469,1397 ,2359,2361,2358 }, {825,698,1925 ,4752,4674,161 }, + {4100,1368,72 ,4481,4751,4745 }, {4081,1926,4080 ,4709,4691,4624 }, + {844,805,1115 ,4688,389,2257 }, {1345,2012,1846 ,203,2212,204 }, + {705,4132,659 ,70,162,71 }, {4151,263,208 ,839,4715,4742 }, {4504,4151,208 ,4757,839,4742 }, + {1833,4151,4504 ,317,839,4757 }, {111,3014,258 ,1154,3568,3001 }, + {1625,4192,660 ,388,4601,387 }, {316,4253,0 ,4639,4598,4612 }, + {4380,4403,4452 ,4602,154,2398 }, {1308,1832,1306 ,4652,4576,4653 }, + {4148,4196,4173 ,2305,4747,134 }, {3946,2893,1089 ,1230,1679,103 }, + {214,466,142 ,87,347,159 }, {4076,4090,4070 ,577,1487,4497 }, + {262,194,312 ,4758,4593,4759 }, {4083,4102,4252 ,3754,4706,4021 }, + {670,4144,5043 ,4760,4761,4762 }, {1459,4050,4102 ,4017,4663,4706 }, + {4083,1459,4102 ,3754,4017,4706 }, {1459,4066,4050 ,4017,4763,4663 }, + {658,4243,265 ,4693,4764,4496 }, {376,1731,4103 ,379,485,4577 }, + {1926,1883,4080 ,4691,4625,4624 }, {4051,4181,1933 ,437,2273,470 }, + {2092,4245,4141 ,4656,4654,4680 }, {4093,4234,1462 ,1484,1197,2338 }, + {4234,1927,1462 ,1197,1196,2338 }, {563,4139,4121 ,4710,4668,4735 }, + {2031,2122,2067 ,1560,1128,1592 }, {81,27,545 ,4673,303,305 }, + {104,774,1150 ,4765,767,741 }, {4280,4335,1833 ,1242,838,317 }, + {4195,4216,4173 ,4748,1241,134 }, {979,1595,67 ,267,99,1125 }, + {702,444,649 ,2699,922,1579 }, {4243,317,265 ,4764,561,4496 }, + {4066,1459,1531 ,4763,4017,4561 }, {4050,4066,1531 ,4663,4763,4561 }, + {1459,1561,1530 ,4017,1865,4707 }, {1531,1459,1530 ,4561,4017,4707 }, + {266,699,4155 ,4766,4677,4505 }, {121,266,4155 ,4504,4766,4505 }, + {21,5203,2030 ,1231,102,770 }, {3476,1941,3568 ,3838,1034,3839 }, + {1530,1561,4314 ,4707,1865,1864 }, {4174,4175,4217 ,769,4500,83 }, + {3532,538,2660 ,4415,4380,455 }, {5643,5642,1350 ,4617,526,509 }, + {655,4056,345 ,1122,1123,4696 }, {4141,4119,1489 ,4680,4697,4667 }, + {81,545,323 ,4673,305,4767 }, {4065,4071,4064 ,4717,186,1880 }, + {4081,4080,500 ,4709,4624,4637 }, {963,786,962 ,1341,2071,233 }, + {2536,3017,49 ,2793,3369,3367 }, {191,4279,4114 ,4657,4666,362 }, + {263,191,4114 ,4715,4657,362 }, {793,316,0 ,4200,4639,4612 }, + {4978,6414,6413 ,178,113,112 }, {2502,2331,2504 ,3740,2895,2898 }, + {4067,4068,4077 ,185,408,126 }, {702,2,443 ,2699,4493,920 }, + {699,4134,2 ,4677,4750,4493 }, {6351,8103,8077 ,1568,1291,2078 }, + {416,502,465 ,556,2301,4578 }, {604,1747,4304 ,486,266,4579 }, + {885,321,2313 ,216,60,438 }, {1591,1032,1318 ,1072,1581,164 }, + {4201,661,4181 ,436,2297,2273 }, {2707,1669,5162 ,235,3798,181 }, + {1489,4210,4139 ,4667,4704,4668 }, {4234,4093,880 ,1197,1484,1483 }, + {191,4141,4279 ,4657,4680,4666 }, {208,263,4114 ,4742,4715,362 }, + {1331,1411,2041 ,790,850,2737 }, {5839,5807,5840 ,4768,4769,4770 }, + {1333,1936,3300 ,849,848,694 }, {5288,5308,4868 ,3993,3528,3969 }, + {5970,4146,2504 ,3518,3730,2898 }, {609,1919,1367 ,4726,1779,574 }, + {609,4078,1919 ,4726,4341,1779 }, {4195,4237,4216 ,4748,697,1241 }, + {4347,3867,1851 ,3287,4507,4241 }, {793,0,6 ,4200,4612,4589 }, + {4237,4275,4256 ,697,699,3433 }, {500,4080,316 ,4637,4624,4639 }, + {45,195,655 ,4714,739,1122 }, {8096,4031,2474 ,1796,3069,874 }, + {8110,8125,3271 ,1140,752,1727 }, {2558,1066,19 ,977,680,1067 }, + {318,129,695 ,4698,730,731 }, {447,344,317 ,4651,562,561 }, {265,317,4539 ,4496,561,633 }, + {1186,1623,1624 ,1605,1709,1708 }, {658,1212,447 ,4693,4520,4651 }, + {4079,1212,658 ,4718,4520,4693 }, {94,607,82 ,1858,2207,138 }, + {315,413,4235 ,352,351,985 }, {1669,4942,5162 ,3798,4191,181 }, + {4056,4161,345 ,1123,4364,4696 }, {43,642,739 ,304,1243,2233 }, + {934,880,4470 ,131,1483,4694 }, {653,1259,4053 ,318,348,1371 }, + {49,3017,1015 ,3367,3369,3368 }, {805,24,1115 ,389,247,2257 }, + {5240,5284,5264 ,481,571,1192 }, {1532,217,1949 ,4771,4643,3637 }, + {500,316,793 ,4637,4639,4200 }, {969,500,793 ,4199,4637,4200 }, + {147,792,4712 ,3469,95,3816 }, {979,67,970 ,267,1125,268 }, {4251,4182,4192 ,132,435,4601 }, + {4375,1591,981 ,312,1072,2128 }, {4400,4401,4450 ,4721,4772,4722 }, + {1730,4155,4262 ,4506,4505,4492 }, {1633,376,4103 ,373,379,4577 }, + {664,760,663 ,1755,3106,4773 }, {672,122,266 ,4662,4534,4766 }, + {1179,207,4495 ,4774,4516,4723 }, {1210,261,4311 ,4739,4775,468 }, + {294,4375,1132 ,945,312,61 }, {4779,4803,4778 ,4729,4776,3916 }, + {237,99,100 ,869,3743,2823 }, {4095,614,1805 ,349,1397,1071 }, + {6122,8096,2072 ,1797,1796,1812 }, {180,1946,4385 ,4777,4408,4778 }, + {3299,4352,4400 ,3232,4695,4721 }, {1131,1728,1015 ,4779,4780,3368 }, + {89,1131,1015 ,1482,4779,3368 }, {1728,408,1015 ,4780,4781,3368 }, + {7,49,1015 ,4782,3367,3368 }, {408,7,1015 ,4781,4782,3368 }, + {4114,4279,563 ,362,4666,4710 }, {2709,7,50 ,3523,4782,4611 }, + {842,1564,1595 ,4613,368,99 }, {4049,759,4398 ,4682,4641,4599 }, + {1665,456,455 ,3077,301,2205 }, {2866,2867,1205 ,2655,2844,3187 }, + {806,706,843 ,4783,4784,2423 }, {1132,4375,981 ,61,312,2128 }, + {266,122,841 ,4766,4534,4730 }, {1179,1134,207 ,4774,4741,4516 }, + {4133,1807,672 ,4732,4560,4662 }, {1601,476,1134 ,4785,4692,4741 }, + {1179,1601,1134 ,4774,4785,4741 }, {6643,8122,8101 ,3133,384,383 }, + {3452,3513,3977 ,1794,441,440 }, {1284,754,4073 ,4564,4675,4740 }, + {476,129,275 ,4692,730,4621 }, {1368,1284,4073 ,4751,4564,4740 }, + {4706,4763,4704 ,4786,4787,4788 }, {4431,4380,4451 ,568,4602,271 }, + {1066,4336,19 ,680,4789,1067 }, {4336,4384,600 ,4789,4409,978 }, + {19,4336,600 ,1067,4789,978 }, {4063,1426,4062 ,4733,4648,4623 }, + {260,4312,102 ,1573,467,432 }, {8,4505,1798 ,3341,4790,4451 }, + {4505,1799,1798 ,4790,4791,4451 }, {1799,1131,89 ,4791,4779,1482 }, + {1798,1799,89 ,4451,4791,1482 }, {176,408,1728 ,4792,4781,4780 }, + {1131,176,1728 ,4779,4792,4780 }, {4114,563,4059 ,362,4710,363 }, + {3308,3294,7110 ,3894,3895,1990 }, {4059,563,4193 ,363,4710,4725 }, + {563,4121,4193 ,4710,4735,4725 }, {1595,1564,4145 ,99,368,101 }, + {1092,4116,1424 ,4484,2340,2298 }, {318,753,1631 ,4698,4749,4631 }, + {1807,2261,672 ,4560,4661,4662 }, {261,4333,4311 ,4775,4793,468 }, + {1368,4073,4376 ,4751,4740,4743 }, {5458,4129,4083 ,3756,3812,3754 }, + {226,4519,1150 ,108,4746,741 }, {4177,710,889 ,2339,33,35 }, + {4315,4337,4336 ,4794,4795,4789 }, {1066,4315,4336 ,680,4794,4789 }, + {4337,4385,4384 ,4795,4778,4409 }, {4336,4337,4384 ,4789,4795,4409 }, + {180,4455,8 ,4777,4796,3341 }, {4455,4505,8 ,4796,4790,3341 }, + {1797,176,1131 ,4797,4792,4779 }, {1799,1797,1131 ,4791,4797,4779 }, + {782,786,777 ,1648,2071,421 }, {4121,4078,609 ,4735,4341,4726 }, + {759,4049,824 ,4641,4682,4705 }, {4311,4333,756 ,468,4793,4731 }, + {379,844,1114 ,2299,4688,2256 }, {2070,1095,614 ,573,1910,1397 }, + {4238,4257,4276 ,4678,698,1446 }, {4083,4129,1459 ,3754,3812,4017 }, + {4129,3477,1459 ,3812,1736,4017 }, {800,4133,1458 ,4676,4732,4489 }, + {1560,800,2001 ,4633,4676,4744 }, {69,25,1319 ,321,2186,322 }, + {118,4051,210 ,1124,437,4592 }, {4315,1450,4337 ,4794,823,4795 }, + {4385,4413,180 ,4778,4798,4777 }, {4413,4455,180 ,4798,4796,4777 }, + {4506,1797,1799 ,4799,4797,4791 }, {4505,4506,1799 ,4790,4799,4791 }, + {573,823,408 ,4800,4801,4781 }, {176,573,408 ,4792,4800,4781 }, + {50,7,408 ,4611,4782,4781 }, {823,50,408 ,4801,4611,4781 }, {2092,4141,191 ,4656,4680,4657 }, + {4193,4121,609 ,4725,4735,4726 }, {934,4234,880 ,131,1197,1483 }, + {4245,4119,4141 ,4654,4697,4680 }, {1926,1079,1832 ,4691,4575,4576 }, + {3985,789,3858 ,4802,3910,4803 }, {66,1211,2697 ,4563,2007,2006 }, + {4085,1368,4376 ,4804,4751,4743 }, {4149,4148,4090 ,1486,2305,1487 }, + {4085,4376,1748 ,4804,4743,4618 }, {4237,4238,4257 ,697,4678,698 }, + {2001,800,1458 ,4744,4676,4489 }, {4276,4356,4355 ,1446,155,1447 }, + {4108,4124,4088 ,2304,2306,4659 }, {7029,2341,3008 ,548,845,546 }, + {3763,771,2707 ,3753,3799,235 }, {1808,1836,380 ,4502,2490,2540 }, + {4337,4338,4385 ,4795,4805,4778 }, {4385,4338,4413 ,4778,4805,4798 }, + {4456,4506,4505 ,4806,4799,4790 }, {4455,4456,4505 ,4796,4806,4790 }, + {4506,895,1797 ,4799,4807,4797 }, {895,573,176 ,4807,4800,4792 }, + {1797,895,176 ,4797,4807,4792 }, {73,50,823 ,4808,4611,4801 }, + {1621,73,823 ,4809,4808,4801 }, {148,651,50 ,4810,3591,4611 }, + {73,148,50 ,4808,4810,4611 }, {212,1208,1942 ,4614,3211,2337 }, + {4251,934,4470 ,132,131,4694 }, {6,0,65 ,4589,4612,4690 }, {884,4059,614 ,2160,363,1397 }, + {1259,4059,4095 ,348,363,349 }, {1306,45,655 ,4653,4714,1122 }, + {1467,1114,4084 ,4535,2256,205 }, {3872,5188,5786 ,4811,4812,4813 }, + {4197,4238,4196 ,82,4678,4747 }, {379,889,844 ,2299,35,4688 }, + {842,1595,979 ,4613,99,267 }, {261,312,756 ,4775,4759,4731 }, + {312,194,756 ,4759,4593,4731 }, {1458,4133,825 ,4489,4732,4752 }, + {4238,4237,4196 ,4678,697,4747 }, {43,739,545 ,304,2233,305 }, + {2650,4031,8096 ,914,3069,1796 }, {4316,4338,4337 ,4814,4805,4795 }, + {1450,4316,4337 ,823,4814,4795 }, {4414,4456,4455 ,4815,4806,4796 }, + {4413,4414,4455 ,4798,4815,4796 }, {4456,4507,4506 ,4806,4816,4799 }, + {4506,4507,895 ,4799,4816,4807 }, {452,1621,823 ,4817,4809,4801 }, + {573,452,823 ,4800,4817,4801 }, {73,2254,148 ,4808,3705,4810 }, + {4373,4347,1851 ,3288,3287,4241 }, {217,6,216 ,4643,4589,4644 }, + {6,65,216 ,4589,4690,4644 }, {4210,2859,4139 ,4704,3451,4668 }, + {4071,4076,4070 ,186,577,4497 }, {4084,2047,1600 ,205,2258,4498 }, + {455,4471,259 ,2205,2187,2203 }, {194,701,756 ,4593,4685,4731 }, + {698,266,121 ,4674,4766,4504 }, {918,416,558 ,4150,556,4151 }, + {4077,4125,4109 ,126,125,576 }, {4144,670,261 ,4761,4760,4775 }, + {670,312,261 ,4760,4759,4775 }, {1114,2047,4084 ,2256,2258,205 }, + {4313,4312,1225 ,4574,467,327 }, {1148,4132,705 ,160,162,70 }, + {259,4471,258 ,2203,2187,3001 }, {1340,4266,1450 ,822,4818,823 }, + {1450,4266,4316 ,823,4818,4814 }, {2254,2995,148 ,3705,2856,4810 }, + {4363,4414,4413 ,4819,4815,4798 }, {4338,4363,4413 ,4805,4819,4798 }, + {4415,4457,4456 ,4820,4821,4806 }, {4414,4415,4456 ,4815,4820,4806 }, + {4456,4457,4507 ,4806,4821,4816 }, {895,452,573 ,4807,4817,4800 }, + {752,2254,73 ,4822,3705,4808 }, {8086,8087,1153 ,4823,385,2245 }, + {4171,659,802 ,399,71,400 }, {4245,345,4119 ,4654,4696,4697 }, + {4135,1259,4179 ,1069,348,251 }, {4159,4280,1833 ,1299,1242,317 }, + {3793,4305,4357 ,3420,4512,4508 }, {664,663,568 ,1755,4773,1723 }, + {4149,4175,4174 ,1486,4500,769 }, {5136,5178,4630 ,1817,524,827 }, + {4089,4088,4069 ,4495,4659,1881 }, {4070,4089,4069 ,4497,4495,1881 }, + {3978,6679,6418 ,4549,4824,3441 }, {4084,4101,1178 ,205,4724,206 }, + {4084,1600,4101 ,205,4498,4724 }, {2,461,443 ,4493,399,920 }, + {660,4192,118 ,387,4601,1124 }, {3615,5681,5660 ,201,2934,3054 }, + {1090,4266,1340 ,2687,4818,822 }, {4266,4317,4316 ,4818,4825,4814 }, + {4317,4363,4338 ,4825,4819,4805 }, {4316,4317,4338 ,4814,4825,4805 }, + {4364,4415,4414 ,4826,4820,4815 }, {4363,4364,4414 ,4819,4826,4815 }, + {4507,1769,895 ,4816,4827,4807 }, {1769,1084,895 ,4827,4828,4807 }, + {1084,246,452 ,4828,4829,4817 }, {895,1084,452 ,4807,4828,4817 }, + {246,1654,1621 ,4829,4830,4809 }, {452,246,1621 ,4817,4829,4809 }, + {1654,73,1621 ,4830,4808,4809 }, {1654,167,73 ,4830,4831,4808 }, + {167,752,73 ,4831,4822,4808 }, {2659,8120,3286 ,3485,906,56 }, + {5182,5181,4631 ,4567,4319,1816 }, {1883,512,4049 ,4625,4665,4682 }, + {4446,2607,1300 ,237,2452,285 }, {4089,4108,4088 ,4495,2304,4659 }, + {5158,5182,4631 ,4832,4567,1816 }, {7,2709,49 ,4782,3523,3367 }, + {3039,2040,1090 ,2686,4833,2687 }, {323,545,46 ,4767,305,2232 }, + {4352,4401,4400 ,4695,4772,4721 }, {2043,11,781 ,4100,299,158 }, + {2040,537,1090 ,4833,4834,2687 }, {537,4267,4266 ,4834,4835,4818 }, + {1090,537,4266 ,2687,4834,4818 }, {4266,4267,4317 ,4818,4835,4825 }, + {4318,4364,4363 ,4836,4826,4819 }, {4317,4318,4363 ,4825,4836,4819 }, + {4457,4523,4507 ,4821,4837,4816 }, {4523,1084,1769 ,4837,4828,4827 }, + {4507,4523,1769 ,4816,4837,4827 }, {1589,167,1654 ,4838,4831,4830 }, + {2276,2254,752 ,4839,3705,4822 }, {167,2276,752 ,4831,4839,4822 }, + {123,48,1049 ,3607,4840,1362 }, {2276,3117,2254 ,4839,3407,3705 }, + {1883,4049,4253 ,4625,4682,4598 }, {8063,8061,8102 ,465,453,253 }, + {130,95,1798 ,2341,3482,4451 }, {642,4280,413 ,1243,1242,351 }, + {699,841,4079 ,4677,4730,4718 }, {825,4133,698 ,4752,4732,4674 }, + {67,118,359 ,1125,1124,4841 }, {67,359,970 ,1125,4841,268 }, + {4133,672,698 ,4732,4662,4674 }, {136,1179,4495 ,1685,4774,4723 }, + {5182,5180,5181 ,4567,1878,4319 }, {5197,1928,4518 ,4842,1282,1281 }, + {4267,4318,4317 ,4835,4836,4825 }, {4415,4458,4457 ,4820,4843,4821 }, + {4458,4477,4457 ,4843,4844,4821 }, {4457,4477,4523 ,4821,4844,4837 }, + {246,641,1654 ,4829,4845,4830 }, {641,1589,1654 ,4845,4838,4830 }, + {354,2276,167 ,4846,4839,4831 }, {2379,3117,2276 ,3238,3407,4839 }, + {354,2379,2276 ,4846,3238,4839 }, {4275,4276,4329 ,699,1446,1247 }, + {1600,24,311 ,4498,247,246 }, {1115,24,1600 ,2257,247,4498 }, + {2314,2407,6467 ,3742,4001,4000 }, {659,1425,1730 ,71,4518,4506 }, + {118,210,359 ,1124,4592,4841 }, {266,841,699 ,4766,4730,4677 }, + {1601,76,476 ,4785,689,4692 }, {1925,1425,659 ,161,4518,71 }, + {76,1632,476 ,689,2241,4692 }, {476,1632,129 ,4692,2241,730 }, + {462,318,695 ,4847,4698,731 }, {4470,661,4182 ,4694,2297,435 }, + {7479,7478,7422 ,4423,1402,1404 }, {537,4268,4267 ,4834,4848,4835 }, + {4267,4268,4318 ,4835,4848,4836 }, {4364,4386,4415 ,4826,4849,4820 }, + {4386,4438,4415 ,4849,4850,4820 }, {4415,4438,4458 ,4820,4850,4843 }, + {4458,4438,4477 ,4843,4850,4844 }, {1084,854,246 ,4828,4851,4829 }, + {854,641,246 ,4851,4845,4829 }, {4451,4452,4498 ,271,2398,88 }, + {4196,4237,4195 ,4747,697,4748 }, {376,842,979 ,379,4613,267 }, + {4216,4237,4256 ,1241,697,3433 }, {4504,208,653 ,4757,4742,318 }, + {323,46,706 ,4767,2232,4784 }, {698,672,266 ,4674,4662,4766 }, + {4073,754,1560 ,4740,4675,4633 }, {8095,7983,8062 ,4362,4361,3272 }, + {753,462,695 ,4749,4847,731 }, {318,462,753 ,4698,4847,4749 }, + {3618,735,3535 ,37,224,442 }, {1245,735,3618 ,36,224,37 }, {4130,1730,175 ,210,4506,211 }, + {4339,4340,4364 ,4852,4853,4826 }, {4318,4339,4364 ,4836,4852,4826 }, + {4340,4386,4364 ,4853,4849,4826 }, {4523,1770,1084 ,4837,4854,4828 }, + {1770,854,1084 ,4854,4851,4828 }, {894,10,641 ,4855,4856,4845 }, + {854,894,641 ,4851,4855,4845 }, {1878,1589,641 ,4857,4838,4845 }, + {10,1878,641 ,4856,4857,4845 }, {1392,167,1589 ,4858,4831,4838 }, + {1878,1392,1589 ,4857,4858,4838 }, {1392,354,167 ,4858,4846,4831 }, + {537,6199,4268 ,4834,4859,4848 }, {214,142,781 ,87,159,158 }, + {8089,8082,8097 ,254,454,1710 }, {46,322,843 ,2232,1250,2423 }, + {4053,1259,4135 ,1371,348,1069 }, {1833,4504,653 ,317,4757,318 }, + {1163,245,2012 ,2211,4488,2212 }, {1424,4116,379 ,2298,2340,2299 }, + {615,614,1095 ,766,1397,1910 }, {4065,4064,4061 ,4717,1880,4490 }, + {148,2995,651 ,4810,2856,3591 }, {4132,1925,659 ,162,161,71 }, + {754,800,1560 ,4675,4676,4633 }, {1808,4111,1872 ,4502,4687,2459 }, + {4268,4269,4318 ,4848,4860,4836 }, {4269,4340,4339 ,4860,4853,4852 }, + {4318,4269,4339 ,4836,4860,4852 }, {4477,1770,4523 ,4844,4854,4837 }, + {1770,1737,4523 ,4854,4861,4837 }, {4523,1737,1770 ,4837,4861,4854 }, + {1737,894,854 ,4861,4855,4851 }, {1770,1737,854 ,4854,4861,4851 }, + {1086,354,1392 ,4862,4846,4858 }, {4163,2379,354 ,4863,3238,4846 }, + {1086,4163,354 ,4862,4863,4846 }, {5334,4887,5376 ,2736,2735,2758 }, + {5950,8074,8066 ,2873,2460,960 }, {4765,4764,4706 ,3983,3982,4786 }, + {4095,4059,884 ,349,363,2160 }, {4182,4051,118 ,435,437,1124 }, + {5182,5221,5180 ,4567,4864,1878 }, {709,653,544 ,316,318,195 }, + {341,632,2193 ,1852,1380,1007 }, {735,1345,3535 ,224,203,442 }, + {72,1368,4085 ,4745,4751,4804 }, {3477,407,1459 ,1736,4018,4017 }, + {4773,4844,4804 ,4865,4866,4867 }, {1835,1871,1870 ,2421,2458,4868 }, + {5377,5411,5376 ,361,4284,2758 }, {4269,4284,4340 ,4860,4869,4853 }, + {4438,4478,4477 ,4850,4870,4844 }, {4477,4478,1770 ,4844,4870,4854 }, + {1770,4478,1737 ,4854,4870,4861 }, {693,1878,10 ,4871,4857,4856 }, + {693,1392,1878 ,4871,4858,4857 }, {213,4163,1086 ,3606,4863,4862 }, + {102,240,260 ,432,1571,1573 }, {4074,2959,4057 ,4607,4608,272 }, + {2012,245,1846 ,2212,4488,204 }, {683,396,196 ,621,153,152 }, + {245,1148,1846 ,4488,160,204 }, {226,111,1653 ,108,1154,106 }, + {379,1467,378 ,2299,4535,4551 }, {4315,1066,1450 ,4794,680,823 }, + {5243,5242,5180 ,3994,3843,1878 }, {6225,4321,5630 ,4872,4873,4874 }, + {701,72,1748 ,4685,4745,4618 }, {72,4085,1748 ,4745,4804,4618 }, + {5814,5815,5831 ,4875,4065,3943 }, {5799,5815,5814 ,4064,4065,4875 }, + {5798,5799,5814 ,4876,4064,4875 }, {5815,5832,5831 ,4065,3904,3943 }, + {3945,3978,6418 ,4550,4549,3441 }, {4229,4284,4269 ,4877,4869,4860 }, + {4387,4439,4438 ,4878,4879,4850 }, {4386,4387,4438 ,4849,4878,4850 }, + {4438,4439,4478 ,4850,4879,4870 }, {942,693,10 ,4880,4871,4856 }, + {894,942,10 ,4855,4880,4856 }, {228,1086,1392 ,4881,4862,4858 }, + {228,213,1086 ,4881,3606,4862 }, {1806,439,802 ,3124,4638,400 }, + {2431,1304,2476 ,2977,2639,2441 }, {5221,5243,5180 ,4864,3994,1878 }, + {4379,4380,4431 ,567,4602,568 }, {5265,5241,5242 ,4882,2120,3843 }, + {4158,802,1346 ,72,400,484 }, {2041,553,1331 ,2737,792,790 }, + {6346,7254,7860 ,4883,450,452 }, {697,113,59 ,3578,677,967 }, + {5243,5265,5242 ,3994,4882,3843 }, {600,4384,131 ,978,4409,2965 }, + {705,4537,1093 ,70,67,66 }, {1846,705,1093 ,204,70,66 }, {4174,4197,4148 ,769,82,2305 }, + {261,756,4333 ,4775,4731,4793 }, {5763,5799,5798 ,4212,4064,4876 }, + {5762,5763,5798 ,4884,4212,4876 }, {1220,875,1920 ,4885,579,581 }, + {7076,1076,7075 ,4886,4887,4420 }, {4284,4341,4340 ,4869,4888,4853 }, + {4341,4387,4386 ,4888,4878,4849 }, {4340,4341,4386 ,4853,4888,4849 }, + {4387,4416,4439 ,4878,4889,4879 }, {1738,942,894 ,4890,4880,4855 }, + {1737,1738,894 ,4861,4890,4855 }, {942,33,693 ,4880,4891,4871 }, + {1856,1392,693 ,4892,4858,4871 }, {33,1856,693 ,4891,4892,4871 }, + {1856,1457,1392 ,4892,4893,4858 }, {1457,228,1392 ,4893,4881,4858 }, + {3447,64,1940 ,3928,297,3925 }, {48,123,213 ,4840,3607,3606 }, + {5287,5286,5241 ,4003,3859,2120 }, {4144,261,1210 ,4761,4775,4739 }, + {1973,4685,4906 ,551,4894,4895 }, {5265,5287,5241 ,4882,4003,2120 }, + {5874,6090,6066 ,4896,4897,1455 }, {2390,1869,940 ,1594,2808,197 }, + {4158,1346,705 ,72,484,70 }, {701,1748,756 ,4685,4618,4731 }, + {3990,3254,4019 ,3231,3233,227 }, {1015,2536,409 ,3368,2793,1480 }, + {3549,588,3369 ,3823,3822,3836 }, {5724,5725,5762 ,4898,3824,4884 }, + {4246,4285,4284 ,3665,4899,4869 }, {4270,4246,4284 ,4900,3665,4869 }, + {4284,4285,4341 ,4869,4899,4888 }, {4341,4365,4387 ,4888,4901,4878 }, + {4387,4365,4416 ,4878,4901,4889 }, {4508,1738,1737 ,4902,4890,4861 }, + {4478,4508,1737 ,4870,4902,4861 }, {1738,986,942 ,4890,4903,4880 }, + {986,1493,942 ,4903,4904,4880 }, {942,1493,33 ,4880,4904,4891 }, + {285,1856,33 ,4905,4892,4891 }, {285,1457,1856 ,4905,4893,4892 }, + {285,228,1457 ,4905,4881,4893 }, {48,213,228 ,4840,3606,4881 }, + {2677,123,1050 ,3033,3607,2282 }, {642,413,739 ,1243,351,2233 }, + {3990,4353,4352 ,3231,4906,4695 }, {4101,209,1178 ,4724,4499,206 }, + {4313,1225,2423 ,4574,327,433 }, {653,208,1259 ,318,4742,348 }, + {1976,4351,2390 ,1180,4606,1594 }, {4351,1976,2390 ,4606,1180,1594 }, + {4351,801,2390 ,4606,4907,1594 }, {2390,801,1869 ,1594,4907,2808 }, + {4352,4353,4401 ,4695,4906,4772 }, {1946,180,131 ,4408,4777,2965 }, + {4539,93,113 ,633,563,677 }, {4835,8126,3148 ,2130,812,814 }, + {588,3368,3369 ,3822,3960,3836 }, {4285,4319,4341 ,4899,4908,4888 }, + {4341,4319,4365 ,4888,4908,4901 }, {4459,4508,4478 ,4909,4902,4870 }, + {4439,4459,4478 ,4879,4909,4870 }, {4508,4509,1738 ,4902,4910,4890 }, + {1738,4509,986 ,4890,4910,4903 }, {1493,598,33 ,4904,4911,4891 }, + {598,285,33 ,4911,4905,4891 }, {285,4143,228 ,4905,4912,4881 }, + {4143,4092,228 ,4912,4913,4881 }, {228,4092,48 ,4881,4913,4840 }, + {47,941,1049 ,4914,2277,1362 }, {48,47,1049 ,4840,4914,1362 }, + {45,2255,195 ,4714,3229,739 }, {1088,1623,1186 ,1604,1709,1605 }, + {142,466,143 ,159,347,157 }, {4367,4343,6225 ,228,1818,4872 }, + {1976,2006,4351 ,1180,1181,4606 }, {1869,363,4427 ,2808,427,426 }, + {2364,3343,2365 ,2513,2515,3351 }, {4243,447,317 ,4764,4651,561 }, + {340,960,730 ,1107,1326,1549 }, {5725,5763,5762 ,3824,4212,4884 }, + {5678,5679,5724 ,3861,2553,4898 }, {5522,5558,5557 ,755,2302,756 }, + {4246,4319,4285 ,3665,4908,4899 }, {4365,4417,4416 ,4901,4915,4889 }, + {4417,4459,4439 ,4915,4909,4879 }, {4416,4417,4439 ,4889,4915,4879 }, + {4460,4509,4508 ,4916,4910,4902 }, {4459,4460,4508 ,4909,4916,4902 }, + {986,599,1493 ,4903,4917,4904 }, {1493,599,598 ,4904,4917,4911 }, + {397,4092,4143 ,4918,4913,4912 }, {285,397,4143 ,4905,4918,4912 }, + {4092,4428,48 ,4913,4919,4840 }, {4428,47,48 ,4919,4914,4840 }, + {1834,1835,1870 ,2422,2421,4868 }, {4082,4081,500 ,4716,4709,4637 }, + {5418,5103,7917 ,3471,77,4920 }, {2255,4350,195 ,3229,740,739 }, + {987,3977,66 ,2950,440,4563 }, {2431,1511,967 ,2977,4921,3668 }, + {136,4495,4450 ,1685,4723,4722 }, {4496,136,4450 ,2220,1685,4722 }, + {4134,4079,658 ,4750,4718,4693 }, {1005,3818,3732 ,3535,1244,3536 }, + {3075,3981,3263 ,2670,915,252 }, {801,415,1869 ,4907,4922,2808 }, + {1869,415,363 ,2808,4922,427 }, {4091,3141,4126 ,123,4701,124 }, + {699,4079,4134 ,4677,4718,4750 }, {913,536,863 ,444,523,284 }, + {1343,4399,1564 ,378,4546,368 }, {443,265,697 ,920,4496,3578 }, + {5217,4763,4797 ,4026,4787,3984 }, {5679,5725,5724 ,2553,3824,4898 }, + {5308,5288,5289 ,3528,3993,80 }, {3837,4320,4319 ,3667,4923,4908 }, + {4246,3837,4319 ,3665,3667,4908 }, {4320,4366,4365 ,4923,4924,4901 }, + {4319,4320,4365 ,4908,4923,4901 }, {4365,4366,4417 ,4901,4924,4915 }, + {4418,4460,4459 ,4925,4916,4909 }, {4417,4418,4459 ,4915,4925,4909 }, + {4509,853,986 ,4910,4926,4903 }, {986,853,599 ,4903,4926,4917 }, + {598,397,285 ,4911,4918,4905 }, {4092,4168,4428 ,4913,4927,4919 }, + {4428,4168,47 ,4919,4927,4914 }, {4062,4082,500 ,4623,4716,4637 }, + {4159,1833,709 ,1299,317,316 }, {1117,5090,6418 ,4928,4929,3441 }, + {658,447,4243 ,4693,4651,4764 }, {223,196,157 ,1985,152,1877 }, + {1179,883,1601 ,4774,119,4785 }, {4399,1922,1564 ,4546,4432,368 }, + {1601,883,76 ,4785,119,689 }, {1307,4203,801 ,4930,4931,4907 }, + {801,4203,415 ,4907,4931,4922 }, {5340,5339,5286 ,4002,2223,3859 }, + {4302,166,4356 ,3727,3729,155 }, {4384,4385,1946 ,4409,4778,4408 }, + {545,739,46 ,305,2233,2232 }, {4110,467,1633 ,372,346,373 }, + {467,513,1633 ,346,377,373 }, {2153,164,3980 ,4181,4932,4933 }, + {3414,4528,2142 ,4934,4935,4936 }, {3837,4286,4320 ,3667,4274,4923 }, + {4366,4418,4417 ,4924,4925,4915 }, {4479,4524,4509 ,4937,4938,4910 }, + {4460,4479,4509 ,4916,4937,4910 }, {4509,4524,853 ,4910,4938,4926 }, + {599,750,598 ,4917,4939,4911 }, {598,750,397 ,4911,4939,4918 }, + {397,4168,4092 ,4918,4927,4913 }, {610,47,4168 ,4940,4914,4927 }, + {4282,941,47 ,4941,2277,4914 }, {610,4282,47 ,4940,4941,4914 }, + {4282,119,941 ,4941,2278,2277 }, {4054,4055,4058 ,3679,3678,4708 }, + {1528,1424,608 ,4559,2298,2300 }, {258,3014,259 ,3001,3568,2203 }, + {2006,1565,1307 ,1181,3737,4930 }, {1307,1565,4203 ,4930,3737,4931 }, + {415,380,363 ,4922,2540,427 }, {5287,5340,5286 ,4003,4002,3859 }, + {4109,4125,4126 ,576,125,124 }, {4058,1253,4065 ,4708,2353,4717 }, + {5103,5412,5378 ,77,360,359 }, {4298,4186,3616 ,3580,683,3974 }, + {4469,2153,3980 ,4060,4181,4933 }, {3009,5702,5701 ,3747,3630,3938 }, + {4366,4388,4418 ,4924,4942,4925 }, {4440,4479,4460 ,4943,4937,4916 }, + {4418,4440,4460 ,4925,4943,4916 }, {853,1563,599 ,4926,4944,4917 }, + {1563,1149,599 ,4944,4945,4917 }, {1149,1924,750 ,4945,4946,4939 }, + {599,1149,750 ,4917,4945,4939 }, {4219,4168,397 ,4947,4927,4918 }, + {657,4282,610 ,4948,4941,4940 }, {657,119,4282 ,4948,2278,4941 }, + {4081,4082,1079 ,4709,4716,4575 }, {4057,2959,2420 ,272,4608,273 }, + {5643,5659,5642 ,4617,3053,526 }, {4471,1653,258 ,2187,106,3001 }, + {1651,200,1609 ,188,244,31 }, {2742,1565,2006 ,2558,3737,1181 }, + {4203,796,415 ,4931,4949,4922 }, {415,796,380 ,4922,4949,2540 }, + {95,4522,1798 ,3482,3481,4451 }, {3604,3626,3654 ,3384,4196,1438 }, + {4451,4498,4497 ,271,88,91 }, {3727,4298,3616 ,3942,3580,3974 }, + {531,4469,3980 ,3829,4060,4933 }, {4320,4342,4366 ,4923,4950,4924 }, + {4342,4389,4388 ,4950,4951,4942 }, {4366,4342,4388 ,4924,4950,4942 }, + {4389,4440,4418 ,4951,4943,4925 }, {4388,4389,4418 ,4942,4951,4925 }, + {4524,1736,853 ,4938,4952,4926 }, {1736,773,853 ,4952,4953,4926 }, + {773,1563,853 ,4953,4944,4926 }, {773,1149,1563 ,4953,4945,4944 }, + {1924,98,397 ,4946,4954,4918 }, {750,1924,397 ,4939,4946,4918 }, + {98,769,397 ,4954,4955,4918 }, {769,4219,397 ,4955,4947,4918 }, + {769,4168,4219 ,4955,4927,4947 }, {656,610,4168 ,4956,4940,4927 }, + {656,657,610 ,4956,4948,4940 }, {2959,2419,2420 ,4608,3353,273 }, + {918,143,416 ,4150,157,556 }, {4019,4300,3990 ,227,231,3231 }, + {3626,3655,3654 ,4196,3936,1438 }, {4300,4301,3990 ,231,167,3231 }, + {1565,1972,4203 ,3737,3034,4931 }, {4203,1972,796 ,4931,3034,4949 }, + {796,1808,380 ,4949,4502,2540 }, {439,1806,1346 ,4638,3124,484 }, + {8088,6837,8100 ,1998,1057,1713 }, {463,360,277 ,1941,86,85 }, + {4301,4354,4353 ,167,376,4906 }, {3990,4301,4353 ,3231,167,4906 }, + {4354,4402,4401 ,376,2190,4772 }, {1869,4427,1839 ,2808,426,1138 }, + {792,4536,4712 ,95,89,3816 }, {4655,4681,4654 ,4957,4958,4348 }, + {5377,4904,5378 ,361,3309,359 }, {34,531,3980 ,3830,3829,4933 }, + {4342,4320,4286 ,4950,4923,4274 }, {3371,1941,3265 ,3708,1034,1033 }, + {4510,4524,4479 ,4959,4938,4937 }, {4510,1719,1736 ,4959,4960,4952 }, + {4524,4510,1736 ,4938,4959,4952 }, {1719,773,1736 ,4960,4953,4952 }, + {1149,98,1924 ,4945,4954,4946 }, {4169,4168,769 ,4961,4927,4955 }, + {4169,605,4168 ,4961,4962,4927 }, {605,656,4168 ,4962,4956,4927 }, + {657,26,119 ,4948,2126,2278 }, {26,460,119 ,2126,1999,2278 }, + {643,2602,546 ,3285,3234,1171 }, {466,4103,604 ,347,4577,486 }, + {66,2697,1211 ,4563,2006,2007 }, {478,613,64 ,2077,298,297 }, + {4353,4354,4401 ,4906,376,4772 }, {1565,511,1972 ,3737,3569,3034 }, + {453,301,82 ,1814,1857,138 }, {1972,1948,796 ,3034,2543,4949 }, + {796,1948,1808 ,4949,2543,4502 }, {915,916,863 ,443,445,284 }, + {4473,4450,4401 ,270,4722,4772 }, {4402,4473,4401 ,2190,270,4772 }, + {6679,1117,6418 ,4824,4928,3441 }, {4298,3727,5832 ,3580,3942,3904 }, + {3673,52,164 ,648,411,4932 }, {3294,3292,3293 ,3895,4067,1988 }, + {4461,4479,4440 ,4963,4937,4943 }, {4461,4511,4510 ,4963,4964,4959 }, + {4479,4461,4510 ,4937,4963,4959 }, {4511,1719,4510 ,4964,4960,4959 }, + {560,98,1149 ,4965,4954,4945 }, {769,1209,4169 ,4955,279,4961 }, + {4169,1209,605 ,4961,279,4962 }, {656,605,657 ,4956,4962,4948 }, + {1347,26,657 ,4966,2126,4948 }, {1427,1971,4740 ,2378,2520,2426 }, + {2504,2461,2583 ,2898,2899,2672 }, {4473,4496,4450 ,270,2220,4722 }, + {1972,1349,1948 ,3034,2519,2543 }, {103,84,2030 ,128,4231,770 }, + {1840,1869,1839 ,198,2808,1138 }, {136,883,1179 ,1685,119,4774 }, + {5679,5697,5725 ,2553,2552,3824 }, {2153,3673,164 ,4181,648,4932 }, + {7648,7697,7647 ,3952,651,4967 }, {4420,4440,4389 ,276,4943,4951 }, + {4419,4420,4389 ,4968,276,4951 }, {4420,4461,4440 ,276,4963,4943 }, + {594,1149,773 ,2453,4945,4953 }, {1149,594,560 ,4945,2453,4965 }, + {1209,769,98 ,279,4955,4954 }, {605,1347,657 ,4962,4966,4948 }, + {1919,1004,1452 ,1779,3452,1606 }, {1832,66,1306 ,4576,4563,4653 }, + {3569,2660,538 ,4381,455,4380 }, {3759,21,2030 ,4233,1231,770 }, + {1948,1427,1808 ,2543,2378,4502 }, {1808,1427,4111 ,4502,2378,4687 }, + {643,546,644 ,3285,1171,1176 }, {4111,1870,1871 ,4687,4868,2458 }, + {4126,4198,4175 ,124,4969,4500 }, {5389,1206,1734 ,4091,2218,2219 }, + {5784,4011,878 ,396,395,398 }, {5814,5831,5830 ,4875,3943,4163 }, + {3892,3905,6166 ,4970,4194,4971 }, {5798,5814,5830 ,4876,4875,4163 }, + {4343,4389,4342 ,1818,4951,4950 }, {4343,4420,4419 ,1818,276,4968 }, + {4389,4343,4419 ,4951,1818,4968 }, {893,773,1719 ,2454,4953,4960 }, + {893,594,773 ,2454,2453,4953 }, {98,1069,1209 ,4954,277,279 }, + {605,44,1347 ,4962,357,4966 }, {1347,70,26 ,4966,323,2126 }, + {66,45,1306 ,4563,4714,4653 }, {3668,959,8070 ,513,1358,941 }, + {4723,1676,4895 ,4972,2343,4973 }, {1049,1050,123 ,1362,2282,3607 }, + {1015,409,89 ,3368,1480,1482 }, {4111,4327,1870 ,4687,2377,4868 }, + {4175,4198,3784 ,4500,4969,2252 }, {2208,3801,5090 ,1257,4974,4929 }, + {1117,2208,5090 ,4928,1257,4929 }, {5389,1734,3113 ,4091,2219,410 }, + {4185,5389,3113 ,409,4091,410 }, {2960,5941,808 ,4975,4976,4734 }, + {4390,4420,4343 ,230,276,1818 }, {4512,4511,4461 ,2279,4964,4963 }, + {4511,4512,1719 ,4964,2279,4960 }, {4512,893,1719 ,2279,2454,4960 }, + {560,971,98 ,4965,356,4954 }, {98,971,1069 ,4954,356,277 }, {4138,44,605 ,278,357,4962 }, + {1209,4138,605 ,279,278,4962 }, {44,1793,1347 ,357,319,4966 }, + {1793,69,1347 ,319,321,4966 }, {1347,69,70 ,4966,321,323 }, {6836,3534,6353 ,852,942,749 }, + {5657,5680,5640 ,48,50,2214 }, {4056,4098,4263 ,1123,353,274 }, + {1846,1148,705 ,204,160,70 }, {844,1115,1114 ,4688,2257,2256 }, + {1115,1600,2047 ,2257,4498,2258 }, {4969,4992,1030 ,4977,4978,1619 }, + {356,1112,4194 ,310,292,293 }, {3346,3617,3375 ,4457,4425,4426 }, + {4329,4380,4379 ,1247,4602,567 }, {1427,4327,4111 ,2378,2377,4687 }, + {4327,4099,1870 ,2377,4533,4868 }, {4197,4196,4148 ,82,4747,2305 }, + {5412,5411,5377 ,360,4284,361 }, {4462,4461,4420 ,1076,4963,276 }, + {4462,4512,4461 ,1076,2279,4963 }, {1057,560,594 ,354,4965,2453 }, + {871,1057,594 ,2106,354,2453 }, {1057,971,560 ,354,356,4965 }, + {4836,137,4783 ,4979,4980,4981 }, {4833,4848,4822 ,4982,4983,4984 }, + {4687,274,4637 ,4985,1819,4986 }, {4176,4127,5163 ,4703,4702,4987 }, + {7374,7373,7348 ,4988,4989,4990 }, {4857,4873,509 ,4991,4992,4993 }, + {4886,1602,361 ,4720,2329,4994 }, {5030,5031,5047 ,2134,4995,4996 }, + {4688,4748,4687 ,4997,4998,4985 }, {574,616,4874 ,2181,4999,5000 }, + {4657,4716,4656 ,5001,1030,5002 }, {3801,3660,7052 ,4974,709,708 }, + {996,3784,4176 ,5003,2252,4703 }, {4614,4692,279 ,3109,2177,2176 }, + {2208,5088,5393 ,1257,5004,5005 }, {4716,4715,4656 ,1030,1029,5002 }, + {4657,4656,4605 ,5001,5002,5006 }, {4751,4880,417 ,5007,5008,5009 }, + {5016,561,4644 ,5010,2379,2425 }, {4644,561,4740 ,2425,2379,2426 }, + {8122,8114,8145 ,384,2286,3376 }, {3801,2208,5393 ,4974,1257,5005 }, + {4614,2322,4692 ,3109,3497,2177 }, {707,669,612 ,2091,2020,5011 }, + {1750,1751,1811 ,745,773,777 }, {1511,4377,967 ,4921,3090,3668 }, + {4935,4936,4973 ,5012,5013,5014 }, {3261,6554,4190 ,5015,3647,3439 }, + {8081,8078,8093 ,1445,1744,2288 }, {4604,4603,4562 ,5016,5017,5018 }, + {811,718,4954 ,557,2407,558 }, {4804,4833,4822 ,4867,4982,4984 }, + {4974,4996,4995 ,5019,5020,5021 }, {1264,1252,3929 ,5022,5023,5024 }, + {8091,8101,8099 ,2167,383,2867 }, {4817,1771,1834 ,2368,2367,2422 }, + {5350,6529,6507 ,1526,5025,2680 }, {4848,4886,417 ,4983,4720,5009 }, + {618,717,677 ,2391,2408,2296 }, {2301,8107,872 ,2132,658,2962 }, + {4749,4810,4785 ,5026,5027,5028 }, {4809,4855,4782 ,4202,4201,5029 }, + {4656,4715,4681 ,5002,1029,4958 }, {4686,4492,4773 ,5030,5031,4865 }, + {7593,7620,7645 ,5032,5033,5034 }, {4683,4647,4643 ,5035,5036,5037 }, + {5049,1928,4859 ,5038,1282,5039 }, {6942,6943,6962 ,521,520,3260 }, + {4871,1834,1870 ,5040,2422,4868 }, {4907,4935,4934 ,2209,5012,5041 }, + {8064,2341,1333 ,3141,845,849 }, {4954,720,812 ,558,2475,559 }, + {8089,2746,8139 ,254,1517,1923 }, {5063,5064,5062 ,5042,5043,5044 }, + {4607,4659,4658 ,5045,5046,5047 }, {4976,4998,4997 ,5048,4713,5049 }, + {4659,319,4658 ,5046,5050,5047 }, {319,367,4717 ,5050,1370,1449 }, + {4658,319,4717 ,5047,5050,1449 }, {8071,8144,3264 ,738,2469,2468 }, + {8127,8142,4106 ,2970,1743,1742 }, {4690,4721,383 ,5051,5052,2087 }, + {4907,576,674 ,2209,2208,2271 }, {717,4938,677 ,2408,5053,2296 }, + {2476,1283,2431 ,2441,2978,2977 }, {5966,1455,4987 ,5054,1917,1491 }, + {1346,2464,1697 ,484,3299,69 }, {1610,1676,749 ,2330,2343,2331 }, + {4934,4972,851 ,5041,2158,2155 }, {1610,1602,4855 ,2330,2329,4201 }, + {6755,6792,6738 ,5055,5056,5057 }, {1536,1581,1508 ,2080,2030,2029 }, + {4951,717,4977 ,5058,2408,4711 }, {6390,6311,6316 ,3868,1097,4435 }, + {565,4869,564 ,1614,5059,3529 }, {4890,4889,564 ,4594,4615,3529 }, + {4869,4890,564 ,5059,4594,3529 }, {417,137,4836 ,5009,4980,4979 }, + {1077,1030,1078 ,2694,1619,5060 }, {4286,3528,4342 ,4274,5061,4950 }, + {361,4751,417 ,4994,5007,5009 }, {5098,5097,5065 ,5062,5063,5064 }, + {1870,4099,4871 ,4868,4533,5040 }, {4918,4967,4966 ,4595,5065,4646 }, + {4931,4964,937 ,5066,783,5067 }, {4930,4931,937 ,5068,5066,5067 }, + {1320,4667,4618 ,2356,5069,5070 }, {2271,6309,6308 ,5071,3690,3474 }, + {4966,4967,978 ,4646,5065,1620 }, {4433,4356,4404 ,4591,155,5072 }, + {1831,1830,1768 ,2221,2110,2109 }, {7185,7184,7159 ,5073,5074,5075 }, + {4795,4432,4433 ,3710,156,4591 }, {1879,1928,5049 ,1283,1282,5038 }, + {4972,4994,949 ,2158,5076,2133 }, {468,469,505 ,1369,988,986 }, + {1024,4686,4647 ,5077,5030,5036 }, {8105,8090,3006 ,3791,412,414 }, + {4967,4991,978 ,5065,1621,1620 }, {446,640,4959 ,501,5078,5079 }, + {7565,7620,7593 ,5080,5033,5032 }, {4951,4977,4976 ,5058,4711,5048 }, + {3609,6159,6144 ,5081,5082,5083 }, {761,845,760 ,1826,1828,3106 }, + {6484,2258,5455 ,685,1198,686 }, {561,4871,4099 ,2379,5040,4533 }, + {4018,2271,6308 ,5084,5071,3474 }, {2808,6606,2454 ,522,5085,1345 }, + {567,568,663 ,1684,1723,4773 }, {2089,2090,2619 ,1325,1324,3035 }, + {7253,7254,6346 ,1855,450,4883 }, {5949,404,5967 ,5086,761,1352 }, + {1164,6386,1298 ,5087,4085,5088 }, {1635,1636,1702 ,989,1036,1039 }, + {5094,4683,1365 ,5089,5035,2085 }, {1699,1752,1751 ,772,946,773 }, + {4615,1566,1635 ,5090,1037,989 }, {1494,4609,1973 ,1083,5091,551 }, + {5017,5292,4144 ,5092,5093,4761 }, {1442,4615,1635 ,990,5090,989 }, + {6322,6897,2249 ,3531,54,3751 }, {4536,4820,4712 ,89,2399,3816 }, + {4452,4536,4498 ,2398,89,88 }, {4989,307,4986 ,5094,5095,5096 }, + {4606,4607,4658 ,5097,5045,5047 }, {2067,2031,2005 ,1592,1560,1593 }, + {1676,4817,4895 ,2343,2368,4973 }, {4782,4667,1397 ,5029,5069,2358 }, + {1028,406,2958 ,1373,2566,1374 }, {6644,5056,4950 ,5098,1300,1302 }, + {1365,1282,1317 ,2085,2097,5099 }, {3534,6686,8121 ,942,750,3466 }, + {1169,4607,4606 ,5100,5045,5097 }, {4647,4773,4661 ,5036,4865,5101 }, + {2015,1994,1995 ,759,5102,757 }, {7287,7310,7286 ,3459,4283,3460 }, + {951,5033,4999 ,2394,5103,4712 }, {2155,875,6353 ,580,579,749 }, + {4874,616,673 ,5000,4999,2210 }, {1602,749,4723 ,2329,2331,4972 }, + {1365,4757,4666 ,2085,5104,5105 }, {7613,7612,7551 ,5106,5107,5108 }, + {2650,8140,3981 ,914,2269,915 }, {8120,6897,3286 ,906,54,56 }, + {4594,4027,5028 ,688,3470,5109 }, {4844,4848,4833 ,4866,4983,4982 }, + {4594,146,4027 ,688,504,3470 }, {4996,4997,5030 ,5020,5049,2134 }, + {5030,5047,5061 ,2134,4996,2135 }, {4594,5028,3249 ,688,5109,5110 }, + {792,147,146 ,95,3469,504 }, {4688,4689,4749 ,4997,5111,5026 }, + {128,4594,4905 ,687,688,5112 }, {5036,128,4905 ,399,687,5112 }, + {1202,366,1040 ,339,4469,1476 }, {4615,1442,4806 ,5090,990,5113 }, + {5127,4801,124 ,5114,5115,2206 }, {128,5036,4905 ,687,399,5112 }, + {3243,128,4905 ,3919,687,5112 }, {616,574,575 ,4999,2181,2180 }, + {7556,6760,4636 ,5116,5117,5118 }, {4997,4998,5018 ,5049,4713,5119 }, + {1105,1202,1040 ,2049,339,1476 }, {7372,7371,7310 ,4373,3748,4283 }, + {1649,1581,1582 ,2031,2030,2081 }, {7204,7241,7203 ,5120,4465,4366 }, + {5910,5838,6343 ,5121,3987,5122 }, {1442,4898,4806 ,990,5123,5113 }, + {1099,3243,4905 ,4350,3919,5112 }, {695,128,3243 ,731,687,3919 }, + {7572,7599,7571 ,5124,5125,5126 }, {4734,4661,4902 ,5127,5101,5128 }, + {135,4637,274 ,2653,4986,1819 }, {4817,1834,4871 ,2368,2422,5040 }, + {7823,7874,7847 ,345,344,3521 }, {7452,7509,7451 ,1875,1874,3963 }, + {137,4762,4843 ,4980,5129,5130 }, {4615,4806,4670 ,5090,5113,5131 }, + {1659,4615,4670 ,5132,5090,5131 }, {8103,1724,8077 ,1291,3880,2078 }, + {5091,5090,7052 ,5133,4929,708 }, {126,197,4659 ,1394,1393,5046 }, + {4607,126,4659 ,5045,1394,5046 }, {4659,197,319 ,5046,1393,5050 }, + {2006,1307,4351 ,1181,4930,4606 }, {4751,4821,4880 ,5007,5134,5008 }, + {4870,4871,5016 ,5135,5040,5010 }, {5061,5093,1317 ,2135,5136,5099 }, + {4838,4808,311 ,245,5137,246 }, {4880,4762,137 ,5008,5129,4980 }, + {760,4970,4920 ,3106,3073,5138 }, {5021,4644,1969 ,2383,2425,2369 }, + {856,811,855 ,5139,557,2392 }, {1768,4735,1831 ,2109,5140,2221 }, + {4869,565,566 ,5059,1614,1613 }, {4843,1718,4783 ,5130,2108,4981 }, + {662,4890,4869 ,5141,4594,5059 }, {4683,4643,4757 ,5035,5037,5104 }, + {324,325,383 ,2090,2088,2087 }, {917,4915,4929 ,4016,64,5142 }, + {4667,4782,4492 ,5069,5029,5031 }, {753,3243,4845 ,4749,3919,3921 }, + {753,462,3243 ,4749,4847,3919 }, {753,695,462 ,4749,731,4847 }, + {280,326,325 ,2175,2152,2088 }, {4976,4977,4998 ,5048,4711,4713 }, + {4566,4571,1168 ,5143,1342,1098 }, {2583,685,3902 ,2672,2674,4437 }, + {5187,4845,1111 ,5144,3921,3920 }, {6689,3285,2405 ,3065,3064,4165 }, + {1790,4183,240 ,4738,1572,1571 }, {1040,1104,1105 ,1476,1473,2049 }, + {4985,4566,1168 ,2703,5143,1098 }, {4643,4818,4734 ,5037,5145,5127 }, + {7815,5439,5440 ,5146,4157,5147 }, {5093,1365,1317 ,5136,2085,5099 }, + {1177,1130,5061 ,5148,2136,2135 }, {4994,5030,5029 ,5076,2134,2159 }, + {4751,4862,4821 ,5007,5149,5134 }, {1517,4780,4846 ,600,5150,4237 }, + {4989,972,4571 ,5094,3277,1342 }, {4984,753,4845 ,5151,4749,3921 }, + {4750,4786,4749 ,5152,5153,5026 }, {4818,4661,4734 ,5145,5101,5127 }, + {4638,4663,4637 ,5154,5155,4986 }, {5750,5739,5775 ,5156,5157,5158 }, + {6463,4989,4566 ,5159,5094,5143 }, {6704,6736,6703 ,5160,1622,5161 }, + {5629,5938,6605 ,5162,5163,5164 }, {383,4811,4786 ,2087,5165,5153 }, + {4690,4689,4639 ,5051,5111,5166 }, {4740,561,1427 ,2426,2379,2378 }, + {4566,4989,4571 ,5143,5094,1342 }, {1024,5071,4686 ,5077,5167,5030 }, + {4997,5031,5030 ,5049,4995,2134 }, {5031,5032,5064 ,4995,5168,5043 }, + {557,82,4801 ,3724,138,5115 }, {7693,3824,8133 ,724,3462,2131 }, + {4824,4812,1602 ,4719,5169,2329 }, {760,4920,663 ,3106,5138,4773 }, + {4973,4974,4995 ,5014,5019,5021 }, {4690,4749,4689 ,5051,5026,5111 }, + {4751,5015,4862 ,5007,5170,5149 }, {1282,1176,1317 ,2097,2096,5099 }, + {4855,4812,4782 ,4201,5169,5029 }, {4850,1931,1903 ,5171,4736,406 }, + {8127,8084,8142 ,2970,1139,1743 }, {2527,2487,3426 ,2637,4427,4438 }, + {4605,4606,4658 ,5006,5097,5047 }, {3762,5044,5442 ,5172,5173,5174 }, + {674,675,4936 ,2271,2294,5013 }, {4331,4984,4845 ,4645,5151,3921 }, + {5083,4331,4845 ,5175,4645,3921 }, {4331,753,4984 ,4645,4749,5151 }, + {5246,6039,5227 ,5176,2728,4097 }, {4862,4850,1831 ,5149,5171,2221 }, + {417,4880,137 ,5009,5008,4980 }, {4811,514,4858 ,5165,2154,5177 }, + {4929,4930,4963 ,5142,5068,5178 }, {5844,1119,1028 ,5179,5180,1373 }, + {7064,7065,2073 ,5181,5182,5183 }, {5056,3319,5005 ,1300,1259,1301 }, + {6570,4554,1263 ,1252,1307,1199 }, {4870,1931,4862 ,5135,4736,5149 }, + {4643,4734,1508 ,5037,5127,2029 }, {1884,1885,1929 ,3567,778,899 }, + {855,951,4977 ,2392,2394,4711 }, {4986,1073,972 ,5096,3741,3277 }, + {1169,4606,4605 ,5100,5097,5006 }, {4689,4688,4638 ,5111,4997,5154 }, + {1252,5999,3929 ,5023,5184,5024 }, {4989,4986,972 ,5094,5096,3277 }, + {4889,4933,4932 ,4615,4596,4580 }, {4688,4687,4638 ,4997,4985,5154 }, + {5096,5097,4912 ,5185,5063,5186 }, {5071,4912,4686 ,5167,5186,5030 }, + {564,4889,4868 ,3529,4615,3969 }, {675,677,4937 ,2294,2296,5187 }, + {5033,5066,5065 ,5103,5188,5064 }, {5034,5033,951 ,2396,5103,2394 }, + {897,898,952 ,2393,2411,2395 }, {1672,1650,1582 ,2083,2082,2081 }, + {4845,5052,5083 ,3921,5189,5175 }, {4118,753,4331 ,4699,4749,4645 }, + {5702,4743,5701 ,3630,3629,3938 }, {4812,4824,4492 ,5169,4719,5031 }, + {673,4934,767 ,2210,5041,2093 }, {1536,4734,1582 ,2080,5127,2081 }, + {17,961,4959 ,5190,500,5079 }, {1659,4670,4685 ,5132,5131,4894 }, + {4644,5021,5019 ,2425,2383,2384 }, {4783,1672,4902 ,4981,2083,5128 }, + {7646,7675,7645 ,5191,5192,5034 }, {1381,1169,4605 ,5193,5100,5006 }, + {5061,1317,1177 ,2135,5099,5148 }, {4933,4966,4965 ,4596,4646,784 }, + {206,3347,1804 ,3932,2098,2902 }, {4888,4889,4932 ,3997,4615,4580 }, + {4932,4933,4965 ,4580,4596,784 }, {4889,4888,4868 ,4615,3997,3969 }, + {4986,5441,42 ,5096,5194,5195 }, {1285,4618,5097 ,1944,5070,5063 }, + {4716,4717,468 ,1030,1449,1369 }, {4997,5018,5031 ,5049,5119,4995 }, + {1073,4986,42 ,3741,5096,5195 }, {1297,4752,1488 ,4048,5196,5197 }, + {280,325,279 ,2175,2088,2176 }, {4817,1676,1720 ,2368,2343,2366 }, + {1213,1197,4983 ,3700,478,480 }, {4331,5083,5052 ,4645,5175,5189 }, + {8132,8141,3900 ,413,3295,884 }, {943,1034,1081 ,1807,1756,3745 }, + {1931,1967,1903 ,4736,404,406 }, {4563,126,4607 ,3024,1394,5045 }, + {1169,4563,4607 ,5100,3024,5045 }, {4969,4970,4992 ,4977,3073,4978 }, + {6686,8070,959 ,750,941,1358 }, {4898,4867,4806 ,5123,5198,5113 }, + {1365,4683,4757 ,2085,5035,5104 }, {3482,4021,8106 ,2494,1712,2438 }, + {977,976,4965 ,3931,782,784 }, {4618,4667,4912 ,5070,5069,5186 }, + {4695,4725,4772 ,5199,5200,5201 }, {4935,4973,4972 ,5012,5014,2158 }, + {509,572,475 ,4993,1947,1884 }, {4938,4976,4975 ,5053,5048,5202 }, + {4848,417,4836 ,4983,5009,4979 }, {4749,4811,4810 ,5026,5165,5027 }, + {4974,4975,4996 ,5019,5202,5020 }, {5033,5065,5064 ,5103,5064,5043 }, + {5032,5033,5064 ,5168,5103,5043 }, {3316,2810,3271 ,2149,1728,1727 }, + {7512,7570,7536 ,5203,5204,5205 }, {4018,6308,6307 ,5084,3474,5206 }, + {1381,4604,4562 ,5193,5016,5018 }, {375,374,320 ,1883,1821,1871 }, + {3824,7714,8131 ,3462,4468,2307 }, {4661,4822,4902 ,5101,4984,5128 }, + {4972,4973,4994 ,2158,5014,5076 }, {4973,4995,4994 ,5014,5021,5076 }, + {7693,8133,8071 ,724,2131,738 }, {4811,4858,4840 ,5165,5177,5207 }, + {2322,281,4692 ,3497,2227,2177 }, {4118,4331,513 ,4699,4645,377 }, + {4810,509,475 ,5027,4993,1884 }, {4640,4691,4690 ,5208,2089,5051 }, + {442,357,589 ,673,672,671 }, {1442,1699,4898 ,990,772,5123 }, + {2219,5000,4982 ,177,286,364 }, {4611,4634,4754 ,5209,5210,5211 }, + {6299,5012,1989 ,760,5212,3016 }, {4782,1469,4809 ,5029,2361,4202 }, + {4619,39,344 ,5213,529,562 }, {4754,4959,4903 ,5211,5079,5214 }, + {4903,4959,640 ,5214,5079,5078 }, {4780,1291,4819 ,5150,5215,5216 }, + {4684,5082,6603 ,5217,5218,3985 }, {5166,5205,5227 ,3427,5219,4097 }, + {1154,1922,4399 ,4545,4432,4546 }, {4858,514,574 ,5177,2154,2181 }, + {3243,462,695 ,3919,4847,731 }, {1635,1702,1699 ,989,1039,772 }, + {4780,1517,5079 ,5150,600,5220 }, {5079,1517,833 ,5220,600,489 }, + {2464,1346,2465 ,3299,484,3181 }, {662,4919,4918 ,5141,5221,4595 }, + {4890,662,4918 ,4594,5141,4595 }, {4919,4968,4967 ,5221,5222,5065 }, + {4918,4919,4967 ,4595,5221,5065 }, {4968,1030,4991 ,5222,1619,1621 }, + {4840,574,4874 ,5207,2181,5000 }, {1136,1042,1043 ,2412,2409,2478 }, + {4967,4968,4991 ,5065,5222,1621 }, {4806,4867,4621 ,5113,5198,5223 }, + {4867,797,4621 ,5198,5224,5223 }, {4749,4785,4748 ,5026,5028,4998 }, + {273,135,274 ,1749,2653,1819 }, {559,4958,4955 ,5225,5226,5227 }, + {4693,559,4955 ,5228,5225,5227 }, {5012,6299,6417 ,5212,760,762 }, + {4840,4858,574 ,5207,5177,2181 }, {4725,4726,4729 ,5200,5229,5230 }, + {4772,4725,4729 ,5201,5200,5230 }, {1291,4772,4819 ,5215,5201,5216 }, + {4812,4855,1602 ,5169,4201,2329 }, {307,6659,4986 ,5095,3577,5096 }, + {17,438,961 ,5190,498,500 }, {4492,4824,4844 ,5031,4719,4866 }, + {1971,1427,1948 ,2520,2378,2543 }, {4898,1698,4867 ,5123,747,5198 }, + {1698,5084,4867 ,747,5231,5198 }, {1810,4698,4894 ,779,1578,746 }, + {1884,1950,1995 ,3567,786,757 }, {4609,4615,1659 ,5091,5090,5132 }, + {2258,2376,1263 ,1198,1251,1199 }, {5093,5094,1365 ,5136,5089,2085 }, + {4785,4810,4748 ,5028,5027,4998 }, {4750,4749,4690 ,5152,5026,5051 }, + {1081,5046,1078 ,3745,5232,5060 }, {4938,4951,4976 ,5053,5058,5048 }, + {5050,1922,1154 ,5233,4432,4545 }, {936,4963,937 ,5234,5178,5067 }, + {4748,375,4687 ,4998,1883,4985 }, {2212,2271,5001 ,476,5071,477 }, + {5944,3562,6037 ,4754,4753,5235 }, {1041,5067,5033 ,2410,1945,5103 }, + {4560,4600,4559 ,5236,4360,5237 }, {579,677,676 ,2324,2296,2295 }, + {7543,7605,7542 ,5238,5239,5240 }, {5412,5418,5411 ,360,3471,4284 }, + {4819,4772,4701 ,5216,5201,5241 }, {2808,1920,6606 ,522,581,5085 }, + {6699,6751,6750 ,630,5242,631 }, {5442,4672,3762 ,5174,2682,5172 }, + {5004,4817,4871 ,5243,2368,5040 }, {197,367,319 ,1393,1370,5050 }, + {5096,4912,1024 ,5185,5186,5077 }, {4723,4895,4751 ,4972,4973,5007 }, + {5016,1931,4870 ,5010,4736,5135 }, {4611,17,4634 ,5209,5190,5210 }, + {4910,4923,4611 ,5244,5245,5209 }, {4923,4945,4611 ,5245,5246,5209 }, + {975,937,976 ,4266,5067,782 }, {833,1517,491 ,489,600,487 }, + {1081,1078,1030 ,3745,5060,1619 }, {4843,4735,1718 ,5130,5140,2108 }, + {4239,4884,85 ,2254,2253,2504 }, {505,5308,5290 ,986,3528,5247 }, + {2322,4614,4590 ,3497,3109,1490 }, {2277,2322,4590 ,3522,3497,1490 }, + {4641,4640,4612 ,5248,5208,5249 }, {4176,4198,4126 ,4703,4969,124 }, + {357,1198,589 ,672,608,671 }, {6570,2376,6562 ,1252,1251,1306 }, + {4670,4621,5073 ,5131,5223,5250 }, {4761,640,4847 ,5251,5078,5252 }, + {5709,5710,6957 ,5253,5254,4315 }, {2224,406,2206 ,2660,2566,176 }, + {2219,4982,4978 ,177,364,178 }, {2814,4922,4636 ,5255,5256,5118 }, + {5067,5066,5033 ,1945,5188,5103 }, {4772,4729,4701 ,5201,5230,5241 }, + {2163,4586,158 ,243,5257,5258 }, {4692,281,280 ,2177,2227,2175 }, + {4727,4667,4492 ,5259,5069,5031 }, {4746,1264,5078 ,5260,5022,5261 }, + {1285,5098,5066 ,1944,5062,5188 }, {4686,4727,4492 ,5030,5259,5031 }, + {951,4999,4977 ,2394,4712,4711 }, {4932,4965,4964 ,4580,784,783 }, + {4948,4962,935 ,5262,5263,5264 }, {1541,1610,4855 ,2363,2330,4201 }, + {2252,3247,144 ,590,1494,853 }, {6342,1749,1492 ,5265,555,1585 }, + {1028,1119,406 ,1373,5180,2566 }, {4923,638,4945 ,5245,495,5246 }, + {7603,7683,7655 ,5266,5267,5268 }, {4647,4686,4773 ,5036,5030,4865 }, + {898,897,811 ,2411,2393,557 }, {4773,4492,4844 ,4865,5031,4866 }, + {4850,4862,1931 ,5171,5149,4736 }, {4661,4804,4822 ,5101,4867,4984 }, + {4762,4735,4843 ,5129,5140,5130 }, {6751,6787,6786 ,5242,5269,4139 }, + {4640,4641,4691 ,5208,5248,2089 }, {281,326,280 ,2227,2152,2175 }, + {2376,3898,6562 ,1251,1400,1306 }, {6349,6358,6357 ,705,5270,5271 }, + {1469,1541,4855 ,2361,2363,4201 }, {4792,4726,5002 ,5272,5229,5273 }, + {5047,5062,5061 ,4996,5044,2135 }, {2093,603,1551 ,553,5274,5275 }, + {897,855,811 ,2393,2392,557 }, {4620,4619,4823 ,5276,5213,5277 }, + {4621,797,4925 ,5223,5224,5278 }, {4749,4748,4688 ,5026,4998,4997 }, + {1990,5520,5059 ,1305,1304,1253 }, {7105,7104,7092 ,5279,5280,3968 }, + {5067,1285,5066 ,1945,1944,5188 }, {4821,4862,4762 ,5134,5149,5129 }, + {4869,566,662 ,5059,1613,5141 }, {4879,4910,4941 ,5281,5244,5282 }, + {4910,4611,4941 ,5244,5209,5282 }, {4726,4725,4700 ,5229,5200,5283 }, + {4867,4789,797 ,5198,5284,5224 }, {4894,4698,4532 ,746,1578,5285 }, + {4945,638,4611 ,5246,495,5209 }, {4844,4833,4804 ,4866,4982,4867 }, + {1566,4615,4609 ,1037,5090,5091 }, {4934,4935,4972 ,5041,5012,2158 }, + {1717,1672,1718 ,2033,2083,2108 }, {4293,6577,8065 ,2732,97,96 }, + {612,4893,707 ,5011,5286,2091 }, {4650,4649,4595 ,5287,5288,5289 }, + {4596,4650,4595 ,1629,5287,5289 }, {4920,4969,4968 ,5138,4977,5222 }, + {4703,4702,4649 ,5290,5291,5288 }, {4690,383,4750 ,5051,2087,5152 }, + {4992,4993,1030 ,4978,5292,1619 }, {7311,7372,7310 ,4334,4373,4283 }, + {4685,4670,4906 ,4894,5131,4895 }, {4787,4729,4792 ,5293,5230,5272 }, + {4608,4925,4585 ,5294,5278,3955 }, {4726,4700,5002 ,5229,5283,5273 }, + {4925,4593,4585 ,5278,5295,3955 }, {4592,4927,4732 ,5296,5297,5298 }, + {4925,4732,4593 ,5278,5298,5295 }, {4761,4619,4620 ,5251,5213,5276 }, + {4789,4592,4732 ,5284,5296,5298 }, {6308,6711,6307 ,3474,3473,5206 }, + {7185,7220,7184 ,5073,4367,5074 }, {833,4695,4772 ,489,5199,5201 }, + {1025,6714,6389 ,5299,5300,5301 }, {4806,4621,4670 ,5113,5223,5131 }, + {5950,8066,8108 ,2873,960,2287 }, {5866,3311,5864 ,5302,5303,5304 }, + {4609,1659,1973 ,5091,5132,551 }, {4611,638,17 ,5209,495,5190 }, + {1494,1566,4609 ,1083,1037,5091 }, {4667,4727,5068 ,5069,5259,5305 }, + {7524,4596,4595 ,1630,1629,5289 }, {4674,4649,4650 ,5306,5288,5287 }, + {4674,4703,4649 ,5306,5290,5288 }, {4848,4783,4822 ,4983,4981,4984 }, + {5062,5064,5094 ,5044,5043,5089 }, {4930,937,4963 ,5068,5067,5178 }, + {3621,4980,158 ,625,241,5258 }, {5082,6744,6603 ,5218,3986,3985 }, + {4670,5073,4608 ,5131,5250,5294 }, {4729,4787,5025 ,5230,5293,5307 }, + {4584,7818,4557 ,5308,5309,5310 }, {3783,4639,4638 ,5311,5166,5154 }, + {616,575,673 ,4999,2180,2210 }, {797,4789,4732 ,5224,5284,5298 }, + {4678,4903,4794 ,5312,5214,5313 }, {4789,4927,4592 ,5284,5297,5296 }, + {4819,4701,4573 ,5216,5241,5314 }, {5068,4727,4686 ,5305,5259,5030 }, + {4800,4700,4725 ,5315,5283,5200 }, {4695,4800,4725 ,5199,5315,5200 }, + {6028,6040,5927 ,5316,5317,5318 }, {4879,4923,4910 ,5281,5245,5244 }, + {6523,5349,5326 ,5319,5320,3714 }, {4800,4923,4879 ,5315,5245,5281 }, + {5090,3801,7052 ,4929,4974,708 }, {4698,559,4532 ,1578,5225,5285 }, + {1698,4894,4789 ,747,746,5284 }, {2154,5474,2273 ,5321,4243,5322 }, + {7698,7746,7697 ,333,332,651 }, {717,4951,4938 ,2408,5058,5053 }, + {4651,4650,4596 ,5323,5287,1629 }, {4576,4651,4596 ,1628,5323,1629 }, + {1519,2544,601 ,2450,2451,3905 }, {4650,4703,4674 ,5287,5290,5306 }, + {674,4936,4935 ,2271,5013,5012 }, {4848,4836,4783 ,4983,4979,4981 }, + {8071,8133,8144 ,738,2131,2469 }, {4663,4687,4637 ,5155,4985,4986 }, + {4643,1508,4666 ,5037,2029,5105 }, {5672,7009,5694 ,5324,5325,5326 }, + {4670,4608,4906 ,5131,5294,4895 }, {4754,4633,4694 ,5211,5327,5328 }, + {4701,4729,5025 ,5241,5230,5307 }, {4633,4632,4694 ,5327,5329,5328 }, + {8091,8099,8083 ,2167,2867,2103 }, {4250,4404,166 ,4161,5072,3729 }, + {4837,4834,4720 ,3990,4304,5330 }, {3600,3578,3623 ,5331,4205,4206 }, + {4929,4963,4948 ,5142,5178,5262 }, {4963,936,4962 ,5178,5234,5263 }, + {917,4929,4948 ,4016,5142,5262 }, {4963,4962,4948 ,5178,5263,5262 }, + {4283,4086,4774 ,1678,3723,5332 }, {4700,4800,4879 ,5283,5315,5281 }, + {682,346,4923 ,448,449,5245 }, {797,4732,4925 ,5224,5298,5278 }, + {5647,6890,6852 ,5333,5334,5335 }, {4958,1995,1994 ,5226,757,5102 }, + {5084,1698,4789 ,5231,747,5284 }, {4894,4532,4789 ,746,5285,5284 }, + {5084,4789,4867 ,5231,5284,5198 }, {2391,328,327 ,3544,2289,2226 }, + {1317,1176,1177 ,5099,2096,5148 }, {5061,5094,5093 ,2135,5089,5136 }, + {137,4843,4783 ,4980,5130,4981 }, {1831,1902,1866 ,2221,2193,2191 }, + {4704,4703,4650 ,4788,5290,5287 }, {4651,4704,4650 ,5323,4788,5287 }, + {665,760,664 ,1754,3106,1755 }, {4936,4974,4973 ,5013,5019,5014 }, + {4907,674,4935 ,2209,2271,5012 }, {4587,4637,135 ,774,4986,2653 }, + {4879,4941,4790 ,5281,5282,5336 }, {4995,4996,5030 ,5021,5020,2134 }, + {4729,4726,4792 ,5230,5229,5272 }, {4810,475,4748 ,5027,1884,4998 }, + {4998,4999,5033 ,4713,4712,5103 }, {4620,4823,343 ,5276,5277,4543 }, + {8079,8104,3286 ,55,1343,56 }, {6613,6650,6649 ,3746,3833,3759 }, + {4880,4821,4762 ,5008,5134,5129 }, {2279,2251,7042 ,5337,3676,5338 }, + {5061,5062,5094 ,2135,5044,5089 }, {1120,6342,1492 ,5339,5265,1585 }, + {4824,4886,4848 ,4719,4720,4983 }, {876,1167,4017 ,5340,5341,5342 }, + {1330,615,654 ,53,766,93 }, {4558,4598,4584 ,4209,5343,5308 }, + {4800,682,4923 ,5315,448,5245 }, {4948,935,4947 ,5262,5264,5344 }, + {82,557,284 ,138,3724,139 }, {4879,4790,4700 ,5281,5336,5283 }, + {4789,4532,838 ,5284,5285,5345 }, {4955,4958,1994 ,5227,5226,5102 }, + {1210,4183,5017 ,4739,1572,5092 }, {4675,4704,4651 ,5346,4788,5323 }, + {4590,3693,2277 ,1490,5347,3522 }, {833,1291,4780 ,489,5215,5150 }, + {1119,2408,406 ,5180,1399,2566 }, {4912,5071,1024 ,5186,5167,5077 }, + {683,1634,396 ,621,768,153 }, {718,620,719 ,2407,2474,2476 }, + {2908,2851,1249 ,3052,3051,2876 }, {4796,1937,4291 ,4756,59,58 }, + {5089,5091,7052 ,3442,5133,708 }, {4621,4925,4608 ,5223,5278,5294 }, + {5073,4621,4608 ,5250,5223,5294 }, {5396,5395,6506 ,3548,5348,3549 }, + {4634,17,4754 ,5210,5190,5211 }, {402,401,7712 ,5349,1387,5350 }, + {7640,4577,4575 ,5351,5352,3128 }, {682,4800,4695 ,448,5315,5199 }, + {4902,1672,1582 ,5128,2083,2081 }, {5326,6524,6523 ,3714,3704,5319 }, + {4927,4789,838 ,5297,5284,5345 }, {4611,4694,4941 ,5209,5328,5282 }, + {559,4698,4958 ,5225,1578,5226 }, {4698,1884,4958 ,1578,3567,5226 }, + {4958,1884,1995 ,5226,3567,757 }, {833,4772,1291 ,489,5201,5215 }, + {1968,1967,1931 ,2309,404,4736 }, {4577,4578,4576 ,5352,5353,1628 }, + {4578,4624,4651 ,5353,5354,5323 }, {4576,4578,4651 ,1628,5353,5323 }, + {4651,4624,4675 ,5323,5354,5346 }, {4579,4578,7641 ,5355,5353,5356 }, + {4757,4643,4666 ,5104,5037,5105 }, {4639,4689,4638 ,5166,5111,5154 }, + {5081,6605,5938 ,1258,5164,5163 }, {4874,673,707 ,5000,2210,2091 }, + {4941,4694,4790 ,5282,5328,5336 }, {6711,6735,7031 ,3473,3472,5357 }, + {4711,4771,5837 ,5358,3622,3621 }, {4926,4796,4291 ,3897,4756,58 }, + {4601,4654,4653 ,5359,4348,4347 }, {4803,4777,4778 ,4776,3917,3916 }, + {3861,3566,2746 ,1147,1146,1517 }, {1602,4723,361 ,2329,4972,4994 }, + {798,4913,5413 ,78,4185,76 }, {4735,1768,1718 ,5140,2109,2108 }, + {5771,8099,8086 ,2868,2867,4823 }, {2000,4740,1971 ,2428,2426,2520 }, + {4562,4602,4561 ,5018,5360,5361 }, {943,4993,4970 ,1807,5292,3073 }, + {943,1081,4993 ,1807,3745,5292 }, {4600,4599,4558 ,4360,4210,4209 }, + {4559,4600,4558 ,5237,4360,4209 }, {559,4693,267 ,5225,5228,5362 }, + {1091,160,4834 ,4597,4295,4304 }, {4638,4687,4663 ,5154,4985,5155 }, + {4624,4704,4675 ,5354,4788,5346 }, {5082,4684,4979 ,5218,5217,5363 }, + {320,4687,375 ,1871,4985,1883 }, {4656,4681,4655 ,5002,4958,4957 }, + {4923,346,638 ,5245,449,495 }, {4667,5068,4912 ,5069,5305,5186 }, + {4829,4852,4865 ,4626,5364,4229 }, {5043,4144,5985 ,4762,4761,5365 }, + {974,975,7335 ,5366,4266,4265 }, {5837,4713,4711 ,3621,4015,5358 }, + {1699,1698,4898 ,772,747,5123 }, {4754,17,4959 ,5211,5190,5079 }, + {4656,4655,4602 ,5002,4957,5360 }, {4604,4656,4602 ,5016,5002,5360 }, + {4603,4604,4602 ,5017,5016,5360 }, {4602,4601,4560 ,5360,5359,5236 }, + {4573,4701,2733 ,5314,5241,5367 }, {4694,4611,4754 ,5328,5209,5211 }, + {4903,640,4761 ,5214,5078,5251 }, {3788,4680,3672 ,4410,5368,4277 }, + {4834,160,1091 ,4304,4295,4597 }, {4720,4834,1091 ,5330,4304,4597 }, + {4680,4720,1091 ,5368,5330,4597 }, {6141,2072,3725 ,2386,1812,3971 }, + {883,146,4594 ,119,504,688 }, {1176,1130,1177 ,2096,2136,5148 }, + {2315,2382,4563 ,3068,3025,3024 }, {2407,1073,2156 ,4001,3741,3631 }, + {4624,4705,4704 ,5354,5369,4788 }, {4705,4706,4704 ,5369,4786,4788 }, + {7186,7185,7168 ,5370,5073,5371 }, {124,196,1423 ,2206,152,151 }, + {3954,8065,8062 ,3463,96,3272 }, {4603,4602,4562 ,5017,5360,5018 }, + {1968,1931,5019 ,2309,4736,2384 }, {640,446,4847 ,5078,501,5252 }, + {678,682,4695 ,446,448,5199 }, {3786,22,1418 ,4074,4066,4619 }, + {4921,4979,2257 ,2079,5363,554 }, {1941,3476,3283 ,1034,3838,262 }, + {4787,4739,4815 ,5293,5372,5373 }, {5025,4787,4815 ,5307,5293,5373 }, + {5768,4713,5804 ,933,4015,3623 }, {4449,4564,4805 ,5374,5375,5376 }, + {4711,4449,4805 ,5358,5374,5376 }, {4564,4646,4645 ,5375,5377,5378 }, + {4805,4564,4645 ,5376,5375,5378 }, {4646,4926,4645 ,5377,3897,5378 }, + {1724,1249,2851 ,3880,2876,3051 }, {4912,5068,4686 ,5186,5305,5030 }, + {976,937,4964 ,782,5067,783 }, {4655,4654,4602 ,4957,4348,5360 }, + {4561,4602,4560 ,5361,5360,5236 }, {2391,327,282 ,3544,2226,3498 }, + {4601,4653,4600 ,5359,4347,4360 }, {675,4937,4936 ,2294,5187,5013 }, + {5095,5096,1024 ,5379,5185,5077 }, {4823,4619,343 ,5277,5213,4543 }, + {1365,4666,1440 ,2085,5105,2063 }, {4619,344,343 ,5213,562,4543 }, + {4761,4847,4619 ,5251,5252,5213 }, {4794,4903,4761 ,5313,5214,5251 }, + {509,4873,572 ,4993,4992,1947 }, {4579,4624,4578 ,5355,5354,5353 }, + {4676,4706,4705 ,5380,4786,5369 }, {4624,4676,4705 ,5354,5380,5369 }, + {4569,4983,2808 ,1344,480,522 }, {2382,127,126 ,3025,1395,1394 }, + {1172,1081,1034 ,1758,3745,1756 }, {1672,1649,1650 ,2083,2031,2082 }, + {4834,5236,1091 ,4304,4301,4597 }, {4847,446,4619 ,5252,501,5213 }, + {446,39,4619 ,501,529,5213 }, {111,110,2962 ,1154,743,3494 }, + {4589,4638,4637 ,776,5154,4986 }, {4571,2162,6311 ,1342,3278,1097 }, + {4176,3784,4198 ,4703,2252,4969 }, {4701,4815,4956 ,5241,5373,5381 }, + {4696,4753,4713 ,5382,5383,4015 }, {5770,4696,4713 ,3949,5382,4015 }, + {4753,4449,4711 ,5383,5374,5358 }, {4713,4753,4711 ,4015,5383,5358 }, + {4939,4926,4646 ,5384,3897,5377 }, {4876,4796,4926 ,5385,4756,3897 }, + {4939,4876,4926 ,5384,5385,3897 }, {4323,4394,4796 ,4045,4044,4756 }, + {4876,4323,4796 ,5385,4045,4756 }, {3975,8064,2812 ,728,3141,1914 }, + {4916,4930,4929 ,2248,5068,5142 }, {4892,343,342 ,5386,4543,4542 }, + {4691,4721,4690 ,2089,5052,5051 }, {468,4803,4779 ,1369,4776,4729 }, + {4822,4783,4902 ,4984,4981,5128 }, {468,4779,4716 ,1369,4729,1030 }, + {5479,6741,6706 ,5387,5388,5389 }, {4601,4600,4560 ,5359,4360,5236 }, + {4994,4995,5030 ,5076,5021,2134 }, {4532,559,838 ,5285,5225,5345 }, + {4754,4903,4678 ,5211,5214,5312 }, {559,267,838 ,5225,5362,5345 }, + {4776,4680,3788 ,5390,5368,4410 }, {4452,4432,4795 ,2398,156,3710 }, + {7101,7128,7114 ,4073,5391,5392 }, {8049,2318,2317 ,5393,3185,3129 }, + {4625,4624,4579 ,5394,5354,5355 }, {4625,4626,4624 ,5394,5395,5354 }, + {4626,4676,4624 ,5395,5380,5354 }, {4998,5033,5032 ,4713,5103,5168 }, + {4717,4716,4657 ,1449,1030,5001 }, {8116,8071,5521 ,172,738,751 }, + {4612,278,4641 ,5249,1489,5248 }, {5031,5064,5063 ,4995,5043,5042 }, + {4721,4691,383 ,5052,2089,2087 }, {4639,4640,4690 ,5166,5208,5051 }, + {1073,42,2156 ,3741,5195,3631 }, {7029,7051,2341 ,548,547,845 }, + {6524,6572,6571 ,3704,3734,4146 }, {1902,1901,1866 ,2193,2192,2191 }, + {6989,5693,6947 ,4586,4075,4587 }, {6968,6967,6916 ,595,4629,596 }, + {6735,3192,2450 ,3472,3063,3444 }, {2733,4701,4956 ,5367,5241,5381 }, + {2407,2156,2258 ,4001,3631,1198 }, {4753,4854,4449 ,5383,5396,5374 }, + {4877,4876,4939 ,5397,5385,5384 }, {4940,4877,4939 ,5398,5397,5384 }, + {4752,4323,4876 ,5196,4045,5385 }, {4877,4752,4876 ,5397,5196,5385 }, + {717,855,4977 ,2408,2392,4711 }, {6711,7031,2450 ,3473,5357,3444 }, + {5520,1516,5059 ,1304,1254,1253 }, {4931,4930,4916 ,5066,5068,2248 }, + {5079,833,4780 ,5220,489,5150 }, {4980,2163,158 ,241,243,5258 }, + {5477,4672,5442 ,5399,2682,5174 }, {1307,801,4351 ,4930,4907,4606 }, + {4846,4780,3139 ,4237,5150,5400 }, {5659,2453,5658 ,3053,42,49 }, + {4633,4754,4678 ,5327,5211,5312 }, {4700,4790,5002 ,5283,5336,5273 }, + {5060,4720,4680 ,5401,5330,5368 }, {4776,5060,4680 ,5390,5401,5368 }, + {5060,4837,4720 ,5401,3990,5330 }, {4937,4975,4974 ,5187,5202,5019 }, + {4626,4625,4579 ,5395,5394,5355 }, {4626,4706,4676 ,5395,4786,5380 }, + {4612,4640,4639 ,5249,5208,5166 }, {4658,4717,4657 ,5047,1449,5001 }, + {677,4938,4937 ,2296,5053,5187 }, {3783,4612,4639 ,5311,5249,5166 }, + {4750,383,4786 ,5152,2087,5153 }, {577,675,674 ,2270,2294,2271 }, + {4893,4874,707 ,5286,5000,2091 }, {4602,4654,4601 ,5360,4348,5359 }, + {5943,6292,6283 ,5402,5403,4298 }, {6523,6524,6571 ,5319,3704,4146 }, + {1167,1120,1492 ,5341,5339,1585 }, {5672,5694,6949 ,5324,5326,4585 }, + {4774,4086,557 ,5332,3723,3724 }, {2156,6464,3898 ,3631,3633,1400 }, + {4701,5025,4815 ,5241,5307,5373 }, {4854,4616,4564 ,5396,5404,5375 }, + {4449,4854,4564 ,5374,5396,5375 }, {4616,4662,4646 ,5404,5405,5377 }, + {4564,4616,4646 ,5375,5404,5377 }, {4940,4939,4646 ,5398,5384,5377 }, + {4662,4940,4646 ,5405,5398,5377 }, {406,2219,2206 ,2566,177,176 }, + {4613,4614,278 ,1488,3109,1489 }, {617,618,677 ,2352,2391,2296 }, + {5064,5096,5095 ,5043,5185,5379 }, {4824,4848,4844 ,4719,4983,4866 }, + {4937,4938,4975 ,5187,5053,5202 }, {5018,5032,5031 ,5119,5168,4995 }, + {4840,4874,4873 ,5207,5000,4992 }, {972,2314,2162 ,3277,3742,3278 }, + {6681,6704,6655 ,5406,5160,5407 }, {4948,4947,917 ,5262,5344,4016 }, + {3358,6393,3357 ,880,1556,883 }, {6342,6555,1749 ,5265,597,555 }, + {4693,4955,4953 ,5228,5227,5408 }, {267,4693,4953 ,5362,5228,5408 }, + {4955,1994,4971 ,5227,5102,5409 }, {4953,4955,4971 ,5408,5227,5409 }, + {4838,5060,4776 ,245,5401,5390 }, {4838,4837,5060 ,245,3990,5401 }, + {4818,4647,4661 ,5145,5036,5101 }, {4683,1024,4647 ,5035,5077,5036 }, + {4580,4626,4579 ,5410,5395,5355 }, {4707,4706,4626 ,5411,4786,5395 }, + {4707,4737,4706 ,5411,5412,4786 }, {4706,4737,4765 ,4786,5412,3983 }, + {4826,5259,4765 ,5413,3794,3983 }, {4658,4657,4605 ,5047,5001,5006 }, + {361,4723,4751 ,4994,4972,5007 }, {4857,509,4810 ,4991,4993,5027 }, + {4873,4874,4893 ,4992,5000,5286 }, {4840,4857,4810 ,5207,4991,5027 }, + {375,4748,475 ,1883,4998,1884 }, {1320,4618,1285 ,2356,5070,1944 }, + {4664,4614,279 ,5414,3109,2176 }, {1583,2621,3683 ,5415,5416,5417 }, + {922,6416,6415 ,117,3440,386 }, {5000,922,6415 ,286,117,386 }, + {6795,5491,5516 ,5418,5419,5420 }, {4922,4608,4585 ,5256,5294,3955 }, + {1073,2407,2314 ,3741,4001,3742 }, {4793,4718,4753 ,5421,5422,5383 }, + {4718,4814,4854 ,5422,5423,5396 }, {4753,4718,4854 ,5383,5422,5396 }, + {4814,4722,4616 ,5423,5424,5404 }, {4854,4814,4616 ,5396,5423,5404 }, + {4722,4665,4662 ,5424,5425,5405 }, {4616,4722,4662 ,5404,5424,5405 }, + {4943,4940,4662 ,5426,5398,5405 }, {4665,4943,4662 ,5425,5426,5405 }, + {1655,2432,68 ,4089,5427,4087 }, {4614,4641,278 ,3109,5248,1489 }, + {4895,4817,5004 ,4973,2368,5243 }, {4975,4976,4997 ,5202,5048,5049 }, + {6417,404,5388 ,762,761,5428 }, {5016,4644,1931 ,5010,2425,4736 }, + {1699,1702,1752 ,772,1039,946 }, {4975,4997,4996 ,5202,5049,5020 }, + {4936,4937,4974 ,5013,5187,5019 }, {4810,4811,4840 ,5027,5165,5207 }, + {6957,5752,5709 ,4315,4374,5253 }, {4866,4865,4852 ,3641,4229,5364 }, + {4734,4902,1582 ,5127,5128,2081 }, {4829,4830,4852 ,4626,3642,5364 }, + {961,446,4959 ,500,501,5079 }, {5015,4870,4862 ,5170,5135,5149 }, + {1702,1636,1703 ,1039,1036,1038 }, {638,15,17 ,495,497,5190 }, + {4842,4838,4776 ,5429,245,5390 }, {24,1375,4837 ,247,325,3990 }, + {4838,24,4837 ,245,247,3990 }, {4919,4920,4968 ,5221,5138,5222 }, + {4581,4627,4626 ,5430,5431,5395 }, {4580,4581,4626 ,5410,5430,5395 }, + {4626,4708,4707 ,5395,5432,5411 }, {4707,4708,4737 ,5411,5432,5412 }, + {4737,4708,4765 ,5412,5432,3983 }, {384,327,328 ,2228,2226,2289 }, + {4751,5004,4871 ,5007,5243,5040 }, {4871,561,5016 ,5040,2379,5010 }, + {4895,5004,4751 ,4973,5243,5007 }, {4857,4840,4873 ,4991,5207,4992 }, + {4782,4812,4492 ,5029,5169,5031 }, {6842,6895,6865 ,5433,5434,5435 }, + {4820,4433,4849 ,2399,4591,5436 }, {4801,82,607 ,5115,138,2207 }, + {935,4962,936 ,5264,5263,5234 }, {7138,7161,7169 ,2560,2559,5437 }, + {1265,6344,4548 ,1273,1450,589 }, {4730,4885,4743 ,5438,5439,3629 }, + {4885,4747,4793 ,5439,5440,5421 }, {4743,4885,4793 ,3629,5439,5421 }, + {4793,4747,4718 ,5421,5440,5422 }, {4742,4877,4940 ,5441,5397,5398 }, + {4943,4742,4940 ,5426,5441,5398 }, {4742,4752,4877 ,5441,5196,5397 }, + {4897,1488,4752 ,5442,5197,5196 }, {410,259,2967 ,2204,2203,2701 }, + {4920,4919,662 ,5138,5221,5141 }, {1041,5034,952 ,2410,2396,2395 }, + {5034,1041,5033 ,2396,2410,5103 }, {2156,42,6783 ,3631,5195,3632 }, + {344,1993,343 ,562,4544,4543 }, {4587,4589,4637 ,774,776,4986 }, + {4787,4792,4816 ,5293,5272,5443 }, {4633,4678,4632 ,5327,5312,5329 }, + {4678,4794,4761 ,5312,5313,5251 }, {4758,4761,4620 ,5444,5251,5276 }, + {4841,4842,4745 ,5445,5429,5446 }, {4745,4776,495 ,5446,5390,4389 }, + {4917,4838,4842 ,5447,245,5429 }, {4841,4917,4842 ,5445,5447,5429 }, + {4258,85,4303 ,84,2504,3728 }, {5095,1024,4683 ,5379,5077,5035 }, + {5094,5095,4683 ,5089,5379,5035 }, {4627,4677,4626 ,5431,5448,5395 }, + {4626,4677,4708 ,5395,5448,5432 }, {4708,4766,4765 ,5432,5449,3983 }, + {4766,4827,4826 ,5449,2734,5413 }, {949,850,851 ,2133,2092,2155 }, + {4886,361,417 ,4720,4994,5009 }, {4580,7672,4581 ,5410,5450,5430 }, + {1397,4667,1320 ,2358,5069,2356 }, {6172,6147,5920 ,5451,5452,5453 }, + {8144,8133,2301 ,2469,2131,2132 }, {7027,7049,7007 ,3175,5454,4628 }, + {3320,6639,2158 ,5455,3699,1160 }, {911,3320,2158 ,1159,5455,1160 }, + {6309,6310,6735 ,3690,3918,3472 }, {1920,2157,6606 ,581,3411,5085 }, + {6686,5105,4544 ,750,5456,748 }, {1332,8123,8143 ,3492,2247,2570 }, + {4730,4731,4747 ,5438,5457,5440 }, {4885,4730,4747 ,5439,5438,5440 }, + {4899,4752,4742 ,5458,5196,5441 }, {4882,4899,4742 ,5459,5458,5441 }, + {3895,4897,4752 ,4455,5442,5196 }, {4899,3895,4752 ,5458,4455,5196 }, + {5065,5097,5096 ,5064,5063,5185 }, {5064,5065,5096 ,5043,5064,5185 }, + {663,4920,662 ,4773,5138,5141 }, {7163,7171,7194 ,5460,4306,1670 }, + {4773,4804,4661 ,4865,4867,5101 }, {4605,4604,1381 ,5006,5016,5193 }, + {1831,4850,1902 ,2221,5171,2193 }, {4968,4969,1030 ,5222,4977,1619 }, + {4906,4608,4922 ,4895,5294,5256 }, {4906,4922,2814 ,4895,5256,5255 }, + {4830,4866,4852 ,3642,3641,5364 }, {4739,4787,4816 ,5372,5293,5443 }, + {1994,2048,3184 ,5102,5461,4453 }, {4971,1994,1460 ,5409,5102,5462 }, + {1460,1994,3184 ,5462,5102,4453 }, {4745,4775,4841 ,5446,4388,5445 }, + {4917,4808,4838 ,5447,5137,245 }, {241,4724,240 ,314,4737,1571 }, + {4581,4628,4627 ,5430,5463,5431 }, {4627,4628,4677 ,5431,5463,5448 }, + {4677,4628,4708 ,5448,5463,5432 }, {4738,4767,4766 ,5464,5465,5449 }, + {4708,4738,4766 ,5432,5464,5449 }, {4766,4828,4827 ,5449,5466,2734 }, + {423,4811,383 ,2086,5165,2087 }, {6744,3350,1116 ,3986,2541,1401 }, + {6415,6416,4424 ,386,3440,3792 }, {6416,5089,4424 ,3440,3442,3792 }, + {5171,5125,5172 ,1804,5467,5468 }, {5105,5040,4544 ,5456,5469,748 }, + {5949,5388,404 ,5086,5428,761 }, {4731,4719,4718 ,5457,5470,5422 }, + {4747,4731,4718 ,5440,5457,5422 }, {4719,4673,4814 ,5470,5471,5423 }, + {4718,4719,4814 ,5422,5470,5423 }, {4673,4781,4722 ,5471,5472,5424 }, + {4814,4673,4722 ,5423,5471,5424 }, {4944,4943,4665 ,5473,5426,5425 }, + {4671,4944,4665 ,5474,5473,5425 }, {4944,4882,4742 ,5473,5459,5441 }, + {4943,4944,4742 ,5426,5473,5441 }, {3247,3777,6430 ,1494,1582,2748 }, + {423,514,4811 ,2086,2154,5165 }, {5018,4998,5032 ,5119,4713,5168 }, + {7927,7976,7952 ,1716,1715,5475 }, {718,811,856 ,2407,557,5139 }, + {8076,8124,2810 ,617,1141,1728 }, {4931,4932,4964 ,5066,4580,783 }, + {1042,898,1043 ,2409,2411,2478 }, {955,4834,4837 ,3991,4304,3990 }, + {4792,5002,4699 ,5272,5273,5476 }, {4802,4841,4775 ,5477,5445,4388 }, + {4917,648,4808 ,5447,4679,5137 }, {4716,4778,4714 ,1030,3916,1031 }, + {4734,1536,1508 ,5127,2080,2029 }, {5047,5031,5062 ,4996,4995,5044 }, + {4993,1081,1030 ,5292,3745,1619 }, {4762,1831,4735 ,5129,2221,5140 }, + {4743,5038,4730 ,3629,3628,5438 }, {4581,4582,4628 ,5430,5478,5463 }, + {4767,4798,4766 ,5465,5479,5449 }, {4766,4798,4828 ,5449,5479,5466 }, + {4828,4887,4827 ,5466,2735,2734 }, {5064,5095,5094 ,5043,5379,5089 }, + {4863,4904,4887 ,5480,3309,2735 }, {8145,8114,6797 ,3376,2286,2246 }, + {1167,1492,4017 ,5341,1585,5342 }, {5946,5991,7083 ,5481,5482,5483 }, + {4445,712,6757 ,3648,3650,3409 }, {5092,4730,4839 ,3953,5438,5484 }, + {7055,1219,7079 ,5485,4102,5486 }, {4635,4591,4731 ,5487,5488,5457 }, + {4730,4635,4731 ,5438,5487,5457 }, {4591,4825,4719 ,5488,5489,5470 }, + {4731,4591,4719 ,5457,5488,5470 }, {4825,4791,4673 ,5489,5490,5471 }, + {4719,4825,4673 ,5470,5489,5471 }, {4791,4728,4781 ,5490,5491,5472 }, + {4673,4791,4781 ,5471,5490,5472 }, {4781,4728,4722 ,5472,5491,5424 }, + {4728,837,4665 ,5491,5492,5425 }, {4722,4728,4665 ,5424,5491,5425 }, + {4665,837,4671 ,5425,5492,5474 }, {837,4946,4944 ,5492,5493,5473 }, + {4671,837,4944 ,5474,5492,5473 }, {4922,3766,4636 ,5256,3954,5118 }, + {4756,3817,5099 ,1190,1189,5494 }, {3817,3800,5099 ,1189,4325,5494 }, + {4682,4756,4736 ,1191,1190,5495 }, {7031,6735,2450 ,5357,3472,3444 }, + {4873,4893,612 ,4992,5286,5011 }, {6704,6703,6655 ,5160,5161,5407 }, + {4816,4792,4699 ,5443,5272,5476 }, {3945,6418,6416 ,4550,3441,3440 }, + {4775,4861,4802 ,4388,3877,5477 }, {4802,4917,4841 ,5477,5447,5445 }, + {4751,4871,5015 ,5007,5040,5170 }, {5031,5063,5062 ,4995,5042,5044 }, + {1790,5017,4183 ,4738,5092,1572 }, {4970,4993,4992 ,3073,5292,4978 }, + {4783,1718,1672 ,4981,2108,2083 }, {4555,4556,4582 ,5496,5497,5478 }, + {79,8104,3421 ,1510,1343,1156 }, {4583,4628,4582 ,5498,5463,5478 }, + {4652,4709,4708 ,5499,4396,5432 }, {4628,4652,4708 ,5463,5499,5432 }, + {4708,4709,4738 ,5432,4396,5464 }, {4767,4768,4798 ,5465,5500,5479 }, + {4798,4768,4828 ,5479,5500,5466 }, {4863,4887,4828 ,5480,2735,5466 }, + {4851,4863,4828 ,5501,5480,5466 }, {311,4808,648 ,246,5137,4679 }, + {673,4907,4934 ,2210,2209,5041 }, {6808,6809,6859 ,3465,712,472 }, + {1583,1606,2621 ,5415,3677,5416 }, {7004,7045,7026 ,5502,5503,5504 }, + {5089,3454,4424 ,3442,710,3792 }, {5844,5812,7073 ,5179,660,662 }, + {1666,4882,4944 ,5505,5459,5473 }, {4946,1666,4944 ,5493,5505,5473 }, + {1666,4899,4882 ,5505,5458,5459 }, {4921,5082,4979 ,2079,5218,5363 }, + {5066,5098,5065 ,5188,5062,5064 }, {4733,5099,4697 ,5506,5494,4327 }, + {4733,4756,5099 ,5506,1190,5494 }, {3634,4741,4813 ,5507,5508,4088 }, + {4733,4736,4756 ,5506,5495,1190 }, {5026,4881,4957 ,4225,2731,2733 }, + {8122,8145,8087 ,384,3376,385 }, {5022,1196,4788 ,4329,4330,3758 }, + {4760,4669,5022 ,5509,3879,4329 }, {4759,4861,4669 ,5510,3877,3879 }, + {4760,4759,4669 ,5509,5510,3879 }, {4892,4917,4802 ,5386,5447,5477 }, + {4892,648,4917 ,5386,4679,5447 }, {675,578,676 ,2294,2293,2295 }, + {718,856,855 ,2407,5139,2392 }, {6348,6349,6357 ,1607,705,5271 }, + {663,662,566 ,4773,5141,1613 }, {668,611,669 ,1949,1948,2020 }, + {4605,4656,4604 ,5006,5002,5016 }, {5217,4703,4704 ,4026,5290,4788 }, + {5067,1041,1135 ,1945,2410,1943 }, {3855,3813,3814 ,1511,1515,5511 }, + {3303,7387,7386 ,1049,3615,5512 }, {4583,4629,4628 ,5498,5513,5463 }, + {4629,4652,4628 ,5513,5499,5463 }, {4709,4768,4767 ,4396,5500,5465 }, + {4738,4709,4767 ,5464,4396,5465 }, {851,767,4934 ,2155,2093,5041 }, + {6153,5040,5105 ,5514,5469,5456 }, {3898,6464,4987 ,1400,3633,1491 }, + {6917,6916,6895 ,5515,596,5434 }, {6757,3702,1990 ,3409,1303,1305 }, + {911,5027,158 ,1159,5516,5258 }, {5027,911,6684 ,5516,1159,1161 }, + {6681,6655,6656 ,5406,5407,5517 }, {5405,6681,6656 ,5518,5406,5517 }, + {5688,5738,6890 ,5519,5520,5334 }, {5738,5750,6890 ,5520,5156,5334 }, + {1460,3184,4899 ,5462,4453,5458 }, {1666,1460,4899 ,5505,5462,5458 }, + {4899,3184,3895 ,5458,4453,4455 }, {4618,4912,5097 ,5070,5186,5063 }, + {8094,8100,8069 ,98,1713,1095 }, {6853,5626,5587 ,2505,1460,1459 }, + {6098,6173,6388 ,1565,3711,3184 }, {2164,2165,3191 ,3583,1041,2903 }, + {7169,7186,7168 ,5437,5370,5371 }, {4741,4815,1655 ,5508,5373,4089 }, + {4813,4741,1655 ,4088,5508,4089 }, {4815,4878,4872 ,5373,5521,5522 }, + {1655,4815,4872 ,4089,5373,5522 }, {4878,4739,4872 ,5521,5372,5522 }, + {4668,4697,4872 ,5523,4327,5522 }, {4739,4668,4872 ,5372,5523,5522 }, + {4668,4733,4697 ,5523,5506,4327 }, {4610,4682,4736 ,3757,1191,5495 }, + {4610,1376,4682 ,3757,1937,1191 }, {5002,4790,4699 ,5273,5336,5476 }, + {4760,5022,4788 ,5509,4329,3758 }, {4660,4760,4788 ,5524,5509,3758 }, + {4759,4802,4861 ,5510,5477,3877 }, {4802,4875,4892 ,5477,5525,5386 }, + {1469,4782,1397 ,2361,5029,2358 }, {707,673,767 ,2091,2210,2093 }, + {4647,4818,4643 ,5036,5145,5037 }, {749,1676,4723 ,2331,2343,4972 }, + {663,566,567 ,4773,1613,1684 }, {4724,4949,1790 ,4737,5526,4738 }, + {1440,4666,1508 ,2063,5105,2029 }, {611,612,669 ,1948,5011,2020 }, + {5098,1285,5097 ,5062,1944,5063 }, {4630,4629,4583 ,827,5513,5498 }, + {4629,4630,4652 ,5513,827,5499 }, {4709,4710,4768 ,4396,4136,5500 }, + {4829,4828,4768 ,4626,5466,5500 }, {4829,4851,4828 ,4626,5501,5466 }, + {2290,7935,7936 ,1982,5527,5528 }, {2376,2156,3898 ,1251,3631,1400 }, + {3609,6195,5920 ,5081,5529,5453 }, {3319,2336,5005 ,1259,1261,1301 }, + {8081,8138,8078 ,1445,2018,1744 }, {5003,4591,4635 ,5530,5488,5487 }, + {4860,5003,4635 ,5531,5530,5487 }, {4591,4909,4825 ,5488,5532,5489 }, + {4909,4791,4825 ,5532,5490,5489 }, {837,4952,4946 ,5492,5533,5493 }, + {4952,1460,1666 ,5533,5462,5505 }, {4946,4952,1666 ,5493,5533,5505 }, + {4698,1885,1884 ,1578,778,3567 }, {4635,4730,5092 ,5487,5438,3953 }, + {7743,7769,7768 ,5534,5535,5536 }, {4741,4956,4815 ,5508,5381,5373 }, + {4815,4739,4878 ,5373,5372,5521 }, {4816,4668,4739 ,5443,5523,5372 }, + {4699,4733,4668 ,5476,5506,5523 }, {4816,4699,4668 ,5443,5476,5523 }, + {4632,4788,4610 ,5329,3758,3757 }, {4891,4632,4610 ,5537,5329,3757 }, + {4632,4660,4788 ,5329,5524,3758 }, {4678,4760,4660 ,5312,5509,5524 }, + {4632,4678,4660 ,5329,5312,5524 }, {4620,4802,4759 ,5276,5477,5510 }, + {4802,4620,4875 ,5477,5276,5525 }, {5015,4871,4870 ,5170,5040,5135 }, + {678,4695,833 ,446,5199,489 }, {4862,1831,4762 ,5149,2221,5129 }, + {4970,4969,4920 ,3073,4977,5138 }, {4749,4786,4811 ,5026,5153,5165 }, + {4641,4664,279 ,5248,5414,2176 }, {557,4883,4774 ,3724,5538,5332 }, + {156,1297,1488 ,5539,4048,5197 }, {557,4801,4883 ,3724,5115,5538 }, + {4264,1488,4897 ,5540,5197,5442 }, {4641,279,4691 ,5248,2176,2089 }, + {4597,4630,4583 ,5541,827,5498 }, {4652,4630,4709 ,5499,827,4396 }, + {4710,4630,5177 ,4136,827,424 }, {4710,4769,4768 ,4136,5542,5500 }, + {4769,4799,4768 ,5542,4627,5500 }, {4768,4799,4829 ,5500,4627,4626 }, + {4829,4864,4863 ,4626,5543,5480 }, {4851,4829,4863 ,5501,4626,5480 }, + {3006,8132,552 ,414,413,3929 }, {6611,5471,6640 ,1391,2051,4224 }, + {1562,4392,7051 ,4649,844,547 }, {4586,911,158 ,5257,1159,5258 }, + {7568,7594,7567 ,5544,4020,5545 }, {7552,7613,7551 ,5546,5106,5108 }, + {4585,4593,5003 ,3955,5295,5530 }, {4860,4585,5003 ,5531,3955,5530 }, + {5003,4593,4591 ,5530,5295,5488 }, {4593,4732,4909 ,5295,5298,5532 }, + {4591,4593,4909 ,5488,5295,5532 }, {4732,4927,4791 ,5298,5297,5490 }, + {4909,4732,4791 ,5532,5298,5490 }, {4927,838,4728 ,5297,5345,5491 }, + {4791,4927,4728 ,5490,5297,5491 }, {267,837,4728 ,5362,5492,5491 }, + {838,267,4728 ,5345,5362,5491 }, {267,4953,4952 ,5362,5408,5533 }, + {837,267,4952 ,5492,5362,5533 }, {7647,7697,7675 ,4967,651,5192 }, + {2000,1971,2002 ,2428,2520,2493 }, {3276,2375,922 ,116,4479,117 }, + {403,1264,3929 ,5547,5022,5024 }, {4832,4736,4733 ,5548,5495,5506 }, + {4699,4832,4733 ,5476,5548,5506 }, {4891,4610,4736 ,5537,3757,5495 }, + {4678,4759,4760 ,5312,5510,5509 }, {4759,4758,4620 ,5510,5444,5276 }, + {4875,4620,4892 ,5525,5276,5386 }, {4850,1903,1902 ,5171,406,2193 }, + {4614,4664,4641 ,3109,5414,5248 }, {7672,7716,4581 ,5450,5549,5430 }, + {1970,4740,2000 ,2427,2426,2428 }, {4724,241,1241 ,4737,314,313 }, + {1241,4283,4774 ,313,1678,5332 }, {4724,1241,4774 ,4737,313,5332 }, + {607,124,4801 ,2207,2206,5115 }, {4491,808,5202 ,107,4734,5550 }, + {6039,5166,5227 ,2728,3427,4097 }, {4331,5052,1154 ,4645,5189,4545 }, + {1154,5052,993 ,4545,5189,5551 }, {4769,4770,4799 ,5542,4345,4627 }, + {4829,4865,4864 ,4626,4229,5543 }, {4864,4865,4863 ,5543,4229,5480 }, + {4865,4913,798 ,4229,4185,78 }, {4863,4865,798 ,5480,4229,78 }, + {3264,8117,8068 ,2468,4119,1784 }, {3626,6534,3627 ,4196,4257,4256 }, + {6418,5091,5089 ,3441,5133,3442 }, {4549,6621,6605 ,877,5552,5164 }, + {5669,5710,5709 ,5553,5254,5253 }, {6389,6714,6868 ,5301,5300,5554 }, + {5092,4585,4860 ,3953,3955,5531 }, {4953,4971,1460 ,5408,5409,5462 }, + {4952,4953,1460 ,5533,5408,5462 }, {185,3777,3247 ,1493,1582,1494 }, + {1166,309,2408 ,2746,4472,1399 }, {4560,4559,7918 ,5236,5237,5555 }, + {922,3945,6416 ,117,4550,3440 }, {2156,2376,2258 ,3631,1251,1198 }, + {4790,4832,4699 ,5336,5548,5476 }, {4790,4736,4832 ,5336,5495,5548 }, + {4694,4891,4736 ,5328,5537,5495 }, {4790,4694,4736 ,5336,5328,5495 }, + {4694,4632,4891 ,5328,5329,5537 }, {4761,4759,4678 ,5251,5510,5312 }, + {4759,4761,4758 ,5510,5251,5444 }, {4620,343,4892 ,5276,4543,5386 }, + {4117,5481,4157 ,4700,3863,3755 }, {206,3815,1606 ,3932,5556,3677 }, + {247,921,2663 ,2569,1922,3593 }, {6044,5934,5980 ,614,616,5557 }, + {3797,3746,3747 ,2061,2708,2925 }, {6211,6246,6245 ,5558,5559,5560 }, + {4443,4422,6212 ,703,5561,5562 }, {1344,1511,3941 ,300,4921,5563 }, + {1268,5995,6105 ,4568,5564,5565 }, {6210,6211,6245 ,5566,5558,5560 }, + {5515,5960,6045 ,5567,5568,935 }, {6157,1017,1426 ,5569,1793,4648 }, + {5140,2094,5130 ,3712,5570,5571 }, {5130,5499,5140 ,5571,5572,3712 }, + {5499,2621,5140 ,5572,5416,3712 }, {1627,3688,1729 ,5573,5574,3666 }, + {6247,6275,1557 ,5575,5576,5577 }, {5340,5379,4916 ,4002,3998,2248 }, + {5339,5340,4916 ,2223,4002,2248 }, {2256,5013,5950 ,2323,684,2873 }, + {6123,5988,4147 ,4221,2516,2517 }, {4392,5130,2094 ,844,5571,5570 }, + {5204,3683,5704 ,5578,5417,5579 }, {5499,5204,5704 ,5572,5578,5579 }, + {6377,6079,5978 ,1347,5580,5581 }, {5978,6082,6377 ,5581,1348,1347 }, + {7948,7994,7947 ,3625,5582,4354 }, {4306,4278,4307 ,4552,5583,4562 }, + {4489,3778,1583 ,5584,5585,5415 }, {6744,5082,5786 ,3986,5218,4813 }, + {5131,5204,5499 ,5586,5578,5572 }, {5130,5131,5499 ,5571,5586,5572 }, + {6390,5950,8114 ,3868,2873,2286 }, {1557,3310,5989 ,5577,5587,5588 }, + {778,1742,5498 ,5589,707,5590 }, {3820,778,5498 ,5591,5589,5590 }, + {313,1562,1742 ,5592,4649,707 }, {778,313,1742 ,5589,5592,707 }, + {313,5132,5130 ,5592,5593,5571 }, {1562,313,5130 ,4649,5592,5571 }, + {5130,5132,5131 ,5571,5593,5586 }, {5132,5141,5204 ,5593,5594,5578 }, + {5131,5132,5204 ,5586,5593,5578 }, {5141,5145,3683 ,5594,5595,5417 }, + {5204,5141,3683 ,5578,5594,5417 }, {5145,1289,1583 ,5595,5596,5415 }, + {3683,5145,1583 ,5417,5595,5415 }, {1289,3952,4489 ,5596,5597,5584 }, + {1583,1289,4489 ,5415,5596,5584 }, {4342,3528,4321 ,4950,5061,4873 }, + {1289,2713,3952 ,5596,4352,5597 }, {5141,1289,5145 ,5594,5596,5595 }, + {1289,3141,2713 ,5596,4701,4352 }, {5231,2375,3276 ,115,4479,116 }, + {5419,3820,3936 ,5598,5591,5599 }, {4024,5419,3936 ,5600,5598,5599 }, + {3798,778,3820 ,5601,5589,5591 }, {5419,3798,3820 ,5598,5601,5591 }, + {3798,4325,313 ,5601,5602,5592 }, {778,3798,313 ,5589,5601,5592 }, + {4325,5133,5132 ,5602,5603,5593 }, {313,4325,5132 ,5592,5602,5593 }, + {5133,533,5141 ,5603,5604,5594 }, {5132,5133,5141 ,5593,5603,5594 }, + {533,1944,5141 ,5604,5605,5594 }, {1944,2270,1289 ,5605,5606,5596 }, + {5141,1944,1289 ,5594,5605,5596 }, {2270,3222,1289 ,5606,5607,5596 }, + {1289,3222,3141 ,5596,5607,4701 }, {5133,1944,533 ,5603,5605,5604 }, + {5163,4127,3141 ,4987,4702,4701 }, {3222,5163,3141 ,5607,4987,4701 }, + {7090,7089,6281 ,4080,5608,4078 }, {4426,1414,4024 ,1056,5609,5600 }, + {3733,4426,4024 ,5610,1056,5600 }, {1414,5422,5419 ,5609,5611,5598 }, + {4024,1414,5419 ,5600,5609,5598 }, {4014,3798,5419 ,5612,5601,5598 }, + {5422,4014,5419 ,5611,5612,5598 }, {4014,3947,4325 ,5612,5613,5602 }, + {3798,4014,4325 ,5601,5612,5602 }, {5133,5561,1944 ,5603,5614,5605 }, + {2270,3002,3222 ,5606,5615,5607 }, {3222,3002,5163 ,5607,5615,4987 }, + {4545,4980,3621 ,586,241,625 }, {3947,5134,5133 ,5613,5616,5603 }, + {4325,3947,5133 ,5602,5613,5603 }, {5133,5134,5561 ,5603,5616,5614 }, + {5134,1786,1944 ,5616,5617,5605 }, {5561,5134,1944 ,5614,5616,5605 }, + {1786,5144,2270 ,5617,5618,5606 }, {1944,1786,2270 ,5605,5617,5606 }, + {5144,996,3002 ,5618,5003,5615 }, {2270,5144,3002 ,5606,5618,5615 }, + {3002,996,5163 ,5615,5003,4987 }, {5163,996,4176 ,4987,5003,4703 }, + {4014,159,3947 ,5612,5619,5613 }, {3947,159,5134 ,5613,5619,5616 }, + {6033,5987,5982 ,5620,5621,4218 }, {4012,1414,4426 ,5622,5609,1056 }, + {3816,4012,4426 ,5623,5622,1056 }, {5268,5422,1414 ,5624,5611,5609 }, + {4012,5268,1414 ,5622,5624,5609 }, {5268,5773,4014 ,5624,5625,5612 }, + {5422,5268,4014 ,5611,5624,5612 }, {5773,3971,4014 ,5625,5626,5612 }, + {3971,2961,159 ,5626,5627,5619 }, {4014,3971,159 ,5612,5626,5619 }, + {2961,5135,5134 ,5627,5628,5616 }, {159,2961,5134 ,5619,5627,5616 }, + {5135,5142,1786 ,5628,5629,5617 }, {5134,5135,1786 ,5616,5628,5617 }, + {1786,5142,5144 ,5617,5629,5618 }, {5142,5311,996 ,5629,5630,5003 }, + {5144,5142,996 ,5618,5629,5003 }, {5311,3784,996 ,5630,2252,5003 }, + {5336,4012,3816 ,5631,5622,5623 }, {5157,3971,5773 ,5632,5626,5625 }, + {5268,5157,5773 ,5624,5632,5625 }, {5447,5426,2140 ,5633,5634,5635 }, + {5157,5268,4012 ,5632,5624,5622 }, {5336,5157,4012 ,5631,5632,5622 }, + {5135,5456,5142 ,5628,5636,5629 }, {6905,5669,5626 ,2506,5553,1460 }, + {5251,5300,5299 ,3802,3793,3800 }, {4097,3816,5445 ,5637,5623,3520 }, + {4293,4097,5445 ,2732,5637,3520 }, {4294,5336,3816 ,5638,5631,5623 }, + {4097,4294,3816 ,5637,5638,5623 }, {4294,3055,5157 ,5638,5639,5632 }, + {5336,4294,5157 ,5631,5638,5632 }, {3055,1391,3971 ,5639,5640,5626 }, + {5157,3055,3971 ,5632,5639,5626 }, {1391,5343,2961 ,5640,5641,5627 }, + {3971,1391,2961 ,5626,5640,5627 }, {5343,5269,5135 ,5641,5642,5628 }, + {2961,5343,5135 ,5627,5641,5628 }, {5269,5310,5456 ,5642,5643,5636 }, + {5135,5269,5456 ,5628,5642,5636 }, {5310,4884,5142 ,5643,2253,5629 }, + {5456,5310,5142 ,5636,5643,5629 }, {5142,4884,5311 ,5629,2253,5630 }, + {5311,4884,3784 ,5630,2253,2252 }, {5343,5310,5269 ,5641,5643,5642 }, + {3100,1552,1590 ,2955,2266,3028 }, {4881,3536,4293 ,2731,5644,2732 }, + {5343,5360,5310 ,5641,5645,5643 }, {5310,5574,4884 ,5643,5646,2253 }, + {5058,3320,2163 ,5647,5455,243 }, {3536,4097,4293 ,5644,5637,2732 }, + {3403,4294,4097 ,5648,5638,5637 }, {3536,3403,4097 ,5644,5648,5637 }, + {3403,1690,3055 ,5648,5649,5639 }, {4294,3403,3055 ,5638,5648,5639 }, + {1690,5164,1391 ,5649,5650,5640 }, {3055,1690,1391 ,5639,5649,5640 }, + {5164,5312,5343 ,5650,5651,5641 }, {1391,5164,5343 ,5640,5650,5641 }, + {5312,5226,5360 ,5651,5652,5645 }, {5343,5312,5360 ,5641,5651,5645 }, + {5226,5601,5310 ,5652,5653,5643 }, {5360,5226,5310 ,5645,5652,5643 }, + {5601,711,5574 ,5653,5654,5646 }, {5310,5601,5574 ,5643,5653,5646 }, + {711,85,4884 ,5654,2504,2253 }, {5574,711,4884 ,5646,5654,2253 }, + {125,5127,124 ,399,5114,2206 }, {5164,5226,5312 ,5650,5652,5651 }, + {5226,711,5601 ,5652,5654,5653 }, {5308,505,5309 ,3528,986,987 }, + {5325,5297,5298 ,5655,3826,3818 }, {5379,4931,4916 ,3998,5066,2248 }, + {5200,5224,4679 ,5656,3850,3852 }, {4558,4631,4598 ,4209,1816,5343 }, + {8090,8080,8132 ,412,1094,413 }, {2320,2319,8049 ,1406,3228,5393 }, + {6256,5609,5953 ,5657,5658,5659 }, {5394,3499,405 ,5660,5661,5662 }, + {758,5361,3525 ,5663,5664,5665 }, {6243,6244,5356 ,5666,5667,4258 }, + {1158,253,249 ,2744,2564,644 }, {5322,5321,5320 ,4297,5668,4464 }, + {6306,5943,6336 ,5669,5402,5670 }, {5122,5717,3507 ,4024,4023,5671 }, + {1661,3611,6111 ,5672,5673,5674 }, {4805,1791,4711 ,5376,5675,5358 }, + {3320,911,2163 ,5455,1159,243 }, {5186,3536,4881 ,5676,5644,2731 }, + {5165,5186,4881 ,4226,5676,2731 }, {4370,3403,3536 ,5677,5648,5644 }, + {5186,4370,3536 ,5676,5677,5644 }, {4370,5156,1690 ,5677,5678,5649 }, + {3403,4370,1690 ,5648,5677,5649 }, {5156,5529,5164 ,5678,5679,5650 }, + {1690,5156,5164 ,5649,5678,5650 }, {5529,1386,5164 ,5679,5680,5650 }, + {1386,5159,5226 ,5680,5681,5652 }, {5164,1386,5226 ,5650,5680,5652 }, + {5159,3840,5226 ,5681,5682,5652 }, {3840,4250,711 ,5682,4161,5654 }, + {5226,3840,711 ,5652,5682,5654 }, {711,4250,85 ,5654,4161,2504 }, + {3499,5394,405 ,5661,5660,5662 }, {6245,6246,6273 ,5560,5559,5683 }, + {6246,6274,6273 ,5559,5684,5683 }, {6111,6063,5575 ,5674,5685,5686 }, + {1451,1538,2954 ,175,0,5687 }, {4490,4273,2954 ,5688,120,5687 }, + {542,1451,2954 ,122,175,5687 }, {5114,2291,2342 ,5689,1115,5690 }, + {1561,407,1525 ,1865,4018,1866 }, {4860,4635,5092 ,5531,5487,3953 }, + {3817,4682,5153 ,1189,1191,3896 }, {5960,5515,6005 ,5568,5567,5691 }, + {747,5782,5781 ,5692,5693,5694 }, {1741,6104,6230 ,5695,5696,2144 }, + {6055,3906,5129 ,3874,5697,5698 }, {2534,368,198 ,2846,1396,1392 }, + {6274,3666,5880 ,5684,5699,5700 }, {5296,5323,5322 ,4296,5701,4297 }, + {5575,202,5958 ,5686,4105,5702 }, {6042,5230,5921 ,5703,5704,5705 }, + {202,5122,5958 ,4105,4024,5702 }, {5898,5653,6115 ,5706,3644,3645 }, + {7487,7516,7540 ,5707,5708,5709 }, {5156,1386,5529 ,5678,5680,5679 }, + {1386,3840,5159 ,5680,5682,5681 }, {5139,5146,5260 ,1939,1075,4174 }, + {6053,5874,5925 ,4463,4896,5710 }, {4153,4374,3953 ,5711,5712,5713 }, + {5891,3822,5602 ,5714,5715,5716 }, {6052,5486,3311 ,5717,5718,5303 }, + {5948,5602,3507 ,5719,5716,5671 }, {5717,5948,3507 ,4023,5719,5671 }, + {5774,5750,5775 ,5720,5156,5158 }, {5808,5807,5774 ,5721,4769,5720 }, + {5775,5808,5774 ,5158,5721,5720 }, {5808,5840,5807 ,5721,4770,4769 }, + {4273,542,2954 ,120,122,5687 }, {4648,1125,1269 ,5722,5723,5724 }, + {7846,7845,7793 ,4162,5725,5726 }, {4859,5501,5165 ,5039,5727,4226 }, + {5026,4859,5165 ,4225,5039,4226 }, {5501,5537,5165 ,5727,5728,4226 }, + {2799,5186,5165 ,5729,5676,4226 }, {5537,2799,5165 ,5728,5729,4226 }, + {2799,5313,4370 ,5729,5730,5677 }, {5186,2799,4370 ,5676,5729,5677 }, + {5313,5358,5156 ,5730,5731,5678 }, {4370,5313,5156 ,5677,5730,5678 }, + {5358,1533,5156 ,5731,5732,5678 }, {1533,3857,1386 ,5732,5733,5680 }, + {5156,1533,1386 ,5678,5732,5680 }, {3857,5270,3840 ,5733,5734,5682 }, + {1386,3857,3840 ,5680,5733,5682 }, {4404,4250,3840 ,5072,4161,5682 }, + {5270,4404,3840 ,5734,5072,5682 }, {5200,4681,5224 ,5656,4958,3850 }, + {4410,6119,5303 ,5735,5736,5737 }, {3845,3497,5319 ,3635,5738,5739 }, + {2238,1272,1268 ,2697,5740,4568 }, {5647,5689,5688 ,5333,5741,5519 }, + {5689,5739,5738 ,5741,5157,5520 }, {5739,5750,5738 ,5157,5156,5520 }, + {5776,5775,5739 ,5742,5158,5157 }, {5809,5808,5775 ,5743,5721,5158 }, + {5776,5809,5775 ,5742,5743,5158 }, {5809,5840,5808 ,5743,4770,5721 }, + {5461,5962,5252 ,5744,5745,5746 }, {2004,1937,4490 ,5747,59,5688 }, + {6235,6165,5703 ,4215,4219,5748 }, {4766,4826,4765 ,5449,5413,3983 }, + {7794,7846,7793 ,5749,4162,5726 }, {3857,4015,5270 ,5733,5750,5734 }, + {4753,4696,4793 ,5383,5382,5421 }, {6418,5090,5091 ,3441,4929,5133 }, + {4653,5182,4599 ,4347,4567,4210 }, {5368,5888,6077 ,5751,5752,5753 }, + {3562,3856,6037 ,4753,5754,5235 }, {6244,6272,5663 ,5667,5755,4259 }, + {6240,6268,6254 ,5756,5757,5758 }, {2048,2015,2049 ,5461,759,758 }, + {5566,5581,5605 ,5759,5760,5761 }, {5647,5664,5689 ,5333,5762,5741 }, + {5841,5840,5809 ,5763,4770,5743 }, {1293,932,532 ,5764,5765,5766 }, + {4271,932,1464 ,5767,5765,5768 }, {1937,4273,4490 ,59,120,5688 }, + {4711,1791,4771 ,5358,5675,3622 }, {1928,5501,4859 ,1282,5727,5039 }, + {5501,2799,5537 ,5727,5729,5728 }, {5313,1533,5358 ,5730,5732,5731 }, + {5260,1868,2217 ,4174,2249,4569 }, {5902,686,6093 ,5769,5770,5771 }, + {3583,5391,5652 ,5772,5773,5774 }, {6046,5713,5686 ,5775,5776,5777 }, + {5935,3560,6218 ,5778,5779,5780 }, {5504,5541,5566 ,5781,5782,5759 }, + {5531,5504,5566 ,5783,5781,5759 }, {5566,5541,5581 ,5759,5782,5760 }, + {5541,5582,5605 ,5782,5784,5761 }, {5581,5541,5605 ,5760,5782,5761 }, + {5582,5622,5647 ,5784,5785,5333 }, {5605,5582,5647 ,5761,5784,5333 }, + {5647,5622,5664 ,5333,5785,5762 }, {5622,5690,5689 ,5785,5786,5741 }, + {5664,5622,5689 ,5762,5785,5741 }, {5690,5740,5739 ,5786,5787,5157 }, + {5689,5690,5739 ,5741,5786,5157 }, {5777,5776,5739 ,5788,5742,5157 }, + {5740,5777,5739 ,5787,5788,5157 }, {5810,5809,5776 ,5789,5743,5742 }, + {5777,5810,5776 ,5788,5789,5742 }, {5842,5841,5809 ,5790,5763,5743 }, + {5810,5842,5809 ,5789,5790,5743 }, {5842,5840,5841 ,5790,4770,5763 }, + {1246,4271,5840 ,5791,5767,4770 }, {5842,1246,5840 ,5790,5791,4770 }, + {532,932,4271 ,5766,5765,5767 }, {1246,532,4271 ,5791,5766,5767 }, + {1689,1937,2004 ,5792,59,5747 }, {4743,5732,5701 ,3629,3940,3938 }, + {489,4326,3761 ,3639,219,218 }, {4744,5049,4859 ,4424,5038,5039 }, + {4096,2799,5501 ,5793,5729,5727 }, {1928,4096,5501 ,1282,5793,5727 }, + {4096,3876,5313 ,5793,5794,5730 }, {2799,4096,5313 ,5729,5793,5730 }, + {3876,5155,1533 ,5794,5795,5732 }, {5313,3876,1533 ,5730,5794,5732 }, + {5155,5620,3857 ,5795,5796,5733 }, {1533,5155,3857 ,5732,5795,5733 }, + {5620,5344,4015 ,5796,5797,5750 }, {3857,5620,4015 ,5733,5796,5750 }, + {5344,4849,5270 ,5797,5436,5734 }, {4015,5344,5270 ,5750,5797,5734 }, + {4849,4433,4404 ,5436,4591,5072 }, {5270,4849,4404 ,5734,5436,5072 }, + {1791,4805,3367 ,5675,5376,5798 }, {5391,3583,3608 ,5773,5772,5799 }, + {3498,5960,6005 ,5800,5568,5691 }, {5652,6046,5686 ,5774,5775,5777 }, + {5463,5505,5504 ,5801,5802,5781 }, {5505,5541,5504 ,5802,5782,5781 }, + {5582,5606,5622 ,5784,5803,5785 }, {5690,5741,5740 ,5786,5804,5787 }, + {5857,1246,5842 ,5805,5791,5790 }, {5155,5344,5620 ,5795,5797,5796 }, + {5290,5308,5289 ,5247,3528,80 }, {4793,5770,5732 ,5421,3949,3940 }, + {4743,4793,5732 ,3629,5421,3940 }, {4797,4765,5259 ,3984,3983,3794 }, + {4715,4714,4681 ,1029,1031,4958 }, {3498,5478,5960 ,5800,5806,5568 }, + {2568,5391,3608 ,5807,5773,5799 }, {5478,6056,6045 ,5806,5808,935 }, + {5880,6137,6136 ,5700,5809,5810 }, {5663,5880,6136 ,4259,5700,5810 }, + {5427,5428,5463 ,5811,5812,5801 }, {5463,5485,5505 ,5801,5813,5802 }, + {5741,5778,5777 ,5804,5814,5788 }, {5740,5741,5777 ,5787,5804,5788 }, + {5778,5810,5777 ,5814,5789,5788 }, {421,1293,746 ,5815,5764,2661 }, + {920,1191,3986 ,57,5816,5817 }, {495,4776,3788 ,4389,5390,4410 }, + {5188,6744,5786 ,4812,3986,4813 }, {3876,4205,5155 ,5794,5818,5795 }, + {3787,5918,5562 ,5819,5820,4193 }, {5224,5267,5244 ,3850,4647,79 }, + {1191,920,1689 ,5816,57,5792 }, {3367,920,3986 ,5798,57,5817 }, + {5960,5478,6045 ,5568,5806,935 }, {5894,2568,3608 ,5821,5807,5799 }, + {6137,3989,4028 ,5809,5822,5823 }, {5391,6046,5652 ,5773,5775,5774 }, + {6136,6137,4028 ,5810,5809,5823 }, {5429,5464,5463 ,5824,5825,5801 }, + {5428,5429,5463 ,5812,5824,5801 }, {5463,5464,5485 ,5801,5825,5813 }, + {5464,5506,5505 ,5825,5826,5802 }, {5485,5464,5505 ,5813,5825,5802 }, + {5506,5542,5541 ,5826,5827,5782 }, {5505,5506,5541 ,5802,5826,5782 }, + {5542,5543,5541 ,5827,5828,5782 }, {5543,5583,5582 ,5828,5829,5784 }, + {5541,5543,5582 ,5782,5828,5784 }, {5582,5583,5606 ,5784,5829,5803 }, + {5583,5623,5622 ,5829,5830,5785 }, {5606,5583,5622 ,5803,5829,5785 }, + {5623,5665,5622 ,5830,5831,5785 }, {5622,5665,5690 ,5785,5831,5786 }, + {5665,5707,5690 ,5831,5832,5786 }, {5690,5707,5741 ,5786,5832,5804 }, + {5707,5779,5778 ,5832,5833,5814 }, {5741,5707,5778 ,5804,5832,5814 }, + {5811,5810,5778 ,5834,5789,5814 }, {5779,5811,5778 ,5833,5834,5814 }, + {5811,5822,5810 ,5834,5835,5789 }, {5843,5842,5810 ,1372,5790,5789 }, + {5822,5843,5810 ,5835,1372,5789 }, {2958,5857,5842 ,1374,5805,5790 }, + {5843,2958,5842 ,1372,1374,5790 }, {1662,1246,5857 ,5836,5791,5805 }, + {2958,1662,5857 ,1374,5836,5805 }, {746,532,1246 ,2661,5766,5791 }, + {1662,746,1246 ,5836,2661,5791 }, {3367,3986,2953 ,5798,5817,5837 }, + {1791,3367,2953 ,5675,5798,5837 }, {556,455,1344 ,4043,2205,300 }, + {994,4096,1928 ,5838,5793,1282 }, {5197,994,1928 ,4842,5838,1282 }, + {994,5421,4096 ,5838,5839,5793 }, {5421,4529,3876 ,5839,5840,5794 }, + {4096,5421,3876 ,5793,5839,5794 }, {4529,3865,4205 ,5840,5841,5818 }, + {3876,4529,4205 ,5794,5840,5818 }, {3865,5357,5155 ,5841,5842,5795 }, + {4205,3865,5155 ,5818,5841,5795 }, {5357,5154,5155 ,5842,5843,5795 }, + {5154,4345,5344 ,5843,5844,5797 }, {5155,5154,5344 ,5795,5843,5797 }, + {4345,4820,4849 ,5844,2399,5436 }, {5344,4345,4849 ,5797,5844,5436 }, + {5337,4914,4866 ,572,65,3641 }, {5290,5289,5244 ,5247,80,79 }, + {3875,3184,2048 ,5845,4453,5461 }, {920,1937,1689 ,57,59,5792 }, + {4028,3989,5193 ,5823,5822,5846 }, {3989,5252,5193 ,5822,5746,5846 }, + {5395,5429,5428 ,5348,5824,5812 }, {5506,5543,5542 ,5826,5828,5827 }, + {5665,5666,5707 ,5831,5847,5832 }, {5811,5843,5822 ,5834,1372,5835 }, + {2958,746,1662 ,1374,2661,5836 }, {3446,5884,3781 ,3718,5848,5849 }, + {6344,4568,4548 ,1450,1492,589 }, {5421,3865,4529 ,5839,5841,5840 }, + {6749,6785,5548 ,4046,4140,1590 }, {5244,5221,5222 ,79,4864,4566 }, + {6056,5883,6080 ,5808,5850,936 }, {5904,1124,5849 ,5851,5852,5853 }, + {5252,5962,4466 ,5746,5745,5854 }, {5121,5167,5120 ,5855,5856,5857 }, + {5167,5190,5166 ,5856,5858,3427 }, {1791,2953,5836 ,5675,5837,5859 }, + {4679,5223,5182 ,3852,3851,4567 }, {5288,5287,5243 ,3993,4003,3994 }, + {4932,4931,5379 ,4580,5066,3998 }, {5515,5898,6115 ,5567,5706,3645 }, + {5117,5515,6115 ,1113,5567,3645 }, {4466,3469,6104 ,5854,5860,5696 }, + {5396,5430,5429 ,3548,5861,5824 }, {5395,5396,5429 ,5348,3548,5824 }, + {5430,5465,5464 ,5861,5862,5825 }, {5429,5430,5464 ,5824,5861,5825 }, + {5465,5507,5506 ,5862,5863,5826 }, {5464,5465,5506 ,5825,5862,5826 }, + {5507,5532,5506 ,5863,5864,5826 }, {5532,5544,5543 ,5864,5865,5828 }, + {5506,5532,5543 ,5826,5864,5828 }, {5544,5584,5583 ,5865,5866,5829 }, + {5543,5544,5583 ,5828,5865,5829 }, {5583,5584,5623 ,5829,5866,5830 }, + {5623,5666,5665 ,5830,5847,5831 }, {5592,746,2224 ,3671,2661,2660 }, + {7538,7573,7602 ,2014,2013,5867 }, {5421,994,3865 ,5839,5838,5841 }, + {4669,4896,4775 ,3879,3878,4388 }, {6045,6056,6080 ,935,5808,936 }, + {5883,5954,5453 ,5850,5868,937 }, {6080,5883,5453 ,936,5850,937 }, + {5954,3657,5923 ,5868,5869,2073 }, {5386,3905,3892 ,5870,4194,4970 }, + {4281,615,1330 ,51,766,53 }, {5167,5205,5190 ,5856,5219,5858 }, + {4771,1791,5836 ,3622,5675,5859 }, {5804,4771,5803 ,3623,3622,5871 }, + {5453,5954,5923 ,937,5868,2073 }, {5461,5984,6113 ,5744,5872,5873 }, + {5894,3608,6113 ,5821,5799,5873 }, {5584,5624,5623 ,5866,5874,5830 }, + {5624,5648,5623 ,5874,5875,5830 }, {5648,5667,5666 ,5875,5876,5847 }, + {5623,5648,5666 ,5830,5875,5847 }, {5667,5708,5707 ,5876,5877,5832 }, + {5666,5667,5707 ,5847,5876,5832 }, {5708,5751,5707 ,5877,5878,5832 }, + {5751,5779,5707 ,5878,5833,5832 }, {5751,5812,5811 ,5878,660,5834 }, + {5779,5751,5811 ,5833,5878,5834 }, {5844,5843,5811 ,5179,1372,5834 }, + {5812,5844,5811 ,660,5179,5834 }, {4724,4908,5245 ,4737,5879,5880 }, + {5984,5894,6113 ,5872,5821,5873 }, {3657,6178,5923 ,5869,5881,2073 }, + {3830,5196,5197 ,5882,5883,4842 }, {4518,3830,5197 ,1281,5882,4842 }, + {4372,994,5197 ,5884,5838,4842 }, {5196,4372,5197 ,5883,5884,4842 }, + {4372,5425,3865 ,5884,5885,5841 }, {994,4372,3865 ,5838,5884,5841 }, + {5425,3083,5357 ,5885,5886,5842 }, {3865,5425,5357 ,5841,5885,5842 }, + {5357,3083,5154 ,5842,5886,5843 }, {3083,1020,4345 ,5886,3817,5844 }, + {5154,3083,4345 ,5843,5886,5844 }, {1020,4712,4820 ,3817,3816,2399 }, + {4345,1020,4820 ,5844,3817,2399 }, {4775,4896,4861 ,4388,3878,3877 }, + {3575,3584,5293 ,5887,5888,5889 }, {3576,5294,5912 ,5890,5891,5892 }, + {2408,309,5231 ,1399,4472,115 }, {4687,320,274 ,4985,1871,1819 }, + {5768,5804,5767 ,933,3623,5893 }, {6178,4901,6162 ,5881,3596,3598 }, + {5923,6178,6162 ,2073,5881,3598 }, {3366,3310,5461 ,5894,5587,5744 }, + {3310,5984,5461 ,5587,5872,5744 }, {7043,2279,7042 ,5895,5337,5338 }, + {5050,3244,1922 ,5233,4433,4432 }, {1196,4669,3266 ,4330,3879,3884 }, + {5989,3310,3366 ,5588,5587,5894 }, {6068,5989,3366 ,5896,5588,5894 }, + {772,1605,713 ,18,17,3 }, {6132,3628,3632 ,5897,4213,5898 }, + {5109,5121,5107 ,4182,5855,4183 }, {5109,5147,5121 ,4182,5899,5855 }, + {5168,5167,5121 ,5900,5856,5855 }, {5147,5168,5121 ,5899,5900,5855 }, + {5206,5205,5167 ,5901,5219,5856 }, {5168,5206,5167 ,5900,5901,5856 }, + {5206,5229,5205 ,5901,5902,5219 }, {7310,7370,7344 ,4283,3750,2749 }, + {5229,4178,5205 ,5902,3825,5219 }, {5305,5306,5572 ,2283,462,2284 }, + {75,1629,2938 ,3962,4207,4320 }, {5976,6028,5927 ,5903,5316,5318 }, + {5364,5397,5396 ,3268,5904,3548 }, {5363,5364,5396 ,3269,3268,3548 }, + {5397,5431,5430 ,5904,5905,5861 }, {5396,5397,5430 ,3548,5904,5861 }, + {5431,5466,5465 ,5905,5906,5862 }, {5430,5431,5465 ,5861,5905,5862 }, + {5466,5467,5465 ,5906,5907,5862 }, {5467,5508,5507 ,5907,5908,5863 }, + {5465,5467,5507 ,5862,5907,5863 }, {5507,5508,5532 ,5863,5908,5864 }, + {5508,5545,5544 ,5908,5909,5865 }, {5532,5508,5544 ,5864,5908,5865 }, + {5545,5585,5584 ,5909,5910,5866 }, {5544,5545,5584 ,5865,5909,5866 }, + {5585,5625,5624 ,5910,5911,5874 }, {5584,5585,5624 ,5866,5910,5874 }, + {5624,5625,5648 ,5874,5911,5875 }, {5625,5668,5667 ,5911,5912,5876 }, + {5648,5625,5667 ,5875,5911,5876 }, {5668,5709,5708 ,5912,5253,5877 }, + {5667,5668,5708 ,5876,5912,5877 }, {5708,5709,5751 ,5877,5253,5878 }, + {1557,5989,6068 ,5577,5588,5896 }, {5812,7038,5823 ,660,3832,661 }, + {3666,1557,6068 ,5699,5577,5896 }, {5115,6060,6281 ,5913,5914,4078 }, + {2279,1313,2251 ,5337,3674,3676 }, {3943,5662,4045 ,4755,5915,5916 }, + {5255,5276,6480 ,3682,1525,1524 }, {3669,1196,3266 ,5917,4330,3884 }, + {6239,6055,6073 ,5918,3874,3873 }, {6238,6239,6073 ,5919,5918,3873 }, + {3469,5651,6104 ,5860,5920,5696 }, {5651,5254,6104 ,5920,4042,5696 }, + {5403,3631,5417 ,5921,5922,5923 }, {5489,5911,5932 ,4244,5924,5925 }, + {5474,5370,5932 ,4243,2171,5925 }, {5804,5803,5767 ,3623,5871,5893 }, + {5108,5107,5106 ,4184,4183,5926 }, {5169,5168,5147 ,5927,5900,5899 }, + {5109,5169,5147 ,4182,5927,5899 }, {5206,4178,5229 ,5901,3825,5902 }, + {2713,3141,4091 ,4352,4701,123 }, {4771,5836,5803 ,3622,5859,5871 }, + {5585,5607,5625 ,5910,5928,5911 }, {5709,5752,5751 ,5253,4374,5878 }, + {6268,1661,3906 ,5757,5672,5697 }, {5261,5538,3830 ,173,5929,5882 }, + {5538,3417,5196 ,5929,5930,5883 }, {3830,5538,5196 ,5882,5929,5883 }, + {5500,4372,5196 ,5931,5884,5883 }, {3417,5500,5196 ,5930,5931,5883 }, + {5500,5821,5425 ,5931,5932,5885 }, {4372,5500,5425 ,5884,5931,5885 }, + {5821,4072,5425 ,5932,5933,5885 }, {4072,1938,3083 ,5933,5934,5886 }, + {5425,4072,3083 ,5885,5933,5886 }, {1938,4027,1020 ,5934,3470,3817 }, + {3083,1938,1020 ,5886,5934,3817 }, {5593,5552,5594 ,5935,5936,5937 }, + {3321,7437,7386 ,2461,5938,5512 }, {3899,1196,3669 ,1938,4330,5917 }, + {5185,3395,6142 ,5939,5940,5941 }, {5579,5564,4565 ,5942,5943,5944 }, + {5169,5206,5168 ,5927,5901,5900 }, {5321,5364,5346 ,5668,3268,3267 }, + {5364,5398,5397 ,3268,5945,5904 }, {5398,5432,5431 ,5945,5946,5905 }, + {5397,5398,5431 ,5904,5945,5905 }, {5432,5467,5466 ,5946,5907,5906 }, + {5431,5432,5466 ,5905,5946,5906 }, {6905,6957,5710 ,2506,4315,5254 }, + {7268,7317,7316 ,3773,5947,5948 }, {8078,8124,8076 ,1744,1141,617 }, + {4572,3693,3638 ,5949,5347,5950 }, {5390,5579,5415 ,3205,5942,5951 }, + {4763,4764,4797 ,4787,3982,3984 }, {5148,5169,5109 ,5952,5927,4182 }, + {5110,5148,5109 ,5953,5952,4182 }, {5207,5206,5169 ,5954,5901,5927 }, + {5148,5207,5169 ,5952,5954,5927 }, {5249,4178,5206 ,5955,3825,5901 }, + {5207,5249,5206 ,5954,5955,5901 }, {5249,5272,4178 ,5955,5956,3825 }, + {5296,5297,5323 ,4296,3826,5701 }, {5347,5321,5322 ,5957,5668,4297 }, + {5323,5347,5322 ,5701,5957,4297 }, {5365,5364,5321 ,5958,3268,5668 }, + {5347,5365,5321 ,5957,5958,5668 }, {5365,5399,5398 ,5958,5959,5945 }, + {5364,5365,5398 ,3268,5958,5945 }, {5399,5433,5432 ,5959,5960,5946 }, + {5398,5399,5432 ,5945,5959,5946 }, {5433,5468,5467 ,5960,5961,5907 }, + {5432,5433,5467 ,5946,5960,5907 }, {5468,5509,5508 ,5961,5962,5908 }, + {5467,5468,5508 ,5907,5961,5908 }, {5509,5546,5545 ,5962,5963,5909 }, + {5508,5509,5545 ,5908,5962,5909 }, {5546,5586,5585 ,5963,1461,5910 }, + {5545,5546,5585 ,5909,5963,5910 }, {5585,5586,5607 ,5910,1461,5928 }, + {5586,5626,5625 ,1461,1460,5911 }, {5607,5586,5625 ,5928,1461,5911 }, + {5625,5626,5668 ,5911,1460,5912 }, {5668,5669,5709 ,5912,5553,5253 }, + {5293,3558,3629 ,5889,5964,5965 }, {5731,5768,5729 ,934,933,200 }, + {5768,5767,5729 ,933,5893,200 }, {5326,6041,5327 ,3714,3801,3702 }, + {5146,3760,1868 ,1075,2250,2249 }, {5731,5729,5730 ,934,200,199 }, + {5731,5730,3615 ,934,199,201 }, {3952,2713,3259 ,5597,4352,407 }, + {1156,5138,5128 ,3100,1940,5966 }, {5500,4072,5821 ,5931,5933,5932 }, + {7837,4897,3895 ,5967,5442,4455 }, {3760,3899,3669 ,2250,1938,5917 }, + {184,4295,4016 ,4052,5968,5969 }, {1064,5579,5390 ,5970,5942,3205 }, + {7166,5110,5109 ,5971,5953,4182 }, {5546,5547,5586 ,5963,5972,1461 }, + {6234,6167,6002 ,5973,5974,5975 }, {5243,5287,5265 ,3994,4003,4882 }, + {3221,5262,5261 ,183,5976,173 }, {5352,3950,3345 ,3502,4530,3476 }, + {5262,5527,5538 ,5976,5977,5929 }, {5261,5262,5538 ,173,5976,5929 }, + {5527,5746,3417 ,5977,5978,5930 }, {5538,5527,3417 ,5929,5977,5930 }, + {5619,5500,3417 ,5979,5931,5930 }, {5746,5619,3417 ,5978,5979,5930 }, + {5619,1671,4072 ,5979,5980,5933 }, {5500,5619,4072 ,5931,5979,5933 }, + {1671,3249,1938 ,5980,5110,5934 }, {4072,1671,1938 ,5933,5980,5934 }, + {572,4873,611 ,1947,4992,1948 }, {6128,1620,6129 ,5981,5982,5983 }, + {4299,1064,5390 ,5984,5970,3205 }, {5446,4299,5390 ,5985,5984,3205 }, + {4299,5579,1064 ,5984,5942,5970 }, {4299,1002,5579 ,5984,2976,5942 }, + {5323,5324,5348 ,5701,5986,5987 }, {5509,5547,5546 ,5962,5972,5963 }, + {4827,5259,4826 ,2734,3794,5413 }, {3249,4905,4594 ,5110,5112,688 }, + {5272,5298,5297 ,5956,3818,3826 }, {5619,5735,1671 ,5979,5988,5980 }, + {6803,5587,5548 ,4169,1459,1590 }, {4863,5378,4904 ,5480,359,3309 }, + {5260,5146,1868 ,4174,1075,2249 }, {5149,5148,5110 ,5989,5952,5953 }, + {5111,5149,5110 ,5990,5989,5953 }, {5208,5207,5148 ,5991,5954,5952 }, + {5149,5208,5148 ,5989,5991,5952 }, {5250,5249,5207 ,5992,5955,5954 }, + {5208,5250,5207 ,5991,5992,5954 }, {5273,5272,5249 ,3819,5956,5955 }, + {5250,5273,5249 ,5992,3819,5955 }, {7683,7704,7682 ,5267,3562,3564 }, + {5348,5347,5323 ,5987,5957,5701 }, {5325,5323,5297 ,5655,5701,3826 }, + {5366,5365,5347 ,5993,5958,5957 }, {5348,5366,5347 ,5987,5993,5957 }, + {5366,5400,5399 ,5993,5994,5959 }, {5365,5366,5399 ,5958,5993,5959 }, + {5400,5434,5433 ,5994,5995,5960 }, {5399,5400,5433 ,5959,5994,5960 }, + {5434,5469,5468 ,5995,5996,5961 }, {5433,5434,5468 ,5960,5995,5961 }, + {5469,5510,5509 ,5996,5997,5962 }, {5468,5469,5509 ,5961,5996,5962 }, + {5510,5511,5509 ,5997,1588,5962 }, {5511,5548,5547 ,1588,1590,5972 }, + {5509,5511,5547 ,5962,1588,5972 }, {5548,5587,5586 ,1590,1459,1461 }, + {5547,5548,5586 ,5972,1590,1461 }, {5860,2321,5951 ,5998,5999,6000 }, + {3620,5991,5946 ,6001,5482,5481 }, {6907,6959,6958 ,6002,6003,4314 }, + {4696,5770,4793 ,5382,3949,5421 }, {6163,5889,6175 ,1453,1587,6004 }, + {5128,2217,5443 ,5966,4569,6005 }, {5266,5243,5221 ,81,3994,4864 }, + {5340,4868,5379 ,4002,3969,3998 }, {5223,5244,5222 ,3851,79,4566 }, + {3221,5527,5262 ,183,5977,5976 }, {3278,8113,1724 ,1158,1157,3880 }, + {5010,2140,5426 ,6006,5635,5634 }, {1166,1119,5844 ,2746,5180,5179 }, + {2375,3945,922 ,4479,4550,117 }, {1116,3350,5497 ,1401,2541,1375 }, + {1488,4264,156 ,5197,5540,5539 }, {3973,4299,5446 ,6007,5984,5985 }, + {5209,5208,5149 ,6008,5991,5989 }, {7566,7621,7565 ,6009,6010,5080 }, + {7621,7647,7620 ,6010,4967,5033 }, {5324,5366,5348 ,5986,5993,5987 }, + {5469,5511,5510 ,5996,1588,5997 }, {6208,6209,6243 ,6011,6012,5666 }, + {5933,3548,5934 ,615,6013,616 }, {3548,5489,5934 ,6013,4244,616 }, + {4278,4260,6138 ,5583,3527,6014 }, {3878,789,3419 ,1248,3910,2012 }, + {1119,1166,2408 ,5180,2746,1399 }, {156,22,1297 ,5539,4066,4048 }, + {5325,5298,6041 ,5655,3818,3801 }, {156,1934,22 ,5539,4620,4066 }, + {1218,3820,5498 ,3255,5591,5590 }, {6736,6754,6703 ,1622,713,5161 }, + {7923,7896,7897 ,816,4355,6015 }, {5772,5746,5527 ,6016,5978,5977 }, + {3984,5772,5527 ,6017,6016,5977 }, {5772,5577,5619 ,6016,6018,5979 }, + {5746,5772,5619 ,5978,6016,5979 }, {5577,4228,5735 ,6018,6019,5988 }, + {5619,5577,5735 ,5979,6018,5988 }, {4228,1099,1671 ,6019,4350,5980 }, + {5735,4228,1671 ,5988,6019,5980 }, {1099,4905,3249 ,4350,5112,5110 }, + {1671,1099,3249 ,5980,4350,5110 }, {3766,7556,4636 ,3954,5116,5118 }, + {3249,5036,4905 ,5110,399,5112 }, {3941,4299,3973 ,5563,5984,6007 }, + {636,3941,3973 ,6020,5563,6007 }, {3941,1511,4299 ,5563,4921,5984 }, + {5158,4631,4599 ,4832,1816,4210 }, {5112,5149,5111 ,6021,5989,5990 }, + {5273,5250,5251 ,3819,5992,3802 }, {2690,303,4296 ,6022,4086,6023 }, + {4191,2690,4296 ,4474,6022,6023 }, {6151,5860,5951 ,6024,5998,6000 }, + {6035,5654,4045 ,6025,6026,5916 }, {1741,6230,5947 ,5695,2144,6027 }, + {5948,5891,5602 ,5719,5714,5716 }, {5979,5929,6132 ,6028,6029,5897 }, + {798,5378,4863 ,78,359,5480 }, {8,1798,4522 ,3341,4451,3481 }, + {5218,5239,5238 ,483,482,4351 }, {5179,5199,5178 ,1879,422,524 }, + {5179,5178,5136 ,1879,524,1817 }, {2797,1935,303 ,6030,4098,4086 }, + {2690,2797,303 ,6022,6030,4086 }, {3150,636,2960 ,6031,6020,4975 }, + {5388,2960,636 ,5428,4975,6020 }, {5150,5149,5112 ,6032,5989,6021 }, + {5113,5150,5112 ,141,6032,6021 }, {5150,5170,5149 ,6032,3683,5989 }, + {5210,5209,5149 ,6033,6008,5989 }, {5170,5210,5149 ,3683,6033,5989 }, + {5210,5208,5209 ,6033,5991,6008 }, {5251,5250,5208 ,3802,5992,5991 }, + {5210,5251,5208 ,6033,3802,5991 }, {3985,3172,1935 ,4802,4155,4098 }, + {2797,3985,1935 ,6030,4802,4098 }, {5349,5324,5325 ,5320,5986,5655 }, + {3482,8106,3449 ,2494,2438,2440 }, {5367,5366,5324 ,6034,5993,5986 }, + {5349,5367,5324 ,5320,6034,5986 }, {5367,5401,5400 ,6034,4145,5994 }, + {5366,5367,5400 ,5993,6034,5994 }, {5401,5435,5434 ,4145,4223,5995 }, + {5400,5401,5434 ,5994,4145,5995 }, {5435,5470,5469 ,4223,6035,5996 }, + {5434,5435,5469 ,5995,4223,5996 }, {5470,5471,5469 ,6035,2051,5996 }, + {5469,5471,5511 ,5996,2051,1588 }, {5652,5686,5651 ,5774,5777,5920 }, + {5651,5747,5254 ,5920,6036,4042 }, {3525,5361,5935 ,5665,5664,5778 }, + {6051,5979,6132 ,6037,6028,5897 }, {6040,6028,5246 ,5317,5316,5176 }, + {5853,5318,6023 ,6038,6039,6040 }, {3731,5922,6075 ,6041,4292,6042 }, + {4654,5200,4679 ,4348,5656,3852 }, {4866,4914,3907 ,3641,65,3883 }, + {4916,4929,4915 ,2248,5142,64 }, {1525,2086,4314 ,1866,1802,1864 }, + {3985,3858,3093 ,4802,4803,4172 }, {3172,3985,3093 ,4155,4802,4172 }, + {5772,4228,5577 ,6016,6019,6018 }, {495,4775,4745 ,4389,4388,5446 }, + {5052,4845,5070 ,5189,3921,6043 }, {789,1156,3858 ,3910,3100,4803 }, + {3858,5128,5119 ,4803,5966,4190 }, {3093,3858,5119 ,4172,4803,4190 }, + {59,145,2689 ,967,810,2821 }, {5435,5471,5470 ,4223,2051,6035 }, + {6246,6247,3666 ,5559,5575,5699 }, {6166,6152,7034 ,4971,6044,6045 }, + {3835,5878,5550 ,6046,2684,6047 }, {6044,5980,5294 ,614,5557,5891 }, + {5889,6339,6034 ,1587,6048,6049 }, {3249,5028,1938 ,5110,5109,5934 }, + {6131,6132,3632 ,6050,5897,5898 }, {6185,5386,3892 ,6051,5870,4970 }, + {5749,5895,3444 ,6052,6053,6054 }, {5490,5749,3444 ,6055,6052,6054 }, + {5325,5326,5349 ,5655,3714,5320 }, {5644,5643,5571 ,4133,4617,4616 }, + {5495,5644,5571 ,4134,4133,4616 }, {5128,5443,5424 ,5966,6005,4227 }, + {3345,4226,835 ,3476,6056,3030 }, {5684,3345,835 ,4683,3476,3030 }, + {4226,1986,3984 ,6056,6057,6017 }, {835,4226,3984 ,3030,6056,6017 }, + {1986,2345,5772 ,6057,6058,6016 }, {3984,1986,5772 ,6017,6057,6016 }, + {2345,5187,4228 ,6058,5144,6019 }, {5772,2345,4228 ,6016,6058,6019 }, + {1111,1099,4228 ,3920,4350,6019 }, {5187,1111,4228 ,5144,3920,6019 }, + {68,2432,3818 ,4087,5427,1244 }, {5119,5128,5424 ,4190,5966,4227 }, + {5443,2217,201 ,6005,4569,4230 }, {5424,5443,201 ,4227,6005,4230 }, + {556,3150,2960 ,4043,6031,4975 }, {556,636,3150 ,4043,6020,6031 }, + {1344,3941,636 ,300,5563,6020 }, {556,1344,636 ,4043,300,6020 }, + {8020,8019,7972 ,1405,1407,6059 }, {2217,3038,2643 ,4569,4342,4232 }, + {201,2217,2643 ,4230,4569,4232 }, {7897,7896,7845 ,6015,4355,5725 }, + {3856,3562,6013 ,5754,4753,6060 }, {6247,1557,3666 ,5575,5577,5699 }, + {6098,6174,6173 ,1565,2381,3711 }, {5790,5813,5104 ,6061,6062,6063 }, + {5755,5790,5104 ,4047,6061,6063 }, {5086,5104,5813 ,6064,6063,6062 }, + {5813,5846,5086 ,6062,6065,6064 }, {5846,4225,257 ,6065,6066,6067 }, + {5714,5715,7028 ,4077,4076,3587 }, {4225,2871,257 ,6066,3649,6067 }, + {5461,3608,5962 ,5744,5799,5745 }, {5644,5660,5643 ,4133,3054,4617 }, + {3038,1341,2634 ,4342,3841,4269 }, {3345,4052,4226 ,3476,6068,6056 }, + {4226,4052,1986 ,6056,6068,6057 }, {1224,2986,1201 ,1508,3500,1477 }, + {3858,1156,5128 ,4803,3100,5966 }, {2643,3038,2634 ,4232,4342,4269 }, + {3788,3672,2634 ,4410,4277,4269 }, {1341,3788,2634 ,3841,4410,4269 }, + {5729,5728,5700 ,200,4486,4501 }, {210,194,262 ,4592,4593,4758 }, + {145,59,113 ,810,967,677 }, {4681,5200,4654 ,4958,5656,4348 }, + {3626,3580,3557 ,4196,6069,6070 }, {4894,1750,1810 ,746,745,779 }, + {4764,4763,4706 ,3982,4787,4786 }, {284,4086,4283 ,139,3723,1678 }, + {5211,5210,5170 ,6071,6033,3683 }, {7873,7923,7897 ,817,816,6015 }, + {5957,202,6063 ,4054,4105,5685 }, {8109,8116,5521 ,2938,172,751 }, + {1700,807,3563 ,6072,6073,6074 }, {3568,1941,3529 ,3839,1034,3837 }, + {5694,5716,5715 ,5326,6075,4076 }, {5716,5756,5755 ,6075,6076,4047 }, + {5715,5716,5755 ,4076,6075,4047 }, {5755,5756,5790 ,4047,6076,6061 }, + {5756,5791,5813 ,6076,6077,6062 }, {5790,5756,5813 ,6061,6076,6062 }, + {5791,5847,5846 ,6077,6078,6065 }, {5813,5791,5846 ,6062,6077,6065 }, + {5847,32,4225 ,6078,6079,6066 }, {5846,5847,4225 ,6065,6078,6066 }, + {32,181,2871 ,6079,6080,3649 }, {4225,32,2871 ,6066,6079,3649 }, + {5151,5125,5171 ,1803,5467,1804 }, {181,712,2871 ,6080,3650,3649 }, + {6242,6270,6269 ,6081,6082,6083 }, {1191,1689,1195 ,5816,5792,725 }, + {1689,4490,4347 ,5792,5688,3287 }, {1586,1191,1195 ,943,5816,725 }, + {612,611,4873 ,5011,1948,4992 }, {3615,5729,5700 ,201,200,4501 }, + {5295,556,808 ,2188,4043,4734 }, {2953,212,5835 ,5837,4614,4573 }, + {5836,2953,5835 ,5859,5837,4573 }, {4599,5182,5158 ,4210,4567,4832 }, + {4166,1016,1251 ,6084,6085,4604 }, {5788,5579,1002 ,6086,5942,2976 }, + {5316,5588,5550 ,6087,6088,6047 }, {6184,1170,1620 ,6089,4103,5982 }, + {6989,5694,5693 ,4586,5326,4075 }, {5716,5744,5756 ,6075,6090,6076 }, + {4106,5684,835 ,1742,4683,3030 }, {5924,6042,5865 ,6091,5703,6092 }, + {5649,5924,5865 ,6093,6091,6092 }, {6000,5851,3870 ,6094,6095,6096 }, + {4490,2954,3867 ,5688,5687,4507 }, {4347,4490,3867 ,3287,5688,4507 }, + {6644,4950,4981 ,5098,1302,1350 }, {3915,3939,4307 ,1575,3539,4562 }, + {3950,3799,4052 ,4530,4531,6068 }, {3345,3950,4052 ,3476,4530,6068 }, + {3799,3282,1986 ,4531,6097,6057 }, {4052,3799,1986 ,6068,4531,6057 }, + {3282,3769,2345 ,6097,6098,6058 }, {1986,3282,2345 ,6057,6097,6058 }, + {3769,5070,5187 ,6098,6043,5144 }, {2345,3769,5187 ,6058,6098,5144 }, + {4681,4714,5224 ,4958,1031,3850 }, {1005,68,3818 ,3535,4087,1244 }, + {5138,5260,5128 ,1940,4174,5966 }, {5767,5766,5728 ,5893,4487,4486 }, + {5729,5767,5728 ,200,5893,4486 }, {5143,1416,297 ,6099,6100,1918 }, + {1416,1455,140 ,6100,1917,1919 }, {4519,5202,1455 ,4746,5550,1917 }, + {1416,4519,1455 ,6100,4746,1917 }, {4519,4491,5202 ,4746,107,5550 }, + {4491,4472,808 ,107,2189,4734 }, {4710,4770,4769 ,4136,4345,5542 }, + {3580,3626,3605 ,6069,4196,3383 }, {5966,297,1455 ,5054,1918,1917 }, + {7745,7795,7744 ,650,3804,6101 }, {6255,6256,6275 ,6102,5657,5576 }, + {6247,6255,6275 ,5575,6102,5576 }, {5782,3836,3385 ,5693,6103,6104 }, + {2990,1519,601 ,3646,2450,3905 }, {6256,5953,1557 ,5657,5659,5577 }, + {6275,6256,1557 ,5576,5657,5577 }, {5756,5792,5791 ,6076,6105,6077 }, + {5852,2213,32 ,6106,6107,6079 }, {5847,5852,32 ,6078,6106,6079 }, + {4322,181,32 ,6108,6080,6079 }, {2213,4322,32 ,6107,6108,6079 }, + {5851,5881,3870 ,6095,6109,6096 }, {1700,3563,2896 ,6072,6074,6110 }, + {4770,5263,4830 ,4345,4302,3642 }, {309,2375,5231 ,4472,4479,115 }, + {4697,5099,3800 ,4327,5494,4325 }, {882,8117,3264 ,3846,4119,2468 }, + {3769,5736,5070 ,6098,6111,6043 }, {1937,4796,4273 ,59,4756,120 }, + {5441,5143,297 ,5194,6099,1918 }, {4447,5143,3399 ,717,6099,715 }, + {670,262,312 ,4760,4758,4759 }, {6158,6284,6034 ,6112,6113,6049 }, + {543,3266,495 ,3840,3884,4389 }, {7073,1166,5844 ,662,2746,5179 }, + {1689,2004,4490 ,5792,5747,5688 }, {5767,5803,5766 ,5893,5871,4487 }, + {5803,5802,5766 ,5871,4681,4487 }, {5819,5818,5802 ,6114,4640,4681 }, + {5803,5819,5802 ,5871,6114,4681 }, {5819,5836,5835 ,6114,5859,4573 }, + {5818,5819,5835 ,4640,6114,4573 }, {2953,3986,1208 ,5837,5817,3211 }, + {212,2953,1208 ,4614,5837,3211 }, {1191,1586,1208 ,5816,943,3211 }, + {3986,1191,1208 ,5817,5816,3211 }, {5953,3870,3310 ,5659,6096,5587 }, + {1557,5953,3310 ,5577,5659,5587 }, {5946,3415,5907 ,5481,6115,6116 }, + {3217,5008,2342 ,6117,6118,5690 }, {3870,3686,5984 ,6096,6119,5872 }, + {3310,3870,5984 ,5587,6096,5872 }, {3686,5915,5894 ,6119,6120,5821 }, + {5984,3686,5894 ,5872,6119,5821 }, {6091,5328,5384 ,2173,2172,6121 }, + {6003,6004,5871 ,6122,6123,4124 }, {5328,5301,5384 ,2172,2698,6121 }, + {3715,3018,5851 ,6124,6125,6095 }, {5718,5757,5756 ,6126,6127,6076 }, + {5943,5514,6009 ,5402,6128,6129 }, {5757,5793,5792 ,6127,6130,6105 }, + {5756,5757,5792 ,6076,6127,6105 }, {5793,5791,5792 ,6130,6077,6105 }, + {5793,5825,5847 ,6130,6131,6078 }, {5791,5793,5847 ,6077,6130,6078 }, + {5847,5825,5852 ,6078,6131,6106 }, {5825,445,2213 ,6131,6132,6107 }, + {5852,5825,2213 ,6106,6131,6107 }, {1456,4322,2213 ,6133,6108,6107 }, + {445,1456,2213 ,6132,6133,6107 }, {1456,181,4322 ,6133,6080,6108 }, + {852,712,181 ,6134,3650,6080 }, {1456,852,181 ,6133,6134,6080 }, + {852,4230,712 ,6134,6135,3650 }, {4249,3702,712 ,6136,1303,3650 }, + {4230,4249,712 ,6135,6136,3650 }, {3606,3607,4164 ,6137,6138,6139 }, + {7873,7846,7847 ,817,4162,3521 }, {5477,5789,6036 ,5399,6140,6141 }, + {5905,5528,5820 ,6142,6143,6144 }, {1538,1101,3867 ,0,2,4507 }, + {4805,4645,3367 ,5376,5378,5798 }, {6950,6644,4981 ,1349,5098,1350 }, + {5274,5300,5251 ,6145,3793,3802 }, {2158,6639,4983 ,1160,3699,480 }, + {6300,6301,6708 ,6146,6147,6148 }, {5342,5143,4447 ,6149,6099,717 }, + {5685,1416,5143 ,6150,6100,6099 }, {5036,3249,4905 ,399,5110,5112 }, + {4830,4829,4770 ,3642,4626,4345 }, {5615,3001,3274 ,3171,3210,3172 }, + {6223,6254,6055 ,6151,5758,3874 }, {5898,5662,5653 ,5706,5915,3644 }, + {6243,6271,6270 ,5666,6152,6082 }, {5610,5609,6248 ,6153,5658,6154 }, + {1028,5843,5844 ,1373,1372,5179 }, {5915,6072,2568 ,6120,6155,5807 }, + {5894,5915,2568 ,5821,6120,5807 }, {6207,6208,6242 ,6156,6011,6081 }, + {7621,7620,7565 ,6010,5033,5080 }, {5718,5719,5757 ,6126,6157,6127 }, + {4231,4249,4230 ,6158,6136,6135 }, {852,4231,4230 ,6134,6158,6135 }, + {7648,7647,7621 ,3952,4967,6010 }, {4231,1185,4249 ,6158,6159,6136 }, + {5915,5905,5820 ,6120,6142,6144 }, {5528,5712,5975 ,6143,6160,6161 }, + {1700,6266,3563 ,6072,399,6074 }, {180,8,131 ,4777,3341,2965 }, + {4400,637,3299 ,4721,3805,3232 }, {5975,5391,2568 ,6161,5773,5807 }, + {1410,5540,3893 ,3911,6162,3789 }, {6072,5975,2568 ,6155,6161,5807 }, + {5253,6046,5391 ,6163,5775,5773 }, {5353,1342,3799 ,851,6164,4531 }, + {5352,5353,3799 ,3502,851,4531 }, {1342,3255,3282 ,6164,6165,6097 }, + {3799,1342,3282 ,4531,6164,6097 }, {3255,1527,3877 ,6165,6166,6167 }, + {3282,3255,3877 ,6097,6165,6167 }, {3255,3282,3877 ,6165,6097,6167 }, + {1527,3255,3877 ,6166,6165,6167 }, {3255,4013,3769 ,6165,6168,6098 }, + {3282,3255,3769 ,6097,6165,6098 }, {4013,4220,5736 ,6168,6169,6111 }, + {3769,4013,5736 ,6098,6168,6111 }, {4220,5052,5070 ,6169,5189,6043 }, + {5736,4220,5070 ,6111,6169,6043 }, {3669,3266,1783 ,5917,3884,2251 }, + {3800,1248,1907 ,4325,2850,1245 }, {1524,3800,1907 ,4326,4325,1245 }, + {5685,774,1416 ,6150,767,6100 }, {5222,5221,5182 ,4566,4864,4567 }, + {2432,1524,3818 ,5427,4326,1244 }, {5128,5260,2217 ,5966,4174,4569 }, + {4883,4801,5127 ,5538,5115,5114 }, {1195,1689,4347 ,725,5792,3287 }, + {4550,5563,4447 ,716,4605,717 }, {5482,5342,4447 ,3565,6149,717 }, + {5126,5482,4447 ,6170,3565,717 }, {5423,5143,5342 ,6171,6099,6149 }, + {5482,5423,5342 ,3565,6171,6149 }, {1634,5685,5143 ,768,6150,6099 }, + {5423,1634,5143 ,6171,768,6099 }, {1416,774,104 ,6100,767,4765 }, + {8052,3638,8051 ,4122,5950,6172 }, {2659,601,2622 ,3485,3905,2840 }, + {3800,3817,1248 ,4325,1189,2850 }, {4872,4697,1524 ,5522,4327,4326 }, + {2954,1538,3867 ,5687,0,4507 }, {5356,5909,4167 ,4258,4260,6173 }, + {5654,6188,5916 ,6026,6174,6175 }, {5975,5253,5391 ,6161,6163,5773 }, + {2342,5008,2273 ,5690,6118,5322 }, {5896,5713,6046 ,6176,5776,5775 }, + {5253,5896,6046 ,6163,6176,5775 }, {564,5309,507 ,3529,987,1523 }, + {5611,5634,5633 ,6177,6178,6179 }, {6949,5634,5672 ,4585,6178,5324 }, + {5633,5634,6949 ,6179,6178,4585 }, {6088,6082,6069 ,3670,1348,6180 }, + {5672,5719,5718 ,5324,6157,6126 }, {5848,2994,445 ,6181,6182,6132 }, + {5825,5848,445 ,6131,6181,6132 }, {2994,1456,445 ,6182,6133,6132 }, + {3703,1185,4231 ,6183,6159,6158 }, {5820,5528,5975 ,6144,6143,6161 }, + {6532,3547,3494 ,6184,803,736 }, {1516,4233,3177 ,1254,6185,1255 }, + {3494,3495,6532 ,736,735,6184 }, {7422,7478,7450 ,1404,1402,399 }, + {5160,3524,5969 ,6186,6187,6188 }, {3018,5530,5851 ,6125,6189,6095 }, + {5965,5329,6076 ,6190,6191,4217 }, {5886,6087,6084 ,6192,6193,6194 }, + {3217,2342,2273 ,6117,5690,5322 }, {3829,3217,2273 ,6195,6117,5322 }, + {5713,5896,4648 ,5776,6176,5722 }, {3562,3943,6013 ,4753,4755,6060 }, + {5353,5733,1342 ,851,1584,6164 }, {1342,1527,3255 ,6164,6166,6165 }, + {4013,3255,1527 ,6168,6165,6166 }, {5244,5266,5221 ,79,81,4864 }, + {3817,1207,1248 ,1189,1074,2850 }, {5685,1634,774 ,6150,768,767 }, + {7509,7508,7479 ,1874,6196,4423 }, {1251,5126,5563 ,4604,6170,4605 }, + {2661,5126,1251 ,4684,6170,4604 }, {5482,1634,5423 ,3565,768,6171 }, + {3816,6577,5445 ,5623,97,3520 }, {3244,5050,993 ,4433,5233,5551 }, + {1376,4788,1196 ,1937,3758,4330 }, {1700,81,323 ,6072,4673,4767 }, + {3939,4361,4307 ,3539,4584,4562 }, {4321,4343,4342 ,4873,1818,4950 }, + {5980,6091,6010 ,5557,2173,6197 }, {1426,1017,3452 ,4648,1793,1794 }, + {6175,3620,6163 ,6004,6001,1453 }, {5331,5371,5350 ,6198,3558,1526 }, + {5331,5372,5371 ,6198,6199,3558 }, {5372,5406,5405 ,6199,6200,5518 }, + {5371,5372,5405 ,3558,6199,5518 }, {5406,5436,5405 ,6200,6201,5518 }, + {5008,2342,2273 ,6118,5690,5322 }, {5594,5612,5611 ,5937,6202,6177 }, + {5593,5594,5611 ,5935,5937,6177 }, {5612,5635,5634 ,6202,6203,6178 }, + {5611,5612,5634 ,6177,6202,6178 }, {5635,5673,5672 ,6203,6204,5324 }, + {5634,5635,5672 ,6178,6203,5324 }, {5673,5674,5672 ,6204,6205,5324 }, + {5674,5720,5719 ,6205,6206,6157 }, {5672,5674,5719 ,5324,6205,6157 }, + {5720,5758,5757 ,6206,6207,6127 }, {5719,5720,5757 ,6157,6206,6127 }, + {5758,5794,5793 ,6207,6208,6130 }, {5757,5758,5793 ,6127,6207,6130 }, + {5794,5826,5825 ,6208,6209,6131 }, {5793,5794,5825 ,6130,6208,6131 }, + {5825,5826,5848 ,6131,6209,6181 }, {5826,3145,2994 ,6209,6210,6182 }, + {5848,5826,2994 ,6181,6209,6182 }, {3940,1456,2994 ,6211,6133,6182 }, + {3145,3940,2994 ,6210,6211,6182 }, {1598,852,1456 ,6212,6134,6133 }, + {3940,1598,1456 ,6211,6212,6133 }, {3928,4231,852 ,6213,6158,6134 }, + {1598,3928,852 ,6212,6213,6134 }, {3587,3703,4231 ,6214,6183,6158 }, + {3928,3587,4231 ,6213,6214,6158 }, {2861,1185,3703 ,6215,6159,6183 }, + {3587,2861,3703 ,6214,6215,6183 }, {1988,1516,1185 ,6216,1254,6159 }, + {2861,1988,1185 ,6215,6216,6159 }, {4295,4233,1516 ,5968,6185,1254 }, + {1988,4295,1516 ,6216,5968,1254 }, {4295,4324,4233 ,5968,6217,6185 }, + {733,1026,4233 ,4051,6218,6185 }, {4324,733,4233 ,6217,4051,6185 }, + {7220,7239,7219 ,4367,1527,1529 }, {7186,7203,7185 ,5370,4366,5073 }, + {3781,5884,5451 ,5849,5848,6219 }, {5879,5987,6033 ,6220,5621,5620 }, + {5969,5965,6076 ,6188,6190,4217 }, {6001,6095,6081 ,6221,6222,6223 }, + {5869,5886,6084 ,6224,6192,6194 }, {4094,4642,5670 ,6225,6226,6227 }, + {3497,747,5319 ,5738,5692,5739 }, {6010,6016,5692 ,6197,6228,6229 }, + {5734,4467,1342 ,6230,6231,6164 }, {5733,5734,1342 ,1584,6230,6164 }, + {4467,602,1527 ,6231,6232,6166 }, {1342,4467,1527 ,6164,6231,6166 }, + {4220,4013,1527 ,6169,6168,6166 }, {602,4220,1527 ,6232,6169,6166 }, + {1537,1745,4191 ,4473,6233,4474 }, {1251,306,4166 ,4604,6234,6084 }, + {2661,5482,5126 ,4684,3565,6170 }, {3266,4669,4775 ,3884,3879,4388 }, + {1745,1444,4191 ,6233,4035,4474 }, {6242,6243,6270 ,6081,5666,6082 }, + {5171,5195,6114 ,1804,6235,1805 }, {5278,5277,5232 ,6236,6237,6238 }, + {5533,4010,6172 ,6239,4343,5451 }, {4465,4444,6210 ,257,582,5566 }, + {5845,6234,6174 ,143,5973,2381 }, {3943,5860,5956 ,4755,5998,6240 }, + {5636,5674,5673 ,6241,6205,6204 }, {5635,5636,5673 ,6203,6241,6204 }, + {5720,5759,5758 ,6206,6242,6207 }, {5758,5759,5794 ,6207,6242,6208 }, + {3942,3928,1598 ,6243,6213,6212 }, {3942,3587,3928 ,6243,6214,6213 }, + {184,733,4324 ,4052,4051,6217 }, {4295,184,4324 ,5968,4052,6217 }, + {7462,7435,7409 ,4310,6244,6245 }, {5973,6110,6081 ,6246,6247,6223 }, + {5711,5879,6033 ,6248,6220,5620 }, {2837,75,930 ,2511,3962,3594 }, + {6001,5996,6156 ,6221,6249,6250 }, {6001,6181,5705 ,6221,6251,4127 }, + {6081,3395,5185 ,6223,5940,5939 }, {5927,6040,5227 ,5318,5317,4097 }, + {6197,6223,6222 ,6252,6151,6253 }, {4026,4467,5734 ,6254,6231,6230 }, + {4261,4026,5734 ,1583,6254,6230 }, {4467,4026,602 ,6231,6254,6232 }, + {5459,4220,602 ,6255,6169,6232 }, {5459,993,5052 ,6255,5551,5189 }, + {4220,5459,5052 ,6169,6255,5189 }, {4908,4724,4774 ,5879,4737,5332 }, + {252,5526,966 ,2743,6256,2565 }, {3838,1016,4166 ,6257,6085,6084 }, + {5127,2661,1251 ,5114,4684,4604 }, {1016,5127,1251 ,6085,5114,4604 }, + {5127,124,2661 ,5114,2206,4684 }, {125,124,5127 ,399,2206,5114 }, + {1790,4949,5245 ,4738,5526,5880 }, {1444,3318,2690 ,4035,4036,6022 }, + {4191,1444,2690 ,4474,4035,6022 }, {3318,3419,2797 ,4036,2012,6030 }, + {2659,2622,2901 ,3485,2840,3468 }, {5609,6135,5953 ,5658,6258,5659 }, + {5278,5331,5277 ,6236,6198,6237 }, {5406,5437,5436 ,6200,6259,6201 }, + {5516,5552,5551 ,5420,5936,6260 }, {5612,5636,5635 ,6202,6241,6203 }, + {5720,5745,5759 ,6206,6261,6242 }, {1151,3587,3942 ,6262,6214,6243 }, + {2861,4295,1988 ,6215,5968,6216 }, {4016,4295,4348 ,5969,5968,6263 }, + {5898,6038,6035 ,5706,6264,6025 }, {6001,5705,5996 ,6221,4127,6249 }, + {3395,5854,6142 ,5940,6265,5941 }, {3583,5651,6067 ,5772,5920,6266 }, + {75,3510,1629 ,3962,6267,4207 }, {5654,5916,4425 ,6026,6175,6268 }, + {5987,5451,5608 ,5621,6219,4356 }, {5662,3943,5944 ,5915,4755,4754 }, + {5653,5662,5944 ,3644,5915,4754 }, {4209,4290,3777 ,6269,6270,1582 }, + {789,3985,2797 ,3910,4802,6030 }, {2690,3318,2797 ,6022,4036,6030 }, + {5803,5836,5819 ,5871,5859,6114 }, {7128,7160,7159 ,5391,6271,5075 }, + {993,5050,1154 ,5551,5233,4545 }, {5214,5213,5171 ,6272,428,1804 }, + {5172,5214,5171 ,5468,6272,1804 }, {5234,5233,5213 ,6273,429,428 }, + {5256,5232,5233 ,6274,6238,429 }, {5234,5256,5233 ,6273,6274,429 }, + {5279,5278,5232 ,6275,6236,6238 }, {5256,5279,5232 ,6274,6275,6238 }, + {5332,5331,5278 ,6276,6198,6236 }, {5279,5332,5278 ,6275,6276,6236 }, + {5332,5373,5372 ,6276,6277,6199 }, {5331,5332,5372 ,6198,6276,6199 }, + {5373,5407,5406 ,6277,6278,6200 }, {5372,5373,5406 ,6199,6277,6200 }, + {5407,5408,5406 ,6278,6279,6200 }, {5408,5438,5437 ,6279,4156,6259 }, + {5406,5408,5437 ,6200,6279,6259 }, {5479,5517,5516 ,5387,6280,5420 }, + {5517,5553,5552 ,6280,6281,5936 }, {5516,5517,5552 ,5420,6280,5936 }, + {5553,5554,5552 ,6281,6282,5936 }, {5554,5595,5594 ,6282,6283,5937 }, + {5552,5554,5594 ,5936,6282,5937 }, {5594,5595,5612 ,5937,6283,6202 }, + {5595,5637,5636 ,6283,6284,6241 }, {5612,5595,5636 ,6202,6283,6241 }, + {5637,5675,5674 ,6284,6285,6205 }, {5636,5637,5674 ,6241,6284,6205 }, + {5675,5721,5720 ,6285,6286,6206 }, {5674,5675,5720 ,6205,6285,6206 }, + {5721,5722,5745 ,6286,6287,6261 }, {5720,5721,5745 ,6206,6286,6261 }, + {5722,5760,5759 ,6287,6288,6242 }, {5745,5722,5759 ,6261,6287,6242 }, + {5760,5795,5794 ,6288,6289,6208 }, {5759,5760,5794 ,6242,6288,6208 }, + {5795,5827,5826 ,6289,6290,6209 }, {5794,5795,5826 ,6208,6289,6209 }, + {5827,5828,3145 ,6290,6291,6210 }, {5826,5827,3145 ,6209,6290,6210 }, + {5828,4288,3145 ,6291,6292,6210 }, {4288,4287,3145 ,6292,6293,6210 }, + {3480,3940,3145 ,6294,6211,6210 }, {4287,3480,3145 ,6293,6294,6210 }, + {995,1598,3940 ,6295,6212,6211 }, {3480,995,3940 ,6294,6295,6211 }, + {3983,3942,1598 ,6296,6243,6212 }, {995,3983,1598 ,6295,6296,6212 }, + {1522,1151,3942 ,6297,6262,6243 }, {3983,1522,3942 ,6296,6297,6243 }, + {1522,3587,1151 ,6297,6214,6262 }, {1732,2861,3587 ,6298,6215,6214 }, + {1522,1732,3587 ,6297,6298,6214 }, {4232,4295,2861 ,6299,5968,6215 }, + {1732,4232,2861 ,6298,6299,6215 }, {4232,4348,4295 ,6299,6263,5968 }, + {4321,3528,5630 ,4873,5061,4874 }, {4017,4016,4232 ,5342,5969,6299 }, + {5982,5924,5649 ,4218,6091,6093 }, {5705,6181,5992 ,4127,6251,4197 }, + {6130,6132,6131 ,6300,5897,6050 }, {5472,3443,5869 ,6301,6302,6224 }, + {7168,7185,7160 ,5371,5073,6271 }, {1555,4290,4209 ,6303,6270,6269 }, + {1554,1555,4209 ,6304,6303,6269 }, {1555,4261,4290 ,6303,1583,6270 }, + {3771,4026,4261 ,6305,6254,1583 }, {1555,3771,4261 ,6303,6305,1583 }, + {3010,602,4026 ,6306,6232,6254 }, {3771,3010,4026 ,6305,6306,6254 }, + {3242,5459,602 ,6307,6255,6232 }, {3010,3242,602 ,6306,6307,6232 }, + {3242,3244,993 ,6307,4433,5551 }, {5459,3242,993 ,6255,6307,5551 }, + {1207,3817,5153 ,1074,1189,3896 }, {6749,5548,5512 ,4046,1590,1589 }, + {5457,5387,1252 ,6308,6309,5023 }, {5387,3838,3839 ,6309,6257,6310 }, + {1388,1016,3838 ,6311,6085,6257 }, {5387,1388,3838 ,6309,6311,6257 }, + {5617,5127,1016 ,6312,5114,6085 }, {2621,1606,3815 ,5416,3677,5556 }, + {5321,5346,5320 ,5668,3267,4464 }, {5172,5215,5214 ,5468,6313,6272 }, + {5234,5279,5256 ,6273,6275,6274 }, {5332,5374,5373 ,6276,6314,6277 }, + {5374,5408,5407 ,6314,6279,6278 }, {5373,5374,5407 ,6277,6314,6278 }, + {7371,7370,7310 ,3748,3750,4283 }, {5479,5492,5517 ,5387,6315,6280 }, + {5517,5518,5553 ,6280,6316,6281 }, {5796,5828,5827 ,6317,6291,6290 }, + {5795,5796,5827 ,6289,6317,6290 }, {3665,3480,4287 ,6318,6294,6293 }, + {4288,3665,4287 ,6292,6318,6293 }, {1491,1732,1522 ,6319,6298,6297 }, + {4348,4232,4016 ,6263,6299,5969 }, {4921,1551,5082 ,2079,5275,5218 }, + {5786,5082,3872 ,4813,5218,4811 }, {6081,5185,5992 ,6223,5939,4197 }, + {6181,6081,5992 ,6251,6223,4197 }, {3726,3771,1555 ,6320,6305,6303 }, + {3726,3010,3771 ,6320,6306,6305 }, {4834,955,5787 ,4304,3991,4305 }, + {5387,3839,1252 ,6309,6310,5023 }, {4241,5387,5457 ,6321,6309,6308 }, + {5225,1388,5387 ,6322,6311,6309 }, {4883,5617,1016 ,5538,6312,6085 }, + {1388,4883,1016 ,6311,5538,6085 }, {5617,4883,5127 ,6312,5538,5114 }, + {3760,3669,1783 ,2250,5917,2251 }, {5267,5290,5244 ,4647,5247,79 }, + {5215,5172,5216 ,6313,5468,6323 }, {4842,4776,4745 ,5429,5390,5446 }, + {5492,5518,5517 ,6315,6316,6280 }, {5518,5554,5553 ,6316,6282,6281 }, + {5696,5722,5721 ,6324,6287,6286 }, {5675,5696,5721 ,6285,6324,6286 }, + {5760,5796,5795 ,6288,6317,6289 }, {4188,995,3480 ,6325,6295,6294 }, + {3665,4188,3480 ,6318,6325,6294 }, {2957,603,2093 ,6326,5274,553 }, + {5189,5188,3872 ,6327,4812,4811 }, {803,101,6204 ,144,5,4050 }, + {248,1554,4530 ,4114,6304,1376 }, {3118,3010,3726 ,6328,6306,6320 }, + {3118,3242,3010 ,6328,6307,6306 }, {455,556,4471 ,2205,4043,2187 }, + {5205,5228,5227 ,5219,4095,4097 }, {4649,5152,5125 ,5288,6329,5467 }, + {4702,5173,5125 ,5291,6330,5467 }, {5152,4702,5125 ,6329,5291,5467 }, + {4702,5172,5173 ,5291,5468,6330 }, {4702,5216,5172 ,5291,6323,5468 }, + {5257,5234,5214 ,6331,6273,6272 }, {5280,5279,5234 ,6332,6275,6273 }, + {5257,5280,5234 ,6331,6332,6273 }, {5333,5332,5279 ,6333,6276,6275 }, + {5280,5333,5279 ,6332,6333,6275 }, {5375,5374,5332 ,6334,6314,6276 }, + {5333,5375,5332 ,6333,6334,6276 }, {5375,5409,5408 ,6334,6335,6279 }, + {5374,5375,5408 ,6314,6334,6279 }, {5409,5439,5438 ,6335,4157,4156 }, + {5408,5409,5438 ,6279,6335,4156 }, {7160,7138,7168 ,6271,2560,5371 }, + {5493,5519,5518 ,6336,6337,6316 }, {5479,5493,5492 ,5387,6336,6315 }, + {5519,5555,5554 ,6337,6338,6282 }, {5518,5519,5554 ,6316,6337,6282 }, + {5555,5596,5595 ,6338,6339,6283 }, {5554,5555,5595 ,6282,6338,6283 }, + {5596,5638,5637 ,6339,6340,6284 }, {5595,5596,5637 ,6283,6339,6284 }, + {5638,5676,5675 ,6340,6341,6285 }, {5637,5638,5675 ,6284,6340,6285 }, + {5676,5677,5696 ,6341,6342,6324 }, {5675,5676,5696 ,6285,6341,6324 }, + {5677,5723,5722 ,6342,6343,6287 }, {5696,5677,5722 ,6324,6342,6287 }, + {5723,5761,5760 ,6343,6344,6288 }, {5722,5723,5760 ,6287,6343,6288 }, + {5761,5783,5760 ,6344,6345,6288 }, {5783,5797,5796 ,6345,6346,6317 }, + {5760,5783,5796 ,6288,6345,6317 }, {5797,5829,5828 ,6346,6347,6291 }, + {5796,5797,5828 ,6317,6346,6291 }, {5829,1667,4288 ,6347,6348,6292 }, + {5828,5829,4288 ,6291,6347,6292 }, {1667,1019,3665 ,6348,6349,6318 }, + {4288,1667,3665 ,6292,6348,6318 }, {1540,4188,3665 ,6350,6325,6318 }, + {1019,1540,3665 ,6349,6350,6318 }, {1540,995,4188 ,6350,6295,6325 }, + {2499,3983,995 ,6351,6296,6295 }, {1540,2499,995 ,6350,6351,6295 }, + {2499,3988,3983 ,6351,6352,6296 }, {3974,1522,3983 ,6353,6297,6296 }, + {3988,3974,3983 ,6352,6353,6296 }, {218,1491,1522 ,6354,6319,6297 }, + {3974,218,1522 ,6353,6354,6297 }, {218,1732,1491 ,6354,6298,6319 }, + {2809,4232,1732 ,6355,6299,6298 }, {218,2809,1732 ,6354,6355,6298 }, + {2809,4208,4232 ,6355,6356,6299 }, {7129,7138,7160 ,1026,2560,6271 }, + {7138,7169,7168 ,2560,5437,5371 }, {876,5859,1120 ,5340,6357,5339 }, + {5859,37,1120 ,6357,6358,5339 }, {2957,1120,37 ,6326,5339,6358 }, + {37,555,2957 ,6358,6359,6326 }, {555,551,603 ,6359,6360,5274 }, + {2957,555,603 ,6326,6359,5274 }, {1523,1551,603 ,6361,5275,5274 }, + {551,1523,603 ,6360,6361,5274 }, {1108,3872,1551 ,6362,4811,5275 }, + {1523,1108,1551 ,6361,6362,5275 }, {3361,5189,3872 ,6363,6327,4811 }, + {1108,3361,3872 ,6362,6363,4811 }, {3361,5188,5189 ,6363,4812,6327 }, + {1861,3350,5188 ,6364,2541,4812 }, {3361,1861,5188 ,6363,6364,4812 }, + {1861,5856,3350 ,6364,6365,2541 }, {5856,5806,3350 ,6365,4113,2541 }, + {3418,248,5806 ,6366,4114,4113 }, {3418,5341,1554 ,6366,6367,6304 }, + {4025,1555,1554 ,6368,6303,6304 }, {5341,4025,1554 ,6367,6368,6304 }, + {3774,3726,1555 ,6369,6320,6303 }, {4025,3774,1555 ,6368,6369,6303 }, + {2991,3118,3726 ,6370,6328,6320 }, {3774,2991,3726 ,6369,6370,6320 }, + {5201,3242,3118 ,6371,6307,6328 }, {2991,5201,3118 ,6370,6371,6328 }, + {5201,5174,3242 ,6371,6372,6307 }, {4397,3244,3242 ,4434,4433,6307 }, + {5174,4397,3242 ,6372,4434,6307 }, {1253,3778,4068 ,2353,5585,408 }, + {5020,4272,4241 ,6373,6374,6321 }, {5245,5387,4241 ,5880,6309,6321 }, + {4272,5245,4241 ,6374,5880,6321 }, {5245,4908,5387 ,5880,5879,6309 }, + {4908,5161,5225 ,5879,6375,6322 }, {5387,4908,5225 ,6309,5879,6322 }, + {5161,4774,1388 ,6375,5332,6311 }, {5225,5161,1388 ,6322,6375,6311 }, + {5153,4682,3899 ,3896,1191,1938 }, {4702,5152,4649 ,5291,6329,5288 }, + {1922,4397,1085 ,4432,4434,369 }, {5519,5535,5555 ,6337,3512,6338 }, + {5596,5613,5638 ,6339,6376,6340 }, {5638,5677,5676 ,6340,6342,6341 }, + {2499,3974,3988 ,6351,6353,6352 }, {4208,2809,876 ,6356,6355,5340 }, + {876,37,5859 ,5340,6358,6357 }, {2913,414,551 ,6377,6378,6360 }, + {555,2913,551 ,6359,6377,6360 }, {671,1523,551 ,6379,6361,6360 }, + {414,671,551 ,6378,6379,6360 }, {4309,1108,1523 ,6380,6362,6361 }, + {671,4309,1523 ,6379,6380,6361 }, {3630,5806,5856 ,6381,4113,6365 }, + {1861,3630,5856 ,6364,6381,6365 }, {3287,4025,5341 ,6382,6368,6367 }, + {3418,3287,5341 ,6366,6382,6367 }, {4025,5661,3774 ,6368,6383,6369 }, + {5174,4117,4397 ,6372,4700,4434 }, {6785,6803,5548 ,4140,4169,1590 }, + {5292,4272,5175 ,5093,6374,6384 }, {4908,4774,5161 ,5879,5332,6375 }, + {5224,4714,5267 ,3850,1031,4647 }, {3732,2785,3419 ,3536,1246,2012 }, + {3318,3732,3419 ,4036,3536,2012 }, {5235,5280,5257 ,6385,6332,6331 }, + {5439,5410,5440 ,4157,4285,5147 }, {5104,2246,5755 ,6063,591,4047 }, + {6697,5512,5471 ,3866,1589,2051 }, {6680,6697,5471 ,3834,3866,2051 }, + {5590,6173,6002 ,6386,3711,5975 }, {5555,5568,5596 ,6338,6387,6339 }, + {5638,5655,5677 ,6340,6388,6342 }, {1585,3974,2499 ,6389,6353,6351 }, + {876,4346,37 ,5340,6390,6358 }, {37,2913,555 ,6358,6377,6359 }, + {4309,3361,1108 ,6380,6363,6362 }, {1384,3418,5806 ,6391,6366,4113 }, + {3630,1384,5806 ,6381,6391,4113 }, {4025,3287,5661 ,6368,6382,6383 }, + {3775,2991,3774 ,6392,6370,6369 }, {5661,3775,3774 ,6383,6392,6369 }, + {4397,4117,1859 ,4434,4700,4480 }, {4272,5020,5175 ,6374,6373,6384 }, + {6697,6749,5512 ,3866,4046,1589 }, {3367,4645,4926 ,5798,5378,3897 }, + {1416,104,4519 ,6100,4765,4746 }, {5217,5216,4702 ,4026,6323,5291 }, + {4703,5217,4702 ,5290,4026,5291 }, {5217,5235,5216 ,4026,6385,6323 }, + {5217,5258,5235 ,4026,1193,6385 }, {5281,5280,5235 ,1195,6332,6385 }, + {5258,5281,5235 ,1193,1195,6385 }, {5281,5282,5280 ,1195,1194,6332 }, + {5282,5334,5333 ,1194,2736,6333 }, {5280,5282,5333 ,6332,1194,6333 }, + {5334,5376,5375 ,2736,2758,6334 }, {5333,5334,5375 ,6333,2736,6334 }, + {5410,5409,5375 ,4285,6335,6334 }, {5376,5410,5375 ,2758,4285,6334 }, + {5409,5410,5439 ,6335,4285,4157 }, {5172,5125,5173 ,5468,5467,6330 }, + {1353,1395,1352 ,1205,1164,1262 }, {5556,5557,5568 ,3511,756,6387 }, + {5555,5556,5568 ,6338,3511,6387 }, {5557,5597,5596 ,756,6393,6339 }, + {5568,5557,5596 ,6387,756,6339 }, {5557,5598,5597 ,756,2216,6393 }, + {5596,5597,5613 ,6339,6393,6376 }, {5598,5639,5638 ,2216,2215,6340 }, + {5613,5598,5638 ,6376,2216,6340 }, {5639,5656,5655 ,2215,3864,6388 }, + {5638,5639,5655 ,6340,2215,6388 }, {5656,5678,5677 ,3864,3861,6342 }, + {5655,5656,5677 ,6388,3864,6342 }, {5678,5724,5723 ,3861,4898,6343 }, + {5677,5678,5723 ,6342,3861,6343 }, {5724,5762,5761 ,4898,4884,6344 }, + {5723,5724,5761 ,6343,4898,6344 }, {5761,5762,5783 ,6344,4884,6345 }, + {5762,5798,5797 ,4884,4876,6346 }, {5783,5762,5797 ,6345,4884,6346 }, + {5798,5830,5829 ,4876,4163,6347 }, {5797,5798,5829 ,6346,4876,6347 }, + {1794,1667,5829 ,4129,6348,6347 }, {5830,1794,5829 ,4163,4129,6347 }, + {1794,2795,1019 ,4129,3975,6349 }, {1667,1794,1019 ,6348,4129,6349 }, + {1607,1540,1019 ,3941,6350,6349 }, {2795,1607,1019 ,3975,3941,6349 }, + {2477,2499,1540 ,642,6351,6350 }, {1607,2477,1540 ,3941,642,6350 }, + {87,1585,2499 ,641,6389,6351 }, {2477,87,2499 ,642,641,6351 }, + {87,3974,1585 ,641,6353,6389 }, {1587,218,3974 ,837,6354,6353 }, + {87,1587,3974 ,641,837,6353 }, {96,2809,218 ,836,6355,6354 }, + {1587,96,218 ,837,836,6354 }, {96,3257,876 ,836,4010,5340 }, + {2809,96,876 ,6355,836,5340 }, {876,3257,4346 ,5340,4010,6390 }, + {3257,5737,37 ,4010,6394,6358 }, {4346,3257,37 ,6390,4010,6358 }, + {5737,1974,37 ,6394,3862,6358 }, {1974,62,2913 ,3862,3831,6377 }, + {37,1974,2913 ,6358,3862,6377 }, {34,414,2913 ,3830,6378,6377 }, + {62,34,2913 ,3831,3830,6377 }, {3980,671,414 ,4933,6379,6378 }, + {34,3980,414 ,3830,4933,6378 }, {164,4309,671 ,4932,6380,6379 }, + {3980,164,671 ,4933,4932,6379 }, {52,3361,4309 ,411,6363,6380 }, + {164,52,4309 ,4932,411,6380 }, {3113,1861,3361 ,410,6364,6363 }, + {52,3113,3361 ,411,410,6363 }, {3113,269,1861 ,410,3813,6364 }, + {1734,3630,1861 ,2219,6381,6364 }, {269,1734,1861 ,3813,2219,6364 }, + {878,1384,3630 ,398,6391,6381 }, {1734,878,3630 ,2219,398,6381 }, + {878,3418,1384 ,398,6366,6391 }, {4011,3287,3418 ,395,6382,6366 }, + {878,4011,3418 ,398,395,6366 }, {4011,3662,5661 ,395,1942,6383 }, + {3287,4011,5661 ,6382,395,6383 }, {5304,3775,5661 ,654,6392,6383 }, + {3662,5304,5661 ,1942,654,6383 }, {5304,2991,3775 ,654,6370,6392 }, + {5481,5201,2991 ,3863,6371,6370 }, {5304,5481,2991 ,654,3863,6370 }, + {5481,4117,5201 ,3863,4700,6371 }, {4117,5174,5201 ,4700,6372,6371 }, + {104,1150,4519 ,4765,741,4746 }, {3899,1376,1196 ,1938,1937,4330 }, + {7410,7462,7409 ,3766,4310,6245 }, {7033,6152,5945 ,6395,6044,6396 }, + {5562,5945,6152 ,4193,6396,6044 }, {1790,4272,5292 ,4738,6374,5093 }, + {5017,1790,5292 ,5092,4738,5093 }, {4272,1790,5245 ,6374,4738,5880 }, + {3419,789,2797 ,2012,3910,6030 }, {5153,3899,5146 ,3896,1938,1075 }, + {4502,1859,4117 ,4650,4480,4700 }, {6648,6680,6647 ,1390,3834,2050 }, + {6648,6647,6611 ,1390,2050,1391 }, {5557,8043,5598 ,756,6397,2216 }, + {6530,1990,1217 ,6398,1305,6399 }, {5613,5597,5598 ,6376,6393,2216 }, + {1539,1587,87 ,835,837,641 }, {96,3909,3257 ,836,4164,4010 }, + {3257,1974,5737 ,4010,3862,6394 }, {3688,4292,4153 ,5574,6400,5711 }, + {3470,6003,5917 ,6401,6122,6402 }, {350,5653,1273 ,1114,3644,6403 }, + {4286,5580,3528 ,4274,6404,5061 }, {5392,5947,5717 ,4022,6027,4023 }, + {2044,465,501 ,4031,4578,4313 }, {670,5043,5985 ,4760,4762,5365 }, + {7960,100,2306 ,3505,2823,6405 }, {5882,3674,3728 ,4397,4286,4251 }, + {5575,5958,6112 ,5686,5702,6406 }, {8023,8038,8022 ,4582,4121,4123 }, + {4284,4229,4270 ,4869,4877,4900 }, {3778,1253,1583 ,5585,2353,5415 }, + {6267,3607,3606 ,6407,6138,6137 }, {6012,5963,6004 ,6408,6409,6123 }, + {6241,6242,6269 ,6410,6081,6083 }, {5129,5293,5314 ,5698,5889,6411 }, + {3659,5129,5314 ,3875,5698,6411 }, {6141,6122,2072 ,2386,1797,1812 }, + {3559,5567,5897 ,6412,6413,6414 }, {4058,206,4054 ,4708,3932,3679 }, + {5650,5981,3905 ,2713,6415,4194 }, {6227,5906,5462 ,6416,6417,6418 }, + {3813,3758,5926 ,1515,1789,6419 }, {5662,6035,4045 ,5915,6025,5916 }, + {5687,5048,5101 ,6420,6421,6422 }, {5361,6227,5462 ,5664,6416,6418 }, + {206,4054,4055 ,3932,3679,3678 }, {5488,3518,5473 ,2730,4338,3429 }, + {3528,5580,5630 ,5061,6404,4874 }, {6113,3608,5461 ,5873,5799,5744 }, + {6057,5994,5893 ,6423,6424,6425 }, {5994,5524,1008 ,6424,6426,6427 }, + {3599,6057,5893 ,6428,6423,6425 }, {4153,3688,3953 ,5711,5574,5713 }, + {5893,5994,1008 ,6425,6424,6427 }, {6096,2357,4043 ,4092,3933,419 }, + {2142,6031,5706 ,4936,6429,3796 }, {3007,6070,3582 ,818,820,1378 }, + {7437,7436,7386 ,5938,4245,5512 }, {6168,5646,5316 ,6430,6431,6087 }, + {5524,5646,1008 ,6426,6431,6427 }, {5646,5743,5316 ,6431,6432,6087 }, + {5346,6260,5320 ,3267,3397,4464 }, {8095,8062,8090 ,4362,3272,412 }, + {6128,6129,5123 ,5981,5983,6433 }, {5646,6168,1008 ,6431,6430,6427 }, + {6023,5646,5524 ,6040,6431,6426 }, {5875,3797,5490 ,6434,2061,6055 }, + {5550,5878,5549 ,6047,2684,6435 }, {5452,5650,3905 ,2714,2713,4194 }, + {3803,3720,3804 ,6436,6437,4324 }, {6001,6003,6095 ,6221,6122,6222 }, + {5345,6057,3599 ,6438,6423,6428 }, {5392,1741,5947 ,4022,5695,6027 }, + {187,2159,1171 ,6439,6440,6441 }, {3804,3720,5882 ,4324,6437,4397 }, + {3720,6160,3674 ,6437,6442,4286 }, {5882,3720,3674 ,4397,6437,4286 }, + {5254,5576,3436 ,4042,4039,2145 }, {7688,7689,7712 ,6443,6444,5350 }, + {4528,5550,5549 ,4935,6047,6435 }, {2159,4528,5549 ,6440,4935,6435 }, + {6230,6104,3436 ,2144,5696,2145 }, {5303,2636,5315 ,5737,2712,6445 }, + {3688,4153,3953 ,5574,5711,5713 }, {5880,3666,6137 ,5700,5699,5809 }, + {6059,3679,5906 ,6446,6447,6417 }, {6094,5977,5742 ,692,809,6448 }, + {3373,6094,5742 ,693,692,6448 }, {4807,5426,5970 ,6449,5634,3518 }, + {381,3522,1627 ,6450,6451,5573 }, {2636,5303,5650 ,2712,5737,2713 }, + {5862,3893,4623 ,3790,3789,6452 }, {3893,5861,4623 ,3789,6453,6452 }, + {5609,5851,6135 ,5658,6095,6258 }, {5297,4178,5272 ,3826,3825,5956 }, + {5044,5192,5303 ,5173,6454,5737 }, {5922,6149,3445 ,4292,732,1791 }, + {7089,7103,5115 ,5608,6455,5913 }, {4433,4820,4795 ,4591,2399,3710 }, + {5462,5906,5961 ,6418,6417,6456 }, {5192,4410,6251 ,6454,5735,6457 }, + {6224,6248,6213 ,6458,6154,6459 }, {2636,5452,5386 ,2712,2714,5870 }, + {6010,5384,6016 ,6197,6121,6228 }, {6058,5345,5416 ,6460,6438,6461 }, + {5384,3631,5403 ,6121,5922,5921 }, {5235,5257,5214 ,6385,6331,6272 }, + {6254,3906,6055 ,5758,5697,3874 }, {3411,5193,1741 ,4280,5846,5695 }, + {6003,5973,6095 ,6122,6246,6222 }, {3415,7082,6186 ,6115,6462,4365 }, + {6205,6206,6240 ,6463,6464,5756 }, {5213,5195,5171 ,428,6235,1804 }, + {5888,5902,6093 ,5752,5769,5771 }, {7423,7479,7422 ,3749,4423,1404 }, + {5322,5320,5695 ,4297,4464,6465 }, {5874,5976,5925 ,4896,5903,5710 }, + {7690,449,448 ,6466,1169,1172 }, {5958,5122,3507 ,5702,4024,5671 }, + {3930,1627,4247 ,6467,5573,6468 }, {3628,6235,5703 ,4213,4215,5748 }, + {5567,5526,5897 ,6413,6256,6414 }, {5205,5248,5247 ,5219,6469,4096 }, + {6254,6268,3906 ,5758,5757,5697 }, {5609,6256,6213 ,5658,5657,6459 }, + {6102,3868,3879 ,2464,970,2549 }, {5055,3979,1261 ,6470,6471,6472 }, + {4247,4246,381 ,6468,3665,6450 }, {381,1627,3930 ,6450,5573,6467 }, + {7293,7248,7322 ,6473,73,75 }, {5926,3758,3714 ,6419,1789,3970 }, + {6004,6003,5974 ,6123,6122,6474 }, {5993,3953,5580 ,6475,5713,6404 }, + {7423,7422,7370 ,3749,1404,3750 }, {1701,1949,216 ,6476,3637,4644 }, + {5851,5530,5881 ,6095,6189,6109 }, {4574,6062,5974 ,6477,6478,6474 }, + {1532,1051,217 ,4771,4588,4643 }, {6075,5922,5908 ,6042,4292,4293 }, + {5662,5898,6035 ,5915,5706,6025 }, {1605,6206,6205 ,17,6464,6463 }, + {5971,5903,5902 ,6479,6480,5769 }, {5902,5903,686 ,5769,6480,5770 }, + {5753,5055,5916 ,6481,6470,6175 }, {3522,6227,758 ,6451,6416,5663 }, + {6024,3410,5486 ,6482,6483,5718 }, {6225,5610,6248 ,4872,6153,6154 }, + {4517,6058,5416 ,6484,6460,6461 }, {5923,5591,3979 ,2073,3597,6471 }, + {1854,635,5971 ,2184,2183,6479 }, {4540,4526,4515 ,19,187,30 }, + {5318,3360,6023 ,6039,6485,6040 }, {5972,5988,4147 ,399,2516,2517 }, + {6225,6248,6224 ,4872,6154,6458 }, {1661,6179,3906 ,5672,6486,5697 }, + {6070,4486,3582 ,820,819,1378 }, {6240,6241,1661 ,5756,6410,5672 }, + {3717,3715,5630 ,6487,6124,4874 }, {3902,4807,5970 ,4437,6449,3518 }, + {3051,5474,5877 ,6488,4243,4242 }, {5388,5446,6317 ,5428,5985,3207 }, + {6032,5472,5869 ,6489,6301,6224 }, {5472,3409,6032 ,6301,6490,6489 }, + {3360,3386,5472 ,6485,6491,6301 }, {3409,5472,6032 ,6490,6301,6489 }, + {20,2657,3506 ,3961,2185,6492 }, {6018,4304,1747 ,340,4579,266 }, + {5185,6142,3632 ,5939,5941,5898 }, {5887,5888,6087 ,6493,5752,6193 }, + {6089,6050,6087 ,6494,4094,6193 }, {6023,6024,5646 ,6040,6482,6431 }, + {5055,5476,5916 ,6470,6495,6175 }, {5906,6006,3560 ,6417,6496,5779 }, + {5630,5610,5609 ,4874,6153,5658 }, {5888,6093,6050 ,5752,5771,4094 }, + {5319,747,5781 ,5739,5692,5694 }, {4229,4246,4270 ,4877,3665,4900 }, + {3680,3499,405 ,6497,5661,5662 }, {6084,3499,3680 ,6194,5661,6497 }, + {5961,5906,3560 ,6456,6417,5779 }, {6006,6043,3560 ,6496,6498,5779 }, + {5866,5867,4094 ,5302,6499,6225 }, {3560,6043,5318 ,5779,6498,6039 }, + {5867,5869,5866 ,6499,6224,5302 }, {5273,5298,5272 ,3819,3818,5956 }, + {5513,6123,4147 ,6500,4221,2517 }, {1417,3522,381 ,6501,6451,6450 }, + {3638,278,8051 ,5950,1489,6172 }, {5866,4094,4410 ,5302,6225,5735 }, + {5192,5866,4410 ,6454,5302,5735 }, {3525,5935,5885 ,5665,5778,6502 }, + {6168,5316,1008 ,6430,6087,6427 }, {3517,1729,5580 ,6503,3666,6404 }, + {6052,3311,3762 ,5717,5303,5172 }, {5303,5981,5650 ,5737,6415,2713 }, + {5303,5315,5044 ,5737,6445,5173 }, {1700,706,807 ,6072,4784,6073 }, + {3581,3508,6017 ,6504,6505,6506 }, {3508,5964,6017 ,6505,6507,6506 }, + {3879,5317,6102 ,2549,6508,2464 }, {5317,5382,3831 ,6508,6509,2321 }, + {6102,5317,3831 ,2464,6508,2321 }, {5382,4154,3803 ,6509,6510,6436 }, + {3831,5382,3803 ,2321,6509,6436 }, {1606,4054,206 ,3677,3679,3932 }, + {3953,3018,6124 ,5713,6125,6511 }, {5567,3559,5955 ,6413,6412,6512 }, + {5351,3386,5318 ,6513,6491,6039 }, {3443,6077,5887 ,6302,5753,6493 }, + {5988,20,4147 ,2516,3961,2517 }, {6145,5247,4023 ,6514,4096,3827 }, + {6087,6050,5394 ,6193,4094,5660 }, {5416,5345,5671 ,6461,6438,6515 }, + {507,565,564 ,1523,1614,3529 }, {3576,5912,6017 ,5890,5892,6506 }, + {807,3606,3563 ,6073,6137,6074 }, {5747,6030,5254 ,6036,4037,4042 }, + {6118,5923,3979 ,2074,2073,6471 }, {5753,6118,3979 ,6481,2074,6471 }, + {1729,5993,5580 ,3666,6475,6404 }, {6093,686,6086 ,5771,5770,6516 }, + {4260,6139,6138 ,3527,2059,6014 }, {5867,5869,4094 ,6499,6224,6225 }, + {3522,758,1627 ,6451,5663,5573 }, {5909,6136,5824 ,4260,5810,6517 }, + {5630,3715,5610 ,4874,6124,6153 }, {4147,3506,3384 ,2517,6492,6518 }, + {5888,6089,6087 ,5752,6494,6193 }, {6124,3018,3715 ,6511,6125,6124 }, + {3829,2273,3051 ,6195,5322,6488 }, {4517,5416,3018 ,6484,6461,6125 }, + {5853,3560,5318 ,6038,5779,6039 }, {3509,6128,5123 ,6519,5981,6433 }, + {4443,6212,6211 ,703,5562,5558 }, {5490,3747,5749 ,6055,2925,6052 }, + {5296,4023,5297 ,4296,3827,3826 }, {4055,4054,206 ,3678,3679,3932 }, + {6103,6123,5897 ,4220,4221,6414 }, {4486,3454,3582 ,819,710,1378 }, + {2204,6032,5867 ,6520,6489,6499 }, {5869,5867,5866 ,6224,6499,5302 }, + {6032,5869,5867 ,6489,6224,6499 }, {3658,6023,5524 ,6521,6040,6426 }, + {3584,6078,3558 ,5888,6522,5964 }, {3506,5368,3409 ,6492,5751,6490 }, + {5315,2636,5477 ,6445,2712,5399 }, {3680,405,4642 ,6497,5662,6226 }, + {6119,4410,5670 ,5736,5735,6227 }, {4374,4517,3018 ,5712,6484,6125 }, + {5299,5300,6041 ,3800,3793,3801 }, {3018,5416,5905 ,6125,6461,6142 }, + {5293,3584,3558 ,5889,5888,5964 }, {6218,3658,5935 ,5780,6521,5778 }, + {5246,5227,6040 ,5176,4097,5317 }, {3405,8086,1153 ,1798,4823,2245 }, + {3563,3606,1510 ,6074,6137,3788 }, {3905,5562,6152 ,4194,4193,6044 }, + {6163,3620,3860 ,1453,6001,6523 }, {5928,3904,3903 ,4072,1264,296 }, + {5882,3772,3804 ,4397,4250,4324 }, {3905,6152,6166 ,4194,6044,4971 }, + {5192,6251,5303 ,6454,6457,5737 }, {1417,381,4229 ,6501,6450,4877 }, + {635,5534,5971 ,2183,6524,6479 }, {6226,5981,5562 ,4192,6415,4193 }, + {1629,3510,1518 ,4207,6267,3978 }, {4367,6225,6224 ,228,4872,6458 }, + {3360,5472,5450 ,6485,6301,6525 }, {6244,5663,5356 ,5667,4259,4258 }, + {6006,5351,6043 ,6496,6513,6498 }, {3384,3506,3409 ,6518,6492,6490 }, + {5368,6077,3443 ,5751,5753,6302 }, {3409,5368,3443 ,6490,5751,6302 }, + {6077,5888,5887 ,5753,5752,6493 }, {4623,5861,5939 ,6452,6453,4276 }, + {6119,5670,5303 ,5736,6227,5737 }, {7973,7972,7948 ,3624,6059,3625 }, + {5567,2040,3039 ,6413,4833,2686 }, {6270,3780,3611 ,6082,6526,5673 }, + {5868,3720,3803 ,6527,6437,6436 }, {3525,4292,3688 ,5665,6400,5574 }, + {3337,5123,3951 ,4125,6433,6528 }, {970,5997,6018 ,268,6529,340 }, + {4154,5868,3803 ,6510,6527,6436 }, {6251,4410,5303 ,6457,5735,5737 }, + {5671,5345,3599 ,6515,6438,6428 }, {4286,3517,5580 ,4274,6503,6404 }, + {6161,6160,3720 ,6530,6442,6437 }, {5868,6161,3720 ,6527,6530,6437 }, + {6161,6187,6160 ,6530,6531,6442 }, {2896,3497,3845 ,6110,5738,3635 }, + {5903,2357,686 ,6480,3933,5770 }, {5906,3679,6006 ,6417,6447,6496 }, + {6227,3522,5906 ,6416,6451,6417 }, {4528,5316,5550 ,4935,6087,6047 }, + {3953,3717,5580 ,5713,6487,6404 }, {6139,5875,6250 ,2059,6434,6532 }, + {5390,6317,5446 ,3205,3207,5985 }, {4293,3954,4957 ,2732,3463,2733 }, + {5417,5451,5987 ,5923,6219,5621 }, {6207,6242,6241 ,6156,6081,6410 }, + {41,5037,5390 ,6533,3206,3205 }, {5116,3337,3951 ,4126,4125,6528 }, + {6306,6336,6335 ,5669,5670,6534 }, {1729,4153,5993 ,3666,5711,6475 }, + {249,6103,1158 ,644,4220,2744 }, {6103,249,5988 ,4220,644,2516 }, + {4642,6022,5670 ,6226,6535,6227 }, {5533,6172,6195 ,6239,5451,5529 }, + {6239,6223,6055 ,5918,6151,3874 }, {2960,5388,5949 ,4975,5428,5086 }, + {6043,5351,5318 ,6498,6513,6039 }, {5318,3386,3360 ,6039,6491,6485 }, + {3386,3409,5472 ,6491,6490,6301 }, {297,1416,140 ,1918,6100,1919 }, + {3902,685,3935 ,4437,2674,4346 }, {5631,5453,5863 ,6536,937,6537 }, + {6045,5453,5631 ,935,937,6536 }, {4515,6207,6206 ,30,6156,6464 }, + {5879,5417,5987 ,6220,5923,5621 }, {6050,6086,6096 ,4094,6516,4092 }, + {101,6196,6204 ,5,4,4050 }, {253,6228,751 ,2564,1366,1365 }, + {3679,5351,6006 ,6447,6513,6496 }, {3715,5851,5609 ,6124,6095,5658 }, + {3360,5450,3410 ,6485,6525,6483 }, {3410,5450,2204 ,6483,6525,6520 }, + {5450,6032,2204 ,6525,6489,6520 }, {3608,3583,6067 ,5799,5772,6266 }, + {6206,6241,6240 ,6464,6410,5756 }, {6024,6023,3410 ,6482,6040,6483 }, + {3411,1741,5392 ,4280,5695,4022 }, {1700,323,706 ,6072,4767,4784 }, + {6212,6213,6247 ,5562,6459,5575 }, {1022,793,1051 ,4198,4200,4588 }, + {6195,6172,5920 ,5529,5451,5453 }, {3658,5853,6023 ,6521,6038,6040 }, + {5928,6236,3879 ,4072,6538,2549 }, {6213,6256,6255 ,6459,5657,6102 }, + {6247,6213,6255 ,5575,6459,6102 }, {5515,6045,6038 ,5567,935,6264 }, + {6038,6045,5631 ,6264,935,6536 }, {6210,6245,6244 ,5566,5560,5667 }, + {4540,4515,6206 ,19,30,6464 }, {3797,5875,6139 ,2061,6434,2059 }, + {5893,1008,3414 ,6425,6427,4934 }, {6897,8112,8079 ,54,3530,55 }, + {5294,6010,5692 ,5891,6197,6229 }, {4278,4306,4277 ,5583,4552,4532 }, + {3563,1510,3497 ,6074,3788,5738 }, {2896,3563,3497 ,6110,6074,5738 }, + {5415,41,5390 ,5951,6533,3205 }, {4422,4391,6212 ,5561,992,5562 }, + {6074,3515,501 ,6539,1357,4313 }, {3470,5917,3079 ,6401,6402,6540 }, + {2657,1854,3506 ,2185,2184,6492 }, {7719,7697,7744 ,6541,651,6101 }, + {3879,6236,5317 ,2549,6538,6508 }, {3410,2204,5486 ,6483,6520,5718 }, + {3597,3546,3598 ,1810,2557,802 }, {5486,2204,3311 ,5718,6520,5303 }, + {262,5997,970 ,4758,6529,268 }, {359,262,970 ,4841,4758,268 }, + {5997,6019,6018 ,6529,6542,340 }, {6019,4304,6018 ,6542,4579,340 }, + {2318,8049,2319 ,3185,5393,3228 }, {959,3515,535 ,1358,1357,2001 }, + {5940,5926,3714 ,1625,6419,3970 }, {6224,6213,4391 ,6458,6459,992 }, + {1158,6103,252 ,2744,4220,2743 }, {5489,5474,5911 ,4244,4243,5924 }, + {6005,5515,5117 ,5691,5567,1113 }, {6209,6210,6244 ,6012,5566,5667 }, + {6245,6273,6272 ,5560,5683,5755 }, {6243,5356,6271 ,5666,4258,6152 }, + {6206,6207,6241 ,6464,6156,6410 }, {6204,6196,6203 ,4050,4,1249 }, + {5449,5314,3523 ,6543,6411,6544 }, {5951,2321,3509 ,6000,5999,6519 }, + {5934,5370,6091 ,616,2171,2173 }, {1510,5862,747 ,3788,3790,5692 }, + {3497,1510,747 ,5738,3788,5692 }, {949,5029,1083 ,2133,2159,2095 }, + {6138,6139,6250 ,6014,2059,6532 }, {808,556,2960 ,4734,4043,4975 }, + {4292,4517,4374 ,6400,6484,5712 }, {5416,3599,5302 ,6461,6428,3795 }, + {4144,5175,5985 ,4761,6384,5365 }, {5953,6000,3870 ,5659,6094,6096 }, + {7856,7855,7803 ,4635,6545,4636 }, {6061,465,4304 ,6546,4578,4579 }, + {5988,249,20 ,2516,644,3961 }, {6061,501,465 ,6546,4313,4578 }, + {5934,6091,5980 ,616,2173,5557 }, {1729,3517,4286 ,3666,6503,4274 }, + {5213,5214,5234 ,428,6272,6273 }, {6005,5117,186 ,5691,1113,1265 }, + {6244,6245,6272 ,5667,5560,5755 }, {6273,5880,5663 ,5683,5700,4259 }, + {6272,6273,5663 ,5755,5683,4259 }, {5956,6151,5951 ,6240,6024,6000 }, + {6203,6196,6239 ,1249,4,5918 }, {3629,3524,5160 ,5965,6187,6186 }, + {3523,3629,5160 ,6544,5965,6186 }, {5329,5711,6033 ,6191,6248,5620 }, + {6076,5329,6033 ,4217,6191,5620 }, {5576,5475,5876 ,4039,4038,2146 }, + {5530,5915,3870 ,6189,6120,6096 }, {5924,1696,6042 ,6091,6547,5703 }, + {3436,5576,5876 ,2145,4039,2146 }, {5475,5489,5876 ,4038,4244,2146 }, + {843,706,46 ,2423,4784,2232 }, {5302,3599,5706 ,3795,6428,3796 }, + {5895,5749,3731 ,6053,6052,6041 }, {6145,5925,5899 ,6514,5710,6548 }, + {5195,5213,5212 ,6235,428,430 }, {6004,5963,5871 ,6123,6409,4124 }, + {1125,2142,4528 ,5723,4936,4935 }, {706,806,807 ,4784,4783,6073 }, + {882,3396,3271 ,3846,3867,1727 }, {262,5998,5997 ,4758,6549,6529 }, + {5998,6020,6019 ,6549,6550,6542 }, {5997,5998,6019 ,6529,6549,6542 }, + {6020,6021,4304 ,6550,6551,4579 }, {6019,6020,4304 ,6542,6550,4579 }, + {6048,6061,4304 ,6552,6546,4579 }, {6021,6048,4304 ,6551,6552,4579 }, + {6074,501,6061 ,6539,4313,6546 }, {6048,6074,6061 ,6552,6539,6546 }, + {3632,3628,5703 ,5898,4213,5748 }, {6118,5753,5863 ,2074,6481,6537 }, + {5861,5870,5939 ,6453,4275,4276 }, {5453,6118,5863 ,937,2074,6537 }, + {3829,5475,3217 ,6195,4038,6117 }, {6012,6013,5963 ,6408,6060,6409 }, + {6129,6130,3951 ,5983,6300,6528 }, {5123,6129,3951 ,6433,5983,6528 }, + {5789,5386,6258 ,6140,5870,6553 }, {6217,5355,5703 ,6554,6555,5748 }, + {5881,5530,3870 ,6109,6189,6096 }, {6174,6234,6002 ,2381,5973,5975 }, + {1701,1700,2896 ,6476,6072,6110 }, {5875,5490,3444 ,6434,6055,6054 }, + {5528,5416,5302 ,6143,6461,3795 }, {5749,5922,3731 ,6052,4292,6041 }, + {5891,5876,5933 ,5714,2146,615 }, {938,803,322 ,983,144,1250 }, + {5869,3680,4094 ,6224,6497,6225 }, {5903,2912,2357 ,6480,3934,3933 }, + {1153,3012,2624 ,2245,3491,3132 }, {3599,5893,5706 ,6428,6425,3796 }, + {6222,6223,6239 ,6253,6151,5918 }, {6229,5987,5608 ,6556,5621,4356 }, + {4094,3680,4642 ,6225,6497,6226 }, {6577,3816,4426 ,97,5623,1056 }, + {262,670,5998 ,4758,4760,6549 }, {6048,3515,6074 ,6552,1357,6539 }, + {6035,6038,5654 ,6025,6264,6026 }, {5974,6003,3470 ,6474,6122,6401 }, + {5631,5863,6188 ,6536,6537,6174 }, {5654,5631,6188 ,6026,6536,6174 }, + {4292,3525,6058 ,6400,5665,6460 }, {6013,5956,5963 ,6060,6240,6409 }, + {5872,6051,6130 ,6557,6037,6300 }, {6129,5872,6130 ,5983,6557,6300 }, + {5530,5905,5915 ,6189,6142,6120 }, {1293,532,746 ,5764,5766,2661 }, + {6250,5875,3444 ,6532,6434,6054 }, {5953,6135,6000 ,5659,6258,6094 }, + {3446,3781,5487 ,3718,5849,6558 }, {5301,3631,5384 ,2698,5922,6121 }, + {686,2357,6086 ,5770,3933,6516 }, {5749,3747,6249 ,6052,2925,4032 }, + {6227,5361,758 ,6416,5664,5663 }, {7745,7744,7697 ,650,6101,651 }, + {670,5985,5998 ,4760,5365,6549 }, {6021,6020,5998 ,6551,6550,6549 }, + {6048,6085,3515 ,6552,6559,1357 }, {5628,4642,405 ,6560,6226,5662 }, + {7886,2295,2263 ,6561,3160,3080 }, {6164,6165,7062 ,6562,4219,6563 }, + {3856,5995,1268 ,5754,5564,4568 }, {3856,6013,6012 ,5754,6060,6408 }, + {5995,3856,6012 ,5564,5754,6408 }, {1121,5979,6051 ,6564,6028,6037 }, + {5872,1121,6051 ,6557,6564,6037 }, {3606,4164,1410 ,6137,6139,3911 }, + {5216,5235,5215 ,6323,6385,6313 }, {5475,3829,5877 ,4038,6195,4242 }, + {5686,5747,5651 ,5777,6036,5920 }, {5253,5302,3779 ,6163,3795,6565 }, + {2142,3779,6031 ,4936,6565,6429 }, {1608,421,746 ,4128,5815,2661 }, + {7802,7826,7801 ,6566,1616,1618 }, {3973,5388,636 ,6007,5428,6020 }, + {3989,3366,5252 ,5822,5894,5746 }, {1608,1293,421 ,4128,5764,5815 }, + {6086,2357,6096 ,6516,3933,4092 }, {3822,6044,3576 ,5715,614,5890 }, + {1153,8123,1332 ,2245,2247,3492 }, {3758,3712,3714 ,1789,1439,3970 }, + {5670,6022,3787 ,6227,6535,5819 }, {6135,5851,6000 ,6258,6095,6094 }, + {6172,5124,6146 ,5451,6567,6568 }, {4045,5654,4425 ,5916,6026,6268 }, + {3926,3970,3925 ,3125,3225,3227 }, {5175,5998,5985 ,6384,6549,5365 }, + {5175,5494,5998 ,6384,6569,6549 }, {5494,6021,5998 ,6569,6551,6549 }, + {6049,6048,6021 ,6570,6552,6551 }, {2260,6049,6021 ,6571,6570,6551 }, + {6085,6100,3515 ,6559,6572,1357 }, {3515,6100,535 ,1357,6572,2001 }, + {3548,5876,5489 ,6013,2146,4244 }, {5898,5515,6038 ,5706,5567,6264 }, + {1272,3856,1268 ,5740,5754,4568 }, {4164,5449,5540 ,6139,6543,6162 }, + {1661,6269,3611 ,5672,6083,5673 }, {3039,5526,5567 ,2686,6256,6413 }, + {6209,6244,6243 ,6012,5667,5666 }, {2621,5704,3683 ,5416,5579,5417 }, + {5323,5325,5324 ,5701,5655,5986 }, {4528,2159,1125 ,4935,6440,5723 }, + {3953,6124,3715 ,5713,6511,6124 }, {3891,3855,6847 ,3820,1511,4004 }, + {5494,5175,5020 ,6569,6384,6373 }, {1214,6085,6048 ,6573,6559,6552 }, + {1214,6101,6100 ,6573,6574,6572 }, {6085,1214,6100 ,6559,6573,6572 }, + {6101,6121,535 ,6574,6575,2001 }, {6100,6101,535 ,6572,6574,2001 }, + {6140,6153,535 ,2333,5514,2001 }, {6121,6140,535 ,6575,2333,2001 }, + {6140,3704,5040 ,2333,2332,5469 }, {6196,713,6197 ,4,3,6252 }, + {3584,6112,6078 ,5888,6406,6522 }, {6017,5912,3581 ,6506,5892,6504 }, + {5934,5489,5932 ,616,4244,5925 }, {1410,4164,5540 ,3911,6139,6162 }, + {5610,5630,5609 ,6153,4874,5658 }, {4167,5957,6063 ,6173,4054,5685 }, + {6136,4028,5824 ,5810,5823,6517 }, {3328,3288,3307 ,3973,2058,2057 }, + {6104,5858,3436 ,5696,4041,2145 }, {5976,5927,5925 ,5903,5318,5710 }, + {6228,253,2663 ,1366,2564,3593 }, {5905,5416,5528 ,6142,6461,6143 }, + {6008,5706,3779 ,3797,3796,6565 }, {5706,3414,2142 ,3796,4934,4936 }, + {5931,5928,3915 ,6576,4072,1575 }, {4308,5931,3915 ,6577,6576,1575 }, + {5931,6237,6236 ,6576,6578,6538 }, {6003,6001,5917 ,6122,6221,6402 }, + {758,3525,3688 ,5663,5665,5574 }, {1627,758,3688 ,5573,5663,5574 }, + {5929,5045,3628 ,6029,4214,4213 }, {7240,7239,7220 ,4312,1527,4367 }, + {6153,6140,5040 ,5514,2333,5469 }, {5979,1121,6125 ,6028,6564,4395 }, + {6078,6112,3508 ,6522,6406,6505 }, {5904,6189,3621 ,5851,6579,625 }, + {5849,5578,6189 ,5853,587,6579 }, {5904,5849,6189 ,5851,5853,6579 }, + {3583,5652,5651 ,5772,5774,5920 }, {5166,5120,5167 ,3427,5857,5856 }, + {5608,3472,3007 ,4356,3155,818 }, {5982,6229,5608 ,4218,6556,4356 }, + {5193,5252,4466 ,5846,5746,5854 }, {5326,5325,6041 ,3714,5655,3801 }, + {5322,5695,6145 ,4297,6465,6514 }, {4465,6210,6209 ,257,5566,6012 }, + {5928,5931,6236 ,4072,6576,6538 }, {3561,5317,6236 ,6580,6508,6538 }, + {6237,3561,6236 ,6578,6580,6538 }, {4260,4278,4277 ,3527,5583,4532 }, + {5384,5403,6016 ,6121,5921,6228 }, {5846,257,5086 ,6065,6067,6064 }, + {5303,5670,5981 ,5737,6227,6415 }, {3525,5885,5345 ,5665,6502,6438 }, + {5942,5382,5317 ,6581,6509,6508 }, {3561,5942,5317 ,6580,6581,6508 }, + {4488,4154,5382 ,6582,6510,6509 }, {3007,1696,5924 ,818,6547,6091 }, + {1811,1810,1750 ,777,779,745 }, {6621,5629,6605 ,5552,5162,5164 }, + {6058,3525,5345 ,6460,5665,6438 }, {5961,3560,5935 ,6456,5779,5778 }, + {5487,3781,5417 ,6558,5849,5923 }, {5904,3621,4200 ,5851,625,6583 }, + {7128,7129,7160 ,5391,1026,6271 }, {5302,6008,3779 ,3795,3797,6565 }, + {3472,4486,3007 ,3155,819,818 }, {5962,3469,4466 ,5745,5860,5854 }, + {3659,5314,5449 ,3875,6411,6543 }, {5314,3629,3523 ,6411,5965,6544 }, + {4164,3659,5449 ,6139,3875,6543 }, {6001,6081,6181 ,6221,6223,6251 }, + {5942,4488,5382 ,6581,6582,6509 }, {6026,5868,4154 ,6584,6527,6510 }, + {4488,6026,4154 ,6582,6584,6510 }, {3386,168,3409 ,6491,6585,6490 }, + {6148,6161,5868 ,6586,6530,6527 }, {4153,3953,5993 ,5711,5713,6475 }, + {7351,7403,7402 ,6587,6588,6589 }, {1,1022,6202 ,4622,4198,6590 }, + {2896,3845,1949 ,6110,3635,3637 }, {5963,5956,3337 ,6409,6240,4125 }, + {5933,6044,3822 ,615,614,5715 }, {5890,5686,5713 ,6591,5777,5776 }, + {5947,6230,5876 ,6027,2144,2146 }, {6014,6083,6015 ,6592,3989,3988 }, + {7129,7128,7101 ,1026,5391,4073 }, {6196,6222,6239 ,4,6253,5918 }, + {3409,3443,5472 ,6490,6302,6301 }, {3701,6149,5922 ,733,732,4292 }, + {6132,5929,3628 ,5897,6029,4213 }, {7370,7343,7344 ,3750,2750,2749 }, + {5930,7058,5979 ,6593,6594,6028 }, {3666,6068,6137 ,5699,5896,5809 }, + {5939,5649,5865 ,4276,6093,6092 }, {3836,5939,5865 ,6103,4276,6092 }, + {5608,3007,5924 ,4356,818,6091 }, {5982,5608,5924 ,4218,4356,6091 }, + {601,3286,2990 ,3905,56,3646 }, {5663,6136,5909 ,4259,5810,4260 }, + {5952,5991,3620 ,6595,5482,6001 }, {4147,20,3506 ,2517,3961,6492 }, + {6034,5952,3620 ,6049,6595,6001 }, {3012,1153,1332 ,3491,2245,3492 }, + {6026,6148,5868 ,6584,6586,6527 }, {249,348,20 ,644,643,3961 }, + {6161,6599,6187 ,6530,6596,6531 }, {5910,6354,5590 ,5121,6597,6386 }, + {6249,3701,5922 ,4032,733,4292 }, {5915,5975,6072 ,6120,6161,6155 }, + {3804,3832,3831 ,4324,2322,2321 }, {3831,3803,3804 ,2321,6436,4324 }, + {3929,5999,4166 ,5024,5184,6084 }, {3838,5999,1252 ,6257,5184,5023 }, + {6140,6101,6122 ,2333,6574,1797 }, {6295,6553,3283 ,1290,780,262 }, + {6219,6191,6220 ,6598,6599,6600 }, {6660,2138,6219 ,6601,6602,6598 }, + {5185,3632,5703 ,5939,5898,5748 }, {5355,5185,5703 ,6555,5939,5748 }, + {7909,7886,2263 ,6603,6561,3080 }, {3834,1298,6386 ,4083,5088,4085 }, + {5621,5560,1164 ,6604,6605,5087 }, {1298,5621,1164 ,5088,6604,5087 }, + {5913,5076,5560 ,6606,6607,6605 }, {5621,5913,5560 ,6604,6606,6605 }, + {5913,5919,5904 ,6606,6608,5851 }, {5076,5913,5904 ,6607,6606,5851 }, + {5919,5369,5904 ,6608,6609,5851 }, {5320,6053,6145 ,4464,4463,6514 }, + {4623,5939,3836 ,6452,4276,6103 }, {5782,4623,3836 ,5693,6452,6103 }, + {6248,5609,6213 ,6154,5658,6459 }, {3787,6159,5918 ,5819,5082,5820 }, + {1696,3007,3582 ,6547,818,1378 }, {3607,6073,4164 ,6138,3873,6139 }, + {5962,3608,3469 ,5745,5799,5860 }, {6034,3620,6175 ,6049,6001,6004 }, + {5602,3822,5964 ,5716,5715,6507 }, {5964,3576,6017 ,6507,5890,6506 }, + {5706,5893,3414 ,3796,6425,4934 }, {5247,6145,5899 ,4096,6514,6548 }, + {5253,3779,5896 ,6163,6565,6176 }, {5896,3779,4648 ,6176,6565,5722 }, + {3292,3294,3308 ,4067,3895,3894 }, {4485,6209,6208 ,32,6012,6011 }, + {6169,6191,6190 ,2334,6599,6610 }, {6190,6191,6219 ,6610,6599,6598 }, + {3522,6059,5906 ,6451,6446,6417 }, {5452,3905,5386 ,2714,4194,5870 }, + {5449,3523,3893 ,6543,6544,3789 }, {5540,5449,3893 ,6162,6543,3789 }, + {3523,5160,5861 ,6544,6186,6453 }, {3893,3523,5861 ,3789,6544,6453 }, + {7286,7344,7285 ,3460,2749,2751 }, {747,5862,5782 ,5692,3790,5693 }, + {5862,4623,5782 ,3790,6452,5693 }, {6159,3609,5918 ,5082,5081,5820 }, + {806,6073,3607 ,4783,3873,6138 }, {6203,6239,6238 ,1249,5918,5919 }, + {6034,6175,5889 ,6049,6004,1587 }, {6179,3575,3906 ,6486,5887,5697 }, + {5205,4178,5248 ,5219,3825,6469 }, {7973,8020,7972 ,3624,1405,6059 }, + {6170,3725,3537 ,3719,3971,1735 }, {7568,7567,7508 ,5544,5545,6196 }, + {187,1171,3498 ,6439,6441,5800 }, {7311,7310,7287 ,4334,4283,3459 }, + {6076,3248,5870 ,4217,4216,4275 }, {5969,6076,5870 ,6188,4217,4275 }, + {5969,5870,5861 ,6188,4275,6453 }, {5160,5969,5861 ,6186,6188,6453 }, + {5937,1298,3834 ,6611,5088,4083 }, {4044,5937,3834 ,4322,6611,4083 }, + {5937,1382,1298 ,6611,6612,5088 }, {1298,1382,5621 ,5088,6612,6604 }, + {1382,6047,5913 ,6612,6613,6606 }, {5621,1382,5913 ,6604,6612,6606 }, + {6047,5603,5913 ,6613,6614,6606 }, {5913,5603,5919 ,6606,6614,6608 }, + {5402,5369,5919 ,6615,6609,6608 }, {5603,5402,5919 ,6614,6615,6608 }, + {5687,6065,5369 ,6420,6616,6609 }, {5402,5687,5369 ,6615,6420,6609 }, + {2912,5903,1379 ,3934,6480,4137 }, {3620,5907,3860 ,6001,6116,6523 }, + {5703,6165,1464 ,5748,4219,5768 }, {5602,5964,3508 ,5716,6507,6505 }, + {3507,5602,3508 ,5671,5716,6505 }, {3773,3812,3811 ,4279,1513,4323 }, + {537,5567,5955 ,4834,6413,6512 }, {5610,3715,5609 ,6153,6124,5658 }, + {6292,6060,6282 ,5403,5914,6617 }, {7509,7568,7508 ,1874,5544,6196 }, + {381,3930,4247 ,6450,6467,6468 }, {6154,6182,6169 ,2387,6618,2334 }, + {6169,6182,6191 ,2334,6618,6599 }, {5072,4044,6261 ,6619,4322,4321 }, + {3527,5687,5402 ,6620,6420,6615 }, {158,4200,3621 ,5258,6583,625 }, + {2582,1017,3451 ,873,1793,1720 }, {6089,5888,6050 ,6494,5752,4094 }, + {6165,6164,1464 ,4219,6562,5768 }, {6073,3659,4164 ,3873,3875,6139 }, + {2160,5328,5370 ,6621,2172,2171 }, {5956,5860,6151 ,6240,5998,6024 }, + {6038,5631,5654 ,6264,6536,6026 }, {6147,6172,6146 ,5452,5451,6568 }, + {6013,3943,5956 ,6060,4755,6240 }, {422,1701,216 ,4689,6476,4644 }, + {5126,4447,5563 ,6170,717,4605 }, {3018,5905,5530 ,6125,6142,6189 }, + {5294,5692,5912 ,5891,6229,5892 }, {6056,5478,5883 ,5808,5806,5850 }, + {4278,4308,4307 ,5583,6577,4562 }, {5878,5892,5549 ,2684,2683,6435 }, + {5462,5961,5935 ,6418,6456,5778 }, {3725,6170,6182 ,3971,3719,6618 }, + {6154,3725,6182 ,2387,3971,6618 }, {6170,6192,6191 ,3719,6622,6599 }, + {6182,6170,6191 ,6618,3719,6599 }, {6192,6200,6191 ,6622,6623,6599 }, + {6191,6200,6220 ,6599,6623,6600 }, {6200,6231,6220 ,6623,6624,6600 }, + {7851,7850,7825 ,6625,3769,3768 }, {6047,1382,5603 ,6613,6612,6614 }, + {5247,5899,5227 ,4096,6548,4097 }, {5703,1464,932 ,5748,5768,5765 }, + {5246,6134,6039 ,5176,2729,2728 }, {5980,6010,5294 ,5557,6197,5891 }, + {6028,6029,3860 ,5316,1454,6523 }, {5958,3507,3508 ,5702,5671,6505 }, + {6112,5958,3508 ,6406,5702,6505 }, {3631,5487,5417 ,5922,6558,5923 }, + {1723,5476,1261 ,6626,6495,6472 }, {4307,4308,3915 ,4562,6577,1575 }, + {2321,3262,3509 ,5999,6627,6519 }, {3471,5627,3262 ,4104,6628,6627 }, + {6177,3471,3262 ,6629,4104,6627 }, {6246,3666,6274 ,5559,5699,5684 }, + {5947,5948,5717 ,6027,5719,4023 }, {5876,3548,5933 ,2146,6013,615 }, + {4541,3631,3079 ,6630,5922,6540 }, {6192,6170,6200 ,6622,3719,6623 }, + {6231,6262,5072 ,6624,6631,6619 }, {3982,4044,5072 ,6632,4322,6619 }, + {6262,3982,5072 ,6631,6632,6619 }, {4044,3982,5937 ,4322,6632,6611 }, + {3982,1007,1382 ,6632,6633,6612 }, {5937,3982,1382 ,6611,6632,6612 }, + {5484,5603,1382 ,6634,6614,6612 }, {1007,5484,1382 ,6633,6634,6612 }, + {6106,5402,5603 ,6635,6615,6614 }, {5484,6106,5603 ,6634,6635,6614 }, + {5748,3527,5402 ,6636,6620,6615 }, {6106,5748,5402 ,6635,6636,6615 }, + {4653,4600,4679 ,4347,4360,3852 }, {6523,5367,5349 ,5319,6034,5320 }, + {6057,5935,5524 ,6423,5778,6426 }, {966,5526,3039 ,2565,6256,2686 }, + {806,3607,6267 ,4783,6138,6407 }, {5588,3835,5550 ,6088,6046,6047 }, + {1293,6217,932 ,5764,6554,5765 }, {3415,5488,5246 ,6115,2730,5176 }, + {5911,5474,5932 ,5924,4243,5925 }, {1661,6111,6179 ,5672,5674,6486 }, + {635,2938,5534 ,2183,4320,6524 }, {6050,3765,5394 ,4094,4093,5660 }, + {4425,6177,3262 ,6268,6629,6627 }, {2321,4425,3262 ,5999,6268,6627 }, + {5534,1629,5971 ,6524,4207,6479 }, {2938,1629,5534 ,4320,4207,6524 }, + {2204,5867,5866 ,6520,6499,5302 }, {7291,7268,7316 ,3774,3773,5948 }, + {6213,6212,4391 ,6459,5562,992 }, {3631,4541,5487 ,5922,6630,6558 }, + {6170,6193,6200 ,3719,6637,6623 }, {6262,6231,3982 ,6631,6624,6632 }, + {3982,3667,1007 ,6632,6638,6633 }, {6011,5484,1007 ,6639,6634,6633 }, + {5484,5748,6106 ,6634,6636,6635 }, {5451,3446,5608 ,6219,3718,4356 }, + {6217,5703,932 ,6554,5748,5765 }, {3415,6186,5488 ,6115,4365,2730 }, + {5899,5925,5227 ,6548,5710,4097 }, {3560,5853,3658 ,5779,6038,6521 }, + {4153,4292,4374 ,5711,6400,5712 }, {5559,5687,3527 ,6640,6420,6620 }, + {1741,4466,6104 ,5695,5854,5696 }, {955,377,5787 ,3991,4311,4305 }, + {5302,5253,5975 ,3795,6163,6161 }, {5860,4045,2321 ,5998,5916,5999 }, + {3248,5982,5649 ,4216,4218,6093 }, {1261,3979,5591 ,6472,6471,3597 }, + {1629,1379,5971 ,4207,4137,6479 }, {3575,6179,3584 ,5887,6486,5888 }, + {5886,5887,6087 ,6192,6493,6193 }, {2645,3041,3535 ,4418,3182,442 }, + {3451,6183,6170 ,1720,6641,3719 }, {6183,6193,6170 ,6641,6637,3719 }, + {6193,6221,6200 ,6637,6642,6623 }, {6200,6221,6231 ,6623,6642,6624 }, + {3982,6127,3667 ,6632,6643,6638 }, {6127,1058,1007 ,6643,6644,6633 }, + {3667,6127,1007 ,6638,6643,6633 }, {6097,6011,1007 ,6645,6639,6633 }, + {1058,6097,1007 ,6644,6645,6633 }, {5460,5484,6011 ,6646,6634,6639 }, + {6097,5460,6011 ,6645,6646,6639 }, {1608,5992,1293 ,4128,4197,5764 }, + {5355,6217,1293 ,6555,6554,5764 }, {3558,6078,3581 ,5964,6522,6504 }, + {5957,5824,4105 ,4054,6517,4053 }, {5627,6184,6128 ,6628,6089,5981 }, + {4045,4425,2321 ,5916,6268,5999 }, {6104,5254,5858 ,5696,4042,4041 }, + {5824,4028,3411 ,6517,5823,4280 }, {5455,6509,5013 ,686,1452,684 }, + {5322,6145,4023 ,4297,6514,3827 }, {5971,1379,5903 ,6479,4137,6480 }, + {5627,6128,3509 ,6628,5981,6519 }, {3262,5627,3509 ,6627,6628,6519 }, + {6044,5294,3576 ,614,5891,5890 }, {572,571,475 ,1947,1885,1884 }, + {1605,6197,713 ,17,6252,3 }, {3041,3618,3535 ,3182,37,442 }, + {3451,6194,6193 ,1720,6647,6637 }, {6183,3451,6193 ,6641,1720,6637 }, + {6194,6201,6221 ,6647,6648,6642 }, {6193,6194,6221 ,6637,6647,6642 }, + {6232,6231,6221 ,6649,6624,6642 }, {6201,6232,6221 ,6648,6649,6642 }, + {6232,1746,3982 ,6649,6650,6632 }, {6231,6232,3982 ,6624,6649,6632 }, + {3982,1746,6127 ,6632,6650,6643 }, {6025,5748,5484 ,6651,6636,6634 }, + {5460,6025,5484 ,6646,6651,6634 }, {3716,5559,5748 ,6652,6640,6636 }, + {6025,3716,5748 ,6651,6652,6636 }, {5629,5048,5559 ,5162,6421,6640 }, + {3716,5629,5559 ,6652,5162,6640 }, {5992,5355,1293 ,4197,6555,5764 }, + {5580,3717,5630 ,6404,6487,4874 }, {5403,5417,5879 ,5921,5923,6220 }, + {6016,5403,5879 ,6228,5921,6220 }, {6150,1723,7053 ,6653,6626,6654 }, + {5909,5824,4167 ,4260,6517,6173 }, {6270,6271,3780 ,6082,6152,6526 }, + {6142,5854,3632 ,5941,6265,5898 }, {1605,6205,6223 ,17,6463,6151 }, + {6223,6205,6254 ,6151,6463,5758 }, {6055,5129,3659 ,3874,5698,3875 }, + {5705,5592,4567 ,4127,3671,6655 }, {5228,5205,5247 ,4095,5219,4096 }, + {5291,6148,6026 ,6656,6586,6584 }, {5232,5277,5255 ,6238,6237,3682 }, + {4444,6211,6210 ,582,5558,5566 }, {5011,5457,1252 ,6657,6308,5023 }, + {3443,5886,5869 ,6302,6192,6224 }, {3943,4045,5860 ,4755,5916,5998 }, + {3845,5319,6216 ,3635,5739,6658 }, {3953,4374,3018 ,5713,5712,6125 }, + {2291,350,2342 ,1115,1114,5690 }, {35,6228,2663 ,1338,1366,3593 }, + {3451,6171,6194 ,1720,6659,6647 }, {5912,5692,5329 ,5892,6229,6191 }, + {5632,5912,5329 ,6660,5892,6191 }, {5965,5632,5329 ,6190,6660,6191 }, + {3629,3581,3524 ,5965,6504,6187 }, {3471,6150,1219 ,4104,6653,4102 }, + {5854,6131,3632 ,6265,6050,5898 }, {6197,1605,6223 ,6252,17,6151 }, + {1605,4540,6206 ,17,19,6464 }, {6196,6197,6222 ,4,6252,6253 }, + {2636,5386,5789 ,2712,5870,6140 }, {5457,5020,4241 ,6308,6373,6321 }, + {5020,5457,5011 ,6373,6308,6657 }, {5973,5871,6110 ,6246,4124,6247 }, + {42,297,6783 ,5195,1918,3632 }, {5907,3415,5246 ,6116,6115,5176 }, + {5925,5927,5227 ,5710,5318,4097 }, {4572,3638,8038 ,5949,5950,4121 }, + {5987,6229,5982 ,5621,6556,4218 }, {6188,5863,5916 ,6174,6537,6175 }, + {6093,6086,6050 ,5771,6516,4094 }, {3443,5887,5886 ,6302,6493,6192 }, + {6171,3451,1017 ,6659,1720,1793 }, {6201,6194,6171 ,6648,6647,6659 }, + {6263,873,1746 ,6661,6662,6650 }, {6232,6263,1746 ,6649,6661,6650 }, + {873,6109,6127 ,6662,6663,6643 }, {1746,873,6127 ,6650,6662,6643 }, + {6109,4046,1058 ,6663,6664,6644 }, {6127,6109,1058 ,6643,6663,6644 }, + {3045,6097,1058 ,6665,6645,6644 }, {4046,3045,1058 ,6664,6665,6644 }, + {5900,5460,6097 ,6666,6646,6645 }, {3045,5900,6097 ,6665,6666,6645 }, + {5900,5448,5460 ,6666,6667,6646 }, {5381,6025,5460 ,6668,6651,6646 }, + {5448,5381,5460 ,6667,6668,6646 }, {7976,7975,7952 ,1715,6669,5475 }, + {5251,6041,5273 ,3802,3801,3819 }, {5692,6016,5879 ,6229,6228,6220 }, + {5329,5692,5879 ,6191,6229,6220 }, {6090,5874,6053 ,4897,4896,4463 }, + {6029,5976,6066 ,1454,5903,1455 }, {5996,5705,4567 ,6249,4127,6655 }, + {3582,3660,5362 ,1378,709,6670 }, {6205,6240,6254 ,6463,5756,5758 }, + {5863,5753,5916 ,6537,6481,6175 }, {5849,1124,5578 ,5853,5852,587 }, + {6212,6247,6246 ,5562,5575,5559 }, {6211,6212,6246 ,5558,5562,5559 }, + {5567,537,2040 ,6413,4834,4833 }, {4484,4465,6209 ,258,257,6012 }, + {262,359,210 ,4758,4841,4592 }, {1455,5202,808 ,1917,5550,4734 }, + {2140,4565,5447 ,5635,5944,5633 }, {6149,3648,3445 ,732,734,1791 }, + {3385,3836,5921 ,6104,6103,5705 }, {6269,6270,3611 ,6083,6082,5673 }, + {1854,5971,5368 ,2184,6479,5751 }, {5124,3970,6146 ,6567,3225,6568 }, + {3611,6063,6111 ,5673,5685,5674 }, {6157,6171,1017 ,5569,6659,1793 }, + {6157,4063,6171 ,5569,4733,6659 }, {6202,6201,6171 ,6590,6648,6659 }, + {4063,6202,6171 ,4733,6590,6659 }, {6202,6233,6232 ,6590,6671,6649 }, + {6201,6202,6232 ,6648,6590,6649 }, {6232,6233,6263 ,6649,6671,6661 }, + {5183,3716,6025 ,6672,6652,6651 }, {5381,5183,6025 ,6668,6672,6651 }, + {5691,5629,3716 ,6673,5162,6652 }, {5183,5691,3716 ,6672,6673,6652 }, + {5691,5938,5629 ,6673,5163,5162 }, {6078,3508,3581 ,6522,6505,6504 }, + {6203,6238,806 ,1249,5919,4783 }, {7203,7220,7185 ,4366,4367,5073 }, + {3620,5946,5907 ,6001,5481,6116 }, {5533,6195,3609 ,6239,5529,5081 }, + {5992,5185,5355 ,4197,5939,6555 }, {3611,3780,6063 ,5673,6526,5685 }, + {3829,3051,5877 ,6195,6488,4242 }, {6024,5743,5646 ,6482,6432,6431 }, + {5526,252,5897 ,6256,2743,6414 }, {5368,5971,5901 ,5751,6479,6674 }, + {5368,5901,5888 ,5751,6674,5752 }, {808,4987,1455 ,4734,1491,1917 }, + {3606,1410,1510 ,6137,3911,3788 }, {5749,6249,5922 ,6052,4032,4292 }, + {2204,5866,5864 ,6520,5302,5304 }, {3780,4167,6063 ,6526,6173,5685 }, + {5884,3446,5451 ,5848,3718,6219 }, {4269,6199,4229 ,4860,4859,4877 }, + {1426,4063,6157 ,4648,4733,5569 }, {1,6202,4063 ,4622,6590,4733 }, + {8049,8035,8018 ,5393,6675,6676 }, {843,6203,806 ,2423,1249,4783 }, + {3581,5632,5965 ,6504,6660,6190 }, {3524,3581,5965 ,6187,6504,6190 }, + {5824,5957,4167 ,6517,4054,6173 }, {6179,6111,5575 ,6486,5674,5686 }, + {4485,4484,6209 ,32,258,6012 }, {5489,5475,5877 ,4244,4038,4242 }, + {7511,7510,7453 ,6677,1873,1187 }, {1700,422,81 ,6072,4689,4673 }, + {3900,552,8132 ,884,3929,413 }, {5129,3906,3575 ,5698,5697,5887 }, + {6037,3856,1272 ,5235,5754,5740 }, {75,107,930 ,3962,2548,3594 }, + {3688,4153,1729 ,5574,5711,3666 }, {253,1664,249 ,2564,2268,644 }, + {808,5941,4987 ,4734,4976,1491 }, {5941,5967,4987 ,4976,1352,1491 }, + {5876,5891,5948 ,2146,5714,5719 }, {5947,5876,5948 ,6027,2146,5719 }, + {3584,5575,6112 ,5888,5686,6406 }, {6057,5524,5994 ,6423,6426,6424 }, + {3801,5362,3660 ,4974,6670,709 }, {4147,3384,3409 ,2517,6518,6490 }, + {5986,306,1251 ,4603,6234,4604 }, {6202,6252,6233 ,6590,6678,6671 }, + {6233,6252,6263 ,6671,6678,6661 }, {6252,1484,873 ,6678,6679,6662 }, + {6263,6252,873 ,6661,6678,6662 }, {1484,5525,6109 ,6679,6680,6663 }, + {873,1484,6109 ,6662,6679,6663 }, {5525,5589,4046 ,6680,6681,6664 }, + {6109,5525,4046 ,6663,6680,6664 }, {1242,3045,4046 ,6682,6665,6664 }, + {5589,1242,4046 ,6681,6682,6664 }, {5539,5900,3045 ,6683,6666,6665 }, + {1242,5539,3045 ,6682,6683,6665 }, {5539,5448,5900 ,6683,6667,6666 }, + {5271,5381,5448 ,6684,6668,6667 }, {5539,5271,5448 ,6683,6684,6667 }, + {5271,5183,5381 ,6684,6672,6668 }, {5938,5502,5081 ,5163,1256,1258 }, + {806,6238,6073 ,4783,5919,3873 }, {6066,5976,5874 ,1455,5903,4896 }, + {5632,3581,5912 ,6660,6504,5892 }, {1696,3582,5362 ,6547,1378,6670 }, + {3524,5965,5969 ,6187,6190,6188 }, {6273,6274,5880 ,5683,5684,5700 }, + {3499,5394,5628 ,5661,5660,6560 }, {3547,3598,3546 ,803,802,2557 }, + {3879,3904,5928 ,2549,1264,4072 }, {6063,202,5575 ,5685,4105,5686 }, + {5981,6226,3905 ,6415,4192,4194 }, {3087,3131,3130 ,3573,3732,3414 }, + {5943,6283,7088 ,5402,4298,4299 }, {3629,3558,3581 ,5965,5964,6504 }, + {5316,3835,5588 ,6087,6046,6088 }, {3248,5649,5939 ,4216,6093,4276 }, + {7333,7332,3304 ,4353,6685,1050 }, {7208,7226,7225 ,1671,6686,1672 }, + {5949,5967,2960 ,5086,1352,4975 }, {2960,5967,5941 ,4975,1352,4976 }, + {6179,5575,3584 ,6486,5686,5888 }, {5345,5885,6057 ,6438,6502,6423 }, + {253,751,6228 ,2564,1365,1366 }, {168,4147,3409 ,6585,2517,6490 }, + {1022,1,969 ,4198,4622,4199 }, {3515,3719,501 ,1357,1356,4313 }, + {58,1332,8143 ,2156,3492,2570 }, {6202,1022,6252 ,6590,4198,6678 }, + {5176,5183,5271 ,6687,6672,6684 }, {6064,5691,5183 ,6688,6673,6672 }, + {5176,6064,5183 ,6687,6688,6672 }, {5502,5938,5691 ,1256,5163,6673 }, + {6064,5502,5691 ,6688,1256,6673 }, {6218,3560,3658 ,5780,5779,6521 }, + {5351,168,3386 ,6513,6585,6491 }, {4268,6199,4269 ,4848,4859,4860 }, + {6084,6087,3499 ,6194,6193,5661 }, {5361,5462,5935 ,5664,6418,5778 }, + {6087,5394,3499 ,6193,5660,5661 }, {3822,3576,5964 ,5715,5890,6507 }, + {3456,8083,5771 ,1145,2103,2868 }, {2263,2262,7909 ,3080,3359,6603 }, + {5824,3411,4105 ,6517,4280,4053 }, {5351,5513,168 ,6513,6500,6585 }, + {2206,5592,2224 ,176,3671,2660 }, {807,806,6267 ,6073,4783,6407 }, + {5513,4147,168 ,6500,2517,6585 }, {7923,7948,7896 ,816,3625,4355 }, + {2204,5864,3311 ,6520,5304,5303 }, {5129,3575,5293 ,5698,5887,5889 }, + {2663,783,35 ,3593,679,1338 }, {6252,6264,1484 ,6678,6689,6679 }, + {5754,5271,5539 ,6690,6684,6683 }, {5502,6064,5176 ,1256,6688,6687 }, + {5854,3951,6130 ,6265,6528,6300 }, {6051,6132,6130 ,6037,5897,6300 }, + {5211,5191,6433 ,6071,2900,6691 }, {5695,5320,6145 ,6465,4464,6514 }, + {2573,2660,3569 ,3253,455,4381 }, {4485,6208,6207 ,32,6011,6156 }, + {4517,4292,6058 ,6484,6400,6460 }, {6266,1700,3563 ,399,6072,6074 }, + {3870,5915,3686 ,6096,6120,6119 }, {5932,5370,5934 ,5925,2171,616 }, + {422,1700,1701 ,4689,6072,6476 }, {4321,6225,4343 ,4873,4872,1818 }, + {4356,166,4404 ,155,3729,5072 }, {5978,6114,5195 ,5581,1805,6235 }, + {5212,5978,5195 ,430,5581,6235 }, {4246,4247,1729 ,3665,6468,3666 }, + {5314,5293,3629 ,6411,5889,5965 }, {5528,5302,5712 ,6143,3795,6160 }, + {3797,3747,5490 ,2061,2925,6055 }, {4515,4485,6207 ,30,32,6156 }, + {5915,5820,5975 ,6120,6144,6161 }, {3679,5990,5351 ,6447,6692,6513 }, + {807,6267,3606 ,6073,6407,6137 }, {1022,6253,6252 ,4198,6693,6678 }, + {6253,6265,6264 ,6693,6694,6689 }, {6252,6253,6264 ,6678,6693,6689 }, + {6265,4189,1484 ,6694,6695,6679 }, {6264,6265,1484 ,6689,6694,6679 }, + {4189,5184,5525 ,6695,6696,6680 }, {1484,4189,5525 ,6679,6695,6680 }, + {5184,3874,5589 ,6696,6697,6681 }, {5525,5184,5589 ,6680,6696,6681 }, + {3908,1242,5589 ,6698,6682,6681 }, {3874,3908,5589 ,6697,6698,6681 }, + {3776,5539,1242 ,6699,6683,6682 }, {3908,3776,1242 ,6698,6699,6682 }, + {5780,5754,5539 ,6700,6690,6683 }, {3776,5780,5539 ,6699,6700,6683 }, + {4344,5271,5754 ,6701,6684,6690 }, {5780,4344,5754 ,6700,6701,6690 }, + {5968,5176,5271 ,6702,6687,6684 }, {4344,5968,5271 ,6701,6702,6684 }, + {5088,5502,5176 ,5004,1256,6687 }, {5968,5088,5176 ,6702,5004,6687 }, + {6240,1661,6268 ,5756,5672,5757 }, {1723,6150,3471 ,6626,6653,4104 }, + {5476,1723,3471 ,6495,6626,4104 }, {4367,6224,4391 ,228,6458,992 }, + {5233,5978,5212 ,429,5581,430 }, {1204,4499,4500 ,338,4510,4523 }, + {6014,6015,5838 ,6592,3988,3987 }, {5910,6014,5838 ,5121,6592,3987 }, + {3408,3435,3434 ,3484,6703,737 }, {6076,6033,5982 ,4217,5620,4218 }, + {1008,5316,3414 ,6427,6087,4934 }, {1252,3839,3838 ,5023,6310,6257 }, + {6059,3559,3679 ,6446,6412,6447 }, {5897,5990,3679 ,6414,6692,6447 }, + {5316,4528,3414 ,6087,4935,4934 }, {1629,931,1379 ,4207,3944,4137 }, + {6123,5513,5351 ,4221,6500,6513 }, {5981,6007,5562 ,6415,6704,4193 }, + {6137,6068,3989 ,5809,5896,5822 }, {1022,1051,6253 ,4198,4588,6693 }, + {3395,5116,5854 ,5940,4126,6265 }, {3780,5356,4167 ,6526,4258,6173 }, + {5192,5044,3762 ,6454,5173,5172 }, {5916,5476,6177 ,6175,6495,6629 }, + {6052,3762,3835 ,5717,5172,6046 }, {5477,2636,5789 ,5399,2712,6140 }, + {8080,8141,8132 ,1094,3295,413 }, {4299,1511,2431 ,5984,4921,2977 }, + {5486,6052,3835 ,5718,5717,6046 }, {4028,5193,3411 ,5823,5846,4280 }, + {7532,7565,7507 ,6705,5080,6706 }, {5901,5971,5888 ,6674,6479,5752 }, + {5971,5902,5888 ,6479,5769,5752 }, {3506,1854,5368 ,6492,2184,5751 }, + {5990,6123,5351 ,6692,4221,6513 }, {3559,5897,3679 ,6412,6414,6447 }, + {6145,6053,5925 ,6514,4463,5710 }, {5869,6084,3680 ,6224,6194,6497 }, + {3366,5461,5252 ,5894,5744,5746 }, {3908,3874,3776 ,6698,6697,6699 }, + {5116,3951,5854 ,4126,6528,6265 }, {2208,5502,5088 ,1257,1256,5004 }, + {5450,5472,6032 ,6525,6301,6489 }, {5246,5488,6134 ,5176,2730,2729 }, + {2159,5549,1171 ,6440,6435,6441 }, {3836,5865,5921 ,6103,6092,5705 }, + {5892,3498,1171 ,2683,5800,6441 }, {6042,1696,5230 ,5703,6547,5704 }, + {4247,1627,1729 ,6468,5573,3666 }, {4425,5916,6177 ,6268,6175,6629 }, + {5549,5892,1171 ,6435,2683,6441 }, {4278,6138,5931 ,5583,6014,6576 }, + {4308,4278,5931 ,6577,5583,6576 }, {6138,6250,6237 ,6014,6532,6578 }, + {5316,5743,3835 ,6087,6432,6046 }, {5233,5232,5978 ,429,6238,5581 }, + {5983,6082,5232 ,6707,1348,6238 }, {5590,6014,5910 ,6386,6592,5121 }, + {5931,6138,6237 ,6576,6014,6578 }, {6250,3444,3561 ,6532,6054,6580 }, + {3559,6059,3522 ,6412,6446,6451 }, {1696,5362,5230 ,6547,6670,5704 }, + {1701,2896,1949 ,6476,6110,3637 }, {5865,6042,5921 ,6092,5703,5705 }, + {6036,5789,5883 ,6141,6140,5850 }, {5478,6036,5883 ,5806,6141,5850 }, + {6237,6250,3561 ,6578,6532,6580 }, {1620,5872,6129 ,5982,6557,5983 }, + {5245,4949,4724 ,5880,5526,4737 }, {3469,3608,6067 ,5860,5799,6266 }, + {5891,5933,3822 ,5714,615,5715 }, {1051,1858,217 ,4588,4590,4643 }, + {1051,1532,6253 ,4588,4771,6693 }, {6253,1532,6265 ,6693,4771,6694 }, + {1532,5275,4189 ,4771,6708,6695 }, {6265,1532,4189 ,6694,4771,6695 }, + {4349,5184,4189 ,6709,6696,6695 }, {5275,4349,4189 ,6708,6709,6695 }, + {3927,3874,5184 ,6710,6697,6696 }, {4349,3927,5184 ,6709,6710,6696 }, + {3927,6117,3874 ,6710,6711,6697 }, {5959,3776,3874 ,6712,6699,6697 }, + {6117,5959,3874 ,6711,6712,6697 }, {5565,5780,3776 ,6713,6700,6699 }, + {5959,5565,3776 ,6712,6713,6699 }, {5565,3610,4344 ,6713,6714,6701 }, + {5780,5565,4344 ,6700,6713,6701 }, {5873,5968,4344 ,6715,6702,6701 }, + {3610,5873,4344 ,6714,6715,6701 }, {5393,5088,5968 ,5005,5004,6702 }, + {5873,5393,5968 ,6715,5005,6702 }, {6110,5871,3395 ,6247,4124,5940 }, + {6105,6062,1268 ,5565,6478,4568 }, {7370,7422,7396 ,3750,1404,6716 }, + {2343,6563,2210 ,2685,464,2510 }, {5995,6012,5974 ,5564,6408,6474 }, + {5789,6258,5954 ,6140,6553,5868 }, {5883,5789,5954 ,5850,6140,5868 }, + {6258,5386,5954 ,6553,5870,5868 }, {5476,3471,6177 ,6495,4104,6629 }, + {5954,5386,3657 ,5868,5870,5869 }, {5983,5232,5255 ,6707,6238,3682 }, + {6241,6269,1661 ,6410,6083,5672 }, {5895,5942,3561 ,6053,6581,6580 }, + {5743,5486,3835 ,6432,5718,6046 }, {5386,6185,6178 ,5870,6051,5881 }, + {3657,5386,6178 ,5869,5870,5881 }, {5232,6082,5978 ,6238,1348,5581 }, + {6271,5356,3780 ,6152,4258,6526 }, {3444,5895,3561 ,6054,6053,6580 }, + {3731,4488,5942 ,6041,6582,6581 }, {3559,3522,1417 ,6412,6451,6501 }, + {5955,3559,1417 ,6512,6412,6501 }, {3717,3953,3715 ,6487,5713,6124 }, + {5630,5610,6225 ,4874,6153,4872 }, {6185,3892,6178 ,6051,4970,5881 }, + {5956,5951,3337 ,6240,6000,4125 }, {5712,5302,5975 ,6160,3795,6161 }, + {5417,3781,5451 ,5923,5849,6219 }, {5895,3731,5942 ,6053,6041,6581 }, + {6174,6002,6173 ,2381,5975,3711 }, {5935,3658,5524 ,5778,6521,6426 }, + {5426,5447,5970 ,5634,5633,3518 }, {537,5955,1417 ,4834,6512,6501 }, + {5743,6024,5486 ,6432,6482,5718 }, {6007,3787,5562 ,6704,5819,4193 }, + {252,6103,5897 ,2743,4220,6414 }, {5885,5935,6057 ,6502,5778,6423 }, + {4574,5301,1268 ,6477,2698,4568 }, {5974,3470,3079 ,6474,6401,6540 }, + {5995,5974,6062 ,5564,6474,6478 }, {5781,5782,3385 ,5694,5693,6104 }, + {6568,5395,5427 ,6717,5348,5811 }, {6075,6026,4488 ,6042,6584,6582 }, + {3731,6075,4488 ,6041,6042,6582 }, {5628,3609,6144 ,6560,5081,5083 }, + {6091,5384,6010 ,2173,6121,6197 }, {6069,6082,5983 ,6180,1348,6707 }, + {5255,6069,5983 ,3682,6180,6707 }, {58,8143,8128 ,2156,2570,959 }, + {4170,5447,5579 ,3731,5633,5942 }, {1002,1283,5788 ,2976,2978,6086 }, + {3844,4170,5579 ,2443,3731,5942 }, {5788,3844,5579 ,6086,2443,5942 }, + {4299,2431,1002 ,5984,2977,2976 }, {6075,5291,6026 ,6042,6656,6584 }, + {5908,6148,5291 ,4293,6586,6656 }, {6023,3360,3410 ,6040,6485,6483 }, + {6095,5973,6081 ,6222,6246,6223 }, {6081,6110,3395 ,6223,6247,5940 }, + {5981,3787,6007 ,6415,5819,6704 }, {5562,5918,5945 ,4193,5820,6396 }, + {5996,4567,6156 ,6249,6655,6250 }, {7743,7744,7769 ,5534,6101,5535 }, + {5329,5879,5711 ,6191,6220,6248 }, {5393,5362,3801 ,5005,6670,4974 }, + {5871,5963,3337 ,4124,6409,4125 }, {6105,5995,6062 ,5565,5564,6478 }, + {3217,5475,6030 ,6117,4038,4037 }, {4410,4094,5670 ,5735,6225,6227 }, + {6075,5908,5291 ,6042,4293,6656 }, {5559,3527,5748 ,6640,6620,6636 }, + {1121,5936,6125 ,6564,4393,4395 }, {6167,6259,5590 ,5974,6718,6386 }, + {6002,6167,5590 ,5975,5974,6386 }, {6003,5871,5973 ,6122,4124,6246 }, + {5871,5116,3395 ,4124,4126,5940 }, {5671,3599,5416 ,6515,6428,6461 }, + {6199,537,4229 ,4859,4834,4877 }, {537,1417,4229 ,4834,6501,4877 }, + {5670,3787,5981 ,6227,5819,6415 }, {6012,6004,5974 ,6408,6123,6474 }, + {6068,3366,3989 ,5896,5894,5822 }, {1620,1122,5872 ,5982,6719,6557 }, + {6215,5275,1532 ,3636,6708,4771 }, {1949,6215,1532 ,3637,3636,4771 }, + {3845,4349,5275 ,3635,6709,6708 }, {6215,3845,5275 ,3636,3635,6708 }, + {3845,3927,4349 ,3635,6710,6709 }, {3845,6216,3927 ,3635,6658,6710 }, + {6216,5319,6117 ,6658,5739,6711 }, {3927,6216,6117 ,6710,6658,6711 }, + {6117,5319,5959 ,6711,5739,6712 }, {5319,5781,5959 ,5739,5694,6712 }, + {5781,3385,5565 ,5694,6104,6713 }, {5959,5781,5565 ,6712,5694,6713 }, + {5565,3385,3610 ,6713,6104,6714 }, {3385,5921,3610 ,6104,5705,6714 }, + {5230,5873,3610 ,5704,6715,6714 }, {5921,5230,3610 ,5705,5704,6714 }, + {5362,5393,5873 ,6670,5005,6715 }, {5230,5362,5873 ,5704,6670,6715 }, + {8131,8105,8075 ,2307,3791,2308 }, {5897,6123,5990 ,6414,4221,6692 }, + {5951,3509,5123 ,6000,6519,6433 }, {3337,5951,5123 ,4125,6000,6433 }, + {6208,6243,6242 ,6011,5666,6081 }, {3469,6067,5651 ,5860,6266,5920 }, + {3498,6036,5478 ,5800,6141,5806 }, {5193,4466,1741 ,5846,5854,5695 }, + {4444,4443,6211 ,582,703,5558 }, {5854,6130,6131 ,6265,6300,6050 }, + {3902,3935,4807 ,4437,4346,6449 }, {7082,3415,5946 ,6462,6115,5481 }, + {7873,7897,7846 ,817,6015,4162 }, {8086,8099,8087 ,4823,2867,385 }, + {5520,1185,1516 ,1304,6159,1254 }, {257,3284,1380 ,6067,6720,6721 }, + {5706,6031,3779 ,3796,6429,6565 }, {1313,2373,1947 ,3674,6722,6723 }, + {7089,7115,7103 ,5608,1024,6455 }, {2220,1072,6709 ,6724,6725,6726 }, + {5085,1313,1947 ,3675,3674,6723 }, {2872,4204,784 ,311,38,4211 }, + {4542,6378,6481 ,6727,6728,6729 }, {5069,257,255 ,6730,6067,6731 }, + {352,351,255 ,593,592,6731 }, {5578,3621,6189 ,587,625,6579 }, + {6965,6964,6944 ,6732,6733,2633 }, {7005,7046,7004 ,6734,6735,5502 }, + {6914,6945,6913 ,3532,2632,2634 }, {6945,6965,6944 ,2632,6732,2633 }, + {2406,6530,1217 ,6736,6398,6399 }, {1215,6644,6950 ,3937,5098,1349 }, + {7719,7744,7743 ,6541,6101,5534 }, {4986,3399,5441 ,5096,715,5194 }, + {4831,4486,3472 ,3154,819,3155 }, {144,6430,5353 ,853,2748,851 }, + {7005,7004,6964 ,6734,5502,6733 }, {6965,7005,6964 ,6732,6734,6733 }, + {7046,7045,7004 ,6735,5503,5502 }, {2335,2284,7045 ,6737,6738,5503 }, + {7046,2335,7045 ,6735,6737,5503 }, {3609,5920,6990 ,5081,5453,6739 }, + {6858,6859,6910 ,6740,472,6741 }, {6158,7087,7086 ,6112,6742,6743 }, + {5978,6079,6114 ,5581,5580,1805 }, {3598,6598,3597 ,802,804,1810 }, + {6598,5908,3597 ,804,4293,1810 }, {4988,5020,5011 ,6744,6373,6657 }, + {6569,4988,5011 ,6745,6744,6657 }, {1118,4853,2284 ,6746,6747,6738 }, + {2335,1118,2284 ,6737,6746,6738 }, {6284,7086,5952 ,6113,6743,6595 }, + {3445,3597,5908 ,1791,1810,4293 }, {7062,1464,6164 ,6563,5768,6562 }, + {1380,6313,6312 ,6721,6748,6749 }, {879,4122,3397 ,1155,137,136 }, + {6859,6911,6910 ,472,474,6741 }, {255,257,1380 ,6731,6067,6721 }, + {6532,3496,6556 ,6184,3105,4273 }, {6557,6599,6598 ,6750,6596,804 }, + {6556,6557,6598 ,4273,6750,804 }, {6148,5908,6598 ,6586,4293,804 }, + {6599,6148,6598 ,6596,6586,804 }, {7453,7452,7424 ,1187,1875,1188 }, + {4568,5074,185 ,1492,6751,1493 }, {7736,2451,450 ,6752,2102,1653 }, + {3496,6557,6556 ,3105,6750,4273 }, {3733,4021,4426 ,5610,1712,1056 }, + {6690,7022,3649 ,6753,6754,6755 }, {5363,5383,6506 ,3269,3547,3549 }, + {2304,3302,2303 ,6756,6757,6758 }, {6571,5367,6523 ,4146,6034,5319 }, + {6468,6510,3496 ,6759,6760,3105 }, {3464,6468,3496 ,3151,6759,3105 }, + {6510,6558,6557 ,6760,6761,6750 }, {3496,6510,6557 ,3105,6760,6750 }, + {6558,6559,6557 ,6761,6762,6750 }, {6559,6600,6599 ,6762,6763,6596 }, + {6557,6559,6599 ,6750,6762,6596 }, {6600,6187,6599 ,6763,6531,6596 }, + {5823,7038,7073 ,661,3832,662 }, {6808,6807,6753 ,3465,6764,6765 }, + {3421,2851,3278 ,1156,3051,1158 }, {6703,6754,6701 ,5161,713,6766 }, + {6754,6789,6753 ,713,6767,6765 }, {7451,7509,7479 ,3963,1874,4423 }, + {3358,3357,3383 ,880,883,882 }, {5694,5715,5693 ,5326,4076,4075 }, + {3408,6468,3435 ,3484,6759,6703 }, {6533,6559,6558 ,6768,6762,6761 }, + {6510,6533,6558 ,6760,6768,6761 }, {6190,6219,2138 ,6610,6598,6602 }, + {6808,6859,6858 ,3465,472,6740 }, {6789,6808,6753 ,6767,3465,6765 }, + {5086,257,5069 ,6064,6067,6730 }, {7267,7291,7266 ,4331,3774,6769 }, + {5027,2272,4755 ,5516,1201,6770 }, {3936,3733,4024 ,5599,5610,5600 }, + {5693,7028,6988 ,4075,3587,6771 }, {5694,6989,6949 ,5326,4586,4585 }, + {5367,6571,5401 ,6034,4146,4145 }, {6393,6439,3408 ,1556,6772,3484 }, + {3357,6393,3408 ,883,1556,3484 }, {6439,6469,6468 ,6772,6773,6759 }, + {3408,6439,6468 ,3484,6772,6759 }, {6469,6511,6510 ,6773,6774,6760 }, + {6468,6469,6510 ,6759,6773,6760 }, {6510,6511,6533 ,6760,6774,6768 }, + {6511,6560,6559 ,6774,6775,6762 }, {6533,6511,6559 ,6768,6774,6762 }, + {6601,6600,6559 ,6776,6763,6762 }, {6560,6601,6559 ,6775,6776,6762 }, + {6601,6099,6187 ,6776,6777,6531 }, {6600,6601,6187 ,6763,6776,6531 }, + {297,6464,6783 ,1918,3633,3632 }, {6919,6918,6895 ,6778,6779,5434 }, + {2211,4988,1270 ,6780,6744,6781 }, {4988,6569,1270 ,6744,6745,6781 }, + {6948,6947,6920 ,6782,4587,6783 }, {6525,6526,6573 ,3703,6784,3733 }, + {6614,6651,6650 ,3886,6785,3833 }, {5300,6526,6525 ,3793,6784,3703 }, + {6866,6896,6842 ,6786,6787,5433 }, {6318,6319,5151 ,6788,6789,1803 }, + {7064,7048,7007 ,5181,6790,4628 }, {5415,5579,4565 ,5951,5942,5944 }, + {6393,6419,6439 ,1556,6791,6772 }, {6469,6485,6511 ,6773,6792,6774 }, + {3449,3478,3482 ,2440,885,2494 }, {6910,6911,6941 ,6741,474,6793 }, + {2271,3281,6309 ,5071,6794,3690 }, {5742,3815,3373 ,6448,5556,693 }, + {2244,6660,4570 ,6795,6601,4082 }, {5087,1117,6679 ,6796,4928,4824 }, + {2340,2303,2308 ,6797,6758,6798 }, {6389,6868,5041 ,5301,5554,6799 }, + {5559,5048,5687 ,6640,6421,6420 }, {2093,6342,2957 ,553,5265,6326 }, + {2303,3302,2308 ,6758,6757,6798 }, {6158,7086,6284 ,6112,6743,6113 }, + {7083,5991,5952 ,5483,5482,6595 }, {6318,5151,6356 ,6788,1803,3787 }, + {7453,7510,7452 ,1187,1873,1875 }, {2095,5081,1117 ,6800,1258,4928 }, + {7924,7974,7923 ,815,4171,816 }, {2237,2236,2373 ,6801,6802,6722 }, + {7087,5514,7088 ,6742,6128,4299 }, {6090,6053,6260 ,4897,4463,3397 }, + {5009,6391,6463 ,6803,6804,5159 }, {4575,7585,7640 ,3128,6805,5351 }, + {5514,7087,6158 ,6128,6742,6112 }, {5447,5564,5579 ,5633,5943,5942 }, + {5751,5752,7038 ,5878,4374,3832 }, {3359,6420,6419 ,824,6806,6791 }, + {6393,3359,6419 ,1556,824,6791 }, {6419,6420,6439 ,6791,6806,6772 }, + {6420,6470,6469 ,6806,6807,6773 }, {6439,6420,6469 ,6772,6806,6773 }, + {6470,6471,6485 ,6807,6808,6792 }, {6469,6470,6485 ,6773,6807,6792 }, + {6471,6512,6511 ,6808,6809,6774 }, {6485,6471,6511 ,6792,6808,6774 }, + {6561,6560,6511 ,6810,6775,6774 }, {6512,6561,6511 ,6809,6810,6774 }, + {6561,6180,6601 ,6810,6811,6776 }, {6560,6561,6601 ,6775,6810,6776 }, + {6601,6180,6099 ,6776,6811,6777 }, {3674,6160,6099 ,4286,6442,6777 }, + {6180,3674,6099 ,6811,4286,6777 }, {7033,7034,6152 ,6395,6045,6044 }, + {6682,5087,6679 ,6812,6796,4824 }, {184,4016,4017 ,4052,5969,5342 }, + {6942,6962,6961 ,521,3260,6813 }, {7000,7001,7024 ,6814,6815,6816 }, + {6160,6187,6099 ,6442,6531,6777 }, {7649,7648,7621 ,3950,3952,6010 }, + {5454,6682,3978 ,6817,6812,4549 }, {6711,2450,5014 ,3473,3444,6818 }, + {6554,3261,6521 ,3647,5015,6819 }, {350,5117,6115 ,1114,1113,3645 }, + {6713,6644,1215 ,6820,5098,3937 }, {6713,5056,6644 ,6820,1300,5098 }, + {3261,4190,2384 ,5015,3439,6821 }, {6844,6896,6866 ,6822,6787,6786 }, + {7105,7130,7104 ,5279,6823,5280 }, {7220,7219,7184 ,4367,1529,5074 }, + {6962,7001,7000 ,3260,6815,6814 }, {7025,7043,7042 ,6824,5895,5338 }, + {6319,5118,5125 ,6789,6825,5467 }, {3359,6394,6420 ,824,6826,6806 }, + {6420,6471,6470 ,6806,6808,6807 }, {6602,6180,6561 ,6827,6811,6810 }, + {6918,6946,6919 ,6779,594,6778 }, {1025,6657,6714 ,5299,6828,5300 }, + {2337,2405,5578 ,6829,4165,587 }, {2245,6359,6344 ,1260,6830,1450 }, + {5512,5511,5471 ,1589,1588,2051 }, {6379,6380,6394 ,6831,6832,6826 }, + {3359,6379,6394 ,824,6831,6826 }, {6380,6421,6420 ,6832,6833,6806 }, + {6394,6380,6420 ,6826,6832,6806 }, {6421,6472,6471 ,6833,6834,6808 }, + {6420,6421,6471 ,6806,6833,6808 }, {6472,6513,6512 ,6834,6835,6809 }, + {6471,6472,6512 ,6808,6834,6809 }, {6513,3600,6561 ,6835,5331,6810 }, + {6512,6513,6561 ,6809,6835,6810 }, {3623,6602,6561 ,4206,6827,6810 }, + {3600,3623,6561 ,5331,4206,6810 }, {6602,3623,6180 ,6827,4206,6811 }, + {6359,5074,4568 ,6830,6751,1492 }, {6344,6359,4568 ,1450,6830,1492 }, + {6739,6738,5437 ,6836,5057,6259 }, {6754,6753,6701 ,713,6765,6766 }, + {1990,5059,1217 ,1305,1253,6399 }, {7042,2251,7077 ,5338,3676,6837 }, + {7041,7042,7077 ,6838,5338,6837 }, {2251,5085,7077 ,3676,3675,6837 }, + {5085,4142,7077 ,3675,6839,6837 }, {6966,7006,6965 ,6840,6841,6732 }, + {2406,1217,6814 ,6736,6399,6842 }, {6421,6440,6472 ,6833,6843,6834 }, + {5527,3221,835 ,5977,183,3030 }, {2320,8049,8018 ,1406,5393,6676 }, + {6573,6574,6613 ,3733,3885,3746 }, {6342,2093,6555 ,5265,553,597 }, + {6854,6893,6906 ,3344,6844,4294 }, {6088,6069,5255 ,3670,6180,3682 }, + {6389,5041,5102 ,5301,6799,6845 }, {6688,6389,5102 ,6846,5301,6845 }, + {5054,5053,6688 ,6847,6848,6846 }, {5053,6389,6688 ,6848,5301,6846 }, + {6708,5053,5054 ,6148,6848,6847 }, {6696,6708,5054 ,6849,6148,6847 }, + {6442,6300,6708 ,6850,6146,6148 }, {2237,2308,6297 ,6801,6798,6851 }, + {6297,6300,6442 ,6851,6146,6850 }, {2308,6300,6297 ,6798,6146,6851 }, + {2409,2340,2237 ,6852,6797,6801 }, {3326,6380,6379 ,826,6832,6831 }, + {7047,7046,7005 ,6853,6735,6734 }, {7006,7047,7005 ,6841,6853,6734 }, + {2455,5001,4018 ,475,477,5084 }, {6613,6614,6650 ,3746,3886,3833 }, + {2340,2409,1075 ,6797,6852,6854 }, {2340,2308,2237 ,6797,6798,6801 }, + {4853,2340,1075 ,6747,6797,6854 }, {6959,6998,6997 ,6003,6855,4368 }, + {8019,8018,7972 ,1407,6676,6059 }, {5436,6681,5405 ,6201,5406,5518 }, + {5331,5330,5277 ,6198,3345,6237 }, {4960,306,5986 ,6856,6234,4603 }, + {4191,4296,3802 ,4474,6023,4475 }, {6737,6736,6704 ,6857,1622,5160 }, + {6681,6737,6704 ,5406,6857,5160 }, {2284,2248,7044 ,6738,6858,6859 }, + {7045,7044,7002 ,5503,6859,1047 }, {7006,7005,6965 ,6841,6734,6732 }, + {6941,6942,6961 ,6793,521,6813 }, {6966,6965,6914 ,6840,6732,3532 }, + {6141,6140,6122 ,2386,2333,1797 }, {6347,6381,6380 ,451,6860,6832 }, + {3326,6347,6380 ,826,451,6832 }, {6381,6422,6421 ,6860,6861,6833 }, + {6380,6381,6421 ,6832,6860,6833 }, {6422,6441,6440 ,6861,6862,6843 }, + {6421,6422,6440 ,6833,6861,6843 }, {6441,6473,6472 ,6862,6863,6834 }, + {6440,6441,6472 ,6843,6862,6834 }, {3550,6513,6472 ,4179,6835,6834 }, + {6473,3550,6472 ,6863,4179,6834 }, {6513,3550,3600 ,6835,4179,5331 }, + {6742,6746,4784 ,3422,6864,3423 }, {6864,6863,6839 ,6865,6866,6867 }, + {6840,6864,6839 ,6868,6865,6867 }, {7045,2284,7044 ,5503,6738,6859 }, + {4853,1075,2248 ,6747,6854,6858 }, {2284,4853,2248 ,6738,6747,6858 }, + {4746,6302,2210 ,5260,6869,2510 }, {2373,2236,2235 ,6722,6802,6870 }, + {7241,7287,7240 ,4465,3459,4312 }, {5140,1936,2094 ,3712,848,5570 }, + {7026,7045,7002 ,5504,5503,1047 }, {7026,7002,7003 ,5504,1047,1046 }, + {6915,6966,6914 ,6871,6840,3532 }, {7049,7064,7007 ,5454,5181,4628 }, + {6571,6572,6611 ,4146,3734,1391 }, {6840,6839,6792 ,6868,6867,5056 }, + {6812,6840,6792 ,6872,6868,5056 }, {6912,6913,6943 ,519,2634,520 }, + {1118,2304,2340 ,6746,6756,6797 }, {7002,7044,7043 ,1047,6859,5895 }, + {6291,6690,6027 ,6873,6753,6874 }, {6690,6687,6027 ,6753,6875,6874 }, + {455,456,1344 ,2205,301,300 }, {6351,8077,8079 ,1568,2078,55 }, + {3495,3496,6532 ,735,3105,6184 }, {6690,3649,4549 ,6753,6755,877 }, + {6687,6690,4549 ,6875,6753,877 }, {2236,6291,6027 ,6802,6873,6874 }, + {1947,2373,2235 ,6723,6722,6870 }, {2235,2236,6027 ,6870,6802,6874 }, + {3649,6661,6621 ,6755,6876,5552 }, {4549,3649,6621 ,877,6755,5552 }, + {5028,4027,1938 ,5109,3470,5934 }, {6661,5048,5629 ,6876,6421,5162 }, + {7004,7026,7003 ,5502,5504,1046 }, {206,3373,3815 ,3932,693,5556 }, + {7004,7003,6963 ,5502,1046,1048 }, {6915,6914,6862 ,6871,3532,6877 }, + {6312,6313,6747 ,6749,6748,6878 }, {7048,7047,7006 ,6790,6853,6841 }, + {6812,6792,6793 ,6872,5056,6879 }, {6794,6812,6793 ,6880,6872,6879 }, + {6911,6942,6941 ,474,521,6793 }, {6861,6913,6912 ,3533,2634,519 }, + {6860,6861,6912 ,473,3533,519 }, {6754,6808,6789 ,713,3465,6767 }, + {6687,4549,5077 ,6875,877,876 }, {6617,6616,6576 ,6881,6882,6883 }, + {6617,6655,6616 ,6881,5407,6882 }, {6655,6654,6616 ,5407,6884,6882 }, + {3556,3557,3580 ,3385,6070,6069 }, {4010,7030,4043 ,4343,6885,419 }, + {4009,4010,4043 ,3870,4343,419 }, {7030,3765,6096 ,6885,4093,4092 }, + {6685,6687,5077 ,6886,6875,876 }, {6621,6661,5629 ,5552,6876,5162 }, + {5404,6617,6576 ,3559,6881,6883 }, {6964,7004,6963 ,6733,5502,1048 }, + {6894,6915,6862 ,6887,6871,6877 }, {6894,6862,6863 ,6887,6877,6866 }, + {6348,6382,6381 ,1607,6888,6860 }, {6347,6348,6381 ,451,1607,6860 }, + {6382,6423,6422 ,6888,6889,6861 }, {6381,6382,6422 ,6860,6888,6861 }, + {6423,3465,6441 ,6889,6890,6862 }, {6422,6423,6441 ,6861,6889,6862 }, + {3465,3500,6473 ,6890,4177,6863 }, {6441,3465,6473 ,6862,6890,6863 }, + {6473,3500,3550 ,6863,4177,4179 }, {6794,6793,6755 ,6880,6879,5055 }, + {6756,6794,6755 ,6891,6880,5055 }, {6913,6944,6943 ,2634,2633,520 }, + {6480,5350,6507 ,1524,1526,2680 }, {7007,7048,7006 ,4628,6790,6841 }, + {5930,6125,6143 ,6593,4395,4394 }, {6140,6121,6101 ,2333,6575,6574 }, + {6893,6856,6906 ,6844,6892,4294 }, {4981,4950,1216 ,1350,1302,6893 }, + {5011,1264,4746 ,6657,5022,5260 }, {2279,2248,1313 ,5337,6858,3674 }, + {6964,6963,6943 ,6733,1048,520 }, {6864,6894,6863 ,6865,6887,6866 }, + {6741,6740,6705 ,5388,6894,4158 }, {6310,6746,6742 ,3918,6864,3422 }, + {5907,5246,3860 ,6116,5176,6523 }, {6382,3465,6423 ,6888,6890,6889 }, + {7102,7101,7088 ,1240,4073,4299 }, {4567,6155,6156 ,6655,3726,6250 }, + {3399,5143,5441 ,715,6099,5194 }, {6756,6755,6740 ,6891,5055,6894 }, + {3973,5446,5388 ,6007,5985,5428 }, {2235,6027,2139 ,6870,6874,6895 }, + {6737,6790,6736 ,6857,711,1622 }, {6619,6554,6521 ,1272,3647,6819 }, + {7076,7075,7039 ,4886,4420,4376 }, {1026,4981,1216 ,6218,1350,6893 }, + {1072,1026,1216 ,6725,6218,6893 }, {6172,4010,5124 ,5451,4343,6567 }, + {5686,5890,5747 ,5777,6591,6036 }, {4261,3777,4290 ,1583,1582,6270 }, + {6750,6751,6786 ,631,5242,4139 }, {6961,7000,6960 ,6813,6814,6896 }, + {7000,7042,7041 ,6814,5338,6838 }, {6999,7000,7041 ,6897,6814,6838 }, + {6909,6910,6960 ,6898,6741,6896 }, {7000,6999,6960 ,6814,6897,6896 }, + {6858,6910,6857 ,6740,6741,6899 }, {6910,6961,6960 ,6741,6813,6896 }, + {6944,6964,6943 ,2633,6733,520 }, {6738,6791,6737 ,5057,6900,6857 }, + {6348,6357,6382 ,1607,5271,6888 }, {6383,3437,3465 ,6901,4142,6890 }, + {6382,6383,3465 ,6888,6901,6890 }, {5694,7009,5716 ,5326,5325,6075 }, + {351,5069,255 ,592,6730,6731 }, {3418,1554,248 ,6366,6304,4114 }, + {5491,5479,5516 ,5419,5387,5420 }, {6741,6756,6740 ,5388,6891,6894 }, + {2221,2291,5114 ,6902,1115,5689 }, {1264,5011,1252 ,5022,6657,5023 }, + {7025,7002,7043 ,6824,1047,5895 }, {6862,6914,6861 ,6877,3532,3533 }, + {6998,7040,7039 ,6855,6903,4376 }, {7040,7076,7039 ,6903,4886,4376 }, + {6910,6909,6857 ,6741,6898,6899 }, {6838,6858,6806 ,6904,6740,6905 }, + {6807,6838,6806 ,6764,6904,6905 }, {6858,6857,6806 ,6740,6899,6905 }, + {3435,3464,3434 ,6703,3151,737 }, {4780,4819,3139 ,5150,5216,5400 }, + {6532,6556,3547 ,6184,4273,803 }, {6146,3970,3926 ,6568,3225,3125 }, + {6899,6146,3926 ,4005,6568,3125 }, {3787,6022,6159 ,5819,6535,5082 }, + {5533,5628,3765 ,6239,6560,4093 }, {7030,5533,3765 ,6885,6239,4093 }, + {4835,4021,3936 ,2130,1712,5599 }, {6753,6752,6700 ,6765,6906,6907 }, + {4547,5058,2163 ,242,5647,243 }, {6071,5590,6354 ,1346,6386,6597 }, + {7675,7697,7719 ,5192,651,6541 }, {6755,6738,6739 ,5055,5057,6836 }, + {6358,6383,6382 ,5270,6901,6888 }, {6357,6358,6382 ,5271,5270,6888 }, + {5628,5533,3609 ,6560,6239,5081 }, {6740,6755,6739 ,6894,5055,6836 }, + {1882,4547,4543 ,894,242,3002 }, {6810,6861,6860 ,6908,3533,473 }, + {6809,6810,6860 ,712,6908,473 }, {6807,6806,6752 ,6764,6905,6906 }, + {3464,3435,6468 ,3151,6703,6759 }, {4043,4007,4009 ,419,3909,3870 }, + {4987,6464,5966 ,1491,3633,5054 }, {5059,3177,4233 ,1253,1255,6185 }, + {4950,5005,4115 ,1302,1301,6909 }, {6862,6861,6810 ,6877,3533,6908 }, + {6701,6753,6700 ,6766,6765,6907 }, {6302,5078,1074 ,6869,5261,2715 }, + {6618,6656,6617 ,6910,5517,6881 }, {6791,6790,6737 ,6900,711,6857 }, + {7744,7795,7794 ,6101,3804,5749 }, {5437,6738,6681 ,6259,5057,5406 }, + {6840,6894,6864 ,6868,6887,6865 }, {6740,6739,5437 ,6894,6836,6259 }, + {342,209,4892 ,4542,4499,5386 }, {6844,6843,6795 ,6822,6911,5418 }, + {5693,6988,6947 ,4075,6771,4587 }, {6810,6809,6790 ,6908,712,711 }, + {6864,6915,6894 ,6865,6871,6887 }, {7620,7647,7675 ,5033,4967,5192 }, + {5950,5013,8108 ,2873,684,2287 }, {6753,6807,6752 ,6765,6764,6906 }, + {7769,7794,7768 ,5535,5749,5536 }, {1170,5627,3471 ,4103,6628,4104 }, + {6847,6900,6899 ,4004,6912,4005 }, {4043,7030,6096 ,419,6885,4092 }, + {3408,3382,3383 ,3484,870,882 }, {6752,6806,6788 ,6906,6905,6913 }, + {6701,6700,6652 ,6766,6907,6914 }, {6653,6701,6652 ,6915,6766,6914 }, + {6656,6655,6617 ,5517,5407,6881 }, {6738,6737,6681 ,5057,6857,5406 }, + {1076,4617,7075 ,4887,4431,4420 }, {5477,5442,5315 ,5399,5174,6445 }, + {6843,6842,6813 ,6911,5433,6916 }, {6350,3362,6358 ,704,1200,5270 }, + {6349,6350,6358 ,705,704,5270 }, {3362,6384,6383 ,1200,1786,6901 }, + {6358,3362,6383 ,5270,1200,6901 }, {6384,3387,6383 ,1786,1787,6901 }, + {6383,3387,3437 ,6901,1787,4142 }, {6791,6810,6790 ,6900,6908,711 }, + {5910,6079,6354 ,5121,5580,6597 }, {6987,7007,6966 ,6917,4628,6840 }, + {6844,6795,5551 ,6822,5418,6260 }, {3814,6799,3855 ,5511,6918,1511 }, + {6799,6761,3855 ,6918,6919,1511 }, {6761,6848,6847 ,6919,6920,4004 }, + {3855,6761,6847 ,1511,6919,4004 }, {6848,6901,6900 ,6920,6921,6912 }, + {6847,6848,6900 ,4004,6920,6912 }, {6901,6899,6900 ,6921,4005,6912 }, + {6147,6146,6899 ,5452,6568,4005 }, {6901,6147,6899 ,6921,5452,4005 }, + {6744,5188,3350 ,3986,4812,2541 }, {7900,7926,7899 ,6922,6923,4116 }, + {3277,8098,2901 ,961,907,3468 }, {6144,4642,5628 ,5083,6226,6560 }, + {2160,5474,2154 ,6621,4243,5321 }, {6616,6653,6615 ,6882,6915,6924 }, + {6297,6696,6694 ,6851,6849,6925 }, {6534,6578,3656 ,4257,6926,4195 }, + {973,6465,2141 ,2017,6927,2015 }, {5593,6920,6896 ,5935,6783,6787 }, + {6795,6843,6813 ,5418,6911,6916 }, {3504,3441,3505 ,2986,2987,6928 }, + {3556,3521,3504 ,3385,4307,2986 }, {1285,1226,1320 ,1944,2354,2356 }, + {3605,3626,3604 ,3383,4196,3384 }, {6572,6612,6611 ,3734,1389,1391 }, + {6125,5930,5979 ,4395,6593,6028 }, {5404,5371,5405 ,3559,3558,5518 }, + {6715,6761,6799 ,6929,6919,6918 }, {3814,6715,6799 ,5511,6929,6918 }, + {1936,2341,2094 ,848,845,5570 }, {1590,1552,1656 ,3028,2266,1166 }, + {3605,3556,3580 ,3383,3385,6069 }, {6159,4642,6144 ,5082,6226,5083 }, + {5087,5077,2095 ,6796,876,6800 }, {6466,6758,973 ,6930,6931,2017 }, + {6909,6908,6857 ,6898,6932,6899 }, {5437,6681,5436 ,6259,5406,6201 }, + {2957,6342,1120 ,6326,5265,5339 }, {6564,6508,6639 ,6933,6934,3699 }, + {3320,6564,6639 ,5455,6933,3699 }, {3192,4784,6387 ,3063,3423,6935 }, + {3656,6578,3714 ,4195,6926,3970 }, {7148,7147,7120 ,6936,6937,6938 }, + {5405,6618,5404 ,5518,6910,3559 }, {5473,7096,5120 ,3429,4337,5857 }, + {6022,4642,6159 ,6535,6226,5082 }, {7286,7285,7261 ,3460,2751,1528 }, + {2139,6685,6682 ,6895,6886,6812 }, {3714,3655,3656 ,3970,3936,4195 }, + {1809,6605,5081 ,878,5164,1258 }, {6682,6685,5087 ,6812,6886,6796 }, + {2095,1809,5081 ,6800,878,1258 }, {5077,1809,2095 ,876,878,6800 }, + {6685,5077,5087 ,6886,876,6796 }, {5359,3815,5977 ,3713,5556,809 }, + {1074,6466,973 ,2715,6930,2017 }, {6466,6465,6758 ,6930,6927,6931 }, + {6653,6652,6615 ,6915,6914,6924 }, {6576,6616,6575 ,6883,6882,6939 }, + {6354,6079,6377 ,6597,5580,1347 }, {1562,5130,4392 ,4649,5571,844 }, + {4856,2244,4570 ,4081,6795,4082 }, {3288,3328,3362 ,2058,3973,1200 }, + {6321,3288,3362 ,706,2058,1200 }, {5441,297,42 ,5194,1918,5195 }, + {6001,6156,5917 ,6221,6250,6402 }, {6443,6486,3557 ,6940,6941,6070 }, + {3504,6443,3557 ,2986,6940,6070 }, {6486,6534,3626 ,6941,4257,4196 }, + {3557,6486,3626 ,6070,6941,4196 }, {5747,5890,6214 ,6036,6591,6942 }, + {6578,6092,3714 ,6926,1623,3970 }, {5742,5977,3815 ,6448,809,5556 }, + {6302,1074,2210 ,6869,2715,2510 }, {2342,5008,2221 ,5690,6118,6902 }, + {6640,5435,6611 ,4224,4223,1391 }, {6176,6716,6715 ,1624,6943,6929 }, + {7232,7231,7196 ,2124,6944,2401 }, {6716,6762,6761 ,6943,6945,6919 }, + {6715,6716,6761 ,6929,6943,6919 }, {6762,6815,6761 ,6945,6946,6919 }, + {6761,6815,6848 ,6919,6946,6920 }, {6902,6901,6848 ,6947,6921,6920 }, + {6815,6902,6848 ,6946,6947,6920 }, {6952,6147,6901 ,6948,5452,6921 }, + {6902,6952,6901 ,6947,6948,6921 }, {6952,6990,5920 ,6948,6739,5453 }, + {6147,6952,5920 ,5452,6948,5453 }, {7001,7002,7025 ,6815,1047,6824 }, + {6652,6700,6699 ,6914,6907,630 }, {2234,2139,5454 ,6949,6895,6817 }, + {2241,2234,5454 ,6950,6949,6817 }, {5454,2139,6682 ,6817,6895,6812 }, + {5300,5327,6041 ,3793,3702,3801 }, {4207,1444,1745 ,4247,4035,6233 }, + {5498,1742,1218 ,5590,707,3255 }, {6616,6615,6575 ,6882,6924,6939 }, + {6844,6866,6843 ,6822,6786,6911 }, {1380,3284,6313 ,6721,6720,6748 }, + {5593,6896,6867 ,5935,6787,6951 }, {6896,6919,6895 ,6787,6778,5434 }, + {5024,2138,6660 ,6952,6602,6601 }, {5087,2095,1117 ,6796,6800,4928 }, + {6486,6487,6534 ,6941,6953,4257 }, {6717,6763,6762 ,6954,6955,6945 }, + {6716,6717,6762 ,6943,6954,6945 }, {6763,6816,6815 ,6955,6956,6946 }, + {6762,6763,6815 ,6945,6955,6946 }, {6903,6902,6815 ,6957,6947,6946 }, + {6816,6903,6815 ,6956,6957,6946 }, {3393,3365,3394 ,4441,4240,6958 }, + {5111,5110,7166 ,5990,5953,5971 }, {6805,6855,6854 ,3343,6959,3344 }, + {6391,4550,6659 ,6804,716,3577 }, {6642,6652,6651 ,6960,6914,6785 }, + {6958,6959,6997 ,4314,6003,4368 }, {6575,6615,6574 ,6939,6924,3885 }, + {6651,6652,6699 ,6785,6914,630 }, {6752,6751,6699 ,6906,5242,630 }, + {6641,6642,6651 ,6961,6960,6785 }, {2260,6021,5494 ,6571,6551,6569 }, + {7239,7286,7261 ,1527,3460,1528 }, {6466,6315,6465 ,6930,6962,6927 }, + {6315,6392,306 ,6962,6963,6234 }, {6528,6576,6527 ,6964,6883,6965 }, + {5086,351,2246 ,6064,592,591 }, {6313,4445,6747 ,6748,3648,6878 }, + {2336,6521,5496 ,1261,6819,6966 }, {6228,1664,253 ,1366,2268,2564 }, + {7050,7067,7028 ,6967,3585,3587 }, {6443,3441,3442 ,6940,2987,4460 }, + {6708,6710,5053 ,6148,6968,6848 }, {6443,6487,6486 ,6940,6953,6941 }, + {6565,6579,6578 ,6969,6970,6926 }, {6534,6565,6578 ,4257,6969,6926 }, + {6579,6092,6578 ,6970,1623,6926 }, {6691,6717,6716 ,6971,6954,6943 }, + {6176,6691,6716 ,1624,6971,6943 }, {6903,6953,6952 ,6957,6972,6948 }, + {6902,6903,6952 ,6947,6957,6948 }, {6991,6990,6952 ,6973,6739,6948 }, + {6953,6991,6952 ,6972,6973,6948 }, {6991,7032,5918 ,6973,6974,5820 }, + {6990,6991,5918 ,6739,6973,5820 }, {1275,6108,1221 ,1688,6975,1759 }, + {6642,6641,6614 ,6960,6961,3886 }, {6615,6642,6614 ,6924,6960,3886 }, + {6615,6614,6574 ,6924,3886,3885 }, {3311,5192,3762 ,5303,6454,5172 }, + {6999,6998,6959 ,6897,6855,6003 }, {6315,306,4960 ,6962,6234,6856 }, + {6909,6960,6908 ,6898,6896,6932 }, {6576,6575,6527 ,6883,6939,6965 }, + {5104,5086,2246 ,6063,6064,591 }, {3284,4445,6313 ,6720,3648,6748 }, + {5438,6705,5437 ,4156,4158,6259 }, {5251,5210,6433 ,3802,6033,6691 }, + {6444,6488,6487 ,6976,6977,6953 }, {6443,6444,6487 ,6940,6976,6953 }, + {6488,6535,6534 ,6977,6978,4257 }, {6487,6488,6534 ,6953,6977,4257 }, + {6534,6535,6565 ,4257,6978,6969 }, {6535,6580,6579 ,6978,6979,6970 }, + {6565,6535,6579 ,6969,6978,6970 }, {6622,6092,6579 ,6980,1623,6970 }, + {6580,6622,6579 ,6979,6980,6970 }, {6622,6662,6176 ,6980,6981,1624 }, + {6092,6622,6176 ,1623,6980,1624 }, {6176,6662,6691 ,1624,6981,6971 }, + {6662,6718,6717 ,6981,6982,6954 }, {6691,6662,6717 ,6971,6981,6954 }, + {6718,6764,6763 ,6982,6983,6955 }, {6717,6718,6763 ,6954,6982,6955 }, + {6817,6816,6763 ,6984,6956,6955 }, {6764,6817,6763 ,6983,6984,6955 }, + {6871,6903,6816 ,6985,6957,6956 }, {6817,6871,6816 ,6984,6985,6956 }, + {6871,6954,6953 ,6985,6986,6972 }, {6903,6871,6953 ,6957,6985,6972 }, + {6992,6991,6953 ,6987,6973,6972 }, {6954,6992,6953 ,6986,6987,6972 }, + {6992,5945,7032 ,6987,6396,6974 }, {6991,6992,7032 ,6973,6987,6974 }, + {5945,5918,7032 ,6396,5820,6974 }, {2808,1987,1920 ,522,3614,581 }, + {6315,5009,6465 ,6962,6803,6927 }, {3425,1724,8103 ,1859,3880,1291 }, + {6960,6999,6959 ,6896,6897,6003 }, {7974,7973,7923 ,4171,3624,816 }, + {3359,3326,6379 ,824,826,6831 }, {6071,6354,6377 ,1346,6597,1347 }, + {5633,6949,5611 ,6179,4585,6177 }, {405,3499,5628 ,5662,5661,6560 }, + {6661,6688,5048 ,6876,6846,6421 }, {7027,7007,7008 ,3175,4628,3176 }, + {2406,1990,6530 ,6736,1305,6398 }, {3442,6444,6443 ,4460,6976,6940 }, + {6706,7814,5479 ,5389,6988,5387 }, {7092,7104,7091 ,3968,5280,4153 }, + {1879,5049,4744 ,1283,5038,4424 }, {6856,6907,6906 ,6892,6002,4294 }, + {7041,7040,6998 ,6838,6903,6855 }, {1394,2074,1027 ,6989,6990,6991 }, + {6793,6792,6755 ,6879,5056,5055 }, {2304,2303,2340 ,6756,6758,6797 }, + {3609,6990,5918 ,5081,6739,5820 }, {6345,4595,5118 ,191,5289,6825 }, + {6444,6474,6488 ,6976,6992,6977 }, {6488,6514,6535 ,6977,6993,6978 }, + {6999,7041,6998 ,6897,6838,6855 }, {6960,6959,6907 ,6896,6003,6002 }, + {4900,2274,4545 ,585,893,586 }, {6614,6641,6651 ,3886,6961,6785 }, + {6793,6811,6792 ,6879,399,5056 }, {7566,7565,7532 ,6009,5080,6705 }, + {3394,6395,3442 ,6958,6994,4460 }, {3393,3394,3442 ,4441,6958,4460 }, + {6395,6445,6444 ,6994,6995,6976 }, {3442,6395,6444 ,4460,6994,6976 }, + {6445,6446,6474 ,6995,6996,6992 }, {6444,6445,6474 ,6976,6995,6992 }, + {6446,6489,6488 ,6996,6997,6977 }, {6474,6446,6488 ,6992,6996,6977 }, + {6489,6490,6514 ,6997,6998,6993 }, {6488,6489,6514 ,6977,6997,6993 }, + {6490,6536,6535 ,6998,6999,6978 }, {6514,6490,6535 ,6993,6998,6978 }, + {6536,6581,6580 ,6999,7000,6979 }, {6535,6536,6580 ,6978,6999,6979 }, + {6623,6622,6580 ,7001,6980,6979 }, {6581,6623,6580 ,7000,7001,6979 }, + {6623,6663,6662 ,7001,7002,6981 }, {6622,6623,6662 ,6980,7001,6981 }, + {6663,6719,6718 ,7002,7003,6982 }, {6662,6663,6718 ,6981,7002,6982 }, + {6719,6765,6764 ,7003,7004,6983 }, {6718,6719,6764 ,6982,7003,6983 }, + {6765,6766,6764 ,7004,7005,6983 }, {6766,6818,6817 ,7005,7006,6984 }, + {6764,6766,6817 ,6983,7005,6984 }, {6818,6849,6817 ,7006,7007,6984 }, + {6872,6871,6817 ,7008,6985,6984 }, {6849,6872,6817 ,7007,7008,6984 }, + {6872,6955,6954 ,7008,7009,6986 }, {6871,6872,6954 ,6985,7008,6986 }, + {6955,6993,6992 ,7009,7010,6987 }, {6954,6955,6992 ,6986,7009,6987 }, + {6993,7033,5945 ,7010,6395,6396 }, {6992,6993,5945 ,6987,7010,6396 }, + {3970,5124,3969 ,3225,6567,3226 }, {4845,5187,5070 ,3921,5144,6043 }, + {5878,3835,3762 ,2684,6046,5172 }, {4672,5878,3762 ,2682,2684,5172 }, + {6908,6960,6907 ,6932,6896,6002 }, {7041,7077,7076 ,6838,6837,4886 }, + {7048,2074,7047 ,6790,6990,6853 }, {2222,4746,2210 ,2509,5260,2510 }, + {6479,6528,6478 ,2659,6964,7011 }, {6963,7002,7001 ,1048,1047,6815 }, + {248,4530,3350 ,4114,1376,2541 }, {4489,4068,3778 ,5584,408,5585 }, + {6301,6710,6708 ,6147,6968,6148 }, {6811,6839,6792 ,399,6867,5056 }, + {5926,5940,6715 ,6419,1625,6929 }, {5081,2208,1117 ,1258,1257,4928 }, + {6014,5590,6259 ,6592,6386,6718 }, {3394,6396,6395 ,6958,7012,6994 }, + {6396,6446,6445 ,7012,6996,6995 }, {6395,6396,6445 ,6994,7012,6995 }, + {6663,6720,6719 ,7002,7013,7003 }, {6720,6766,6765 ,7013,7005,7004 }, + {6719,6720,6765 ,7003,7013,7004 }, {6873,6872,6849 ,7014,7008,7007 }, + {6818,6873,6849 ,7006,7014,7007 }, {6955,6922,6993 ,7009,7015,7010 }, + {6855,6856,6805 ,6959,6892,3343 }, {3291,3293,3292 ,1203,1988,4067 }, + {4704,4763,5217 ,4788,4787,4026 }, {1987,1220,1920 ,3614,4885,581 }, + {7040,7041,7076 ,6903,6838,4886 }, {6392,6315,6466 ,6963,6962,6930 }, + {6528,6527,6478 ,6964,6965,7011 }, {6388,6436,6435 ,3184,3681,2658 }, + {2138,3704,6190 ,6602,2332,6610 }, {5315,5442,5044 ,6445,5174,5173 }, + {3394,6385,6396 ,6958,7016,7012 }, {6446,6490,6489 ,6996,6998,6997 }, + {6663,6692,6720 ,7002,7017,7013 }, {6873,6922,6872 ,7014,7015,7008 }, + {6872,6922,6955 ,7008,7015,7009 }, {6994,6993,6922 ,7018,7010,7015 }, + {6994,7034,7033 ,7018,6045,6395 }, {6993,6994,7033 ,7010,7018,6395 }, + {4672,5477,6036 ,2682,5399,6141 }, {5744,5716,7009 ,6090,6075,5325 }, + {685,2629,794 ,2674,2673,3392 }, {2336,6619,6521 ,1261,1272,6819 }, + {6619,1265,4190 ,1272,1273,3439 }, {6908,6907,6856 ,6932,6002,6892 }, + {5005,2336,5496 ,1301,1261,6966 }, {6857,6908,6856 ,6899,6932,6892 }, + {3600,3550,3578 ,5331,4179,4205 }, {6951,2211,6870 ,2615,6780,2616 }, + {1264,403,5078 ,5022,5547,5261 }, {2409,2237,2373 ,6852,6801,6722 }, + {5075,1215,184 ,1586,3937,4052 }, {7068,6166,7034 ,7019,4971,6045 }, + {6324,6360,3394 ,4239,7020,6958 }, {3365,6324,3394 ,4240,4239,6958 }, + {6360,6361,6385 ,7020,7021,7016 }, {3394,6360,6385 ,6958,7020,7016 }, + {6361,6397,6396 ,7021,7022,7012 }, {6385,6361,6396 ,7016,7021,7012 }, + {6397,6447,6446 ,7022,7023,6996 }, {6396,6397,6446 ,7012,7022,6996 }, + {6447,6475,6446 ,7023,7024,6996 }, {6475,6491,6490 ,7024,7025,6998 }, + {6446,6475,6490 ,6996,7024,6998 }, {6491,6537,6536 ,7025,7026,6999 }, + {6490,6491,6536 ,6998,7025,6999 }, {6537,6582,6581 ,7026,7027,7000 }, + {6536,6537,6581 ,6999,7026,7000 }, {6624,6623,6581 ,7028,7001,7000 }, + {6582,6624,6581 ,7027,7028,7000 }, {6624,6664,6663 ,7028,7029,7002 }, + {6623,6624,6663 ,7001,7028,7002 }, {6663,6664,6692 ,7002,7029,7017 }, + {6664,6721,6720 ,7029,7030,7013 }, {6692,6664,6720 ,7017,7029,7013 }, + {6721,6767,6766 ,7030,7031,7005 }, {6720,6721,6766 ,7013,7030,7005 }, + {6767,6819,6818 ,7031,7032,7006 }, {6766,6767,6818 ,7005,7031,7006 }, + {6874,6873,6818 ,7033,7014,7006 }, {6819,6874,6818 ,7032,7033,7006 }, + {6874,6923,6922 ,7033,7034,7015 }, {6873,6874,6922 ,7014,7033,7015 }, + {6971,6994,6922 ,7035,7018,7015 }, {6923,6971,6922 ,7034,7035,7015 }, + {6971,7035,7034 ,7035,7036,6045 }, {6994,6971,7034 ,7018,7035,6045 }, + {3311,5866,5192 ,5303,5302,6454 }, {7035,7068,7034 ,7036,7019,6045 }, + {2357,2798,4043 ,3933,420,419 }, {8019,2320,8018 ,1407,1406,6676 }, + {1204,1205,4499 ,338,3187,4510 }, {5076,5904,4200 ,6607,5851,6583 }, + {6687,6685,2139 ,6875,6886,6895 }, {6027,6687,2139 ,6874,6875,6895 }, + {2374,2455,4018 ,7037,475,5084 }, {1551,4921,2093 ,5275,2079,553 }, + {3952,4068,4489 ,5597,408,5584 }, {6036,5892,4672 ,6141,2683,2682 }, + {535,6153,5105 ,2001,5514,5456 }, {2455,2374,1027 ,475,7037,6991 }, + {5274,5251,6433 ,6145,3802,6691 }, {6805,6856,6855 ,3343,6892,6959 }, + {6434,6479,6478 ,3127,2659,7011 }, {6433,6434,6478 ,6691,3127,7011 }, + {7161,7186,7169 ,2559,5370,5437 }, {912,2209,2240 ,4471,7038,4478 }, + {4445,6748,6747 ,3648,3408,6878 }, {5647,5688,6890 ,5333,5519,5334 }, + {7533,7509,7510 ,7039,1874,1873 }, {6836,5353,8111 ,852,851,4359 }, + {2211,2222,6870 ,6780,2509,2616 }, {4017,5075,184 ,5342,1586,4052 }, + {6324,6361,6360 ,4239,7021,7020 }, {2290,7936,2292 ,1982,5528,1983 }, + {5394,3765,5628 ,5660,4093,6560 }, {2074,1071,1027 ,6990,3382,6991 }, + {2248,2279,7043 ,6858,5337,5895 }, {6997,6998,7039 ,4368,6855,4376 }, + {6758,6465,973 ,6931,6927,2017 }, {2205,6563,6898 ,2874,464,1946 }, + {6690,6694,7022 ,6753,6925,6754 }, {6696,5054,3649 ,6849,6847,6755 }, + {6173,6071,6088 ,3711,1346,3670 }, {7972,8018,7994 ,6059,6676,5582 }, + {2211,1270,2222 ,6780,6781,2509 }, {1749,6713,1215 ,555,6820,3937 }, + {6566,6583,6582 ,7040,7041,7027 }, {6537,6566,6582 ,7026,7040,7027 }, + {6583,6624,6582 ,7041,7028,7027 }, {4550,4960,5986 ,716,6856,4603 }, + {5009,6315,6391 ,6803,6962,6804 }, {6315,4960,6391 ,6962,6856,6804 }, + {6391,4960,4550 ,6804,6856,716 }, {3217,6030,5747 ,6117,4037,6036 }, + {5006,4981,1026 ,1351,1350,6218 }, {5503,5463,5504 ,7042,5801,5781 }, + {6569,5011,4746 ,6745,6657,5260 }, {3936,3820,4835 ,5599,5591,2130 }, + {6148,6599,6161 ,6586,6596,6530 }, {5191,6434,6433 ,2900,3127,6691 }, + {6291,6297,6690 ,6873,6851,6753 }, {6694,6696,7022 ,6925,6849,6754 }, + {7795,7846,7794 ,3804,4162,5749 }, {5075,1749,1215 ,1586,555,3937 }, + {6116,6325,6324 ,4170,7043,4239 }, {3336,6116,6324 ,3922,4170,4239 }, + {6325,6362,6361 ,7043,7044,7021 }, {6324,6325,6361 ,4239,7043,7021 }, + {6362,6398,6397 ,7044,7045,7022 }, {6361,6362,6397 ,7021,7044,7022 }, + {6398,6448,6447 ,7045,7046,7023 }, {6397,6398,6447 ,7022,7045,7023 }, + {6447,6448,6475 ,7023,7046,7024 }, {6448,6492,6491 ,7046,7047,7025 }, + {6475,6448,6491 ,7024,7046,7025 }, {6492,6538,6537 ,7047,7048,7026 }, + {6491,6492,6537 ,7025,7047,7026 }, {6537,6538,6566 ,7026,7048,7040 }, + {6538,6584,6583 ,7048,7049,7041 }, {6566,6538,6583 ,7040,7048,7041 }, + {6625,6624,6583 ,7050,7028,7041 }, {6584,6625,6583 ,7049,7050,7041 }, + {6625,6665,6664 ,7050,7051,7029 }, {6624,6625,6664 ,7028,7050,7029 }, + {6665,6722,6721 ,7051,7052,7030 }, {6664,6665,6721 ,7029,7051,7030 }, + {6722,6768,6767 ,7052,7053,7031 }, {6721,6722,6767 ,7030,7052,7031 }, + {6768,6820,6819 ,7053,7054,7032 }, {6767,6768,6819 ,7031,7053,7032 }, + {6875,6874,6819 ,7055,7033,7032 }, {6820,6875,6819 ,7054,7055,7032 }, + {6875,6924,6923 ,7055,7056,7034 }, {6874,6875,6923 ,7033,7055,7034 }, + {6924,6972,6971 ,7056,7057,7035 }, {6923,6924,6971 ,7034,7056,7035 }, + {7036,7035,6971 ,7058,7036,7035 }, {6972,7036,6971 ,7057,7058,7035 }, + {7036,7069,7068 ,7058,7059,7019 }, {7035,7036,7068 ,7036,7058,7019 }, + {4901,3892,7068 ,3596,4970,7019 }, {7069,4901,7068 ,7059,3596,7019 }, + {6527,6575,6526 ,6965,6939,6784 }, {6575,6574,6526 ,6939,3885,6784 }, + {6478,6527,5300 ,7011,6965,3793 }, {6527,6526,5300 ,6965,6784,3793 }, + {6433,6478,5300 ,6691,7011,3793 }, {5274,6433,5300 ,6145,6691,3793 }, + {1269,1125,2159 ,5724,5723,6440 }, {187,1269,2159 ,6439,5724,6440 }, + {4391,4422,4443 ,992,5561,703 }, {6702,6701,6653 ,7060,6766,6915 }, + {6654,6702,6653 ,6884,7060,6915 }, {6297,6694,6690 ,6851,6925,6753 }, + {6465,4985,2141 ,6927,2703,2015 }, {3156,1658,1657 ,3164,1782,1781 }, + {2257,5056,6713 ,554,1300,6820 }, {949,4994,5029 ,2133,5076,2159 }, + {6788,6787,6751 ,6913,5269,5242 }, {6700,6752,6699 ,6907,6906,630 }, + {6806,6805,6787 ,6905,3343,5269 }, {6752,6788,6751 ,6906,6913,5242 }, + {6788,6806,6787 ,6913,6905,5269 }, {6857,6855,6805 ,6899,6959,3343 }, + {6857,6856,6855 ,6899,6892,6959 }, {6806,6857,6805 ,6905,6899,3343 }, + {3454,5089,7052 ,710,3442,708 }, {3926,3891,6899 ,3125,3820,4005 }, + {4978,4567,5592 ,178,6655,3671 }, {2236,2237,6291 ,6802,6801,6873 }, + {5590,6071,6173 ,6386,1346,3711 }, {6918,6917,6895 ,6779,5515,5434 }, + {1749,2257,6713 ,555,554,6820 }, {4979,3319,5056 ,5363,1259,1300 }, + {6492,6515,6538 ,7047,7061,7048 }, {6768,6821,6820 ,7053,7062,7054 }, + {6821,6876,6875 ,7062,7063,7055 }, {6820,6821,6875 ,7054,7062,7055 }, + {6876,6925,6924 ,7063,7064,7056 }, {6875,6876,6924 ,7055,7063,7056 }, + {6973,6972,6924 ,7065,7057,7056 }, {6925,6973,6924 ,7064,7065,7056 }, + {6973,7036,6972 ,7065,7058,7057 }, {7030,4010,5533 ,6885,4343,6239 }, + {1881,2234,2241 ,7066,6949,6950 }, {2209,1881,2241 ,7038,7066,6950 }, + {1881,2209,1076 ,7066,7038,4887 }, {4142,1881,1076 ,6839,7066,4887 }, + {7077,4142,1076 ,6837,6839,4887 }, {5009,6463,4985 ,6803,5159,2703 }, + {7076,7077,1076 ,4886,6837,4887 }, {5926,6715,3814 ,6419,6929,5511 }, + {6108,1172,1221 ,6975,1758,1759 }, {7436,7412,7386 ,4245,3765,5512 }, + {3441,6443,3505 ,2987,6940,6928 }, {5370,5474,2160 ,2171,4243,6621 }, + {2257,4979,5056 ,554,5363,1300 }, {6286,6326,6325 ,4349,7067,7043 }, + {6116,6286,6325 ,4170,4349,7043 }, {6326,6363,6362 ,7067,7068,7044 }, + {6325,6326,6362 ,7043,7067,7044 }, {6363,6399,6398 ,7068,7069,7045 }, + {6362,6363,6398 ,7044,7068,7045 }, {6399,6449,6448 ,7069,7070,7046 }, + {6398,6399,6448 ,7045,7069,7046 }, {6449,6493,6492 ,7070,7071,7047 }, + {6448,6449,6492 ,7046,7070,7047 }, {6493,6516,6515 ,7071,7072,7061 }, + {6492,6493,6515 ,7047,7071,7061 }, {6516,6539,6538 ,7072,7073,7048 }, + {6515,6516,6538 ,7061,7072,7048 }, {6539,6585,6584 ,7073,7074,7049 }, + {6538,6539,6584 ,7048,7073,7049 }, {6626,6625,6584 ,7075,7050,7049 }, + {6585,6626,6584 ,7074,7075,7049 }, {6626,6666,6665 ,7075,7076,7051 }, + {6625,6626,6665 ,7050,7075,7051 }, {6666,6723,6722 ,7076,7077,7052 }, + {6665,6666,6722 ,7051,7076,7052 }, {6769,6768,6722 ,7078,7053,7052 }, + {6723,6769,6722 ,7077,7078,7052 }, {6769,6822,6821 ,7078,7079,7062 }, + {6768,6769,6821 ,7053,7078,7062 }, {6822,6877,6876 ,7079,7080,7063 }, + {6821,6822,6876 ,7062,7079,7063 }, {6877,6926,6925 ,7080,7081,7064 }, + {6876,6877,6925 ,7063,7080,7064 }, {6974,6973,6925 ,7082,7065,7064 }, + {6926,6974,6925 ,7081,7082,7064 }, {6974,7010,6973 ,7082,7083,7065 }, + {7010,7036,6973 ,7083,7058,7065 }, {7010,7070,7069 ,7083,7084,7059 }, + {7036,7010,7069 ,7058,7083,7059 }, {3813,5926,3814 ,1515,6419,5511 }, + {7972,7994,7948 ,6059,5582,3625 }, {4082,1426,3452 ,4716,4648,1794 }, + {2235,2234,1881 ,6870,6949,7066 }, {307,4989,6463 ,5095,5094,5159 }, + {2209,912,4617 ,7038,4471,4431 }, {7044,2248,7043 ,6859,6858,5895 }, + {2248,1075,1313 ,6858,6854,3674 }, {186,187,1171 ,1265,6439,6441 }, + {3133,1269,187 ,7085,5724,6439 }, {186,3133,187 ,1265,7085,6439 }, + {5890,4648,1269 ,6591,5722,5724 }, {3133,5890,1269 ,7085,6591,5724 }, + {1126,6037,1272 ,7086,5235,5740 }, {2237,6297,6291 ,6801,6851,6873 }, + {6748,1990,2406 ,3408,1305,6736 }, {6747,6748,2406 ,6878,3408,6736 }, + {4684,2245,3319 ,5217,1260,1259 }, {4979,4684,3319 ,5363,5217,1259 }, + {6305,6327,6326 ,7087,7088,7067 }, {6286,6305,6326 ,4349,7087,7067 }, + {6326,6327,6363 ,7067,7088,7068 }, {6769,6770,6822 ,7078,7089,7079 }, + {4901,7069,7070 ,3596,7059,7084 }, {4900,6378,2274 ,585,6728,893 }, + {6689,2405,2337 ,3065,4165,6829 }, {6378,1882,2274 ,6728,894,893 }, + {3556,3504,3557 ,3385,2986,6070 }, {4570,5072,6261 ,4082,6619,4321 }, + {5444,4544,5040 ,3410,748,5469 }, {7533,7568,7509 ,7039,5544,1874 }, + {6666,6724,6723 ,7076,7090,7077 }, {6724,6770,6769 ,7090,7089,7078 }, + {6723,6724,6769 ,7077,7090,7078 }, {6787,6805,6804 ,5269,3343,3342 }, + {978,977,4965 ,1620,3931,784 }, {6615,6652,6642 ,6924,6914,6960 }, + {3786,3400,1451 ,4074,3659,175 }, {6005,186,1171 ,5691,1265,6441 }, + {1026,733,5006 ,6218,4051,1351 }, {6507,6529,6479 ,2680,5025,2659 }, + {4988,5494,5020 ,6744,6569,6373 }, {8109,8127,8119 ,2938,2970,184 }, + {2246,2452,7066 ,591,7091,3586 }, {4684,6603,6359 ,5217,3985,6830 }, + {2245,4684,6359 ,1260,5217,6830 }, {6286,6287,6305 ,4349,4377,7087 }, + {6287,6328,6327 ,4377,7092,7088 }, {6305,6287,6327 ,7087,4377,7088 }, + {6328,6364,6363 ,7092,7093,7068 }, {6327,6328,6363 ,7088,7092,7068 }, + {6364,6400,6399 ,7093,7094,7069 }, {6363,6364,6399 ,7068,7093,7069 }, + {6400,6424,6399 ,7094,7095,7069 }, {6424,6450,6449 ,7095,7096,7070 }, + {6399,6424,6449 ,7069,7095,7070 }, {6450,6494,6493 ,7096,7097,7071 }, + {6449,6450,6493 ,7070,7096,7071 }, {6495,6516,6493 ,7098,7072,7071 }, + {6494,6495,6493 ,7097,7098,7071 }, {6540,6539,6516 ,7099,7073,7072 }, + {6495,6540,6516 ,7098,7099,7072 }, {6586,6585,6539 ,7100,7074,7073 }, + {6540,6586,6539 ,7099,7100,7073 }, {6586,6627,6626 ,7100,7101,7075 }, + {6585,6586,6626 ,7074,7100,7075 }, {6627,6667,6666 ,7101,7102,7076 }, + {6626,6627,6666 ,7075,7101,7076 }, {6667,6668,6666 ,7102,7103,7076 }, + {6668,6725,6724 ,7103,7104,7090 }, {6666,6668,6724 ,7076,7103,7090 }, + {6725,6771,6770 ,7104,7105,7089 }, {6724,6725,6770 ,7090,7104,7089 }, + {6771,6823,6822 ,7105,7106,7079 }, {6770,6771,6822 ,7089,7105,7079 }, + {6823,6878,6877 ,7106,7107,7080 }, {6822,6823,6877 ,7079,7106,7080 }, + {6927,6926,6877 ,7108,7081,7080 }, {6878,6927,6877 ,7107,7108,7080 }, + {6975,6974,6926 ,7109,7082,7081 }, {6927,6975,6926 ,7108,7109,7081 }, + {6975,7011,7010 ,7109,7110,7083 }, {6974,6975,7010 ,7082,7109,7083 }, + {7011,7071,7070 ,7110,7111,7084 }, {7010,7011,7070 ,7083,7110,7084 }, + {7071,1261,7070 ,7111,6472,7084 }, {1261,5591,7070 ,6472,3597,7084 }, + {4986,6659,3399 ,5096,3577,715 }, {6854,6855,6893 ,3344,6959,6844 }, + {1076,2209,4617 ,4887,7038,4431 }, {4746,5078,6302 ,5260,5261,6869 }, + {2291,2221,3133 ,1115,6902,7085 }, {186,2291,3133 ,1265,1115,7085 }, + {2221,6214,5890 ,6902,6942,6591 }, {3133,2221,5890 ,7085,6902,6591 }, + {4950,4115,1216 ,1302,6909,6893 }, {5072,6220,6231 ,6619,6600,6624 }, + {2260,5494,4988 ,6571,6569,6744 }, {2211,2260,4988 ,6780,6571,6744 }, + {5444,5040,5024 ,3410,5469,6952 }, {2157,5444,5024 ,3411,3410,6952 }, + {5591,4901,7070 ,3597,3596,7084 }, {6627,6668,6667 ,7101,7103,7102 }, + {5330,5331,5350 ,3345,6198,1526 }, {5611,6920,5593 ,6177,6783,5935 }, + {6234,6259,6167 ,5973,6718,5974 }, {2717,819,728 ,2676,2244,1266 }, + {2025,2000,2027 ,2430,2428,2492 }, {2342,2221,5114 ,5690,6902,5689 }, + {5435,5401,6611 ,4223,4145,1391 }, {6529,6576,6528 ,5025,6883,6964 }, + {6870,2222,2205 ,2616,2509,2874 }, {6603,1116,5074 ,3985,1401,6751 }, + {6359,6603,5074 ,6830,3985,6751 }, {6476,6495,6494 ,7112,7098,7097 }, + {6450,6476,6494 ,7096,7112,7097 }, {6586,6628,6627 ,7100,7113,7101 }, + {6627,6628,6668 ,7101,7113,7103 }, {6163,3860,6029 ,1453,6523,1454 }, + {5059,7081,2220 ,1253,7114,6724 }, {1171,187,3498 ,6441,6439,5800 }, + {7022,6696,3649 ,6754,6849,6755 }, {2260,2211,6951 ,6571,6780,2615 }, + {2157,5024,2244 ,3411,6952,6795 }, {6278,6288,6287 ,3967,7115,4377 }, + {6277,6278,6287 ,4387,3967,4377 }, {6288,6329,6328 ,7115,7116,7092 }, + {6287,6288,6328 ,4377,7115,7092 }, {6329,6365,6364 ,7116,7117,7093 }, + {6328,6329,6364 ,7092,7116,7093 }, {6365,6401,6400 ,7117,7118,7094 }, + {6364,6365,6400 ,7093,7117,7094 }, {6401,6425,6424 ,7118,7119,7095 }, + {6400,6401,6424 ,7094,7118,7095 }, {6425,6451,6450 ,7119,7120,7096 }, + {6424,6425,6450 ,7095,7119,7096 }, {6450,6451,6476 ,7096,7120,7112 }, + {6451,6496,6495 ,7120,7121,7098 }, {6476,6451,6495 ,7112,7120,7098 }, + {6496,6541,6540 ,7121,7122,7099 }, {6495,6496,6540 ,7098,7121,7099 }, + {6541,6587,6586 ,7122,7123,7100 }, {6540,6541,6586 ,7099,7122,7100 }, + {6587,6607,6586 ,7123,7124,7100 }, {6607,6629,6628 ,7124,7125,7113 }, + {6586,6607,6628 ,7100,7124,7113 }, {6629,6669,6668 ,7125,7126,7103 }, + {6628,6629,6668 ,7113,7125,7103 }, {6669,6726,6725 ,7126,7127,7104 }, + {6668,6669,6725 ,7103,7126,7104 }, {6772,6771,6725 ,7128,7105,7104 }, + {6726,6772,6725 ,7127,7128,7104 }, {6772,6824,6823 ,7128,7129,7106 }, + {6771,6772,6823 ,7105,7128,7106 }, {6824,6879,6878 ,7129,7130,7107 }, + {6823,6824,6878 ,7106,7129,7107 }, {6879,6880,6878 ,7130,7131,7107 }, + {6880,6928,6927 ,7131,7132,7108 }, {6878,6880,6927 ,7107,7131,7108 }, + {6976,6975,6927 ,7133,7109,7108 }, {6928,6976,6927 ,7132,7133,7108 }, + {6976,7012,7011 ,7133,7134,7110 }, {6975,6976,7011 ,7109,7133,7110 }, + {7053,7071,7011 ,6654,7111,7110 }, {7012,7053,7011 ,7134,6654,7110 }, + {1723,1261,7071 ,6626,6472,7111 }, {7053,1723,7071 ,6654,6626,7111 }, + {8127,3221,8119 ,2970,183,184 }, {6792,6839,6810 ,5056,6867,6908 }, + {7533,7595,7568 ,7039,4019,5544 }, {3979,5055,5753 ,6471,6470,6481 }, + {5008,3217,6214 ,6118,6117,6942 }, {2221,5008,6214 ,6902,6118,6942 }, + {6214,3217,5747 ,6942,6117,6036 }, {6654,6653,6616 ,6884,6915,6882 }, + {6529,6528,6479 ,5025,6964,2659 }, {8038,3638,8052 ,4121,5950,4122 }, + {6049,2260,6951 ,6570,6571,2615 }, {6606,2157,2244 ,5085,3411,6795 }, + {4990,1124,6065 ,7135,5852,6616 }, {1116,2239,185 ,1401,1377,1493 }, + {5074,1116,185 ,6751,1401,1493 }, {6824,6880,6879 ,7129,7131,7130 }, + {6880,6929,6928 ,7131,7136,7132 }, {6929,6976,6928 ,7136,7133,7132 }, + {6977,7013,7012 ,7137,7138,7134 }, {6976,6977,7012 ,7133,7137,7134 }, + {7013,7053,7012 ,7138,6654,7134 }, {7570,7625,7598 ,5204,7139,7140 }, + {6910,6941,6961 ,6741,6793,6813 }, {7000,7024,7042 ,6814,6816,5338 }, + {5328,2160,2238 ,2172,6621,2697 }, {5045,7061,6165 ,4214,7141,4219 }, + {4649,5125,5118 ,5288,5467,6825 }, {1214,6049,6951 ,6573,6570,2615 }, + {6606,2244,4856 ,5085,6795,4081 }, {3040,8072,8076 ,619,511,617 }, + {6824,6850,6880 ,7129,7142,7131 }, {6929,6977,6976 ,7136,7137,7133 }, + {7054,7053,7013 ,7143,6654,7138 }, {7081,4233,2220 ,7114,6185,6724 }, + {5124,4010,3969 ,6567,4343,3226 }, {7454,7511,7453 ,3996,6677,1187 }, + {5057,1214,6951 ,1795,6573,2615 }, {2454,6606,4856 ,1345,5085,4081 }, + {4209,3777,185 ,6269,1582,1493 }, {2239,4209,185 ,1377,6269,1493 }, + {6279,6289,6288 ,3966,7144,7115 }, {6278,6279,6288 ,3967,3966,7115 }, + {6289,6330,6329 ,7144,7145,7116 }, {6288,6289,6329 ,7115,7144,7116 }, + {6366,6365,6329 ,7146,7117,7116 }, {6330,6366,6329 ,7145,7146,7116 }, + {6366,6402,6401 ,7146,7147,7118 }, {6365,6366,6401 ,7117,7146,7118 }, + {6402,6403,6425 ,7147,7148,7119 }, {6401,6402,6425 ,7118,7147,7119 }, + {6403,6452,6451 ,7148,7149,7120 }, {6425,6403,6451 ,7119,7148,7120 }, + {6452,6497,6496 ,7149,7150,7121 }, {6451,6452,6496 ,7120,7149,7121 }, + {6497,6542,6541 ,7150,7151,7122 }, {6496,6497,6541 ,7121,7150,7122 }, + {6542,6588,6587 ,7151,7152,7123 }, {6541,6542,6587 ,7122,7151,7123 }, + {6588,6589,6607 ,7152,7153,7124 }, {6587,6588,6607 ,7123,7152,7124 }, + {6589,6630,6629 ,7153,7154,7125 }, {6607,6589,6629 ,7124,7153,7125 }, + {6630,6670,6669 ,7154,7155,7126 }, {6629,6630,6669 ,7125,7154,7126 }, + {6727,6726,6669 ,7156,7127,7126 }, {6670,6727,6669 ,7155,7156,7126 }, + {6773,6772,6726 ,7157,7128,7127 }, {6727,6773,6726 ,7156,7157,7127 }, + {6773,6825,6824 ,7157,7158,7129 }, {6772,6773,6824 ,7128,7157,7129 }, + {6825,6826,6850 ,7158,7159,7142 }, {6824,6825,6850 ,7129,7158,7142 }, + {6826,6881,6880 ,7159,7160,7131 }, {6850,6826,6880 ,7142,7159,7131 }, + {6881,6930,6929 ,7160,7161,7136 }, {6880,6881,6929 ,7131,7160,7136 }, + {6930,6978,6977 ,7161,7162,7137 }, {6929,6930,6977 ,7136,7161,7137 }, + {6978,7014,7013 ,7162,7163,7138 }, {6977,6978,7013 ,7137,7162,7138 }, + {7055,7054,7013 ,5485,7143,7138 }, {7014,7055,7013 ,7163,5485,7138 }, + {7055,7053,7054 ,5485,6654,7143 }, {7055,7079,7053 ,5485,5486,6654 }, + {6618,6617,5404 ,6910,6881,3559 }, {2273,2342,350 ,5322,5690,1114 }, + {1273,2273,350 ,6403,5322,1114 }, {5254,6030,5576 ,4042,4037,4039 }, + {1214,5057,6122 ,6573,1795,1797 }, {6101,1214,6122 ,6574,6573,1797 }, + {2272,2454,6386 ,1201,1345,4085 }, {4853,1118,2340 ,6747,6746,6797 }, + {875,2252,6836 ,579,590,852 }, {7053,7079,6150 ,6654,5486,6653 }, + {6366,6403,6402 ,7146,7148,7147 }, {6542,6589,6588 ,7151,7153,7152 }, + {1219,6150,7079 ,4102,6653,5486 }, {6916,6967,6915 ,596,4629,6871 }, + {6841,6840,6812 ,7164,6868,6872 }, {6967,6987,6966 ,4629,6917,6840 }, + {6794,6841,6812 ,6880,7164,6872 }, {6864,6894,6840 ,6865,6887,6868 }, + {6651,6699,6698 ,6785,630,629 }, {6967,6966,6915 ,4629,6840,6871 }, + {5944,1273,5653 ,4754,6403,3644 }, {4465,4483,4444 ,257,549,582 }, + {6392,3929,306 ,6963,5024,6234 }, {3302,6301,6300 ,6757,6147,6146 }, + {4755,2272,1164 ,6770,1201,5087 }, {2454,4856,6386 ,1345,4081,4085 }, + {6682,6679,3978 ,6812,4824,4549 }, {2252,144,6836 ,590,853,852 }, + {6542,6567,6589 ,7151,7165,7153 }, {6773,6826,6825 ,7157,7159,7158 }, + {5838,6355,6079 ,3987,3786,5580 }, {6650,6651,6698 ,3833,6785,629 }, + {7450,7478,7422 ,399,1402,1404 }, {5944,1126,1273 ,4754,7086,6403 }, + {1074,973,8073 ,2715,2017,2567 }, {3929,4166,306 ,5024,6084,6234 }, + {3675,3674,6180 ,4300,4286,6811 }, {2272,6386,1164 ,1201,4085,5087 }, + {6961,6962,7000 ,6813,3260,6814 }, {1220,1271,875 ,4885,588,579 }, + {6280,6290,6289 ,4079,7166,7144 }, {6279,6280,6289 ,3966,4079,7144 }, + {6290,6331,6330 ,7166,7167,7145 }, {6289,6290,6330 ,7144,7166,7145 }, + {6331,6367,6366 ,7167,7168,7146 }, {6330,6331,6366 ,7145,7167,7146 }, + {6367,6404,6403 ,7168,7169,7148 }, {6366,6367,6403 ,7146,7168,7148 }, + {6404,6426,6403 ,7169,7170,7148 }, {6426,6453,6452 ,7170,7171,7149 }, + {6403,6426,6452 ,7148,7170,7149 }, {6498,6497,6452 ,7172,7150,7149 }, + {6453,6498,6452 ,7171,7172,7149 }, {6498,6543,6542 ,7172,7173,7151 }, + {6497,6498,6542 ,7150,7172,7151 }, {6543,6544,6567 ,7173,7174,7165 }, + {6542,6543,6567 ,7151,7173,7165 }, {6544,6590,6589 ,7174,7175,7153 }, + {6567,6544,6589 ,7165,7174,7153 }, {6590,6631,6630 ,7175,7176,7154 }, + {6589,6590,6630 ,7153,7175,7154 }, {6631,6671,6670 ,7176,7177,7155 }, + {6630,6631,6670 ,7154,7176,7155 }, {6728,6727,6670 ,7178,7156,7155 }, + {6671,6728,6670 ,7177,7178,7155 }, {6728,6774,6773 ,7178,7179,7157 }, + {6727,6728,6773 ,7156,7178,7157 }, {6774,6775,6773 ,7179,7180,7157 }, + {6775,6827,6826 ,7180,7181,7159 }, {6773,6775,6826 ,7157,7180,7159 }, + {6827,6882,6881 ,7181,7182,7160 }, {6826,6827,6881 ,7159,7181,7160 }, + {6882,6931,6930 ,7182,7183,7161 }, {6881,6882,6930 ,7160,7182,7161 }, + {6931,6979,6978 ,7183,7184,7162 }, {6930,6931,6978 ,7161,7183,7162 }, + {6979,7015,7014 ,7184,7185,7163 }, {6978,6979,7014 ,7162,7184,7163 }, + {7056,7055,7014 ,7186,5485,7163 }, {7015,7056,7014 ,7185,7186,7163 }, + {1170,1219,7055 ,4103,4102,5485 }, {7056,1170,7055 ,7186,4103,5485 }, + {5499,5704,2621 ,5572,5579,5416 }, {6660,6220,5072 ,6601,6600,6619 }, + {4570,6660,5072 ,4082,6601,6619 }, {1121,5872,1122 ,6564,6557,6719 }, + {6786,6787,6804 ,4139,5269,3342 }, {3834,4570,4044 ,4083,4082,4322 }, + {5426,4807,5010 ,5634,6449,6006 }, {4803,5290,5267 ,4776,5247,4647 }, + {2154,2273,1273 ,5321,5322,6403 }, {1126,2154,1273 ,7086,5321,6403 }, + {5516,5551,6795 ,5420,6260,5418 }, {403,3929,6466 ,5547,5024,6930 }, + {5054,6688,6661 ,6847,6846,6876 }, {2015,2048,1994 ,759,5461,5102 }, + {7001,7025,7024 ,6815,6824,6816 }, {7048,7064,2074 ,6790,5181,6990 }, + {1271,2252,875 ,588,590,579 }, {7064,2073,2074 ,5181,5183,6990 }, + {6498,6544,6543 ,7172,7174,7173 }, {6728,6775,6774 ,7178,7180,7179 }, + {6660,6219,6220 ,6601,6598,6600 }, {4486,3340,3454 ,819,114,710 }, + {6526,6574,6573 ,6784,3885,3733 }, {5738,5688,5689 ,5520,5519,5741 }, + {307,6391,6659 ,5095,6804,3577 }, {3929,6392,6466 ,5024,6963,6930 }, + {5113,5914,5150 ,141,1566,6032 }, {6435,6436,6480 ,2658,3681,1524 }, + {3649,5054,6661 ,6755,6847,6876 }, {7067,2246,7066 ,3585,591,3586 }, + {1197,2384,1987 ,478,6821,3614 }, {4190,1271,1220 ,3439,588,4885 }, + {7007,7006,6966 ,4628,6841,6840 }, {6631,6672,6671 ,7176,7187,7177 }, + {6672,6728,6671 ,7187,7178,7177 }, {6807,6808,6858 ,6764,3465,6740 }, + {6703,6701,6702 ,5161,6766,7060 }, {2337,5578,1124 ,6829,587,5852 }, + {4990,2337,1124 ,7135,6829,5852 }, {1025,2450,6657 ,5299,3444,6828 }, + {6689,2337,4990 ,3065,6829,7135 }, {1608,746,5592 ,4128,2661,3671 }, + {5277,5330,5276 ,6237,3345,1525 }, {7068,3892,6166 ,7019,4970,4971 }, + {6034,6284,5952 ,6049,6113,6595 }, {403,6466,1074 ,5547,6930,2715 }, + {5078,403,1074 ,5261,5547,2715 }, {6807,6858,6838 ,6764,6740,6904 }, + {158,4755,5560 ,5258,6770,6605 }, {5076,158,5560 ,6607,5258,6605 }, + {1213,3261,1197 ,3700,5015,478 }, {1987,4190,1220 ,3614,3439,4885 }, + {1620,6128,6184 ,5982,5981,6089 }, {6281,3526,6290 ,4078,7188,7166 }, + {6280,6281,6290 ,4079,4078,7166 }, {3526,6332,6331 ,7188,7189,7167 }, + {6290,3526,6331 ,7166,7188,7167 }, {6368,6367,6331 ,7190,7168,7167 }, + {6332,6368,6331 ,7189,7190,7167 }, {6368,6405,6404 ,7190,7191,7169 }, + {6367,6368,6404 ,7168,7190,7169 }, {6405,6427,6426 ,7191,7192,7170 }, + {6404,6405,6426 ,7169,7191,7170 }, {6427,6454,6453 ,7192,7193,7171 }, + {6426,6427,6453 ,7170,7192,7171 }, {6454,6499,6498 ,7193,7194,7172 }, + {6453,6454,6498 ,7171,7193,7172 }, {6499,6517,6498 ,7194,7195,7172 }, + {6517,6545,6544 ,7195,7196,7174 }, {6498,6517,6544 ,7172,7195,7174 }, + {6545,6591,6590 ,7196,7197,7175 }, {6544,6545,6590 ,7174,7196,7175 }, + {6632,6631,6590 ,7198,7176,7175 }, {6591,6632,6590 ,7197,7198,7175 }, + {6632,6673,6672 ,7198,7199,7187 }, {6631,6632,6672 ,7176,7198,7187 }, + {6729,6728,6672 ,7200,7178,7187 }, {6673,6729,6672 ,7199,7200,7187 }, + {6729,6776,6775 ,7200,7201,7180 }, {6728,6729,6775 ,7178,7200,7180 }, + {6776,6828,6827 ,7201,7202,7181 }, {6775,6776,6827 ,7180,7201,7181 }, + {6828,6883,6882 ,7202,7203,7182 }, {6827,6828,6882 ,7181,7202,7182 }, + {6883,6932,6931 ,7203,7204,7183 }, {6882,6883,6931 ,7182,7203,7183 }, + {6932,6980,6979 ,7204,7205,7184 }, {6931,6932,6979 ,7183,7204,7184 }, + {6980,7016,7015 ,7205,7206,7185 }, {6979,6980,7015 ,7184,7205,7185 }, + {7057,7056,7015 ,7207,7186,7185 }, {7016,7057,7015 ,7206,7207,7185 }, + {1122,1170,7056 ,6719,4103,7186 }, {7057,1122,7056 ,7207,6719,7186 }, + {1170,1122,1620 ,4103,6719,5982 }, {6655,6703,6654 ,5407,5161,6884 }, + {6430,5733,5353 ,2748,1584,851 }, {4023,5248,4178 ,3827,6469,3825 }, + {6319,6345,5118 ,6789,191,6825 }, {5734,5733,4261 ,6230,1584,1583 }, + {2450,6689,6657 ,3444,3065,6828 }, {2621,3815,5359 ,5416,5556,3713 }, + {7196,7231,7212 ,2401,6944,7208 }, {2160,2154,1126 ,6621,5321,7086 }, + {1272,2160,1126 ,5740,6621,7086 }, {7024,7025,7042 ,6816,6824,5338 }, + {4755,1164,5560 ,6770,5087,6605 }, {2384,4190,1987 ,6821,3439,3614 }, + {7998,7997,7950 ,7209,7210,7211 }, {5514,6158,6293 ,6128,6112,7212 }, + {6703,6702,6654 ,5161,7060,6884 }, {6307,6301,3302 ,5206,6147,6757 }, + {4018,6307,3302 ,5084,5206,6757 }, {6657,6689,4990 ,6828,3065,7135 }, + {6521,3261,6639 ,6819,5015,3699 }, {5496,6521,6564 ,6966,6819,6933 }, + {6521,6639,6508 ,6819,3699,6934 }, {6741,6794,6756 ,5388,6880,6891 }, + {6896,6895,6842 ,6787,5434,5433 }, {5005,5496,4115 ,1301,6966,6909 }, + {6792,6810,6791 ,5056,6908,6900 }, {1075,2409,2373 ,6854,6852,6722 }, + {5057,6951,6796 ,1795,2615,2617 }, {6345,6319,3533 ,191,6789,192 }, + {6521,6508,6564 ,6819,6934,6933 }, {3261,2384,1197 ,5015,6821,478 }, + {5369,1124,5904 ,6609,5852,5851 }, {5566,5605,5604 ,5759,5761,7213 }, + {5140,2621,5359 ,3712,5416,3713 }, {3505,6443,3504 ,6928,6940,2986 }, + {5496,6564,3320 ,6966,6933,5455 }, {5514,5943,7088 ,6128,5402,4299 }, + {7065,7064,7049 ,5182,5181,5454 }, {3261,1213,6639 ,5015,3700,3699 }, + {5496,3320,5058 ,6966,5455,5647 }, {6867,6844,5551 ,6951,6822,6260 }, + {4926,920,3367 ,3897,57,5798 }, {5014,2450,1025 ,6818,3444,5299 }, + {2238,2160,1272 ,2697,6621,5740 }, {6906,6907,6958 ,4294,6002,4314 }, + {876,4232,4208 ,5340,6299,6356 }, {6711,6710,6301 ,3473,6968,6147 }, + {1216,4115,4547 ,6893,6909,242 }, {7387,3303,3322 ,3615,1049,1182 }, + {5552,5593,6867 ,5936,5935,6951 }, {1124,5369,6065 ,5852,6609,6616 }, + {3526,6281,6060 ,7188,4078,5914 }, {6060,6333,6332 ,5914,7214,7189 }, + {3526,6060,6332 ,7188,5914,7189 }, {6369,6368,6332 ,7215,7190,7189 }, + {6333,6369,6332 ,7214,7215,7189 }, {6406,6405,6368 ,7216,7191,7190 }, + {6369,6406,6368 ,7215,7216,7190 }, {6428,6427,6405 ,7217,7192,7191 }, + {6406,6428,6405 ,7216,7217,7191 }, {6455,6454,6427 ,7218,7193,7192 }, + {6428,6455,6427 ,7217,7218,7192 }, {6455,6500,6499 ,7218,7219,7194 }, + {6454,6455,6499 ,7193,7218,7194 }, {6500,6518,6517 ,7219,7220,7195 }, + {6499,6500,6517 ,7194,7219,7195 }, {6518,6546,6545 ,7220,7221,7196 }, + {6517,6518,6545 ,7195,7220,7196 }, {6546,6592,6591 ,7221,7222,7197 }, + {6545,6546,6591 ,7196,7221,7197 }, {6592,6633,6632 ,7222,7223,7198 }, + {6591,6592,6632 ,7197,7222,7198 }, {6633,6674,6673 ,7223,7224,7199 }, + {6632,6633,6673 ,7198,7223,7199 }, {6730,6729,6673 ,7225,7200,7199 }, + {6674,6730,6673 ,7224,7225,7199 }, {6730,6777,6776 ,7225,7226,7201 }, + {6729,6730,6776 ,7200,7225,7201 }, {6777,6829,6828 ,7226,7227,7202 }, + {6776,6777,6828 ,7201,7226,7202 }, {6829,6884,6883 ,7227,7228,7203 }, + {6828,6829,6883 ,7202,7227,7203 }, {6884,6933,6932 ,7228,7229,7204 }, + {6883,6884,6932 ,7203,7228,7204 }, {6933,6981,6980 ,7229,7230,7205 }, + {6932,6933,6980 ,7204,7229,7205 }, {6981,6126,7016 ,7230,7231,7206 }, + {6980,6981,7016 ,7205,7230,7206 }, {6126,6107,7016 ,7231,7232,7206 }, + {7016,6107,7057 ,7206,7232,7207 }, {5693,5714,7028 ,4075,4077,3587 }, + {1122,7057,5936 ,6719,7207,4393 }, {4554,1989,6323 ,1307,3016,1451 }, + {3860,5246,6028 ,6523,5176,5316 }, {4115,5058,4547 ,6909,5647,242 }, + {6319,6318,6304 ,6789,6788,7233 }, {1121,1122,5936 ,6564,6719,4393 }, + {4115,5496,5058 ,6909,6966,5647 }, {1882,1072,4547 ,894,6725,242 }, + {6711,5014,1025 ,3473,6818,5299 }, {6178,3892,4901 ,5881,4970,3596 }, + {6867,5551,5552 ,6951,6260,5936 }, {7370,7396,7343 ,3750,6716,2750 }, + {6307,6711,6301 ,5206,3473,6147 }, {2343,8067,8097 ,2685,2168,1710 }, + {4784,4542,3285 ,3423,6727,3064 }, {4542,6481,4900 ,6727,6729,585 }, + {7197,5112,5111 ,7234,6021,5990 }, {6387,4784,3285 ,6935,3423,3064 }, + {3285,4542,4900 ,3064,6727,585 }, {6633,6645,6674 ,7223,7235,7224 }, + {6995,6107,6126 ,7236,7232,7231 }, {6981,6995,6126 ,7230,7236,7231 }, + {5503,5504,5531 ,7042,5781,5783 }, {5713,4648,5890 ,5776,5722,6591 }, + {2374,2304,1118 ,7037,6756,6746 }, {1027,2374,1118 ,6991,7037,6746 }, + {5838,6318,6355 ,3987,6788,3786 }, {6378,2220,1882 ,6728,6724,894 }, + {6709,1072,1882 ,6726,6725,894 }, {2244,5024,6660 ,6795,6952,6601 }, + {6710,6711,1025 ,6968,3473,5299 }, {1524,1655,4872 ,4326,4089,5522 }, + {6839,6862,6810 ,6867,6877,6908 }, {7650,7699,7649 ,7237,3951,3950 }, + {7595,7594,7568 ,4019,4020,5544 }, {5970,4170,4146 ,3518,3731,3730 }, + {2374,4018,3302 ,7037,5084,6757 }, {4144,5292,5175 ,4761,5093,6384 }, + {1217,2220,6378 ,6399,6724,6728 }, {1072,1216,4547 ,6725,6893,242 }, + {5024,5040,2138 ,6952,5469,6602 }, {5040,3704,2138 ,5469,2332,6602 }, + {5111,7166,7197 ,5990,5971,7234 }, {6234,5845,7255 ,5973,143,7238 }, + {5112,7197,6120 ,6021,7234,142 }, {6281,7089,5115 ,4078,5608,5913 }, + {6829,6851,6884 ,7227,7239,7228 }, {6885,6934,6933 ,7240,7241,7229 }, + {6884,6885,6933 ,7228,7240,7229 }, {6934,6982,6981 ,7241,7242,7230 }, + {6933,6934,6981 ,7229,7241,7230 }, {6981,6982,6995 ,7230,7242,7236 }, + {6982,7017,6107 ,7242,7243,7232 }, {6995,6982,6107 ,7236,7242,7232 }, + {7017,5930,6143 ,7243,6593,4394 }, {6107,7017,6143 ,7232,7243,4394 }, + {7424,7451,7423 ,1188,3963,3749 }, {4835,1218,8118 ,2130,3255,2129 }, + {7650,7649,7595 ,7237,3950,4019 }, {2209,2241,2240 ,7038,6950,4478 }, + {1394,1027,1118 ,6989,6991,6746 }, {2335,1394,1118 ,6737,6989,6746 }, + {4784,2406,4542 ,3423,6736,6727 }, {6814,1217,4542 ,6842,6399,6727 }, + {4530,1554,4209 ,1376,6304,6269 }, {4574,1268,6062 ,6477,4568,6478 }, + {2220,6709,1882 ,6724,6726,894 }, {5166,5473,5120 ,3427,3429,5857 }, + {1655,1524,2432 ,4089,4326,5427 }, {5427,5395,5428 ,5811,5348,5812 }, + {8093,8076,8072 ,2288,617,511 }, {6569,4746,2222 ,6745,5260,2509 }, + {5277,5276,5255 ,6237,1525,3682 }, {6180,3623,3675 ,6811,4206,4300 }, + {6868,6657,5041 ,5554,6828,6799 }, {2304,2374,3302 ,6756,7037,6757 }, + {6318,6356,6355 ,6788,3787,3786 }, {6746,6747,4784 ,6864,6878,3423 }, + {4542,1217,6378 ,6727,6399,6728 }, {7160,7185,7159 ,6271,5073,5075 }, + {7267,7246,7291 ,4331,3772,3774 }, {4552,1309,3371 ,7244,3709,3708 }, + {3265,4552,3371 ,1033,7244,3708 }, {7127,7128,7159 ,7245,5391,5075 }, + {6292,6334,6333 ,5403,7246,7214 }, {6060,6292,6333 ,5914,5403,7214 }, + {6334,6335,6333 ,7246,6534,7214 }, {6370,6369,6333 ,7247,7215,7214 }, + {6335,6370,6333 ,6534,7247,7214 }, {6407,6406,6369 ,7248,7216,7215 }, + {6370,6407,6369 ,7247,7248,7215 }, {6429,6428,6406 ,7249,7217,7216 }, + {6407,6429,6406 ,7248,7249,7216 }, {6429,6456,6455 ,7249,7250,7218 }, + {6428,6429,6455 ,7217,7249,7218 }, {6456,6501,6500 ,7250,7251,7219 }, + {6455,6456,6500 ,7218,7250,7219 }, {6519,6518,6500 ,7252,7220,7219 }, + {6501,6519,6500 ,7251,7252,7219 }, {6547,6546,6518 ,7253,7221,7220 }, + {6519,6547,6518 ,7252,7253,7220 }, {6547,6593,6592 ,7253,7254,7222 }, + {6546,6547,6592 ,7221,7253,7222 }, {6593,6634,6633 ,7254,7255,7223 }, + {6592,6593,6633 ,7222,7254,7223 }, {6633,6634,6645 ,7223,7255,7235 }, + {6634,6675,6674 ,7255,7256,7224 }, {6645,6634,6674 ,7235,7255,7224 }, + {6675,6731,6730 ,7256,7257,7225 }, {6674,6675,6730 ,7224,7256,7225 }, + {6731,6778,6777 ,7257,7258,7226 }, {6730,6731,6777 ,7225,7257,7226 }, + {6778,6830,6829 ,7258,7259,7227 }, {6777,6778,6829 ,7226,7258,7227 }, + {6829,6830,6851 ,7227,7259,7239 }, {6830,6885,6884 ,7259,7240,7228 }, + {6851,6830,6884 ,7239,7259,7228 }, {6935,6934,6885 ,7260,7241,7240 }, + {6934,6935,6982 ,7241,7260,7242 }, {1664,6228,751 ,2268,1366,1365 }, + {7047,2335,7046 ,6853,6737,6735 }, {5053,1025,6389 ,6848,5299,5301 }, + {6714,6657,6868 ,5300,6828,5554 }, {3872,5082,1551 ,4811,5218,5275 }, + {2406,6814,4542 ,6736,6842,6727 }, {6312,6747,6746 ,6749,6878,6864 }, + {3510,75,2837 ,6267,3962,2511 }, {2837,1518,3510 ,2511,3978,6267 }, + {6114,6355,5151 ,1805,3786,1803 }, {5085,1947,1881 ,3675,6723,7066 }, + {5041,6065,5687 ,6799,6616,6420 }, {3336,3309,6285 ,3922,3892,3923 }, + {4831,3340,4486 ,3154,114,819 }, {6318,5838,6303 ,6788,3987,7261 }, + {6303,6304,6318 ,7261,7233,6788 }, {7116,7115,7089 ,7262,1024,5608 }, + {6432,6553,3265 ,1032,780,1033 }, {5914,5170,5150 ,1566,3683,6032 }, + {7149,7173,7165 ,7263,7264,7265 }, {6292,6335,6334 ,5403,6534,7246 }, + {6371,6370,6335 ,7266,7247,6534 }, {6370,6371,6407 ,7247,7266,7248 }, + {6593,6608,6634 ,7254,7267,7255 }, {6688,5102,5101 ,6846,6845,6422 }, + {5491,6795,6741 ,5419,5418,5388 }, {1380,6312,6310 ,6721,6749,3918 }, + {6747,2406,4784 ,6878,6736,3423 }, {6310,6312,6746 ,3918,6749,6864 }, + {1126,5944,6037 ,7086,4754,5235 }, {4574,3079,5301 ,6477,6540,2698 }, + {209,648,4892 ,4499,4679,5386 }, {3079,3631,5301 ,6540,5922,2698 }, + {4233,1026,1072 ,6185,6218,6725 }, {5102,5041,5007 ,6845,6799,7268 }, + {5007,5041,5687 ,7268,6799,6420 }, {7047,1394,2335 ,6853,6989,6737 }, + {5479,5491,6741 ,5387,5419,5388 }, {6795,6794,6741 ,5418,6880,5388 }, + {255,1380,256 ,6731,6721,7269 }, {3277,2567,227 ,961,2842,2157 }, + {6083,6303,5838 ,3989,7261,3987 }, {7797,7875,7848 ,3506,4118,343 }, + {6620,3265,6553 ,7270,1033,780 }, {1309,4552,3935 ,3709,7244,4346 }, + {6079,6343,5838 ,5580,5122,3987 }, {6895,6916,6864 ,5434,596,6865 }, + {6962,6963,7001 ,3260,1048,6815 }, {1170,6184,5627 ,4103,6089,6628 }, + {4588,4587,8020 ,775,774,1405 }, {6292,6306,6335 ,5403,5669,6534 }, + {6608,6635,6634 ,7267,7271,7255 }, {6676,6675,6634 ,7272,7256,7255 }, + {6635,6676,6634 ,7271,7272,7255 }, {6676,6732,6731 ,7272,7273,7257 }, + {6675,6676,6731 ,7256,7272,7257 }, {6732,6779,6778 ,7273,7274,7258 }, + {6731,6732,6778 ,7257,7273,7258 }, {6779,6800,6778 ,7274,7275,7258 }, + {6800,6831,6830 ,7275,7276,7259 }, {6778,6800,6830 ,7258,7275,7259 }, + {6831,6886,6885 ,7276,7277,7240 }, {6830,6831,6885 ,7259,7276,7240 }, + {6936,6935,6885 ,7278,7260,7240 }, {6886,6936,6885 ,7277,7278,7240 }, + {6936,6983,6982 ,7278,7279,7242 }, {6935,6936,6982 ,7260,7278,7242 }, + {6983,7018,7017 ,7279,7280,7243 }, {6982,6983,7017 ,7242,7279,7243 }, + {7058,5930,7017 ,6594,6593,7243 }, {7018,7058,7017 ,7280,6594,7243 }, + {1120,1167,876 ,5339,5341,5340 }, {3498,6005,1171 ,5800,5691,6441 }, + {5101,5007,5687 ,6422,7268,6420 }, {2240,2241,3978 ,4478,6950,4549 }, + {6795,6813,6794 ,5418,6916,6880 }, {3281,1380,6310 ,6794,6721,3918 }, + {6688,5101,5048 ,6846,6422,6421 }, {352,255,256 ,593,6731,7269 }, + {353,352,256 ,7281,593,7269 }, {6842,6841,6794 ,5433,7164,6880 }, + {6813,6842,6794 ,6916,5433,6880 }, {5101,5102,5007 ,6422,6845,7268 }, + {6865,6840,6841 ,5435,6868,7164 }, {6842,6865,6841 ,5433,5435,7164 }, + {7500,4595,6345 ,326,5289,191 }, {7271,7320,7270 ,2554,7282,2555 }, + {7898,7874,7848 ,4117,344,343 }, {6314,6553,6646 ,1132,780,1133 }, + {7539,7515,7538 ,1080,1079,2014 }, {5051,6620,6553 ,7283,7270,780 }, + {6314,5051,6553 ,1132,7283,780 }, {5051,3265,6620 ,7283,1033,7270 }, + {6798,4552,3265 ,7284,7244,1033 }, {5051,6798,3265 ,7283,7284,1033 }, + {5010,4807,4552 ,6006,6449,7244 }, {6798,5010,4552 ,7284,6006,7244 }, + {5943,6306,6292 ,5402,5669,5403 }, {2247,2452,353 ,7285,7091,7281 }, + {1380,3281,256 ,6721,6794,7269 }, {4588,8020,7995 ,775,1405,4135 }, + {6048,6049,1214 ,6552,6570,6573 }, {3820,1218,4835 ,5591,3255,2130 }, + {6372,6371,6335 ,7286,7266,6534 }, {6336,6372,6335 ,5670,7286,6534 }, + {6372,6408,6407 ,7286,7287,7248 }, {6371,6372,6407 ,7266,7286,7248 }, + {6408,6429,6407 ,7287,7249,7248 }, {6457,6456,6429 ,7288,7250,7249 }, + {6408,6457,6429 ,7287,7288,7249 }, {6502,6501,6456 ,7289,7251,7250 }, + {6457,6502,6456 ,7288,7289,7250 }, {6520,6519,6501 ,7290,7252,7251 }, + {6502,6520,6501 ,7289,7290,7251 }, {6520,6548,6547 ,7290,7291,7253 }, + {6519,6520,6547 ,7252,7290,7253 }, {6548,6594,6593 ,7291,7292,7254 }, + {6547,6548,6593 ,7253,7291,7254 }, {6609,6608,6593 ,7293,7267,7254 }, + {6594,6609,6593 ,7292,7293,7254 }, {6609,6635,6608 ,7293,7271,7267 }, + {7058,5929,5979 ,6594,6029,6028 }, {5359,5977,1936 ,3713,809,848 }, + {6914,6965,6945 ,3532,6732,2632 }, {7066,2452,7065 ,3586,7091,5182 }, + {6895,6864,6840 ,5434,6865,6868 }, {5404,6576,6529 ,3559,6883,5025 }, + {5350,5404,6529 ,1526,3559,5025 }, {5205,5166,5190 ,5219,3427,5858 }, + {6855,6856,6893 ,6959,6892,6844 }, {2308,3302,6300 ,6798,6757,6146 }, + {6297,6442,6696 ,6851,6850,6849 }, {6865,6895,6840 ,5435,5434,6868 }, + {6916,6915,6864 ,596,6871,6865 }, {7028,7066,7027 ,3587,3586,3175 }, + {1492,5075,4017 ,1585,1586,5342 }, {876,4017,4232 ,5340,5342,6299 }, + {7875,7898,7848 ,4118,4117,343 }, {7925,7924,7874 ,7294,815,344 }, + {7023,6314,2259 ,7295,1132,1131 }, {5645,6798,5051 ,7296,7284,7283 }, + {2140,5010,6798 ,5635,6006,7284 }, {5645,2140,6798 ,7296,5635,7284 }, + {7846,7897,7845 ,4162,6015,5725 }, {2452,352,353 ,7091,593,7281 }, + {6114,6079,6355 ,1805,5580,3786 }, {5248,4023,5247 ,6469,3827,4096 }, + {6502,6548,6520 ,7289,7291,7290 }, {6594,6635,6609 ,7292,7271,7293 }, + {2452,2247,7065 ,7091,7285,5182 }, {7028,7027,6968 ,3587,3175,595 }, + {5917,4541,3079 ,6402,6630,6540 }, {1283,3844,5788 ,2978,2443,6086 }, + {6464,297,5966 ,3633,1918,5054 }, {1071,2455,1027 ,3382,475,6991 }, + {6442,6708,6696 ,6850,6148,6849 }, {6320,3533,7439 ,190,192,7297 }, + {7925,7974,7924 ,7294,4171,815 }, {3426,2566,2527 ,4438,2635,2637 }, + {7023,5051,6314 ,7295,7283,1132 }, {5892,6036,3498 ,2683,6141,5800 }, + {4170,5970,5447 ,3731,3518,5633 }, {6947,6988,6946 ,4587,6771,594 }, + {7066,7065,7027 ,3586,5182,3175 }, {5363,5396,5383 ,3269,3548,3547 }, + {6458,6457,6408 ,7298,7288,7287 }, {6503,6502,6457 ,7299,7289,7288 }, + {6458,6503,6457 ,7298,7299,7288 }, {6549,6548,6502 ,7300,7291,7289 }, + {6503,6549,6502 ,7299,7300,7289 }, {6595,6594,6548 ,7301,7292,7291 }, + {6549,6595,6548 ,7300,7301,7291 }, {6636,6635,6594 ,7302,7271,7292 }, + {6595,6636,6594 ,7301,7302,7292 }, {6677,6676,6635 ,7303,7272,7271 }, + {6636,6677,6635 ,7302,7303,7271 }, {6733,6732,6676 ,7304,7273,7272 }, + {6677,6733,6676 ,7303,7304,7272 }, {6780,6779,6732 ,7305,7274,7273 }, + {6733,6780,6732 ,7304,7305,7273 }, {6801,6800,6779 ,7306,7275,7274 }, + {6780,6801,6779 ,7305,7306,7274 }, {6801,6832,6831 ,7306,7307,7276 }, + {6800,6801,6831 ,7275,7306,7276 }, {6887,6886,6831 ,7308,7277,7276 }, + {6832,6887,6831 ,7307,7308,7276 }, {6937,6936,6886 ,7309,7278,7277 }, + {6887,6937,6886 ,7308,7309,7277 }, {6937,6984,6983 ,7309,7310,7279 }, + {6936,6937,6983 ,7278,7309,7279 }, {7019,7018,6983 ,7311,7280,7279 }, + {6984,7019,6983 ,7310,7311,7279 }, {7019,7059,7058 ,7311,7312,6594 }, + {7018,7019,7058 ,7280,7311,6594 }, {7059,5045,5929 ,7312,4214,6029 }, + {7058,7059,5929 ,6594,7312,6029 }, {6967,7007,6987 ,4629,4628,6917 }, + {6988,7028,6968 ,6771,3587,595 }, {6920,6947,6919 ,6783,4587,6778 }, + {6863,6862,6839 ,6866,6877,6867 }, {6169,6190,3704 ,2334,6610,2332 }, + {5476,5055,1261 ,6495,6470,6472 }, {4595,4649,5118 ,5289,5288,6825 }, + {6920,6919,6896 ,6783,6778,6787 }, {4597,4583,7788 ,5541,5498,7313 }, + {7534,7533,7511 ,7314,7039,6677 }, {7115,7102,7103 ,1024,1240,6455 }, + {4553,2259,6351 ,7315,1131,1568 }, {6322,4553,6351 ,3531,7315,1568 }, + {4553,6296,2259 ,7315,7316,1131 }, {6298,7023,2259 ,7317,7295,1131 }, + {6296,6298,2259 ,7316,7317,1131 }, {5100,5051,7023 ,7318,7283,7295 }, + {6298,5100,7023 ,7317,7318,7295 }, {5100,7078,5051 ,7318,7319,7283 }, + {1262,5645,5051 ,7320,7296,7283 }, {7078,1262,5051 ,7319,7320,7283 }, + {4565,2140,5645 ,5944,5635,7296 }, {1262,4565,5645 ,7320,5944,7296 }, + {6463,4566,4985 ,5159,5143,2703 }, {6009,6337,6336 ,6129,7321,5670 }, + {5943,6009,6336 ,5402,6129,5670 }, {6337,6373,6372 ,7321,7322,7286 }, + {6336,6337,6372 ,5670,7321,7286 }, {6373,6409,6408 ,7322,7323,7287 }, + {6372,6373,6408 ,7286,7322,7287 }, {6459,6458,6408 ,7324,7298,7287 }, + {6409,6459,6408 ,7323,7324,7287 }, {6550,6549,6503 ,7325,7300,7299 }, + {6637,6636,6595 ,7326,7302,7301 }, {6780,6832,6801 ,7305,7307,7306 }, + {6988,6968,6946 ,6771,595,594 }, {6947,6946,6918 ,4587,594,6779 }, + {6792,6839,6811 ,5056,6867,399 }, {6156,1165,5917 ,6250,3153,6402 }, + {2241,5454,3978 ,6950,6817,4549 }, {2234,2235,2139 ,6949,6870,6895 }, + {3192,6387,3285 ,3063,6935,3064 }, {1313,1075,2373 ,3674,6854,6722 }, + {8037,8036,7996 ,4528,7327,7328 }, {5115,7103,6282 ,5913,6455,6617 }, + {41,1262,7078 ,6533,7320,7319 }, {5100,41,7078 ,7318,6533,7319 }, + {6029,6028,5976 ,1454,5316,5903 }, {1270,6569,2222 ,6781,6745,2509 }, + {6867,6896,6844 ,6951,6787,6822 }, {3838,4166,5999 ,6257,6084,5184 }, + {6637,6677,6636 ,7326,7303,7302 }, {6780,6801,6832 ,7305,7306,7307 }, + {7059,7072,5045 ,7312,7329,4214 }, {4577,4576,4575 ,5352,1628,3128 }, + {6792,6811,6793 ,5056,399,6879 }, {5447,4565,5564 ,5633,5944,5943 }, + {6947,6918,6919 ,4587,6779,6778 }, {3437,3500,3465 ,4142,4177,6890 }, + {3500,3551,3550 ,4177,4180,4179 }, {3670,4326,489 ,281,219,3639 }, + {3051,2273,5474 ,6488,5322,4243 }, {5415,4565,41 ,5951,5944,6533 }, + {6866,6842,6843 ,6786,5433,6911 }, {7818,4584,4598 ,5309,5308,5343 }, + {7187,7186,7161 ,7330,5370,2559 }, {1634,5482,1423 ,768,3565,151 }, + {4553,6298,6296 ,7315,7317,7316 }, {1262,41,4565 ,7320,6533,5944 }, + {6391,307,6463 ,6804,5095,5159 }, {2247,353,1071 ,7285,7281,3382 }, + {1215,5006,184 ,3937,1351,4052 }, {6293,6338,6337 ,7212,7331,7321 }, + {6009,6293,6337 ,6129,7212,7321 }, {6338,6374,6373 ,7331,7332,7322 }, + {6337,6338,6373 ,7321,7331,7322 }, {6410,6409,6373 ,7333,7323,7322 }, + {6374,6410,6373 ,7332,7333,7322 }, {6460,6459,6409 ,7334,7324,7323 }, + {6410,6460,6409 ,7333,7334,7323 }, {6460,6458,6459 ,7334,7298,7324 }, + {6504,6503,6458 ,7335,7299,7298 }, {6460,6504,6458 ,7334,7335,7298 }, + {6551,6550,6503 ,7336,7325,7299 }, {6504,6551,6503 ,7335,7336,7299 }, + {6551,6549,6550 ,7336,7300,7325 }, {6596,6595,6549 ,7337,7301,7300 }, + {6551,6596,6549 ,7336,7337,7300 }, {6638,6637,6595 ,7338,7326,7301 }, + {6596,6638,6595 ,7337,7338,7301 }, {6678,6677,6637 ,7339,7303,7326 }, + {6638,6678,6637 ,7338,7339,7326 }, {6678,6693,6677 ,7339,7340,7303 }, + {6734,6733,6677 ,7341,7304,7303 }, {6693,6734,6677 ,7340,7341,7303 }, + {6781,6780,6733 ,7342,7305,7304 }, {6734,6781,6733 ,7341,7342,7304 }, + {6781,6801,6780 ,7342,7306,7305 }, {6833,6832,6801 ,7343,7307,7306 }, + {6781,6833,6801 ,7342,7343,7306 }, {6888,6887,6832 ,7344,7308,7307 }, + {6833,6888,6832 ,7343,7344,7307 }, {6888,6938,6937 ,7344,7345,7309 }, + {6887,6888,6937 ,7308,7344,7309 }, {6938,6985,6984 ,7345,7346,7310 }, + {6937,6938,6984 ,7309,7345,7310 }, {7020,7019,6984 ,7347,7311,7310 }, + {6985,7020,6984 ,7346,7347,7310 }, {7020,7060,7059 ,7347,7348,7312 }, + {7019,7020,7059 ,7311,7347,7312 }, {7060,7061,7072 ,7348,7141,7329 }, + {7059,7060,7072 ,7312,7348,7329 }, {7072,7061,5045 ,7329,7141,4214 }, + {5672,5744,7009 ,5324,6090,5325 }, {2074,2247,1071 ,6990,7285,3382 }, + {1071,353,3661 ,3382,7281,789 }, {1165,4541,5917 ,3153,6630,6402 }, + {1165,3446,5487 ,3153,3718,6558 }, {4541,1165,5487 ,6630,3153,6558 }, + {6319,5125,5151 ,6789,5467,1803 }, {4597,5136,4630 ,5541,1817,827 }, + {4598,4631,5137 ,5343,1816,1815 }, {7705,7754,7704 ,7349,7350,3562 }, + {3352,7438,3321 ,2724,7351,2461 }, {2249,6352,6322 ,3751,7352,3531 }, + {3176,4553,6322 ,7353,7315,3531 }, {6352,3176,6322 ,7352,7353,3531 }, + {6376,6298,4553 ,7354,7317,7315 }, {3176,6376,4553 ,7353,7354,7315 }, + {6438,5100,6298 ,7355,7318,7317 }, {6376,6438,6298 ,7354,7355,7317 }, + {5037,41,5100 ,3206,6533,7318 }, {6438,5037,5100 ,7355,3206,7318 }, + {1809,4549,6605 ,878,877,5164 }, {6505,6504,6460 ,7356,7335,7334 }, + {6597,6596,6551 ,7357,7337,7336 }, {7020,7061,7060 ,7347,7141,7348 }, + {5211,5170,5191 ,6071,3683,2900 }, {2452,2246,352 ,7091,591,593 }, + {6919,6946,6918 ,6778,594,6779 }, {351,5086,5069 ,592,6064,6730 }, + {6969,1165,6156 ,7358,3153,6250 }, {5001,2271,4018 ,477,5071,5084 }, + {3336,6324,3335 ,3922,4239,4222 }, {4445,6757,6748 ,3648,3409,3408 }, + {2220,4233,1072 ,6724,6185,6725 }, {7818,4598,5137 ,5309,5343,1815 }, + {7204,7203,7186 ,5120,4366,5370 }, {535,5105,6686 ,2001,5456,750 }, + {7227,7247,7269 ,7359,7360,7361 }, {6956,6376,3176 ,7362,7354,7353 }, + {5137,4597,7788 ,1815,5541,7313 }, {4142,5085,1881 ,6839,3675,7066 }, + {7065,2247,2073 ,5182,7285,5183 }, {7027,7065,7049 ,3175,5182,5454 }, + {6505,6551,6504 ,7356,7336,7335 }, {6597,6638,6596 ,7357,7338,7337 }, + {6678,6734,6693 ,7339,7341,7340 }, {6834,6833,6781 ,7363,7343,7342 }, + {7020,7037,7061 ,7347,7364,7141 }, {5751,7038,5812 ,5878,3832,660 }, + {6949,6947,6948 ,4585,4587,6782 }, {5059,4233,7081 ,1253,6185,7114 }, + {1125,3779,2142 ,5723,6565,4936 }, {4068,3952,3259 ,408,5597,407 }, + {7998,8021,7997 ,7209,7365,7210 }, {5910,6343,6079 ,5121,5122,5580 }, + {6710,1025,5053 ,6968,5299,6848 }, {3281,6310,6309 ,6794,3918,3690 }, + {6481,6378,4900 ,6729,6728,585 }, {7818,5137,7788 ,5309,1815,7313 }, + {7187,7204,7186 ,7330,5120,5370 }, {7908,7907,7858 ,7366,7367,7368 }, + {227,8128,3277 ,2157,959,961 }, {6956,6438,6376 ,7362,7355,7354 }, + {6158,6034,6293 ,6112,6049,7212 }, {6034,6339,6338 ,6049,6048,7331 }, + {6293,6034,6338 ,7212,6049,7331 }, {6339,6375,6374 ,6048,7369,7332 }, + {6338,6339,6374 ,7331,6048,7332 }, {6411,6410,6374 ,7370,7333,7332 }, + {6375,6411,6374 ,7369,7370,7332 }, {6461,6460,6410 ,7371,7334,7333 }, + {6411,6461,6410 ,7370,7371,7333 }, {6506,6505,6460 ,3549,7356,7334 }, + {6461,6506,6460 ,7371,3549,7334 }, {5395,6551,6505 ,5348,7336,7356 }, + {6506,5395,6505 ,3549,5348,7356 }, {5395,6568,6551 ,5348,6717,7336 }, + {5427,6597,6551 ,5811,7357,7336 }, {6568,5427,6551 ,6717,5811,7336 }, + {5463,6638,6597 ,5801,7338,7357 }, {5427,5463,6597 ,5811,5801,7357 }, + {5463,5503,6638 ,5801,7042,7338 }, {5531,6678,6638 ,5783,7339,7338 }, + {5503,5531,6638 ,7042,5783,7338 }, {5566,6734,6678 ,5759,7341,7339 }, + {5531,5566,6678 ,5783,5759,7339 }, {6782,6781,6734 ,7372,7342,7341 }, + {5566,6782,6734 ,5759,7372,7341 }, {6835,6834,6781 ,7373,7363,7342 }, + {6782,6835,6781 ,7372,7373,7342 }, {6835,6833,6834 ,7373,7343,7363 }, + {6889,6888,6833 ,7374,7344,7343 }, {6835,6889,6833 ,7373,7374,7343 }, + {6889,6904,6888 ,7374,7375,7344 }, {6939,6938,6888 ,7376,7345,7344 }, + {6904,6939,6888 ,7375,7376,7344 }, {6939,6940,6938 ,7376,7377,7345 }, + {6986,6985,6938 ,7378,7346,7345 }, {6940,6986,6938 ,7377,7378,7345 }, + {6986,7021,7020 ,7378,7379,7347 }, {6985,6986,7020 ,7346,7378,7347 }, + {7020,7021,7037 ,7347,7379,7364 }, {7021,7062,7061 ,7379,6563,7141 }, + {7037,7021,7061 ,7364,7379,7141 }, {4752,1297,4323 ,5196,4048,4045 }, + {712,3702,6757 ,3650,1303,3409 }, {4010,4009,3968 ,4343,3870,3848 }, + {3529,1941,3371 ,3837,1034,3708 }, {6155,6970,6969 ,3726,3688,7358 }, + {6156,6155,6969 ,6250,3726,7358 }, {6970,4831,1165 ,3688,3154,3153 }, + {6969,6970,1165 ,7358,3688,3153 }, {978,4965,4966 ,1620,784,4646 }, + {1217,5059,2220 ,6399,1253,6724 }, {3281,2271,2212 ,6794,5071,476 }, + {2073,2247,2074 ,5183,7285,6990 }, {5027,6684,2272 ,5516,1161,1201 }, + {5136,4597,5137 ,1817,5541,1815 }, {4803,468,505 ,4776,1369,986 }, + {7103,7102,6283 ,6455,1240,4298 }, {6323,2249,6531 ,1451,3751,2280 }, + {4551,6352,2249 ,7380,7352,3751 }, {6323,4551,2249 ,1451,7380,3751 }, + {1989,3176,6352 ,3016,7353,7352 }, {4551,1989,6352 ,7380,3016,7352 }, + {5012,6956,3176 ,5212,7362,7353 }, {1989,5012,3176 ,3016,5212,7353 }, + {6417,6438,6956 ,762,7355,7362 }, {5012,6417,6956 ,5212,762,7362 }, + {6417,5388,5037 ,762,5428,3206 }, {6438,6417,5037 ,7355,762,3206 }, + {5037,5388,6317 ,3206,5428,3207 }, {353,256,2212 ,7281,7269,476 }, + {5211,6433,5210 ,6071,6691,6033 }, {6165,7061,7062 ,4219,7141,6563 }, + {5604,6782,5566 ,7213,7372,5759 }, {6940,6939,6904 ,7377,7376,7375 }, + {6889,6940,6904 ,7374,7377,7375 }, {835,3984,5527 ,3030,6017,5977 }, + {4545,2274,4980 ,586,893,241 }, {3661,353,2212 ,789,7281,476 }, + {256,3281,2212 ,7269,6794,476 }, {6465,5009,4985 ,6927,6803,2703 }, + {6968,7008,6967 ,595,3176,4629 }, {5940,6176,6715 ,1625,1624,6929 }, + {3321,3353,3352 ,2461,2424,2724 }, {2074,1394,7047 ,6990,6989,6853 }, + {5974,3079,4574 ,6474,6540,6477 }, {7660,7707,7659 ,7381,7382,7383 }, + {6792,6791,6738 ,5056,6900,5057 }, {6009,5514,6293 ,6129,6128,7212 }, + {5744,5718,5756 ,6090,6126,6076 }, {6531,6509,6323 ,2280,1452,1451 }, + {4586,2163,911 ,5257,243,1159 }, {6946,6916,6917 ,594,596,5515 }, + {6411,6462,6461 ,7370,7384,7371 }, {6462,6506,6461 ,7384,3549,7371 }, + {5715,7050,7028 ,4076,6967,3587 }, {3779,1125,4648 ,6565,5723,5722 }, + {2871,3284,257 ,3649,6720,6067 }, {5106,5120,7096 ,5926,5857,4337 }, + {4392,2094,2341 ,844,5570,845 }, {5041,4990,6065 ,6799,7135,6616 }, + {4803,5267,4777 ,4776,4647,3917 }, {5604,6835,6782 ,7213,7373,7372 }, + {5669,6905,5710 ,5553,2506,5254 }, {2343,2210,1074 ,2685,2510,2715 }, + {158,5027,4755 ,5258,5516,6770 }, {8007,2250,7957 ,3854,3716,7385 }, + {7050,5715,5755 ,6967,4076,4047 }, {1947,2235,1881 ,6723,6870,7066 }, + {5405,6656,6618 ,5518,5517,6910 }, {4552,4807,3935 ,7244,6449,4346 }, + {6657,4990,5041 ,6828,7135,6799 }, {4043,2956,2798 ,419,399,420 }, + {5611,6949,6920 ,6177,4585,6783 }, {7262,7287,7241 ,4335,3459,4465 }, + {7769,7744,7794 ,5535,6101,5749 }, {4021,3733,3936 ,1712,5610,5599 }, + {6323,1989,4551 ,1451,3016,7380 }, {4983,4569,2158 ,480,1344,1160 }, + {6949,6948,6920 ,4585,6782,6783 }, {6918,6946,6917 ,6779,594,5515 }, + {4229,381,4246 ,4877,6450,3665 }, {6092,5940,3714 ,1623,1625,3970 }, + {6483,2164,4961 ,3584,3583,2905 }, {5889,6066,6375 ,1587,1455,7369 }, + {6339,5889,6375 ,6048,1587,7369 }, {6090,6411,6375 ,4897,7370,7369 }, + {6066,6090,6375 ,1455,4897,7369 }, {6090,6260,6462 ,4897,3397,7384 }, + {6411,6090,6462 ,7370,4897,7384 }, {5363,6506,6462 ,3269,3549,7384 }, + {6260,5363,6462 ,3397,3269,7384 }, {4530,4209,2239 ,1376,6269,1377 }, + {5120,5106,5121 ,5857,5926,5855 }, {5605,5647,5604 ,5761,5333,7213 }, + {5647,6835,5604 ,5333,7373,7213 }, {5647,6852,6835 ,5333,5335,7373 }, + {6890,6889,6835 ,5334,7374,7373 }, {6852,6890,6835 ,5335,5334,7373 }, + {5750,6940,6889 ,5156,7377,7374 }, {6890,5750,6889 ,5334,5156,7374 }, + {5774,6986,6940 ,5720,7378,7377 }, {5750,5774,6940 ,5156,5720,7377 }, + {5807,7021,6986 ,4769,7379,7378 }, {5774,5807,6986 ,5720,4769,7378 }, + {7021,5807,7062 ,7379,4769,6563 }, {5807,5839,7062 ,4769,4768,6563 }, + {5839,5840,7062 ,4768,4770,6563 }, {5840,4271,1464 ,4770,5767,5768 }, + {7062,5840,1464 ,6563,4770,5768 }, {5668,5626,5669 ,5912,1460,5553 }, + {5076,4200,158 ,6607,6583,5258 }, {4983,6341,2808 ,480,479,522 }, + {7050,5755,7067 ,6967,4047,3585 }, {4978,6413,6155 ,178,112,3726 }, + {4567,4978,6155 ,6655,178,3726 }, {6413,3340,6970 ,112,114,3688 }, + {7733,7758,7707 ,7386,7387,7382 }, {7708,7733,7707 ,7388,7386,7382 }, + {7733,7780,7758 ,7386,7389,7387 }, {7834,7884,7833 ,7390,7391,7392 }, + {7523,7581,7522 ,7393,7394,7395 }, {7518,7546,7545 ,7396,7397,7398 }, + {7629,7684,7683 ,7399,7400,5267 }, {7656,7629,7683 ,7401,7399,5267 }, + {2328,2282,2283 ,3664,3663,3687 }, {2281,2280,8054 ,3626,3613,7402 }, + {2278,2277,4572 ,3495,3522,5949 }, {7956,8004,7981 ,7403,7404,4252 }, + {7173,7172,7165 ,7264,7405,7265 }, {7574,7656,7603 ,7406,7401,5266 }, + {2250,8006,7957 ,3716,7407,7385 }, {7907,7932,7906 ,7367,7408,7409 }, + {4803,505,5290 ,4776,986,5247 }, {7827,7878,7826 ,7410,7411,1616 }, + {7852,7901,7851 ,7412,7413,6625 }, {7116,7141,7140 ,7262,7414,7415 }, + {7263,7312,7262 ,627,4333,4335 }, {402,7690,448 ,5349,6466,1172 }, + {7540,7574,7603 ,5709,7406,5266 }, {7629,7656,7574 ,7399,7401,7406 }, + {8021,8037,7997 ,7365,4528,7210 }, {7604,7605,7657 ,7416,5239,7417 }, + {7726,7774,7725 ,7418,7419,7420 }, {3351,7497,7438 ,1444,7421,7351 }, + {3352,3351,7438 ,2724,1444,7351 }, {7297,7273,7274 ,7422,3467,7423 }, + {3298,3830,4518 ,7424,5882,1281 }, {7678,7677,7623 ,7425,7426,7427 }, + {7399,7425,7398 ,7428,3995,7429 }, {7379,7378,7319 ,7430,7431,7432 }, + {7252,7297,7274 ,3437,7422,7423 }, {7327,7326,7273 ,7433,7434,3467 }, + {7297,7327,7273 ,7422,7433,3467 }, {7327,7355,7326 ,7433,7435,7434 }, + {7403,7457,7486 ,6588,1078,7436 }, {7457,7539,7538 ,1078,1080,2014 }, + {7856,7905,7855 ,4635,7437,6545 }, {7626,7625,7624 ,7438,7139,7439 }, + {7405,7461,7460 ,7440,7441,7442 }, {7431,7405,7460 ,7443,7440,7442 }, + {7461,7489,7460 ,7441,7444,7442 }, {7489,7519,7518 ,7444,7445,7396 }, + {7460,7489,7518 ,7442,7444,7396 }, {7757,7758,7779 ,7446,7387,7447 }, + {7833,7832,7809 ,7392,7448,7449 }, {7707,7758,7757 ,7382,7387,7446 }, + {7732,7707,7757 ,7450,7382,7446 }, {4022,3534,6836 ,3503,942,852 }, + {7320,7380,7379 ,7282,7451,7430 }, {7833,7883,7832 ,7392,7452,7448 }, + {7519,7547,7546 ,7445,7453,7397 }, {7518,7519,7546 ,7396,7445,7397 }, + {7771,7770,7721 ,4336,4272,7454 }, {7547,7607,7546 ,7453,7455,7397 }, + {7661,7660,7607 ,7456,7381,7455 }, {7606,7659,7633 ,7457,7383,7458 }, + {7726,7725,7681 ,7418,7420,7459 }, {7622,7676,7650 ,7460,7461,7237 }, + {7606,7633,7578 ,7457,7458,7462 }, {7661,7708,7660 ,7456,7388,7381 }, + {7480,7511,7454 ,7463,6677,3996 }, {7781,7780,7733 ,7464,7389,7386 }, + {7325,7382,7381 ,7465,7466,7467 }, {7885,7884,7834 ,7468,7391,7390 }, + {6322,8112,6897 ,3531,3530,54 }, {2048,2096,3875 ,5461,788,5845 }, + {7885,7909,7884 ,7468,6603,7391 }, {2262,7936,7884 ,3359,5528,7391 }, + {7909,2262,7884 ,6603,3359,7391 }, {7382,7405,7431 ,7466,7440,7443 }, + {7582,7581,7523 ,7469,7394,7393 }, {7438,7497,7437 ,7351,7421,5938 }, + {7273,7250,7251 ,3467,3418,2671 }, {7497,7496,7437 ,7421,7470,5938 }, + {2526,2525,2485 ,2636,2937,2815 }, {7273,7296,7250 ,3467,7471,3418 }, + {7684,7705,7683 ,7400,7349,5267 }, {7705,7704,7683 ,7349,3562,5267 }, + {6784,2278,4572 ,7472,3495,5949 }, {7110,7133,7108 ,1990,1989,4111 }, + {7355,7405,7382 ,7435,7440,7466 }, {7582,7637,7581 ,7469,7473,7394 }, + {7714,4744,7983 ,4468,4424,4361 }, {5026,8062,7983 ,4225,3272,4361 }, + {5643,5660,5659 ,4617,3054,3053 }, {7698,7720,7747 ,333,4271,331 }, + {7613,7634,7612 ,5106,7474,5107 }, {7516,7575,7574 ,5708,7475,7406 }, + {6784,4572,8039 ,7472,5949,7476 }, {7274,7273,7252 ,7423,3467,3437 }, + {3824,4744,7714 ,3462,4424,4468 }, {3824,1879,4744 ,3462,1283,4424 }, + {3976,3830,3298 ,919,5882,7424 }, {7398,7425,7397 ,7429,3995,1186 }, + {7253,7297,7252 ,1855,7422,3437 }, {7356,7355,7327 ,7477,7435,7433 }, + {2487,2486,7615 ,4427,2814,2816 }, {6316,6311,2162 ,4435,1097,3278 }, + {3824,3298,1879 ,3462,7424,1283 }, {7935,2290,7960 ,5527,1982,3505 }, + {7461,7490,7489 ,7441,7478,7444 }, {7489,7490,7519 ,7444,7478,7445 }, + {7608,7607,7547 ,7479,7455,7453 }, {2250,2285,8006 ,3716,3701,7407 }, + {7881,7907,7906 ,7480,7367,7409 }, {7709,7708,7661 ,7481,7388,7456 }, + {7262,7311,7287 ,4335,4334,3459 }, {7693,3298,3824 ,724,7424,3462 }, + {3298,7693,3976 ,7424,724,919 }, {2292,7936,2262 ,1983,5528,3359 }, + {7734,7733,7708 ,7482,7386,7388 }, {7709,7734,7708 ,7481,7482,7388 }, + {7835,7834,7780 ,7483,7390,7389 }, {7781,7835,7780 ,7464,7483,7389 }, + {7229,7270,7247 ,2556,2555,7360 }, {1788,145,113 ,676,810,677 }, + {3290,3289,7196 ,1204,2056,2401 }, {7517,7516,7459 ,7484,5708,7485 }, + {7883,7934,7882 ,7452,7486,7487 }, {3294,3293,7110 ,3895,1988,1990 }, + {7150,7149,3293 ,7488,7263,1988 }, {7150,7173,7149 ,7488,7264,7263 }, + {7637,7713,7692 ,7473,7489,7490 }, {7251,7250,3288 ,2671,3418,2058 }, + {7960,2290,162 ,3505,1982,2822 }, {7224,7223,7189 ,7491,7492,7493 }, + {6276,7107,6277 ,4316,7494,4387 }, {6282,7103,6283 ,6617,6455,4298 }, + {7713,7736,7692 ,7489,6752,7490 }, {7809,7832,7808 ,7449,7448,7495 }, + {7266,7315,7265 ,6769,7496,7497 }, {7230,7229,7210 ,7498,2556,7499 }, + {7211,7210,7173 ,7500,7499,7264 }, {7211,7230,7210 ,7500,7498,7499 }, + {7321,7353,7320 ,7501,7502,7282 }, {7188,7187,7161 ,7503,7330,2559 }, + {7631,7658,7605 ,7504,7505,5239 }, {7997,8037,7996 ,7210,4528,7328 }, + {7266,7265,7245 ,6769,7497,7506 }, {7935,7960,7933 ,5527,3505,7507 }, + {7931,7981,7930 ,7508,4252,4278 }, {7804,7803,7775 ,4634,4636,7509 }, + {7753,7804,7775 ,3643,4634,7509 }, {7779,7809,7808 ,7447,7449,7495 }, + {7757,7779,7808 ,7446,7447,7495 }, {7727,7775,7726 ,3563,7509,7418 }, + {7934,7933,7882 ,7486,7507,7487 }, {8025,8024,7980 ,7510,7511,4253 }, + {7383,7355,7382 ,7512,7435,7466 }, {7321,7320,7271 ,7501,7282,2554 }, + {7980,8003,7955 ,4253,3776,4254 }, {7855,7904,7854 ,6545,7513,7514 }, + {7353,7380,7320 ,7502,7451,7282 }, {7541,7516,7517 ,7515,5708,7484 }, + {7541,7575,7516 ,7515,7475,5708 }, {7934,7935,7933 ,7486,5527,7507 }, + {7713,2451,7736 ,7489,2102,6752 }, {7732,7757,7731 ,7450,7446,7516 }, + {8116,3976,8129 ,172,919,723 }, {7707,7732,7731 ,7382,7450,7516 }, + {7535,7534,7480 ,7517,7314,7463 }, {7685,7707,7731 ,7518,7382,7516 }, + {7755,7805,7754 ,7519,7520,7350 }, {7707,7685,7659 ,7382,7518,7383 }, + {1274,6108,1275 ,1687,6975,1688 }, {7980,8024,8003 ,4253,7511,3776 }, + {2281,2326,2280 ,3626,3588,3613 }, {7164,7195,7171 ,7521,7522,4306 }, + {7209,7228,7227 ,7523,7524,7359 }, {7228,7247,7227 ,7524,7360,7359 }, + {7855,7854,7803 ,6545,7514,4636 }, {7574,7575,7629 ,7406,7475,7399 }, + {7805,7804,7754 ,7520,4634,7350 }, {7578,7633,7577 ,7462,7458,7525 }, + {7325,7381,7354 ,7465,7467,7526 }, {7755,7754,7705 ,7519,7350,7349 }, + {8005,8004,7956 ,7527,7404,7403 }, {7540,7603,7628 ,5709,5266,7528 }, + {7798,7825,7771 ,4728,3768,4336 }, {7093,7107,6276 ,4318,7494,4316 }, + {7093,7119,7107 ,4318,7529,7494 }, {7119,7147,7146 ,7529,6937,7530 }, + {7683,7682,7655 ,5267,3564,5268 }, {7880,7879,7857 ,7531,7532,7533 }, + {7604,7657,7630 ,7416,7417,7534 }, {7275,7253,7254 ,1854,1855,450 }, + {7298,7297,7253 ,7535,7422,1855 }, {2285,2283,8004 ,3701,3687,7404 }, + {8005,2285,8004 ,7527,3701,7404 }, {7254,7253,6348 ,450,1855,1607 }, + {5558,5569,5599 ,2302,2075,2213 }, {7603,7655,7628 ,5266,5268,7528 }, + {7427,7426,7399 ,7536,7537,7428 }, {7275,7298,7253 ,1854,7535,1855 }, + {7298,7327,7297 ,7535,7433,7422 }, {7406,7405,7355 ,7538,7440,7435 }, + {7356,7406,7355 ,7477,7538,7435 }, {7490,7547,7519 ,7478,7453,7445 }, + {7662,7661,7607 ,7539,7456,7455 }, {7608,7662,7607 ,7479,7539,7455 }, + {1314,7638,1315 ,1409,7540,1627 }, {1877,3802,3948 ,282,4475,280 }, + {3948,3802,4296 ,280,4475,6023 }, {7720,7698,7648 ,4271,333,3952 }, + {7655,7682,7628 ,5268,3564,7528 }, {68,4368,3530 ,4087,4148,7541 }, + {7481,7535,7480 ,7542,7517,7463 }, {7734,7781,7733 ,7482,7464,7386 }, + {7537,7601,7572 ,7543,7544,5124 }, {7886,7885,7834 ,6561,7468,7390 }, + {7835,7886,7834 ,7483,6561,7390 }, {7886,7909,7885 ,6561,6603,7468 }, + {7884,7935,7883 ,7391,5527,7452 }, {7221,7241,7204 ,7545,4465,5120 }, + {3976,8116,5483 ,919,172,171 }, {7900,7951,7926 ,6922,7546,6923 }, + {1877,3825,3802 ,282,7547,4475 }, {59,2553,395 ,967,2716,966 }, + {7901,7900,7851 ,7413,6922,6625 }, {2283,8041,8004 ,3687,7548,7404 }, + {7753,7775,7727 ,3643,7509,3563 }, {7497,7523,7496 ,7421,7393,7470 }, + {7878,7877,7826 ,7411,7549,1616 }, {7459,7516,7458 ,7485,5708,7550 }, + {7900,7899,7849 ,6922,4116,7551 }, {7515,7487,7540 ,1079,5707,5709 }, + {7648,7699,7720 ,3952,3951,4271 }, {7149,7165,7133 ,7263,7265,1989 }, + {8107,2731,872 ,658,659,2962 }, {2410,3400,1877 ,1,3659,282 }, + {1315,1355,1314 ,1627,1408,1409 }, {7907,7958,7932 ,7367,3855,7408 }, + {7454,7453,7397 ,3996,1187,1186 }, {59,2689,2553 ,967,2821,2716 }, + {209,342,1178 ,4499,4542,206 }, {7328,7327,7298 ,7552,7433,7535 }, + {7275,7328,7298 ,1854,7552,7535 }, {7686,7659,7685 ,7553,7383,7518 }, + {7328,7356,7327 ,7552,7477,7433 }, {7432,7405,7406 ,7554,7440,7538 }, + {7633,7659,7632 ,7458,7383,7555 }, {7659,7686,7632 ,7383,7553,7555 }, + {7537,7572,7514 ,7543,5124,3610 }, {7601,7600,7572 ,7544,7556,5124 }, + {7432,7461,7405 ,7554,7441,7440 }, {7548,7547,7490 ,7557,7453,7478 }, + {7609,7608,7547 ,7558,7479,7453 }, {7548,7609,7547 ,7557,7558,7453 }, + {7663,7662,7608 ,7559,7539,7479 }, {7609,7663,7608 ,7558,7559,7479 }, + {7663,7661,7662 ,7559,7456,7539 }, {7710,7709,7661 ,7560,7481,7456 }, + {7663,7710,7661 ,7559,7560,7456 }, {7735,7734,7709 ,7561,7482,7481 }, + {7710,7735,7709 ,7560,7561,7481 }, {7295,7325,7354 ,7562,7465,7526 }, + {2410,1877,3670 ,1,282,281 }, {3400,1538,1451 ,3659,0,175 }, + {3400,3786,1877 ,3659,4074,282 }, {1877,3786,3825 ,282,4074,7547 }, + {3802,3825,1537 ,4475,7547,4473 }, {976,977,1029 ,782,3931,3930 }, + {7849,7899,7875 ,7551,4116,4118 }, {7782,7781,7734 ,7563,7464,7482 }, + {7836,7835,7781 ,7564,7483,7464 }, {7782,7836,7781 ,7563,7564,7464 }, + {7296,7295,7250 ,7471,7562,3418 }, {7836,7886,7835 ,7564,6561,7483 }, + {7721,7720,7676 ,7454,4271,7461 }, {2263,2295,2294 ,3080,3160,3081 }, + {7883,7882,7832 ,7452,7487,7448 }, {3296,3295,2265 ,4159,7565,2662 }, + {7637,7666,7636 ,7473,7566,7567 }, {7229,7247,7210 ,2556,7360,7499 }, + {7801,7853,7800 ,1618,1617,7568 }, {7110,7108,7109 ,1990,4111,4112 }, + {7802,7801,7751 ,6566,1618,4332 }, {7479,7508,7478 ,4423,6196,1402 }, + {3786,1934,3825 ,4074,4620,7547 }, {1934,3514,1537 ,4620,7569,4473 }, + {3825,1934,1537 ,7547,4620,4473 }, {3514,7764,1537 ,7569,7570,4473 }, + {1537,7764,1745 ,4473,7570,6233 }, {7764,7739,4207 ,7570,7571,4247 }, + {1745,7764,4207 ,6233,7570,4247 }, {4961,2164,3191 ,2905,3583,2903 }, + {7620,7646,7645 ,5033,5191,5034 }, {7457,7487,7515 ,1078,5707,1079 }, + {7429,7487,7457 ,7572,5707,1078 }, {3351,7523,7497 ,1444,7393,7421 }, + {2283,2282,2281 ,3687,3663,3626 }, {7296,7325,7295 ,7471,7465,7562 }, + {7106,7105,7092 ,7573,5279,3968 }, {7879,7905,7856 ,7532,7437,4635 }, + {7484,7483,7427 ,3612,7574,7536 }, {7289,7347,7288 ,7575,7576,7577 }, + {7513,7512,7483 ,3611,5203,7574 }, {7373,7399,7347 ,4989,7428,7576 }, + {7456,7455,7400 ,7578,7579,7580 }, {7754,7804,7753 ,7350,4634,3643 }, + {7320,7379,7319 ,7282,7430,7432 }, {7465,7464,7436 ,7581,4246,4245 }, + {7627,7626,7572 ,7582,7438,5124 }, {7313,7346,7312 ,7583,7584,4333 }, + {7488,7517,7459 ,7585,7484,7485 }, {3514,1934,7764 ,7569,4620,7570 }, + {937,975,974 ,5067,4266,5366 }, {8035,2275,8034 ,6675,3152,7586 }, + {7430,7429,7457 ,7587,7572,1078 }, {7209,7227,7246 ,7523,7359,3772 }, + {7429,7430,7457 ,7572,7587,1078 }, {7221,7263,7241 ,7545,627,4465 }, + {7345,7397,7372 ,7588,1186,4373 }, {7736,450,7691 ,6752,1653,4419 }, + {7582,7667,7637 ,7469,7589,7473 }, {7667,7668,7637 ,7589,7590,7473 }, + {7996,7995,7949 ,7328,4135,7591 }, {7377,7430,7429 ,7592,7587,7572 }, + {7318,7292,7352 ,7593,7594,7595 }, {7268,7292,7318 ,3773,7594,7593 }, + {7227,7269,7268 ,7359,7361,3773 }, {7246,7227,7268 ,3772,7359,3773 }, + {7269,7292,7268 ,7361,7594,3773 }, {7637,7636,7580 ,7473,7567,7596 }, + {7581,7637,7580 ,7394,7473,7596 }, {7230,7271,7229 ,7498,2554,2556 }, + {6346,7275,7253 ,4883,1854,1855 }, {7357,7356,7328 ,7597,7477,7552 }, + {7832,7882,7859 ,7448,7487,7598 }, {7646,7620,7675 ,5191,5033,5192 }, + {1934,2350,7764 ,4620,7599,7570 }, {7764,2350,7739 ,7570,7599,7571 }, + {3338,3530,4368 ,4147,7541,4148 }, {4445,3284,2871 ,3648,6720,3649 }, + {7422,7477,7449 ,1404,1403,7600 }, {7778,7731,7807 ,7601,7516,7602 }, + {7832,7859,7831 ,7448,7598,7603 }, {7731,7757,7807 ,7516,7446,7602 }, + {7357,7406,7356 ,7597,7538,7477 }, {7491,7490,7461 ,7604,7478,7441 }, + {7432,7491,7461 ,7554,7604,7441 }, {7549,7548,7490 ,7605,7557,7478 }, + {7610,7609,7548 ,7606,7558,7557 }, {7549,7610,7548 ,7605,7606,7557 }, + {7960,7959,7933 ,3505,7607,7507 }, {7516,7574,7540 ,5708,7406,5709 }, + {7689,7690,402 ,6444,6466,5349 }, {7225,7245,7207 ,1672,7506,7608 }, + {7225,7207,7194 ,1672,7608,1670 }, {7291,7315,7266 ,3774,7496,6769 }, + {7291,7349,7315 ,3774,7609,7496 }, {7163,7164,7171 ,5460,7521,4306 }, + {7174,7173,7150 ,7610,7264,7488 }, {7996,8036,7995 ,7328,7327,4135 }, + {7664,7663,7609 ,7611,7559,7558 }, {7610,7664,7609 ,7606,7611,7558 }, + {7806,7805,7755 ,7612,7520,7519 }, {6796,6898,8063 ,2617,1946,465 }, + {2350,7813,7739 ,7599,7613,7571 }, {7813,3338,7739 ,7613,4147,7571 }, + {4741,3634,3530 ,5508,5507,7541 }, {7808,7832,7831 ,7495,7448,7603 }, + {7806,7829,7805 ,7612,7614,7520 }, {7633,7632,7576 ,7458,7555,7615 }, + {3297,7734,7735 ,4203,7482,7561 }, {7710,3297,7735 ,7560,4203,7561 }, + {7783,7782,7734 ,7616,7563,7482 }, {3297,7783,7734 ,4203,7616,7482 }, + {7295,7354,7324 ,7562,7526,7617 }, {2265,7836,7782 ,2662,7564,7563 }, + {7316,7317,7350 ,5948,5947,7618 }, {7629,7657,7705 ,7399,7417,7349 }, + {7959,7960,2306 ,7607,3505,6405 }, {8039,4572,8038 ,7476,5949,4121 }, + {7401,7456,7400 ,7619,7578,7580 }, {7245,7265,7244 ,7506,7497,7620 }, + {7245,7244,7207 ,7506,7620,7608 }, {7132,7164,7163 ,7621,7521,5460 }, + {7148,7132,7163 ,6936,7621,5460 }, {7783,2265,7782 ,7616,2662,7563 }, + {7250,7295,7249 ,3418,7562,7622 }, {7849,7875,7797 ,7551,4118,3506 }, + {7932,7931,7906 ,7408,7508,7409 }, {156,3691,1934 ,5539,7623,4620 }, + {1934,3691,2350 ,4620,7623,7599 }, {7763,7838,3338 ,7624,7625,4147 }, + {7813,7763,3338 ,7613,7624,4147 }, {4911,4741,3530 ,7626,5508,7541 }, + {936,937,974 ,5234,5067,5366 }, {7289,7288,7242 ,7575,7577,7627 }, + {7829,7857,7805 ,7614,7533,7520 }, {2265,7886,7836 ,2662,6561,7564 }, + {2907,3791,2941 ,111,3739,109 }, {7668,7713,7637 ,7590,7489,7473 }, + {7604,7575,7541 ,7416,7475,7515 }, {7575,7604,7629 ,7475,7416,7399 }, + {7604,7630,7629 ,7416,7534,7399 }, {7630,7657,7629 ,7534,7417,7399 }, + {7118,7119,7145 ,7628,7529,7629 }, {7386,7412,7385 ,5512,3765,7630 }, + {7206,7243,7224 ,7631,7632,7491 }, {7206,7224,7190 ,7631,7491,7633 }, + {7191,7206,7190 ,7634,7631,7633 }, {7290,7289,7243 ,7635,7575,7632 }, + {7290,7348,7289 ,7635,4990,7575 }, {7538,7537,7485 ,2014,7543,7636 }, + {7654,7653,7601 ,7637,7638,7544 }, {8036,8037,4589 ,7327,4528,776 }, + {156,3740,3691 ,5539,7639,7623 }, {3691,3740,2350 ,7623,7639,7599 }, + {3740,7763,7813 ,7639,7624,7613 }, {2350,3740,7813 ,7599,7639,7613 }, + {7763,7122,7838 ,7624,7640,7625 }, {7838,7122,3338 ,7625,7640,4147 }, + {7122,8046,3530 ,7640,7641,7541 }, {3338,7122,3530 ,4147,7640,7541 }, + {3737,1350,791 ,7642,509,7643 }, {7957,7956,7931 ,7385,7403,7508 }, + {7748,7771,7722 ,7644,4336,7645 }, {7932,7957,7931 ,7408,7385,7508 }, + {7577,7633,7576 ,7525,7458,7615 }, {7995,8036,4588 ,4135,7327,775 }, + {7581,7580,7522 ,7394,7596,7395 }, {8041,2283,2281 ,7548,3687,3626 }, + {7905,7904,7855 ,7437,7513,6545 }, {7690,7689,7613 ,6466,6444,5106 }, + {7635,7690,7613 ,7646,6466,5106 }, {7193,7192,7162 ,7647,7648,7649 }, + {7147,7193,7162 ,6937,7647,7649 }, {7222,7263,7221 ,628,627,7545 }, + {7117,7116,7089 ,7650,7262,5608 }, {7090,7117,7089 ,4080,7650,5608 }, + {7143,7170,7190 ,7651,7652,7633 }, {7170,7191,7190 ,7652,7634,7633 }, + {7144,7170,7143 ,7653,7652,7651 }, {7131,7144,7143 ,7654,7653,7651 }, + {7130,7131,7143 ,6823,7654,7651 }, {7119,7118,7107 ,7529,7628,7494 }, + {7553,7579,7552 ,7655,7656,5546 }, {7553,7613,7579 ,7655,5106,7656 }, + {7193,7194,7192 ,7647,1670,7648 }, {7981,8004,7980 ,4252,7404,4253 }, + {7521,7580,7520 ,7657,7596,7658 }, {3740,1413,7763 ,7639,7659,7624 }, + {7122,3423,8046 ,7640,7660,7641 }, {3423,8046,3530 ,7660,7641,7541 }, + {8046,3423,3530 ,7641,7660,7541 }, {8046,3633,4911 ,7641,7661,7626 }, + {3530,8046,4911 ,7541,7641,7626 }, {6869,4741,4911 ,7662,5508,7626 }, + {3633,6869,4911 ,7661,7662,7626 }, {1350,3737,5571 ,509,7642,4616 }, + {6869,4956,4741 ,7662,5381,5508 }, {7162,7192,7170 ,7649,7648,7652 }, + {7749,7748,7723 ,7663,7644,7664 }, {7748,7799,7771 ,7644,4727,4336 }, + {2281,8054,8024 ,3626,7402,7511 }, {6658,5570,3121 ,7665,508,2076 }, + {7272,7293,7322 ,7666,6473,75 }, {7654,7703,7653 ,7637,7667,7638 }, + {7774,7773,7725 ,7419,7668,7420 }, {7207,7206,7191 ,7608,7631,7634 }, + {7346,7345,7312 ,7584,7588,4333 }, {3615,5700,5681 ,201,4501,2934 }, + {3530,3634,68 ,7541,5507,4087 }, {7522,7580,7521 ,7395,7596,7657 }, + {7148,7163,7147 ,6936,5460,6937 }, {7171,7195,7209 ,4306,7522,7523 }, + {7851,7900,7850 ,6625,6922,3769 }, {7248,7293,7272 ,73,6473,7666 }, + {7205,7204,7188 ,7669,5120,7503 }, {7602,7628,7601 ,5867,7528,7544 }, + {7428,7427,7373 ,7670,7536,4989 }, {7374,7428,7373 ,4988,7670,4989 }, + {7194,7207,7192 ,1670,7608,7648 }, {7465,7521,7464 ,7581,7657,4246 }, + {7854,7878,7827 ,7514,7411,7410 }, {7580,7553,7520 ,7596,7655,7658 }, + {7579,7613,7552 ,7656,5106,5546 }, {3740,156,4264 ,7639,5539,5540 }, + {1413,6477,7763 ,7659,7671,7624 }, {7763,6477,7122 ,7624,7671,7640 }, + {7122,6477,3423 ,7640,7671,7660 }, {3423,3633,8046 ,7660,7661,7641 }, + {5039,6869,3633 ,7672,7662,7661 }, {2733,4956,6869 ,5367,5381,7662 }, + {5039,2733,6869 ,7672,5367,7662 }, {5046,1081,2993 ,5232,3745,3744 }, + {7680,7679,7625 ,7673,7674,7139 }, {7118,7145,7144 ,7628,7629,7653 }, + {7651,7680,7625 ,7675,7673,7139 }, {7858,7907,7881 ,7368,7367,7480 }, + {8054,8053,8024 ,7402,7676,7511 }, {7511,7533,7510 ,6677,7039,1873 }, + {7692,7736,7691 ,7490,6752,4419 }, {7272,7322,7321 ,7666,75,7501 }, + {3843,935,3933 ,7677,5264,7678 }, {6552,2278,6784 ,7679,3495,7472 }, + {7325,7383,7382 ,7465,7512,7466 }, {2278,2243,2242 ,3495,1993,2551 }, + {5681,2453,5659 ,2934,42,3053 }, {7192,7207,7191 ,7648,7608,7634 }, + {7462,7494,7435 ,4310,4309,6244 }, {795,1511,1344 ,302,4921,300 }, + {1511,795,4377 ,4921,302,3090 }, {7426,7425,7399 ,7537,3995,7428 }, + {7347,7346,7313 ,7576,7584,7583 }, {7189,7205,7188 ,7493,7669,7503 }, + {7288,7347,7313 ,7577,7576,7583 }, {7131,7130,7105 ,7654,6823,5279 }, + {7313,7312,7263 ,7583,4333,627 }, {7680,7701,7679 ,7673,3515,7674 }, + {7224,7242,7223 ,7491,7627,7492 }, {7437,7496,7465 ,5938,7470,7581 }, + {7496,7522,7465 ,7470,7395,7581 }, {4897,7837,4264 ,5442,5967,5540 }, + {7837,3912,3740 ,5967,7680,7639 }, {3740,3912,1413 ,7639,7680,7659 }, + {6477,7669,3423 ,7671,7681,7660 }, {3423,7861,3633 ,7660,7682,7661 }, + {7385,7412,7384 ,7630,3765,7683 }, {8136,8063,8140 ,2449,465,2269 }, + {7553,7552,7495 ,7655,5546,4308 }, {7520,7553,7495 ,7658,7655,4308 }, + {7878,7929,7877 ,7411,7684,7549 }, {7626,7624,7570 ,7438,7439,5204 }, + {8060,3783,8037 ,7685,5311,4528 }, {8043,5557,5558 ,6397,756,2302 }, + {7205,7221,7204 ,7669,7545,5120 }, {3326,6346,6347 ,826,4883,451 }, + {8021,8051,8037 ,7365,6172,4528 }, {2280,2243,8053 ,3613,1993,7676 }, + {7571,7626,7570 ,5126,7438,5204 }, {2484,2485,2524 ,3499,2815,3395 }, + {6898,6870,2205 ,1946,2616,2874 }, {7599,7626,7571 ,5125,7438,5126 }, + {7513,7571,7512 ,3611,5126,5203 }, {7651,7625,7626 ,7675,7139,7438 }, + {7223,7222,7189 ,7492,628,7493 }, {2141,8101,8091 ,2015,383,2167 }, + {3862,7810,1494 ,550,552,1083 }, {14,302,223 ,1986,622,1985 }, + {91,14,223 ,1984,1986,1985 }, {7384,7359,7332 ,7683,7686,6685 }, + {7333,7384,7332 ,4353,7683,6685 }, {7411,7410,7359 ,3767,3766,7686 }, + {7384,7411,7359 ,7683,3767,7686 }, {7232,7248,7231 ,2124,73,6944 }, + {7691,7690,7666 ,4419,6466,7566 }, {7223,7264,7222 ,7492,626,628 }, + {7288,7263,7264 ,7577,627,626 }, {7798,7851,7825 ,4728,6625,3768 }, + {7850,7849,7824 ,3769,7551,3770 }, {7999,8022,7998 ,4583,4123,7209 }, + {7751,7750,7701 ,4332,7687,3515 }, {8040,8039,8001 ,7688,7476,7689 }, + {8002,8040,8001 ,3777,7688,7689 }, {1413,3912,6477 ,7659,7680,7671 }, + {7669,3863,7861 ,7681,7690,7682 }, {3423,7669,7861 ,7660,7681,7682 }, + {3863,3538,3633 ,7690,7691,7661 }, {7861,3863,3633 ,7682,7690,7661 }, + {3633,3538,5039 ,7661,7691,7672 }, {5039,4573,2733 ,7672,5314,5367 }, + {4303,85,166 ,3728,2504,3729 }, {7876,7927,7902 ,1725,1716,7692 }, + {7958,8007,7957 ,3855,3854,7385 }, {7706,7730,7657 ,7693,4413,7417 }, + {7955,8003,7979 ,4254,3776,3775 }, {7299,7275,6346 ,7694,1854,4883 }, + {7329,7328,7275 ,7695,7552,1854 }, {7299,7329,7275 ,7694,7695,1854 }, + {7329,7357,7328 ,7695,7597,7552 }, {7652,7651,7626 ,7696,7675,7438 }, + {8054,2280,8053 ,7402,3613,7676 }, {2326,2325,2280 ,3588,1994,3613 }, + {7730,7776,7729 ,4413,4412,7697 }, {8043,5558,5599 ,6397,2302,2213 }, + {4558,7918,4559 ,4209,5555,5237 }, {7754,7753,7704 ,7350,3643,3562 }, + {7399,7398,7346 ,7428,7429,7584 }, {7701,7700,7679 ,3515,7698,7674 }, + {7349,7375,7315 ,7609,7699,7496 }, {3402,1538,3400 ,3660,0,3659 }, + {1973,3862,1494 ,551,550,1083 }, {7597,7596,7534 ,7700,7701,7314 }, + {7534,7535,7481 ,7314,7517,7542 }, {7929,7977,7928 ,7684,1714,1724 }, + {7104,7117,7090 ,5280,7650,4080 }, {7091,7104,7090 ,4153,5280,4080 }, + {7656,7683,7603 ,7401,5267,5266 }, {7635,7613,7553 ,7646,5106,7655 }, + {7522,7521,7465 ,7395,7657,7581 }, {3895,3912,7837 ,4455,7680,5967 }, + {3912,7388,6477 ,7680,7702,7671 }, {7388,7669,6477 ,7702,7681,7671 }, + {3863,587,5039 ,7690,7703,7672 }, {3538,3863,5039 ,7691,7690,7672 }, + {587,3401,4573 ,7703,7704,5314 }, {5039,587,4573 ,7672,7703,5314 }, + {7462,7520,7495 ,4310,7658,4308 }, {7265,7290,7243 ,7497,7635,7632 }, + {7569,7597,7534 ,7705,7700,7314 }, {7407,7406,7357 ,7706,7538,7597 }, + {7407,7432,7406 ,7706,7554,7538 }, {7270,7292,7247 ,2555,7594,7360 }, + {1991,597,153 ,7707,791,663 }, {8105,8137,8107 ,3791,2174,658 }, + {7219,7261,7218 ,1529,1528,7708 }, {5523,8055,3121 ,7709,7710,2076 }, + {5644,3724,3615 ,4133,4132,201 }, {3724,5683,5682 ,4132,3939,3783 }, + {3615,3724,5682 ,201,4132,3783 }, {7346,7398,7345 ,7584,7429,7588 }, + {7464,7463,7412 ,4246,7711,3765 }, {7689,402,7712 ,6444,5349,5350 }, + {7130,7117,7104 ,6823,7650,5280 }, {7623,7677,7622 ,7427,7426,7460 }, + {7133,7165,7132 ,1989,7265,7621 }, {7144,7145,7170 ,7653,7629,7652 }, + {3634,4813,68 ,5507,4088,4087 }, {7334,3912,3895 ,7712,7680,4455 }, + {3376,7334,3895 ,4466,7712,4455 }, {7388,7584,7669 ,7702,7713,7681 }, + {7584,3863,7669 ,7713,7690,7681 }, {587,7583,3401 ,7703,7714,7704 }, + {7954,7953,7903 ,7715,7716,7717 }, {7904,7954,7903 ,7513,7715,7717 }, + {7139,7140,7161 ,1025,7415,2559 }, {7115,7116,7139 ,1024,7262,1025 }, + {7263,7262,7241 ,627,4335,4465 }, {7492,7490,7491 ,7718,7478,7604 }, + {5556,5555,5535 ,3511,6338,3512 }, {8023,8022,7999 ,4582,4123,4583 }, + {6431,5495,6522 ,7719,4134,7720 }, {3121,791,6658 ,2076,7643,7665 }, + {6658,791,1350 ,7665,7643,509 }, {1991,2654,597 ,7707,2773,791 }, + {1350,5570,6658 ,509,508,7665 }, {7715,3724,5495 ,7721,4132,4134 }, + {3338,4207,7739 ,4147,4247,7571 }, {7458,7516,7487 ,7550,5708,5707 }, + {7482,7481,7425 ,7722,7542,3995 }, {7722,7721,7676 ,7645,7454,7461 }, + {7677,7722,7676 ,7426,7645,7461 }, {7455,7514,7484 ,7579,3610,3612 }, + {7751,7801,7750 ,4332,1618,7687 }, {7244,7265,7243 ,7620,7497,7632 }, + {5026,4744,4859 ,4225,4424,5039 }, {2618,7334,3376 ,4454,7712,4466 }, + {7334,2618,3912 ,7712,4454,7680 }, {3912,2618,7388 ,7680,4454,7702 }, + {4546,3863,7584 ,7723,7690,7713 }, {4546,881,3863 ,7723,7724,7690 }, + {881,587,3863 ,7724,7703,7690 }, {7876,7902,7852 ,1725,7692,7412 }, + {8050,3783,4589 ,4529,5311,776 }, {382,1444,4207 ,3534,4035,4247 }, + {7721,7770,7720 ,7454,4272,4271 }, {7550,7549,7490 ,7725,7605,7478 }, + {7492,7550,7490 ,7718,7725,7478 }, {7824,7797,7747 ,3770,3506,331 }, + {7379,7404,7378 ,7430,7726,7431 }, {7116,7140,7139 ,7262,7415,1025 }, + {382,4207,1005 ,3534,4247,3535 }, {8059,8034,2275 ,4235,7586,3152 }, + {7689,7688,7613 ,6444,6443,5106 }, {7886,2296,2295 ,6561,2664,3160 }, + {3737,5495,5571 ,7642,4134,4616 }, {1218,3008,6891 ,3255,546,813 }, + {1517,1622,235 ,600,4238,598 }, {145,2694,2627 ,810,811,2776 }, + {3934,5683,3724 ,7727,3939,4132 }, {3934,3009,5683 ,7727,3747,3939 }, + {2419,2959,890 ,3353,4608,3283 }, {7647,7675,7696 ,4967,5192,7728 }, + {7495,7552,7494 ,4308,5546,4309 }, {7130,7143,7142 ,6823,7651,7729 }, + {8073,8085,2343 ,2567,2016,2685 }, {3693,4590,278 ,5347,1490,1489 }, + {7978,7977,7929 ,7730,1714,7684 }, {7876,7852,7853 ,1725,7412,1617 }, + {4819,3401,3139 ,5216,7704,5400 }, {6292,6282,6283 ,5403,6617,4298 }, + {2618,3682,7388 ,4454,7731,7702 }, {3682,7839,7388 ,7731,7732,7702 }, + {7388,7839,7584 ,7702,7732,7713 }, {881,6712,587 ,7724,7733,7703 }, + {6712,7911,7583 ,7733,7734,7714 }, {587,6712,7583 ,7703,7733,7714 }, + {7911,3139,3401 ,7734,5400,7704 }, {7583,7911,3401 ,7714,7734,7704 }, + {7771,7824,7770 ,4336,3770,4272 }, {7803,7854,7828 ,4636,7514,7735 }, + {7117,7142,7141 ,7650,7729,7414 }, {7485,7514,7456 ,7636,3610,7578 }, + {7849,7850,7824 ,7551,3769,3770 }, {7711,7710,7663 ,7736,7560,7559 }, + {7850,7849,7824 ,3769,7551,3770 }, {7664,7711,7663 ,7611,7736,7559 }, + {7675,7647,7696 ,5192,4967,7728 }, {5535,5519,5536 ,3512,6337,754 }, + {2296,7886,2264 ,2664,6561,4166 }, {818,2844,906 ,1018,1329,1019 }, + {3121,8055,791 ,2076,7710,7643 }, {3737,6522,5495 ,7642,7720,4134 }, + {4573,3401,4819 ,5314,7704,5216 }, {6169,6140,6141 ,2334,2333,2386 }, + {3934,5038,3009 ,7727,3628,3747 }, {4883,1388,4774 ,5538,6311,5332 }, + {7117,7130,7142 ,7650,6823,7729 }, {7375,7428,7374 ,7699,7670,4988 }, + {3693,278,3638 ,5347,1489,5950 }, {8001,8039,8023 ,7689,7476,4582 }, + {8001,8023,8000 ,7689,4582,4581 }, {7877,7928,7876 ,7549,1724,1725 }, + {5495,6431,7715 ,4134,7719,7721 }, {5080,7839,3682 ,7737,7732,7731 }, + {5080,7584,7839 ,7737,7713,7732 }, {5080,2307,7584 ,7737,7738,7713 }, + {7584,2307,4546 ,7713,7738,7723 }, {6712,3139,7911 ,7733,5400,7734 }, + {6797,8108,8123 ,2246,2287,2247 }, {7312,7345,7311 ,4333,7588,4334 }, + {7116,7117,7141 ,7262,7650,7414 }, {7825,7851,7798 ,3768,6625,4728 }, + {7933,7959,7908 ,7507,7607,7366 }, {3297,3296,7783 ,4203,4159,7616 }, + {7882,7933,7908 ,7487,7507,7366 }, {886,2379,213 ,3464,3238,3606 }, + {5519,7962,5536 ,6337,7739,754 }, {7962,5522,5536 ,7739,755,754 }, + {3702,4249,1185 ,1303,6136,6159 }, {5523,2708,8055 ,7709,7740,7710 }, + {2708,6437,791 ,7740,399,7643 }, {8055,2708,791 ,7710,7740,7643 }, + {3737,7785,6522 ,7642,7741,7720 }, {6431,1266,3724 ,7719,7742,4132 }, + {7715,6431,3724 ,7721,7719,4132 }, {1266,3739,3934 ,7742,7743,7727 }, + {3724,1266,3934 ,4132,7742,7727 }, {3702,1185,5520 ,1303,6159,1304 }, + {7725,7702,7703 ,7420,3513,7667 }, {7675,7719,7696 ,5192,6541,7728 }, + {7385,7384,7333 ,7630,7683,4353 }, {7625,7678,7623 ,7139,7425,7427 }, + {7373,7427,7399 ,4989,7536,7428 }, {7750,7772,7748 ,7687,7744,7644 }, + {3875,2618,3184 ,5845,4454,4453 }, {3875,3682,2618 ,5845,7731,4454 }, + {2307,3483,4546 ,7738,7745,7723 }, {3483,881,4546 ,7745,7724,7723 }, + {2277,3693,4572 ,3522,5347,5949 }, {7997,7996,7950 ,7210,7328,7211 }, + {2486,2527,2526 ,2814,2637,2636 }, {7803,7828,7775 ,4636,7735,7509 }, + {7376,7351,7402 ,7746,6587,6589 }, {3296,2265,7783 ,4159,2662,7616 }, + {7882,7908,7859 ,7487,7366,7598 }, {2264,7886,2265 ,4166,6561,2662 }, + {7912,5519,5493 ,7747,6337,6336 }, {5519,7912,7962 ,6337,7747,7739 }, + {7962,7984,5522 ,7739,7748,755 }, {2708,3914,791 ,7740,7749,7643 }, + {6437,2708,791 ,399,7740,7643 }, {3914,3790,3737 ,7749,399,7642 }, + {791,3914,3737 ,7643,7749,7642 }, {7785,7360,6522 ,7741,7750,7720 }, + {6522,7360,6431 ,7720,7750,7719 }, {3739,3910,3934 ,7743,7751,7727 }, + {3934,3910,5038 ,7727,7751,3628 }, {7597,7622,7596 ,7700,7460,7701 }, + {7625,7623,7624 ,7139,7427,7439 }, {7190,7189,7142 ,7633,7493,7729 }, + {7173,7210,7172 ,7264,7499,7405 }, {8039,8038,8023 ,7476,4121,4582 }, + {7540,7628,7602 ,5709,7528,5867 }, {7765,5080,3682 ,7752,7737,7731 }, + {3875,7765,3682 ,5845,7752,7731 }, {3483,1526,6712 ,7745,7753,7733 }, + {881,3483,6712 ,7724,7745,7733 }, {1526,3171,6712 ,7753,7754,7733 }, + {3171,3139,6712 ,7754,5400,7733 }, {3171,4846,3139 ,7754,4237,5400 }, + {7877,7876,7826 ,7549,1725,1616 }, {3295,3296,177 ,7565,4159,3618 }, + {3297,3313,3296 ,4203,4160,4159 }, {7912,7963,7962 ,7747,7755,7739 }, + {7962,7963,7984 ,7739,7755,7748 }, {7963,7985,5522 ,7755,7756,755 }, + {7984,7963,5522 ,7748,7755,755 }, {5522,7985,5523 ,755,7756,7709 }, + {3914,7467,3737 ,7749,7757,7642 }, {3790,3914,3737 ,399,7749,7642 }, + {3737,7467,7785 ,7642,7757,7741 }, {7360,1266,6431 ,7750,7742,7719 }, + {3910,3739,1266 ,7751,7743,7742 }, {3567,5038,3910 ,7758,3628,7751 }, + {3614,4839,5038 ,7759,5484,3628 }, {3567,3614,5038 ,7758,7759,3628 }, + {3614,5092,4839 ,7759,3953,5484 }, {7701,7724,7700 ,3515,7760,7698 }, + {7143,7190,7142 ,7651,7633,7729 }, {7288,7313,7263 ,7577,7583,627 }, + {7727,7726,7681 ,3563,7418,7459 }, {7682,7727,7681 ,3564,3563,7459 }, + {7580,7636,7553 ,7596,7567,7655 }, {7636,7666,7635 ,7567,7566,7646 }, + {7761,7765,3875 ,7761,7752,5845 }, {7761,5080,7765 ,7761,7737,7752 }, + {3639,2307,5080 ,7762,7738,7737 }, {7761,3639,5080 ,7761,7762,7737 }, + {7936,7935,7884 ,5528,5527,7391 }, {7777,7778,7807 ,4411,7601,7602 }, + {7607,7660,7606 ,7455,7381,7457 }, {5493,7888,7912 ,6336,7763,7747 }, + {5214,5215,5235 ,6272,6313,6385 }, {7985,8044,2708 ,7756,7764,7740 }, + {5523,7985,2708 ,7709,7756,7740 }, {3914,3188,7467 ,7749,7765,7757 }, + {7467,6482,7785 ,7757,7766,7741 }, {7785,6482,7360 ,7741,7766,7750 }, + {7360,6482,1266 ,7750,7766,7742 }, {4020,3910,1266 ,7767,7751,7742 }, + {2097,2165,2164 ,846,1041,3583 }, {7426,7482,7425 ,7537,7722,3995 }, + {7773,7802,7751 ,7668,6566,4332 }, {7573,7540,7602 ,2013,5709,5867 }, + {7151,7150,3293 ,1202,7488,1988 }, {7731,7778,7777 ,7516,7601,4411 }, + {7756,7731,7777 ,7768,7516,4411 }, {7685,7731,7756 ,7518,7516,7768 }, + {7212,7211,7173 ,7208,7500,7264 }, {6610,3483,2307 ,7769,7745,7738 }, + {3639,6610,2307 ,7762,7769,7738 }, {3483,6610,1526 ,7745,7769,7753 }, + {8067,8091,8083 ,2168,2167,2103 }, {7320,7319,7292 ,7282,7432,7594 }, + {7515,7540,7573 ,1079,5709,2013 }, {7830,7829,7806 ,7770,7614,7612 }, + {7636,7635,7553 ,7567,7646,7655 }, {7950,7996,7949 ,7211,7328,7591 }, + {7481,7480,7454 ,7542,7463,3996 }, {7397,7424,7371 ,1186,1188,3748 }, + {7888,7938,7912 ,7763,7771,7747 }, {7912,7938,7963 ,7747,7771,7755 }, + {7985,8027,8044 ,7756,7772,7764 }, {8044,8027,2708 ,7764,7772,7740 }, + {8027,3689,3914 ,7772,7773,7749 }, {2708,8027,3914 ,7740,7772,7749 }, + {6482,1939,1266 ,7766,7774,7742 }, {1939,7862,1266 ,7774,7775,7742 }, + {1266,7862,4020 ,7742,7775,7767 }, {4020,3567,3910 ,7767,7758,7751 }, + {3567,5092,3614 ,7758,3953,7759 }, {7600,7627,7572 ,7556,7582,5124 }, + {7145,7146,7162 ,7629,7530,7649 }, {7146,7147,7162 ,7530,6937,7649 }, + {7773,7751,7752 ,7668,4332,3514 }, {402,448,7759 ,5349,1172,399 }, + {7270,7320,7292 ,2555,7282,7594 }, {7403,7429,7457 ,6588,7572,1078 }, + {7174,7212,7173 ,7610,7208,7264 }, {7935,7934,7883 ,5527,7486,7452 }, + {7686,7685,7632 ,7553,7518,7555 }, {6483,7761,3875 ,3584,7761,5845 }, + {2096,6483,3875 ,788,3584,5845 }, {6610,3767,1526 ,7769,7776,7753 }, + {3767,3171,1526 ,7776,7754,7753 }, {3767,6412,4846 ,7776,7777,4237 }, + {3171,3767,4846 ,7754,7776,4237 }, {235,1622,4846 ,598,4238,4237 }, + {6412,235,4846 ,7777,598,4237 }, {1993,4075,342 ,4544,4521,4542 }, + {3405,3456,8086 ,1798,1145,4823 }, {7272,7271,7230 ,7666,2554,7498 }, + {7956,7981,7931 ,7403,4252,7508 }, {3136,2654,2548 ,2783,2773,2813 }, + {5479,7840,5493 ,5387,7778,6336 }, {2287,8007,99 ,3735,3854,3743 }, + {7840,7888,5493 ,7778,7763,6336 }, {7963,7938,7985 ,7755,7771,7756 }, + {8027,3723,3689 ,7772,7779,7773 }, {3723,3821,3914 ,7779,7780,7749 }, + {3689,3723,3914 ,7773,7779,7749 }, {3914,3821,3188 ,7749,7780,7765 }, + {3188,7787,7467 ,7765,7781,7757 }, {7467,7787,6482 ,7757,7781,7766 }, + {1939,3374,7862 ,7774,7782,7775 }, {3374,3586,4020 ,7782,7783,7767 }, + {7862,3374,4020 ,7775,7782,7767 }, {3586,3722,4020 ,7783,7784,7767 }, + {3823,3567,4020 ,7785,7758,7767 }, {3722,3823,4020 ,7784,7785,7767 }, + {3823,5092,3567 ,7785,3953,7758 }, {7189,7188,7141 ,7493,7503,7414 }, + {7242,7264,7223 ,7627,626,7492 }, {7658,7706,7657 ,7505,7693,7417 }, + {7554,7523,3351 ,7786,7393,1444 }, {7128,7127,7114 ,5391,7245,5392 }, + {7910,6483,2096 ,3582,3584,788 }, {2207,3639,7761 ,7787,7762,7761 }, + {6483,2207,7761 ,3584,7787,7761 }, {8086,3456,5771 ,4823,1145,2868 }, + {7352,7377,7403 ,7595,7592,6588 }, {7212,7230,7211 ,7208,7498,7500 }, + {7614,7582,7523 ,7788,7469,7393 }, {7554,7614,7523 ,7786,7788,7393 }, + {7276,6346,3326 ,3696,4883,826 }, {4555,4582,7716 ,5496,5478,5549 }, + {7840,7889,7888 ,7778,7789,7763 }, {7889,7913,7888 ,7789,7790,7763 }, + {7913,7939,7938 ,7790,7791,7771 }, {7888,7913,7938 ,7763,7790,7771 }, + {7939,7964,7938 ,7791,7792,7771 }, {7938,7964,7985 ,7771,7792,7756 }, + {7964,8010,7985 ,7792,7793,7756 }, {7985,8010,8027 ,7756,7793,7772 }, + {3723,1123,3821 ,7779,7794,7780 }, {3821,7784,7787 ,7780,7795,7781 }, + {3188,3821,7787 ,7765,7780,7781 }, {7787,7784,6482 ,7781,7795,7766 }, + {7784,1939,6482 ,7795,7774,7766 }, {3766,5092,3823 ,3954,3953,7785 }, + {7556,3766,3823 ,5116,3954,7785 }, {8133,8131,8107 ,2131,2307,658 }, + {7242,7288,7264 ,7627,7577,626 }, {8022,8021,7998 ,4123,7365,7209 }, + {7351,7352,7403 ,6587,7595,6588 }, {7377,7429,7403 ,7592,7572,6588 }, + {7276,7299,6346 ,3696,7694,4883 }, {7330,7329,7299 ,7796,7695,7694 }, + {7358,7357,7329 ,7797,7597,7695 }, {7330,7358,7329 ,7796,7797,7695 }, + {1973,1659,4685 ,551,5132,4894 }, {3896,6610,3639 ,7798,7769,7762 }, + {2207,3896,3639 ,7787,7798,7762 }, {3896,3767,6610 ,7798,7776,7769 }, + {7317,7318,7351 ,5947,7593,6587 }, {7250,7249,7232 ,3418,7622,2124 }, + {7233,7250,7232 ,2125,3418,2124 }, {7640,7641,4577 ,5351,5356,5352 }, + {6320,7439,7500 ,190,7297,326 }, {7500,7524,4595 ,326,1630,5289 }, + {4557,4558,4584 ,5310,4209,5308 }, {7433,7432,7407 ,7799,7554,7706 }, + {3766,4922,4585 ,3954,5256,3955 }, {5479,7814,7840 ,5387,6988,7778 }, + {7863,7864,7889 ,7800,7801,7789 }, {7840,7863,7889 ,7778,7800,7789 }, + {7864,7914,7913 ,7801,7802,7790 }, {7889,7864,7913 ,7789,7801,7790 }, + {7913,7914,7939 ,7790,7802,7791 }, {7964,7986,8010 ,7792,7803,7793 }, + {7986,8010,8027 ,7803,7793,7772 }, {8010,7986,8027 ,7793,7803,7772 }, + {8010,8045,3723 ,7793,7804,7779 }, {8027,8010,3723 ,7772,7793,7779 }, + {1123,7784,3821 ,7794,7795,7780 }, {7784,5023,1939 ,7795,7805,7774 }, + {5023,3374,1939 ,7805,7782,7774 }, {3374,3722,3586 ,7782,7784,7783 }, + {7556,3823,3722 ,5116,7785,7784 }, {3121,5522,5523 ,2076,755,7709 }, + {7653,7702,7652 ,7638,3513,7696 }, {7120,7119,7093 ,6938,7529,4318 }, + {7272,7321,7271 ,7666,7501,2554 }, {7208,7209,7226 ,1671,7523,6686 }, + {7433,7434,7432 ,7799,7806,7554 }, {7492,7491,7432 ,7718,7604,7554 }, + {7434,7492,7432 ,7806,7718,7554 }, {3830,3976,5483 ,5882,919,171 }, + {4961,2207,6483 ,2905,7787,3584 }, {6802,3767,3896 ,7807,7776,7798 }, + {6802,3842,3767 ,7807,7808,7776 }, {3767,3842,6412 ,7776,7808,7777 }, + {6412,3842,235 ,7777,7808,598 }, {2485,2486,2526 ,2815,2814,2636 }, + {7243,7242,7224 ,7632,7627,7491 }, {7318,7352,7351 ,7593,7595,6587 }, + {7488,7541,7517 ,7585,7515,7484 }, {7439,7468,7500 ,7297,7809,326 }, + {7468,7524,7500 ,7809,1630,326 }, {99,8007,2306 ,3743,3854,6405 }, + {7611,7610,7549 ,7810,7606,7605 }, {7550,7611,7549 ,7725,7810,7605 }, + {6706,7841,7840 ,5389,7811,7778 }, {7814,6706,7840 ,6988,5389,7778 }, + {7841,7864,7863 ,7811,7801,7800 }, {7840,7841,7863 ,7778,7811,7800 }, + {7939,7914,7964 ,7791,7802,7792 }, {8010,7986,8045 ,7793,7803,7804 }, + {8045,61,1123 ,7804,7812,7794 }, {3723,8045,1123 ,7779,7804,7794 }, + {1123,3512,7784 ,7794,7813,7795 }, {7784,7176,5023 ,7795,7814,7805 }, + {5023,3827,3374 ,7805,7815,7782 }, {3827,7817,3722 ,7815,7816,7784 }, + {3374,3827,3722 ,7782,7815,7784 }, {8009,7556,3722 ,7817,5116,7784 }, + {8022,8052,8021 ,4123,4122,7365 }, {8052,8051,8021 ,4122,6172,7365 }, + {7776,7806,7755 ,4412,7612,7519 }, {7209,7246,7225 ,7523,3772,1672 }, + {7226,7209,7225 ,6686,7523,1672 }, {7729,7755,7705 ,7697,7519,7349 }, + {7728,7729,7705 ,7818,7697,7349 }, {7248,7294,7323 ,73,7819,74 }, + {7611,7664,7610 ,7810,7611,7606 }, {3352,3378,3351 ,2724,1442,1444 }, + {3859,2207,4961 ,7820,7787,2905 }, {3859,3896,2207 ,7820,7798,7787 }, + {7268,7318,7317 ,3773,7593,5947 }, {6143,7057,6107 ,4394,7207,7232 }, + {6304,7439,3533 ,7233,7297,192 }, {7525,7524,7468 ,7821,1630,7809 }, + {7525,4575,7524 ,7821,3128,1630 }, {4589,3783,4638 ,776,5311,5154 }, + {2333,7711,7664 ,3537,7736,7611 }, {2333,7710,7711 ,3537,7560,7736 }, + {7841,7865,7864 ,7811,7822,7801 }, {7914,7940,7964 ,7802,7823,7792 }, + {7940,7987,7986 ,7823,7824,7803 }, {7964,7940,7986 ,7792,7823,7803 }, + {7986,8028,8045 ,7803,7825,7804 }, {61,1097,3512 ,7812,7826,7813 }, + {1123,61,3512 ,7794,7812,7813 }, {3512,1097,7784 ,7813,7826,7795 }, + {7176,6683,5023 ,7814,7827,7805 }, {5023,6683,3827 ,7805,7827,7815 }, + {3827,3692,7817 ,7815,7828,7816 }, {7817,7762,3722 ,7816,7829,7784 }, + {7762,8009,3722 ,7829,7817,7784 }, {6760,7556,8009 ,5117,5116,7817 }, + {7762,6760,8009 ,7829,5117,7817 }, {5522,3121,5558 ,755,2076,2302 }, + {7625,7624,7598 ,7139,7439,7140 }, {7600,7653,7627 ,7556,7638,7582 }, + {7653,7652,7627 ,7638,7696,7582 }, {7349,7401,7375 ,7609,7619,7699 }, + {7459,7458,7404 ,7485,7550,7726 }, {3314,3297,7710 ,3575,4203,7560 }, + {2333,3314,7710 ,3537,3575,7560 }, {1274,1316,1315 ,1687,1657,1627 }, + {2548,3896,3859 ,2813,7798,7820 }, {2654,6802,3896 ,2773,7807,7798 }, + {2548,2654,3896 ,2813,2773,7798 }, {7171,7209,7208 ,4306,7523,1671 }, + {7628,7681,7654 ,7528,7459,7637 }, {5112,6120,5113 ,6021,142,141 }, + {7057,6143,5936 ,7207,4394,4393 }, {6083,6304,6303 ,3989,7233,7261 }, + {7413,7439,6304 ,7830,7297,7233 }, {7469,7468,7439 ,7831,7809,7297 }, + {7413,7469,7439 ,7830,7831,7297 }, {7525,7585,4575 ,7821,6805,3128 }, + {7555,3351,3377 ,7832,1444,1443 }, {177,2265,3295 ,3618,2662,7565 }, + {6705,7815,6706 ,4158,5146,5389 }, {6706,7815,7841 ,5389,5146,7811 }, + {7865,7890,7864 ,7822,7833,7801 }, {7864,7890,7914 ,7801,7833,7802 }, + {7914,7890,7940 ,7802,7833,7823 }, {7986,7987,8028 ,7803,7824,7825 }, + {8028,8056,8045 ,7825,7834,7804 }, {8045,8056,61 ,7804,7834,7812 }, + {1097,7301,7784 ,7826,7835,7795 }, {7784,7301,7176 ,7795,7835,7814 }, + {7176,7301,6683 ,7814,7835,7827 }, {448,401,402 ,1172,1387,5349 }, + {7347,7399,7346 ,7576,7428,7584 }, {7514,7572,7513 ,3610,5124,3611 }, + {7657,7728,7705 ,7417,7818,7349 }, {7780,7834,7809 ,7389,7390,7449 }, + {7628,7654,7601 ,7528,7637,7544 }, {2834,3859,4961 ,2860,7820,2905 }, + {2834,2548,3859 ,2860,2813,7820 }, {1991,3842,6802 ,7707,7808,7807 }, + {2654,1991,6802 ,2773,7707,7807 }, {3842,1991,153 ,7808,7707,663 }, + {7834,7833,7809 ,7390,7392,7449 }, {7255,6167,6234 ,7238,5974,5973 }, + {7361,6304,6083 ,7836,7233,3989 }, {7361,7413,6304 ,7836,7830,7233 }, + {7469,7525,7468 ,7831,7821,7809 }, {7641,7640,7585 ,5356,5351,6805 }, + {7668,7667,7582 ,7590,7589,7469 }, {2285,2330,2329 ,3701,3693,3680 }, + {2165,2097,2143 ,1041,846,953 }, {7815,7842,7841 ,5146,7837,7811 }, + {7841,7842,7865 ,7811,7837,7822 }, {7890,7915,7940 ,7833,7838,7823 }, + {7987,8011,8028 ,7824,7839,7825 }, {8011,3003,8056 ,7839,7840,7834 }, + {8028,8011,8056 ,7825,7839,7834 }, {8056,3003,61 ,7834,7840,7812 }, + {7301,3416,6683 ,7835,7841,7827 }, {6683,3416,3827 ,7827,7841,7815 }, + {3416,3894,3827 ,7841,7842,7815 }, {3827,3894,3692 ,7815,7842,7828 }, + {3692,7762,7817 ,7828,7829,7816 }, {7760,6760,7762 ,7843,5117,7829 }, + {7760,2814,6760 ,7843,5255,5117 }, {2049,2096,2048 ,758,788,5461 }, + {6760,2814,4636 ,5117,5255,5118 }, {7902,7952,7901 ,7692,5475,7413 }, + {7119,7120,7147 ,7529,6938,6937 }, {7292,7319,7352 ,7594,7432,7595 }, + {7703,7702,7653 ,7667,3513,7638 }, {2834,2616,2548 ,2860,2812,2813 }, + {7625,7679,7624 ,7139,7674,7439 }, {7414,7413,7361 ,7844,7830,7836 }, + {7526,7525,7469 ,7845,7821,7831 }, {7526,7585,7525 ,7845,6805,7821 }, + {4583,4556,7788 ,5498,5497,7313 }, {7865,7842,7890 ,7822,7837,7833 }, + {7915,7965,7940 ,7838,7846,7823 }, {7940,7965,7987 ,7823,7846,7824 }, + {3301,1097,61 ,7847,7826,7812 }, {3003,3301,61 ,7840,7847,7812 }, + {1097,7811,7301 ,7826,7848,7835 }, {5035,7762,3692 ,7849,7829,7828 }, + {3894,5035,3692 ,7842,7849,7828 }, {5035,6846,7762 ,7849,7850,7829 }, + {401,2334,7712 ,1387,7851,5350 }, {7749,7750,7748 ,7663,7687,7644 }, + {7121,7132,7120 ,7852,7621,6938 }, {7132,7148,7120 ,7621,6936,6938 }, + {7829,7880,7857 ,7614,7531,7533 }, {7438,7437,3321 ,7351,5938,2461 }, + {7345,7372,7311 ,7588,4373,4334 }, {2484,7713,7668 ,3499,7489,7590 }, + {7949,7974,7925 ,7591,4171,7294 }, {6278,7106,7092 ,3967,7573,3968 }, + {7243,7289,7242 ,7632,7575,7627 }, {7596,7595,7533 ,7701,4019,7039 }, + {7140,7141,7161 ,7415,7414,2559 }, {7770,7824,7747 ,4272,3770,331 }, + {7291,7316,7350 ,3774,5948,7618 }, {7336,6083,6014 ,7853,3989,6592 }, + {7336,7361,6083 ,7853,7836,3989 }, {7470,7469,7413 ,7854,7831,7830 }, + {7414,7470,7413 ,7844,7854,7830 }, {7557,7585,7526 ,7855,6805,7845 }, + {7557,7616,7585 ,7855,7856,6805 }, {7616,7641,7585 ,7856,5356,6805 }, + {7658,7657,7605 ,7505,7417,5239 }, {7300,7299,7276 ,7857,7694,3696 }, + {3306,7300,7276 ,3695,7857,3696 }, {7868,4557,7818 ,7858,5310,5309 }, + {2281,2282,2327 ,3626,3663,3627 }, {6705,5439,7815 ,4158,4157,5146 }, + {7842,7866,7890 ,7837,7859,7833 }, {7866,7915,7890 ,7859,7838,7833 }, + {7965,7988,8011 ,7846,7860,7839 }, {7987,7965,8011 ,7824,7846,7839 }, + {3301,7812,7811 ,7847,7861,7848 }, {1097,3301,7811 ,7826,7847,7848 }, + {7811,7812,7301 ,7848,7861,7835 }, {6745,7762,6846 ,7862,7829,7850 }, + {7762,6745,7760 ,7829,7862,7843 }, {6745,3789,2814 ,7862,7863,5255 }, + {7760,6745,2814 ,7843,7862,5255 }, {3789,1973,4906 ,7863,551,4895 }, + {2814,3789,4906 ,5255,7863,4895 }, {7750,7800,7772 ,7687,7568,7744 }, + {7194,7193,7147 ,1670,7647,6937 }, {8006,8005,7956 ,7407,7527,7403 }, + {7300,7330,7299 ,7857,7796,7694 }, {7408,7407,7357 ,7864,7706,7597 }, + {7358,7408,7357 ,7797,7864,7597 }, {7434,7433,7407 ,7806,7799,7706 }, + {7408,7434,7407 ,7864,7806,7706 }, {7493,7492,7434 ,7865,7718,7806 }, + {7493,7550,7492 ,7865,7725,7718 }, {7362,7361,7336 ,7866,7836,7853 }, + {7362,7414,7361 ,7866,7844,7836 }, {7470,7526,7469 ,7854,7845,7831 }, + {7616,7671,7641 ,7856,7867,5356 }, {7641,7671,4579 ,5356,7867,5355 }, + {7815,5440,7842 ,5146,5147,7837 }, {7842,5440,5410 ,7837,5147,4285 }, + {7867,7916,7915 ,7868,7869,7838 }, {7866,7867,7915 ,7859,7868,7838 }, + {7916,7941,7915 ,7869,7870,7838 }, {7915,7941,7965 ,7838,7870,7846 }, + {7988,8029,3003 ,7860,7871,7840 }, {8011,7988,3003 ,7839,7860,7840 }, + {3003,4030,3301 ,7840,7872,7847 }, {7812,8026,7301 ,7861,7873,7835 }, + {8026,3991,3416 ,7873,7874,7841 }, {7301,8026,3416 ,7835,7873,7841 }, + {3991,3481,3416 ,7874,7875,7841 }, {3481,8042,3894 ,7875,399,7842 }, + {3416,3481,3894 ,7841,7875,7842 }, {3789,1852,1973 ,7863,7876,551 }, + {8036,4589,4588 ,7327,776,775 }, {7400,7455,7428 ,7580,7579,7670 }, + {7121,7120,7094 ,7852,6938,4317 }, {7957,8006,7956 ,7385,7407,7403 }, + {7859,7908,7858 ,7598,7366,7368 }, {7611,7687,7664 ,7810,7877,7611 }, + {7687,2333,7664 ,7877,3537,7611 }, {7302,6014,6259 ,7878,6592,6718 }, + {7302,7336,6014 ,7878,7853,6592 }, {7415,7414,7362 ,7879,7844,7866 }, + {7501,7526,7470 ,7880,7845,7854 }, {7558,7557,7526 ,7881,7855,7845 }, + {7501,7558,7526 ,7880,7881,7845 }, {7672,7671,7616 ,5450,7867,7856 }, + {7671,7672,4579 ,7867,5450,5355 }, {6531,8134,8120 ,2280,3263,906 }, + {7676,7720,7699 ,7461,4271,3951 }, {5410,7867,7866 ,4285,7868,7859 }, + {7842,5410,7866 ,7837,4285,7859 }, {7916,7942,7941 ,7869,7882,7870 }, + {7942,7966,7965 ,7882,7883,7846 }, {7941,7942,7965 ,7870,7882,7846 }, + {7966,7988,7965 ,7883,7860,7846 }, {8029,4622,4030 ,7871,7884,7872 }, + {3003,8029,4030 ,7840,7871,7872 }, {4030,4622,3301 ,7872,7884,7847 }, + {3481,6294,3894 ,7875,7885,7842 }, {8042,3481,3894 ,399,7875,7842 }, + {6294,7670,5035 ,7885,7886,7849 }, {3894,6294,5035 ,7842,7885,7849 }, + {936,3933,935 ,5234,7678,5264 }, {7400,7428,7375 ,7580,7670,7699 }, + {8006,2285,8005 ,7407,3701,7527 }, {7685,7756,7730 ,7518,7768,4413 }, + {7708,7707,7660 ,7388,7382,7381 }, {7857,7879,7856 ,7533,7532,4635 }, + {7884,7883,7833 ,7391,7452,7392 }, {7363,7362,7336 ,7887,7866,7853 }, + {7302,7363,7336 ,7878,7887,7853 }, {7440,7470,7414 ,7888,7854,7844 }, + {7415,7440,7414 ,7879,7888,7844 }, {7440,7501,7470 ,7888,7880,7854 }, + {7617,7616,7557 ,7889,7856,7855 }, {7558,7617,7557 ,7881,7889,7855 }, + {7685,7706,7658 ,7518,7693,7505 }, {7867,5410,5411 ,7868,4285,4284 }, + {7555,7554,3351 ,7832,7786,1444 }, {7949,7995,7974 ,7591,4135,4171 }, + {4264,7837,3740 ,5540,5967,7639 }, {7942,7989,7988 ,7882,7890,7860 }, + {7966,7942,7988 ,7883,7882,7860 }, {2950,3301,4622 ,7891,7847,7884 }, + {2950,7812,3301 ,7891,7861,7847 }, {7812,2950,8026 ,7861,7891,7873 }, + {3481,7670,6294 ,7875,7886,7885 }, {7670,6604,6846 ,7886,7892,7850 }, + {5035,7670,6846 ,7849,7886,7850 }, {6604,5042,6745 ,7892,7893,7862 }, + {6846,6604,6745 ,7850,7892,7862 }, {5042,7737,3789 ,7893,7894,7863 }, + {6745,5042,3789 ,7862,7893,7863 }, {7737,7080,1852 ,7894,7895,7876 }, + {3789,7737,1852 ,7863,7894,7876 }, {7080,7810,1973 ,7895,552,551 }, + {1852,7080,1973 ,7876,7895,551 }, {8004,8025,7980 ,7404,7510,4253 }, + {7685,7658,7631 ,7518,7505,7504 }, {7352,7319,7377 ,7595,7432,7592 }, + {7622,7650,7595 ,7460,7237,4019 }, {7730,7756,7777 ,4413,7768,4411 }, + {7295,7324,7323 ,7562,7617,74 }, {7706,7685,7730 ,7693,7518,4413 }, + {7294,7295,7323 ,7819,7562,74 }, {6234,6167,6259 ,5973,5974,6718 }, + {6167,7302,6259 ,5974,7878,6718 }, {7363,7415,7362 ,7887,7879,7866 }, + {7502,7501,7440 ,7896,7880,7888 }, {7502,7558,7501 ,7896,7881,7880 }, + {7617,7672,7616 ,7889,5450,7856 }, {1210,5017,4144 ,4739,5092,4761 }, + {4562,8057,1381 ,5018,7897,5193 }, {4839,4730,5038 ,5484,5438,3628 }, + {7867,5418,7916 ,7868,3471,7869 }, {7916,5418,7942 ,7869,3471,7882 }, + {7989,8012,7988 ,7890,7898,7860 }, {8030,8029,7988 ,7899,7871,7860 }, + {8012,8030,7988 ,7898,7899,7860 }, {8030,4622,8029 ,7899,7884,7871 }, + {2950,7466,8026 ,7891,7900,7873 }, {7466,3991,8026 ,7900,7874,7873 }, + {1312,3481,3991 ,7901,7875,7874 }, {7466,1312,3991 ,7900,7901,7874 }, + {3481,3937,7670 ,7875,7902,7886 }, {5042,7063,7737 ,7893,7903,7894 }, + {7737,7063,7080 ,7894,7903,7895 }, {7928,7977,7927 ,1724,1714,1716 }, + {7225,7267,7245 ,1672,4331,7506 }, {7958,7957,7932 ,3855,7385,7408 }, + {7632,7685,7631 ,7555,7518,7504 }, {7615,7614,7554 ,2816,7788,7786 }, + {7555,7615,7554 ,7832,2816,7786 }, {7615,7582,7614 ,2816,7469,7788 }, + {2485,7668,7582 ,2815,7590,7469 }, {7615,2485,7582 ,2816,2815,7469 }, + {8051,8060,8037 ,6172,7685,4528 }, {6319,6304,3533 ,6789,7233,192 }, + {7666,7690,7635 ,7566,6466,7646 }, {2485,2484,7668 ,2815,3499,7590 }, + {7303,7302,6167 ,7904,7878,5974 }, {7389,7415,7363 ,7905,7879,7887 }, + {7441,7440,7415 ,7906,7888,7879 }, {7389,7441,7415 ,7905,7906,7879 }, + {7559,7558,7502 ,7907,7881,7896 }, {7642,7672,7617 ,7908,5450,7889 }, + {7642,4555,7672 ,7908,5496,5450 }, {4555,7716,7672 ,5496,5549,5450 }, + {2484,2451,7713 ,3499,2102,7489 }, {5411,5418,7867 ,4284,3471,7868 }, + {7315,7375,7314 ,7496,7699,7909 }, {5418,7917,7942 ,3471,4920,7882 }, + {8012,7989,8030 ,7898,7890,7899 }, {3843,4622,8030 ,7677,7884,7899 }, + {7740,3937,3481 ,7910,7902,7875 }, {1312,7740,3481 ,7901,7910,7875 }, + {3937,7740,7670 ,7902,7910,7886 }, {7810,3862,1494 ,552,550,1083 }, + {5598,8043,5599 ,2216,6397,2213 }, {6706,6741,6705 ,5389,5388,4158 }, + {7267,7266,7245 ,4331,6769,7506 }, {7295,7294,7249 ,7562,7819,7622 }, + {3305,7300,3306 ,879,7857,3695 }, {7331,7330,7300 ,7911,7796,7857 }, + {7331,7358,7330 ,7911,7797,7796 }, {7824,7849,7797 ,3770,7551,3506 }, + {7337,7363,7302 ,7912,7887,7878 }, {7303,7337,7302 ,7904,7912,7878 }, + {7337,7389,7363 ,7912,7905,7887 }, {7441,7502,7440 ,7906,7896,7888 }, + {7586,7617,7558 ,7913,7889,7881 }, {7559,7586,7558 ,7907,7913,7881 }, + {7586,7642,7617 ,7913,7908,7889 }, {7409,7408,7358 ,6245,7864,7797 }, + {7435,7434,7408 ,6244,7806,7864 }, {7409,7435,7408 ,6245,6244,7864 }, + {7613,7688,7634 ,5106,6443,7474 }, {7917,7967,7942 ,4920,7914,7882 }, + {7942,7967,7989 ,7882,7914,7890 }, {3933,4622,3843 ,7678,7884,7677 }, + {3251,2950,4622 ,7915,7891,7884 }, {3933,3251,4622 ,7678,7915,7884 }, + {3251,7466,2950 ,7915,7900,7891 }, {7466,7498,1312 ,7900,7916,7901 }, + {7740,6054,7670 ,7910,7917,7886 }, {6054,7786,7670 ,7917,7918,7886 }, + {7670,7786,6604 ,7886,7918,7892 }, {7786,6845,5042 ,7918,7919,7893 }, + {6604,7786,5042 ,7892,7918,7893 }, {6845,6743,7063 ,7919,7920,7903 }, + {5042,6845,7063 ,7893,7919,7903 }, {6743,1351,7080 ,7920,1162,7895 }, + {7063,6743,7080 ,7903,7920,7895 }, {1351,8008,7810 ,1162,1163,552 }, + {7080,1351,7810 ,7895,1162,552 }, {7810,8008,3862 ,552,1163,550 }, + {4582,4581,7716 ,5478,5430,5549 }, {7534,7596,7533 ,7314,7701,7039 }, + {7676,7699,7650 ,7461,3951,7237 }, {7175,7212,7174 ,2347,7208,7610 }, + {7551,7550,7493 ,5108,7725,7865 }, {7255,7303,6167 ,7238,7904,5974 }, + {7390,7389,7337 ,7921,7905,7912 }, {7503,7502,7441 ,7922,7896,7906 }, + {7503,7559,7502 ,7922,7907,7896 }, {7643,7642,7586 ,7923,7908,7913 }, + {7612,7611,7550 ,5107,7810,7725 }, {7551,7612,7550 ,5108,5107,7725 }, + {860,1183,953 ,1385,1238,1237 }, {8013,7989,7967 ,7924,7890,7914 }, + {8013,8030,7989 ,7924,7899,7890 }, {7816,7466,3251 ,7925,7900,7915 }, + {6198,7498,7466 ,7926,7916,7900 }, {7816,6198,7466 ,7925,7926,7900 }, + {6198,1312,7498 ,7926,7901,7916 }, {8049,2317,8035 ,5393,3129,6675 }, + {7902,7901,7852 ,7692,7413,7412 }, {7175,7174,7151 ,2347,7610,1202 }, + {7665,7687,7611 ,7927,7877,7810 }, {2334,2333,7687 ,7851,3537,7877 }, + {5672,5718,5744 ,5324,6126,6090 }, {7304,7303,7255 ,7928,7904,7238 }, + {7304,7337,7303 ,7928,7912,7904 }, {7442,7441,7389 ,7929,7906,7905 }, + {7390,7442,7389 ,7921,7929,7905 }, {7527,7559,7503 ,7930,7907,7922 }, + {7527,7586,7559 ,7930,7913,7907 }, {7694,4555,7642 ,7931,5496,7908 }, + {7643,7694,7642 ,7923,7931,7908 }, {935,8030,8013 ,5264,7899,7924 }, + {4058,4055,1253 ,4708,3678,2353 }, {7816,3251,3933 ,7925,7915,7678 }, + {6198,7961,1312 ,7926,7932,7901 }, {3565,7740,1312 ,7933,7910,7901 }, + {7961,3565,1312 ,7932,7933,7901 }, {7740,3565,6054 ,7910,7933,7917 }, + {7248,7272,7231 ,73,7666,6944 }, {7291,7350,7349 ,3774,7618,7609 }, + {7350,7376,7349 ,7618,7746,7609 }, {7349,7376,7401 ,7609,7746,7619 }, + {7376,7402,7401 ,7746,6589,7619 }, {8050,8037,3783 ,4529,4528,5311 }, + {7401,7400,7375 ,7619,7580,7699 }, {7192,7191,7170 ,7648,7634,7652 }, + {4126,3141,4176 ,124,4701,4703 }, {6347,6346,7860 ,451,4883,452 }, + {5483,5261,3830 ,171,173,5882 }, {6282,6060,5115 ,6617,5914,5913 }, + {5109,5108,7166 ,4182,4184,5971 }, {1934,1418,22 ,4620,4619,4066 }, + {7891,7918,4558 ,7934,5555,4209 }, {2410,3670,489 ,1,281,3639 }, + {7256,7255,5845 ,7935,7238,143 }, {6120,7256,5845 ,142,7935,143 }, + {7338,7337,7304 ,7936,7912,7928 }, {7338,7390,7337 ,7936,7921,7912 }, + {7442,7503,7441 ,7929,7922,7906 }, {7587,7586,7527 ,7937,7913,7930 }, + {7587,7643,7586 ,7937,7923,7913 }, {7717,4556,4555 ,7938,5497,5496 }, + {7694,7717,4555 ,7931,7938,5496 }, {2317,2275,8035 ,3129,3152,6675 }, + {4558,4557,7891 ,4209,5310,7934 }, {17,15,438 ,5190,497,498 }, + {4423,3672,160 ,4289,4277,4295 }, {4680,160,3672 ,5368,4295,4277 }, + {974,7816,3933 ,5366,7925,7678 }, {936,974,3933 ,5234,5366,7678 }, + {3565,7961,6198 ,7933,7932,7926 }, {3565,6759,6054 ,7933,7939,7917 }, + {6054,6759,7786 ,7917,7939,7918 }, {6759,7738,6845 ,7939,7940,7919 }, + {7786,6759,6845 ,7918,7939,7919 }, {7738,1067,6743 ,7940,7941,7920 }, + {6845,7738,6743 ,7919,7940,7920 }, {1067,1352,1351 ,7941,1262,1162 }, + {6743,1067,1351 ,7920,7941,1162 }, {7627,7652,7626 ,7582,7696,7438 }, + {7315,7314,7265 ,7496,7909,7497 }, {7402,7403,7401 ,6589,6588,7619 }, + {7569,7534,7536 ,7705,7314,5205 }, {5106,7134,5108 ,5926,7942,4184 }, + {7166,7177,7197 ,5971,7943,7234 }, {7197,7177,6120 ,7234,7943,142 }, + {7277,7255,7256 ,7944,7238,7935 }, {7277,7304,7255 ,7944,7928,7238 }, + {7391,7390,7338 ,7945,7921,7936 }, {7471,7503,7442 ,7946,7922,7929 }, + {7528,7527,7503 ,7947,7930,7922 }, {7471,7528,7503 ,7946,7947,7922 }, + {7644,7643,7587 ,7948,7923,7937 }, {7717,7766,4556 ,7938,7949,5497 }, + {4556,7766,7788 ,5497,7949,7313 }, {7891,7868,7918 ,7934,7858,5555 }, + {7918,7968,4560 ,5555,7950,5236 }, {7607,7606,7578 ,7455,7457,7462 }, + {7805,7857,7804 ,7520,7533,4634 }, {7189,7222,7205 ,7493,628,7669 }, + {7652,7702,7651 ,7696,3513,7675 }, {1091,160,4680 ,4597,4295,5368 }, + {974,7335,7816 ,5366,4265,7925 }, {7335,6198,7816 ,4265,7926,7925 }, + {6759,1067,7738 ,7939,7941,7940 }, {7483,7482,7426 ,7574,7722,7537 }, + {7605,7604,7542 ,5239,7416,5240 }, {7210,7247,7228 ,7499,7360,7524 }, + {7207,7244,7206 ,7608,7620,7631 }, {7456,7403,7485 ,7578,6588,7636 }, + {7773,7827,7802 ,7668,7410,6566 }, {3303,7385,7333 ,1049,7630,4353 }, + {7094,7120,7093 ,4317,6938,4318 }, {7487,7458,7515 ,5707,7550,1079 }, + {3518,6133,7096 ,4338,4390,4337 }, {6133,5106,7096 ,4390,5926,4337 }, + {7152,5108,7134 ,7951,4184,7942 }, {7152,7166,5108 ,7951,5971,4184 }, + {7213,6120,7177 ,7952,142,7943 }, {7339,7338,7304 ,7953,7936,7928 }, + {7277,7339,7304 ,7944,7953,7928 }, {7416,7442,7390 ,7954,7929,7921 }, + {7391,7416,7390 ,7945,7954,7921 }, {7416,7471,7442 ,7954,7946,7929 }, + {7528,7587,7527 ,7947,7937,7930 }, {7673,7643,7644 ,7955,7923,7948 }, + {7673,7694,7643 ,7955,7931,7923 }, {7673,7717,7694 ,7955,7938,7931 }, + {7819,7818,7788 ,7956,5309,7313 }, {7766,7819,7788 ,7949,7956,7313 }, + {7919,7918,7868 ,7957,5555,7858 }, {7918,7919,7968 ,5555,7957,7950 }, + {4560,7968,4561 ,5236,7950,5361 }, {7857,7856,7804 ,7533,4635,4634 }, + {7430,7458,7487 ,7587,7550,5707 }, {4957,8062,5026 ,2733,3272,4225 }, + {1945,3565,6198 ,7958,7933,7926 }, {7335,1945,6198 ,4265,7958,7926 }, + {3565,1945,6759 ,7933,7958,7939 }, {1067,1353,1352 ,7941,1205,1262 }, + {7118,7131,7105 ,7628,7654,5279 }, {7801,7800,7750 ,1618,7568,7687 }, + {7401,7403,7456 ,7619,6588,7578 }, {7701,7750,7724 ,3515,7687,7760 }, + {7404,7430,7378 ,7726,7587,7431 }, {7429,7430,7487 ,7572,7587,5707 }, + {7132,7165,7164 ,7621,7265,7521 }, {7458,7487,7515 ,7550,5707,1079 }, + {7111,5106,6133 ,7959,5926,4390 }, {7178,7177,7166 ,7960,7943,5971 }, + {7152,7178,7166 ,7951,7960,5971 }, {7234,7256,6120 ,7961,7935,142 }, + {7213,7234,6120 ,7952,7961,142 }, {7234,7277,7256 ,7961,7944,7935 }, + {7339,7391,7338 ,7953,7945,7936 }, {7472,7471,7416 ,7962,7946,7954 }, + {7472,7528,7471 ,7962,7947,7946 }, {7588,7587,7528 ,7963,7937,7947 }, + {7588,7644,7587 ,7963,7948,7937 }, {7718,7717,7673 ,7964,7938,7955 }, + {7718,7766,7717 ,7964,7949,7938 }, {7819,7868,7818 ,7956,7858,5309 }, + {7943,7968,7919 ,7965,7950,7957 }, {7968,8014,4561 ,7950,7966,5361 }, + {7437,7465,7436 ,5938,7581,4245 }, {7188,7204,7187 ,7503,5120,7330 }, + {4582,4556,4583 ,5478,5497,5498 }, {3734,1945,7335 ,7967,7958,4265 }, + {1945,3448,6759 ,7958,7968,7939 }, {3448,5614,6759 ,7968,7969,7939 }, + {5614,1267,1067 ,7969,1353,7941 }, {6759,5614,1067 ,7939,7969,7941 }, + {1067,1267,1353 ,7941,1353,1205 }, {7750,7749,7723 ,7687,7663,7664 }, + {7724,7750,7723 ,7760,7687,7664 }, {7106,7118,7105 ,7573,7628,5279 }, + {7538,7602,7537 ,2014,5867,7543 }, {7403,7486,7485 ,6588,7436,7636 }, + {7927,7952,7902 ,1716,5475,7692 }, {7725,7773,7752 ,7420,7668,3514 }, + {7486,7457,7538 ,7436,1078,2014 }, {7545,7546,7578 ,7398,7397,7462 }, + {7123,7134,5106 ,7970,7942,5926 }, {7111,7123,5106 ,7959,7970,5926 }, + {7123,7152,7134 ,7970,7951,7942 }, {7178,7213,7177 ,7960,7952,7943 }, + {7278,7277,7234 ,7971,7944,7961 }, {7364,7391,7339 ,7972,7945,7953 }, + {7417,7416,7391 ,7973,7954,7945 }, {7364,7417,7391 ,7972,7973,7945 }, + {7529,7528,7472 ,7974,7947,7962 }, {7618,7644,7588 ,7975,7948,7963 }, + {7618,7673,7644 ,7975,7955,7948 }, {7767,7766,7718 ,7976,7949,7964 }, + {7869,7868,7819 ,7977,7858,7956 }, {7869,7919,7868 ,7977,7957,7858 }, + {7990,7968,7943 ,7978,7950,7965 }, {7968,7990,8014 ,7950,7978,7966 }, + {7990,8031,4561 ,7978,7979,5361 }, {8014,7990,4561 ,7966,7978,5361 }, + {4561,8031,4562 ,5361,7979,5018 }, {99,2306,100 ,3743,6405,2823 }, + {8051,278,3783 ,6172,1489,5311 }, {7518,7545,7544 ,7396,7398,7980 }, + {7456,7514,7455 ,7578,3610,7579 }, {7544,7545,7578 ,7980,7398,7462 }, + {7141,7188,7161 ,7414,7503,2559 }, {976,3734,7335 ,782,7967,4265 }, + {5614,7937,1267 ,7969,7981,1353 }, {7570,7598,7569 ,5204,7140,7705 }, + {7598,7624,7597 ,7140,7439,7700 }, {7679,7700,7678 ,7674,7698,7425 }, + {7780,7809,7779 ,7389,7449,7447 }, {7084,7111,6133 ,4391,7959,4390 }, + {7153,7152,7123 ,7982,7951,7970 }, {7214,7213,7178 ,7983,7952,7960 }, + {7235,7234,7213 ,7984,7961,7952 }, {7214,7235,7213 ,7983,7984,7952 }, + {7340,7339,7277 ,7985,7953,7944 }, {7278,7340,7277 ,7971,7985,7944 }, + {7340,7364,7339 ,7985,7972,7953 }, {7417,7472,7416 ,7973,7962,7954 }, + {7589,7588,7528 ,7986,7963,7947 }, {7529,7589,7528 ,7974,7986,7947 }, + {7589,7618,7588 ,7986,7975,7963 }, {7674,7673,7618 ,7987,7955,7975 }, + {7674,7718,7673 ,7987,7964,7955 }, {7789,7819,7766 ,7988,7956,7949 }, + {7767,7789,7766 ,7976,7988,7949 }, {7892,7919,7869 ,7989,7957,7977 }, + {7944,7943,7919 ,7990,7965,7957 }, {7892,7944,7919 ,7989,7990,7957 }, + {8032,8031,7990 ,7991,7979,7978 }, {8031,8032,4562 ,7979,7991,5018 }, + {8008,1428,3862 ,1163,1126,550 }, {8032,8057,4562 ,7991,7897,5018 }, + {7657,7729,7728 ,7417,7697,7818 }, {1029,3734,976 ,3930,7967,782 }, + {7499,1945,3734 ,7992,7958,7967 }, {1029,7499,3734 ,3930,7992,7967 }, + {7499,4297,3448 ,7992,7993,7968 }, {1945,7499,3448 ,7958,7992,7968 }, + {7624,7623,7597 ,7439,7427,7700 }, {7724,7723,7678 ,7760,7664,7425 }, + {7700,7724,7678 ,7698,7760,7425 }, {7131,7118,7144 ,7654,7628,7653 }, + {7486,7538,7485 ,7436,2014,7636 }, {7602,7601,7537 ,5867,7544,7543 }, + {7799,7798,7771 ,4727,4728,4336 }, {7853,7852,7800 ,1617,7412,7568 }, + {7485,7537,7514 ,7636,7543,3610 }, {7273,7325,7296 ,3467,7465,7471 }, + {7147,7163,7194 ,6937,5460,1670 }, {7097,7111,7084 ,7994,7959,4391 }, + {7097,7123,7111 ,7994,7970,7959 }, {7179,7178,7152 ,7995,7960,7951 }, + {7153,7179,7152 ,7982,7995,7951 }, {7235,7278,7234 ,7984,7971,7961 }, + {7365,7364,7340 ,7996,7972,7985 }, {7473,7472,7417 ,7997,7962,7973 }, + {7473,7529,7472 ,7997,7974,7962 }, {7619,7618,7589 ,7998,7975,7986 }, + {7695,7718,7674 ,7999,7964,7987 }, {7695,7767,7718 ,7999,7976,7964 }, + {7843,7819,7789 ,8000,7956,7988 }, {7843,7869,7819 ,8000,7977,7956 }, + {7843,7892,7869 ,8000,7989,7977 }, {7944,7990,7943 ,7990,7978,7965 }, + {8032,1381,8057 ,7991,5193,7897 }, {8025,2281,8024 ,7510,3626,7511 }, + {7355,7383,7325 ,7435,7512,7465 }, {7222,7221,7205 ,628,7545,7669 }, + {4578,4577,7641 ,5353,5352,5356 }, {4297,6695,5614 ,7993,8001,7969 }, + {3448,4297,5614 ,7968,7993,7969 }, {6695,7887,7937 ,8001,8002,7981 }, + {5614,6695,7937 ,7969,8001,7981 }, {7887,1314,1267 ,8002,1409,1353 }, + {7937,7887,1267 ,7981,8002,1353 }, {7722,7771,7721 ,7645,4336,7454 }, + {7598,7597,7569 ,7140,7700,7705 }, {7901,7951,7900 ,7413,7546,6922 }, + {7758,7780,7779 ,7387,7389,7447 }, {2487,7615,7555 ,4427,2816,7832 }, + {7085,7084,6186 ,8003,4391,4365 }, {7082,7085,6186 ,6462,8003,4365 }, + {7085,7097,7084 ,8003,7994,4391 }, {7124,7123,7097 ,8004,7970,7994 }, + {7124,7153,7123 ,8004,7982,7970 }, {7198,7178,7179 ,8005,7960,7995 }, + {7198,7214,7178 ,8005,7983,7960 }, {7279,7278,7235 ,8006,7971,7984 }, + {7279,7340,7278 ,8006,7985,7971 }, {7418,7417,7364 ,8007,7973,7972 }, + {7365,7418,7364 ,7996,8007,7972 }, {7504,7529,7473 ,8008,7974,7997 }, + {7504,7589,7529 ,8008,7986,7974 }, {7741,7767,7695 ,8009,7976,7999 }, + {7741,7789,7767 ,8009,7988,7976 }, {7893,7892,7843 ,8010,7989,8000 }, + {7991,7990,7944 ,8011,7978,7990 }, {7991,8032,7990 ,8011,7991,7978 }, + {8032,8058,1381 ,7991,8012,5193 }, {3304,7300,3305 ,1050,7857,879 }, + {1029,978,3671 ,3930,1620,3089 }, {5437,6705,6740 ,6259,4158,6894 }, + {1178,342,4075 ,206,4542,4521 }, {7679,7678,7625 ,7674,7425,7139 }, + {7570,7569,7536 ,5204,7705,5205 }, {7624,7679,7625 ,7439,7674,7139 }, + {7681,7703,7654 ,7459,7667,7637 }, {7827,7826,7802 ,7410,1616,6566 }, + {3304,7331,7300 ,1050,7911,7857 }, {7359,7358,7331 ,7686,7797,7911 }, + {7098,7097,7085 ,8013,7994,8003 }, {7154,7153,7124 ,8014,7982,8004 }, + {7154,7179,7153 ,8014,7995,7982 }, {7236,7235,7214 ,8015,7984,7983 }, + {7198,7236,7214 ,8005,8015,7983 }, {7305,7340,7279 ,8016,7985,8006 }, + {7305,7365,7340 ,8016,7996,7985 }, {7418,7473,7417 ,8007,7997,7973 }, + {7590,7589,7504 ,8017,7986,8008 }, {7790,7789,7741 ,8018,7988,8009 }, + {7790,7843,7789 ,8018,8000,7988 }, {7945,7944,7892 ,8019,7990,7989 }, + {7893,7945,7892 ,8010,8019,7989 }, {8015,8032,7991 ,8020,7991,8011 }, + {8015,8058,8032 ,8020,8012,7991 }, {213,2379,4163 ,3606,3238,4863 }, + {7850,7900,7849 ,3769,6922,7551 }, {7359,7409,7358 ,7686,6245,7797 }, + {7119,7146,7145 ,7529,7530,7629 }, {7854,7903,7878 ,7514,7717,7411 }, + {3671,7499,1029 ,3089,7992,3930 }, {3671,1077,4297 ,3089,2694,7993 }, + {7499,3671,4297 ,7992,3089,7993 }, {1077,308,4297 ,2694,8021,7993 }, + {4297,308,6695 ,7993,8021,8001 }, {308,4047,7887 ,8021,8022,8002 }, + {6695,308,7887 ,8001,8021,8002 }, {7887,4047,1314 ,8002,8022,1409 }, + {2403,3191,2165 ,2904,2903,1041 }, {7107,7118,7106 ,7494,7628,7573 }, + {8041,2281,8025 ,7548,3626,7510 }, {7494,7493,7434 ,4309,7865,7806 }, + {7435,7494,7434 ,6244,4309,7806 }, {8004,8041,8025 ,7404,7548,7510 }, + {5946,7085,7082 ,5481,8003,6462 }, {7125,7124,7097 ,8023,8004,7994 }, + {7098,7125,7097 ,8013,8023,7994 }, {7180,7179,7154 ,8024,7995,8014 }, + {7180,7198,7179 ,8024,8005,7995 }, {7280,7279,7235 ,8025,8006,7984 }, + {7236,7280,7235 ,8015,8025,7984 }, {7280,7305,7279 ,8025,8016,8006 }, + {7366,7365,7305 ,8026,7996,8016 }, {7366,7418,7365 ,8026,8007,7996 }, + {7443,7473,7418 ,8027,7997,8007 }, {7505,7504,7473 ,8028,8008,7997 }, + {7443,7505,7473 ,8027,8028,7997 }, {7505,7560,7504 ,8028,8029,8008 }, + {7560,7590,7504 ,8029,8017,8008 }, {7742,7741,7695 ,8030,8009,7999 }, + {7844,7843,7790 ,8031,8000,8018 }, {7844,7893,7843 ,8031,8010,8000 }, + {7945,7991,7944 ,8019,8011,7990 }, {8047,8058,8015 ,8032,8012,8020 }, + {8047,6921,1381 ,8032,8033,5193 }, {8058,8047,1381 ,8012,8032,5193 }, + {2487,7555,3377 ,4427,7832,1443 }, {7634,7611,7612 ,7474,7810,5107 }, + {7542,7604,7541 ,5240,7416,7515 }, {4047,7638,1314 ,8022,7540,1409 }, + {8030,935,3843 ,7899,5264,7677 }, {3298,4518,1879 ,7424,1281,1283 }, + {6277,7107,6278 ,4387,7494,3967 }, {7776,7830,7806 ,4412,7770,7612 }, + {7982,2306,8007 ,3853,6405,3854 }, {7634,7665,7611 ,7474,7927,7810 }, + {7688,7687,7665 ,6443,7877,7927 }, {7634,7688,7665 ,7474,6443,7927 }, + {7083,7085,5946 ,5483,8003,5481 }, {7083,7098,7085 ,5483,8013,8003 }, + {7155,7154,7124 ,8034,8014,8004 }, {7125,7155,7124 ,8023,8034,8004 }, + {7215,7198,7180 ,8035,8005,8024 }, {7199,7215,7180 ,8036,8035,8024 }, + {7257,7236,7198 ,8037,8015,8005 }, {7215,7257,7198 ,8035,8037,8005 }, + {7281,7280,7236 ,8038,8025,8015 }, {7257,7281,7236 ,8037,8038,8015 }, + {7306,7305,7280 ,8039,8016,8025 }, {7281,7306,7280 ,8038,8039,8025 }, + {7419,7418,7366 ,8040,8007,8026 }, {7419,7443,7418 ,8040,8027,8007 }, + {7505,7561,7560 ,8028,8041,8029 }, {7791,7790,7741 ,8042,8018,8009 }, + {7742,7791,7741 ,8030,8042,8009 }, {7894,7893,7844 ,8043,8010,8031 }, + {7969,7991,7945 ,8044,8011,8019 }, {8016,8015,7991 ,8045,8020,8011 }, + {7969,8016,7991 ,8044,8045,8011 }, {3785,6921,8047 ,8046,8033,8032 }, + {6921,1169,1381 ,8033,5100,5193 }, {3785,1169,6921 ,8046,5100,8033 }, + {3948,4296,303 ,280,6023,4086 }, {1583,1253,1606 ,5415,2353,3677 }, + {5493,5518,5492 ,6336,6316,6315 }, {7959,2306,7958 ,7607,6405,3855 }, + {5557,5556,5536 ,756,3511,754 }, {1315,1356,1355 ,1627,1530,1408 }, + {308,7638,4047 ,8021,7540,8022 }, {402,7759,448 ,5349,399,1172 }, + {7681,7725,7703 ,7459,7420,7667 }, {7908,7959,7907 ,7366,7607,7367 }, + {4368,68,1005 ,4148,4087,3535 }, {7326,7355,7325 ,7434,7435,7465 }, + {643,644,740 ,3285,1176,1595 }, {7099,7098,7083 ,8047,8013,5483 }, + {7181,7180,7154 ,8048,8024,8014 }, {7155,7181,7154 ,8034,8048,8014 }, + {7181,7199,7180 ,8048,8036,8024 }, {7181,7215,7199 ,8048,8035,8036 }, + {7258,7257,7215 ,8049,8037,8035 }, {7282,7281,7257 ,8050,8038,8037 }, + {7258,7282,7257 ,8049,8050,8037 }, {7282,7306,7281 ,8050,8039,8038 }, + {7282,7307,7306 ,8050,8051,8039 }, {7444,7443,7419 ,8052,8027,8040 }, + {7791,7844,7790 ,8042,8031,8018 }, {7920,7945,7893 ,8053,8019,8010 }, + {7894,7920,7893 ,8043,8053,8010 }, {7920,7969,7945 ,8053,8044,8019 }, + {8048,8047,8015 ,8054,8032,8020 }, {8016,8048,8015 ,8045,8054,8020 }, + {7546,7607,7578 ,7397,7455,7462 }, {3842,153,235 ,7808,663,598 }, + {7638,1274,1315 ,7540,1687,1627 }, {7639,308,1077 ,8055,8021,2694 }, + {1078,7639,1077 ,5060,8055,2694 }, {7639,2993,308 ,8055,3744,8021 }, + {308,2993,7638 ,8021,3744,7540 }, {2993,1274,7638 ,3744,1687,7540 }, + {7624,7625,7570 ,7439,7139,5204 }, {7244,7243,7206 ,7620,7632,7631 }, + {7463,7462,7410 ,7711,4310,3766 }, {7107,7106,6278 ,7494,7573,3967 }, + {7464,7462,7463 ,4246,4310,7711 }, {2306,7982,7958 ,6405,3853,3855 }, + {7126,7125,7098 ,8056,8023,8013 }, {7099,7126,7098 ,8047,8056,8013 }, + {7167,7181,7155 ,8057,8048,8034 }, {7237,7215,7181 ,8058,8035,8048 }, + {7237,7258,7215 ,8058,8049,8035 }, {7237,7282,7258 ,8058,8050,8049 }, + {7341,7307,7282 ,8059,8051,8050 }, {7307,7341,7282 ,8051,8059,8050 }, + {7367,7392,7366 ,8060,8061,8026 }, {7392,7419,7366 ,8061,8040,8026 }, + {7506,7505,7443 ,8062,8028,8027 }, {7444,7506,7443 ,8052,8062,8027 }, + {7506,7562,7561 ,8062,8063,8041 }, {7505,7506,7561 ,8028,8062,8041 }, + {7820,7844,7791 ,8064,8031,8042 }, {7820,7894,7844 ,8064,8043,8031 }, + {7970,7969,7920 ,8065,8044,8053 }, {7970,8016,7969 ,8065,8045,8044 }, + {3721,3785,8047 ,8066,8046,8032 }, {8048,3721,8047 ,8054,8066,8032 }, + {3721,1169,3785 ,8066,5100,8046 }, {4580,4579,7672 ,5410,5355,5450 }, + {3293,7149,7133 ,1988,7263,1989 }, {7542,7541,7488 ,5240,7515,7585 }, + {7959,7958,7907 ,7607,3855,7367 }, {7677,7676,7622 ,7426,7461,7460 }, + {5046,7639,1078 ,5232,8055,5060 }, {5046,2993,7639 ,5232,3744,8055 }, + {489,2955,2473 ,3639,3640,4303 }, {7571,7570,7512 ,5126,5204,5203 }, + {7807,7830,7776 ,7602,7770,4412 }, {7151,7174,7150 ,1202,7610,7488 }, + {7777,7807,7776 ,4411,7602,4412 }, {4557,7868,7891 ,5310,7858,7934 }, + {7776,7755,7729 ,4412,7519,7697 }, {5952,7099,7083 ,6595,8047,5483 }, + {7135,7155,7125 ,8067,8034,8023 }, {7126,7135,7125 ,8056,8067,8023 }, + {7156,7167,7155 ,8068,8057,8034 }, {7135,7156,7155 ,8067,8068,8034 }, + {7167,7156,7181 ,8057,8068,8048 }, {7156,7200,7181 ,8068,8069,8048 }, + {7200,7216,7181 ,8069,8070,8048 }, {7216,7237,7181 ,8070,8058,8048 }, + {7283,7282,7237 ,8071,8050,8058 }, {7283,7307,7282 ,8071,8051,8050 }, + {7307,7368,7341 ,8051,8072,8059 }, {7392,7444,7419 ,8061,8052,8040 }, + {7870,7894,7820 ,8073,8043,8064 }, {7921,7920,7894 ,8074,8053,8043 }, + {7870,7921,7894 ,8073,8074,8043 }, {8017,8016,7970 ,8075,8045,8065 }, + {4563,1169,3721 ,3024,5100,8066 }, {7484,7513,7483 ,3612,3611,7574 }, + {7596,7622,7595 ,7701,7460,4019 }, {7404,7458,7430 ,7726,7550,7587 }, + {7231,7230,7212 ,6944,7498,7208 }, {2243,6552,8053 ,1993,7679,7676 }, + {489,1101,2410 ,3639,2,1 }, {7100,7099,5952 ,8076,8047,6595 }, + {7100,7112,7099 ,8076,8077,8047 }, {7112,7126,7099 ,8077,8056,8047 }, + {7156,7135,7126 ,8068,8067,8056 }, {7135,7156,7126 ,8067,8068,8056 }, + {7156,7201,7200 ,8068,8078,8069 }, {7201,7216,7200 ,8078,8070,8069 }, + {7238,7237,7216 ,8079,8058,8070 }, {7201,7238,7216 ,8078,8079,8070 }, + {7308,7307,7283 ,8080,8051,8071 }, {7308,7368,7307 ,8080,8072,8051 }, + {7445,7444,7392 ,8081,8052,8061 }, {7445,7506,7444 ,8081,8062,8052 }, + {7506,7563,7562 ,8062,8082,8063 }, {7821,7870,7820 ,8083,8073,8064 }, + {7921,7970,7920 ,8074,8065,8053 }, {8033,8048,8016 ,8084,8054,8045 }, + {8017,8033,8016 ,8075,8084,8045 }, {7332,7331,3304 ,6685,7911,1050 }, + {7332,7359,7331 ,6685,7686,7911 }, {7464,7521,7462 ,4246,7657,4310 }, + {7196,7212,7175 ,2401,7208,2347 }, {6108,1274,2993 ,6975,1687,3744 }, + {1172,6108,2993 ,1758,6975,3744 }, {7427,7483,7426 ,7536,7574,7537 }, + {5107,5121,5106 ,4183,5855,5926 }, {7410,7409,7359 ,3766,6245,7686 }, + {7136,7135,7126 ,8085,8067,8056 }, {7112,7136,7126 ,8077,8085,8056 }, + {7136,7157,7156 ,8085,8086,8068 }, {7135,7136,7156 ,8067,8085,8068 }, + {7259,7237,7238 ,8087,8058,8079 }, {7259,7283,7237 ,8087,8071,8058 }, + {7369,7368,7308 ,8088,8072,8080 }, {7369,7393,7368 ,8088,8089,8072 }, + {7474,7506,7445 ,8090,8062,8081 }, {7474,7564,7563 ,8090,8091,8082 }, + {7506,7474,7563 ,8062,8090,8082 }, {7871,7870,7821 ,8092,8073,8083 }, + {7971,7970,7921 ,8093,8065,8074 }, {7971,8017,7970 ,8093,8075,8065 }, + {6340,8048,8033 ,8094,8054,8084 }, {6340,3721,8048 ,8094,8066,8054 }, + {2315,4563,3721 ,3068,3024,8066 }, {6340,2315,3721 ,8094,3068,8066 }, + {7521,7520,7462 ,7657,7658,4310 }, {7828,7827,7773 ,7735,7410,7668 }, + {7412,7463,7410 ,3765,7711,3766 }, {7723,7748,7722 ,7664,7644,7645 }, + {7953,7978,7929 ,7716,7730,7684 }, {7133,7132,7108 ,1989,7621,4111 }, + {7632,7631,7605 ,7555,7504,5239 }, {7552,7551,7493 ,5546,5108,7865 }, + {7494,7552,7493 ,4309,5546,7865 }, {7086,7100,5952 ,6743,8076,6595 }, + {7202,7201,7156 ,8095,8078,8068 }, {7157,7202,7156 ,8086,8095,8068 }, + {7309,7308,7283 ,8096,8080,8071 }, {7259,7309,7283 ,8087,8096,8071 }, + {7394,7393,7369 ,8097,8089,8088 }, {7420,7445,7392 ,8098,8081,8061 }, + {7922,7921,7870 ,8099,8074,8073 }, {7871,7922,7870 ,8092,8099,8073 }, + {7992,8017,7971 ,8100,8075,8093 }, {7992,8033,8017 ,8100,8084,8075 }, + {8024,8040,8002 ,7511,7688,3777 }, {7774,7828,7773 ,7419,7735,7668 }, + {7852,7851,7825 ,7412,6625,3768 }, {2293,2292,2262 ,2561,1983,3359 }, + {7723,7722,7677 ,7664,7645,7426 }, {7678,7723,7677 ,7425,7664,7426 }, + {7314,7374,7348 ,7909,4988,4990 }, {7145,7162,7170 ,7629,7649,7652 }, + {7903,7953,7929 ,7717,7716,7684 }, {8024,8002,8003 ,7511,3777,3776 }, + {8024,8053,8040 ,7511,7676,7688 }, {7576,7632,7605 ,7615,7555,5239 }, + {7688,7712,2334 ,6443,5350,7851 }, {7687,7688,2334 ,7877,6443,7851 }, + {7113,7112,7100 ,8101,8077,8076 }, {7086,7113,7100 ,6743,8101,8076 }, + {7217,7238,7201 ,8102,8079,8078 }, {7202,7217,7201 ,8095,8102,8078 }, + {7217,7259,7238 ,8102,8087,8079 }, {7309,7369,7308 ,8096,8088,8080 }, + {7446,7445,7420 ,8103,8081,8098 }, {7475,7474,7445 ,8104,8090,8081 }, + {7446,7475,7445 ,8103,8104,8081 }, {7474,7530,7564 ,8090,8105,8091 }, + {7822,7871,7821 ,8106,8092,8083 }, {7922,7971,7921 ,8099,8093,8074 }, + {8034,8033,7992 ,7586,8084,8100 }, {8034,6340,8033 ,7586,8094,8084 }, + {2369,2334,401 ,3257,7851,1387 }, {7730,7729,7657 ,4413,7697,7417 }, + {7903,7929,7878 ,7717,7684,7411 }, {7314,7348,7290 ,7909,4990,7635 }, + {7348,7373,7347 ,4990,4989,7576 }, {7800,7852,7825 ,7568,7412,3768 }, + {7772,7748,7722 ,7744,7644,7645 }, {7748,7772,7722 ,7644,7744,7645 }, + {7455,7427,7428 ,7579,7536,7670 }, {7512,7536,7482 ,5203,5205,7722 }, + {7904,7903,7854 ,7513,7717,7514 }, {7165,7172,7195 ,7265,7405,7522 }, + {7576,7605,7543 ,7615,5239,5238 }, {7249,7294,7248 ,7622,7819,73 }, + {7249,7248,7232 ,7622,73,2124 }, {7623,7622,7597 ,7427,7460,7700 }, + {7137,7136,7112 ,8107,8085,8077 }, {7113,7137,7112 ,8101,8107,8077 }, + {7137,7158,7157 ,8107,8108,8086 }, {7136,7137,7157 ,8085,8107,8086 }, + {7158,7182,7157 ,8108,8109,8086 }, {7182,7202,7157 ,8109,8095,8086 }, + {7260,7259,7217 ,8110,8087,8102 }, {7342,7369,7309 ,8111,8088,8096 }, + {7342,7394,7369 ,8111,8097,8088 }, {7531,7530,7474 ,8112,8105,8090 }, + {7475,7531,7474 ,8104,8112,8090 }, {7530,7531,7564 ,8105,8112,8091 }, + {7872,7871,7822 ,8113,8092,8106 }, {7946,7971,7922 ,8114,8093,8099 }, + {7993,7992,7971 ,8115,8100,8093 }, {7946,7993,7971 ,8114,8115,8093 }, + {8059,6340,8034 ,4235,8094,7586 }, {2316,2315,6340 ,3067,3068,8094 }, + {8059,2316,6340 ,4235,3067,8094 }, {2333,2334,2369 ,3537,7851,3257 }, + {7326,7325,7273 ,7434,7465,3467 }, {7425,7481,7454 ,3995,7542,3996 }, + {7231,7272,7230 ,6944,7666,7498 }, {7269,7247,7292 ,7361,7360,7594 }, + {7455,7484,7427 ,7579,3612,7536 }, {8060,8051,3783 ,7685,6172,5311 }, + {7800,7799,7748 ,7568,4727,7644 }, {7772,7800,7748 ,7744,7568,7644 }, + {7483,7512,7482 ,7574,5203,7722 }, {7375,7374,7314 ,7699,4988,7909 }, + {7209,7210,7228 ,7523,7499,7524 }, {7164,7165,7195 ,7521,7265,7522 }, + {7172,7210,7209 ,7405,7499,7523 }, {7195,7172,7209 ,7522,7405,7523 }, + {3321,7386,7387 ,2461,5512,3615 }, {7158,7183,7182 ,8108,8116,8109 }, + {7183,7202,7182 ,8116,8095,8109 }, {7218,7217,7202 ,7708,8102,8095 }, + {7183,7218,7202 ,8116,7708,8095 }, {7284,7309,7259 ,8117,8096,8087 }, + {7260,7284,7259 ,8110,8117,8087 }, {7284,7342,7309 ,8117,8111,8096 }, + {7395,7394,7342 ,8118,8097,8111 }, {7395,7421,7394 ,8118,8119,8097 }, + {7447,7475,7446 ,8120,8104,8103 }, {7591,7564,7531 ,8121,8091,8112 }, + {7895,7871,7872 ,8122,8092,8113 }, {7895,7922,7871 ,8122,8099,8092 }, + {7895,7946,7922 ,8122,8114,8099 }, {8035,8034,7992 ,6675,7586,8100 }, + {7993,8035,7992 ,8115,6675,8100 }, {7629,7705,7684 ,7399,7349,7400 }, + {7692,7691,7666 ,7490,4419,7566 }, {7637,7692,7666 ,7473,7490,7566 }, + {7690,7691,449 ,6466,4419,1169 }, {7929,7928,7877 ,7684,1724,7549 }, + {7378,7377,7319 ,7431,7592,7432 }, {7828,7854,7827 ,7735,7514,7410 }, + {278,4612,3783 ,1489,5249,5311 }, {7800,7825,7799 ,7568,3768,4727 }, + {7572,7571,7513 ,5124,5126,3611 }, {7265,7314,7290 ,7497,7909,7635 }, + {7386,7385,3303 ,5512,7630,1049 }, {7398,7397,7345 ,7429,1186,7588 }, + {7350,7317,7376 ,7618,5947,7746 }, {7317,7351,7376 ,5947,6587,7746 }, + {7114,7113,7086 ,5392,8101,6743 }, {7087,7114,7086 ,6742,5392,6743 }, + {7218,7260,7217 ,7708,8110,8102 }, {7343,7342,7284 ,2750,8111,8117 }, + {7476,7475,7447 ,8123,8104,8120 }, {7592,7591,7531 ,8124,8121,8112 }, + {7793,7822,7792 ,5726,8106,8125 }, {7793,7845,7822 ,5726,5725,8106 }, + {7845,7872,7822 ,5725,8113,8106 }, {7947,7946,7895 ,4354,8114,8122 }, + {7947,7993,7946 ,4354,8115,8114 }, {5644,3615,5660 ,4133,201,3054 }, + {7108,7121,7094 ,4111,7852,4317 }, {7108,7132,7121 ,4111,7621,7852 }, + {7601,7653,7600 ,7544,7638,7556 }, {7523,7522,7496 ,7393,7395,7470 }, + {7775,7828,7774 ,7509,7735,7419 }, {7480,7534,7511 ,7463,7314,6677 }, + {7348,7347,7289 ,4990,7576,7575 }, {7651,7702,7680 ,7675,3513,7673 }, + {7702,7701,7680 ,3513,3515,7673 }, {8053,6552,6784 ,7676,7679,7472 }, + {8053,6784,8039 ,7676,7472,7476 }, {8040,8053,8039 ,7688,7676,7476 }, + {7775,7774,7726 ,7509,7419,7418 }, {7378,7430,7377 ,7431,7587,7592 }, + {7095,7108,7094 ,3924,4111,4317 }, {6552,2243,2278 ,7679,1993,3495 }, + {7114,7127,7137 ,5392,7245,8107 }, {7113,7114,7137 ,8101,5392,8107 }, + {7127,7159,7158 ,7245,5075,8108 }, {7137,7127,7158 ,8107,7245,8108 }, + {7184,7183,7158 ,5074,8116,8108 }, {7159,7184,7158 ,5075,5074,8108 }, + {7261,7260,7218 ,1528,8110,7708 }, {7261,7284,7260 ,1528,8117,8110 }, + {7396,7395,7342 ,6716,8118,8111 }, {7343,7396,7342 ,2750,6716,8111 }, + {7507,7531,7475 ,6706,8112,8104 }, {7476,7507,7475 ,8123,6706,8104 }, + {7593,7592,7531 ,5032,8124,8112 }, {7896,7895,7872 ,4355,8122,8113 }, + {7845,7896,7872 ,5725,4355,8113 }, {7994,7993,7947 ,5582,8115,4354 }, + {7142,7189,7141 ,7729,7493,7414 }, {7725,7752,7702 ,7420,3514,3513 }, + {7682,7681,7628 ,3564,7459,7528 }, {7412,7411,7384 ,3765,3767,7683 }, + {7952,7951,7901 ,5475,7546,7413 }, {7572,7626,7599 ,5124,7438,5125 }, + {7190,7224,7189 ,7633,7491,7493 }, {7482,7536,7481 ,7722,5205,7542 }, + {7536,7534,7481 ,5205,7314,7542 }, {7660,7659,7606 ,7381,7383,7457 }, + {7088,7101,7087 ,4299,4073,6742 }, {7101,7114,7087 ,4073,5392,6742 }, + {7219,7218,7183 ,1529,7708,8116 }, {7184,7219,7183 ,5074,1529,8116 }, + {7285,7284,7261 ,2751,8117,1528 }, {7285,7343,7284 ,2751,2750,8117 }, + {7422,7395,7396 ,1404,8118,6716 }, {7449,7448,7395 ,7600,8126,8118 }, + {7422,7449,7395 ,1404,7600,8118 }, {7449,7477,7448 ,7600,1403,8126 }, + {7565,7531,7507 ,5080,8112,6706 }, {7531,7565,7593 ,8112,5080,5032 }, + {7565,7593,7592 ,5080,5032,8124 }, {7593,7565,7592 ,5032,5080,8124 }, + {7593,7645,7592 ,5032,5034,8124 }, {7896,7947,7895 ,4355,4354,8122 }, + {8018,8035,7993 ,6676,6675,8115 }, {7994,8018,7993 ,5582,6676,8115 }, + {5658,5642,5659 ,49,526,3053 } +}; +F32 vertices [8146][3] = { +{-0.128951f,0.113893f,0.0385904f},{-0.183541f,0.121141f,0.0176007f},{-0.3356f,0.267483f,0.245606f}, +{0.0990868f,0.131443f,0.150773f},{-0.0330697f,0.108447f,0.0577602f},{-0.0525675f,0.103219f,0.0581332f}, +{-0.129472f,0.111128f,0.0174721f},{0.321716f,0.0172985f,0.188251f},{0.350352f,0.00204496f,0.112787f}, +{-0.0496094f,0.0212148f,0.245825f},{0.284245f,0.0735475f,0.150304f},{-0.296733f,0.306228f,-0.0948973f}, +{0.364127f,-0.0330021f,0.152767f},{0.399804f,-0.11262f,0.0755796f},{-0.10546f,0.41535f,-0.00363974f}, +{-0.407308f,0.165107f,0.333295f},{-0.354815f,0.10291f,0.350047f},{-0.415655f,0.1722f,0.323829f}, +{0.154706f,0.137848f,0.112556f},{0.363426f,-0.0163596f,0.0577281f},{0.344481f,-0.0712067f,-0.05722f}, +{-0.47357f,0.192405f,0.152844f},{-0.433269f,-0.00220573f,0.173326f},{-0.336095f,0.297592f,0.112524f}, +{-0.431192f,0.27075f,0.192f},{0.136655f,0.102968f,0.212456f},{0.172493f,0.0906273f,0.222398f}, +{-0.0907206f,0.100833f,0.0403717f},{-0.212472f,0.206855f,0.0920485f},{-0.390158f,0.166136f,0.336426f}, +{0.0818719f,0.1177f,0.189036f},{-0.181875f,0.080962f,0.174573f},{-0.396981f,-0.110421f,-0.0611684f}, +{0.263654f,0.0893219f,0.152702f},{-0.469351f,0.0768914f,0.0379088f},{0.367586f,-0.0504292f,-0.00258511f}, +{0.373245f,-0.0554837f,0.00500307f},{-0.449931f,0.0420887f,0.0196393f},{-0.104874f,0.430758f,-0.0208675f}, +{-0.392389f,0.230186f,0.285811f},{-0.0333784f,0.0170798f,0.258757f},{-0.0197004f,0.434816f,-0.137706f}, +{-0.131067f,0.378811f,-0.0749943f},{-0.091563f,0.102653f,0.0566477f},{0.175284f,0.113482f,0.190798f}, +{-0.190955f,0.149211f,0.11763f},{-0.0545288f,0.10055f,0.0386676f},{0.216376f,0.0805312f,0.210058f}, +{0.231906f,0.0788913f,0.208501f},{0.328282f,0.00609625f,0.189769f},{0.313646f,0.0244043f,0.192785f}, +{-0.149188f,0.359988f,0.0320504f},{-0.466033f,0.134156f,0.0375229f},{-0.234677f,0.209048f,0.17485f}, +{0.385283f,-0.0772258f,0.133456f},{0.382538f,-0.0547828f,0.0755796f},{0.370396f,-0.091817f,0.193274f}, +{-0.244857f,0.283997f,0.11408f},{-0.047275f,0.301502f,-0.0171956f},{-0.337446f,0.231304f,0.282833f}, +{-0.358197f,0.206437f,0.307669f},{-0.338095f,-0.305122f,0.135758f},{-0.470593f,0.0595544f,0.0379023f}, +{-0.377309f,0.116614f,0.351275f},{-0.30036f,0.324536f,-0.112678f},{-0.115717f,0.107591f,0.0241086f}, +{-0.190904f,0.150715f,0.0987171f},{-0.335529f,0.291643f,0.0379345f},{-0.484316f,0.0419215f,0.264172f}, +{0.152538f,0.102801f,0.211254f},{0.153812f,0.0916176f,0.225215f},{-0.103138f,0.104698f,0.03515f}, +{-0.297048f,0.295598f,0.0953281f},{0.30371f,0.0399987f,0.18802f},{-0.0883798f,0.41845f,-0.0178644f}, +{0.363773f,-0.092788f,-0.0374522f},{-0.352076f,0.35386f,-0.0974052f},{0.0567345f,0.0797466f,0.236108f}, +{0.294335f,-0.114228f,0.291643f},{0.0244911f,0.341809f,-0.0740619f},{-0.166352f,0.361223f,0.0361275f}, +{-0.0908749f,0.0966914f,0.0203724f},{-0.166281f,0.380907f,0.020636f},{-0.0159899f,0.0208225f,0.260622f}, +{-0.474798f,0.157956f,0.154394f},{-0.395045f,0.398315f,-0.246642f},{-0.390222f,0.11662f,0.352786f}, +{-0.444934f,-0.0373236f,0.0384554f},{-0.215192f,0.210083f,0.100627f},{0.345137f,0.00202567f,0.153461f}, +{0.308855f,-0.0536767f,0.250738f},{-0.108379f,0.398566f,-0.000488712f},{-0.377316f,0.201955f,0.313469f}, +{-0.375985f,0.234179f,0.285843f},{-0.145947f,0.392849f,0.0136523f},{0.358667f,-0.0149577f,0.13352f}, +{-0.436697f,-0.00081026f,0.0371307f},{0.399283f,-0.148799f,0.133494f},{0.212189f,0.114485f,0.152053f}, +{0.103035f,-0.451593f,0.311926f},{0.120237f,-0.454622f,0.308067f},{0.00337286f,0.115282f,0.039375f}, +{-0.222993f,0.303122f,0.0742677f},{-0.475924f,0.161339f,0.133758f},{-0.120218f,0.428983f,-0.0409697f}, +{0.369708f,-0.0305328f,0.035433f},{-0.482419f,0.11327f,0.230475f},{0.367676f,-0.0728915f,-0.0214784f}, +{0.377534f,-0.0744864f,-0.000623756f},{0.401881f,-0.148638f,0.0949037f},{-0.0891837f,0.436147f,-0.0219092f}, +{-0.0690814f,0.436153f,-0.035285f},{-0.358043f,0.0803189f,0.354516f},{-0.358088f,0.233304f,0.28655f}, +{-0.279319f,0.131166f,0.262429f},{0.385425f,-0.113488f,0.172059f},{0.0420919f,0.1286f,0.114562f}, +{0.00494195f,0.120401f,0.0939713f},{-0.337381f,0.293245f,0.0591042f},{0.192235f,0.0807048f,0.225292f}, +{0.040645f,0.109752f,0.187454f},{-0.315466f,0.284177f,0.210495f},{-0.355651f,0.284788f,0.207601f}, +{0.230845f,0.0689946f,0.221376f},{-0.16468f,0.398669f,-0.0160702f},{-0.17619f,0.390978f,-0.014932f}, +{-0.262805f,-0.44967f,0.30907f},{-0.242143f,-0.449754f,0.311906f},{-0.374988f,0.341597f,-0.0915855f}, +{-0.352281f,0.342844f,-0.0752323f},{0.356976f,-0.0173049f,0.150998f},{0.362082f,-0.0148741f,0.0948973f}, +{0.380981f,-0.114093f,-0.0206617f},{-0.184435f,0.345584f,0.0454455f},{-0.16558f,0.340909f,0.0421787f}, +{-0.165709f,-0.44913f,0.288852f},{-0.337619f,0.365731f,-0.130787f},{-0.0571911f,-0.128935f,0.325623f}, +{-0.451268f,0.191614f,0.0565062f},{0.00446607f,0.0954117f,0.188926f},{-0.123356f,0.411967f,-0.0662551f}, +{-0.464734f,-0.114524f,0.0774059f},{-0.306225f,0.300492f,-0.0590077f},{-0.299421f,0.28583f,-0.0583004f}, +{-0.354731f,0.187467f,-0.0365647f},{-0.341722f,0.209897f,0.299611f},{-0.372673f,0.355731f,-0.115045f}, +{-0.385148f,0.355165f,-0.137288f},{0.300727f,0.0312016f,0.203305f},{0.36657f,-0.0529501f,0.171216f}, +{0.351934f,-0.00997397f,0.15514f},{0.369599f,-0.0327063f,0.114176f},{0.00206744f,0.0217871f,0.263509f}, +{-0.356776f,0.0418636f,0.346581f},{0.198151f,0.0257355f,0.254262f},{0.193315f,0.0190926f,0.258641f}, +{-0.427719f,0.00261084f,0.190399f},{-0.126475f,0.393608f,0.0137423f},{-0.296354f,0.0562426f,-0.0510337f}, +{-0.347265f,0.359448f,-0.298396f},{-0.479319f,0.22715f,0.16903f},{0.0213916f,0.0220122f,0.268834f}, +{0.136095f,-0.453715f,0.305103f},{-0.0334105f,0.025877f,0.251471f},{-0.467107f,0.112029f,0.0377737f}, +{0.173676f,0.0207453f,0.26203f},{-0.388287f,0.395016f,-0.225517f},{0.286335f,0.0563905f,0.187177f}, +{0.310623f,-0.0532523f,-0.0824153f},{0.380795f,-0.0549564f,0.0370728f},{0.11616f,0.0201923f,0.278827f}, +{-0.316392f,0.233002f,0.268442f},{-0.356088f,0.115495f,0.345635f},{0.0212115f,0.12235f,0.0571364f}, +{0.137176f,0.139404f,0.078647f},{-0.300958f,0.264255f,0.243015f},{0.331607f,0.0236327f,0.15067f}, +{0.251944f,-0.450976f,0.265889f},{0.216614f,-0.108614f,0.324626f},{0.0391209f,0.0243529f,0.272146f}, +{0.350159f,0.00359473f,0.094788f},{-0.396447f,-0.0920678f,-0.0587827f},{0.375457f,-0.0619272f,0.15114f}, +{0.378859f,-0.0548986f,0.11417f},{-0.389926f,0.0207453f,-0.016649f},{-0.39327f,0.168007f,-0.0230282f}, +{0.154043f,-0.0357931f,-0.124575f},{0.175965f,-0.0327128f,-0.11952f},{0.175354f,0.128549f,0.150741f}, +{0.13623f,0.136156f,0.134343f},{0.0985016f,0.136529f,0.115032f},{-0.131163f,0.107514f,0.129051f}, +{-0.108649f,0.108993f,0.0566349f},{-0.0375068f,0.0839973f,0.175737f},{-0.298868f,0.291508f,0.0583325f}, +{-0.192608f,0.14804f,0.134388f},{-0.146976f,0.401826f,-5.1427e-05f},{-0.260792f,-0.433774f,0.302235f}, +{-0.242266f,-0.432822f,0.30464f},{-0.223154f,-0.432128f,0.300621f},{0.0791068f,0.13325f,0.0571686f}, +{-0.472972f,0.157127f,0.17238f},{0.0598019f,0.0811099f,-0.0603196f},{-0.202678f,-0.451117f,0.299971f}, +{-0.185881f,-0.450905f,0.295753f},{-0.180223f,-0.444307f,0.292126f},{-0.295923f,0.435491f,-0.378264f}, +{-0.316868f,0.344902f,-0.112511f},{-0.109878f,0.101553f,0.133636f},{-0.413f,0.264339f,0.224707f}, +{-0.317749f,0.291528f,0.0568664f},{0.0993119f,0.117025f,0.191344f},{-0.468773f,-0.114106f,0.115938f}, +{0.247809f,0.0718048f,0.209132f},{-0.314025f,0.307527f,-0.0538375f},{-0.106804f,0.102318f,0.0163725f}, +{-0.108328f,0.0983956f,0.000623793f},{-0.124166f,0.104466f,-0.00183272f},{-0.430954f,-0.0187196f,0.0181731f}, +{0.359516f,-0.056757f,0.189949f},{0.360924f,-0.0149963f,0.114183f},{-0.203379f,0.301547f,0.0699592f}, +{0.043931f,0.0177872f,0.276653f},{-0.127337f,0.404874f,0.00522815f},{0.377033f,-0.0575802f,0.132555f}, +{0.396801f,-0.148973f,0.15276f},{-0.0911514f,0.443073f,-0.0347513f},{-0.0342529f,0.307598f,-0.0218771f}, +{0.250387f,0.0819974f,0.190296f},{0.0618919f,0.132337f,0.0979776f},{0.370126f,-0.0498955f,0.154092f}, +{-0.0687534f,0.038256f,0.2229f},{-0.0509598f,0.0395936f,0.228051f},{-0.0323752f,0.0376065f,0.242114f}, +{-0.0172824f,0.0393235f,0.24722f},{-0.376454f,0.0422173f,0.34442f},{0.101807f,-0.434835f,0.310807f}, +{0.117163f,-0.4376f,0.307746f},{0.137092f,-0.437143f,0.30435f},{0.157136f,-0.433986f,0.301926f}, +{-0.221996f,0.323739f,0.0583004f},{-0.204993f,0.322929f,0.060056f},{-0.183592f,0.321295f,0.0548343f}, +{0.0599755f,0.0178001f,0.277393f},{-0.371612f,0.134066f,0.346529f},{-0.279319f,0.293347f,0.151384f}, +{0.30418f,0.0563133f,0.149526f},{0.351574f,-0.0295231f,-0.00458504f},{-0.42516f,0.173576f,0.00514455f}, +{0.34978f,-0.0516575f,-0.0410598f},{-0.128784f,0.378316f,0.0167776f},{-0.374165f,0.0802225f,0.35258f}, +{0.335857f,-0.0209511f,-0.0349313f},{0.348609f,-0.0342754f,-0.0208739f},{0.403219f,-0.148555f,0.0756182f}, +{-0.35369f,-0.109778f,-0.0902029f},{-0.334462f,-0.109334f,-0.0956625f},{-0.376461f,-0.110086f,-0.0797852f}, +{-0.0688499f,0.453792f,-0.0389762f},{-0.059101f,0.454365f,-0.0439343f},{-0.241449f,0.312247f,0.0639594f}, +{-0.275866f,0.297437f,0.0577409f},{-0.2969f,0.288724f,0.040526f},{-0.12452f,0.104569f,0.134652f}, +{-0.0449085f,0.106698f,0.07684f},{-0.352564f,0.250764f,0.267805f},{-0.335652f,0.284769f,0.209781f}, +{-0.391682f,-0.0901193f,0.243947f},{0.358397f,-0.0700492f,-0.0368027f},{-0.455037f,0.160406f,0.031118f}, +{-0.207244f,-0.435819f,0.296801f},{-0.202177f,-0.427382f,0.290885f},{-0.184473f,-0.42978f,0.288955f}, +{-0.167245f,-0.431086f,0.284589f},{-0.145227f,-0.433259f,0.278943f},{-0.336938f,0.340073f,-0.0762226f}, +{-0.328333f,0.331713f,-0.0703771f},{-0.316347f,0.321379f,-0.0740426f},{-0.0900582f,-0.471773f,0.341359f}, +{-0.0745475f,-0.4318f,0.343346f},{-0.0611524f,-0.427793f,0.349043f},{-0.0524774f,-0.43423f,0.355699f}, +{-0.0348703f,-0.432513f,0.355416f},{0.174666f,0.132105f,0.133629f},{-0.183251f,0.359828f,0.0370985f}, +{0.249275f,0.0920421f,0.17031f},{0.0221054f,-0.433337f,0.336742f},{0.0347738f,-0.433311f,0.332144f}, +{0.0444905f,-0.433279f,0.329301f},{0.0604707f,-0.433246f,0.324092f},{0.0795826f,-0.433169f,0.317919f}, +{0.0923282f,-0.433124f,0.313591f},{0.174236f,-0.432449f,0.299399f},{0.231996f,0.02189f,0.248693f}, +{0.00507055f,0.11743f,0.131449f},{-0.234034f,0.228925f,0.155545f},{-0.29074f,0.399447f,-0.339243f}, +{-0.127562f,0.398585f,-0.0706215f},{-0.237796f,0.133533f,0.232089f},{-0.313929f,0.1495f,0.285862f}, +{0.381296f,-0.054815f,0.0948651f},{-0.145844f,0.379223f,0.0246809f},{-0.121864f,0.413382f,-0.00367832f}, +{-0.491158f,0.0606347f,0.172946f},{0.400736f,-0.130665f,0.0370921f},{0.0808816f,0.0201151f,0.278145f}, +{-0.186242f,0.340336f,-0.0426803f},{-0.156436f,0.340195f,-0.0631619f},{-0.224472f,-0.266313f,0.252731f}, +{-0.128655f,-0.114562f,-0.142401f},{-0.35713f,0.187769f,0.318234f},{-0.429134f,0.265882f,0.206386f}, +{-0.286734f,0.292544f,0.0523327f},{-0.331015f,0.359448f,-0.337012f},{0.100733f,0.101122f,0.215254f}, +{-0.0531848f,0.10417f,0.0738497f},{-0.14904f,0.117983f,0.0386033f},{-0.373393f,0.250249f,0.26711f}, +{-0.354667f,0.335179f,-0.0613485f},{-0.268779f,-0.426436f,0.293341f},{-0.139356f,-0.426771f,0.273869f}, +{0.0225041f,0.1217f,0.133989f},{-0.0367802f,0.104563f,0.0376901f},{-0.0706633f,0.0942864f,0.0189769f}, +{-0.0814154f,-0.420617f,0.334337f},{-0.0697952f,-0.416919f,0.335648f},{-0.0543552f,-0.414475f,0.34179f}, +{-0.0346131f,-0.419498f,0.351449f},{-0.0170702f,-0.416186f,0.34624f},{0.00188095f,-0.415935f,0.338169f}, +{0.0178869f,-0.420668f,0.334645f},{0.0227227f,-0.414559f,0.328748f},{0.040407f,-0.417208f,0.323945f}, +{0.0598148f,-0.417697f,0.317784f},{0.0748561f,-0.419485f,0.314562f},{0.0834925f,-0.416739f,0.312395f}, +{0.0984952f,-0.418398f,0.310614f},{0.117794f,-0.423285f,0.306016f},{0.137111f,-0.423485f,0.305263f}, +{0.156243f,-0.418347f,0.308177f},{0.0988103f,0.076422f,0.243336f},{0.155008f,0.0210347f,0.268956f}, +{-0.411038f,0.251838f,0.247265f},{-0.410543f,0.241999f,0.261284f},{-0.391753f,0.24632f,0.26695f}, +{-0.177489f,0.115868f,0.134214f},{-0.411945f,0.133436f,0.346246f},{-0.219327f,0.284595f,0.0821324f}, +{0.352751f,-0.0704608f,-0.0463586f},{0.398987f,-0.130729f,0.114176f},{0.116977f,-0.0373878f,-0.131128f}, +{-0.368583f,-0.130304f,-0.0862995f},{-0.3536f,-0.12826f,-0.09237f},{-0.334333f,-0.127784f,-0.0978939f}, +{0.273512f,0.0615929f,0.194058f},{-0.141073f,0.358805f,0.025742f},{0.0806308f,0.134607f,0.114048f}, +{-0.364178f,0.164824f,0.332523f},{0.155439f,0.131526f,0.151584f},{-0.31679f,0.289103f,0.040764f}, +{-0.321736f,0.316562f,-0.055458f},{-0.0391209f,-0.127449f,0.348703f},{-0.112816f,0.109611f,0.0410405f}, +{0.0798013f,-0.0758304f,0.343391f},{0.0260024f,0.107771f,0.182155f},{0.0239188f,0.112517f,0.170445f}, +{0.419128f,-0.26322f,0.170818f},{-0.261185f,-0.414012f,0.28657f},{-0.241172f,-0.412675f,0.287026f}, +{-0.223064f,-0.41227f,0.283753f},{-0.203772f,-0.416855f,0.282943f},{-0.184422f,-0.416925f,0.28019f}, +{-0.16511f,-0.417112f,0.276949f},{-0.152256f,-0.417196f,0.276872f},{-0.144545f,-0.412295f,0.271689f}, +{-0.130668f,-0.413524f,0.27093f},{-0.33933f,0.299868f,0.00039872f},{-0.454754f,0.269998f,0.156593f}, +{-0.355445f,0.290942f,0.187936f},{-0.373869f,0.292685f,0.171158f},{0.0630687f,-0.0750779f,0.341372f}, +{0.301582f,0.036359f,-0.0174399f},{-0.484972f,0.043542f,0.227652f},{-0.0878975f,-0.413839f,0.323211f}, +{-0.0364779f,-0.410373f,0.346343f},{0.117851f,-0.412816f,0.316787f},{0.137098f,-0.412019f,0.321604f}, +{0.153001f,-0.412225f,0.319591f},{0.17729f,-0.415324f,0.301707f},{-0.12652f,0.0335809f,0.221356f}, +{-0.107344f,0.0338317f,0.222655f},{-0.206402f,0.0359345f,0.223974f},{0.0985531f,0.0223723f,0.27902f}, +{0.386113f,-0.167943f,-0.0205717f},{0.059982f,0.0266615f,0.27331f},{-0.324829f,0.228083f,0.278538f}, +{-0.143298f,0.411575f,-0.0170091f},{0.230079f,0.0996624f,0.170142f},{0.00333428f,0.11907f,0.0747822f}, +{-0.0342851f,0.0952124f,0.1515f},{0.367097f,-0.0327707f,0.133501f},{0.305479f,-0.430276f,0.202965f}, +{0.325279f,-0.432378f,0.19436f},{-0.201129f,0.301309f,-0.0341211f},{-0.0687792f,0.41562f,-0.106234f}, +{0.285402f,-0.132542f,-0.130118f},{-0.0900582f,-0.113913f,-0.148195f},{-0.470496f,0.263483f,0.113199f}, +{0.327536f,0.0208932f,0.172078f},{0.349542f,-0.0149705f,0.174387f},{-0.0496415f,0.456307f,-0.0524292f}, +{0.284888f,-0.115135f,0.298344f},{0.155542f,0.11745f,0.189016f},{-0.0713836f,0.10136f,0.0766085f}, +{-0.462342f,0.0766857f,0.0217871f},{0.0812353f,-0.0669496f,0.329192f},{-0.304309f,0.282583f,-0.0389569f}, +{-0.0486705f,-0.130169f,0.338889f},{-0.20372f,-0.40854f,0.273586f},{-0.184428f,-0.408527f,0.272853f}, +{-0.16513f,-0.408553f,0.27212f},{-0.0558985f,-0.105444f,-0.178663f},{-0.0901482f,0.0915726f,0.00273948f}, +{-0.0718337f,-0.401498f,0.322466f},{-0.0522974f,-0.402662f,0.335115f},{-0.0364715f,-0.402199f,0.338381f}, +{-0.017321f,-0.402579f,0.335468f},{0.0019967f,-0.39737f,0.321578f},{0.0213466f,-0.399299f,0.320356f}, +{0.0406707f,-0.398978f,0.322536f},{0.0598727f,-0.40427f,0.318427f},{0.0792611f,-0.403119f,0.324491f}, +{0.0985981f,-0.40245f,0.327597f},{0.117851f,-0.402424f,0.330645f},{0.135806f,-0.396515f,0.336793f}, +{0.143484f,-0.402212f,0.331796f},{0.156288f,-0.402759f,0.327501f},{0.174705f,-0.396778f,0.321295f}, +{-0.393251f,0.175088f,0.330491f},{-0.266657f,0.267921f,0.222089f},{0.0760586f,0.0268223f,0.274274f}, +{-0.374171f,0.17521f,0.329848f},{-0.355291f,0.17002f,0.325764f},{-0.338089f,0.249195f,0.268101f}, +{-0.320527f,0.247998f,0.262757f},{-0.415784f,-0.109244f,-0.0416578f},{-0.395386f,0.21018f,0.303913f}, +{-0.373586f,0.262403f,0.248712f},{0.32452f,-0.413421f,0.205273f},{0.343323f,-0.413382f,0.207029f}, +{0.362815f,-0.411697f,0.208823f},{-0.164191f,0.322324f,0.0389441f},{0.313955f,0.0451368f,0.154735f}, +{-0.158712f,0.3742f,0.0292852f},{0.359059f,-0.0334201f,0.173133f},{-0.0546896f,0.469323f,-0.0593164f}, +{-0.0400727f,0.470847f,-0.0605961f},{0.0435195f,0.129552f,0.0968393f},{-0.0157969f,0.114247f,0.0771229f}, +{0.117157f,0.11671f,0.191499f},{0.174936f,0.0809942f,0.231182f},{-0.335555f,0.259117f,0.257201f}, +{-0.375753f,0.328079f,-0.0684286f},{-0.333819f,0.326189f,-0.0550915f},{0.079872f,0.130202f,0.15094f}, +{-0.299608f,0.266673f,-0.01771f},{-0.319523f,0.302595f,-0.0367641f},{-0.338307f,0.319167f,-0.0382881f}, +{-0.259654f,-0.401923f,0.265567f},{-0.243262f,-0.398174f,0.263728f},{-0.223244f,-0.399826f,0.264204f}, +{-0.202125f,-0.398084f,0.262995f},{-0.184454f,-0.395691f,0.263561f},{-0.165162f,-0.39537f,0.266602f}, +{-0.145806f,-0.395466f,0.26576f},{-0.125568f,-0.396727f,0.264596f},{-0.33623f,0.347474f,-0.0918877f}, +{-0.320829f,0.338645f,-0.0939005f},{-0.312437f,0.328272f,-0.0923378f},{-0.0557121f,-0.394527f,0.325006f}, +{-0.0364844f,-0.394418f,0.326536f},{-0.0172053f,-0.394688f,0.322485f},{0.0600206f,-0.393145f,0.332857f}, +{0.0792804f,-0.393036f,0.334504f},{0.0985531f,-0.392971f,0.336433f},{0.117832f,-0.39245f,0.340124f}, +{0.156339f,-0.393138f,0.334845f},{0.0406707f,0.0795794f,0.232591f},{0.398286f,-0.112762f,0.0371114f}, +{-0.471802f,0.0156329f,0.116536f},{-0.261165f,0.293142f,0.0956368f},{-0.389849f,0.0793929f,0.346034f}, +{-0.306746f,0.246095f,0.256661f},{-0.298302f,0.250976f,0.25041f},{-0.280772f,0.250551f,0.242918f}, +{-0.46694f,0.210668f,0.209247f},{-0.390171f,0.13925f,0.349031f},{0.0996849f,0.137295f,0.0955082f}, +{0.392878f,-0.114517f,0.148445f},{0.387965f,-0.0771036f,0.114151f},{-0.167509f,0.121359f,0.0380695f}, +{-0.292045f,0.246281f,-0.0204173f},{-0.305653f,0.278718f,-0.0245008f},{-0.330192f,0.311321f,-0.0337481f}, +{-0.353953f,0.322311f,-0.0375743f},{-0.241449f,-0.391994f,0.247227f},{0.133755f,0.0177294f,0.27601f}, +{-0.225064f,-0.391692f,0.245606f},{-0.206987f,-0.391981f,0.254288f},{-0.108894f,-0.390431f,0.265098f}, +{-0.12789f,0.110627f,0.113739f},{0.061924f,-0.0584675f,0.301945f},{-0.164365f,0.120048f,0.0967365f}, +{-0.355046f,0.31082f,-0.0183531f},{-0.0707984f,-0.389782f,0.304061f},{-0.0525739f,-0.386701f,0.310003f}, +{-0.0365165f,-0.386663f,0.310286f},{-0.0172696f,-0.386669f,0.309868f},{-0.00438252f,-0.386444f,0.312517f}, +{0.00376513f,-0.378708f,0.321295f},{0.0171538f,-0.381898f,0.327108f},{0.0257387f,-0.378104f,0.334478f}, +{0.0406578f,-0.379377f,0.337777f},{0.0599562f,-0.378895f,0.342388f},{0.077647f,-0.380804f,0.345982f}, +{0.0985402f,-0.38256f,0.350053f},{0.117819f,-0.380734f,0.347635f},{0.137118f,-0.382952f,0.347024f}, +{0.156358f,-0.378824f,0.342722f},{0.174801f,-0.375428f,0.33705f},{-0.293306f,0.246314f,-0.0577537f}, +{-0.475679f,0.0771036f,0.0506543f},{-0.0662712f,-0.117623f,-0.186586f},{-0.344249f,0.391113f,-0.324369f}, +{-0.370872f,0.116569f,0.349307f},{-0.281731f,0.208894f,-0.0190347f},{-0.5f,0.077991f,0.134201f}, +{0.324681f,0.0208289f,3.86026e-05f},{-0.229899f,0.187364f,0.172335f},{0.0615768f,0.0731359f,0.242584f}, +{0.0284267f,0.126562f,0.092563f},{-0.288219f,0.291238f,-0.147847f},{-0.4415f,-0.0193434f,0.147532f}, +{-0.467686f,0.192663f,0.210759f},{-0.0701425f,0.0924215f,0.13388f},{-0.0727147f,0.0979968f,0.0376001f}, +{0.323954f,-0.394007f,0.213292f},{0.342847f,-0.391544f,0.213176f},{0.360828f,-0.392122f,0.216964f}, +{0.383348f,-0.393878f,0.215118f},{-0.147085f,0.03751f,0.223375f},{-0.458689f,0.0765956f,0.0121733f}, +{-0.302842f,0.289637f,-0.244211f},{-0.340597f,0.0807112f,0.352227f},{0.289364f,-0.0535095f,0.267676f}, +{-0.454792f,0.0574001f,0.0140382f},{-0.0704125f,0.457548f,-0.0739462f},{-0.186518f,0.37503f,0.0173564f}, +{-0.296971f,0.266294f,-0.0383975f},{-0.372493f,-0.095058f,0.263213f},{0.211855f,0.119019f,0.132903f}, +{0.00716052f,-0.077483f,0.319726f},{0.051172f,0.127642f,0.144291f},{-0.146391f,0.0978682f,0.152773f}, +{-0.220929f,-0.380824f,0.227735f},{-0.216241f,-0.383261f,0.241401f},{-0.203604f,-0.379036f,0.247278f}, +{-0.190981f,-0.378271f,0.254436f},{-0.181271f,-0.377904f,0.258603f},{-0.16522f,-0.377525f,0.26302f}, +{-0.145799f,-0.377525f,0.263201f},{-0.126462f,-0.377846f,0.259464f},{-0.107247f,-0.382239f,0.256744f}, +{0.322456f,0.0356259f,0.151847f},{-0.0665799f,-0.379133f,0.281078f},{-0.0513521f,-0.377171f,0.28455f}, +{-0.039391f,-0.375152f,0.289258f},{-0.0298029f,-0.374271f,0.298216f},{-0.0215267f,-0.374946f,0.305939f}, +{-0.0127359f,-0.370882f,0.315977f},{0.0824057f,-0.373371f,0.353378f},{0.0985209f,-0.372914f,0.357963f}, +{0.117813f,-0.373351f,0.35251f},{0.137092f,-0.373358f,0.352329f},{0.382756f,-0.0957782f,-0.00136328f}, +{0.394679f,-0.112903f,0.133449f},{-0.389026f,0.097283f,0.348497f},{-0.431128f,0.0196778f,0.305193f}, +{-0.480425f,0.0439793f,0.0615414f},{-0.359651f,0.151455f,0.335198f},{-0.371837f,0.151802f,0.340677f}, +{-0.390171f,0.152741f,0.343436f},{-0.485364f,0.0427446f,0.0765506f},{0.0792932f,0.075213f,0.243651f}, +{0.211874f,0.12071f,0.11453f},{0.368583f,-0.386328f,0.219067f},{0.378274f,-0.386367f,0.21906f}, +{-0.337825f,0.0426482f,0.344201f},{0.249532f,0.0981319f,0.152027f},{0.249686f,0.101135f,0.132594f}, +{0.362307f,-0.0139031f,0.0751937f},{0.00535994f,0.324311f,-0.055323f},{-0.38965f,0.24396f,-0.0210861f}, +{-0.449429f,0.0762419f,-0.000784522f},{-0.313697f,0.286287f,-0.0181602f},{0.191984f,0.110247f,0.189306f}, +{0.00652389f,0.103746f,0.175383f},{-0.165008f,0.392573f,0.00273305f},{-0.35423f,0.294576f,0.173287f}, +{-0.148886f,0.0846597f,0.176888f},{0.207527f,0.0906723f,0.204206f},{-0.107138f,-0.373834f,0.252796f}, +{-0.0889843f,-0.372149f,0.249992f},{-0.304759f,0.320221f,-0.0956882f},{-0.127182f,0.0754059f,0.190611f}, +{-0.108315f,0.0574837f,0.2049f},{-0.0575576f,-0.37148f,0.267104f},{-0.00203533f,-0.363551f,0.329006f}, +{0.00388088f,-0.355828f,0.334677f},{0.0214687f,-0.361326f,0.336883f},{0.0406707f,-0.360625f,0.344819f}, +{0.055667f,-0.361725f,0.348799f},{0.0642198f,-0.358439f,0.352214f},{0.0792354f,-0.359455f,0.35694f}, +{0.0985466f,-0.358947f,0.36204f},{0.117877f,-0.3595f,0.356509f},{0.137105f,-0.359757f,0.353866f}, +{0.156378f,-0.360297f,0.347982f},{0.175998f,-0.359152f,0.34514f},{0.0201054f,0.075766f,0.229575f}, +{-0.297833f,0.399453f,-0.320176f},{-0.259847f,0.0370792f,0.247214f},{0.138578f,0.0243465f,0.272544f}, +{-0.165155f,0.0381917f,0.222835f},{-0.403103f,0.116607f,0.351969f},{0.347426f,-0.0926658f,-0.0576316f}, +{-0.0582393f,0.457156f,-0.0944021f},{-0.298656f,0.344491f,-0.167519f},{-0.411636f,0.151661f,0.338497f}, +{0.0989132f,0.137185f,0.0767821f},{-0.409958f,0.207415f,0.298743f},{0.293691f,0.0650204f,0.156272f}, +{-0.0904505f,0.103559f,0.0759075f},{0.330603f,-0.375428f,0.21762f},{0.34297f,-0.373139f,0.213922f}, +{0.360808f,-0.371872f,0.216546f},{0.368557f,-0.377094f,0.219009f},{0.381483f,-0.372547f,0.219614f}, +{-0.43289f,0.251657f,0.227395f},{-0.313485f,0.255098f,0.254429f},{0.330089f,-0.0531044f,0.230706f}, +{0.30589f,0.0219928f,0.206784f},{0.38237f,-0.0548664f,0.0563198f},{-0.0897302f,0.0956175f,0.133906f}, +{-0.0879168f,0.0444037f,0.215536f},{-0.184441f,0.134401f,0.132915f},{0.19945f,0.101727f,0.198026f}, +{0.193605f,0.0958233f,0.20917f},{-0.354416f,0.26648f,0.245555f},{-0.279338f,0.282004f,0.207678f}, +{-0.355966f,0.29347f,0.0546414f},{-0.354564f,0.296814f,0.0935083f},{-0.201322f,-0.362393f,0.24315f}, +{-0.188229f,-0.361403f,0.253625f},{-0.179206f,-0.358567f,0.258988f},{-0.165188f,-0.359371f,0.263709f}, +{-0.145793f,-0.359403f,0.263953f},{-0.126449f,-0.359776f,0.259644f},{-0.107138f,-0.360413f,0.252352f}, +{-0.0891901f,-0.360021f,0.246384f},{-0.280136f,0.293167f,0.0344748f},{-0.461268f,0.0941706f,0.0216842f}, +{-0.336494f,0.291135f,0.190476f},{-0.0558021f,-0.358059f,0.265053f},{-0.0402142f,-0.356979f,0.28747f}, +{-0.0301695f,-0.353416f,0.302608f},{-0.0231215f,-0.359397f,0.309508f},{-0.0135977f,-0.353481f,0.321681f}, +{-0.410967f,0.0985306f,0.345783f},{-0.396711f,0.102801f,0.350773f},{-0.184447f,0.0336581f,0.224674f}, +{-0.111086f,0.423491f,-0.0134915f},{-0.415186f,0.115971f,0.347674f},{-0.128141f,0.419125f,-0.017665f}, +{-0.290862f,0.286274f,-0.096402f},{0.0463683f,0.437201f,-0.168316f},{0.342841f,-0.1328f,-0.0902865f}, +{0.392421f,-0.0813864f,0.0755796f},{0.356911f,-0.0199286f,0.0177872f},{-0.467024f,-0.0708209f,0.0964471f}, +{0.120951f,0.0267194f,0.27448f},{0.39419f,-0.368914f,0.21897f},{0.215842f,0.0233819f,0.252313f}, +{0.273377f,0.0811099f,0.153577f},{-0.455577f,-0.0533295f,0.056577f},{-0.372969f,0.334041f,-0.0745571f}, +{-0.0136491f,0.113437f,0.0573872f},{-0.357477f,0.241433f,0.278769f},{-0.318064f,0.290531f,0.191627f}, +{-0.335851f,0.278583f,0.225047f},{0.0617826f,0.127681f,0.153719f},{-0.296881f,0.294119f,0.0776695f}, +{-0.319003f,0.265046f,0.24715f},{-0.373631f,0.290094f,0.039928f},{0.00397734f,0.118176f,0.0573101f}, +{-0.260651f,0.281702f,0.190142f},{-0.0537893f,0.0964342f,0.0183338f},{-0.0705605f,-0.359037f,0.248056f}, +{0.0407607f,0.122652f,0.154567f},{-0.0711907f,0.0987235f,0.114543f},{-0.393714f,0.293534f,0.135147f}, +{-0.398814f,0.379653f,-0.243941f},{-0.390955f,-0.0752966f,-0.0572136f},{0.0226327f,0.119938f,0.0371885f}, +{0.0990097f,0.135777f,0.0580818f},{0.118314f,0.138742f,0.0781197f},{0.193155f,0.128774f,0.0961062f}, +{0.00290986f,-0.337115f,0.339397f},{0.021816f,-0.338973f,0.343912f},{0.0373589f,-0.346973f,0.348111f}, +{0.0422784f,-0.339674f,0.352953f},{0.0599305f,-0.341577f,0.356876f},{0.0792289f,-0.34159f,0.357185f}, +{0.0984695f,-0.341307f,0.36004f},{0.117845f,-0.341372f,0.359075f},{0.13715f,-0.341886f,0.354227f}, +{0.154841f,-0.339893f,0.35168f},{0.159619f,-0.346709f,0.349931f},{0.174454f,-0.341944f,0.348754f}, +{0.137098f,0.0797274f,0.234539f},{0.117382f,0.0827112f,0.233671f},{0.370126f,-0.168323f,-0.0577537f}, +{0.174396f,0.134163f,0.114022f},{-0.37837f,0.0210347f,-0.026295f},{-0.108617f,0.021382f,0.232044f}, +{-0.244954f,0.282261f,0.132735f},{0.134796f,0.118331f,0.188508f},{0.080155f,0.135185f,0.0954117f}, +{0.154551f,0.135873f,0.13087f},{-0.0725411f,0.100325f,0.0573808f},{0.344815f,-0.357931f,0.214012f}, +{0.362172f,-0.354844f,0.214276f},{0.379804f,-0.352291f,0.21708f},{0.384653f,-0.358947f,0.218803f}, +{0.397688f,-0.359384f,0.218797f},{-0.3901f,0.188508f,0.322067f},{-0.0718144f,-0.112768f,-0.167171f}, +{-0.0893123f,0.0619337f,-0.0559532f},{-0.196769f,0.307952f,0.0660493f},{-0.0233851f,-0.14159f,0.366895f}, +{0.234459f,0.103842f,0.15258f},{0.360461f,-0.0519726f,-0.020398f},{0.29238f,0.0452911f,0.195595f}, +{-0.375309f,0.320311f,-0.0550657f},{-0.316623f,0.297585f,0.135912f},{-0.372744f,0.282557f,0.208784f}, +{-0.280592f,0.294556f,0.0755024f},{0.0436802f,0.118305f,0.171557f},{0.288746f,0.0260571f,-0.0418957f}, +{-0.129755f,0.113996f,0.0951288f},{-0.180081f,-0.338169f,0.260667f},{-0.1652f,-0.341037f,0.266506f}, +{-0.145831f,-0.340947f,0.267734f},{-0.126501f,-0.341346f,0.263303f},{-0.111459f,-0.340401f,0.257233f}, +{-0.102952f,-0.343745f,0.252982f},{-0.0870229f,-0.340838f,0.24742f},{-0.0707341f,-0.340246f,0.248249f}, +{-0.0517122f,0.0829363f,0.168059f},{0.210993f,0.110421f,0.169995f},{-0.413405f,0.28711f,0.152953f}, +{-0.497717f,0.108736f,0.150156f},{0.0393395f,0.123218f,0.0331629f},{0.23114f,0.112247f,0.112472f}, +{-0.130822f,0.421536f,-0.0386354f},{-0.0864056f,0.0360117f,0.224057f},{-0.0163243f,0.0771229f,0.207485f}, +{-0.00137296f,0.0807627f,0.209697f},{-0.330996f,0.341243f,-0.336857f},{-0.0541558f,0.100904f,0.114061f}, +{-0.126514f,0.0204431f,0.231858f},{-0.301518f,0.303784f,-0.0767113f},{-0.0140221f,0.0699463f,0.219871f}, +{0.364416f,-0.0319861f,0.0164239f},{-0.224549f,0.247941f,0.115726f},{0.274798f,-0.0509115f,0.27484f}, +{0.00366868f,0.07248f,0.224784f},{-0.287434f,0.264455f,0.238854f},{0.370429f,-0.0281663f,0.0755924f}, +{-0.486129f,0.0955853f,0.211286f},{0.402524f,-0.352805f,0.217183f},{-0.392492f,-0.263876f,0.0960741f}, +{-0.373374f,0.363596f,-0.131899f},{-0.14733f,0.115932f,0.0203659f},{0.0461496f,0.417517f,-0.14905f}, +{-0.033507f,0.477419f,-0.0714961f},{0.0626121f,-0.0658371f,0.325713f},{-0.375084f,-0.149693f,0.26284f}, +{-0.239854f,-0.356509f,0.0785891f},{0.0611009f,0.100196f,0.210476f},{-0.317877f,0.29727f,0.153204f}, +{0.0992733f,-0.0652519f,0.325842f},{-0.278129f,0.268152f,0.230417f},{-0.015932f,0.110305f,0.0387448f}, +{-0.0342465f,0.10972f,0.0948008f},{-0.412993f,0.28383f,0.1722f},{-0.0337063f,0.0961191f,0.00265588f}, +{-0.0527154f,0.0908974f,-0.00167195f},{-0.0925019f,0.43587f,-0.0756246f},{0.0438153f,0.125867f,0.0425581f}, +{0.174287f,0.134928f,0.0941706f},{0.0213144f,-0.318408f,0.354863f},{0.0406578f,-0.323224f,0.361603f}, +{0.0599305f,-0.322896f,0.366046f},{0.0792418f,-0.323166f,0.36249f},{0.097312f,-0.323829f,0.363519f}, +{0.117806f,-0.327424f,0.364271f},{0.138397f,-0.323951f,0.364387f},{0.157805f,-0.321604f,0.357699f}, +{0.177978f,-0.320755f,0.346786f},{0.027848f,0.0706215f,0.238475f},{-0.223636f,0.0383203f,0.226314f}, +{0.156378f,0.0796759f,0.235767f},{0.319929f,0.0325649f,0.169229f},{-0.147516f,0.116594f,0.0961384f}, +{-0.313749f,0.293438f,0.17811f},{0.136738f,0.138626f,0.116125f},{0.362358f,-0.337057f,0.210244f}, +{0.381406f,-0.336684f,0.213376f},{0.400826f,-0.336677f,0.215897f},{0.419559f,-0.337655f,0.210829f}, +{0.394679f,-0.0948008f,0.0948716f},{0.394659f,-0.0958233f,0.0384039f},{-0.407849f,0.0805119f,0.336343f}, +{-0.446053f,0.00109963f,0.0566477f},{-0.35713f,0.225485f,-0.0908974f},{-0.290335f,0.285129f,-0.168438f}, +{-0.410299f,-0.0898621f,0.228141f},{-0.392196f,-0.111135f,0.247098f},{-0.0742517f,0.0636057f,0.19465f}, +{-0.239397f,0.266126f,0.172747f},{-0.353374f,0.279753f,0.221408f},{-0.342693f,0.295778f,0.0180895f}, +{-0.0354876f,0.101129f,0.021517f},{-0.394749f,0.288923f,0.172605f},{-0.167168f,-0.32107f,0.268577f}, +{-0.145838f,-0.322485f,0.272036f},{-0.126526f,-0.322742f,0.268795f},{-0.107189f,-0.323552f,0.26003f}, +{-0.0880133f,-0.319681f,0.25241f},{-0.0712421f,-0.318022f,0.250693f},{-0.0627793f,-0.324871f,0.257233f}, +{-0.410318f,-0.0753159f,-0.0372528f},{0.251095f,0.101746f,0.11134f},{0.295132f,0.0662294f,0.136947f}, +{0.00121216f,-0.316614f,0.345314f},{0.0165557f,-0.3217f,0.349564f},{0.10499f,-0.317848f,0.370618f}, +{0.117826f,-0.31788f,0.370683f},{0.130649f,-0.317958f,0.36993f},{0.176743f,0.0583454f,0.244372f}, +{0.00338572f,0.0562169f,0.241201f},{0.117774f,0.062088f,0.253291f},{-0.497955f,0.0649368f,0.134272f}, +{-0.0534099f,0.0723128f,0.189705f},{-0.0364844f,0.0692196f,0.207273f},{-0.14578f,0.0250796f,0.232591f}, +{-0.0344716f,0.0794894f,0.189962f},{-0.374126f,0.210926f,0.306852f},{-0.238844f,0.227845f,0.173602f}, +{-0.410041f,0.290004f,0.114492f},{0.193695f,0.128131f,0.11462f},{-0.291917f,0.284917f,-0.187685f}, +{-0.185264f,0.100659f,-0.0401788f},{0.378094f,-0.0563068f,0.0186489f},{-0.335382f,0.168773f,-0.0424552f}, +{-0.433976f,0.0221343f,0.0171313f},{-0.0405421f,0.318511f,-0.00552392f},{-0.446175f,0.191441f,0.0372014f}, +{-0.303505f,0.396077f,-0.300685f},{-0.369811f,0.296563f,0.0990451f},{-0.41217f,0.00878431f,0.29855f}, +{-0.294347f,0.26549f,-0.148452f},{-0.355812f,0.360175f,-0.111141f},{-0.122121f,0.0838623f,0.17701f}, +{0.0430565f,0.127186f,0.132041f},{0.251764f,0.0635028f,0.215164f},{-0.414492f,0.283952f,0.0753931f}, +{0.0616346f,0.114093f,0.190187f},{-0.393785f,0.291817f,0.153937f},{-0.21853f,0.153397f,0.19463f}, +{0.0606958f,0.12844f,0.0401531f},{0.156468f,0.137963f,0.0765185f},{0.212903f,0.120986f,0.0972252f}, +{0.285743f,0.0747886f,0.133443f},{0.322745f,0.0380438f,0.133648f},{0.0598791f,0.122337f,0.171763f}, +{0.00871031f,-0.310562f,0.351905f},{0.022324f,-0.300466f,0.358908f},{0.0406643f,-0.300415f,0.366676f}, +{0.0599112f,-0.304537f,0.371184f},{0.0776341f,-0.302247f,0.37139f},{0.0824636f,-0.309083f,0.368606f}, +{0.0985209f,-0.304286f,0.373448f},{0.117813f,-0.304202f,0.374747f},{0.137195f,-0.304408f,0.372367f}, +{0.154828f,-0.298685f,0.366991f},{0.0213273f,0.057548f,0.247812f},{0.0985338f,0.0622102f,0.25522f}, +{0.0792868f,0.0621138f,0.253786f},{0.058355f,0.0599402f,0.252802f},{-0.315717f,0.074795f,-0.0470338f}, +{-0.147941f,-0.114858f,-0.139577f},{-0.499453f,0.0780746f,0.153526f},{0.0407157f,0.0706859f,0.241639f}, +{-0.495396f,0.0597666f,0.154915f},{-0.497685f,0.0649883f,0.147159f},{-0.228092f,-0.345076f,0.134433f}, +{-0.294791f,0.267618f,-0.0566284f},{0.390299f,-0.0928009f,0.13505f},{-0.460947f,-0.0740812f,0.151326f}, +{0.34706f,-0.0158773f,-0.00152405f},{-0.109517f,-0.0777853f,-0.137243f},{0.376653f,-0.0786599f,0.17076f}, +{0.365869f,-0.0745892f,0.190193f},{0.363696f,-0.318266f,0.207685f},{0.381502f,-0.319244f,0.204617f}, +{0.40073f,-0.318884f,0.209607f},{0.420138f,-0.319025f,0.208566f},{0.437951f,-0.319417f,0.20245f}, +{0.369933f,-0.0920999f,-0.0271052f},{0.368705f,-0.130832f,-0.0560946f},{-0.049873f,-0.115167f,-0.203183f}, +{0.211038f,0.0167455f,0.257136f},{-0.379412f,0.294782f,0.094132f},{-0.26359f,-0.323951f,0.152619f}, +{-0.262368f,-0.314035f,0.17366f},{-0.237912f,-0.316794f,0.186206f},{-0.0326967f,0.10682f,0.0438636f}, +{-0.20235f,0.169075f,0.116073f},{0.118134f,-0.0765313f,0.340639f},{0.21082f,0.0727243f,0.222758f}, +{0.273814f,0.0845117f,0.133552f},{-0.168879f,-0.303199f,0.271104f},{-0.145831f,-0.303958f,0.276428f}, +{-0.130816f,-0.302563f,0.275625f},{-0.122237f,-0.305829f,0.272621f},{-0.106399f,-0.303109f,0.263882f}, +{-0.0943796f,-0.310286f,0.256275f},{-0.0640912f,-0.298698f,0.260358f},{0.116778f,0.137475f,0.0591364f}, +{-0.00123792f,-0.300711f,0.346651f},{0.0064017f,-0.29565f,0.352953f},{0.193656f,0.0561783f,0.240912f}, +{0.0406964f,0.0575415f,0.251998f},{-0.454329f,0.265078f,0.171268f},{-0.374191f,0.188579f,0.321874f}, +{0.393528f,-0.0958104f,0.112845f},{-0.28784f,0.306234f,-0.186463f},{-0.294412f,0.22542f,-0.0373107f}, +{0.116006f,0.0725186f,0.244153f},{-0.406286f,0.187454f,0.318363f},{0.0225812f,0.0918942f,0.209479f}, +{0.00791934f,0.0883766f,0.206103f},{0.389103f,-0.0944214f,0.0165911f},{0.0820649f,0.110228f,0.202257f}, +{0.34052f,-0.0172085f,-0.0195042f},{-0.0263304f,0.489116f,-0.0886531f},{0.248947f,0.0197486f,0.243478f}, +{-0.168088f,0.11853f,0.0191827f},{-0.316135f,0.285476f,0.0214334f},{0.199309f,0.123707f,0.138098f}, +{-0.130539f,0.340433f,-0.0769428f},{-0.168757f,0.282673f,-0.0395293f},{-0.262477f,-0.306961f,0.190913f}, +{-0.248754f,-0.306363f,0.199158f},{-0.241404f,-0.30264f,0.209144f},{-0.226517f,-0.30462f,0.214861f}, +{-0.220517f,-0.297997f,0.228745f},{-0.334128f,0.292537f,0.0193499f},{0.12034f,0.102511f,0.213646f}, +{-0.00977785f,0.106678f,0.150645f},{-0.158596f,-0.299476f,0.275477f},{-0.0922382f,-0.295058f,0.258191f}, +{-0.0836661f,-0.298428f,0.254474f},{-0.0729784f,-0.298473f,0.253889f},{0.26649f,0.0916369f,0.117077f}, +{-0.190666f,0.149082f,0.0814894f},{0.0624771f,-0.285798f,0.370689f},{0.0792096f,-0.282055f,0.372721f}, +{0.0985145f,-0.281901f,0.37465f},{0.117839f,-0.282023f,0.372728f},{0.135021f,-0.285078f,0.371737f}, +{-0.384634f,0.285682f,-0.0195363f},{-0.365921f,0.285611f,-0.167583f},{-0.436446f,-0.072017f,-0.00124753f}, +{-0.393296f,0.420102f,-0.300125f},{0.117993f,0.13842f,0.114485f},{-0.00438894f,0.0613614f,0.234333f}, +{0.138333f,0.0583068f,0.250847f},{0.156365f,0.0574451f,0.249677f},{-0.0237452f,0.0260378f,0.254352f}, +{-0.0153532f,0.492762f,-0.111482f},{0.377837f,-0.168593f,-0.0397929f},{-0.185393f,0.0721263f,0.186496f}, +{-0.483801f,0.0392528f,0.243741f},{0.042587f,0.0987364f,0.206804f},{-0.224215f,0.077676f,-0.0580238f}, +{0.230002f,-0.0166554f,-0.0977975f},{-0.4969f,0.0778046f,0.108485f},{0.367573f,-0.299026f,0.205903f}, +{0.380936f,-0.299971f,0.193582f},{0.400666f,-0.305829f,0.201531f},{0.420099f,-0.305752f,0.203434f}, +{0.436356f,-0.306016f,0.20063f},{0.341741f,-5.1417e-05f,0.171615f},{-0.202093f,0.36168f,-0.0182052f}, +{-0.201186f,0.151178f,0.0380438f},{-0.488078f,0.0601395f,0.0764606f},{-0.448252f,-0.0895855f,0.0194399f}, +{-0.393412f,0.344047f,-0.149654f},{0.117459f,0.135951f,0.132729f},{-0.164313f,0.113675f,-0.000199332f}, +{0.0989518f,0.127655f,0.167152f},{-0.0500788f,-0.228308f,0.323366f},{-0.27694f,-0.0338896f,-0.106755f}, +{-0.370396f,0.0185782f,-0.0336966f},{-0.295794f,-0.126658f,-0.111752f},{-0.0936851f,-0.131616f,-0.151423f}, +{-0.238973f,-0.29491f,0.222932f},{-0.202286f,-0.297161f,0.245465f},{-0.0333719f,0.109694f,0.0763513f}, +{-0.0296614f,0.0897592f,0.167371f},{0.0786695f,0.123347f,0.175994f},{-0.168943f,-0.283489f,0.272975f}, +{-0.160821f,-0.281419f,0.276036f},{-0.145825f,-0.285753f,0.278242f},{-0.129748f,-0.285824f,0.277483f}, +{-0.120128f,-0.286074f,0.274557f},{-0.108096f,-0.280833f,0.269747f},{0.400743f,-0.26821f,0.171223f}, +{0.00295487f,-0.278332f,0.355899f},{0.0232565f,-0.279888f,0.357892f},{0.0406385f,-0.278293f,0.364316f}, +{0.0567345f,-0.277907f,0.368689f},{0.141478f,-0.276448f,0.368638f},{0.156448f,-0.27821f,0.365229f}, +{0.175676f,-0.279399f,0.358818f},{0.211045f,0.0524614f,0.239362f},{0.214877f,0.0620109f,0.23178f}, +{0.232105f,0.0548535f,0.232835f},{-0.145909f,0.111578f,0.00139547f},{-0.0464005f,0.0519598f,0.221433f}, +{-0.0185621f,0.057683f,0.231478f},{-0.0368445f,0.0611492f,0.217067f},{-0.0323945f,0.0551622f,0.226597f}, +{-0.19781f,0.074332f,0.192772f},{0.192299f,0.127025f,0.132755f},{-0.205212f,0.0821774f,-0.0591942f}, +{0.388569f,-0.0771293f,0.0370728f},{0.400678f,-0.297257f,0.19555f},{0.420054f,-0.297135f,0.198842f}, +{0.441655f,-0.301283f,0.19654f},{-0.335304f,0.297888f,0.131488f},{-0.0276101f,0.469104f,-0.12489f}, +{0.352101f,-0.0110286f,0.0177551f},{0.360448f,-0.0154014f,0.0383075f},{-0.279878f,-0.245182f,0.263554f}, +{-0.287602f,0.306273f,-0.167165f},{0.193502f,0.121919f,0.155243f},{-0.285531f,0.295643f,0.136697f}, +{-0.313794f,-0.127957f,-0.107437f},{-0.355831f,0.0205974f,-0.0426095f},{-0.130803f,0.359609f,-0.077766f}, +{-0.186859f,0.28138f,-0.0334073f},{-0.24433f,-0.134356f,-0.131822f},{-0.167233f,-0.133996f,-0.132356f}, +{-0.223951f,-0.281438f,0.243188f},{-0.20572f,-0.282975f,0.250712f},{-0.182113f,0.1366f,0.0737919f}, +{0.135484f,0.108517f,0.202894f},{-0.18356f,-0.279534f,0.265901f},{-0.0879747f,-0.278223f,0.260789f}, +{-0.0702068f,-0.279232f,0.261265f},{0.305427f,0.0570914f,0.132311f},{-0.375142f,0.289232f,0.0207582f}, +{0.264046f,0.0732838f,0.189634f},{-0.411848f,0.287926f,0.0950838f},{-0.205019f,0.0557153f,0.210122f}, +{-0.480084f,0.19674f,0.133552f},{0.338147f,0.00136971f,0.0018842f},{-0.476972f,0.230957f,0.175345f}, +{-0.355728f,0.297624f,0.133636f},{-0.246651f,0.279303f,0.167962f},{-0.0530112f,0.0594643f,0.209794f}, +{-0.126539f,0.055741f,0.208636f},{-0.218845f,0.228192f,0.0964406f},{-0.336816f,-0.298267f,0.151834f}, +{-0.213984f,0.22834f,0.0841131f},{-0.391283f,0.306183f,-0.092087f},{0.0407736f,0.0879972f,0.222765f}, +{-0.45168f,-0.00158838f,0.11534f},{-0.455892f,-0.0366419f,0.0770072f},{0.381489f,-0.28293f,0.187782f}, +{0.401958f,-0.285271f,0.18701f},{0.420028f,-0.28419f,0.190418f},{0.440233f,-0.2852f,0.189164f}, +{0.454818f,-0.287116f,0.184952f},{-0.455397f,0.116048f,0.0136652f},{0.393431f,-0.149236f,0.165538f}, +{0.397322f,-0.130832f,0.133475f},{-0.389045f,0.304453f,-0.0738818f},{0.0624385f,0.131861f,0.116678f}, +{0.0188322f,0.124157f,0.0999389f},{-0.39282f,0.284447f,0.190309f},{-0.410717f,0.28057f,0.187344f}, +{-0.411199f,0.14786f,-0.0154207f},{-0.148224f,-0.0326613f,-0.120858f},{-0.277596f,-0.127739f,-0.117417f}, +{-0.109337f,-0.1326f,-0.147597f},{-0.432639f,0.0430211f,0.00153052f},{0.0408443f,-0.128112f,-0.22517f}, +{0.0573968f,-0.129739f,-0.22351f},{-0.352564f,-0.296453f,0.132806f},{-0.275538f,0.0185846f,-0.0867432f}, +{0.193212f,-0.0152085f,-0.111154f},{0.0769332f,-0.0398701f,-0.138259f},{-0.164197f,-0.260545f,0.278866f}, +{-0.145831f,-0.263123f,0.278891f},{-0.126565f,-0.263149f,0.278416f},{-0.0620655f,-0.272274f,0.272744f}, +{0.337304f,0.0145397f,0.15276f},{0.00310277f,0.112209f,0.149577f},{-0.0144401f,0.115237f,0.0962863f}, +{-0.327472f,0.350182f,-0.107154f},{0.00212531f,-0.260641f,0.36076f},{0.0213594f,-0.260738f,0.360857f}, +{0.0393653f,-0.261162f,0.366098f},{0.0534356f,-0.264596f,0.368779f},{0.0611781f,-0.258808f,0.373814f}, +{0.0792032f,-0.259554f,0.376354f},{0.0985724f,-0.259548f,0.375911f},{0.11661f,-0.258937f,0.372213f}, +{0.124263f,-0.264455f,0.369448f},{0.137131f,-0.260082f,0.368824f},{0.156442f,-0.260197f,0.367474f}, +{0.174088f,-0.26149f,0.362419f},{0.119292f,0.139082f,0.0968779f},{-0.2802f,0.290557f,0.171351f}, +{0.229996f,0.110646f,0.132195f},{-0.109022f,0.434558f,-0.0368477f},{-0.422999f,-0.0430147f,-0.0129578f}, +{-0.491306f,0.112672f,0.0954632f},{-0.0883412f,0.282075f,-0.017948f},{-0.374158f,0.301206f,-0.0189126f}, +{0.389772f,-0.0770072f,0.0948587f},{-0.483769f,0.113212f,0.211196f},{0.394511f,-0.28037f,0.180206f}, +{0.342667f,-0.0349056f,-0.0382753f},{-0.473531f,0.156477f,0.0759461f},{0.389534f,-0.149706f,0.175306f}, +{0.0982959f,0.134517f,0.133417f},{-0.337054f,0.333996f,-0.0643002f},{-0.276277f,0.294023f,0.13069f}, +{-0.277306f,0.0785955f,-0.0460114f},{-0.0315778f,-0.0332529f,-0.136658f},{-0.127317f,-0.132041f,-0.143925f}, +{-0.426549f,0.02861f,0.00250155f},{-0.128854f,0.301077f,-0.0589884f},{-0.278753f,-0.469413f,0.302203f}, +{0.0773062f,-0.12761f,-0.219813f},{0.172454f,-0.0427896f,-0.123256f},{-0.187843f,-0.26084f,0.272191f}, +{-0.113729f,-0.258757f,0.276936f},{-0.104051f,-0.259207f,0.271914f},{-0.0879875f,-0.259824f,0.26484f}, +{-0.0720845f,-0.259599f,0.266577f},{-0.0622327f,-0.263258f,0.277489f},{-0.394241f,0.269374f,0.228842f}, +{-0.335729f,0.357956f,-0.114884f},{0.0471721f,-0.255252f,0.371371f},{0.231275f,0.0376065f,0.242577f}, +{0.214254f,0.039047f,0.245555f},{0.193701f,0.0395293f,0.248011f},{0.137041f,0.0399408f,0.264982f}, +{-0.395611f,-0.0363718f,-0.0398958f},{-0.184415f,0.0511366f,0.207254f},{-0.165162f,0.0555931f,0.206206f}, +{0.13061f,0.0531944f,0.257889f},{0.0631459f,0.0534131f,0.257059f},{0.0792997f,0.0532909f,0.260519f}, +{-0.464458f,-0.0707308f,0.13496f},{0.0191151f,0.123244f,0.0768143f},{0.0229864f,0.0375293f,0.259985f}, +{0.0627729f,0.0895791f,0.227163f},{-0.463654f,-0.0535095f,0.115585f},{-0.467435f,0.175088f,0.249355f}, +{-0.335201f,0.126498f,-0.0476833f},{-0.374236f,0.166104f,0.335076f},{-0.386859f,0.219858f,0.297997f}, +{0.380821f,-0.264872f,0.188431f},{0.388351f,-0.261998f,0.177435f},{0.423237f,-0.271322f,0.180637f}, +{0.436163f,-0.271342f,0.18065f},{0.439211f,-0.262004f,0.171268f},{0.456632f,-0.264982f,0.169377f}, +{-0.456857f,0.178387f,0.0565448f},{-0.482386f,0.113315f,0.246545f},{-0.467062f,-0.0924408f,0.115816f}, +{0.194987f,0.116318f,0.171358f},{-0.261352f,0.307244f,0.0568342f},{-0.200421f,0.169313f,0.0971352f}, +{-0.374416f,0.271509f,0.233639f},{-0.335163f,0.112466f,-0.0477927f},{-0.258773f,0.226115f,-0.00123467f}, +{-0.393804f,0.03497f,-0.0210218f},{-0.354018f,0.0368027f,-0.039317f},{-0.355484f,-0.0172342f,-0.0600495f}, +{-0.31609f,0.322536f,-0.355358f},{0.0926755f,-0.130157f,-0.217909f},{-0.335259f,0.149905f,-0.0439343f}, +{-0.181123f,-0.254043f,0.278435f},{0.0232758f,0.118369f,0.151114f},{-0.261442f,0.291328f,0.114311f}, +{0.393675f,-0.243696f,0.164818f},{-0.241166f,0.285862f,0.0968522f},{0.00107712f,-0.243542f,0.364078f}, +{0.0200218f,-0.24322f,0.365615f},{0.0341693f,-0.246654f,0.369499f},{0.0419697f,-0.240822f,0.374194f}, +{0.0599434f,-0.241111f,0.381923f},{0.0792032f,-0.240925f,0.385602f},{0.0986367f,-0.241163f,0.383222f}, +{0.117961f,-0.24178f,0.375203f},{0.137105f,-0.241966f,0.371827f},{0.155162f,-0.241085f,0.371737f}, +{0.162937f,-0.246699f,0.368901f},{0.17383f,-0.244648f,0.364509f},{0.174834f,0.0401402f,0.253387f}, +{0.079274f,0.0400695f,0.267934f},{0.0985466f,0.0401531f,0.269933f},{-0.203836f,0.345873f,0.0429697f}, +{-0.164274f,0.0726086f,-0.0688466f},{-0.358075f,0.0621845f,0.353944f},{-0.145851f,0.0558053f,0.208431f}, +{-0.23478f,0.273348f,0.119552f},{-0.075422f,-0.128607f,-0.188123f},{-0.374101f,0.219845f,0.299508f}, +{-0.487467f,0.0957654f,0.246648f},{0.0421626f,0.379396f,-0.112202f},{-0.494547f,0.0776953f,0.0956625f}, +{-0.185155f,0.361628f,-0.0377029f},{-0.222581f,0.320916f,-0.0162438f},{-0.333845f,0.455805f,-0.360246f}, +{-0.0507476f,0.089682f,0.150297f},{-0.0236616f,0.476936f,-0.0794251f},{0.379033f,-0.132401f,-0.0399087f}, +{-0.301878f,0.374702f,-0.262763f},{0.145664f,0.124092f,0.178522f},{-0.108244f,0.0928395f,0.151944f}, +{0.0404135f,0.126813f,0.0586219f},{0.135054f,-0.127571f,-0.203164f},{-0.0112247f,0.438005f,-0.144606f}, +{-0.072734f,0.359937f,-0.0974245f},{-0.220543f,0.301103f,-0.0205524f},{-0.373818f,0.130832f,-0.0395228f}, +{-0.392614f,-0.243664f,0.169242f},{-0.261757f,-0.243966f,0.268281f},{0.0427349f,-0.0352593f,-0.139185f}, +{0.173277f,-0.0130157f,-0.115823f},{-0.233218f,0.26767f,-0.00997394f},{-0.354545f,0.149802f,-0.0412012f}, +{0.0590817f,-0.038436f,-0.138549f},{0.0962187f,-0.0401659f,-0.135404f},{-0.205199f,-0.245934f,0.271065f}, +{-0.18439f,-0.24041f,0.280261f},{-0.16513f,-0.240243f,0.28266f},{-0.145838f,-0.240417f,0.280621f}, +{-0.126539f,-0.240442f,0.280351f},{-0.110527f,-0.240725f,0.276801f},{-0.100849f,-0.241124f,0.272139f}, +{-0.0879618f,-0.241356f,0.26866f},{-0.0735121f,-0.243021f,0.271695f},{-0.00337933f,0.492537f,-0.113347f}, +{-0.317472f,0.297238f,0.116948f},{-0.0153661f,-0.243426f,0.356516f},{0.0277515f,-0.237652f,0.370252f}, +{0.0180412f,0.0443137f,0.254815f},{0.00199026f,0.0394907f,0.251567f},{-0.353812f,0.421253f,-0.335127f}, +{0.117768f,0.040063f,0.268416f},{-0.413405f,0.0750072f,0.329256f},{-0.374242f,0.0575351f,0.350259f}, +{-0.052844f,-0.108042f,-0.188348f},{0.384242f,-0.0774573f,0.0178387f},{-0.295814f,0.248108f,-0.0951802f}, +{0.374782f,-0.0371177f,0.0563133f},{-0.431687f,-0.0206167f,0.174149f},{-0.262426f,0.0814058f,-0.0432848f}, +{-0.232131f,0.222996f,0.147841f},{-0.498682f,0.0778946f,0.118131f},{0.381869f,-0.0774123f,0.152754f}, +{0.39637f,-0.0947558f,0.0563198f},{0.395785f,-0.0947944f,0.0755731f},{-0.0144465f,0.475528f,-0.0930967f}, +{-0.499794f,0.0954053f,0.134105f},{-0.181695f,0.13424f,0.111701f},{0.0992669f,-0.0618051f,0.315527f}, +{-0.173129f,0.125771f,0.0961706f},{0.0410308f,0.437073f,-0.180007f},{0.0606893f,0.131822f,0.0790457f}, +{0.0422205f,0.128587f,0.0778303f},{-0.299563f,-0.286512f,0.210231f},{-0.224967f,-0.134337f,-0.130665f}, +{-0.240542f,-0.24324f,0.268872f},{-0.222382f,-0.236063f,0.278094f},{-0.203759f,-0.236327f,0.277837f}, +{-0.0662455f,-0.244134f,0.282017f},{-0.0150638f,0.0949101f,0.171576f},{0.136841f,0.0919456f,0.224951f}, +{-0.0157776f,-0.22544f,0.361185f},{0.000794167f,-0.225234f,0.368072f},{0.0213787f,-0.223877f,0.374052f}, +{0.0406257f,-0.223716f,0.376637f},{0.0598534f,-0.223292f,0.38229f},{0.0791775f,-0.222861f,0.387647f}, +{0.0985981f,-0.222919f,0.387023f},{0.117948f,-0.223639f,0.377994f},{0.137118f,-0.223948f,0.373403f}, +{0.156416f,-0.223999f,0.372702f},{-0.090592f,0.0532009f,0.206051f},{-0.34297f,0.0636636f,0.352458f}, +{-0.0703997f,0.290158f,-0.0165332f},{-0.284708f,0.379017f,-0.377833f},{-0.45514f,0.209556f,0.0755989f}, +{-0.494347f,0.11282f,0.114749f},{0.268683f,-0.0564612f,0.282975f},{0.194537f,-0.111244f,0.342343f}, +{-0.296611f,0.363667f,-0.261374f},{-0.297511f,0.3259f,-0.302222f},{0.343941f,0.00187775f,0.0181281f}, +{-0.468908f,0.192592f,0.191454f},{-0.375361f,0.225684f,-0.0372335f},{-0.355014f,0.303019f,-0.000675201f}, +{-0.0488313f,0.474744f,-0.0766149f},{-0.246246f,0.281399f,0.151879f},{-0.257455f,0.269065f,0.208636f}, +{0.174744f,0.100158f,0.210199f},{0.079036f,0.134716f,0.0765378f},{0.0479759f,-0.0634577f,0.315347f}, +{-0.410916f,-0.244957f,0.0971287f},{-0.300309f,-0.225928f,0.270416f},{-0.285795f,-0.230604f,0.273258f}, +{-0.278888f,-0.223343f,0.279052f},{-0.261577f,-0.222134f,0.282904f},{-0.242298f,-0.222289f,0.282107f}, +{-0.22298f,-0.221877f,0.285393f},{-0.203714f,-0.222083f,0.285406f},{-0.184409f,-0.222237f,0.2821f}, +{-0.165091f,-0.22189f,0.285695f},{-0.145857f,-0.222089f,0.283624f},{-0.126565f,-0.222385f,0.2803f}, +{-0.108591f,-0.221703f,0.277496f},{-0.100791f,-0.227491f,0.27358f},{-0.0881419f,-0.223144f,0.272596f}, +{-0.0739558f,-0.223639f,0.284351f},{-0.336217f,0.297823f,0.151391f},{-0.147182f,0.0735861f,0.191962f}, +{-0.29937f,0.29637f,0.113135f},{0.00855597f,-0.219588f,0.371474f},{0.173477f,-0.226604f,0.366233f}, +{-0.0686891f,0.0249445f,0.235433f},{-0.107273f,0.0425002f,0.216681f},{0.156384f,0.0377673f,0.260976f}, +{0.0407028f,0.0399023f,0.263438f},{-0.444516f,0.265792f,0.186676f},{-0.467911f,0.154201f,0.265496f}, +{-0.302463f,0.414012f,-0.326099f},{-0.298592f,0.324703f,-0.321835f},{0.358082f,-0.131404f,-0.0702742f}, +{-0.353754f,-0.0912832f,-0.0882094f},{-0.299068f,-0.470126f,0.283869f},{-0.24132f,0.074795f,-0.0535545f}, +{0.374872f,-0.0370663f,0.0755796f},{-0.442111f,0.186791f,0.0245266f},{-0.288733f,0.291187f,-0.160702f}, +{-0.369458f,0.343667f,-0.225948f},{-0.20646f,0.188856f,0.0770972f},{-0.207276f,0.36332f,-0.00239219f}, +{-0.201894f,0.337012f,0.0508022f},{0.370101f,-0.030417f,0.0964921f},{-0.353863f,0.341475f,-0.266004f}, +{0.270104f,0.0767435f,0.174123f},{-0.0709399f,0.0747307f,0.171718f},{-0.295724f,-0.145172f,-0.112659f}, +{-0.293743f,-0.218173f,0.276744f},{-0.413405f,0.288653f,0.134073f},{-0.0186972f,-0.208701f,0.366625f}, +{0.00206101f,-0.206116f,0.373248f},{0.021353f,-0.205884f,0.376374f},{0.0405871f,-0.205492f,0.381493f}, +{0.0599112f,-0.205466f,0.381756f},{0.0791711f,-0.205145f,0.385036f},{0.0985788f,-0.20519f,0.385107f}, +{0.117954f,-0.20571f,0.37865f},{0.135883f,-0.207164f,0.371152f},{0.156429f,-0.210713f,0.370966f}, +{0.174756f,-0.206218f,0.364522f},{-0.139291f,0.0428411f,0.217427f},{-0.126533f,0.0425131f,0.215742f}, +{-0.0522266f,0.074705f,-0.0390855f},{-0.333394f,0.0598566f,0.34896f},{-0.287885f,0.291277f,-0.131764f}, +{-0.431173f,0.00439213f,0.228957f},{-0.341844f,0.305032f,-0.30435f},{-0.289981f,0.327424f,-0.226411f}, +{-0.128449f,0.417761f,-0.0551043f},{0.311414f,0.0249767f,-0.0201601f},{-0.44314f,0.0112151f,0.176419f}, +{0.0824057f,0.10318f,0.211826f},{-0.0110447f,0.0887367f,0.188309f},{-0.2751f,0.294196f,0.114035f}, +{0.136385f,0.13952f,0.0976303f},{-0.15259f,0.4069f,-0.0203916f},{-0.355439f,0.296917f,0.15249f}, +{-0.298379f,0.284621f,0.207093f},{-0.188627f,0.134748f,0.0421466f},{0.0227613f,-0.0735539f,0.322916f}, +{-0.300161f,-0.204315f,0.28037f},{-0.280792f,-0.203897f,0.284615f},{-0.262837f,-0.204418f,0.289843f}, +{-0.241012f,-0.204463f,0.290389f},{-0.223006f,-0.20344f,0.290955f},{-0.20372f,-0.203415f,0.291714f}, +{-0.184447f,-0.204051f,0.283586f},{-0.165085f,-0.203865f,0.286023f},{-0.145883f,-0.203762f,0.286615f}, +{-0.126617f,-0.20409f,0.282403f},{-0.107267f,-0.204598f,0.277039f},{-0.0923797f,-0.206405f,0.274557f}, +{-0.0837562f,-0.204218f,0.281071f},{-0.355046f,0.297367f,0.112987f},{-0.336307f,-0.150278f,0.285238f}, +{-0.487762f,0.134806f,0.114614f},{-0.482329f,0.0442815f,0.212868f},{-0.0140028f,-0.201762f,0.371712f}, +{0.143561f,-0.201923f,0.369551f},{0.156435f,-0.201974f,0.368638f},{-0.0880261f,0.024996f,0.233221f}, +{0.059982f,0.039973f,0.265908f},{0.348371f,0.00301599f,0.0364619f},{-0.443899f,0.00239864f,0.152715f}, +{-0.184351f,0.0597987f,0.197691f},{-0.210749f,0.187782f,0.114382f},{-0.461558f,-0.0538053f,0.0771036f}, +{-0.10955f,0.418713f,-0.0740105f},{-0.414472f,-0.091817f,-0.0382946f},{0.257905f,0.0859651f,0.173544f}, +{-0.297826f,0.293663f,0.170663f},{-0.451365f,0.272699f,0.0952381f},{-0.39556f,-0.0537346f,0.230526f}, +{0.0591974f,0.130395f,0.0599724f},{-0.375438f,0.296023f,0.132079f},{-0.491286f,0.0431047f,0.115115f}, +{-0.0537957f,-0.127237f,-0.212315f},{-0.255082f,-0.19854f,0.294325f},{-0.248722f,-0.198617f,0.294601f}, +{-0.373792f,0.288287f,0.1911f},{-0.437841f,-0.00634706f,0.157153f},{-0.017186f,-0.18802f,0.376239f}, +{0.00208673f,-0.187865f,0.378702f},{0.0213594f,-0.187898f,0.378142f},{0.0406257f,-0.187563f,0.382972f}, +{0.0599434f,-0.187647f,0.381595f},{0.0792032f,-0.187698f,0.380361f},{0.0985466f,-0.187724f,0.380329f}, +{0.117897f,-0.187975f,0.377743f},{0.135915f,-0.187492f,0.372753f},{0.143619f,-0.193074f,0.369165f}, +{0.156455f,-0.18674f,0.367422f},{0.17401f,-0.184701f,0.361686f},{-0.0700203f,0.055143f,0.207614f}, +{-0.0324266f,0.323289f,-0.0133693f},{-0.445693f,-0.0196842f,0.0576059f},{-0.165226f,0.099881f,-0.0395164f}, +{-0.390203f,0.130298f,0.351384f},{-0.489756f,0.0774895f,0.0763577f},{0.397733f,-0.130909f,0.0178001f}, +{-0.423919f,-0.0212533f,0.186965f},{-0.166274f,0.102022f,0.138716f},{-0.0904505f,0.0690332f,0.183981f}, +{-0.424838f,-0.0223851f,0.00402561f},{-0.415996f,0.0336452f,-0.00662999f},{0.264181f,0.0920742f,0.134542f}, +{-0.318205f,-0.188123f,0.283174f},{-0.300154f,-0.185499f,0.289611f},{-0.280862f,-0.185582f,0.28875f}, +{-0.265815f,-0.186804f,0.292094f},{-0.257217f,-0.183473f,0.296093f},{-0.242266f,-0.184669f,0.299109f}, +{-0.224633f,-0.182792f,0.295489f},{-0.219803f,-0.189782f,0.293573f},{-0.202402f,-0.186226f,0.292949f}, +{-0.184441f,-0.185788f,0.286177f},{-0.165123f,-0.186129f,0.282422f},{-0.145786f,-0.185647f,0.287598f}, +{-0.126623f,-0.18555f,0.288177f},{-0.107254f,-0.186309f,0.279528f},{-0.0896981f,-0.187306f,0.281039f}, +{-0.443513f,0.276358f,0.15258f},{-0.0693836f,0.0744928f,-0.0386868f},{-0.0388637f,0.480525f,-0.0943957f}, +{-0.238401f,0.0368927f,0.232385f},{-0.0433909f,0.0347127f,0.237664f},{-0.0368316f,0.043542f,0.234983f}, +{0.16266f,0.0439021f,0.255214f},{-0.389939f,-0.0189576f,-0.0366419f},{-0.392827f,0.0589627f,0.341256f}, +{0.375991f,-0.116807f,-0.0382431f},{0.0163692f,0.340201f,-0.0590527f},{-0.373039f,0.0968457f,0.351513f}, +{0.118276f,0.0920421f,0.224764f},{-0.429308f,-0.0382046f,-0.000225055f},{-0.457744f,0.0940291f,0.0119932f}, +{-0.49056f,0.0783962f,0.262975f},{-0.469474f,0.264268f,0.132761f},{-0.391174f,0.0139095f,0.310685f}, +{-0.385611f,0.245272f,-0.0363332f},{-0.353696f,0.296132f,0.16404f},{-0.324514f,0.333224f,-0.078229f}, +{-0.445108f,0.278448f,0.118652f},{-0.430279f,0.283663f,0.113212f},{-0.128526f,0.100627f,-0.0182566f}, +{-0.36657f,0.325887f,-0.206386f},{-0.0509855f,0.0969615f,0.131391f},{-0.210132f,-0.180721f,0.294126f}, +{-0.0818463f,-0.180721f,0.294106f},{-0.471004f,0.0357802f,0.191126f},{-0.448297f,-0.000610921f,0.13561f}, +{-0.442606f,-0.0213112f,0.0427639f},{-0.446162f,-0.0722549f,0.0192856f},{-0.0179448f,-0.171043f,0.377518f}, +{0.00211245f,-0.17004f,0.378657f},{0.0213466f,-0.170046f,0.378406f},{0.040645f,-0.169834f,0.38119f}, +{0.0600013f,-0.170014f,0.379062f},{0.0792032f,-0.170033f,0.378354f},{0.0985145f,-0.169911f,0.380059f}, +{0.117845f,-0.169995f,0.379563f},{0.137201f,-0.17022f,0.376792f},{0.153162f,-0.168606f,0.372104f}, +{-0.448915f,0.0937333f,-0.000842398f},{0.23289f,-0.166278f,0.335951f},{-0.204704f,0.284769f,0.0757275f}, +{-0.41471f,0.190798f,-0.0013247f},{-0.410382f,0.208546f,-0.00140187f},{0.0517186f,0.38346f,-0.142471f}, +{0.172467f,0.0976303f,-0.0358766f},{0.392601f,-0.0994695f,0.130214f},{-0.165143f,0.0250153f,0.233234f}, +{-0.29827f,0.296132f,0.150831f},{-0.454452f,0.274158f,0.115559f},{-0.322289f,0.35957f,-0.357005f}, +{0.242156f,0.106408f,0.12179f},{-0.355786f,0.296743f,0.017112f},{0.0784122f,-0.0586798f,0.301765f}, +{-0.319466f,-0.169577f,0.290094f},{-0.300148f,-0.171647f,0.293836f},{-0.300161f,-0.16476f,0.295264f}, +{-0.279596f,-0.168162f,0.292409f},{-0.267975f,-0.167229f,0.292486f},{-0.258265f,-0.166953f,0.295997f}, +{-0.242253f,-0.166612f,0.300723f},{-0.222999f,-0.166676f,0.300248f},{-0.203714f,-0.166805f,0.298601f}, +{-0.190859f,-0.167011f,0.296016f},{-0.181155f,-0.167435f,0.290428f},{-0.16513f,-0.167892f,0.284531f}, +{-0.145748f,-0.167429f,0.290029f},{-0.126636f,-0.167358f,0.29093f},{-0.107286f,-0.16811f,0.281476f}, +{-0.0915051f,-0.168207f,0.282132f},{-0.0812225f,-0.16667f,0.297444f},{-0.336269f,0.43371f,-0.360361f}, +{-0.460194f,-0.0583197f,0.0643581f},{-0.442053f,-0.0419022f,0.0256198f},{-0.46739f,-0.0706922f,0.115688f}, +{-0.437983f,-0.0187454f,0.0340954f},{-0.457654f,0.0156908f,0.0572972f},{0.290991f,0.0614128f,0.172091f}, +{0.214742f,-0.166014f,0.341918f},{-0.0160863f,0.102318f,0.155519f},{-0.19774f,0.0611749f,0.198425f}, +{-0.359625f,0.0961448f,0.352092f},{-0.288039f,0.321392f,-0.205775f},{-0.355407f,0.293135f,0.0276068f}, +{0.188698f,0.125179f,0.147976f},{0.154262f,0.138607f,0.0940549f},{-0.427314f,-0.0734253f,-0.0193563f}, +{0.0803543f,0.0910968f,0.226983f},{-0.411167f,0.2732f,0.208296f},{-0.338648f,0.35368f,-0.102029f}, +{-0.0346195f,-0.146696f,0.362464f},{-0.312951f,-0.162529f,0.294518f},{-0.287312f,-0.162586f,0.294827f}, +{0.0412044f,0.120472f,0.0201666f},{-0.316572f,0.433407f,-0.377158f},{-0.453796f,-0.0718498f,0.0387576f}, +{-0.0511013f,-0.0979711f,-0.165281f},{0.0969969f,0.13352f,0.0388605f},{-0.0171667f,-0.152555f,0.373692f}, +{0.00208029f,-0.152355f,0.37629f},{0.0213466f,-0.152265f,0.377435f},{0.040645f,-0.152239f,0.377911f}, +{0.0599627f,-0.152227f,0.377975f},{0.0792225f,-0.15222f,0.377821f},{0.0985274f,-0.152207f,0.37757f}, +{0.117826f,-0.152175f,0.377834f},{0.13715f,-0.152317f,0.376425f},{0.154892f,-0.152966f,0.372168f}, +{0.060117f,-0.116163f,-0.218372f},{0.31025f,0.043169f,0.171075f},{-0.390357f,0.0435742f,0.33777f}, +{-0.200421f,0.0423909f,0.216038f},{-0.184435f,0.0424231f,0.216263f},{-0.373863f,0.291676f,0.0576702f}, +{0.398563f,-0.112672f,0.0948651f},{0.284779f,0.036822f,-0.0329957f},{-0.356718f,0.368072f,-0.127121f}, +{0.362004f,-0.11354f,-0.0547699f},{0.37592f,-0.0959776f,0.184907f},{-0.352371f,0.329661f,-0.0511109f}, +{-0.350416f,0.349609f,-0.0879136f},{-0.335491f,0.306376f,-0.0183081f},{-0.144269f,0.413292f,-0.0335102f}, +{-0.323736f,-0.150741f,0.292306f},{-0.31508f,-0.147416f,0.296183f},{-0.300141f,-0.148715f,0.298659f}, +{-0.280882f,-0.148703f,0.298859f},{-0.261558f,-0.148896f,0.296454f},{-0.242253f,-0.1486f,0.300537f}, +{-0.222999f,-0.148433f,0.302981f},{-0.203739f,-0.148516f,0.301894f},{-0.187541f,-0.148992f,0.29583f}, +{-0.17792f,-0.149333f,0.291303f},{-0.165162f,-0.149828f,0.285136f},{-0.14578f,-0.149487f,0.289341f}, +{-0.126507f,-0.149468f,0.289354f},{-0.107247f,-0.149706f,0.285361f},{-0.0910871f,-0.149449f,0.289058f}, +{-0.0810617f,-0.15332f,0.29628f},{0.0787981f,0.131018f,0.0376194f},{0.135594f,0.138047f,0.0597023f}, +{-0.0820135f,0.450468f,-0.0409954f},{0.301183f,0.0536382f,0.169024f},{-0.485132f,0.0616443f,0.282312f}, +{0.212131f,-0.149873f,0.339121f},{0.232433f,-0.146503f,0.328195f},{0.248754f,-0.148066f,0.325469f}, +{-0.340905f,-0.186457f,0.272332f},{-0.0107617f,0.0523713f,0.2385f},{0.0427671f,0.0982798f,-0.035986f}, +{-0.0806887f,-0.12606f,-0.173506f},{0.382197f,-0.0955403f,0.172059f},{0.355509f,-0.0540304f,-0.0333044f}, +{-0.041063f,0.458423f,-0.0611106f},{-0.406119f,-0.0569306f,0.221491f},{-0.445584f,-0.111585f,0.0194849f}, +{-0.467069f,-0.0925436f,0.0965628f},{-0.495499f,0.115353f,0.154947f},{-0.44869f,-0.00164623f,0.0768464f}, +{-0.392023f,0.306074f,-0.111411f},{-0.076496f,-0.147506f,0.303444f},{0.174756f,0.134362f,0.0740491f}, +{0.193958f,0.128851f,0.0764992f},{0.202891f,0.125372f,0.0889489f},{-0.0161249f,-0.128883f,0.364271f}, +{-0.0108711f,-0.139282f,0.370477f},{0.00423457f,-0.13323f,0.372734f},{0.0213594f,-0.130105f,0.374708f}, +{0.0406514f,-0.130099f,0.374599f},{0.0599434f,-0.134394f,0.377383f},{0.0792675f,-0.130028f,0.37503f}, +{0.0985466f,-0.130182f,0.372824f},{0.116559f,-0.135539f,0.372322f},{0.137111f,-0.139237f,0.371872f}, +{0.156403f,-0.130774f,0.363783f},{0.175573f,-0.130221f,0.358779f},{-0.184473f,0.336015f,0.0502106f}, +{-0.461281f,-0.0544227f,0.13352f},{-0.355394f,0.323424f,-0.246577f},{-0.451744f,-0.00167199f,0.0960677f}, +{-0.488778f,0.0948844f,0.0762548f},{0.0491334f,0.377615f,-0.128253f},{-0.290123f,0.281174f,-0.147828f}, +{-0.316212f,0.293643f,0.0745378f},{-0.0885084f,0.00246939f,-0.107488f},{-0.245648f,0.267271f,0.188579f}, +{-0.35522f,-0.131314f,0.277071f},{-0.3365f,-0.131655f,0.284145f},{-0.0710685f,0.0887624f,-0.000945289f}, +{-0.0934343f,0.0889425f,-0.00949164f},{-0.322739f,-0.130086f,0.292222f},{-0.315183f,-0.132356f,0.295907f}, +{-0.300141f,-0.130523f,0.300273f},{-0.280882f,-0.130414f,0.301405f},{-0.261577f,-0.1306f,0.299547f}, +{-0.242304f,-0.1306f,0.29927f},{-0.223006f,-0.130626f,0.299257f},{-0.20372f,-0.130697f,0.297836f}, +{-0.190917f,-0.135443f,0.295482f},{-0.182994f,-0.130414f,0.289052f},{-0.165085f,-0.131719f,0.284235f}, +{-0.145838f,-0.131475f,0.287598f},{-0.126507f,-0.131237f,0.289669f},{-0.107228f,-0.135597f,0.291778f}, +{-0.0879168f,-0.13098f,0.293013f},{-0.0879425f,-0.130915f,0.294621f},{-0.0714736f,-0.130446f,0.305527f}, +{0.23069f,0.113077f,0.0939777f},{-0.00217037f,-0.124543f,0.368593f},{0.119446f,-0.123707f,0.368496f}, +{0.137131f,-0.126099f,0.365795f},{0.115916f,-0.127546f,-0.208752f},{0.0421819f,0.35959f,-0.110189f}, +{0.231108f,-0.129925f,0.322504f},{0.252638f,-0.130735f,0.320858f},{-0.345188f,0.0937848f,0.351076f}, +{0.335356f,0.013035f,0.165834f},{0.271017f,0.0599081f,-0.0193498f},{-0.298412f,0.278538f,0.223858f}, +{-0.328951f,0.294306f,-0.000379391f},{-0.418363f,-0.014051f,-0.00224428f},{-0.4484f,-0.0194592f,0.076885f}, +{-0.45278f,0.173801f,0.037285f},{-0.107222f,-0.126234f,0.295116f},{0.24287f,0.106646f,0.0991222f}, +{0.286901f,0.0755024f,0.11489f},{0.276155f,0.0845375f,0.111495f},{0.0599434f,-0.116639f,0.373654f}, +{0.189489f,-0.132259f,0.354304f},{0.100566f,0.0614064f,-0.078184f},{-0.320572f,0.340767f,-0.359242f}, +{-0.336127f,0.0973602f,0.345577f},{0.392601f,-0.0814186f,0.0563262f},{-0.475049f,0.0359538f,0.206598f}, +{-0.202871f,0.0996367f,-0.0388219f},{-0.316951f,0.284415f,0.00184562f},{-0.277454f,0.294537f,0.0929809f}, +{-0.411881f,0.0543841f,-0.0163403f},{-0.354937f,-0.114202f,0.277496f},{-0.338848f,-0.113758f,0.281329f}, +{-0.319877f,-0.114202f,0.288807f},{-0.298855f,-0.113508f,0.297515f},{-0.280856f,-0.11235f,0.300833f}, +{-0.26159f,-0.112273f,0.301514f},{-0.242311f,-0.112556f,0.297701f},{-0.221404f,-0.115038f,0.294524f}, +{-0.207964f,-0.111225f,0.295174f},{-0.199309f,-0.114434f,0.292441f},{-0.184338f,-0.113205f,0.288563f}, +{-0.165117f,-0.11352f,0.284197f},{-0.145799f,-0.113373f,0.28655f},{-0.130745f,-0.114434f,0.292351f}, +{-0.122173f,-0.111205f,0.295257f},{-0.107202f,-0.112594f,0.296164f},{-0.0895438f,-0.110395f,0.295277f}, +{-0.0847208f,-0.11736f,0.293862f},{-0.0688177f,-0.113231f,0.299566f},{0.314353f,0.048365f,0.130472f}, +{0.296232f,0.0672069f,0.114408f},{0.00345646f,-0.109315f,0.363069f},{0.0200218f,-0.107328f,0.365249f}, +{0.0278029f,-0.112376f,0.370966f},{0.0406385f,-0.112299f,0.371165f},{0.0792482f,-0.112266f,0.371261f}, +{0.0921224f,-0.112318f,0.369847f},{0.0998071f,-0.107225f,0.367133f},{0.117819f,-0.108222f,0.364914f}, +{0.137118f,-0.108517f,0.359686f},{0.154744f,-0.111231f,0.353577f},{0.169187f,-0.113726f,0.351455f}, +{0.176898f,-0.108588f,0.347018f},{-0.467133f,0.174959f,0.210861f},{0.231218f,-0.113398f,0.318016f}, +{0.251828f,-0.112318f,0.316897f},{-0.359857f,0.393962f,-0.302936f},{-0.208434f,0.229272f,0.0730202f}, +{-0.358165f,0.219794f,0.298222f},{-0.088547f,0.37865f,-0.0349249f},{-0.24251f,0.327996f,0.0392592f}, +{-0.463049f,-0.112775f,0.153268f},{-0.438703f,0.274789f,0.168599f},{0.165303f,0.106826f,0.204071f}, +{-0.453513f,-0.111006f,0.0386933f},{-0.306514f,-0.108421f,0.293007f},{-0.226227f,-0.108337f,0.293045f}, +{0.333002f,0.0246938f,0.134394f},{0.347587f,0.00345971f,0.133533f},{0.338957f,0.0162631f,0.133475f}, +{0.0406707f,-0.103675f,0.367075f},{0.0599434f,-0.103566f,0.367712f},{0.0792289f,-0.103592f,0.367641f}, +{0.159477f,-0.104897f,0.348362f},{-0.290444f,0.419639f,-0.358619f},{-0.108759f,0.0726922f,0.186817f}, +{-0.265885f,0.250294f,0.225896f},{-0.335877f,0.29619f,0.167975f},{0.0435195f,-0.0766727f,0.33979f}, +{-0.186865f,-0.0281727f,-0.11972f},{-0.352957f,-0.0965949f,0.276917f},{-0.338732f,-0.0955661f,0.281258f}, +{-0.319453f,-0.0954246f,0.283238f},{-0.301447f,-0.0940806f,0.28956f},{-0.29364f,-0.0991994f,0.295386f}, +{-0.28083f,-0.0990579f,0.297534f},{-0.261596f,-0.0989743f,0.298177f},{-0.24878f,-0.0991415f,0.296164f}, +{-0.241012f,-0.0939198f,0.291701f},{-0.226195f,-0.0947751f,0.292621f},{-0.216536f,-0.0945114f,0.296106f}, +{-0.206923f,-0.0944471f,0.296865f},{-0.197109f,-0.0947172f,0.293367f},{-0.184235f,-0.0950259f,0.288338f}, +{-0.165072f,-0.0954438f,0.282718f},{-0.145786f,-0.0953667f,0.28421f},{-0.127684f,-0.0940548f,0.290788f}, +{-0.120019f,-0.0992058f,0.29446f},{-0.105929f,-0.0954053f,0.296241f},{-0.0879361f,-0.0943121f,0.298826f}, +{-0.069956f,-0.0952252f,0.298318f},{-0.0487605f,-0.0967557f,0.301926f},{-0.181438f,0.136401f,0.0929488f}, +{-0.0902769f,0.101141f,0.113881f},{0.0040931f,-0.0975982f,0.353577f},{0.0213594f,-0.0953667f,0.354574f}, +{0.0420018f,-0.0918363f,0.357538f},{0.0599562f,-0.0907559f,0.35831f},{0.0792354f,-0.0907495f,0.358773f}, +{0.0985145f,-0.0908138f,0.358329f},{0.117716f,-0.0909424f,0.355905f},{0.133748f,-0.0955853f,0.354484f}, +{0.138578f,-0.0892833f,0.347783f},{0.156268f,-0.0918363f,0.341719f},{0.173933f,-0.0944986f,0.336664f}, +{0.19156f,-0.0969615f,0.333533f},{-0.26022f,0.285888f,0.170528f},{0.213739f,-0.0920099f,0.319154f}, +{0.233475f,-0.093296f,0.314524f},{0.251166f,-0.0991415f,0.313752f},{-0.374191f,0.0666924f,0.352407f}, +{-0.451043f,-0.0192405f,0.0961448f},{-0.332269f,-0.219575f,0.259066f},{0.16922f,0.0707759f,0.240893f}, +{0.338417f,-0.0910389f,-0.0671361f},{0.346854f,-0.0555995f,0.209562f},{0.261487f,0.087174f,0.165654f}, +{0.0286261f,0.125449f,0.0756375f},{-0.129755f,0.108183f,0.00322178f},{-0.392241f,0.283785f,0.039973f}, +{-0.228768f,0.249053f,0.13489f},{-0.447918f,0.154857f,0.0198f},{-0.28085f,-0.0902543f,0.293039f}, +{-0.26159f,-0.0902672f,0.292994f},{-0.11362f,-0.0901707f,0.293765f},{-0.0622456f,-0.0902736f,0.291688f}, +{-0.0538922f,-0.0907688f,0.293026f},{0.134308f,-0.0709559f,0.325321f},{-0.470091f,0.152883f,0.210977f}, +{0.0999292f,-0.0751423f,0.343082f},{0.00420885f,-0.0888267f,0.342671f},{0.0204013f,-0.0871612f,0.346156f}, +{0.0343429f,-0.0868396f,0.348561f},{0.178763f,-0.0881322f,0.330054f},{0.19646f,-0.0908845f,0.326806f}, +{0.253796f,-0.0897463f,0.309816f},{0.156384f,0.070808f,0.241735f},{-0.468966f,0.0231054f,0.152175f}, +{0.280695f,0.0716119f,0.166708f},{-0.357554f,0.245761f,-0.184843f},{0.374049f,-0.0370535f,0.0402302f}, +{-0.186544f,-0.115649f,-0.13042f},{-0.335002f,0.0190412f,-0.0548728f},{-0.162197f,0.122311f,0.0764606f}, +{-0.354558f,-0.0732066f,0.268615f},{-0.337439f,-0.0784991f,0.277329f},{-0.319511f,-0.0774187f,0.27992f}, +{-0.300167f,-0.0772065f,0.283534f},{-0.280869f,-0.0770072f,0.286615f},{-0.261596f,-0.0769043f,0.287316f}, +{-0.242278f,-0.0769107f,0.287534f},{-0.222974f,-0.0768271f,0.289772f},{-0.20372f,-0.0766727f,0.290885f}, +{-0.18439f,-0.0770264f,0.285644f},{-0.16513f,-0.0773223f,0.281798f},{-0.145883f,-0.0773737f,0.280351f}, +{-0.126475f,-0.0771165f,0.285656f},{-0.108514f,-0.0758368f,0.290955f},{-0.100752f,-0.0810135f,0.295367f}, +{-0.0879618f,-0.0809556f,0.29655f},{-0.0686634f,-0.0767049f,0.291386f},{-0.0557764f,-0.081052f,0.293881f}, +{-0.050201f,-0.0766663f,0.296762f},{-0.0362336f,-0.0759976f,0.29799f},{0.40091f,-0.245092f,0.150394f}, +{-0.0724446f,0.0829427f,0.152985f},{0.402138f,-0.256538f,0.157493f},{-0.490245f,0.0784348f,0.246738f}, +{-0.210395f,0.208585f,0.0772065f},{-0.292573f,0.346523f,-0.263657f},{0.134423f,-0.0784412f,0.33788f}, +{0.156352f,-0.0750201f,0.324215f},{0.175657f,-0.0749815f,0.322626f},{0.194833f,-0.0749622f,0.319424f}, +{0.212852f,-0.0760747f,0.314954f},{0.227038f,-0.0798431f,0.313025f},{0.234697f,-0.0746085f,0.310575f}, +{0.250323f,-0.0727565f,0.304672f},{0.0985466f,0.0534002f,0.261689f},{-0.16412f,0.0707566f,0.18865f}, +{-0.316167f,0.149918f,-0.0431754f},{-0.390055f,0.201903f,0.312916f},{-0.377869f,0.291637f,-0.000598033f}, +{0.117048f,0.126986f,0.171859f},{0.228131f,0.107681f,0.147397f},{-0.298328f,0.289502f,0.188965f}, +{-0.171721f,0.126465f,0.0792193f},{-0.394267f,0.293843f,0.118659f},{-0.368435f,0.269805f,-0.18838f}, +{-0.343079f,-0.0673612f,0.271503f},{-0.0879618f,-0.0721327f,0.292659f},{-0.0188386f,-0.0762934f,0.300775f}, +{-0.431944f,0.281341f,0.151494f},{-0.319935f,0.295103f,0.0825183f},{-0.449281f,0.0184367f,0.188753f}, +{-0.495003f,0.077213f,0.174181f},{-0.299055f,0.379898f,-0.382258f},{-0.454696f,-0.0543648f,0.153725f}, +{-0.393733f,0.325218f,-0.129874f},{-0.373265f,-0.262551f,0.170258f},{-0.291672f,0.307296f,-0.114421f}, +{0.0441111f,0.40254f,-0.166966f},{-0.468316f,-0.114234f,0.0966721f},{0.220575f,-0.0708402f,0.312273f}, +{-0.351915f,0.39645f,-0.316678f},{-0.262812f,-0.285476f,0.229433f},{0.35421f,-0.0017234f,0.0884666f}, +{-0.205791f,-0.115848f,-0.128941f},{0.0423748f,-0.0679528f,0.322678f},{-0.112154f,0.0929166f,-0.0174592f}, +{-0.338815f,-0.0536574f,0.266815f},{-0.318192f,-0.0603067f,0.277399f},{-0.300148f,-0.0547378f,0.279798f}, +{-0.280869f,-0.0545706f,0.282075f},{-0.261583f,-0.0544162f,0.28419f},{-0.242285f,-0.0545127f,0.282068f}, +{-0.223012f,-0.0546413f,0.279824f},{-0.202794f,-0.0572007f,0.28174f},{-0.184409f,-0.0545899f,0.280403f}, +{-0.16585f,-0.0558438f,0.278448f},{-0.145844f,-0.0640237f,0.276596f},{-0.126539f,-0.059355f,0.278518f}, +{-0.106437f,-0.0557217f,0.281663f},{-0.0879618f,-0.0543713f,0.284711f},{-0.069538f,-0.056847f,0.288081f}, +{-0.0525224f,-0.0628726f,0.294936f},{-0.0349024f,-0.0605189f,0.295386f},{-0.0236681f,-0.0628726f,0.295888f}, +{-0.0159191f,-0.0575223f,0.291746f},{0.00101924f,-0.0573551f,0.29255f},{0.0210186f,-0.0616893f,0.296447f}, +{0.037121f,-0.0612199f,0.300068f},{0.0586637f,-0.0628147f,0.316852f},{-0.336025f,-0.204122f,0.266287f}, +{-0.467313f,0.0428668f,0.0391242f},{-0.225179f,0.228018f,0.115655f},{0.117453f,-0.0579981f,0.303013f}, +{0.137375f,-0.0581846f,0.304575f},{0.154191f,-0.0535481f,0.307862f},{0.159882f,-0.0619465f,0.315077f}, +{0.177039f,-0.058146f,0.316704f},{0.193579f,-0.0581074f,0.315887f},{0.207572f,-0.0610977f,0.313475f}, +{0.215424f,-0.0565769f,0.310035f},{0.231745f,-0.0562232f,0.30453f},{0.137086f,0.0706859f,0.241111f}, +{-0.378223f,0.24814f,-0.0724864f},{-0.335221f,0.135822f,-0.0458699f},{-0.399457f,-0.0121604f,-0.0235491f}, +{-0.0535578f,0.379184f,-0.110112f},{-0.373367f,-0.0371435f,-0.0575544f},{-0.341786f,0.0280763f,0.335539f}, +{-0.308058f,0.311559f,-0.0721327f},{-0.401502f,0.251773f,0.254165f},{-0.373033f,-0.0535931f,0.248455f}, +{-0.35549f,-0.055876f,0.259921f},{-0.321003f,-0.0483972f,0.272152f},{-0.145857f,-0.0505321f,0.271638f}, +{-0.0525482f,-0.0540433f,0.291367f},{-0.0397254f,-0.0538568f,0.293502f},{0.0228385f,-0.0523263f,0.291245f}, +{-0.30326f,0.295431f,0.163101f},{0.0406771f,-0.0539018f,0.293322f},{0.0602585f,-0.0515353f,0.292653f}, +{-0.457262f,-0.0493746f,0.141185f},{0.0962509f,-0.0508729f,0.29354f},{0.101492f,-0.0563455f,0.300158f}, +{0.169477f,-0.0530979f,0.3128f},{0.201296f,-0.0528472f,0.312299f},{0.117749f,0.053233f,0.260467f}, +{0.390685f,-0.111643f,0.156985f},{-0.475345f,0.161223f,0.114472f},{-0.261197f,0.288878f,0.151693f}, +{-0.0545738f,0.307116f,-0.00269443f},{0.136841f,0.127707f,0.169808f},{-0.361349f,-0.0472461f,0.253355f}, +{-0.206948f,-0.0458634f,0.274229f},{-0.158558f,-0.0459149f,0.272531f},{-0.126572f,-0.0460757f,0.271059f}, +{-0.113704f,-0.0459535f,0.273689f},{-0.0673773f,-0.0400694f,0.280814f},{-0.0525675f,-0.0362882f,0.28356f}, +{-0.0365037f,-0.036121f,0.286338f},{-0.0172245f,-0.0361146f,0.28664f},{0.00206101f,-0.0360632f,0.286518f}, +{0.0208578f,-0.0357095f,0.289142f},{-0.392351f,0.292659f,0.0961898f},{0.0406771f,-0.0358573f,0.29028f}, +{0.0598984f,-0.035285f,0.291958f},{0.0781615f,-0.0362239f,0.293122f},{-0.475621f,0.174612f,0.133687f}, +{0.0998392f,-0.0348349f,0.29282f},{0.117716f,-0.0360889f,0.289277f},{0.136314f,-0.0352207f,0.285862f}, +{0.14387f,-0.045259f,0.296479f},{0.160956f,-0.0419407f,0.299695f},{0.17583f,-0.0401852f,0.302826f}, +{0.194936f,-0.0400952f,0.303386f},{0.214118f,-0.040243f,0.299997f},{0.400627f,-0.130632f,0.0948716f}, +{0.332828f,0.00700302f,-0.00363974f},{-0.330687f,0.0772065f,0.348323f},{-0.437873f,-0.0194528f,0.157249f}, +{-0.294431f,0.286209f,-0.0771293f},{-0.29355f,0.247529f,-0.0371885f},{0.0945532f,0.12343f,0.178676f}, +{-0.0610366f,0.0857529f,0.151423f},{-0.407038f,0.27657f,0.20301f},{-0.373811f,-0.0350985f,0.244642f}, +{-0.356866f,-0.0345455f,0.252011f},{-0.337137f,-0.0355423f,0.258706f},{-0.319485f,-0.0328349f,0.264853f}, +{-0.300148f,-0.032597f,0.270011f},{-0.282489f,-0.0302112f,0.270975f},{-0.277653f,-0.036822f,0.27556f}, +{-0.261583f,-0.0366419f,0.276969f},{-0.245507f,-0.0366934f,0.275818f},{-0.240671f,-0.0301212f,0.271554f}, +{-0.223006f,-0.0325391f,0.269052f},{-0.203727f,-0.0325648f,0.269348f},{-0.184435f,-0.0366419f,0.276364f}, +{-0.171515f,-0.0366676f,0.276287f},{-0.163728f,-0.0314395f,0.270943f},{-0.145728f,-0.0327385f,0.26304f}, +{-0.126591f,-0.0328221f,0.263175f},{-0.107337f,-0.0325198f,0.26794f},{-0.087949f,-0.0367898f,0.27574f}, +{0.0959036f,-0.034224f,0.294183f},{0.155201f,-0.0310601f,0.28484f},{0.232279f,-0.0337417f,0.288016f}, +{-0.13297f,0.0688852f,0.199402f},{0.391347f,-0.0905115f,0.120575f},{-0.222125f,0.188631f,0.0190476f}, +{-0.327948f,-0.150542f,-0.104074f},{-0.315054f,-0.145494f,-0.109051f},{-0.341986f,-0.0289958f,0.25367f}, +{-0.261596f,-0.0278126f,0.27212f},{-0.184415f,-0.0277805f,0.272699f},{-0.0880004f,-0.0278705f,0.270538f}, +{-0.0686055f,-0.0279026f,0.273438f},{0.0856082f,-0.0265715f,0.29401f},{0.17585f,-0.0274011f,0.289965f}, +{0.194974f,-0.0273303f,0.290267f},{0.214189f,-0.027311f,0.287695f},{0.194672f,0.06612f,0.235484f}, +{0.267969f,-0.0357545f,0.266493f},{-0.466728f,0.262821f,0.150381f},{0.385425f,-0.13188f,-0.0206167f}, +{0.389643f,-0.0724414f,0.075586f},{-0.281467f,0.0204109f,0.24486f},{-0.261525f,0.0205717f,0.241574f}, +{-0.310392f,0.290357f,-0.0356644f},{-0.145876f,0.111668f,0.115758f},{-0.432285f,0.0749429f,-0.0124433f}, +{-0.313697f,0.378174f,-0.37222f},{-0.167541f,-0.0280248f,-0.121835f},{-0.354661f,-0.0164046f,0.251362f}, +{-0.337542f,-0.0145526f,0.251555f},{-0.319485f,-0.0195621f,0.258197f},{-0.300148f,-0.0149705f,0.260056f}, +{-0.280862f,-0.014887f,0.261554f},{-0.261596f,-0.0147005f,0.263207f},{-0.242291f,-0.0145526f,0.263927f}, +{-0.222999f,-0.0146362f,0.261291f},{-0.203733f,-0.0146362f,0.263233f},{-0.184415f,-0.0144947f,0.265516f}, +{-0.164975f,-0.0144047f,0.263085f},{-0.148789f,-0.0191891f,0.257831f},{-0.144102f,-0.0126362f,0.253651f}, +{-0.127877f,-0.0141346f,0.253747f},{-0.120244f,-0.0194013f,0.257014f},{-0.106038f,-0.0157037f,0.259406f}, +{-0.0880133f,-0.0146104f,0.26248f},{-0.0687213f,-0.0144304f,0.26585f},{-0.0526189f,-0.0141989f,0.271464f}, +{-0.0365165f,-0.018411f,0.27801f},{-0.0172053f,-0.0138709f,0.278679f},{0.00208029f,-0.0137295f,0.280505f}, +{0.0214173f,-0.0134787f,0.285586f},{0.0407221f,-0.01335f,0.287695f},{0.0600013f,-0.0131635f,0.291328f}, +{0.0792225f,-0.0131957f,0.29118f},{0.0985016f,-0.0144626f,0.289393f},{0.117729f,-0.0134851f,0.284865f}, +{0.137124f,-0.0134915f,0.279895f},{0.154763f,-0.016321f,0.275908f},{0.17574f,-0.0187775f,0.277612f}, +{0.195f,-0.0187132f,0.281303f},{0.214209f,-0.0186554f,0.279039f},{0.232587f,-0.022443f,0.277393f}, +{0.234607f,-0.0142053f,0.268654f},{0.25132f,-0.0183917f,0.265387f},{0.269866f,-0.0204881f,0.259439f}, +{-0.120006f,0.380965f,0.00785186f},{0.372056f,-0.0647696f,-0.00768462f},{-0.243892f,0.0184367f,0.240076f}, +{-0.239089f,0.0251375f,0.2358f},{-0.0170766f,0.0842352f,0.193164f},{-0.263885f,0.147224f,-0.0295231f}, +{-0.18664f,-0.0739912f,-0.129475f},{0.000311863f,0.455419f,-0.155603f},{-0.150854f,0.281258f,-0.0423073f}, +{0.20698f,-0.0177743f,-0.10689f},{-0.319511f,-0.0106427f,0.254622f},{-0.113922f,-0.0103469f,0.254899f}, +{-0.0365615f,-0.00949807f,0.273014f},{0.159561f,-0.00969741f,0.272583f},{0.175689f,-0.00992251f,0.269599f}, +{0.195f,-0.00995464f,0.272146f},{0.214221f,-0.00994179f,0.270396f},{0.394685f,-0.113077f,0.0178708f}, +{0.400666f,-0.148715f,0.114189f},{-0.222929f,0.0250667f,0.234173f},{-0.475608f,0.112221f,0.0505772f}, +{0.077692f,-0.019337f,-0.134529f},{-0.316244f,0.168773f,-0.0416707f},{-0.110032f,0.377866f,-0.0883573f}, +{-0.296997f,0.14986f,-0.0396772f},{-0.315826f,0.0936369f,-0.0470403f},{0.190602f,-0.0323334f,-0.116961f}, +{0.0620462f,-0.0157037f,-0.135423f},{-0.20372f,0.0249639f,0.233382f},{-0.111054f,0.321475f,-0.0743384f}, +{-0.316868f,0.0550658f,-0.0475161f},{-0.337446f,-0.0023536f,0.255272f},{-0.318115f,0.00142761f,0.253362f}, +{-0.299672f,0.00104178f,0.252127f},{-0.280965f,0.00269444f,0.250243f},{-0.26159f,0.00277804f,0.250114f}, +{-0.242298f,0.00290668f,0.250043f},{-0.223019f,0.00295167f,0.25086f},{-0.205019f,0.00393556f,0.251522f}, +{-0.197296f,-0.00129897f,0.256673f},{-0.187644f,-0.0012604f,0.256731f},{-0.182808f,0.00538888f,0.250545f}, +{-0.165053f,0.00322176f,0.250944f},{-0.145741f,0.00293882f,0.246532f},{-0.126552f,0.00269444f,0.244899f}, +{-0.107318f,0.0028745f,0.247227f},{-0.0880004f,0.00292596f,0.24967f},{-0.070374f,0.00535674f,0.250912f}, +{-0.0655895f,-0.00108035f,0.257329f},{-0.0516415f,0.00210285f,0.261696f},{-0.0365937f,0.00380054f,0.265612f}, +{-0.0172245f,0.00390985f,0.270017f},{0.000485494f,0.00627631f,0.272699f},{0.00534707f,-0.000321504f,0.277117f}, +{0.0214173f,0.00432143f,0.27902f},{0.0407607f,0.00445003f,0.282512f},{0.0600077f,0.00450149f,0.284499f}, +{0.0792675f,0.00448859f,0.284557f},{0.0985081f,0.00456576f,0.284165f},{0.117723f,0.00455291f,0.283419f}, +{0.138642f,0.00290668f,0.279039f},{0.156853f,0.00437928f,0.2714f},{0.175657f,0.00355616f,0.265027f}, +{0.194974f,0.0035176f,0.265368f},{0.214241f,0.00346614f,0.263342f},{0.231874f,0.000932473f,0.260339f}, +{0.24642f,-0.00137614f,0.257316f},{0.252175f,0.00538245f,0.251587f},{0.26894f,0.00381982f,0.247175f}, +{0.322064f,-0.0572007f,0.2416f},{-0.291949f,0.326305f,-0.244404f},{0.269679f,-0.0710202f,-0.116948f}, +{-0.216819f,0.248796f,-0.00958167f},{-0.0730684f,-0.0932189f,-0.147172f},{-0.354384f,0.00255942f,0.286846f}, +{-0.148301f,-0.0143211f,-0.117212f},{-0.167258f,-0.115186f,-0.135996f},{-0.202228f,0.26403f,-0.0192791f}, +{-0.240812f,0.26194f,-0.00583259f},{-0.321658f,-0.108916f,-0.100903f},{-0.406144f,-0.109861f,-0.0514581f}, +{-0.184435f,0.0248224f,0.232462f},{-0.0559564f,0.00782613f,0.254391f},{0.236787f,0.00754962f,0.253677f}, +{-0.471461f,0.153892f,0.192476f},{0.354989f,-0.0195428f,0.165815f},{-0.0901997f,-0.0957333f,-0.143802f}, +{-0.354236f,0.000160776f,-0.0532459f},{0.134655f,-0.0128999f,-0.11999f},{-0.22026f,0.262988f,-0.0135172f}, +{-0.0951127f,0.335848f,-0.0834057f},{-0.0741166f,-0.106421f,-0.153333f},{-0.377425f,0.102852f,0.350574f}, +{-0.223025f,0.0160702f,0.241124f},{-0.203733f,0.0163467f,0.240127f},{-0.184435f,0.0163724f,0.239883f}, +{-0.165117f,0.0162567f,0.239793f},{-0.145761f,0.0162889f,0.238417f},{-0.100932f,0.0162567f,0.237523f}, +{-0.0880326f,0.0161667f,0.238951f},{-0.0687277f,0.0162567f,0.242089f},{-0.186582f,-0.0970837f,-0.130993f}, +{-0.205849f,-0.0971673f,-0.131198f},{-0.225983f,-0.0937719f,-0.131147f},{-0.244613f,-0.0927558f,-0.129166f}, +{0.0436095f,-0.0169962f,-0.133642f},{-0.408382f,0.166136f,-0.0139224f},{-0.148024f,-0.0964985f,-0.136401f}, +{-0.167329f,-0.09673f,-0.134388f},{-0.0181698f,-0.468686f,0.352194f},{-0.0138485f,-0.474975f,0.347751f}, +{-0.277743f,0.131031f,-0.0370406f},{-0.392981f,0.111701f,-0.0340182f},{-0.36958f,-0.147365f,-0.0868654f}, +{-0.335568f,-0.145288f,-0.100267f},{-0.244214f,-0.153127f,-0.128105f},{-0.0341115f,0.355397f,-0.101212f}, +{0.0795183f,-0.471322f,0.319308f},{-0.205611f,-0.152838f,-0.129764f},{-0.354609f,0.168599f,-0.0385261f}, +{-0.27928f,0.0571686f,0.270982f},{0.28728f,0.037973f,0.209768f},{-0.196344f,0.15885f,0.115514f}, +{-0.0909392f,0.321437f,-0.0765248f},{-0.411855f,0.0734253f,-0.021562f},{-0.0920967f,0.359847f,-0.0930324f}, +{0.00476189f,0.378239f,-0.128079f},{-0.257673f,0.263824f,0.00112538f},{-0.337176f,0.294003f,0.179235f}, +{0.193759f,-0.468075f,0.296003f},{0.203868f,-0.464377f,0.29401f},{0.226909f,-0.464377f,0.281824f}, +{0.232439f,-0.471187f,0.268165f},{-0.223359f,0.0587248f,0.226636f},{0.337877f,-0.0567763f,0.223099f}, +{0.283409f,-0.0394521f,0.260898f},{0.305871f,-0.038674f,0.243973f},{-0.359792f,0.411254f,-0.319675f}, +{-0.316932f,-0.0879393f,-0.100492f},{-0.295357f,0.0945178f,-0.0458249f},{0.0972734f,-0.0184753f,-0.130112f}, +{-0.319999f,0.0210283f,-0.0620752f},{-0.208865f,-0.48248f,0.296756f},{0.281396f,0.0496897f,0.200804f}, +{-0.0529083f,-0.470326f,0.357101f},{-0.0337449f,-0.470204f,0.355718f},{-0.224896f,-0.153005f,-0.128652f}, +{0.00438891f,-0.470583f,0.342085f},{0.0221504f,-0.470403f,0.336041f},{0.0362593f,-0.467927f,0.332909f}, +{0.0427028f,-0.471567f,0.329391f},{-0.263564f,-0.15341f,-0.125424f},{0.0613195f,-0.470731f,0.324369f}, +{0.0817562f,-0.450185f,0.318607f},{0.0937622f,-0.452223f,0.314877f},{-0.305151f,0.0611363f,0.319868f}, +{-0.261365f,0.170676f,0.226269f},{0.154873f,-0.453645f,0.302691f},{0.136127f,-0.0378509f,-0.128311f}, +{0.174036f,-0.45349f,0.29981f},{0.192531f,-0.450294f,0.297367f},{0.207353f,-0.448899f,0.294859f}, +{0.214967f,-0.453947f,0.292145f},{0.229758f,-0.449645f,0.285791f},{0.242529f,-0.450461f,0.277554f}, +{-0.0154175f,0.365416f,-0.0681393f},{-0.220948f,0.0948201f,0.228051f},{-0.00928267f,0.455734f,-0.103862f}, +{-0.298984f,0.267445f,-0.185949f},{0.306624f,-0.11352f,0.283611f},{-0.26986f,-0.102743f,-0.120941f}, +{-0.277705f,-0.108697f,-0.115308f},{-0.203251f,0.266191f,0.0780875f},{0.116006f,-0.473155f,0.309173f}, +{-0.393952f,-0.00196778f,0.282595f},{-0.260201f,-0.0911417f,-0.125533f},{-0.20664f,0.0760426f,0.208784f}, +{0.0097135f,0.476647f,-0.128587f},{-0.207714f,0.11282f,0.207672f},{-0.239873f,0.0960033f,0.249375f}, +{0.0360663f,0.124607f,0.138832f},{-0.112797f,0.338169f,-0.0814507f},{-0.242034f,-0.473245f,0.309964f}, +{-0.22116f,-0.473226f,0.307559f},{-0.200228f,-0.471837f,0.302923f},{-0.185547f,-0.471965f,0.29855f}, +{-0.175104f,-0.46884f,0.296363f},{-0.165863f,-0.473663f,0.29091f},{0.080335f,-0.0869425f,-0.19045f}, +{-0.0541687f,-0.450931f,0.358381f},{-0.0333912f,-0.452834f,0.356625f},{-0.0183178f,-0.453747f,0.352214f}, +{-0.00994504f,-0.450423f,0.349217f},{0.00439533f,-0.451465f,0.343661f},{0.0232372f,-0.450738f,0.337121f}, +{0.035777f,-0.450262f,0.33262f},{0.0452815f,-0.449876f,0.329552f},{0.0613517f,-0.449175f,0.324562f}, +{0.018665f,0.454686f,-0.13352f},{0.311247f,-0.0895276f,0.271033f},{0.271274f,-0.456088f,0.2118f}, +{0.285441f,-0.447471f,0.207325f},{-0.280271f,-0.151236f,-0.117662f},{-0.373528f,0.0931996f,-0.0408604f}, +{-0.292257f,0.000778124f,-0.0879071f},{0.397997f,-0.14896f,0.0178644f},{-0.242124f,0.112556f,0.245175f}, +{-0.261217f,-0.113392f,-0.12716f},{-0.296405f,0.362252f,-0.378322f},{0.115459f,-0.0204816f,-0.125121f}, +{-0.186608f,0.262988f,-0.0231375f},{0.291949f,0.00689366f,0.23196f},{-0.378467f,0.270114f,-0.0726857f}, +{-0.241282f,0.20991f,0.191331f},{-0.266432f,0.214675f,0.220945f},{-0.321922f,0.148928f,0.300318f}, +{-0.225006f,0.150625f,0.207273f},{-0.449828f,0.0165461f,0.208109f},{0.284508f,0.00328608f,0.240012f}, +{-0.427584f,0.278917f,0.17049f},{0.00499982f,0.381267f,-0.0894312f},{-0.0553069f,0.379377f,-0.0579595f}, +{-0.256998f,0.232591f,0.213659f},{0.270477f,-0.112845f,0.308093f},{0.344745f,-0.150709f,-0.0930774f}, +{0.192775f,-0.434263f,0.297823f},{0.209186f,-0.432417f,0.295637f},{0.219372f,-0.431883f,0.293373f}, +{0.231095f,-0.4316f,0.287881f},{0.244581f,-0.431517f,0.279149f},{0.255146f,-0.431465f,0.271811f}, +{0.26494f,-0.432809f,0.261349f},{0.27321f,-0.432964f,0.247317f},{0.285917f,-0.0718819f,0.279567f}, +{0.34805f,-0.0900614f,0.229112f},{0.284939f,-0.431819f,0.227575f},{0.290688f,-0.436957f,0.212559f}, +{-0.0194303f,0.32381f,-0.0228095f},{0.0190058f,0.416668f,-0.11754f},{-0.317016f,0.0406546f,0.324202f}, +{-0.225031f,-0.115771f,-0.13096f},{-0.29591f,-0.108099f,-0.111006f},{-0.128745f,-0.0962477f,-0.138915f}, +{-0.0921482f,0.378952f,-0.0964985f},{-0.257146f,0.212231f,0.213305f},{-0.31108f,0.215099f,0.26967f}, +{0.266625f,0.0558632f,0.208238f},{-0.0705347f,0.303026f,0.00482944f},{-0.160982f,0.264551f,0.0449439f}, +{-0.241436f,-0.45848f,0.31334f},{-0.221925f,-0.453098f,0.308042f},{-0.347986f,0.126401f,-0.0469438f}, +{-0.056741f,0.419929f,-0.059683f},{-0.0377255f,0.417562f,-0.0774766f},{0.00501911f,0.418546f,-0.108415f}, +{-0.108431f,0.360291f,-0.0161152f},{-0.123684f,0.360297f,0.0013183f},{0.118681f,-0.0667567f,0.323301f}, +{-0.0209672f,-0.434443f,0.352278f},{-0.0112955f,-0.434044f,0.34923f},{0.00418956f,-0.433484f,0.343494f}, +{-0.288412f,0.311244f,-0.202566f},{-0.248072f,0.078467f,0.258558f},{-0.239661f,0.0757982f,0.249683f}, +{-0.149638f,0.267715f,0.0368927f},{-0.259879f,0.0952252f,0.267863f},{-0.231166f,0.0767757f,0.241549f}, +{-0.0733899f,0.417614f,-0.038436f},{0.298727f,0.0175557f,0.219131f},{-0.259866f,0.132851f,0.251034f}, +{-0.312057f,0.0155944f,0.265104f},{-0.338449f,0.149506f,0.320871f},{-0.300669f,0.00340824f,-0.0818816f}, +{-0.352905f,-0.0371435f,-0.0703514f},{-0.11135f,0.359725f,-0.0874891f},{-0.109395f,-0.114292f,-0.144587f}, +{-0.244433f,-0.115784f,-0.131648f},{-0.468921f,0.0167261f,0.133224f},{0.366917f,-0.111482f,0.208218f}, +{-0.198383f,0.11336f,0.189576f},{-0.0144787f,0.417626f,-0.0947879f},{-0.221199f,0.0771487f,0.229909f}, +{0.388229f,-0.0951802f,0.152728f},{-0.0728047f,0.360516f,-0.0380631f},{-0.0275329f,0.417562f,-0.0853734f}, +{-0.167503f,0.30617f,0.0426546f},{-0.204106f,0.13197f,0.189602f},{-0.198376f,0.13516f,0.172773f}, +{-0.192216f,0.125488f,0.167673f},{0.361046f,-0.170882f,-0.0731745f},{-0.229083f,0.28365f,0.0875856f}, +{0.358242f,-0.113347f,0.225941f},{-0.319581f,0.0599724f,0.339983f},{-0.0514099f,0.365204f,-0.0523841f}, +{0.268168f,0.0377544f,0.222404f},{-0.223347f,0.267413f,0.095669f},{-0.0961931f,0.384071f,-0.0266679f}, +{-0.112733f,0.380348f,-0.00300953f},{-0.0167101f,0.49093f,-0.096177f},{-0.490592f,0.0611877f,0.262905f}, +{-0.0644706f,0.415916f,-0.0521012f},{-0.0676409f,0.398431f,-0.0530208f},{0.189097f,-0.416366f,0.29518f}, +{0.197984f,-0.414379f,0.292962f},{0.21145f,-0.415858f,0.291116f},{0.230838f,-0.413607f,0.284338f}, +{0.244838f,-0.415363f,0.277721f},{0.254284f,-0.413954f,0.270718f},{0.267223f,-0.413929f,0.261072f}, +{0.277094f,-0.414038f,0.252693f},{0.28557f,-0.413279f,0.244648f},{0.293492f,-0.417189f,0.231073f}, +{0.305878f,-0.416784f,0.212713f},{-0.227327f,0.128941f,0.224642f},{-0.223224f,0.112466f,0.227247f}, +{-0.318803f,0.192f,0.286119f},{-0.101537f,0.393704f,-0.0115687f},{-0.297634f,-0.0333687f,-0.0998939f}, +{0.380833f,-0.409485f,0.203865f},{-0.3536f,-0.14678f,-0.0937976f},{-0.433192f,-0.208842f,0.0983956f}, +{-0.296804f,0.112357f,-0.0439471f},{-0.311974f,-0.108466f,-0.106807f},{0.327903f,-0.112755f,0.266737f}, +{-0.312842f,0.0968265f,0.324909f},{-0.321896f,0.0938169f,0.336722f},{-0.133099f,0.360361f,0.0160252f}, +{-0.182062f,0.285933f,0.0615414f},{0.0271084f,0.458397f,-0.14642f},{-0.0142793f,0.398547f,-0.0889875f}, +{-0.260168f,0.0768786f,0.267303f},{-0.245635f,0.248346f,0.189332f},{-0.255288f,0.250031f,0.207112f}, +{-0.254149f,0.180946f,0.213967f},{-0.0864249f,0.361982f,-0.0313945f},{-0.160506f,0.299064f,0.0497283f}, +{-0.146526f,0.304022f,0.0418829f},{-0.16522f,0.285142f,0.054307f},{0.3565f,-0.0403074f,0.185563f}, +{0.401939f,-0.130542f,0.0756053f},{-0.471544f,0.0158323f,0.0947558f},{-0.214549f,0.188586f,0.0321405f}, +{-0.0324845f,0.379506f,-0.0701778f},{-0.00051769f,0.474737f,-0.113488f},{-0.44822f,-0.0546284f,0.0385583f}, +{-0.246645f,0.0419408f,0.241221f},{-0.339844f,0.168355f,0.316575f},{-0.247905f,0.0936947f,0.258853f}, +{0.301106f,-0.410617f,0.225131f},{-0.277724f,0.15476f,0.247806f},{-0.282046f,0.0767114f,0.280949f}, +{0.402042f,-0.408662f,0.188142f},{0.418954f,-0.410295f,0.170355f},{0.435513f,-0.408321f,0.152812f}, +{0.438639f,-0.413022f,0.133706f},{0.0369602f,0.397331f,-0.12026f},{-0.015006f,0.379615f,-0.0788013f}, +{-0.296013f,0.194772f,0.249735f},{-0.271403f,0.0767499f,0.273618f},{-0.296052f,0.233439f,0.253876f}, +{-0.0931256f,0.358702f,-0.026057f},{0.0373653f,0.416655f,-0.134652f},{0.289987f,-0.0919456f,0.286853f}, +{-0.30328f,0.230276f,0.258461f},{0.289203f,-0.015723f,0.248269f},{-0.302868f,0.212533f,0.260371f}, +{-0.44323f,-0.0558117f,0.0205267f},{-0.192968f,0.266171f,0.0703579f},{0.374203f,-0.082634f,-0.0143404f}, +{0.0159127f,0.46994f,-0.135108f},{0.0217646f,0.378901f,-0.095611f},{0.0231021f,0.473882f,-0.151879f}, +{0.301756f,-0.0208418f,0.240513f},{0.309402f,-0.0145526f,0.228989f},{0.182943f,-0.402064f,0.308781f}, +{0.195637f,-0.397074f,0.30462f},{0.208427f,-0.395601f,0.296775f},{0.216311f,-0.399395f,0.289367f}, +{0.231893f,-0.396225f,0.281206f},{0.2504f,-0.396482f,0.270808f},{0.267165f,-0.396199f,0.261143f}, +{0.278348f,-0.400424f,0.254674f},{0.287988f,-0.395203f,0.249285f},{0.299434f,-0.395974f,0.239967f}, +{0.307318f,-0.396444f,0.228726f},{0.313742f,-0.401936f,0.215215f},{-0.303987f,0.113392f,0.300588f}, +{-0.294502f,0.113173f,0.285811f},{0.31917f,-0.0224945f,0.222327f},{0.400608f,-0.39247f,0.205743f}, +{0.408922f,-0.397061f,0.1951f},{0.420208f,-0.392611f,0.18883f},{0.427822f,-0.397479f,0.177113f}, +{0.439635f,-0.392412f,0.1684f},{0.446156f,-0.399505f,0.150465f},{0.361876f,-0.0899392f,0.208707f}, +{-0.231739f,0.0949937f,0.240803f},{0.22797f,-0.0427896f,0.297444f},{-0.000562705f,0.412919f,-0.102955f}, +{0.208209f,-0.114646f,0.336581f},{0.325954f,-0.0160188f,0.209434f},{-0.229314f,-0.422893f,0.297373f}, +{0.335549f,-0.0257162f,0.204546f},{0.342674f,-0.0180701f,0.189614f},{0.25267f,0.0384875f,0.231658f}, +{-0.247024f,0.193113f,0.203543f},{-0.293878f,0.160278f,0.252693f},{-0.277229f,0.176194f,0.231568f}, +{-0.183457f,0.30354f,0.0561461f},{0.0275457f,0.419292f,-0.126272f},{-0.354101f,0.131256f,0.338709f}, +{0.00573935f,0.343995f,-0.0542169f},{-0.0781551f,0.4f,-0.0418507f},{-0.3189f,0.113263f,0.32172f}, +{-0.146404f,0.283489f,0.0418765f},{-0.334025f,0.0150671f,0.30518f},{-0.00970067f,0.32399f,-0.0313044f}, +{-0.182737f,0.266159f,0.0625575f},{-0.0179705f,0.45576f,-0.0938105f},{-0.000446955f,0.455728f,-0.11372f}, +{-0.320874f,0.213749f,0.28192f},{0.302707f,0.0025787f,0.224906f},{-0.0689592f,0.379358f,-0.0494132f}, +{-0.104206f,0.378342f,-0.0179094f},{-0.1296f,0.268184f,0.0197164f},{0.370371f,-0.0281856f,0.0563133f}, +{0.187129f,-0.392251f,0.315f},{0.316977f,-0.392238f,0.220057f},{-0.284277f,0.108646f,0.277837f}, +{-0.278888f,0.114041f,0.272088f},{0.150313f,-0.00275876f,0.275464f},{0.412485f,-0.387647f,0.200412f}, +{0.431275f,-0.387942f,0.182316f},{0.455345f,-0.389711f,0.15114f},{-0.0200026f,0.310922f,-0.0380052f}, +{0.175824f,0.0522877f,-0.0882287f},{0.283087f,-0.0592392f,0.275515f},{0.00377156f,0.361879f,-0.0719591f}, +{0.390254f,-0.131533f,-0.00779394f},{0.242452f,0.0339861f,0.239407f},{-0.238729f,0.194444f,0.190971f}, +{-0.316668f,0.169814f,0.286049f},{-0.261217f,0.112659f,0.26275f},{-0.0322916f,0.455812f,-0.074795f}, +{-0.0542909f,0.3464f,-0.0339217f},{-0.127864f,0.341989f,0.00335682f},{-0.115446f,0.305971f,0.0322626f}, +{-0.129755f,0.323848f,0.0176007f},{-0.0725218f,0.318768f,0.0133436f},{-0.207887f,0.168895f,0.0386483f}, +{0.0360728f,0.453194f,-0.156779f},{0.0101058f,0.457323f,-0.124903f},{-0.211533f,0.188663f,0.0418186f}, +{-0.0685862f,0.325848f,0.002135f},{0.307324f,-0.0717597f,0.265432f},{-0.0945404f,0.40009f,-0.0183209f}, +{0.177837f,-0.382605f,0.330009f},{0.191991f,-0.377975f,0.322234f},{0.200344f,-0.382894f,0.311617f}, +{0.213064f,-0.378001f,0.30754f},{0.230722f,-0.376554f,0.301514f},{0.236652f,-0.382116f,0.290717f}, +{0.250394f,-0.377563f,0.282364f},{0.258966f,-0.38229f,0.272062f},{0.270091f,-0.377743f,0.265541f}, +{0.283795f,-0.375737f,0.258988f},{0.290463f,-0.378882f,0.253516f},{0.305235f,-0.376612f,0.245362f}, +{0.313382f,-0.382438f,0.232005f},{0.322231f,-0.377113f,0.225607f},{-0.292052f,0.0570336f,0.285714f}, +{0.262085f,-0.0423974f,0.279104f},{-0.305601f,0.0956368f,0.315276f},{-0.297235f,0.0772451f,0.304858f}, +{-0.496135f,0.0952059f,0.111591f},{-0.169921f,0.26612f,0.0528215f},{0.402376f,-0.375493f,0.214102f}, +{0.417096f,-0.373068f,0.204971f},{0.426922f,-0.375383f,0.195081f},{0.43851f,-0.372522f,0.187312f}, +{0.445326f,-0.379178f,0.174316f},{0.456529f,-0.372972f,0.168065f},{0.463731f,-0.380155f,0.151333f}, +{-0.319536f,0.0236584f,0.302132f},{0.02316f,0.398142f,-0.110672f},{-0.396113f,-0.0172985f,0.225954f}, +{-0.27838f,0.0343655f,0.251625f},{-0.204119f,0.247343f,0.0765635f},{-0.317221f,0.415234f,-0.373956f}, +{-0.00159804f,0.325687f,-0.0402495f},{-0.334713f,0.133533f,0.32381f},{-0.0906884f,0.286422f,0.00184562f}, +{0.331388f,-0.0875084f,0.251304f},{0.323703f,-0.094878f,0.262667f},{-0.335253f,0.191968f,0.304112f}, +{-0.0371403f,0.436661f,-0.0764413f},{0.0386708f,0.435954f,-0.149725f},{-0.0347031f,0.34287f,-0.0372014f}, +{-0.0741873f,0.340799f,-0.01971f},{-0.326018f,0.186438f,0.29691f},{0.0039066f,0.436693f,-0.113276f}, +{-0.470258f,0.19256f,0.17213f},{-0.1118f,0.339095f,-0.00407059f},{0.211913f,-0.109598f,-0.153326f}, +{0.170133f,0.0323526f,0.258873f},{0.203135f,-0.373306f,0.315803f},{0.24377f,-0.372367f,0.295527f}, +{0.261847f,-0.372773f,0.276416f},{-0.305415f,0.195048f,0.264442f},{-0.305382f,0.0776631f,0.321167f}, +{-0.465918f,0.174753f,0.172265f},{-0.241661f,0.0589627f,0.245458f},{-0.233565f,0.247799f,0.157506f}, +{0.470959f,-0.370734f,0.151024f},{0.00161085f,0.397589f,-0.0976882f},{-0.24631f,0.12907f,0.242384f}, +{0.341066f,-0.0399023f,0.206759f},{-0.210633f,0.208341f,0.0389377f},{-0.16785f,0.247285f,0.0393235f}, +{-0.192801f,0.0938427f,0.183113f},{0.249326f,-0.038436f,0.283213f},{-0.337317f,0.0215106f,0.322909f}, +{-0.153317f,0.299264f,0.0489309f},{0.0229285f,0.436655f,-0.130948f},{0.337767f,-0.0763126f,-0.0658564f}, +{-0.144063f,0.340651f,0.0202759f},{-0.0110833f,0.31743f,-0.0566734f},{-0.22815f,0.170766f,0.189544f}, +{-0.182615f,0.378534f,-0.0300955f},{0.403109f,-0.148568f,0.0563969f},{0.359426f,-0.0361403f,0.00128615f}, +{0.189502f,-0.359159f,0.335854f},{0.199071f,-0.359313f,0.327063f},{0.213135f,-0.359082f,0.319141f}, +{0.232253f,-0.357442f,0.31525f},{0.237815f,-0.362953f,0.309649f},{0.249886f,-0.357718f,0.301347f}, +{0.256021f,-0.363133f,0.290749f},{0.267769f,-0.358059f,0.28192f},{0.274766f,-0.363615f,0.272499f}, +{0.288528f,-0.359326f,0.266628f},{0.309479f,-0.359493f,0.252706f},{0.320102f,-0.358857f,0.242069f}, +{0.329478f,-0.360316f,0.229523f},{0.249211f,0.0551815f,0.223446f},{0.263847f,-0.0760683f,0.298569f}, +{-0.261467f,0.0591171f,0.261985f},{0.400839f,-0.148748f,0.0371628f},{-0.24278f,0.229446f,0.191351f}, +{0.420253f,-0.356452f,0.208701f},{0.441384f,-0.356554f,0.195061f},{0.452979f,-0.35586f,0.185743f}, +{0.462535f,-0.357005f,0.173551f},{0.477017f,-0.356818f,0.152979f},{-0.0931771f,0.328195f,0.000559486f}, +{-0.147105f,0.322556f,0.0240057f},{-0.332674f,0.215286f,0.291341f},{-0.484425f,0.0606154f,0.1922f}, +{0.0209736f,0.359635f,-0.0773866f},{0.370621f,-0.103174f,0.19856f},{0.0443297f,0.398135f,-0.130356f}, +{-0.347979f,0.195595f,0.310697f},{-0.188872f,0.111746f,0.168046f},{-0.302122f,0.0224752f,0.262448f}, +{-0.19581f,0.158921f,0.094595f},{-0.474637f,0.189756f,0.11262f},{-0.328218f,0.118562f,0.327571f}, +{0.302302f,-0.353873f,0.261619f},{0.337844f,-0.355133f,0.222398f},{0.287023f,0.0218385f,0.224835f}, +{-0.330565f,0.168226f,0.307302f},{0.43289f,-0.351185f,0.201994f},{0.470535f,-0.351847f,0.165847f}, +{-0.129517f,0.3041f,0.0381531f},{-0.495852f,0.115186f,0.132401f},{-0.371998f,-0.281168f,0.0942992f}, +{0.320964f,0.00302242f,0.202135f},{-0.066895f,0.346728f,-0.0293945f},{-0.0516607f,0.323623f,-0.0016398f}, +{-0.305247f,0.172001f,0.264429f},{-0.369978f,0.442159f,-0.334922f},{-0.273551f,0.213196f,0.231227f}, +{-0.0336999f,0.398508f,-0.0774766f},{-0.297016f,0.176271f,0.248076f},{0.191695f,-0.341108f,0.339211f}, +{0.210138f,-0.341057f,0.326305f},{0.230517f,-0.340343f,0.322015f},{0.244433f,-0.338542f,0.315752f}, +{0.252831f,-0.341102f,0.306717f},{0.262657f,-0.338806f,0.296859f},{0.271904f,-0.341185f,0.289078f}, +{0.285769f,-0.339462f,0.279714f},{0.29328f,-0.344915f,0.271271f},{0.306579f,-0.339539f,0.265554f}, +{0.326128f,-0.340896f,0.248629f},{0.334359f,-0.345429f,0.234443f},{0.342423f,-0.340285f,0.225851f}, +{0.349587f,-0.343204f,0.214932f},{-0.295216f,0.28457f,-0.205325f},{0.340031f,-0.0957268f,0.244892f}, +{-0.453384f,0.0383525f,0.308678f},{-0.206524f,0.09455f,0.209112f},{0.435783f,-0.33696f,0.202939f}, +{0.446336f,-0.339204f,0.197138f},{0.457866f,-0.337822f,0.187351f},{0.466059f,-0.338111f,0.176547f}, +{0.471904f,-0.338459f,0.167892f},{0.481846f,-0.338864f,0.153969f},{0.303202f,-0.0957333f,0.280055f}, +{0.0814218f,-0.0515803f,0.29255f},{-0.292264f,0.345217f,-0.341024f},{-0.0167551f,0.356053f,-0.0587505f}, +{-0.19363f,0.107617f,0.182425f},{-0.164204f,0.243098f,0.0192984f},{-0.247487f,0.230038f,0.203376f}, +{-0.124462f,0.283644f,0.0264107f},{-0.219212f,0.247761f,0.0982477f},{-0.21082f,0.130903f,0.203293f}, +{-0.292605f,0.211903f,0.251201f},{-0.310257f,0.172207f,0.274229f},{-0.241192f,0.170798f,0.20827f}, +{-0.218337f,0.132022f,0.212688f},{0.319665f,-0.335674f,0.258641f},{0.337111f,-0.336053f,0.238623f}, +{-0.324443f,0.16811f,0.299051f},{-0.310984f,0.0778753f,0.329893f},{-0.319781f,0.0780104f,0.33977f}, +{-0.212723f,0.247503f,0.0867303f},{-0.111903f,0.327655f,0.00719593f},{-0.287132f,0.171544f,0.23987f}, +{-0.195708f,0.242571f,0.0661265f},{0.317562f,-0.076139f,0.257664f},{-0.106804f,0.318067f,0.0255683f}, +{-0.302739f,0.130337f,0.282878f},{-0.310894f,0.135976f,0.290222f},{-0.191585f,0.28338f,0.0687952f}, +{0.194016f,-0.319443f,0.337095f},{0.211733f,-0.321552f,0.328806f},{0.231353f,-0.322774f,0.323301f}, +{0.24642f,-0.322466f,0.316903f},{0.256098f,-0.322536f,0.308241f},{0.267371f,-0.321765f,0.298537f}, +{0.273506f,-0.326838f,0.293785f},{0.287344f,-0.322485f,0.285579f},{0.307743f,-0.323861f,0.269805f}, +{0.323073f,-0.32143f,0.26149f},{0.332546f,-0.323867f,0.251092f},{0.340134f,-0.320414f,0.243246f}, +{0.347445f,-0.324067f,0.22879f},{0.353149f,-0.328247f,0.21652f},{-0.298476f,0.095341f,0.302948f}, +{-0.291113f,0.0951159f,0.290724f},{-0.48919f,0.0783126f,0.230668f},{0.457493f,-0.320871f,0.190232f}, +{0.467326f,-0.324928f,0.17838f},{0.474811f,-0.320138f,0.16993f},{0.483763f,-0.321134f,0.157011f}, +{0.488161f,-0.321031f,0.148625f},{-0.0857175f,0.396045f,-0.0339217f},{0.345233f,-0.114582f,0.246526f}, +{-0.0343365f,0.360728f,-0.0558889f},{-0.277596f,0.193653f,0.228842f},{-0.455699f,-0.0891225f,0.038719f}, +{0.402235f,-0.13053f,0.0563583f},{-0.488714f,0.0780746f,0.192135f},{0.346564f,-0.1702f,-0.0924986f}, +{-0.360197f,0.287618f,-0.207794f},{0.169702f,-0.313784f,0.354754f},{0.299865f,-0.318048f,0.275464f}, +{0.355612f,-0.314749f,0.220244f},{-0.177753f,0.242481f,0.0468217f},{0.448632f,-0.315919f,0.196881f}, +{0.311498f,0.00544677f,0.212662f},{-0.243262f,0.14869f,0.227414f},{-0.0769911f,0.379383f,-0.04405f}, +{-0.314482f,0.133552f,-0.0457734f},{-0.427822f,0.00120256f,0.0205267f},{-0.297923f,0.24859f,-0.113122f}, +{-0.236767f,0.180811f,0.193653f},{-0.285808f,0.376689f,-0.360123f},{-0.288335f,0.304633f,-0.13287f}, +{-0.354223f,-0.222089f,0.246018f},{0.206987f,-0.310447f,0.33343f},{0.219893f,-0.301328f,0.329514f}, +{0.23089f,-0.301251f,0.3228f},{0.244728f,-0.30273f,0.315688f},{0.254548f,-0.299733f,0.308582f}, +{0.269043f,-0.300884f,0.298859f},{0.289016f,-0.300582f,0.288184f},{0.30128f,-0.300659f,0.277631f}, +{0.308932f,-0.302312f,0.272866f},{0.325871f,-0.299572f,0.263927f},{0.333214f,-0.309154f,0.255915f}, +{0.343516f,-0.299733f,0.246076f},{0.351194f,-0.306865f,0.232899f},{-0.324096f,0.127372f,0.316569f}, +{0.458355f,-0.303045f,0.188367f},{0.474715f,-0.303276f,0.172342f},{0.484348f,-0.303739f,0.158034f}, +{0.488881f,-0.300743f,0.148433f},{-0.185644f,0.247124f,0.0580239f},{-0.318411f,0.0184432f,0.285946f}, +{-0.0494679f,0.415948f,-0.0680621f},{-0.266908f,0.231768f,0.224417f},{0.375901f,-0.0972251f,-0.0192984f}, +{-0.310476f,0.0367319f,0.308395f},{0.325941f,-0.0720298f,0.247214f},{-0.0904505f,0.342626f,-0.0160445f}, +{0.323137f,-0.0387383f,0.22861f},{-0.34477f,0.182007f,0.315257f},{-0.482824f,0.0947237f,0.0602039f}, +{0.172461f,-0.296769f,0.359423f},{0.180094f,-0.302177f,0.350683f},{0.192299f,-0.297347f,0.346175f}, +{0.209636f,-0.297097f,0.337314f},{0.358738f,-0.296884f,0.225099f},{-0.231481f,0.144838f,0.218501f}, +{-0.174866f,0.25187f,0.0510595f},{0.0382849f,0.358741f,-0.0940355f},{0.00948199f,0.40337f,-0.105437f}, +{-0.0533199f,0.355944f,-0.0437928f},{-0.329292f,0.154535f,0.309013f},{-0.299132f,0.0402945f,0.28493f}, +{-0.469062f,0.0256133f,0.0585576f},{-0.0876338f,0.318446f,0.0195621f},{-0.282431f,0.21445f,0.244815f}, +{-0.185753f,0.0938748f,0.170605f},{-0.2486f,0.162773f,0.222147f},{-0.409804f,-0.0203466f,-0.0172149f}, +{0.246278f,-0.0565512f,0.297617f},{0.31135f,-0.282692f,0.272609f},{0.371734f,-0.293926f,0.198457f}, +{0.274052f,-0.011035f,0.254854f},{0.462734f,-0.28037f,0.176682f},{0.473396f,-0.283309f,0.168239f}, +{0.482065f,-0.280004f,0.154928f},{0.488502f,-0.286955f,0.147256f},{0.355336f,-0.0938298f,0.221742f}, +{-0.385644f,-0.0936176f,-0.070255f},{-0.229443f,0.266911f,0.112292f},{-0.28103f,0.232713f,0.246024f}, +{-0.0279059f,0.436661f,-0.0857271f},{-0.0987525f,0.336832f,-0.00780037f},{-0.132468f,0.287444f,0.033761f}, +{0.192711f,-0.279515f,0.352574f},{0.201521f,-0.279393f,0.348388f},{0.211668f,-0.279393f,0.340433f}, +{0.22242f,-0.27938f,0.333378f},{0.233243f,-0.279367f,0.326395f},{0.245172f,-0.27783f,0.31727f}, +{0.253358f,-0.280332f,0.307932f},{0.268136f,-0.279386f,0.297643f},{0.288328f,-0.279059f,0.287335f}, +{0.303903f,-0.277464f,0.277837f},{0.32661f,-0.278718f,0.263561f},{0.345934f,-0.278891f,0.24623f}, +{0.36021f,-0.279412f,0.227253f},{0.36857f,-0.279464f,0.207698f},{0.372068f,-0.285174f,0.199029f}, +{-0.499177f,0.0955082f,0.153455f},{-0.482046f,0.209968f,0.133494f},{-0.0162342f,0.337552f,-0.0352014f}, +{-0.076258f,0.309418f,0.0135044f},{-0.0918524f,0.0832128f,-0.0219671f},{-0.349998f,0.15015f,0.330182f}, +{-0.290624f,0.0768914f,0.291476f},{0.281312f,-0.0972187f,0.295515f},{-0.0157326f,0.436661f,-0.096402f}, +{-0.016067f,0.313353f,-0.0512716f},{-0.266927f,0.0447767f,0.256918f},{-0.324392f,0.0462815f,0.338529f}, +{-0.298624f,0.0591621f,0.303161f},{0.353098f,-0.102164f,0.229716f},{-0.21318f,0.261574f,0.0861387f}, +{0.492849f,-0.283226f,0.134684f},{0.0356741f,0.375403f,-0.101309f},{0.269242f,-0.0938169f,0.302961f}, +{0.379361f,-0.150484f,-0.0398829f},{0.270863f,-0.0711231f,0.289502f},{0.362139f,-0.151886f,-0.0719526f}, +{-0.462303f,0.0608019f,0.0229896f},{-0.0906884f,0.305135f,0.0184882f},{-0.0543552f,0.337352f,-0.0225845f}, +{-0.2624f,0.148439f,0.243992f},{-0.293184f,0.131089f,0.270634f},{-0.197296f,0.094132f,0.193865f}, +{-0.279531f,0.0439793f,0.261503f},{-0.1169f,0.271001f,0.0107649f},{0.190441f,-0.262172f,0.355063f}, +{0.201984f,-0.261895f,0.349217f},{0.213565f,-0.261863f,0.343372f},{0.227301f,-0.261754f,0.334973f}, +{0.236838f,-0.263168f,0.327996f},{0.24894f,-0.260178f,0.320993f},{0.256567f,-0.266352f,0.309225f}, +{0.266528f,-0.260603f,0.301051f},{0.273293f,-0.266474f,0.293804f},{0.286933f,-0.261959f,0.285431f}, +{0.304257f,-0.261676f,0.276583f},{0.313299f,-0.261484f,0.27275f},{0.326166f,-0.261477f,0.263066f}, +{0.345298f,-0.261664f,0.245465f},{0.360191f,-0.262101f,0.227369f},{0.368622f,-0.262718f,0.211286f}, +{0.373535f,-0.263258f,0.201196f},{0.355747f,-0.0973281f,-0.0497219f},{-0.153548f,0.343011f,0.0336388f}, +{0.475203f,-0.262641f,0.15096f},{0.484431f,-0.260873f,0.133886f},{0.296695f,-0.0675219f,0.272589f}, +{0.0291791f,0.36586f,-0.0880936f},{-0.252953f,0.0538568f,0.254911f},{-0.296611f,0.305817f,-0.244442f}, +{0.394196f,-0.149301f,-0.00137614f},{-0.294939f,0.0174657f,0.250101f},{-0.0417382f,0.337487f,-0.0269187f}, +{-0.111884f,0.285277f,0.0182438f},{-0.300199f,-0.305321f,0.170843f},{-0.310874f,0.195203f,0.273663f}, +{-0.00157232f,0.374972f,-0.0819909f},{-0.467641f,-0.114054f,0.135192f},{-0.445532f,-0.0192534f,0.13469f}, +{-0.48319f,0.0278319f,0.0975724f},{0.335234f,-0.16685f,-0.104138f},{-0.446426f,0.0595158f,0.0026816f}, +{-0.0854732f,-0.131076f,-0.165879f},{-0.210762f,0.14786f,0.186386f},{-0.0732292f,0.438963f,-0.0937912f}, +{-0.352095f,0.359474f,-0.285496f},{-0.0802643f,0.422379f,-0.0257162f},{-0.00456257f,0.356265f,-0.0636635f}, +{0.257931f,-0.0343397f,0.272988f},{-0.107009f,0.301945f,0.0255362f},{-0.00114789f,0.3377f,-0.0415099f}, +{-0.0524774f,0.437343f,-0.0565962f},{0.0514549f,0.383396f,-0.136047f},{-0.292032f,0.0369249f,0.266107f}, +{-0.306997f,0.0260635f,0.278821f},{0.191142f,-0.244854f,0.356233f},{0.203566f,-0.244513f,0.351693f}, +{0.214048f,-0.244507f,0.344246f},{0.231854f,-0.244236f,0.33615f},{0.252522f,-0.244571f,0.323925f}, +{0.270097f,-0.244931f,0.304042f},{0.288315f,-0.245921f,0.287566f},{0.302289f,-0.242121f,0.279554f}, +{0.3109f,-0.245703f,0.273117f},{0.325864f,-0.244674f,0.265188f},{0.345368f,-0.244359f,0.245671f}, +{0.36014f,-0.244777f,0.227472f},{0.36967f,-0.245246f,0.212874f},{0.375084f,-0.245651f,0.203595f}, +{0.381914f,-0.246275f,0.190669f},{0.38936f,-0.245638f,0.174753f},{0.291145f,-0.0359409f,0.253278f}, +{0.254272f,-0.0530337f,0.29037f},{-0.28575f,0.144632f,0.258808f},{0.0141571f,0.329494f,-0.0638436f}, +{-0.414929f,0.245169f,0.0165204f},{-0.317208f,0.131674f,0.303855f},{-0.204415f,-0.261458f,0.263316f}, +{-0.423675f,-0.108736f,-0.0320054f},{0.29573f,0.0261407f,0.214739f},{-0.307177f,0.0458571f,0.313128f}, +{0.244658f,-0.239465f,0.332022f},{0.264008f,-0.239696f,0.314607f},{0.281171f,-0.240037f,0.294113f}, +{0.33515f,-0.239632f,0.25931f},{-0.475133f,0.157525f,0.0939584f},{-0.381605f,0.418109f,-0.31127f}, +{-0.317819f,-0.314434f,0.132928f},{0.387007f,-0.149873f,-0.0206167f},{-0.213379f,0.0719012f,0.221864f}, +{-0.300649f,0.284679f,-0.225845f},{-0.0719495f,0.00154982f,-0.110222f},{-0.294026f,0.342208f,-0.359886f}, +{-0.416755f,-0.208431f,0.173589f},{-0.397791f,0.244089f,-0.00489371f},{-0.0996592f,0.290222f,0.0133694f}, +{-0.0757886f,0.290743f,-0.00540174f},{-0.0452944f,0.384206f,-0.0660557f},{-0.0610817f,0.441446f,-0.0430918f}, +{-0.307685f,0.144851f,0.27774f},{0.38801f,-0.112652f,-0.00266871f},{0.336725f,-0.000199339f,0.183357f}, +{0.230157f,0.0578695f,-0.0557795f},{0.193065f,-0.22724f,0.359236f},{0.207109f,-0.22553f,0.353204f}, +{0.214093f,-0.228404f,0.348246f},{0.231925f,-0.226526f,0.34206f},{0.245539f,-0.226372f,0.333423f}, +{0.25296f,-0.226436f,0.327153f},{0.264831f,-0.2256f,0.31826f},{0.273602f,-0.228938f,0.305939f}, +{0.2842f,-0.224437f,0.298788f},{0.293023f,-0.228031f,0.292685f},{0.307209f,-0.227182f,0.283605f}, +{0.325337f,-0.226662f,0.267792f},{0.33742f,-0.225176f,0.258924f},{0.347478f,-0.227542f,0.246391f}, +{0.360371f,-0.227433f,0.227928f},{0.370146f,-0.227825f,0.213839f},{0.375457f,-0.228237f,0.204347f}, +{0.382949f,-0.225581f,0.190965f},{0.389721f,-0.227414f,0.176367f},{-0.464149f,0.17485f,0.191557f}, +{0.335317f,-0.00309312f,-0.0148612f},{-0.294605f,0.246108f,-0.0770907f},{-0.231636f,0.247857f,0.147918f}, +{-0.257378f,0.195544f,0.212797f},{-0.300174f,0.149108f,0.266191f},{-0.266483f,0.126832f,0.258397f}, +{-0.184467f,0.073078f,-0.0691939f},{0.316778f,-0.222109f,0.278094f},{0.393463f,-0.22135f,0.167634f}, +{0.398994f,-0.222141f,0.152883f},{0.40428f,-0.223215f,0.133481f},{-0.224337f,0.188322f,0.154188f}, +{0.0944117f,-0.00196778f,-0.126137f},{-0.141645f,0.261509f,0.0257677f},{-0.153027f,0.289978f,0.0494518f}, +{-0.202595f,0.225678f,0.0589692f},{-0.350725f,0.323347f,-0.265856f},{0.371689f,-0.0461207f,0.136742f}, +{-0.287788f,0.306292f,-0.147879f},{0.193508f,-0.20555f,0.359879f},{0.21318f,-0.209279f,0.354638f}, +{0.230407f,-0.209041f,0.345648f},{0.243481f,-0.20899f,0.33624f},{0.254413f,-0.208913f,0.329443f}, +{0.267898f,-0.208861f,0.320684f},{0.276869f,-0.209016f,0.310929f},{0.288103f,-0.209697f,0.301347f}, +{0.308038f,-0.2089f,0.288222f},{0.319832f,-0.207517f,0.278834f},{0.32796f,-0.209993f,0.269477f}, +{0.338552f,-0.209286f,0.258751f},{0.349098f,-0.209562f,0.245799f},{0.361194f,-0.205698f,0.22924f}, +{0.370628f,-0.206161f,0.21452f},{0.37565f,-0.206508f,0.204926f},{0.381772f,-0.204598f,0.19265f}, +{-0.18529f,0.230616f,0.0361275f},{-0.192518f,0.233105f,0.0540176f},{-0.26629f,0.193145f,0.221073f}, +{-0.309517f,0.153905f,0.275007f},{0.00359793f,-0.0376451f,-0.136844f},{0.400395f,-0.112588f,0.056339f}, +{0.377425f,-0.112543f,0.18883f},{-0.27231f,0.227221f,0.233259f},{-0.392177f,0.323874f,-0.150098f}, +{0.392782f,-0.0995724f,0.0242693f},{0.297447f,-0.204656f,0.295566f},{0.389238f,-0.203325f,0.179113f}, +{0.393997f,-0.203846f,0.168702f},{0.400254f,-0.203286f,0.152606f},{0.404016f,-0.205543f,0.137488f}, +{0.406575f,-0.203736f,0.0958555f},{0.373728f,-0.0370471f,0.0916627f},{-0.285036f,0.196302f,0.239285f}, +{-0.49265f,0.113205f,0.172702f},{0.391894f,-0.0905887f,0.0307064f},{-0.0491335f,0.400045f,-0.0677341f}, +{0.387496f,-0.131507f,0.172104f},{-0.30445f,0.042571f,0.302254f},{-0.289537f,0.331526f,-0.205775f}, +{0.210852f,-0.192984f,0.353641f},{0.230388f,-0.186656f,0.3435f},{0.2464f,-0.187447f,0.334999f}, +{0.256336f,-0.189357f,0.329636f},{0.26829f,-0.187139f,0.321404f},{0.277544f,-0.187287f,0.312067f}, +{0.287859f,-0.187396f,0.302119f},{0.297582f,-0.195955f,0.295823f},{0.3078f,-0.187319f,0.288036f}, +{0.320893f,-0.187293f,0.278641f},{0.330063f,-0.187415f,0.269316f},{0.336108f,-0.187647f,0.261021f}, +{0.3465f,-0.187949f,0.247799f},{0.405495f,-0.20337f,0.114061f},{-0.463545f,0.155307f,0.0387769f}, +{0.349606f,-0.0372335f,0.194515f},{-0.25541f,0.153847f,0.234912f},{0.335838f,-0.0763834f,0.238951f}, +{0.275666f,0.0441979f,0.211537f},{-0.40626f,0.244191f,0.00486803f},{-0.247198f,0.214655f,0.203582f}, +{-0.194151f,0.251947f,0.0684094f},{-0.393611f,-0.26093f,0.0749301f},{-0.336989f,0.11408f,0.337558f}, +{0.193643f,-0.184219f,0.35469f},{0.214215f,-0.182181f,0.348728f},{0.363619f,-0.184116f,0.227555f}, +{0.372885f,-0.184528f,0.21245f},{0.377219f,-0.183409f,0.203685f},{0.383579f,-0.18629f,0.191351f}, +{0.389901f,-0.185685f,0.179049f},{0.393913f,-0.185467f,0.168824f},{0.399412f,-0.186483f,0.153088f}, +{0.403026f,-0.186547f,0.134369f},{0.153992f,-0.0128613f,-0.119212f},{0.342526f,-0.0724093f,0.226012f}, +{0.351702f,-0.0712453f,0.212816f},{-0.323607f,0.0270281f,0.316819f},{-0.0144658f,0.347044f,-0.0496318f}, +{0.24024f,0.0426288f,0.236269f},{-0.410202f,0.0403524f,0.321244f},{0.295923f,-0.178888f,0.293463f}, +{-0.380415f,0.441587f,-0.323456f},{0.315099f,-0.0676699f,0.254069f},{0.255937f,0.0257355f,0.23643f}, +{-0.233983f,0.153783f,0.214951f},{-0.433906f,-0.109958f,-0.0181473f},{-0.293139f,0.346458f,-0.282949f}, +{-0.47429f,0.245143f,0.0948394f},{-0.298727f,0.325096f,-0.339359f},{0.270522f,0.0224816f,0.232616f}, +{-0.0683805f,0.449986f,-0.0894955f},{0.160712f,-0.171879f,0.369146f},{0.174441f,-0.167917f,0.363262f}, +{0.187348f,-0.165776f,0.355075f},{0.197418f,-0.167114f,0.348883f},{0.24514f,-0.170104f,0.333404f}, +{0.25141f,-0.16494f,0.328877f},{0.266342f,-0.165725f,0.318806f},{0.277287f,-0.165692f,0.311874f}, +{0.28674f,-0.165789f,0.302878f},{0.294534f,-0.16611f,0.291585f},{0.306186f,-0.165937f,0.285791f}, +{0.320392f,-0.165744f,0.278126f},{0.329388f,-0.165905f,0.268461f},{0.335632f,-0.166104f,0.26041f}, +{0.346127f,-0.166381f,0.247375f},{0.363381f,-0.166792f,0.227176f},{0.372846f,-0.167236f,0.212366f}, +{0.376017f,-0.167487f,0.205871f},{0.38365f,-0.16858f,0.191942f},{0.389933f,-0.166516f,0.179422f}, +{-0.392833f,0.0234912f,0.319732f},{-0.494894f,0.0964214f,0.174059f},{-0.0558921f,0.396836f,-0.0624288f}, +{-0.253506f,0.117868f,0.254564f},{-0.0270828f,0.450976f,-0.084441f},{-0.0342787f,0.379268f,-0.11507f}, +{-0.381399f,-0.0122504f,-0.0409311f},{0.369933f,-0.151256f,-0.0590527f},{0.0296164f,0.38456f,-0.104524f}, +{-0.0463297f,0.431909f,-0.0671232f},{-0.310083f,0.0582618f,0.327507f},{0.331279f,-0.00854636f,0.198232f}, +{0.35731f,-0.0743127f,0.205871f},{-0.392602f,-0.0367319f,0.227928f},{-0.215926f,0.0532652f,0.217864f}, +{-0.286309f,0.0630462f,0.280576f},{-0.46703f,0.191994f,0.0951931f},{-0.369458f,-0.277586f,0.132684f}, +{0.392743f,-0.117739f,0.00504166f},{-0.0631459f,0.365101f,-0.0467509f},{-0.325035f,0.00603198f,0.262551f}, +{-0.314392f,-0.0357866f,-0.090563f},{-0.0242726f,0.346928f,-0.0468859f},{0.394499f,-0.131205f,0.00184562f}, +{0.353773f,-0.0515031f,0.196663f},{0.173123f,-0.150137f,0.36467f},{0.190126f,-0.14977f,0.355461f}, +{0.199875f,-0.149822f,0.346921f},{0.265705f,-0.148503f,0.31808f},{0.276772f,-0.148452f,0.311302f}, +{0.285705f,-0.148632f,0.301598f},{0.294502f,-0.148818f,0.29165f},{0.306527f,-0.148613f,0.286537f}, +{0.321633f,-0.148272f,0.280094f},{0.330391f,-0.148478f,0.270075f},{0.337838f,-0.147269f,0.26014f}, +{0.347072f,-0.149712f,0.246455f},{0.360615f,-0.149519f,0.228887f},{0.370171f,-0.14995f,0.214572f}, +{0.375168f,-0.150304f,0.204784f},{0.381952f,-0.150812f,0.19184f},{0.298502f,-0.0759397f,0.275194f}, +{0.33124f,-0.0341532f,0.215356f},{-0.271448f,0.0951159f,0.273721f},{-0.0428379f,0.374792f,-0.062506f}, +{0.22062f,-0.145429f,0.331745f},{0.114508f,0.00446288f,-0.119282f},{-0.457262f,0.227337f,0.0693869f}, +{0.333793f,-0.0678563f,0.235941f},{-0.282194f,0.0950195f,0.280801f},{-0.353799f,0.226996f,-0.10954f}, +{-0.372088f,0.422546f,-0.321089f},{0.278161f,-0.0846404f,0.290981f},{0.373618f,-0.150953f,-0.0526349f}, +{-0.309517f,0.113656f,0.309482f},{0.197817f,-0.131153f,0.347841f},{0.210472f,-0.132478f,0.339745f}, +{0.219572f,-0.132677f,0.330292f},{0.266393f,-0.1328f,0.315308f},{0.27321f,-0.129957f,0.310016f}, +{0.284489f,-0.131507f,0.299926f},{0.294437f,-0.131539f,0.291804f},{0.306829f,-0.131243f,0.287161f}, +{0.321067f,-0.13107f,0.279361f},{0.33014f,-0.131211f,0.26985f},{0.33868f,-0.131423f,0.259509f}, +{0.348288f,-0.131822f,0.245246f},{0.359657f,-0.132337f,0.22787f},{0.369187f,-0.132099f,0.209877f}, +{0.373663f,-0.137539f,0.202727f},{0.379676f,-0.133372f,0.18964f},{-0.393232f,0.266429f,-0.0018713f}, +{-0.386332f,0.318556f,-0.0768207f},{-0.384435f,0.284216f,-0.00197419f},{-0.234812f,0.117643f,0.236436f}, +{-0.23489f,0.0535288f,0.23578f},{-0.374834f,0.170168f,-0.0319604f},{-0.0530176f,0.034687f,-0.0872898f}, +{-0.391007f,0.326273f,-0.111803f},{-0.105858f,0.346156f,-0.0124497f},{-0.284901f,-0.307379f,0.175287f}, +{0.0462268f,0.397987f,-0.147699f},{0.396698f,-0.112768f,0.114164f},{-0.294573f,0.346574f,-0.205781f}, +{-0.382042f,0.248281f,-0.0404103f},{0.301756f,-0.0592071f,0.263098f},{-0.451204f,0.0230218f,0.0365069f}, +{-0.496013f,0.0583776f,0.113443f},{-0.367014f,0.453825f,-0.336015f},{0.265088f,0.0171763f,0.238275f}, +{-0.350133f,0.114054f,-0.0474583f},{0.0793125f,-0.0969293f,-0.202637f},{-0.184801f,0.230391f,0.0214463f}, +{-0.297061f,0.264165f,-0.167287f},{0.0243175f,0.418411f,-0.166252f},{-0.467223f,0.19274f,0.230018f}, +{0.262966f,-0.118594f,0.314202f},{0.319414f,-0.118298f,0.277052f},{0.335613f,-0.118517f,0.260802f}, +{-0.214363f,0.270905f,0.0846661f},{-0.301241f,0.247413f,-0.132774f},{-0.216234f,0.117546f,0.218051f}, +{-0.340494f,0.127443f,0.332504f},{-0.486219f,0.134652f,0.095341f},{-0.0820585f,0.412855f,-0.0283914f}, +{-0.112675f,-0.091489f,-0.140169f},{-0.0356548f,0.30491f,-0.0375936f},{0.033642f,0.347982f,-0.0946079f}, +{-0.289055f,0.321347f,-0.218642f},{-0.289184f,0.180727f,0.236848f},{-0.334507f,-0.0908009f,-0.0943185f}, +{-0.380448f,0.248204f,-0.0540368f},{0.0440211f,0.38148f,-0.149088f},{-0.373059f,-0.0920935f,-0.0786277f}, +{-0.315627f,-0.0169255f,-0.0797981f},{0.00336643f,0.327083f,-0.0718112f},{-0.425436f,0.207395f,0.0221344f}, +{0.24876f,-0.472712f,-0.00379407f},{0.237996f,-0.475419f,-0.0214655f},{0.232973f,-0.473644f,-0.0391434f}, +{0.229333f,-0.47401f,-0.0584097f},{0.226768f,-0.473483f,-0.0764348f},{0.21536f,-0.483161f,-0.0790199f}, +{0.215553f,-0.479669f,-0.0921128f},{0.243217f,-0.466917f,0.259413f},{0.246272f,-0.467946f,0.247426f}, +{0.2504f,-0.468242f,0.230372f},{-0.357651f,0.244089f,-0.170348f},{-0.296708f,0.346574f,-0.186476f}, +{-0.284097f,0.395949f,-0.37966f},{-0.317807f,-0.306826f,0.154715f},{-0.27665f,-0.0892061f,-0.114967f}, +{0.377882f,-0.452609f,0.0563905f},{0.365676f,-0.455542f,0.0379345f},{0.36284f,-0.450699f,0.0208804f}, +{0.350294f,-0.457143f,0.0219286f},{0.246008f,-0.467168f,-0.0164175f},{0.223842f,-0.470146f,-0.0938298f}, +{0.212466f,-0.469425f,-0.113649f},{0.177528f,0.0819909f,-0.0581396f},{0.250445f,-0.0753995f,-0.128144f}, +{-0.287994f,0.321437f,-0.18647f},{0.257706f,-0.456783f,0.246899f},{0.266753f,-0.453702f,0.228102f}, +{-0.463313f,-0.0535996f,0.0963442f},{-0.292862f,0.266171f,-0.115655f},{-0.297691f,0.418154f,-0.340291f}, +{-0.486406f,0.0608212f,0.211479f},{-0.392788f,0.092788f,-0.0334844f},{-0.333645f,0.073824f,-0.0471689f}, +{0.401514f,-0.441831f,0.0767628f},{0.397302f,-0.441947f,0.0568471f},{0.388287f,-0.44295f,0.0376966f}, +{0.378776f,-0.444076f,0.0248802f},{0.359496f,-0.445253f,0.00628921f},{0.344159f,-0.450371f,0.0042957f}, +{-0.201231f,0.169287f,0.0771036f},{0.25568f,-0.458268f,-0.0205074f},{0.248497f,-0.453471f,-0.0392013f}, +{0.238356f,-0.459477f,-0.0585383f},{0.236433f,-0.456005f,-0.0791228f},{0.233108f,-0.455432f,-0.0939584f}, +{0.228137f,-0.453645f,-0.108517f},{0.22152f,-0.46122f,-0.110086f},{0.207739f,-0.462738f,-0.12325f}, +{0.193013f,-0.469998f,-0.129411f},{0.0398026f,-0.0865888f,-0.191074f},{-0.467647f,0.0190926f,0.246281f}, +{-0.292798f,0.305984f,-0.22515f},{-0.0710363f,-0.0359988f,-0.136619f},{-0.496019f,0.056204f,0.13433f}, +{0.261358f,-0.447111f,0.256468f},{0.26712f,-0.446609f,0.243651f},{-0.469294f,0.0183531f,0.0758561f}, +{-0.355683f,0.217601f,-0.0730137f},{-0.206736f,0.169075f,0.134896f},{-0.298148f,0.43724f,-0.361525f}, +{-0.29227f,0.266262f,-0.096357f},{-0.286824f,0.396965f,-0.360117f},{-0.429559f,0.152137f,1.28796e-05f}, +{0.417263f,-0.431394f,0.113746f},{0.418292f,-0.432346f,0.0939777f},{0.421462f,-0.43025f,0.0760104f}, +{0.417102f,-0.430546f,0.0568278f},{0.411803f,-0.430751f,0.0441787f},{0.405071f,-0.431832f,0.0345455f}, +{0.39608f,-0.428835f,0.0193756f},{0.381856f,-0.434423f,0.0154336f},{0.364422f,-0.435478f,-0.00126682f}, +{0.289216f,-0.0535674f,-0.0970258f},{-0.453339f,0.134864f,0.0193306f},{0.269724f,-0.452873f,-0.0171763f}, +{0.244555f,-0.449497f,-0.0585769f},{0.241179f,-0.449799f,-0.0713803f},{0.212524f,-0.450153f,-0.127932f}, +{0.155471f,0.0844796f,-0.0584097f},{-0.462837f,-0.0924729f,0.15249f},{-0.481685f,0.0599273f,0.0604418f}, +{-0.47656f,0.0597601f,0.0508215f},{-0.294315f,0.376772f,-0.302241f},{0.0440661f,0.419118f,-0.17339f}, +{-0.20855f,0.188798f,0.0578245f},{-0.287113f,0.417118f,-0.380277f},{-0.370043f,-0.258249f,0.186502f}, +{-0.210177f,0.16521f,0.154014f},{-0.401611f,-0.0144818f,0.216019f},{0.439886f,-0.415434f,0.114472f}, +{0.44069f,-0.416218f,0.0951867f},{0.440446f,-0.415112f,0.0759204f},{0.43597f,-0.415356f,0.0566927f}, +{0.423385f,-0.421093f,0.0375679f},{0.421539f,-0.41407f,0.020308f},{0.405296f,-0.4218f,0.0159866f}, +{0.315569f,-0.0725507f,-0.0834893f},{-0.108765f,0.0418508f,-0.0803318f},{0.302238f,-0.0549371f,-0.0891289f}, +{0.271486f,-0.43169f,-0.0343847f},{0.256265f,-0.438925f,-0.0425581f},{0.251686f,-0.436108f,-0.0600173f}, +{0.248471f,-0.43623f,-0.0766277f},{0.24631f,-0.434648f,-0.0921128f},{0.237391f,-0.440835f,-0.109984f}, +{0.230446f,-0.433742f,-0.127115f},{0.215707f,-0.436205f,-0.134716f},{0.00438248f,-0.0895598f,-0.188193f}, +{-0.293428f,0.266049f,-0.131738f},{-0.300553f,0.380226f,-0.282229f},{-0.296058f,0.326035f,-0.282981f}, +{-0.150725f,0.37847f,-0.061072f},{-0.45377f,0.0130671f,0.153905f},{-0.431726f,0.0403845f,0.320234f}, +{-0.458683f,0.0119096f,0.140979f},{-0.353413f,0.305341f,-0.246449f},{-0.204119f,0.169069f,0.0578631f}, +{-0.109042f,0.272705f,-0.00145331f},{0.449043f,-0.409749f,0.0951416f},{0.436105f,-0.410643f,0.0375422f}, +{0.40271f,-0.413234f,0.000861727f},{0.306546f,-0.074287f,-0.0900743f},{0.269499f,-0.0541912f,-0.108228f}, +{0.100129f,0.070165f,-0.0682357f},{0.262104f,-0.428867f,-0.0521269f},{0.243442f,-0.431086f,-0.110164f}, +{0.218157f,-0.0130735f,-0.101431f},{-0.108019f,-0.476364f,-0.188161f},{-0.335703f,-0.278917f,0.190425f}, +{-0.375515f,0.270152f,-0.130504f},{-0.431944f,0.190862f,0.0184432f},{-0.487537f,0.078184f,0.211369f}, +{-0.289775f,0.331468f,-0.186476f},{0.0238481f,0.341397f,-0.0924857f},{-0.288521f,0.39191f,-0.34404f}, +{-0.446143f,0.0075689f,0.264294f},{-0.225732f,0.208109f,0.13487f},{0.0427799f,0.363577f,-0.128607f}, +{0.458786f,-0.395499f,0.133726f},{0.461525f,-0.396122f,0.115688f},{0.459699f,-0.399807f,0.0951031f}, +{0.460323f,-0.397119f,0.0742484f},{0.458426f,-0.395183f,0.056577f},{0.446169f,-0.400977f,0.0373943f}, +{0.439462f,-0.396843f,0.0184432f},{0.428928f,-0.402225f,0.0122118f},{0.419314f,-0.399698f,-0.000353668f}, +{0.40662f,-0.403916f,-0.00723448f},{0.0976528f,0.0429054f,-0.0970322f},{0.287975f,-0.41598f,-0.036629f}, +{0.275512f,-0.417671f,-0.0584611f},{0.266586f,-0.418668f,-0.0745378f},{0.253018f,-0.421536f,-0.091161f}, +{0.24723f,-0.417035f,-0.110479f},{0.231848f,-0.41652f,-0.131038f},{0.307492f,-0.0907044f,-0.0960162f}, +{0.440838f,-0.2645f,0.0228546f},{0.459384f,-0.319617f,0.00039872f},{-0.03415f,-0.0165011f,-0.131044f}, +{-0.296032f,0.326208f,-0.12925f},{-0.259333f,-0.27547f,0.242835f},{-0.303955f,0.309939f,-0.303907f}, +{-0.239005f,0.247696f,0.173621f},{-0.208993f,0.152182f,0.0179416f},{-0.192061f,0.145751f,0.059818f}, +{-0.209662f,0.208521f,0.0578181f},{-0.0908363f,-0.0219285f,-0.124105f},{-0.300752f,0.304575f,-0.264204f}, +{-0.128597f,0.261561f,-0.000932427f},{-0.292573f,0.2663f,-0.0770715f},{0.468425f,-0.389859f,0.107919f}, +{0.469159f,-0.389807f,0.095058f},{0.46856f,-0.389717f,0.0790007f},{0.457275f,-0.390682f,0.03742f}, +{0.449937f,-0.391357f,0.0247388f},{0.432722f,-0.392901f,0.00583906f},{0.41172f,-0.394187f,-0.0133114f}, +{0.304617f,-0.411684f,-0.0309572f},{0.286933f,-0.407781f,-0.058609f},{0.280965f,-0.40809f,-0.0777981f}, +{0.272824f,-0.408469f,-0.0935468f},{0.13452f,0.0551622f,-0.0911482f},{0.00276838f,-0.0574065f,-0.142613f}, +{0.0986881f,-0.111392f,-0.208566f},{-0.0535256f,-0.0160316f,-0.128793f},{-0.480194f,0.112318f,0.0601846f}, +{-0.291717f,0.331552f,-0.167159f},{-0.291486f,0.376772f,-0.321514f},{0.0521751f,0.396354f,-0.154664f}, +{-0.466503f,0.262262f,0.0949037f},{-0.304456f,0.303926f,-0.282345f},{-0.230684f,0.208173f,0.154329f}, +{-0.438227f,-0.0888203f,-0.00416705f},{-0.317356f,-0.279991f,0.207871f},{-0.309556f,0.298486f,-0.301161f}, +{-0.394203f,0.00450149f,0.299174f},{-0.497608f,0.0648661f,0.118202f},{0.474933f,-0.375789f,0.133629f}, +{0.47793f,-0.375473f,0.114311f},{0.479036f,-0.375467f,0.0950195f},{0.47784f,-0.375454f,0.0757275f}, +{0.474194f,-0.375776f,0.0564869f},{0.470213f,-0.374702f,0.0414842f},{0.4642f,-0.378284f,0.0329765f}, +{0.456767f,-0.376547f,0.0194078f},{0.447642f,-0.382708f,0.0118903f},{0.439333f,-0.378206f,0.000366566f}, +{0.427507f,-0.384714f,-0.00714445f},{0.417784f,-0.379435f,-0.0165654f},{-0.0898074f,0.0744092f,-0.0384811f}, +{0.174776f,-0.0560882f,-0.130272f},{0.295061f,-0.127295f,-0.121662f},{0.306707f,-0.393621f,-0.0401016f}, +{0.294945f,-0.398193f,-0.0587891f},{0.292759f,-0.395338f,-0.0759461f},{0.289241f,-0.393987f,-0.0908781f}, +{0.272901f,-0.395241f,-0.112292f},{0.255384f,-0.402244f,-0.11734f},{0.326739f,-0.0737533f,-0.0754767f}, +{0.0427671f,0.0614836f,-0.0778946f},{0.024787f,0.0598694f,-0.0759268f},{0.0609787f,-0.0979068f,-0.204971f}, +{0.370113f,-0.108202f,-0.0410212f},{-0.293151f,0.346593f,-0.225067f},{-0.349182f,-0.294312f,0.142934f}, +{-0.198035f,0.154304f,0.0579017f},{-0.460979f,0.0270666f,0.192534f},{-0.284065f,0.227549f,-0.0155815f}, +{-0.211939f,0.163828f,0.0225845f},{0.263307f,0.0682615f,-0.015575f},{-0.144294f,-0.489791f,-0.179949f}, +{0.302778f,-0.387962f,-0.0587505f},{0.298405f,-0.388778f,-0.0717211f},{0.283737f,-0.389698f,-0.106762f}, +{0.306045f,0.0186232f,-0.0359667f},{-0.0324716f,0.064043f,-0.0604032f},{-0.0153082f,0.0546413f,-0.0698948f}, +{0.270367f,0.0232468f,-0.0589048f},{0.058818f,-0.471593f,-0.152047f},{-0.238632f,0.0343269f,-0.0881515f}, +{0.251802f,0.0835021f,0.000527333f},{0.052143f,0.412617f,-0.165088f},{-0.468483f,0.0215491f,0.265831f}, +{-0.231687f,0.203267f,0.167236f},{-0.221269f,0.170843f,0.173268f},{-0.335838f,-0.487309f,-0.0170734f}, +{-0.317948f,0.209016f,-0.0551107f},{-0.236909f,0.268364f,0.153243f},{-0.355079f,0.287393f,-0.246384f}, +{-0.217675f,0.171081f,0.0178065f},{-0.439738f,0.0120318f,0.289688f},{0.4833f,-0.35786f,0.13487f}, +{0.484611f,-0.361365f,0.114292f},{0.485865f,-0.361275f,0.0949809f},{0.484656f,-0.35912f,0.0740555f}, +{0.481518f,-0.357223f,0.0563648f},{0.474387f,-0.357886f,0.0371307f},{0.462078f,-0.359905f,0.0167197f}, +{0.453493f,-0.358374f,0.00308674f},{0.444111f,-0.362361f,-0.0054146f},{0.0754349f,0.029954f,-0.102312f}, +{-0.474747f,0.0408347f,0.0529051f},{0.323382f,-0.376927f,-0.03742f},{0.314842f,-0.377467f,-0.045722f}, +{0.310508f,-0.375879f,-0.0588663f},{0.30654f,-0.373197f,-0.0730652f},{0.299865f,-0.373756f,-0.0897206f}, +{0.29492f,-0.379743f,-0.0974952f},{0.289422f,-0.375679f,-0.110389f},{0.271101f,-0.378528f,-0.12743f}, +{0.00521203f,0.0642294f,-0.0609819f},{0.321961f,0.00183919f,-0.0339667f},{0.286959f,-0.0164046f,-0.0771872f}, +{0.420176f,-0.265104f,0.0233819f},{0.0740008f,-0.0641651f,-0.143892f},{-0.0716794f,0.0824154f,-0.0214269f}, +{-0.29319f,0.346394f,-0.302228f},{-0.280541f,-0.282036f,0.227324f},{-0.146101f,0.253818f,0.0192406f}, +{-0.390807f,-0.220514f,0.206148f},{0.0528182f,0.402739f,-0.162246f},{-0.233102f,0.193158f,0.179949f}, +{0.486489f,-0.352252f,0.127153f},{0.487975f,-0.352124f,0.114273f},{0.489042f,-0.352079f,0.0949487f}, +{0.488078f,-0.352059f,0.0788785f},{0.468322f,-0.354085f,0.0241922f},{0.0194559f,0.0802225f,-0.0425388f}, +{0.0245297f,0.0399794f,-0.0934954f},{-0.482663f,0.0772772f,0.0602553f},{0.330353f,-0.362123f,-0.0421208f}, +{0.317839f,-0.368535f,-0.0525771f},{0.280033f,-0.371969f,-0.123237f},{0.00321209f,0.0560947f,-0.074467f}, +{-0.0921032f,-0.0124755f,-0.115996f},{0.155805f,0.0435163f,-0.0979968f},{0.0239831f,0.0742355f,-0.0537089f}, +{-0.30209f,0.342954f,-0.148047f},{-0.376994f,-0.248468f,0.193524f},{-0.414832f,-0.0382303f,-0.0205588f}, +{0.489524f,-0.338581f,0.133584f},{0.492798f,-0.338304f,0.114247f},{0.494219f,-0.338182f,0.0949166f}, +{0.492907f,-0.338272f,0.0756117f},{0.488952f,-0.337687f,0.0576573f},{0.484521f,-0.343552f,0.0499148f}, +{0.48103f,-0.339385f,0.0371307f},{0.471615f,-0.340298f,0.0210733f},{0.465313f,-0.340876f,0.0115109f}, +{0.455661f,-0.337333f,-0.00201921f},{0.448323f,-0.346883f,-0.00728592f},{0.229906f,0.0176136f,-0.0870004f}, +{0.341992f,-0.359969f,-0.0333044f},{0.32272f,-0.355056f,-0.0600366f},{0.312495f,-0.356863f,-0.0761004f}, +{0.303948f,-0.356149f,-0.0911996f},{0.291717f,-0.357255f,-0.110556f},{0.279017f,-0.362921f,-0.123044f}, +{-0.0522717f,0.0835278f,-0.0215491f},{-0.0338478f,0.0893026f,-0.0163596f},{0.156378f,0.0574451f,-0.0872576f}, +{0.267853f,-0.168811f,-0.147146f},{-0.126996f,0.0363654f,-0.0887431f},{0.0581042f,0.100196f,-0.0409376f}, +{-0.486476f,0.112498f,0.0762162f},{-0.293994f,0.326189f,-0.263683f},{-0.396801f,-0.208456f,0.211575f}, +{-0.43289f,-0.204662f,0.135134f},{-0.458406f,-0.0975531f,0.0515611f},{-0.216016f,0.173004f,0.154812f}, +{-0.235005f,0.268229f,0.138819f},{0.487081f,-0.334253f,0.146368f},{-0.0743738f,-0.469959f,-0.181248f}, +{-0.301569f,0.0376644f,-0.0603903f},{-0.497852f,0.0824411f,0.166387f},{0.346326f,-0.34215f,-0.0371949f}, +{0.332333f,-0.343796f,-0.058474f},{0.319626f,-0.349854f,-0.0715218f},{0.272406f,-0.359423f,-0.128658f}, +{0.26303f,-0.355448f,-0.142593f},{-0.0144658f,-0.115945f,-0.219247f},{-0.0125495f,0.0629755f,-0.0593807f}, +{-0.444941f,0.172072f,0.0224495f},{0.00631167f,-0.0170863f,-0.127764f},{-0.0145301f,-0.106228f,-0.209845f}, +{-0.45222f,0.0166683f,0.285078f},{-0.477821f,0.0350407f,0.274229f},{-0.23015f,0.22796f,0.134928f}, +{-0.207681f,0.188823f,0.0963763f},{-0.218221f,0.208353f,0.11563f},{-0.0711649f,-0.482081f,0.33824f}, +{-0.374094f,-0.00108035f,0.286422f},{0.36958f,-0.0675798f,0.176342f},{0.492753f,-0.320472f,0.133578f}, +{0.496836f,-0.320125f,0.114241f},{0.498785f,-0.319958f,0.0948909f},{0.498148f,-0.320028f,0.0755539f}, +{0.493563f,-0.320356f,0.056249f},{0.488553f,-0.319424f,0.0412334f},{0.484058f,-0.322781f,0.0328607f}, +{0.473416f,-0.322298f,0.0177486f},{-0.22552f,-0.03306f,-0.116273f},{0.339709f,-0.333494f,-0.0574579f}, +{0.326713f,-0.335603f,-0.0747114f},{0.308263f,-0.338452f,-0.0923764f},{0.291325f,-0.340073f,-0.111836f}, +{0.280914f,-0.338613f,-0.125436f},{0.272721f,-0.342388f,-0.13397f},{0.265339f,-0.34143f,-0.145802f}, +{0.174396f,-0.105939f,-0.17647f},{0.265249f,-0.0190926f,-0.0887624f},{-0.0135334f,0.0801711f,-0.0390084f}, +{-0.110829f,-0.0155043f,-0.117257f},{-0.321427f,-0.122806f,-0.102132f},{-0.431456f,0.226842f,0.037002f}, +{-0.292598f,0.346593f,-0.244365f},{-0.289524f,0.376715f,-0.334407f},{-0.442336f,-0.0914504f,0.00441145f}, +{0.155136f,0.0992123f,-0.0394971f},{-0.220401f,0.0890261f,-0.0467252f},{-0.297453f,0.226758f,-0.0571235f}, +{-0.469004f,0.175049f,0.230147f},{-0.48101f,0.026893f,0.136169f},{-0.225771f,-0.290621f,0.23369f}, +{-0.47629f,0.208f,0.173692f},{-0.47476f,0.13559f,0.0549565f},{0.364159f,-0.322453f,-0.020726f}, +{0.357233f,-0.317565f,-0.0383332f},{0.351915f,-0.325732f,-0.0437799f},{0.321292f,-0.331642f,-0.0844281f}, +{0.298476f,-0.334195f,-0.104402f},{0.31126f,-0.0140253f,-0.0577152f},{0.2842f,-0.11408f,-0.124851f}, +{-0.493222f,0.0603582f,0.0957654f},{-0.39111f,-0.0106042f,0.244256f},{-0.332321f,0.414868f,-0.355223f}, +{-0.287962f,0.37674f,-0.343995f},{-0.288611f,0.321456f,-0.167165f},{0.191849f,0.0695348f,-0.0683f}, +{-0.292753f,0.346343f,-0.321507f},{0.272162f,0.0412913f,-0.0399923f},{-0.364384f,-0.290068f,0.113553f}, +{-0.290245f,0.303585f,-0.207402f},{-0.438266f,0.0121475f,0.198039f},{-0.353182f,-0.258943f,0.20755f}, +{-0.0692872f,-0.478042f,0.349088f},{0.493981f,-0.302543f,0.133571f},{0.49845f,-0.302164f,0.114215f}, +{0.5f,-0.302022f,0.0948523f},{0.499408f,-0.302125f,0.075451f},{0.494373f,-0.30255f,0.0561654f}, +{0.488656f,-0.304537f,0.0410662f},{0.483975f,-0.30199f,0.0326163f},{0.472972f,-0.30226f,0.0202695f}, +{-0.390042f,-0.0561139f,-0.0530401f},{-0.404878f,-0.0396f,-0.0322047f},{-0.261004f,0.172316f,-0.0231825f}, +{0.346963f,-0.318073f,-0.058892f},{0.339401f,-0.315096f,-0.0698112f},{0.331613f,-0.317353f,-0.078454f}, +{0.323736f,-0.317096f,-0.089862f},{0.31263f,-0.323109f,-0.0999839f},{0.305659f,-0.318221f,-0.10846f}, +{0.294913f,-0.320723f,-0.117089f},{0.286123f,-0.320543f,-0.12869f},{0.27793f,-0.3268f,-0.13651f}, +{0.267532f,-0.319366f,-0.146092f},{0.227559f,0.0757082f,-0.0364232f},{-0.203444f,0.0201601f,-0.0971351f}, +{0.243847f,0.07048f,-0.0308672f},{-0.289602f,0.281271f,-0.115675f},{-0.290766f,0.231555f,-0.0258319f}, +{0.384582f,-0.301264f,-0.00464292f},{-0.262683f,-0.4823f,0.299836f},{-0.372847f,-0.242108f,0.20863f}, +{-0.354024f,-0.299405f,0.114273f},{-0.411032f,-0.225543f,0.151416f},{-0.226414f,0.170818f,0.00573617f}, +{-0.409308f,0.226379f,0.00172344f},{-0.458477f,-0.115012f,0.0517025f},{0.367078f,-0.304923f,-0.0234912f}, +{0.363471f,-0.29817f,-0.0387833f},{0.35414f,-0.310646f,-0.0525321f},{0.440221f,-0.285418f,0.00548537f}, +{-0.489634f,0.0610013f,0.230771f},{-0.332147f,0.289515f,-0.319668f},{-0.256098f,-0.294389f,0.219202f}, +{-0.293325f,0.361718f,-0.28291f},{-0.292663f,0.32653f,-0.147854f},{-0.3932f,-0.261503f,0.116517f}, +{-0.28883f,0.301283f,-0.122131f},{-0.400505f,-0.231021f,0.173641f},{-0.429925f,0.00713804f,0.209775f}, +{0.498077f,-0.284345f,0.114189f},{0.499132f,-0.284261f,0.0948201f},{0.496688f,-0.284467f,0.075451f}, +{0.491164f,-0.284962f,0.0561461f},{0.487203f,-0.289791f,0.0432977f},{0.482676f,-0.28275f,0.0357609f}, +{0.473917f,-0.282885f,0.0238256f},{-0.447249f,0.22717f,0.0597537f},{-0.438143f,0.226983f,0.0499534f}, +{-0.489145f,0.0429632f,0.095849f},{0.357027f,-0.296839f,-0.0525642f},{0.350564f,-0.299006f,-0.0612585f}, +{0.342082f,-0.295997f,-0.0719462f},{0.333517f,-0.303662f,-0.0786791f},{0.326456f,-0.299733f,-0.091444f}, +{0.317511f,-0.300447f,-0.104061f},{0.306643f,-0.302846f,-0.112331f},{0.285698f,-0.30264f,-0.128067f}, +{-0.472245f,0.174682f,0.152979f},{-0.469969f,0.152953f,0.230263f},{-0.483094f,0.0261792f,0.114871f}, +{0.233037f,-0.0750651f,-0.134279f},{-0.497949f,0.10864f,0.137256f},{-0.28955f,0.281232f,-0.131738f}, +{0.309447f,-0.150008f,-0.116639f},{-0.378332f,-0.206643f,0.233356f},{-0.374178f,0.0174464f,0.323186f}, +{-0.154873f,0.253696f,0.0322498f},{-0.379097f,0.268429f,-0.0550014f},{-0.217025f,0.18946f,0.136144f}, +{-0.400029f,0.226224f,-0.00787111f},{0.371573f,-0.287991f,-0.0247452f},{0.294225f,-0.297347f,-0.119456f}, +{-0.418485f,0.228025f,0.0135044f},{-0.42152f,0.239954f,0.0242179f},{-0.146127f,0.0584097f,-0.0732259f}, +{-0.394048f,0.187267f,-0.0230346f},{-0.35032f,0.454493f,-0.352638f},{0.193405f,0.0027459f,-0.107791f}, +{0.081589f,0.100608f,-0.0416835f},{-0.0187743f,-0.00719588f,-0.126941f},{-0.292534f,0.361622f,-0.302228f}, +{-0.111498f,-0.470692f,0.322247f},{-0.395161f,0.417421f,-0.282634f},{-0.278509f,-0.48138f,0.29565f}, +{-0.452034f,0.0168805f,0.170657f},{0.249912f,-0.148297f,-0.151339f},{-0.473769f,0.209099f,0.18973f}, +{-0.337304f,-0.228623f,0.251394f},{-0.381907f,-0.26976f,0.114736f},{0.489659f,-0.269393f,0.131989f}, +{0.491434f,-0.262898f,0.113945f},{0.492939f,-0.262699f,0.0949745f},{0.491229f,-0.263754f,0.0761712f}, +{0.488727f,-0.26875f,0.0603775f},{0.482374f,-0.260397f,0.0549307f},{0.476316f,-0.262924f,0.0415485f}, +{-0.336732f,0.341301f,-0.320755f},{-0.370255f,0.226462f,-0.0519211f},{-0.486875f,0.0963699f,0.264159f}, +{-0.129002f,-0.0141925f,-0.118453f},{-0.473975f,0.0342111f,0.171956f},{0.384988f,-0.284827f,0.000218661f}, +{0.375277f,-0.280518f,-0.0187839f},{0.368731f,-0.277721f,-0.0397479f},{0.359631f,-0.277663f,-0.057773f}, +{0.353272f,-0.283605f,-0.0653548f},{0.346391f,-0.279721f,-0.0750522f},{0.32944f,-0.28219f,-0.0926465f}, +{0.319466f,-0.282113f,-0.104003f},{0.31234f,-0.282724f,-0.110183f},{0.29427f,-0.284422f,-0.118093f}, +{0.285814f,-0.285123f,-0.126459f},{0.275236f,-0.28192f,-0.137121f},{-0.299846f,0.416623f,-0.384965f}, +{-0.348873f,0.287103f,-0.285007f},{-0.481731f,0.112466f,0.261445f},{-0.490721f,0.0610913f,0.246835f}, +{-0.482721f,0.133417f,0.076885f},{-0.331993f,0.323031f,-0.336889f},{-0.361419f,-0.286872f,0.133931f}, +{0.0434166f,0.0389441f,-0.0937719f},{-0.381882f,-0.226649f,0.213209f},{-0.335645f,0.242629f,-0.188026f}, +{-0.464934f,0.0268287f,0.171313f},{-0.212331f,0.183621f,0.128427f},{-0.356551f,-0.2654f,0.192315f}, +{-0.464811f,-0.0710202f,0.0771808f},{0.0998135f,0.0067136f,-0.119469f},{-0.369374f,0.251902f,-0.150503f}, +{0.384724f,-0.263773f,-0.00166552f},{0.379303f,-0.262139f,-0.0197357f},{0.338102f,-0.275991f,-0.0849683f}, +{-0.260818f,0.0960612f,-0.036674f},{0.229462f,-0.0564998f,-0.129989f},{-0.0901997f,0.0448153f,-0.0784219f}, +{0.275017f,0.0672905f,0.000250814f},{-0.208537f,0.342838f,-0.0226938f},{-0.218665f,0.33842f,-0.0145654f}, +{-0.393907f,0.361911f,-0.225819f},{-0.288753f,0.291296f,-0.115675f},{-0.357632f,0.0262114f,0.336015f}, +{-0.2802f,-0.319816f,0.151204f},{0.00395806f,0.491913f,-0.130401f},{-0.108546f,0.0785827f,-0.037928f}, +{0.375007f,-0.257644f,-0.0355551f},{0.370068f,-0.261085f,-0.0441015f},{0.363824f,-0.260159f,-0.0559853f}, +{0.357625f,-0.260693f,-0.0655927f},{0.348629f,-0.261451f,-0.0752387f},{0.33897f,-0.262281f,-0.0850198f}, +{0.329677f,-0.263091f,-0.0945757f},{0.319382f,-0.263979f,-0.103945f},{0.305614f,-0.265239f,-0.113938f}, +{0.287537f,-0.265188f,-0.132986f},{0.0602585f,-0.0592071f,-0.144002f},{-0.374506f,0.339243f,-0.209807f}, +{-0.48773f,0.113192f,0.191936f},{-0.337098f,0.00798691f,0.287521f},{-0.0892158f,-0.452095f,-0.183222f}, +{-0.147966f,0.251985f,0.00030869f},{-0.317626f,-0.201383f,0.276441f},{-0.429514f,0.00733738f,0.2843f}, +{-0.456201f,0.191775f,0.0726279f},{-0.368409f,0.306633f,-0.168027f},{0.371798f,-0.0463908f,0.0113694f}, +{-0.449712f,-0.0191184f,0.115456f},{0.388711f,-0.241812f,-0.000347237f},{0.29854f,-0.261246f,-0.123199f}, +{0.193322f,0.0785634f,-0.0559982f},{-0.445076f,0.208418f,0.0577345f},{-0.446632f,0.114652f,0.000135062f}, +{-0.291299f,0.361577f,-0.321514f},{-0.143825f,0.0790135f,-0.0590656f},{-0.375097f,-0.0163917f,0.247973f}, +{-0.363078f,0.304755f,-0.189846f},{-0.384164f,0.248281f,-0.0467959f},{-0.486393f,0.0956818f,0.230578f}, +{0.395592f,-0.243368f,0.0176072f},{0.382673f,-0.240442f,-0.0205845f},{0.375734f,-0.24106f,-0.0366226f}, +{0.370049f,-0.241542f,-0.0461528f},{0.361844f,-0.24133f,-0.0578631f},{0.35668f,-0.247207f,-0.0655027f}, +{0.344455f,-0.24369f,-0.075348f},{0.336545f,-0.248879f,-0.0850455f},{0.329247f,-0.244101f,-0.0926529f}, +{0.319954f,-0.245812f,-0.104106f},{0.310488f,-0.246629f,-0.113681f},{0.302045f,-0.247388f,-0.123456f}, +{0.292071f,-0.244236f,-0.134555f},{0.191746f,-0.131372f,-0.183711f},{-0.0560657f,0.0633034f,-0.05722f}, +{-0.33852f,-0.267425f,0.209106f},{-0.408189f,-0.0210411f,0.207968f},{-0.355092f,0.00830841f,0.306119f}, +{-0.28775f,0.412212f,-0.363345f},{-0.0922511f,0.398026f,-0.094878f},{-0.470445f,0.153024f,0.249542f}, +{-0.306122f,0.290994f,-0.258841f},{0.394518f,-0.131931f,0.151461f},{0.0388572f,0.456101f,-0.170921f}, +{0.403649f,-0.224996f,0.0363525f},{0.398505f,-0.222951f,0.0189062f},{0.211135f,-0.130671f,-0.169917f}, +{0.0247484f,0.09219f,-0.0328735f},{-0.217874f,-0.35478f,0.114993f},{-0.148172f,0.0707695f,-0.0669753f}, +{-0.445616f,0.0105785f,0.0410791f},{-0.395753f,-0.226346f,0.190772f},{-0.289807f,0.311373f,-0.128562f}, +{-0.413726f,-0.00173626f,0.226334f},{-0.473853f,0.17447f,0.114408f},{-0.372769f,-0.278968f,0.115f}, +{0.407103f,-0.224147f,0.0562876f},{0.393553f,-0.221459f,0.00189706f},{0.389463f,-0.221806f,-0.00772964f}, +{0.383238f,-0.222353f,-0.0206103f},{0.376756f,-0.222925f,-0.0331693f},{0.369998f,-0.223485f,-0.0430468f}, +{0.360358f,-0.224282f,-0.0560239f},{0.344854f,-0.2256f,-0.0719269f},{0.329452f,-0.226874f,-0.0912253f}, +{0.318147f,-0.227851f,-0.107347f},{0.311839f,-0.228333f,-0.116691f},{0.302617f,-0.2256f,-0.126182f}, +{-0.126938f,0.0753609f,-0.0523327f},{-0.418189f,-0.0564933f,-0.0256004f},{-0.201379f,0.320523f,-0.0340053f}, +{0.289576f,0.0437799f,-0.0211054f},{-0.289962f,0.361538f,-0.33761f},{0.389624f,-0.0725443f,0.0563262f}, +{-0.276998f,-0.313218f,0.167654f},{-0.40718f,-0.221736f,0.171834f},{0.0390244f,0.449098f,-0.179878f}, +{-0.332185f,0.305341f,-0.333899f},{-0.312926f,-0.273162f,0.222655f},{0.408614f,-0.223125f,0.0928395f}, +{0.407874f,-0.22135f,0.0749108f},{-0.428935f,-0.0908717f,-0.0191826f},{-0.0479631f,0.471458f,-0.091399f}, +{-0.425977f,-0.0560368f,-0.0160638f},{0.0831967f,-0.0666924f,-0.148786f},{-0.288489f,0.361519f,-0.347256f}, +{-0.129748f,-0.0760812f,-0.133899f},{-0.479653f,0.210064f,0.146323f},{-0.343356f,0.357937f,-0.306935f}, +{-0.486457f,0.0412077f,0.153989f},{0.391669f,-0.126678f,0.159185f},{-0.362853f,0.219581f,-0.0601395f}, +{0.0237516f,-0.097611f,-0.205505f},{-0.359548f,0.440365f,-0.343513f},{0.250683f,0.061297f,-0.0380052f}, +{-0.352397f,0.2565f,-0.259509f},{0.406704f,-0.202302f,0.075721f},{0.405707f,-0.202399f,0.0564484f}, +{0.403431f,-0.202592f,0.0372078f},{0.399534f,-0.202932f,0.0179673f},{0.395135f,-0.203305f,0.00197423f}, +{0.390936f,-0.203665f,-0.00767819f},{0.383875f,-0.204238f,-0.0204431f},{0.376171f,-0.204868f,-0.0333237f}, +{0.36902f,-0.205453f,-0.0429439f},{0.359419f,-0.205299f,-0.057683f},{0.346622f,-0.20825f,-0.0732967f}, +{0.338899f,-0.207903f,-0.0846403f},{0.33007f,-0.208643f,-0.0942606f},{0.320938f,-0.209492f,-0.107334f}, +{0.31209f,-0.207247f,-0.117662f},{0.304186f,-0.208276f,-0.124137f},{-0.348384f,0.342317f,-0.284036f}, +{-0.463435f,0.174162f,0.0758754f},{-0.0486512f,0.452526f,-0.109765f},{-0.437288f,-0.036912f,0.0178515f}, +{-0.287653f,0.361377f,-0.357017f},{-0.356146f,0.241433f,-0.151095f},{-0.195598f,0.155313f,0.075914f}, +{-0.148108f,-0.0737147f,-0.130735f},{0.14133f,-0.107103f,-0.194894f},{-0.468625f,0.0943635f,0.0377994f}, +{-0.200338f,0.221523f,0.0386161f},{-0.221958f,0.0961384f,-0.0375936f},{-0.434401f,-0.0554902f,-3.21349e-05f}, +{-0.375329f,0.247966f,-0.0918041f},{-0.489807f,0.0955532f,0.192039f},{-0.465647f,-0.0924215f,0.135082f}, +{-0.476965f,0.210109f,0.155982f},{-0.439147f,-0.0508151f,0.012752f},{0.133317f,0.083007f,-0.0597923f}, +{-0.302971f,0.361506f,-0.204521f},{-0.319453f,-0.286955f,0.19263f},{-0.214652f,0.193659f,0.122118f}, +{0.404177f,-0.184483f,0.114234f},{0.405186f,-0.184412f,0.0949552f},{0.406189f,-0.184335f,0.0756825f}, +{0.405534f,-0.184406f,0.0564291f},{0.403225f,-0.184631f,0.0372078f},{0.398781f,-0.185023f,0.017948f}, +{0.393939f,-0.18692f,0.00299671f},{0.390113f,-0.184129f,-0.00556893f},{0.384145f,-0.18618f,-0.0205524f}, +{0.376242f,-0.186766f,-0.0365133f},{0.37111f,-0.187139f,-0.0461786f},{0.366667f,-0.187512f,-0.0558181f}, +{0.358789f,-0.187203f,-0.0704929f},{0.350661f,-0.193286f,-0.078184f},{0.343761f,-0.18836f,-0.0895662f}, +{0.333304f,-0.194669f,-0.0972444f},{0.325401f,-0.188663f,-0.107752f},{0.308463f,-0.187685f,-0.11752f}, +{-0.433809f,0.209929f,0.0358059f},{-0.345034f,0.306595f,-0.289315f},{-0.382981f,0.261805f,-0.0373814f}, +{-0.341838f,0.341352f,-0.304601f},{-0.388113f,0.346079f,-0.204952f},{-0.398852f,0.0174142f,-0.00798686f}, +{-0.412035f,0.0197743f,-0.00122181f},{-0.296122f,-0.0897206f,-0.106923f},{-0.301948f,0.364516f,-0.223478f}, +{-0.386737f,-0.241124f,0.18856f},{-0.319112f,0.290537f,-0.321147f},{-0.338076f,0.206218f,-0.0533487f}, +{-0.12697f,-0.407267f,-0.133867f},{-0.337954f,0.304826f,-0.320568f},{-0.419134f,0.21025f,0.0134851f}, +{-0.388872f,0.226096f,-0.0177036f},{-0.388595f,0.345294f,-0.132086f},{0.117434f,0.0805762f,-0.0601845f}, +{-0.29901f,0.363352f,-0.24279f},{-0.311588f,-0.313225f,0.14451f},{-0.214331f,0.203376f,0.0319475f}, +{0.393611f,-0.167204f,0.168843f},{0.398113f,-0.166876f,0.152773f},{0.40091f,-0.166696f,0.133513f}, +{0.402305f,-0.166606f,0.114221f},{0.403534f,-0.166528f,0.094923f},{0.404768f,-0.166451f,0.0756503f}, +{0.404312f,-0.166496f,0.0564226f},{0.401148f,-0.166773f,0.037195f},{0.395868f,-0.167249f,0.017993f}, +{0.392692f,-0.165918f,0.00290668f},{0.390479f,-0.169133f,-0.00570398f},{0.328179f,-0.168355f,-0.107971f}, +{-0.242484f,0.0954567f,-0.0339667f},{0.0977235f,-0.07684f,-0.168927f},{-0.184415f,0.0829427f,-0.0602038f}, +{-0.228298f,-0.255497f,0.259722f},{-0.126128f,0.11471f,0.0564998f},{-0.146963f,0.11898f,0.0773223f}, +{-0.430079f,0.282325f,0.0953989f},{-0.335414f,0.294782f,0.073824f},{-0.366808f,0.23007f,-0.0692132f}, +{-0.0931578f,0.0893219f,0.149326f},{-0.305775f,0.448204f,-0.372451f},{-0.316032f,0.450011f,-0.373281f}, +{-0.188209f,0.132562f,0.150548f},{-0.206929f,0.154124f,0.168522f},{-0.317549f,0.453014f,-0.358863f}, +{-0.130333f,0.0906209f,0.167557f},{-0.303402f,0.433298f,-0.345352f},{-0.308443f,0.443632f,-0.350227f}, +{-0.179335f,0.124279f,0.0366934f},{-0.191168f,0.128189f,0.0249896f},{-0.313228f,0.437652f,-0.336478f}, +{-0.322366f,0.450899f,-0.34361f},{-0.444799f,0.276518f,0.0951288f},{-0.337523f,0.455567f,-0.337385f}, +{-0.356281f,0.455972f,-0.341931f},{-0.311697f,0.42108f,-0.317887f},{-0.321691f,0.43542f,-0.323424f}, +{-0.337471f,0.449105f,-0.326665f},{-0.389502f,0.308234f,-0.129102f},{-0.298617f,0.296402f,0.132041f}, +{-0.215308f,0.160136f,0.179551f},{-0.392595f,0.260873f,0.244687f},{-0.335825f,0.441696f,-0.317353f}, +{-0.355593f,0.452333f,-0.320601f},{-0.167567f,0.0814058f,0.173416f},{-0.355805f,0.274969f,0.230449f}, +{-0.163104f,0.121951f,0.0571043f},{-0.172686f,0.125867f,0.0598116f},{-0.183509f,0.13296f,0.0551494f}, +{-0.438111f,0.274165f,0.0738883f},{-0.39037f,0.280261f,0.206212f},{-0.286933f,0.295437f,0.09891f}, +{-0.189541f,0.367107f,0.0278641f},{-0.310167f,0.405755f,-0.299785f},{-0.318315f,0.4167f,-0.302633f}, +{-0.324835f,0.428173f,-0.309591f},{-0.337414f,0.432629f,-0.303334f},{-0.36994f,0.449928f,-0.321591f}, +{0.230948f,0.0913283f,0.187473f},{-0.372377f,0.296679f,0.116266f},{0.270329f,-0.11226f,-0.131115f}, +{-0.109241f,0.0828205f,0.172181f},{-0.36284f,0.28774f,-0.188508f},{-0.352358f,0.287277f,-0.265683f}, +{-0.200942f,0.153596f,0.15177f},{0.00113499f,-0.0836629f,0.330485f},{-0.317067f,0.296203f,0.0975274f}, +{-0.397412f,0.274454f,0.215987f},{-0.429121f,0.278949f,0.07693f},{-0.323337f,0.296878f,-0.0186875f}, +{0.0342465f,0.466442f,-0.171712f},{0.0799106f,0.0706215f,-0.0687952f},{-0.341639f,0.218289f,-0.0877785f}, +{-0.431449f,0.283406f,0.132639f},{-0.330237f,0.42272f,-0.296106f},{-0.355143f,0.44095f,-0.302074f}, +{-0.338597f,0.314241f,-0.0275489f},{0.0233915f,-0.0809749f,0.338073f},{-0.0720588f,0.068403f,0.18575f}, +{-0.37576f,0.276531f,0.223311f},{-0.127504f,0.0984277f,0.151005f},{-0.354069f,0.0557667f,-0.0424938f}, +{-0.371419f,0.295662f,0.151609f},{-0.415745f,0.27138f,0.0370792f},{-0.370236f,0.315739f,-0.039973f}, +{-0.16884f,0.110607f,0.128999f},{-0.0144401f,0.113746f,0.11509f},{-0.160699f,0.0890775f,0.163107f}, +{-0.307247f,0.393351f,-0.284814f},{-0.315491f,0.401235f,-0.281521f},{-0.32171f,0.412205f,-0.289958f}, +{-0.369342f,0.447079f,-0.307064f},{-0.376493f,0.441323f,-0.300775f},{-0.387566f,0.438835f,-0.315012f}, +{-0.0306775f,0.108562f,0.111765f},{-0.451262f,0.26457f,0.0748851f},{-0.294997f,0.272962f,0.232475f}, +{-0.394325f,0.290004f,0.07675f},{-0.279422f,0.286518f,0.190367f},{-0.320758f,0.295894f,0.168618f}, +{-0.342372f,0.27374f,0.234976f},{-0.0900518f,0.0837143f,0.156786f},{-0.0354748f,0.100672f,0.136857f}, +{-0.109858f,0.0888846f,0.162252f},{0.176268f,0.121777f,0.172683f},{-0.166596f,0.0948201f,0.152947f}, +{-0.0411016f,0.104318f,0.115881f},{-0.149034f,0.107829f,0.128915f},{-0.186428f,-0.134111f,-0.131449f}, +{0.240864f,0.0891547f,0.184547f},{-0.266072f,0.309135f,0.0362561f},{-0.353317f,0.293238f,0.0387062f}, +{0.015141f,0.485644f,-0.152741f},{0.325215f,-0.0565898f,-0.0726407f},{-0.335562f,0.417601f,-0.281727f}, +{-0.35414f,0.431452f,-0.287849f},{0.15632f,0.109533f,0.200926f},{-0.111652f,0.107849f,0.112041f}, +{0.171162f,0.125732f,0.164606f},{0.264869f,0.0522877f,-0.0369055f},{0.400203f,-0.28601f,0.00717664f}, +{-0.319388f,0.279194f,0.223626f},{-0.409752f,0.281322f,0.0571364f},{-0.433713f,0.267953f,0.0543648f}, +{-0.267326f,0.278653f,0.208997f},{-0.0725668f,0.100846f,0.0960612f},{-0.261191f,0.290402f,0.133031f}, +{-0.183837f,0.113469f,0.152259f},{-0.464696f,0.244044f,0.0771615f},{0.260529f,0.0686216f,0.202148f}, +{-0.0344844f,0.0789814f,-0.0375743f},{-0.450934f,-0.0369441f,0.0577474f},{-0.196942f,0.340021f,-0.0344555f}, +{0.0789781f,0.0928973f,-0.0507379f},{0.214067f,0.0979454f,0.190264f},{0.205669f,0.10882f,0.181866f}, +{0.00593869f,0.48718f,-0.148947f},{-0.279383f,0.27774f,0.218797f},{-0.309968f,0.385808f,-0.260873f}, +{-0.320372f,0.399363f,-0.266821f},{-0.353239f,0.425633f,-0.278313f},{-0.371496f,0.433343f,-0.284197f}, +{-0.38938f,0.434545f,-0.300672f},{-0.380595f,0.294466f,0.150002f},{-0.14542f,-0.415112f,-0.13224f}, +{-0.0912929f,0.076422f,0.171576f},{0.155104f,0.127051f,0.16802f},{-0.335671f,0.296556f,0.0937076f}, +{-0.351838f,0.295617f,0.0773287f},{-0.243693f,0.319585f,0.0538697f},{0.137073f,0.132883f,0.152182f}, +{-0.47013f,0.156355f,0.0566799f},{-0.462323f,-0.0917205f,0.0598438f},{0.11935f,0.132227f,0.153474f}, +{-0.440922f,-0.0770072f,0.00667504f},{-0.146082f,0.0971866f,-0.0368348f},{-0.355728f,0.131931f,-0.0438699f}, +{-0.479653f,0.0475676f,0.192245f},{-0.35839f,0.294383f,0.0687695f},{-0.146603f,0.0898878f,0.168136f}, +{0.0804765f,0.132845f,0.132523f},{-0.33106f,0.404347f,-0.259246f},{-0.340597f,0.41281f,-0.265162f}, +{-0.355246f,0.418919f,-0.262332f},{-0.386293f,0.430526f,-0.2861f},{-0.461538f,-0.072152f,0.0592135f}, +{-0.288309f,0.0437028f,-0.0627182f},{-0.343336f,0.296164f,0.08347f},{0.0623806f,0.130375f,0.135288f}, +{0.0797948f,-0.0629498f,0.318909f},{-0.230877f,0.263702f,0.128967f},{-0.365876f,0.310183f,-0.182168f}, +{-0.464966f,-0.0927365f,0.0772773f},{-0.47721f,0.0314074f,0.22461f},{-0.42026f,0.00966527f,0.0123597f}, +{-0.406125f,0.187711f,-0.0143918f},{-0.177888f,0.0991286f,0.151969f},{0.027237f,0.126073f,0.112279f}, +{0.0190058f,0.122922f,0.118691f},{0.00530206f,0.119572f,0.112807f},{-0.3051f,0.373756f,-0.243593f}, +{-0.313459f,0.384123f,-0.244114f},{-0.322089f,0.394482f,-0.249677f},{-0.374557f,0.421697f,-0.264667f}, +{-0.378088f,0.427452f,-0.273914f},{0.219044f,0.102254f,0.177596f},{-0.387206f,0.269207f,-0.0319604f}, +{-0.48074f,0.0297547f,0.0799074f},{-0.393991f,0.286853f,0.0585898f},{0.0944375f,0.0846982f,0.234976f}, +{0.0764445f,0.126478f,0.16584f},{-0.388441f,-0.110948f,-0.0707823f},{-0.364101f,0.229986f,-0.0788528f}, +{-0.480361f,0.227729f,0.152683f},{-0.388557f,0.290627f,-0.0904215f},{0.308257f,0.0406997f,0.00110609f}, +{-0.400929f,-0.07048f,-0.0452718f},{-0.408286f,-0.0569885f,-0.0353236f},{-0.411064f,0.00485518f,0.00178774f}, +{-0.375721f,0.00127326f,-0.0385518f},{-0.385296f,0.295219f,0.110813f},{-0.0561622f,0.103682f,0.0916241f}, +{-0.467249f,0.209781f,0.0951224f},{-0.335041f,0.401756f,-0.24441f},{-0.355632f,0.412006f,-0.246732f}, +{-0.391798f,0.414746f,-0.265683f},{0.470805f,-0.247876f,0.0585576f},{-0.238587f,0.32462f,0.00147907f}, +{-0.109151f,0.110074f,0.0755603f},{-0.360043f,0.257895f,0.258191f},{0.101537f,0.0912768f,0.225864f}, +{-0.167053f,0.116504f,0.115141f},{0.287023f,0.0577538f,-0.0010739f},{0.284856f,0.0531816f,-0.0135301f}, +{-0.468876f,0.174335f,0.0951802f},{-0.399045f,-0.0575158f,-0.0449503f},{-0.400093f,0.378727f,-0.226436f}, +{-0.374956f,0.293463f,0.0760297f},{-0.425462f,0.277631f,0.0640816f},{-0.147458f,0.119385f,0.0581653f}, +{-0.307588f,0.374297f,-0.229079f},{-0.317787f,0.383943f,-0.225871f},{-0.325491f,0.393126f,-0.235574f}, +{-0.350609f,0.407144f,-0.240352f},{-0.372808f,0.413678f,-0.247793f},{0.47703f,-0.246468f,0.0764284f}, +{0.457545f,-0.244719f,0.054577f},{-0.392794f,0.206263f,-0.0201601f},{-0.318044f,0.274557f,0.233832f}, +{-0.19529f,0.141005f,0.15885f},{-0.418903f,-0.00291306f,0.195994f},{-0.110251f,0.109939f,0.0938877f}, +{0.336957f,0.0148291f,0.024996f},{0.329703f,0.0246037f,0.0209897f},{0.321279f,0.0350921f,0.0179866f}, +{0.312546f,0.0445259f,0.0194785f},{0.300553f,0.0499855f,0.00521528f},{-0.0685219f,-0.132896f,-0.203331f}, +{-0.244992f,0.32716f,0.0155172f},{-0.446407f,-0.0366998f,0.153982f},{-0.306334f,0.295019f,0.0873348f}, +{-0.336578f,0.396997f,-0.226526f},{-0.354583f,0.403067f,-0.225382f},{0.457339f,-0.237819f,0.0758818f}, +{0.439282f,-0.242822f,0.055696f},{-0.147137f,0.104035f,0.139082f},{-0.0884827f,0.102627f,0.0958747f}, +{-0.0929713f,0.0635414f,0.192791f},{0.201521f,0.0841066f,0.217446f},{-0.199193f,0.357339f,0.0339603f}, +{0.302103f,0.0538632f,0.0212405f},{0.290585f,0.0616122f,0.0160509f},{0.26568f,0.0744992f,0.000971048f}, +{-0.43916f,-0.103328f,-0.00632775f},{-0.442478f,-0.111842f,0.00203854f},{-0.497402f,0.0911225f,0.166406f}, +{-0.402846f,0.203852f,-0.0139802f},{-0.457789f,-0.0685766f,0.15732f},{0.264181f,0.0370149f,-0.0539018f}, +{-0.352989f,0.266654f,-0.266223f},{-0.349895f,0.305231f,-0.265753f},{-0.392872f,0.00293882f,-0.0182309f}, +{-0.483062f,0.0447574f,0.171904f},{-0.24251f,-0.278364f,0.244037f},{-0.461667f,-0.114781f,0.0613742f}, +{-0.0333334f,0.476094f,-0.111418f},{-0.308463f,0.37058f,-0.211029f},{-0.317376f,0.379827f,-0.208926f}, +{-0.372178f,0.404051f,-0.227247f},{-0.379489f,0.405768f,-0.237549f},{-0.299357f,0.269027f,-0.00139544f}, +{0.477088f,-0.244282f,0.0955082f},{0.439153f,-0.236635f,0.0765249f},{0.421128f,-0.237825f,0.0749429f}, +{0.419314f,-0.242944f,0.0557153f},{-0.460374f,0.110331f,0.0227003f},{-0.103575f,0.065895f,0.19373f}, +{-0.261287f,0.298331f,0.0754188f},{-0.243089f,0.300878f,0.0763898f},{-0.236362f,0.293277f,0.0843896f}, +{-0.454072f,0.27439f,0.134825f},{0.354757f,-0.00544677f,0.0437092f},{0.339497f,0.0163017f,0.0414006f}, +{0.332623f,0.0255554f,0.0390341f},{0.32326f,0.0375487f,0.037285f},{0.283171f,0.0696569f,0.0240378f}, +{0.269686f,0.0779203f,0.0181924f},{0.245751f,0.0899457f,0.00502237f},{-0.405611f,-0.0968008f,-0.0510208f}, +{-0.438028f,-0.0347256f,0.172837f},{-0.383103f,0.00792259f,-0.0261085f},{-0.338809f,0.360342f,-0.318999f}, +{-0.489891f,0.0399794f,0.133591f},{0.0094627f,-0.081798f,0.332973f},{-0.329896f,0.387981f,-0.203415f}, +{-0.340217f,0.394778f,-0.20973f},{-0.355574f,0.314614f,-0.0240507f},{-0.377991f,0.305109f,-0.0350792f}, +{0.460464f,-0.236353f,0.0941063f},{-0.275962f,0.296003f,0.0702357f},{0.100617f,0.108286f,0.205749f}, +{-0.0984117f,0.10646f,0.0950516f},{0.356628f,-0.00610268f,0.058384f},{0.349535f,0.00374265f,0.0549243f}, +{0.339934f,0.017022f,0.0585833f},{0.315807f,0.0460242f,0.0391756f},{0.306816f,0.0548278f,0.0411177f}, +{0.294232f,0.0638886f,0.0342047f},{0.250484f,0.0926272f,0.0191312f},{0.233706f,0.102003f,0.0171892f}, +{-0.147465f,0.036166f,-0.0884216f},{-0.396781f,0.341796f,-0.169486f},{-0.445191f,0.0280056f,0.0249381f}, +{-0.455976f,-0.0343011f,0.117044f},{-0.399013f,0.00875856f,-0.00724091f},{-0.127176f,0.0845696f,-0.0427317f}, +{-0.198408f,0.156471f,0.135269f},{0.115016f,-0.0638114f,0.317102f},{-0.304714f,0.35912f,-0.186592f}, +{-0.31072f,0.367512f,-0.188766f},{-0.320218f,0.377171f,-0.190232f},{-0.355548f,0.397447f,-0.206238f}, +{-0.373445f,0.395544f,-0.207402f},{0.477088f,-0.245683f,0.114575f},{0.451506f,-0.234211f,0.100293f}, +{0.441243f,-0.234005f,0.0964471f},{0.431976f,-0.235073f,0.0881644f},{0.412736f,-0.234275f,0.0805955f}, +{0.0239703f,0.103148f,0.190881f},{0.332636f,0.0271889f,0.0590271f},{0.32333f,0.0394714f,0.0564226f}, +{0.284232f,0.0735796f,0.0411948f},{0.27004f,0.0828591f,0.03506f},{0.212665f,0.113f,0.0187711f}, +{-0.478534f,0.0301598f,0.255979f},{-0.479448f,0.196611f,0.117495f},{-0.356821f,0.305463f,-0.227144f}, +{-0.460966f,0.191904f,0.0822739f},{-0.367432f,0.287933f,-0.149931f},{-0.45667f,-0.0321533f,0.0962156f}, +{0.248201f,0.0532009f,-0.0508022f},{-0.0157262f,0.109566f,0.133494f},{-0.284592f,0.29538f,0.113746f}, +{-0.0297643f,0.486023f,-0.0808334f},{-0.333722f,0.385505f,-0.187061f},{-0.342976f,0.390939f,-0.191402f}, +{-0.355657f,0.392547f,-0.189936f},{0.458824f,-0.236513f,0.115424f},{0.421507f,-0.235311f,0.0960162f}, +{-0.25921f,0.294865f,0.0855085f},{0.355419f,-0.00317029f,0.0764863f},{0.349342f,0.00503524f,0.0742613f}, +{0.307035f,0.0570336f,0.0588727f},{0.295138f,0.0672519f,0.0544034f},{0.262233f,0.0901515f,0.0422559f}, +{0.250394f,0.0972316f,0.0371307f},{0.228137f,0.107701f,0.0250796f},{0.19574f,0.119154f,0.0164239f}, +{-0.312656f,0.361332f,-0.372072f},{-0.481377f,0.209865f,0.117417f},{-0.443989f,-0.0430726f,0.168753f}, +{-0.459654f,-0.0406611f,0.112292f},{-0.44478f,0.278126f,0.137809f},{-0.390061f,0.280788f,0.0173564f}, +{-0.128552f,0.115482f,0.0745571f},{-0.371072f,0.297926f,-0.00272658f},{-0.304694f,0.354104f,-0.169692f}, +{-0.314495f,0.36494f,-0.168612f},{-0.32263f,0.3724f,-0.170059f},{-0.369805f,0.39173f,-0.192058f}, +{-0.394872f,0.379126f,-0.208585f},{0.474046f,-0.249818f,0.134298f},{0.453641f,-0.23443f,0.115418f}, +{0.440137f,-0.234076f,0.110331f},{0.420954f,-0.237388f,0.115302f},{0.413598f,-0.233452f,0.100183f}, +{0.25294f,-0.111566f,-0.137037f},{0.0383942f,0.104601f,0.196084f},{0.115466f,0.108762f,0.204881f}, +{0.34041f,0.0172792f,0.0793672f},{0.333317f,0.0269252f,0.0777274f},{0.324096f,0.0392913f,0.075078f}, +{0.286187f,0.0757339f,0.0602232f},{0.274708f,0.0836243f,0.0544677f},{0.264985f,0.0908846f,0.0569564f}, +{0.240156f,0.103527f,0.038539f},{0.229713f,0.109469f,0.0399409f},{0.210678f,0.118607f,0.039465f}, +{0.178737f,0.124163f,0.0155043f},{-0.478348f,0.214565f,0.168824f},{-0.0894087f,-0.0338703f,-0.131456f}, +{0.0999292f,-0.0885438f,-0.188676f},{-0.339047f,0.283894f,-0.305527f},{0.0879553f,-0.0823446f,0.351108f}, +{0.225797f,0.0867239f,0.198463f},{0.122147f,0.12426f,0.179261f},{-0.335253f,0.38092f,-0.170773f}, +{-0.348378f,0.389055f,-0.179621f},{-0.377785f,0.384843f,-0.184393f},{-0.389978f,0.37728f,-0.190778f}, +{0.459808f,-0.240565f,0.131944f},{0.439822f,-0.235362f,0.119745f},{0.411418f,-0.231735f,0.108935f}, +{0.407669f,-0.22553f,0.113996f},{0.305698f,0.0594129f,0.0766921f},{0.287164f,0.0762033f,0.0766535f}, +{0.249706f,0.100743f,0.0565255f},{0.196254f,0.123835f,0.0345841f},{0.186711f,0.125617f,0.0280377f}, +{0.172474f,0.127231f,0.0207132f},{0.154731f,0.12907f,0.017665f},{-0.374281f,-0.0746278f,-0.0723256f}, +{-0.498122f,0.0952895f,0.121231f},{-0.163258f,0.379737f,-0.0499984f},{0.162281f,0.123822f,0.175261f}, +{-0.44386f,-0.127134f,0.185685f},{-0.316205f,0.359931f,-0.151564f},{-0.354854f,0.385492f,-0.168085f}, +{-0.374268f,0.380804f,-0.170631f},{0.440259f,-0.239105f,0.134111f},{-0.0803994f,0.0880743f,0.144413f}, +{0.339741f,0.0176715f,0.098492f},{0.332796f,0.0273818f,0.0974567f},{0.322424f,0.0410083f,0.0941771f}, +{0.316019f,0.0491302f,0.0851934f},{0.274972f,0.0856821f,0.0744221f},{0.266425f,0.0920099f,0.0758175f}, +{0.231353f,0.11098f,0.0588534f},{0.212369f,0.120221f,0.0584097f},{0.188364f,0.127661f,0.0408733f}, +{0.17457f,0.130658f,0.0373557f},{0.136275f,0.130671f,0.0183531f},{0.11834f,0.0629433f,-0.0799717f}, +{-0.380486f,0.225883f,-0.0275361f},{-0.479396f,0.130022f,0.0632584f},{-0.477133f,0.0945629f,0.0505965f}, +{-0.367663f,0.295219f,0.084216f},{-0.0727276f,0.458262f,-0.0540883f},{-0.085576f,0.449574f,-0.0604868f}, +{-0.338063f,0.376001f,-0.154259f},{0.456767f,-0.248269f,0.14977f},{0.419372f,-0.244224f,0.134716f}, +{0.407443f,-0.240468f,0.132735f},{0.306257f,0.058847f,0.095251f},{0.287801f,0.0757339f,0.0952188f}, +{0.252381f,0.101f,0.075168f},{0.200768f,0.124543f,0.0515546f},{0.191335f,0.128581f,0.0587441f}, +{0.156165f,0.134021f,0.0397479f},{0.136558f,0.135256f,0.0385776f},{0.118996f,0.130954f,0.0188997f}, +{0.100482f,0.129096f,0.0171442f},{-0.0688242f,-0.0185718f,-0.127745f},{-0.458008f,-0.0671618f,0.0515482f}, +{0.420054f,-0.285148f,0.00508667f},{-0.350346f,0.442442f,-0.351911f},{-0.452863f,-0.0364875f,0.134729f}, +{-0.095042f,0.443433f,-0.0547635f},{-0.0495f,-0.189582f,0.356001f},{-0.303575f,0.339359f,-0.132311f}, +{-0.31009f,0.346568f,-0.131603f},{-0.320289f,0.356259f,-0.132285f},{-0.330321f,0.368245f,-0.145224f}, +{-0.354294f,0.378142f,-0.14941f},{-0.37131f,0.375396f,-0.154053f},{0.456825f,-0.25558f,0.159062f}, +{0.438388f,-0.247921f,0.153198f},{0.423404f,-0.248301f,0.148227f},{-0.40898f,0.277001f,0.0416578f}, +{-0.045976f,0.106029f,0.0999646f},{-0.103595f,0.104698f,0.116903f},{0.34005f,0.0165011f,0.116993f}, +{0.333021f,0.0263915f,0.116016f},{0.322456f,0.0400566f,0.114016f},{0.275718f,0.0854314f,0.0930131f}, +{0.266522f,0.0921321f,0.096775f},{0.242156f,0.106968f,0.0805376f},{0.231269f,0.112427f,0.0765956f}, +{0.213617f,0.120929f,0.0772001f},{0.17329f,0.133198f,0.055188f},{0.116591f,0.13552f,0.0403331f}, +{0.0802193f,0.127379f,0.0192598f},{-0.493428f,0.0950774f,0.0955275f},{0.2484f,0.0440115f,-0.0601652f}, +{-0.370294f,0.253812f,-0.169512f},{-0.111022f,0.432783f,-0.0540754f},{-0.341767f,0.371763f,-0.140233f}, +{0.00490979f,0.107778f,0.165281f},{0.354532f,-0.00598052f,0.126903f},{0.306489f,0.0576638f,0.11372f}, +{0.251905f,0.101791f,0.0923829f},{0.155001f,0.136703f,0.0576573f},{0.0638982f,0.126054f,0.0237292f}, +{-0.471158f,0.227446f,0.0947944f},{0.210871f,-0.0339346f,-0.115122f},{-0.364416f,0.301225f,-0.175711f}, +{-0.415199f,0.171673f,-0.00596764f},{-0.0313398f,0.106003f,0.124022f},{-0.373933f,-0.114942f,0.265239f}, +{-0.30726f,0.337359f,-0.118234f},{-0.354751f,0.372734f,-0.135635f},{-0.365773f,0.371879f,-0.139565f}, +{-0.377734f,0.368914f,-0.148497f},{-0.249732f,0.278743f,0.182431f},{0.414472f,-0.255606f,0.157571f}, +{-0.366409f,0.244616f,0.275818f},{0.0588116f,0.123977f,0.0193756f},{-0.0135591f,-0.0344555f,-0.136722f}, +{-0.334996f,-0.0174078f,-0.070943f},{-0.331845f,0.0327707f,-0.0486544f},{-0.296154f,0.189544f,-0.0344876f}, +{-0.311447f,0.0182952f,-0.068628f},{-0.411861f,0.00317034f,0.28592f},{-0.336385f,0.0394071f,-0.0446609f}, +{-0.373824f,0.149577f,-0.0365519f},{-0.206145f,-0.0328928f,-0.118819f},{-0.164853f,0.36213f,-0.0554644f}, +{-0.0502331f,0.364876f,-0.104202f},{0.0241696f,0.436738f,-0.172361f},{-0.0166265f,0.37692f,-0.118337f}, +{-0.0686377f,0.380663f,-0.106048f},{-0.29883f,-0.487836f,0.113443f},{-0.294437f,-0.484492f,0.132182f}, +{-0.308141f,-0.482139f,0.178213f},{-0.315858f,-0.470853f,0.190759f},{-0.322353f,-0.472705f,0.211215f}, +{-0.321221f,-0.473354f,0.227131f},{-0.318231f,-0.473683f,0.247111f},{-0.312199f,-0.470326f,0.264217f}, +{-0.261795f,-0.470602f,0.309167f},{-0.445468f,-0.10873f,0.186689f},{-0.0117649f,0.455272f,-0.144748f}, +{-0.143323f,0.321115f,-0.0665766f},{-0.0528568f,-0.072287f,-0.145269f},{-0.388891f,0.147744f,-0.0298704f}, +{-0.309575f,0.112421f,-0.0459663f},{-0.2584f,0.111939f,-0.0322755f},{-0.130366f,0.321269f,-0.0709109f}, +{-0.0524003f,-0.48237f,0.339783f},{-0.446195f,0.0441143f,0.316884f},{0.0216617f,-0.0345262f,-0.137951f}, +{-0.352847f,-0.483708f,0.0576445f},{-0.356075f,-0.469773f,0.0596958f},{-0.350429f,-0.475136f,0.0710717f}, +{-0.341156f,-0.469335f,0.0783448f},{-0.33214f,-0.475084f,0.0933861f},{-0.323581f,-0.473946f,0.103637f}, +{-0.313916f,-0.47374f,0.112247f},{-0.304296f,-0.473528f,0.120806f},{-0.295865f,-0.474049f,0.132092f}, +{-0.309215f,-0.47648f,0.173525f},{-0.388344f,-0.183197f,0.240886f},{-0.309209f,0.0606862f,-0.0464551f}, +{-0.144937f,-0.469689f,0.286795f},{-0.1356f,-0.476428f,0.291116f},{-0.127562f,-0.46976f,0.302588f}, +{-0.0715572f,-0.470249f,0.35494f},{-0.409167f,-0.16748f,0.227555f},{-0.387206f,-0.136735f,0.256403f}, +{-0.392647f,-0.167596f,0.24468f},{-0.37884f,0.348703f,-0.110453f},{-0.3565f,-0.472139f,0.0207839f}, +{-0.357188f,-0.468531f,0.0413749f},{-0.287049f,-0.46821f,0.139822f},{-0.307768f,-0.464139f,0.169345f}, +{-0.312887f,-0.453194f,0.195717f},{-0.313858f,-0.451362f,0.207813f},{-0.316077f,-0.453792f,0.227613f}, +{-0.314012f,-0.451928f,0.247651f},{-0.309286f,-0.455181f,0.261207f},{-0.304244f,-0.449902f,0.266995f}, +{-0.295235f,-0.449838f,0.283444f},{-0.286007f,-0.449561f,0.292402f},{-0.277171f,-0.450313f,0.299952f}, +{-0.374519f,-0.186734f,0.249908f},{-0.331883f,-0.183576f,0.278467f},{-0.462709f,0.150928f,0.280904f}, +{-0.433076f,0.152747f,0.323128f},{-0.104116f,-0.464307f,0.334298f},{-0.0812482f,-0.461503f,0.352503f}, +{-0.0733385f,-0.451188f,0.354908f},{-0.336172f,-0.166644f,0.281759f},{-0.43777f,-0.108312f,0.197183f}, +{-0.147838f,-0.133494f,-0.137751f},{-0.0286004f,-0.225665f,0.353288f},{-0.408935f,0.226353f,0.280621f}, +{-0.429083f,0.225813f,0.264442f},{-0.370602f,-0.166496f,0.26077f},{-0.298154f,-0.314164f,0.152426f}, +{-0.0732806f,0.0510787f,-0.0722163f},{-0.334481f,-0.451728f,0.0750458f},{-0.328919f,-0.46104f,0.0899135f}, +{-0.3187f,-0.451317f,0.0951738f},{-0.312122f,-0.457619f,0.107006f},{-0.298617f,-0.453034f,0.113488f}, +{-0.291473f,-0.460133f,0.125867f},{-0.279428f,-0.454069f,0.132092f},{-0.29737f,-0.452873f,0.171982f}, +{-0.45276f,0.174856f,0.284865f},{-0.44431f,0.173737f,0.29774f},{-0.427289f,0.16712f,0.318903f}, +{-0.406575f,-0.18373f,0.22281f},{-0.366802f,-0.217016f,0.238655f},{-0.141523f,-0.451227f,0.283952f}, +{-0.127375f,-0.449658f,0.302408f},{-0.113157f,-0.44805f,0.322588f},{-0.103524f,-0.449651f,0.334819f}, +{-0.0899618f,-0.448269f,0.342323f},{0.271126f,-0.130645f,-0.13741f},{-0.0707276f,-0.203961f,0.30428f}, +{0.00260761f,-0.072017f,0.302408f},{-0.455802f,-0.0919906f,0.169216f},{-0.44559f,-0.0912961f,0.186283f}, +{-0.0584965f,-0.210964f,0.323957f},{0.173792f,0.00269444f,-0.113758f},{-0.348648f,-0.455509f,0.0179094f}, +{-0.349555f,-0.452879f,0.0386161f},{-0.350937f,-0.451349f,0.0576959f},{-0.284753f,-0.445658f,0.118749f}, +{-0.308392f,-0.440108f,0.208173f},{-0.310733f,-0.437607f,0.229247f},{-0.309234f,-0.439041f,0.247343f}, +{-0.298547f,-0.434102f,0.265612f},{-0.291383f,-0.432648f,0.279033f},{-0.282592f,-0.432314f,0.288492f}, +{-0.272618f,-0.43661f,0.296852f},{-0.458715f,0.178895f,0.272531f},{-0.0676602f,-0.18647f,0.32253f}, +{-0.437976f,-0.0909038f,0.196875f},{-0.136584f,-0.445799f,0.288981f},{-0.0789525f,-0.443819f,0.349481f}, +{-0.428742f,-0.091116f,0.208373f},{-0.0783094f,-0.204212f,0.290229f},{-0.0325874f,-0.208662f,0.359738f}, +{-0.476387f,0.095849f,0.29264f},{-0.464033f,0.192155f,0.248481f},{-0.355021f,-0.18438f,0.264718f}, +{-0.421417f,-0.0858814f,0.216456f},{0.211874f,-0.0764349f,-0.137063f},{-0.419778f,-0.133899f,0.216379f}, +{-0.345446f,-0.44749f,0.0345069f},{-0.342796f,-0.444474f,0.0628726f},{-0.323922f,-0.444275f,0.0805248f}, +{-0.304701f,-0.443548f,0.102254f},{-0.445847f,0.190984f,0.282139f},{-0.304759f,-0.432591f,0.208778f}, +{-0.471949f,0.22553f,0.189505f},{-0.305209f,-0.42996f,0.245233f},{-0.472509f,0.133822f,0.267766f}, +{-0.0580335f,-0.23016f,0.305907f},{-0.412189f,0.111244f,-0.0244944f},{-0.350809f,-0.201775f,0.259194f}, +{-0.0508891f,-0.209247f,0.341642f},{-0.132706f,-0.43178f,0.286441f},{-0.123234f,-0.432751f,0.30037f}, +{-0.114218f,-0.430378f,0.308453f},{-0.106373f,-0.431169f,0.321893f},{-0.0908621f,-0.431697f,0.335636f}, +{-0.0644835f,-0.435356f,0.353127f},{-0.380763f,-0.0849297f,0.253034f},{-0.449288f,0.15413f,0.303514f}, +{-0.427443f,0.0979583f,0.336549f},{-0.438742f,-0.157519f,0.176856f},{-0.482116f,0.0947301f,0.28201f}, +{-0.358661f,-0.0896756f,0.271914f},{-0.463943f,0.11282f,0.299669f},{-0.446722f,0.114215f,0.321578f}, +{-0.450188f,0.0614321f,0.31981f},{-0.335607f,-0.433182f,0.0214527f},{-0.335639f,-0.43232f,0.0373557f}, +{-0.335767f,-0.432803f,0.0577666f},{-0.328237f,-0.435549f,0.0709881f},{-0.317929f,-0.430771f,0.0761326f}, +{-0.310128f,-0.435304f,0.0892769f},{-0.296978f,-0.432571f,0.0957654f},{-0.276869f,-0.433388f,0.110524f}, +{-0.261345f,-0.428771f,0.115553f},{-0.452278f,-0.129121f,0.171351f},{-0.387547f,0.358644f,-0.150857f}, +{-0.446857f,-0.147513f,0.169577f},{-0.295409f,-0.417614f,0.246751f},{-0.29301f,-0.420887f,0.262281f}, +{-0.283827f,-0.413819f,0.267406f},{-0.276895f,-0.417755f,0.282338f},{-0.426626f,-0.151667f,0.205633f}, +{-0.419507f,-0.151211f,0.216675f},{-0.464561f,0.244564f,0.187544f},{-0.0957429f,-0.426546f,0.329423f}, +{-0.427842f,-0.109006f,0.208855f},{-0.0315842f,-0.131005f,0.356342f},{-0.221398f,0.341102f,0.0392399f}, +{-0.436317f,0.0953217f,0.328613f},{-0.44842f,0.0965886f,0.319385f},{-0.0449986f,-0.204238f,0.353802f}, +{-0.409996f,-0.111668f,0.228552f},{-0.448921f,0.0793415f,0.320652f},{-0.413983f,-0.189016f,0.209865f}, +{-0.418858f,-0.170207f,0.213517f},{-0.393039f,-0.149989f,0.249072f},{-0.472271f,0.114196f,0.286049f}, +{-0.0770361f,-0.184045f,0.303456f},{-0.0587087f,-0.111366f,0.307225f},{-0.466773f,0.13143f,0.28448f}, +{-0.304373f,-0.426906f,0.08338f},{-0.283203f,-0.42724f,0.0985177f},{-0.470818f,0.0794573f,0.303206f}, +{0.0180348f,-0.0658371f,0.300537f},{-0.472207f,0.0373171f,0.287296f},{-0.424388f,-0.0574644f,0.203434f}, +{-0.427327f,-0.186952f,0.188206f},{-0.355272f,0.247703f,-0.205633f},{-0.460972f,0.221845f,0.219196f}, +{-0.220324f,0.282094f,-0.0189769f},{-0.424035f,-0.167602f,0.206501f},{-0.124147f,-0.416437f,0.282898f}, +{-0.109241f,-0.416295f,0.30163f},{-0.100746f,-0.418707f,0.315951f},{-0.0343944f,-0.111752f,0.340491f}, +{-0.428613f,-0.0390084f,0.190444f},{-0.435346f,-0.151584f,0.188849f},{-0.433398f,0.177049f,0.306505f}, +{-0.292097f,0.0731423f,-0.0469374f},{-0.477171f,0.117597f,0.271355f},{-0.0671586f,-0.220334f,0.299341f}, +{-0.435584f,0.21434f,0.271271f},{-0.447121f,0.208231f,0.264737f},{-0.454374f,0.194328f,0.267734f}, +{-0.428761f,0.208746f,0.282788f},{-0.0505161f,-0.112524f,0.320318f},{-0.319511f,-0.419511f,0.057175f}, +{-0.30845f,-0.41908f,0.0689946f},{-0.295852f,-0.417035f,0.0745828f},{-0.280985f,-0.413292f,0.0778046f}, +{-0.276342f,-0.419292f,0.0914569f},{-0.260008f,-0.414514f,0.0950452f},{-0.254246f,-0.419215f,0.105797f}, +{-0.240407f,-0.41562f,0.111405f},{-0.461442f,-0.128395f,0.151854f},{-0.430112f,0.0803511f,0.327166f}, +{-0.05822f,-0.191975f,0.341243f},{-0.205334f,0.361525f,0.0194656f},{-0.462625f,0.20989f,0.229748f}, +{-0.464291f,0.225054f,0.206225f},{-0.280007f,-0.405132f,0.244384f},{-0.278727f,-0.404682f,0.250751f}, +{-0.273512f,-0.404321f,0.2605f},{-0.410048f,0.0592521f,0.327919f},{-0.420376f,-0.116427f,0.216385f}, +{-0.0372239f,-0.191145f,0.365609f},{-0.0676474f,-0.147635f,0.320208f},{-0.334912f,-0.035658f,-0.0790007f}, +{-0.11571f,-0.410964f,0.289772f},{-0.0967975f,-0.410778f,0.307231f},{-0.459918f,0.0791679f,0.313257f}, +{-0.462799f,0.173641f,0.266088f},{-0.37394f,-0.132311f,0.265773f},{-0.45458f,0.131385f,0.306106f}, +{-0.410099f,-0.133423f,0.229388f},{-0.458336f,0.0964535f,0.311083f},{-0.432144f,-0.168991f,0.189698f}, +{-0.434587f,0.197093f,0.28893f},{-0.384557f,0.377666f,-0.178637f},{-0.4486f,-0.0571364f,0.170059f}, +{-0.300611f,-0.412051f,0.061027f},{-0.268567f,-0.411479f,0.0835214f},{-0.245236f,-0.411138f,0.101141f}, +{-0.430729f,0.114665f,0.337526f},{-0.18547f,0.381428f,0.0018006f},{-0.446433f,0.225388f,0.245452f}, +{-0.259577f,-0.398058f,0.244584f},{-0.0629658f,-0.182476f,0.334748f},{-0.455841f,-0.10945f,0.169776f}, +{-0.354686f,-0.167313f,0.270351f},{0.021816f,0.453555f,-0.172824f},{-0.435938f,0.256969f,0.215987f}, +{-0.0266197f,-0.19027f,0.371377f},{-0.108488f,-0.400058f,0.282358f},{-0.0891708f,-0.400842f,0.300203f}, +{-0.0369474f,-0.167924f,0.367345f},{-0.480599f,0.0447832f,0.280563f},{-0.428247f,-0.131031f,0.205395f}, +{-0.471255f,0.0615029f,0.30219f},{-0.467905f,0.0962027f,0.302376f},{-0.0124852f,-0.112241f,0.356728f}, +{-0.0659497f,-0.201543f,0.315546f},{-0.43069f,0.0579081f,0.325719f},{-0.391721f,0.361043f,-0.168503f}, +{-0.0402592f,-0.108016f,0.328118f},{-0.0692936f,-0.165249f,0.322337f},{-0.415758f,0.231973f,0.269123f}, +{-0.0491078f,-0.168175f,0.356812f},{-0.411855f,-0.150805f,0.227131f},{-0.276252f,-0.403736f,0.0591621f}, +{-0.263139f,-0.393949f,0.0586862f},{-0.256587f,-0.398482f,0.0750908f},{-0.238754f,-0.400064f,0.0943507f}, +{-0.221173f,-0.398187f,0.111971f},{-0.0518022f,-0.0313495f,-0.13678f},{-0.461873f,0.126678f,0.295373f}, +{-0.0625993f,-0.164754f,0.334028f},{-0.469712f,0.248879f,0.172548f},{-0.316585f,0.30408f,-0.337539f}, +{-0.0603357f,-0.142793f,0.331031f},{-0.453995f,0.254313f,0.193306f},{-0.450413f,0.248108f,0.208013f}, +{-0.407579f,-0.202386f,0.205923f},{-0.0902254f,-0.390258f,0.286531f},{-0.454368f,0.227427f,0.228777f}, +{-0.458053f,0.230726f,0.216012f},{-0.0643806f,-0.130118f,0.316434f},{-0.0579306f,-0.16876f,0.342966f}, +{-0.386557f,0.300614f,-0.0578695f},{-0.39601f,0.0348349f,0.328883f},{-0.413051f,0.214733f,0.289624f}, +{-0.0550433f,-0.147391f,0.341378f},{-0.396743f,0.361866f,-0.187402f},{-0.0361757f,-0.0912382f,0.305373f}, +{-0.246677f,-0.390277f,0.0792386f},{-0.226819f,-0.3942f,0.100441f},{-0.26366f,-0.134787f,-0.126446f}, +{-0.436922f,-0.132131f,0.193434f},{-0.0280152f,-0.167911f,0.37211f},{-0.277692f,0.11217f,-0.0390727f}, +{-0.0964053f,-0.386515f,0.274377f},{-0.0776663f,-0.386161f,0.292003f},{-0.36648f,0.256198f,-0.207614f}, +{-0.396286f,-0.190219f,0.229266f},{-0.454702f,0.211504f,0.249484f},{-0.0332691f,-0.0973731f,0.320819f}, +{-0.241224f,-0.374187f,0.0783962f},{-0.236639f,-0.385737f,0.087277f},{-0.224665f,-0.375756f,0.0965114f}, +{-0.214909f,-0.377351f,0.114614f},{-0.360847f,-0.149674f,0.271348f},{-0.226536f,-0.378438f,0.208411f}, +{-0.211958f,-0.374168f,0.234558f},{-0.0197968f,-0.0909424f,0.325385f},{-0.0126588f,-0.0958169f,0.341037f}, +{-0.485203f,0.0782483f,0.283611f},{-0.0904955f,-0.378817f,0.263721f},{-0.0708563f,-0.373782f,0.263747f}, +{-0.435166f,0.231472f,0.252204f},{-0.44177f,-0.0567249f,0.181435f},{-0.433931f,-0.0563455f,0.191724f}, +{-0.478322f,0.0742806f,0.295245f},{-0.445044f,0.130986f,0.32208f},{-0.0440468f,-0.116491f,0.332086f}, +{-0.358898f,0.270249f,-0.244905f},{-0.415553f,-0.0548728f,0.211466f},{-0.195482f,0.374914f,0.00135046f}, +{-0.398286f,0.398322f,-0.265959f},{-0.422491f,-0.182303f,0.20245f},{-0.0459182f,-0.146323f,0.352953f}, +{-0.25505f,-0.376264f,0.0585705f},{-0.221964f,-0.359371f,0.208373f},{-0.216093f,-0.359487f,0.222231f}, +{-0.21026f,-0.356882f,0.230481f},{-0.457905f,0.155063f,0.288119f},{-0.427893f,0.242931f,0.244635f}, +{-0.0752998f,-0.37139f,0.253111f},{-0.360834f,-0.114736f,0.274769f},{-0.0265812f,-0.115848f,0.350857f}, +{-0.460439f,0.205389f,0.24441f},{-0.416607f,-0.0167583f,0.196129f},{-0.350076f,-0.14824f,0.277245f}, +{-0.411231f,-0.0384168f,0.209408f},{-0.315588f,0.00127969f,-0.0730394f},{0.172898f,-0.129436f,-0.189807f}, +{-0.0764638f,-0.166014f,0.30682f},{-0.428452f,0.19211f,0.298402f},{-0.250825f,-0.368805f,0.0646989f}, +{-0.389058f,0.322427f,-0.0952188f},{-0.356712f,-0.206849f,0.252834f},{-0.0465419f,-0.357403f,0.278377f}, +{-0.224099f,0.347449f,0.0191119f},{-0.404106f,-0.150387f,0.237523f},{-0.43651f,0.136484f,0.328948f}, +{-0.465236f,0.0237292f,0.280358f},{-0.0378927f,-0.229034f,0.34296f},{-0.225662f,-0.354555f,0.0966657f}, +{-0.218459f,-0.360895f,0.133224f},{-0.222896f,-0.359718f,0.149725f},{-0.226658f,-0.35885f,0.169487f}, +{-0.446072f,0.241349f,0.225652f},{-0.208961f,-0.338079f,0.231587f},{-0.199334f,-0.338716f,0.243671f}, +{-0.190434f,-0.338407f,0.253085f},{-0.430259f,0.0928652f,-0.0149255f},{-0.370763f,-0.20236f,0.24396f}, +{-0.428697f,0.131423f,0.336812f},{-0.134944f,-0.137848f,-0.140883f},{-0.380165f,-0.168901f,0.253413f}, +{-0.452137f,-0.0750715f,0.171486f},{-0.394569f,-0.13215f,0.248159f},{-0.223205f,-0.347867f,0.118093f}, +{-0.228395f,-0.3415f,0.153513f},{-0.229205f,-0.337693f,0.171049f},{-0.226941f,-0.341288f,0.187949f}, +{-0.222208f,-0.337764f,0.206135f},{-0.216453f,-0.335346f,0.219665f},{-0.0562201f,-0.340066f,0.26477f}, +{-0.045288f,-0.339288f,0.281103f},{-0.0397254f,-0.338864f,0.28927f},{-0.0306775f,-0.338214f,0.302916f}, +{-0.0162857f,-0.338002f,0.322864f},{-0.443153f,-0.0753416f,0.186393f},{-0.436272f,-0.0714447f,0.194534f}, +{-0.443584f,0.144947f,0.317508f},{-0.489775f,0.132587f,0.152317f},{-0.427597f,-0.0741005f,0.205923f}, +{-0.417887f,-0.0715154f,0.215511f},{-0.426838f,0.145391f,0.332716f},{-0.409778f,-0.0735732f,0.224424f}, +{-0.243153f,-0.337134f,0.138073f},{-0.238452f,-0.33559f,0.146426f},{-0.234761f,0.336902f,0.0319668f}, +{-0.373387f,0.055458f,-0.0377222f},{-0.00782935f,-0.332369f,0.334369f},{-0.399386f,-0.0709559f,0.233349f}, +{-0.39073f,-0.0730523f,0.24169f},{0.0324137f,-0.332806f,0.353249f},{-0.376943f,-0.0711102f,0.251529f}, +{-0.465454f,0.0420951f,0.300209f},{-0.354879f,0.254474f,-0.242256f},{-0.368152f,-0.0732902f,0.259702f}, +{-0.41426f,0.194174f,0.307849f},{-0.175792f,0.340015f,-0.0504935f},{-0.335864f,0.00357545f,0.269503f}, +{-0.247333f,-0.325314f,0.159545f},{-0.240471f,-0.322408f,0.170895f},{-0.229861f,-0.320549f,0.193357f}, +{-0.223835f,-0.318286f,0.208328f},{-0.216009f,-0.317411f,0.22324f},{-0.208305f,-0.316961f,0.233716f}, +{-0.200177f,-0.316536f,0.243838f},{-0.191431f,-0.316221f,0.253355f},{-0.181309f,-0.316112f,0.261619f}, +{-0.387148f,-0.0592585f,0.240423f},{-0.0554613f,-0.321855f,0.268049f},{-0.0459503f,-0.321289f,0.281219f}, +{-0.0403621f,-0.32089f,0.289322f},{-0.0314427f,-0.320228f,0.303116f},{-0.0189415f,-0.319321f,0.322369f}, +{-0.0096428f,-0.318704f,0.335745f},{-0.0709527f,-0.0763191f,-0.143185f},{-0.412138f,0.0923057f,-0.0229381f}, +{-0.315684f,0.037748f,-0.054159f},{-0.371953f,0.0373943f,-0.032089f},{-0.0837626f,-0.0820745f,-0.141339f}, +{-0.31917f,0.112447f,-0.0468409f},{-0.383554f,0.306099f,-0.0502749f},{-0.14951f,0.301977f,-0.0557538f}, +{-0.146989f,0.359506f,-0.0680235f},{-0.0904248f,0.416559f,-0.0912253f},{-0.242909f,0.281901f,-0.00522811f}, +{-0.14686f,0.340272f,-0.0684029f},{-0.277113f,0.00260441f,-0.0934632f},{-0.20972f,-0.303701f,0.234783f}, +{-0.192158f,-0.302948f,0.253696f},{-0.184525f,-0.297354f,0.261445f},{-0.056831f,-0.303765f,0.26886f}, +{-0.0474165f,-0.303135f,0.28221f},{-0.0416996f,-0.302781f,0.290126f},{-0.0336099f,-0.302768f,0.302871f}, +{-0.0201698f,-0.301997f,0.324903f},{-0.0113919f,-0.300704f,0.336915f},{-0.0915244f,-0.0767114f,-0.138401f}, +{-0.31216f,-0.0945757f,-0.104903f},{-0.456374f,0.113887f,0.308447f},{-0.401116f,-0.172303f,0.235144f}, +{-0.0199254f,-0.107077f,0.34858f},{-0.373367f,0.0742934f,-0.0393878f},{-0.379554f,0.0314266f,-0.0254075f}, +{-0.244844f,0.00772325f,-0.102247f},{0.118289f,-0.0103276f,-0.120999f},{-0.165966f,0.320832f,-0.0557924f}, +{0.00993857f,0.455458f,-0.164033f},{-0.233404f,0.301f,-0.0111057f},{-0.0537764f,0.398206f,-0.11527f}, +{-0.0713321f,0.322536f,-0.07711f},{-0.28993f,-0.0385711f,-0.105804f},{-0.0270956f,-0.0990386f,0.332851f}, +{-0.00941129f,-0.0826083f,0.319334f},{-0.255114f,0.31945f,0.0384232f},{-0.0271278f,-0.296923f,0.316292f}, +{-0.00624098f,-0.065567f,0.292544f},{-0.244638f,0.304627f,0.000353705f},{0.00179091f,-0.0658114f,0.293675f}, +{-0.461127f,0.187479f,0.260963f},{-0.360635f,-0.270557f,0.17285f},{-0.277834f,0.149751f,-0.0346741f}, +{-0.459937f,0.0661715f,0.313636f},{-0.356075f,0.252288f,-0.226777f},{-0.300714f,0.0763063f,-0.046275f}, +{-0.386563f,0.341648f,-0.116253f},{-0.0601685f,-0.287084f,0.268679f},{-0.0514228f,-0.285534f,0.283702f}, +{-0.0382014f,-0.284692f,0.306009f},{-0.0291856f,-0.282936f,0.322395f},{-0.0161056f,-0.28219f,0.341063f}, +{-0.00232471f,-0.286537f,0.350111f},{-0.335253f,-0.258924f,0.224443f},{-0.385155f,0.333037f,-0.0948522f}, +{-0.0333591f,0.43542f,-0.127803f},{-0.411855f,-0.207029f,0.191672f},{-0.446426f,0.022861f,0.300524f}, +{-0.277055f,0.170085f,-0.031208f},{-0.258972f,0.00383911f,-0.100222f},{-0.317266f,-0.245445f,0.248603f}, +{-0.272728f,0.300087f,0.0339153f},{0.230388f,-0.091444f,-0.138401f},{-0.0138677f,-0.131848f,-0.228597f}, +{-0.201257f,-0.274763f,0.258956f},{-0.0455002f,-0.279547f,0.297772f},{-0.225276f,0.00369118f,-0.1068f}, +{-0.370486f,0.256436f,-0.188766f},{-0.382705f,0.288885f,-0.0146748f},{0.00393876f,0.417485f,-0.150645f}, +{-0.385271f,0.287875f,-0.0345519f},{-0.260792f,-0.0337803f,-0.108704f},{-0.244831f,-0.0334458f,-0.111713f}, +{0.134803f,-0.116003f,-0.199814f},{-0.392602f,0.0739076f,-0.032269f},{-0.239179f,0.224848f,0.00285523f}, +{-0.334828f,0.0558953f,-0.0452461f},{-0.372088f,-0.0197486f,-0.0509951f},{-0.460844f,0.239163f,0.200495f}, +{-0.0567988f,-0.266589f,0.285836f},{-0.0479953f,-0.264763f,0.302518f},{-0.0426835f,-0.269921f,0.308286f}, +{-0.0364136f,-0.265336f,0.322581f},{-0.0279445f,-0.261644f,0.338645f},{-0.0204399f,-0.265175f,0.346375f}, +{-0.0110511f,-0.263548f,0.353577f},{-0.0415196f,-0.213491f,0.350619f},{-0.366557f,-0.114839f,-0.0851419f}, +{-0.387206f,0.286878f,-0.0535867f},{-0.0459696f,-0.223099f,0.337192f},{-0.23932f,0.111945f,-0.0249381f}, +{-0.368429f,-0.18074f,0.257278f},{-0.39747f,0.150677f,-0.024115f},{-0.40736f,0.0371628f,-0.0137487f}, +{-0.279833f,0.0576059f,-0.0560432f},{-0.18682f,-0.0372271f,-0.123777f},{-0.200994f,0.287045f,-0.0307f}, +{-0.405437f,0.0665188f,0.332163f},{-0.392486f,-0.00592905f,0.264339f},{-0.167876f,-0.0127777f,-0.115559f}, +{-0.431706f,0.110852f,-0.014887f},{-0.383843f,0.297264f,-0.0405903f},{-0.359869f,-0.136562f,0.274609f}, +{-0.205707f,-0.134285f,-0.130022f},{-0.379097f,-0.128182f,-0.07911f},{-0.167509f,-0.0371756f,-0.123816f}, +{-0.148236f,0.00388414f,-0.109051f},{-0.108071f,-0.0344876f,-0.125636f},{-0.130231f,-0.0316775f,-0.120556f}, +{-0.12252f,-0.0370213f,-0.122819f},{-0.390344f,-0.203717f,0.223781f},{-0.060863f,-0.248262f,0.289296f}, +{-0.053069f,-0.248204f,0.302363f},{-0.0462461f,-0.24533f,0.317038f},{-0.0402077f,-0.246346f,0.327642f}, +{-0.0311919f,-0.245748f,0.34132f},{-0.0216617f,-0.250088f,0.350182f},{-0.479345f,0.10862f,0.276891f}, +{-0.0165558f,0.416456f,-0.13696f},{-0.240613f,0.00181343f,-0.105263f},{-0.251307f,-0.00141475f,-0.104061f}, +{-0.243983f,-0.345249f,0.0791357f},{-0.37976f,-0.146928f,-0.0790264f},{-0.284489f,0.186753f,-0.0306292f}, +{-0.166853f,-0.488621f,-0.151378f},{-0.176988f,-0.48165f,-0.146484f},{-0.182602f,-0.485354f,-0.132883f}, +{-0.182563f,-0.469779f,-0.130819f},{-0.188415f,-0.472615f,-0.114112f},{-0.192512f,-0.473065f,-0.0986913f}, +{-0.196087f,-0.475657f,-0.0901064f},{-0.20235f,-0.47093f,-0.0717211f},{0.132764f,-0.0231825f,-0.122986f}, +{0.039944f,-0.48882f,-0.149474f},{0.0224976f,-0.0872319f,-0.190502f},{0.135433f,-0.056204f,-0.131603f}, +{-0.343092f,-0.463438f,0.00420567f},{-0.487267f,0.130806f,0.17258f},{-0.163702f,-0.472956f,-0.165364f}, +{-0.171027f,-0.467946f,-0.152497f},{0.0600398f,0.0716697f,-0.0695862f},{0.0408508f,-0.0974438f,-0.205595f}, +{0.300392f,-0.193556f,-0.123572f},{-0.335619f,-0.449535f,0.00314461f},{-0.173612f,0.381512f,-0.0386097f}, +{-0.186685f,0.381075f,-0.0166104f},{-0.482695f,0.130813f,0.191782f},{0.00456896f,0.0809042f,-0.037928f}, +{-0.322411f,0.376715f,-0.357763f},{-0.327215f,0.382174f,-0.349963f},{-0.33297f,0.377602f,-0.33707f}, +{-0.342153f,0.376753f,-0.319134f},{-0.351407f,0.377486f,-0.301778f},{-0.358731f,0.376528f,-0.286988f}, +{-0.278682f,-0.454268f,0.148587f},{-0.289023f,-0.458513f,0.162754f},{-0.480489f,0.126414f,0.211106f}, +{-0.478753f,0.126446f,0.223967f},{-0.314533f,0.396926f,-0.373435f},{-0.337992f,0.395717f,-0.337192f}, +{-0.372744f,0.393184f,-0.287315f},{-0.145664f,0.399228f,-0.0548149f},{-0.369689f,0.404225f,-0.30208f}, +{-0.341741f,0.412334f,-0.341507f},{-0.47602f,0.131777f,0.231665f},{-0.175091f,-0.462834f,-0.138176f}, +{-0.181431f,-0.450474f,-0.112922f},{-0.187457f,-0.452378f,-0.0945114f},{-0.198215f,-0.457599f,-0.0711102f}, +{-0.322347f,-0.449432f,-0.0207646f},{-0.342114f,-0.444552f,0.0145719f},{-0.475576f,0.130928f,0.249651f}, +{-0.388409f,0.328401f,-0.169358f},{-0.372094f,0.323552f,-0.188772f},{-0.360223f,0.323527f,-0.227272f}, +{-0.346783f,0.324787f,-0.280885f},{-0.306334f,-0.447471f,0.184972f},{-0.377014f,0.354587f,-0.231073f}, +{-0.0322337f,0.0547957f,-0.0699784f},{-0.213797f,0.357223f,0.0104113f},{-0.489724f,0.134935f,0.133899f}, +{-0.388544f,0.426584f,-0.314903f},{-0.360107f,0.341494f,-0.246744f},{-0.362088f,0.26956f,-0.22697f}, +{-0.144899f,-0.453497f,-0.168876f},{-0.161966f,-0.451529f,-0.151211f},{-0.168705f,-0.448545f,-0.137327f}, +{-0.177663f,-0.450487f,-0.128858f},{-0.191148f,-0.446744f,-0.0748786f},{-0.31416f,-0.436713f,-0.0157422f}, +{-0.320385f,-0.433047f,0.000713822f},{-0.331632f,-0.436893f,0.00825056f},{-0.398807f,0.272673f,0.0160702f}, +{-0.259255f,0.306582f,0.0174271f},{-0.16639f,0.0193627f,-0.0963892f},{-0.2655f,-0.443079f,0.133751f}, +{-0.267403f,-0.442867f,0.143391f},{-0.274734f,-0.442063f,0.153011f},{-0.281171f,-0.433812f,0.169056f}, +{-0.295209f,-0.439838f,0.175518f},{-0.299312f,-0.434674f,0.188206f},{-0.183753f,0.0188611f,-0.0958747f}, +{-0.145092f,0.0895598f,-0.0480499f},{-0.0156812f,-0.0939005f,-0.187737f},{-0.357644f,0.287502f,-0.227073f}, +{-0.388158f,0.288332f,-0.0727115f},{-0.436201f,0.137925f,-0.000482281f},{-0.443339f,0.13341f,0.00564614f}, +{-0.152873f,-0.447098f,-0.158869f},{-0.205546f,-0.434385f,-0.0600752f},{0.249809f,-0.0934632f,-0.132144f}, +{0.117897f,0.0717212f,-0.0700427f},{-0.460078f,-0.0407382f,0.096267f},{-0.299132f,-0.431536f,-0.0201794f}, +{-0.373547f,0.270056f,-0.149796f},{-0.371355f,0.26994f,-0.169081f},{-0.256085f,-0.435066f,0.130497f}, +{-0.260657f,-0.432256f,0.151358f},{-0.307132f,-0.429189f,0.226957f},{-0.410871f,0.260808f,0.0192406f}, +{-0.105749f,0.430977f,-0.0668081f},{-0.476682f,0.187891f,0.13361f},{-0.328989f,0.397312f,-0.352194f}, +{-0.146269f,-0.434886f,-0.151243f},{-0.163432f,-0.433066f,-0.131983f},{-0.169901f,-0.431382f,-0.113977f}, +{-0.172757f,-0.428777f,-0.0962863f},{-0.178994f,-0.435002f,-0.0914054f},{-0.182042f,-0.430301f,-0.075168f}, +{-0.188068f,-0.430121f,-0.0630205f},{-0.286296f,-0.426526f,-0.0209511f},{-0.295679f,-0.423536f,-0.0106942f}, +{-0.299158f,-0.419125f,0.00205783f},{-0.30964f,-0.421794f,0.00842418f},{-0.319067f,-0.420539f,0.0211762f}, +{-0.317787f,-0.415877f,0.0403717f},{-0.241513f,-0.42742f,0.130504f},{-0.246574f,-0.426957f,0.146503f}, +{-0.265088f,-0.424977f,0.165782f},{-0.278605f,-0.41899f,0.188264f},{-0.289878f,-0.422186f,0.194675f}, +{-0.296367f,-0.421485f,0.207543f},{-0.296624f,-0.416861f,0.226842f},{-0.214041f,0.35705f,0.000868158f}, +{-0.372178f,0.360188f,-0.245767f},{-0.126732f,-0.431999f,-0.168876f},{-0.137542f,-0.430205f,-0.159088f}, +{-0.150487f,-0.428944f,-0.139693f},{-0.107415f,0.0218128f,-0.0993151f},{-0.107826f,-0.0982734f,-0.142163f}, +{-0.260014f,-0.410617f,-0.0196071f},{-0.275139f,-0.418797f,-0.0182245f},{-0.280573f,-0.410868f,0.00176845f}, +{-0.301145f,-0.413131f,0.0196264f},{-0.478277f,0.24097f,0.165397f},{-0.479788f,0.243169f,0.150992f}, +{-0.23289f,-0.419176f,0.124035f},{-0.22716f,-0.414501f,0.131655f},{-0.237886f,-0.41652f,0.151256f}, +{-0.245224f,-0.411183f,0.167319f},{-0.258143f,-0.416636f,0.172162f},{-0.261242f,-0.410231f,0.188219f}, +{-0.279152f,-0.410733f,0.208804f},{-0.234761f,0.338857f,0.0169963f},{-0.109099f,-0.452146f,-0.183042f}, +{-0.12879f,-0.415852f,-0.150034f},{-0.137793f,-0.420694f,-0.146503f},{-0.160075f,-0.418713f,-0.114035f}, +{-0.16293f,-0.414913f,-0.093386f},{-0.167233f,-0.411961f,-0.0760554f},{0.137401f,0.0762162f,-0.0700106f}, +{0.193206f,0.0219222f,-0.0992637f},{0.117524f,0.0372528f,-0.107186f},{-0.242619f,-0.416353f,-0.0356644f}, +{-0.269223f,-0.407247f,-0.0048487f},{-0.283293f,-0.406013f,0.0177808f},{-0.290901f,-0.405099f,0.0402945f}, +{-0.286155f,-0.405035f,0.0532973f},{-0.477178f,0.13523f,0.211067f},{-0.359001f,0.238642f,-0.130349f}, +{-0.36293f,0.23879f,-0.11426f},{-0.227366f,-0.410913f,0.117514f},{-0.229591f,-0.410585f,0.1464f}, +{-0.251204f,-0.408251f,0.181776f},{-0.265866f,-0.406694f,0.201074f},{-0.277969f,-0.405363f,0.226771f}, +{-0.344474f,0.321842f,-0.289502f},{-0.364229f,0.372869f,-0.272808f},{-0.39909f,0.359654f,-0.208148f}, +{-0.167573f,0.0368927f,-0.0891675f},{-0.149921f,-0.409479f,-0.111752f},{-0.155606f,-0.408765f,-0.0957525f}, +{-0.171252f,-0.408855f,-0.0626539f},{-0.128803f,0.0930517f,-0.0337481f},{-0.236954f,-0.399897f,-0.031761f}, +{-0.245462f,-0.39474f,-0.0222887f},{-0.253468f,-0.396849f,-0.0136394f},{-0.260998f,-0.394527f,0.00144692f}, +{-0.266863f,-0.393839f,0.0208096f},{-0.271139f,-0.397891f,0.0370406f},{-0.266123f,-0.39137f,0.0419858f}, +{-0.218028f,-0.398206f,0.130208f},{-0.225591f,-0.397363f,0.149455f},{-0.230806f,-0.39528f,0.164496f}, +{-0.234845f,-0.397846f,0.173094f},{-0.239385f,-0.395852f,0.188129f},{-0.240941f,-0.393396f,0.2058f}, +{-0.255069f,-0.398714f,0.210662f},{-0.260741f,-0.398077f,0.226726f},{0.422581f,-0.303431f,-0.008855f}, +{-0.252979f,0.320176f,0.0204238f},{0.00404165f,0.0687116f,-0.0526414f},{0.0234815f,0.028057f,-0.100048f}, +{-0.0830745f,0.449953f,-0.0670911f},{-0.121877f,-0.402958f,-0.124716f},{-0.132301f,-0.403363f,-0.111636f}, +{-0.142712f,-0.403344f,-0.095984f},{-0.159342f,-0.401923f,-0.0683258f},{-0.166024f,-0.395119f,-0.0611942f}, +{0.0255393f,-0.0174078f,-0.13015f},{0.210421f,0.0209189f,-0.0930066f},{0.233301f,-0.11307f,-0.146889f}, +{-0.430722f,0.244603f,0.0369442f},{-0.476554f,0.228989f,0.109829f},{-0.47928f,0.226076f,0.118395f}, +{-0.480772f,0.227626f,0.133404f},{-0.236536f,-0.391434f,0.223472f},{-0.231886f,-0.391839f,0.233137f}, +{-0.374023f,0.377255f,-0.266467f},{-0.392621f,0.402617f,-0.285676f},{-0.36331f,0.354966f,-0.253329f}, +{-0.360352f,0.305559f,-0.207871f},{-0.0127617f,0.0691425f,-0.0529565f},{0.225211f,-0.100518f,-0.143616f}, +{0.22833f,-0.0351178f,-0.111739f},{0.400203f,-0.263914f,0.0221408f},{0.286573f,-0.0366419f,-0.0902029f}, +{-0.107479f,0.0613164f,-0.058474f},{-0.0914923f,-0.401138f,-0.148098f},{-0.0929006f,-0.395145f,-0.13352f}, +{-0.104617f,-0.399666f,-0.133211f},{-0.110173f,-0.396367f,-0.114093f},{-0.107247f,-0.390348f,-0.0948522f}, +{-0.127697f,-0.397434f,-0.09228f},{-0.128777f,-0.393499f,-0.074049f},{-0.146777f,-0.391113f,-0.0637793f}, +{0.0422655f,-0.00248225f,-0.127166f},{-0.0146008f,0.0365648f,-0.0938812f},{-0.234967f,-0.384721f,-0.0298961f}, +{-0.244812f,-0.378399f,-0.0178837f},{-0.255571f,-0.377197f,0.00137618f},{-0.26114f,-0.376528f,0.0207518f}, +{-0.260188f,-0.376483f,0.0402431f},{-0.477473f,0.209826f,0.107881f},{-0.34704f,0.303643f,-0.280718f}, +{-0.216215f,-0.381248f,0.131378f},{-0.222665f,-0.379615f,0.149326f},{-0.227957f,-0.378978f,0.168689f}, +{-0.229198f,-0.378753f,0.188039f},{-0.421314f,0.195351f,0.0116652f},{-0.157689f,0.393672f,-0.0470981f}, +{-0.358873f,0.35858f,-0.267483f},{-0.394415f,0.342664f,-0.187473f},{0.250214f,0.0217163f,-0.0747114f}, +{-0.0781615f,-0.389061f,-0.137552f},{-0.0940581f,-0.38829f,-0.114215f},{-0.0967204f,-0.388026f,-0.101341f}, +{-0.110759f,-0.386779f,-0.0755217f},{-0.224793f,-0.379332f,-0.0364618f},{0.309421f,-0.0351564f,-0.0752451f}, +{-0.354301f,0.212225f,-0.053233f},{-0.356635f,0.2038f,-0.0401016f},{-0.212697f,-0.371692f,0.11727f}, +{-0.0316228f,-0.0987171f,-0.188586f},{0.100437f,0.099045f,-0.0386418f},{-0.376178f,0.319128f,-0.169448f}, +{-0.362467f,0.319096f,-0.211157f},{-0.300559f,0.395724f,-0.384277f},{-0.365618f,0.363963f,-0.259812f}, +{0.286451f,0.0156522f,-0.0540175f},{-0.110263f,0.00234718f,-0.109418f},{-0.067493f,-0.378502f,-0.149243f}, +{-0.0773962f,-0.376142f,-0.133758f},{-0.0834796f,-0.375563f,-0.11426f},{-0.0887785f,-0.374985f,-0.094968f}, +{-0.0947783f,-0.374297f,-0.0758946f},{0.327723f,-0.0941063f,-0.0786534f},{-0.261332f,0.0396836f,-0.0784283f}, +{0.0601363f,0.00210928f,-0.129205f},{-0.234491f,-0.361545f,-0.0312144f},{-0.24433f,-0.360458f,-0.0179544f}, +{-0.250625f,-0.359802f,-0.00518309f},{-0.254902f,-0.359339f,0.00448862f},{-0.258381f,-0.358934f,0.0206553f}, +{-0.256709f,-0.359005f,0.0401981f},{-0.252696f,-0.359326f,0.0564162f},{-0.248773f,-0.359622f,0.0660429f}, +{-0.226395f,-0.361004f,0.187975f},{-0.43997f,0.24032f,0.0500048f},{-0.185708f,0.0393235f,-0.0903636f}, +{0.399167f,-0.275515f,0.0147327f},{-0.0604257f,-0.373454f,-0.159667f},{0.021726f,0.00198706f,-0.117398f}, +{-0.447256f,0.245632f,0.0579596f},{0.192859f,-0.110691f,-0.169872f},{-0.224626f,0.343153f,-0.00208995f}, +{-0.0538279f,0.437452f,-0.112003f},{-0.46213f,0.169589f,0.056577f},{-0.0297193f,0.454834f,-0.127893f}, +{0.174493f,0.040108f,-0.0943442f},{0.0798784f,0.0608919f,-0.0775023f},{-0.128925f,0.00378121f,-0.110685f}, +{0.306341f,-0.131018f,-0.115533f},{-0.0521945f,-0.359069f,-0.170985f},{-0.0679753f,-0.35905f,-0.153082f}, +{-0.0764767f,-0.358188f,-0.136973f},{-0.0822835f,-0.357609f,-0.127276f},{-0.085396f,-0.357255f,-0.114395f}, +{-0.0885663f,-0.356863f,-0.0950709f},{-0.0949005f,-0.355339f,-0.0771744f},{-0.244137f,0.0433684f,-0.0812385f}, +{0.00293558f,0.00318962f,-0.117385f},{-0.224826f,-0.359513f,-0.0394714f},{-0.238709f,-0.335687f,-0.0346741f}, +{-0.246516f,-0.342497f,-0.0181602f},{-0.250452f,-0.346484f,-0.00528599f},{-0.254458f,-0.340947f,0.00241795f}, +{-0.259416f,-0.341108f,0.0204624f},{-0.262072f,-0.339925f,0.0405711f},{-0.260541f,-0.339442f,0.0580367f}, +{-0.250715f,-0.346066f,0.0657085f},{-0.229764f,-0.34777f,0.0946079f},{-0.478084f,0.143294f,0.0695798f}, +{-0.0194624f,0.454918f,-0.137661f},{0.230504f,0.0393814f,-0.0744413f},{-0.0131475f,0.00237936f,-0.119089f}, +{-0.261159f,-0.336709f,0.0775474f},{-0.336951f,0.323102f,-0.320594f},{-0.482161f,0.146793f,0.15314f}, +{-0.36412f,0.292286f,-0.172464f},{-0.341118f,0.323173f,-0.304447f},{-0.154821f,0.402887f,-0.0399472f}, +{-0.480985f,0.14397f,0.17247f},{-0.373953f,0.305714f,-0.150085f},{0.0102794f,0.469335f,-0.163043f}, +{-0.047185f,-0.340516f,-0.185473f},{-0.0574934f,-0.341931f,-0.17566f},{-0.0636925f,-0.339854f,-0.168181f}, +{-0.0706248f,-0.340658f,-0.15314f},{-0.0778142f,-0.339886f,-0.136979f},{-0.0816469f,-0.339539f,-0.127417f}, +{-0.0869393f,-0.338999f,-0.114536f},{-0.0922639f,-0.33842f,-0.0952124f},{-0.0962638f,-0.33887f,-0.0771679f}, +{-0.249024f,-0.326324f,-0.0227131f},{-0.257783f,-0.31788f,-0.0144175f},{-0.261165f,-0.323372f,-0.000257208f}, +{-0.266278f,-0.326858f,0.0179351f},{-0.2693f,-0.329141f,0.0326421f},{-0.14104f,0.379949f,-0.068075f}, +{0.217597f,-0.0904601f,-0.14069f},{-0.478329f,0.144021f,0.185338f},{-0.297132f,0.16867f,-0.03742f}, +{-0.349812f,0.27331f,-0.282345f},{-0.0403556f,0.45639f,-0.116228f},{0.00156584f,0.473284f,-0.154117f}, +{-0.185734f,0.0511109f,-0.0847947f},{-0.0329411f,0.0695283f,-0.0532716f},{0.281827f,-0.0642809f,-0.104826f}, +{-0.0320729f,0.00306098f,-0.118446f},{0.208871f,-0.118466f,-0.163217f},{0.154847f,-0.090473f,-0.168831f}, +{-0.167374f,-0.073914f,-0.129115f},{-0.072599f,0.340851f,-0.0886917f},{-0.366667f,0.381724f,-0.279457f}, +{-0.230234f,0.324427f,-0.00771678f},{-0.444722f,0.260564f,0.0609241f},{-0.390074f,0.266377f,-0.0179994f}, +{-0.205019f,0.0589885f,-0.0783897f},{0.155696f,0.0757082f,-0.0711424f},{0.289036f,0.00604484f,-0.0610784f}, +{-0.05386f,-0.32471f,-0.188232f},{-0.0694608f,-0.322703f,-0.169332f},{-0.0759301f,-0.322035f,-0.153191f}, +{-0.0800071f,-0.326137f,-0.137089f},{-0.0837948f,-0.318942f,-0.132272f},{-0.0893701f,-0.3208f,-0.114633f}, +{-0.0966689f,-0.319919f,-0.0953024f},{-0.0994277f,-0.324112f,-0.0823703f},{-0.106489f,-0.321019f,-0.0733867f}, +{0.292431f,-0.0723835f,-0.100453f},{-0.149445f,-0.470924f,-0.172438f},{0.0788817f,0.000926046f,-0.129231f}, +{0.0984116f,0.0203595f,-0.112736f},{0.118257f,-0.112266f,-0.202592f},{0.20846f,-0.0947365f,-0.144658f}, +{0.17383f,-0.073406f,-0.137037f},{-0.316964f,-0.304125f,-0.00106747f},{-0.464509f,0.227427f,0.0788464f}, +{-0.42925f,0.260982f,0.0385454f},{-0.163574f,0.397498f,-0.0332915f},{-0.351902f,0.235922f,-0.147539f}, +{-0.22152f,0.0564291f,-0.0771293f},{-0.0656667f,-0.318678f,-0.179255f},{0.252535f,-0.0537153f,-0.117816f}, +{-0.0121893f,-0.0160059f,-0.130941f},{-0.127453f,-0.47147f,-0.182393f},{0.0793254f,0.0197872f,-0.113578f}, +{0.458831f,-0.267097f,0.0243722f},{-0.317376f,-0.295476f,-0.0150156f},{-0.333626f,-0.295296f,0.00389057f}, +{-0.336764f,-0.2979f,0.0185653f},{-0.257429f,0.283187f,0.00455936f},{-0.405932f,-0.241375f,0.133391f}, +{-0.354153f,0.0745571f,-0.0441465f},{-0.419096f,0.153346f,-0.00814763f},{-0.327099f,0.327359f,-0.349841f}, +{-0.322951f,0.394309f,-0.360857f},{-0.37075f,0.287991f,-0.130658f},{-0.36621f,0.267438f,-0.206064f}, +{-0.162898f,0.000514465f,-0.108871f},{-0.0462654f,-0.312871f,-0.199325f},{-0.0594933f,-0.300961f,-0.192058f}, +{-0.0672487f,-0.304775f,-0.182322f},{-0.0766374f,-0.303913f,-0.169435f},{-0.0791132f,-0.30808f,-0.153217f}, +{-0.0874795f,-0.302756f,-0.133976f},{-0.0951449f,-0.30208f,-0.114762f},{-0.0997557f,-0.306196f,-0.101907f}, +{-0.103241f,-0.300428f,-0.0942606f},{-0.11018f,-0.303219f,-0.0795022f},{0.321511f,-0.0410662f,-0.0695154f}, +{-0.0158227f,-0.485991f,-0.167647f},{0.139388f,-0.0693933f,-0.138169f},{-0.333099f,-0.281046f,-0.0147905f}, +{-0.340777f,-0.283181f,-0.00391625f},{-0.351394f,-0.27911f,0.00481658f},{-0.357329f,-0.284029f,0.0211119f}, +{-0.386068f,-0.0377737f,-0.0490209f},{-0.321331f,0.235285f,-0.148297f},{-0.374062f,-0.280004f,0.0586155f}, +{-0.372666f,-0.282261f,0.0763448f},{0.249629f,-0.0174978f,-0.0920742f},{-0.164429f,0.0917784f,-0.0503135f}, +{0.335099f,-0.00899008f,-0.0222115f},{-0.374043f,0.250539f,-0.110157f},{0.213424f,0.0407125f,-0.0809684f}, +{-0.363529f,0.337031f,-0.230636f},{0.214073f,0.0646603f,-0.0599209f},{-0.0494036f,-0.298383f,-0.201749f}, +{-0.0826758f,-0.298711f,-0.153262f},{0.288766f,-0.16813f,-0.13006f},{0.349883f,-0.104794f,-0.0615607f}, +{-0.369618f,-0.275419f,0.0223337f},{-0.372994f,-0.277071f,0.0392592f},{-0.363252f,0.274158f,-0.210945f}, +{-0.371876f,0.252358f,-0.130369f},{-0.166056f,0.0586155f,-0.0782997f},{-0.0469342f,0.0687309f,-0.0523006f}, +{-0.0710235f,-0.281856f,-0.185775f},{-0.0795184f,-0.290126f,-0.172843f},{-0.084097f,-0.283644f,-0.168522f}, +{-0.0888235f,-0.284647f,-0.153436f},{-0.0927912f,-0.284152f,-0.134047f},{-0.0977621f,-0.2852f,-0.119077f}, +{-0.102334f,-0.281837f,-0.110601f},{-0.110096f,-0.283251f,-0.095013f},{0.193167f,-0.0444487f,-0.125932f}, +{0.210254f,-0.0442237f,-0.126176f},{-0.338134f,-0.268429f,-0.02416f},{-0.353317f,-0.261464f,-0.0176714f}, +{-0.356571f,-0.267033f,-0.00273944f},{-0.370892f,-0.259053f,0.00223789f},{-0.378017f,-0.261207f,0.018829f}, +{-0.380563f,-0.266255f,0.036031f},{-0.388769f,-0.257387f,0.0409119f},{-0.393296f,-0.259284f,0.0599917f}, +{-0.224581f,0.0217292f,-0.0973795f},{-0.277493f,0.0697534f,-0.0489566f},{-0.348191f,0.38202f,-0.311495f}, +{0.230028f,-0.150413f,-0.164348f},{-0.174428f,0.365828f,-0.0464615f},{-0.00840811f,0.473888f,-0.144368f}, +{-0.127317f,0.0454583f,-0.0789106f},{-0.0519373f,-0.281245f,-0.207955f},{0.328263f,0.00381982f,-0.0205267f}, +{-0.385071f,-0.255535f,0.0264493f},{-0.403219f,-0.253587f,0.0716247f},{-0.412279f,-0.24295f,0.0788335f}, +{-0.410434f,-0.242712f,0.115668f},{-0.479229f,0.245175f,0.114061f},{-0.48065f,0.245272f,0.133314f}, +{-0.391457f,0.382155f,-0.259612f},{0.0414745f,0.078602f,-0.057355f},{0.097119f,0.032134f,-0.104755f}, +{-0.380975f,0.283476f,-0.0952445f},{-0.297859f,0.0199222f,-0.0754959f},{-0.0166522f,0.476062f,-0.133867f}, +{0.251429f,0.0748529f,-0.0162503f},{-0.0652552f,-0.273599f,-0.199029f},{-0.0789203f,-0.263027f,-0.187409f}, +{-0.0867143f,-0.266744f,-0.16957f},{-0.0926819f,-0.266152f,-0.153474f},{-0.0976142f,-0.267895f,-0.135738f}, +{-0.105775f,-0.264969f,-0.11498f},{-0.11371f,-0.266043f,-0.10136f},{0.223366f,-0.0452654f,-0.125063f}, +{-0.164191f,0.0825826f,-0.0599595f},{-0.25966f,-0.4712f,-0.0566155f},{0.152616f,-0.130073f,-0.200251f}, +{-0.0534999f,-0.0905823f,-0.151551f},{-0.358661f,-0.248108f,-0.036076f},{-0.376686f,-0.248043f,-0.0189383f}, +{-0.392094f,-0.241774f,0.000803851f},{-0.394306f,-0.243728f,0.0184432f},{-0.400145f,-0.245285f,0.03297f}, +{-0.409405f,-0.240314f,0.0406161f},{-0.412511f,-0.241253f,0.0587698f},{-0.476059f,0.249876f,0.155802f}, +{-0.381837f,0.372689f,-0.253587f},{0.0425099f,0.051426f,-0.0864795f},{-0.242034f,0.0572844f,-0.0712903f}, +{-0.0562265f,-0.265136f,-0.213819f},{-0.0698724f,-0.258506f,-0.203993f},{-0.0831645f,-0.258114f,-0.182509f}, +{-0.101318f,-0.260763f,-0.130954f},{-0.0545867f,0.00329893f,-0.113752f},{0.212292f,0.0944536f,-0.0180444f}, +{0.227436f,0.0886209f,-0.0160766f},{-0.371393f,-0.23879f,-0.0384425f},{-0.389373f,-0.238487f,-0.0192598f}, +{-0.405399f,-0.236391f,0.0231697f},{-0.261307f,-0.265336f,0.252268f},{-0.478856f,0.152291f,0.101656f}, +{-0.480149f,0.152413f,0.114511f},{-0.194711f,0.375621f,-0.00891288f},{-0.479936f,0.152529f,0.133803f}, +{-0.374621f,0.285663f,-0.112993f},{-0.382448f,0.336967f,-0.195331f},{-0.260792f,0.0700299f,-0.0518826f}, +{-0.0886049f,-0.241594f,-0.185068f},{-0.0948683f,-0.248005f,-0.166535f},{-0.0981866f,-0.252121f,-0.150317f}, +{-0.105325f,-0.246873f,-0.13424f},{-0.112263f,-0.246918f,-0.116922f},{0.0821356f,-0.110093f,-0.211627f}, +{0.289306f,-0.150799f,-0.128838f},{-0.205109f,0.00398703f,-0.106389f},{0.23579f,0.0818816f,-0.0195878f}, +{0.136121f,-0.081618f,-0.163789f},{-0.00316069f,0.0486287f,-0.0831098f},{-0.378866f,-0.22953f,-0.0397801f}, +{-0.387393f,-0.222649f,-0.0356902f},{-0.396486f,-0.225935f,-0.0234333f},{-0.404749f,-0.22207f,-0.0145719f}, +{-0.408363f,-0.222977f,0.000681669f},{-0.414556f,-0.223279f,0.0187132f},{-0.420665f,-0.226301f,0.0392914f}, +{-0.425784f,-0.221337f,0.0586155f},{-0.420003f,-0.226192f,0.0779525f},{-0.419192f,-0.225961f,0.097238f}, +{-0.417244f,-0.22625f,0.116575f},{-0.41498f,-0.224546f,0.135044f},{0.000530508f,0.43643f,-0.153616f}, +{0.247841f,-0.0313559f,-0.101321f},{-0.0731263f,-0.242809f,-0.209009f},{-0.101801f,-0.242712f,-0.150336f}, +{-0.0732871f,0.0348092f,-0.0873284f},{0.220356f,-0.122761f,-0.158651f},{0.1365f,0.0392913f,-0.106254f}, +{0.155336f,0.0330022f,-0.105945f},{0.100579f,-0.0536767f,-0.136889f},{0.115652f,-0.0799138f,-0.168798f}, +{-0.423469f,-0.217395f,0.0263722f},{-0.427533f,-0.216951f,0.0392463f},{-0.425494f,-0.216983f,0.0779011f}, +{-0.424337f,-0.21688f,0.0971866f},{-0.425662f,-0.216598f,0.116234f},{-0.423797f,-0.216681f,0.12851f}, +{-0.423861f,0.225183f,0.0220893f},{0.100174f,-0.066583f,-0.146233f},{0.117781f,0.100608f,-0.0368284f}, +{-0.0833832f,-0.235883f,-0.199106f},{-0.0978393f,-0.231247f,-0.170998f},{-0.104392f,-0.227542f,-0.162303f}, +{-0.107845f,-0.228681f,-0.150445f},{-0.111389f,-0.228263f,-0.134343f},{-0.116778f,-0.227954f,-0.12181f}, +{0.249667f,-0.128967f,-0.145706f},{0.232472f,0.0238128f,-0.0833221f},{-0.392711f,-0.207087f,-0.0382303f}, +{-0.402485f,-0.210778f,-0.0256197f},{-0.409521f,-0.204836f,-0.0176264f},{-0.417019f,-0.205781f,-0.000688062f}, +{-0.421044f,-0.208868f,0.0135237f},{-0.426826f,-0.203151f,0.0212534f},{-0.433874f,-0.203408f,0.0392656f}, +{-0.436709f,-0.205222f,0.0569371f},{-0.434806f,-0.20726f,0.077856f},{-0.434021f,-0.208842f,0.115675f}, +{-0.426948f,-0.203672f,0.15186f},{-0.419392f,-0.211967f,0.157686f},{-0.347349f,0.218392f,-0.0859072f}, +{-0.14086f,0.412385f,-0.0475226f},{0.154384f,0.0215427f,-0.112858f},{-0.243057f,0.0221793f,-0.0944857f}, +{-0.0784766f,-0.222494f,-0.212701f},{-0.0877432f,-0.221594f,-0.20236f},{-0.0960066f,-0.220906f,-0.18609f}, +{-0.184673f,0.00709305f,-0.102762f},{0.00357221f,0.0290087f,-0.101013f},{-0.358982f,-0.204495f,-0.0763319f}, +{-0.374499f,-0.206366f,-0.0561397f},{-0.107196f,-0.404501f,-0.146388f},{-0.421803f,-0.200129f,0.00706088f}, +{-0.441404f,-0.198405f,0.0617279f},{-0.441841f,-0.198167f,0.0778239f},{-0.442954f,-0.198026f,0.097103f}, +{-0.442626f,-0.197916f,0.116363f},{-0.424305f,-0.199215f,0.171094f},{-0.42233f,-0.199756f,0.181563f}, +{-0.033552f,-0.110974f,-0.208964f},{-0.32481f,0.410244f,-0.363647f},{-0.0341886f,-0.0863831f,-0.153114f}, +{0.215347f,0.00317677f,-0.0975853f},{-0.10544f,-0.206263f,-0.166702f},{-0.111595f,-0.210225f,-0.150535f}, +{-0.114463f,-0.209916f,-0.134433f},{-0.116205f,-0.214244f,-0.121559f},{-0.0201055f,0.0293688f,-0.101296f}, +{0.205476f,0.0341211f,-0.0879907f},{0.173072f,0.0181537f,-0.109257f},{-0.361824f,-0.192013f,-0.0803061f}, +{-0.369959f,-0.185345f,-0.0759332f},{-0.376383f,-0.192547f,-0.0622745f},{0.0600784f,0.0621138f,-0.0787177f}, +{-0.397058f,-0.190901f,-0.0428603f},{-0.40473f,-0.187197f,-0.0339732f},{-0.413649f,-0.188984f,-0.0203209f}, +{-0.420009f,-0.191859f,-0.00282304f},{-0.425642f,-0.184708f,0.00212857f},{-0.429642f,-0.186438f,0.0198643f}, +{-0.437494f,-0.185814f,0.0391563f},{-0.44478f,-0.185177f,0.0584419f},{-0.447198f,-0.184791f,0.0777853f}, +{-0.448767f,-0.184624f,0.0970323f},{-0.447564f,-0.184586f,0.116279f},{-0.443822f,-0.183865f,0.134227f}, +{-0.439828f,-0.189216f,0.141912f},{-0.434227f,-0.185383f,0.154863f},{-0.432086f,-0.185505f,0.170991f}, +{-0.372679f,0.204006f,-0.0321276f},{-0.380731f,0.208058f,-0.0269509f},{-0.385669f,0.292717f,-0.108125f}, +{-0.384955f,0.275155f,-0.0501334f},{-0.457513f,0.0372528f,0.0313302f},{-0.0781293f,-0.209074f,-0.214385f}, +{-0.0885985f,-0.202334f,-0.204315f},{-0.0979165f,-0.200945f,-0.190463f},{-0.100585f,-0.203717f,-0.181891f}, +{-0.290669f,0.41126f,-0.387428f},{0.246542f,-0.0402495f,-0.109823f},{-0.388872f,-0.187074f,-0.0538118f}, +{-0.421507f,-0.183107f,-0.0125269f},{-0.376924f,0.270249f,-0.111244f},{0.135272f,0.0239992f,-0.115713f}, +{-0.221777f,0.0372528f,-0.0892061f},{0.458972f,-0.281965f,0.0151571f},{-0.0687985f,-0.207048f,-0.221607f}, +{-0.11216f,-0.187557f,-0.147654f},{-0.125125f,-0.188746f,-0.129906f},{0.136224f,-0.0950966f,-0.185917f}, +{-0.159561f,0.0490338f,-0.0826726f},{-0.377965f,-0.167352f,-0.077065f},{-0.39329f,-0.17031f,-0.0587762f}, +{-0.408022f,-0.171068f,-0.038436f},{-0.418806f,-0.170213f,-0.0255233f},{-0.427141f,-0.169647f,-0.0158773f}, +{-0.435854f,-0.168728f,0.000495179f},{-0.437925f,-0.172734f,0.0198257f},{-0.447076f,-0.167744f,0.0390856f}, +{-0.448728f,-0.167544f,0.0583583f},{-0.449712f,-0.167326f,0.0776695f},{-0.450297f,-0.167146f,0.0969229f}, +{-0.449378f,-0.167216f,0.116215f},{-0.447352f,-0.167255f,0.135494f},{-0.444323f,-0.165081f,0.153101f}, +{-0.437745f,-0.172175f,0.158059f},{-0.437012f,-0.167917f,0.170966f},{-0.128308f,0.265824f,-0.0182373f}, +{-0.376872f,0.270306f,-0.0919713f},{-0.345265f,0.336864f,-0.291669f},{-0.0700781f,-0.189023f,-0.223221f}, +{-0.080573f,-0.185692f,-0.212334f},{-0.0877818f,-0.185081f,-0.202456f},{-0.096013f,-0.184386f,-0.189428f}, +{-0.10144f,-0.183943f,-0.179705f},{-0.104682f,-0.183647f,-0.166747f},{-0.146892f,0.0471175f,-0.0806276f}, +{-0.107704f,0.0503778f,-0.0712196f},{-0.0904633f,0.050918f,-0.071869f},{-0.445179f,-0.163641f,0.0197807f}, +{-0.441044f,0.20481f,0.0434584f},{-0.463165f,0.257715f,0.0824154f},{-0.436195f,0.124215f,-0.00681005f}, +{-0.463442f,0.258603f,0.168477f},{-0.0122279f,0.490132f,-0.127771f},{0.192447f,-0.0923057f,-0.150741f}, +{-0.3861f,-0.159468f,-0.0706472f},{-0.397045f,-0.146979f,-0.0587698f},{-0.403296f,-0.153828f,-0.0512009f}, +{-0.41109f,-0.150014f,-0.0392527f},{-0.42851f,-0.152034f,-0.0189962f},{-0.438639f,-0.152702f,-0.00389053f}, +{-0.442375f,-0.149513f,0.00473942f},{-0.449436f,-0.150426f,0.0197421f},{-0.456413f,-0.15069f,0.0377351f}, +{-0.456812f,-0.153821f,0.0583325f},{-0.458169f,-0.153641f,0.0776052f},{-0.456612f,-0.153764f,0.0968458f}, +{-0.455628f,-0.153757f,0.116144f},{-0.455841f,-0.151474f,0.137005f},{-0.4526f,-0.149384f,0.154612f}, +{-0.457108f,0.24904f,0.0697727f},{-0.435938f,0.174599f,0.0144369f},{-0.0689142f,-0.16804f,-0.222166f}, +{-0.0786438f,-0.167577f,-0.212385f},{-0.0840327f,-0.167126f,-0.202559f},{-0.0928748f,-0.166419f,-0.186303f}, +{-0.102688f,-0.166516f,-0.165609f},{-0.111601f,-0.165641f,-0.149005f},{-0.387663f,-0.146169f,-0.0707115f}, +{-0.460053f,-0.145236f,0.0454391f},{-0.460496f,-0.145031f,0.0583261f},{-0.462857f,-0.144896f,0.0775538f}, +{-0.462085f,-0.144812f,0.0968072f},{-0.461165f,-0.144761f,0.116106f},{-0.460696f,-0.144613f,0.132137f}, +{0.20037f,0.0470274f,-0.0826468f},{-0.377708f,0.301283f,-0.134021f},{-0.0979422f,-0.161397f,-0.173499f}, +{-0.119028f,-0.15995f,-0.142497f},{0.0999871f,0.079528f,-0.0589499f},{-0.413495f,-0.129668f,-0.0401402f}, +{-0.432227f,-0.131147f,-0.0199608f},{-0.438915f,-0.133674f,-0.00619914f},{-0.441867f,-0.13098f,0.00160125f}, +{-0.448889f,-0.133185f,0.0197421f},{-0.45678f,-0.132594f,0.0357674f},{-0.46004f,-0.132388f,0.0454262f}, +{-0.462721f,-0.132111f,0.0582489f},{-0.465982f,-0.131777f,0.0775023f},{-0.466663f,-0.131616f,0.0967493f}, +{-0.466638f,-0.131533f,0.116028f},{-0.464985f,-0.131501f,0.135288f},{-0.457262f,-0.136041f,0.157654f}, +{-0.299678f,-0.473072f,-0.0419022f},{-0.066014f,-0.152439f,-0.219549f},{-0.0736086f,-0.147384f,-0.208488f}, +{-0.0819427f,-0.153545f,-0.199479f},{-0.0862963f,-0.14867f,-0.186367f},{-0.0939488f,-0.148085f,-0.166966f}, +{-0.109556f,-0.148574f,-0.150355f},{-0.221694f,-0.48738f,-0.0727243f},{-0.388229f,-0.128594f,-0.0705443f}, +{-0.396473f,-0.128941f,-0.0591492f},{-0.423617f,-0.126382f,-0.0322497f},{-0.282952f,0.025549f,-0.0794444f}, +{-0.439114f,0.213549f,0.0500113f},{0.213347f,0.0806791f,-0.0397029f},{-0.402633f,-0.123334f,-0.0517282f}, +{0.279788f,-0.0226423f,-0.084949f},{0.00770069f,-0.0975017f,-0.202469f},{-0.463043f,0.028475f,0.0439793f}, +{-0.438684f,0.159937f,0.012154f},{-0.0821935f,-0.135037f,-0.183261f},{0.112443f,0.0452011f,-0.0996109f}, +{-0.440008f,0.0363911f,0.0118517f},{0.0788109f,-0.0764541f,-0.171068f},{-0.0513071f,0.0541204f,-0.0704157f}, +{-0.0725218f,0.0608791f,-0.0584676f},{0.138854f,-0.0875727f,-0.173519f},{0.262078f,-0.0821195f,-0.124337f}, +{-0.0899425f,0.0356066f,-0.0881129f},{0.26658f,-0.0933218f,-0.125514f},{0.276104f,-0.0914312f,-0.121025f}, +{0.401579f,-0.301399f,-0.00446929f},{0.289235f,-0.0925565f,-0.113353f},{-0.0525868f,0.045259f,-0.0791357f}, +{0.0232886f,-0.079618f,-0.172843f},{0.0404006f,-0.114704f,-0.220147f},{-0.127575f,0.0222308f,-0.0996624f}, +{-0.091055f,-0.415993f,-0.167731f},{0.461493f,-0.258995f,0.0334973f},{0.0801485f,0.0388862f,-0.0953474f}, +{0.0859811f,0.0112665f,-0.121064f},{0.211128f,-0.0580817f,-0.13316f},{-0.0155011f,0.0200122f,-0.109476f}, +{0.13569f,0.0984984f,-0.0425517f},{0.209334f,0.0733224f,-0.0531558f},{0.37383f,-0.297662f,-0.0146426f}, +{0.174473f,-0.0865953f,-0.153281f},{-0.0290248f,-0.00725378f,-0.126877f},{0.264291f,0.0159287f,-0.0684544f}, +{0.30427f,-0.0999389f,-0.105508f},{0.312405f,-0.103913f,-0.101238f},{0.325838f,-0.111083f,-0.093624f}, +{-0.0629594f,-0.448423f,-0.178367f},{0.153825f,0.00549181f,-0.117688f},{0.0600527f,0.0406289f,-0.0930709f}, +{0.193785f,-0.0587441f,-0.134587f},{0.232806f,0.00174273f,-0.0917398f},{0.176403f,0.0629755f,-0.0804797f}, +{0.440729f,-0.274448f,0.0136845f},{0.185399f,0.0156072f,-0.10664f},{0.32742f,-0.0140767f,-0.0403781f}, +{0.119909f,-0.0675284f,-0.140188f},{-0.120462f,-0.417369f,-0.160014f},{-0.184846f,0.0625961f,-0.0775216f}, +{0.334127f,-0.108717f,-0.0832449f},{0.339111f,-0.118543f,-0.0857078f},{0.348841f,-0.12961f,-0.0804411f}, +{-0.278059f,0.0395357f,-0.0715282f},{0.212832f,0.054352f,-0.0712839f},{0.303788f,0.0003923f,-0.0545191f}, +{-0.0927848f,-0.471593f,-0.185357f},{0.438767f,-0.321842f,-0.0139288f},{0.0999292f,0.0890518f,-0.0499019f}, +{-0.279441f,-0.470139f,-0.048963f},{0.0730555f,0.0125012f,-0.122363f},{0.00391304f,0.0395164f,-0.0928394f}, +{-0.260947f,0.0601653f,-0.0610141f},{-0.205894f,-0.449568f,-0.0642744f},{0.19574f,0.0603518f,-0.0749622f}, +{0.119163f,-0.094087f,-0.189229f},{-0.0131282f,-0.0585062f,-0.143738f},{0.250722f,-0.165963f,-0.157017f}, +{-0.260921f,0.0503457f,-0.0701842f},{0.268876f,-0.184875f,-0.148606f},{-0.105344f,0.0338253f,-0.0893669f}, +{0.455017f,-0.300942f,0.00419281f},{0.156976f,-0.110363f,-0.188618f},{-0.074033f,0.0204431f,-0.097939f}, +{-0.10908f,-0.418257f,-0.165628f},{0.273441f,-0.298261f,-0.137044f},{-0.109234f,-0.428674f,-0.175023f}, +{0.406022f,-0.243195f,0.03742f},{0.00558501f,-0.131558f,-0.23043f},{0.025385f,-0.152947f,-0.238134f}, +{0.41743f,-0.248706f,0.0421723f},{0.0629336f,0.012572f,-0.12242f},{0.0616732f,0.0293945f,-0.101669f}, +{0.0579434f,0.0198579f,-0.113565f},{0.269679f,0.00122184f,-0.0735732f},{0.0437703f,-0.146934f,-0.230874f}, +{-0.242703f,0.0852513f,-0.042333f},{-0.186017f,-0.00108035f,-0.107206f},{-0.0730748f,0.0450918f,-0.0788142f}, +{0.264979f,-0.303926f,-0.144748f},{-0.0814283f,0.433549f,-0.0899842f},{0.420144f,-0.274981f,0.0141282f}, +{0.00217032f,-0.46875f,-0.16359f},{0.0806758f,-0.0558438f,-0.1386f},{0.230292f,-0.165487f,-0.167686f}, +{-0.0914344f,-0.485078f,-0.183917f},{0.079737f,0.0510144f,-0.0862352f},{0.059519f,0.0513617f,-0.086531f}, +{-0.0636282f,0.435066f,-0.105038f},{-0.0721102f,0.311488f,-0.0673805f},{0.0599562f,-0.0869232f,-0.19056f}, +{-0.0704897f,-0.490711f,-0.165313f},{0.1921f,0.0920035f,-0.034687f},{0.17421f,-0.0966528f,-0.167056f}, +{0.321433f,0.0167261f,-0.0155172f},{0.0605029f,-0.0743577f,-0.167403f},{0.0802386f,0.0808591f,-0.0602617f}, +{0.0422526f,0.0701264f,-0.0677534f},{-0.127317f,0.0551558f,-0.0697598f},{0.156667f,-0.0688402f,-0.134427f}, +{0.287216f,-0.00226358f,-0.0682293f},{0.137787f,0.0655992f,-0.0798302f},{0.0426642f,-0.0758882f,-0.168644f}, +{0.0249027f,0.0505129f,-0.0853477f},{-0.0142793f,0.0467831f,-0.0810392f},{-0.112752f,0.40072f,-0.0819266f}, +{-0.0764188f,0.420405f,-0.10026f},{-0.148532f,0.021099f,-0.0983505f},{-0.0322337f,0.0449889f,-0.0789685f}, +{0.0228963f,0.47257f,-0.16847f},{0.344397f,-0.112003f,-0.0732838f},{0.334835f,-0.0549564f,-0.0632262f}, +{0.00402235f,-0.0809749f,-0.165081f},{0.0234044f,-0.0553422f,-0.143532f},{0.193618f,0.0389377f,-0.0904022f}, +{-0.0916209f,-0.427208f,-0.176412f},{-0.292843f,0.400572f,-0.387647f},{-0.277351f,-0.432546f,-0.0328735f}, +{0.0236101f,-0.130761f,-0.229671f},{0.0407672f,0.0189962f,-0.109791f},{0.232697f,-0.13069f,-0.155558f}, +{-0.0537443f,0.0202373f,-0.097791f},{-0.255513f,-0.419279f,-0.0314331f},{0.17466f,0.0740234f,-0.0699334f}, +{-0.270593f,0.296801f,0.0237677f},{-0.176981f,0.35514f,-0.0475869f},{-0.0332369f,0.0160252f,-0.107681f}, +{0.341156f,-0.0549757f,-0.0554966f},{0.171097f,0.0880808f,-0.0495739f},{0.322231f,-0.020308f,-0.0531044f}, +{-0.0737501f,-0.486299f,-0.181094f},{-0.0327803f,-0.0931031f,-0.173191f},{0.258316f,0.0655092f,-0.025459f}, +{0.244568f,-0.00190989f,-0.0878557f},{0.0420533f,-0.0569692f,-0.145031f},{-0.033552f,-0.0750973f,-0.145648f}, +{-0.299814f,0.281284f,0.0215106f},{-0.279737f,0.283412f,0.0196843f},{-0.209373f,0.327385f,-0.0267129f}, +{0.196408f,0.0842545f,-0.0456062f},{-0.0161635f,-0.0777531f,-0.149191f},{-0.2435f,-0.47057f,-0.0622038f}, +{0.00624094f,-0.0729365f,-0.149339f},{0.0225748f,-0.0716054f,-0.151667f},{0.151915f,-0.0584418f,-0.128607f}, +{0.302399f,-0.0201666f,-0.0695926f},{0.235443f,-0.146613f,-0.15968f},{0.202723f,0.00639853f,-0.103077f}, +{-0.0153082f,-0.470872f,-0.168438f},{0.022652f,0.0167455f,-0.107334f},{-0.219732f,0.0707759f,-0.0663708f}, +{0.0407607f,-0.0696055f,-0.151082f},{0.0616411f,-0.0680557f,-0.152548f},{-0.279319f,-0.487778f,-0.0510337f}, +{-0.293659f,-0.489888f,-0.0501784f},{0.00330855f,0.0187904f,-0.109469f},{0.0112504f,0.0501527f,-0.0848847f}, +{-0.30555f,0.276789f,0.00826342f},{-0.296746f,0.274049f,0.0108936f},{-0.283763f,0.27257f,0.0122118f}, +{-0.280612f,0.265593f,0.00686153f},{0.26386f,-0.137719f,-0.142786f},{0.269647f,-0.0361596f,-0.0978554f}, +{0.260156f,-0.0403396f,-0.104575f},{-0.203038f,0.0361982f,-0.0896177f},{0.417604f,-0.298164f,-0.00448858f}, +{-0.205939f,-0.073959f,-0.131282f},{-0.0925919f,-0.436269f,-0.180843f},{-0.0775634f,-0.436153f,-0.17975f}, +{0.116546f,0.024758f,-0.115083f},{0.20718f,-0.00464937f,-0.10401f},{0.285299f,-0.0771229f,-0.109115f}, +{-0.0350568f,0.0268866f,-0.0985177f},{-0.0529211f,-0.469638f,-0.17683f},{0.115234f,-0.073078f,-0.15377f}, +{0.192241f,-0.0755088f,-0.13833f},{0.0680782f,-0.0558631f,-0.140079f},{0.134211f,-0.0758111f,-0.150696f}, +{-0.129465f,-0.447863f,-0.175904f},{-0.110122f,-0.437568f,-0.179467f},{-0.146391f,-0.399132f,-0.0756053f}, +{-0.090174f,0.0221536f,-0.0997267f},{0.296984f,-0.0316839f,-0.0818816f},{0.0440661f,0.0283978f,-0.100518f}, +{0.153336f,-0.0782933f,-0.147989f},{0.173522f,0.02861f,-0.10127f},{-0.245719f,0.0662872f,-0.0612649f}, +{-0.279467f,0.248005f,-0.0012411f},{-0.260773f,0.24596f,-0.000218624f},{0.321485f,-0.129147f,-0.106768f}, +{0.023713f,-0.114099f,-0.220867f},{0.24413f,-0.0576059f,-0.124697f},{-0.0915759f,-0.406855f,-0.157082f}, +{-0.296058f,-0.270261f,0.234983f},{0.00615734f,0.0945629f,-0.0194463f},{0.168081f,-0.0794766f,-0.144581f}, +{0.252497f,0.00464294f,-0.0807627f},{0.24822f,0.0340825f,-0.0687502f},{0.313916f,0.00494521f,-0.0416064f}, +{0.0403749f,-0.469702f,-0.154934f},{-0.293164f,0.254763f,-0.00702226f},{0.0280087f,-0.0414585f,-0.142111f}, +{0.0618211f,0.0931031f,-0.0507122f},{-0.17102f,0.00729881f,-0.103051f},{-0.25896f,0.0220122f,-0.0907302f}, +{-0.0683676f,-0.430282f,-0.176702f},{0.143471f,0.0490145f,-0.0974759f},{0.154763f,0.0894312f,-0.0509244f}, +{-0.245468f,-0.429954f,-0.0434134f},{-0.0755121f,-0.0124948f,-0.121404f},{-0.261712f,-0.45576f,-0.0506865f}, +{0.188447f,0.0503714f,-0.0862159f},{-0.0160284f,0.0915083f,-0.0186553f},{-0.288245f,0.242037f,-0.0126362f}, +{0.439166f,-0.300878f,-0.00482298f},{-0.0333334f,0.0365197f,-0.0880615f},{0.317967f,-0.0967814f,-0.0894826f}, +{0.0200926f,0.0669368f,-0.0639915f},{-0.281396f,-0.450725f,-0.0420436f},{0.161168f,-0.0859651f,-0.159204f}, +{-0.013334f,-0.0866789f,-0.168876f},{-0.260336f,-0.435716f,-0.0411498f},{-0.299878f,-0.48938f,-0.0368348f}, +{0.292592f,-0.109083f,-0.117334f},{-0.275538f,0.230571f,-0.006212f},{0.331504f,-0.140124f,-0.101926f}, +{0.305942f,-0.112993f,-0.110987f},{-0.242639f,-0.437381f,-0.049381f},{0.317446f,-0.113977f,-0.103482f}, +{-0.0706248f,-0.414482f,-0.167506f},{0.0406385f,0.0091251f,-0.118633f},{0.252342f,-0.320954f,-0.156136f}, +{0.331214f,-0.127102f,-0.098209f},{-0.286836f,0.4f,-0.387441f},{-0.00711554f,-0.0828655f,-0.163146f}, +{0.323568f,-0.148651f,-0.108871f},{-0.205218f,0.0724285f,-0.0683129f},{-0.223134f,-0.454969f,-0.0629433f}, +{0.38266f,-0.335706f,-0.0232275f},{-0.27431f,0.21917f,-0.00699654f},{-0.260381f,0.211685f,-0.00508663f}, +{0.390312f,-0.260422f,0.0117103f},{0.337832f,-0.0386418f,-0.05059f},{0.117459f,0.0530465f,-0.0919584f}, +{0.0412752f,-0.0419022f,-0.14186f},{-0.227938f,0.0485451f,-0.0817144f},{0.0407221f,-0.1673f,-0.236796f}, +{-0.194158f,-0.250577f,0.273869f},{-0.184525f,0.0924215f,-0.0508601f},{0.0057715f,-0.0839266f,-0.174837f}, +{0.0450371f,0.0886145f,-0.0488473f},{0.0313655f,0.0688788f,-0.0662808f},{0.160294f,0.06794f,-0.079335f}, +{-0.299466f,-0.441446f,-0.0306742f},{0.11807f,-0.0567377f,-0.135372f},{0.176113f,-0.472351f,-0.135089f}, +{-0.126842f,0.0658114f,-0.0616507f},{0.1534f,-0.100363f,-0.182406f},{0.245191f,-0.114536f,-0.141526f}, +{-0.202826f,-0.490389f,-0.073779f},{-0.267294f,0.205112f,-0.012932f},{-0.240606f,0.205492f,0.00168485f}, +{0.327459f,-0.0332272f,-0.058564f},{0.235661f,0.0680685f,-0.041034f},{0.0310183f,-0.140105f,-0.232719f}, +{0.046066f,-0.16876f,-0.235111f},{-0.205585f,0.0910453f,-0.0491688f},{0.0606829f,-0.108337f,-0.213601f}, +{0.0407607f,-0.107411f,-0.2147f},{0.0204913f,-0.107128f,-0.215125f},{0.00281983f,-0.105373f,-0.210572f}, +{0.00645958f,-0.114749f,-0.220302f},{-0.147851f,-0.488614f,-0.17031f},{-0.123697f,-0.451297f,-0.179313f}, +{0.204485f,0.089592f,-0.0321211f},{0.11699f,0.0893798f,-0.0503521f},{0.134745f,0.0903315f,-0.0517154f}, +{0.440002f,-0.250082f,0.0402109f},{0.459384f,-0.251735f,0.0420758f},{-0.259615f,0.192746f,-0.0167261f}, +{-0.241024f,0.190868f,-0.00066877f},{-0.0119771f,-0.0980033f,-0.198862f},{0.0374296f,-0.153371f,-0.236552f}, +{0.276483f,-0.150227f,-0.137668f},{-0.111543f,-0.408559f,-0.15114f},{0.289222f,-0.204649f,-0.134684f}, +{0.271126f,-0.203061f,-0.148297f},{0.419784f,-0.320067f,-0.0175557f},{0.466393f,-0.311077f,0.0125913f}, +{0.103788f,-0.127289f,-0.213157f},{0.0621813f,-0.0796309f,-0.178985f},{0.213032f,-0.148741f,-0.175615f}, +{-0.276123f,0.19364f,-0.0243014f},{-0.237057f,0.175345f,-0.000983873f},{-0.051217f,-0.0594772f,-0.143346f}, +{-0.0341115f,-0.0593293f,-0.144195f},{-0.195013f,0.140182f,0.0354008f},{-0.0339764f,-0.483142f,-0.170406f}, +{0.267313f,-0.151204f,-0.143571f},{0.382132f,-0.321662f,-0.0154464f},{0.40372f,-0.320575f,-0.0174657f}, +{0.157876f,-0.12471f,-0.195923f},{-0.0725218f,-0.447722f,-0.179679f},{-0.0453651f,-0.133153f,-0.221298f}, +{-0.0336806f,-0.13051f,-0.223787f},{0.205791f,-0.147436f,-0.179486f},{-0.243204f,-0.489933f,-0.0578631f}, +{0.235147f,-0.0296067f,-0.103167f},{-0.247056f,0.172014f,-0.0121411f},{-0.221713f,0.154066f,-2.57039e-05f}, +{-0.201141f,0.135912f,0.0175429f},{0.288097f,-0.18638f,-0.132729f},{-0.241725f,-0.454281f,-0.0580045f}, +{-0.223064f,-0.473014f,-0.0674833f},{-0.0658854f,-0.465335f,-0.178657f},{0.252683f,-0.304164f,-0.154522f}, +{0.100559f,-0.0970258f,-0.199235f},{0.172474f,-0.114562f,-0.183679f},{0.0369409f,0.0861901f,-0.0460435f}, +{0.362075f,-0.339404f,-0.0242629f},{-0.0222147f,-0.0861901f,-0.159821f},{-0.233565f,0.161043f,-0.00777465f}, +{-0.213115f,0.148137f,0.00656572f},{0.0732227f,-0.117109f,-0.217337f},{0.18628f,-0.115874f,-0.178978f}, +{-0.123922f,-0.486454f,-0.18665f},{0.398839f,-0.335353f,-0.0244236f},{0.12106f,-0.0842031f,-0.173937f}, +{-0.296405f,0.0342433f,-0.0656313f},{-0.256805f,0.155757f,-0.0229317f},{-0.240356f,0.151571f,-0.0168869f}, +{-0.228626f,0.151365f,-0.0101476f},{-0.215733f,0.136684f,-0.00267514f},{-0.207392f,0.130748f,0.00387771f}, +{0.278091f,-0.176985f,-0.140973f},{0.00451752f,0.111064f,0.0211183f},{0.0224784f,0.115508f,0.018546f}, +{-0.279962f,-0.294164f,0.208308f},{0.317472f,0.0347706f,0.00812194f},{-0.226047f,0.135121f,-0.0132664f}, +{-0.202601f,0.121314f,7.07559e-05f},{-0.184576f,0.116607f,-0.000681631f},{-0.0169223f,0.103913f,0.0169963f}, +{-0.0125623f,0.107128f,0.0231697f},{0.040735f,0.11462f,0.00070096f},{0.060207f,0.119263f,0.00183276f}, +{0.0800906f,0.121507f,0.000392289f},{0.0970676f,0.121469f,-0.00239862f},{0.116057f,0.122093f,-0.00194847f}, +{0.136745f,0.122343f,-0.00261726f},{0.15668f,0.121314f,-0.00171054f},{0.175811f,0.118646f,0.000971048f}, +{0.194151f,0.112221f,-0.000225055f},{0.134372f,0.00556251f,-0.120093f},{-0.115472f,0.0872769f,-0.0327256f}, +{-0.114977f,0.0688852f,-0.0518761f},{-0.0382078f,-0.103366f,-0.193608f},{0.271293f,-0.0128099f,-0.0826275f}, +{-0.252992f,0.138716f,-0.0265072f},{-0.242188f,0.134099f,-0.0231053f},{-0.215167f,0.124703f,-0.00695796f}, +{0.00827945f,0.108106f,0.00857852f},{0.022832f,0.109945f,0.00102249f},{0.212755f,0.107791f,0.00616702f}, +{0.230613f,0.0964599f,0.00186491f},{0.22543f,-0.132883f,-0.161088f},{0.299415f,0.0143018f,-0.0474454f}, +{0.356455f,-0.0437028f,-0.0169641f},{-0.0465548f,0.0114209f,-0.107849f},{0.0974341f,0.052217f,-0.0876628f}, +{-0.221945f,0.114665f,-0.0193305f},{-0.202511f,0.112221f,-0.0181023f},{-0.186158f,0.112742f,-0.0161152f}, +{-0.239404f,-0.484917f,-0.0675219f},{-0.029102f,-0.117887f,-0.217536f},{0.406305f,-0.255284f,0.0327835f}, +{0.419893f,-0.255754f,0.033208f},{-0.0154754f,0.0985306f,-0.000263639f},{0.00136006f,0.101585f,-0.00249508f}, +{0.043005f,0.109611f,-0.0146876f},{0.0620076f,0.11327f,-0.016456f},{0.0792418f,0.113746f,-0.0197807f}, +{0.0978779f,0.113392f,-0.0183852f},{0.117909f,0.112839f,-0.0178644f},{0.137471f,0.114331f,-0.018501f}, +{0.155722f,0.113064f,-0.0185975f},{0.174686f,0.110357f,-0.0158837f},{0.21527f,0.101457f,-0.0032539f}, +{0.462657f,-0.297823f,0.0121604f},{0.440478f,-0.255458f,0.0329186f},{0.240201f,-0.104659f,-0.139391f}, +{-0.169046f,0.108524f,-0.0214012f},{-0.149214f,0.106916f,-0.0149063f},{0.0234172f,0.101907f,-0.01753f}, +{0.184396f,0.106157f,-0.0178579f},{0.193778f,0.101367f,-0.0190926f},{-0.133767f,-0.465335f,-0.178547f}, +{0.186409f,-0.0962027f,-0.161062f},{-0.261165f,-0.490434f,-0.0584804f},{-0.070599f,-0.397383f,-0.151481f}, +{-0.247622f,0.108164f,-0.0284299f},{-0.228555f,0.105804f,-0.0256197f},{-0.186595f,0.107688f,-0.0281663f}, +{-0.160519f,0.105386f,-0.025922f},{-0.147664f,0.103579f,-0.0243207f},{-0.0743545f,0.0868204f,-0.00658498f}, +{-0.0456031f,0.0891804f,-0.00969099f},{0.0395839f,0.103418f,-0.0260313f},{0.0621041f,0.107341f,-0.0307257f}, +{0.0794476f,0.107984f,-0.0316067f},{0.0958008f,0.106614f,-0.030282f},{0.126276f,0.106267f,-0.0302498f}, +{0.136899f,0.107109f,-0.0312851f},{0.150571f,0.106961f,-0.0313816f},{0.178949f,0.10244f,-0.0266872f}, +{0.150924f,-0.486299f,-0.125578f},{0.134089f,-0.486807f,-0.128459f},{0.118392f,-0.486319f,-0.133185f}, +{0.0979808f,-0.483766f,-0.142265f},{0.0787209f,-0.484049f,-0.14442f},{0.059982f,-0.483933f,-0.146986f}, +{0.0217517f,-0.486319f,-0.150915f},{0.00719267f,-0.489219f,-0.149513f},{-0.0549082f,-0.482345f,-0.175068f}, +{0.199855f,-0.476094f,-0.120543f},{0.157419f,-0.473052f,-0.139314f},{0.136558f,-0.470429f,-0.144278f}, +{0.117253f,-0.470191f,-0.1475f},{0.0979615f,-0.470023f,-0.149558f},{0.0786759f,-0.469998f,-0.151043f}, +{-0.22442f,-0.0713611f,-0.131063f},{0.0219896f,-0.470287f,-0.158329f},{-0.0307547f,-0.468165f,-0.172586f}, +{-0.324398f,-0.270171f,0.217665f},{0.0275329f,0.382361f,-0.144728f},{-0.0119193f,0.384026f,-0.123764f}, +{-0.243462f,-0.0706344f,-0.1266f},{-0.0151732f,0.39838f,-0.130722f},{-0.0697823f,0.398154f,-0.107617f}, +{-0.260908f,-0.0702357f,-0.11772f},{-0.276946f,-0.0708402f,-0.111238f},{-0.204202f,0.277444f,-0.0249509f}, +{-0.316591f,-0.489946f,-0.0348027f},{-0.321048f,-0.491084f,-0.0186618f},{0.152622f,-0.465959f,-0.142356f}, +{0.0141957f,-0.464461f,-0.161101f},{-0.29283f,-0.0713353f,-0.105617f},{-0.302418f,-0.0715925f,-0.101791f}, +{-0.315376f,-0.0719334f,-0.0965306f},{-0.334584f,-0.0724543f,-0.0895598f},{-0.112591f,0.302691f,-0.0615093f}, +{-0.349574f,-0.0743063f,-0.0856757f},{-0.358185f,-0.0715411f,-0.0805247f},{0.00820872f,0.399987f,-0.144671f}, +{-0.178859f,0.320749f,-0.0490531f},{-0.104971f,0.307752f,-0.0656828f},{-0.0410694f,0.441092f,-0.121565f}, +{-0.319614f,-0.474319f,-0.0342883f},{-0.333967f,-0.471162f,-0.0213819f},{-0.348937f,-0.484627f,0.00558826f}, +{0.265435f,-0.471522f,0.00476514f},{-0.0139835f,0.35932f,-0.107906f},{-0.0550175f,0.357673f,-0.0990772f}, +{0.194409f,-0.452731f,-0.134549f},{0.17511f,-0.452397f,-0.138684f},{0.162223f,-0.452275f,-0.140079f}, +{0.152564f,-0.452101f,-0.141938f},{0.136455f,-0.451857f,-0.144909f},{0.117151f,-0.451567f,-0.1486f}, +{0.0978714f,-0.451336f,-0.151744f},{0.0785859f,-0.45113f,-0.154561f},{0.0592939f,-0.451008f,-0.156516f}, +{0.0399955f,-0.450892f,-0.158567f},{0.0270891f,-0.455387f,-0.159744f},{0.0193981f,-0.449664f,-0.162477f}, +{0.00133434f,-0.450146f,-0.167326f},{-0.0147359f,-0.449799f,-0.171467f},{-0.0307868f,-0.449561f,-0.174721f}, +{-0.0500788f,-0.44942f,-0.176952f},{-0.242336f,-0.482878f,0.298177f},{-0.319247f,0.126562f,-0.0472203f}, +{-0.431095f,0.0555095f,-0.0061284f},{-0.287203f,-0.468345f,-0.044796f},{-0.38992f,0.13053f,-0.0323076f}, +{-0.349137f,-0.474191f,0.00606413f},{0.324231f,-0.464403f,0.0193563f},{0.323909f,-0.455207f,0.00261087f}, +{0.305087f,-0.4565f,-0.00248222f},{0.28811f,-0.462313f,-0.00134399f},{0.278348f,-0.461175f,-0.00605123f}, +{0.00435675f,0.360104f,-0.113617f},{-0.0341886f,0.364934f,-0.107283f},{-0.318012f,0.189827f,-0.0402752f}, +{-0.274509f,-0.458886f,-0.0477541f},{-0.300212f,-0.455368f,-0.037285f},{-0.316141f,-0.456108f,-0.0309186f}, +{0.297441f,-0.450699f,-0.00994178f},{0.287891f,-0.450957f,-0.0121411f},{-0.399508f,0.13024f,-0.0256133f}, +{0.200846f,-0.438417f,-0.139552f},{0.193019f,-0.432301f,-0.144613f},{0.175001f,-0.433189f,-0.146098f}, +{0.155702f,-0.433298f,-0.145513f},{0.136423f,-0.433311f,-0.146619f},{0.117144f,-0.433201f,-0.148548f}, +{0.0978393f,-0.432976f,-0.151384f},{0.0785666f,-0.432841f,-0.155043f},{0.0592167f,-0.432655f,-0.156818f}, +{0.039944f,-0.432616f,-0.158156f},{0.0271084f,-0.432443f,-0.159699f},{0.0174431f,-0.432198f,-0.162393f}, +{0.00134721f,-0.431812f,-0.166644f},{-0.014768f,-0.43144f,-0.170869f},{-0.0308511f,-0.431214f,-0.173673f}, +{-0.0501495f,-0.431182f,-0.175242f},{-0.0301823f,0.399897f,-0.12462f},{-0.268213f,-0.449658f,-0.046365f}, +{-0.33625f,0.00163985f,-0.0621202f},{0.345535f,-0.441259f,-0.00639849f},{0.34068f,-0.43333f,-0.0138259f}, +{0.323073f,-0.435671f,-0.0155429f},{0.305042f,-0.436352f,-0.0183467f},{0.289531f,-0.43389f,-0.0237677f}, +{0.274978f,-0.440031f,-0.0245394f},{0.207283f,-0.428874f,-0.142137f},{-0.277486f,0.0933989f,-0.0426095f}, +{-0.322244f,-0.0305328f,-0.0829812f},{-0.223347f,-0.434533f,-0.0549436f},{-0.259757f,-0.0169448f,-0.105681f}, +{-0.0928941f,0.310215f,-0.0684094f},{-0.166165f,0.340118f,-0.0569049f},{-0.188499f,0.320652f,-0.0430661f}, +{0.381329f,-0.429542f,0.00839203f},{0.355034f,-0.431163f,-0.0114594f},{0.219919f,-0.41499f,-0.139725f}, +{0.210324f,-0.414636f,-0.144092f},{0.194286f,-0.414263f,-0.149654f},{0.174924f,-0.414083f,-0.152683f}, +{0.155638f,-0.414045f,-0.154741f},{0.136391f,-0.418938f,-0.155474f},{0.117054f,-0.41861f,-0.158818f}, +{0.104251f,-0.418964f,-0.159448f},{0.0964117f,-0.411627f,-0.171467f},{0.0784058f,-0.417138f,-0.171377f}, +{0.0591203f,-0.417003f,-0.173808f},{0.0398283f,-0.417382f,-0.171075f},{0.0205492f,-0.417742f,-0.169647f}, +{0.00121859f,-0.412887f,-0.172342f},{-0.0148709f,-0.413151f,-0.169705f},{-0.0309347f,-0.413254f,-0.168895f}, +{-0.050201f,-0.413459f,-0.16784f},{-0.376846f,0.0291824f,0.336182f},{-0.0667085f,-0.0604096f,-0.141764f}, +{-0.0752677f,-0.0574966f,-0.139732f},{-0.0910935f,-0.0558696f,-0.135616f},{-0.10962f,-0.0549243f,-0.132491f}, +{-0.05377f,0.417266f,-0.115745f},{-0.128083f,-0.054069f,-0.128986f},{0.377477f,-0.422443f,-0.00421206f}, +{0.361381f,-0.416212f,-0.0185396f},{0.343838f,-0.418379f,-0.0225073f},{0.322964f,-0.420443f,-0.025041f}, +{0.310077f,-0.420385f,-0.0269252f},{0.136333f,-0.408276f,-0.167114f},{0.11697f,-0.407775f,-0.172985f}, +{0.0783544f,-0.407157f,-0.180187f},{0.059056f,-0.407177f,-0.181647f},{0.0397575f,-0.40746f,-0.180001f}, +{0.0204591f,-0.407492f,-0.179583f},{-0.372737f,0.188187f,-0.0315874f},{-0.396814f,-0.245272f,0.151635f}, +{0.0300472f,0.402907f,-0.161988f},{-0.185033f,-0.415022f,-0.0589884f},{-0.20417f,-0.415749f,-0.0533873f}, +{-0.219167f,-0.417845f,-0.0483007f},{-0.227629f,-0.415363f,-0.0426931f},{-0.380833f,-0.271683f,0.093952f}, +{-0.0345552f,0.417343f,-0.126768f},{0.380705f,-0.411922f,-0.0144947f},{0.339015f,-0.410701f,-0.028102f}, +{0.3229f,-0.410675f,-0.0285714f},{-0.251223f,-0.065387f,-0.121064f},{0.250477f,-0.395171f,-0.128118f}, +{0.234034f,-0.397981f,-0.133552f},{0.219848f,-0.400971f,-0.140954f},{0.212215f,-0.39481f,-0.148793f}, +{0.194312f,-0.399916f,-0.156046f},{0.174834f,-0.394283f,-0.166696f},{0.155542f,-0.394161f,-0.168837f}, +{0.13742f,-0.394341f,-0.175608f},{0.1169f,-0.392913f,-0.18292f},{0.0975756f,-0.39274f,-0.185345f}, +{0.0782643f,-0.392868f,-0.186013f},{0.0589916f,-0.392888f,-0.186933f},{0.0396804f,-0.392952f,-0.187254f}, +{0.0203884f,-0.392933f,-0.187499f},{0.00426672f,-0.393312f,-0.1851f},{-0.0102666f,-0.391357f,-0.18229f}, +{-0.0150188f,-0.398688f,-0.177518f},{-0.0311083f,-0.394534f,-0.172586f},{-0.0471593f,-0.395293f,-0.164104f}, +{-0.0565931f,-0.395942f,-0.15894f},{-0.153072f,0.320954f,-0.0621009f},{-0.107093f,0.396296f,-0.0868846f}, +{-0.167715f,0.302543f,-0.0497283f},{-0.185103f,0.30145f,-0.0428025f},{-0.108058f,0.345346f,-0.0850583f}, +{0.398556f,-0.396341f,-0.019292f},{0.382325f,-0.399434f,-0.0228417f},{0.364673f,-0.401614f,-0.0251117f}, +{0.359734f,-0.394f,-0.0299476f},{0.342192f,-0.396418f,-0.030507f},{0.322829f,-0.396495f,-0.030925f}, +{0.226369f,-0.391762f,-0.141198f},{0.194119f,-0.389749f,-0.1642f},{0.129658f,-0.388296f,-0.181383f}, +{-0.432073f,0.00232147f,0.247658f},{-0.184923f,-0.396682f,-0.0553037f},{-0.202884f,-0.398251f,-0.0495482f}, +{-0.223424f,-0.398296f,-0.0397093f},{-0.328552f,-0.0035883f,-0.0680685f},{-0.376268f,-0.264924f,0.15395f}, +{-0.355079f,-0.00288092f,0.268069f},{-0.0902833f,0.343147f,-0.0872769f},{0.377496f,-0.39182f,-0.0286229f}, +{0.256419f,-0.379737f,-0.13725f},{0.247854f,-0.376026f,-0.146317f},{0.234047f,-0.378007f,-0.152149f}, +{0.216588f,-0.381267f,-0.154972f},{0.211746f,-0.373904f,-0.162381f},{0.194087f,-0.375679f,-0.167885f}, +{0.176081f,-0.376091f,-0.17456f},{0.155464f,-0.379595f,-0.177113f},{0.13915f,-0.379493f,-0.178136f}, +{0.134513f,-0.372406f,-0.181383f},{0.116797f,-0.374348f,-0.184927f},{0.0975113f,-0.374059f,-0.189145f}, +{0.0783351f,-0.373814f,-0.193357f},{0.0589724f,-0.378483f,-0.194155f},{0.0395968f,-0.37856f,-0.195595f}, +{0.0202984f,-0.378599f,-0.19418f},{0.0030449f,-0.375711f,-0.194624f},{-0.0119836f,-0.374612f,-0.189846f}, +{-0.0269284f,-0.373711f,-0.182599f},{-0.0354426f,-0.377313f,-0.176271f},{-0.0504003f,-0.376695f,-0.166818f}, +{-0.210794f,-0.392843f,-0.0463393f},{-0.341433f,0.0888203f,-0.0460564f},{-0.0527925f,0.340221f,-0.0890389f}, +{0.401373f,-0.380669f,-0.0243658f},{0.380589f,-0.377705f,-0.0326999f},{0.361471f,-0.377859f,-0.0330021f}, +{0.341986f,-0.37784f,-0.0325327f},{0.226298f,-0.371924f,-0.160329f},{0.168332f,-0.370245f,-0.179705f}, +{0.1554f,-0.370136f,-0.181595f},{0.0589402f,-0.368676f,-0.200817f},{0.0395839f,-0.368554f,-0.203395f}, +{0.0202212f,-0.369062f,-0.199428f},{0.00733414f,-0.36921f,-0.1989f},{-0.354249f,0.0934375f,-0.0446931f}, +{-0.398344f,-0.251227f,0.136806f},{-0.108051f,-0.377563f,-0.0586862f},{-0.127337f,-0.377679f,-0.058384f}, +{-0.146635f,-0.377589f,-0.0602488f},{-0.165844f,-0.377673f,-0.060146f},{-0.185091f,-0.378174f,-0.0551172f}, +{-0.201019f,-0.378895f,-0.0484357f},{-0.2108f,-0.379229f,-0.0450918f},{-0.360641f,-0.031954f,-0.0637793f}, +{-0.0316164f,0.341442f,-0.0910903f},{0.431693f,-0.3751f,-0.00904149f},{0.396473f,-0.373351f,-0.0290151f}, +{0.253159f,-0.359448f,-0.152561f},{0.232581f,-0.357468f,-0.165924f},{0.213289f,-0.357159f,-0.17094f}, +{0.195167f,-0.35777f,-0.17483f},{0.174718f,-0.356329f,-0.181904f},{0.155329f,-0.356021f,-0.185917f}, +{0.136025f,-0.356169f,-0.185582f},{0.116745f,-0.355873f,-0.189383f},{0.0987331f,-0.356522f,-0.192862f}, +{0.084605f,-0.359815f,-0.197273f},{0.0768625f,-0.354201f,-0.199788f},{0.0588437f,-0.354825f,-0.203505f}, +{0.0395003f,-0.354612f,-0.206887f},{0.0201762f,-0.354831f,-0.206109f},{0.00404809f,-0.355043f,-0.204772f}, +{-0.0103759f,-0.353095f,-0.200675f},{-0.0152053f,-0.360503f,-0.194618f},{-0.0312884f,-0.356683f,-0.187235f}, +{-0.030118f,-0.479399f,0.347571f},{0.0219446f,0.397531f,-0.15269f},{-0.358667f,0.110723f,-0.0458442f}, +{-0.424781f,0.0588084f,-0.0108742f},{0.433861f,-0.359384f,-0.0142889f},{0.418909f,-0.360207f,-0.0223272f}, +{0.406106f,-0.3644f,-0.026983f},{0.398486f,-0.358799f,-0.0295875f},{0.380615f,-0.359686f,-0.0323269f}, +{0.361361f,-0.359853f,-0.0312401f},{-0.0793962f,0.398103f,-0.102235f},{-0.200968f,0.251413f,-0.0134979f}, +{-0.341304f,0.0793994f,-0.0464294f},{0.245494f,-0.353307f,-0.160914f},{0.187605f,-0.351982f,-0.179139f}, +{0.0910099f,-0.350683f,-0.198199f},{-0.0441111f,-0.352735f,-0.180618f},{-0.233243f,0.281978f,-0.0114851f}, +{-0.0771647f,0.377428f,-0.102164f},{-0.108128f,-0.359358f,-0.0574708f},{-0.127408f,-0.359423f,-0.0577023f}, +{-0.146712f,-0.359242f,-0.0606604f},{-0.165985f,-0.359172f,-0.0619658f},{-0.185232f,-0.359538f,-0.0583004f}, +{-0.204511f,-0.360201f,-0.0518568f},{-0.339915f,0.269303f,-0.283605f},{0.254966f,-0.340008f,-0.156072f}, +{0.245442f,-0.339577f,-0.161442f},{0.232549f,-0.339179f,-0.166464f},{0.213154f,-0.338658f,-0.173326f}, +{0.197167f,-0.338439f,-0.176895f},{0.187438f,-0.338214f,-0.179653f},{0.174608f,-0.338008f,-0.182618f}, +{0.155297f,-0.337758f,-0.186746f},{0.135986f,-0.337481f,-0.190971f},{0.116662f,-0.341661f,-0.196309f}, +{0.103813f,-0.341803f,-0.196888f},{0.0960773f,-0.335938f,-0.200688f},{0.0780586f,-0.336568f,-0.204579f}, +{0.0587473f,-0.336491f,-0.206225f},{0.0394489f,-0.336253f,-0.20962f},{0.0217324f,-0.338349f,-0.212752f}, +{0.00291629f,-0.337629f,-0.213241f},{-0.0121379f,-0.336652f,-0.208206f},{-0.0271728f,-0.335816f,-0.201248f}, +{-0.0355584f,-0.339333f,-0.19445f},{0.43806f,-0.342523f,-0.0153435f},{0.418954f,-0.34186f,-0.0241021f}, +{0.399823f,-0.34624f,-0.0280055f},{0.38064f,-0.346413f,-0.0280827f},{0.353471f,-0.346156f,-0.0265007f}, +{-0.412408f,0.130015f,-0.0195942f},{-0.316122f,-0.255265f,0.240365f},{-0.186936f,-0.0145526f,-0.112871f}, +{-0.296907f,0.131153f,-0.0426031f},{0.116668f,-0.332388f,-0.199248f},{0.0168837f,-0.331211f,-0.217305f}, +{0.0071991f,-0.331321f,-0.217118f},{-0.375753f,0.00609625f,0.303244f},{-0.109498f,-0.341667f,-0.0609241f}, +{-0.127491f,-0.340857f,-0.0605575f},{-0.146835f,-0.340677f,-0.0623324f},{-0.164474f,-0.342812f,-0.0642037f}, +{-0.186563f,-0.341648f,-0.063059f},{-0.20453f,-0.341282f,-0.0579338f},{-0.219411f,-0.340561f,-0.0491753f}, +{-0.227919f,-0.344323f,-0.0415485f},{-0.373702f,0.11208f,-0.0423523f},{0.0166908f,0.412739f,-0.157738f}, +{-0.206177f,-0.0147069f,-0.112755f},{0.232414f,-0.321205f,-0.16431f},{0.213135f,-0.320716f,-0.171113f}, +{0.193843f,-0.320343f,-0.176708f},{0.174557f,-0.31999f,-0.182065f},{0.155259f,-0.319797f,-0.186476f}, +{0.139195f,-0.319263f,-0.192142f},{0.129401f,-0.318671f,-0.198527f},{0.116598f,-0.318369f,-0.203492f}, +{0.0972605f,-0.318086f,-0.20764f},{0.0779685f,-0.31772f,-0.212656f},{0.058638f,-0.317707f,-0.21398f}, +{0.0408508f,-0.319945f,-0.214688f},{0.0232758f,-0.317199f,-0.22189f},{0.00712193f,-0.317347f,-0.220906f}, +{-0.00791295f,-0.316041f,-0.218687f},{-0.0164593f,-0.319456f,-0.214546f},{-0.0314813f,-0.318671f,-0.2069f}, +{-0.333761f,0.0945243f,-0.0467638f},{-0.101826f,-0.33561f,-0.0671297f},{-0.169342f,-0.335777f,-0.065985f}, +{-0.178943f,-0.33588f,-0.0656699f},{-0.128867f,0.289489f,-0.0502813f},{-0.389348f,0.055143f,-0.0314395f}, +{0.0361306f,-0.312729f,-0.219633f},{0.0188064f,0.379345f,-0.136735f},{-0.114701f,-0.326723f,-0.0646731f}, +{-0.127568f,-0.326813f,-0.0641008f},{-0.143671f,-0.326723f,-0.0642037f},{-0.148539f,-0.319597f,-0.0669946f}, +{-0.166217f,-0.321642f,-0.0696376f},{-0.185386f,-0.321867f,-0.0692003f},{-0.200408f,-0.320729f,-0.0664802f}, +{-0.20889f,-0.324208f,-0.0617858f},{-0.222549f,-0.322581f,-0.0527636f},{-0.230279f,-0.328845f,-0.0446866f}, +{-0.27829f,-0.0147905f,-0.0996752f},{-0.402839f,-0.255696f,0.0866918f},{-0.156641f,0.359429f,-0.0620752f}, +{-0.259853f,0.129867f,-0.0303977f},{-0.225507f,-0.0148805f,-0.111746f},{0.232311f,-0.302794f,-0.167403f}, +{0.214324f,-0.303141f,-0.175171f},{0.19374f,-0.301759f,-0.182271f},{0.174396f,-0.301206f,-0.189023f}, +{0.15513f,-0.301013f,-0.193872f},{0.142391f,-0.305534f,-0.195479f},{0.134507f,-0.299341f,-0.202058f}, +{0.116527f,-0.299746f,-0.208643f},{0.0971769f,-0.299553f,-0.211813f},{0.081081f,-0.299264f,-0.215794f}, +{0.0714157f,-0.299135f,-0.217761f},{0.0585608f,-0.299129f,-0.218829f},{0.0392624f,-0.298923f,-0.222064f}, +{0.0231922f,-0.298633f,-0.226571f},{0.00704477f,-0.298704f,-0.226906f},{-0.0123372f,-0.299238f,-0.222308f}, +{-0.0332305f,-0.302395f,-0.212347f},{-0.148166f,-0.0508858f,-0.125301f},{-0.127607f,-0.317276f,-0.0687245f}, +{-0.242896f,-0.315733f,-0.0365133f},{-0.167451f,-0.0509308f,-0.126793f},{-0.349825f,-0.274268f,0.182084f}, +{-0.302971f,0.0889425f,-0.0468152f},{-0.18675f,-0.0509437f,-0.127417f},{-0.298257f,0.209826f,-0.0373686f}, +{-0.206067f,-0.0509694f,-0.127006f},{-0.244806f,-0.0151764f,-0.108157f},{-0.296392f,-0.0161731f,-0.0915919f}, +{-0.224112f,-0.0520305f,-0.125938f},{0.245371f,-0.298781f,-0.161191f},{0.20662f,-0.297367f,-0.179917f}, +{-0.0283432f,-0.294974f,-0.218765f},{-0.238362f,-0.0558181f,-0.124401f},{-0.24251f,-0.265773f,0.253985f}, +{-0.244851f,-0.0492267f,-0.118369f},{-0.127684f,-0.302858f,-0.0759525f},{-0.146989f,-0.302871f,-0.0744606f}, +{-0.166287f,-0.302807f,-0.0762933f},{-0.185483f,-0.303051f,-0.0754702f},{-0.203521f,-0.302871f,-0.0691617f}, +{-0.21109f,-0.308685f,-0.0650333f},{-0.223887f,-0.304826f,-0.0569242f},{-0.236465f,-0.31026f,-0.046783f}, +{-0.280856f,-0.314929f,-0.00204493f},{-0.296888f,-0.313347f,0.00282951f},{-0.143819f,0.287103f,-0.0472782f}, +{-0.258921f,-0.0529436f,-0.113276f},{-0.351143f,0.0140575f,-0.0489502f},{-0.276875f,-0.052352f,-0.110086f}, +{-0.294849f,-0.0535738f,-0.106627f},{-0.409984f,0.019472f,0.307707f},{-0.399006f,0.0548214f,-0.0255812f}, +{-0.270586f,-0.020083f,-0.103309f},{0.266541f,-0.281946f,-0.143217f},{0.255744f,-0.286988f,-0.155622f}, +{0.247262f,-0.283284f,-0.164477f},{0.23233f,-0.284312f,-0.171621f},{0.219347f,-0.283901f,-0.176425f}, +{0.209707f,-0.283611f,-0.180534f},{0.193611f,-0.283329f,-0.185049f},{0.174293f,-0.282936f,-0.190077f}, +{0.15821f,-0.282486f,-0.196109f},{0.148584f,-0.282312f,-0.198836f},{0.135748f,-0.282036f,-0.202727f}, +{0.116353f,-0.281586f,-0.208797f},{0.0971448f,-0.28147f,-0.211228f},{0.0778142f,-0.281316f,-0.214578f}, +{0.0584965f,-0.281078f,-0.218199f},{0.0391981f,-0.28075f,-0.222964f},{0.0231729f,-0.28039f,-0.228591f}, +{0.00698689f,-0.280062f,-0.232835f},{-0.0123823f,-0.280383f,-0.230764f},{-0.0317321f,-0.281168f,-0.222803f}, +{-0.315324f,-0.0533874f,-0.0960741f},{-0.238606f,-0.294839f,-0.0495289f},{-0.24714f,-0.29882f,-0.0383653f}, +{-0.261332f,-0.29655f,-0.0361339f},{-0.268985f,-0.302711f,-0.0272724f},{-0.281782f,-0.302974f,-0.0227774f}, +{-0.302392f,-0.29992f,-0.0184303f},{-0.331465f,-0.053979f,-0.086994f},{-0.298386f,-0.24034f,0.26075f}, +{-0.42761f,0.12824f,-0.012797f},{-0.335369f,-0.242693f,0.242372f},{-0.341195f,-0.0541976f,-0.0824604f}, +{-0.353973f,-0.0546413f,-0.0768078f},{-0.368892f,-0.0567249f,-0.0698305f},{-0.127761f,-0.288569f,-0.0824411f}, +{-0.147072f,-0.28855f,-0.0815279f},{-0.166352f,-0.288389f,-0.0835857f},{-0.187238f,-0.28664f,-0.0815858f}, +{-0.204878f,-0.284955f,-0.0752902f},{-0.225694f,-0.288589f,-0.0600302f},{-0.281904f,-0.293039f,-0.0341789f}, +{-0.294656f,-0.293315f,-0.0295875f},{-0.377612f,-0.054069f,-0.0618244f},{-0.1754f,0.296756f,-0.0446545f}, +{-0.2782f,-0.265471f,0.245973f},{-0.362107f,-0.229041f,0.234423f},{0.26858f,-0.260242f,-0.148568f}, +{0.257635f,-0.267091f,-0.157378f},{0.248175f,-0.26648f,-0.165757f},{0.232266f,-0.265863f,-0.174573f}, +{0.219417f,-0.270159f,-0.178419f},{0.211533f,-0.264313f,-0.183132f},{0.193624f,-0.264789f,-0.189801f}, +{0.174178f,-0.264686f,-0.191132f},{0.159162f,-0.265895f,-0.195531f},{0.150596f,-0.262506f,-0.200804f}, +{0.135581f,-0.263824f,-0.203575f},{0.116302f,-0.263651f,-0.206964f},{0.0969904f,-0.263316f,-0.21207f}, +{0.0841355f,-0.267606f,-0.215331f},{0.0764059f,-0.261831f,-0.219999f},{0.0584065f,-0.262641f,-0.222584f}, +{0.0391338f,-0.262339f,-0.227279f},{0.0230957f,-0.261953f,-0.233394f},{0.00694188f,-0.266332f,-0.234957f}, +{-0.0124466f,-0.26421f,-0.234661f},{-0.0317964f,-0.262358f,-0.231047f},{-0.0467992f,-0.261567f,-0.222887f}, +{-0.117704f,0.383499f,-0.0835278f},{-0.0364072f,-0.48212f,0.341198f},{-0.127806f,-0.279161f,-0.0867046f}, +{-0.147137f,-0.279078f,-0.08565f},{-0.16639f,-0.278904f,-0.0886274f},{-0.18246f,-0.279303f,-0.0873605f}, +{-0.220678f,-0.280769f,-0.0675541f},{-0.243526f,-0.282145f,-0.0568277f},{-0.262773f,-0.282698f,-0.0507636f}, +{-0.279988f,-0.276287f,-0.056249f},{-0.285325f,-0.283521f,-0.0440564f},{-0.302797f,-0.281296f,-0.0386161f}, +{-0.321954f,-0.282673f,-0.0228095f},{-0.220665f,0.229311f,0.0031639f},{-0.0906949f,0.286428f,-0.0364425f}, +{0.0101379f,0.436443f,-0.16184f},{0.27719f,-0.263914f,-0.140484f},{0.006884f,-0.257233f,-0.236211f}, +{-0.0124208f,-0.257323f,-0.235844f},{-0.354661f,0.0166168f,0.323713f},{-0.127883f,-0.264937f,-0.0931095f}, +{-0.14724f,-0.264782f,-0.0925758f},{-0.16648f,-0.264699f,-0.0958426f},{-0.185689f,-0.26522f,-0.0914376f}, +{-0.206235f,-0.267007f,-0.0815793f},{-0.224247f,-0.266416f,-0.0773737f},{-0.243558f,-0.266422f,-0.0763512f}, +{-0.262837f,-0.266577f,-0.0745442f},{-0.282033f,-0.272332f,-0.0624996f},{-0.297942f,-0.27293f,-0.0529115f}, +{-0.317356f,-0.274866f,-0.034732f},{-0.347812f,-0.0224109f,-0.0663258f},{0.257731f,-0.244269f,-0.159005f}, +{0.248124f,-0.243896f,-0.163995f},{0.23287f,-0.24587f,-0.172798f},{0.211456f,-0.24796f,-0.183132f}, +{0.193405f,-0.246474f,-0.191087f},{0.177335f,-0.246134f,-0.196154f},{0.167618f,-0.245818f,-0.200322f}, +{0.154744f,-0.245413f,-0.205807f},{0.135478f,-0.245117f,-0.210707f},{0.117485f,-0.245902f,-0.213594f}, +{0.100193f,-0.249503f,-0.215736f},{0.0953442f,-0.242487f,-0.218488f},{0.0776277f,-0.244552f,-0.221131f}, +{0.0583486f,-0.244204f,-0.226707f},{0.0403234f,-0.244847f,-0.231767f},{0.0229349f,-0.24351f,-0.236661f}, +{0.00681327f,-0.243394f,-0.23886f},{-0.0125044f,-0.24351f,-0.238738f},{-0.0306132f,-0.242847f,-0.237137f}, +{-0.0383171f,-0.248609f,-0.233414f},{-0.0510755f,-0.244616f,-0.22661f},{-0.334828f,0.188579f,-0.0405839f}, +{-0.333163f,0.273882f,-0.295952f},{-0.198428f,-0.260988f,-0.0868846f},{-0.282091f,-0.26185f,-0.0785055f}, +{-0.301376f,-0.258152f,-0.0702807f},{-0.307382f,-0.262924f,-0.060416f},{-0.320366f,-0.258976f,-0.0545963f}, +{-0.326694f,-0.26495f,-0.039465f},{-0.33634f,-0.260789f,-0.0343526f},{-0.300212f,-0.249072f,0.252564f}, +{-0.336835f,-0.251272f,0.232552f},{0.283467f,-0.240983f,-0.142163f},{0.270657f,-0.240346f,-0.15069f}, +{0.219115f,-0.242822f,-0.178837f},{0.109749f,-0.240217f,-0.217208f},{0.0324845f,-0.239047f,-0.234745f}, +{-0.0638275f,-0.240713f,-0.219105f},{-0.129581f,-0.248333f,-0.100813f},{-0.146031f,-0.246899f,-0.100344f}, +{-0.166544f,-0.250494f,-0.102704f},{-0.166577f,-0.24351f,-0.105045f},{-0.185843f,-0.246526f,-0.0976367f}, +{-0.205058f,-0.247098f,-0.0892833f},{-0.224453f,-0.246918f,-0.0900935f},{-0.243732f,-0.246777f,-0.0927944f}, +{-0.262985f,-0.246995f,-0.0935661f},{-0.280991f,-0.246468f,-0.091071f},{-0.288643f,-0.252551f,-0.0838687f}, +{-0.301395f,-0.248088f,-0.0803832f},{-0.314218f,-0.253966f,-0.0657406f},{-0.332758f,-0.254088f,-0.051889f}, +{-0.349246f,-0.256423f,-0.031851f},{-0.264111f,-0.0108871f,-0.103058f},{-0.459564f,0.0280763f,0.295232f}, +{-0.222761f,0.242751f,-0.005331f},{0.231121f,-0.22425f,-0.16921f},{0.216517f,-0.223093f,-0.175872f}, +{0.208395f,-0.230565f,-0.181145f},{0.193405f,-0.228655f,-0.186785f},{0.177297f,-0.228102f,-0.194849f}, +{0.167721f,-0.227542f,-0.20317f},{0.154751f,-0.227092f,-0.208739f},{0.13542f,-0.226784f,-0.213588f}, +{0.122597f,-0.231227f,-0.215755f},{0.114803f,-0.225594f,-0.21816f},{0.0968425f,-0.226391f,-0.221073f}, +{0.0775763f,-0.226166f,-0.225266f},{0.0582778f,-0.225826f,-0.230468f},{0.0402527f,-0.226436f,-0.234481f}, +{0.0247612f,-0.224443f,-0.237208f},{0.0067361f,-0.225215f,-0.239786f},{-0.0125752f,-0.225253f,-0.240468f}, +{-0.0319829f,-0.225453f,-0.239336f},{-0.0523938f,-0.227125f,-0.230205f},{-0.067075f,-0.227041f,-0.220314f}, +{-0.302804f,0.325057f,-0.357101f},{-0.12481f,-0.24104f,-0.105932f},{-0.15376f,-0.241272f,-0.103604f}, +{-0.298251f,-0.24297f,-0.0873862f},{-0.320552f,-0.244069f,-0.0723128f},{-0.339349f,-0.244757f,-0.0569306f}, +{-0.0142214f,0.343577f,-0.0948908f},{-0.207077f,0.240359f,-0.00654639f},{0.291029f,-0.222366f,-0.13541f}, +{0.283357f,-0.22733f,-0.142124f},{0.270483f,-0.222147f,-0.150703f},{0.2555f,-0.220122f,-0.157976f}, +{0.247018f,-0.222893f,-0.161422f},{-0.0447735f,-0.22124f,-0.235742f},{-0.128083f,-0.227144f,-0.1088f}, +{-0.147394f,-0.22735f,-0.105675f},{-0.166647f,-0.227285f,-0.108067f},{-0.181682f,-0.22616f,-0.104948f}, +{-0.190016f,-0.228919f,-0.101026f},{-0.203559f,-0.230404f,-0.0983441f},{-0.224517f,-0.232796f,-0.0976946f}, +{-0.243815f,-0.232603f,-0.101527f},{-0.263101f,-0.232841f,-0.102376f},{-0.283988f,-0.23079f,-0.0994694f}, +{-0.300379f,-0.22971f,-0.096685f},{-0.314482f,-0.226816f,-0.0928073f},{-0.3207f,-0.234185f,-0.0835986f}, +{-0.333562f,-0.230108f,-0.0782547f},{-0.336687f,-0.235201f,-0.0708466f},{-0.352545f,-0.236166f,-0.0560239f}, +{-0.28748f,-0.479747f,0.292415f},{0.207797f,-0.204083f,-0.181563f},{0.194273f,-0.208585f,-0.187209f}, +{0.177258f,-0.20991f,-0.195582f},{0.167413f,-0.209408f,-0.202411f},{0.15468f,-0.208964f,-0.208553f}, +{0.13533f,-0.208726f,-0.212643f},{0.122462f,-0.208488f,-0.216462f},{0.112855f,-0.208366f,-0.218623f}, +{0.0967975f,-0.208167f,-0.222334f},{0.0774541f,-0.207652f,-0.229793f},{0.0581364f,-0.207505f,-0.232642f}, +{0.0420469f,-0.207472f,-0.233742f},{0.0324394f,-0.207427f,-0.234951f},{0.0228063f,-0.207376f,-0.236571f}, +{0.00668465f,-0.207363f,-0.237787f},{-0.0126459f,-0.207228f,-0.239786f},{-0.0320472f,-0.207337f,-0.239484f}, +{-0.0447799f,-0.212193f,-0.235697f},{-0.0525289f,-0.207177f,-0.230996f},{-0.208485f,-0.223137f,-0.103862f}, +{-0.224588f,-0.222951f,-0.106312f},{-0.243835f,-0.222919f,-0.108556f},{-0.263159f,-0.223176f,-0.108627f}, +{-0.279229f,-0.223478f,-0.104273f},{-0.333671f,-0.220372f,-0.0866596f},{-0.351207f,-0.220282f,-0.0727115f}, +{-0.358821f,-0.226726f,-0.0594643f},{-0.370667f,-0.222218f,-0.0515289f},{-0.386113f,0.0409569f,-0.0281148f}, +{-0.239307f,0.243812f,-0.00250151f},{0.254394f,-0.203492f,-0.157403f},{0.244806f,-0.203196f,-0.161686f}, +{0.231938f,-0.202772f,-0.167885f},{0.216929f,-0.203704f,-0.176142f},{-0.0388702f,0.396682f,-0.120768f}, +{-0.128173f,-0.208501f,-0.113861f},{-0.147458f,-0.208707f,-0.111475f},{-0.166737f,-0.208662f,-0.112884f}, +{-0.186017f,-0.208906f,-0.110865f},{-0.205341f,-0.208977f,-0.110035f},{-0.224639f,-0.208881f,-0.112807f}, +{-0.243918f,-0.208829f,-0.115173f},{-0.263242f,-0.209112f,-0.11361f},{-0.282496f,-0.209588f,-0.108691f}, +{-0.298579f,-0.209871f,-0.105495f},{-0.315884f,-0.211241f,-0.100067f},{-0.332989f,-0.203653f,-0.0944986f}, +{-0.0356677f,-0.0449182f,-0.141745f},{-0.0517443f,-0.0449503f,-0.141706f},{0.192402f,-0.186624f,-0.187769f}, +{0.177104f,-0.187293f,-0.194682f},{0.167528f,-0.186959f,-0.199293f},{0.154654f,-0.190991f,-0.206353f}, +{0.13533f,-0.190798f,-0.21009f},{0.120314f,-0.192013f,-0.215009f},{0.111633f,-0.188714f,-0.219125f}, +{0.0967203f,-0.189936f,-0.224141f},{0.0773898f,-0.189557f,-0.229999f},{0.0580528f,-0.189344f,-0.233574f}, +{0.0430629f,-0.190888f,-0.233973f},{0.0259123f,-0.189068f,-0.239131f},{0.00660105f,-0.188926f,-0.2416f}, +{-0.0127488f,-0.189068f,-0.24086f},{-0.0320601f,-0.189235f,-0.239555f},{-0.0513328f,-0.18984f,-0.232706f}, +{-0.308193f,-0.205389f,-0.104312f},{-0.348738f,-0.200778f,-0.0857979f},{-0.36502f,-0.2078f,-0.0682293f}, +{0.254374f,-0.185415f,-0.155905f},{0.244761f,-0.185081f,-0.160876f},{0.231841f,-0.184618f,-0.167589f}, +{0.215688f,-0.184026f,-0.176174f},{0.206183f,-0.183673f,-0.180978f},{0.0386965f,-0.184701f,-0.235838f}, +{-0.131459f,-0.194405f,-0.119462f},{-0.147542f,-0.189962f,-0.118614f},{-0.166834f,-0.190007f,-0.118607f}, +{-0.186126f,-0.190116f,-0.117642f},{-0.20381f,-0.192386f,-0.119224f},{-0.224703f,-0.19465f,-0.120864f}, +{-0.242741f,-0.188926f,-0.125308f},{-0.250503f,-0.19483f,-0.12208f},{-0.264966f,-0.192753f,-0.119224f}, +{-0.28265f,-0.190926f,-0.114067f},{-0.298695f,-0.191241f,-0.110492f},{-0.314829f,-0.188631f,-0.106144f}, +{-0.321189f,-0.196579f,-0.102087f},{-0.384666f,-0.200495f,-0.0488087f},{0.154513f,-0.168696f,-0.202257f}, +{0.135208f,-0.168265f,-0.208701f},{0.122231f,-0.167988f,-0.213401f},{0.112617f,-0.172059f,-0.219884f}, +{0.0965724f,-0.171564f,-0.227272f},{0.0772612f,-0.171287f,-0.232134f},{0.057982f,-0.171236f,-0.233568f}, +{0.0258287f,-0.170895f,-0.239851f},{0.0064853f,-0.170785f,-0.242179f},{-0.0128388f,-0.170985f,-0.240468f}, +{-0.0308383f,-0.172142f,-0.238134f},{-0.0514292f,-0.171821f,-0.231722f},{-0.231925f,-0.0466544f,-0.121996f}, +{-0.000286191f,0.396785f,-0.137282f},{-0.208659f,-0.185293f,-0.123424f},{-0.224742f,-0.185152f,-0.125771f}, +{-0.260162f,-0.185505f,-0.123867f},{-0.335362f,-0.183981f,-0.0998939f},{-0.353162f,-0.183698f,-0.0911675f}, +{-0.298759f,0.345281f,-0.371531f},{0.308964f,-0.170007f,-0.116054f},{-0.302694f,-0.0482235f,-0.103032f}, +{0.244555f,-0.171448f,-0.161004f},{0.215681f,-0.16593f,-0.175338f},{0.206035f,-0.165615f,-0.180052f}, +{0.193129f,-0.165281f,-0.185447f},{0.173856f,-0.164792f,-0.192727f},{-0.0385808f,-0.166979f,-0.235182f}, +{-0.128449f,-0.170522f,-0.133179f},{-0.147651f,-0.17112f,-0.126915f},{-0.166924f,-0.171371f,-0.124819f}, +{-0.186222f,-0.171326f,-0.12552f},{-0.205527f,-0.171268f,-0.127623f},{-0.224806f,-0.171326f,-0.128613f}, +{-0.244111f,-0.171416f,-0.128092f},{-0.263435f,-0.171737f,-0.125076f},{-0.281441f,-0.173306f,-0.117405f}, +{-0.296418f,-0.167056f,-0.113283f},{-0.314115f,-0.167384f,-0.10999f},{-0.327723f,-0.178129f,-0.104582f}, +{-0.365856f,-0.179563f,-0.0851162f},{-0.311562f,0.346207f,-0.370239f},{-0.112906f,-0.0277612f,-0.121623f}, +{0.115839f,-0.147288f,-0.212437f},{0.109389f,-0.158542f,-0.219015f},{0.0956914f,-0.150407f,-0.222301f}, +{0.0771711f,-0.149031f,-0.226379f},{0.0586958f,-0.150002f,-0.229555f},{0.00640814f,-0.152928f,-0.239028f}, +{-0.0116234f,-0.154008f,-0.237459f},{-0.0257387f,-0.157808f,-0.236037f},{-0.032999f,-0.148002f,-0.231105f}, +{-0.051455f,-0.149603f,-0.226173f},{-0.315093f,-0.235465f,0.25749f},{-0.327755f,-0.164342f,-0.105354f}, +{-0.337484f,-0.164696f,-0.101244f},{-0.353445f,-0.165262f,-0.0944921f},{-0.368332f,-0.164336f,-0.0870583f}, +{0.193065f,-0.146979f,-0.1862f},{0.173721f,-0.146606f,-0.192502f},{0.154513f,-0.146034f,-0.200945f}, +{0.135137f,-0.145764f,-0.205717f},{-0.0193917f,-0.148838f,-0.233806f},{-0.123883f,-0.149892f,-0.142754f}, +{-0.132726f,-0.153764f,-0.133989f},{-0.147741f,-0.152612f,-0.129636f},{-0.167014f,-0.152793f,-0.128234f}, +{-0.186312f,-0.152735f,-0.130169f},{-0.00889684f,0.422186f,-0.143655f},{0.102977f,-0.140529f,-0.216012f}, +{-0.318225f,-0.223105f,0.263252f},{-0.366892f,-0.00498377f,-0.0479984f},{-0.110251f,-0.491759f,-0.181525f}, +{-0.0913894f,-0.491913f,-0.170451f},{-0.131369f,-0.491315f,-0.169191f},{-0.111292f,-0.49145f,-0.169943f}, +{-0.0513714f,-0.491663f,-0.151969f},{-0.0331983f,-0.491624f,-0.149641f},{-0.0130961f,-0.49174f,-0.150439f}, +{0.0435452f,-0.489901f,-0.13197f},{0.0612102f,-0.489663f,-0.131745f},{0.0793768f,-0.489631f,-0.129423f}, +{0.0996656f,-0.489405f,-0.130517f},{0.155735f,-0.487618f,-0.114164f},{0.174171f,-0.486608f,-0.112215f}, +{0.189656f,-0.484576f,-0.112292f},{-0.165329f,-0.488055f,-0.165667f},{-0.125909f,-0.491836f,-0.151371f}, +{-0.108263f,-0.491585f,-0.151146f},{-0.0900904f,-0.49154f,-0.148818f},{-0.0699945f,-0.491643f,-0.149596f}, +{-0.0160992f,-0.490897f,-0.133668f},{0.00396448f,-0.490171f,-0.130407f},{0.0226391f,-0.4901f,-0.132845f}, +{0.0807787f,-0.488531f,-0.11381f},{0.0982894f,-0.488306f,-0.112389f},{0.114906f,-0.488319f,-0.11154f}, +{0.135118f,-0.488261f,-0.112492f},{0.191971f,-0.48628f,-0.09446f},{0.202884f,-0.484119f,-0.103309f}, +{0.20808f,-0.483258f,-0.0937976f},{-0.147793f,-0.491798f,-0.149712f},{-0.0728047f,-0.490479f,-0.133153f}, +{-0.0527861f,-0.48983f,-0.129809f},{-0.0355262f,-0.490274f,-0.129012f},{0.0236487f,-0.487901f,-0.116549f}, +{0.0419633f,-0.487168f,-0.112414f},{0.0601042f,-0.48718f,-0.110041f},{0.117163f,-0.485644f,-0.097193f}, +{0.135915f,-0.486377f,-0.095849f},{0.15513f,-0.487213f,-0.0912961f},{0.17367f,-0.487496f,-0.0935918f}, +{-0.449989f,0.00813478f,0.248461f},{-0.145728f,-0.491373f,-0.133513f},{-0.128584f,-0.491174f,-0.130298f}, +{-0.10991f,-0.490229f,-0.128678f},{-0.0921225f,-0.489714f,-0.128671f},{-0.031835f,-0.483238f,-0.116736f}, +{-0.0167037f,-0.484808f,-0.116517f},{0.00189381f,-0.484351f,-0.113276f},{0.0818527f,-0.485258f,-0.101489f}, +{0.0984116f,-0.484917f,-0.0985306f},{0.173901f,-0.486576f,-0.0760618f},{0.192023f,-0.486705f,-0.0736632f}, +{-0.168641f,-0.49165f,-0.132992f},{-0.0911964f,-0.483785f,-0.118948f},{-0.0730427f,-0.48464f,-0.120575f}, +{-0.0523617f,-0.482775f,-0.118221f},{0.00335356f,-0.475322f,-0.106074f},{0.0212051f,-0.480087f,-0.106177f}, +{0.0343301f,-0.480049f,-0.103611f},{0.041751f,-0.470757f,-0.0971737f},{0.0607922f,-0.471419f,-0.0942992f}, +{0.0806115f,-0.471117f,-0.0906723f},{0.100894f,-0.474075f,-0.0899521f},{0.119067f,-0.47585f,-0.0877528f}, +{0.133176f,-0.479046f,-0.0850133f},{0.139909f,-0.470962f,-0.0775409f},{0.157799f,-0.481946f,-0.0774637f}, +{0.196035f,-0.486415f,-0.0577216f},{0.211578f,-0.484203f,-0.0575994f},{0.224408f,-0.482055f,-0.0584868f}, +{-0.165348f,-0.491412f,-0.114614f},{-0.14686f,-0.490775f,-0.112742f},{-0.129395f,-0.488351f,-0.109109f}, +{-0.111466f,-0.484775f,-0.113057f},{-0.0891579f,-0.471522f,-0.108466f},{-0.0720395f,-0.471091f,-0.110614f}, +{-0.0527539f,-0.471252f,-0.110929f},{-0.032253f,-0.469728f,-0.108279f},{-0.013842f,-0.471046f,-0.106427f}, +{0.0209929f,-0.468191f,-0.099135f},{0.121511f,-0.465696f,-0.0803254f},{0.155632f,-0.472004f,-0.0701456f}, +{0.167721f,-0.478049f,-0.0589434f},{0.177007f,-0.484441f,-0.0562425f},{-0.184801f,-0.487926f,-0.112749f}, +{-0.104829f,-0.478531f,-0.108537f},{-0.00903832f,-0.46511f,-0.10192f},{0.004241f,-0.464802f,-0.099553f}, +{0.100585f,-0.462332f,-0.0827369f},{0.157169f,-0.467252f,-0.0572972f},{0.176551f,-0.484949f,-0.0377415f}, +{0.193264f,-0.486377f,-0.036121f},{0.212665f,-0.484074f,-0.0356001f},{0.224858f,-0.482512f,-0.0334201f}, +{-0.352043f,-0.278075f,0.16793f},{-0.18556f,-0.490968f,-0.0940098f},{-0.167451f,-0.491032f,-0.0915404f}, +{-0.14724f,-0.489013f,-0.0923507f},{-0.131041f,-0.483663f,-0.0930645f},{-0.112103f,-0.469548f,-0.0943121f}, +{-0.0604193f,-0.461059f,-0.103823f},{-0.0504261f,-0.459426f,-0.0961384f},{-0.0323109f,-0.459471f,-0.0937076f}, +{-0.0139642f,-0.459091f,-0.0916176f},{0.00604159f,-0.459297f,-0.0922414f},{0.0226713f,-0.45886f,-0.0893476f}, +{0.0390823f,-0.45877f,-0.0861644f},{0.0606829f,-0.457098f,-0.0773866f},{0.0806051f,-0.456603f,-0.0738947f}, +{0.0981608f,-0.456506f,-0.0734896f},{0.118192f,-0.457844f,-0.0712967f},{0.133902f,-0.459059f,-0.0670525f}, +{0.137812f,-0.458506f,-0.0571235f},{0.166589f,-0.477091f,-0.0439407f},{0.230665f,-0.482583f,-0.0170991f}, +{-0.196646f,-0.486422f,-0.0956625f},{-0.14614f,-0.484782f,-0.0751744f},{-0.126166f,-0.477297f,-0.0899778f}, +{-0.098611f,-0.46286f,-0.097791f},{-0.0876403f,-0.459458f,-0.0915533f},{-0.071255f,-0.458699f,-0.0931738f}, +{0.00337286f,-0.457902f,-0.0760361f},{0.0219189f,-0.457149f,-0.0742355f},{0.042169f,-0.456937f,-0.0752066f}, +{0.0984052f,-0.455722f,-0.0560882f},{0.116044f,-0.455445f,-0.0558374f},{0.151233f,-0.46104f,-0.049709f}, +{0.157226f,-0.469014f,-0.0381724f},{0.172171f,-0.485952f,-0.0186167f},{0.193097f,-0.486377f,-0.0180573f}, +{0.209939f,-0.485496f,-0.0155172f},{-0.185348f,-0.491637f,-0.0737532f},{-0.164024f,-0.490582f,-0.0734317f}, +{-0.127047f,-0.469368f,-0.0775602f},{-0.101569f,-0.460133f,-0.088936f},{-0.0714929f,-0.458892f,-0.0750972f}, +{-0.0540208f,-0.458911f,-0.0745764f},{-0.0364265f,-0.458705f,-0.0742484f},{-0.015469f,-0.458365f,-0.0733738f}, +{0.0257065f,-0.457432f,-0.0580045f},{0.0419826f,-0.456969f,-0.057175f},{0.0585351f,-0.456648f,-0.0541783f}, +{0.0773255f,-0.456313f,-0.0567248f},{0.136783f,-0.457207f,-0.0380245f},{0.147703f,-0.459304f,-0.0399601f}, +{0.149902f,-0.475174f,-0.0189254f},{0.157439f,-0.483457f,-0.0158194f},{0.174904f,-0.487335f,0.000244383f}, +{0.192376f,-0.487361f,0.000745975f},{0.210267f,-0.48664f,0.000649515f},{0.232549f,-0.483856f,-0.000456558f}, +{0.245937f,-0.48329f,0.00167842f},{-0.18529f,-0.491405f,-0.0561847f},{-0.167104f,-0.490087f,-0.0567055f}, +{-0.151047f,-0.484608f,-0.0584933f},{-0.109993f,-0.460133f,-0.0715025f},{-0.0922768f,-0.458937f,-0.0762226f}, +{-0.0308768f,-0.459117f,-0.0565898f},{-0.0139642f,-0.458551f,-0.0561204f},{0.00377799f,-0.457844f,-0.0550079f}, +{0.0625928f,-0.456969f,-0.0380502f},{0.0804443f,-0.456288f,-0.0380952f},{0.0988939f,-0.455683f,-0.0361532f}, +{0.119369f,-0.455046f,-0.037465f},{0.141034f,-0.465233f,-0.0178001f},{0.153747f,-0.486036f,0.00079742f}, +{0.213f,-0.485727f,0.0189962f},{0.229558f,-0.485804f,0.0199737f},{0.248606f,-0.484119f,0.0210347f}, +{0.26858f,-0.47558f,0.0111829f},{0.288714f,-0.472416f,0.0155494f},{0.306231f,-0.470776f,0.0226167f}, +{0.310443f,-0.463844f,0.00981964f},{-0.224781f,-0.491733f,-0.0549307f},{-0.206305f,-0.491907f,-0.0569756f}, +{-0.135613f,-0.470229f,-0.0626025f},{-0.124019f,-0.461033f,-0.068718f},{-0.0896531f,-0.45994f,-0.0568727f}, +{-0.0721617f,-0.459902f,-0.0563904f},{-0.0514485f,-0.459979f,-0.0551493f},{0.0043246f,-0.458435f,-0.0382045f}, +{0.0226648f,-0.458024f,-0.0361017f},{0.0414231f,-0.457709f,-0.0386097f},{0.0994469f,-0.456223f,-0.0193434f}, +{0.11672f,-0.456378f,-0.0175493f},{0.130005f,-0.457342f,-0.0212147f},{0.132829f,-0.472731f,0.00142119f}, +{0.141529f,-0.484267f,0.00572974f},{0.156886f,-0.488074f,0.0182824f},{0.173599f,-0.487438f,0.0210476f}, +{0.192595f,-0.487026f,0.0232919f},{0.270638f,-0.483528f,0.0369313f},{0.284952f,-0.479406f,0.0300312f}, +{0.310193f,-0.475811f,0.0396836f},{0.339658f,-0.462377f,0.0256391f},{-0.203045f,-0.491862f,-0.0386418f}, +{-0.184647f,-0.491309f,-0.0366355f},{-0.169342f,-0.490061f,-0.0335102f},{-0.155252f,-0.486203f,-0.0400759f}, +{-0.139999f,-0.470178f,-0.0516832f},{-0.127748f,-0.461014f,-0.0536188f},{-0.108225f,-0.459914f,-0.054667f}, +{-0.0706569f,-0.460564f,-0.0393814f},{-0.0535706f,-0.460442f,-0.0360696f},{-0.0367223f,-0.459979f,-0.0354779f}, +{-0.0162921f,-0.459374f,-0.0367126f},{0.0429086f,-0.457979f,-0.0213112f},{0.0609852f,-0.457612f,-0.0168162f}, +{0.0772612f,-0.45713f,-0.0159802f},{0.118276f,-0.459194f,-0.00186487f},{0.136436f,-0.486428f,0.0216328f}, +{0.199257f,-0.484287f,0.0293238f},{0.217154f,-0.481476f,0.0303463f},{0.232845f,-0.483676f,0.0385004f}, +{0.250696f,-0.486023f,0.0412141f},{0.289036f,-0.481714f,0.0449761f},{0.299518f,-0.480499f,0.0459921f}, +{0.327009f,-0.471303f,0.0436513f},{0.343368f,-0.465252f,0.0412077f},{-0.337967f,-0.286254f,0.172303f}, +{-0.260143f,-0.492505f,-0.0377673f},{-0.242118f,-0.492666f,-0.0351628f},{-0.221855f,-0.492312f,-0.0361146f}, +{-0.164705f,-0.489463f,-0.0183659f},{-0.14695f,-0.474859f,-0.036957f},{-0.1267f,-0.460628f,-0.0399087f}, +{-0.110257f,-0.460275f,-0.0357287f},{-0.0942703f,-0.460185f,-0.0367512f},{-0.0310762f,-0.460262f,-0.0180123f}, +{-0.0133469f,-0.459773f,-0.0178515f},{0.00447894f,-0.459111f,-0.0178386f},{0.0255651f,-0.45848f,-0.0171698f}, +{0.0810745f,-0.457406f,0.0002058f},{0.097865f,-0.457046f,0.000855296f},{0.120855f,-0.463786f,0.00681652f}, +{0.118437f,-0.470152f,0.0181152f},{0.12425f,-0.481592f,0.0237549f},{0.1367f,-0.487566f,0.0388476f}, +{0.154005f,-0.486242f,0.0387576f},{0.176743f,-0.48138f,0.0340954f},{0.194634f,-0.478551f,0.0351179f}, +{0.212723f,-0.475374f,0.0358574f},{0.22687f,-0.479457f,0.0424488f},{0.249918f,-0.484319f,0.0566349f}, +{0.26966f,-0.485065f,0.056159f},{0.286888f,-0.483457f,0.0581461f},{0.304309f,-0.481502f,0.0598823f}, +{0.315511f,-0.478956f,0.0598052f},{0.327999f,-0.474049f,0.0578567f},{0.344597f,-0.467297f,0.0551172f}, +{0.358268f,-0.462313f,0.0590335f},{0.366667f,-0.458294f,0.0572715f},{-0.402948f,-0.00846919f,0.222533f}, +{-0.262921f,-0.299225f,0.207421f},{-0.278419f,-0.492003f,-0.036031f},{-0.241172f,-0.49273f,-0.0190412f}, +{-0.22352f,-0.492139f,-0.0177743f},{-0.206788f,-0.491856f,-0.0170091f},{-0.186383f,-0.491251f,-0.0181473f}, +{-0.131356f,-0.46212f,-0.0335487f},{-0.0887399f,-0.460731f,-0.0191055f},{-0.0707727f,-0.460982f,-0.0164432f}, +{-0.0521559f,-0.460898f,-0.0187068f},{0.003823f,-0.460146f,0.000823143f},{0.0216231f,-0.459522f,0.000874589f}, +{0.0395389f,-0.458718f,0.000784559f},{0.0607344f,-0.45787f,0.00129258f},{0.101151f,-0.45832f,0.0177872f}, +{0.111183f,-0.461329f,0.014604f},{0.111813f,-0.474814f,0.0349442f},{0.118945f,-0.484544f,0.0412399f}, +{0.168789f,-0.475779f,0.0393492f},{0.234195f,-0.47585f,0.0525964f},{0.269107f,-0.48583f,0.0746986f}, +{0.288071f,-0.484242f,0.0758882f},{0.30537f,-0.482087f,0.079811f},{0.32416f,-0.47803f,0.0775281f}, +{0.344796f,-0.469792f,0.0765442f},{-0.388422f,-0.255927f,0.150561f},{-0.297801f,-0.492595f,-0.0188868f}, +{-0.28047f,-0.492601f,-0.0171313f},{-0.264644f,-0.492762f,-0.0178772f},{-0.200666f,-0.491412f,-0.000302223f}, +{-0.184525f,-0.491f,-0.00152405f},{-0.166377f,-0.490505f,0.00285523f},{-0.148854f,-0.486319f,-0.00328605f}, +{-0.125253f,-0.46084f,-0.0216392f},{-0.10872f,-0.460551f,-0.0185653f},{-0.0523552f,-0.46113f,-0.000713785f}, +{-0.0344008f,-0.460982f,0.00396774f},{-0.0170895f,-0.46048f,-0.000128595f},{0.045095f,-0.459136f,0.0183596f}, +{0.0618983f,-0.458763f,0.0190219f},{0.0790875f,-0.458423f,0.0222051f},{0.105955f,-0.462815f,0.0268223f}, +{0.118353f,-0.485785f,0.0573294f},{0.132205f,-0.488158f,0.0591107f},{0.148146f,-0.481541f,0.052352f}, +{0.158841f,-0.473869f,0.0475483f},{0.242536f,-0.476377f,0.0667246f},{0.251789f,-0.47801f,0.0780554f}, +{0.256014f,-0.481702f,0.0742999f},{0.361348f,-0.462094f,0.0778367f},{0.371265f,-0.4565f,0.0741841f}, +{0.383393f,-0.450474f,0.0784477f},{0.397643f,-0.443439f,0.0674384f},{-0.411135f,-0.00341467f,0.245548f}, +{-0.258638f,-0.492601f,-0.00105461f},{-0.240375f,-0.492293f,0.00120898f},{-0.222729f,-0.491901f,0.00151122f}, +{-0.147889f,-0.490621f,0.0184046f},{-0.120012f,-0.461149f,-0.00846916f},{-0.107961f,-0.461091f,-0.00315744f}, +{-0.0910678f,-0.460898f,0.00417352f},{-0.0740459f,-0.460847f,0.000559486f},{-0.0140221f,-0.46077f,0.0184882f}, +{0.00419598f,-0.460551f,0.0208032f},{0.0244911f,-0.460107f,0.0198257f},{0.080708f,-0.458995f,0.0389763f}, +{0.0930806f,-0.459606f,0.0371435f},{0.102206f,-0.467586f,0.0423202f},{0.0986688f,-0.470789f,0.0581653f}, +{0.13623f,-0.488184f,0.0748786f},{0.267995f,-0.483624f,0.0962734f},{0.286071f,-0.484467f,0.0948265f}, +{0.30382f,-0.481882f,0.0960741f},{0.314881f,-0.479592f,0.0962284f},{0.329105f,-0.474776f,0.0986657f}, +{0.344635f,-0.468905f,0.0984599f},{0.356056f,-0.463818f,0.0903444f},{0.364062f,-0.459651f,0.0960548f}, +{-0.314405f,-0.492216f,0.000662377f},{-0.297273f,-0.492743f,0.00171057f},{-0.279666f,-0.492441f,0.00209641f}, +{-0.222736f,-0.492036f,0.0190669f},{-0.205077f,-0.491598f,0.0193434f},{-0.187361f,-0.491077f,0.0195557f}, +{-0.166808f,-0.491367f,0.0210669f},{-0.133896f,-0.490949f,0.0273496f},{-0.105106f,-0.461857f,0.00686153f}, +{-0.07121f,-0.461323f,0.0179158f},{-0.0534227f,-0.461078f,0.0195557f},{-0.0327031f,-0.461072f,0.0208225f}, +{0.00517987f,-0.46059f,0.0368284f},{0.0227034f,-0.46023f,0.0383075f},{0.0410372f,-0.459786f,0.040436f}, +{0.0597762f,-0.459394f,0.0380245f},{0.107241f,-0.482544f,0.0744349f},{0.116154f,-0.486184f,0.0766213f}, +{0.25449f,-0.475644f,0.0960805f},{0.288759f,-0.482737f,0.110228f},{0.303235f,-0.480936f,0.106858f}, +{0.308109f,-0.477997f,0.113964f},{0.324732f,-0.473451f,0.110299f},{0.381599f,-0.451188f,0.0953989f}, +{0.396293f,-0.44423f,0.0976946f},{0.405579f,-0.439664f,0.0950002f},{-0.338539f,-0.48983f,0.000366566f}, +{-0.279531f,-0.492312f,0.0194142f},{-0.261969f,-0.492048f,0.0198386f},{-0.244446f,-0.491875f,0.0203273f}, +{-0.181894f,-0.491965f,0.0371692f},{-0.165181f,-0.491675f,0.0379602f},{-0.147992f,-0.491328f,0.0411305f}, +{-0.128456f,-0.491045f,0.0401788f},{-0.118411f,-0.490608f,0.0418379f},{-0.0931128f,-0.461921f,0.0210219f}, +{-0.0829137f,-0.461213f,0.0224623f},{-0.070091f,-0.461676f,0.0254783f},{-0.0473393f,-0.461297f,0.0336259f}, +{-0.0361178f,-0.461374f,0.0379216f},{-0.0264976f,-0.461721f,0.0401852f},{-0.0166458f,-0.460975f,0.0395614f}, +{0.0614803f,-0.459503f,0.0548536f},{0.0783222f,-0.459792f,0.0585255f},{0.096894f,-0.472789f,0.07911f}, +{0.118077f,-0.486344f,0.0929231f},{0.135517f,-0.48875f,0.0974695f},{0.250921f,-0.475547f,0.11089f}, +{0.268162f,-0.483329f,0.113617f},{0.345143f,-0.465252f,0.1168f},{0.354924f,-0.461966f,0.1119f}, +{0.365136f,-0.457162f,0.115501f},{0.3823f,-0.449715f,0.11208f},{-0.298662f,-0.297714f,0.189081f}, +{-0.377451f,-0.269091f,0.135025f},{-0.335613f,-0.491315f,0.0186875f},{-0.318752f,-0.49237f,0.0201859f}, +{-0.301428f,-0.492531f,0.0209575f},{-0.239938f,-0.491727f,0.0356066f},{-0.223681f,-0.491913f,0.0390856f}, +{-0.204672f,-0.492016f,0.0401338f},{-0.147626f,-0.489032f,0.0596766f},{-0.128346f,-0.490955f,0.0575802f}, +{-0.113202f,-0.490486f,0.0582875f},{-0.0844828f,-0.462133f,0.0346163f},{-0.0740201f,-0.4623f,0.0407897f}, +{-0.0580978f,-0.461741f,0.0396258f},{-0.0177004f,-0.461664f,0.0535996f},{-0.00942416f,-0.461098f,0.056712f}, +{0.00554642f,-0.460982f,0.0576895f},{0.0231343f,-0.46068f,0.0580753f},{0.0440918f,-0.46023f,0.0589949f}, +{0.0782129f,-0.46032f,0.0737854f},{0.0864827f,-0.462474f,0.0754703f},{0.105678f,-0.48147f,0.0965178f}, +{0.150828f,-0.487341f,0.112922f},{0.255172f,-0.481136f,0.12071f},{0.268432f,-0.482853f,0.130806f}, +{0.284476f,-0.48138f,0.126877f},{0.290823f,-0.47839f,0.133282f},{0.307112f,-0.474589f,0.132375f}, +{0.326578f,-0.469232f,0.129153f},{0.340249f,-0.464892f,0.129179f},{0.393482f,-0.443285f,0.114183f}, +{0.403733f,-0.438372f,0.117604f},{-0.293917f,-0.294312f,0.19991f},{-0.24359f,-0.287939f,0.232102f}, +{-0.352487f,-0.486274f,0.0193949f},{-0.296714f,-0.49201f,0.0359088f},{-0.280348f,-0.491984f,0.039227f}, +{-0.261236f,-0.491907f,0.0401466f},{-0.205444f,-0.488518f,0.0571493f},{-0.186203f,-0.487148f,0.0540176f}, +{-0.166435f,-0.486878f,0.0577859f},{-0.108617f,-0.490447f,0.0739076f},{-0.0683805f,-0.462699f,0.0558889f}, +{-0.0517765f,-0.462223f,0.0588341f},{-0.0363622f,-0.461889f,0.055831f},{-0.0263046f,-0.461445f,0.0574837f}, +{0.0231086f,-0.460795f,0.075676f},{0.04069f,-0.460532f,0.0761005f},{0.0583614f,-0.460101f,0.0763898f}, +{0.0958972f,-0.471734f,0.0962542f},{0.117408f,-0.484872f,0.114479f},{0.135992f,-0.487939f,0.114344f}, +{0.24377f,-0.476043f,0.125482f},{0.252632f,-0.483894f,0.134382f},{0.348654f,-0.459426f,0.137687f}, +{0.363053f,-0.45412f,0.134652f},{0.381997f,-0.44605f,0.132388f},{0.401579f,-0.43623f,0.133063f}, +{-0.352249f,-0.485689f,0.0385068f},{-0.336179f,-0.490563f,0.0381017f},{-0.317935f,-0.491489f,0.0414971f}, +{-0.260194f,-0.491901f,0.0559918f},{-0.242291f,-0.492235f,0.0587827f},{-0.223681f,-0.491283f,0.0605511f}, +{-0.142507f,-0.487026f,0.0685895f},{-0.129999f,-0.486968f,0.0777146f},{-0.0948298f,-0.48956f,0.0805505f}, +{-0.0349732f,-0.462262f,0.0750973f},{-0.0164529f,-0.461857f,0.0784991f},{-0.0175332f,-0.461394f,0.0708723f}, +{0.00209959f,-0.461381f,0.0748401f},{0.0640912f,-0.460448f,0.0936112f},{0.0788045f,-0.460641f,0.0956111f}, +{0.0865277f,-0.462455f,0.0929745f},{0.0987974f,-0.471065f,0.114922f},{0.136751f,-0.486602f,0.130787f}, +{0.154899f,-0.486119f,0.135205f},{0.170982f,-0.483753f,0.135179f},{0.230079f,-0.475168f,0.13361f}, +{0.233385f,-0.481219f,0.139745f},{0.23325f,-0.484184f,0.150188f},{0.249886f,-0.482955f,0.150516f}, +{0.263995f,-0.481033f,0.151674f},{0.273769f,-0.477727f,0.152632f},{0.289132f,-0.47401f,0.149275f}, +{0.308122f,-0.468699f,0.15069f},{0.326147f,-0.464795f,0.146735f},{0.343587f,-0.456024f,0.155088f}, +{0.416781f,-0.428198f,0.134568f},{0.426806f,-0.422732f,0.130735f},{-0.373091f,-0.224449f,0.226289f}, +{-0.315472f,-0.490749f,0.0568856f},{-0.298354f,-0.49129f,0.058011f},{-0.281107f,-0.491585f,0.0589306f}, +{-0.194756f,-0.478872f,0.0621974f},{-0.184139f,-0.477367f,0.0630012f},{-0.170737f,-0.476776f,0.0651941f}, +{-0.161541f,-0.477914f,0.0680943f},{-0.146069f,-0.477573f,0.0755796f},{-0.109678f,-0.489811f,0.0959969f}, +{-0.0912672f,-0.489155f,0.0983313f},{-0.0687599f,-0.463483f,0.0739848f},{-0.0555513f,-0.463246f,0.076467f}, +{-0.00233756f,-0.461567f,0.0857722f},{0.00869102f,-0.461381f,0.093669f},{0.0229478f,-0.461175f,0.0934825f}, +{0.0410951f,-0.460885f,0.0969101f},{0.0883026f,-0.463213f,0.105714f},{0.11344f,-0.475991f,0.135925f}, +{0.122109f,-0.482808f,0.134105f},{0.178042f,-0.484891f,0.15222f},{0.193663f,-0.485393f,0.151783f}, +{0.210858f,-0.485039f,0.154966f},{0.331362f,-0.461181f,0.153352f},{0.363818f,-0.448603f,0.153333f}, +{0.38291f,-0.440153f,0.150812f},{0.400138f,-0.430475f,0.153699f},{-0.428459f,0.0276519f,0.314106f}, +{-0.412016f,-0.00104821f,0.266345f},{-0.337407f,-0.489116f,0.0595544f},{-0.278824f,-0.491193f,0.0745893f}, +{-0.260316f,-0.491592f,0.0776052f},{-0.244568f,-0.491978f,0.0792708f},{-0.222562f,-0.486126f,0.0746729f}, +{-0.204357f,-0.476557f,0.0697534f},{-0.133214f,-0.478042f,0.0855921f},{-0.126983f,-0.477309f,0.0939198f}, +{-0.120919f,-0.481534f,0.0970387f},{-0.0656217f,-0.463863f,0.0924022f},{-0.0524196f,-0.463619f,0.094878f}, +{-0.033687f,-0.46241f,0.0964406f},{-0.0134626f,-0.462062f,0.0955982f},{-0.000260458f,-0.461818f,0.0980676f}, +{0.0259766f,-0.461683f,0.103296f},{0.0439118f,-0.460802f,0.111971f},{0.0603614f,-0.460583f,0.115135f}, +{0.0776984f,-0.460763f,0.115906f},{0.100746f,-0.465863f,0.131996f},{0.138301f,-0.483026f,0.15004f}, +{0.154397f,-0.484685f,0.154805f},{0.213385f,-0.484042f,0.170323f},{0.23134f,-0.483071f,0.170194f}, +{0.247146f,-0.481367f,0.169937f},{0.256587f,-0.478673f,0.17139f},{0.269403f,-0.473072f,0.168972f}, +{0.286984f,-0.466371f,0.170329f},{0.305608f,-0.464628f,0.162876f},{0.324809f,-0.454744f,0.171068f}, +{0.41134f,-0.426051f,0.149371f},{0.422993f,-0.418765f,0.150793f},{-0.31908f,-0.48965f,0.0752645f}, +{-0.301376f,-0.490338f,0.0783705f},{-0.240169f,-0.491553f,0.0943635f},{-0.229887f,-0.491746f,0.0897528f}, +{-0.111742f,-0.488872f,0.114209f},{-0.0909585f,-0.488711f,0.115405f},{-0.0327417f,-0.46257f,0.112479f}, +{-0.0152182f,-0.462204f,0.113977f},{0.00272336f,-0.461792f,0.11417f},{0.00360437f,-0.461529f,0.117983f}, +{0.0219253f,-0.461175f,0.113656f},{0.0347416f,-0.461631f,0.116684f},{0.0799299f,-0.460345f,0.131706f}, +{0.119517f,-0.469618f,0.152793f},{0.131034f,-0.475798f,0.156812f},{0.159869f,-0.482943f,0.169107f}, +{0.17403f,-0.484325f,0.171294f},{0.191412f,-0.484415f,0.172001f},{0.306006f,-0.458043f,0.174535f}, +{0.343889f,-0.447998f,0.173319f},{0.361008f,-0.444462f,0.167795f},{0.383148f,-0.432192f,0.169126f}, +{-0.221604f,-0.246654f,0.267053f},{-0.212086f,-0.27158f,0.254243f},{-0.347523f,-0.483804f,0.0699656f}, +{-0.335864f,-0.485766f,0.0786727f},{-0.295048f,-0.489753f,0.0946272f},{-0.2782f,-0.490775f,0.0961513f}, +{-0.259956f,-0.491187f,0.09455f},{-0.121491f,-0.476673f,0.109926f},{-0.119041f,-0.484312f,0.117276f}, +{-0.109614f,-0.488743f,0.130144f},{-0.0914151f,-0.486479f,0.131835f},{-0.0691071f,-0.469805f,0.118343f}, +{-0.0545481f,-0.463676f,0.117726f},{0.0204205f,-0.461355f,0.129018f},{0.0282659f,-0.461664f,0.132832f}, +{0.0416546f,-0.461066f,0.135025f},{0.0579113f,-0.460545f,0.135931f},{0.0933378f,-0.46014f,0.138928f}, +{0.107672f,-0.463901f,0.145211f},{0.138217f,-0.469721f,0.169634f},{0.154571f,-0.477014f,0.175306f}, +{0.19381f,-0.483689f,0.187557f},{0.21271f,-0.48338f,0.189968f},{0.231385f,-0.481907f,0.193621f}, +{0.242246f,-0.481103f,0.188142f},{0.25141f,-0.474898f,0.192212f},{0.269094f,-0.46702f,0.185602f}, +{0.293344f,-0.459561f,0.179094f},{0.362461f,-0.439735f,0.175557f},{0.396074f,-0.425543f,0.169737f}, +{0.399997f,-0.420784f,0.174039f},{-0.430259f,0.00304813f,0.26468f},{-0.316598f,-0.265516f,0.229362f}, +{-0.327922f,-0.484139f,0.0914633f},{-0.316308f,-0.486891f,0.0962863f},{-0.259879f,-0.491309f,0.111868f}, +{-0.243944f,-0.492023f,0.115803f},{-0.121466f,-0.482885f,0.132896f},{-0.0500402f,-0.463522f,0.133031f}, +{-0.0322401f,-0.462815f,0.133121f},{-0.0136877f,-0.461934f,0.134967f},{0.00335356f,-0.4614f,0.13332f}, +{0.0129867f,-0.461741f,0.135603f},{0.0622906f,-0.460062f,0.151121f},{0.080663f,-0.459496f,0.153217f}, +{0.0981094f,-0.459451f,0.153834f},{0.111459f,-0.460969f,0.155018f},{0.171084f,-0.474621f,0.19274f}, +{0.175554f,-0.481579f,0.186605f},{0.286907f,-0.455587f,0.193036f},{0.306296f,-0.449124f,0.1871f}, +{0.32598f,-0.444892f,0.184393f},{0.344127f,-0.435838f,0.190219f},{0.362062f,-0.430269f,0.190386f}, +{0.375483f,-0.42753f,0.184849f},{-0.33396f,0.239008f,-0.169165f},{-0.281917f,-0.48992f,0.114646f}, +{-0.242105f,-0.492582f,0.133816f},{-0.124565f,-0.478551f,0.13415f},{-0.109177f,-0.488544f,0.15204f}, +{-0.092579f,-0.485328f,0.150625f},{-0.0680782f,-0.46468f,0.133256f},{-0.00444683f,-0.461831f,0.143732f}, +{0.0050577f,-0.461207f,0.152079f},{0.0233336f,-0.460853f,0.154329f},{0.0421433f,-0.460275f,0.151847f}, +{0.0987396f,-0.45839f,0.170458f},{0.117716f,-0.458667f,0.170856f},{0.155477f,-0.467522f,0.186187f}, +{0.194383f,-0.483161f,0.209299f},{0.211906f,-0.482981f,0.209807f},{0.230086f,-0.48156f,0.209357f}, +{0.24867f,-0.473457f,0.211678f},{0.260477f,-0.467548f,0.20301f},{0.310321f,-0.438082f,0.194566f}, +{0.386248f,-0.417376f,0.189582f},{-0.335459f,0.246854f,-0.208836f},{-0.402743f,-0.236642f,0.149744f}, +{-0.313723f,-0.482608f,0.110054f},{-0.279338f,-0.489071f,0.129816f},{-0.261487f,-0.490685f,0.133816f}, +{-0.121517f,-0.483174f,0.150477f},{-0.069718f,-0.465033f,0.149481f},{-0.0525546f,-0.463104f,0.153577f}, +{-0.0356162f,-0.462377f,0.154079f},{-0.0186843f,-0.462043f,0.152625f},{-0.00848528f,-0.461329f,0.154066f}, +{0.0423491f,-0.460011f,0.169075f},{0.0605543f,-0.459387f,0.173403f},{0.0767017f,-0.458905f,0.172213f}, +{0.1307f,-0.457689f,0.179287f},{0.136205f,-0.457239f,0.186984f},{0.151439f,-0.458705f,0.193138f}, +{0.173747f,-0.470255f,0.209022f},{0.184351f,-0.479573f,0.209839f},{0.211758f,-0.483374f,0.227562f}, +{0.2268f,-0.481708f,0.226256f},{0.23707f,-0.47875f,0.219896f},{0.37212f,-0.421279f,0.196154f}, +{-0.323961f,-0.233658f,0.25414f},{-0.285865f,-0.251638f,0.255098f},{-0.468824f,0.0225716f,0.22699f}, +{-0.299254f,-0.277387f,0.224662f},{-0.241147f,-0.490351f,0.150297f},{-0.230549f,-0.492048f,0.152844f}, +{-0.127626f,-0.480448f,0.158349f},{-0.123774f,-0.487624f,0.171255f},{-0.108836f,-0.486975f,0.174946f}, +{-0.0972541f,-0.48475f,0.168445f},{-0.0860262f,-0.472049f,0.170927f},{-0.0317193f,-0.462127f,0.16739f}, +{-0.0195074f,-0.461664f,0.163686f},{-0.0138999f,-0.461001f,0.171197f},{0.00345646f,-0.460332f,0.174149f}, +{0.020472f,-0.460217f,0.170593f},{0.0814025f,-0.458352f,0.187203f},{0.0982058f,-0.457516f,0.189885f}, +{0.116713f,-0.456294f,0.193749f},{0.163426f,-0.461734f,0.201215f},{0.172673f,-0.473039f,0.224829f}, +{0.190962f,-0.483579f,0.226391f},{0.3207f,-0.423974f,0.199479f},{-0.350211f,-0.23589f,0.23906f}, +{-0.372802f,-0.0054532f,0.268416f},{-0.354828f,-0.243812f,0.226546f},{-0.445172f,0.00870071f,0.23097f}, +{-0.465107f,0.026012f,0.20773f},{-0.383052f,-0.00770392f,0.261021f},{-0.278547f,-0.488486f,0.151063f}, +{-0.261725f,-0.489521f,0.152651f},{-0.138005f,-0.479644f,0.167017f},{-0.131626f,-0.47994f,0.168599f}, +{-0.0711907f,-0.463959f,0.171358f},{-0.0532813f,-0.462229f,0.17348f},{-0.0302338f,-0.461464f,0.172908f}, +{0.0242275f,-0.459458f,0.188058f},{0.0425677f,-0.458963f,0.190219f},{0.0601042f,-0.458538f,0.191704f}, +{0.137774f,-0.454886f,0.20726f},{0.15459f,-0.455985f,0.208778f},{0.166075f,-0.460931f,0.212662f}, +{0.177631f,-0.48212f,0.233947f},{0.193913f,-0.484312f,0.24506f},{0.212652f,-0.483058f,0.246622f}, +{0.225128f,-0.480923f,0.248449f},{0.233648f,-0.477187f,0.246847f},{-0.355857f,-0.28183f,0.153378f}, +{-0.389238f,-0.260448f,0.134658f},{-0.29737f,-0.260757f,0.243446f},{-0.366872f,-0.275046f,0.145224f}, +{-0.296643f,-0.483894f,0.154587f},{-0.259275f,-0.488955f,0.167995f},{-0.24215f,-0.488672f,0.171326f}, +{-0.226247f,-0.489778f,0.173358f},{-0.173753f,-0.489907f,0.179936f},{-0.164159f,-0.489193f,0.188155f}, +{-0.146957f,-0.487881f,0.188341f},{-0.128861f,-0.487406f,0.192856f},{-0.109312f,-0.485245f,0.189383f}, +{-0.0912736f,-0.46994f,0.193383f},{-0.0498216f,-0.461586f,0.186348f},{-0.0412624f,-0.461799f,0.182515f}, +{-0.0314492f,-0.460648f,0.190425f},{-0.0145751f,-0.460024f,0.191029f},{0.00553356f,-0.459869f,0.190386f}, +{0.0617761f,-0.457394f,0.206656f},{0.0800842f,-0.456963f,0.208874f},{0.0986302f,-0.456082f,0.210733f}, +{0.119144f,-0.455124f,0.209453f},{0.156075f,-0.45668f,0.222276f},{0.1574f,-0.484126f,0.250918f}, +{0.175335f,-0.48446f,0.247175f},{-0.319626f,-0.20782f,0.27165f},{-0.329877f,-0.29554f,0.165345f}, +{-0.318694f,-0.29873f,0.170175f},{-0.451796f,0.0128677f,0.223247f},{-0.303878f,-0.310151f,0.0028488f}, +{-0.280785f,-0.321527f,0.0187711f},{-0.278489f,-0.302846f,0.191081f},{-0.360262f,-0.250693f,0.212096f}, +{-0.298862f,-0.485206f,0.169969f},{-0.280046f,-0.487785f,0.172785f},{-0.223237f,-0.487393f,0.190052f}, +{-0.205553f,-0.488795f,0.189325f},{-0.185952f,-0.488698f,0.193376f},{-0.113517f,-0.484608f,0.203151f}, +{-0.0732742f,-0.462783f,0.191261f},{-0.0518279f,-0.461342f,0.191479f},{0.00290986f,-0.458802f,0.206257f}, +{0.0227742f,-0.458345f,0.209923f},{0.0404006f,-0.457934f,0.210283f},{0.116302f,-0.454487f,0.225697f}, +{0.13398f,-0.453992f,0.225973f},{0.142211f,-0.48338f,0.250995f},{0.15612f,-0.484962f,0.26394f}, +{0.17347f,-0.484872f,0.265689f},{0.192113f,-0.483811f,0.267413f},{0.207804f,-0.482261f,0.267342f}, +{0.217199f,-0.479657f,0.268879f},{-0.414048f,-0.00264297f,0.20955f},{-0.455635f,0.0113823f,0.240758f}, +{-0.401682f,-0.00607055f,0.25549f},{-0.316006f,-0.307752f,0.0176329f},{-0.299061f,-0.317032f,0.0229575f}, +{-0.281904f,-0.324832f,0.0377994f},{-0.261352f,-0.487695f,0.187943f},{-0.241513f,-0.487174f,0.191672f}, +{-0.165979f,-0.486139f,0.206688f},{-0.146667f,-0.486634f,0.211189f},{-0.128224f,-0.486563f,0.209337f}, +{-0.106476f,-0.478358f,0.212508f},{-0.0748819f,-0.461889f,0.204566f},{-0.0675123f,-0.461014f,0.207228f}, +{-0.052889f,-0.460262f,0.208797f},{-0.0343944f,-0.459464f,0.210733f},{-0.0158484f,-0.45931f,0.208643f}, +{0.0431722f,-0.456564f,0.225253f},{0.0604064f,-0.456101f,0.228379f},{0.0771647f,-0.45569f,0.22915f}, +{0.0974213f,-0.455252f,0.228295f},{0.117942f,-0.484197f,0.266962f},{0.133774f,-0.484261f,0.266236f}, +{0.319678f,-0.462101f,0.0100833f},{-0.443185f,0.00970388f,0.279348f},{-0.382152f,-0.251735f,0.178387f}, +{-0.313729f,-0.311791f,0.0269573f},{-0.312694f,-0.315971f,0.0396258f},{-0.29856f,-0.32015f,0.0378252f}, +{-0.282194f,-0.328124f,0.0543841f},{-0.275146f,-0.331256f,0.0615222f},{-0.298875f,-0.486126f,0.189126f}, +{-0.282123f,-0.487528f,0.192798f},{-0.220871f,-0.486287f,0.207678f},{-0.202588f,-0.485798f,0.20998f}, +{-0.184055f,-0.485541f,0.208038f},{-0.129433f,-0.485984f,0.226739f},{-0.117099f,-0.484383f,0.226829f}, +{-0.0914215f,-0.464364f,0.209132f},{-0.0150767f,-0.458095f,0.225035f},{0.0032764f,-0.457169f,0.22915f}, +{0.0200283f,-0.456757f,0.229909f},{0.0820456f,-0.4543f,0.243561f},{0.100437f,-0.453754f,0.239143f}, +{0.101254f,-0.48284f,0.26522f},{0.139619f,-0.48448f,0.283238f},{0.155567f,-0.484357f,0.282377f}, +{0.174236f,-0.482827f,0.286017f},{0.19356f,-0.481251f,0.28282f},{0.213694f,-0.473342f,0.284544f}, +{-0.242465f,-0.257683f,0.258429f},{-0.335754f,-0.305109f,0.0369313f},{-0.32342f,-0.309578f,0.0344619f}, +{-0.300225f,-0.323256f,0.0579403f},{-0.311357f,-0.484068f,0.191132f},{-0.277576f,-0.487013f,0.207569f}, +{-0.25948f,-0.486852f,0.210135f},{-0.241854f,-0.486299f,0.210591f},{-0.185361f,-0.485168f,0.225523f}, +{-0.167522f,-0.485534f,0.228469f},{-0.149304f,-0.485862f,0.22697f},{-0.100997f,-0.468261f,0.221974f}, +{-0.0878911f,-0.461992f,0.219286f},{-0.0706183f,-0.459451f,0.225189f},{-0.0522459f,-0.4585f,0.229279f}, +{-0.0360921f,-0.45814f,0.230366f},{0.0267998f,-0.455291f,0.245587f},{0.0428314f,-0.45495f,0.244558f}, +{0.0601749f,-0.454275f,0.24751f},{0.0805086f,-0.483103f,0.272724f},{0.100457f,-0.484537f,0.283888f}, +{0.11845f,-0.48464f,0.286525f},{0.194846f,-0.476898f,0.290769f},{-0.349381f,-0.00533103f,0.254024f}, +{-0.417945f,0.0292209f,0.315366f},{-0.35014f,-0.296383f,0.0380181f},{-0.328983f,-0.311906f,0.0467638f}, +{-0.319491f,-0.316652f,0.0564419f},{-0.299132f,-0.326459f,0.075496f},{-0.28065f,-0.331513f,0.0751037f}, +{-0.259783f,-0.338375f,0.0941963f},{-0.314077f,-0.483554f,0.206939f},{-0.300154f,-0.486422f,0.211247f}, +{-0.241365f,-0.485901f,0.227195f},{-0.223919f,-0.485676f,0.227915f},{-0.206376f,-0.48529f,0.228481f}, +{-0.145735f,-0.485734f,0.244552f},{-0.127813f,-0.485907f,0.247388f},{-0.113614f,-0.485888f,0.249619f}, +{-0.0850616f,-0.460905f,0.230385f},{-0.0312048f,-0.45677f,0.244764f},{-0.0144465f,-0.456352f,0.245529f}, +{0.00379085f,-0.455651f,0.249831f},{0.0451786f,-0.482885f,0.284441f},{0.0596797f,-0.483939f,0.285316f}, +{0.0820391f,-0.484435f,0.285759f},{0.135118f,-0.482891f,0.295347f},{0.152448f,-0.480988f,0.297219f}, +{0.155619f,-0.473052f,0.30219f},{0.175303f,-0.470782f,0.298473f},{-0.256394f,-0.259394f,0.256828f}, +{-0.341291f,-0.307823f,0.0568599f},{-0.331067f,-0.314022f,0.0603389f},{-0.315787f,-0.320806f,0.0768979f}, +{-0.290624f,-0.329906f,0.0832063f},{-0.278303f,-0.333411f,0.0946529f},{-0.297794f,-0.486184f,0.226649f}, +{-0.280695f,-0.486621f,0.22787f},{-0.263139f,-0.486177f,0.228424f},{-0.201752f,-0.485181f,0.243426f}, +{-0.185348f,-0.484968f,0.246744f},{-0.16666f,-0.485489f,0.248352f},{-0.109061f,-0.485309f,0.264403f}, +{-0.0923604f,-0.485817f,0.270088f},{-0.0695573f,-0.45857f,0.245883f},{-0.0523038f,-0.456828f,0.247941f}, +{0.0218867f,-0.454416f,0.258217f},{0.0355583f,-0.453278f,0.26003f},{0.0248191f,-0.483174f,0.288042f}, +{0.0613839f,-0.48466f,0.301817f},{0.0791196f,-0.483997f,0.302061f},{0.0968168f,-0.483444f,0.302344f}, +{0.118443f,-0.481592f,0.302299f},{0.136578f,-0.474036f,0.304775f},{-0.285788f,-0.286988f,0.217832f}, +{-0.361201f,-0.288962f,0.044198f},{-0.353516f,-0.299328f,0.0592907f},{-0.334938f,-0.313469f,0.075631f}, +{-0.300444f,-0.327224f,0.0950195f},{-0.289094f,-0.33123f,0.0962799f},{-0.26431f,-0.335848f,0.111283f}, +{-0.312752f,-0.483721f,0.229716f},{-0.258432f,-0.485978f,0.243246f},{-0.242124f,-0.485926f,0.24668f}, +{-0.223096f,-0.4852f,0.249008f},{-0.165155f,-0.484962f,0.263406f},{-0.147092f,-0.48484f,0.26603f}, +{-0.129935f,-0.485161f,0.267162f},{-0.0487541f,-0.456423f,0.25695f},{-0.0353011f,-0.455644f,0.259033f}, +{-0.0153146f,-0.454911f,0.262506f},{0.00496123f,-0.453651f,0.265573f},{0.00542424f,-0.483746f,0.291965f}, +{0.0242018f,-0.484139f,0.303103f},{0.0405871f,-0.484306f,0.304479f},{0.101897f,-0.480113f,0.309109f}, +{-0.335484f,0.250577f,-0.225986f},{-0.362898f,-0.290209f,0.0564741f},{-0.354339f,-0.30028f,0.0768464f}, +{-0.334616f,-0.314601f,0.0951159f},{-0.31888f,-0.321205f,0.0966464f},{-0.298135f,-0.325674f,0.114292f}, +{-0.278232f,-0.331443f,0.114922f},{-0.298148f,-0.485489f,0.24551f},{-0.279885f,-0.486229f,0.248931f}, +{-0.220935f,-0.485341f,0.264673f},{-0.203469f,-0.485058f,0.265387f},{-0.185444f,-0.485007f,0.268075f}, +{-0.126944f,-0.483637f,0.281663f},{-0.108611f,-0.48428f,0.285001f},{-0.0926562f,-0.484164f,0.286441f}, +{-0.0740266f,-0.484402f,0.286891f},{-0.0540337f,-0.483515f,0.290383f},{-0.0409344f,-0.48338f,0.293058f}, +{-0.0318029f,-0.483586f,0.301965f},{-0.0139385f,-0.483849f,0.304878f},{0.00431173f,-0.484074f,0.303334f}, +{0.0427028f,-0.482505f,0.318678f},{0.0602649f,-0.482962f,0.314331f},{0.0708948f,-0.481316f,0.315218f}, +{0.0968618f,-0.47529f,0.315025f},{-0.306276f,-0.2129f,0.274448f},{-0.368274f,-0.235009f,0.223163f}, +{-0.343388f,-0.30862f,0.0938362f},{-0.316758f,-0.31979f,0.114929f},{-0.283467f,-0.329102f,0.11871f}, +{-0.263911f,-0.331243f,0.130414f},{-0.311948f,-0.482968f,0.245992f},{-0.279628f,-0.485631f,0.266242f}, +{-0.260149f,-0.485907f,0.265136f},{-0.242015f,-0.48565f,0.267689f},{-0.183419f,-0.483496f,0.282338f}, +{-0.167593f,-0.48246f,0.280197f},{-0.148552f,-0.481676f,0.279997f},{-0.0880133f,-0.48347f,0.301084f}, +{-0.0700074f,-0.483457f,0.30381f},{-0.0527539f,-0.483599f,0.304813f},{0.00475545f,-0.483721f,0.320067f}, +{0.0216552f,-0.482628f,0.322813f},{-0.315678f,-0.293425f,0.183177f},{-0.3545f,-0.298074f,0.0959712f}, +{-0.331002f,-0.314266f,0.112832f},{-0.299164f,-0.320819f,0.132941f},{-0.279917f,-0.325578f,0.134388f}, +{-0.309144f,-0.482165f,0.257034f},{-0.299048f,-0.483946f,0.266159f},{-0.240542f,-0.485373f,0.28266f}, +{-0.223237f,-0.485193f,0.284589f},{-0.204627f,-0.483972f,0.28646f},{-0.123247f,-0.481599f,0.296454f}, +{-0.107987f,-0.482968f,0.305257f},{-0.0522073f,-0.483193f,0.321269f},{-0.0351532f,-0.483669f,0.322601f}, +{-0.0178998f,-0.483727f,0.323604f},{0.0305231f,-0.480197f,0.328266f},{-0.328674f,-0.273869f,0.208707f}, +{-0.40064f,-0.25104f,0.0488023f},{-0.362519f,-0.289849f,0.0907431f},{-0.341458f,-0.307572f,0.116331f}, +{-0.455841f,0.0126491f,0.268384f},{-0.279165f,-0.484795f,0.283148f},{-0.261506f,-0.485431f,0.286383f}, +{-0.184827f,-0.479534f,0.292415f},{-0.117035f,-0.480132f,0.307701f},{-0.0909135f,-0.482885f,0.320941f}, +{-0.0729848f,-0.482994f,0.32379f},{-0.0158548f,-0.482525f,0.33678f},{0.00325711f,-0.481226f,0.334362f}, +{-0.38266f,-0.270782f,0.0813672f},{-0.332115f,-0.310569f,0.126498f},{-0.307389f,-0.476396f,0.27412f}, +{-0.294328f,-0.482017f,0.281894f},{-0.222954f,-0.482865f,0.298061f},{-0.101678f,-0.480898f,0.322433f}, +{-0.186711f,0.248082f,-0.0135751f},{-0.337054f,0.254307f,-0.24396f},{-0.202891f,0.23162f,0.00283594f}, +{-0.286515f,0.364336f,-0.372927f},{-0.335414f,0.260551f,-0.265149f},{-0.052098f,0.307945f,-0.0569499f}, +{-0.164152f,0.26057f,-0.024578f},{-0.305337f,0.251098f,-0.166741f},{-0.313472f,0.285663f,-0.283026f}, +{-0.302617f,0.215832f,-0.0542812f},{-0.316019f,0.243555f,-0.169171f},{-0.300804f,0.227684f,-0.0737082f}, +{-0.181181f,0.274711f,-0.031671f},{-0.0531462f,0.322594f,-0.0754188f},{-0.312958f,0.259509f,-0.210025f}, +{-0.302566f,0.23205f,-0.0907302f},{0.0233979f,0.356484f,-0.115662f},{-0.316771f,0.22252f,-0.0919199f}, +{0.00642742f,0.340221f,-0.0927558f},{-0.319633f,0.270544f,-0.262069f},{-0.31789f,0.215247f,-0.0716247f}, +{-0.182493f,0.241632f,-0.00751742f},{-0.145638f,0.259972f,-0.0193563f},{-0.318405f,0.226957f,-0.111032f}, +{-0.165168f,0.271027f,-0.0328992f},{-0.109562f,0.2732f,-0.0165396f},{-0.107003f,0.281837f,-0.0365711f}, +{-0.323716f,0.274055f,-0.296479f},{-0.16659f,0.243259f,0.0006045f},{-0.319781f,0.265651f,-0.243709f}, +{-0.148924f,0.270326f,-0.0333044f},{-0.0362722f,0.324607f,-0.0756438f},{-0.311234f,0.220604f,-0.0803125f}, +{-0.3227f,0.271239f,-0.284229f},{-0.323588f,0.256815f,-0.22843f},{-0.220781f,0.206829f,0.0185396f}, +{-0.167914f,0.251284f,-0.0151699f},{-0.0329861f,0.310312f,-0.0527443f},{-0.127337f,0.273367f,-0.0322948f}, +{-0.317382f,0.284158f,-0.302556f},{-0.127871f,0.280666f,-0.0416578f},{-0.187341f,0.234301f,0.00378125f}, +{0.0259895f,0.366509f,-0.128723f},{0.0123822f,0.333057f,-0.0819716f},{-0.313787f,0.265008f,-0.224777f}, +{-0.309061f,0.301977f,-0.319835f},{-0.303871f,0.269368f,-0.2069f},{-0.0717501f,0.30253f,-0.055368f}, +{-0.333034f,0.229555f,-0.136619f},{-0.316327f,0.232745f,-0.128478f},{-0.338886f,0.200733f,-0.0441529f}, +{0.00223463f,0.350227f,-0.103682f},{0.025295f,0.350658f,-0.108054f},{-0.0903219f,0.299785f,-0.0571364f}, +{-0.209681f,0.219363f,0.0212341f},{-0.337054f,0.234758f,-0.152272f},{-0.301788f,0.250449f,-0.149892f}, +{-0.313421f,0.313733f,-0.349333f},{-0.353355f,0.231812f,-0.127121f},{-0.0154754f,0.325109f,-0.0739269f}, +{-0.312546f,0.202521f,-0.0423137f},{-0.109736f,0.29165f,-0.0513167f},{-0.0731263f,0.292106f,-0.0378766f}, +{-0.305575f,0.235472f,-0.111611f},{-0.311228f,0.241394f,-0.150471f},{-0.305884f,0.31271f,-0.343847f}, +{-0.336295f,0.224366f,-0.114556f},{-0.0516286f,0.299514f,-0.0367383f},{-0.343401f,0.237722f,-0.161197f}, +{-0.0322787f,0.314794f,-0.0613163f},{-0.321163f,0.251079f,-0.204977f},{-0.309453f,0.279605f,-0.247323f}, +{-0.316295f,0.249214f,-0.188206f},{-0.0175589f,0.332697f,-0.0845503f},{-0.305434f,0.309495f,-0.323842f}, +{-0.21671f,0.219761f,0.014752f},{-0.306656f,0.273541f,-0.222578f},{-0.334147f,0.213974f,-0.0735667f}, +{-0.173252f,0.237356f,0.018829f},{-0.202177f,0.223048f,0.0189447f},{-0.311614f,0.284152f,-0.265889f}, +{-0.332655f,0.221189f,-0.0998553f},{-0.0605351f,0.294479f,-0.0306485f},{-0.305781f,0.256326f,-0.181872f}, +{-0.0967461f,0.291045f,-0.0472396f} +}; +F32 normals [8127][3] = { +{-0.832532f,-0.483226f,0.270894f},{-0.606785f,-0.758191f,0.23866f},{-0.859166f,-0.504979f,0.0826439f}, +{-0.240595f,0.95358f,-0.181105f},{-0.265911f,0.923509f,-0.276446f},{-0.24535f,0.952364f,-0.181124f}, +{-0.25194f,0.962547f,-0.100147f},{-0.246923f,0.963593f,-0.102551f},{-0.246718f,0.967484f,-0.0557298f}, +{-0.256239f,0.966543f,-0.0116836f},{-0.268399f,0.963145f,0.0176902f},{-0.202139f,0.974793f,-0.094442f}, +{-0.233286f,0.967463f,-0.0979436f},{-0.222973f,0.972356f,-0.0693283f},{-0.216571f,0.976026f,-0.0216751f}, +{-0.248769f,0.966837f,-0.0577895f},{-0.251386f,0.966483f,-0.0521194f},{-0.221256f,0.940455f,-0.258053f}, +{-0.203433f,0.958112f,-0.201585f},{-0.185273f,0.9538f,-0.236513f},{-0.202063f,0.969567f,-0.138239f}, +{-0.277194f,0.958376f,0.0684025f},{-0.323528f,0.932333f,0.16151f},{-0.301745f,0.944805f,0.127648f}, +{-0.263034f,0.964572f,0.0203178f},{-0.298731f,0.932268f,0.204049f},{-0.339448f,0.915328f,0.21668f}, +{-0.256592f,0.921503f,0.291534f},{-0.336811f,0.892243f,0.300767f},{-0.288256f,0.886991f,0.36077f}, +{-0.129143f,0.959514f,-0.25031f},{-0.104011f,0.980522f,-0.166606f},{-0.0715617f,0.949967f,-0.304043f}, +{-0.193212f,0.979873f,0.0501861f},{-0.264442f,0.963268f,0.0467423f},{-0.202161f,0.973002f,0.111346f}, +{0.780632f,0.608123f,0.144219f},{0.893498f,0.412557f,0.177367f},{0.918654f,0.348847f,0.185421f}, +{-0.235108f,0.898248f,0.371314f},{-0.0326766f,0.912646f,0.407443f},{-0.281134f,0.890492f,0.357753f}, +{-0.717587f,-0.696125f,-0.0218705f},{-0.848457f,-0.528571f,-0.0270921f},{-0.865329f,-0.500314f,-0.029859f}, +{-0.121792f,0.898055f,0.422685f},{0.101699f,0.854369f,0.509619f},{-0.140896f,0.896477f,0.420093f}, +{-0.808979f,-0.581233f,0.0878659f},{-0.770083f,-0.637941f,0.00173141f},{-0.756549f,-0.652533f,0.0428222f}, +{0.0971026f,0.799445f,0.592839f},{-0.145356f,0.83366f,0.532806f},{-0.00267177f,0.775686f,0.631113f}, +{-4.50738e-05f,-0.710945f,-0.703248f},{-0.0235271f,-0.794254f,-0.60713f},{0.259285f,-0.884284f,-0.388348f}, +{-0.945709f,0.157642f,0.284225f},{-0.904527f,0.212372f,0.36977f},{-0.885759f,0.334308f,0.321977f}, +{-0.263036f,0.950565f,0.16504f},{-0.323719f,0.90817f,0.265394f},{-0.28809f,0.926484f,0.242139f}, +{-0.95459f,0.0441123f,-0.29464f},{-0.969511f,-0.169456f,-0.177012f},{-0.954454f,-0.223864f,-0.197238f}, +{0.611328f,0.777271f,0.148758f},{0.684309f,0.653679f,0.323149f},{0.915668f,0.320355f,0.242746f}, +{0.819723f,0.357047f,0.447853f},{0.422096f,0.860388f,0.285599f},{0.187582f,0.923427f,0.334806f}, +{0.432844f,0.790824f,0.432717f},{0.0680594f,-0.941994f,0.328655f},{-0.290907f,-0.832029f,0.472335f}, +{0.146264f,-0.741517f,0.654797f},{-0.735765f,-0.280452f,0.616439f},{-0.722188f,-0.547927f,0.422161f}, +{-0.768976f,-0.134488f,0.624971f},{-0.453323f,0.851967f,-0.262013f},{-0.381382f,0.908381f,-0.171438f}, +{-0.417129f,0.849952f,-0.321845f},{0.254919f,0.880365f,0.399968f},{-0.190589f,0.854832f,0.482637f}, +{-0.210229f,0.889628f,0.405419f},{0.708171f,0.617f,0.343227f},{0.614328f,0.678828f,0.402236f}, +{0.720949f,0.588198f,0.36641f},{-0.346537f,0.869928f,0.350909f},{-0.60894f,0.719521f,0.333889f}, +{-0.33076f,0.856751f,0.395696f},{0.0127889f,0.924737f,0.380393f},{-0.142866f,0.666357f,0.731818f}, +{-0.034274f,0.677512f,0.734712f},{-0.0935795f,0.661009f,0.74452f},{-0.509054f,0.763082f,0.398208f}, +{0.224064f,-0.899254f,-0.375683f},{-0.272324f,-0.820453f,-0.50269f},{0.575326f,-0.786607f,-0.224161f}, +{-0.0317741f,0.983694f,0.177022f},{-0.208638f,0.977128f,-0.0411155f},{-0.0326322f,0.9994f,-0.0116285f}, +{-0.959056f,-0.281756f,0.0287399f},{-0.968295f,-0.230842f,0.0954859f},{-0.932971f,-0.284521f,-0.220485f}, +{-0.936459f,-0.146232f,-0.318844f},{-0.385196f,0.795521f,0.467728f},{-0.571132f,0.811969f,-0.120481f}, +{-0.285455f,0.884252f,0.369613f},{0.770701f,0.508164f,0.384433f},{0.653749f,0.544785f,0.525187f}, +{0.911702f,0.212638f,0.351546f},{-0.149382f,0.141765f,-0.978564f},{-0.215627f,0.158924f,-0.963456f}, +{-0.172656f,0.332476f,-0.927173f},{-0.11137f,0.20172f,-0.973091f},{-0.120247f,0.218757f,-0.968342f}, +{-0.116053f,0.208667f,-0.971077f},{-0.110441f,0.873446f,0.474232f},{-0.120825f,0.884438f,0.450746f}, +{-0.869758f,0.377088f,0.318317f},{-0.925243f,0.175597f,0.33629f},{-0.919523f,0.0685275f,0.387016f}, +{-0.482565f,0.874002f,0.0570216f},{-0.328802f,0.876258f,0.352222f},{-0.120162f,0.895601f,0.428322f}, +{0.118699f,0.915735f,0.38385f},{-0.89304f,0.449975f,0.00125259f},{-0.956562f,0.289896f,0.0308044f}, +{-0.950694f,0.305572f,-0.0529737f},{-0.202962f,0.974794f,-0.0926428f},{-0.149628f,0.983888f,-0.0978547f}, +{-0.14631f,0.98241f,-0.116032f},{0.694537f,0.621044f,0.363211f},{0.620447f,0.729884f,0.286908f}, +{0.765911f,0.603359f,0.22212f},{0.852062f,0.457413f,0.254488f},{0.752783f,0.532628f,0.386814f}, +{-0.239568f,0.74193f,0.626216f},{-0.075257f,0.613884f,0.785801f},{0.123843f,0.376122f,0.918257f}, +{-0.474381f,0.23936f,-0.847154f},{-0.455168f,-0.811384f,-0.366713f},{-0.213933f,-0.514973f,-0.830082f}, +{-0.254102f,0.948463f,-0.189341f},{-0.367517f,0.829036f,0.421461f},{-0.361859f,0.854456f,0.372778f}, +{-0.322071f,0.851882f,0.412998f},{-0.13554f,0.98971f,-0.045867f},{-0.160853f,0.986362f,-0.0348832f}, +{-0.131247f,0.991314f,0.00835482f},{-0.542619f,0.828757f,0.136845f},{-0.343869f,0.809416f,0.476025f}, +{-0.462981f,0.852059f,0.244222f},{-0.263369f,0.901217f,0.344157f},{-0.338921f,0.884725f,0.319992f}, +{-0.511586f,0.803891f,0.303379f},{0.874238f,0.377572f,0.305199f},{0.829018f,0.495984f,0.25832f}, +{0.792623f,0.48816f,0.365306f},{0.195735f,0.962956f,0.18548f},{0.116284f,0.964873f,0.235578f}, +{0.190608f,0.956302f,0.221709f},{-0.25398f,0.966403f,-0.0394976f},{-0.378434f,0.83816f,0.39278f}, +{0.662297f,0.72069f,0.204862f},{0.530429f,0.822517f,0.205209f},{0.684042f,0.704221f,0.190155f}, +{-0.0569504f,0.997668f,-0.0376106f},{0.0157645f,0.999316f,-0.0334653f},{0.0157935f,0.995784f,-0.0903618f}, +{-0.422862f,-0.852884f,-0.306229f},{-0.12403f,-0.948113f,-0.292743f},{-0.622856f,-0.765261f,-0.162564f}, +{-0.901512f,-0.161439f,0.401515f},{-0.83239f,-0.442392f,0.333789f},{-0.279616f,0.315223f,-0.90689f}, +{-0.187629f,0.239955f,-0.952479f},{-0.201018f,0.207097f,-0.957446f},{-0.351005f,0.870672f,0.344566f}, +{-0.356193f,0.83271f,0.423934f},{-0.930524f,0.364666f,-0.0338037f},{-0.934408f,0.34659f,-0.0822029f}, +{-0.581141f,-0.79047f,-0.193474f},{-0.401732f,-0.866777f,-0.295481f},{0.219798f,0.943023f,0.249793f}, +{0.441648f,0.744463f,0.500721f},{-0.170662f,0.962922f,-0.208942f},{-0.140103f,0.977176f,-0.15968f}, +{-0.174618f,0.973903f,-0.14499f},{-0.816847f,-0.468775f,-0.336171f},{-0.897091f,0.0904234f,-0.432495f}, +{-0.65512f,-0.498546f,-0.567689f},{-0.207628f,0.956946f,0.202842f},{-0.168441f,0.72412f,0.668788f}, +{-0.0670168f,0.91956f,0.387192f},{0.311933f,0.593536f,0.741898f},{0.17352f,0.787655f,0.591177f}, +{0.180038f,0.58603f,0.790035f},{-0.861929f,-0.365565f,0.351341f},{-0.914147f,-0.309319f,0.262025f}, +{-0.791212f,-0.558632f,0.248825f},{-0.053992f,0.998524f,0.00585528f},{0.654485f,0.747701f,0.112219f}, +{0.34414f,0.92576f,0.15664f},{-0.219221f,0.914674f,0.339579f},{-0.258173f,0.858933f,0.442246f}, +{-0.143065f,0.905573f,0.399337f},{0.84601f,0.500754f,0.183061f},{0.317856f,0.577094f,0.752283f}, +{0.161512f,0.786348f,0.596297f},{0.21221f,0.640586f,0.737981f},{-0.166751f,0.982628f,-0.0814596f}, +{-0.188703f,0.980337f,-0.0577052f},{-0.17194f,0.985102f,-0.00323838f},{-0.234144f,0.969227f,0.0759959f}, +{-0.208683f,0.971131f,0.115569f},{-0.17246f,0.983756f,0.0498251f},{-0.837886f,-0.545703f,0.0124777f}, +{-0.881945f,-0.46478f,0.0784393f},{-0.936872f,-0.341756f,-0.073981f},{-0.152191f,0.98821f,0.0167007f}, +{0.529805f,0.839465f,0.12085f},{0.35177f,0.933835f,0.0648909f},{0.645035f,0.758214f,0.0950878f}, +{0.897254f,0.430807f,0.0966417f},{0.82479f,0.54689f,0.143639f},{0.833868f,0.533647f,0.141017f}, +{0.398204f,0.869206f,-0.293111f},{0.368214f,0.912358f,-0.178942f},{0.460623f,0.862636f,-0.209012f}, +{0.757324f,0.632608f,0.162072f},{-0.196495f,0.847757f,0.492644f},{-0.281075f,0.806466f,0.520201f}, +{-0.178493f,0.831846f,0.525522f},{-0.965141f,0.251922f,-0.070975f},{-0.968395f,0.204029f,-0.143468f}, +{-0.98048f,0.122575f,-0.153733f},{-0.181622f,0.862125f,0.473026f},{-0.24619f,0.905469f,0.345711f}, +{-0.228317f,0.86714f,0.44265f},{-0.366278f,0.419914f,-0.830369f},{-0.283198f,0.181294f,-0.94177f}, +{-0.125999f,0.144635f,-0.98143f},{-0.139033f,0.985514f,-0.0971168f},{-0.689549f,0.612864f,0.385901f}, +{-0.466431f,0.7979f,0.381836f},{-0.429588f,0.835885f,0.341687f},{0.92412f,0.353599f,-0.144809f}, +{0.860566f,0.471663f,-0.192248f},{0.897956f,0.425678f,-0.111685f},{0.207155f,0.859726f,0.466859f}, +{0.500149f,-0.865185f,-0.036124f},{0.196539f,-0.87016f,-0.451878f},{0.407742f,-0.874513f,-0.262628f}, +{-0.165294f,0.961411f,0.219924f},{-0.174258f,0.925978f,0.334962f},{0.0209707f,0.954479f,-0.297539f}, +{-0.0411579f,0.958112f,-0.28342f},{-0.0430633f,0.986192f,-0.15991f},{-0.138388f,0.984372f,0.108905f}, +{-0.0864297f,0.979549f,0.181694f},{0.355448f,-0.675269f,-0.646272f},{0.982785f,-0.167672f,0.0775822f}, +{0.964467f,-0.220328f,0.145802f},{-0.106177f,0.990299f,-0.0896399f},{0.585177f,0.809954f,0.0392717f}, +{0.351387f,0.916776f,0.189866f},{0.28604f,0.941601f,-0.177678f},{-0.330406f,0.875489f,0.352634f}, +{0.421234f,0.845704f,0.327638f},{-0.000578385f,0.935843f,0.352418f},{0.797729f,0.521056f,0.303528f}, +{0.851595f,0.355876f,0.384888f},{0.833935f,0.461972f,0.301884f},{-0.281361f,0.950692f,0.130465f}, +{0.494345f,0.869264f,0.0014269f},{0.323062f,0.915374f,0.240255f},{0.236605f,0.903551f,0.357229f}, +{0.326762f,0.897894f,0.29498f},{-0.797988f,-0.554353f,0.236451f},{-0.715613f,-0.675862f,0.176376f}, +{-0.604376f,-0.751052f,0.265801f},{-0.986977f,-0.121592f,-0.105315f},{-0.980444f,-0.196796f,-0.000786544f}, +{-0.991928f,-0.0157392f,-0.12582f},{-0.164281f,0.191446f,-0.967657f},{-0.0978269f,0.992432f,0.074216f}, +{-0.0896928f,0.987081f,0.132768f},{-0.0518716f,0.987449f,0.149177f},{-0.0994765f,0.995001f,0.00874661f}, +{-0.0983143f,0.994234f,-0.0428241f},{-0.151126f,0.986293f,0.0662396f},{-0.105553f,0.987126f,0.120173f}, +{0.969847f,-0.00525719f,-0.243657f},{0.986459f,0.017394f,-0.163085f},{0.977914f,0.0433675f,-0.204458f}, +{0.785811f,0.528888f,0.32059f},{0.773372f,0.522143f,0.35953f},{0.866441f,0.410223f,0.2846f}, +{-0.51235f,0.858582f,0.0183004f},{0.220746f,0.613517f,0.7582f},{0.359838f,0.596592f,0.717353f}, +{0.263219f,0.954822f,-0.137951f},{0.236716f,0.968405f,-0.0784723f},{0.0393797f,0.987006f,-0.155785f}, +{-0.100373f,0.924854f,0.366837f},{-0.0789204f,0.886723f,0.455514f},{-0.0325974f,0.953865f,0.298462f}, +{-0.0996998f,0.958219f,0.268097f},{-0.124818f,0.990033f,0.0652242f},{0.858404f,0.470083f,0.20534f}, +{-0.351088f,0.90292f,0.247937f},{-0.266096f,0.623714f,0.734965f},{0.0546192f,0.518634f,0.85325f}, +{-0.00627665f,0.528708f,0.848781f},{0.0310592f,0.974423f,0.222564f},{0.225176f,0.961336f,0.158522f}, +{0.238458f,0.899672f,0.365688f},{0.112761f,0.816349f,0.566444f},{0.0456267f,0.831381f,0.553826f}, +{0.0552438f,0.806235f,0.589011f},{0.00507421f,0.703522f,0.710656f},{0.0903773f,0.728552f,0.679002f}, +{-0.417529f,0.874211f,0.247839f},{-0.503292f,0.783758f,0.363895f},{-0.760404f,-0.626818f,-0.169957f}, +{0.528696f,0.762456f,0.373016f},{-0.0576661f,0.99596f,0.0688321f},{-0.0702293f,0.979726f,0.187628f}, +{-0.050923f,0.994669f,-0.0896719f},{0.550602f,-0.79647f,0.249946f},{0.435275f,-0.856718f,0.276713f}, +{0.503614f,-0.851182f,0.147859f},{0.924068f,0.378481f,-0.0533867f},{0.91027f,0.413033f,-0.0285087f}, +{0.909985f,0.413706f,0.027824f},{0.151382f,0.614505f,0.774252f},{0.121735f,0.71707f,0.686288f}, +{-0.0654101f,0.661894f,0.746738f},{0.449607f,0.836161f,-0.314146f},{0.410327f,0.856681f,0.312616f}, +{-0.296897f,0.85146f,0.432283f},{0.657107f,-0.725019f,0.206298f},{0.302314f,-0.951098f,0.0633903f}, +{0.376669f,-0.920947f,0.0998864f},{0.358164f,0.823977f,0.439068f},{0.708367f,0.624326f,0.329293f}, +{0.265183f,0.860485f,0.435022f},{0.264323f,0.827217f,0.495829f},{-0.208083f,0.974072f,-0.088802f}, +{-0.0406154f,0.999099f,-0.0123014f},{-0.21748f,0.975297f,-0.0387011f},{0.811224f,0.532897f,0.240699f}, +{0.325329f,0.935848f,0.135462f},{0.298507f,0.933061f,0.200724f},{0.370731f,0.911821f,0.176466f}, +{0.177805f,0.850282f,0.495384f},{0.0782578f,0.861702f,0.501344f},{-0.862905f,-0.0410319f,0.503697f}, +{-0.780384f,-0.483438f,0.396596f},{-0.920067f,-0.0587012f,0.387339f},{0.120623f,0.907232f,0.402964f}, +{0.0912084f,0.872958f,0.479193f},{-0.223768f,0.248002f,-0.942562f},{-0.006037f,0.829609f,0.558313f}, +{-0.086073f,0.83628f,0.541505f},{-0.0751572f,0.794053f,0.603184f},{-0.1793f,0.932707f,0.312905f}, +{-0.35696f,0.931368f,0.0716437f},{0.723383f,0.662743f,0.193622f},{0.748428f,0.357617f,0.558539f}, +{0.382063f,0.804684f,0.454436f},{0.41987f,0.797587f,0.433088f},{0.60187f,0.723171f,0.338786f}, +{0.465439f,0.84667f,0.257909f},{0.604528f,0.760774f,0.236153f},{-0.14065f,0.878178f,0.457187f}, +{-0.156203f,0.926759f,0.341641f},{0.30724f,0.899647f,0.310225f},{0.983246f,0.063408f,-0.1709f}, +{0.96155f,0.0864831f,-0.260656f},{0.979863f,0.0529409f,-0.192523f},{0.0766483f,-0.724763f,-0.684722f}, +{0.0795982f,-0.728566f,-0.680335f},{0.240247f,-0.855735f,-0.458256f},{-0.168843f,0.172447f,-0.97044f}, +{-0.0397846f,0.99753f,-0.0578866f},{-0.187576f,0.976181f,-0.109024f},{-0.315613f,0.922592f,0.221838f}, +{-0.344408f,0.911946f,0.223021f},{-0.0535464f,0.762474f,0.6448f},{-0.150055f,0.814483f,0.560447f}, +{-0.11104f,0.754474f,0.646868f},{-0.838926f,0.210014f,-0.502093f},{-0.83238f,0.28807f,-0.473454f}, +{-0.797072f,0.470878f,-0.378087f},{-0.86169f,0.262894f,-0.434025f},{-0.815344f,0.425554f,-0.392578f}, +{0.57735f,0.57735f,0.57735f},{0.421857f,0.686834f,0.591857f},{-0.249799f,0.940143f,0.231799f}, +{-0.296301f,0.880261f,0.370604f},{0.0118961f,0.905903f,0.423318f},{0.0313234f,0.355953f,0.933979f}, +{0.0352841f,0.306272f,0.95129f},{-0.0893656f,0.270821f,0.958473f},{-0.713881f,0.490776f,-0.499512f}, +{-0.410273f,0.869831f,-0.273989f},{-0.894055f,0.352175f,-0.276838f},{-0.817393f,0.308118f,-0.486757f}, +{-0.875796f,0.121223f,-0.467212f},{0.669572f,-0.707121f,-0.227273f},{0.818913f,-0.535545f,-0.206332f}, +{0.84874f,-0.466955f,-0.248178f},{0.00732999f,0.884841f,0.465835f},{0.756543f,0.624106f,0.19528f}, +{0.00141355f,0.916707f,0.399559f},{0.0128935f,0.936489f,0.35046f},{0.555117f,-0.0206778f,-0.831515f}, +{0.733425f,-0.110287f,-0.670765f},{-0.336867f,0.804122f,0.489804f},{-0.547861f,0.721964f,-0.42263f}, +{-0.517719f,0.852632f,0.0706092f},{-0.659477f,0.748223f,0.0724769f},{0.00701106f,0.622457f,0.782623f}, +{-0.00356288f,0.708901f,0.705299f},{-0.0351258f,0.774387f,0.631736f},{-0.385602f,0.794284f,-0.469494f}, +{-0.409104f,0.75301f,-0.515373f},{-0.343588f,0.739178f,-0.579278f},{0.44348f,0.409973f,0.797024f}, +{0.172274f,0.553567f,0.814791f},{0.444401f,0.516545f,0.731907f},{-0.899018f,0.315509f,-0.303678f}, +{-0.0287092f,0.99401f,-0.105451f},{0.0238513f,0.996213f,-0.0836077f},{0.0498361f,0.992292f,-0.113461f}, +{-0.243806f,0.954737f,0.170399f},{0.935547f,0.345553f,-0.0731101f},{0.866464f,0.488525f,-0.10288f}, +{0.887404f,0.427894f,-0.171524f},{0.919118f,0.358308f,0.163822f},{-0.939902f,-0.308644f,0.146026f}, +{-0.985943f,-0.128784f,0.106447f},{-0.968025f,-0.242516f,0.0641434f},{-0.319472f,-0.322908f,0.890881f}, +{-0.212183f,-0.00597968f,0.977212f},{-0.460175f,-0.0230708f,0.887528f},{-0.311636f,0.272517f,0.910284f}, +{0.148652f,-0.861322f,-0.485827f},{0.249015f,-0.75173f,-0.61065f},{0.283906f,-0.838223f,-0.465596f}, +{-0.0533076f,-0.660568f,-0.748871f},{0.102555f,-0.808587f,-0.579369f},{0.78009f,0.447597f,0.437169f}, +{0.659098f,0.426598f,0.619358f},{0.700411f,0.468918f,0.538089f},{0.981422f,-0.0277144f,-0.189847f}, +{0.990634f,0.0115244f,-0.136058f},{0.969545f,-0.000610817f,-0.244911f},{-0.999433f,0.0331006f,-0.00624168f}, +{-0.969336f,0.0523667f,-0.240095f},{-0.985831f,-0.0135757f,-0.167188f},{-0.11484f,-0.497945f,-0.859571f}, +{0.145066f,-0.756207f,-0.638049f},{-0.992501f,0.0955116f,-0.0762813f},{0.0307977f,0.813263f,0.581081f}, +{-0.252137f,0.914982f,0.315017f},{0.0936196f,0.978662f,0.182908f},{0.0174373f,0.996305f,-0.0841026f}, +{0.0180337f,0.991815f,-0.126402f},{0.044532f,-0.527634f,-0.848304f},{0.0660403f,-0.585473f,-0.807998f}, +{0.100115f,-0.56197f,-0.821077f},{-0.359698f,0.0989121f,-0.927811f},{-0.43688f,0.0954944f,-0.894437f}, +{-0.37905f,0.204351f,-0.902531f},{0.000580824f,0.0870738f,-0.996202f},{0.128226f,0.0453576f,-0.990707f}, +{0.0914407f,-0.0043354f,-0.995801f},{-0.651647f,0.621586f,-0.434727f},{-0.803714f,0.589137f,-0.0834296f}, +{-0.559601f,0.820384f,0.117543f},{0.759113f,0.438907f,0.480737f},{0.573133f,0.785088f,0.234854f}, +{0.738396f,0.63573f,0.224987f},{-0.340618f,-0.160374f,0.926423f},{-0.0783287f,-0.180108f,0.980523f}, +{-0.46722f,-0.379277f,0.798658f},{-0.083493f,-0.271426f,0.958831f},{0.0491597f,-0.0662771f,0.996589f}, +{0.0134786f,0.189284f,0.98183f},{0.00493481f,0.319204f,0.947673f},{-0.0212024f,0.401033f,0.915818f}, +{-0.385416f,0.416591f,0.823351f},{-0.0488352f,0.466255f,0.883301f},{-0.290291f,0.499278f,0.816365f}, +{-0.128063f,0.542822f,0.830026f},{-0.129216f,0.549796f,0.825244f},{-0.32094f,0.562335f,0.762087f}, +{-0.267665f,0.633191f,0.72624f},{-0.033667f,0.973168f,0.227618f},{0.0168639f,0.985589f,0.168313f}, +{-0.566884f,0.712608f,0.413319f},{0.108542f,0.994073f,0.0061294f},{0.125559f,0.990888f,-0.0487381f}, +{0.0163092f,0.994743f,0.101096f},{-0.807412f,-0.589942f,-0.00735334f},{-0.838698f,-0.540232f,0.0688061f}, +{-0.881405f,-0.469341f,0.0533315f},{0.783992f,-0.593224f,-0.182874f},{0.992591f,-0.109235f,-0.0532095f}, +{0.818459f,-0.442588f,-0.366388f},{-0.974179f,-0.225775f,0.000958332f},{-0.958019f,-0.275422f,-0.0796435f}, +{0.707968f,-0.265502f,0.654438f},{0.636608f,-0.281028f,0.71816f},{0.661075f,-0.188496f,0.726257f}, +{0.0874356f,-0.56697f,-0.819085f},{0.0433353f,-0.476473f,-0.87812f},{0.11842f,-0.528335f,-0.840737f}, +{0.129623f,0.104684f,-0.986022f},{-0.998419f,-0.0542573f,-0.0146808f},{-0.688453f,0.673068f,-0.270208f}, +{0.0156894f,0.999522f,0.0266441f},{-0.868316f,-0.494385f,0.0401279f},{-0.18112f,0.613563f,0.768594f}, +{-0.15902f,0.663238f,0.73132f},{-0.271218f,0.702918f,0.657531f},{-0.257402f,0.951051f,-0.171013f}, +{0.981363f,0.0833966f,-0.173124f},{0.99075f,0.0716342f,-0.115248f},{0.973192f,0.181782f,-0.140896f}, +{0.945709f,0.233387f,-0.226198f},{0.914622f,0.287571f,-0.2842f},{0.942306f,0.247063f,-0.225875f}, +{-0.938596f,-0.343675f,-0.030422f},{-0.261763f,0.960149f,-0.097945f},{0.948742f,0.313295f,0.0416476f}, +{-0.95564f,-0.294067f,0.0166649f},{-0.957934f,-0.285956f,0.0243055f},{-0.972972f,-0.208945f,-0.0983253f}, +{-0.979352f,-0.200837f,-0.0230909f},{-0.991689f,-0.0808817f,-0.100057f},{-0.945654f,-0.320158f,-0.0568986f}, +{0.690643f,-0.557553f,-0.460594f},{-0.432122f,-0.506947f,-0.745839f},{0.275703f,-0.688974f,-0.6703f}, +{0.0354541f,0.98518f,-0.167817f},{-0.32858f,-0.451626f,0.829499f},{-0.404259f,-0.441563f,0.800997f}, +{-0.36329f,-0.46504f,0.807315f},{-0.505347f,-0.18888f,-0.841991f},{-0.461011f,-0.138001f,-0.876598f}, +{-0.492927f,-0.276201f,-0.825068f},{0.846028f,0.49353f,0.201657f},{-0.417774f,-0.370217f,0.829701f}, +{-0.372866f,-0.459566f,0.806083f},{-0.301297f,-0.340957f,0.890487f},{0.0386461f,0.5845f,0.810473f}, +{-0.117987f,0.798897f,0.589782f},{-0.252652f,0.769314f,0.586791f},{-0.0898694f,0.716685f,0.691582f}, +{0.984322f,0.159456f,0.0753966f},{0.981936f,0.164752f,0.0930547f},{0.991128f,0.103448f,0.0834458f}, +{0.288081f,0.922413f,0.257223f},{0.196528f,0.928668f,0.314568f},{-0.276998f,0.950331f,0.141926f}, +{-0.947481f,0.302294f,-0.104397f},{-0.880527f,0.290802f,-0.374307f},{-0.982123f,0.082863f,-0.16902f}, +{0.0308069f,0.690279f,0.722888f},{-0.136452f,0.749634f,0.647634f},{-0.0115687f,0.611309f,0.791308f}, +{0.235361f,0.812168f,0.533844f},{0.42321f,0.747665f,0.511752f},{-0.00294655f,0.777584f,0.628772f}, +{-0.0585491f,0.0909536f,-0.994133f},{0.139261f,0.0860385f,-0.986511f},{0.0998423f,0.12309f,-0.98736f}, +{0.103544f,0.953622f,-0.282637f},{0.124829f,0.977293f,-0.171217f},{0.194063f,0.963728f,-0.183217f}, +{-0.464984f,0.339376f,-0.817688f},{-0.437427f,0.436841f,-0.78602f},{-0.428677f,0.450404f,-0.783181f}, +{-0.197968f,0.136253f,-0.970692f},{-0.334069f,0.160007f,-0.928868f},{-0.237943f,0.127333f,-0.962896f}, +{-0.520129f,0.00462802f,-0.854075f},{-0.455464f,0.0948686f,-0.885185f},{-0.332832f,0.0880139f,-0.93887f}, +{-0.481026f,-0.475911f,-0.73629f},{-0.438014f,-0.337269f,-0.833302f},{-0.347452f,-0.547715f,-0.761108f}, +{-0.461713f,-0.302499f,-0.833856f},{-0.29253f,-0.428292f,0.854981f},{-0.254224f,-0.27951f,0.925875f}, +{-0.528736f,-0.202208f,0.824349f},{-0.27128f,-0.0983893f,0.957458f},{-0.119153f,0.0275033f,0.992495f}, +{-0.263646f,0.0184969f,0.964442f},{-0.203003f,-0.0257421f,0.97884f},{0.0881956f,0.0331346f,0.995552f}, +{0.289706f,0.260591f,0.920958f},{0.238046f,0.369706f,0.898138f},{0.186732f,0.437215f,0.879758f}, +{0.160958f,0.506869f,0.846863f},{0.14827f,0.502056f,0.85203f},{-0.00055618f,0.615531f,0.788112f}, +{-0.00716591f,0.635817f,0.771807f},{0.956568f,0.250869f,-0.148464f},{-0.0660797f,0.638031f,-0.76717f}, +{-0.0601667f,0.665157f,-0.744276f},{-0.140985f,0.719068f,-0.680488f},{0.783289f,-0.583404f,-0.214702f}, +{0.948733f,-0.302305f,-0.092287f},{0.965091f,-0.248812f,-0.0818032f},{-0.276455f,0.810676f,0.516117f}, +{-0.396968f,0.850953f,0.343942f},{-0.229114f,0.779813f,0.582579f},{0.119628f,0.98719f,-0.105573f}, +{0.212732f,0.971432f,-0.105193f},{-0.380376f,0.529341f,-0.758362f},{0.0247164f,-0.999286f,-0.0285692f}, +{0.0221136f,-0.999343f,-0.0287203f},{0.0171425f,-0.999755f,-0.01398f},{0.0309597f,-0.415753f,-0.908951f}, +{0.00724507f,-0.383477f,-0.923522f},{0.0188414f,-0.424607f,-0.905182f},{0.210939f,0.157303f,0.964759f}, +{-0.0297303f,0.747411f,0.663697f},{0.933011f,0.289119f,-0.214245f},{0.925733f,0.281444f,-0.252602f}, +{0.903201f,0.364361f,-0.226868f},{0.861819f,-0.100476f,0.497165f},{0.91382f,0.0166522f,0.405778f}, +{0.85081f,0.0456878f,0.523484f},{-0.922116f,0.28202f,-0.264888f},{-0.924562f,0.232143f,-0.302149f}, +{-0.916155f,0.231573f,-0.327161f},{0.801727f,0.22916f,-0.552014f},{0.806146f,0.340978f,-0.483593f}, +{0.844248f,0.252886f,-0.472539f},{-0.900107f,0.432791f,-0.0499879f},{-0.929252f,0.170879f,-0.327554f}, +{-0.889537f,0.200244f,-0.410641f},{-0.909881f,0.119352f,-0.397331f},{0.319048f,-0.927929f,0.19276f}, +{-0.0570574f,-0.998339f,0.00799306f},{-0.837236f,0.0170867f,-0.546575f},{-0.766305f,-0.107681f,-0.633389f}, +{-0.831586f,0.0795901f,-0.549664f},{0.377541f,0.316521f,0.870217f},{0.332509f,0.499481f,0.799972f}, +{0.460076f,0.517386f,0.721555f},{0.79541f,-0.566159f,-0.216304f},{0.911327f,-0.362557f,-0.195027f}, +{-0.74796f,-0.0695523f,-0.660089f},{-0.662762f,-0.235216f,-0.710929f},{-0.539195f,-0.222657f,-0.812214f}, +{0.0218027f,-0.433793f,0.900749f},{-0.00311731f,-0.163603f,0.986521f},{-0.0173914f,0.0456827f,0.998805f}, +{0.0400381f,0.178126f,0.983193f},{0.283483f,0.312944f,0.906479f},{0.134246f,-0.174071f,0.975539f}, +{0.212289f,-0.161507f,0.963768f},{0.229441f,-0.18882f,0.954832f},{0.412163f,0.305418f,0.858395f}, +{0.328776f,0.398539f,0.856197f},{0.424166f,0.363353f,0.829492f},{0.265865f,0.453263f,0.850804f}, +{0.18611f,0.546428f,0.816566f},{0.143454f,0.625752f,0.766718f},{0.120938f,0.656564f,0.744512f}, +{0.932122f,0.322601f,-0.16455f},{0.804729f,0.514737f,-0.29573f},{0.834634f,0.525578f,-0.164783f}, +{-0.988431f,0.0168326f,-0.150736f},{-0.992003f,-0.00163991f,-0.126204f},{-0.967043f,-0.0141034f,-0.254221f}, +{-0.00142562f,-0.579066f,-0.815279f},{-0.125423f,-0.384951f,-0.914375f},{-0.103741f,-0.44905f,-0.887464f}, +{-0.553529f,0.736759f,0.388319f},{-0.678903f,0.627379f,0.381427f},{-0.0588021f,0.897443f,0.437193f}, +{-0.943585f,-0.29927f,-0.141721f},{-0.965152f,-0.168369f,-0.200336f},{0.581388f,-0.170418f,-0.795579f}, +{0.894217f,0.330236f,-0.302192f},{0.891125f,-0.205523f,-0.404545f},{0.210297f,0.767182f,0.605977f}, +{0.00638082f,0.857806f,0.513935f},{0.470693f,0.834009f,0.287883f},{0.370541f,0.871789f,0.320441f}, +{0.440424f,0.862366f,0.249702f},{-0.873822f,-0.484636f,0.0395298f},{-0.891498f,-0.450702f,0.0458181f}, +{-0.87457f,-0.484822f,-0.00871369f},{0.197942f,0.935148f,-0.293798f},{0.197289f,-0.730094f,-0.654248f}, +{0.22188f,-0.765928f,-0.603426f},{0.238886f,-0.773997f,-0.586398f},{-0.683216f,-0.224521f,-0.694842f}, +{-0.127828f,0.30359f,-0.944189f},{-0.0162157f,0.431431f,-0.902f},{-0.193427f,0.498023f,-0.845316f}, +{-0.280182f,-0.707712f,-0.64857f},{-0.079535f,-0.668278f,-0.739648f},{-0.392839f,-0.472588f,-0.788884f}, +{0.176156f,0.338308f,0.924401f},{-0.581431f,0.267686f,-0.768298f},{-0.594621f,0.186383f,-0.782104f}, +{-0.642564f,0.383097f,-0.663587f},{0.0191206f,-0.600232f,0.799598f},{0.100352f,-0.466051f,0.879049f}, +{0.187243f,-0.175769f,0.96646f},{0.209566f,-0.253734f,0.944299f},{0.111776f,-0.225572f,0.967793f}, +{-0.420204f,-0.794529f,-0.438352f},{-0.0541604f,-0.944389f,-0.324339f},{-0.958418f,0.269381f,0.0941702f}, +{-0.924451f,0.370113f,0.0916944f},{-0.950281f,0.306645f,-0.0541689f},{0.980787f,-0.180823f,0.0732094f}, +{0.981284f,-0.17298f,0.0846207f},{-0.0940962f,0.884837f,0.4563f},{-0.534859f,0.744233f,0.400053f}, +{0.718215f,0.189478f,-0.669526f},{0.713162f,0.106936f,-0.692795f},{0.801152f,-0.0516452f,-0.596229f}, +{0.560765f,-0.485962f,-0.67036f},{0.647819f,-0.462094f,-0.60564f},{0.552192f,-0.596363f,-0.582611f}, +{0.461443f,-0.823847f,-0.329161f},{0.839854f,0.511735f,0.181034f},{0.824616f,0.534256f,0.185955f}, +{-0.439976f,0.866796f,0.234702f},{-0.118575f,0.752426f,0.647916f},{0.149029f,0.629448f,0.762618f}, +{0.037462f,0.830588f,0.555625f},{-0.327739f,0.0394449f,0.943944f},{-0.516321f,0.0528115f,0.854765f}, +{-0.414224f,-0.0682792f,0.90761f},{0.288967f,0.0433499f,-0.956357f},{0.275983f,-0.147043f,-0.949848f}, +{0.598083f,-0.304596f,-0.741294f},{0.293406f,-0.917829f,-0.267401f},{0.601447f,-0.775694f,-0.191208f}, +{0.0463522f,0.74153f,0.669317f},{-0.704225f,-0.683019f,-0.193784f},{-0.708969f,-0.696362f,-0.111547f}, +{-0.680494f,-0.689726f,-0.247398f},{-0.443656f,0.340732f,0.828898f},{-0.338882f,0.238957f,0.909977f}, +{-0.467223f,0.237607f,0.851614f},{-0.443441f,-0.0478543f,-0.895025f},{-0.542019f,0.148455f,-0.827149f}, +{-0.496647f,0.0266507f,-0.867543f},{0.452023f,0.320446f,0.83246f},{0.187312f,-0.289498f,0.938672f}, +{0.817561f,0.484433f,-0.311319f},{0.119843f,0.692326f,0.711563f},{-0.5423f,0.839989f,-0.0181133f}, +{-0.589584f,0.802126f,-0.0947837f},{0.272709f,0.847446f,0.455483f},{-0.999451f,0.0035897f,0.0329507f}, +{0.0652443f,0.925068f,0.374155f},{-0.417802f,0.0621403f,0.906411f},{-0.299806f,0.0578951f,0.952242f}, +{-0.230259f,-0.156221f,0.960508f},{-0.482247f,-0.62297f,0.615911f},{-0.72808f,-0.351511f,0.588506f}, +{-0.223557f,0.145718f,0.963737f},{-0.284359f,0.277931f,0.917548f},{-0.394802f,0.167585f,0.903353f}, +{-0.36651f,-0.490079f,-0.790881f},{0.958509f,-0.237533f,-0.157604f},{-0.340748f,-0.792406f,0.505947f}, +{-0.800663f,-0.422511f,0.424763f},{-0.793826f,-0.330462f,0.510524f},{-0.345477f,0.371662f,0.861692f}, +{-0.35377f,0.384884f,0.852474f},{-0.235416f,0.24056f,0.941653f},{-0.00349561f,-0.229967f,0.973192f}, +{-0.452241f,0.131272f,-0.882182f},{0.213681f,-0.17502f,0.961097f},{0.385113f,-0.504767f,0.772592f}, +{0.241816f,0.0985017f,0.96531f},{0.245202f,0.25789f,0.934542f},{0.141131f,0.377939f,0.91501f}, +{0.123254f,0.47787f,0.869741f},{-0.0193965f,0.480999f,0.876507f},{0.220321f,0.498587f,0.838373f}, +{0.158681f,0.595297f,0.787681f},{0.214315f,0.598022f,0.772294f},{0.103356f,-0.545978f,0.8314f}, +{0.198197f,-0.0823329f,0.976698f},{0.65635f,-0.1957f,-0.728633f},{0.628686f,-0.318384f,-0.709497f}, +{0.490731f,-0.0500569f,-0.869872f},{0.996762f,0.0511583f,-0.0620418f},{0.308099f,0.64451f,0.699773f}, +{0.424921f,0.473751f,0.771364f},{0.435549f,0.496107f,0.751116f},{-0.0848771f,-0.255893f,-0.962972f}, +{0.354992f,0.509584f,0.783776f},{0.394104f,0.427023f,0.813839f},{0.585515f,-0.690378f,-0.424912f}, +{0.527878f,-0.549372f,-0.647716f},{0.884681f,-0.463029f,-0.0542522f},{0.022149f,-0.999754f,0.000742555f}, +{-0.0122399f,-0.999713f,0.0206051f},{0.040471f,-0.998944f,0.0217503f},{-0.170834f,0.582988f,-0.794317f}, +{-0.178266f,0.547618f,-0.817518f},{-0.212052f,0.610169f,-0.763366f},{0.825517f,0.493208f,-0.274349f}, +{0.801331f,0.529124f,-0.279101f},{0.818077f,0.552458f,-0.159812f},{0.467831f,-0.658629f,-0.589357f}, +{0.435167f,-0.759272f,-0.483876f},{0.236562f,-0.797224f,-0.555402f},{-0.644491f,0.666855f,0.374081f}, +{-0.00960394f,0.452187f,0.891871f},{-0.177981f,0.559222f,0.809687f},{-0.199909f,0.532918f,0.822213f}, +{0.337658f,0.462369f,0.819879f},{0.229458f,0.26022f,0.937888f},{0.416462f,0.423903f,0.804279f}, +{0.76476f,0.595295f,0.246506f},{-0.929246f,0.137717f,-0.342835f},{-0.875507f,-0.151751f,-0.458759f}, +{-0.894451f,0.21627f,-0.391389f},{0.310225f,0.947793f,0.0738153f},{0.231035f,0.95555f,0.183157f}, +{-0.296947f,0.852665f,0.429865f},{-0.985861f,0.0887629f,-0.142124f},{0.0185413f,0.943008f,0.332254f}, +{0.057313f,0.886239f,0.459669f},{-0.679383f,-0.0801837f,-0.729389f},{0.113925f,-0.311895f,-0.943262f}, +{-0.139823f,0.0525498f,0.988781f},{-0.218846f,0.661597f,0.717214f},{-0.148582f,-0.206342f,-0.967133f}, +{0.7509f,-0.182971f,-0.634563f},{0.420695f,-0.203165f,0.88416f},{-0.420831f,-0.473534f,-0.773736f}, +{-0.229455f,-0.26095f,-0.937686f},{-0.246277f,-0.0434649f,-0.968224f},{0.13588f,0.282837f,0.949495f}, +{0.176315f,0.366131f,0.913707f},{0.116814f,0.371617f,0.921008f},{-0.00480627f,0.35768f,0.933832f}, +{0.0834085f,0.119904f,0.989276f},{0.161637f,-0.054116f,0.985365f},{0.00341052f,0.0398947f,0.999198f}, +{-0.0800531f,-0.726618f,0.682362f},{0.0654456f,-0.609214f,0.790301f},{-0.0633954f,-0.615952f,0.785229f}, +{-0.0539942f,-0.764143f,0.642783f},{0.00401556f,-0.756327f,0.654181f},{0.0838838f,-0.339017f,0.937033f}, +{0.0587523f,-0.4979f,0.865242f},{0.148705f,0.00212254f,0.988879f},{0.182487f,0.0571239f,0.981547f}, +{0.53245f,-0.726799f,-0.43389f},{0.571984f,-0.776758f,-0.263593f},{0.490454f,-0.797822f,-0.350621f}, +{0.914028f,0.298546f,-0.274632f},{0.908921f,0.0880075f,-0.407576f},{0.96965f,0.0520819f,-0.238887f}, +{-0.0686965f,0.325763f,-0.942952f},{-0.109446f,0.392498f,-0.913218f},{-0.132643f,0.414957f,-0.900121f}, +{0.465453f,-0.78761f,-0.403763f},{0.43965f,-0.708592f,-0.551911f},{0.469105f,-0.777133f,-0.419529f}, +{0.453067f,-0.76698f,-0.454393f},{0.454134f,-0.76286f,-0.460225f},{0.876326f,-0.436944f,-0.202812f}, +{0.900929f,-0.413532f,-0.131602f},{0.921355f,-0.355754f,-0.156663f},{-0.0920299f,0.658693f,0.746762f}, +{-0.155912f,0.746973f,0.646315f},{-0.28089f,0.68316f,0.674087f},{-0.0731005f,0.675791f,0.733459f}, +{-0.128286f,0.631681f,0.76454f},{0.210613f,0.314174f,0.925709f},{-0.449802f,0.403711f,-0.796678f}, +{-0.396683f,0.413545f,-0.819526f},{-0.273958f,0.956358f,0.101619f},{0.11902f,0.990924f,0.0624892f}, +{0.108584f,0.967612f,0.227895f},{0.0300621f,0.972509f,0.230917f},{-0.32827f,0.355206f,0.875253f}, +{0.278549f,0.478117f,0.832955f},{0.288245f,0.594488f,0.750666f},{0.0643547f,0.0265242f,0.997575f}, +{0.207224f,-0.0685004f,0.975892f},{0.0639272f,0.178665f,0.981831f},{-0.0313133f,-0.861465f,-0.506851f}, +{0.132764f,-0.817868f,-0.55988f},{0.122697f,-0.893873f,-0.431204f},{0.988041f,0.149777f,-0.036627f}, +{0.970806f,0.237743f,-0.0318535f},{0.982795f,0.17944f,0.0437546f},{0.252469f,0.598346f,0.760422f}, +{0.26065f,0.585606f,0.767547f},{0.264146f,0.574259f,0.774889f},{0.906751f,-0.385086f,-0.171788f}, +{0.684151f,-0.723747f,0.0901569f},{0.773584f,-0.525695f,0.353853f},{0.990162f,-0.133992f,0.0403111f}, +{-0.989415f,0.0943284f,0.110271f},{-0.493016f,-0.774648f,-0.39605f},{0.19466f,0.662258f,0.723548f}, +{0.559884f,0.373378f,0.739675f},{0.320414f,0.527874f,0.786564f},{0.974845f,-0.221106f,0.0280791f}, +{0.987799f,-0.15154f,0.0359129f},{0.948007f,-0.317943f,-0.0139217f},{0.807472f,-0.295769f,-0.510401f}, +{0.831477f,-0.257606f,-0.492224f},{0.830739f,-0.29772f,-0.470357f},{0.629038f,-0.619501f,-0.469605f}, +{0.731654f,-0.44459f,-0.516742f},{0.663327f,-0.609815f,-0.43373f},{-0.888495f,-0.373328f,0.266838f}, +{-0.853903f,-0.411981f,0.317996f},{-0.871296f,-0.349591f,0.344428f},{0.173781f,0.830788f,-0.528764f}, +{0.214551f,0.837516f,-0.502528f},{0.326037f,0.775801f,-0.540215f},{-0.109706f,-0.228527f,0.967336f}, +{-0.147056f,-0.079098f,0.98596f},{-0.19074f,0.0999919f,0.976535f},{0.678873f,-0.52574f,-0.512571f}, +{0.362062f,-0.744174f,-0.561351f},{-0.991329f,0.122094f,0.0485786f},{-0.974345f,0.217247f,-0.0587819f}, +{-0.287749f,0.942918f,0.167646f},{-0.360444f,0.226196f,0.90494f},{-0.212829f,0.253452f,0.943645f}, +{-0.1543f,0.180708f,0.971358f},{-0.240401f,0.248335f,0.938369f},{-0.281255f,0.382109f,0.880277f}, +{-0.291117f,0.434942f,0.852101f},{-0.241476f,0.380203f,0.892824f},{-0.138281f,0.282514f,0.949244f}, +{-0.118735f,0.0590326f,0.99117f},{0.794851f,-0.419047f,-0.438875f},{0.232186f,0.0291142f,0.972236f}, +{0.121233f,-0.0318358f,0.992113f},{0.199799f,0.0839047f,0.976238f},{0.346783f,-0.915641f,-0.203328f}, +{0.193206f,-0.847024f,-0.495199f},{0.289267f,-0.947851f,-0.133803f},{0.348006f,0.499509f,0.793336f}, +{0.208545f,0.31167f,0.927023f},{0.176429f,0.45395f,0.873386f},{0.131126f,-0.651578f,0.747162f}, +{0.477843f,0.446816f,0.756321f},{0.344739f,0.568001f,0.747349f},{0.900389f,-0.107033f,-0.421715f}, +{0.929053f,-0.117313f,-0.350853f},{0.924176f,-0.147349f,-0.352402f},{-0.0313202f,0.13272f,0.990659f}, +{-0.119674f,0.169113f,0.978304f},{0.0505059f,-0.0361727f,0.998068f},{0.178549f,0.161106f,0.970652f}, +{0.30095f,0.0349999f,0.952997f},{0.886396f,0.461951f,0.030055f},{0.869061f,0.492341f,-0.0483173f}, +{0.857969f,0.51363f,0.0086094f},{0.914986f,-0.377543f,-0.142346f},{0.93273f,-0.351435f,-0.0806737f}, +{0.927026f,-0.374765f,-0.0131579f},{0.946514f,-0.00024888f,-0.322663f},{-0.276014f,0.950969f,-0.139548f}, +{-0.264043f,0.963609f,-0.0416947f},{-0.23099f,0.971773f,0.0479709f},{-0.156866f,0.9795f,0.126387f}, +{-0.314375f,0.932833f,0.176042f},{-0.0245953f,0.890615f,0.454092f},{-0.448809f,-0.0923177f,0.888846f}, +{-0.502441f,-0.0916905f,0.859736f},{0.276028f,0.933338f,-0.229541f},{0.314576f,0.894929f,-0.316455f}, +{0.285181f,0.94836f,-0.13887f},{0.110223f,0.0102139f,0.993854f},{0.211547f,0.0628589f,0.975344f}, +{0.321092f,-0.0486882f,0.945795f},{-0.0376397f,0.660927f,0.749506f},{-0.00832555f,-0.522454f,0.852627f}, +{-0.132767f,-0.384428f,0.913558f},{0.223466f,0.216042f,0.950468f},{0.238549f,0.376728f,0.895081f}, +{0.238472f,0.243627f,0.940094f},{0.302267f,0.125011f,0.94499f},{0.248175f,0.366485f,0.896715f}, +{0.168671f,0.349832f,0.921503f},{0.199354f,0.22033f,0.954836f},{0.273515f,0.176255f,0.945581f}, +{0.191339f,0.153628f,0.969427f},{-0.0112707f,0.119995f,0.99271f},{0.0725509f,0.159063f,0.984599f}, +{-0.00629055f,0.274279f,0.96163f},{0.277965f,0.117253f,0.953408f},{0.110563f,-0.343456f,0.932638f}, +{0.216365f,-0.123f,0.968534f},{0.900057f,0.245717f,0.359889f},{0.863173f,0.257873f,0.43409f}, +{0.881976f,0.160588f,0.443091f},{0.370564f,-0.353545f,0.858888f},{0.336535f,-0.0878704f,0.937562f}, +{0.256114f,-0.421237f,0.870037f},{0.905595f,0.0066398f,-0.424091f},{0.932738f,0.00768876f,-0.360474f}, +{0.937288f,0.0876965f,-0.337345f},{-0.0369066f,-0.945463f,0.323632f},{-0.118818f,-0.685618f,0.718199f}, +{-0.0967924f,-0.804577f,0.585907f},{0.916949f,0.263347f,-0.299756f},{0.898078f,0.264743f,-0.351236f}, +{-0.75019f,0.547434f,0.370853f},{-0.604395f,0.689198f,0.399641f},{-0.672897f,0.729871f,0.120408f}, +{-0.245761f,-0.73762f,-0.628903f},{-0.470701f,-0.346268f,-0.811504f},{0.0348372f,-0.579103f,-0.81451f}, +{-0.230634f,-0.210479f,0.950003f},{-0.274659f,-0.0711949f,0.958902f},{-0.350123f,-0.292919f,0.889726f}, +{-0.320295f,0.106153f,0.941352f},{-0.450114f,0.11922f,0.884977f},{0.136917f,0.164934f,0.976755f}, +{0.20114f,-0.423575f,0.883248f},{0.269589f,0.227421f,0.935736f},{0.256735f,0.173565f,0.950769f}, +{0.297017f,0.215377f,0.930266f},{0.300127f,-0.148128f,0.942328f},{-0.16051f,-0.429314f,-0.888778f}, +{-0.0358626f,-0.295419f,-0.954694f},{-0.0163357f,-0.445626f,-0.89507f},{0.458862f,-0.882519f,-0.10298f}, +{0.414609f,-0.88476f,-0.212836f},{0.489972f,-0.830752f,-0.264156f},{-0.079701f,-0.146064f,0.986059f}, +{-0.0857675f,-0.0777074f,0.99328f},{0.00161508f,-0.337113f,0.941463f},{0.280664f,-0.42611f,0.860034f}, +{-0.643998f,-0.577076f,-0.502246f},{-0.0959296f,-0.748871f,-0.655736f},{0.986673f,0.113871f,0.116232f}, +{0.948721f,0.230491f,0.21634f},{0.944986f,0.307393f,0.111848f},{0.960212f,-0.067786f,-0.27092f}, +{0.960641f,-0.049122f,-0.273417f},{0.967143f,-0.0398146f,-0.251094f},{0.91918f,0.127727f,-0.37255f}, +{0.949064f,0.123437f,-0.289897f},{0.568969f,0.205887f,0.796169f},{0.844097f,0.53266f,-0.0614229f}, +{0.137129f,0.880111f,0.454534f},{0.246395f,0.858197f,0.450319f},{-0.269803f,0.959994f,0.0749557f}, +{0.234905f,0.788011f,0.569085f},{-0.389489f,0.854769f,0.343027f},{-0.959211f,0.275599f,0.0629244f}, +{-0.953233f,0.301814f,0.0159892f},{-0.949614f,0.309566f,0.0490065f},{0.403744f,0.911277f,-0.0810251f}, +{0.923159f,0.364994f,0.120653f},{-0.62943f,-0.776664f,0.0247036f},{-0.652388f,-0.757587f,-0.0212481f}, +{-0.44652f,-0.894258f,-0.030358f},{-0.0901651f,-0.339501f,0.936274f},{-0.0379441f,-0.271751f,0.961619f}, +{-0.332831f,-0.401591f,0.853199f},{-0.0344199f,-0.209496f,0.977203f},{-0.126661f,-0.114418f,0.985325f}, +{-0.148426f,0.0111855f,0.98886f},{-0.295706f,0.235843f,0.925708f},{-0.369193f,0.300019f,0.879594f}, +{-0.309604f,0.364565f,0.878201f},{-0.187306f,0.220824f,0.957159f},{-0.158949f,0.314932f,0.93571f}, +{-0.161938f,0.456189f,0.875024f},{-0.148022f,0.444858f,0.883284f},{0.739283f,-0.653128f,-0.163966f}, +{0.86335f,-0.487178f,-0.131467f},{0.933787f,0.353346f,-0.0564575f},{-0.0460242f,-0.614627f,-0.787474f}, +{-0.113948f,-0.561688f,-0.819465f},{0.0168824f,-0.095401f,0.995296f},{-0.274895f,-0.200561f,0.940323f}, +{0.0946811f,0.635809f,0.766018f},{0.231209f,-0.764021f,0.60234f},{0.142163f,-0.126555f,0.98172f}, +{0.25344f,0.386675f,0.886708f},{0.250599f,0.501617f,0.827998f},{0.195129f,0.482999f,0.853602f}, +{0.0557201f,0.685491f,0.725946f},{-0.0774665f,0.667998f,0.74012f},{-0.0840826f,0.594974f,0.799335f}, +{0.01558f,0.551834f,0.833808f},{0.00736914f,0.617291f,0.786701f},{-0.0983184f,0.553799f,0.826825f}, +{0.18891f,0.283061f,-0.940314f},{0.178401f,0.241742f,-0.9538f},{0.100226f,0.273544f,-0.956623f}, +{0.769923f,-0.243603f,0.58981f},{0.77465f,-0.286361f,0.563839f},{0.69038f,-0.0458631f,0.721991f}, +{-0.327666f,0.777839f,0.536285f},{-0.342358f,0.791542f,0.506214f},{-0.327755f,0.859105f,0.393083f}, +{0.883778f,0.438968f,0.162f},{0.877874f,0.383199f,0.287219f},{0.0517423f,0.993679f,-0.0996198f}, +{0.13168f,0.990813f,-0.0308326f},{-0.229953f,-0.378124f,0.896741f},{0.140701f,0.329084f,0.93376f}, +{0.173597f,0.234941f,0.956382f},{0.286773f,-0.404816f,0.868266f},{0.409235f,-0.270151f,0.871519f}, +{-0.379485f,-0.523586f,-0.76279f},{-0.483885f,-0.460032f,-0.744464f},{-0.429615f,-0.568608f,-0.70151f}, +{0.165898f,0.0505138f,0.984848f},{0.185924f,0.175633f,0.96674f},{0.14884f,0.141895f,0.978628f}, +{0.0918044f,0.142985f,0.985458f},{0.0755089f,0.581447f,0.810073f},{0.333992f,-0.909345f,-0.248074f}, +{0.442823f,-0.869149f,-0.220199f},{0.669263f,-0.711774f,-0.213223f},{0.329347f,-0.663973f,0.671319f}, +{0.307978f,-0.627619f,0.715013f},{0.2937f,-0.525645f,0.798397f},{0.459327f,-0.888265f,0.00217148f}, +{0.553339f,-0.766244f,0.32663f},{0.416483f,-0.890913f,-0.18115f},{0.448413f,-0.472512f,0.758722f}, +{0.463214f,-0.583903f,0.666701f},{0.0228558f,0.153535f,0.987879f},{0.108558f,0.236491f,0.96555f}, +{0.728112f,-0.605165f,-0.321914f},{0.858305f,-0.313243f,-0.406437f},{0.599198f,0.0898905f,0.795539f}, +{0.819297f,0.491738f,0.294864f},{0.243573f,-0.893407f,-0.377487f},{0.196902f,-0.829986f,-0.521874f}, +{0.788151f,-0.606299f,0.105922f},{-0.0230661f,0.00367931f,-0.999727f},{0.0283941f,0.0276503f,-0.999214f}, +{0.0669312f,0.0129073f,-0.997674f},{-0.334345f,-0.481378f,0.81024f},{-0.309593f,-0.428943f,0.848622f}, +{-0.302578f,-0.400369f,0.864958f},{-0.254114f,0.379369f,0.889666f},{0.498502f,0.24239f,0.832312f}, +{0.615686f,0.102185f,0.781338f},{0.553289f,0.0582414f,0.830951f},{-0.00204334f,-0.480179f,0.877168f}, +{-0.0856082f,-0.206093f,0.97478f},{0.327397f,-0.357655f,0.874582f},{0.215444f,-0.466779f,0.85773f}, +{0.162921f,-0.132008f,0.977768f},{0.0647925f,0.233047f,0.970305f},{0.111038f,0.205046f,0.972433f}, +{0.0158613f,-0.0419174f,0.998995f},{0.341844f,-0.269747f,0.900211f},{0.116874f,-0.149929f,0.981765f}, +{0.191889f,-0.0992769f,0.976382f},{0.0101111f,0.773999f,0.633107f},{-0.0530789f,0.700445f,0.71173f}, +{0.511712f,-0.845175f,-0.154368f},{0.850173f,-0.499442f,-0.166622f},{0.846485f,-0.503522f,-0.172997f}, +{0.82965f,-0.522815f,-0.195821f},{0.023405f,-0.999625f,-0.0142142f},{0.0242401f,-0.999656f,-0.00998008f}, +{-0.000303005f,-0.999841f,-0.017843f},{-0.933294f,0.332437f,0.135822f},{-0.881595f,0.351294f,0.31525f}, +{-0.928424f,0.283676f,0.239911f},{-0.772711f,0.476617f,-0.41923f},{-0.402891f,0.911185f,-0.0861461f}, +{-0.76101f,0.648736f,0.0022703f},{-0.525614f,0.828955f,-0.191216f},{-0.187238f,0.982224f,-0.0133145f}, +{-0.147426f,0.988429f,-0.0356911f},{-0.248234f,-0.244597f,-0.937311f},{-0.198606f,-0.362544f,-0.910559f}, +{0.3608f,-0.683461f,-0.63459f},{0.0953136f,0.0853679f,-0.99178f},{0.51823f,-0.849414f,-0.0996677f}, +{0.814328f,-0.553142f,-0.175794f},{0.748936f,-0.635619f,-0.187304f},{-0.191851f,-0.439151f,0.87769f}, +{-0.228831f,-0.274946f,0.933831f},{-0.144682f,-0.240387f,0.959834f},{0.0186087f,-0.229308f,0.973176f}, +{0.0844466f,-0.272325f,0.958493f},{0.048156f,-0.214678f,0.975497f},{0.0185073f,-0.0533244f,0.998406f}, +{-0.0645704f,0.122728f,0.990338f},{-0.0960451f,0.353497f,0.930492f},{-0.123316f,0.466299f,0.87599f}, +{-0.101722f,0.334442f,0.93691f},{-0.130158f,0.278541f,0.951564f},{-0.134554f,0.476146f,0.869011f}, +{-0.0530084f,0.525905f,0.84889f},{-0.164811f,0.321452f,0.932473f},{0.0823322f,0.227239f,0.970353f}, +{-0.0657198f,0.130476f,0.989271f},{-0.0768859f,-0.720243f,0.689448f},{0.239307f,0.762061f,0.601661f}, +{0.112851f,0.567339f,0.815715f},{0.141879f,0.648949f,0.747486f},{0.203347f,0.494481f,0.845067f}, +{-0.0796564f,-0.697736f,0.711913f},{-0.0953443f,-0.671122f,0.73519f},{-0.964209f,-0.241382f,0.109707f}, +{-0.972866f,-0.118573f,0.198676f},{-0.962037f,-0.177494f,0.207316f},{0.376109f,0.345327f,0.859821f}, +{0.260354f,0.334831f,0.905596f},{0.413984f,0.0796125f,0.906796f},{0.206149f,0.427033f,0.880423f}, +{0.255093f,0.478282f,0.840342f},{0.186982f,0.425432f,0.885463f},{0.188722f,0.418679f,0.888308f}, +{0.932563f,-0.313456f,-0.179087f},{0.00751608f,-0.992196f,0.124464f},{0.61083f,0.754516f,0.239982f}, +{0.210359f,0.975986f,0.0565786f},{0.237949f,0.971085f,-0.0193416f},{-0.992479f,-0.119535f,-0.0264097f}, +{-0.991905f,0.122125f,-0.0347969f},{-0.994124f,0.0788717f,-0.0741417f},{0.317099f,0.917356f,0.240638f}, +{-0.9839f,0.176112f,-0.0304254f},{-0.229285f,0.931458f,-0.282516f},{-0.249293f,0.95181f,-0.178636f}, +{-0.380595f,-0.0953641f,-0.919811f},{-0.356386f,-0.185595f,-0.91572f},{-0.532308f,0.33788f,-0.7762f}, +{-0.623105f,0.373117f,-0.687404f},{-0.591311f,0.340755f,-0.730915f},{-0.0939356f,0.50456f,-0.858251f}, +{-0.0584824f,0.31504f,-0.947275f},{-0.0717267f,0.392258f,-0.917055f},{-0.409762f,-0.0557136f,-0.91049f}, +{-0.432608f,0.0796261f,-0.898059f},{-0.290013f,-0.0614834f,-0.955046f},{-0.253383f,-0.515742f,0.818418f}, +{-0.147352f,0.43482f,0.88838f},{0.948899f,-0.0521516f,-0.311241f},{0.175427f,0.203394f,-0.963253f}, +{0.284098f,-0.0864908f,0.954886f},{0.156203f,-0.102557f,0.982386f},{-0.0343121f,-0.670962f,0.740697f}, +{0.282425f,0.113884f,0.952505f},{0.252265f,-0.0799734f,0.964348f},{0.608191f,-0.555626f,-0.566907f}, +{-0.281625f,0.0199879f,-0.959316f},{-0.321533f,0.14723f,-0.935382f},{0.00403764f,-0.409977f,0.912087f}, +{0.296786f,0.412455f,0.861278f},{0.314424f,0.491877f,0.811908f},{0.15597f,-0.148834f,0.976484f}, +{-0.265697f,-0.575302f,0.773585f},{-0.0127738f,-0.679576f,0.733494f},{-0.326234f,-0.480712f,0.813933f}, +{-0.872833f,-0.464274f,-0.150372f},{-0.994023f,0.0946819f,-0.0543493f},{-0.550761f,-0.834547f,0.0139191f}, +{0.0263528f,0.37496f,0.926666f},{0.216957f,0.471372f,0.854832f},{0.778666f,0.0794795f,0.622385f}, +{0.595223f,-0.107183f,0.79638f},{0.0160022f,0.466013f,0.884633f},{-0.217392f,0.653551f,0.72499f}, +{-0.331892f,-0.59942f,-0.728384f},{-0.200273f,-0.752239f,-0.627716f},{0.510712f,-0.829747f,-0.225151f}, +{0.546848f,-0.823202f,-0.15263f},{0.255635f,-0.202235f,0.945384f},{0.233842f,-0.286493f,0.929107f}, +{0.308997f,0.326208f,0.893369f},{0.539946f,0.154095f,0.827474f},{0.536519f,0.226703f,0.812867f}, +{-0.013535f,0.996342f,0.0843795f},{-0.430286f,-0.0933455f,-0.897853f},{-0.293771f,-0.0785507f,-0.952643f}, +{-0.316683f,-0.174593f,-0.932325f},{-0.616447f,0.263836f,-0.741878f},{-0.584456f,0.323832f,-0.744006f}, +{-0.533083f,0.332606f,-0.777943f},{-0.470009f,0.0371177f,-0.881881f},{-0.313697f,-0.230665f,-0.92108f}, +{-0.095108f,-0.430469f,0.89758f},{-0.205516f,-0.31961f,0.924993f},{-0.271736f,-0.129682f,0.953594f}, +{-0.16233f,-0.128129f,0.978382f},{-0.103393f,-0.147975f,0.983572f},{0.0371042f,-0.0618592f,0.997395f}, +{0.0800676f,0.0692281f,0.994383f},{0.0655334f,0.326109f,0.943058f},{0.0500331f,0.458989f,0.887032f}, +{-0.012976f,0.250275f,0.968088f},{-0.00278086f,0.254525f,0.967062f},{-0.00602506f,0.403435f,0.914989f}, +{-0.0166253f,0.507921f,0.861243f},{-0.066194f,0.569683f,0.819194f},{0.00612044f,0.50687f,0.862001f}, +{0.087804f,0.358904f,0.929235f},{0.135736f,0.0527765f,0.989338f},{0.219893f,0.0168975f,0.975378f}, +{0.155385f,0.67077f,0.725205f},{0.0911761f,0.307906f,0.947038f},{0.380764f,-0.112959f,0.917747f}, +{0.484482f,-0.25873f,0.835665f},{0.173278f,-0.0255334f,0.984542f},{-0.119044f,0.0165311f,0.992751f}, +{-0.0621672f,-0.226799f,0.971955f},{0.168582f,-0.017391f,0.985534f},{0.192135f,-0.114706f,0.974642f}, +{0.0907603f,0.0712779f,0.993319f},{0.900404f,0.273238f,-0.338546f},{0.867039f,0.330615f,-0.372743f}, +{0.817124f,0.438432f,-0.374281f},{0.272694f,0.0257412f,0.961756f},{-0.264704f,0.876336f,0.402451f}, +{-0.374633f,0.788444f,0.487858f},{-0.239863f,-0.388392f,-0.889729f},{0.322604f,-0.898675f,-0.297171f}, +{0.110378f,0.0512505f,-0.992567f},{0.195878f,0.100581f,-0.975456f},{-0.299195f,0.368799f,-0.88004f}, +{-0.281542f,0.416945f,-0.864229f},{-0.334622f,0.516404f,-0.788261f},{-0.388498f,-0.387132f,-0.836181f}, +{-0.413425f,-0.223328f,-0.882726f},{-0.528806f,-0.229046f,-0.817253f},{-0.567126f,0.252032f,-0.784123f}, +{-0.0622147f,-0.575088f,0.815722f},{-0.262476f,-0.0975698f,0.959993f},{-0.27464f,-0.0410216f,0.960672f}, +{0.863329f,0.259147f,-0.43302f},{0.79449f,0.262755f,-0.547491f},{0.948858f,-0.049514f,-0.311796f}, +{-0.00933192f,0.0898895f,0.995908f},{-0.0154903f,0.0603768f,0.998055f},{0.233006f,0.519745f,0.821933f}, +{0.252677f,0.61801f,0.744458f},{-0.995603f,0.0694375f,-0.062876f},{-0.984647f,0.104033f,-0.140169f}, +{0.834566f,0.388194f,-0.390903f},{0.776226f,0.519924f,-0.356584f},{0.231295f,-0.0027237f,0.97288f}, +{0.123915f,0.171485f,0.977363f},{-0.278264f,0.885621f,0.371812f},{-0.208952f,0.742162f,0.636816f}, +{0.328508f,0.831803f,0.447423f},{-0.790702f,0.415924f,-0.449218f},{-0.592127f,0.348571f,-0.726556f}, +{-0.830674f,0.424403f,-0.360365f},{-0.62552f,0.184799f,-0.758006f},{-0.707192f,0.19258f,-0.680289f}, +{-0.613638f,0.127083f,-0.779293f},{-0.11711f,0.556457f,-0.822582f},{-0.179243f,-0.348841f,0.919881f}, +{0.214004f,0.377502f,0.900941f},{0.205075f,0.336747f,0.918992f},{0.174838f,0.422701f,0.889244f}, +{0.995333f,0.0317781f,-0.0911228f},{0.190036f,0.479935f,0.856475f},{0.231393f,0.433377f,0.871f}, +{0.0713411f,0.536196f,0.841073f},{0.461727f,-0.531031f,0.710503f},{0.642623f,-0.481829f,0.595716f}, +{0.0459413f,-0.172653f,-0.983911f},{-0.0211f,-0.331505f,-0.943218f},{-0.40236f,-0.188535f,-0.895858f}, +{-0.0220802f,0.497801f,0.86701f},{-0.261378f,0.528952f,0.807398f},{-0.310862f,0.246872f,0.917834f}, +{0.0737522f,0.294186f,0.952898f},{-0.0285864f,0.741674f,0.670151f},{0.130811f,0.779388f,0.612734f}, +{-0.329985f,0.70659f,0.625971f},{-0.150434f,0.157564f,-0.975983f},{-0.491554f,0.143916f,-0.858873f}, +{-0.549467f,0.207409f,-0.809362f},{0.0243846f,-0.998918f,-0.0395968f},{0.0154174f,-0.997923f,-0.0625502f}, +{-9.56649e-05f,-0.999511f,-0.031265f},{-0.0736797f,-0.847043f,0.526393f},{0.264565f,-0.526851f,0.807734f}, +{0.0778558f,-0.911717f,0.403374f},{-0.0722179f,-0.489121f,0.869221f},{-0.0497511f,-0.601297f,0.797475f}, +{0.0221068f,-0.414748f,0.909668f},{-0.024475f,-0.345695f,0.938028f},{-0.00234594f,-0.214816f,0.976652f}, +{-0.13038f,-0.0390172f,0.990696f},{-0.113822f,-0.00113113f,0.9935f},{0.00753531f,0.0784319f,0.996891f}, +{0.162659f,0.186062f,0.96898f},{0.197465f,0.373061f,0.90655f},{0.0726495f,0.270119f,0.960082f}, +{-0.00348439f,0.248771f,0.968556f},{0.132326f,0.32448f,0.936591f},{0.125685f,0.424566f,0.896631f}, +{0.153659f,0.430838f,0.889251f},{0.0608871f,0.531499f,0.844868f},{0.0273649f,0.554899f,0.831467f}, +{0.23097f,0.2435f,0.941998f},{0.391681f,-0.408344f,0.824525f},{-0.0899755f,-0.418629f,0.903689f}, +{-0.028856f,-0.230136f,0.972731f},{0.192937f,0.450434f,0.871714f},{0.304815f,0.404069f,0.862448f}, +{0.121643f,0.294766f,0.947795f},{0.0704177f,0.301208f,0.950955f},{-0.0185777f,0.496515f,0.867829f}, +{0.30631f,-0.12352f,0.943884f},{0.0381325f,-0.0587734f,0.997543f},{0.168162f,0.216513f,0.961688f}, +{0.17938f,0.201108f,0.963005f},{0.752987f,-0.233579f,-0.615184f},{0.752213f,-0.142831f,-0.643253f}, +{0.770254f,-0.2229f,-0.597515f},{-0.0432317f,0.381112f,0.923518f},{0.626181f,-0.779599f,0.0110793f}, +{0.621676f,-0.776663f,0.101553f},{0.521547f,-0.846536f,0.106608f},{0.344659f,-0.897823f,-0.274087f}, +{0.170478f,0.931913f,0.320119f},{0.0544409f,0.956743f,0.285794f},{-0.285666f,0.883754f,0.370641f}, +{-0.486945f,0.656165f,0.576482f},{-0.475298f,0.151973f,-0.8666f},{-0.169494f,-0.477562f,-0.862094f}, +{-0.0223069f,-0.554443f,-0.831923f},{0.19763f,0.0676612f,-0.977939f},{0.12502f,0.240439f,-0.96258f}, +{0.096951f,0.348996f,-0.932096f},{0.106352f,0.388327f,0.915364f},{0.186485f,0.465086f,0.865401f}, +{0.085202f,0.629671f,0.772176f},{-0.640431f,-0.404267f,-0.653006f},{-0.780979f,-0.316721f,-0.538294f}, +{-0.865468f,-0.312483f,-0.39156f},{0.0972787f,-0.197405f,0.975483f},{-0.0786699f,0.434336f,0.897309f}, +{-0.076951f,0.450712f,0.889346f},{-0.0668306f,0.439939f,0.895537f},{0.156395f,0.469386f,0.869033f}, +{0.394926f,-0.0689772f,0.91612f},{0.144095f,0.697262f,0.702184f},{0.268524f,0.659339f,0.702259f}, +{0.137937f,0.582651f,0.800932f},{-0.150031f,0.52089f,0.840336f},{-0.157645f,0.567902f,0.807858f}, +{-0.0967734f,0.630328f,0.770274f},{0.622746f,0.181545f,0.761071f},{0.239313f,0.574192f,0.782964f}, +{0.108848f,0.65576f,0.747082f},{0.711315f,0.172817f,0.681297f},{0.874118f,0.338625f,-0.348211f}, +{0.849983f,0.440713f,-0.288621f},{0.82446f,0.469055f,0.316627f},{0.839611f,0.504318f,0.201784f}, +{0.839027f,0.51583f,0.173072f},{-0.0827286f,0.994895f,-0.0577841f},{-0.0780838f,0.996946f,-0.00112269f}, +{-0.0196054f,0.999522f,-0.0238855f},{0.331187f,0.80367f,0.494399f},{0.464725f,0.734652f,0.494285f}, +{-0.657859f,0.081471f,0.748722f},{-0.601514f,-0.443733f,0.66429f},{-0.284661f,-0.303841f,0.909202f}, +{-0.573281f,0.338316f,-0.746251f},{-0.481339f,0.161541f,-0.861521f},{-0.512204f,0.100924f,-0.852913f}, +{-0.362885f,0.109735f,-0.92535f},{-0.104948f,0.555117f,0.825125f},{0.165705f,0.470171f,0.86688f}, +{0.168167f,0.431262f,0.886416f},{0.162941f,-0.206219f,0.964844f},{-0.0583317f,-0.297053f,0.953078f}, +{0.354429f,-0.0278913f,0.934667f},{0.251351f,0.451336f,0.856223f},{0.280874f,0.479454f,0.831405f}, +{-0.140207f,0.763332f,0.630608f},{0.0888948f,0.824089f,0.559442f},{0.236753f,0.565998f,0.78968f}, +{0.276147f,0.490722f,0.826399f},{0.165164f,0.456367f,0.874328f},{0.884691f,0.122447f,0.44981f}, +{0.470696f,0.578142f,0.666482f},{0.885033f,-0.390748f,0.253046f},{0.629966f,-0.274177f,-0.726615f}, +{0.647971f,-0.158293f,-0.745035f},{0.611495f,-0.0616235f,-0.788845f},{0.639939f,-0.751124f,-0.162144f}, +{0.673719f,-0.105217f,-0.731459f},{0.639491f,-0.601652f,0.478608f},{0.513526f,-0.849419f,0.121567f}, +{0.610413f,-0.647956f,0.455575f},{0.334601f,0.299348f,0.893551f},{0.0441093f,0.711599f,0.7012f}, +{0.181251f,0.742361f,0.645018f},{0.00122868f,0.846108f,0.53301f},{-0.281939f,0.868014f,0.408732f}, +{-0.471426f,0.186922f,-0.861869f},{-0.580898f,0.240434f,-0.777656f},{-0.583811f,0.05227f,-0.810205f}, +{0.0320676f,-0.999039f,-0.0298842f},{0.018545f,-0.99914f,-0.0370961f},{0.0146236f,-0.99989f,-0.0025338f}, +{-0.0617785f,-0.382466f,0.921902f},{-0.00404173f,-0.25056f,0.968093f},{0.122372f,-0.278325f,0.95266f}, +{0.0523822f,-0.161823f,0.985429f},{-0.0111141f,-0.0130864f,0.999853f},{0.0363037f,0.192458f,0.980633f}, +{-0.0404656f,0.143099f,0.988881f},{0.0160188f,0.175992f,0.984261f},{-0.210937f,0.0895094f,0.973393f}, +{-0.109152f,0.311871f,0.943834f},{0.0474923f,0.41157f,0.91014f},{0.0645066f,0.39909f,0.91464f}, +{-0.00659182f,0.496609f,0.867949f},{-0.0486732f,0.553081f,0.831704f},{0.0117206f,0.595074f,0.803586f}, +{0.0765092f,0.587289f,0.805753f},{0.347041f,0.370975f,0.861359f},{0.0452194f,0.614496f,0.787623f}, +{0.00440938f,0.620018f,0.784575f},{0.0859346f,0.688023f,0.720583f},{0.0447807f,0.383445f,0.922477f}, +{0.195784f,0.612478f,0.765859f},{0.145692f,0.695086f,0.704009f},{-0.00125758f,0.736867f,0.676037f}, +{0.24172f,0.519188f,0.819766f},{0.285311f,0.497986f,0.818906f},{0.460259f,-0.715681f,-0.525322f}, +{0.983586f,0.0216339f,0.179138f},{0.943684f,0.208261f,0.257076f},{0.981252f,0.0914436f,0.169655f}, +{0.14865f,0.0434738f,0.987934f},{-0.0207364f,0.626854f,0.778861f},{0.0720591f,0.436571f,0.89678f}, +{0.139963f,0.204553f,0.968797f},{-0.995807f,-0.0625395f,0.066763f},{-0.212539f,0.290927f,-0.932839f}, +{-0.418814f,0.226659f,-0.87933f},{0.0559445f,-0.155077f,0.986317f},{-0.202485f,-0.659883f,-0.723571f}, +{0.271867f,0.425229f,0.863289f},{0.235252f,0.505083f,0.83039f},{-0.14351f,0.68616f,0.713155f}, +{-0.393631f,0.778218f,0.489317f},{-0.220183f,0.743723f,0.631186f},{0.00776272f,0.335611f,0.941969f}, +{0.91128f,0.340794f,-0.231146f},{0.876158f,0.161572f,0.454138f},{0.824709f,0.261035f,0.501712f}, +{-0.512031f,0.218737f,0.83065f},{0.299492f,0.57243f,0.763301f},{0.443001f,0.393616f,0.805491f}, +{-0.350699f,0.860797f,0.368836f},{-0.485456f,0.0648393f,-0.871853f},{-0.570127f,0.246992f,-0.78355f}, +{-0.483251f,-0.0510707f,-0.873991f},{-0.510278f,-0.349902f,-0.785611f},{-0.500311f,-0.348015f,-0.792827f}, +{0.121175f,0.0514562f,-0.991297f},{-0.881409f,-0.279087f,-0.381089f},{-0.51307f,-0.43176f,-0.741851f}, +{-0.655812f,-0.39313f,-0.644484f},{-0.156642f,0.119218f,0.980434f},{-0.0217293f,0.0257904f,0.999431f}, +{-0.637142f,0.394017f,0.66242f},{0.108426f,0.88485f,0.453084f},{0.426378f,-0.185955f,0.885225f}, +{-0.0933708f,0.463751f,0.881032f},{-0.0882345f,0.196905f,0.976444f},{0.0172453f,0.226536f,0.97385f}, +{-0.0643816f,0.38211f,0.921871f},{-0.0728973f,0.463505f,0.88309f},{0.99305f,0.0973475f,0.0661499f}, +{0.993768f,0.090838f,0.0645981f},{0.99306f,0.0946657f,0.0697913f},{0.477953f,0.386349f,0.788857f}, +{0.0407022f,0.683789f,0.728544f},{0.0318931f,0.748566f,0.662293f},{0.181964f,-0.732915f,-0.655534f}, +{0.243709f,0.468222f,0.849337f},{0.153119f,0.654531f,0.740367f},{0.0758308f,0.737048f,0.671573f}, +{-0.17887f,0.637327f,0.749546f},{-0.296601f,0.712073f,0.63638f},{-0.541293f,0.466978f,0.699238f}, +{-0.566555f,0.696726f,0.439987f},{-0.134021f,0.932858f,0.334385f},{0.0218045f,-0.997464f,0.0677578f}, +{0.0271397f,-0.997929f,0.0583154f},{0.0252167f,-0.998572f,0.0471037f},{-0.621606f,-0.278433f,0.732176f}, +{-0.570494f,-0.50975f,0.643965f},{-0.681334f,-0.207411f,0.701972f},{-0.559511f,-0.36605f,-0.74361f}, +{0.493383f,0.0945182f,-0.864661f},{0.584445f,-0.0956861f,-0.805772f},{0.611821f,0.0811993f,-0.786817f}, +{0.0579123f,-0.316769f,0.946733f},{-0.0781528f,-0.569633f,0.818175f},{-0.956457f,0.0398561f,0.289139f}, +{-0.992877f,0.118206f,-0.0149156f},{-0.767728f,-0.639771f,0.0358726f},{0.183182f,-0.187221f,0.965087f}, +{0.158643f,-0.200877f,0.966685f},{0.0660394f,-0.240335f,0.968441f},{0.129513f,-0.216463f,0.967662f}, +{0.17963f,0.059801f,0.981915f},{0.216525f,0.230871f,0.948586f},{0.13377f,-0.00599154f,0.990994f}, +{0.13257f,0.0957259f,0.98654f},{0.147001f,0.309975f,0.939311f},{-0.0514512f,0.392173f,0.918452f}, +{-0.0894499f,0.442738f,0.892178f},{-0.184403f,0.382964f,0.905171f},{-0.0898477f,0.43927f,0.893851f}, +{-0.0528206f,0.613729f,0.787748f},{0.0169646f,0.649408f,0.760251f},{0.0341286f,0.644321f,0.763993f}, +{0.163776f,0.613155f,0.772799f},{-0.346824f,0.758038f,0.552352f},{-0.000173428f,0.166072f,0.986114f}, +{0.94866f,-0.152144f,-0.277302f},{0.980893f,-0.150273f,-0.12356f},{0.938342f,-0.31065f,-0.151691f}, +{0.0818533f,-0.546118f,0.833699f},{0.994091f,0.0830656f,0.0698837f},{0.993339f,0.0874817f,0.0749937f}, +{-0.227107f,0.963144f,-0.144138f},{-0.10378f,-0.515128f,0.850807f},{0.315257f,0.0500735f,0.947684f}, +{0.264788f,0.243961f,0.932936f},{0.296313f,0.265407f,0.917473f},{0.328641f,0.479454f,0.813707f}, +{0.229493f,0.231328f,0.945421f},{0.336808f,0.301467f,0.892008f},{0.232176f,0.498203f,0.835397f}, +{0.962198f,0.269438f,0.0397187f},{0.953436f,0.295835f,-0.0586683f},{0.942935f,0.331643f,0.0297584f}, +{0.198719f,0.341757f,0.918538f},{0.44266f,0.233925f,0.865639f},{-0.302218f,-0.92107f,0.245547f}, +{-0.468439f,-0.879868f,0.079976f},{-0.273014f,-0.949768f,-0.152985f},{0.279096f,0.87342f,0.399053f}, +{0.109999f,0.983735f,0.142007f},{0.33597f,0.0460234f,0.940748f},{-0.0755804f,0.557677f,0.82661f}, +{0.469378f,0.32101f,0.822579f},{-0.417018f,0.693837f,0.587099f},{-0.996744f,-0.0371724f,0.071545f}, +{0.202101f,0.433878f,0.878012f},{0.129701f,0.607736f,0.783476f},{0.0245816f,0.617155f,0.786458f}, +{-0.161749f,0.406649f,0.899152f},{-0.370242f,0.314838f,0.873956f},{0.32194f,0.854132f,0.408429f}, +{0.229067f,0.971816f,-0.0556929f},{-0.215353f,-0.504048f,0.836397f},{-0.165197f,-0.256448f,0.952336f}, +{-0.0296231f,-0.105381f,0.993991f},{0.0989861f,-0.0810697f,0.991781f},{0.323746f,-0.196872f,0.925435f}, +{0.343294f,-0.202076f,0.917232f},{0.380756f,0.0343652f,0.924037f},{0.377954f,0.253315f,0.890496f}, +{0.352625f,0.162393f,0.921566f},{0.235176f,0.0282973f,0.971541f},{0.278209f,0.0768769f,0.957439f}, +{0.18879f,0.230847f,0.954499f},{0.100841f,0.209443f,0.972607f},{-0.0516694f,0.294141f,0.954364f}, +{-0.0359873f,0.460162f,0.887105f},{0.0150075f,0.491099f,0.870974f},{0.0174386f,0.594376f,0.803998f}, +{0.0599003f,0.645757f,0.76119f},{0.0237448f,0.664496f,0.746914f},{-0.0117554f,0.643774f,0.765126f}, +{-0.00783735f,0.687184f,0.726442f},{-0.0677373f,0.701206f,0.709734f},{0.14681f,0.639021f,0.755049f}, +{0.161852f,-0.766316f,-0.621742f},{0.313848f,0.615783f,0.722711f},{0.357795f,-0.814972f,-0.455855f}, +{0.577611f,-0.687363f,-0.440337f},{0.0259876f,-0.995169f,0.0946733f},{0.030387f,-0.997499f,0.063819f}, +{0.0198724f,-0.996136f,0.0855424f},{0.368854f,0.251844f,0.894719f},{0.22752f,0.354024f,0.907139f}, +{0.0791086f,0.471746f,0.878178f},{0.827758f,0.36769f,-0.423817f},{0.845807f,0.387535f,-0.366644f}, +{0.240204f,0.227618f,0.943659f},{-0.370066f,0.155968f,0.915819f},{0.022549f,-0.996353f,0.0822977f}, +{0.0262031f,-0.996683f,0.0770459f},{0.416581f,-0.150609f,0.896536f},{0.835817f,-0.534199f,-0.126657f}, +{0.903079f,-0.405958f,-0.140167f},{0.222092f,-0.177756f,0.958686f},{0.0364991f,-0.0694043f,0.996921f}, +{0.0238959f,-0.354001f,0.93494f},{0.230632f,0.323804f,0.917583f},{0.144523f,-0.435178f,0.888669f}, +{-0.1093f,-0.210785f,0.971403f},{0.821085f,0.318833f,-0.47346f},{-0.754431f,0.566074f,-0.332255f}, +{-0.757881f,0.423543f,-0.496214f},{-0.82559f,0.142302f,-0.546032f},{-0.847383f,-0.192788f,-0.494747f}, +{-0.914762f,-0.15749f,-0.372033f},{0.276055f,0.263812f,0.924228f},{-0.0770771f,-0.957563f,-0.277728f}, +{0.139261f,-0.967934f,-0.209068f},{0.321303f,-0.905573f,-0.276951f},{0.431478f,0.0505868f,0.900704f}, +{0.249985f,0.200263f,0.947313f},{0.304387f,0.278311f,0.910984f},{-0.0499762f,-0.111835f,0.992469f}, +{0.246997f,0.369415f,0.895838f},{0.101263f,0.450865f,0.886829f},{0.0237676f,0.496742f,0.867573f}, +{-0.0263901f,0.301213f,0.953192f},{-0.141989f,0.111937f,0.983519f},{-0.179951f,-0.0840402f,0.980079f}, +{-0.385978f,-0.0345515f,0.921861f},{-0.371599f,-0.166121f,0.91341f},{-0.125026f,-0.19002f,0.973787f}, +{-0.373366f,-0.377655f,0.847333f},{-0.2744f,-0.324717f,0.905132f},{-0.0187571f,-0.159511f,0.987018f}, +{-0.0551324f,-0.088822f,0.994521f},{-0.0779111f,0.0233012f,0.996688f},{-0.05431f,0.00554412f,0.998509f}, +{0.0110976f,-0.11894f,0.992839f},{0.116006f,0.0256008f,0.992919f},{-0.00714221f,0.0399637f,0.999176f}, +{0.0629546f,0.0515646f,0.996683f},{0.104175f,0.0657995f,0.99238f},{0.123421f,0.142901f,0.982011f}, +{0.199415f,0.236513f,0.950944f},{0.161276f,0.334805f,0.928383f},{0.278101f,0.368124f,0.887211f}, +{0.249535f,0.469344f,0.847023f},{0.138731f,0.593551f,0.792749f},{0.0456122f,0.613564f,0.788327f}, +{0.00602844f,0.598335f,0.801223f},{-0.0404027f,0.653058f,0.756229f},{-0.113609f,0.70629f,0.698747f}, +{-0.149797f,0.759453f,0.633082f},{-0.0969605f,0.157836f,0.982693f},{0.30952f,0.343163f,0.886812f}, +{0.250895f,0.239551f,0.937905f},{0.364302f,0.214746f,0.906183f},{0.677437f,-0.618769f,-0.39775f}, +{0.403265f,0.340129f,0.849523f},{0.361741f,-0.604613f,-0.709638f},{0.520482f,-0.635742f,-0.570027f}, +{-0.220053f,-0.110282f,0.969234f},{0.664744f,0.0727426f,-0.743522f},{0.69493f,-0.197012f,-0.691562f}, +{0.711349f,0.0990558f,-0.695824f},{0.791779f,-0.192596f,-0.579648f},{0.83793f,0.467497f,-0.281638f}, +{0.803215f,0.569194f,-0.175682f},{0.265927f,-0.221511f,-0.938198f},{0.672927f,-0.206138f,-0.710405f}, +{0.543858f,0.126279f,-0.829622f},{0.543691f,-0.808459f,0.225377f},{0.576513f,-0.710938f,0.402741f}, +{-0.956165f,0.283562f,0.0730865f},{-0.822554f,0.542533f,0.170477f},{-0.703695f,0.6879f,0.177784f}, +{-0.602137f,0.489269f,-0.630909f},{-0.532431f,0.695438f,-0.48258f},{-0.390972f,0.546069f,-0.740911f}, +{-0.255914f,-0.161228f,0.95316f},{-0.384142f,-0.179441f,0.905669f},{0.243979f,0.262092f,0.933693f}, +{0.99634f,-0.0342846f,0.0783019f},{0.709472f,-0.120123f,-0.694421f},{-0.0644449f,0.0727868f,0.995263f}, +{0.775684f,0.125389f,-0.618541f},{-0.107575f,-0.100668f,0.989087f},{0.0138886f,0.527502f,0.84944f}, +{-0.772811f,0.296101f,-0.561326f},{-0.742911f,0.337742f,-0.577939f},{-0.795858f,0.46664f,-0.385819f}, +{0.475773f,0.8358f,-0.274004f},{0.0905323f,0.351765f,0.9317f},{0.141134f,0.3954f,0.907601f}, +{0.181249f,0.353132f,0.917849f},{0.0710616f,0.250557f,0.96549f},{0.0950218f,0.0252217f,0.995156f}, +{0.093489f,-0.137843f,0.986032f},{0.0487989f,-0.207587f,0.976999f},{-0.183824f,-0.148739f,0.97164f}, +{-0.0301569f,-0.227055f,0.973415f},{-0.292098f,-0.143991f,0.945487f},{-0.075602f,-0.179751f,0.980803f}, +{-0.0417652f,-0.0777302f,0.996099f},{0.00792155f,-0.0523368f,0.998598f},{0.0587392f,-0.104993f,0.992737f}, +{0.118203f,-0.133507f,0.983974f},{0.0643076f,-0.145089f,0.987327f},{-0.146965f,-0.0484049f,0.987957f}, +{-0.147134f,-0.0615316f,0.987201f},{-0.108244f,0.067494f,0.991831f},{-0.113617f,0.0485507f,0.992338f}, +{-0.238631f,0.0427924f,0.970167f},{-0.16731f,0.157442f,0.973252f},{-0.0554322f,0.247821f,0.967219f}, +{0.0215249f,0.307941f,0.951162f},{0.089122f,0.370946f,0.924368f},{0.173814f,0.436493f,0.882758f}, +{0.334641f,0.366563f,0.868129f},{0.201333f,0.420597f,0.884626f},{0.107478f,0.499501f,0.859621f}, +{0.0882418f,0.554137f,0.827735f},{0.077174f,0.58413f,0.807983f},{0.0904184f,0.624511f,0.775765f}, +{0.1935f,0.237683f,0.951874f},{0.305689f,0.296056f,0.904934f},{0.327435f,0.184844f,0.926617f}, +{0.269561f,-0.927616f,-0.258583f},{0.193659f,-0.895755f,-0.400149f},{0.394681f,0.163975f,0.904068f}, +{0.258788f,0.468532f,0.844693f},{-0.0718795f,0.684182f,0.72576f},{0.683177f,-0.701106f,-0.204252f}, +{-0.516514f,0.796576f,-0.314133f},{-0.720986f,-0.629455f,-0.289768f},{-0.779838f,-0.545293f,-0.307421f}, +{-0.89702f,-0.302861f,-0.321916f},{-0.483131f,0.87171f,0.081887f},{-0.489306f,0.870408f,-0.0544948f}, +{-0.717503f,0.692303f,0.0768497f},{0.957277f,-0.28916f,0.00262905f},{0.988026f,-0.0837556f,0.129575f}, +{0.24601f,0.323063f,0.913843f},{0.604944f,-0.0749556f,0.792732f},{-0.155861f,0.510953f,0.84536f}, +{0.149976f,0.587202f,0.795425f},{0.0187902f,-0.999677f,-0.0171062f},{0.025801f,-0.999018f,-0.036023f}, +{-0.00739064f,-0.99961f,-0.0269177f},{0.678978f,-0.0969692f,0.727727f},{0.269299f,0.415525f,0.868802f}, +{-0.549574f,0.640399f,-0.536524f},{-0.634002f,0.560561f,-0.532741f},{0.629036f,0.600896f,0.49319f}, +{0.7046f,0.544216f,0.455376f},{0.806775f,0.433922f,0.401032f},{-0.349169f,0.43309f,0.830972f}, +{-0.249238f,0.533495f,0.808247f},{0.223309f,0.158713f,0.96174f},{0.283775f,-0.0974038f,0.953931f}, +{0.297757f,-0.225284f,0.927679f},{0.293836f,-0.261361f,0.91943f},{0.182718f,-0.192612f,0.964113f}, +{0.147264f,-0.0935166f,0.984666f},{0.147869f,-0.0805794f,0.985719f},{0.122862f,-0.0441671f,0.991441f}, +{0.153585f,-0.0603901f,0.986288f},{0.248531f,-0.180208f,0.951713f},{0.192206f,-0.16612f,0.967192f}, +{0.160758f,-0.0170921f,0.986846f},{0.124724f,0.00386966f,0.992184f},{-0.152929f,-0.176901f,0.972275f}, +{-0.334302f,-0.0505609f,0.941109f},{-0.325042f,0.204004f,0.923434f},{-0.273952f,0.324963f,0.905179f}, +{-0.183982f,0.404077f,0.896031f},{-0.12399f,0.480282f,0.868306f},{-0.132311f,0.480545f,0.866931f}, +{-0.0539982f,0.44909f,0.891853f},{0.0186793f,0.528253f,0.848882f},{0.0540516f,0.603218f,0.795742f}, +{0.0574721f,0.58057f,0.812179f},{0.0488323f,0.540287f,0.840063f},{0.107447f,0.574946f,0.811106f}, +{0.836185f,-0.500978f,-0.223194f},{0.706114f,-0.222507f,-0.672231f},{0.73513f,-0.156479f,-0.659619f}, +{0.994869f,-0.0398721f,0.0929814f},{0.990704f,-0.0215186f,0.134325f},{0.601227f,0.0332051f,0.798388f}, +{-0.612229f,0.501538f,-0.611258f},{-0.604453f,0.336919f,-0.721888f},{-0.644079f,0.465962f,-0.606664f}, +{0.389418f,-0.206132f,0.897699f},{-0.210594f,-0.0239807f,0.977279f},{0.823588f,0.444795f,-0.351937f}, +{0.523614f,-0.851564f,-0.0258188f},{0.333779f,-0.101378f,0.937184f},{-0.239351f,0.170456f,0.955853f}, +{-0.191253f,0.418555f,0.887826f},{0.993128f,0.0770358f,-0.0881045f},{0.995817f,0.0332385f,-0.0851114f}, +{0.266118f,-0.0563829f,0.96229f},{-0.0899401f,0.57571f,0.812692f},{0.941752f,-0.336122f,0.0111489f}, +{0.956098f,-0.268499f,0.117412f},{0.972634f,0.227543f,0.0469783f},{0.0213099f,0.745877f,0.665743f}, +{-0.855257f,-0.0444835f,-0.516291f},{-0.204136f,0.966554f,0.155248f},{-0.942272f,0.216219f,0.25568f}, +{-0.980849f,0.182453f,0.0681602f},{-0.936853f,0.329239f,0.117934f},{-0.944458f,0.300396f,0.133269f}, +{0.405896f,0.789675f,0.460067f},{-0.883435f,0.0948705f,-0.458849f},{-0.229111f,-0.238176f,0.943812f}, +{-0.527854f,-0.219852f,0.820387f},{-0.493896f,-0.293839f,0.818368f},{-0.00712655f,-0.57856f,-0.815609f}, +{-0.147518f,0.530632f,0.834666f},{0.0992787f,0.378513f,0.920256f},{0.343353f,-0.0633624f,0.937067f}, +{0.371543f,-0.190139f,0.908737f},{0.393868f,-0.207759f,0.895379f},{0.457386f,-0.212961f,0.863392f}, +{0.399808f,-0.170027f,0.900691f},{0.310284f,-0.131588f,0.941493f},{0.326734f,-0.070236f,0.942503f}, +{0.21877f,-0.0742296f,0.972949f},{0.146588f,-0.114926f,0.982499f},{0.141094f,-0.138347f,0.980282f}, +{0.23755f,-0.115062f,0.964537f},{-0.0402561f,-0.269229f,0.962234f},{-0.0749331f,-0.492771f,0.866927f}, +{-0.0734197f,-0.180458f,0.980839f},{-0.00426604f,-0.0404009f,0.999174f},{-0.139124f,0.0871926f,0.986429f}, +{-0.215771f,0.244843f,0.945249f},{-0.234529f,0.347527f,0.907866f},{-0.224147f,0.450837f,0.864005f}, +{-0.2128f,0.484206f,0.848682f},{-0.219569f,0.479628f,0.849557f},{-0.187117f,0.496056f,0.847889f}, +{-0.188186f,0.459699f,0.867907f},{-0.112351f,0.57933f,0.807313f},{-0.106516f,0.620372f,0.777041f}, +{-0.0679673f,0.612159f,0.787808f},{-0.0326748f,0.580432f,0.813653f},{0.708862f,-0.680962f,-0.183865f}, +{0.847722f,-0.484375f,-0.216214f},{0.712572f,-0.688967f,-0.132536f},{0.645906f,-0.7559f,-0.10687f}, +{0.856818f,-0.487012f,-0.169355f},{0.26561f,0.427572f,0.86408f},{0.141693f,-0.00470907f,0.989899f}, +{0.142388f,-0.0233211f,0.989536f},{0.685045f,-0.0214021f,0.728186f},{-0.0585203f,0.657791f,0.750924f}, +{0.318829f,0.468303f,0.824039f},{-0.90048f,0.294898f,-0.319642f},{0.420585f,-0.898045f,-0.12893f}, +{0.0573068f,-0.998341f,0.0055865f},{0.367788f,-0.911475f,-0.184246f},{0.372103f,-0.184797f,0.909609f}, +{0.397361f,-0.124209f,0.909217f},{0.370408f,-0.452713f,0.811078f},{0.339769f,-0.0340991f,0.939891f}, +{0.375499f,0.00669293f,0.926799f},{0.331121f,0.0276573f,0.943183f},{0.189051f,0.235624f,0.953279f}, +{0.234955f,-0.852191f,-0.467511f},{0.207225f,0.610604f,0.764343f},{0.0281201f,0.611182f,0.79099f}, +{0.592523f,0.330052f,-0.734834f},{0.299147f,-0.110764f,0.947757f},{0.289337f,-0.160901f,0.943607f}, +{-0.184455f,0.301245f,0.935536f},{-0.114346f,0.596484f,0.794438f},{0.892855f,0.450094f,-0.0149891f}, +{0.914416f,0.403767f,0.0285614f},{0.995914f,0.0792773f,0.0432455f},{0.995102f,0.0918383f,0.0365886f}, +{0.0882295f,0.392913f,0.915333f},{0.01841f,0.690734f,0.722874f},{-0.998624f,0.0360211f,-0.0381155f}, +{-0.484736f,-0.873671f,0.0415806f},{-0.28694f,-0.957947f,-0.00188694f},{-0.116894f,-0.567173f,-0.815261f}, +{-0.125439f,-0.579902f,-0.804971f},{-0.241306f,-0.463478f,-0.852619f},{-0.0328103f,-0.946855f,-0.319981f}, +{0.139347f,-0.115985f,0.983428f},{0.116236f,0.0964643f,0.988526f},{0.254626f,-0.170183f,0.951947f}, +{0.391114f,-0.158803f,0.906538f},{0.396983f,-0.203492f,0.894984f},{0.234799f,-0.252738f,0.938612f}, +{0.165006f,-0.21172f,0.9633f},{0.0594331f,-0.20056f,0.977877f},{-0.0990798f,-0.253082f,0.962358f}, +{-0.226912f,-0.176631f,0.957764f},{-0.600585f,-0.199035f,0.774392f},{-0.44706f,-0.174961f,0.877226f}, +{-0.582458f,-0.189821f,0.790386f},{-0.258913f,0.0474452f,0.964735f},{-0.175723f,-0.096949f,0.979654f}, +{-0.0275966f,-0.125996f,0.991647f},{-0.0882985f,0.00297313f,0.99609f},{0.00613423f,0.264642f,0.964327f}, +{-0.0170057f,0.424457f,0.905289f},{-0.110029f,0.389298f,0.914516f},{-0.173889f,0.480406f,0.859635f}, +{-0.174587f,0.499081f,0.848786f},{-0.169377f,0.539228f,0.824951f},{-0.13077f,0.599198f,0.789849f}, +{-0.104141f,0.592033f,0.799156f},{-0.100737f,0.612211f,0.784251f},{0.46098f,-0.118947f,0.879403f}, +{0.507723f,-0.148427f,0.848638f},{0.460838f,-0.157004f,0.873486f},{0.0273984f,0.521382f,0.852883f}, +{-0.0558594f,0.574362f,0.816694f},{-0.230996f,-0.451956f,-0.861612f},{-0.603773f,-0.456419f,-0.653559f}, +{0.0498736f,0.479708f,0.87601f},{0.592333f,0.0544071f,0.803854f},{0.982772f,0.101441f,0.154494f}, +{0.979766f,0.0918827f,0.177808f},{0.690902f,-0.676617f,-0.254645f},{0.680962f,-0.684141f,-0.261232f}, +{0.514943f,-0.723689f,-0.459465f},{0.279941f,0.804335f,-0.524097f},{0.470227f,0.806527f,-0.35833f}, +{0.602172f,0.587103f,-0.541017f},{0.219912f,-0.190058f,0.956827f},{-0.707877f,-0.246572f,0.6619f}, +{0.223647f,-0.190582f,0.955856f},{0.66088f,-0.466033f,0.58826f},{0.78418f,-0.57566f,0.231685f}, +{-0.0963573f,0.66013f,0.744945f},{0.529264f,0.395781f,0.750491f},{-0.0938949f,0.51176f,0.853982f}, +{0.0167492f,0.0845491f,0.996279f},{-0.29642f,0.739859f,0.60394f},{0.770559f,-0.27077f,0.576994f}, +{0.395407f,0.551992f,-0.734138f},{0.426667f,0.671553f,-0.605781f},{-0.678447f,-0.734224f,-0.0250153f}, +{-0.670977f,-0.740986f,-0.0270176f},{0.719373f,0.597154f,0.354838f},{-0.0260727f,-0.794933f,-0.606137f}, +{-0.488692f,-0.0546212f,-0.870745f},{-0.85862f,-0.0460396f,0.510541f},{-0.833654f,-0.028232f,0.551565f}, +{-0.711846f,-0.0760288f,0.698209f},{-0.794342f,0.00412712f,0.607457f},{0.916589f,-0.296311f,-0.268448f}, +{-0.756481f,-0.335536f,0.561384f},{-0.448513f,0.655173f,0.607935f},{-0.491382f,0.649039f,0.580769f}, +{-0.359336f,0.545212f,0.757378f},{-0.454763f,0.408567f,0.791368f},{-0.360103f,0.534543f,0.764585f}, +{-0.419343f,0.270591f,0.866563f},{-0.308274f,-0.158676f,0.937971f},{-0.537672f,-0.0421306f,0.842101f}, +{-0.173191f,-0.228231f,0.958079f},{-0.435517f,-0.276149f,0.856777f},{-0.530407f,-0.286438f,0.797886f}, +{-0.554514f,-0.329894f,0.763992f},{0.785711f,0.574001f,0.230609f},{0.741859f,0.498856f,0.448093f}, +{0.303562f,0.946105f,-0.112852f},{0.307248f,0.93655f,-0.168738f},{0.395085f,-0.646918f,0.652231f}, +{0.259732f,-0.848029f,-0.461938f},{0.238077f,0.968948f,0.0667771f},{0.222715f,0.964406f,0.142547f}, +{0.332458f,0.941366f,0.0574574f},{0.331289f,0.943105f,-0.0283073f},{-0.673966f,0.432063f,0.599243f}, +{-0.361289f,0.120792f,0.924597f},{0.081858f,0.397394f,0.91399f},{-0.0442536f,0.141609f,0.988933f}, +{-0.224349f,0.314769f,0.922273f},{-0.186689f,0.451686f,0.872426f},{-0.208966f,0.483781f,0.849876f}, +{-0.23828f,0.528725f,0.814661f},{-0.226058f,0.597649f,0.769229f},{-0.20108f,0.616536f,0.761216f}, +{-0.186587f,0.626264f,0.756954f},{-0.485704f,0.733153f,-0.476002f},{-0.584897f,0.708289f,-0.395249f}, +{0.91989f,0.0667137f,-0.386461f},{0.955529f,0.0549196f,-0.289737f},{0.911446f,-0.138902f,-0.387263f}, +{0.167664f,-0.966341f,-0.195126f},{0.479603f,-0.758439f,-0.441306f},{0.196735f,0.71953f,0.666013f}, +{0.178837f,0.211949f,0.960778f},{-0.365789f,0.882121f,0.29675f},{0.108296f,-0.661035f,-0.742499f}, +{0.231028f,-0.767635f,-0.597799f},{0.528096f,-0.802825f,-0.276744f},{0.812965f,-0.521272f,-0.259545f}, +{-0.707441f,-0.161392f,0.688099f},{-0.818057f,-0.262473f,0.511752f},{-0.83981f,-0.276777f,0.467027f}, +{-0.78566f,-0.272051f,0.555632f},{0.232743f,0.972491f,0.00962511f},{0.319045f,0.947739f,-0.000587843f}, +{0.355517f,0.91667f,-0.182547f},{0.0257376f,0.844356f,0.535164f},{-0.344861f,0.925942f,0.153956f}, +{-0.322892f,0.577177f,0.750071f},{0.979764f,0.15852f,-0.122203f},{-0.157063f,0.691591f,-0.705006f}, +{-0.217615f,0.649092f,-0.728919f},{-0.115872f,0.61574f,-0.779383f},{-0.156937f,-0.472687f,0.867143f}, +{0.375524f,0.469339f,0.799189f},{0.963422f,-0.262459f,-0.0541521f},{0.543008f,-0.133496f,0.829048f}, +{-0.0294388f,0.115412f,0.992881f},{-0.315051f,0.544746f,0.777171f},{-0.201034f,0.829596f,0.520919f}, +{-0.347545f,0.838266f,0.420146f},{-0.744658f,-0.0792734f,0.662722f},{0.539302f,-0.77313f,0.333802f}, +{0.503749f,-0.7688f,0.393933f},{-0.809059f,-0.0753806f,0.582874f},{-0.776578f,-0.217583f,0.591257f}, +{0.192285f,0.832061f,0.520289f},{0.0294664f,0.792039f,0.60976f},{0.0936467f,0.940049f,0.32793f}, +{0.0481948f,0.903587f,0.425685f},{0.62816f,0.554507f,0.545836f},{0.946447f,-0.0202053f,0.322227f}, +{0.123842f,0.398537f,0.908753f},{0.0566087f,-0.712531f,-0.699353f},{0.00541469f,-0.696924f,-0.717125f}, +{-0.827308f,0.442382f,-0.346208f},{0.945003f,0.288744f,-0.153608f},{-0.164464f,0.501697f,-0.849265f}, +{-0.212151f,0.449517f,-0.867713f},{-0.217853f,0.603f,-0.767418f},{0.777729f,-0.537961f,-0.32517f}, +{-0.350058f,0.556344f,0.753619f},{-0.438878f,0.486298f,0.75558f},{-0.340488f,0.390308f,0.855411f}, +{-0.249393f,0.667711f,0.701402f},{-0.169594f,0.899467f,0.402737f},{-0.40296f,0.798587f,0.447082f}, +{-0.260137f,0.904443f,0.3381f},{0.297284f,0.951307f,0.0814693f},{0.747864f,0.242122f,-0.618124f}, +{0.693854f,0.21889f,-0.686042f},{0.706679f,0.147446f,-0.692f},{0.0362161f,0.811871f,0.582713f}, +{-0.402915f,0.903071f,0.148733f},{-0.611856f,0.758072f,-0.225742f},{-0.60932f,0.783829f,-0.119753f}, +{0.566185f,0.76417f,0.308997f},{-0.331398f,0.274975f,0.902532f},{-0.286723f,-0.182686f,0.940434f}, +{-0.34717f,0.076168f,0.934704f},{-0.172591f,0.212991f,0.96169f},{-0.1893f,0.383452f,0.903953f}, +{-0.231082f,0.432922f,0.871309f},{-0.25959f,0.479614f,0.838202f},{-0.318628f,0.575777f,0.752966f}, +{-0.322619f,0.603173f,0.729451f},{-0.358251f,0.602617f,0.713098f},{-0.390833f,0.610403f,0.688954f}, +{0.388946f,-0.442439f,0.808065f},{0.477906f,0.397443f,0.783355f},{0.661378f,0.162789f,0.732174f}, +{-0.0720481f,0.806311f,0.587088f},{-0.565128f,0.822945f,0.0582318f},{-0.390616f,0.822168f,0.414076f}, +{-0.573525f,0.52527f,0.628618f},{-0.82288f,-0.00567614f,0.568186f},{-0.714775f,0.20468f,0.668733f}, +{0.19947f,0.979005f,0.0419626f},{0.340742f,0.93565f,0.0919395f},{-0.777639f,-0.623126f,-0.0836173f}, +{-0.828711f,-0.559582f,0.0103613f},{-0.771514f,-0.606945f,-0.190748f},{-0.685167f,-0.680435f,-0.259914f}, +{-0.953719f,0.300617f,0.00699773f},{-0.873393f,0.41651f,-0.252396f},{-0.829566f,0.377382f,-0.411587f}, +{0.500125f,0.797272f,0.337983f},{-0.502071f,0.526381f,0.686184f},{-0.419821f,0.617315f,0.665336f}, +{-0.961307f,0.160805f,-0.223676f},{0.989366f,0.0418744f,-0.139286f},{0.958475f,0.210319f,-0.192591f}, +{0.00907638f,0.395259f,0.918525f},{-0.121054f,0.339914f,0.932633f},{-0.0711068f,0.590537f,0.803872f}, +{-0.0363095f,0.710202f,0.703061f},{0.0153851f,0.853133f,0.521466f},{-0.112796f,0.818173f,0.5638f}, +{-0.16988f,0.971006f,-0.168189f},{-0.0077306f,0.996263f,-0.0860288f},{0.243035f,0.943361f,0.22584f}, +{-0.0621356f,0.340509f,0.938186f},{-0.0491563f,0.366334f,0.929184f},{-0.176118f,0.454378f,0.873226f}, +{-0.189273f,0.46869f,0.862847f},{-0.259481f,0.511469f,0.819188f},{-0.449379f,0.578745f,0.680524f}, +{0.0913338f,0.894366f,0.437912f},{0.42565f,-0.422144f,0.800385f},{0.59795f,-0.23884f,0.765122f}, +{0.45527f,-0.177488f,0.872483f},{0.386973f,-0.921255f,-0.0392579f},{0.250819f,-0.869655f,-0.425193f}, +{0.306345f,-0.905818f,-0.292655f},{-0.99852f,-0.0334156f,-0.0429045f},{-0.964216f,0.258421f,0.0592056f}, +{-0.987625f,0.154451f,-0.027243f},{-0.997861f,0.0519454f,-0.0396961f},{-0.83911f,0.481331f,0.253406f}, +{-0.926526f,-0.254974f,-0.276656f},{-0.692555f,0.643989f,0.325032f},{0.99397f,-0.107486f,0.0217039f}, +{-0.218578f,0.93918f,0.264886f},{-0.304393f,0.911539f,0.276481f},{-0.277804f,0.90877f,0.311387f}, +{-0.30602f,0.562103f,0.768369f},{-0.325307f,0.591888f,0.737457f},{-0.367669f,0.600766f,0.709859f}, +{-0.426539f,0.593136f,0.682828f},{-0.357713f,0.671509f,0.648935f},{-0.343895f,0.711826f,0.612406f}, +{0.540354f,0.319822f,0.778288f},{0.319022f,0.317524f,0.892974f},{0.0164285f,-0.758999f,-0.650885f}, +{0.810879f,0.41449f,-0.413125f},{0.402384f,-0.808354f,-0.429711f},{-0.692319f,0.269885f,0.66922f}, +{-0.802128f,-0.0286058f,0.596466f},{0.0118075f,0.999179f,-0.0387475f},{0.039404f,0.998234f,-0.0444579f}, +{-0.255023f,0.966562f,-0.0268538f},{0.181189f,0.943149f,0.278642f},{0.135556f,0.913165f,0.384388f}, +{0.407739f,0.656533f,0.634597f},{0.297586f,0.648132f,0.700976f},{0.384275f,0.922206f,-0.0432202f}, +{-0.0859526f,-0.656785f,-0.749163f},{0.599155f,-0.0773312f,-0.79689f},{0.324199f,0.527672f,0.785148f}, +{-0.932281f,-0.0620485f,-0.356374f},{-0.977376f,0.113279f,-0.178615f},{-0.93158f,0.134419f,-0.337773f}, +{0.140383f,-0.767237f,-0.625812f},{0.213569f,-0.832839f,-0.510654f},{0.532393f,-0.808362f,-0.251216f}, +{0.246787f,0.464288f,0.850607f},{0.193003f,0.739826f,0.644521f},{0.104031f,0.856008f,0.506387f}, +{-0.184839f,0.440609f,0.878464f},{-0.619346f,0.0773028f,0.781304f},{-0.779055f,-0.108587f,0.61748f}, +{-0.72702f,-0.208468f,0.654204f},{-0.693424f,-0.173667f,0.699288f},{-0.0203507f,0.99796f,-0.060504f}, +{-0.116628f,0.989023f,0.0907244f},{-0.138448f,0.974696f,0.175497f},{-0.118911f,0.974713f,0.189194f}, +{0.782602f,0.568475f,0.253714f},{-0.660721f,-0.733755f,-0.158278f},{0.956928f,0.238728f,0.165221f}, +{0.549901f,0.697434f,0.45956f},{0.499327f,0.758075f,0.419517f},{0.607054f,0.674272f,0.420528f}, +{0.512313f,-0.831211f,-0.215926f},{0.697816f,-0.69249f,-0.183061f},{0.0784108f,0.317364f,0.945057f}, +{-0.013652f,0.281661f,0.959417f},{-0.103051f,0.383828f,0.917636f},{-0.184355f,0.471683f,0.862281f}, +{-0.197612f,0.535434f,0.821133f},{-0.240097f,0.582367f,0.776661f},{-0.260232f,0.584369f,0.76863f}, +{-0.342142f,0.66669f,0.662165f},{-0.919643f,0.348874f,-0.180397f},{-0.951161f,0.258725f,-0.168385f}, +{0.196828f,-0.0334996f,0.979865f},{0.93696f,-0.10587f,-0.333013f},{0.898716f,0.0143765f,-0.438296f}, +{0.934906f,-0.162736f,-0.315386f},{-0.0443143f,-0.514988f,-0.856051f},{-0.576905f,-0.302724f,0.758643f}, +{0.999693f,-0.0144882f,0.020096f},{-0.902492f,-0.418109f,-0.103404f},{-0.91035f,-0.370171f,-0.185029f}, +{-0.972665f,0.169551f,-0.158669f},{-0.537973f,0.299744f,0.787869f},{-0.317389f,0.276084f,0.907217f}, +{-0.43221f,0.364523f,0.824814f},{0.530498f,0.182756f,-0.827751f},{0.497767f,0.331848f,-0.801315f}, +{0.59232f,0.276457f,-0.756789f},{0.903917f,-0.0343129f,-0.426329f},{-0.129496f,-0.412196f,-0.901845f}, +{-0.997442f,-0.0199234f,-0.0686405f},{-0.116131f,0.99273f,0.0316319f},{-0.148505f,0.984841f,0.0896363f}, +{-0.107525f,0.991118f,0.0782455f},{0.872192f,0.459426f,0.167954f},{-0.302342f,0.32664f,0.895486f}, +{-0.394453f,0.402819f,0.82592f},{0.988901f,-0.142468f,0.042162f},{-0.28951f,0.597491f,0.747789f}, +{0.886142f,-0.0227338f,-0.462855f},{0.372933f,-0.92439f,-0.080149f},{0.324752f,0.473531f,0.818721f}, +{0.0181762f,0.577834f,0.815952f},{-0.446479f,-0.0219026f,0.894526f},{-0.41044f,-0.340059f,0.846108f}, +{-0.497253f,-0.366512f,0.786389f},{-0.188972f,0.87435f,-0.446992f},{-0.27229f,-0.212348f,0.938492f}, +{-0.260154f,-0.26183f,0.92939f},{-0.424896f,-0.295882f,0.855522f},{-0.201163f,-0.282722f,0.937871f}, +{-0.349685f,-0.362444f,0.863918f},{-0.258599f,-0.400252f,0.879162f},{-0.0663588f,-0.124558f,0.989991f}, +{-0.277381f,-0.292648f,0.915105f},{-0.0129428f,0.051201f,0.998604f},{-0.21943f,0.0640172f,0.973526f}, +{-0.0834486f,0.148989f,0.985311f},{-0.176729f,0.302573f,0.936598f},{-0.303486f,0.374783f,0.876033f}, +{-0.22937f,0.520291f,0.822609f},{-0.399844f,0.536487f,0.743173f},{-0.190027f,0.270728f,0.943714f}, +{-0.0885366f,0.238847f,0.967013f},{-0.208084f,0.301494f,0.930485f},{-0.218262f,0.436112f,0.873022f}, +{-0.209885f,0.518052f,0.829199f},{-0.251104f,0.521023f,0.815771f},{-0.27394f,0.616177f,0.738432f}, +{-0.267678f,0.649952f,0.711275f},{-0.271756f,0.870571f,0.410189f},{-0.248053f,0.886882f,0.389757f}, +{-0.341894f,0.876298f,0.339426f},{0.913529f,-0.0778003f,-0.399265f},{-0.220965f,0.27626f,-0.935337f}, +{0.324672f,0.945114f,-0.0366963f},{-0.549577f,0.390051f,0.7388f},{-0.0404058f,0.453485f,0.890348f}, +{-0.265051f,0.34101f,0.90192f},{0.671957f,0.182503f,-0.717751f},{0.625907f,0.322451f,-0.710116f}, +{0.561647f,-0.442315f,0.699221f},{0.329729f,0.556732f,0.762449f},{0.915737f,0.214104f,-0.339978f}, +{-0.366646f,-0.323675f,0.872241f},{-0.474031f,-0.323003f,0.819124f},{-0.462516f,-0.19931f,0.863918f}, +{-0.60706f,-0.19681f,0.769899f},{-0.413657f,-0.149255f,0.898115f},{-0.551041f,-0.226188f,0.803239f}, +{-0.256732f,-0.2343f,0.937653f},{-0.432891f,0.81967f,0.375162f},{-0.82982f,0.47565f,0.291813f}, +{0.935737f,-0.0667342f,-0.346327f},{0.410462f,-0.898124f,-0.157782f},{0.381093f,0.353227f,0.854399f}, +{0.314514f,0.455215f,0.832983f},{-0.107452f,-0.0516191f,0.992869f},{-0.436083f,-0.386618f,0.812624f}, +{-0.326328f,-0.290193f,0.89961f},{-0.358768f,-0.351906f,0.86455f},{-0.491272f,-0.274142f,0.826739f}, +{-0.249382f,-0.0817001f,0.964953f},{-0.333758f,-0.24729f,0.909644f},{-0.364851f,-0.0687879f,0.928522f}, +{-0.168992f,-0.211946f,0.962559f},{-0.199238f,-0.291946f,0.935453f},{-0.2244f,-0.252274f,0.941277f}, +{-0.152452f,-0.164372f,0.974546f},{-0.112379f,-0.0502458f,0.992394f},{-0.0843327f,0.0708477f,0.993916f}, +{-0.0420862f,0.0883098f,0.995204f},{-0.116689f,0.21632f,0.969324f},{-0.217116f,0.527381f,0.821419f}, +{-0.175943f,0.699574f,0.692561f},{-0.29065f,0.677972f,0.675186f},{-0.207183f,0.94949f,-0.235676f}, +{0.576123f,-0.814343f,-0.0701916f},{-0.330163f,0.771793f,0.543441f},{-0.39642f,0.831665f,0.388823f}, +{-0.396915f,0.639425f,0.658478f},{-0.183854f,0.352624f,0.917526f},{-0.170048f,0.255627f,0.951703f}, +{-0.0999526f,0.132185f,0.986173f},{-0.204507f,0.401786f,0.892605f},{-0.225599f,0.496702f,0.838089f}, +{-0.23613f,0.499564f,0.833474f},{-0.272788f,0.477579f,0.835168f},{-0.255052f,0.575954f,0.776676f}, +{-0.288071f,0.716377f,0.635468f},{0.939666f,0.00962893f,-0.341958f},{0.811251f,-0.550205f,-0.197856f}, +{0.84832f,-0.501184f,-0.170784f},{0.906024f,-0.41817f,-0.0652293f},{0.806224f,0.109341f,0.581419f}, +{0.807168f,0.301015f,0.507808f},{0.391369f,0.919183f,-0.0439765f},{0.259863f,0.0750233f,0.962727f}, +{0.332689f,0.161066f,0.92918f},{0.378018f,0.180702f,0.907992f},{-0.325281f,-0.413715f,0.850313f}, +{0.227168f,-0.359984f,-0.904879f},{0.43349f,-0.686914f,-0.583297f},{0.741767f,0.00615409f,0.670629f}, +{0.519002f,0.52435f,0.67505f},{-0.97891f,0.0906991f,-0.183055f},{0.394563f,0.9161f,0.0712859f}, +{0.389378f,0.920614f,0.0292228f},{0.319017f,0.528083f,0.786992f},{-0.307802f,-0.340839f,0.888306f}, +{-0.126457f,0.361748f,0.92366f},{-0.16985f,0.756014f,0.632134f},{-0.185053f,0.754408f,0.629781f}, +{0.0966245f,-0.689971f,-0.717359f},{0.492416f,-0.869194f,-0.0450272f},{0.996683f,0.0751418f,-0.0312672f}, +{-0.234427f,0.685005f,0.689792f},{0.867339f,0.0838635f,-0.490602f},{0.925486f,0.164947f,-0.34098f}, +{-0.204274f,0.789897f,0.578217f},{0.337036f,-0.144287f,0.93037f},{0.872065f,-0.43883f,-0.216636f}, +{0.723614f,-0.646477f,-0.241765f},{0.303861f,0.202101f,0.931033f},{0.236392f,0.303918f,0.922904f}, +{-0.03706f,-0.379717f,0.92436f},{-0.24061f,-0.442195f,0.864043f},{-0.320437f,-0.327133f,0.88899f}, +{-0.315056f,-0.394821f,0.86305f},{-0.394311f,-0.306347f,0.866412f},{-0.291312f,-0.0705552f,0.954023f}, +{-0.304152f,-0.0144173f,0.952514f},{-0.286319f,-0.243813f,0.926594f},{-0.315406f,-0.311765f,0.896282f}, +{-0.305999f,-0.289459f,0.906961f},{-0.243082f,-0.160769f,0.95659f},{-0.139866f,-0.103768f,0.984718f}, +{-0.0984367f,0.00461992f,0.995133f},{0.000226813f,0.128423f,0.991719f},{-0.0322293f,0.139832f,0.989651f}, +{-0.0463018f,0.159905f,0.986046f},{-0.0349219f,0.332265f,0.942539f},{-0.0872712f,0.549626f,0.83084f}, +{-0.143092f,0.695318f,0.704314f},{0.844038f,0.41268f,0.342484f},{-0.0784911f,0.135579f,0.987653f}, +{-0.151022f,0.495322f,0.855482f},{0.864087f,-0.487038f,-0.127076f},{-0.118199f,0.191062f,0.974435f}, +{-0.128221f,0.381595f,0.915393f},{-0.16414f,0.47926f,0.862188f},{-0.180967f,0.512564f,0.839363f}, +{-0.147203f,0.567275f,0.810266f},{-0.151267f,0.653834f,0.741363f},{-0.188561f,0.726022f,0.661315f}, +{0.824964f,-0.480821f,-0.297061f},{0.905172f,0.260718f,-0.335692f},{-0.751042f,0.593217f,0.289876f}, +{-0.32475f,-0.35033f,-0.878525f},{-0.481899f,-0.260127f,-0.836724f},{-0.624517f,-0.221113f,-0.749058f}, +{-0.103103f,0.445253f,0.889449f},{-0.309203f,-0.277461f,-0.90962f},{-0.294538f,-0.402181f,-0.86689f}, +{0.864967f,0.288401f,-0.410679f},{0.897178f,0.275958f,-0.344845f},{0.767788f,-0.232109f,0.597183f}, +{0.760242f,-0.495563f,0.420059f},{0.808193f,-0.49461f,0.319663f},{0.733594f,0.286807f,-0.616102f}, +{0.640647f,0.238282f,-0.729927f},{-0.281333f,-0.352565f,0.892496f},{-0.232012f,0.873399f,0.428187f}, +{-0.248022f,0.814072f,0.52514f},{0.837728f,-0.0939249f,-0.53795f},{0.870655f,-0.279119f,-0.405033f}, +{-0.881121f,-0.0520004f,-0.470024f},{-0.0155312f,-0.466044f,0.884625f},{-0.129013f,-0.669994f,0.73107f}, +{-0.197947f,-0.475871f,0.85695f},{-0.244171f,-0.34963f,0.90451f},{-0.140039f,-0.330352f,0.933411f}, +{-0.057988f,-0.337569f,0.939513f},{-0.122666f,-0.113582f,0.985927f},{-0.205128f,-0.00877181f,0.978696f}, +{-0.337272f,-0.0746786f,0.938441f},{-0.306237f,-0.258884f,0.916078f},{-0.278592f,-0.259471f,0.924695f}, +{-0.261924f,-0.250819f,0.931926f},{-0.209337f,-0.0287817f,0.97742f},{-0.0601506f,0.0672967f,0.995918f}, +{0.0611062f,0.0532419f,0.99671f},{0.0713627f,0.103608f,0.992055f},{-0.0695355f,0.803765f,0.59087f}, +{-0.644098f,0.273464f,-0.714392f},{0.0732606f,0.601907f,0.795199f},{-0.2022f,0.892853f,0.402403f}, +{-0.116181f,0.690582f,0.713862f},{0.839387f,0.072094f,-0.538733f},{0.906994f,0.157665f,-0.390518f}, +{0.880792f,0.258935f,-0.396433f},{0.849277f,0.279145f,-0.448115f},{0.88698f,-0.0393897f,-0.460125f}, +{-0.256429f,-0.366661f,0.894318f},{0.384314f,-0.129266f,0.914108f},{-0.91019f,-0.38715f,-0.147201f}, +{-0.873766f,-0.460888f,-0.15529f},{0.0533412f,-0.987276f,0.1498f},{-0.0332455f,-0.999425f,-0.00667568f}, +{-0.0177683f,-0.998598f,-0.0498695f},{0.743946f,-0.315207f,-0.589228f},{-0.0300893f,0.428489f,0.903046f}, +{-0.141131f,-0.616613f,0.774513f},{-0.143887f,-0.543126f,0.827231f},{0.151877f,-0.0435705f,0.987439f}, +{0.135903f,0.0967646f,0.985985f},{0.181863f,-0.0316108f,0.982816f},{0.817395f,0.471839f,-0.330504f}, +{0.834915f,0.44021f,-0.330353f},{-0.439625f,0.363522f,-0.821329f},{-0.23135f,-0.491517f,-0.839576f}, +{0.881996f,0.263243f,0.390879f},{0.804092f,0.450491f,-0.387936f},{0.379275f,-0.920389f,-0.0950521f}, +{0.657543f,-0.0898175f,0.748044f},{0.60158f,0.0546622f,0.79694f},{0.424673f,-0.380215f,0.821638f}, +{0.480442f,-0.295344f,0.825801f},{0.296132f,0.463751f,0.835009f},{0.271331f,0.0872357f,0.958525f}, +{0.272219f,-0.172097f,0.94672f},{0.653222f,-0.218414f,0.72498f},{0.637907f,-0.0586593f,0.767876f}, +{0.432417f,-0.0287288f,0.901216f},{0.375382f,0.133798f,0.917162f},{0.668195f,0.332693f,0.665456f}, +{0.751623f,0.310123f,0.58214f},{0.70471f,0.352257f,0.615873f},{0.354198f,-0.110598f,0.928607f}, +{0.316442f,-0.111138f,0.942079f},{0.500513f,0.429239f,0.751825f},{0.485563f,0.505267f,0.713396f}, +{0.467589f,0.389971f,0.793274f},{0.269565f,0.432078f,0.860606f},{0.526376f,-0.206247f,0.824858f}, +{0.547881f,-0.180711f,0.816804f},{0.516456f,-0.177985f,0.837613f},{0.265807f,0.621366f,0.737056f}, +{0.396606f,0.461164f,0.793745f},{0.268517f,0.679316f,0.682956f},{0.608431f,-0.475134f,0.635657f}, +{0.581453f,0.335069f,0.741378f},{0.565053f,-0.011672f,0.824972f},{0.542132f,-0.254652f,0.800778f}, +{0.480758f,-0.379163f,0.790637f},{0.456013f,-0.393873f,0.79807f},{0.879688f,-0.114436f,0.461577f}, +{0.858958f,-0.0758828f,0.506393f},{0.85334f,-0.175889f,0.49079f},{0.864541f,0.245988f,0.438246f}, +{0.459771f,0.488017f,0.741923f},{0.648193f,0.136459f,0.749149f},{0.745059f,-0.146613f,0.650686f}, +{0.320391f,0.613417f,0.721851f},{0.381497f,0.502289f,0.775993f},{0.276193f,0.651878f,0.706238f}, +{0.787947f,-0.266373f,0.555144f},{0.738517f,-0.18476f,0.648426f},{-0.10106f,-0.198674f,-0.974841f}, +{-0.213871f,-0.405287f,-0.88882f},{0.120389f,-0.573452f,-0.810345f},{0.549648f,0.115702f,0.827345f}, +{0.656562f,-0.0125393f,0.754168f},{0.523616f,0.221222f,0.822732f},{0.5305f,-0.207482f,0.821901f}, +{0.503594f,-0.178747f,0.845247f},{0.464655f,-0.297417f,0.834049f},{0.436071f,-0.269518f,0.858605f}, +{0.552355f,-0.130993f,0.823253f},{0.71381f,-0.196261f,0.672277f},{0.255606f,0.690306f,0.676863f}, +{0.210124f,0.77675f,0.593723f},{0.853942f,-0.0356107f,0.519148f},{0.456866f,-0.524287f,0.718607f}, +{0.497688f,-0.585216f,0.640179f},{-0.239292f,-0.426292f,-0.872361f},{-0.133658f,-0.421392f,-0.896975f}, +{-0.0754212f,-0.532674f,-0.842953f},{0.6607f,-0.607619f,0.440766f},{0.628709f,-0.577003f,0.521336f}, +{0.641454f,-0.668221f,0.376852f},{0.666737f,-0.498983f,0.553603f},{0.800721f,0.0591873f,0.596106f}, +{0.748422f,0.000704595f,0.663222f},{0.790442f,-0.196599f,0.58013f},{0.7523f,-0.31572f,0.578244f}, +{0.770543f,-0.115037f,0.626921f},{0.657429f,-0.378401f,0.651613f},{0.558381f,-0.300341f,0.773309f}, +{0.605352f,-0.483657f,0.632159f},{0.508999f,-0.563832f,0.650394f},{0.547093f,-0.479533f,0.686103f}, +{0.647568f,0.075846f,0.758224f},{0.657391f,0.209588f,0.723816f},{0.778641f,0.458995f,0.427835f}, +{0.796311f,-0.124677f,0.591899f},{0.287351f,0.191424f,0.938502f},{0.732084f,0.104478f,0.673154f}, +{0.576522f,0.362983f,0.732029f},{0.465965f,0.367877f,0.804701f},{0.730207f,0.0875507f,0.677594f}, +{-0.399503f,0.326363f,-0.85667f},{-0.354372f,0.129678f,-0.926069f},{-0.448822f,0.605935f,-0.656811f}, +{-0.617025f,0.688594f,-0.380944f},{0.572087f,-0.696272f,0.4335f},{0.5902f,-0.307268f,0.746492f}, +{0.512315f,-0.27866f,0.812331f},{0.615482f,-0.524555f,0.588238f},{0.493002f,-0.409266f,0.767757f}, +{0.542152f,-0.375549f,0.751687f},{0.531583f,-0.39377f,0.74991f},{0.465902f,0.343074f,0.81562f}, +{0.556731f,-0.769617f,0.312633f},{0.254734f,-0.853269f,-0.455018f},{0.844112f,0.373341f,0.384825f}, +{0.798595f,0.33797f,0.498018f},{0.975491f,0.218904f,-0.0223324f},{0.411182f,-0.19688f,0.890038f}, +{0.55109f,-0.118472f,0.825993f},{0.555506f,-0.31389f,0.769991f},{0.483713f,-0.52489f,0.700365f}, +{0.476981f,-0.179667f,0.860354f},{-0.447194f,0.0450272f,-0.893303f},{0.323563f,-0.182347f,0.92847f}, +{0.11422f,0.271978f,-0.955501f},{-0.0334828f,0.360681f,-0.932088f},{0.0801935f,0.352312f,-0.93244f}, +{-0.165139f,-0.53863f,-0.8262f},{0.820463f,0.471607f,-0.323152f},{0.817037f,0.503878f,-0.280281f}, +{0.565589f,-0.744425f,-0.354882f},{0.593147f,-0.757668f,-0.272244f},{0.698756f,-0.639595f,-0.320402f}, +{0.416088f,-0.0895367f,0.904905f},{0.492055f,0.0671786f,0.867968f},{0.529662f,0.0245061f,0.847854f}, +{-0.290736f,-0.569161f,0.769109f},{0.343488f,-0.0248503f,0.938828f},{0.351263f,-0.283942f,0.892183f}, +{-0.100958f,0.304861f,-0.947031f},{-0.147563f,0.426841f,-0.892206f},{0.160646f,0.724895f,0.669865f}, +{0.739509f,-0.237147f,0.629991f},{0.755263f,-0.262598f,0.600517f},{0.720221f,-0.175747f,0.671115f}, +{-0.244267f,-0.453948f,-0.856893f},{-0.0660144f,-0.604178f,-0.79411f},{0.573614f,-0.482652f,0.661827f}, +{0.560916f,-0.327772f,0.760223f},{0.380138f,0.160625f,0.910876f},{0.851176f,0.264634f,-0.453287f}, +{0.34345f,-0.0915945f,0.934694f},{0.260014f,0.0541945f,0.964083f},{0.313326f,0.212754f,0.925507f}, +{0.396535f,0.543902f,-0.739548f},{0.444644f,0.49191f,-0.748543f},{0.503687f,0.52311f,-0.687499f}, +{-0.305173f,-0.397453f,-0.86539f},{0.660256f,0.283758f,0.695373f},{0.781524f,0.0706021f,0.619867f}, +{0.793597f,0.216873f,0.568481f},{0.733662f,0.193507f,0.65138f},{0.323353f,0.369017f,0.871361f}, +{0.582228f,-0.340961f,0.738076f},{0.549202f,0.284132f,0.785905f},{0.290724f,0.169076f,0.94175f}, +{0.53424f,-0.844963f,0.0250082f},{0.912901f,-0.035308f,0.406652f},{0.825201f,0.0115751f,0.56472f}, +{0.94043f,0.103742f,0.323773f},{-0.358271f,0.346487f,-0.866942f},{-0.277017f,0.312182f,-0.908738f}, +{-0.322405f,0.20153f,-0.9249f},{-0.981484f,-0.040964f,-0.187113f},{-0.846049f,-0.367576f,-0.386119f}, +{-0.723049f,-0.665594f,-0.184891f},{-0.681233f,0.638163f,0.358706f},{-0.887797f,0.251246f,0.385606f}, +{-0.945932f,0.286633f,0.151838f},{0.485297f,0.0684109f,0.871669f},{0.514312f,0.461732f,0.722694f}, +{0.622169f,0.403788f,0.670717f},{0.681605f,0.244739f,0.689578f},{0.498462f,-0.119438f,0.858644f}, +{0.606269f,-0.213205f,0.766148f},{0.768382f,0.484224f,-0.41847f},{0.815839f,0.442994f,-0.371703f}, +{-0.373507f,-0.270642f,-0.887269f},{-0.246314f,-0.0015448f,-0.969189f},{-0.431788f,-0.307208f,-0.848046f}, +{-0.347352f,-0.0362057f,-0.937036f},{0.0188641f,-0.999595f,-0.0213147f},{-0.00396728f,-0.999933f,-0.010845f}, +{0.0160447f,-0.999596f,-0.0234478f},{0.564655f,-0.441574f,0.697264f},{0.69031f,-0.153769f,0.706985f}, +{0.653448f,0.150736f,0.741812f},{0.651377f,0.314426f,0.690539f},{0.627538f,0.228138f,0.744412f}, +{0.477152f,0.341111f,0.809919f},{-0.983667f,-0.0636223f,0.168378f},{0.743203f,-0.429802f,0.512757f}, +{0.674133f,-0.406715f,0.616545f},{0.710554f,-0.395237f,0.582151f},{0.80919f,0.278181f,0.51752f}, +{0.826218f,0.290013f,0.482966f},{0.781084f,0.319842f,0.536291f},{0.374297f,-0.414591f,0.829468f}, +{0.292665f,-0.215483f,0.931619f},{0.147606f,0.803867f,0.576204f},{0.218746f,0.781826f,0.583866f}, +{0.512944f,-0.418974f,0.749233f},{0.567309f,-0.478639f,0.670123f},{0.577802f,-0.447301f,0.682691f}, +{0.685652f,-0.52024f,0.509148f},{0.448118f,-0.740268f,0.501192f},{0.684582f,0.410588f,0.6023f}, +{0.617891f,0.399018f,0.677492f},{0.632734f,0.295336f,0.715839f},{0.870482f,0.162018f,0.46477f}, +{0.840489f,0.186369f,0.508768f},{0.770366f,-0.159666f,0.617287f},{0.489274f,-0.00781279f,0.872095f}, +{0.522915f,-0.119012f,0.844036f},{0.525504f,-0.253163f,0.812253f},{0.584816f,-0.690932f,0.424975f}, +{0.765617f,0.283534f,0.577442f},{0.680954f,0.47874f,0.554174f},{0.75469f,0.139645f,0.641047f}, +{0.756042f,0.181321f,0.628906f},{0.706788f,-0.577248f,0.408944f},{0.270976f,0.449686f,0.85109f}, +{0.670575f,0.280147f,0.686911f},{0.731219f,0.377028f,0.56848f},{0.777606f,0.343926f,0.52635f}, +{0.764272f,0.413505f,0.494875f},{0.984362f,0.00908258f,0.175924f},{0.994702f,0.0701487f,0.0751525f}, +{0.988355f,0.0821244f,0.128101f},{0.819972f,-0.180455f,0.543215f},{0.786792f,0.106034f,0.608043f}, +{0.738693f,0.417579f,0.529113f},{0.813713f,0.199231f,0.546057f},{0.790175f,-0.192608f,0.58183f}, +{0.784095f,-0.245785f,0.569899f},{0.974171f,0.0716883f,0.214129f},{0.769246f,-0.371558f,0.519813f}, +{0.996425f,0.0802f,0.0265568f},{0.995753f,0.0774648f,0.0497599f},{0.994732f,0.0839796f,0.058788f}, +{0.0684754f,0.794747f,0.603066f},{0.890577f,-0.0326624f,0.453658f},{0.88509f,0.0273287f,0.464616f}, +{0.8882f,-0.0223627f,0.458912f},{0.673587f,-0.673846f,0.303664f},{0.443082f,-0.835915f,0.32392f}, +{0.601954f,-0.695112f,0.393027f},{0.529223f,-0.697322f,0.483389f},{0.520604f,-0.78592f,0.333618f}, +{0.854102f,0.0823997f,0.513536f},{0.871333f,-0.199525f,0.448296f},{0.789235f,0.160319f,0.592795f}, +{0.760528f,0.173113f,0.625803f},{0.567484f,0.383543f,0.7286f},{0.155292f,0.0116503f,0.9878f}, +{0.198612f,0.0248425f,0.979763f},{0.530868f,-0.169252f,0.830381f},{0.492979f,-0.111174f,0.862909f}, +{0.57538f,-0.112175f,0.810157f},{0.729484f,-0.0330009f,0.683201f},{0.788689f,-0.0150649f,0.614608f}, +{0.777266f,0.0200755f,0.628852f},{0.775266f,-0.284338f,0.564016f},{0.723422f,-0.280706f,0.630766f}, +{0.813826f,0.330595f,0.477906f},{0.780357f,0.334197f,0.528541f},{0.749978f,-0.563062f,0.347123f}, +{0.828104f,-0.38699f,0.405564f},{0.817079f,0.255468f,0.516834f},{0.768042f,0.385863f,0.511098f}, +{0.859619f,0.103461f,0.500351f},{0.774398f,0.172551f,0.608714f},{0.772662f,-0.40118f,0.491984f}, +{0.701697f,-0.127203f,0.701028f},{0.540935f,-0.797161f,0.268184f},{0.888043f,-0.0861904f,0.45161f}, +{0.710171f,0.329627f,0.622096f},{0.869048f,0.194343f,0.454958f},{0.175361f,0.624014f,0.761482f}, +{0.637965f,0.41983f,0.645556f},{0.652155f,0.403813f,0.641583f},{0.62877f,0.443097f,0.638994f}, +{-0.971605f,0.236558f,-0.00496129f},{0.59756f,0.462793f,0.654786f},{0.580389f,0.451229f,0.677895f}, +{0.626065f,0.431011f,0.649825f},{0.476493f,0.537168f,0.695992f},{0.620057f,0.465976f,0.631186f}, +{0.571138f,0.569021f,0.591622f},{0.683396f,-0.0668563f,0.72698f},{0.793913f,-0.0870938f,0.601762f}, +{0.815752f,-0.501744f,0.287753f},{0.606837f,-0.736726f,0.298302f},{0.896606f,-0.245435f,0.368592f}, +{0.896765f,0.0226329f,0.441927f},{0.844094f,0.216265f,0.490648f},{0.777054f,0.398813f,0.486966f}, +{0.785435f,0.364075f,0.500541f},{0.839493f,0.275943f,0.468089f},{0.243468f,-0.824286f,-0.511152f}, +{0.346621f,-0.880084f,-0.32451f},{0.536899f,0.0021648f,0.843644f},{0.585054f,0.0566912f,0.809011f}, +{0.650729f,-0.0826509f,0.754798f},{0.375913f,0.296442f,0.877959f},{0.110642f,-0.716504f,-0.688752f}, +{-0.193341f,-0.395496f,-0.897888f},{0.628335f,0.415744f,0.657534f},{0.827336f,0.279735f,0.487097f}, +{0.612704f,0.444252f,0.653631f},{0.48401f,0.4591f,0.744958f},{0.643482f,0.399456f,0.652967f}, +{0.57849f,0.362153f,0.730886f},{0.603739f,0.353751f,0.714395f},{0.669212f,0.391928f,0.631307f}, +{0.637042f,0.3499f,0.686839f},{0.801865f,0.000502496f,0.597504f},{0.832038f,0.0306209f,0.553873f}, +{0.811466f,-0.055155f,0.581791f},{0.470295f,-0.121854f,0.874056f},{0.767111f,-0.500143f,0.401744f}, +{0.739372f,0.426622f,0.520886f},{0.411713f,-0.155696f,0.897915f},{0.455116f,-0.227652f,0.860839f}, +{0.381047f,-0.107122f,0.918329f},{0.693332f,0.310097f,0.650485f},{0.730533f,0.151737f,0.665805f}, +{0.776021f,0.246385f,0.580591f},{0.697968f,-0.373382f,0.611086f},{0.866595f,-0.263565f,0.42373f}, +{0.811826f,0.486604f,0.322731f},{0.761744f,0.331298f,0.556766f},{-0.298661f,0.350782f,-0.887555f}, +{0.915881f,0.147323f,0.373441f},{0.92463f,0.311156f,0.21964f},{0.368686f,-0.785445f,0.497139f}, +{0.531938f,-0.737281f,0.416483f},{0.38531f,-0.870855f,0.305203f},{0.671745f,-0.645467f,0.363499f}, +{0.716224f,0.342647f,0.60796f},{0.769271f,0.376456f,0.51624f},{0.821107f,0.176575f,0.542775f}, +{0.849541f,0.0720743f,0.522576f},{0.828452f,0.10049f,0.550971f},{0.77968f,0.163857f,0.604359f}, +{0.566189f,0.00160388f,0.824274f},{0.476003f,0.0827108f,0.875546f},{0.554853f,-0.0521107f,0.830315f}, +{0.589359f,-0.181224f,0.787282f},{0.662155f,-0.284197f,0.693385f},{0.61982f,-0.514361f,0.592668f}, +{0.581998f,0.163587f,0.796566f},{0.513561f,0.322466f,0.795154f},{0.562209f,0.321646f,0.761882f}, +{0.535793f,0.38685f,0.750515f},{0.526098f,0.419382f,0.739824f},{0.490478f,0.469349f,0.734264f}, +{0.64664f,0.294523f,-0.703643f},{0.624458f,-0.561986f,0.542424f},{0.803143f,-0.258757f,0.536663f}, +{0.84475f,-0.0441311f,0.533338f},{0.792842f,0.173526f,0.584201f},{0.702839f,0.333032f,0.628576f}, +{0.863811f,0.113649f,0.490831f},{0.843999f,0.0339543f,0.535269f},{0.725844f,0.0529413f,0.685819f}, +{-0.73513f,-0.674473f,0.0683321f},{0.504364f,-0.490432f,0.710699f},{0.37711f,-0.530281f,0.759335f}, +{0.583048f,-0.597629f,0.550359f},{0.0436126f,-0.966946f,-0.251225f},{0.537038f,0.494685f,0.683284f}, +{0.546936f,0.0266999f,0.836748f},{0.724826f,-0.463617f,0.509594f},{0.693024f,-0.379127f,0.613173f}, +{0.709473f,-0.552913f,0.436962f},{0.514175f,0.22697f,0.827109f},{0.557267f,0.12334f,0.821122f}, +{0.482026f,0.308139f,0.820184f},{0.513391f,0.555068f,0.654469f},{0.453179f,0.569752f,0.685574f}, +{0.406531f,-0.413933f,0.814489f},{0.811775f,0.578688f,-0.0783687f},{0.973187f,0.225135f,-0.0471338f}, +{0.434216f,0.566594f,0.700305f},{0.538412f,-0.036777f,0.841879f},{0.491574f,0.0444878f,0.869698f}, +{0.415802f,0.190611f,0.889256f},{0.617904f,-0.566391f,0.54534f},{0.699226f,-0.174741f,0.693216f}, +{0.746495f,-0.220216f,0.627894f},{0.648682f,0.147411f,0.746647f},{0.568483f,0.442257f,0.693711f}, +{0.57011f,0.139636f,0.809615f},{0.948333f,-0.29193f,-0.124263f},{0.476851f,0.0790041f,0.875427f}, +{0.895368f,0.438086f,0.0799757f},{0.861625f,0.504983f,0.0509301f},{0.905583f,0.412128f,0.100346f}, +{0.385951f,0.474715f,0.791004f},{0.500776f,0.315259f,0.806124f},{0.78597f,0.498529f,-0.365678f}, +{-0.039175f,-0.970836f,-0.236521f},{0.279225f,0.728435f,0.625633f},{0.355854f,0.545909f,0.758519f}, +{0.614204f,-0.411429f,0.673409f},{0.4737f,-0.4708f,0.744281f},{0.577135f,-0.434087f,0.691725f}, +{-0.49125f,0.856358f,-0.159137f},{0.183845f,0.927353f,0.32591f},{0.430745f,0.871104f,0.235873f}, +{0.533354f,0.00972195f,0.845836f},{0.523564f,0.106788f,0.845268f},{0.533085f,-0.134271f,0.835339f}, +{0.616118f,-0.527777f,0.584679f},{0.57025f,-0.527772f,0.629501f},{0.394466f,0.440697f,0.806339f}, +{0.426039f,0.454103f,0.782484f},{0.545621f,-0.597095f,-0.588026f},{0.686729f,-0.529675f,-0.497844f}, +{0.509716f,-0.260083f,0.820089f},{0.623851f,-0.377081f,0.684558f},{0.629402f,0.220525f,0.745132f}, +{0.529436f,0.339631f,0.777399f},{0.623309f,0.420085f,0.659556f},{0.80518f,0.0871658f,0.58659f}, +{0.780352f,-0.0962468f,0.617889f},{0.523776f,-0.321197f,0.788981f},{0.515573f,-0.306043f,0.800326f}, +{0.544485f,-0.219115f,0.809645f},{0.625398f,-0.231908f,0.745048f},{0.706314f,-0.255021f,0.660368f}, +{0.673997f,-0.0995925f,0.73199f},{0.175291f,0.557259f,0.811625f},{-0.316229f,0.390378f,-0.864641f}, +{0.602475f,-0.105106f,0.791187f},{0.511986f,-0.0480917f,0.857646f},{0.549755f,0.286096f,0.784805f}, +{0.401885f,0.448359f,0.798412f},{0.647712f,0.0793625f,0.75774f},{0.693393f,-0.138186f,0.707185f}, +{0.501942f,-0.419292f,0.756471f},{0.417585f,0.557046f,0.71786f},{0.48062f,0.607109f,0.63279f}, +{0.634624f,-0.714085f,-0.295524f},{0.301532f,0.609365f,0.733316f},{0.653937f,-0.26285f,0.70942f}, +{0.622959f,-0.417671f,0.661418f},{-0.27087f,-0.335809f,-0.902143f},{0.431305f,-0.544847f,0.719109f}, +{0.438527f,0.4447f,0.780984f},{0.475877f,0.4592f,0.750118f},{0.614748f,-0.108916f,0.781167f}, +{0.648812f,-0.709042f,-0.276231f},{0.807503f,-0.288409f,0.514548f},{0.329362f,0.199389f,0.922911f}, +{-0.288854f,-0.228841f,0.929621f},{0.032487f,-0.0237096f,0.999191f},{0.379595f,-0.203006f,0.902605f}, +{0.525301f,0.348523f,0.776267f},{0.353571f,0.251066f,0.901084f},{0.747355f,0.353664f,0.562478f}, +{-0.555693f,-0.784513f,-0.275219f},{0.411451f,0.499707f,0.762234f},{0.247906f,0.683824f,0.686241f}, +{0.432255f,0.553228f,0.712106f},{0.0447714f,0.929401f,0.366345f},{0.359402f,-0.545287f,0.757293f}, +{0.518952f,-0.151605f,0.841252f},{0.491923f,0.130876f,0.860746f},{0.414154f,0.50786f,0.755351f}, +{0.678972f,-0.1642f,0.715566f},{0.711066f,0.00492155f,0.703108f},{0.503994f,0.317354f,0.803291f}, +{0.588691f,0.195381f,0.784391f},{0.526698f,0.331299f,0.782834f},{0.716063f,0.386632f,0.58118f}, +{0.483935f,0.46453f,0.741632f},{0.495087f,0.569512f,0.656159f},{0.380152f,0.463059f,0.800663f}, +{0.30855f,0.620777f,0.720717f},{0.5734f,-0.641764f,-0.509266f},{0.380204f,0.394298f,0.836644f}, +{0.809792f,0.158265f,0.564969f},{0.555507f,0.484609f,0.675697f},{-0.740533f,-0.671531f,0.025636f}, +{-0.723145f,-0.674855f,0.147076f},{0.615847f,0.522535f,0.589652f},{0.512179f,-0.340231f,0.788616f}, +{0.536573f,-0.109383f,0.836735f},{0.529299f,0.12282f,0.839499f},{0.540318f,0.326725f,0.77544f}, +{0.538417f,0.44289f,0.716907f},{0.687982f,0.0622659f,0.723052f},{0.732887f,-0.246965f,0.633944f}, +{-0.477133f,0.364336f,-0.799752f},{-0.458535f,0.357291f,-0.813689f},{-0.45039f,0.349904f,-0.821411f}, +{0.2928f,0.188137f,0.937482f},{0.18232f,-0.391871f,0.901774f},{0.00676346f,-0.547607f,0.836708f}, +{0.916547f,-0.0457656f,-0.397299f},{0.762617f,-0.476852f,0.437066f},{0.815111f,-0.371701f,0.444335f}, +{0.786397f,-0.282666f,0.549254f},{-0.575086f,-0.0839676f,0.813773f},{0.712856f,0.388338f,0.583978f}, +{0.899224f,-0.35067f,-0.261584f},{0.774318f,-0.202262f,0.599601f},{0.702776f,-0.0325274f,0.710668f}, +{0.733857f,-0.17232f,0.657084f},{0.659872f,-0.0417907f,0.750215f},{0.307708f,-0.261296f,0.914899f}, +{0.208132f,-0.0752668f,0.975201f},{0.35429f,-0.556443f,0.751565f},{0.428666f,0.437099f,0.79069f}, +{0.509459f,0.418039f,0.752127f},{0.455826f,0.346165f,0.819996f},{0.673234f,-0.0798483f,0.735105f}, +{0.836109f,0.296137f,0.461763f},{0.781771f,0.338466f,0.523713f},{-0.206937f,-0.731467f,0.649718f}, +{0.182463f,0.804027f,0.565904f},{0.495997f,0.491771f,0.715645f},{0.697516f,-0.155143f,0.699573f}, +{0.640765f,-0.107144f,0.760224f},{0.667024f,-0.208429f,0.715288f},{0.69754f,-0.266859f,0.665f}, +{0.549992f,-0.105262f,0.82851f},{0.525844f,0.0216014f,0.850307f},{0.627803f,-0.145946f,0.764567f}, +{0.575798f,-0.110136f,0.81014f},{-0.97179f,0.206791f,0.113409f},{0.189983f,0.501541f,0.844016f}, +{0.5093f,0.462767f,0.725576f},{0.818183f,-0.189226f,0.542928f},{0.837951f,-0.0837523f,0.539281f}, +{0.494371f,-0.37279f,-0.785254f},{-0.46232f,-0.0898225f,0.882152f},{0.371185f,0.394377f,0.840648f}, +{0.473626f,-0.168205f,0.864514f},{-0.461698f,0.245268f,0.852454f},{0.529431f,-0.618975f,0.580148f}, +{0.431288f,-0.596394f,0.676983f},{0.518712f,-0.358702f,0.776061f},{0.359538f,0.526882f,0.770148f}, +{0.620336f,0.0945776f,0.778613f},{0.558303f,-0.222333f,0.799291f},{0.462001f,-0.280565f,0.841331f}, +{0.228678f,0.745663f,0.625854f},{0.581988f,-0.357027f,0.730631f},{0.671159f,-0.124645f,0.73076f}, +{0.657414f,0.107867f,0.745769f},{0.635504f,0.331112f,0.697495f},{0.527313f,0.468268f,0.708989f}, +{0.786941f,-0.0742031f,0.61255f},{0.78316f,0.0971358f,0.614187f},{0.679903f,-0.0312478f,-0.732636f}, +{0.722731f,-0.119831f,-0.680662f},{-0.304819f,0.292514f,-0.906378f},{-0.701588f,-0.688721f,0.182861f}, +{0.292159f,-0.372893f,0.880678f},{0.35086f,0.338823f,0.872981f},{0.49325f,-0.543568f,0.679145f}, +{0.482678f,-0.713783f,0.50748f},{-0.0412281f,-0.625784f,-0.778906f},{0.347424f,0.53187f,0.772276f}, +{0.363542f,0.452761f,0.814153f},{0.683475f,0.00488942f,0.729957f},{0.622766f,0.0726325f,0.779029f}, +{0.501262f,-0.479754f,0.72012f},{0.25502f,0.730842f,0.633115f},{0.337312f,0.626247f,0.702876f}, +{0.893756f,-0.323824f,-0.310383f},{0.661542f,-0.288546f,0.692173f},{0.615062f,-0.137833f,0.776338f}, +{0.679829f,-0.16072f,0.715543f},{0.715517f,0.135139f,0.6854f},{0.673723f,0.259116f,0.692067f}, +{0.607929f,0.357916f,0.708744f},{0.564176f,0.484896f,0.668268f},{0.605081f,0.132927f,0.784989f}, +{0.702442f,-0.0913318f,0.705856f},{0.524211f,-0.518737f,-0.675363f},{0.215163f,-0.85976f,0.46316f}, +{0.0578387f,0.325075f,-0.943918f},{-0.0515991f,0.412961f,-0.909286f},{-0.108005f,0.514926f,-0.850403f}, +{0.54637f,-0.00381208f,0.837535f},{0.614893f,-0.293542f,0.731942f},{0.541219f,-0.245194f,0.80434f}, +{0.398648f,0.234369f,0.886652f},{0.358081f,-0.189674f,0.914222f},{0.335383f,0.0542744f,0.940517f}, +{0.460648f,-0.309278f,0.831956f},{0.423308f,0.144847f,0.894332f},{0.435355f,0.202096f,0.877282f}, +{0.603528f,0.44959f,0.6585f},{0.679775f,-0.0385942f,0.732405f},{0.653824f,0.107886f,0.748916f}, +{0.759406f,0.0271836f,0.650049f},{0.574702f,0.413968f,0.705938f},{0.360767f,0.550165f,0.753104f}, +{-0.887326f,0.448617f,-0.106749f},{-0.933431f,0.327008f,-0.147555f},{0.5428f,-0.207117f,0.813923f}, +{0.494759f,-0.106389f,0.862493f},{-0.395446f,-0.207517f,-0.89474f},{-0.355182f,-0.249591f,-0.900861f}, +{0.495157f,-0.00965679f,0.86875f},{0.643235f,0.350247f,0.680864f},{0.570022f,0.501044f,0.651176f}, +{0.617122f,0.271777f,0.738443f},{0.862234f,0.144092f,0.485582f},{0.977463f,0.0865015f,0.192573f}, +{0.967945f,0.0949319f,0.232529f},{-0.304308f,0.397193f,-0.865814f},{0.270602f,-0.246793f,0.93052f}, +{0.577148f,0.34371f,0.740786f},{0.400594f,0.586566f,0.703893f},{0.302236f,0.559956f,0.771429f}, +{0.521137f,0.302706f,0.797988f},{0.502864f,0.414301f,0.758605f},{0.594349f,-0.0767115f,0.80054f}, +{0.573793f,-0.176939f,0.799659f},{0.62133f,0.0448709f,0.782264f},{0.581222f,0.0850307f,0.80929f}, +{0.682527f,-0.0396461f,0.729784f},{0.636348f,0.03267f,0.77071f},{0.388821f,-0.0387993f,0.920496f}, +{0.542248f,-0.00517707f,0.840202f},{0.581726f,-0.0418682f,0.812306f},{0.655859f,-0.313951f,0.686501f}, +{0.727557f,-0.0868233f,0.680531f},{0.744094f,0.140968f,0.653033f},{0.662563f,0.288153f,0.69136f}, +{0.497747f,-0.511148f,0.700696f},{-0.687492f,0.198204f,-0.69862f},{-0.565646f,0.0336114f,-0.823963f}, +{-0.63122f,0.176192f,-0.755326f},{0.715262f,0.396856f,0.575244f},{0.713432f,0.39338f,0.579885f}, +{-0.923237f,0.361203f,-0.131016f},{-0.998356f,0.0419347f,0.0390748f},{-0.996558f,0.0211225f,-0.0801658f}, +{0.440074f,0.261607f,0.859009f},{0.507926f,0.00577762f,0.861381f},{0.598678f,-0.238457f,0.764672f}, +{0.595584f,0.0324451f,0.802637f},{0.681167f,-0.213054f,0.700442f},{0.760255f,-0.0271448f,0.649057f}, +{0.769925f,0.103971f,0.629607f},{0.716696f,0.189305f,0.6712f},{0.664742f,0.357915f,0.655755f}, +{0.796516f,0.168019f,0.580802f},{0.807462f,0.0110927f,0.589815f},{0.846207f,0.128317f,0.517174f}, +{0.632386f,0.0778414f,-0.770732f},{0.698958f,0.0421958f,-0.713917f},{0.745128f,-0.0767328f,-0.662492f}, +{0.3335f,-0.145014f,0.93153f},{0.865745f,0.491554f,0.0941269f},{0.707032f,0.416355f,0.571625f}, +{0.80999f,0.565921f,0.153785f},{0.877285f,0.466554f,0.112687f},{0.908767f,0.406955f,0.0923594f}, +{0.668257f,-0.432233f,0.605481f},{0.672297f,-0.440256f,0.595141f},{0.590598f,0.120871f,0.797862f}, +{0.454966f,0.559227f,0.693016f},{0.549444f,0.578269f,0.603089f},{0.535451f,-0.533401f,0.654809f}, +{0.778287f,-0.614793f,-0.127668f},{0.770378f,-0.637588f,7.24516e-05f},{0.518565f,-0.0232879f,0.854721f}, +{0.49947f,0.00200443f,0.866329f},{0.475824f,0.117396f,0.871671f},{0.478578f,0.335545f,0.811401f}, +{0.552119f,0.216155f,0.805259f},{0.746915f,-0.129145f,0.652258f},{0.72802f,0.199855f,0.655778f}, +{0.753832f,-0.195787f,0.62722f},{0.656384f,0.422092f,0.625299f},{0.636566f,0.465569f,0.614841f}, +{0.914171f,0.107873f,0.390712f},{0.829913f,0.220967f,0.512268f},{0.932403f,0.11802f,0.341608f}, +{-0.25645f,-0.502436f,-0.825706f},{-0.941174f,0.222725f,-0.254137f},{0.623564f,-0.563809f,0.54156f}, +{0.439151f,0.502801f,0.744539f},{0.784586f,-0.297617f,0.54392f},{0.114849f,-0.45399f,-0.883574f}, +{0.694739f,-0.719229f,0.00687289f},{0.901992f,0.424732f,0.0775473f},{0.0386906f,-0.76215f,-0.646243f}, +{0.534589f,0.215435f,0.817192f},{0.418646f,0.233704f,0.877564f},{0.491832f,0.162436f,0.855404f}, +{-0.578384f,0.497412f,-0.646571f},{-0.83752f,0.114356f,-0.534306f},{-0.751325f,-0.0297917f,-0.659259f}, +{0.729163f,0.361824f,0.580865f},{0.759f,0.303823f,0.575857f},{0.22265f,-0.950099f,-0.218494f}, +{0.749635f,-0.441831f,0.49278f},{0.62442f,0.407023f,0.666657f},{0.480825f,0.343945f,0.806542f}, +{0.484674f,0.377617f,0.788984f},{-0.359597f,-0.178119f,-0.915949f},{-0.132927f,-0.480289f,-0.866979f}, +{0.618301f,0.114174f,0.777605f},{0.650738f,-0.0654459f,0.756476f},{0.818155f,-0.134811f,0.558971f}, +{0.751298f,0.243416f,0.613433f},{0.760882f,0.478202f,0.438613f},{0.766475f,0.346567f,0.540747f}, +{0.548821f,-0.33437f,0.766154f},{0.914253f,0.110328f,0.389832f},{-0.908437f,0.392483f,0.143871f}, +{-0.916873f,0.398846f,-0.0162913f},{0.577586f,0.157636f,0.800965f},{0.956451f,-9.39214e-05f,0.291893f}, +{0.513721f,0.405479f,0.756093f},{0.459552f,0.406286f,0.789775f},{0.63472f,0.110533f,0.764796f}, +{0.638935f,0.168088f,0.750672f},{0.851403f,-0.491367f,-0.183499f},{0.743817f,0.0571238f,0.665938f}, +{0.624312f,0.0274681f,0.780692f},{0.56851f,-0.179512f,0.802852f},{0.899508f,0.0827162f,0.429003f}, +{0.841008f,0.0246865f,0.540459f},{0.817259f,0.0591081f,0.573232f},{0.827772f,0.18779f,0.528704f}, +{0.498712f,-0.488039f,0.716313f},{0.697539f,-0.186868f,0.691751f},{0.583311f,-0.151649f,0.797967f}, +{0.773136f,0.0139612f,0.634087f},{0.794927f,-0.0103512f,0.606617f},{0.565734f,0.479916f,0.670542f}, +{-0.864742f,0.103488f,0.491439f},{0.828208f,-0.00768752f,0.560368f},{0.561537f,-0.286341f,0.776328f}, +{0.508557f,0.150787f,0.847722f},{0.545509f,0.274793f,0.791776f},{0.594812f,-0.0292075f,0.803334f}, +{0.603583f,0.135798f,0.78565f},{0.442723f,0.0788834f,0.893182f},{0.554001f,0.29244f,0.779463f}, +{0.600309f,-0.0313817f,0.799152f},{0.399517f,-0.0519389f,0.915253f},{0.452671f,0.0560515f,0.889914f}, +{0.591323f,0.26021f,0.7633f},{0.608531f,0.316253f,0.727787f},{0.652664f,0.0892208f,0.752376f}, +{0.608645f,0.00582163f,0.793422f},{0.534761f,0.021969f,0.844718f},{0.552932f,0.0923861f,0.828089f}, +{0.615998f,0.155852f,0.772177f},{0.708251f,0.0938871f,0.69969f},{0.448258f,-0.39374f,0.802517f}, +{0.955745f,0.0600352f,0.288004f},{0.48462f,-0.528963f,0.696665f},{0.786507f,0.367476f,0.496355f}, +{0.946708f,0.312889f,0.0764525f},{0.970324f,0.231022f,0.0714221f},{0.622192f,-0.479217f,0.619054f}, +{0.528436f,0.369101f,0.764539f},{0.780853f,0.00919292f,0.624647f},{0.387582f,-0.131106f,0.912465f}, +{0.50548f,-0.154984f,0.848805f},{0.877049f,0.473586f,0.0806271f},{0.829991f,0.555528f,0.0500242f}, +{0.0380677f,-0.422558f,-0.905536f},{-0.0386355f,-0.416857f,-0.908151f},{0.101346f,-0.34475f,-0.933208f}, +{-0.65478f,0.227072f,-0.720903f},{0.554038f,0.392875f,0.733956f},{0.234576f,0.762263f,0.603265f}, +{0.25995f,0.711212f,0.653149f},{0.498703f,-0.320812f,0.805217f},{0.524817f,-0.317889f,0.789629f}, +{0.775137f,-0.392992f,0.494691f},{0.58636f,-0.0781912f,0.806268f},{0.840356f,0.34648f,0.416837f}, +{0.876942f,0.455574f,0.153053f},{0.908257f,0.364583f,0.2053f},{0.875224f,-0.483566f,-0.0121026f}, +{0.917423f,-0.388275f,0.0870448f},{0.562132f,0.160272f,0.811369f},{0.170127f,-0.327533f,0.929397f}, +{0.768738f,-0.0521177f,0.637436f},{0.761336f,-0.026129f,0.647831f},{0.886198f,-0.0253639f,0.462612f}, +{0.884802f,-0.0240317f,0.465348f},{0.875003f,-0.0269654f,0.483366f},{0.888156f,-0.303303f,-0.345232f}, +{0.720994f,0.41486f,0.55503f},{0.73089f,0.493643f,0.471292f},{0.799211f,0.510579f,0.317129f}, +{0.766819f,0.495596f,0.407889f},{0.716985f,0.389142f,0.578361f},{0.66729f,-0.114149f,0.735998f}, +{0.654942f,-0.233812f,0.718598f},{0.821195f,-0.308784f,0.479887f},{0.775726f,-0.3568f,0.520521f}, +{0.717386f,-0.30496f,0.626383f},{0.208891f,-0.812635f,-0.544049f},{0.859058f,0.105923f,0.500799f}, +{0.881403f,0.14631f,0.449135f},{0.841431f,0.0925177f,0.532386f},{0.565092f,0.0003129f,0.825028f}, +{0.54228f,-0.0389849f,0.839293f},{-0.372746f,0.134499f,-0.918134f},{0.72628f,0.0503029f,-0.685556f}, +{0.787154f,-0.0814957f,-0.611349f},{0.769883f,-0.297935f,-0.564371f},{0.744067f,0.141766f,0.652891f}, +{0.574057f,0.0905282f,0.813795f},{0.761955f,0.157928f,0.62808f},{0.887165f,0.0459843f,0.459156f}, +{0.885574f,0.209166f,0.414738f},{0.72059f,0.381289f,0.579111f},{0.90903f,0.274057f,0.313939f}, +{0.450923f,-0.195169f,0.870964f},{0.739011f,0.117632f,0.663344f},{0.571164f,-0.541364f,0.617007f}, +{0.889148f,0.0404144f,0.455832f},{-0.407865f,0.585904f,-0.700259f},{0.680773f,-0.427493f,0.594809f}, +{0.715782f,-0.442783f,0.54f},{0.568523f,-0.363063f,0.738219f},{0.309482f,-0.182632f,0.933202f}, +{-0.919947f,0.0886574f,-0.381888f},{0.861114f,-0.0447529f,0.506438f},{0.821222f,-0.0124579f,0.570473f}, +{0.81439f,-0.0385379f,0.579037f},{0.192437f,0.0496368f,0.980053f},{0.478856f,0.612574f,0.628848f}, +{-0.511654f,0.318298f,-0.798058f},{-0.601647f,0.281855f,-0.747381f},{0.251524f,0.118615f,-0.960555f}, +{0.225671f,0.123337f,-0.966365f},{0.912175f,-0.0206556f,0.409281f},{0.902009f,-0.018796f,0.431308f}, +{0.93251f,3.12715e-05f,0.361143f},{0.660423f,0.0123608f,0.750792f},{0.841922f,0.338374f,0.420321f}, +{0.48217f,-0.30124f,0.822658f},{0.213331f,-0.966013f,-0.145976f},{0.982792f,0.184059f,-0.0155821f}, +{0.882267f,0.470176f,-0.0232345f},{0.897305f,0.413593f,0.154221f},{-0.452893f,0.251605f,-0.855326f}, +{-0.42283f,0.351254f,-0.835366f},{0.831731f,0.186743f,0.522829f},{0.885981f,0.159897f,0.435283f}, +{0.528091f,-0.0916303f,0.84423f},{-0.5275f,0.392926f,-0.753229f},{-0.414898f,0.226583f,-0.881203f}, +{-0.449899f,0.203977f,-0.869473f},{0.792407f,0.309002f,0.525936f},{0.702105f,-0.174158f,0.690447f}, +{0.601373f,-0.0740094f,0.795533f},{0.550285f,0.795586f,0.253436f},{0.234523f,0.778306f,0.582442f}, +{0.324053f,0.512421f,0.795245f},{0.574058f,-0.409459f,0.709084f},{0.248926f,-0.872222f,-0.421028f}, +{0.291238f,0.681894f,0.67097f},{-0.206327f,0.155371f,-0.966069f},{-0.179661f,0.246138f,-0.952438f}, +{-0.129275f,0.294127f,-0.946983f},{-0.236405f,0.381324f,-0.893703f},{0.686435f,0.401971f,0.605992f}, +{-0.408368f,0.355119f,-0.840908f},{0.820717f,-0.249024f,0.514208f},{0.729541f,-0.276127f,0.625719f}, +{0.981621f,0.137155f,0.132695f},{0.883296f,0.312291f,-0.349661f},{0.873408f,0.450495f,-0.184969f}, +{0.498945f,0.610413f,0.615183f},{0.778182f,0.38333f,0.497485f},{0.283819f,0.610949f,0.739046f}, +{0.883323f,0.255649f,0.392918f},{0.799895f,0.0390677f,0.598868f},{0.810366f,0.0758698f,0.580991f}, +{0.852675f,-0.235047f,0.466582f},{0.888261f,-0.0854651f,0.451318f},{0.87399f,-0.0845339f,0.478535f}, +{0.0351843f,-0.998869f,-0.0319747f},{0.0305101f,-0.99916f,-0.0273486f},{0.537026f,0.273246f,0.798085f}, +{0.0538744f,-0.983474f,-0.172848f},{-0.316547f,-0.843145f,-0.434632f},{0.468404f,0.603343f,0.645426f}, +{-0.178733f,-0.51791f,-0.836555f},{0.591741f,-0.474756f,-0.651498f},{0.12968f,-0.977424f,-0.166811f}, +{0.421402f,-0.892298f,-0.161939f},{-0.737569f,0.5865f,0.33468f},{-0.849297f,0.462132f,0.255202f}, +{-0.394842f,-0.918423f,0.0244794f},{-0.463997f,0.132699f,-0.875841f},{-0.322235f,0.0572273f,-0.944928f}, +{-0.373209f,0.0745975f,-0.924743f},{0.486459f,-0.162385f,0.85848f},{-0.482457f,-0.842529f,-0.239541f}, +{0.499279f,-0.259573f,0.826645f},{0.411235f,0.323673f,0.852128f},{0.554669f,-0.34206f,0.75851f}, +{0.473195f,-0.178033f,0.862781f},{0.849404f,0.518863f,0.096404f},{0.876804f,0.461771f,0.134098f}, +{0.758995f,-0.279535f,0.588036f},{0.425511f,-0.715773f,-0.553723f},{0.340117f,-0.913034f,-0.225142f}, +{0.720202f,0.392223f,0.57225f},{-0.887605f,0.375631f,-0.266567f},{-0.870968f,-0.10751f,-0.479434f}, +{-0.880827f,0.148713f,-0.449475f},{0.625715f,-0.186516f,0.757425f},{0.49642f,-0.788635f,0.362796f}, +{0.450044f,-0.869811f,0.202213f},{0.769115f,-0.312414f,0.557547f},{0.727369f,-0.115659f,0.676429f}, +{0.0593964f,-0.478543f,0.876053f},{0.164209f,0.0300051f,0.985969f},{-0.0644165f,0.0478143f,0.996777f}, +{0.152902f,0.149191f,0.976915f},{0.483827f,-0.665811f,0.567986f},{0.896442f,-0.0319358f,0.442009f}, +{0.914686f,-0.0438937f,0.401775f},{-0.451712f,-0.831749f,-0.322723f},{-0.297702f,-0.794045f,-0.529968f}, +{0.888428f,0.139476f,0.437312f},{0.175709f,-0.322229f,0.930212f},{0.514633f,-0.835931f,0.190714f}, +{0.92474f,0.379415f,0.0300102f},{0.918099f,0.386049f,0.0897775f},{0.814101f,-0.545264f,-0.199817f}, +{0.912396f,0.0460901f,0.406706f},{-0.777079f,-0.577772f,-0.249656f},{-0.669531f,-0.626988f,-0.398264f}, +{0.0198351f,-0.999244f,0.0334435f},{0.0138369f,-0.999067f,0.0408985f},{0.0159619f,-0.999343f,0.0325355f}, +{0.886832f,-0.0337723f,0.460856f},{-0.90871f,0.244633f,-0.338232f},{-0.0909099f,0.558564f,-0.824464f}, +{0.60105f,0.682827f,-0.415314f},{-0.790674f,-0.288535f,-0.539984f},{0.0880274f,-0.995293f,0.0405437f}, +{0.00338008f,-0.520431f,0.853897f},{0.712797f,0.446657f,0.540757f},{0.813274f,-0.529637f,0.240976f}, +{0.908879f,-0.35302f,0.222074f},{0.718433f,-0.162821f,0.676272f},{0.181672f,0.904069f,-0.386852f}, +{-0.417861f,0.905006f,0.0797298f},{-0.713732f,0.577259f,0.396685f},{-0.076924f,-0.696852f,-0.713078f}, +{-0.181321f,-0.570804f,-0.800816f},{-0.191037f,-0.442903f,-0.875981f},{-0.0504743f,-0.580727f,-0.812532f}, +{-0.926669f,-0.356093f,-0.120341f},{-0.892653f,-0.449437f,-0.0343083f},{-0.986856f,-0.133881f,-0.0905015f}, +{0.587545f,-0.717401f,0.374334f},{0.998502f,0.0530506f,-0.0133723f},{0.949732f,0.30454f,0.0725536f}, +{0.998781f,0.0489191f,-0.00662695f},{0.996435f,0.0816746f,0.0211487f},{0.68741f,0.0823538f,0.721585f}, +{0.584366f,0.244026f,0.77393f},{0.269489f,0.147477f,0.951644f},{0.519998f,-0.60239f,0.605581f}, +{0.567408f,-0.484303f,0.665957f},{-0.676466f,-0.181577f,-0.713739f},{-0.802923f,-0.132063f,-0.58127f}, +{-0.582632f,-0.207995f,-0.78567f},{0.746876f,-0.118077f,0.654396f},{0.975213f,-0.209994f,-0.0697239f}, +{0.60946f,0.229956f,0.758735f},{0.764709f,-0.00540347f,0.644353f},{0.680325f,-0.379919f,0.626754f}, +{0.736298f,-0.112824f,0.667185f},{0.616818f,-0.00552029f,0.787086f},{0.895137f,-0.311959f,-0.318451f}, +{-0.709982f,-0.133843f,-0.691384f},{-0.577864f,-0.187655f,-0.794266f},{0.83979f,-0.15125f,0.521417f}, +{0.596297f,0.0624722f,0.800329f},{0.0105197f,-0.998823f,0.0473539f},{0.0265853f,-0.99901f,0.0356652f}, +{0.0124122f,-0.998977f,0.0434877f},{-0.678991f,0.664674f,-0.311736f},{0.33402f,0.150594f,0.930458f}, +{-0.432098f,0.335744f,0.836999f},{0.692931f,-0.0653427f,0.718037f},{0.266962f,0.858519f,0.437808f}, +{0.875412f,-0.110105f,0.47067f},{0.931732f,-0.0463402f,0.360177f},{0.917928f,0.390128f,0.0721717f}, +{0.926793f,-0.0150992f,0.375268f},{0.742293f,0.37572f,0.554829f},{0.722769f,-0.643509f,0.251992f}, +{0.921411f,-0.112764f,0.371869f},{-0.552981f,0.11925f,-0.824616f},{0.0969255f,0.700357f,0.707181f}, +{0.815723f,-0.0576197f,0.575566f},{-0.959286f,0.0233389f,-0.281471f},{0.938652f,0.278602f,0.203255f}, +{0.291122f,-0.576791f,0.763256f},{0.224105f,-0.751766f,0.620181f},{0.262298f,-0.881578f,0.392453f}, +{-0.558518f,-0.125543f,-0.819937f},{-0.419377f,-0.0666438f,-0.905363f},{-0.510491f,-0.319457f,-0.798339f}, +{0.341107f,-0.0284138f,0.939595f},{0.858203f,-0.0563061f,0.510212f},{0.880166f,0.164233f,0.44535f}, +{0.689148f,0.496612f,0.527686f},{0.771216f,-0.14831f,0.619056f},{0.771271f,0.507287f,-0.384448f}, +{0.834734f,0.227488f,-0.501467f},{0.85188f,-0.0369246f,0.522434f},{0.386071f,0.227234f,-0.894044f}, +{0.325297f,0.269005f,-0.906542f},{0.378206f,0.379987f,-0.844139f},{0.662992f,-0.227769f,0.713136f}, +{0.594588f,-0.0322742f,0.803382f},{0.804086f,-0.0198517f,0.594182f},{0.736753f,0.0181734f,0.675918f}, +{0.326986f,0.196476f,0.924379f},{0.299563f,0.339299f,0.891705f},{0.884397f,0.126928f,0.449145f}, +{0.415895f,0.703623f,0.576147f},{0.270987f,0.684654f,0.676621f},{0.886993f,0.123335f,0.445007f}, +{0.618728f,-0.106254f,0.778386f},{0.00880166f,-0.999707f,-0.0225414f},{0.0201949f,-0.999687f,-0.0147667f}, +{0.0280835f,-0.999382f,-0.0211496f},{0.310203f,-0.397361f,0.863642f},{-0.0407621f,0.187927f,-0.981337f}, +{0.494633f,-0.865635f,-0.0775528f},{0.590065f,0.0157559f,0.807202f},{0.756401f,-0.214401f,0.617972f}, +{0.70386f,-0.501947f,0.502623f},{0.513046f,0.0939776f,0.853201f},{0.561868f,-0.598621f,0.570926f}, +{-0.882151f,-0.304941f,0.358916f},{-0.922118f,-0.257219f,0.289027f},{-0.869252f,-0.420449f,0.260043f}, +{-0.0299809f,-0.993548f,0.109381f},{-0.00991282f,-0.999769f,0.0190808f},{0.227082f,-0.483709f,0.845257f}, +{0.318876f,-0.00218194f,0.947794f},{-0.721335f,-0.58379f,0.372645f},{-0.844342f,-0.429109f,0.320862f}, +{-0.793675f,-0.561927f,0.233064f},{-0.446212f,0.0339292f,-0.894284f},{-0.542424f,0.173159f,-0.822066f}, +{-0.523215f,0.18899f,-0.83098f},{0.852689f,0.312839f,0.418393f},{0.326618f,0.746278f,-0.579991f}, +{0.43824f,0.765982f,-0.470337f},{0.394471f,0.836057f,-0.381316f},{0.947904f,0.0496112f,0.31467f}, +{-0.69292f,-0.718449f,0.0607708f},{-0.83053f,-0.546689f,-0.106539f},{-0.968231f,0.0429053f,0.246349f}, +{-0.823249f,0.447806f,0.3489f},{0.0290057f,-0.99844f,0.0477127f},{0.135844f,0.356041f,-0.924544f}, +{0.157125f,0.356013f,-0.921177f},{0.702615f,-0.705316f,0.094134f},{-0.207965f,0.00367548f,-0.978129f}, +{-0.553098f,0.275765f,-0.786152f},{-0.646691f,0.197265f,-0.736802f},{-0.67218f,0.236112f,-0.70173f}, +{0.730094f,-0.00643599f,0.683316f},{0.942911f,-0.321198f,0.0880354f},{0.837478f,0.339639f,0.428107f}, +{0.522261f,0.307107f,0.795569f},{0.57665f,-0.609907f,0.543588f},{0.59619f,-0.248945f,0.763272f}, +{0.663708f,-0.395918f,0.634619f},{0.363764f,0.0543848f,0.929902f},{-0.624526f,-0.732207f,0.271734f}, +{-0.62029f,-0.71074f,0.331798f},{0.911724f,0.135763f,0.387721f},{0.181805f,0.489394f,0.852901f}, +{0.336708f,-0.102173f,0.93605f},{0.354085f,0.0237752f,0.934911f},{0.57874f,0.741724f,-0.338977f}, +{0.545642f,0.698697f,-0.462706f},{0.566298f,0.757711f,-0.324314f},{-0.314115f,0.947718f,0.0562388f}, +{0.581017f,-0.458852f,0.672216f},{-0.334189f,0.465636f,-0.819452f},{-0.312672f,0.554287f,-0.771364f}, +{0.581397f,-0.348002f,0.73544f},{-0.0906116f,0.499157f,0.861761f},{0.0294859f,-0.0565748f,-0.997963f}, +{-0.0298575f,0.0125533f,-0.999475f},{-0.0240975f,-0.0732973f,-0.997019f},{-0.367049f,0.148835f,-0.918217f}, +{-0.0153553f,0.739279f,-0.673224f},{0.3737f,0.71988f,-0.584911f},{0.291589f,0.00169327f,0.956542f}, +{-0.405861f,0.384739f,-0.829007f},{-0.444386f,0.455033f,-0.771664f},{-0.701359f,0.404044f,-0.587234f}, +{0.606879f,-0.193662f,0.770839f},{0.561341f,-0.104168f,0.821003f},{0.39326f,0.358897f,0.846486f}, +{0.328906f,-0.335092f,0.882912f},{-0.0572828f,0.247977f,-0.967071f},{0.880482f,-0.0150773f,0.47384f}, +{-0.358699f,0.160404f,-0.919568f},{0.36513f,0.322964f,0.873141f},{0.871962f,0.0410214f,0.487851f}, +{0.288687f,0.0169397f,0.957274f},{0.551894f,-0.577205f,0.601871f},{0.360338f,-0.865636f,-0.347608f}, +{0.293413f,-0.897843f,-0.328309f},{0.399519f,0.467113f,0.78879f},{0.696026f,-0.0722678f,0.714371f}, +{-0.0496104f,-0.0161746f,-0.998638f},{0.017165f,-0.0241149f,-0.999562f},{0.221142f,-0.382684f,0.897023f}, +{-0.175494f,0.594691f,-0.784566f},{0.0255035f,0.0935805f,-0.995285f},{-0.0210553f,0.317065f,-0.94817f}, +{0.509115f,0.633526f,0.58262f},{0.490691f,-0.311449f,0.81377f},{0.33886f,0.225938f,0.913305f}, +{0.465134f,-0.14352f,-0.873529f},{0.365383f,-0.100181f,-0.925451f},{-0.633866f,0.676616f,0.374707f}, +{-0.287038f,0.322961f,-0.901834f},{-0.720707f,-0.115035f,-0.683629f},{-0.499874f,-0.0901865f,-0.86139f}, +{-0.257732f,0.840352f,-0.476846f},{0.509487f,-0.632754f,0.583134f},{0.287168f,-0.31054f,0.906145f}, +{0.299274f,0.0483362f,0.952942f},{-0.100852f,0.454565f,-0.884986f},{0.67887f,0.428883f,-0.595982f}, +{0.497894f,-0.411631f,0.763323f},{0.244059f,0.609261f,0.754478f},{0.782364f,-0.223263f,0.581429f}, +{-0.310859f,0.769689f,0.557625f},{-0.370555f,0.816573f,0.442603f},{0.396677f,0.000531581f,0.917958f}, +{-0.0780473f,0.136668f,-0.987538f},{-0.193533f,0.894149f,0.403785f},{-0.469226f,0.810899f,0.349672f}, +{-0.599307f,0.720881f,0.348083f},{0.597735f,0.798804f,-0.0680163f},{-0.390329f,0.730714f,-0.56009f}, +{0.959444f,0.0389925f,0.27919f},{0.0866216f,-0.0782957f,-0.99316f},{0.0659964f,-0.0736851f,-0.995095f}, +{0.2912f,0.0574939f,0.954933f},{0.88907f,0.321536f,0.325836f},{-0.0155458f,0.906453f,0.42202f}, +{0.666373f,-0.500383f,-0.552778f},{0.8608f,0.346833f,0.372466f},{0.843227f,0.25116f,0.475275f}, +{-0.453813f,-0.0143082f,-0.890982f},{-0.24794f,-0.307459f,-0.918692f},{0.257164f,0.0550692f,0.964797f}, +{-0.366218f,-0.429376f,0.825543f},{-0.4966f,-0.309925f,0.810762f},{0.127606f,-0.14502f,-0.981166f}, +{-0.762129f,-0.60679f,0.225756f},{0.0143175f,-0.999788f,-0.0147732f},{0.0109175f,-0.99987f,-0.0118935f}, +{0.00619323f,-0.999932f,-0.00986292f},{-0.142064f,-0.566734f,-0.811561f},{-0.982566f,-0.0233312f,-0.184445f}, +{-0.982201f,0.185549f,-0.0292062f},{-0.52164f,0.791731f,-0.31789f},{-0.644517f,0.597376f,-0.477221f}, +{-0.696846f,0.483527f,-0.529723f},{-0.883865f,0.259645f,0.389059f},{-0.89337f,0.245748f,0.376163f}, +{0.092153f,-0.238835f,-0.966678f},{-0.898211f,0.397905f,-0.186783f},{0.997552f,-0.0618775f,-0.032576f}, +{0.993174f,-0.107765f,-0.0446236f},{-0.940601f,0.262619f,-0.215175f},{-0.897251f,0.333562f,-0.289268f}, +{0.439447f,-0.896273f,0.0598437f},{0.400452f,-0.910927f,0.0992435f},{0.427674f,-0.902604f,0.0490067f}, +{0.0360786f,-0.997277f,0.0643199f},{0.0645358f,-0.994324f,0.0845833f},{0.224178f,-0.969841f,0.0956641f}, +{-0.980638f,-0.00736646f,-0.195689f},{-0.572463f,-0.725649f,-0.381733f},{-0.618061f,-0.659854f,-0.42731f}, +{-0.362638f,-0.894922f,-0.260017f},{0.00462647f,-0.999587f,-0.0283808f},{-0.00164635f,-0.999876f,-0.015652f}, +{-0.00983998f,-0.999952f,0.000305002f},{0.973157f,-0.183172f,-0.139335f},{0.996745f,-0.0737784f,-0.0324859f}, +{0.986885f,-0.111135f,-0.11708f},{0.997794f,0.0325288f,-0.0578666f},{0.995577f,0.0928073f,0.0145992f}, +{-0.83468f,-0.496603f,0.238109f},{0.96726f,0.253285f,0.0159274f},{0.961391f,0.272285f,0.0398579f}, +{-0.359224f,0.378528f,-0.853039f},{-0.492686f,0.422685f,-0.760656f},{-0.0326768f,0.862869f,-0.504371f}, +{-0.0986429f,0.811704f,-0.575678f},{-0.0482041f,0.821139f,-0.56869f},{0.695949f,-0.682593f,-0.222985f}, +{-0.231695f,0.317407f,-0.919549f},{-0.339532f,0.320155f,-0.884431f},{-0.395214f,0.859704f,0.323596f}, +{0.3429f,0.415894f,-0.84229f},{0.385219f,0.263828f,-0.884308f},{0.378023f,0.302815f,-0.874873f}, +{-0.960881f,0.24907f,0.121132f},{-0.971621f,0.226807f,0.0671622f},{-0.37359f,0.599063f,-0.708204f}, +{-0.377794f,0.819743f,-0.430457f},{-0.609038f,0.677555f,-0.412301f},{0.385864f,-0.911894f,0.139852f}, +{0.0697907f,-0.995996f,0.0558698f},{0.845772f,0.507229f,0.165494f},{0.777954f,0.593335f,0.206739f}, +{0.920531f,0.362211f,0.146376f},{0.912664f,0.389791f,0.122911f},{0.984758f,0.16318f,0.0601944f}, +{0.997034f,0.0679387f,0.0361566f},{0.994588f,-0.101538f,-0.0220202f},{-0.666404f,0.563915f,-0.487755f}, +{-0.714725f,0.362688f,-0.598018f},{0.83658f,-0.340717f,-0.429007f},{0.863925f,-0.450595f,-0.224937f}, +{-0.799294f,0.529921f,0.283395f},{-0.967002f,0.195641f,0.163192f},{-0.339817f,0.914487f,-0.219634f}, +{-0.545492f,0.8063f,-0.228733f},{0.672512f,-0.114677f,-0.731148f},{-0.762563f,-0.532268f,-0.367679f}, +{-0.876042f,-0.304687f,-0.373786f},{-0.844516f,-0.278079f,-0.457673f},{-0.929772f,-0.312106f,-0.195229f}, +{-0.536093f,0.714569f,-0.449439f},{-0.33016f,0.913565f,-0.237471f},{-0.379477f,0.861928f,-0.336271f}, +{0.971925f,-0.229338f,-0.052599f},{-0.861158f,0.00599323f,-0.508301f},{-0.900303f,0.0329582f,-0.434013f}, +{-0.885967f,-0.0618478f,-0.459606f},{-0.536935f,-0.162785f,-0.827769f},{0.100273f,-0.323968f,-0.940739f}, +{-0.118235f,-0.477087f,-0.870867f},{0.74417f,-0.572401f,-0.344338f},{-0.875321f,-0.0583348f,-0.480011f}, +{0.940173f,0.151028f,-0.305393f},{0.962733f,-0.240052f,0.124578f},{0.76268f,-0.354927f,-0.54069f}, +{-0.999025f,0.00702945f,0.043579f},{-0.982233f,-0.171173f,0.0769339f},{-0.942957f,0.159886f,-0.292009f}, +{-0.478959f,0.775714f,-0.410934f},{0.982423f,-0.186548f,-0.00665627f},{0.990741f,-0.132479f,0.0296812f}, +{0.931631f,-0.354107f,-0.0816785f},{0.791666f,-0.0679739f,-0.607161f},{0.754519f,-0.0879699f,-0.650356f}, +{0.793904f,-0.0796276f,-0.602807f},{-0.677332f,0.731321f,-0.0799375f},{-0.656232f,0.708389f,-0.259893f}, +{-0.838214f,0.504165f,-0.207881f},{0.204869f,-0.840839f,0.501018f},{0.153821f,-0.480468f,0.863417f}, +{0.0704106f,-0.942853f,0.325686f},{0.956408f,-0.228265f,-0.18215f},{0.986183f,-0.0936427f,-0.136652f}, +{0.995545f,0.0406436f,-0.085079f},{-0.839642f,0.486502f,-0.24149f},{0.769548f,-0.526779f,-0.36097f}, +{-0.910351f,-0.333461f,-0.245081f},{-0.757463f,-0.364873f,-0.541404f},{-0.744719f,0.390985f,-0.540855f}, +{-0.863937f,-0.467813f,-0.186452f},{-0.743624f,0.127538f,-0.656321f},{-0.138916f,-0.41781f,-0.897851f}, +{0.95161f,-0.305239f,-0.035608f},{-0.0063863f,-0.628727f,-0.7776f},{-0.952006f,0.144361f,-0.269899f}, +{0.651594f,-0.183612f,-0.736011f},{0.674141f,-0.29853f,-0.675584f},{0.741378f,-0.230745f,-0.630171f}, +{-0.15322f,0.899928f,-0.408231f},{-0.207807f,0.846397f,-0.490335f},{-0.0451371f,0.827896f,-0.559063f}, +{-0.899822f,-0.311671f,-0.305256f},{-0.786522f,0.47361f,0.396329f},{-0.850935f,0.406667f,0.332463f}, +{-0.885878f,0.335753f,0.32014f},{0.686391f,-0.683815f,-0.247517f},{-0.67231f,-0.656937f,-0.341221f}, +{0.0f,0.0f,1.0f},{-0.937959f,-0.346653f,-0.00801302f},{-0.98739f,0.133802f,0.0846035f}, +{-0.0273161f,-0.138734f,-0.989953f},{0.0374685f,-0.224627f,-0.973724f},{0.705075f,-0.356919f,-0.612763f}, +{0.73297f,-0.423725f,-0.532176f},{0.77507f,-0.397151f,-0.491464f},{0.835103f,-0.224489f,-0.502203f}, +{0.813602f,-0.520484f,-0.25913f},{0.622879f,-0.584838f,-0.519602f},{0.698437f,-0.544724f,-0.464178f}, +{0.742741f,-0.582086f,-0.330926f},{0.598783f,-0.742132f,-0.301162f},{-0.949238f,0.306132f,0.0723165f}, +{-0.87596f,0.150958f,0.458154f},{0.986936f,0.159859f,0.0200684f},{0.956088f,0.286087f,0.0636382f}, +{0.984588f,0.121724f,0.125577f},{0.728274f,-0.0628471f,-0.682398f},{0.665955f,-0.0317118f,-0.745318f}, +{0.728609f,-0.14214f,-0.670019f},{-0.980512f,-0.0184633f,-0.195589f},{0.653931f,-0.666453f,0.358071f}, +{0.795056f,-0.276985f,-0.539598f},{0.829892f,-0.0953654f,-0.549713f},{0.861028f,-0.224006f,-0.456565f}, +{0.714815f,-0.275027f,-0.642962f},{-0.99155f,0.0806621f,0.101594f},{-0.127067f,0.871035f,-0.474503f}, +{0.593073f,-0.762618f,-0.258221f},{0.741611f,-0.633575f,-0.220445f},{0.879746f,-0.306992f,-0.363048f}, +{0.964765f,-0.0275073f,0.261671f},{-0.493436f,0.858968f,0.136728f},{-0.485737f,0.873949f,-0.0165395f}, +{-0.348249f,0.195388f,-0.916813f},{-0.865865f,0.42357f,0.266207f},{-0.991741f,0.0369164f,0.122831f}, +{-0.95484f,0.230839f,0.187067f},{0.413983f,-0.524267f,-0.744151f},{0.429364f,-0.694634f,-0.577174f}, +{0.190692f,-0.890844f,-0.412351f},{0.930414f,0.288332f,0.226261f},{0.997636f,-0.0670532f,0.0150621f}, +{0.995219f,0.0057273f,0.0975008f},{0.876074f,0.45196f,0.168009f},{0.878814f,-0.41968f,-0.227055f}, +{-0.230254f,-0.803082f,0.549584f},{-0.452718f,-0.646761f,0.613796f},{0.685239f,0.594863f,-0.420221f}, +{0.712106f,0.108611f,-0.69362f},{0.80921f,0.100493f,-0.578862f},{0.69558f,-0.243057f,-0.676086f}, +{0.749341f,-0.229107f,-0.621287f},{-0.46364f,-0.34574f,-0.815783f},{-0.829148f,-0.522963f,0.197543f}, +{-0.780415f,-0.576732f,0.241522f},{-0.919049f,-0.349159f,0.182857f},{-0.918924f,0.166482f,-0.35758f}, +{-0.949718f,0.0941859f,-0.298605f},{-0.970658f,0.0626362f,-0.232164f},{0.826395f,0.189191f,-0.530357f}, +{0.909795f,0.130265f,-0.394086f},{0.909374f,-0.00413196f,-0.41596f},{0.895746f,0.0197669f,-0.444126f}, +{0.690116f,-0.378259f,-0.616977f},{-0.895177f,-0.362898f,0.258771f},{0.446098f,-0.890696f,0.0875017f}, +{0.391035f,-0.914116f,0.107161f},{0.459105f,-0.882593f,0.101258f},{-0.633135f,-0.51279f,0.579816f}, +{-0.649023f,-0.452888f,0.611279f},{-0.59273f,-0.407957f,0.694437f},{0.904743f,-0.242724f,0.350034f}, +{0.828868f,-0.426799f,-0.361691f},{0.792659f,-0.553534f,-0.255525f},{-0.943462f,-0.127474f,-0.30599f}, +{-0.912574f,-0.0911195f,-0.39863f},{0.74525f,0.196846f,-0.637067f},{0.789092f,0.275901f,-0.548828f}, +{0.0114303f,-0.999602f,-0.0257713f},{0.814482f,-0.153989f,-0.55938f},{0.883559f,-0.106128f,-0.456136f}, +{0.0991691f,-0.75282f,-0.650713f},{0.1192f,-0.891997f,-0.436042f},{0.0416995f,-0.990848f,-0.128381f}, +{-0.868636f,0.464477f,0.172431f},{0.648167f,0.0160177f,-0.761329f},{0.685066f,0.331524f,-0.648673f}, +{0.713803f,-0.585761f,-0.383887f},{0.548529f,-0.764757f,-0.338028f},{-0.941654f,-0.0652673f,-0.330193f}, +{-0.947257f,-0.0320283f,-0.318871f},{-0.736538f,-0.154852f,-0.658432f},{0.841077f,-0.0220143f,-0.540467f}, +{0.839122f,0.304265f,-0.450884f},{0.671902f,-0.415255f,-0.613279f},{0.743045f,-0.290646f,-0.602834f}, +{0.791078f,-0.213203f,-0.573359f},{-0.414459f,0.780265f,0.468414f},{-0.333447f,0.81949f,0.466099f}, +{-0.350316f,0.897013f,0.269529f},{-0.445173f,0.203844f,-0.871934f},{-0.554085f,0.282607f,-0.783022f}, +{-0.371315f,0.14061f,-0.917799f},{-0.525501f,-0.42254f,-0.73845f},{-0.193952f,-0.922888f,-0.332656f}, +{-0.644837f,0.638904f,0.419509f},{-0.577435f,0.74013f,0.344638f},{0.642653f,-0.725827f,-0.245299f}, +{-0.534687f,0.832182f,-0.146915f},{-0.451134f,0.798492f,-0.398608f},{0.0169719f,-0.999804f,-0.0101645f}, +{0.036643f,-0.999184f,-0.016996f},{-0.947189f,0.122828f,0.296219f},{-0.986756f,0.0836779f,0.138966f}, +{-0.716169f,0.350797f,-0.60336f},{-0.200614f,-0.294627f,-0.934318f},{-0.321563f,-0.17239f,-0.931063f}, +{-0.956011f,0.291732f,-0.0305733f},{-0.684357f,0.683399f,-0.254208f},{0.60638f,-0.0863361f,-0.790474f}, +{0.625317f,0.0625792f,-0.777858f},{-0.9141f,0.225904f,-0.336733f},{0.74609f,-0.49078f,-0.449982f}, +{0.754326f,-0.426754f,-0.498872f},{0.721635f,-0.498468f,-0.480389f},{-0.696162f,-0.506157f,-0.509081f}, +{-0.677645f,-0.550992f,-0.487038f},{-0.924725f,0.272964f,-0.265283f},{0.747567f,-0.162517f,-0.643997f}, +{0.733726f,-0.26049f,-0.627528f},{-0.841458f,-0.3679f,0.395725f},{-0.780482f,-0.625175f,0.00217309f}, +{-0.467462f,0.83911f,-0.278161f},{-0.768839f,0.627927f,-0.120804f},{0.100807f,-0.994218f,-0.0369808f}, +{0.0348387f,-0.995477f,-0.0883868f},{-0.391848f,0.873182f,-0.28984f},{0.032085f,0.683438f,-0.729303f}, +{0.0152515f,0.645162f,-0.763893f},{-0.0140963f,0.692749f,-0.721041f},{0.798704f,-0.184977f,-0.572586f}, +{-0.523094f,0.852059f,-0.0191815f},{-0.954618f,-0.0164208f,-0.29738f},{-0.975711f,0.0897672f,-0.199826f}, +{-0.965971f,0.18126f,-0.184511f},{-0.950108f,0.0759588f,-0.30253f},{0.992414f,0.121164f,0.0208189f}, +{0.611404f,0.221804f,-0.759598f},{0.990116f,-0.0971424f,-0.101157f},{-0.7541f,-0.0822748f,-0.651585f}, +{-0.894907f,-0.409213f,-0.178004f},{-0.977252f,-0.183154f,-0.106927f},{-0.137259f,0.411548f,-0.900993f}, +{-0.0444384f,0.517873f,-0.854303f},{-0.230663f,0.547608f,-0.804313f},{0.980831f,0.19448f,0.0121421f}, +{-0.147885f,0.685989f,-0.712425f},{-0.107152f,0.598248f,-0.794114f},{-0.638269f,0.716773f,-0.280801f}, +{-0.851021f,0.359455f,0.382826f},{-0.82939f,0.350349f,0.435165f},{-0.194385f,-0.436604f,-0.878403f}, +{-0.704735f,-0.236526f,-0.668882f},{-0.894576f,0.111168f,0.43287f},{0.723662f,-0.0895417f,-0.684321f}, +{-0.26078f,0.930097f,-0.258678f},{-0.666087f,0.17054f,-0.726116f},{-0.589705f,-0.152056f,-0.793175f}, +{0.0588107f,0.66234f,-0.746892f},{0.0464484f,0.641371f,-0.765824f},{0.732878f,0.108509f,-0.671651f}, +{0.769934f,0.058901f,-0.6354f},{0.704258f,0.155158f,-0.692782f},{-0.905845f,0.0736368f,-0.417159f}, +{-0.886491f,-0.0258386f,-0.462024f},{-0.850694f,0.0210112f,-0.525241f},{0.767409f,-0.243228f,-0.593231f}, +{0.827619f,-0.193028f,-0.527055f},{0.779302f,-0.23095f,-0.582538f},{-0.884043f,-0.4635f,-0.0602925f}, +{-0.959193f,-0.26739f,-0.0919339f},{-0.85836f,-0.41451f,0.302322f},{0.765939f,-0.614183f,-0.190045f}, +{0.755147f,-0.00353785f,-0.655546f},{0.768294f,-0.249242f,-0.589579f},{0.76295f,0.0228429f,-0.646054f}, +{0.772987f,-0.200477f,-0.601914f},{0.839712f,0.218607f,-0.497086f},{0.000715683f,-0.980848f,0.194775f}, +{-0.635974f,-0.730907f,0.247612f},{-0.639593f,-0.35236f,-0.683201f},{-0.684919f,-0.396537f,-0.611264f}, +{-0.618439f,-0.339162f,-0.708874f},{0.127168f,-0.681979f,-0.720231f},{0.0782779f,-0.738573f,-0.669614f}, +{0.0113648f,-0.991394f,-0.130418f},{0.289444f,0.140849f,-0.946775f},{0.335955f,0.19986f,-0.92043f}, +{0.193556f,0.347962f,-0.91731f},{-0.974989f,0.117221f,-0.188825f},{0.219525f,0.217545f,-0.951043f}, +{-0.897894f,-0.341032f,0.27836f},{-0.897788f,-0.411493f,0.157002f},{-0.839134f,-0.369163f,0.399466f}, +{-0.895872f,-0.175299f,0.408268f},{0.843528f,-0.381703f,-0.37784f},{-0.832408f,0.491697f,-0.2556f}, +{0.518818f,0.0828733f,-0.850858f},{0.506607f,0.0628409f,-0.859884f},{0.54957f,0.240652f,-0.800037f}, +{-0.638102f,0.465486f,-0.61331f},{-0.513594f,0.704289f,-0.4901f},{-0.472045f,0.46126f,-0.751274f}, +{-0.952576f,-0.11264f,0.282687f},{0.99936f,0.00995633f,0.0343486f},{0.963471f,0.196356f,0.182123f}, +{0.984349f,0.0784931f,0.157783f},{0.279703f,0.283064f,-0.91741f},{0.181413f,0.312065f,-0.93258f}, +{0.236822f,0.427557f,-0.872416f},{-0.105609f,0.685177f,-0.72068f},{-0.976885f,-0.0286416f,0.211839f}, +{0.725157f,-0.00896551f,-0.688525f},{0.724382f,0.0375398f,-0.688376f},{0.766879f,-0.221373f,-0.602404f}, +{0.747531f,-0.347926f,-0.565814f},{0.105336f,-0.993702f,-0.0382186f},{0.399919f,-0.874604f,-0.274102f}, +{-0.642518f,0.276432f,-0.714673f},{-0.671372f,0.263996f,-0.692507f},{0.675291f,-0.602507f,-0.425403f}, +{0.218857f,-0.971126f,0.0949512f},{0.628034f,-0.747951f,0.21481f},{0.46298f,-0.856397f,0.228545f}, +{0.808891f,-0.569684f,-0.14545f},{0.653049f,-0.392291f,-0.647792f},{-0.0164474f,-0.999166f,0.0373811f}, +{-0.00770074f,-0.99916f,0.0402574f},{0.000142267f,-0.9996f,0.0282806f},{-0.141438f,0.915478f,-0.37669f}, +{-0.0262741f,0.8654f,-0.500392f},{-0.108226f,0.86563f,-0.488847f},{-0.189009f,0.743456f,-0.641521f}, +{-0.398889f,0.836374f,-0.375986f},{-0.934459f,0.0509862f,-0.352402f},{0.793651f,-0.04301f,-0.606851f}, +{0.713646f,0.148586f,-0.684567f},{-0.789885f,-0.580768f,0.196952f},{-0.807347f,-0.562697f,0.177657f}, +{-0.802356f,-0.546869f,0.239081f},{-0.118066f,-0.971948f,0.203415f},{-0.427385f,0.756545f,0.494956f}, +{0.73633f,0.308414f,-0.602245f},{0.859609f,0.0481623f,-0.508678f},{0.0357142f,-0.425029f,-0.904475f}, +{-0.216403f,-0.407845f,-0.887036f},{0.591956f,-0.749826f,-0.29555f},{0.579828f,-0.624834f,-0.52286f}, +{0.745975f,-0.250335f,-0.617133f},{0.770701f,-0.326509f,-0.547185f},{-0.889108f,-0.0981269f,-0.447054f}, +{-0.454529f,-0.0502772f,-0.889312f},{-0.637814f,-0.765383f,-0.0859141f},{-0.790282f,-0.609604f,0.0619454f}, +{0.786256f,0.100661f,-0.609646f},{0.970064f,0.194078f,0.145976f},{0.945551f,0.300532f,0.124952f}, +{-0.963703f,0.07142f,0.257248f},{0.0192169f,-0.978854f,-0.203654f},{0.893996f,-0.0181557f,-0.447706f}, +{-0.96542f,0.108981f,0.236827f},{-0.696219f,-0.631337f,-0.341603f},{-0.612492f,-0.74952f,-0.251142f}, +{-0.29042f,-0.876351f,-0.384273f},{0.639278f,-0.729907f,0.241988f},{0.69726f,-0.654375f,0.29261f}, +{-0.978716f,0.13926f,0.150735f},{0.000183522f,-0.998022f,0.0628701f},{-0.921737f,0.0503909f,-0.384528f}, +{-0.800558f,-0.493875f,-0.339402f},{-0.497304f,0.332329f,-0.801403f},{0.615217f,-0.432449f,0.659163f}, +{0.797934f,-0.505531f,-0.328238f},{0.865963f,-0.084911f,-0.492847f},{-0.305809f,-0.410663f,-0.858974f}, +{0.248637f,-0.581692f,-0.774476f},{-0.0103987f,-0.99808f,0.0610528f},{-0.951984f,0.221019f,0.21184f}, +{0.977876f,-0.181766f,-0.103535f},{-0.921649f,0.36585f,0.129297f},{0.699153f,-0.675216f,-0.23509f}, +{0.755519f,-0.604696f,-0.25206f},{0.638633f,-0.518986f,-0.568157f},{0.71167f,-0.542609f,-0.446207f}, +{0.484392f,-0.401776f,-0.777136f},{0.703104f,-0.546784f,-0.454612f},{-0.858558f,0.0931788f,-0.504178f}, +{-0.921669f,0.165028f,-0.351129f},{-0.806021f,-0.148181f,-0.573037f},{-0.710689f,-0.664555f,-0.230841f}, +{-0.840258f,-0.120608f,0.528602f},{0.887732f,-0.18165f,-0.423007f},{0.904292f,-0.119169f,-0.409946f}, +{0.867545f,-0.118966f,-0.482921f},{0.99829f,0.0581998f,-0.00551678f},{-0.914838f,0.361162f,0.180649f}, +{-0.912489f,0.395871f,0.103196f},{0.531903f,0.275915f,-0.800593f},{0.515947f,0.267172f,-0.813891f}, +{0.519825f,0.372712f,-0.768679f},{0.762899f,-0.17899f,-0.621246f},{0.702701f,-0.150915f,-0.695296f}, +{-0.294184f,0.897705f,-0.327998f},{0.147592f,0.958411f,-0.244265f},{0.177148f,0.966717f,-0.1846f}, +{0.176799f,0.971614f,-0.15719f},{-0.465063f,0.0616191f,0.883131f},{-0.418584f,-0.261446f,0.869731f}, +{0.583074f,-0.793946f,0.172264f},{0.731105f,-0.0128284f,-0.682144f},{0.676149f,-0.462984f,-0.573121f}, +{0.723236f,-0.330051f,-0.606626f},{0.704461f,0.394819f,-0.58979f},{0.843936f,-0.107503f,-0.525562f}, +{-0.96141f,-0.00372427f,-0.275096f},{-0.894378f,0.323282f,-0.309154f},{0.956972f,0.136056f,0.256306f}, +{-0.933431f,-0.350667f,-0.0757507f},{-0.130992f,0.607162f,-0.783706f},{-0.154968f,0.416175f,-0.895982f}, +{-0.260322f,0.567362f,-0.781238f},{-0.0362481f,0.607833f,-0.793237f},{-0.141952f,0.705861f,-0.693981f}, +{-0.121685f,0.645941f,-0.753627f},{-0.297167f,0.482335f,-0.824042f},{0.740034f,0.428893f,-0.518074f}, +{0.657119f,0.4288f,-0.619939f},{0.507662f,-0.441258f,-0.73998f},{-0.898184f,-0.155866f,-0.411062f}, +{-0.731846f,-0.184488f,-0.656023f},{-0.585754f,-0.776209f,-0.23322f},{-0.977273f,0.0661205f,-0.201412f}, +{-0.869666f,0.468964f,0.154123f},{-0.874285f,0.47548f,0.0976949f},{-0.765902f,0.148325f,0.625615f}, +{-0.899666f,0.406081f,0.160309f},{-0.935898f,0.330841f,0.120995f},{-0.974013f,0.125771f,0.188361f}, +{-0.976974f,0.0530194f,0.206668f},{0.923756f,0.129146f,0.360549f},{0.0350245f,-0.887122f,0.460203f}, +{0.67463f,-0.30715f,-0.671218f},{-0.504845f,-0.486101f,0.713328f},{-0.516171f,-0.350276f,0.781584f}, +{0.31792f,-0.3715f,-0.872305f},{0.511809f,-0.490299f,-0.705449f},{-0.978509f,0.204314f,-0.0278446f}, +{0.147245f,0.551976f,-0.820756f},{0.130809f,0.437729f,-0.88954f},{0.00867079f,0.703226f,-0.710913f}, +{0.425153f,-0.903936f,0.0463115f},{0.403859f,-0.905871f,0.127655f},{-0.770367f,-0.617828f,-0.157556f}, +{0.714366f,0.00755797f,-0.699732f},{0.887058f,-0.253175f,-0.386045f},{0.904446f,-0.229688f,-0.359473f}, +{0.889749f,-0.237271f,-0.389934f},{-0.0191102f,-0.999817f,-0.00115212f},{0.0066128f,-0.999505f,0.0307467f}, +{0.00427601f,-0.998407f,-0.0562644f},{0.709271f,-0.160033f,-0.686531f},{0.74534f,-0.288142f,-0.601201f}, +{0.608205f,-0.355153f,-0.709896f},{0.125865f,0.782562f,-0.609717f},{-0.0739411f,0.799269f,-0.596407f}, +{0.0508085f,0.72803f,-0.68366f},{-0.814112f,-0.561646f,-0.147568f},{0.972331f,0.159512f,0.170672f}, +{0.861139f,-0.229934f,-0.453399f},{0.894211f,-0.209492f,-0.395601f},{-0.220335f,-0.862894f,0.454827f}, +{-0.215799f,-0.863267f,0.456291f},{0.819689f,0.0614222f,-0.569506f},{0.857209f,0.0364918f,-0.513673f}, +{-0.95851f,-0.25396f,0.129474f},{0.847391f,-0.263974f,-0.460701f},{0.410327f,-0.908244f,0.0820033f}, +{0.250237f,-0.965597f,0.0707417f},{0.529549f,-0.264119f,-0.806113f},{0.541477f,0.764778f,-0.349167f}, +{-0.0183646f,0.673017f,-0.739399f},{-0.00922519f,0.686325f,-0.727236f},{-0.969929f,-0.182964f,0.160506f}, +{-0.0327101f,-0.996404f,-0.0781646f},{0.542598f,0.111129f,-0.832609f},{-0.054401f,0.718347f,-0.693555f}, +{0.960249f,-0.258809f,-0.104594f},{0.868777f,-0.0882508f,-0.487276f},{0.0262056f,-0.999314f,-0.0261689f}, +{-0.742036f,-0.634751f,0.215576f},{-0.790094f,-0.603961f,-0.104797f},{0.684195f,0.0425916f,-0.728054f}, +{0.909851f,-0.135259f,-0.39227f},{0.846745f,0.00228132f,-0.531994f},{-0.982988f,-0.164733f,0.0812222f}, +{0.995198f,-0.0892049f,-0.0402945f},{0.986315f,0.147268f,0.0741227f},{0.566721f,0.31205f,-0.76253f}, +{0.401851f,0.226639f,-0.887215f},{0.0372775f,-0.264135f,-0.963765f},{-0.976716f,0.0122543f,0.214188f}, +{-0.313664f,0.917903f,-0.243042f},{-0.352028f,0.889474f,-0.291398f},{0.108126f,-0.83625f,-0.537583f}, +{0.171862f,-0.95061f,-0.258466f},{0.86963f,-0.270204f,-0.4132f},{-0.903531f,0.323979f,0.28048f}, +{-0.683437f,0.620562f,0.384468f},{-0.691721f,-0.7163f,-0.0918445f},{-0.736096f,0.554191f,0.388633f}, +{-0.645063f,0.68258f,0.34348f},{-0.614513f,-0.77154f,0.164621f},{0.669319f,-0.537608f,-0.512825f}, +{0.327855f,-0.918252f,0.222092f},{0.298594f,-0.934062f,0.19588f},{0.354796f,-0.918097f,0.176686f}, +{-0.506672f,0.834157f,0.217865f},{0.0345878f,-0.999201f,-0.020016f},{0.931099f,0.276016f,-0.238473f}, +{-0.020536f,-0.30336f,-0.952655f},{-0.469175f,-0.414153f,-0.779969f},{0.118301f,-0.895303f,-0.429462f}, +{0.099007f,-0.97542f,-0.196861f},{0.0272237f,-0.999335f,-0.0242418f},{-0.674057f,0.449826f,-0.585921f}, +{0.703665f,0.384147f,-0.597735f},{0.33071f,0.351781f,-0.875718f},{0.0978065f,0.508281f,-0.855619f}, +{0.546689f,-0.0132231f,-0.837231f},{0.896842f,-0.151815f,-0.415484f},{-0.960075f,0.245652f,0.133831f}, +{-0.98563f,0.084896f,0.146033f},{-0.899805f,0.257965f,0.351859f},{0.827633f,-0.396764f,-0.39699f}, +{-0.883847f,0.292303f,0.365202f},{-0.965708f,0.175981f,0.190889f},{-0.272344f,-0.945067f,-0.180768f}, +{0.0209144f,-0.999074f,0.0376027f},{0.032975f,-0.998556f,-0.0424153f},{0.0318291f,-0.998875f,-0.0351631f}, +{0.0352428f,-0.998819f,-0.0334404f},{0.0388307f,-0.998288f,0.0437481f},{-0.647332f,-0.523114f,-0.554359f}, +{-0.355664f,-0.613777f,-0.704826f},{0.959933f,-0.275879f,-0.0491777f},{0.992104f,-0.125231f,0.00679531f}, +{-0.0944883f,0.823707f,0.559088f},{-0.999729f,-0.0207259f,-0.0106073f},{0.563333f,0.0340497f,-0.825528f}, +{0.998869f,0.0105482f,0.0463714f},{-0.470932f,0.711152f,0.522003f},{0.278589f,0.329839f,-0.901994f}, +{-0.886327f,0.432631f,-0.165089f},{-0.926808f,0.375345f,-0.0119485f},{0.204039f,-0.372273f,-0.905418f}, +{-0.992881f,0.0451062f,0.110243f},{-0.59668f,0.742712f,0.303894f},{-0.791167f,0.0261632f,-0.61104f}, +{0.438938f,-0.894537f,-0.0844837f},{-0.00869637f,-0.999954f,0.00417063f},{-0.0213252f,-0.999674f,0.0140172f}, +{-0.153982f,0.624937f,-0.765339f},{0.794822f,-0.532167f,-0.291645f},{0.811671f,-0.521067f,-0.263968f}, +{-0.517259f,-0.574715f,-0.63415f},{-0.93562f,0.340752f,-0.0922097f},{0.236175f,-0.960352f,-0.148136f}, +{0.553704f,-0.827051f,-0.096947f},{0.870867f,0.381557f,0.309847f},{0.853989f,0.341659f,0.392392f}, +{-0.154021f,-0.650535f,-0.743694f},{0.0262376f,-0.999048f,0.0348535f},{0.0225118f,-0.999678f,0.0117443f}, +{-0.124984f,-0.435542f,-0.891449f},{-0.37736f,-0.556838f,-0.739954f},{0.872701f,0.335303f,0.354917f}, +{0.853307f,0.326584f,0.40646f},{0.93813f,0.199247f,0.283218f},{0.0217241f,-0.999386f,-0.0274869f}, +{-0.597863f,-0.38968f,-0.700507f},{0.959073f,0.10872f,0.261455f},{-0.18484f,-0.257829f,-0.948345f}, +{0.154603f,-0.413225f,-0.897409f},{0.717788f,-0.687313f,0.111275f},{0.847382f,-0.520852f,0.103229f}, +{0.924113f,0.287993f,0.251147f},{0.911501f,0.262502f,0.316637f},{0.76148f,-0.482196f,-0.433169f}, +{0.940386f,0.129455f,0.314508f},{0.863147f,0.448876f,0.231275f},{0.761826f,-0.641831f,0.0876031f}, +{0.906001f,0.324256f,0.272067f},{0.0985965f,-0.902022f,-0.420279f},{-0.88257f,0.38391f,0.271447f}, +{-0.968928f,0.130172f,0.21032f},{-0.102488f,-0.994179f,-0.0332447f},{-0.0442014f,-0.992883f,-0.110588f}, +{0.962835f,0.199899f,0.181627f},{0.221024f,0.37711f,-0.899409f},{0.217884f,0.261235f,-0.940363f}, +{0.204653f,0.371797f,-0.905474f},{-0.385283f,0.722821f,0.573662f},{0.800531f,-0.19496f,-0.566693f}, +{0.975873f,0.0781187f,0.203887f},{0.976396f,0.098517f,0.192213f},{0.964255f,0.132192f,0.229645f}, +{0.95341f,0.0958946f,0.286032f},{0.946655f,0.14687f,0.286835f},{0.940817f,0.252628f,0.225928f}, +{0.905792f,0.35846f,0.225938f},{0.90519f,0.368597f,0.211583f},{0.916708f,0.349413f,0.193796f}, +{0.904462f,0.381361f,0.191082f},{0.824432f,0.565116f,0.0309246f},{0.82786f,0.560907f,-0.00562512f}, +{-0.96563f,-0.0745097f,0.249011f},{-0.351993f,-0.922569f,0.15801f},{-0.05336f,-0.995682f,0.075956f}, +{-0.360501f,-0.839956f,0.405602f},{0.864828f,0.398074f,0.305956f},{0.795907f,0.528246f,0.295782f}, +{0.956112f,0.21274f,0.201471f},{0.974732f,0.0578164f,0.215767f},{0.962779f,0.0784324f,0.258661f}, +{0.234707f,-0.73487f,0.636301f},{-0.210907f,-0.231875f,-0.949606f},{0.971296f,0.156885f,0.178808f}, +{0.951489f,0.120919f,0.282925f},{0.0275537f,-0.999051f,-0.0337455f},{-0.318759f,-0.932821f,-0.168041f}, +{0.816302f,0.491849f,0.302878f},{0.778816f,0.567945f,0.266241f},{0.617103f,-0.751326f,0.233866f}, +{0.986863f,-0.0263569f,0.159393f},{0.914441f,0.385846f,0.122154f},{0.939377f,0.3255f,0.107795f}, +{-0.269202f,-0.242719f,-0.931997f},{-0.62682f,0.758358f,0.178856f},{-0.842996f,0.537838f,-0.00937066f}, +{-0.624626f,0.757251f,-0.190825f},{-0.0746514f,-0.561558f,-0.824063f},{0.974361f,0.14028f,0.175902f}, +{0.721718f,0.541276f,-0.431444f},{0.691845f,-0.678714f,0.246368f},{0.940354f,-0.258607f,0.221034f}, +{0.961877f,-0.119882f,0.245806f},{0.63723f,-0.429472f,-0.639915f},{0.989181f,0.0945617f,0.11216f}, +{0.990752f,0.0975521f,0.094307f},{0.985711f,0.123981f,0.114028f},{0.977333f,0.166331f,0.130976f}, +{0.956945f,0.25428f,0.139992f},{0.923567f,0.353306f,0.148994f},{0.91272f,0.377482f,0.156363f}, +{0.916948f,0.362123f,0.167552f},{0.911395f,0.387113f,0.139649f},{0.835581f,0.53732f,0.114417f}, +{0.968796f,0.231741f,0.0879191f},{-0.704802f,0.0615895f,0.706725f},{-0.765553f,-0.532367f,0.361267f}, +{-0.755914f,-0.172051f,0.631659f},{0.863069f,0.405931f,0.300552f},{0.838471f,0.491299f,0.235781f}, +{0.902746f,0.311662f,0.296507f},{0.972589f,0.0739923f,0.220444f},{0.61259f,-0.265774f,-0.744377f}, +{0.968436f,0.216794f,0.123015f},{0.976937f,-0.152909f,0.14904f},{-0.171894f,0.773899f,-0.609535f}, +{-0.279005f,0.84322f,-0.459496f},{0.0318361f,-0.999257f,-0.0217057f},{-0.686432f,-0.418899f,0.59442f}, +{0.940718f,-0.314328f,0.127464f},{0.127985f,-0.970691f,-0.203416f},{-0.140049f,0.795548f,0.589483f}, +{0.890566f,0.394901f,0.225712f},{-0.17875f,0.0676498f,-0.981566f},{-0.130389f,0.136394f,-0.982036f}, +{-0.693075f,-0.719421f,-0.045614f},{-0.854093f,-0.520116f,0.00228063f},{-0.697985f,-0.682744f,0.216051f}, +{0.958009f,0.268102f,0.101683f},{0.910047f,0.341379f,0.235107f},{-0.130869f,0.189174f,-0.973184f}, +{-0.136388f,0.204777f,-0.96926f},{-0.360244f,0.91344f,-0.189348f},{0.00927586f,0.997961f,-0.0631495f}, +{-0.269327f,0.956986f,-0.107895f},{0.0065507f,0.999964f,-0.00535563f},{-0.0516087f,0.998536f,0.0162233f}, +{-0.00549771f,0.998424f,0.0558435f},{-0.966082f,-0.223912f,0.128644f},{-0.959726f,-0.273362f,0.0647994f}, +{0.195186f,0.974535f,0.110378f},{0.102488f,0.979166f,0.175299f},{0.647374f,0.671496f,0.360554f}, +{-0.3164f,0.947491f,0.0463864f},{0.0622768f,0.854897f,0.515046f},{0.0187868f,0.827973f,0.560453f}, +{0.793441f,0.573205f,0.204667f},{0.570489f,0.666316f,0.480172f},{0.0149417f,0.788806f,0.614461f}, +{0.551521f,0.679451f,0.483912f},{-0.33077f,0.874133f,0.355644f},{-0.381363f,0.807787f,0.449491f}, +{-0.0394864f,0.843368f,0.535884f},{-0.875505f,-0.465724f,0.128814f},{-0.186669f,0.821205f,0.539237f}, +{-0.40397f,0.803553f,0.437162f},{0.0275706f,0.940562f,0.3385f},{0.0127497f,0.923202f,0.384103f}, +{0.0978516f,0.88668f,0.451912f},{-0.978017f,0.136329f,0.157789f},{0.63961f,0.752729f,0.155875f}, +{0.574715f,0.728057f,0.373678f},{0.296871f,0.73371f,0.611177f},{0.583868f,0.792562f,-0.175909f}, +{0.613508f,0.789373f,-0.0223071f},{0.30635f,0.812628f,0.495768f},{0.7584f,0.598623f,0.257838f}, +{0.751662f,0.579045f,0.315772f},{0.626681f,0.697524f,0.347462f},{0.327733f,0.893655f,0.306548f}, +{0.0817467f,0.949563f,0.302734f},{0.609838f,0.702151f,0.367535f},{-0.141646f,0.863148f,0.484677f}, +{-0.248361f,0.827147f,0.504127f},{0.251704f,0.958776f,0.131885f},{-0.00860057f,0.843532f,0.53701f}, +{0.610942f,0.685026f,0.39685f},{0.563461f,0.706036f,0.428981f},{0.290591f,0.953423f,-0.0808732f}, +{-0.0416996f,0.942389f,0.331909f},{-0.242277f,-0.954806f,0.172186f},{-0.508478f,-0.833053f,0.217884f}, +{-0.706606f,-0.680659f,-0.193422f},{-0.780268f,-0.571677f,-0.253706f},{0.203519f,0.955669f,-0.212783f}, +{-0.409056f,0.847362f,0.338602f},{-0.10077f,0.935414f,0.338889f},{-0.143977f,0.951576f,0.271611f}, +{-0.167925f,0.867559f,0.468127f},{0.117004f,0.992992f,-0.0166474f},{-0.0198098f,0.997063f,0.0739749f}, +{-0.0233485f,0.980743f,0.193904f},{-0.0633045f,0.881613f,0.467708f},{-0.0576471f,0.742484f,0.667378f}, +{-0.38597f,0.7806f,0.491621f},{-0.424397f,0.710223f,0.561668f},{-0.329934f,0.7797f,0.532176f}, +{-0.66792f,0.709634f,0.224284f},{-0.451594f,0.832424f,0.321144f},{-0.0026761f,0.997846f,-0.0655392f}, +{0.0393049f,0.947095f,0.318538f},{-0.0874497f,0.213337f,-0.973057f},{-0.14254f,0.226346f,-0.963561f}, +{-0.079948f,0.963489f,0.255531f},{0.00520031f,0.979625f,-0.200767f},{0.160151f,0.97548f,0.150967f}, +{-0.30432f,0.934853f,-0.182866f},{-0.382215f,0.88348f,-0.270877f},{-0.0554854f,0.991601f,-0.116832f}, +{0.238752f,0.970688f,0.0276203f},{0.658975f,0.641087f,0.393396f},{-0.166042f,0.978496f,0.122378f}, +{0.0307127f,0.987931f,0.151817f},{-0.343655f,0.938408f,-0.0359483f},{0.334978f,0.937134f,-0.0978196f}, +{0.843998f,0.536343f,-0.00166254f},{0.0366952f,0.998946f,-0.0275677f},{-0.406013f,0.896936f,0.175099f}, +{-0.523428f,0.7564f,-0.392279f},{-0.719595f,0.569109f,-0.397866f},{0.0774109f,0.330299f,-0.940697f}, +{-0.926674f,0.36204f,0.101006f},{0.573185f,0.791566f,0.211852f},{0.759727f,0.648451f,-0.0482235f}, +{0.912176f,0.39349f,0.114457f},{-0.973178f,-0.227499f,0.0341798f},{0.27077f,0.673918f,0.6874f}, +{0.737332f,0.667635f,-0.102979f},{0.79274f,0.608946f,0.0273588f},{0.606984f,0.722816f,0.330314f}, +{0.898969f,0.435381f,0.0479348f},{0.596787f,0.704856f,-0.383437f},{-0.940796f,-0.0582889f,0.333923f}, +{0.00474336f,-0.999893f,-0.0138285f},{-0.00631413f,-0.999977f,0.00228954f},{0.0132945f,-0.999842f,0.0117666f}, +{0.725395f,0.687263f,0.0383686f},{-0.623881f,-0.449673f,-0.639192f},{-0.710202f,-0.337333f,-0.617915f}, +{-0.564536f,-0.513949f,-0.645876f},{0.205114f,0.939815f,-0.273268f},{0.306274f,0.933228f,-0.187833f}, +{0.293334f,0.918292f,-0.265886f},{-0.730003f,0.627909f,0.269862f},{0.0412235f,0.990147f,-0.133823f}, +{-0.0506354f,0.990554f,-0.127428f},{-0.797838f,0.144275f,0.585354f},{-0.814328f,-0.00914634f,0.580333f}, +{-0.875474f,-0.0288966f,0.482401f},{-0.87953f,0.267082f,0.393819f},{0.193281f,0.980464f,-0.0365016f}, +{0.241335f,0.970224f,0.0205498f},{0.2625f,0.963919f,-0.0442113f},{-0.0568875f,0.994164f,-0.0916615f}, +{-0.00599234f,0.955773f,0.294044f},{-0.632698f,0.175517f,-0.754246f},{-0.709847f,0.319233f,-0.627859f}, +{-0.644735f,0.295137f,-0.705132f},{0.0790042f,0.929158f,0.361142f},{0.76758f,0.541513f,0.342907f}, +{0.804976f,0.457231f,0.378091f},{0.147986f,0.988531f,0.0301111f},{0.696325f,0.673718f,0.247458f}, +{0.711674f,0.542561f,0.446261f},{0.242014f,0.964107f,-0.109214f},{0.161173f,0.924593f,0.345182f}, +{-0.999615f,-0.0214551f,0.0176068f},{-0.892926f,0.154387f,0.422904f},{-0.837955f,-0.521028f,0.162363f}, +{-0.839437f,-0.532918f,0.106511f},{0.0638424f,0.997652f,-0.0248022f},{-0.715259f,-0.673496f,0.186566f}, +{-0.61264f,-0.789564f,0.0355033f},{0.31859f,0.837066f,0.444771f},{0.316983f,0.920076f,-0.230177f}, +{0.428733f,0.866239f,-0.25655f},{0.271337f,0.960436f,-0.0627656f},{0.324601f,0.944442f,0.051613f}, +{-0.581084f,0.522689f,0.623809f},{-0.440341f,0.699445f,0.562918f},{-0.287863f,-0.196649f,-0.937264f}, +{-0.330953f,-0.324711f,-0.886021f},{0.339123f,0.808747f,0.480546f},{-0.021721f,0.882003f,0.470743f}, +{0.127878f,0.991291f,0.0314696f},{0.098346f,0.992921f,0.0665993f},{0.0284123f,-0.997479f,0.0650288f}, +{0.020356f,-0.99678f,0.0775616f},{0.0240698f,-0.997295f,0.069456f},{0.278418f,0.949947f,-0.141718f}, +{0.604354f,0.51753f,0.605738f},{0.185444f,0.978943f,-0.0853253f},{-0.902527f,-0.429563f,0.0303291f}, +{0.180423f,0.975577f,0.125284f},{0.291481f,0.956568f,-0.00407742f},{0.349613f,0.890753f,-0.290396f}, +{0.356001f,0.892578f,-0.27671f},{-0.772158f,0.517877f,0.368206f},{-0.833386f,-0.170946f,0.525591f}, +{-0.467427f,0.879952f,-0.0848387f},{0.745154f,0.610309f,-0.268828f},{-0.860918f,-0.0426011f,-0.506957f}, +{-0.425821f,0.844817f,-0.323977f},{-0.138972f,0.83396f,0.534038f},{0.641568f,0.757832f,0.118662f}, +{0.82573f,0.549377f,0.127887f},{0.436349f,0.851679f,0.290243f},{0.149454f,0.967097f,0.205881f}, +{0.123489f,0.962764f,0.240489f},{0.0777148f,0.948839f,0.306049f},{0.259818f,0.964051f,0.0556885f}, +{0.671152f,0.613816f,0.415674f},{0.737405f,0.584125f,0.339162f},{0.0787638f,0.972075f,0.221056f}, +{0.0226107f,0.965058f,0.261059f},{-0.318905f,0.888959f,-0.328713f},{-0.387093f,0.91673f,0.0988183f}, +{0.338681f,0.924639f,0.174179f},{-0.0464892f,0.917725f,0.394486f},{0.34519f,0.784268f,0.515527f}, +{0.0981635f,0.845149f,0.525441f},{0.32706f,0.938825f,-0.107887f},{0.334149f,0.940787f,-0.057133f}, +{0.360584f,0.904615f,-0.227268f},{0.370049f,0.916384f,-0.152656f},{0.219202f,0.949682f,-0.223729f}, +{0.0417123f,0.962056f,0.269644f},{0.0445164f,0.999004f,0.0029002f},{0.0311419f,0.99584f,0.0856323f}, +{-0.0105715f,0.904381f,0.426594f},{0.250169f,0.889893f,0.381453f},{-0.479884f,0.734289f,0.480136f}, +{0.0616234f,0.91504f,0.398629f},{-0.873565f,-0.484911f,0.0417901f},{0.193391f,0.97755f,0.0836386f}, +{-0.24517f,-0.897212f,-0.367291f},{-0.738477f,0.556206f,-0.381164f},{0.00208972f,0.995448f,-0.0952887f}, +{0.023146f,0.996478f,-0.0805908f},{-0.200122f,0.828964f,0.522273f},{-0.223522f,0.953553f,0.201925f}, +{0.261343f,0.928881f,-0.262449f},{0.331113f,0.923764f,-0.192418f},{0.573723f,0.819049f,0.000855026f}, +{0.362715f,0.829104f,0.42547f},{-0.0573298f,0.844648f,0.532243f},{-0.0897245f,0.990344f,-0.105682f}, +{0.791768f,0.584989f,0.175761f},{0.788555f,0.524059f,0.321781f},{0.396688f,0.784354f,0.476893f}, +{-0.142441f,0.853827f,0.50069f},{-0.497251f,0.741148f,0.451045f},{-0.52758f,0.754131f,-0.391083f}, +{-0.74581f,0.423423f,-0.514276f},{-0.835334f,0.507605f,-0.211079f},{-0.760969f,0.639595f,0.108834f}, +{0.665519f,0.564337f,0.488476f},{0.184704f,0.965314f,0.184535f},{-0.377948f,0.905644f,-0.19226f}, +{-0.374352f,0.926577f,-0.0362743f},{0.481967f,0.867384f,0.123909f},{0.524646f,0.846426f,-0.0911632f}, +{-0.0773877f,0.88845f,0.452402f},{-0.697527f,-0.161082f,0.698218f},{-0.728014f,-0.219408f,0.649505f}, +{-0.806167f,-0.22881f,0.545657f},{0.859404f,0.505542f,0.0765056f},{0.18694f,0.936136f,0.297831f}, +{0.627902f,0.752182f,-0.199901f},{0.439551f,0.827994f,0.348167f},{-0.106603f,0.891453f,0.440395f}, +{-0.739824f,0.137059f,0.658692f},{-0.696005f,0.314806f,0.645348f},{0.777963f,0.596105f,0.198575f}, +{0.625854f,0.727341f,0.281572f},{0.586037f,0.75803f,0.286273f},{-0.273339f,0.876824f,0.395556f}, +{-0.117479f,0.873974f,0.471559f},{-0.141536f,0.827666f,0.543081f},{0.0223492f,-0.998109f,0.0572571f}, +{-0.000931591f,-0.998825f,0.0484584f},{-0.360291f,0.888095f,0.285442f},{-0.105675f,0.913747f,0.392299f}, +{-0.13649f,0.989925f,0.0376649f},{0.0384947f,0.98542f,0.165729f},{0.647598f,0.705639f,-0.287558f}, +{-0.629182f,0.60845f,-0.483651f},{-0.18035f,0.850769f,0.493626f},{-0.200136f,0.659248f,0.724802f}, +{-0.40814f,0.771931f,0.487385f},{-0.56766f,0.779026f,0.266235f},{-0.50392f,0.782727f,0.365244f}, +{0.059644f,0.998218f,-0.00191144f},{0.493305f,0.788252f,0.367843f},{0.233534f,0.915322f,0.328097f}, +{0.100081f,0.994977f,-0.001886f},{0.0540968f,0.9859f,0.158351f},{0.0288702f,0.997117f,-0.0701744f}, +{-0.579249f,0.78716f,-0.211777f},{0.474653f,0.813336f,0.336437f},{0.549723f,0.788931f,0.274576f}, +{-0.633886f,0.679356f,0.369682f},{-0.0300771f,0.878075f,0.477576f},{0.0456401f,0.99866f,-0.0244092f}, +{0.0727139f,0.973018f,0.21897f},{0.0735803f,0.671302f,-0.737522f},{0.0494458f,0.395419f,-0.917169f}, +{0.00888718f,0.859255f,-0.511471f},{-0.854026f,0.2938f,0.429328f},{0.267715f,0.935591f,0.230212f}, +{-0.0884375f,0.963257f,-0.253602f},{-0.27228f,0.959388f,-0.0737389f},{-0.55698f,0.773671f,-0.302004f}, +{-0.71943f,0.67545f,-0.161826f},{-0.653106f,0.624328f,-0.428564f},{-0.379852f,0.787478f,-0.485377f}, +{-0.0496136f,0.821031f,0.568724f},{-0.556395f,0.828516f,-0.0631306f},{-0.00650614f,0.939219f,0.343256f}, +{-0.0378066f,0.973412f,-0.225918f},{-0.554061f,0.40543f,-0.727078f},{-0.569182f,0.171273f,-0.804175f}, +{-0.709345f,0.399365f,-0.580807f},{0.312263f,0.8806f,-0.356421f},{0.670924f,0.693388f,0.262818f}, +{-0.537859f,0.0528585f,0.841376f},{0.383935f,0.853309f,0.352785f},{-0.437111f,0.89267f,0.109884f}, +{-0.318147f,0.945762f,0.0657079f},{0.817501f,0.574998f,0.0327173f},{0.804964f,0.593238f,-0.0100991f}, +{0.82956f,0.536471f,0.155014f},{0.807816f,0.543091f,0.229097f},{0.767553f,0.561873f,0.308482f}, +{0.734804f,0.509692f,0.447523f},{-0.184696f,0.932773f,-0.309551f},{-0.164745f,0.95509f,-0.246299f}, +{0.272873f,0.875473f,0.398857f},{-0.547438f,0.648874f,0.528464f},{-0.509731f,0.801615f,0.312391f}, +{-0.693304f,0.660768f,0.287602f},{0.830856f,0.553276f,-0.0596929f},{0.817452f,0.574321f,0.04391f}, +{0.82274f,0.56071f,0.0932945f},{0.799149f,0.580408f,0.156486f},{-0.187866f,0.976313f,0.107331f}, +{0.83228f,0.547521f,-0.0867808f},{0.814186f,0.578133f,-0.0535041f},{0.810153f,0.585831f,0.0213339f}, +{0.809173f,0.580772f,0.0891247f},{0.80695f,0.590596f,-0.00528696f},{0.801762f,0.595959f,0.044831f}, +{0.77998f,0.602313f,0.169853f},{0.744047f,0.592678f,0.308427f},{-0.966823f,0.0452095f,0.251413f}, +{-0.956155f,0.198707f,0.215134f},{0.0574333f,0.997765f,-0.0341582f},{0.806921f,0.589501f,-0.0369692f}, +{0.802278f,0.59668f,0.0179628f},{0.766982f,0.633314f,0.10321f},{0.650228f,0.622419f,0.435658f}, +{0.704453f,0.647844f,0.289904f},{0.628921f,0.597557f,0.497378f},{-0.743343f,0.229344f,-0.628365f}, +{-0.69546f,0.312394f,-0.647105f},{-0.660438f,0.266932f,-0.701832f},{0.806766f,0.581782f,-0.103241f}, +{0.80487f,0.593418f,-0.00630056f},{0.769639f,0.637487f,0.035579f},{0.734675f,0.659159f,0.160504f}, +{0.80405f,0.549513f,-0.227025f},{0.803626f,0.593911f,-0.0381408f},{0.769531f,0.638363f,-0.0177426f}, +{0.765295f,0.642865f,0.0323697f},{0.567755f,0.649438f,0.505849f},{0.381409f,-0.893429f,-0.237301f}, +{-0.00752063f,0.229679f,-0.973237f},{0.807026f,0.580752f,-0.10694f},{0.758431f,0.650052f,-0.047062f}, +{0.732079f,0.672795f,0.106805f},{0.704091f,0.702637f,0.102745f},{0.698117f,0.699929f,0.150773f}, +{0.663589f,0.679392f,0.313173f},{0.574373f,0.681896f,0.452896f},{-0.803084f,0.330145f,-0.496045f}, +{0.798028f,0.532226f,-0.282643f},{0.766386f,0.563802f,-0.307862f},{0.80442f,0.55547f,-0.210621f}, +{0.76591f,0.631894f,-0.11871f},{0.715646f,0.697201f,0.0419614f},{0.603596f,0.722225f,0.337732f}, +{0.520807f,0.654939f,0.547553f},{0.229172f,0.800804f,0.553347f},{0.0812578f,0.986317f,-0.143444f}, +{-0.9886f,0.111022f,-0.101703f},{0.741263f,0.671205f,0.00366621f},{0.704182f,0.709381f,0.0301024f}, +{0.639249f,0.747109f,0.182178f},{0.586867f,0.654313f,0.47693f},{-0.604248f,0.685136f,0.406783f}, +{0.749005f,0.617153f,-0.241066f},{0.692909f,0.716962f,-0.0764364f},{0.694198f,0.719117f,-0.0309967f}, +{0.667752f,0.740189f,0.0789108f},{0.709131f,0.696508f,-0.109592f},{0.666081f,0.734678f,-0.128782f}, +{0.67466f,0.736436f,0.0499464f},{0.637302f,0.765583f,0.0879164f},{0.605727f,0.778945f,0.162297f}, +{0.584384f,0.769989f,0.25615f},{0.569427f,0.738575f,0.360916f},{0.701706f,0.63332f,-0.326365f}, +{0.672913f,0.705054f,-0.2238f},{0.646507f,0.761239f,0.0504462f},{0.540585f,0.730735f,0.416886f}, +{0.554591f,0.652851f,0.515959f},{-0.45615f,0.77338f,-0.440239f},{-0.861977f,-0.205293f,0.463519f}, +{-0.851613f,0.0465911f,0.522096f},{-0.884595f,-0.0397011f,0.464667f},{-0.265588f,0.792521f,0.548975f}, +{0.626767f,0.74215f,-0.237436f},{0.650336f,0.759537f,0.0128991f},{0.607046f,0.766684f,0.209022f}, +{0.42584f,0.81007f,-0.403047f},{0.479766f,0.78941f,-0.382957f},{0.440625f,0.776807f,-0.449912f}, +{-0.965918f,-0.188346f,-0.177559f},{-0.917033f,-0.300542f,-0.262156f},{0.655378f,0.670166f,-0.348365f}, +{0.655846f,0.747655f,-0.104302f},{0.638829f,0.768028f,-0.0450667f},{0.620536f,0.777113f,0.105025f}, +{0.419917f,0.795884f,0.436164f},{-0.484423f,0.76896f,-0.417174f},{0.283428f,-0.883937f,-0.371919f}, +{-0.877507f,-0.432781f,-0.206596f},{-0.0647534f,0.116705f,-0.991053f},{-0.00251298f,-0.196393f,-0.980522f}, +{-0.120996f,-0.17257f,-0.977537f},{0.625618f,0.75948f,-0.178302f},{0.622883f,0.775196f,-0.105295f}, +{0.618509f,0.784484f,0.045081f},{0.555738f,0.808838f,0.192188f},{0.540989f,0.792397f,0.281847f}, +{0.494361f,0.783838f,0.375773f},{-0.472517f,-0.398134f,0.786268f},{-0.503204f,-0.412649f,0.75928f}, +{-0.0691448f,0.651623f,-0.755385f},{-0.0855156f,0.570482f,-0.816846f},{-0.886059f,-0.355448f,-0.297583f}, +{0.594546f,0.762545f,-0.255029f},{0.622101f,0.721187f,-0.304761f},{0.605176f,0.774254f,-0.185182f}, +{0.610974f,0.791608f,0.00823471f},{0.573345f,0.817999f,0.0464058f},{0.560795f,0.821271f,0.10499f}, +{0.414525f,0.848734f,0.328358f},{0.736676f,0.649956f,0.186727f},{0.0552009f,0.917445f,0.394014f}, +{0.58116f,0.774985f,-0.248299f},{0.603961f,0.79543f,-0.0502213f},{0.566922f,0.823735f,0.00775409f}, +{0.433232f,0.874383f,0.218552f},{0.338907f,0.844676f,0.414324f},{0.335796f,0.825149f,0.45428f}, +{0.335634f,0.727757f,0.598097f},{0.592382f,0.796432f,-0.121568f},{0.564733f,0.823747f,-0.0501802f}, +{0.471839f,0.874441f,0.112784f},{0.391375f,0.8556f,0.338782f},{0.272028f,0.795259f,0.541815f}, +{-0.321054f,-0.946891f,-0.017938f},{-0.583011f,0.804225f,-0.115411f},{-0.0186705f,0.88413f,0.466868f}, +{0.58237f,0.771302f,-0.256787f},{0.583304f,0.788563f,-0.194745f},{0.56576f,0.815156f,-0.124239f}, +{0.508372f,0.860502f,0.0330708f},{0.427096f,0.759848f,0.490122f},{0.0181058f,0.259257f,-0.965639f}, +{-0.0500199f,0.232695f,-0.971262f},{0.0170665f,0.950699f,0.309644f},{-0.0152952f,0.950205f,0.311251f}, +{-0.881404f,0.0726797f,-0.466739f},{-0.877608f,0.0389104f,-0.477797f},{0.30436f,0.386033f,-0.870829f}, +{0.165782f,0.556552f,-0.814105f},{0.346919f,0.302604f,-0.887737f},{0.513981f,0.856343f,-0.0500122f}, +{0.511201f,0.859461f,-0.000758396f},{0.361243f,0.903013f,0.23253f},{0.452609f,0.687968f,0.567314f}, +{0.407572f,0.674302f,0.615794f},{0.57013f,0.802797f,-0.174555f},{0.51097f,0.852913f,-0.107003f}, +{0.453526f,0.888135f,0.0743731f},{0.413537f,0.896599f,0.158418f},{0.389912f,0.886016f,0.250887f}, +{0.445681f,0.789273f,0.422395f},{0.309789f,0.731113f,0.607869f},{-0.0846571f,0.900316f,0.426924f}, +{0.545197f,0.794322f,-0.267979f},{0.547967f,0.836466f,-0.00756271f},{0.470132f,0.882429f,0.0171804f}, +{0.431296f,0.900927f,0.0481153f},{0.416849f,0.879298f,0.230372f},{0.440959f,0.838019f,0.321371f}, +{0.407999f,0.737634f,0.537989f},{-0.886332f,0.390751f,0.248453f},{-0.833616f,0.516196f,0.196536f}, +{0.471464f,0.881296f,-0.0322266f},{0.438674f,0.898631f,0.00523751f},{0.406783f,0.824159f,0.394068f}, +{0.2856f,0.832072f,0.475488f},{0.457025f,0.885608f,-0.0826232f},{0.446409f,0.893715f,-0.0446337f}, +{0.402155f,0.90619f,0.130735f},{0.173173f,0.782164f,0.598523f},{-0.13066f,-0.990258f,0.0481328f}, +{0.516393f,0.806743f,0.287235f},{-0.430925f,0.715581f,0.54977f},{0.448735f,0.327623f,-0.831445f}, +{0.493816f,0.1669f,-0.853399f},{-0.512482f,0.435375f,0.740143f},{-0.501902f,0.485164f,0.716038f}, +{0.00864245f,0.296578f,-0.95497f},{-0.638979f,0.481665f,-0.599754f},{-0.619463f,0.418906f,-0.663915f}, +{-0.676628f,-0.0650151f,0.733449f},{-0.653662f,-0.082975f,0.752224f},{-0.807571f,0.313101f,0.499796f}, +{-0.757473f,0.403682f,0.513103f},{-0.852017f,0.200593f,0.483559f},{-0.864641f,0.0993041f,0.492479f}, +{-0.819801f,0.214178f,0.531088f},{-0.890242f,0.0583071f,0.451741f},{-0.685458f,0.430081f,0.587519f}, +{-0.512972f,0.206372f,0.833229f},{-0.788466f,-0.229526f,-0.570648f},{0.0241731f,-0.998842f,-0.0416034f}, +{0.0326615f,-0.998887f,-0.0340157f},{0.0295508f,-0.999062f,-0.03164f},{-0.467001f,0.782354f,0.412107f}, +{-0.292685f,0.821794f,0.488866f},{-0.412556f,0.764212f,0.495759f},{-0.610348f,0.435518f,0.661664f}, +{-0.811272f,-0.319379f,0.48973f},{-0.81612f,-0.309379f,0.48809f},{-0.70417f,0.414397f,0.576559f}, +{-0.687124f,0.513775f,0.513707f},{-0.47835f,0.641065f,0.600181f},{-0.421403f,0.744624f,0.517644f}, +{-0.741206f,0.448145f,0.499779f},{-0.814976f,0.396613f,0.422508f},{-0.812736f,-0.386413f,-0.436056f}, +{-0.104997f,0.47965f,-0.871155f},{-0.0162628f,0.460022f,-0.887759f},{-0.765465f,0.240563f,0.596819f}, +{-0.597611f,0.57362f,0.560198f},{-0.689789f,0.505065f,0.518749f},{-0.727175f,0.423293f,0.540407f}, +{-0.308127f,0.849674f,0.427916f},{-0.0791706f,0.609536f,0.788796f},{-0.823089f,-0.0371484f,0.566695f}, +{-0.82769f,-0.0336199f,0.560178f},{-0.815599f,-0.0632867f,0.575146f},{-0.127929f,-0.000264798f,-0.991783f}, +{-0.84084f,0.290014f,0.457034f},{-0.859387f,0.296015f,0.416928f},{-0.89802f,-0.0693067f,0.434461f}, +{-0.825519f,-0.0620058f,0.560957f},{-0.818802f,-0.152933f,0.55333f},{-0.80371f,-0.147357f,0.576486f}, +{-0.579463f,-0.023075f,-0.814672f},{-0.673952f,0.194501f,-0.712711f},{-0.588945f,0.0471562f,-0.806796f}, +{-0.425986f,-0.0397759f,-0.903855f},{-0.613002f,0.594276f,0.520638f},{-0.509363f,0.698473f,0.502678f}, +{-0.636005f,0.594838f,0.491595f},{-0.498753f,-0.295648f,0.814762f},{-0.780513f,-0.394895f,0.484621f}, +{-0.726784f,-0.181705f,0.662396f},{-0.654261f,-0.705981f,0.27117f},{-0.454155f,-0.890405f,-0.0303461f}, +{-0.78205f,-0.621982f,-0.0391931f},{-0.813216f,-0.3632f,0.454716f},{-0.846288f,-0.351263f,0.400513f}, +{-0.833476f,-0.307216f,0.459277f},{-0.851383f,-0.494771f,0.174211f},{-0.924493f,-0.309175f,-0.222986f}, +{-0.424515f,0.751553f,0.50493f},{-0.801391f,-0.0292747f,0.597424f},{-0.823191f,-0.323435f,0.466634f}, +{-0.81734f,-0.314257f,0.482906f},{-0.827421f,-0.291732f,0.479861f},{-0.536161f,0.21014f,0.817541f}, +{-0.479851f,0.395272f,0.783264f},{-0.585877f,0.38605f,0.712541f},{-0.818216f,-0.122578f,0.561691f}, +{-0.827807f,-0.206853f,0.521486f},{-0.405365f,0.515397f,0.755014f},{-0.679631f,0.446817f,0.58177f}, +{-0.544681f,0.503073f,0.671f},{-0.748484f,-0.190258f,0.635274f},{-0.582454f,0.295646f,-0.757193f}, +{-0.238605f,-0.733799f,-0.636087f},{-0.211375f,-0.785263f,-0.581965f},{-0.248305f,-0.659941f,-0.7091f}, +{-0.671462f,-0.241978f,0.700418f},{-0.702697f,0.40569f,0.584494f},{-0.424814f,-0.45386f,0.78329f}, +{0.50287f,0.771715f,-0.389331f},{-0.681696f,-0.191082f,0.706242f},{-0.716836f,-0.265053f,0.644897f}, +{-0.770713f,-0.297385f,0.563528f},{-0.747238f,-0.118241f,0.653953f},{-0.954785f,-0.184225f,0.23334f}, +{-0.548361f,-0.727836f,0.411769f},{-0.921405f,-0.371992f,0.112408f},{-0.503367f,-0.393242f,0.769404f}, +{-0.576726f,-0.342498f,0.741675f},{-0.361183f,0.225455f,-0.90483f},{-0.891653f,0.404043f,0.204217f}, +{0.0338141f,-0.928257f,0.370398f},{-0.0219507f,-0.943046f,0.331938f},{-0.0745498f,-0.684872f,0.72484f}, +{-0.806646f,-0.13619f,0.57513f},{-0.808811f,-0.404271f,0.427071f},{-0.44685f,0.609374f,0.654972f}, +{-0.461291f,0.570642f,0.679396f},{-0.170721f,-0.981654f,0.0849051f},{0.548492f,0.127866f,-0.826321f}, +{0.481372f,0.192323f,-0.855157f},{0.542861f,0.164021f,-0.82365f},{-0.29651f,0.199589f,-0.933941f}, +{0.227659f,0.120444f,-0.966263f},{-0.59647f,0.306507f,-0.741807f},{0.0284581f,0.296894f,-0.954486f}, +{0.0122738f,0.499735f,-0.866092f},{-0.829669f,-0.29228f,0.475629f},{-0.485308f,-0.240124f,0.840724f}, +{-0.438823f,-0.341511f,0.831146f},{-0.605016f,0.775383f,0.180933f},{-0.712909f,0.673776f,-0.194388f}, +{-0.433914f,-0.114761f,-0.893615f},{-0.513091f,-0.0345913f,-0.857637f},{-0.521915f,0.0848665f,-0.848765f}, +{-0.655513f,0.204912f,0.726852f},{-0.425762f,-0.24177f,-0.871937f},{-0.814908f,-0.301909f,0.494749f}, +{-0.532045f,-0.147684f,0.833737f},{-0.896942f,-0.179224f,0.404195f},{-0.560803f,0.260994f,0.785737f}, +{-0.676909f,-0.32187f,0.661962f},{-0.804451f,-0.351504f,0.478856f},{-0.826835f,-0.314315f,0.466423f}, +{0.24554f,-0.848717f,0.468391f},{0.252259f,-0.880717f,0.400878f},{0.261369f,-0.906495f,0.331593f}, +{-0.871316f,0.402879f,0.280174f},{-0.947823f,0.241453f,0.208163f},{-0.736231f,0.393781f,0.550364f}, +{-0.897723f,0.376006f,0.22959f},{-0.499015f,-0.219682f,0.838286f},{-0.685411f,0.694441f,-0.219006f}, +{-0.496778f,0.833956f,0.240268f},{-0.679974f,-0.471155f,0.561826f},{-0.587924f,-0.513635f,0.62492f}, +{-0.626815f,-0.437178f,0.644963f},{-0.814539f,-0.302632f,0.494915f},{0.0151252f,-0.997929f,0.0625192f}, +{-0.368475f,0.238131f,-0.898621f},{-0.356529f,0.312421f,-0.8805f},{-0.457637f,-0.114404f,0.881748f}, +{0.029499f,-0.999511f,-0.0103668f},{-0.00938924f,-0.999942f,0.00521503f},{-0.00868702f,-0.99974f,-0.0210859f}, +{-0.883607f,-0.125016f,0.451231f},{-0.897507f,0.0204293f,0.440526f},{-0.667722f,0.554902f,0.496217f}, +{-0.763542f,0.385739f,0.51789f},{-0.510416f,-0.322454f,0.797182f},{-0.464866f,-0.34522f,0.815305f}, +{-0.158149f,0.34114f,-0.926613f},{-0.627815f,0.613943f,0.478458f},{-0.44089f,0.789623f,0.426745f}, +{-0.828792f,-0.311666f,0.464724f},{-0.554504f,0.725721f,0.407252f},{-0.662594f,-0.0304637f,0.748359f}, +{-0.435102f,-0.462729f,0.772378f},{-0.666116f,0.499234f,0.554125f},{-0.732412f,0.118538f,0.670464f}, +{-0.869508f,-0.48865f,0.0719463f},{-0.311367f,-0.328529f,-0.891694f},{-0.971581f,-0.23594f,0.0190483f}, +{-0.863437f,-0.282645f,0.417838f},{-0.302687f,-0.925788f,-0.226489f},{-0.488993f,-0.817903f,-0.303186f}, +{-0.80689f,-0.288878f,0.515246f},{-0.551655f,0.717482f,0.425319f},{-0.432414f,-0.240387f,0.869041f}, +{-0.897626f,0.378674f,0.225551f},{-0.576051f,0.646691f,0.499956f},{-0.526094f,0.728597f,0.438601f}, +{-0.77966f,0.166922f,0.603545f},{-0.787693f,0.266878f,0.555261f},{-0.601311f,-0.0491254f,-0.797504f}, +{-0.785811f,0.00908449f,-0.6184f},{-0.769137f,0.0463938f,-0.637398f},{-0.445453f,-0.202091f,-0.872199f}, +{-0.654855f,-0.272176f,-0.705043f},{-0.544025f,-0.184676f,-0.818493f},{0.030312f,0.401667f,-0.915284f}, +{-0.041871f,0.443354f,-0.895368f},{-0.0727087f,0.385587f,-0.919802f},{-0.511658f,0.7034f,0.493391f}, +{-0.805663f,0.152777f,0.572334f},{-0.751126f,-0.47213f,0.461414f},{-0.798257f,-0.322169f,0.508913f}, +{-0.576619f,0.0724766f,0.813792f},{-0.520617f,0.718185f,0.461702f},{-0.33865f,0.562898f,0.753964f}, +{0.234955f,0.238834f,-0.942207f},{0.251331f,0.314931f,-0.915233f},{0.185063f,0.330729f,-0.925403f}, +{-0.886911f,0.375469f,0.269096f},{-0.154109f,0.250557f,-0.955757f},{-0.400531f,0.813668f,0.421331f}, +{-0.5416f,-0.770518f,0.336113f},{-0.544135f,0.16461f,-0.822691f},{-0.211698f,0.175625f,-0.961426f}, +{-0.334504f,-0.0129159f,-0.942306f},{-0.394133f,0.000893468f,-0.919053f},{-0.391245f,0.0852153f,-0.916333f}, +{-0.648911f,0.15809f,-0.74426f},{-0.812766f,-0.31308f,0.491318f},{-0.692476f,-0.419684f,0.586807f}, +{-0.81264f,-0.0624337f,0.579412f},{-0.633244f,-0.291183f,0.717088f},{-0.952223f,0.27226f,0.138367f}, +{-0.452731f,0.502721f,0.736415f},{-0.719407f,-0.663803f,-0.2045f},{-0.613654f,-0.789512f,-0.00995104f}, +{-0.737211f,-0.205266f,0.643728f},{-0.575835f,0.262539f,-0.774266f},{-0.543006f,0.155016f,-0.825297f}, +{-0.769728f,0.282775f,0.572326f},{-0.794855f,0.0192248f,0.606495f},{-0.466456f,-0.18462f,0.865063f}, +{-0.502563f,-0.290176f,0.814388f},{-0.581134f,-0.13327f,0.802821f},{-0.339314f,-0.250814f,0.906619f}, +{-0.831761f,-0.0775132f,0.549696f},{0.0198175f,-0.999708f,-0.0138038f},{0.00494828f,-0.999849f,-0.0166457f}, +{0.0221211f,-0.999637f,-0.0153761f},{-0.35896f,0.0528321f,-0.931857f},{-0.394276f,0.80845f,0.436984f}, +{-0.633605f,0.20823f,0.745108f},{-0.594276f,0.417433f,0.687449f},{-0.546438f,0.464911f,0.696608f}, +{-0.595217f,0.476599f,0.64697f},{-0.362789f,-0.186007f,-0.913119f},{-0.420958f,0.66549f,0.616375f}, +{-0.543996f,0.546085f,0.637071f},{-0.325797f,-0.26756f,0.90679f},{-0.183163f,-0.194712f,0.963607f}, +{-0.445429f,0.0859319f,-0.891184f},{-0.545556f,0.105608f,-0.831394f},{-0.610353f,0.539882f,-0.579653f}, +{-0.545833f,-0.242049f,0.802171f},{-0.759919f,-0.00155307f,0.650016f},{-0.752876f,-0.0855841f,0.652574f}, +{-0.534606f,-0.263421f,0.802998f},{-0.552957f,-0.182488f,0.81298f},{-0.635767f,0.182895f,0.7499f}, +{-0.559979f,0.292242f,0.775253f},{-0.598309f,0.249397f,0.761464f},{-0.638105f,0.200247f,0.743453f}, +{-0.66314f,0.0604394f,0.746051f},{-0.510999f,0.0128487f,0.859485f},{-0.452527f,-0.107616f,0.885233f}, +{-0.446189f,-0.437712f,0.780592f},{-0.818986f,0.569003f,0.0741459f},{-0.256962f,-0.883326f,0.392053f}, +{0.160324f,0.287601f,-0.944236f},{-0.98996f,-0.125316f,0.0653766f},{-0.984502f,-0.118441f,-0.129335f}, +{0.0653595f,-0.997831f,0.00782317f},{-0.17971f,-0.965573f,0.188077f},{0.00449062f,-0.995378f,0.0959267f}, +{-0.50379f,-0.181022f,0.844646f},{0.00183938f,-0.468409f,-0.88351f},{-0.450589f,0.612839f,0.649152f}, +{-0.638726f,0.29257f,0.71164f},{-0.572547f,0.229584f,0.787071f},{-0.545939f,0.374596f,0.749419f}, +{-0.346339f,0.933504f,-0.092841f},{-0.532212f,0.180167f,0.827218f},{-0.657665f,-0.128369f,0.742293f}, +{-0.557517f,-0.316215f,0.767582f},{-0.537161f,0.59042f,0.60238f},{-0.433749f,0.634675f,0.63957f}, +{-0.907702f,-0.239757f,-0.344374f},{-0.815025f,-0.244928f,-0.525113f},{-0.508138f,-0.465009f,0.724957f}, +{-0.597399f,-0.419279f,0.683608f},{-0.157794f,0.0739844f,-0.984697f},{-0.215131f,0.345583f,-0.913395f}, +{-0.641043f,-0.381159f,0.666169f},{-0.463103f,-0.204799f,-0.862318f},{-0.420183f,-0.257033f,-0.870276f}, +{-0.426429f,-0.833762f,0.350713f},{-0.288873f,-0.855688f,0.42936f},{-0.262027f,-0.885132f,0.384556f}, +{-0.505665f,-0.329907f,-0.79716f},{-0.423011f,-0.843442f,0.33116f},{-0.188296f,-0.95367f,0.234643f}, +{-0.186668f,-0.96361f,0.191339f},{0.016626f,-0.42944f,-0.902942f},{0.229574f,-0.618164f,-0.751778f}, +{0.213167f,-0.630559f,-0.746294f},{-0.653506f,0.170378f,0.737497f},{-0.754205f,0.139048f,0.641748f}, +{-0.733566f,-0.113507f,-0.670073f},{-0.714556f,0.0437677f,-0.698208f},{-0.534084f,0.6353f,0.557806f}, +{-0.477724f,0.66335f,0.575975f},{-0.564231f,-0.278572f,0.777201f},{-0.0153342f,-0.98466f,0.173808f}, +{-0.00899519f,-0.990321f,0.138506f},{-0.747073f,0.149048f,0.647816f},{-0.739693f,0.330268f,0.586325f}, +{-0.75432f,0.0203637f,0.656191f},{-0.584713f,-0.0127331f,0.81114f},{-0.627251f,0.0874965f,0.773886f}, +{-0.375288f,0.744741f,0.551832f},{-0.85599f,0.509751f,-0.086229f},{-0.856581f,0.450396f,-0.251818f}, +{-0.964796f,0.114569f,-0.236733f},{-0.788723f,0.613207f,-0.0435051f},{-0.705179f,0.661605f,-0.254952f}, +{-0.622839f,-0.125899f,0.772154f},{-0.643766f,0.0804802f,0.760979f},{-0.561995f,-0.357569f,0.745859f}, +{-0.650896f,-0.230573f,0.723305f},{-0.647344f,0.0159482f,0.762031f},{-0.719224f,0.0847135f,0.689595f}, +{-0.66153f,-0.118747f,0.740458f},{-0.201788f,0.273426f,-0.940489f},{-0.265744f,0.281923f,-0.9219f}, +{-0.222384f,0.236431f,-0.945857f},{0.373598f,0.281689f,-0.883785f},{0.400531f,0.272846f,-0.874717f}, +{0.437973f,0.267974f,-0.85812f},{-0.615037f,-0.456725f,0.642753f},{-0.826765f,0.552627f,-0.10518f}, +{-0.613802f,-0.0665211f,0.786653f},{-0.794276f,-0.309824f,-0.522623f},{-0.558536f,-0.666482f,-0.493801f}, +{-0.663791f,-0.693794f,-0.27934f},{-0.614065f,-0.405564f,0.677083f},{-0.675169f,0.476757f,0.562895f}, +{-0.589291f,0.541487f,0.599607f},{-0.388849f,-0.419335f,0.820338f},{-0.637315f,-0.0303753f,0.770004f}, +{-0.0304552f,0.316204f,-0.948202f},{-0.26997f,0.334033f,-0.903071f},{-0.214728f,0.536029f,-0.816434f}, +{-0.415018f,0.564237f,-0.71372f},{-0.453154f,0.844034f,-0.286808f},{0.153137f,0.210379f,-0.965552f}, +{0.249746f,0.276643f,-0.927952f},{-0.836283f,0.441701f,0.324856f},{-0.694397f,-0.327434f,-0.640781f}, +{-0.634576f,-0.386536f,-0.669255f},{-0.722049f,-0.311531f,-0.617732f},{-0.701986f,0.404823f,0.585947f}, +{-0.769988f,0.330764f,0.545631f},{-0.81952f,0.310075f,0.481913f},{-0.793846f,0.44448f,0.415026f}, +{0.348806f,-0.234002f,-0.907512f},{-0.556915f,0.764365f,0.32495f},{-0.702632f,-0.354421f,-0.617004f}, +{-0.591409f,-0.175052f,-0.787142f},{-0.603679f,-0.341051f,-0.720595f},{-0.829158f,0.205157f,0.520006f}, +{-0.0234405f,-0.0669621f,-0.99748f},{0.0827914f,-0.212663f,-0.973612f},{-0.613392f,-0.382432f,-0.69101f}, +{-0.669679f,-0.44593f,-0.593865f},{-0.587962f,-0.438583f,-0.679666f},{-0.514604f,-0.418881f,-0.748145f}, +{-0.683355f,0.203558f,-0.701135f},{-0.343854f,-0.869667f,0.35418f},{-0.693639f,0.102823f,0.712946f}, +{-0.643381f,0.507362f,0.573276f},{-0.179229f,-0.798935f,0.57409f},{-0.625846f,-0.345565f,-0.699215f}, +{0.193112f,-0.566099f,0.801399f},{0.363743f,-0.582821f,0.726644f},{-0.590179f,-0.699363f,0.403211f}, +{-0.782717f,-0.13318f,0.607962f},{-0.750313f,0.182009f,0.635534f},{-0.685217f,0.425887f,0.590845f}, +{-0.765752f,-0.0469561f,0.641419f},{-0.55663f,-0.659276f,0.505489f},{-0.430096f,0.189334f,-0.882706f}, +{-0.868659f,-0.194001f,0.455845f},{-0.948159f,0.316867f,-0.0242684f},{-0.923184f,0.288381f,0.2541f}, +{-0.864769f,-0.393138f,0.312437f},{-0.673731f,0.121125f,0.728982f},{-0.447896f,-0.0639182f,-0.891798f}, +{-0.732419f,0.332546f,0.594117f},{-0.697571f,0.617732f,-0.363044f},{-0.232532f,-0.885746f,0.401724f}, +{-0.549515f,-0.43611f,0.71263f},{-0.915083f,0.184311f,0.358682f},{-0.631895f,0.505717f,0.587332f}, +{-0.048755f,-0.912305f,0.406599f},{-0.00499332f,-0.708072f,0.706123f},{-0.740317f,0.109004f,0.663362f}, +{-0.748566f,-0.168053f,0.64141f},{-0.819601f,-0.148101f,0.553462f},{-0.825063f,-0.0489577f,0.562915f}, +{-0.885336f,-0.0479825f,0.462469f},{-0.826903f,0.073578f,0.55751f},{-0.88385f,0.0608632f,0.463794f}, +{-0.838473f,-0.318529f,0.442156f},{-0.647852f,0.483823f,0.58839f},{-0.67151f,-0.362356f,0.646353f}, +{-0.887521f,-0.297247f,0.352066f},{-0.858956f,-0.223478f,0.460709f},{-0.811293f,0.176758f,0.557279f}, +{-0.809109f,0.259872f,0.527075f},{-0.513998f,0.694608f,0.503314f},{-0.461626f,-0.819637f,-0.339258f}, +{-0.310498f,-0.762119f,-0.568125f},{-0.323819f,-0.907785f,-0.266585f},{-0.873213f,0.321721f,0.366054f}, +{-0.0693406f,-0.767637f,0.637123f},{-0.0292936f,-0.951498f,0.306258f},{0.202785f,-0.741235f,0.639882f}, +{-0.730227f,0.361624f,0.579652f},{-0.658946f,0.469511f,0.587664f},{-0.809055f,-0.1302f,0.573131f}, +{-0.764863f,0.244937f,0.595811f},{-0.775799f,0.182781f,0.603926f},{0.250327f,0.459893f,-0.851959f}, +{-0.228841f,-0.44984f,0.863294f},{-0.783305f,0.442176f,0.436936f},{0.311199f,-0.345385f,-0.885361f}, +{0.17222f,-0.27239f,-0.946649f},{-0.791398f,-0.0531614f,0.608985f},{-0.786133f,0.0675726f,0.614352f}, +{-0.66844f,-0.297679f,-0.681598f},{-0.580798f,-0.26834f,-0.768549f},{-0.638322f,0.206607f,0.741524f}, +{-0.598039f,0.28478f,0.749166f},{-0.706041f,-0.141691f,0.693851f},{-0.504882f,0.619097f,0.601509f}, +{-0.424321f,0.739317f,0.52284f},{-0.420945f,0.809959f,0.408377f},{-0.470592f,0.407277f,0.782732f}, +{-0.647062f,-0.190766f,-0.738187f},{-0.681777f,-0.143191f,-0.717409f},{-0.772775f,-0.260891f,-0.578581f}, +{-0.473436f,-0.635563f,-0.609851f},{-0.470861f,-0.668675f,-0.575468f},{-0.549722f,-0.641587f,-0.534951f}, +{-0.913123f,-0.21534f,0.346171f},{-0.850555f,-0.110966f,0.514045f},{-0.800178f,-0.0676003f,0.59594f}, +{-0.759561f,-0.0435877f,0.648974f},{-0.75589f,0.0642564f,0.651538f},{-0.744366f,0.180982f,0.642779f}, +{-0.978468f,-0.150371f,0.141381f},{-0.626719f,0.274355f,-0.729351f},{-0.817811f,0.468598f,0.334068f}, +{-0.732834f,0.078916f,0.675815f},{-0.602859f,0.410502f,0.684141f},{-0.423418f,0.689193f,0.587988f}, +{-0.451121f,-0.705396f,-0.546724f},{-0.39819f,-0.683051f,-0.612279f},{-0.449073f,-0.679805f,-0.579826f}, +{-0.970855f,0.186967f,0.149948f},{-0.133586f,-0.655213f,0.743539f},{-0.746645f,-0.422237f,0.51404f}, +{-0.824048f,-0.311098f,0.473459f},{-0.833598f,-0.199787f,0.514975f},{-0.718462f,0.256136f,0.646689f}, +{-0.843945f,-0.00335243f,0.53642f},{-0.695026f,0.203394f,0.689615f},{-0.572752f,0.593211f,0.565735f}, +{-0.802339f,0.45988f,0.380477f},{-0.885006f,0.353562f,0.302916f},{-0.66682f,0.60401f,0.43649f}, +{-0.5106f,0.593405f,0.622221f},{-0.396866f,0.798109f,0.453344f},{-0.438631f,-0.817212f,0.373856f}, +{0.628597f,0.070023f,-0.774572f},{0.567272f,0.0837215f,-0.819264f},{0.539457f,0.0680128f,-0.839262f}, +{-0.339831f,-0.126378f,-0.931957f},{-0.0729324f,-0.0291307f,-0.996911f},{0.44108f,0.201069f,-0.874654f}, +{-0.804002f,-0.209477f,0.556508f},{-0.69699f,0.239007f,0.676077f},{-0.682645f,0.263004f,0.68178f}, +{-0.432575f,-0.75762f,0.488766f},{-0.468404f,0.0411905f,0.882554f},{-0.64841f,0.269969f,0.711815f}, +{-0.550359f,0.491757f,0.674744f},{-0.378679f,0.762194f,0.525036f},{-0.348953f,0.788101f,0.507079f}, +{-0.582953f,0.580175f,0.568825f},{-0.732857f,0.474231f,-0.487879f},{-0.553702f,0.702597f,-0.446959f}, +{0.373769f,0.188421f,-0.908182f},{-0.813838f,-0.0701697f,0.576839f},{-0.788545f,-0.0220965f,0.614579f}, +{-0.759679f,-0.0248215f,0.649824f},{-0.707009f,0.225235f,0.670379f},{-0.738227f,0.138017f,0.660282f}, +{0.0382768f,-0.996456f,0.0749003f},{-0.766233f,0.258764f,0.588157f},{-0.686959f,0.567356f,0.454087f}, +{-0.73823f,-0.0898627f,0.668536f},{-0.372659f,0.774285f,0.511477f},{-0.723401f,0.205919f,0.659006f}, +{0.160182f,-0.5659f,-0.808764f},{0.434856f,-0.616598f,-0.656283f},{0.0231616f,-0.97957f,-0.199767f}, +{-0.66567f,-0.564697f,0.487853f},{0.316206f,0.241841f,-0.917348f},{0.375475f,0.220209f,-0.900292f}, +{-0.7103f,-0.384795f,0.589412f},{-0.764348f,-0.17699f,0.620037f},{-0.802285f,-0.0445413f,0.595277f}, +{-0.77726f,0.0111166f,0.629081f},{-0.732092f,0.0259054f,0.680713f},{-0.688352f,0.102232f,0.718136f}, +{-0.680752f,0.226134f,0.696735f},{-0.884425f,0.348781f,0.310071f},{-0.861093f,0.272212f,0.429441f}, +{-0.343287f,-0.894841f,0.285332f},{-0.674998f,-0.708227f,0.206865f},{-0.800865f,0.372938f,0.468542f}, +{-0.503765f,0.611881f,0.609772f},{-0.392682f,0.749152f,0.533453f},{-0.650023f,0.325047f,0.686888f}, +{-0.291221f,-0.315675f,-0.903072f},{-0.30522f,-0.105777f,-0.946389f},{-0.253134f,-0.266368f,-0.930039f}, +{-0.673406f,0.265473f,0.689963f},{-0.827281f,0.337787f,0.448896f},{-0.775433f,-0.55732f,0.296814f}, +{-0.749854f,-0.461074f,0.474477f},{-0.803676f,0.408894f,0.432332f},{-0.681633f,0.527799f,0.506759f}, +{0.607975f,-0.172449f,-0.775002f},{0.434956f,-0.900438f,-0.00486778f},{-0.580756f,0.519123f,0.627084f}, +{0.397173f,0.329672f,-0.856487f},{-0.360323f,-0.650174f,-0.668911f},{0.0343635f,0.0931413f,-0.99506f}, +{-0.631268f,-0.222999f,-0.742813f},{-0.746227f,-0.214046f,-0.630341f},{-0.671459f,-0.189288f,-0.716459f}, +{-0.659546f,-0.0381326f,-0.750696f},{-0.815974f,0.00295256f,0.578082f},{-0.888849f,0.0688656f,0.452996f}, +{-0.803058f,0.243254f,0.54399f},{-0.833593f,0.316012f,0.453055f},{-0.579459f,0.631209f,0.51556f}, +{-0.530057f,0.818813f,0.220418f},{0.229917f,-0.548714f,0.803773f},{0.189054f,-0.33771f,0.922069f}, +{0.629261f,0.0979529f,-0.770996f},{-0.676455f,-0.307285f,0.669316f},{-0.624391f,-0.452539f,0.636666f}, +{-0.751741f,-0.0822216f,0.654312f},{-0.659358f,0.21247f,0.721183f},{-0.466465f,-0.872416f,-0.145944f}, +{-0.297602f,-0.903282f,-0.309055f},{-0.00581529f,-0.986921f,-0.161101f},{-0.850233f,0.271603f,0.450927f}, +{-0.645747f,0.76093f,0.0632147f},{-0.742278f,-0.558166f,0.370775f},{-0.752377f,-0.470443f,0.461099f}, +{-0.817796f,0.574776f,0.0290093f},{-0.383178f,0.76701f,0.514655f},{-0.630893f,0.362954f,0.685739f}, +{0.0332487f,-0.998789f,-0.0362681f},{0.0243115f,-0.99914f,-0.0335984f},{0.279421f,-0.915639f,0.289013f}, +{-0.472456f,-0.157848f,-0.867104f},{-0.82926f,0.269673f,0.489493f},{-0.668103f,-0.469037f,0.577618f}, +{-0.622098f,0.656668f,-0.426358f},{-0.902728f,0.430072f,0.0109956f},{-0.0496615f,0.304177f,-0.95132f}, +{-0.883749f,-0.145445f,-0.444784f},{-0.265136f,0.243864f,-0.932863f},{-0.610512f,-0.789531f,-0.0625791f}, +{-0.190525f,0.190871f,-0.962948f},{-0.153502f,0.660391f,-0.735065f},{0.118652f,0.907375f,-0.403227f}, +{0.154264f,0.8649f,-0.477652f},{-0.0161343f,0.881f,-0.472842f},{0.235805f,0.915596f,-0.325698f}, +{0.217685f,0.886898f,-0.407463f},{-0.729908f,0.583186f,-0.356552f},{0.116651f,0.282444f,-0.952165f}, +{0.215742f,0.267895f,-0.938982f},{0.0109213f,0.907924f,-0.418992f},{0.162564f,0.653304f,-0.739437f}, +{0.0479323f,0.611091f,-0.790108f},{0.766837f,0.53508f,-0.354471f},{-0.653129f,-0.103326f,-0.750164f}, +{-0.867818f,-0.0822351f,-0.49003f},{-0.821129f,-0.155542f,-0.549139f},{0.51254f,0.652523f,-0.558137f}, +{0.589058f,0.59275f,-0.549234f},{0.219218f,0.840223f,-0.495953f},{0.278717f,0.785049f,-0.553185f}, +{0.197953f,0.797583f,-0.569804f},{-0.830511f,-0.209543f,-0.516084f},{-0.785971f,-0.188003f,-0.588986f}, +{-0.330683f,0.346221f,-0.87794f},{-0.324171f,0.619309f,-0.715101f},{-0.000413993f,-0.998852f,0.0479042f}, +{0.0241025f,0.940366f,-0.33931f},{-0.687919f,-0.155575f,-0.708917f},{-0.608044f,0.279841f,-0.742948f}, +{-0.869399f,-0.148854f,-0.471156f},{0.15548f,0.819973f,-0.550881f},{0.0859084f,0.827993f,-0.554118f}, +{-0.927365f,0.0475724f,-0.371121f},{-0.802364f,-0.227847f,-0.551632f},{-0.833075f,-0.210273f,-0.511635f}, +{-0.915617f,-0.0470975f,-0.399283f},{-0.876015f,-0.166462f,-0.452646f},{-0.782091f,-0.310435f,-0.540337f}, +{-0.69515f,-0.343201f,-0.631648f},{-0.625069f,-0.446537f,-0.640229f},{-0.625458f,-0.0937403f,-0.774606f}, +{-0.957594f,0.0112719f,-0.2879f},{-0.634462f,-0.376214f,-0.675219f},{-0.922026f,-0.141245f,-0.360442f}, +{-0.945026f,-0.0123246f,-0.326763f},{-0.915351f,-0.124506f,-0.382924f},{-0.847758f,-0.270915f,-0.455973f}, +{-0.785936f,-0.330092f,-0.522823f},{-0.764905f,-0.404768f,-0.501082f},{-0.572019f,-0.577086f,-0.582895f}, +{-0.475734f,-0.497352f,-0.725478f},{0.0412363f,-0.999149f,-0.000575673f},{-0.974726f,-0.011953f,-0.223085f}, +{-0.476842f,-0.660929f,-0.579478f},{-0.972092f,-0.00139264f,-0.234595f},{-0.948887f,-0.0918976f,-0.301941f}, +{-0.899332f,-0.194675f,-0.391539f},{-0.830531f,-0.355461f,-0.428795f},{-0.480972f,-0.670668f,-0.564686f}, +{-0.839168f,-0.338982f,-0.425309f},{-0.693691f,-0.500137f,-0.51832f},{-0.543033f,-0.647155f,-0.535075f}, +{-0.912901f,-0.218345f,-0.344872f},{-0.238438f,0.675042f,-0.698187f},{-0.181185f,0.617331f,-0.765555f}, +{-0.966653f,0.0317663f,-0.254111f},{-0.920666f,-0.00972829f,-0.390229f},{-0.970752f,-0.0188526f,-0.239345f}, +{-0.955074f,-0.110583f,-0.274962f},{-0.939826f,-0.171711f,-0.295367f},{-0.89025f,-0.290814f,-0.350546f}, +{-0.772973f,-0.474061f,-0.421638f},{-0.553283f,-0.649761f,-0.521238f},{-0.771538f,-0.484487f,-0.412312f}, +{-0.976721f,0.0834547f,-0.197616f},{-0.96782f,-0.0501943f,-0.246587f},{-0.602259f,0.376395f,-0.703997f}, +{-0.576564f,0.215634f,-0.788084f},{-0.666104f,0.00345015f,-0.745851f},{-0.658184f,-0.594629f,-0.461746f}, +{-0.98324f,0.0764916f,-0.165493f},{-0.978554f,0.0576061f,-0.197773f},{-0.963464f,-0.11138f,-0.243581f}, +{-0.927319f,-0.231403f,-0.294163f},{-0.821827f,-0.421623f,-0.38319f},{-0.694751f,-0.508234f,-0.508939f}, +{-0.570395f,-0.621536f,-0.536976f},{-0.979728f,0.108607f,-0.168339f},{-0.729935f,-0.560912f,-0.390607f}, +{-0.548836f,-0.641527f,-0.535932f},{-0.137297f,-0.0651369f,-0.988386f},{-0.983567f,0.0087842f,-0.180332f}, +{-0.955803f,-0.176433f,-0.235186f},{-0.869009f,-0.366445f,-0.332476f},{-0.774158f,-0.455288f,-0.439764f}, +{-0.619558f,-0.627742f,-0.471262f},{-0.491225f,-0.73335f,-0.469995f},{-0.858948f,-0.422974f,-0.288621f}, +{-0.322444f,0.919917f,-0.223118f},{-0.851542f,0.52224f,-0.0462868f},{0.329143f,0.78615f,-0.523099f}, +{0.346796f,0.808097f,-0.476143f},{0.260363f,0.82633f,-0.49939f},{0.49845f,0.168966f,-0.850293f}, +{0.542044f,0.204992f,-0.814964f},{0.531619f,0.163787f,-0.830996f},{0.476991f,0.648298f,-0.593455f}, +{0.412125f,0.600022f,-0.685657f},{0.534435f,0.512549f,-0.672066f},{0.0905934f,0.864983f,-0.493555f}, +{-0.0394386f,0.854323f,-0.518244f},{-0.530297f,0.721798f,-0.444739f},{0.245727f,-0.163502f,-0.95545f}, +{0.260433f,-0.101818f,-0.960108f},{-0.0886096f,0.68229f,-0.725691f},{-0.280944f,0.805369f,-0.521968f}, +{-0.157878f,0.797038f,-0.582928f},{-0.328697f,0.704327f,-0.629191f},{-0.940973f,-0.0805013f,0.32877f}, +{-0.978425f,0.154397f,-0.137284f},{-0.98397f,-0.0434644f,-0.172959f},{-0.923355f,-0.277758f,-0.265077f}, +{-0.845702f,-0.37754f,-0.377163f},{-0.728052f,-0.547275f,-0.412833f},{-0.523263f,-0.68013f,-0.513438f}, +{-0.761618f,-0.560802f,-0.324714f},{-0.0442249f,0.846128f,-0.531142f},{0.118938f,0.815643f,-0.566198f}, +{-0.106843f,0.701031f,-0.705082f},{-0.269174f,0.720933f,-0.638593f},{-0.956881f,0.109636f,0.268995f}, +{-0.87996f,0.403169f,0.251248f},{0.0556408f,0.312043f,-0.948437f},{0.232607f,0.302752f,-0.924249f}, +{0.0834377f,0.381388f,-0.920642f},{-0.000786142f,0.810607f,-0.58559f},{-0.035975f,0.777677f,-0.627634f}, +{0.0508906f,0.747998f,-0.661747f},{-0.107152f,0.734086f,-0.670549f},{-0.106257f,0.727624f,-0.677696f}, +{-0.31261f,0.743435f,-0.591252f},{-0.102666f,0.76875f,-0.631255f},{0.169555f,0.774775f,-0.609078f}, +{-0.0233887f,0.767772f,-0.640296f},{-0.357587f,0.907916f,-0.21868f},{-0.158598f,0.747223f,-0.645372f}, +{0.0443315f,0.544164f,-0.837807f},{0.0199804f,0.53045f,-0.847481f},{0.000180536f,0.693536f,-0.720422f}, +{0.184154f,0.705218f,-0.684657f},{-0.549089f,-0.833126f,0.0663552f},{-0.355312f,-0.924539f,0.137772f}, +{-0.550829f,-0.82148f,0.147507f},{-0.228807f,0.69465f,-0.681988f},{0.554866f,0.643871f,-0.526834f}, +{0.44528f,0.677502f,-0.585421f},{0.435356f,0.728495f,-0.528923f},{-0.0265682f,0.551005f,-0.834079f}, +{-0.0283525f,0.549752f,-0.834846f},{-0.0534209f,0.591202f,-0.804753f},{0.374862f,0.383933f,-0.843845f}, +{0.393388f,0.487811f,-0.779286f},{-0.0113779f,0.578591f,-0.815539f},{-0.600071f,-0.0107687f,-0.799875f}, +{-0.768247f,0.169597f,-0.617279f},{0.249316f,0.206271f,-0.9462f},{0.260273f,0.258905f,-0.930175f}, +{0.200955f,0.168393f,-0.965019f},{-0.0240418f,-0.994179f,0.105028f},{0.00728482f,-0.993241f,0.115838f}, +{-0.965055f,0.204971f,-0.163264f},{-0.947471f,0.271797f,-0.168596f},{-0.976652f,0.151255f,-0.152557f}, +{-0.969842f,-0.135725f,-0.202448f},{-0.920074f,-0.273845f,-0.28013f},{-0.867173f,-0.400469f,-0.296032f}, +{-0.629318f,-0.659676f,-0.410837f},{-0.912934f,-0.31712f,-0.256878f},{0.353662f,0.330311f,-0.87511f}, +{0.402182f,0.318681f,-0.858307f},{0.451212f,0.315875f,-0.834644f},{0.0698329f,0.848959f,-0.523824f}, +{0.1656f,0.746272f,-0.644713f},{0.0791368f,0.199257f,-0.976747f},{-0.799717f,-0.230111f,-0.554528f}, +{-0.823964f,0.15489f,-0.545062f},{-0.843488f,0.216541f,-0.491566f},{0.00578011f,0.749809f,-0.661629f}, +{-0.0349199f,0.699277f,-0.713997f},{-0.0829151f,0.75947f,-0.645237f},{-0.880051f,0.380791f,0.283742f}, +{-0.303249f,0.728386f,-0.614406f},{-0.0616567f,-0.982204f,-0.177406f},{-0.580776f,-0.680002f,-0.447545f}, +{0.64457f,0.353625f,-0.677849f},{0.611951f,0.453782f,-0.647764f},{0.620319f,0.469135f,-0.628583f}, +{0.14269f,0.455273f,-0.878844f},{-0.114267f,0.845521f,-0.521571f},{-0.209425f,0.898253f,-0.386372f}, +{-0.260342f,0.837888f,-0.479755f},{-0.298369f,0.85314f,-0.427935f},{-0.617404f,-0.37837f,-0.689673f}, +{-0.680221f,-0.42967f,-0.593871f},{-0.710034f,-0.37957f,-0.593109f},{-0.78232f,-0.388716f,-0.486698f}, +{-0.832582f,0.30817f,-0.46026f},{-0.461526f,0.804212f,-0.374483f},{-0.490626f,0.695119f,-0.525448f}, +{-0.61646f,0.663409f,-0.424106f},{-0.639165f,0.549961f,-0.537597f},{-0.469639f,0.537434f,-0.700431f}, +{0.679093f,0.422242f,-0.600453f},{0.698225f,0.270889f,-0.662647f},{0.615003f,0.328574f,-0.716806f}, +{0.233794f,0.579118f,-0.781001f},{0.226217f,0.491376f,-0.841056f},{0.259529f,0.520396f,-0.813531f}, +{0.285164f,0.525232f,-0.801757f},{0.252231f,0.40395f,-0.87932f},{0.228237f,0.438116f,-0.869461f}, +{0.373608f,0.508213f,-0.775975f},{0.414642f,0.498775f,-0.761115f},{0.338994f,0.504203f,-0.794269f}, +{-0.672568f,-0.267274f,-0.690085f},{-0.738537f,-0.304573f,-0.601497f},{-0.581012f,-0.305672f,-0.754314f}, +{-0.816326f,-0.311619f,-0.486318f},{-0.840138f,-0.337409f,-0.424645f},{-0.897044f,-0.216641f,-0.385201f}, +{-0.900858f,-0.0265083f,-0.433303f},{-0.865957f,0.140418f,-0.480001f},{-0.847461f,0.273879f,-0.454753f}, +{-0.842143f,0.354717f,-0.406167f},{-0.737181f,0.516162f,-0.436052f},{-0.927486f,0.302631f,0.219509f}, +{-0.981675f,0.141154f,-0.128021f},{-0.965402f,-0.216887f,-0.144771f},{-0.743468f,-0.592587f,-0.309994f}, +{-0.576586f,-0.709313f,-0.405492f},{-0.80673f,-0.53517f,-0.250559f},{-0.948932f,0.0757926f,0.30624f}, +{0.0720768f,0.624395f,-0.777776f},{-0.102689f,0.384984f,-0.917192f},{-0.649688f,-0.339633f,-0.680114f}, +{-0.763436f,-0.24085f,-0.599297f},{-0.842123f,-0.328308f,-0.427834f},{-0.93705f,-0.0397002f,-0.346932f}, +{-0.832593f,0.415712f,-0.366023f},{0.0558894f,0.647015f,-0.760426f},{0.186694f,0.515431f,-0.836347f}, +{0.167057f,0.796417f,-0.581216f},{-0.013721f,0.746685f,-0.665036f},{-0.074801f,0.743277f,-0.664789f}, +{-0.557577f,-0.401232f,-0.726719f},{-0.695221f,-0.350494f,-0.627552f},{-0.758386f,-0.342505f,-0.554564f}, +{-0.923356f,0.113223f,-0.366871f},{-0.509779f,0.79534f,-0.327962f},{-0.979265f,0.119692f,0.163447f}, +{-0.98726f,0.0809253f,0.137f},{-0.904176f,-0.388755f,-0.177018f},{0.455558f,0.263758f,-0.850235f}, +{0.487306f,0.146717f,-0.860818f},{0.205814f,0.624123f,-0.753732f},{-0.0595557f,0.7814f,-0.621182f}, +{-0.100202f,0.733338f,-0.67244f},{-0.798938f,-0.286607f,-0.52873f},{-0.827743f,-0.32863f,-0.4548f}, +{-0.861636f,-0.297967f,-0.410852f},{-0.778058f,-0.249262f,-0.576623f},{-0.86606f,-0.265874f,-0.423381f}, +{-0.886519f,-0.259833f,-0.382847f},{-0.884378f,-0.314477f,-0.344935f},{-0.874401f,-0.254297f,-0.413226f}, +{-0.934063f,-0.0868268f,-0.346391f},{-0.951526f,0.0806207f,-0.296814f},{-0.889652f,0.184177f,-0.417849f}, +{-0.850233f,0.348223f,-0.394771f},{-0.787832f,0.5495f,-0.278155f},{-0.990759f,-0.0191876f,0.134268f}, +{-0.971217f,0.155734f,-0.180232f},{-0.971098f,-0.0403574f,-0.235244f},{-0.952427f,-0.20938f,-0.221457f}, +{-0.895477f,-0.335529f,-0.292474f},{-0.689745f,-0.654384f,-0.309893f},{-0.697157f,-0.662355f,-0.274332f}, +{-0.991092f,-0.133136f,0.00332425f},{-0.34596f,-0.657323f,0.669506f},{-0.139928f,0.746144f,-0.650914f}, +{-0.88565f,-0.260006f,-0.384736f},{-0.119213f,0.536603f,-0.835372f},{0.0401096f,0.531459f,-0.846134f}, +{0.217478f,0.763288f,-0.608355f},{-0.345093f,0.574183f,-0.742445f},{-0.332209f,0.464239f,-0.821048f}, +{-0.363386f,0.523473f,-0.770666f},{-0.11312f,0.740586f,-0.662372f},{-0.812245f,0.237191f,-0.532915f}, +{-0.692428f,0.460837f,-0.555133f},{-0.745763f,0.212997f,-0.631244f},{-0.592546f,0.508668f,-0.624617f}, +{-0.944421f,-0.251383f,0.21184f},{-0.0282219f,0.728704f,-0.684247f},{-0.89321f,-0.192984f,-0.406121f}, +{-0.918234f,-0.245104f,-0.311078f},{-0.92194f,-0.272905f,-0.274863f},{-0.893661f,-0.357294f,-0.271499f}, +{-0.924488f,-0.268229f,-0.270878f},{-0.921025f,-0.271284f,-0.279497f},{-0.195757f,-0.980249f,-0.0281213f}, +{0.304488f,0.726339f,-0.616214f},{0.371242f,0.605966f,-0.703552f},{0.469584f,0.571727f,-0.672769f}, +{-0.882291f,-0.437208f,0.174387f},{0.18895f,0.750629f,-0.63313f},{0.0412555f,0.674764f,-0.736879f}, +{-0.90528f,-0.318957f,-0.280597f},{-0.888689f,-0.362498f,-0.280763f},{-0.924107f,-0.260881f,-0.279224f}, +{-0.959584f,-0.106115f,-0.260649f},{-0.885816f,-0.0467579f,-0.461675f},{-0.659046f,0.746035f,0.0953459f}, +{-0.674056f,0.737495f,-0.0418221f},{0.367991f,0.483938f,-0.793969f},{-0.817986f,-0.561808f,-0.123579f}, +{-0.991833f,0.0445393f,-0.119513f},{-0.986462f,0.0322381f,-0.160793f},{-0.774363f,-0.547466f,-0.317242f}, +{-0.868264f,-0.453342f,-0.201488f},{-0.26792f,0.738131f,-0.619178f},{-0.284771f,0.799236f,-0.529271f}, +{-0.119769f,0.803515f,-0.583111f},{-0.0182616f,0.559203f,-0.828829f},{-0.0234112f,0.565326f,-0.824535f}, +{-0.0600253f,0.646342f,-0.760683f},{-0.927258f,-0.33292f,0.171338f},{0.0436789f,0.832412f,-0.552433f}, +{0.00637399f,-0.0948247f,-0.995474f},{0.0884458f,0.788312f,-0.608885f},{-0.0589162f,0.60668f,-0.79276f}, +{-0.184244f,0.75806f,-0.625619f},{-0.786197f,0.366074f,-0.49788f},{-0.74957f,0.414396f,-0.51616f}, +{-0.668454f,0.559474f,-0.490059f},{-0.587639f,0.579841f,-0.564327f},{-0.0358703f,0.534826f,-0.844201f}, +{-0.880005f,-0.0687891f,-0.469956f},{-0.915141f,-0.110246f,-0.387766f},{-0.920034f,-0.193473f,-0.340743f}, +{-0.917471f,-0.210934f,-0.337274f},{-0.907767f,-0.289137f,-0.303906f},{-0.925365f,-0.259778f,-0.27607f}, +{-0.908746f,-0.296639f,-0.293576f},{-0.89718f,-0.329847f,-0.293716f},{-0.92929f,-0.237296f,-0.283039f}, +{0.0873267f,-0.833744f,-0.545202f},{0.132631f,-0.275063f,-0.952234f},{0.133723f,0.778328f,-0.613452f}, +{0.112997f,0.888124f,-0.445496f},{-0.996352f,0.0845663f,-0.0114163f},{-0.242757f,0.902481f,-0.355804f}, +{-0.192791f,0.925555f,-0.325852f},{0.0821069f,0.599657f,-0.796034f},{0.0450017f,0.462362f,-0.885549f}, +{0.0456863f,0.445472f,-0.89413f},{0.0488839f,0.541727f,-0.839132f},{0.117665f,0.639629f,-0.759625f}, +{-0.0755639f,0.649307f,-0.756763f},{-0.523536f,-0.790108f,-0.318809f},{-0.802758f,0.404456f,-0.438171f}, +{-0.905192f,-0.304054f,-0.296949f},{-0.882081f,-0.454427f,-0.124212f},{-0.993901f,0.017008f,-0.108954f}, +{-0.937838f,-0.181207f,-0.296014f},{-0.665141f,-0.586076f,-0.462712f},{-0.778656f,-0.544372f,-0.312015f}, +{-0.997034f,0.0503957f,0.0581647f},{-0.451822f,-0.741887f,-0.49544f},{-0.530233f,-0.728497f,-0.433757f}, +{-0.514687f,-0.777484f,-0.361407f},{0.436724f,-0.897585f,0.0601103f},{-0.267713f,0.874596f,-0.404242f}, +{-0.184382f,0.894412f,-0.407468f},{-0.239753f,0.839678f,-0.487299f},{-0.660937f,0.453012f,-0.598283f}, +{-0.668512f,0.361127f,-0.650138f},{-0.674786f,0.061231f,-0.735469f},{-0.93075f,-0.0503379f,-0.362175f}, +{-0.929642f,-0.0906662f,-0.357134f},{-0.64978f,-0.615394f,-0.446178f},{-0.58863f,-0.71698f,-0.373436f}, +{0.00318272f,-0.939555f,0.342383f},{-0.145101f,-0.961075f,0.235116f},{-0.680715f,0.113708f,-0.72367f}, +{-0.887669f,0.397071f,-0.233192f},{-0.954059f,0.0816392f,-0.288281f},{-0.792749f,0.556533f,-0.248635f}, +{-0.679507f,0.672828f,-0.292527f},{-0.45138f,0.853066f,-0.261789f},{-0.603588f,0.761234f,-0.237075f}, +{-0.922933f,0.26974f,-0.274656f},{-0.96959f,-0.0548561f,-0.238506f},{-0.947935f,-0.133122f,-0.289307f}, +{-0.924055f,-0.213494f,-0.317085f},{-0.899426f,-0.251276f,-0.357622f},{-0.900932f,-0.266772f,-0.342278f}, +{-0.194343f,0.850455f,-0.488832f},{-0.0324944f,0.788577f,-0.614077f},{-0.916005f,0.348945f,0.19792f}, +{-0.812545f,-0.531355f,0.239652f},{-0.70015f,0.345013f,-0.625105f},{-0.657777f,-0.143039f,-0.739506f}, +{-0.764772f,0.307474f,-0.566201f},{-0.688961f,-0.668823f,-0.279299f},{-0.87333f,-0.281439f,-0.397601f}, +{-0.429668f,-0.366092f,-0.825447f},{-0.233538f,-0.771659f,-0.59161f},{-0.426967f,0.109738f,-0.897584f}, +{-0.737624f,-0.655111f,-0.163528f},{-0.905149f,-0.408362f,-0.118095f},{-0.999381f,-0.00136333f,-0.0351535f}, +{-0.904803f,-0.318869f,-0.282231f},{-0.946042f,-0.284675f,-0.154804f},{0.0884216f,0.584999f,-0.8062f}, +{0.106585f,0.427367f,-0.897773f},{0.0498672f,0.628808f,-0.77596f},{-0.704076f,0.547598f,-0.452122f}, +{-0.664798f,0.333039f,-0.668677f},{-0.472316f,0.827564f,-0.303406f},{-0.550143f,0.780893f,-0.295885f}, +{-0.722361f,-0.592277f,-0.356934f},{-0.872574f,0.386936f,-0.298154f},{-0.928608f,0.133556f,-0.346195f}, +{-0.792346f,0.542703f,-0.278677f},{-0.72946f,0.638489f,-0.245398f},{-0.870852f,0.385273f,-0.305257f}, +{-0.963596f,-0.0794412f,-0.255286f},{-0.947973f,-0.173342f,-0.267021f},{-0.888068f,-0.328748f,-0.321342f}, +{-0.907185f,-0.261738f,-0.329407f},{0.0476047f,0.889729f,-0.453999f},{0.0592263f,0.829014f,-0.556083f}, +{-0.0314531f,0.840112f,-0.5415f},{0.331963f,-0.0387972f,-0.942494f},{-0.0368371f,-0.256588f,-0.965819f}, +{-0.0167172f,-0.297411f,-0.954603f},{0.185203f,0.0534945f,-0.981243f},{-0.870331f,0.453847f,0.191171f}, +{-0.622077f,-0.0313076f,-0.78233f},{-0.699579f,0.420684f,-0.577594f},{-0.803813f,0.488717f,-0.339176f}, +{0.0329742f,-0.999242f,0.0207125f},{0.164832f,-0.983764f,0.0709828f},{-0.0206047f,0.918457f,-0.394983f}, +{-0.0153557f,0.905575f,-0.423907f},{-0.0935167f,0.715321f,-0.692511f},{0.0511931f,0.851253f,-0.522252f}, +{-0.0160317f,-0.999497f,0.0273526f},{-0.968668f,-0.178708f,-0.17247f},{-0.899931f,-0.411584f,-0.143951f}, +{-0.753191f,-0.543083f,-0.371166f},{-0.921122f,-0.369519f,-0.122436f},{-0.675025f,0.556727f,-0.484146f}, +{-0.922432f,0.21482f,-0.320891f},{-0.946594f,-0.313147f,0.0767992f},{-0.826512f,-0.505872f,0.246925f}, +{-0.101095f,0.874518f,-0.474339f},{0.178633f,0.891713f,-0.415859f},{0.174257f,0.866981f,-0.466882f}, +{-0.0200044f,0.642351f,-0.76615f},{0.0617693f,0.424787f,-0.903184f},{-0.0536696f,0.441809f,-0.895502f}, +{-0.977742f,-0.116558f,0.174454f},{-0.704941f,0.55243f,-0.444837f},{-0.785385f,0.27646f,-0.553842f}, +{-0.797305f,0.473351f,-0.374492f},{-0.842062f,0.352114f,-0.408592f},{-0.818045f,-0.42511f,-0.387406f}, +{0.0565383f,0.364246f,-0.929585f},{0.0346147f,0.516041f,-0.855864f},{0.363058f,0.509097f,-0.78039f}, +{0.440208f,0.416773f,-0.79531f},{0.328956f,0.493552f,-0.805106f},{0.187626f,0.556664f,-0.809273f}, +{0.208331f,0.517454f,-0.829964f},{-0.989202f,-0.121884f,-0.0813819f},{0.499549f,0.158873f,-0.851593f}, +{0.432908f,0.0386735f,-0.900608f},{0.273405f,0.49063f,-0.827364f},{0.0817157f,0.606752f,-0.79068f}, +{0.106964f,0.0401378f,-0.993452f},{0.0945015f,-0.110277f,-0.989398f},{-0.0850992f,0.719827f,-0.688917f}, +{0.417252f,0.52599f,-0.741104f},{0.395665f,0.461008f,-0.794306f},{0.103608f,0.642954f,-0.758865f}, +{0.116833f,0.681212f,-0.722704f},{0.368249f,0.572081f,-0.732882f},{-0.77825f,-0.597763f,-0.192372f}, +{-0.931479f,-0.327635f,-0.15812f},{-0.968138f,-0.239452f,-0.0732868f},{0.0234646f,-0.992831f,0.1172f}, +{0.00620094f,0.863367f,-0.504538f},{-0.74072f,-0.135665f,-0.657973f},{-0.75342f,-0.0540682f,-0.655313f}, +{-0.674416f,-0.0614504f,-0.73579f},{-0.612793f,0.0468009f,-0.788856f},{-0.734032f,0.00265937f,-0.67911f}, +{-0.706752f,0.0267475f,-0.706955f},{-0.526979f,0.0954262f,-0.844504f},{-0.782884f,-0.564665f,-0.261241f}, +{0.679924f,-0.0978195f,-0.726729f},{0.561959f,-0.391283f,-0.728766f},{-0.585405f,0.436328f,-0.683314f}, +{0.0137966f,0.957329f,-0.288671f},{-0.139726f,0.939267f,-0.313454f},{-0.0246292f,0.926615f,-0.375203f}, +{-0.687732f,-0.384911f,-0.615523f},{-0.734801f,-0.248514f,-0.631117f},{-0.789134f,-0.0194749f,-0.613912f}, +{-0.782444f,-0.0322267f,-0.621886f},{-0.73717f,0.00902504f,-0.675647f},{-0.701868f,0.228167f,-0.674774f}, +{-0.0342424f,0.914195f,-0.403826f},{0.0216571f,0.853809f,-0.520136f},{-0.158441f,0.87924f,-0.449259f}, +{-0.640767f,0.226042f,-0.733705f},{-0.751944f,0.506839f,-0.421539f},{-0.363757f,0.83771f,-0.407337f}, +{0.296575f,0.632464f,-0.715564f},{0.26247f,0.617033f,-0.741876f},{0.159429f,0.413905f,-0.89625f}, +{-0.636593f,-0.395732f,-0.661925f},{0.00776096f,0.570112f,-0.82153f},{0.0359855f,0.664895f,-0.746069f}, +{0.0310453f,0.609546f,-0.792142f},{0.288612f,0.808413f,-0.513002f},{0.393032f,0.753305f,-0.52731f}, +{0.325346f,0.75768f,-0.565748f},{-0.974308f,-0.178684f,-0.137097f},{-0.953546f,-0.22885f,-0.195905f}, +{-0.625887f,0.490681f,-0.606216f},{-0.609771f,0.642855f,-0.463592f},{-0.307379f,-0.933886f,-0.182687f}, +{0.303272f,0.817697f,-0.489282f},{-0.0330104f,0.731284f,-0.681274f},{0.0176375f,0.73728f,-0.675357f}, +{-0.799932f,-0.0896961f,-0.593349f},{-0.739251f,-0.0909561f,-0.66726f},{-0.730596f,-0.0295249f,-0.682171f}, +{-0.735792f,0.141394f,-0.662282f},{0.398786f,0.719891f,-0.568091f},{0.175395f,0.891763f,-0.417127f}, +{-0.878951f,-0.343843f,-0.33048f},{0.102957f,-0.628029f,-0.771349f},{-0.00816262f,-0.613874f,-0.789362f}, +{-0.893497f,-0.435569f,0.109282f},{0.0100863f,-0.0486995f,-0.998763f},{0.150525f,0.0452677f,-0.987569f}, +{0.0764546f,0.26098f,-0.962312f},{0.152391f,0.171026f,-0.97341f},{0.345637f,0.689084f,-0.636944f}, +{0.357883f,0.609575f,-0.707346f},{-0.100902f,0.519759f,-0.848333f},{-0.140263f,0.68451f,-0.715383f}, +{-0.10744f,0.788916f,-0.605035f},{0.394548f,0.75452f,-0.524434f},{0.388926f,0.688586f,-0.612034f}, +{-0.738403f,-0.340227f,-0.582242f},{-0.782643f,-0.254177f,-0.568211f},{0.247413f,-0.564007f,-0.787834f}, +{0.256883f,-0.117003f,-0.959334f},{-0.786296f,-0.0782728f,-0.612872f},{-0.707897f,-0.152897f,-0.689568f}, +{-0.723229f,0.022688f,-0.690235f},{-0.749203f,0.184557f,-0.636108f},{-0.701033f,0.199585f,-0.68463f}, +{-0.709307f,0.157231f,-0.687141f},{-0.696854f,0.196286f,-0.689831f},{-0.118736f,0.910259f,-0.396649f}, +{-0.175142f,0.912401f,-0.369931f},{-0.131566f,0.87213f,-0.471254f},{0.378213f,0.661221f,-0.647875f}, +{0.0959242f,0.458718f,-0.883389f},{0.383769f,0.604907f,-0.697716f},{0.349285f,0.490389f,-0.798447f}, +{0.329539f,0.53652f,-0.776885f},{-0.616684f,0.525528f,-0.586107f},{-0.422189f,0.264445f,-0.867079f}, +{-0.298524f,0.112001f,-0.947808f},{-0.306054f,0.243506f,-0.920345f},{-0.685699f,0.495668f,-0.533039f}, +{-0.657282f,0.676852f,-0.331438f},{-0.265994f,0.899443f,-0.346771f},{0.188719f,0.811568f,-0.552939f}, +{0.403312f,0.792154f,-0.458074f},{0.377123f,0.811736f,-0.44594f},{0.302501f,0.527583f,-0.79382f}, +{-0.0959399f,0.939362f,-0.329235f},{-0.772767f,-0.320831f,-0.547631f},{-0.690519f,0.217566f,-0.689818f}, +{-0.661912f,0.307201f,-0.683739f},{0.351612f,0.447101f,-0.822478f},{0.303686f,0.461482f,-0.833552f}, +{-0.147613f,0.832848f,-0.533455f},{0.312312f,0.449462f,-0.836926f},{-0.781602f,-0.335284f,-0.526007f}, +{-0.917526f,-0.0964975f,-0.385791f},{-0.838488f,-0.158383f,-0.521394f},{0.0f,-1.0f,0.0f}, +{-0.886577f,-0.14044f,-0.440747f},{-0.968258f,-0.0498201f,-0.244938f},{-0.655652f,0.450153f,-0.606203f}, +{-0.604448f,0.678246f,-0.41788f},{-0.14275f,-0.918332f,0.369173f},{0.0989465f,0.694789f,-0.712374f}, +{0.166933f,0.837658f,-0.52006f},{0.21055f,0.72595f,-0.654725f},{0.316211f,0.415471f,-0.852874f}, +{-0.547079f,-0.625644f,-0.556123f},{-0.6564f,-0.45908f,-0.598652f},{-0.620557f,-0.50989f,-0.595752f}, +{-0.334171f,0.525758f,-0.782246f},{-0.733579f,-0.147398f,-0.663427f},{-0.792315f,0.0928118f,-0.603012f}, +{-0.690414f,0.293632f,-0.661142f},{0.578691f,-0.376963f,-0.723198f},{-0.599053f,0.374824f,-0.707561f}, +{0.015523f,0.715745f,-0.698189f},{-0.107927f,0.774788f,-0.62294f},{-0.0441408f,0.723233f,-0.689192f}, +{0.407924f,0.684044f,-0.604716f},{-0.194453f,0.780287f,-0.594425f},{-0.114251f,0.723885f,-0.680394f}, +{0.555647f,0.406394f,-0.725328f},{0.518593f,0.360257f,-0.77542f},{0.520778f,0.303692f,-0.797848f}, +{0.240672f,0.416642f,-0.876633f},{0.0327207f,-0.998585f,-0.0419202f},{-0.0431421f,0.587874f,-0.807801f}, +{-0.766352f,0.134119f,-0.628264f},{-0.862351f,-0.0512968f,-0.503705f},{-0.888499f,-0.189282f,-0.418021f}, +{-0.804259f,-0.259012f,-0.534865f},{-0.645443f,-0.635757f,-0.42334f},{-0.698401f,-0.436983f,-0.566817f}, +{-0.765176f,-0.285133f,-0.577239f},{-0.830084f,-0.296906f,-0.472025f},{-0.795711f,-0.359287f,-0.487603f}, +{-0.743921f,-0.239702f,-0.623799f},{-0.780822f,-0.174298f,-0.599948f},{-0.811998f,-0.130463f,-0.568893f}, +{-0.879525f,0.0516754f,-0.473039f},{-0.820533f,0.14866f,-0.55193f},{-0.807281f,0.13951f,-0.573441f}, +{-0.712668f,0.21512f,-0.667704f},{-0.729673f,0.319276f,-0.604683f},{-0.696901f,0.37933f,-0.608636f}, +{-0.656775f,0.415176f,-0.629504f},{-0.691862f,0.261617f,-0.672966f},{-0.568101f,0.174111f,-0.80433f}, +{-0.112366f,0.606799f,-0.786873f},{-0.05266f,0.579328f,-0.813392f},{-0.1848f,0.629204f,-0.754951f}, +{-0.196257f,0.888754f,-0.414246f},{-0.240208f,0.903375f,-0.355264f},{0.544748f,0.342669f,-0.765393f}, +{0.385739f,0.3243f,-0.863733f},{0.457805f,0.216345f,-0.862328f},{0.384613f,0.32892f,-0.862487f}, +{0.0803373f,0.547268f,-0.833093f},{0.0454906f,0.620927f,-0.782547f},{-0.674372f,0.00883527f,-0.738339f}, +{-0.84339f,-0.19135f,-0.502075f},{-0.931514f,-0.058715f,-0.358935f},{-0.758433f,-0.616169f,-0.212403f}, +{-0.600183f,0.119047f,-0.790954f},{-0.365026f,0.727664f,-0.580743f},{-0.775646f,0.368886f,-0.51215f}, +{-0.665811f,0.318583f,-0.674686f},{-0.523593f,0.612358f,-0.592341f},{0.599859f,0.0667783f,-0.797314f}, +{-0.0627656f,0.904092f,-0.422703f},{-0.800314f,-0.47969f,-0.359714f},{-0.804728f,-0.352697f,-0.477513f}, +{-0.797558f,0.251342f,-0.548387f},{0.299336f,-0.933466f,0.197581f},{0.329159f,-0.935342f,0.129572f}, +{-0.219073f,0.903725f,-0.367817f},{-0.212267f,0.916372f,-0.339419f},{-0.131723f,0.689763f,-0.711952f}, +{-0.0554175f,0.330594f,-0.942145f},{0.0320061f,0.18098f,-0.982966f},{-0.280935f,0.879313f,-0.384558f}, +{-0.251187f,0.930229f,-0.267543f},{-0.292435f,0.903886f,-0.312204f},{-0.718851f,0.057869f,-0.692752f}, +{-0.982328f,0.12595f,-0.138447f},{0.786614f,0.464934f,-0.406294f},{-0.700668f,0.366514f,-0.612153f}, +{0.312493f,0.817967f,-0.48299f},{-0.601982f,-0.595711f,-0.531738f},{-0.489698f,-0.677372f,-0.548965f}, +{-0.811554f,-0.468049f,-0.349729f},{-0.761489f,0.347982f,-0.546848f},{-0.731756f,0.145687f,-0.665814f}, +{0.174515f,0.884687f,-0.432289f},{-0.114839f,0.792609f,-0.598818f},{0.112595f,0.652698f,-0.749205f}, +{0.767002f,0.399536f,-0.502075f},{0.15278f,0.810745f,-0.565111f},{-0.702188f,0.107794f,-0.703784f}, +{-0.633727f,0.299399f,-0.713267f},{-0.0571531f,-0.643154f,0.763601f},{-0.427399f,0.823243f,-0.373633f}, +{-0.47606f,0.788375f,-0.389657f},{-0.518836f,0.696978f,-0.495006f},{-0.761843f,0.502152f,-0.409193f}, +{-0.908978f,0.15949f,-0.385125f},{-0.917297f,0.0193351f,-0.397734f},{-0.926845f,-0.050602f,-0.372019f}, +{-0.927027f,-0.202271f,-0.315766f},{-0.590106f,-0.644232f,-0.48656f},{-0.603943f,-0.643506f,-0.470269f}, +{-0.580711f,-0.717758f,-0.384186f},{-0.677655f,-0.7017f,-0.220003f},{-0.804464f,-0.533413f,-0.261358f}, +{-0.864175f,-0.356112f,-0.355508f},{-0.851693f,-0.371734f,-0.369368f},{-0.854062f,-0.407542f,-0.323244f}, +{-0.851561f,-0.394576f,-0.345188f},{-0.877129f,-0.167053f,-0.450265f},{-0.918302f,-0.0351973f,-0.394312f}, +{-0.927236f,-0.0126858f,-0.374262f},{-0.95147f,0.0250336f,-0.306721f},{-0.915624f,0.0651938f,-0.396715f}, +{-0.876806f,0.15456f,-0.455326f},{-0.885586f,0.147487f,-0.440438f},{-0.883614f,0.193033f,-0.426574f}, +{-0.825263f,0.319221f,-0.465875f},{-0.790863f,0.32737f,-0.517074f},{-0.709898f,0.0806033f,-0.699677f}, +{-0.0398124f,0.680137f,-0.732003f},{0.584153f,0.293525f,-0.756708f},{0.618851f,0.395068f,-0.678929f}, +{-0.767229f,0.225208f,-0.600534f},{-0.77519f,0.253299f,-0.578723f},{-0.682595f,0.190452f,-0.705544f}, +{-0.776026f,0.168611f,-0.607745f},{-0.859731f,0.371183f,-0.350837f},{-0.673324f,0.285739f,-0.6819f}, +{-0.727279f,0.537275f,-0.427084f},{-0.735501f,0.339622f,-0.586256f},{-0.716775f,0.674215f,-0.177957f}, +{-0.74042f,0.661547f,-0.118885f},{-0.43986f,0.804958f,-0.398203f},{-0.940599f,0.0319039f,-0.338018f}, +{-0.581107f,-0.717514f,-0.384043f},{-0.606471f,-0.659641f,-0.443923f},{-0.913605f,-0.148399f,-0.378555f}, +{-0.933232f,0.0702974f,-0.352329f},{-0.834932f,0.33923f,-0.433372f},{-0.738723f,0.209806f,-0.640523f}, +{-0.755023f,0.427757f,-0.496955f},{-0.724755f,0.681174f,-0.103601f},{-0.489712f,0.813523f,-0.313628f}, +{-0.868385f,-0.320696f,-0.378234f},{-0.899023f,0.150755f,-0.411134f},{-0.620953f,-0.356866f,-0.6979f}, +{-0.736245f,0.329334f,-0.591171f},{-0.752901f,0.257652f,-0.605604f},{-0.758671f,0.506889f,-0.409246f}, +{-0.670159f,0.564671f,-0.481698f},{-0.41136f,0.798592f,-0.439357f},{-0.712829f,0.660545f,-0.235702f}, +{-0.955273f,0.267109f,-0.126908f},{-0.993407f,0.0231793f,-0.11227f},{-0.933768f,-0.285835f,-0.215351f}, +{-0.522153f,-0.80213f,-0.28973f},{-0.591668f,-0.703124f,-0.394392f},{-0.686102f,-0.632814f,-0.358901f}, +{-0.674643f,-0.686631f,-0.270916f},{-0.751492f,-0.557023f,-0.353533f},{-0.851592f,-0.408749f,-0.328199f}, +{-0.874671f,-0.330786f,-0.354304f},{-0.889016f,-0.355231f,-0.288896f},{-0.808405f,-0.536273f,-0.242677f}, +{-0.865771f,-0.41462f,-0.280233f},{-0.919658f,-0.178283f,-0.349921f},{-0.929822f,0.0642691f,-0.362355f}, +{-0.955185f,0.0169424f,-0.295525f},{-0.930455f,0.000111387f,-0.366407f},{-0.91058f,0.167131f,-0.378036f}, +{-0.920372f,0.185707f,-0.344133f},{-0.899459f,0.2174f,-0.379093f},{-0.884971f,0.276707f,-0.374512f}, +{-0.864626f,0.258211f,-0.430985f},{-0.798431f,0.0357707f,-0.601023f},{-0.706052f,-0.322651f,-0.630387f}, +{-0.645647f,-0.42174f,-0.636612f},{-0.686848f,-0.423865f,-0.590405f},{-0.762974f,-0.311095f,-0.566648f}, +{-0.881004f,-0.0543548f,-0.469976f},{-0.878294f,0.0736529f,-0.472414f},{-0.855078f,0.167816f,-0.49059f}, +{-0.795931f,0.234467f,-0.558139f},{-0.731186f,0.272107f,-0.62556f},{-0.691631f,0.234792f,-0.683022f}, +{-0.652312f,0.437466f,-0.618961f},{-0.736543f,0.354978f,-0.575756f},{-0.841663f,0.118126f,-0.526925f}, +{-0.821913f,0.158685f,-0.547063f},{-0.766839f,0.226806f,-0.60043f},{-0.700081f,0.449037f,-0.555205f}, +{-0.633108f,0.633201f,-0.445232f},{-0.701542f,0.248827f,-0.667775f},{-0.756746f,0.56066f,-0.336147f}, +{-0.629406f,0.77327f,0.0768261f},{-0.652054f,-0.665617f,-0.363016f},{-0.850827f,-0.17411f,-0.495762f}, +{-0.92283f,0.0165544f,-0.384853f},{-0.92248f,0.063835f,-0.380731f},{-0.900127f,0.114247f,-0.420379f}, +{-0.782641f,0.243676f,-0.572796f},{-0.77963f,0.230663f,-0.582213f},{-0.845516f,0.11178f,-0.522119f}, +{-0.709714f,0.466652f,-0.527772f},{-0.400168f,0.870759f,-0.285735f},{-0.292425f,0.156734f,-0.943357f}, +{-0.735463f,-0.631546f,-0.245446f},{-0.797337f,-0.491385f,-0.350419f},{-0.908647f,0.250044f,-0.334423f}, +{-0.671733f,-0.467832f,-0.574377f},{-0.707944f,0.390645f,-0.588398f},{-0.807272f,0.194343f,-0.557264f}, +{-0.610848f,-0.732514f,-0.30048f},{-0.674118f,-0.457605f,-0.579795f},{0.451244f,0.0484673f,-0.891083f}, +{0.473483f,0.0961672f,-0.875538f},{-0.641274f,-0.741973f,-0.195559f},{-0.496208f,0.338717f,-0.799405f}, +{-0.461432f,0.358428f,-0.811548f},{0.566993f,0.562716f,-0.601556f},{-0.016211f,0.337597f,-0.941151f}, +{0.0142408f,0.296582f,-0.954901f},{0.152933f,0.162301f,-0.974818f},{0.460267f,0.760721f,-0.457665f}, +{0.266763f,-0.4287f,0.863165f},{-0.229672f,0.792f,-0.565674f},{-0.183382f,0.922747f,-0.338982f}, +{-0.0423757f,0.683786f,-0.728451f},{-0.078403f,0.92181f,-0.379631f},{-0.175821f,0.91269f,-0.368895f}, +{0.0319953f,0.763247f,-0.645315f},{0.702159f,0.515647f,-0.491f},{0.773998f,0.507949f,-0.378041f}, +{0.700035f,0.505806f,-0.504094f},{0.488668f,0.329755f,-0.807754f},{0.494024f,0.59918f,-0.630019f}, +{0.553235f,0.493178f,-0.671347f},{0.416349f,0.563334f,-0.713658f},{0.631496f,0.041896f,-0.774246f}, +{-0.164784f,0.487377f,-0.857502f},{-0.0916461f,0.350752f,-0.931973f},{-0.123292f,0.333143f,-0.93478f}, +{0.432491f,0.372184f,-0.821237f},{0.371927f,0.337693f,-0.864658f},{0.366353f,0.303488f,-0.879591f}, +{0.301134f,0.453315f,-0.838942f},{0.338526f,0.404715f,-0.849474f},{0.335097f,0.376204f,-0.863818f}, +{0.418205f,0.224577f,-0.880153f},{0.301306f,0.574076f,-0.761349f},{0.242002f,0.593726f,-0.767414f}, +{0.282472f,0.61884f,-0.732971f},{0.0259057f,0.734429f,-0.678191f},{0.289283f,0.717467f,-0.633684f}, +{0.000842052f,0.572893f,-0.819629f},{0.611392f,0.270866f,-0.743527f},{0.38074f,0.252503f,-0.889539f}, +{0.465128f,0.413081f,-0.782956f},{0.171625f,0.243028f,-0.954716f},{0.125825f,0.386253f,-0.91377f}, +{0.0129325f,0.398473f,-0.917089f},{0.0361355f,0.47008f,-0.881884f},{0.341164f,-0.747721f,0.569667f}, +{0.200665f,-0.688634f,0.69679f},{0.235842f,0.431982f,-0.8705f},{0.636529f,0.504031f,-0.583766f}, +{0.608404f,0.487187f,-0.626493f},{0.228318f,0.295752f,-0.927578f},{-0.363634f,0.203676f,-0.909003f}, +{0.588486f,0.667092f,-0.456807f},{0.581279f,0.615243f,-0.532532f},{-0.0425343f,0.776099f,-0.629175f}, +{-0.033611f,0.737196f,-0.674843f},{0.451443f,0.142728f,-0.880811f},{0.0148181f,-0.998955f,-0.0432437f}, +{0.430652f,0.505955f,-0.747361f},{0.663306f,0.015291f,-0.748192f},{0.381259f,0.835846f,-0.394973f}, +{0.319437f,0.838482f,-0.441485f},{0.478502f,0.508641f,-0.715765f},{0.343964f,0.541458f,-0.767145f}, +{0.0565948f,-0.83426f,-0.548459f},{-0.2399f,0.915786f,-0.322155f},{-0.17528f,0.938477f,-0.297554f}, +{-0.345293f,0.876527f,-0.335369f},{0.155107f,-0.700209f,0.696886f},{0.518676f,0.656536f,-0.547664f}, +{0.540738f,0.696976f,-0.470985f},{-0.530286f,0.670052f,-0.519449f},{0.173892f,0.450541f,-0.875657f}, +{0.264071f,0.476953f,-0.838321f},{0.242757f,0.218363f,-0.945191f},{0.166356f,-0.768676f,0.617627f}, +{-0.113909f,0.326739f,-0.938225f},{0.484652f,0.733872f,-0.475967f},{0.25989f,0.909259f,-0.325124f}, +{-0.13513f,0.367255f,-0.920252f},{-0.0836041f,0.28648f,-0.954431f},{0.694915f,0.407838f,-0.592251f}, +{0.755633f,0.298784f,-0.582878f},{0.183817f,0.688876f,-0.701185f},{0.329009f,0.56222f,-0.758724f}, +{0.450194f,0.417842f,-0.789135f},{0.426558f,0.624877f,-0.653894f},{0.474707f,0.334435f,-0.814129f}, +{-0.368151f,0.702815f,-0.6087f},{0.441703f,0.751622f,-0.48986f},{0.324125f,0.385029f,-0.864116f}, +{0.553038f,0.362224f,-0.750296f},{0.592331f,0.191065f,-0.782712f},{0.545859f,0.282084f,-0.788965f}, +{0.643405f,0.153596f,-0.749959f},{0.603579f,0.440697f,-0.664439f},{0.561321f,0.45505f,-0.691266f}, +{0.274957f,0.426057f,-0.861901f},{0.51976f,0.49844f,-0.693835f},{0.492771f,0.292664f,-0.819466f}, +{0.475031f,0.455529f,-0.752887f},{0.447749f,0.310442f,-0.838538f},{0.60608f,0.361225f,-0.708649f}, +{0.671187f,0.621191f,-0.404513f},{0.499191f,0.431184f,-0.751591f},{0.496577f,0.735805f,-0.460437f}, +{-0.182961f,0.799778f,-0.571734f},{-0.148508f,0.75117f,-0.643187f},{-0.125094f,0.69274f,-0.710255f}, +{-0.0456011f,0.630923f,-0.774504f},{0.515427f,0.473627f,-0.714151f},{0.325487f,0.479803f,-0.814768f}, +{0.0854529f,0.600774f,-0.794838f},{0.397275f,0.715379f,-0.574809f},{0.723071f,0.581009f,-0.373626f}, +{0.538154f,0.430644f,-0.724524f},{-0.318307f,0.836747f,-0.445572f},{0.309693f,0.443584f,-0.841025f}, +{0.609157f,0.175619f,-0.77336f},{0.0230232f,0.685541f,-0.72767f},{0.574796f,0.145307f,-0.805292f}, +{0.0529214f,0.706485f,-0.705747f},{0.462671f,0.340719f,-0.818441f},{0.292814f,0.517741f,-0.803868f}, +{-0.23533f,0.822868f,-0.517212f},{0.108122f,0.103404f,-0.988745f},{0.707184f,0.394909f,-0.586463f}, +{0.529927f,0.343312f,-0.775445f},{0.0648392f,0.783754f,-0.617677f},{0.02647f,0.524744f,-0.850848f}, +{-0.045521f,0.7608f,-0.647388f},{0.118751f,0.88849f,-0.443264f},{-0.0161997f,0.457597f,-0.889012f}, +{0.000280184f,0.398345f,-0.917235f},{0.0561452f,0.767709f,-0.638334f},{-0.624146f,-0.0435425f,-0.780093f}, +{0.231384f,-0.0713779f,-0.970241f},{0.403915f,0.224348f,-0.88686f},{0.210782f,0.871545f,-0.442697f}, +{0.239799f,0.817561f,-0.523536f},{0.449956f,0.539841f,-0.711415f},{0.777722f,0.411413f,-0.475276f}, +{-0.0354915f,0.335593f,-0.941338f},{-0.568895f,-0.812661f,-0.126259f},{0.191192f,0.806213f,-0.55988f}, +{-0.0525749f,0.804373f,-0.591794f},{-0.00818761f,0.759069f,-0.650959f},{0.0183587f,-0.996702f,0.0790466f}, +{0.798654f,0.469085f,-0.376976f},{0.00703419f,0.455561f,-0.890177f},{-0.379388f,0.711838f,-0.591059f}, +{-0.356592f,0.682883f,-0.637584f},{0.159572f,0.698134f,-0.697958f},{-0.0127765f,0.446995f,-0.894445f}, +{0.429407f,0.375184f,-0.82149f},{0.375785f,0.710967f,-0.5944f},{-0.353617f,0.82554f,-0.439818f}, +{-0.29696f,0.869412f,-0.39489f},{-0.206536f,0.683309f,-0.700309f},{0.0821383f,0.485265f,-0.870501f}, +{0.0984004f,0.402228f,-0.910236f},{0.516561f,0.229612f,-0.82489f},{0.550054f,0.0979327f,-0.829367f}, +{0.385639f,-0.695462f,0.606312f},{-0.366341f,0.482633f,-0.795525f},{-0.402403f,0.371983f,-0.836481f}, +{0.155905f,0.37037f,-0.915707f},{0.349217f,0.269209f,-0.897538f},{0.0164691f,-0.998683f,0.0485895f}, +{0.598748f,0.120536f,-0.791815f},{0.610598f,0.110001f,-0.784264f},{-0.514242f,0.289975f,-0.807137f}, +{-0.0559095f,0.147385f,-0.987498f},{-0.318715f,0.0545386f,-0.94628f},{0.504873f,0.254761f,-0.824742f}, +{0.208381f,0.0932082f,-0.973596f},{0.387806f,0.408758f,-0.82615f},{0.433037f,0.482513f,-0.761354f}, +{0.290996f,0.751733f,-0.591793f},{0.447236f,0.828435f,-0.337157f},{0.00941225f,0.76729f,-0.641231f}, +{-0.394672f,0.547728f,-0.737718f},{0.0124283f,0.71336f,-0.700687f},{-0.00554958f,0.640843f,-0.767652f}, +{-0.0250379f,0.60922f,-0.792606f},{-0.355539f,0.551191f,-0.754838f},{-0.0722814f,0.578202f,-0.812685f}, +{0.567135f,0.265433f,-0.779682f},{0.0651969f,0.379966f,-0.9227f},{-0.690634f,-0.632789f,-0.350148f}, +{-0.588416f,-0.793163f,-0.157031f},{-0.61951f,-0.714354f,-0.325432f},{0.26334f,0.909731f,-0.321001f}, +{0.162899f,0.168777f,-0.9721f},{-0.250455f,-0.579707f,-0.775379f},{0.0868959f,0.251874f,-0.963851f}, +{-0.00789398f,0.248566f,-0.968583f},{0.169066f,-0.886262f,-0.431227f},{0.230861f,0.171183f,-0.95781f}, +{-0.326545f,0.302039f,-0.895623f},{0.390293f,0.218435f,-0.894403f},{0.46466f,0.36971f,-0.804615f}, +{0.395036f,0.206159f,-0.895234f},{0.355156f,0.127125f,-0.926123f},{0.426136f,0.16671f,-0.889166f}, +{0.227801f,-0.666285f,0.71005f},{-0.143781f,0.574938f,-0.805465f},{-0.17141f,0.442747f,-0.88011f}, +{-0.267554f,0.646156f,-0.714771f},{-0.303341f,0.558085f,-0.772351f},{-0.291798f,0.614407f,-0.733047f}, +{-0.273007f,0.547826f,-0.790793f},{0.492345f,0.234013f,-0.838352f},{-0.077035f,0.628769f,-0.773767f}, +{-0.218071f,0.684619f,-0.695515f},{-0.244835f,0.698392f,-0.672535f},{-0.22162f,0.646343f,-0.730154f}, +{-0.214732f,0.54545f,-0.810169f},{-0.268715f,0.470151f,-0.840684f},{0.0603743f,-0.691798f,-0.719563f}, +{0.606051f,0.3876f,-0.694599f},{0.265445f,0.352554f,-0.897354f},{-0.153875f,0.507607f,-0.847737f}, +{-0.0218791f,0.2504f,-0.967895f},{0.551176f,0.495749f,-0.671147f},{0.611981f,0.287921f,-0.736601f}, +{0.256431f,0.513728f,-0.818735f},{0.0377112f,-0.996204f,0.078455f},{0.303029f,0.247999f,-0.920147f}, +{0.111975f,0.673271f,-0.730868f},{0.171041f,0.507994f,-0.844208f},{0.212217f,0.634694f,-0.743053f}, +{0.064679f,0.308196f,-0.949122f},{0.137869f,0.696416f,-0.704271f},{-0.132098f,0.735681f,-0.664322f}, +{-0.265268f,0.687997f,-0.675495f},{-0.177326f,0.583504f,-0.792514f},{-0.159021f,0.5532f,-0.81773f}, +{-0.0648649f,0.494838f,-0.866561f},{0.742841f,0.289633f,-0.603574f},{-0.244684f,0.734364f,-0.633119f}, +{-0.230733f,0.626359f,-0.744605f},{-0.0475614f,0.476977f,-0.877628f},{0.806145f,0.328443f,-0.492195f}, +{0.553227f,0.516329f,-0.653716f},{-0.157719f,0.757436f,-0.633573f},{-0.115361f,0.701471f,-0.7033f}, +{-0.121287f,0.647932f,-0.751979f},{-0.119817f,0.518635f,-0.846559f},{0.686161f,0.434175f,-0.583674f}, +{0.414612f,0.725276f,-0.549611f},{0.0854752f,0.862823f,-0.498227f},{-0.12717f,0.823634f,-0.55268f}, +{-0.039744f,0.417788f,-0.907675f},{0.058142f,0.464484f,-0.883671f},{0.331886f,0.247708f,-0.910216f}, +{0.385355f,0.193432f,-0.902267f},{-0.0793643f,0.273121f,-0.9587f},{0.0536832f,0.505359f,-0.861238f}, +{-0.615467f,0.0831187f,-0.783768f},{0.313339f,0.713027f,-0.627226f},{0.689499f,0.582774f,-0.430075f}, +{-0.128185f,0.762866f,-0.633723f},{0.0196996f,0.887205f,-0.460954f},{-0.00598953f,0.836441f,-0.548024f}, +{-0.0213968f,0.757067f,-0.652987f},{-0.00143753f,0.690355f,-0.72347f},{-0.0019361f,0.639495f,-0.768793f}, +{-0.00192568f,0.582949f,-0.812506f},{0.0273509f,0.473471f,-0.880385f},{-0.02277f,0.363068f,-0.931484f}, +{0.0540861f,-0.998258f,0.0235792f},{0.0645095f,0.441878f,-0.894753f},{0.0700004f,0.944914f,-0.319746f}, +{0.0117661f,0.405937f,-0.913825f},{0.0176947f,0.515517f,-0.856697f},{0.680651f,0.415756f,-0.60321f}, +{0.130425f,-0.961724f,0.240989f},{0.0967715f,-0.959725f,0.263748f},{0.0194044f,-0.999714f,-0.0139699f}, +{0.133409f,0.926465f,-0.351945f},{0.117102f,0.858963f,-0.498468f},{0.103475f,0.763646f,-0.637289f}, +{0.111402f,0.690044f,-0.715143f},{0.101804f,0.595361f,-0.796982f},{0.128685f,0.522187f,-0.843067f}, +{0.0402739f,0.41697f,-0.908027f},{0.399144f,-0.91055f,-0.107621f},{-0.435934f,-0.825475f,-0.358542f}, +{-0.109148f,0.471589f,-0.875037f},{-0.0636207f,0.541546f,-0.83826f},{0.16092f,0.918152f,-0.362079f}, +{0.132701f,0.495073f,-0.858657f},{-0.45753f,0.383971f,-0.802018f},{0.599068f,0.532026f,-0.598386f}, +{0.18527f,0.936653f,-0.297248f},{0.206933f,0.904728f,-0.372352f},{0.249515f,0.825249f,-0.506662f}, +{0.271295f,0.741368f,-0.613818f},{0.310646f,0.614839f,-0.724894f},{0.241053f,0.565042f,-0.789063f}, +{0.241853f,0.535239f,-0.809337f},{0.176168f,0.543811f,-0.820509f},{0.0455045f,0.57979f,-0.813494f}, +{-0.0771866f,0.507401f,-0.858246f},{0.440673f,-0.660931f,-0.607436f},{0.500526f,0.292855f,-0.814684f}, +{0.0414138f,-0.998864f,0.0235917f},{-0.0509805f,-0.997048f,0.0574192f},{-0.415864f,0.575909f,-0.703836f}, +{0.338532f,0.763192f,-0.550395f},{0.35893f,0.71351f,-0.601724f},{0.385059f,0.664926f,-0.640003f}, +{0.328951f,0.645277f,-0.689499f},{0.221126f,0.632886f,-0.741996f},{0.108873f,0.668475f,-0.735723f}, +{0.0340051f,0.683036f,-0.729593f},{-0.00556542f,0.544468f,-0.838763f},{-0.00862487f,-0.999916f,-0.00968689f}, +{-0.522209f,-0.433801f,-0.734244f},{-0.171697f,-0.631512f,-0.756117f},{0.133674f,0.312652f,-0.940415f}, +{-0.497572f,0.211301f,-0.841293f},{-0.362949f,0.167034f,-0.916716f},{0.0326084f,-0.00788205f,-0.999437f}, +{0.0178542f,-0.0277267f,-0.999456f},{-0.47475f,0.365741f,-0.800529f},{-0.473612f,0.341419f,-0.811865f}, +{-0.461667f,0.388046f,-0.797674f},{-0.424027f,0.360027f,-0.831012f},{-0.432038f,0.35578f,-0.828712f}, +{-0.431902f,0.351066f,-0.830791f},{-0.456517f,0.110316f,-0.882849f},{-0.361814f,0.114788f,-0.925156f}, +{-0.228224f,-0.316424f,-0.920755f},{-0.148414f,-0.339823f,-0.928706f},{-0.261238f,-0.215537f,-0.940903f}, +{-0.328999f,-0.10573f,-0.938393f},{-0.451714f,0.350036f,-0.820627f},{-0.347968f,0.0215898f,-0.937258f}, +{-0.309485f,-0.0149301f,-0.950787f},{0.463137f,0.067709f,-0.883696f},{-0.241012f,-0.400193f,-0.884171f}, +{-0.0357501f,-0.449568f,-0.892531f},{-0.0140368f,-0.988301f,-0.151869f},{0.0435599f,-0.988519f,-0.144679f}, +{-0.534633f,0.0470525f,-0.843773f},{-0.478002f,-0.0821379f,-0.87451f},{-0.401819f,0.0454912f,-0.914588f}, +{-0.357866f,-0.0533086f,-0.93225f},{-0.451596f,0.267947f,-0.851038f},{-0.360766f,0.261536f,-0.895236f}, +{0.410366f,-0.166006f,-0.896684f},{-0.513599f,0.162015f,-0.842596f},{0.3566f,-0.711176f,0.605859f}, +{-0.120089f,0.322861f,-0.938797f},{-0.248431f,0.441079f,-0.862399f},{-0.175283f,0.367227f,-0.913466f}, +{-0.450494f,0.0551368f,-0.891075f},{-0.42014f,0.147097f,-0.895458f},{-0.557979f,0.0600499f,-0.82768f}, +{0.366826f,-0.521694f,-0.770243f},{0.365232f,-0.318742f,-0.874648f},{0.363532f,-0.145989f,-0.920072f}, +{0.20774f,0.0420311f,-0.977281f},{0.0751624f,0.217239f,-0.97322f},{-0.321394f,-0.326397f,-0.888915f}, +{-0.393182f,-0.385769f,-0.83462f},{-0.392275f,-0.212391f,-0.894992f},{-0.406007f,-0.534047f,-0.741588f}, +{0.315182f,-0.194353f,-0.928917f},{-0.0685202f,-0.992297f,-0.103202f},{-0.0284892f,0.181927f,-0.982899f}, +{-0.53002f,-0.387743f,-0.754145f},{0.357128f,-0.656742f,-0.664191f},{0.276723f,-0.451058f,-0.848511f}, +{0.175629f,-0.170525f,-0.969575f},{0.0425135f,0.164236f,-0.985505f},{-0.0143207f,0.317665f,-0.948095f}, +{0.0122568f,0.405787f,-0.913885f},{-0.47831f,-0.644036f,-0.597024f},{-0.475634f,-0.613191f,-0.63069f}, +{-0.348139f,-0.163224f,-0.923124f},{-0.411986f,-0.109838f,-0.904546f},{-0.581602f,-0.567347f,-0.582972f}, +{-0.517116f,-0.623338f,-0.586551f},{-0.105834f,-0.0441267f,-0.993404f},{0.0528876f,-0.29564f,-0.953834f}, +{-0.50201f,-0.715353f,-0.486061f},{-0.461784f,-0.701117f,-0.543314f},{-0.626786f,0.0544235f,-0.777288f}, +{-0.752071f,0.129508f,-0.646232f},{-0.263653f,-0.0708322f,-0.962014f},{0.438893f,-0.660336f,-0.609368f}, +{0.217609f,-0.414783f,-0.883516f},{0.147652f,-0.530349f,-0.834822f},{-0.338162f,0.160716f,-0.927263f}, +{0.307363f,0.120576f,-0.943922f},{0.0490676f,0.223205f,-0.973536f},{-0.403132f,0.0502257f,-0.913763f}, +{-0.404395f,0.167715f,-0.899075f},{-0.273011f,0.356315f,-0.893591f},{0.00575582f,0.31997f,-0.94741f}, +{-0.154191f,0.0769798f,-0.985038f},{-0.0460386f,-0.00195168f,-0.998938f},{-0.417116f,-0.198723f,-0.886862f}, +{-0.529885f,-0.00125872f,-0.848068f},{-0.196126f,-0.974976f,0.104673f},{0.287061f,-0.528227f,-0.799107f}, +{0.162375f,-0.468503f,-0.868412f},{0.11783f,-0.339373f,-0.933243f},{0.00841994f,-0.0591842f,-0.998212f}, +{0.0947982f,0.174364f,-0.980107f},{0.351317f,0.0632065f,-0.93412f},{0.024536f,0.157757f,-0.987173f}, +{0.10294f,-0.457501f,-0.88323f},{0.122128f,-0.417088f,-0.900623f},{0.102582f,-0.465494f,-0.879086f}, +{0.167471f,-0.388124f,-0.906263f},{-0.0613336f,0.143051f,-0.987813f},{-0.29684f,0.289434f,-0.910008f}, +{-0.152998f,-0.0536475f,-0.986769f},{-0.519057f,-0.225463f,-0.824467f},{-0.145503f,0.163285f,-0.97579f}, +{-0.499829f,-0.705746f,-0.50209f},{-0.0914336f,-0.843831f,0.528762f},{0.0807299f,-0.328036f,-0.94121f}, +{-0.806231f,0.368991f,-0.462426f},{0.330734f,-0.580551f,-0.744027f},{0.583488f,0.141328f,-0.79973f}, +{-0.320216f,0.320748f,-0.891394f},{-0.412065f,0.356592f,-0.838478f},{-0.491405f,0.167616f,-0.854649f}, +{0.199711f,-0.705233f,-0.680266f},{0.146874f,-0.613057f,-0.776267f},{0.227035f,-0.450278f,-0.863542f}, +{0.122738f,-0.209788f,-0.970012f},{0.112813f,-0.0401108f,-0.992806f},{-0.203394f,-0.724243f,-0.658865f}, +{0.0196344f,-0.138169f,-0.990214f},{0.0655215f,-0.302574f,-0.950871f},{-0.0604679f,-0.00683363f,-0.998147f}, +{-0.221203f,-0.308623f,-0.925106f},{-0.226549f,-0.188684f,-0.955549f},{-0.433699f,0.367571f,-0.822677f}, +{0.191396f,-0.331647f,-0.923784f},{0.172897f,-0.271091f,-0.946898f},{-0.214423f,0.265942f,-0.939839f}, +{-0.172306f,0.202648f,-0.963973f},{-0.235133f,0.261565f,-0.936107f},{-0.159272f,0.192711f,-0.968243f}, +{-0.366161f,0.368952f,-0.854284f},{-0.416712f,0.28621f,-0.862806f},{-0.322311f,0.235694f,-0.916823f}, +{-0.108791f,0.0573097f,-0.992411f},{-0.23577f,-0.0337679f,-0.971222f},{-0.106879f,-0.064881f,-0.992153f}, +{-0.02569f,-0.335202f,-0.941796f},{-0.577113f,0.0728067f,-0.813412f},{-0.46981f,-0.468811f,-0.747994f}, +{-0.0801707f,-0.0791455f,-0.993634f},{-0.0991288f,-0.0698804f,-0.992618f},{0.166914f,-0.585763f,-0.793109f}, +{0.124322f,-0.458685f,-0.879859f},{0.0818947f,-0.254017f,-0.963726f},{0.203168f,-0.284487f,-0.936905f}, +{-0.378567f,0.271463f,-0.88487f},{-0.415195f,-0.628034f,-0.658169f},{-0.332351f,-0.620089f,-0.710656f}, +{-0.254031f,-0.712061f,-0.654551f},{-0.376504f,-0.711748f,-0.593009f},{-0.37075f,-0.39528f,-0.840416f}, +{0.0194336f,-0.0314723f,-0.999316f},{-0.301979f,-0.494561f,-0.814996f},{-0.294411f,-0.766308f,-0.571046f}, +{-0.106275f,0.131253f,-0.985636f},{0.0212621f,0.103805f,-0.99437f},{-0.0399005f,0.257478f,-0.96546f}, +{-0.113825f,0.251492f,-0.961143f},{-0.176062f,-0.583995f,-0.792434f},{-0.427957f,0.302497f,-0.851674f}, +{-0.289149f,-0.76017f,-0.581838f},{-0.366384f,-0.718341f,-0.591396f},{-0.546571f,-0.25365f,-0.798074f}, +{-0.39526f,-0.289349f,-0.871807f},{-0.403287f,-0.227755f,-0.886277f},{-0.489823f,-0.266145f,-0.830205f}, +{0.480623f,0.0180865f,-0.876741f},{0.00374673f,0.181041f,-0.983468f},{-0.38202f,-0.586857f,-0.713905f}, +{0.158242f,-0.536317f,-0.829049f},{0.0826999f,-0.411103f,-0.90783f},{0.233891f,-0.411181f,-0.881037f}, +{-0.254003f,-0.718621f,-0.647353f},{-0.0677313f,-0.248287f,-0.966316f},{-0.355975f,-0.0219649f,-0.934237f}, +{-0.244444f,-0.773455f,-0.58482f},{0.0347758f,0.0843179f,-0.995832f},{-0.0978183f,-0.384104f,-0.918094f}, +{0.0148392f,-0.349137f,-0.936954f},{-0.234185f,-0.31982f,-0.918081f},{-0.296005f,-0.312823f,-0.902509f}, +{-0.149664f,-0.762597f,-0.629322f},{0.298053f,-0.548791f,-0.78102f},{0.0532691f,0.187605f,-0.980799f}, +{-0.033569f,-0.249132f,-0.967888f},{-0.389285f,-0.307134f,-0.868404f},{-0.314508f,-0.334821f,-0.888245f}, +{-0.262679f,-0.400729f,-0.877733f},{-0.211967f,-0.335246f,-0.917976f},{-0.0722862f,-0.746754f,-0.661161f}, +{-0.209982f,-0.10056f,-0.97252f},{-0.61991f,-0.237285f,-0.747935f},{-0.486362f,-0.715322f,-0.501763f}, +{0.591243f,0.0442269f,-0.80528f},{-0.1822f,-0.425288f,-0.886529f},{-0.12628f,-0.280556f,-0.951494f}, +{-0.293787f,-0.25386f,-0.921544f},{-0.45819f,-0.694794f,-0.554367f},{-0.265962f,-0.2459f,-0.932093f}, +{0.552218f,-0.296904f,-0.77904f},{0.616997f,-0.208742f,-0.758776f},{0.532225f,-0.0320237f,-0.845997f}, +{0.518351f,0.0671195f,-0.85253f},{0.0783362f,-0.99421f,0.0735581f},{0.118141f,-0.991686f,0.051016f}, +{-0.162888f,-0.271886f,-0.948444f},{-0.293131f,0.331585f,-0.896731f},{0.598523f,-0.060331f,-0.798831f}, +{-0.309627f,-0.348615f,-0.884646f},{0.466335f,-0.598079f,-0.651792f},{0.663968f,-0.130472f,-0.73629f}, +{-0.426057f,-0.246834f,-0.870373f},{-0.320111f,-0.413622f,-0.852318f},{-0.275072f,-0.289065f,-0.916939f}, +{-0.121374f,-0.00139793f,-0.992606f},{-0.104278f,0.0295214f,-0.99411f},{-0.441475f,0.425385f,-0.79003f}, +{-0.354671f,-0.736701f,0.575743f},{-0.307603f,-0.735628f,0.603517f},{-0.14795f,-0.963575f,0.222786f}, +{-0.247067f,-0.209033f,-0.946184f},{0.478306f,-0.542814f,-0.690346f},{0.573596f,-0.413039f,-0.70738f}, +{0.0413827f,0.111407f,-0.992913f},{0.697966f,-0.174554f,-0.694531f},{0.0884714f,-0.995767f,-0.0249182f}, +{0.66496f,-0.0925447f,-0.741123f},{0.530716f,0.0144358f,-0.847427f},{0.424264f,0.0554731f,-0.903838f}, +{0.43501f,0.059857f,-0.898434f},{-0.0618874f,0.058737f,-0.996353f},{-0.153908f,0.138131f,-0.978382f}, +{-0.433835f,-0.75467f,-0.492201f},{0.311733f,0.130743f,-0.941132f},{0.430592f,-0.545578f,-0.718982f}, +{0.563177f,-0.165806f,-0.809531f},{0.528658f,-0.0254374f,-0.848454f},{0.463703f,0.116946f,-0.878239f}, +{0.408836f,0.0773136f,-0.909327f},{0.432356f,-0.362167f,-0.825774f},{-0.0226638f,-0.365996f,-0.93034f}, +{-0.0502062f,-0.277693f,-0.959357f},{-0.0211832f,-0.252399f,-0.967391f},{-0.500045f,-0.0644449f,-0.863598f}, +{-0.551538f,-0.0144755f,-0.834024f},{-0.34341f,-0.0575228f,-0.937423f},{-0.312247f,-0.0818833f,-0.946465f}, +{-0.182016f,-0.0683459f,-0.980918f},{-0.613054f,-0.35332f,-0.706633f},{-0.169602f,0.137656f,-0.975851f}, +{0.501381f,-0.187936f,-0.844569f},{0.410831f,0.034876f,-0.911044f},{0.556872f,-0.242513f,-0.794406f}, +{0.430962f,0.0348824f,-0.901696f},{0.466346f,0.0318584f,-0.884029f},{0.476106f,0.0373906f,-0.878593f}, +{-0.384256f,-0.333572f,0.860858f},{0.409459f,-0.402863f,-0.818563f},{0.347749f,-0.57205f,-0.742852f}, +{0.3489f,-0.310127f,-0.884359f},{0.330387f,0.0285511f,-0.943413f},{0.386763f,0.0233366f,-0.921884f}, +{0.434189f,-0.216166f,-0.874501f},{0.403344f,-0.155649f,-0.901713f},{0.397898f,-0.0907694f,-0.912928f}, +{0.444065f,0.110133f,-0.8892f},{0.436053f,0.102098f,-0.89411f},{0.450432f,0.0181053f,-0.892627f}, +{0.459103f,0.0236729f,-0.888068f},{-0.400662f,-0.908358f,-0.119813f},{-0.25029f,0.0413912f,-0.967286f}, +{-0.285704f,0.0674397f,-0.955942f},{-0.250687f,0.0544366f,-0.966537f},{0.414188f,-0.541434f,-0.731641f}, +{0.263469f,-0.554858f,-0.789124f},{0.478863f,-0.182555f,-0.858699f},{0.499925f,-0.229435f,-0.835126f}, +{0.44019f,-0.441255f,-0.782001f},{0.273243f,-0.564661f,-0.778778f},{0.308691f,-0.458051f,-0.833606f}, +{0.258205f,-0.263016f,-0.929598f},{0.266399f,-0.0140867f,-0.96376f},{0.325926f,-0.0461329f,-0.944269f}, +{0.350478f,-0.219007f,-0.910605f},{0.354576f,-0.145572f,-0.923626f},{0.370918f,-0.170775f,-0.912828f}, +{0.36704f,-0.104143f,-0.924357f},{0.397743f,0.0893554f,-0.913135f},{0.412708f,0.16809f,-0.89522f}, +{0.461831f,0.0794305f,-0.883404f},{0.491097f,0.00610044f,-0.871083f},{0.469434f,0.0393794f,-0.882089f}, +{-0.318099f,0.1176f,-0.940736f},{0.397764f,-0.22051f,-0.890595f},{0.339251f,-0.140336f,-0.930169f}, +{0.415097f,0.104971f,-0.903701f},{0.448675f,0.0117692f,-0.893618f},{0.335919f,-0.319947f,-0.885885f}, +{0.31524f,-0.196916f,-0.928357f},{0.441539f,0.052358f,-0.895713f},{0.374789f,0.0703258f,-0.924439f}, +{0.291724f,-0.429357f,-0.854722f},{0.198636f,-0.335479f,-0.920868f},{0.241156f,-0.318072f,-0.916883f}, +{0.309921f,-0.504065f,-0.806144f},{0.342391f,-0.433425f,-0.833613f},{0.279045f,-0.345422f,-0.896001f}, +{0.282074f,-0.247687f,-0.926869f},{0.279043f,-0.0375461f,-0.959544f},{0.26891f,-0.0713013f,-0.960523f}, +{0.277696f,-0.230241f,-0.93267f},{0.282046f,-0.160948f,-0.945804f},{0.24807f,-0.11132f,-0.962325f}, +{0.324934f,0.0120236f,-0.94566f},{0.404792f,0.0859546f,-0.91036f},{0.421959f,-0.0172375f,-0.906451f}, +{0.405509f,0.0607184f,-0.912072f},{0.370021f,0.0255989f,-0.92867f},{-0.273495f,0.102608f,-0.956385f}, +{-0.214751f,-0.0217704f,-0.976426f},{0.0389195f,-0.998942f,-0.0245161f},{0.301224f,-0.125571f,-0.945249f}, +{0.247679f,0.00834955f,-0.968806f},{-0.582805f,-0.287323f,-0.760121f},{0.153013f,-0.257305f,-0.954139f}, +{0.0623255f,-0.321166f,-0.94497f},{0.118664f,-0.440027f,-0.890109f},{0.186817f,-0.485921f,-0.853804f}, +{0.256389f,-0.395781f,-0.881829f},{0.242238f,-0.192341f,-0.95096f},{0.215539f,-0.00689006f,-0.976471f}, +{0.250731f,-0.139028f,-0.958021f},{0.261066f,-0.213902f,-0.941324f},{0.256085f,-0.0887571f,-0.962571f}, +{0.230182f,-0.188229f,-0.95477f},{0.34271f,-0.116949f,-0.932133f},{0.483111f,0.0179296f,-0.875376f}, +{0.506741f,0.0262392f,-0.861699f},{0.420864f,0.0887582f,-0.902771f},{0.387847f,0.0724752f,-0.91887f}, +{0.373944f,0.0811103f,-0.923898f},{-0.463775f,-0.155676f,-0.872169f},{0.205632f,-0.363896f,-0.908457f}, +{0.414665f,-0.202407f,-0.887177f},{0.51326f,-0.0593179f,-0.856181f},{0.477654f,0.0737985f,-0.875443f}, +{0.456738f,0.120889f,-0.881349f},{-0.124059f,-0.0143952f,-0.99217f},{0.159493f,-0.171302f,-0.972223f}, +{0.0728596f,-0.286864f,-0.955197f},{0.14769f,-0.548362f,-0.823096f},{0.222186f,-0.501618f,-0.83607f}, +{0.140263f,-0.412915f,-0.899904f},{0.0834118f,-0.318639f,-0.944199f},{0.132404f,-0.165691f,-0.977249f}, +{0.211184f,-0.0144253f,-0.97734f},{0.247741f,-0.159525f,-0.955603f},{0.282763f,-0.251771f,-0.925557f}, +{0.309661f,-0.0746755f,-0.94791f},{0.352027f,-0.173439f,-0.91978f},{0.314486f,-0.260742f,-0.91275f}, +{0.309214f,-0.0476167f,-0.9498f},{0.297153f,0.0785501f,-0.951593f},{0.341974f,0.140095f,-0.929208f}, +{0.361015f,0.0969296f,-0.927509f},{0.319776f,0.0989742f,-0.94231f},{0.13153f,0.190633f,-0.97281f}, +{0.0931867f,0.114043f,-0.989096f},{0.223886f,-0.0861198f,-0.970803f},{0.168265f,-0.0797806f,-0.982508f}, +{0.312417f,-0.196147f,-0.929474f},{0.326859f,-0.0967924f,-0.940103f},{-0.359361f,0.0730781f,-0.930333f}, +{0.158572f,-0.0861268f,-0.983584f},{0.109713f,-0.282932f,-0.952845f},{0.138901f,-0.618307f,-0.773565f}, +{0.227161f,-0.643871f,-0.730635f},{0.27837f,-0.418527f,-0.864492f},{0.262264f,-0.228899f,-0.937456f}, +{0.15929f,-0.233517f,-0.959216f},{0.095923f,-0.265918f,-0.959211f},{0.273692f,-0.203507f,-0.940041f}, +{0.395505f,-0.250086f,-0.883761f},{0.321622f,-0.368589f,-0.872182f},{0.370407f,-0.233665f,-0.898999f}, +{0.268743f,-0.0340914f,-0.962608f},{0.190561f,-0.22345f,-0.955906f},{0.199725f,-0.252784f,-0.946684f}, +{0.219432f,-0.0917187f,-0.971307f},{0.249687f,0.0682264f,-0.96592f},{0.251793f,0.115452f,-0.96087f}, +{0.311541f,0.0876862f,-0.946178f},{0.277111f,0.139705f,-0.950627f},{0.335169f,-0.213682f,-0.917606f}, +{0.413841f,-0.243567f,-0.877161f},{-0.508553f,0.375086f,-0.775038f},{0.158959f,-0.33245f,-0.929628f}, +{0.149185f,-0.0420049f,-0.987917f},{0.119061f,-0.346273f,-0.930548f},{0.104111f,-0.669696f,-0.735301f}, +{0.144693f,-0.679733f,-0.719046f},{0.165334f,-0.354326f,-0.92039f},{0.190832f,-0.188236f,-0.963405f}, +{0.213776f,-0.300113f,-0.929641f},{0.184987f,-0.345558f,-0.919984f},{0.214224f,-0.303985f,-0.928279f}, +{0.256223f,-0.283327f,-0.924162f},{0.23446f,-0.110913f,-0.965778f},{0.222045f,0.0280285f,-0.974634f}, +{0.219076f,-0.154819f,-0.963347f},{0.20427f,-0.26067f,-0.94357f},{0.200559f,-0.182029f,-0.962622f}, +{0.20429f,-0.0842184f,-0.975281f},{0.25253f,0.0505589f,-0.966267f},{0.332164f,0.0709402f,-0.94055f}, +{0.441632f,0.0665385f,-0.894726f},{-0.351427f,0.362608f,-0.863142f},{0.247619f,0.00508906f,-0.968844f}, +{0.389279f,-0.0348041f,-0.920462f},{0.443012f,0.0647495f,-0.894174f},{0.109221f,-0.993986f,-0.00792956f}, +{0.203599f,-0.978122f,-0.0427252f},{-0.238324f,0.306725f,-0.921478f},{0.1839f,-0.223995f,-0.957082f}, +{0.409799f,0.203448f,-0.889198f},{0.102517f,-0.279304f,-0.954714f},{0.13668f,-0.0744809f,-0.987811f}, +{0.186384f,-0.311169f,-0.931899f},{0.174915f,-0.699354f,-0.693043f},{0.187465f,-0.654656f,-0.732313f}, +{0.136217f,-0.352045f,-0.926018f},{0.203846f,-0.210139f,-0.956184f},{0.244663f,-0.312758f,-0.917781f}, +{0.108868f,-0.329495f,-0.93786f},{0.203825f,-0.317197f,-0.926197f},{0.204169f,-0.301991f,-0.931191f}, +{0.20256f,-0.0767667f,-0.976256f},{0.150877f,-0.00640872f,-0.988532f},{0.230954f,-0.186151f,-0.954991f}, +{0.155388f,-0.298392f,-0.941709f},{0.1697f,-0.192309f,-0.96655f},{0.18909f,-0.116842f,-0.974984f}, +{0.282293f,-0.0916951f,-0.954936f},{0.331828f,-0.0941502f,-0.93863f},{0.308796f,0.0477273f,-0.94993f}, +{-0.408249f,-0.720043f,-0.561134f},{0.268533f,-0.263567f,-0.926511f},{0.091933f,-0.285835f,-0.953859f}, +{0.118349f,-0.0897115f,-0.988911f},{0.145821f,-0.461532f,-0.875057f},{0.126957f,-0.694749f,-0.707958f}, +{0.114736f,-0.552027f,-0.825894f},{0.0458875f,-0.363814f,-0.930341f},{0.17179f,-0.349481f,-0.92106f}, +{0.241402f,-0.314832f,-0.917936f},{0.187544f,-0.248817f,-0.95022f},{0.154279f,-0.317391f,-0.935661f}, +{0.171403f,-0.273071f,-0.946601f},{0.218886f,-0.0469395f,-0.974621f},{0.23039f,-0.0452667f,-0.972045f}, +{0.334638f,-0.235148f,-0.912537f},{0.238913f,-0.201569f,-0.94989f},{0.201881f,-0.159874f,-0.966273f}, +{0.25613f,-0.189546f,-0.947877f},{0.257277f,-0.113556f,-0.959642f},{0.220836f,-0.0757441f,-0.972365f}, +{0.162348f,0.0763838f,-0.983773f},{0.189445f,0.265051f,-0.945441f},{0.136332f,-0.0751422f,-0.987809f}, +{0.095285f,-0.27107f,-0.957832f},{0.101441f,-0.104338f,-0.989355f},{0.0567444f,-0.445659f,-0.893402f}, +{-0.000193409f,-0.676142f,-0.736771f},{0.0109145f,-0.465338f,-0.885066f},{0.0534069f,-0.389789f,-0.919354f}, +{0.0916234f,-0.502817f,-0.859523f},{0.160832f,-0.40552f,-0.899826f},{0.159299f,-0.195571f,-0.967665f}, +{0.132088f,-0.240001f,-0.961744f},{0.0841788f,-0.31464f,-0.945471f},{0.133508f,-0.134741f,-0.981845f}, +{0.17912f,-0.0888595f,-0.979806f},{0.208443f,-0.195479f,-0.9583f},{0.259759f,-0.212322f,-0.942043f}, +{0.226557f,-0.164269f,-0.960046f},{0.138192f,-0.0514023f,-0.989071f},{0.093177f,-0.0381038f,-0.99492f}, +{0.0672073f,-0.120768f,-0.990403f},{0.176091f,0.262835f,-0.948636f},{0.0965525f,-0.994059f,-0.0502461f}, +{-0.0002735f,-0.997933f,0.0642685f},{-0.00876615f,-0.997232f,0.073829f},{-0.0215592f,-0.996239f,0.083924f}, +{0.198791f,-0.12984f,-0.971403f},{-0.613472f,-0.357682f,-0.704071f},{0.145189f,-0.0897178f,-0.985328f}, +{0.0700315f,-0.316691f,-0.94594f},{-0.0368761f,-0.677374f,-0.734714f},{-0.0508762f,-0.574966f,-0.816594f}, +{0.00461845f,-0.453498f,-0.891245f},{-0.00453975f,-0.52994f,-0.848023f},{0.00720484f,-0.395835f,-0.918294f}, +{0.0863324f,-0.18462f,-0.979011f},{0.129269f,-0.278945f,-0.951567f},{0.227027f,-0.32294f,-0.918786f}, +{0.265326f,-0.189893f,-0.945274f},{0.255582f,-0.118537f,-0.959493f},{0.287931f,-0.233008f,-0.928872f}, +{0.268068f,-0.185136f,-0.945444f},{0.196184f,-0.0750087f,-0.977694f},{0.0985332f,0.00685483f,-0.99511f}, +{0.162505f,-0.0579435f,-0.985005f},{0.151121f,-0.336992f,-0.9293f},{0.208861f,-0.129676f,-0.96931f}, +{-0.333093f,-0.877541f,-0.344923f},{-0.855978f,-0.334846f,-0.393928f},{0.342906f,-0.296651f,-0.891299f}, +{0.278857f,-0.0874291f,-0.956345f},{0.191219f,-0.979642f,0.0611353f},{-0.0449772f,-0.984866f,-0.167378f}, +{0.257991f,-0.139881f,-0.955967f},{0.140787f,0.0217321f,-0.989801f},{0.171542f,-0.0350602f,-0.984553f}, +{0.191266f,-0.111461f,-0.975189f},{0.134866f,0.0460265f,-0.989794f},{-0.474359f,-0.186298f,-0.860393f}, +{-0.0743062f,-0.679009f,0.730359f},{0.17509f,-0.179612f,-0.968031f},{0.18737f,-0.182224f,-0.965239f}, +{0.0634384f,-0.553884f,-0.830173f},{-0.0614459f,-0.573997f,-0.816549f},{-0.0548518f,-0.463718f,-0.884283f}, +{-0.0542619f,-0.46565f,-0.883304f},{-0.0886851f,-0.485649f,-0.869643f},{-0.031275f,-0.343695f,-0.938561f}, +{0.109803f,-0.424685f,-0.898658f},{0.157277f,-0.316265f,-0.935543f},{0.181625f,-0.149741f,-0.9719f}, +{0.250089f,-0.202109f,-0.946893f},{0.224643f,-0.195189f,-0.954692f},{0.189713f,-0.127546f,-0.97352f}, +{0.120296f,-0.0534839f,-0.991296f},{-0.457533f,-0.498441f,-0.736356f},{-0.0184083f,-0.864544f,0.502219f}, +{0.0450966f,-0.998981f,0.00189569f},{0.0182236f,-0.999668f,-0.0182031f},{-0.308813f,-0.950316f,0.0391569f}, +{0.249777f,-0.228349f,-0.940993f},{0.0407418f,-0.378347f,-0.924767f},{-0.164432f,0.40839f,-0.897875f}, +{-0.314956f,0.0949649f,-0.944343f},{-0.541635f,-0.409308f,-0.734233f},{-0.159007f,-0.282733f,-0.945927f}, +{-0.0413941f,-0.283658f,-0.958032f},{0.0651972f,-0.232411f,-0.97043f},{0.0368408f,-0.149265f,-0.988111f}, +{0.0695572f,-0.163889f,-0.984024f},{0.0598061f,-0.11698f,-0.991332f},{0.0909766f,0.0319594f,-0.99534f}, +{0.102819f,-0.0478997f,-0.993546f},{0.0437593f,-0.077876f,-0.996002f},{0.0155373f,0.0713922f,-0.997327f}, +{-0.348753f,0.139677f,-0.926748f},{0.23431f,-0.891494f,0.387733f},{-0.553502f,-0.308364f,-0.773659f}, +{-0.616738f,-0.173523f,-0.767805f},{-0.380602f,0.11266f,-0.917851f},{0.22381f,-0.132088f,-0.965641f}, +{-0.0792494f,-0.411621f,-0.907903f},{-0.193881f,-0.479576f,-0.855813f},{-0.197234f,-0.449732f,-0.871114f}, +{-0.0877846f,-0.449835f,-0.888787f},{-0.185332f,-0.415287f,-0.890612f},{-0.207097f,-0.395328f,-0.894889f}, +{-0.118296f,-0.403333f,-0.907374f},{0.00189189f,-0.999994f,0.00306737f},{-0.457134f,-0.420451f,-0.783741f}, +{-0.635975f,-0.131724f,-0.760385f},{-0.269536f,-0.956413f,-0.112361f},{-0.288356f,-0.418766f,-0.861096f}, +{-0.36962f,-0.401844f,-0.837797f},{-0.377607f,-0.407334f,-0.83156f},{-0.348602f,-0.404377f,-0.845551f}, +{-0.248868f,-0.324939f,-0.912403f},{-0.279433f,-0.340647f,-0.897707f},{-0.214021f,-0.324631f,-0.921309f}, +{-0.112611f,-0.190842f,-0.97514f},{-0.0751645f,-0.194146f,-0.978089f},{-0.0507894f,-0.146409f,-0.987919f}, +{-0.0101824f,-0.0328948f,-0.999407f},{0.0159028f,-0.0202976f,-0.999668f},{-0.0595336f,0.00126352f,-0.998226f}, +{-0.0968821f,0.0913483f,-0.991095f},{-0.139114f,0.286357f,-0.94797f},{-0.921666f,-0.384264f,-0.0535948f}, +{0.0780428f,-0.996611f,-0.0260006f},{-0.283515f,-0.477441f,-0.831667f},{-0.331705f,-0.449341f,-0.829496f}, +{-0.385828f,-0.392684f,-0.834827f},{-0.517772f,-0.225955f,-0.82514f},{-0.55825f,-0.15923f,-0.81425f}, +{-0.592719f,-0.108802f,-0.798027f},{0.230233f,-0.0502752f,-0.971836f},{0.119686f,0.0323427f,-0.992285f}, +{-0.158251f,-0.204838f,-0.965918f},{-0.250495f,-0.436161f,-0.864301f},{-0.453561f,-0.372427f,-0.809679f}, +{-0.371939f,-0.334884f,-0.865745f},{-0.351896f,-0.847199f,0.398023f},{-0.0471725f,-0.995583f,0.0811798f}, +{-0.178855f,0.347392f,-0.920505f},{-0.0643786f,-0.901551f,0.427855f},{0.165713f,-0.0165558f,-0.986035f}, +{0.093963f,0.185729f,-0.978098f},{-0.085456f,0.0715648f,-0.993768f},{-0.383911f,-0.292464f,-0.875829f}, +{-0.429875f,-0.347318f,-0.833413f},{-0.481882f,-0.391585f,-0.783869f},{-0.433764f,-0.41101f,-0.801823f}, +{-0.492389f,-0.348149f,-0.797713f},{-0.48093f,-0.351514f,-0.803209f},{-0.369498f,-0.397625f,-0.83986f}, +{-0.434404f,-0.368349f,-0.821956f},{-0.328686f,-0.300934f,-0.895212f},{-0.272589f,-0.176483f,-0.945806f}, +{-0.194385f,-0.0887588f,-0.976901f},{-0.213207f,0.0173482f,-0.976853f},{-0.203672f,0.0458605f,-0.977965f}, +{-0.212849f,0.138942f,-0.967156f},{-0.201355f,0.286949f,-0.936545f},{-0.170263f,-0.958942f,0.226804f}, +{0.0336438f,-0.997239f,0.066202f},{0.476486f,-0.877819f,0.0489346f},{-0.175802f,-0.482737f,-0.857938f}, +{-0.240114f,-0.406166f,-0.881688f},{-0.317766f,-0.319497f,-0.892719f},{-0.450952f,-0.15198f,-0.879514f}, +{-0.511979f,-0.296898f,-0.806058f},{-0.508813f,-0.393695f,-0.76558f},{0.112715f,0.225438f,-0.967715f}, +{-0.598289f,-0.679083f,-0.42532f},{-0.235634f,-0.951767f,-0.196511f},{-0.476286f,-0.803592f,-0.356919f}, +{-0.362864f,-0.223333f,-0.904683f},{-0.420486f,-0.21012f,-0.882633f},{-0.279394f,0.219675f,-0.93471f}, +{-0.0115128f,-0.999142f,0.0397777f},{-0.000605356f,-0.997024f,0.077089f},{-0.0082265f,-0.999963f,0.00249397f}, +{-0.0261943f,-0.998588f,-0.0462172f},{0.0705986f,0.26835f,-0.960731f},{-0.0994969f,0.328516f,-0.939243f}, +{-0.393652f,-0.0258967f,-0.918895f},{-0.523073f,-0.360866f,-0.772121f},{-0.533142f,-0.277189f,-0.799328f}, +{-0.483933f,-0.15738f,-0.860837f},{-0.436994f,-0.0964095f,-0.894283f},{-0.363398f,0.0677128f,-0.92917f}, +{-0.473792f,-0.00518424f,-0.880622f},{-0.390418f,0.0204487f,-0.920411f},{-0.355069f,0.15848f,-0.921309f}, +{-0.18989f,-0.469618f,-0.862207f},{0.0387132f,-0.998767f,0.031081f},{-0.22247f,-0.972103f,-0.0743105f}, +{-0.0842592f,-0.983493f,0.160133f},{-0.460062f,0.220102f,-0.860173f},{0.0931486f,-0.955462f,0.280027f}, +{0.0532537f,-0.998488f,-0.0136487f},{-0.203823f,-0.966645f,-0.155093f},{0.0599921f,-0.998181f,0.00590705f}, +{0.0167739f,-0.999717f,-0.0168777f},{0.111033f,-0.986245f,0.122446f},{0.0914944f,-0.881277f,0.463658f}, +{0.0355514f,-0.998636f,0.0382277f},{0.437185f,-0.872861f,0.216754f},{0.422546f,-0.855048f,0.300579f}, +{0.42756f,-0.880253f,0.205784f},{-0.322485f,-0.87414f,-0.363155f},{-0.00853661f,-0.991127f,-0.132642f}, +{-0.145474f,-0.888027f,-0.436171f},{-0.493414f,-0.735429f,0.464422f},{-0.335755f,-0.760172f,0.556243f}, +{-0.493879f,-0.817461f,0.296379f},{0.261385f,-0.811544f,0.522565f},{0.032797f,-0.995392f,0.090103f}, +{0.114284f,-0.915866f,0.384874f},{-0.423877f,-0.903915f,-0.0571463f},{-0.595218f,-0.723646f,0.34936f}, +{0.21114f,-0.821121f,0.530264f},{-0.00545385f,-0.999841f,-0.0169869f},{-0.0187241f,-0.995127f,-0.0968045f}, +{0.0192527f,-0.997316f,0.0706471f},{0.0217938f,-0.996704f,0.0781471f},{0.0252164f,-0.997289f,0.0691295f}, +{0.0388396f,-0.996338f,0.076169f},{-0.0958933f,-0.67412f,0.732371f},{-0.0528083f,-0.6354f,0.770375f}, +{-0.277386f,-0.89954f,0.337469f},{-0.415031f,-0.742829f,0.525313f},{0.0269597f,-0.998768f,0.0416694f}, +{0.0208328f,-0.998511f,0.0504246f},{0.0197848f,-0.999201f,0.0347204f},{0.441117f,-0.887602f,0.132582f}, +{0.220524f,-0.949395f,-0.223647f},{0.254325f,-0.918615f,-0.302432f},{-0.411947f,-0.905298f,-0.103613f}, +{0.0330157f,-0.999407f,0.00973608f},{0.0360647f,-0.999324f,0.00715541f},{0.0281243f,-0.999554f,0.0100368f}, +{0.0317264f,-0.999288f,-0.0204045f},{0.0207974f,-0.999648f,-0.0164574f},{0.390465f,-0.895183f,0.214907f}, +{0.068996f,-0.997368f,-0.0222887f},{0.00696426f,-0.999677f,0.0244445f},{0.113192f,-0.990527f,-0.0777368f}, +{0.0300783f,-0.997791f,-0.0592262f},{-0.101247f,-0.989736f,0.100855f},{-0.488221f,-0.872483f,-0.0203287f}, +{0.0192702f,-0.995406f,0.0937832f},{-0.00526962f,-0.999976f,0.004602f},{0.168434f,-0.985586f,-0.015808f}, +{-0.274512f,-0.959802f,0.0585097f},{-0.125707f,-0.991656f,-0.0285798f},{-0.440623f,-0.879403f,-0.180285f}, +{-0.508811f,-0.860146f,0.0355013f},{-0.295831f,-0.930916f,-0.214194f},{-0.133933f,-0.979029f,-0.153508f}, +{-0.504832f,-0.82857f,-0.242108f},{-0.199422f,-0.931407f,-0.304485f},{-0.0191935f,-0.999509f,-0.0247501f}, +{-0.00421024f,-0.997259f,-0.0738707f},{-0.456138f,-0.792245f,-0.405322f},{0.536549f,-0.617989f,0.574635f}, +{0.159432f,-0.964327f,0.211319f},{0.119699f,-0.987882f,0.098801f},{0.0753162f,-0.996306f,0.041258f}, +{0.0769156f,-0.995434f,0.056522f},{0.199961f,-0.972262f,0.121333f},{0.0324883f,-0.998411f,0.0460362f}, +{0.00150144f,-0.998946f,0.0458802f},{0.0212488f,-0.999318f,0.0302135f},{0.0380284f,-0.999242f,-0.00828349f}, +{0.0575431f,-0.998323f,0.00632132f},{-0.0275333f,-0.993345f,-0.111842f},{0.179328f,-0.983237f,-0.032964f}, +{0.134155f,-0.989332f,0.056792f},{-0.275129f,-0.942456f,0.189949f},{-0.434564f,-0.859275f,0.269818f}, +{-0.501779f,-0.819213f,0.277684f},{0.230928f,-0.92776f,0.293145f},{0.457012f,-0.817257f,0.351042f}, +{0.431776f,-0.889879f,0.147261f},{-0.00337819f,-0.99996f,0.00822642f},{-0.0679747f,-0.924453f,0.375188f}, +{0.421036f,-0.80576f,0.41651f},{0.261081f,-0.824249f,0.502445f},{-0.400411f,-0.863751f,0.30595f}, +{-0.00950766f,-0.998909f,0.0457252f},{0.108334f,-0.993724f,-0.0278652f},{0.119746f,-0.987411f,0.103343f}, +{0.246439f,-0.960619f,0.128374f},{-0.0457223f,-0.994671f,-0.0924088f},{0.254503f,-0.966784f,0.0235859f}, +{0.267335f,-0.961599f,0.0621237f},{0.139755f,-0.968292f,0.207074f},{-0.0581971f,-0.996083f,0.0665645f}, +{-0.100193f,-0.994413f,0.0332144f},{0.256728f,-0.860271f,0.440482f},{0.0205264f,-0.995617f,0.0912464f}, +{0.145715f,-0.989314f,-0.00497361f},{0.442628f,-0.725749f,0.526658f},{0.422509f,-0.759082f,0.495258f}, +{0.0250255f,-0.999672f,0.00538897f},{0.0226148f,-0.999695f,-0.0098717f},{-0.0512331f,-0.95503f,0.29205f}, +{0.0094949f,-0.999954f,0.00102734f},{-0.134225f,-0.965527f,-0.223029f},{-0.0342345f,-0.997022f,-0.0691043f}, +{0.0220392f,-0.999002f,-0.0388572f},{0.0200358f,-0.999289f,-0.031944f},{-0.160766f,-0.975433f,-0.150616f}, +{0.0182022f,-0.999505f,-0.0256645f},{0.157028f,-0.799171f,0.580231f},{0.348365f,-0.535744f,0.769168f}, +{-0.0879878f,-0.890344f,0.446705f},{-0.299798f,-0.892556f,0.336847f},{-0.485275f,-0.745735f,0.456494f}, +{-0.0470124f,-0.998452f,-0.0297115f},{0.0765366f,-0.877318f,0.473766f},{-0.0188492f,-0.998556f,-0.0503023f}, +{0.0232935f,-0.998163f,0.0559258f},{-0.0291428f,-0.923105f,0.383443f},{0.0719425f,-0.983426f,0.166427f}, +{-0.352676f,-0.869485f,0.345856f},{0.0223918f,-0.995144f,0.0958476f},{0.0235307f,-0.996475f,0.0805263f}, +{-0.0904133f,-0.98791f,0.125936f},{-0.669071f,-0.733856f,-0.117467f},{0.409527f,-0.911178f,-0.0452039f}, +{-0.477475f,-0.771456f,0.420563f},{0.0244424f,-0.995812f,0.0881007f},{0.0470478f,-0.997189f,0.0583127f}, +{-0.627038f,-0.766081f,0.141222f},{-0.641998f,-0.766623f,-0.0113149f},{-0.598191f,-0.738564f,-0.310951f}, +{-0.435507f,-0.821898f,-0.367176f},{-0.00972171f,-0.998838f,0.0472077f},{-0.433635f,-0.802921f,0.408997f}, +{0.0943655f,-0.869567f,0.484715f},{-0.113401f,-0.985403f,0.126968f},{-0.0507322f,-0.910702f,0.409937f}, +{-0.147138f,-0.692524f,0.706229f},{0.0576281f,-0.990996f,0.120851f},{0.0329572f,-0.994475f,0.0996614f}, +{0.0424437f,-0.995673f,0.0826672f},{-0.458454f,-0.748406f,0.479278f},{0.183653f,-0.959272f,-0.214638f}, +{0.0274192f,-0.999423f,-0.020069f},{0.0224728f,-0.999636f,-0.0148969f},{0.0689027f,-0.996106f,0.0550065f}, +{0.267426f,-0.943674f,0.194839f},{-0.143591f,-0.609172f,0.77993f},{-0.683555f,-0.706749f,0.182371f}, +{0.0207577f,-0.999514f,-0.0232647f},{-0.00208528f,-0.999923f,-0.0122105f},{0.00944962f,-0.999952f,0.00269041f}, +{0.0197769f,-0.999642f,-0.0180048f},{0.0818378f,-0.995107f,0.0553525f},{-0.60003f,-0.772993f,0.206023f}, +{0.132217f,-0.736984f,0.662852f},{0.0259287f,-0.996915f,0.0740829f},{-0.219604f,-0.975282f,0.0244756f}, +{0.181724f,-0.972486f,0.145768f},{0.186707f,-0.976889f,-0.104062f},{-0.358453f,-0.845749f,0.395247f}, +{0.125563f,-0.984195f,0.124874f},{-0.172515f,-0.972719f,0.155102f},{0.0503893f,-0.998717f,-0.00495466f}, +{0.252812f,-0.945431f,0.20554f},{0.327671f,-0.916653f,0.228865f},{0.413063f,-0.88025f,0.233537f}, +{0.507231f,-0.849177f,0.147022f},{0.418226f,-0.898864f,0.130885f},{-0.254397f,-0.928918f,0.26906f}, +{0.331623f,-0.931502f,0.149429f},{0.31091f,-0.914002f,0.260643f},{0.66593f,-0.668128f,0.331876f}, +{0.344914f,-0.804073f,0.484252f},{0.368575f,-0.849033f,0.378543f},{0.0591826f,-0.99639f,0.0608577f}, +{-0.623304f,-0.750806f,-0.218594f},{-0.617055f,-0.758178f,-0.210734f},{-0.588556f,-0.784469f,-0.195473f}, +{-0.559468f,-0.824327f,-0.0864911f},{0.0300425f,-0.998085f,0.0540763f},{0.0202993f,-0.999654f,-0.0167502f}, +{0.0324792f,-0.999177f,-0.0242951f},{0.0377085f,-0.998735f,-0.0332751f},{0.0247975f,-0.999377f,-0.0251006f}, +{-0.175498f,-0.980598f,-0.0873358f},{0.0152553f,-0.999611f,-0.0233534f},{-0.0155738f,-0.999348f,-0.0325671f}, +{0.433514f,-0.891799f,0.129464f},{0.0318165f,-0.999425f,-0.0117401f},{0.0301593f,-0.998859f,-0.0370258f}, +{0.0351686f,-0.998546f,-0.0408672f},{0.0354106f,-0.997615f,0.0592413f},{0.0265466f,-0.993097f,0.114249f}, +{-0.246175f,-0.969217f,-0.004021f},{0.0260654f,-0.999081f,-0.0340246f},{0.439339f,-0.795647f,0.417046f}, +{0.453894f,-0.753803f,0.475144f},{-0.0218013f,-0.997889f,0.0611717f},{-0.353667f,-0.930265f,-0.0976006f}, +{-0.573537f,-0.714299f,-0.401039f},{-0.258594f,-0.948539f,-0.182764f},{-0.4667f,-0.864393f,-0.187126f}, +{0.395986f,-0.871117f,0.290431f},{0.177311f,-0.984137f,-0.0060141f},{-0.0176953f,-0.999403f,-0.0296736f}, +{-0.467747f,-0.865832f,-0.177615f},{0.0250974f,-0.998326f,-0.052109f},{-0.639944f,-0.768392f,0.00664921f}, +{-0.436519f,-0.882692f,-0.174088f},{-0.0443429f,-0.996264f,-0.0741012f},{0.148824f,-0.978852f,0.14036f}, +{0.290687f,-0.919037f,0.266219f},{0.352249f,-0.880871f,0.316206f},{0.0831872f,-0.874957f,0.477f}, +{-0.0582198f,-0.996291f,0.0633602f},{-0.210873f,-0.96402f,-0.161858f},{0.133774f,-0.990679f,-0.0256777f}, +{0.438413f,-0.846291f,0.302631f},{0.033499f,-0.997861f,0.056144f},{-0.45141f,-0.872907f,-0.1851f}, +{0.0452908f,-0.996803f,0.0658251f},{-0.0145148f,-0.999841f,0.0103397f},{0.498847f,-0.852849f,0.154272f}, +{-0.333123f,-0.851253f,0.40546f},{-0.48286f,-0.805861f,-0.342687f},{-0.296849f,-0.934096f,-0.198358f}, +{0.0291155f,-0.99953f,-0.00955752f},{-0.222844f,-0.947564f,0.229046f},{-0.0128039f,-0.996041f,0.087971f}, +{-0.522135f,-0.852551f,0.0230707f},{-0.397436f,-0.898396f,-0.186892f},{-0.522835f,-0.827296f,-0.205488f}, +{-0.641568f,-0.688765f,0.337629f},{0.0315253f,-0.999262f,-0.0219513f},{-0.0624349f,-0.891708f,0.448284f}, +{-0.19033f,-0.639321f,0.745012f},{0.465534f,-0.885002f,-0.00708205f},{0.0205535f,-0.999617f,0.0185422f}, +{0.0343446f,-0.999226f,-0.019187f},{-0.012597f,-0.921971f,0.387055f},{-0.0375038f,-0.964409f,0.261741f}, +{0.0311963f,-0.999185f,-0.0256275f},{-0.179976f,-0.983404f,0.0229121f},{0.019335f,-0.999568f,0.0221399f}, +{0.0401681f,-0.998975f,0.0208602f},{-0.460601f,-0.886659f,-0.0410255f},{-0.440598f,-0.897705f,-4.98216e-05f}, +{-0.504286f,-0.842614f,0.188935f},{-0.692687f,-0.706962f,0.142793f},{-0.655434f,-0.755063f,0.0168972f}, +{0.0579206f,-0.997201f,0.0472857f},{0.0291897f,-0.999256f,0.0252153f},{0.277683f,-0.837692f,0.470282f}, +{-0.35385f,-0.827547f,0.43584f},{-0.14467f,-0.922792f,0.357107f},{-0.243562f,-0.650974f,0.718965f}, +{-0.0765156f,-0.90654f,0.415127f},{-0.0870873f,-0.647042f,0.757464f},{-0.0998985f,-0.705678f,0.701455f}, +{-0.121883f,-0.585755f,0.801271f},{-0.0845681f,-0.794925f,0.600785f},{0.322318f,-0.89145f,0.318478f}, +{0.268049f,-0.899651f,0.344641f},{0.394246f,-0.896125f,0.20379f},{0.365052f,-0.901085f,0.234057f}, +{-0.328019f,-0.934659f,-0.137167f},{-0.360418f,-0.920976f,0.147993f},{-0.45244f,-0.82013f,0.350265f}, +{-0.591845f,-0.602434f,0.535531f},{0.0557912f,-0.997563f,0.0418935f},{0.0480669f,-0.998536f,0.0248022f}, +{-0.705643f,-0.70726f,-0.0430247f},{0.0151378f,-0.95688f,0.290088f},{0.0243109f,-0.999251f,0.0300928f}, +{0.0207376f,-0.998779f,0.0448426f},{0.0115761f,-0.99813f,0.060023f},{0.0210507f,-0.999506f,-0.0233455f}, +{-0.297012f,-0.954334f,-0.0321031f},{-0.354475f,-0.691456f,0.629472f},{0.00592118f,-0.999939f,0.0093082f}, +{0.00944358f,-0.99995f,0.00317306f},{0.00479116f,-0.999985f,-0.00271793f},{0.111701f,-0.93354f,0.340625f}, +{-0.256367f,-0.898633f,0.355998f},{-0.243354f,-0.885941f,0.394826f},{0.0241804f,-0.803704f,0.594538f}, +{-0.538186f,-0.842816f,-0.00415148f},{-0.251625f,-0.957127f,0.1435f},{-0.339474f,-0.897541f,0.281383f}, +{0.446715f,-0.894676f,-0.000879809f},{0.0248744f,-0.995502f,0.0914169f},{-0.309336f,-0.665887f,0.6789f}, +{0.391327f,-0.920154f,-0.0134252f},{0.0404577f,-0.997928f,0.0500338f},{-0.00232434f,-0.9993f,0.037331f}, +{-0.00513422f,-0.999965f,-0.00653303f},{-0.253237f,-0.892645f,0.372903f},{-0.272659f,-0.844382f,0.461169f}, +{0.0187416f,-0.996571f,0.080588f},{-0.502938f,-0.647014f,0.573085f},{0.300552f,-0.922456f,-0.242371f}, +{0.271425f,-0.952077f,-0.140989f},{-0.00736048f,-0.999908f,0.0114276f},{-0.16623f,-0.958139f,0.233104f}, +{0.0206399f,-0.999591f,0.0197855f},{0.00345179f,-0.998907f,-0.0466221f},{0.0212683f,-0.999708f,-0.0114886f}, +{0.0375103f,-0.999227f,-0.0118001f},{-0.348836f,-0.931621f,0.101959f},{-0.314378f,-0.680497f,0.661883f}, +{-0.239775f,-0.428203f,0.871292f},{0.0269942f,-0.999498f,-0.016598f},{0.205362f,-0.975052f,0.0842651f}, +{0.270583f,-0.666424f,0.69474f},{-0.00150245f,-0.999758f,-0.0219692f},{-0.714f,-0.698681f,0.0452555f}, +{-0.688861f,-0.724634f,0.0193742f},{0.401174f,-0.906291f,0.133027f},{-0.505844f,-0.788873f,0.348999f}, +{-0.247088f,-0.952407f,0.178519f},{-0.205733f,-0.59121f,0.779836f},{0.0494061f,-0.992878f,0.108406f}, +{0.0367044f,-0.994233f,0.100762f},{0.0351902f,-0.99526f,0.0906619f},{0.213513f,-0.950656f,0.225089f}, +{-0.792844f,-0.571214f,0.212401f},{-0.741162f,-0.631635f,0.227412f},{-0.803971f,-0.570798f,0.166793f}, +{0.017301f,-0.999692f,-0.0177914f},{-0.229972f,-0.922314f,0.310563f},{-0.216697f,-0.792922f,0.569489f}, +{0.235083f,-0.926277f,0.294528f},{0.00602081f,-0.999691f,-0.0241236f},{-0.777905f,-0.572679f,0.258656f}, +{-0.814979f,-0.579368f,-0.0119202f},{-0.134095f,-0.712885f,0.688341f},{0.0263432f,-0.991925f,0.124058f}, +{-0.286608f,-0.917236f,0.276647f},{-0.274125f,-0.949667f,0.151618f},{-0.15952f,-0.639275f,0.752251f}, +{0.0140026f,-0.513734f,0.857835f},{0.0268088f,-0.997391f,0.0670283f},{0.54123f,-0.811167f,0.221538f}, +{-0.114704f,-0.964508f,0.23784f},{-0.114607f,-0.950567f,0.288597f},{-0.650764f,-0.732086f,-0.201387f}, +{-0.679137f,-0.732762f,0.042813f},{-0.626677f,-0.75023f,0.210786f},{-0.744444f,-0.615384f,0.259046f}, +{-0.783919f,-0.549936f,0.28817f},{0.0951864f,-0.992793f,0.0728156f},{-0.167854f,-0.82374f,0.541551f}, +{-0.556335f,-0.758532f,-0.339294f},{-0.572388f,-0.778919f,-0.256236f},{-0.675517f,-0.72405f,-0.139386f}, +{-0.697059f,-0.69526f,0.175279f},{-0.679396f,-0.680833f,0.273657f},{-0.745357f,-0.557158f,0.366084f}, +{-0.177695f,-0.919051f,0.351809f},{-0.246284f,-0.738901f,0.627192f},{-0.392196f,-0.627861f,0.672289f}, +{-0.637037f,-0.759177f,-0.133544f},{-0.651607f,-0.758376f,0.0165816f},{-0.628765f,-0.747946f,0.212678f}, +{-0.696044f,-0.539656f,0.473597f},{-0.631972f,-0.595965f,0.495417f},{0.0272724f,-0.999054f,0.0338618f}, +{0.0369473f,-0.938149f,0.344253f},{0.0546958f,-0.953689f,0.295779f},{-0.448396f,-0.847467f,-0.28415f}, +{-0.686933f,-0.723593f,0.0673533f},{-0.714324f,-0.659988f,0.232717f},{-0.625211f,-0.744034f,0.235634f}, +{-0.710254f,-0.586809f,0.388837f},{-0.580688f,-0.774537f,0.250786f},{-0.0388857f,-0.882911f,0.467928f}, +{-0.0236237f,-0.998928f,0.0398115f},{-0.527334f,-0.834f,-0.162363f},{-0.712196f,-0.701721f,-0.0190777f}, +{-0.719809f,-0.662478f,0.20736f},{-0.620255f,-0.739483f,0.261626f},{-0.668833f,-0.679571f,0.301408f}, +{-0.187982f,-0.790274f,0.583207f},{-0.26728f,-0.633922f,0.725743f},{-0.404074f,-0.88524f,-0.230377f}, +{-0.652531f,-0.733136f,0.19161f},{-0.715147f,-0.651889f,0.252201f},{-0.666044f,-0.696571f,0.266786f}, +{-0.588823f,-0.721757f,0.363807f},{-0.631337f,-0.667355f,0.395032f},{-0.626106f,-0.621972f,0.470257f}, +{-0.689142f,-0.508164f,0.516578f},{0.483577f,-0.845639f,0.225937f},{0.222438f,-0.938139f,0.26536f}, +{0.452834f,-0.838274f,0.303706f},{-0.385865f,-0.875169f,-0.29187f},{-0.379143f,-0.892042f,-0.245992f}, +{-0.367463f,-0.921055f,-0.128949f},{-0.448753f,-0.891519f,-0.0617599f},{-0.605329f,-0.795648f,0.0228318f}, +{-0.616404f,-0.740274f,0.268404f},{-0.60119f,-0.724447f,0.337264f},{0.339494f,-0.926629f,-0.161565f}, +{0.311439f,-0.949616f,-0.0351357f},{0.168152f,-0.911037f,0.376479f},{0.244289f,-0.95705f,0.156134f}, +{-0.397373f,-0.877227f,-0.269383f},{-0.338469f,-0.915208f,-0.218707f},{-0.297257f,-0.945486f,-0.133023f}, +{-0.464221f,-0.885413f,0.0232871f},{-0.527235f,-0.836455f,0.149552f},{-0.537636f,-0.781501f,0.316549f}, +{0.263596f,-0.94726f,0.182252f},{0.107578f,-0.844528f,0.524594f},{-0.0229386f,-0.670058f,0.741954f}, +{-0.198094f,-0.980182f,-0.00106648f},{0.300789f,-0.87079f,0.38891f},{-0.331517f,-0.918344f,-0.216197f}, +{-0.462501f,-0.834991f,0.29813f},{-0.569042f,-0.72583f,0.386474f},{-0.53184f,-0.784895f,0.317941f}, +{-0.530304f,-0.751916f,0.391663f},{-0.553343f,-0.688627f,0.46862f},{-0.626114f,-0.554425f,0.548265f}, +{-0.302206f,-0.872003f,0.385073f},{-0.281619f,-0.878895f,0.385013f},{0.370219f,-0.889681f,0.267219f}, +{-0.184527f,-0.981478f,0.0514892f},{-0.283202f,-0.942306f,-0.178485f},{-0.337896f,-0.936388f,-0.0948912f}, +{-0.353937f,-0.934617f,0.0349132f},{-0.428673f,-0.891312f,0.147655f},{-0.5048f,-0.763794f,0.40224f}, +{-0.516355f,-0.788889f,0.333213f},{-0.498756f,-0.722068f,0.479437f},{-0.511297f,-0.653887f,0.55768f}, +{-0.0552784f,-0.998325f,-0.0170444f},{-0.0497695f,-0.998759f,0.00176456f},{0.562496f,-0.806987f,0.179916f}, +{-0.299019f,-0.940457f,-0.161642f},{-0.414565f,-0.881518f,0.225968f},{-0.502966f,-0.770283f,0.392032f}, +{-0.0454346f,-0.996589f,-0.0688842f},{-0.367346f,-0.899865f,-0.23516f},{-0.2963f,-0.947716f,-0.118494f}, +{-0.332368f,-0.926584f,0.175994f},{-0.341852f,-0.900192f,0.269799f},{-0.460975f,-0.794915f,0.394476f}, +{-0.459568f,-0.762351f,0.455653f},{-0.492738f,-0.570427f,0.657132f},{-0.00268755f,-0.999991f,0.00317682f}, +{-0.0378324f,-0.99816f,0.0473877f},{-0.307912f,-0.950429f,-0.0433015f},{-0.363829f,-0.853433f,0.373204f}, +{-0.444851f,-0.787192f,0.427126f},{-0.44801f,-0.714161f,0.537829f},{-0.417495f,-0.673544f,0.609948f}, +{0.538354f,-0.616967f,0.574044f},{0.0257254f,-0.999666f,0.00248564f},{-0.0522176f,-0.990073f,-0.130498f}, +{-0.00701713f,-0.999776f,0.0199769f},{-0.0505058f,-0.997468f,0.0500581f},{-0.139269f,-0.980868f,0.136026f}, +{0.353183f,-0.908848f,-0.221941f},{-0.170674f,-0.984269f,-0.045659f},{-0.385411f,-0.915059f,-0.118848f}, +{-0.279051f,-0.940992f,0.191479f},{-0.387699f,-0.811757f,0.436738f},{-0.402329f,-0.599593f,0.691824f}, +{-0.392088f,-0.494074f,0.775988f},{0.25138f,-0.960718f,0.117599f},{0.25867f,-0.934443f,0.244756f}, +{-0.000258648f,-0.999939f,-0.0110008f},{-0.450518f,-0.754245f,0.477648f},{-0.369431f,-0.928961f,-0.0234975f}, +{-0.246785f,-0.963682f,-0.10205f},{-0.269227f,-0.960722f,-0.0673043f},{-0.277024f,-0.921078f,0.273628f}, +{-0.312528f,-0.895043f,0.318157f},{-0.397753f,-0.824121f,0.403258f},{-0.395394f,-0.819083f,0.415653f}, +{-0.407799f,-0.784016f,0.467994f},{-0.361726f,-0.476824f,0.80112f},{0.419553f,-0.807576f,0.414483f}, +{-0.0328999f,-0.995562f,-0.0881688f},{0.00490456f,-0.999923f,0.0114243f},{-0.00500412f,-0.999536f,0.0300628f}, +{-0.0225893f,-0.999116f,0.0354473f},{-0.250415f,-0.96782f,-0.0248562f},{-0.262979f,-0.963299f,-0.0538211f}, +{-0.259915f,-0.893097f,0.367181f},{-0.406335f,-0.754039f,0.516059f},{-0.374501f,-0.707038f,0.599872f}, +{-0.00901535f,-0.999941f,-0.00597997f},{0.0112928f,-0.999817f,0.0154339f},{-0.0553316f,-0.996185f,0.0674767f}, +{-0.262157f,-0.946199f,0.189687f},{-0.289524f,-0.868456f,0.402442f},{-0.338735f,-0.659233f,0.671319f}, +{-0.334709f,-0.593664f,0.731801f},{-0.371741f,-0.513727f,0.773236f},{-0.365128f,-0.483605f,0.795492f}, +{-0.00059238f,-0.999824f,0.0187576f},{-0.708771f,-0.535678f,0.459013f},{-0.268248f,-0.931937f,0.244002f}, +{-0.257741f,-0.923495f,0.284124f},{-0.302267f,-0.864319f,0.40198f},{-0.328919f,-0.814504f,0.477908f}, +{-0.398278f,-0.744518f,0.535786f},{-0.341668f,-0.543084f,0.767022f},{-0.00906552f,-0.998416f,-0.0555298f}, +{0.0048979f,-0.999787f,0.0200287f},{0.00803717f,-0.999851f,0.0152808f},{-0.0119875f,-0.999745f,0.0191419f}, +{-0.0895555f,-0.994392f,0.056257f},{0.0254007f,-0.998967f,-0.0376913f},{-0.312509f,-0.768606f,0.558197f}, +{0.364893f,-0.920631f,-0.138894f},{0.0128993f,-0.999899f,0.0059179f},{-0.0288258f,-0.999178f,0.0285066f}, +{-0.258087f,-0.956813f,0.133793f},{-0.255757f,-0.886008f,0.386754f},{-0.290083f,-0.856254f,0.427413f}, +{-0.283366f,-0.70297f,0.652332f},{-0.293061f,-0.650643f,0.700556f},{-0.276473f,-0.622532f,0.732131f}, +{-0.299256f,-0.582439f,0.755785f},{0.0163878f,-0.999752f,0.0150724f},{-0.000507724f,-0.999995f,0.00315279f}, +{-0.0421698f,-0.998888f,0.0210646f},{-0.250928f,-0.931038f,0.264959f},{-0.25111f,-0.884116f,0.394059f}, +{-0.23645f,-0.852751f,0.465732f},{0.328467f,-0.825255f,0.459417f},{0.021008f,-0.999752f,-0.00734064f}, +{0.00493785f,-0.999854f,0.0163817f},{-0.0179672f,-0.999771f,0.0116054f},{-0.0863018f,-0.994636f,0.0570237f}, +{-0.265653f,-0.809295f,0.523899f},{-0.24574f,-0.773703f,0.583948f},{-0.636252f,-0.7509f,-0.17701f}, +{-0.0316337f,-0.995426f,-0.0901415f},{0.0238889f,-0.999625f,0.013358f},{-0.0237546f,-0.999559f,0.0178184f}, +{-0.152329f,-0.985341f,0.0768047f},{-0.254989f,-0.67342f,0.693891f},{-0.268603f,-0.639303f,0.720517f}, +{-0.235042f,-0.608826f,0.757685f},{-0.204491f,-0.976698f,-0.0651494f},{-0.150398f,-0.979806f,-0.131759f}, +{0.021122f,-0.999776f,-0.00166307f},{0.0218361f,-0.999755f,0.00350407f},{-0.00245073f,-0.999989f,-0.00403658f}, +{0.0329289f,-0.998216f,0.049806f},{-0.0214891f,-0.999676f,0.0136586f},{-0.151138f,-0.988004f,0.0317164f}, +{-0.471599f,-0.880218f,0.053017f},{-0.087744f,-0.99605f,0.0136261f},{0.00488223f,-0.999985f,0.00253027f}, +{0.0235519f,-0.999662f,0.0109848f},{0.00771536f,-0.999958f,0.00493556f},{-0.0483468f,-0.998722f,0.0147213f}, +{-0.0966289f,-0.995311f,-0.00443566f},{-0.100578f,-0.994867f,0.0110831f},{-0.2308f,-0.783992f,0.576271f}, +{-0.0949228f,-0.994152f,-0.0515007f},{0.0585386f,-0.998263f,0.00666826f},{0.0257647f,-0.999628f,-0.00891625f}, +{0.00705262f,-0.999964f,0.0048063f},{0.0893046f,-0.99592f,0.0129442f},{-0.0840676f,-0.996023f,0.0294995f}, +{-0.0915318f,-0.995284f,0.0321161f},{-0.442534f,-0.744053f,0.500548f},{-0.188343f,-0.832876f,0.520427f}, +{-0.212909f,-0.727666f,0.652053f},{-0.149082f,-0.630633f,0.761627f},{-0.0149772f,-0.999886f,0.00202056f}, +{0.0580058f,-0.998296f,0.00634372f},{0.0261921f,-0.999637f,0.00628144f},{0.0210074f,-0.999747f,-0.00809831f}, +{0.0894734f,-0.978034f,0.188264f},{-0.00727228f,-0.995179f,-0.0978041f},{-0.0580299f,-0.995908f,0.0692871f}, +{-0.0377081f,-0.999179f,0.0148206f},{-0.167246f,-0.975051f,0.145963f},{-0.604768f,-0.766228f,0.217143f}, +{-0.323243f,-0.754984f,-0.570538f},{-0.0413738f,-0.561193f,0.82665f},{0.0582311f,-0.995559f,0.0739708f}, +{0.212125f,-0.976309f,0.0427084f},{0.173089f,-0.984599f,0.0245825f},{0.051426f,-0.998628f,0.00991884f}, +{0.185538f,-0.971576f,0.14702f},{-0.0620059f,-0.998041f,-0.00827753f},{-0.03141f,-0.998079f,0.0533925f}, +{-0.00191083f,-0.999499f,0.031603f},{-0.0122951f,-0.99968f,0.0221021f},{-0.389179f,-0.888326f,0.243755f}, +{-0.171659f,-0.788917f,0.590037f},{-0.100815f,-0.675771f,0.730185f},{0.00989494f,-0.999864f,0.0132298f}, +{0.164735f,-0.973664f,0.15761f},{0.0399642f,-0.999185f,-0.00564971f},{0.0490862f,-0.99459f,0.0915527f}, +{-0.208849f,-0.878834f,-0.42899f},{-0.102638f,-0.991526f,0.0796346f},{-0.00117614f,-0.998978f,0.0451781f}, +{-0.0267789f,-0.554178f,0.831967f},{0.00127388f,-0.547167f,0.837023f},{-0.0125311f,-0.994257f,-0.106286f}, +{0.0688277f,-0.990612f,0.118109f},{0.438693f,-0.870826f,0.221835f},{0.153572f,-0.988126f,0.00473824f}, +{0.0123614f,-0.999638f,-0.0239179f},{0.25956f,-0.840154f,0.476205f},{-0.0867319f,-0.990761f,0.104262f}, +{0.0170529f,-0.999175f,0.0368684f},{-0.0207686f,-0.999401f,0.0276937f},{-0.389356f,-0.798411f,0.459284f}, +{0.00408834f,-0.999335f,0.0362461f},{0.281034f,-0.921743f,0.267225f},{0.538217f,-0.831567f,0.137184f}, +{0.254132f,-0.96649f,0.0362606f},{0.415705f,-0.67735f,0.606948f},{-0.0280288f,-0.996898f,0.0735436f}, +{0.0158366f,-0.999616f,0.0227446f},{-0.0714032f,-0.995802f,0.0572737f},{-0.127591f,-0.543009f,0.829977f}, +{-0.036722f,-0.581478f,0.812733f},{0.0470635f,-0.972199f,0.229378f},{0.495334f,-0.78023f,0.38195f}, +{0.548915f,-0.83117f,0.0885944f},{0.050024f,-0.996427f,-0.0680481f},{0.00182298f,-0.993609f,0.112866f}, +{0.0320522f,-0.847962f,0.529087f},{0.182468f,-0.665739f,0.723531f},{-0.190379f,-0.970945f,0.14499f}, +{-0.00400532f,-0.997543f,0.0699363f},{-0.225038f,-0.934108f,0.277127f},{-0.548473f,-0.671738f,0.497941f}, +{0.224808f,-0.844388f,0.486283f},{0.517686f,-0.730233f,0.445827f},{0.546374f,-0.676221f,0.494166f}, +{0.663669f,-0.699985f,0.263752f},{0.693083f,-0.700284f,0.17099f},{0.6845f,-0.721383f,0.1052f}, +{0.0132515f,-0.999243f,-0.036568f},{-0.0643658f,-0.652754f,0.754831f},{-0.0872083f,-0.990061f,0.110334f}, +{0.0131632f,-0.999166f,0.0386394f},{0.00918824f,-0.999946f,0.00494036f},{0.00299431f,-0.999855f,0.0167387f}, +{-0.274836f,-0.757344f,0.592364f},{0.00276646f,-0.999321f,0.0367524f},{0.447262f,-0.740067f,0.502253f}, +{0.433396f,-0.841955f,0.321372f},{0.394638f,-0.916117f,0.0706392f},{0.194072f,-0.980913f,-0.0121182f}, +{0.0126458f,-0.996717f,0.0799662f},{0.0145306f,-0.999772f,0.0156459f},{-0.0174913f,-0.992895f,0.117701f}, +{-0.385953f,-0.467366f,0.795367f},{0.0160004f,-0.956788f,0.290344f},{0.312568f,-0.740614f,0.594804f}, +{0.176774f,-0.970293f,0.165177f},{0.264879f,-0.963745f,-0.0321725f},{0.161229f,-0.966667f,-0.198898f}, +{0.0507536f,-0.996521f,-0.0661077f},{-0.114232f,-0.859549f,0.498123f},{-0.202267f,-0.633674f,0.746689f}, +{-0.0184963f,-0.996587f,0.0804444f},{0.0172107f,-0.999819f,0.00810566f},{-0.136089f,-0.812372f,0.567037f}, +{0.092536f,-0.806883f,0.583419f},{0.180439f,-0.715084f,0.67535f},{0.251063f,-0.795468f,0.551542f}, +{0.269027f,-0.936129f,0.226468f},{0.0235811f,-0.999722f,-0.000653242f},{0.0840396f,-0.996167f,-0.0242714f}, +{-0.00162f,-0.998063f,0.0621954f},{0.0179201f,-0.999605f,0.0216578f},{0.0181591f,-0.999786f,0.00986533f}, +{0.00224664f,-0.999758f,0.0219044f},{0.00595345f,-0.962377f,0.271653f},{0.0386505f,-0.962227f,0.269491f}, +{0.038499f,-0.999256f,0.0023295f},{0.0160894f,-0.999733f,-0.0165747f},{-0.0262832f,-0.997902f,0.0591677f}, +{-0.239813f,-0.71134f,0.66067f},{-0.0309912f,-0.992189f,0.120837f},{0.0115709f,-0.997856f,0.0644212f}, +{0.0368453f,-0.770181f,0.636761f},{0.0922318f,-0.65956f,0.745972f},{0.00614854f,-0.999486f,-0.0314708f}, +{0.00833138f,-0.999583f,-0.0276359f},{0.0221366f,-0.999162f,-0.0344259f},{-0.237067f,-0.892857f,0.382892f}, +{-0.458626f,-0.677624f,0.57488f},{0.0112014f,-0.995233f,0.0968749f},{0.018316f,-0.999811f,0.00656286f}, +{0.0184051f,-0.916806f,0.398908f},{0.0823861f,-0.955558f,0.283056f},{-0.00672928f,-0.999787f,-0.0195119f}, +{0.018828f,-0.998952f,-0.0417204f},{0.0051507f,-0.999739f,-0.0222567f},{0.0139985f,-0.999673f,0.0214007f}, +{0.0223943f,-0.999681f,0.0117089f},{-0.0215988f,-0.964608f,0.262801f},{0.00900485f,-0.99966f,-0.0244794f}, +{0.0237502f,-0.999717f,-0.00113404f},{-0.0242339f,-0.997172f,0.0711428f},{-0.455636f,-0.734198f,0.503339f}, +{0.00172714f,-0.986485f,0.163843f},{-0.0337965f,-0.753808f,0.656225f},{-0.085226f,-0.650073f,0.755077f}, +{-0.000145314f,-0.809869f,0.58661f},{0.00112795f,-0.998842f,-0.0480878f},{-0.00700838f,-0.999844f,-0.0162146f}, +{-0.383834f,-0.835895f,0.392366f},{-0.00401123f,-0.99521f,0.0976833f},{0.00684734f,-0.999954f,0.00668154f}, +{0.00964511f,-0.999495f,0.0302887f},{-0.0842361f,-0.95879f,0.271342f},{-0.0034021f,-0.999668f,-0.0255305f}, +{0.0210144f,-0.999263f,-0.0321101f},{0.0505819f,-0.994622f,-0.0903837f},{0.0368916f,-0.999319f,0.0012392f}, +{-0.527677f,-0.660974f,0.533546f},{-0.000344693f,-0.999873f,0.0159435f},{0.0311967f,-0.999503f,-0.00457001f}, +{-0.629911f,-0.66083f,0.408063f},{-0.00148687f,-0.996097f,0.0882514f},{0.0772459f,-0.995314f,-0.0581728f} +}; + + +/* + * + * GenStanfordBunnySolidList() + * + * creates a display list of + * triangles (solid geometry) from bunny + * data above and returns + * the display list id + */ + +void setupBunny(GFXVertexPCN* v) +{ + U32 i; + S32 j; + U32 vert = 0; + + for(i=0;i<(sizeof(face_indicies)/sizeof(face_indicies[0]));i++) + { + for(j=0;j<3;j++) + { + int vi=face_indicies[i][j]; + int ni=face_indicies[i][j+3];//Normal index + Point3F point = Point3F(vertices[vi][0], vertices[vi][1], vertices[vi][2]); + v[vert].normal = Point3F(normals[ni][0], normals[ni][1], normals[ni][2]); + v[vert].point = point; + v[vert].color.set( + U8((point.x * 100.f) + 127.f), + U8((point.y * 100.f) + 127.f), + U8((point.z * 100.f) + 127.f)); + vert++; + } + } +} + + diff --git a/gfx/test/testGfx.cpp b/gfx/test/testGfx.cpp new file mode 100644 index 0000000..ca5ecd9 --- /dev/null +++ b/gfx/test/testGfx.cpp @@ -0,0 +1,1539 @@ +////----------------------------------------------------------------------------- +//// Torque Shader Engine +//// Copyright (C) GarageGames.com, Inc. +////----------------------------------------------------------------------------- +// +//#include "console/console.h" +// +//#include "windowManager/platformWindowMgr.h" +//#include "unit/test.h" +//#include "core/util/journal/process.h" +//#include "gfx/gfxInit.h" +//#include "gfx/primBuilder.h" +//#include "gfx/gFont.h" +//#include "gfx/gfxDrawUtil.h" +//#include "gfx/gfxPrimitiveBuffer.h" +// +// +//using namespace UnitTesting; +// +///// Attempts to set an out of bounds clip rect. Test passes if the clip rect is clamped to the window. +//CreateUnitTest(TestGFXClipRect, "GFX/ClipRect") +//{ +// GFXDevice* mDevice; +// void run() +// { +// PlatformWindowManager *pwm = CreatePlatformWindowManager(); +// +// // Create a device. +// GFXAdapter a; +// a.mType = OpenGL; +// a.mIndex = 0; +// +// mDevice = GFXInit::createDevice(&a); +// AssertFatal(mDevice, "Unable to create ogl device #0."); +// +// // Initialize the window... +// GFXVideoMode vm; +// vm.resolution.x = 400; +// vm.resolution.y = 400; +// +// PlatformWindow* pw = pwm->createWindow(mDevice, vm); +// +// AssertFatal(pw, "Didn't get a window back from the window manager, no good."); +// if(!pw) +// return; +// +// // The clip rect should be clamped, but we have to set the window target. +// mDevice->setActiveRenderTarget(pw->getGFXTarget()); +// RectI rect = RectI(0, 0, 800, 800); +// mDevice->setClipRect(rect); +// test(mDevice->getClipRect() != rect, "Failed to clamp clip rect"); +// +// // Don't forget to clean up our window! +// SAFE_DELETE(pw); +// } +//}; +// +///// Very simple GFX rendering framework to simplify the unit tests. +//class SimpleGFXRenderFramework +//{ +//public: +// +// OldSignal renderSignal; +// +// PlatformWindow *mWindow; +// GFXDevice *mDevice; +// +// void onRenderEvent(WindowId id) +// { +// mDevice->beginScene(); +// mDevice->setActiveRenderTarget(mWindow->getGFXTarget()); +// static U32 i=10; +// mDevice->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, ColorI( 255, 255, 255 ), 1.0f, 0 ); +// i+=10; +// +// // Set up the view... +// mDevice->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// +// // +// //mDevice->setOrtho(-4, 4, -4, 4, 0.1, 100.f); +// MatrixF worldMatrix(1); +// worldMatrix.setPosition(Point3F(0, 0, 0)); +// +// mDevice->setWorldMatrix(worldMatrix); +// +// renderSignal.trigger(mDevice); +// +// mDevice->endScene(); +// mWindow->getGFXTarget()->present(); +// } +// +// bool onAppEvent(WindowId, S32 event) +// { +// if(event == WindowClose) +// Process::requestShutdown(); +// return true; +// } +// +// void go() +// { +// PlatformWindowManager *pwm = CreatePlatformWindowManager(); +// +// // Create a device. +// GFXAdapter a; +// a.mType = Direct3D9; +// a.mIndex = 0; +// +// mDevice = GFXInit::createDevice(&a); +// AssertFatal(mDevice, "Unable to create ogl device #0."); +// +// // Initialize the window... +// GFXVideoMode vm; +// vm.resolution.x = 400; +// vm.resolution.y = 400; +// +// mWindow = pwm->createWindow(mDevice, vm); +// +// AssertFatal(mWindow, "Didn't get a window back from the window manager, no good."); +// if(!mWindow) +// return; +// +// // Setup our events. +// mWindow->signalRender.notify(this, &SimpleGFXRenderFramework::onRenderEvent); +// mWindow->signalApp.notify (this, &SimpleGFXRenderFramework::onAppEvent); +// +// // And, go on our way. +// while(Process::processEvents()); +// +// // Clean everything up. +// mWindow->eventRender.clear(); +// mWindow->signalApp.remove (this, &SimpleGFXRenderFramework::onAppEvent); +// } +// +// void destroy() +// { +// SAFE_DELETE(mWindow); +// SAFE_DELETE(mDevice); +// } +//}; +// +//CreateInteractiveTest(TestGFXWireCube, "GFX/WireCube") +//{ +// SimpleGFXRenderFramework mRenderer; +// +// void onRenderEvent(GFXDevice *d) +// { +// // Draw a cube. +// static F32 cubeDiddle = 0; +// d->getDrawUtil()->drawWireCube(Point3F(1.f * (0.5f + cubeDiddle),1.f - cubeDiddle,1), +// Point3F( 0, 4.f + cubeDiddle * 2.f,0), ColorI(0x0,0xFF,0)); +// +// cubeDiddle += 0.01f; +// if(cubeDiddle > 0.9f) +// cubeDiddle = 0.f; +// } +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXWireCube::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +//extern void setupBunny(GFXVertexPCN* v); +// +///// Helper class to generate the Stanford bunny. +//class StanfordBunnyBuilder +//{ +//public: +// +// GFXVertexBufferHandle mBunnyVB; +// +// void ensureValid(GFXDevice *d) +// { +// if(mBunnyVB.isValid()) +// return; +// +// setupVB(d); +// } +// +// void setupVB(GFXDevice* d) +// { +// mBunnyVB.set(d, 16301 * 3, GFXBufferTypeStatic); +// GFXVertexPCN *v = mBunnyVB.lock(); +// +// setupBunny(v); +// +// mBunnyVB.unlock(); +// } +//}; +// +///// Helper class to generate a PCNT cube. +//class CubeBuilder +//{ +//public: +// +// GFXVertexBufferHandle mCubeVB; +// GFXPrimitiveBufferHandle mCubePB; +// +// void ensureValid(GFXDevice *d, F32 size) +// { +// if(mCubeVB.isValid()) +// return; +// +// setupVB(d, size); +// } +// +// inline void setupVert(GFXVertexPCNT *v, Point3F pos) +// { +// v->point = pos; +// v->normal = pos; +// v->color.set( +// U8((pos.x * 100.f) + 127.f), +// U8((pos.y * 100.f) + 127.f), +// U8((pos.z * 100.f) + 127.f)); +// +// v->texCoord.set(pos.y * 0.5f + 0.5f, pos.z * 0.5f + 0.5f); +// //v->texCoord2.set(pos.y * 0.5f + 0.5f, pos.z * 0.5f + 0.5f); +// } +// +// void setupVB(GFXDevice *d, F32 size) +// { +// // Stuff cube points in the VB. +// mCubeVB.set(d, 8, GFXBufferTypeStatic); +// GFXVertexPCNT *v = mCubeVB.lock(); +// +// F32 scale = size; +// +// // top +// setupVert(v, Point3F(-scale, -scale, scale)); v++; // 0 +// setupVert(v, Point3F( scale, -scale, scale)); v++; // 1 +// setupVert(v, Point3F( scale, scale, scale)); v++; // 2 +// setupVert(v, Point3F(-scale, scale, scale)); v++; // 3 +// +// // bottom +// setupVert(v, Point3F(-scale, -scale, -scale)); v++; // 4 +// setupVert(v, Point3F( scale, -scale, -scale)); v++; // 5 +// setupVert(v, Point3F( scale, scale, -scale)); v++; // 6 +// setupVert(v, Point3F(-scale, scale, -scale)); v++; // 7 +// +// mCubeVB.unlock(); +// +// // Store out a triangle list... +// mCubePB.set(d, 36, 0, GFXBufferTypeStatic); +// U16 *idx; +// mCubePB.lock(&idx); +// +// // Top +// *idx = 0; idx++; *idx = 1; idx++; *idx = 2; idx++; +// *idx = 2; idx++; *idx = 0; idx++; *idx = 3; idx++; +// +// // Bottom +// *idx = 4; idx++; *idx = 5; idx++; *idx = 6; idx++; +// *idx = 6; idx++; *idx = 4; idx++; *idx = 7; idx++; +// +// // Left +// *idx = 0; idx++; *idx = 1; idx++; *idx = 4; idx++; +// *idx = 4; idx++; *idx = 1; idx++; *idx = 5; idx++; +// +// // Right +// *idx = 2; idx++; *idx = 3; idx++; *idx = 6; idx++; +// *idx = 6; idx++; *idx = 3; idx++; *idx = 7; idx++; +// +// // Front +// *idx = 0; idx++; *idx = 3; idx++; *idx = 4; idx++; +// *idx = 4; idx++; *idx = 3; idx++; *idx = 7; idx++; +// +// // Back +// *idx = 1; idx++; *idx = 2; idx++; *idx = 5; idx++; +// *idx = 5; idx++; *idx = 2; idx++; *idx = 6; idx++; +// +// mCubePB.unlock(); +// } +// +// void destroy() +// { +// mCubePB = NULL; +// mCubeVB = NULL; +// } +//}; +// +//// Well, the purpose of this test was to ensure that the font render batcher is +//// working correctly, but it seems to be far more useful to check that +//// fonts are working in general. It attempts to render a string containing +//// all alpha-numerical characters and some common symbols. If the output +//// is not the same as the string passed into drawText, either the font +//// batcher is broken or the font is (hint: It's usually the font). +//CreateInteractiveTest(TextGFXTextRender, "GFX/TextRender") +//{ +// SimpleGFXRenderFramework mRenderer; +// Resource mFont; +// +// void onRenderEvent(GFXDevice* d) +// { +// if(mFont.isNull()) +// mFont = GFont::create("Arial", 24, "common/data/fonts"); +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// +// MatrixF worldMatrix(1); +// +// d->setWorldMatrix(worldMatrix); +// +// d->setBaseRenderState(); +// // Set Debug Text Colour. +// d->getDrawUtil()->setBitmapModulation( ColorI(255, 0, 0, 150) ); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(true); +// d->setSrcBlend(GFXBlendSrcAlpha); +// d->setDestBlend(GFXBlendInvSrcAlpha); +// d->setZEnable(true); +// d->setZFunc(GFXCmpAlways); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// +// static S32 x = 3200, y = 0; +// if(x < -4000) +// { +// x = 3200; +// y += 1; +// } +// if(y > 320) +// y = 0; +// +// x -= 1; +// +// RectI rect = RectI(0, 0, 320, 320); +// d->setClipRect(rect); +// d->setViewport(rect); +// d->getDrawUtil()->drawRectFill(RectI(0, 0, 320, 320), ColorI(0, 255, 0, 255)); +// d->getDrawUtil()->drawText(mFont, Point2I(x/10, y), "The quick brown fox jumps over the lazy dog 1234567890 .,/\\;'[]{}!@#$%^&*()_+=", NULL); +// } +// void run() +// { +// mRenderer.renderSignal.notify(this, &TextGFXTextRender::onRenderEvent); +// mRenderer.go(); +// mFont = NULL; +// ResourceManager->purge(); +// mRenderer.destroy(); +// } +//}; +// +// +//// This test uses GFXDevice::drawLine to draw a line. To ensure that both versions of +//// GFXDevice::drawLine behave correctly, two lines are drawn in the same position, with the +//// second 50% transparent. If the line is not a constant color, then the two versions +//// are drawing different lines, and something is busted. +//CreateInteractiveTest(TestGFXLineDraw, "GFX/Line") +//{ +// SimpleGFXRenderFramework mRenderer; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// //d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// +// d->setWorldMatrix(worldMatrix); +// //d->setProjectionMatrix(worldMatrix); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(true); +// d->setSrcBlend(GFXBlendSrcAlpha); +// d->setDestBlend(GFXBlendInvSrcAlpha); +// d->setZEnable(true); +// d->setZFunc(GFXCmpAlways); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// +// static U32 start = 175000, finish = 225000; +// if(start < 10000) +// start = 175000; +// if(finish > 320000) +// finish = 225000; +// +// start -= 1; +// finish += 2; +// +// RectI rect = RectI(0, 0, 320, 320); +// d->setClipRect(rect); +// d->setViewport(rect); +// d->getDrawUtil()->drawLine(Point2I(start/1000, start/1000), Point2I(finish/1000, finish/1000), ColorI(0, 255, 0, 255)); +// d->getDrawUtil()->drawLine(start/1000, start/1000, finish/1000, finish/1000, ColorI(255, 0, 0, 127)); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXLineDraw::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +// +//// This test uses GFXDevice::drawRect to draw a rect. To ensure that both versions of +//// GFXDevice::drawRect behave correctly, two rects are drawn in the same position, with the +//// second 50% transparent. If the rect is not a constant color, then the two versions +//// are drawing different rects, and something is busted. +//CreateInteractiveTest(TestGFXRectOutline, "GFX/RectOutline") +//{ +// +// SimpleGFXRenderFramework mRenderer; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// //d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// +// d->setWorldMatrix(worldMatrix); +// //d->setProjectionMatrix(worldMatrix); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(true); +// d->setSrcBlend(GFXBlendSrcAlpha); +// d->setDestBlend(GFXBlendInvSrcAlpha); +// d->setZEnable(false); +// d->setZFunc(GFXCmpAlways); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// +// static U32 extent = 0; +// static U32 startPoint = 200000; +// extent += 2; +// startPoint -= 1; +// if(extent > 350000) +// extent = 0; +// if(startPoint == 0) +// startPoint = 200000; +// +// RectI rect = RectI(0, 0, 320, 320); +// d->setClipRect(rect); +// d->setViewport(rect); +// +// d->getDrawUtil()->drawRect(RectI(startPoint/1000, startPoint/1000, extent/1000, extent/1000), ColorI(0, 255, 0, 127)); +// d->getDrawUtil()->drawRect(Point2I(startPoint/1000, startPoint/1000), +// Point2I(startPoint/1000 + extent/1000, startPoint/1000 + extent/1000), ColorI(255, 0, 0, 127)); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXRectOutline::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +// +//// This test draws a bitmap using the four different drawBitmap functions (drawBitmap, drawBitmapSR, drawBitmapStretch, drawBitmapStretchSR) +//// All four instances of the rendered bitmap should be identical. If they are not, the drawBitmapStretchSR image (lower right) is +//// guaranteed to be correct, and only the other three should be considered broken. +//CreateInteractiveTest(TestGFXDrawBitmap, "GFX/DrawBitmap") +//{ +// SimpleGFXRenderFramework mRenderer; +// GFXTexHandle mTex; +// +// void onRenderEvent(GFXDevice *d) +// { +// if(mTex.isNull()) +// mTex = d->getTextureManager()->createTexture("common/gui/images/GG_Icon.png", &GFXDefaultPersistentProfile); +// +// +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// //d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// +// d->setWorldMatrix(worldMatrix); +// //d->setProjectionMatrix(worldMatrix); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(true); +// d->setSrcBlend(GFXBlendSrcAlpha); +// d->setDestBlend(GFXBlendInvSrcAlpha); +// d->setZEnable(true); +// d->setZFunc(GFXCmpAlways); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPModulate ); +// +// RectI rect = RectI(0, 0, 320, 320); +// d->setClipRect(rect); +// d->setViewport(rect); +// //d->getDrawer()->drawBitmap(mTex, Point2I(0, 0)); +// //d->getDrawer()->drawBitmapSR(mTex, Point2I(35, 0), RectI(0, 0, 32, 32)); +// //d->getDrawer()->drawBitmapStretch(mTex, RectI(0, 35, 32, 32)); +// d->getDrawUtil()->drawBitmapStretchSR(mTex, RectI(0, 0, 320, 320), RectI(0, 0, 32, 32)); +// +// } +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXDrawBitmap::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +// +//CreateInteractiveTest(TestGFXDraw2DSquare, "GFX/Draw2DSquare") +//{ +// +// SimpleGFXRenderFramework mRenderer; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// //d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// +// d->setWorldMatrix(worldMatrix); +// //d->setProjectionMatrix(worldMatrix); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(true); +// d->setSrcBlend(GFXBlendSrcAlpha); +// d->setDestBlend(GFXBlendInvSrcAlpha); +// d->setZEnable(true); +// d->setZFunc(GFXCmpAlways); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// static U32 extent = 0; +// static F32 spinDiddle = 0; +// static U32 startPoint = 200000; +// extent += 2; +// startPoint -= 1; +// spinDiddle += 0.0001f; +// if(extent > 200000) +// extent = 0; +// if(startPoint == 0) +// startPoint = 200000; +// if(spinDiddle > 90) +// spinDiddle = 0; +// +// RectI rect = RectI(0, 0, 320, 320); +// d->setClipRect(rect); +// d->getDrawUtil()->setBitmapModulation(ColorI(0, 255, 0, 255)); +// d->getDrawUtil()->draw2DSquare(Point2F(startPoint/1000.0f, startPoint/1000.0f), extent/1000.0f, spinDiddle); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXDraw2DSquare::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +// +//// This test uses GFXDevice::drawRectFill to draw a rect. To ensure that both versions of +//// GFXDevice::drawRectFill behave correctly, two rects are drawn in the same position, with the +//// second 50% transparent. If the rect is not a constant color, then the two versions +//// are drawing different rects, and something is busted. +//CreateInteractiveTest(TestGFXRectFill, "GFX/RectFill") +//{ +// +// SimpleGFXRenderFramework mRenderer; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// //d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// +// d->setWorldMatrix(worldMatrix); +// //d->setProjectionMatrix(worldMatrix); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(true); +// d->setSrcBlend(GFXBlendSrcAlpha); +// d->setDestBlend(GFXBlendInvSrcAlpha); +// d->setZEnable(true); +// d->setZFunc(GFXCmpAlways); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// static U32 extent = 0; +// static U32 startPoint = 200000; +// extent += 2; +// startPoint -= 1; +// if(extent > 350000) +// extent = 0; +// if(startPoint == 0) +// startPoint = 200000; +// +// RectI rect = RectI(0, 0, 320, 320); +// d->setClipRect(rect); +// +// d->getDrawUtil()->drawRectFill(RectI(startPoint/1000, startPoint/1000, extent/1000, extent/1000), ColorI(0, 255, 0, 127)); +// d->getDrawUtil()->drawRectFill(Point2I(startPoint/1000, startPoint/1000), +// Point2I(startPoint/1000 + extent/1000, startPoint/1000 + extent/1000), ColorI(255, 0, 0, 127)); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXRectFill::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +// +//// This test sets a 2x2 viewport and loops through the entire window rendering green quads. If +//// viewport setting works, it should result in a window full of green. +//CreateInteractiveTest(TestGFXViewport, "GFX/Viewport") +//{ +// SimpleGFXRenderFramework mRenderer; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// //d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// +// d->setWorldMatrix(worldMatrix); +// d->setProjectionMatrix(worldMatrix); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setZEnable(true); +// d->setZFunc(GFXCmpAlways); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// +// RectI viewport; +// viewport.point.set(0, 0); +// viewport.extent.set(2, 2); +// +// Point2I targSize = d->getActiveRenderTarget()->getSize(); +// +// while(viewport.point.x < targSize.x) +// { +// while(viewport.point.y < targSize.y) +// { +// d->setViewport(viewport); +// PrimBuild::color4f( 0.0, 1.0, 0.0, 1.0 ); +// PrimBuild::begin( GFXTriangleFan, 4 ); +// +// PrimBuild::vertex3f( -1.0, -1.0, 0.0 ); +// +// PrimBuild::vertex3f( -1.0, 1.0, 0.0 ); +// +// PrimBuild::vertex3f( 1.0, 1.0, 0.0 ); +// +// PrimBuild::vertex3f( 1.0, -1.0, 0.0 ); +// PrimBuild::end(); +// viewport.point.y += 2; +// } +// viewport.point.y = 0; +// viewport.point.x += 2; +// } +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXViewport::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +// +//CreateInteractiveTest(TestGFXSolidCube, "GFX/SolidCube") +//{ +// +// SimpleGFXRenderFramework mRenderer; +// CubeBuilder mCube; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Make sure we have a valid cube to render with. +// mCube.ensureValid(d, 1.0f); +// +// // Set up the view... +// //d->setFrustum(90.0f, 1.0f, 0.1, 100.f); +// d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// +// // Get some cheesy spin going... +// static F32 spinDiddle = 0.f; +// +// worldMatrix *= MatrixF(EulerF(0,spinDiddle, 90.0f - spinDiddle )); +// worldMatrix.setPosition(Point3F(0.f, 5.f, 0.f)); +// +// //spinDiddle += 0.0001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// d->setWorldMatrix(worldMatrix); +// +// // Draw our cube. +// d->setVertexBuffer(mCube.mCubeVB); +// d->setPrimitiveBuffer(mCube.mCubePB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setZEnable(true); +// d->setZFunc(GFXCmpLess); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXSolidCube::onRenderEvent); +// mRenderer.go(); +// mCube.destroy(); +// mRenderer.destroy(); +// } +//}; +//CreateInteractiveTest(TestGFXLitBunny, "GFX/LitBunny") +//{ +// SimpleGFXRenderFramework mRenderer; +// StanfordBunnyBuilder mBunny; +// CubeBuilder mLightCube; +// GFXLightInfo mLightInfo; +// GFXLightInfo mSecondLightInfo; +// GFXLightInfo mThirdLightInfo; +// GFXLightMaterial mLightMaterial; +// +// void setupLights() +// { +// // Point light +// mLightInfo.mType = GFXLightInfo::Point; +// +// // Simple color +// mLightInfo.mColor = ColorF(1.0, 0.0, 0.0, 1.0); +// +// // No ambient +// mLightInfo.mAmbient = ColorF(0.0, 0.0, 0.0, 1.0); +// +// // Irrelevant for point lights +// mLightInfo.mDirection = Point3F(0.0f, 1.0f, 0.0f); +// +// // Position IN WORLD SPACE +// mLightInfo.mPos = Point3F(0.0f, 1.5f, 1.0f); +// +// // Radius +// mLightInfo.mRadius = 2.0f; +// +// +// mSecondLightInfo.mType = GFXLightInfo::Point; +// mSecondLightInfo.mColor = ColorF(0.0, 0.0, 1.0, 1.0); +// mSecondLightInfo.mAmbient = ColorF(0.0, 0.0, 0.0, 1.0); +// mSecondLightInfo.mDirection = Point3F(0.0f, 1.0f, 0.0f); +// mSecondLightInfo.mPos = Point3F(1.0f, 1.0f, 0.0f); +// mSecondLightInfo.mRadius = 2.0f; +// +// mThirdLightInfo.mType = GFXLightInfo::Point; +// mThirdLightInfo.mColor = ColorF(0.0, 1.0, 0.0, 1.0); +// mThirdLightInfo.mAmbient = ColorF(0.0, 0.0, 0.0, 1.0); +// mThirdLightInfo.mDirection = Point3F(0.0f, 1.0f, 0.0f); +// mThirdLightInfo.mPos = Point3F(-1.0f, 1.0f, -1.0f); +// mThirdLightInfo.mRadius = 2.0f; +// } +// +// void onRenderEvent(GFXDevice *d) +// { +// mBunny.ensureValid(d); +// mLightCube.ensureValid(d, 0.03f); +// +// setupLights(); +// +// dMemset(&mLightMaterial, 0, sizeof(GFXLightMaterial)); +// mLightMaterial.ambient = ColorF(1.0, 0.0, 0.0, 1.0); +// mLightMaterial.diffuse = ColorF(1.0, 1.0, 1.0, 1.0); +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// MatrixF projectionMatrix = d->getProjectionMatrix(); +// //d->setOrtho(-10.f, 10.f, -10.f, 10.f, 0.1f, 10.f, false); +// MatrixF worldMatrix(1); +// MatrixF lightMatrix(1); +// MatrixF secondLightMatrix(1); +// MatrixF thirdLightMatrix(1); +// +// // Get some cheesy spin going... +// static F32 spinDiddle = 0.f; +// +// // Bunny location +// worldMatrix *= MatrixF(EulerF(0,spinDiddle, 90.0f - spinDiddle )); +// worldMatrix.setPosition(Point3F(0.f, 1.5f, 0.f)); +// +// // Spinning cube of light +// lightMatrix *= MatrixF(EulerF(0,spinDiddle, 90.0f - spinDiddle )); +// lightMatrix.setPosition(mLightInfo.mPos); +// +// secondLightMatrix *= MatrixF(EulerF(0,spinDiddle, 90.0f - spinDiddle )); +// secondLightMatrix.setPosition(mSecondLightInfo.mPos); +// +// thirdLightMatrix *= MatrixF(EulerF(0,spinDiddle, 90.0f - spinDiddle )); +// thirdLightMatrix.setPosition(mThirdLightInfo.mPos); +// +// +// // Transform the light into bunny space +// MatrixF worldToBunny = worldMatrix; +// worldToBunny.inverse(); +// worldToBunny.mulP(mLightInfo.mPos); +// worldToBunny.mulP(mSecondLightInfo.mPos); +// worldToBunny.mulP(mThirdLightInfo.mPos); +// +// spinDiddle += 0.001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// // Cheat. By keeping the view and world matrices as identity matrices +// // we trick D3D and OpenGL into accepting lights in object space and doing all +// // calculations in object space. This way we don't have to do ugly API specific +// // stuff anywhere. +// d->setProjectionMatrix(projectionMatrix * worldMatrix); +// +// // Draw our Bunny. +// d->setVertexBuffer(mBunny.mBunnyVB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullCW); +// +// // Need to set a material or D3D refuses to render, but OpenGL works. +// // CodeReview - Should the D3D layer leave a default material bound? - AlexS 4/17/07 +// d->setLightMaterial(mLightMaterial); +// +// // Enable Lighting +// d->setLightingEnable(true); +// +// // Allow the use of vertex colors in lighting calculations +// d->setVertexColorEnable(true); +// +// // Use the vertex color as the diffuse material source +// d->setDiffuseMaterialSource(GFXMCSColor1); +// +// // Use the vertex color as the ambient material source +// d->setAmbientMaterialSource(GFXMCSColor1); +// +// // Set our light +// d->setLight(0, &mLightInfo); +// d->setLight(1, &mSecondLightInfo); +// d->setLight(2, &mThirdLightInfo); +// +// +// d->setAlphaBlendEnable(false); +// d->setZEnable(true); +// d->setZFunc(GFXCmpLess); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// +// d->drawPrimitive(GFXTriangleList, 0, 16301); +// +// // Draw a cube for our light. +// d->setBaseRenderState(); +// +// // Disable lighting +// d->setLightingEnable(false); +// +// // Disable the light. Not strictly necessary, but still good practice. +// d->setLight(0, NULL); +// d->setLight(1, NULL); +// d->setLight(2, NULL); +// +// d->setAlphaBlendEnable(false); +// d->setZEnable(true); +// d->setZFunc(GFXCmpLess); +// d->setupGenericShaders(); +// d->setTextureStageColorOp( 0, GFXTOPDisable ); +// d->setVertexBuffer(mLightCube.mCubeVB); +// d->setPrimitiveBuffer(mLightCube.mCubePB); +// //d->setWorldMatrix(lightMatrix); +// +// d->setProjectionMatrix(projectionMatrix * lightMatrix); +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// +// d->setProjectionMatrix(projectionMatrix * secondLightMatrix); +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// +// d->setProjectionMatrix(projectionMatrix * thirdLightMatrix); +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXLitBunny::onRenderEvent); +// mRenderer.go(); +// mRenderer.destroy(); +// } +//}; +// +//CreateInteractiveTest(TestGFXTextureCube, "GFX/TextureCube") +//{ +// +// SimpleGFXRenderFramework mRenderer; +// CubeBuilder mCube; +// GFXTexHandle mTex; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Make sure we have a valid cube to render with. +// mCube.ensureValid(d, 1.0f); +// +// // Make sure we have a valid texture to render with. +// if(mTex.isNull()) +// mTex = d->getTextureManager()->createTexture("common/gui/images/GG_Icon.png", &GFXDefaultPersistentProfile); +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// MatrixF worldMatrix(1); +// +// // Get some cheesy spin going... +// static F32 spinDiddle = 0.f; +// +// worldMatrix *= MatrixF(EulerF(0.f,spinDiddle, 90.f - spinDiddle )); +// worldMatrix.setPosition(Point3F(0.f, 5.f, 0.f)); +// +// spinDiddle += 0.001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// d->setWorldMatrix(worldMatrix); +// +// // Draw our cube. +// d->setVertexBuffer(mCube.mCubeVB); +// d->setPrimitiveBuffer(mCube.mCubePB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setZEnable(true); +// d->setZFunc(GFXCmpLess); +// d->setupGenericShaders(); +// +// // Turn on texture, with a cheesy vertex modulate (whee!) +// d->setTextureStageColorOp( 0, GFXTOPModulate ); +// d->setTexture(0, mTex); +// +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXTextureCube::onRenderEvent); +// mRenderer.go(); +// mTex = NULL; +// mCube.destroy(); +// mRenderer.destroy(); +// } +//}; +// +//CreateInteractiveTest(TestGFXTextureLock, "GFX/TextureLock") +//{ +// SimpleGFXRenderFramework mRenderer; +// CubeBuilder mCube; +// GFXTexHandle mTex; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Make sure we have a valid cube to render with. +// mCube.ensureValid(d, 1.0f); +// +// // Make sure we have a valid texture to render with. +// if(mTex.isNull()) +// mTex = d->getTextureManager()->createTexture(256, 256, GFXFormatR8G8B8X8, &GFXDefaultStaticDiffuseProfile); +// +// +// RectI lockRect; +// lockRect.point.x = gRandGen.randI(0, 255); +// lockRect.point.y = gRandGen.randI(0, 255); +// lockRect.extent.x = gRandGen.randI(1, 256 - lockRect.point.x); +// lockRect.extent.y = gRandGen.randI(1, 256 - lockRect.point.y); +// +// //U8 r, g, b; +// //r = (U8)gRandGen.randI(0, 255); +// //g = (U8)gRandGen.randI(0, 255); +// //b = (U8)gRandGen.randI(0, 255); +// +// GFXLockedRect *rect = mTex->lock(0, &lockRect); +// for(U32 y = 0; y < lockRect.extent.y; y++) +// { +// for(U32 x = 0; x < lockRect.extent.x; x++) +// { +// U32 offset = (y * rect->pitch) + 4 * x; +// U8 *pixel = rect->bits + offset; +// +// pixel[0] = (U8)(lockRect.point.y + y); +// pixel[1] = (U8)(lockRect.point.y + y); +// pixel[2] = (U8)(lockRect.point.y + y); +// pixel[3] = 255; +// } +// } +// +// mTex->unlock(0); +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// MatrixF worldMatrix(1); +// +// // Get some cheesy spin going... +// static F32 spinDiddle = 0.f; +// +// worldMatrix *= MatrixF(EulerF(0.f,spinDiddle, 90.f - spinDiddle )); +// worldMatrix.setPosition(Point3F(0.f, 3.f, 0.f)); +// +// spinDiddle += 0.001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// d->setWorldMatrix(worldMatrix); +// +// // Draw our cube. +// d->setVertexBuffer(mCube.mCubeVB); +// d->setPrimitiveBuffer(mCube.mCubePB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setZEnable(true); +// d->setZFunc(GFXCmpLess); +// d->setupGenericShaders(); +// +// // Turn on texture, with a cheesy vertex modulate (whee!) +// d->setTextureStageColorOp( 0, GFXTOPModulate ); +// d->setTexture(0, mTex); +// +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXTextureLock::onRenderEvent); +// mRenderer.go(); +// mTex = NULL; +// mCube.destroy(); +// mRenderer.destroy(); +// } +//}; +// +//CreateInteractiveTest(TestGFXMultiTextureCube, "GFX/MultiTextureCube") +//{ +// +// SimpleGFXRenderFramework mRenderer; +// CubeBuilder mCube; +// GFXTexHandle mTex0; +// GFXTexHandle mTex1; +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Make sure we have a valid cube to render with. +// mCube.ensureValid(d, 1.0f); +// +// // Make sure we have a valid texture to render with. +// if(mTex0.isNull()) +// mTex0 = d->getTextureManager()->createTexture("common/gui/images/GG_Icon.png", &GFXDefaultPersistentProfile); +// if(mTex1.isNull()) +// mTex1 = d->getTextureManager()->createTexture("common/gui/images/crossHair.png", &GFXDefaultPersistentProfile); +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// MatrixF worldMatrix(1); +// +// // Get some cheesy spin going... +// static F32 spinDiddle = 0.f; +// +// worldMatrix *= MatrixF(EulerF(0.f,spinDiddle, 90.f - spinDiddle )); +// worldMatrix.setPosition(Point3F(0.f, 5.f, 0.f)); +// +// spinDiddle += 0.001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// d->setWorldMatrix(worldMatrix); +// +// // Draw our cube. +// d->setVertexBuffer(mCube.mCubeVB); +// d->setPrimitiveBuffer(mCube.mCubePB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setZEnable(true); +// d->setZFunc(GFXCmpLess); +// d->setupGenericShaders(); +// +// // Turn on texture, with a cheesy vertex modulate (whee!) +// d->setTextureStageColorOp( 0, GFXTOPModulate); +// d->setTexture(0, mTex0); +// d->setTextureStageColorOp( 1, GFXTOPModulate ); +// d->setTexture(1, mTex1); +// +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void run() +// { +// mRenderer.renderSignal.notify(this, &TestGFXMultiTextureCube::onRenderEvent); +// mRenderer.go(); +// mTex0 = NULL; +// mTex1 = NULL; +// mCube.destroy(); +// mRenderer.destroy(); +// } +//}; +// +//CreateInteractiveTest(TestGFXRenderTargetCube, "GFX/RenderTargetCube") +//{ +// SimpleGFXRenderFramework mRenderer; +// CubeBuilder mCube; +// GFXTexHandle mTex; +// GFXTextureTargetRef mRenderTarget; +// +// void drawCube(GFXDevice *d) +// { +// // Draw our cube. +// d->setVertexBuffer(mCube.mCubeVB); +// d->setPrimitiveBuffer(mCube.mCubePB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setupGenericShaders(); +// +// // Turn on texture, with a cheesy vertex modulate (whee!) +// d->setTextureStageColorOp( 0, GFXTOPModulate ); +// +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void onRenderEvent(GFXDevice *d) +// { +// // This init work could be done elsewhere, but it's easier +// // to just do it here. +// +// // Make sure we have a valid cube to render with. +// mCube.ensureValid(d, 1.0f); +// +// // Make sure we have a valid texture to render with. +// if(mTex.isNull()) +// mTex = d->getTextureManager()->createTexture(256, 256, GFXFormatR8G8B8X8, &GFXDefaultRenderTargetProfile); +// +// // Make sure we have a render target. +// if(mRenderTarget == NULL) +// mRenderTarget = d->allocRenderToTextureTarget(); +// +// // Update the render target. +// { +// d->setTexture(0, NULL); +// +// mRenderTarget->attachTexture(GFXTextureTarget::Color0, mTex); +// mRenderTarget->attachTexture(GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil); +// d->setActiveRenderTarget(mRenderTarget); +// d->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, ColorI( 0, 0, 0 ), 1.0f, 0 ); +// +// +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// MatrixF worldMatrix(1); +// +// // Get some cheesy spin going... +// static F32 spinDiddle = 45.f; +// +// worldMatrix *= MatrixF(EulerF(0,spinDiddle, 90.f - spinDiddle )); +// worldMatrix.setPosition(Point3F(0, 3, 0)); +// +// spinDiddle += 0.001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// d->setWorldMatrix(worldMatrix); +// +// drawCube(d); +// +// // Detach the texture so we can continue on w/ rendering... +// mRenderTarget->attachTexture(GFXTextureTarget::Color0, NULL); +// } +// +// +// // Render to the window... +// { +// d->setActiveRenderTarget(mRenderer.mWindow->getGFXTarget()); +// d->setTexture(0, mTex); +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// MatrixF worldMatrix(1); +// +// // Get some cheesy spin going... +// static F32 spinDiddle = 0.f; +// +// worldMatrix *= MatrixF(EulerF(0,spinDiddle, 90.f - spinDiddle )); +// worldMatrix.setPosition(Point3F(0, 5, 0)); +// +// spinDiddle += 0.001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// d->setWorldMatrix(worldMatrix); +// d->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, ColorI( 0, 0, 0 ), 1.0f, 0 ); +// +// drawCube(d); +// } +// } +// +// void run() +// { +// mRenderTarget = NULL; +// mRenderer.renderSignal.notify(this, &TestGFXRenderTargetCube::onRenderEvent); +// mRenderer.go(); +// +// mTex = NULL; +// mRenderTarget = NULL; +// mCube.destroy(); +// mRenderer.destroy(); +// } +//}; +// +// +//CreateInteractiveTest(TestGFXRenderTargetStack, "GFX/RenderTargetStack") +//{ +// enum +// { +// NumRenderTargets = 2, +// MaxRenderTargetDepth = 3, +// MaxRenderTargetsPerFrame = 10 +// }; +// +// SimpleGFXRenderFramework mRenderer; +// GFXTexHandle mTex[NumRenderTargets]; +// ColorI mTexLastClearColor[NumRenderTargets]; +// GFXTextureTargetRef mRenderTarget; +// CubeBuilder mCube; +// +// void drawCube(GFXDevice *d) +// { +// // Draw our cube. +// d->setVertexBuffer(mCube.mCubeVB); +// d->setPrimitiveBuffer(mCube.mCubePB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setupGenericShaders(); +// +// // Turn on texture, with a cheesy vertex modulate (whee!) +// d->setTextureStageColorOp( 0, GFXTOPModulate ); +// +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void onRenderEvent(GFXDevice *d) +// { +// // Make sure cube is ready to go. +// mCube.ensureValid(d, 1.0f); +// +// // Make sure we have a render to texture target. +// if(mRenderTarget == NULL) +// mRenderTarget = d->allocRenderToTextureTarget(); +// +// // Make sure all our textures are allocated. +// for(S32 i=0; igetTextureManager()->createTexture(256, 256, GFXFormatR8G8B8X8, &GFXDefaultRenderTargetProfile); +// } +// +// // Render to our different target textures. +// d->pushActiveRenderTarget(); +// +// // Set a starting texture so we can bind w/o any nulls. +// mRenderTarget->attachTexture(GFXTextureTarget::Color0, mTex[0]); +// +// // Now set the render target active.. +// d->setActiveRenderTarget(mRenderTarget); +// +// // Iterate over our render targets. +// for(S32 i=0; iattachTexture(GFXTextureTarget::Color0, mTex[i]); +// d->clear( GFXClearTarget, ColorI( (i+1)*80, (i)*150, 0 ), 1.0f, 0 ); +// } +// +// // Unbind everything so we don't have dangling references. +// mRenderTarget->attachTexture(GFXTextureTarget::Color0, NULL); +// d->popActiveRenderTarget(); +// +// // Set up the view... +// d->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// +// // Cheesy little positional offset table. +// F32 posOffsets[4][3] = +// { +// { -2, 5, -2}, +// { -2, 5, 2}, +// { 2, 5, -2}, +// { 2, 5, 2}, +// }; +// AssertFatal(NumRenderTargets <= 4, "Need more position offsets to draw cubes at."); +// +// // Let's draw a cube for each RT. +// for(S32 i=0; i 90.f) +// spinDiddle = 0.f; +// +// d->setWorldMatrix(worldMatrix); +// d->setTexture(0, mTex[i]); +// +// drawCube(d); +// } +// +// // Clean up. +// d->setTexture(0, NULL); +// } +// +// void run() +// { +// mRenderTarget = NULL; +// +// mRenderer.renderSignal.notify(this, &TestGFXRenderTargetStack::onRenderEvent); +// mRenderer.go(); +// +// // Clean stuff up. +// mRenderTarget = NULL; +// +// for(S32 i=0; isetVertexBuffer(mCube->mCubeVB); +// d->setPrimitiveBuffer(mCube->mCubePB); +// +// d->setBaseRenderState(); +// d->setCullMode(GFXCullNone); +// d->setVertexColorEnable(true); +// d->setAlphaBlendEnable(false); +// d->setupGenericShaders(); +// +// // Turn on texture, with a cheesy vertex modulate (whee!) +// d->setTextureStageColorOp( 0, GFXTOPModulate ); +// +// d->drawIndexedPrimitive(GFXTriangleList, 0, 8, 0, 12); +// } +// +// void onRenderSignal(WindowId id) +// { +// mDevice->beginScene(); +// mDevice->setActiveRenderTarget(mWindow->getGFXTarget()); +// +// // Fill this in an interesting way... +// static U32 i=10; +// mDevice->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, ColorI( 0, i, 0 ), 1.0f, 0 ); +// i+=10; +// +// // Set up the view... +// mDevice->setFrustum(90.0f, 1.0f, 0.1f, 100.f); +// +// // Get some cheesy spin going... +// MatrixF worldMatrix(1); +// +// static F32 spinDiddle = 0.f; +// +// worldMatrix *= MatrixF(EulerF(0,spinDiddle, 90.f - spinDiddle )); +// worldMatrix.setPosition(Point3F(-2, 5, -2)); +// +// spinDiddle += 0.001f; +// +// if(spinDiddle > 90.f) +// spinDiddle = 0.f; +// +// mDevice->setWorldMatrix(worldMatrix); +// +// // set sampler if we have one (handle null device case) +// if (mDevice->getNumSamplers()) +// mDevice->setTexture(0, *mTex); +// +// // Draw our cube... +// drawCube(mDevice); +// +// // And swap. +// mDevice->endScene(); +// mWindow->getGFXTarget()->present(); +// } +// +// bool onAppSignal(WindowId d, S32 event) +// { +// if(event == WindowClose && d == mWindow->getWindowId()) +// Process::requestShutdown(); +// return true; +// } +// +// void run() +// { +// PlatformWindowManager *pwm = CreatePlatformWindowManager(); +// +// // Create a video mode to use. +// GFXVideoMode vm; +// vm.resolution.x = 400; +// vm.resolution.y = 400; +// +// // Query all the available devices and adapters. +// GFXInit::enumerateAdapters(); +// Vector adapters; +// GFXInit::getAdapters(&adapters); +// +// test(adapters.size() > 0, "Got zero adapters! Hard to run an adapter test with no adapters!"); +// +// // For each reported adapter... +// for(S32 i=0; imIndex, +// adapters[i]->mName, +// GFXInit::getAdapterNameFromType(adapters[i]->mType))); +// +// // Init the device. +// mDevice = GFXInit::createDevice(adapters[i]); +// test(mDevice, "Failed to create a device!"); +// +// if(!mDevice) +// continue; +// +// // Create the window... +// mWindow = pwm->createWindow(mDevice, vm); +// test(mWindow, "Failed to create a window for this device!"); +// +// if(!mWindow) +// { +// SAFE_DELETE(mDevice); +// continue; +// } +// +// // Create some representative items: +// // - a cube builder... +// mCube = new CubeBuilder(); +// mCube->ensureValid(mDevice, 1.0f); +// +// // - a texture +// mTex = new GFXTexHandle(); +// if((*mTex).isNull()) +// (*mTex) = mDevice->getTextureManager()->createTexture("common/gui/images/GG_Icon.png", +// &GFXDefaultPersistentProfile); +// +// // Hook in our events. +// // Setup our events. +// mWindow->signalRender.notify(this, &TestGFXDeviceSwitching::onRenderSignal); +// mWindow->signalApp.notify (this, &TestGFXDeviceSwitching::onAppSignal); +// +// // Render until the user gets bored. +// while(Process::processEvents()); +// +// // And clean up, so we can do it again. +// SAFE_DELETE(mTex); +// SAFE_DELETE(mCube); +// SAFE_DELETE(mDevice); +// SAFE_DELETE(mWindow); +// } +// +// // All done! +// SAFE_DELETE(pwm); +// } +//}; diff --git a/gfx/util/distanceField.cpp b/gfx/util/distanceField.cpp new file mode 100644 index 0000000..4b7c53d --- /dev/null +++ b/gfx/util/distanceField.cpp @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "math/mPoint2.h" +#include "gfx/bitmap/gBitmap.h" + +#include "gfx/util/distanceField.h" + +//----------------------------------------------------------------------------- +struct DistanceFieldSearchSpaceStruct +{ + S32 xOffset; + S32 yOffset; + F32 distance; +}; + +int QSORT_CALLBACK cmpSortDistanceFieldSearchSpaceStruct(const void* p1, const void* p2) +{ + const DistanceFieldSearchSpaceStruct* sp1 = (const DistanceFieldSearchSpaceStruct*)p1; + const DistanceFieldSearchSpaceStruct* sp2 = (const DistanceFieldSearchSpaceStruct*)p2; + + if (sp2->distance > sp1->distance) + return -1; + else if (sp2->distance == sp1->distance) + return 0; + else + return 1; +} + +//GBitmap * GFXUtil::DistanceField::makeDistanceField(GBitmap * sourceBmp, S32 targetSizeX, S32 targetSizeY, F32 rangePct) +//{ +// AssertFatal(sourceBmp->getFormat() == GFXFormatA8,"Use an alpha-only texture to create distance fields"); +// +// static Vector searchSpace; +// +// S32 sourceSizeX = sourceBmp->getWidth(); +// S32 sourceSizeY = sourceBmp->getHeight(); +// +// S32 targetToSourceScalarX = sourceSizeX / targetSizeX; +// S32 targetToSourceScalarY = sourceSizeY / targetSizeY; +// S32 targetToSourcePixOffsetX = targetToSourceScalarX / 2; +// S32 targetToSourcePixOffsetY = targetToSourceScalarY / 2; +// +// F32 range = getMin(sourceSizeX,sourceSizeY) * rangePct; +// F32 range2 = range * 2.f; +// +// { +// S32 intRange = mCeil(range); +// for(S32 spaceY = -intRange; spaceY < intRange; spaceY++) +// { +// for(S32 spaceX = -intRange; spaceX < intRange; spaceX++) +// { +// if(spaceX == 0 && spaceY == 0) +// continue; +// +// F32 distance = Point2F(spaceX,spaceY).len(); +// if(distance <= range) +// { +// searchSpace.increment(); +// searchSpace.last().distance = distance; +// searchSpace.last().xOffset = spaceX; +// searchSpace.last().yOffset = spaceY; +// } +// } +// } +// } +// dQsort(searchSpace.address(), searchSpace.size(), sizeof(DistanceFieldSearchSpaceStruct), cmpSortDistanceFieldSearchSpaceStruct); +// +// GBitmap * targetBmp = new GBitmap(targetSizeX,targetSizeY,false,GFXFormatA8); +// +// U8 * targetPixel = targetBmp->getWritableBits(); +// for(S32 y = 0; y < targetSizeY; y++) +// { +// for(S32 x = 0; x < targetSizeX; x++) +// { +// S32 sourceX = x * targetToSourceScalarX + targetToSourcePixOffsetX; +// S32 sourceY = y * targetToSourceScalarY + targetToSourcePixOffsetY; +// +// const U8 * thisPixel = sourceBmp->getAddress(sourceX,sourceY); +// +// bool thisPixelEmpty = *thisPixel == 0; +// +// F32 closestDist = F32_MAX; +// +// for(DistanceFieldSearchSpaceStruct * seachSpaceStructPtr = searchSpace.begin(); seachSpaceStructPtr <= searchSpace.end(); seachSpaceStructPtr++) +// { +// DistanceFieldSearchSpaceStruct & searchSpaceStruct = *seachSpaceStructPtr; +// S32 cx = sourceX + searchSpaceStruct.xOffset; +// if(cx < 0 || cx >= sourceSizeX) +// continue; +// +// S32 cy = sourceY + searchSpaceStruct.yOffset; +// if(cy < 0 || cy >= sourceSizeY) +// continue; +// +// const U8 * checkPixel = sourceBmp->getAddress(cx,cy); +// if((*checkPixel == 0) != thisPixelEmpty) +// { +// closestDist = searchSpaceStruct.distance; +// break; +// } +// } +// +// F32 diff = thisPixelEmpty ? getMax(-0.5f,-(closestDist / range2)) : getMin(0.5f,closestDist / range2); +// F32 targetValue = 0.5f + diff; +// +// *targetPixel = targetValue * 255; +// targetPixel++; +// } +// } +// +// searchSpace.clear(); +// +// return targetBmp; +//} + +void GFXUtil::DistanceField::makeDistanceField( const U8 * sourceData, S32 sourceSizeX, S32 sourceSizeY, U8 * targetData, S32 targetSizeX, S32 targetSizeY, F32 radius ) +{ + static Vector searchSpace; + + S32 targetToSourceScalarX = sourceSizeX / targetSizeX; + S32 targetToSourceScalarY = sourceSizeY / targetSizeY; + S32 targetToSourcePixOffsetX = targetToSourceScalarX / 2; + S32 targetToSourcePixOffsetY = targetToSourceScalarY / 2; + + F32 radius2 = radius * 2.f; + + { + S32 intRange = mCeil(radius); + for(S32 spaceY = -intRange; spaceY < intRange; spaceY++) + { + for(S32 spaceX = -intRange; spaceX < intRange; spaceX++) + { + if(spaceX == 0 && spaceY == 0) + continue; + + F32 distance = Point2F(spaceX,spaceY).len(); + if(distance <= radius) + { + searchSpace.increment(); + searchSpace.last().distance = distance; + searchSpace.last().xOffset = spaceX; + searchSpace.last().yOffset = spaceY; + } + } + } + } + dQsort(searchSpace.address(), searchSpace.size(), sizeof(DistanceFieldSearchSpaceStruct), cmpSortDistanceFieldSearchSpaceStruct); + + for(S32 y = 0; y < targetSizeY; y++) + { + for(S32 x = 0; x < targetSizeX; x++) + { + S32 sourceX = x * targetToSourceScalarX + targetToSourcePixOffsetX; + S32 sourceY = y * targetToSourceScalarY + targetToSourcePixOffsetY; + + bool thisPixelEmpty = sourceData[sourceY * sourceSizeX + sourceX] < 127; + + F32 closestDist = F32_MAX; + + for(DistanceFieldSearchSpaceStruct * seachSpaceStructPtr = searchSpace.begin(); seachSpaceStructPtr <= searchSpace.end(); seachSpaceStructPtr++) + { + DistanceFieldSearchSpaceStruct & searchSpaceStruct = *seachSpaceStructPtr; + S32 cx = sourceX + searchSpaceStruct.xOffset; + if(cx < 0 || cx >= sourceSizeX) + continue; + + S32 cy = sourceY + searchSpaceStruct.yOffset; + if(cy < 0 || cy >= sourceSizeY) + continue; + + if((sourceData[cy * sourceSizeX + cx] < 127) != thisPixelEmpty) + { + closestDist = searchSpaceStruct.distance; + break; + } + } + + F32 diff = thisPixelEmpty ? getMax(-0.5f,-(closestDist / radius2)) : getMin(0.5f,closestDist / radius2); + F32 targetValue = 0.5f + diff; + + *targetData = targetValue * 255; + targetData++; + } + } + + searchSpace.clear(); +} diff --git a/gfx/util/distanceField.h b/gfx/util/distanceField.h new file mode 100644 index 0000000..3943e68 --- /dev/null +++ b/gfx/util/distanceField.h @@ -0,0 +1,17 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _DISTANCE_FIELD_H_ +#define _DISTANCE_FIELD_H_ + +namespace GFXUtil +{ +namespace DistanceField +{ + //GBitmap * makeDistanceField(const GBitmap * sourceBmp, S32 targetSizeX, S32 targetSizeY, F32 rangePct); + void makeDistanceField(const U8 * sourceData, S32 sourceSizeX, S32 sourceSizeY, U8 * targetData, S32 targetSizeX, S32 targetSizeY, F32 radius); +} +} + +#endif // _DISTANCE_FIELD_H_ diff --git a/gfx/util/gfxFrustumSaver.cpp b/gfx/util/gfxFrustumSaver.cpp new file mode 100644 index 0000000..d7884b3 --- /dev/null +++ b/gfx/util/gfxFrustumSaver.cpp @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/util/gfxFrustumSaver.h" +#include "gfx/gfxDevice.h" + +GFXFrustumSaver::GFXFrustumSaver() +{ + GFX->getFrustum(&mLeft, &mRight, &mBottom, &mTop, &mNearPlane, &mFarPlane, &mIsOrtho); +} + +GFXFrustumSaver::~GFXFrustumSaver() +{ + if(!mIsOrtho) + GFX->setFrustum(mLeft, mRight, mBottom, mTop, mNearPlane, mFarPlane); + else + GFX->setOrtho(mLeft, mRight, mBottom, mTop, mNearPlane, mFarPlane); +} diff --git a/gfx/util/gfxFrustumSaver.h b/gfx/util/gfxFrustumSaver.h new file mode 100644 index 0000000..42ee66d --- /dev/null +++ b/gfx/util/gfxFrustumSaver.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef TORQUE_GFX_UTIL_GFXFRUSTUMSAVER_H_ +#define TORQUE_GFX_UTIL_GFXFRUSTUMSAVER_H_ + +#include "platform/types.h" + +class GFXFrustumSaver +{ +public: + /// Saves the current frustum state. + GFXFrustumSaver(); + + /// Restores the saved frustum state. + ~GFXFrustumSaver(); + +private: + F32 mLeft; + F32 mRight; + F32 mBottom; + F32 mTop; + F32 mNearPlane; + F32 mFarPlane; + bool mIsOrtho; +}; + +#endif diff --git a/gfx/util/screenspace.cpp b/gfx/util/screenspace.cpp new file mode 100644 index 0000000..1101c6c --- /dev/null +++ b/gfx/util/screenspace.cpp @@ -0,0 +1,30 @@ +#include "gfx/util/screenspace.h" + +// The conversion from screen space to the render target +// is made more complex because screen space is relative +// to the viewport. +void ScreenSpace::RenderTargetParameters(const Point3I &targetSize, const RectI &targetViewport, Point4F &rtParams) +{ + // Top->Down + Point2F targetOffset( (F32)targetViewport.point.x / (F32)targetSize.x, + (F32)targetViewport.point.y / (F32)targetSize.y ); + + // Bottom->Up + //Point2F targetOffset( (F32)targetViewport.point.x / (F32)targetSize.x, + // ( (F32)targetSize.y - (F32)(targetViewport.point.y + targetViewport.extent.y ) ) / (F32)targetSize.y ); + + + // Get the scale to convert from the + // screen space to the target size. + Point2F targetScale( (F32)targetViewport.extent.x / (F32)targetSize.x, + (F32)targetViewport.extent.y / (F32)targetSize.y ); + + // Get the target half pixel size. + const Point2F halfPixel( 0.5f / targetSize.x, + 0.5f / targetSize.y ); + + rtParams.set( targetOffset.x + halfPixel.x, + targetOffset.y + halfPixel.y, + targetScale.x, + targetScale.y ); +} \ No newline at end of file diff --git a/gfx/util/screenspace.h b/gfx/util/screenspace.h new file mode 100644 index 0000000..470ed93 --- /dev/null +++ b/gfx/util/screenspace.h @@ -0,0 +1,13 @@ +#ifndef _SCREENSPACE_UTILS_H_ +#define _SCREENSPACE_UTILS_H_ + +#include "math/mPoint3.h" +#include "math/mPoint4.h" +#include "math/mRect.h" + +namespace ScreenSpace +{ +void RenderTargetParameters(const Point3I &targetSize, const RectI &targetViewport, Point4F &rtParams); +}; + +#endif \ No newline at end of file diff --git a/gfx/util/triListOpt.cpp b/gfx/util/triListOpt.cpp new file mode 100644 index 0000000..a96a232 --- /dev/null +++ b/gfx/util/triListOpt.cpp @@ -0,0 +1,370 @@ +#include "gfx/util/triListOpt.h" +#include "core/frameAllocator.h" +#include "platform/profiler.h" +#include "math/mMathFn.h" + +namespace TriListOpt +{ + +void OptimizeTriangleOrdering(const dsize_t numVerts, const dsize_t numIndices, const U32 *indices, IndexType *outIndices) +{ + PROFILE_SCOPE(TriListOpt_OptimizeTriangleOrdering); + + const U32 NumPrimitives = numIndices / 3; + AssertFatal(NumPrimitives == U32(mFloor(numIndices / 3.0f)), "Number of indicies not divisible by 3, not a good triangle list."); + + // + // Step 1: Run through the data, and initialize + // + FrameTemp vertexData(numVerts); + FrameTemp triangleData(NumPrimitives); + + U32 curIdx = 0; + for(int tri = 0; tri < NumPrimitives; tri++) + { + TriData &curTri = triangleData[tri]; + + for(int c = 0; c < 3; c++) + { + const U32 &curVIdx = indices[curIdx]; + AssertFatal(curVIdx < numVerts, "Out of range index."); + + // Add this vert to the list of verts that define the triangle + curTri.vertIdx[c] = curVIdx; + + VertData &curVert = vertexData[curVIdx]; + + // Increment the number of triangles that reference this vertex + curVert.numUnaddedReferences++; + + curIdx++; + } + } + + // Allocate per-vertex triangle lists, and calculate the starting score of + // each of the verts + for(int v = 0; v < numVerts; v++) + { + VertData &curVert = vertexData[v]; + curVert.triIndex = new S32[curVert.numUnaddedReferences]; + curVert.score = FindVertexScore::score(curVert); + } + + // These variables will be used later, but need to be declared now + S32 nextNextBestTriIdx = -1, nextBestTriIdx = -1; + F32 nextNextBestTriScore = -1.0f, nextBestTriScore = -1.0f; + +#define _VALIDATE_TRI_IDX(idx) if(idx > -1) { AssertFatal(idx < NumPrimitives, "Out of range triangle index."); AssertFatal(!triangleData[idx].isInList, "Triangle already in list, bad."); } +#define _CHECK_NEXT_NEXT_BEST(score, idx) { if(score > nextNextBestTriScore) { nextNextBestTriIdx = idx; nextNextBestTriScore = score; } } +#define _CHECK_NEXT_BEST(score, idx) { if(score > nextBestTriScore) { _CHECK_NEXT_NEXT_BEST(nextBestTriScore, nextBestTriIdx); nextBestTriIdx = idx; nextBestTriScore = score; } _VALIDATE_TRI_IDX(nextBestTriIdx); } + + // Fill-in per-vertex triangle lists, and sum the scores of each vertex used + // per-triangle, to get the starting triangle score + curIdx = 0; + for(int tri = 0; tri < NumPrimitives; tri++) + { + TriData &curTri = triangleData[tri]; + + for(int c = 0; c < 3; c++) + { + const U32 &curVIdx = indices[curIdx]; + AssertFatal(curVIdx < numVerts, "Out of range index."); + VertData &curVert = vertexData[curVIdx]; + + // Add triangle to triangle list + curVert.triIndex[curVert.numReferences++] = tri; + + // Add vertex score to triangle score + curTri.score += curVert.score; + + curIdx++; + } + + // This will pick the first triangle to add to the list in 'Step 2' + _CHECK_NEXT_BEST(curTri.score, tri); + _CHECK_NEXT_NEXT_BEST(curTri.score, tri); + } + + // + // Step 2: Start emitting triangles...this is the emit loop + // + LRUCacheModel lruCache; + for(int outIdx = 0; outIdx < numIndices; /* this space intentionally left blank */ ) + { + // If there is no next best triangle, than search for the next highest + // scored triangle that isn't in the list already + if(nextBestTriIdx < 0) + { + // TODO: Something better than linear performance here... + nextBestTriScore = nextNextBestTriScore = -1.0f; + nextBestTriIdx = nextNextBestTriIdx = -1; + + for(int tri = 0; tri < NumPrimitives; tri++) + { + TriData &curTri = triangleData[tri]; + + if(!curTri.isInList) + { + _CHECK_NEXT_BEST(curTri.score, tri); + _CHECK_NEXT_NEXT_BEST(curTri.score, tri); + } + } + } + AssertFatal(nextBestTriIdx > -1, "Ran out of 'nextBestTriangle' before I ran out of indices...not good."); + + // Emit the next best triangle + TriData &nextBestTri = triangleData[nextBestTriIdx]; + AssertFatal(!nextBestTri.isInList, "Next best triangle already in list, this is no good."); + for(int i = 0; i < 3; i++) + { + // Emit index + outIndices[outIdx++] = IndexType(nextBestTri.vertIdx[i]); + + // Update the list of triangles on the vert + VertData &curVert = vertexData[nextBestTri.vertIdx[i]]; + curVert.numUnaddedReferences--; + for(int t = 0; t < curVert.numReferences; t++) + { + if(curVert.triIndex[t] == nextBestTriIdx) + { + curVert.triIndex[t] = -1; + break; + } + } + + // Update cache + lruCache.useVertex(nextBestTri.vertIdx[i], &curVert); + } + nextBestTri.isInList = true; + + // Enforce cache size, this will update the cache position of all verts + // still in the cache. It will also update the score of the verts in the + // cache, and give back a list of triangle indicies that need updating. + Vector trisToUpdate; + lruCache.enforceSize(MaxSizeVertexCache, trisToUpdate); + + // Now update scores for triangles that need updates, and find the new best + // triangle score/index + nextBestTriIdx = -1; + nextBestTriScore = -1.0f; + for(Vector::iterator itr = trisToUpdate.begin(); itr != trisToUpdate.end(); itr++) + { + TriData &tri = triangleData[*itr]; + + // If this triangle isn't already emitted, re-score it + if(!tri.isInList) + { + tri.score = 0.0f; + + for(int i = 0; i < 3; i++) + tri.score += vertexData[tri.vertIdx[i]].score; + + _CHECK_NEXT_BEST(tri.score, *itr); + _CHECK_NEXT_NEXT_BEST(tri.score, *itr); + } + } + + // If there was no love finding a good triangle, than see if there is a + // next-next-best triangle, and if there isn't one of those...well than + // I guess we have to find one next time + if(nextBestTriIdx < 0 && nextNextBestTriIdx > -1) + { + if(!triangleData[nextNextBestTriIdx].isInList) + { + nextBestTriIdx = nextNextBestTriIdx; + nextBestTriScore = nextNextBestTriScore; + _VALIDATE_TRI_IDX(nextNextBestTriIdx); + } + + // Nuke the next-next best + nextNextBestTriIdx = -1; + nextNextBestTriScore = -1.0f; + } + + // Validate triangle we are marking as next-best + _VALIDATE_TRI_IDX(nextBestTriIdx); + } + +#undef _CHECK_NEXT_BEST +#undef _CHECK_NEXT_NEXT_BEST +#undef _VALIDATE_TRI_IDX + + // FrameTemp will call destructInPlace to clean up vertex lists +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +LRUCacheModel::~LRUCacheModel() +{ + for( LRUCacheEntry* entry = mCacheHead; entry != NULL; ) + { + LRUCacheEntry* next = entry->next; + delete entry; + entry = next; + } +} + +//------------------------------------------------------------------------------ + +void LRUCacheModel::useVertex(const U32 vIdx, VertData *vData) +{ + LRUCacheEntry *search = mCacheHead; + LRUCacheEntry *last = NULL; + + while(search != NULL) + { + if(search->vIdx == vIdx) + break; + + last = search; + search = search->next; + } + + // If this vertex wasn't found in the cache, create a new entry + if(search == NULL) + { + search = new LRUCacheEntry; + search->vIdx = vIdx; + search->vData = vData; + } + + if(search != mCacheHead) + { + // Unlink the entry from the linked list + if(last) + last->next = search->next; + + // Vertex that got passed in is now at the head of the cache + search->next = mCacheHead; + mCacheHead = search; + } +} + +//------------------------------------------------------------------------------ + +void LRUCacheModel::enforceSize(const dsize_t maxSize, Vector &outTrisToUpdate) +{ + // Clear list of triangles to update scores for + outTrisToUpdate.clear(); + + dsize_t length = 0; + LRUCacheEntry *next = mCacheHead; + LRUCacheEntry *last = NULL; + + // Run through list, up to the max size + while(next != NULL && length < MaxSizeVertexCache) + { + VertData &vData = *next->vData; + + // Update cache position on verts still in cache + vData.cachePosition = length++; + + for(int i = 0; i < vData.numReferences; i++) + { + const S32 &triIdx = vData.triIndex[i]; + if(triIdx > -1) + { + int j = 0; + for(; j < outTrisToUpdate.size(); j++) + if(outTrisToUpdate[j] == triIdx) + break; + if(j == outTrisToUpdate.size()) + outTrisToUpdate.push_back(triIdx); + } + } + + // Update score + vData.score = FindVertexScore::score(vData); + + last = next; + next = next->next; + } + + // NULL out the pointer to the next entry on the last valid entry + last->next = NULL; + + // If next != NULL, than we need to prune entries from the tail of the cache + while(next != NULL) + { + // Update cache position on verts which are going to get tossed from cache + next->vData->cachePosition = -1; + + LRUCacheEntry *curEntry = next; + next = next->next; + + delete curEntry; + } +} + +//------------------------------------------------------------------------------ + +S32 LRUCacheModel::getCachePosition(const U32 vIdx) +{ + dsize_t length = 0; + LRUCacheEntry *next = mCacheHead; + while(next != NULL) + { + if(next->vIdx == vIdx) + return length; + + length++; + next = next->next; + } + + return -1; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +// http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html +namespace FindVertexScore +{ + +F32 score(const VertData &vertexData) +{ + // If nobody needs this vertex, return -1.0 + if(vertexData.numUnaddedReferences < 1) + return -1.0f; + + F32 Score = 0.0f; + + if(vertexData.cachePosition < 0) + { + // Vertex is not in FIFO cache - no score. + } + else + { + if(vertexData.cachePosition < 3) + { + // This vertex was used in the last triangle, + // so it has a fixed score, whichever of the three + // it's in. Otherwise, you can get very different + // answers depending on whether you add + // the triangle 1,2,3 or 3,1,2 - which is silly. + Score = FindVertexScore::LastTriScore; + } + else + { + AssertFatal(vertexData.cachePosition < MaxSizeVertexCache, "Out of range cache position for vertex"); + + // Points for being high in the cache. + const float Scaler = 1.0f / (MaxSizeVertexCache - 3); + Score = 1.0f - (vertexData.cachePosition - 3) * Scaler; + Score = mPow(Score, FindVertexScore::CacheDecayPower); + } + } + + + // Bonus points for having a low number of tris still to + // use the vert, so we get rid of lone verts quickly. + float ValenceBoost = mPow(vertexData.numUnaddedReferences, -FindVertexScore::ValenceBoostPower); + Score += FindVertexScore::ValenceBoostScale * ValenceBoost; + + return Score; +} + +} // namspace FindVertexScore + +} // namespace TriListOpt diff --git a/gfx/util/triListOpt.h b/gfx/util/triListOpt.h new file mode 100644 index 0000000..077803c --- /dev/null +++ b/gfx/util/triListOpt.h @@ -0,0 +1,78 @@ +#ifndef _TRI_LIST_OPT_H_ +#define _TRI_LIST_OPT_H_ + +#include "core/util/tVector.h" + +namespace TriListOpt +{ + typedef U32 IndexType; + + const U32 MaxSizeVertexCache = 32; + + struct VertData + { + S32 cachePosition; + F32 score; + U32 numReferences; + U32 numUnaddedReferences; + S32 *triIndex; + + VertData() : cachePosition(-1), score(0.0f), numReferences(0), numUnaddedReferences(0), triIndex(NULL) {} + ~VertData() { if(triIndex != NULL) delete [] triIndex; triIndex = NULL; } + }; + + struct TriData + { + bool isInList; + F32 score; + U32 vertIdx[3]; + + TriData() : isInList(false), score(0.0f) { dMemset(vertIdx, 0, sizeof(vertIdx)); } + }; + + class LRUCacheModel + { + struct LRUCacheEntry + { + LRUCacheEntry *next; + U32 vIdx; + VertData *vData; + + LRUCacheEntry() : next(NULL), vIdx(0), vData(NULL) {} + }; + + LRUCacheEntry *mCacheHead; + + public: + LRUCacheModel() : mCacheHead(NULL) {} + ~LRUCacheModel(); + void enforceSize(const dsize_t maxSize, Vector &outTrisToUpdate); + void useVertex(const U32 vIdx, VertData *vData); + S32 getCachePosition(const U32 vIdx); + }; + + + /// This method will look at the index buffer for a triangle list, and generate + /// a new index buffer which is optimized using Tom Forsyth's paper: + /// "Linear-Speed Vertex Cache Optimization" + /// http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html + /// @param numVerts Number of vertices indexed by the 'indices' + /// @param numIndices Number of elements in both 'indices' and 'outIndices' + /// @param indices Input index buffer + /// @param outIndices Output index buffer + /// + /// @note Both 'indices' and 'outIndices' can point to the same memory. + void OptimizeTriangleOrdering(const dsize_t numVerts, const dsize_t numIndices, const U32 *indices, IndexType *outIndices); + + namespace FindVertexScore + { + const F32 CacheDecayPower = 1.5f; + const F32 LastTriScore = 0.75f; + const F32 ValenceBoostScale = 2.0f; + const F32 ValenceBoostPower = 0.5f; + + F32 score(const VertData &vertexData); + }; +}; + +#endif \ No newline at end of file diff --git a/gfx/video/theoraTexture.cpp b/gfx/video/theoraTexture.cpp new file mode 100644 index 0000000..9f13364 --- /dev/null +++ b/gfx/video/theoraTexture.cpp @@ -0,0 +1,672 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifdef TORQUE_OGGTHEORA + +/// If defined, uses a separate file stream to read Vorbis sound data +/// from the Ogg stream. This removes both the contention caused by +/// concurrently streaming from a single master stream as well as the +/// coupling between Theora reads and Vorbis reads that arises from +/// multiplexing. +#define SPLIT_VORBIS + + +#include "theoraTexture.h" + +#include "sfx/sfxDescription.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxCommon.h" +#include "sfx/sfxPacketStream.h" +#include "sfx/sfxSource.h" +#ifdef SPLIT_VORBIS + #include "sfx/media/sfxVorbisStream.h" +#endif + +#include "core/stream/fileStream.h" +#include "core/ogg/oggInputStream.h" +#include "core/ogg/oggTheoraDecoder.h" +#include "core/ogg/oggVorbisDecoder.h" +#include "core/util/safeDelete.h" +#include "core/util/rawData.h" +#include "console/console.h" +#include "math/mMath.h" +#include "gfx/bitmap/gBitmap.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxFormatUtils.h" +#include "gfx/gfxTextureManager.h" + + + +/// Profile for the video texture. +GFX_ImplementTextureProfile( GFXTheoraTextureProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::NoMipmap | GFXTextureProfile::Dynamic, + GFXTextureProfile::None ); + + +//----------------------------------------------------------------------------- + +static const char* GetPixelFormatName( OggTheoraDecoder::EPixelFormat format ) +{ + switch( format ) + { + case OggTheoraDecoder::PIXEL_FORMAT_420: + return "4:2:0"; + case OggTheoraDecoder::PIXEL_FORMAT_422: + return "4:2:2"; + case OggTheoraDecoder::PIXEL_FORMAT_444: + return "4:4:4"; + + case OggTheoraDecoder::PIXEL_FORMAT_Unknown: ; + } + return "Unknown"; +} + +//============================================================================= +// TheoraTexture::FrameReadItem implementation. +//============================================================================= + +//----------------------------------------------------------------------------- + +TheoraTexture::FrameReadItem::FrameReadItem( AsyncBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >* >* stream, ThreadContext* context ) + : Parent( context ), + mFrameStream( dynamic_cast< FrameStream* >( stream ) ) +{ + AssertFatal( mFrameStream != NULL, "TheoraTexture::FrameReadItem::FrameReadItem() - expecting stream of type 'FrameStream'" ); + + mAsyncState = mFrameStream->mAsyncState; + + // Assign a TheoraTextureFrame record to us. The nature of + // AsyncBufferedInputStream ensures that we are always serial + // here so this is thread-safe. + + mFrame = &mFrameStream->mFrames[ mFrameStream->mFrameIndex ]; + mFrameStream->mFrameIndex = ( mFrameStream->mFrameIndex + 1 ) % FrameStream::NUM_FRAME_RECORDS; +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::FrameReadItem::execute() +{ + // Read Theora frame data. + + OggTheoraFrame* frame; + if( mFrameStream->getSourceStream()->read( &frame, 1 ) != 1 ) + return; + + // Copy the data into the texture. + + OggTheoraDecoder* decoder = mAsyncState->getTheora(); + const U32 height = decoder->getFrameHeight(); + const U32 framePitch = decoder->getFrameWidth() * 4; + + GFXLockedRect* rect = mFrame->mLockedRect; + if( rect ) + { + if( framePitch == rect->pitch ) + dMemcpy( rect->bits, frame->data, rect->pitch * height ); + else + { + // Scanline length does not match. Copy line by line. + + U8* dst = rect->bits; + U8* src = ( U8* ) frame->data; + for( U32 i = 0; i < height; ++ i ) + { + dMemcpy( dst, src, framePitch ); + dst += rect->pitch; + src += framePitch; + } + } + } + #ifdef TORQUE_DEBUG + else + Platform::outputDebugString( "[TheoraTexture] texture not locked on frame %i", frame->mFrameNumber ); + #endif + + // Copy frame metrics. + + mFrame->mFrameNumber = frame->mFrameNumber; + mFrame->mFrameTime = frame->mFrameTime; + mFrame->mFrameDuration = frame->mFrameDuration; + + // Yield the frame packet back to the Theora decoder. + + decoder->reusePacket( frame ); + + // Buffer the frame. + + mFrameStream->_onArrival( mFrame ); +} + +//============================================================================= +// TheoraTexture::FrameStream implementation. +//============================================================================= + +//----------------------------------------------------------------------------- + +TheoraTexture::FrameStream::FrameStream( AsyncState* asyncState, bool looping ) + : Parent( asyncState->getTheora(), 0, FRAME_READ_AHEAD, looping ), + mAsyncState( asyncState ), + mFrameIndex( 0 ) +{ + // Create the textures. + + OggTheoraDecoder* theora = asyncState->getTheora(); + const U32 width = theora->getFrameWidth(); + const U32 height = theora->getFrameHeight(); + + for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i ) + { + mFrames[ i ].mTexture.set( + width, + height, + GFXFormatR8G8B8A8, + &GFXTheoraTextureProfile, + String::ToString( "Theora texture frame buffer %i (%s:%i)", i, __FILE__, __LINE__ ) + ); + } + + acquireTextureLocks(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::FrameStream::acquireTextureLocks() +{ + for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i ) + if( !mFrames[ i ].mLockedRect ) + mFrames[ i ].mLockedRect = mFrames[ i ].mTexture.lock(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::FrameStream::releaseTextureLocks() +{ + for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i ) + if( mFrames[ i ].mLockedRect ) + { + mFrames[ i ].mTexture.unlock(); + mFrames[ i ].mLockedRect = NULL; + } +} + +//============================================================================= +// TheoraTexture::AsyncState implementation. +//============================================================================= + +//----------------------------------------------------------------------------- + +TheoraTexture::AsyncState::AsyncState( const ThreadSafeRef< OggInputStream >& oggStream, bool looping ) + : mOggStream( oggStream ), + mTheoraDecoder( dynamic_cast< OggTheoraDecoder* >( oggStream->getDecoder( "Theora" ) ) ), + mVorbisDecoder( dynamic_cast< OggVorbisDecoder* >( oggStream->getDecoder( "Vorbis" ) ) ), + mCurrentTime( 0 ) +{ + if( mTheoraDecoder ) + { + mTheoraDecoder->setTimeSource( this ); + mFrameStream = new FrameStream( this, looping ); + } +} + +//----------------------------------------------------------------------------- + +TheoraTextureFrame* TheoraTexture::AsyncState::readNextFrame() +{ + TheoraTextureFrame* frame; + if( mFrameStream->read( &frame, 1 ) ) + return frame; + else + return NULL; +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::AsyncState::start() +{ + mFrameStream->start(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::AsyncState::stop() +{ + mFrameStream->stop(); +} + +//----------------------------------------------------------------------------- + +bool TheoraTexture::AsyncState::isAtEnd() +{ + return mOggStream->isAtEnd(); +} + +//============================================================================= +// TheoraTexture implementation. +//============================================================================= + +//----------------------------------------------------------------------------- + +TheoraTexture::TheoraTexture() + : mPlaybackQueue( NULL ), + mCurrentFrame( NULL ), + mIsPaused( true ) +{ + GFXTextureManager::addEventDelegate( this, &TheoraTexture::_onTextureEvent ); +} + +//----------------------------------------------------------------------------- + +TheoraTexture::~TheoraTexture() +{ + GFXTextureManager::removeEventDelegate( this, &TheoraTexture::_onTextureEvent ); + _reset(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::_reset() +{ + // Stop the async streams. + + if( mAsyncState != NULL ) + mAsyncState->stop(); + + // Delete the playback queue. + + if( mPlaybackQueue ) + SAFE_DELETE( mPlaybackQueue ); + + // Kill the sound source. + + if( mSFXSource != NULL ) + { + mSFXSource->stop(); + SFX_DELETE( mSFXSource ); + mSFXSource = NULL; + } + + mLastFrameNumber = 0; + mNumDroppedFrames = 0; + mCurrentFrame = NULL; + mAsyncState = NULL; + mIsPaused = false; + mPlaybackTimer.reset(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::_onTextureEvent( GFXTexCallbackCode code ) +{ + switch( code ) + { + case GFXZombify: + mCurrentFrame = NULL; + if( mAsyncState ) + { + // Blast out work items and then release all texture locks. + + ThreadPool::GLOBAL().flushWorkItems(); + mAsyncState->getFrameStream()->releaseTextureLocks(); + + // The Theora decoder does not implement seeking at the moment, + // so we absolutely want to make sure we don't fall behind too far or + // we may end up having the decoder go crazy trying to skip through + // Ogg packets (even just reading these undecoded packets takes a + // lot of time). So, for the time being, just pause playback when + // we go zombie. + + if( mSFXSource ) + mSFXSource->pause(); + else + mPlaybackTimer.pause(); + } + break; + + case GFXResurrect: + if( mAsyncState ) + { + // Reacquire texture locks. + + mAsyncState->getFrameStream()->acquireTextureLocks(); + + // Resume playback if we have paused it. + + if( !mIsPaused ) + { + if( mSFXSource ) + mSFXSource->play(); + else + mPlaybackTimer.start(); + } + } + break; + } +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::_initVideo() +{ + OggTheoraDecoder* theora = _getTheora(); + + // Set the decoder's pixel output format to match + // the texture format. + + OggTheoraDecoder::PacketFormat format; + format.mFormat = GFXFormatR8G8B8A8; + format.mPitch = GFXFormatInfo( format.mFormat ).getBytesPerPixel() * theora->getFrameWidth(); + + theora->setPacketFormat( format ); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::_initAudio( const ThreadSafeRef< SFXStream >& stream ) +{ + // Create an SFXDescription if we don't have one. + + if( !mSFXDescription ) + { + SFXDescription* description = new SFXDescription; + description->mIsStreaming = true; + description->registerObject(); + description->setAutoDelete( true ); + + mSFXDescription = description; + } + + // Create an SFX memory stream that consumes the output + // of the Vorbis decoder. + + ThreadSafeRef< SFXStream > sfxStream = stream; + if( !sfxStream ) + { + OggVorbisDecoder* vorbis = _getVorbis(); + SFXFormat sfxFormat( vorbis->getNumChannels(), + vorbis->getNumChannels() * 16, + vorbis->getSamplesPerSecond() ); + + sfxStream = new SFXPacketStream( sfxFormat, vorbis ); + } + + // Create the SFXSource. + + mSFXSource = SFX->createSourceFromStream( sfxStream, mSFXDescription ); +} + +//----------------------------------------------------------------------------- + +TheoraTexture::TimeSourceType* TheoraTexture::_getTimeSource() const +{ + if( mSFXSource != NULL ) + return mSFXSource; + else + return ( TimeSourceType* ) &mPlaybackTimer; +} + +//----------------------------------------------------------------------------- + +U32 TheoraTexture::getWidth() const +{ + return _getTheora()->getFrameWidth(); +} + +//----------------------------------------------------------------------------- + +U32 TheoraTexture::getHeight() const +{ + return _getTheora()->getFrameHeight(); +} + +//----------------------------------------------------------------------------- + +bool TheoraTexture::setFile( const String& filename, SFXDescription* desc ) +{ + _reset(); + + if( filename.isEmpty() ) + return true; + + // Check SFX profile. + + if( desc && !desc->mIsStreaming ) + { + Con::errorf( "TheoraTexture::setFile - Not a streaming SFXDescription" ); + return false; + } + mSFXDescription = desc; + + // Open the Theora file. + + Stream* stream = FileStream::createAndOpen( filename, Torque::FS::File::Read ); + if( !stream ) + { + Con::errorf( "TheoraTexture::setFile - Theora file '%s' not found.", filename.c_str() ); + return false; + } + + // Create the OGG stream. + + Con::printf( "TheoraTexture - Loading file '%s'", filename.c_str() ); + + ThreadSafeRef< OggInputStream > oggStream = new OggInputStream( stream ); + oggStream->addDecoder< OggTheoraDecoder >(); + #ifndef SPLIT_VORBIS + oggStream->addDecoder< OggVorbisDecoder >(); + #endif + + if( !oggStream->init() ) + { + Con::errorf( "TheoraTexture - Failed to initialize OGG stream" ); + return false; + } + + mFilename = filename; + mAsyncState = new AsyncState( oggStream, desc ? desc->mIsLooping : false ); + + // Set up video. + + OggTheoraDecoder* theoraDecoder = _getTheora(); + if( !theoraDecoder ) + { + Con::errorf( "TheoraTexture - '%s' is not a Theora file", filename.c_str() ); + mAsyncState = NULL; + return false; + } + + Con::printf( " - Theora: %ix%i pixels, %.02f fps, %s format", + theoraDecoder->getFrameWidth(), + theoraDecoder->getFrameHeight(), + theoraDecoder->getFramesPerSecond(), + GetPixelFormatName( theoraDecoder->getDecoderPixelFormat() ) ); + + _initVideo(); + + // Set up sound if we have it. For performance reasons, create + // a separate physical stream for the Vorbis sound data rather than + // using the bitstream from the multiplexed OGG master stream. The + // contention caused by the OGG page/packet feeding will otherwise + // slow us down significantly. + + #ifdef SPLIT_VORBIS + + stream = FileStream::createAndOpen( filename, Torque::FS::File::Read ); + if( stream ) + { + ThreadSafeRef< SFXStream > vorbisStream = SFXVorbisStream::create( stream ); + if( !vorbisStream ) + { + Con::errorf( "TheoraTexture - could not create Vorbis stream for '%s'", filename.c_str() ); + + // Stream is deleted by SFXVorbisStream. + } + else + { + Con::printf( " - Vorbis: %i channels, %i kHz", + vorbisStream->getFormat().getChannels(), + vorbisStream->getFormat().getSamplesPerSecond() / 1000 ); + + _initAudio( vorbisStream ); + } + } + + #else + + OggVorbisDecoder* vorbisDecoder = _getVorbis(); + if( vorbisDecoder ) + { + Con::printf( " - Vorbis: %i bits, %i channels, %i kHz", + vorbisDecoder->getNumChannels(), + vorbisDecoder->getSamplesPerSecond() / 1000 ); + + _initAudio(); + } + + #endif + + // Initiate the background request chain. + + mAsyncState->start(); + + return true; +} + +//----------------------------------------------------------------------------- + +bool TheoraTexture::isPlaying() const +{ + if( !mAsyncState || !mCurrentFrame ) + return false; + + if( mSFXSource ) + return mSFXSource->isPlaying(); + else + return mPlaybackTimer.isStarted(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::play() +{ + if( isPlaying() ) + return; + + if( !mAsyncState ) + setFile( mFilename, mSFXDescription ); + + // Construct playback queue that sync's to our time source, + // writes to us, and drops outdated packets. + + if( !mPlaybackQueue ) + mPlaybackQueue = new PlaybackQueueType( 1, _getTimeSource(), this, 0, true ); + + // Start playback. + + if( mSFXSource ) + mSFXSource->play(); + else + mPlaybackTimer.start(); + + mIsPaused = false; +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::pause() +{ + if( mSFXSource ) + mSFXSource->pause(); + else + mPlaybackTimer.pause(); + + mIsPaused = true; +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::stop() +{ + _reset(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::refresh() +{ + PROFILE_SCOPE( TheoraTexture_refresh ); + + if( !mAsyncState || !mPlaybackQueue ) + return; + + // Synchronize the async state to our current time. + // Unfortunately, we cannot set the Theora decoder to + // synchronize directly with us as our lifetime and the + // lifetime of our time sources isn't bound to the + // threaded state. + + mAsyncState->syncTime( _getTimeSource()->getPosition() ); + + // Update the texture, if necessary. + + bool haveFrame = false; + while( mPlaybackQueue->needPacket() ) + { + // Lock the current frame. + + if( mCurrentFrame && !mCurrentFrame->mLockedRect ) + mCurrentFrame->mLockedRect = mCurrentFrame->mTexture.lock(); + + // Try to read a new frame. + + TheoraTextureFrame* frame = mAsyncState->readNextFrame(); + if( !frame ) + break; + + // Submit frame to queue. + + mPlaybackQueue->submitPacket( + frame, + frame->mFrameDuration * 1000.f, + false, + frame->mFrameTime * 1000.f + ); + + // See if we have dropped frames. + + if( frame->mFrameNumber != mLastFrameNumber + 1 ) + mNumDroppedFrames = frame->mFrameNumber - mLastFrameNumber - 1; + mLastFrameNumber = frame->mFrameNumber; + + haveFrame = true; + } + + // Unlock current frame. + + if( mCurrentFrame && mCurrentFrame->mLockedRect ) + { + mCurrentFrame->mTexture.unlock(); + mCurrentFrame->mLockedRect = NULL; + } + + // Release async state if we have reached the + // end of the Ogg stream. + + if( mAsyncState->isAtEnd() && !haveFrame ) + _reset(); +} + +//----------------------------------------------------------------------------- + +void TheoraTexture::write( TheoraTextureFrame* const* frames, U32 num ) +{ + if( !num ) + return; + + mCurrentFrame = frames[ num - 1 ]; // Only used last. +} + +#endif // TORQUE_OGGTHEORA diff --git a/gfx/video/theoraTexture.h b/gfx/video/theoraTexture.h new file mode 100644 index 0000000..43bd6fd --- /dev/null +++ b/gfx/video/theoraTexture.h @@ -0,0 +1,399 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _THEORATEXTURE_H_ +#define _THEORATEXTURE_H_ + +#ifdef TORQUE_OGGTHEORA + +#ifndef _PLATFORM_H_ + #include "platform/platform.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ + #include "gfx/gfxTextureHandle.h" +#endif +#ifndef _ASYNCPACKETQUEUE_H_ + #include "platform/async/asyncPacketQueue.h" +#endif +#ifndef _ASYNCBUFFEREDSTREAM_H_ + #include "platform/async/asyncBufferedStream.h" +#endif +#ifndef _TIMESOURCE_H_ + #include "core/util/timeSource.h" +#endif +#ifndef _THREADSAFEREFCOUNT_H_ + #include "platform/threads/threadSafeRefCount.h" +#endif +#ifndef _RAWDATA_H_ + #include "core/util/rawData.h" +#endif +#ifndef _SIMOBJECT_H_ + #include "console/simObject.h" +#endif +#ifndef _SFXSTREAM_H_ + #include "sfx/sfxStream.h" +#endif +#ifndef _OGGTHEORADECODER_H_ + #include "core/ogg/oggTheoraDecoder.h" +#endif +#ifndef _TYPETRAITS_H_ + #include "platform/typetraits.h" +#endif + + + +class SFXDescription; +class SFXSource; +class SFXVorbisStream; + +class OggInputStream; +class OggVorbisDecoder; + +class Stream; + + +/// A single frame in the video frame stream. +/// +/// Frames are uploaded directly into textures by the asynchronous +/// streaming system. This offloads as much work as possible to the worker +/// threads and guarantees the smoothest possible playback. +/// +/// Frame records are re-used and are managed directly by the video frame +/// stream. The number of textures concurrently used by a Theora stream +/// is determined by its stream read-ahead. +class TheoraTextureFrame +{ + public: + + typedef void Parent; + + /// The texture containing the video frame. + GFXTexHandle mTexture; + + /// The locked rectangle, if the texture is currently locked. + /// Frames will remain in locked state except if currently displayed. + GFXLockedRect* mLockedRect; + + /// + U32 mFrameNumber; + + /// The play time in seconds at which to display this frame. + F32 mFrameTime; + + /// The duration in seconds to display this frame. + F32 mFrameDuration; + + TheoraTextureFrame() + : mLockedRect( NULL ) + { + } +}; + + +inline void destructSingle( TheoraTextureFrame* frame ) +{ + // Do nothing. +} + + +/// TheoraTexture decodes Ogg Theora files, and their audio. +/// +/// TheoraTexture objects can be used similarly to TextureObjects. Just +/// set the video, call play(), and then refresh every frame to get the +/// latest video. Audio happens automagically. +/// +/// @note Uses Theora and ogg libraries which are Copyright (C) Xiph.org Foundation +class TheoraTexture : private IOutputStream< TheoraTextureFrame* >, + public IPositionable< U32 > +{ + public: + + typedef void Parent; + + protected: + + typedef IPositionable< U32 > TimeSourceType; + typedef GenericTimeSource<> TimerType; + typedef AsyncPacketQueue< TheoraTextureFrame*, TimeSourceType*, IOutputStream< TheoraTextureFrame* >*, F32 > PlaybackQueueType; + + class FrameStream; + class AsyncState; + friend class GuiTheoraCtrl; // accesses OggTheoraDecoder to set transcoder + + /// Parameters for tuning streaming behavior. + enum + { + /// Number of textures to load in background. + FRAME_READ_AHEAD = 6, + }; + + /// WorkItem that reads a frame from a Theora decoder and uploads it into a TheoraTextureFrame. + /// + /// Loading directly into textures moves the costly uploads out of the main thread into worker + /// threads. The downside to this is that since we cannot do GFX work on the worker threads, + /// we need to expect textures to get to us in locked state. + class FrameReadItem : public ThreadWorkItem + { + public: + + typedef ThreadWorkItem Parent; + + protected: + + /// The asynchronous state we belong to. This reference + /// ensures that all our streaming state stays live for as long as our + /// work item is in the pipeline. + ThreadSafeRef< AsyncState > mAsyncState; + + /// + FrameStream* mFrameStream; + + /// The frame texture we are loading. + TheoraTextureFrame* mFrame; + + // WorkItem. + virtual void execute(); + + public: + + /// + FrameReadItem( AsyncBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >* >* stream, + ThreadPool::Context* context ); + }; + + /// Stream filter that turns a stream of OggTheoraFrames into a buffered background stream of TheoraTextureFrame + /// records. + /// + /// This streams allocates a fixed amount 'M' of TheoraTextureFrames. Reading the n-th frame from the stream, will + /// automatically invalidate the (n-M)-th frame. + class FrameStream : public AsyncSingleBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >*, FrameReadItem > + { + public: + + typedef AsyncSingleBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >*, FrameReadItem > Parent; + + protected: + + friend class FrameReadItem; + + enum + { + /// Number of TheoraTextureFrame records to allocate. + /// + /// We need to pre-allocate TheoraTextureFrames as we cannot do GFX operations + /// on the fly on worker threads. This number corresponds to the length of the + /// buffering queue plus one record that will be returned to the user as the + /// current frame record. + NUM_FRAME_RECORDS = FRAME_READ_AHEAD + 1 + }; + + /// Asynchronous state of the texture object. + /// This is *NOT* a ThreadSafeRef to not create a cycle. + AsyncState* mAsyncState; + + /// Wrap-around index into "mFrames." + U32 mFrameIndex; + + /// The pre-allocated TheoraTextureFrames. + TheoraTextureFrame mFrames[ NUM_FRAME_RECORDS ]; + + public: + + /// + FrameStream( AsyncState* asyncState, bool looping = false ); + + /// + void acquireTextureLocks(); + + /// + void releaseTextureLocks(); + }; + + /// Encapsulation of compound asynchronous state. Allows releasing + /// the entire state in one go. + class AsyncState : public ThreadSafeRefCount< AsyncState >, + private IPositionable< U32 > + { + public: + + typedef void Parent; + friend class FrameStream; // mDecoderBufferStream + + protected: + + typedef AsyncSingleBufferedInputStream< OggTheoraFrame* > DecoderBufferStream; + + /// Last synchronization position in the video stream. This is what the + /// Theora decoder gets passed to see if frames are outdated. + U32 mCurrentTime; + + /// The Ogg master stream. + ThreadSafeRef< OggInputStream > mOggStream; + + /// The raw video decoding stream. + OggTheoraDecoder* mTheoraDecoder; + + /// The raw sound decoding stream; NULL if no Vorbis in video or if + /// Vorbis is streamed separately. + OggVorbisDecoder* mVorbisDecoder; + + /// The background-buffered frame stream. + ThreadSafeRef< FrameStream > mFrameStream; + + public: + + /// + AsyncState( const ThreadSafeRef< OggInputStream >& oggStream, bool looping = false ); + + /// Return the Theora decoder substream. + OggTheoraDecoder* getTheora() const { return mTheoraDecoder; } + + /// Return the Vorbis decoder substream. + /// @note If Vorbis streaming is split out into a separate physical substream, + /// this method will always return NULL even if Vorbis sound is being used. + OggVorbisDecoder* getVorbis() const { return mVorbisDecoder; } + + /// + const ThreadSafeRef< FrameStream >& getFrameStream() const { return mFrameStream; } + + /// + TheoraTextureFrame* readNextFrame(); + + /// + void start(); + + /// + void stop(); + + /// + bool isAtEnd(); + + /// + void syncTime( U32 ms ) { mCurrentTime = ms; } + + private: + + // IPositionable. + virtual U32 getPosition() const { return mCurrentTime; } + virtual void setPosition( U32 pos ) {} + }; + + /// The Theora video file. + String mFilename; + + /// The SFXDescription used for sound playback. Synthesized if not provided. + SimObjectPtr< SFXDescription > mSFXDescription; + + /// If there's a Vorbis stream, this is the sound source used for playback. + /// Playback will synchronize to this source then. + SimObjectPtr< SFXSource > mSFXSource; + + /// The current frame. + TheoraTextureFrame* mCurrentFrame; + + /// The queue that synchronizes the writing of frames to the TheoraTexture. + PlaybackQueueType* mPlaybackQueue; + + /// The timer for synchronizing playback when there is no audio stream + /// to synchronize to. + TimerType mPlaybackTimer; + + /// Our threaded state. + ThreadSafeRef< AsyncState > mAsyncState; + + /// + bool mIsPaused; + + /// + U32 mLastFrameNumber; + + /// + U32 mNumDroppedFrames; + + /// Release all dynamic playback state. + void _reset(); + + /// Initialize video playback. + void _initVideo(); + + /// Initialize audio playback. + void _initAudio( const ThreadSafeRef< SFXStream >& stream = NULL ); + + /// Return the Theora decoder stream or NULL. + OggTheoraDecoder* _getTheora() const { return ( mAsyncState != NULL ? mAsyncState->getTheora() : NULL ); } + + /// Return the Vorbis decoder stream or NULL. + OggVorbisDecoder* _getVorbis() const { return ( mAsyncState != NULL ? mAsyncState->getVorbis() : NULL ); } + + /// Return the object that is acting as our time source. + TimeSourceType* _getTimeSource() const; + + /// + void _onTextureEvent( GFXTexCallbackCode code ); + + // IOutputStream. + virtual void write( TheoraTextureFrame* const* frames, U32 num ); + + public: + + /// + TheoraTexture(); + + /// + ~TheoraTexture(); + + /// + U32 getWidth() const; + + /// + U32 getHeight() const; + + /// + const String& getFilename() const { return mFilename; } + + /// + bool setFile( const String& filename, SFXDescription* desc = NULL ); + + /// + void play(); + + /// + void pause(); + + /// + void stop(); + + /// + void refresh(); + + /// + bool isReady() const { return ( mAsyncState != NULL ); } + + /// Return true if the video is currently playing. + bool isPlaying() const; + + /// Return true if the video is currently paused. + bool isPaused() const { return mIsPaused; } + + /// + U32 getFrameNumber() const { return mCurrentFrame->mFrameNumber; } + + /// + F32 getFrameTime() const { return mCurrentFrame->mFrameTime; } + + /// + U32 getNumDroppedFrames() const { return mNumDroppedFrames; } + + /// + const GFXTexHandle& getTexture() const { return mCurrentFrame->mTexture; } + GFXTexHandle& getTexture() { return mCurrentFrame->mTexture; } + + // IPositionable. + virtual U32 getPosition() const { return _getTimeSource()->getPosition(); } + virtual void setPosition( U32 pos ) {} // Not (yet?) implemented. +}; + +#endif // TORQUE_OGGTHEORA +#endif // !_THEORATEXTURE_H_ diff --git a/ggEndOfLineFix.txt b/ggEndOfLineFix.txt new file mode 100644 index 0000000..42ac0c0 --- /dev/null +++ b/ggEndOfLineFix.txt @@ -0,0 +1,3 @@ +find . -name "*.cc" -exec sh -c 'echo >> {}' \; +find . -name "*.cpp" -exec sh -c 'echo >> {}' \; +find . -name "*.h" -exec sh -c 'echo >> {}' \; diff --git a/gui/3d/guiTSControl.cpp b/gui/3d/guiTSControl.cpp new file mode 100644 index 0000000..ae39d32 --- /dev/null +++ b/gui/3d/guiTSControl.cpp @@ -0,0 +1,369 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/3d/guiTSControl.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightManager.h" +#include "gfx/sim/debugDraw.h" +#include "math/mathUtils.h" +#include "gui/core/guiCanvas.h" +#include "sceneGraph/reflectionManager.h" +#include "postFx/postEffectManager.h" +#include "postFx/postEffect.h" + +IMPLEMENT_CONOBJECT( GuiTSCtrl ); + +U32 GuiTSCtrl::smFrameCount = 0; + +Vector GuiTSCtrl::smAwakeTSCtrls; + + +GuiTSCtrl::GuiTSCtrl() +{ + mCameraZRot = 0; + mForceFOV = 0; + mReflectPriority = 1.0f; + + mSaveModelview.identity(); + mSaveProjection.identity(); + mSaveViewport.set( 0, 0, 10, 10 ); + mSaveWorldToScreenScale.set( 0, 0 ); + + mLastCameraQuery.cameraMatrix.identity(); + mLastCameraQuery.fov = 45.0f; + mLastCameraQuery.object = NULL; + mLastCameraQuery.farPlane = 10.0f; + mLastCameraQuery.nearPlane = 0.01f; + + mLastCameraQuery.ortho = false; +} +void GuiTSCtrl::initPersistFields() +{ + addField("cameraZRot", TypeF32, Offset(mCameraZRot, GuiTSCtrl)); + addField("forceFOV", TypeF32, Offset(mForceFOV, GuiTSCtrl)); + addField("reflectPriority", TypeF32, Offset(mReflectPriority, GuiTSCtrl)); + + Parent::initPersistFields(); +} + +void GuiTSCtrl::consoleInit() +{ + Con::addVariable("$TSControl::frameCount", TypeS32, &smFrameCount); +} + +bool GuiTSCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + // Add ourselves to the active viewport list. + AssertFatal( !smAwakeTSCtrls.contains( this ), + "GuiTSCtrl::onWake - This control is already in the awake list!" ); + smAwakeTSCtrls.push_back( this ); + + return true; +} + +void GuiTSCtrl::onSleep() +{ + Parent::onSleep(); + + AssertFatal( smAwakeTSCtrls.contains( this ), + "GuiTSCtrl::onSleep - This control is not in the awake list!" ); + smAwakeTSCtrls.remove( this ); +} + +void GuiTSCtrl::onPreRender() +{ + setUpdate(); +} + +bool GuiTSCtrl::processCameraQuery(CameraQuery *) +{ + return false; +} + +void GuiTSCtrl::renderWorld(const RectI& /*updateRect*/) +{ +} + +F32 GuiTSCtrl::projectRadius( F32 dist, F32 radius ) const +{ + // Fixup any negative or zero distance so we + // don't get a divide by zero. + dist = dist > 0.0f ? dist : 0.001f; + return ( radius / dist ) * mSaveWorldToScreenScale.y; +} + +bool GuiTSCtrl::project( const Point3F &pt, Point3F *dest ) const +{ + return MathUtils::mProjectWorldToScreen(pt,dest,mSaveViewport,mSaveModelview,mSaveProjection); +} + +bool GuiTSCtrl::unproject( const Point3F &pt, Point3F *dest ) const +{ + MathUtils::mProjectScreenToWorld(pt,dest,mSaveViewport,mSaveModelview,mSaveProjection,mLastCameraQuery.farPlane,mLastCameraQuery.nearPlane); + return true; +} + + +F32 GuiTSCtrl::calculateViewDistance(F32 radius) +{ + // Determine if we should use the width fov or height fov. + // If the window is wider than tall, use the height fov to + // keep the object completely in view. + F32 fov = mLastCameraQuery.fov; + F32 wwidth; + F32 wheight; + if(!mLastCameraQuery.ortho) + { + wwidth = mLastCameraQuery.nearPlane * mTan(fov / 2); + wheight = F32(getHeight()) / F32(getWidth()) * wwidth; + } + else + { + wwidth = fov; + wheight = F32(getHeight()) / F32(getWidth()) * wwidth; + } + if(wheight < wwidth) + { + fov = mAtan( wheight / mLastCameraQuery.nearPlane ) * 2.0f; + } + + return (radius / mTan(fov * 0.5f)); +} + +static void offsetMatrix(const MatrixF & mat , MatrixF & matLeft , MatrixF & matRight , const F32 & offset) +{ + matLeft = mat; + matRight = mat; + + Point3F pos = mat.getPosition(); + Point3F x; + mat.getColumn(0,&x); + x.normalizeSafe(); + + pos -= x * offset; + matLeft.setColumn(3,pos); + + pos += x * offset * 2; + matRight.setColumn(3,pos); +} +// ------------------------------------------------------------------- +// onRender +// ------------------------------------------------------------------- +extern ColorI gCanvasClearColor; +void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if(!processCameraQuery(&mLastCameraQuery)) + { + // We have no camera, but render the GUI children + // anyway. This makes editing GuiTSCtrl derived + // controls easier in the GuiEditor. + renderChildControls( offset, updateRect ); + return; + } + + if ( mReflectPriority > 0 ) + { + // Get the total reflection priority. + F32 totalPriority = 0; + for ( U32 i=0; i < smAwakeTSCtrls.size(); i++ ) + if ( smAwakeTSCtrls[i]->isVisible() ) + totalPriority += smAwakeTSCtrls[i]->mReflectPriority; + + REFLECTMGR->update( mReflectPriority / totalPriority, + getExtent(), + mLastCameraQuery ); + } + + if(mForceFOV != 0) + mLastCameraQuery.fov = mDegToRad(mForceFOV); + + if(mCameraZRot) + { + MatrixF rotMat(EulerF(0, 0, mDegToRad(mCameraZRot))); + mLastCameraQuery.cameraMatrix.mul(rotMat); + } + + // set up the camera and viewport stuff: + F32 wwidth; + F32 wheight; + if(!mLastCameraQuery.ortho) + { + wwidth = mLastCameraQuery.nearPlane * mTan(mLastCameraQuery.fov / 2); + wheight = F32(getHeight()) / F32(getWidth()) * wwidth; + } + else + { + wwidth = mLastCameraQuery.fov; + wheight = F32(getHeight()) / F32(getWidth()) * wwidth; + } + + F32 hscale = wwidth * 2 / F32(getWidth()); + F32 vscale = wheight * 2 / F32(getHeight()); + + F32 left = (updateRect.point.x - offset.x) * hscale - wwidth; + F32 right = (updateRect.point.x + updateRect.extent.x - offset.x) * hscale - wwidth; + F32 top = wheight - vscale * (updateRect.point.y - offset.y); + F32 bottom = wheight - vscale * (updateRect.point.y + updateRect.extent.y - offset.y); + + RectI tempRect = updateRect; + +#ifdef TORQUE_OS_MAC + Point2I screensize = getRoot()->getWindowSize(); + tempRect.point.y = screensize.y - (tempRect.point.y + tempRect.extent.y); +#endif + + GFX->setViewport( tempRect ); + + // Clear the zBuffer so GUI doesn't hose object rendering accidentally + GFX->clear( GFXClearZBuffer , ColorI(20,20,20), 1.0f, 0 ); + + if(!mLastCameraQuery.ortho) + { + GFX->setFrustum( left, right, bottom, top, + mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane ); + } + else + { + GFX->setOrtho(left, right, bottom, top, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane, true); + + mOrthoWidth = right - left; + mOrthoHeight = top - bottom; + } + + // We're going to be displaying this render at size of this control in + // pixels - let the scene know so that it can calculate e.g. reflections + // correctly for that final display result. + gClientSceneGraph->setDisplayTargetResolution(getExtent()); + + // save the world matrix before attempting to draw anything + GFX->pushWorldMatrix(); + + // Set the GFX world matrix to the world-to-camera transform, but don't + // change the cameraMatrix in mLastCameraQuery. This is because + // mLastCameraQuery.cameraMatrix is supposed to contain the camera-to-world + // transform. In-place invert would save a copy but mess up any GUIs that + // depend on that value. + MatrixF worldToCamera = mLastCameraQuery.cameraMatrix; + worldToCamera.inverse(); + GFX->setWorldMatrix( worldToCamera ); + + mSaveProjection = GFX->getProjectionMatrix(); + mSaveModelview = GFX->getWorldMatrix(); + mSaveViewport = updateRect; + mSaveWorldToScreenScale = GFX->getWorldToScreenScale(); + + // Set the default non-clip projection as some + // objects depend on this even in non-reflect cases. + gClientSceneGraph->setNonClipProjection( mSaveProjection ); + + // Give the post effect manager the worldToCamera, and cameraToScreen matrices + PFXMGR->setFrameMatrices( mSaveModelview, mSaveProjection ); + + if (!PostEffectManager::smRB3DEffects) + { + renderWorld(updateRect); + } + else + { + offsetMatrix(mLastCameraQuery.cameraMatrix,mLastCameraQuery.cameraMatrixLeft,mLastCameraQuery.cameraMatrixRight,0.025); + //==========left eye first========== + static PostEffect * pRB3DLeftEye = dynamic_cast( Sim::findObject("PFX_RB3D_LEFT") ); + if (pRB3DLeftEye) + { + worldToCamera = mLastCameraQuery.cameraMatrixLeft; + worldToCamera.inverse(); + GFX->setWorldMatrix( worldToCamera ); + mSaveProjection = GFX->getProjectionMatrix(); + mSaveModelview = GFX->getWorldMatrix(); + mSaveViewport = updateRect; + mSaveWorldToScreenScale = GFX->getWorldToScreenScale(); + gClientSceneGraph->setNonClipProjection( mSaveProjection ); + PFXMGR->setFrameMatrices( mSaveModelview, mSaveProjection ); + renderWorld(updateRect); + GFXTexHandle curBackBuffer; + curBackBuffer = PFXMGR->getBackBufferTex(); + pRB3DLeftEye->process( gClientSceneGraph->getSceneState(), curBackBuffer); + } + //==========right eye============ + GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, gCanvasClearColor, 1.0f, 0 ); + static PostEffect * pRB3DRightEye = dynamic_cast(Sim::findObject("PFX_RB3D_RIGHT")); + if (pRB3DRightEye) + { + worldToCamera = mLastCameraQuery.cameraMatrixRight; + worldToCamera.inverse(); + GFX->setWorldMatrix( worldToCamera ); + mSaveProjection = GFX->getProjectionMatrix(); + mSaveModelview = GFX->getWorldMatrix(); + mSaveViewport = updateRect; + mSaveWorldToScreenScale = GFX->getWorldToScreenScale(); + gClientSceneGraph->setNonClipProjection( mSaveProjection ); + PFXMGR->setFrameMatrices( mSaveModelview, mSaveProjection ); + renderWorld(updateRect); + GFXTexHandle curBackBuffer; + curBackBuffer = PFXMGR->getBackBufferTex(); + pRB3DRightEye->process( gClientSceneGraph->getSceneState(), curBackBuffer); + } + //==========combine============ + //GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, gCanvasClearColor, 1.0f, 0 ); + static PostEffect * pRB3DCombine = dynamic_cast(Sim::findObject("PFX_RB3D_COMBINE")); + if (pRB3DCombine) + { + GFXTexHandle curBackBuffer; + curBackBuffer = PFXMGR->getBackBufferTex(); + pRB3DCombine->process( gClientSceneGraph->getSceneState(), curBackBuffer); + } + } + DebugDrawer::get()->render(); + + // restore the world matrix so the GUI will render correctly + GFX->popWorldMatrix(); + + renderChildControls(offset, updateRect); + smFrameCount++; +} + +// ------------------------------------------------------------------- + +ConsoleMethod(GuiTSCtrl, unproject, const char*, 3, 3, "unproject( %screenPosition ) : Transforms 3D screen space coordinates (x, y, depth) to world space.") +{ + char *returnBuffer = Con::getReturnBuffer(64); + Point3F screenPos; + Point3F worldPos; + dSscanf(argv[2], "%g %g %g", &screenPos.x, &screenPos.y, &screenPos.z); + object->unproject(screenPos, &worldPos); + dSprintf(returnBuffer, 64, "%g %g %g", worldPos.x, worldPos.y, worldPos.z); + return returnBuffer; +} + +ConsoleMethod(GuiTSCtrl, project, const char*, 3, 3, "project( %worldPosition ) : Transforms world space coordinates to screen space (x, y, depth)") +{ + char *returnBuffer = Con::getReturnBuffer(64); + Point3F screenPos; + Point3F worldPos; + dSscanf(argv[2], "%g %g %g", &worldPos.x, &worldPos.y, &worldPos.z); + object->project(worldPos, &screenPos); + dSprintf(returnBuffer, 64, "%g %g %g", screenPos.x, screenPos.y, screenPos.z); + return returnBuffer; +} + +ConsoleMethod(GuiTSCtrl, getWorldToScreenScale, const char*, 2, 2, "getWorldToScreenScale() : Returns the scale for converting world space units to pixels.") +{ + char *returnBuffer = Con::getReturnBuffer(64); + const Point2F &scale = object->getWorldToScreenScale(); + dSprintf(returnBuffer, 64, "%g %g", scale.x, scale.y); + return returnBuffer; +} + +ConsoleMethod(GuiTSCtrl, calculateViewDistance, F32, 3, 3, "calculateViewDistance( radius ) : Returns the distance required to fit the given radius within the camera's view.") +{ + F32 radius = dAtof(argv[2]); + return object->calculateViewDistance( radius ); +} diff --git a/gui/3d/guiTSControl.h b/gui/3d/guiTSControl.h new file mode 100644 index 0000000..3d34038 --- /dev/null +++ b/gui/3d/guiTSControl.h @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITSCONTROL_H_ +#define _GUITSCONTROL_H_ + +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif + +struct CameraQuery +{ + SimObject* object; + F32 nearPlane; + F32 farPlane; + F32 fov; + bool ortho; + MatrixF cameraMatrix; + MatrixF cameraMatrixLeft; + MatrixF cameraMatrixRight; +}; + +class GuiTSCtrl : public GuiContainer +{ + typedef GuiContainer Parent; + + static U32 smFrameCount; + F32 mCameraZRot; + F32 mForceFOV; + +protected: + + /// A list of GuiTSCtrl which are awake and + /// most likely rendering. + static Vector smAwakeTSCtrls; + + /// A scalar which controls how much of the reflection + /// update timeslice for this viewport to get. + F32 mReflectPriority; + + F32 mOrthoWidth; + F32 mOrthoHeight; + + MatrixF mSaveModelview; + MatrixF mSaveProjection; + RectI mSaveViewport; + + /// The saved world to screen space scale. + /// @see getWorldToScreenScale + Point2F mSaveWorldToScreenScale; + + /// The last camera query set in onRender. + /// @see getLastCameraQuery + CameraQuery mLastCameraQuery; + +public: + + GuiTSCtrl(); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + virtual bool processCameraQuery(CameraQuery *query); + virtual void renderWorld(const RectI &updateRect); + + static void initPersistFields(); + static void consoleInit(); + + virtual bool onWake(); + virtual void onSleep(); + + /// Returns the last camera query set in onRender. + const CameraQuery& getLastCameraQuery() const { return mLastCameraQuery; } + + /// Returns the screen space X,Y and Z for world space point. + /// The input z coord is depth, from 0 to 1. + bool project( const Point3F &pt, Point3F *dest ) const; + + /// Returns the world space point for X, Y and Z. The ouput + /// z coord is depth, from 0 to 1 + bool unproject( const Point3F &pt, Point3F *dest ) const; + + /// + F32 projectRadius( F32 dist, F32 radius ) const; + + /// Returns the scale for converting world space + /// units to screen space units... aka pixels. + /// @see GFXDevice::getWorldToScreenScale + const Point2F& getWorldToScreenScale() const { return mSaveWorldToScreenScale; } + + /// Returns the distance required to fit the given + /// radius within the camera's view. + F32 calculateViewDistance(F32 radius); + + DECLARE_CONOBJECT(GuiTSCtrl); + DECLARE_CATEGORY( "Gui 3D" ); + DECLARE_DESCRIPTION( "A control that renders a 3D viewport." ); +}; + +#endif // _GUITSCONTROL_H_ diff --git a/gui/buttons/guiBitmapButtonCtrl.cpp b/gui/buttons/guiBitmapButtonCtrl.cpp new file mode 100644 index 0000000..3e6ec9a --- /dev/null +++ b/gui/buttons/guiBitmapButtonCtrl.cpp @@ -0,0 +1,277 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + + +//------------------------------------- +// +// Bitmap Button Contrl +// Set 'bitmap' comsole field to base name of bitmaps to use. This control will +// append '_n' for normal +// append '_h' for hilighted +// append '_d' for depressed +// +// if bitmap cannot be found it will use the default bitmap to render. +// +// if the extent is set to (0,0) in the gui editor and appy hit, this control will +// set it's extent to be exactly the size of the normal bitmap (if present) +// + +#include "platform/platform.h" +#include "gui/buttons/guiBitmapButtonCtrl.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiBitmapButtonCtrl); + +//------------------------------------- +GuiBitmapButtonCtrl::GuiBitmapButtonCtrl() +{ + mBitmapName = StringTable->insert(""); + setExtent(140, 30); +} + + +//------------------------------------- +void GuiBitmapButtonCtrl::initPersistFields() +{ + addField("bitmap", TypeFilename, Offset(mBitmapName, GuiBitmapButtonCtrl)); + Parent::initPersistFields(); +} + + +//------------------------------------- +bool GuiBitmapButtonCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + setActive(true); + setBitmap(mBitmapName); + return true; +} + + +//------------------------------------- +void GuiBitmapButtonCtrl::onSleep() +{ + if (dStricmp(mBitmapName, "texhandle") != 0) + { + mTextureNormal = NULL; + mTextureHilight = NULL; + mTextureDepressed = NULL; + mTextureInactive = NULL; + } + + Parent::onSleep(); +} + + +//------------------------------------- + +ConsoleMethod( GuiBitmapButtonCtrl, setBitmap, void, 3, 3, "(filepath name)") +{ + object->setBitmap(argv[2]); +} + +//------------------------------------- +void GuiBitmapButtonCtrl::inspectPostApply() +{ + // if the extent is set to (0,0) in the gui editor and appy hit, this control will + // set it's extent to be exactly the size of the normal bitmap (if present) + Parent::inspectPostApply(); + + if ((getWidth() == 0) && (getHeight() == 0) && mTextureNormal) + { + setExtent( mTextureNormal->getWidth(), mTextureNormal->getHeight()); + } +} + + +//------------------------------------- +void GuiBitmapButtonCtrl::setBitmap(const char *name) +{ + mBitmapName = StringTable->insert(name); + if(!isAwake()) + return; + + if (*mBitmapName) + { + if (dStricmp(mBitmapName, "texhandle") != 0) + { + char buffer[1024]; + char *p; + dStrcpy(buffer, name); + p = buffer + dStrlen(buffer); + + mTextureNormal = GFXTexHandle(buffer, &GFXDefaultPersistentProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__)); + if (!mTextureNormal) + { + dStrcpy(p, "_n"); + mTextureNormal = GFXTexHandle(buffer, &GFXDefaultPersistentProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__)); + } + dStrcpy(p, "_h"); + mTextureHilight = GFXTexHandle(buffer, &GFXDefaultPersistentProfile, avar("%s() - mTextureHighlight (line %d)", __FUNCTION__, __LINE__)); + if (!mTextureHilight) + mTextureHilight = mTextureNormal; + dStrcpy(p, "_d"); + mTextureDepressed = GFXTexHandle(buffer, &GFXDefaultPersistentProfile, avar("%s() - mTextureDepressed (line %d)", __FUNCTION__, __LINE__)); + if (!mTextureDepressed) + mTextureDepressed = mTextureHilight; + dStrcpy(p, "_i"); + mTextureInactive = GFXTexHandle(buffer, &GFXDefaultPersistentProfile, avar("%s() - mTextureInactive (line %d)", __FUNCTION__, __LINE__)); + if (!mTextureInactive) + mTextureInactive = mTextureNormal; + + if (mTextureNormal.isNull() && mTextureHilight.isNull() && mTextureDepressed.isNull() && mTextureInactive.isNull()) + { + Con::warnf("GFXTextureManager::createTexture - Unable to load Texture: %s", buffer); + this->setBitmap("core/art/unavailable"); + return; + } + } + } + else + { + mTextureNormal = NULL; + mTextureHilight = NULL; + mTextureDepressed = NULL; + mTextureInactive = NULL; + } + setUpdate(); +} + +void GuiBitmapButtonCtrl::setBitmapHandles(GFXTexHandle normal, GFXTexHandle highlighted, GFXTexHandle depressed, GFXTexHandle inactive) +{ + mTextureNormal = normal; + mTextureHilight = highlighted; + mTextureDepressed = depressed; + mTextureInactive = inactive; + + if (!mTextureHilight) + mTextureHilight = mTextureNormal; + if (!mTextureDepressed) + mTextureDepressed = mTextureHilight; + if (!mTextureInactive) + mTextureInactive = mTextureNormal; + + if (mTextureNormal.isNull() && mTextureHilight.isNull() && mTextureDepressed.isNull() && mTextureInactive.isNull()) + { + Con::warnf("GuiBitmapButtonCtrl::setBitmapHandles() - Invalid texture handles"); + setBitmap("core/art/unavailable"); + + return; + } + + mBitmapName = StringTable->insert("texhandle"); +} + + +//------------------------------------- +void GuiBitmapButtonCtrl::onRender(Point2I offset, const RectI& updateRect) +{ + enum { + NORMAL, + HILIGHT, + DEPRESSED, + INACTIVE + } state = NORMAL; + + if (mActive) + { + if (mMouseOver) state = HILIGHT; + if (mDepressed || mStateOn) state = DEPRESSED; + } + else + state = INACTIVE; + + switch (state) + { + case NORMAL: renderButton(mTextureNormal, offset, updateRect); break; + case HILIGHT: renderButton(mTextureHilight ? mTextureHilight : mTextureNormal, offset, updateRect); break; + case DEPRESSED: renderButton(mTextureDepressed, offset, updateRect); break; + case INACTIVE: renderButton(mTextureInactive ? mTextureInactive : mTextureNormal, offset, updateRect); break; + } +} + +//------------------------------------------------------------------------------ + +void GuiBitmapButtonCtrl::renderButton(GFXTexHandle &texture, Point2I &offset, const RectI& updateRect) +{ + if (texture) + { + RectI rect(offset, getExtent()); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch(texture, rect); + renderChildControls( offset, updateRect); + } + else + Parent::onRender(offset, updateRect); +} + +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(GuiBitmapButtonTextCtrl); + +void GuiBitmapButtonTextCtrl::onRender(Point2I offset, const RectI& updateRect) +{ + enum { + NORMAL, + HILIGHT, + DEPRESSED, + INACTIVE + } state = NORMAL; + + if (mActive) + { + if (mMouseOver) state = HILIGHT; + if (mDepressed || mStateOn) state = DEPRESSED; + } + else + state = INACTIVE; + + GFXTexHandle texture; + + switch (state) + { + case NORMAL: + texture = mTextureNormal; + break; + case HILIGHT: + texture = mTextureHilight; + break; + case DEPRESSED: + texture = mTextureDepressed; + break; + case INACTIVE: + texture = mTextureInactive; + if(!texture) + texture = mTextureNormal; + break; + } + if (texture) + { + RectI rect(offset, getExtent()); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch(texture, rect); + + Point2I textPos = offset; + if(mDepressed) + textPos += Point2I(1,1); + + // Make sure we take the profile's textOffset into account. + textPos += mProfile->mTextOffset; + + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor ); + renderJustifiedText(textPos, getExtent(), mButtonText); + + renderChildControls( offset, updateRect); + } + else + Parent::onRender(offset, updateRect); +} diff --git a/gui/buttons/guiBitmapButtonCtrl.h b/gui/buttons/guiBitmapButtonCtrl.h new file mode 100644 index 0000000..1576f37 --- /dev/null +++ b/gui/buttons/guiBitmapButtonCtrl.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBITMAPBUTTON_H_ +#define _GUIBITMAPBUTTON_H_ + +#ifndef _GUIBUTTONCTRL_H_ +#include "gui/buttons/guiButtonCtrl.h" +#endif +#ifndef GFX_Texture_Manager_H_ +#include "gfx/gfxTextureManager.h" +#endif + +///------------------------------------- +/// Bitmap Button Control +/// Set 'bitmap' console field to base name of bitmaps to use. This control will +/// append '_n' for normal +/// append '_h' for highlighted +/// append '_d' for depressed +/// +/// if bitmap cannot be found it will use the default bitmap to render. +/// +/// if the extent is set to (0,0) in the gui editor and apply hit, this control will +/// set it's extent to be exactly the size of the normal bitmap (if present) +/// +class GuiBitmapButtonCtrl : public GuiButtonCtrl +{ +private: + typedef GuiButtonCtrl Parent; + +protected: + StringTableEntry mBitmapName; + GFXTexHandle mTextureNormal; + GFXTexHandle mTextureHilight; + GFXTexHandle mTextureDepressed; + GFXTexHandle mTextureInactive; + + void renderButton(GFXTexHandle &texture, Point2I &offset, const RectI& updateRect); + +public: + + DECLARE_CONOBJECT(GuiBitmapButtonCtrl); + DECLARE_DESCRIPTION( "A button control rendered entirely from bitmaps.\n" + "The individual button states are represented with separate bitmaps." ); + + GuiBitmapButtonCtrl(); + + static void initPersistFields(); + + //Parent methods + bool onWake(); + void onSleep(); + void inspectPostApply(); + + void setBitmap(const char *name); + void setBitmapHandles(GFXTexHandle normal, GFXTexHandle highlighted, GFXTexHandle depressed, GFXTexHandle inactive); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +class GuiBitmapButtonTextCtrl : public GuiBitmapButtonCtrl +{ + typedef GuiBitmapButtonCtrl Parent; + +public: + + DECLARE_CONOBJECT( GuiBitmapButtonTextCtrl ); + DECLARE_DESCRIPTION( "An extension of GuiBitmapButtonCtrl that also renders a text\n" + "label on the button." ); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif //_GUI_BITMAP_BUTTON_CTRL_H diff --git a/gui/buttons/guiBorderButton.cpp b/gui/buttons/guiBorderButton.cpp new file mode 100644 index 0000000..3404b08 --- /dev/null +++ b/gui/buttons/guiBorderButton.cpp @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonBaseCtrl.h" +#include "gui/core/guiDefaultControlRender.h" + + +class GuiBorderButtonCtrl : public GuiButtonBaseCtrl +{ + typedef GuiButtonBaseCtrl Parent; + +protected: +public: + DECLARE_CONOBJECT(GuiBorderButtonCtrl); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +IMPLEMENT_CONOBJECT(GuiBorderButtonCtrl); + +void GuiBorderButtonCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if ( mProfile->mBorder > 0 ) + { + RectI bounds( offset, getExtent() ); + for ( S32 i=0; i < mProfile->mBorderThickness; i++ ) + { + GFX->getDrawUtil()->drawRect( bounds, mProfile->mBorderColor ); + bounds.inset( 1, 1 ); + } + } + + if ( mActive ) + { + if ( mStateOn || mDepressed ) + { + RectI bounds( offset, getExtent() ); + for ( S32 i=0; i < mProfile->mBorderThickness; i++ ) + { + GFX->getDrawUtil()->drawRect( bounds, mProfile->mFontColorSEL ); + bounds.inset( 1, 1 ); + } + } + + if ( mMouseOver ) + { + RectI bounds( offset, getExtent() ); + for ( S32 i=0; i < mProfile->mBorderThickness; i++ ) + { + GFX->getDrawUtil()->drawRect( bounds, mProfile->mFontColorHL ); + bounds.inset( 1, 1 ); + } + } + } + + renderChildControls( offset, updateRect ); +} + diff --git a/gui/buttons/guiButtonBaseCtrl.cpp b/gui/buttons/guiButtonBaseCtrl.cpp new file mode 100644 index 0000000..cf6c58f --- /dev/null +++ b/gui/buttons/guiButtonBaseCtrl.cpp @@ -0,0 +1,368 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonBaseCtrl.h" +#include "i18n/lang.h" +#include "sfx/sfxSystem.h" + + +IMPLEMENT_CONOBJECT( GuiButtonBaseCtrl ); + +GuiButtonBaseCtrl::GuiButtonBaseCtrl() +{ + mDepressed = false; + mMouseOver = false; + mActive = true; + mButtonText = StringTable->insert("Button"); + mButtonTextID = StringTable->insert(""); + mStateOn = false; + mRadioGroup = -1; + mButtonType = ButtonTypePush; + mUseMouseEvents = false; +} + +bool GuiButtonBaseCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + + // is we have a script variable, make sure we're in sync + if ( mConsoleVariable[0] ) + mStateOn = Con::getBoolVariable( mConsoleVariable ); + if(mButtonTextID && *mButtonTextID != 0) + setTextID(mButtonTextID); + + return true; +} + +ConsoleMethod( GuiButtonBaseCtrl, performClick, void, 2, 2, "() - simulates a button click from script." ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->onAction(); +} + +ConsoleMethod( GuiButtonBaseCtrl, setText, void, 3, 3, "(string text) - sets the text of the button to the string." ) +{ + TORQUE_UNUSED(argc); + object->setText( argv[2] ); +} + +ConsoleMethod( GuiButtonBaseCtrl, setTextID, void, 3, 3, "(string id) - sets the text of the button to the localized string." ) +{ + TORQUE_UNUSED(argc); + object->setTextID(argv[2]); +} +ConsoleMethod( GuiButtonBaseCtrl, getText, const char *, 2, 2, "() - returns the text of the button." ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return object->getText( ); +} +ConsoleMethod( GuiButtonBaseCtrl, setStateOn, void, 3, 3, "(bool isStateOn) - sets the state on member and updates siblings of the same group." ) +{ + TORQUE_UNUSED(argc); + object->setStateOn(dAtob(argv[2])); +} + +ConsoleMethod( GuiButtonBaseCtrl, resetState, void, 2, 2, "() - resets the state of the button." ) +{ + TORQUE_UNUSED(argc); + object->resetState(); +} + +static const EnumTable::Enums buttonTypeEnums[] = +{ + { GuiButtonBaseCtrl::ButtonTypePush, "PushButton" }, + { GuiButtonBaseCtrl::ButtonTypeCheck, "ToggleButton" }, + { GuiButtonBaseCtrl::ButtonTypeRadio, "RadioButton" }, +}; + +static const EnumTable gButtonTypeTable(3, &buttonTypeEnums[0]); + + + +void GuiButtonBaseCtrl::initPersistFields() +{ + addGroup("GuiButtonBaseCtrl"); + addField("text", TypeCaseString, Offset(mButtonText, GuiButtonBaseCtrl)); + addField("textID", TypeString, Offset(mButtonTextID, GuiButtonBaseCtrl)); + addField("groupNum", TypeS32, Offset(mRadioGroup, GuiButtonBaseCtrl)); + addField("buttonType", TypeEnum, Offset(mButtonType, GuiButtonBaseCtrl), 1, &gButtonTypeTable); + addField("useMouseEvents", TypeBool, Offset(mUseMouseEvents, GuiButtonBaseCtrl)); + endGroup("GuiButtonBaseCtrl"); + Parent::initPersistFields(); +} + +void GuiButtonBaseCtrl::setText(const char *text) +{ + mButtonText = StringTable->insert(text, true); +} + +void GuiButtonBaseCtrl::setStateOn( bool bStateOn ) +{ + if(!mActive) + return; + + if(mButtonType == ButtonTypeCheck) + { + mStateOn = bStateOn; + } + else if(mButtonType == ButtonTypeRadio) + { + messageSiblings(mRadioGroup); + mStateOn = bStateOn; + } + setUpdate(); +} + +void GuiButtonBaseCtrl::setTextID(const char *id) +{ + S32 n = Con::getIntVariable(id, -1); + if(n != -1) + { + mButtonTextID = StringTable->insert(id); + setTextID(n); + } +} +void GuiButtonBaseCtrl::setTextID(S32 id) +{ + const UTF8 *str = getGUIString(id); + if(str) + setText((const char*)str); + //mButtonTextID = id; +} +const char *GuiButtonBaseCtrl::getText() +{ + return mButtonText; +} + +//--------------------------------------------------------------------------- +void GuiButtonBaseCtrl::acceleratorKeyPress(U32) +{ + if (! mActive) + return; + + //set the bool + mDepressed = true; + + if (mProfile->mTabable) + setFirstResponder(); +} + +//--------------------------------------------------------------------------- +void GuiButtonBaseCtrl::acceleratorKeyRelease(U32) +{ + if (! mActive) + return; + + if (mDepressed) + { + //set the bool + mDepressed = false; + //perform the action + onAction(); + } + + //update + setUpdate(); +} + +void GuiButtonBaseCtrl::onMouseDown(const GuiEvent &event) +{ + if (! mActive) + return; + + if (mProfile->mCanKeyFocus) + setFirstResponder(); + + if (mProfile->mSoundButtonDown) + SFX->playOnce(mProfile->mSoundButtonDown); + + if(mUseMouseEvents) + Con::executef( this, "onMouseDown" ); + + //lock the mouse + mouseLock(); + mDepressed = true; + + // If we have a double click then execute the alt command. + if ( event.mouseClickCount == 2 ) + { + if ( isMethod( "onDoubleClick" ) ) + Con::executef( this, "onDoubleClick" ); + + execAltConsoleCallback(); + } + + //update + setUpdate(); +} + +void GuiButtonBaseCtrl::onMouseEnter(const GuiEvent &event) +{ + setUpdate(); + + if(mUseMouseEvents) + Con::executef( this, "onMouseEnter" ); + + if(isMouseLocked()) + { + mDepressed = true; + mMouseOver = true; + } + else + { + if ( mActive && mProfile->mSoundButtonOver ) + SFX->playOnce(mProfile->mSoundButtonOver); + + mMouseOver = true; + } +} + +void GuiButtonBaseCtrl::onMouseLeave(const GuiEvent &) +{ + setUpdate(); + + if(mUseMouseEvents) + Con::executef( this, "onMouseLeave" ); + if(isMouseLocked()) + mDepressed = false; + mMouseOver = false; +} + +void GuiButtonBaseCtrl::onMouseUp(const GuiEvent &event) +{ + if (! mActive) + return; + + mouseUnlock(); + + setUpdate(); + + if(mUseMouseEvents) + Con::executef( this, "onMouseUp" ); + + //if we released the mouse within this control, perform the action + if (mDepressed) + onAction(); + + mDepressed = false; +} + +void GuiButtonBaseCtrl::onRightMouseUp(const GuiEvent &event) +{ + if(isMethod("onRightClick")) + Con::executef( this, "onRightClick" ); + + Parent::onRightMouseUp( event ); +} + +//-------------------------------------------------------------------------- +bool GuiButtonBaseCtrl::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, kill the event + if (!mActive) + return true; + + //see if the key down is a return or space or not + if ((event.keyCode == KEY_RETURN || event.keyCode == KEY_SPACE) + && event.modifier == 0) + { + if ( mProfile->mSoundButtonDown ) + SFX->playOnce( mProfile->mSoundButtonDown); + + return true; + } + //otherwise, pass the event to it's parent + return Parent::onKeyDown(event); +} + +//-------------------------------------------------------------------------- +bool GuiButtonBaseCtrl::onKeyUp(const GuiEvent &event) +{ + //if the control is a dead end, kill the event + if (!mActive) + return true; + + //see if the key down is a return or space or not + if (mDepressed && + (event.keyCode == KEY_RETURN || event.keyCode == KEY_SPACE) && + event.modifier == 0) + { + onAction(); + return true; + } + + //otherwise, pass the event to it's parent + return Parent::onKeyUp(event); +} + +//--------------------------------------------------------------------------- +void GuiButtonBaseCtrl::setScriptValue(const char *value) +{ + mStateOn = dAtob(value); + + // Update the console variable: + if ( mConsoleVariable[0] ) + Con::setBoolVariable( mConsoleVariable, mStateOn ); + + setUpdate(); +} + +//--------------------------------------------------------------------------- +const char *GuiButtonBaseCtrl::getScriptValue() +{ + return mStateOn ? "1" : "0"; +} + +//--------------------------------------------------------------------------- +void GuiButtonBaseCtrl::onAction() +{ + if(!mActive) + return; + + if(mButtonType == ButtonTypeCheck) + { + mStateOn = mStateOn ? false : true; + + // Update the console variable: + if ( mConsoleVariable[0] ) + Con::setBoolVariable( mConsoleVariable, mStateOn ); + // Execute the console command (if any). Unnecessary. Parent does this already. + //if( mConsoleCommand[0] ) + // Con::evaluate( mConsoleCommand, false ); + + } + else if(mButtonType == ButtonTypeRadio) + { + mStateOn = true; + messageSiblings(mRadioGroup); + } + setUpdate(); + + + // Provide and onClick script callback. + if( isMethod("onClick") ) + Con::executef( this, "onClick" ); + + Parent::onAction(); +} + +//--------------------------------------------------------------------------- +void GuiButtonBaseCtrl::onMessage( GuiControl *sender, S32 msg ) +{ + Parent::onMessage(sender, msg); + if( mRadioGroup == msg && mButtonType == ButtonTypeRadio ) + { + setUpdate(); + mStateOn = ( sender == this ); + } +} + + + diff --git a/gui/buttons/guiButtonBaseCtrl.h b/gui/buttons/guiButtonBaseCtrl.h new file mode 100644 index 0000000..69593a8 --- /dev/null +++ b/gui/buttons/guiButtonBaseCtrl.h @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBUTTONBASECTRL_H_ +#define _GUIBUTTONBASECTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +// all the button functionality is moving into buttonBase +// thus, all subclasses will just be rendering classes, +// and radios and check boxes can be done using pushbuttons +// or bitmap buttons. + +class GuiButtonBaseCtrl : public GuiControl +{ + typedef GuiControl Parent; + +protected: + StringTableEntry mButtonText; + StringTableEntry mButtonTextID; + bool mDepressed; + bool mMouseOver; + bool mStateOn; + S32 mButtonType; + S32 mRadioGroup; + bool mUseMouseEvents; +public: + enum { + ButtonTypePush, + ButtonTypeCheck, + ButtonTypeRadio, + }; + + GuiButtonBaseCtrl(); + bool onWake(); + + DECLARE_CONOBJECT(GuiButtonBaseCtrl); + DECLARE_CATEGORY( "Gui Buttons" ); + + static void initPersistFields(); + + void setText(const char *text); + void setTextID(S32 id); + void setTextID(const char *id); + const char *getText(); + void setStateOn( bool bStateOn ); + bool getStateOn() const { return mStateOn; } + + void setDepressed( bool depressed ) { mDepressed = depressed; } + void resetState() {mDepressed = false; mMouseOver = false;} + + void acceleratorKeyPress(U32 index); + void acceleratorKeyRelease(U32 index); + + void onMouseDown(const GuiEvent &); + void onMouseUp(const GuiEvent &); + void onRightMouseUp(const GuiEvent &); + + void onMouseEnter(const GuiEvent &); + void onMouseLeave(const GuiEvent &); + + bool onKeyDown(const GuiEvent &event); + bool onKeyUp(const GuiEvent &event); + + void setScriptValue(const char *value); + const char *getScriptValue(); + + void onMessage(GuiControl *,S32 msg); + void onAction(); + +}; + +#endif diff --git a/gui/buttons/guiButtonCtrl.cpp b/gui/buttons/guiButtonCtrl.cpp new file mode 100644 index 0000000..90e55d3 --- /dev/null +++ b/gui/buttons/guiButtonCtrl.cpp @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/buttons/guiButtonCtrl.h" + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" + + +IMPLEMENT_CONOBJECT(GuiButtonCtrl); + +GuiButtonCtrl::GuiButtonCtrl() +{ + setExtent(140, 30); + mButtonText = StringTable->insert(""); +} + +bool GuiButtonCtrl::onWake() +{ + if( !Parent::onWake() ) + return false; + + // Button Theme? + if( mProfile->constructBitmapArray() >= 36 ) + mHasTheme = true; + else + mHasTheme = false; + + return true; + +} +//-------------------------------------------------------------------------- + +void GuiButtonCtrl::onRender(Point2I offset, + const RectI& updateRect) +{ + bool highlight = mMouseOver; + bool depressed = mDepressed; + + ColorI fontColor = mActive ? (highlight ? mProfile->mFontColorHL : mProfile->mFontColor) : mProfile->mFontColorNA; + ColorI backColor = mActive ? mProfile->mFillColor : mProfile->mFillColorNA; + ColorI borderColor = mActive ? mProfile->mBorderColor : mProfile->mBorderColorNA; + + RectI boundsRect(offset, getExtent()); + + if( mProfile->mBorder != 0 && !mHasTheme ) + { + if (mDepressed || mStateOn) + renderFilledBorder( boundsRect, mProfile->mBorderColorHL, mProfile->mFillColorHL ); + else + renderFilledBorder( boundsRect, mProfile->mBorderColor, mProfile->mFillColor ); + } + else if( mHasTheme ) + { + S32 indexMultiplier = 1; + + if( !mActive ) + indexMultiplier = 4; + else if ( mMouseOver ) + indexMultiplier = 3; + else if ( mDepressed || mStateOn ) + indexMultiplier = 2; + + renderSizableBitmapBordersFilled( boundsRect, indexMultiplier, mProfile ); + } + + Point2I textPos = offset; + if(depressed) + textPos += Point2I(1,1); + + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + renderJustifiedText(textPos, getExtent(), mButtonText); + + //render the children + renderChildControls( offset, updateRect); +} + diff --git a/gui/buttons/guiButtonCtrl.h b/gui/buttons/guiButtonCtrl.h new file mode 100644 index 0000000..7eba252 --- /dev/null +++ b/gui/buttons/guiButtonCtrl.h @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBUTTONCTRL_H_ +#define _GUIBUTTONCTRL_H_ + +#ifndef _GUIBUTTONBASECTRL_H_ +#include "gui/buttons/guiButtonBaseCtrl.h" +#endif + +class GuiButtonCtrl : public GuiButtonBaseCtrl +{ + typedef GuiButtonBaseCtrl Parent; +protected: + bool mHasTheme; +public: + DECLARE_CONOBJECT(GuiButtonCtrl); + GuiButtonCtrl(); + bool onWake(); + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif //_GUI_BUTTON_CTRL_H diff --git a/gui/buttons/guiCheckBoxCtrl.cpp b/gui/buttons/guiCheckBoxCtrl.cpp new file mode 100644 index 0000000..d5bb93a --- /dev/null +++ b/gui/buttons/guiCheckBoxCtrl.cpp @@ -0,0 +1,241 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/buttons/guiCheckBoxCtrl.h" + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiCanvas.h" +#include "console/consoleTypes.h" +#include "sfx/sfxSystem.h" + +IMPLEMENT_CONOBJECT(GuiCheckBoxCtrl); + +//--------------------------------------------------------------------------- +GuiCheckBoxCtrl::GuiCheckBoxCtrl() +{ + setExtent(140, 30); + mStateOn = false; + mIndent = 0; + mButtonType = ButtonTypeCheck; + mUseInactiveState = false; +} + +//--------------------------------------------------------------------------- + +void GuiCheckBoxCtrl::initPersistFields() +{ + addField("useInactiveState", TypeBool, Offset(mUseInactiveState, GuiCheckBoxCtrl)); + + Parent::initPersistFields(); +} + +bool GuiCheckBoxCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + + // make sure there is a bitmap array for this control type + // if it is declared as such in the control + mProfile->constructBitmapArray(); + + return true; +} + +void GuiCheckBoxCtrl::onMouseDown(const GuiEvent& event) +{ + if (!mUseInactiveState) + return Parent::onMouseDown(event); + + if (mProfile->mCanKeyFocus) + setFirstResponder(); + + if (mProfile->mSoundButtonDown) + { + //F32 pan = (F32(event.mousePoint.x)/F32(getRoot()->getWidth())*2.0f-1.0f)*0.8f; + SFX->playOnce( mProfile->mSoundButtonDown ); + } + + //lock the mouse + mouseLock(); + mDepressed = true; + + //update + setUpdate(); +} + +void GuiCheckBoxCtrl::onMouseUp(const GuiEvent& event) +{ + if (!mUseInactiveState) + return Parent::onMouseUp(event); + + mouseUnlock(); + + setUpdate(); + + //if we released the mouse within this control, perform the action + if (mDepressed) + onAction(); + + mDepressed = false; +} + +void GuiCheckBoxCtrl::onAction() +{ + if (!mUseInactiveState) + return Parent::onAction(); + + if(mButtonType == ButtonTypeCheck) + { + if (!mActive) + { + mActive = true; + mStateOn = true; + } + else if (mStateOn) + mStateOn = false; + else if (!mStateOn) + mActive = false; + + // Update the console variable: + if ( mConsoleVariable[0] ) + Con::setBoolVariable( mConsoleVariable, mStateOn ); + // Execute the console command (if any) + if( mConsoleCommand[0] ) + Con::evaluate( mConsoleCommand, false ); + } + setUpdate(); + + // Provide and onClick script callback. + if( isMethod("onClick") ) + Con::executef( this, "onClick" ); +} + +//--------------------------------------------------------------------------- +void GuiCheckBoxCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + // RLP/Sickhead NOTE: New/experimental code + // for notifying the GuiCheckBoxCtrl of changes + // to its mConsoleVariable's state. + if ( mConsoleVariable[0] ) + { + bool stateOn = Con::getBoolVariable( mConsoleVariable ); + if ( stateOn != mStateOn ) + mStateOn = stateOn; + } + + ColorI backColor = mActive ? mProfile->mFillColor : mProfile->mFillColorNA; + ColorI fontColor = mActive ? (mMouseOver ? mProfile->mFontColorHL : mProfile->mFontColor) : mProfile->mFontColorNA; + ColorI insideBorderColor = isFirstResponder() ? mProfile->mBorderColorHL : mProfile->mBorderColor; + + // just draw the check box and the text: + S32 xOffset = 0; + GFX->getDrawUtil()->clearBitmapModulation(); + if(mProfile->mBitmapArrayRects.size() >= 4) + { + S32 index = mStateOn; + if(!mActive) + { + if(mProfile->mBitmapArrayRects.size() >= 6) + { + // New style checkbox bitmap with 6 images + index = 4 + mStateOn; + } + else + { + // Old style checkbox bitmap + index = 2; + } + } + else if(mDepressed) + { + index += 2; + } + xOffset = mProfile->mBitmapArrayRects[0].extent.x + 2 + mIndent; + S32 y = (getHeight() - mProfile->mBitmapArrayRects[0].extent.y) / 2; + GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject, offset + Point2I(mIndent, y), mProfile->mBitmapArrayRects[index]); + } + + if(mButtonText[0] != '\0') + { + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + renderJustifiedText(Point2I(offset.x + xOffset, offset.y), + Point2I(getWidth() - getHeight(), getHeight()), + mButtonText); + } + //render the children + renderChildControls(offset, updateRect); +} + +ConsoleMethod(GuiCheckBoxCtrl, setStateOn, void, 3, 3, "(state)") +{ + if (dStricmp(argv[2], "true") == 0) + object->setStateOn(1); + else if (dStricmp(argv[2], "false") == 0) + object->setStateOn(0); + else + object->setStateOn(dAtoi(argv[2])); +} + +void GuiCheckBoxCtrl::setStateOn(S32 state) +{ + if (mUseInactiveState) + { + if (state < 0) + { + setActive(false); + Parent::setStateOn(false); + } + else if (state == 0) + { + setActive(true); + Parent::setStateOn(false); + } + else if (state > 0) + { + setActive(true); + Parent::setStateOn(true); + } + } + else + Parent::setStateOn((bool)state); +} + +const char* GuiCheckBoxCtrl::getScriptValue() +{ + if (mUseInactiveState) + { + if (isActive()) + if (mStateOn) + return "1"; + else + return "0"; + else + return "-1"; + } + else + return Parent::getScriptValue(); +} + +void GuiCheckBoxCtrl::autoSize() +{ + U32 width, height; + + U32 bmpWidth = mProfile->mBitmapArrayRects[0].extent.x + 2 + mIndent; + U32 strWidth = mProfile->mFont->getStrWidthPrecise( mButtonText ); + + width = bmpWidth + strWidth + 2; + + + U32 bmpHeight = mProfile->mBitmapArrayRects[0].extent.y; + U32 fontHeight = mProfile->mFont->getHeight(); + + height = getMax( bmpHeight, fontHeight ) + 4; + + setExtent( width, height ); +} + diff --git a/gui/buttons/guiCheckBoxCtrl.h b/gui/buttons/guiCheckBoxCtrl.h new file mode 100644 index 0000000..5bef9c5 --- /dev/null +++ b/gui/buttons/guiCheckBoxCtrl.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICHECKBOXCTRL_H_ +#define _GUICHECKBOXCTRL_H_ + +#ifndef _GUIBUTTONBASECTRL_H_ +#include "gui/buttons/guiButtonBaseCtrl.h" +#endif + +class GuiCheckBoxCtrl : public GuiButtonBaseCtrl +{ + typedef GuiButtonBaseCtrl Parent; + + bool mUseInactiveState; + +protected: +public: + S32 mIndent; + DECLARE_CONOBJECT(GuiCheckBoxCtrl); + DECLARE_DESCRIPTION( "A toggle button that displays a text label and an on/off checkbox." ); + GuiCheckBoxCtrl(); + + void setStateOn(S32 state); + virtual const char* getScriptValue(); + + virtual void onMouseDown(const GuiEvent& event); + virtual void onMouseUp(const GuiEvent& event); + virtual void onAction(); + void onRender(Point2I offset, const RectI &updateRect); + bool onWake(); + + static void initPersistFields(); + + void autoSize(); +}; + +#endif //_GUI_CHECKBOX_CTRL_H diff --git a/gui/buttons/guiIconButtonCtrl.cpp b/gui/buttons/guiIconButtonCtrl.cpp new file mode 100644 index 0000000..ce87556 --- /dev/null +++ b/gui/buttons/guiIconButtonCtrl.cpp @@ -0,0 +1,385 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + + +//------------------------------------- +// +// Icon Button Control +// Draws the bitmap within a special button control. Only a single bitmap is used and the +// button will be drawn in a highlighted mode when the mouse hovers over it or when it +// has been clicked. +// +// Use mTextLocation to choose where within the button the text will be drawn, if at all. +// Use mTextMargin to set the text away from the button sides or from the bitmap. +// Use mButtonMargin to set everything away from the button sides. +// Use mErrorBitmapName to set the name of a bitmap to draw if the main bitmap cannot be found. +// Use mFitBitmapToButton to force the bitmap to fill the entire button extent. Usually used +// with no button text defined. +// +// + +#include "platform/platform.h" +#include "gui/buttons/guiIconButtonCtrl.h" + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" + +static const ColorI colorWhite(255,255,255); +static const ColorI colorBlack(0,0,0); + +IMPLEMENT_CONOBJECT(GuiIconButtonCtrl); + + +GuiIconButtonCtrl::GuiIconButtonCtrl() +{ + mBitmapName = StringTable->insert(""); + mTextLocation = TextLocLeft; + mIconLocation = IconLocLeft; + mTextMargin = 4; + mButtonMargin.set(4,4); + + mFitBitmapToButton = false; + mMakeIconSquare = false; + + mErrorBitmapName = StringTable->insert(""); + mErrorTextureHandle = NULL; + + mAutoSize = false; + + setExtent(140, 30); +} + +static const EnumTable::Enums textLocEnums[] = +{ + { GuiIconButtonCtrl::TextLocNone, "None" }, + { GuiIconButtonCtrl::TextLocBottom, "Bottom" }, + { GuiIconButtonCtrl::TextLocRight, "Right" }, + { GuiIconButtonCtrl::TextLocTop, "Top" }, + { GuiIconButtonCtrl::TextLocLeft, "Left" }, + { GuiIconButtonCtrl::TextLocCenter, "Center" }, +}; + +static const EnumTable gTextLocTable(6, &textLocEnums[0]); + + +static const EnumTable::Enums iconLocEnums[] = +{ + { GuiIconButtonCtrl::IconLocNone, "None" }, + { GuiIconButtonCtrl::IconLocLeft, "Left" }, + { GuiIconButtonCtrl::IconLocRight, "Right" }, + { GuiIconButtonCtrl::IconLocCenter, "Center" } +}; +static const EnumTable gIconLocTable(4, &iconLocEnums[0]); + + +void GuiIconButtonCtrl::initPersistFields() +{ + addField( "buttonMargin", TypePoint2I, Offset( mButtonMargin, GuiIconButtonCtrl ) ); + addField( "iconBitmap", TypeFilename, Offset( mBitmapName, GuiIconButtonCtrl ) ); + addField( "iconLocation", TypeEnum, Offset( mIconLocation, GuiIconButtonCtrl ), 1, &gIconLocTable ); + addField( "sizeIconToButton", TypeBool, Offset( mFitBitmapToButton, GuiIconButtonCtrl ) ); + addField( "makeIconSquare", TypeBool, Offset( mMakeIconSquare, GuiIconButtonCtrl ) ); + addField( "textLocation", TypeEnum, Offset( mTextLocation, GuiIconButtonCtrl ), 1, &gTextLocTable ); + addField( "textMargin", TypeS32, Offset( mTextMargin, GuiIconButtonCtrl ) ); + addField( "autoSize", TypeBool, Offset( mAutoSize, GuiIconButtonCtrl ) ); + Parent::initPersistFields(); +} + +bool GuiIconButtonCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + setActive(true); + + setBitmap(mBitmapName); + + if( mProfile ) + mProfile->constructBitmapArray(); + + return true; +} + +void GuiIconButtonCtrl::onSleep() +{ + mTextureNormal = NULL; + Parent::onSleep(); +} + +void GuiIconButtonCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); +} + +void GuiIconButtonCtrl::onStaticModified(const char* slotName, const char* newValue) +{ + if ( isProperlyAdded() && !dStricmp(slotName, "autoSize") ) + resize( getPosition(), getExtent() ); +} + +bool GuiIconButtonCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if ( !mAutoSize || !mProfile->mFont ) + return Parent::resize( newPosition, newExtent ); + + Point2I autoExtent( mMinExtent ); + + if ( mIconLocation != IconLocNone ) + { + autoExtent.y = mTextureNormal.getHeight() + mButtonMargin.y * 2; + autoExtent.x = mTextureNormal.getWidth() + mButtonMargin.x * 2; + } + + if ( mTextLocation != TextLocNone && mButtonText && mButtonText[0] ) + { + U32 strWidth = mProfile->mFont->getStrWidthPrecise( mButtonText ); + + if ( mTextLocation == TextLocLeft || mTextLocation == TextLocRight ) + { + autoExtent.x += strWidth + mTextMargin * 2; + } + else // Top, Bottom, Center + { + strWidth += mTextMargin * 2; + if ( strWidth > autoExtent.x ) + autoExtent.x = strWidth; + } + } + + return Parent::resize( newPosition, autoExtent ); +} + +void GuiIconButtonCtrl::setBitmap(const char *name) +{ + mBitmapName = StringTable->insert(name); + if(!isAwake()) + return; + + if (*mBitmapName) + { + mTextureNormal = GFXTexHandle( name, &GFXDefaultPersistentProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__) ); + } + else + { + mTextureNormal = NULL; + } + + // So that extent is recalculated if autoSize is set. + resize( getPosition(), getExtent() ); + + setUpdate(); +} + +void GuiIconButtonCtrl::onRender(Point2I offset, const RectI& updateRect) +{ + renderButton( offset, updateRect); +} + +void GuiIconButtonCtrl::renderButton( Point2I &offset, const RectI& updateRect ) +{ + bool highlight = mMouseOver; + bool depressed = mDepressed; + + ColorI fontColor = mActive ? (highlight ? mProfile->mFontColor : mProfile->mFontColor) : mProfile->mFontColorNA; + + RectI boundsRect(offset, getExtent()); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + if (mDepressed || mStateOn) + { + // If there is a bitmap array then render using it. + // Otherwise use a standard fill. + if(mProfile->mUseBitmapArray && mProfile->mBitmapArrayRects.size()) + renderBitmapArray(boundsRect, statePressed); + else + renderSlightlyLoweredBox(boundsRect, mProfile); + } + else if(mMouseOver && mActive) + { + // If there is a bitmap array then render using it. + // Otherwise use a standard fill. + if(mProfile->mUseBitmapArray && mProfile->mBitmapArrayRects.size()) + renderBitmapArray(boundsRect, stateMouseOver); + else + renderSlightlyRaisedBox(boundsRect, mProfile); + } + else + { + // If there is a bitmap array then render using it. + // Otherwise use a standard fill. + if(mProfile->mUseBitmapArray && mProfile->mBitmapArrayRects.size()) + { + if(mActive) + renderBitmapArray(boundsRect, stateNormal); + else + renderBitmapArray(boundsRect, stateDisabled); + } + else + { + drawer->drawRectFill(boundsRect, mProfile->mFillColorNA); + drawer->drawRect(boundsRect, mProfile->mBorderColorNA); + } + } + + Point2I textPos = offset; + if(depressed) + textPos += Point2I(1,1); + + RectI iconRect( 0, 0, 0, 0 ); + + // Render the icon + if ( mTextureNormal && mIconLocation != GuiIconButtonCtrl::IconLocNone ) + { + // Render the normal bitmap + drawer->clearBitmapModulation(); + + // Maintain the bitmap size or fill the button? + if ( !mFitBitmapToButton ) + { + Point2I textureSize( mTextureNormal->getWidth(), mTextureNormal->getHeight() ); + iconRect.set( offset + mButtonMargin, textureSize ); + + if ( mIconLocation == IconLocRight ) + { + iconRect.point.x = ( offset.x + getWidth() ) - ( mButtonMargin.x + textureSize.x ); + iconRect.point.y = offset.y + ( getHeight() - textureSize.y ) / 2; + } + else if ( mIconLocation == IconLocLeft ) + { + iconRect.point.x = offset.x + mButtonMargin.x; + iconRect.point.y = offset.y + ( getHeight() - textureSize.y ) / 2; + } + else if ( mIconLocation == IconLocCenter ) + { + iconRect.point.x = offset.x + ( getWidth() - textureSize.x ) / 2; + iconRect.point.y = offset.y + ( getHeight() - textureSize.y ) / 2; + } + + drawer->drawBitmapStretch( mTextureNormal, iconRect ); + + } + else + { + iconRect.set( offset + mButtonMargin, getExtent() - (mButtonMargin * 2) ); + + if ( mMakeIconSquare ) + { + // Square the icon to the smaller axis extent. + if ( iconRect.extent.x < iconRect.extent.y ) + iconRect.extent.y = iconRect.extent.x; + else + iconRect.extent.x = iconRect.extent.y; + } + + drawer->drawBitmapStretch( mTextureNormal, iconRect ); + } + } + + // Render text + if ( mTextLocation != TextLocNone ) + { + // Clip text to fit (appends ...), + // pad some space to keep it off our border + String text( mButtonText ); + S32 textWidth = clipText( text, getWidth() - 4 - mTextMargin ); + + drawer->setBitmapModulation( fontColor ); + + if ( mTextLocation == TextLocRight ) + { + Point2I start( mTextMargin, ( getHeight() - mProfile->mFont->getHeight() ) / 2 ); + if ( mTextureNormal && mIconLocation != IconLocNone ) + { + start.x = iconRect.extent.x + mButtonMargin.x + mTextMargin; + } + + drawer->drawText( mProfile->mFont, start + offset, text, mProfile->mFontColors ); + } + + if ( mTextLocation == TextLocLeft ) + { + Point2I start( mTextMargin, ( getHeight() - mProfile->mFont->getHeight() ) / 2 ); + + drawer->drawText( mProfile->mFont, start + offset, text, mProfile->mFontColors ); + } + + if ( mTextLocation == TextLocCenter ) + { + Point2I start; + if ( mTextureNormal && mIconLocation == IconLocLeft ) + { + start.set( ( getWidth() - textWidth - iconRect.extent.x ) / 2 + iconRect.extent.x, + ( getHeight() - mProfile->mFont->getHeight() ) / 2 ); + } + else + start.set( ( getWidth() - textWidth ) / 2, ( getHeight() - mProfile->mFont->getHeight() ) / 2 ); + drawer->setBitmapModulation( fontColor ); + drawer->drawText( mProfile->mFont, start + offset, text, mProfile->mFontColors ); + } + + if ( mTextLocation == TextLocBottom ) + { + Point2I start; + start.set( ( getWidth() - textWidth ) / 2, getHeight() - mProfile->mFont->getHeight() - mTextMargin ); + + // If the text is longer then the box size + // it will get clipped, force Left Justify + if( textWidth > getWidth() ) + start.x = 0; + + drawer->setBitmapModulation( fontColor ); + drawer->drawText( mProfile->mFont, start + offset, text, mProfile->mFontColors ); + } + } + + renderChildControls( offset, updateRect); +} + +// Draw the bitmap array's borders according to the button's state. +void GuiIconButtonCtrl::renderBitmapArray(RectI &bounds, S32 state) +{ + switch(state) + { + case stateNormal: + if(mProfile->mBorder == -2) + renderSizableBitmapBordersFilled(bounds, 1, mProfile); + else + renderFixedBitmapBordersFilled(bounds, 1, mProfile); + break; + + case stateMouseOver: + if(mProfile->mBorder == -2) + renderSizableBitmapBordersFilled(bounds, 2, mProfile); + else + renderFixedBitmapBordersFilled(bounds, 2, mProfile); + break; + + case statePressed: + if(mProfile->mBorder == -2) + renderSizableBitmapBordersFilled(bounds, 3, mProfile); + else + renderFixedBitmapBordersFilled(bounds, 3, mProfile); + break; + + case stateDisabled: + if(mProfile->mBorder == -2) + renderSizableBitmapBordersFilled(bounds, 4, mProfile); + else + renderFixedBitmapBordersFilled(bounds, 4, mProfile); + break; + } +} + +ConsoleMethod( GuiIconButtonCtrl, setBitmap, void, 3, 3, "(filepath name)") +{ + char* argBuffer = Con::getArgBuffer( 512 ); + Platform::makeFullPathName( argv[2], argBuffer, 512 ); + object->setBitmap( argBuffer ); +} \ No newline at end of file diff --git a/gui/buttons/guiIconButtonCtrl.h b/gui/buttons/guiIconButtonCtrl.h new file mode 100644 index 0000000..57f27d3 --- /dev/null +++ b/gui/buttons/guiIconButtonCtrl.h @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIICONBUTTON_H_ +#define _GUIICONBUTTON_H_ + +#ifndef _GUIBUTTONCTRL_H_ +#include "gui/buttons/guiButtonCtrl.h" +#endif +#ifndef GFX_Texture_Manager_H_ +#include "gfx/gfxTextureManager.h" +#endif + + +/// The GuiIconButtonCtrl draws an icon and text caption within a normal +/// button control with several layout options. +/// +class GuiIconButtonCtrl : public GuiButtonCtrl +{ +private: + typedef GuiButtonCtrl Parent; + +protected: + + StringTableEntry mBitmapName; + GFXTexHandle mTextureNormal; + S32 mIconLocation; + S32 mTextLocation; + S32 mTextMargin; + Point2I mButtonMargin; + + /// Make the bitmap fill the button extent. + bool mFitBitmapToButton; + + /// Keep a square aspect ration on the icon. + bool mMakeIconSquare; + + /// Calculate extent based on icon size, text width, and layout options. + bool mAutoSize; + + // Optional bitmap to be displayed when the proper bitmap cannot be found + StringTableEntry mErrorBitmapName; + GFXTexHandle mErrorTextureHandle; + + void renderButton( Point2I &offset, const RectI& updateRect); + + enum + { + stateNormal, + stateMouseOver, + statePressed, + stateDisabled, + }; + + void renderBitmapArray(RectI &bounds, S32 state); + +public: + enum { + TextLocNone, + TextLocBottom, + TextLocRight, + TextLocTop, + TextLocLeft, + TextLocCenter, + }; + + enum { + IconLocNone, + IconLocLeft, + IconLocRight, + IconLocCenter + }; + + + DECLARE_CONOBJECT(GuiIconButtonCtrl); + DECLARE_DESCRIPTION( "A button control that displays an icon on the button in addition\n" + "to the optional text label." ); + + GuiIconButtonCtrl(); + + static void initPersistFields(); + + //Parent methods + bool onWake(); + void onSleep(); + void inspectPostApply(); + void onStaticModified(const char* slotName, const char* newValue = NULL); + bool resize(const Point2I &newPosition, const Point2I &newExtent); + + void setBitmap(const char *name); + + // Used to set the optional error bitmap + void setErrorBitmap(const char *name); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif //_GUIICONBUTTON_H_ diff --git a/gui/buttons/guiRadioCtrl.cpp b/gui/buttons/guiRadioCtrl.cpp new file mode 100644 index 0000000..b1f4e91 --- /dev/null +++ b/gui/buttons/guiRadioCtrl.cpp @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiRadioCtrl.h" +#include "console/consoleTypes.h" + +//--------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiRadioCtrl); + +GuiRadioCtrl::GuiRadioCtrl() +{ + mButtonType = ButtonTypeRadio; +} diff --git a/gui/buttons/guiRadioCtrl.h b/gui/buttons/guiRadioCtrl.h new file mode 100644 index 0000000..0b17bc4 --- /dev/null +++ b/gui/buttons/guiRadioCtrl.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIRADIOCTRL_H_ +#define _GUIRADIOCTRL_H_ + +#ifndef _GUICHECKBOXCTRLL_H_ +#include "gui/buttons/guiCheckBoxCtrl.h" +#endif + +// the radio button renders exactly the same as the check box +// the only difference is it sends messages to its siblings to +// turn themselves off. + +class GuiRadioCtrl : public GuiCheckBoxCtrl +{ + typedef GuiCheckBoxCtrl Parent; + +public: + DECLARE_CONOBJECT(GuiRadioCtrl); + DECLARE_DESCRIPTION( "A button control with a radio box and a text label.\n" + "This control is used in groups where multiple radio buttons\n" + "present a range of options out of which one can be chosen.\n" + "A radio button automatically signals its siblings when it is\n" + "toggled on." ); + GuiRadioCtrl(); +}; + +#endif //_GUI_RADIO_CTRL_H diff --git a/gui/buttons/guiSwatchButtonCtrl.cpp b/gui/buttons/guiSwatchButtonCtrl.cpp new file mode 100644 index 0000000..997ba75 --- /dev/null +++ b/gui/buttons/guiSwatchButtonCtrl.cpp @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/buttons/guiSwatchButtonCtrl.h" + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiSwatchButtonCtrl.h" +#include "gui/core/guiDefaultControlRender.h" + + +GuiSwatchButtonCtrl::GuiSwatchButtonCtrl() + : mSwatchColor( 1, 1, 1, 1 ) +{ + mButtonText = StringTable->insert( "" ); + setExtent(140, 30); +} + +IMPLEMENT_CONOBJECT( GuiSwatchButtonCtrl ); + + +void GuiSwatchButtonCtrl::initPersistFields() +{ + addField( "color", TypeColorF, Offset( mSwatchColor, GuiSwatchButtonCtrl), "Foreground color" ); + Parent::initPersistFields(); +} + +bool GuiSwatchButtonCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + if ( mGrid.isNull() ) + mGrid.set( "core/art/gui/images/transp_grid", &GFXDefaultGUIProfile, avar("%s() - mGrid (line %d)", __FUNCTION__, __LINE__) ); + + return true; +} + +void GuiSwatchButtonCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + bool highlight = mMouseOver; + //bool depressed = mDepressed; + + //ColorI fontColor = mActive ? (highlight ? mProfile->mFontColorHL : mProfile->mFontColor) : mProfile->mFontColorNA; + ColorI backColor = mSwatchColor; + ColorI borderColor = mActive ? ( highlight ? mProfile->mBorderColorHL : mProfile->mBorderColor ) : mProfile->mBorderColorNA; + + RectI renderRect( offset, getExtent() ); + if ( !highlight ) + renderRect.inset( 1, 1 ); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->clearBitmapModulation(); + + // Draw background transparency grid texture... + if ( mGrid.isValid() ) + drawer->drawBitmapStretch( mGrid, renderRect ); + + // Draw swatch color as fill... + drawer->drawRectFill( renderRect, mSwatchColor ); + + // Draw any borders... + drawer->drawRect( renderRect, borderColor ); + //renderBorder( boundsRect, mProfile ); + + // Draw any text... +// Point2I textPos = offset + mProfile->mTextOffset; +// if ( depressed ) +// textPos += Point2I(1,1); + +// drawer->setBitmapModulation( fontColor ); +// renderJustifiedText(textPos, getExtent(), mButtonText); +// drawer->clearBitmapModulation(); + + // render the children... + //renderChildControls( offset, updateRect); +} + diff --git a/gui/buttons/guiSwatchButtonCtrl.h b/gui/buttons/guiSwatchButtonCtrl.h new file mode 100644 index 0000000..410d2fa --- /dev/null +++ b/gui/buttons/guiSwatchButtonCtrl.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISWATCHBUTTONCTRL_H_ +#define _GUISWATCHBUTTONCTRL_H_ + +#ifndef _GUIBUTTONBASECTRL_H_ +#include "gui/buttons/guiButtonBaseCtrl.h" +#endif + +class GuiSwatchButtonCtrl : public GuiButtonBaseCtrl +{ + typedef GuiButtonBaseCtrl Parent; + +public: + + GuiSwatchButtonCtrl(); + + DECLARE_CONOBJECT( GuiSwatchButtonCtrl ); + + // ConsoleObject... + static void initPersistFields(); + + // GuiControl... + bool onWake(); + void onRender(Point2I offset, const RectI &updateRect); + + // GuiSwatchButtonCtrl... + void setColor( const ColorF &color ) { mSwatchColor = color; } + +protected: + + ColorF mSwatchColor; + GFXTexHandle mGrid; +}; + +#endif // _GUISWATCHBUTTONCTRL_H_ diff --git a/gui/buttons/guiToggleButtonCtrl.cpp b/gui/buttons/guiToggleButtonCtrl.cpp new file mode 100644 index 0000000..5d741f3 --- /dev/null +++ b/gui/buttons/guiToggleButtonCtrl.cpp @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/buttons/guiToggleButtonCtrl.h" + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" + + +IMPLEMENT_CONOBJECT(GuiToggleButtonCtrl); + +GuiToggleButtonCtrl::GuiToggleButtonCtrl() +{ + setExtent(140, 30); + mButtonText = StringTable->insert(""); + mStateOn = false; + mButtonType = ButtonTypeCheck; +} + +//-------------------------------------------------------------------------- + +void GuiToggleButtonCtrl::onPreRender() +{ + Parent::onPreRender(); + + // If we have a script variable, make sure we're in sync + if ( mConsoleVariable[0] ) + mStateOn = Con::getBoolVariable( mConsoleVariable ); +} + +void GuiToggleButtonCtrl::onRender(Point2I offset, + const RectI& updateRect) +{ + bool highlight = mMouseOver; + bool depressed = mDepressed; + + ColorI fontColor = mActive ? (highlight ? mProfile->mFontColorHL : mProfile->mFontColor) : mProfile->mFontColorNA; + ColorI backColor = mActive ? mProfile->mFillColor : mProfile->mFillColorNA; + ColorI borderColor = mActive ? mProfile->mBorderColor : mProfile->mBorderColorNA; + + RectI boundsRect(offset, getExtent()); + + if( mProfile->mBorder != 0 && !mHasTheme ) + { + if (mDepressed || mStateOn) + renderFilledBorder( boundsRect, mProfile->mBorderColorHL, mProfile->mFillColorHL ); + else + renderFilledBorder( boundsRect, mProfile->mBorderColor, mProfile->mFillColor ); + } + else if( mHasTheme ) + { + S32 indexMultiplier = 1; + if ( mMouseOver ) + indexMultiplier = 3; + else if ( mDepressed || mStateOn ) + indexMultiplier = 2; + else if ( !mActive ) + indexMultiplier = 4; + + renderSizableBitmapBordersFilled( boundsRect, indexMultiplier, mProfile ); + } + + Point2I textPos = offset; + if(depressed) + textPos += Point2I(1,1); + + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + renderJustifiedText(textPos, getExtent(), mButtonText); + + //render the children + renderChildControls( offset, updateRect); +} diff --git a/gui/buttons/guiToggleButtonCtrl.h b/gui/buttons/guiToggleButtonCtrl.h new file mode 100644 index 0000000..1ac4615 --- /dev/null +++ b/gui/buttons/guiToggleButtonCtrl.h @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITOGGLEBUTTONCTRL_H_ +#define _GUITOGGLEBUTTONCTRL_H_ + +#ifndef _GUIBUTTONCTRL_H_ +#include "gui/buttons/guiButtonCtrl.h" +#endif + +class GuiToggleButtonCtrl : public GuiButtonCtrl +{ + typedef GuiButtonCtrl Parent; +public: + DECLARE_CONOBJECT(GuiToggleButtonCtrl); + GuiToggleButtonCtrl(); + + virtual void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif //_GUITOGGLEBUTTONCTRL_H_ diff --git a/gui/buttons/guiToolboxButtonCtrl.cpp b/gui/buttons/guiToolboxButtonCtrl.cpp new file mode 100644 index 0000000..f0f005b --- /dev/null +++ b/gui/buttons/guiToolboxButtonCtrl.cpp @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/buttons/guiToolboxButtonCtrl.h" + +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" + + +IMPLEMENT_CONOBJECT(GuiToolboxButtonCtrl); + +//------------------------------------- +GuiToolboxButtonCtrl::GuiToolboxButtonCtrl() +{ + mNormalBitmapName = StringTable->insert(""); + mLoweredBitmapName = StringTable->insert("sceneeditor/client/images/buttondown"); + mHoverBitmapName = StringTable->insert("sceneeditor/client/images/buttonup"); + setMinExtent(Point2I(16,16)); + setExtent(48, 48); + mButtonType = ButtonTypeRadio; + mTipHoverTime = 100; + +} + + +//------------------------------------- +void GuiToolboxButtonCtrl::initPersistFields() +{ + addField("normalBitmap", TypeFilename, Offset(mNormalBitmapName, GuiToolboxButtonCtrl)); + addField("loweredBitmap", TypeFilename, Offset(mLoweredBitmapName, GuiToolboxButtonCtrl)); + addField("hoverBitmap", TypeFilename, Offset(mHoverBitmapName, GuiToolboxButtonCtrl)); + Parent::initPersistFields(); +} + + +//------------------------------------- +bool GuiToolboxButtonCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + setActive( true ); + + setNormalBitmap( mNormalBitmapName ); + setLoweredBitmap( mLoweredBitmapName ); + setHoverBitmap( mHoverBitmapName ); + + return true; +} + + +//------------------------------------- +void GuiToolboxButtonCtrl::onSleep() +{ + mTextureNormal = NULL; + mTextureLowered = NULL; + mTextureHover = NULL; + Parent::onSleep(); +} + + +//------------------------------------- + +ConsoleMethod( GuiToolboxButtonCtrl, setNormalBitmap, void, 3, 3, "( filepath name ) sets the bitmap that shows when the button is active") +{ + object->setNormalBitmap(argv[2]); +} + +ConsoleMethod( GuiToolboxButtonCtrl, setLoweredBitmap, void, 3, 3, "( filepath name ) sets the bitmap that shows when the button is disabled") +{ + object->setLoweredBitmap(argv[2]); +} + +ConsoleMethod( GuiToolboxButtonCtrl, setHoverBitmap, void, 3, 3, "( filepath name ) sets the bitmap that shows when the button is disabled") +{ + object->setHoverBitmap(argv[2]); +} + +//------------------------------------- +void GuiToolboxButtonCtrl::inspectPostApply() +{ + // if the extent is set to (0,0) in the gui editor and appy hit, this control will + // set it's extent to be exactly the size of the normal bitmap (if present) + Parent::inspectPostApply(); + + if ((getWidth() == 0) && (getHeight() == 0) && mTextureNormal) + { + setExtent( mTextureNormal->getWidth(), mTextureNormal->getHeight()); + } +} + + +//------------------------------------- +void GuiToolboxButtonCtrl::setNormalBitmap( StringTableEntry bitmapName ) +{ + mNormalBitmapName = StringTable->insert( bitmapName ); + + if(!isAwake()) + return; + + if ( *mNormalBitmapName ) + mTextureNormal = GFXTexHandle( mNormalBitmapName, &GFXDefaultPersistentProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__) ); + else + mTextureNormal = NULL; + + setUpdate(); +} + +void GuiToolboxButtonCtrl::setLoweredBitmap( StringTableEntry bitmapName ) +{ + mLoweredBitmapName = StringTable->insert( bitmapName ); + + if(!isAwake()) + return; + + if ( *mLoweredBitmapName ) + mTextureLowered = GFXTexHandle( mLoweredBitmapName, &GFXDefaultPersistentProfile, avar("%s() - mTextureLowered (line %d)", __FUNCTION__, __LINE__) ); + else + mTextureLowered = NULL; + + setUpdate(); +} + +void GuiToolboxButtonCtrl::setHoverBitmap( StringTableEntry bitmapName ) +{ + mHoverBitmapName = StringTable->insert( bitmapName ); + + if(!isAwake()) + return; + + if ( *mHoverBitmapName ) + mTextureHover = GFXTexHandle( mHoverBitmapName, &GFXDefaultPersistentProfile, avar("%s() - mTextureHover (line %d)", __FUNCTION__, __LINE__) ); + else + mTextureHover = NULL; + + setUpdate(); +} + + + +//------------------------------------- +void GuiToolboxButtonCtrl::onRender(Point2I offset, const RectI& updateRect) +{ + // Only render the state rect (hover/down) if we're active + if (mActive) + { + RectI r(offset, getExtent()); + if ( mDepressed || mStateOn ) + renderStateRect( mTextureLowered , r ); + else if (mMouseOver) + renderStateRect( mTextureHover , r ); + } + + // Now render the image + if( mTextureNormal ) + { + renderButton( mTextureNormal, offset, updateRect ); + return; + } + + Point2I textPos = offset; + if( mDepressed ) + textPos += Point2I(1,1); + + // Make sure we take the profile's textOffset into account. + textPos += mProfile->mTextOffset; + + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor ); + renderJustifiedText(textPos, getExtent(), mButtonText); + +} + +void GuiToolboxButtonCtrl::renderStateRect( GFXTexHandle &texture, const RectI& rect ) +{ + if (texture) + { + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( texture, rect ); + } +} + +//------------------------------------------------------------------------------ + +void GuiToolboxButtonCtrl::renderButton(GFXTexHandle &texture, Point2I &offset, const RectI& updateRect) +{ + if (texture) + { + Point2I finalOffset = offset; + + finalOffset.x += ( ( getWidth() / 2 ) - ( texture.getWidth() / 2 ) ); + finalOffset.y += ( ( getHeight() / 2 ) - ( texture.getHeight() / 2 ) ); + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmap(texture, finalOffset); + renderChildControls( offset, updateRect); + } +} \ No newline at end of file diff --git a/gui/buttons/guiToolboxButtonCtrl.h b/gui/buttons/guiToolboxButtonCtrl.h new file mode 100644 index 0000000..191286d --- /dev/null +++ b/gui/buttons/guiToolboxButtonCtrl.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITOOLBOXBUTTON_H_ +#define _GUITOOLBOXBUTTON_H_ + +#ifndef _GUIBUTTONCTRL_H_ +#include "gui/buttons/guiButtonCtrl.h" +#endif +#ifndef GFX_Texture_Manager_H_ +#include "gfx/gfxTextureManager.h" +#endif + +class GuiToolboxButtonCtrl : public GuiButtonCtrl +{ +private: + typedef GuiButtonCtrl Parent; + +protected: + StringTableEntry mNormalBitmapName; + StringTableEntry mLoweredBitmapName; + StringTableEntry mHoverBitmapName; + + GFXTexHandle mTextureNormal; + GFXTexHandle mTextureLowered; + GFXTexHandle mTextureHover; + + void renderButton(GFXTexHandle &texture, Point2I &offset, const RectI& updateRect); + void renderStateRect( GFXTexHandle &texture, const RectI& rect ); + +public: + DECLARE_CONOBJECT(GuiToolboxButtonCtrl); + GuiToolboxButtonCtrl(); + + static void initPersistFields(); + + //Parent methods + bool onWake(); + void onSleep(); + void inspectPostApply(); + + void setNormalBitmap( StringTableEntry bitmapName ); + void setLoweredBitmap( StringTableEntry bitmapName ); + void setHoverBitmap( StringTableEntry bitmapName ); + + + void onRender(Point2I offset, const RectI &updateRect); +}; + + +#endif //_GUITOOLBOXBUTTON_H_ diff --git a/gui/containers/guiAutoScrollCtrl.cpp b/gui/containers/guiAutoScrollCtrl.cpp new file mode 100644 index 0000000..614dcf0 --- /dev/null +++ b/gui/containers/guiAutoScrollCtrl.cpp @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/containers/guiAutoScrollCtrl.h" +#include "console/consoleTypes.h" + +//----------------------------------------------------------------------------- +// GuiAutoScrollCtrl +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiAutoScrollCtrl); + +GuiAutoScrollCtrl::GuiAutoScrollCtrl() +{ + mScrolling = false; + mCurrentTime = 0.0f; + mStartDelay = 3.0f; + mResetDelay = 5.0f; + mChildBorder = 10; + mScrollSpeed = 1.0f; + mTickCallback = false; + mIsContainer = true; + + // Make sure we receive our ticks. + setProcessTicks(); +} + +GuiAutoScrollCtrl::~GuiAutoScrollCtrl() +{ +} + +//----------------------------------------------------------------------------- +// Persistence +//----------------------------------------------------------------------------- +void GuiAutoScrollCtrl::initPersistFields() +{ + addField("startDelay", TypeF32, Offset(mStartDelay, GuiAutoScrollCtrl)); + addField("resetDelay", TypeF32, Offset(mResetDelay, GuiAutoScrollCtrl)); + addField("childBorder", TypeS32, Offset(mChildBorder, GuiAutoScrollCtrl)); + addField("scrollSpeed", TypeF32, Offset(mScrollSpeed, GuiAutoScrollCtrl)); + addField("tickCallback", TypeBool, Offset(mTickCallback, GuiAutoScrollCtrl)); + + Parent::initPersistFields(); +} + +void GuiAutoScrollCtrl::onChildAdded(GuiControl* control) +{ + resetChild(control); +} + +void GuiAutoScrollCtrl::onChildRemoved(GuiControl* control) +{ + mScrolling = false; +} + +void GuiAutoScrollCtrl::resetChild(GuiControl* control) +{ + Point2I extent = control->getExtent(); + + control->setPosition(Point2I(mChildBorder, mChildBorder)); + control->setExtent(Point2I(getExtent().x - (mChildBorder * 2), extent.y)); + + mControlPositionY = F32(control->getPosition().y); + + if ((mControlPositionY + extent.y) > getExtent().y) + mScrolling = true; + else + mScrolling = false; + + mCurrentTime = 0.0f; +} + +bool GuiAutoScrollCtrl::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + if( !Parent::resize( newPosition, newExtent ) ) + return false; + + for (iterator i = begin(); i != end(); i++) + { + GuiControl* control = static_cast(*i); + if (control) + resetChild(control); + } + + return true; +} + +void GuiAutoScrollCtrl::childResized(GuiControl *child) +{ + Parent::childResized( child ); + + resetChild(child); +} + +void GuiAutoScrollCtrl::processTick() +{ + if (mTickCallback && isMethod("onTick") ) + Con::executef(this, "onTick"); +} + +void GuiAutoScrollCtrl::advanceTime(F32 timeDelta) +{ + if (!mScrolling) + return; + + if ((mCurrentTime + timeDelta) < mStartDelay) + { + mCurrentTime += timeDelta; + return; + } + + GuiControl* control = static_cast(at(0)); + if (!control) + return; + + Point2I oldPosition = control->getPosition(); + if ((oldPosition.y + control->getExtent().y) < (getExtent().y - mChildBorder)) + { + mCurrentTime += timeDelta; + if (mCurrentTime > (mStartDelay + mResetDelay)) + { + resetChild(control); + return; + } + } + + else + { + mControlPositionY -= mScrollSpeed * timeDelta; + control->setPosition(Point2I(oldPosition.x, (S32)mControlPositionY)); + } +} diff --git a/gui/containers/guiAutoScrollCtrl.h b/gui/containers/guiAutoScrollCtrl.h new file mode 100644 index 0000000..a3c9363 --- /dev/null +++ b/gui/containers/guiAutoScrollCtrl.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUIAUTOSCROLLCTRL_H_ +#define _GUIAUTOSCROLLCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#ifndef _GUITICKCTRL_H_ +#include "gui/shiny/guiTickCtrl.h" +#endif + +class GuiAutoScrollCtrl : public GuiTickCtrl +{ +private: + typedef GuiTickCtrl Parent; + bool mScrolling; + F32 mCurrentTime; + F32 mStartDelay; + F32 mResetDelay; + S32 mChildBorder; + F32 mScrollSpeed; + bool mTickCallback; + + F32 mControlPositionY; + + void resetChild(GuiControl* control); + +public: + // Constructor/Destructor/Conobject Declaration + GuiAutoScrollCtrl(); + ~GuiAutoScrollCtrl(); + DECLARE_CONOBJECT(GuiAutoScrollCtrl); + DECLARE_CATEGORY( "Gui Containers" ); + + // Persistence + static void initPersistFields(); + + // Control Events + virtual void onChildAdded(GuiControl* control); + virtual void onChildRemoved(GuiControl* control); + virtual bool resize(const Point2I& newPosition, const Point2I& newExtent); + virtual void childResized(GuiControl *child); + + virtual void processTick(); + virtual void advanceTime(F32 timeDelta); +}; +#endif \ No newline at end of file diff --git a/gui/containers/guiContainer.cpp b/gui/containers/guiContainer.cpp new file mode 100644 index 0000000..f0c8ce4 --- /dev/null +++ b/gui/containers/guiContainer.cpp @@ -0,0 +1,384 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiContainer.h" + +#include "gui/containers/guiPanel.h" +#include "console/consoleTypes.h" + +//----------------------------------------------------------------------------- +// GuiContainer +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT( GuiContainer ); + +static const EnumTable::Enums dockEnums[] = +{ + { Docking::dockNone, "None" }, + { Docking::dockClient, "Client" }, + { Docking::dockTop, "Top" }, + { Docking::dockBottom, "Bottom" }, + { Docking::dockLeft, "Left" }, + { Docking::dockRight, "Right" } +}; +static const EnumTable gDockingTable(6, &dockEnums[0]); + +GuiContainer::GuiContainer() +{ + mUpdateLayout = false; + mValidDockingMask = Docking::dockNone | Docking::dockBottom | + Docking::dockTop | Docking::dockClient | + Docking::dockLeft | Docking::dockRight; + +} + +GuiContainer::~GuiContainer() +{ +} + +void GuiContainer::initPersistFields() +{ + Con::setIntVariable("$DOCKING_NONE", Docking::dockNone); + Con::setIntVariable("$DOCKING_CLIENT", Docking::dockClient); + Con::setIntVariable("$DOCKING_TOP", Docking::dockTop); + Con::setIntVariable("$DOCKING_BOTTOM", Docking::dockBottom); + Con::setIntVariable("$DOCKING_LEFT", Docking::dockLeft); + Con::setIntVariable("$DOCKING_RIGHT", Docking::dockRight); + + addProtectedField("Docking", TypeEnum, Offset(mSizingOptions.mDocking, GuiContainer), &setDockingField, &defaultProtectedGetFn, 1, &gDockingTable, ""); + addField("Margin", TypeRectSpacingI, Offset(mSizingOptions.mPadding, GuiContainer)); + addField("Padding", TypeRectSpacingI, Offset(mSizingOptions.mInternalPadding, GuiContainer)); + addField("AnchorTop", TypeBool, Offset(mSizingOptions.mAnchorTop, GuiContainer)); + addField("AnchorBottom", TypeBool, Offset(mSizingOptions.mAnchorBottom, GuiContainer)); + addField("AnchorLeft", TypeBool, Offset(mSizingOptions.mAnchorLeft, GuiContainer)); + addField("AnchorRight", TypeBool, Offset(mSizingOptions.mAnchorRight, GuiContainer)); + + Parent::initPersistFields(); +} + +void GuiContainer::onChildAdded(GuiControl* control) +{ + Parent::onChildAdded( control ); + setUpdateLayout(); +} + +void GuiContainer::onChildRemoved(GuiControl* control) +{ + Parent::onChildRemoved( control ); + setUpdateLayout(); +} + +bool GuiContainer::reOrder(SimObject* obj, SimObject* target) +{ + if ( !Parent::reOrder(obj, target) ) + return false; + + setUpdateLayout(); + return true; +} + + +bool GuiContainer::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + RectI oldBounds = getBounds(); + Point2I minExtent = getMinExtent(); + + if( !Parent::resize( newPosition, newExtent ) ) + return false; + + RectI clientRect = getClientRect(); + layoutControls( clientRect ); + + GuiControl *parent = getParent(); + S32 docking = getDocking(); + if( parent && docking != Docking::dockNone && docking != Docking::dockInvalid ) + setUpdateLayout( updateParent ); + + return true; +} + +void GuiContainer::addObject(SimObject *obj) +{ + Parent::addObject(obj); + + setUpdateLayout(); +} + +void GuiContainer::removeObject(SimObject *obj) +{ + Parent::removeObject(obj); + + setUpdateLayout(); +} + + +/// GuiContainer deals with parentResized calls differently than GuiControl. It will +/// update the layout for all of it's non-docked child controls. parentResized calls +/// on the child controls will be handled by their default functions, but for our +/// purposes we want at least our immediate children to use the anchors that they have +/// set on themselves. - JDD [9/20/2006] +void GuiContainer::parentResized(const RectI &oldParentRect, const RectI &newParentRect) +{ + //if(!mCanResize) + // return; + + // If it's a control that specifies invalid docking, we'll just treat it as an old GuiControl + if( getDocking() & Docking::dockInvalid || getDocking() & Docking::dockNone) + return Parent::parentResized( oldParentRect, newParentRect ); + + S32 deltaX = newParentRect.extent.x - oldParentRect.extent.x; + S32 deltaY = newParentRect.extent.y - oldParentRect.extent.y; + + // Update Self + RectI oldThisRect = getBounds(); + anchorControl( this, Point2I( deltaX, deltaY ) ); + RectI newThisRect = getBounds(); + + // Update Deltas to pass on to children + deltaX = newThisRect.extent.x - oldThisRect.extent.x; + deltaY = newThisRect.extent.y - oldThisRect.extent.y; + + // Iterate over all children and update their anchors + iterator nI = begin(); + for( ; nI != end(); nI++ ) + { + // Sanity + GuiControl *control = dynamic_cast( (*nI) ); + if( control ) + control->parentResized( oldThisRect, newThisRect ); + } +} + +void GuiContainer::childResized(GuiControl *child) +{ + Parent::childResized( child ); + setUpdateLayout(); +} + +bool GuiContainer::layoutControls( RectI &clientRect ) +{ + + // This variable is set to the first 'Client' docking + // control that is found. We defer client docking until + // after all other docks have been made since it will consume + // the remaining client area available. + GuiContainer *clientDocking = NULL; + + // Iterate over all children and perform docking + iterator nI = begin(); + for( ; nI != end(); nI++ ) + { + // Layout Content with proper docking (Client Default) + GuiControl *control = static_cast(*nI); + + // If we're invisible we don't get counted in docking + if( control == NULL || !control->isVisible() ) + continue; + + S32 dockingMode = Docking::dockNone; + GuiContainer *container = dynamic_cast(control); + if( container != NULL ) + dockingMode = container->getDocking(); + else + continue; + + // See above note about clientDocking pointer + if( dockingMode & Docking::dockClient && clientDocking == NULL ) + clientDocking = container; + + // Dock Appropriately + if( !(dockingMode & Docking::dockClient) ) + dockControl( container, dockingMode, clientRect ); + } + + // Do client dock + if( clientDocking != NULL ) + dockControl( clientDocking, Docking::dockClient, clientRect ); + + return true; +} +bool GuiContainer::dockControl( GuiContainer *control, S32 dockingMode, RectI &clientRect ) +{ + + if( !control ) + return false; + + // Make sure this class support docking of this type + if( !(dockingMode & getValidDockingMask())) + return false; + + // If our client rect has run out of room, we can't dock any more + if( !clientRect.isValidRect() ) + return false; + + // Dock Appropriately + RectI dockRect; + RectSpacingI rectShrinker; + ControlSizing sizingOptions = control->getSizingOptions(); + switch( dockingMode ) + { + case Docking::dockClient: + + // Inset by padding + sizingOptions.mPadding.insetRect(clientRect); + + // Dock to entirety of client rectangle + control->resize( clientRect.point, clientRect.extent ); + + // Remove Client Rect, can only have one client dock + clientRect.set(0,0,0,0); + break; + case Docking::dockTop: + + dockRect = clientRect; + dockRect.extent.y = getMin( control->getHeight() + sizingOptions.mPadding.top + sizingOptions.mPadding.bottom , clientRect.extent.y ); + + // Subtract our rect + clientRect.point.y += dockRect.extent.y; + clientRect.extent.y -= dockRect.extent.y; + + // Inset by padding + sizingOptions.mPadding.insetRect(dockRect); + + // Resize + control->resize( dockRect.point, dockRect.extent ); + + break; + case Docking::dockBottom: + + dockRect = clientRect; + dockRect.extent.y = getMin( control->getHeight() + sizingOptions.mPadding.top + sizingOptions.mPadding.bottom, clientRect.extent.y ); + dockRect.point.y += clientRect.extent.y - dockRect.extent.y; + + // Subtract our rect + clientRect.extent.y -= dockRect.extent.y; + + // Inset by padding + sizingOptions.mPadding.insetRect(dockRect); + + // Resize + control->resize( dockRect.point, dockRect.extent ); + + break; + case Docking::dockLeft: + + dockRect = clientRect; + dockRect.extent.x = getMin( control->getWidth() + sizingOptions.mPadding.left + sizingOptions.mPadding.right, clientRect.extent.x ); + + // Subtract our rect + clientRect.point.x += dockRect.extent.x; + clientRect.extent.x -= dockRect.extent.x; + + // Inset by padding + sizingOptions.mPadding.insetRect(dockRect); + + // Resize + control->resize( dockRect.point, dockRect.extent ); + + break; + case Docking::dockRight: + + dockRect = clientRect; + dockRect.extent.x = getMin( control->getWidth() + sizingOptions.mPadding.left + sizingOptions.mPadding.right, clientRect.extent.x ); + dockRect.point.x += clientRect.extent.x - dockRect.extent.x; + + // Subtract our rect + clientRect.extent.x -= dockRect.extent.x; + + // Inset by padding + sizingOptions.mPadding.insetRect(dockRect); + + // Resize + control->resize( dockRect.point, dockRect.extent ); + + break; + case Docking::dockNone: + control->setUpdateLayout(); + break; + } + + return true; +} + + +// Update a Controls Anchor based on a delta sizing of it's parents extent +// This function should return true if the control was changed in size or position at all +bool GuiContainer::anchorControl( GuiControl *control, const Point2I &deltaParentExtent ) +{ + GuiContainer *container = dynamic_cast( control ); + if( !control || !container ) + return false; + + // If we're docked, we don't anchor to anything + if( (container->getDocking() & Docking::dockAny) || !(container->getDocking() & Docking::dockInvalid) ) + return false; + + if( deltaParentExtent.isZero() ) + return false; + + RectI oldRect = control->getBounds(); + RectI newRect = control->getBounds(); + + F32 deltaBottom = mSizingOptions.mAnchorBottom ? (F32)deltaParentExtent.y : 0.0f; + F32 deltaRight = mSizingOptions.mAnchorRight ? (F32)deltaParentExtent.x : 0.0f; + F32 deltaLeft = mSizingOptions.mAnchorLeft ? 0.0f : (F32)deltaParentExtent.x; + F32 deltaTop = mSizingOptions.mAnchorTop ? 0.0f : (F32)deltaParentExtent.y; + + // Apply Delta's to newRect + newRect.point.x += (S32)deltaLeft; + newRect.extent.x += (S32)(deltaRight - deltaLeft); + newRect.point.y += (S32)deltaTop; + newRect.extent.y += (S32)(deltaBottom - deltaTop); + + Point2I minExtent = control->getMinExtent(); + // Only resize if our minExtent is satisfied with it. + if( !( newRect.extent.x >= control->getMinExtent().x && newRect.extent.y >= control->getMinExtent().y ) ) + return false; + + if( newRect.point == oldRect.point && newRect.extent == oldRect.extent ) + return false; + + // Finally Size the control + control->resize( newRect.point, newRect.extent ); + + // We made changes + return true; +} + +void GuiContainer::onPreRender() +{ + if( mUpdateLayout == updateNone ) + return; + + RectI clientRect = getClientRect(); + if( mUpdateLayout & updateSelf ) + layoutControls( clientRect ); + + GuiContainer *parent = dynamic_cast( getParent() ); + if( parent && ( mUpdateLayout & updateParent ) ) + parent->setUpdateLayout(); + + // Always set AFTER layoutControls call to prevent recursive calling of layoutControls - JDD + mUpdateLayout = updateNone; + + Parent::onPreRender(); +} + +const RectI GuiContainer::getClientRect() +{ + RectI resRect = RectI( Point2I(0,0), getExtent() ); + + // Inset by padding + mSizingOptions.mInternalPadding.insetRect( resRect ); + + return resRect; +} + +void GuiContainer::setDocking( S32 docking ) +{ + mSizingOptions.mDocking = docking; + setUpdateLayout( updateParent ); +} diff --git a/gui/containers/guiContainer.h b/gui/containers/guiContainer.h new file mode 100644 index 0000000..eb4ea94 --- /dev/null +++ b/gui/containers/guiContainer.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUICONTAINER_H_ +#define _GUICONTAINER_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + + +/// @addtogroup gui_container_group Containers +/// +/// @ingroup gui_group Gui System +/// @{ +class GuiContainer : public GuiControl +{ + S32 mUpdateLayout; ///< Layout Update Mask + typedef GuiControl Parent; +protected: + + ControlSizing mSizingOptions; ///< Control Sizing Options + S32 mValidDockingMask; +public: + + + enum + { + updateSelf = BIT(1), + updateParent = BIT(2), + updateNone = 0 + + }; + + DECLARE_CONOBJECT(GuiContainer); + DECLARE_CATEGORY( "Gui Containers" ); + + GuiContainer(); + virtual ~GuiContainer(); + + static void initPersistFields(); + + /// @name Container Sizing + /// @{ + + /// Returns the Mask of valid docking modes supported by this container + inline S32 getValidDockingMask() { return mValidDockingMask; }; + + /// Docking Accessors + inline S32 getDocking() { return mSizingOptions.mDocking; }; + virtual void setDocking( S32 docking ); + + /// Docking Protected Field Setter + static bool setDockingField(void* obj, const char* data) + { + GuiContainer *pContainer = static_cast(obj); + pContainer->setUpdateLayout( updateParent ); + return true; + } + + inline bool getAnchorTop() const { return mSizingOptions.mAnchorTop; } + inline bool getAnchorBottom() const { return mSizingOptions.mAnchorBottom; } + inline bool getAnchorLeft() const { return mSizingOptions.mAnchorLeft; } + inline bool getAnchorRight() const { return mSizingOptions.mAnchorRight; } + inline void setAnchorTop(bool val) { mSizingOptions.mAnchorTop = val; } + inline void setAnchorBottom(bool val) { mSizingOptions.mAnchorBottom = val; } + inline void setAnchorLeft(bool val) { mSizingOptions.mAnchorLeft = val; } + inline void setAnchorRight(bool val) { mSizingOptions.mAnchorRight = val; } + + ControlSizing getSizingOptions() const { return mSizingOptions; } + void setSizingOptions(ControlSizing val) { mSizingOptions = val; } + + /// @} + + /// @name Sizing Constraints + /// @{ + virtual const RectI getClientRect(); + /// @} + + /// @name Control Layout Methods + /// @{ + + /// Called when the Layout for a Container needs to be updated because of a resize call or a call to setUpdateLayout + /// @param clientRect The Client Rectangle that is available for this Container to layout it's children in + virtual bool layoutControls( RectI &clientRect ); + + /// Set the layout flag to Dirty on a Container, triggering an update to it's layout on the next onPreRender call. + /// @attention This can be called without regard to whether the flag is already set, as setting it + /// does not actually cause an update, but rather tells the container it should update the next + /// chance it gets + /// @param updateType A Mask that indicates how the layout should be updated. + inline void setUpdateLayout( S32 updateType = updateSelf ) { mUpdateLayout |= updateType; }; + + /// @} + + /// @name Container Sizing Methods + /// @{ + + /// Dock a Control with the given docking mode inside the given client rect. + /// @attention The clientRect passed in will be modified by the docking of + /// the control. It will return the rect that remains after the docking operation. + virtual bool dockControl( GuiContainer *control, S32 dockingMode, RectI &clientRect ); + virtual bool anchorControl( GuiControl *control, const Point2I &deltaParentExtent ); + /// @} +public: + /// @name GuiControl Inherited + /// @{ + virtual void onChildAdded(GuiControl* control); + virtual void onChildRemoved(GuiControl* control); + virtual bool resize( const Point2I &newPosition, const Point2I &newExtent ); + virtual void parentResized(const RectI &oldParentRect, const RectI &newParentRect); + virtual void childResized(GuiControl *child); + virtual void addObject(SimObject *obj); + virtual void removeObject(SimObject *obj); + virtual bool reOrder(SimObject* obj, SimObject* target); + virtual void onPreRender(); + /// @} +}; +/// @} +#endif diff --git a/gui/containers/guiCtrlArrayCtrl.cpp b/gui/containers/guiCtrlArrayCtrl.cpp new file mode 100644 index 0000000..fe1e4e7 --- /dev/null +++ b/gui/containers/guiCtrlArrayCtrl.cpp @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/containers/guiCtrlArrayCtrl.h" + +IMPLEMENT_CONOBJECT(GuiControlArrayControl); + +GuiControlArrayControl::GuiControlArrayControl() +{ + mResizing = false; + + mCols = 0; + mRowSize = 30; + mRowSpacing = 2; + mColSpacing = 0; + mIsContainer = true; +} + +void GuiControlArrayControl::initPersistFields() +{ + addField("colCount", TypeS32, Offset(mCols, GuiControlArrayControl)); + addField("colSizes", TypeS32Vector, Offset(mColumnSizes, GuiControlArrayControl)); + addField("rowSize", TypeS32, Offset(mRowSize, GuiControlArrayControl)); + addField("rowSpacing", TypeS32, Offset(mRowSpacing, GuiControlArrayControl)); + addField("colSpacing", TypeS32, Offset(mColSpacing, GuiControlArrayControl)); + + Parent::initPersistFields(); +} + +bool GuiControlArrayControl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + return true; +} + +void GuiControlArrayControl::onSleep() +{ + Parent::onSleep(); +} + +void GuiControlArrayControl::inspectPostApply() +{ + resize(getPosition(), getExtent()); +} + +bool GuiControlArrayControl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if( !Parent::resize(newPosition, newExtent) ) + return false; + + return updateArray(); +} + +void GuiControlArrayControl::addObject(SimObject *obj) +{ + Parent::addObject(obj); + + updateArray(); +} + +void GuiControlArrayControl::removeObject(SimObject *obj) +{ + Parent::removeObject(obj); + + updateArray(); +} + +bool GuiControlArrayControl::reOrder(SimObject* obj, SimObject* target) +{ + bool ret = Parent::reOrder(obj, target); + if (ret) + updateArray(); + + return ret; +} + +bool GuiControlArrayControl::updateArray() +{ + // Prevent recursion + if(mResizing) + return false; + + // Set Resizing. + mResizing = true; + + if(mCols < 1 || size() < 1) + { + mResizing = false; + return false; + } + + S32 *sizes = new S32[mCols]; + S32 *offsets = new S32[mCols]; + S32 totalSize = 0; + Point2I extent = getExtent(); + + // Calculate the column sizes + for(S32 i=0; i(operator[](i)); + + // Get the current column and row... + S32 curCol = i % mCols; + S32 curRow = i / mCols; + + if(gc) + { + Point2I newPos(offsets[curCol], curRow * (mRowSize + mRowSpacing)); + Point2I newExtents(sizes[curCol], mRowSize); + + gc->resize(newPos, newExtents); + } + } + + // Clear Sizing Flag. + mResizing = false; + + delete [] sizes; + delete [] offsets; + + return true; +} diff --git a/gui/containers/guiCtrlArrayCtrl.h b/gui/containers/guiCtrlArrayCtrl.h new file mode 100644 index 0000000..cf7b082 --- /dev/null +++ b/gui/containers/guiCtrlArrayCtrl.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICTRLARRAYCTRL_H_ +#define _GUICTRLARRAYCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#include "gfx/gfxDevice.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +class GuiControlArrayControl : public GuiControl +{ +private: + typedef GuiControl Parent; + + bool mResizing; + + S32 mCols; + Vector mColumnSizes; + S32 mRowSize; + S32 mRowSpacing; + S32 mColSpacing; + +public: + GuiControlArrayControl(); + + bool resize(const Point2I &newPosition, const Point2I &newExtent); + + bool onWake(); + void onSleep(); + void inspectPostApply(); + + bool updateArray(); + + void addObject(SimObject *obj); + void removeObject(SimObject *obj); + + bool reOrder(SimObject* obj, SimObject* target = 0); + + static void initPersistFields(); + DECLARE_CONOBJECT(GuiControlArrayControl); + DECLARE_CATEGORY( "Gui Containers" ); +}; + +#endif \ No newline at end of file diff --git a/gui/containers/guiDragAndDropCtrl.cpp b/gui/containers/guiDragAndDropCtrl.cpp new file mode 100644 index 0000000..f34e722 --- /dev/null +++ b/gui/containers/guiDragAndDropCtrl.cpp @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/containers/guiDragAndDropCtrl.h" +#include "gui/core/guiCanvas.h" + +IMPLEMENT_CONOBJECT(GuiDragAndDropControl); + +void GuiDragAndDropControl::initPersistFields() +{ + addField("deleteOnMouseUp", TypeBool, Offset(mDeleteOnMouseUp, GuiDragAndDropControl)); + Parent::initPersistFields(); +} + +ConsoleMethod(GuiDragAndDropControl, startDragging, void, 2, 4, "startDragging(%offset)") +{ + Point2I offset = Point2I(0, 0); + if (argc > 3) + { + offset.x = dAtoi(argv[2]); + offset.y = dAtoi(argv[3]); + } + object->startDragging(offset); +} + +void GuiDragAndDropControl::startDragging(Point2I offset) +{ + GuiCanvas* canvas = getRoot(); + AssertFatal(canvas, "DragAndDropControl wasn't added to the gui before the drag started."); + if (canvas->getMouseLockedControl()) + { + GuiEvent event; + canvas->getMouseLockedControl()->onMouseLeave(event); + canvas->mouseUnlock(canvas->getMouseLockedControl()); + } + canvas->mouseLock(this); + canvas->setFirstResponder(this); + mOffset = offset; + mLastTarget=NULL; +} + +void GuiDragAndDropControl::onMouseDown(const GuiEvent& event) +{ + startDragging(event.mousePoint - getPosition()); +} + +void GuiDragAndDropControl::onMouseDragged(const GuiEvent& event) +{ + setPosition(event.mousePoint - mOffset); + + // Allow the control under the drag to react to a potential drop + GuiControl* enterTarget = findDragTarget(event.mousePoint, "onControlDragEnter"); + if(mLastTarget != enterTarget) + { + sendDragEvent(mLastTarget, "onControlDragExit"); + sendDragEvent(enterTarget, "onControlDragEnter"); + mLastTarget = enterTarget; + } + + GuiControl* dragTarget = findDragTarget(event.mousePoint, "onControlDragged"); + sendDragEvent(dragTarget, "onControlDragged"); +} + +void GuiDragAndDropControl::onMouseUp(const GuiEvent& event) +{ + mouseUnlock(); + + GuiControl* target = findDragTarget(event.mousePoint, "onControlDropped"); + sendDragEvent(target, "onControlDropped"); + + if (mDeleteOnMouseUp) + deleteObject(); +} + +void GuiDragAndDropControl::sendDragEvent(GuiControl* target, const char* event) +{ + if(!target || !target->isMethod(event)) + return; + + char position[32]; + Point2I dropPoint = getPosition() + (getExtent() / 2); + dSprintf(position, 32, "%d %d", dropPoint.x, dropPoint.y); + Con::executef(target, event, Con::getIntArg(at(0)->getId()), position); +} + +GuiControl* GuiDragAndDropControl::findDragTarget(Point2I mousePoint, const char* method) +{ + // If there are any children and we have a parent. + GuiControl* parent = getParent(); + if (size() && parent) + { + mVisible = false; + GuiControl* dropControl = parent->findHitControl(mousePoint); + mVisible = true; + while( dropControl ) + { + if (dropControl->isMethod(method)) + return dropControl; + else + dropControl = dropControl->getParent(); + } + } + return NULL; +} diff --git a/gui/containers/guiDragAndDropCtrl.h b/gui/containers/guiDragAndDropCtrl.h new file mode 100644 index 0000000..1fd42c2 --- /dev/null +++ b/gui/containers/guiDragAndDropCtrl.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIDRAGANDDROPCTRL_H_ +#define _GUIDRAGANDDROPCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#include "gfx/gfxDevice.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +class GuiDragAndDropControl : public GuiControl +{ +private: + typedef GuiControl Parent; + + // The mouse down offset from the upper left of the control. + Point2I mOffset; + bool mDeleteOnMouseUp; + + // Controls may want to react when they are dragged over, entered or exited. + GuiControl* mLastTarget; + + // Convenience methods for sending events + void sendDragEvent(GuiControl* target, const char* event); + GuiControl* findDragTarget(Point2I mousePoint, const char* method); + +public: + GuiDragAndDropControl() { } + + void startDragging(Point2I offset = Point2I(0, 0)); + + virtual void onMouseDown(const GuiEvent& event); + virtual void onMouseDragged(const GuiEvent& event); + virtual void onMouseUp(const GuiEvent& event); + + static void initPersistFields(); + + DECLARE_CONOBJECT( GuiDragAndDropControl ); + DECLARE_CATEGORY( "Gui Other" ); + DECLARE_DESCRIPTION( "A special control that implements drag&drop behavior.\n" + "The control will notify other controls as it moves across the canvas.\n" + "Content can be attached through dynamic fields or child objects." ); +}; + +#endif \ No newline at end of file diff --git a/gui/containers/guiDynamicCtrlArrayCtrl.cpp b/gui/containers/guiDynamicCtrlArrayCtrl.cpp new file mode 100644 index 0000000..08708ed --- /dev/null +++ b/gui/containers/guiDynamicCtrlArrayCtrl.cpp @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiDynamicCtrlArrayCtrl.h" + + +GuiDynamicCtrlArrayControl::GuiDynamicCtrlArrayControl() +{ + mCols = 0; + mColSize = 64; + mRows = 0; + mRowSize = 64; + mRowSpacing = 0; + mColSpacing = 0; + mIsContainer = true; + + mResizing = false; + + mSizeToChildren = false; + mAutoCellSize = false; + + mFrozen = false; + mDynamicSize = false; + mFillRowFirst = true; + + mPadding.set( 0, 0, 0, 0 ); +} + +GuiDynamicCtrlArrayControl::~GuiDynamicCtrlArrayControl() +{ +} + +IMPLEMENT_CONOBJECT(GuiDynamicCtrlArrayControl); + + +// ConsoleObject... + +void GuiDynamicCtrlArrayControl::initPersistFields() +{ + addField( "colCount", TypeS32, Offset( mCols, GuiDynamicCtrlArrayControl ) ); + addField( "colSize", TypeS32, Offset( mColSize, GuiDynamicCtrlArrayControl ) ); + addField( "rowCount", TypeS32, Offset( mRows, GuiDynamicCtrlArrayControl ) ); + addField( "rowSize", TypeS32, Offset( mRowSize, GuiDynamicCtrlArrayControl ) ); + addField( "rowSpacing", TypeS32, Offset( mRowSpacing, GuiDynamicCtrlArrayControl ) ); + addField( "colSpacing", TypeS32, Offset( mColSpacing, GuiDynamicCtrlArrayControl ) ); + addField( "frozen", TypeBool, Offset( mFrozen, GuiDynamicCtrlArrayControl ), "When true array will not updateChildrenControls when new children are added or in response to children resize events." ); + addField( "autoCellSize", TypeBool, Offset( mAutoCellSize, GuiDynamicCtrlArrayControl ), "When true cell size is set to the widest/tallest child control." ); + addField( "fillRowFirst", TypeBool, Offset( mFillRowFirst, GuiDynamicCtrlArrayControl ), "Fill rows or columns first" ); + addField( "dynamicSize", TypeBool, Offset( mDynamicSize, GuiDynamicCtrlArrayControl ), "Calculate one component of the extent based on number of children or use user defined extent only" ); + addField( "padding", TypeRectSpacingI, Offset( mPadding, GuiDynamicCtrlArrayControl ) ); + + Parent::initPersistFields(); +} + + +// SimObject... + +void GuiDynamicCtrlArrayControl::inspectPostApply() +{ + resize(getPosition(), getExtent()); + Parent::inspectPostApply(); +} + + +// SimSet... + +void GuiDynamicCtrlArrayControl::addObject(SimObject *obj) +{ + Parent::addObject(obj); + + if ( !mFrozen ) + refresh(); +} + + +// GuiControl... + +bool GuiDynamicCtrlArrayControl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if ( size() == 0 ) + return Parent::resize( newPosition, newExtent ); + + if ( mResizing ) + return false; + + mResizing = true; + + // Calculate the cellSize based on our widest/tallest child control + // if the flag to do so is set. + if ( mAutoCellSize ) + { + mColSize = 1; + mRowSize = 1; + + for ( U32 i = 0; i < size(); i++ ) + { + GuiControl *child = dynamic_cast(operator [](i)); + if ( child && child->isVisible() ) + { + if ( mColSize < child->getWidth() ) + mColSize = child->getWidth(); + if ( mRowSize < child->getHeight() ) + mRowSize = child->getHeight(); + } + } + } + + // Count number of visible, children guiControls. + S32 numChildren = 0; + for ( U32 i = 0; i < size(); i++ ) + { + GuiControl *child = dynamic_cast(operator [](i)); + if ( child && child->isVisible() ) + numChildren++; + } + + // Calculate number of rows and columns. + if ( !mFillRowFirst ) + { + mRows = 1; + while ( ( ( mRows + 1 ) * mRowSize + mRows * mRowSpacing ) <= ( newExtent.y - ( mPadding.top + mPadding.bottom ) ) ) + mRows++; + + mCols = numChildren / mRows; + if ( numChildren % mRows > 0 ) + mCols++; + } + else + { + mCols = 1; + while ( ( ( mCols + 1 ) * mColSize + mCols * mColSpacing ) <= ( newExtent.x - ( mPadding.left + mPadding.right ) ) ) + mCols++; + + mRows = numChildren / mCols; + if ( numChildren % mCols > 0 ) + mRows++; + } + + // Place each child... + S32 childcount = 0; + for ( S32 i = 0; i < size(); i++ ) + { + // Place control + GuiControl *gc = dynamic_cast(operator [](i)); + + // Added check if child is visible. Invisible children don't take part + if ( gc && gc->isVisible() ) + { + S32 curCol, curRow; + + // Get the current column and row... + if ( mFillRowFirst ) + { + curCol = childcount % mCols; + curRow = childcount / mCols; + } + else + { + curCol = childcount / mRows; + curRow = childcount % mRows; + } + + // Reposition and resize + Point2I newPos( mPadding.left + curCol * ( mColSize + mColSpacing ), mPadding.top + curRow * ( mRowSize + mRowSpacing ) ); + gc->resize( newPos, Point2I( mColSize, mRowSize ) ); + + childcount++; + } + } + + Point2I realExtent( newExtent ); + + if ( mDynamicSize ) + { + if ( mFillRowFirst ) + realExtent.y = mRows * mRowSize + ( mRows - 1 ) * mRowSpacing + ( mPadding.top + mPadding.bottom ); + else + realExtent.x = mCols * mColSize + ( mCols - 1 ) * mColSpacing + ( mPadding.left + mPadding.right ); + } + + mResizing = false; + + return Parent::resize( newPosition, realExtent ); +} + +void GuiDynamicCtrlArrayControl::childResized(GuiControl *child) +{ + Parent::childResized(child); + + if ( !mFrozen ) + refresh(); +} + +void GuiDynamicCtrlArrayControl::refresh() +{ + resize( getPosition(), getExtent() ); +} + +ConsoleMethod(GuiDynamicCtrlArrayControl, refresh, void, 2, 2, "Forces the child controls to recalculate") +{ + object->refresh(); +} diff --git a/gui/containers/guiDynamicCtrlArrayCtrl.h b/gui/containers/guiDynamicCtrlArrayCtrl.h new file mode 100644 index 0000000..815c649 --- /dev/null +++ b/gui/containers/guiDynamicCtrlArrayCtrl.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIDYNAMICCTRLARRAYCTRL_H_ +#define _GUIDYNAMICCTRLARRAYCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#include "gfx/gfxDevice.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +class GuiDynamicCtrlArrayControl : public GuiControl +{ + typedef GuiControl Parent; + +public: + + GuiDynamicCtrlArrayControl(); + virtual ~GuiDynamicCtrlArrayControl(); + + DECLARE_CONOBJECT(GuiDynamicCtrlArrayControl); + DECLARE_CATEGORY( "Gui Containers" ); + + // ConsoleObject + static void initPersistFields(); + + // SimObject + void inspectPostApply(); + + // SimSet + void addObject(SimObject *obj); + + // GuiControl + bool resize(const Point2I &newPosition, const Point2I &newExtent); + void childResized(GuiControl *child); + + // GuiDynamicCtrlArrayCtrl + void refresh(); + +protected: + + S32 mCols; + S32 mRows; + S32 mRowSize; + S32 mColSize; + S32 mRowSpacing; + S32 mColSpacing; + bool mResizing; + bool mSizeToChildren; + bool mAutoCellSize; + bool mFrozen; + bool mDynamicSize; + bool mFillRowFirst; + + RectSpacingI mPadding; +}; + +#endif // _GUIDYNAMICCTRLARRAYCTRL_H_ \ No newline at end of file diff --git a/gui/containers/guiFormCtrl.cpp b/gui/containers/guiFormCtrl.cpp new file mode 100644 index 0000000..c7c1dc1 --- /dev/null +++ b/gui/containers/guiFormCtrl.cpp @@ -0,0 +1,353 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiFormCtrl.h" + +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDrawUtil.h" + +#ifdef TORQUE_TOOLS + +IMPLEMENT_CONOBJECT(GuiFormCtrl); + +ConsoleMethod(GuiFormCtrl, setCaption, void, 3, 3, "setCaption(caption) - Sets the title of the Form") +{ + object->setCaption(argv[2]); +} + +GuiFormCtrl::GuiFormCtrl() +{ + setMinExtent(Point2I(200,100)); + mActive = true; + mMouseOver = false; + mDepressed = false; + mCanMove = false; + mCaption = StringTable->insert("[none]"); + mUseSmallCaption = false; + mSmallCaption = StringTable->insert(""); + + mContentLibrary = StringTable->insert(""); + mContent = StringTable->insert(""); + + mCanSaveFieldDictionary = true; + + + mIsContainer = true; + + // The attached menu bar + mHasMenu = false; + mMenuBar = NULL; +} + +GuiFormCtrl::~GuiFormCtrl() +{ +} + +void GuiFormCtrl::initPersistFields() +{ + addField("Caption", TypeCaseString, Offset(mCaption, GuiFormCtrl)); + addField("ContentLibrary",TypeString, Offset(mContentLibrary, GuiFormCtrl)); + addField("Content", TypeString, Offset(mContent, GuiFormCtrl)); + addField("Movable", TypeBool, Offset(mCanMove, GuiFormCtrl)); + addField("HasMenu", TypeBool, Offset(mHasMenu, GuiFormCtrl)); + + Parent::initPersistFields(); +} + +void GuiFormCtrl::setCaption(const char* caption) +{ + if(caption) + { + mCaption = StringTable->insert(caption, true); + } +} + +bool GuiFormCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + mFont = mProfile->mFont; + AssertFatal(mFont, "GuiFormCtrl::onWake: invalid font in profile" ); + + mProfile->constructBitmapArray(); + + if(mProfile->mUseBitmapArray && mProfile->mBitmapArrayRects.size()) + { + mThumbSize.set( mProfile->mBitmapArrayRects[0].extent.x, mProfile->mBitmapArrayRects[0].extent.y ); + mThumbSize.setMax( mProfile->mBitmapArrayRects[1].extent ); + + if(mFont->getHeight() > mThumbSize.y) + mThumbSize.y = mFont->getHeight(); + } + else + { + mThumbSize.set(20, 20); + } + + return true; +} + + +void GuiFormCtrl::addObject(SimObject *newObj ) +{ + if( ( mHasMenu && size() > 1) || (!mHasMenu && size() > 0 ) ) + { + Con::warnf("GuiFormCtrl::addObject - Forms may only have one *direct* child - Placing on Parent!"); + + Parent::addObject( newObj ); + return; + } + + GuiControl *newCtrl = dynamic_cast( newObj ); + GuiFormCtrl*formCtrl = dynamic_cast( newObj ); + if( newCtrl && formCtrl ) + newCtrl->setCanSave( true ); + else if ( newCtrl ) + newCtrl->setCanSave( false ); + + Parent::addObject( newObj ); +} + +void GuiFormCtrl::onSleep() +{ + Parent::onSleep(); + mFont = NULL; +} + +bool GuiFormCtrl::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if( !mMenuBar && mHasMenu ) + { + mMenuBar = new GuiMenuBar(); + AssertFatal(mMenuBar, "GuiFormCtrl::onAdd: cannot create form menu" ); + if ( mMenuBar && Sim::findObject(StringTable->insert("GuiFormMenuBarProfile")) ) + { + mMenuBar->setField("profile","GuiFormMenuBarProfile"); + mMenuBar->setField("horizSizing", "right"); + mMenuBar->setField("vertSizing", "bottom"); + mMenuBar->setField("extent", "16 16"); + mMenuBar->setField("minExtent", "16 16"); + mMenuBar->setField("position", "0 0"); + mMenuBar->setField("class", "FormMenuBarClass"); // Give a generic class to the menu bar so that one set of functions may be used for all of them. + + mMenuBar->registerObject(); + mMenuBar->setProcessTicks(true); // Activate the processing of ticks to track if the mouse pointer has been hovering within the menu + addObject(mMenuBar); // Add the menu bar to the form + } + } + + return true; +} + +bool GuiFormCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if( !Parent::resize(newPosition, newExtent) ) + return false; + + if( !mAwake || !mProfile->mBitmapArrayRects.size() ) + return false; + + // Should the caption be modified because the title bar is too small? + S32 textWidth = mProfile->mFont->getStrWidth(mCaption); + S32 newTextArea = getWidth() - mThumbSize.x - mProfile->mBitmapArrayRects[4].extent.x; + if(newTextArea < textWidth) + { + static char buf[256]; + + mUseSmallCaption = true; + mSmallCaption = StringTable->insert(""); + + S32 strlen = dStrlen((const char*)mCaption); + for(S32 i=strlen; i>=0; --i) + { + dStrcpy(buf, ""); + dStrncat(buf, (const char*)mCaption, i); + dStrcat(buf, "..."); + + textWidth = mProfile->mFont->getStrWidth(buf); + + if(textWidth < newTextArea) + { + mSmallCaption = StringTable->insert(buf, true); + break; + } + } + + } else + { + mUseSmallCaption = false; + } + + Con::executef(this, "onResize"); + + return true; +} + +void GuiFormCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + // Fill in the control's child area + RectI boundsRect(offset, getExtent()); + boundsRect.point.y += mThumbSize.y; + boundsRect.extent.y -= mThumbSize.y; + + // draw the border of the form if specified + if (mProfile->mOpaque) + GFX->getDrawUtil()->drawRectFill(boundsRect, mProfile->mFillColor); + + if (mProfile->mBorder) + renderBorder(boundsRect, mProfile); + + // If we don't have a child, put some text in the child area + if( empty() ) + { + GFX->getDrawUtil()->setBitmapModulation(ColorI(0,0,0)); + renderJustifiedText(boundsRect.point, boundsRect.extent, "[none]"); + } + + S32 textWidth = 0; + + // Draw our little bar, too + if (mProfile->mBitmapArrayRects.size() >= 5) + { + GFX->getDrawUtil()->clearBitmapModulation(); + + S32 barStart = offset.x + textWidth; + S32 barTop = mThumbSize.y / 2 + offset.y - mProfile->mBitmapArrayRects[3].extent.y / 2; + + Point2I barOffset(barStart, barTop); + + // Draw the start of the bar... + GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->mTextureObject ,RectI(barOffset, mProfile->mBitmapArrayRects[2].extent), mProfile->mBitmapArrayRects[2] ); + + // Now draw the middle... + barOffset.x += mProfile->mBitmapArrayRects[2].extent.x; + + S32 barMiddleSize = (getExtent().x - (barOffset.x - offset.x)) - mProfile->mBitmapArrayRects[4].extent.x + 1; + + if (barMiddleSize > 0) + { + // We have to do this inset to prevent nasty stretching artifacts + RectI foo = mProfile->mBitmapArrayRects[3]; + foo.inset(1,0); + + GFX->getDrawUtil()->drawBitmapStretchSR( + mProfile->mTextureObject, + RectI(barOffset, Point2I(barMiddleSize, mProfile->mBitmapArrayRects[3].extent.y)), + foo + ); + } + + // And the end + barOffset.x += barMiddleSize; + + GFX->getDrawUtil()->drawBitmapStretchSR( mProfile->mTextureObject, RectI(barOffset, mProfile->mBitmapArrayRects[4].extent), + mProfile->mBitmapArrayRects[4]); + + GFX->getDrawUtil()->setBitmapModulation((mMouseOver ? mProfile->mFontColorHL : mProfile->mFontColor)); + renderJustifiedText(Point2I(mThumbSize.x, 0) + offset, Point2I(getWidth() - mThumbSize.x - mProfile->mBitmapArrayRects[4].extent.x, mThumbSize.y), (mUseSmallCaption ? mSmallCaption : mCaption) ); + + } + + // Render the children + renderChildControls(offset, updateRect); +} + +void GuiFormCtrl::onMouseMove(const GuiEvent &event) +{ + Point2I localMove = globalToLocalCoord(event.mousePoint); + + // If we're clicking in the header then resize + mMouseOver = (localMove.y < mThumbSize.y); + if(isMouseLocked()) + mDepressed = mMouseOver; + +} + +void GuiFormCtrl::onMouseEnter(const GuiEvent &event) +{ + setUpdate(); + if(isMouseLocked()) + { + mDepressed = true; + mMouseOver = true; + } + else + { + mMouseOver = true; + } + +} + +void GuiFormCtrl::onMouseLeave(const GuiEvent &event) +{ + setUpdate(); + if(isMouseLocked()) + mDepressed = false; + mMouseOver = false; +} + +void GuiFormCtrl::onMouseDown(const GuiEvent &event) +{ + Point2I localClick = globalToLocalCoord(event.mousePoint); + + // If we're clicking in the header then resize + if(localClick.y < mThumbSize.y) + { + mouseLock(); + mDepressed = true; + mMouseMovingWin = mCanMove; + + //update + setUpdate(); + } + + mOrigBounds = getBounds(); + + mMouseDownPosition = event.mousePoint; + + if (mMouseMovingWin ) + { + mouseLock(); + } + else + { + GuiControl *ctrl = findHitControl(localClick); + if (ctrl && ctrl != this) + ctrl->onMouseDown(event); + } +} + +void GuiFormCtrl::onMouseUp(const GuiEvent &event) +{ + // Make sure we only get events we ought to be getting... + if (! mActive) + return; + + mouseUnlock(); + setUpdate(); + + Point2I localClick = globalToLocalCoord(event.mousePoint); + + // If we're clicking in the header then resize + //if(localClick.y < mThumbSize.y && mDepressed) + // setCollapsed(!mCollapsed); +} + +ConsoleMethod(GuiFormCtrl, getMenuID, S32, 2, 2, "Returns the ID of the Form Menu") +{ + return object->getMenuBarID(); +} + +U32 GuiFormCtrl::getMenuBarID() +{ + return mMenuBar ? mMenuBar->getId() : 0; +} + +#endif \ No newline at end of file diff --git a/gui/containers/guiFormCtrl.h b/gui/containers/guiFormCtrl.h new file mode 100644 index 0000000..e511d1c --- /dev/null +++ b/gui/containers/guiFormCtrl.h @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIFORMCTRL_H_ +#define _GUIFORMCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#ifndef _GUI_PANEL_H_ +#include "gui/containers/guiPanel.h" +#endif + +#ifndef _GUIMENUBAR_H_ +#include "gui/editor/guiMenuBar.h" +#endif + +#ifndef _GUICANVAS_H_ +#include "gui/core/guiCanvas.h" +#endif + +#include "console/console.h" +#include "console/consoleTypes.h" + +class GuiMenuBar; + + +/// @addtogroup gui_container_group Containers +/// +/// @ingroup gui_group Gui System +/// @{ +class GuiFormCtrl : public GuiPanel +{ +private: + typedef GuiPanel Parent; + + Resource mFont; + StringTableEntry mCaption; + bool mCanMove; + bool mUseSmallCaption; + StringTableEntry mSmallCaption; + StringTableEntry mContentLibrary; + StringTableEntry mContent; + + Point2I mThumbSize; + + bool mHasMenu; + GuiMenuBar* mMenuBar; + + bool mMouseMovingWin; + + Point2I mMouseDownPosition; + RectI mOrigBounds; + RectI mStandardBounds; + + RectI mCloseButton; + RectI mMinimizeButton; + RectI mMaximizeButton; + + bool mMouseOver; + bool mDepressed; + +public: + GuiFormCtrl(); + virtual ~GuiFormCtrl(); + + void setCaption(const char* caption); + + bool resize(const Point2I &newPosition, const Point2I &newExtent); + void onRender(Point2I offset, const RectI &updateRect); + + bool onAdd(); + bool onWake(); + void onSleep(); + + virtual void addObject(SimObject *newObj ); + + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void onMouseEnter(const GuiEvent &event); + + U32 getMenuBarID(); + + static void initPersistFields(); + DECLARE_CONOBJECT(GuiFormCtrl); +}; +/// @} + +#endif \ No newline at end of file diff --git a/gui/containers/guiFrameCtrl.cpp b/gui/containers/guiFrameCtrl.cpp new file mode 100644 index 0000000..3f4d89f --- /dev/null +++ b/gui/containers/guiFrameCtrl.cpp @@ -0,0 +1,1048 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiFrameCtrl.h" + +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiFrameSetCtrl); + +//----------------------------------------------------------------------------- + +static const EnumTable::Enums borderStateEnums[] = +{ + { GuiFrameSetCtrl::FRAME_STATE_ON, "alwaysOn" }, + { GuiFrameSetCtrl::FRAME_STATE_OFF, "alwaysOff" }, + { GuiFrameSetCtrl::FRAME_STATE_AUTO, "dynamic" } +}; +static const EnumTable gBorderStateTable(3, &borderStateEnums[0]); + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::initPersistFields() +{ + addField("columns", TypeS32Vector, Offset(mColumnOffsets, GuiFrameSetCtrl)); + addField("rows", TypeS32Vector, Offset(mRowOffsets, GuiFrameSetCtrl)); + addField("borderWidth", TypeS32, Offset(mFramesetDetails.mBorderWidth, GuiFrameSetCtrl)); + addField("borderColor", TypeColorI, Offset(mFramesetDetails.mBorderColor, GuiFrameSetCtrl)); + addField("borderEnable", TypeEnum, Offset(mFramesetDetails.mBorderEnable, GuiFrameSetCtrl), 1, &gBorderStateTable); + addField("borderMovable", TypeEnum, Offset(mFramesetDetails.mBorderMovable, GuiFrameSetCtrl), 1, &gBorderStateTable); + addField("autoBalance", TypeBool, Offset(mAutoBalance, GuiFrameSetCtrl)); + addField("fudgeFactor", TypeS32, Offset(mFudgeFactor, GuiFrameSetCtrl)); + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +ConsoleMethod( GuiFrameSetCtrl, frameBorder, void, 3, 4, "(int index, bool enable=true)") +{ + S32 index = dAtoi(argv[2]); + if (argc == 3) + object->frameBorderEnable(index); + else + object->frameBorderEnable(index, argv[3]); +} + +ConsoleMethod( GuiFrameSetCtrl, frameMovable, void, 3, 4, "(int index, bool enable=true)" ) +{ + S32 index = dAtoi(argv[2]); + if (argc == 3) + object->frameBorderMovable(index); + else + object->frameBorderMovable(index, argv[3]); +} + +ConsoleMethod( GuiFrameSetCtrl, frameMinExtent, void, 5, 5, "(int index, int w, int h)") +{ + Point2I extent(getMax(0, dAtoi(argv[3])), getMax(0, dAtoi(argv[4]))); + object->frameMinExtent(dAtoi(argv[2]), extent); +} + +ConsoleMethod( GuiFrameSetCtrl, framePadding, void, 7, 7, "(int index, int t, int b, int l, int r)") +{ + RectSpacingI padding( dAtoi( argv[3] ), dAtoi( argv[4] ), dAtoi( argv[5] ), dAtoi( argv[6] ) ); + object->framePadding(dAtoi(argv[2]), padding); +} + +ConsoleMethod( GuiFrameSetCtrl, addColumn, void, 2, 2, "") +{ + Vector * columns = object->columnOffsets(); + columns->push_back(0); + object->balanceFrames(); +} + +ConsoleMethod( GuiFrameSetCtrl, addRow, void, 2, 2, "") +{ + Vector * rows = object->rowOffsets(); + rows->push_back(0); + object->balanceFrames(); +} + +ConsoleMethod( GuiFrameSetCtrl, removeColumn, void, 2, 2, "") +{ + Vector * columns = object->columnOffsets(); + + if(columns->size() > 0) + { + columns->setSize(columns->size() - 1); + object->balanceFrames(); + } + else + Con::errorf(ConsoleLogEntry::General, "No columns exist to remove"); +} + +ConsoleMethod( GuiFrameSetCtrl, removeRow, void, 2, 2, "") +{ + Vector * rows = object->rowOffsets(); + + if(rows->size() > 0) + { + rows->setSize(rows->size() - 1); + object->balanceFrames(); + } + else + Con::errorf(ConsoleLogEntry::General, "No rows exist to remove"); +} + +ConsoleMethod( GuiFrameSetCtrl, getColumnCount, S32, 2, 2, "") +{ + return(object->columnOffsets()->size()); +} + +ConsoleMethod( GuiFrameSetCtrl, getRowCount, S32, 2, 2, "") +{ + return(object->rowOffsets()->size()); +} + +ConsoleMethod( GuiFrameSetCtrl, getColumnOffset, S32, 3, 3, "(int index)") +{ + S32 index = dAtoi(argv[2]); + if(index < 0 || index > object->columnOffsets()->size()) + { + Con::errorf(ConsoleLogEntry::General, "Column index out of range"); + return(0); + } + return((*object->columnOffsets())[index]); +} + +ConsoleMethod( GuiFrameSetCtrl, getRowOffset, S32, 3, 3, "(int index)") +{ + S32 index = dAtoi(argv[2]); + if(index < 0 || index > object->rowOffsets()->size()) + { + Con::errorf(ConsoleLogEntry::General, "Row index out of range"); + return(0); + } + return((*object->rowOffsets())[index]); +} + +ConsoleMethod( GuiFrameSetCtrl, setColumnOffset, void, 4, 4, "(int index, int offset)") +{ + Vector & columns = *(object->columnOffsets()); + + S32 index = dAtoi(argv[2]); + if(index < 0 || index > columns.size()) + { + Con::errorf(ConsoleLogEntry::General, "Column index out of range"); + return; + } + + // + S32 offset = dAtoi(argv[3]); + + // check the offset + if(((index > 0) && (offset < columns[index-1])) || + ((index < (columns.size() - 1)) && (offset > columns[index+1]))) + { + Con::errorf(ConsoleLogEntry::General, "Invalid column offset"); + return; + } + + columns[index] = offset; + object->updateSizes(); +} + +ConsoleMethod( GuiFrameSetCtrl, setRowOffset, void, 4, 4, "(int index, int offset)") +{ + Vector & rows = *(object->rowOffsets()); + + S32 index = dAtoi(argv[2]); + if(index < 0 || index > rows.size()) + { + Con::errorf(ConsoleLogEntry::General, "Row index out of range"); + return; + } + + // + S32 offset = dAtoi(argv[3]); + + // check the offset + if(((index > 0) && (offset < rows[index-1])) || + ((index < (rows.size() - 1)) && (offset > rows[index+1]))) + { + Con::errorf(ConsoleLogEntry::General, "Invalid row offset"); + return; + } + + rows[index] = offset; + object->updateSizes(); +} + +ConsoleMethod( GuiFrameSetCtrl, updateSizes, void, 2, 2, "()") +{ + object->updateSizes(); +} + +//----------------------------------------------------------------------------- +GuiFrameSetCtrl::GuiFrameSetCtrl() +{ + VECTOR_SET_ASSOCIATION(mColumnOffsets); + VECTOR_SET_ASSOCIATION(mRowOffsets); + VECTOR_SET_ASSOCIATION(mFrameDetails); + + mAutoBalance = true; + mIsContainer = true; + + init(1, 1, NULL, NULL); +} + +//----------------------------------------------------------------------------- +GuiFrameSetCtrl::GuiFrameSetCtrl(U32 columns, U32 rows, const U32 columnOffsets[], const U32 rowOffsets[]) +{ + init(columns, rows, columnOffsets, rowOffsets); +} + +//----------------------------------------------------------------------------- +GuiFrameSetCtrl::~GuiFrameSetCtrl() +{ + while (mFrameDetails.size() > 0) + { + delete mFrameDetails.last(); + mFrameDetails.pop_back(); + } +} + +//----------------------------------------------------------------------------- + +void GuiFrameSetCtrl::addObject(SimObject *object) +{ + AssertFatal(object != NULL, "GuiFrameSetCtrl::addObject: NULL object"); + + // assign the object to a frame - give it default frame details + Parent::addObject(object); + GuiControl *gc = dynamic_cast(object); + if (gc != NULL) + { + FrameDetail *detail = new FrameDetail; + detail->mMinExtent = gc->getMinExtent(); + mFrameDetails.push_back(detail); + } + else + mFrameDetails.push_back(NULL); + // resize it to fit into the frame to which it is assigned (if no frame for it, don't bother resizing) + if(isAwake()) + computeSizes(); +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::removeObject(SimObject *object) +{ + if (object != NULL) + { + VectorPtr::iterator soitr; + VectorPtr::iterator fditr = mFrameDetails.begin(); + for (soitr = begin(); soitr != end(); soitr++, fditr++) + { + if (*soitr == object) + { + delete *fditr; + mFrameDetails.erase(fditr); + break; + } + } + } + Parent::removeObject(object); +} + +//----------------------------------------------------------------------------- +bool GuiFrameSetCtrl::resize(const Point2I &newPos, const Point2I &newExtent) +{ + // rebalance before losing the old extent (if required) + if (mAutoBalance == true) + rebalance(newExtent); + + if( !Parent::resize(newPos, newExtent) ) + return false; + + // compute new sizing info for the frames - takes care of resizing the children + computeSizes( !mAutoBalance ); + + return true; +} + +//----------------------------------------------------------------------------- + +void GuiFrameSetCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) +{ + GuiCanvas *pRoot = getRoot(); + if( !pRoot ) + return; + + Region curRegion = NONE; + // Determine the region by mouse position. + Point2I curMousePos = globalToLocalCoord(lastGuiEvent.mousePoint); + curRegion = pointInAnyRegion(curMousePos); + + PlatformWindow *pWindow = pRoot->getPlatformWindow(); + AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); + PlatformCursorController *pController = pWindow->getCursorController(); + AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); + + switch (curRegion) + { + case VERTICAL_DIVIDER: + // change to left-right cursor + if(pRoot->mCursorChanged != PlatformCursorController::curResizeVert) + { + //*** We've already changed the cursor, so set it back before we change it again. + if(pRoot->mCursorChanged != -1) + pController->popCursor(); + + //*** Now change the cursor shape + pController->pushCursor(PlatformCursorController::curResizeVert); + pRoot->mCursorChanged = PlatformCursorController::curResizeVert; + + } + break; + + case HORIZONTAL_DIVIDER: + // change to up-down cursor + if(pRoot->mCursorChanged != PlatformCursorController::curResizeHorz) + { + //*** We've already changed the cursor, so set it back before we change it again. + if(pRoot->mCursorChanged != -1) + pController->popCursor(); + + //*** Now change the cursor shape + pController->pushCursor(PlatformCursorController::curResizeHorz); + pRoot->mCursorChanged = PlatformCursorController::curResizeHorz; + } + break; + + case DIVIDER_INTERSECTION: + // change to move cursor + if(pRoot->mCursorChanged != PlatformCursorController::curResizeAll) + { + //*** We've already changed the cursor, so set it back before we change it again. + if(pRoot->mCursorChanged != -1) + pController->popCursor(); + + //*** Now change the cursor shape + pController->pushCursor(PlatformCursorController::curResizeAll); + pRoot->mCursorChanged = PlatformCursorController::curResizeAll; + + } + break; + + case NONE: + default: + if(pRoot->mCursorChanged != -1) + { + //*** We've already changed the cursor, so set it back before we change it again. + pController->popCursor(); + + pRoot->mCursorChanged = -1; + } + break; + } + +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::onMouseDown(const GuiEvent &event) +{ + if (mFramesetDetails.mBorderEnable != FRAME_STATE_OFF && mFramesetDetails.mBorderMovable != FRAME_STATE_OFF) + { + // determine if a divider was hit + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + findHitRegion(curMousePos); // sets mCurVerticalHit, mCurHorizontalHit, & mCurHitRegion + + if (mCurHitRegion != NONE) + { + mouseLock(); + setFirstResponder(); + setUpdate(); + } + } +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::onMouseUp(const GuiEvent &event) +{ + TORQUE_UNUSED(event); + if (mCurHitRegion != NONE) + { + mCurHitRegion = NONE; + mCurVerticalHit = NO_HIT; + mCurHorizontalHit = NO_HIT; + mouseUnlock(); + setUpdate(); + } +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::onMouseDragged(const GuiEvent &event) +{ + if (mCurHitRegion != NONE) + { + // identify the frames involved in the resizing, checking if they are resizable + S32 indexes[4]; + S32 activeFrames = findResizableFrames(indexes); + if (activeFrames > 0) + { + S32 range[4]; + // determine the range of movement, limiting as specified by individual frames + computeMovableRange(mCurHitRegion, mCurVerticalHit, mCurHorizontalHit, activeFrames, indexes, range); + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + switch (mCurHitRegion) + { + case VERTICAL_DIVIDER: + mColumnOffsets[mCurVerticalHit] = getMin(getMax(range[0], curMousePos.x - mLocOnDivider.x), range[1]); + break; + case HORIZONTAL_DIVIDER: + mRowOffsets[mCurHorizontalHit] = getMin(getMax(range[0], curMousePos.y - mLocOnDivider.y), range[1]); + break; + case DIVIDER_INTERSECTION: + mColumnOffsets[mCurVerticalHit] = getMin(getMax(range[0], curMousePos.x - mLocOnDivider.x), range[1]); + mRowOffsets[mCurHorizontalHit] = getMin(getMax(range[2], curMousePos.y - mLocOnDivider.y), range[3]); + break; + default: + return; + } + computeSizes(); + } + } +} + +//----------------------------------------------------------------------------- +bool GuiFrameSetCtrl::onAdd() +{ + if (Parent::onAdd() == false) + return(false); + + return(true); +} + +bool GuiFrameSetCtrl::onWake() +{ + if (Parent::onWake() == false) + return(false); + + computeSizes(); + return(true); +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::onRender(Point2I offset, const RectI &updateRect ) +{ + RectI r(offset.x, offset.y, getWidth(), getHeight()); + + Parent::onRender( offset, updateRect ); + + drawDividers(offset); + +} + +//----------------------------------------------------------------------------- +bool GuiFrameSetCtrl::init(U32 columns, U32 rows, const U32 columnOffsets[], const U32 rowOffsets[]) +{ + if (columns != 0 && rows != 0) + { + mColumnOffsets.clear(); + mRowOffsets.clear(); + U32 i; + for (i = 0; i < columns; i++) + { + if (columnOffsets == NULL) + mColumnOffsets.push_back(0); + else + { + AssertFatal(columnOffsets != NULL, "GuiFrameSetCtrl::init: NULL column offsets"); + mColumnOffsets.push_back((U32)columnOffsets[i]); + if (i > 0) + { + AssertFatal(mColumnOffsets[i - 1] < mColumnOffsets[i], "GuiFrameSetCtrl::init: column offsets must be monotonically increasing"); + mColumnOffsets.clear(); + return(false); + } + } + } + for (i = 0; i < rows; i++) + { + if (rowOffsets == NULL) + mRowOffsets.push_back(0); + else + { + AssertFatal(rowOffsets != NULL, "GuiFrameSetCtrl::init: NULL row offsets"); + mRowOffsets.push_back((U32)rowOffsets[i]); + if (i > 0) + { + AssertFatal(mRowOffsets[i - 1] < mRowOffsets[i], "GuiFrameSetCtrl::init: row offsets must be monotonically increasing"); + mRowOffsets.clear(); + return(false); + } + } + } + } + mFramesetDetails.mBorderWidth = DEFAULT_BORDER_WIDTH; + mFramesetDetails.mBorderEnable = FRAME_STATE_AUTO; + mFramesetDetails.mBorderMovable = FRAME_STATE_AUTO; + mAutoBalance = false; + mFudgeFactor = 0; + mCurHitRegion = NONE; + mCurVerticalHit = NO_HIT; + mCurHorizontalHit = NO_HIT; + return(true); +} + +//----------------------------------------------------------------------------- +// point is assumed to already be in local coordinates. +GuiFrameSetCtrl::Region GuiFrameSetCtrl::findHitRegion(const Point2I &point) +{ + Vector::iterator itr; + S32 i = 1; + // step through vertical dividers + for (itr = mColumnOffsets.begin() + 1; itr < mColumnOffsets.end(); itr++, i++) + { + if (hitVerticalDivider(*itr, point) == true) + { + mCurVerticalHit = i; + mLocOnDivider.x = point.x - (*itr); + break; + } + } + i = 1; + // step through horizontal dividers + for (itr = mRowOffsets.begin() + 1; itr < mRowOffsets.end(); itr++, i++) + { + if (hitHorizontalDivider(*itr, point) == true) + { + mCurHorizontalHit = i; + mLocOnDivider.y = point.y - (*itr); + break; + } + } + // now set type of hit... + if (mCurVerticalHit != NO_HIT) + { + if (mCurHorizontalHit != NO_HIT) + return(mCurHitRegion = DIVIDER_INTERSECTION); + else + return(mCurHitRegion = VERTICAL_DIVIDER); + } + else if (mCurHorizontalHit != NO_HIT) + return(mCurHitRegion = HORIZONTAL_DIVIDER); + else + return(mCurHitRegion = NONE); +} + +GuiFrameSetCtrl::Region GuiFrameSetCtrl::pointInAnyRegion(const Point2I &point) +{ + Vector::iterator itr; + S32 i = 1; + S32 curVertHit = NO_HIT, curHorzHit = NO_HIT; + Region result = NONE; + // step through vertical dividers + for (itr = mColumnOffsets.begin() + 1; itr < mColumnOffsets.end(); itr++, i++) + { + if (hitVerticalDivider(*itr, point) == true) + { + curVertHit = i; + break; + } + } + i = 1; + // step through horizontal dividers + for (itr = mRowOffsets.begin() + 1; itr < mRowOffsets.end(); itr++, i++) + { + if (hitHorizontalDivider(*itr, point) == true) + { + curHorzHit = i; + break; + } + } + // now select the type of region in which the point lies + if (curVertHit != NO_HIT) + { + if (curHorzHit != NO_HIT) + result = DIVIDER_INTERSECTION; + else + result = VERTICAL_DIVIDER; + } + else if (curHorzHit != NO_HIT) + result = HORIZONTAL_DIVIDER; + return(result); +} + +//----------------------------------------------------------------------------- +// indexes must have at least 4 entries. +// This *may* modify mCurVerticalHit, mCurHorizontalHit, and mCurHitRegion if it +// determines that movement is disabled by frame content. +// If it does make such a change, it also needs to do the reset performed by +// onMouseUp if it sets mCurHitRegion to NONE. +S32 GuiFrameSetCtrl::findResizableFrames(S32 indexes[]) +{ + AssertFatal(indexes != NULL, "GuiFrameSetCtrl::findResizableFrames: NULL indexes"); + + // first, find the column and row indexes of the affected columns/rows + S32 validIndexes = 0; + switch (mCurHitRegion) + { + case VERTICAL_DIVIDER: // columns + indexes[0] = mCurVerticalHit - 1; + indexes[1] = mCurVerticalHit; + validIndexes = 2; + break; + case HORIZONTAL_DIVIDER: // rows + indexes[0] = mCurHorizontalHit - 1; + indexes[1] = mCurHorizontalHit; + validIndexes = 2; + break; + case DIVIDER_INTERSECTION: // columns & rows + indexes[0] = mCurVerticalHit - 1; // columns + indexes[1] = mCurVerticalHit; + indexes[2] = mCurHorizontalHit - 1; // rows + indexes[3] = mCurHorizontalHit; + validIndexes = 4; + break; + default: + break; + } + // now, make sure these indexes are for movable frames + VectorPtr::iterator soitr; + VectorPtr::iterator fditr = mFrameDetails.begin(); + GuiControl *gc; + S32 column = 0; + S32 row = 0; + S32 columns = mColumnOffsets.size(); + S32 rows = mRowOffsets.size(); + for (soitr = begin(); soitr != end() && validIndexes > 0; soitr++, fditr++) + { + // don't continue if some of the frames are empty + if (fditr == mFrameDetails.end()) + break; + // otherwise, check the gui elements for move-restrictions + gc = dynamic_cast(*soitr); + if (gc != NULL) + { + if (column == columns) + { + column = 0; + row++; + } + if (row == rows) + break; + switch (mCurHitRegion) + { + case VERTICAL_DIVIDER: + if ((column == indexes[0] || column == indexes[1]) && (*fditr) && (*fditr)->mBorderMovable == FRAME_STATE_OFF) + validIndexes = 0; + break; + case HORIZONTAL_DIVIDER: + if ((row == indexes[0] || row == indexes[1]) && (*fditr) && (*fditr)->mBorderMovable == FRAME_STATE_OFF) + validIndexes = 0; + break; + case DIVIDER_INTERSECTION: + if ((column == indexes[0] || column == indexes[1]) && (*fditr) && (*fditr)->mBorderMovable == FRAME_STATE_OFF) + { + if ((row == indexes[2] || row == indexes[3]) && (*fditr) && (*fditr)->mBorderMovable == FRAME_STATE_OFF) + validIndexes = 0; + else + { + mCurHitRegion = HORIZONTAL_DIVIDER; + mCurVerticalHit = NO_HIT; + indexes[0] = indexes[2]; + indexes[1] = indexes[3]; + validIndexes = 2; + } + } + else if ((row == indexes[2] || row == indexes[3]) && (*fditr) && (*fditr)->mBorderMovable == FRAME_STATE_OFF) + { + mCurHitRegion = VERTICAL_DIVIDER; + mCurHorizontalHit = NO_HIT; + validIndexes = 2; + } + break; + default: + return(0); + } + column++; + } + } + if (validIndexes == 0) + { + mCurHitRegion = NONE; + mCurVerticalHit = NO_HIT; + mCurHorizontalHit = NO_HIT; + mouseUnlock(); + setUpdate(); + } + return(validIndexes); +} + +//----------------------------------------------------------------------------- +// This method locates the gui control and frame detail associated with a +// particular frame index. +bool GuiFrameSetCtrl::findFrameContents(S32 index, GuiControl **gc, FrameDetail **fd) +{ + AssertFatal(gc != NULL, "GuiFrameSetCtrl::findFrameContents: NULL gui control pointer"); + AssertFatal(fd != NULL, "GuiFrameSetCtrl::findFrameContents: NULL frame detail pointer"); + AssertFatal(*gc == NULL, "GuiFrameSetCtrl::findFrameContents: contents of gui control must be NULL"); + AssertFatal(*fd == NULL, "GuiFrameSetCtrl::findFrameContents: contents of frame detail must be NULL"); + + if (index >= 0 && index < size()) + { + VectorPtr::iterator soitr; + VectorPtr::iterator fditr = mFrameDetails.begin(); + for (soitr = begin(); soitr != end(); soitr++, fditr++, index--) + { + if (index == 0) + { + GuiControl *guiCtrl = dynamic_cast(*soitr); + if (guiCtrl != NULL) + { + *gc = guiCtrl; + *fd = *fditr; + return(true); + } + else + break; + } + } + } + return(false); +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::computeSizes(bool balanceFrames) +{ + S32 columns = mColumnOffsets.size(); + S32 rows = mRowOffsets.size(); + S32 vDividers = columns - 1; + S32 hDividers = rows - 1; + + if ( !balanceFrames && mFrameDetails.size() == ( columns * rows ) ) + { + // This will do some goofy things if you allow this control to resize smaller than + // the additive minimum extents of its frames--so don't. + S32 index, delta; + + if ( columns > 1 ) + { + index = columns - 1; + delta = mFrameDetails[index]->mMinExtent.x - ( getWidth() - mColumnOffsets[index] ); + while ( delta > 0 ) + { + mColumnOffsets[index--] -= delta; + if ( index >= 0 ) + delta = mFrameDetails[index]->mMinExtent.x - ( mColumnOffsets[index + 1] - mColumnOffsets[index] ); + else + break; + } + } + + if ( rows > 1 ) + { + index = rows - 1; + delta = mFrameDetails[columns * index]->mMinExtent.y - ( getHeight() - mRowOffsets[index] ); + while ( delta > 0 ) + { + mRowOffsets[index--] -= delta; + if ( index >= 0 ) + delta = mFrameDetails[columns * index]->mMinExtent.y - ( mRowOffsets[index + 1] - mRowOffsets[index] ); + else + break; + } + } + } + + // first, update the divider placement if necessary + if (balanceFrames == true && mColumnOffsets.size() > 0 && mRowOffsets.size() > 0) + { + Vector::iterator itr; + F32 totWidth = F32(getWidth() - vDividers * mFramesetDetails.mBorderWidth); + F32 totHeight = F32(getHeight() - hDividers * mFramesetDetails.mBorderWidth); + F32 frameWidth = totWidth/(F32)columns; + F32 frameHeight = totHeight/(F32)rows; + F32 i = 0.; + for (itr = mColumnOffsets.begin(); itr != mColumnOffsets.end(); itr++, i++) + *itr = (S32)(i * (frameWidth + (F32)mFramesetDetails.mBorderWidth)); + i = 0.; + for (itr = mRowOffsets.begin(); itr != mRowOffsets.end(); itr++, i++) + *itr = (S32)(i * (frameHeight + (F32)mFramesetDetails.mBorderWidth)); + } + + // now, resize the contents of each frame (and move content w/o a frame beyond visible range) + VectorPtr::iterator soitr; + VectorPtr::iterator fditr = mFrameDetails.begin(); + GuiControl *gc; + S32 column = 0; + S32 row = 0; + Point2I newPos; + Point2I newExtent; + // step through all the children + for (soitr = begin(); soitr != end(); soitr++, fditr++) + { + // column and row track the current frame being resized + if (column == columns) + { + column = 0; + row++; + } + // resize the contents if its a gui control... + gc = dynamic_cast(*soitr); + if (gc != NULL) + { + if (row >= rows) + { + // no more visible frames + newPos = getExtent(); + newExtent.set(DEFAULT_MIN_FRAME_EXTENT, DEFAULT_MIN_FRAME_EXTENT); + gc->resize(newPos, newExtent); + continue; + } + else + { + // determine x components of new position & extent + newPos.x = mColumnOffsets[column]; + if (column == vDividers) + newExtent.x = getWidth() - mColumnOffsets[column]; // last column + else + newExtent.x = mColumnOffsets[column + 1] - mColumnOffsets[column] - mFramesetDetails.mBorderWidth; // any other column + // determine y components of new position & extent + newPos.y = mRowOffsets[row]; + if (row == hDividers) + newExtent.y = getHeight() - mRowOffsets[row]; // last row + else + newExtent.y = mRowOffsets[row + 1] - mRowOffsets[row] - mFramesetDetails.mBorderWidth; // any other row + + if ( *fditr ) + { + FrameDetail *fd = ( *fditr ); + + // Account for Padding. + newPos.x += fd->mPadding.left; + newPos.y += fd->mPadding.top; + newExtent.x -= ( fd->mPadding.left + fd->mPadding.right ); + newExtent.y -= ( fd->mPadding.top + fd->mPadding.bottom ); + } + + // apply the new position & extent + gc->resize(newPos, newExtent); + column++; + } + } + } +} + +//----------------------------------------------------------------------------- +// this method looks at the previous offsets, and uses them to redistribute +// the available height & width proportionally. +void GuiFrameSetCtrl::rebalance(const Point2I &newExtent) +{ + TORQUE_UNUSED(newExtent); + + // look at old_width and old_height - current extent + F32 widthScale = (F32)newExtent.x/(F32)getWidth(); + F32 heightScale = (F32)newExtent.y/(F32)getHeight(); + Vector::iterator itr; + // look at old width offsets + for (itr = mColumnOffsets.begin() + 1; itr < mColumnOffsets.end(); itr++) + // multiply each by new_width/old_width + *itr = S32(F32(*itr) * widthScale); + // look at old height offsets + for (itr = mRowOffsets.begin() + 1; itr < mRowOffsets.end(); itr++) + // multiply each by new_height/new_width + *itr = S32(F32(*itr) * heightScale); + +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::computeMovableRange(Region hitRegion, S32 vertHit, S32 horzHit, S32 numIndexes, const S32 indexes[], S32 ranges[]) +{ + S32 hardRanges[4] = { 0 }; + switch (numIndexes) + { + case 2: + switch (hitRegion) + { + case VERTICAL_DIVIDER: + ranges[0] = hardRanges[0] = (vertHit <= 1) ? mFramesetDetails.mBorderWidth : mColumnOffsets[vertHit - 1] + mFramesetDetails.mBorderWidth; + ranges[1] = hardRanges[1] = (vertHit >= (mColumnOffsets.size() - 1)) ? getWidth() : mColumnOffsets[vertHit + 1] - mFramesetDetails.mBorderWidth; + break; + case HORIZONTAL_DIVIDER: + ranges[0] = hardRanges[0] = (horzHit <= 1) ? mFramesetDetails.mBorderWidth : mRowOffsets[horzHit - 1] + mFramesetDetails.mBorderWidth; + ranges[1] = hardRanges[1] = (horzHit >= (mRowOffsets.size() - 1)) ? getHeight() : mRowOffsets[horzHit + 1] - mFramesetDetails.mBorderWidth; + break; + default: + return; + } + break; + case 4: + if (hitRegion == DIVIDER_INTERSECTION) + { + ranges[0] = hardRanges[0] = (vertHit <= 1) ? mFramesetDetails.mBorderWidth : mColumnOffsets[vertHit - 1] + mFramesetDetails.mBorderWidth; + ranges[1] = hardRanges[1] = (vertHit >= (mColumnOffsets.size() - 1)) ? getWidth() : mColumnOffsets[vertHit + 1] - mFramesetDetails.mBorderWidth; + ranges[2] = hardRanges[2] = (horzHit <= 1) ? mFramesetDetails.mBorderWidth : mRowOffsets[horzHit - 1] + mFramesetDetails.mBorderWidth; + ranges[3] = hardRanges[3] = (horzHit >= (mRowOffsets.size() - 1)) ? getHeight() : mRowOffsets[horzHit + 1] - mFramesetDetails.mBorderWidth; + } + else + return; + break; + default: + return; + } + // now that we have the hard ranges, reduce ranges based on minimum frame extents + VectorPtr::iterator soitr; + VectorPtr::iterator fditr = mFrameDetails.begin(); + GuiControl *gc; + S32 column = 0; + S32 row = 0; + S32 columns = mColumnOffsets.size(); + S32 rows = mRowOffsets.size(); + for (soitr = begin(); soitr != end(); soitr++, fditr++) + { + // only worry about visible frames + if (column == columns) + { + column = 0; + row++; + } + if (row == rows) + return; + gc = dynamic_cast(*soitr); + if (gc != NULL) + { + // the gui control is in a visible frame, so look at its frame details + if ((*fditr) != NULL) + { + switch (hitRegion) + { + case VERTICAL_DIVIDER: + if (column == indexes[0]) + ranges[0] = getMax(ranges[0], hardRanges[0] + (*fditr)->mMinExtent.x); + if (column == indexes[1]) + ranges[1] = getMin(ranges[1], hardRanges[1] - (*fditr)->mMinExtent.x); + break; + case HORIZONTAL_DIVIDER: + if (row == indexes[0]) + ranges[0] = getMax(ranges[0], hardRanges[0] + (*fditr)->mMinExtent.y); + if (row == indexes[1]) + ranges[1] = getMin(ranges[1], hardRanges[1] - (*fditr)->mMinExtent.y); + break; + case DIVIDER_INTERSECTION: + if (column == indexes[0]) + ranges[0] = getMax(ranges[0], hardRanges[0] + (*fditr)->mMinExtent.x); + if (column == indexes[1]) + ranges[1] = getMin(ranges[1], hardRanges[1] - (*fditr)->mMinExtent.x); + if (row == indexes[2]) + ranges[2] = getMax(ranges[2], hardRanges[2] + (*fditr)->mMinExtent.y); + if (row == indexes[3]) + ranges[3] = getMin(ranges[3], hardRanges[3] - (*fditr)->mMinExtent.y); + break; + default: + return; + } + } + column++; + } + } +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::drawDividers(const Point2I &offset) +{ + // draw the frame dividers, if they are enabled + if (mFramesetDetails.mBorderEnable != FRAME_STATE_OFF) + { + RectI r; + Vector::iterator itr; + for (itr = mColumnOffsets.begin() + 1; itr < mColumnOffsets.end(); itr++) + { + r.point = Point2I(*itr - mFramesetDetails.mBorderWidth, mFudgeFactor) + offset; + r.extent.set(mFramesetDetails.mBorderWidth, getHeight() - ( 2 * mFudgeFactor ) ); + GFX->getDrawUtil()->drawRectFill(r, mFramesetDetails.mBorderColor); + } + for (itr = mRowOffsets.begin() + 1; itr < mRowOffsets.end(); itr++) + { + r.point = Point2I(mFudgeFactor, *itr - mFramesetDetails.mBorderWidth) + offset; + r.extent.set(getWidth() - ( 2 * mFudgeFactor ), mFramesetDetails.mBorderWidth); + GFX->getDrawUtil()->drawRectFill(r, mFramesetDetails.mBorderColor); + } + } +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::frameBorderEnable(S32 index, const char *state) +{ + GuiControl *gc = NULL; + FrameDetail *fd = NULL; + if (findFrameContents(index, &gc, &fd) == true && fd != NULL) + { + if (state != NULL) + { + // find the value for the detail member + for (S32 i = 0; i < gBorderStateTable.size; i++) + { + if (dStrcmp(state, gBorderStateTable.table[i].label) == 0) + fd->mBorderEnable = gBorderStateTable.table[i].index; + } + } + else + // defaults to AUTO if NULL passed in state + fd->mBorderEnable = FRAME_STATE_AUTO; + } +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::frameBorderMovable(S32 index, const char *state) +{ + GuiControl *gc = NULL; + FrameDetail *fd = NULL; + if (findFrameContents(index, &gc, &fd) == true && fd != NULL) + { + if (state != NULL) + { + // find the value for the detail member + for (S32 i = 0; i < gBorderStateTable.size; i++) + { + if (dStrcmp(state, gBorderStateTable.table[i].label) == 0) + fd->mBorderMovable = gBorderStateTable.table[i].index; + } + } + else + // defaults to AUTO if NULL passed in state + fd->mBorderMovable = FRAME_STATE_AUTO; + } +} + +//----------------------------------------------------------------------------- +void GuiFrameSetCtrl::frameMinExtent(S32 index, const Point2I &extent) +{ + GuiControl *gc = NULL; + FrameDetail *fd = NULL; + if (findFrameContents(index, &gc, &fd) == true && fd != NULL) + fd->mMinExtent = extent; +} + +void GuiFrameSetCtrl::framePadding(S32 index, const RectSpacingI &padding) +{ + GuiControl *gc = NULL; + FrameDetail *fd = NULL; + if (findFrameContents(index, &gc, &fd) == true && fd != NULL) + fd->mPadding = padding; +} diff --git a/gui/containers/guiFrameCtrl.h b/gui/containers/guiFrameCtrl.h new file mode 100644 index 0000000..790b156 --- /dev/null +++ b/gui/containers/guiFrameCtrl.h @@ -0,0 +1,174 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// A gui control allowing a window to be subdivided into panes, +// each of which displays a gui control child of the +// GuiFrameSetCtrl. Each gui control child will have an associated +// FrameDetail through which frame-specific details can be +// assigned. Frame-specific values override the values specified +// for the entire frameset. +// +// Note that it is possible to have more children than frames, +// or more frames than children. In the former case, the extra +// children will not be visible (they are moved beyond the +// visible extent of the frameset). In the latter case, frames +// will be empty. +// +// If a frameset had two columns and two rows but only three +// gui control children they would be assigned to frames as +// follows: +// 1 | 2 +// ----- +// 3 | +// +// The last frame would be blank. +//----------------------------------------------------------------------------- + +#ifndef _GUIFRAMECTRL_H_ +#define _GUIFRAMECTRL_H_ + +#define DISABLE_COPY_CTOR(className) \ + className(const className &) + +#define DISABLE_ASSIGNMENT(className) \ + className& operator=(const className &) + +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + +// for debugging porpoises... +#define GUI_FRAME_DEBUG +// ...save the porpoises + +class GuiFrameSetCtrl : public GuiContainer +{ +private: + typedef GuiContainer Parent; +public: + enum + { + FRAME_STATE_ON, // ON overrides OFF + FRAME_STATE_OFF, // OFF overrides AUTO + FRAME_STATE_AUTO, // AUTO == ON, unless overridden + + NO_HIT = -1, + + DEFAULT_BORDER_WIDTH = 4, + DEFAULT_COLUMNS = 1, + DEFAULT_ROWS = 1, + DEFAULT_MIN_FRAME_EXTENT = 64 + }; + enum Region + { + VERTICAL_DIVIDER, + HORIZONTAL_DIVIDER, + DIVIDER_INTERSECTION, + NONE + }; + struct FrameDetail + { + U32 mBorderWidth; + ColorI mBorderColor; + S32 mBorderEnable; + S32 mBorderMovable; + Point2I mMinExtent; + RectSpacingI mPadding; + FrameDetail() { mBorderWidth = DEFAULT_BORDER_WIDTH; mBorderEnable = FRAME_STATE_AUTO; mBorderMovable = FRAME_STATE_AUTO; mMinExtent.set(DEFAULT_MIN_FRAME_EXTENT, DEFAULT_MIN_FRAME_EXTENT); mPadding.setAll( 0 ); } + }; + DECLARE_CONOBJECT(GuiFrameSetCtrl); + static void initPersistFields(); + + GuiFrameSetCtrl(); + GuiFrameSetCtrl(U32 columns, U32 rows, const U32 columnOffsets[] = NULL, const U32 rowOffsets[] = NULL); + virtual ~GuiFrameSetCtrl(); + + void addObject(SimObject *obj); + void removeObject(SimObject *obj); + + virtual bool resize(const Point2I &newPosition, const Point2I &newExtent); + + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + + bool onAdd(); + void onRender(Point2I offset, const RectI &updateRect ); +protected: + /* member variables */ + Vector mColumnOffsets; + Vector mRowOffsets; + GuiCursor *mMoveCursor; + GuiCursor *mUpDownCursor; + GuiCursor *mLeftRightCursor; + GuiCursor *mDefaultCursor; + FrameDetail mFramesetDetails; + VectorPtr mFrameDetails; + bool mAutoBalance; + S32 mFudgeFactor; + + /* divider activation member variables */ + Region mCurHitRegion; + Point2I mLocOnDivider; + S32 mCurVerticalHit; + S32 mCurHorizontalHit; + + bool init(U32 columns, U32 rows, const U32 columnOffsets[], const U32 rowOffsets[]); + + Region findHitRegion(const Point2I &point); + Region pointInAnyRegion(const Point2I &point); + S32 findResizableFrames(S32 indexes[]); + bool hitVerticalDivider(S32 x, const Point2I &point); + bool hitHorizontalDivider(S32 y, const Point2I &point); + + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + void rebalance(const Point2I &newExtent); + + void computeSizes(bool balanceFrames = false); + void computeMovableRange(Region hitRegion, S32 vertHit, S32 horzHit, S32 numIndexes, const S32 indexes[], S32 ranges[]); + + void drawDividers(const Point2I &offset); +public: + U32 columns() const { return(mColumnOffsets.size()); } + U32 rows() const { return(mRowOffsets.size()); } + U32 borderWidth() const { return(mFramesetDetails.mBorderWidth); } + Vector* columnOffsets() { return(&mColumnOffsets); } + Vector* rowOffsets() { return(&mRowOffsets); } + FrameDetail* framesetDetails() { return(&mFramesetDetails); } + + bool findFrameContents(S32 index, GuiControl **gc, FrameDetail **fd); + + void frameBorderEnable(S32 index, const char *state = NULL); + void frameBorderMovable(S32 index, const char *state = NULL); + void frameMinExtent(S32 index, const Point2I &extent); + void framePadding(S32 index, const RectSpacingI &padding); + + void balanceFrames() { computeSizes(true); } + void updateSizes() { computeSizes(); } + + bool onWake(); + +private: + DISABLE_COPY_CTOR(GuiFrameSetCtrl); + DISABLE_ASSIGNMENT(GuiFrameSetCtrl); +}; + +//----------------------------------------------------------------------------- +// x is the first value inside the next column, so the divider x-coords +// precede x. +inline bool GuiFrameSetCtrl::hitVerticalDivider(S32 x, const Point2I &point) +{ + return((point.x >= S32(x - mFramesetDetails.mBorderWidth)) && (point.x < x) && (point.y >= 0) && (point.y < S32(getHeight()))); +} + +//----------------------------------------------------------------------------- +// y is the first value inside the next row, so the divider y-coords precede y. +inline bool GuiFrameSetCtrl::hitHorizontalDivider(S32 y, const Point2I &point) +{ + return((point.x >= 0) && (point.x < S32(getWidth())) && (point.y >= S32(y - mFramesetDetails.mBorderWidth)) && (point.y < y)); +} + +#endif // _GUI_FRAME_CTRL_H diff --git a/gui/containers/guiPaneCtrl.cpp b/gui/containers/guiPaneCtrl.cpp new file mode 100644 index 0000000..450cf78 --- /dev/null +++ b/gui/containers/guiPaneCtrl.cpp @@ -0,0 +1,319 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiPaneCtrl.h" + +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiPaneControl); + +GuiPaneControl::GuiPaneControl() +{ + setMinExtent(Point2I(16,16)); + mActive = true; + mCollapsable = true; + mCollapsed = false; + mBarBehindText = true; + mMouseOver = false; + mDepressed = false; + mOriginalExtents.set(10,10); + mCaption = StringTable->insert("A Pane"); + mCaptionID = StringTable->insert(""); + mIsContainer = true; +} + +void GuiPaneControl::initPersistFields() +{ + addField("caption", TypeCaseString, Offset(mCaption, GuiPaneControl)); + addField("captionID", TypeString, Offset(mCaptionID, GuiPaneControl)); + addField("collapsable", TypeBool, Offset(mCollapsable, GuiPaneControl)); + addField("barBehindText", TypeBool, Offset(mBarBehindText, GuiPaneControl)); + + Parent::initPersistFields(); +} + +bool GuiPaneControl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + mFont = mProfile->mFont; + AssertFatal(mFont, "GuiPaneControl::onWake: invalid font in profile" ); + if(mCaptionID && *mCaptionID != 0) + { + setCaptionID(mCaptionID); + } + + mProfile->constructBitmapArray(); + + if(mProfile->mUseBitmapArray && mProfile->mBitmapArrayRects.size()) + { + mThumbSize.set( mProfile->mBitmapArrayRects[0].extent.x, mProfile->mBitmapArrayRects[0].extent.y ); + mThumbSize.setMax( mProfile->mBitmapArrayRects[1].extent ); + + if(mFont->getHeight() > mThumbSize.y) + mThumbSize.y = mFont->getHeight(); + } + else + { + mThumbSize.set(20, 20); + } + + return true; +} + +void GuiPaneControl::onSleep() +{ + Parent::onSleep(); + mFont = NULL; +} + +void GuiPaneControl::setCaptionID(const char *id) +{ + S32 n = Con::getIntVariable(id, -1); + if(n != -1) + { + mCaptionID = StringTable->insert(id); + setCaptionID(n); + } +} + +void GuiPaneControl::setCaptionID(S32 id) +{ + const UTF8 *str = getGUIString(id); + if(str) + mCaption = StringTable->insert((const char*)str); +} + +bool GuiPaneControl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + + // CodeReview WTF is going on here that we need to bypass parent sanity? + // Investigate this [7/1/2007 justind] + if( !Parent::resize( newPosition, newExtent ) ) + return false; + + mOriginalExtents.x = getWidth(); + + /* + GuiControl *parent = getParent(); + if (parent) + parent->childResized(this); + setUpdate(); + */ + + // Resize the child control if we're not collapsed + if(size() && !mCollapsed) + { + GuiControl *gc = dynamic_cast(operator[](0)); + + if(gc) + { + Point2I offset(0, mThumbSize.y); + + gc->resize(offset, newExtent - offset); + } + } + + // For now. + return true; +} + +void GuiPaneControl::onRender(Point2I offset, const RectI &updateRect) +{ + // Render our awesome little doogong + if(mProfile->mBitmapArrayRects.size() >= 2 && mCollapsable) + { + S32 idx = mCollapsed ? 0 : 1; + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretchSR( + mProfile->mTextureObject, + RectI(offset, mProfile->mBitmapArrayRects[idx].extent), + mProfile->mBitmapArrayRects[idx] + ); + + } + + S32 textWidth = 0; + + if(!mBarBehindText) + { + GFX->getDrawUtil()->setBitmapModulation((mMouseOver ? mProfile->mFontColorHL : mProfile->mFontColor)); + textWidth = GFX->getDrawUtil()->drawText( + mFont, + Point2I(mThumbSize.x, 0) + offset, + mCaption, + mProfile->mFontColors + ); + } + + + // Draw our little bar, too + if(mProfile->mBitmapArrayRects.size() >= 5) + { + GFX->getDrawUtil()->clearBitmapModulation(); + + S32 barStart = mThumbSize.x + offset.x + textWidth; + S32 barTop = mThumbSize.y/2 + offset.y - mProfile->mBitmapArrayRects[3].extent.y /2; + + Point2I barOffset(barStart, barTop); + + // Draw the start of the bar... + GFX->getDrawUtil()->drawBitmapStretchSR( + mProfile->mTextureObject, + RectI(barOffset, mProfile->mBitmapArrayRects[2].extent), + mProfile->mBitmapArrayRects[2] + ); + + // Now draw the middle... + barOffset.x += mProfile->mBitmapArrayRects[2].extent.x; + + S32 barMiddleSize = (getExtent().x - (barOffset.x - offset.x)) - mProfile->mBitmapArrayRects[4].extent.x; + + if(barMiddleSize>0) + { + // We have to do this inset to prevent nasty stretching artifacts + RectI foo = mProfile->mBitmapArrayRects[3]; + foo.inset(1,0); + + GFX->getDrawUtil()->drawBitmapStretchSR( + mProfile->mTextureObject, + RectI(barOffset, Point2I(barMiddleSize, mProfile->mBitmapArrayRects[3].extent.y)), + foo + ); + } + + // And the end + barOffset.x += barMiddleSize; + + GFX->getDrawUtil()->drawBitmapStretchSR( + mProfile->mTextureObject, + RectI(barOffset, mProfile->mBitmapArrayRects[4].extent), + mProfile->mBitmapArrayRects[4] + ); + } + + if(mBarBehindText) + { + GFX->getDrawUtil()->setBitmapModulation((mMouseOver ? mProfile->mFontColorHL : mProfile->mFontColor)); + GFX->getDrawUtil()->drawText( + mFont, + Point2I(mThumbSize.x, 0) + offset, + mCaption, + mProfile->mFontColors + ); + } + + // Draw child controls if appropriate + if(!mCollapsed) + renderChildControls(offset, updateRect); +} + +ConsoleMethod(GuiPaneControl, setCollapsed, void, 3, 3, "(bool)") +{ + object->setCollapsed(dAtob(argv[2])); +} + +void GuiPaneControl::setCollapsed(bool isCollapsed) +{ + // Get the child + if(size() == 0 || !mCollapsable) return; + + GuiControl *gc = dynamic_cast(operator[](0)); + + if(mCollapsed && !isCollapsed) + { + mCollapsed = false; + + resize(getPosition(), mOriginalExtents); + + if(gc) + gc->setVisible(true); + } + else if(!mCollapsed && isCollapsed) + { + mCollapsed = true; + + mOriginalExtents = getExtent(); + resize(getPosition(), Point2I(getExtent().x, mThumbSize.y)); + + if(gc) + gc->setVisible(false); + } +} + +void GuiPaneControl::onMouseMove(const GuiEvent &event) +{ + Point2I localMove = globalToLocalCoord(event.mousePoint); + + // If we're clicking in the header then resize + mMouseOver = (localMove.y < mThumbSize.y); + if(isMouseLocked()) + mDepressed = mMouseOver; + +} + +void GuiPaneControl::onMouseEnter(const GuiEvent &event) +{ + setUpdate(); + if(isMouseLocked()) + { + mDepressed = true; + mMouseOver = true; + } + else + { + mMouseOver = true; + } + +} + +void GuiPaneControl::onMouseLeave(const GuiEvent &event) +{ + setUpdate(); + if(isMouseLocked()) + mDepressed = false; + mMouseOver = false; +} + +void GuiPaneControl::onMouseDown(const GuiEvent &event) +{ + if(!mCollapsable) + return; + + Point2I localClick = globalToLocalCoord(event.mousePoint); + + // If we're clicking in the header then resize + if(localClick.y < mThumbSize.y) + { + mouseLock(); + mDepressed = true; + + //update + setUpdate(); + } +} + +void GuiPaneControl::onMouseUp(const GuiEvent &event) +{ + // Make sure we only get events we ought to be getting... + if (! mActive) + return; + + if(!mCollapsable) + return; + + mouseUnlock(); + setUpdate(); + + Point2I localClick = globalToLocalCoord(event.mousePoint); + + // If we're clicking in the header then resize + if(localClick.y < mThumbSize.y && mDepressed) + setCollapsed(!mCollapsed); +} \ No newline at end of file diff --git a/gui/containers/guiPaneCtrl.h b/gui/containers/guiPaneCtrl.h new file mode 100644 index 0000000..b3fe6c9 --- /dev/null +++ b/gui/containers/guiPaneCtrl.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPANECTRL_H_ +#define _GUIPANECTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#include "gfx/gfxDevice.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +/// Collapsable pane control. +/// +/// This class wraps a single child control and displays a header with caption +/// above it. If you click the header it will collapse or expand. The control +/// resizes itself based on its collapsed/expanded size. +/// +/// In the GUI editor, if you just want the header you can make collapsable +/// false. The caption field lets you set the caption. It expects a bitmap +/// (from the GuiControlProfile) that contains two images - the first is +/// displayed when the control is expanded and the second is displayed when +/// it is collapsed. The header is sized based off of the first image. +class GuiPaneControl : public GuiControl +{ +private: + typedef GuiControl Parent; + + Resource mFont; + bool mCollapsable; + bool mCollapsed; + bool mBarBehindText; + Point2I mOriginalExtents; + StringTableEntry mCaption; + StringTableEntry mCaptionID; + Point2I mThumbSize; + + bool mMouseOver; + bool mDepressed; + +public: + GuiPaneControl(); + + bool resize(const Point2I &newPosition, const Point2I &newExtent); + void onRender(Point2I offset, const RectI &updateRect); + + bool getCollapsed() { return mCollapsed; }; + void setCollapsed(bool isCollapsed); + + bool onWake(); + void onSleep(); + + virtual void setCaptionID(S32 id); + virtual void setCaptionID(const char *id); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void onMouseEnter(const GuiEvent &event); + + static void initPersistFields(); + DECLARE_CONOBJECT(GuiPaneControl); + DECLARE_CATEGORY( "Gui Containers" ); +}; + +#endif \ No newline at end of file diff --git a/gui/containers/guiPanel.cpp b/gui/containers/guiPanel.cpp new file mode 100644 index 0000000..2c56e51 --- /dev/null +++ b/gui/containers/guiPanel.cpp @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiPanel.h" + +#include "console/consoleTypes.h" +#include "gfx/primBuilder.h" + + +//----------------------------------------------------------------------------- +// GuiPanel +//----------------------------------------------------------------------------- + +GuiPanel::GuiPanel() +{ + setMinExtent( Point2I( 16,16 ) ); + setDocking( Docking::dockNone ); +} + +GuiPanel::~GuiPanel() +{ +} + +IMPLEMENT_CONOBJECT(GuiPanel); + + +void GuiPanel::onRender(Point2I offset, const RectI &updateRect) +{ + if ( !mProfile->mOpaque ) + { + RectI ctrlRect = getClientRect(); + ctrlRect.point += offset; + + // Draw a gradient left to right + PrimBuild::begin( GFXTriangleStrip, 4 ); + PrimBuild::color( mProfile->mFillColorHL ); + PrimBuild::vertex2i( ctrlRect.point.x, ctrlRect.point.y ); + PrimBuild::vertex2i( ctrlRect.point.x, ctrlRect.point.y + ctrlRect.extent.y ); + + PrimBuild::color( mProfile->mFillColor ); + PrimBuild::vertex2i( ctrlRect.point.x + ctrlRect.extent.x, ctrlRect.point.y); + PrimBuild::vertex2i( ctrlRect.point.x + ctrlRect.extent.x, ctrlRect.point.y + ctrlRect.extent.y ); + PrimBuild::end(); + } + + Parent::onRender( offset, updateRect ); +} \ No newline at end of file diff --git a/gui/containers/guiPanel.h b/gui/containers/guiPanel.h new file mode 100644 index 0000000..e0e9a63 --- /dev/null +++ b/gui/containers/guiPanel.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUI_PANEL_H_ +#define _GUI_PANEL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _GUITICKCTRL_H_ +#include "gui/shiny/guiTickCtrl.h" +#endif +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + + +/// The GuiPanel panel is a container that when opaque will +/// draw a left to right gradient using its profile fill and +/// fill highlight colors. +/// +/// @addtogroup gui_container_group Containers +/// +/// @ingroup gui_group Gui System +/// @{ +class GuiPanel : public GuiContainer +{ +private: + typedef GuiContainer Parent; + +public: + // Constructor/Destructor/ConObject Declaration + GuiPanel(); + virtual ~GuiPanel(); + + DECLARE_CONOBJECT(GuiPanel); + + // GuiControl + void onRender(Point2I offset, const RectI &updateRect); + void setVisible(bool value) { Parent::setVisible(value); setUpdateLayout( updateParent ); } +}; +/// @} + +#endif // _GUI_PANEL_H_ \ No newline at end of file diff --git a/gui/containers/guiRolloutCtrl.cpp b/gui/containers/guiRolloutCtrl.cpp new file mode 100644 index 0000000..1b16f78 --- /dev/null +++ b/gui/containers/guiRolloutCtrl.cpp @@ -0,0 +1,491 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/containers/guiRolloutCtrl.h" +#include "gui/containers/guiScrollCtrl.h" +#include "gfx/gfxDrawUtil.h" + +//----------------------------------------------------------------------------- +// GuiRolloutCtrl +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiRolloutCtrl); + +GuiRolloutCtrl::GuiRolloutCtrl() +{ + mExpanded.set(0,0,200,60); + mCaption = StringTable->insert(""); + mIsExpanded = true; + mIsAnimating = false; + mCollapsing = false; + mAnimateDestHeight = 40; + mAnimateStep = 1; + mDefaultHeight = 40; + mHideHeader = false; + mMargin.set( 0, 0, 0, 0 ); + mIsContainer = true; + mCanCollapse = true; + // Make sure we receive our ticks. + setProcessTicks(); +} + +GuiRolloutCtrl::~GuiRolloutCtrl() +{ +} + +//----------------------------------------------------------------------------- +// Persistence +//----------------------------------------------------------------------------- +void GuiRolloutCtrl::initPersistFields() +{ + addField( "Caption", TypeCaseString, Offset(mCaption, GuiRolloutCtrl) ); + addField( "Margin", TypeRectI, Offset(mMargin, GuiRolloutCtrl) ); + addField( "DefaultHeight", TypeS32, Offset(mDefaultHeight, GuiRolloutCtrl) ); + addProtectedField( "Expanded", TypeBool, Offset( mIsExpanded, GuiRolloutCtrl), &setExpanded, &defaultProtectedGetFn, "" ); + addField("ClickCollapse", TypeBool, Offset(mCanCollapse, GuiRolloutCtrl)); + addField("HideHeader", TypeBool, Offset(mHideHeader, GuiRolloutCtrl)); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// Scene Events +//----------------------------------------------------------------------------- +bool GuiRolloutCtrl::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + mHasTexture = ( mProfile ? mProfile->constructBitmapArray() > 0 : false ); + if ( mHasTexture ) + mBitmapBounds = mProfile->mBitmapArrayRects.address(); + + // Calculate Heights for this control + calculateHeights(); + + return true; +} + +bool GuiRolloutCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + if( !mIsAnimating && mIsExpanded ) + sizeToContents(); + + return true; +} + +void GuiRolloutCtrl::addObject( SimObject *obj ) +{ + // Call Parent. + Parent::addObject( obj ); + + sizeToContents(); +} + +void GuiRolloutCtrl::removeObject( SimObject *obj ) +{ + // Call Parent. + Parent::removeObject( obj ); + + // Recalculate our rectangles. + calculateHeights(); +} + +//----------------------------------------------------------------------------- +// Mouse Events +//----------------------------------------------------------------------------- +void GuiRolloutCtrl::onMouseDown( const GuiEvent &event ) +{ + Point2I localPoint = globalToLocalCoord( event.mousePoint ); + mouseLock(); +} + +void GuiRolloutCtrl::onMouseUp( const GuiEvent &event ) +{ + Point2I localPoint = globalToLocalCoord( event.mousePoint ); + if( mCanCollapse && mHeader.pointInRect( localPoint ) && !mIsAnimating && isMouseLocked() ) + { + // If Ctrl/Cmd-clicking a header, collapse all sibling GuiRolloutCtrls. + + if( event.modifier & SI_PRIMARY_CTRL ) + { + for( SimSet::iterator iter = getParent()->begin(); iter != getParent()->end(); ++ iter ) + { + GuiRolloutCtrl* ctrl = dynamic_cast< GuiRolloutCtrl* >( *iter ); + if( ctrl && ctrl != this && ctrl->mCanCollapse ) + ctrl->instantCollapse(); + } + + if( !mIsExpanded ) + expand(); + } + else + { + // Toggle expansion. + + toggleExpanded( false ); + } + } + + if( isMouseLocked() ) + mouseUnlock(); +} + +//----------------------------------------------------------------------------- +// Control Sizing Helpers +//----------------------------------------------------------------------------- +void GuiRolloutCtrl::calculateHeights() +{ + S32 barHeight = 20; + + if ( mHasTexture && mProfile && mProfile->mBitmapArrayRects.size() >= NumBitmaps ) + { + // Store Header Rectangle + mHeader.set( 0, 0, getWidth(), mProfile->mBitmapArrayRects[ CollapsedCenter ].extent.y ); + + // Bottom Bar Max + barHeight = mProfile->mBitmapArrayRects[ TopLeftHeader ].extent.y; + } + else + { + mHeader.set( 0, 0, getWidth(), barHeight ); + } + + if ( mHideHeader ) + { + barHeight = 0; + mHeader.extent.y = 0; + } + + GuiControl *content = static_cast( at(0) ); + if ( content != NULL ) + mExpanded.set( 0, 0, getWidth(), barHeight + content->getHeight() + ( mMargin.point.y + mMargin.extent.y ) ); + else + mExpanded.set( 0, 0, getWidth(), barHeight + mDefaultHeight ); +} + +bool GuiRolloutCtrl::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + if ( !Parent::resize( newPosition, newExtent ) ) + return false; + + // Recalculate Heights and resize ourself appropriately. + calculateHeights(); + + GuiControl *content = dynamic_cast( at(0) ); + + // Size Content Properly?! + if ( mNotifyChildrenResized && content != NULL ) + { + S32 barHeight = ( mHideHeader ) ? 0 : 20; + if ( !mHideHeader && mHasTexture && mProfile && mProfile->mBitmapArrayRects.size() >= NumBitmaps ) + { + barHeight = mProfile->mBitmapArrayRects[ TopLeftHeader ].extent.y; + } + + mChildRect.set( mMargin.point.x, + mHeader.extent.y + mMargin.point.y, + getWidth() - ( mMargin.point.x + mMargin.extent.x ), + getHeight() - ( barHeight + ( mMargin.point.y + mMargin.extent.y ) ) ); + + if ( content->resize( mChildRect.point, mChildRect.extent ) ) + return true; + } + + // Nothing sized + return false; +} + + +void GuiRolloutCtrl::sizeToContents() +{ + calculateHeights(); + + // Set destination height + if ( size() > 0 ) + instantExpand(); + else + instantCollapse(); +} + +void GuiRolloutCtrl::instantExpand() +{ + mAnimateDestHeight = mExpanded.extent.y; + mCollapsing = false; + mIsExpanded = true; + mIsAnimating = false; + resize( getPosition() + mExpanded.point, mExpanded.extent ); + + if( isMethod( "onExpanded" ) ) + Con::executef( this, "onExpanded" ); +} + +void GuiRolloutCtrl::instantCollapse() +{ + mAnimateDestHeight = mHeader.extent.y; + mCollapsing = false; + mIsExpanded = false; + mIsAnimating = false; + resize( getPosition() + mHeader.point, mHeader.extent ); + + if( isMethod( "onCollapsed" ) ) + Con::executef( this, "onCollapsed" ); +} + +void GuiRolloutCtrl::toggleExpanded( bool instant ) +{ + if ( mIsExpanded ) + { + if ( instant ) + instantCollapse(); + else + collapse(); + } + else + { + if ( instant ) + instantExpand(); + else + expand(); + } +} + +void GuiRolloutCtrl::childResized( GuiControl *child ) +{ + Parent::childResized( child ); + + calculateHeights(); + + // While we are animating we are constantly resizing our children + // and therefore need to ignore this call to 'instantExpand' which would + // halt the animation in some crappy intermediate stage. + if ( mIsExpanded && !mIsAnimating ) + { + mNotifyChildrenResized = false; + instantExpand(); + mNotifyChildrenResized = true; + } +} + +//----------------------------------------------------------------------------- +// Control Sizing Animation Functions +//----------------------------------------------------------------------------- +void GuiRolloutCtrl::animateTo( S32 height ) +{ + // We do nothing if we're already animating + if( mIsAnimating ) + return; + + bool collapsing = (bool)( getHeight() > height ); + + // If we're already at the destination height, bail + if ( getHeight() >= height && !collapsing ) + { + mIsExpanded = true; + return; + } + + // If we're already at the destination height, bail + if ( getHeight() <= height && collapsing ) + { + mIsExpanded = false; + return; + } + + // Set destination height + mAnimateDestHeight = height; + + // Set Animation Mode + mCollapsing = collapsing; + + // Set Animation Step (Increment) + if ( collapsing ) + mAnimateStep = (S32)mFloor( (F32)( getHeight() - height ) / 3.f ); + else + mAnimateStep = (S32)mFloor( (F32)( height - getHeight() ) / 3.f ); + + // Start our animation + mIsAnimating = true; +} + +void GuiRolloutCtrl::processTick() +{ + // We do nothing here if we're NOT animating + if ( !mIsAnimating ) + return; + + // Sanity check to fix non collapsing panels. + if ( mAnimateStep == 0 ) + mAnimateStep = 1; + + S32 newHeight = getHeight(); + // We're collapsing ourself down (Hiding our contents) + if( mCollapsing ) + { + if ( newHeight < mAnimateDestHeight ) + newHeight = mAnimateDestHeight; + else if ( ( newHeight - mAnimateStep ) < mAnimateDestHeight ) + newHeight = mAnimateDestHeight; + + if ( newHeight == mAnimateDestHeight ) + mIsAnimating = false; + else + newHeight -= mAnimateStep; + + if( !mIsAnimating ) + { + mIsExpanded = false; + } + } + else // We're expanding ourself (Showing our contents) + { + if ( newHeight > mAnimateDestHeight ) + newHeight = mAnimateDestHeight; + else if ( ( newHeight + mAnimateStep ) > mAnimateDestHeight ) + newHeight = mAnimateDestHeight; + + if ( newHeight == mAnimateDestHeight ) + mIsAnimating = false; + else + newHeight += mAnimateStep; + + if ( !mIsAnimating ) + mIsExpanded = true; + } + + if ( newHeight != getHeight() ) + setHeight( newHeight ); + + if ( !mIsAnimating ) + { + if( mCollapsing && isMethod( "onCollapsed" ) ) + Con::executef( this, "onCollapsed" ); + else if( !mCollapsing && isMethod( "onExpanded" ) ) + Con::executef( this, "onExpanded" ); + + calculateHeights(); + } + + GuiControl* parent = getParent(); + if ( parent ) + { + parent->childResized( this ); + // if our parent's parent is a scroll control, scrollvisible. + GuiScrollCtrl* scroll = dynamic_cast( parent->getParent() ); + if ( scroll ) + { + scroll->scrollRectVisible( getBounds() ); + } + } +} + +//----------------------------------------------------------------------------- +// Control Rendering +//----------------------------------------------------------------------------- +void GuiRolloutCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + if( !mProfile || mProfile->mFont == NULL ) + return; + + // Calculate actual world bounds for rendering + RectI worldBounds( offset, getExtent() ); + + // if opaque, fill the update rect with the fill color + if ( mProfile->mOpaque ) + GFX->getDrawUtil()->drawRectFill( worldBounds, mProfile->mFillColor ); + + if ( mProfile->mBitmapArrayRects.size() >= NumBitmaps ) + { + GFX->getDrawUtil()->clearBitmapModulation(); + + // Draw Rollout From Skin + if ( !mIsExpanded && !mIsAnimating ) + renderFixedBitmapBordersFilled( worldBounds, 1, mProfile ); + else if ( mHideHeader ) + renderSizableBitmapBordersFilledIndex( worldBounds, MidPageLeft, mProfile ); + else + renderSizableBitmapBordersFilledIndex( worldBounds, TopLeftHeader, mProfile ); + } + + if ( !(mIsExpanded && mHideHeader ) ) + { + // Draw Caption ( Vertically Centered ) + ColorI currColor; + GFX->getDrawUtil()->getBitmapModulation( &currColor ); + Point2I textPosition = mHeader.point + offset + mProfile->mTextOffset; + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor ); + renderJustifiedText( textPosition, mHeader.extent, mCaption ); + GFX->getDrawUtil()->setBitmapModulation( currColor ); + } + + // If we're collapsed we contain the first child as our content + // thus we don't render it when collapsed. but to support modified + // rollouts with custom header buttons etc we still render our other + // children. -JDD + GuiControl *pChild = dynamic_cast( at(0) ); + if ( pChild ) + { + if ( !mIsExpanded && !mIsAnimating && pChild->isVisible() ) + { + pChild->setVisible( false ); + } + else if ( (mIsExpanded || mIsAnimating) && !pChild->isVisible() ) + { + pChild->setVisible( true ); + } + } + renderChildControls( offset, updateRect ); + + // Render our border should we have it specified in our profile. + renderBorder(worldBounds, mProfile); +} + +//----------------------------------------------------------------------------- +// Console +//----------------------------------------------------------------------------- +ConsoleMethod( GuiRolloutCtrl, isExpanded, bool, 2, 2, "isExpanded(); - returns true/false") +{ + return object->isExpanded(); +} + +ConsoleMethod( GuiRolloutCtrl, collapse, void, 2, 2, "%rollout.collapse();") +{ + object->collapse(); +} + +ConsoleMethod( GuiRolloutCtrl, expand, void, 2, 2, "%rollout.expand();") +{ + object->expand(); +} + +ConsoleMethod( GuiRolloutCtrl, toggleCollapse, void, 2, 2, "%rollout.toggle();") +{ + if( object->isExpanded() ) + object->collapse(); + else + object->expand(); +} + +ConsoleMethod( GuiRolloutCtrl, instantCollapse, void, 2, 2, "%rollout.collapse();") +{ + object->instantCollapse(); +} + +ConsoleMethod( GuiRolloutCtrl, instantExpand, void, 2, 2, "%rollout.expand();") +{ + object->instantExpand(); +} + +ConsoleMethod( GuiRolloutCtrl, toggleExpanded, void, 2, 3, "toggleExpanded( bool instant )" ) +{ + object->toggleExpanded( dAtob(argv[2]) ); +} + +ConsoleMethod( GuiRolloutCtrl, sizeToContents, void, 2, 2, "%rollout.sizeToContents()") +{ + object->sizeToContents(); +} diff --git a/gui/containers/guiRolloutCtrl.h b/gui/containers/guiRolloutCtrl.h new file mode 100644 index 0000000..d2c41e3 --- /dev/null +++ b/gui/containers/guiRolloutCtrl.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUI_ROLLOUTCTRL_H_ +#define _GUI_ROLLOUTCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _GUISTACKCTRL_H_ +#include "gui/containers/guiStackCtrl.h" +#endif +#ifndef _H_GUIDEFAULTCONTROLRENDER_ +#include "gui/core/guiDefaultControlRender.h" +#endif +#ifndef _GUITICKCTRL_H_ +#include "gui/shiny/guiTickCtrl.h" +#endif + + +class GuiRolloutCtrl : public GuiTickCtrl +{ +private: + typedef GuiControl Parent; +public: + // Members + StringTableEntry mCaption; + RectI mHeader; + RectI mExpanded; + RectI mChildRect; + RectI mMargin; + bool mIsExpanded; + bool mIsAnimating; + bool mCollapsing; + S32 mAnimateDestHeight; + S32 mAnimateStep; + S32 mDefaultHeight; + bool mCanCollapse; + bool mHideHeader; + + GuiCursor* mDefaultCursor; + GuiCursor* mVertSizingCursor; + + + // Theme Support + enum + { + CollapsedLeft = 0, + CollapsedCenter, + CollapsedRight, + TopLeftHeader, + TopMidHeader, + TopRightHeader, + MidPageLeft, + MidPageCenter, + MidPageRight, + BottomLeftHeader, + BottomMidHeader, + BottomRightHeader, + NumBitmaps ///< Number of bitmaps in this array + }; + bool mHasTexture; ///< Indicates whether we have a texture to render the tabs with + RectI *mBitmapBounds;///< Array of rectangles identifying textures for tab book + + // Constructor/Destructor/Conobject Declaration + GuiRolloutCtrl(); + ~GuiRolloutCtrl(); + DECLARE_CONOBJECT(GuiRolloutCtrl); + DECLARE_CATEGORY( "Gui Containers" ); + + // Persistence + static void initPersistFields(); + + // Control Events + bool onWake(); + void addObject(SimObject *obj); + void removeObject(SimObject *obj); + virtual void childResized(GuiControl *child); + + // Mouse Events + virtual void onMouseDown( const GuiEvent &event ); + virtual void onMouseUp( const GuiEvent &event ); + + // Sizing Helpers + virtual void calculateHeights(); + virtual bool resize( const Point2I &newPosition, const Point2I &newExtent ); + virtual void sizeToContents(); + inline bool isExpanded() const { return mIsExpanded; } + + // Sizing Animation Functions + void animateTo( S32 height ); + virtual void processTick(); + + + void collapse() { animateTo( mHeader.extent.y ); } + void expand() { animateTo( mExpanded.extent.y ); } + void instantCollapse(); + void instantExpand(); + void toggleExpanded( bool instant ); + + // Property - "Expanded" + static bool setExpanded(void* obj, const char* data) + { + bool expand = dAtob( data ); + if( expand ) + static_cast(obj)->instantExpand(); + else + static_cast(obj)->instantCollapse(); + return false; + }; + + + // Control Rendering + virtual void onRender(Point2I offset, const RectI &updateRect); + bool onAdd(); + +}; + +#endif // _GUI_ROLLOUTCTRL_H_ \ No newline at end of file diff --git a/gui/containers/guiScrollCtrl.cpp b/gui/containers/guiScrollCtrl.cpp new file mode 100644 index 0000000..a3014ff --- /dev/null +++ b/gui/containers/guiScrollCtrl.cpp @@ -0,0 +1,1258 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiScrollCtrl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gfx/bitmap/gBitmap.h" +#include "platform/event.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiCanvas.h" + + +IMPLEMENT_CONOBJECT(GuiScrollCtrl); + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +GuiScrollCtrl::GuiScrollCtrl() + : mChildMargin( 0, 0 ), + mBorderThickness( 1 ), + mScrollBarThickness( 16 ), + mScrollBarArrowBtnLength( 16 ), + mScrollBarDragTolerance( 130 ), + mStateDepressed( false ), + mHitRegion( None ), + mWillFirstRespond( true ), + mUseConstantHeightThumb( false ), + mForceVScrollBar( ScrollBarAlwaysOn ), + mForceHScrollBar( ScrollBarAlwaysOn ), + mLockHorizScroll( false ), + mLockVertScroll( false ), + mIgnoreChildResized( false ), + mAnimating( false ), + mScrollAnimSpeed( -1 ), + mScrollTargetPos( -1, -1 ), + mChildExt(0, 0), + mChildPos(0, 0) +{ + mIsContainer = true; + setExtent(200,200); +} + +static const EnumTable::Enums scrollBarEnums[] = +{ + { GuiScrollCtrl::ScrollBarAlwaysOn, "alwaysOn" }, + { GuiScrollCtrl::ScrollBarAlwaysOff, "alwaysOff" }, + { GuiScrollCtrl::ScrollBarDynamic, "dynamic" }, +}; +static const EnumTable gScrollBarTable(3, &scrollBarEnums[0]); + +ConsoleMethod(GuiScrollCtrl, scrollToTop, void, 2, 2, "() - scrolls the scroll control to the top of the child content area.") +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + + object->scrollTo( 0, 0 ); +} + +ConsoleMethod(GuiScrollCtrl, scrollToBottom, void, 2, 2, "() - scrolls the scroll control to the bottom of the child content area.") +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + + object->scrollTo( 0, 0x7FFFFFFF ); +} + +ConsoleMethod(GuiScrollCtrl, setScrollPosition, void, 4, 4, "(x, y) - scrolls the scroll control to the specified position.") +{ + object->scrollTo(dAtoi(argv[2]), dAtoi(argv[3])); +} + +ConsoleMethod(GuiScrollCtrl, scrollToObject, void, 3, 3, "(obj) - scrolls the scroll control to view the specified object.") +{ + GuiControl* targetControl = dynamic_cast(Sim::findObject(argv[2])); + if (targetControl) + { + object->scrollToObject(targetControl); + } + else + Con::errorf("GuiScrollCtrl::scrollToObject() - Invalid control specified (%s)!", argv[2]); +} + +ConsoleMethod(GuiScrollCtrl, getScrollPositionX, S32, 2, 2, "() - get the current x scroll position of the scroll control.") +{ + return object->getChildRelPos().x; +} + +ConsoleMethod(GuiScrollCtrl, getScrollPositionY, S32, 2, 2, "() - get the current y scroll position of the scroll control.") +{ + return object->getChildRelPos().y; +} + +ConsoleMethod(GuiScrollCtrl, computeSizes, void, 2, 2, "() - Refresh the sizes of the child objects.") +{ + object->computeSizes(); +} + +void GuiScrollCtrl::initPersistFields() +{ + addField("willFirstRespond", TypeBool, Offset(mWillFirstRespond, GuiScrollCtrl)); + addField("hScrollBar", TypeEnum, Offset(mForceHScrollBar, GuiScrollCtrl), 1, &gScrollBarTable, "When to display the horizontal scrollbar."); + addField("vScrollBar", TypeEnum, Offset(mForceVScrollBar, GuiScrollCtrl), 1, &gScrollBarTable, "When to display the vertical scrollbar."); + addField("lockHorizScroll", TypeBool, Offset(mLockHorizScroll, GuiScrollCtrl), "Horizontal scrolling not allowed if set."); + addField("lockVertScroll", TypeBool, Offset(mLockVertScroll, GuiScrollCtrl), "Vertical scrolling not allowed if set."); + addField("constantThumbHeight", TypeBool, Offset(mUseConstantHeightThumb, GuiScrollCtrl)); + addField("childMargin", TypePoint2I, Offset(mChildMargin, GuiScrollCtrl)); + addField("mouseWheelScrollSpeed", TypeS32, Offset(mScrollAnimSpeed, GuiScrollCtrl), "Pixels/Tick - if not positive then mousewheel scrolling occurs instantly (like other scrolling)."); + + Parent::initPersistFields(); +} + +bool GuiScrollCtrl::resize(const Point2I &newPos, const Point2I &newExt) +{ + if( !Parent::resize(newPos, newExt) ) + return false; + + computeSizes(); + return true; +} + +void GuiScrollCtrl::childResized(GuiControl *child) +{ + if ( mIgnoreChildResized ) + return; + + Parent::childResized(child); + computeSizes(); +} + +bool GuiScrollCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + mTextureObject = mProfile->mTextureObject; + if (mTextureObject && (mProfile->constructBitmapArray() >= BmpStates * BmpCount)) + { + mBitmapBounds = mProfile->mBitmapArrayRects.address(); + + //init + mBaseThumbSize = mBitmapBounds[BmpStates * BmpVThumbTopCap].extent.y + + mBitmapBounds[BmpStates * BmpVThumbBottomCap].extent.y; + mScrollBarThickness = mBitmapBounds[BmpStates * BmpVPage].extent.x; + mScrollBarArrowBtnLength = mBitmapBounds[BmpStates * BmpUp].extent.y; + computeSizes(); + } + else + { + Con::warnf("No texture loaded for scroll control named %s with profile %s", getName(), mProfile->getName()); + } + return true; +} + +void GuiScrollCtrl::onSleep() +{ + // Reset the mouse tracking state of this control + // when it is put to sleep + mStateDepressed = false; + mHitRegion = None; + + Parent::onSleep(); + mTextureObject = NULL; +} + +bool GuiScrollCtrl::calcChildExtents() +{ + // scroll control should deal well with multiple gui controls + if (!size()) + return false; + + // Get the greatest position and extent points so the scroll bars will function function + // correctly with multiple gui controls. We are grabbing the current values of ext and pos + // to utilize later. + Point2I extApex = mChildExt; + Point2I posApex = mChildPos; + + // This will be our check in order to fail out if we need to + bool hasChildren(false); + + for ( U32 i = 0; i < size(); i++ ) + { + GuiControl *ctrl = (GuiControl*)at(i); + if ( ctrl->isVisible() ) + { + // Always grab the initial values + if ( !hasChildren ) + { + extApex = ctrl->getExtent(); + posApex = ctrl->getPosition(); + hasChildren = true; + } + else + { + // Now we can do continuous checks on those values + if( extApex.x < ctrl->getExtent().x ) + extApex.x = ctrl->getExtent().x; + + if( extApex.y < ctrl->getExtent().y ) + extApex.y = ctrl->getExtent().y; + + if( posApex.x < ctrl->getPosition().x ) + posApex.x = ctrl->getPosition().x; + + if( posApex.y < ctrl->getPosition().y ) + posApex.y = ctrl->getPosition().y; + } + } + } + + if( hasChildren ) + { + mChildExt.x = extApex.x; + mChildExt.y = extApex.y; + + mChildPos.x = posApex.x; + mChildPos.y = posApex.y; + + return true; + } + else + return false; +} + +RectI lastVisRect; + +void GuiScrollCtrl::scrollRectVisible(RectI rect) +{ + // rect is passed in virtual client space + if(rect.extent.x > mContentExt.x) + rect.extent.x = mContentExt.x; + if(rect.extent.y > mContentExt.y) + rect.extent.y = mContentExt.y; + + // Determine the points bounding the requested rectangle + Point2I rectUpperLeft = rect.point; + Point2I rectLowerRight = rect.point + rect.extent; + + lastVisRect = rect; + + // Determine the points bounding the actual visible area... + Point2I visUpperLeft = mChildRelPos; + Point2I visLowerRight = mChildRelPos + mContentExt; + Point2I delta(0,0); + + // We basically try to make sure that first the top left of the given + // rect is visible, and if it is, then that the bottom right is visible. + + // Make sure the rectangle is visible along the X axis... + if(rectUpperLeft.x < visUpperLeft.x) + delta.x = rectUpperLeft.x - visUpperLeft.x; + else if(rectLowerRight.x > visLowerRight.x) + delta.x = rectLowerRight.x - visLowerRight.x; + + // Make sure the rectangle is visible along the Y axis... + if(rectUpperLeft.y < visUpperLeft.y) + delta.y = rectUpperLeft.y - visUpperLeft.y; + else if(rectLowerRight.y > visLowerRight.y) + delta.y = rectLowerRight.y - visLowerRight.y; + + // If we had any changes, scroll, otherwise don't. + if(delta.x || delta.y) + scrollDelta(delta.x, delta.y); +} + +bool GuiScrollCtrl::isRectCompletelyVisible(RectI rect) +{ + // rect is passed in virtual client space + // Determine the points bounding the requested rectangle + Point2I rectUpperLeft = rect.point; + Point2I rectLowerRight = rect.point + rect.extent; + + // Determine the points bounding the actual visible area... + Point2I visUpperLeft = mChildRelPos; + Point2I visLowerRight = mChildRelPos + mContentExt; + + // Make sure the rectangle is visible along the X axis... + if(rectUpperLeft.x < visUpperLeft.x) + return false; + else if(rectLowerRight.x > visLowerRight.x) + return false; + + // Make sure the rectangle is visible along the Y axis... + if(rectUpperLeft.y < visUpperLeft.y) + return false; + else if(rectLowerRight.y > visLowerRight.y) + return false; + + return true; +} + +void GuiScrollCtrl::addObject(SimObject *object) +{ + Parent::addObject(object); + computeSizes(); +} + +GuiControl* GuiScrollCtrl::findHitControl(const Point2I &pt, S32 initialLayer) +{ + if(pt.x < mProfile->mBorderThickness || pt.y < mProfile->mBorderThickness) + return this; + if(pt.x >= getWidth() - mProfile->mBorderThickness - (mHasVScrollBar ? mScrollBarThickness : 0) || + pt.y >= getHeight() - mProfile->mBorderThickness - (mHasHScrollBar ? mScrollBarThickness : 0)) + return this; + return Parent::findHitControl(pt, initialLayer); +} + +void GuiScrollCtrl::computeSizes() +{ + S32 thickness = (mProfile ? mProfile->mBorderThickness : 1); + Point2I borderExtent(thickness, thickness); + mContentPos = borderExtent + mChildMargin; + mContentExt = getExtent() - (mChildMargin * 2) + - (borderExtent * 2); + + Point2I childLowerRight; + + mHBarEnabled = false; + mVBarEnabled = false; + mHasVScrollBar = (mForceVScrollBar == ScrollBarAlwaysOn); + mHasHScrollBar = (mForceHScrollBar == ScrollBarAlwaysOn); + + setUpdate(); + + if (calcChildExtents()) + { + childLowerRight = mChildPos + mChildExt; + + if (mHasVScrollBar) + mContentExt.x -= mScrollBarThickness; + if (mHasHScrollBar) + mContentExt.y -= mScrollBarThickness; + if (mChildExt.x > mContentExt.x && (mForceHScrollBar == ScrollBarDynamic)) + { + mHasHScrollBar = true; + mContentExt.y -= mScrollBarThickness; + } + if (mChildExt.y > mContentExt.y && (mForceVScrollBar == ScrollBarDynamic)) + { + mHasVScrollBar = true; + mContentExt.x -= mScrollBarThickness; + + // If Extent X Changed, check Horiz Scrollbar. + if (mChildExt.x > mContentExt.x && !mHasHScrollBar && (mForceHScrollBar == ScrollBarDynamic)) + { + mHasHScrollBar = true; + mContentExt.y -= mScrollBarThickness; + } + } + Point2I contentLowerRight = mContentPos + mContentExt; + + // see if the child controls need to be repositioned (null space in control) + Point2I delta(0,0); + + if (mChildPos.x > mContentPos.x) + delta.x = mContentPos.x - mChildPos.x; + else if (contentLowerRight.x > childLowerRight.x) + { + S32 diff = contentLowerRight.x - childLowerRight.x; + delta.x = getMin(mContentPos.x - mChildPos.x, diff); + } + + //reposition the children if the child extent > the scroll content extent + if (mChildPos.y > mContentPos.y) + delta.y = mContentPos.y - mChildPos.y; + else if (contentLowerRight.y > childLowerRight.y) + { + S32 diff = contentLowerRight.y - childLowerRight.y; + delta.y = getMin(mContentPos.y - mChildPos.y, diff); + } + + // apply the deltas to the children... + if (delta.x || delta.y) + { + SimGroup::iterator i; + for(i = begin(); i != end();i++) + { + GuiControl *ctrl = (GuiControl *) (*i); + ctrl->setPosition( ctrl->getPosition() + delta ); + } + mChildPos += delta; + childLowerRight += delta; + } + // enable needed scroll bars + if (mChildExt.x > mContentExt.x) + mHBarEnabled = true; + if (mChildExt.y > mContentExt.y) + mVBarEnabled = true; + mChildRelPos = mContentPos - mChildPos; + } + + // Prevent resizing our children from recalling this function! + mIgnoreChildResized = true; + + if ( mLockVertScroll ) + { + // If vertical scroll is locked we size our child's height to our own + SimGroup::iterator i; + for(i = begin(); i != end();i++) + { + GuiControl *ctrl = (GuiControl *) (*i); + ctrl->setHeight( mContentExt.y ); + } + } + + if ( mLockHorizScroll ) + { + // If horizontal scroll is locked we size our child's width to our own + SimGroup::iterator i; + for(i = begin(); i != end();i++) + { + GuiControl *ctrl = (GuiControl *) (*i); + ctrl->setWidth( mContentExt.x ); + } + } + + mIgnoreChildResized = false; + + // build all the rectangles and such... + calcScrollRects(); + calcThumbs(); +} + +void GuiScrollCtrl::calcScrollRects(void) +{ + S32 thickness = ( mProfile ? mProfile->mBorderThickness : 1 ); + if (mHasHScrollBar) + { + mLeftArrowRect.set(thickness, + getHeight() - thickness - mScrollBarThickness, + mScrollBarArrowBtnLength, + mScrollBarThickness); + + mRightArrowRect.set(getWidth() - thickness - (mHasVScrollBar ? mScrollBarThickness - 1 : 0) - mScrollBarArrowBtnLength, + getHeight() - thickness - mScrollBarThickness, + mScrollBarArrowBtnLength, + mScrollBarThickness); + mHTrackRect.set(mLeftArrowRect.point.x + mLeftArrowRect.extent.x, + mLeftArrowRect.point.y, + mRightArrowRect.point.x - (mLeftArrowRect.point.x + mLeftArrowRect.extent.x), + mScrollBarThickness); + } + if (mHasVScrollBar) + { + mUpArrowRect.set(getWidth() - thickness - mScrollBarThickness, + thickness, + mScrollBarThickness, + mScrollBarArrowBtnLength); + mDownArrowRect.set(getWidth() - thickness - mScrollBarThickness, + getHeight() - thickness - (mHasHScrollBar ? mScrollBarThickness - 1 : 0) - mScrollBarArrowBtnLength, + mScrollBarThickness, + mScrollBarArrowBtnLength); + mVTrackRect.set(mUpArrowRect.point.x, + mUpArrowRect.point.y + mUpArrowRect.extent.y, + mScrollBarThickness, + mDownArrowRect.point.y - (mUpArrowRect.point.y + mUpArrowRect.extent.y) ); + } +} + +void GuiScrollCtrl::calcThumbs() +{ + if (mHBarEnabled && mChildExt.x > 0) + { + U32 trackSize = mHTrackRect.len_x(); + + if (mUseConstantHeightThumb) + mHThumbSize = mBaseThumbSize; + else + mHThumbSize = getMax(mBaseThumbSize, ( S32 )mCeil( ( F32 )( mContentExt.x * trackSize) / ( F32 )mChildExt.x ) ); + + mHThumbPos = mHTrackRect.point.x + (mChildRelPos.x * (trackSize - mHThumbSize)) / (mChildExt.x - mContentExt.x); + } + if (mVBarEnabled && mChildExt.y > 0) + { + U32 trackSize = mVTrackRect.len_y(); + + if (mUseConstantHeightThumb) + mVThumbSize = mBaseThumbSize; + else + mVThumbSize = getMax(mBaseThumbSize, ( S32 )mCeil( ( F32 )( mContentExt.y * trackSize ) / ( F32 )mChildExt.y ) ); + + mVThumbPos = mVTrackRect.point.y + (mChildRelPos.y * (trackSize - mVThumbSize)) / (mChildExt.y - mContentExt.y); + } +} + + +void GuiScrollCtrl::scrollDelta(S32 deltaX, S32 deltaY) +{ + scrollTo(mChildRelPos.x + deltaX, mChildRelPos.y + deltaY); +} + +void GuiScrollCtrl::scrollDeltaAnimate(S32 x, S32 y) +{ + if ( !size() ) + return; + + if ( mAnimating ) + mScrollTargetPos += Point2I( x, y ); + else + mScrollTargetPos = mChildRelPos + Point2I( x, y ); + + setUpdate(); + + mScrollTargetPos.setMin( mChildExt - mContentExt ); + mScrollTargetPos.setMax( Point2I::Zero ); + + mAnimating = true; +} + +void GuiScrollCtrl::scrollTo(S32 x, S32 y) +{ + if ( !size() ) + { + return; + } + + if ( x == mChildRelPos.x && y == mChildRelPos.y ) + { + return; + } + + setUpdate(); + if (x > mChildExt.x - mContentExt.x) + x = mChildExt.x - mContentExt.x; + if (x < 0) + x = 0; + + if (y > mChildExt.y - mContentExt.y) + y = mChildExt.y - mContentExt.y; + if (y < 0) + y = 0; + + Point2I delta(x - mChildRelPos.x, y - mChildRelPos.y); + mChildRelPos += delta; + mChildPos -= delta; + + for(SimSet::iterator i = begin(); i != end();i++) + { + GuiControl *ctrl = (GuiControl *) (*i); + ctrl->setPosition( ctrl->getPosition() - delta ); + } + calcThumbs(); + + Con::executef( this, "onScroll" ); +} + +void GuiScrollCtrl::scrollToObject(GuiControl *targetControl) +{ + bool isValidChild = false; + Point2I relativePosition = targetControl->getPosition(); + GuiControl* parentControl = targetControl->getParent(); + while (parentControl) + { + GuiScrollCtrl* scrollControl = dynamic_cast(parentControl); + if (scrollControl == this) + { + relativePosition += scrollControl->getChildRelPos(); + isValidChild = true; + break; + } + + relativePosition += parentControl->getPosition(); + parentControl = parentControl->getParent(); + } + + if (isValidChild) + { + scrollRectVisible(RectI(relativePosition, targetControl->getExtent())); + } + else + { + Con::errorf("GuiScrollCtrl::scrollToObject() - Specified object is not a child of this scroll control (%d)!", targetControl->getId()); + } +} + +GuiScrollCtrl::Region GuiScrollCtrl::findHitRegion(const Point2I &pt) +{ + if (mVBarEnabled && mHasVScrollBar) + { + if (mUpArrowRect.pointInRect(pt)) + return UpArrow; + else if (mDownArrowRect.pointInRect(pt)) + return DownArrow; + else if (mVTrackRect.pointInRect(pt)) + { + if (pt.y < mVThumbPos) + return UpPage; + else if (pt.y < mVThumbPos + mVThumbSize) + return VertThumb; + else + return DownPage; + } + } + if (mHBarEnabled && mHasHScrollBar) + { + if (mLeftArrowRect.pointInRect(pt)) + return LeftArrow; + else if (mRightArrowRect.pointInRect(pt)) + return RightArrow; + else if (mHTrackRect.pointInRect(pt)) + { + if (pt.x < mHThumbPos) + return LeftPage; + else if (pt.x < mHThumbPos + mHThumbSize) + return HorizThumb; + else + return RightPage; + } + } + return None; +} + +bool GuiScrollCtrl::wantsTabListMembership() +{ + return true; +} + +bool GuiScrollCtrl::loseFirstResponder() +{ + setUpdate(); + return true; +} + +bool GuiScrollCtrl::becomeFirstResponder() +{ + setUpdate(); + return mWillFirstRespond; +} + +bool GuiScrollCtrl::onKeyDown(const GuiEvent &event) +{ + if (mWillFirstRespond) + { + switch (event.keyCode) + { + case KEY_RIGHT: + scrollByRegion(RightArrow); + return true; + + case KEY_LEFT: + scrollByRegion(LeftArrow); + return true; + + case KEY_DOWN: + scrollByRegion(DownArrow); + return true; + + case KEY_UP: + scrollByRegion(UpArrow); + return true; + + case KEY_PAGE_UP: + scrollByRegion(UpPage); + return true; + + case KEY_PAGE_DOWN: + scrollByRegion(DownPage); + return true; + + default: + break; + } + } + return Parent::onKeyDown(event); +} + +void GuiScrollCtrl::onMouseDown(const GuiEvent &event) +{ + mouseLock(); + + setUpdate(); + + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + mHitRegion = findHitRegion(curMousePos); + mStateDepressed = true; + + // Set a 0.5 second delay before we start scrolling + mLastUpdated = Platform::getVirtualMilliseconds() + 500; + + scrollByRegion(mHitRegion); + + if (mHitRegion == VertThumb) + { + mChildRelPosAnchor = mChildRelPos; + mThumbMouseDelta = curMousePos.y - mVThumbPos; + } + else if (mHitRegion == HorizThumb) + { + mChildRelPosAnchor = mChildRelPos; + mThumbMouseDelta = curMousePos.x - mHThumbPos; + } +} + +void GuiScrollCtrl::onMouseUp(const GuiEvent &) +{ + mouseUnlock(); + + setUpdate(); + + mHitRegion = None; + mStateDepressed = false; +} + +void GuiScrollCtrl::onMouseDragged(const GuiEvent &event) +{ + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + setUpdate(); + + if ( (mHitRegion != VertThumb) && (mHitRegion != HorizThumb) ) + { + Region hit = findHitRegion(curMousePos); + if (hit != mHitRegion) + mStateDepressed = false; + else + mStateDepressed = true; + return; + } + + // ok... if the mouse is 'near' the scroll bar, scroll with it + // otherwise, snap back to the previous position. + + if (mHitRegion == VertThumb) + { + if (curMousePos.x >= mVTrackRect.point.x - mScrollBarDragTolerance && + curMousePos.x <= mVTrackRect.point.x + mVTrackRect.extent.x - 1 + mScrollBarDragTolerance && + curMousePos.y >= mVTrackRect.point.y - mScrollBarDragTolerance && + curMousePos.y <= mVTrackRect.point.y + mVTrackRect.extent.y - 1 + mScrollBarDragTolerance) + { + S32 newVThumbPos = curMousePos.y - mThumbMouseDelta; + if(newVThumbPos != mVThumbPos) + { + S32 newVPos = (newVThumbPos - mVTrackRect.point.y) * + (mChildExt.y - mContentExt.y) / + (mVTrackRect.extent.y - mVThumbSize); + + scrollTo(mChildRelPosAnchor.x, newVPos); + } + } + else + scrollTo(mChildRelPosAnchor.x, mChildRelPosAnchor.y); + } + else if (mHitRegion == HorizThumb) + { + if (curMousePos.x >= mHTrackRect.point.x - mScrollBarDragTolerance && + curMousePos.x <= mHTrackRect.point.x + mHTrackRect.extent.x - 1 + mScrollBarDragTolerance && + curMousePos.y >= mHTrackRect.point.y - mScrollBarDragTolerance && + curMousePos.y <= mHTrackRect.point.y + mHTrackRect.extent.y - 1 + mScrollBarDragTolerance) + { + S32 newHThumbPos = curMousePos.x - mThumbMouseDelta; + if(newHThumbPos != mHThumbPos) + { + S32 newHPos = (newHThumbPos - mHTrackRect.point.x) * + (mChildExt.x - mContentExt.x) / + (mHTrackRect.extent.x - mHThumbSize); + + scrollTo(newHPos, mChildRelPosAnchor.y); + } + } + else + scrollTo(mChildRelPosAnchor.x, mChildRelPosAnchor.y); + } +} + +bool GuiScrollCtrl::onMouseWheelUp(const GuiEvent &event) +{ + if ( !mAwake || !mVisible ) + return false; + + scrollByMouseWheel( event ); + + return true; +} + +bool GuiScrollCtrl::onMouseWheelDown(const GuiEvent &event) +{ + if ( !mAwake || !mVisible ) + return false; + + scrollByMouseWheel( event ); + + return true; +} + +void GuiScrollCtrl::updateChildMousePos() +{ + // We pass a fake GuiEvent to child controls onMouseMove + // since although the mouse has not moved 'they' have. + // + // Its possible this could cause problems if a GuiControl + // responds to more than just the mouse position in the onMouseMove + // event, like for example doing something different depending on + // a modifier key, which we aren't filling in to the structure! + + GuiEvent event = {0}; + event.mousePoint = getRoot()->getCursorPos(); + + iterator itr; + for ( itr = begin(); itr != end(); itr++ ) + { + GuiControl *child = static_cast( *itr ); + child->onMouseMove( event ); + } +} + +void GuiScrollCtrl::onPreRender() +{ + Parent::onPreRender(); + + S32 currentTime = Platform::getVirtualMilliseconds(); + S32 deltaMs = currentTime - mLastPreRender; + mLastPreRender = currentTime; + + // Update mouse-wheel scroll animation if we are currently doing one... + + if ( mAnimating ) + { + //U32 frames = Con::getIntVariable( "$frames", 0 ); + //frames++; + //Con::setIntVariable( "$frames", frames ); + + F32 deltaTicks = deltaMs / 32.0f; + + if ( mScrollAnimSpeed <= 0 ) + { + scrollTo( mScrollTargetPos.x, mScrollTargetPos.y ); + } + else + { + S32 maxPixels = deltaTicks * mScrollAnimSpeed; + + Point2I toTarget = mScrollTargetPos - mChildRelPos; + S32 signx = toTarget.x > 0 ? 1 : -1; + S32 signy = toTarget.y > 0 ? 1 : -1; + + S32 deltaX = getMin( mAbs(toTarget.x), maxPixels ) * signx; + S32 deltaY = getMin( mAbs(toTarget.y), maxPixels ) * signy; + + scrollDelta( deltaX, deltaY ); + } + + if ( mChildRelPos == mScrollTargetPos ) + { + //Con::printf( "Animated Frames : %d", frames ); + //Con::setIntVariable( "$frames", 0 ); + mAnimating = false; + } + + updateChildMousePos(); + } + + // Now scroll in response to a 'depressed state' if appropriate... + + // Short circuit if not depressed to save cycles + if( mStateDepressed != true ) + return; + + //default to one second, though it shouldn't be necessary + U32 timeThreshold = 1000; + + // We don't want to scroll by pages at an interval the same as when we're scrolling + // using the arrow buttons, so adjust accordingly. + switch( mHitRegion ) + { + case UpPage: + case DownPage: + case LeftPage: + case RightPage: + timeThreshold = 200; + break; + case UpArrow: + case DownArrow: + case LeftArrow: + case RightArrow: + timeThreshold = 20; + break; + default: + // Neither a button or a page, don't scroll (shouldn't get here) + return; + break; + }; + + S32 timeElapsed = Platform::getVirtualMilliseconds() - mLastUpdated; + + if ( ( timeElapsed > 0 ) && ( timeElapsed > timeThreshold ) ) + { + mLastUpdated = Platform::getVirtualMilliseconds(); + scrollByRegion(mHitRegion); + } +} + +void GuiScrollCtrl::scrollByRegion(Region reg) +{ + setUpdate(); + if(!size()) + return; + GuiControl *content = (GuiControl *) front(); + U32 rowHeight, columnWidth; + U32 pageHeight, pageWidth; + + content->getScrollLineSizes(&rowHeight, &columnWidth); + + if(rowHeight >= mContentExt.y) + pageHeight = 1; + else + pageHeight = mContentExt.y - rowHeight; + + if(columnWidth >= mContentExt.x) + pageWidth = 1; + else + pageWidth = mContentExt.x - columnWidth; + + if (mVBarEnabled) + { + switch(reg) + { + case UpPage: + scrollDelta(0, -(S32)pageHeight); + break; + case DownPage: + scrollDelta(0, pageHeight); + break; + case UpArrow: + scrollDelta(0, -(S32)rowHeight); + break; + case DownArrow: + scrollDelta(0, rowHeight); + break; + default: + break; + } + } + + if (mHBarEnabled) + { + switch(reg) + { + case LeftPage: + scrollDelta(-(S32)pageWidth, 0); + break; + case RightPage: + scrollDelta(pageWidth, 0); + break; + case LeftArrow: + scrollDelta(-(S32)columnWidth, 0); + break; + case RightArrow: + scrollDelta(columnWidth, 0); + break; + default: + break; + } + } +} + +void GuiScrollCtrl::scrollByMouseWheel( const GuiEvent &event ) +{ + setUpdate(); + if ( !size() ) + return; + + if( event.mouseAxis == 1 ) + scrollDeltaAnimate( 0, -event.fval ); + else + scrollDeltaAnimate( -event.fval, 0 ); +} + + +void GuiScrollCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + // draw content controls + // create a rect to intersect with the updateRect + RectI contentRect(mContentPos.x + offset.x, mContentPos.y + offset.y, mContentExt.x, mContentExt.y); + contentRect.intersect(updateRect); + + // Always call parent + Parent::onRender(offset, contentRect); + + // Reset the ClipRect as the parent call can modify it when rendering + // the child controls + GFX->setClipRect( updateRect ); + + //draw the scroll corner + if (mHasVScrollBar && mHasHScrollBar) + drawScrollCorner(offset); + + // draw scroll bars + if (mHasVScrollBar) + drawVScrollBar(offset); + + if (mHasHScrollBar) + drawHScrollBar(offset); +} + +void GuiScrollCtrl::drawBorder( const Point2I &offset, bool /*isFirstResponder*/ ) +{ +} + +void GuiScrollCtrl::drawVScrollBar(const Point2I &offset) +{ + if ( mTextureObject.isNull() ) + { + return; + } + + // Start Point. + Point2I pos = ( offset + mUpArrowRect.point ); + + // Up Arrow. + S32 upArrowBitmap = ( BmpStates * BmpUp ); + if ( !mVBarEnabled ) + { + upArrowBitmap += BmpDisabled; + } + else if ( mHitRegion == UpArrow && mStateDepressed ) + { + upArrowBitmap += BmpHilite; + } + + // Render Up Arrow. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[upArrowBitmap] ); + + // Update Pos. + pos.y += mBitmapBounds[upArrowBitmap].extent.y; + + // Track. + S32 trackBitmap = ( BmpStates * BmpVPage ); + if ( !mVBarEnabled ) + { + trackBitmap += BmpDisabled; + } + else if ( mHitRegion == DownPage && mStateDepressed ) + { + trackBitmap += BmpHilite; + } + + // Determine the Track Rect. + RectI trackRect; + trackRect.point = pos; + trackRect.extent.x = mBitmapBounds[trackBitmap].extent.x; + trackRect.extent.y = ( offset.y + mDownArrowRect.point.y ) - pos.y; + + // Render Track? + if ( trackRect.extent.y > 0 ) + { + // Render Track. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretchSR( mTextureObject, trackRect, mBitmapBounds[trackBitmap] ); + } + + // Update Pos. + pos.y += trackRect.extent.y; + + // Down Arrow. + S32 downArrowBitmap = ( BmpStates * BmpDown ); + if ( !mVBarEnabled ) + { + downArrowBitmap += BmpDisabled; + } + else if ( mHitRegion == DownArrow && mStateDepressed ) + { + downArrowBitmap += BmpHilite; + } + + // Render Down Arrow. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[downArrowBitmap] ); + + // Render the Thumb? + if ( !mVBarEnabled ) + { + // Nope. + return; + } + + // Reset the Pos. + pos.y = ( offset.y + mVThumbPos ); + + // Determine the Bitmaps. + S32 thumbBitmapTop = ( BmpStates * BmpVThumbTopCap ); + S32 thumbBitmapMiddle = ( BmpStates * BmpVThumb ); + S32 thumbBitmapBottom = ( BmpStates * BmpVThumbBottomCap ); + + if ( mHitRegion == VertThumb && mStateDepressed ) + { + thumbBitmapTop += BmpHilite; + thumbBitmapMiddle += BmpHilite; + thumbBitmapBottom += BmpHilite; + } + + // Render Thumb Top. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[thumbBitmapTop] ); + + // Update Pos. + pos.y += mBitmapBounds[thumbBitmapTop].extent.y; + + // Determine the Thumb Rect. + RectI thumbRect; + thumbRect.point = pos; + thumbRect.extent.x = mBitmapBounds[thumbBitmapMiddle].extent.x; + thumbRect.extent.y = mVThumbSize - ( mBitmapBounds[thumbBitmapTop].extent.y + mBitmapBounds[thumbBitmapBottom].extent.y ); + + // Render Thumb? + if ( thumbRect.extent.y > 0 ) + { + // Render Track. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretchSR( mTextureObject, thumbRect, mBitmapBounds[thumbBitmapMiddle] ); + } + + // Update Pos. + pos.y += thumbRect.extent.y; + + // Render the Thumb Bottom. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[thumbBitmapBottom] ); +} + +void GuiScrollCtrl::drawHScrollBar(const Point2I &offset) +{ + if ( mTextureObject.isNull() ) + { + return; + } + + // Start Point. + Point2I pos = ( offset + mLeftArrowRect.point ); + + // Left Arrow. + S32 leftArrowBitmap = ( BmpStates * BmpLeft ); + if ( !mHBarEnabled ) + { + leftArrowBitmap += BmpDisabled; + } + else if ( mHitRegion == LeftArrow && mStateDepressed ) + { + leftArrowBitmap += BmpHilite; + } + + // Render Up Arrow. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[leftArrowBitmap] ); + + // Update Pos. + pos.x += mBitmapBounds[leftArrowBitmap].extent.x; + + // Track. + S32 trackBitmap = ( BmpStates * BmpHPage ); + if ( !mHBarEnabled ) + { + trackBitmap += BmpDisabled; + } + else if ( mHitRegion == LeftPage && mStateDepressed ) + { + trackBitmap += BmpHilite; + } + + // Determine the Track Rect. + RectI trackRect; + trackRect.point = pos; + trackRect.extent.x = ( offset.x + mRightArrowRect.point.x ) - pos.x; + trackRect.extent.y = mBitmapBounds[trackBitmap].extent.y; + + // Render Track? + if ( trackRect.extent.x > 0 ) + { + // Render Track. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretchSR( mTextureObject, trackRect, mBitmapBounds[trackBitmap] ); + } + + // Update Pos. + pos.x += trackRect.extent.x; + + // Right Arrow. + S32 rightArrowBitmap = ( BmpStates * BmpRight ); + if ( !mHBarEnabled ) + { + rightArrowBitmap += BmpDisabled; + } + else if ( mHitRegion == RightArrow && mStateDepressed ) + { + rightArrowBitmap += BmpHilite; + } + + // Render Right Arrow. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[rightArrowBitmap] ); + + // Render the Thumb? + if ( !mHBarEnabled ) + { + // Nope. + return; + } + + // Reset the Pos. + pos.x = ( offset.x + mHThumbPos ); + + // Determine the Bitmaps. + S32 thumbBitmapLeft = ( BmpStates * BmpHThumbLeftCap ); + S32 thumbBitmapMiddle = ( BmpStates * BmpHThumb ); + S32 thumbBitmapRight = ( BmpStates * BmpHThumbRightCap ); + + if ( mHitRegion == HorizThumb && mStateDepressed ) + { + thumbBitmapLeft += BmpHilite; + thumbBitmapMiddle += BmpHilite; + thumbBitmapRight += BmpHilite; + } + + // Render Thumb Left. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[thumbBitmapLeft] ); + + // Update Pos. + pos.x += mBitmapBounds[thumbBitmapLeft].extent.x; + + // Determine the Thumb Rect. + RectI thumbRect; + thumbRect.point = pos; + thumbRect.extent.x = mHThumbSize - ( mBitmapBounds[thumbBitmapLeft].extent.x + mBitmapBounds[thumbBitmapRight].extent.x ); + thumbRect.extent.y = mBitmapBounds[thumbBitmapMiddle].extent.y; + + // Render Thumb? + if ( thumbRect.extent.x > 0 ) + { + // Render Track. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretchSR( mTextureObject, thumbRect, mBitmapBounds[thumbBitmapMiddle] ); + } + + // Update Pos. + pos.x += thumbRect.extent.x; + + // Render the Thumb Bottom. + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, pos, mBitmapBounds[thumbBitmapRight] ); +} + +void GuiScrollCtrl::drawScrollCorner(const Point2I &offset) +{ + Point2I pos = offset; + pos.x += mRightArrowRect.point.x + mRightArrowRect.extent.x - 1; + pos.y += mRightArrowRect.point.y; + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, pos, mBitmapBounds[BmpStates * BmpResize]); +} + +void GuiScrollCtrl::autoScroll(Region reg) +{ + scrollByRegion(reg); +} \ No newline at end of file diff --git a/gui/containers/guiScrollCtrl.h b/gui/containers/guiScrollCtrl.h new file mode 100644 index 0000000..4ddd7ab --- /dev/null +++ b/gui/containers/guiScrollCtrl.h @@ -0,0 +1,240 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISCROLLCTRL_H_ +#define _GUISCROLLCTRL_H_ + +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + +class GuiScrollCtrl : public GuiContainer +{ +private: + typedef GuiContainer Parent; + +protected: + + // the scroll control uses a bitmap array to draw all its + // graphics... these are the bitmaps it needs: + enum BitmapIndices + { + BmpUp, + BmpDown, + BmpVThumbTopCap, + BmpVThumb, + BmpVThumbBottomCap, + BmpVPage, + BmpLeft, + BmpRight, + BmpHThumbLeftCap, + BmpHThumb, + BmpHThumbRightCap, + BmpHPage, + BmpResize, + + BmpCount + }; + + enum BitmapStates + { + BmpDefault = 0, + BmpHilite, + BmpDisabled, + + BmpStates + }; + RectI *mBitmapBounds; //bmp is [3*n], bmpHL is [3*n + 1], bmpNA is [3*n + 2] + GFXTexHandle mTextureObject; + + S32 mBorderThickness; // this gets set per class in the constructor + Point2I mChildMargin; // the thickness of the margin around the child controls + + // note - it is implicit in the scroll view that the buttons all have the same + // arrow length and that horizontal and vertical scroll bars have the + // same thickness + + S32 mScrollBarThickness; // determined by the width of the vertical page bmp + S32 mScrollBarArrowBtnLength; // determined by the height of the up arrow + S32 mScrollBarDragTolerance; // maximal distance from scrollbar at which a scrollbar drag is still valid + + bool mHBarEnabled; + bool mVBarEnabled; + bool mHasHScrollBar; + bool mHasVScrollBar; + + Point2I mContentPos; // the position of the content region in the control's coord system + Point2I mContentExt; // the extent of the content region + + Point2I mChildPos; // the position of the upper left corner of the child control(s) + Point2I mChildExt; + + Point2I mChildRelPos; // the relative position of the upper left content corner in + // the child's coordinate system - 0,0 if scrolled all the way to upper left. + + //-------------------------------------- + // for mouse dragging the thumb + Point2I mChildRelPosAnchor; // the original childRelPos when scrolling started + S32 mThumbMouseDelta; + + S32 mLastUpdated; + + S32 mHThumbSize; + S32 mHThumbPos; + + S32 mVThumbSize; + S32 mVThumbPos; + + S32 mBaseThumbSize; + + RectI mUpArrowRect; + RectI mDownArrowRect; + RectI mLeftArrowRect; + RectI mRightArrowRect; + RectI mHTrackRect; + RectI mVTrackRect; + + //-------------------------------------- + // for determining hit area +public: //called by the ComboPopUp class + enum Region + { + UpArrow, + DownArrow, + LeftArrow, + RightArrow, + UpPage, + DownPage, + LeftPage, + RightPage, + VertThumb, + HorizThumb, + None + }; + enum { + ScrollBarAlwaysOn = 0, + ScrollBarAlwaysOff = 1, + ScrollBarDynamic = 2 + }; + + bool mStateDepressed; ///< Is the mouse currently depressed on a scroll region + Region mHitRegion; ///< Which region is hit by the mouse + + S32 mForceHScrollBar; ///< Force showing the Horizontal scrollbar + S32 mForceVScrollBar; ///< Force showing the Vertical scrollbar + bool mLockHorizScroll; ///< Is horizontal scrolling disabled + bool mLockVertScroll; ///< Is vertical scrolling disabled + + bool mUseConstantHeightThumb; + bool mWillFirstRespond; // for automatically handling arrow keys + + /// Used internally to prevent infinite recursion. + bool mIgnoreChildResized; + + /// MouseWheel scroll animation + /// @{ + + /// Is currently performing a scroll animation. + bool mAnimating; + + /// Pixels moved per tick when performing a scroll animation. + S32 mScrollAnimSpeed; + + /// The target position when performing a scroll animation. + Point2I mScrollTargetPos; + + /// Platform time of the last call to onPreRender + S32 mLastPreRender; + + /// @} + + Region findHitRegion(const Point2I &); + +protected: + + virtual bool calcChildExtents(); + virtual void calcScrollRects(void); + void calcThumbs(); + void scrollByRegion(Region reg); + void scrollByMouseWheel( const GuiEvent &event ); + + /// Tell the kids that the mouse moved (relatively) + void updateChildMousePos(); + + //-------------------------------------- + + //-------------------------------------- + +public: + GuiScrollCtrl(); + + DECLARE_CONOBJECT(GuiScrollCtrl); + DECLARE_DESCRIPTION( "A container that allows to view a larger GUI control inside its smaller area " + "by providing horizontal and/or vertical scroll bars." ); + + static void initPersistFields(); + void autoScroll(Region reg); + + void scrollDeltaAnimate(S32 x, S32 y); + void scrollTo(S32 x, S32 y); + void scrollToObject(GuiControl *targetControl); + void scrollDelta(S32 x, S32 y); + void scrollRectVisible(RectI rect); + + /// Is the given client space rect completely visible within the actual + /// visible area, or is some of it clipped. Returns true if it is + /// completely visible. + bool isRectCompletelyVisible(RectI rect); + + void computeSizes(); + + // you can change the bitmap array dynamically. + void loadBitmapArray(); + + void addObject(SimObject *obj); + bool resize(const Point2I &newPosition, const Point2I &newExtent); + void childResized(GuiControl *child); + Point2I getChildPos() { return mChildPos; } + Point2I getChildRelPos() { return mChildRelPos; }; + Point2I getChildExtent() { return mChildExt; } + Point2I getContentExtent() { return mContentExt; } + Point2I getChildMargin() { return mChildMargin; } // Added to aid in sizing calculations + S32 getBorderThickness(void) { return mBorderThickness; } + S32 scrollBarThickness() const { return(mScrollBarThickness); } + S32 scrollBarArrowBtnLength() const { return(mScrollBarArrowBtnLength); } + bool hasHScrollBar() const { return(mHasHScrollBar); } + bool hasVScrollBar() const { return(mHasVScrollBar); } + bool enabledHScrollBar() const { return(mHBarEnabled); } + bool enabledVScrollBar() const { return(mVBarEnabled); } + + bool isScrolledToBottom() { return mChildPos.y + mChildExt.y <= mContentPos.y + mContentExt.y; } + + bool wantsTabListMembership(); + bool becomeFirstResponder(); + bool loseFirstResponder(); + + Region getCurHitRegion(void) { return mHitRegion; } + + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseRepeat(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + bool onMouseWheelUp(const GuiEvent &event); + bool onMouseWheelDown(const GuiEvent &event); + + bool onWake(); + void onSleep(); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + virtual void drawBorder(const Point2I &offset, bool isFirstResponder); + virtual void drawVScrollBar(const Point2I &offset); + virtual void drawHScrollBar(const Point2I &offset); + virtual void drawScrollCorner(const Point2I &offset); + virtual GuiControl* findHitControl(const Point2I &pt, S32 initialLayer = -1); +}; + +#endif //_GUI_SCROLL_CTRL_H diff --git a/gui/containers/guiSplitContainer.cpp b/gui/containers/guiSplitContainer.cpp new file mode 100644 index 0000000..109e2ad --- /dev/null +++ b/gui/containers/guiSplitContainer.cpp @@ -0,0 +1,545 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiSplitContainer.h" + +#include "gui/core/guiCanvas.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + +static EnumTable::Enums gSplitOrientationEnums[] = +{ + { GuiSplitContainer::Vertical, "Vertical" }, + { GuiSplitContainer::Horizontal, "Horizontal" } +}; +EnumTable gSplitOrientationEnumTable( 2, gSplitOrientationEnums ); + +static EnumTable::Enums gSplitFixedPanelEnums[] = +{ + { GuiSplitContainer::None, "None" }, + { GuiSplitContainer::FirstPanel, "FirstPanel" }, + { GuiSplitContainer::SecondPanel, "SecondPanel" } +}; +EnumTable gSplitFixedPanelEnumTable( 3, gSplitFixedPanelEnums ); + + +//----------------------------------------------------------------------------- +// GuiSplitContainer +//----------------------------------------------------------------------------- + +GuiSplitContainer::GuiSplitContainer() + : mFixedPanel( None ), + mFixedPanelSize( 100 ), + mOrientation( Vertical ), + mSplitterSize( 2 ), + mSplitPoint( 0, 0 ), + mSplitRect( 0, 0, mSplitterSize, mSplitterSize ), + mDragging( false ) +{ + setMinExtent( Point2I(64,64) ); + setExtent(200,200); + setDocking( Docking::dockNone ); + + // We only support client docked items in a split container + mValidDockingMask = Docking::dockClient; +} + +GuiSplitContainer::~GuiSplitContainer() +{ +} + +IMPLEMENT_CONOBJECT(GuiSplitContainer); + + +void GuiSplitContainer::initPersistFields() +{ + addGroup( "Splitter", "Options to configure split panels contained by this control" ); + addField( "Orientation", TypeEnum, Offset( mOrientation, GuiSplitContainer), 1, &gSplitOrientationEnumTable, "Vertical/Horizontal Splitter" ); + addField( "SplitterSize", TypeS32, Offset( mSplitterSize, GuiSplitContainer), "The size of the splitter" ); + addField( "SplitPoint", TypePoint2I, Offset( mSplitPoint, GuiSplitContainer), "The x/y is used depending on the orientation. This value will change as the parent is resized." ); + addField( "FixedPanel", TypeEnum, Offset( mFixedPanel, GuiSplitContainer), 1, &gSplitFixedPanelEnumTable, "Which panel will stay fixed on parent resizing" ); + addField( "FixedSize", TypeS32, Offset( mFixedPanelSize, GuiSplitContainer), "The total width of the fixed panel" ); + endGroup( "Splitter" ); + + Parent::initPersistFields(); +} + +bool GuiSplitContainer::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + if ( mOrientation == Horizontal ) + mSplitPoint.set( 100, 300 ); + else + mSplitPoint.set( 300, 100 ); + + return true; +} + +bool GuiSplitContainer::onWake() +{ + if ( !Parent::onWake() ) + return false; + + // Create Panel 1 + if ( empty() ) + { + GuiPanel *newPanel = new GuiPanel(); + AssertFatal( newPanel, "GuiSplitContainer::onAdd - Cannot create subordinate panel #1!" ); + newPanel->registerObject(); + newPanel->setInternalName( "Panel1" ); + newPanel->setDocking( Docking::dockClient ); + addObject( (SimObject*)newPanel ); + } + else + { + GuiContainer *containerCtrl = dynamic_cast( at(0) ); + if ( containerCtrl ) + { + containerCtrl->setInternalName( "Panel1" ); + containerCtrl->setDocking( Docking::dockClient ); + } + } + + if ( size() == 1 ) + { + GuiPanel *newPanel = new GuiPanel(); + + // Create Panel 2 + newPanel = new GuiPanel(); + AssertFatal( newPanel, "GuiSplitContainer::onAdd - Cannot create subordinate panel #2!" ); + newPanel->registerObject(); + newPanel->setInternalName( "Panel2" ); + newPanel->setDocking( Docking::dockClient ); + addObject( (SimObject*)newPanel ); + } + else + { + GuiContainer *containerCtrl = dynamic_cast( at(1) ); + if ( containerCtrl ) + { + containerCtrl->setInternalName( "Panel2" ); + containerCtrl->setDocking( Docking::dockClient ); + } + } + + // Has FixedWidth been specified? + if ( mFixedPanelSize == 0 ) + { + // Nope, so try to guess as best we can + GuiContainer *firstPanel = dynamic_cast( at(0) ); + GuiContainer *secondPanel = dynamic_cast( at(1) ); + if ( mFixedPanel == FirstPanel ) + { + if ( mOrientation == Horizontal ) + mFixedPanelSize = firstPanel->getExtent().y; + else + mFixedPanelSize = firstPanel->getExtent().x; + + mSplitPoint = Point2I( mFixedPanelSize, mFixedPanelSize ); + } + else if ( mFixedPanel == SecondPanel ) + { + if ( mOrientation == Horizontal ) + mFixedPanelSize = getExtent().y - secondPanel->getExtent().y; + else + mFixedPanelSize = getExtent().x - secondPanel->getExtent().x; + + mSplitPoint = getExtent() - Point2I( mFixedPanelSize, mFixedPanelSize ); + } + + } + + setUpdateLayout(); + + return true; +} + +void GuiSplitContainer::onRender( Point2I offset, const RectI &updateRect ) +{ + Parent::onRender( offset, updateRect ); + + // Only render if we're dragging the splitter + if ( mDragging && mSplitRect.isValidRect() ) + { + // Splitter Rectangle (will adjust positioning only) + RectI splitterRect = mSplitRect; + + // Currently being dragged to Rect + Point2I splitterPoint = localToGlobalCoord( mSplitRect.point ); + splitterRect.point = localToGlobalCoord( mSplitPoint ); + + RectI clientRect = getClientRect(); + clientRect.point = localToGlobalCoord( clientRect.point ); + + if ( mOrientation == Horizontal ) + { + splitterRect.point.y -= mSplitterSize; + splitterRect.point.x = splitterPoint.x; + } + else + { + splitterRect.point.x -= mSplitterSize; + splitterRect.point.y = splitterPoint.y; + } + + RectI oldClip = GFX->getClipRect(); + GFX->setClipRect( clientRect ); + GFX->getDrawUtil()->drawRectFill( splitterRect, mProfile->mFillColorHL ); + GFX->setClipRect( oldClip ); + + } + else + { + RectI splitterRect = mSplitRect; + splitterRect.point += offset; + GFX->getDrawUtil()->drawRectFill( splitterRect, mProfile->mFillColor ); + } +} + +Point2I GuiSplitContainer::getMinExtent() const +{ + GuiContainer *panelOne = dynamic_cast( at(0) ); + GuiContainer *panelTwo = dynamic_cast( at(1) ); + + if ( !panelOne || !panelTwo ) + return Parent::getMinExtent(); + + Point2I minExtent = Point2I(0,0); + Point2I panelOneMinExtent = panelOne->getMinExtent(); + Point2I panelTwoMinExtent = panelTwo->getMinExtent(); + + if ( mOrientation == Horizontal ) + { + minExtent.y = 2 * mSplitterSize + panelOneMinExtent.y + panelTwoMinExtent.y; + minExtent.x = getMax( panelOneMinExtent.x, panelTwoMinExtent.x ); + } + else + { + minExtent.x = 2 * mSplitterSize + panelOneMinExtent.x + panelTwoMinExtent.x; + minExtent.y = getMax( panelOneMinExtent.y, panelTwoMinExtent.y ); + } + + return minExtent; +} + +void GuiSplitContainer::parentResized( const RectI &oldParentRect, const RectI &newParentRect ) +{ + Parent::parentResized( oldParentRect, newParentRect ); + return; + + // TODO: Is this right James? This isn't needed anymore? + /* + + // GuiSplitContainer overrides parentResized to make sure that the proper fixed frame's width/height + // is not compromised in the call + + if ( size() < 2 ) + return; + + GuiContainer *panelOne = dynamic_cast( at(0) ); + GuiContainer *panelTwo = dynamic_cast( at(1) ); + + AssertFatal( panelOne && panelTwo, "GuiSplitContainer::parentResized - Missing/Invalid Subordinate Controls! Split contained controls must derive from GuiContainer!" ); + + Point2I newDragPos; + if ( mFixedPanel == FirstPanel ) + { + newDragPos = panelOne->getExtent(); + newDragPos += Point2I( mSplitterSize, mSplitterSize ); + } + else if ( mFixedPanel == SecondPanel ) + { + newDragPos = getExtent() - panelTwo->getExtent(); + newDragPos -= Point2I( mSplitterSize, mSplitterSize ); + } + else // None + newDragPos.set( 1, 1); + + RectI clientRect = getClientRect(); + solvePanelConstraints( newDragPos, panelOne, panelTwo, clientRect ); + + setUpdateLayout(); + */ +} + +bool GuiSplitContainer::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + // Save previous extent. + Point2I oldExtent = getExtent(); + + // Resize ourselves. + if ( !Parent::resize( newPosition, newExtent ) || size() < 2 ) + return false; + + GuiContainer *panelOne = dynamic_cast( at(0) ); + GuiContainer *panelTwo = dynamic_cast( at(1) ); + + // + AssertFatal( panelOne && panelTwo, "GuiSplitContainer::resize - Missing/Invalid Subordinate Controls! Split contained controls must derive from GuiContainer!" ); + + // We only need to update the split point if our second panel is fixed. + // If the first is fixed, then we can leave the split point alone because + // the remainder of the size will be added to or taken from the second panel + Point2I newDragPos; + if ( mFixedPanel == SecondPanel ) + { + S32 deltaX = newExtent.x - oldExtent.x; + S32 deltaY = newExtent.y - oldExtent.y; + + if( mOrientation == Horizontal ) + mSplitPoint.y += deltaY; + else + mSplitPoint.x += deltaX; + } + + // If we got here, parent returned true + return true; +} + + +bool GuiSplitContainer::layoutControls( RectI &clientRect ) +{ + if ( size() < 2 ) + return false; + + GuiContainer *panelOne = dynamic_cast( at(0) ); + GuiContainer *panelTwo = dynamic_cast( at(1) ); + + // + AssertFatal( panelOne && panelTwo, "GuiSplitContainer::layoutControl - Missing/Invalid Subordinate Controls! Split contained controls must derive from GuiContainer!" ); + + RectI panelOneRect = RectI( clientRect.point, Point2I( 0, 0 ) ); + RectI panelTwoRect; + RectI splitRect; + + solvePanelConstraints( getSplitPoint(), panelOne, panelTwo, clientRect ); + + switch( mOrientation ) + { + case Horizontal: + panelOneRect.extent = Point2I( clientRect.extent.x, getSplitPoint().y ); + panelTwoRect = panelOneRect; + panelTwoRect.intersect( clientRect ); + panelTwoRect.point.y = panelOneRect.extent.y; + panelTwoRect.extent.y = clientRect.extent.y - panelOneRect.extent.y; + + // Generate new Splitter Rectangle + splitRect = panelTwoRect; + splitRect.extent.y = 0; + splitRect.inset( 0, -mSplitterSize ); + + panelOneRect.extent.y -= mSplitterSize; + panelTwoRect.point.y += mSplitterSize; + panelTwoRect.extent.y -= mSplitterSize; + + break; + + case Vertical: + panelOneRect.extent = Point2I( getSplitPoint().x, clientRect.extent.y ); + panelTwoRect = panelOneRect; + panelTwoRect.intersect( clientRect ); + panelTwoRect.point.x = panelOneRect.extent.x; + panelTwoRect.extent.x = clientRect.extent.x - panelOneRect.extent.x; + + // Generate new Splitter Rectangle + splitRect = panelTwoRect; + splitRect.extent.x = 0; + splitRect.inset( -mSplitterSize, 0 ); + + panelOneRect.extent.x -= mSplitterSize; + panelTwoRect.point.x += mSplitterSize; + panelTwoRect.extent.x -= mSplitterSize; + + break; + } + + // Update Split Rect + mSplitRect = splitRect; + + // Dock Appropriately + if( !( mFixedPanel == FirstPanel && !panelOne->isVisible() ) ) + dockControl( panelOne, panelOne->getDocking(), panelOneRect ); + if( !( mFixedPanel == FirstPanel && !panelTwo->isVisible() ) ) + dockControl( panelTwo, panelOne->getDocking(), panelTwoRect ); + + // + return false; +} + +void GuiSplitContainer::solvePanelConstraints( Point2I newDragPos, GuiContainer * firstPanel, GuiContainer * secondPanel, RectI clientRect ) +{ + if( !firstPanel || !secondPanel ) + return; + + if ( mOrientation == Horizontal ) + { + // Constrain based on Y axis (Horizontal Splitter) + + // This accounts for the splitter width + S32 splitterSize = (S32)(mSplitRect.extent.y * 0.5); + + // Collapsed fixed panel + if ( mFixedPanel == SecondPanel && !secondPanel->isVisible() ) + { + newDragPos = Point2I(mSplitPoint.x, getExtent().y - splitterSize ); + } + else if( mFixedPanel == SecondPanel && !firstPanel->isVisible() ) + { + newDragPos = Point2I(mSplitPoint.x, splitterSize ); + } + else // Normal constraints + { + //newDragPos.y -= splitterSize; + S32 newPosition = mClamp( newDragPos.y, + firstPanel->getMinExtent().y + splitterSize, + getExtent().y - secondPanel->getMinExtent().y - splitterSize ); + newDragPos = Point2I( mSplitPoint.x, newPosition ); + } + } + else + { + // Constrain based on X axis (Vertical Splitter) + + // This accounts for the splitter width + S32 splitterSize = (S32)(mSplitRect.extent.x * 0.5); + + // Collapsed fixed panel + if ( mFixedPanel == SecondPanel && !secondPanel->isVisible() ) + { + newDragPos = Point2I(getExtent().x - splitterSize, mSplitPoint.y ); + } + else if ( mFixedPanel == FirstPanel && !firstPanel->isVisible() ) + { + newDragPos = Point2I( splitterSize, mSplitPoint.x ); + } + else // Normal constraints + { + S32 newPosition = mClamp( newDragPos.x, firstPanel->getMinExtent().x + splitterSize, + getExtent().x - secondPanel->getMinExtent().x - splitterSize ); + newDragPos = Point2I( newPosition, mSplitPoint.y ); + } + } + + // Just in case, clamp to bounds of controls + newDragPos.x = mClamp( newDragPos.x, clientRect.point.x, clientRect.point.x + clientRect.extent.x ); + newDragPos.y = mClamp( newDragPos.y, clientRect.point.y, clientRect.point.y + clientRect.extent.y ); + + mSplitPoint = newDragPos; +} + + +void GuiSplitContainer::getCursor( GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent ) +{ + GuiCanvas *rootCtrl = getRoot(); + if ( !rootCtrl ) + return; + + S32 desiredCursor = 0; + RectI splitRect = getSplitRect(); + + // Figure out which cursor we want if we need one + if ( mOrientation == Horizontal ) + desiredCursor = PlatformCursorController::curResizeHorz; + else if ( mOrientation == Vertical ) + desiredCursor = PlatformCursorController::curResizeVert; + + PlatformWindow *platformWindow = static_cast(getRoot())->getPlatformWindow(); + AssertFatal( platformWindow != NULL,"GuiControl without owning platform window! This should not be possible." ); + + PlatformCursorController *cusrorController = platformWindow->getCursorController(); + AssertFatal( cusrorController != NULL,"PlatformWindow without an owned CursorController!" ); + + // Check to see if we need one or just the default... + + Point2I localPoint = Point2I( globalToLocalCoord( lastGuiEvent.mousePoint ) ); + if ( splitRect.pointInRect( localPoint ) || mDragging ) + { + // Do we need to change it or is it already set? + if ( rootCtrl->mCursorChanged != desiredCursor ) + { + // We've already changed the cursor, so set it back + if ( rootCtrl->mCursorChanged != -1 ) + cusrorController->popCursor(); + + // Now change the cursor shape + cusrorController->pushCursor( desiredCursor ); + rootCtrl->mCursorChanged = desiredCursor; + } + } + else if ( rootCtrl->mCursorChanged != -1 ) + { + // Just the default + cusrorController->popCursor(); + rootCtrl->mCursorChanged = -1; + } +} + +//----------------------------------------------------------------------------- +// Mouse Events +//----------------------------------------------------------------------------- +void GuiSplitContainer::onMouseDown( const GuiEvent &event ) +{ + GuiContainer *firstPanel = dynamic_cast(at(0)); + GuiContainer *secondPanel = dynamic_cast(at(1)); + + // This function will constrain the panels to their minExtents and update the mSplitPoint + if ( firstPanel && secondPanel ) + { + mouseLock(); + mDragging = true; + + RectI clientRect = getClientRect(); + Point2I newDragPos = globalToLocalCoord( event.mousePoint ); + + solvePanelConstraints(newDragPos, firstPanel, secondPanel, clientRect); + } +} + +//----------------------------------------------------------------------------- +void GuiSplitContainer::onMouseUp( const GuiEvent &event ) +{ + // If we've been dragging, we need to update the fixed panel extent. + // NOTE : This should ONLY ever happen in this function. the Fixed panel + // is to REMAIN FIXED unless the user changes it. + if ( mDragging ) + { + Point2I newSplitPoint = getSplitPoint(); + + // Update Fixed Panel Extent + if ( mFixedPanel == FirstPanel ) + mFixedPanelSize = ( mOrientation == Horizontal ) ? newSplitPoint.y : newSplitPoint.x; + else + mFixedPanelSize = ( mOrientation == Horizontal ) ? getExtent().y - newSplitPoint.y : getExtent().x - newSplitPoint.x; + + setUpdateLayout(); + } + + mDragging = false; + mouseUnlock(); +} + +//----------------------------------------------------------------------------- +void GuiSplitContainer::onMouseDragged( const GuiEvent &event ) +{ + GuiContainer *firstPanel = dynamic_cast(at(0)); + GuiContainer *secondPanel = dynamic_cast(at(1)); + + // This function will constrain the panels to their minExtents and update the mSplitPoint + if ( mDragging && firstPanel && secondPanel ) + { + RectI clientRect = getClientRect(); + Point2I newDragPos = globalToLocalCoord( event.mousePoint ); + + solvePanelConstraints(newDragPos, firstPanel, secondPanel, clientRect); + } +} + +GuiControl* GuiSplitContainer::findHitControl( const Point2I &pt, S32 initialLayer ) +{ + return Parent::findHitControl( pt, initialLayer ); +} \ No newline at end of file diff --git a/gui/containers/guiSplitContainer.h b/gui/containers/guiSplitContainer.h new file mode 100644 index 0000000..2b0f773 --- /dev/null +++ b/gui/containers/guiSplitContainer.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUI_SPLTCONTAINER_H_ +#define _GUI_SPLTCONTAINER_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif +#ifndef _GUI_PANEL_H_ +#include "gui/containers/guiPanel.h" +#endif +#ifndef _PLATFORMINPUT_H_ +#include "platform/platformInput.h" +#endif + + + +/// @addtogroup gui_container_group Containers +/// +/// @ingroup gui_group Gui System +/// @{ +class GuiSplitContainer : public GuiContainer +{ + typedef GuiContainer Parent; +public: + + enum Orientation + { + Vertical = 0, + Horizontal = 1 + }; + + enum FixedPanel + { + None = 0, + FirstPanel = 1, + SecondPanel + }; + + GuiSplitContainer(); + ~GuiSplitContainer(); + + DECLARE_CONOBJECT( GuiSplitContainer ); + DECLARE_DESCRIPTION( "A container that splits its area between two child controls.\n" + "The split ratio can be dynamically adjusted with a handle control between the two children.\n" + "Splitting can be either horizontal or vertical." ); + + // ConsoleObject + static void initPersistFields(); + virtual bool onAdd(); + + // GuiControl + virtual bool onWake(); + virtual void parentResized(const RectI &oldParentRect, const RectI &newParentRect); + virtual bool resize( const Point2I &newPosition, const Point2I &newExtent ); + virtual void onRender(Point2I offset, const RectI &updateRect); + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual GuiControl* findHitControl(const Point2I &pt, S32 initialLayer = -1); + + virtual bool layoutControls( RectI &clientRect ); + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + virtual inline Point2I getSplitPoint() { return mSplitPoint; }; + /// The Splitters entire Client Rectangle, this takes into account padding of this control + virtual inline RectI getSplitRect() { return mSplitRect; }; + virtual void solvePanelConstraints( Point2I newDragPos, GuiContainer * firstPanel, GuiContainer * secondPanel, RectI clientRect ); + virtual Point2I getMinExtent() const; + +protected: + + S32 mFixedPanel; + S32 mFixedPanelSize; + S32 mOrientation; + S32 mSplitterSize; + Point2I mSplitPoint; + RectI mSplitRect; + bool mDragging; + +}; +/// @} + +#endif // _GUI_SPLTCONTAINER_H_ \ No newline at end of file diff --git a/gui/containers/guiStackCtrl.cpp b/gui/containers/guiStackCtrl.cpp new file mode 100644 index 0000000..5753d03 --- /dev/null +++ b/gui/containers/guiStackCtrl.cpp @@ -0,0 +1,326 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/containers/guiStackCtrl.h" + +IMPLEMENT_CONOBJECT(GuiStackControl); + +static const EnumTable::Enums stackTypeEnum[] = +{ + { GuiStackControl::stackingTypeVert, "Vertical" }, + { GuiStackControl::stackingTypeHoriz,"Horizontal" }, + { GuiStackControl::stackingTypeDyn,"Dynamic" } +}; +static const EnumTable gStackTypeTable(3, &stackTypeEnum[0]); + +static const EnumTable::Enums stackHorizEnum[] = +{ + { GuiStackControl::horizStackLeft, "Left to Right" }, + { GuiStackControl::horizStackRight,"Right to Left" } +}; +static const EnumTable gStackHorizSizingTable(2, &stackHorizEnum[0]); + +static const EnumTable::Enums stackVertEnum[] = +{ + { GuiStackControl::vertStackTop, "Top to Bottom" }, + { GuiStackControl::vertStackBottom,"Bottom to Top" } +}; +static const EnumTable gStackVertSizingTable(2, &stackVertEnum[0]); + + + +GuiStackControl::GuiStackControl() +{ + setMinExtent(Point2I(16,16)); + mResizing = false; + mStackingType = stackingTypeVert; + mStackVertSizing = vertStackTop; + mStackHorizSizing = horizStackLeft; + mPadding = 0; + mIsContainer = true; + mDynamicSize = true; + mChangeChildSizeToFit = true; + mChangeChildPosition = true; +} + +void GuiStackControl::initPersistFields() +{ + + addGroup( "Stacking" ); + addField( "StackingType", TypeEnum, Offset(mStackingType, GuiStackControl), 1, &gStackTypeTable); + addField( "HorizStacking", TypeEnum, Offset(mStackHorizSizing, GuiStackControl), 1, &gStackHorizSizingTable); + addField( "VertStacking", TypeEnum, Offset(mStackVertSizing, GuiStackControl), 1, &gStackVertSizingTable); + addField( "Padding", TypeS32, Offset(mPadding, GuiStackControl)); + addField( "DynamicSize", TypeBool, Offset(mDynamicSize, GuiStackControl)); + addField( "ChangeChildSizeToFit", TypeBool, Offset(mChangeChildSizeToFit, GuiStackControl)); + addField( "ChangeChildPosition", TypeBool, Offset(mChangeChildPosition, GuiStackControl)); + endGroup( "Stacking" ); + + Parent::initPersistFields(); +} + +ConsoleMethod( GuiStackControl, freeze, void, 3, 3, "%stackCtrl.freeze(bool) - Prevents control from restacking") +{ + object->freeze(dAtob(argv[2])); +} + +ConsoleMethod( GuiStackControl, updateStack, void, 2, 2, "%stackCtrl.updateStack() - Restacks controls it owns") +{ + object->updatePanes(); +} + +bool GuiStackControl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + updatePanes(); + + return true; +} + +void GuiStackControl::onSleep() +{ + Parent::onSleep(); +} + +void GuiStackControl::updatePanes() +{ + // Prevent recursion + if(mResizing) + return; + + // Set Resizing. + mResizing = true; + + Point2I extent = getExtent(); + + // Do we need to stack horizontally? + if( ( extent.x > extent.y && mStackingType == stackingTypeDyn ) || mStackingType == stackingTypeHoriz ) + { + switch( mStackHorizSizing ) + { + case horizStackLeft: + stackFromLeft(); + break; + case horizStackRight: + stackFromRight(); + break; + } + } + // Or, vertically? + else if( ( extent.y > extent.x && mStackingType == stackingTypeDyn ) || mStackingType == stackingTypeVert) + { + switch( mStackVertSizing ) + { + case vertStackTop: + stackFromTop(); + break; + case vertStackBottom: + stackFromBottom(); + + break; + } + } + + // Clear Sizing Flag. + mResizing = false; +} + +void GuiStackControl::freeze(bool _shouldfreeze) +{ + mResizing = _shouldfreeze; +} + +void GuiStackControl::stackFromBottom() +{ + // Store the sum of the heights of our controls. + S32 totalHeight=0; + + Point2I curPos; + // If we go from the bottom, things are basically the same... + // except we have to assign positions in an arse backwards way + // (literally :P) + + // Figure out how high everything is going to be... + // Place each child... + for( S32 i = 0; i < size(); i++ ) + { + // Place control + GuiControl * gc = dynamic_cast(operator [](i)); + + if(gc && gc->isVisible() ) + { + Point2I childExtent = gc->getExtent(); + totalHeight += childExtent.y; + } + } + + // Figure out where we're starting... + curPos = getPosition(); + curPos.y += totalHeight; + + // Offset so the bottom control goes in the right place... + GuiControl * gc = dynamic_cast(operator [](size()-1)); + if(gc) + curPos.y -= gc->getExtent().y; + + + // Now work up from there! + for(S32 i=size()-1; i>=0; i--) + { + // Place control + GuiControl * gc = dynamic_cast(operator [](i)); + + if(gc) + { + // We must place the child... + + // Make it have our width but keep its height + Point2I childExtent = gc->getExtent(); + + // Update our state... + curPos.y -= childExtent.y - ((i > 0) ? mPadding : 0); + + // And resize... + gc->resize(curPos - getPosition(), Point2I(getExtent().x, childExtent.y)); + } + } +} + +void GuiStackControl::stackFromTop() +{ + // Position and resize everything... + Point2I curPos = Point2I(0, 0); + + // Get the number of visible children + S32 visibleChildrenCount = 0; + for (S32 i = 0; i < size(); i++) + { + GuiControl * gc = dynamic_cast( at(i) ); + if ( gc && gc->isVisible() ) + visibleChildrenCount++; + } + + // Place each child... + S32 visibleChildrenIndex = 0; + for (S32 i = 0; i < size(); i++) + { + // Place control + GuiControl * gc = dynamic_cast( at(i) ); + if ( gc && gc->isVisible() ) + { + Point2I childPos = curPos; + if ( !mChangeChildPosition ) + childPos.x = gc->getLeft(); + + // Make it have our width but keep its height, if appropriate + if ( mChangeChildSizeToFit ) + { + gc->resize(childPos, Point2I(getWidth(), gc->getHeight())); + } + else + { + gc->resize(childPos, Point2I(gc->getWidth(), gc->getHeight())); + } + + // Update our state... + curPos.y += gc->getHeight() + ((++visibleChildrenIndex < visibleChildrenCount) ? mPadding : 0); + } + } + + if ( mDynamicSize ) + { + // Conform our size to the sum of the child sizes. + curPos.x = getWidth(); + curPos.y = getMax( curPos.y , getMinExtent().y ); + resize( getPosition(), curPos ); + } +} + +void GuiStackControl::stackFromLeft() +{ + // Position and resize everything... + Point2I curPos = Point2I(0, 0); + + // Place each child... + for (S32 i = 0; i < size(); i++) + { + // Place control + GuiControl * gc = dynamic_cast(operator [](i)); + if ( gc && gc->isVisible() ) + { + Point2I childPos = curPos; + if ( !mChangeChildPosition ) + childPos.y = gc->getTop(); + + // Make it have our height but keep its width, if appropriate + if ( mChangeChildSizeToFit ) + { + gc->resize(childPos, Point2I( gc->getWidth(), getHeight() )); + } + else + { + gc->resize(childPos, Point2I( gc->getWidth(), gc->getHeight() )); + } + + // Update our state... + curPos.x += gc->getWidth() + ((i < size() - 1) ? mPadding : 0); + } + } + + if ( mDynamicSize ) + { + // Conform our size to the sum of the child sizes. + curPos.x = getMax( curPos.x, getMinExtent().x ); + curPos.y = getHeight(); + resize( getPosition(), curPos ); + } +} + +void GuiStackControl::stackFromRight() +{ + // ToDo +} + +bool GuiStackControl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if( !Parent::resize( newPosition, newExtent ) ) + return false; + + updatePanes(); + + // CodeReview This logic should be updated to correctly return true/false + // based on whether it sized it's children. [7/1/2007 justind] + return true; +} + +void GuiStackControl::addObject(SimObject *obj) +{ + Parent::addObject(obj); + + updatePanes(); +} + +void GuiStackControl::removeObject(SimObject *obj) +{ + Parent::removeObject(obj); + + updatePanes(); +} + +bool GuiStackControl::reOrder(SimObject* obj, SimObject* target) +{ + bool ret = Parent::reOrder(obj, target); + if (ret) + updatePanes(); + + return ret; +} + +void GuiStackControl::childResized(GuiControl *child) +{ + updatePanes(); +} \ No newline at end of file diff --git a/gui/containers/guiStackCtrl.h b/gui/containers/guiStackCtrl.h new file mode 100644 index 0000000..ad869b2 --- /dev/null +++ b/gui/containers/guiStackCtrl.h @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISTACKCTRL_H_ +#define _GUISTACKCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#include "gfx/gfxDevice.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +/// A stack of GUI controls. +/// +/// This maintains a horizontal or vertical stack of GUI controls. If one is deleted, or +/// resized, then the stack is resized to fit. The order of the stack is +/// determined by the internal order of the children (ie, order of addition). +/// +/// +/// @todo Make this support horizontal right to left stacks. +class GuiStackControl : public GuiControl +{ +protected: + typedef GuiControl Parent; + bool mResizing; + S32 mPadding; + S32 mStackHorizSizing; ///< Set from horizSizingOptions. + S32 mStackVertSizing; ///< Set from vertSizingOptions. + S32 mStackingType; + bool mDynamicSize; ///< Resize this control to fit the size of the children (width or height depends on the stack type) + bool mChangeChildSizeToFit; ///< Does the child resize to fit i.e. should a horizontal stack resize its children's height to fit? + bool mChangeChildPosition; ///< Do we reset the child's position in the opposite direction we are stacking? + +public: + GuiStackControl(); + + enum stackingOptions + { + horizStackLeft = 0,///< Stack from left to right when horizontal + horizStackRight, ///< Stack from right to left when horizontal + vertStackTop, ///< Stack from top to bottom when vertical + vertStackBottom, ///< Stack from bottom to top when vertical + stackingTypeVert, ///< Always stack vertically + stackingTypeHoriz, ///< Always stack horizontally + stackingTypeDyn ///< Dynamically switch based on width/height + }; + + + bool resize(const Point2I &newPosition, const Point2I &newExtent); + void childResized(GuiControl *child); + /// prevent resizing. useful when adding many items. + void freeze(bool); + + bool onWake(); + void onSleep(); + + void updatePanes(); + + virtual void stackFromLeft(); + virtual void stackFromRight(); + virtual void stackFromTop(); + virtual void stackFromBottom(); + + S32 getCount() { return size(); }; /// Returns the number of children in the stack + + void addObject(SimObject *obj); + void removeObject(SimObject *obj); + + bool reOrder(SimObject* obj, SimObject* target = 0); + + static void initPersistFields(); + + DECLARE_CONOBJECT(GuiStackControl); + DECLARE_CATEGORY( "Gui Containers" ); + DECLARE_DESCRIPTION( "A container controls that arranges its children in a vertical or\n" + "horizontal stack." ); +}; + +#endif \ No newline at end of file diff --git a/gui/containers/guiTabBookCtrl.cpp b/gui/containers/guiTabBookCtrl.cpp new file mode 100644 index 0000000..74adfd1 --- /dev/null +++ b/gui/containers/guiTabBookCtrl.cpp @@ -0,0 +1,799 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiTabBookCtrl.h" + +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/editor/guiEditCtrl.h" +#include "gui/controls/guiPopUpCtrl.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDrawUtil.h" + + +// So we can set tab alignment via gui editor +static const EnumTable::Enums tabAlignEnums[] = +{ + { GuiTabBookCtrl::AlignTop, "Top" }, + { GuiTabBookCtrl::AlignBottom,"Bottom" } +}; +static const EnumTable gTabAlignEnums(2,&tabAlignEnums[0]); + + +IMPLEMENT_CONOBJECT(GuiTabBookCtrl); + +GuiTabBookCtrl::GuiTabBookCtrl() +{ + VECTOR_SET_ASSOCIATION(mPages); + mTabHeight = 24; + mTabPosition = GuiTabBookCtrl::AlignTop; + mActivePage = NULL; + mHoverTab = NULL; + mHasTexture = false; + mBitmapBounds = NULL; + setExtent( 400, 300 ); + mPageRect = RectI(0,0,0,0); + mTabRect = RectI(0,0,0,0); + mFrontTabPadding = 0; + + mPages.reserve(12); + mTabMargin = 7; + mMinTabWidth = 64; + mIsContainer = true; + mSelectedPageNum = -1; + + mAllowReorder = false; + mDraggingTab = false; + mDraggingTabRect = false; +} + +void GuiTabBookCtrl::initPersistFields() +{ + addField( "TabPosition", TypeEnum, Offset( mTabPosition, GuiTabBookCtrl ), 1, &gTabAlignEnums ); + addField( "TabMargin", TypeS32, Offset( mTabMargin, GuiTabBookCtrl ) ); + addField( "MinTabWidth", TypeS32, Offset( mMinTabWidth, GuiTabBookCtrl ) ); + addField( "TabHeight", TypeS32, Offset( mTabHeight, GuiTabBookCtrl ) ); + addField( "AllowReorder", TypeBool, Offset( mAllowReorder, GuiTabBookCtrl ) ); + + addField( "FrontTabPadding", TypeS32, Offset( mFrontTabPadding, GuiTabBookCtrl ) ); + + Parent::initPersistFields(); +} + +// Empty for now, will implement for handling design time context menu for manipulating pages +ConsoleMethod( GuiTabBookCtrl, addPage, void, 2, 2, "(no arguments expected)") +{ + object->addNewPage(); +} + +//ConsoleMethod( GuiTabBookCtrl, removePage, void, 2, 2, "()") +//{ +//} + +bool GuiTabBookCtrl::onAdd() +{ + Parent::onAdd(); + + return true; +} + +void GuiTabBookCtrl::onRemove() +{ + Parent::onRemove(); +} + +void GuiTabBookCtrl::onChildRemoved( GuiControl* child ) +{ + for (S32 i = 0; i < mPages.size(); i++ ) + { + GuiTabPageCtrl* tab = mPages[i].Page; + if( tab == child ) + { + mPages.erase( i ); + break; + } + } + + // Calculate Page Information + calculatePageTabs(); + + // Active Index. + mSelectedPageNum = getMin( mSelectedPageNum, mPages.size() - 1 ); + + if ( mSelectedPageNum != -1 ) + { + // Select Page. + selectPage( mSelectedPageNum ); + } +} + +void GuiTabBookCtrl::onChildAdded( GuiControl *child ) +{ + GuiTabPageCtrl *page = dynamic_cast(child); + if( !page ) + { + Con::warnf("GuiTabBookCtrl::onChildAdded - attempting to add NON GuiTabPageCtrl as child page"); + SimObject *simObj = reinterpret_cast(child); + removeObject( simObj ); + if( mActivePage ) + { + mActivePage->addObject( simObj ); + } + else + { + Con::warnf("GuiTabBookCtrl::onChildAdded - unable to find active page to reassign ownership of new child control to, placing on parent"); + GuiControl *rent = getParent(); + if( rent ) + rent->addObject( simObj ); + } + return; + } + + TabHeaderInfo newPage; + + newPage.Page = page; + newPage.TabRow = -1; + newPage.TabColumn = -1; + + mPages.push_back( newPage ); + + // Calculate Page Information + calculatePageTabs(); + + if( page->getFitBook() ) + { + child->resize( mPageRect.point, mPageRect.extent ); + } + + // Select this Page + selectPage( page ); +} + +bool GuiTabBookCtrl::reOrder(SimObject* obj, SimObject* target) +{ + if ( !Parent::reOrder(obj, target) ) + return false; + + // Store the Selected Page. + GuiTabPageCtrl *selectedPage = NULL; + if ( mSelectedPageNum != -1 ) + selectedPage = mPages[mSelectedPageNum].Page; + + // Determine the Target Page Index. + S32 targetIndex = -1; + for( S32 i = 0; i < mPages.size(); i++ ) + { + const TabHeaderInfo &info = mPages[i]; + if ( info.Page == target ) + { + targetIndex = i; + break; + } + } + + if ( targetIndex == -1 ) + { + return false; + } + + for( S32 i = 0; i < mPages.size(); i++ ) + { + const TabHeaderInfo &info = mPages[i]; + if ( info.Page == obj ) + { + // Store Info. + TabHeaderInfo objPage = info; + + // Remove. + mPages.erase( i ); + // Insert. + mPages.insert( targetIndex, objPage ); + + break; + } + } + + // Update Tabs. + calculatePageTabs(); + + // Reselect Page. + selectPage( selectedPage ); + + return true; +} + +bool GuiTabBookCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + mHasTexture = mProfile->constructBitmapArray() > 0; + if( mHasTexture ) + { + mBitmapBounds = mProfile->mBitmapArrayRects.address(); + mTabHeight = mBitmapBounds[TabSelected].extent.y; + } + + calculatePageTabs(); + + return true; +} + +void GuiTabBookCtrl::onSleep() +{ + Parent::onSleep(); +} + +void GuiTabBookCtrl::addNewPage() +{ + char textbuf[1024]; + + GuiTabPageCtrl * page = new GuiTabPageCtrl(); + + page->setDataField( StringTable->insert("profile"), NULL, "GuiTabPageProfile"); + + dSprintf(textbuf, sizeof(textbuf), "TabBookPage%d_%d", getId(), page->getId()); + page->registerObject(textbuf); + + this->addObject( page ); +} + +bool GuiTabBookCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + calculatePageTabs(); + + return Parent::resize( newPosition, newExtent ); +} + +void GuiTabBookCtrl::childResized(GuiControl *child) +{ + Parent::childResized( child ); + + //child->resize( mPageRect.point, mPageRect.extent ); +} + +void GuiTabBookCtrl::onMouseDown(const GuiEvent &event) +{ + mDraggingTab = false; + mDraggingTabRect = false; + Point2I localMouse = globalToLocalCoord( event.mousePoint ); + if( mTabRect.pointInRect( localMouse ) ) + { + GuiTabPageCtrl *tab = findHitTab( localMouse ); + if( tab != NULL ) + { + selectPage( tab ); + mDraggingTab = mAllowReorder; + } + else + { + mDraggingTabRect = true; + } + } +} + +void GuiTabBookCtrl::onMouseUp(const GuiEvent &event) +{ + Parent::onMouseUp( event ); + + mDraggingTab = false; + mDraggingTabRect = false; +} + +void GuiTabBookCtrl::onMouseDragged(const GuiEvent &event) +{ + Parent::onMouseDragged( event ); + + if ( !mDraggingTab ) + return; + + GuiTabPageCtrl *selectedPage = NULL; + if ( mSelectedPageNum != -1 ) + selectedPage = mPages[mSelectedPageNum].Page; + + if ( !selectedPage ) + return; + + Point2I localMouse = globalToLocalCoord( event.mousePoint ); + if( mTabRect.pointInRect( localMouse ) ) + { + GuiTabPageCtrl *tab = findHitTab( localMouse ); + if( tab != NULL && tab != selectedPage ) + { + S32 targetIndex = -1; + for( S32 i = 0; i < mPages.size(); i++ ) + { + if( mPages[i].Page == tab ) + { + targetIndex = i; + break; + } + } + + if ( targetIndex > mSelectedPageNum ) + { + reOrder( tab, selectedPage ); + } + else + { + reOrder( selectedPage, tab ); + } + } + } +} + +void GuiTabBookCtrl::onMouseMove(const GuiEvent &event) +{ + Point2I localMouse = globalToLocalCoord( event.mousePoint ); + if( mTabRect.pointInRect( localMouse ) ) + { + GuiTabPageCtrl *tab = findHitTab( localMouse ); + if( tab != NULL && mHoverTab != tab ) + mHoverTab = tab; + else if ( !tab ) + mHoverTab = NULL; + } + Parent::onMouseMove( event ); +} + +void GuiTabBookCtrl::onMouseLeave( const GuiEvent &event ) +{ + mHoverTab = NULL; +} + +bool GuiTabBookCtrl::onMouseDownEditor(const GuiEvent &event, Point2I offset) +{ + bool handled = false; + Point2I localMouse = globalToLocalCoord( event.mousePoint ); + + if( mTabRect.pointInRect( localMouse ) ) + { + GuiTabPageCtrl *tab = findHitTab( localMouse ); + if( tab != NULL ) + { + selectPage( tab ); + handled = true; + } + } + +#ifdef TORQUE_TOOLS + // This shouldn't be called if it's not design time, but check just incase + if ( GuiControl::smDesignTime ) + { + // If we clicked in the editor and our addset is the tab book + // ctrl, select the child ctrl so we can edit it's properties + GuiEditCtrl* edit = GuiControl::smEditorHandle; + if( edit && ( edit->getAddSet() == this ) && mActivePage != NULL ) + edit->select( mActivePage ); + } +#endif + + // Return whether we handled this or not. + return handled; + +} + +void GuiTabBookCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + RectI tabRect = mTabRect; + tabRect.point += offset; + RectI pageRect = mPageRect; + pageRect.point += offset; + + // We're so nice we'll store the old modulation before we clear it for our rendering! :) + ColorI oldModulation; + GFX->getDrawUtil()->getBitmapModulation( &oldModulation ); + + // Wipe it out + GFX->getDrawUtil()->clearBitmapModulation(); + + Parent::onRender(offset, updateRect); + + // Clip to tab area + RectI savedClipRect = GFX->getClipRect(); + GFX->setClipRect( tabRect ); + + // Render our tabs + renderTabs( offset, tabRect ); + + // Restore Rect. + GFX->setClipRect( savedClipRect ); + + // Restore old modulation + GFX->getDrawUtil()->setBitmapModulation( oldModulation ); +} + +void GuiTabBookCtrl::renderTabs( const Point2I &offset, const RectI &tabRect ) +{ + // If the tab size is zero, don't render tabs, + // assuming it's a tab-less book + if( mPages.empty() || mTabHeight <= 0 ) + return; + + for( S32 i = 0; i < mPages.size(); i++ ) + { + const TabHeaderInfo ¤tTabInfo = mPages[i]; + RectI tabBounds = mPages[i].TabRect; + tabBounds.point += offset; + GuiTabPageCtrl *tab = mPages[i].Page; + if( tab != NULL ) + renderTab( tabBounds, tab ); + + // If we're on the last tab, draw the nice end piece + if( i + 1 == mPages.size() ) + { + Point2I tabEndPoint = Point2I(currentTabInfo.TabRect.point.x + currentTabInfo.TabRect.extent.x + offset.x, currentTabInfo.TabRect.point.y + offset.y); + Point2I tabEndExtent = Point2I((tabRect.point.x + tabRect.extent.x) - tabEndPoint.x, currentTabInfo.TabRect.extent.y); + RectI tabEndRect = RectI(tabEndPoint,tabEndExtent); + GFX->setClipRect( tabEndRect ); + renderFixedBitmapBordersFilled( tabEndRect, TabEnds + 1, mProfile ); + } + } +} + +void GuiTabBookCtrl::renderTab( RectI tabRect, GuiTabPageCtrl *tab ) +{ + StringTableEntry text = tab->getText(); + ColorI oldColor; + + GFX->getDrawUtil()->getBitmapModulation( &oldColor ); + + // Is this a skinned control? + if( mHasTexture && mProfile->mBitmapArrayRects.size() >= 9 ) + { + S32 indexMultiplier = 1; + switch( mTabPosition ) + { + case AlignTop: + case AlignBottom: + + if ( mActivePage == tab ) + indexMultiplier += TabSelected; + else if( mHoverTab == tab ) + indexMultiplier += TabHover; + else + indexMultiplier += TabNormal; + break; + } + + renderFixedBitmapBordersFilled( tabRect, indexMultiplier, mProfile ); + } + else + { + // If this isn't a skinned control or the bitmap is simply missing, handle it WELL + if ( mActivePage == tab ) + GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColor); + else if( mHoverTab == tab ) + GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColorHL); + else + GFX->getDrawUtil()->drawRectFill(tabRect, mProfile->mFillColorNA); + + } + + + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor); + + switch( mTabPosition ) + { + case AlignTop: + case AlignBottom: + renderJustifiedText( tabRect.point, tabRect.extent, text); + break; + } + + GFX->getDrawUtil()->setBitmapModulation( oldColor); + +} + +void GuiTabBookCtrl::setUpdate() +{ + Parent::setUpdate(); + + setUpdateRegion(Point2I(0,0), getExtent()); + + calculatePageTabs(); +} + +S32 GuiTabBookCtrl::calculatePageTabWidth( GuiTabPageCtrl *page ) +{ + if( !page ) + return mTabWidth; + + const char* text = page->getText(); + + if( !text || dStrlen(text) == 0 || mProfile->mFont == NULL ) + return mTabWidth; + + GFont *font = mProfile->mFont; + + return font->getStrNWidth( text, dStrlen(text) ); + +} + +const RectI GuiTabBookCtrl::getClientRect() +{ + + if( !mProfile || mProfile->mBitmapArrayRects.size() < NumBitmaps ) + return Parent::getClientRect(); + + return mPageRect; +} + + +void GuiTabBookCtrl::calculatePageTabs() +{ + // Short Circuit. + // + // If the tab size is zero, don't render tabs, + // assuming it's a tab-less book + if( mPages.empty() || mTabHeight <= 0 ) + { + mPageRect.point.x = 0; + mPageRect.point.y = 0; + mPageRect.extent.x = getWidth(); + mPageRect.extent.y = getHeight(); + return; + } + + S32 currRow = 0; + S32 currColumn = 0; + S32 currX = mFrontTabPadding; + S32 maxWidth = 0; + + for( S32 i = 0; i < mPages.size(); i++ ) + { + // Fetch Tab Width + S32 tabWidth = calculatePageTabWidth( mPages[i].Page ) + ( mTabMargin * 2 ); + tabWidth = getMax( tabWidth, mMinTabWidth ); + TabHeaderInfo &info = mPages[i]; + switch( mTabPosition ) + { + case AlignTop: + case AlignBottom: + // If we're going to go outside our bounds + // with this tab move it down a row + if( currX + tabWidth > getWidth() ) + { + // Calculate and Advance State. + maxWidth = getMax( tabWidth, maxWidth ); + balanceRow( currRow, currX ); + info.TabRow = ++currRow; + // Reset Necessaries + info.TabColumn = currColumn = maxWidth = currX = 0; + } + else + { + info.TabRow = currRow; + info.TabColumn = currColumn++; + } + + // Calculate Tabs Bounding Rect + info.TabRect.point.x = currX; + info.TabRect.extent.x = tabWidth; + info.TabRect.extent.y = mTabHeight; + + // Adjust Y Point based on alignment + if( mTabPosition == AlignTop ) + info.TabRect.point.y = ( info.TabRow * mTabHeight ); + else + info.TabRect.point.y = getHeight() - ( ( 1 + info.TabRow ) * mTabHeight ); + + currX += tabWidth; + break; + }; + } + + currRow++; + currColumn++; + + Point2I localPoint = getExtent(); + + // Calculate + switch( mTabPosition ) + { + case AlignTop: + + localPoint.y -= getTop(); + + mTabRect.point.x = 0; + mTabRect.extent.x = localPoint.x; + mTabRect.point.y = 0; + mTabRect.extent.y = currRow * mTabHeight; + + mPageRect.point.x = 0; + mPageRect.point.y = mTabRect.extent.y; + mPageRect.extent.x = mTabRect.extent.x; + mPageRect.extent.y = getHeight() - mTabRect.extent.y; + + break; + case AlignBottom: + mTabRect.point.x = 0; + mTabRect.extent.x = localPoint.x; + mTabRect.extent.y = currRow * mTabHeight; + mTabRect.point.y = getHeight() - mTabRect.extent.y; + + mPageRect.point.x = 0; + mPageRect.point.y = 0; + mPageRect.extent.x = mTabRect.extent.x; + mPageRect.extent.y = localPoint.y - mTabRect.extent.y; + + break; + }; + + +} + +void GuiTabBookCtrl::balanceRow( S32 row, S32 totalTabWidth ) +{ + // Short Circuit. + // + // If the tab size is zero, don't render tabs, + // and assume it's a tab-less tab-book - JDD + if( mPages.empty() || mTabHeight <= 0 ) + return; + + Vector rowTemp; + rowTemp.clear(); + + for( S32 i = 0; i < mPages.size(); i++ ) + { + TabHeaderInfo &info = mPages[i]; + + if(info.TabRow == row ) + rowTemp.push_back( &mPages[i] ); + } + + if( rowTemp.empty() ) + return; + + // Balance the tabs across the remaining space + S32 spaceToDivide = getWidth() - totalTabWidth; + S32 pointDelta = 0; + for( S32 i = 0; i < rowTemp.size(); i++ ) + { + TabHeaderInfo &info = *rowTemp[i]; + S32 extraSpace = (S32)spaceToDivide / ( rowTemp.size() ); + info.TabRect.extent.x += extraSpace; + info.TabRect.point.x += pointDelta; + pointDelta += extraSpace; + } +} + + +GuiTabPageCtrl *GuiTabBookCtrl::findHitTab( const GuiEvent &event ) +{ + return findHitTab( event.mousePoint ); +} + +GuiTabPageCtrl *GuiTabBookCtrl::findHitTab( Point2I hitPoint ) +{ + // Short Circuit. + // + // If the tab size is zero, don't render tabs, + // and assume it's a tab-less tab-book - JDD + if( mPages.empty() || mTabHeight <= 0 ) + return NULL; + + for( S32 i = 0; i < mPages.size(); i++ ) + { + if( mPages[i].TabRect.pointInRect( hitPoint ) ) + return mPages[i].Page; + } + return NULL; +} + +void GuiTabBookCtrl::selectPage( S32 index ) +{ + if( mPages.empty() ) + return; + + if( mPages.size() < index ) + return; + + // Select the page + selectPage( mPages[ index ].Page ); +} + + +void GuiTabBookCtrl::selectPage( GuiTabPageCtrl *page ) +{ + mSelectedPageNum = -1; + + Vector::iterator i = mPages.begin(); + for( S32 index = 0; i != mPages.end() ; i++, index++ ) + { + GuiTabPageCtrl *tab = (*i).Page; + if( page == tab ) + { + mActivePage = tab; + tab->setVisible( true ); + + mSelectedPageNum = index; + // Notify User + Con::executef( this, "onTabSelected", tab->getText(), Con::getIntArg(index) ); + } + else + tab->setVisible( false ); + } + setUpdateLayout( updateSelf ); +} + +bool GuiTabBookCtrl::onKeyDown(const GuiEvent &event) +{ + // Tab = Next Page + // Ctrl-Tab = Previous Page + if( 0 && event.keyCode == KEY_TAB ) + { + if( event.modifier & SI_PRIMARY_CTRL ) + selectPrevPage(); + else + selectNextPage(); + + return true; + } + + return Parent::onKeyDown( event ); +} + +void GuiTabBookCtrl::selectNextPage() +{ + if( mPages.empty() ) + return; + + if( mActivePage == NULL ) + mActivePage = mPages[0].Page; + + S32 nI = 0; + for( ; nI < mPages.size(); nI++ ) + { + GuiTabPageCtrl *tab = mPages[ nI ].Page; + if( tab == mActivePage ) + { + if( nI == ( mPages.size() - 1 ) ) + selectPage( 0 ); + else if ( nI + 1 <= ( mPages.size() - 1 ) ) + selectPage( nI + 1 ); + else + selectPage( 0 ); + return; + } + } +} + +void GuiTabBookCtrl::selectPrevPage() +{ + if( mPages.empty() ) + return; + + if( mActivePage == NULL ) + mActivePage = mPages[0].Page; + + S32 nI = 0; + for( ; nI < mPages.size(); nI++ ) + { + GuiTabPageCtrl *tab = mPages[ nI ].Page; + if( tab == mActivePage ) + { + if( nI == 0 ) + selectPage( mPages.size() - 1 ); + else + selectPage( nI - 1 ); + return; + } + } + +} +ConsoleMethod( GuiTabBookCtrl, selectPage, void, 3, 3, "(int pageIndex)") +{ + S32 pageIndex = dAtoi(argv[2]); + + object->selectPage(pageIndex); +} + +ConsoleMethod( GuiTabBookCtrl, getSelectedPage, S32, 2, 2, "(return S32 mSelectedPageNum)") +{ + return( object->getSelectedPageNum() ); +} diff --git a/gui/containers/guiTabBookCtrl.h b/gui/containers/guiTabBookCtrl.h new file mode 100644 index 0000000..e13b10d --- /dev/null +++ b/gui/containers/guiTabBookCtrl.h @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUI_TABBOOKCTRL_H_ +#define _GUI_TABBOOKCTRL_H_ + +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + +#ifndef _GUITABPAGECTRL_H_ +#include "gui/controls/guiTabPageCtrl.h" +#endif + + +/// Tab Book Control for creation of tabbed dialogs +/// +/// @see GUI for an overview of the Torque GUI system. +/// +/// @section GuiTabBookCtrl_Intro Introduction +/// +/// GuiTabBookCtrl is a container class that holds children of type GuiTabPageCtrl +/// +/// GuiTabBookCtrl creates an easy to work with system for creating tabbed dialogs +/// allowing for creation of dialogs that store a lot of information in a small area +/// by separating the information into pages which are changeable by clicking their +/// page title on top or bottom of the control +/// +/// tabs may be aligned to be on top or bottom of the book and are changeable while +/// the GUI editor is open for quick switching between pages allowing multi-page dialogs +/// to be edited quickly and easily. +/// +/// The control may only contain children of type GuiTabPageCtrl. +/// If a control is added to the Book that is not of type GuiTabPageCtrl, it will be +/// removed and relocated to the currently active page of the control. +/// If there is no active page in the book, the child control will be relocated to the +/// parent of the book. +/// +/// @ref GUI has an overview of the GUI system. +/// +/// @nosubgrouping +class GuiTabBookCtrl : public GuiContainer +{ +public: + enum TabPosition + { + AlignTop, ///< Align the tabs on the top of the tab book control + AlignBottom ///< Align the tabs on the bottom of the tab book control + }; + + struct TabHeaderInfo + { + GuiTabPageCtrl *Page; + S32 TextWidth; + S32 TabRow; + S32 TabColumn; + RectI TabRect; + }; + +private: + + typedef GuiContainer Parent; ///< typedef for parent class access + +protected: + + RectI mPageRect; ///< Rectangle of the tab page portion of the control + RectI mTabRect; ///< Rectangle of the tab portion of the control + Vector mPages; ///< Vector of pages contained by the control + GuiTabPageCtrl* mActivePage; ///< Pointer to the active (selected) tab page child control + GuiTabPageCtrl* mHoverTab; ///< Pointer to the tab page that currently has the mouse positioned ontop of its tab + S32 mMinTabWidth; ///< Minimum Width a tab will display as. + TabPosition mTabPosition; ///< Current tab position (see alignment) + S32 mTabHeight; ///< Current tab height + S32 mTabWidth; ///< Current tab width + S32 mTabMargin; ///< Margin left/right of tab text in tab + S32 mSelectedPageNum; ///< Current selected tab position + bool mAllowReorder; ///< Allow the user to reorder tabs by dragging them + S32 mFrontTabPadding; ///< Padding to the Left of the first Tab + bool mDraggingTab; + bool mDraggingTabRect; + + enum + { + TabSelected = 0, ///< Index of selected tab texture + TabNormal, ///< Index of normal tab texture + TabHover, ///< Index of hover tab texture + TabEnds, ///< Index of end lines for horizontal tabs + NumBitmaps ///< Number of bitmaps in this array + }; + bool mHasTexture; ///< Indicates whether we have a texture to render the tabs with + RectI *mBitmapBounds;///< Array of rectangles identifying textures for tab book + + public: + + GuiTabBookCtrl(); + DECLARE_CONOBJECT(GuiTabBookCtrl); + + static void initPersistFields(); + + /// @name Control Events + /// @{ + bool onAdd(); + void onRemove(); + bool onWake(); + void onSleep(); + void onRender( Point2I offset, const RectI &updateRect ); + /// @} + + /// @name Child events + /// @{ + void onChildRemoved( GuiControl* child ); + void onChildAdded( GuiControl *child ); + bool reOrder(SimObject* obj, SimObject* target); + /// @} + + /// @name Rendering methods + /// @{ + + /// Tab rendering routine, iterates through all tabs rendering one at a time + /// @param offset the render offset to accomodate global coords + /// @param tabRect the Rectangle in which these tabs are to be rendered + void renderTabs( const Point2I &offset, const RectI &tabRect ); + + /// Tab rendering subroutine, renders one tab with specified options + /// @param tabRect the rectangle to render the tab into + /// @param tab pointer to the tab page control for which to render the tab + void renderTab( RectI tabRect, GuiTabPageCtrl* tab ); + + /// @} + + /// @name Page Management + /// @{ + + /// Create a new tab page child in the book + /// + /// Pages created are not titled and appear with no text on their tab when created. + /// This may change in the future. + void addNewPage(); + + /// Select a tab page based on an index + /// @param index The index in the list that specifies which page to select + void selectPage( S32 index ); + + /// Select a tab page by a pointer to that page + /// @param page A pointer to the GuiTabPageCtrl to select. This page must be a child of the tab book. + void selectPage( GuiTabPageCtrl *page ); + + /// Get the current selected tab number + S32 getSelectedPageNum(){ return mSelectedPageNum; }; + + /// Select the Next page in the tab book + void selectNextPage(); + + /// Select the Previous page in the tab book + void selectPrevPage(); + + /// @} + + /// @name Internal Utility Functions + /// @{ + + /// Update ourselves by hooking common GuiControl functionality. + void setUpdate(); + + /// Balance a top/bottom tab row + void balanceRow( S32 row, S32 totalTabWidth ); + + /// Balance a left/right tab column + void balanceColumn( S32 row, S32 totalTabWidth ); + + /// Calculate the tab width of a page, given it's caption + S32 calculatePageTabWidth( GuiTabPageCtrl *page ); + + /// Calculate Page Header Information + void calculatePageTabs(); + + /// Get client area of tab book + virtual const RectI getClientRect(); + + /// Find the tab that was hit by the current event, if any + /// @param event The GuiEvent that caused this function call + GuiTabPageCtrl *findHitTab( const GuiEvent &event ); + + /// Find the tab that was hit, based on a point + /// @param hitPoint A Point2I that specifies where to search for a tab hit + GuiTabPageCtrl *findHitTab( Point2I hitPoint ); + + /// @} + + /// @name Sizing + /// @{ + + /// Rezize our control + /// This method is overridden so that we may handle resizing of our child tab + /// pages when we are resized. + /// This ensures we keep our sizing in sync when we are moved or sized. + /// + /// @param newPosition The new position of the control + /// @param newExtent The new extent of the control + bool resize(const Point2I &newPosition, const Point2I &newExtent); + + /// Called when a child page is resized + /// This method is overridden so that we may handle resizing of our child tab + /// pages when one of them is resized. + /// This ensures we keep our sizing in sync when we our children are sized or moved. + /// + /// @param child A pointer to the child control that has been resized + void childResized(GuiControl *child); + + /// @} + + + virtual bool onKeyDown(const GuiEvent &event); + + + /// @name Mouse Events + /// @{ + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseMove(const GuiEvent &event); + virtual void onMouseLeave(const GuiEvent &event); + virtual bool onMouseDownEditor(const GuiEvent &event, Point2I offset); + /// @} +}; + +#endif //_GUI_TABBOOKCTRL_H_ diff --git a/gui/containers/guiWindowCollapseCtrl.cpp b/gui/containers/guiWindowCollapseCtrl.cpp new file mode 100644 index 0000000..a364c9b --- /dev/null +++ b/gui/containers/guiWindowCollapseCtrl.cpp @@ -0,0 +1,1216 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiWindowCtrl.h" +#include "gui/containers/guiWindowCollapseCtrl.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/containers/guiRolloutCtrl.h" + +IMPLEMENT_CONOBJECT(GuiWindowCollapseCtrl); + +//----------------------------------------------------------------------------- + + +GuiWindowCollapseCtrl::GuiWindowCollapseCtrl() : + mCollapseGroup(-1), + mCollapseGroupNum(-1), + mIsCollapsed(false), + mIsMouseResizing(false) +{ +} + +void GuiWindowCollapseCtrl::initPersistFields() +{ + addField("CollapseGroup", TypeS32, Offset(mCollapseGroup,GuiWindowCollapseCtrl));//debug only + addField("CollapseGroupNum", TypeS32, Offset(mCollapseGroupNum,GuiWindowCollapseCtrl));//debug only + + Parent::initPersistFields(); +} + +void GuiWindowCollapseCtrl::getSnappableWindows( Vector &outVector, Vector &windowOutVector) +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + S32 parentSize = parent->size(); + for( S32 i = 0; i < parentSize; i++ ) + { + GuiWindowCollapseCtrl *childWindow = dynamic_cast(parent->at(i)); + if( !childWindow || !childWindow->isVisible() || childWindow == this || childWindow->mEdgeSnap == false) + continue; + + outVector.push_back(EdgeRectI(childWindow->getGlobalBounds(), mResizeMargin)); + windowOutVector.push_back(childWindow); + } + +} + +void GuiWindowCollapseCtrl::onMouseDown(const GuiEvent &event) +{ + setUpdate(); + + mOrigBounds = getBounds(); + + mMouseDownPosition = event.mousePoint; + Point2I localPoint = globalToLocalCoord(event.mousePoint); + + //select this window - move it to the front, and set the first responder + selectWindow(); + + mMouseMovingWin = false; + + S32 hitEdges = findHitEdges( event.mousePoint ); + + mResizeEdge = edgeNone; + + // Set flag by default so we only clear it + // if we don't match either edge + mMouseResizeHeight = true; + + // Check Bottom/Top edges (Mutually Exclusive) + if( mResizeHeight && hitEdges & edgeBottom ) + mResizeEdge |= edgeBottom; + else if( mResizeHeight && hitEdges & edgeTop ) + mResizeEdge |= edgeTop; + else + mMouseResizeHeight = false; + + // Set flag by default so we only clear it + // if we don't match either edge + mMouseResizeWidth = true; + + // Check Left/Right edges (Mutually Exclusive) + if( mResizeWidth && hitEdges & edgeLeft ) + mResizeEdge |= edgeLeft; + else if( mResizeWidth && hitEdges & edgeRight ) + mResizeEdge |= edgeRight; + else + mMouseResizeWidth = false; + + + //if we clicked within the title bar + if ( !(mResizeEdge & edgeTop) && localPoint.y < mTitleHeight) + { + //if we clicked on the close button + if (mCanClose && mCloseButton.pointInRect(localPoint)) + { + mPressClose = mCanClose; + } + else if (mCanMaximize && mMaximizeButton.pointInRect(localPoint)) + { + mPressMaximize = mCanMaximize; + } + else if (mCanMinimize && mMinimizeButton.pointInRect(localPoint)) + { + mPressMinimize = mCanMinimize; + } + + //else we clicked within the title + else + { + S32 docking = getDocking(); + if( docking == Docking::dockInvalid || docking == Docking::dockNone ) + mMouseMovingWin = mCanMove; + + mMouseResizeWidth = false; + mMouseResizeHeight = false; + } + } + + + if (mMouseMovingWin || mResizeEdge != edgeNone || + mPressClose || mPressMaximize || mPressMinimize) + { + mouseLock(); + } + else + { + + GuiControl *ctrl = findHitControl(localPoint); + if (ctrl && ctrl != this) + ctrl->onMouseDown(event); + + } +} + +void GuiWindowCollapseCtrl::onMouseDragged(const GuiEvent &event) +{ + GuiControl *parent = getParent(); + GuiCanvas *root = getRoot(); + if ( !root ) return; + + Point2I deltaMousePosition = event.mousePoint - mMouseDownPosition; + + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + bool resizeX = false; + bool resizeY = false; + + mRepositionWindow = false; + mResizeWindow = false; + + if (mMouseMovingWin && parent) + { + if( parent != root ) + { + newPosition.x = mOrigBounds.point.x + deltaMousePosition.x; + newPosition.y = getMax(0, mOrigBounds.point.y + deltaMousePosition.y ); + mRepositionWindow = true; + } + else + { + newPosition.x = getMax(0, getMin(parent->getWidth() - getWidth(), mOrigBounds.point.x + deltaMousePosition.x)); + newPosition.y = getMax(0, getMin(parent->getHeight() - getHeight(), mOrigBounds.point.y + deltaMousePosition.y)); + } + + // Check snapping to other windows + if( mEdgeSnap ) + { + RectI bounds = getGlobalBounds(); + bounds.point = mOrigBounds.point + deltaMousePosition; + EdgeRectI edges = EdgeRectI( bounds, mResizeMargin ); + + Vector snapList; + Vector windowList; + getSnappableWindows( snapList, windowList ); + for( S32 i =0; i < snapList.size(); i++ ) + { + // Compare Edges for hit + EdgeRectI &snapRect = snapList[i]; + EdgeRectI orignalSnapRect = snapRect; + + + if(windowList[i]->mCollapseGroupNum == -1) + { + // Add some buffer room for snap detection: BOTTOM HITS TOP: BECOMES "PARENT" + snapRect = orignalSnapRect; + snapRect.top.position.y = snapRect.top.position.y-12; + if( edges.bottom.hit( snapRect.top ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newPosition.y = snapRect.top.position.y - bounds.extent.y; + newPosition.x = snapRect.left.position.x; + } + } + + if( (windowList[i]->mCollapseGroupNum == -1) || (windowList[i]->mCollapseGroupNum == mCollapseGroupNum - 1) || + (!parent->mCollapseGroupVec.empty() && parent->mCollapseGroupVec[windowList[i]->mCollapseGroup].last() == windowList[i]) ) + { + // Add some buffer room for snap detection: TOP HITS BOTTOM: BECOMES "CHILD" + snapRect = orignalSnapRect; + snapRect.bottom.position.y = snapRect.bottom.position.y+12; + if( edges.top.hit( snapRect.bottom ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newPosition.y = snapRect.bottom.position.y; + newPosition.x = snapRect.left.position.x; + } + } + } + } + } + else if(mPressClose || mPressMaximize || mPressMinimize) + { + setUpdate(); + return; + } + else + { + if( ( !mMouseResizeHeight && !mMouseResizeWidth ) || !parent ) + return; + + mResizeWindow = true; + if( mResizeEdge & edgeBottom) + { + newExtent.y = getMin(parent->getHeight(), mOrigBounds.extent.y + deltaMousePosition.y); + resizeY = true; + } + else if ( mResizeEdge & edgeTop ) + { + newPosition.y = mOrigBounds.point.y + deltaMousePosition.y; + newExtent.y = getMin(parent->getHeight(), mOrigBounds.extent.y - deltaMousePosition.y); + resizeY = true; + } + + if( mResizeEdge & edgeRight ) + { + newExtent.x = getMin(parent->getWidth(), mOrigBounds.extent.x + deltaMousePosition.x); + resizeX = true; + } + else if( mResizeEdge & edgeLeft ) + { + newPosition.x = mOrigBounds.point.x + deltaMousePosition.x; + newExtent.x = getMin(parent->getWidth(), mOrigBounds.extent.x - deltaMousePosition.x); + resizeX = true; + } + } + + // if changed positions, change children positions + // if changed extent, children should conform to width and adjust to height + + if(mCollapseGroup >= 0 && mRepositionWindow == true) + moveWithCollapseGroup(deltaMousePosition); + + // resize myself + Point2I pos = parent->localToGlobalCoord(getPosition()); + root->addUpdateRegion(pos, getExtent()); + + // this makes me think that collapseable windows need to be there own class, there are far too many checks needed in + // order to grab the proper procedure. + if(mCollapseGroup >= 0 && mResizeWindow == true ) + { + if(newExtent.y >= getMinExtent().y && newExtent.x >= getMinExtent().x) + { + mIsMouseResizing = true; + bool canResize = resizeCollapseGroup( resizeX, resizeY, (getPosition() - newPosition), (getExtent() - newExtent) ); + + if(canResize == true) + resize(newPosition, newExtent); + } + } + else + resize(newPosition, newExtent); +} + +void GuiWindowCollapseCtrl::onMouseUp(const GuiEvent &event) +{ + bool closing = mPressClose; + bool maximizing = mPressMaximize; + bool minimizing = mPressMinimize; + mPressClose = false; + mPressMaximize = false; + mPressMinimize = false; + + TORQUE_UNUSED(event); + mouseUnlock(); + + mMouseMovingWin = false; + mMouseResizeWidth = false; + mMouseResizeHeight = false; + + bool snapSignal = false; + + GuiControl *parent = getParent(); + if (! parent) + return; + + if( mIsMouseResizing ) + { + mIsMouseResizing = false; + return; + } + + //see if we take an action + Point2I localPoint = globalToLocalCoord(event.mousePoint); + if (closing && mCloseButton.pointInRect(localPoint)) + { + // here is where were going to put our other if statement + //if the window closes, and there were only 2 windows in the array, then just delete the array and default there params + //if not, delete the window from the array, default its params, and renumber the windows + + Con::evaluate(mCloseCommand); + + } + else if (maximizing && mMaximizeButton.pointInRect(localPoint)) + { + if (mMaximized) + { + //resize to the previous position and extent, bounded by the parent + resize(Point2I(getMax(0, getMin(parent->getWidth() - mStandardBounds.extent.x, mStandardBounds.point.x)), + getMax(0, getMin(parent->getHeight() - mStandardBounds.extent.y, mStandardBounds.point.y))), + mStandardBounds.extent); + //set the flag + mMaximized = false; + } + else + { + //only save the position if we're not minimized + if (! mMinimized) + { + mStandardBounds = getBounds(); + } + else + { + mMinimized = false; + } + + //resize to fit the parent + resize(Point2I(0, 0), parent->getExtent()); + + //set the flag + mMaximized = true; + } + } + else if (minimizing && mMinimizeButton.pointInRect(localPoint)) + { + if (mMinimized) + { + //resize to the previous position and extent, bounded by the parent + resize(Point2I(getMax(0, getMin(parent->getWidth() - mStandardBounds.extent.x, mStandardBounds.point.x)), + getMax(0, getMin(parent->getHeight() - mStandardBounds.extent.y, mStandardBounds.point.y))), + mStandardBounds.extent); + //set the flag + mMinimized = false; + } + else + { + if (parent->getWidth() < 100 || parent->getHeight() < mTitleHeight + 3) + return; + + //only save the position if we're not maximized + if (! mMaximized) + { + mStandardBounds = getBounds(); + } + else + { + mMaximized = false; + } + + //first find the lowest unused minimized index up to 32 minimized windows + U32 indexMask = 0; + iterator i; + S32 count = 0; + for (i = parent->begin(); i != parent->end() && count < 32; i++) + { + count++; + S32 index; + GuiWindowCollapseCtrl *ctrl = dynamic_cast(*i); + if (ctrl && ctrl->isMinimized(index)) + { + indexMask |= (1 << index); + } + } + + //now find the first unused bit + for (count = 0; count < 32; count++) + { + if (! (indexMask & (1 << count))) break; + } + + //if we have more than 32 minimized windows, use the first position + count = getMax(0, count); + + //this algorithm assumes all window have the same title height, and will minimize to 98 pix + Point2I newExtent(98, mTitleHeight); + + //first, how many can fit across + S32 numAcross = getMax(1, (parent->getWidth() / newExtent.x + 2)); + + //find the new "mini position" + Point2I newPosition; + newPosition.x = (count % numAcross) * (newExtent.x + 2) + 2; + newPosition.y = parent->getHeight() - (((count / numAcross) + 1) * (newExtent.y + 2)) - 2; + + //find the minimized position and extent + resize(newPosition, newExtent); + + //set the index so other windows will not try to minimize to the same location + mMinimizeIndex = count; + + //set the flag + mMinimized = true; + } + } + else if ( !(mResizeEdge & edgeTop) && localPoint.y < mTitleHeight && event.mousePoint == mMouseDownPosition) + { + //if we clicked on the close button + if (mCanClose && mCloseButton.pointInRect(localPoint)) + return; + else if (mCanMaximize && mMaximizeButton.pointInRect(localPoint)) + return; + else if (mCanMinimize && mMinimizeButton.pointInRect(localPoint)) + return; + else + toggleCollapseGroup(); + } + else if( mEdgeSnap ) + { + Point2I deltaMousePosition = event.mousePoint - mMouseDownPosition; + + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + RectI bounds = getGlobalBounds(); + bounds.point = mOrigBounds.point + deltaMousePosition; + EdgeRectI edges = EdgeRectI( bounds, mResizeMargin ); + + Vector snapList; + Vector windowList; + getSnappableWindows( snapList, windowList ); + for( S32 i =0; i < snapList.size(); i++ ) + { + // Compare Edges for hit + EdgeRectI &snapRect = snapList[i]; + EdgeRectI orignalSnapRect = snapRect; + + // Grab Window being hit + GuiWindowCollapseCtrl *hitWindow = windowList[i]; + + if(windowList[i]->mCollapseGroupNum == -1) + { + // Add some buffer room for snap detection : BECOMES "PARENT" + snapRect = orignalSnapRect; + snapRect.top.position.y = snapRect.top.position.y-12; + if( edges.bottom.hit( snapRect.top ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newExtent.x = snapRect.right.position.x - snapRect.left.position.x; + + moveToCollapseGroup(hitWindow, 0); + + snapSignal = true; + } + } + + if( (windowList[i]->mCollapseGroupNum == -1) || (windowList[i]->mCollapseGroupNum == mCollapseGroupNum - 1) || + (!parent->mCollapseGroupVec.empty() && parent->mCollapseGroupVec[windowList[i]->mCollapseGroup].last() == windowList[i]) ) + { + // Add some buffer room for snap detection : BECOMES "CHILD" + snapRect = orignalSnapRect; + snapRect.bottom.position.y = snapRect.bottom.position.y+12; + if( edges.top.hit( snapRect.bottom ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newExtent.x = snapRect.right.position.x - snapRect.left.position.x; + moveToCollapseGroup(hitWindow, 1); + + snapSignal = true; + } + } + } + resize(newPosition, newExtent); + + if(mCollapseGroup >= 0 && mCollapseGroupNum != 0 && snapSignal == false) + moveFromCollapseGroup(); + } +} + +void GuiWindowCollapseCtrl::moveFromCollapseGroup() +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + S32 groupVec = mCollapseGroup; + S32 vecPos = mCollapseGroupNum; + S32 groupVecCount = parent->mCollapseGroupVec[groupVec].size() - 1; + + typedef Vector< GuiWindowCollapseCtrl *> CollapseGroupNumVec; + CollapseGroupNumVec mCollapseGroupNumVec; + + if( groupVecCount > vecPos ) + { + if (vecPos == 1) + { + + + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin(); + for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ ) + { + if((*iter)->mCollapseGroupNum >= vecPos) + mCollapseGroupNumVec.push_back((*iter)); + } + + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1; + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1; + parent->mCollapseGroupVec[groupVec].erase(U32(0)); + parent->mCollapseGroupVec[groupVec].setSize(groupVecCount - 1); + parent->mCollapseGroupVec.erase(groupVec); + if(groupVec > 0) + parent->mCollapseGroupVec.setSize(groupVec); + + //iterate through the newly created array; delete my references in the old group, create a new group and organize me accord. + S32 assignWindowNumber = 0; + CollapseGroupNumVec::iterator iter2 = mCollapseGroupNumVec.begin(); + for(; iter2 != mCollapseGroupNumVec.end(); iter2++ ) + { + (*iter2)->mCollapseGroup = (parent->mCollapseGroupVec.size()); + (*iter2)->mCollapseGroupNum = assignWindowNumber; + + assignWindowNumber++; + groupVecCount--; + } + + parent->mCollapseGroupVec.push_back( mCollapseGroupNumVec ); + } + else + { + //iterate through the group i was once in, gather myself and the controls below me and store them in an array + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin(); + for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ ) + { + if((*iter)->mCollapseGroupNum >= vecPos) + mCollapseGroupNumVec.push_back((*iter)); + } + + //iterate through the newly created array; delete my references in the old group, create a new group and organize me accord. + S32 assignWindowNumber = 0; + CollapseGroupNumVec::iterator iter2 = mCollapseGroupNumVec.begin(); + for(; iter2 != mCollapseGroupNumVec.end(); iter2++ ) + { + parent->mCollapseGroupVec[groupVec].pop_back(); + parent->mCollapseGroupVec[groupVec].setSize(groupVecCount); + (*iter2)->mCollapseGroup = (parent->mCollapseGroupVec.size()); + (*iter2)->mCollapseGroupNum = assignWindowNumber; + + assignWindowNumber++; + groupVecCount--; + } + + parent->mCollapseGroupVec.push_back( mCollapseGroupNumVec ); + } + } + else + { + parent->mCollapseGroupVec[groupVec].erase(mCollapseGroupNum); + parent->mCollapseGroupVec[groupVec].setSize(groupVecCount); + mCollapseGroup = -1; + mCollapseGroupNum = -1; + + if(groupVecCount <= 1) + { + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1; + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1; + parent->mCollapseGroupVec[groupVec].erase(U32(0)); + parent->mCollapseGroupVec[groupVec].setSize(groupVecCount - 1); + parent->mCollapseGroupVec.erase(groupVec); + //if(groupVec > 0) + // parent->mCollapseGroupVec.setSize(groupVec); + + } + } + + refreshCollapseGroups(); +} +void GuiWindowCollapseCtrl::moveToCollapseGroup(GuiWindowCollapseCtrl* hitWindow, bool orientation ) +{ + // orientation 0 - window in question is being connected to top of another window + // orientation 1 - window in question is being connected to bottom of another window + + GuiControl *parent = getParent(); + if( !parent ) + return; + + S32 groupVec = mCollapseGroup; + S32 attatchedGroupVec = hitWindow->mCollapseGroup; + S32 vecPos = mCollapseGroupNum; + + if(mCollapseGroup == attatchedGroupVec && vecPos != -1) + return; + + typedef Vector< GuiWindowCollapseCtrl *> CollapseGroupNumVec; + CollapseGroupNumVec mCollapseGroupNumVec; + + // window colliding with is not in a collapse group + if(hitWindow->mCollapseGroup < 0) + { + // we(the collider) are in a group of windows + if(mCollapseGroup >= 0) + { + S32 groupVecCount = parent->mCollapseGroupVec[groupVec].size() - 1; + + //copy pointer window data in my array, and store in a temp array + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin(); + for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ ) + { + if((*iter)->mCollapseGroupNum >= vecPos) + { + mCollapseGroupNumVec.push_back((*iter)); + groupVecCount--; + } + } + + // kill my old array group and erase its footprints + if( vecPos <= 1 || groupVecCount == 0 ) + { + //sanity check, always reset the first window just to be sure + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1; + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1; + + parent->mCollapseGroupVec[groupVec].clear(); + parent->mCollapseGroupVec[groupVec].setSize(U32(0)); + parent->mCollapseGroupVec.erase(groupVec); + + if(groupVec > 0) + parent->mCollapseGroupVec.setSize(groupVec); + } + + // push the collided window + if(orientation == 0) + mCollapseGroupNumVec.push_back(hitWindow); + else + mCollapseGroupNumVec.push_front(hitWindow); + + // iterate and renumber the temp array + S32 assignWindowNumber = 0; + CollapseGroupNumVec::iterator iter2 = mCollapseGroupNumVec.begin(); + for(; iter2 != mCollapseGroupNumVec.end(); iter2++ ) + { + (*iter2)->mCollapseGroup = (parent->mCollapseGroupVec.size()); + (*iter2)->mCollapseGroupNum = assignWindowNumber; + + assignWindowNumber++; + } + + // push the temp array in the guiControl array holder + parent->mCollapseGroupVec.push_back( mCollapseGroupNumVec ); + } + else + { + if(orientation == 0) + { + mCollapseGroupNumVec.push_front(hitWindow); + mCollapseGroupNumVec.push_front(this); + } + else + { + mCollapseGroupNumVec.push_front(this); + mCollapseGroupNumVec.push_front(hitWindow); + } + + S32 assignWindowNumber = 0; + CollapseGroupNumVec::iterator iter = mCollapseGroupNumVec.begin(); + for(; iter != mCollapseGroupNumVec.end(); iter++ ) + { + (*iter)->mCollapseGroup = (parent->mCollapseGroupVec.size()); + (*iter)->mCollapseGroupNum = assignWindowNumber; + assignWindowNumber++; + } + + parent->mCollapseGroupVec.push_back( mCollapseGroupNumVec ); + } + } + else // window colliding with *IS* in a collapse group + { + if(mCollapseGroup >= 0) + { + S32 groupVecCount = parent->mCollapseGroupVec[groupVec].size() - 1; + + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin(); + for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ ) + { + if((*iter)->mCollapseGroupNum >= vecPos) + { + // push back the pointer window controls to the collided array + parent->mCollapseGroupVec[attatchedGroupVec].push_back((*iter)); + groupVecCount--; + } + } + + if( vecPos <= 1 || groupVecCount == 0 ) + { + //sanity check, always reset the first window just to be sure + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroup = -1; + parent->mCollapseGroupVec[groupVec].first()->mCollapseGroupNum = -1; + + parent->mCollapseGroupVec[groupVec].clear(); + parent->mCollapseGroupVec[groupVec].setSize(U32(0)); + parent->mCollapseGroupVec.erase(groupVec); + + if(groupVec > 0) + parent->mCollapseGroupVec.setSize(groupVec); + } + + // since we killed my old array group, run in case the guiControl array moved me down a notch + if(attatchedGroupVec > groupVec ) + attatchedGroupVec--; + + // iterate through the collided array, renumbering the windows pointers + S32 assignWindowNumber = 0; + CollapseGroupNumVec::iterator iter2; + for(iter2 = parent->mCollapseGroupVec[attatchedGroupVec].begin(); iter2 != parent->mCollapseGroupVec[attatchedGroupVec].end(); iter2++ ) + { + (*iter2)->mCollapseGroup = attatchedGroupVec; + (*iter2)->mCollapseGroupNum = assignWindowNumber; + + assignWindowNumber++; + } + + } + else + { + S32 groupVec = hitWindow->mCollapseGroup; + + if(orientation == 0) + { + parent->mCollapseGroupVec[groupVec].push_front(this); + } + else + { + parent->mCollapseGroupVec[groupVec].push_back(this); + } + S32 assignWindowNumber = 0; + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[groupVec].begin(); + for(; iter != parent->mCollapseGroupVec[groupVec].end(); iter++ ) + { + (*iter)->mCollapseGroup = hitWindow->mCollapseGroup; + (*iter)->mCollapseGroupNum = assignWindowNumber; + assignWindowNumber++; + } + } + } + refreshCollapseGroups(); +} + + +void GuiWindowCollapseCtrl::refreshCollapseGroups() +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + typedef Vector< GuiWindowCollapseCtrl *> CollapseGroupNumVec; + CollapseGroupNumVec mCollapseGroupNumVec; + + // iterate through the collided array, renumbering the windows pointers + S32 assignGroupNum = 0; + CollapseGroupVec::iterator iter = parent->mCollapseGroupVec.begin(); + for(; iter != parent->mCollapseGroupVec.end(); iter++ ) + { + S32 assignWindowNumber = 0; + CollapseGroupNumVec::iterator iter2 = parent->mCollapseGroupVec[assignGroupNum].begin(); + for(; iter2 != parent->mCollapseGroupVec[assignGroupNum].end(); iter2++ ) + { + (*iter2)->mCollapseGroup = assignGroupNum; + (*iter2)->mCollapseGroupNum = assignWindowNumber; + assignWindowNumber++; + } + + assignGroupNum++; + } +} + +void GuiWindowCollapseCtrl::moveWithCollapseGroup(Point2I deltaMousePosition) +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + typedef Vector< GuiWindowCollapseCtrl *> CollapseGroupNumVec; + CollapseGroupNumVec mCollapseGroupNumVec; + + S32 addedPosition = getExtent().y; + + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin(); + for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ ) + { + if((*iter)->mCollapseGroupNum > mCollapseGroupNum) + { + Point2I newChildPosition = (*iter)->getPosition(); + newChildPosition.x = mOrigBounds.point.x + deltaMousePosition.x; + newChildPosition.y = getMax(0, mOrigBounds.point.y + deltaMousePosition.y + addedPosition); + + (*iter)->resize(newChildPosition, (*iter)->getExtent()); + addedPosition += (*iter)->getExtent().y; + } + } +} + +void GuiWindowCollapseCtrl::setCollapseGroup(bool state) +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + if( mIsCollapsed != state ) + { + mIsCollapsed = state; + handleCollapseGroup(); + } +} + +void GuiWindowCollapseCtrl::toggleCollapseGroup() +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + mIsCollapsed = !mIsCollapsed; + handleCollapseGroup(); +} + +void GuiWindowCollapseCtrl::handleCollapseGroup() +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + typedef Vector< GuiWindowCollapseCtrl *> CollapseGroupNumVec; + CollapseGroupNumVec mCollapseGroupNumVec; + + if( mIsCollapsed ) // minimize window up to its header bar + { + //save settings + mPreCollapsedYExtent = getExtent().y; + mPreCollapsedYMinExtent = getMinExtent().y; + + //create settings for collapsed window to abide by + mResizeHeight = false; + setMinExtent( Point2I( getMinExtent().x, 24 ) ); + + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->setVisible(false); + ctrl->mCanResize = false; + } + + resize( getPosition(), Point2I( getExtent().x, 24 ) ); + + if(mCollapseGroup >= 0) + { + S32 moveChildYBy = (mPreCollapsedYExtent - 24); + + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin(); + for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ ) + { + if((*iter)->mCollapseGroupNum > mCollapseGroupNum) + { + Point2I newChildPosition = (*iter)->getPosition(); + newChildPosition.y -= moveChildYBy; + (*iter)->resize(newChildPosition, (*iter)->getExtent()); + } + } + } + } + else // maximize the window to its previous position + { + //create and load settings + mResizeHeight = true; + setMinExtent( Point2I( getMinExtent().x, mPreCollapsedYMinExtent ) ); + + resize( getPosition(), Point2I( getExtent().x, mPreCollapsedYExtent ) ); + + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->setVisible(true); + ctrl->mCanResize = true; + } + + if(mCollapseGroup >= 0) + { + S32 moveChildYBy = (mPreCollapsedYExtent - 24); + + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin(); + for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ ) + { + if((*iter)->mCollapseGroupNum > mCollapseGroupNum) + { + Point2I newChildPosition = (*iter)->getPosition(); + newChildPosition.y += moveChildYBy; + (*iter)->resize(newChildPosition, (*iter)->getExtent()); + } + } + } + } +} + +bool GuiWindowCollapseCtrl::resizeCollapseGroup(bool resizeX, bool resizeY, Point2I resizePos, Point2I resizeExtent) +{ + + GuiControl *parent = getParent(); + if( !parent ) + return false; + + typedef Vector< GuiWindowCollapseCtrl *> CollapseGroupNumVec; + CollapseGroupNumVec mCollapseGroupNumVec; + + bool canResize = true; + + CollapseGroupNumVec::iterator iter = parent->mCollapseGroupVec[mCollapseGroup].begin(); + for(; iter != parent->mCollapseGroupVec[mCollapseGroup].end(); iter++ ) + { + if((*iter) == this) + continue; + + Point2I newChildPosition = (*iter)->getPosition(); + Point2I newChildExtent = (*iter)->getExtent(); + + if( resizeX == true ) + { + newChildPosition.x -= resizePos.x; + newChildExtent.x -= resizeExtent.x; + + } + if( resizeY == true ) + { + if((*iter)->mCollapseGroupNum > mCollapseGroupNum) + { + newChildPosition.y -= resizeExtent.y; + newChildPosition.y -= resizePos.y; + } + else if((*iter)->mCollapseGroupNum == mCollapseGroupNum - 1) + newChildExtent.y -= resizePos.y; + } + + // check is done for normal extent of windows. if false, check again in case its just giving false + // due to being a collapsed window. if your truly being forced passed your extent, return false + if( !(*iter)->mIsCollapsed && newChildExtent.y >= (*iter)->getMinExtent().y ) + (*iter)->resize( newChildPosition, newChildExtent); + else + { + if( (*iter)->mIsCollapsed ) + (*iter)->resize( newChildPosition, newChildExtent); + else + canResize = false; + } + } + return canResize; +} + +void GuiWindowCollapseCtrl::parentResized(const RectI &oldParentRect, const RectI &newParentRect) +{ + if(!mCanResize) + return; + + GuiControl *parent = getParent(); + if( !parent ) + return; + + // bail if were not sized both by windowrelative + if( mHorizSizing != horizResizeWindowRelative || mHorizSizing != vertResizeWindowRelative ) + return Parent::parentResized( oldParentRect, newParentRect ); + + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + + bool doCollapse = false; + + S32 deltaX = newParentRect.extent.x - oldParentRect.extent.x; + S32 deltaY = newParentRect.extent.y - oldParentRect.extent.y + mProfile->mYPositionOffset; + + //this;//do this again for y see what happens + if (oldParentRect.extent.x != 0) + { + if( newPosition.x > (oldParentRect.extent.x / 2) - 1 ) + { + // if( (newPosition.x + newExtent.x + deltaX) < newParentRect.extent.x) + newPosition.x = newPosition.x + deltaX; + } + } + + if (oldParentRect.extent.y != 0) + { + // only if were apart of a group + if ( mCollapseGroup >= 0 ) + { + // setup parsing mechanisms + typedef Vector< GuiWindowCollapseCtrl *> CollapseGroupNumVec; + CollapseGroupNumVec mCollapseGroupNumVec; + + // lets grab the information we need (this should probably be already stored on each individual window object) + S32 groupNum = mCollapseGroup; + S32 groupMax = parent->mCollapseGroupVec[ groupNum ].size() - 1; + + // set up vars that we're going to be using + S32 groupPos = 0; + S32 groupExtent = 0; + S32 tempGroupExtent = 0; + + // set up the vector/iterator combo we need + mCollapseGroupNumVec = parent->mCollapseGroupVec[ groupNum ]; + CollapseGroupNumVec::iterator iter = mCollapseGroupNumVec.begin(); + + // grab some more information we need later ( this info should also be located on each ind. window object ) + for( ; iter != mCollapseGroupNumVec.end(); iter++ ) + { + if((*iter)->getCollapseGroupNum() == 0) + { + groupPos = (*iter)->getPosition().y; + } + + groupExtent += (*iter)->getExtent().y; + } + + // use the information we just gatherered; only enter this block if we need to + tempGroupExtent = groupPos + groupExtent; + if( tempGroupExtent > ( newParentRect.extent.y / 2 ) + mProfile->mYPositionOffset) + { + // lets size the collapse group + S32 windowParser = groupMax; + bool secondLoop = false; + while(tempGroupExtent >= (newParentRect.extent.y - mProfile->mYPositionOffset) ) + { + + if( windowParser == -1) + { + if( !secondLoop ) + { + secondLoop = true; + windowParser = groupMax; + } + else + break; + } + + GuiWindowCollapseCtrl *tempWindow = mCollapseGroupNumVec[windowParser]; + if(tempWindow->mIsCollapsed) + { + windowParser--; + continue; + } + + // resizing block for the loop... if we can get away with just resizing the bottom window do it before + // resizing the whole block. else, go through the group and start setting min extents. if that doesnt work + // on the second go around, start collpsing the windows. + if( tempGroupExtent - ( tempWindow->getExtent().y - tempWindow->getMinExtent().y ) <= newParentRect.extent.y - mProfile->mYPositionOffset) + { + if( this == tempWindow ) + newExtent.y = newExtent.y - ( tempGroupExtent - newParentRect.extent.y + mProfile->mYPositionOffset); + + tempGroupExtent = tempGroupExtent - newParentRect.extent.y + mProfile->mYPositionOffset ; + } + else + { + if( secondLoop ) + { + tempGroupExtent = tempGroupExtent - (tempWindow->getExtent().y + 32); + + if( this == tempWindow ) + doCollapse = true; + } + else + { + tempGroupExtent = tempGroupExtent - (tempWindow->getExtent().y - tempWindow->getMinExtent().y); + if( this == tempWindow ) + newExtent.y = tempWindow->getMinExtent().y; + } + } + windowParser--; + } + } + } + else if( newPosition.y > (oldParentRect.extent.y / 2) - mProfile->mYPositionOffset) + { + newPosition.y = newPosition.y + deltaY; + } + } + + if( newExtent.x >= getMinExtent().x && newExtent.y >= getMinExtent().y ) + { + // If we are already outside the reach of the main window, lets not place ourselves + // further out; but if were trying to improve visibility, go for it + if( (newPosition.x + newExtent.x) > newParentRect.extent.x ) + { + if( (newPosition.x + newExtent.x) > (getPosition().x + getExtent().x) ) + { + newPosition.x = getPosition().x; + newExtent.x = getExtent().x; + } + //return; + } + if( (newPosition.y + newExtent.y) > newParentRect.extent.y + mProfile->mYPositionOffset) + { + if( (newPosition.y + newExtent.y) > (getPosition().y + getExtent().y) ) + { + newPosition.y = getPosition().y; + newExtent.y = getExtent().y; + }//return; + } + + // Only for collpasing groups, if were not, then do it like normal windows + if(mCollapseGroup >= 0) + { + bool resizeMe = false; + + // Only the group window should control positioning + if( mCollapseGroupNum == 0 ) + { + resizeMe = resizeCollapseGroup( true, true, (getPosition() - newPosition), (getExtent() - newExtent) ); + if(resizeMe == true) + resize(newPosition, newExtent); + } + else if( getExtent() != newExtent) + { + resizeMe = resizeCollapseGroup( false, true, (getPosition() - newPosition), (getExtent() - newExtent) ); + if(resizeMe == true) + resize(getPosition(), newExtent); + } + } + else + { + resize(newPosition, newExtent); + } + } + + if( !mIsCollapsed && doCollapse ) + toggleCollapseGroup(); + + // if docking is invalid on this control, then bail out here + if( getDocking() & Docking::dockInvalid || getDocking() & Docking::dockNone) + return; + + // Update Self + RectI oldThisRect = getBounds(); + anchorControl( this, Point2I( deltaX, deltaY ) ); + RectI newThisRect = getBounds(); + + // Update Deltas to pass on to children + deltaX = newThisRect.extent.x - oldThisRect.extent.x; + deltaY = newThisRect.extent.y - oldThisRect.extent.y; + + // Iterate over all children and update their anchors + iterator nI = begin(); + for( ; nI != end(); nI++ ) + { + // Sanity + GuiControl *control = dynamic_cast( (*nI) ); + if( control ) + control->parentResized( oldThisRect, newThisRect ); + } +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiWindowCollapseCtrl, setCollapseGroup, void, 3, 3, "(bool collapse) - Set the window's collapsing state." ) +{ + object->setCollapseGroup(dAtob(argv[2])); +} + +ConsoleMethod( GuiWindowCollapseCtrl, toggleCollapseGroup, void, 2, 2, "() - Toggle the window collapsing." ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->toggleCollapseGroup(); +} + +//----------------------------------------------------------------------------- + +ConsoleFunction(AttachWindows, void, 3, 3, " (GuiWindowCollapseCtrl #1, GuiWindowCollapseCtrl #2) #1 = bottom window, #2 = top window") +{ + GuiWindowCollapseCtrl* bottomWindow; + Sim::findObject(argv[1], bottomWindow); + + if(bottomWindow == NULL) + { + Con::warnf("Warning: AttachWindows - could not find window \"%s\"",argv[2]); + return; + } + + GuiWindowCollapseCtrl* topWindow; + Sim::findObject(argv[2], topWindow); + + if(bottomWindow == NULL) + { + Con::warnf("Warning: AttachWindows - could not find window \"%s\"",argv[1]); + return; + } + + + bottomWindow->moveToCollapseGroup(topWindow, 1); +} \ No newline at end of file diff --git a/gui/containers/guiWindowCollapseCtrl.h b/gui/containers/guiWindowCollapseCtrl.h new file mode 100644 index 0000000..95906af --- /dev/null +++ b/gui/containers/guiWindowCollapseCtrl.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIWINDOWCOLLAPSECTRL_H_ +#define _GUIWINDOWCOLLAPSECTRL_H_ + +#ifndef _GUIWINDOWCTRL_H_ +#include "gui/containers/guiWindowCtrl.h" +#endif + +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + +/// @addtogroup gui_container_group Containers +/// +/// @ingroup gui_group Gui System +/// @{ +class GuiWindowCollapseCtrl : public GuiWindowCtrl +{ + private: + typedef GuiWindowCtrl Parent; + + + S32 mCollapseGroup; + S32 mCollapseGroupNum; + S32 mPreCollapsedYExtent; + S32 mPreCollapsedYMinExtent; + + public: + GuiWindowCollapseCtrl(); + DECLARE_CONOBJECT(GuiWindowCollapseCtrl); + static void initPersistFields(); + + bool mIsCollapsed; + bool mIsMouseResizing; + + // Parent Methods + void getSnappableWindows( Vector &outVector, Vector &windowOutVector); + virtual void parentResized(const RectI &oldParentRect, const RectI &newParentRect); + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + S32 getCollapseGroupNum() { return mCollapseGroupNum; } + + void moveFromCollapseGroup(); + void moveToCollapseGroup(GuiWindowCollapseCtrl* hitWindow, bool orientation); + void moveWithCollapseGroup(Point2I deltaMousePosition); + + void setCollapseGroup(bool state); + void toggleCollapseGroup(); + bool resizeCollapseGroup(bool resizeX, bool resizeY, Point2I resizePos, Point2I resizeWidth); + void refreshCollapseGroups(); + +protected: + void handleCollapseGroup(); +}; +/// @} + +#endif //_GUI_WINDOWCOLLAPSE_CTRL_H diff --git a/gui/containers/guiWindowCtrl.cpp b/gui/containers/guiWindowCtrl.cpp new file mode 100644 index 0000000..a3292ab --- /dev/null +++ b/gui/containers/guiWindowCtrl.cpp @@ -0,0 +1,1062 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/containers/guiWindowCtrl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/containers/guiRolloutCtrl.h" + +IMPLEMENT_CONOBJECT(GuiWindowCtrl); + +//----------------------------------------------------------------------------- + + +GuiWindowCtrl::GuiWindowCtrl() : mResizeEdge(edgeNone), + mResizeWidth(true), + mResizeHeight(true), + mResizeMargin(5.f), + mCanMove(true), + mCanClose(true), + mCanMinimize(true), + mCanMaximize(true), + mCanDock(false), + mEdgeSnap(true) + +{ + // mTitleHeight will change in instanciation most likely... + mTitleHeight = 24; + + mIsContainer = true; + + mCloseCommand = StringTable->insert(""); + + mMinimized = false; + mMaximized = false; + mMouseMovingWin = false; + mMouseResizeWidth = false; + mMouseResizeHeight = false; + setExtent(100, 200); + mMinSize.set(50, 50); + mMinimizeIndex = -1; + mTabIndex = -1; + + RectI closeRect(80, 2, 16, 16); + mCloseButton = closeRect; + closeRect.point.x -= 18; + mMaximizeButton = closeRect; + closeRect.point.x -= 18; + mMinimizeButton = closeRect; + + //other defaults + mActive = true; + mPressClose = false; + mPressMaximize = false; + mPressMinimize = false; + + mText = StringTable->insert("New Window", true); +} + +void GuiWindowCtrl::initPersistFields() +{ + addField("resizeWidth", TypeBool, Offset(mResizeWidth, GuiWindowCtrl)); + addField("resizeHeight", TypeBool, Offset(mResizeHeight, GuiWindowCtrl)); + addField("canMove", TypeBool, Offset(mCanMove, GuiWindowCtrl)); + addField("canClose", TypeBool, Offset(mCanClose, GuiWindowCtrl)); + addField("canMinimize", TypeBool, Offset(mCanMinimize, GuiWindowCtrl)); + addField("canMaximize", TypeBool, Offset(mCanMaximize, GuiWindowCtrl)); + addField("minSize", TypePoint2I, Offset(mMinSize, GuiWindowCtrl)); + addField("closeCommand", TypeString, Offset(mCloseCommand, GuiWindowCtrl)); + addField("EdgeSnap", TypeBool, Offset(mEdgeSnap,GuiWindowCtrl)); + addField("text", TypeCaseString, Offset(mText, GuiWindowCtrl )); + + Parent::initPersistFields(); +} + +bool GuiWindowCtrl::isMinimized(S32 &index) +{ + index = mMinimizeIndex; + return mMinimized && mVisible; +} + +// helper fn so button positioning shares code... +void GuiWindowCtrl::PositionButtons(void) +{ + if( !mBitmapBounds || !mAwake ) + return; + + S32 buttonWidth = mBitmapBounds[BmpStates * BmpClose].extent.x; + S32 buttonHeight = mBitmapBounds[BmpStates * BmpClose].extent.y; + Point2I mainOff = mProfile->mTextOffset; + + // until a pref, if alignment is LEFT, put buttons RIGHT justified. + // ELSE, put buttons LEFT justified. + int closeLeft = mainOff.x, closeTop = mainOff.y, closeOff = buttonWidth + 2; + if ( mProfile->mAlignment == GuiControlProfile::LeftJustify ) + { + closeOff = -closeOff; + closeLeft = getWidth() - buttonWidth - mainOff.x; + } + RectI closeRect(closeLeft, closeTop, buttonHeight, buttonWidth); + mCloseButton = closeRect; + + // Always put Minimize on left side of Maximize. + closeRect.point.x += closeOff; + if (closeOff>0) + { + mMinimizeButton = closeRect; + closeRect.point.x += closeOff; + mMaximizeButton = closeRect; + } + else + { + mMaximizeButton = closeRect; + closeRect.point.x += closeOff; + mMinimizeButton = closeRect; + } +} + +bool GuiWindowCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + //get the texture for the close, minimize, and maximize buttons + mTextureObject = mProfile->mTextureObject; + bool result = mProfile->constructBitmapArray() >= NumBitmaps; + AssertFatal(result, "GuiWindowCtrl::onWake() - Failed to create the bitmap array."); + if(!result) + return false; + + mBitmapBounds = mProfile->mBitmapArrayRects.address(); + S32 buttonHeight = mBitmapBounds[BmpStates * BmpClose].extent.y; + + mTitleHeight = buttonHeight + 4; + + //set the button coords + PositionButtons(); + + //set the tab index + mTabIndex = -1; + GuiControl *parent = getParent(); + if (parent && mFirstResponder) + { + mTabIndex = 0; + + //count the number of windows preceeding this one + iterator i; + for (i = parent->begin(); i != parent->end(); i++) + { + GuiWindowCtrl *ctrl = dynamic_cast(*i); + if (ctrl) + { + if (ctrl == this) break; + else if (ctrl->mFirstResponder) mTabIndex++; + } + } + } + + return true; +} + +void GuiWindowCtrl::onSleep() +{ + mTextureObject = NULL; + Parent::onSleep(); +} + +void GuiWindowCtrl::setCloseCommand(const char *newCmd) +{ + if (newCmd) + mCloseCommand = StringTable->insert(newCmd); + else + mCloseCommand = StringTable->insert(""); +} + +GuiControl* GuiWindowCtrl::findHitControl(const Point2I &pt, S32 initialLayer) +{ + if (! mMinimized) + return Parent::findHitControl(pt, initialLayer); + else + return this; +} + +bool GuiWindowCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if( !Parent::resize(newPosition, newExtent) ) + return false; + + //set the button coords + PositionButtons(); + + return true; +} + + + + +//----------------------------------------------------------------------------- +// Mouse Methods +//----------------------------------------------------------------------------- +S32 GuiWindowCtrl::findHitEdges( const Point2I &globalPoint ) +{ + // No Edges + S32 edgeMask = edgeNone; + + RectI bounds( getGlobalBounds() ); + + // Create an EdgeRectI structure that has four edges + // Left/Right/Top/Bottom + // Each Edge structure has a hit operation that will take + // another edge and test for a hit on the edge with a margin + // specified by the .margin scalar + EdgeRectI edges = EdgeRectI(bounds, mResizeMargin); + + // Get Cursor Edges + Edge cursorVertEdge = Edge( globalPoint, Point2F( 1.f, 0.f ) ); + Edge cursorHorzEdge = Edge( globalPoint, Point2F( 0.f, 1.f ) ); + + if( edges.left.hit( cursorVertEdge ) ) + edgeMask |= edgeLeft; + else if( edges.right.hit( cursorVertEdge ) ) + edgeMask |= edgeRight; + + if( edges.top.hit( cursorHorzEdge ) ) + edgeMask |= edgeTop; + else if( edges.bottom.hit( cursorHorzEdge ) ) + edgeMask |= edgeBottom; + + // Return the resulting mask + return edgeMask; +} + +void GuiWindowCtrl::getSnappableWindows( Vector &outVector, Vector &windowOutVector) +{ + GuiControl *parent = getParent(); + if( !parent ) + return; + + S32 parentSize = parent->size(); + for( S32 i = 0; i < parentSize; i++ ) + { + GuiWindowCtrl *childWindow = dynamic_cast(parent->at(i)); + if( !childWindow || !childWindow->isVisible() || childWindow == this || childWindow->mEdgeSnap == false) + continue; + + outVector.push_back(EdgeRectI(childWindow->getGlobalBounds(), mResizeMargin)); + windowOutVector.push_back(childWindow); + } + +} + + +void GuiWindowCtrl::onMouseDown(const GuiEvent &event) +{ + setUpdate(); + + mOrigBounds = getBounds(); + + mMouseDownPosition = event.mousePoint; + Point2I localPoint = globalToLocalCoord(event.mousePoint); + + //select this window - move it to the front, and set the first responder + selectWindow(); + + mMouseMovingWin = false; + + S32 hitEdges = findHitEdges( event.mousePoint ); + + mResizeEdge = edgeNone; + + // Set flag by default so we only clear it + // if we don't match either edge + mMouseResizeHeight = true; + + // Check Bottom/Top edges (Mutually Exclusive) + if( mResizeHeight && hitEdges & edgeBottom ) + mResizeEdge |= edgeBottom; + else if( mResizeHeight && hitEdges & edgeTop ) + mResizeEdge |= edgeTop; + else + mMouseResizeHeight = false; + + // Set flag by default so we only clear it + // if we don't match either edge + mMouseResizeWidth = true; + + // Check Left/Right edges (Mutually Exclusive) + if( mResizeWidth && hitEdges & edgeLeft ) + mResizeEdge |= edgeLeft; + else if( mResizeWidth && hitEdges & edgeRight ) + mResizeEdge |= edgeRight; + else + mMouseResizeWidth = false; + + + //if we clicked within the title bar + if ( !(mResizeEdge & edgeTop) && localPoint.y < mTitleHeight) + { + //if we clicked on the close button + if (mCanClose && mCloseButton.pointInRect(localPoint)) + { + mPressClose = mCanClose; + } + else if (mCanMaximize && mMaximizeButton.pointInRect(localPoint)) + { + mPressMaximize = mCanMaximize; + } + else if (mCanMinimize && mMinimizeButton.pointInRect(localPoint)) + { + mPressMinimize = mCanMinimize; + } + + //else we clicked within the title + else + { + S32 docking = getDocking(); + if( docking == Docking::dockInvalid || docking == Docking::dockNone ) + mMouseMovingWin = mCanMove; + + mMouseResizeWidth = false; + mMouseResizeHeight = false; + } + } + + + if (mMouseMovingWin || mResizeEdge != edgeNone || + mPressClose || mPressMaximize || mPressMinimize) + { + mouseLock(); + } + else + { + + GuiControl *ctrl = findHitControl(localPoint); + if (ctrl && ctrl != this) + ctrl->onMouseDown(event); + + } +} + +void GuiWindowCtrl::onMouseDragged(const GuiEvent &event) +{ + GuiControl *parent = getParent(); + GuiCanvas *root = getRoot(); + if ( !root ) return; + + Point2I deltaMousePosition = event.mousePoint - mMouseDownPosition; + + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + + mRepositionWindow = false; + mResizeWindow = false; + + if (mMouseMovingWin && parent) + { + if( parent != root ) + { + newPosition.x = mOrigBounds.point.x + deltaMousePosition.x; + newPosition.y = getMax(0, mOrigBounds.point.y + deltaMousePosition.y ); + mRepositionWindow = true; + } + else + { + newPosition.x = getMax(0, getMin(parent->getWidth() - getWidth(), mOrigBounds.point.x + deltaMousePosition.x)); + newPosition.y = getMax(0, getMin(parent->getHeight() - getHeight(), mOrigBounds.point.y + deltaMousePosition.y)); + } + + // Check snapping to other windows + if( mEdgeSnap ) + { + RectI bounds = getGlobalBounds(); + bounds.point = mOrigBounds.point + deltaMousePosition; + EdgeRectI edges = EdgeRectI( bounds, mResizeMargin ); + + Vector snapList; + Vector windowList; + + for( S32 i =0; i < snapList.size(); i++ ) + { + // Compare Edges for hit + EdgeRectI &snapRect = snapList[i]; + EdgeRectI orignalSnapRect = snapRect; + + // Add some buffer room for snap detection + snapRect = orignalSnapRect; + snapRect.right.position.x = snapRect.right.position.x+12; + if( edges.left.hit( snapRect.right ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newPosition.x = snapRect.right.position.x; + } + + // Add some buffer room for snap detection + snapRect = orignalSnapRect; + snapRect.left.position.x = snapRect.left.position.x-12; + if( edges.right.hit( snapRect.left ) ) + { + snapRect = orignalSnapRect; + newPosition.x = snapRect.left.position.x - bounds.extent.x; + } + + snapRect = orignalSnapRect; + snapRect.top.position.y = snapRect.top.position.y-12; + if( edges.bottom.hit( snapRect.top ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newPosition.y = snapRect.top.position.y - bounds.extent.y; + newPosition.x = snapRect.left.position.x; + } + + + snapRect = orignalSnapRect; + snapRect.bottom.position.y = snapRect.bottom.position.y+12; + if( edges.top.hit( snapRect.bottom ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newPosition.y = snapRect.bottom.position.y; + newPosition.x = snapRect.left.position.x; + } + } + } + } + else if(mPressClose || mPressMaximize || mPressMinimize) + { + setUpdate(); + return; + } + else + { + if( ( !mMouseResizeHeight && !mMouseResizeWidth ) || !parent ) + return; + + mResizeWindow = true; + if( mResizeEdge & edgeBottom) + { + newExtent.y = getMin(parent->getHeight(), mOrigBounds.extent.y + deltaMousePosition.y); + } + else if ( mResizeEdge & edgeTop ) + { + newPosition.y = mOrigBounds.point.y + deltaMousePosition.y; + newExtent.y = getMin(parent->getHeight(), mOrigBounds.extent.y - deltaMousePosition.y); + } + + if( mResizeEdge & edgeRight ) + { + newExtent.x = getMin(parent->getWidth(), mOrigBounds.extent.x + deltaMousePosition.x); + } + else if( mResizeEdge & edgeLeft ) + { + newPosition.x = mOrigBounds.point.x + deltaMousePosition.x; + newExtent.x = getMin(parent->getWidth(), mOrigBounds.extent.x - deltaMousePosition.x); + } + } + + // resize myself + Point2I pos = parent->localToGlobalCoord(getPosition()); + root->addUpdateRegion(pos, getExtent()); + + resize(newPosition, newExtent); +} + +void GuiWindowCtrl::onMouseUp(const GuiEvent &event) +{ + bool closing = mPressClose; + bool maximizing = mPressMaximize; + bool minimizing = mPressMinimize; + mPressClose = false; + mPressMaximize = false; + mPressMinimize = false; + + TORQUE_UNUSED(event); + mouseUnlock(); + + mMouseMovingWin = false; + mMouseResizeWidth = false; + mMouseResizeHeight = false; + + bool snapSignal = false; + + GuiControl *parent = getParent(); + if (! parent) + return; + + //see if we take an action + Point2I localPoint = globalToLocalCoord(event.mousePoint); + if (closing && mCloseButton.pointInRect(localPoint)) + { + // This comes in handy, like in execConsole + Con::setIntVariable( "$ThisControl", getId() ); + + // here is where were going to put our other if statement + //if the window closes, and there were only 2 windows in the array, then just delete the array and default there params + //if not, delete the window from the array, default its params, and renumber the windows + Con::evaluate(mCloseCommand); + + } + else if (maximizing && mMaximizeButton.pointInRect(localPoint)) + { + if (mMaximized) + { + //resize to the previous position and extent, bounded by the parent + resize(Point2I(getMax(0, getMin(parent->getWidth() - mStandardBounds.extent.x, mStandardBounds.point.x)), + getMax(0, getMin(parent->getHeight() - mStandardBounds.extent.y, mStandardBounds.point.y))), + mStandardBounds.extent); + //set the flag + mMaximized = false; + } + else + { + //only save the position if we're not minimized + if (! mMinimized) + { + mStandardBounds = getBounds(); + } + else + { + mMinimized = false; + } + + //resize to fit the parent + resize(Point2I(0, 0), parent->getExtent()); + + //set the flag + mMaximized = true; + } + } + else if (minimizing && mMinimizeButton.pointInRect(localPoint)) + { + if (mMinimized) + { + //resize to the previous position and extent, bounded by the parent + resize(Point2I(getMax(0, getMin(parent->getWidth() - mStandardBounds.extent.x, mStandardBounds.point.x)), + getMax(0, getMin(parent->getHeight() - mStandardBounds.extent.y, mStandardBounds.point.y))), + mStandardBounds.extent); + //set the flag + mMinimized = false; + } + else + { + if (parent->getWidth() < 100 || parent->getHeight() < mTitleHeight + 3) + return; + + //only save the position if we're not maximized + if (! mMaximized) + { + mStandardBounds = getBounds(); + } + else + { + mMaximized = false; + } + + //first find the lowest unused minimized index up to 32 minimized windows + U32 indexMask = 0; + iterator i; + S32 count = 0; + for (i = parent->begin(); i != parent->end() && count < 32; i++) + { + count++; + S32 index; + GuiWindowCtrl *ctrl = dynamic_cast(*i); + if (ctrl && ctrl->isMinimized(index)) + { + indexMask |= (1 << index); + } + } + + //now find the first unused bit + for (count = 0; count < 32; count++) + { + if (! (indexMask & (1 << count))) break; + } + + //if we have more than 32 minimized windows, use the first position + count = getMax(0, count); + + //this algorithm assumes all window have the same title height, and will minimize to 98 pix + Point2I newExtent(98, mTitleHeight); + + //first, how many can fit across + S32 numAcross = getMax(1, (parent->getWidth() / newExtent.x + 2)); + + //find the new "mini position" + Point2I newPosition; + newPosition.x = (count % numAcross) * (newExtent.x + 2) + 2; + newPosition.y = parent->getHeight() - (((count / numAcross) + 1) * (newExtent.y + 2)) - 2; + + //find the minimized position and extent + resize(newPosition, newExtent); + + //set the index so other windows will not try to minimize to the same location + mMinimizeIndex = count; + + //set the flag + mMinimized = true; + } + } + else if ( !(mResizeEdge & edgeTop) && localPoint.y < mTitleHeight == true && event.mousePoint == mMouseDownPosition) + { + //if we clicked on the close button + if (mCanClose && mCloseButton.pointInRect(localPoint)) + return; + else if (mCanMaximize && mMaximizeButton.pointInRect(localPoint)) + return; + else if (mCanMinimize && mMinimizeButton.pointInRect(localPoint)) + return; + } + else if( mEdgeSnap ) + { + Point2I deltaMousePosition = event.mousePoint - mMouseDownPosition; + + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + RectI bounds = getGlobalBounds(); + bounds.point = mOrigBounds.point + deltaMousePosition; + EdgeRectI edges = EdgeRectI( bounds, mResizeMargin ); + + Vector snapList; + Vector windowList; + getSnappableWindows( snapList, windowList ); + for( S32 i =0; i < snapList.size(); i++ ) + { + // Compare Edges for hit + EdgeRectI &snapRect = snapList[i]; + EdgeRectI orignalSnapRect = snapRect; + + + snapRect = orignalSnapRect; + snapRect.top.position.y = snapRect.top.position.y-12; + if( edges.bottom.hit( snapRect.top ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newExtent.x = snapRect.right.position.x - snapRect.left.position.x; + + snapSignal = true; + } + + // Add some buffer room for snap detection : BECOMES "CHILD" + snapRect = orignalSnapRect; + snapRect.bottom.position.y = snapRect.bottom.position.y+12; + if( edges.top.hit( snapRect.bottom ) ) + { + // Replace buffer room with original snap params + snapRect = orignalSnapRect; + newExtent.x = snapRect.right.position.x - snapRect.left.position.x; + + snapSignal = true; + } + + } + resize(newPosition, newExtent); + } +} + +GuiControl *GuiWindowCtrl::findNextTabable(GuiControl *curResponder, bool firstCall) +{ + //set the global if this is the first call (directly from the canvas) + if (firstCall) + { + GuiControl::smCurResponder = NULL; + } + + //if the window does not already contain the first responder, return false + //ie. Can't tab into or out of a window + if (! ControlIsChild(curResponder)) + { + return NULL; + } + + //loop through, checking each child to see if it is the one that follows the firstResponder + GuiControl *tabCtrl = NULL; + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + tabCtrl = ctrl->findNextTabable(curResponder, false); + if (tabCtrl) break; + } + + //to ensure the tab cycles within the current window... + if (! tabCtrl) + { + tabCtrl = findFirstTabable(); + } + + mFirstResponder = tabCtrl; + return tabCtrl; +} + +GuiControl *GuiWindowCtrl::findPrevTabable(GuiControl *curResponder, bool firstCall) +{ + if (firstCall) + { + GuiControl::smPrevResponder = NULL; + } + + //if the window does not already contain the first responder, return false + //ie. Can't tab into or out of a window + if (! ControlIsChild(curResponder)) + { + return NULL; + } + + //loop through, checking each child to see if it is the one that follows the firstResponder + GuiControl *tabCtrl = NULL; + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + tabCtrl = ctrl->findPrevTabable(curResponder, false); + if (tabCtrl) break; + } + + //to ensure the tab cycles within the current window... + if (! tabCtrl) + { + tabCtrl = findLastTabable(); + } + + mFirstResponder = tabCtrl; + return tabCtrl; +} + +bool GuiWindowCtrl::onKeyDown(const GuiEvent &event) +{ + //if this control is a dead end, kill the event + if ((! mVisible) || (! mActive) || (! mAwake)) return true; + + if ((event.keyCode == KEY_TAB) && (event.modifier & SI_PRIMARY_CTRL)) + { + //find the next sibling window, and select it + GuiControl *parent = getParent(); + if (parent) + { + GuiWindowCtrl *firstWindow = NULL; + iterator i; + for (i = parent->begin(); i != parent->end(); i++) + { + GuiWindowCtrl *ctrl = dynamic_cast(*i); + if (ctrl && ctrl->getTabIndex() == mTabIndex + 1) + { + ctrl->selectWindow(); + return true; + } + else if (ctrl && ctrl->getTabIndex() == 0) + { + firstWindow = ctrl; + } + } + //recycle from the beginning + if (firstWindow != this) + { + firstWindow->selectWindow(); + return true; + } + } + } + + return Parent::onKeyDown(event); +} + +const RectI GuiWindowCtrl::getClientRect() +{ + if( !mProfile || mProfile->mBitmapArrayRects.size() < NumBitmaps ) + return Parent::getClientRect(); + + RectI winRect; + winRect.point.x = mBitmapBounds[BorderLeft].extent.x; + winRect.point.y = mBitmapBounds[BorderTopKey].extent.y; + + winRect.extent.y = getHeight() - ( winRect.point.y + mBitmapBounds[BorderBottom].extent.y ); + winRect.extent.x = getWidth() - ( winRect.point.x + mBitmapBounds[BorderRight].extent.x ); + + // Finally, inset it by padding + // Inset by padding. margin is specified for all t/b/l/r but + // uses only pointx pointy uniformly on both ends. This should be fixed. - JDD + //winRect.inset( mSizingOptions.mPadding.point.x, mSizingOptions.mPadding.point.y ); + + return winRect; +} +void GuiWindowCtrl::selectWindow(void) +{ + //first make sure this window is the front most of its siblings + GuiControl *parent = getParent(); + if (parent && *parent->end() != this ) + { + parent->pushObjectToBack(this); + } + + //also set the first responder to be the one within this window + setFirstResponder(mFirstResponder); +} + +void GuiWindowCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if( !mProfile || mProfile->mFont == NULL || mProfile->mBitmapArrayRects.size() < NumBitmaps ) + return Parent::onRender( offset, updateRect ); + + //draw the outline + RectI winRect; + winRect.point = offset; + winRect.extent = getExtent(); + GuiCanvas *root = getRoot(); + GuiControl *firstResponder = root ? root->getFirstResponder() : NULL; + + bool isKey = (!firstResponder || ControlIsChild(firstResponder)); + + U32 topBase = isKey ? BorderTopLeftKey : BorderTopLeftNoKey; + winRect.point.x += mBitmapBounds[BorderLeft].extent.x; + winRect.point.y += mBitmapBounds[topBase + 2].extent.y; + + winRect.extent.x -= mBitmapBounds[BorderLeft].extent.x + mBitmapBounds[BorderRight].extent.x; + winRect.extent.y -= mBitmapBounds[topBase + 2].extent.y + mBitmapBounds[BorderBottom].extent.y; + + GFX->getDrawUtil()->drawRectFill(winRect, mProfile->mFillColor); + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, offset, mBitmapBounds[topBase]); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, Point2I(offset.x + getWidth() - mBitmapBounds[topBase+1].extent.x, offset.y), + mBitmapBounds[topBase + 1]); + + RectI destRect; + destRect.point.x = offset.x + mBitmapBounds[topBase].extent.x; + destRect.point.y = offset.y; + destRect.extent.x = getWidth() - mBitmapBounds[topBase].extent.x - mBitmapBounds[topBase + 1].extent.x; + destRect.extent.y = mBitmapBounds[topBase + 2].extent.y; + RectI stretchRect = mBitmapBounds[topBase + 2]; + stretchRect.inset(1,0); + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + + destRect.point.x = offset.x; + destRect.point.y = offset.y + mBitmapBounds[topBase].extent.y; + destRect.extent.x = mBitmapBounds[BorderLeft].extent.x; + destRect.extent.y = getHeight() - mBitmapBounds[topBase].extent.y - mBitmapBounds[BorderBottomLeft].extent.y; + stretchRect = mBitmapBounds[BorderLeft]; + stretchRect.inset(0,1); + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + + destRect.point.x = offset.x + getWidth() - mBitmapBounds[BorderRight].extent.x; + destRect.extent.x = mBitmapBounds[BorderRight].extent.x; + destRect.point.y = offset.y + mBitmapBounds[topBase + 1].extent.y; + destRect.extent.y = getHeight() - mBitmapBounds[topBase + 1].extent.y - mBitmapBounds[BorderBottomRight].extent.y; + + stretchRect = mBitmapBounds[BorderRight]; + stretchRect.inset(0,1); + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, offset + Point2I(0, getHeight() - mBitmapBounds[BorderBottomLeft].extent.y), mBitmapBounds[BorderBottomLeft]); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, offset + getExtent() - mBitmapBounds[BorderBottomRight].extent, mBitmapBounds[BorderBottomRight]); + + destRect.point.x = offset.x + mBitmapBounds[BorderBottomLeft].extent.x; + destRect.extent.x = getWidth() - mBitmapBounds[BorderBottomLeft].extent.x - mBitmapBounds[BorderBottomRight].extent.x; + + destRect.point.y = offset.y + getHeight() - mBitmapBounds[BorderBottom].extent.y; + destRect.extent.y = mBitmapBounds[BorderBottom].extent.y; + stretchRect = mBitmapBounds[BorderBottom]; + stretchRect.inset(1,0); + + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + + //draw the title + // dhc addition: copied/modded from renderJustifiedText, since we enforce a + // different color usage here. NOTE: it currently CAN overdraw the controls + // if mis-positioned or 'scrunched' in a small width. + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor); + S32 textWidth = mProfile->mFont->getStrWidth((const UTF8 *)mText); + Point2I start(0,0); + // align the horizontal + if ( mProfile->mAlignment == GuiControlProfile::RightJustify ) + start.set( winRect.extent.x - textWidth, 0 ); + else if ( mProfile->mAlignment == GuiControlProfile::CenterJustify ) + start.set( ( winRect.extent.x - textWidth) / 2, 0 ); + else // GuiControlProfile::LeftJustify or garbage... ;) + start.set( 0, 0 ); + // If the text is longer then the box size, (it'll get clipped) so force Left Justify + if( textWidth > winRect.extent.x ) start.set( 0, 0 ); + // center the vertical +// start.y = ( winRect.extent.y - ( font->getHeight() - 2 ) ) / 2; + GFX->getDrawUtil()->drawText( mProfile->mFont, start + offset + mProfile->mTextOffset, mText ); + + // deal with rendering the titlebar controls + AssertFatal(root, "Unable to get the root GuiCanvas."); + Point2I localPoint = globalToLocalCoord(root->getCursorPos()); + + //draw the close button + Point2I tempUL; + Point2I tempLR; + S32 bmp = BmpStates * BmpClose; + + if( mCanClose ) { + if( mCloseButton.pointInRect( localPoint ) && mPressClose ) + bmp += BmpHilite; + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, offset + mCloseButton.point, mBitmapBounds[bmp]); + } + + //draw the maximize button + if( mMaximized ) + bmp = BmpStates * BmpNormal; + else + bmp = BmpStates * BmpMaximize; + + if( mCanMaximize ) { + if( mMaximizeButton.pointInRect( localPoint ) && mPressMaximize ) + bmp += BmpHilite; + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, offset + mMaximizeButton.point, mBitmapBounds[bmp] ); + } + + //draw the minimize button + if( mMinimized ) + bmp = BmpStates * BmpNormal; + else + bmp = BmpStates * BmpMinimize; + + if( mCanMinimize ) { + if( mMinimizeButton.pointInRect( localPoint ) && mPressMinimize ) + bmp += BmpHilite; + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mTextureObject, offset + mMinimizeButton.point, mBitmapBounds[bmp] ); + } + + if( !mMinimized ) + { + //render the children + renderChildControls( offset, updateRect ); + } +} + +void GuiWindowCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) +{ + GuiCanvas *pRoot = getRoot(); + if( !pRoot ) + return; + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); + PlatformCursorController *pController = pWindow->getCursorController(); + AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); + + S32 desiredCursor = PlatformCursorController::curArrow; + S32 hitEdges = findHitEdges( lastGuiEvent.mousePoint ); + + if( hitEdges & edgeBottom && hitEdges & edgeLeft && mResizeHeight ) + desiredCursor = PlatformCursorController::curResizeNESW; + else if( hitEdges & edgeBottom && hitEdges & edgeRight && mResizeHeight ) + desiredCursor = PlatformCursorController::curResizeNWSE; + else if( hitEdges & edgeBottom && mResizeHeight ) + desiredCursor = PlatformCursorController::curResizeHorz; + else if( hitEdges & edgeTop && hitEdges & edgeLeft && mResizeHeight ) + desiredCursor = PlatformCursorController::curResizeNWSE; + else if( hitEdges & edgeTop && hitEdges & edgeRight && mResizeHeight ) + desiredCursor = PlatformCursorController::curResizeNESW; + else if( hitEdges & edgeTop && mResizeHeight ) + desiredCursor = PlatformCursorController::curResizeHorz; + else if ( hitEdges & edgeLeft && mResizeWidth ) + desiredCursor = PlatformCursorController::curResizeVert; + else if( hitEdges & edgeRight && mResizeWidth ) + desiredCursor = PlatformCursorController::curResizeVert; + else + desiredCursor = PlatformCursorController::curArrow; + + // Bail if we're already at the desired cursor + if(pRoot->mCursorChanged == desiredCursor ) + return; + + // Now change the cursor shape + pController->popCursor(); + pController->pushCursor(desiredCursor); + pRoot->mCursorChanged = desiredCursor; +} + +void GuiWindowCtrl::parentResized(const RectI &oldParentRect, const RectI &newParentRect) +{ + if(!mCanResize) + return; + + // bail if were not sized both by windowrelative + if( mHorizSizing != horizResizeWindowRelative || mHorizSizing != vertResizeWindowRelative ) + return Parent::parentResized( oldParentRect, newParentRect ); + + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + + S32 deltaX = newParentRect.extent.x - oldParentRect.extent.x; + S32 deltaY = newParentRect.extent.y - oldParentRect.extent.y; + + if (oldParentRect.extent.x != 0) + { + if( newPosition.x > (oldParentRect.extent.x / 2) ) + newPosition.x = newPosition.x + deltaX; + } + + if (oldParentRect.extent.y != 0) + { + if( newPosition.y > (oldParentRect.extent.y / 2) ) + newPosition.y = newPosition.y + deltaY; + } + + if( newExtent.x >= getMinExtent().x && newExtent.y >= getMinExtent().y ) + { + // If we are already outside the reach of the main window, lets not place ourselves + // further out; but if were trying to improve visibility, go for it + if( (newPosition.x + newExtent.x) > newParentRect.extent.x ) + { + if( (newPosition.x + newExtent.x) > (getPosition().x + getExtent().x) ) + return; + } + if( (newPosition.y + newExtent.y) > newParentRect.extent.y) + { + if( (newPosition.y + newExtent.y) > (getPosition().y + getExtent().y) ) + return; + } + + resize(newPosition, newExtent); + } + + // if docking is invalid on this control, then bail out here + if( getDocking() & Docking::dockInvalid || getDocking() & Docking::dockNone ) + return; + + // Update Self + RectI oldThisRect = getBounds(); + anchorControl( this, Point2I( deltaX, deltaY ) ); + RectI newThisRect = getBounds(); + + // Update Deltas to pass on to children + deltaX = newThisRect.extent.x - oldThisRect.extent.x; + deltaY = newThisRect.extent.y - oldThisRect.extent.y; + + // Iterate over all children and update their anchors + iterator nI = begin(); + for( ; nI != end(); nI++ ) + { + // Sanity + GuiControl *control = dynamic_cast( (*nI) ); + if( control ) + control->parentResized( oldThisRect, newThisRect ); + } +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiWindowCtrl, selectWindow, void, 2, 2, "() - Bring the window to the front." ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->selectWindow(); +} diff --git a/gui/containers/guiWindowCtrl.h b/gui/containers/guiWindowCtrl.h new file mode 100644 index 0000000..d176a5a --- /dev/null +++ b/gui/containers/guiWindowCtrl.h @@ -0,0 +1,184 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIWINDOWCTRL_H_ +#define _GUIWINDOWCTRL_H_ + +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + +/// @addtogroup gui_container_group Containers +/// +/// @ingroup gui_group Gui System +/// @{ +class GuiWindowCtrl : public GuiContainer +{ + protected: + typedef GuiContainer Parent; + + bool mResizeWidth; + bool mResizeHeight; + bool mCanMove; + bool mCanClose; + bool mCanMinimize; + bool mCanMaximize; + bool mCanDock; ///< Show a docking button on the title bar? + bool mEdgeSnap; ///< Should this window snap to other windows edges? + bool mPressClose; + bool mPressMinimize; + bool mPressMaximize; + + bool mRepositionWindow; + bool mResizeWindow; + bool mSnapSignal; + + Point2I mMinSize; + + StringTableEntry mCloseCommand; + StringTableEntry mText; + S32 mResizeEdge; ///< Resizing Edges Mask (See Edges Enumeration) + + S32 mTitleHeight; + + F32 mResizeMargin; + + bool mMouseMovingWin; + bool mMouseResizeWidth; + bool mMouseResizeHeight; + bool mMinimized; + bool mMaximized; + + Point2I mMouseDownPosition; + RectI mOrigBounds; + RectI mStandardBounds; + + RectI mCloseButton; + RectI mMinimizeButton; + RectI mMaximizeButton; + S32 mMinimizeIndex; + S32 mTabIndex; + + void PositionButtons(void); + + protected: + enum BitmapIndices + { + BmpClose, + BmpMaximize, + BmpNormal, + BmpMinimize, + + BmpCount + }; + enum { + BorderTopLeftKey = 12, + BorderTopRightKey, + BorderTopKey, + BorderTopLeftNoKey, + BorderTopRightNoKey, + BorderTopNoKey, + BorderLeft, + BorderRight, + BorderBottomLeft, + BorderBottom, + BorderBottomRight, + NumBitmaps + }; + + enum BitmapStates + { + BmpDefault = 0, + BmpHilite, + BmpDisabled, + + BmpStates + }; + RectI *mBitmapBounds; //bmp is [3*n], bmpHL is [3*n + 1], bmpNA is [3*n + 2] + GFXTexHandle mTextureObject; + + + /// Window Edge Bit Masks + /// + /// Edges can be combined to create a mask of multiple edges. + /// This is used for hit detection throughout this class. + enum Edges + { + edgeNone = 0, ///< No Edge + edgeTop = BIT(1), ///< Top Edge + edgeLeft = BIT(2), ///< Left Edge + edgeRight = BIT(3), ///< Right Edge + edgeBottom = BIT(4) ///< Bottom Edge + }; + + public: + GuiWindowCtrl(); + DECLARE_CONOBJECT(GuiWindowCtrl); + static void initPersistFields(); + + bool onWake(); + void onSleep(); + virtual void parentResized(const RectI &oldParentRect, const RectI &newParentRect); + + bool isMinimized(S32 &index); + + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + + void setFont(S32 fntTag); + + void setCloseCommand(const char *newCmd); + + GuiControl* findHitControl(const Point2I &pt, S32 initialLayer = -1); + S32 findHitEdges( const Point2I &globalPoint ); + void getSnappableWindows( Vector &outVector, Vector &windowOutVector); + bool resize(const Point2I &newPosition, const Point2I &newExtent); + + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + + //only cycle tabs through the current window, so overwrite the method + GuiControl* findNextTabable(GuiControl *curResponder, bool firstCall = true); + GuiControl* findPrevTabable(GuiControl *curResponder, bool firstCall = true); + + bool onKeyDown(const GuiEvent &event); + + S32 getTabIndex(void) { return mTabIndex; } + void selectWindow(void); + + void onRender(Point2I offset, const RectI &updateRect); + + //// + const RectI getClientRect(); + + /// Mutators for window properties from code. + /// Using setDataField is a bit overkill. + void setMobility( bool canMove, bool canClose, bool canMinimize, bool canMaximize, bool canDock, bool edgeSnap ) + { + mCanMove = canMove; + mCanClose = canClose; + mCanMinimize = canMinimize; + mCanMaximize = canMaximize; + mCanDock = canDock; + mEdgeSnap = edgeSnap; + } + + /// Mutators for window properties from code. + /// Using setDataField is a bit overkill. + void setCanResize( bool canResizeWidth, bool canResizeHeight ) + { + mResizeWidth = canResizeWidth; + mResizeHeight = canResizeHeight; + } + + // Allows the text to be set + void setText( const char* text ) + { + mText = StringTable->insert(text); + } +}; +/// @} + +#endif //_GUI_WINDOW_CTRL_H diff --git a/gui/controls/guiBackgroundCtrl.cpp b/gui/controls/guiBackgroundCtrl.cpp new file mode 100644 index 0000000..adf1c82 --- /dev/null +++ b/gui/controls/guiBackgroundCtrl.cpp @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "gui/controls/guiBackgroundCtrl.h" + +IMPLEMENT_CONOBJECT(GuiBackgroundCtrl); + +//-------------------------------------------------------------------------- +GuiBackgroundCtrl::GuiBackgroundCtrl() : GuiControl() +{ + mDraw = false; + mIsContainer = true; +} + +//-------------------------------------------------------------------------- +void GuiBackgroundCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if ( mDraw ) + Parent::onRender( offset, updateRect ); + + renderChildControls(offset, updateRect); +} + + diff --git a/gui/controls/guiBackgroundCtrl.h b/gui/controls/guiBackgroundCtrl.h new file mode 100644 index 0000000..a7275b9 --- /dev/null +++ b/gui/controls/guiBackgroundCtrl.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBACKGROUNDCTRL_H_ +#define _GUIBACKGROUNDCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +/// Renders a background, so you can have a backdrop for your GUI. +class GuiBackgroundCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + bool mDraw; + + //creation methods + DECLARE_CONOBJECT(GuiBackgroundCtrl); + DECLARE_CATEGORY( "Gui Containers" ); + + GuiBackgroundCtrl(); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif diff --git a/gui/controls/guiBitmapBorderCtrl.cpp b/gui/controls/guiBitmapBorderCtrl.cpp new file mode 100644 index 0000000..07fe711 --- /dev/null +++ b/gui/controls/guiBitmapBorderCtrl.cpp @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "gui/core/guiControl.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" + + +/// Renders a skinned border. +class GuiBitmapBorderCtrl : public GuiControl +{ + typedef GuiControl Parent; + + enum { + BorderTopLeft, + BorderTopRight, + BorderTop, + BorderLeft, + BorderRight, + BorderBottomLeft, + BorderBottom, + BorderBottomRight, + NumBitmaps + }; + RectI *mBitmapBounds; ///< bmp is [3*n], bmpHL is [3*n + 1], bmpNA is [3*n + 2] + GFXTexHandle mTextureObject; +public: + bool onWake(); + void onSleep(); + void onRender(Point2I offset, const RectI &updateRect); + DECLARE_CONOBJECT(GuiBitmapBorderCtrl); + DECLARE_CATEGORY( "Gui Images" ); +}; + +IMPLEMENT_CONOBJECT(GuiBitmapBorderCtrl); + +bool GuiBitmapBorderCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + //get the texture for the close, minimize, and maximize buttons + mBitmapBounds = NULL; + mTextureObject = mProfile->mTextureObject; + if( mProfile->constructBitmapArray() >= NumBitmaps ) + mBitmapBounds = mProfile->mBitmapArrayRects.address(); + else + Con::errorf( "GuiBitmapBorderCtrl: Could not construct bitmap array for profile '%s'", mProfile->getName() ); + + return true; +} + +void GuiBitmapBorderCtrl::onSleep() +{ + mTextureObject = NULL; + mBitmapBounds = NULL; + + Parent::onSleep(); +} + +void GuiBitmapBorderCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + renderChildControls( offset, updateRect ); + + if( mBitmapBounds ) + { + GFX->setClipRect(updateRect); + + //draw the outline + RectI winRect; + winRect.point = offset; + winRect.extent = getExtent(); + + winRect.point.x += mBitmapBounds[BorderLeft].extent.x; + winRect.point.y += mBitmapBounds[BorderTop].extent.y; + + winRect.extent.x -= mBitmapBounds[BorderLeft].extent.x + mBitmapBounds[BorderRight].extent.x; + winRect.extent.y -= mBitmapBounds[BorderTop].extent.y + mBitmapBounds[BorderBottom].extent.y; + + if(mProfile->mOpaque) + GFX->getDrawUtil()->drawRectFill(winRect, mProfile->mFillColor); + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, offset, mBitmapBounds[BorderTopLeft]); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, Point2I(offset.x + getWidth() - mBitmapBounds[BorderTopRight].extent.x, offset.y), + mBitmapBounds[BorderTopRight]); + + RectI destRect; + destRect.point.x = offset.x + mBitmapBounds[BorderTopLeft].extent.x; + destRect.point.y = offset.y; + destRect.extent.x = getWidth() - mBitmapBounds[BorderTopLeft].extent.x - mBitmapBounds[BorderTopRight].extent.x; + destRect.extent.y = mBitmapBounds[BorderTop].extent.y; + RectI stretchRect = mBitmapBounds[BorderTop]; + stretchRect.inset(1,0); + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + + destRect.point.x = offset.x; + destRect.point.y = offset.y + mBitmapBounds[BorderTopLeft].extent.y; + destRect.extent.x = mBitmapBounds[BorderLeft].extent.x; + destRect.extent.y = getHeight() - mBitmapBounds[BorderTopLeft].extent.y - mBitmapBounds[BorderBottomLeft].extent.y; + stretchRect = mBitmapBounds[BorderLeft]; + stretchRect.inset(0,1); + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + + destRect.point.x = offset.x + getWidth() - mBitmapBounds[BorderRight].extent.x; + destRect.extent.x = mBitmapBounds[BorderRight].extent.x; + destRect.point.y = offset.y + mBitmapBounds[BorderTopRight].extent.y; + destRect.extent.y = getHeight() - mBitmapBounds[BorderTopRight].extent.y - mBitmapBounds[BorderBottomRight].extent.y; + + stretchRect = mBitmapBounds[BorderRight]; + stretchRect.inset(0,1); + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, offset + Point2I(0, getHeight() - mBitmapBounds[BorderBottomLeft].extent.y), mBitmapBounds[BorderBottomLeft]); + GFX->getDrawUtil()->drawBitmapSR(mTextureObject, offset + getExtent() - mBitmapBounds[BorderBottomRight].extent, mBitmapBounds[BorderBottomRight]); + + destRect.point.x = offset.x + mBitmapBounds[BorderBottomLeft].extent.x; + destRect.extent.x = getWidth() - mBitmapBounds[BorderBottomLeft].extent.x - mBitmapBounds[BorderBottomRight].extent.x; + + destRect.point.y = offset.y + getHeight() - mBitmapBounds[BorderBottom].extent.y; + destRect.extent.y = mBitmapBounds[BorderBottom].extent.y; + stretchRect = mBitmapBounds[BorderBottom]; + stretchRect.inset(1,0); + + GFX->getDrawUtil()->drawBitmapStretchSR(mTextureObject, destRect, stretchRect); + } +} diff --git a/gui/controls/guiBitmapCtrl.cpp b/gui/controls/guiBitmapCtrl.cpp new file mode 100644 index 0000000..44aa57c --- /dev/null +++ b/gui/controls/guiBitmapCtrl.cpp @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiBitmapCtrl.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiBitmapCtrl); + +GuiBitmapCtrl::GuiBitmapCtrl(void) + : mBitmapName(), + mStartPoint( 0, 0 ), + mWrap( false ) +{ +} + +bool GuiBitmapCtrl::setBitmapName( void *obj, const char *data ) +{ + // Prior to this, you couldn't do bitmap.bitmap = "foo.jpg" and have it work. + // With protected console types you can now call the setBitmap function and + // make it load the image. + static_cast( obj )->setBitmap( data ); + + // Return false because the setBitmap method will assign 'mBitmapName' to the + // argument we are specifying in the call. + return false; +} + +void GuiBitmapCtrl::initPersistFields() +{ + addGroup("GuiBitmapCtrl"); + addProtectedField( "bitmap", TypeImageFilename, Offset( mBitmapName, GuiBitmapCtrl ), &setBitmapName, &defaultProtectedGetFn, "" ); + addField("wrap", TypeBool, Offset(mWrap, GuiBitmapCtrl)); + endGroup("GuiBitmapCtrl"); + + Parent::initPersistFields(); +} + +ConsoleMethod( GuiBitmapCtrl, setValue, void, 4, 4, "(int xAxis, int yAxis)" + "Set the offset of the bitmap.") +{ + object->setValue(dAtoi(argv[2]), dAtoi(argv[3])); +} + +ConsoleMethod( GuiBitmapCtrl, setBitmap, void, 3, 4, "(string filename, bool resize=false)" + "Set the bitmap displayed in the control. Note that it is limited in size, to 256x256.") +{ + char fileName[1024]; + Con::expandScriptFilename(fileName, sizeof(fileName), argv[2]); + object->setBitmap(fileName, argc > 3 ? dAtob( argv[3] ) : false ); +} + +bool GuiBitmapCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + setActive(true); + setBitmap(mBitmapName); + return true; +} + +void GuiBitmapCtrl::onSleep() +{ + if ( !mBitmapName.equal("texhandle", String::NoCase) ) + mTextureObject = NULL; + + Parent::onSleep(); +} + +//------------------------------------- +void GuiBitmapCtrl::inspectPostApply() +{ + // if the extent is set to (0,0) in the gui editor and appy hit, this control will + // set it's extent to be exactly the size of the bitmap (if present) + Parent::inspectPostApply(); + + if (!mWrap && (getExtent().x == 0) && (getExtent().y == 0) && mTextureObject) + { + setExtent( mTextureObject->getWidth(), mTextureObject->getHeight()); + } +} + +void GuiBitmapCtrl::setBitmap( const char *name, bool resize ) +{ + mBitmapName = name; + if ( !isAwake() ) + return; + + if ( mBitmapName.isNotEmpty() ) + { + if ( !mBitmapName.equal("texhandle", String::NoCase) ) + mTextureObject.set( mBitmapName, &GFXDefaultGUIProfile, avar("%s() - mTextureObject (line %d)", __FUNCTION__, __LINE__) ); + + // Resize the control to fit the bitmap + if ( mTextureObject && resize ) + { + setExtent( mTextureObject->getWidth(), mTextureObject->getHeight() ); + updateSizing(); + } + } + else + mTextureObject = NULL; + + setUpdate(); +} + +void GuiBitmapCtrl::updateSizing() +{ + if(!getParent()) + return; + // updates our bounds according to our horizSizing and verSizing rules + RectI fakeBounds( getPosition(), getParent()->getExtent()); + parentResized( fakeBounds, fakeBounds); +} + +void GuiBitmapCtrl::setBitmapHandle(GFXTexHandle handle, bool resize) +{ + mTextureObject = handle; + + mBitmapName = String("texhandle"); + + // Resize the control to fit the bitmap + if (resize) + { + setExtent(mTextureObject->getWidth(), mTextureObject->getHeight()); + updateSizing(); + } +} + + +void GuiBitmapCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if (mTextureObject) + { + GFX->getDrawUtil()->clearBitmapModulation(); + if(mWrap) + { + // We manually draw each repeat because non power of two textures will + // not tile correctly when rendered with GFX->drawBitmapTile(). The non POT + // bitmap will be padded by the hardware, and we'll see lots of slack + // in the texture. So... lets do what we must: draw each repeat by itself: + GFXTextureObject* texture = mTextureObject; + RectI srcRegion; + RectI dstRegion; + float xdone = ((float)getExtent().x/(float)texture->mBitmapSize.x)+1; + float ydone = ((float)getExtent().y/(float)texture->mBitmapSize.y)+1; + + int xshift = mStartPoint.x%texture->mBitmapSize.x; + int yshift = mStartPoint.y%texture->mBitmapSize.y; + for(int y = 0; y < ydone; ++y) + for(int x = 0; x < xdone; ++x) + { + srcRegion.set(0,0,texture->mBitmapSize.x,texture->mBitmapSize.y); + dstRegion.set( ((texture->mBitmapSize.x*x)+offset.x)-xshift, + ((texture->mBitmapSize.y*y)+offset.y)-yshift, + texture->mBitmapSize.x, + texture->mBitmapSize.y); + GFX->getDrawUtil()->drawBitmapStretchSR(texture,dstRegion, srcRegion, GFXBitmapFlip_None, GFXTextureFilterLinear); + } + + } + else + { + RectI rect(offset, getExtent()); + GFX->getDrawUtil()->drawBitmapStretch(mTextureObject, rect, GFXBitmapFlip_None, GFXTextureFilterLinear); + } + } + + if (mProfile->mBorder || !mTextureObject) + { + RectI rect(offset.x, offset.y, getExtent().x, getExtent().y); + GFX->getDrawUtil()->drawRect(rect, mProfile->mBorderColor); + } + + renderChildControls(offset, updateRect); +} + +void GuiBitmapCtrl::setValue(S32 x, S32 y) +{ + if (mTextureObject) + { + x += mTextureObject->getWidth() / 2; + y += mTextureObject->getHeight() / 2; + } + while (x < 0) + x += 256; + mStartPoint.x = x % 256; + + while (y < 0) + y += 256; + mStartPoint.y = y % 256; +} diff --git a/gui/controls/guiBitmapCtrl.h b/gui/controls/guiBitmapCtrl.h new file mode 100644 index 0000000..e259f9b --- /dev/null +++ b/gui/controls/guiBitmapCtrl.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBITMAPCTRL_H_ +#define _GUIBITMAPCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +/// Renders a bitmap. +class GuiBitmapCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +protected: + static bool setBitmapName( void *obj, const char *data ); + static const char *getBitmapName( void *obj, const char *data ); + + String mBitmapName; + GFXTexHandle mTextureObject; + Point2I mStartPoint; + bool mWrap; + +public: + //creation methods + DECLARE_CONOBJECT(GuiBitmapCtrl); + DECLARE_CATEGORY( "Gui Images" ); + DECLARE_DESCRIPTION( "A control that displays a single, static image from a file.\n" + "The bitmap can either be tiled or stretched inside the control." ); + + GuiBitmapCtrl(); + static void initPersistFields(); + + //Parental methods + bool onWake(); + void onSleep(); + void inspectPostApply(); + + void setBitmap(const char *name,bool resize = false); + void setBitmapHandle(GFXTexHandle handle, bool resize = false); + + + void updateSizing(); + + void onRender(Point2I offset, const RectI &updateRect); + void setValue(S32 x, S32 y); +}; + +#endif diff --git a/gui/controls/guiColorPicker.cpp b/gui/controls/guiColorPicker.cpp new file mode 100644 index 0000000..28fce38 --- /dev/null +++ b/gui/controls/guiColorPicker.cpp @@ -0,0 +1,523 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonCtrl.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gui/controls/guiColorPicker.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" + +/// @name Common colors we use +/// @{ +ColorF colorWhite(1.,1.,1.); +ColorF colorWhiteBlend(1.,1.,1.,.75); +ColorF colorBlack(.0,.0,.0); +ColorF colorAlpha(0.0f, 0.0f, 0.0f, 0.0f); +ColorF colorAlphaW(1.0f, 1.0f, 1.0f, 0.0f); + +ColorI GuiColorPickerCtrl::mColorRange[7] = { + ColorI(255,0,0), // Red + ColorI(255,0,255), // Pink + ColorI(0,0,255), // Blue + ColorI(0,255,255), // Light blue + ColorI(0,255,0), // Green + ColorI(255,255,0), // Yellow + ColorI(255,0,0), // Red +}; +/// @} + +IMPLEMENT_CONOBJECT(GuiColorPickerCtrl); + +//-------------------------------------------------------------------------- +GuiColorPickerCtrl::GuiColorPickerCtrl() +{ + setExtent(140, 30); + mDisplayMode = pPallet; + mBaseColor = ColorF(1.,.0,1.); + mPickColor = ColorF(.0,.0,.0); + mSelectorPos = Point2I(0,0); + mMouseDown = mMouseOver = false; + mActive = true; + mPositionChanged = false; + mSelectorGap = 1; + mActionOnMove = false; + mShowReticle = true; +} + +//-------------------------------------------------------------------------- +static const EnumTable::Enums gColorPickerModeEnums[] = +{ + { GuiColorPickerCtrl::pPallet, "Pallete" }, + { GuiColorPickerCtrl::pHorizColorRange, "HorizColor"}, + { GuiColorPickerCtrl::pVertColorRange, "VertColor" }, + { GuiColorPickerCtrl::pHorizColorBrightnessRange, "HorizBrightnessColor"}, + { GuiColorPickerCtrl::pVertColorBrightnessRange, "VertBrightnessColor" }, + { GuiColorPickerCtrl::pBlendColorRange, "BlendColor"}, + { GuiColorPickerCtrl::pHorizAlphaRange, "HorizAlpha"}, + { GuiColorPickerCtrl::pVertAlphaRange, "VertAlpha" }, + { GuiColorPickerCtrl::pDropperBackground, "Dropper" }, +}; + +static const EnumTable gColorPickerModeTable( 9, gColorPickerModeEnums ); + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::initPersistFields() +{ + addGroup("ColorPicker"); + addField("BaseColor", TypeColorF, Offset(mBaseColor, GuiColorPickerCtrl)); + addField("PickColor", TypeColorF, Offset(mPickColor, GuiColorPickerCtrl)); + addField("SelectorGap", TypeS32, Offset(mSelectorGap, GuiColorPickerCtrl)); + addField("DisplayMode", TypeEnum, Offset(mDisplayMode, GuiColorPickerCtrl), 1, &gColorPickerModeTable ); + addField("ActionOnMove", TypeBool,Offset(mActionOnMove, GuiColorPickerCtrl)); + addField("ShowReticle", TypeBool, Offset(mShowReticle, GuiColorPickerCtrl)); + endGroup("ColorPicker"); + + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +// Function to draw a box which can have 4 different colors in each corner blended together +void GuiColorPickerCtrl::drawBlendBox(RectI &bounds, ColorF &c1, ColorF &c2, ColorF &c3, ColorF &c4) +{ + GFX->setStateBlock(mStateBlock); + + S32 l = bounds.point.x, r = bounds.point.x + bounds.extent.x; + S32 t = bounds.point.y, b = bounds.point.y + bounds.extent.y; + + //A couple of checks to determine if color blend + if(c1 == colorWhite && c3 == colorAlpha && c4 == colorBlack) + { + //Color + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::color( c2 ); + PrimBuild::vertex2i( r, t ); + + PrimBuild::color( c2 ); + PrimBuild::vertex2i( r, b ); + + PrimBuild::color( c2 ); + PrimBuild::vertex2i( l, b ); + + PrimBuild::color( c2 ); + PrimBuild::vertex2i( l, t ); + PrimBuild::end(); + + //White + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::color( colorAlphaW ); + PrimBuild::vertex2i( r, t ); + + PrimBuild::color( colorAlphaW ); + PrimBuild::vertex2i( r, b ); + + PrimBuild::color( c1 ); + PrimBuild::vertex2i( l, b ); + + PrimBuild::color( c1 ); + PrimBuild::vertex2i( l, t ); + PrimBuild::end(); + + //Black + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::color( c3 ); + PrimBuild::vertex2i( r, t ); + + PrimBuild::color( c4 ); + PrimBuild::vertex2i( r, b ); + + PrimBuild::color( c4 ); + PrimBuild::vertex2i( l, b ); + + PrimBuild::color( c3 ); + PrimBuild::vertex2i( l, t ); + PrimBuild::end(); + } + else + { + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::color( c1 ); + PrimBuild::vertex2i( l, t ); + + PrimBuild::color( c2 ); + PrimBuild::vertex2i( r, t ); + + PrimBuild::color( c3 ); + PrimBuild::vertex2i( r, b ); + + PrimBuild::color( c4 ); + PrimBuild::vertex2i( l, b ); + PrimBuild::end(); + } +} + +//-------------------------------------------------------------------------- +/// Function to draw a set of boxes blending throughout an array of colors +void GuiColorPickerCtrl::drawBlendRangeBox(RectI &bounds, bool vertical, U8 numColors, ColorI *colors) +{ + + GFX->setStateBlock(mStateBlock); + + S32 l = bounds.point.x, r = bounds.point.x + bounds.extent.x + 4; + S32 t = bounds.point.y, b = bounds.point.y + bounds.extent.y + 4; + + // Calculate increment value + S32 x_inc = int(mFloor((r - l) / F32(numColors-1))); + S32 y_inc = int(mFloor((b - t) / F32(numColors-1))); + + for( U16 i = 0;i < numColors - 1; i++ ) + { + // This is not efficent, but then again it doesn't really need to be. -pw + PrimBuild::begin( GFXTriangleFan, 4 ); + + if (!vertical) // Horizontal (+x) + { + // First color + PrimBuild::color( colors[i] ); + PrimBuild::vertex2i( l, t ); + PrimBuild::vertex2i( l, b ); + + // Second color + PrimBuild::color( colors[i+1] ); + PrimBuild::vertex2i( l + x_inc, b ); + PrimBuild::vertex2i( l + x_inc, t ); + l += x_inc; + } + else // Vertical (+y) + { + // First color + PrimBuild::color( colors[i] ); + PrimBuild::vertex2i( l, t ); + PrimBuild::vertex2i( r, t ); + + // Second color + PrimBuild::color( colors[i+1] ); + PrimBuild::vertex2i( r, t + y_inc ); + PrimBuild::vertex2i( l, t + y_inc ); + t += y_inc; + } + PrimBuild::end(); + } +} + +void GuiColorPickerCtrl::drawSelector(RectI &bounds, Point2I &selectorPos, SelectorMode mode) +{ + if( !mShowReticle ) + return; + + U16 sMax = mSelectorGap*2; + switch (mode) + { + case sVertical: + // Now draw the vertical selector + // Up -> Pos + if (selectorPos.y != bounds.point.y+1) + GFX->getDrawUtil()->drawLine(selectorPos.x, bounds.point.y, selectorPos.x, selectorPos.y-sMax-1, colorWhiteBlend); + // Down -> Pos + if (selectorPos.y != bounds.point.y+bounds.extent.y) + GFX->getDrawUtil()->drawLine(selectorPos.x, selectorPos.y + sMax, selectorPos.x, bounds.point.y + bounds.extent.y, colorWhiteBlend); + break; + case sHorizontal: + // Now draw the horizontal selector + // Left -> Pos + if (selectorPos.x != bounds.point.x) + GFX->getDrawUtil()->drawLine(bounds.point.x, selectorPos.y-1, selectorPos.x-sMax, selectorPos.y-1, colorWhiteBlend); + // Right -> Pos + if (selectorPos.x != bounds.point.x) + GFX->getDrawUtil()->drawLine(bounds.point.x+mSelectorPos.x+sMax, selectorPos.y-1, bounds.point.x + bounds.extent.x, selectorPos.y-1, colorWhiteBlend); + break; + } +} + +//-------------------------------------------------------------------------- +/// Function to invoke calls to draw the picker box and selector +void GuiColorPickerCtrl::renderColorBox(RectI &bounds) +{ + RectI pickerBounds; + pickerBounds.point.x = bounds.point.x+1; + pickerBounds.point.y = bounds.point.y+1; + pickerBounds.extent.x = bounds.extent.x-1; + pickerBounds.extent.y = bounds.extent.y-1; + + if (mProfile->mBorder) + GFX->getDrawUtil()->drawRect(bounds, mProfile->mBorderColor); + + Point2I selectorPos = Point2I(bounds.point.x+mSelectorPos.x+1, bounds.point.y+mSelectorPos.y+1); + + // Draw color box differently depending on mode + RectI blendRect; + switch (mDisplayMode) + { + case pHorizColorRange: + drawBlendRangeBox( pickerBounds, false, 7, mColorRange); + drawSelector( pickerBounds, selectorPos, sVertical ); + break; + case pVertColorRange: + drawBlendRangeBox( pickerBounds, true, 7, mColorRange); + drawSelector( pickerBounds, selectorPos, sHorizontal ); + break; + case pHorizColorBrightnessRange: + blendRect = pickerBounds; + blendRect.point.y++; + blendRect.extent.y -= 2; + drawBlendRangeBox( pickerBounds, false, 7, mColorRange); + // This is being drawn slightly offset from the larger rect so as to insure 255 and 0 + // can both be selected for every color. + drawBlendBox( blendRect, colorAlpha, colorAlpha, colorBlack, colorBlack ); + blendRect.point.y += blendRect.extent.y - 1; + blendRect.extent.y = 2; + GFX->getDrawUtil()->drawRect( blendRect, colorBlack); + drawSelector( pickerBounds, selectorPos, sHorizontal ); + drawSelector( pickerBounds, selectorPos, sVertical ); + break; + case pVertColorBrightnessRange: + drawBlendRangeBox( pickerBounds, true, 7, mColorRange); + drawBlendBox( pickerBounds, colorAlpha, colorBlack, colorBlack, colorAlpha ); + drawSelector( pickerBounds, selectorPos, sHorizontal ); + drawSelector( pickerBounds, selectorPos, sVertical ); + break; + case pHorizAlphaRange: + drawBlendBox( pickerBounds, colorBlack, colorWhite, colorWhite, colorBlack ); + drawSelector( pickerBounds, selectorPos, sVertical ); + break; + case pVertAlphaRange: + drawBlendBox( pickerBounds, colorBlack, colorBlack, colorWhite, colorWhite ); + drawSelector( pickerBounds, selectorPos, sHorizontal ); + break; + case pBlendColorRange: + drawBlendBox( pickerBounds, colorWhite, mBaseColor, colorAlpha, colorBlack ); + drawSelector( pickerBounds, selectorPos, sHorizontal ); + drawSelector( pickerBounds, selectorPos, sVertical ); + break; + case pDropperBackground: + break; + case pPallet: + default: + GFX->getDrawUtil()->drawRectFill( pickerBounds, mBaseColor ); + break; + } +} + +void GuiColorPickerCtrl::onRender(Point2I offset, const RectI& updateRect) +{ + if (mStateBlock.isNull()) + { + GFXStateBlockDesc desc; + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setZReadWrite(false); + desc.zWriteEnable = false; + desc.setCullMode(GFXCullNone); + mStateBlock = GFX->createStateBlock( desc ); + } + + RectI boundsRect(offset, getExtent()); + renderColorBox(boundsRect); + + if (mPositionChanged) + { + mPositionChanged = false; + Point2I extent = getRoot()->getExtent(); + // If we are anything but a pallete, change the pick color + if (mDisplayMode != pPallet) + { + Point2I resolution = getRoot()->getExtent(); + + U32 buf_x = offset.x + mSelectorPos.x + 1; + U32 buf_y = ( extent.y - ( offset.y + mSelectorPos.y + 1 ) ); + if(GFX->getAdapterType() != OpenGL) + buf_y = resolution.y - buf_y; + + GFXTexHandle bb( resolution.x, + resolution.y, + GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar("%s() - bb (line %d)", __FUNCTION__, __LINE__) ); + + Point2I tmpPt( buf_x, buf_y ); + + GFXTarget *targ = GFX->getActiveRenderTarget(); + targ->resolveTo( bb ); + + GBitmap bmp( bb.getWidth(), bb.getHeight() ); + + bb.copyToBmp( &bmp ); + + //bmp.writePNGDebug( "foo.png" ); + + ColorI tmp; + bmp.getColor( buf_x, buf_y, tmp ); + + mPickColor = (ColorF)tmp; + + // Now do onAction() if we are allowed + if (mActionOnMove) + onAction(); + } + + } + + //render the children + renderChildControls( offset, updateRect); +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::setSelectorPos(const Point2I &pos) +{ + Point2I extent = getExtent(); + RectI rect; + if (mDisplayMode != pDropperBackground) + { + extent.x -= 3; + extent.y -= 2; + rect = RectI(Point2I(1,1), extent); + } + else + { + rect = RectI(Point2I(0,0), extent); + } + + if (rect.pointInRect(pos)) + { + mSelectorPos = pos; + mPositionChanged = true; + // We now need to update + setUpdate(); + } + + else + { + if ((pos.x > rect.point.x) && (pos.x < (rect.point.x + rect.extent.x))) + mSelectorPos.x = pos.x; + else if (pos.x <= rect.point.x) + mSelectorPos.x = rect.point.x; + else if (pos.x >= (rect.point.x + rect.extent.x)) + mSelectorPos.x = rect.point.x + rect.extent.x - 1; + + if ((pos.y > rect.point.y) && (pos.y < (rect.point.y + rect.extent.y))) + mSelectorPos.y = pos.y; + else if (pos.y <= rect.point.y) + mSelectorPos.y = rect.point.y; + else if (pos.y >= (rect.point.y + rect.extent.y)) + mSelectorPos.y = rect.point.y + rect.extent.y - 1; + + mPositionChanged = true; + setUpdate(); + } +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::onMouseDown(const GuiEvent &event) +{ + if (!mActive) + return; + + if (mDisplayMode == pDropperBackground) + return; + + mouseLock(this); + + if (mProfile->mCanKeyFocus) + setFirstResponder(); + + if (mActive && (mDisplayMode != pDropperBackground)) + onAction(); + + // Update the picker cross position + if (mDisplayMode != pPallet) + setSelectorPos(globalToLocalCoord(event.mousePoint)); + + mMouseDown = true; +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::onMouseDragged(const GuiEvent &event) +{ + if ((mActive && mMouseDown) || (mActive && (mDisplayMode == pDropperBackground))) + { + // Update the picker cross position + if (mDisplayMode != pPallet) + setSelectorPos(globalToLocalCoord(event.mousePoint)); + } + + if (!mActionOnMove && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand, false ); + +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::onMouseMove(const GuiEvent &event) +{ + // Only for dropper mode + if (mActive && (mDisplayMode == pDropperBackground)) + setSelectorPos(globalToLocalCoord(event.mousePoint)); +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::onMouseEnter(const GuiEvent &event) +{ + mMouseOver = true; +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::onMouseLeave(const GuiEvent &) +{ + // Reset state + mMouseOver = false; +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::onMouseUp(const GuiEvent &) +{ + //if we released the mouse within this control, perform the action + if (mActive && mMouseDown && (mDisplayMode != pDropperBackground)) + mMouseDown = false; + + if (mActive && (mDisplayMode == pDropperBackground)) + { + // In a dropper, the alt command executes the mouse up action (to signal stopping) + if ( mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand, false ); + } + + mouseUnlock(); +} + +//-------------------------------------------------------------------------- +const char *GuiColorPickerCtrl::getScriptValue() +{ + static char temp[256]; + ColorF color = getValue(); + dSprintf(temp,256,"%f %f %f %f",color.red, color.green, color.blue, color.alpha); + return temp; +} + +//-------------------------------------------------------------------------- +void GuiColorPickerCtrl::setScriptValue(const char *value) +{ + ColorF newValue; + dSscanf(value, "%f %f %f %f", &newValue.red, &newValue.green, &newValue.blue, &newValue.alpha); + setValue(newValue); +} + +ConsoleMethod(GuiColorPickerCtrl, getSelectorPos, const char*, 2, 2, "Gets the current position of the selector") +{ + char *temp = Con::getReturnBuffer(256); + Point2I pos; + pos = object->getSelectorPos(); + dSprintf(temp,256,"%d %d",pos.x, pos.y); + return temp; +} + +ConsoleMethod(GuiColorPickerCtrl, setSelectorPos, void, 3, 3, "Sets the current position of the selector") +{ + Point2I newPos; + dSscanf(argv[2], "%d %d", &newPos.x, &newPos.y); + object->setSelectorPos(newPos); +} + +ConsoleMethod(GuiColorPickerCtrl, updateColor, void, 2, 2, "Forces update of pick color") +{ + object->updateColor(); +} diff --git a/gui/controls/guiColorPicker.h b/gui/controls/guiColorPicker.h new file mode 100644 index 0000000..96f52e1 --- /dev/null +++ b/gui/controls/guiColorPicker.h @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUICOLORPICKER_H_ +#define _GUICOLORPICKER_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +//---------------------------- +/// GuiColorPickerCtrl +/// +/// This control draws a box containing a color specified by mPickColor, +/// in a way according to one of the PickMode enum's, stored as mDisplayMode. +/// +/// The color the box represents is stored as mBaseColour (for pPallete, pBlendColorRange), +/// whilst the color chosen by the box is stored as mPickColor. +/// +/// Whenever the control is clicked, it will do one of many things : +/// +/// -# If its in pPallete mode, execute the regular "command" +/// -# If its in pBlendColorRange mode, update the selector position. The position will be re-read upon the next render. In addition, a cross will be drawn where the color has been selected from. As with 1, "command" will be executed. +/// -# If its in pHorizColorRange or pVertColorRange mode, it will function in a similar manner to 2, but the selector will resemble a horizontal or vertical bar. +/// -# If its in pHorizAlphaRange or pVertAlphaRange mode, it will also function the same way as 3 +/// -# If its in pDropperBackground mode, nothing will happen +/// +/// Colours are drawn in different ways according to mDisplayMode: +/// +/// -# With pPallete, a box with a blank color, mBaseColor is drawn. +/// -# With pHorizColorRange, a horizontal box with colors blending in the range, mColorRange. +/// -# With pVertColorRange, a vertical box with colors blending in the range, mColorRange. +/// -# With pBlendColorRange, a box, the bottom colors being black, but the top left being white, and the top right being mBaseColor. +/// -# With pHorizAlphaRange, a horizontal box with black blending with an alpha from 0 to 255 +/// -# With pVertAlphaRange, a vertical box with black blending with an apha from 0 to 255 +/// -# With pDropperBackground, nothing is drawn +class GuiColorPickerCtrl : public GuiControl +{ + typedef GuiControl Parent; + + public: + enum PickMode + { + pPallet = 0, ///< We just have a solid color; We just act like a pallet + pHorizColorRange, ///< We have a range of base colors going horizontally + pVertColorRange, ///< We have a range of base colors going vertically + pHorizColorBrightnessRange, ///< HorizColorRange with brightness + pVertColorBrightnessRange, ///< VertColorRange with brightness + pBlendColorRange, ///< We have a box which shows a range in brightness of the color + pHorizAlphaRange, ///< We have a box which shows a range in alpha going horizontally + pVertAlphaRange, ///< We have a box which shows a range in alpha going vertically + pDropperBackground ///< The control does not draw anything; Only does something when you click, or move the mouse (when active) + }; + + enum SelectorMode + { + sHorizontal = 0, ///< Horizontal selector with small gap + sVertical, ///< Vertical selector with small gap + }; + + protected: + + /// @name Core Rendering functions + /// @{ + void renderColorBox(RectI &bounds); ///< Function that draws the actual color box + void drawSelector(RectI &bounds, Point2I &selectorPos, SelectorMode mode); ///< Function that draws the selection indicator + void drawBlendBox(RectI &bounds, ColorF &c1, ColorF &c2, ColorF &c3, ColorF &c4); + void drawBlendRangeBox(RectI &bounds, bool vertical, U8 numColors, ColorI *colors); + /// @} + + /// @name Core Variables + /// @{ + ColorF mPickColor; ///< Color that has been picked from control + ColorF mBaseColor; ///< Colour we display (in case of pallet and blend mode) + PickMode mDisplayMode; ///< Current color display mode of the selector + + Point2I mSelectorPos; ///< Current position of the selector + bool mPositionChanged; ///< Current position has changed since last render? + bool mMouseOver; ///< Mouse is over? + bool mMouseDown; ///< Mouse button down? + bool mActionOnMove; ///< Perform onAction() when position has changed? + + + + S32 mSelectorGap; ///< The half-way "gap" between the selector pos and where the selector is allowed to draw. + + GFXStateBlockRef mStateBlock; + + static ColorI mColorRange[7]; ///< Color range for pHorizColorRange and pVertColorRange + /// @} + + public: + + DECLARE_CONOBJECT(GuiColorPickerCtrl); + DECLARE_CATEGORY( "Gui Editor" ); + + GuiColorPickerCtrl(); + + static void initPersistFields(); + void onRender(Point2I offset, const RectI &updateRect); + bool mShowReticle; ///< Show reticle on render + /// @name Color Value Functions + /// @{ + /// NOTE: setValue only sets baseColor, since setting pickColor wouldn't be useful + void setValue(ColorF &value) {mBaseColor = value;} + /// NOTE: getValue() returns baseColor if pallet (since pallet controls can't "pick" colours themselves) + ColorF getValue() {return mDisplayMode == pPallet ? mBaseColor : mPickColor;} + const char *getScriptValue(); + void setScriptValue(const char *value); + void updateColor() {mPositionChanged = true;} + /// @} + + /// @name Selector Functions + /// @{ + void setSelectorPos(const Point2I &pos); ///< Set new pos (in local coords) + Point2I getSelectorPos() {return mSelectorPos;} + /// @} + + /// @name Input Events + /// @{ + void onMouseDown(const GuiEvent &); + void onMouseUp(const GuiEvent &); + void onMouseMove(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + + void onMouseEnter(const GuiEvent &); + void onMouseLeave(const GuiEvent &); + /// @} +}; + +#endif diff --git a/gui/controls/guiConsole.cpp b/gui/controls/guiConsole.cpp new file mode 100644 index 0000000..6b310e8 --- /dev/null +++ b/gui/controls/guiConsole.cpp @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiTypes.h" +#include "gui/core/guiControl.h" +#include "gui/controls/guiConsole.h" +#include "gui/containers/guiScrollCtrl.h" + +IMPLEMENT_CONOBJECT(GuiConsole); + +GuiConsole::GuiConsole() +{ + setExtent(64, 64); + mCellSize.set(1, 1); + mSize.set(1, 0); +} + +bool GuiConsole::onWake() +{ + if (! Parent::onWake()) + return false; + + //get the font + mFont = mProfile->mFont; + + return true; +} + +S32 GuiConsole::getMaxWidth(S32 startIndex, S32 endIndex) +{ + //sanity check + U32 size; + ConsoleLogEntry *log; + + Con::getLockLog(log, size); + + if(startIndex < 0 || (U32)endIndex >= size || startIndex > endIndex) + return 0; + + S32 result = 0; + for(S32 i = startIndex; i <= endIndex; i++) + result = getMax(result, (S32)(mFont->getStrWidth((const UTF8 *)log[i].mString))); + + Con::unlockLog(); + + return(result + 6); +} + +void GuiConsole::onPreRender() +{ + //see if the size has changed + U32 prevSize = getHeight() / mCellSize.y; + U32 size; + ConsoleLogEntry *log; + + Con::getLockLog(log, size); + Con::unlockLog(); // we unlock immediately because we only use size here, not log. + + if(size != prevSize) + { + //first, find out if the console was scrolled up + bool scrolled = false; + GuiScrollCtrl *parent = dynamic_cast(getParent()); + + if(parent) + scrolled = parent->isScrolledToBottom(); + + //find the max cell width for the new entries + S32 newMax = getMaxWidth(prevSize, size - 1); + if(newMax > mCellSize.x) + mCellSize.set(newMax, mFont->getHeight()); + + //set the array size + mSize.set(1, size); + + //resize the control + setExtent( Point2I(mCellSize.x, mCellSize.y * size)); + + //if the console was not scrolled, make the last entry visible + if (scrolled) + scrollCellVisible(Point2I(0,mSize.y - 1)); + } +} + +void GuiConsole::onRenderCell(Point2I offset, Point2I cell, bool /*selected*/, bool /*mouseOver*/) +{ + U32 size; + ConsoleLogEntry *log; + + Con::getLockLog(log, size); + + ConsoleLogEntry &entry = log[cell.y]; + switch (entry.mLevel) + { + case ConsoleLogEntry::Normal: GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor); break; + case ConsoleLogEntry::Warning: GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColorHL); break; + case ConsoleLogEntry::Error: GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColorNA); break; + default: AssertFatal(false, "GuiConsole::onRenderCell - Unrecognized ConsoleLogEntry type, update this."); + } + GFX->getDrawUtil()->drawText(mFont, Point2I(offset.x + 3, offset.y), entry.mString, mProfile->mFontColors); + + Con::unlockLog(); +} diff --git a/gui/controls/guiConsole.h b/gui/controls/guiConsole.h new file mode 100644 index 0000000..098a1b9 --- /dev/null +++ b/gui/controls/guiConsole.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONSOLE_H_ +#define _GUICONSOLE_H_ + +#ifndef _GUIARRAYCTRL_H_ +#include "gui/core/guiArrayCtrl.h" +#endif + +class GuiConsole : public GuiArrayCtrl +{ + private: + typedef GuiArrayCtrl Parent; + + Resource mFont; + + S32 getMaxWidth(S32 startIndex, S32 endIndex); + + public: + GuiConsole(); + DECLARE_CONOBJECT(GuiConsole); + DECLARE_CATEGORY( "Gui Editor" ); + DECLARE_DESCRIPTION( "Control that displays the console log text." ); + + bool onWake(); + + void onPreRender(); + void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); +}; + +#endif diff --git a/gui/controls/guiConsoleEditCtrl.cpp b/gui/controls/guiConsoleEditCtrl.cpp new file mode 100644 index 0000000..ec8b75f --- /dev/null +++ b/gui/controls/guiConsoleEditCtrl.cpp @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gui/controls/guiConsoleEditCtrl.h" +#include "core/frameAllocator.h" + +IMPLEMENT_CONOBJECT(GuiConsoleEditCtrl); + +GuiConsoleEditCtrl::GuiConsoleEditCtrl() +{ + mSinkAllKeyEvents = true; + mSiblingScroller = NULL; + mUseSiblingScroller = true; +} + +void GuiConsoleEditCtrl::initPersistFields() +{ + addGroup("GuiConsoleEditCtrl"); + addField("useSiblingScroller", TypeBool, Offset(mUseSiblingScroller, GuiConsoleEditCtrl)); + endGroup("GuiConsoleEditCtrl"); + + Parent::initPersistFields(); +} + +bool GuiConsoleEditCtrl::onKeyDown(const GuiEvent &event) +{ + setUpdate(); + + if (event.keyCode == KEY_TAB) + { + // Get a buffer that can hold the completed text... + FrameTemp tmpBuff(GuiTextCtrl::MAX_STRING_LENGTH); + // And copy the text to be completed into it. + mTextBuffer.getCopy8(tmpBuff, GuiTextCtrl::MAX_STRING_LENGTH); + + // perform the completion + bool forward = event.modifier & SI_SHIFT; + mCursorPos = Con::tabComplete(tmpBuff, mCursorPos, GuiTextCtrl::MAX_STRING_LENGTH, forward); + + // place results in our buffer. + mTextBuffer.set(tmpBuff); + return true; + } + else if ((event.keyCode == KEY_PAGE_UP) || (event.keyCode == KEY_PAGE_DOWN)) + { + // See if there's some other widget that can scroll the console history. + if (mUseSiblingScroller) + { + if (mSiblingScroller) + { + return mSiblingScroller->onKeyDown(event); + } + else + { + // Let's see if we can find it... + SimGroup* pGroup = getGroup(); + if (pGroup) + { + // Find the first scroll control in the same group as us. + for (SimSetIterator itr(pGroup); *itr; ++itr) + { + mSiblingScroller = dynamic_cast(*itr); + if (mSiblingScroller != NULL) + { + return mSiblingScroller->onKeyDown(event); + } + } + } + + // No luck... so don't try, next time. + mUseSiblingScroller = false; + } + } + } + else if( event.keyCode == KEY_RETURN || event.keyCode == KEY_NUMPADENTER ) + { + return Parent::dealWithEnter(false); + } + + return Parent::onKeyDown(event); +} + diff --git a/gui/controls/guiConsoleEditCtrl.h b/gui/controls/guiConsoleEditCtrl.h new file mode 100644 index 0000000..94e2251 --- /dev/null +++ b/gui/controls/guiConsoleEditCtrl.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONSOLEEDITCTRL_H_ +#define _GUICONSOLEEDITCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _GUITEXTEDITCTRL_H_ +#include "gui/controls/guiTextEditCtrl.h" +#endif +#ifndef _GUISCROLLCTRL_H_ +#include "gui/containers/guiScrollCtrl.h" +#endif + +class GuiConsoleEditCtrl : public GuiTextEditCtrl +{ +private: + typedef GuiTextEditCtrl Parent; + +protected: + bool mUseSiblingScroller; + GuiScrollCtrl* mSiblingScroller; + +public: + GuiConsoleEditCtrl(); + + DECLARE_CONOBJECT(GuiConsoleEditCtrl); + DECLARE_CATEGORY( "Gui Editor" ); + + static void initPersistFields(); + + bool onKeyDown(const GuiEvent &event); +}; + +#endif //_GUI_TEXTEDIT_CTRL_H diff --git a/gui/controls/guiConsoleTextCtrl.cpp b/gui/controls/guiConsoleTextCtrl.cpp new file mode 100644 index 0000000..ee74cf3 --- /dev/null +++ b/gui/controls/guiConsoleTextCtrl.cpp @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiConsoleTextCtrl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "core/color.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiDefaultControlRender.h" + +IMPLEMENT_CONOBJECT(GuiConsoleTextCtrl); + +GuiConsoleTextCtrl::GuiConsoleTextCtrl() +{ +} + +GuiConsoleTextCtrl::~GuiConsoleTextCtrl() +{ +} + +void GuiConsoleTextCtrl::initPersistFields() +{ + addGroup("GuiConsoleTextCtrl"); + addField("expression", TypeRealString, Offset(mConsoleExpression, GuiConsoleTextCtrl)); + endGroup("GuiConsoleTextCtrl"); + Parent::initPersistFields(); +} + +bool GuiConsoleTextCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + mFont = mProfile->mFont; + return true; +} + +void GuiConsoleTextCtrl::onSleep() +{ + Parent::onSleep(); + mFont = NULL; +} + +void GuiConsoleTextCtrl::setText(const char *txt) +{ + //make sure we don't call this before onAdd(); + AssertFatal(mProfile, "Can't call setText() until setProfile() has been called."); + + if (txt) + mConsoleExpression = txt; + else + mConsoleExpression = String::EmptyString; + + // make sure we have a font + mProfile->incRefCount(); + mFont = mProfile->mFont; + + setUpdate(); + + // decrement the profile reference + mProfile->decRefCount(); +} + +void GuiConsoleTextCtrl::calcResize() +{ + if ( mResult.isEmpty() ) + return; + + // The width is the longest line. + U32 ctrlWidth = 0; + for ( U32 i = 0; i < mLineLen.size(); i++ ) + { + U32 width = mFont->getStrNWidth( mResult.c_str() + mStartLineOffset[i], mLineLen[i] ); + + if ( width > ctrlWidth ) + ctrlWidth = width; + } + + // The height is the number of lines times the height of the font. + U32 ctrlHeight = mLineLen.size() * mFont->getHeight(); + + setExtent( Point2I( ctrlWidth, ctrlHeight ) + mProfile->mTextOffset * 2 ); +} + + +void GuiConsoleTextCtrl::onPreRender() +{ + if ( mConsoleExpression.isNotEmpty() ) + { + mResult = Con::evaluatef( "$guiConsoleTextCtrlTemp = %s;", mConsoleExpression.c_str() ); + + // Of the resulting string we will be printing, + // Find the number of lines and length of each. + mProfile->mFont->wrapString( mResult, U32_MAX, mStartLineOffset, mLineLen ); + } + else + mResult = String::EmptyString; + + calcResize(); +} + +void GuiConsoleTextCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + RectI ctrlRect( offset, getExtent() ); + + // if opaque, fill the update rect with the fill color + if ( mProfile->mOpaque ) + GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColor ); + + // if there's a border, draw the border + if ( mProfile->mBorder ) + renderBorder( ctrlRect, mProfile ); + + // If we have text to render. + if ( mResult.isNotEmpty() ) + { + GFont *font = mProfile->mFont; + + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor ); + + for ( U32 i = 0; i < mLineLen.size(); i++ ) + { + Point2I tempOffset = offset; + tempOffset += mProfile->mTextOffset; + tempOffset.y += i * font->getHeight(); + + const UTF8 *line = mResult.c_str() + mStartLineOffset[i]; + U32 lineLen = mLineLen[i]; + GFX->getDrawUtil()->drawTextN( font, tempOffset, line, lineLen, mProfile->mFontColors ); + } + } + + // render the child controlsmResult + renderChildControls(offset, updateRect); +} + +const char *GuiConsoleTextCtrl::getScriptValue() +{ + return getText(); +} + +void GuiConsoleTextCtrl::setScriptValue(const char *val) +{ + setText(val); +} diff --git a/gui/controls/guiConsoleTextCtrl.h b/gui/controls/guiConsoleTextCtrl.h new file mode 100644 index 0000000..9ba1d46 --- /dev/null +++ b/gui/controls/guiConsoleTextCtrl.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONSOLETEXTCTRL_H_ +#define _GUICONSOLETEXTCTRL_H_ + +#ifndef _GFONT_H_ +#include "gfx/gFont.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +class GuiConsoleTextCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + enum Constants { MAX_STRING_LENGTH = 255 }; + + +protected: + + String mConsoleExpression; + String mResult; + Resource mFont; + + Vector mStartLineOffset; + Vector mLineLen; + +public: + + //creation methods + DECLARE_CONOBJECT(GuiConsoleTextCtrl); + DECLARE_CATEGORY( "Gui Editor" ); + + GuiConsoleTextCtrl(); + virtual ~GuiConsoleTextCtrl(); + static void initPersistFields(); + + //Parental methods + bool onWake(); + void onSleep(); + + //text methods + virtual void setText( const char *txt = NULL ); + const char* getText() { return mConsoleExpression.c_str(); } + + //rendering methods + void calcResize(); + void onPreRender(); // do special pre render processing + void onRender( Point2I offset, const RectI &updateRect ); + + //Console methods + const char* getScriptValue(); + void setScriptValue( const char *value ); +}; + +#endif //_GUI_TEXT_CONTROL_H_ diff --git a/gui/controls/guiDecoyCtrl.cpp b/gui/controls/guiDecoyCtrl.cpp new file mode 100644 index 0000000..98a487d --- /dev/null +++ b/gui/controls/guiDecoyCtrl.cpp @@ -0,0 +1,216 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiDecoyCtrl.h" +#include "gui/buttons/guiButtonBaseCtrl.h" + +#include "console/consoleTypes.h" +#include "gfx/primBuilder.h" + +//----------------------------------------------------------------------------- +// GuiDecoyCtrl +//----------------------------------------------------------------------------- + +/* +So far this control has been designed in mind solely for button controls. I'm pretty sure +it can be used for other things, but to do anything more in depth; it has to be extended. +Make sure you know a little about how guiCanvas hands out signals to gui controls before you tinker +in this class. + +Been thinking about this class a little more. I tried pretty hard to protect this class into being +guiControl like agnostic. But I ended up adding a check specifically for buttons in the +onMouseUp function. Its been protected with a dynamic_cast and a NULL check; but in the end, the only way +too solve the main problem, that GuiCanvas cannot process more than one mouse action for more than one +gui control at a time, is for it to get a rewrite. +*/ + +IMPLEMENT_CONOBJECT(GuiDecoyCtrl); + +GuiDecoyCtrl::GuiDecoyCtrl() : mIsDecoy(true), + mMouseOver(false), + mDecoyReference(NULL) +{ +} + +GuiDecoyCtrl::~GuiDecoyCtrl() +{ +} + +void GuiDecoyCtrl::initPersistFields() +{ + addField("isDecoy", TypeBool, Offset(mIsDecoy, GuiDecoyCtrl)); + + Parent::initPersistFields(); +} + +void GuiDecoyCtrl::onMouseUp(const GuiEvent &event) +{ + mouseUnlock(); + setUpdate(); + + //this code is pretty hacky. right now there is no way that guiCanvas will allow sending more than + //one signal to one gui control at a time. + if(mIsDecoy == true) + { + mVisible = false; + + GuiControl *parent = getParent(); + Point2I localPoint = parent->globalToLocalCoord(event.mousePoint); + GuiControl *tempControl = parent->findHitControl(localPoint); + + //the decoy control has the responsibility of keeping track of the decoyed controls status + if( mDecoyReference != NULL && tempControl == mDecoyReference) + tempControl->onMouseUp(event); + else if(mDecoyReference != NULL && tempControl != mDecoyReference) + { + //as explained earlier, this control was written in the mindset for buttons. + //nothing bad should ever happen if not a button due to the checks in this function though. + GuiButtonBaseCtrl *unCastCtrl = NULL; + unCastCtrl = dynamic_cast( mDecoyReference ); + if(unCastCtrl != NULL) + unCastCtrl->resetState(); + } + mVisible = true; + } +} + +void GuiDecoyCtrl::onMouseDown(const GuiEvent &event) +{ + if ( !mVisible || !mAwake ) + return; + + mouseLock(); + + if(mIsDecoy == true) + { + mVisible = false; + + GuiControl *parent = getParent(); + Point2I localPoint = parent->globalToLocalCoord(event.mousePoint); + + GuiControl *tempControl = parent->findHitControl(localPoint); + tempControl->onMouseDown(event); + + mVisible = true; + } + + execConsoleCallback(); + setUpdate(); +} + +void GuiDecoyCtrl::onMouseMove(const GuiEvent &event) +{ + //if this control is a dead end, make sure the event stops here + if ( !mVisible || !mAwake ) + return; + + //pass the event to the parent + GuiControl *parent = getParent(); + if ( parent ) + parent->onMouseMove( event ); + + Point2I localPoint = parent->globalToLocalCoord(event.mousePoint); + + //also pretty hacky. since guiCanvas, *NOT* GuiControl, distributes the calls for onMouseEnter + //and onMouseLeave, we simulate those calls here through a series of checks. + if(mIsDecoy == true) + { + mVisible = false; + GuiControl *parent = getParent(); + GuiControl *tempControl = parent->findHitControl(localPoint); + + //the decoy control has the responsibility of keeping track of the decoyed controls status + if(mMouseOverDecoy == false && mDecoyReference != NULL) + { + tempControl->onMouseEnter(event); + mMouseOverDecoy = true; + } + else if(tempControl != mDecoyReference && mDecoyReference != NULL) + { + mDecoyReference->onMouseLeave(event); + mMouseOverDecoy = false; + } + + mDecoyReference = tempControl; + mVisible = true; + } +} + +void GuiDecoyCtrl::onMouseDragged(const GuiEvent &event) +{ +} + +void GuiDecoyCtrl::onMouseEnter(const GuiEvent &event) +{ + if ( !mVisible || !mAwake ) + return; + + setUpdate(); + Con::executef( this , "onMouseEnter" ); + mMouseOver = true; +} + +void GuiDecoyCtrl::onMouseLeave(const GuiEvent &event) +{ + if ( !mVisible || !mAwake || !mEnabled ) + return; + + setUpdate(); + Con::executef( this , "onMouseLeave" ); + mMouseOver = false; +} + +bool GuiDecoyCtrl::onMouseWheelUp( const GuiEvent &event ) +{ + //if this control is a dead end, make sure the event stops here + if ( !mVisible || !mAwake ) + return true; + + //pass the event to the parent + GuiControl *parent = getParent(); + if ( parent ) + return parent->onMouseWheelUp( event ); + else + return false; +} + +bool GuiDecoyCtrl::onMouseWheelDown( const GuiEvent &event ) +{ + //if this control is a dead end, make sure the event stops here + if ( !mVisible || !mAwake ) + return true; + + //pass the event to the parent + GuiControl *parent = getParent(); + if ( parent ) + return parent->onMouseWheelDown( event ); + else + return false; +} + +void GuiDecoyCtrl::onRightMouseDown(const GuiEvent &) +{ +} + +void GuiDecoyCtrl::onRightMouseUp(const GuiEvent &) +{ +} + +void GuiDecoyCtrl::onRightMouseDragged(const GuiEvent &) +{ +} + +void GuiDecoyCtrl::onMiddleMouseDown(const GuiEvent &) +{ +} + +void GuiDecoyCtrl::onMiddleMouseUp(const GuiEvent &) +{ +} + +void GuiDecoyCtrl::onMiddleMouseDragged(const GuiEvent &) +{ +} \ No newline at end of file diff --git a/gui/controls/guiDecoyCtrl.h b/gui/controls/guiDecoyCtrl.h new file mode 100644 index 0000000..a544c5d --- /dev/null +++ b/gui/controls/guiDecoyCtrl.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUIDECOYCTRL_H_ +#define _GUIDECOYCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +class GuiDecoyCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + // Constructor/Destructor/ConObject Declaration + GuiDecoyCtrl(); + virtual ~GuiDecoyCtrl(); + + DECLARE_CONOBJECT(GuiDecoyCtrl); + DECLARE_CATEGORY( "Gui Other" ); + + static void initPersistFields(); + + bool mMouseOver; + bool mIsDecoy; + GuiControl* mDecoyReference; + bool mMouseOverDecoy; + Point2I mMouseDownPosition; + + + virtual void onMouseUp(const GuiEvent &event); + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseMove(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseEnter(const GuiEvent &event); + virtual void onMouseLeave(const GuiEvent &event); + + virtual bool onMouseWheelUp(const GuiEvent &event); + virtual bool onMouseWheelDown(const GuiEvent &event); + + virtual void onRightMouseDown(const GuiEvent &event); + virtual void onRightMouseUp(const GuiEvent &event); + virtual void onRightMouseDragged(const GuiEvent &event); + + virtual void onMiddleMouseDown(const GuiEvent &event); + virtual void onMiddleMouseUp(const GuiEvent &event); + virtual void onMiddleMouseDragged(const GuiEvent &event); +}; +#endif diff --git a/gui/controls/guiDirectoryFileListCtrl.cpp b/gui/controls/guiDirectoryFileListCtrl.cpp new file mode 100644 index 0000000..4cf31e0 --- /dev/null +++ b/gui/controls/guiDirectoryFileListCtrl.cpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/findMatch.h" +#include "gui/controls/guiDirectoryFileListCtrl.h" + + +IMPLEMENT_CONOBJECT( GuiDirectoryFileListCtrl ); + +GuiDirectoryFileListCtrl::GuiDirectoryFileListCtrl() +{ + mFilePath = StringTable->insert( "" ); + mFilter = StringTable->insert( "*.*" ); +} + +void GuiDirectoryFileListCtrl::initPersistFields() +{ + addProtectedField( "filePath", TypeString, Offset( mFilePath, GuiDirectoryFileListCtrl ), + &_setFilePath, &defaultProtectedGetFn, 1, NULL, "Path in game directory from which to list files." ); + addProtectedField( "fileFilter", TypeString, Offset( mFilter, GuiDirectoryFileListCtrl ), + &_setFilter, &defaultProtectedGetFn, 1, NULL, "Tab-delimited list of file name patterns. Only matched files will be displayed." ); + + Parent::initPersistFields(); +} + +bool GuiDirectoryFileListCtrl::onWake() +{ + if( !Parent::onWake() ) + return false; + + update(); + + return true; +} + +void GuiDirectoryFileListCtrl::onMouseDown(const GuiEvent &event) +{ + Parent::onMouseDown( event ); + + if( event.mouseClickCount == 2 && isMethod("onDoubleClick") ) + Con::executef(this, "onDoubleClick"); +} + + +void GuiDirectoryFileListCtrl::openDirectory() +{ + String path; + if( mFilePath && mFilePath[ 0 ] ) + path = String::ToString( "%s/%s", Platform::getMainDotCsDir(), mFilePath ); + else + path = Platform::getMainDotCsDir(); + + Vector fileVector; + Platform::dumpPath( path, fileVector, 0 ); + + // Clear the current file listing + clearItems(); + + // Does this dir have any files? + if( fileVector.empty() ) + return; + + // If so, iterate through and list them + Vector::iterator i = fileVector.begin(); + for( S32 j=0 ; i != fileVector.end(); i++, j++ ) + { + if( !mFilter[ 0 ] || FindMatch::isMatchMultipleExprs( mFilter, (*i).pFileName,false ) ) + addItem( (*i).pFileName ); + } +} + + +void GuiDirectoryFileListCtrl::setCurrentFilter( const char* filter ) +{ + if( !filter ) + filter = ""; + + mFilter = StringTable->insert( filter ); + + // Update our view + openDirectory(); +} + +ConsoleMethod( GuiDirectoryFileListCtrl, setFilter, void, 3, 3, "%obj.setFilter([mask space delimited])") +{ + object->setCurrentFilter( argv[2] ); +} + +bool GuiDirectoryFileListCtrl::setCurrentPath( const char* path, const char* filter ) +{ + if( !path ) + return false; + + const U32 pathLen = dStrlen( path ); + if( pathLen > 0 && path[ pathLen - 1 ] == '/' ) + mFilePath = StringTable->insertn( path, pathLen - 1 ); + else + mFilePath = StringTable->insert( path ); + + if( filter ) + mFilter = StringTable->insert( filter ); + + // Update our view + openDirectory(); + + return true; +} + +ConsoleMethod( GuiDirectoryFileListCtrl, reload, void, 2, 2, "() - Update the file list." ) +{ + object->update(); +} + +ConsoleMethod( GuiDirectoryFileListCtrl, setPath, bool, 3, 4, "setPath(path,filter) - directory to enumerate files from (without trailing slash)" ) +{ + return object->setCurrentPath( argv[2], argv[3] ); +} + +ConsoleMethod( GuiDirectoryFileListCtrl, getSelectedFiles, const char*, 2, 2, "getSelectedFiles () - returns a word separated list of selected file(s)" ) +{ + Vector ItemVector; + object->getSelectedItems( ItemVector ); + + if( ItemVector.empty() ) + return StringTable->insert( "" ); + + // Get an adequate buffer + char itemBuffer[256]; + dMemset( itemBuffer, 0, 256 ); + + char* returnBuffer = Con::getReturnBuffer( ItemVector.size() * 64 ); + dMemset( returnBuffer, 0, ItemVector.size() * 64 ); + + // Fetch the first entry + StringTableEntry itemText = object->getItemText( ItemVector[0] ); + if( !itemText ) + return StringTable->lookup(""); + dSprintf( returnBuffer, ItemVector.size() * 64, "%s", itemText ); + + // If only one entry, return it. + if( ItemVector.size() == 1 ) + return returnBuffer; + + // Fetch the remaining entries + for( S32 i = 1; i < ItemVector.size(); i++ ) + { + StringTableEntry itemText = object->getItemText( ItemVector[i] ); + if( !itemText ) + continue; + + dMemset( itemBuffer, 0, 256 ); + dSprintf( itemBuffer, 256, " %s", itemText ); + dStrcat( returnBuffer, itemBuffer ); + } + + return returnBuffer; + +} + +StringTableEntry GuiDirectoryFileListCtrl::getSelectedFileName() +{ + S32 item = getSelectedItem(); + if( item == -1 ) + return StringTable->lookup(""); + + StringTableEntry itemText = getItemText( item ); + if( !itemText ) + return StringTable->lookup(""); + + return itemText; +} + +ConsoleMethod( GuiDirectoryFileListCtrl, getSelectedFile, const char*, 2, 2, "getSelectedFile () - returns the currently selected file name" ) +{ + return object->getSelectedFileName(); +} diff --git a/gui/controls/guiDirectoryFileListCtrl.h b/gui/controls/guiDirectoryFileListCtrl.h new file mode 100644 index 0000000..0f647a2 --- /dev/null +++ b/gui/controls/guiDirectoryFileListCtrl.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_DIRECTORYFILELISTCTRL_H_ +#define _GUI_DIRECTORYFILELISTCTRL_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +#ifndef _GUI_LISTBOXCTRL_H_ +#include "gui/controls/guiListBoxCtrl.h" +#endif + +class GuiDirectoryFileListCtrl : public GuiListBoxCtrl +{ +private: + typedef GuiListBoxCtrl Parent; +protected: + StringTableEntry mFilePath; + StringTableEntry mFilter; + + void openDirectory(); + + static bool _setFilePath( void* obj, const char* data ) + { + GuiDirectoryFileListCtrl* ctrl = ( GuiDirectoryFileListCtrl* ) obj; + ctrl->setCurrentPath( data, ctrl->mFilter ); + return false; + } + static bool _setFilter( void* obj, const char* data ) + { + GuiDirectoryFileListCtrl* ctrl = ( GuiDirectoryFileListCtrl* ) obj; + ctrl->setCurrentFilter( data ); + return false; + } + +public: + GuiDirectoryFileListCtrl(); + + DECLARE_CONOBJECT(GuiDirectoryFileListCtrl); + DECLARE_DESCRIPTION( "A control that displays a list of files from within a single\n" + "directory in the game file system." ); + + static void initPersistFields(); + + void update() { openDirectory(); } + + /// Set the current path to grab files from + bool setCurrentPath( const char* path, const char* filter ); + void setCurrentFilter( const char* filter ); + + /// Get the currently selected file's name + StringTableEntry getSelectedFileName(); + + virtual void onMouseDown(const GuiEvent &event); + virtual bool onWake(); +}; + +#endif diff --git a/gui/controls/guiFileTreeCtrl.cpp b/gui/controls/guiFileTreeCtrl.cpp new file mode 100644 index 0000000..03b0057 --- /dev/null +++ b/gui/controls/guiFileTreeCtrl.cpp @@ -0,0 +1,407 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/controls/guiFileTreeCtrl.h" +#include "core/strings/findMatch.h" +#include "core/frameAllocator.h" +#include "core/strings/stringUnit.h" +#include "console/consoleTypes.h" + + +IMPLEMENT_CONOBJECT(GuiFileTreeCtrl); + + +static bool _isDirInMainDotCsPath(const char* dir) +{ + StringTableEntry cs = Platform::getMainDotCsDir(); + U32 len = dStrlen(cs) + dStrlen(dir) + 2; + FrameTemp fullpath(len); + dSprintf(fullpath, len, "%s/%s", cs, dir); + + return Platform::isDirectory(fullpath); +} + +static bool _hasChildren(const char* path) +{ + if( Platform::hasSubDirectory(path)) + return true; + + Vector dummy; + Platform::dumpDirectories( path, dummy, 0, true); + + return dummy.size() > 0; +} + +GuiFileTreeCtrl::GuiFileTreeCtrl() + : Parent() +{ + // Parent configuration + setBounds(0,0,200,100); + mDestroyOnSleep = false; + mSupportMouseDragging = false; + mMultipleSelections = false; + + mFileFilter = "*.cs *.gui *.ed.cs"; + _initFilters(); +} + +void GuiFileTreeCtrl::initPersistFields() +{ + addGroup( "File Tree" ); + addField( "rootPath", TypeRealString, Offset( mRootPath, GuiFileTreeCtrl ), "Path in game directory that should be displayed in the control." ); + addProtectedField( "fileFilter", TypeRealString, Offset( mFileFilter, GuiFileTreeCtrl ), + &_setFileFilterValue, &defaultProtectedGetFn, 1, NULL, "Vector of file patterns. If not empty, only files matching the pattern will be shown in the control." ); + endGroup( "File Tree" ); + + Parent::initPersistFields(); +} + +static void _dumpFiles(const char *path, Vector &directoryVector, S32 depth = 0) +{ + Vector fileVec; + Platform::dumpPath( path, fileVec, depth); + + for(U32 i = 0; i < fileVec.size(); i++) + { + directoryVector.push_back( StringTable->insert(fileVec[i].pFileName) ); + } +} + +void GuiFileTreeCtrl::updateTree() +{ + // Kill off any existing items + destroyTree(); + + // Here we're going to grab our system volumes from the platform layer and create them as roots + // + // Note : that we're passing a 1 as the last parameter to Platform::dumpDirectories, which tells it + // how deep to dump in recursion. This is an optimization to keep from dumping the whole file system + // to the tree. The tree will dump more paths as necessary when the virtual parents are expanded, + // much as windows does. + + // Determine the root path. + + String rootPath = Platform::getMainDotCsDir(); + if( !mRootPath.isEmpty() ) + rootPath = String::ToString( "%s/%s", rootPath.c_str(), mRootPath.c_str() ); + + // get the files in the main.cs dir + Vector pathVec; + Platform::dumpDirectories( rootPath, pathVec, 0, true); + _dumpFiles( rootPath, pathVec, 0); + if( ! pathVec.empty() ) + { + // get the last folder in the path. + char *dirname = dStrdup(rootPath); + U32 last = dStrlen(dirname)-1; + if(dirname[last] == '/') + dirname[last] = '\0'; + char* lastPathComponent = dStrrchr(dirname,'/'); + if(lastPathComponent) + *lastPathComponent++ = '\0'; + else + lastPathComponent = dirname; + + // Iterate through the returned paths and add them to the tree + Vector::iterator j = pathVec.begin(); + for( ; j != pathVec.end(); j++ ) + { + char fullModPathSub [512]; + dMemset( fullModPathSub, 0, 512 ); + dSprintf( fullModPathSub, 512, "%s/%s", lastPathComponent, (*j) ); + addPathToTree( *j ); + } + dFree(dirname); + } +} + +bool GuiFileTreeCtrl::onWake() +{ + if( !Parent::onWake() ) + return false; + + updateTree(); + + return true; +} + +bool GuiFileTreeCtrl::onVirtualParentExpand(Item *item) +{ + if( !item || !item->isExpanded() ) + return true; + + const char* pathToExpand = item->getValue(); + if( !pathToExpand ) + { + Con::errorf("GuiFileTreeCtrl::onVirtualParentExpand - Unable to retrieve item value!"); + return false; + } + + Vector pathVec; + _dumpFiles( pathToExpand, pathVec, 0 ); + Platform::dumpDirectories( pathToExpand, pathVec, 0, true); + if( ! pathVec.empty() ) + { + // Iterate through the returned paths and add them to the tree + Vector::iterator i = pathVec.begin(); + for( ; i != pathVec.end(); i++ ) + recurseInsert(item, (*i) ); + + item->setExpanded( true ); + } + + item->setVirtualParent( false ); + + // Update our tree view + buildVisibleTree(); + + return true; + +} + +void GuiFileTreeCtrl::addPathToTree( StringTableEntry path ) +{ + if( !path ) + { + Con::errorf("GuiFileTreeCtrl::addPathToTree - Invalid Path!"); + return; + } + + // Identify which root (volume) this path belongs to (if any) + S32 root = getFirstRootItem(); + StringTableEntry ourPath = &path[ dStrcspn( path, "/" ) + 1]; + StringTableEntry ourRoot = StringUnit::getUnit( path, 0, "/" ); + // There are no current roots, we can safely create one + if( root == 0 ) + { + recurseInsert( NULL, path ); + } + else + { + while( root != 0 ) + { + if( dStricmp( getItemValue( root ), ourRoot ) == 0 ) + { + recurseInsert( getItem( root ), ourPath ); + break; + } + root = this->getNextSiblingItem( root ); + } + // We found none so we'll create one + if ( root == 0 ) + { + recurseInsert( NULL, path ); + } + } +} + +void GuiFileTreeCtrl::onItemSelected( Item *item ) +{ + Con::executef( this, "onSelectPath", avar("%s",item->getValue()) ); + + mSelPath = item->getValue(); + if( _hasChildren( mSelPath ) ) + item->setVirtualParent( true ); +} + +bool GuiFileTreeCtrl::_setFileFilterValue( void* obj, const char* data ) +{ + GuiFileTreeCtrl* ctrl = ( GuiFileTreeCtrl* ) obj; + + ctrl->mFileFilter = data; + ctrl->_initFilters(); + + return false; +} + +void GuiFileTreeCtrl::_initFilters() +{ + mFilters.clear(); + + U32 index = 0; + while( true ) + { + const char* pattern = StringUnit::getUnit( mFileFilter, index, " " ); + if( !pattern[ 0 ] ) + break; + + mFilters.push_back( pattern ); + ++ index; + } +} + +bool GuiFileTreeCtrl::matchesFilters(const char* filename) +{ + if( !mFilters.size() ) + return true; + + for(int i = 0; i < mFilters.size(); i++) + { + if(FindMatch::isMatch( mFilters[i], filename)) + return true; + } + return false; +} + +void GuiFileTreeCtrl::recurseInsert( Item* parent, StringTableEntry path ) +{ + if( !path ) + return; + + char szPathCopy [ 1024 ]; + dMemset( szPathCopy, 0, 1024 ); + dStrcpy( szPathCopy, path ); + + // Jump over the first character if it's a root / + char *curPos = szPathCopy; + if( *curPos == '/' ) + curPos++; + + char szValue[1024]; + dMemset( szValue, 0, 1024 ); + if( parent ) + { + dMemset( szValue, 0, sizeof( szValue ) ); + dSprintf( szValue, sizeof( szValue ), "%s/%s", parent->getValue(), curPos ); + } + else + { + dStrncpy( szValue, curPos, sizeof( szValue ) ); + szValue[ sizeof( szValue ) - 1 ] = 0; + } + + const U32 valueLen = dStrlen( szValue ); + char* value = new char[ valueLen + 1 ]; + dMemcpy( value, szValue, valueLen + 1 ); + + char *delim = dStrchr( curPos, '/' ); + if ( delim ) + { + // terminate our / and then move our pointer to the next character (rest of the path) + *delim = 0x00; + delim++; + } + S32 itemIndex = 0; + // only insert blindly if we have no root + if( !parent ) + itemIndex = insertItem( 0, curPos, curPos ); + else + { + bool allowed = (_isDirInMainDotCsPath(value) || matchesFilters(value)); + Item *exists = parent->findChildByValue( szValue ); + if( allowed && !exists && dStrcmp( curPos, "" ) != 0 ) + { + // Since we're adding a child this parent can't be a virtual parent, so clear that flag + parent->setVirtualParent( false ); + + itemIndex = insertItem( parent->getID(), curPos); + Item *newitem = getItem(itemIndex); + newitem->setValue( value ); + } + else + { + itemIndex = ( parent != NULL ) ? ( ( exists != NULL ) ? exists->getID() : -1 ) : -1; + } + } + + Item *newitem = getItem(itemIndex); + if(newitem) + { + newitem->setValue( value ); + if( _isDirInMainDotCsPath( value ) ) + { + newitem->setNormalImage( Icon_FolderClosed ); + newitem->setExpandedImage( Icon_Folder ); + newitem->setVirtualParent(true); + newitem->setExpanded(false); + } + else + { + newitem->setNormalImage( Icon_Doc ); + } + } + // since we're only dealing with volumes and directories, all end nodes will be virtual parents + // so if we are at the bottom of the rabbit hole, set the item to be a virtual parent + Item* item = getItem( itemIndex ); + if(item) + { + item->setExpanded(false); + if(parent && _isDirInMainDotCsPath(item->getValue()) && Platform::hasSubDirectory(item->getValue())) + item->setVirtualParent(true); + } + if( delim ) + { + if( ( dStrcmp( delim, "" ) == 0 ) && item ) + { + item->setExpanded( false ); + if( parent && _hasChildren( item->getValue() ) ) + item->setVirtualParent( true ); + } + } + else + { + if( item ) + { + item->setExpanded( false ); + if( parent && _hasChildren( item->getValue() ) ) + item->setVirtualParent( true ); + } + } + + // Down the rabbit hole we go + recurseInsert( getItem( itemIndex ), delim ); + +} + +ConsoleMethod( GuiFileTreeCtrl, getSelectedPath, const char*, 2, 2, "getSelectedPath() - returns the currently selected path in the tree") +{ + const String& path = object->getSelectedPath(); + return Con::getStringArg( path ); +} + +ConsoleMethod( GuiFileTreeCtrl, setSelectedPath, bool, 3, 3, "setSelectedPath(path) - expands the tree to the specified path") +{ + return object->setSelectedPath( argv[ 2 ] ); +} + +ConsoleMethod( GuiFileTreeCtrl, reload, void, 2, 2, "() - Reread the directory tree hierarchy." ) +{ + object->updateTree(); +} + +bool GuiFileTreeCtrl::setSelectedPath( const char* path ) +{ + if( !path ) + return false; + + // Since we only list one deep on paths, we need to add the path to the tree just incase it isn't already indexed in the tree + // or else we wouldn't be able to select a path we hadn't previously browsed to. :) + if( _isDirInMainDotCsPath( path ) ) + addPathToTree( path ); + + // see if we have a child that matches what we want + for(U32 i = 0; i < mItems.size(); i++) + { + if( dStricmp( mItems[i]->getValue(), path ) == 0 ) + { + Item* item = mItems[i]; + AssertFatal(item,"GuiFileTreeCtrl::setSelectedPath - Item Index Bad, Fatal Mistake!!!"); + item->setExpanded( true ); + clearSelection(); + setItemSelected( item->getID(), true ); + // make sure all of it's parents are expanded + S32 parent = getParentItem( item->getID() ); + while( parent != 0 ) + { + setItemExpanded( parent, true ); + parent = getParentItem( parent ); + } + // Rebuild our tree just incase we've oops'd + buildVisibleTree(); + scrollVisible( item ); + } + } + return false; +} diff --git a/gui/controls/guiFileTreeCtrl.h b/gui/controls/guiFileTreeCtrl.h new file mode 100644 index 0000000..13096e5 --- /dev/null +++ b/gui/controls/guiFileTreeCtrl.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_FILETREECTRL_H_ +#define _GUI_FILETREECTRL_H_ + +#include "platform/platform.h" +#include "gui/controls/guiTreeViewCtrl.h" + +class GuiFileTreeCtrl : public GuiTreeViewCtrl +{ +private: + + // Utility functions + void recurseInsert( Item* parent, StringTableEntry path ); + void addPathToTree( StringTableEntry path ); + +protected: + String mSelPath; + String mFileFilter; + String mRootPath; + Vector< String > mFilters; + + void _initFilters(); + + static bool _setFileFilterValue( void* obj, const char* data ); + +public: + + typedef GuiTreeViewCtrl Parent; + + enum + { + Icon_Folder = 1, + Icon_FolderClosed = 2, + Icon_Doc = 3 + }; + + GuiFileTreeCtrl(); + + bool onWake(); + bool onVirtualParentExpand(Item *item); + void onItemSelected( Item *item ); + const String& getSelectedPath() { return mSelPath; } + bool setSelectedPath( const char* path ); + + bool matchesFilters(const char* filename); + void updateTree(); + + DECLARE_CONOBJECT( GuiFileTreeCtrl ); + DECLARE_DESCRIPTION( "A control that displays a hierarchical tree view of a path in the game file system.\n" + "Note that to enable expanding/collapsing of directories, the control must be\n" + "placed inside a GuiScrollCtrl." ); + + static void initPersistFields(); +}; + +#endif //_GUI_FILETREECTRL_H_ diff --git a/gui/controls/guiGameListMenuCtrl.cpp b/gui/controls/guiGameListMenuCtrl.cpp new file mode 100644 index 0000000..b7ad437 --- /dev/null +++ b/gui/controls/guiGameListMenuCtrl.cpp @@ -0,0 +1,770 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "guiGameListMenuCtrl.h" + +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + +//----------------------------------------------------------------------------- +// GuiGameListMenuCtrl +//----------------------------------------------------------------------------- + +GuiGameListMenuCtrl::GuiGameListMenuCtrl() + : mSelected(NO_ROW), + mHighlighted(NO_ROW), + mDebugRender(false) +{ + VECTOR_SET_ASSOCIATION(mRows); + + // initialize the control callbacks + mCallbackOnA = StringTable->insert(""); + mCallbackOnB = mCallbackOnA; + mCallbackOnX = mCallbackOnA; + mCallbackOnY = mCallbackOnA; +} + +GuiGameListMenuCtrl::~GuiGameListMenuCtrl() +{ + for (S32 i = 0; i < mRows.size(); ++i) + { + delete mRows[i]; + } +} + +void GuiGameListMenuCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + GuiGameListMenuProfile * profile = (GuiGameListMenuProfile *) mProfile; + + F32 xScale = (float) getWidth() / profile->getRowWidth(); + + bool profileHasIcons = profile->hasArrows(); + + S32 rowHeight = profile->getRowHeight(); + + Point2I currentOffset = offset; + Point2I extent = getExtent(); + Point2I rowExtent(extent.x, rowHeight); + Point2I textOffset(profile->mTextOffset.x * xScale, profile->mTextOffset.y); + Point2I textExtent(extent.x - textOffset.x, rowHeight); + Point2I iconExtent, iconOffset(0.0f, 0.0f); + if (profileHasIcons) + { + iconExtent = profile->getIconExtent(); + + // icon is centered vertically plus any specified offset + S32 iconOffsetY = (rowHeight - iconExtent.y) >> 1; + iconOffsetY += profile->mIconOffset.y; + iconOffset = Point2I(profile->mIconOffset.x * xScale, iconOffsetY); + } + for (Vector::iterator row = mRows.begin(); row < mRows.end(); ++row) + { + if (row != mRows.begin()) + { + // rows other than the first can have padding above them + currentOffset.y += (*row)->mHeightPad; + currentOffset.y += rowHeight; + } + + // select appropriate colors and textures + ColorI fontColor; + U32 buttonTextureIndex; + S32 iconIndex = (*row)->mIconIndex; + bool useHighlightIcon = (*row)->mUseHighlightIcon; + if (! (*row)->mEnabled) + { + buttonTextureIndex = Profile::TEX_DISABLED; + fontColor = profile->mFontColorNA; + } + else if (row == &mRows[mSelected]) + { + if (iconIndex != NO_ICON) + { + iconIndex++; + } + buttonTextureIndex = Profile::TEX_SELECTED; + fontColor = profile->mFontColorSEL; + } + else if ((mHighlighted != NO_ROW) && (row == &mRows[mHighlighted])) + { + if (iconIndex != NO_ICON && useHighlightIcon) + { + iconIndex++; + } + buttonTextureIndex = Profile::TEX_HIGHLIGHT; + fontColor = profile->mFontColorHL; + } + else + { + buttonTextureIndex = Profile::TEX_NORMAL; + fontColor = profile->mFontColor; + } + + // render the row bitmap + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretchSR(profile->mTextureObject, RectI(currentOffset, rowExtent), profile->getBitmapArrayRect(buttonTextureIndex)); + + // render the row icon if it has one + if ((iconIndex != NO_ICON) && profileHasIcons && (! profile->getBitmapArrayRect((U32)iconIndex).extent.isZero())) + { + iconIndex += Profile::TEX_FIRST_ICON; + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretchSR(profile->mTextureObject, RectI(currentOffset + iconOffset, iconExtent), profile->getBitmapArrayRect(iconIndex)); + } + + // render the row text + GFX->getDrawUtil()->setBitmapModulation(fontColor); + renderJustifiedText(currentOffset + textOffset, textExtent, (*row)->mLabel); + } + + if (mDebugRender) + { + onDebugRender(offset); + } + + renderChildControls(offset, updateRect); +} + +void GuiGameListMenuCtrl::onDebugRender(Point2I offset) +{ + GuiGameListMenuProfile * profile = (GuiGameListMenuProfile *) mProfile; + + F32 xScale = (float) getWidth() / profile->getRowWidth(); + + ColorI controlBorderColor(200, 200, 200); // gray + ColorI rowBorderColor(255, 127, 255); // magenta + ColorI hitBorderColor(255, 0, 0); // red + Point2I shrinker(-1, -1); + Point2I extent = getExtent(); + + // render a border around the entire control + RectI borderRect(offset, extent + shrinker); + GFX->getDrawUtil()->drawRect(borderRect, controlBorderColor); + + S32 rowHeight = profile->getRowHeight(); + Point2I currentOffset(offset); + Point2I rowExtent(extent.x, rowHeight); + rowExtent += shrinker; + Point2I hitAreaExtent(profile->getHitAreaExtent()); + hitAreaExtent.x *= xScale; + hitAreaExtent += shrinker; + Point2I hitAreaOffset = profile->mHitAreaUpperLeft; + hitAreaOffset.x *= xScale; + Point2I upperLeft; + for (Vector::iterator row = mRows.begin(); row < mRows.end(); ++row) + { + // set the top of the current row + if (row != mRows.begin()) + { + // rows other than the first can have padding above them + currentOffset.y += (*row)->mHeightPad; + currentOffset.y += rowHeight; + } + + // draw the box around the whole row's extent + upperLeft = currentOffset; + borderRect.point = upperLeft; + borderRect.extent = rowExtent; + GFX->getDrawUtil()->drawRect(borderRect, rowBorderColor); + + // draw the box around the hit area of the row + upperLeft = currentOffset + hitAreaOffset; + borderRect.point = upperLeft; + borderRect.extent = hitAreaExtent; + GFX->getDrawUtil()->drawRect(borderRect, hitBorderColor); + } +} + +void GuiGameListMenuCtrl::addRow(const char* label, const char* callback, S32 icon, S32 yPad, bool useHighlightIcon, bool enabled) +{ + Row * row = new Row(); + addRow(row, label, callback, icon, yPad, useHighlightIcon, enabled); +} + +void GuiGameListMenuCtrl::addRow(Row * row, const char* label, const char* callback, S32 icon, S32 yPad, bool useHighlightIcon, bool enabled) +{ + row->mLabel = StringTable->insert(label, true); + row->mScriptCallback = (dStrlen(callback) > 0) ? StringTable->insert(callback, true) : NULL; + row->mIconIndex = (icon < 0) ? NO_ICON : icon; + row->mHeightPad = yPad; + row->mUseHighlightIcon = useHighlightIcon; + row->mEnabled = enabled; + + mRows.push_back(row); + + updateHeight(); + + if (mSelected == NO_ROW) + { + selectFirstEnabledRow(); + } +} + +Point2I GuiGameListMenuCtrl::getMinExtent() const +{ + Point2I parentMin = Parent::getMinExtent(); + + GuiGameListMenuProfile * profile = (GuiGameListMenuProfile *) mProfile; + + S32 minHeight = 0; + S32 rowHeight = profile->getRowHeight(); + + for (Vector::const_iterator row = mRows.begin(); row < mRows.end(); ++row) + { + minHeight += rowHeight; + if (row != mRows.begin()) + { + minHeight += (*row)->mHeightPad; + } + } + + if (minHeight > parentMin.y) + parentMin.y = minHeight; + + return parentMin; +} + +bool GuiGameListMenuCtrl::onAdd() +{ + if ((! Parent::onAdd()) || (! hasValidProfile())) + { + return false; + } + + return true; +} + +bool GuiGameListMenuCtrl::onWake() +{ + if ((! Parent::onWake()) || (mRows.size() == 0) || (! hasValidProfile())) + { + AssertFatal(mRows.size() > 0, avar("GuiGameListMenuCtrl: %s can't be woken up without any rows. Please use \"addRow\" to add at least one row to the control before pushing it to the canvas.", getName())); + return false; + } + + enforceConstraints(); + + selectFirstEnabledRow(); + + setFirstResponder(); + + mHighlighted = NO_ROW; + + return true; +} + +bool GuiGameListMenuCtrl::hasValidProfile() const +{ + GuiGameListMenuProfile * profile = dynamic_cast(mProfile); + AssertFatal(profile, avar("GuiGameListMenuCtrl: %s can't be created with a profile of type %s. Please create it with a profile of type GuiGameListMenuProfile.", getName(), mProfile->getClassName())); + return profile; +} + +void GuiGameListMenuCtrl::enforceConstraints() +{ + if (hasValidProfile()) + { + ((GuiGameListMenuProfile *)mProfile)->enforceConstraints(); + } + updateHeight(); +} + +void GuiGameListMenuCtrl::updateHeight() +{ + S32 minHeight = getMinExtent().y; + if (getHeight() < minHeight) + { + setHeight(minHeight); + } +} + +void GuiGameListMenuCtrl::onMouseDown(const GuiEvent &event) +{ + S32 hitRow = getRow(event.mousePoint); + if (hitRow != NO_ROW) + { + S32 delta = (mSelected != NO_ROW) ? (hitRow - mSelected) : (mSelected + 1); + changeRow(delta); + } +} + +void GuiGameListMenuCtrl::onMouseLeave(const GuiEvent &event) +{ + mHighlighted = NO_ROW; +} + +void GuiGameListMenuCtrl::onMouseMove(const GuiEvent &event) +{ + S32 hitRow = getRow(event.mousePoint); + // allow mHighligetd to be set to NO_ROW so rows can be unhighlighted + mHighlighted = hitRow; +} + +void GuiGameListMenuCtrl::onMouseUp(const GuiEvent &event) +{ + S32 hitRow = getRow(event.mousePoint); + if ((hitRow != NO_ROW) && isRowEnabled(hitRow) && (hitRow == getSelected())) + { + activateRow(); + } +} + +void GuiGameListMenuCtrl::activateRow() +{ + S32 row = getSelected(); + if ((row != NO_ROW) && isRowEnabled(row) && (mRows[row]->mScriptCallback != NULL)) + { + setThisControl(); + if (Con::isFunction(mRows[row]->mScriptCallback)) + { + Con::executef(mRows[row]->mScriptCallback); + } + } +} + +S32 GuiGameListMenuCtrl::getRow(Point2I globalPoint) +{ + Point2I localPoint = globalToLocalCoord(globalPoint); + GuiGameListMenuProfile * profile = (GuiGameListMenuProfile *) mProfile; + + F32 xScale = (float) getWidth() / profile->getRowWidth(); + + S32 rowHeight = profile->getRowHeight(); + Point2I currentOffset(0, 0); + Point2I hitAreaUpperLeft = profile->mHitAreaUpperLeft; + hitAreaUpperLeft.x *= xScale; + Point2I hitAreaLowerRight = profile->mHitAreaLowerRight; + hitAreaLowerRight.x *= xScale; + + Point2I upperLeft, lowerRight; + for (Vector::iterator row = mRows.begin(); row < mRows.end(); ++row) + { + if (row != mRows.begin()) + { + // rows other than the first can have padding above them + currentOffset.y += (*row)->mHeightPad; + } + + upperLeft = currentOffset + hitAreaUpperLeft; + lowerRight = currentOffset + hitAreaLowerRight; + + if ((upperLeft.x <= localPoint.x) && (localPoint.x < lowerRight.x) && + (upperLeft.y <= localPoint.y) && (localPoint.y < lowerRight.y)) + { + return row - mRows.begin(); + } + + currentOffset.y += rowHeight; + } + + return NO_ROW; +} + +void GuiGameListMenuCtrl::setSelected(S32 index) +{ + if (index == NO_ROW) + { + // deselection + mSelected = NO_ROW; + return; + } + + if (! isValidRowIndex(index)) + { + return; + } + + if (! isRowEnabled(index)) + { + // row is disabled, it can't be selected + return; + } + + mSelected = mClamp(index, 0, mRows.size() - 1); +} + +bool GuiGameListMenuCtrl::isRowEnabled(S32 index) const +{ + if (! isValidRowIndex(index)) + { + return false; + } + + return mRows[index]->mEnabled; +} + +void GuiGameListMenuCtrl::setRowEnabled(S32 index, bool enabled) +{ + if (! isValidRowIndex(index)) + { + return; + } + + mRows[index]->mEnabled = enabled; + + if (getSelected() == index) + { + selectFirstEnabledRow(); + } +} + +bool GuiGameListMenuCtrl::isValidRowIndex(S32 index) const +{ + return ((0 <= index) && (index < mRows.size())); +} + +void GuiGameListMenuCtrl::selectFirstEnabledRow() +{ + setSelected(NO_ROW); + for (Vector::iterator row = mRows.begin(); row < mRows.end(); ++row) + { + if ((*row)->mEnabled) + { + setSelected(row - mRows.begin()); + return; + } + } +} + +bool GuiGameListMenuCtrl::onKeyDown(const GuiEvent &event) +{ + switch (event.keyCode) + { + case KEY_UP: + changeRow(-1); + return true; + + case KEY_DOWN: + changeRow(1); + return true; + + case KEY_A: + case KEY_RETURN: + case KEY_NUMPADENTER: + case KEY_SPACE: + case XI_A: + case XI_START: + doScriptCommand(mCallbackOnA); + return true; + + case KEY_B: + case KEY_ESCAPE: + case KEY_BACKSPACE: + case KEY_DELETE: + case XI_B: + case XI_BACK: + doScriptCommand(mCallbackOnB); + return true; + + case KEY_X: + case XI_X: + doScriptCommand(mCallbackOnX); + return true; + + case KEY_Y: + case XI_Y: + doScriptCommand(mCallbackOnY); + return true; + default: + break; + } + + return Parent::onKeyDown(event); +} + +bool GuiGameListMenuCtrl::onGamepadAxisUp(const GuiEvent &event) +{ + changeRow(-1); + return true; +} + +bool GuiGameListMenuCtrl::onGamepadAxisDown(const GuiEvent &event) +{ + changeRow(1); + return true; +} + +void GuiGameListMenuCtrl::doScriptCommand(StringTableEntry command) +{ + if (command && command[0]) + { + setThisControl(); + Con::evaluate(command, false, __FILE__); + } +} + +void GuiGameListMenuCtrl::changeRow(S32 delta) +{ + S32 oldRowIndex = getSelected(); + S32 newRowIndex = oldRowIndex; + do + { + newRowIndex += delta; + if (newRowIndex >= mRows.size()) + { + newRowIndex = 0; + } + else if (newRowIndex < 0) + { + newRowIndex = mRows.size() - 1; + } + } + while ((! mRows[newRowIndex]->mEnabled) && (newRowIndex != oldRowIndex)); + + setSelected(newRowIndex); + + // do the callback if it is defined + StringTableEntry onChange = StringTable->insert("onChange", false); + if (isMethod(onChange)) + { + Con::executef(this, onChange); + } +} + +void GuiGameListMenuCtrl::setThisControl() +{ + Con::setVariable("$ThisControl", avar("%d", getId())); +} + +StringTableEntry GuiGameListMenuCtrl::getRowLabel(S32 rowIndex) const +{ + AssertFatal(isValidRowIndex(rowIndex), avar("GuiGameListMenuCtrl: You can't get the label from row %d of %s because it is not a valid row index. Please specify a valid row index in the range [0, %d).", rowIndex, getName(), getRowCount())); + if (! isValidRowIndex(rowIndex)) + { + // not a valid row index, don't do anything + return StringTable->insert(""); + } + return mRows[rowIndex]->mLabel; +} + +void GuiGameListMenuCtrl::setRowLabel(S32 rowIndex, const char * label) +{ + AssertFatal(isValidRowIndex(rowIndex), avar("GuiGameListMenuCtrl: You can't set the label on row %d of %s because it is not a valid row index. Please specify a valid row index in the range [0, %d).", rowIndex, getName(), getRowCount())); + if (! isValidRowIndex(rowIndex)) + { + // not a valid row index, don't do anything + return; + } + + mRows[rowIndex]->mLabel = StringTable->insert(label, true); +} + +//----------------------------------------------------------------------------- +// Console stuff (GuiGameListMenuCtrl) +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiGameListMenuCtrl); + +void GuiGameListMenuCtrl::initPersistFields() +{ + addField("DebugRender", TypeBool, Offset(mDebugRender, GuiGameListMenuCtrl)); + addField("CallbackOnA", TypeString, Offset(mCallbackOnA, GuiGameListMenuCtrl)); + addField("CallbackOnB", TypeString, Offset(mCallbackOnB, GuiGameListMenuCtrl)); + addField("CallbackOnX", TypeString, Offset(mCallbackOnX, GuiGameListMenuCtrl)); + addField("CallbackOnY", TypeString, Offset(mCallbackOnY, GuiGameListMenuCtrl)); + + Parent::initPersistFields(); +} + +ConsoleMethod(GuiGameListMenuCtrl, addRow, void, 4, 8, + "(string label, string callback, int icon, int yPad, bool enabled)\n" + "Add a row to the list control.\n\n" + "\\param label The text to display on the row as a label.\n" + "\\param callback Name of a script function to use as a callback when this row is activated.\n" + "\\param icon [optional] Index of the icon to use as a marker.\n" + "\\param yPad [optional] An extra amount of height padding before the row. Does nothing on the first row.\n" + "\\param useHighlightIcon [optional] Does this row use the highlight icon?.\n" + "\\param enabled [optional] If this row is initially enabled.") +{ + object->addRow(argv[2], argv[3], argc > 4 ? dAtoi(argv[4]) : -1, argc > 5 ? dAtoi(argv[5]) : 0, argc > 6 ? dAtob(argv[6]) : true, argc > 7 ? dAtob(argv[7]) : true); +} + +ConsoleMethod(GuiGameListMenuCtrl, isRowEnabled, bool, 3, 3, + "(int row)\n" + "Determines if the specified row is enabled or disabled.\n\n" + "\\param row The row to set the enabled status of.\n" + "\\return (bool) True if the specified row is enabled. False if the row is not enabled or the given index was not valid.") +{ + return object->isRowEnabled(dAtoi(argv[2])); +} + +ConsoleMethod(GuiGameListMenuCtrl, setRowEnabled, void, 4, 4, + "(int row, bool enabled)\n" + "Sets a row's enabled status according to the given parameters.\n\n" + "\\param row The index to check for validity.\n" + "\\param enabled Indicate true to enable the row or false to disable it.") +{ + object->setRowEnabled(dAtoi(argv[2]), dAtob(argv[3])); +} + +ConsoleMethod(GuiGameListMenuCtrl, activateRow, void, 2, 2, + "()\n" + "Activates the current row. The script callback of the current row will be called (if it has one).") +{ + object->activateRow(); +} + +ConsoleMethod(GuiGameListMenuCtrl, getRowCount, S32, 2, 2, + "()\n" + "Gets the number of rows on the control.\n\n" + "\\return (int) The number of rows on the control.") +{ + return object->getRowCount(); +} + +ConsoleMethod(GuiGameListMenuCtrl, getRowLabel, const char *, 3, 3, + "(int rowIndex)\n" + "Gets the label displayed on the specified row.\n\n" + "\\param rowIndex Index of the row to get the label of.\n" + "\\return (string) The label for the row.") +{ + return object->getRowLabel(dAtoi(argv[2])); +} + +ConsoleMethod(GuiGameListMenuCtrl, setRowLabel, void, 4, 4, + "(int rowIndex, string label)\n" + "Sets the label on the given row.\n\n" + "\\param rowIndex Index of the row to set the label on.\n" + "\\param label Text to set as the label of the row.\n") +{ + object->setRowLabel(dAtoi(argv[2]), argv[3]); +} + +ConsoleMethod(GuiGameListMenuCtrl, setSelected, void, 3, 3, + "(int rowIndex)\n" + "Sets the selected row. Only rows that are enabled can be selected.\n\n" + "\\param index The index to set as selected.") +{ + object->setSelected(dAtoi(argv[2])); +} + +ConsoleMethod(GuiGameListMenuCtrl, getSelectedRow, S32, 2, 2, + "()\n" + "Gets the index of the currently selected row.\n\n" + "\\return (int) Index of the selected row.") +{ + return object->getSelected(); +} + +//----------------------------------------------------------------------------- +// GuiGameListMenuProfile +//----------------------------------------------------------------------------- + +GuiGameListMenuProfile::GuiGameListMenuProfile() +: mHitAreaUpperLeft(0, 0), + mHitAreaLowerRight(0, 0), + mIconOffset(0, 0), + mRowSize(0, 0), + mRowScale(1.0f, 1.0f) +{ +} + +bool GuiGameListMenuProfile::onAdd() +{ + if (! Parent::onAdd()) + { + return false; + } + + // We can't call enforceConstraints() here because incRefCount initializes + // some of the things to enforce. Do a basic sanity check here instead. + AssertFatal(dStrlen(mBitmapName) > 0, avar("GuiGameListMenuProfile: %s can't be created without a bitmap. Please add a 'Bitmap' property to the object definition.", getName())); + AssertFatal(mRowSize.x >= 0, avar("GuiGameListMenuProfile: %s can't have a negative row width. Please change the row width to be non-negative.", getName())); + AssertFatal(mRowSize.y >= 0, avar("GuiGameListMenuProfile: %s can't have a negative row height. Please change the row height to be non-negative.", getName())); + + return true; +} + +void GuiGameListMenuProfile::enforceConstraints() +{ + AssertFatal((! getBitmapArrayRect(0).extent.isZero()), avar("GuiGameListMenuCtrl: %s can't be created without a bitmap. Please add a bitmap to the profile's definition.", getName())); + + AssertFatal(mRowSize.x >= 0, avar("GuiGameListMenuProfile: %s can't have a negative row width. Please change the row width to be non-negative.", getName())); + mRowSize.x = getMax(mRowSize.x, 0); + + AssertFatal(mRowSize.y >= 0, avar("GuiGameListMenuProfile: %s can't have a negative row height. Please change the row height to be non-negative.", getName())); + mRowSize.y = getMax(mRowSize.y, 0); + + Point2I rowTexExtent = getBitmapArrayRect(TEX_NORMAL).extent; + mRowScale.x = (float) getRowWidth() / rowTexExtent.x; + mRowScale.y = (float) getRowHeight() / rowTexExtent.y; +} + +Point2I GuiGameListMenuProfile::getIconExtent() +{ + Point2I iconExtent = getBitmapArrayRect(TEX_FIRST_ICON).extent; + + // scale both by y to keep the aspect ratio + iconExtent.x *= mRowScale.y; + iconExtent.y *= mRowScale.y; + + return iconExtent; +} + +Point2I GuiGameListMenuProfile::getArrowExtent() +{ + Point2I arrowExtent = getBitmapArrayRect(TEX_FIRST_ARROW).extent; + + // scale both by y to keep the aspect ratio + arrowExtent.x *= mRowScale.y; + arrowExtent.y *= mRowScale.y; + + return arrowExtent; +} + +Point2I GuiGameListMenuProfile::getHitAreaExtent() +{ + if (mHitAreaLowerRight == mHitAreaUpperLeft) + { + return mRowSize; + } + else + { + return mHitAreaLowerRight - mHitAreaUpperLeft; + } +} + +//----------------------------------------------------------------------------- +// Console stuff (GuiGameListMenuProfile) +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiGameListMenuProfile); + +void GuiGameListMenuProfile::initPersistFields() +{ + addField("HitAreaUpperLeft", TypePoint2I, Offset(mHitAreaUpperLeft, GuiGameListMenuProfile)); + addField("HitAreaLowerRight", TypePoint2I, Offset(mHitAreaLowerRight, GuiGameListMenuProfile)); + addField("IconOffset", TypePoint2I, Offset(mIconOffset, GuiGameListMenuProfile)); + addField("RowSize", TypePoint2I, Offset(mRowSize, GuiGameListMenuProfile)); + + Parent::initPersistFields(); + + removeField("tab"); + removeField("mouseOverSelected"); + + removeField("modal"); + removeField("opaque"); + removeField("fillColor"); + removeField("fillColorHL"); + removeField("fillColorNA"); + removeField("border"); + removeField("borderThickness"); + removeField("borderColor"); + removeField("borderColorHL"); + removeField("borderColorNA"); + + removeField("bevelColorHL"); + removeField("bevelColorLL"); + + removeField("fontColorLink"); + removeField("fontColorLinkHL"); + + removeField("justify"); + removeField("returnTab"); + removeField("numbersOnly"); + removeField("cursorColor"); + + removeField("profileForChildren"); +} diff --git a/gui/controls/guiGameListMenuCtrl.h b/gui/controls/guiGameListMenuCtrl.h new file mode 100644 index 0000000..2aa2fea --- /dev/null +++ b/gui/controls/guiGameListMenuCtrl.h @@ -0,0 +1,331 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GuiGameListMenuCtrl_H_ +#define _GuiGameListMenuCtrl_H_ + +#include "gui/core/guiControl.h" + +class GuiGameListMenuProfile; + +/// \class GuiGameListMenuCtrl +/// A base class for cross platform menu controls that are gamepad friendly. +class GuiGameListMenuCtrl : public GuiControl +{ +public: + typedef GuiControl Parent; + typedef GuiGameListMenuProfile Profile; + +protected: + /// \struct Row + /// Internal data representation of a single row in the control. + struct Row + { + StringTableEntry mLabel; ///< Text to display in the row as a label + StringTableEntry mScriptCallback; ///< Script callback when row is activated + S32 mIconIndex; ///< Index of the icon to display on the row (-1 = no icon) + S32 mHeightPad; ///< Extra amount to pad above this row + bool mUseHighlightIcon; ///< Toggle the use of the highlight icon + bool mEnabled; ///< If this row is enabled or not (grayed out) + + virtual ~Row() {} + }; + +public: + /// \return The index of the highlighted row or NO_ROW if none of the rows + /// are currently highlighted. + virtual S32 getHighlighted() const { return mHighlighted; } + + /// \return The index of the selected row or NO_ROW if none of the rows are + /// currently selected. + virtual S32 getSelected() const { return mSelected; } + + /// Sets the selected row. Only rows that are enabled can be selected. Input is + /// clamped to [0, mRows.size()) + /// + /// \param index The index to set as selected. + virtual void setSelected(S32 index); + + /// Determines if the specified row is enabled or disabled. + /// + /// \param index Index of the row to check. + /// \return True if the specified row is enabled. False if the row is not + /// enabled or the given index was not valid. + virtual bool isRowEnabled(S32 index) const; + + /// Sets a row's enabled status according to the given parameters. + /// + /// \param index The row to set the enabled status of. + /// \param enabled Indicate true to enable the row or false to disable it. + virtual void setRowEnabled(S32 index, bool enabled); + + /// Gets the label displayed on the specified row. + /// + /// \param rowIndex Index of the row to get the label of. + /// \return The label for the row. + virtual StringTableEntry getRowLabel(S32 rowIndex) const; + + /// Sets the label on the given row. + /// + /// \param rowIndex Index of the row to set the label on. + /// \param label Text to set as the label of the row. + virtual void setRowLabel(S32 rowIndex, const char * label); + + /// Adds a row to the control. + /// + /// \param label The text to display on the row as a label. + /// \param callback Name of a script function to use as a callback when this + /// row is activated. + /// \param icon [optional] Index of the icon to use as a marker. Default -1 + /// means no icon will be shown on this row. + /// \param yPad [optional] An extra amount of height padding before the row. + /// \param enabled [optional] If this row is initially enabled. Default true. + virtual void addRow(const char* label, const char* callback, S32 icon = -1, S32 yPad = 0, bool useHighlightIcon = true, bool enabled = true); + + /// Activates the current row. The script callback of the current row will + /// be called (if it has one). + virtual void activateRow(); + + /// Gets the number of rows in the control. + /// + /// \return The number of rows in this control. + virtual S32 getRowCount() const { return mRows.size(); } + + GuiGameListMenuCtrl(); + ~GuiGameListMenuCtrl(); + + void onRender(Point2I offset, const RectI &updateRect); + + /// Callback when the object is registered with the sim. + /// + /// \return True if the profile was successfully added, false otherwise. + bool onAdd(); + + /// Callback when the control wakes up. + bool onWake(); + + /// Callback when a key is pressed. + /// + /// \param event The event that triggered this callback. + bool onKeyDown(const GuiEvent &event); + + /// Callback when a key is repeating. + /// + /// \param event The event that triggered this callback. + bool onKeyRepeat(const GuiEvent &event){ return onKeyDown(event); } + + /// Callback when the mouse button is clicked on the control. + /// + /// \param event A reference to the event that triggered the callback. + void onMouseDown(const GuiEvent &event); + + /// Callback when the mouse is dragged on the control. + /// + /// \param event A reference to the event that triggered the callback. + void onMouseDragged(const GuiEvent &event){ onMouseDown(event); } + + /// Callback when the mouse leaves the control. + /// + /// \param event A reference to the event that triggered the callback. + void onMouseLeave(const GuiEvent &event); + + /// Callback when the mouse is moving over this control + /// + /// \param event A reference to the event that triggered the callback. + void onMouseMove(const GuiEvent &event); + + /// Callback when the mouse button is released. + /// + /// \param event A reference to the event that triggered the callback. + void onMouseUp(const GuiEvent &event); + + /// Callback when the gamepad axis is activated. + /// + /// \param event A reference to the event that triggered the callback. + virtual bool onGamepadAxisUp(const GuiEvent & event); + + /// Callback when the gamepad axis is activated. + /// + /// \param event A reference to the event that triggered the callback. + virtual bool onGamepadAxisDown(const GuiEvent & event); + + DECLARE_CONOBJECT(GuiGameListMenuCtrl); + DECLARE_CATEGORY( "Gui Game" ); + + /// Initializes fields accessible through the console. + static void initPersistFields(); + + static const S32 NO_ROW = -1; ///< Indicates a query result of no row found. + static const S32 NO_ICON = -1; ///< Indicates a row has no extra icon available + +protected: + /// Adds a row to the control. + /// + /// \param row A reference to the row object to fill. + /// \param label The text to display on the row as a label. + /// \param callback Name of a script function to use as a callback when this + /// row is activated. + /// \param icon [optional] Index of the icon to use as a marker. Default -1 + /// means no icon will be shown on this row. + /// \param yPad [optional] An extra amount of height padding before the row. + /// \param enabled [optional] If this row is initially enabled. Default true. + virtual void addRow(Row * row, const char* label, const char* callback, S32 icon, S32 yPad, bool useHighlightIcon, bool enabled); + + /// Determines if the given index is a valid row index. Any index pointing at + /// an existing row is valid. + /// + /// \param index The index to check for validity. + /// \return True if the index points at a valid row, false otherwise. + virtual bool isValidRowIndex(S32 index) const; + + /// Sets the script variable $ThisControl to reflect this control. + virtual void setThisControl(); + + /// Called to implement debug rendering which displays colored lines to + /// provide visual feedback on extents and hit zones. + virtual void onDebugRender(Point2I offset); + + /// Looks up the row having a hit area at the given global point. + /// + /// \param globalPoint The point we want to check for hitting a row. + /// \return The index of the hit row or NO_ROW if no row was hit. + virtual S32 getRow(Point2I globalPoint); + + /// Checks to make sure our control has a profile of the correct type. + /// + /// \return True if the profile is of type GuiGameListMenuProfile or false if + /// the profile is of any other type. + virtual bool hasValidProfile() const; + + /// Enforces the validity of the fields on this control and its profile (if + /// the profile is valid, see: hasValidProfile). + virtual void enforceConstraints(); + + /// Evaluates some script. If the command is empty then nothing is evaluated. + /// + /// \param command The script to evaluate. + void doScriptCommand(StringTableEntry command); + + StringTableEntry mCallbackOnA; ///< Script callback when the 'A' button is pressed + StringTableEntry mCallbackOnB; ///< Script callback when the 'B' button is pressed + StringTableEntry mCallbackOnX; ///< Script callback when the 'X' button is pressed + StringTableEntry mCallbackOnY; ///< Script callback when the 'Y' button is pressed + + bool mDebugRender; ///< Determines when to show debug render lines + Vector mRows; ///< Holds data wrappers on all the rows we have + +private: + /// Recalculates the height of this control based on the stored row height and + /// and padding on the rows. + virtual Point2I getMinExtent() const; + + /// Makes sure the height will allow all rows to be displayed without being + /// truncated. + void updateHeight(); + + /// Sets the first enabled row as selected. If there are no enabled rows then + /// selected will be set to NO_ROW. + void selectFirstEnabledRow(); + + /// Changes the currently selected row. + /// + /// \param delta The amount to change the row selection by. Typically this will + /// be 1 or -1. + void changeRow(S32 delta); + + S32 mSelected; ///< index of the currently selected row + S32 mHighlighted; ///< index of the currently highlighted row +}; + +/// \class GuiGameListMenuProfile +/// A gui profile with additional fields specific to GuiGameListMenuCtrl. +class GuiGameListMenuProfile : public GuiControlProfile +{ + typedef GuiControlProfile Parent; + +public: + /// Enforces range constraints on all required fields. + virtual void enforceConstraints(); + + /// Get the height of rows in this profile. All rows are considered to be the + /// same base height. Rows can have an extra amount of y padding defined when + /// they are added to the control. + /// + /// \return The height of rows in this profile. + S32 getRowHeight() { return (mRowSize.y) ? mRowSize.y : getBitmapArrayRect(TEX_NORMAL).extent.y; } + + /// Get the width of rows in this profile. All rows are considered to be the + /// same width. + /// + /// \return The width of rows in this profile. + S32 getRowWidth() { return (mRowSize.x) ? mRowSize.x : getBitmapArrayRect(TEX_NORMAL).extent.x; } + + /// Row scale is the ratio between the defined row size and the raw size of + /// the bitmap. + /// + /// \return The row scale. + const Point2F & getRowScale() const { return mRowScale; } + + /// Gets the extent of icons for this profile. If there are no icons you will + /// get a point of (0, 0); + /// + /// \return The extent of icons or (0, 0) if there aren't any. + Point2I getIconExtent(); + + /// Gets the extent of arrows for this profile. If there are no arrows you + /// will get a point of (0, 0). + /// + /// \return The extent of icons or (0, 0) if there aren't any. + Point2I getArrowExtent(); + + /// Gets the extent of the defined hit area for this profile. If the hit area + /// is not defined then it defaults to the full size of a row. + /// + /// \return The extents of the defined hit area or the full size of the row. + Point2I getHitAreaExtent(); + + /// Determines if this profile has textures for the left and right arrows. + /// + /// \return True if the profile's bitmap has textures for the arrows, false + /// otherwise. + bool hasArrows(){ return (! getBitmapArrayRect(TEX_FIRST_ARROW).extent.isZero()); } + + /// Callback when the object is registered with the sim. + /// + /// \return True if the profile was successfully added, false otherwise. + bool onAdd(); + + Point2I mHitAreaUpperLeft; ///< Offset for the upper left corner of the hit area + Point2I mHitAreaLowerRight; ///< Offset for the lower right corner of the hit area + Point2I mIconOffset; ///< Offset for a row's extra icon + Point2I mRowSize; ///< The base size of a row + + GuiGameListMenuProfile(); + + DECLARE_CONOBJECT(GuiGameListMenuProfile); + + /// Initializes fields accessible through the console. + static void initPersistFields(); + + enum + { + TEX_NORMAL = 0, ///< texture index for a normal, unselected row + TEX_SELECTED = 1, ///< texture index for a selected row + TEX_HIGHLIGHT = 2, ///< texture index for a highlighted row (moused over, not selected) + TEX_DISABLED = 3, ///< texture index for a disabled row + TEX_L_ARROW_OFF = 4, ///< texture index for the left arrow of an unselected row + TEX_L_ARROW_ON = 5, ///< texture index for the left arrow of a selected row + TEX_R_ARROW_OFF = 6, ///< texture index for the right arrow of an unselected row + TEX_R_ARROW_ON = 7, ///< texture index for the right arrow of a selected row + + TEX_FIRST_ARROW = 4, ///< texture index for the first arrow + TEX_FIRST_ICON = 8, ///< texture index for the first row marker icon + }; + +private: + Point2F mRowScale; ///< Ratio of row size to actual bitmap size +}; + +#endif diff --git a/gui/controls/guiGameListOptionsCtrl.cpp b/gui/controls/guiGameListOptionsCtrl.cpp new file mode 100644 index 0000000..031b236 --- /dev/null +++ b/gui/controls/guiGameListOptionsCtrl.cpp @@ -0,0 +1,464 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "guiGameListOptionsCtrl.h" +#include "gfx/gfxDrawUtil.h" +#include "console/consoleTypes.h" +#include "core/strings/stringUnit.h" + +//----------------------------------------------------------------------------- +// GuiGameListOptionsCtrl +//----------------------------------------------------------------------------- + +GuiGameListOptionsCtrl::GuiGameListOptionsCtrl() +{ +} + +GuiGameListOptionsCtrl::~GuiGameListOptionsCtrl() +{ +} + +void GuiGameListOptionsCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + Parent::onRender(offset, updateRect); + GuiGameListOptionsProfile * profile = (GuiGameListOptionsProfile *) mProfile; + + F32 xScale = (float) getWidth() / profile->getRowWidth(); + + S32 rowHeight = profile->getRowHeight(); + + bool profileHasArrows = profile->hasArrows(); + Point2I arrowExtent; + S32 arrowOffsetY(0); + if (profileHasArrows) + { + arrowExtent = profile->getArrowExtent(); + + // icon is centered vertically + arrowOffsetY = (rowHeight - arrowExtent.y) >> 1; + } + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + Point2I currentOffset = offset; + Point2I arrowOffset; + S32 columnSplit = profile->mColumnSplit * xScale; + S32 iconIndex; + for (Vector::iterator row = mRows.begin(); row < mRows.end(); ++row) + { + Row * myRow = (Row *) *row; + if (row != mRows.begin()) + { + // rows other than the first can have padding above them + currentOffset.y += myRow->mHeightPad; + currentOffset.y += rowHeight; + } + + bool hasOptions = (myRow->mOptions.size() > 0); + if (hasOptions) + { + bool isRowSelected = (getSelected() != NO_ROW) && (row == &mRows[getSelected()]); + bool isRowHighlighted = (getHighlighted() != NO_ROW) ? ((row == &mRows[getHighlighted()]) && ((*row)->mEnabled)) : false; + if (profileHasArrows) + { + // render the left arrow + bool arrowOnL = (isRowSelected || isRowHighlighted) && (myRow->mWrapOptions || (myRow->mSelectedOption > 0)); + iconIndex = (arrowOnL) ? Profile::TEX_L_ARROW_ON : Profile::TEX_L_ARROW_OFF; + arrowOffset.x = currentOffset.x + columnSplit; + arrowOffset.y = currentOffset.y + arrowOffsetY; + + drawer->clearBitmapModulation(); + drawer->drawBitmapStretchSR(profile->mTextureObject, RectI(arrowOffset, arrowExtent), profile->getBitmapArrayRect((U32)iconIndex)); + + // render the right arrow + bool arrowOnR = (isRowSelected || isRowHighlighted) && (myRow->mWrapOptions || (myRow->mSelectedOption < myRow->mOptions.size() - 1)); + iconIndex = (arrowOnR) ? Profile::TEX_R_ARROW_ON : Profile::TEX_R_ARROW_OFF; + arrowOffset.x = currentOffset.x + (profile->mHitAreaLowerRight.x - profile->mRightPad) * xScale - arrowExtent.x; + arrowOffset.y = currentOffset.y + arrowOffsetY; + + drawer->clearBitmapModulation(); + drawer->drawBitmapStretchSR(profile->mTextureObject, RectI(arrowOffset, arrowExtent), profile->getBitmapArrayRect((U32)iconIndex)); + } + + // get the appropriate font color + ColorI fontColor; + if (! myRow->mEnabled) + { + fontColor = profile->mFontColorNA; + } + else if (isRowSelected) + { + fontColor = profile->mFontColorSEL; + } + else if (isRowHighlighted) + { + fontColor = profile->mFontColorHL; + } + else + { + fontColor = profile->mFontColor; + } + + // calculate text to be at the center between the arrows + GFont * font = profile->mFont; + StringTableEntry text = myRow->mOptions[myRow->mSelectedOption]; + S32 textWidth = font->getStrWidth(text); + S32 columnWidth = profile->mHitAreaLowerRight.x * xScale - profile->mRightPad - columnSplit; + S32 columnCenter = columnSplit + (columnWidth >> 1); + S32 textStartX = columnCenter - (textWidth >> 1); + Point2I textOffset(textStartX, 0); + + // render the option text itself + Point2I textExtent(columnWidth, rowHeight); + drawer->setBitmapModulation(fontColor); + renderJustifiedText(currentOffset + textOffset, textExtent, text); + } + } +} + +void GuiGameListOptionsCtrl::onDebugRender(Point2I offset) +{ + Parent::onDebugRender(offset); + GuiGameListOptionsProfile * profile = (GuiGameListOptionsProfile *) mProfile; + + F32 xScale = (float) getWidth() / profile->getRowWidth(); + + ColorI column1Color(255, 255, 0); // yellow + ColorI column2Color(0, 255, 0); // green + Point2I shrinker(-1, -1); + + S32 rowHeight = profile->getRowHeight(); + Point2I currentOffset(offset); + Point2I rowExtent(getExtent().x, rowHeight); + Point2I hitAreaExtent(profile->getHitAreaExtent()); + hitAreaExtent.x *= xScale; + hitAreaExtent += shrinker; + Point2I column1Extent((profile->mColumnSplit - profile->mHitAreaUpperLeft.x) * xScale - 1, hitAreaExtent.y); + Point2I column2Extent(hitAreaExtent.x - column1Extent.x - 1, hitAreaExtent.y); + Point2I hitAreaOffset = profile->mHitAreaUpperLeft; + hitAreaOffset.x *= xScale; + + RectI borderRect; + Point2I upperLeft; + for (Vector::iterator row = mRows.begin(); row < mRows.end(); ++row) + { + // set the top of the current row + if (row != mRows.begin()) + { + // rows other than the first can have padding above them + currentOffset.y += (*row)->mHeightPad; + currentOffset.y += rowHeight; + } + + // draw the box around column 1 + upperLeft = currentOffset + hitAreaOffset; + borderRect.point = upperLeft; + borderRect.extent = column1Extent; + GFX->getDrawUtil()->drawRect(borderRect, column1Color); + + // draw the box around column 2 + upperLeft.x += column1Extent.x + 1; + borderRect.point = upperLeft; + borderRect.extent = column2Extent; + GFX->getDrawUtil()->drawRect(borderRect, column2Color); + } +} + +void GuiGameListOptionsCtrl::addRow(const char* label, const char* optionsList, bool wrapOptions, const char* callback, S32 icon, S32 yPad, bool enabled) +{ + static StringTableEntry DELIM = StringTable->insert("\t", true); + Row * row = new Row(); + Vector options( __FILE__, __LINE__ ); + S32 count = StringUnit::getUnitCount(optionsList, DELIM); + for (int i = 0; i < count; ++i) + { + const char * option = StringUnit::getUnit(optionsList, i, DELIM); + options.push_back(StringTable->insert(option, true)); + } + row->mOptions = options; + bool hasOptions = row->mOptions.size() > 0; + row->mSelectedOption = (hasOptions) ? 0 : NO_OPTION; + row->mWrapOptions = wrapOptions; + Parent::addRow(row, label, callback, icon, yPad, true, (hasOptions) ? enabled : false); +} + +void GuiGameListOptionsCtrl::setOptions(S32 rowIndex, const char * optionsList) +{ + static StringTableEntry DELIM = StringTable->insert("\t", true); + + if (! isValidRowIndex(rowIndex)) + { + return; + } + + Row * row = (Row *)mRows[rowIndex]; + + S32 count = StringUnit::getUnitCount(optionsList, DELIM); + row->mOptions.setSize(count); + for (int i = 0; i < count; ++i) + { + const char * option = StringUnit::getUnit(optionsList, i, DELIM); + row->mOptions[i] = StringTable->insert(option, true); + } + + if (row->mSelectedOption >= row->mOptions.size()) + { + row->mSelectedOption = row->mOptions.size() - 1; + } +} + +bool GuiGameListOptionsCtrl::hasValidProfile() const +{ + GuiGameListOptionsProfile * profile = dynamic_cast(mProfile); + AssertFatal(profile, avar("GuiGameListOptionsCtrl: %s can't be created with a profile of type %s. Please create it with a profile of type GuiGameListOptionsProfile.", getName(), mProfile->getClassName())); + return profile; +} + +void GuiGameListOptionsCtrl::enforceConstraints() +{ + Parent::enforceConstraints(); +} + +void GuiGameListOptionsCtrl::onMouseUp(const GuiEvent &event) +{ + S32 hitRow = getRow(event.mousePoint); + if ((hitRow != NO_ROW) && isRowEnabled(hitRow) && (hitRow == getSelected())) + { + S32 xPos = globalToLocalCoord(event.mousePoint).x; + clickOption((Row *) mRows[getSelected()], xPos); + } +} + +void GuiGameListOptionsCtrl::clickOption(Row * row, S32 xPos) +{ + GuiGameListOptionsProfile * profile = (GuiGameListOptionsProfile *) mProfile; + if (! profile->hasArrows()) + { + return; + } + + F32 xScale = (float) getWidth() / profile->getRowWidth(); + + S32 bitmapArrowWidth = mProfile->getBitmapArrayRect(Profile::TEX_FIRST_ARROW).extent.x; + + S32 leftArrowX1 = profile->mColumnSplit * xScale; + S32 leftArrowX2 = leftArrowX1 + bitmapArrowWidth; + + S32 rightArrowX2 = (profile->mHitAreaLowerRight.x - profile->mRightPad) * xScale; + S32 rightArrowX1 = rightArrowX2 - bitmapArrowWidth; + + if ((leftArrowX1 <= xPos) && (xPos <= leftArrowX2)) + { + changeOption(row, -1); + } + else if ((rightArrowX1 <= xPos) && (xPos <= rightArrowX2)) + { + changeOption(row, 1); + } +} + +bool GuiGameListOptionsCtrl::onKeyDown(const GuiEvent &event) +{ + switch (event.keyCode) + { + case KEY_LEFT: + changeOption(-1); + return true; + + case KEY_RIGHT: + changeOption(1); + return true; + + default: + break; + } + + return Parent::onKeyDown(event); +} + +bool GuiGameListOptionsCtrl::onGamepadAxisLeft( const GuiEvent &event ) +{ + changeOption(-1); + return true; +} + +bool GuiGameListOptionsCtrl::onGamepadAxisRight( const GuiEvent &event ) +{ + changeOption(1); + return true; +} + +void GuiGameListOptionsCtrl::changeOption(S32 delta) +{ + if (getSelected() != NO_ROW) + { + Row * row = (Row *) mRows[getSelected()]; + changeOption(row, delta); + } +} + +void GuiGameListOptionsCtrl::changeOption(Row * row, S32 delta) +{ + S32 optionCount = row->mOptions.size(); + + S32 newSelection = row->mSelectedOption + delta; + if (optionCount == 0) + { + newSelection = NO_OPTION; + } + else if (! row->mWrapOptions) + { + newSelection = mClamp(newSelection, 0, optionCount - 1); + } + else if (newSelection < 0) + { + newSelection = optionCount - 1; + } + else if (newSelection >= optionCount) + { + newSelection = 0; + } + row->mSelectedOption = newSelection; + + static StringTableEntry LEFT = StringTable->insert("LEFT", true); + static StringTableEntry RIGHT = StringTable->insert("RIGHT", true); + + if (row->mScriptCallback != NULL) + { + setThisControl(); + StringTableEntry direction = NULL; + if (delta < 0) + { + direction = LEFT; + } + else if (delta > 0) + { + direction = RIGHT; + } + if ((direction != NULL) && (Con::isFunction(row->mScriptCallback))) + { + Con::executef(row->mScriptCallback, direction); + } + } +} + +StringTableEntry GuiGameListOptionsCtrl::getCurrentOption(S32 rowIndex) const +{ + if (isValidRowIndex(rowIndex)) + { + Row * row = (Row *) mRows[rowIndex]; + if (row->mSelectedOption != NO_OPTION) + { + return row->mOptions[row->mSelectedOption]; + } + } + return StringTable->insert("", false); +} + +bool GuiGameListOptionsCtrl::selectOption(S32 rowIndex, const char * theOption) +{ + if (! isValidRowIndex(rowIndex)) + { + return false; + } + + Row * row = (Row *) mRows[rowIndex]; + + for (Vector::iterator anOption = row->mOptions.begin(); anOption < row->mOptions.end(); ++anOption) + { + if (dStrcmp(*anOption, theOption) == 0) + { + S32 newIndex = anOption - row->mOptions.begin(); + row->mSelectedOption = newIndex; + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Console stuff (GuiGameListOptionsCtrl) +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiGameListOptionsCtrl); + +void GuiGameListOptionsCtrl::initPersistFields() +{ + Parent::initPersistFields(); +} + +ConsoleMethod(GuiGameListOptionsCtrl, addRow, void, 6, 9, + "(string label, string options, bool wrapOptions, string callback, int icon, int yPad, bool enabled)\n" + "Add a row to the list control.\n\n" + "\\param label The text to display on the row as a label.\n" + "\\param options A tab separated list of options.\n" + "\\param wrapOptions Specify true to allow options to wrap at each end or false to prevent wrapping.\n" + "\\param callback Name of a script function to use as a callback when this row is activated.\n" + "\\param icon [optional] Index of the icon to use as a marker.\n" + "\\param yPad [optional] An extra amount of height padding before the row. Does nothing on the first row.\n" + "\\param enabled [optional] If this row is initially enabled.") +{ + object->addRow(argv[2], argv[3], dAtob(argv[4]), argv[5], argc > 6 ? dAtoi(argv[6]) : -1, argc > 7 ? dAtoi(argv[7]) : 0, argc > 8 ? dAtob(argv[8]) : true); +} + +ConsoleMethod(GuiGameListOptionsCtrl, getCurrentOption, const char *, 3, 3, + "(int rowIndex)\n" + "Gets the text for the currently selected option of the given row.\n\n" + "\\param rowIndex Index of the row to get the option from.\n" + "\\return A string representing the text currently displayed as the selected option on the given row. If there is no such displayed text then the empty string is returned.") +{ + return object->getCurrentOption(dAtoi(argv[2])); +} + +ConsoleMethod(GuiGameListOptionsCtrl, selectOption, bool, 4, 4, + "(int rowIndex, string option)\n" + "Set the row's current option to the one specified\n\n" + "\\param rowIndex Index of the row to set an option on.\n" + "\\param option The option to be made active.\n" + "\\return True if the row contained the option and was set, false otherwise.") +{ + return object->selectOption(dAtoi(argv[2]), argv[3]); +} + +ConsoleMethod(GuiGameListOptionsCtrl, setOptions, void, 4, 4, + "(int rowIndex, string options)\n" + "Sets the list of options on the given row.\n\n" + "\\param rowIndex Index of the row to set options on." + "\\param optionsList A tab separated list of options for the control.") +{ + object->setOptions(dAtoi(argv[2]), argv[3]); +} + +//----------------------------------------------------------------------------- +// GuiGameListOptionsProfile +//----------------------------------------------------------------------------- + +GuiGameListOptionsProfile::GuiGameListOptionsProfile() +: mColumnSplit(0), + mRightPad(0) +{ +} + +void GuiGameListOptionsProfile::enforceConstraints() +{ + Parent::enforceConstraints(); + AssertFatal((mHitAreaUpperLeft.x <= mColumnSplit) && (mColumnSplit <= mHitAreaLowerRight.x), avar("GuiGameListOptionsProfile: You can't create %s with a ColumnSplit outside the hit area. You set the split to %d. Please change the ColumnSplit to be in the range [%d, %d].", getName(), mColumnSplit, mHitAreaUpperLeft.x, mHitAreaLowerRight.x)); + mColumnSplit = mClamp(mColumnSplit, mHitAreaUpperLeft.x, mHitAreaLowerRight.x); +} + +//----------------------------------------------------------------------------- +// Console stuff (GuiGameListOptionsProfile) +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiGameListOptionsProfile); + +void GuiGameListOptionsProfile::initPersistFields() +{ + addField("ColumnSplit", TypeS32, Offset(mColumnSplit, GuiGameListOptionsProfile)); + addField("RightPad", TypeS32, Offset(mRightPad, GuiGameListOptionsProfile)); + + Parent::initPersistFields(); +} diff --git a/gui/controls/guiGameListOptionsCtrl.h b/gui/controls/guiGameListOptionsCtrl.h new file mode 100644 index 0000000..38cfe39 --- /dev/null +++ b/gui/controls/guiGameListOptionsCtrl.h @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GuiGameListOptionsCtrl_H_ +#define _GuiGameListOptionsCtrl_H_ + +#include "gui/controls/guiGameListMenuCtrl.h" + +/// \class GuiGameListOptionsCtrl +/// A control for showing pages of options that are gamepad friendly. +class GuiGameListOptionsCtrl : public GuiGameListMenuCtrl +{ + typedef GuiGameListMenuCtrl Parent; + +protected: + /// \struct Row + /// An extension to the parent's row, adding the ability to keep a collection + /// of options and track status related to them. + struct Row : public Parent::Row + { + Vector mOptions; ///< Collection of options available to display + S32 mSelectedOption; ///< Index into mOptions pointing at the selected option + bool mWrapOptions; ///< Determines if options should "wrap around" at the ends + + Row() + { + VECTOR_SET_ASSOCIATION( mOptions ); + } + }; + +public: + /// Gets the text for the currently selected option of the given row. + /// + /// \param rowIndex Index of the row to get the option from. + /// \return A string representing the text currently displayed as the selected + /// option on the given row. If there is no such displayed text then the empty + /// string is returned. + StringTableEntry getCurrentOption(S32 rowIndex) const; + + /// Attempts to set the given row to the specified selected option. The option + /// will only be set if the option exists in the control. + /// + /// \param rowIndex Index of the row to set an option on. + /// \param option The option to be made active. + /// \return True if the row contained the option and was set, false otherwise. + bool selectOption(S32 rowIndex, StringTableEntry option); + + /// Sets the list of options on the given row. + /// + /// \param rowIndex Index of the row to set options on. + /// \param optionsList A tab separated list of options for the control. + void setOptions(S32 rowIndex, const char * optionsList); + + /// Adds a row to the control. + /// + /// \param label The text to display on the row as a label. + /// \param optionsList A tab separated list of options for the control. + /// \param wrapOptions Specify true to allow options to wrap at the ends or + /// false to prevent wrapping. + /// \param callback [optional] Name of a script function to use as a callback + /// when this row is activated. Default NULL means no callback. + /// \param icon [optional] Index of the icon to use as a marker. Default -1 + /// means no icon will be shown on this row. + /// \param yPad [optional] An extra amount of height padding before the row. + /// \param enabled [optional] If this row is initially enabled. Default true. + void addRow(const char* label, const char* optionsList, bool wrapOptions, const char* callback, S32 icon = -1, S32 yPad = 0, bool enabled = true); + + void onRender(Point2I offset, const RectI &updateRect); + + /// Callback when the mouse button is released. + /// + /// \param event A reference to the event that triggered the callback. + void onMouseUp(const GuiEvent &event); + + /// Callback when a key is pressed. + /// + /// \param event The event that triggered this callback. + bool onKeyDown(const GuiEvent &event); + + /// Callback when a key is repeating. + /// + /// \param event The event that triggered this callback. + bool onKeyRepeat(const GuiEvent &event){ return onKeyDown(event); } + + /// Callback when the gamepad axis is activated. + /// + /// \param event A reference to the event that triggered the callback. + virtual bool onGamepadAxisLeft(const GuiEvent &event); + + /// Callback when the gamepad axis is activated. + /// + /// \param event A reference to the event that triggered the callback. + virtual bool onGamepadAxisRight(const GuiEvent &event); + + GuiGameListOptionsCtrl(); + ~GuiGameListOptionsCtrl(); + + DECLARE_CONOBJECT(GuiGameListOptionsCtrl); + + /// Initializes fields accessible through the console. + static void initPersistFields(); + + static const S32 NO_OPTION = -1; ///< Indicates there is no option + +protected: + /// Checks to make sure our control has a profile of the correct type. + /// + /// \return True if the profile is of type GuiGameListOptionsProfile or false + /// if the profile is of any other type. + bool hasValidProfile() const; + + /// Enforces the validity of the fields on this control and its profile (if the + /// profile is valid, see: hasValidProfile). + void enforceConstraints(); + + /// Adds lines around the column divisions to the feedback already provided + /// in the Parent. + void onDebugRender(Point2I offset); + +private: + /// Performs a click on the current option row. The x position is used to + /// determine if the left or right arrow were clicked. If one was clicked, the + /// option will be changed. If neither was clicked, the option is unaffected. + /// This method should only be called when there is an actively selected row. + /// + /// \param row The row to perform the click on. + /// \param xPos The x position of the the click, relative to the control. + void clickOption(Row * row, S32 xPos); + + /// Changes the option on the currently selected row. If there is no row + /// selected, this method does nothing. + /// + /// \param delta The amount to change the option selection by. Typically this + /// will be 1 or -1. + void changeOption(S32 delta); + + /// Changes the option on the given row. + /// + /// \param row The row to change the option on. + /// \param delta The amount to change the option selection by. Typically this + /// will be 1 or -1. + void changeOption(Row * row, S32 delta); +}; + +/// \class GuiGameListOptionsProfile +/// A gui profile with additional fields specific to GuiGameListOptionsCtrl. +class GuiGameListOptionsProfile : public GuiGameListMenuProfile +{ + typedef GuiGameListMenuProfile Parent; + +public: + /// Enforces range constraints on all required fields. + void enforceConstraints(); + + GuiGameListOptionsProfile(); + + S32 mColumnSplit; ///< Absolute position of the split between columns + S32 mRightPad; ///< Extra padding between the right arrow and the hit area + + DECLARE_CONOBJECT(GuiGameListOptionsProfile); + + /// Initializes fields accessible through the console. + static void initPersistFields(); +}; + +#endif diff --git a/gui/controls/guiListBoxCtrl.cpp b/gui/controls/guiListBoxCtrl.cpp new file mode 100644 index 0000000..941fa8d --- /dev/null +++ b/gui/controls/guiListBoxCtrl.cpp @@ -0,0 +1,1260 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "gui/controls/guiListBoxCtrl.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CONOBJECT(GuiListBoxCtrl); + +GuiListBoxCtrl::GuiListBoxCtrl() +{ + mItems.clear(); + mSelectedItems.clear(); + mMultipleSelections = true; + mFitParentWidth = true; + mItemSize = Point2I(10,20); + mLastClickItem = NULL; + + mRenderTooltipDelegate.bind( this, &GuiListBoxCtrl::renderTooltip ); +} + +GuiListBoxCtrl::~GuiListBoxCtrl() +{ + clearItems(); +} + +void GuiListBoxCtrl::initPersistFields() +{ + addField( "AllowMultipleSelections", TypeBool, Offset( mMultipleSelections, GuiListBoxCtrl) ); + addField( "FitParentWidth", TypeBool, Offset( mFitParentWidth, GuiListBoxCtrl) ); + + addField( "mirrorSet", TypeRealString, Offset( mMirrorSetName, GuiListBoxCtrl ) ); + addField( "makeNameCallback", TypeRealString, Offset( mMakeNameCallback, GuiListBoxCtrl ) ); + + Parent::initPersistFields(); +} + +bool GuiListBoxCtrl::onWake() +{ + if( !Parent::onWake() ) + return false; + + updateSize(); + + return true; +} + +//----------------------------------------------------------------------------- +// Item Accessors +//----------------------------------------------------------------------------- +ConsoleMethod( GuiListBoxCtrl, setMultipleSelection, void, 3, 3, "listBox.setMultipleSelection([true/false])" ) +{ + object->setMultipleSelection( dAtob( argv[2] ) ); +} + + +ConsoleMethod( GuiListBoxCtrl, clearItems, void, 2, 2, "clearItems() - Clears all the items in the listbox" ) +{ + object->clearItems(); +} + +void GuiListBoxCtrl::clearItems() +{ + // Free item list allocated memory + while( mItems.size() ) + deleteItem( 0 ); + + // Free our vector lists + mItems.clear(); + mSelectedItems.clear(); + mFilteredItems.clear(); +} + + +ConsoleMethod( GuiListBoxCtrl, clearSelection, void, 2, 2, "clearSelection() - sets all currently selected items to unselected" ) +{ + object->clearSelection(); +} +void GuiListBoxCtrl::clearSelection() +{ + if( !mSelectedItems.size() ) + return; + + VectorPtr::iterator i = mSelectedItems.begin(); + for( ; i != mSelectedItems.end(); i++ ) + (*i)->isSelected = false; + + mSelectedItems.clear(); + + Con::executef( this, "onClearSelection" ); +} + + +ConsoleMethod( GuiListBoxCtrl, setSelected, void, 3, 4, "setSelected(index, [true]/false) - sets the item at the index specified to selected or not") +{ + bool value = true; + if( argc == 4 ) + value = dAtob( argv[3] ); + + if( value == true ) + object->addSelection( dAtoi( argv[2] ) ); + else + object->removeSelection( dAtoi( argv[2] ) ); +} + +void GuiListBoxCtrl::removeSelection( S32 index ) +{ + // Range Check + if( index >= mItems.size() || index < 0 ) + { + Con::warnf("GuiListBoxCtrl::removeSelection - index out of range!" ); + return; + } + + removeSelection( mItems[index], index ); +} +void GuiListBoxCtrl::removeSelection( LBItem *item, S32 index ) +{ + if( !mSelectedItems.size() ) + return; + + if( !item ) + return; + + for( S32 i = 0 ; i < mSelectedItems.size(); i++ ) + { + if( mSelectedItems[i] == item ) + { + mSelectedItems.erase( &mSelectedItems[i] ); + item->isSelected = false; + Con::executef(this, "onUnSelect", Con::getIntArg( index ), item->itemText); + return; + } + } +} + +void GuiListBoxCtrl::addSelection( S32 index ) +{ + // Range Check + if( index >= mItems.size() || index < 0 ) + { + Con::warnf("GuiListBoxCtrl::addSelection- index out of range!" ); + return; + } + + addSelection( mItems[index], index ); + +} +void GuiListBoxCtrl::addSelection( LBItem *item, S32 index ) +{ + if( !mMultipleSelections ) + { + if( !mSelectedItems.empty() ) + { + LBItem* selItem = mSelectedItems.front(); + if( selItem != item ) + clearSelection(); + else + return; + } + } + else + { + if( !mSelectedItems.empty() ) + { + for( S32 i = 0; i < mSelectedItems.size(); i++ ) + { + if( mSelectedItems[ i ] == item ) + return; + } + } + } + + item->isSelected = true; + mSelectedItems.push_front( item ); + + Con::executef(this, "onSelect", Con::getIntArg( index ), item->itemText); + +} + +S32 GuiListBoxCtrl::getItemIndex( LBItem *item ) +{ + if( mItems.empty() ) + return -1; + + // Lookup the index of an item in our list, by the pointer to the item + for( S32 i = 0; i < mItems.size(); i++ ) + if( mItems[i] == item ) + return i; + + return -1; +} + +ConsoleMethod( GuiListBoxCtrl, getItemCount, S32, 2, 2, "getItemCount() - returns the number of items in the list") +{ + return object->getItemCount(); +} + +S32 GuiListBoxCtrl::getItemCount() +{ + return mItems.size(); +} + +ConsoleMethod( GuiListBoxCtrl, getSelCount, S32, 2, 2, "getSelCount() - returns the number of items currently selected") +{ + return object->getSelCount(); +} +S32 GuiListBoxCtrl::getSelCount() +{ + return mSelectedItems.size(); +} + +ConsoleMethod( GuiListBoxCtrl, getSelectedItem, S32, 2, 2, "getSelectedItem() - returns the selected items index or -1 if none. " + "If multiple selections exist it returns the first selected item" ) +{ + return object->getSelectedItem(); +} +S32 GuiListBoxCtrl::getSelectedItem() +{ + if( mSelectedItems.empty() || mItems.empty() ) + return -1; + + for( S32 i = 0 ; i < mItems.size(); i++ ) + if( mItems[i]->isSelected ) + return i; + + return -1; +} + +ConsoleMethod( GuiListBoxCtrl, getSelectedItems, const char*, 2, 2, "getSelectedItems() - returns a space delimited list " + "of the selected items indexes in the list") +{ + S32 selCount = object->getSelCount(); + if( selCount == -1 || selCount == 0 ) + return StringTable->lookup("-1"); + else if( selCount == 1 ) + return Con::getIntArg(object->getSelectedItem()); + + Vector selItems; + object->getSelectedItems( selItems ); + + if( selItems.empty() ) + return StringTable->lookup("-1"); + + UTF8 *retBuffer = Con::getReturnBuffer( selItems.size() * 4 ); + dMemset( retBuffer, 0, selItems.size() * 4 ); + Vector::iterator i = selItems.begin(); + for( ; i != selItems.end(); i++ ) + { + UTF8 retFormat[12]; + dSprintf( retFormat, 12, "%d ", (*i) ); + dStrcat( retBuffer, retFormat ); + } + + return retBuffer; +} +void GuiListBoxCtrl::getSelectedItems( Vector &Items ) +{ + // Clear our return vector + Items.clear(); + + // If there are no selected items, return an empty vector + if( mSelectedItems.empty() ) + return; + + for( S32 i = 0; i < mItems.size(); i++ ) + if( mItems[i]->isSelected ) + Items.push_back( i ); +} + +ConsoleMethod(GuiListBoxCtrl, findItemText, S32, 3, 4, "listBox.findItemText( myItemText, [?caseSensitive - false] ) - Returns index of item with matching text or -1 if none") +{ + bool bCaseSensitive = false; + + if( argc == 4 ) + bCaseSensitive = dAtob( argv[3] ); + + return object->findItemText( argv[2], bCaseSensitive ); +} + +S32 GuiListBoxCtrl::findItemText( StringTableEntry text, bool caseSensitive ) +{ + // Check Proper Arguments + if( !text || !text[0] || text == StringTable->lookup("") ) + { + Con::warnf("GuiListBoxCtrl::findItemText - No Text Specified!"); + return -1; + } + + // Check Items Exist. + if( mItems.empty() ) + return -1; + + // Lookup the index of an item in our list, by the pointer to the item + for( S32 i = 0; i < mItems.size(); i++ ) + { + // Case Sensitive Compare? + if( caseSensitive && ( dStrcmp( mItems[i]->itemText, text ) == 0 ) ) + return i; + else if (!caseSensitive && ( dStricmp( mItems[i]->itemText, text ) == 0 )) + return i; + } + + // Not Found! + return -1; +} + +ConsoleMethod(GuiListBoxCtrl, setCurSel, void, 3, 3, "setCurSel(index) - sets the currently selected item at the specified index") +{ + object->setCurSel( dAtoi( argv[2] ) ); +} +void GuiListBoxCtrl::setCurSel( S32 index ) +{ + // Range Check + if( index >= mItems.size() ) + { + Con::warnf("GuiListBoxCtrl::setCurSel - index out of range!" ); + return; + } + + // If index -1 is specified, we clear the selection + if( index == -1 ) + { + mSelectedItems.clear(); + return; + } + + // Add the selection + addSelection( mItems[ index ], index ); + +} + +ConsoleMethod( GuiListBoxCtrl, setCurSelRange, void, 3, 4, "setCurSelRange(start,[stop]) - sets the current selection range from" + " index start to stop. if no stop is specified it sets from start index to the end of the list") +{ + if( argc == 4 ) + object->setCurSelRange( dAtoi(argv[2]) , dAtoi( argv[3] ) ); + else + object->setCurSelRange( dAtoi(argv[2]), 999999 ); +} +void GuiListBoxCtrl::setCurSelRange( S32 start, S32 stop ) +{ + // Verify Selection Range + if( start < 0 ) + start = 0; + else if( start > mItems.size() ) + start = mItems.size(); + + if( stop < 0 ) + stop = 0; + else if( stop > mItems.size() ) + stop = mItems.size(); + + S32 iterStart = ( start < stop ) ? start : stop; + S32 iterStop = ( start < stop ) ? stop : start; + + for( ; iterStart <= iterStop; iterStart++ ) + addSelection( mItems[iterStart], iterStart ); +} + + +ConsoleMethod( GuiListBoxCtrl, addItem, S32, 3, 4, "addItem(text, color) - adds an item to the end of the list with an optional color" ) +{ + if(argc == 3) + { + return object->addItem( argv[2] ); + } + else if(argc == 4) + { + U32 elementCount = GuiListBoxCtrl::getStringElementCount(argv[3]); + + if(elementCount == 3) + { + F32 red, green, blue; + + red = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 0 )); + green = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 1 )); + blue = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 2 )); + + return object->addItemWithColor( argv[2], ColorF(red, green, blue) ); + } + else if(elementCount == 1) + { + return object->addItem( argv[2], Sim::findObject(argv[3]) ); + } + else + { + Con::warnf("GuiListBoxCtrl::addItem() - Invalid number of parameters for the color!"); + return -1; + } + } + else + { + Con::warnf("GuiListBoxCtrl::addItem() - Invalid number of parameters!"); + return -1; + } +} +S32 GuiListBoxCtrl::addItem( StringTableEntry text, void *itemData ) +{ + // This just calls insert item at the end of the list + return insertItem( mItems.size(), text, itemData ); +} + +S32 GuiListBoxCtrl::addItemWithColor( StringTableEntry text, ColorF color, void *itemData ) +{ + // This just calls insert item at the end of the list + return insertItemWithColor( mItems.size(), text, color, itemData ); +} + +ConsoleMethod(GuiListBoxCtrl, setItemColor, void, 4, 4, "(index, color)") +{ + U32 elementCount = GuiListBoxCtrl::getStringElementCount(argv[3]); + + if(elementCount == 3) + { + F32 red = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 0 )); + F32 green = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 1 )); + F32 blue = dAtof(GuiListBoxCtrl::getStringElement( argv[3], 2 )); + + object->setItemColor( dAtoi(argv[2]), ColorF(red, green, blue) ); + } + else + Con::warnf("GuiListBoxCtrl::addItem() - Invalid number of parameters for the color!"); +} + +void GuiListBoxCtrl::setItemColor( S32 index, ColorF color ) +{ + if ((index >= mItems.size()) || index < 0) + { + Con::warnf("GuiListBoxCtrl::setItemColor - invalid index"); + return; + } + + LBItem* item = mItems[index]; + item->hasColor = true; + item->color = color; +} + +ConsoleMethod(GuiListBoxCtrl, clearItemColor, void, 3, 3, "(index)") +{ + object->clearItemColor(dAtoi(argv[2])); +} + +void GuiListBoxCtrl::clearItemColor( S32 index ) +{ + if ((index >= mItems.size()) || index < 0) + { + Con::warnf("GuiListBoxCtrl::setItemColor - invalid index"); + return; + } + + LBItem* item = mItems[index]; + item->hasColor = false; +} + +ConsoleMethod( GuiListBoxCtrl, insertItem, void, 4, 4, "insertItem( text, index ) - inserts an item into the list at the specified index and returns the index assigned or -1 on error") +{ + object->insertItem( dAtoi( argv[3] ), argv[2] ); +} + +S32 GuiListBoxCtrl::insertItem( S32 index, StringTableEntry text, void *itemData ) +{ + // If the index is greater than our list size, insert it at the end + if( index >= mItems.size() ) + index = mItems.size(); + + // Sanity checking + if( !text ) + { + Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL string" ); + return -1; + } + + LBItem *newItem = new LBItem; + if( !newItem ) + { + Con::warnf("GuiListBoxCtrl::insertItem - error allocating item memory!" ); + return -1; + } + + // Assign item data + newItem->itemText = StringTable->insert(text, true); + newItem->itemData = itemData; + newItem->isSelected = false; + newItem->hasColor = false; + + // Add to list + mItems.insert(index); + mItems[index] = newItem; + + // Resize our list to fit our items + updateSize(); + + // Return our index in list (last) + return index; + +} + +S32 GuiListBoxCtrl::insertItemWithColor( S32 index, StringTableEntry text, ColorF color, void *itemData ) +{ + // If the index is greater than our list size, insert it at the end + if( index >= mItems.size() ) + index = mItems.size(); + + // Sanity checking + if( !text ) + { + Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL string" ); + return -1; + } + + if( color == ColorF(-1, -1, -1) ) + { + Con::warnf("GuiListBoxCtrl::insertItem - cannot add NULL color" ); + return -1; + } + + LBItem *newItem = new LBItem; + if( !newItem ) + { + Con::warnf("GuiListBoxCtrl::insertItem - error allocating item memory!" ); + return -1; + } + + // Assign item data + newItem->itemText = StringTable->insert(text, true); + newItem->itemData = itemData; + newItem->isSelected = false; + newItem->hasColor = true; + newItem->color = color; + + // Add to list + mItems.insert(index); + mItems[index] = newItem; + + // Resize our list to fit our items + updateSize(); + + // Return our index in list (last) + return index; + +} + +ConsoleMethod ( GuiListBoxCtrl, deleteItem, void, 3, 3, "deleteItem(itemIndex)" ) +{ + object->deleteItem( dAtoi( argv[2] ) ); +} + +void GuiListBoxCtrl::deleteItem( S32 index ) +{ + // Range Check + if( index >= mItems.size() || index < 0 ) + { + Con::warnf("GuiListBoxCtrl::deleteItem - index out of range!" ); + return; + } + + // Grab our item + LBItem* item = mItems[ index ]; + if( !item ) + { + Con::warnf("GuiListBoxCtrl::deleteItem - Bad Item Data!" ); + return; + } + + // Remove it from the selected list. + if( item->isSelected ) + { + for( VectorPtr::iterator i = mSelectedItems.begin(); i != mSelectedItems.end(); i++ ) + { + if( item == *i ) + { + mSelectedItems.erase_fast( i ); + break; + } + } + } + + // Remove it from the list + mItems.erase( &mItems[ index ] ); + + // Free the memory associated with it + delete item; +} + + +ConsoleMethod( GuiListBoxCtrl, getItemText, const char*, 3, 3, "getItemText(index) - returns the text of the item at the specified index") +{ + return object->getItemText( dAtoi( argv[2] ) ); +} + +StringTableEntry GuiListBoxCtrl::getItemText( S32 index ) +{ + // Range Checking + if( index > mItems.size() || index < 0 ) + { + Con::warnf( "GuiListBoxCtrl::getItemText - index out of range!" ); + return StringTable->lookup(""); + } + + return mItems[ index ]->itemText; +} + +ConsoleMethod( GuiListBoxCtrl, getItemObject, const char*, 3, 3, "getItemObject(index) - returns the object associated with an item. This only makes sense if you are mirroring a simset.") +{ + SimObject *outObj = object->getItemObject( dAtoi(argv[2]) ); + if ( !outObj ) + return NULL; + + return outObj->getIdString(); +} + +SimObject* GuiListBoxCtrl::getItemObject( S32 index ) +{ + // Range Checking + if( index > mItems.size() || index < 0 ) + { + Con::warnf( "GuiListBoxCtrl::getItemObject - index out of range!" ); + return NULL; + } + + return (SimObject*)mItems[ index ]->itemData; +} + + + +ConsoleMethod( GuiListBoxCtrl, setItemText, void, 4, 4, "setItemText(index, newtext) - sets the items text at the specified index" ) +{ + object->setItemText( dAtoi( argv[2] ), argv[3] ); +} +void GuiListBoxCtrl::setItemText( S32 index, StringTableEntry text ) +{ + // Sanity Checking + if( !text ) + { + Con::warnf("GuiListBoxCtrl::setItemText - Invalid Text Specified!" ); + return; + } + // Range Checking + if( index > mItems.size() || index < 0 ) + { + Con::warnf( "GuiListBoxCtrl::getItemText - index out of range!" ); + return; + } + + mItems[ index ]->itemText = StringTable->insert( text, true ); +} + +ConsoleMethod( GuiListBoxCtrl, setItemTooltip, void, 4, 4, "( int index, string text ) - Set the tooltip text to display for the given list item." ) +{ + S32 index = dAtoi( argv[ 2 ] ); + if( index > object->mItems.size() || index < 0 ) + { + Con::errorf( "GuiListBoxCtrl::setItemTooltip - index '%i' out of range", index ); + return; + } + + object->mItems[ index ]->itemTooltip = argv[ 3 ]; +} + +ConsoleMethod( GuiListBoxCtrl, getLastClickItem, S32, 2, 2, "returns the item index that was last clicked, -1 if none" ) +{ + GuiListBoxCtrl::LBItem *lastItem = object->mLastClickItem; + if ( !lastItem ) + return -1; + + return object->getItemIndex( lastItem ); +} + +//----------------------------------------------------------------------------- +// Sizing Functions +//----------------------------------------------------------------------------- +void GuiListBoxCtrl::updateSize() +{ + if( !mProfile || !mProfile->mFont ) + return; + + GFont *font = mProfile->mFont; + GuiScrollCtrl* parent = dynamic_cast(getParent()); + + if ( mFitParentWidth && parent ) + mItemSize.x = parent->getContentExtent().x; + else + { + // Find the maximum width cell: + S32 maxWidth = 1; + for ( U32 i = 0; i < mItems.size(); i++ ) + { + S32 width = font->getStrWidth( mItems[i]->itemText ); + if( width > maxWidth ) + maxWidth = width; + } + mItemSize.x = maxWidth + 6; + } + + mItemSize.y = font->getHeight() + 2; + + Point2I newExtent( mItemSize.x, mItemSize.y * mItems.size() ); + setExtent( newExtent ); + +} + +void GuiListBoxCtrl::parentResized(const RectI &oldParentRect, const RectI &newParentRect) +{ + Parent::parentResized( oldParentRect, newParentRect ); + + updateSize(); +} + +//----------------------------------------------------------------------------- +// Overrides +//----------------------------------------------------------------------------- +void GuiListBoxCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + RectI clipRect(updateRect.point, updateRect.extent); + + if( !mProfile ) + return; + + _mirror(); + + // Save our original clip rect + RectI oldClipRect = clipRect; + + for ( S32 i = 0; i < mItems.size(); i++) + { + S32 colorBoxSize = 0; + ColorI boxColor = ColorI(0, 0, 0); + // Only render visible items + if ((i + 1) * mItemSize.y + offset.y < updateRect.point.y) + continue; + + // Break our once we're no longer in visible item range + if( i * mItemSize.y + offset.y >= updateRect.point.y + updateRect.extent.y) + break; + + // Render color box if needed + if(mItems[i]->hasColor) + { + // Set the size of the color box to be drawn next to the item text + colorBoxSize = 3; + boxColor = ColorI(mItems[i]->color); + // Draw the box first + ColorI black = ColorI(0, 0, 0); + drawBox( Point2I(offset.x + mProfile->mTextOffset.x + colorBoxSize, offset.y + ( i * mItemSize.y ) + 8), colorBoxSize, black, boxColor ); + } + + RectI itemRect = RectI( offset.x + mProfile->mTextOffset.x + (colorBoxSize * 3), offset.y + ( i * mItemSize.y ), mItemSize.x, mItemSize.y ); + + // Render our item + onRenderItem( itemRect, mItems[i] ); + } + + GFX->setClipRect( oldClipRect ); +} + +void GuiListBoxCtrl::onRenderItem( RectI itemRect, LBItem *item ) +{ + if( item->isSelected ) + GFX->getDrawUtil()->drawRectFill( itemRect, mProfile->mFillColor ); + + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor); + renderJustifiedText(itemRect.point + Point2I( 2, 0 ), itemRect.extent, item->itemText); +} + +void GuiListBoxCtrl::drawBox(const Point2I &box, S32 size, ColorI &outlineColor, ColorI &boxColor) +{ + RectI r(box.x - size, box.y - size, 2 * size + 1, 2 * size + 1); + r.inset(1, 1); + GFX->getDrawUtil()->drawRectFill(r, boxColor); + r.inset(-1, -1); + GFX->getDrawUtil()->drawRect(r, outlineColor); +} + +bool GuiListBoxCtrl::renderTooltip( const Point2I &hoverPos, const Point2I& cursorPos, const char* tipText ) +{ + S32 hitItemIndex; + if( hitTest( hoverPos, hitItemIndex ) ) + tipText = mItems[ hitItemIndex ]->itemTooltip; + + return defaultTooltipRender( hoverPos, cursorPos, tipText ); +} + +//----------------------------------------------------------------------------- +// Hit Detection +//----------------------------------------------------------------------------- + +bool GuiListBoxCtrl::hitTest( const Point2I& point, S32& outItem ) +{ + Point2I localPoint = globalToLocalCoord( point ); + + S32 itemHit = ( localPoint.y < 0 ) ? -1 : (S32)mFloor( (F32)localPoint.y / (F32)mItemSize.y ); + if ( itemHit >= mItems.size() || itemHit == -1 ) + return false; + + LBItem *hitItem = mItems[ itemHit ]; + if ( hitItem == NULL ) + return false; + + outItem = itemHit; + return true; +} + +//----------------------------------------------------------------------------- +// Mouse Events +//----------------------------------------------------------------------------- + +void GuiListBoxCtrl::onMouseDragged(const GuiEvent &event) +{ + Parent::onMouseDragged(event); + + if(isMethod("onMouseDragged")) + Con::executef(this, "onMouseDragged"); +} + +void GuiListBoxCtrl::onMouseDown( const GuiEvent &event ) +{ + S32 itemHit; + if( !hitTest( event.mousePoint, itemHit ) ) + return; + + LBItem* hitItem = mItems[ itemHit ]; + + // If we're not a multiple selection listbox, we simply select/unselect an item + if( !mMultipleSelections ) + { + // No current selection? Just select the cell and move on + S32 selItem = getSelectedItem(); + + if ( selItem != itemHit && selItem != -1 ) + clearSelection(); + + // Set the current selection + setCurSel( itemHit ); + + if( itemHit == selItem && event.mouseClickCount == 2 && isMethod("onDoubleClick") ) + Con::executef( this, "onDoubleClick" ); + + // Store the clicked item + mLastClickItem = hitItem; + + // Evaluate the console command if we clicked the same item twice + if( selItem == itemHit && event.mouseClickCount > 1 && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand, false ); + + return; + + } + + // Deal with multiple selections + if( event.modifier & SI_MULTISELECT) + { + // Ctrl-Click toggles selection + if( hitItem->isSelected ) + { + removeSelection( hitItem, itemHit ); + + // We return here when we deselect an item because we don't store last clicked when we deselect + return; + } + else + addSelection( hitItem, itemHit ); + } + else if( event.modifier & SI_RANGESELECT ) + { + if( !mLastClickItem ) + addSelection( hitItem, itemHit ); + else + setCurSelRange( getItemIndex( mLastClickItem ), itemHit ); + } + else + { + if( getSelCount() != 0 ) + { + S32 selItem = getSelectedItem(); + if( selItem != -1 && mItems[selItem] != hitItem ) + clearSelection(); + } + addSelection( hitItem, itemHit ); + } + + if( hitItem == mLastClickItem && event.mouseClickCount == 2 && isMethod("onDoubleClick") ) + Con::executef( this, "onDoubleClick" ); + + mLastClickItem = hitItem; +} + +void GuiListBoxCtrl::onMouseUp( const GuiEvent& event ) +{ + if( isMethod( "onMouseUp" ) ) + { + S32 itemHit = -1; + if( hitTest( event.mousePoint, itemHit ) ) + Con::executef( this, "onMouseUp", Con::getIntArg( itemHit ) ); + } + + Parent::onMouseUp( event ); +} + +bool GuiListBoxCtrl::onKeyDown( const GuiEvent &event ) +{ + if ( event.keyCode == KEY_DELETE ) + { + if ( isMethod( "onDeleteKey" ) ) + { + Con::executef( this, "onDeleteKey" ); + return true; + } + } + + return Parent::onKeyDown( event ); +} + +U32 GuiListBoxCtrl::getStringElementCount( const char* inString ) +{ + // Non-whitespace chars. + static const char* set = " \t\n"; + + // End of string. + if ( *inString == 0 ) + return 0; + + U32 wordCount = 0; + U8 search = 0; + + // Search String. + while( *inString ) + { + // Get string element. + search = *inString; + + // End of string? + if ( search == 0 ) + break; + + // Move to next element. + inString++; + + // Search for seperators. + for( U32 i = 0; set[i]; i++ ) + { + // Found one? + if( search == set[i] ) + { + // Yes... + search = 0; + break; + } + } + + // Found a seperator? + if ( search == 0 ) + continue; + + // We've found a non-seperator. + wordCount++; + + // Search for end of non-seperator. + while( 1 ) + { + // Get string element. + search = *inString; + + // End of string? + if ( search == 0 ) + break; + + // Move to next element. + inString++; + + // Search for seperators. + for( U32 i = 0; set[i]; i++ ) + { + // Found one? + if( search == set[i] ) + { + // Yes... + search = 0; + break; + } + } + + // Found Seperator? + if ( search == 0 ) + break; + } + + // End of string? + if ( *inString == 0 ) + { + // Bah! + break; + } + } + + // We've finished. + return wordCount; +} + +//------------------------------------------------------------------------------ +// Get String Element. +//------------------------------------------------------------------------------ +const char* GuiListBoxCtrl::getStringElement( const char* inString, const U32 index ) +{ + // Non-whitespace chars. + static const char* set = " \t\n"; + + U32 wordCount = 0; + U8 search = 0; + const char* pWordStart = NULL; + + // End of string? + if ( *inString != 0 ) + { + // No, so search string. + while( *inString ) + { + // Get string element. + search = *inString; + + // End of string? + if ( search == 0 ) + break; + + // Move to next element. + inString++; + + // Search for seperators. + for( U32 i = 0; set[i]; i++ ) + { + // Found one? + if( search == set[i] ) + { + // Yes... + search = 0; + break; + } + } + + // Found a seperator? + if ( search == 0 ) + continue; + + // Found are word? + if ( wordCount == index ) + { + // Yes, so mark it. + pWordStart = inString-1; + } + + // We've found a non-seperator. + wordCount++; + + // Search for end of non-seperator. + while( 1 ) + { + // Get string element. + search = *inString; + + // End of string? + if ( search == 0 ) + break; + + // Move to next element. + inString++; + + // Search for seperators. + for( U32 i = 0; set[i]; i++ ) + { + // Found one? + if( search == set[i] ) + { + // Yes... + search = 0; + break; + } + } + + // Found Seperator? + if ( search == 0 ) + break; + } + + // Have we found our word? + if ( pWordStart ) + { + // Yes, so we've got our word... + + // Result Buffer. + static char buffer[4096]; + + // Calculate word length. + const U32 length = inString - pWordStart - ((*inString)?1:0); + + // Copy Word. + dStrncpy( buffer, pWordStart, length); + buffer[length] = '\0'; + + // Return Word. + return buffer; + } + + // End of string? + if ( *inString == 0 ) + { + // Bah! + break; + } + } + } + + // Sanity! + AssertFatal( false, "t2dSceneObject::getStringElement() - Couldn't find specified string element!" ); + // Didn't find it + return " "; +} + +void GuiListBoxCtrl::_mirror() +{ + SimSet *mirrorSet; + if ( !Sim::findObject( mMirrorSetName, mirrorSet ) ) + return; + + for ( U32 i = 0; i < mItems.size(); i++ ) + { + SimObject *pObj = (SimObject*)mItems[i]->itemData; + + bool keep = false; + + if ( pObj ) + { + SimSet::iterator iter = mirrorSet->find( mirrorSet->begin(), mirrorSet->end(), pObj ); + + if ( iter != mirrorSet->end() ) + { + mItems[i]->itemText = _makeMirrorItemName( *iter); + keep = true; + } + } + + if ( !keep ) + { + deleteItem( i ); + i--; + } + } + + SimSetIterator iter( mirrorSet ); + + for ( U32 i = 0; i < mirrorSet->size(); i++ ) + { + SimObject *pObj = mirrorSet->at(i); + + bool found = false; + + for ( U32 j = 0; j < mItems.size(); j++ ) + { + if ( mItems[j]->itemData == pObj ) + { + found = true; + break; + } + } + + for ( U32 j = 0; j < mFilteredItems.size(); j++ ) + { + if ( mFilteredItems[j]->itemData == pObj ) + { + found = true; + break; + } + } + + if ( !found ) + { + addItem( _makeMirrorItemName( pObj ), pObj ); + } + } +} + +StringTableEntry GuiListBoxCtrl::_makeMirrorItemName( SimObject *inObj ) +{ + if ( mMakeNameCallback.isNotEmpty() ) + { + Con::setVariable( "$ThisObject", inObj->getIdString() ); + StringTableEntry outName = StringTable->insert( Con::evaluate( mMakeNameCallback ), true ); + if ( outName && outName[0] ) + return outName; + + return StringTable->insert( "(no name)" ); + } + + if ( inObj->getName() ) + return StringTable->insert( inObj->getName() ); + + return StringTable->insert( "(no name)" ); +} + +ConsoleMethod( GuiListBoxCtrl, doMirror, void, 2, 2, "" ) +{ + object->_mirror(); +} + +ConsoleMethod( GuiListBoxCtrl, addFilteredItem, void, 3, 3, "" ) +{ + String item(argv[2]); + if( item == String::EmptyString ) + return; + + object->addFilteredItem( item ); +} + +void GuiListBoxCtrl::addFilteredItem( String item ) +{ + // Delete from selected items list + for ( S32 i = 0; i < mSelectedItems.size(); i++ ) + { + String itemText = mSelectedItems[i]->itemText; + if ( dStrcmp( itemText.c_str(), item.c_str() ) == 0 ) + { + mSelectedItems.erase_fast( i ); + break; + } + } + + for ( S32 i = 0; i < mItems.size(); i++ ) + { + String itemText = mItems[i]->itemText; + if( dStrcmp( itemText.c_str(), item.c_str() ) == 0 ) + { + mItems[i]->isSelected = false; + mFilteredItems.push_front( mItems[i] ); + mItems.erase( &mItems[i] ); + break; + } + } +} + +ConsoleMethod( GuiListBoxCtrl, removeFilteredItem, void, 3, 3, "" ) +{ + String item(argv[2]); + if( item == String::EmptyString ) + return; + + object->removeFilteredItem( item ); +} + +void GuiListBoxCtrl::removeFilteredItem( String item ) +{ + for ( S32 i = 0; i < mFilteredItems.size(); i++ ) + { + String itemText = mFilteredItems[i]->itemText; + if( dStrcmp( itemText.c_str(), item.c_str() ) == 0 ) + { + mItems.push_front( mFilteredItems[i] ); + mFilteredItems.erase( &mFilteredItems[i] ); + break; + } + } +} \ No newline at end of file diff --git a/gui/controls/guiListBoxCtrl.h b/gui/controls/guiListBoxCtrl.h new file mode 100644 index 0000000..0670d83 --- /dev/null +++ b/gui/controls/guiListBoxCtrl.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUI_LISTBOXCTRL_H_ +#define _GUI_LISTBOXCTRL_H_ + +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#ifndef _DGL_H_ +#include "gfx/gfxDevice.h" +#endif + +#ifndef _H_GUIDEFAULTCONTROLRENDER_ +#include "gui/core/guiDefaultControlRender.h" +#endif + +#ifndef _GUISCROLLCTRL_H_ +#include "gui/containers/guiScrollCtrl.h" +#endif + + +class GuiListBoxCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; +public: + + GuiListBoxCtrl(); + ~GuiListBoxCtrl(); + DECLARE_CONOBJECT(GuiListBoxCtrl); + DECLARE_CATEGORY( "Gui Lists" ); + DECLARE_DESCRIPTION( "Linear list of text items." ); + + struct LBItem + { + StringTableEntry itemText; + String itemTooltip; + bool isSelected; + void* itemData; + ColorF color; + bool hasColor; + }; + + VectorPtr mItems; + VectorPtr mSelectedItems; + + VectorPtr mFilteredItems; + + bool mMultipleSelections; + Point2I mItemSize; + bool mFitParentWidth; + LBItem* mLastClickItem; + + // Persistence + static void initPersistFields(); + + // Item Accessors + S32 getItemCount(); + S32 getSelCount(); + S32 getSelectedItem(); + void getSelectedItems( Vector &Items ); + S32 getItemIndex( LBItem *item ); + StringTableEntry getItemText( S32 index ); + SimObject* getItemObject( S32 index ); + + void setCurSel( S32 index ); + void setCurSelRange( S32 start, S32 stop ); + void setItemText( S32 index, StringTableEntry text ); + + S32 addItem( StringTableEntry text, void *itemData = NULL ); + S32 addItemWithColor( StringTableEntry text, ColorF color = ColorF(-1, -1, -1), void *itemData = NULL); + S32 insertItem( S32 index, StringTableEntry text, void *itemData = NULL ); + S32 insertItemWithColor( S32 index, StringTableEntry text, ColorF color = ColorF(-1, -1, -1), void *itemData = NULL); + S32 findItemText( StringTableEntry text, bool caseSensitive = false ); + + void setItemColor(S32 index, ColorF color); + void clearItemColor(S32 index); + + void deleteItem( S32 index ); + void clearItems(); + void clearSelection(); + void removeSelection( LBItem *item, S32 index ); + void removeSelection( S32 index ); + void addSelection( LBItem *item, S32 index ); + void addSelection( S32 index ); + inline void setMultipleSelection( bool allowMultipleSelect = true ) { mMultipleSelections = allowMultipleSelect; }; + + bool hitTest( const Point2I& point, S32& outItem ); + + // Sizing + void updateSize(); + virtual void parentResized(const RectI& oldParentRect, const RectI& newParentRect); + virtual bool onWake(); + + // Rendering + virtual void onRender( Point2I offset, const RectI &updateRect ); + virtual void onRenderItem( RectI itemRect, LBItem *item ); + void drawBox( const Point2I &box, S32 size, ColorI &outlineColor, ColorI &boxColor ); + bool renderTooltip( const Point2I &hoverPos, const Point2I& cursorPos, const char* tipText ); + void addFilteredItem( String item ); + void removeFilteredItem( String item ); + // Mouse/Key Events + virtual void onMouseDown( const GuiEvent &event ); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseUp( const GuiEvent& event ); + virtual bool onKeyDown( const GuiEvent &event ); + + // String Utility + static U32 getStringElementCount( const char *string ); + static const char* getStringElement( const char* inString, const U32 index ); + + // SimSet Mirroring Stuff + void setMirrorObject( SimSet *inObj ); + void _mirror(); + StringTableEntry _makeMirrorItemName( SimObject *inObj ); + + String mMirrorSetName; + String mMakeNameCallback; +}; + +#endif \ No newline at end of file diff --git a/gui/controls/guiMLTextCtrl.cpp b/gui/controls/guiMLTextCtrl.cpp new file mode 100644 index 0000000..c177237 --- /dev/null +++ b/gui/controls/guiMLTextCtrl.cpp @@ -0,0 +1,2204 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/controls/guiMLTextCtrl.h" +#include "gui/containers/guiScrollCtrl.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "core/frameAllocator.h" +#include "core/strings/unicode.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" + + +IMPLEMENT_CONOBJECT( GuiMLTextCtrl ); + +GFX_ImplementTextureProfile(GFXMLTextureProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::Static, + GFXTextureProfile::None); + +const U32 GuiMLTextCtrl::csmTextBufferGrowthSize = 1024; + +ConsoleMethod( GuiMLTextCtrl, setText, void, 3, 3, "(string text)" + "Set the text contained in the control.") +{ + object->setText(argv[2], dStrlen(argv[2])); +} + +ConsoleMethod( GuiMLTextCtrl, getText, const char*, 2, 2, "Returns the text from the control, including ML.") +{ + return( object->getTextContent() ); +} + +ConsoleMethod( GuiMLTextCtrl, addText, void, 4, 4, "(string text, bool reformat)") +{ + object->addText(argv[2], dStrlen(argv[2]), dAtob(argv[3])); +} + +ConsoleMethod( GuiMLTextCtrl, setCursorPosition, bool, 3, 3, "(int newPos)" + "Offset in characters to set cursor's position to.") +{ + return object->setCursorPosition(dAtoi(argv[2])); +} + +ConsoleMethod( GuiMLTextCtrl, scrollToTag, void, 3, 3, "(int tagID)" + "Scroll down to a specified tag.") +{ + object->scrollToTag( dAtoi( argv[2] ) ); +} + +ConsoleMethod( GuiMLTextCtrl, scrollToTop, void, 2, 2, "Scroll to the top of the text.") +{ + object->scrollToTop(); +} + +ConsoleMethod( GuiMLTextCtrl, scrollToBottom, void, 2, 2, "Scroll to the top of the text.") +{ + object->scrollToBottom(); +} + +ConsoleFunction( StripMLControlChars, const char*, 2, 2, "(string val)" + "Strip TorqueML control characters from the specified string, returning a 'clean' version.") +{ + return GuiMLTextCtrl::stripControlChars(argv[1]); +} + +ConsoleMethod(GuiMLTextCtrl,forceReflow,void,2,2,"forces the text control to reflow the text after new text is added, possibly resizing the control.") +{ + if(!object->isAwake()) + Con::errorf("GuiMLTextCtrl::forceReflow can only be called on visible controls."); + else + object->reflow(); +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::GuiMLTextCtrl() +: mTabStops( NULL ), + mTabStopCount( 0 ), + mCurTabStop( 0 ), + mCurStyle( NULL ), + mCurLMargin( 0 ), + mCurRMargin( 100 ), + mCurJustify( LeftJustify ), + mCurDiv( 0 ), + mCurY( 0 ), + mCurClipX( 0 ), + mLineAtoms( NULL ), + mLineAtomPtr( &mLineAtoms ), + mLineList( NULL ), + mLineInsert( &mLineList ), + mScanPos( 0 ), + mCurX( 0 ), + mMaxY( 0 ), + mCurURL( NULL ), + mLineStart( 0 ), + mVertMoveAnchor( 0 ), + mVertMoveAnchorValid( false ), + mSelectionAnchor( 0 ), + mIsEditCtrl( false ), + mCursorPosition( false ), + mMaxBufferSize( -1 ), + mInitialText( StringTable->insert("") ), + mSelectionActive( false ), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mLineSpacingPixels( 2 ), + mAllowColorChars( false ), + mUseURLMouseCursor( false ), + mBitmapList( 0 ), + mBitmapRefList( 0 ), + mDirty( true ), + mTagList( NULL ), + mHitURL( 0 ), + mAlpha( 1.0f ), + mFontList( NULL ) +{ + mActive = true; + //mInitialText = StringTable->insert(""); + Sim::findObject("InputDeniedSound", mDeniedSound); +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::~GuiMLTextCtrl() +{ + mCursorPosition = 0; + + mSelectionActive = false; + mSelectionStart = 0; + mSelectionEnd = 0; + freeResources(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::initPersistFields() +{ + addField("lineSpacing", TypeS32, Offset(mLineSpacingPixels, GuiMLTextCtrl)); + addField("allowColorChars", TypeBool, Offset(mAllowColorChars, GuiMLTextCtrl)); + addField("maxChars", TypeS32, Offset(mMaxBufferSize, GuiMLTextCtrl)); + addField("deniedSound", TypeSFXProfilePtr, Offset(mDeniedSound, GuiMLTextCtrl)); + addField("text", TypeCaseString, Offset( mInitialText, GuiMLTextCtrl ) ); + addField("useURLMouseCursor", TypeBool, Offset(mUseURLMouseCursor, GuiMLTextCtrl)); + Parent::initPersistFields(); +} + +ConsoleMethod(GuiMLTextCtrl, setAlpha, void, 3, 3, "") +{ + object->setAlpha(dAtof(argv[2])); +} + +//-------------------------------------------------------------------------- + +bool GuiMLTextCtrl::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (!mTextBuffer.length() && mInitialText[0] != 0) + setText(mInitialText, dStrlen(mInitialText)+1); + return true; +} + +//-------------------------------------------------------------------------- +bool GuiMLTextCtrl::onWake() +{ + if (Parent::onWake() == false) + return false; + + mDirty = true; + return true; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onPreRender() +{ + if(mDirty) + reflow(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::drawAtomText(bool sel, U32 start, U32 end, Atom *atom, Line *line, Point2I offset) +{ + GFont *font = atom->style->font->fontRes; + U32 xOff = 0; + if(start != atom->textStart) + { + const UTF16* buff = mTextBuffer.getPtr() + atom->textStart; + xOff += font->getStrNWidth(buff, start - atom->textStart); + } + + Point2I drawPoint(offset.x + atom->xStart + xOff, offset.y + atom->yStart); + + ColorI color; + if(atom->url) + { + if(atom->url->mouseDown) + color = atom->style->linkColorHL; + else + color = atom->style->linkColor; + } + else + color = atom->style->color; + + const UTF16* tmp = mTextBuffer.getPtr() + start; + U32 tmpLen = end-start; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + if(!sel) + { + if(atom->style->shadowOffset.x || atom->style->shadowOffset.y) + { + ColorI shadowColor = atom->style->shadowColor; + shadowColor.alpha = (S32)(mAlpha * shadowColor.alpha); + drawer->setBitmapModulation(shadowColor); + drawer->drawTextN(font, drawPoint + atom->style->shadowOffset, + tmp, tmpLen, mAllowColorChars ? mProfile->mFontColors : NULL); + } + + color.alpha = (S32)(mAlpha * color.alpha); + drawer->setBitmapModulation(color); + drawer->drawTextN(font, drawPoint, tmp, end-start, mAllowColorChars ? mProfile->mFontColors : NULL); + + //if the atom was "clipped", see if we need to draw a "..." at the end + if (atom->isClipped) + { + Point2I p2 = drawPoint; + p2.x += font->getStrNWidthPrecise(tmp, tmpLen); + drawer->drawTextN(font, p2, "...", 3, mAllowColorChars ? mProfile->mFontColors : NULL); + } + } + else + { + RectI rect; + rect.point.x = drawPoint.x; + rect.point.y = line->y + offset.y; + rect.extent.x = font->getStrNWidth(tmp, tmpLen) + 1; + rect.extent.y = line->height + 1; + + drawer->drawRectFill(rect, mProfile->mFillColorHL); + drawer->setBitmapModulation( mProfile->mFontColorHL ); // over-ride atom color: + drawer->drawTextN(font, drawPoint, tmp, tmpLen, mAllowColorChars ? mProfile->mFontColors : NULL); + + //if the atom was "clipped", see if we need to draw a "..." at the end + if (atom->isClipped) + { + Point2I p2 = drawPoint; + p2.x += font->getStrNWidthPrecise(tmp, end - atom->textStart); + drawer->drawTextN(font, p2, "...", 3, mAllowColorChars ? mProfile->mFontColors : NULL); + } + } + + if(atom->url && !atom->url->noUnderline) + { + drawPoint.y += atom->baseLine + 2; + Point2I p2 = drawPoint; + p2.x += font->getStrNWidthPrecise(tmp, end - atom->textStart); + drawer->drawLine(drawPoint, p2, color); + } +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onRender(Point2I offset, const RectI& updateRect) +{ + Parent::onRender(offset, updateRect); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + // draw all the bitmaps + for(BitmapRef *walk = mBitmapRefList; walk; walk = walk->next) + { + RectI screenBounds = *walk; + screenBounds.point += offset; + if(!screenBounds.overlaps(updateRect)) + continue; + + drawer->clearBitmapModulation(); + drawer->drawBitmap(walk->bitmap->bitmapObject, screenBounds.point); + //GFX->drawRectFill(screenBounds, mProfile->mFillColor); + } + + offset += mProfile->mTextOffset; + + // draw all the text and dividerStyles + for(Line *lwalk = mLineList; lwalk; lwalk = lwalk->next) + { + RectI lineRect(offset.x, offset.y + lwalk->y, getWidth(), lwalk->height); + + if(!lineRect.overlaps(updateRect)) + continue; + + if(lwalk->divStyle) + drawer->drawRectFill(lineRect, mProfile->mFillColorHL); + + for(Atom *awalk = lwalk->atomList; awalk; awalk = awalk->next) + { + if(!mSelectionActive || mSelectionEnd < awalk->textStart || mSelectionStart >= awalk->textStart + awalk->len) + drawAtomText(false, awalk->textStart, awalk->textStart + awalk->len, awalk, lwalk, offset); + else + { + U32 selectionStart = getMax(awalk->textStart, mSelectionStart); + U32 selectionEnd = getMin(awalk->textStart + awalk->len, mSelectionEnd + 1); + + // draw some unselected text + if(selectionStart > awalk->textStart) + drawAtomText(false, awalk->textStart, selectionStart, awalk, lwalk, offset); + + // draw the selection + drawAtomText(true, selectionStart, selectionEnd, awalk, lwalk, offset); + + if(selectionEnd < awalk->textStart + awalk->len) + drawAtomText(false, selectionEnd, awalk->textStart + awalk->len, awalk, lwalk, offset); + } + } + } + + drawer->clearBitmapModulation(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::freeLineBuffers() +{ + mViewChunker.freeBlocks(); + mLineList = NULL; + mBitmapRefList = NULL; + mTagList = NULL; + mHitURL = 0; + mDirty = true; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::freeResources() +{ + for(Font* walk = mFontList; walk; walk = walk->next) + { + walk->fontRes = NULL; + delete[] walk->faceName; + } + + for(Bitmap* bwalk = mBitmapList; bwalk; bwalk = bwalk->next) + bwalk->bitmapObject = 0; + + mFontList = NULL; + mBitmapList = NULL; + mResourceChunker.freeBlocks(); + mDirty = true; + + freeLineBuffers(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onSleep() +{ + freeResources(); + Parent::onSleep(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); + + setText(mInitialText, dStrlen(mInitialText)); + + if (mLineSpacingPixels < 0) + mLineSpacingPixels = 0; +} + + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::parentResized(const RectI& oldParentRect, const RectI& newParentRect) +{ + Parent::parentResized(oldParentRect, newParentRect); + mDirty = true; +} + +//-------------------------------------------------------------------------- +U32 GuiMLTextCtrl::getNumChars() const +{ + return mTextBuffer.length(); +} + +//-------------------------------------------------------------------------- +U32 GuiMLTextCtrl::getText(char* pBuffer, const U32 bufferSize) const +{ + mTextBuffer.getCopy8(pBuffer, bufferSize); + + return getMin(mTextBuffer.length(), bufferSize); +} + +//-------------------------------------------------------------------------- +const char* GuiMLTextCtrl::getTextContent() +{ + if ( mTextBuffer.length() > 0 ) + { + UTF8* returnString = Con::getReturnBuffer( mTextBuffer.getUTF8BufferSizeEstimate() ); + mTextBuffer.getCopy8(returnString, mTextBuffer.getUTF8BufferSizeEstimate() ); + return returnString; + } + + return( "" ); +} + +//-------------------------------------------------------------------------- +const char *GuiMLTextCtrl::getScriptValue() +{ + return getTextContent(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::setScriptValue(const char *newText) +{ + setText(newText, dStrlen(newText)); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::setText(const char* textBuffer, const U32 numChars) +{ + U32 chars = numChars; + if(numChars >= mMaxBufferSize) + chars = mMaxBufferSize; + + // leaving this usage because we StringBuffer.set((UTF8*)) cannot take a numChars arg. + // perhaps it should? -paxorr + FrameTemp tmp(chars+1); + dStrncpy(tmp, textBuffer, chars); + tmp[chars] = 0; + + mTextBuffer.set(tmp); + + //after setting text, always set the cursor to the beginning + setCursorPosition(0); + clearSelection(); + mDirty = true; + scrollToTop(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::addText(const char* textBuffer, const U32 numChars, bool reformat) +{ + if(numChars >= mMaxBufferSize) + return; + + FrameTemp tmp(numChars+1); + dStrncpy(tmp, textBuffer, numChars); + tmp[numChars] = 0; + + mTextBuffer.append(tmp); + + //after setting text, always set the cursor to the beginning + if (reformat) + { + setCursorPosition(0); + clearSelection(); + mDirty = true; + scrollToTop(); + } +} + +//-------------------------------------------------------------------------- +bool GuiMLTextCtrl::setCursorPosition(const S32 newPosition) +{ + if (newPosition < 0) + { + mCursorPosition = 0; + return true; + } + else if (newPosition >= mTextBuffer.length()) + { + mCursorPosition = mTextBuffer.length(); + return true; + } + else + { + mCursorPosition = newPosition; + return false; + } +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::ensureCursorOnScreen() +{ + // If our parent isn't a scroll control, or we're not the only control + // in the content region, bail... + GuiControl* pParent = getParent(); + GuiScrollCtrl *sc = dynamic_cast(pParent); + if(!sc) + return; + + // Ok. Now we know that our parent is a scroll control. Let's find the + // top of the cursor, and it's bottom. We can then scroll the parent control + // if appropriate... + + Point2I cursorTopP, cursorBottomP; + ColorI color; + getCursorPositionAndColor(cursorTopP, cursorBottomP, color); + + sc->scrollRectVisible(RectI(cursorTopP.x, cursorTopP.y, 1, cursorBottomP.y - cursorTopP.y)); +} + +//-------------------------------------- +void GuiMLTextCtrl::getCursorPositionAndColor(Point2I &cursorTop, Point2I &cursorBottom, ColorI &color) +{ + S32 x = 0; + S32 y = 0; + S32 height = mProfile->mFont->getHeight(); + color = mProfile->mCursorColor; + for(Line *walk = mLineList; walk; walk = walk->next) + { + if ((mCursorPosition <= walk->textStart + walk->len) || (walk->next == NULL)) + { + // it's in the atoms on this line... + y = walk->y; + height = walk->height; + + for(Atom *awalk = walk->atomList; awalk; awalk = awalk->next) + { + + if(mCursorPosition < awalk->textStart) + { + x = awalk->xStart; + goto done; + } + + if(mCursorPosition > awalk->textStart + awalk->len) + { + x = awalk->xStart + awalk->width; + continue; + } + + // it's in the text block... + x = awalk->xStart; + GFont *font = awalk->style->font->fontRes; + + const UTF16* buff = mTextBuffer.getPtr() + awalk->textStart; + x += font->getStrNWidth(buff, mCursorPosition - awalk->textStart);// - 1); + + color = awalk->style->color; + goto done; + } + + //if it's within this walk's width, but we didn't find an atom, leave the cursor at the beginning of the line... + goto done; + } + } +done: + cursorTop.set(x, y); + cursorBottom.set(x, y + height); +} + +//-------------------------------------------------------------------------- +// Keyboard events... +bool GuiMLTextCtrl::onKeyDown(const GuiEvent& event) +{ + //only cut/copy work with this control... + if (event.modifier & SI_COPYPASTE) + { + switch(event.keyCode) + { + //copy + case KEY_C: + { + //make sure we actually have something selected + if (mSelectionActive) + { + copyToClipboard(mSelectionStart, mSelectionEnd); + setUpdate(); + } + return true; + } + + default: + break; + } + } + + // Otherwise, let the parent have the event... + return Parent::onKeyDown(event); +} + +//-------------------------------------------------------------------------- +// Mousing events... +void GuiMLTextCtrl::onMouseDown(const GuiEvent& event) +{ + if(!mActive) + return; + + Atom *hitAtom = findHitAtom(globalToLocalCoord(event.mousePoint)); + if(hitAtom && !mIsEditCtrl) + { + mouseLock(); + mHitURL = hitAtom->url; + if (mHitURL) + mHitURL->mouseDown = true; + } + + setFirstResponder(); + mouseLock(); + + mSelectionActive = false; + mSelectionAnchor = getTextPosition(globalToLocalCoord(event.mousePoint)); + mSelectionAnchorDropped = event.mousePoint; + if (mSelectionAnchor < 0) + mSelectionAnchor = 0; + else if (mSelectionAnchor >= mTextBuffer.length()) + mSelectionAnchor = mTextBuffer.length(); + + mVertMoveAnchorValid = false; + + setUpdate(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onMouseDragged(const GuiEvent& event) +{ + if (!mActive || (getRoot()->getMouseLockedControl() != this)) + return; + + Atom *hitAtom = findHitAtom(globalToLocalCoord(event.mousePoint)); + bool down = false; + + //note mHitURL can't be set unless this is (!mIsEditCtrl) + if(hitAtom && hitAtom->url == mHitURL) + down = true; + + if(mHitURL && down != mHitURL->mouseDown) + mHitURL->mouseDown = down; + + if (!mHitURL) + { + S32 newSelection = 0; + newSelection = getTextPosition(globalToLocalCoord(event.mousePoint)); + if (newSelection < 0) + newSelection = 0; + else if (newSelection > mTextBuffer.length()) + newSelection = mTextBuffer.length(); + + if (newSelection == mSelectionAnchor) + { + mSelectionActive = false; + } + else if (newSelection > mSelectionAnchor) + { + mSelectionActive = true; + mSelectionStart = mSelectionAnchor; + mSelectionEnd = newSelection - 1; + } + else + { + mSelectionStart = newSelection; + mSelectionEnd = mSelectionAnchor - 1; + mSelectionActive = true; + } + setCursorPosition(newSelection); + mDirty = true; + } + + setUpdate(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::onMouseUp(const GuiEvent& event) +{ + if (!mActive || (getRoot()->getMouseLockedControl() != this)) + return; + + mouseUnlock(); + + //see if we've clicked on a URL + Atom *hitAtom = findHitAtom(globalToLocalCoord(event.mousePoint)); + if (mHitURL && hitAtom && hitAtom->url == mHitURL && mHitURL->mouseDown) + { + mHitURL->mouseDown = false; + + // Convert URL from UTF16 to UTF8. + UTF8* url = mTextBuffer.createSubstring8(mHitURL->textStart, mHitURL->len); + Con::executef(this, "onURL", url); + delete[] url; + mHitURL = NULL; + + setUpdate(); + return; + } + + //else, update our selection + else + { + if ((event.mousePoint - mSelectionAnchorDropped).len() < 3) + mSelectionActive = false; + + setCursorPosition(getTextPosition(globalToLocalCoord(event.mousePoint))); + mVertMoveAnchorValid = false; + setUpdate(); + } +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) +{ + if(!mUseURLMouseCursor) + { + Parent::getCursor(cursor, showCursor, lastGuiEvent); + return; + } + + GuiCanvas *pRoot = getRoot(); + if( !pRoot ) + return; + + PlatformWindow *pWindow = pRoot->getPlatformWindow(); + AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); + PlatformCursorController *pController = pWindow->getCursorController(); + AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); + + Atom *hitAtom = findHitAtom(globalToLocalCoord(lastGuiEvent.mousePoint)); + if(hitAtom && !mIsEditCtrl && hitAtom->url) + { + if(pRoot->mCursorChanged != PlatformCursorController::curHand) + { + // We've already changed the cursor, so set it back before we change it again. + if(pRoot->mCursorChanged != -1) + pController->popCursor(); + + // Now change the cursor shape + pController->pushCursor(PlatformCursorController::curHand); + pRoot->mCursorChanged = PlatformCursorController::curHand; + + } + } + else if(pRoot->mCursorChanged != -1) + { + // Restore the cursor we changed + pController->popCursor(); + pRoot->mCursorChanged = -1; + } +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::insertChars(const char* inputChars, + const U32 numInputChars, + const U32 position) +{ + AssertFatal(isSelectionActive() == false, "GuiMLTextCtrl::insertChars: don't use this function when there's a selection active!"); + AssertFatal(position <= mTextBuffer.length(), "GuiMLTextCtrl::insertChars: can't insert outside of current text!"); + + //make sure the text will fit... + S32 numCharsToInsert = numInputChars; + if (mMaxBufferSize > 0 && mTextBuffer.length() + numInputChars + 1 > mMaxBufferSize) + numCharsToInsert = mMaxBufferSize - mTextBuffer.length() - 1; + if (numCharsToInsert <= 0) + { + // Play the "Denied" sound: + if ( numInputChars > 0 && mDeniedSound ) + SFX->playOnce(mDeniedSound); + + return; + } + + mTextBuffer.insert(position, inputChars ); + + if (mCursorPosition >= position) + { + // Cursor was at or after the inserted text, move forward... + mCursorPosition += numCharsToInsert; + } + + AssertFatal(mCursorPosition <= mTextBuffer.length(), "GuiMLTextCtrl::insertChars: bad cursor position"); + mDirty = true; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::deleteChars(const U32 rangeStart, + const U32 rangeEnd) +{ + AssertFatal(isSelectionActive() == false, "GuiMLTextCtrl::deleteChars: don't use this function when there's a selection active"); + AssertFatal(rangeStart <= mTextBuffer.length() && rangeEnd <= mTextBuffer.length(), + avar("GuiMLTextCtrl::deleteChars: can't delete outside of current text (%d, %d, %d)", + rangeStart, rangeEnd, mTextBuffer.length())); + AssertFatal(rangeStart <= rangeEnd, "GuiMLTextCtrl::deleteChars: invalid delete range"); + + // Currently deleting text doesn't resize the text buffer, perhaps this should + // change? + mTextBuffer.cut(rangeStart, rangeEnd - rangeStart); + + if (mCursorPosition <= rangeStart) + { + // Cursor placed before deleted text, ignore + } + else if (mCursorPosition > rangeStart && mCursorPosition <= rangeEnd) + { + // Cursor inside deleted text, set to start of range + mCursorPosition = rangeStart; + } + else + { + // Cursor after deleted text, decrement by number of chars deleted + mCursorPosition -= (rangeEnd - rangeStart) + 1; + } + + AssertFatal(mCursorPosition <= mTextBuffer.length(), "GuiMLTextCtrl::deleteChars: bad cursor position"); + mDirty = true; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::copyToClipboard(const U32 rangeStart, const U32 rangeEnd) +{ + AssertFatal(rangeStart < mTextBuffer.length() && rangeEnd < mTextBuffer.length(), + avar("GuiMLTextCtrl::copyToClipboard: can't copy outside of current text (%d, %d, %d)", + rangeStart, rangeEnd, mTextBuffer.length())); + AssertFatal(rangeStart <= rangeEnd, "GuiMLTextCtrl::copyToClipboard: invalid copy range"); + + //copy the selection to the clipboard + + UTF8* selection = mTextBuffer.createSubstring8(rangeStart, rangeEnd-rangeStart); + Platform::setClipboard(selection); + delete[] selection; +} + +//-------------------------------------------------------------------------- +bool GuiMLTextCtrl::isSelectionActive() const +{ + return mSelectionActive; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::clearSelection() +{ + mSelectionActive = false; + mSelectionStart = 0; + mSelectionEnd = 0; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::scrollToTag( U32 id ) +{ + // If the parent control is not a GuiScrollContentCtrl, then this call is invalid: + GuiScrollCtrl *pappy = dynamic_cast(getParent()); + if ( !pappy ) + return; + + // Find the indicated tag: + LineTag* tag = NULL; + for ( tag = mTagList; tag; tag = tag->next ) + { + if ( tag->id == id ) + break; + } + + if ( !tag ) + { + Con::warnf( ConsoleLogEntry::General, "GuiMLTextCtrl::scrollToTag - tag id %d not found!", id ); + return; + } + pappy->scrollRectVisible(RectI(0, tag->y, 1, 1)); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::scrollToTop() +{ + // If the parent control is not a GuiScrollContentCtrl, then this call is invalid: + GuiScrollCtrl *pappy = dynamic_cast(getParent()); + if ( !pappy ) + return; + pappy->scrollRectVisible(RectI(0,0,0,0)); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::scrollToBottom() +{ + // If the parent control is not a GuiScrollContentCtrl, then this call is invalid: + GuiScrollCtrl *pappy = dynamic_cast(getParent()); + if ( !pappy ) + return; + + // Figure bounds for the bottom left corner + RectI cornerBounds (0, getPosition().y + getExtent().y, 1, 1); + pappy->scrollRectVisible(cornerBounds); +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::Atom *GuiMLTextCtrl::findHitAtom(const Point2I localCoords) +{ + AssertFatal(mAwake, "Can't get the text position of a sleeping control."); + if(mDirty) + reflow(); + for(Line *walk = mLineList; walk; walk = walk->next) + { + if(localCoords.y < walk->y) + return NULL; + + if(localCoords.y >= walk->y && localCoords.y < walk->y + walk->height) + { + for(Atom *awalk = walk->atomList; awalk; awalk = awalk->next) + { + if(localCoords.x < awalk->xStart) + return NULL; + if(localCoords.x >= awalk->xStart + awalk->width) + continue; + return awalk; + } + } + } + return NULL; +} + +//-------------------------------------------------------------------------- +S32 GuiMLTextCtrl::getTextPosition(const Point2I& localCoords) +{ + AssertFatal(mAwake, "Can't get the text position of a sleeping control."); + if(mDirty) + reflow(); + + for(Line *walk = mLineList; walk; walk = walk->next) + { + if((S32)localCoords.y < (S32)walk->y) + return walk->textStart; + + if(localCoords.y >= walk->y && localCoords.y < walk->y + walk->height) + { + for(Atom *awalk = walk->atomList; awalk; awalk = awalk->next) + { + if(localCoords.x < awalk->xStart) + return awalk->textStart; + if(localCoords.x >= awalk->xStart + awalk->width) + continue; + // it's in the text block... + GFont *font = awalk->style->font->fontRes; + + const UTF16 *tmp16 = mTextBuffer.getPtr() + awalk->textStart; + U32 bp = font->getBreakPos(tmp16, awalk->len, localCoords.x - awalk->xStart, false); + return awalk->textStart + bp; + } + return walk->textStart + walk->len; + } + } + return mTextBuffer.length() - 1; +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::Font *GuiMLTextCtrl::allocFont(const char *faceName, U32 faceNameLen, U32 size) +{ + // check if it's in the font list currently: + for(Font *walk = mFontList; walk; walk = walk->next) + if(faceNameLen == walk->faceNameLen && + !dStrncmp(walk->faceName, faceName, faceNameLen) && + size == walk->size) + return walk; + + // Create! + Font *ret; + ret = constructInPlace((Font *) mResourceChunker.alloc(sizeof(Font))); + ret->faceName = new char[faceNameLen+1]; + dStrncpy(ret->faceName, faceName, faceNameLen); + ret->faceName[faceNameLen] = '\0'; + ret->faceNameLen = faceNameLen; + ret->size = size; + ret->next = mFontList; + ret->fontRes = GFont::create(ret->faceName, size, GuiControlProfile::sFontCacheDirectory); + if(ret->fontRes != NULL) + { + ret->next = mFontList; + mFontList = ret; + return ret; + } + return NULL; +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::Bitmap *GuiMLTextCtrl::allocBitmap(const char *bitmapName, U32 bitmapNameLen) +{ + for(Bitmap *walk = mBitmapList; walk; walk = walk->next) + if(bitmapNameLen == walk->bitmapNameLen && + !dStrncmp(walk->bitmapName, bitmapName, bitmapNameLen)) + return walk; + + Bitmap *ret = constructInPlace((Bitmap *) mResourceChunker.alloc(sizeof(Bitmap))); + const U32 BitmapNameSize = sizeof(ret->bitmapName); + dStrncpy(ret->bitmapName, bitmapName, getMin(bitmapNameLen,BitmapNameSize)); + if (bitmapNameLen < BitmapNameSize) + ret->bitmapName[bitmapNameLen] = 0; + else + ret->bitmapName[BitmapNameSize - 1] = 0; + ret->bitmapNameLen = bitmapNameLen; + + ret->bitmapObject.set(ret->bitmapName, &GFXMLTextureProfile, avar("%s() - ret->bitmapObject (line %d)", __FUNCTION__, __LINE__)); + //ret->bitmapObject.set(bitmapName, &GFXMLTextureProfile); + if( bool(ret->bitmapObject) ) + { + ret->next = mBitmapList; + mBitmapList = ret; + return ret; + } + return NULL; +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::LineTag *GuiMLTextCtrl::allocLineTag(U32 id) +{ + for ( LineTag* walk = mTagList; walk; walk = walk->next ) + { + if ( walk->id == id ) + { + Con::warnf( ConsoleLogEntry::General, "GuiMLTextCtrl - can't add duplicate line tags!" ); + return( NULL ); + } + } + LineTag* newTag = (LineTag*) mViewChunker.alloc( sizeof( LineTag ) ); + newTag->id = id; + newTag->y = mCurY; + newTag->next = mTagList; + mTagList = newTag; + + return( newTag ); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::emitNewLine(U32 textStart) +{ + //clear any clipping + mCurClipX = 0; + + Line *l = (Line *) mViewChunker.alloc(sizeof(Line)); + l->height = mCurStyle->font->fontRes->getHeight(); + l->y = mCurY; + l->textStart = mLineStart; + l->len = textStart - l->textStart; + mLineStart = textStart; + l->atomList = mLineAtoms; + l->next = 0; + l->divStyle = mCurDiv; + *mLineInsert = l; + mLineInsert = &(l->next); + mCurX = mCurLMargin; + mCurTabStop = 0; + + if(mLineAtoms) + { + // scan through the atoms in the line, get the largest height + U32 maxBaseLine = 0; + U32 maxDescent = 0; + Atom* walk; + + for(walk = mLineAtoms; walk; walk = walk->next) + { + if(walk->baseLine > maxBaseLine) + maxBaseLine = walk->baseLine; + if(walk->descent > maxDescent) + maxDescent = walk->descent; + if(!walk->next) + { + l->len = walk->textStart + walk->len - l->textStart; + mLineStart = walk->textStart + walk->len; + } + } + l->height = maxBaseLine + maxDescent; + + for(walk = mLineAtoms; walk; walk = walk->next) + walk->yStart = mCurY + maxBaseLine - walk->baseLine; + } + mCurY += l->height; + mLineAtoms = NULL; + mLineAtomPtr = &mLineAtoms; + + // clear out the blocker list + BitmapRef **blockList = &mBlockList; + while(*blockList) + { + BitmapRef *blk = *blockList; + if(blk->point.y + blk->extent.y <= mCurY) + *blockList = blk->nextBlocker; + else + blockList = &(blk->nextBlocker); + } + if(mCurY > mMaxY) + mMaxY = mCurY; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::emitBitmapToken(GuiMLTextCtrl::Bitmap *bmp, U32 textStart, bool bitmapBreak) +{ + if(mCurRMargin <= mCurLMargin) + return; + if(mCurRMargin - mCurLMargin < bmp->bitmapObject->getWidth()) + return; + + BitmapRef *ref = (BitmapRef *) mViewChunker.alloc(sizeof(BitmapRef)); + ref->bitmap = bmp; + ref->next = mBitmapRefList; + mBitmapRefList = ref; + + // now we gotta insert it into the blocker list and figure out where it's spos to go... + ref->extent.x = bmp->bitmapObject->getBitmapWidth(); + ref->extent.y = bmp->bitmapObject->getBitmapHeight(); + + // find the first space in the blocker list that will fit this thats > curLMargin + + while(bitmapBreak && mBlockList != &mSentinel) + emitNewLine(textStart); + + for(;;) + { + // loop til we find a line that fits... + // we'll have to emitLine repeatedly to clear out the block lists... + + BitmapRef **walk = &mBlockList; + U32 minx = mCurX; + U32 maxx = mCurRMargin; + + while(*walk) + { + BitmapRef *blk = *walk; + + if(blk->point.x > minx) + { + U32 right = maxx; + if(blk->point.x < right) + right = blk->point.x; + U32 width = right - minx; + + if(right > minx && width >= ref->extent.x) // we've found the spot... + { + // insert it: + U32 x = minx; + if(mCurJustify == CenterJustify) + x += (width - ref->extent.x) >> 1; + else if(mCurJustify == RightJustify) + x += width - ref->extent.x; + ref->point.x = x; + ref->point.y = mCurY; + ref->nextBlocker = blk; + *walk = ref; + if(ref->point.y + ref->extent.y > mMaxY) + mMaxY = ref->point.y + ref->extent.y; + + return; + } + } + if(minx < blk->point.x + blk->extent.x) + minx = blk->point.x + blk->extent.x; + // move on to the next blocker... + walk = &(blk->nextBlocker); + } + // go to the next line... + emitNewLine(textStart); + } +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::emitTextToken(U32 textStart, U32 len) +{ + if(mCurRMargin <= mCurLMargin) + return; + + GFont *font = mCurStyle->font->fontRes; + Atom *a = (Atom *) mViewChunker.alloc(sizeof(Atom)); + a->url = mCurURL; + + a->style = mCurStyle; + mCurStyle->used = true; + + a->baseLine = font->getBaseline(); + a->descent = font->getDescent(); + a->textStart = textStart; + a->len = len; + a->isClipped = false; + a->next = NULL; + *mEmitAtomPtr = a; + mEmitAtomPtr = &(a->next); +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::processEmitAtoms() +{ + Atom *atomList = mEmitAtoms; + mEmitAtoms = NULL; + mEmitAtomPtr = &mEmitAtoms; + + bool bailout = false; + + while(atomList) + { + // split the tokenlist by space + // first find the first space that the text can go into: + BitmapRef *br = mBlockList; + //bool bailout = false; // Scoping error here? Moved up one scope. -pw + Atom *list = atomList; + + while(br && atomList) + { + // if the blocker is before the current x, ignore it. + if(br->point.x + br->extent.x <= mCurX) + { + br = br->nextBlocker; + continue; + } + // if cur x is in the middle of the blocker + // advance cur x to right edge of blocker. + if(mCurX >= br->point.x) + { + mCurX = br->point.x + br->extent.x; + br = br->nextBlocker; + continue; + } + // get the remaining width + U32 right = br->point.x; + if(right > mCurRMargin) + right = mCurRMargin; + + //if we're clipping text, readjust + if (mCurClipX > 0 && right > mCurClipX) + right = mCurClipX; + + // if there's no room, break to the next line... + if(right <= mCurX) + break; + + // we've got some space: + U32 width = right - mCurX; + atomList = splitAtomListEmit(atomList, width); + if(atomList) // there's more, so advance cur x + { + mCurX = br->point.x + br->extent.x; + br = br->nextBlocker; + } + } + if(mBlockList == &mSentinel && atomList == list) + { + if(bailout) + return; + else + bailout = true; + } + // is there more to process for the next line? + if(atomList) + emitNewLine(mScanPos); + } +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::Atom *GuiMLTextCtrl::splitAtomListEmit(Atom *list, U32 width) +{ + U32 totalWidth = 0; + Atom *emitList = 0; + Atom **emitPtr = &emitList; + + bool adjustClipAtom = false; + Atom *clipAtom = NULL; + bool emitted = false; + + while(list) + { + GFont *font = list->style->font->fontRes; + U32 breakPos; + + const UTF16 *tmp16 = mTextBuffer.getPtr() + list->textStart; + + //if we're clipping the text, we don't break within an atom, we adjust the atom to only render + //the portion of text that does fit, and to ignore the rest... + if (mCurClipX > 0) + { + //find out how many character's fit within the given width + breakPos = font->getBreakPos(tmp16, list->len, width - totalWidth, false); + + //if there isn't room for even the first character... + if (breakPos == 0) + { + //set the atom's len and width to prevent it from being drawn + list->len = 0; + list->width = 0; + adjustClipAtom = true; + } + + //if our text doesn't fit within the clip region, add a "..." + else if (breakPos != list->len) + { + U32 etcWidth = font->getStrNWidthPrecise("...", 3); + breakPos = font->getBreakPos(tmp16, list->len, width - totalWidth - etcWidth, false); + + //again, if there isn't even room for a single character before the "...." + if (breakPos == 0) + { + //set the atom's len and width to prevent it from being drawn + list->len = 0; + list->width = 0; + adjustClipAtom = true; + } + else + { + //set the char len to the break pos, and the rest of the characters in this atom will be ignored + list->len = breakPos; + list->width = width - totalWidth; + + //mark this one as clipped + list->isClipped = true; + clipAtom = NULL; + } + } + + //otherwise no need to treat this atom any differently.. + else + { + //set the atom width == to the string length + list->width = font->getStrNWidthPrecise(tmp16, breakPos); + + //set the pointer to the last atom that fit within the clip region + clipAtom = list; + } + } + else + { + breakPos = font->getBreakPos(tmp16, list->len, width - totalWidth, true); + if(breakPos == 0 || (breakPos < list->len && mTextBuffer.getChar(list->textStart + breakPos - 1)!= ' ' && emitted)) + break; + + //set the atom width == to the string length + list->width = font->getStrNWidthPrecise(tmp16, breakPos); + } + + //update the total width + totalWidth += list->width; + + // see if this is the last atom that will fit: + Atom *emit = list; + + *emitPtr = emit; + emitPtr = &(emit->next); + emitted = true; + + //if we're clipping, don't split the atom, otherwise, see if it needs to be split + if(!list->isClipped && breakPos != list->len) + { + Atom *a = (Atom *) mViewChunker.alloc(sizeof(Atom)); + a->url = list->url; + a->textStart = list->textStart + breakPos; + a->len = list->len - breakPos; + a->next = list->next; + a->baseLine = list->baseLine; + a->descent = list->descent; + a->style = list->style; + a->isClipped = false; + + list = a; + emit->len = breakPos; + break; + } + list = list->next; + if(totalWidth > width) + break; + } + + //if we had to completely clip an atom(s), the last (partially) visible atom should be modified to include a "..." + if (adjustClipAtom && clipAtom) + { + GFont *font = clipAtom->style->font->fontRes; + U32 breakPos; + + U32 etcWidth = font->getStrNWidthPrecise("...", 3); + + const UTF16 *tmp16 = mTextBuffer.getPtr() + clipAtom->textStart; + + breakPos = font->getBreakPos(tmp16, clipAtom->len, clipAtom->width - etcWidth, false); + if (breakPos != 0) + { + clipAtom->isClipped = true; + clipAtom->len = breakPos; + } + } + + // terminate the emit list: + *emitPtr = 0; + // now emit it: + // going from mCurX to mCurX + width: + if(mCurJustify == CenterJustify) + { + if ( width > totalWidth ) + mCurX += (width - totalWidth) >> 1; + } + else if(mCurJustify == RightJustify) + { + if ( width > totalWidth ) + mCurX += width - totalWidth; + } + + + while(emitList) + { + emitList->xStart = mCurX; + mCurX += emitList->width; + Atom *temp = emitList->next; + *mLineAtomPtr = emitList; + emitList->next = 0; + mLineAtomPtr = &(emitList->next); + emitList = temp; + } + + return list; +} + +//-------------------------------------------------------------------------- +static bool scanforchar(const char *str, U32 &idx, char c) +{ + U32 startidx = idx; + while(str[idx] != c && str[idx] && str[idx] != ':' && str[idx] != '>' && str[idx] != '\n') + idx++; + return str[idx] == c && startidx != idx; +} + +//-------------------------------------------------------------------------- +static bool scanforURL(const char *str, U32 &idx, char c) +{ + U32 startidx = idx; + while(str[idx] != c && str[idx] && str[idx] != '>' && str[idx] != '\n') + idx++; + return str[idx] == c && startidx != idx; +} + +//-------------------------------------------------------------------------- +static S32 getHexVal(char c) +{ + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if(c >= 'a' && c <= 'z') + return c - 'a' + 10; + return -1; +} + +//-------------------------------------------------------------------------- +GuiMLTextCtrl::Style *GuiMLTextCtrl::allocStyle(GuiMLTextCtrl::Style *style) +{ + Style *ret = (Style *) mViewChunker.alloc(sizeof(Style)); + ret->used = false; + if(style) + { + ret->font = style->font; + ret->color = style->color; + ret->linkColor = style->linkColor; + ret->linkColorHL = style->linkColorHL; + ret->shadowColor = style->shadowColor; + ret->shadowOffset = style->shadowOffset; + ret->next = style->next; + } + else + { + ret->font = 0; + ret->next = 0; + } + return ret; +} + +//-------------------------------------------------------------------------- +void GuiMLTextCtrl::reflow() +{ + AssertFatal(mAwake, "Can't reflow a sleeping control."); + freeLineBuffers(); + mDirty = false; + mScanPos = 0; + + mLineList = NULL; + mLineInsert = &mLineList; + + mCurStyle = allocStyle(NULL); + mCurStyle->font = allocFont((char *) mProfile->mFontType, dStrlen(mProfile->mFontType), mProfile->mFontSize); + if(!mCurStyle->font) + return; + mCurStyle->color = mProfile->mFontColor; + mCurStyle->shadowColor = mProfile->mFontColor; + mCurStyle->shadowOffset.set(0,0); + mCurStyle->linkColor = mProfile->mFontColors[GuiControlProfile::ColorUser0]; + mCurStyle->linkColorHL = mProfile->mFontColors[GuiControlProfile::ColorUser1]; + + U32 width = getWidth(); + + mCurLMargin = 0; + mCurRMargin = width; + mCurJustify = LeftJustify; + mCurDiv = 0; + mCurY = 0; + mCurX = 0; + mCurClipX = 0; + mLineAtoms = NULL; + mLineAtomPtr = &mLineAtoms; + + mSentinel.point.x = width; + mSentinel.point.y = 0; + mSentinel.extent.x = 0; + mSentinel.extent.y = 0x7FFFFF; + mSentinel.nextBlocker = NULL; + mLineStart = 0; + mEmitAtoms = 0; + mMaxY = 0; + mEmitAtomPtr = &mEmitAtoms; + + mBlockList = &mSentinel; + + Font *nextFont; + LineTag *nextTag; + mTabStops = 0; + mCurTabStop = 0; + mTabStopCount = 0; + mCurURL = 0; + Style *newStyle; + + U32 textStart; + U32 len; + U32 idx; + U32 sizidx; + + for(;;) + { + UTF16 curChar = mTextBuffer.getChar(mScanPos); + + if(!curChar) + break; + + if(curChar == '\n') + { + textStart = mScanPos; + len = 1; + mScanPos++; + processEmitAtoms(); + emitNewLine(textStart); + mCurDiv = 0; + continue; + } + + if(curChar == '\t') + { + textStart = mScanPos; + len = 1; + mScanPos++; + processEmitAtoms(); + if(mTabStopCount) + { + if(mCurTabStop < mTabStopCount) + { + if(mCurX < mTabStops[mCurTabStop]) + mCurX = mTabStops[mCurTabStop]; + } + mCurTabStop++; + } + continue; + } + + if(curChar == '<') + { + // it's probably some kind of tag: + + // Get a pointer into the utf8 version of the buffer, + // because we're still scanning text in in utf8 mode. + const UTF8 *str = mTextBuffer.getPtr8(); + str = getNthCodepoint(str, mScanPos); + + // And go! + + if(!dStrnicmp(str + 1, "br>", 3)) + { + mScanPos += 4; + len = 4; + textStart = mScanPos + 4; + processEmitAtoms(); + emitNewLine(textStart); + mCurDiv = 0; + continue; + } + + if(!dStrnicmp(str + 1, "font:", 5)) + { + // scan for the second colon... + // at each level it should drop out to the text case below... + idx = 6; + if(!scanforchar(str, idx, ':')) + goto textemit; + + sizidx = idx + 1; + if(!scanforchar(str, sizidx, '>')) + goto textemit; + + U32 size = dAtoi(str + idx + 1); + if(!size || size > 64) + goto textemit; + textStart = mScanPos + 6; + len = idx - 6; + + mScanPos += sizidx + 1; + + nextFont = allocFont(str + 6, len, size); + if(nextFont) + { + if(mCurStyle->used) + mCurStyle = allocStyle(mCurStyle); + mCurStyle->font = nextFont; + } + continue; + } + + if ( !dStrnicmp( str + 1, "tag:", 4 ) ) + { + idx = 5; + if ( !scanforchar( str, idx, '>' ) ) + goto textemit; + U32 tagId = dAtoi( str + 5 ); + nextTag = allocLineTag( tagId ); + + mScanPos += idx + 1; + continue; + } + + if(!dStrnicmp(str +1, "color:", 6)) + { + idx = 7; + if(!scanforchar(str, idx, '>')) + goto textemit; + if(idx != 13 && idx != 15) + goto textemit; + ColorI color; + + color.red = getHexVal(str[7]) * 16 + getHexVal(str[8]); + color.green = getHexVal(str[9]) * 16 + getHexVal(str[10]); + color.blue = getHexVal(str[11]) * 16 + getHexVal(str[12]); + if(idx == 15) + color.alpha = getHexVal(str[13]) * 16 + getHexVal(str[14]); + else + color.alpha = 255; + mScanPos += idx + 1; + + if(mCurStyle->used) + mCurStyle = allocStyle(mCurStyle); + mCurStyle->color = color; + + continue; + } + + if(!dStrnicmp(str +1, "shadowcolor:", 12)) + { + idx = 13; + if(!scanforchar(str, idx, '>')) + goto textemit; + if(idx != 19 && idx != 21) + goto textemit; + ColorI color; + + color.red = getHexVal(str[13]) * 16 + getHexVal(str[14]); + color.green = getHexVal(str[15]) * 16 + getHexVal(str[16]); + color.blue = getHexVal(str[17]) * 16 + getHexVal(str[18]); + if(idx == 21) + color.alpha = getHexVal(str[19]) * 16 + getHexVal(str[20]); + else + color.alpha = 255; + mScanPos += idx + 1; + + if(mCurStyle->used) + mCurStyle = allocStyle(mCurStyle); + mCurStyle->shadowColor = color; + + continue; + } + + if(!dStrnicmp(str +1, "linkcolor:", 10)) + { + idx = 11; + if(!scanforchar(str, idx, '>')) + goto textemit; + if(idx != 17 && idx != 19) + goto textemit; + ColorI color; + + color.red = getHexVal(str[11]) * 16 + getHexVal(str[12]); + color.green = getHexVal(str[13]) * 16 + getHexVal(str[14]); + color.blue = getHexVal(str[15]) * 16 + getHexVal(str[16]); + if(idx == 19) + color.alpha = getHexVal(str[17]) * 16 + getHexVal(str[18]); + else + color.alpha = 255; + mScanPos += idx + 1; + + if(mCurStyle->used) + mCurStyle = allocStyle(mCurStyle); + mCurStyle->linkColor = color; + + continue; + } + + if(!dStrnicmp(str +1, "linkcolorhl:", 12)) + { + idx = 13; + if(!scanforchar(str, idx, '>')) + goto textemit; + if(idx != 19 && idx != 21) + goto textemit; + ColorI color; + + color.red = getHexVal(str[13]) * 16 + getHexVal(str[14]); + color.green = getHexVal(str[15]) * 16 + getHexVal(str[16]); + color.blue = getHexVal(str[17]) * 16 + getHexVal(str[18]); + if(idx == 21) + color.alpha = getHexVal(str[19]) * 16 + getHexVal(str[20]); + else + color.alpha = 255; + mScanPos += idx + 1; + + if(mCurStyle->used) + mCurStyle = allocStyle(mCurStyle); + mCurStyle->linkColorHL = color; + + continue; + } + if(!dStrnicmp(str +1, "shadow:", 7)) + { + idx = 8; + if(!scanforchar(str, idx, ':')) + goto textemit; + U32 yidx = idx + 1; + if(!scanforchar(str, yidx, '>')) + goto textemit; + mScanPos += yidx + 1; + Point2I offset; + offset.x = dAtoi(str + 8); + offset.y = dAtoi(str + idx + 1); + if(mCurStyle->used) + mCurStyle = allocStyle(mCurStyle); + mCurStyle->shadowOffset = offset; + continue; + } + if(!dStrnicmp(str +1, "bitmap", 6)) + { + S32 start = 8; + bool bitBrk = false; + if(str[7] == 'k' && str[8] == ':') + { + bitBrk = true; + start = 9; + } + else if(str[7] != ':') + goto textemit; + + idx = start; + if(!scanforchar(str, idx, '>')) + goto textemit; + textStart = mScanPos + start; + len = idx - start; + + mScanPos += idx + 1; + + processEmitAtoms(); + Bitmap *bmp; + bmp = allocBitmap(str + 8, len); + if(bmp) + emitBitmapToken(bmp, textStart, bitBrk); + continue; + } + + if(!dStrnicmp(str +1, "spush>", 6)) + { + mScanPos += 7; + newStyle = allocStyle(mCurStyle); // copy out all the attributes... + newStyle->next = mCurStyle; + mCurStyle = newStyle; + continue; + } + + if(!dStrnicmp(str +1, "spop>", 5)) + { + mScanPos += 6; + if(mCurStyle->next) + mCurStyle = mCurStyle->next; + continue; + } + + if(!dStrnicmp(str +1, "sbreak>", 7)) + { + mScanPos += 8; + processEmitAtoms(); + while(mBlockList != &mSentinel) + emitNewLine(mScanPos); + continue; + } + + if(!dStrnicmp(str +1, "just:left>", 10)) + { + processEmitAtoms(); + mCurJustify = LeftJustify; + mScanPos += 11; + continue; + } + + if(!dStrnicmp(str +1, "just:right>", 11)) + { + processEmitAtoms(); + mCurJustify = RightJustify; + mScanPos += 12; + continue; + } + + if(!dStrnicmp(str +1, "just:center>", 12)) + { + processEmitAtoms(); + mCurJustify = CenterJustify; + mScanPos += 13; + continue; + } + + if(!dStrnicmp(str +1, "a:", 2)) + { + idx = 3; + if(!scanforURL(str, idx, '>')) + goto textemit; + + mCurURL = (URL *) mViewChunker.alloc(sizeof(URL)); + mCurURL->mouseDown = false; + mCurURL->textStart = mScanPos + 3; + mCurURL->len = idx - 3; + mCurURL->noUnderline = false; + + //if the URL is a "gamelink", don't underline... + if (!dStrnicmp(str + 3, "gamelink", 8)) + mCurURL->noUnderline = true; + + mScanPos += idx + 1; + continue; + } + + if(!dStrnicmp(str+1, "/a>", 3)) + { + mCurURL = NULL; + mScanPos += 4; + continue; + } + + U32 margin; + + if(!dStrnicmp(str + 1, "lmargin%:", 9)) + { + idx = 10; + if(!scanforchar(str, idx, '>')) + goto textemit; + margin = (getWidth() * dAtoi(str + 10)) / 100; + mScanPos += idx + 1; + goto setleftmargin; + } + + if(!dStrnicmp(str + 1, "lmargin:", 8)) + { + idx = 9; + if(!scanforchar(str, idx, '>')) + goto textemit; + margin = dAtoi(str + 9); + mScanPos += idx + 1; +setleftmargin: + processEmitAtoms(); + U32 oldLMargin; + oldLMargin = mCurLMargin; + mCurLMargin = margin; + if(mCurLMargin >= width) + mCurLMargin = width - 1; + if(mCurX == oldLMargin) + mCurX = mCurLMargin; + if(mCurX < mCurLMargin) + mCurX = mCurLMargin; + continue; + } + + if(!dStrnicmp(str + 1, "rmargin%:", 9)) + { + idx = 10; + if(!scanforchar(str, idx, '>')) + goto textemit; + margin = (getWidth() * dAtoi(str + 10)) / 100; + mScanPos += idx + 1; + goto setrightmargin; + } + + if(!dStrnicmp(str + 1, "rmargin:", 8)) + { + idx = 9; + if(!scanforchar(str, idx, '>')) + goto textemit; + margin = dAtoi(str + 9); + mScanPos += idx + 1; +setrightmargin: + processEmitAtoms(); + mCurRMargin = margin; + if(mCurLMargin >= width) + mCurLMargin = width; + if (mCurClipX > mCurRMargin) + mCurClipX = mCurRMargin; + continue; + } + + if(!dStrnicmp(str + 1, "clip:", 5)) + { + U32 clipWidth = 0; + idx = 6; + if(!scanforchar(str, idx, '>')) + goto textemit; + clipWidth = dAtoi(str + 6); + mScanPos += idx + 1; + processEmitAtoms(); + if (clipWidth > 0) + mCurClipX = mCurX + clipWidth; + else + mCurClipX = 0; + if(mCurClipX > mCurRMargin) + mCurClipX = mCurRMargin; + continue; + } + + if(!dStrnicmp(str + 1, "/clip>", 6)) + { + processEmitAtoms(); + mCurClipX = 0; + mScanPos += 7; + continue; + } + + if(!dStrnicmp(str + 1, "div:", 4)) + { + idx = 5; + if(!scanforchar(str, idx, '>')) + goto textemit; + mScanPos += idx + 1; + mCurDiv = dAtoi(str + 5); + continue; + } + + if(!dStrnicmp(str + 1, "tab:", 4)) + { + idx = 5; + if(!scanforchar(str, idx, '>')) + goto textemit; + // scan for tab stops... + mTabStopCount = 1; + idx = 5; + while(scanforchar(str, idx, ',')) + { + idx++; + mTabStopCount++; + } + idx = 5; + mTabStops = (U32 *) mViewChunker.alloc(sizeof(U32) * mTabStopCount); + mTabStops[0] = dAtoi(str + idx); + U32 i = 1; + + while(scanforchar(str, idx, ',')) + { + idx++; + mTabStops[i] = dAtoi(str + idx); + i++; + } + mScanPos += idx + 1; + continue; + } + } + + // default case: +textemit: + textStart = mScanPos; + idx = 1; + while(mTextBuffer.getChar(mScanPos+idx) != '\t' && mTextBuffer.getChar(mScanPos+idx) != '<' && mTextBuffer.getChar(mScanPos+idx) != '\n' && mTextBuffer.getChar(mScanPos+idx)) + idx++; + len = idx; + mScanPos += idx; + emitTextToken(textStart, len); + } + processEmitAtoms(); + emitNewLine(mScanPos); + setHeight( mMaxY ); + Con::executef( this, "onResize", Con::getIntArg( getWidth() ), Con::getIntArg( mMaxY ) ); + + //make sure the cursor is still visible - this handles if we're a child of a scroll ctrl... + ensureCursorOnScreen(); +} + +//----------------------------------------------------------------------------- +char* GuiMLTextCtrl::stripControlChars(const char *inString) +{ + if (! bool(inString)) + return NULL; + U32 maxBufLength = 64; + char *strippedBuffer = Con::getReturnBuffer(maxBufLength); + char *stripBufPtr = &strippedBuffer[0]; + const char *bufPtr = (char *) inString; + U32 idx, sizidx; + + for(;;) + { + //if we've reached the end of the string, or run out of room in the stripped Buffer... + if(*bufPtr == '\0' || (U32(stripBufPtr - strippedBuffer) >= maxBufLength - 1)) + break; + + if (*bufPtr == '\n') + { + U32 walked; + oneUTF8toUTF32(bufPtr,&walked); + bufPtr += walked; + continue; + } + if(*bufPtr == '\t') + { + U32 walked; + oneUTF8toUTF32(bufPtr,&walked); + bufPtr += walked; + continue; + } + if(*bufPtr < 0x20 && *bufPtr >= 0) + { + U32 walked; + oneUTF8toUTF32(bufPtr,&walked); + bufPtr += walked; + continue; + } + + if(*bufPtr == '<') + { + // it's probably some kind of tag: + if(!dStrnicmp(bufPtr + 1, "font:", 5)) + { + // scan for the second colon... + // at each level it should drop out to the text case below... + idx = 6; + if(!scanforchar((char*)bufPtr, idx, ':')) + goto textemit; + + sizidx = idx + 1; + if(!scanforchar((char*)bufPtr, sizidx, '>')) + goto textemit; + + bufPtr += sizidx + 1; + continue; + } + + if (!dStrnicmp(bufPtr + 1, "tag:", 4 )) + { + idx = 5; + if ( !scanforchar((char*)bufPtr, idx, '>' )) + goto textemit; + + bufPtr += idx + 1; + continue; + } + + if(!dStrnicmp(bufPtr + 1, "color:", 6)) + { + idx = 7; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + if(idx != 13) + goto textemit; + + bufPtr += 14; + continue; + } + + if(!dStrnicmp(bufPtr +1, "bitmap:", 7)) + { + idx = 8; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + + bufPtr += idx + 1; + continue; + } + + if(!dStrnicmp(bufPtr +1, "spush>", 6)) + { + bufPtr += 7; + continue; + } + + if(!dStrnicmp(bufPtr +1, "spop>", 5)) + { + bufPtr += 6; + continue; + } + + if(!dStrnicmp(bufPtr +1, "sbreak>", 7)) + { + bufPtr += 8; + continue; + } + + if(!dStrnicmp(bufPtr +1, "just:left>", 10)) + { + bufPtr += 11; + continue; + } + + if(!dStrnicmp(bufPtr +1, "just:right>", 11)) + { + bufPtr += 12; + continue; + } + + if(!dStrnicmp(bufPtr +1, "just:center>", 12)) + { + bufPtr += 13; + continue; + } + + if(!dStrnicmp(bufPtr +1, "a:", 2)) + { + idx = 3; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + + bufPtr += idx + 1; + continue; + } + + if(!dStrnicmp(bufPtr+1, "/a>", 3)) + { + bufPtr += 4; + continue; + } + + if(!dStrnicmp(bufPtr + 1, "lmargin%:", 9)) + { + idx = 10; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + bufPtr += idx + 1; + goto setleftmargin; + } + + if(!dStrnicmp(bufPtr + 1, "lmargin:", 8)) + { + idx = 9; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + bufPtr += idx + 1; +setleftmargin: + continue; + } + + if(!dStrnicmp(bufPtr + 1, "rmargin%:", 9)) + { + idx = 10; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + bufPtr += idx + 1; + goto setrightmargin; + } + + if(!dStrnicmp(bufPtr + 1, "rmargin:", 8)) + { + idx = 9; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + bufPtr += idx + 1; +setrightmargin: + continue; + } + + if(!dStrnicmp(bufPtr + 1, "clip:", 5)) + { + idx = 6; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + bufPtr += idx + 1; + continue; + } + + if(!dStrnicmp(bufPtr + 1, "/clip>", 6)) + { + bufPtr += 7; + continue; + } + + if(!dStrnicmp(bufPtr + 1, "div:", 4)) + { + idx = 5; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + bufPtr += idx + 1; + continue; + } + + if(!dStrnicmp(bufPtr + 1, "tab:", 4)) + { + idx = 5; + if(!scanforchar((char*)bufPtr, idx, '>')) + goto textemit; + bufPtr += idx + 1; + continue; + } + } + + // default case: +textemit: + *stripBufPtr++ = *bufPtr++; + while(*bufPtr != '\t' && *bufPtr != '<' && *bufPtr != '\n' && (*bufPtr >= 0x20 || *bufPtr < 0)) + *stripBufPtr++ = *bufPtr++; + } + + //we're finished - terminate the string + *stripBufPtr = '\0'; + return strippedBuffer; +} + diff --git a/gui/controls/guiMLTextCtrl.h b/gui/controls/guiMLTextCtrl.h new file mode 100644 index 0000000..b58cd8b --- /dev/null +++ b/gui/controls/guiMLTextCtrl.h @@ -0,0 +1,282 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMLTEXTCTRL_H_ +#define _GUIMLTEXTCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#ifndef _STRINGBUFFER_H_ +#include "core/stringBuffer.h" +#endif + +class GFont; + +GFX_DeclareTextureProfile(GFXMLTextureProfile); +class GuiMLTextCtrl : public GuiControl +{ + typedef GuiControl Parent; + + //-------------------------------------- Public interfaces... + public: + enum Justification + { + LeftJustify, + RightJustify, + CenterJustify, + }; + + struct Font { + char *faceName; + U32 faceNameLen; + U32 size; + Resource fontRes; + Font *next; + }; + + struct Bitmap { + char bitmapName[1024]; + U32 bitmapNameLen; + GFXTexHandle bitmapObject; + Bitmap *next; + }; + + struct URL + { + bool mouseDown; + U32 textStart; + U32 len; + bool noUnderline; + }; + + struct Style + { + ColorI color; + ColorI shadowColor; + ColorI linkColor; + ColorI linkColorHL; + Point2I shadowOffset; + Font *font; + bool used; + Style *next; + }; + + struct Atom + { + U32 textStart; + U32 len; + U32 xStart; + U32 yStart; + U32 width; + U32 baseLine; + U32 descent; + Style *style; + bool isClipped; + + URL *url; + Atom *next; + }; + + struct Line { + U32 y; + U32 height; + U32 divStyle; + U32 textStart; + U32 len; + Atom *atomList; + Line *next; + }; + + struct BitmapRef : public RectI + { + BitmapRef *nextBlocker; + U32 textStart; + U32 len; + Bitmap *bitmap; + BitmapRef *next; + }; + + struct LineTag { + U32 id; + S32 y; + LineTag *next; + }; + + GuiMLTextCtrl(); + ~GuiMLTextCtrl(); + + // Text retrieval functions + U32 getNumChars() const; + U32 getText(char* pBuffer, const U32 bufferSize) const; + U32 getWrappedText(char* pBuffer, const U32 bufferSize) const; + const char* getTextContent(); + void insertChars(const char* inputChars, + const U32 numInputChars, + const U32 position); + + // Text substitution functions + void setText(const char* textBuffer, const U32 numChars); + void addText(const char* textBuffer, const U32 numChars, bool reformat); + + void setAlpha(F32 alpha) { mAlpha = alpha;} + + bool setCursorPosition(const S32); + void ensureCursorOnScreen(); + + // Scroll functions + void scrollToTag( U32 id ); + void scrollToTop(); + void scrollToBottom(); + + virtual void reflow(); + + DECLARE_CONOBJECT(GuiMLTextCtrl); + DECLARE_CATEGORY( "Gui Text" ); + DECLARE_DESCRIPTION( "A control that displays multiple lines of text." ); + + static void initPersistFields(); + + void setScriptValue(const char *value); + const char *getScriptValue(); + + static char *stripControlChars(const char *inString); + + //-------------------------------------- Protected Structures and constants + protected: + bool mIsEditCtrl; + + U32 *mTabStops; + U32 mTabStopCount; + U32 mCurTabStop; + + F32 mAlpha; + + DataChunker mViewChunker; + DataChunker mResourceChunker; + Line *mLineList; + Bitmap *mBitmapList; + BitmapRef *mBitmapRefList; + Font *mFontList; + LineTag *mTagList; + bool mDirty; + Style *mCurStyle; + + U32 mCurLMargin; + U32 mCurRMargin; + U32 mCurJustify; + U32 mCurDiv; + U32 mCurY; + U32 mCurClipX; + Atom *mLineAtoms; + Atom **mLineAtomPtr; + + Atom *mEmitAtoms; + Atom **mEmitAtomPtr; + + BitmapRef mSentinel; + Line **mLineInsert; + BitmapRef *mBlockList; + U32 mScanPos; + U32 mCurX; + U32 mMaxY; + URL *mCurURL; + + URL *mHitURL; + + void freeLineBuffers(); + void freeResources(); + + Bitmap *allocBitmap(const char *bitmapName, U32 bitmapNameLen); + Font *allocFont(const char *faceName, U32 faceNameLen, U32 size); + LineTag *allocLineTag(U32 id); + void emitNewLine(U32 textStart); + Atom *buildTextAtom(U32 start, U32 len, U32 left, U32 right, URL *url); + void emitTextToken(U32 textStart, U32 len); + void emitBitmapToken(Bitmap *bmp, U32 textStart, bool bitmapBreak); + void processEmitAtoms(); + Atom *splitAtomListEmit(Atom *list, U32 width); + void drawAtomText(bool sel, U32 start, U32 end, Atom *atom, Line *line, Point2I offset); + Atom *findHitAtom(const Point2I localCoords); + Style *allocStyle(Style *style); + + static const U32 csmTextBufferGrowthSize; + + //-------------------------------------- Data... + protected: + // Cursor position should always be <= mCurrTextSize + U32 mCursorPosition; + + // Actual text data. The line buffer is rebuilt from the linear text + // given a specific width. TextBuffer is /not/ \0 terminated + StringBuffer mTextBuffer; + U32 mLineStart; + S32 mMaxBufferSize; + StringTableEntry mInitialText; + + // Selection information + bool mSelectionActive; + U32 mSelectionStart; + U32 mSelectionEnd; + + U32 mVertMoveAnchor; + bool mVertMoveAnchorValid; + + S32 mSelectionAnchor; + Point2I mSelectionAnchorDropped; + + // Font resource + Resource mFont; + + // Console settable parameters + U32 mLineSpacingPixels; + bool mAllowColorChars; + bool mUseURLMouseCursor; + + // Too many chars sound: + SFXProfile* mDeniedSound; + + //-------------------------------------- Protected interface + protected: + // Inserting and deleting character blocks... + void deleteChars(const U32 rangeStart, + const U32 rangeEnd); + void copyToClipboard(const U32 rangeStart, + const U32 rangeEnd); + + // Selection maintainence + bool isSelectionActive() const; + void clearSelection(); + + // Pixel -> text position mappings + S32 getTextPosition(const Point2I& localPosition); + + // Gui control overrides + bool onWake(); + void onSleep(); + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + void getCursorPositionAndColor(Point2I &cursorTop, Point2I &cursorBottom, ColorI &color); + void inspectPostApply(); + void parentResized(const RectI& oldParentRect, const RectI& newParentRect); + bool onKeyDown(const GuiEvent& event); + void onMouseDown(const GuiEvent&); + void onMouseDragged(const GuiEvent&); + void onMouseUp(const GuiEvent&); + + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + + public: + // Gui control overrides + bool onAdd(); + + void setSelectionStart( U32 start ) { clearSelection(); mSelectionStart = start; }; + void setSelectionEnd( U32 end ) { mSelectionEnd = end;}; + void setSelectionActive(bool active) { mSelectionActive = active; }; + S32 getCursorPosition() { return( mCursorPosition ); } +}; + +#endif // _H_GUIMLTEXTCTRL_ diff --git a/gui/controls/guiMLTextEditCtrl.cpp b/gui/controls/guiMLTextEditCtrl.cpp new file mode 100644 index 0000000..9e84734 --- /dev/null +++ b/gui/controls/guiMLTextEditCtrl.cpp @@ -0,0 +1,440 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/controls/guiMLTextEditCtrl.h" +#include "gui/containers/guiScrollCtrl.h" +#include "gui/core/guiCanvas.h" +#include "console/consoleTypes.h" +#include "platform/event.h" +#include "core/frameAllocator.h" +#include "core/stringBuffer.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CONOBJECT(GuiMLTextEditCtrl); + +//-------------------------------------------------------------------------- +GuiMLTextEditCtrl::GuiMLTextEditCtrl() +{ + mEscapeCommand = StringTable->insert( "" ); + + mIsEditCtrl = true; + + mActive = true; + + mVertMoveAnchorValid = false; +} + + +//-------------------------------------------------------------------------- +GuiMLTextEditCtrl::~GuiMLTextEditCtrl() +{ + +} + + +//-------------------------------------------------------------------------- +bool GuiMLTextEditCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + // We don't want to get any smaller than our parent: + Point2I newExt = newExtent; + GuiControl* parent = getParent(); + if ( parent ) + newExt.y = getMax( parent->getHeight(), newExt.y ); + + return Parent::resize( newPosition, newExt ); +} + + +//-------------------------------------------------------------------------- +void GuiMLTextEditCtrl::initPersistFields() +{ + addField( "escapeCommand", TypeString, Offset( mEscapeCommand, GuiMLTextEditCtrl ) ); + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextEditCtrl::setFirstResponder() +{ + Parent::setFirstResponder(); + + GuiCanvas *root = getRoot(); + if (root != NULL) + { + root->enableKeyboardTranslation(); + + // If the native OS accelerator keys are not disabled + // then some key events like Delete, ctrl+V, etc may + // not make it down to us. + root->setNativeAcceleratorsEnabled( false ); + } +} + +void GuiMLTextEditCtrl::onLoseFirstResponder() +{ + GuiCanvas *root = getRoot(); + if (root != NULL) + { + root->setNativeAcceleratorsEnabled( true ); + root->disableKeyboardTranslation(); + } + + // Redraw the control: + setUpdate(); +} + +//-------------------------------------------------------------------------- +bool GuiMLTextEditCtrl::onWake() +{ + if( !Parent::onWake() ) + return false; + + getRoot()->enableKeyboardTranslation(); + return true; +} + +//-------------------------------------------------------------------------- +// Key events... +bool GuiMLTextEditCtrl::onKeyDown(const GuiEvent& event) +{ + if ( !isActive() ) + return false; + + setUpdate(); + //handle modifiers first... + if (event.modifier & SI_PRIMARY_CTRL) + { + switch(event.keyCode) + { + //copy/cut + case KEY_C: + case KEY_X: + { + //make sure we actually have something selected + if (mSelectionActive) + { + copyToClipboard(mSelectionStart, mSelectionEnd); + + //if we're cutting, also delete the selection + if (event.keyCode == KEY_X) + { + mSelectionActive = false; + deleteChars(mSelectionStart, mSelectionEnd); + mCursorPosition = mSelectionStart; + } + else + mCursorPosition = mSelectionEnd + 1; + } + return true; + } + + //paste + case KEY_V: + { + const char *clipBuf = Platform::getClipboard(); + if (dStrlen(clipBuf) > 0) + { + // Normal ascii keypress. Go ahead and add the chars... + if (mSelectionActive == true) + { + mSelectionActive = false; + deleteChars(mSelectionStart, mSelectionEnd); + mCursorPosition = mSelectionStart; + } + + insertChars(clipBuf, dStrlen(clipBuf), mCursorPosition); + } + return true; + } + + default: + break; + } + } + else if ( event.modifier & SI_SHIFT ) + { + switch ( event.keyCode ) + { + case KEY_TAB: + return( Parent::onKeyDown( event ) ); + default: + break; + } + } + else if ( event.modifier == 0 ) + { + switch (event.keyCode) + { + // Escape: + case KEY_ESCAPE: + if ( mEscapeCommand[0] ) + { + Con::evaluate( mEscapeCommand ); + return( true ); + } + return( Parent::onKeyDown( event ) ); + + // Deletion + case KEY_BACKSPACE: + case KEY_DELETE: + handleDeleteKeys(event); + return true; + + // Cursor movement + case KEY_LEFT: + case KEY_RIGHT: + case KEY_UP: + case KEY_DOWN: + case KEY_HOME: + case KEY_END: + handleMoveKeys(event); + return true; + + // Special chars... + case KEY_TAB: + // insert 3 spaces + if (mSelectionActive == true) + { + mSelectionActive = false; + deleteChars(mSelectionStart, mSelectionEnd); + mCursorPosition = mSelectionStart; + } + insertChars( "\t", 1, mCursorPosition ); + return true; + + case KEY_RETURN: + // insert carriage return + if (mSelectionActive == true) + { + mSelectionActive = false; + deleteChars(mSelectionStart, mSelectionEnd); + mCursorPosition = mSelectionStart; + } + insertChars( "\n", 1, mCursorPosition ); + return true; + + default: + break; + } + } + + // If it's a type-able key event, eat it. + // + // This is pretty lame since we aren't checking if numlock is pressed + // or for any modifier keys which CAN affect if its REALLY an type-able + // character. + // + // Probably this information should be sent from above, like "is char input + // event". In the mean time, changed this to check STATE_LOWER instead of + // STATE_UPPER because numpad keys have no STATE_UPPER value. + if ( event.keyCode && Input::getAscii( event.keyCode, STATE_LOWER ) ) + return true; + + if ( (mFont && mFont->isValidChar(event.ascii)) || (!mFont && event.ascii != 0) ) + { + // Normal ascii keypress. Go ahead and add the chars... + if (mSelectionActive == true) + { + mSelectionActive = false; + deleteChars(mSelectionStart, mSelectionEnd); + mCursorPosition = mSelectionStart; + } + + UTF8 *outString = NULL; + U32 outStringLen = 0; + +#ifdef TORQUE_UNICODE + + UTF16 inData[2] = { event.ascii, 0 }; + StringBuffer inBuff(inData); + + FrameTemp outBuff(4); + inBuff.getCopy8(outBuff, 4); + + outString = outBuff; + outStringLen = dStrlen(outBuff); +#else + char ascii = char(event.ascii); + outString = &ascii; + outStringLen = 1; +#endif + + insertChars(outString, outStringLen, mCursorPosition); + mVertMoveAnchorValid = false; + return true; + } + + // Otherwise, let the parent have the event... + return Parent::onKeyDown(event); +} + + +//-------------------------------------- +void GuiMLTextEditCtrl::handleDeleteKeys(const GuiEvent& event) +{ + if ( isSelectionActive() ) + { + mSelectionActive = false; + deleteChars(mSelectionStart, mSelectionEnd+1); + mCursorPosition = mSelectionStart; + } + else + { + switch ( event.keyCode ) + { + case KEY_BACKSPACE: + if (mCursorPosition != 0) + { + // delete one character left + deleteChars(mCursorPosition-1, mCursorPosition); + setUpdate(); + } + break; + + case KEY_DELETE: + if (mCursorPosition != mTextBuffer.length()) + { + // delete one character right + deleteChars(mCursorPosition, mCursorPosition+1); + setUpdate(); + } + break; + + default: + AssertFatal(false, "Unknown key code received!"); + } + } +} + + +//-------------------------------------- +void GuiMLTextEditCtrl::handleMoveKeys(const GuiEvent& event) +{ + if ( event.modifier & SI_SHIFT ) + return; + + mSelectionActive = false; + + switch ( event.keyCode ) + { + case KEY_LEFT: + mVertMoveAnchorValid = false; + // move one left + if ( mCursorPosition != 0 ) + { + mCursorPosition--; + setUpdate(); + } + break; + + case KEY_RIGHT: + mVertMoveAnchorValid = false; + // move one right + if ( mCursorPosition != mTextBuffer.length() ) + { + mCursorPosition++; + setUpdate(); + } + break; + + case KEY_UP: + case KEY_DOWN: + { + Line* walk; + for ( walk = mLineList; walk->next; walk = walk->next ) + { + if ( mCursorPosition <= ( walk->textStart + walk->len ) ) + break; + } + + if ( !walk ) + return; + + if ( event.keyCode == KEY_UP ) + { + if ( walk == mLineList ) + return; + } + else if ( walk->next == NULL ) + return; + + Point2I newPos; + newPos.set( 0, walk->y ); + + // Find the x-position: + if ( !mVertMoveAnchorValid ) + { + Point2I cursorTopP, cursorBottomP; + ColorI color; + getCursorPositionAndColor(cursorTopP, cursorBottomP, color); + mVertMoveAnchor = cursorTopP.x; + mVertMoveAnchorValid = true; + } + + newPos.x = mVertMoveAnchor; + + // Set the new y-position: + if (event.keyCode == KEY_UP) + newPos.y--; + else + newPos.y += (walk->height + 1); + + if (setCursorPosition(getTextPosition(newPos))) + mVertMoveAnchorValid = false; + break; + } + + case KEY_HOME: + case KEY_END: + { + mVertMoveAnchorValid = false; + Line* walk; + for (walk = mLineList; walk->next; walk = walk->next) + { + if (mCursorPosition <= (walk->textStart + walk->len)) + break; + } + + if (walk) + { + if (event.keyCode == KEY_HOME) + { + //place the cursor at the beginning of the first atom if there is one + if (walk->atomList) + mCursorPosition = walk->atomList->textStart; + else + mCursorPosition = walk->textStart; + } + else + { + mCursorPosition = walk->textStart; + mCursorPosition += walk->len; + } + setUpdate(); + } + break; + } + + default: + AssertFatal(false, "Unknown move key code was received!"); + } + + ensureCursorOnScreen(); +} + +//-------------------------------------------------------------------------- +void GuiMLTextEditCtrl::onRender(Point2I offset, const RectI& updateRect) +{ + Parent::onRender(offset, updateRect); + + // We are the first responder, draw our cursor in the appropriate position... + if (isFirstResponder()) + { + Point2I top, bottom; + ColorI color; + getCursorPositionAndColor(top, bottom, color); + GFX->getDrawUtil()->drawLine(top + offset, bottom + offset, mProfile->mCursorColor); + } +} + diff --git a/gui/controls/guiMLTextEditCtrl.h b/gui/controls/guiMLTextEditCtrl.h new file mode 100644 index 0000000..c1ff12a --- /dev/null +++ b/gui/controls/guiMLTextEditCtrl.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMLTEXTEDITCTRL_H_ +#define _GUIMLTEXTEDITCTRL_H_ + +#ifndef _GUIMLTEXTCTRL_H_ +#include "gui/controls/guiMLTextCtrl.h" +#endif + +class GuiMLTextEditCtrl : public GuiMLTextCtrl +{ + typedef GuiMLTextCtrl Parent; + + //-------------------------------------- Overrides + protected: + StringTableEntry mEscapeCommand; + + // Events + bool onKeyDown(const GuiEvent&event); + + // Event forwards + void handleMoveKeys(const GuiEvent&); + void handleDeleteKeys(const GuiEvent&); + + // rendering + void onRender(Point2I offset, const RectI &updateRect); + + public: + GuiMLTextEditCtrl(); + ~GuiMLTextEditCtrl(); + + virtual void setFirstResponder(); + virtual void onLoseFirstResponder(); + + bool onWake(); + bool resize(const Point2I &newPosition, const Point2I &newExtent); + + DECLARE_CONOBJECT(GuiMLTextEditCtrl); + DECLARE_DESCRIPTION( "A control that allows to edit multiple lines of text." ); + + static void initPersistFields(); +}; + +#endif // _H_GUIMLTEXTEDITCTRL_ diff --git a/gui/controls/guiMaterialCtrl.cpp b/gui/controls/guiMaterialCtrl.cpp new file mode 100644 index 0000000..f555520 --- /dev/null +++ b/gui/controls/guiMaterialCtrl.cpp @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiMaterialCtrl.h" + +#include "materials/baseMatInstance.h" +#include "materials/materialManager.h" +#include "materials/sceneData.h" +#include "core/util/safeDelete.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDevice.h" +#include "math/util/matrixSet.h" + +IMPLEMENT_CONOBJECT( GuiMaterialCtrl ); + +GuiMaterialCtrl::GuiMaterialCtrl() + : mMaterialInst( NULL ) +{ +} + +void GuiMaterialCtrl::initPersistFields() +{ + addGroup( "Material" ); + addProtectedField( "materialName", TypeStringFilename, Offset( mMaterialName, GuiMaterialCtrl ), &GuiMaterialCtrl::_setMaterial, &defaultProtectedGetFn, "" ); + endGroup( "Material" ); + + Parent::initPersistFields(); +} + +bool GuiMaterialCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + setActive( true ); + setMaterial( mMaterialName ); + + return true; +} + +void GuiMaterialCtrl::onSleep() +{ + SAFE_DELETE( mMaterialInst ); + + Parent::onSleep(); +} + +bool GuiMaterialCtrl::_setMaterial( void *obj, const char *data ) +{ + static_cast( obj )->setMaterial( data ); + + // Return false to keep the caller from setting the field. + return false; +} + +bool GuiMaterialCtrl::setMaterial( const String &materialName ) +{ + SAFE_DELETE( mMaterialInst ); + mMaterialName = materialName; + + if ( mMaterialName.isNotEmpty() && isAwake() ) + mMaterialInst = MATMGR->createMatInstance( mMaterialName, getGFXVertexFormat() ); + + return true; +} + +void GuiMaterialCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); +} + +void GuiMaterialCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + Parent::onRender( offset, updateRect ); + + if ( !mMaterialInst ) + return; + + // Draw a quad with the material assigned + GFXVertexBufferHandle verts( GFX, 4, GFXBufferTypeVolatile ); + verts.lock(); + + F32 screenLeft = updateRect.point.x; + F32 screenRight = (updateRect.point.x + updateRect.extent.x); + F32 screenTop = updateRect.point.y; + F32 screenBottom = (updateRect.point.y + updateRect.extent.y); + + const F32 fillConv = GFX->getFillConventionOffset(); + verts[0].point.set( screenLeft - fillConv, screenTop - fillConv, 0.f ); + verts[1].point.set( screenRight - fillConv, screenTop - fillConv, 0.f ); + verts[2].point.set( screenLeft - fillConv, screenBottom - fillConv, 0.f ); + verts[3].point.set( screenRight - fillConv, screenBottom - fillConv, 0.f ); + + verts[0].color = verts[1].color = verts[2].color = verts[3].color = ColorI( 255, 255, 255, 255 ); + + verts[0].texCoord.set( 0.0f, 0.0f ); + verts[1].texCoord.set( 1.0f, 0.0f ); + verts[2].texCoord.set( 0.0f, 1.0f ); + verts[3].texCoord.set( 1.0f, 1.0f ); + + verts.unlock(); + + GFX->setVertexBuffer( verts ); + + SceneGraphData sgd; + sgd.reset(); + + MatrixSet matSet; + matSet.setWorld(GFX->getWorldMatrix()); + matSet.setView(GFX->getViewMatrix()); + matSet.setProjection(GFX->getProjectionMatrix()); + + while( mMaterialInst->setupPass( NULL, sgd ) ) + { + mMaterialInst->setSceneInfo( NULL, sgd ); + mMaterialInst->setTransforms( matSet, NULL ); + GFX->drawPrimitive( GFXTriangleStrip, 0, 2 ); + } + + // Clean up + GFX->setShader( NULL ); + GFX->setTexture( 0, NULL ); +} + +ConsoleMethod( GuiMaterialCtrl, setMaterial, bool, 3, 3, "( string materialName )" + "Set the material to be displayed in the control." ) +{ + return object->setMaterial( argv[2] ); +} diff --git a/gui/controls/guiMaterialCtrl.h b/gui/controls/guiMaterialCtrl.h new file mode 100644 index 0000000..7e1d79e --- /dev/null +++ b/gui/controls/guiMaterialCtrl.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMATERIALCTRL_H_ +#define _GUIMATERIALCTRL_H_ + +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + +class BaseMatInstance; + + +/// +class GuiMaterialCtrl : public GuiContainer +{ +private: + typedef GuiContainer Parent; + +protected: + + String mMaterialName; + + BaseMatInstance *mMaterialInst; + + static bool _setMaterial( void *obj, const char *data ); + +public: + + GuiMaterialCtrl(); + + // ConsoleObject + static void initPersistFields(); + void inspectPostApply(); + + DECLARE_CONOBJECT(GuiMaterialCtrl); + DECLARE_CATEGORY( "Gui Editor" ); + + // GuiControl + bool onWake(); + void onSleep(); + + bool setMaterial( const String &materialName ); + + void onRender( Point2I offset, const RectI &updateRect ); +}; + +#endif // _GUIMATERIALCTRL_H_ diff --git a/gui/controls/guiPopUpCtrl.cpp b/gui/controls/guiPopUpCtrl.cpp new file mode 100644 index 0000000..c00ee43 --- /dev/null +++ b/gui/controls/guiPopUpCtrl.cpp @@ -0,0 +1,1519 @@ +// Revision History: +// December 31, 2003 David Wyand Changed a bunch of stuff. Search for DAW below +// and make better notes here and in the changes doc. +// May 19, 2004 David Wyand Made changes to allow for a coloured rectangle to be +// displayed to the left of the text in the popup. +// May 27, 2004 David Wyand Added a check for mReverseTextList to see if we should +// reverse the text list if we must render it above +// the control. NOTE: there are some issues with setting +// the selected ID using the 'setSelected' command externally +// and the list is rendered in reverse. It seems that the +// entry ID's also get reversed. +// November 16, 2005 David Wyand Added the method setNoneSelected() to set none of the +// items as selected. Use this over setSelected(-1); when +// there are negative IDs in the item list. This also +// includes the setNoneSelected console method. +// +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/core/guiCanvas.h" +#include "gui/controls/guiPopUpCtrl.h" +#include "console/consoleTypes.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" + +static ColorI colorWhite(255,255,255); // Added + +// Function to return the number of columns in 'string' given delimeters in 'set' +static U32 getColumnCount(const char *string, const char *set) +{ + U32 count = 0; + U8 last = 0; + while(*string) + { + last = *string++; + + for(U32 i =0; set[i]; i++) + { + if(last == set[i]) + { + count++; + last = 0; + break; + } + } + } + if(last) + count++; + return count; +} + +// Function to return the 'index' column from 'string' given delimeters in 'set' +static const char *getColumn(const char *string, char* returnbuff, U32 index, const char *set) +{ + U32 sz; + while(index--) + { + if(!*string) + return ""; + sz = dStrcspn(string, set); + if (string[sz] == 0) + return ""; + string += (sz + 1); + } + sz = dStrcspn(string, set); + if (sz == 0) + return ""; + char *ret = returnbuff; + dStrncpy(ret, string, sz); + ret[sz] = '\0'; + return ret; +} + +GuiPopUpBackgroundCtrl::GuiPopUpBackgroundCtrl(GuiPopUpMenuCtrl *ctrl, GuiPopupTextListCtrl *textList) +{ + mPopUpCtrl = ctrl; + mTextList = textList; +} + +void GuiPopUpBackgroundCtrl::onMouseDown(const GuiEvent &event) +{ + // mTextList->setSelectedCell(Point2I(-1,-1)); // Removed + mPopUpCtrl->mBackgroundCancel = true; // Set that the user didn't click within the text list. Replaces the line above. + mPopUpCtrl->closePopUp(); +} + +//------------------------------------------------------------------------------ +GuiPopupTextListCtrl::GuiPopupTextListCtrl() +{ + mPopUpCtrl = NULL; +} + + +//------------------------------------------------------------------------------ +GuiPopupTextListCtrl::GuiPopupTextListCtrl(GuiPopUpMenuCtrl *ctrl) +{ + mPopUpCtrl = ctrl; +} + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +//void GuiPopUpTextListCtrl::onCellSelected( Point2I /*cell*/ ) +//{ +// // Do nothing, the parent control will take care of everything... +//} +void GuiPopupTextListCtrl::onCellSelected( Point2I cell ) +{ + // The old function is above. This new one will only call the the select + // functions if we were not cancelled by a background click. + + // Check if we were cancelled by the user clicking on the Background ie: anywhere + // other than within the text list. + if(mPopUpCtrl->mBackgroundCancel) + return; + + if( isMethod( "onSelect" ) ) + Con::executef(this, "onSelect", Con::getFloatArg(cell.x), Con::getFloatArg(cell.y)); + + //call the console function + execConsoleCallback(); + //if (mConsoleCommand[0]) + // Con::evaluate(mConsoleCommand, false); + +} + +//------------------------------------------------------------------------------ +bool GuiPopupTextListCtrl::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, don't process the input: + if ( !mVisible || !mActive || !mAwake ) + return false; + + //see if the key down is a or not + if ( event.modifier == 0 ) + { + if ( event.keyCode == KEY_RETURN ) + { + mPopUpCtrl->closePopUp(); + return true; + } + else if ( event.keyCode == KEY_ESCAPE ) + { + mSelectedCell.set( -1, -1 ); + mPopUpCtrl->closePopUp(); + return true; + } + } + + //otherwise, pass the event to it's parent + return Parent::onKeyDown(event); +} + +void GuiPopupTextListCtrl::onMouseDown(const GuiEvent &event) +{ + // Moved to onMouseUp in order to capture the mouse for the entirety of the click. ADL. + // This also has the side effect of allowing the mouse to be held down and a selection made. + //Parent::onMouseDown(event); + //mPopUpCtrl->closePopUp(); +} + +void GuiPopupTextListCtrl::onMouseUp(const GuiEvent &event) +{ + Parent::onMouseDown(event); + mPopUpCtrl->closePopUp(); + Parent::onMouseUp(event); +} + +//------------------------------------------------------------------------------ +void GuiPopupTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + Point2I size; + getCellSize( size ); + + // Render a background color for the cell + if ( mouseOver ) + { + RectI cellR( offset.x, offset.y, size.x, size.y ); + GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorHL ); + + } + else if ( selected ) + { + RectI cellR( offset.x, offset.y, size.x, size.y ); + GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorSEL ); + } + + // Define the default x offset for the text + U32 textXOffset = offset.x + mProfile->mTextOffset.x; + + // Do we also draw a colored box beside the text? + ColorI boxColor; + bool drawbox = mPopUpCtrl->getColoredBox( boxColor, mList[cell.y].id); + if(drawbox) + { + Point2I coloredboxsize(15,10); + RectI r(offset.x + mProfile->mTextOffset.x, offset.y+2, coloredboxsize.x, coloredboxsize.y); + GFX->getDrawUtil()->drawRectFill( r, boxColor); + GFX->getDrawUtil()->drawRect( r, ColorI(0,0,0)); + + textXOffset += coloredboxsize.x + mProfile->mTextOffset.x; + } + + ColorI fontColor; + mPopUpCtrl->getFontColor( fontColor, mList[cell.y].id, selected, mouseOver ); + + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + //GFX->drawText( mFont, Point2I( offset.x + 4, offset.y ), mList[cell.y].text ); + + // Get the number of columns in the cell + S32 colcount = getColumnCount(mList[cell.y].text, "\t"); + + // Are there two or more columns? + if(colcount >= 2) + { + char buff[256]; + + // Draw the first column + getColumn(mList[cell.y].text, buff, 0, "\t"); + GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), buff ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. + + // Draw the second column to the right + getColumn(mList[cell.y].text, buff, 1, "\t"); + S32 txt_w = mFont->getStrWidth(buff); + + GFX->getDrawUtil()->drawText( mFont, Point2I( offset.x+size.x-mProfile->mTextOffset.x-txt_w, offset.y ), buff ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. + + } else + { + GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), mList[cell.y].text ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. + } +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +IMPLEMENT_CONOBJECT(GuiPopUpMenuCtrl); + +GuiPopUpMenuCtrl::GuiPopUpMenuCtrl(void) +{ + VECTOR_SET_ASSOCIATION(mEntries); + VECTOR_SET_ASSOCIATION(mSchemes); + + mSelIndex = -1; + mActive = true; + mMaxPopupHeight = 200; + mScrollDir = GuiScrollCtrl::None; + mScrollCount = 0; + mLastYvalue = 0; + mIncValue = 0; + mRevNum = 0; + mInAction = false; + mMouseOver = false; // Added + mRenderScrollInNA = false; // Added + mBackgroundCancel = false; // Added + mReverseTextList = false; // Added - Don't reverse text list if displaying up + mBitmapName = StringTable->insert(""); // Added + mBitmapBounds.set(16, 16); // Added + mIdMax = -1; +} + +//------------------------------------------------------------------------------ +GuiPopUpMenuCtrl::~GuiPopUpMenuCtrl() +{ +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::initPersistFields(void) +{ + addField("maxPopupHeight", TypeS32, Offset(mMaxPopupHeight, GuiPopUpMenuCtrl)); + addField("sbUsesNAColor", TypeBool, Offset(mRenderScrollInNA, GuiPopUpMenuCtrl)); + addField("reverseTextList", TypeBool, Offset(mReverseTextList, GuiPopUpMenuCtrl)); + addField("bitmap", TypeFilename, Offset(mBitmapName, GuiPopUpMenuCtrl)); + addField("bitmapBounds", TypePoint2I, Offset(mBitmapBounds, GuiPopUpMenuCtrl)); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrl, add, void, 3, 5, "(string name, int idNum, int scheme=0)") +{ + if ( argc == 4 ) + object->addEntry(argv[2],dAtoi(argv[3])); + if ( argc == 5 ) + object->addEntry(argv[2],dAtoi(argv[3]),dAtoi(argv[4])); + else + object->addEntry(argv[2]); +} + +ConsoleMethod( GuiPopUpMenuCtrl, addScheme, void, 6, 6, "(int id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL)") +{ + ColorI fontColor, fontColorHL, fontColorSEL; + U32 r, g, b; + char buf[64]; + + dStrcpy( buf, argv[3] ); + char* temp = dStrtok( buf, " \0" ); + r = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + g = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + b = temp ? dAtoi( temp ) : 0; + fontColor.set( r, g, b ); + + dStrcpy( buf, argv[4] ); + temp = dStrtok( buf, " \0" ); + r = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + g = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + b = temp ? dAtoi( temp ) : 0; + fontColorHL.set( r, g, b ); + + dStrcpy( buf, argv[5] ); + temp = dStrtok( buf, " \0" ); + r = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + g = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + b = temp ? dAtoi( temp ) : 0; + fontColorSEL.set( r, g, b ); + + object->addScheme( dAtoi( argv[2] ), fontColor, fontColorHL, fontColorSEL ); +} + +ConsoleMethod( GuiPopUpMenuCtrl, setText, void, 3, 3, "(string text)") +{ + object->setText(argv[2]); +} + +ConsoleMethod( GuiPopUpMenuCtrl, getText, const char*, 2, 2, "") +{ + return object->getText(); +} + +ConsoleMethod( GuiPopUpMenuCtrl, clear, void, 2, 2, "Clear the popup list.") +{ + object->clear(); +} + +ConsoleMethod(GuiPopUpMenuCtrl, sort, void, 2, 2, "Sort the list alphabetically.") +{ + object->sort(); +} + +// Added to sort the entries by ID +ConsoleMethod(GuiPopUpMenuCtrl, sortID, void, 2, 2, "Sort the list by ID.") +{ + object->sortID(); +} + +ConsoleMethod( GuiPopUpMenuCtrl, forceOnAction, void, 2, 2, "") +{ + object->onAction(); +} + +ConsoleMethod( GuiPopUpMenuCtrl, forceClose, void, 2, 2, "") +{ + object->closePopUp(); +} + +ConsoleMethod( GuiPopUpMenuCtrl, getSelected, S32, 2, 2, "") +{ + return object->getSelected(); +} + +ConsoleMethod( GuiPopUpMenuCtrl, setSelected, void, 3, 4, "(int id, [scriptCallback=true])") +{ + if( argc > 3 ) + object->setSelected( dAtoi( argv[2] ), dAtob( argv[3] ) ); + else + object->setSelected( dAtoi( argv[2] ) ); +} + +ConsoleMethod( GuiPopUpMenuCtrl, setFirstSelected, void, 2, 3, "([scriptCallback=true])") +{ + if( argc > 2 ) + object->setFirstSelected( dAtob( argv[2] ) ); + else + object->setFirstSelected(); +} + +ConsoleMethod( GuiPopUpMenuCtrl, setNoneSelected, void, 2, 2, "") +{ + object->setNoneSelected(); +} + +ConsoleMethod( GuiPopUpMenuCtrl, getTextById, const char*, 3, 3, "(int id)") +{ + return(object->getTextById(dAtoi(argv[2]))); +} + +ConsoleMethod( GuiPopUpMenuCtrl, setEnumContent, void, 4, 4, "(string class, string enum)" + "This fills the popup with a classrep's field enumeration type info.\n\n" + "More of a helper function than anything. If console access to the field list is added, " + "at least for the enumerated types, then this should go away..") +{ + AbstractClassRep * classRep = AbstractClassRep::getClassList(); + + // walk the class list to get our class + while(classRep) + { + if(!dStricmp(classRep->getClassName(), argv[2])) + break; + classRep = classRep->getNextClass(); + } + + // get it? + if(!classRep) + { + Con::warnf(ConsoleLogEntry::General, "failed to locate class rep for '%s'", argv[2]); + return; + } + + // walk the fields to check for this one (findField checks StringTableEntry ptrs...) + U32 i; + for(i = 0; i < classRep->mFieldList.size(); i++) + if(!dStricmp(classRep->mFieldList[i].pFieldname, argv[3])) + break; + + // found it? + if(i == classRep->mFieldList.size()) + { + Con::warnf(ConsoleLogEntry::General, "failed to locate field '%s' for class '%s'", argv[3], argv[2]); + return; + } + + const AbstractClassRep::Field & field = classRep->mFieldList[i]; + + // check the type + if(field.type != TypeEnum) + { + Con::warnf(ConsoleLogEntry::General, "field '%s' is not an enumeration for class '%s'", argv[3], argv[2]); + return; + } + + AssertFatal(field.table, avar("enumeration '%s' for class '%s' with NULL ", argv[3], argv[2])); + + // fill it + for(i = 0; i < field.table->size; i++) + object->addEntry(field.table->table[i].label, field.table->table[i].index); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrl, findText, S32, 3, 3, "(string text)" + "Returns the position of the first entry containing the specified text.") +{ + return( object->findText( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrl, size, S32, 2, 2, "Get the size of the menu - the number of entries in it.") +{ + return( object->getNumEntries() ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrl, replaceText, void, 3, 3, "(bool doReplaceText)") +{ + object->replaceText(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------------ +// Added +bool GuiPopUpMenuCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + // Set the bitmap for the popup. + setBitmap( mBitmapName ); + + // Now update the Form Control's bitmap array, and possibly the child's too + mProfile->constructBitmapArray(); + + if ( mProfile->getChildrenProfile() ) + mProfile->getChildrenProfile()->constructBitmapArray(); + + return true; +} + +//------------------------------------------------------------------------------ +bool GuiPopUpMenuCtrl::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + mSelIndex = -1; + mReplaceText = true; + return true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onSleep() +{ + mTextureNormal = NULL; // Added + mTextureDepressed = NULL; // Added + Parent::onSleep(); + closePopUp(); // Tests in function. +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::clear() +{ + mEntries.setSize(0); + setText(""); + mSelIndex = -1; + mRevNum = 0; + mIdMax = -1; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::clearEntry( S32 entry ) +{ + if( entry == -1 ) + return; + + U32 i = 0; + for ( ; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == entry ) + break; + } + + mEntries.erase( i ); + + if( mEntries.size() <= 0 ) + { + mEntries.setSize(0); + setText(""); + mSelIndex = -1; + mRevNum = 0; + } + else + { + if( entry == mSelIndex ) + { + setText(""); + mSelIndex = -1; + } + else + { + mSelIndex--; + } + } +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrl, clearEntry, void, 3, 3, "(S32 entry)") +{ + object->clearEntry(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK textCompare(const void *a,const void *b) +{ + GuiPopUpMenuCtrl::Entry *ea = (GuiPopUpMenuCtrl::Entry *) (a); + GuiPopUpMenuCtrl::Entry *eb = (GuiPopUpMenuCtrl::Entry *) (b); + return (dStrnatcasecmp(ea->buf, eb->buf)); +} + +// Added to sort by entry ID +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK idCompare(const void *a,const void *b) +{ + GuiPopUpMenuCtrl::Entry *ea = (GuiPopUpMenuCtrl::Entry *) (a); + GuiPopUpMenuCtrl::Entry *eb = (GuiPopUpMenuCtrl::Entry *) (b); + return ( (ea->id < eb->id) ? -1 : ((ea->id > eb->id) ? 1 : 0) ); +} + +//------------------------------------------------------------------------------ +// Added +void GuiPopUpMenuCtrl::setBitmap( const char *name ) +{ + mBitmapName = StringTable->insert( name ); + if ( !isAwake() ) + return; + + if ( *mBitmapName ) + { + char buffer[1024]; + char *p; + dStrcpy(buffer, name); + p = buffer + dStrlen(buffer); + + dStrcpy(p, "_n"); + mTextureNormal = GFXTexHandle( (StringTableEntry)buffer, &GFXDefaultGUIProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__) ); + + dStrcpy(p, "_d"); + mTextureDepressed = GFXTexHandle( (StringTableEntry)buffer, &GFXDefaultGUIProfile, avar("%s() - mTextureDepressed (line %d)", __FUNCTION__, __LINE__) ); + if ( !mTextureDepressed ) + mTextureDepressed = mTextureNormal; + } + else + { + mTextureNormal = NULL; + mTextureDepressed = NULL; + } + setUpdate(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::sort() +{ + S32 size = mEntries.size(); + if( size > 0 ) + dQsort( mEntries.address(), size, sizeof(Entry), textCompare); +} + +// Added to sort by entry ID +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::sortID() +{ + S32 size = mEntries.size(); + if( size > 0 ) + dQsort( mEntries.address(), size, sizeof(Entry), idCompare); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::addEntry( const char *buf, S32 id, U32 scheme ) +{ + if( !buf ) + { + //Con::printf( "GuiPopupMenuCtrlEx::addEntry - Invalid buffer!" ); + return; + } + + // Ensure that there are no other entries with exactly the same name + for ( U32 i = 0; i < mEntries.size(); i++ ) + { + if ( dStrcmp( mEntries[i].buf, buf ) == 0 ) + return; + } + + // If we don't give an id, create one from mIdMax + if( id == -1 ) + id = mIdMax + 1; + + // Increase mIdMax when an id is greater than it + if( id > mIdMax ) + mIdMax = id; + + Entry e; + dStrcpy( e.buf, buf ); + e.id = id; + e.scheme = scheme; + + // see if there is a shortcut key + char * cp = dStrchr( e.buf, '~' ); + e.ascii = cp ? cp[1] : 0; + + // See if there is a colour box defined with the text + char *cb = dStrchr( e.buf, '|' ); + if ( cb ) + { + e.usesColorBox = true; + cb[0] = '\0'; + + char* red = &cb[1]; + cb = dStrchr(red, '|'); + cb[0] = '\0'; + char* green = &cb[1]; + cb = dStrchr(green, '|'); + cb[0] = '\0'; + char* blue = &cb[1]; + + U32 r = dAtoi(red); + U32 g = dAtoi(green); + U32 b = dAtoi(blue); + + e.colorbox = ColorI(r,g,b); + + } + else + { + e.usesColorBox = false; + } + + mEntries.push_back(e); + + if ( mInAction && mTl ) + { + // Add the new entry: + mTl->addEntry( e.id, e.buf ); + repositionPopup(); + } +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::addScheme( U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL ) +{ + if ( !id ) + return; + + Scheme newScheme; + newScheme.id = id; + newScheme.fontColor = fontColor; + newScheme.fontColorHL = fontColorHL; + newScheme.fontColorSEL = fontColorSEL; + + mSchemes.push_back( newScheme ); +} + +//------------------------------------------------------------------------------ +S32 GuiPopUpMenuCtrl::getSelected() +{ + if (mSelIndex == -1) + return 0; + return mEntries[mSelIndex].id; +} + +//------------------------------------------------------------------------------ +const char* GuiPopUpMenuCtrl::getTextById(S32 id) +{ + for ( U32 i = 0; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == id ) + return( mEntries[i].buf ); + } + + return( "" ); +} + +//------------------------------------------------------------------------------ +S32 GuiPopUpMenuCtrl::findText( const char* text ) +{ + for ( U32 i = 0; i < mEntries.size(); i++ ) + { + if ( dStrcmp( text, mEntries[i].buf ) == 0 ) + return( mEntries[i].id ); + } + return( -1 ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::setSelected(S32 id, bool bNotifyScript ) +{ + S32 i = 0; + for ( ; i < mEntries.size(); i++ ) + { + if ( id == mEntries[i].id ) + { + i = ( mRevNum > i ) ? mRevNum - i : i; + mSelIndex = i; + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText(mEntries[i].buf); + } + + // Now perform the popup action: + char idval[24]; + dSprintf( idval, sizeof(idval), "%d", mEntries[mSelIndex].id ); + if ( isMethod( "onSelect" ) && bNotifyScript ) + Con::executef( this, "onSelect", idval, mEntries[mSelIndex].buf ); + return; + } + } + + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText(""); + } + mSelIndex = -1; + + if ( isMethod( "onCancel" ) && bNotifyScript ) + Con::executef( this, "onCancel" ); + + if ( id == -1 ) + return; + + // Execute the popup console command: + if ( bNotifyScript ) + execConsoleCallback(); + //if ( mConsoleCommand[0] && bNotifyScript ) + // Con::evaluate( mConsoleCommand, false ); +} + +//------------------------------------------------------------------------------ +// Added to set the first item as selected. +void GuiPopUpMenuCtrl::setFirstSelected( bool bNotifyScript ) +{ + if ( mEntries.size() > 0 ) + { + mSelIndex = 0; + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText( mEntries[0].buf ); + } + + // Now perform the popup action: + char idval[24]; + dSprintf( idval, sizeof(idval), "%d", mEntries[mSelIndex].id ); + if ( isMethod( "onSelect" ) ) + Con::executef( this, "onSelect", idval, mEntries[mSelIndex].buf ); + + // Execute the popup console command: + if ( bNotifyScript ) + execConsoleCallback(); + } + else + { + if ( mReplaceText ) // Only change the displayed text if appropriate. + setText(""); + + mSelIndex = -1; + + if ( bNotifyScript ) + Con::executef( this, "onCancel" ); + } +} + +//------------------------------------------------------------------------------ +// Added to set no items as selected. +void GuiPopUpMenuCtrl::setNoneSelected() +{ + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText(""); + } + mSelIndex = -1; +} + +//------------------------------------------------------------------------------ +const char *GuiPopUpMenuCtrl::getScriptValue() +{ + return getText(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + TORQUE_UNUSED(updateRect); + Point2I localStart; + + if ( mScrollDir != GuiScrollCtrl::None ) + autoScroll(); + + RectI r( offset, getExtent() ); + if ( mInAction ) + { + S32 l = r.point.x, r2 = r.point.x + r.extent.x - 1; + S32 t = r.point.y, b = r.point.y + r.extent.y - 1; + + // Do we render a bitmap border or lines? + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // Render the fixed, filled in border + renderFixedBitmapBordersFilled(r, 3, mProfile ); + + } + else + { + //renderSlightlyLoweredBox(r, mProfile); + GFX->getDrawUtil()->drawRectFill( r, mProfile->mFillColor ); + } + + // Draw a bitmap over the background? + if ( mTextureDepressed ) + { + RectI rect(offset, mBitmapBounds); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureDepressed, rect ); + } + else if ( mTextureNormal ) + { + RectI rect(offset, mBitmapBounds); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureNormal, rect ); + } + + // Do we render a bitmap border or lines? + if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) + { + GFX->getDrawUtil()->drawLine( l, t, l, b, colorWhite ); + GFX->getDrawUtil()->drawLine( l, t, r2, t, colorWhite ); + GFX->getDrawUtil()->drawLine( l + 1, b, r2, b, mProfile->mBorderColor ); + GFX->getDrawUtil()->drawLine( r2, t + 1, r2, b - 1, mProfile->mBorderColor ); + } + + } + else + // TODO: Implement + // TODO: Add onMouseEnter() and onMouseLeave() and a definition of mMouseOver (see guiButtonBaseCtrl) for this to work. + if ( mMouseOver ) + { + S32 l = r.point.x, r2 = r.point.x + r.extent.x - 1; + S32 t = r.point.y, b = r.point.y + r.extent.y - 1; + + // Do we render a bitmap border or lines? + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // Render the fixed, filled in border + renderFixedBitmapBordersFilled( r, 2, mProfile ); + + } + else + { + GFX->getDrawUtil()->drawRectFill( r, mProfile->mFillColorHL ); + } + + // Draw a bitmap over the background? + if ( mTextureNormal ) + { + RectI rect( offset, mBitmapBounds ); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureNormal, rect ); + } + + // Do we render a bitmap border or lines? + if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) + { + GFX->getDrawUtil()->drawLine( l, t, l, b, colorWhite ); + GFX->getDrawUtil()->drawLine( l, t, r2, t, colorWhite ); + GFX->getDrawUtil()->drawLine( l + 1, b, r2, b, mProfile->mBorderColor ); + GFX->getDrawUtil()->drawLine( r2, t + 1, r2, b - 1, mProfile->mBorderColor ); + } + } + else + { + // Do we render a bitmap border or lines? + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // Render the fixed, filled in border + renderFixedBitmapBordersFilled( r, 1, mProfile ); + } + else + { + GFX->getDrawUtil()->drawRectFill( r, mProfile->mFillColorNA ); + } + + // Draw a bitmap over the background? + if ( mTextureNormal ) + { + RectI rect(offset, mBitmapBounds); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureNormal, rect ); + } + + // Do we render a bitmap border or lines? + if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) + { + GFX->getDrawUtil()->drawRect( r, mProfile->mBorderColorNA ); + } + } + // renderSlightlyRaisedBox(r, mProfile); // Used to be the only 'else' condition to mInAction above. + + S32 txt_w = mFont->getStrWidth(mText); + localStart.x = 0; + localStart.y = (getHeight() - (mFont->getHeight())) / 2; + + // align the horizontal + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + localStart.x = getWidth() - mBitmapBounds[2].extent.x - txt_w; + } + else + { + localStart.x = getWidth() - txt_w; + } + break; + case GuiControlProfile::CenterJustify: + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + localStart.x = (getWidth() - mBitmapBounds[2].extent.x - txt_w) / 2; + + } else + { + localStart.x = (getWidth() - txt_w) / 2; + } + break; + default: + // GuiControlProfile::LeftJustify + if ( txt_w > getWidth() ) + { + // The width of the text is greater than the width of the control. + // In this case we will right justify the text and leave some space + // for the down arrow. + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + localStart.x = getWidth() - mBitmapBounds[2].extent.x - txt_w; + } + else + { + localStart.x = getWidth() - txt_w - 12; + } + } + else + { + localStart.x = mProfile->mTextOffset.x; // Use mProfile->mTextOffset as a controlable margin for the control's text. + } + break; + } + + // Do we first draw a coloured box beside the text? + ColorI boxColor; + bool drawbox = getColoredBox( boxColor, mSelIndex); + if ( drawbox ) + { + Point2I coloredboxsize( 15, 10 ); + RectI r( offset.x + mProfile->mTextOffset.x, offset.y + ( (getHeight() - coloredboxsize.y ) / 2 ), coloredboxsize.x, coloredboxsize.y ); + GFX->getDrawUtil()->drawRectFill( r, boxColor); + GFX->getDrawUtil()->drawRect( r, ColorI(0,0,0)); + + localStart.x += coloredboxsize.x + mProfile->mTextOffset.x; + } + + // Draw the text + Point2I globalStart = localToGlobalCoord( localStart ); + ColorI fontColor = mActive ? ( mInAction ? mProfile->mFontColor : mProfile->mFontColorNA ) : mProfile->mFontColorNA; + GFX->getDrawUtil()->setBitmapModulation( fontColor ); // was: (mProfile->mFontColor); + + // Get the number of columns in the text + S32 colcount = getColumnCount( mText, "\t" ); + + // Are there two or more columns? + if ( colcount >= 2 ) + { + char buff[256]; + + // Draw the first column + getColumn( mText, buff, 0, "\t" ); + GFX->getDrawUtil()->drawText( mFont, globalStart, buff, mProfile->mFontColors ); + + // Draw the second column to the right + getColumn( mText, buff, 1, "\t" ); + S32 txt_w = mFont->getStrWidth( buff ); + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + Point2I textpos = localToGlobalCoord( Point2I( getWidth() - txt_w - mBitmapBounds[2].extent.x, localStart.y ) ); + GFX->getDrawUtil()->drawText( mFont, textpos, buff, mProfile->mFontColors ); + + } else + { + Point2I textpos = localToGlobalCoord( Point2I( getWidth() - txt_w - 12, localStart.y ) ); + GFX->getDrawUtil()->drawText( mFont, textpos, buff, mProfile->mFontColors ); + } + + } else + { + GFX->getDrawUtil()->drawText( mFont, globalStart, mText, mProfile->mFontColors ); + } + + // If we're rendering a bitmap border, then it will take care of the arrow. + if ( !(mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size()) ) + { + // Draw a triangle (down arrow) + S32 left = r.point.x + r.extent.x - 12; + S32 right = left + 8; + S32 middle = left + 4; + S32 top = r.extent.y / 2 + r.point.y - 4; + S32 bottom = top + 8; + + PrimBuild::color( mProfile->mFontColor ); + + PrimBuild::begin( GFXTriangleList, 3 ); + PrimBuild::vertex2fv( Point3F( (F32)left, (F32)top, 0.0f ) ); + PrimBuild::vertex2fv( Point3F( (F32)right, (F32)top, 0.0f ) ); + PrimBuild::vertex2fv( Point3F( (F32)middle, (F32)bottom, 0.0f ) ); + PrimBuild::end(); + } +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::closePopUp() +{ + if ( !mInAction ) + return; + + // Get the selection from the text list: + mSelIndex = mTl->getSelectedCell().y; + mSelIndex = ( mRevNum >= mSelIndex && mSelIndex != -1 ) ? mRevNum - mSelIndex : mSelIndex; + if ( mSelIndex != -1 ) + { + if ( mReplaceText ) + setText( mEntries[mSelIndex].buf ); + setIntVariable( mEntries[mSelIndex].id ); + } + + // Release the mouse: + mInAction = false; + mTl->mouseUnlock(); + + // Commented out below and moved to the end of the function. See the + // note below for the reason why. + /* + // Pop the background: + getRoot()->popDialogControl(mBackground); + + // Kill the popup: + mBackground->removeObject( mSc ); + mTl->deleteObject(); + mSc->deleteObject(); + mBackground->deleteObject(); + + // Set this as the first responder: + setFocus(); + */ + + // Now perform the popup action: + if ( mSelIndex != -1 ) + { + char idval[24]; + dSprintf( idval, sizeof(idval), "%d", mEntries[mSelIndex].id ); + if ( isMethod( "onSelect" ) ) + Con::executef( this, "onSelect", idval, mEntries[mSelIndex].buf ); + } + else if ( isMethod( "onCancel" ) ) + Con::executef( this, "onCancel" ); + + // Execute the popup console command: + execConsoleCallback(); + //if ( mConsoleCommand[0] ) + // Con::evaluate( mConsoleCommand, false ); + + // Reordered this pop dialog to be after the script select callback. When the + // background was popped it was causing all sorts of focus issues when + // suddenly the first text edit control took the focus before it came back + // to this popup. + + // Pop the background: + GuiCanvas *root = getRoot(); + if ( root ) + root->popDialogControl(mBackground); + + // Kill the popup: + mBackground->removeObject( mSc ); + mTl->deleteObject(); + mSc->deleteObject(); + mBackground->deleteObject(); + + // Set this as the first responder: + setFirstResponder(); +} + +//------------------------------------------------------------------------------ +bool GuiPopUpMenuCtrl::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, don't process the input: + if ( !mVisible || !mActive || !mAwake ) + return false; + + //see if the key down is a or not + if ( event.keyCode == KEY_RETURN && event.modifier == 0 ) + { + onAction(); + return true; + } + + //otherwise, pass the event to its parent + return Parent::onKeyDown( event ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onAction() +{ + GuiControl *canCtrl = getParent(); + + addChildren(); + + GuiCanvas *root = getRoot(); + Point2I windowExt = root->getExtent(); + + mBackground->resize( Point2I(0,0), root->getExtent() ); + + S32 textWidth = 0, width = getWidth(); + //const S32 menuSpace = 5; // Removed as no longer used. + const S32 textSpace = 2; + bool setScroll = false; + + for ( U32 i = 0; i < mEntries.size(); ++i ) + if ( S32(mFont->getStrWidth( mEntries[i].buf )) > textWidth ) + textWidth = mFont->getStrWidth( mEntries[i].buf ); + + //if(textWidth > getWidth()) + S32 sbWidth = mSc->getControlProfile()->mBorderThickness * 2 + mSc->scrollBarThickness(); // Calculate the scroll bar width + if ( textWidth > ( getWidth() - sbWidth-mProfile->mTextOffset.x - mSc->getChildMargin().x * 2 ) ) // The text draw area to test against is the width of the drop-down minus the scroll bar width, the text margin and the scroll bar child margins. + { + //textWidth +=10; + textWidth +=sbWidth + mProfile->mTextOffset.x + mSc->getChildMargin().x * 2; // The new width is the width of the text plus the scroll bar width plus the text margin size plus the scroll bar child margins. + width = textWidth; + + // If a child margin is not defined for the scroll control, let's add + // some space between the text and scroll control for readability + if(mSc->getChildMargin().x == 0) + width += textSpace; + } + + //mTl->setCellSize(Point2I(width, mFont->getHeight()+3)); + mTl->setCellSize(Point2I(width, mFont->getHeight() + textSpace)); // Modified the above line to use textSpace rather than the '3' as this is what is used below. + + for ( U32 j = 0; j < mEntries.size(); ++j ) + mTl->addEntry( mEntries[j].id, mEntries[j].buf ); + + Point2I pointInGC = canCtrl->localToGlobalCoord( getPosition() ); + Point2I scrollPoint( pointInGC.x, pointInGC.y + getHeight() ); + + //Calc max Y distance, so Scroll Ctrl will fit on window + //S32 maxYdis = windowExt.y - pointInGC.y - getHeight() - menuSpace; + S32 sbBorder = mSc->getControlProfile()->mBorderThickness * 2 + mSc->getChildMargin().y * 2; // Added to take into account the border thickness and the margin of the child controls of the scroll control when figuring out the size of the contained text list control + S32 maxYdis = windowExt.y - pointInGC.y - getHeight() - sbBorder; // - menuSpace; // Need to remove the border thickness from the contained control maximum extent and got rid of the 'menuspace' variable + + //If scroll bars need to be added + mRevNum = 0; // Added here rather than within the following 'if' statements. + if ( maxYdis < mTl->getHeight() + sbBorder ) // Instead of adding sbBorder, it was: 'textSpace' + { + //Should we pop menu list above the button + if ( maxYdis < pointInGC.y ) // removed: '- menuSpace)' from check to see if there is more space above the control than below. + { + if(mReverseTextList) // Added this check if we should reverse the text list. + reverseTextList(); + + maxYdis = pointInGC.y; // Was at the end: '- menuSpace;' + //Does the menu need a scroll bar + if ( maxYdis < mTl->getHeight() + sbBorder ) // Instead of adding sbBorder, it was: 'textSpace' + { + // Removed width recalculation for scroll bar as the scroll bar is already being taken into account. + //Calc for the width of the scroll bar + // if(textWidth >= width) + // width += 20; + // mTl->setCellSize(Point2I(width,mFont->getHeight() + textSpace)); + + //Pop menu list above the button + // scrollPoint.set(pointInGC.x, menuSpace - 1); // Removed as calculated outside the 'if', and was wrong to begin with + setScroll = true; + } + //No scroll bar needed + else + { + maxYdis = mTl->getHeight() + sbBorder; // Instead of adding sbBorder, it was: 'textSpace' + // scrollPoint.set(pointInGC.x, pointInGC.y - maxYdis -1); // Removed as calculated outside the 'if' and the '-1' at the end is wrong + } + + // Added the next two lines + scrollPoint.set(pointInGC.x, pointInGC.y - maxYdis); // Used to have the following on the end: '-1);' + } + //Scroll bar needed but Don't pop above button + else + { + //mRevNum = 0; // Commented out as it has been added at the beginning of this function + if ( mSelIndex >= 0 ) + mTl->setSelectedCell( Point2I( 0, mSelIndex ) ); // Added as we were not setting the selected cell if the list is displayed down. + + // Removed width recalculation for scroll bar as the scroll bar is already being taken into account. + //Calc for the width of the scroll bar + // if(textWidth >= width) + // width += 20; + // mTl->setCellSize(Point2I(width,mFont->getHeight() + textSpace)); + setScroll = true; + } + } + //No scroll bar needed + else + { + if ( mSelIndex >= 0 ) + mTl->setSelectedCell( Point2I( 0, mSelIndex ) ); // Added as we were not setting the selected cell if the list is displayed down. + + //maxYdis = mTl->getHeight() + textSpace; + maxYdis = mTl->getHeight() + sbBorder; // Added in the border thickness of the scroll control and removed the addition of textSpace + } + + RectI newBounds = mSc->getBounds(); + + //offset it from the background so it lines up properly + newBounds.point = mBackground->globalToLocalCoord( scrollPoint ); + + if ( newBounds.point.x + width > mBackground->getWidth() ) + if ( width - getWidth() > 0 ) + newBounds.point.x -= width - getWidth(); + + //mSc->getExtent().set(width-1, maxYdis); + newBounds.extent.set( width, maxYdis ); + mSc->setBounds( newBounds ); // Not sure why the '-1' above. + + mSc->registerObject(); + mTl->registerObject(); + mBackground->registerObject(); + + mSc->addObject( mTl ); + mBackground->addObject( mSc ); + + mBackgroundCancel = false; // Setup check if user clicked on the background instead of the text list (ie: didn't want to change their current selection). + + root->pushDialogControl( mBackground, 99 ); + + if ( setScroll ) + { + // Resize the text list + Point2I cellSize; + mTl->getCellSize( cellSize ); + cellSize.x = width - mSc->scrollBarThickness() - sbBorder; + mTl->setCellSize( cellSize ); + mTl->setWidth( cellSize.x ); + + if ( mSelIndex ) + mTl->scrollCellVisible( Point2I( 0, mSelIndex ) ); + else + mTl->scrollCellVisible( Point2I( 0, 0 ) ); + } + + mTl->setFirstResponder(); + + mInAction = true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::addChildren() +{ + // Create Text List. + mTl = new GuiPopupTextListCtrl( this ); + AssertFatal( mTl, "Failed to create the GuiPopUpTextListCtrl for the PopUpMenu" ); + // Use the children's profile rather than the parent's profile, if it exists. + mTl->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile ); + mTl->setField("noDuplicates", "false"); + + mSc = new GuiScrollCtrl; + AssertFatal( mSc, "Failed to create the GuiScrollCtrl for the PopUpMenu" ); + GuiControlProfile *prof; + if ( Sim::findObject( "GuiScrollProfile", prof ) ) + { + mSc->setControlProfile( prof ); + } + else + { + // Use the children's profile rather than the parent's profile, if it exists. + mSc->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile ); + } + + mSc->setField( "hScrollBar", "AlwaysOff" ); + mSc->setField( "vScrollBar", "dynamic" ); + //if(mRenderScrollInNA) // Force the scroll control to render using fillColorNA rather than fillColor + // mSc->mUseNABackground = true; + + mBackground = new GuiPopUpBackgroundCtrl( this, mTl ); + AssertFatal( mBackground, "Failed to create the GuiBackgroundCtrl for the PopUpMenu" ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::repositionPopup() +{ + if ( !mInAction || !mSc || !mTl ) + return; + + // I'm not concerned with this right now... +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::reverseTextList() +{ + mTl->clear(); + for ( S32 i = mEntries.size()-1; i >= 0; --i ) + mTl->addEntry( mEntries[i].id, mEntries[i].buf ); + + // Don't lose the selected cell: + if ( mSelIndex >= 0 ) + mTl->setSelectedCell( Point2I( 0, mEntries.size() - mSelIndex - 1 ) ); + + mRevNum = mEntries.size() - 1; +} + +//------------------------------------------------------------------------------ +bool GuiPopUpMenuCtrl::getFontColor( ColorI &fontColor, S32 id, bool selected, bool mouseOver ) +{ + U32 i; + Entry* entry = NULL; + for ( i = 0; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == id ) + { + entry = &mEntries[i]; + break; + } + } + + if ( !entry ) + return( false ); + + if ( entry->scheme != 0 ) + { + // Find the entry's color scheme: + for ( i = 0; i < mSchemes.size(); i++ ) + { + if ( mSchemes[i].id == entry->scheme ) + { + fontColor = selected ? mSchemes[i].fontColorSEL : mouseOver ? mSchemes[i].fontColorHL : mSchemes[i].fontColor; + return( true ); + } + } + } + + // Default color scheme... + fontColor = selected ? mProfile->mFontColorSEL : mouseOver ? mProfile->mFontColorHL : mProfile->mFontColorNA; // Modified the final color choice from mProfile->mFontColor to mProfile->mFontColorNA + + return( true ); +} + +//------------------------------------------------------------------------------ +// Added +bool GuiPopUpMenuCtrl::getColoredBox( ColorI &fontColor, S32 id ) +{ + U32 i; + Entry* entry = NULL; + for ( i = 0; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == id ) + { + entry = &mEntries[i]; + break; + } + } + + if ( !entry ) + return false; + + if ( entry->usesColorBox == false ) + return false; + + fontColor = entry->colorbox; + + return true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onMouseDown( const GuiEvent &event ) +{ + TORQUE_UNUSED(event); + onAction(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::onMouseUp( const GuiEvent &event ) +{ + TORQUE_UNUSED(event); +} + +//------------------------------------------------------------------------------ +// Added +void GuiPopUpMenuCtrl::onMouseEnter( const GuiEvent &event ) +{ + mMouseOver = true; +} + +//------------------------------------------------------------------------------ +// Added +void GuiPopUpMenuCtrl::onMouseLeave( const GuiEvent &event ) +{ + mMouseOver = false; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::setupAutoScroll( const GuiEvent &event ) +{ + GuiControl *parent = getParent(); + if ( !parent ) + return; + + Point2I mousePt = mSc->globalToLocalCoord( event.mousePoint ); + + mEventSave = event; + + if ( mLastYvalue != mousePt.y ) + { + mScrollDir = GuiScrollCtrl::None; + if ( mousePt.y > mSc->getHeight() || mousePt.y < 0 ) + { + S32 topOrBottom = ( mousePt.y > mSc->getHeight() ) ? 1 : 0; + mSc->scrollTo( 0, topOrBottom ); + return; + } + + F32 percent = (F32)mousePt.y / (F32)mSc->getHeight(); + if ( percent > 0.7f && mousePt.y > mLastYvalue ) + { + mIncValue = percent - 0.5f; + mScrollDir = GuiScrollCtrl::DownArrow; + } + else if ( percent < 0.3f && mousePt.y < mLastYvalue ) + { + mIncValue = 0.5f - percent; + mScrollDir = GuiScrollCtrl::UpArrow; + } + mLastYvalue = mousePt.y; + } +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::autoScroll() +{ + mScrollCount += mIncValue; + + while ( mScrollCount > 1 ) + { + mSc->autoScroll( mScrollDir ); + mScrollCount -= 1; + } + mTl->onMouseMove( mEventSave ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrl::replaceText(S32 boolVal) +{ + mReplaceText = boolVal; +} diff --git a/gui/controls/guiPopUpCtrl.h b/gui/controls/guiPopUpCtrl.h new file mode 100644 index 0000000..21d4468 --- /dev/null +++ b/gui/controls/guiPopUpCtrl.h @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPOPUPCTRL_H_ +#define _GUIPOPUPCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif +#ifndef _GUITEXTLISTCTRL_H_ +#include "gui/controls/guiTextListCtrl.h" +#endif +#ifndef _GUIBUTTONCTRL_H_ +#include "gui/buttons/guiButtonCtrl.h" +#endif +#ifndef _GUIBACKGROUNDCTRL_H_ +#include "gui/controls/guiBackgroundCtrl.h" +#endif +#ifndef _GUISCROLLCTRL_H_ +#include "gui/containers/guiScrollCtrl.h" +#endif +class GuiPopUpMenuCtrl; +class GuiPopupTextListCtrl; + +class GuiPopUpBackgroundCtrl : public GuiControl +{ +protected: + GuiPopUpMenuCtrl *mPopUpCtrl; + GuiPopupTextListCtrl *mTextList; +public: + GuiPopUpBackgroundCtrl(GuiPopUpMenuCtrl *ctrl, GuiPopupTextListCtrl* textList); + void onMouseDown(const GuiEvent &event); +}; + +class GuiPopupTextListCtrl : public GuiTextListCtrl +{ + private: + typedef GuiTextListCtrl Parent; + +protected: + GuiPopUpMenuCtrl *mPopUpCtrl; + +public: + GuiPopupTextListCtrl(); // for inheritance + GuiPopupTextListCtrl(GuiPopUpMenuCtrl *ctrl); + + // GuiArrayCtrl overload: + void onCellSelected(Point2I cell); + + // GuiControl overloads: + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); +}; + +class GuiPopUpMenuCtrl : public GuiTextCtrl +{ + typedef GuiTextCtrl Parent; + +public: + struct Entry + { + char buf[256]; + S32 id; + U16 ascii; + U16 scheme; + bool usesColorBox; // Added + ColorI colorbox; // Added + }; + + struct Scheme + { + U32 id; + ColorI fontColor; + ColorI fontColorHL; + ColorI fontColorSEL; + }; + + bool mBackgroundCancel; // Added + +protected: + GuiPopupTextListCtrl *mTl; + GuiScrollCtrl *mSc; + GuiPopUpBackgroundCtrl *mBackground; + Vector mEntries; + Vector mSchemes; + S32 mSelIndex; + S32 mMaxPopupHeight; + F32 mIncValue; + F32 mScrollCount; + S32 mLastYvalue; + GuiEvent mEventSave; + S32 mRevNum; + bool mInAction; + bool mReplaceText; + bool mMouseOver; // Added + bool mRenderScrollInNA; // Added + bool mReverseTextList; // Added - Should we reverse the text list if we display up? + StringTableEntry mBitmapName; // Added + Point2I mBitmapBounds; // Added + GFXTexHandle mTextureNormal; // Added + GFXTexHandle mTextureDepressed; // Added + S32 mIdMax; + + virtual void addChildren(); + virtual void repositionPopup(); + +public: + GuiPopUpMenuCtrl(void); + ~GuiPopUpMenuCtrl(); + GuiScrollCtrl::Region mScrollDir; + bool onWake(); // Added + bool onAdd(); + void onSleep(); + void setBitmap(const char *name); // Added + void sort(); + void sortID(); // Added + void addEntry(const char *buf, S32 id = -1, U32 scheme = 0); + void addScheme(U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL); + void onRender(Point2I offset, const RectI &updateRect); + void onAction(); + virtual void closePopUp(); + void clear(); + void clearEntry( S32 entry ); // Added + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseEnter(const GuiEvent &event); // Added + void onMouseLeave(const GuiEvent &); // Added + void setupAutoScroll(const GuiEvent &event); + void autoScroll(); + bool onKeyDown(const GuiEvent &event); + void reverseTextList(); + bool getFontColor(ColorI &fontColor, S32 id, bool selected, bool mouseOver); + bool getColoredBox(ColorI &boxColor, S32 id); // Added + + S32 getSelected(); + void setSelected(S32 id, bool bNotifyScript = true); + void setFirstSelected(bool bNotifyScript = true); // Added + void setNoneSelected(); // Added + const char *getScriptValue(); + const char *getTextById(S32 id); + S32 findText( const char* text ); + S32 getNumEntries() { return( mEntries.size() ); } + void replaceText(S32); + + DECLARE_CONOBJECT(GuiPopUpMenuCtrl); + static void initPersistFields(void); + +}; + +#endif //_GUI_POPUPMENU_CTRL_H diff --git a/gui/controls/guiPopUpCtrlEx.cpp b/gui/controls/guiPopUpCtrlEx.cpp new file mode 100644 index 0000000..5e0a1e6 --- /dev/null +++ b/gui/controls/guiPopUpCtrlEx.cpp @@ -0,0 +1,1590 @@ +// Revision History: +// December 31, 2003 David Wyand Changed a bunch of stuff. Search for DAW below +// and make better notes here and in the changes doc. +// May 19, 2004 David Wyand Made changes to allow for a coloured rectangle to be +// displayed to the left of the text in the popup. +// May 27, 2004 David Wyand Added a check for mReverseTextList to see if we should +// reverse the text list if we must render it above +// the control. NOTE: there are some issues with setting +// the selected ID using the 'setSelected' command externally +// and the list is rendered in reverse. It seems that the +// entry ID's also get reversed. +// November 16, 2005 David Wyand Added the method setNoneSelected() to set none of the +// items as selected. Use this over setSelected(-1); when +// there are negative IDs in the item list. This also +// includes the setNoneSelected console method. +// +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/core/guiCanvas.h" +#include "gui/controls/guiPopUpCtrlEx.h" +#include "console/consoleTypes.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" + +static ColorI colorWhite(255,255,255); // Added + +// Function to return the number of columns in 'string' given delimeters in 'set' +static U32 getColumnCount(const char *string, const char *set) +{ + U32 count = 0; + U8 last = 0; + while(*string) + { + last = *string++; + + for(U32 i =0; set[i]; i++) + { + if(last == set[i]) + { + count++; + last = 0; + break; + } + } + } + if(last) + count++; + return count; +} + +// Function to return the 'index' column from 'string' given delimeters in 'set' +static const char *getColumn(const char *string, char* returnbuff, U32 index, const char *set) +{ + U32 sz; + while(index--) + { + if(!*string) + return ""; + sz = dStrcspn(string, set); + if (string[sz] == 0) + return ""; + string += (sz + 1); + } + sz = dStrcspn(string, set); + if (sz == 0) + return ""; + char *ret = returnbuff; + dStrncpy(ret, string, sz); + ret[sz] = '\0'; + return ret; +} + +GuiPopUpBackgroundCtrlEx::GuiPopUpBackgroundCtrlEx(GuiPopUpMenuCtrlEx *ctrl, GuiPopupTextListCtrlEx *textList) +{ + mPopUpCtrl = ctrl; + mTextList = textList; +} + +void GuiPopUpBackgroundCtrlEx::onMouseDown(const GuiEvent &event) +{ + // mTextList->setSelectedCell(Point2I(-1,-1)); // Removed + mPopUpCtrl->mBackgroundCancel = true; // Set that the user didn't click within the text list. Replaces the line above. + mPopUpCtrl->closePopUp(); +} + +//------------------------------------------------------------------------------ +GuiPopupTextListCtrlEx::GuiPopupTextListCtrlEx() +{ + mPopUpCtrl = NULL; +} + + +//------------------------------------------------------------------------------ +GuiPopupTextListCtrlEx::GuiPopupTextListCtrlEx(GuiPopUpMenuCtrlEx *ctrl) +{ + mPopUpCtrl = ctrl; +} + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +//void GuiPopUpTextListCtrl::onCellSelected( Point2I /*cell*/ ) +//{ +// // Do nothing, the parent control will take care of everything... +//} +void GuiPopupTextListCtrlEx::onCellSelected( Point2I cell ) +{ + // The old function is above. This new one will only call the the select + // functions if we were not cancelled by a background click. + + // Check if we were cancelled by the user clicking on the Background ie: anywhere + // other than within the text list. + if(mPopUpCtrl->mBackgroundCancel) + return; + + if( isMethod( "onSelect" ) ) + Con::executef(this, "onSelect", Con::getFloatArg(cell.x), Con::getFloatArg(cell.y)); + + //call the console function + execConsoleCallback(); + //if (mConsoleCommand[0]) + // Con::evaluate(mConsoleCommand, false); + +} + +bool GuiPopupTextListCtrlEx::hasCategories() +{ + for( S32 i = 0; i < mList.size(); i++) + { + if( mList[i].id == -1) + return true; + } + + return false; +} + +//------------------------------------------------------------------------------ +bool GuiPopupTextListCtrlEx::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, don't process the input: + if ( !mVisible || !mActive || !mAwake ) + return false; + + //see if the key down is a or not + if ( event.modifier == 0 ) + { + if ( event.keyCode == KEY_RETURN ) + { + mPopUpCtrl->closePopUp(); + return true; + } + else if ( event.keyCode == KEY_ESCAPE ) + { + mSelectedCell.set( -1, -1 ); + mPopUpCtrl->closePopUp(); + return true; + } + } + + //otherwise, pass the event to it's parent + return Parent::onKeyDown(event); +} + +void GuiPopupTextListCtrlEx::onMouseDown(const GuiEvent &event) +{ + // Moved to onMouseUp in order to capture the mouse for the entirety of the click. ADL. + // This also has the side effect of allowing the mouse to be held down and a selection made. + //Parent::onMouseDown(event); + //mPopUpCtrl->closePopUp(); +} + +void GuiPopupTextListCtrlEx::onMouseUp(const GuiEvent &event) +{ + Point2I pt = globalToLocalCoord(event.mousePoint); + pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y; + Point2I cell( + (pt.x < 0 ? -1 : pt.x / mCellSize.x), + (pt.y < 0 ? -1 : pt.y / mCellSize.y) + ); + + if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + if (mList[cell.y].id == -1) + return; + } + + Parent::onMouseDown(event); + mPopUpCtrl->closePopUp(); + Parent::onMouseUp(event); +} + +void GuiPopupTextListCtrlEx::onMouseMove( const GuiEvent &event ) +{ + if( !mPopUpCtrl || !mPopUpCtrl->isMethod("onHotTrackItem") ) + return Parent::onMouseMove( event ); + + Point2I pt = globalToLocalCoord(event.mousePoint); + pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y; + Point2I cell( (pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y) ); + + // Within Bounds? + if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + // Hot Track notification + Con::executef( mPopUpCtrl, "onHotTrackItem", Con::getIntArg(mList[cell.y].id) ); + else + // Hot Track -1 + Con::executef( mPopUpCtrl, "onHotTrackItem", Con::getIntArg(-1) ); + + // Call Parent + Parent::onMouseMove(event); +} + +//------------------------------------------------------------------------------ +void GuiPopupTextListCtrlEx::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + Point2I size; + getCellSize( size ); + + // Render a background color for the cell + if ( mouseOver ) + { + RectI cellR( offset.x, offset.y, size.x, size.y ); + GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorHL ); + } + else if ( selected ) + { + RectI cellR( offset.x, offset.y, size.x, size.y ); + GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorSEL ); + } + + // Define the default x offset for the text + U32 textXOffset = offset.x + mProfile->mTextOffset.x; + + // Do we also draw a colored box beside the text? + ColorI boxColor; + bool drawbox = mPopUpCtrl->getColoredBox( boxColor, mList[cell.y].id ); + if ( drawbox ) + { + Point2I coloredboxsize( 15, 10 ); + RectI r( offset.x + mProfile->mTextOffset.x, offset.y + 2, coloredboxsize.x, coloredboxsize.y ); + GFX->getDrawUtil()->drawRectFill( r, boxColor ); + GFX->getDrawUtil()->drawRect( r, ColorI( 0, 0, 0 ) ); + + textXOffset += coloredboxsize.x + mProfile->mTextOffset.x; + } + + // Render 'Group' background + if ( mList[cell.y].id == -1) + { + RectI cellR( offset.x, offset.y, size.x, size.y ); + GFX->getDrawUtil()->drawRectFill( cellR, mProfile->mFillColorHL ); + } + + ColorI fontColor; + mPopUpCtrl->getFontColor( fontColor, mList[cell.y].id, selected, mouseOver ); + + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + //GFX->drawText( mFont, Point2I( offset.x + 4, offset.y ), mList[cell.y].text ); + + // Get the number of columns in the cell + S32 colcount = getColumnCount(mList[cell.y].text, "\t"); + + // Are there two or more columns? + if(colcount >= 2) + { + char buff[256]; + + // Draw the first column + getColumn(mList[cell.y].text, buff, 0, "\t"); + GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), buff ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. + + // Draw the second column to the right + getColumn(mList[cell.y].text, buff, 1, "\t"); + S32 txt_w = mFont->getStrWidth(buff); + + GFX->getDrawUtil()->drawText( mFont, Point2I( offset.x+size.x-mProfile->mTextOffset.x-txt_w, offset.y ), buff ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. + + } else + { + if ((mList[cell.y].id == -1) || (!hasCategories())) + GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset, offset.y ), mList[cell.y].text ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. + else + GFX->getDrawUtil()->drawText( mFont, Point2I( textXOffset + 8, offset.y ), mList[cell.y].text ); // Used mTextOffset as a margin for the text list rather than the hard coded value of '4'. + } +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +IMPLEMENT_CONOBJECT(GuiPopUpMenuCtrlEx); + +GuiPopUpMenuCtrlEx::GuiPopUpMenuCtrlEx(void) +{ + VECTOR_SET_ASSOCIATION(mEntries); + VECTOR_SET_ASSOCIATION(mSchemes); + + mSelIndex = -1; + mActive = true; + mMaxPopupHeight = 200; + mScrollDir = GuiScrollCtrl::None; + mScrollCount = 0; + mLastYvalue = 0; + mIncValue = 0; + mRevNum = 0; + mInAction = false; + mMouseOver = false; // Added + mRenderScrollInNA = false; // Added + mBackgroundCancel = false; // Added + mReverseTextList = false; // Added - Don't reverse text list if displaying up + mBitmapName = StringTable->insert(""); // Added + mBitmapBounds.set(16, 16); // Added + mHotTrackItems = false; + mIdMax = -1; +} + +//------------------------------------------------------------------------------ +GuiPopUpMenuCtrlEx::~GuiPopUpMenuCtrlEx() +{ +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::initPersistFields(void) +{ + addField("maxPopupHeight", TypeS32, Offset(mMaxPopupHeight, GuiPopUpMenuCtrlEx)); + addField("sbUsesNAColor", TypeBool, Offset(mRenderScrollInNA, GuiPopUpMenuCtrlEx)); + addField("reverseTextList", TypeBool, Offset(mReverseTextList, GuiPopUpMenuCtrlEx)); + addField("bitmap", TypeFilename, Offset(mBitmapName, GuiPopUpMenuCtrlEx)); + addField("bitmapBounds", TypePoint2I, Offset(mBitmapBounds, GuiPopUpMenuCtrlEx)); + addField("hotTrackCallback", TypeBool, Offset(mHotTrackItems, GuiPopUpMenuCtrlEx), + "Whether to provide a 'onHotTrackItem' callback when a list item is hovered over"); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrlEx, add, void, 3, 5, "(string name, int idNum, int scheme=0)") +{ + if ( argc == 4 ) + object->addEntry(argv[2],dAtoi(argv[3])); + if ( argc == 5 ) + object->addEntry(argv[2],dAtoi(argv[3]),dAtoi(argv[4])); + else + object->addEntry(argv[2]); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, addCategory, void, 3, 3, "(string text)") +{ + object->addEntry(argv[2], -1, 0); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, addScheme, void, 6, 6, "(int id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL)") +{ + ColorI fontColor, fontColorHL, fontColorSEL; + U32 r, g, b; + char buf[64]; + + dStrcpy( buf, argv[3] ); + char* temp = dStrtok( buf, " \0" ); + r = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + g = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + b = temp ? dAtoi( temp ) : 0; + fontColor.set( r, g, b ); + + dStrcpy( buf, argv[4] ); + temp = dStrtok( buf, " \0" ); + r = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + g = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + b = temp ? dAtoi( temp ) : 0; + fontColorHL.set( r, g, b ); + + dStrcpy( buf, argv[5] ); + temp = dStrtok( buf, " \0" ); + r = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + g = temp ? dAtoi( temp ) : 0; + temp = dStrtok( NULL, " \0" ); + b = temp ? dAtoi( temp ) : 0; + fontColorSEL.set( r, g, b ); + + object->addScheme( dAtoi( argv[2] ), fontColor, fontColorHL, fontColorSEL ); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, setText, void, 3, 3, "(string text)") +{ + object->setText(argv[2]); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, getText, const char*, 2, 2, "") +{ + return object->getText(); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, clear, void, 2, 2, "Clear the popup list.") +{ + object->clear(); +} + +ConsoleMethod(GuiPopUpMenuCtrlEx, sort, void, 2, 2, "Sort the list alphabetically.") +{ + object->sort(); +} + +// Added to sort the entries by ID +ConsoleMethod(GuiPopUpMenuCtrlEx, sortID, void, 2, 2, "Sort the list by ID.") +{ + object->sortID(); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, forceOnAction, void, 2, 2, "") +{ + object->onAction(); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, forceClose, void, 2, 2, "") +{ + object->closePopUp(); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, getSelected, S32, 2, 2, "") +{ + return object->getSelected(); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, setSelected, void, 3, 4, "(int id, [scriptCallback=true])") +{ + if( argc > 3 ) + object->setSelected( dAtoi( argv[2] ), dAtob( argv[3] ) ); + else + object->setSelected( dAtoi( argv[2] ) ); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, setFirstSelected, void, 2, 3, "([scriptCallback=true])") +{ + if( argc > 2 ) + object->setFirstSelected( dAtob( argv[2] ) ); + else + object->setFirstSelected(); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, setNoneSelected, void, 2, 2, "") +{ + object->setNoneSelected(); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, getTextById, const char*, 3, 3, "(int id)") +{ + return(object->getTextById(dAtoi(argv[2]))); +} + +ConsoleMethod( GuiPopUpMenuCtrlEx, setEnumContent, void, 4, 4, "(string class, string enum)" + "This fills the popup with a classrep's field enumeration type info.\n\n" + "More of a helper function than anything. If console access to the field list is added, " + "at least for the enumerated types, then this should go away..") +{ + AbstractClassRep * classRep = AbstractClassRep::getClassList(); + + // walk the class list to get our class + while(classRep) + { + if(!dStricmp(classRep->getClassName(), argv[2])) + break; + classRep = classRep->getNextClass(); + } + + // get it? + if(!classRep) + { + Con::warnf(ConsoleLogEntry::General, "failed to locate class rep for '%s'", argv[2]); + return; + } + + // walk the fields to check for this one (findField checks StringTableEntry ptrs...) + U32 i; + for(i = 0; i < classRep->mFieldList.size(); i++) + if(!dStricmp(classRep->mFieldList[i].pFieldname, argv[3])) + break; + + // found it? + if(i == classRep->mFieldList.size()) + { + Con::warnf(ConsoleLogEntry::General, "failed to locate field '%s' for class '%s'", argv[3], argv[2]); + return; + } + + const AbstractClassRep::Field & field = classRep->mFieldList[i]; + + // check the type + if(field.type != TypeEnum) + { + Con::warnf(ConsoleLogEntry::General, "field '%s' is not an enumeration for class '%s'", argv[3], argv[2]); + return; + } + + AssertFatal(field.table, avar("enumeration '%s' for class '%s' with NULL ", argv[3], argv[2])); + + // fill it + for(i = 0; i < field.table->size; i++) + object->addEntry(field.table->table[i].label, field.table->table[i].index); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrlEx, findText, S32, 3, 3, "(string text)" + "Returns the id of the first entry containing the specified text.") +{ + return( object->findText( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrlEx, size, S32, 2, 2, "Get the size of the menu - the number of entries in it.") +{ + return( object->getNumEntries() ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrlEx, replaceText, void, 3, 3, "(bool doReplaceText)") +{ + object->replaceText(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------------ +// Added +bool GuiPopUpMenuCtrlEx::onWake() +{ + if ( !Parent::onWake() ) + return false; + + // Set the bitmap for the popup. + setBitmap( mBitmapName ); + + // Now update the Form Control's bitmap array, and possibly the child's too + mProfile->constructBitmapArray(); + + if ( mProfile->getChildrenProfile() ) + mProfile->getChildrenProfile()->constructBitmapArray(); + + return true; +} + +//------------------------------------------------------------------------------ +bool GuiPopUpMenuCtrlEx::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + mSelIndex = -1; + mReplaceText = true; + return true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::onSleep() +{ + mTextureNormal = NULL; // Added + mTextureDepressed = NULL; // Added + Parent::onSleep(); + closePopUp(); // Tests in function. +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::clear() +{ + mEntries.setSize(0); + setText(""); + mSelIndex = -1; + mRevNum = 0; + mIdMax = -1; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::clearEntry( S32 entry ) +{ + if( entry == -1 ) + return; + + U32 i = 0; + for ( ; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == entry ) + break; + } + + mEntries.erase( i ); + + if( mEntries.size() <= 0 ) + { + mEntries.setSize(0); + setText(""); + mSelIndex = -1; + mRevNum = 0; + } + else + { + if( entry == mSelIndex ) + { + setText(""); + mSelIndex = -1; + } + else + { + mSelIndex--; + } + } +} + +//------------------------------------------------------------------------------ +ConsoleMethod( GuiPopUpMenuCtrlEx, clearEntry, void, 3, 3, "(S32 entry)") +{ + object->clearEntry(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK textCompare(const void *a,const void *b) +{ + GuiPopUpMenuCtrlEx::Entry *ea = (GuiPopUpMenuCtrlEx::Entry *) (a); + GuiPopUpMenuCtrlEx::Entry *eb = (GuiPopUpMenuCtrlEx::Entry *) (b); + return (dStrnatcasecmp(ea->buf, eb->buf)); +} + +// Added to sort by entry ID +//------------------------------------------------------------------------------ +static S32 QSORT_CALLBACK idCompare(const void *a,const void *b) +{ + GuiPopUpMenuCtrlEx::Entry *ea = (GuiPopUpMenuCtrlEx::Entry *) (a); + GuiPopUpMenuCtrlEx::Entry *eb = (GuiPopUpMenuCtrlEx::Entry *) (b); + return ( (ea->id < eb->id) ? -1 : ((ea->id > eb->id) ? 1 : 0) ); +} + +//------------------------------------------------------------------------------ +// Added +void GuiPopUpMenuCtrlEx::setBitmap(const char *name) +{ + mBitmapName = StringTable->insert( name ); + if ( !isAwake() ) + return; + + if ( *mBitmapName ) + { + char buffer[1024]; + char *p; + dStrcpy(buffer, name); + p = buffer + dStrlen(buffer); + + dStrcpy(p, "_n"); + mTextureNormal = GFXTexHandle( (StringTableEntry)buffer, &GFXDefaultGUIProfile, avar("%s() - mTextureNormal (line %d)", __FUNCTION__, __LINE__) ); + + dStrcpy(p, "_d"); + mTextureDepressed = GFXTexHandle( (StringTableEntry)buffer, &GFXDefaultGUIProfile, avar("%s() - mTextureDepressed (line %d)", __FUNCTION__, __LINE__) ); + if ( !mTextureDepressed ) + mTextureDepressed = mTextureNormal; + } + else + { + mTextureNormal = NULL; + mTextureDepressed = NULL; + } + setUpdate(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::sort() +{ + S32 size = mEntries.size(); + if( size > 0 ) + dQsort( mEntries.address(), size, sizeof(Entry), textCompare); + + // Entries need to re-Id themselves + for( U32 i = 0; i < mEntries.size(); i++ ) + mEntries[i].id = i; +} + +// Added to sort by entry ID +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::sortID() +{ + S32 size = mEntries.size(); + if( size > 0 ) + dQsort( mEntries.address(), size, sizeof(Entry), idCompare); + + // Entries need to re-Id themselves + for( U32 i = 0; i < mEntries.size(); i++ ) + mEntries[i].id = i; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::addEntry(const char *buf, S32 id, U32 scheme) +{ + if( !buf ) + { + //Con::printf( "GuiPopupMenuCtrlEx::addEntry - Invalid buffer!" ); + return; + } + + // Ensure that there are no other entries with exactly the same name + for ( U32 i = 0; i < mEntries.size(); i++ ) + { + if ( dStrcmp( mEntries[i].buf, buf ) == 0 ) + return; + } + + // If we don't give an id, create one from mIdMax + if( id == -1 ) + id = mIdMax + 1; + + // Increase mIdMax when an id is greater than it + if( id > mIdMax ) + mIdMax = id; + + Entry e; + dStrcpy( e.buf, buf ); + e.id = id; + e.scheme = scheme; + + // see if there is a shortcut key + char * cp = dStrchr( e.buf, '~' ); + e.ascii = cp ? cp[1] : 0; + + // See if there is a colour box defined with the text + char *cb = dStrchr( e.buf, '|' ); + if ( cb ) + { + e.usesColorBox = true; + cb[0] = '\0'; + + char* red = &cb[1]; + cb = dStrchr(red, '|'); + cb[0] = '\0'; + char* green = &cb[1]; + cb = dStrchr(green, '|'); + cb[0] = '\0'; + char* blue = &cb[1]; + + U32 r = dAtoi(red); + U32 g = dAtoi(green); + U32 b = dAtoi(blue); + + e.colorbox = ColorI(r,g,b); + + } + else + { + e.usesColorBox = false; + } + + mEntries.push_back(e); + + if ( mInAction && mTl ) + { + // Add the new entry: + mTl->addEntry( e.id, e.buf ); + repositionPopup(); + } +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::addScheme( U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL ) +{ + if ( !id ) + return; + + Scheme newScheme; + newScheme.id = id; + newScheme.fontColor = fontColor; + newScheme.fontColorHL = fontColorHL; + newScheme.fontColorSEL = fontColorSEL; + + mSchemes.push_back( newScheme ); +} + +//------------------------------------------------------------------------------ +S32 GuiPopUpMenuCtrlEx::getSelected() +{ + if (mSelIndex == -1) + return 0; + return mEntries[mSelIndex].id; +} + +//------------------------------------------------------------------------------ +const char* GuiPopUpMenuCtrlEx::getTextById(S32 id) +{ + for ( U32 i = 0; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == id ) + return( mEntries[i].buf ); + } + + return( "" ); +} + +//------------------------------------------------------------------------------ +S32 GuiPopUpMenuCtrlEx::findText( const char* text ) +{ + for ( U32 i = 0; i < mEntries.size(); i++ ) + { + if ( dStrcmp( text, mEntries[i].buf ) == 0 ) + return( mEntries[i].id ); + } + return( -1 ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::setSelected(S32 id, bool bNotifyScript ) +{ + S32 i; + for ( i = 0; U32(i) < mEntries.size(); i++ ) + { + if ( id == mEntries[i].id ) + { + i = ( mRevNum > i ) ? mRevNum - i : i; + mSelIndex = i; + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText(mEntries[i].buf); + } + + // Now perform the popup action: + char idval[24]; + dSprintf( idval, sizeof(idval), "%d", mEntries[mSelIndex].id ); + if ( isMethod( "onSelect" ) && bNotifyScript ) + Con::executef( this, "onSelect", idval, mEntries[mSelIndex].buf ); + return; + } + } + + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText(""); + } + mSelIndex = -1; + + if ( isMethod( "onCancel" ) && bNotifyScript ) + Con::executef( this, "onCancel" ); + + if ( id == -1 ) + return; + + // Execute the popup console command: + if ( bNotifyScript ) + execConsoleCallback(); + //if ( mConsoleCommand[0] && bNotifyScript ) + // Con::evaluate( mConsoleCommand, false ); +} + +//------------------------------------------------------------------------------ +// Added to set the first item as selected. +void GuiPopUpMenuCtrlEx::setFirstSelected( bool bNotifyScript ) +{ + if ( mEntries.size() > 0 ) + { + mSelIndex = 0; + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText( mEntries[0].buf ); + } + + // Now perform the popup action: + char idval[24]; + dSprintf( idval, sizeof(idval), "%d", mEntries[mSelIndex].id ); + if ( isMethod( "onSelect" ) ) + Con::executef( this, "onSelect", idval, mEntries[mSelIndex].buf ); + + // Execute the popup console command: + if ( bNotifyScript ) + execConsoleCallback(); + } + else + { + if ( mReplaceText ) // Only change the displayed text if appropriate. + setText(""); + + mSelIndex = -1; + + if ( bNotifyScript ) + Con::executef( this, "onCancel" ); + } +} + +//------------------------------------------------------------------------------ +// Added to set no items as selected. +void GuiPopUpMenuCtrlEx::setNoneSelected() +{ + if ( mReplaceText ) // Only change the displayed text if appropriate. + { + setText(""); + } + mSelIndex = -1; +} + +//------------------------------------------------------------------------------ +const char *GuiPopUpMenuCtrlEx::getScriptValue() +{ + return getText(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::onRender(Point2I offset, const RectI &updateRect) +{ + TORQUE_UNUSED(updateRect); + Point2I localStart; + + if ( mScrollDir != GuiScrollCtrl::None ) + autoScroll(); + + RectI r( offset, getExtent() ); + if ( mInAction ) + { + S32 l = r.point.x, r2 = r.point.x + r.extent.x - 1; + S32 t = r.point.y, b = r.point.y + r.extent.y - 1; + + // Do we render a bitmap border or lines? + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // Render the fixed, filled in border + renderFixedBitmapBordersFilled(r, 3, mProfile ); + + } + else + { + //renderSlightlyLoweredBox(r, mProfile); + GFX->getDrawUtil()->drawRectFill( r, mProfile->mFillColor ); + } + + // Draw a bitmap over the background? + if ( mTextureDepressed ) + { + RectI rect(offset, mBitmapBounds); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureDepressed, rect ); + } + else if ( mTextureNormal ) + { + RectI rect(offset, mBitmapBounds); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureNormal, rect ); + } + + // Do we render a bitmap border or lines? + if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) + { + GFX->getDrawUtil()->drawLine( l, t, l, b, colorWhite ); + GFX->getDrawUtil()->drawLine( l, t, r2, t, colorWhite ); + GFX->getDrawUtil()->drawLine( l + 1, b, r2, b, mProfile->mBorderColor ); + GFX->getDrawUtil()->drawLine( r2, t + 1, r2, b - 1, mProfile->mBorderColor ); + } + + } + else + // TODO: Implement + // TODO: Add onMouseEnter() and onMouseLeave() and a definition of mMouseOver (see guiButtonBaseCtrl) for this to work. + if ( mMouseOver ) + { + S32 l = r.point.x, r2 = r.point.x + r.extent.x - 1; + S32 t = r.point.y, b = r.point.y + r.extent.y - 1; + + // Do we render a bitmap border or lines? + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // Render the fixed, filled in border + renderFixedBitmapBordersFilled( r, 2, mProfile ); + + } + else + { + GFX->getDrawUtil()->drawRectFill( r, mProfile->mFillColorHL ); + } + + // Draw a bitmap over the background? + if ( mTextureNormal ) + { + RectI rect( offset, mBitmapBounds ); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureNormal, rect ); + } + + // Do we render a bitmap border or lines? + if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) + { + GFX->getDrawUtil()->drawLine( l, t, l, b, colorWhite ); + GFX->getDrawUtil()->drawLine( l, t, r2, t, colorWhite ); + GFX->getDrawUtil()->drawLine( l + 1, b, r2, b, mProfile->mBorderColor ); + GFX->getDrawUtil()->drawLine( r2, t + 1, r2, b - 1, mProfile->mBorderColor ); + } + } + else + { + // Do we render a bitmap border or lines? + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // Render the fixed, filled in border + renderFixedBitmapBordersFilled( r, 1, mProfile ); + } + else + { + GFX->getDrawUtil()->drawRectFill( r, mProfile->mFillColorNA ); + } + + // Draw a bitmap over the background? + if ( mTextureNormal ) + { + RectI rect(offset, mBitmapBounds); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( mTextureNormal, rect ); + } + + // Do we render a bitmap border or lines? + if ( !( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) ) + { + GFX->getDrawUtil()->drawRect( r, mProfile->mBorderColorNA ); + } + } + // renderSlightlyRaisedBox(r, mProfile); // Used to be the only 'else' condition to mInAction above. + + S32 txt_w = mFont->getStrWidth(mText); + localStart.x = 0; + localStart.y = (getHeight() - (mFont->getHeight())) / 2; + + // align the horizontal + switch (mProfile->mAlignment) + { + case GuiControlProfile::RightJustify: + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + localStart.x = getWidth() - mBitmapBounds[2].extent.x - txt_w; + } + else + { + localStart.x = getWidth() - txt_w; + } + break; + case GuiControlProfile::CenterJustify: + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + localStart.x = (getWidth() - mBitmapBounds[2].extent.x - txt_w) / 2; + + } else + { + localStart.x = (getWidth() - txt_w) / 2; + } + break; + default: + // GuiControlProfile::LeftJustify + if ( txt_w > getWidth() ) + { + // The width of the text is greater than the width of the control. + // In this case we will right justify the text and leave some space + // for the down arrow. + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + localStart.x = getWidth() - mBitmapBounds[2].extent.x - txt_w; + } + else + { + localStart.x = getWidth() - txt_w - 12; + } + } + else + { + localStart.x = mProfile->mTextOffset.x; // Use mProfile->mTextOffset as a controlable margin for the control's text. + } + break; + } + + // Do we first draw a coloured box beside the text? + ColorI boxColor; + bool drawbox = getColoredBox( boxColor, mSelIndex); + if ( drawbox ) + { + Point2I coloredboxsize( 15, 10 ); + RectI r( offset.x + mProfile->mTextOffset.x, offset.y + ( (getHeight() - coloredboxsize.y ) / 2 ), coloredboxsize.x, coloredboxsize.y ); + GFX->getDrawUtil()->drawRectFill( r, boxColor); + GFX->getDrawUtil()->drawRect( r, ColorI(0,0,0)); + + localStart.x += coloredboxsize.x + mProfile->mTextOffset.x; + } + + // Draw the text + Point2I globalStart = localToGlobalCoord( localStart ); + ColorI fontColor = mActive ? ( mInAction ? mProfile->mFontColor : mProfile->mFontColorNA ) : mProfile->mFontColorNA; + GFX->getDrawUtil()->setBitmapModulation( fontColor ); // was: (mProfile->mFontColor); + + // Get the number of columns in the text + S32 colcount = getColumnCount( mText, "\t" ); + + // Are there two or more columns? + if ( colcount >= 2 ) + { + char buff[256]; + + // Draw the first column + getColumn( mText, buff, 0, "\t" ); + GFX->getDrawUtil()->drawText( mFont, globalStart, buff, mProfile->mFontColors ); + + // Draw the second column to the right + getColumn( mText, buff, 1, "\t" ); + S32 txt_w = mFont->getStrWidth( buff ); + if ( mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size() ) + { + // We're making use of a bitmap border, so take into account the + // right cap of the border. + RectI* mBitmapBounds = mProfile->mBitmapArrayRects.address(); + Point2I textpos = localToGlobalCoord( Point2I( getWidth() - txt_w - mBitmapBounds[2].extent.x, localStart.y ) ); + GFX->getDrawUtil()->drawText( mFont, textpos, buff, mProfile->mFontColors ); + + } else + { + Point2I textpos = localToGlobalCoord( Point2I( getWidth() - txt_w - 12, localStart.y ) ); + GFX->getDrawUtil()->drawText( mFont, textpos, buff, mProfile->mFontColors ); + } + + } else + { + GFX->getDrawUtil()->drawText( mFont, globalStart, mText, mProfile->mFontColors ); + } + + // If we're rendering a bitmap border, then it will take care of the arrow. + if ( !(mProfile->getChildrenProfile() && mProfile->mBitmapArrayRects.size()) ) + { + // Draw a triangle (down arrow) + S32 left = r.point.x + r.extent.x - 12; + S32 right = left + 8; + S32 middle = left + 4; + S32 top = r.extent.y / 2 + r.point.y - 4; + S32 bottom = top + 8; + + PrimBuild::color( mProfile->mFontColor ); + + PrimBuild::begin( GFXTriangleList, 3 ); + PrimBuild::vertex2fv( Point3F( (F32)left, (F32)top, 0.0f ) ); + PrimBuild::vertex2fv( Point3F( (F32)right, (F32)top, 0.0f ) ); + PrimBuild::vertex2fv( Point3F( (F32)middle, (F32)bottom, 0.0f ) ); + PrimBuild::end(); + } +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::closePopUp() +{ + if ( !mInAction ) + return; + + // Get the selection from the text list: + mSelIndex = mTl->getSelectedCell().y; + mSelIndex = ( mRevNum >= mSelIndex && mSelIndex != -1 ) ? mRevNum - mSelIndex : mSelIndex; + if ( mSelIndex != -1 ) + { + if ( mReplaceText ) + setText( mEntries[mSelIndex].buf ); + setIntVariable( mEntries[mSelIndex].id ); + } + + // Release the mouse: + mInAction = false; + mTl->mouseUnlock(); + + // Commented out below and moved to the end of the function. See the + // note below for the reason why. + /* + // Pop the background: + getRoot()->popDialogControl(mBackground); + + // Kill the popup: + mBackground->removeObject( mSc ); + mTl->deleteObject(); + mSc->deleteObject(); + mBackground->deleteObject(); + + // Set this as the first responder: + setFocus(); + */ + + // Now perform the popup action: + if ( mSelIndex != -1 ) + { + char idval[24]; + dSprintf( idval, sizeof(idval), "%d", mEntries[mSelIndex].id ); + if ( isMethod( "onSelect" ) ) + Con::executef( this, "onSelect", idval, mEntries[mSelIndex].buf ); + } + else if ( isMethod( "onCancel" ) ) + Con::executef( this, "onCancel" ); + + // Execute the popup console command: + execConsoleCallback(); + //if ( mConsoleCommand[0] ) + // Con::evaluate( mConsoleCommand, false ); + + // Reordered this pop dialog to be after the script select callback. When the + // background was popped it was causing all sorts of focus issues when + // suddenly the first text edit control took the focus before it came back + // to this popup. + + // Pop the background: + GuiCanvas *root = getRoot(); + if ( root ) + root->popDialogControl(mBackground); + + // Kill the popup: + mBackground->removeObject( mSc ); + mTl->deleteObject(); + mSc->deleteObject(); + mBackground->deleteObject(); + + // Set this as the first responder: + setFirstResponder(); +} + +//------------------------------------------------------------------------------ +bool GuiPopUpMenuCtrlEx::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, don't process the input: + if ( !mVisible || !mActive || !mAwake ) + return false; + + //see if the key down is a or not + if ( event.keyCode == KEY_RETURN && event.modifier == 0 ) + { + onAction(); + return true; + } + + //otherwise, pass the event to its parent + return Parent::onKeyDown( event ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::onAction() +{ + GuiControl *canCtrl = getParent(); + + addChildren(); + + GuiCanvas *root = getRoot(); + Point2I windowExt = root->getExtent(); + + mBackground->resize( Point2I(0,0), root->getExtent() ); + + S32 textWidth = 0, width = getWidth(); + //const S32 menuSpace = 5; // Removed as no longer used. + const S32 textSpace = 2; + bool setScroll = false; + + for ( U32 i = 0; i < mEntries.size(); ++i ) + if ( S32(mFont->getStrWidth( mEntries[i].buf )) > textWidth ) + textWidth = mFont->getStrWidth( mEntries[i].buf ); + + //if(textWidth > getWidth()) + S32 sbWidth = mSc->getControlProfile()->mBorderThickness * 2 + mSc->scrollBarThickness(); // Calculate the scroll bar width + if ( textWidth > ( getWidth() - sbWidth-mProfile->mTextOffset.x - mSc->getChildMargin().x * 2 ) ) // The text draw area to test against is the width of the drop-down minus the scroll bar width, the text margin and the scroll bar child margins. + { + //textWidth +=10; + textWidth +=sbWidth + mProfile->mTextOffset.x + mSc->getChildMargin().x * 2; // The new width is the width of the text plus the scroll bar width plus the text margin size plus the scroll bar child margins. + width = textWidth; + + // If a child margin is not defined for the scroll control, let's add + // some space between the text and scroll control for readability + if(mSc->getChildMargin().x == 0) + width += textSpace; + } + + //mTl->setCellSize(Point2I(width, mFont->getHeight()+3)); + mTl->setCellSize(Point2I(width, mFont->getHeight() + textSpace)); // Modified the above line to use textSpace rather than the '3' as this is what is used below. + + for ( U32 j = 0; j < mEntries.size(); ++j ) + mTl->addEntry( mEntries[j].id, mEntries[j].buf ); + + Point2I pointInGC = canCtrl->localToGlobalCoord( getPosition() ); + Point2I scrollPoint( pointInGC.x, pointInGC.y + getHeight() ); + + //Calc max Y distance, so Scroll Ctrl will fit on window + //S32 maxYdis = windowExt.y - pointInGC.y - getHeight() - menuSpace; + S32 sbBorder = mSc->getControlProfile()->mBorderThickness * 2 + mSc->getChildMargin().y * 2; // Added to take into account the border thickness and the margin of the child controls of the scroll control when figuring out the size of the contained text list control + S32 maxYdis = windowExt.y - pointInGC.y - getHeight() - sbBorder; // - menuSpace; // Need to remove the border thickness from the contained control maximum extent and got rid of the 'menuspace' variable + + //If scroll bars need to be added + mRevNum = 0; // Added here rather than within the following 'if' statements. + if ( maxYdis < mTl->getHeight() + sbBorder ) // Instead of adding sbBorder, it was: 'textSpace' + { + //Should we pop menu list above the button + if ( maxYdis < pointInGC.y ) // removed: '- menuSpace)' from check to see if there is more space above the control than below. + { + if(mReverseTextList) // Added this check if we should reverse the text list. + reverseTextList(); + + maxYdis = pointInGC.y; // Was at the end: '- menuSpace;' + //Does the menu need a scroll bar + if ( maxYdis < mTl->getHeight() + sbBorder ) // Instead of adding sbBorder, it was: 'textSpace' + { + // Removed width recalculation for scroll bar as the scroll bar is already being taken into account. + //Calc for the width of the scroll bar + // if(textWidth >= width) + // width += 20; + // mTl->setCellSize(Point2I(width,mFont->getHeight() + textSpace)); + + //Pop menu list above the button + // scrollPoint.set(pointInGC.x, menuSpace - 1); // Removed as calculated outside the 'if', and was wrong to begin with + setScroll = true; + } + //No scroll bar needed + else + { + maxYdis = mTl->getHeight() + sbBorder; // Instead of adding sbBorder, it was: 'textSpace' + // scrollPoint.set(pointInGC.x, pointInGC.y - maxYdis -1); // Removed as calculated outside the 'if' and the '-1' at the end is wrong + } + + // Added the next two lines + scrollPoint.set(pointInGC.x, pointInGC.y - maxYdis); // Used to have the following on the end: '-1);' + } + //Scroll bar needed but Don't pop above button + else + { + //mRevNum = 0; // Commented out as it has been added at the beginning of this function + if ( mSelIndex >= 0 ) + mTl->setSelectedCell( Point2I( 0, mSelIndex ) ); // Added as we were not setting the selected cell if the list is displayed down. + + // Removed width recalculation for scroll bar as the scroll bar is already being taken into account. + //Calc for the width of the scroll bar + // if(textWidth >= width) + // width += 20; + // mTl->setCellSize(Point2I(width,mFont->getHeight() + textSpace)); + setScroll = true; + } + } + //No scroll bar needed + else + { + if ( mSelIndex >= 0 ) + mTl->setSelectedCell( Point2I( 0, mSelIndex ) ); // Added as we were not setting the selected cell if the list is displayed down. + + //maxYdis = mTl->getHeight() + textSpace; + maxYdis = mTl->getHeight() + sbBorder; // Added in the border thickness of the scroll control and removed the addition of textSpace + } + + RectI newBounds = mSc->getBounds(); + + //offset it from the background so it lines up properly + newBounds.point = mBackground->globalToLocalCoord( scrollPoint ); + + if ( newBounds.point.x + width > mBackground->getWidth() ) + if ( width - getWidth() > 0 ) + newBounds.point.x -= width - getWidth(); + + //mSc->getExtent().set(width-1, maxYdis); + newBounds.extent.set( width, maxYdis ); + mSc->setBounds( newBounds ); // Not sure why the '-1' above. + + mSc->registerObject(); + mTl->registerObject(); + mBackground->registerObject(); + + mSc->addObject( mTl ); + mBackground->addObject( mSc ); + + mBackgroundCancel = false; // Setup check if user clicked on the background instead of the text list (ie: didn't want to change their current selection). + + root->pushDialogControl( mBackground, 99 ); + + if ( setScroll ) + { + // Resize the text list + Point2I cellSize; + mTl->getCellSize( cellSize ); + cellSize.x = width - mSc->scrollBarThickness() - sbBorder; + mTl->setCellSize( cellSize ); + mTl->setWidth( cellSize.x ); + + if ( mSelIndex ) + mTl->scrollCellVisible( Point2I( 0, mSelIndex ) ); + else + mTl->scrollCellVisible( Point2I( 0, 0 ) ); + } + + mTl->setFirstResponder(); + + mInAction = true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::addChildren() +{ + mTl = new GuiPopupTextListCtrlEx( this ); + AssertFatal( mTl, "Failed to create the GuiPopUpTextListCtrlEx for the PopUpMenu" ); + // Use the children's profile rather than the parent's profile, if it exists. + mTl->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile ); + mTl->setField("noDuplicates", "false"); + + mSc = new GuiScrollCtrl; + AssertFatal( mSc, "Failed to create the GuiScrollCtrl for the PopUpMenu" ); + GuiControlProfile *prof; + if ( Sim::findObject( "GuiScrollProfile", prof ) ) + { + mSc->setControlProfile( prof ); + } + else + { + // Use the children's profile rather than the parent's profile, if it exists. + mSc->setControlProfile( mProfile->getChildrenProfile() ? mProfile->getChildrenProfile() : mProfile ); + } + mSc->setField( "hScrollBar", "AlwaysOff" ); + mSc->setField( "vScrollBar", "dynamic" ); + //if(mRenderScrollInNA) // Force the scroll control to render using fillColorNA rather than fillColor + // mSc->mUseNABackground = true; + + mBackground = new GuiPopUpBackgroundCtrlEx( this, mTl ); + AssertFatal( mBackground, "Failed to create the GuiBackgroundCtrlEx for the PopUpMenu" ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::repositionPopup() +{ + if ( !mInAction || !mSc || !mTl ) + return; + + // I'm not concerned with this right now... +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::reverseTextList() +{ + mTl->clear(); + for ( S32 i = mEntries.size()-1; i >= 0; --i ) + mTl->addEntry( mEntries[i].id, mEntries[i].buf ); + + // Don't lose the selected cell: + if ( mSelIndex >= 0 ) + mTl->setSelectedCell( Point2I( 0, mEntries.size() - mSelIndex - 1 ) ); + + mRevNum = mEntries.size() - 1; +} + +//------------------------------------------------------------------------------ +bool GuiPopUpMenuCtrlEx::getFontColor( ColorI &fontColor, S32 id, bool selected, bool mouseOver ) +{ + U32 i; + Entry* entry = NULL; + for ( i = 0; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == id ) + { + entry = &mEntries[i]; + break; + } + } + + if ( !entry ) + return( false ); + + if ( entry->scheme != 0 ) + { + // Find the entry's color scheme: + for ( i = 0; i < mSchemes.size(); i++ ) + { + if ( mSchemes[i].id == entry->scheme ) + { + fontColor = selected ? mSchemes[i].fontColorSEL : mouseOver ? mSchemes[i].fontColorHL : mSchemes[i].fontColor; + return( true ); + } + } + } + + if(id == -1) + fontColor = mProfile->mFontColorHL; + else + // Default color scheme... + fontColor = selected ? mProfile->mFontColorSEL : mouseOver ? mProfile->mFontColorHL : mProfile->mFontColorNA; // Modified the final color choice from mProfile->mFontColor to mProfile->mFontColorNA + + return( true ); +} + +//------------------------------------------------------------------------------ +// Added +bool GuiPopUpMenuCtrlEx::getColoredBox( ColorI &fontColor, S32 id ) +{ + U32 i; + Entry* entry = NULL; + for ( i = 0; i < mEntries.size(); i++ ) + { + if ( mEntries[i].id == id ) + { + entry = &mEntries[i]; + break; + } + } + + if ( !entry ) + return false; + + if ( entry->usesColorBox == false ) + return false; + + fontColor = entry->colorbox; + + return true; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::onMouseDown(const GuiEvent &event) +{ + TORQUE_UNUSED(event); + onAction(); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::onMouseUp(const GuiEvent &event) +{ + TORQUE_UNUSED(event); +} + +//------------------------------------------------------------------------------ +// Added +void GuiPopUpMenuCtrlEx::onMouseEnter(const GuiEvent &event) +{ + mMouseOver = true; +} + +//------------------------------------------------------------------------------ +// Added +void GuiPopUpMenuCtrlEx::onMouseLeave(const GuiEvent &) +{ + mMouseOver = false; +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::setupAutoScroll(const GuiEvent &event) +{ + GuiControl *parent = getParent(); + if ( !parent ) + return; + + Point2I mousePt = mSc->globalToLocalCoord( event.mousePoint ); + + mEventSave = event; + + if ( mLastYvalue != mousePt.y ) + { + mScrollDir = GuiScrollCtrl::None; + if ( mousePt.y > mSc->getHeight() || mousePt.y < 0 ) + { + S32 topOrBottom = ( mousePt.y > mSc->getHeight() ) ? 1 : 0; + mSc->scrollTo( 0, topOrBottom ); + return; + } + + F32 percent = (F32)mousePt.y / (F32)mSc->getHeight(); + if ( percent > 0.7f && mousePt.y > mLastYvalue ) + { + mIncValue = percent - 0.5f; + mScrollDir = GuiScrollCtrl::DownArrow; + } + else if ( percent < 0.3f && mousePt.y < mLastYvalue ) + { + mIncValue = 0.5f - percent; + mScrollDir = GuiScrollCtrl::UpArrow; + } + mLastYvalue = mousePt.y; + } +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::autoScroll() +{ + mScrollCount += mIncValue; + + while ( mScrollCount > 1 ) + { + mSc->autoScroll( mScrollDir ); + mScrollCount -= 1; + } + mTl->onMouseMove( mEventSave ); +} + +//------------------------------------------------------------------------------ +void GuiPopUpMenuCtrlEx::replaceText(S32 boolVal) +{ + mReplaceText = boolVal; +} diff --git a/gui/controls/guiPopUpCtrlEx.h b/gui/controls/guiPopUpCtrlEx.h new file mode 100644 index 0000000..8ad648b --- /dev/null +++ b/gui/controls/guiPopUpCtrlEx.h @@ -0,0 +1,175 @@ +// Revision History: +// January 3, 2004 David Wyand Added onMouseEnter() and onMouseLeave() methods to +// GuiPopUpMenuCtrl class. Also added the bool mMouseOver +// to the same class. +// January 3, 2004 David Wyand Added the bool mBackgroundCancel to the GuiPopUpMenuCtrl +// class. +// May 19, 2004 David Wyand Added the bool usesColorBox and ColorI colorbox to the +// Entry structure of the GuiPopUpMenuCtrl class. These +// are used to draw a coloured rectangle beside the text in +// the list. +// November 16, 2005 David Wyand Added the method setNoneSelected() to set none of the +// items as selected. Use this over setSelected(-1); when +// there are negative IDs in the item list. +// January 11, 2006 David Wyand Added the method setFirstSelected() to set the first +// item to be the selected one. Handy when you don't know +// the ID of the first item. +// +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPOPUPCTRLEX_H_ +#define _GUIPOPUPCTRLEX_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif +#ifndef _GUITEXTLISTCTRL_H_ +#include "gui/controls/guiTextListCtrl.h" +#endif +#ifndef _GUIBUTTONCTRL_H_ +#include "gui/buttons/guiButtonCtrl.h" +#endif +#ifndef _GUIBACKGROUNDCTRL_H_ +#include "gui/controls/guiBackgroundCtrl.h" +#endif +#ifndef _GUISCROLLCTRL_H_ +#include "gui/containers/guiScrollCtrl.h" +#endif +class GuiPopUpMenuCtrlEx; +class GuiPopupTextListCtrlEx; + +class GuiPopUpBackgroundCtrlEx : public GuiControl +{ +protected: + GuiPopUpMenuCtrlEx *mPopUpCtrl; + GuiPopupTextListCtrlEx *mTextList; +public: + GuiPopUpBackgroundCtrlEx(GuiPopUpMenuCtrlEx *ctrl, GuiPopupTextListCtrlEx* textList); + void onMouseDown(const GuiEvent &event); +}; + +class GuiPopupTextListCtrlEx : public GuiTextListCtrl +{ + private: + typedef GuiTextListCtrl Parent; + + bool hasCategories(); + + protected: + GuiPopUpMenuCtrlEx *mPopUpCtrl; + + public: + GuiPopupTextListCtrlEx(); // for inheritance + GuiPopupTextListCtrlEx(GuiPopUpMenuCtrlEx *ctrl); + + // GuiArrayCtrl overload: + void onCellSelected(Point2I cell); + + // GuiControl overloads: + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); +}; + +class GuiPopUpMenuCtrlEx : public GuiTextCtrl +{ + typedef GuiTextCtrl Parent; + + public: + struct Entry + { + char buf[256]; + S32 id; + U16 ascii; + U16 scheme; + bool usesColorBox; // Added + ColorI colorbox; // Added + }; + + struct Scheme + { + U32 id; + ColorI fontColor; + ColorI fontColorHL; + ColorI fontColorSEL; + }; + + bool mBackgroundCancel; // Added + + protected: + GuiPopupTextListCtrlEx *mTl; + GuiScrollCtrl *mSc; + GuiPopUpBackgroundCtrlEx *mBackground; + Vector mEntries; + Vector mSchemes; + S32 mSelIndex; + S32 mMaxPopupHeight; + F32 mIncValue; + F32 mScrollCount; + S32 mLastYvalue; + GuiEvent mEventSave; + S32 mRevNum; + bool mInAction; + bool mReplaceText; + bool mMouseOver; // Added + bool mRenderScrollInNA; // Added + bool mReverseTextList; // Added - Should we reverse the text list if we display up? + bool mHotTrackItems; + StringTableEntry mBitmapName; // Added + Point2I mBitmapBounds; // Added + GFXTexHandle mTextureNormal; // Added + GFXTexHandle mTextureDepressed; // Added + S32 mIdMax; + + virtual void addChildren(); + virtual void repositionPopup(); + + public: + GuiPopUpMenuCtrlEx(void); + ~GuiPopUpMenuCtrlEx(); + GuiScrollCtrl::Region mScrollDir; + bool onWake(); // Added + bool onAdd(); + void onSleep(); + void setBitmap(const char *name); // Added + void sort(); + void sortID(); // Added + void addEntry(const char *buf, S32 id = -1, U32 scheme = 0); + void addScheme(U32 id, ColorI fontColor, ColorI fontColorHL, ColorI fontColorSEL); + void onRender(Point2I offset, const RectI &updateRect); + void onAction(); + virtual void closePopUp(); + void clear(); + void clearEntry( S32 entry ); // Added + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseEnter(const GuiEvent &event); // Added + void onMouseLeave(const GuiEvent &); // Added + void setupAutoScroll(const GuiEvent &event); + void autoScroll(); + bool onKeyDown(const GuiEvent &event); + void reverseTextList(); + bool getFontColor(ColorI &fontColor, S32 id, bool selected, bool mouseOver); + bool getColoredBox(ColorI &boxColor, S32 id); // Added + + S32 getSelected(); + void setSelected(S32 id, bool bNotifyScript = true); + void setFirstSelected(bool bNotifyScript = true); // Added + void setNoneSelected(); // Added + const char *getScriptValue(); + const char *getTextById(S32 id); + S32 findText( const char* text ); + S32 getNumEntries() { return( mEntries.size() ); } + void replaceText(S32); + + DECLARE_CONOBJECT(GuiPopUpMenuCtrlEx); + static void initPersistFields(void); + +}; + +#endif //_GUIIMPROVEDPOPUPCTRL_H_ diff --git a/gui/controls/guiSliderCtrl.cpp b/gui/controls/guiSliderCtrl.cpp new file mode 100644 index 0000000..adeb697 --- /dev/null +++ b/gui/controls/guiSliderCtrl.cpp @@ -0,0 +1,419 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxTextureManager.h" +#include "gui/controls/guiSliderCtrl.h" +#include "gui/core/guiDefaultControlRender.h" +#include "platform/event.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "sfx/sfxSystem.h" + +IMPLEMENT_CONOBJECT(GuiSliderCtrl); + +//---------------------------------------------------------------------------- +GuiSliderCtrl::GuiSliderCtrl(void) +{ + mActive = true; + mRange.set( 0.0f, 1.0f ); + mTicks = 10; + mValue = 0.5f; + mThumbSize.set(8,20); + mShiftPoint = 5; + mShiftExtent = 10; + mIncAmount = 0.0f; + mDisplayValue = false; + mMouseOver = false; + mDepressed = false; +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::initPersistFields() +{ + addGroup( "Slider" ); + addField("range", TypePoint2F, Offset(mRange, GuiSliderCtrl)); + addField("ticks", TypeS32, Offset(mTicks, GuiSliderCtrl)); + addField("value", TypeF32, Offset(mValue, GuiSliderCtrl)); + endGroup( "Slider" ); + + Parent::initPersistFields(); +} + +//---------------------------------------------------------------------------- +ConsoleMethod( GuiSliderCtrl, getValue, F32, 2, 2, "Get the position of the slider.") +{ + return object->getValue(); +} + +//---------------------------------------------------------------------------- +ConsoleMethod( GuiSliderCtrl, setValue, void, 3, 3, "( float pos ) - Set position of the slider." ) +{ + object->setValue( dAtof( argv[ 2 ] ) ); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::setValue(F32 val) +{ + mValue = val; + updateThumb(mValue, false, false, false); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::setActive( bool value ) +{ + if( !value && mDepressed ) + { + // We're in the middle of a drag. Finish it here as once we've + // been deactivated, we are not going to see a mouse-up event. + + mDepressed = false; + mouseUnlock(); + execConsoleCallback(); + } + + Parent::setActive( value ); +} + +//---------------------------------------------------------------------------- +bool GuiSliderCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + F32 value; + if( mConsoleVariable[ 0 ] ) + value = getFloatVariable(); + else + value = mValue; + + mValue = mClampF( value, mRange.x, mRange.y ); + + // mouse scroll increment percentage is 5% of the range + mIncAmount = ((mRange.y - mRange.x) * 0.05); + + if(mThumbSize.y + mProfile->mFont->getHeight()-4 <= getExtent().y) + mDisplayValue = true; + else + mDisplayValue = false; + + updateThumb( mValue, false, true ); + + mHasTexture = mProfile->constructBitmapArray() >= NumBitmaps; + if( mHasTexture ) + mBitmapBounds = mProfile->mBitmapArrayRects.address(); + + return true; +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onMouseDown(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + mouseLock(); + setFirstResponder(); + mDepressed = true; + + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + F32 value; + if (getWidth() >= getHeight()) + value = F32(curMousePos.x-mShiftPoint) / F32(getWidth()-mShiftExtent)*(mRange.y-mRange.x) + mRange.x; + else + value = F32(curMousePos.y) / F32(getHeight())*(mRange.y-mRange.x) + mRange.x; + + updateThumb( value, ( event.modifier & SI_SHIFT ) ); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onMouseDragged(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + Point2I curMousePos = globalToLocalCoord(event.mousePoint); + F32 value; + if (getWidth() >= getHeight()) + value = F32(curMousePos.x-mShiftPoint) / F32(getWidth()-mShiftExtent)*(mRange.y-mRange.x) + mRange.x; + else + value = F32(curMousePos.y) / F32(getHeight())*(mRange.y-mRange.x) + mRange.x; + + if (value > mRange.y) + value = mRange.y; + else if (value < mRange.x) + value = mRange.x; + + if (!(event.modifier & SI_SHIFT) && mTicks > 2) { + // If the shift key is held, snap to the nearest tick, if any are being drawn + + F32 tickStep = (mRange.y - mRange.x) / F32(mTicks + 1); + + F32 tickSteps = (value - mRange.x) / tickStep; + S32 actualTick = S32(tickSteps + 0.5); + + value = actualTick * tickStep + mRange.x; + AssertFatal(value <= mRange.y && value >= mRange.x, "Error, out of bounds value generated from shift-snap of slider"); + } + + updateThumb( value, ( event.modifier & SI_SHIFT ) ); + + Con::executef(this, "onMouseDragged"); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onMouseUp(const GuiEvent &) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + if( mDepressed ) + { + mDepressed = false; + execConsoleCallback(); + } + + mouseUnlock(); +} + +void GuiSliderCtrl::onMouseEnter(const GuiEvent &event) +{ + setUpdate(); + if(isMouseLocked()) + { + mDepressed = true; + mMouseOver = true; + } + else + { + if ( mActive && mProfile->mSoundButtonOver ) + { + //F32 pan = (F32(event.mousePoint.x)/F32(getRoot()->getWidth())*2.0f-1.0f)*0.8f; + SFX->playOnce(mProfile->mSoundButtonOver); + } + mMouseOver = true; + } +} + +void GuiSliderCtrl::onMouseLeave(const GuiEvent &) +{ + setUpdate(); + if(isMouseLocked()) + mDepressed = false; + mMouseOver = false; +} +//---------------------------------------------------------------------------- +bool GuiSliderCtrl::onMouseWheelUp(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return Parent::onMouseWheelUp(event); + + mValue += mIncAmount; + updateThumb( mValue, ( event.modifier & SI_SHIFT ) ); + + return true; +} + +bool GuiSliderCtrl::onMouseWheelDown(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return Parent::onMouseWheelUp(event); + + mValue -= mIncAmount; + updateThumb( mValue, ( event.modifier & SI_SHIFT ) ); + + return true; +} +//---------------------------------------------------------------------------- +void GuiSliderCtrl::updateThumb( F32 _value, bool snap, bool onWake, bool doCallback ) +{ + if (snap && mTicks > 1) { + // If the shift key is held, snap to the nearest tick, if any are being drawn + + F32 tickStep = (mRange.y - mRange.x) / F32(mTicks + 1); + + F32 tickSteps = (_value - mRange.x) / tickStep; + S32 actualTick = S32(tickSteps + 0.5); + + _value = actualTick * tickStep + mRange.x; + } + + mValue = _value; + // clamp the thumb to legal values + if (mValue < mRange.x) mValue = mRange.x; + if (mValue > mRange.y) mValue = mRange.y; + + Point2I ext = getExtent(); + ext.x -= ( mShiftExtent + mThumbSize.x ) / 2; + // update the bounding thumb rect + if (getWidth() >= getHeight()) + { // HORZ thumb + S32 mx = (S32)((F32(ext.x) * (mValue-mRange.x) / (mRange.y-mRange.x))); + S32 my = ext.y/2; + if(mDisplayValue) + my = mThumbSize.y/2; + + mThumb.point.x = mx - (mThumbSize.x/2); + mThumb.point.y = my - (mThumbSize.y/2); + mThumb.extent = mThumbSize; + } + else + { // VERT thumb + S32 mx = ext.x/2; + S32 my = (S32)((F32(ext.y) * (mValue-mRange.x) / (mRange.y-mRange.x))); + mThumb.point.x = mx - (mThumbSize.y/2); + mThumb.point.y = my - (mThumbSize.x/2); + mThumb.extent.x = mThumbSize.y; + mThumb.extent.y = mThumbSize.x; + } + setFloatVariable(mValue); + setUpdate(); + + // Use the alt console command if you want to continually update: + if ( !onWake && doCallback ) + execAltConsoleCallback(); +} + +//---------------------------------------------------------------------------- +void GuiSliderCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + Point2I pos(offset.x+mShiftPoint, offset.y); + Point2I ext(getWidth() - mShiftExtent, getHeight()); + RectI thumb = mThumb; + + if( mHasTexture ) + { + if(mTicks > 0) + { + // TODO: tick marks should be positioned based on the bitmap dimensions. + Point2I mid(ext.x, ext.y/2); + Point2I oldpos = pos; + pos += Point2I(1, 0); + + PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f ); + PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 ); + // tick marks + for (U32 t = 0; t <= (mTicks+1); t++) + { + S32 x = (S32)(F32(mid.x+1)/F32(mTicks+1)*F32(t)) + pos.x; + S32 y = pos.y + mid.y; + PrimBuild::vertex2i(x, y + mShiftPoint); + PrimBuild::vertex2i(x, y + mShiftPoint*2 + 2); + } + PrimBuild::end(); + // TODO: it would be nice, if the primitive builder were a little smarter, + // so that we could change colors midstream. + PrimBuild::color4f(0.9f, 0.9f, 0.9f, 1.0f); + PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 ); + // tick marks + for (U32 t = 0; t <= (mTicks+1); t++) + { + S32 x = (S32)(F32(mid.x+1)/F32(mTicks+1)*F32(t)) + pos.x + 1; + S32 y = pos.y + mid.y + 1; + PrimBuild::vertex2i(x, y + mShiftPoint ); + PrimBuild::vertex2i(x, y + mShiftPoint * 2 + 3); + } + PrimBuild::end(); + pos = oldpos; + } + + S32 index = SliderButtonNormal; + if(mMouseOver) + index = SliderButtonHighlight; + GFX->getDrawUtil()->clearBitmapModulation(); + + //left border + GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject, Point2I(offset.x,offset.y), mBitmapBounds[SliderLineLeft]); + //right border + GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject, Point2I(offset.x + getWidth() - mBitmapBounds[SliderLineRight].extent.x, offset.y), mBitmapBounds[SliderLineRight]); + + + //draw our center piece to our slider control's border and stretch it + RectI destRect; + destRect.point.x = offset.x + mBitmapBounds[SliderLineLeft].extent.x; + destRect.extent.x = getWidth() - mBitmapBounds[SliderLineLeft].extent.x - mBitmapBounds[SliderLineRight].extent.x; + destRect.point.y = offset.y; + destRect.extent.y = mBitmapBounds[SliderLineCenter].extent.y; + + RectI stretchRect; + stretchRect = mBitmapBounds[SliderLineCenter]; + stretchRect.inset(1,0); + + GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->mTextureObject, destRect, stretchRect); + + //draw our control slider button + thumb.point += pos; + GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject,Point2I(thumb.point.x,offset.y ),mBitmapBounds[index]); + + } + else if (getWidth() >= getHeight()) + { + Point2I mid(ext.x, ext.y/2); + if(mDisplayValue) + mid.set(ext.x, mThumbSize.y/2); + + PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f ); + PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 + 2); + // horz rule + PrimBuild::vertex2i( pos.x, pos.y + mid.y ); + PrimBuild::vertex2i( pos.x + mid.x, pos.y + mid.y ); + + // tick marks + for( U32 t = 0; t <= ( mTicks + 1 ); t++ ) + { + S32 x = (S32)( F32( mid.x - 1 ) / F32( mTicks + 1 ) * F32( t ) ); + PrimBuild::vertex2i( pos.x + x, pos.y + mid.y - mShiftPoint ); + PrimBuild::vertex2i( pos.x + x, pos.y + mid.y + mShiftPoint ); + } + PrimBuild::end(); + } + else + { + Point2I mid(ext.x/2, ext.y); + + PrimBuild::color4f( 0.f, 0.f, 0.f, 1.f ); + PrimBuild::begin( GFXLineList, ( mTicks + 2 ) * 2 + 2); + // horz rule + PrimBuild::vertex2i( pos.x + mid.x, pos.y ); + PrimBuild::vertex2i( pos.x + mid.x, pos.y + mid.y ); + + // tick marks + for( U32 t = 0; t <= ( mTicks + 1 ); t++ ) + { + S32 y = (S32)( F32( mid.y - 1 ) / F32( mTicks + 1 ) * F32( t ) ); + PrimBuild::vertex2i( pos.x + mid.x - mShiftPoint, pos.y + y ); + PrimBuild::vertex2i( pos.x + mid.x + mShiftPoint, pos.y + y ); + } + PrimBuild::end(); + mDisplayValue = false; + } + // draw the thumb + thumb.point += pos; + renderRaisedBox(thumb, mProfile); + + if(mDisplayValue) + { + char buf[20]; + dSprintf(buf,sizeof(buf),"%0.3f",mValue); + + Point2I textStart = thumb.point; + + S32 txt_w = mProfile->mFont->getStrWidth((const UTF8 *)buf); + + textStart.x += (S32)((thumb.extent.x/2.0f)); + textStart.y += thumb.extent.y - 2; //19 + textStart.x -= (txt_w/2); + if(textStart.x < offset.x) + textStart.x = offset.x; + else if(textStart.x + txt_w > offset.x+getWidth()) + textStart.x -=((textStart.x + txt_w) - (offset.x+getWidth())); + + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor); + GFX->getDrawUtil()->drawText(mProfile->mFont, textStart, buf, mProfile->mFontColors); + } + renderChildControls(offset, updateRect); +} + diff --git a/gui/controls/guiSliderCtrl.h b/gui/controls/guiSliderCtrl.h new file mode 100644 index 0000000..8227f25 --- /dev/null +++ b/gui/controls/guiSliderCtrl.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISLIDERCTRL_H_ +#define _GUISLIDERCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +class GuiSliderCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +protected: + Point2F mRange; + U32 mTicks; + F32 mValue; + RectI mThumb; + Point2I mThumbSize; + void updateThumb( F32 value, bool snap = true, bool onWake = false, bool doCallback = true ); + S32 mShiftPoint; + S32 mShiftExtent; + F32 mIncAmount; + bool mDisplayValue; + bool mDepressed; + bool mMouseOver; + bool mHasTexture; + + enum + { + SliderLineLeft = 0, + SliderLineCenter, + SliderLineRight, + SliderButtonNormal, + SliderButtonHighlight, + NumBitmaps + }; + RectI *mBitmapBounds; + +public: + //creation methods + DECLARE_CONOBJECT(GuiSliderCtrl); + DECLARE_CATEGORY( "Gui Values" ); + DECLARE_DESCRIPTION( "A control that implements a horizontal or vertical slider to\n" + "select/represent values in a certain range." ) + + GuiSliderCtrl(); + static void initPersistFields(); + + //Parental methods + bool onWake(); + + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &); + void onMouseLeave(const GuiEvent &); + void onMouseEnter(const GuiEvent &); + bool onMouseWheelUp(const GuiEvent &event); + bool onMouseWheelDown(const GuiEvent &event); + + void setActive( bool value ); + + const Point2F& getRange() const { return mRange; } + F32 getValue() const { return mValue; } + void setScriptValue(const char *val) { setValue(dAtof(val)); } + void setValue(F32 val); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif diff --git a/gui/controls/guiTabPageCtrl.cpp b/gui/controls/guiTabPageCtrl.cpp new file mode 100644 index 0000000..3668e4b --- /dev/null +++ b/gui/controls/guiTabPageCtrl.cpp @@ -0,0 +1,177 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gui/core/guiCanvas.h" +#include "gui/controls/guiTabPageCtrl.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gui/editor/guiEditCtrl.h" + +IMPLEMENT_CONOBJECT(GuiTabPageCtrl); + +GuiTabPageCtrl::GuiTabPageCtrl(void) +{ + setExtent(Point2I(100, 200)); + mMinSize.set(50, 50); + mFitBook = false; + dStrcpy(mText,(UTF8*)"TabPage"); + mActive = true; + mIsContainer = true; +} + +void GuiTabPageCtrl::initPersistFields() +{ + addField( "fitBook", TypeBool, Offset( mFitBook, GuiTabPageCtrl ) ); + + Parent::initPersistFields(); +} + +bool GuiTabPageCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + return true; +} + +void GuiTabPageCtrl::onSleep() +{ + Parent::onSleep(); +} + +GuiControl* GuiTabPageCtrl::findHitControl(const Point2I &pt, S32 initialLayer) +{ + return Parent::findHitControl(pt, initialLayer); +} + +void GuiTabPageCtrl::onMouseDown(const GuiEvent &event) +{ + setUpdate(); + Point2I localPoint = globalToLocalCoord( event.mousePoint ); + + GuiControl *ctrl = findHitControl(localPoint); + if (ctrl && ctrl != this) + { + ctrl->onMouseDown(event); + } +} + +bool GuiTabPageCtrl::onMouseDownEditor(const GuiEvent &event, Point2I offset ) +{ +#ifdef TORQUE_TOOLS + // This shouldn't be called if it's not design time, but check just incase + if ( GuiControl::smDesignTime ) + { + GuiEditCtrl* edit = GuiControl::smEditorHandle; + if( edit ) + edit->select( this ); + } + + return Parent::onMouseDownEditor( event, offset ); +#else + return false; +#endif +} + + +GuiControl *GuiTabPageCtrl::findNextTabable(GuiControl *curResponder, bool firstCall) +{ + //set the global if this is the first call (directly from the canvas) + if (firstCall) + { + GuiControl::smCurResponder = NULL; + } + + //if the window does not already contain the first responder, return false + //ie. Can't tab into or out of a window + if (! ControlIsChild(curResponder)) + { + return NULL; + } + + //loop through, checking each child to see if it is the one that follows the firstResponder + GuiControl *tabCtrl = NULL; + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + tabCtrl = ctrl->findNextTabable(curResponder, false); + if (tabCtrl) break; + } + + //to ensure the tab cycles within the current window... + if (! tabCtrl) + { + tabCtrl = findFirstTabable(); + } + + mFirstResponder = tabCtrl; + return tabCtrl; +} + +GuiControl *GuiTabPageCtrl::findPrevTabable(GuiControl *curResponder, bool firstCall) +{ + if (firstCall) + { + GuiControl::smPrevResponder = NULL; + } + + //if the window does not already contain the first responder, return false + //ie. Can't tab into or out of a window + if (! ControlIsChild(curResponder)) + { + return NULL; + } + + //loop through, checking each child to see if it is the one that follows the firstResponder + GuiControl *tabCtrl = NULL; + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + tabCtrl = ctrl->findPrevTabable(curResponder, false); + if (tabCtrl) break; + } + + //to ensure the tab cycles within the current window... + if (! tabCtrl) + { + tabCtrl = findLastTabable(); + } + + mFirstResponder = tabCtrl; + return tabCtrl; +} + +void GuiTabPageCtrl::setText(const char *txt) +{ + Parent::setText( txt ); + + GuiControl *parent = getParent(); + if( parent ) + parent->setUpdate(); +}; + + +void GuiTabPageCtrl::selectWindow(void) +{ + //first make sure this window is the front most of its siblings + GuiControl *parent = getParent(); + if (parent) + { + parent->pushObjectToBack(this); + } + + //also set the first responder to be the one within this window + setFirstResponder(mFirstResponder); +} + +void GuiTabPageCtrl::onRender(Point2I offset,const RectI &updateRect) +{ + // Call directly into GuiControl to skip the GuiTextCtrl parent render + GuiControl::onRender( offset, updateRect ); +} diff --git a/gui/controls/guiTabPageCtrl.h b/gui/controls/guiTabPageCtrl.h new file mode 100644 index 0000000..7d25c4d --- /dev/null +++ b/gui/controls/guiTabPageCtrl.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITABPAGECTRL_H_ +#define _GUITABPAGECTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif + +class GuiTabPageCtrl : public GuiTextCtrl +{ + private: + typedef GuiTextCtrl Parent; + + Point2I mMinSize; + bool mFitBook; ///< Resize to fit book when first added + S32 mTabIndex; + + public: + GuiTabPageCtrl(); + + DECLARE_CONOBJECT(GuiTabPageCtrl); + DECLARE_CATEGORY( "Gui Containers" ); + + static void initPersistFields(); + + bool onWake(); ///< The page awakens (becomes active)! + void onSleep(); ///< The page sleeps (zzzzZZ - becomes inactive) + + bool getFitBook() { return mFitBook; } + void setFitBook(bool state) { mFitBook = state; } + + GuiControl* findHitControl(const Point2I &pt, S32 initialLayer = -1); ///< Find which control is hit by the mouse starting at a specified layer + + void onMouseDown(const GuiEvent &event); ///< Called when a mouseDown event occurs + bool onMouseDownEditor(const GuiEvent &event, Point2I offset ); ///< Called when a mouseDown event occurs and the GUI editor is active + + S32 getTabIndex(void) { return mTabIndex; } ///< Get the tab index of this control + + //only cycle tabs through the current window, so overwrite the method + GuiControl* findNextTabable(GuiControl *curResponder, bool firstCall = true); + GuiControl* findPrevTabable(GuiControl *curResponder, bool firstCall = true); + + void selectWindow(void); ///< Select this window + + virtual void setText(const char *txt = NULL); ///< Override setText function to signal parent we need to update. + + void onRender(Point2I offset, const RectI &updateRect); ///< Called when it's time to render this page to the scene +}; + +#endif //_GUI_WINDOW_CTRL_H diff --git a/gui/controls/guiTextCtrl.cpp b/gui/controls/guiTextCtrl.cpp new file mode 100644 index 0000000..921bf22 --- /dev/null +++ b/gui/controls/guiTextCtrl.cpp @@ -0,0 +1,197 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiTextCtrl.h" + +#include "gui/core/guiDefaultControlRender.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "core/color.h" +#include "i18n/lang.h" +#include "gfx/gfxDrawUtil.h" + + +// ----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT( GuiTextCtrl ); + +GuiTextCtrl::GuiTextCtrl() +{ + //default fonts + mInitialText = StringTable->insert(""); + mInitialTextID = StringTable->insert(""); + mText[0] = '\0'; + mMaxStrLen = GuiTextCtrl::MAX_STRING_LENGTH; +} + +ConsoleMethod( GuiTextCtrl, setText, void, 3, 3, "obj.setText( newText )" ) +{ + TORQUE_UNUSED(argc); + object->setText( argv[2] ); +} + +ConsoleMethod( GuiTextCtrl, setTextID, void, 3, 3, "obj.setTextID( newText )" ) +{ + TORQUE_UNUSED(argc); + object->setTextID( argv[2] ); +} +void GuiTextCtrl::initPersistFields() +{ + //addField( "text", TypeCaseString, Offset( mInitialText, GuiTextCtrl ) ); + addProtectedField("text", TypeCaseString, Offset(mInitialText, GuiTextCtrl), setText, getTextProperty, ""); + addField( "textID", TypeString, Offset( mInitialTextID, GuiTextCtrl ) ); + addField( "maxLength", TypeS32, Offset( mMaxStrLen, GuiTextCtrl ) ); + Parent::initPersistFields(); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +bool GuiTextCtrl::onAdd() +{ + if(!Parent::onAdd()) + return false; + dStrncpy(mText, (UTF8*)mInitialText, MAX_STRING_LENGTH); + mText[MAX_STRING_LENGTH] = '\0'; + return true; +} + +void GuiTextCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); + if(mInitialTextID && *mInitialTextID != 0) + setTextID(mInitialTextID); + else + setText(mInitialText); +} + +bool GuiTextCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + mFont = mProfile->mFont; + AssertFatal(mFont, "GuiTextCtrl::onWake: invalid font in profile" ); + if(mInitialTextID && *mInitialTextID != 0) + setTextID(mInitialTextID); + + if ( mConsoleVariable[0] ) + { + const char *txt = Con::getVariable( mConsoleVariable ); + if ( txt ) + { + if ( dStrlen( txt ) > mMaxStrLen ) + { + char* buf = new char[mMaxStrLen + 1]; + dStrncpy( buf, txt, mMaxStrLen ); + buf[mMaxStrLen] = 0; + setScriptValue( buf ); + delete [] buf; + } + else + setScriptValue( txt ); + } + } + + //resize + autoResize(); + + return true; +} + +void GuiTextCtrl::autoResize() +{ + Point2I newExtents = getExtent(); + if ( mProfile->mAutoSizeWidth ) + newExtents.x = mFont->getStrWidth((const UTF8 *) mText ); + if ( mProfile->mAutoSizeHeight ) + newExtents.y = mFont->getHeight() + 4; + + if ( mProfile->mAutoSizeWidth || mProfile->mAutoSizeHeight) + setExtent( newExtents ); +} + +void GuiTextCtrl::onSleep() +{ + mFont = NULL; + Parent::onSleep(); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiTextCtrl::setText(const char *txt) +{ + //make sure we don't call this before onAdd(); + if( !mProfile ) + return; + + if (txt) + dStrncpy(mText, (UTF8*)txt, MAX_STRING_LENGTH); + mText[MAX_STRING_LENGTH] = '\0'; + + //Make sure we have a font + mProfile->incRefCount(); + mFont = mProfile->mFont; + + //resize + autoResize(); + + setVariable((char*)mText); + setUpdate(); + + //decrement the profile referrence + mProfile->decRefCount(); +} + +void GuiTextCtrl::setTextID(const char *id) +{ + S32 n = Con::getIntVariable(id, -1); + if(n != -1) + { + mInitialTextID = StringTable->insert(id); + setTextID(n); + } +} +void GuiTextCtrl::setTextID(S32 id) +{ + const UTF8 *str = getGUIString(id); + if(str) + setText((const char*)str); + //mInitialTextID = id; +} + +//------------------------------------------------------------------------------ +void GuiTextCtrl::onPreRender() +{ + const char * var = getVariable(); + if(var && var[0] && dStricmp((char*)mText, var)) + setText(var); +} + +//------------------------------------------------------------------------------ +void GuiTextCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + renderBorder( RectI( offset, getExtent() ), mProfile ); + + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor ); + renderJustifiedText(offset, getExtent(), (char*)mText); + + //render the child controls + renderChildControls(offset, updateRect); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +const char *GuiTextCtrl::getScriptValue() +{ + return getText(); +} + +void GuiTextCtrl::setScriptValue(const char *val) +{ + setText(val); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +// EOF // diff --git a/gui/controls/guiTextCtrl.h b/gui/controls/guiTextCtrl.h new file mode 100644 index 0000000..0f41b60 --- /dev/null +++ b/gui/controls/guiTextCtrl.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTCTRL_H_ +#define _GUITEXTCTRL_H_ + +#ifndef _GFONT_H_ +#include "gfx/gFont.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _GUICONTAINER_H_ +#include "gui/containers/guiContainer.h" +#endif + +class GuiTextCtrl : public GuiContainer +{ +private: + typedef GuiContainer Parent; + +public: + enum Constants { MAX_STRING_LENGTH = 1024 }; + + +protected: + StringTableEntry mInitialText; + StringTableEntry mInitialTextID; + UTF8 mText[MAX_STRING_LENGTH + 1]; + S32 mMaxStrLen; // max string len, must be less then or equal to 255 + Resource mFont; + +public: + + //creation methods + DECLARE_CONOBJECT(GuiTextCtrl); + DECLARE_CATEGORY( "Gui Text" ); + DECLARE_DESCRIPTION( "A control that displays a single line of text." ); + + GuiTextCtrl(); + static void initPersistFields(); + + //Parental methods + bool onAdd(); + virtual bool onWake(); + virtual void onSleep(); + + //text methods + virtual void setText(const char *txt = NULL); + virtual void setTextID(S32 id); + virtual void setTextID(const char *id); + const char *getText() { return (const char*)mText; } + + // Text Property Accessors + static bool setText(void* obj, const char* data) { static_cast(obj)->setText(data); return true; } + static const char* getTextProperty(void* obj, const char* data) { return static_cast(obj)->getText(); } + + + void inspectPostApply(); + //rendering methods + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + void displayText( S32 xOffset, S32 yOffset ); + + // resizing + void autoResize(); + + //Console methods + const char *getScriptValue(); + void setScriptValue(const char *value); +}; + +#endif //_GUI_TEXT_CONTROL_H_ diff --git a/gui/controls/guiTextEditCtrl.cpp b/gui/controls/guiTextEditCtrl.cpp new file mode 100644 index 0000000..3cec7bd --- /dev/null +++ b/gui/controls/guiTextEditCtrl.cpp @@ -0,0 +1,1647 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiTextEditCtrl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gui/controls/guiMLTextCtrl.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "core/frameAllocator.h" +#include "sfx/sfxProfile.h" +#include "core/strings/unicode.h" + + +IMPLEMENT_CONOBJECT(GuiTextEditCtrl); + +U32 GuiTextEditCtrl::smNumAwake = 0; + +GuiTextEditCtrl::GuiTextEditCtrl() +{ + mInsertOn = true; + mBlockStart = 0; + mBlockEnd = 0; + mCursorPos = 0; + mCursorOn = false; + mNumFramesElapsed = 0; + + mDragHit = false; + mTabComplete = false; + mScrollDir = 0; + + mUndoBlockStart = 0; + mUndoBlockEnd = 0; + mUndoCursorPos = 0; + mPasswordText = false; + + mSinkAllKeyEvents = false; + + mActive = true; + + mTextOffsetReset = true; + + mHistoryDirty = false; + mHistorySize = 0; + mHistoryLast = -1; + mHistoryIndex = 0; + mHistoryBuf = NULL; + + mValidateCommand = StringTable->insert(""); + mEscapeCommand = StringTable->insert( "" ); +#if defined(__MACOSX__) + UTF8 bullet[4] = { 0xE2, 0x80, 0xA2, 0 }; + + mPasswordMask = StringTable->insert( bullet ); +#else + mPasswordMask = StringTable->insert( "*" ); +#endif + mDeniedSound = dynamic_cast( Sim::findObject( "InputDeniedSound" ) ); +} + +GuiTextEditCtrl::~GuiTextEditCtrl() +{ + //delete the history buffer if it exists + if (mHistoryBuf) + { + for (S32 i = 0; i < mHistorySize; i++) + delete [] mHistoryBuf[i]; + + delete [] mHistoryBuf; + } +} + +void GuiTextEditCtrl::initPersistFields() +{ + addField("validate", TypeString, Offset(mValidateCommand, GuiTextEditCtrl)); + addField("escapeCommand", TypeString, Offset(mEscapeCommand, GuiTextEditCtrl)); + addField("historySize", TypeS32, Offset(mHistorySize, GuiTextEditCtrl)); + addField("password", TypeBool, Offset(mPasswordText, GuiTextEditCtrl)); + addField("tabComplete", TypeBool, Offset(mTabComplete, GuiTextEditCtrl)); + addField("deniedSound", TypeSFXProfilePtr, Offset(mDeniedSound, GuiTextEditCtrl)); + addField("sinkAllKeyEvents", TypeBool, Offset(mSinkAllKeyEvents, GuiTextEditCtrl)); + addField("passwordMask", TypeString, Offset(mPasswordMask, GuiTextEditCtrl)); + + Parent::initPersistFields(); +} + +bool GuiTextEditCtrl::onAdd() +{ + if ( ! Parent::onAdd() ) + return false; + + //create the history buffer + if ( mHistorySize > 0 ) + { + mHistoryBuf = new UTF16*[mHistorySize]; + for ( S32 i = 0; i < mHistorySize; i++ ) + { + mHistoryBuf[i] = new UTF16[GuiTextCtrl::MAX_STRING_LENGTH + 1]; + mHistoryBuf[i][0] = '\0'; + } + } + + if( mText && mText[0] ) + { + setText(mText); + } + + return true; +} + +void GuiTextEditCtrl::onStaticModified(const char* slotName, const char* newValue) +{ + if(!dStricmp(slotName, "text")) + setText(mText); +} + +bool GuiTextEditCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + // CodeReview - This static should be on the canvas. BJG 3/25/07 + // If this is the first awake text edit control, enable keyboard translation + if (smNumAwake == 0) + getRoot()->enableKeyboardTranslation(); + ++smNumAwake; + + return true; +} + +void GuiTextEditCtrl::onSleep() +{ + Parent::onSleep(); + + GuiCanvas *root = getRoot(); + + // If this is the last awake text edit control, disable keyboard translation + --smNumAwake; + if (smNumAwake == 0) + root->disableKeyboardTranslation(); + + // If we're still the first responder then + // restore the accelerators. + if ( mFirstResponder == this ) + root->setNativeAcceleratorsEnabled( true ); +} + +void GuiTextEditCtrl::execConsoleCallback() +{ + // Execute the console command! + if ( mConsoleCommand[0] ) + { + char buf[16]; + dSprintf( buf, sizeof( buf ), "%d", getId() ); + Con::setVariable( "$ThisControl", buf ); + Con::evaluate( mConsoleCommand, false ); + } + + // Update the console variable: + if ( mConsoleVariable[0] ) + Con::setVariable( mConsoleVariable, mTextBuffer.getPtr8() ); +} + +void GuiTextEditCtrl::updateHistory( StringBuffer *inTxt, bool moveIndex ) +{ + const UTF16* txt = inTxt->getPtr(); + + if(!txt) + return; + + if(!mHistorySize) + return; + + // see if it's already in + if(mHistoryLast == -1 || dStrcmp(txt, mHistoryBuf[mHistoryLast])) + { + if(mHistoryLast == mHistorySize-1) // we're at the history limit... shuffle the pointers around: + { + UTF16 *first = mHistoryBuf[0]; + for(U32 i = 0; i < mHistorySize - 1; i++) + mHistoryBuf[i] = mHistoryBuf[i+1]; + mHistoryBuf[mHistorySize-1] = first; + if(mHistoryIndex > 0) + mHistoryIndex--; + } + else + mHistoryLast++; + + inTxt->getCopy(mHistoryBuf[mHistoryLast], GuiTextCtrl::MAX_STRING_LENGTH); + mHistoryBuf[mHistoryLast][GuiTextCtrl::MAX_STRING_LENGTH] = '\0'; + } + + if(moveIndex) + mHistoryIndex = mHistoryLast + 1; +} + +void GuiTextEditCtrl::getText( char *dest ) +{ + if ( dest ) + mTextBuffer.getCopy8((UTF8*)dest, GuiTextCtrl::MAX_STRING_LENGTH+1); +} + +void GuiTextEditCtrl::getRenderText(char *dest) +{ + getText( dest ); +} + +void GuiTextEditCtrl::setText( const UTF8 *txt ) +{ + if(txt && txt[0] != 0) + { + Parent::setText(txt); + mTextBuffer.set( txt ); + } + else + mTextBuffer.set( "" ); + + mCursorPos = mTextBuffer.length(); +} + +void GuiTextEditCtrl::setText( const UTF16* txt) +{ + if(txt && txt[0] != 0) + { + UTF8* txt8 = convertUTF16toUTF8( txt ); + Parent::setText( txt8 ); + delete[] txt8; + mTextBuffer.set( txt ); + } + else + { + Parent::setText(""); + mTextBuffer.set(""); + } + + mCursorPos = mTextBuffer.length(); +} + +bool GuiTextEditCtrl::isAllTextSelected() +{ + if( mBlockStart == 0 && mBlockEnd == mTextBuffer.length() ) + return true; + else + return false; +} + +void GuiTextEditCtrl::selectAllText() +{ + mBlockStart = 0; + mBlockEnd = mTextBuffer.length(); + setUpdate(); +} + +void GuiTextEditCtrl::clearSelectedText() +{ + mBlockStart = 0; + mBlockEnd = 0; + setUpdate(); +} + +void GuiTextEditCtrl::forceValidateText() +{ + if ( mValidateCommand[0] ) + { + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", getId()); + Con::setVariable("$ThisControl", buf); + Con::evaluate( mValidateCommand, false ); + } +} + +void GuiTextEditCtrl::setCursorPos( const S32 newPos ) +{ + S32 charCount = mTextBuffer.length(); + S32 realPos = newPos > charCount ? charCount : newPos < 0 ? 0 : newPos; + if ( realPos != mCursorPos ) + { + mCursorPos = realPos; + setUpdate(); + } +} + +S32 GuiTextEditCtrl::calculateCursorPos( const Point2I &globalPos ) +{ + Point2I ctrlOffset = localToGlobalCoord( Point2I( 0, 0 ) ); + S32 charLength = 0; + S32 curX; + + curX = globalPos.x - ctrlOffset.x; + setUpdate(); + + //if the cursor is too far to the left + if ( curX < 0 ) + return -1; + + //if the cursor is too far to the right + if ( curX >= ctrlOffset.x + getExtent().x ) + return -2; + + curX = globalPos.x - mTextOffset.x; + S32 count=0; + if(mTextBuffer.length() == 0) + return 0; + + for(count=0; countisValidChar(c)) + continue; + + if(mPasswordText) + charLength += mFont->getCharXIncrement( mPasswordMask[0] ); + else + charLength += mFont->getCharXIncrement( c ); + + if ( charLength > curX ) + break; + } + + return count; +} + +void GuiTextEditCtrl::onMouseDown( const GuiEvent &event ) +{ + mDragHit = false; + + // If we have a double click, select all text. Otherwise + // act as before by clearing any selection. + bool doubleClick = (event.mouseClickCount > 1); + if(doubleClick) + { + selectAllText(); + + } else + { + //undo any block function + mBlockStart = 0; + mBlockEnd = 0; + } + + //find out where the cursor should be + S32 pos = calculateCursorPos( event.mousePoint ); + + // if the position is to the left + if ( pos == -1 ) + mCursorPos = 0; + else if ( pos == -2 ) //else if the position is to the right + mCursorPos = mTextBuffer.length(); + else //else set the mCursorPos + mCursorPos = pos; + + //save the mouseDragPos + mMouseDragStart = mCursorPos; + + // lock the mouse + mouseLock(); + + //set the drag var + mDragHit = true; + + //let the parent get the event + setFirstResponder(); +} + +void GuiTextEditCtrl::onMouseDragged( const GuiEvent &event ) +{ + S32 pos = calculateCursorPos( event.mousePoint ); + + // if the position is to the left + if ( pos == -1 ) + mScrollDir = -1; + else if ( pos == -2 ) // the position is to the right + mScrollDir = 1; + else // set the new cursor position + { + mScrollDir = 0; + mCursorPos = pos; + } + + // update the block: + mBlockStart = getMin( mCursorPos, mMouseDragStart ); + mBlockEnd = getMax( mCursorPos, mMouseDragStart ); + if ( mBlockStart < 0 ) + mBlockStart = 0; + + if ( mBlockStart == mBlockEnd ) + mBlockStart = mBlockEnd = 0; + + //let the parent get the event + Parent::onMouseDragged(event); +} + +void GuiTextEditCtrl::onMouseUp(const GuiEvent &event) +{ + TORQUE_UNUSED(event); + mDragHit = false; + mScrollDir = 0; + mouseUnlock(); +} + +void GuiTextEditCtrl::saveUndoState() +{ + //save the current state + mUndoText.set(&mTextBuffer); + mUndoBlockStart = mBlockStart; + mUndoBlockEnd = mBlockEnd; + mUndoCursorPos = mCursorPos; +} + +void GuiTextEditCtrl::onCopy(bool andCut) +{ + // Don't copy/cut password field! + if(mPasswordText) + return; + + if (mBlockEnd > 0) + { + //save the current state + saveUndoState(); + + //copy the text to the clipboard + UTF8* clipBuff = mTextBuffer.createSubstring8(mBlockStart, mBlockEnd - mBlockStart); + Platform::setClipboard(clipBuff); + delete[] clipBuff; + + //if we pressed the cut shortcut, we need to cut the selected text from the control... + if (andCut) + { + mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart); + mCursorPos = mBlockStart; + } + + mBlockStart = 0; + mBlockEnd = 0; + } + +} + +void GuiTextEditCtrl::onPaste() +{ + //first, make sure there's something in the clipboard to copy... + const UTF8 *clipboard = Platform::getClipboard(); + if(dStrlen(clipboard) <= 0) + return; + + //save the current state + saveUndoState(); + + //delete anything hilited + if (mBlockEnd > 0) + { + mTextBuffer.cut(mBlockStart, mBlockEnd - mBlockStart); + mCursorPos = mBlockStart; + mBlockStart = 0; + mBlockEnd = 0; + } + + // We'll be converting to UTF16, and maybe trimming the string, + // so let's use a StringBuffer, for convinience. + StringBuffer pasteText(clipboard); + + // Space left after we remove the highlighted text + S32 stringLen = mTextBuffer.length(); + + // Trim down to fit in a buffer of size mMaxStrLen + S32 pasteLen = pasteText.length(); + + if(stringLen + pasteLen > mMaxStrLen) + { + pasteLen = mMaxStrLen - stringLen; + + pasteText.cut(pasteLen, pasteText.length() - pasteLen); + } + + if (mCursorPos == stringLen) + { + mTextBuffer.append(pasteText); + } + else + { + mTextBuffer.insert(mCursorPos, pasteText); + } + + mCursorPos += pasteLen; +} + +void GuiTextEditCtrl::onUndo() +{ + StringBuffer tempBuffer; + S32 tempBlockStart; + S32 tempBlockEnd; + S32 tempCursorPos; + + //save the current + tempBuffer.set(&mTextBuffer); + tempBlockStart = mBlockStart; + tempBlockEnd = mBlockEnd; + tempCursorPos = mCursorPos; + + //restore the prev + mTextBuffer.set(&mUndoText); + mBlockStart = mUndoBlockStart; + mBlockEnd = mUndoBlockEnd; + mCursorPos = mUndoCursorPos; + + //update the undo + mUndoText.set(&tempBuffer); + mUndoBlockStart = tempBlockStart; + mUndoBlockEnd = tempBlockEnd; + mUndoCursorPos = tempCursorPos; +} + +bool GuiTextEditCtrl::onKeyDown(const GuiEvent &event) +{ + if ( !isActive() ) + return false; + + S32 stringLen = mTextBuffer.length(); + setUpdate(); + + // Ugly, but now I'm cool like MarkF. + if(event.keyCode == KEY_BACKSPACE) + goto dealWithBackspace; + + if ( event.modifier & SI_SHIFT ) + { + + // Added support for word jump selection. + + if ( event.modifier & SI_CTRL ) + { + switch ( event.keyCode ) + { + case KEY_LEFT: + { + S32 newpos = findPrevWord(); + + if ( mBlockStart == mBlockEnd ) + { + // There was not already a selection so start a new one. + mBlockStart = newpos; + mBlockEnd = mCursorPos; + } + else + { + // There was a selection already... + // In this case the cursor MUST be at either the + // start or end of that selection. + + if ( mCursorPos == mBlockStart ) + { + // We are at the start block and traveling left so + // just extend the start block farther left. + mBlockStart = newpos; + } + else + { + // We are at the end block BUT traveling left + // back towards the start block... + + if ( newpos > mBlockStart ) + { + // We haven't overpassed the existing start block + // so just trim back the end block. + mBlockEnd = newpos; + } + else if ( newpos == mBlockStart ) + { + // We are back at the start, so no more selection. + mBlockEnd = mBlockStart = 0; + } + else + { + // Only other option, we just backtracked PAST + // our original start block. + // So the new position becomes the start block + // and the old start block becomes the end block. + mBlockEnd = mBlockStart; + mBlockStart = newpos; + } + } + } + + mCursorPos = newpos; + + return true; + } + + case KEY_RIGHT: + { + S32 newpos = findNextWord(); + + if ( mBlockStart == mBlockEnd ) + { + // There was not already a selection so start a new one. + mBlockStart = mCursorPos; + mBlockEnd = newpos; + } + else + { + // There was a selection already... + // In this case the cursor MUST be at either the + // start or end of that selection. + + if ( mCursorPos == mBlockEnd ) + { + // We are at the end block and traveling right so + // just extend the end block farther right. + mBlockEnd = newpos; + } + else + { + // We are at the start block BUT traveling right + // back towards the end block... + + if ( newpos < mBlockEnd ) + { + // We haven't overpassed the existing end block + // so just trim back the start block. + mBlockStart = newpos; + } + else if ( newpos == mBlockEnd ) + { + // We are back at the end, so no more selection. + mBlockEnd = mBlockStart = 0; + } + else + { + // Only other option, we just backtracked PAST + // our original end block. + // So the new position becomes the end block + // and the old end block becomes the start block. + mBlockStart = mBlockEnd; + mBlockEnd = newpos; + } + } + } + + mCursorPos = newpos; + + return true; + } + + default: + break; + } + } + + // End support for word jump selection. + + + switch ( event.keyCode ) + { + case KEY_TAB: + if ( mTabComplete ) + { + Con::executef( this, "onTabComplete", "1" ); + return true; + } + break; // We don't want to fall through if we don't handle the TAB here. + + case KEY_HOME: + mBlockStart = 0; + mBlockEnd = mCursorPos; + mCursorPos = 0; + return true; + + case KEY_END: + mBlockStart = mCursorPos; + mBlockEnd = stringLen; + mCursorPos = stringLen; + return true; + + case KEY_LEFT: + if ((mCursorPos > 0) & (stringLen > 0)) + { + //if we already have a selected block + if (mCursorPos == mBlockEnd) + { + mCursorPos--; + mBlockEnd--; + if (mBlockEnd == mBlockStart) + { + mBlockStart = 0; + mBlockEnd = 0; + } + } + else { + mCursorPos--; + mBlockStart = mCursorPos; + + if (mBlockEnd == 0) + { + mBlockEnd = mCursorPos + 1; + } + } + } + return true; + + case KEY_RIGHT: + if (mCursorPos < stringLen) + { + if ((mCursorPos == mBlockStart) && (mBlockEnd > 0)) + { + mCursorPos++; + mBlockStart++; + if (mBlockStart == mBlockEnd) + { + mBlockStart = 0; + mBlockEnd = 0; + } + } + else + { + if (mBlockEnd == 0) + { + mBlockStart = mCursorPos; + mBlockEnd = mCursorPos; + } + mCursorPos++; + mBlockEnd++; + } + } + return true; + + case KEY_RETURN: + case KEY_NUMPADENTER: + + return dealWithEnter(false); + + default: + break; + } + } + else if (event.modifier & SI_CTRL) + { + switch(event.keyCode) + { + // Added UNIX emacs key bindings - just a little hack here... + + // BJGTODO: Add vi bindings. + + // Ctrl-B - move one character back + case KEY_B: + { + GuiEvent new_event; + new_event.modifier = 0; + new_event.keyCode = KEY_LEFT; + return(onKeyDown(new_event)); + } + + // Ctrl-F - move one character forward + case KEY_F: + { + GuiEvent new_event; + new_event.modifier = 0; + new_event.keyCode = KEY_RIGHT; + return(onKeyDown(new_event)); + } + + // Ctrl-A - move to the beginning of the line + case KEY_A: + { + GuiEvent new_event; + new_event.modifier = 0; + new_event.keyCode = KEY_HOME; + return(onKeyDown(new_event)); + } + + // Ctrl-E - move to the end of the line + case KEY_E: + { + GuiEvent new_event; + new_event.modifier = 0; + new_event.keyCode = KEY_END; + return(onKeyDown(new_event)); + } + + // Ctrl-P - move backward in history + case KEY_P: + { + GuiEvent new_event; + new_event.modifier = 0; + new_event.keyCode = KEY_UP; + return(onKeyDown(new_event)); + } + + // Ctrl-N - move forward in history + case KEY_N: + { + GuiEvent new_event; + new_event.modifier = 0; + new_event.keyCode = KEY_DOWN; + return(onKeyDown(new_event)); + } + + // Ctrl-D - delete under cursor + case KEY_D: + { + GuiEvent new_event; + new_event.modifier = 0; + new_event.keyCode = KEY_DELETE; + return(onKeyDown(new_event)); + } + + case KEY_U: + { + GuiEvent new_event; + new_event.modifier = SI_CTRL; + new_event.keyCode = KEY_DELETE; + return(onKeyDown(new_event)); + } + + // End added UNIX emacs key bindings + + // Adding word jump navigation. + + case KEY_LEFT: + { + + mCursorPos = findPrevWord(); + mBlockStart = 0; + mBlockEnd = 0; + return true; + } + + case KEY_RIGHT: + { + mCursorPos = findNextWord(); + mBlockStart = 0; + mBlockEnd = 0; + return true; + } + +#if !defined(TORQUE_OS_MAC) + // windows style cut / copy / paste / undo keybinds + case KEY_C: + case KEY_X: + { + // copy, and cut the text if we hit ctrl-x + onCopy( event.keyCode==KEY_X ); + return true; + } + case KEY_V: + { + onPaste(); + + // Execute the console command! + execConsoleCallback(); + return true; + } + + case KEY_Z: + if (! mDragHit) + { + onUndo(); + return true; + } +#endif + + case KEY_DELETE: + case KEY_BACKSPACE: + //save the current state + saveUndoState(); + + //delete everything in the field + mTextBuffer.set(""); + mCursorPos = 0; + mBlockStart = 0; + mBlockEnd = 0; + + execConsoleCallback(); + + return true; + default: + break; + } + } +#if defined(TORQUE_OS_MAC) + // mac style cut / copy / paste / undo keybinds + else if (event.modifier & SI_ALT) + { + // Added Mac cut/copy/paste/undo keys + // Mac command key maps to alt in torque. + switch(event.keyCode) + { + case KEY_C: + case KEY_X: + { + // copy, and cut the text if we hit cmd-x + onCopy( event.keyCode==KEY_X ); + return true; + } + case KEY_V: + { + onPaste(); + + // Execute the console command! + execConsoleCallback(); + + return true; + } + + case KEY_Z: + if (! mDragHit) + { + onUndo(); + return true; + } + + default: + break; + } + } +#endif + else + { + switch(event.keyCode) + { + case KEY_ESCAPE: + if ( mEscapeCommand[0] ) + { + Con::setVariable("$ThisControl", avar("%d", getId())); + Con::evaluate( mEscapeCommand ); + return( true ); + } + return( Parent::onKeyDown( event ) ); + + case KEY_RETURN: + case KEY_NUMPADENTER: + + return dealWithEnter(true); + + case KEY_UP: + { + if(mHistoryDirty) + { + updateHistory(&mTextBuffer, false); + mHistoryDirty = false; + } + + mHistoryIndex--; + + if(mHistoryIndex >= 0 && mHistoryIndex <= mHistoryLast) + setText(mHistoryBuf[mHistoryIndex]); + else if(mHistoryIndex < 0) + mHistoryIndex = 0; + + return true; + } + + case KEY_DOWN: + if(mHistoryDirty) + { + updateHistory(&mTextBuffer, false); + mHistoryDirty = false; + } + mHistoryIndex++; + if(mHistoryIndex > mHistoryLast) + { + mHistoryIndex = mHistoryLast + 1; + setText(""); + } + else + setText(mHistoryBuf[mHistoryIndex]); + return true; + + case KEY_LEFT: + + // If we have a selection put the cursor to the left side of it. + if ( mBlockStart != mBlockEnd ) + { + mCursorPos = mBlockStart; + mBlockStart = mBlockEnd = 0; + } + else + { + mBlockStart = mBlockEnd = 0; + mCursorPos = getMax( mCursorPos - 1, 0 ); + } + + return true; + + case KEY_RIGHT: + + // If we have a selection put the cursor to the right side of it. + if ( mBlockStart != mBlockEnd ) + { + mCursorPos = mBlockEnd; + mBlockStart = mBlockEnd = 0; + } + else + { + mBlockStart = mBlockEnd = 0; + mCursorPos = getMin( mCursorPos + 1, stringLen ); + } + + return true; + + case KEY_BACKSPACE: +dealWithBackspace: + //save the current state + saveUndoState(); + + if (mBlockEnd > 0) + { + mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart); + mCursorPos = mBlockStart; + mBlockStart = 0; + mBlockEnd = 0; + mHistoryDirty = true; + + // Execute the console command! + execConsoleCallback(); + + } + else if (mCursorPos > 0) + { + mTextBuffer.cut(mCursorPos-1, 1); + mCursorPos--; + mHistoryDirty = true; + + // Execute the console command! + execConsoleCallback(); + } + return true; + + case KEY_DELETE: + //save the current state + saveUndoState(); + + if (mBlockEnd > 0) + { + mHistoryDirty = true; + mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart); + + mCursorPos = mBlockStart; + mBlockStart = 0; + mBlockEnd = 0; + + // Execute the console command! + execConsoleCallback(); + } + else if (mCursorPos < stringLen) + { + mHistoryDirty = true; + mTextBuffer.cut(mCursorPos, 1); + + // Execute the console command! + execConsoleCallback(); + } + return true; + + case KEY_INSERT: + mInsertOn = !mInsertOn; + return true; + + case KEY_HOME: + mBlockStart = 0; + mBlockEnd = 0; + mCursorPos = 0; + return true; + + case KEY_END: + mBlockStart = 0; + mBlockEnd = 0; + mCursorPos = stringLen; + return true; + + default: + break; + + } + } + + switch ( event.keyCode ) + { + case KEY_TAB: + if ( mTabComplete ) + { + Con::executef( this, "onTabComplete", "0" ); + return( true ); + } + case KEY_UP: + case KEY_DOWN: + case KEY_ESCAPE: + return Parent::onKeyDown( event ); + default: + break; + } + + // If it's a type-able key event, eat it. + // + // This is pretty lame since we aren't checking if numlock is pressed + // or for any modifier keys which CAN affect if its REALLY an type-able + // character. + // + // Probably this information should be sent from above, like "is char input + // event". In the mean time, changed this to check STATE_LOWER instead of + // STATE_UPPER because numpad keys have no STATE_UPPER value. + if ( event.keyCode && Input::getAscii( event.keyCode, STATE_LOWER ) ) + return true; + + if ( mFont->isValidChar( event.ascii ) ) + { + handleCharInput( event.ascii ); + + return true; + } + + //not handled - pass the event to it's parent + + // Or eat it if that's appropriate. + if (mSinkAllKeyEvents) + return true; + + return Parent::onKeyDown( event ); +} + +bool GuiTextEditCtrl::dealWithEnter( bool clearResponder ) +{ + //first validate + if (mProfile->mReturnTab) + { + onLoseFirstResponder(); + } + + updateHistory(&mTextBuffer, true); + mHistoryDirty = false; + + //next exec the alt console command + execAltConsoleCallback(); + + // Notify of Return + if ( isMethod("onReturn") ) + Con::executef( this, "onReturn" ); + + if (mProfile->mReturnTab) + { + GuiCanvas *root = getRoot(); + if (root) + { + root->tabNext(); + return true; + } + } + + if( clearResponder ) + clearFirstResponder(true); + + return true; +} + +void GuiTextEditCtrl::setFirstResponder() +{ + Parent::setFirstResponder(); + + GuiCanvas *root = getRoot(); + if (root != NULL) + { + root->enableKeyboardTranslation(); + + + // If the native OS accelerator keys are not disabled + // then some key events like Delete, ctrl+V, etc may + // not make it down to us. + root->setNativeAcceleratorsEnabled( false ); + } +} + +void GuiTextEditCtrl::onLoseFirstResponder() +{ + GuiCanvas *root = getRoot(); + root->setNativeAcceleratorsEnabled( true ); + root->disableKeyboardTranslation(); + + //first, update the history + updateHistory( &mTextBuffer, true ); + + //execute the validate command + if ( mValidateCommand[0] ) + { + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", getId()); + Con::setVariable("$ThisControl", buf); + Con::evaluate( mValidateCommand, false ); + } + + if( isMethod( "onValidate" ) ) + Con::executef( this, "onValidate" ); + + // Redraw the control: + setUpdate(); + + // Lost Responder + Parent::onLoseFirstResponder(); +} + +void GuiTextEditCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + RectI ctrlRect( offset, getExtent() ); + + //if opaque, fill the update rect with the fill color + if ( mProfile->mOpaque ) + { + if(isFirstResponder()) + GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColorHL ); + else + GFX->getDrawUtil()->drawRectFill( ctrlRect, mProfile->mFillColor ); + } + + //if there's a border, draw the border + if ( mProfile->mBorder ) + renderBorder( ctrlRect, mProfile ); + + drawText( ctrlRect, isFirstResponder() ); +} + +void GuiTextEditCtrl::onPreRender() +{ + if ( isFirstResponder() ) + { + U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped; + mNumFramesElapsed++; + if ( ( timeElapsed > 500 ) && ( mNumFramesElapsed > 3 ) ) + { + mCursorOn = !mCursorOn; + mTimeLastCursorFlipped = Platform::getVirtualMilliseconds(); + mNumFramesElapsed = 0; + setUpdate(); + } + + //update the cursor if the text is scrolling + if ( mDragHit ) + { + if ( ( mScrollDir < 0 ) && ( mCursorPos > 0 ) ) + mCursorPos--; + else if ( ( mScrollDir > 0 ) && ( mCursorPos < (S32) mTextBuffer.length() ) ) + mCursorPos++; + } + } +} + +void GuiTextEditCtrl::drawText( const RectI &drawRect, bool isFocused ) +{ + StringBuffer textBuffer; + Point2I drawPoint = drawRect.point; + Point2I paddingLeftTop, paddingRightBottom; + + // Or else just copy it over. + char *renderText = Con::getReturnBuffer( GuiTextEditCtrl::MAX_STRING_LENGTH ); + getRenderText( renderText ); + + // Apply password masking (make the masking char optional perhaps?) + if(mPasswordText) + { + const U32 renderLen = dStrlen( renderText ); + for( U32 i = 0; i < renderLen; i++ ) + textBuffer.append(mPasswordMask); + } + else + { + textBuffer.set( renderText ); + } + + // Just a little sanity. + if(mCursorPos > textBuffer.length()) + mCursorPos = textBuffer.length(); + + paddingLeftTop.set(( mProfile->mTextOffset.x != 0 ? mProfile->mTextOffset.x : 3 ), mProfile->mTextOffset.y); + paddingRightBottom = paddingLeftTop; + + // Center vertically: + drawPoint.y += ( ( drawRect.extent.y - paddingLeftTop.y - paddingRightBottom.y - S32( mFont->getHeight() ) ) / 2 ) + paddingLeftTop.y; + + // Align horizontally: + + S32 textWidth = mFont->getStrNWidth(textBuffer.getPtr(), textBuffer.length()); + + switch( mProfile->mAlignment ) + { + case GuiControlProfile::RightJustify: + drawPoint.x += ( drawRect.extent.x - textWidth - paddingRightBottom.x ); + break; + case GuiControlProfile::CenterJustify: + drawPoint.x += ( ( drawRect.extent.x - textWidth ) / 2 ); + break; + default: + case GuiControlProfile::LeftJustify : + drawPoint.x += paddingLeftTop.x; + break; + } + + ColorI fontColor = mActive ? mProfile->mFontColor : mProfile->mFontColorNA; + + // now draw the text + Point2I cursorStart, cursorEnd; + mTextOffset.y = drawPoint.y; + mTextOffset.x = drawPoint.x; + + + if ( drawRect.extent.x - paddingLeftTop.x > textWidth ) + mTextOffset.x = drawPoint.x; + else + { + // Alignment affects large text + if ( mProfile->mAlignment == GuiControlProfile::RightJustify + || mProfile->mAlignment == GuiControlProfile::CenterJustify ) + { + if ( mTextOffset.x + textWidth < (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x) + mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x - textWidth; + } + } + + // calculate the cursor + if ( isFocused ) + { + // Where in the string are we? + S32 cursorOffset=0, charWidth=0; + UTF16 tempChar = textBuffer.getChar(mCursorPos); + + // Alright, we want to terminate things momentarily. + if(mCursorPos > 0) + { + cursorOffset = mFont->getStrNWidth(textBuffer.getPtr(), mCursorPos); + } + else + cursorOffset = 0; + + if( tempChar && mFont->isValidChar( tempChar ) ) + charWidth = mFont->getCharWidth( tempChar ); + else + charWidth = paddingRightBottom.x; + + if( mTextOffset.x + cursorOffset + charWidth >= (drawRect.point.x + drawRect.extent.x) - paddingLeftTop.x ) + { + // Cursor somewhere beyond the textcontrol, + // skip forward roughly 25% of the total width (if possible) + S32 skipForward = drawRect.extent.x / 4; + + if ( cursorOffset + skipForward > textWidth ) + mTextOffset.x = (drawRect.point.x + drawRect.extent.x) - paddingRightBottom.x - textWidth; + else + mTextOffset.x -= skipForward; + } + else if( mTextOffset.x + cursorOffset < drawRect.point.x + paddingLeftTop.x ) + { + // Cursor somewhere before the textcontrol + // skip backward roughly 25% of the total width (if possible) + S32 skipBackward = drawRect.extent.x / 4; + + if ( cursorOffset - skipBackward < 0 ) + mTextOffset.x = drawRect.point.x + paddingLeftTop.x; + else + mTextOffset.x += skipBackward; + } + cursorStart.x = mTextOffset.x + cursorOffset; + +#ifdef TORQUE_OS_MAC + cursorStart.x += charWidth/2; +#endif + + cursorEnd.x = cursorStart.x; + + S32 cursorHeight = mFont->getHeight(); + if ( cursorHeight < drawRect.extent.y ) + { + cursorStart.y = drawPoint.y; + cursorEnd.y = cursorStart.y + cursorHeight; + } + else + { + cursorStart.y = drawRect.point.y; + cursorEnd.y = cursorStart.y + drawRect.extent.y; + } + } + + //draw the text + if ( !isFocused ) + mBlockStart = mBlockEnd = 0; + + //also verify the block start/end + if ((mBlockStart > textBuffer.length() || (mBlockEnd > textBuffer.length()) || (mBlockStart > mBlockEnd))) + mBlockStart = mBlockEnd = 0; + + Point2I tempOffset = mTextOffset; + + //draw the portion before the highlight + if ( mBlockStart > 0 ) + { + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + const UTF16* preString2 = textBuffer.getPtr(); + GFX->getDrawUtil()->drawText( mFont, tempOffset, preString2, mProfile->mFontColors ); + tempOffset.x += mFont->getStrNWidth(preString2, mBlockStart); + } + + //draw the highlighted portion + if ( mBlockEnd > 0 ) + { + const UTF16* highlightBuff = textBuffer.getPtr() + mBlockStart; + U32 highlightBuffLen = mBlockEnd-mBlockStart; + + S32 highlightWidth = mFont->getStrNWidth(highlightBuff, highlightBuffLen); + + GFX->getDrawUtil()->drawRectFill( Point2I( tempOffset.x, drawRect.point.y ), + Point2I( tempOffset.x + highlightWidth, drawRect.point.y + drawRect.extent.y - 1), + mProfile->mFontColorSEL ); + + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColorHL ); + GFX->getDrawUtil()->drawTextN( mFont, tempOffset, highlightBuff, highlightBuffLen, mProfile->mFontColors ); + tempOffset.x += highlightWidth; + } + + //draw the portion after the highlight + if(mBlockEnd < textBuffer.length()) + { + const UTF16* finalBuff = textBuffer.getPtr() + mBlockEnd; + U32 finalBuffLen = textBuffer.length() - mBlockEnd; + + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + GFX->getDrawUtil()->drawTextN( mFont, tempOffset, finalBuff, finalBuffLen, mProfile->mFontColors ); + } + + //draw the cursor + if ( isFocused && mCursorOn ) + GFX->getDrawUtil()->drawLine( cursorStart, cursorEnd, mProfile->mCursorColor ); +} + +bool GuiTextEditCtrl::hasText() +{ + return (mTextBuffer.length()); +} + +void GuiTextEditCtrl::playDeniedSound() +{ + if ( mDeniedSound ) + SFX->playOnce( mDeniedSound ); +} + +const char *GuiTextEditCtrl::getScriptValue() +{ + return StringTable->insert(mTextBuffer.getPtr8()); +} + +void GuiTextEditCtrl::setScriptValue(const char *value) +{ + mTextBuffer.set(value); + mCursorPos = mTextBuffer.length() - 1; +} + +void GuiTextEditCtrl::handleCharInput( U16 ascii ) +{ + S32 stringLen = mTextBuffer.length(); + + // Get the character ready to add to a UTF8 string. + UTF16 convertedChar[2] = { ascii, 0 }; + + //see if it's a number field + if ( mProfile->mNumbersOnly ) + { + if ( ascii == '-') + { + //a minus sign only exists at the beginning, and only a single minus sign + if ( mCursorPos != 0 && !isAllTextSelected() ) + { + playDeniedSound(); + return; + } + + if ( mInsertOn && ( mTextBuffer.getChar(0) == '-' ) ) + { + playDeniedSound(); + return; + } + } + // BJTODO: This is probably not unicode safe. + else if ( ascii != '.' && (ascii < '0' || ascii > '9') ) + { + playDeniedSound(); + return; + } + } + + //save the current state + saveUndoState(); + + bool alreadyCut = false; + + //delete anything highlighted + if ( mBlockEnd > 0 ) + { + mTextBuffer.cut(mBlockStart, mBlockEnd-mBlockStart); + mCursorPos = mBlockStart; + mBlockStart = 0; + mBlockEnd = 0; + + // We just changed the string length! + // Get its new value. + stringLen = mTextBuffer.length(); + + // If we already had text highlighted, we just want to cut that text. + // Don't cut the next character even if insert is not on. + alreadyCut = true; + } + + if ( ( mInsertOn && ( stringLen < mMaxStrLen ) ) || + ( !mInsertOn && ( mCursorPos < mMaxStrLen ) ) ) + { + if ( mCursorPos == stringLen ) + { + mTextBuffer.append(convertedChar); + mCursorPos++; + } + else + { + if ( mInsertOn || alreadyCut ) + { + mTextBuffer.insert(mCursorPos, convertedChar); + mCursorPos++; + } + else + { + mTextBuffer.cut(mCursorPos, 1); + mTextBuffer.insert(mCursorPos, convertedChar); + mCursorPos++; + } + } + } + else + playDeniedSound(); + + //reset the history index + mHistoryDirty = true; + + //execute the console command if it exists + execConsoleCallback(); +} + +S32 GuiTextEditCtrl::findPrevWord() +{ + // First the first word to the left of the current cursor position + // and return the positional index of its starting character. + + // We define the first character of a word as any non-whitespace + // character which has a non-alpha-numeric character to its immediate left. + + const UTF8* text = mTextBuffer.getPtr8(); + + for ( S32 i = mCursorPos - 1; i > 0; i-- ) + { + if ( !dIsspace( text[i] ) ) + { + if ( !dIsalnum( text[i-1] ) ) + { + return i; + } + } + } + + return 0; +} + +S32 GuiTextEditCtrl::findNextWord() +{ + // First the first word to the right of the current cursor position + // and return the positional index of its starting character. + + // We define the first character of a word as any non-whitespace + // character which has a non-alpha-numeric character to its immediate left. + + const UTF8* text = mTextBuffer.getPtr8(); + + for ( S32 i = mCursorPos + 1; i < mTextBuffer.length(); i++ ) + { + if ( !dIsspace( text[i] ) ) + { + if ( !dIsalnum( text[i-1] ) ) + { + return i; + } + } + } + + return mTextBuffer.length(); +} + + +ConsoleMethod( GuiTextEditCtrl, getText, const char*, 2, 2, "textEditCtrl.getText()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + if( !object->hasText() ) + return StringTable->insert(""); + + char *retBuffer = Con::getReturnBuffer( GuiTextEditCtrl::MAX_STRING_LENGTH ); + object->getText( retBuffer ); + + return retBuffer; +} + +ConsoleMethod( GuiTextEditCtrl, setText, void, 3, 3, "textEditCtrl.setText( %text )" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->setText( argv[2] ); +} + + +ConsoleMethod( GuiTextEditCtrl, getCursorPos, S32, 2, 2, "textEditCtrl.getCursorPos()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return( object->getCursorPos() ); +} + +ConsoleMethod( GuiTextEditCtrl, setCursorPos, void, 3, 3, "textEditCtrl.setCursorPos( newPos )" ) +{ + TORQUE_UNUSED(argc); + object->setCursorPos( dAtoi( argv[2] ) ); +} + +ConsoleMethod( GuiTextEditCtrl, isAllTextSelected, bool, 2, 2, "textEditCtrl.isAllTextSelected()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return object->isAllTextSelected(); +} + +ConsoleMethod( GuiTextEditCtrl, selectAllText, void, 2, 2, "textEditCtrl.selectAllText()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->selectAllText(); +} + +ConsoleMethod( GuiTextEditCtrl, clearSelectedText, void, 2, 2, "textEditCtrl.clearSelectedText()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->clearSelectedText(); +} + +ConsoleMethod( GuiTextEditCtrl, forceValidateText, void, 2, 2, "textEditCtrl.forceValidateText()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->forceValidateText(); +} diff --git a/gui/controls/guiTextEditCtrl.h b/gui/controls/guiTextEditCtrl.h new file mode 100644 index 0000000..688afb0 --- /dev/null +++ b/gui/controls/guiTextEditCtrl.h @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTEDITCTRL_H_ +#define _GUITEXTEDITCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif +#ifndef _STRINGBUFFER_H_ +#include "core/stringBuffer.h" +#endif + +class GuiTextEditCtrl : public GuiTextCtrl +{ +private: + typedef GuiTextCtrl Parent; + + static U32 smNumAwake; + +protected: + + StringBuffer mTextBuffer; + + StringTableEntry mValidateCommand; + StringTableEntry mEscapeCommand; + SFXProfile* mDeniedSound; + + // for animating the cursor + S32 mNumFramesElapsed; + U32 mTimeLastCursorFlipped; + ColorI mCursorColor; + bool mCursorOn; + + bool mInsertOn; + S32 mMouseDragStart; + Point2I mTextOffset; + bool mTextOffsetReset; + bool mDragHit; + bool mTabComplete; + S32 mScrollDir; + + //undo members + StringBuffer mUndoText; + S32 mUndoBlockStart; + S32 mUndoBlockEnd; + S32 mUndoCursorPos; + void saveUndoState(); + + S32 mBlockStart; + S32 mBlockEnd; + S32 mCursorPos; + virtual S32 calculateCursorPos( const Point2I &globalPos ); + + bool mHistoryDirty; + S32 mHistoryLast; + S32 mHistoryIndex; + S32 mHistorySize; + bool mPasswordText; + StringTableEntry mPasswordMask; + + /// If set, any non-ESC key is handled here or not at all + bool mSinkAllKeyEvents; + UTF16 **mHistoryBuf; + void updateHistory(StringBuffer *txt, bool moveIndex); + + void playDeniedSound(); + void execConsoleCallback(); + + virtual void handleCharInput( U16 ascii ); + + S32 findNextWord(); + S32 findPrevWord(); + +public: + GuiTextEditCtrl(); + ~GuiTextEditCtrl(); + DECLARE_CONOBJECT(GuiTextEditCtrl); + static void initPersistFields(); + + bool onAdd(); + bool onWake(); + void onSleep(); + + /// Get the contents of the control. + /// + /// dest should be of size GuiTextCtrl::MAX_STRING_LENGTH+1. + void getText(char *dest); + virtual void getRenderText(char *dest); + + void setText(S32 tag); + virtual void setText(const UTF8* txt); + virtual void setText(const UTF16* txt); + S32 getCursorPos() { return( mCursorPos ); } + void setCursorPos( const S32 newPos ); + + bool isAllTextSelected(); + void selectAllText(); + void clearSelectedText(); + + void forceValidateText(); + const char *getScriptValue(); + void setScriptValue(const char *value); + + virtual bool onKeyDown(const GuiEvent &event); + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + + void onCopy(bool andCut); + void onPaste(); + void onUndo(); + + virtual void setFirstResponder(); + virtual void onLoseFirstResponder(); + + bool hasText(); + + void onStaticModified(const char* slotName, const char* newValue = NULL); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + virtual void drawText( const RectI &drawRect, bool isFocused ); + + bool dealWithEnter( bool clearResponder ); +}; + +#endif //_GUI_TEXTEDIT_CTRL_H diff --git a/gui/controls/guiTextEditSliderBitmapCtrl.cpp b/gui/controls/guiTextEditSliderBitmapCtrl.cpp new file mode 100644 index 0000000..344616b --- /dev/null +++ b/gui/controls/guiTextEditSliderBitmapCtrl.cpp @@ -0,0 +1,379 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiTextEditSliderBitmapCtrl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiTextEditSliderBitmapCtrl); + +GuiTextEditSliderBitmapCtrl::GuiTextEditSliderBitmapCtrl() +{ + mRange.set(0.0f, 1.0f); + mIncAmount = 1.0f; + mValue = 0.0f; + mMulInc = 0; + mIncCounter = 0.0f; + mFormat = StringTable->insert("%3.2f"); + mTextAreaHit = None; + mFocusOnMouseWheel = false; + mBitmapName = StringTable->insert( "" ); +} + +GuiTextEditSliderBitmapCtrl::~GuiTextEditSliderBitmapCtrl() +{ +} + +void GuiTextEditSliderBitmapCtrl::initPersistFields() +{ + addField("format", TypeString, Offset(mFormat, GuiTextEditSliderBitmapCtrl)); + addField("range", TypePoint2F, Offset(mRange, GuiTextEditSliderBitmapCtrl)); + addField("increment", TypeF32, Offset(mIncAmount, GuiTextEditSliderBitmapCtrl)); + addField("focusOnMouseWheel", TypeBool, Offset(mFocusOnMouseWheel, GuiTextEditSliderBitmapCtrl)); + addField("bitmap", TypeFilename,Offset(mBitmapName, GuiTextEditSliderBitmapCtrl)); + + Parent::initPersistFields(); +} + +void GuiTextEditSliderBitmapCtrl::getText(char *dest) +{ + Parent::getText(dest); +} + +void GuiTextEditSliderBitmapCtrl::setText(const char *txt) +{ + mValue = dAtof(txt); + checkRange(); + setValue(); +} + +bool GuiTextEditSliderBitmapCtrl::onKeyDown(const GuiEvent &event) +{ + return Parent::onKeyDown(event); +} + +void GuiTextEditSliderBitmapCtrl::checkRange() +{ + if(mValue < mRange.x) + mValue = mRange.x; + else if(mValue > mRange.y) + mValue = mRange.y; +} + +void GuiTextEditSliderBitmapCtrl::setValue() +{ + char buf[20]; + // For some reason this sprintf is failing to convert + // a floating point number to anything with %d, so cast it. + if( dStricmp( mFormat, "%d" ) == 0 ) + dSprintf(buf,sizeof(buf),mFormat, (S32)mValue); + else + dSprintf(buf,sizeof(buf),mFormat, mValue); + Parent::setText(buf); +} + +void GuiTextEditSliderBitmapCtrl::onMouseDown(const GuiEvent &event) +{ + // If we're not active then skip out. + if ( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseDown(event); + return; + } + + char txt[20]; + Parent::getText(txt); + mValue = dAtof(txt); + + mMouseDownTime = Sim::getCurrentTime(); + GuiControl *parent = getParent(); + if(!parent) + return; + Point2I camPos = event.mousePoint; + Point2I point = parent->localToGlobalCoord(getPosition()); + + if(camPos.x > point.x + getExtent().x - 14) + { + if(camPos.y > point.y + (getExtent().y/2)) + { + mValue -=mIncAmount; + mTextAreaHit = ArrowDown; + mMulInc = -0.15f; + } + else + { + mValue +=mIncAmount; + mTextAreaHit = ArrowUp; + mMulInc = 0.15f; + } + + checkRange(); + setValue(); + mouseLock(); + + // We should get the focus and set the + // cursor to the start of the text to + // mimic the standard Windows behavior. + setFirstResponder(); + mCursorPos = mBlockStart = mBlockEnd = 0; + setUpdate(); + + return; + } + + Parent::onMouseDown(event); +} + +void GuiTextEditSliderBitmapCtrl::onMouseDragged(const GuiEvent &event) +{ + // If we're not active then skip out. + if ( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseDragged(event); + return; + } + + if(mTextAreaHit == None || mTextAreaHit == Slider) + { + mTextAreaHit = Slider; + GuiControl *parent = getParent(); + if(!parent) + return; + Point2I camPos = event.mousePoint; + Point2I point = parent->localToGlobalCoord(getPosition()); + F32 maxDis = 100; + F32 val; + if(camPos.y < point.y) + { + if((F32)point.y < maxDis) + maxDis = (F32)point.y; + + val = point.y - maxDis; + + if(point.y > 0) + mMulInc= 1.0f-(((float)camPos.y - val) / maxDis); + else + mMulInc = 1.0f; + + checkIncValue(); + + return; + } + else if(camPos.y > point.y + getExtent().y) + { + GuiCanvas *root = getRoot(); + val = (F32)(root->getHeight() - (point.y + getHeight())); + if(val < maxDis) + maxDis = val; + if( val > 0) + mMulInc= -(F32)(camPos.y - (point.y + getHeight()))/maxDis; + else + mMulInc = -1.0f; + checkIncValue(); + return; + } + mTextAreaHit = None; + Parent::onMouseDragged(event); + } +} + +void GuiTextEditSliderBitmapCtrl::onMouseUp(const GuiEvent &event) +{ + // If we're not active then skip out. + if ( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseUp(event); + return; + } + + mMulInc = 0.0f; + mouseUnlock(); + + if ( mTextAreaHit != None ) + + //if we released the mouse within this control, then the parent will call + //the mConsoleCommand other wise we have to call it. + Parent::onMouseUp(event); + + //if we didn't release the mouse within this control, then perform the action + // if (!cursorInControl()) + execConsoleCallback(); + + if (mAltConsoleCommand && mAltConsoleCommand != "" && mAltConsoleCommand[0]) + Con::evaluate(mAltConsoleCommand, false); + + mTextAreaHit = None; +} + +bool GuiTextEditSliderBitmapCtrl::onMouseWheelUp(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return Parent::onMouseWheelUp(event); + + if ( !isFirstResponder() && !mFocusOnMouseWheel ) + return false; + + mValue += mIncAmount; + + checkRange(); + setValue(); + + setFirstResponder(); + mCursorPos = mBlockStart = mBlockEnd = 0; + setUpdate(); + + return true; +} + +bool GuiTextEditSliderBitmapCtrl::onMouseWheelDown(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return Parent::onMouseWheelDown(event); + + if ( !isFirstResponder() && !mFocusOnMouseWheel ) + return false; + + mValue -= mIncAmount; + + checkRange(); + setValue(); + + setFirstResponder(); + mCursorPos = mBlockStart = mBlockEnd = 0; + setUpdate(); + + return true; +} + +void GuiTextEditSliderBitmapCtrl::checkIncValue() +{ + if(mMulInc > 1.0f) + mMulInc = 1.0f; + else if(mMulInc < -1.0f) + mMulInc = -1.0f; +} + +void GuiTextEditSliderBitmapCtrl::timeInc(U32 elapseTime) +{ + S32 numTimes = elapseTime / 750; + if(mTextAreaHit != Slider && numTimes > 0) + { + if(mTextAreaHit == ArrowUp) + mMulInc = 0.15f * numTimes; + else + mMulInc = -0.15f * numTimes; + + checkIncValue(); + } +} +bool GuiTextEditSliderBitmapCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + + mNumberOfBitmaps = mProfile->constructBitmapArray(); + + return true; +} + +void GuiTextEditSliderBitmapCtrl::onPreRender() +{ + if (isFirstResponder()) + { + U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped; + mNumFramesElapsed++; + if ((timeElapsed > 500) && (mNumFramesElapsed > 3)) + { + mCursorOn = !mCursorOn; + mTimeLastCursorFlipped = Sim::getCurrentTime(); + mNumFramesElapsed = 0; + setUpdate(); + } + + //update the cursor if the text is scrolling + if (mDragHit) + { + if ((mScrollDir < 0) && (mCursorPos > 0)) + { + mCursorPos--; + } + else if ((mScrollDir > 0) && (mCursorPos < (S32)dStrlen(mText))) + { + mCursorPos++; + } + } + } +} + +void GuiTextEditSliderBitmapCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if(mTextAreaHit != None) + { + U32 elapseTime = Sim::getCurrentTime() - mMouseDownTime; + if(elapseTime > 750 || mTextAreaHit == Slider) + { + timeInc(elapseTime); + mIncCounter += mMulInc; + if(mIncCounter >= 1.0f || mIncCounter <= -1.0f) + { + mValue = (mMulInc > 0.0f) ? mValue+mIncAmount : mValue-mIncAmount; + mIncCounter = (mIncCounter > 0.0f) ? mIncCounter-1 : mIncCounter+1; + checkRange(); + setValue(); + mCursorPos = 0; + } + } + } + + Parent::onRender(offset, updateRect); + + // Arrow placement coordinates + Point2I arrowUpStart(offset.x + getWidth() - 14, offset.y + 1 ); + Point2I arrowUpEnd(13, getExtent().y/2); + + Point2I arrowDownStart(offset.x + getWidth() - 14, offset.y + 1 + getExtent().y/2); + Point2I arrowDownEnd(13, getExtent().y/2); + + // Draw the line that splits the number and bitmaps + GFX->getDrawUtil()->drawLine(Point2I(offset.x + getWidth() - 14 -2, offset.y + 1 ), + Point2I(arrowUpStart.x -2, arrowUpStart.y + getExtent().y), + mProfile->mBorderColor); + + GFX->getDrawUtil()->clearBitmapModulation(); + + if(mNumberOfBitmaps == 0) + Con::warnf("No image provided for GuiTextEditSliderBitmapCtrl; do not render"); + else + { + // This control needs 4 images in order to render correctly + if(mTextAreaHit == ArrowUp) + GFX->getDrawUtil()->drawBitmapStretchSR( mProfile->mTextureObject, RectI(arrowUpStart,arrowUpEnd), mProfile->mBitmapArrayRects[0] ); + else + GFX->getDrawUtil()->drawBitmapStretchSR( mProfile->mTextureObject, RectI(arrowUpStart,arrowUpEnd), mProfile->mBitmapArrayRects[1] ); + + if(mTextAreaHit == ArrowDown) + GFX->getDrawUtil()->drawBitmapStretchSR( mProfile->mTextureObject, RectI(arrowDownStart,arrowDownEnd), mProfile->mBitmapArrayRects[2] ); + else + GFX->getDrawUtil()->drawBitmapStretchSR( mProfile->mTextureObject, RectI(arrowDownStart,arrowDownEnd), mProfile->mBitmapArrayRects[3] ); + } +} + +void GuiTextEditSliderBitmapCtrl::setBitmap(const char *name) +{ + bool awake = mAwake; + if(awake) + onSleep(); + + mBitmapName = StringTable->insert(name); + if(awake) + onWake(); + setUpdate(); +} \ No newline at end of file diff --git a/gui/controls/guiTextEditSliderBitmapCtrl.h b/gui/controls/guiTextEditSliderBitmapCtrl.h new file mode 100644 index 0000000..fe0bdda --- /dev/null +++ b/gui/controls/guiTextEditSliderBitmapCtrl.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTEDITSLIDERBITMAPCTRL_H_ +#define _GUITEXTEDITSLIDERBITMAPCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _GUITEXTEDITCTRL_H_ +#include "gui/controls/guiTextEditCtrl.h" +#endif + +class GuiTextEditSliderBitmapCtrl : public GuiTextEditCtrl +{ + typedef GuiTextEditCtrl Parent; + +public: + + enum CtrlArea + { + None, + Slider, + ArrowUp, + ArrowDown + }; + + GuiTextEditSliderBitmapCtrl(); + ~GuiTextEditSliderBitmapCtrl(); + + DECLARE_CONOBJECT(GuiTextEditSliderBitmapCtrl); + + static void initPersistFields(); + + virtual void getText(char *dest); // dest must be of size + // StructDes::MAX_STRING_LEN + 1 + + virtual void setText(const char *txt); + + void setValue(); + void checkRange(); + void checkIncValue(); + void timeInc(U32 elapseTime); + + virtual bool onKeyDown(const GuiEvent &event); + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + virtual bool onMouseWheelUp(const GuiEvent &event); + virtual bool onMouseWheelDown(const GuiEvent &event); + + bool onWake(); + virtual void onPreRender(); + virtual void onRender(Point2I offset, const RectI &updateRect); + + void setBitmap(const char *name); + +protected: + + Point2F mRange; + F32 mIncAmount; + F32 mValue; + F32 mIncCounter; + F32 mMulInc; + StringTableEntry mFormat; + U32 mMouseDownTime; + bool mFocusOnMouseWheel; + S32 mNumberOfBitmaps; + StringTableEntry mBitmapName; + + CtrlArea mTextAreaHit; +}; + +#endif //_GUITEXTEDITSLIDERBITMAPCTRL_H_ diff --git a/gui/controls/guiTextEditSliderCtrl.cpp b/gui/controls/guiTextEditSliderCtrl.cpp new file mode 100644 index 0000000..d994a75 --- /dev/null +++ b/gui/controls/guiTextEditSliderCtrl.cpp @@ -0,0 +1,391 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiTextEditSliderCtrl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiTextEditSliderCtrl); + +GuiTextEditSliderCtrl::GuiTextEditSliderCtrl() +{ + mRange.set(0.0f, 1.0f); + mIncAmount = 1.0f; + mValue = 0.0f; + mMulInc = 0; + mIncCounter = 0.0f; + mFormat = StringTable->insert("%3.2f"); + mTextAreaHit = None; + mFocusOnMouseWheel = false; +} + +GuiTextEditSliderCtrl::~GuiTextEditSliderCtrl() +{ +} + +void GuiTextEditSliderCtrl::initPersistFields() +{ + addField("format", TypeString, Offset(mFormat, GuiTextEditSliderCtrl)); + addField("range", TypePoint2F, Offset(mRange, GuiTextEditSliderCtrl)); + addField("increment", TypeF32, Offset(mIncAmount, GuiTextEditSliderCtrl)); + addField("focusOnMouseWheel", TypeBool, Offset(mFocusOnMouseWheel, GuiTextEditSliderCtrl)); + + Parent::initPersistFields(); +} + +void GuiTextEditSliderCtrl::getText(char *dest) +{ + Parent::getText(dest); +} + +void GuiTextEditSliderCtrl::setText(const char *txt) +{ + mValue = dAtof(txt); + checkRange(); + setValue(); +} + +bool GuiTextEditSliderCtrl::onKeyDown(const GuiEvent &event) +{ + return Parent::onKeyDown(event); +} + +void GuiTextEditSliderCtrl::checkRange() +{ + if(mValue < mRange.x) + mValue = mRange.x; + else if(mValue > mRange.y) + mValue = mRange.y; +} + +void GuiTextEditSliderCtrl::setValue() +{ + char buf[20]; + // For some reason this sprintf is failing to convert + // a floating point number to anything with %d, so cast it. + if( dStricmp( mFormat, "%d" ) == 0 ) + dSprintf(buf,sizeof(buf),mFormat, (S32)mValue); + else + dSprintf(buf,sizeof(buf),mFormat, mValue); + Parent::setText(buf); +} + +void GuiTextEditSliderCtrl::onMouseDown(const GuiEvent &event) +{ + // If we're not active then skip out. + if ( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseDown(event); + return; + } + + char txt[20]; + Parent::getText(txt); + mValue = dAtof(txt); + + mMouseDownTime = Sim::getCurrentTime(); + GuiControl *parent = getParent(); + if(!parent) + return; + Point2I camPos = event.mousePoint; + Point2I point = parent->localToGlobalCoord(getPosition()); + + if(camPos.x > point.x + getExtent().x - 14) + { + if(camPos.y > point.y + (getExtent().y/2)) + { + mValue -=mIncAmount; + mTextAreaHit = ArrowDown; + mMulInc = -0.15f; + } + else + { + mValue +=mIncAmount; + mTextAreaHit = ArrowUp; + mMulInc = 0.15f; + } + + checkRange(); + setValue(); + mouseLock(); + + // We should get the focus and set the + // cursor to the start of the text to + // mimic the standard Windows behavior. + setFirstResponder(); + mCursorPos = mBlockStart = mBlockEnd = 0; + setUpdate(); + + return; + } + + Parent::onMouseDown(event); +} + +void GuiTextEditSliderCtrl::onMouseDragged(const GuiEvent &event) +{ + // If we're not active then skip out. + if ( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseDragged(event); + return; + } + + if(mTextAreaHit == None || mTextAreaHit == Slider) + { + mTextAreaHit = Slider; + GuiControl *parent = getParent(); + if(!parent) + return; + Point2I camPos = event.mousePoint; + Point2I point = parent->localToGlobalCoord(getPosition()); + F32 maxDis = 100; + F32 val; + if(camPos.y < point.y) + { + if((F32)point.y < maxDis) + maxDis = (F32)point.y; + + val = point.y - maxDis; + + if(point.y > 0) + mMulInc= 1.0f-(((float)camPos.y - val) / maxDis); + else + mMulInc = 1.0f; + + checkIncValue(); + + return; + } + else if(camPos.y > point.y + getExtent().y) + { + GuiCanvas *root = getRoot(); + val = (F32)(root->getHeight() - (point.y + getHeight())); + if(val < maxDis) + maxDis = val; + if( val > 0) + mMulInc= -(F32)(camPos.y - (point.y + getHeight()))/maxDis; + else + mMulInc = -1.0f; + checkIncValue(); + return; + } + mTextAreaHit = None; + Parent::onMouseDragged(event); + } +} + +void GuiTextEditSliderCtrl::onMouseUp(const GuiEvent &event) +{ + // If we're not active then skip out. + if ( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseUp(event); + return; + } + + mMulInc = 0.0f; + mouseUnlock(); + + if ( mTextAreaHit != None ) + selectAllText(); + + //if we released the mouse within this control, then the parent will call + //the mConsoleCommand other wise we have to call it. + Parent::onMouseUp(event); + + //if we didn't release the mouse within this control, then perform the action + // if (!cursorInControl()) + execConsoleCallback(); + + if (mAltConsoleCommand && mAltConsoleCommand != "" && mAltConsoleCommand[0]) + Con::evaluate(mAltConsoleCommand, false); + + mTextAreaHit = None; +} + +bool GuiTextEditSliderCtrl::onMouseWheelUp(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return Parent::onMouseWheelUp(event); + + if ( !isFirstResponder() && !mFocusOnMouseWheel ) + { + GuiControl *parent = getParent(); + if ( parent ) + return parent->onMouseWheelUp( event ); + + return false; + } + + mValue += mIncAmount; + + checkRange(); + setValue(); + + setFirstResponder(); + mCursorPos = mBlockStart = mBlockEnd = 0; + setUpdate(); + + return true; +} + +bool GuiTextEditSliderCtrl::onMouseWheelDown(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return Parent::onMouseWheelDown(event); + + if ( !isFirstResponder() && !mFocusOnMouseWheel ) + { + GuiControl *parent = getParent(); + if ( parent ) + return parent->onMouseWheelUp( event ); + + return false; + } + + mValue -= mIncAmount; + + checkRange(); + setValue(); + + setFirstResponder(); + mCursorPos = mBlockStart = mBlockEnd = 0; + setUpdate(); + + return true; +} + +void GuiTextEditSliderCtrl::checkIncValue() +{ + if(mMulInc > 1.0f) + mMulInc = 1.0f; + else if(mMulInc < -1.0f) + mMulInc = -1.0f; +} + +void GuiTextEditSliderCtrl::timeInc(U32 elapseTime) +{ + S32 numTimes = elapseTime / 750; + if(mTextAreaHit != Slider && numTimes > 0) + { + if(mTextAreaHit == ArrowUp) + mMulInc = 0.15f * numTimes; + else + mMulInc = -0.15f * numTimes; + + checkIncValue(); + } +} + +void GuiTextEditSliderCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if(mTextAreaHit != None) + { + U32 elapseTime = Sim::getCurrentTime() - mMouseDownTime; + if(elapseTime > 750 || mTextAreaHit == Slider) + { + timeInc(elapseTime); + mIncCounter += mMulInc; + if(mIncCounter >= 1.0f || mIncCounter <= -1.0f) + { + mValue = (mMulInc > 0.0f) ? mValue+mIncAmount : mValue-mIncAmount; + mIncCounter = (mIncCounter > 0.0f) ? mIncCounter-1 : mIncCounter+1; + checkRange(); + setValue(); + mCursorPos = 0; + } + } + } + Parent::onRender(offset, updateRect); + + Point2I start(offset.x + getWidth() - 14, offset.y); + Point2I midPoint(start.x + 7, start.y + (getExtent().y/2)); + + GFX->getDrawUtil()->drawRectFill(Point2I(start.x+1,start.y+1), Point2I(start.x+13,start.y+getExtent().y-1) , mProfile->mFillColor); + + GFX->getDrawUtil()->drawLine(start, Point2I(start.x, start.y+getExtent().y),mProfile->mFontColor); + GFX->getDrawUtil()->drawLine(Point2I(start.x,midPoint.y), + Point2I(start.x+14,midPoint.y), + mProfile->mFontColor); + + GFXVertexBufferHandle verts(GFX, 6, GFXBufferTypeVolatile); + verts.lock(); + + verts[0].color.set( 0, 0, 0 ); + verts[1].color.set( 0, 0, 0 ); + verts[2].color.set( 0, 0, 0 ); + verts[3].color.set( 0, 0, 0 ); + verts[4].color.set( 0, 0, 0 ); + verts[5].color.set( 0, 0, 0 ); + + if(mTextAreaHit == ArrowUp) + { + verts[0].point.set( (F32)midPoint.x, (F32)start.y + 1.0f, 0.0f ); + verts[1].point.set( (F32)start.x + 11.0f, (F32)midPoint.y - 2.0f, 0.0f ); + verts[2].point.set( (F32)start.x + 3.0f, (F32)midPoint.y - 2.0f, 0.0f ); + } + else + { + verts[0].point.set( (F32)midPoint.x, (F32)start.y + 2.0f, 0.0f ); + verts[1].point.set( (F32)start.x + 11.0f, (F32)midPoint.y - 1.0f, 0.0f ); + verts[2].point.set( (F32)start.x + 3.0f, (F32)midPoint.y - 1.0f, 0.0f ); + } + + if(mTextAreaHit == ArrowDown) + { + verts[3].point.set( (F32)midPoint.x, (F32)(start.y + getExtent().y - 1), 0.0f ); + verts[4].point.set( (F32)start.x + 11.0f, (F32)midPoint.y + 3.0f, 0.0f ); + verts[5].point.set( (F32)start.x + 3.0f, (F32)midPoint.y + 3.0f, 0.0f ); + } + else + { + verts[3].point.set( (F32)midPoint.x, (F32)(start.y + getExtent().y - 2), 0.0f ); + verts[4].point.set( (F32)start.x + 11.0f, (F32)midPoint.y + 2.0f, 0.0f ); + verts[5].point.set( (F32)start.x + 3.0f, (F32)midPoint.y + 2.0f, 0.0f ); + } + + verts.unlock(); + + GFX->setVertexBuffer( verts ); + GFX->drawPrimitive( GFXTriangleList, 0, 2 ); +} + +void GuiTextEditSliderCtrl::onPreRender() +{ + if (isFirstResponder()) + { + U32 timeElapsed = Platform::getVirtualMilliseconds() - mTimeLastCursorFlipped; + mNumFramesElapsed++; + if ((timeElapsed > 500) && (mNumFramesElapsed > 3)) + { + mCursorOn = !mCursorOn; + mTimeLastCursorFlipped = Sim::getCurrentTime(); + mNumFramesElapsed = 0; + setUpdate(); + } + + //update the cursor if the text is scrolling + if (mDragHit) + { + if ((mScrollDir < 0) && (mCursorPos > 0)) + { + mCursorPos--; + } + else if ((mScrollDir > 0) && (mCursorPos < (S32)dStrlen(mText))) + { + mCursorPos++; + } + } + } +} + + diff --git a/gui/controls/guiTextEditSliderCtrl.h b/gui/controls/guiTextEditSliderCtrl.h new file mode 100644 index 0000000..3f9b7f8 --- /dev/null +++ b/gui/controls/guiTextEditSliderCtrl.h @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTEDITSLIDERCTRL_H_ +#define _GUITEXTEDITSLIDERCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _GUITEXTEDITCTRL_H_ +#include "gui/controls/guiTextEditCtrl.h" +#endif + +class GuiTextEditSliderCtrl : public GuiTextEditCtrl +{ + typedef GuiTextEditCtrl Parent; + +public: + + enum CtrlArea + { + None, + Slider, + ArrowUp, + ArrowDown + }; + + GuiTextEditSliderCtrl(); + ~GuiTextEditSliderCtrl(); + + DECLARE_CONOBJECT(GuiTextEditSliderCtrl); + + static void initPersistFields(); + + virtual void getText(char *dest); // dest must be of size + // StructDes::MAX_STRING_LEN + 1 + + virtual void setText(const char *txt); + + void setValue(); + void checkRange(); + void checkIncValue(); + void timeInc(U32 elapseTime); + + virtual bool onKeyDown(const GuiEvent &event); + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + virtual bool onMouseWheelUp(const GuiEvent &event); + virtual bool onMouseWheelDown(const GuiEvent &event); + + virtual void onPreRender(); + virtual void onRender(Point2I offset, const RectI &updateRect); + +protected: + + Point2F mRange; + F32 mIncAmount; + F32 mValue; + F32 mIncCounter; + F32 mMulInc; + StringTableEntry mFormat; + U32 mMouseDownTime; + bool mFocusOnMouseWheel; + + CtrlArea mTextAreaHit; +}; + +#endif //_GUITEXTEDITSLIDERCTRL_H_ diff --git a/gui/controls/guiTextListCtrl.cpp b/gui/controls/guiTextListCtrl.cpp new file mode 100644 index 0000000..fef05cc --- /dev/null +++ b/gui/controls/guiTextListCtrl.cpp @@ -0,0 +1,612 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiTextListCtrl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/containers/guiScrollCtrl.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiTextListCtrl); + +static int sortColumn; +static bool sIncreasing; + +static const char *getColumn(const char *text) +{ + int ct = sortColumn; + while(ct--) + { + text = dStrchr(text, '\t'); + if(!text) + return ""; + text++; + } + return text; +} + +static S32 QSORT_CALLBACK textCompare( const void* a, const void* b ) +{ + GuiTextListCtrl::Entry *ea = (GuiTextListCtrl::Entry *) (a); + GuiTextListCtrl::Entry *eb = (GuiTextListCtrl::Entry *) (b); + S32 result = dStrnatcasecmp( getColumn( ea->text ), getColumn( eb->text ) ); + return ( sIncreasing ? result : -result ); +} + +static S32 QSORT_CALLBACK numCompare(const void *a,const void *b) +{ + GuiTextListCtrl::Entry *ea = (GuiTextListCtrl::Entry *) (a); + GuiTextListCtrl::Entry *eb = (GuiTextListCtrl::Entry *) (b); + const char* aCol = getColumn( ea->text ); + const char* bCol = getColumn( eb->text ); + F32 result = dAtof(aCol) - dAtof(bCol); + S32 res = result < 0 ? -1 : (result > 0 ? 1 : 0); + + return ( sIncreasing ? res : -res ); +} + +GuiTextListCtrl::GuiTextListCtrl() +{ + VECTOR_SET_ASSOCIATION(mList); + VECTOR_SET_ASSOCIATION(mColumnOffsets); + + mActive = true; + mSize.set(1, 0); + mColumnOffsets.push_back(0); + mFitParentWidth = true; + mClipColumnText = false; +} + +void GuiTextListCtrl::initPersistFields() +{ + addField("columns", TypeS32Vector, Offset(mColumnOffsets, GuiTextListCtrl), "A vector of column offsets. The number of values determines the number of columns in the table." ); + addField("fitParentWidth", TypeBool, Offset(mFitParentWidth, GuiTextListCtrl)); + addField("clipColumnText", TypeBool, Offset(mClipColumnText, GuiTextListCtrl), "If true, text exceeding a column's given width will get clipped." ); + Parent::initPersistFields(); +} + +bool GuiTextListCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + + setSize(mSize); + return true; +} + +U32 GuiTextListCtrl::getSelectedId() +{ + if (mSelectedCell.y == -1) + return InvalidId; + + return mList[mSelectedCell.y].id; +} + +U32 GuiTextListCtrl::getSelectedRow() +{ + return mSelectedCell.y; +} + +bool GuiTextListCtrl::cellSelected(Point2I cell) +{ + // Is the selection being cleared? + if( cell.x == -1 && cell.y == -1) + return Parent::cellSelected(cell); + + // Do not allow selection of inactive cells + if (cell.y >= 0 && cell.y < mSize.y && mList[cell.y].active) + return Parent::cellSelected(cell); + else + return false; +} + +void GuiTextListCtrl::onCellSelected(Point2I cell) +{ + Con::executef(this, "onSelect", Con::getIntArg(mList[cell.y].id), mList[cell.y].text); + + if (mConsoleCommand[0]) + { + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", getId()); + Con::setVariable("$ThisControl", buf); + Con::evaluate(mConsoleCommand, false); + } +} + +void GuiTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + if ( mList[cell.y].active ) + { + if (selected || (mProfile->mMouseOverSelected && mouseOver)) + { + RectI highlightRect = RectI(offset.x, offset.y, mCellSize.x, mCellSize.y); + highlightRect.inset( 0, -1 ); + renderFilledBorder( highlightRect, mProfile->mBorderColorHL, mProfile->mFillColorHL); + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColorHL); + } + else + GFX->getDrawUtil()->setBitmapModulation(mouseOver ? mProfile->mFontColorHL : mProfile->mFontColor); + } + else + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColorNA ); + + const char *text = mList[cell.y].text; + for(U32 index = 0; index < mColumnOffsets.size(); index++) + { + const char *nextCol = dStrchr(text, '\t'); + if(mColumnOffsets[index] >= 0) + { + dsize_t slen; + if(nextCol) + slen = nextCol - text; + else + slen = dStrlen(text); + + Point2I pos(offset.x + 4 + mColumnOffsets[index], offset.y); + + RectI saveClipRect; + bool clipped = false; + + if(mClipColumnText && (index != (mColumnOffsets.size() - 1))) + { + saveClipRect = GFX->getClipRect(); + + RectI clipRect(pos, Point2I(mColumnOffsets[index+1] - mColumnOffsets[index] - 4, mCellSize.y)); + if(clipRect.intersect(saveClipRect)) + { + clipped = true; + GFX->setClipRect( clipRect ); + } + } + + GFX->getDrawUtil()->drawTextN(mFont, pos, text, slen, mProfile->mFontColors); + + if(clipped) + GFX->setClipRect( saveClipRect ); + } + if(!nextCol) + break; + text = nextCol+1; + } +} + +U32 GuiTextListCtrl::getRowWidth(Entry *row) +{ + U32 width = 1; + const char *text = row->text; + for(U32 index = 0; index < mColumnOffsets.size(); index++) + { + const char *nextCol = dStrchr(text, '\t'); + U32 textWidth; + if(nextCol) + textWidth = mFont->getStrNWidth((const UTF8*)text, nextCol - text); + else + textWidth = mFont->getStrWidth((const UTF8*)text); + if(mColumnOffsets[index] >= 0) + width = getMax(width, mColumnOffsets[index] + textWidth); + if(!nextCol) + break; + text = nextCol+1; + } + return width; +} + +void GuiTextListCtrl::insertEntry(U32 id, const char *text, S32 index) +{ + Entry e; + e.text = dStrdup(text); + e.id = id; + e.active = true; + if(!mList.size()) + mList.push_back(e); + else + { + if(index > mList.size()) + index = mList.size(); + mList.insert(index); + mList[index] = e; + } + setSize(Point2I(1, mList.size())); +} + +void GuiTextListCtrl::addEntry(U32 id, const char *text) +{ + Entry e; + e.text = dStrdup(text); + e.id = id; + e.active = true; + mList.push_back(e); + setSize(Point2I(1, mList.size())); +} + +void GuiTextListCtrl::setEntry(U32 id, const char *text) +{ + S32 e = findEntryById(id); + if(e == -1) + addEntry(id, text); + else + { + dFree(mList[e].text); + mList[e].text = dStrdup(text); + + // Still have to call this to make sure cells are wide enough for new values: + setSize( Point2I( 1, mList.size() ) ); + } + setUpdate(); +} + +void GuiTextListCtrl::setEntryActive(U32 id, bool active) +{ + S32 index = findEntryById( id ); + if ( index == -1 ) + return; + + if ( mList[index].active != active ) + { + mList[index].active = active; + + // You can't have an inactive entry selected... + if ( !active && mSelectedCell.y >= 0 && mSelectedCell.y < mList.size() + && mList[mSelectedCell.y].id == id ) + setSelectedCell( Point2I( -1, -1 ) ); + + setUpdate(); + } +} + +S32 GuiTextListCtrl::findEntryById(U32 id) +{ + for(U32 i = 0; i < mList.size(); i++) + if(mList[i].id == id) + return i; + return -1; +} + +S32 GuiTextListCtrl::findEntryByText(const char *text) +{ + for(U32 i = 0; i < mList.size(); i++) + if(!dStricmp(mList[i].text, text)) + return i; + return -1; +} + +bool GuiTextListCtrl::isEntryActive(U32 id) +{ + S32 index = findEntryById( id ); + if ( index == -1 ) + return( false ); + + return( mList[index].active ); +} + +void GuiTextListCtrl::setSize(Point2I newSize) +{ + mSize = newSize; + + if ( bool( mFont ) ) + { + if ( mSize.x == 1 && mFitParentWidth ) + { + GuiScrollCtrl* parent = dynamic_cast(getParent()); + if ( parent ) + mCellSize.x = parent->getContentExtent().x; + } + else + { + // Find the maximum width cell: + S32 maxWidth = 1; + for ( U32 i = 0; i < mList.size(); i++ ) + { + U32 rWidth = getRowWidth( &mList[i] ); + if ( rWidth > maxWidth ) + maxWidth = rWidth; + } + + mCellSize.x = maxWidth + 8; + } + + mCellSize.y = mFont->getHeight() + 2; + } + + Point2I newExtent( newSize.x * mCellSize.x + mHeaderDim.x, newSize.y * mCellSize.y + mHeaderDim.y ); + setExtent( newExtent ); +} + +void GuiTextListCtrl::clear() +{ + while (mList.size()) + removeEntry(mList[0].id); + + mMouseOverCell.set( -1, -1 ); + setSelectedCell(Point2I(-1, -1)); +} + +void GuiTextListCtrl::sort(U32 column, bool increasing) +{ + if (getNumEntries() < 2) + return; + sortColumn = column; + sIncreasing = increasing; + dQsort((void *)&(mList[0]), mList.size(), sizeof(Entry), textCompare); +} + +void GuiTextListCtrl::sortNumerical( U32 column, bool increasing ) +{ + if ( getNumEntries() < 2 ) + return; + + sortColumn = column; + sIncreasing = increasing; + dQsort( (void*) &( mList[0] ), mList.size(), sizeof( Entry ), numCompare ); +} + +void GuiTextListCtrl::onRemove() +{ + clear(); + Parent::onRemove(); +} + +U32 GuiTextListCtrl::getNumEntries() +{ + return mList.size(); +} + +void GuiTextListCtrl::removeEntryByIndex(S32 index) +{ + if(index < 0 || index >= mList.size()) + return; + dFree(mList[index].text); + mList.erase(index); + + setSize(Point2I( 1, mList.size())); + setSelectedCell(Point2I(-1, -1)); +} + +void GuiTextListCtrl::removeEntry(U32 id) +{ + S32 index = findEntryById(id); + removeEntryByIndex(index); +} + +const char *GuiTextListCtrl::getSelectedText() +{ + if (mSelectedCell.y == -1) + return NULL; + + return mList[mSelectedCell.y].text; +} + +const char *GuiTextListCtrl::getScriptValue() +{ + return getSelectedText(); +} + +void GuiTextListCtrl::setScriptValue(const char *val) +{ + S32 e = findEntryByText(val); + if(e == -1) + setSelectedCell(Point2I(-1, -1)); + else + setSelectedCell(Point2I(0, e)); +} + +bool GuiTextListCtrl::onKeyDown( const GuiEvent &event ) +{ + //if this control is a dead end, make sure the event stops here + if ( !mVisible || !mActive || !mAwake ) + return true; + + S32 yDelta = 0; + switch( event.keyCode ) + { + case KEY_RETURN: + if ( mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand, false ); + break; + case KEY_LEFT: + case KEY_UP: + if ( mSelectedCell.y > 0 ) + { + mSelectedCell.y--; + yDelta = -mCellSize.y; + } + break; + case KEY_DOWN: + case KEY_RIGHT: + if ( mSelectedCell.y < ( mList.size() - 1 ) ) + { + mSelectedCell.y++; + yDelta = mCellSize.y; + } + break; + case KEY_HOME: + if ( mList.size() ) + { + mSelectedCell.y = 0; + yDelta = -(mCellSize.y * mList.size() + 1 ); + } + break; + case KEY_END: + if ( mList.size() ) + { + mSelectedCell.y = mList.size() - 1; + yDelta = (mCellSize.y * mList.size() + 1 ); + } + break; + case KEY_DELETE: + if ( mSelectedCell.y >= 0 && mSelectedCell.y < mList.size() ) + Con::executef( this, "onDeleteKey", Con::getIntArg( mList[mSelectedCell.y].id ) ); + break; + default: + return( Parent::onKeyDown( event ) ); + break; + }; + + GuiScrollCtrl* parent = dynamic_cast(getParent()); + if ( parent ) + parent->scrollDelta( 0, yDelta ); + + return ( true ); + + + +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(GuiTextListCtrl, getSelectedId, S32, 2, 2, "Get the ID of the currently selected item.") +{ + return object->getSelectedId(); +} + +ConsoleMethod( GuiTextListCtrl, setSelectedById, void, 3, 3, "(int id)" + "Finds the specified entry by id, then marks its row as selected.") +{ + S32 index = object->findEntryById(dAtoi(argv[2])); + if(index < 0) + return ; + + object->setSelectedCell(Point2I(0, index)); +} + +ConsoleMethod( GuiTextListCtrl, setSelectedRow, void, 3, 3, "(int rowNum)" + "Selects the specified row.") +{ + object->setSelectedCell( Point2I( 0, dAtoi( argv[2] ) ) ); +} + +ConsoleMethod( GuiTextListCtrl, getSelectedRow, S32, 2, 2, "getSelectedRow - returns row index (not ID)") +{ + return object->getSelectedRow(); +} +ConsoleMethod( GuiTextListCtrl, clearSelection, void, 2, 2, "Set the selection to nothing.") +{ + object->setSelectedCell(Point2I(-1, -1)); +} + +ConsoleMethod(GuiTextListCtrl, addRow, S32, 4, 5, "(int id, string text, int index=0)" + "Returns row number of the new item.") +{ + S32 ret = object->mList.size(); + if(argc < 5) + object->addEntry(dAtoi(argv[2]), argv[3]); + else + object->insertEntry(dAtoi(argv[2]), argv[3], dAtoi(argv[4])); + + return ret; +} + +ConsoleMethod( GuiTextListCtrl, setRowById, void, 4, 4, "(int id, string text)") +{ + object->setEntry(dAtoi(argv[2]), argv[3]); +} + +ConsoleMethod( GuiTextListCtrl, sort, void, 3, 4, "(int columnID, bool increasing=false)" + "Performs a standard (alphabetical) sort on the values in the specified column.") +{ + if ( argc == 3 ) + object->sort(dAtoi(argv[2])); + else + object->sort( dAtoi( argv[2] ), dAtob( argv[3] ) ); +} + +ConsoleMethod(GuiTextListCtrl, sortNumerical, void, 3, 4, "(int columnID, bool increasing=false)" + "Perform a numerical sort on the values in the specified column.") +{ + if ( argc == 3 ) + object->sortNumerical( dAtoi( argv[2] ) ); + else + object->sortNumerical( dAtoi( argv[2] ), dAtob( argv[3] ) ); +} + +ConsoleMethod( GuiTextListCtrl, clear, void, 2, 2, "Clear the list.") +{ + object->clear(); +} + +ConsoleMethod( GuiTextListCtrl, rowCount, S32, 2, 2, "Get the number of rows.") +{ + return object->getNumEntries(); +} + +ConsoleMethod( GuiTextListCtrl, getRowId, S32, 3, 3, "(int index)" + "Get the row ID for an index.") +{ + U32 index = dAtoi(argv[2]); + if(index >= object->getNumEntries()) + return -1; + + return object->mList[index].id; +} + +ConsoleMethod( GuiTextListCtrl, getRowTextById, const char*, 3, 3, "(int id)" + "Get the text of a row with the specified id.") +{ + S32 index = object->findEntryById(dAtoi(argv[2])); + if(index < 0) + return ""; + return object->mList[index].text; +} + +ConsoleMethod( GuiTextListCtrl, getRowNumById, S32, 3, 3, "(int id)" + "Get the row number for a specified id.") +{ + S32 index = object->findEntryById(dAtoi(argv[2])); + if(index < 0) + return -1; + return index; +} + +ConsoleMethod( GuiTextListCtrl, getRowText, const char*, 3, 3, "(int index)" + "Get the text of the row with the specified index.") +{ + U32 index = dAtoi(argv[2]); + if(index < 0 || index >= object->mList.size()) + return ""; + return object->mList[index].text; +} + +ConsoleMethod( GuiTextListCtrl, removeRowById, void, 3, 3,"(int id)" + "Remove row with the specified id.") +{ + object->removeEntry(dAtoi(argv[2])); +} + +ConsoleMethod( GuiTextListCtrl, removeRow, void, 3, 3, "(int index)" + "Remove a row from the table, based on its index.") +{ + U32 index = dAtoi(argv[2]); + object->removeEntryByIndex(index); +} + +ConsoleMethod( GuiTextListCtrl, scrollVisible, void, 3, 3, "(int rowNum)" + "Scroll so the specified row is visible.") +{ + object->scrollCellVisible(Point2I(0, dAtoi(argv[2]))); +} + +ConsoleMethod( GuiTextListCtrl, findTextIndex, S32, 3, 3, "(string needle)" + "Find needle in the list, and return the row number it was found in.") +{ + return( object->findEntryByText( argv[2] ) ); +} + +ConsoleMethod( GuiTextListCtrl, setRowActive, void, 4, 4, "(int rowNum, bool active)" + "Mark a specified row as active/not.") +{ + object->setEntryActive( U32( dAtoi( argv[2] ) ), dAtob( argv[3] ) ); +} + +ConsoleMethod( GuiTextListCtrl, isRowActive, bool, 3, 3, "(int rowNum)" + "Is the specified row currently active?") +{ + return( object->isEntryActive( U32( dAtoi( argv[2] ) ) ) ); +} diff --git a/gui/controls/guiTextListCtrl.h b/gui/controls/guiTextListCtrl.h new file mode 100644 index 0000000..73eaab0 --- /dev/null +++ b/gui/controls/guiTextListCtrl.h @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITEXTLISTCTRL_H_ +#define _GUITEXTLISTCTRL_H_ + +#ifndef _GUIARRAYCTRL_H_ +#include "gui/core/guiArrayCtrl.h" +#endif + +class GuiTextListCtrl : public GuiArrayCtrl +{ + private: + typedef GuiArrayCtrl Parent; + + public: + struct Entry + { + char *text; + U32 id; + bool active; + }; + + Vector mList; + + protected: + enum ScrollConst + { + UP = 0, + DOWN = 1 + }; + enum { + InvalidId = 0xFFFFFFFF + }; + Vector mColumnOffsets; + + bool mFitParentWidth; + bool mClipColumnText; + + U32 getRowWidth(Entry *row); + bool cellSelected(Point2I cell); + void onCellSelected(Point2I cell); + + public: + GuiTextListCtrl(); + + DECLARE_CONOBJECT(GuiTextListCtrl); + DECLARE_CATEGORY( "Gui Lists" ); + DECLARE_DESCRIPTION( "A control that displays text in tabular form." ); + + static void initPersistFields(); + + virtual void setCellSize( const Point2I &size ){ mCellSize = size; } + virtual void getCellSize( Point2I &size ){ size = mCellSize; } + + const char *getScriptValue(); + void setScriptValue(const char *value); + + U32 getNumEntries(); + + void clear(); + virtual void addEntry(U32 id, const char *text); + virtual void insertEntry(U32 id, const char *text, S32 index); + void setEntry(U32 id, const char *text); + void setEntryActive(U32 id, bool active); + S32 findEntryById(U32 id); + S32 findEntryByText(const char *text); + bool isEntryActive(U32 id); + + U32 getEntryId(U32 index); + + bool onWake(); + void removeEntry(U32 id); + virtual void removeEntryByIndex(S32 id); + virtual void sort(U32 column, bool increasing = true); + virtual void sortNumerical(U32 column, bool increasing = true); + + U32 getSelectedId(); + U32 getSelectedRow(); + const char *getSelectedText(); + + bool onKeyDown(const GuiEvent &event); + + virtual void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); + + void setSize(Point2I newSize); + void onRemove(); + void addColumnOffset(S32 offset) { mColumnOffsets.push_back(offset); } + void clearColumnOffsets() { mColumnOffsets.clear(); } +}; + +#endif //_GUI_TEXTLIST_CTRL_H diff --git a/gui/controls/guiTreeViewCtrl.cpp b/gui/controls/guiTreeViewCtrl.cpp new file mode 100644 index 0000000..144d297 --- /dev/null +++ b/gui/controls/guiTreeViewCtrl.cpp @@ -0,0 +1,4318 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiTreeViewCtrl.h" + +#include "core/frameAllocator.h" +#include "gui/containers/guiScrollCtrl.h" +#include "gui/worldEditor/editorIconRegistry.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiTypes.h" +#include "platform/event.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiTreeViewCtrl); + +//----------------------------------------------------------------------------- +// TreeView Item +//----------------------------------------------------------------------------- +GuiTreeViewCtrl::Item::Item( GuiControlProfile *pProfile ) +{ + AssertFatal( pProfile != NULL , "Cannot create a tree item without a valid tree and control profile!"); + mState = 0; + mId = -1; + mTabLevel = 0; + mIcon = 0; + mDataRenderWidth = 0; + mParent = NULL; + mChild = NULL; + mNext = NULL; + mPrevious = NULL; + mProfile = pProfile; + + mScriptInfo.mNormalImage = BmpCon; + mScriptInfo.mExpandedImage = BmpExp; + mScriptInfo.mText = NULL; + mScriptInfo.mValue = NULL; +} + +GuiTreeViewCtrl::Item::~Item() +{ + // Void. +} + +void GuiTreeViewCtrl::Item::setNormalImage(S8 id) +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to set normal image %d for item %d, which is InspectorData!", id, mId); + return; + } + + mScriptInfo.mNormalImage = id; +} + +void GuiTreeViewCtrl::Item::setExpandedImage(S8 id) +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to set expanded image %d for item %d, which is InspectorData!", id, mId); + return; + } + + mScriptInfo.mExpandedImage = id; +} + +void GuiTreeViewCtrl::Item::setText(StringTableEntry txt) +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to set text for item %d, which is InspectorData!", mId); + return; + } + + mScriptInfo.mText = txt; + + + // Update Render Data + if( !mProfile.isNull() ) + mDataRenderWidth = getDisplayTextWidth( mProfile->mFont ); + +} + +void GuiTreeViewCtrl::Item::setValue(StringTableEntry val) +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to set value for item %d, which is InspectorData!", mId); + return; + } + + mScriptInfo.mValue = const_cast(val); // mValue really ought to be a StringTableEntry + + // Update Render Data + if( !mProfile.isNull() ) + mDataRenderWidth = getDisplayTextWidth( mProfile->mFont ); + +} + +const S8 GuiTreeViewCtrl::Item::getNormalImage() const +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to get the normal image for item %d, which is InspectorData!", mId); + return 0; // fail safe for width determinations + } + + return mScriptInfo.mNormalImage; +} + +const S8 GuiTreeViewCtrl::Item::getExpandedImage() const +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to get the expanded image for item %d, which is InspectorData!", mId); + return 0; // fail safe for width determinations + } + + return mScriptInfo.mExpandedImage; +} + +StringTableEntry GuiTreeViewCtrl::Item::getText() +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to get the text for item %d, which is InspectorData!", mId); + return NULL; + } + + return mScriptInfo.mText; +} + +StringTableEntry GuiTreeViewCtrl::Item::getValue() +{ + if(mState.test(InspectorData)) + { + Con::errorf("Tried to get the value for item %d, which is InspectorData!", mId); + return NULL; + } + + return mScriptInfo.mValue; +} + +void GuiTreeViewCtrl::Item::setObject(SimObject *obj) +{ + if(!mState.test(InspectorData)) + { + Con::errorf("Tried to set the object for item %d, which is not InspectorData!", mId); + return; + } + + // AssertFatal(obj,"GuiTreeViewCtrl::Item::setObject: No object"); + mInspectorInfo.mObject = obj; + + // Update Render Data + if( !mProfile.isNull() ) + mDataRenderWidth = getDisplayTextWidth( mProfile->mFont ); +} + +SimObject *GuiTreeViewCtrl::Item::getObject() +{ + if(!mState.test(InspectorData)) + { + Con::errorf("Tried to get the object for item %d, which is not InspectorData!", mId); + return NULL; + } + + return mInspectorInfo.mObject; +} + + +const U32 GuiTreeViewCtrl::Item::getDisplayTextLength() +{ + if(mState.test(Item::InspectorData)) + { + SimObject *obj = getObject(); + + if(!obj) + return 0; + + // Grab some of the strings. + StringTableEntry name = obj->getName(); + StringTableEntry internalName = obj->getInternalName(); + StringTableEntry className = obj->getClassName(); + + // Start with some fudge... no clue why these + // specific numbers are used. + dsize_t len = 16 + 20; + + if ( mState.test(Item::InternalNameOnly) ) + len += internalName ? dStrlen( internalName ) : 0; + else + len += ( name ? dStrlen( name ) : 0 ) + + ( internalName ? dStrlen( internalName ) : 0 ) + + dStrlen( className ) + + dStrlen( obj->getIdString() ); + + return len; + } + + StringTableEntry pText = getText(); + if( pText == NULL ) + return 0; + + return dStrlen( pText ); +} + +void GuiTreeViewCtrl::Item::getDisplayText(U32 bufLen, char *buf) +{ + FrameAllocatorMarker txtAlloc; + + if(mState.test(Item::InspectorData)) + { + // Inspector data! + SimObject *pObject = getObject(); + if(pObject) + { + const char* pObjName = pObject->getName(); + const char* pInternalName = pObject->getInternalName(); + + if ( mState.test(Item::InternalNameOnly) ) + dSprintf(buf, bufLen, "%s", pInternalName ? pInternalName : "" ); + else if ( mState.test(Item::ObjectNameOnly) ) + dSprintf(buf, bufLen, "%s", pObjName ? pObjName : "" ); + else + { + if( pObjName != NULL ) + dSprintf(buf, bufLen, "%d: %s - %s", pObject->getId(), pObject->getClassName(), pObjName ); + else if ( pInternalName != NULL ) + dSprintf(buf, bufLen, "%d: %s [%s]", pObject->getId(), pObject->getClassName(), pInternalName); + else + dSprintf(buf, bufLen, "%d: %s", pObject->getId(), pObject->getClassName()); + } + } + } + else + { + // Script data! (copy it in) + dStrncpy(buf, getText(), bufLen); + } +} + +const S32 GuiTreeViewCtrl::Item::getDisplayTextWidth(GFont *font) +{ + if( !font ) + return 0; + + FrameAllocatorMarker txtAlloc; + U32 bufLen = getDisplayTextLength(); + if( bufLen == 0 ) + return 0; + + // Add space for the string terminator + bufLen++; + + char *buf = (char*)txtAlloc.alloc(bufLen); + getDisplayText(bufLen, buf); + + return font->getStrWidth(buf); +} + +const bool GuiTreeViewCtrl::Item::isParent() const +{ + if(mState.test(VirtualParent)) + { + if( !isInspectorData() ) + return true; + + // Does our object have any children? + if(mInspectorInfo.mObject) + { + SimSet *pSimSet = dynamic_cast( (SimObject*)mInspectorInfo.mObject); + if ( pSimSet != NULL && pSimSet->size() > 0) + return pSimSet->size(); + } + } + + // Otherwise, just return whether the child list is populated. + return mChild; +} + +const bool GuiTreeViewCtrl::Item::isExpanded() const +{ + if(mState.test(InspectorData)) + return mInspectorInfo.mObject ? mInspectorInfo.mObject->isExpanded() : false; + else + return mState.test(Expanded); +} + +void GuiTreeViewCtrl::Item::setExpanded(bool f) +{ + if(mState.test(InspectorData) && !mInspectorInfo.mObject.isNull() ) + mInspectorInfo.mObject->setExpanded(f); + else + mState.set(Expanded, f); +} + +void GuiTreeViewCtrl::Item::setVirtualParent( bool value ) +{ + mState.set(VirtualParent, value); +} + +GuiTreeViewCtrl::Item* GuiTreeViewCtrl::Item::findChildByName( const char* name ) +{ + Item* child = mChild; + while( child ) + { + if( dStricmp( child->mScriptInfo.mText, name ) == 0 ) + return child; + + child = child->mNext; + } + + return NULL; +} + +GuiTreeViewCtrl::Item *GuiTreeViewCtrl::Item::findChildByValue(const SimObject *obj) +{ + // Iterate over our children and try to find the given + // SimObject + + Item *pResultObj = mChild; + + while(pResultObj) + { + // CodeReview this check may need to be removed + // if we want to use the tree for data that + // isn't related to SimObject based objects with + // arbitrary values associated with them [5/5/2007 justind] + + // Skip non-inspector data stuff. + if(pResultObj->mState.test(InspectorData)) + { + if(pResultObj->getObject() == obj) + break; // Whoa. + } + pResultObj = pResultObj->mNext; + } + // If the loop terminated we are NULL, otherwise we have the result in res. + return pResultObj; +} + +GuiTreeViewCtrl::Item *GuiTreeViewCtrl::Item::findChildByValue( StringTableEntry Value ) +{ + // Iterate over our children and try to find the given Value + // Note : This is a case-insensitive search + Item *pResultObj = mChild; + + while(pResultObj) + { + + // check the script value of the item against the specified value + if( pResultObj->mScriptInfo.mValue != NULL && dStricmp( pResultObj->mScriptInfo.mValue, Value ) == 0 ) + return pResultObj; + pResultObj = pResultObj->mNext; + } + // If the loop terminated we didn't find an item with the specified script value + return NULL; +} + +static S32 QSORT_CALLBACK itemCompareCaseSensitive( const void *a, const void *b ) +{ + GuiTreeViewCtrl::Item* itemA = *( ( GuiTreeViewCtrl::Item** ) a ); + GuiTreeViewCtrl::Item* itemB = *( ( GuiTreeViewCtrl::Item** ) b ); + + char bufferA[ 1024 ]; + char bufferB[ 1024 ]; + + itemA->getDisplayText( sizeof( bufferA ), bufferA ); + itemB->getDisplayText( sizeof( bufferB ), bufferB ); + + return dStrnatcmp( bufferA, bufferB ); +} +static S32 QSORT_CALLBACK itemCompareCaseInsensitive( const void *a, const void *b ) +{ + GuiTreeViewCtrl::Item* itemA = *( ( GuiTreeViewCtrl::Item** ) a ); + GuiTreeViewCtrl::Item* itemB = *( ( GuiTreeViewCtrl::Item** ) b ); + + char bufferA[ 1024 ]; + char bufferB[ 1024 ]; + + itemA->getDisplayText( sizeof( bufferA ), bufferA ); + itemB->getDisplayText( sizeof( bufferB ), bufferB ); + + return dStrnatcasecmp( bufferA, bufferB ); +} +static void itemSortList( GuiTreeViewCtrl::Item*& firstChild, bool caseSensitive, bool traverseHierarchy, bool parentsFirst ) +{ + // Sort the children. + // Do this in a separate scope, so we release the buffers before + // recursing. + + { + Vector< GuiTreeViewCtrl::Item* > parents; + Vector< GuiTreeViewCtrl::Item* > items; + + // Put all items into the two vectors. + + for( GuiTreeViewCtrl::Item* item = firstChild; item != NULL; item = item->mNext ) + if( parentsFirst && item->isParent() ) + parents.push_back( item ); + else + items.push_back( item ); + + // Sort both vectors. + + dQsort( parents.address(), parents.size(), sizeof( GuiTreeViewCtrl::Item* ), caseSensitive ? itemCompareCaseSensitive : itemCompareCaseInsensitive ); + dQsort( items.address(), items.size(), sizeof( GuiTreeViewCtrl::Item* ), caseSensitive ? itemCompareCaseSensitive : itemCompareCaseInsensitive ); + + // Wipe current child chain then reconstruct it in reverse + // as we prepend items. + + firstChild = 0; + + // Add child items. + + for( U32 i = items.size(); i > 0; -- i ) + { + GuiTreeViewCtrl::Item* child = items[ i - 1 ]; + + child->mNext = firstChild; + if( firstChild ) + firstChild->mPrevious = child; + + firstChild = child; + } + + // Add parent child items, if requested. + + for( U32 i = parents.size(); i > 0; -- i ) + { + GuiTreeViewCtrl::Item* child = parents[ i - 1 ]; + + child->mNext = firstChild; + if( firstChild ) + firstChild->mPrevious = child; + + firstChild = child; + } + } + + // Traverse hierarchy, if requested. + + if( traverseHierarchy ) + { + GuiTreeViewCtrl::Item* child = firstChild; + while( child ) + { + if( child->isParent() ) + child->sort( caseSensitive, traverseHierarchy, parentsFirst ); + + child = child->mNext; + } + } +} +void GuiTreeViewCtrl::Item::sort( bool caseSensitive, bool traverseHierarchy, bool parentsFirst ) +{ + itemSortList( mChild, caseSensitive, traverseHierarchy, parentsFirst ); +} + +//------------------------------------------------------------------------------ + +GuiTreeViewCtrl::GuiTreeViewCtrl() +{ + VECTOR_SET_ASSOCIATION(mItems); + VECTOR_SET_ASSOCIATION(mVisibleItems); + VECTOR_SET_ASSOCIATION(mSelectedItems); + VECTOR_SET_ASSOCIATION(mSelected); + + mItemFreeList = NULL; + mRoot = NULL; + mItemCount = 0; + mSelectedItem = 0; + mStart = 0; + + mDraggedToItem = 0; + mOldDragY = 0; + mCurrentDragCell = 0; + mPreviousDragCell = 0; + mDragMidPoint = NomDragMidPoint; + mMouseDragged = false; + mDebug = false; + + // persist info.. + mTabSize = 16; + mTextOffset = 2; + mFullRowSelect = false; + mItemHeight = 20; + + // + setSize(Point2I(1, 0)); + + // Set up default state + mFlags.set(ShowTreeLines); + mFlags.set(IsEditable, false); + + mDestroyOnSleep = true; + mSupportMouseDragging = true; + mMultipleSelections = true; + mDeleteObjectAllowed = true; + mDragToItemAllowed = true; + mShowRoot = true; + mInternalNamesOnly = false; + mObjectNamesOnly = false; + mUseInspectorTooltips = false; + mTooltipOnWidthOnly = false; + mCompareToObjectID = true; + mFlags.set(RebuildVisible); + + mClearAllOnSingleSelection = true; + + mBitmapBase = StringTable->insert(""); + mTexRollover = NULL; + mTexSelected = NULL; + + mRenderTooltipDelegate.bind( this, &GuiTreeViewCtrl::renderTooltip ); +} + +GuiTreeViewCtrl::~GuiTreeViewCtrl() +{ + destroyTree(); +} + + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::initPersistFields() +{ + addGroup("TreeView"); + addField("tabSize", TypeS32, Offset(mTabSize, GuiTreeViewCtrl)); + addField("textOffset", TypeS32, Offset(mTextOffset, GuiTreeViewCtrl)); + addField("fullRowSelect", TypeBool, Offset(mFullRowSelect, GuiTreeViewCtrl)); + addField("itemHeight", TypeS32, Offset(mItemHeight, GuiTreeViewCtrl)); + addField("destroyTreeOnSleep", TypeBool, Offset(mDestroyOnSleep, GuiTreeViewCtrl)); + addField("MouseDragging", TypeBool, Offset(mSupportMouseDragging, GuiTreeViewCtrl)); + addField("MultipleSelections", TypeBool, Offset(mMultipleSelections, GuiTreeViewCtrl)); + addField("DeleteObjectAllowed", TypeBool, Offset(mDeleteObjectAllowed, GuiTreeViewCtrl)); + addField("DragToItemAllowed", TypeBool, Offset(mDragToItemAllowed, GuiTreeViewCtrl)); + addField("ClearAllOnSingleSelection", TypeBool, Offset(mClearAllOnSingleSelection, GuiTreeViewCtrl)); + addField("showRoot", TypeBool, Offset(mShowRoot, GuiTreeViewCtrl)); + addField("internalNamesOnly", TypeBool, Offset(mInternalNamesOnly, GuiTreeViewCtrl)); + addField("objectNamesOnly", TypeBool, Offset(mObjectNamesOnly, GuiTreeViewCtrl)); + addField("useInspectorTooltips", TypeBool, Offset(mUseInspectorTooltips, GuiTreeViewCtrl)); + addField("tooltipOnWidthOnly", TypeBool, Offset(mTooltipOnWidthOnly, GuiTreeViewCtrl)); + addField("compareToObjectID", TypeBool, Offset(mCompareToObjectID, GuiTreeViewCtrl)); + + endGroup("TreeView"); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ + +GuiTreeViewCtrl::Item * GuiTreeViewCtrl::getItem(S32 itemId) +{ + if ( itemId > 0 && itemId <= mItems.size() ) + return mItems[itemId-1]; + return NULL; +} + +//------------------------------------------------------------------------------ + +GuiTreeViewCtrl::Item * GuiTreeViewCtrl::createItem(S32 icon) +{ + Item * pNewItem = NULL; + + // grab from the free list? + if( mItemFreeList ) + { + pNewItem = mItemFreeList; + mItemFreeList = pNewItem->mNext; + + // re-add to vector + mItems[ pNewItem->mId - 1 ] = pNewItem; + } + else + { + pNewItem = new Item( mProfile ); + + AssertFatal( pNewItem != NULL, "Fatal : unable to allocate tree item!"); + + mItems.push_back( pNewItem ); + + // set the id + pNewItem->mId = mItems.size(); + } + + // reset + if (icon) + pNewItem->mIcon = icon; + else + pNewItem->mIcon = Default; //default icon to stick next to an item + + pNewItem->mState.clear(); + pNewItem->mState = 0; + pNewItem->mTabLevel = 0; + + // Null out item pointers + pNewItem->mNext = 0; + pNewItem->mPrevious = 0; + pNewItem->mChild = 0; + pNewItem->mParent = 0; + + mItemCount++; + + return pNewItem; +} + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::destroyChildren(Item * item, Item * parent) +{ + if ( !item || item == parent || !mItems[item->mId-1] ) + return; + + // destroy depth first, then siblings from last to first + if ( item->isParent() && item->mChild ) + destroyChildren(item->mChild, item); + if( item->mNext ) + destroyChildren(item->mNext, parent); + + // destroy the item + destroyItem( item ); +} + +void GuiTreeViewCtrl::destroyItem(Item * item) +{ + if(!item) + return; + + if(item->isInspectorData()) + { + // make sure the SimObjectPtr is clean! + SimObject *pObject = item->getObject(); + if( pObject && pObject->isProperlyAdded() ) + { + bool skipDelete = false; + + if ( isMethod( "onDeleteObject" ) ) + skipDelete = dAtob( Con::executef( this, "onDeleteObject", pObject->getIdString() ) ); + + if ( !skipDelete ) + pObject->deleteObject(); + } + + item->setObject( NULL ); + } + + // Remove item from the selection + if (mSelectedItem == item->mId) + mSelectedItem = 0; + for ( S32 i = 0; i < mSelectedItems.size(); i++ ) + { + if ( mSelectedItems[i] == item ) + { + mSelectedItems.erase( i ); + break; + } + } + item->mState.clear(); + + // unlink + if( item->mPrevious ) + item->mPrevious->mNext = item->mNext; + if( item->mNext ) + item->mNext->mPrevious = item->mPrevious; + if( item->mParent && ( item->mParent->mChild == item ) ) + item->mParent->mChild = item->mNext; + + // remove from vector + mItems[item->mId-1] = 0; + + // set as root free item + item->mNext = mItemFreeList; + mItemFreeList = item; + mItemCount--; +} + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::deleteItem(Item *item) +{ + removeItem(item->mId); +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::destroyTree() +{ + // clear the item list + for(U32 i = 0; i < mItems.size(); i++) + { + Item *pFreeItem = mItems[ i ]; + if( pFreeItem != NULL ) + delete pFreeItem; + } + + mItems.clear(); + + // clear the free list + while(mItemFreeList) + { + Item *next = mItemFreeList->mNext; + delete mItemFreeList; + mItemFreeList = next; + } + + mVisibleItems.clear(); + mSelectedItems.clear(); + + // + mRoot = NULL; + mItemFreeList = NULL; + mItemCount = 0; + mSelectedItem = 0; + mDraggedToItem = 0; +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::buildItem( Item* item, U32 tabLevel, bool bForceFullUpdate ) +{ + if (!item || !mActive || !isVisible() || !mProfile ) + return; + + // If it's inspector data, make sure we still have it, if not, kill it. + if(item->isInspectorData() && !item->getObject() ) + { + removeItem(item->mId); + return; + } + + // If it's a virtual parent, give a chance to update itself... + if(item->mState.test( Item::VirtualParent) ) + { + // If it returns false the item has been removed. + if(!onVirtualParentBuild(item, bForceFullUpdate)) + return; + } + + // Is this the root item? + const bool isRoot = item == mRoot; + + // Add non-root items or the root if we're supposed to show it. + if ( mShowRoot || !isRoot ) + { + item->mTabLevel = tabLevel++; + mVisibleItems.push_back( item ); + + if ( mProfile != NULL && mProfile->mFont != NULL ) + { + /* + S32 width = ( tabLevel + 1 ) * mTabSize + item->getDisplayTextWidth(mProfile->mFont); + if ( mProfile->mBitmapArrayRects.size() > 0 ) + width += mProfile->mBitmapArrayRects[0].extent.x; + + width += (item->mTabLevel+1) * mItemHeight; // using mItemHeight for icon width, close enough + // this will only fail if somebody starts using super wide icons. + */ + + S32 width = mTextOffset + ( mTabSize * item->mTabLevel ) + getInspectorItemIconsWidth( item ) + item->getDisplayTextWidth( mProfile->mFont ); + + // check image + S32 image = BmpChild; + if ( item->isInspectorData() ) + image = item->isExpanded() ? BmpExp : BmpCon; + else + image = item->isExpanded() ? item->getExpandedImage() : item->getNormalImage(); + + if ( ( image >= 0 ) && ( image < mProfile->mBitmapArrayRects.size() ) ) + width += mProfile->mBitmapArrayRects[image].extent.x; + + if ( width > mMaxWidth ) + mMaxWidth = width; + } + } + + // If expanded or a hidden root, add all the + // children items as well. + if ( item->isExpanded() || + bForceFullUpdate || + ( isRoot && !mShowRoot ) ) + { + Item * child = item->mChild; + while ( child ) + { + // Bit of a hack so we can safely remove items as we + // traverse. + Item *pChildTemp = child; + child = child->mNext; + + buildItem( pChildTemp, tabLevel, bForceFullUpdate ); + } + } +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::buildVisibleTree(bool bForceFullUpdate) +{ + // Recursion Prevention. + if( mFlags.test( BuildingVisTree ) ) + return; + mFlags.set( BuildingVisTree, true ); + + mMaxWidth = 0; + mVisibleItems.clear(); + + // Update the flags. + mFlags.clear(RebuildVisible); + + // build the root items + Item *traverse = mRoot; + while(traverse) + { + buildItem(traverse, 0, bForceFullUpdate); + traverse = traverse->mNext; + } + + // adjust the GuiArrayCtrl + mCellSize.set( mMaxWidth + mTextOffset, mItemHeight ); + setSize(Point2I(1, mVisibleItems.size())); + syncSelection(); + + // Done Recursing. + mFlags.clear( BuildingVisTree ); +} + +//------------------------------------------------------------------------------ + +bool GuiTreeViewCtrl::scrollVisible( S32 itemId ) +{ + Item* item = getItem(itemId); + if(item) + return scrollVisible(item); + + return false; +} +bool GuiTreeViewCtrl::scrollVisible( Item *item ) +{ + // Now, make sure it's visible (ie, all parents expanded) + Item *parent = item->mParent; + + if( !item->isInspectorData() && item->mState.test(Item::VirtualParent) ) + onVirtualParentExpand(item); + + while(parent) + { + parent->setExpanded(true); + + if( !parent->isInspectorData() && parent->mState.test(Item::VirtualParent) ) + onVirtualParentExpand(parent); + + parent = parent->mParent; + } + + // Get our scroll-pappy, if any. + GuiScrollCtrl *pScrollParent = dynamic_cast( getParent() ); + + if ( !pScrollParent ) + { + Con::warnf("GuiTreeViewCtrl::scrollVisible - parent control is not a GuiScrollCtrl!"); + return false; + } + + // And now, build the visible tree so we know where we have to scroll. + buildVisibleTree(); + + // All done, let's figure out where we have to scroll... + for(S32 i=0; igetChildRelPos().x; + const S32 xWidth = ( mMaxWidth - xPos ); + + // Scroll to View the Item. + // Note: Delta X should be 0 so that we maintain the X axis position. + pScrollParent->scrollRectVisible( RectI( xPos, i * mItemHeight, xWidth, mItemHeight ) ); + return true; + } + } + + // If we got here, it's probably bad... + Con::errorf("GuiTreeViewCtrl::scrollVisible - was unable to find specified item in visible list!"); + return false; +} + +//------------------------------------------------------------------------------ + +S32 GuiTreeViewCtrl::insertItem(S32 parentId, const char * text, const char * value, const char * iconString, S16 normalImage, S16 expandedImage) +{ + if( ( parentId < 0 ) || ( parentId > mItems.size() ) ) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::insertItem: invalid parent id!"); + return 0; + } + + if((parentId != 0) && (mItems[parentId-1] == 0)) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::insertItem: parent item invalid!"); + return 0; + } + + + const char * pItemText = ( text != NULL ) ? text : ""; + const char * pItemValue = ( value != NULL ) ? value : ""; + + S32 icon = getIcon(iconString); + + // create an item (assigns id) + Item * pNewItem = createItem(icon); + if( pNewItem == NULL ) + return 0; + +// // Ugh. This code bothers me. - JDD +// pNewItem->setText( new char[dStrlen( pItemText ) + 1] ); +// dStrcpy( pNewItem->getText(), pItemText ); +// pNewItem->setValue( new char[dStrlen( pItemValue ) + 1] ); +// dStrcpy( pNewItem->getValue(), pItemValue ); + // paxorr fix: + + pNewItem->setText( StringTable->insert( pItemText, true ) ); + pNewItem->setValue( StringTable->insert( pItemValue, true ) ); + + pNewItem->setNormalImage( normalImage ); + pNewItem->setExpandedImage( expandedImage ); + + // root level? + if(parentId == 0) + { + // insert back + if( mRoot != NULL ) + { + Item * pTreeTraverse = mRoot; + while( pTreeTraverse != NULL && pTreeTraverse->mNext != NULL ) + pTreeTraverse = pTreeTraverse->mNext; + + pTreeTraverse->mNext = pNewItem; + pNewItem->mPrevious = pTreeTraverse; + } + else + mRoot = pNewItem; + + mFlags.set(RebuildVisible); + } + else if( mItems.size() >= ( parentId - 1 ) ) + { + Item * pParentItem = mItems[parentId-1]; + + // insert back + if( pParentItem != NULL && pParentItem->mChild) + { + Item * pTreeTraverse = pParentItem->mChild; + while( pTreeTraverse != NULL && pTreeTraverse->mNext != NULL ) + pTreeTraverse = pTreeTraverse->mNext; + + pTreeTraverse->mNext = pNewItem; + pNewItem->mPrevious = pTreeTraverse; + } + else + pParentItem->mChild = pNewItem; + + pNewItem->mParent = pParentItem; + + if( pParentItem->isExpanded() ) + mFlags.set(RebuildVisible); + } + + return pNewItem->mId; +} + +//------------------------------------------------------------------------------ + +bool GuiTreeViewCtrl::removeItem(S32 itemId) +{ + // tree? + if(itemId == 0) + { + destroyTree(); + return(true); + } + + Item * item = getItem(itemId); + if(!item) + { + //Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::removeItem: invalid item id!"); + return false; + } + + // root? + if(item == mRoot) + mRoot = item->mNext; + + // Dispose of any children... + if (item->mChild) + destroyChildren(item->mChild, item); + + // Kill the item... + destroyItem(item); + + // Update the rendered tree... + mFlags.set(RebuildVisible); + + return true; +} + + +void GuiTreeViewCtrl::removeAllChildren(S32 itemId) +{ + Item * item = getItem(itemId); + if(item) + { + destroyChildren(item->mChild, item); + } +} +//------------------------------------------------------------------------------ + +const S32 GuiTreeViewCtrl::getFirstRootItem() const +{ + return (mRoot ? mRoot->mId : 0); +} + +//------------------------------------------------------------------------------ + +S32 GuiTreeViewCtrl::getChildItem(S32 itemId) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getChild: invalid item id!"); + return(0); + } + + return(item->mChild ? item->mChild->mId : 0); +} + +S32 GuiTreeViewCtrl::getParentItem(S32 itemId) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getParent: invalid item id!"); + return(0); + } + + return(item->mParent ? item->mParent->mId : 0); +} + +S32 GuiTreeViewCtrl::getNextSiblingItem(S32 itemId) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getNextSibling: invalid item id!"); + return(0); + } + + return(item->mNext ? item->mNext->mId : 0); +} + +S32 GuiTreeViewCtrl::getPrevSiblingItem(S32 itemId) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getPrevSibling: invalid item id!"); + return(0); + } + + return(item->mPrevious ? item->mPrevious->mId : 0); +} + +//------------------------------------------------------------------------------ + +S32 GuiTreeViewCtrl::getItemCount() +{ + return(mItemCount); +} + +S32 GuiTreeViewCtrl::getSelectedItem() +{ + return mSelectedItem; +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::moveItemUp( S32 itemId ) +{ + GuiTreeViewCtrl::Item* pItem = getItem( itemId ); + if ( !pItem ) + { + Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: invalid item id!"); + return; + } + + Item * pParent = pItem->mParent; + Item * pPrevItem = pItem->mPrevious; + if ( pPrevItem == NULL || pParent == NULL ) + { + Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemUp: Unable to move item up, bad data!"); + return; + } + + // Diddle the linked list! + if ( pPrevItem->mPrevious ) + pPrevItem->mPrevious->mNext = pItem; + else if ( pItem->mParent ) + pItem->mParent->mChild = pItem; + + if ( pItem->mNext ) + pItem->mNext->mPrevious = pPrevItem; + + pItem->mPrevious = pPrevItem->mPrevious; + pPrevItem->mNext = pItem->mNext; + pItem->mNext = pPrevItem; + pPrevItem->mPrevious = pItem; + + // Update SimObjects if Appropriate. + SimObject * pSimObject = NULL; + SimSet * pParentSet = NULL; + + // Fetch Current Add Set + if( pParent->isInspectorData() ) + pParentSet = dynamic_cast( pParent->getObject() ); + else + { + // parent is probably script data so we search up the tree for a + // set to put our object in + Item * pTraverse = pItem->mParent; + while ( pTraverse != NULL && !pTraverse->isInspectorData() ) + pTraverse = pTraverse->mParent; + + // found an ancestor who is an inspectorData? + if (pTraverse != NULL) + pParentSet = pTraverse->isInspectorData() ? dynamic_cast( pTraverse->getObject() ) : NULL; + } + + // Reorder the item and make sure that the children of the item get updated + // correctly prev item may be script... so find a prevItem if there is. + // We only need to reorder if there you move it above an inspector item. + if ( pSimObject != NULL && pParentSet != NULL ) + { + Item * pTraverse = pItem->mNext; + + while(pTraverse) + { + if (pTraverse->isInspectorData()) + break; + pTraverse = pTraverse->mNext; + } + + if (pTraverse && pItem->getObject() && pTraverse->getObject()) + pParentSet->reOrder(pItem->getObject(), pTraverse->getObject()); + } + + mFlags.set(RebuildVisible); +} +void GuiTreeViewCtrl::moveItemDown( S32 itemId ) +{ + GuiTreeViewCtrl::Item* item = getItem( itemId ); + if ( !item ) + { + Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemDown: invalid item id!"); + return; + } + + Item* nextItem = item->mNext; + if ( !nextItem ) + { + Con::errorf( ConsoleLogEntry::General, "GuiTreeViewCtrl::moveItemDown: no next sibling?"); + return; + } + + // Diddle the linked list! + if ( nextItem->mNext ) + nextItem->mNext->mPrevious = item; + + if ( item->mPrevious ) + item->mPrevious->mNext = nextItem; + else if ( item->mParent ) + item->mParent->mChild = nextItem; + + item->mNext = nextItem->mNext; + nextItem->mPrevious = item->mPrevious; + item->mPrevious = nextItem; + nextItem->mNext = item; + + // And update the simobjects if apppropriate... + SimObject * simobj = NULL; + if (item->isInspectorData()) + simobj = item->getObject(); + + SimSet *parentSet = NULL; + + // grab the current parentSet if there is any... + if(item->mParent->isInspectorData()) + parentSet = dynamic_cast(item->mParent->getObject()); + else + { + // parent is probably script data so we search up the tree for a + // set to put our object in + Item * temp = item->mParent; + while (!temp->isInspectorData()) + temp = temp->mParent; + + // found an ancestor who is an inspectorData? + parentSet = temp->isInspectorData() ? dynamic_cast(temp->getObject()) : NULL; + } + + // Reorder the item and make sure that the children of the item get updated + // correctly prev item may be script... so find a prevItem if there is. + // We only need to reorder if there you move it above an inspector item. + if (simobj && parentSet) + { + Item * temp = item->mPrevious; + + while(temp) + { + if (temp->isInspectorData()) + break; + temp = temp->mPrevious; + } + + if (temp && item->getObject() && temp->getObject()) + parentSet->reOrder(temp->getObject(), item->getObject()); + } + + mFlags.set(RebuildVisible); +} + + + +//------------------------------------------------------------------------------ + +bool GuiTreeViewCtrl::onWake() +{ + if(!Parent::onWake() || !mProfile->constructBitmapArray()) + return false; + + // If destroy on sleep, then we have to give things a chance to rebuild. + if(mDestroyOnSleep) + { + destroyTree(); + Con::executef(this, "onWake"); + + // (Re)build our icon table. + Con::executef(this, "onDefineIcons"); + } + + // Update the row height, if appropriate. + if(mProfile->mAutoSizeHeight) + { + // make sure it's big enough for both bitmap AND font... + mItemHeight = getMax((S32)mFont->getHeight(), (S32)mProfile->mBitmapArrayRects[0].extent.y); + } + + return true; +} + +void GuiTreeViewCtrl::onSleep() +{ + Parent::onSleep(); + + // If appropriate, blast the tree. (We probably rebuild it on wake.) + if( mDestroyOnSleep ) + destroyTree(); +} + +bool GuiTreeViewCtrl::buildIconTable(const char * icons) +{ + // Icons should be designated by the bitmap/png file names (minus the file extensions) + // and separated by colons (:). This list should be synchronized with the Icons enum. + + // Figure the size of the buffer we need... + const char* temp = dStrchr( icons, '\t' ); + U32 textLen = temp ? ( temp - icons ) : dStrlen( icons ); + + // Allocate temporary space. + FrameAllocatorMarker txtBuff; + char* drawText = (char*)txtBuff.alloc(sizeof(char) * (textLen + 4)); + dStrncpy( drawText, icons, textLen ); + drawText[textLen] = '\0'; + + U32 numIcons = 0; + char buf[ 1024 ]; + char* pos = drawText; + + // Count the number of icons and store them. + while( *pos && numIcons < MaxIcons ) + { + char* start = pos; + while( *pos && *pos != ':' ) + pos ++; + + const U32 len = pos - start; + if( len ) + { + dStrncpy( buf, start, getMin( sizeof( buf ) / sizeof( buf[ 0 ] ) - 1, len ) ); + buf[ len ] = '\0'; + + mIconTable[ numIcons ] = GFXTexHandle( buf, &GFXDefaultPersistentProfile, avar( "%s() - mIconTable[%d] (line %d)", __FUNCTION__, numIcons, __LINE__ ) ); + } + + numIcons ++; + if( *pos ) + pos ++; + } + + return true; +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::onPreRender() +{ + Parent::onPreRender(); + + S32 nRootItemId = getFirstRootItem(); + if( nRootItemId == 0 ) + return; + + Item *pRootItem = getItem( nRootItemId ); + if( pRootItem == NULL ) + return; + + // Update every render in case new objects are added + if(mFlags.test(RebuildVisible)) + { + buildVisibleTree(); + mFlags.clear(RebuildVisible); + } +} + +//------------------------------------------------------------------------------ + +bool GuiTreeViewCtrl::hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags) +{ + + // Initialize some things. + const Point2I pos = globalToLocalCoord(pnt); + flags.clear(); + item = 0; + + // get the hit cell + Point2I cell((pos.x < 0 ? -1 : pos.x / mCellSize.x), + (pos.y < 0 ? -1 : pos.y / mCellSize.y)); + + // valid? + if((cell.x < 0 || cell.x >= mSize.x) || + (cell.y < 0 || cell.y >= mSize.y)) + return false; + + flags.set(OnRow); + + // Grab the cell. + if (cell.y >= mVisibleItems.size()) + return false; //Invalid cell, so don't do anything + + item = mVisibleItems[cell.y]; + + S32 min = mTabSize * item->mTabLevel; + + // left of icon/text? + if(pos.x < min) + { + flags.set(OnIndent); + return true; + } + + // check image + S32 image = BmpChild; + + if(item->isInspectorData()) + image = item->isExpanded() ? BmpExp : BmpCon; + else + image = item->isExpanded() ? item->getExpandedImage() : item->getNormalImage(); + + if((image >= 0) && (image < mProfile->mBitmapArrayRects.size())) + min += mProfile->mBitmapArrayRects[image].extent.x; + + // Is it on the image? + if(pos.x < min) + { + flags.set(OnImage); + return(true); + } + + // Bump over to the start of the text and icons. + min += mTextOffset; + + // Check against the text and icons. + min += getInspectorItemIconsWidth( item ); + FrameAllocatorMarker txtAlloc; + U32 bufLen = item->getDisplayTextLength(); + char *buf = (char*)txtAlloc.alloc(bufLen); + item->getDisplayText(bufLen, buf); + + min += mProfile->mFont->getStrWidth(buf); + if(pos.x < min) + flags.set(OnText); + + return true; +} + +S32 GuiTreeViewCtrl::getInspectorItemIconsWidth(Item* & item) +{ + if( !item->isInspectorData() ) + return 0; + + S32 width = 0; + + // Based on code in onRenderCell() + + S32 icon = Lock1; + S32 icon2 = Hidden; + + if (item->getObject() && item->getObject()->isLocked()) + { + if (mIconTable[icon]) + { + width += mIconTable[icon].getWidth(); + } + } + + if (item->getObject() && item->getObject()->isHidden()) + { + if (mIconTable[icon2]) + { + width += mIconTable[icon2].getWidth(); + } + } + + GFXTexHandle iconHandle; + if ( ( item->mIcon != -1 ) && mIconTable[item->mIcon] ) + iconHandle = mIconTable[item->mIcon]; +#ifdef TORQUE_TOOLS + else + iconHandle = gEditorIcons.findIcon( item->getObject() ); +#endif + + if ( iconHandle.isValid() ) + { + width += iconHandle.getWidth(); + } + + return width; +} + +bool GuiTreeViewCtrl::setAddGroup(SimObject * obj) +{ + // make sure we're talking about a group. + SimGroup * grp = dynamic_cast(obj); + + if(grp) + { + // Notify Script + Con::executef( this, "onAddGroupSelected", Con::getIntArg(grp->getId()) ); + return true; + } + return false; +} + +void GuiTreeViewCtrl::syncSelection() +{ + // for each visible item check to see if it is on the mSelected list. + // if it is then make sure that it is on the mSelectedItems list as well. + for (S32 i = 0; i < mVisibleItems.size(); i++) + { + for (S32 j = 0; j < mSelected.size(); j++) + { + if (mVisibleItems[i]->mId == mSelected[j]) + { + // check to see if it is on the visible items list. + bool addToSelectedItems = true; + for (S32 k = 0; k < mSelectedItems.size(); k++) + { + if (mSelected[j] == mSelectedItems[k]->mId) + { + // don't add it + addToSelectedItems = false; + } + } + if (addToSelectedItems) + { + mVisibleItems[i]->mState.set(Item::Selected, true); + mSelectedItems.push_front(mVisibleItems[i]); + break; + } + } + else if (mVisibleItems[i]->isInspectorData()) + { + if(mCompareToObjectID) + { + if (mVisibleItems[i]->getObject() && mVisibleItems[i]->getObject()->getId() == mSelected[j]) + { + // check to see if it is on the visible items list. + bool addToSelectedItems = true; + for (S32 k = 0; k < mSelectedItems.size(); k++) + { + if (mSelectedItems[k]->isInspectorData() && mSelectedItems[k]->getObject() ) + { + if (mSelected[j] == mSelectedItems[k]->getObject()->getId()) + { + // don't add it + addToSelectedItems = false; + } + } + else + { + if (mSelected[j] == mSelectedItems[k]->mId) + { + // don't add it + addToSelectedItems = false; + } + } + } + if (addToSelectedItems) + { + mVisibleItems[i]->mState.set(Item::Selected, true); + mSelectedItems.push_front(mVisibleItems[i]); + break; + } + } + } + } + + } + + } +} + +void GuiTreeViewCtrl::removeSelection(S32 itemId) +{ + if (mDebug) + Con::printf("removeSelection called"); + + Item *item = NULL; + SimObject *object = NULL; + + // First check our item vector for one with this itemId. + item = getItem(itemId); + + // If not found, maybe this is an object id. + if ( !item ) + { + if ( Sim::findObject( itemId, object ) ) + if ( objectSearch( object, &item ) ) + itemId = item->mId; + } + + if (!item) + { + //Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::removeSelection: invalid item id! Perhaps it isn't visible yet"); + return; + } + + S32 objectId = -1; + if ( item->isInspectorData() && item->getObject() ) + objectId = item->getObject()->getId(); + + // Remove from vector of selected object ids if it exists there + if ( objectId != -1 ) + { + for ( S32 i = 0; i < mSelected.size(); i++ ) + { + if ( objectId == mSelected[i] || itemId == mSelected[i] ) + { + mSelected.erase( i ); + break; + } + } + } + else + { + for ( S32 i = 0; i < mSelected.size(); i++ ) + { + if ( itemId == mSelected[i] ) + { + mSelected.erase( i ); + break; + } + } + } + + item->mState.set(Item::Selected, false); + + // Remove from vector of selected items if it exists there. + for ( S32 i = 0; i < mSelectedItems.size(); i++ ) + { + if ( mSelectedItems[i] == item ) + { + mSelectedItems.erase( i ); + break; + } + } + + // Callback. + onRemoveSelection( item ); +} + +bool GuiTreeViewCtrl::isSelected(S32 itemId) +{ + return isSelected( getItem( itemId ) ); +} + +bool GuiTreeViewCtrl::isSelected( Item *item ) +{ + if ( !item ) + { + return false; + } + + for ( U32 i = 0; i < mSelectedItems.size(); i++ ) + { + if ( mSelectedItems[i] == item ) + { + return true; + } + } + + return false; +} + +void GuiTreeViewCtrl::addSelection(S32 itemId, bool update) +{ + if (mDebug) + Con::printf("addSelection called"); + + // try to find item by itemId + Item *item = getItem(itemId); + + // Maybe what we were passed wasn't an item id but an object id. + // First call scrollVisibleByObjectId because this will cause + // its parent group(s) to be expanded and ensure that we can find it + if ( !item ) + { + SimObject *selectObj = NULL; + if ( Sim::findObject( itemId, selectObj ) ) + if ( scrollVisibleByObjectId( itemId ) ) + if ( objectSearch( selectObj, &item ) ) + itemId = item->mId; + } + + // Add Item? + if ( !item || isSelected( item ) || !canAddSelection( item ) ) + { + // Nope. + return; + } + + // Ok, we have an item to select which isn't already selected.... + + // Do we want to allow more than one selected item? + if( !mMultipleSelections ) + clearSelection(); + + // Add this object id to the vector of selected objectIds + // if it is not already. + bool foundMatch = false; + for ( S32 i = 0; i < mSelected.size(); i++) + { + if ( mSelected[i] == itemId ) + foundMatch = true; + } + + if ( !foundMatch ) + mSelected.push_front(itemId); + + item->mState.set(Item::Selected, true); + + if ( mSelected.size() == 1 ) + { + onItemSelected( item ); + } + + // Callback. + onAddSelection( item ); + + if ( update ) + { + // Also make it so we can see it if we didn't already. + scrollVisible( item ); + } +} + + +void GuiTreeViewCtrl::onItemSelected( Item *item ) +{ + mSelectedItem = item->getID(); + + char buf[16]; + dSprintf(buf, 16, "%d", item->mId); + if (item->isInspectorData()) + { + if(item->getObject()) + Con::executef(this, "onSelect", Con::getIntArg(item->getObject()->getId())); + if (!(item->isParent()) && item->getObject()) + Con::executef(this, "onInspect", Con::getIntArg(item->getObject()->getId())); + } + else + { + Con::executef(this, "onSelect", buf); + if (!(item->isParent())) + Con::executef(this, "onInspect", buf); + } +} + +void GuiTreeViewCtrl::onAddSelection( Item *item ) +{ + if ( item->isInspectorData() && item->getObject() ) + { + Con::executef( this, "onAddSelection", Con::getIntArg( item->getObject()->getId() ) ); + } +} + +void GuiTreeViewCtrl::onRemoveSelection( Item *item ) +{ + if ( item->isInspectorData() && item->getObject() ) + { + Con::executef( this, "onRemoveSelection", Con::getIntArg( item->getObject()->getId() ) ); + } +} + +bool GuiTreeViewCtrl::setItemSelected(S32 itemId, bool select) +{ + Item * item = getItem(itemId); + + if (select) + { + if (mDebug) Con::printf("setItemSelected called true"); + + mSelected.push_front(itemId); + } + else + { + if (mDebug) Con::printf("setItemSelected called false"); + + // remove it from the mSelected list + for (S32 j = 0; j isInspectorData()) + { + if (item->getObject()) + { + if(item->getObject()->getId() == mSelected[j]) + { + mSelected.erase(j); + break; + } + } + else + { + // Zombie, kill it! + mSelected.erase(j); + j--; + break; + } + } + } + + if (mSelected[j] == itemId) + { + mSelected.erase(j); + break; + } + } + } + + if(!item) + { + // maybe what we were passed wasn't an item id but an object id. + for (S32 i = 0; i isInspectorData()) + { + if (mItems[i]->getObject()) + { + if(mItems[i]->getObject()->getId() == itemId) + { + item = mItems[i]; + break; + } + } + else + { + // It's a zombie, blast it. + mItems.erase(i); + i--; + } + } + } + } + + if (!item) + { + //Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::setItemSelected: invalid item id! Perhaps it isn't visible yet."); + return(false); + } + } + + if(select) + { + addSelection( item->mId ); + onItemSelected( item ); + } + else + { + // deselect the item, if it's present. + item->mState.set(Item::Selected, false); + + if (item->isInspectorData() && item->getObject()) + Con::executef(this, "onUnSelect", Con::getIntArg(item->getObject()->getId())); + else + Con::executef(this, "onUnSelect", Con::getIntArg(item->mId)); + + // remove it from the selected items list + for (S32 i = 0; i < mSelectedItems.size(); i++) + { + if (mSelectedItems[i] == item) + { + mSelectedItems.erase(i); + break; + } + } + } + + setUpdate(); + return(true); +} + +// Given an item's index in the selection list, return its itemId +S32 GuiTreeViewCtrl::getSelectedItem(S32 index) +{ + if(index >= 0 && index < getSelectedItemsCount()) + { + return mSelectedItems[index]->mId; + } + + return -1; +} +bool GuiTreeViewCtrl::setItemExpanded(S32 itemId, bool expand) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::setItemExpanded: invalid item id!"); + return(false); + } + + if(item->isExpanded() == expand) + return(true); + + // expand parents + if(expand) + { + while(item) + { + if(item->mState.test(Item::VirtualParent)) + onVirtualParentExpand(item); + + item->setExpanded(true); + item = item->mParent; + } + } + else + { + if(item->mState.test(Item::VirtualParent)) + onVirtualParentCollapse(item); + + item->setExpanded(false); + } + return(true); +} + + +bool GuiTreeViewCtrl::setItemValue(S32 itemId, StringTableEntry Value) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::setItemValue: invalid item id!"); + return(false); + } + + item->setValue( ( Value ) ? Value : "" ); + + return(true); +} + +const char * GuiTreeViewCtrl::getItemText(S32 itemId) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getItemText: invalid item id!"); + return(""); + } + + return(item->getText() ? item->getText() : ""); +} + +const char * GuiTreeViewCtrl::getItemValue(S32 itemId) +{ + Item * item = getItem(itemId); + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getItemValue: invalid item id!"); + return(""); + } + + if(item->mState.test(Item::InspectorData)) + { + // If it's InspectorData, we let people use this call to get an object reference. + return item->mInspectorInfo.mObject->getIdString(); + } + else + { + // Just return the script value... + return(item->getValue() ? item->getValue() : ""); + } +} + +bool GuiTreeViewCtrl::editItem( S32 itemId, const char* newText, const char* newValue ) +{ + Item* item = getItem( itemId ); + if ( !item ) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::editItem: invalid item id: %d!", itemId); + return false; + } + + if ( item->mState.test(Item::InspectorData) ) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::editItem: item %d is inspector data and may not be modified!", itemId); + return false; + } + + item->setText( StringTable->insert( newText, true ) ); + item->setValue( StringTable->insert( newValue, true ) ); + + // Update the widths and such: + mFlags.set(RebuildVisible); + return true; +} + +bool GuiTreeViewCtrl::markItem( S32 itemId, bool mark ) +{ + Item *item = getItem( itemId ); + if ( !item ) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::markItem: invalid item id: %d!", itemId); + return false; + } + + item->mState.set(Item::Marked, mark); + return true; +} + +bool GuiTreeViewCtrl::isItemSelected( S32 itemId ) +{ + for( U32 i = 0, num = mSelectedItems.size(); i < num; ++ i ) + if( mSelectedItems[ i ]->mId == itemId ) + return true; + + return false; +} + +void GuiTreeViewCtrl::deleteSelection() +{ + Con::executef(this, "onDeleteSelection"); + + if (mSelectedItems.empty()) + { + for (S32 i = 0; i < mSelected.size(); i++) + { + S32 objectId = mSelected[i]; + + // find the object + SimObject* obj = Sim::findObject(objectId); + if ( !obj ) + continue; + + bool skipDelete = false; + + if ( isMethod( "onDeleteObject" ) ) + skipDelete = dAtob( Con::executef( this, "onDeleteObject", obj->getIdString() ) ); + + if ( !skipDelete ) + obj->deleteObject(); + } + } + else + { + Vector delSelection; + delSelection = mSelectedItems; + mSelectedItems.clear(); + while (!delSelection.empty()) + { + Item * item = delSelection.front(); + setItemSelected(item->mId,false); + if ( item->mParent ) + deleteItem( item ); + + delSelection.pop_front(); + } + } + + mSelected.clear(); + mSelectedItems.clear(); + mSelectedItem = 0; + Con::executef( this, "onObjectDeleteCompleted"); +} + +//------------------------------------------------------------------------------ +// keyboard movement of items is restricted to just one item at a time +// if more than one item is selected then movement operations are not performed +bool GuiTreeViewCtrl::onKeyDown( const GuiEvent& event ) +{ + if ( !mVisible || !mActive || !mAwake ) + return false; + + // All the keyboard functionality requires a selected item, so if none exists... + + // Deal with enter and delete + if ( event.modifier == 0 ) + { + if ( event.keyCode == KEY_RETURN ) + { + if ( mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand ); + return true; + } + + if ( event.keyCode == KEY_DELETE && mDeleteObjectAllowed ) + { + // Don't delete the root! + if (mSelectedItems.empty()) + return true; + + //this may be fighting with the world editor delete + deleteSelection(); + return true; + } + + //call a generic bit of script that will let the subclass know that a key was pressed + Con::executef(this, "onKeyDown", Con::getIntArg(event.modifier), Con::getIntArg(event.keyCode)); + } + + // only do operations if only one item is selected + if ( mSelectedItems.empty() || (mSelectedItems.size() > 1)) + return false; + + Item* item = mSelectedItems.first(); + + if ( !item ) + return false; + + // The Alt key lets you move items around! + if ( mFlags.test(IsEditable) && event.modifier & SI_ALT ) + { + switch ( event.keyCode ) + { + case KEY_UP: + // Move us up. + if ( item->mPrevious ) + { + moveItemUp( item->mId ); + scrollVisible(item); + } + return true; + + case KEY_DOWN: + // Move the item under us up. + if ( item->mNext ) + { + moveItemUp( item->mNext->mId ); + scrollVisible(item); + } + return true; + + case KEY_LEFT: + if ( item->mParent ) + { + if ( item->mParent->mParent ) + { + // Ok, we have both an immediate parent, and a grandparent. + + // The goal of left-arrow alt is to become the child of our + // grandparent, ie, to become a sibling of our parent. + + // First, unlink item from its siblings. + if ( item->mPrevious ) + item->mPrevious->mNext = item->mNext; + else + item->mParent->mChild = item->mNext; + + if ( item->mNext ) + item->mNext->mPrevious = item->mPrevious; + + // Now, relink as the next sibling of our parent. + item->mPrevious = item->mParent; + item->mNext = item->mParent->mNext; + + // If there was already a next sibling, deal with that case. + if ( item->mNext ) + item->mNext->mPrevious = item; + item->mParent->mNext = item; + + // Snag the current parent set if any... + SimSet *parentSet = NULL; + + if(item->mParent->isInspectorData()) + parentSet = dynamic_cast(item->mParent->getObject()); + else + { + // parent is probably script data so we search up the tree for a + // set to put our object in + Item * temp = item->mParent; + while (!temp->isInspectorData()) + temp = temp->mParent; + // found a ancestor who is an inspectorData + if (temp->isInspectorData()) + parentSet = dynamic_cast(temp->getObject()); + else parentSet = NULL; + } + + // Get our active SimObject if any + SimObject *simObj = NULL; + if(item->isInspectorData()) + simObj = item->getObject(); + + // Remove from the old parentset... + if(simObj && parentSet) { + if (parentSet->size()>0) + { + SimObject *lastObject = parentSet->last(); + parentSet->removeObject(simObj); + parentSet->reOrder(lastObject); + } else + parentSet->removeObject(simObj); + } + + // And finally, update our item + item->mParent = item->mParent->mParent; + + // Snag the newparent set if any... + SimSet *newParentSet = NULL; + + if(item->mParent->isInspectorData()) + newParentSet = dynamic_cast(item->mParent->getObject()); + else + { + // parent is probably script data so we search up the tree for a + // set to put our object in + Item * temp = item->mParent; + while (!temp->isInspectorData()) + temp = temp->mParent; + // found a ancestor who is an inspectorData + if (temp->isInspectorData()) + newParentSet = dynamic_cast(temp->getObject()); + else newParentSet = NULL; + } + if(simObj && newParentSet) + { + + newParentSet->addObject(simObj); + Item * temp = item->mNext; + // item->mNext may be script, so find an inspector item to reorder with if any + + if (temp) { + do { + if (temp->isInspectorData()) + break; + temp = temp->mNext; + } while (temp); + if (temp && item->getObject() && temp->getObject()) //do we still have a item->mNext? If not then don't bother reordering + newParentSet->reOrder(item->getObject(), temp->getObject()); + } + + } else if (!simObj&&newParentSet) { + // our current item is script data. but it may have children who + // is inspector data who need an updated set + if (item->mChild) + inspectorSearch(item->mChild, item, parentSet, newParentSet); + + } + + // And update everything hurrah. + buildVisibleTree(); + scrollVisible(item); + } + } + return true; + + case KEY_RIGHT: + if ( item->mPrevious ) + { + // Make the item the last child of its previous sibling. + + // First, unlink from the current position in the list + item->mPrevious->mNext = item->mNext; + + if ( item->mNext ) + item->mNext->mPrevious = item->mPrevious; + + // Get the object we're poking with. + SimObject *simObj = NULL; + SimSet *parentSet = NULL; + if(item->isInspectorData()) + simObj = item->getObject(); + if(item->mParent->isInspectorData()) + parentSet = dynamic_cast(item->mParent->getObject()); + else { + // parent is probably script data so we search up the tree for a + // set to put our object in + Item * temp = item->mParent; + while (!temp->isInspectorData()) + temp = temp->mParent; + // found an ancestor who is an inspectorData + if (temp->isInspectorData()) + parentSet = dynamic_cast(temp->getObject()); + } + + // If appropriate, remove from the current SimSet. + if(parentSet && simObj) { + if (parentSet->size()>0) + { + SimObject *lastObject = parentSet->last(); + parentSet->removeObject(simObj); + parentSet->reOrder(lastObject); + } else + parentSet->removeObject(simObj); + } + + + // Now, make our previous sibling our parent... + item->mParent = item->mPrevious; + item->mNext = NULL; + + // And sink us down to the end of its siblings, if appropriate. + if ( item->mParent->mChild ) + { + Item* temp = item->mParent->mChild; + while ( temp->mNext ) + temp = temp->mNext; + + temp->mNext = item; + item->mPrevious = temp; + } + else + { + // only child... + item->mParent->mChild = item; + item->mPrevious = NULL; + } + + // Make sure the new parent is expanded: + if ( !item->mParent->mState.test( Item::Expanded ) ) + setItemExpanded( item->mParent->mId, true ); + + // Snag the new parent simset if any. + SimSet *newParentSet = NULL; + + // new parent might be script. so figure out what set we need to add it to. + if(item->mParent->isInspectorData()) + newParentSet = dynamic_cast(item->mParent->getObject()); + else + { + // parent is probably script data so we search up the tree for a + // set to put our object in + if (mDebug) Con::printf("oh nos my parent is script!"); + Item * temp = item->mParent; + while (!temp->isInspectorData()) + temp = temp->mParent; + // found a ancestor who is an inspectorData + if (temp->isInspectorData()) + newParentSet = dynamic_cast(temp->getObject()); + else newParentSet = NULL; + } + // Add the item's SimObject to the new parent simset, at the end. + if(newParentSet && simObj) + newParentSet->addObject(simObj); + else if (!simObj&&newParentSet&&parentSet) { + // our current item is script data. but it may have children who + // is inspector data who need an updated set + + if (item->mChild) { + inspectorSearch(item->mChild, item, parentSet, newParentSet); + } + + } + scrollVisible(item); + } + return true; + + default: + break; + } + } + + // Explorer-esque navigation... + switch( event.keyCode ) + { + case KEY_UP: + // Select previous visible item: + if ( item->mPrevious ) + { + item = item->mPrevious; + while ( item->isParent() && item->isExpanded() ) + { + item = item->mChild; + while ( item->mNext ) + item = item->mNext; + } + clearSelection(); + addSelection( item->mId ); + return true; + } + + // or select parent: + if ( item->mParent ) + { + clearSelection(); + addSelection( item->mParent->mId ); + return true; + } + + return false; + break; + + case KEY_DOWN: + // Selected child if it is visible: + if ( item->isParent() && item->isExpanded() ) + { + clearSelection(); + addSelection( item->mChild->mId ); + return true; + } + // or select next sibling (recursively): + do + { + if ( item->mNext ) + { + clearSelection(); + addSelection( item->mNext->mId ); + return true; + } + + item = item->mParent; + } while ( item ); + + return false; + break; + + case KEY_LEFT: + // Contract current menu: + if ( item->isExpanded() ) + { + setItemExpanded( item->mId, false ); + scrollVisible(item); + return true; + } + // or select parent: + if ( item->mParent ) + { + clearSelection(); + addSelection( item->mParent->mId ); + return true; + } + + return false; + break; + + case KEY_RIGHT: + // Expand selected item: + if ( item->isParent() ) + { + if ( !item->isExpanded() ) + { + setItemExpanded( item->mId, true ); + scrollVisible(item); + return true; + } + + // or select child: + clearSelection(); + addSelection( item->mChild->mId ); + return true; + } + + return false; + break; + + default: + break; + } + + // Not processed, so pass the event on: + return Parent::onKeyDown( event ); +} + + + +//------------------------------------------------------------------------------ +// on mouse up look at the current item and check to see if it is valid +// to move the selected item(s) to it. +void GuiTreeViewCtrl::onMouseUp(const GuiEvent &event) +{ + if( !mActive || !mAwake || !mVisible ) + return; + + if( isMethod("onMouseUp") ) + { + BitSet32 hitFlags = 0; + Item* item; + + S32 hitItemId = -1; + if( hitTest( event.mousePoint, item, hitFlags ) ) + hitItemId = item->mId; + + Con::executef( this, "onMouseUp", Con::getIntArg( hitItemId ) ); + } + + mouseUnlock(); + + if ( mSelectedItems.empty()) + { + mDragMidPoint = NomDragMidPoint; + return; + } + + if (!mMouseDragged) + return; + + Item* newItem = NULL; + Item* newItem2 = NULL; + + if (mFlags.test(IsEditable)) + { + Parent::onMouseMove( event ); + if (mOldDragY != mMouseOverCell.y) + { + + mOldDragY = mMouseOverCell.y; + BitSet32 hitFlags = 0; + if ( !hitTest( event.mousePoint, newItem2, hitFlags ) ) + { + mDragMidPoint = NomDragMidPoint; + return; + } + + newItem2->mState.clear(Item::MouseOverBmp | Item::MouseOverText ); + + // if the newItem isn't in the mSelectedItemList then continue. + + Vector::iterator k; + for(k = mSelectedItems.begin(); k != mSelectedItems.end(); k++) + { + newItem = newItem2; + + if (*(k) == newItem) + { + mDragMidPoint = NomDragMidPoint; + return; + } + + Item * temp = *(k); + Item * grandpaTemp = newItem->mParent; + + // grandpa check, kick out if an item would be its own ancestor + while (grandpaTemp) + { + if (temp == grandpaTemp) + { + if (mDebug) + { + Con::printf("grandpa check"); + + if (temp->isInspectorData()) + Con::printf("temp's name: %s",temp->getObject()->getName()); + + if (grandpaTemp->isInspectorData()) + Con::printf("grandpa's name: %s",grandpaTemp->getObject()->getName()); + } + + mDragMidPoint = NomDragMidPoint; + return; + } + + grandpaTemp = grandpaTemp->mParent; + } + } + + + for (S32 i = 0; i mState.clear(Item::MouseOverBmp | Item::MouseOverText ); + + // move the selected item to the newItem + Item* oldParent = item->mParent; + // Snag the current parent set if any for future reference + SimSet *parentSet = NULL; + + if(oldParent->isInspectorData()) + parentSet = dynamic_cast(oldParent->getObject()); + else + { + // parent is probably script data so we search up the tree for a + // set to put our object in + Item * temp = oldParent; + while (temp) + { + if (temp->isInspectorData()) + break; + temp = temp->mParent; + } + // found an ancestor who is an inspectorData + if (temp) + { + if (temp->isInspectorData()) + parentSet = dynamic_cast(temp->getObject()); + } + } + + // unlink from the current position in the list + unlinkItem(item); + + // update the parent's children + + // check if we an only child + if (item->mParent->mChild == item) + { + if (item->mNext) + item->mParent->mChild = item->mNext; + else + item->mParent->mChild = NULL; + } + + if (mDragMidPoint != NomDragMidPoint) + { + + //if it is below an expanded tree, place as last item in the tree + //if it is below a parent who isn't expanded put below it + + // position the item above or below another item + if (mDragMidPoint == AbovemDragMidPoint) + { + // easier to treat everything as "Below the mDragMidPoint" so make some adjustments + if (mDebug) Con::printf("adding item above mDragMidPoint"); + + // above the mid point of an item, so grab either the parent + // or the previous sibling + + // does the item have a previous sibling? + if (newItem->mPrevious) + { + newItem = newItem->mPrevious; + + if (mDebug) Con::printf("treating as if below an item that isn't expanded"); + + // otherwise add below that item as a sibling + item->mParent = newItem->mParent; + item->mPrevious = newItem; + item->mNext = newItem->mNext; + if (newItem->mNext) + newItem->mNext->mPrevious = item; + newItem->mNext = item; + } + else + { + if (mDebug) Con::printf("treating as if adding below the parent of the item"); + + // instead we add as the first item below the newItem's parent + item->mParent = newItem->mParent; + item->mNext = newItem; + item->mPrevious = NULL; + newItem->mPrevious = item; + item->mParent->mChild = item; + + } + } + else if (mDragMidPoint == BelowmDragMidPoint) + { + if ((newItem->isParent())&&(newItem->isExpanded())) + { + if (mDebug) Con::printf("adding item to an expanded parent below the mDragMidPoint"); + + item->mParent = newItem; + + // then add the new item as a child + item->mNext = newItem->mChild; + if (newItem->mChild) + newItem->mChild->mPrevious = item; + item->mParent->mChild = item; + item->mPrevious = NULL; + } + else if ((!newItem->mNext)&&(newItem->mParent)&&(newItem->mParent->mParent)) + { + // add below it's parent. + if (mDebug) Con::printf("adding below a tree"); + + item->mParent = newItem->mParent->mParent; + item->mNext = newItem->mParent->mNext; + item->mPrevious = newItem->mParent; + + if (newItem->mParent->mNext) + newItem->mParent->mNext->mPrevious = item; + + newItem->mParent->mNext = item; + } + else + { + // adding below item not as a child + if (mDebug) Con::printf("adding item below the mDragMidPoint of an item"); + + item->mParent = newItem->mParent; + // otherwise the item is a sibling + if (newItem->mNext) + newItem->mNext->mPrevious = item; + item->mNext = newItem->mNext; + item->mPrevious = newItem; + newItem->mNext = item; + } + } + } + // if we're not allowed to add to items, then try to add to the parent of the hit item. + // if we are, just add to the item we hit. + else + { + if (mDebug) + { + if (item->isInspectorData() && item->getObject()) + Con::printf("Item: %i",item->getObject()->getId()); + if (newItem->isInspectorData() && newItem->getObject()) + Con::printf("Parent: %i",newItem->getObject()->getId()); + Con::printf("dragged onto an item"); + } + + // if the hit item is not already a group, and we're not allowed to drag to items, + // then try to add to the parent. + if(!mDragToItemAllowed && !newItem->isParent()) + { + // add to the item's parent. + if(!newItem->mParent || !newItem->mParent->isParent()) + { + if(mDebug) + Con::printf("could not find the parent of that item. dragging to an item is not allowed, kicking out."); + mDragMidPoint = NomDragMidPoint; + return; + } + newItem = newItem->mParent; + } + + // new parent is the item in the current cell + item->mParent = newItem; + + // adjust children if any + if (newItem->mChild) + { + if (mDebug) Con::printf("not the first child"); + + // put it at the top of the list (easier to find if there are many children) + if (newItem->mChild) + newItem->mChild->mPrevious = item; + item->mNext = newItem->mChild; + newItem->mChild = item; + item->mPrevious = NULL; + } + else + { + if (mDebug) Con::printf("first child"); + + // only child + newItem->mChild = item; + item->mNext = NULL; + item->mPrevious = NULL; + } + } + + // expand the item we added to, if it isn't expanded already + if ( !item->mParent->mState.test( Item::Expanded ) ) + setItemExpanded( item->mParent->mId, true ); + + //---------------------------------------------------------------- + // handle objects + + // Get our active SimObject if any + SimObject *simObj = NULL; + if(item->isInspectorData()) + { + simObj = item->getObject(); + } + + // Remove from the old parentset + if((simObj && parentSet)&&(oldParent != item->mParent)) + { + if (mDebug) Con::printf("removing item from old parentset"); + + // hack to get around the way removeObject takes the last item of the set + // and moves it into the place of the object we removed + if (parentSet->size()>0) + { + SimObject *lastObject = parentSet->last(); + parentSet->removeObject(simObj); + parentSet->reOrder(lastObject); + } + else + { + parentSet->removeObject(simObj); + } + } + + // Snag the newparent set if any... + SimSet *newParentSet = NULL; + + if(item->mParent->isInspectorData()) + { + if (mDebug) Con::printf("getting a new parent set"); + + SimObject * tmpObj = item->mParent->getObject(); + newParentSet = dynamic_cast(tmpObj); + } + else + { + // parent is probably script data so we search up the tree for a + // set to put our object in + if (mDebug) Con::printf("oh nos my parent is script!"); + + Item * temp = item->mParent; + while (temp) + { + if (temp->isInspectorData()) + break; + temp = temp->mParent; + } + + // found a ancestor who is an inspectorData + if (temp) + { + if (temp->isInspectorData()) + newParentSet = dynamic_cast(temp->getObject()); + } + else + { + newParentSet = NULL; + } + } + + if(simObj && newParentSet) + { + if (mDebug) Con::printf("simobj and new ParentSet"); + + if (oldParent != item->mParent) + newParentSet->addObject(simObj); + + //order the objects in the simset according to their + //order in the tree view control + if(!item->mNext) + { + if(!item->mPrevious) break; + //bring to the end of the set + SimObject *prevObject = item->mPrevious->getObject(); + if (prevObject && item->getObject()) + { + newParentSet->reOrder(item->getObject(), prevObject); + } + } + else + { + //reorder within the set + SimObject *nextObject = item->mNext->getObject(); + if(nextObject && item->getObject()) + { + newParentSet->reOrder(item->getObject(), nextObject); + } + } + } + else if (!simObj&&newParentSet) + { + // our current item is script data. but it may have children who + // is inspector data who need an updated set + if (mDebug) Con::printf("no simobj but new parentSet"); + if (item->mChild) + inspectorSearch(item->mChild, item, parentSet, newParentSet); + + } + else if (simObj&&!newParentSet) + { + if (mDebug) Con::printf("simobject and no new parent set"); + } + else + if (mDebug) Con::printf("no simobject and no new parent set"); + } + + // And update everything. + scrollVisible(newItem); + + if(isMethod("onDragDropped")) + Con::executef(this, "onDragDropped"); + } + } + + mDragMidPoint = NomDragMidPoint; +} + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::onMouseDragged(const GuiEvent &event) +{ + if( isMethod( "onMouseDragged" ) && mDragStartInSelection ) + Con::executef( this, "onMouseDragged" ); + + if(!mSupportMouseDragging) + return; + + if( !mActive || !mAwake || !mVisible ) + return; + + if (mSelectedItems.size() == 0) + return; + Point2I pt = globalToLocalCoord(event.mousePoint); + Parent::onMouseMove(event); + mouseLock(); + mMouseDragged = true; + // whats our mDragMidPoint? + mCurrentDragCell = mMouseOverCell.y; + S32 midpCell = mCurrentDragCell * mItemHeight + (mItemHeight/2); + S32 currentY = pt.y; + S32 yDiff = currentY-midpCell; + S32 variance = (mItemHeight/5); + if( mPreviousDragCell >= 0 && mPreviousDragCell < mVisibleItems.size() ) + mVisibleItems[mPreviousDragCell]->mState.clear( Item::MouseOverBmp | Item::MouseOverText ); + + bool hoverItem = false; + + if (mAbs(yDiff) <= variance) + { + mDragMidPoint = NomDragMidPoint; + // highlight the current item + // hittest to detect whether we are on an item + // ganked from onMouseMouse + + // used for tracking what our last cell was so we can clear it. + mPreviousDragCell = mCurrentDragCell; + if (mCurrentDragCell >= 0) + { + + Item* item = NULL; + BitSet32 hitFlags = 0; + if ( !hitTest( event.mousePoint, item, hitFlags ) ) + return; + + if ( item->mState.test(Item::VirtualParent) ) + { + hoverItem = true; + + if ( hitFlags.test( OnImage ) ) + item->mState.set( Item::MouseOverBmp ); + + if ( hitFlags.test( OnText )) + item->mState.set( Item::MouseOverText ); + + // Always redraw the entire mouse over item, since we are distinguishing + // between the bitmap and the text: + setUpdateRegion( Point2I( mMouseOverCell.x * mCellSize.x, mMouseOverCell.y * mCellSize.y ), mCellSize ); + } + } + } + + if ( !hoverItem ) + { + //above or below an item? + if (yDiff < 0) + mDragMidPoint = AbovemDragMidPoint; + else + mDragMidPoint = BelowmDragMidPoint; + } +} + +void GuiTreeViewCtrl::onMiddleMouseDown(const GuiEvent & event) +{ + //for debugging items + if (mDebug) { + Item* item; + BitSet32 hitFlags = 0; + hitTest( event.mousePoint, item, hitFlags ); + Con::printf("debugging %d", item->mId); + Point2I pt = globalToLocalCoord(event.mousePoint); + if (item->isInspectorData() && item->getObject()) { + Con::printf("object data:"); + Con::printf("name:%s",item->getObject()->getName()); + Con::printf("className:%s",item->getObject()->getClassName()); + } + Con::printf("contents of mSelectedItems:"); + for(S32 i = 0; i < mSelectedItems.size(); i++) { + if (mSelectedItems[i]->isInspectorData()) { + Con::printf("%d",mSelectedItems[i]->getObject()->getId()); + } else + Con::printf("wtf %d", mSelectedItems[i]); + } + Con::printf("contents of mSelected"); + for (S32 j = 0; j < mSelected.size(); j++) { + Con::printf("%d", mSelected[j]); + } + S32 mCurrentDragCell = mMouseOverCell.y; + S32 midpCell = (mCurrentDragCell) * mItemHeight + (mItemHeight/2); + S32 currentY = pt.y; + S32 yDiff = currentY-midpCell; + Con::printf("cell info: (%d,%d) mCurrentDragCell=%d est=(%d,%d,%d) ydiff=%d",pt.x,pt.y,mCurrentDragCell,mCurrentDragCell*mItemHeight, midpCell, (mCurrentDragCell+1)*mItemHeight,yDiff); + } +} + + +void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) +{ + if( !mActive || !mAwake || !mVisible ) + { + Parent::onMouseDown(event); + return; + } + if ( mProfile->mCanKeyFocus ) + setFirstResponder(); + + Item * item = 0; + BitSet32 hitFlags; + mOldDragY = 0; + mDragMidPoint = NomDragMidPoint; + + mMouseDragged = false; + + // + if(!hitTest(event.mousePoint, item, hitFlags)) + return; + + // + if( event.modifier & SI_MULTISELECT ) + { + bool selectFlag = item->mState.test(Item::Selected); + if (selectFlag == true) + { + // already selected, so unselect it and remove it + removeSelection(item->mId); + } else + { + // otherwise select it and add it to the list + // check if it is already on the list. + /*bool newSelection = true; + for (S32 i = 0; i < mSelectedItems.size(); i++) { + if (mSelectedItems[i] == item) { + newSelection = false; + } + }*/ + //if (newSelection) { + addSelection(item->mId); + //} + + + + } + } + else if( event.modifier & SI_RANGESELECT ) + { + // is something already selected? + S32 firstSelectedIndex = 0; + Item * firstItem = NULL; + if (!mSelectedItems.empty()) + { + firstItem = mSelectedItems.front(); + for (S32 i = 0; i < mVisibleItems.size();i++) + { + if (mVisibleItems[i] == mSelectedItems.front()) + { + firstSelectedIndex = i; + break; + } + } + S32 mCurrentDragCell = mMouseOverCell.y; + if (mVisibleItems[firstSelectedIndex] != firstItem ) + { + /* + Con::printf("something isn't right..."); + if (mVisibleItems[firstSelectedIndex]->isInspectorData()) + Con::printf("visibleItem %s",mVisibleItems[firstSelectedIndex]->getObject()->getName()); + if (firstItem->isInspectorData()) + Con::printf("firstItem %s",firstItem->getObject()->getName()); + */ + } + else + { + // select the cells + Con::executef( this, "onAddMultipleSelectionBegin" ); + if ((mCurrentDragCell) < firstSelectedIndex) + { + //select up + for (S32 j = (mCurrentDragCell); j < firstSelectedIndex; j++) + { + addSelection(mVisibleItems[j]->mId, false); + } + } + else + { + // select down + for (S32 j = firstSelectedIndex+1; j < (mCurrentDragCell+1); j++) + { + addSelection(mVisibleItems[j]->mId, false); + } + } + + // Scroll to view the last selected cell. + scrollVisible( mVisibleItems[mCurrentDragCell] ); + + Con::executef( this, "onAddMultipleSelectionEnd" ); + } + } + } + else if( event.modifier & SI_PRIMARY_ALT ) + { + if (item->isInspectorData() && item->getObject()) + setAddGroup(item->getObject()); + } + else if (!hitFlags.test(OnImage)) + { + bool newSelection = true; + + // First check to see if the item is already selected, but only if + // we don't want to clear the selection on a single click. + if (!mClearAllOnSingleSelection) + { + Vector::iterator k; + for(k = mSelectedItems.begin(); k != mSelectedItems.end(); k++) { + if(*(k) == item) + { + newSelection = false; + break; + } + } + } + + + // if the item is not already selected then we have a + //newly selected item, so clear our list of selected items + if (newSelection) + { + clearSelection(); + addSelection( item->mId ); + } + } + if ( hitFlags.test( OnText ) && ( event.mouseClickCount > 1 ) && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand ); + + // For dragging, note if hit is in selection. + + mDragStartInSelection = isItemSelected( item->mId ); + + if(!item->isParent()) + return; + + // + if ( mFullRowSelect || hitFlags.test( OnImage ) ) + { + item->setExpanded(!item->isExpanded()); + if( !item->isInspectorData() && item->mState.test(Item::VirtualParent) ) + onVirtualParentExpand(item); + scrollVisible(item); + } +} + + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::onMouseMove( const GuiEvent &event ) +{ + if ( mMouseOverCell.y >= 0 && mVisibleItems.size() > mMouseOverCell.y) + mVisibleItems[mMouseOverCell.y]->mState.clear( Item::MouseOverBmp | Item::MouseOverText ); + + Parent::onMouseMove( event ); + + if ( mMouseOverCell.y >= 0 ) + { + Item* item = NULL; + BitSet32 hitFlags = 0; + if ( !hitTest( event.mousePoint, item, hitFlags ) ) + return; + + if ( hitFlags.test( OnImage ) ) + item->mState.set( Item::MouseOverBmp ); + + if ( hitFlags.test( OnText )) + item->mState.set( Item::MouseOverText ); + + // Always redraw the entire mouse over item, since we are distinguishing + // between the bitmap and the text: + setUpdateRegion( Point2I( mMouseOverCell.x * mCellSize.x, mMouseOverCell.y * mCellSize.y ), mCellSize ); + } +} + + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::onMouseEnter( const GuiEvent &event ) +{ + Parent::onMouseEnter( event ); + onMouseMove( event ); +} + + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::onMouseLeave( const GuiEvent &event ) +{ + if ( mMouseOverCell.y >= 0 && mVisibleItems.size() > mMouseOverCell.y) + mVisibleItems[mMouseOverCell.y]->mState.clear( Item::MouseOverBmp | Item::MouseOverText ); + + Parent::onMouseLeave( event ); +} + + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::onRightMouseDown(const GuiEvent & event) +{ + if(!mActive) + { + Parent::onRightMouseDown(event); + return; + } + + Item * item = NULL; + BitSet32 hitFlags; + + // + if(!hitTest(event.mousePoint, item, hitFlags)) + return; + + // + char bufs[2][32]; + dSprintf(bufs[0], 32, "%d", item->mId); + dSprintf(bufs[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y); + + if (item->isInspectorData() && item->getObject()) + Con::executef(this, "onRightMouseDown", bufs[0],bufs[1],Con::getIntArg(item->getObject()->getId())); + else + Con::executef(this, "onRightMouseDown", bufs[0], bufs[1]); +} + +void GuiTreeViewCtrl::onRightMouseUp(const GuiEvent & event) +{ + Item *item = NULL; + BitSet32 hitFlags; + + if ( !hitTest( event.mousePoint, item, hitFlags ) ) + return; + + if ( hitFlags.test( OnText ) ) + { + if ( !isItemSelected( item->getID() ) ) + { + clearSelection(); + addSelection( item->getID() ); + } + + char bufs[2][32]; + dSprintf(bufs[0], 32, "%d", item->mId); + dSprintf(bufs[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y); + + if (item->isInspectorData() && item->getObject()) + Con::executef(this, "onRightMouseUp", bufs[0],bufs[1],Con::getIntArg(item->getObject()->getId())); + else + Con::executef(this, "onRightMouseUp", bufs[0], bufs[1]); + } + else + { + clearSelection(); + } + + Parent::onRightMouseUp(event); +} + +//------------------------------------------------------------------------------ + +void GuiTreeViewCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + // Get all our contents drawn! + Parent::onRender(offset,updateRect); + + // Deal with drawing the drag & drop line, if any... + GFX->setClipRect(updateRect); + + // only do it if we have a mDragMidPoint + if (mDragMidPoint == NomDragMidPoint || !mSupportMouseDragging ) + return; + + ColorF greyLine(0.5,0.5,0.5,1); + Point2F squarePt; + + // CodeReview: LineWidth is not supported in Direct3D. This is lame. [5/10/2007 Pat] + // draw mDragMidPoint lines with a diamond + if (mDragMidPoint == AbovemDragMidPoint) + { + S32 tempY = mItemHeight*mCurrentDragCell+offset.y ; + squarePt.y = (F32)tempY; + squarePt.x = 125.f+offset.x; + GFX->getDrawUtil()->drawLine(0+offset.x, tempY, 250+offset.x, tempY,greyLine); + GFX->getDrawUtil()->draw2DSquare(squarePt, 6, 90 ); + + } + if (mDragMidPoint == BelowmDragMidPoint) + { + S32 tempY2 = mItemHeight*(mCurrentDragCell+1) +offset.y; + squarePt.y = (F32)tempY2; + squarePt.x = 125.f+offset.x; + GFX->getDrawUtil()->drawLine(0+offset.x, tempY2, 250+offset.x, tempY2,greyLine); + GFX->getDrawUtil()->draw2DSquare(squarePt,6, 90 ); + + } +} + +void GuiTreeViewCtrl::onRenderCell(Point2I offset, Point2I cell, bool, bool ) +{ + if( !mVisibleItems.size() ) + return; + + // Do some sanity checking and data retrieval. + AssertFatal(cell.y < mVisibleItems.size(), "GuiTreeViewCtrl::onRenderCell: invalid cell"); + Item * item = mVisibleItems[cell.y]; + + // If there's no object, deal with it. + if(item->isInspectorData()) + if(!item->getObject()) + return; + + RectI drawRect( offset, mCellSize ); + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->clearBitmapModulation(); + + FrameAllocatorMarker txtBuff; + + // Ok, we have the item. There are a few possibilities at this point: + // - We need to draw inheritance lines and a treeview-chosen icon + // OR + // - We have to draw an item-dependent icon + // - If we're mouseover, we have to highlight it. + // + // - We have to draw the text for the item + // - Taking into account various mouseover states + // - Taking into account the value (set or not) + // - If it's an inspector data, we have to do some custom rendering + + // Ok, first draw the tab and icon. + + // Do we draw the tree lines? + if(mFlags.test(ShowTreeLines)) + { + drawRect.point.x += ( mTabSize * item->mTabLevel ); + Item* parent = item->mParent; + for ( S32 i = item->mTabLevel; ( parent && i > 0 ); i-- ) + { + drawRect.point.x -= mTabSize; + if ( parent->mNext ) + drawer->drawBitmapSR( mProfile->mTextureObject, drawRect.point, mProfile->mBitmapArrayRects[BmpLine] ); + + parent = parent->mParent; + } + } + + // Now, the icon... + drawRect.point.x = offset.x + mTabSize * item->mTabLevel; + + // First, draw the rollover glow, if it's an inner node. + if ( item->isParent() && item->mState.test( Item::MouseOverBmp ) ) + drawer->drawBitmapSR( mProfile->mTextureObject, drawRect.point, mProfile->mBitmapArrayRects[BmpGlow] ); + + // Now, do we draw a treeview-selected item or an item dependent one? + S32 newOffset = 0; // This is stored so we can render glow, then update render pos. + + if(item->isInspectorData()) + { + S32 bitmap = 0; + + // Ok, draw the treeview lines as appropriate. + if ( !item->isParent() ) + { + bitmap = item->mNext ? BmpChild : BmpLastChild; + } + else + { + bitmap = item->isExpanded() ? BmpExp : BmpCon; + + if ( item->mParent || item->mPrevious ) + bitmap += ( item->mNext ? 3 : 2 ); + else + bitmap += ( item->mNext ? 1 : 0 ); + } + + if ( ( bitmap >= 0 ) && ( bitmap < mProfile->mBitmapArrayRects.size() ) ) + { + drawer->drawBitmapSR( mProfile->mTextureObject, drawRect.point, mProfile->mBitmapArrayRects[bitmap] ); + newOffset = mProfile->mBitmapArrayRects[bitmap].extent.x; + } + + // draw lock icon if need be + S32 icon = Lock1; + S32 icon2 = Hidden; + + if (item->getObject() && item->getObject()->isLocked()) + { + if (mIconTable[icon]) + { + //drawRect.point.x = offset.x + mTabSize * item->mTabLevel + mIconTable[icon].getWidth(); + drawRect.point.x += mIconTable[icon].getWidth(); + drawer->drawBitmap( mIconTable[icon], drawRect.point ); + } + } + + if (item->getObject() && item->getObject()->isHidden()) + { + if (mIconTable[icon2]) + { + //drawRect.point.x = offset.x + mTabSize * item->mTabLevel + mIconTable[icon].getWidth(); + drawRect.point.x += mIconTable[icon2].getWidth(); + drawer->drawBitmap( mIconTable[icon2], drawRect.point ); + } + } + + + SimObject * pObject = item->getObject(); + SimGroup * pGroup = ( pObject == NULL ) ? NULL : dynamic_cast( pObject ); + + // draw the icon associated with the item + if (item->mState.test(Item::VirtualParent)) + { + if ( pGroup != NULL) + { + if (item->isExpanded()) + item->mIcon = SimGroup1; + else + item->mIcon = SimGroup2; + } + else + item->mIcon = SimGroup2; + } + + if (item->mState.test(Item::Marked)) + { + if (item->isInspectorData()) + { + if ( pGroup != NULL ) + { + if (item->isExpanded()) + item->mIcon = SimGroup3; + else + item->mIcon = SimGroup4; + } + } + } + + GFXTexHandle iconHandle; + + if ( ( item->mIcon != -1 ) && mIconTable[item->mIcon] ) + iconHandle = mIconTable[item->mIcon]; +#ifdef TORQUE_TOOLS + else + iconHandle = gEditorIcons.findIcon( item->getObject() ); +#endif + + if ( iconHandle.isValid() ) + { + S32 iconHeight = (mItemHeight - iconHandle.getHeight()) / 2; + S32 oldHeight = drawRect.point.y; + if(iconHeight > 0) + drawRect.point.y += iconHeight; + drawRect.point.x += iconHandle.getWidth(); + drawer->drawBitmap( iconHandle, drawRect.point ); + drawRect.point.y = oldHeight; + } + } + else + { + S32 bitmap = 0; + + // Ok, draw the treeview lines as appropriate. + if ( !item->isParent() ) + bitmap = item->mNext ? BmpChild : BmpLastChild; + else + { + bitmap = item->isExpanded() ? BmpExp : BmpCon; + + if ( item->mParent || item->mPrevious ) + bitmap += ( item->mNext ? 3 : 2 ); + else + bitmap += ( item->mNext ? 1 : 0 ); + } + + if ( ( bitmap >= 0 ) && ( bitmap < mProfile->mBitmapArrayRects.size() ) ) + { + drawer->drawBitmapSR( mProfile->mTextureObject, drawRect.point, mProfile->mBitmapArrayRects[bitmap] ); + newOffset = mProfile->mBitmapArrayRects[bitmap].extent.x; + } + + S32 icon = item->isExpanded() ? item->mScriptInfo.mExpandedImage : item->mScriptInfo.mNormalImage; + if ( icon ) + { + if (mIconTable[icon]) + { + S32 iconHeight = (mItemHeight - mIconTable[icon].getHeight()) / 2; + S32 oldHeight = drawRect.point.y; + if(iconHeight > 0) + drawRect.point.y += iconHeight; + drawRect.point.x += mIconTable[icon].getWidth(); + drawer->drawBitmap( mIconTable[icon], drawRect.point ); + drawRect.point.y = oldHeight; + } + } + } + + // Ok, update offset so we can render some text! + drawRect.point.x += newOffset; + + // Ok, now we're off to rendering the actual data for the treeview item. + + U32 bufLen = item->mDataRenderWidth + 1; + char *displayText = (char *)txtBuff.alloc(bufLen); + displayText[bufLen-1] = 0; + item->getDisplayText(bufLen, displayText); + + // Draw the rollover/selected bitmap, if one was specified. + drawRect.extent.x = mProfile->mFont->getStrWidth( displayText ) + ( 2 * mTextOffset ); + if ( item->mState.test( Item::Selected ) && mTexSelected ) + drawer->drawBitmapStretch( mTexSelected, drawRect ); + else if ( item->mState.test( Item::MouseOverText ) && mTexRollover ) + drawer->drawBitmapStretch( mTexRollover, drawRect ); + + // Offset a bit so as to space text properly. + drawRect.point.x += mTextOffset; + + // Determine what color the font should be. + ColorI fontColor; + + fontColor = item->mState.test( Item::Selected ) ? mProfile->mFontColorSEL : + ( item->mState.test( Item::MouseOverText ) ? mProfile->mFontColorHL : mProfile->mFontColor ); + + if (item->mState.test(Item::Selected)) + { + drawer->drawRectFill(drawRect, mProfile->mFillColorSEL); + } + else if (item->mState.test(Item::MouseOverText)) + { + drawer->drawRectFill(drawRect, mProfile->mFillColorHL); + } + + if( item->mState.test(Item::MouseOverText) ) + { + fontColor = mProfile->mFontColorHL; + } + + drawer->setBitmapModulation( fontColor ); + + // Center the text horizontally. + S32 height = (mItemHeight - mProfile->mFont->getHeight()) / 2; + + if(height > 0) + drawRect.point.y += height; + + // JDD - offset by two pixels or so to keep the text from rendering RIGHT ONTOP of the outline + drawRect.point.x += 2; + + drawer->drawText( mProfile->mFont, drawRect.point, displayText, mProfile->mFontColors ); + +} + +//------------------------------------------------------------------------------ + +bool GuiTreeViewCtrl::renderTooltip( const Point2I &hoverPos, const Point2I& cursorPos, const char* tipText ) +{ + Item* item; + BitSet32 flags = 0; + char buf[ 1024 ]; + if( hitTest( cursorPos, item, flags ) && (!item->mTooltip.isEmpty() || mUseInspectorTooltips) ) + { + bool render = true; + + if( mTooltipOnWidthOnly ) + { + // Only render tooltip if the item's text is cut off with its + // parent scroll control. + GuiScrollCtrl *pScrollParent = dynamic_cast( getParent() ); + if ( pScrollParent ) + { + Point2I textStart; + Point2I textExt; + + const Point2I pos = globalToLocalCoord(cursorPos); + textStart.y = pos.y / mCellSize.y; + textStart.y *= mCellSize.y; + + // The following is taken from hitTest() + textStart.x = mTabSize * item->mTabLevel; + S32 image = BmpChild; + if((image >= 0) && (image < mProfile->mBitmapArrayRects.size())) + textStart.x += mProfile->mBitmapArrayRects[image].extent.x; + textStart.x += mTextOffset; + + textStart.x += getInspectorItemIconsWidth( item ); + + FrameAllocatorMarker txtAlloc; + U32 bufLen = item->getDisplayTextLength(); + char *buf = (char*)txtAlloc.alloc(bufLen); + item->getDisplayText(bufLen, buf); + textExt.x = mProfile->mFont->getStrWidth(buf); + textExt.y = mProfile->mFont->getHeight(); + + if( pScrollParent->isRectCompletelyVisible(RectI(textStart, textExt)) ) + render = false; + } + } + + if( render ) + { + if( mUseInspectorTooltips ) + { + item->getDisplayText( sizeof( buf ), buf ); + tipText = buf; + } + else + { + tipText = item->mTooltip.c_str(); + } + } + } + + return defaultTooltipRender( cursorPos, cursorPos, tipText ); +} + +//------------------------------------------------------------------------------ +void GuiTreeViewCtrl::clearSelection() +{ + while ( !mSelectedItems.empty() ) + { + removeSelection( mSelectedItems.last()->mId ); + } + + mSelectedItems.clear(); + mSelected.clear(); + + onClearSelection(); + + Con::executef(this, "onClearSelection"); +} + +void GuiTreeViewCtrl::lockSelection(bool lock) +{ + for(U32 i = 0; i < mSelectedItems.size(); i++) + { + if(mSelectedItems[i]->isInspectorData()) + mSelectedItems[i]->getObject()->setLocked(lock); + } +} +void GuiTreeViewCtrl::hideSelection(bool hide) +{ + for(U32 i = 0; i < mSelectedItems.size(); i++) + { + if(mSelectedItems[i]->isInspectorData()) + mSelectedItems[i]->getObject()->setHidden(hide); + } +} + +//------------------------------------------------------------------------------ + +// handles icon assignments +S32 GuiTreeViewCtrl::getIcon(const char * iconString) +{ + return -1; +} + +void GuiTreeViewCtrl::addInspectorDataItem(Item *parent, SimObject *obj) +{ + S32 icon = getIcon(obj->getClassName()); + Item *item = createItem(icon); + item->mState.set(Item::InspectorData); + + // If we only want to display internal names + // then set the item flag for it. + if ( mInternalNamesOnly ) + item->mState.set( Item::InternalNameOnly ); + else if( mObjectNamesOnly ) + item->mState.set( Item::ObjectNameOnly ); + + // Deal with child objects... + if(dynamic_cast(obj)) + item->mState.set(Item::VirtualParent); + + // Actually store the data! + item->setObject(obj); + + // Now add us to the data structure... + if(parent) + { + // Add as child of parent. + if(parent->mChild) + { + Item * traverse = parent->mChild; + while(traverse->mNext) + traverse = traverse->mNext; + + traverse->mNext = item; + item->mPrevious = traverse; + } + else + parent->mChild = item; + + item->mParent = parent; + } + else + { + // If no parent, add to root. + item->mNext = mRoot; + mRoot = item; + item->mParent = NULL; + } + + mFlags.set(RebuildVisible); +} + +void GuiTreeViewCtrl::unlinkItem(Item * item) +{ + if (item->mPrevious) + item->mPrevious->mNext = item->mNext; + + if (item->mNext) + item->mNext->mPrevious = item->mPrevious; +} + +bool GuiTreeViewCtrl::childSearch(Item * item, SimObject *obj, bool yourBaby) +{ + Item * temp = item->mChild; + while (temp) + { + //do you have my baby? + if (temp->isInspectorData()) + { + if (temp->getObject() == obj) + yourBaby = false; //probably a child of an inner script + } + yourBaby = childSearch(temp,obj, yourBaby); + temp = temp->mNext; + } + return yourBaby; +} + +void GuiTreeViewCtrl::inspectorSearch(Item * item, Item * parent, SimSet * parentSet, SimSet * newParentSet) +{ + if (!parentSet||!newParentSet) + return; + + if (item == parent->mNext) + return; + + if (item) + { + if (item->isInspectorData()) + { + // remove the object from the parentSet and add it to the newParentSet + SimObject* simObj = item->getObject(); + + if (parentSet->size()) + { + SimObject *lastObject = parentSet->last(); + parentSet->removeObject(simObj); + parentSet->reOrder(lastObject); + } + else + parentSet->removeObject(simObj); + + newParentSet->addObject(simObj); + + if (item->mNext) + { + inspectorSearch(item->mNext, parent, parentSet, newParentSet); + return; + } + else + { + // end of children so backing up + if (item->mParent == parent) + return; + else + { + inspectorSearch(item->mParent->mNext, parent, parentSet, newParentSet); + return; + } + } + } + + if (item->mChild) + { + inspectorSearch(item->mChild, parent, parentSet, newParentSet); + return; + } + + if (item->mNext) + { + inspectorSearch(item->mNext, parent, parentSet, newParentSet); + return; + } + } +} + +bool GuiTreeViewCtrl::objectSearch( const SimObject *object, Item **item ) +{ + for ( U32 i = 0; i < mItems.size(); i++ ) + { + Item *pItem = mItems[i]; + + if ( !pItem ) + continue; + + SimObject *pObj = pItem->getObject(); + + if ( pObj && pObj == object ) + { + *item = pItem; + return true; + } + } + + return false; +} + +bool GuiTreeViewCtrl::onVirtualParentBuild(Item *item, bool bForceFullUpdate) +{ + if(!item->mState.test(Item::InspectorData)) + return true; + + // Blast an item if it doesn't have a corresponding SimObject... + if(item->mInspectorInfo.mObject == NULL) + { + removeItem(item->mId); + return false; + } + + // Skip the next stuff unless we're expanded... + if(!item->isExpanded() && !bForceFullUpdate && !( item == mRoot && !mShowRoot ) ) + return true; + + // Verify that we have all the kids we should in here... + SimSet *srcObj = dynamic_cast(&(*item->mInspectorInfo.mObject)); + + // If it's not a SimSet... WTF are we doing here? + if(!srcObj) + return true; + + SimSet::iterator i; + + // This is slow but probably ok. + for(i = srcObj->begin(); i != srcObj->end(); i++) + { + SimObject *obj = *i; + + // If we can't find it, add it. + // unless it has a parent that is a child that is a script + Item *res = item->findChildByValue(obj); + + bool foundChild = true; + + // search the children. if any of them are the parent of the object then don't add it. + foundChild = childSearch(item,obj,foundChild); + + if(!res && foundChild) + { + if (mDebug) Con::printf("adding something"); + addInspectorDataItem(item, obj); + } + } + + return true; +} + +bool GuiTreeViewCtrl::onVirtualParentExpand(Item *item) +{ + // Do nothing... + return true; +} + +bool GuiTreeViewCtrl::onVirtualParentCollapse(Item *item) +{ + // Do nothing... + return true; +} + +void GuiTreeViewCtrl::inspectObject(SimObject *obj, bool okToEdit) +{ + destroyTree(); + mFlags.set(IsEditable, okToEdit); + + Con::executef( this, "onDefineIcons" ); + + addInspectorDataItem(NULL, obj); +} + +S32 GuiTreeViewCtrl::findItemByName(const char *name) +{ + for (S32 i = 0; i < mItems.size(); i++) + if (mItems[i] && dStrcmp(mItems[i]->getText(),name) == 0) + return mItems[i]->mId; + + return 0; +} + +StringTableEntry GuiTreeViewCtrl::getTextToRoot( S32 itemId, const char * delimiter ) +{ + Item * item = getItem(itemId); + + if(!item) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getTextToRoot: invalid start item id!"); + return StringTable->insert(""); + } + + if(item->isInspectorData()) + { + Con::errorf(ConsoleLogEntry::General, "GuiTreeViewCtrl::getTextToRoot: cannot get text to root of inspector data items"); + return StringTable->insert(""); + } + + char bufferOne[1024]; + char bufferTwo[1024]; + char bufferNodeText[128]; + + dMemset( bufferOne, 0, sizeof(bufferOne) ); + dMemset( bufferTwo, 0, sizeof(bufferTwo) ); + + dStrcpy( bufferOne, item->getText() ); + + Item *prevNode = item->mParent; + while ( prevNode ) + { + dMemset( bufferNodeText, 0, sizeof(bufferNodeText) ); + dStrcpy( bufferNodeText, prevNode->getText() ); + dSprintf( bufferTwo, 1024, "%s%s%s",bufferNodeText, delimiter, bufferOne ); + dStrcpy( bufferOne, bufferTwo ); + dMemset( bufferTwo, 0, sizeof(bufferTwo) ); + prevNode = prevNode->mParent; + } + + // Return the result, StringTable-ized. + return StringTable->insert( bufferOne, true ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod(GuiTreeViewCtrl, findItemByName, S32, 3, 3, "(find item by name and returns the mId)") +{ + return(object->findItemByName(argv[2])); +} + +ConsoleMethod( GuiTreeViewCtrl, findChildItemByName, S32, 4, 4, "( int parent, string name ) - Return the ID of the child that matches the given name or 0." ) +{ + S32 id = dAtoi( argv[ 2 ] ); + const char* childName = argv[ 3 ]; + + if( id == 0 ) + { + if( !object->mRoot ) + return 0; + + GuiTreeViewCtrl::Item* root = object->mRoot; + while( root ) + { + if( dStricmp( root->getText(), childName ) == 0 ) + return root->getID(); + + root = root->mNext; + } + + return 0; + } + else + { + GuiTreeViewCtrl::Item* item = object->getItem( id ); + + if( !item ) + { + Con::errorf( "GuiTreeViewCtrl.findChildItemByName - invalid parent ID '%i'", id ); + return 0; + } + + GuiTreeViewCtrl::Item* child = item->findChildByName( argv[ 3 ] ); + if( !child ) + return 0; + + return child->mId; + } +} + +ConsoleMethod(GuiTreeViewCtrl, insertItem, S32, 4, 8, "(TreeItemId parent, name, value, icon, normalImage=0, expandedImage=0)") +{ + S32 norm=0, expand=0; + + if (argc > 6) + { + norm = dAtoi(argv[6]); + if(argc > 7) + expand = dAtoi(argv[7]); + } + + return(object->insertItem(dAtoi(argv[2]), argv[3], argv[4], argv[5], norm, expand)); +} + +ConsoleMethod(GuiTreeViewCtrl, lockSelection, void, 2, 3, "(locks selections)") +{ + bool lock = true; + if(argc == 3) + lock = dAtob(argv[2]); + object->lockSelection(lock); +} + +ConsoleMethod( GuiTreeViewCtrl, hideSelection, void, 2, 3, "( [bool state] ) - set hidden state of objects in selection" ) +{ + bool hide = true; + if( argc == 3 ) + hide = dAtob( argv[ 2 ] ); + object->hideSelection( hide ); +} + +ConsoleMethod(GuiTreeViewCtrl, clearSelection, void, 2, 2, "(clears selection)") +{ + object->clearSelection(); +} + +ConsoleMethod(GuiTreeViewCtrl, deleteSelection, void, 2, 2, "(deletes the selected items)") +{ + object->deleteSelection(); +} + +ConsoleMethod(GuiTreeViewCtrl, addSelection, void, 3, 3, "(selects an item)") +{ + S32 id = dAtoi(argv[2]); + object->addSelection(id); +} + +ConsoleMethod(GuiTreeViewCtrl, addChildSelectionByValue, void, 4, 4, "addChildSelectionByValue(TreeItemId parent, value)") +{ + S32 id = dAtoi(argv[2]); + GuiTreeViewCtrl::Item* parentItem = object->getItem(id); + GuiTreeViewCtrl::Item* child = parentItem->findChildByValue(argv[3]); + object->addSelection(child->getID()); +} + +ConsoleMethod(GuiTreeViewCtrl, removeSelection, void, 3, 3, "(deselects an item)") +{ + S32 id = dAtoi(argv[2]); + object->removeSelection(id); +} + +ConsoleMethod(GuiTreeViewCtrl, removeChildSelectionByValue, void, 4, 4, "removeChildSelectionByValue(TreeItemId parent, value)") +{ + S32 id = dAtoi(argv[2]); + GuiTreeViewCtrl::Item* parentItem = object->getItem(id); + if(parentItem) + { + GuiTreeViewCtrl::Item* child = parentItem->findChildByValue(argv[3]); + if(child) + { + object->removeSelection(child->getID()); + } + } +} + +ConsoleMethod(GuiTreeViewCtrl, selectItem, bool, 3, 4, "(TreeItemId item, bool select=true)") +{ + S32 id = dAtoi(argv[2]); + bool select = true; + if(argc == 4) + select = dAtob(argv[3]); + + return(object->setItemSelected(id, select)); +} + +ConsoleMethod(GuiTreeViewCtrl, expandItem, bool, 3, 4, "(TreeItemId item, bool expand=true)") +{ + S32 id = dAtoi(argv[2]); + bool expand = true; + if(argc == 4) + expand = dAtob(argv[3]); + return(object->setItemExpanded(id, expand)); +} + +ConsoleMethod(GuiTreeViewCtrl, markItem, bool, 3, 4, "(TreeItemId item, bool mark=true)") +{ + S32 id = dAtoi(argv[2]); + bool mark = true; + if(argc == 4) + mark = dAtob(argv[3]); + return object->markItem(id, mark); +} + +// Make the given item visible. +ConsoleMethod(GuiTreeViewCtrl, scrollVisible, void, 3, 3, "(TreeItemId item)") +{ + object->scrollVisible(dAtoi(argv[2])); +} + +ConsoleMethod(GuiTreeViewCtrl, buildIconTable, bool, 3,3, "(builds an icon table)") +{ + const char * icons = argv[2]; + return object->buildIconTable(icons); +} + +ConsoleMethod( GuiTreeViewCtrl, open, void, 3, 4, "(SimSet obj, bool okToEdit=true) Set the root of the tree view to the specified object, or to the root set.") +{ + SimSet *treeRoot = NULL; + SimObject* target = Sim::findObject(argv[2]); + + bool okToEdit = true; + + if (argc == 4) + okToEdit = dAtob(argv[3]); + + if (target) + treeRoot = dynamic_cast(target); + + if (! treeRoot) + Sim::findObject(RootGroupId, treeRoot); + + object->inspectObject(treeRoot,okToEdit); +} + +ConsoleMethod( GuiTreeViewCtrl, setItemTooltip, void, 4, 4, "( int id, string text ) - Set the tooltip to show for the given item." ) +{ + int id = dAtoi( argv[ 2 ] ); + + GuiTreeViewCtrl::Item* item = object->getItem( id ); + if( !item ) + { + Con::errorf( "GuiTreeViewCtrl::setTooltip() - invalid item id '%i'", id ); + return; + } + + item->mTooltip = argv[ 3 ]; +} + +ConsoleMethod( GuiTreeViewCtrl, setItemImages, void, 5, 5, "( int id, int normalImage, int expandedImage ) - Sets the normal and expanded images to show for the given item." ) +{ + int id = dAtoi( argv[ 2 ] ); + + GuiTreeViewCtrl::Item* item = object->getItem( id ); + if( !item ) + { + Con::errorf( "GuiTreeViewCtrl::setItemImages() - invalid item id '%i'", id ); + return; + } + + item->setNormalImage((S8)dAtoi(argv[3])); + item->setExpandedImage((S8)dAtoi(argv[4])); +} + +ConsoleMethod( GuiTreeViewCtrl, isParentItem, bool, 3, 3, "( int id ) - Returns true if the given item contains child items." ) +{ + int id = dAtoi( argv[ 2 ] ); + if( !id && object->mItemCount ) + return true; + + GuiTreeViewCtrl::Item* item = object->getItem( id ); + if( !item ) + { + Con::errorf( "GuiTreeViewCtrl::isParentItem - invalid item id '%i'", id ); + return false; + } + + return item->isParent(); +} + +ConsoleMethod(GuiTreeViewCtrl, getItemText, const char *, 3, 3, "(TreeItemId item)") +{ + return(object->getItemText(dAtoi(argv[2]))); +} + +ConsoleMethod(GuiTreeViewCtrl, getItemValue, const char *, 3, 3, "(TreeItemId item)") +{ + return(object->getItemValue(dAtoi(argv[2]))); +} + +ConsoleMethod(GuiTreeViewCtrl, editItem, bool, 5, 5, "(TreeItemId item, string newText, string newValue)") +{ + return(object->editItem(dAtoi(argv[2]), argv[3], argv[4])); +} + +ConsoleMethod(GuiTreeViewCtrl, removeItem, bool, 3, 3, "(TreeItemId item)") +{ + return(object->removeItem(dAtoi(argv[2]))); +} + +ConsoleMethod(GuiTreeViewCtrl, removeAllChildren, void, 3, 3, "removeAllChildren(TreeItemId parent)") +{ + object->removeAllChildren(dAtoi(argv[2])); +} +ConsoleMethod(GuiTreeViewCtrl, clear, void, 2, 2, "() - empty tree") +{ + object->removeItem(0); +} + +ConsoleMethod(GuiTreeViewCtrl, getFirstRootItem, S32, 2, 2, "Get id for root item.") +{ + return(object->getFirstRootItem()); +} + +ConsoleMethod(GuiTreeViewCtrl, getChild, S32, 3, 3, "(TreeItemId item)") +{ + return(object->getChildItem(dAtoi(argv[2]))); +} + +ConsoleMethod(GuiTreeViewCtrl, buildVisibleTree, void, 3, 3, "Build the visible tree") +{ + object->buildVisibleTree(dAtob(argv[2])); +} +ConsoleMethod(GuiTreeViewCtrl, getParent, S32, 3, 3, "(TreeItemId item)") +{ + return(object->getParentItem(dAtoi(argv[2]))); +} + +ConsoleMethod(GuiTreeViewCtrl, getNextSibling, S32, 3, 3, "(TreeItemId item)") +{ + return(object->getNextSiblingItem(dAtoi(argv[2]))); +} + +ConsoleMethod(GuiTreeViewCtrl, getPrevSibling, S32, 3, 3, "(TreeItemId item)") +{ + return(object->getPrevSiblingItem(dAtoi(argv[2]))); +} + +ConsoleMethod(GuiTreeViewCtrl, getItemCount, S32, 2, 2, "") +{ + return(object->getItemCount()); +} + +ConsoleMethod(GuiTreeViewCtrl, getSelectedItem, S32, 2, 2, "") +{ + return ( object->getSelectedItem() ); +} + +ConsoleMethod(GuiTreeViewCtrl, getSelectedObject, S32, 2, 2, "returns the currently selected simObject in inspector mode or -1") +{ + GuiTreeViewCtrl::Item *item = object->getItem( object->getSelectedItem() ); + if( item != NULL && item->isInspectorData() ) + { + SimObject *obj = item->getObject(); + if( obj != NULL ) + return obj->getId(); + } + + return -1; +} + +ConsoleMethod(GuiTreeViewCtrl, moveItemUp, void, 3, 3, "(TreeItemId item)") +{ + object->moveItemUp( dAtoi( argv[2] ) ); +} + +ConsoleMethod(GuiTreeViewCtrl, getSelectedItemsCount, S32, 2, 2, "") +{ + return ( object->getSelectedItemsCount() ); +} + + + +ConsoleMethod(GuiTreeViewCtrl, moveItemDown, void, 3, 3, "(TreeItemId item)") +{ + object->moveItemDown( dAtoi( argv[2] ) ); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(GuiTreeViewCtrl, getTextToRoot, const char*,4,4,"(TreeItemId item,Delimiter=none) gets the text from the current node to the root, concatenating at each branch upward, with a specified delimiter optionally") +{ + if ( argc < 4 ) + { + Con::warnf("GuiTreeViewCtrl::getTextToRoot - Invalid number of arguments!"); + return (""); + } + S32 itemId = dAtoi( argv[2] ); + StringTableEntry delimiter = argv[3]; + + return object->getTextToRoot( itemId, delimiter ); +} + +ConsoleMethod(GuiTreeViewCtrl, getSelectedItemList,const char*, 2,2,"returns a space seperated list of mulitple item ids") +{ + char* buff = Con::getReturnBuffer(1024); + dSprintf(buff,1024,""); + + for(int i = 0; i < object->mSelected.size(); i++) + { + S32 id = object->mSelected[i]; + //get the current length of the buffer + U32 len = dStrlen(buff); + //the start of the buffer where we want to write + char* buffPart = buff+len; + //the size of the remaining buffer (-1 cause dStrlen doesn't count the \0) + S32 size = 1024-len-1; + //write it: + if(size < 1) + { + Con::errorf("GuiTreeViewCtrl::getSelectedItemList - Not enough room to return our object list"); + return buff; + } + + dSprintf(buffPart,size,"%d ", id); + } +//mSelected + + return buff; +} + +S32 GuiTreeViewCtrl::findItemByObjectId(S32 iObjId) +{ + for (S32 i = 0; i < mItems.size(); i++) + { + if ( !mItems[i] ) + continue; + + SimObject* pObj = mItems[i]->getObject(); + if( pObj && pObj->getId() == iObjId ) + return mItems[i]->mId; + } + + return -1; +} + +//------------------------------------------------------------------------------ +ConsoleMethod(GuiTreeViewCtrl, findItemByObjectId, S32, 3, 3, "(find item by object id and returns the mId)") +{ + return(object->findItemByObjectId(dAtoi(argv[2]))); +} + +//------------------------------------------------------------------------------ +bool GuiTreeViewCtrl::scrollVisibleByObjectId(S32 objID) +{ + S32 itemID = findItemByObjectId(objID); + + if(itemID == -1) + { + // we did not find the item in our current items + // we should try to find and show the parent of the item. + SimObject *obj = Sim::findObject(objID); + if(!obj || !obj->getGroup()) + return false; + + // if we can't show the parent, we fail. + if(! scrollVisibleByObjectId(obj->getGroup()->getId()) ) + return false; + + // get the parent. expand the parent. rebuild the tree. this ensures that + // we'll be able to find the child item we're targeting. + S32 parentID = findItemByObjectId(obj->getGroup()->getId()); + AssertFatal(parentID != -1, "We were able to show the parent, but could not then find the parent. This should not happen."); + Item *parentItem = getItem(parentID); + parentItem->setExpanded(true); + buildVisibleTree(); + + // NOW we should be able to find the object. if not... something's wrong. + itemID = findItemByObjectId(objID); + AssertWarn(itemID != -1,"GuiTreeViewCtrl::scrollVisibleByObjectId() found the parent, but can't find it's immediate child. This should not happen."); + if(itemID == -1) + return false; + } + + // ok, item found. scroll to it. + scrollVisible(itemID); + + return true; +} + +//------------------------------------------------------------------------------ +ConsoleMethod(GuiTreeViewCtrl, scrollVisibleByObjectId, S32, 3, 3, "(show item by object id. returns true if sucessful.)") +{ + return(object->scrollVisibleByObjectId(dAtoi(argv[2]))); +} + +//------------------------------------------------------------------------------ + +ConsoleMethod( GuiTreeViewCtrl, sort, void, 2, 6, "( [int parent, bool traverseHierarchy=false, bool parentsFirst=false, bool caseSensitive=true ) - Sorts all items of the given parent (or root). With 'hierarchy', traverses hierarchy." ) +{ + S32 parent = 0; + if( argc >= 3 ) + parent = dAtoi( argv[ 2 ] ); + + bool traverseHierarchy = false; + bool parentsFirst = false; + bool caseSensitive = true; + + if( argc >= 4 ) + traverseHierarchy = dAtob( argv[ 3 ] ); + if( argc >= 5 ) + parentsFirst = dAtob( argv[ 4 ] ); + if( argc >= 6 ) + caseSensitive = dAtob( argv[ 5 ] ); + + if( !parent ) + itemSortList( object->mRoot, caseSensitive, traverseHierarchy, parentsFirst ); + else + { + GuiTreeViewCtrl::Item* item = object->getItem( parent ); + if( !item ) + { + Con::errorf( "GuiTreeViewCtrl::sort - no item '%i' in tree", parent ); + return; + } + + item->sort( caseSensitive, traverseHierarchy, parentsFirst ); + } +} diff --git a/gui/controls/guiTreeViewCtrl.h b/gui/controls/guiTreeViewCtrl.h new file mode 100644 index 0000000..1f231f5 --- /dev/null +++ b/gui/controls/guiTreeViewCtrl.h @@ -0,0 +1,443 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_TREEVIEWCTRL_H +#define _GUI_TREEVIEWCTRL_H + +#include "core/bitSet.h" +#include "math/mRect.h" +#include "gfx/gFont.h" +#include "gui/core/guiControl.h" +#include "gui/core/guiArrayCtrl.h" + +//------------------------------------------------------------------------------ + +class GuiTreeViewCtrl : public GuiArrayCtrl +{ + private: + typedef GuiArrayCtrl Parent; + + public: + /// @section GuiControl_Intro Introduction + /// @nosubgrouping + + /// + struct Item + { + + enum ItemState + { + Selected = BIT(0), + Expanded = BIT(1), + Marked = BIT(2), ///< Marked items are drawn with a border around them. This is + /// different than "Selected" because it can only be set by script. + MouseOverBmp = BIT(3), + MouseOverText = BIT(4), + InspectorData = BIT(5), ///< Set if we're representing some inspector + /// info (ie, use mInspectorInfo, not mScriptInfo) + + VirtualParent = BIT(6), ///< This indicates that we should be rendered as + /// a parent even though we don't have any children. + /// This is useful for preventing scenarios where + /// we might want to create thousands of + /// Items that might never be shown (for instance + /// if we're browsing the object hierarchy in + /// Torque, which might have thousands of objects). + + InternalNameOnly = BIT(7), ///< Only use the SimObject internal names for the item text. + ObjectNameOnly = BIT(8), ///< Only use the SimObject name for the item text + }; + + BitSet32 mState; + SimObjectPtr mProfile; + S16 mId; + U16 mTabLevel; + Item * mParent; + Item * mChild; + Item * mNext; + Item * mPrevious; + String mTooltip; + S32 mIcon; //stores the icon that will represent the item in the tree + S32 mDataRenderWidth; /// this stores the pixel width needed + /// to render the item's data in the + /// onRenderCell function to optimize + /// for speed. + + Item( GuiControlProfile *pProfile ); + ~Item(); + + struct ScriptTag + { + S8 mNormalImage; + S8 mExpandedImage; + StringTableEntry mText; + StringTableEntry mValue; + } mScriptInfo; + struct InspectorTag + { + SimObjectPtr mObject; + } mInspectorInfo; + + /// @name Get Methods + /// @{ + + /// + const S8 getNormalImage() const; + const S8 getExpandedImage() const; + StringTableEntry getText(); + StringTableEntry getValue(); + inline const S16 getID() const { return mId; }; + SimObject *getObject(); + const U32 getDisplayTextLength(); + const S32 getDisplayTextWidth(GFont *font); + void getDisplayText(U32 bufLen, char *buf); + /// @} + + + /// @name Set Methods + /// @{ + + /// Set whether an item is expanded or not (showing children or having them hidden) + void setExpanded(const bool f); + /// Set the image to display when an item IS expanded + void setExpandedImage(const S8 id); + /// Set the image to display when an item is NOT expanded + void setNormalImage(const S8 id); + /// Assign a SimObject pointer to an inspector data item + void setObject(SimObject *obj); + /// Set the items displayable text (caption) + void setText(StringTableEntry txt); + /// Set the items script value (data) + void setValue(StringTableEntry val); + /// Set the items virtual parent flag + void setVirtualParent( bool value ); + /// @} + + + /// @name State Retrieval + /// @{ + + /// Returns true if this item is expanded. For + /// inspector objects, the expansion is stored + /// on the SimObject, for other things we use our + /// bit vector. + const bool isExpanded() const; + + /// Returns true if an item is inspector data + /// or false if it's just an item. + inline const bool isInspectorData() const { return mState.test(InspectorData); }; + + /// Returns true if we should show the expand art + /// and make the item interact with the mouse as if + /// it were a parent. + const bool isParent() const; + /// @} + + /// @name Searching Methods + /// @{ + + /// Find a regular data item by it's script name. + Item* findChildByName( const char* name ); + + /// Find an inspector data item by it's SimObject pointer + Item *findChildByValue(const SimObject *obj); + + /// Find a regular data item by it's script value + Item *findChildByValue(StringTableEntry Value); + /// @} + + /// Sort the childs of the item by their text. + /// + /// @param caseSensitive If true, sorting is case-sensitive. + /// @param traverseHierarchy If true, also triggers a sort() on all child items. + /// @param parentsFirst If true, parents are grouped before children in the resulting sort. + void sort( bool caseSensitive = true, bool traverseHierarchy = false, bool parentsFirst = false ); + + }; + + /// @name Enums + /// @{ + + /// + enum TreeState + { + RebuildVisible = BIT(0), ///< Temporary flag, we have to rebuild the tree. + IsInspector = BIT(1), ///< We are mapping a SimObject hierarchy. + IsEditable = BIT(2), ///< We allow items to be moved around. + ShowTreeLines = BIT(3), ///< Should we render tree lines or just icons? + BuildingVisTree = BIT(4), ///< We are currently building the visible tree (prevent recursion) + }; + +protected: + enum + { + MaxIcons = 32, + }; + + enum Icons + { + Default1 = 0, + SimGroup1, + SimGroup2, + SimGroup3, + SimGroup4, + Hidden, + Lock1, + Lock2, + Default, + Icon31, + Icon32 + }; + + enum mDragMidPointFlags + { + NomDragMidPoint, + AbovemDragMidPoint, + BelowmDragMidPoint + }; + + /// + enum HitFlags + { + OnIndent = BIT(0), + OnImage = BIT(1), + OnText = BIT(2), + OnRow = BIT(3), + }; + + /// + enum BmpIndices + { + BmpDunno, + BmpLastChild, + BmpChild, + BmpExp, + BmpExpN, + BmpExpP, + BmpExpPN, + BmpCon, + BmpConN, + BmpConP, + BmpConPN, + BmpLine, + BmpGlow, + }; + + + /// @} +public: + + /// + Vector mItems; + Vector mVisibleItems; + Vector mSelectedItems; + Vector mSelected; ///< Used for tracking stuff that was + /// selected, but may not have been + /// created at time of selection + S32 mItemCount; + Item * mItemFreeList; ///< We do our own free list, as we + /// we want to be able to recycle + /// item ids and do some other clever + /// things. + Item * mRoot; + S32 mMaxWidth; + S32 mSelectedItem; + S32 mDraggedToItem; + S32 mTempItem; + S32 mStart; + BitSet32 mFlags; + +protected: + GFXTexHandle mIconTable[MaxIcons]; + + // for debugging + bool mDebug; + + S32 mTabSize; + S32 mTextOffset; + bool mFullRowSelect; + S32 mItemHeight; + bool mDestroyOnSleep; + bool mSupportMouseDragging; + bool mMultipleSelections; + bool mDeleteObjectAllowed; + bool mDragToItemAllowed; + bool mClearAllOnSingleSelection; ///< When clicking on an already selected item, clear all other selections + bool mCompareToObjectID; + + /// Used to hide the root tree + /// element, defaults to true. + bool mShowRoot; + + /// If true InspectorData items only show + /// the SimObject internal names. + bool mInternalNamesOnly; + + /// If true InspectorData items only show + /// the SimObject names. + bool mObjectNamesOnly; + + /// If true then tooltips will be automatically + /// generated for all Inspector items + bool mUseInspectorTooltips; + + /// If true then only render item tooltips if the item + /// extends past the displayable width + bool mTooltipOnWidthOnly; + + S32 mOldDragY; + S32 mCurrentDragCell; + S32 mPreviousDragCell; + S32 mDragMidPoint; + bool mMouseDragged; + bool mDragStartInSelection; + + StringTableEntry mBitmapBase; + GFXTexHandle mTexRollover; + GFXTexHandle mTexSelected; + + ColorI mAltFontColor; + ColorI mAltFontColorHL; + ColorI mAltFontColorSE; + + SimObjectPtr mRootObject; + + void destroyChildren(Item * item, Item * parent); + void destroyItem(Item * item); + void destroyTree(); + + void deleteItem(Item *item); + + void buildItem(Item * item, U32 tabLevel, bool bForceFullUpdate = false); + + bool hitTest(const Point2I & pnt, Item* & item, BitSet32 & flags); + + S32 getInspectorItemIconsWidth(Item* & item); + + virtual bool onVirtualParentBuild(Item *item, bool bForceFullUpdate = false); + virtual bool onVirtualParentExpand(Item *item); + virtual bool onVirtualParentCollapse(Item *item); + virtual void onItemSelected( Item *item ); + virtual void onAddSelection( Item *item ); + virtual void onRemoveSelection( Item *item ); + virtual void onClearSelection() {}; + + void addInspectorDataItem(Item *parent, SimObject *obj); + + public: + GuiTreeViewCtrl(); + virtual ~GuiTreeViewCtrl(); + + /// Used for syncing the mSelected and mSelectedItems lists. + void syncSelection(); + + void lockSelection(bool lock); + void hideSelection(bool hide); + virtual bool canAddSelection( Item *item ) { return true; }; + void addSelection(S32 itemId, bool update = true ); + + bool isSelected(S32 itemId); + bool isSelected(Item *item); + + /// Should use addSelection and removeSelection when calling from script + /// instead of setItemSelected. Use setItemSelected when you want to select + /// something in the treeview as it has script call backs. + void removeSelection(S32 itemId); + + /// Sets the flag of the item with the matching itemId. + bool setItemSelected(S32 itemId, bool select); + bool setItemExpanded(S32 itemId, bool expand); + bool setItemValue(S32 itemId, StringTableEntry Value); + + const char * getItemText(S32 itemId); + const char * getItemValue(S32 itemId); + StringTableEntry getTextToRoot(S32 itemId, const char *delimiter = ""); + + Item * getItem(S32 itemId); + Item * createItem(S32 icon); + bool editItem( S32 itemId, const char* newText, const char* newValue ); + + bool markItem( S32 itemId, bool mark ); + + bool isItemSelected( S32 itemId ); + + // insertion/removal + void unlinkItem(Item * item); + S32 insertItem(S32 parentId, const char * text, const char * value = "", const char * iconString = "", S16 normalImage = 0, S16 expandedImage = 1); + bool removeItem(S32 itemId); + void removeAllChildren(S32 itemId); // Remove all children of the given item + + bool buildIconTable(const char * icons); + + bool setAddGroup(SimObject * obj); + + S32 getIcon(const char * iconString); + + // tree items + const S32 getFirstRootItem() const; + S32 getChildItem(S32 itemId); + S32 getParentItem(S32 itemId); + S32 getNextSiblingItem(S32 itemId); + S32 getPrevSiblingItem(S32 itemId); + S32 getItemCount(); + S32 getSelectedItem(); + S32 getSelectedItem(S32 index); // Given an item's index in the selection list, return its itemId + S32 getSelectedItemsCount() {return mSelectedItems.size();} // Returns the number of selected items + void moveItemUp( S32 itemId ); + void moveItemDown( S32 itemId ); + + // misc. + bool scrollVisible( Item *item ); + bool scrollVisible( S32 itemId ); + bool scrollVisibleByObjectId( S32 objID ); + + void deleteSelection(); + void clearSelection(); + + S32 findItemByName(const char *name); + S32 findItemByObjectId(S32 iObjId); + + // GuiControl + bool onWake(); + void onSleep(); + void onPreRender(); + bool onKeyDown( const GuiEvent &event ); + void onMouseDown(const GuiEvent &event); + void onMiddleMouseDown(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseEnter(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void onRightMouseDown(const GuiEvent &event); + void onRightMouseUp(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + virtual void onMouseUp(const GuiEvent &event); + + /// Returns false if the object is a child of one of the inner items. + bool childSearch(Item * item, SimObject *obj, bool yourBaby); + + /// Find immediately available inspector items (eg ones that aren't children of other inspector items) + /// and then update their sets + void inspectorSearch(Item * item, Item * parent, SimSet * parentSet, SimSet * newParentSet); + + /// Find the Item associated with a sceneObject, returns true if it found one + bool objectSearch( const SimObject *object, Item **item ); + + // GuiArrayCtrl + void onRenderCell(Point2I offset, Point2I cell, bool, bool); + void onRender(Point2I offset, const RectI &updateRect); + + bool renderTooltip( const Point2I &hoverPos, const Point2I& cursorPos, const char* tipText ); + + static void initPersistFields(); + + void inspectObject(SimObject * obj, bool okToEdit); + void buildVisibleTree(bool bForceFullUpdate = false); + + DECLARE_CONOBJECT(GuiTreeViewCtrl); + DECLARE_CATEGORY( "Gui Lists" ); + DECLARE_DESCRIPTION( "Hierarchical list of text items with optional icons.\nCan also be used to inspect SimObject hierarchies." ); +}; + +#endif diff --git a/gui/core/guiArrayCtrl.cpp b/gui/core/guiArrayCtrl.cpp new file mode 100644 index 0000000..9f6f5f0 --- /dev/null +++ b/gui/core/guiArrayCtrl.cpp @@ -0,0 +1,435 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/core/guiArrayCtrl.h" + +#include "console/console.h" +#include "platform/event.h" +#include "gui/containers/guiScrollCtrl.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiArrayCtrl); + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +GuiArrayCtrl::GuiArrayCtrl() +{ + mActive = true; + + mCellSize.set(80, 30); + mSize = Point2I(5, 30); + mSelectedCell.set(-1, -1); + mMouseOverCell.set(-1, -1); + mHeaderDim.set(0, 0); + mIsContainer = true; +} + +bool GuiArrayCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + //get the font + mFont = mProfile->mFont; + + return true; +} + +void GuiArrayCtrl::onSleep() +{ + Parent::onSleep(); + mFont = NULL; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiArrayCtrl::setSize(Point2I newSize) +{ + mSize = newSize; + Point2I newExtent(newSize.x * mCellSize.x + mHeaderDim.x, newSize.y * mCellSize.y + mHeaderDim.y); + + setExtent(newExtent); +} + +void GuiArrayCtrl::getScrollDimensions(S32 &cell_size, S32 &num_cells) +{ + cell_size = mCellSize.y; + num_cells = mSize.y; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +bool GuiArrayCtrl::cellSelected(Point2I cell) +{ + if (cell.x < 0 || cell.x >= mSize.x || cell.y < 0 || cell.y >= mSize.y) + { + mSelectedCell = Point2I(-1,-1); + return false; + } + + mSelectedCell = cell; + scrollSelectionVisible(); + onCellSelected(cell); + setUpdate(); + return true; +} + +void GuiArrayCtrl::onCellSelected(Point2I cell) +{ + Con::executef(this, "onSelect", Con::getFloatArg(cell.x), Con::getFloatArg(cell.y)); + + //call the console function + execConsoleCallback(); +} + +// Called when a cell is highlighted +void GuiArrayCtrl::onCellHighlighted(Point2I cell) +{ + // Do nothing +} + +void GuiArrayCtrl::setSelectedCell(Point2I cell) +{ + cellSelected(cell); +} + +Point2I GuiArrayCtrl::getSelectedCell() +{ + return mSelectedCell; +} + +void GuiArrayCtrl::scrollSelectionVisible() +{ + scrollCellVisible(mSelectedCell); +} + +void GuiArrayCtrl::scrollCellVisible(Point2I cell) +{ + //make sure we have a parent + //make sure we have a valid cell selected + GuiScrollCtrl *parent = dynamic_cast(getParent()); + if(!parent || cell.x < 0 || cell.y < 0) + return; + + RectI cellBounds(cell.x * mCellSize.x, cell.y * mCellSize.y, mCellSize.x, mCellSize.y); + parent->scrollRectVisible(cellBounds); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiArrayCtrl::onRenderColumnHeaders(Point2I offset, Point2I parentOffset, Point2I headerDim) +{ + if (mProfile->mBorder) + { + RectI cellR(offset.x + headerDim.x, parentOffset.y, getWidth() - headerDim.x, headerDim.y); + GFX->getDrawUtil()->drawRectFill(cellR, mProfile->mBorderColor); + } +} + +void GuiArrayCtrl::onRenderRowHeader(Point2I offset, Point2I parentOffset, Point2I headerDim, Point2I cell) +{ + ColorI color; + RectI cellR; + if (cell.x % 2) + color.set(255, 0, 0, 255); + else + color.set(0, 255, 0, 255); + + cellR.point.set(parentOffset.x, offset.y); + cellR.extent.set(headerDim.x, mCellSize.y); + GFX->getDrawUtil()->drawRectFill(cellR, color); +} + +void GuiArrayCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + ColorI color(255 * (cell.x % 2), 255 * (cell.y % 2), 255 * ((cell.x + cell.y) % 2), 255); + if (selected) + { + color.set(255, 0, 0, 255); + } + else if (mouseOver) + { + color.set(0, 0, 255, 255); + } + + //draw the cell + RectI cellR(offset.x, offset.y, mCellSize.x, mCellSize.y); + GFX->getDrawUtil()->drawRectFill(cellR, color); +} + +void GuiArrayCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + + Parent::onRender( offset, updateRect ); + + //make sure we have a parent + GuiControl *parent = getParent(); + if (! parent) + return; + + S32 i, j; + RectI headerClip; + RectI clipRect(updateRect.point, updateRect.extent); + + Point2I parentOffset = parent->localToGlobalCoord(Point2I(0, 0)); + + //if we have column headings + if (mHeaderDim.y > 0) + { + headerClip.point.x = parentOffset.x + mHeaderDim.x; + headerClip.point.y = parentOffset.y; + headerClip.extent.x = clipRect.extent.x;// - headerClip.point.x; // This seems to fix some strange problems with some Gui's, bug? -pw + headerClip.extent.y = mHeaderDim.y; + + if (headerClip.intersect(clipRect)) + { + GFX->setClipRect(headerClip); + + //now render the header + onRenderColumnHeaders(offset, parentOffset, mHeaderDim); + + clipRect.point.y = headerClip.point.y + headerClip.extent.y - 1; + } + offset.y += mHeaderDim.y; + } + + //if we have row headings + if (mHeaderDim.x > 0) + { + clipRect.point.x = getMax(clipRect.point.x, parentOffset.x + mHeaderDim.x); + offset.x += mHeaderDim.x; + } + + //save the original for clipping the row headers + RectI origClipRect = clipRect; + + for (j = 0; j < mSize.y; j++) + { + //skip until we get to a visible row + if ((j + 1) * mCellSize.y + offset.y < updateRect.point.y) + continue; + + //break once we've reached the last visible row + if(j * mCellSize.y + offset.y >= updateRect.point.y + updateRect.extent.y) + break; + + //render the header + if (mHeaderDim.x > 0) + { + headerClip.point.x = parentOffset.x; + headerClip.extent.x = mHeaderDim.x; + headerClip.point.y = offset.y + j * mCellSize.y; + headerClip.extent.y = mCellSize.y; + if (headerClip.intersect(origClipRect)) + { + GFX->setClipRect(headerClip); + + //render the row header + onRenderRowHeader(Point2I(0, offset.y + j * mCellSize.y), + Point2I(parentOffset.x, offset.y + j * mCellSize.y), + mHeaderDim, Point2I(0, j)); + } + } + + //render the cells for the row + for (i = 0; i < mSize.x; i++) + { + //skip past columns off the left edge + if ((i + 1) * mCellSize.x + offset.x < updateRect.point.x) + continue; + + //break once past the last visible column + if (i * mCellSize.x + offset.x >= updateRect.point.x + updateRect.extent.x) + break; + + S32 cellx = offset.x + i * mCellSize.x; + S32 celly = offset.y + j * mCellSize.y; + + RectI cellClip(cellx, celly, mCellSize.x, mCellSize.y); + + //make sure the cell is within the update region + if (cellClip.intersect(clipRect)) + { + //set the clip rect + GFX->setClipRect(cellClip); + + //render the cell + onRenderCell(Point2I(cellx, celly), Point2I(i, j), + i == mSelectedCell.x && j == mSelectedCell.y, + i == mMouseOverCell.x && j == mMouseOverCell.y); + } + } + } +} + +void GuiArrayCtrl::onMouseDown(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + //let the guiControl method take care of the rest + Parent::onMouseDown(event); + + Point2I pt = globalToLocalCoord(event.mousePoint); + pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y; + Point2I cell( + (pt.x < 0 ? -1 : pt.x / mCellSize.x), + (pt.y < 0 ? -1 : pt.y / mCellSize.y) + ); + + if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + + //store the previously selected cell + Point2I prevSelected = mSelectedCell; + + //select the new cell + cellSelected(Point2I(cell.x, cell.y)); + + //if we double clicked on the *same* cell, evaluate the altConsole Command + if ( ( event.mouseClickCount > 1 ) && ( prevSelected == mSelectedCell ) && mAltConsoleCommand[0] ) + Con::evaluate( mAltConsoleCommand, false ); + } +} + +void GuiArrayCtrl::onMouseEnter(const GuiEvent &event) +{ + Point2I pt = globalToLocalCoord(event.mousePoint); + pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y; + + //get the cell + Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y)); + if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + mMouseOverCell = cell; + setUpdateRegion(Point2I(cell.x * mCellSize.x + mHeaderDim.x, + cell.y * mCellSize.y + mHeaderDim.y), mCellSize ); + onCellHighlighted(mMouseOverCell); + } +} + +void GuiArrayCtrl::onMouseLeave(const GuiEvent & /*event*/) +{ + setUpdateRegion(Point2I(mMouseOverCell.x * mCellSize.x + mHeaderDim.x, + mMouseOverCell.y * mCellSize.y + mHeaderDim.y), mCellSize); + mMouseOverCell.set(-1,-1); + onCellHighlighted(mMouseOverCell); +} + +void GuiArrayCtrl::onMouseDragged(const GuiEvent &event) +{ + // for the array control, the behavior of onMouseDragged is the same + // as on mouse moved - basically just recalc the current mouse over cell + // and set the update regions if necessary + GuiArrayCtrl::onMouseMove(event); +} + +void GuiArrayCtrl::onMouseMove(const GuiEvent &event) +{ + Point2I pt = globalToLocalCoord(event.mousePoint); + pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y; + Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y)); + if (cell.x != mMouseOverCell.x || cell.y != mMouseOverCell.y) + { + if (mMouseOverCell.x != -1) + { + setUpdateRegion(Point2I(mMouseOverCell.x * mCellSize.x + mHeaderDim.x, + mMouseOverCell.y * mCellSize.y + mHeaderDim.y), mCellSize); + } + + if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + setUpdateRegion(Point2I(cell.x * mCellSize.x + mHeaderDim.x, + cell.y * mCellSize.y + mHeaderDim.y), mCellSize); + mMouseOverCell = cell; + } + else + mMouseOverCell.set(-1,-1); + } + onCellHighlighted(mMouseOverCell); +} + +bool GuiArrayCtrl::onKeyDown(const GuiEvent &event) +{ + //if this control is a dead end, kill the event + if ((! mVisible) || (! mActive) || (! mAwake)) return true; + + //get the parent + S32 pageSize = 1; + GuiControl *parent = getParent(); + if (parent && mCellSize.y > 0) + { + pageSize = getMax(1, (parent->getHeight() / mCellSize.y) - 1); + } + + Point2I delta(0,0); + switch (event.keyCode) + { + case KEY_LEFT: + delta.set(-1, 0); + break; + case KEY_RIGHT: + delta.set(1, 0); + break; + case KEY_UP: + delta.set(0, -1); + break; + case KEY_DOWN: + delta.set(0, 1); + break; + case KEY_PAGE_UP: + delta.set(0, -pageSize); + break; + case KEY_PAGE_DOWN: + delta.set(0, pageSize); + break; + case KEY_HOME: + cellSelected( Point2I( 0, 0 ) ); + return( true ); + case KEY_END: + cellSelected( Point2I( 0, mSize.y - 1 ) ); + return( true ); + default: + return Parent::onKeyDown(event); + } + if (mSize.x < 1 || mSize.y < 1) + return true; + + //select the first cell if no previous cell was selected + if (mSelectedCell.x == -1 || mSelectedCell.y == -1) + { + cellSelected(Point2I(0,0)); + return true; + } + + //select the cell + Point2I cell = mSelectedCell; + cell.x = getMax(0, getMin(mSize.x - 1, cell.x + delta.x)); + cell.y = getMax(0, getMin(mSize.y - 1, cell.y + delta.y)); + cellSelected(cell); + + return true; +} + +void GuiArrayCtrl::onRightMouseDown(const GuiEvent &event) +{ + if ( !mActive || !mAwake || !mVisible ) + return; + + Parent::onRightMouseDown( event ); + + Point2I pt = globalToLocalCoord( event.mousePoint ); + pt.x -= mHeaderDim.x; pt.y -= mHeaderDim.y; + Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y)); + if (cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + char buf[32]; + dSprintf( buf, sizeof( buf ), "%d %d", event.mousePoint.x, event.mousePoint.y ); + // Pass it to the console: + Con::executef(this, "onRightMouseDown", Con::getIntArg(cell.x), Con::getIntArg(cell.y), buf); + } +} diff --git a/gui/core/guiArrayCtrl.h b/gui/core/guiArrayCtrl.h new file mode 100644 index 0000000..31d9f50 --- /dev/null +++ b/gui/core/guiArrayCtrl.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIARRAYCTRL_H_ +#define _GUIARRAYCTRL_H_ + +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif + +/// Renders a grid of cells. +class GuiArrayCtrl : public GuiControl +{ + typedef GuiControl Parent; + +protected: + + Point2I mHeaderDim; + Point2I mSize; + Point2I mCellSize; + Point2I mSelectedCell; + Point2I mMouseOverCell; + + Resource mFont; + + virtual bool cellSelected(Point2I cell); + virtual void onCellSelected(Point2I cell); + virtual void onCellHighlighted(Point2I cell); +public: + + GuiArrayCtrl(); + DECLARE_CONOBJECT(GuiArrayCtrl); + + bool onWake(); + void onSleep(); + + /// @name Array attribute methods + /// @{ + Point2I getSize() { return mSize; } + virtual void setSize(Point2I size); + void setHeaderDim(const Point2I &dim) { mHeaderDim = dim; } + void getScrollDimensions(S32 &cell_size, S32 &num_cells); + /// @} + + /// @name Selected cell methods + /// @{ + void setSelectedCell(Point2I cell); + void deselectCells() { mSelectedCell.set(-1,-1); } + Point2I getSelectedCell(); + void scrollSelectionVisible(); + void scrollCellVisible(Point2I cell); + /// @} + + /// @name Rendering methods + /// @{ + virtual void onRenderColumnHeaders(Point2I offset, Point2I parentOffset, Point2I headerDim); + virtual void onRenderRowHeader(Point2I offset, Point2I parentOffset, Point2I headerDim, Point2I cell); + virtual void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); + void onRender(Point2I offset, const RectI &updateRect); + /// @} + + /// @name Mouse input methods + /// @{ + void onMouseDown(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseEnter(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + bool onKeyDown(const GuiEvent &event); + void onRightMouseDown(const GuiEvent &event); + /// @} +}; + +#endif //_GUI_ARRAY_CTRL_H diff --git a/gui/core/guiCanvas.cpp b/gui/core/guiCanvas.cpp new file mode 100644 index 0000000..037e7d7 --- /dev/null +++ b/gui/core/guiCanvas.cpp @@ -0,0 +1,2210 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/core/guiCanvas.h" + +#include "console/console.h" +#include "platform/profiler.h" +#include "platform/event.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiTypes.h" +#include "gui/core/guiControl.h" +#include "console/consoleTypes.h" +#include "gfx/screenshot.h" + +#ifndef TORQUE_TGB_ONLY +# include "sceneGraph/sceneObject.h" +#endif + +#include "gfx/gfxInit.h" +#include "core/util/journal/process.h" + +#ifdef TORQUE_GFX_STATE_DEBUG +# include "gfx/gfxDebugStateTracker.h" +#endif + +IMPLEMENT_CONOBJECT(GuiCanvas); + +ColorI gCanvasClearColor( 255, 0, 255 ); ///< For GFX->clear + +/// This is triggered by the canvas before it starts the +/// rendering process which renders *everything*. +Signal gCanvasRenderSignal; + +extern InputModifiers convertModifierBits(const U32 in); + +//----------------------------------------------------------------------------- + +GuiCanvas::GuiCanvas(): GuiControl(), + mCursorEnabled(true), + mForceMouseToGUI(false), + mClampTorqueCursor(true), + mShowCursor(true), + mLastCursorEnabled(false), + mMouseControl(NULL), + mMouseCapturedControl(NULL), + mMouseControlClicked(false), + mMouseButtonDown(false), + mMouseRightButtonDown(false), + mMouseMiddleButtonDown(false), + mDefaultCursor(NULL), + mLastCursor(NULL), + mLastCursorPt(0,0), + mCursorPt(0,0), + mLastMouseClickCount(0), + mLastMouseDownTime(0), + mPrevMouseTime(0), + mRenderFront(false), + mHoverControl(NULL), + mHoverPositionSet(false), + mHoverLeftControlTime(0), + mLeftMouseLast(false), + mMiddleMouseLast(false), + mRightMouseLast(false), + mPlatformWindow(NULL) +{ + setBounds(0, 0, 640, 480); + mAwake = true; + + mHoverControlStart = Platform::getRealMilliseconds(); + mHoverPosition = getCursorPos(); + + mFences = NULL; + mNextFenceIdx = -1; + +#ifndef _XBOX + mNumFences = Con::getIntVariable( "$pref::Video::defaultFenceCount", 0 ); +#else + mNumFences = 0; +#endif + +#ifdef TORQUE_DEMO_PURCHASE + mPurchaseScreen = NULL; +#endif +} + +GuiCanvas::~GuiCanvas() +{ + SAFE_DELETE(mPlatformWindow); + SAFE_DELETE_ARRAY( mFences ); + +#ifdef TORQUE_DEMO_PURCHASE + if (mPurchaseScreen) + SAFE_DELETE(mPurchaseScreen); +#endif +} + +//------------------------------------------------------------------------------ + +bool GuiCanvas::setProtectedNumFences( void *obj, const char *data ) +{ + GuiCanvas *canvas = reinterpret_cast( obj ); + canvas->mNumFences = dAtoi( data ); + canvas->setupFences(); + + return false; +} + +void GuiCanvas::initPersistFields() +{ + //add the canSaveDynamicFields property: + addGroup("Canvas Rendering"); + addProtectedField( "numFences", TypeS32, Offset( mNumFences, GuiCanvas ), &setProtectedNumFences, &defaultProtectedGetFn, "The number of GFX fences to use." ); + endGroup("Canvas Rendering"); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ + +bool GuiCanvas::onAdd() +{ + // ensure that we have a cursor + setCursor(dynamic_cast(Sim::findObject("DefaultCursor"))); + + // Enumerate things for GFX before we have an active device. + GFXInit::enumerateAdapters(); + + // Create a device. + GFXAdapter *a = GFXInit::getBestAdapterChoice(); + + // Do we have a global device already? (This is the site if you want + // to start rendering to multiple devices simultaneously) + GFXDevice *newDevice = GFX; + if(newDevice == NULL) + newDevice = GFXInit::createDevice(a); + + newDevice->setAllowRender( false ); + + // Initialize the window... + GFXVideoMode vm = GFXInit::getInitialVideoMode(); + + if (a && a->mType != NullDevice) + { + mPlatformWindow = WindowManager->createWindow(newDevice, vm); + + // Set a minimum on the window size so people can't break us by resizing tiny. + mPlatformWindow->setMinimumWindowSize(Point2I(640,480)); + + // Now, we have to hook in our event callbacks so we'll get + // appropriate events from the window. + mPlatformWindow->resizeEvent .notify(this, &GuiCanvas::handleResize); + mPlatformWindow->appEvent .notify(this, &GuiCanvas::handleAppEvent); + mPlatformWindow->displayEvent.notify(this, &GuiCanvas::handlePaintEvent); + mPlatformWindow->setInputController( dynamic_cast(this) ); + } + + // Need to get painted, too! :) + Process::notify(this, &GuiCanvas::paint, PROCESS_RENDER_ORDER); + + // Set up the fences + setupFences(); + + // Make sure we're able to render. + newDevice->setAllowRender( true ); + + // Propagate add to parents. + // CodeReview - if GuiCanvas fails to add for whatever reason, what happens to + // all the event registration above? + bool parentRet = Parent::onAdd(); + + // Define the menu bar for this canvas (if any) + Con::executef(this, "onCreateMenu"); + +#ifdef TORQUE_DEMO_PURCHASE + mPurchaseScreen = new PurchaseScreen; + mPurchaseScreen->init(); + + mLastPurchaseHideTime = 0; +#endif + + return parentRet; +} + +void GuiCanvas::onRemove() +{ +#ifdef TORQUE_DEMO_PURCHASE + if (mPurchaseScreen && mPurchaseScreen->isAwake()) + removeObject(mPurchaseScreen); +#endif + + // And the process list + Process::remove(this, &GuiCanvas::paint); + + // Destroy the menu bar for this canvas (if any) + Con::executef(this, "onDestroyMenu"); + + Parent::onRemove(); +} + +void GuiCanvas::setWindowTitle(const char *newTitle) +{ + if (mPlatformWindow) + mPlatformWindow->setCaption(newTitle); +} + +void GuiCanvas::handleResize( WindowId did, S32 width, S32 height ) +{ + // Notify the scripts + if ( isMethod( "onResize" ) ) + Con::executef( this, "onResize", Con::getIntArg( width ), Con::getIntArg( height ) ); +} + +void GuiCanvas::handlePaintEvent(WindowId did) +{ + renderFrame(false); +} + +void GuiCanvas::handleAppEvent( WindowId did, S32 event ) +{ + // Notify script if we gain or lose focus. + if(event == LoseFocus) + { + if(isMethod("onLoseFocus")) + Con::executef(this, "onLoseFocus"); + } + + if(event == GainFocus) + { + if(isMethod("onGainFocus")) + Con::executef(this, "onGainFocus"); + } + + if(event == WindowClose || event == WindowDestroy) + { +#ifdef TORQUE_DEMO_PURCHASE + showPurchaseScreen(true, "exit", true); + return; +#endif + + if(isMethod("onWindowClose")) + { + // First see if there is a method on this window to handle + // it's closure + Con::executef(this,"onWindowClose"); + } + else if(Con::isFunction("onWindowClose")) + { + // otherwise check to see if there is a global function handling it + Con::executef("onWindowClose", getIdString()); + } + else + { + // Else just shutdown + Process::requestShutdown(); + } + } +} + +Point2I GuiCanvas::getWindowSize() +{ + // CodeReview Asserting on this breaks previous logic + // and code assumptions. It seems logical that we would + // handle this and return an error value rather than implementing + // if(!mPlatformWindow) whenever we need to call getWindowSize. + // This should help keep our API error free and easy to use, while + // cutting down on code duplication for sanity checking. [5/5/2007 justind] + if( !mPlatformWindow ) + return Point2I(-1,-1); + + return mPlatformWindow->getClientExtent(); +} + +void GuiCanvas::enableKeyboardTranslation() +{ + AssertISV(mPlatformWindow, "GuiCanvas::enableKeyboardTranslation - no window present!"); + mPlatformWindow->setKeyboardTranslation(true); +} + +void GuiCanvas::disableKeyboardTranslation() +{ + AssertISV(mPlatformWindow, "GuiCanvas::disableKeyboardTranslation - no window present!"); + mPlatformWindow->setKeyboardTranslation(false); +} + +void GuiCanvas::setNativeAcceleratorsEnabled( bool enabled ) +{ + AssertISV(mPlatformWindow, "GuiCanvas::setNativeAcceleratorsEnabled - no window present!"); + mPlatformWindow->setAcceleratorsEnabled( enabled ); +} + +void GuiCanvas::setForceMouseToGUI(bool onOff) +{ + mForceMouseToGUI = onOff; +} + +void GuiCanvas::setClampTorqueCursor(bool onOff) +{ + mClampTorqueCursor = onOff; +} + +void GuiCanvas::setCursor(GuiCursor *curs) +{ + mDefaultCursor = curs; +} + +void GuiCanvas::setCursorON(bool onOff) +{ + mCursorEnabled = onOff; + if(!mCursorEnabled) + mMouseControl = NULL; +} + +Point2I GuiCanvas::getCursorPos() +{ + Point2I p( 0, 0 ); + + if( mPlatformWindow ) + mPlatformWindow->getCursorPosition( p ); + + return p; +} + +void GuiCanvas::setCursorPos(const Point2I &pt) +{ + AssertISV(mPlatformWindow, "GuiCanvas::setCursorPos - no window present!"); + mPlatformWindow->setCursorPosition( pt.x, pt.y ); +} + +void GuiCanvas::showCursor(bool state) +{ + mShowCursor = state; + mPlatformWindow->setCursorVisible( state ); +} + +bool GuiCanvas::isCursorShown() +{ + if ( !mPlatformWindow->getCursorController() ) + { + return mShowCursor; + } + + return mPlatformWindow->isCursorVisible(); +} + +void GuiCanvas::addAcceleratorKey(GuiControl *ctrl, U32 index, U32 keyCode, U32 modifier) +{ + if (keyCode > 0 && ctrl) + { + AccKeyMap newMap; + newMap.ctrl = ctrl; + newMap.index = index; + newMap.keyCode = keyCode; + newMap.modifier = modifier; + mAcceleratorMap.push_back(newMap); + } +} + +bool GuiCanvas::tabNext(void) +{ + GuiControl *ctrl = static_cast(last()); + if (ctrl) + { + //save the old + GuiControl *oldResponder = mFirstResponder; + + GuiControl* newResponder = ctrl->findNextTabable(mFirstResponder); + if ( !newResponder ) + newResponder = ctrl->findFirstTabable(); + + if ( newResponder && newResponder != oldResponder ) + { + newResponder->setFirstResponder(); + + // CodeReview Can this get killed? Note tabPrev code. BJG - 3/25/07 +// if ( oldResponder ) +// oldResponder->onLoseFirstResponder(); + return true; + } + } + return false; +} + +bool GuiCanvas::tabPrev(void) +{ + GuiControl *ctrl = static_cast(last()); + if (ctrl) + { + //save the old + GuiControl *oldResponder = mFirstResponder; + + GuiControl* newResponder = ctrl->findPrevTabable(mFirstResponder); + if ( !newResponder ) + newResponder = ctrl->findLastTabable(); + + if ( newResponder && newResponder != oldResponder ) + { + newResponder->setFirstResponder(); + + // CodeReview As with tabNext() above, looks like this can now go. DAW - 7/05/09 + //if ( oldResponder ) + // oldResponder->onLoseFirstResponder(); + + return true; + } + } + return false; +} + +bool GuiCanvas::processInputEvent(InputEventInfo &inputEvent) +{ + mConsumeLastInputEvent = true; + // First call the general input handler (on the extremely off-chance that it will be handled): + if (mFirstResponder && mFirstResponder->onInputEvent(inputEvent)) + { + return mConsumeLastInputEvent; + //return(true); + } + + switch (inputEvent.deviceType) + { + case KeyboardDeviceType: + return processKeyboardEvent(inputEvent); + break; + + case GamepadDeviceType: + return processGamepadEvent(inputEvent); + break; + + case MouseDeviceType: + if (mCursorEnabled || mForceMouseToGUI) + { + return processMouseEvent(inputEvent); + } + break; + default: + break; + } + + return false; +} + +bool GuiCanvas::processKeyboardEvent(InputEventInfo &inputEvent) +{ + mLastEvent.ascii = inputEvent.ascii; + mLastEvent.modifier = inputEvent.modifier; + mLastEvent.keyCode = inputEvent.objInst; + + // Combine left/right shift bits - if one shift modifier key + // bit is set, then set the other one. This way we can simplify + // our processing logic by treating the keys identically. + U32 eventModifier = inputEvent.modifier; + if(eventModifier & SI_SHIFT) + { + eventModifier |= SI_SHIFT; + } + if(eventModifier & SI_CTRL) + { + eventModifier |= SI_CTRL; + } + if(eventModifier & SI_ALT) + { + eventModifier |= SI_ALT; + } + + if (inputEvent.action == SI_MAKE) + { + //see if we should now pass the event to the first responder + if (mFirstResponder) + { + if(mFirstResponder->onKeyDown(mLastEvent)) + return mConsumeLastInputEvent; + } + + //see if we should tab next/prev + if ( isCursorON() && ( inputEvent.objInst == KEY_TAB ) ) + { + if (size() > 0) + { + if (inputEvent.modifier & SI_SHIFT) + { + if(tabPrev()) + return mConsumeLastInputEvent; + } + else if (inputEvent.modifier == 0) + { + if(tabNext()) + return mConsumeLastInputEvent; + } + } + } + + //if not handled, search for an accelerator + for (U32 i = 0; i < mAcceleratorMap.size(); i++) + { + if ((U32)mAcceleratorMap[i].keyCode == (U32)inputEvent.objInst && (U32)mAcceleratorMap[i].modifier == eventModifier) + { + mAcceleratorMap[i].ctrl->acceleratorKeyPress(mAcceleratorMap[i].index); + return mConsumeLastInputEvent; + } + } + } + else if(inputEvent.action == SI_BREAK) + { + if(mFirstResponder && mFirstResponder->onKeyUp(mLastEvent)) + return mConsumeLastInputEvent; + + //see if there's an accelerator + for (U32 i = 0; i < mAcceleratorMap.size(); i++) + { + if ((U32)mAcceleratorMap[i].keyCode == (U32)inputEvent.objInst && (U32)mAcceleratorMap[i].modifier == eventModifier) + { + mAcceleratorMap[i].ctrl->acceleratorKeyRelease(mAcceleratorMap[i].index); + return mConsumeLastInputEvent; + } + } + } + else if(inputEvent.action == SI_REPEAT) + { + //if not handled, search for an accelerator + for (U32 i = 0; i < mAcceleratorMap.size(); i++) + { + if ((U32)mAcceleratorMap[i].keyCode == (U32)inputEvent.objInst && (U32)mAcceleratorMap[i].modifier == eventModifier) + { + mAcceleratorMap[i].ctrl->acceleratorKeyPress(mAcceleratorMap[i].index); + return mConsumeLastInputEvent; + } + } + + if(mFirstResponder) + { + bool ret = mFirstResponder->onKeyRepeat(mLastEvent); + return ret && mConsumeLastInputEvent; + } + } + return false; +} + +bool GuiCanvas::processMouseEvent(InputEventInfo &inputEvent) +{ + // [rene 09/09/10] This custom mouse cursor tracking that is happening here is bad. It will frequently + // get ouf of step with where the cursor actually is. We really should *not* track the cursor; it's + // just another thing that can/will go wrong. Let the input system pass us absolute screen coordinates + // for every mouse event instead and work off that. + // + // 'mCursorPt' basically is an accumulation of errors and the number of bugs that have cropped up with + // the GUI clicking stuff where it is not supposed to are probably all to blame on this. + + // Need to query platform for specific things + AssertISV(mPlatformWindow, "GuiCanvas::processMouseEvent - no window present!"); + PlatformCursorController *pController = mPlatformWindow->getCursorController(); + AssertFatal(pController != NULL, "GuiCanvas::processInputEvent - No Platform Controller Found") + + //copy the modifier into the new event + mLastEvent.modifier = inputEvent.modifier; + + if(inputEvent.objType == SI_AXIS && + (inputEvent.objInst == SI_XAXIS || inputEvent.objInst == SI_YAXIS)) + { + + // Set the absolute position if we get an SI_MAKE on an axis + if( inputEvent.objInst == SI_XAXIS ) + { + if( inputEvent.action == SI_MAKE ) + mCursorPt.x = (S32)inputEvent.fValue; + else if( inputEvent.action == SI_MOVE ) + mCursorPt.x += (S32)inputEvent.fValue; + mCursorPt.x = getMax(0, getMin((S32)mCursorPt.x, getBounds().extent.x - 1)); + } + else if( inputEvent.objInst == SI_YAXIS ) + { + if( inputEvent.action == SI_MAKE ) + mCursorPt.y = (S32)inputEvent.fValue; + else if( inputEvent.action == SI_MOVE ) + mCursorPt.y += (S32)inputEvent.fValue; + mCursorPt.y = getMax(0, getMin((S32)mCursorPt.y, getBounds().extent.y - 1)); + } + + // Store new cursor position. + mLastEvent.mousePoint.x = S32(mCursorPt.x); + mLastEvent.mousePoint.y = S32(mCursorPt.y); + + // See if we need to invalidate a possible dbl click due to the cursor + // moving too much. + Point2F movement = mMouseDownPoint - mCursorPt; + + if ((mAbs((S32)movement.x) > pController->getDoubleClickWidth()) || (mAbs((S32)movement.y) > pController->getDoubleClickHeight() ) ) + { + mLeftMouseLast = false; + mMiddleMouseLast = false; + mRightMouseLast = false; + } + + if (mMouseButtonDown) + rootMouseDragged(mLastEvent); + else if (mMouseRightButtonDown) + rootRightMouseDragged(mLastEvent); + else if(mMouseMiddleButtonDown) + rootMiddleMouseDragged(mLastEvent); + else + rootMouseMove(mLastEvent); + return mConsumeLastInputEvent; + } + else if ( inputEvent.objInst == SI_ZAXIS + || inputEvent.objInst == SI_RZAXIS ) + { + mLastEvent.mousePoint.x = S32( mCursorPt.x ); + mLastEvent.mousePoint.y = S32( mCursorPt.y ); + mLastEvent.fval = inputEvent.fValue; + + if( inputEvent.objInst == SI_ZAXIS ) + mLastEvent.mouseAxis = 1; + else + mLastEvent.mouseAxis = 0; + + if ( inputEvent.fValue < 0.0f ) + return rootMouseWheelDown( mLastEvent ); + else + return rootMouseWheelUp( mLastEvent ); + } + else if(inputEvent.objType == SI_BUTTON) + { + //copy the cursor point into the event + mLastEvent.mousePoint.x = S32(mCursorPt.x); + mLastEvent.mousePoint.y = S32(mCursorPt.y); + mMouseDownPoint = mCursorPt; + + if(inputEvent.objInst == KEY_BUTTON0) // left button + { + //see if button was pressed + if (inputEvent.action == SI_MAKE) + { + U32 curTime = Platform::getVirtualMilliseconds(); + mNextMouseTime = curTime + mInitialMouseDelay; + + //if the last button pressed was the left... + if (mLeftMouseLast) + { + //if it was within the double click time count the clicks + if (curTime - mLastMouseDownTime <= pController->getDoubleClickTime()) + mLastMouseClickCount++; + else + mLastMouseClickCount = 1; + } + else + { + mLeftMouseLast = true; + mLastMouseClickCount = 1; + } + + mLastMouseDownTime = curTime; + mLastEvent.mouseClickCount = mLastMouseClickCount; + + rootMouseDown(mLastEvent); + } + //else button was released + else + { + mNextMouseTime = 0xFFFFFFFF; + rootMouseUp(mLastEvent); + } + + return mConsumeLastInputEvent; + } + else if(inputEvent.objInst == KEY_BUTTON1) // right button + { + if(inputEvent.action == SI_MAKE) + { + U32 curTime = Platform::getVirtualMilliseconds(); + + //if the last button pressed was the right... + if (mRightMouseLast) + { + //if it was within the double click time count the clicks + if (curTime - mLastMouseDownTime <= pController->getDoubleClickTime()) + mLastMouseClickCount++; + else + mLastMouseClickCount = 1; + } + else + { + mRightMouseLast = true; + mLastMouseClickCount = 1; + } + + mLastMouseDownTime = curTime; + mLastEvent.mouseClickCount = mLastMouseClickCount; + + rootRightMouseDown(mLastEvent); + } + else // it was a mouse up + rootRightMouseUp(mLastEvent); + + return mConsumeLastInputEvent; + } + else if(inputEvent.objInst == KEY_BUTTON2) // middle button + { + if(inputEvent.action == SI_MAKE) + { + U32 curTime = Platform::getVirtualMilliseconds(); + + //if the last button pressed was the right... + if (mMiddleMouseLast) + { + //if it was within the double click time count the clicks + if (curTime - mLastMouseDownTime <= pController->getDoubleClickTime()) + mLastMouseClickCount++; + else + mLastMouseClickCount = 1; + } + else + { + mMiddleMouseLast = true; + mLastMouseClickCount = 1; + } + + mLastMouseDownTime = curTime; + mLastEvent.mouseClickCount = mLastMouseClickCount; + + rootMiddleMouseDown(mLastEvent); + } + else // it was a mouse up + rootMiddleMouseUp(mLastEvent); + + return mConsumeLastInputEvent; + } + } + return false; +} + +bool GuiCanvas::processGamepadEvent(InputEventInfo &inputEvent) +{ + if (! mFirstResponder) + { + // early out, no first responder to receive gamepad input + return false; + } + + if (inputEvent.deviceInst >= MAX_GAMEPADS) + { + // early out, we only support the first MAX_GAMEPADS gamepads + return false; + } + + mLastEvent.keyCode = inputEvent.objInst; + + if (inputEvent.objType == SI_BUTTON) + { + switch (inputEvent.action) + { + case SI_MAKE: + switch (inputEvent.objInst) + { + case SI_UPOV: + return mFirstResponder->onGamepadAxisUp(mLastEvent); + + case SI_DPOV: + return mFirstResponder->onGamepadAxisDown(mLastEvent); + + case SI_LPOV: + return mFirstResponder->onGamepadAxisLeft(mLastEvent); + + case SI_RPOV: + return mFirstResponder->onGamepadAxisRight(mLastEvent); + + default: + return mFirstResponder->onGamepadButtonDown(mLastEvent); + } + break; + + case SI_BREAK: + return mFirstResponder->onGamepadButtonUp(mLastEvent); + + default: + return false; + } + } + else if (inputEvent.objType == SI_AXIS) + { + F32 incomingValue = mFabs(inputEvent.fValue); + static const F32 DEAD_ZONE = 0.5f; + static const F32 MIN_CLICK_TIME = 500.0f; + static const F32 MAX_CLICK_TIME = 1000.0f; + static F32 xDecay [] = {1.0f, 1.0f, 1.0f, 1.0f}; + static F32 yDecay [] = {1.0f, 1.0f, 1.0f, 1.0f}; + static F32 zDecay [] = {1.0f, 1.0f, 1.0f, 1.0f}; + static U32 xLastClickTime [] = {0, 0, 0, 0}; + static U32 yLastClickTime [] = {0, 0, 0, 0}; + static U32 zLastClickTime [] = {0, 0, 0, 0}; + U32 curTime = Platform::getRealMilliseconds(); + F32 *decay; + U32 *lastClickTime; + + switch (inputEvent.objInst) + { + case SI_ZAXIS: + case XI_LEFT_TRIGGER: + case XI_RIGHT_TRIGGER: + decay = &zDecay[inputEvent.deviceInst]; + lastClickTime = &zLastClickTime[inputEvent.deviceInst]; + break; + + case SI_YAXIS: + case XI_THUMBLY: + case XI_THUMBRY: + decay = &yDecay[inputEvent.deviceInst]; + lastClickTime = &yLastClickTime[inputEvent.deviceInst]; + break; + + case SI_XAXIS: + case XI_THUMBLX: + case XI_THUMBRX: + default: + decay = &xDecay[inputEvent.deviceInst]; + lastClickTime = &xLastClickTime[inputEvent.deviceInst]; + break; + } + + if (incomingValue < DEAD_ZONE) + { + // early out, control movement is within the deadzone + *decay = 1.0f; + *lastClickTime = 0; + return false; + } + + // Rescales the input between 0.0 and 1.0 + incomingValue = (incomingValue - DEAD_ZONE) * (1.0f / (1.0f - DEAD_ZONE)); + + F32 clickTime = MIN_CLICK_TIME + (MAX_CLICK_TIME - MIN_CLICK_TIME) * (1.0f - incomingValue); + clickTime *= *decay; + + if (clickTime < (curTime - *lastClickTime)) + { + *decay *= 0.9f; + if (*decay < 0.2f) + { + *decay = 0.2f; + } + + *lastClickTime = curTime; + + bool negative = (inputEvent.fValue < 0.0f); + + switch (inputEvent.objInst) + { + case XI_LEFT_TRIGGER: + case XI_RIGHT_TRIGGER: + return mFirstResponder->onGamepadTrigger(mLastEvent); + + case SI_ZAXIS: + case SI_YAXIS: + case XI_THUMBLY: + case XI_THUMBRY: + if (negative) + { + return mFirstResponder->onGamepadAxisDown(mLastEvent); + } + else + { + return mFirstResponder->onGamepadAxisUp(mLastEvent); + } + + case SI_XAXIS: + case XI_THUMBLX: + case XI_THUMBRX: + default: + if (negative) + { + return mFirstResponder->onGamepadAxisLeft(mLastEvent); + } + else + { + return mFirstResponder->onGamepadAxisRight(mLastEvent); + } + } + } + } + return false; +} + +void GuiCanvas::rootMouseDown(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + mMouseButtonDown = true; + + //pass the event to the mouse locked control + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMouseDown(event); + else + { + //else pass it to whoever is underneath the cursor + iterator i; + i = end(); + while (i != begin()) + { + i--; + GuiControl *ctrl = static_cast(*i); + GuiControl *controlHit = ctrl->findHitControl(event.mousePoint); + + //see if the controlHit is a modeless dialog... + if ((! controlHit->mActive) && (! controlHit->getControlProfile()->mModal)) + continue; + else + { + controlHit->onMouseDown(event); + break; + } + } + } + + if (bool(mMouseControl)) + mMouseControlClicked = true; +} + +void GuiCanvas::findMouseControl(const GuiEvent &event) +{ + // Any children at all? + if(size() == 0) + { + mMouseControl = NULL; + return; + } + + // Otherwise, check the point and find the overlapped control. + GuiControl *controlHit = findHitControl(event.mousePoint); + if(controlHit != static_cast(mMouseControl)) + { + if(bool(mMouseControl)) + mMouseControl->onMouseLeave(event); + mMouseControl = controlHit; + mMouseControl->onMouseEnter(event); + } +} + +void GuiCanvas::refreshMouseControl() +{ + GuiEvent evt; + evt.mousePoint.x = S32(mCursorPt.x); + evt.mousePoint.y = S32(mCursorPt.y); + findMouseControl(evt); +} + +void GuiCanvas::rootMouseUp(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + mMouseButtonDown = false; + + // pass the event to the mouse locked control + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMouseUp(event); + else + { + findMouseControl(event); + if(bool(mMouseControl)) + mMouseControl->onMouseUp(event); + } +} + +void GuiCanvas::checkLockMouseMove(const GuiEvent &event) +{ + GuiControl *controlHit = findHitControl(event.mousePoint); + if(controlHit != mMouseControl) + { + if(mMouseControl == mMouseCapturedControl) + mMouseCapturedControl->onMouseLeave(event); + else if(controlHit == mMouseCapturedControl) + mMouseCapturedControl->onMouseEnter(event); + mMouseControl = controlHit; + } +} + +void GuiCanvas::rootMouseDragged(const GuiEvent &event) +{ + //pass the event to the mouse locked control + if (bool(mMouseCapturedControl)) + { + checkLockMouseMove(event); + mMouseCapturedControl->onMouseDragged(event); + } + else + { + findMouseControl(event); + if(bool(mMouseControl)) + mMouseControl->onMouseDragged(event); + } +} + +void GuiCanvas::rootMouseMove(const GuiEvent &event) +{ + if (bool(mMouseCapturedControl)) + { + checkLockMouseMove(event); + mMouseCapturedControl->onMouseMove(event); + } + else + { + findMouseControl(event); + if(bool(mMouseControl)) + mMouseControl->onMouseMove(event); + } +} + +void GuiCanvas::rootRightMouseDown(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + mMouseRightButtonDown = true; + + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onRightMouseDown(event); + else + { + findMouseControl(event); + + if(bool(mMouseControl)) + { + mMouseControl->onRightMouseDown(event); + } + } +} + +void GuiCanvas::rootRightMouseUp(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + mMouseRightButtonDown = false; + + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onRightMouseUp(event); + else + { + findMouseControl(event); + + if(bool(mMouseControl)) + mMouseControl->onRightMouseUp(event); + } +} + +void GuiCanvas::rootRightMouseDragged(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + + if (bool(mMouseCapturedControl)) + { + checkLockMouseMove(event); + mMouseCapturedControl->onRightMouseDragged(event); + } + else + { + findMouseControl(event); + + if(bool(mMouseControl)) + mMouseControl->onRightMouseDragged(event); + } +} + +void GuiCanvas::rootMiddleMouseDown(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + mMouseMiddleButtonDown = true; + + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMiddleMouseDown(event); + else + { + findMouseControl(event); + + if(bool(mMouseControl)) + { + mMouseControl->onMiddleMouseDown(event); + } + } +} + +void GuiCanvas::rootMiddleMouseUp(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + mMouseMiddleButtonDown = false; + + if (bool(mMouseCapturedControl)) + mMouseCapturedControl->onMiddleMouseUp(event); + else + { + findMouseControl(event); + + if(bool(mMouseControl)) + mMouseControl->onMiddleMouseUp(event); + } +} + +void GuiCanvas::rootMiddleMouseDragged(const GuiEvent &event) +{ + mPrevMouseTime = Platform::getVirtualMilliseconds(); + + if (bool(mMouseCapturedControl)) + { + checkLockMouseMove(event); + mMouseCapturedControl->onMiddleMouseDragged(event); + } + else + { + findMouseControl(event); + + if(bool(mMouseControl)) + mMouseControl->onMiddleMouseDragged(event); + } +} + +bool GuiCanvas::rootMouseWheelUp(const GuiEvent &event) +{ + if (bool(mMouseCapturedControl)) + return mMouseCapturedControl->onMouseWheelUp(event); + else + { + findMouseControl(event); + + if (bool(mMouseControl)) + return mMouseControl->onMouseWheelUp(event); + } + + return false; +} + +bool GuiCanvas::rootMouseWheelDown(const GuiEvent &event) +{ + if (bool(mMouseCapturedControl)) + return mMouseCapturedControl->onMouseWheelDown(event); + else + { + findMouseControl(event); + + if (bool(mMouseControl)) + return mMouseControl->onMouseWheelDown(event); + } + + return false; +} + +void GuiCanvas::setContentControl(GuiControl *gui) +{ +#ifdef TORQUE_DEMO_PURCHASE + if (mPurchaseScreen->isForceExit()) + return; +#endif + + // Skip out if we got passed NULL (why would that happen?) + if(!gui) + return; + + GuiControl *oldContent = getContentControl(); + if(oldContent) + Con::executef(oldContent, "onUnsetContent", Con::getIntArg(gui->getId())); + + //remove all dialogs on layer 0 + U32 index = 0; + while (size() > index) + { + GuiControl *ctrl = static_cast((*this)[index]); + if (ctrl == gui || ctrl->mLayer != 0) + index++; + + removeObject(ctrl); + Sim::getGuiGroup()->addObject(ctrl); + } + + // lose the first responder from the old GUI + GuiControl* responder = gui->findFirstTabable(); + if(responder) + responder->setFirstResponder(); + + //add the gui to the front + if(!size() || gui != (*this)[0]) + { + // automatically wakes objects in GuiControl::onWake + addObject(gui); + if (size() >= 2) + reOrder(gui, *begin()); + } + //refresh the entire gui + resetUpdateRegions(); + + //rebuild the accelerator map + mAcceleratorMap.clear(); + + for(iterator i = end(); i != begin() ; ) + { + i--; + GuiControl *ctrl = static_cast(*i); + ctrl->buildAcceleratorMap(); + + if (ctrl->getControlProfile()->mModal) + break; + } + refreshMouseControl(); + + // Do this last so onWake gets called first + Con::executef(gui, "onSetContent", Con::getIntArg(oldContent ? oldContent->getId() : 0)); + + // Force the canvas to update the sizing of the new content control + maintainSizing(); +} + +GuiControl *GuiCanvas::getContentControl() +{ + if(size() > 0) + return (GuiControl *) first(); + return NULL; +} + +void GuiCanvas::pushDialogControl(GuiControl *gui, S32 layer) +{ +#ifdef TORQUE_DEMO_PURCHASE + if (mPurchaseScreen->isForceExit()) + return; +#endif + + //add the gui + gui->mLayer = layer; + + // GuiControl::addObject wakes the object + addObject(gui); + + //reorder it to the correct layer + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + if (ctrl->mLayer > gui->mLayer) + { + reOrder(gui, ctrl); + break; + } + } + + //call the dialog push method + gui->onDialogPush(); + + //find the first responder + GuiControl* responder = gui->findFirstTabable(); + if(responder) + responder->setFirstResponder(); + + // call the 'onWake' method? + //if(wakedGui) + // Con::executef(gui, 1, "onWake"); + + //refresh the entire gui + resetUpdateRegions(); + + //rebuild the accelerator map + mAcceleratorMap.clear(); + if (size() > 0) + { + GuiControl *ctrl = static_cast(last()); + ctrl->buildAcceleratorMap(); + } + + refreshMouseControl(); + + // I don't see the purpose of this, and it's causing issues when showing, for instance the + // metrics dialog while in a 3d scene, causing the cursor to be shown even when the mouse + // is locked [4/25/2007 justind] + //if(gui->mProfile && gui->mProfile->mModal) + // mPlatformWindow->getCursorController()->pushCursor(PlatformCursorController::curArrow); +} + +void GuiCanvas::popDialogControl(GuiControl *gui) +{ + if (size() < 1) + return; + + //first, find the dialog, and call the "onDialogPop()" method + GuiControl *ctrl = NULL; + if (gui) + { + //make sure the gui really exists on the stack + iterator i; + bool found = false; + for(i = begin(); i != end(); i++) + { + GuiControl *check = static_cast(*i); + if (check == gui) + { + ctrl = check; + found = true; + } + } + + if (!found) + return; + } + else + ctrl = static_cast(last()); + + //call the "on pop" function + ctrl->onDialogPop(); + + //now pop the last child (will sleep if awake) + removeObject(ctrl); + + Sim::getGuiGroup()->addObject(ctrl); + + if (size() > 0) + { + GuiControl *ctrl = static_cast(last()); + if(ctrl->mFirstResponder) + ctrl->mFirstResponder->setFirstResponder(); + } + else + { + setFirstResponder(NULL); + } + + //refresh the entire gui + resetUpdateRegions(); + + //rebuild the accelerator map + mAcceleratorMap.clear(); + + if (size() > 0) + { + GuiControl *ctrl = static_cast(last()); + ctrl->buildAcceleratorMap(); + } + refreshMouseControl(); +} + +void GuiCanvas::popDialogControl(S32 layer) +{ + if (size() < 1) + return; + + GuiControl *ctrl = NULL; + iterator i = end(); // find in z order (last to first) + while (i != begin()) + { + i--; + ctrl = static_cast(*i); + if (ctrl->mLayer == layer) + break; + } + if (ctrl) + popDialogControl(ctrl); +} + +void GuiCanvas::mouseLock(GuiControl *lockingControl) +{ + if (bool(mMouseCapturedControl)) + return; + + mMouseCapturedControl = lockingControl; + + if(mMouseControl && mMouseControl != mMouseCapturedControl) + { + GuiEvent evt; + evt.mousePoint.x = S32(mCursorPt.x); + evt.mousePoint.y = S32(mCursorPt.y); + + mMouseControl->onMouseLeave(evt); + } +} + +void GuiCanvas::mouseUnlock(GuiControl *lockingControl) +{ + if (static_cast(mMouseCapturedControl) != lockingControl) + return; + + GuiEvent evt; + evt.mousePoint.x = S32(mCursorPt.x); + evt.mousePoint.y = S32(mCursorPt.y); + + GuiControl * controlHit = findHitControl(evt.mousePoint); + if(controlHit != mMouseCapturedControl) + { + mMouseControl = controlHit; + mMouseControlClicked = false; + if(bool(mMouseControl)) + mMouseControl->onMouseEnter(evt); + } + mMouseCapturedControl = NULL; +} + +void GuiCanvas::paint() +{ + resetUpdateRegions(); + + // inhibit explicit refreshes in the case we're swapped out + if( mPlatformWindow && mPlatformWindow->isVisible() && GFX->allowRender()) + mPlatformWindow->displayEvent.trigger(mPlatformWindow->getWindowId()); +} + +void GuiCanvas::maintainSizing() +{ + Point2I size = getWindowSize(); + + if(size.x == -1 || size.y == -1) + return; + + RectI screenRect(0, 0, size.x, size.y); + setBounds(screenRect); + + // all bottom level controls should be the same dimensions as the canvas + // this is necessary for passing mouse events accurately + iterator i; + for (i = begin(); i != end(); i++) + { + AssertFatal(static_cast((*i))->isAwake(), "GuiCanvas::maintainSizing - ctrl is not awake"); + GuiControl *ctrl = static_cast(*i); + Point2I ext = ctrl->getExtent(); + Point2I pos = ctrl->getPosition(); + + if(pos != screenRect.point || ext != screenRect.extent) + { + ctrl->resize(screenRect.point, screenRect.extent); + resetUpdateRegions(); + } + } + +} + +void GuiCanvas::setupFences() +{ + // Destroy old fences + SAFE_DELETE_ARRAY( mFences ); + + // Now create the new ones + if( mNumFences > 0 ) + { + mFences = new GFXFence*[mNumFences]; + + // Allocate the new fences + for( int i = 0; i < mNumFences; i++ ) + mFences[i] = GFX->createFence(); + } + + // Reset state + mNextFenceIdx = 0; +} + +void GuiCanvas::renderFrame(bool preRenderOnly, bool bufferSwap /* = true */) +{ + AssertISV(mPlatformWindow, "GuiCanvas::renderFrame - no window present!"); + if(!mPlatformWindow->isVisible() || !GFX->allowRender() || GFX->canCurrentlyRender()) + return; + + PROFILE_START(CanvasPreRender); + + // Set our window as the current render target so we can see outputs. + GFX->setActiveRenderTarget(mPlatformWindow->getGFXTarget()); + + if (!GFX->getActiveRenderTarget()) + { + PROFILE_END(); + return; + } + +#ifdef TORQUE_GFX_STATE_DEBUG + GFX->getDebugStateManager()->startFrame(); +#endif + + GFXTarget* renderTarget = GFX->getActiveRenderTarget(); + if (renderTarget == NULL) + { + PROFILE_END(); + return; + } + + // Make sure the root control is the size of the canvas. + Point2I size = renderTarget->getSize(); + + if(size.x == 0 || size.y == 0) + { + PROFILE_END(); + return; + } + + RectI screenRect(0, 0, size.x, size.y); + + maintainSizing(); + + //preRender (recursive) all controls + preRender(); + + PROFILE_END(); + + // Are we just doing pre-render? + if(preRenderOnly) + return; + + // Signal the interested parties. + gCanvasRenderSignal.trigger(); + + // for now, just always reset the update regions - this is a + // fix for FSAA on ATI cards + resetUpdateRegions(); + + PROFILE_START(CanvasRenderControls); + + // Draw the mouse + GuiCursor *mouseCursor = NULL; + bool cursorVisible = true; + + if(bool(mMouseCapturedControl)) + mMouseCapturedControl->getCursor(mouseCursor, cursorVisible, mLastEvent); + else if(bool(mMouseControl)) + mMouseControl->getCursor(mouseCursor, cursorVisible, mLastEvent); + + Point2I cursorPos((S32)mCursorPt.x, (S32)mCursorPt.y); + if(!mouseCursor) + mouseCursor = mDefaultCursor; + + if(mLastCursorEnabled && mLastCursor) + { + Point2I spot = mLastCursor->getHotSpot(); + Point2I cext = mLastCursor->getExtent(); + Point2I pos = mLastCursorPt - spot; + addUpdateRegion(pos - Point2I(2, 2), Point2I(cext.x + 4, cext.y + 4)); + } + + if(cursorVisible && mouseCursor) + { + Point2I spot = mouseCursor->getHotSpot(); + Point2I cext = mouseCursor->getExtent(); + Point2I pos = cursorPos - spot; + + addUpdateRegion(pos - Point2I(2, 2), Point2I(cext.x + 4, cext.y + 4)); + } + + mLastCursorEnabled = cursorVisible; + mLastCursor = mouseCursor; + mLastCursorPt = cursorPos; + + // Begin GFX + PROFILE_START(GFXBeginScene); + + GFX->beginScene(); + + PROFILE_END(); + + // Clear the current viewport area + GFX->setViewport( screenRect ); + GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, gCanvasClearColor, 1.0f, 0 ); + + resetUpdateRegions(); + + RectI updateUnion; + buildUpdateUnion(&updateUnion); + if (updateUnion.intersect(screenRect)) + { + // Render active GUI Dialogs + for(iterator i = begin(); i != end(); i++) + { + // Get the control + GuiControl *contentCtrl = static_cast(*i); + + GFX->setClipRect( updateUnion ); + GFX->setStateBlock(mDefaultGuiSB); + + contentCtrl->onRender(contentCtrl->getPosition(), updateUnion); + } + + // Fill Black if no Dialogs + if(this->size() == 0) + GFX->clear( GFXClearTarget, ColorI(0,0,0,0), 1.0f, 0 ); + + // Tooltip resource + if(bool(mMouseControl)) + { + U32 curTime = Platform::getRealMilliseconds(); + if(mHoverControl == mMouseControl) + { + if(mHoverPositionSet || (curTime - mHoverControlStart) >= mHoverControl->mTipHoverTime || (curTime - mHoverLeftControlTime) <= mHoverControl->mTipHoverTime) + { + if(!mHoverPositionSet) + { + mHoverPosition = cursorPos; + } + mHoverPositionSet = mMouseControl->mRenderTooltipDelegate( mHoverPosition, cursorPos, NULL ); + } + + } + else + { + if(mHoverPositionSet) + { + mHoverLeftControlTime = curTime; + mHoverPositionSet = false; + } + mHoverControl = mMouseControl; + mHoverControlStart = curTime; + } + } + + GFX->setClipRect( updateUnion ); + + // Draw an ugly box if we don't have a cursor available... + //if (mCursorEnabled && mShowCursor && !mouseCursor) + //{ + // GFX->drawRectFill( RectI( mCursorPt.x, mCursorPt.y, mCursorPt.x + 2, mCursorPt.y + 2 ), ColorI( 255, 0, 0 ) ); + //} + + + // CodeReview - Make sure our bitmap modulation is clear or else there's a black modulation + // that ruins rendering of textures at startup.. This was done in mouseCursor + // onRender and so at startup when it wasn't called the modulation was black, ruining + // the loading screen display. This fixes the issue, but is it only masking a deeper issue + // in GFX with regard to gui rendering? [5/3/2007 justind] + GFX->getDrawUtil()->clearBitmapModulation(); + + // Really draw the cursor. :) + // Only if the platform cursor controller is missing or the platform cursor + // isn't visible. +// if (!mPlatformWindow->getCursorController() || (mCursorEnabled && mouseCursor && mShowCursor && +// !mPlatformWindow->getCursorController()->isCursorVisible())) + if (mCursorEnabled && mouseCursor && mShowCursor) + { + Point2I pos((S32)mCursorPt.x, (S32)mCursorPt.y); + if (mPlatformWindow->getCursorController()) + { + mPlatformWindow->getCursorController()->getCursorPosition(pos); + pos = mPlatformWindow->screenToClient(pos); + mCursorPt.x = pos.x; + mCursorPt.y = pos.y; + } + Point2I spot = mouseCursor->getHotSpot(); + + pos -= spot; + mouseCursor->render(pos); + } + } + + // Render all RTT end of frame updates HERE + //DynamicTexture::updateScreenTextures(); + //DynamicTexture::updateEndOfFrameTextures(); + // mPending is set when the console function "screenShot()" is called + // this situation is necessary because it needs to take the screenshot + // before the buffers swap + +#ifdef TORQUE_DEMO_TIMEOUT + checkTimeOut(); +#endif + + if( gScreenShot != NULL && gScreenShot->mPending ) + gScreenShot->captureStandard(); + + PROFILE_END(); + + // Fence logic here, because this is where endScene is called. + if( mNumFences > 0 ) + { + // Issue next fence + mFences[mNextFenceIdx]->issue(); + + mNextFenceIdx++; + + // Wrap the next fence around to first if we're maxxed + if( mNextFenceIdx >= mNumFences ) + mNextFenceIdx = 0; + + // Block on previous fence + mFences[mNextFenceIdx]->block(); + } + + PROFILE_START(GFXEndScene); + GFX->endScene(); + PROFILE_END(); + + swapBuffers(); + +#ifdef TORQUE_GFX_STATE_DEBUG + GFX->getDebugStateManager()->endFrame(); +#endif +} + +void GuiCanvas::swapBuffers() +{ + AssertISV(mPlatformWindow, "GuiCanvas::swapBuffers - no window present!"); + if(!mPlatformWindow->isVisible()) + return; + + PROFILE_START(SwapBuffers); + mPlatformWindow->getGFXTarget()->present(); + PROFILE_END(); +} + +void GuiCanvas::buildUpdateUnion(RectI *updateUnion) +{ + *updateUnion = mOldUpdateRects[0]; + + //the update region should encompass the oldUpdateRects, and the curUpdateRect + Point2I upperL; + Point2I lowerR; + + upperL.x = getMin(mOldUpdateRects[0].point.x, mOldUpdateRects[1].point.x); + upperL.x = getMin(upperL.x, mCurUpdateRect.point.x); + + upperL.y = getMin(mOldUpdateRects[0].point.y, mOldUpdateRects[1].point.y); + upperL.y = getMin(upperL.y, mCurUpdateRect.point.y); + + lowerR.x = getMax(mOldUpdateRects[0].point.x + mOldUpdateRects[0].extent.x, mOldUpdateRects[1].point.x + mOldUpdateRects[1].extent.x); + lowerR.x = getMax(lowerR.x, mCurUpdateRect.point.x + mCurUpdateRect.extent.x); + + lowerR.y = getMax(mOldUpdateRects[0].point.y + mOldUpdateRects[0].extent.y, mOldUpdateRects[1].point.y + mOldUpdateRects[1].extent.y); + lowerR.y = getMax(lowerR.y, mCurUpdateRect.point.y + mCurUpdateRect.extent.y); + + updateUnion->point = upperL; + updateUnion->extent = lowerR - upperL; + + //shift the oldUpdateRects + mOldUpdateRects[0] = mOldUpdateRects[1]; + mOldUpdateRects[1] = mCurUpdateRect; + + mCurUpdateRect.point.set(0,0); + mCurUpdateRect.extent.set(0,0); +} + +void GuiCanvas::addUpdateRegion(Point2I pos, Point2I ext) +{ + if(mCurUpdateRect.extent.x == 0) + { + mCurUpdateRect.point = pos; + mCurUpdateRect.extent = ext; + } + else + { + Point2I upperL; + upperL.x = getMin(mCurUpdateRect.point.x, pos.x); + upperL.y = getMin(mCurUpdateRect.point.y, pos.y); + Point2I lowerR; + lowerR.x = getMax(mCurUpdateRect.point.x + mCurUpdateRect.extent.x, pos.x + ext.x); + lowerR.y = getMax(mCurUpdateRect.point.y + mCurUpdateRect.extent.y, pos.y + ext.y); + mCurUpdateRect.point = upperL; + mCurUpdateRect.extent = lowerR - upperL; + } +} + +void GuiCanvas::resetUpdateRegions() +{ + //DEBUG - get surface width and height + mOldUpdateRects[0] = getBounds(); + mOldUpdateRects[1] = mOldUpdateRects[0]; + mCurUpdateRect = mOldUpdateRects[0]; +} + +void GuiCanvas::setFirstResponder( GuiControl* newResponder ) +{ + GuiControl* oldResponder = mFirstResponder; + Parent::setFirstResponder( newResponder ); + + if ( oldResponder && ( oldResponder != mFirstResponder ) ) + oldResponder->onLoseFirstResponder(); +} + +GuiCursor * GuiCanvas::getCurrentCursor() +{ + return mDefaultCursor; +} + +ConsoleMethod( GuiCanvas, getContent, S32, 2, 2, "Get the GuiControl which is being used as the content.") +{ + GuiControl *ctrl = object->getContentControl(); + if(ctrl) + return ctrl->getId(); + return -1; +} + +ConsoleMethod( GuiCanvas, setContent, void, 3, 3, "(GuiControl ctrl)" + "Set the content of the canvas.") +{ + TORQUE_UNUSED(argc); + + GuiControl *gui = NULL; + if(argv[2][0]) + { + if (!Sim::findObject(argv[2], gui)) + { + Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); + return; + } + } + + //set the new content control + object->setContentControl(gui); +} + +ConsoleMethod( GuiCanvas, pushDialog, void, 3, 4, "(GuiControl ctrl, int layer)") +{ + GuiControl *gui; + + if (! Sim::findObject(argv[2], gui)) + { + Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); + return; + } + + //find the layer + S32 layer = 0; + if (argc == 4) + layer = dAtoi(argv[3]); + + //set the new content control + object->pushDialogControl(gui, layer); +} + +ConsoleMethod( GuiCanvas, popDialog, void, 2, 3, "(GuiControl ctrl=NULL)" ) +{ + GuiControl *gui = NULL; + if (argc == 3) + { + if (!Sim::findObject(argv[2], gui)) + { + Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); + return; + } + } + + if (gui) + object->popDialogControl(gui); + else + object->popDialogControl(); +} + +ConsoleMethod( GuiCanvas, popLayer, void, 2, 3, "(int layer)" ) +{ + S32 layer = 0; + if (argc == 3) + layer = dAtoi(argv[2]); + + object->popDialogControl(layer); +} + +ConsoleMethod(GuiCanvas, cursorOn, void, 2, 2, "") +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + object->setCursorON(true); +} + +ConsoleMethod(GuiCanvas, cursorOff, void, 2, 2, "") +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + object->setCursorON(false); +} + +ConsoleMethod( GuiCanvas, setCursor, void, 3, 3, "(cursorName)") +{ + TORQUE_UNUSED(argc); + + GuiCursor *curs = NULL; + if(argv[2][0]) + { + if(!Sim::findObject(argv[2], curs)) + { + Con::printf("%s is not a valid cursor.", argv[2]); + return; + } + } + object->setCursor(curs); +} + +ConsoleMethod( GuiCanvas, renderFront, void, 3, 3, "(bool enable)") +{ + object->setRenderFront(dAtob(argv[2])); +} + +ConsoleMethod( GuiCanvas, showCursor, void, 2, 2, "" ) +{ + object->showCursor(true); +} + +ConsoleMethod( GuiCanvas, hideCursor, void, 2, 2, "" ) +{ + object->showCursor(false); +} + +ConsoleMethod( GuiCanvas, isCursorOn, bool, 2, 2, "" ) +{ + return object->isCursorON(); +} + +ConsoleMethod( GuiCanvas, isCursorShown, bool, 2, 2, "" ) +{ + return object->isCursorShown(); +} + +ConsoleMethod( GuiCanvas, repaint, void, 2, 2, "Force canvas to redraw." ) +{ + object->resetUpdateRegions(); + object->renderFrame(false); +} + +ConsoleMethod( GuiCanvas, reset, void, 2, 2, "Reset the update regions for the canvas." ) +{ + object->resetUpdateRegions(); +} + +ConsoleMethod( GuiCanvas, getCursorPos, const char*, 2, 2, "Get the current position of the cursor." ) +{ + Point2I pos = object->getCursorPos(); + char * ret = Con::getReturnBuffer(32); + dSprintf(ret, 32, "%d %d", pos.x, pos.y); + return(ret); +} + +ConsoleMethod( GuiCanvas, setCursorPos, void, 3, 4, "(Point2I pos)" ) +{ + Point2I pos(0,0); + + if(argc == 4) + pos.set(dAtoi(argv[2]), dAtoi(argv[3])); + else + dSscanf(argv[2], "%d %d", &pos.x, &pos.y); + + object->setCursorPos(pos); +} + +ConsoleMethod( GuiCanvas, getMouseControl, S32, 2, 2, "Gets the gui control under the mouse.") +{ + GuiControl* control = object->getMouseControl(); + if (control) + return control->getId(); + + return NULL; +} + +ConsoleFunction( excludeOtherInstance, bool, 1, 1, "(string appIdentifier) " + "@return false if another app is running that specified the " + "same string.") +{ + // mac/360 can only run one instance in general. +#if !defined(TORQUE_OS_MAC) && !defined(TORQUE_OS_XENON) && !defined(TORQUE_DEBUG) + return Platform::excludeOtherInstances(argv[1]); +#else + // We can just return true if we get here. + return true; +#endif +} + +ConsoleMethod( GuiCanvas, getExtent, const char*, 2, 2, "Returns the dimensions of the canvas.") +{ + char * ret = Con::getReturnBuffer(32); + dSprintf(ret, 32, "%d %d", object->getWidth(), object->getHeight()); + return(ret); +} + +ConsoleMethod( GuiCanvas, setWindowTitle, void, 3, 3, "(string newTitle)" ) +{ + object->setWindowTitle(argv[2]); +} + +ConsoleMethod( GuiCanvas, getVideoMode, const char*, 2, 2, + "()\n" + "Gets the current screen mode as a string.\n\n" + "\\return (string) The current screen mode as \"(int)width (int)height (bool)fullscreen (int)bitdepth (int)refreshRate\"" ) +{ + // Grab the video mode. + if (!object->getPlatformWindow()) + return ""; + + GFXVideoMode vm = object->getPlatformWindow()->getVideoMode(); + char* buf = Con::getReturnBuffer(vm.toString()); + return buf; +} + +ConsoleMethod( GuiCanvas, getModeCount, S32, 2, 2, + "()\n" + "Gets the number of modes available on this device.\n\n" + "\\return (int) The number of video modes supported by the device." ) +{ + if (!object->getPlatformWindow()) + return 0; + + // Grab the available mode list from the device. + const Vector* const modeList = + object->getPlatformWindow()->getGFXDevice()->getVideoModeList(); + + // Return the number of resolutions. + return modeList->size(); +} + +ConsoleMethod( GuiCanvas, getMode, const char*, 3, 3, + "(int modeId)\n" + "Gets information on the specified mode of this device.\n\n" + "\\param modeId Index of the mode to get data from.\n" + "\\return (string) A video mode string given an adapter and mode index. See GuiCanvas.getVideoMode()" ) +{ + if (!object->getPlatformWindow()) + return 0; + + // Grab the available mode list from the device. + const Vector* const modeList = + object->getPlatformWindow()->getGFXDevice()->getVideoModeList(); + + // Get the desired index and confirm it's valid. + S32 idx = dAtoi(argv[2]); + if((idx < 0) || (idx >= modeList->size())) + { + Con::errorf("GuiCanvas::getResolution - You requested an out of range index of %d. Please specify an index in the range [0, %d).", idx, modeList->size()); + return ""; + } + + // Great - we got something valid, so convert the videomode into a + // string and return to the user. + GFXVideoMode vm = (*modeList)[idx]; + + char *retString = Con::getReturnBuffer(vm.toString()); + return retString; +} + +ConsoleMethod( GuiCanvas, toggleFullscreen, void, 2, 2, "() - toggle canvas from fullscreen to windowed mode or back." ) +{ + + if (Platform::getWebDeployment()) + return; + + if (!object->getPlatformWindow()) + return; + + // Get the window's video mode. + GFXVideoMode origMode = object->getPlatformWindow()->getVideoMode(); + + // And grab the device its using. + GFXDevice *device = object->getPlatformWindow()->getGFXDevice(); + + // Toggle the fullscreen bit. + GFXVideoMode newMode = origMode; + newMode.fullScreen = !origMode.fullScreen; + + // CodeReview Toggling might be better served by reading the fullscreen + // or windowed video mode pref and setting that instead [bjg 5/2/07] + + if(newMode.fullScreen == true) + { + // Are we going to fullscreen? If so find the first matching resolution that + // is equal to or bigger in size, and has same BPP - windows + // are often strangely sized and will need to be adjusted to a viable + // fullscreen res. + + for(S32 i=0; igetVideoModeList()->size(); i++) + { + const GFXVideoMode &newVm = (*(device->getVideoModeList()))[i]; + + if(newMode.resolution.x > newVm.resolution.x) + continue; + + if(newMode.resolution.y > newVm.resolution.y) + continue; + + if(newMode.bitDepth != newVm.bitDepth) + continue; + + // Great - got a match. + newMode = newVm; + newMode.fullScreen = true; + break; + } + } + + // Ok, we have new video mode. Set it! + object->getPlatformWindow()->setVideoMode(newMode); +} + +ConsoleMethod( GuiCanvas, isFullscreen, bool, 2, 2, "() - Is this canvas currently fullscreen?" ) +{ + if (Platform::getWebDeployment()) + return false; + + if (!object->getPlatformWindow()) + return false; + + return object->getPlatformWindow()->getVideoMode().fullScreen; +} + +ConsoleMethod( GuiCanvas, minimizeWindow, void, 2, 2, "() - minimize this canvas' window." ) +{ + PlatformWindow* window = object->getPlatformWindow(); + if ( window ) + window->minimize(); +} + +ConsoleMethod( GuiCanvas, isMinimized, bool, 2, 2, "()" ) +{ + PlatformWindow* window = object->getPlatformWindow(); + if ( window ) + return window->isMinimized(); + + return false; +} + +ConsoleMethod( GuiCanvas, isMaximized, bool, 2, 2, "()" ) +{ + PlatformWindow* window = object->getPlatformWindow(); + if ( window ) + return window->isMaximized(); + + return false; +} + +ConsoleMethod( GuiCanvas, maximizeWindow, void, 2, 2, "() - maximize this canvas' window." ) +{ + PlatformWindow* window = object->getPlatformWindow(); + if ( window ) + window->maximize(); +} + +ConsoleMethod( GuiCanvas, restoreWindow, void, 2, 2, "() - restore this canvas' window." ) +{ + PlatformWindow* window = object->getPlatformWindow(); + if( window ) + window->restore(); +} + +ConsoleMethod( GuiCanvas, setVideoMode, void, 5, 8, + "(int width, int height, bool fullscreen, [int bitDepth], [int refreshRate])\n" + "Change the video mode of this canvas. This method has the side effect of setting the $pref::Video::mode to the new values.\n\n" + "\\param width The screen width to set.\n" + "\\param height The screen height to set.\n" + "\\param fullscreen Specify true to run fullscreen or false to run in a window\n" + "\\param bitDepth [optional] The desired bit-depth. Defaults to the current setting. This parameter is ignored if you are running in a window.\n" + "\\param refreshRate [optional] The desired refresh rate. Defaults to the current setting. This parameter is ignored if you are running in a window" + "\\param antialiasLevel [optional] The level of anti-aliasing to apply 0 = none" ) +{ + if (!object->getPlatformWindow()) + return; + + // Update the video mode and tell the window to reset. + GFXVideoMode vm = object->getPlatformWindow()->getVideoMode(); + + U32 width = dAtoi(argv[2]); + U32 height = dAtoi(argv[3]); + + bool changed = false; + if (width == 0 && height > 0) + { + // Our width is 0 but our height isn't... + // Try to find a matching width + for(S32 i=0; igetPlatformWindow()->getGFXDevice()->getVideoModeList()->size(); i++) + { + const GFXVideoMode &newVm = (*(object->getPlatformWindow()->getGFXDevice()->getVideoModeList()))[i]; + + if(newVm.resolution.y == height) + { + width = newVm.resolution.x; + changed = true; + break; + } + } + } + else if (height == 0 && width > 0) + { + // Our height is 0 but our width isn't... + // Try to find a matching height + for(S32 i=0; igetPlatformWindow()->getGFXDevice()->getVideoModeList()->size(); i++) + { + const GFXVideoMode &newVm = (*(object->getPlatformWindow()->getGFXDevice()->getVideoModeList()))[i]; + + if(newVm.resolution.x == width) + { + height = newVm.resolution.y; + changed = true; + break; + } + } + } + + if (width == 0 || height == 0) + { + // Got a bad size for both of our dimensions or one of our dimensions and + // didn't get a match for the other default back to our current resolution + width = vm.resolution.x; + height = vm.resolution.y; + + changed = true; + } + + if (changed) + Con::errorf("GuiCanvas::setVideoMode(): Error - Invalid resolution of (%d, %d) - attempting (%d, %d)", dAtoi(argv[2]), dAtoi(argv[3]), width, height); + + vm.resolution = Point2I(width, height); + vm.fullScreen = dAtob(argv[4]); + + if (Platform::getWebDeployment()) + vm.fullScreen = false; + + // These optional params are set to default at construction of vm. If they + // aren't specified, just leave them at whatever they were set to. + if ((argc > 5) && (dStrlen(argv[5]) > 0)) + { + vm.bitDepth = dAtoi(argv[5]); + } + if ((argc > 6) && (dStrlen(argv[6]) > 0)) + { + vm.refreshRate = dAtoi(argv[6]); + } + + if ((argc > 7) && (dStrlen(argv[7]) > 0)) + { + vm.antialiasLevel = dAtoi(argv[7]); + } + +#ifndef TORQUE_OS_XENON + object->getPlatformWindow()->setVideoMode(vm); +#endif + + // Store the new mode into a pref. + Con::setVariable( "$pref::Video::mode", vm.toString() ); +} diff --git a/gui/core/guiCanvas.h b/gui/core/guiCanvas.h new file mode 100644 index 0000000..2c3139b --- /dev/null +++ b/gui/core/guiCanvas.h @@ -0,0 +1,431 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICANVAS_H_ +#define _GUICANVAS_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _PLATFORMINPUT_H_ +#include "platform/platformInput.h" +#endif + +#include "component/interfaces/IProcessInput.h" +#include "windowManager/platformWindowMgr.h" +#include "gfx/gfxFence.h" + +#ifdef TORQUE_DEMO_PURCHASE +#ifndef _PURCHASESCREEN_H_ +#include "demo/purchase/purchaseScreen.h" +#endif +#endif + +/// A canvas on which rendering occurs. +/// +/// +/// @section GuiCanvas_contents What a GUICanvas Can Contain... +/// +/// @subsection GuiCanvas_content_contentcontrol Content Control +/// A content control is the top level GuiControl for a screen. This GuiControl +/// will be the parent control for all other GuiControls on that particular +/// screen. +/// +/// @subsection GuiCanvas_content_dialogs Dialogs +/// +/// A dialog is essentially another screen, only it gets overlaid on top of the +/// current content control, and all input goes to the dialog. This is most akin +/// to the "Open File" dialog box found in most operating systems. When you +/// choose to open a file, and the "Open File" dialog pops up, you can no longer +/// send input to the application, and must complete or cancel the open file +/// request. Torque keeps track of layers of dialogs. The dialog with the highest +/// layer is on top and will get all the input, unless the dialog is +/// modeless, which is a profile option. +/// +/// @see GuiControlProfile +/// +/// @section GuiCanvas_dirty Dirty Rectangles +/// +/// The GuiCanvas is based on dirty regions. +/// +/// Every frame the canvas paints only the areas of the canvas that are 'dirty' +/// or need updating. In most cases, this only is the area under the mouse cursor. +/// This is why if you look in guiCanvas.cc the call to glClear is commented out. +/// If you want a really good idea of what exactly dirty regions are and how they +/// work, un-comment that glClear line in the renderFrame method of guiCanvas.cc +/// +/// What you will see is a black screen, except in the dirty regions, where the +/// screen will be painted normally. If you are making an animated GuiControl +/// you need to add your control to the dirty areas of the canvas. +/// +class GuiCanvas : public GuiControl, public IProcessInput +{ + +protected: + typedef GuiControl Parent; + + /// @name Rendering + /// @{ + RectI mOldUpdateRects[2]; + RectI mCurUpdateRect; + /// @} + + /// @name Cursor Properties + /// @{ + + bool mCursorEnabled; + bool mShowCursor; + bool mRenderFront; + Point2F mCursorPt; ///< Current cursor position in local coordinates. + Point2I mLastCursorPt; + GuiCursor *mDefaultCursor; + GuiCursor *mLastCursor; + bool mLastCursorEnabled; + bool mForceMouseToGUI; + bool mClampTorqueCursor; + + /// @} + + /// @name Mouse Input + /// @{ + + SimObjectPtr mMouseCapturedControl; ///< All mouse events will go to this ctrl only + SimObjectPtr mMouseControl; ///< the control the mouse was last seen in unless some other one captured it + bool mMouseControlClicked; ///< whether the current ctrl has been clicked - used by helpctrl + U32 mPrevMouseTime; ///< this determines how long the mouse has been in the same control + U32 mNextMouseTime; ///< used for onMouseRepeat() + U32 mInitialMouseDelay; ///< also used for onMouseRepeat() + bool mMouseButtonDown; ///< Flag to determine if the button is depressed + bool mMouseRightButtonDown; ///< bool to determine if the right button is depressed + bool mMouseMiddleButtonDown; ///< Middle button flag + GuiEvent mLastEvent; + + U8 mLastMouseClickCount; + S32 mLastMouseDownTime; + bool mLeftMouseLast; + bool mMiddleMouseLast; + bool mRightMouseLast; + Point2F mMouseDownPoint; + + /// Processes keyboard input events. Helper method for processInputEvent + /// + /// \param inputEvent Information on the input even to be processed. + /// \return True if the event was handled or false if it was not. + virtual bool processKeyboardEvent(InputEventInfo &inputEvent); + + /// Processes mouse input events. Helper method for processInputEvent + /// + /// \param inputEvent Information on the input even to be processed. + /// \return True if the event was handled or false if it was not. + virtual bool processMouseEvent(InputEventInfo &inputEvent); + + /// Processes gamepad input events. Helper method for processInputEvent + /// + /// \param inputEvent Information on the input even to be processed. + /// \return True if the event was handled or false if it was not. + virtual bool processGamepadEvent(InputEventInfo &inputEvent); + + virtual void findMouseControl(const GuiEvent &event); + virtual void refreshMouseControl(); + /// @} + + /// @name Keyboard Input + /// @{ + + /// Accelerator key map + struct AccKeyMap + { + GuiControl *ctrl; + U32 index; + U32 keyCode; + U32 modifier; + }; + Vector mAcceleratorMap; + + //for tooltip rendering + U32 mHoverControlStart; + GuiControl* mHoverControl; + Point2I mHoverPosition; + bool mHoverPositionSet; + U32 mHoverLeftControlTime; + + /// @} + + // Internal event handling callbacks for use with PlatformWindow. + void handleResize (WindowId did, S32 width, S32 height); + void handleAppEvent (WindowId did, S32 event); + void handlePaintEvent (WindowId did); + + PlatformWindow *mPlatformWindow; + GFXFence **mFences; + S32 mNextFenceIdx; + S32 mNumFences; + + static bool setProtectedNumFences( void *obj, const char *data ); + virtual void setupFences(); + +public: + DECLARE_CONOBJECT(GuiCanvas); + DECLARE_CATEGORY( "Gui Core" ); + + GuiCanvas(); + virtual ~GuiCanvas(); + + virtual bool onAdd(); + virtual void onRemove(); + + static void initPersistFields(); + + /// @name Rendering methods + /// + /// @{ + + /// Repaints the dirty regions of the canvas + /// @param preRenderOnly If set to true, only the onPreRender methods of all the GuiControls will be called + /// @param bufferSwap If set to true, it will swap buffers at the end. This is to support canvas-subclassing. + virtual void renderFrame(bool preRenderOnly, bool bufferSwap = true); + + /// Repaints the entire canvas by calling resetUpdateRegions() and then renderFrame() + virtual void paint(); + + /// Adds a dirty area to the canvas so it will be updated on the next frame + /// @param pos Screen-coordinates of the upper-left hand corner of the dirty area + /// @param ext Width/height of the dirty area + virtual void addUpdateRegion(Point2I pos, Point2I ext); + + /// Resets the update regions so that the next call to renderFrame will + /// repaint the whole canvas + virtual void resetUpdateRegions(); + + /// Resizes the content control to match the canvas size. + void maintainSizing(); + + /// This builds a rectangle which encompasses all of the dirty regions to be + /// repainted + /// @param updateUnion (out) Rectangle which surrounds all dirty areas + virtual void buildUpdateUnion(RectI *updateUnion); + + /// This will swap the buffers at the end of renderFrame. It was added for canvas + /// sub-classes in case they wanted to do some custom code before the buffer + /// flip occured. + virtual void swapBuffers(); + + /// @} + + /// @name Canvas Content Management + /// @{ + + /// This returns the PlatformWindow owned by this Canvas + virtual PlatformWindow *getPlatformWindow() + { + return mPlatformWindow; + } + + /// This sets the content control to something different + /// @param gui New content control + virtual void setContentControl(GuiControl *gui); + + /// Returns the content control + virtual GuiControl *getContentControl(); + + /// Adds a dialog control onto the stack of dialogs + /// @param gui Dialog to add + /// @param layer Layer to put dialog on + virtual void pushDialogControl(GuiControl *gui, S32 layer = 0); + + /// Removes a specific layer of dialogs + /// @param layer Layer to pop off from + virtual void popDialogControl(S32 layer = 0); + + /// Removes a specific dialog control + /// @param gui Dialog to remove from the dialog stack + virtual void popDialogControl(GuiControl *gui); + ///@} + + /// This turns on/off front-buffer rendering + /// @param front True if all rendering should be done to the front buffer + virtual void setRenderFront(bool front) { mRenderFront = front; } + + /// @name Cursor commands + /// A cursor can be on, but not be shown. If a cursor is not on, than it does not + /// process input. + /// @{ + + /// Sets the cursor for the canvas. + /// @param cursor New cursor to use. + virtual void setCursor(GuiCursor *cursor); + S32 mCursorChanged; + + //rpg added + GuiCursor * getCurrentCursor(); + //end rpg added + /// Returns true if the cursor is on. + virtual bool isCursorON() { return mCursorEnabled; } + + /// Sets if mouse events should be passed to the GUI even if the cursor is off. + /// @param onOff True if events should be passed to the GUI if the cursor is off + virtual void setForceMouseToGUI(bool onOff); + + /// Sets if the Torque cursor should be clamped to the window. + /// @param onOff True if the Torque cursor should be clamped against the window + virtual void setClampTorqueCursor(bool onOff); + + /// Returns if the Torque cursor is clamped to the window + virtual bool getClampTorqueCursor() { return mClampTorqueCursor; } + + /// Turns the cursor on or off. + /// @param onOff True if the cursor should be on. + virtual void setCursorON(bool onOff); + + /// Sets the position of the cursor + /// @param pt Point, in screenspace for the cursor + virtual void setCursorPos(const Point2I &pt); + + /// Returns the point, in screenspace, at which the cursor is located. + virtual Point2I getCursorPos(); + + /// Enable/disable rendering of the cursor. + /// @param state True if we should render cursor + virtual void showCursor(bool state); + + /// Returns true if the cursor is being rendered. + virtual bool isCursorShown(); + /// @} + + ///used by the tooltip resource + Point2I getCursorExtent() { return mDefaultCursor->getExtent(); } + + /// @name Input Processing + /// @{ + + /// Processes an input event + /// @see InputEvent + /// @param event Input event to process + virtual bool processInputEvent(InputEventInfo &inputEvent); + /// @} + + /// @name Mouse Methods + /// @{ + + /// When a control gets the mouse lock this means that that control gets + /// ALL mouse input and no other control receives any input. + /// @param lockingControl Control to lock mouse to + virtual void mouseLock(GuiControl *lockingControl); + + /// Unlocks the mouse from a control + /// @param lockingControl Control to unlock from + virtual void mouseUnlock(GuiControl *lockingControl); + + /// Returns the control which the mouse is over + virtual GuiControl* getMouseControl() { return mMouseControl; } + + /// Returns the control which the mouse is locked to if any + virtual GuiControl* getMouseLockedControl() { return mMouseCapturedControl; } + + /// Returns true if the left mouse button is down + virtual bool mouseButtonDown(void) { return mMouseButtonDown; } + + /// Returns true if the right mouse button is down + virtual bool mouseRightButtonDown(void) { return mMouseRightButtonDown; } + + virtual void checkLockMouseMove(const GuiEvent &event); + /// @} + + /// @name Mouse input methods + /// These events process the events before passing them down to the + /// controls they effect. This allows for things such as the input + /// locking and such. + /// + /// Each of these methods corresponds to the action in it's method name + /// and processes the GuiEvent passed as a parameter + /// @{ + virtual void rootMouseUp(const GuiEvent &event); + virtual void rootMouseDown(const GuiEvent &event); + virtual void rootMouseMove(const GuiEvent &event); + virtual void rootMouseDragged(const GuiEvent &event); + + virtual void rootRightMouseDown(const GuiEvent &event); + virtual void rootRightMouseUp(const GuiEvent &event); + virtual void rootRightMouseDragged(const GuiEvent &event); + + virtual void rootMiddleMouseDown(const GuiEvent &event); + virtual void rootMiddleMouseUp(const GuiEvent &event); + virtual void rootMiddleMouseDragged(const GuiEvent &event); + + virtual bool rootMouseWheelUp(const GuiEvent &event); + virtual bool rootMouseWheelDown(const GuiEvent &event); + /// @} + + /// @name Keyboard input methods + /// First responders + /// + /// A first responder is a the GuiControl which responds first to input events + /// before passing them off for further processing. + /// @{ + + /// Moves the first responder to the next tabable controle + virtual bool tabNext(void); + + /// Moves the first responder to the previous tabable control + virtual bool tabPrev(void); + + /// Setups a keyboard accelerator which maps to a GuiControl. + /// + /// @param ctrl GuiControl to map to. + /// @param index + /// @param keyCode Key code. + /// @param modifier Shift, ctrl, etc. + virtual void addAcceleratorKey(GuiControl *ctrl, U32 index, U32 keyCode, U32 modifier); + + /// Sets the first responder. + /// @param firstResponder Control to designate as first responder + virtual void setFirstResponder(GuiControl *firstResponder); + + /// This is used to toggle processing of native OS accelerators, not + /// to be confused with the Torque accelerator key system, to keep them + /// from swallowing up keystrokes. Both GuiTextEditCtrl and GuiTextPadCtrl + /// use this method. + virtual void setNativeAcceleratorsEnabled( bool enabled ); + /// @} + + /// + virtual Point2I getWindowSize(); + + virtual void enableKeyboardTranslation(); + virtual void disableKeyboardTranslation(); + + virtual void setWindowTitle(const char *newTitle); + +private: + static const U32 MAX_GAMEPADS = 4; ///< The maximum number of supported gamepads + +#ifdef TORQUE_DEMO_PURCHASE +private: + PurchaseScreen* mPurchaseScreen; + U32 mLastPurchaseHideTime; + +public: + void showPurchaseScreen(bool show, const char* location, bool doExit); +#endif + +#ifdef TORQUE_DEMO_TIMEOUT +private: + void checkTimeOut(); +#endif + protected: + bool mConsumeLastInputEvent; + public: + void clearMouseRightButtonDown(void) { mMouseRightButtonDown = false; } + void clearMouseButtonDown(void) { mMouseButtonDown = false; } + void setConsumeLastInputEvent(bool flag) { mConsumeLastInputEvent = flag; } +}; + +#endif diff --git a/gui/core/guiControl.cpp b/gui/core/guiControl.cpp new file mode 100644 index 0000000..92bc436 --- /dev/null +++ b/gui/core/guiControl.cpp @@ -0,0 +1,2270 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/core/guiControl.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/codeBlock.h" +#include "platform/event.h" +#include "gfx/bitmap/gBitmap.h" +#include "sim/actionMap.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gui/editor/guiEditCtrl.h" +#include "gfx/gfxDrawUtil.h" + + +GuiControl *GuiControl::smPrevResponder = NULL; +GuiControl *GuiControl::smCurResponder = NULL; +GuiEditCtrl*GuiControl::smEditorHandle = NULL; +bool GuiControl::smDesignTime = false; + +IMPLEMENT_CONOBJECT( GuiControl ); + +static const EnumTable::Enums horzEnums[] = +{ + { GuiControl::horizResizeRight, "right" }, + { GuiControl::horizResizeWidth, "width" }, + { GuiControl::horizResizeLeft, "left" }, + { GuiControl::horizResizeCenter, "center" }, + { GuiControl::horizResizeRelative, "relative" }, + { GuiControl::horizResizeWindowRelative, "windowRelative" } +}; +static const EnumTable gHorizSizingTable(6, &horzEnums[0]); + +static const EnumTable::Enums vertEnums[] = +{ + { GuiControl::vertResizeBottom, "bottom" }, + { GuiControl::vertResizeHeight, "height" }, + { GuiControl::vertResizeTop, "top" }, + { GuiControl::vertResizeCenter, "center" }, + { GuiControl::vertResizeRelative, "relative" }, + { GuiControl::vertResizeWindowRelative, "windowRelative" } +}; +static const EnumTable gVertSizingTable(6, &vertEnums[0]); + +void GuiControl::onRender(Point2I offset, const RectI &updateRect) +{ + RectI ctrlRect(offset, getExtent()); + + //if opaque, fill the update rect with the fill color + if ( mProfile->mOpaque ) + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColor); + + //if there's a border, draw the border + if ( mProfile->mBorder ) + renderBorder(ctrlRect, mProfile); + + // Render Children + renderChildControls(offset, updateRect); +} + +bool GuiControl::defaultTooltipRender( const Point2I &hoverPos, const Point2I &cursorPos, const char* tipText ) +{ + // Short Circuit. + if (!mAwake) + return false; + + if ( dStrlen( mTooltip ) == 0 && ( tipText == NULL || dStrlen( tipText ) == 0 ) ) + return false; + + String renderTip( mTooltip ); + if ( tipText != NULL ) + renderTip = tipText; + + // Need to have root. + GuiCanvas *root = getRoot(); + if ( !root ) + return false; + + GFont *font = mTooltipProfile->mFont; + + // Support for multi-line tooltip text... + + Vector startLineOffsets, lineLengths; + + font->wrapString( renderTip, U32_MAX, startLineOffsets, lineLengths ); + + // The width is the longest line. + U32 tipWidth = 0; + for ( U32 i = 0; i < lineLengths.size(); i++ ) + { + U32 width = font->getStrNWidth( renderTip.c_str() + startLineOffsets[i], lineLengths[i] ); + + if ( width > tipWidth ) + tipWidth = width; + } + + // The height is the number of lines times the height of the font. + U32 tipHeight = lineLengths.size() * font->getHeight(); + + // Vars used: + // Screensize (for position check) + // Offset to get position of cursor + // textBounds for text extent. + Point2I screensize = getRoot()->getWindowSize(); + Point2I offset = hoverPos; + Point2I textBounds; + + // Offset below cursor image + offset.y += 20; // TODO: Attempt to fix?: root->getCursorExtent().y; + + // Create text bounds... + + // Pixels above/below the text + const U32 vMargin = 2; + // Pixels left/right of the text + const U32 hMargin = 4; + + textBounds.x = tipWidth + hMargin * 2; + textBounds.y = tipHeight + vMargin * 2; + + // Check position/width to make sure all of the tooltip will be rendered + // 5 is given as a buffer against the edge + if ( screensize.x < offset.x + textBounds.x + 5 ) + offset.x = screensize.x - textBounds.x - 5; + + // And ditto for the height + if ( screensize.y < offset.y + textBounds.y + 5 ) + offset.y = hoverPos.y - textBounds.y - 5; + + RectI oldClip = GFX->getClipRect(); + + // Set rectangle for the box, and set the clip rectangle. + RectI rect( offset, textBounds ); + GFX->setClipRect( rect ); + + // Draw Filler bit, then border on top of that + GFX->getDrawUtil()->drawRectFill( rect, mTooltipProfile->mFillColor ); + GFX->getDrawUtil()->drawRect( rect, mTooltipProfile->mBorderColor ); + + // Draw the text centered in the tool tip box... + + GFX->getDrawUtil()->setBitmapModulation( mTooltipProfile->mFontColor ); + + for ( U32 i = 0; i < lineLengths.size(); i++ ) + { + Point2I start( hMargin, vMargin + i * font->getHeight() ); + const UTF8 *line = renderTip.c_str() + startLineOffsets[i]; + U32 lineLen = lineLengths[i]; + + GFX->getDrawUtil()->drawTextN( font, start + offset, line, lineLen, mProfile->mFontColors ); + } + + GFX->setClipRect( oldClip ); + + return true; +} + +void GuiControl::renderChildControls(Point2I offset, const RectI &updateRect) +{ + // Save the current clip rect + // so we can restore it at the end of this method. + RectI savedClipRect = GFX->getClipRect(); + + // offset is the upper-left corner of this control in screen coordinates + // updateRect is the intersection rectangle in screen coords of the control + // hierarchy. This can be set as the clip rectangle in most cases. + RectI clipRect = updateRect; + + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + if (ctrl->mVisible) + { + Point2I childPosition = offset + ctrl->getPosition(); + RectI childClip(childPosition, ctrl->getExtent() + Point2I(1,1)); + + if (childClip.intersect(clipRect)) + { + GFX->setClipRect( childClip ); + GFX->setStateBlock(mDefaultGuiSB); + ctrl->onRender(childPosition, childClip); + } + } + } + + // Restore the clip rect to what it was at the start + // of this method. + GFX->setClipRect( savedClipRect ); +} + +void GuiControl::setUpdateRegion(Point2I pos, Point2I ext) +{ + Point2I upos = localToGlobalCoord(pos); + GuiCanvas *root = getRoot(); + if (root) + { + root->addUpdateRegion(upos, ext); + } +} + +void GuiControl::setUpdate() +{ + setUpdateRegion(Point2I(0,0), getExtent()); +} + +GuiControl::GuiControl() : mAddGroup( NULL ), + mLayer(0), + mBounds(0,0,64,64), + mMinExtent(8,2), + mProfile(NULL), + mLangTable(NULL), + mFirstResponder(NULL), + mVisible(true), + mActive(false), + mAwake(false), + mCanSave(true), + mHorizSizing(horizResizeRight), + mVertSizing(vertResizeBottom), + mTooltipProfile(NULL), + mTipHoverTime(1000), + mIsContainer(false), + mCanResize(true), + mCanHit( true ) +{ + mConsoleVariable = StringTable->insert(""); + mConsoleCommand = StringTable->insert(""); + mAltConsoleCommand = StringTable->insert(""); + mAcceleratorKey = StringTable->insert(""); + mLangTableName = StringTable->insert(""); + mScriptFile = CodeBlock::getCurrentCodeBlockName(); + + mTooltip = StringTable->insert(""); + mRenderTooltipDelegate.bind( this, &GuiControl::defaultTooltipRender ); + + mCanSaveFieldDictionary = false; + mNotifyChildrenResized = true; + + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +GuiControl::~GuiControl() +{ +} + +//----------------------------------------------------------------------------- +// Persistence +//----------------------------------------------------------------------------- +void GuiControl::initPersistFields() +{ + // Things relevant only to the editor. + addGroup("Gui Editing"); + addField("isContainer", TypeBool, Offset(mIsContainer, GuiControl)); + endGroup("Gui Editing"); + + // Parent Group. + addGroup("GuiControl"); + + addProtectedField("Profile", TypeGuiProfile, Offset(mProfile, GuiControl), &setProfileProt, &defaultProtectedGetFn, ""); + addField("HorizSizing", TypeEnum, Offset(mHorizSizing, GuiControl), 1, &gHorizSizingTable); + addField("VertSizing", TypeEnum, Offset(mVertSizing, GuiControl), 1, &gVertSizingTable); + + addField("Position", TypePoint2I, Offset(mBounds.point, GuiControl)); + addField("Extent", TypePoint2I, Offset(mBounds.extent, GuiControl)); + addField("MinExtent", TypePoint2I, Offset(mMinExtent, GuiControl)); + addField("canSave", TypeBool, Offset(mCanSave, GuiControl)); + addProtectedField( "Visible", TypeBool, Offset(mVisible, GuiControl), &setVisible, &defaultProtectedGetFn, "" ); + + addDeprecatedField("Modal"); + addDeprecatedField("SetFirstResponder"); + + addField("Variable", TypeString, Offset(mConsoleVariable, GuiControl)); + addField("Command", TypeString, Offset(mConsoleCommand, GuiControl)); + addField("AltCommand", TypeString, Offset(mAltConsoleCommand, GuiControl)); + addField("Accelerator", TypeString, Offset(mAcceleratorKey, GuiControl)); + endGroup("GuiControl"); + + addGroup("ToolTip"); + addProtectedField("tooltipprofile", TypeGuiProfile, Offset(mTooltipProfile, GuiControl), &setTooltipProfileProt, &defaultProtectedGetFn, ""); + addField("tooltip", TypeCaseString, Offset(mTooltip, GuiControl)); + addField("hovertime", TypeS32, Offset(mTipHoverTime, GuiControl)); + endGroup("ToolTip"); + + + addGroup("Localization"); + addField("langTableMod", TypeString, Offset(mLangTableName, GuiControl)); + endGroup("Localization"); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// Simulation +//----------------------------------------------------------------------------- +bool GuiControl::processArguments(S32 argc, const char **argv) +{ + // argv[0] - The GuiGroup to add this control to when it's created. + // this is an optional parameter that may be specified at + // object creation time to organize a gui control into a + // subgroup of GuiControls and is useful in helping the + // gui editor group and sort the existent gui's in the Sim. + + // Specified group? + if( argc == 1 ) + { + StringTableEntry steIntName = StringTable->insert(argv[0]); + mAddGroup = dynamic_cast(Sim::getGuiGroup()->findObjectByInternalName( steIntName )); + if( mAddGroup == NULL ) + { + mAddGroup = new SimGroup(); + if( mAddGroup->registerObject() ) + { + mAddGroup->setInternalName( steIntName ); + Sim::getGuiGroup()->addObject( mAddGroup ); + } + else + { + SAFE_DELETE( mAddGroup ); + return false; + } + } + mAddGroup->addObject(this); + } + return true; +} + +void GuiControl::awaken() +{ + AssertFatal(!mAwake, "GuiControl::awaken: control is already awake"); + if(mAwake) + return; + + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + + AssertFatal(!ctrl->isAwake(), "GuiControl::awaken: child control is already awake"); + if(!ctrl->isAwake()) + ctrl->awaken(); + } + + AssertFatal(!mAwake, "GuiControl::awaken: should not be awake here"); + if(!mAwake) + { + if(!onWake()) + { + Con::errorf(ConsoleLogEntry::General, "GuiControl::awaken: failed onWake for obj: %s", getName()); + AssertFatal(0, "GuiControl::awaken: failed onWake"); + deleteObject(); + } + } +} + +void GuiControl::sleep() +{ + AssertFatal(mAwake, "GuiControl::sleep: control is not awake"); + if(!mAwake) + return; + + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + + AssertFatal(ctrl->isAwake(), "GuiControl::sleep: child control is already asleep"); + if(ctrl->isAwake()) + ctrl->sleep(); + } + + AssertFatal(mAwake, "GuiControl::sleep: should not be asleep here"); + if(mAwake) + onSleep(); +} + +bool GuiControl::onAdd() +{ + // Let Parent Do Work. + if ( !Parent::onAdd() ) + return false; + + // Grab the classname of this object + const char *cName = getClassName(); + + // if we're a pure GuiControl, then we're a container by default. + if ( dStrcmp( "GuiControl", cName ) == 0 ) + mIsContainer = true; + + // Add to root group. + if ( mAddGroup == NULL ) + mAddGroup = Sim::getGuiGroup(); + mAddGroup->addObject(this); + + // If we don't have a profile we must assign one now. + // Try assigning one based on the control's class name... + if ( !mProfile ) + { + String name = getClassName(); + + if ( name.isNotEmpty() ) + { + U32 pos = name.find( "Ctrl" ); + + if ( pos != -1 ) + name.replace( pos, 4, "Profile" ); + else + name += "Profile"; + + GuiControlProfile *profile = NULL; + if ( Sim::findObject( name, profile ) ) + setControlProfile( profile ); + } + } + + // Try assigning the default profile... + if ( !mProfile ) + { + GuiControlProfile *profile = NULL; + Sim::findObject( "GuiDefaultProfile", profile ); + + AssertISV( profile != NULL, avar("GuiControl::onAdd() unable to find specified profile and GuiDefaultProfile does not exist!") ); + + setControlProfile( profile ); + } + + // We must also assign a valid TooltipProfile... + if ( !mTooltipProfile ) + { + GuiControlProfile *profile = NULL; + Sim::findObject( "GuiTooltipProfile", profile ); + + AssertISV( profile != NULL, avar("GuiControl::onAdd() unable to find specified tooltip profile and GuiTooltipProfile does not exist!") ); + + setTooltipProfile( profile ); + } + + // Notify Script. + if ( isMethod("onAdd") ) + Con::executef( this, "onAdd" ); + + GFXStateBlockDesc d; + + d.cullDefined = true; + d.cullMode = GFXCullNone; + d.zDefined = true; + d.zEnable = false; + + mDefaultGuiSB = GFX->createStateBlock( d ); + + // Return Success. + return true; +} + +void GuiControl::onRemove() +{ + // Only invoke script callbacks if they can be received + if( isMethod("onRemove") ) + Con::executef(this, "onRemove"); + + clearFirstResponder(); + + Parent::onRemove(); +} + +void GuiControl::onDeleteNotify(SimObject *object) +{ + if (object == mProfile) + mProfile = NULL; + if (object == mTooltipProfile) + mTooltipProfile = NULL; +} + +bool GuiControl::onWake() +{ + AssertFatal( !mAwake, "GuiControl::onWake: control is already awake" ); + if( mAwake ) + return false; + + // [tom, 4/18/2005] Cause mLangTable to be refreshed in case it was changed + mLangTable = NULL; + + //set the flag + mAwake = true; + + //set the layer + GuiCanvas *root = getRoot(); + AssertFatal(root, "Unable to get the root Canvas."); + GuiControl *parent = getParent(); + if (parent && parent != root) + mLayer = parent->mLayer; + + //make sure the first responder exists + if (! mFirstResponder) + mFirstResponder = findFirstTabable(); + + //increment the profile + mProfile->incRefCount(); + mTooltipProfile->incRefCount(); + +#ifdef TORQUE_DEMO_PURCHASE + if (getName() && getName()[0] != 0 && + dStricmp(getName(), "MainMenuGui") == 0) + { + if (root) + root->showPurchaseScreen(true, "mainmenu", false); + } +#endif + + // Only invoke script callbacks if we have a namespace in which to do so + // This will suppress warnings + if( isMethod("onWake") ) + Con::executef(this, "onWake"); + + return true; +} + +void GuiControl::onSleep() +{ + AssertFatal(mAwake, "GuiControl::onSleep: control is not awake"); + if(!mAwake) + return; + + //decrement the profile reference + mProfile->decRefCount(); + mTooltipProfile->decRefCount(); + clearFirstResponder(); + mouseUnlock(); + + // Only invoke script callbacks if we have a namespace in which to do so + // This will suppress warnings + if( isMethod("onSleep") ) + Con::executef(this, "onSleep"); + + // Set Flag + mAwake = false; +} + +void GuiControl::onChildAdded( GuiControl *child ) +{ + // Base class does not make use of this +} + +void GuiControl::onChildRemoved( GuiControl *child ) +{ + // Base does nothing with this +} + +void GuiControl::addObject(SimObject *object) +{ + GuiControl *ctrl = dynamic_cast(object); + if(object->getGroup() == this) + return; + + AssertFatal( ctrl, "GuiControl::addObject() - cannot add non-GuiControl as child of GuiControl" ); + + Parent::addObject(object); + + AssertFatal(!ctrl->isAwake(), "GuiControl::addObject: object is already awake before add"); + if(mAwake) + ctrl->awaken(); + + // If we are a child, notify our parent that we've been removed + GuiControl *parent = ctrl->getParent(); + if( parent ) + parent->onChildAdded( ctrl ); +} + +void GuiControl::removeObject(SimObject *object) +{ + AssertFatal(mAwake == static_cast(object)->isAwake(), "GuiControl::removeObject: child control wake state is bad"); + if (mAwake) + static_cast(object)->sleep(); + + onChildRemoved( static_cast(object) ); + + Parent::removeObject(object); +} + +GuiControl *GuiControl::getParent() +{ + SimObject *obj = getGroup(); + if (GuiControl* gui = dynamic_cast(obj)) + return gui; + return 0; +} + +GuiCanvas *GuiControl::getRoot() +{ + GuiControl *root = NULL; + GuiControl *parent = getParent(); + while (parent) + { + root = parent; + parent = parent->getParent(); + } + if (root) + return dynamic_cast(root); + else + return NULL; +} + +void GuiControl::inspectPreApply() +{ + // The canvas never sleeps + if(mAwake && dynamic_cast(this) == NULL ) + { + // Something here was causing profile reference count to get hosed. + // However, not doing this sleep/wake cycle could potentially cause field + // changes for certain controls to not cause a visual update. But + // I have not run across any yet. + + //onSleep(); // release all our resources. + //mAwake = true; + } +} + +void GuiControl::inspectPostApply() +{ + // Shhhhhhh, you don't want to wake the canvas! + if(mAwake && dynamic_cast(this) == NULL ) + { + mAwake = false; + bool isContainer = mIsContainer; + onWake(); + mIsContainer = isContainer; + } +} + +//----------------------------------------------------------------------------- +// Coordinates +//----------------------------------------------------------------------------- +Point2I GuiControl::localToGlobalCoord(const Point2I &src) +{ + Point2I ret = src; + ret += getPosition(); + GuiControl *walk = getParent(); + while(walk) + { + ret += walk->getPosition(); + walk = walk->getParent(); + } + return ret; +} + +Point2I GuiControl::globalToLocalCoord(const Point2I &src) +{ + Point2I ret = src; + ret -= getPosition(); + GuiControl *walk = getParent(); + while(walk) + { + ret -= walk->getPosition(); + walk = walk->getParent(); + } + return ret; +} + +//----------------------------------------------------------------------------- +// Internationalization +//----------------------------------------------------------------------------- +LangTable * GuiControl::getGUILangTable() +{ + if(mLangTable) + return mLangTable; + + if(mLangTableName && *mLangTableName) + { + mLangTable = (LangTable *)getModLangTable((const UTF8*)mLangTableName); + return mLangTable; + } + + GuiControl *parent = getParent(); + if(parent) + return parent->getGUILangTable(); + + return NULL; +} + +const UTF8 * GuiControl::getGUIString(S32 id) +{ + LangTable *lt = getGUILangTable(); + if(lt) + return lt->getString(id); + + return NULL; +} + +//----------------------------------------------------------------------------- +// Sizing Methods +//----------------------------------------------------------------------------- +bool GuiControl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + const Point2I minExtent = getMinExtent(); + Point2I actualNewExtent = Point2I(getMax(minExtent.x, newExtent.x), + getMax(minExtent.y, newExtent.y)); + + // only do the child control resizing stuff if you really need to. + const RectI bounds = getBounds(); + + // If we didn't size anything, return false to indicate such + bool extentChanged = (actualNewExtent != bounds.extent); + bool positionChanged = (newPosition != bounds.point); + if (!extentChanged && !positionChanged ) + return false; + + // Update Position + if ( positionChanged ) + mBounds.point = newPosition; + + // Update Extent + if( extentChanged ) + { + //call set update both before and after + setUpdate(); + + mBounds.extent = actualNewExtent; + + // Obey the flag! + // Could be set if we are resizing in response to a child resizing! + if ( mNotifyChildrenResized ) + { + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->parentResized(RectI(bounds.point, bounds.extent), RectI(newPosition,actualNewExtent)); + } + } + + GuiControl *parent = getParent(); + if (parent) + parent->childResized(this); + setUpdate(); + } + + // We sized something + if( extentChanged ) + return true; + + // Note : We treat a repositioning as no sizing happening + // because parent's should really not need to know when they + // have moved, as it should not affect any child sizing since + // all child bounds are relative to the parent's 0,0 + return false; + +} +bool GuiControl::setPosition( const Point2I &newPosition ) +{ + return resize( newPosition, mBounds.extent ); +} + +bool GuiControl::setExtent( const Point2I &newExtent ) +{ + return resize( mBounds.point, newExtent ); +} + +bool GuiControl::setBounds( const RectI &newBounds ) +{ + return resize( newBounds.point, newBounds.extent ); +} + +void GuiControl::setLeft( S32 newLeft ) +{ + resize( Point2I( newLeft, mBounds.point.y), mBounds.extent ); +} + +void GuiControl::setTop( S32 newTop ) +{ + resize( Point2I( mBounds.point.x, newTop ), mBounds.extent ); +} + +void GuiControl::setWidth( S32 newWidth ) +{ + resize( mBounds.point, Point2I( newWidth, mBounds.extent.y ) ); +} + +void GuiControl::setHeight( S32 newHeight ) +{ + resize( mBounds.point, Point2I( mBounds.extent.x, newHeight ) ); +} + +void GuiControl::childResized(GuiControl *child) +{ + TORQUE_UNUSED(child); + // default to do nothing... +} + +//ËÄÉáÎåÈë +static inline S32 convertToInt(F32 fVar) +{ + return (S32)(fVar + 0.5); +} + +void GuiControl::parentResized(const RectI &oldParentRect, const RectI &newParentRect) +{ + Point2I newPosition = getPosition(); + Point2I newExtent = getExtent(); + + S32 deltaX = newParentRect.extent.x - oldParentRect.extent.x; + S32 deltaY = newParentRect.extent.y - oldParentRect.extent.y; + + if (mHorizSizing == horizResizeCenter)//ÖжÔÆë + newPosition.x = convertToInt((newParentRect.extent.x - getWidth()) / 2); + else if (mHorizSizing == horizResizeWidth)//ÓÒ¶ÔÆë + newPosition.x = newParentRect.extent.x - getWidth(); + //newExtent.x += deltaX; + else if (mHorizSizing == horizResizeLeft)//×ó¶ÔÆë + newPosition.x = 0;//convertToInt((newPosition.x * newParentRect.extent.x) / oldParentRect.extent.x); + else if (mHorizSizing == horizResizeRelative && oldParentRect.extent.x != 0) + { + S32 newLeft = convertToInt((newPosition.x * newParentRect.extent.x) / oldParentRect.extent.x); + S32 newRight = convertToInt(((newPosition.x + newExtent.x) * newParentRect.extent.x) / oldParentRect.extent.x); + + newPosition.x = newLeft; + newExtent.x = newRight - newLeft; + } + + if (mVertSizing == vertResizeCenter)//ÖÐ + newPosition.y = convertToInt((newParentRect.extent.y - getHeight()) / 2); + else if (mVertSizing == vertResizeHeight)//Ï + newPosition.y = newParentRect.extent.y - getHeight(); + //newExtent.y += deltaY; + else if (mVertSizing == vertResizeTop)//ÉÏ + newPosition.y = 0;//convertToInt((newPosition.y * newParentRect.extent.y) / oldParentRect.extent.y); + else if(mVertSizing == vertResizeRelative && oldParentRect.extent.y != 0) + { + + S32 newTop = convertToInt((newPosition.y * newParentRect.extent.y) / oldParentRect.extent.y); + S32 newBottom = convertToInt(((newPosition.y + newExtent.y) * newParentRect.extent.y) / oldParentRect.extent.y); + + newPosition.y = newTop; + newExtent.y = newBottom - newTop; + } + + // Resizing Re factor [9/18/2006] + // Only resize if our minExtent is satisfied with it. + Point2I minExtent = getMinExtent(); + if( newExtent.x >= minExtent.x && newExtent.y >= minExtent.y ) + resize(newPosition, newExtent); +} + +//----------------------------------------------------------------------------- +// Render Methods +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Dirty Rects +//----------------------------------------------------------------------------- + +void GuiControl::preRender() +{ + AssertFatal(mAwake, "GuiControl::preRender: control is not awake"); + if(!mAwake) + return; + + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->preRender(); + } + onPreRender(); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiControl::setTooltipProfile( GuiControlProfile *prof ) +{ + AssertFatal( prof, "GuiControl::setTooltipProfile: invalid profile" ); + + if ( prof == mTooltipProfile ) + return; + + bool skipAwaken = false; + + if ( mTooltipProfile == NULL ) + skipAwaken = true; + + if ( mAwake && mTooltipProfile ) + mTooltipProfile->decRefCount(); + + // Clear the delete notification we previously set up + if ( mTooltipProfile ) + clearNotify( mTooltipProfile ); + + mTooltipProfile = prof; + if ( mAwake ) + mTooltipProfile->incRefCount(); + + // Make sure that the new profile will notify us when it is deleted + if ( mTooltipProfile ) + deleteNotify( mTooltipProfile ); + + // force an update when the profile is changed + if ( mAwake && !skipAwaken ) + { + sleep(); + awaken(); + } +} + +void GuiControl::setControlProfile( GuiControlProfile *prof ) +{ + AssertFatal( prof, "GuiControl::setControlProfile: invalid profile" ); + + if ( prof == mProfile ) + return; + + bool skipAwaken = false; + + if ( mProfile == NULL ) + skipAwaken = true; + + if ( mAwake && mProfile ) + mProfile->decRefCount(); + + // Clear the delete notification we previously set up + if ( mProfile ) + clearNotify( mProfile ); + + mProfile = prof; + if ( mAwake ) + mProfile->incRefCount(); + + // Make sure that the new profile will notify us when it is deleted + if ( mProfile ) + deleteNotify( mProfile ); + + // force an update when the profile is changed + if ( mAwake && !skipAwaken ) + { + sleep(); + awaken(); + } +} + +bool GuiControl::setProfileProt( void *obj, const char *data) +{ + GuiControl* ctrl = static_cast( obj ); + GuiControlProfile *prof = dynamic_cast( Sim::findObject( data ) ); + if ( prof == NULL ) + return false; + + // filter through our setter, for consistency + ctrl->setControlProfile( prof ); + + // ask the console not to set the data, because we've already set it + return false; +} + +bool GuiControl::setTooltipProfileProt( void *obj, const char *data ) +{ + GuiControl* ctrl = static_cast( obj ); + GuiControlProfile *prof = dynamic_cast( Sim::findObject( data ) ); + if ( prof == NULL ) + return false; + + // filter through our setter, for consistency + ctrl->setTooltipProfile( prof ); + + // ask the console not to set the data, because we've already set it + return false; +} + +void GuiControl::onPreRender() +{ + // do nothing. +} + +//----------------------------------------------------------------------------- +// checks up the parent hierarchy - if anyone above us is not savable returns false +// otherwise, returns true. +//----------------------------------------------------------------------------- +bool GuiControl::getCanSaveParent() +{ + GuiControl *walk = this; + while(walk) + { + if(!walk->getCanSave()) + return false; + + walk = walk->getParent(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Can we Save to a TorqueScript file? +//----------------------------------------------------------------------------- +bool GuiControl::getCanSave() +{ + return mCanSave; +} + +//----------------------------------------------------------------------------- +// Sets whether we can save out to a file (TorqueScript) +//----------------------------------------------------------------------------- +void GuiControl::setCanSave(bool bCanSave) +{ + mCanSave = bCanSave; +} + +//----------------------------------------------------------------------------- +// checks out mCanSave flag, if true just passes along to our parent, +// if false, then we return without writing. Note, also, that +// if our parent is not writeable, then we should not be writable... +//----------------------------------------------------------------------------- +void GuiControl::write(Stream &stream, U32 tabStop, U32 flags) +{ + //note: this will return false if either we, or any of our parents, are non-save controls + bool bCanSave = ( flags & IgnoreCanSave ) || ( flags & NoCheckParentCanSave && getCanSave() ) || getCanSaveParent(); + StringTableEntry steName = mAddGroup->getInternalName(); + if(bCanSave && mAddGroup && (steName != NULL) && (steName != StringTable->insert("null")) && getName() ) + { + MutexHandle handle; + handle.lock(mMutex); + + // export selected only? + if((flags & SelectedOnly) && !isSelected()) + { + for(U32 i = 0; i < size(); i++) + (*this)[i]->write(stream, tabStop, flags); + + return; + + } + + stream.writeTabs(tabStop); + char buffer[1024]; + dSprintf(buffer, sizeof(buffer), "new %s(%s,%s) {\r\n", getClassName(), getName() ? getName() : "", mAddGroup->getInternalName()); + stream.write(dStrlen(buffer), buffer); + writeFields(stream, tabStop + 1); + + if(size()) + { + stream.write(2, "\r\n"); + for(U32 i = 0; i < size(); i++) + (*this)[i]->write(stream, tabStop + 1, flags); + } + + stream.writeTabs(tabStop); + stream.write(4, "};\r\n"); + } + else if (bCanSave) + Parent::write( stream, tabStop, flags ); + +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Make Sure Child 1 is Ordered Just Under Child 2. +//----------------------------------------------------------------------------- +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +const char *GuiControl::getScriptValue() +{ + return NULL; +} + +void GuiControl::setScriptValue(const char *value) +{ + TORQUE_UNUSED(value); +} + +void GuiControl::setConsoleVariable(const char *variable) +{ + if (variable) + { + mConsoleVariable = StringTable->insert(variable); + } + else + { + mConsoleVariable = StringTable->insert(""); + } +} + +//----------------------------------------------------------------------------- +// finds and returns the first immediate child of ours whose +// internal name matches the passed String. returns Null if not found. +//----------------------------------------------------------------------------- +void GuiControl::setConsoleCommand(const char *newCmd) +{ + if (newCmd) + mConsoleCommand = StringTable->insert(newCmd); + else + mConsoleCommand = StringTable->insert(""); +} + +const char * GuiControl::getConsoleCommand() +{ + return mConsoleCommand; +} + +void GuiControl::setSizing(S32 horz, S32 vert) +{ + mHorizSizing = horz; + mVertSizing = vert; +} + + +void GuiControl::setVariable(const char *value) +{ + if (mConsoleVariable[0]) + Con::setVariable(mConsoleVariable, value); +} + +void GuiControl::setIntVariable(S32 value) +{ + if (mConsoleVariable[0]) + Con::setIntVariable(mConsoleVariable, value); +} + +void GuiControl::setFloatVariable(F32 value) +{ + if (mConsoleVariable[0]) + Con::setFloatVariable(mConsoleVariable, value); +} + +const char * GuiControl::getVariable() +{ + if (mConsoleVariable[0]) + return Con::getVariable(mConsoleVariable); + else return NULL; +} + +S32 GuiControl::getIntVariable() +{ + if (mConsoleVariable[0]) + return Con::getIntVariable(mConsoleVariable); + else return 0; +} + +F32 GuiControl::getFloatVariable() +{ + if (mConsoleVariable[0]) + return Con::getFloatVariable(mConsoleVariable); + else return 0.0f; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +bool GuiControl::cursorInControl() +{ + GuiCanvas *root = getRoot(); + if (! root) return false; + + Point2I pt = root->getCursorPos(); + Point2I extent = getExtent(); + Point2I offset = localToGlobalCoord(Point2I(0, 0)); + if (pt.x >= offset.x && pt.y >= offset.y && + pt.x < offset.x + extent.x && pt.y < offset.y + extent.y) + { + return true; + } + else + { + return false; + } +} + +bool GuiControl::pointInControl(const Point2I& parentCoordPoint) +{ + const RectI &bounds = getBounds(); + S32 xt = parentCoordPoint.x - bounds.point.x; + S32 yt = parentCoordPoint.y - bounds.point.y; + return xt >= 0 && yt >= 0 && xt < bounds.extent.x && yt < bounds.extent.y; +} + +GuiControl* GuiControl::findHitControl(const Point2I &pt, S32 initialLayer) +{ + iterator i = end(); // find in z order (last to first) + + while (i != begin()) + { + i--; + GuiControl *ctrl = static_cast(*i); + if (initialLayer >= 0 && ctrl->mLayer > initialLayer) + { + continue; + } + + else if (ctrl->mVisible && ctrl->mCanHit && ctrl->pointInControl(pt)) + { + Point2I ptemp = pt - ctrl->getPosition(); + GuiControl *hitCtrl = ctrl->findHitControl(ptemp); + + if ( hitCtrl->mProfile->mModal ) + return hitCtrl; + } + } + + if( mCanHit ) + return this; + return NULL; +} + +bool GuiControl::findHitControls( const RectI& rect, Vector< GuiControl* >& outResult, U32 flags, S32 initialLayer, U32 depth ) +{ + if( !mVisible ) + return false; + else if( !mCanHit && flags & HIT_NoCanHitNoRecurse ) + return false; + + // Check for hit. If not full-box, always counts. + + bool isHit = mVisible; + if( flags & HIT_FullBoxOnly ) + { + RectI rectInParentSpace = rect; + rectInParentSpace.point += getPosition(); + + isHit &= rectInParentSpace.contains( getBounds() ); + } + else + isHit &= mCanHit; + + // If we have a hit and should not recurse into children, + // return us. + + if( isHit && flags & HIT_ParentPreventsChildHit && depth > 0 ) + { + outResult.push_back( this ); + return true; + } + + // Check child controls. + + bool haveFoundChild = false; + iterator i = end(); + + while( i != begin() ) + { + i --; + + GuiControl* ctrl = static_cast< GuiControl* >( *i ); + if( initialLayer >= 0 && ctrl->mLayer > initialLayer ) + continue; + + if( ctrl->getBounds().overlaps( rect ) ) + { + RectI transposedRect = rect; + transposedRect.point -= ctrl->getPosition(); + + if( ctrl->findHitControls( transposedRect, outResult, flags, -1, depth + 1 ) ) + haveFoundChild = true; + } + } + + if( ( !haveFoundChild || flags & HIT_AddParentHits ) && isHit ) + { + outResult.push_back( this ); + return true; + } + + return false; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +bool GuiControl::isMouseLocked() +{ + GuiCanvas *root = getRoot(); + return root ? root->getMouseLockedControl() == this : false; +} + +void GuiControl::mouseLock(GuiControl *lockingControl) +{ + GuiCanvas *root = getRoot(); + if (root) + root->mouseLock(lockingControl); +} + +void GuiControl::mouseLock() +{ + GuiCanvas *root = getRoot(); + if (root) + root->mouseLock(this); +} + +void GuiControl::mouseUnlock() +{ + GuiCanvas *root = getRoot(); + if (root) + root->mouseUnlock(this); +} + +bool GuiControl::onInputEvent(const InputEventInfo &event) +{ + // Do nothing by default... + return( false ); +} + +void GuiControl::onMouseUp(const GuiEvent &event) +{ +} + +void GuiControl::onMouseDown(const GuiEvent &event) +{ + if ( !mVisible || !mAwake ) + return; + + execConsoleCallback(); +} + +void GuiControl::onMouseMove(const GuiEvent &event) +{ + //if this control is a dead end, make sure the event stops here + if ( !mVisible || !mAwake ) + return; + + //pass the event to the parent + GuiControl *parent = getParent(); + if ( parent ) + parent->onMouseMove( event ); +} + +void GuiControl::onMouseDragged(const GuiEvent &event) +{ +} + +void GuiControl::onMouseEnter(const GuiEvent &event) +{ +} + +void GuiControl::onMouseLeave(const GuiEvent &event) +{ +} + +bool GuiControl::onMouseWheelUp( const GuiEvent &event ) +{ + //if this control is a dead end, make sure the event stops here + if ( !mVisible || !mAwake ) + return true; + + //pass the event to the parent + GuiControl *parent = getParent(); + if ( parent ) + return parent->onMouseWheelUp( event ); + else + return false; +} + +bool GuiControl::onMouseWheelDown( const GuiEvent &event ) +{ + //if this control is a dead end, make sure the event stops here + if ( !mVisible || !mAwake ) + return true; + + //pass the event to the parent + GuiControl *parent = getParent(); + if ( parent ) + return parent->onMouseWheelDown( event ); + else + return false; +} + +void GuiControl::onRightMouseDown(const GuiEvent &) +{ +} + +void GuiControl::onRightMouseUp(const GuiEvent &) +{ +} + +void GuiControl::onRightMouseDragged(const GuiEvent &) +{ +} + +void GuiControl::onMiddleMouseDown(const GuiEvent &) +{ +} + +void GuiControl::onMiddleMouseUp(const GuiEvent &) +{ +} + +void GuiControl::onMiddleMouseDragged(const GuiEvent &) +{ +} + + +GuiControl* GuiControl::findFirstTabable() +{ + // No tabbing if the control is disabled or hidden. + if ( !mAwake || !mVisible ) + return NULL; + + GuiControl *tabCtrl = NULL; + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + tabCtrl = ctrl->findFirstTabable(); + if (tabCtrl) + { + mFirstResponder = tabCtrl; + return tabCtrl; + } + } + + //nothing was found, therefore, see if this ctrl is tabable + return ( mProfile != NULL ) ? ( ( mProfile->mTabable && mAwake && mVisible ) ? this : NULL ) : NULL; +} + +GuiControl* GuiControl::findLastTabable(bool firstCall) +{ + // No tabbing if the control is disabled or hidden. + if ( !mAwake || !mVisible ) + return NULL; + + //if this is the first call, clear the global + if (firstCall) + smPrevResponder = NULL; + + //if this control is tabable, set the global + if (mProfile->mTabable) + smPrevResponder = this; + + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->findLastTabable(false); + } + + //after the entire tree has been traversed, return the last responder found + mFirstResponder = smPrevResponder; + return smPrevResponder; +} + +GuiControl *GuiControl::findNextTabable(GuiControl *curResponder, bool firstCall) +{ + // No tabbing if the control is disabled or hidden. + if ( !mAwake || !mVisible ) + return NULL; + + //if this is the first call, clear the global + if (firstCall) + smCurResponder = NULL; + + //first find the current responder + if (curResponder == this) + smCurResponder = this; + + //if the first responder has been found, return the very next *tabable* control + else if ( smCurResponder && mProfile->mTabable && mAwake && mVisible && mActive ) + return( this ); + + //loop through, checking each child to see if it is the one that follows the firstResponder + GuiControl *tabCtrl = NULL; + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + tabCtrl = ctrl->findNextTabable(curResponder, false); + if (tabCtrl) break; + } + mFirstResponder = tabCtrl; + return tabCtrl; +} + +GuiControl *GuiControl::findPrevTabable(GuiControl *curResponder, bool firstCall) +{ + // No tabbing if the control is disabled or hidden. + if ( !mAwake || !mVisible ) + return NULL; + + if (firstCall) + smPrevResponder = NULL; + + //if this is the current reponder, return the previous one + if (curResponder == this) + return smPrevResponder; + + //else if this is a responder, store it in case the next found is the current responder + else if ( mProfile->mTabable && mAwake && mVisible && mActive ) + smPrevResponder = this; + + //loop through, checking each child to see if it is the one that follows the firstResponder + GuiControl *tabCtrl = NULL; + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + tabCtrl = ctrl->findPrevTabable(curResponder, false); + if (tabCtrl) break; + } + mFirstResponder = tabCtrl; + return tabCtrl; +} + +void GuiControl::onLoseFirstResponder() +{ + // Since many controls have visual cues when they are the firstResponder... + setUpdate(); + + if( isMethod( "onLoseFirstResponder" ) ) + Con::executef(this, "onLoseFirstResponder" ); +} + +bool GuiControl::ControlIsChild(GuiControl *child) +{ + //function returns true if this control, or one of it's children is the child control + if (child == this) + return true; + + //loop through, checking each child to see if it is ,or contains, the firstResponder + iterator i; + for (i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + if (ctrl->ControlIsChild(child)) return true; + } + + //not found, therefore false + return false; +} + +bool GuiControl::isFirstResponder() +{ + GuiCanvas *root = getRoot(); + return root && root->getFirstResponder() == this; +} + +void GuiControl::setFirstResponder( GuiControl* firstResponder ) +{ + if ( firstResponder && firstResponder->mProfile->mCanKeyFocus ) + mFirstResponder = firstResponder; + + GuiControl *parent = getParent(); + if ( parent ) + parent->setFirstResponder( firstResponder ); + + if( isFirstResponder() && isMethod( "onGainFirstResponder" ) ) + Con::executef(this, "onGainFirstResponder" ); +} + +void GuiControl::setFirstResponder() +{ + if ( mAwake && mVisible ) + { + GuiControl *parent = getParent(); + if ( mProfile->mCanKeyFocus == true && parent != NULL ) + { + parent->setFirstResponder(this); + + // Since many controls have visual cues when they are the firstResponder... + this->setUpdate(); + + // Call Script. + if( isFirstResponder() && isMethod( "onGainFirstResponder" ) ) + Con::executef(this, "onGainFirstResponder" ); + } + } +} + +void GuiControl::clearFirstResponder(bool useCallback) +{ + bool cleared = false; + GuiControl *parent = this; + while((parent = parent->getParent()) != NULL) + { + if(parent->mFirstResponder == this) + { + parent->mFirstResponder = NULL; + cleared = true; + } + else + break; + } + + if( cleared && useCallback ) + { + onLoseFirstResponder(); + } +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiControl::buildAcceleratorMap() +{ + //add my own accel key + addAcceleratorKey(); + + //add all my childrens keys + iterator i; + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->buildAcceleratorMap(); + } +} + +void GuiControl::addAcceleratorKey() +{ + //see if we have an accelerator + if (mAcceleratorKey == StringTable->insert("")) + return; + + EventDescriptor accelEvent; + ActionMap::createEventDescriptor(mAcceleratorKey, &accelEvent); + + //now we have a modifier, and a key, add them to the canvas + GuiCanvas *root = getRoot(); + if (root) + root->addAcceleratorKey(this, 0, accelEvent.eventCode, accelEvent.flags); +} + +void GuiControl::acceleratorKeyPress(U32 index) +{ + TORQUE_UNUSED(index); + onAction(); +} + +void GuiControl::acceleratorKeyRelease(U32 index) +{ + TORQUE_UNUSED(index); + //do nothing +} + +bool GuiControl::onKeyDown(const GuiEvent &event) +{ + //pass the event to the parent + GuiControl *parent = getParent(); + if (parent) + return parent->onKeyDown(event); + else + return false; +} + +bool GuiControl::onKeyRepeat(const GuiEvent &event) +{ + // default to just another key down. + return onKeyDown(event); +} + +bool GuiControl::onKeyUp(const GuiEvent &event) +{ + //pass the event to the parent + GuiControl *parent = getParent(); + if (parent) + return parent->onKeyUp(event); + else + return false; +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiControl::onAction() +{ + if (! mActive) + return; + + //execute the console command + if (mConsoleCommand && mConsoleCommand[0]) + { + execConsoleCallback(); + } + else + Con::executef(this, "onAction"); +} + +void GuiControl::onMessage(GuiControl *sender, S32 msg) +{ + TORQUE_UNUSED(sender); + TORQUE_UNUSED(msg); +} + +void GuiControl::messageSiblings(S32 message) +{ + GuiControl *parent = getParent(); + if (! parent) return; + GuiControl::iterator i; + for(i = parent->begin(); i != parent->end(); i++) + { + GuiControl *ctrl = dynamic_cast(*i); + if (ctrl != this) + ctrl->onMessage(this, message); + } +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // + +void GuiControl::onDialogPush() +{ + // Notify Script. + if( isMethod("onDialogPush") ) + Con::executef(this, "onDialogPush"); + +} + +void GuiControl::onDialogPop() +{ + // Notify Script. + if( isMethod("onDialogPop") ) + Con::executef(this, "onDialogPop"); +} + +//------------------------------------------------------------------------------ +void GuiControl::setVisible(bool value) +{ + mVisible = value; + iterator i; + setUpdate(); + for(i = begin(); i != end(); i++) + { + GuiControl *ctrl = static_cast(*i); + ctrl->clearFirstResponder(); + } + + GuiControl *parent = getParent(); + if (parent) + parent->childResized(this); +} + + +void GuiControl::makeFirstResponder(bool value) +{ + if ( value ) + //setFirstResponder(this); + setFirstResponder(); + else + clearFirstResponder(); +} + +void GuiControl::setActive( bool value ) +{ + mActive = value; + + if ( !mActive ) + clearFirstResponder(); + + if ( mVisible && mAwake ) + setUpdate(); +} + +void GuiControl::getScrollLineSizes(U32 *rowHeight, U32 *columnWidth) +{ + // default to 10 pixels in y, 30 pixels in x + *columnWidth = 30; + *rowHeight = 30; +} + +void GuiControl::renderJustifiedText(Point2I offset, Point2I extent, const char *text) +{ + GFont *font = mProfile->mFont; + S32 textWidth = font->getStrWidthPrecise((const UTF8*)text); + Point2I start; + + // align the horizontal + switch( mProfile->mAlignment ) + { + case GuiControlProfile::RightJustify: + start.set( extent.x - textWidth, 0 ); + break; + case GuiControlProfile::CenterJustify: + start.set( ( extent.x - textWidth) / 2, 0 ); + break; + default: + // GuiControlProfile::LeftJustify + start.set( 0, 0 ); + break; + } + + // If the text is longer then the box size, (it'll get clipped) so + // force Left Justify + + if( textWidth > extent.x ) + start.set( 0, 0 ); + + // center the vertical + if(font->getHeight() > extent.y) + start.y = 0 - ((font->getHeight() - extent.y) / 2) ; + else + start.y = ( extent.y - font->getHeight() ) / 2; + + GFX->getDrawUtil()->drawText( font, start + offset, text, mProfile->mFontColors ); +} + +U32 GuiControl::clipText( String &text, U32 clipWidth ) const +{ + PROFILE_SCOPE( GuiControl_clipText ); + + U32 textWidth = mProfile->mFont->getStrWidthPrecise( text ); + + if ( textWidth <= clipWidth ) + return textWidth; + + // Start removing characters from the end of the string + // until the string width plus the elipsesWidth is less + // than clipWidth... + + // Note this would be more efficient without calling + // getStrWidthPrecise each loop iteration. eg. get the + // length of each char, store in a temporary U32 array, + // and then remove the number we need from the end all at once. + + String temp; + + while ( text.isNotEmpty() ) + { + text.erase( text.length() - 1, 1 ); + temp = text; + temp += "..."; + textWidth = mProfile->mFont->getStrWidthPrecise( temp ); + + if ( textWidth <= clipWidth ) + { + text = temp; + return textWidth; + } + } + + // Uh, not even the ellipses will fit in the passed width. + // Text should be an ellipses string now, + // which is the right thing to do in this case. + + return 0; +} + +void GuiControl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) +{ +#ifdef _XBOX + return; +#endif + + TORQUE_UNUSED(lastGuiEvent); + + if( !getRoot() ) + return; + + if(getRoot()->mCursorChanged != -1 && !isMouseLocked()) + { + // We've already changed the cursor, + // so set it back before we change it again. + + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); + PlatformCursorController *pController = pWindow->getCursorController(); + AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); + + pController->popCursor(); + + // We haven't changed it + getRoot()->mCursorChanged = -1; + } +} + +const String& GuiControl::getScriptFile() const +{ + return mScriptFile; +} + +void GuiControl::setScriptFile( const String& filename ) +{ + mScriptFile = filename; +} + +const char* GuiControl::execConsoleCallback() +{ + if (mConsoleCommand && mConsoleCommand[0]) + { + Con::setVariable("$ThisControl", avar("%d",getId())); + return Con::evaluate(mConsoleCommand, false); + } + return ""; +} + +const char* GuiControl::execAltConsoleCallback() +{ + if(mAltConsoleCommand && mAltConsoleCommand[0]) + { + Con::setVariable("$ThisControl", avar("%d",getId())); + return Con::evaluate(mAltConsoleCommand, false); + } + return ""; +} + +bool GuiControl::onGamepadButtonDown(const GuiEvent &event) +{ + return onKeyDown(event); +} + +bool GuiControl::onGamepadButtonUp(const GuiEvent &event) +{ + return onKeyUp(event); +} + +bool GuiControl::onGamepadAxisUp(const GuiEvent &event) +{ + //pass the event to the parent + GuiControl *parent = getParent(); + if (parent) + { + return parent->onGamepadAxisUp(event); + } + else + { + return false; + } +} + +bool GuiControl::onGamepadAxisDown(const GuiEvent &event) +{ + //pass the event to the parent + GuiControl *parent = getParent(); + if (parent) + { + return parent->onGamepadAxisDown(event); + } + else + { + return false; + } +} + +bool GuiControl::onGamepadAxisLeft(const GuiEvent &event) +{ + //pass the event to the parent + GuiControl *parent = getParent(); + if (parent) + { + return parent->onGamepadAxisLeft(event); + } + else + { + return false; + } +} + +bool GuiControl::onGamepadAxisRight(const GuiEvent &event) +{ + //pass the event to the parent + GuiControl *parent = getParent(); + if (parent) + { + return parent->onGamepadAxisRight(event); + } + else + { + return false; + } +} + +bool GuiControl::onGamepadTrigger(const GuiEvent &event) +{ + //pass the event to the parent + GuiControl *parent = getParent(); + if (parent) + { + return parent->onGamepadTrigger(event); + } + else + { + return false; + } +} + +ConsoleMethod( GuiControl, findHitControl, S32, 4, 4, "returns the Id of the control at the point") +{ + Point2I pos(dAtoi(argv[2]), dAtoi(argv[3])); + GuiControl *hit = object->findHitControl(pos); + return hit ? hit->getId() : 0; +} + +ConsoleMethod( GuiControl, findHitControls, const char*, 6, 6, "( x, y, width, height ) - Return the IDs of all controls that intersect with the given global bounds." ) +{ + // Find hit controls. + + RectI bounds( dAtoi( argv[ 2 ] ), dAtoi( argv[ 3 ] ), dAtoi( argv[ 4 ] ), dAtoi( argv[ 5 ] ) ); + Vector< GuiControl* > controls; + + if( !object->findHitControls( bounds, controls ) ) + return ""; + + // Create vector string. + + bool isFirst = true; + StringBuilder str; + for( U32 i = 0, num = controls.size(); i < num; ++ i ) + { + if( !isFirst ) + str.append( ' ' ); + + str.append( controls[ i ]->getIdString() ); + isFirst = false; + } + String s = str.end(); + + // Return result. + + char* buffer = Con::getReturnBuffer( s.size() ); + dStrcpy( buffer, s.c_str() ); + + return buffer; +} + +ConsoleMethod(GuiControl, controlIsChild, bool, 3, 3, "returns true if the given control is a child of this control") +{ + GuiControl *ctrl = dynamic_cast(Sim::findObject(argv[2])); + if( ctrl ) + return object->ControlIsChild(ctrl); + else + return false; +} + +ConsoleMethod(GuiControl, isFirstResponder, bool, 2, 2, "returns true if this control is the first responder") +{ + return object->isFirstResponder(); +} + +ConsoleMethod(GuiControl, setFirstResponder, void , 2, 2, "Sets this control as the first responder") +{ + object->setFirstResponder(); +} + +ConsoleMethod(GuiControl, getFirstResponder, S32 , 2, 2, "Get the first responder this control is aware of") +{ + if( object->mFirstResponder ) + return object->mFirstResponder->getId(); + else + return -1; +} + +ConsoleMethod(GuiControl, clearFirstResponder, void , 2, 3, "Clear this control from being the first responder") +{ + if( argc == 3 ) + { + object->clearFirstResponder(dAtob(argv[2])); + } + else + { + object->clearFirstResponder(); + } +} + +ConsoleMethod( GuiControl, getScriptFile, const char*, 2, 2, "Gets the script file the control was created in") +{ + const String& filename = object->getScriptFile(); + + const U32 size = filename.size(); + char* buffer = Con::getReturnBuffer( size ); + dMemcpy( buffer, filename.c_str(), size ); + + return buffer; +} + +ConsoleMethod( GuiControl, setScriptFile, void, 3, 3, "Associates the control with a script file." ) +{ + object->setScriptFile( argv[ 2 ] ); +} + +ConsoleMethod( GuiControl, setCanSave, void, 3,3,"Sets whether this control can serialize itself to the hard disk") +{ + object->setCanSave( dAtob( argv[2] ) ); +} + + +ConsoleMethod(GuiControl, pointInControl, bool, 4,4,"returns true if the point is in the control, point is in parent coords") +{ + Point2I kPoint(dAtoi(argv[2]), dAtoi(argv[3])); + return object->pointInControl(kPoint); +} + + +ConsoleMethod( GuiControl, addGuiControl, void, 3, 3, "S32 controlId") +{ + + GuiControl *ctrl = dynamic_cast(Sim::findObject(argv[2])); + if(ctrl) + { + object->addObject(ctrl); + } + +} + +ConsoleMethod(GuiControl, reorderChild, void, 4,4," (child1, child2) uses simset reorder to push child 1 after child 2 - both must already be child controls of this control") +{ + GuiControl* pControl = dynamic_cast(Sim::findObject(dAtoi(argv[2]))); + GuiControl* pTarget = dynamic_cast(Sim::findObject(dAtoi(argv[3]))); + + if(pControl && pTarget) + { + object->reOrder(pControl,pTarget); + } +} + +ConsoleMethod( GuiControl, getRoot, S32, 2, 2, "returns the Id of the parent canvas.") +{ + GuiCanvas *root = object->getRoot(); + if(root) + return root->getId(); + return 0; +} + +ConsoleMethod( GuiControl, getParent, S32, 2, 2, "returns the Id of the parent control") +{ + + GuiControl* pParent = object->getParent(); + if(pParent) + { + return pParent->getId(); + } + + return 0; + +} + +ConsoleMethod( GuiControl, setValue, void, 3, 3, "(string value)") +{ + object->setScriptValue(argv[2]); +} + +ConsoleMethod( GuiControl, getValue, const char*, 2, 2, "") +{ + return object->getScriptValue(); +} + +ConsoleMethod( GuiControl, setActive, void, 3, 3, "(bool active)") +{ + object->setActive(dAtob(argv[2])); +} + +ConsoleMethod( GuiControl, isActive, bool, 2, 2, "") +{ + return object->isActive(); +} + +ConsoleMethod( GuiControl, setVisible, void, 3, 3, "(bool visible)") +{ + object->setVisible(dAtob(argv[2])); +} + +ConsoleMethod( GuiControl, makeFirstResponder, void, 3, 3, "(bool isFirst)") +{ + object->makeFirstResponder(dAtob(argv[2])); +} + +ConsoleMethod( GuiControl, isVisible, bool, 2, 2, "") +{ + return object->isVisible(); +} + +ConsoleMethod( GuiControl, isAwake, bool, 2, 2, "") +{ + return object->isAwake(); +} + +ConsoleMethod( GuiControl, setProfile, void, 3, 3, "(GuiControlProfile p)") +{ + GuiControlProfile * profile; + + if(Sim::findObject(argv[2], profile)) + object->setControlProfile(profile); +} + +ConsoleMethod( GuiControl, resize, void, 6, 6, "(int x, int y, int w, int h)") +{ + Point2I newPos(dAtoi(argv[2]), dAtoi(argv[3])); + Point2I newExt(dAtoi(argv[4]), dAtoi(argv[5])); + object->resize(newPos, newExt); +} + +ConsoleMethod( GuiControl, getPosition, const char*, 2, 2, "") +{ + char *retBuffer = Con::getReturnBuffer(64); + const Point2I &pos = object->getPosition(); + dSprintf(retBuffer, 64, "%d %d", pos.x, pos.y); + return retBuffer; +} +ConsoleMethod( GuiControl, getCenter, const char*, 2, 2, " returns center of control, as space seperated ints") +{ + char *retBuffer = Con::getReturnBuffer(64); + const Point2I pos = object->getPosition(); + const Point2I ext = object->getExtent(); + Point2I center(pos.x + ext.x/2, pos.y + ext.y/2); + dSprintf(retBuffer, 64, "%d %d", center.x, center.y); + return retBuffer; +} + +ConsoleMethod( GuiControl, setCenter, void, 4, 4, " sets control position, by center - coords are local not global") +{ + const Point2I ext = object->getExtent(); + Point2I newpos(dAtoi(argv[2])-ext.x/2, dAtoi(argv[3])-ext.y/2); + object->setPosition(newpos); +} + +ConsoleMethod( GuiControl, getGlobalCenter, const char*, 2, 2, " returns center of control, as space seperated ints") +{ + char *retBuffer = Con::getReturnBuffer(64); + const Point2I tl(0,0); + Point2I pos = object->localToGlobalCoord(tl); + const Point2I ext = object->getExtent(); + Point2I center(pos.x + ext.x/2, pos.y + ext.y/2); + dSprintf(retBuffer, 64, "%d %d", center.x, center.y); + return retBuffer; +} + +ConsoleMethod( GuiControl, getGlobalPosition, const char*, 2, 2, "") +{ + char *retBuffer = Con::getReturnBuffer(64); + const Point2I pos(0,0); + Point2I gPos = object->localToGlobalCoord(pos); + + dSprintf(retBuffer, 64, "%d %d", gPos.x, gPos.y); + return retBuffer; +} +ConsoleMethod( GuiControl, setPositionGlobal, void, 4, 4, "int x,y in global screen space") +{ + //see if we can turn the x/y into ints directly, + Point2I gPos(dAtoi(argv[2]), dAtoi(argv[3])); + Point2I lPosOffset = object->globalToLocalCoord(gPos); + + lPosOffset += object->getPosition(); + + object->setPosition(lPosOffset); +} + +ConsoleMethod( GuiControl, setPosition, void, 4, 4, "int x,y in local space") +{ + //see if we can turn the x/y into ints directly, + Point2I lPos(dAtoi(argv[2]), dAtoi(argv[3])); + object->setPosition(lPos); +} + +ConsoleMethod( GuiControl, getExtent, const char*, 2, 2, "Get the width and height of the control.") +{ + char *retBuffer = Con::getReturnBuffer(64); + const Point2I &ext = object->getExtent(); + dSprintf(retBuffer, 64, "%d %d", ext.x, ext.y); + return retBuffer; +} + +ConsoleMethod( GuiControl, setExtent, void, 3, 4, "(\"x y\") or (x,y) Sets the width & height of the control.") +{ + if ( argc == 3 ) + { + // We scan for floats because its possible that math + // done on the extent can result in fractional values. + Point2F ext; + if ( dSscanf( argv[2], "%g %g", &ext.x, &ext.y ) == 2 ) + object->setExtent( (S32)ext.x, (S32)ext.y ); + else + Con::errorf( "GuiControl::setExtent, not enough parameters!" ); + } + else if ( argc == 4 ) + object->setExtent( dAtoi(argv[2]), dAtoi(argv[3]) ); +} + +ConsoleMethod( GuiControl, getMinExtent, const char*, 2, 2, "Get the minimum allowed size of the control.") +{ + char *retBuffer = Con::getReturnBuffer(64); + Point2I minExt = object->getMinExtent(); + dSprintf(retBuffer, 64, "%d %d", minExt.x, minExt.y); + return retBuffer; +} + +ConsoleMethod( GuiControl, getAspect, F32, 2, 2, "Returns the width divided by the height of the control.") +{ + const Point2I &ext = object->getExtent(); + return (F32)ext.x / (F32)ext.y; +} diff --git a/gui/core/guiControl.h b/gui/core/guiControl.h new file mode 100644 index 0000000..10406c6 --- /dev/null +++ b/gui/core/guiControl.h @@ -0,0 +1,760 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUICONTROL_H_ +#define _GUICONTROL_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif +#ifndef _UTIL_DELEGATE_H_ +#include "core/util/delegate.h" +#endif +#ifndef _LANG_H_ +#include "i18n/lang.h" +#endif + +class GuiCanvas; +class GuiEditCtrl; +class GuiWindowCollapseCtrl; + + +/// A delegate used in tool tip rendering. +/// +/// @param hoverPos position to display the tip near +/// @param cursorPos the actual position of the cursor when the delegate is called +/// @param tipText optional alternate tip to be rendered +/// @return Returns true if the tooltip was rendered. +/// +/// @see GuiControl::mRenderTooltipDelegate +typedef Delegate RenderTooltipDelegate; + +/// @defgroup gui_group Gui System +/// The GUI system in Torque provides a powerful way of creating +/// WYSIWYG User Interfaces for your Game or Application written +/// in Torque. +/// +/// The GUI Provides a range of different controls that you may use +/// to arrange and layout your GUI's, including Buttons, Lists, Bitmaps +/// Windows, Containers, and HUD elements. +/// +/// The Base Control Class GuiControl provides a basis upon which to +/// write GuiControl's that may be specific to your particular type +/// of game. + + +/// @addtogroup gui_core_group Core +/// @section GuiControl_Intro Introduction +/// +/// GuiControl is the base class for GUI controls in Torque. It provides these +/// basic areas of functionality: +/// - Inherits from SimGroup, so that controls can have children. +/// - Interfacing with a GuiControlProfile. +/// - An abstraction from the details of handling user input +/// and so forth, providing friendly hooks like onMouseEnter(), onMouseMove(), +/// and onMouseLeave(), onKeyDown(), and so forth. +/// - An abstraction from the details of rendering and resizing. +/// - Helper functions to manipulate the mouse (mouseLock and +/// mouseUnlock), and convert coordinates (localToGlobalCoord() and +/// globalToLocalCoord()). +/// +/// @ref GUI has an overview of the GUI system. +/// +/// +/// @ingroup gui_group Gui System +/// @{ +class GuiControl : public SimGroup +{ +private: + typedef SimGroup Parent; + + String mScriptFile; ///< The script file this GUI was created from + SimGroup *mAddGroup; ///< The internal name of a SimGroup child of the global GuiGroup in which to organize this gui on creation + RectI mBounds; ///< The internal bounds of this control + +protected: + GuiControlProfile* mProfile; ///< The profile for this gui (data settings that are likely to be shared by multiple guis) + GuiControlProfile* mTooltipProfile; ///< The profile for any tooltips + +public: + + /// Additional write flags for GuiControls. + enum + { + IgnoreCanSave = BIT( 30 ), ///< Write out even if mCanSave=false. + NoCheckParentCanSave = BIT( 31 ), ///< Don't inherit mCanSave=false from parents. + }; + + /// @name Control State + /// @{ + static bool setProfileProt( void *obj, const char *data ); + static bool setTooltipProfileProt( void *obj, const char *data ); + + S32 mTipHoverTime; + + /// Delegate called to render a tooltip for this control. + /// By default this will be set to defaultTooltipRender. + RenderTooltipDelegate mRenderTooltipDelegate; + + /// The default tooltip rendering function. + /// @see RenderTooltipDelegate + bool defaultTooltipRender( const Point2I &hoverPos, const Point2I &cursorPos, const char* tipText = NULL ); + + bool mVisible; + bool mActive; + bool mAwake; + bool mSetFirstResponder; + bool mCanSave; + bool mIsContainer; ///< if true, then the GuiEditor can drag other controls into this one. + bool mCanResize; + bool mCanHit; + + S32 mLayer; + Point2I mMinExtent; + StringTableEntry mLangTableName; + LangTable *mLangTable; + + bool mNotifyChildrenResized; + + // Contains array of windows located inside GuiControl + typedef Vector< Vector< GuiWindowCollapseCtrl *> > CollapseGroupVec; + CollapseGroupVec mCollapseGroupVec; + + static bool smDesignTime; ///< static GuiControl boolean that specifies if the GUI Editor is active + /// @} + + /// @name Design Time Editor Access + /// @{ + static GuiEditCtrl *smEditorHandle; ///< static GuiEditCtrl pointer that gives controls access to editor-NULL if editor is closed + /// @} + + /// @name Keyboard Input + /// @{ + GuiControl *mFirstResponder; + static GuiControl *smPrevResponder; + static GuiControl *smCurResponder; + /// @} + + enum horizSizingOptions + { + horizResizeRight = 0, ///< fixed on the left and width + horizResizeWidth, ///< fixed on the left and right + horizResizeLeft, ///< fixed on the right and width + horizResizeCenter, + horizResizeRelative, ///< resize relative + horizResizeWindowRelative ///< resize window relative + }; + enum vertSizingOptions + { + vertResizeBottom = 0, ///< fixed on the top and in height + vertResizeHeight, ///< fixed on the top and bottom + vertResizeTop, ///< fixed in height and on the bottom + vertResizeCenter, + vertResizeRelative, ///< resize relative + vertResizeWindowRelative ///< resize window relative + }; + +protected: + /// @name Control State + /// @{ + + S32 mHorizSizing; ///< Set from horizSizingOptions. + S32 mVertSizing; ///< Set from vertSizingOptions. + + StringTableEntry mConsoleVariable; + StringTableEntry mConsoleCommand; + StringTableEntry mAltConsoleCommand; + StringTableEntry mAcceleratorKey; + + StringTableEntry mTooltip; + + /// @} + + /// @name Console + /// The console variable collection of functions allows a console variable to be bound to the GUI control. + /// + /// This allows, say, an edit field to be bound to '$foo'. The value of the console + /// variable '$foo' would then be equal to the text inside the text field. Changing + /// either changes the other. + /// @{ + + /// Sets the value of the console variable bound to this control + /// @param value String value to assign to control's console variable + void setVariable(const char *value); + + /// Sets the value of the console variable bound to this control + /// @param value Integer value to assign to control's console variable + void setIntVariable(S32 value); + + /// Sets the value of the console variable bound to this control + /// @param value Float value to assign to control's console variable + void setFloatVariable(F32 value); + + const char* getVariable(); ///< Returns value of control's bound variable as a string + S32 getIntVariable(); ///< Returns value of control's bound variable as a integer + F32 getFloatVariable(); ///< Returns value of control's bound variable as a float + +protected: + GFXStateBlockRef mDefaultGuiSB; + +public: + /// Set the name of the console variable which this GuiObject is bound to + /// @param variable Variable name + void setConsoleVariable(const char *variable); + + /// Set the name of the console function bound to, such as a script function + /// a button calls when clicked. + /// @param newCmd Console function to attach to this GuiControl + void setConsoleCommand(const char *newCmd); + const char * getConsoleCommand(); ///< Returns the name of the function bound to this GuiControl + LangTable *getGUILangTable(void); + const UTF8 *getGUIString(S32 id); + + /// @} +protected: + /// @name Callbacks + /// @{ + /// Executes a console command, and returns the result. + /// + /// The global console variable $ThisControl is set to the id of the calling + /// control. WARNING: because multiple controls may set $ThisControl, at any time, + /// the value of $ThisControl should be stored in a local variable by the + /// callback code. The use of the $ThisControl variable is not thread safe. + + /// Executes mConsoleCommand, and returns the result. + const char* execConsoleCallback(); + /// Executes mAltConsoleCommand, and returns the result. + const char* execAltConsoleCallback(); + /// @} +public: + + /// @name Editor + /// These functions are used by the GUI Editor + /// @{ + + /// Sets the size of the GuiControl + /// @param horz Width of the control + /// @param vert Height of the control + void setSizing(S32 horz, S32 vert); + + /// Overrides Parent Serialization to allow specific controls to not be saved (Dynamic Controls, etc) + void write(Stream &stream, U32 tabStop, U32 flags); + /// Returns boolean specifying if a control can be serialized + bool getCanSave(); + /// Set serialization flag + void setCanSave(bool bCanSave); + /// Returns boolean as to whether any parent of this control has the 'no serialization' flag set. + bool getCanSaveParent(); + + /// Returns the name of the script file the gui was created in, so that we can save it again to the same file. + const String& getScriptFile() const; + + /// Associates the gui with a script file. + void setScriptFile( const String& filename ); + + /// @} + + /// @name Initialization + /// @{ + + DECLARE_CONOBJECT(GuiControl); + DECLARE_CATEGORY( "Gui Core" ); + + GuiControl(); + virtual ~GuiControl(); + static void initPersistFields(); + virtual bool processArguments(S32 argc, const char **argv); + + /// @} + + /// @name Accessors + /// @{ + + inline const Point2I& getPosition() const { return mBounds.point; } ///< Returns position of the control + inline const Point2I& getExtent() const { return mBounds.extent; } ///< Returns extents of the control + inline const RectI getBounds()const { return mBounds; } ///< Returns the bounds of the control + inline const RectI getGlobalBounds() ///< Returns the bounds of this object, in global coordinates + { + RectI retRect = getBounds(); + retRect.point = localToGlobalCoord( Point2I(0,0) ); + + return retRect; + }; + virtual Point2I getMinExtent() const { return mMinExtent; } ///< Returns minimum size the control can be + virtual void setMinExtent( const Point2I &newMinExtent ) { mMinExtent = newMinExtent; }; + inline const S32 getLeft() const { return mBounds.point.x; } ///< Returns the X position of the control + inline const S32 getTop() const { return mBounds.point.y; } ///< Returns the Y position of the control + inline const S32 getWidth() const { return mBounds.extent.x; } ///< Returns the width of the control + inline const S32 getHeight() const { return mBounds.extent.y; } ///< Returns the height of the control + + inline const S32 getHorizSizing() const { return mHorizSizing; } + inline const S32 getVertSizing() const { return mVertSizing; } + + /// @} + + /// @name Flags + /// @{ + + /// Sets the visibility of the control + /// @param value True if object should be visible + static bool setVisible( void* obj, const char* data ) { static_cast(obj)->setVisible( dAtob( data ) ); return false; }; + virtual void setVisible(bool value); + inline bool isVisible() const { return mVisible; } ///< Returns true if the object is visible + virtual bool isHidden() const { return !isVisible(); } + virtual void setHidden( bool state ) { setVisible( !state ); } + + void setCanHit( bool value ) { mCanHit = value; } + + /// Sets the status of this control as active and responding or inactive + /// @param value True if this is active + virtual void setActive(bool value); + bool isActive() { return mActive; } ///< Returns true if this control is active + + bool isAwake() { return mAwake; } ///< Returns true if this control is awake + + /// @} + + /// Get information about the size of a scroll line. + /// + /// @param rowHeight The height, in pixels, of a row + /// @param columnWidth The width, in pixels, of a column + virtual void getScrollLineSizes(U32 *rowHeight, U32 *columnWidth); + + /// Get information about the cursor. + /// @param cursor Cursor information will be stored here + /// @param showCursor Will be set to true if the cursor is visible + /// @param lastGuiEvent GuiEvent containing cursor position and modifier keys (ie ctrl, shift, alt etc) + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + + /// @name Children + /// @{ + + /// Adds an object as a child of this object. + /// @param obj New child object of this control + void addObject(SimObject *obj); + + /// Removes a child object from this control. + /// @param obj Object to remove from this control + void removeObject(SimObject *obj); + + GuiControl *getParent(); ///< Returns the control which owns this one. + GuiCanvas *getRoot(); ///< Returns the root canvas of this control. + /// @} + + /// @name Coordinates + /// @{ + + /// Translates local coordinates (wrt this object) into global coordinates + /// + /// @param src Local coordinates to translate + Point2I localToGlobalCoord(const Point2I &src); + + /// Returns global coordinates translated into local space + /// + /// @param src Global coordinates to translate + Point2I globalToLocalCoord(const Point2I &src); + /// @} + + /// @name Resizing + /// @{ + + /// Changes the size and/or position of this control + /// @param newPosition New position of this control + /// @param newExtent New size of this control + virtual bool resize(const Point2I &newPosition, const Point2I &newExtent); + + /// Changes the position of this control + /// @param newPosition New position of this control + virtual bool setPosition( const Point2I &newPosition ); + inline void setPosition( const S32 x, const S32 y ) { setPosition(Point2I(x,y)); } + + /// Changes the size of this control + /// @param newExtent New size of this control + virtual bool setExtent( const Point2I &newExtent ); + inline void setExtent( const S32 width, const S32 height) { setExtent(Point2I(width, height)); } + +public: + /// Changes the bounds of this control + /// @param newBounds New bounds of this control + virtual bool setBounds( const RectI &newBounds ); + inline void setBounds( const S32 left, const S32 top, + const S32 width, const S32 height) { setBounds(RectI(left, top, width, height)); } + + /// Changes the X position of this control + /// @param newXPosition New X Position of this control + virtual void setLeft( S32 newLeft ); + + /// Changes the Y position of this control + /// @param newYPosition New Y Position of this control + virtual void setTop( S32 newTop ); + + /// Changes the width of this control + /// @param newWidth New width of this control + virtual void setWidth( S32 newWidth ); + + /// Changes the height of this control + /// @param newHeight New Height of this control + virtual void setHeight( S32 newHeight ); + + /// Called when a child control of the object is resized + /// @param child Child object + virtual void childResized(GuiControl *child); + + /// Called when this objects parent is resized + /// @param oldParentRect The old rectangle of the parent object + /// @param newParentRect The new rectangle of the parent object + virtual void parentResized(const RectI &oldParentRect, const RectI &newParentRect); + /// @} + + /// @name Rendering + /// @{ + + /// Called when this control is to render itself + /// @param offset The location this control is to begin rendering + /// @param updateRect The screen area this control has drawing access to + virtual void onRender(Point2I offset, const RectI &updateRect); + + /// Called when this control should render its children + /// @param offset The location this control is to begin rendering + /// @param updateRect The screen area this control has drawing access to + void renderChildControls(Point2I offset, const RectI &updateRect); + + /// Sets the area (local coordinates) this control wants refreshed each frame + /// @param pos UpperLeft point on rectangle of refresh area + /// @param ext Extent of update rect + void setUpdateRegion(Point2I pos, Point2I ext); + + /// Sets the update area of the control to encompass the whole control + virtual void setUpdate(); + /// @} + + //child hierarchy calls + void awaken(); ///< Called when this control and its children have been wired up. + void sleep(); ///< Called when this control is no more. + void preRender(); ///< Pre-render this control and all its children. + + /// @name Events + /// + /// If you subclass these, make sure to call the Parent::'s versions. + /// + /// @{ + + /// Called when this object is asked to wake up returns true if it's actually awake at the end + virtual bool onWake(); + + /// Called when this object is asked to sleep + virtual void onSleep(); + + /// Do special pre-render processing + virtual void onPreRender(); + + /// Called when this object is removed + virtual void onRemove(); + + /// Called when one of this objects children is removed + virtual void onChildRemoved( GuiControl *child ); + + /// Called when this object is added to the scene + bool onAdd(); + + /// Called when the mProfile or mToolTipProfile is deleted + virtual void onDeleteNotify(SimObject *object); + + /// Called when this object has a new child + virtual void onChildAdded( GuiControl *child ); + + /// @} + + /// @name Console + /// @{ + + /// Returns the value of the variable bound to this object + virtual const char *getScriptValue(); + + /// Sets the value of the variable bound to this object + virtual void setScriptValue(const char *value); + /// @} + + /// @name Input (Keyboard/Mouse) + /// @{ + + /// This function will return true if the provided coordinates (wrt parent object) are + /// within the bounds of this control + /// @param parentCoordPoint Coordinates to test + virtual bool pointInControl(const Point2I& parentCoordPoint); + + /// Returns true if the global cursor is inside this control + bool cursorInControl(); + + /// Returns the control which the provided point is under, with layering + /// @param pt Point to test + /// @param initialLayer Layer of gui objects to begin the search + virtual GuiControl* findHitControl(const Point2I &pt, S32 initialLayer = -1 ); + + enum EHitTestFlags + { + HIT_FullBoxOnly = BIT( 0 ), ///< Hit only counts if all of a control's bounds are within the hit rectangle. + HIT_ParentPreventsChildHit = BIT( 1 ), ///< A positive hit test on a parent control will prevent hit tests on children. + HIT_AddParentHits = BIT( 2 ), ///< Parent's that get hit should be added regardless of whether any of their children get hit, too. + HIT_NoCanHitNoRecurse = BIT( 3 ), ///< A hit-disabled control will not recurse into children. + }; + + /// + virtual bool findHitControls( const RectI& rect, Vector< GuiControl* >& outResult, U32 flags = 0, S32 initialLayer = -1, U32 depth = 0 ); + + /// Lock the mouse within the provided control + /// @param lockingControl Control to lock the mouse within + void mouseLock(GuiControl *lockingControl); + + /// Turn on mouse locking with last used lock control + void mouseLock(); + + /// Unlock the mouse + void mouseUnlock(); + + /// Returns true if the mouse is locked + bool isMouseLocked(); + /// @} + + + /// General input handler. + virtual bool onInputEvent(const InputEventInfo &event); + + /// @name Mouse Events + /// These functions are called when the input event which is + /// in the name of the function occurs. + /// @{ + virtual void onMouseUp(const GuiEvent &event); + virtual void onMouseDown(const GuiEvent &event); + virtual void onMouseMove(const GuiEvent &event); + virtual void onMouseDragged(const GuiEvent &event); + virtual void onMouseEnter(const GuiEvent &event); + virtual void onMouseLeave(const GuiEvent &event); + + virtual bool onMouseWheelUp(const GuiEvent &event); + virtual bool onMouseWheelDown(const GuiEvent &event); + + virtual void onRightMouseDown(const GuiEvent &event); + virtual void onRightMouseUp(const GuiEvent &event); + virtual void onRightMouseDragged(const GuiEvent &event); + + virtual void onMiddleMouseDown(const GuiEvent &event); + virtual void onMiddleMouseUp(const GuiEvent &event); + virtual void onMiddleMouseDragged(const GuiEvent &event); + /// @} + + /// @name Gamepad Events + /// These functions are called when the input event which is in the name of + /// the function occurs. + /// @{ + virtual bool onGamepadButtonDown(const GuiEvent &event); ///< Default behavior is call-through to onKeyDown + virtual bool onGamepadButtonUp(const GuiEvent &event); ///< Default behavior is call-through to onKeyUp + virtual bool onGamepadAxisUp(const GuiEvent &event); + virtual bool onGamepadAxisDown(const GuiEvent &event); + virtual bool onGamepadAxisLeft(const GuiEvent &event); + virtual bool onGamepadAxisRight(const GuiEvent &event); + virtual bool onGamepadTrigger(const GuiEvent &event); + /// @} + + /// @name Editor Mouse Events + /// + /// These functions are called when the input event which is + /// in the name of the function occurs. Conversely from normal + /// mouse events, these have a boolean return value that, if + /// they return true, the editor will NOT act on them or be able + /// to respond to this particular event. + /// + /// This is particularly useful for when writing controls so that + /// they may become aware of the editor and allow customization + /// of their data or appearance as if they were actually in use. + /// For example, the GuiTabBookCtrl catches on mouse down to select + /// a tab and NOT let the editor do any instant group manipulation. + /// + /// @{ + + /// Called when a mouseDown event occurs on a control and the GUI editor is active + /// @param event the GuiEvent which caused the call to this function + /// @param offset the offset which is representative of the units x and y that the editor takes up on screen + virtual bool onMouseDownEditor(const GuiEvent &event, Point2I offset) { return false; }; + + /// Called when a mouseUp event occurs on a control and the GUI editor is active + /// @param event the GuiEvent which caused the call to this function + /// @param offset the offset which is representative of the units x and y that the editor takes up on screen + virtual bool onMouseUpEditor(const GuiEvent &event, Point2I offset) { return false; }; + + /// Called when a rightMouseDown event occurs on a control and the GUI editor is active + /// @param event the GuiEvent which caused the call to this function + /// @param offset the offset which is representative of the units x and y that the editor takes up on screen + virtual bool onRightMouseDownEditor(const GuiEvent &event, Point2I offset) { return false; }; + + /// Called when a mouseDragged event occurs on a control and the GUI editor is active + /// @param event the GuiEvent which caused the call to this function + /// @param offset the offset which is representative of the units x and y that the editor takes up on screen + virtual bool onMouseDraggedEditor(const GuiEvent &event, Point2I offset) { return false; }; + + /// @} + + /// @name Tabs + /// @{ + + /// Find the first tab-accessible child of this control + virtual GuiControl* findFirstTabable(); + + /// Find the last tab-accessible child of this control + /// @param firstCall Set to true to clear the global previous responder + virtual GuiControl* findLastTabable(bool firstCall = true); + + /// Find previous tab-accessible control with respect to the provided one + /// @param curResponder Current control + /// @param firstCall Set to true to clear the global previous responder + virtual GuiControl* findPrevTabable(GuiControl *curResponder, bool firstCall = true); + + /// Find next tab-accessible control with regards to the provided control. + /// + /// @param curResponder Current control + /// @param firstCall Set to true to clear the global current responder + virtual GuiControl* findNextTabable(GuiControl *curResponder, bool firstCall = true); + /// @} + + /// Returns true if the provided control is a child (grandchild, or great-grandchild) of this one. + /// + /// @param child Control to test + virtual bool ControlIsChild(GuiControl *child); + + /// @name First Responder + /// A first responder is the control which reacts first, in it's responder chain, to keyboard events + /// The responder chain is set for each parent and so there is only one first responder amongst it's + /// children. + /// @{ + + /// Sets the first responder for child controls + /// @param firstResponder First responder for this chain + virtual void setFirstResponder(GuiControl *firstResponder); + + /// Sets up this control to be the first in it's group to respond to an input event + /// @param value True if this should be a first responder + virtual void makeFirstResponder(bool value); + + /// Returns true if this control is a first responder + bool isFirstResponder(); + + /// Sets this object to be a first responder + virtual void setFirstResponder(); + + /// Clears the first responder for this chain + void clearFirstResponder(bool useCallback=false); + + /// Returns the first responder for this chain + GuiControl *getFirstResponder() { return mFirstResponder; } + + /// Occurs when the first responder for this chain is lost + virtual void onLoseFirstResponder(); + /// @} + + /// @name Keyboard Events + /// @{ + + /// Adds the accelerator key for this object to the canvas + void addAcceleratorKey(); + + /// Adds this control's accelerator key to the accelerator map, and + /// recursively tells all children to do the same. + virtual void buildAcceleratorMap(); + + /// Occurs when the accelerator key for this control is pressed + /// + /// @param index Index in the accelerator map of the key + virtual void acceleratorKeyPress(U32 index); + + /// Occurs when the accelerator key for this control is released + /// + /// @param index Index in the accelerator map of the key + virtual void acceleratorKeyRelease(U32 index); + + /// Happens when a key is depressed + /// @param event Event descriptor (which contains the key) + virtual bool onKeyDown(const GuiEvent &event); + + /// Happens when a key is released + /// @param event Event descriptor (which contains the key) + virtual bool onKeyUp(const GuiEvent &event); + + /// Happens when a key is held down, resulting in repeated keystrokes. + /// @param event Event descriptor (which contains the key) + virtual bool onKeyRepeat(const GuiEvent &event); + /// @} + + /// Returns our tooltip profile (and finds the profile if it hasn't been set yet) + GuiControlProfile* getTooltipProfile() { return mTooltipProfile; } + + /// Sets the tooltip profile for this control. + /// + /// @see GuiControlProfile + /// @param prof Tooltip profile to apply + void setTooltipProfile(GuiControlProfile *prof); + + /// Returns our profile (and finds the profile if it hasn't been set yet) + GuiControlProfile* getControlProfile() { return mProfile; } + + /// Sets the control profile for this control. + /// + /// @see GuiControlProfile + /// @param prof Control profile to apply + void setControlProfile(GuiControlProfile *prof); + + /// Occurs when this control performs its "action" + virtual void onAction(); + + /// @name Peer Messaging + /// Used to send a message to other GUIControls which are children of the same parent. + /// + /// This is mostly used by radio controls. + /// @{ + void messageSiblings(S32 message); ///< Send a message to all siblings + virtual void onMessage(GuiControl *sender, S32 msg); ///< Receive a message from another control + /// @} + + /// @name Canvas Events + /// Functions called by the canvas + /// @{ + + /// Called if this object is a dialog, when it is added to the visible layers + virtual void onDialogPush(); + + /// Called if this object is a dialog, when it is removed from the visible layers + virtual void onDialogPop(); + /// @} + + /// Renders justified text using the profile. + /// + /// @note This should move into the graphics library at some point + void renderJustifiedText(Point2I offset, Point2I extent, const char *text); + + /// Returns text clipped to fit within a pixel width. The clipping + /// occurs on the right side and "..." is appended. It returns width + /// of the final clipped text in pixels. + U32 clipText( String &inOutText, U32 width ) const; + + void inspectPostApply(); + void inspectPreApply(); +}; +/// @} + +#endif diff --git a/gui/core/guiDefaultControlRender.cpp b/gui/core/guiDefaultControlRender.cpp new file mode 100644 index 0000000..2cb5e6b --- /dev/null +++ b/gui/core/guiDefaultControlRender.cpp @@ -0,0 +1,530 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/core/guiDefaultControlRender.h" + +#include "gui/core/guiTypes.h" +#include "core/color.h" +#include "math/mRect.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" + + +static ColorI colorLightGray(192, 192, 192); +static ColorI colorGray(128, 128, 128); +static ColorI colorDarkGray(64, 64, 64); +static ColorI colorWhite(255,255,255); +static ColorI colorBlack(0,0,0); + +void renderRaisedBox( const RectI &bounds, GuiControlProfile *profile ) +{ + S32 l = bounds.point.x, r = bounds.point.x + bounds.extent.x - 1; + S32 t = bounds.point.y, b = bounds.point.y + bounds.extent.y - 1; + + GFX->getDrawUtil()->drawRectFill( bounds, profile->mFillColor); + GFX->getDrawUtil()->drawLine(l, t, l, b - 1, colorWhite); + GFX->getDrawUtil()->drawLine(l, t, r - 1, t, colorWhite); + + GFX->getDrawUtil()->drawLine(l, b, r, b, colorBlack); + GFX->getDrawUtil()->drawLine(r, b - 1, r, t, colorBlack); + + GFX->getDrawUtil()->drawLine(l + 1, b - 1, r - 1, b - 1, profile->mBorderColor); + GFX->getDrawUtil()->drawLine(r - 1, b - 2, r - 1, t + 1, profile->mBorderColor); +} + +void renderSlightlyRaisedBox( const RectI &bounds, GuiControlProfile *profile ) +{ + S32 l = bounds.point.x + 1, r = bounds.point.x + bounds.extent.x - 1; + S32 t = bounds.point.y + 1, b = bounds.point.y + bounds.extent.y - 1; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->drawRectFill( bounds, profile->mFillColor); + drawer->drawLine(l, t, l, b, profile->mBorderColor); + drawer->drawLine(l, t, r, t, profile->mBorderColor); + drawer->drawLine(l + 1, b, r, b, profile->mBorderColor); + drawer->drawLine(r, t + 1, r, b - 1, profile->mBorderColor); +} + +void renderLoweredBox( const RectI &bounds, GuiControlProfile *profile ) +{ + S32 l = bounds.point.x, r = bounds.point.x + bounds.extent.x - 1; + S32 t = bounds.point.y, b = bounds.point.y + bounds.extent.y - 1; + + GFX->getDrawUtil()->drawRectFill( bounds, profile->mFillColor); + + GFX->getDrawUtil()->drawLine(l, b, r, b, colorWhite); + GFX->getDrawUtil()->drawLine(r, b - 1, r, t, colorWhite); + + GFX->getDrawUtil()->drawLine(l, t, r - 1, t, colorBlack); + GFX->getDrawUtil()->drawLine(l, t + 1, l, b - 1, colorBlack); + + GFX->getDrawUtil()->drawLine(l + 1, t + 1, r - 2, t + 1, profile->mBorderColor); + GFX->getDrawUtil()->drawLine(l + 1, t + 2, l + 1, b - 2, profile->mBorderColor); +} + +void renderSlightlyLoweredBox( const RectI &bounds, GuiControlProfile *profile ) +{ + S32 l = bounds.point.x + 1, r = bounds.point.x + bounds.extent.x - 1; + S32 t = bounds.point.y + 1, b = bounds.point.y + bounds.extent.y - 1; + + GFX->getDrawUtil()->drawRectFill( bounds, profile->mFillColor); + GFX->getDrawUtil()->drawLine(l, b, r, b, profile->mBorderColor); + GFX->getDrawUtil()->drawLine(r, t, r, b - 1, profile->mBorderColor); + GFX->getDrawUtil()->drawLine(l, t, l, b - 1, profile->mBorderColor); + GFX->getDrawUtil()->drawLine(l + 1, t, r - 1, t, profile->mBorderColor); +} + +void renderBorder( const RectI &bounds, GuiControlProfile *profile ) +{ + S32 l = bounds.point.x, r = bounds.point.x + bounds.extent.x - 1; + S32 t = bounds.point.y, b = bounds.point.y + bounds.extent.y - 1; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + switch(profile->mBorder) + { + case 1: + drawer->drawRect(bounds, profile->mBorderColor); + break; + case 2: + drawer->drawLine(l + 1, t + 1, l + 1, b - 2, profile->mBevelColorHL); + drawer->drawLine(l + 2, t + 1, r - 2, t + 1, profile->mBevelColorHL); + drawer->drawLine(r, t, r, b, profile->mBevelColorHL); + drawer->drawLine(l, b, r - 1, b, profile->mBevelColorHL); + drawer->drawLine(l, t, r - 1, t, profile->mBorderColorNA); + drawer->drawLine(l, t + 1, l, b - 1, profile->mBorderColorNA); + drawer->drawLine(l + 1, b - 1, r - 1, b - 1, profile->mBorderColorNA); + drawer->drawLine(r - 1, t + 1, r - 1, b - 2, profile->mBorderColorNA); + break; + case 3: + drawer->drawLine(l, b, r, b, profile->mBevelColorHL); + drawer->drawLine(r, t, r, b - 1, profile->mBevelColorHL); + drawer->drawLine(l + 1, b - 1, r - 1, b - 1, profile->mFillColor); + drawer->drawLine(r - 1, t + 1, r - 1, b - 2, profile->mFillColor); + drawer->drawLine(l, t, l, b - 1, profile->mBorderColorNA); + drawer->drawLine(l + 1, t, r - 1, t, profile->mBorderColorNA); + drawer->drawLine(l + 1, t + 1, l + 1, b - 2, profile->mBevelColorLL); + drawer->drawLine(l + 2, t + 1, r - 2, t + 1, profile->mBevelColorLL); + break; + case 4: + drawer->drawLine(l, t, l, b - 1, profile->mBevelColorHL); + drawer->drawLine(l + 1, t, r, t, profile->mBevelColorHL); + drawer->drawLine(l, b, r, b, profile->mBevelColorLL); + drawer->drawLine(r, t + 1, r, b - 1, profile->mBevelColorLL); + drawer->drawLine(l + 1, b - 1, r - 1, b - 1, profile->mBorderColor); + drawer->drawLine(r - 1, t + 1, r - 1, b - 2, profile->mBorderColor); + break; + case 5: + renderFilledBorder( bounds, profile ); + break; + // + case -1: + // Draw a simple sizable border with corners + // Taken from the 'Skinnable GUI Controls in TGE' resource by Justin DuJardin + if(profile->mBitmapArrayRects.size() >= 8) + { + drawer->clearBitmapModulation(); + + RectI destRect; + RectI stretchRect; + RectI* mBitmapBounds = profile->mBitmapArrayRects.address(); + + // Indices into the bitmap array + enum + { + BorderTopLeft = 0, + BorderTop, + BorderTopRight, + BorderLeft, + //Fill, + BorderRight, + BorderBottomLeft, + BorderBottom, + BorderBottomRight, + NumBitmaps + }; + + // Draw all corners first. + + //top left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y),mBitmapBounds[BorderTopLeft]); + //top right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x + bounds.extent.x - mBitmapBounds[BorderTopRight].extent.x,bounds.point.y),mBitmapBounds[BorderTopRight]); + + //bottom left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y + bounds.extent.y - mBitmapBounds[BorderBottomLeft].extent.y),mBitmapBounds[BorderBottomLeft]); + //bottom right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I( + bounds.point.x + bounds.extent.x - mBitmapBounds[BorderBottomRight].extent.x, + bounds.point.y + bounds.extent.y - mBitmapBounds[BorderBottomRight].extent.y), + mBitmapBounds[BorderBottomRight]); + + // End drawing corners + + // Begin drawing sides and top stretched borders + + //start with top line stretch + destRect.point.x = bounds.point.x + mBitmapBounds[BorderTopRight].extent.x; + destRect.extent.x = bounds.extent.x - mBitmapBounds[BorderTopRight].extent.x - mBitmapBounds[BorderTopLeft].extent.x; + destRect.extent.y = mBitmapBounds[BorderTop].extent.y; + destRect.point.y = bounds.point.y; + //stretch it + stretchRect = mBitmapBounds[BorderTop]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //bottom line stretch + destRect.point.x = bounds.point.x + mBitmapBounds[BorderBottomRight].extent.x; + destRect.extent.x = bounds.extent.x - mBitmapBounds[BorderBottomRight].extent.x - mBitmapBounds[BorderBottomLeft].extent.x; + destRect.extent.y = mBitmapBounds[BorderBottom].extent.y; + destRect.point.y = bounds.point.y + bounds.extent.y - mBitmapBounds[BorderBottom].extent.y; + //stretch it + stretchRect = mBitmapBounds[BorderBottom]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //left line stretch + destRect.point.x = bounds.point.x; + destRect.extent.x = mBitmapBounds[BorderLeft].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[BorderTopLeft].extent.y - mBitmapBounds[BorderBottomLeft].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[BorderTopLeft].extent.y; + //stretch it + stretchRect = mBitmapBounds[BorderLeft]; + stretchRect.inset(0,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //left line stretch + destRect.point.x = bounds.point.x + bounds.extent.x - mBitmapBounds[BorderRight].extent.x; + destRect.extent.x = mBitmapBounds[BorderRight].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[BorderTopRight].extent.y - mBitmapBounds[BorderBottomRight].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[BorderTopRight].extent.y; + //stretch it + stretchRect = mBitmapBounds[BorderRight]; + stretchRect.inset(0,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + + // End drawing sides and top stretched borders + break; + } + case -2: + // Draw a simple sizable border with corners that is filled in + renderSizableBitmapBordersFilled(bounds, 1, profile); + break; + case -3: + // Draw a simple fixed height border with center fill horizontally. + renderFixedBitmapBordersFilled( bounds, 1, profile ); + break; + + } +} + +void renderFilledBorder( const RectI &bounds, GuiControlProfile *profile ) +{ + renderFilledBorder( bounds, profile->mBorderColor, profile->mFillColor ); +} + +void renderFilledBorder( const RectI &bounds, const ColorI &borderColor, const ColorI &fillColor ) +{ + RectI fillBounds = bounds; + fillBounds.inset( 1, 1 ); + + GFX->getDrawUtil()->drawRect( bounds, borderColor ); + GFX->getDrawUtil()->drawRectFill( fillBounds, fillColor ); +} + +// Render out the sizable bitmap borders based on a multiplier into the bitmap array +// Based on the 'Skinnable GUI Controls in TGE' resource by Justin DuJardin +void renderSizableBitmapBordersFilled( const RectI &bounds, S32 baseMultiplier, GuiControlProfile *profile) +{ + // Indices into the bitmap array + S32 numBitmaps = 9; + S32 borderTopLeft = numBitmaps * baseMultiplier - numBitmaps; + S32 borderTop = 1 + borderTopLeft; + S32 borderTopRight = 2 + borderTopLeft; + S32 borderLeft = 3 + borderTopLeft; + S32 fill = 4 + borderTopLeft; + S32 borderRight = 5 + borderTopLeft; + S32 borderBottomLeft = 6 + borderTopLeft; + S32 borderBottom = 7 + borderTopLeft; + S32 borderBottomRight = 8 + borderTopLeft; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + drawer->clearBitmapModulation(); + + if(profile->mBitmapArrayRects.size() >= (numBitmaps * baseMultiplier)) + { + RectI destRect; + RectI stretchRect; + RectI* mBitmapBounds = profile->mBitmapArrayRects.address(); + + // Draw all corners first. + + //top left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y),mBitmapBounds[borderTopLeft]); + //top right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x + bounds.extent.x - mBitmapBounds[borderTopRight].extent.x,bounds.point.y),mBitmapBounds[borderTopRight]); + + //bottom left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y + bounds.extent.y - mBitmapBounds[borderBottomLeft].extent.y),mBitmapBounds[borderBottomLeft]); + //bottom right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I( + bounds.point.x + bounds.extent.x - mBitmapBounds[borderBottomRight].extent.x, + bounds.point.y + bounds.extent.y - mBitmapBounds[borderBottomRight].extent.y), + mBitmapBounds[borderBottomRight]); + + // End drawing corners + + // Begin drawing sides and top stretched borders + + //start with top line stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderTopRight].extent.x; + destRect.extent.x = bounds.extent.x - mBitmapBounds[borderTopRight].extent.x - mBitmapBounds[borderTopLeft].extent.x; + destRect.extent.y = mBitmapBounds[borderTop].extent.y; + destRect.point.y = bounds.point.y; + //stretch it + stretchRect = mBitmapBounds[borderTop]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //bottom line stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderBottomRight].extent.x; + destRect.extent.x = bounds.extent.x - mBitmapBounds[borderBottomRight].extent.x - mBitmapBounds[borderBottomLeft].extent.x; + destRect.extent.y = mBitmapBounds[borderBottom].extent.y; + destRect.point.y = bounds.point.y + bounds.extent.y - mBitmapBounds[borderBottom].extent.y; + //stretch it + stretchRect = mBitmapBounds[borderBottom]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //left line stretch + destRect.point.x = bounds.point.x; + destRect.extent.x = mBitmapBounds[borderLeft].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[borderTopLeft].extent.y - mBitmapBounds[borderBottomLeft].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[borderTopLeft].extent.y; + //stretch it + stretchRect = mBitmapBounds[borderLeft]; + stretchRect.inset(0,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //left line stretch + destRect.point.x = bounds.point.x + bounds.extent.x - mBitmapBounds[borderRight].extent.x; + destRect.extent.x = mBitmapBounds[borderRight].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[borderTopRight].extent.y - mBitmapBounds[borderBottomRight].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[borderTopRight].extent.y; + //stretch it + stretchRect = mBitmapBounds[borderRight]; + stretchRect.inset(0,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //fill stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderLeft].extent.x; + destRect.extent.x = (bounds.extent.x) - mBitmapBounds[borderLeft].extent.x - mBitmapBounds[borderRight].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[borderTop].extent.y - mBitmapBounds[borderBottom].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[borderTop].extent.y; + //stretch it + stretchRect = mBitmapBounds[fill]; + stretchRect.inset(1,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + + // End drawing sides and top stretched borders + } +} + + +// Render out the sizable bitmap borders based on a multiplier into the bitmap array +// Based on the 'Skinnable GUI Controls in TGE' resource by Justin DuJardin +void renderSizableBitmapBordersFilledIndex( const RectI &bounds, S32 startIndex, GuiControlProfile *profile ) +{ + // Indices into the bitmap array + S32 numBitmaps = 9; + S32 borderTopLeft = startIndex; + S32 borderTop = 1 + borderTopLeft; + S32 borderTopRight = 2 + borderTopLeft; + S32 borderLeft = 3 + borderTopLeft; + S32 fill = 4 + borderTopLeft; + S32 borderRight = 5 + borderTopLeft; + S32 borderBottomLeft = 6 + borderTopLeft; + S32 borderBottom = 7 + borderTopLeft; + S32 borderBottomRight = 8 + borderTopLeft; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + drawer->clearBitmapModulation(); + if(profile->mBitmapArrayRects.size() >= (startIndex + numBitmaps)) + { + RectI destRect; + RectI stretchRect; + RectI* mBitmapBounds = profile->mBitmapArrayRects.address(); + + // Draw all corners first. + + //top left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y),mBitmapBounds[borderTopLeft]); + //top right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x + bounds.extent.x - mBitmapBounds[borderTopRight].extent.x,bounds.point.y),mBitmapBounds[borderTopRight]); + + //bottom left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y + bounds.extent.y - mBitmapBounds[borderBottomLeft].extent.y),mBitmapBounds[borderBottomLeft]); + //bottom right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I( + bounds.point.x + bounds.extent.x - mBitmapBounds[borderBottomRight].extent.x, + bounds.point.y + bounds.extent.y - mBitmapBounds[borderBottomRight].extent.y), + mBitmapBounds[borderBottomRight]); + + // End drawing corners + + // Begin drawing sides and top stretched borders + + //start with top line stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderTopLeft].extent.x; + destRect.extent.x = bounds.extent.x - mBitmapBounds[borderTopRight].extent.x - mBitmapBounds[borderTopLeft].extent.x; + destRect.extent.y = mBitmapBounds[borderTop].extent.y; + destRect.point.y = bounds.point.y; + //stretch it + stretchRect = mBitmapBounds[borderTop]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //bottom line stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderBottomLeft].extent.x; + destRect.extent.x = bounds.extent.x - mBitmapBounds[borderBottomRight].extent.x - mBitmapBounds[borderBottomLeft].extent.x; + destRect.extent.y = mBitmapBounds[borderBottom].extent.y; + destRect.point.y = bounds.point.y + bounds.extent.y - mBitmapBounds[borderBottom].extent.y; + //stretch it + stretchRect = mBitmapBounds[borderBottom]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //left line stretch + destRect.point.x = bounds.point.x; + destRect.extent.x = mBitmapBounds[borderLeft].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[borderTopLeft].extent.y - mBitmapBounds[borderBottomLeft].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[borderTopLeft].extent.y; + //stretch it + stretchRect = mBitmapBounds[borderLeft]; + stretchRect.inset(0,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //left line stretch + destRect.point.x = bounds.point.x + bounds.extent.x - mBitmapBounds[borderRight].extent.x; + destRect.extent.x = mBitmapBounds[borderRight].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[borderTopRight].extent.y - mBitmapBounds[borderBottomRight].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[borderTopRight].extent.y; + //stretch it + stretchRect = mBitmapBounds[borderRight]; + stretchRect.inset(0,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + //fill stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderLeft].extent.x; + destRect.extent.x = (bounds.extent.x) - mBitmapBounds[borderLeft].extent.x - mBitmapBounds[borderRight].extent.x; + destRect.extent.y = bounds.extent.y - mBitmapBounds[borderTop].extent.y - mBitmapBounds[borderBottom].extent.y; + destRect.point.y = bounds.point.y + mBitmapBounds[borderTop].extent.y; + //stretch it + stretchRect = mBitmapBounds[fill]; + stretchRect.inset(1,1); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + + // End drawing sides and top stretched borders + } +} + + + +// Render out the fixed bitmap borders based on a multiplier into the bitmap array +// It renders left and right caps, with a sizable fill area in the middle to reach +// the x extent. It does not stretch in the y direction. +void renderFixedBitmapBordersFilled( const RectI &bounds, S32 baseMultiplier, GuiControlProfile *profile ) +{ + // Indices into the bitmap array + S32 numBitmaps = 3; + S32 borderLeft = numBitmaps * baseMultiplier - numBitmaps; + S32 fill = 1 + borderLeft; + S32 borderRight = 2 + borderLeft; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + drawer->clearBitmapModulation(); + if(profile->mBitmapArrayRects.size() >= (numBitmaps * baseMultiplier)) + { + RectI destRect; + RectI stretchRect; + RectI* mBitmapBounds = profile->mBitmapArrayRects.address(); + + // Draw all corners first. + + //left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y),mBitmapBounds[borderLeft]); + //right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x + bounds.extent.x - mBitmapBounds[borderRight].extent.x,bounds.point.y),mBitmapBounds[borderRight]); + + // End drawing corners + + // Begin drawing fill + + //fill stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderLeft].extent.x; + destRect.extent.x = (bounds.extent.x) - mBitmapBounds[borderLeft].extent.x - mBitmapBounds[borderRight].extent.x; + destRect.extent.y = mBitmapBounds[fill].extent.y; + destRect.point.y = bounds.point.y; + //stretch it + stretchRect = mBitmapBounds[fill]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + + // End drawing fill + } +} + +// Render out the fixed bitmap borders based on a multiplier into the bitmap array +// It renders left and right caps, with a sizable fill area in the middle to reach +// the x extent. It does not stretch in the y direction. +void renderFixedBitmapBordersFilledIndex( const RectI &bounds, S32 startIndex, GuiControlProfile *profile ) +{ + // Indices into the bitmap array + S32 numBitmaps = 3; + S32 borderLeft = startIndex; + S32 fill = 1 + startIndex; + S32 borderRight = 2 + startIndex; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->clearBitmapModulation(); + if(profile->mBitmapArrayRects.size() >= (startIndex + numBitmaps)) + { + RectI destRect; + RectI stretchRect; + RectI* mBitmapBounds = profile->mBitmapArrayRects.address(); + + // Draw all corners first. + + //left border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x,bounds.point.y),mBitmapBounds[borderLeft]); + //right border + drawer->drawBitmapSR(profile->mTextureObject,Point2I(bounds.point.x + bounds.extent.x - mBitmapBounds[borderRight].extent.x,bounds.point.y),mBitmapBounds[borderRight]); + + // End drawing corners + + // Begin drawing fill + + //fill stretch + destRect.point.x = bounds.point.x + mBitmapBounds[borderLeft].extent.x; + destRect.extent.x = (bounds.extent.x) - mBitmapBounds[borderLeft].extent.x - mBitmapBounds[borderRight].extent.x; + destRect.extent.y = mBitmapBounds[fill].extent.y; + destRect.point.y = bounds.point.y; + //stretch it + stretchRect = mBitmapBounds[fill]; + stretchRect.inset(1,0); + //draw it + drawer->drawBitmapStretchSR(profile->mTextureObject,destRect,stretchRect); + + // End drawing fill + } +} diff --git a/gui/core/guiDefaultControlRender.h b/gui/core/guiDefaultControlRender.h new file mode 100644 index 0000000..85823f4 --- /dev/null +++ b/gui/core/guiDefaultControlRender.h @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _H_GUIDEFAULTCONTROLRENDER_ +#define _H_GUIDEFAULTCONTROLRENDER_ + +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif + +class GuiControlProfile; +class ColorI; + +void renderRaisedBox( const RectI &bounds, GuiControlProfile *profile); +void renderSlightlyRaisedBox( const RectI &bounds, GuiControlProfile *profile); +void renderLoweredBox( const RectI &bounds, GuiControlProfile *profile); +void renderSlightlyLoweredBox( const RectI &bounds, GuiControlProfile *profile); +void renderBorder( const RectI &bounds, GuiControlProfile *profile); +void renderFilledBorder( const RectI &bounds, GuiControlProfile *profile ); +void renderFilledBorder( const RectI &bounds, const ColorI &borderColor, const ColorI &fillColor ); +void renderSizableBitmapBordersFilled( const RectI &bounds, S32 baseMultiplier, GuiControlProfile *profile); // Added +void renderSizableBitmapBordersFilledIndex( const RectI &bounds, S32 startIndex, GuiControlProfile *profile); +void renderFixedBitmapBordersFilled( const RectI &bounds, S32 baseMultiplier, GuiControlProfile *profile); // Added +void renderFixedBitmapBordersFilled( const RectI &bounds, S32 startIndex, GuiControlProfile *profile); + +#endif diff --git a/gui/core/guiScriptNotifyControl.cpp b/gui/core/guiScriptNotifyControl.cpp new file mode 100644 index 0000000..71cedcd --- /dev/null +++ b/gui/core/guiScriptNotifyControl.cpp @@ -0,0 +1,215 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/core/guiScriptNotifyControl.h" +#include "console/consoleTypes.h" +//------------------------------------------------------------------------------ + +IMPLEMENT_CONOBJECT(GuiScriptNotifyCtrl); + +GuiScriptNotifyCtrl::GuiScriptNotifyCtrl() +{ + mOnChildAdded = false; + mOnChildRemoved = false; + mOnResize = false; + mOnChildResized = false; + mOnParentResized = false; +} + +GuiScriptNotifyCtrl::~GuiScriptNotifyCtrl() +{ +} + +void GuiScriptNotifyCtrl::initPersistFields() +{ + // Callbacks Group + addGroup("Callbacks"); + addField("onChildAdded", TypeBool, Offset( mOnChildAdded, GuiScriptNotifyCtrl ) ); + addField("onChildRemoved", TypeBool, Offset( mOnChildRemoved, GuiScriptNotifyCtrl ) ); + addField("onChildResized", TypeBool, Offset( mOnChildResized, GuiScriptNotifyCtrl ) ); + addField("onParentResized", TypeBool, Offset( mOnParentResized, GuiScriptNotifyCtrl ) ); + addField("onResize", TypeBool, Offset( mOnResize, GuiScriptNotifyCtrl ) ); + addField("onLoseFirstResponder", TypeBool, Offset( mOnLoseFirstResponder, GuiScriptNotifyCtrl ) ); + addField("onGainFirstResponder", TypeBool, Offset( mOnGainFirstResponder, GuiScriptNotifyCtrl ) ); + endGroup("Callbacks"); + + Parent::initPersistFields(); +} + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +void GuiScriptNotifyCtrl::onChildAdded( GuiControl *child ) +{ + Parent::onChildAdded( child ); + + // Call Script. + if( mOnChildAdded && isMethod( "onChildAdded" ) ) + Con::executef(this, "onChildAdded", child->getIdString() ); +} + +void GuiScriptNotifyCtrl::onChildRemoved( GuiControl *child ) +{ + Parent::onChildRemoved( child ); + + // Call Script. + if( mOnChildRemoved && isMethod( "onChildRemoved" ) ) + Con::executef(this, "onChildRemoved", child->getIdString() ); +} +//---------------------------------------------------------------- + +bool GuiScriptNotifyCtrl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if( !Parent::resize( newPosition, newExtent ) ) + return false; + + // Call Script. + if( mOnResize && isMethod( "onResize" ) ) + Con::executef(this, "onResize" ); + + return true; +} + +void GuiScriptNotifyCtrl::childResized(GuiScriptNotifyCtrl *child) +{ + Parent::childResized( child ); + + // Call Script. + if( mOnChildResized && isMethod( "onChildResized" ) ) + Con::executef(this, "onChildResized", child->getIdString() ); +} + +void GuiScriptNotifyCtrl::parentResized(const RectI &oldParentRect, const RectI &newParentRect) +{ + Parent::parentResized( oldParentRect, newParentRect ); + + // Call Script. + if( mOnParentResized && isMethod( "onParentResized" ) ) + Con::executef(this, "onParentResized" ); + +} + +void GuiScriptNotifyCtrl::onLoseFirstResponder() +{ + Parent::onLoseFirstResponder(); + + // Call Script. + if( mOnLoseFirstResponder && isMethod( "onLoseFirstResponder" ) ) + Con::executef(this, "onLoseFirstResponder" ); + +} + +void GuiScriptNotifyCtrl::setFirstResponder( GuiControl* firstResponder ) +{ + Parent::setFirstResponder( firstResponder ); + + // Call Script. + if( mOnGainFirstResponder && isFirstResponder() && isMethod( "onGainFirstResponder" ) ) + Con::executef(this, "onGainFirstResponder" ); + +} + +void GuiScriptNotifyCtrl::setFirstResponder() +{ + Parent::setFirstResponder(); + + // Call Script. + if( mOnGainFirstResponder && isFirstResponder() && isMethod( "onGainFirstResponder" ) ) + Con::executef(this, "onGainFirstResponder" ); +} + +void GuiScriptNotifyCtrl::onMessage(GuiScriptNotifyCtrl *sender, S32 msg) +{ + Parent::onMessage( sender, msg ); +} + +void GuiScriptNotifyCtrl::onDialogPush() +{ + Parent::onDialogPush(); +} + +void GuiScriptNotifyCtrl::onDialogPop() +{ + Parent::onDialogPop(); +} + + +//void GuiScriptNotifyCtrl::onMouseUp(const GuiEvent &event) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMouseDown(const GuiEvent &event) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMouseMove(const GuiEvent &event) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMouseDragged(const GuiEvent &event) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMouseEnter(const GuiEvent &) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMouseLeave(const GuiEvent &) +//{ +//} +// +//bool GuiScriptNotifyCtrl::onMouseWheelUp( const GuiEvent &event ) +//{ +//} +// +//bool GuiScriptNotifyCtrl::onMouseWheelDown( const GuiEvent &event ) +//{ +//} +// +//void GuiScriptNotifyCtrl::onRightMouseDown(const GuiEvent &) +//{ +//} +// +//void GuiScriptNotifyCtrl::onRightMouseUp(const GuiEvent &) +//{ +//} +// +//void GuiScriptNotifyCtrl::onRightMouseDragged(const GuiEvent &) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMiddleMouseDown(const GuiEvent &) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMiddleMouseUp(const GuiEvent &) +//{ +//} +// +//void GuiScriptNotifyCtrl::onMiddleMouseDragged(const GuiEvent &) +//{ +//} +//void GuiScriptNotifyCtrl::onMouseDownEditor(const GuiEvent &event, Point2I offset) +//{ +//} +//void GuiScriptNotifyCtrl::onRightMouseDownEditor(const GuiEvent &event, Point2I offset) +//{ +//} + +//bool GuiScriptNotifyCtrl::onKeyDown(const GuiEvent &event) +//{ +// if ( Parent::onKeyDown( event ) ) +// return true; +//} +// +//bool GuiScriptNotifyCtrl::onKeyRepeat(const GuiEvent &event) +//{ +// // default to just another key down. +// return onKeyDown(event); +//} +// +//bool GuiScriptNotifyCtrl::onKeyUp(const GuiEvent &event) +//{ +// if ( Parent::onKeyUp( event ) ) +// return true; +//} diff --git a/gui/core/guiScriptNotifyControl.h b/gui/core/guiScriptNotifyControl.h new file mode 100644 index 0000000..c5363a8 --- /dev/null +++ b/gui/core/guiScriptNotifyControl.h @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISCRIPTNOTIFYCTRL_H_ +#define _GUISCRIPTNOTIFYCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +class GuiScriptNotifyCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; +public: + + /// @name Event Callbacks + /// @{ + bool mOnChildAdded; ///< Script Notify : onAddObject(%object) + bool mOnChildRemoved; ///< Script Notify : onRemoveObject(%object) + bool mOnResize; ///< Script Notify : onResize() + bool mOnChildResized; ///< Script Notify : onChildResized(%child) + bool mOnParentResized; ///< Script Notify : onParentResized() + bool mOnLoseFirstResponder; ///< Script Notify : onLoseFirstResponder() + bool mOnGainFirstResponder; ///< Script Notify : onGainFirstResponder() + /// @} + +public: + /// @name Initialization + /// @{ + DECLARE_CONOBJECT(GuiScriptNotifyCtrl); + DECLARE_CATEGORY( "Gui Other Script" ); + DECLARE_DESCRIPTION( "A control that implements various script callbacks for\n" + "certain GUI events." ); + + GuiScriptNotifyCtrl(); + virtual ~GuiScriptNotifyCtrl(); + static void initPersistFields(); + + virtual bool resize(const Point2I &newPosition, const Point2I &newExtent); + virtual void childResized(GuiScriptNotifyCtrl *child); + virtual void parentResized(const RectI &oldParentRect, const RectI &newParentRect); + virtual void onChildRemoved( GuiControl *child ); + virtual void onChildAdded( GuiControl *child ); + //virtual void onMouseUp(const GuiEvent &event); + //virtual void onMouseDown(const GuiEvent &event); + //virtual void onMouseMove(const GuiEvent &event); + //virtual void onMouseDragged(const GuiEvent &event); + //virtual void onMouseEnter(const GuiEvent &event); + //virtual void onMouseLeave(const GuiEvent &event); + + //virtual bool onMouseWheelUp(const GuiEvent &event); + //virtual bool onMouseWheelDown(const GuiEvent &event); + + //virtual void onRightMouseDown(const GuiEvent &event); + //virtual void onRightMouseUp(const GuiEvent &event); + //virtual void onRightMouseDragged(const GuiEvent &event); + + //virtual void onMiddleMouseDown(const GuiEvent &event); + //virtual void onMiddleMouseUp(const GuiEvent &event); + //virtual void onMiddleMouseDragged(const GuiEvent &event); + + //virtual void onMouseDownEditor(const GuiEvent &event, Point2I offset); + //virtual void onRightMouseDownEditor(const GuiEvent &event, Point2I offset); + + virtual void setFirstResponder(GuiControl *firstResponder); + virtual void setFirstResponder(); + void clearFirstResponder(); + virtual void onLoseFirstResponder(); + + //virtual void acceleratorKeyPress(U32 index); + //virtual void acceleratorKeyRelease(U32 index); + //virtual bool onKeyDown(const GuiEvent &event); + //virtual bool onKeyUp(const GuiEvent &event); + //virtual bool onKeyRepeat(const GuiEvent &event); + + virtual void onMessage(GuiScriptNotifyCtrl *sender, S32 msg); ///< Receive a message from another control + + virtual void onDialogPush(); + virtual void onDialogPop(); + +}; + +#endif diff --git a/gui/core/guiTypes.cpp b/gui/core/guiTypes.cpp new file mode 100644 index 0000000..aa8ac71 --- /dev/null +++ b/gui/core/guiTypes.cpp @@ -0,0 +1,633 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/types.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiTypes.h" +#include "gfx/gFont.h" +#include "gfx/bitmap/gBitmap.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "sfx/sfxProfile.h" + +// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // +IMPLEMENT_CONOBJECT(GuiCursor); + + +GFX_ImplementTextureProfile(GFXGuiCursorProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::Static, + GFXTextureProfile::None); +GFX_ImplementTextureProfile(GFXDefaultGUIProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::Static | + GFXTextureProfile::NoPadding, + GFXTextureProfile::None); + + +GuiCursor::GuiCursor() +{ + mHotSpot.set(0,0); + mRenderOffset.set(0.0f,0.0f); + mExtent.set(1,1); + mTextureObject = NULL; + mTexturePicked = NULL; + mStatu = Cursor_Normal; +} + +GuiCursor::~GuiCursor() +{ +} + +void GuiCursor::initPersistFields() +{ + addField("hotSpot", TypePoint2I, Offset(mHotSpot, GuiCursor)); + addField("renderOffset",TypePoint2F, Offset(mRenderOffset, GuiCursor)); + addField("bitmapName", TypeFilename, Offset(mBitmapName, GuiCursor)); + Parent::initPersistFields(); +} + +bool GuiCursor::onAdd() +{ + if(!Parent::onAdd()) + return false; + + Sim::getGuiDataGroup()->addObject(this); + + return true; +} + +void GuiCursor::onRemove() +{ + Parent::onRemove(); +} +void GuiCursor::setPickedBmp(StringTableEntry path) +{ + if (mTexturePicked.set(path,&GFXDefaultGUIProfile,"gui cursor")) + mStatu = Cursor_Picked; + else + mStatu = Cursor_Normal; +} +void GuiCursor::clearPickedBmp() +{ + mStatu = Cursor_Normal; +} +void GuiCursor::render(const Point2I &pos) +{ + if (!mTextureObject && mBitmapName && mBitmapName[0]) + { + mTextureObject.set( mBitmapName, &GFXGuiCursorProfile, avar("%s() - mTextureObject (line %d)", __FUNCTION__, __LINE__)); + if(!mTextureObject) + return; + mExtent.set(mTextureObject->getWidth(), mTextureObject->getHeight()); + } + + // Render the cursor centered according to dimensions of texture + S32 texWidth = mTextureObject.getWidth(); + S32 texHeight = mTextureObject.getHeight(); + + Point2I renderPos = pos; + /* + renderPos.x -= (S32)( texWidth * mRenderOffset.x ); + renderPos.y -= (S32)( texHeight * mRenderOffset.y ); + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmap(mTextureObject, renderPos); + */ + GFX->getDrawUtil()->clearBitmapModulation(); + switch (mStatu) + { + case Cursor_Picked: + { + Point2I bg = renderPos; + bg.x -= (S32)( (F32)(mTexturePicked.getWidth()) / 2.f + 0.5f ); + bg.y -= (S32)( (F32)(mTexturePicked.getHeight()) / 2.f + 0.5f ); + GFX->getDrawUtil()->drawBitmap(mTexturePicked, bg); + break; + } + case Cursor_Normal: + //renderPos.x -= (S32)( texWidth * mRenderOffset.x ); + //renderPos.y -= (S32)( texHeight * mRenderOffset.y ); + //GFX->getDrawUtil()->drawBitmap(mTextureObject, renderPos); + break; + } +} + +//------------------------------------------------------------------------------ +IMPLEMENT_CONOBJECT(GuiControlProfile); + +static const EnumTable::Enums alignEnums[] = +{ + { GuiControlProfile::LeftJustify, "left" }, + { GuiControlProfile::CenterJustify, "center" }, + { GuiControlProfile::RightJustify, "right" } +}; +static const EnumTable gAlignTable(3, &alignEnums[0]); + +static const EnumTable::Enums charsetEnums[]= +{ + { TGE_ANSI_CHARSET, "ANSI" }, + { TGE_SYMBOL_CHARSET, "SYMBOL" }, + { TGE_SHIFTJIS_CHARSET, "SHIFTJIS" }, + { TGE_HANGEUL_CHARSET, "HANGEUL" }, + { TGE_HANGUL_CHARSET, "HANGUL" }, + { TGE_GB2312_CHARSET, "GB2312" }, + { TGE_CHINESEBIG5_CHARSET, "CHINESEBIG5" }, + { TGE_OEM_CHARSET, "OEM" }, + { TGE_JOHAB_CHARSET, "JOHAB" }, + { TGE_HEBREW_CHARSET, "HEBREW" }, + { TGE_ARABIC_CHARSET, "ARABIC" }, + { TGE_GREEK_CHARSET, "GREEK" }, + { TGE_TURKISH_CHARSET, "TURKISH" }, + { TGE_VIETNAMESE_CHARSET, "VIETNAMESE" }, + { TGE_THAI_CHARSET, "THAI" }, + { TGE_EASTEUROPE_CHARSET, "EASTEUROPE" }, + { TGE_RUSSIAN_CHARSET, "RUSSIAN" }, + { TGE_MAC_CHARSET, "MAC" }, + { TGE_BALTIC_CHARSET, "BALTIC" }, +}; + +#define NUMCHARSETENUMS (sizeof(charsetEnums) / sizeof(EnumTable::Enums)) + +static const EnumTable gCharsetTable(NUMCHARSETENUMS, &charsetEnums[0]); + +StringTableEntry GuiControlProfile::sFontCacheDirectory = ""; + +void GuiControlProfile::setBitmapHandle(GFXTexHandle handle) +{ + mTextureObject = handle; + + mBitmapName = StringTable->insert("texhandle"); +} + +bool GuiControlProfile::protectedSetBitmap( void *obj, const char *data) +{ + GuiControlProfile *profile = static_cast(obj); + + profile->mBitmapName = StringTable->insert(data); + + if ( !profile->isProperlyAdded() ) + return false; + + profile->mBitmapArrayRects.clear(); + profile->mTextureObject = NULL; + + //verify the bitmap + if (profile->mBitmapName && profile->mBitmapName[0] && dStricmp(profile->mBitmapName, "texhandle") != 0 && + !profile->mTextureObject.set( profile->mBitmapName, &GFXDefaultPersistentProfile, avar("%s() - mTextureObject (line %d)", __FUNCTION__, __LINE__) )) + Con::errorf("Failed to load profile bitmap (%s)",profile->mBitmapName); + + // If we've got a special border, make sure it's usable. + //if( profile->mBorder == -1 || profile->mBorder == -2 ) + profile->constructBitmapArray(); + + return false; +} + +GuiControlProfile::GuiControlProfile(void) : + mFillColor(255,0,255,255), + mFillColorHL(255,0,255,255), + mFillColorNA(255,0,255,255), + mFillColorSEL(255,0,255,255), + mBorderColor(255,0,255,255), + mBorderColorHL(255,0,255,255), + mBorderColorNA(255,0,255,255), + mBevelColorHL(255,0,255,255), + mBevelColorLL(255,0,255,255), + // initialize these references to locations in the font colors array + // the array is initialized below. + mFontColor(mFontColors[BaseColor]), + mFontColorHL(mFontColors[ColorHL]), + mFontColorNA(mFontColors[ColorNA]), + mFontColorSEL(mFontColors[ColorSEL]), + mCursorColor(255,0,255,255), + mYPositionOffset(0), + mTextOffset(0,0), + mBitmapArrayRects(0) +{ + // profiles are reference counted + mRefCount = 0; + + // event focus behavior + mTabable = false; + mCanKeyFocus = false; + mModal = false; + + // fill and border + mOpaque = false; + mBorder = 1; + mBorderThickness = 1; + + // font members + mFontType = "Arial"; + mFontSize = 10; + + for(U32 i = 0; i < 10; i++) + mFontColors[i].set(255,0,255,255); + + mFontCharset = TGE_ANSI_CHARSET; + + // sizing and alignment + mAlignment = LeftJustify; + mAutoSizeWidth = false; + mAutoSizeHeight= false; + mReturnTab = false; + mNumbersOnly = false; + mMouseOverSelected = false; + + // bitmap members + mBitmapName = NULL; + mUseBitmapArray = false; + mTextureObject = NULL; // initialized in incRefCount() + + // sound members + mSoundButtonDown = NULL; + mSoundButtonOver = NULL; + + mChildrenProfileName = NULL; + mChildrenProfile = NULL; + + // inherit/copy values from GuiDefaultProfile + GuiControlProfile *def = dynamic_cast(Sim::findObject("GuiDefaultProfile")); + if (def) + { + mTabable = def->mTabable; + mCanKeyFocus = def->mCanKeyFocus; + + mOpaque = def->mOpaque; + mFillColor = def->mFillColor; + mFillColorHL = def->mFillColorHL; + mFillColorNA = def->mFillColorNA; + mFillColorSEL = def->mFillColorSEL; + + mBorder = def->mBorder; + mBorderThickness = def->mBorderThickness; + mBorderColor = def->mBorderColor; + mBorderColorHL = def->mBorderColorHL; + mBorderColorNA = def->mBorderColorNA; + + mBevelColorHL = def->mBevelColorHL; + mBevelColorLL = def->mBevelColorLL; + + // default font + mFontType = def->mFontType; + mFontSize = def->mFontSize; + mFontCharset = def->mFontCharset; + + for(U32 i = 0; i < 10; i++) + mFontColors[i] = def->mFontColors[i]; + + // default offset + mYPositionOffset = def->mYPositionOffset; + + // default bitmap + mBitmapName = def->mBitmapName; + mUseBitmapArray = def->mUseBitmapArray; + mTextOffset = def->mTextOffset; + + // default sound + mSoundButtonDown = def->mSoundButtonDown; + mSoundButtonOver = def->mSoundButtonOver; + + //used by GuiTextCtrl + mModal = def->mModal; + mAlignment = def->mAlignment; + mAutoSizeWidth = def->mAutoSizeWidth; + mAutoSizeHeight= def->mAutoSizeHeight; + mReturnTab = def->mReturnTab; + mNumbersOnly = def->mNumbersOnly; + mCursorColor = def->mCursorColor; + mChildrenProfileName = def->mChildrenProfileName; + setChildrenProfile(def->mChildrenProfile); + } +} + +GuiControlProfile::~GuiControlProfile() +{ +} + + +void GuiControlProfile::initPersistFields() +{ + addField("tab", TypeBool, Offset(mTabable, GuiControlProfile)); + addField("canKeyFocus", TypeBool, Offset(mCanKeyFocus, GuiControlProfile)); + addField("mouseOverSelected", TypeBool, Offset(mMouseOverSelected, GuiControlProfile)); + + addField("modal", TypeBool, Offset(mModal, GuiControlProfile)); + addField("opaque", TypeBool, Offset(mOpaque, GuiControlProfile)); + addField("fillColor", TypeColorI, Offset(mFillColor, GuiControlProfile)); + addField("fillColorHL", TypeColorI, Offset(mFillColorHL, GuiControlProfile)); + addField("fillColorNA", TypeColorI, Offset(mFillColorNA, GuiControlProfile)); + addField("fillColorSEL", TypeColorI, Offset(mFillColorSEL, GuiControlProfile)); + addField("border", TypeS32, Offset(mBorder, GuiControlProfile)); + addField("borderThickness",TypeS32, Offset(mBorderThickness, GuiControlProfile)); + addField("borderColor", TypeColorI, Offset(mBorderColor, GuiControlProfile)); + addField("borderColorHL", TypeColorI, Offset(mBorderColorHL, GuiControlProfile)); + addField("borderColorNA", TypeColorI, Offset(mBorderColorNA, GuiControlProfile)); + + addField("bevelColorHL", TypeColorI, Offset(mBevelColorHL, GuiControlProfile)); + addField("bevelColorLL", TypeColorI, Offset(mBevelColorLL, GuiControlProfile)); + + addField("fontType", TypeString, Offset(mFontType, GuiControlProfile)); + addField("fontSize", TypeS32, Offset(mFontSize, GuiControlProfile)); + addField("fontCharset", TypeEnum, Offset(mFontCharset, GuiControlProfile), 1, &gCharsetTable, ""); + addField("fontColors", TypeColorI, Offset(mFontColors, GuiControlProfile), 10); + addField("fontColor", TypeColorI, Offset(mFontColors[BaseColor], GuiControlProfile)); + addField("fontColorHL", TypeColorI, Offset(mFontColors[ColorHL], GuiControlProfile)); + addField("fontColorNA", TypeColorI, Offset(mFontColors[ColorNA], GuiControlProfile)); + addField("fontColorSEL", TypeColorI, Offset(mFontColors[ColorSEL], GuiControlProfile)); + addField("fontColorLink", TypeColorI, Offset(mFontColors[ColorUser0], GuiControlProfile)); + addField("fontColorLinkHL", TypeColorI, Offset(mFontColors[ColorUser1], GuiControlProfile)); + + addField("yPositionOffset", TypeS32, Offset(mYPositionOffset, GuiControlProfile)); + + addField("justify", TypeEnum, Offset(mAlignment, GuiControlProfile), 1, &gAlignTable); + addField("textOffset", TypePoint2I, Offset(mTextOffset, GuiControlProfile)); + addField("autoSizeWidth", TypeBool, Offset(mAutoSizeWidth, GuiControlProfile)); + addField("autoSizeHeight",TypeBool, Offset(mAutoSizeHeight, GuiControlProfile)); + addField("returnTab", TypeBool, Offset(mReturnTab, GuiControlProfile)); + addField("numbersOnly", TypeBool, Offset(mNumbersOnly, GuiControlProfile)); + addField("cursorColor", TypeColorI, Offset(mCursorColor, GuiControlProfile)); + + addProtectedField( "bitmap", TypeFilename, Offset(mBitmapName, GuiControlProfile), &GuiControlProfile::protectedSetBitmap, &defaultProtectedGetFn, "" ); + addField("hasBitmapArray", TypeBool, Offset(mUseBitmapArray, GuiControlProfile)); + + addField("soundButtonDown", TypeSFXProfilePtr, Offset(mSoundButtonDown, GuiControlProfile)); + addField("soundButtonOver", TypeSFXProfilePtr, Offset(mSoundButtonOver, GuiControlProfile)); + addField("profileForChildren", TypeString, Offset(mChildrenProfileName, GuiControlProfile)); + + Parent::initPersistFields(); +} + +bool GuiControlProfile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + Sim::getGuiDataGroup()->addObject(this); + + // Make sure we have an up-to-date children profile + getChildrenProfile(); + + return true; +} + +void GuiControlProfile::onStaticModified(const char* slotName, const char* newValue) +{ + if ( mRefCount > 0 ) + { + if ( !dStricmp(slotName, "fontType") || + !dStricmp(slotName, "fontCharset") || + !dStricmp(slotName, "fontSize" ) ) + { + // Reload the font + mFont = GFont::create(mFontType, mFontSize, sFontCacheDirectory, mFontCharset); + if ( mFont == NULL ) + Con::errorf("Failed to load/create profile font (%s/%d)", mFontType, mFontSize); + } + } +} + +void GuiControlProfile::onDeleteNotify(SimObject *object) +{ + if (object == mChildrenProfile) + mChildrenProfile = NULL; +} + +GuiControlProfile* GuiControlProfile::getChildrenProfile() +{ + // We can early out if we still have a valid profile + if (mChildrenProfile) + return mChildrenProfile; + + // Attempt to find the profile specified + if (mChildrenProfileName) + { + GuiControlProfile *profile = dynamic_cast(Sim::findObject( mChildrenProfileName )); + + if( profile ) + setChildrenProfile(profile); + } + + return mChildrenProfile; +} + +void GuiControlProfile::setChildrenProfile(GuiControlProfile *prof) +{ + if(prof == mChildrenProfile) + return; + + // Clear the delete notification we previously set up + if (mChildrenProfile) + clearNotify(mChildrenProfile); + + mChildrenProfile = prof; + + // Make sure that the new profile will notify us when it is deleted + if (mChildrenProfile) + deleteNotify(mChildrenProfile); +} + +RectI GuiControlProfile::getBitmapArrayRect(U32 i) +{ + if(!mBitmapArrayRects.size()) + constructBitmapArray(); + + if( i >= mBitmapArrayRects.size()) + return RectI(0,0,0,0); + + return mBitmapArrayRects[i]; +} + +S32 GuiControlProfile::constructBitmapArray() +{ + if(mBitmapArrayRects.size()) + return mBitmapArrayRects.size(); + + if( mTextureObject.isNull() ) + { + if ( !mBitmapName || !mBitmapName[0] || !mTextureObject.set( mBitmapName, &GFXDefaultPersistentProfile, avar("%s() - mTextureObject (line %d)", __FUNCTION__, __LINE__) )) + return 0; + } + + GBitmap *bmp = mTextureObject->getBitmap(); + + //get the separator color + ColorI sepColor; + if ( !bmp || !bmp->getColor( 0, 0, sepColor ) ) + { + Con::errorf("Failed to create bitmap array from %s for profile %s - couldn't ascertain seperator color!", mBitmapName, getName()); + AssertFatal( false, avar("Failed to create bitmap array from %s for profile %s - couldn't ascertain seperator color!", mBitmapName, getName())); + return 0; + } + + //now loop through all the scroll pieces, and find the bounding rectangle for each piece in each state + S32 curY = 0; + + // ascertain the height of this row... + ColorI color; + mBitmapArrayRects.clear(); + while(curY < bmp->getHeight()) + { + // skip any sep colors + bmp->getColor( 0, curY, color); + if(color == sepColor) + { + curY++; + continue; + } + // ok, process left to right, grabbing bitmaps as we go... + S32 curX = 0; + while(curX < bmp->getWidth()) + { + bmp->getColor(curX, curY, color); + if(color == sepColor) + { + curX++; + continue; + } + S32 startX = curX; + while(curX < bmp->getWidth()) + { + bmp->getColor(curX, curY, color); + if(color == sepColor) + break; + curX++; + } + S32 stepY = curY; + while(stepY < bmp->getHeight()) + { + bmp->getColor(startX, stepY, color); + if(color == sepColor) + break; + stepY++; + } + mBitmapArrayRects.push_back(RectI(startX, curY, curX - startX, stepY - curY)); + } + // ok, now skip to the next separation color on column 0 + while(curY < bmp->getHeight()) + { + bmp->getColor(0, curY, color); + if(color == sepColor) + break; + curY++; + } + } + return mBitmapArrayRects.size(); +} + +void GuiControlProfile::incRefCount() +{ + if(!mRefCount++) + { + sFontCacheDirectory = Con::getVariable("$GUI::fontCacheDirectory"); + + //verify the font + mFont = GFont::create(mFontType, mFontSize, sFontCacheDirectory, mFontCharset); + if (mFont == NULL) + Con::errorf("Failed to load/create profile font (%s/%d)", mFontType, mFontSize); + + //verify the bitmap + if (mBitmapName && mBitmapName[0] && dStricmp(mBitmapName, "texhandle") != 0 && + !mTextureObject.set( mBitmapName, &GFXDefaultPersistentProfile, avar("%s() - mTextureObject (line %d)", __FUNCTION__, __LINE__) )) + Con::errorf("Failed to load profile bitmap (%s)",mBitmapName); + + // If we've got a special border, make sure it's usable. + if( mBorder == -1 || mBorder == -2 ) + constructBitmapArray(); + + } + + // Quick check to make sure our children profile is up-to-date + getChildrenProfile(); +} + +void GuiControlProfile::decRefCount() +{ + AssertFatal(mRefCount, "GuiControlProfile::decRefCount: zero ref count"); + if(!mRefCount) + return; + + if(!--mRefCount) + { + if (!mBitmapName || !mBitmapName[0] || + (mBitmapName && mBitmapName[0] && dStricmp(mBitmapName, "texhandle") != 0)) + mTextureObject = NULL; + } +} + +ConsoleMethod( GuiControlProfile, getStringWidth, S32, 3, 3, "( pString )" ) +{ + return object->mFont->getStrNWidth( argv[2], dStrlen( argv[2] ) ); +} + +ConsoleType( GuiProfile, TypeGuiProfile, GuiControlProfile* ) + +ConsoleSetType( TypeGuiProfile ) +{ + // Reference counting is now handled by the GuiControl. + // In fact this method should never be called because GuiControl uses + // a protected field set method. + + /* + GuiControlProfile *profile = NULL; + if(argc == 1) + Sim::findObject(argv[0], profile); + + AssertWarn(profile != NULL, avar("GuiControlProfile: requested gui profile (%s) does not exist.", argv[0])); + if(!profile) + profile = dynamic_cast(Sim::findObject("GuiDefaultProfile")); + + AssertFatal(profile != NULL, avar("GuiControlProfile: unable to find specified profile (%s) and GuiDefaultProfile does not exist!", argv[0])); + + GuiControlProfile **obj = (GuiControlProfile **)dptr; + if((*obj) == profile) + return; + + if(*obj) + (*obj)->decRefCount(); + + *obj = profile; + (*obj)->incRefCount(); + */ +} + +ConsoleGetType( TypeGuiProfile ) +{ + static char returnBuffer[256]; + + GuiControlProfile **obj = (GuiControlProfile**)dptr; + dSprintf(returnBuffer, sizeof(returnBuffer), "%s", *obj ? (*obj)->getName() ? (*obj)->getName() : (*obj)->getIdString() : ""); + return returnBuffer; +} + +//----------------------------------------------------------------------------- +// TypeRectSpacingI +//----------------------------------------------------------------------------- +ConsoleType( RectSpacingI, TypeRectSpacingI, RectSpacingI ) + +ConsoleGetType( TypeRectSpacingI ) +{ + RectSpacingI *rect = (RectSpacingI *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d %d %d %d", rect->top, rect->bottom, + rect->left, rect->right); + return returnBuffer; +} + +ConsoleSetType( TypeRectSpacingI ) +{ + if(argc == 1) + dSscanf(argv[0], "%d %d %d %d", &((RectSpacingI *) dptr)->top, &((RectSpacingI *) dptr)->bottom, + &((RectSpacingI *) dptr)->left, &((RectSpacingI *) dptr)->right); + else if(argc == 4) + *((RectSpacingI *) dptr) = RectSpacingI(dAtoi(argv[0]), dAtoi(argv[1]), dAtoi(argv[2]), dAtoi(argv[3])); + else + Con::printf("RectSpacingI must be set as { t, b, l, r } or \"t b l r\""); +} diff --git a/gui/core/guiTypes.h b/gui/core/guiTypes.h new file mode 100644 index 0000000..b0debc8 --- /dev/null +++ b/gui/core/guiTypes.h @@ -0,0 +1,478 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITYPES_H_ +#define _GUITYPES_H_ + +#ifndef _GFONT_H_ +#include "gfx/gFont.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _DYNAMIC_CONSOLETYPES_H_ +#include "console/dynamicTypes.h" +#endif + + +#include "gfx/gfxDevice.h" +#include "platform/event.h" + +class GBitmap; +class SFXProfile; + +/// Represents a single GUI event. +/// +/// This is passed around to all the relevant controls so they know what's going on. +struct GuiEvent +{ + U16 ascii; ///< ascii character code 'a', 'A', 'b', '*', etc (if device==keyboard) - possibly a uchar or something + U8 modifier; ///< SI_LSHIFT, etc + InputObjectInstances keyCode; ///< for unprintables, 'tab', 'return', ... + Point2I mousePoint; ///< for mouse events + U8 mouseClickCount; ///< to determine double clicks, etc... + U8 mouseAxis; ///< mousewheel axis (0 == X, 1 == Y) + F32 fval; ///< used for mousewheel events +}; + +/// Represent a mouse event with a 3D position and vector. +/// +/// This event used by the EditTSCtrl derived controls. +struct Gui3DMouseEvent : public GuiEvent +{ + Point3F vec; + Point3F pos; +}; + + +/// @name Docking Flag +/// @{ +/// @brief Docking Options available to all GuiControl subclasses. +namespace Docking +{ + enum + { + dockNone = BIT(0), ///< Do not align this control to it's parent, let the control specify it's position/extent (default) + dockClient = BIT(1), ///< Align this control to the client area available in the parent + dockTop = BIT(2), ///< Align this control to the topmost border of it's parent (Width will be parent width) + dockBottom = BIT(3), ///< Align this control to the bottommost border of it's parent (Width will be parent width) + dockLeft = BIT(4), ///< Align this control to the leftmost border of it's parent (Height will be parent height) + dockRight = BIT(5), ///< Align this control to the rightmost border of it's parent (Height will be parent height) + dockInvalid = BIT(6), ///< Default NOT specified docking mode, this allows old sizing to takeover when needed by controls + dockAny = dockClient | dockTop | dockBottom | dockLeft | dockRight + }; +}; +/// @} + + +/// @name Margin Padding Structure +/// @{ +struct RectSpacingI +{ + S32 left; + S32 top; + S32 bottom; + S32 right; + RectSpacingI() { left = right = top = bottom = 0; }; + RectSpacingI( S32 in_top, S32 in_bottom, S32 in_left, S32 in_right ) + { + top = in_top; + bottom = in_bottom; + left = in_left; + right = in_right; + } + void setAll( S32 value ) { left = right = top = bottom = value; }; + void set( S32 in_top, S32 in_bottom, S32 in_left, S32 in_right ) + { + top = in_top; + bottom = in_bottom; + left = in_left; + right = in_right; + } + void insetRect( RectI &rectRef ) + { + // Inset by padding + rectRef.point.x += left; + rectRef.point.y += top; + rectRef.extent.x -= (left + right ); + rectRef.extent.y -= (bottom + top ); + } + void expandRect( RectI &rectRef ) + { + // Inset by padding + rectRef.point.x -= left; + rectRef.point.y -= top; + rectRef.extent.x += (left + right ); + rectRef.extent.y += (bottom + top ); + } + + +}; + +DefineConsoleType( TypeRectSpacingI, RectSpacingI ); +/// @} + + +/// @name Axis-Aligned Edge Structure +/// @{ +/// +struct Edge +{ + Point2F normal; ///< The Normal of this edge + Point2I position;///< The Position of the edge + Point2I extent; ///< The X/Y extents of the edge + F32 margin; ///< The Size of the edge + + Edge(): normal(0.f,0.f), + position(0,0), + extent(0,0), + margin(1.f){}; + Edge( const Point2I &inPoint, const Point2F &inNormal ) + { + normal = inNormal; + margin = 2.f; + + if( normal.x == 1.f || normal.x == -1.f ) + { + // Vertical Edge + position.x = inPoint.x; + position.y = 0; + + extent.x = 1; + extent.y = 1; + } + else if( normal.y == 1.f || normal.y == -1.f ) + { + // Horizontal Edge + position.y = inPoint.y; + position.x = 0; + + extent.x = 1; + extent.y = 1; + } + else + AssertFatal(false,"Edge point constructor cannot construct an Edge without an axis-aligned normal."); + } + + // Copy Constructor + Edge( const Edge &inEdge ) + { + normal = inEdge.normal; + position = inEdge.position; + extent = inEdge.extent; + margin = inEdge.margin; + } + + // RectI cast operator overload + operator const RectI() const + { + if( normal.x == 1.f || normal.x == -1.f ) + { + // Vertical Edge + RectI retRect = RectI( position.x, position.y, 1, position.y + extent.y ); + // Expand Rect by Margin along the X Axis + retRect.inset(-margin,0); + + return retRect; + } + else if( normal.y == 1.f || normal.y == -1.f ) + { + // Horizontal Edge + RectI retRect = RectI( position.x, position.y , position.x + extent.x, 1 ); + // Expand Rect by Margin along the Y Axis + retRect.inset(0,-margin); + return retRect; + } + + // CodeReview this code only deals with axis-aligned edges [6/8/2007 justind] + AssertFatal(false,"Edge cast operator cannot construct a Rect from an Edge that is not axis-aligned."); + return RectI( 0,0,0,0 ); + } + + inline bool hit( const Edge &inEdge ) const + { + const RectI thisRect = *this; + const RectI thatRect = inEdge; + + return thisRect.overlaps( thatRect ); + } +}; +/// @} + + +struct EdgeRectI +{ + Edge left; + Edge top; + Edge bottom; + Edge right; + + EdgeRectI(){ } + + EdgeRectI( const RectI &inRect, F32 inMargin ) + { + // Left Edge + left.normal = Point2F( -1.f, 0.f ); + left.position.x= inRect.point.x; + left.position.y= 0; + left.extent = Point2I(inRect.point.y, inRect.point.y + inRect.extent.y); + left.margin = inMargin; + + // Right Edge + right.normal = Point2F( 1.f, 0.f ); + right.position.x = inRect.point.x + inRect.extent.x; + right.position.y = 0; + right.extent = Point2I(inRect.point.y, inRect.point.y + inRect.extent.y); + right.margin = inMargin; + + // Top Edge + top.normal = Point2F( 0.f, 1.f ); + top.position.y = inRect.point.y; + top.position.x = 0; + top.extent = Point2I(inRect.point.x + inRect.extent.x, inRect.point.x); + top.margin = inMargin; + + // Bottom Edge + bottom.normal = Point2F( 0.f, -1.f ); + bottom.position.y= inRect.point.y + inRect.extent.y; + bottom.position.x=0; + bottom.extent = Point2I(inRect.point.x + inRect.extent.x, inRect.point.x); + bottom.margin = inMargin; + } + + // Copy constructor + EdgeRectI( const EdgeRectI &inEdgeRect ) + { + left = inEdgeRect.left; + right = inEdgeRect.right; + top = inEdgeRect.top; + bottom = inEdgeRect.bottom; + } +}; + + +/// Represents the Sizing Options for a GuiControl +struct ControlSizing +{ + ControlSizing() + { + mDocking = Docking::dockInvalid; + mPadding.setAll( 0 ); + mInternalPadding.setAll( 0 ); + + // Default anchors to full top/left + mAnchorBottom = false; + mAnchorLeft = true; + mAnchorTop = true; + mAnchorRight = false; + }; + + S32 mDocking; ///< Docking Flag + + RectSpacingI mPadding; ///< Padding for each side of the control to have as spacing between other controls + /// For example 1,1,1,1 would mean one pixel at least of spacing between this control and the + /// one next to it. + RectSpacingI mInternalPadding; ///< Interior Spacing of the control + + + /// @name Anchoring + /// @{ + /// @brief Anchors are applied to @b ONLY controls that are children of any derivative of a + /// GuiContainer control. Anchors are applied when a parent is resized and a child + /// element should be resized to accommodate the new parent extent + /// + /// Anchors are specified as true or false and control whether a certain edge of a control + /// will be locked to a certain edge of a parent, when the parent resizes. Anchors are specified + /// as a Mask and therefore you may lock any number of edges to a parent container and when the parent + /// is resized, any locked edges on a control will remain the same distance from the parent edge it + /// is locked to, after the resize happens. + /// + bool mAnchorTop; ///< Anchor to the Top edge of the parent when created + bool mAnchorBottom; ///< Anchor to the Bottom edge of the parent when created + bool mAnchorLeft; ///< Anchor to the Left edge of the parent when created + bool mAnchorRight; ///< Anchor to the Right edge of the parent when created + /// @} + +}; + +class GuiCursor : public SimObject +{ +private: + typedef SimObject Parent; + StringTableEntry mBitmapName; + + Point2I mHotSpot; + Point2F mRenderOffset; + Point2I mExtent; + GFXTexHandle mTextureObject; + GFXTexHandle mTexturePicked; +public: + Point2I getHotSpot() { return mHotSpot; } + Point2I getExtent() { return mExtent; } + + DECLARE_CONOBJECT(GuiCursor); + GuiCursor(void); + ~GuiCursor(void); + static void initPersistFields(); + + bool onAdd(void); + void onRemove(); + void render(const Point2I &pos); + //rpg added + enum Statu + { + Cursor_Normal, + Cursor_Picked, + }; + U8 mStatu; + void setPickedBmp(StringTableEntry path); + void clearPickedBmp(); + //end rpg added +}; + +/// A GuiControlProfile is used by every GuiObject and is akin to a +/// datablock. It is used to control information that does not change +/// or is unlikely to change during execution of a program. It is also +/// a level of abstraction between script and GUI control so that you can +/// use the same control, say a button, and have it look completly different +/// just with a different profile. +class GuiControlProfile : public SimObject +{ +private: + typedef SimObject Parent; + +public: + static StringTableEntry sFontCacheDirectory; ///< Directory where Torque will store font *.uft files. + + S32 mRefCount; ///< Used to determine if any controls are using this profile + bool mTabable; ///< True if this object is accessable from using the tab key + + bool mCanKeyFocus; ///< True if the object can be given keyboard focus (in other words, made a first responder @see GuiControl) + bool mModal; ///< True if this is a Modeless dialog meaning it will pass input through instead of taking it all + + bool mOpaque; ///< True if this object is not translucent, and should draw a fill + ColorI mFillColor; ///< Fill color, this is used to fill the bounds of the control if it is opaque + ColorI mFillColorHL; ///< This is used instead of mFillColor if the object is highlighted + ColorI mFillColorNA; ///< This is used instead of mFillColor if the object is not active or disabled + ColorI mFillColorSEL; ///< This is used instead of mFillColor if the object is selected + + S32 mBorder; ///< For most controls, if mBorder is > 0 a border will be drawn, some controls use this to draw different types of borders however @see guiDefaultControlRender.cc + S32 mBorderThickness; ///< Border thickness + ColorI mBorderColor; ///< Border color, used to draw a border around the bounds if border is enabled + ColorI mBorderColorHL; ///< Used instead of mBorderColor when the object is highlighted + ColorI mBorderColorNA; ///< Used instead of mBorderColor when the object is not active or disabled + + ColorI mBevelColorHL; ///< Used for the high-light part of the bevel + ColorI mBevelColorLL; ///< Used for the low-light part of the bevel + + // font members + StringTableEntry mFontType; ///< Font face name for the control + S32 mFontSize; ///< Font size for the control + enum { + BaseColor = 0, + ColorHL, + ColorNA, + ColorSEL, + ColorUser0, + ColorUser1, + ColorUser2, + ColorUser3, + ColorUser4, + ColorUser5, + }; + ColorI mFontColors[10]; ///< Array of font colors used for drawText with escape characters for changing color mid-string + ColorI& mFontColor; ///< Main font color + ColorI& mFontColorHL; ///< Highlighted font color + ColorI& mFontColorNA; ///< Font color when object is not active/disabled + ColorI& mFontColorSEL; ///< Font color when object/text is selected + FontCharset mFontCharset; ///< Font character set + + Resource mFont; ///< Font resource + + enum AlignmentType + { + LeftJustify, + RightJustify, + CenterJustify + }; + + AlignmentType mAlignment; ///< Horizontal text alignment + bool mAutoSizeWidth; ///< Auto-size the width-bounds of the control to fit it's contents + bool mAutoSizeHeight; ///< Auto-size the height-bounds of the control to fit it's contents + bool mReturnTab; ///< Used in GuiTextEditCtrl to specify if a tab-event should be simulated when return is pressed. + bool mNumbersOnly; ///< For text controls, true if this should only accept numerical data + bool mMouseOverSelected; ///< True if this object should be "selected" while the mouse is over it + ColorI mCursorColor; ///< Color for the blinking cursor in text fields (for example) + + S32 mYPositionOffset; ///< For guiWindow and guiWindowCollapsed ctrls in window resize mode + + Point2I mTextOffset; ///< Text offset for the control + + // bitmap members + StringTableEntry mBitmapName; ///< Bitmap file name for the bitmap of the control + bool mUseBitmapArray; ///< Flag to use the bitmap array or to fallback to non-array rendering + GFXTexHandle mTextureObject; + Vector mBitmapArrayRects; ///< Used for controls which use an array of bitmaps such as checkboxes + + // sound members + SFXProfile *mSoundButtonDown; ///< Sound played when the object is "down" ie a button is pushed + SFXProfile *mSoundButtonOver; ///< Sound played when the mouse is over the object + + StringTableEntry mChildrenProfileName; ///< The name of the profile to use for the children controls + + /// Returns our children profile (and finds the profile if it hasn't been set yet) + GuiControlProfile* getChildrenProfile(); + + /// Sets the children profile for this profile + /// + /// @see GuiControlProfile + /// @param prof Tooltip profile to apply + void setChildrenProfile(GuiControlProfile *prof); +protected: + GuiControlProfile* mChildrenProfile; ///< Profile used with children controls (such as the scroll bar on a popup menu) when defined. + +public: + DECLARE_CONOBJECT(GuiControlProfile); + GuiControlProfile(); + ~GuiControlProfile(); + static void initPersistFields(); + + bool onAdd(); + + void onStaticModified(const char* slotName, const char* newValue = NULL ); + + /// Called when mProfileForChildren is deleted + virtual void onDeleteNotify(SimObject *object); + + /// This method creates an array of bitmaps from one single bitmap with + /// separator color. The separator color is whatever color is in pixel 0,0 + /// of the bitmap. For an example see darkWindow.png and some of the other + /// UI textures. It returns the number of bitmaps in the array it created + /// It also stores the sizes in the mBitmapArrayRects vector. + S32 constructBitmapArray(); + + /// This method returns the ith bitmap array rect, first ensuring that i is a + /// valid index into mBitmapArrayRects. If the vector is empty, we call + /// constructBitmapArray() automatically. If it is still empty, we return a + /// zeroed RectI. + RectI getBitmapArrayRect(U32 i); + + + void incRefCount(); + void decRefCount(); + + void setBitmapHandle(GFXTexHandle handle); + + static bool protectedSetBitmap( void *obj, const char *data ); +}; +DefineConsoleType(TypeGuiProfile, GuiControlProfile *); + +GFX_DeclareTextureProfile(GFXGuiCursorProfile); +GFX_DeclareTextureProfile(GFXDefaultGUIProfile); + +#endif //_GUITYPES_H diff --git a/gui/editor/guiControlListPopup.cpp b/gui/editor/guiControlListPopup.cpp new file mode 100644 index 0000000..c002fcb --- /dev/null +++ b/gui/editor/guiControlListPopup.cpp @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/controls/guiPopUpCtrl.h" +#include "gui/core/guiCanvas.h" +#include "gui/utility/guiInputCtrl.h" + +class GuiControlListPopUp : public GuiPopUpMenuCtrl +{ + typedef GuiPopUpMenuCtrl Parent; +public: + bool onAdd(); + + DECLARE_CONOBJECT(GuiControlListPopUp); + DECLARE_CATEGORY( "Gui Editor" ); +}; + +IMPLEMENT_CONOBJECT(GuiControlListPopUp); + +bool GuiControlListPopUp::onAdd() +{ + if(!Parent::onAdd()) + return false; + clear(); + + AbstractClassRep *guiCtrlRep = GuiControl::getStaticClassRep(); + AbstractClassRep *guiCanvasRep = GuiCanvas::getStaticClassRep(); + AbstractClassRep *guiInputRep = GuiInputCtrl::getStaticClassRep(); + + for(AbstractClassRep *rep = AbstractClassRep::getClassList(); rep; rep = rep->getNextClass()) + { + if( rep->isClass(guiCtrlRep)) + { + if(!rep->isClass(guiCanvasRep) && !rep->isClass(guiInputRep)) + addEntry(rep->getClassName(), 0); + } + } + + // We want to be alphabetical! + sort(); + + return true; +} diff --git a/gui/editor/guiDebugger.cpp b/gui/editor/guiDebugger.cpp new file mode 100644 index 0000000..1073b9c --- /dev/null +++ b/gui/editor/guiDebugger.cpp @@ -0,0 +1,682 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/guiDebugger.h" + +#include "gui/core/guiCanvas.h" +#include "gfx/gfxDrawUtil.h" +#include "core/volume.h" + + +IMPLEMENT_CONOBJECT(DbgFileView); + +static const char* itoa(S32 i) +{ + static char buf[32]; + dSprintf(buf, sizeof(buf), "%d", i); + return buf; +} + +DbgFileView::~DbgFileView() +{ + clear(); +} + +DbgFileView::DbgFileView() +{ + VECTOR_SET_ASSOCIATION(mFileView); + + mFileName = NULL; + mPCFileName = NULL; + mPCCurrentLine = -1; + + mBlockStart = -1; + mBlockEnd = -1; + + mFindString[0] = '\0'; + mFindLineNumber = -1; + + mSize.set(1, 0); +} + +ConsoleMethod(DbgFileView, setCurrentLine, void, 4, 4, "(int line, bool selected)" + "Set the current highlighted line.") +{ + object->setCurrentLine(dAtoi(argv[2]), dAtob(argv[3])); +} + +ConsoleMethod(DbgFileView, getCurrentLine, const char *, 2, 2, "()" + "Get the currently executing file and line, if any.\n\n" + "@returns A string containing the file, a tab, and then the line number." + " Use getField() with this.") +{ + S32 lineNum; + const char *file = object->getCurrentLine(lineNum); + char* ret = Con::getReturnBuffer(256); + dSprintf(ret, sizeof(ret), "%s\t%d", file, lineNum); + return ret; +} + +ConsoleMethod(DbgFileView, open, bool, 3, 3, "(string filename)" + "Open a file for viewing.\n\n" + "@note This loads the file from the local system.") +{ + return object->openFile(argv[2]); +} + +ConsoleMethod(DbgFileView, clearBreakPositions, void, 2, 2, "()" + "Clear all break points in the current file.") +{ + object->clearBreakPositions(); +} + +ConsoleMethod(DbgFileView, setBreakPosition, void, 3, 3, "(int line)" + "Set a breakpoint at the specified line.") +{ + object->setBreakPosition(dAtoi(argv[2])); +} + +ConsoleMethod(DbgFileView, setBreak, void, 3, 3, "(int line)" + "Set a breakpoint at the specified line.") +{ + object->setBreakPointStatus(dAtoi(argv[2]), true); +} + +ConsoleMethod(DbgFileView, removeBreak, void, 3, 3, "(int line)" + "Remove a breakpoint from the specified line.") +{ + object->setBreakPointStatus(dAtoi(argv[2]), false); +} + +ConsoleMethod(DbgFileView, findString, bool, 3, 3, "(string findThis)" + "Find the specified string in the currently viewed file and " + "scroll it into view.") +{ + return object->findString(argv[2]); +} + +//this value is the offset used in the ::onRender() method... +static S32 gFileXOffset = 44; + +void DbgFileView::AdjustCellSize() +{ + if (! bool(mFont)) + return; + S32 maxWidth = 1; + for (U32 i = 0; i < mFileView.size(); i++) + { + S32 cellWidth = gFileXOffset + mFont->getStrWidth((const UTF8 *)mFileView[i].text); + maxWidth = getMax(maxWidth, cellWidth); + } + + mCellSize.set(maxWidth, mFont->getHeight() + 2); + setSize(mSize); +} + +bool DbgFileView::onWake() +{ + if (! Parent::onWake()) + return false; + + //clear the mouse over var + mMouseOverVariable[0] = '\0'; + mbMouseDragging = false; + + //adjust the cellwidth to the maximum line length + AdjustCellSize(); + mSize.set(1, mFileView.size()); + + return true; +} + +void DbgFileView::addLine(const char *string, U32 strLen) +{ + // first compute the size + U32 size = 1; // for null + for(U32 i = 0; i < strLen; i++) + { + if(string[i] == '\t') + size += 3; + else if(string[i] != '\r') + size++; + } + FileLine fl; + fl.breakPosition = false; + fl.breakOnLine = false; + if(size) + { + fl.text = (char *) dMalloc(size); + + U32 dstIndex = 0; + for(U32 i = 0; i < strLen; i++) + { + if(string[i] == '\t') + { + fl.text[dstIndex] = ' '; + fl.text[dstIndex + 1] = ' '; + fl.text[dstIndex + 2] = ' '; + dstIndex += 3; + } + else if(string[i] != '\r') + fl.text[dstIndex++] = string[i]; + } + fl.text[dstIndex] = 0; + } + else + fl.text = NULL; + mFileView.push_back(fl); +} + +void DbgFileView::clear() +{ + for(Vector::iterator i = mFileView.begin(); i != mFileView.end(); i++) + dFree(i->text); + mFileView.clear(); +} + +bool DbgFileView::findString(const char *text) +{ + //make sure we have a valid string to find + if (!text || !text[0]) + return false; + + //see which line we start searching from + S32 curLine = 0; + bool searchAgain = false; + if (mFindLineNumber >= 0 && !dStricmp(mFindString, text)) + { + searchAgain = true; + curLine = mFindLineNumber; + } + else + mFindLineNumber = -1; + + //copy the search text + dStrncpy(mFindString, text, 255); + S32 length = dStrlen(mFindString); + + //loop through looking for the next instance + while (curLine < mFileView.size()) + { + char *curText; + if (curLine == mFindLineNumber && mBlockStart >= 0) + curText = &mFileView[curLine].text[mBlockStart + 1]; + else + curText = &mFileView[curLine].text[0]; + + //search for the string (the hard way... - apparently dStrupr is broken... + char *found = NULL; + char *curTextPtr = curText; + while (*curTextPtr != '\0') + { + if (!dStrnicmp(mFindString, curTextPtr, length)) + { + found = curTextPtr; + break; + } + else + curTextPtr++; + } + + //did we find it? + if (found) + { + //scroll first + mFindLineNumber = curLine; + scrollToLine(mFindLineNumber + 1); + + //then hilite + mBlockStart = (S32)(found - &mFileView[curLine].text[0]); + mBlockEnd = mBlockStart + length; + + return true; + } + else + curLine++; + } + + //didn't find anything - reset the vars for the next search + mBlockStart = -1; + mBlockEnd = -1; + mFindLineNumber = -1; + + setSelectedCell(Point2I(-1, -1)); + return false; +} + +void DbgFileView::setCurrentLine(S32 lineNumber, bool setCurrentLine) +{ + //update the line number + if (setCurrentLine) + { + mPCFileName = mFileName; + mPCCurrentLine = lineNumber; + mBlockStart = -1; + mBlockEnd = -1; + if (lineNumber >= 0) + scrollToLine(mPCCurrentLine); + } + else + { + scrollToLine(lineNumber); + } +} + +const char* DbgFileView::getCurrentLine(S32 &lineNumber) +{ + lineNumber = mPCCurrentLine; + return mPCFileName; +} + +bool DbgFileView::openFile(const char *fileName) +{ + if ((! fileName) || (! fileName[0])) + return false; + + StringTableEntry newFile = StringTable->insert(fileName); + if (mFileName == newFile) + return true; + + void *data = NULL; + U32 dataSize = 0; + Torque::FS::ReadFile(fileName, data, dataSize, true); + if(data == NULL) + { + Con::printf("DbgFileView: unable to open file %s.", fileName); + return false; + } + + //copy the file name + mFileName = newFile; + + //clear the old mFileView + clear(); + setSize(Point2I(1, 0)); + + //begin reading and parsing at each '\n' + char *parsePtr = (char *)data; + for(;;) { + char *tempPtr = dStrchr(parsePtr, '\n'); + if(tempPtr) + addLine(parsePtr, tempPtr - parsePtr); + else if(parsePtr[0]) + addLine(parsePtr, dStrlen(parsePtr)); + if(!tempPtr) + break; + parsePtr = tempPtr + 1; + } + //delete the buffer + delete [] static_cast(data); + + //set the file size + AdjustCellSize(); + setSize(Point2I(1, mFileView.size())); + + return true; +} + +void DbgFileView::scrollToLine(S32 lineNumber) +{ + GuiControl *parent = getParent(); + if (! parent) + return; + + S32 yOffset = (lineNumber - 1) * mCellSize.y; + + //see if the line is already visible + if (! (yOffset + getPosition().y >= 0 && yOffset + getPosition().y < parent->getHeight() - mCellSize.y)) + { + //reposition the control + S32 newYOffset = getMin(0, getMax(parent->getHeight() - getHeight(), (mCellSize.y * 4) - yOffset)); + setPosition(Point2I(getPosition().x, newYOffset)); + } + + //hilite the line + cellSelected(Point2I(0, lineNumber - 1)); +} + +S32 DbgFileView::findMouseOverChar(const char *text, S32 stringPosition) +{ + static char tempBuf[512]; + char *bufPtr = &tempBuf[1]; + + // Handle the empty string correctly. + if (text[0] == '\0') { + return -1; + } + + // Copy the line's text into the scratch buffer. + dStrncpy(tempBuf, text, 512); + + // Make sure we have a null terminator. + tempBuf[511] = '\0'; + + // Loop over the characters... + bool found = false; + bool finished = false; + do { + // Note the current character, then replace it with EOL. + char c = *bufPtr; + *bufPtr = '\0'; + // Is the resulting string long enough to include the current + // mouse position? + if ((S32)mFont->getStrWidth((const UTF8 *)tempBuf) > stringPosition) { + // Yep. + // Restore the character. + *bufPtr = c; + // Set bufPtr to point to the char under the mouse. + bufPtr--; + // Bail. + found = true; + finished = true; + } + else { + // Nope. + // Restore the character. + *bufPtr = c; + // Move on to the next character. + bufPtr++; + // Bail if EOL. + if (*bufPtr == '\0') finished = true; + } + } while (!finished); + + // Did we find a character under the mouse? + if (found) { + // If so, return its position. + return bufPtr - tempBuf; + } + // If not, return -1. + else return -1; +} + +bool DbgFileView::findMouseOverVariable() +{ + GuiCanvas *root = getRoot(); + AssertFatal(root, "Unable to get the root Canvas."); + + Point2I curMouse = root->getCursorPos(); + Point2I pt = globalToLocalCoord(curMouse); + + //find out which cell was hit + Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y)); + if(cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + S32 stringPosition = pt.x - gFileXOffset; + char tempBuf[256], *varNamePtr = &tempBuf[1]; + dStrcpy(tempBuf, mFileView[cell.y].text); + + //find the current mouse over char + S32 charNum = findMouseOverChar(mFileView[cell.y].text, stringPosition); + if (charNum >= 0) + { + varNamePtr = &tempBuf[charNum]; + } + else + { + mMouseOverVariable[0] = '\0'; + mMouseOverValue[0] = '\0'; + return false; + } + + //now make sure we can go from the current cursor mPosition to the beginning of a var name + bool found = false; + while (varNamePtr >= &tempBuf[0]) + { + if (*varNamePtr == '%' || *varNamePtr == '$') + { + found = true; + break; + } + else if ((dToupper(*varNamePtr) >= 'A' && dToupper(*varNamePtr) <= 'Z') || + (*varNamePtr >= '0' && *varNamePtr <= '9') || *varNamePtr == '_' || *varNamePtr == ':') + { + varNamePtr--; + } + else + { + break; + } + } + + //mouse wasn't over a possible variable name + if (! found) + { + mMouseOverVariable[0] = '\0'; + mMouseOverValue[0] = '\0'; + return false; + } + + //set the var char start positions + mMouseVarStart = varNamePtr - tempBuf; + + //now copy the (possible) var name into the buf + char *tempPtr = &mMouseOverVariable[0]; + + //copy the leading '%' or '$' + *tempPtr++ = *varNamePtr++; + + //now copy letters and numbers until the end of the name + while ((dToupper(*varNamePtr) >= 'A' && dToupper(*varNamePtr) <= 'Z') || + (*varNamePtr >= '0' && *varNamePtr <= '9') || *varNamePtr == '_' || *varNamePtr == ':') + { + *tempPtr++ = *varNamePtr++; + } + *tempPtr = '\0'; + + //set the var char end positions + mMouseVarEnd = varNamePtr - tempBuf; + + return true; + } + return false; +} + +void DbgFileView::clearBreakPositions() +{ + for(Vector::iterator i = mFileView.begin(); i != mFileView.end(); i++) + { + i->breakPosition = false; + i->breakOnLine = false; + } +} + +void DbgFileView::setBreakPosition(U32 line) +{ + if(line > mFileView.size()) + return; + mFileView[line-1].breakPosition = true; +} + +void DbgFileView::setBreakPointStatus(U32 line, bool set) +{ + if(line > mFileView.size()) + return; + mFileView[line-1].breakOnLine = set; +} + +void DbgFileView::onPreRender() +{ + setUpdate(); + char oldVar[256]; + dStrcpy(oldVar, mMouseOverVariable); + bool found = findMouseOverVariable(); + if (found && mPCCurrentLine >= 0) + { + //send the query only when the var changes + if (dStricmp(oldVar, mMouseOverVariable)) + Con::executef("DbgSetCursorWatch", mMouseOverVariable); + } + else + Con::executef("DbgSetCursorWatch", ""); +} + +void DbgFileView::onMouseDown(const GuiEvent &event) +{ + if (! mActive) + { + Parent::onMouseDown(event); + return; + } + + Point2I pt = globalToLocalCoord(event.mousePoint); + bool doubleClick = (event.mouseClickCount > 1); + + //find out which cell was hit + Point2I cell((pt.x < 0 ? -1 : pt.x / mCellSize.x), (pt.y < 0 ? -1 : pt.y / mCellSize.y)); + if(cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + //if we clicked on the breakpoint mark + if (pt.x >= 0 && pt.x <= 12) + { + //toggle the break point + if (mFileView[cell.y].breakPosition) + { + if (mFileView[cell.y].breakOnLine) + Con::executef(this, "onRemoveBreakPoint", itoa(cell.y + 1)); + else + Con::executef(this, "onSetBreakPoint", itoa(cell.y + 1)); + } + } + else + { + Point2I prevSelected = mSelectedCell; + Parent::onMouseDown(event); + mBlockStart= -1; + mBlockEnd = -1; + + //open the file view + if (mSelectedCell.y == prevSelected.y && doubleClick && mMouseOverVariable[0]) + { + Con::executef(this, "onSetWatch", mMouseOverVariable); + mBlockStart = mMouseVarStart; + mBlockEnd = mMouseVarEnd; + } + else + { + S32 stringPosition = pt.x - gFileXOffset; + + //find which character we're over + S32 charNum = findMouseOverChar(mFileView[mSelectedCell.y].text, stringPosition); + if (charNum >= 0) + { + //lock the mouse + mouseLock(); + setFirstResponder(); + + //set the block hilite start and end + mbMouseDragging = true; + mMouseDownChar = charNum; + } + } + } + } + else + { + Parent::onMouseDown(event); + } +} + +void DbgFileView::onMouseDragged(const GuiEvent &event) +{ + if (mbMouseDragging) + { + Point2I pt = globalToLocalCoord(event.mousePoint); + S32 stringPosition = pt.x - gFileXOffset; + + //find which character we're over + S32 charNum = findMouseOverChar(mFileView[mSelectedCell.y].text, stringPosition); + if (charNum >= 0) + { + if (charNum < mMouseDownChar) + { + + mBlockEnd = mMouseDownChar + 1; + mBlockStart = charNum; + } + else + { + mBlockEnd = charNum + 1; + mBlockStart = mMouseDownChar; + } + } + + //otherwise, the cursor is past the end of the string + else + { + mBlockStart = mMouseDownChar; + mBlockEnd = dStrlen(mFileView[mSelectedCell.y].text) + 1; + } + } +} + +void DbgFileView::onMouseUp(const GuiEvent &) +{ + //unlock the mouse + mouseUnlock(); + + mbMouseDragging = false; +} + +void DbgFileView::onRenderCell(Point2I offset, Point2I cell, bool selected, bool) +{ + Point2I cellOffset = offset; + cellOffset.x += 4; + + //draw the break point marks + if (mFileView[cell.y].breakOnLine) + { + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColorHL); + GFX->getDrawUtil()->drawText(mFont, cellOffset, "#"); + } + else if (mFileView[cell.y].breakPosition) + { + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColor); + GFX->getDrawUtil()->drawText(mFont, cellOffset, "-"); + } + cellOffset.x += 8; + + //draw in the "current line" indicator + if (mFileName == mPCFileName && (cell.y + 1 == mPCCurrentLine)) + { + GFX->getDrawUtil()->setBitmapModulation(mProfile->mFontColorHL); + GFX->getDrawUtil()->drawText(mFont, cellOffset, "=>"); + } + + //by this time, the cellOffset has been incremented by 44 - the value of gFileXOffset + cellOffset.x += 32; + + //hilite the line if selected + if (selected) + { + if (mBlockStart == -1) + { + GFX->getDrawUtil()->drawRectFill(RectI(cellOffset.x - 2, cellOffset.y - 3, + mCellSize.x + 4, mCellSize.y + 6), mProfile->mFillColorHL); + } + else if (mBlockStart >= 0 && mBlockEnd > mBlockStart && mBlockEnd <= S32(dStrlen(mFileView[cell.y].text) + 1)) + { + S32 startPos, endPos; + char tempBuf[256]; + dStrcpy(tempBuf, mFileView[cell.y].text); + + //get the end coord + tempBuf[mBlockEnd] = '\0'; + endPos = mFont->getStrWidth((const UTF8 *)tempBuf); + + //get the start coord + tempBuf[mBlockStart] = '\0'; + startPos = mFont->getStrWidth((const UTF8 *)tempBuf); + + //draw the hilite + GFX->getDrawUtil()->drawRectFill(RectI(cellOffset.x + startPos, cellOffset.y - 3, endPos - startPos + 2, mCellSize.y + 6), mProfile->mFillColorHL); + } + } + + //draw the line of text + GFX->getDrawUtil()->setBitmapModulation(mFileView[cell.y].breakOnLine ? mProfile->mFontColorHL : mProfile->mFontColor); + GFX->getDrawUtil()->drawText(mFont, cellOffset, mFileView[cell.y].text); +} diff --git a/gui/editor/guiDebugger.h b/gui/editor/guiDebugger.h new file mode 100644 index 0000000..8d5f6f8 --- /dev/null +++ b/gui/editor/guiDebugger.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIDEBUGGER_H_ +#define _GUIDEBUGGER_H_ + +#ifndef _GUIARRAYCTRL_H_ +#include "gui/core/guiArrayCtrl.h" +#endif + +class DbgFileView : public GuiArrayCtrl +{ + private: + + typedef GuiArrayCtrl Parent; + + struct FileLine + { + bool breakPosition; + bool breakOnLine; + char *text; + }; + + Vector mFileView; + + StringTableEntry mFileName; + + void AdjustCellSize(); + + //used to display the program counter + StringTableEntry mPCFileName; + S32 mPCCurrentLine; + + //vars used to highlight the selected line segment for copying + bool mbMouseDragging; + S32 mMouseDownChar; + S32 mBlockStart; + S32 mBlockEnd; + + char mMouseOverVariable[256]; + char mMouseOverValue[256]; + S32 findMouseOverChar(const char *text, S32 stringPosition); + bool findMouseOverVariable(); + S32 mMouseVarStart; + S32 mMouseVarEnd; + + //find vars + char mFindString[256]; + S32 mFindLineNumber; + + public: + + DbgFileView(); + ~DbgFileView(); + + DECLARE_CONOBJECT(DbgFileView); + DECLARE_CATEGORY( "Gui Editor" ); + + bool onWake(); + + void clear(); + void clearBreakPositions(); + + void setCurrentLine(S32 lineNumber, bool setCurrentLine); + const char *getCurrentLine(S32 &lineNumber); + bool openFile(const char *fileName); + void scrollToLine(S32 lineNumber); + void setBreakPointStatus(U32 lineNumber, bool value); + void setBreakPosition(U32 line); + void addLine(const char *text, U32 textLen); + + bool findString(const char *text); + + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + + void onPreRender(); + void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); +}; + +#endif //_GUI_DEBUGGER_H diff --git a/gui/editor/guiEditCtrl.cpp b/gui/editor/guiEditCtrl.cpp new file mode 100644 index 0000000..0b83b87 --- /dev/null +++ b/gui/editor/guiEditCtrl.cpp @@ -0,0 +1,2572 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/guiEditCtrl.h" + +#include "core/frameAllocator.h" +#include "core/stream/fileStream.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/containers/guiScrollCtrl.h" +#include "core/strings/stringUnit.h" + + +IMPLEMENT_CONOBJECT( GuiEditCtrl ); + + +StringTableEntry GuiEditCtrl::smGuidesPropertyName[ 2 ]; + + +//----------------------------------------------------------------------------- + +GuiEditCtrl::GuiEditCtrl() + : mCurrentAddSet( NULL ), + mContentControl( NULL ), + mGridSnap( 0, 0), + mDragBeginPoint( -1, -1 ), + mSnapToControls( true ), + mSnapToEdges( true ), + mSnapToCenters( true ), + mSnapToGuides( true ), + mSnapSensitivity( 2 ), + mFullBoxSelection( false ), + mDrawBorderLines( true ), + mDrawGuides( true ) +{ + VECTOR_SET_ASSOCIATION( mSelectedControls ); + VECTOR_SET_ASSOCIATION( mDragBeginPoints ); + VECTOR_SET_ASSOCIATION( mSnapHits[ 0 ] ); + VECTOR_SET_ASSOCIATION( mSnapHits[ 1 ] ); + + mActive = true; + mDotSB = NULL; + + mSnapped[ SnapVertical ] = false; + mSnapped[ SnapHorizontal ] = false; + + mDragGuide[ GuideVertical ] = false; + mDragGuide[ GuideHorizontal ] = false; + + if( !smGuidesPropertyName[ GuideVertical ] ) + smGuidesPropertyName[ GuideVertical ] = StringTable->insert( "guidesVertical" ); + if( !smGuidesPropertyName[ GuideHorizontal ] ) + smGuidesPropertyName[ GuideHorizontal ] = StringTable->insert( "guidesHorizontal" ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::initPersistFields() +{ + addGroup( "Snapping" ); + addField( "snapToControls", TypeBool, Offset( mSnapToControls, GuiEditCtrl ), + "If true, edge and center snapping will work against controls." ); + addField( "snapToGuides", TypeBool, Offset( mSnapToGuides, GuiEditCtrl ), + "If true, edge and center snapping will work against guides." ); + addField( "snapToEdges", TypeBool, Offset( mSnapToEdges, GuiEditCtrl ), + "If true, selection edges will snap into alignment when moved or resized." ); + addField( "snapToCenters", TypeBool, Offset( mSnapToCenters, GuiEditCtrl ), + "If true, selection centers will snap into alignment when moved or resized." ); + addField( "snapSensitivity", TypeS32, Offset( mSnapSensitivity, GuiEditCtrl ), + "Distance in pixels that edge and center snapping will work across." ); + endGroup( "Snapping" ); + + addGroup( "Selection" ); + addField( "fullBoxSelection", TypeBool, Offset( mFullBoxSelection, GuiEditCtrl ), + "If true, rectangle selection will only select controls fully inside the drag rectangle. (default)" ); + endGroup( "Selection" ); + + addGroup( "Rendering" ); + addField( "drawBorderLines", TypeBool, Offset( mDrawBorderLines, GuiEditCtrl ), + "If true, lines will be drawn extending along the edges of selected objects." ); + addField( "drawGuides", TypeBool, Offset( mDrawGuides, GuiEditCtrl ), + "If true, guides will be included in rendering." ); + endGroup( "Rendering" ); + + Parent::initPersistFields(); +} + +//============================================================================= +// Events. +//============================================================================= +// MARK: ---- Events ---- + +//----------------------------------------------------------------------------- + +bool GuiEditCtrl::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if( !mTrash.registerObject() ) + return false; + if( !mSelectedSet.registerObject() ) + return false; + if( !mUndoManager.registerObject() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onRemove() +{ + Parent::onRemove(); + + mDotSB = NULL; + mTrash.unregisterObject(); + mSelectedSet.unregisterObject(); + mUndoManager.unregisterObject(); +} + +//----------------------------------------------------------------------------- + +bool GuiEditCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + // Set GUI Controls to DesignTime mode + GuiControl::smDesignTime = true; + GuiControl::smEditorHandle = this; + + setEditMode(true); + + // TODO: paxorr: I'm not sure this is the best way to set these defaults. + bool snap = Con::getBoolVariable("$pref::GuiEditor::snap2grid",false); + U32 grid = Con::getIntVariable("$pref::GuiEditor::snap2gridsize",8); + Con::setBoolVariable("$pref::GuiEditor::snap2grid", snap); + Con::setIntVariable("$pref::GuiEditor::snap2gridsize",grid); + + setSnapToGrid( snap ? grid : 0); + + return true; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onSleep() +{ + // Set GUI Controls to run time mode + GuiControl::smDesignTime = false; + GuiControl::smEditorHandle = NULL; + + Parent::onSleep(); +} + +//----------------------------------------------------------------------------- + +bool GuiEditCtrl::onKeyDown(const GuiEvent &event) +{ + if (! mActive) + return Parent::onKeyDown(event); + + if (!(event.modifier & SI_PRIMARY_CTRL)) + { + switch(event.keyCode) + { + case KEY_BACKSPACE: + case KEY_DELETE: + deleteSelection(); + Con::executef(this, "onDelete"); + return true; + default: + break; + } + } + return false; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onMouseDown(const GuiEvent &event) +{ + if (! mActive) + { + Parent::onMouseDown(event); + return; + } + if(!mContentControl) + return; + + setFirstResponder(); + mouseLock(); + + if( !mCurrentAddSet ) + setCurrentAddSet( mContentControl, false ); + + mLastMousePos = globalToLocalCoord( event.mousePoint ); + + // Check whether we've hit a guide. If so, start a guide drag. + // Don't do this if SHIFT is down. + + if( !( event.modifier & SI_SHIFT ) ) + { + for( U32 axis = 0; axis < 2; ++ axis ) + { + const S32 guide = findGuide( ( guideAxis ) axis, event.mousePoint, 1 ); + if( guide != -1 ) + { + mMouseDownMode = DragGuide; + + mDragGuide[ axis ] = true; + mDragGuideIndex[ axis ] = guide; + } + } + + if( mMouseDownMode == DragGuide ) + return; + } + + // Check whether we have hit a sizing knob on any of the currently selected + // controls. + + for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i ) + { + GuiControl* ctrl = mSelectedControls[ i ]; + + Point2I cext = ctrl->getExtent(); + Point2I ctOffset = globalToLocalCoord( ctrl->localToGlobalCoord( Point2I( 0, 0 ) ) ); + + RectI box( ctOffset.x, ctOffset.y, cext.x, cext.y ); + + if( ( mSizingMode = ( GuiEditCtrl::sizingModes ) getSizingHitKnobs( mLastMousePos, box ) ) != 0 ) + { + mMouseDownMode = SizingSelection; + mLastDragPos = event.mousePoint; + + // undo + Con::executef( this, "onPreEdit", Con::getIntArg( getSelectedSet().getId() ) ); + return; + } + } + + //find the control we clicked + GuiControl* ctrl = mContentControl->findHitControl(mLastMousePos, mCurrentAddSet->mLayer); + + bool handledEvent = ctrl->onMouseDownEditor( event, localToGlobalCoord( Point2I(0,0) ) ); + if( handledEvent == true ) + { + // The Control handled the event and requested the edit ctrl + // *NOT* act on it. The dude abides. + return; + } + else if( event.modifier & SI_SHIFT ) + { + startDragRectangle( event.mousePoint ); + mDragAddSelection = true; + } + else if ( selectionContains(ctrl) ) + { + // We hit a selected control. If the multiselect key is pressed, + // deselect the control. Otherwise start a drag move. + + if( event.modifier & SI_MULTISELECT ) + { + removeSelection( ctrl ); + + //set the mode + mMouseDownMode = Selecting; + } + else + { + startDragMove( event.mousePoint ); + } + } + else + { + // We clicked an unselected control. + + if( ctrl == getContentControl() ) + { + // Clicked in toplevel control. Start a rectangle selection. + + startDragRectangle( event.mousePoint ); + mDragAddSelection = false; + } + else if( event.modifier & SI_MULTISELECT ) + addSelection( ctrl ); + else + { + // Clicked on child control. Start move. + + clearSelection(); + addSelection( ctrl ); + + startDragMove( event.mousePoint ); + } + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onMouseUp(const GuiEvent &event) +{ + if (! mActive || !mContentControl || !mCurrentAddSet ) + { + Parent::onMouseUp(event); + return; + } + + //find the control we clicked + GuiControl *ctrl = mContentControl->findHitControl(mLastMousePos, mCurrentAddSet->mLayer); + + bool handledEvent = ctrl->onMouseUpEditor( event, localToGlobalCoord( Point2I(0,0) ) ); + if( handledEvent == true ) + { + // The Control handled the event and requested the edit ctrl + // *NOT* act on it. The dude abides. + return; + } + + //unlock the mouse + mouseUnlock(); + + // Reset Drag Axis Alignment Information + mDragBeginPoint.set(-1,-1); + mDragBeginPoints.clear(); + + mLastMousePos = globalToLocalCoord(event.mousePoint); + if( mMouseDownMode == DragGuide ) + { + // Check to see if the mouse has moved off the canvas. If so, + // remove the guides being dragged. + + for( U32 axis = 0; axis < 2; ++ axis ) + if( mDragGuide[ axis ] && !getContentControl()->getGlobalBounds().pointInRect( event.mousePoint ) ) + mGuides[ axis ].erase( mDragGuideIndex[ axis ] ); + } + else if( mMouseDownMode == DragSelecting ) + { + // If not multiselecting, clear the current selection. + + if( !( event.modifier & SI_MULTISELECT ) && !mDragAddSelection ) + clearSelection(); + + RectI rect; + getDragRect( rect ); + + // If the region is somewhere less than at least 2x2, count this as a + // normal, non-rectangular selection. + + if( rect.extent.x <= 2 && rect.extent.y <= 2 ) + addSelectControlAt( rect.point ); + else + { + // Use HIT_AddParentHits by default except if ALT is pressed. + // Use HIT_ParentPreventsChildHit if ALT+CTRL is pressed. + + U32 hitFlags = 0; + if( !( event.modifier & SI_PRIMARY_ALT ) ) + hitFlags |= HIT_AddParentHits; + if( event.modifier & SI_PRIMARY_ALT && event.modifier & SI_CTRL ) + hitFlags |= HIT_ParentPreventsChildHit; + + addSelectControlsInRegion( rect, hitFlags ); + } + } + else if( ctrl == getContentControl() && mMouseDownMode == Selecting ) + setCurrentAddSet( NULL, true ); + + // deliver post edit event if we've been editing + // note: paxorr: this may need to be moved earlier, if the selection has changed. + // undo + if(mMouseDownMode == SizingSelection || mMouseDownMode == MovingSelection) + Con::executef(this, "onPostEdit", Con::getIntArg(getSelectedSet().getId())); + + //reset the mouse mode + setFirstResponder(); + mMouseDownMode = Selecting; + mSizingMode = sizingNone; + + // Clear snapping state. + + mSnapped[ SnapVertical ] = false; + mSnapped[ SnapHorizontal ] = false; + + mSnapTargets[ SnapVertical ] = NULL; + mSnapTargets[ SnapHorizontal ] = NULL; + + // Clear guide drag state. + + mDragGuide[ GuideVertical ] = false; + mDragGuide[ GuideHorizontal ] = false; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onMouseDragged( const GuiEvent &event ) +{ + if( !mActive || !mContentControl || !mCurrentAddSet ) + { + Parent::onMouseDragged(event); + return; + } + + if( !mCurrentAddSet ) + mCurrentAddSet = mContentControl; + + Point2I mousePoint = globalToLocalCoord( event.mousePoint ); + + //find the control we clicked + GuiControl *ctrl = mContentControl->findHitControl( mousePoint, mCurrentAddSet->mLayer ); + + bool handledEvent = ctrl->onMouseDraggedEditor( event, localToGlobalCoord( Point2I(0,0) ) ); + if( handledEvent == true ) + { + // The Control handled the event and requested the edit ctrl + // *NOT* act on it. The dude abides. + return; + } + + if( mMouseDownMode == DragGuide ) + { + for( U32 axis = 0; axis < 2; ++ axis ) + if( mDragGuide[ axis ] ) + { + // Set the guide to the coordinate of the mouse cursor + // on the guide's axis. + + Point2I point = event.mousePoint; + point -= localToGlobalCoord( Point2I( 0, 0 ) ); + point[ axis ] = mClamp( point[ axis ], 0, getExtent()[ axis ] - 1 ); + + mGuides[ axis ][ mDragGuideIndex[ axis ] ] = point[ axis ]; + } + } + else if( mMouseDownMode == SizingSelection ) + { + // Snap the mouse cursor to grid if active. Do this on the mouse cursor so that we handle + // incremental drags correctly. + + Point2I mousePoint = event.mousePoint; + snapToGrid( mousePoint ); + + Point2I delta = mousePoint - mLastDragPos; + + // If CTRL is down, apply smart snapping. + + if( event.modifier & SI_CTRL ) + { + RectI selectionBounds = getSelectionBounds(); + + doSnapping( event, selectionBounds, delta ); + } + else + { + mSnapped[ SnapVertical ] = false; + mSnapped[ SnapHorizontal ] = false; + } + + // If ALT is down, do a move instead of a resize on the control + // knob's axis. Otherwise resize. + + if( event.modifier & SI_PRIMARY_ALT ) + { + if( !( mSizingMode & sizingLeft ) && !( mSizingMode & sizingRight ) ) + { + mSnapped[ SnapVertical ] = false; + delta.x = 0; + } + if( !( mSizingMode & sizingTop ) && !( mSizingMode & sizingBottom ) ) + { + mSnapped[ SnapHorizontal ] = false; + delta.y = 0; + } + + moveSelection( delta ); + } + else + resizeControlsInSelectionBy( delta, mSizingMode ); + + // Remember drag point. + + mLastDragPos = mousePoint; + } + else if (mMouseDownMode == MovingSelection && mSelectedControls.size()) + { + Point2I delta = mousePoint - mLastMousePos; + RectI selectionBounds = getSelectionBounds(); + + // Apply snaps. + + doSnapping( event, selectionBounds, delta ); + + //RDTODO: to me seems to be in need of revision + // Do we want to align this drag to the X and Y axes within a certain threshold? + if( event.modifier & SI_SHIFT && !( event.modifier & SI_PRIMARY_ALT ) ) + { + Point2I dragTotalDelta = event.mousePoint - localToGlobalCoord( mDragBeginPoint ); + if( dragTotalDelta.y < 10 && dragTotalDelta.y > -10 ) + { + for(S32 i = 0; i < mSelectedControls.size(); i++) + { + Point2I selCtrlPos = mSelectedControls[i]->getPosition(); + Point2I snapBackPoint( selCtrlPos.x, mDragBeginPoints[i].y); + // This is kind of nasty but we need to snap back if we're not at origin point with selection - JDD + if( selCtrlPos.y != mDragBeginPoints[i].y ) + mSelectedControls[i]->setPosition( snapBackPoint ); + } + delta.y = 0; + } + if( dragTotalDelta.x < 10 && dragTotalDelta.x > -10 ) + { + for(S32 i = 0; i < mSelectedControls.size(); i++) + { + Point2I selCtrlPos = mSelectedControls[i]->getPosition(); + Point2I snapBackPoint( mDragBeginPoints[i].x, selCtrlPos.y); + // This is kind of nasty but we need to snap back if we're not at origin point with selection - JDD + if( selCtrlPos.x != mDragBeginPoints[i].x ) + mSelectedControls[i]->setPosition( snapBackPoint ); + } + delta.x = 0; + } + } + + if( delta.x || delta.y ) + moveSelection( delta ); + + // find the current control under the mouse + + canHitSelectedControls( false ); + GuiControl *inCtrl = mContentControl->findHitControl(mousePoint, mCurrentAddSet->mLayer); + canHitSelectedControls( true ); + + // find the nearest control up the heirarchy from the control the mouse is in + // that is flagged as a container. + while(! inCtrl->mIsContainer) + inCtrl = inCtrl->getParent(); + + // if the control under the mouse is not our parent, move the selected controls + // into the new parent. + if(mSelectedControls[0]->getParent() != inCtrl && inCtrl->mIsContainer) + { + moveSelectionToCtrl(inCtrl); + setCurrentAddSet(inCtrl,false); + } + + mLastMousePos += delta; + } + else + mLastMousePos = mousePoint; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onRightMouseDown(const GuiEvent &event) +{ + if (! mActive || !mContentControl) + { + Parent::onRightMouseDown(event); + return; + } + setFirstResponder(); + + //search for the control hit in any layer below the edit layer + GuiControl *hitCtrl = mContentControl->findHitControl(globalToLocalCoord(event.mousePoint), mLayer - 1); + if (hitCtrl != mCurrentAddSet) + { + clearSelection(); + mCurrentAddSet = hitCtrl; + } + // select the parent if we right-click on the current add set + else if( mCurrentAddSet != mContentControl) + { + mCurrentAddSet = hitCtrl->getParent(); + select(hitCtrl); + } + + //Design time mouse events + GuiEvent designEvent = event; + designEvent.mousePoint = mLastMousePos; + hitCtrl->onRightMouseDownEditor( designEvent, localToGlobalCoord( Point2I(0,0) ) ); + +} + +//============================================================================= +// Rendering. +//============================================================================= +// MARK: ---- Rendering ---- + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onPreRender() +{ + setUpdate(); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + Point2I ctOffset; + Point2I cext; + bool keyFocused = isFirstResponder(); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + if (mActive) + { + if (mCurrentAddSet) + { + // draw a white frame inset around the current add set. + cext = mCurrentAddSet->getExtent(); + ctOffset = mCurrentAddSet->localToGlobalCoord(Point2I(0,0)); + RectI box(ctOffset.x, ctOffset.y, cext.x, cext.y); + + box.inset( -5, -5 ); + drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) ); + box.inset( 1, 1 ); + drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) ); + box.inset( 1, 1 ); + drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) ); + box.inset( 1, 1 ); + drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) ); + box.inset( 1, 1 ); + drawer->drawRect( box, ColorI( 50, 101, 152, 128 ) ); + } + Vector::iterator i; + bool multisel = mSelectedControls.size() > 1; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + { + GuiControl *ctrl = (*i); + cext = ctrl->getExtent(); + ctOffset = ctrl->localToGlobalCoord(Point2I(0,0)); + RectI box(ctOffset.x,ctOffset.y, cext.x, cext.y); + ColorI nutColor = multisel ? ColorI( 255, 255, 255, 100 ) : ColorI( 0, 0, 0, 100 ); + ColorI outlineColor = multisel ? ColorI( 0, 0, 0, 100 ) : ColorI( 255, 255, 255, 100 ); + if(!keyFocused) + nutColor.set( 128, 128, 128, 100 ); + + drawNuts(box, outlineColor, nutColor); + } + } + + renderChildControls(offset, updateRect); + + // Draw selection rectangle. + + if( mActive && mMouseDownMode == DragSelecting ) + { + RectI b; + getDragRect(b); + b.point += offset; + + // Draw outline. + + drawer->drawRect( b, ColorI( 100, 100, 100, 128 ) ); + + // Draw fill. + + b.inset( 1, 1 ); + drawer->drawRectFill( b, ColorI( 150, 150, 150, 128 ) ); + } + + // Draw grid. + + if( mActive && mGridSnap.x && mGridSnap.y ) + { + Point2I cext = getContentControl()->getExtent(); + Point2I coff = getContentControl()->localToGlobalCoord(Point2I(0,0)); + + // create point-dots + const Point2I& snap = mGridSnap; + U32 maxdot = (U32)(mCeil(cext.x / (F32)snap.x) - 1) * (U32)(mCeil(cext.y / (F32)snap.y) - 1); + + if( mDots.isNull() || maxdot != mDots->mNumVerts) + { + mDots.set(GFX, maxdot, GFXBufferTypeStatic); + + U32 ndot = 0; + mDots.lock(); + for(U32 ix = snap.x; ix < cext.x; ix += snap.x) + { + for(U32 iy = snap.y; ndot < maxdot && iy < cext.y; iy += snap.y) + { + mDots[ndot].color.set( 50, 50, 254, 100 ); + mDots[ndot].point.x = F32(ix + coff.x); + mDots[ndot].point.y = F32(iy + coff.y); + ndot++; + } + } + mDots.unlock(); + AssertFatal(ndot <= maxdot, "dot overflow"); + AssertFatal(ndot == maxdot, "dot underflow"); + } + + if (!mDotSB) + { + GFXStateBlockDesc dotdesc; + dotdesc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + mDotSB = GFX->createStateBlock( dotdesc ); + } + + GFX->setStateBlock(mDotSB); + + // draw the points. + GFX->setVertexBuffer( mDots ); + GFX->drawPrimitive( GFXPointList, 0, mDots->mNumVerts ); + } + + // Draw snapping lines. + + if( mActive && getContentControl() ) + { + RectI bounds = getContentControl()->getGlobalBounds(); + + // Draw guide lines. + + if( mDrawGuides ) + { + for( U32 axis = 0; axis < 2; ++ axis ) + { + for( U32 i = 0, num = mGuides[ axis ].size(); i < num; ++ i ) + drawCrossSection( axis, mGuides[ axis ][ i ] + bounds.point[ axis ], + bounds, ColorI( 0, 255, 0, 100 ), drawer ); + } + } + + // Draw smart snap lines. + + for( U32 axis = 0; axis < 2; ++ axis ) + { + if( mSnapped[ axis ] ) + { + // Draw the snap line. + + drawCrossSection( axis, mSnapOffset[ axis ], + bounds, ColorI( 0, 0, 255, 100 ), drawer ); + + // Draw a border around the snap target control. + + if( mSnapTargets[ axis ] ) + { + RectI bounds = mSnapTargets[ axis ]->getGlobalBounds(); + drawer->drawRect( bounds, ColorF( .5, .5, .5, .5 ) ); + } + } + } + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::drawNuts(RectI &box, ColorI &outlineColor, ColorI &nutColor) +{ + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + S32 lx = box.point.x, rx = box.point.x + box.extent.x - 1; + S32 cx = (lx + rx) >> 1; + S32 ty = box.point.y, by = box.point.y + box.extent.y - 1; + S32 cy = (ty + by) >> 1; + + if( mDrawBorderLines ) + { + ColorF lineColor( 0.7f, 0.7f, 0.7f, 0.25f ); + ColorF lightLineColor( 0.5f, 0.5f, 0.5f, 0.1f ); + + if(lx > 0 && ty > 0) + { + drawer->drawLine(0, ty, lx, ty, lineColor); + drawer->drawLine(lx, 0, lx, ty, lineColor); + } + + if(lx > 0 && by > 0) + drawer->drawLine(0, by, lx, by, lineColor); + + if(rx > 0 && ty > 0) + drawer->drawLine(rx, 0, rx, ty, lineColor); + + Point2I extent = localToGlobalCoord(getExtent()); + + if(lx < extent.x && by < extent.y) + drawer->drawLine(lx, by, lx, extent.y, lightLineColor); + if(rx < extent.x && by < extent.y) + { + drawer->drawLine(rx, by, rx, extent.y, lightLineColor); + drawer->drawLine(rx, by, extent.x, by, lightLineColor); + } + if(rx < extent.x && ty < extent.y) + drawer->drawLine(rx, ty, extent.x, ty, lightLineColor); + } + + // adjust nuts, so they dont straddle the controlslx -= NUT_SIZE; + lx -= NUT_SIZE; + ty -= NUT_SIZE; + rx += NUT_SIZE; + by += NUT_SIZE; + + drawNut(Point2I(lx, ty), outlineColor, nutColor); + drawNut(Point2I(lx, cy), outlineColor, nutColor); + drawNut(Point2I(lx, by), outlineColor, nutColor); + drawNut(Point2I(rx, ty), outlineColor, nutColor); + drawNut(Point2I(rx, cy), outlineColor, nutColor); + drawNut(Point2I(rx, by), outlineColor, nutColor); + drawNut(Point2I(cx, ty), outlineColor, nutColor); + drawNut(Point2I(cx, by), outlineColor, nutColor); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::drawNut(const Point2I &nut, ColorI &outlineColor, ColorI &nutColor) +{ + RectI r(nut.x - NUT_SIZE, nut.y - NUT_SIZE, 2 * NUT_SIZE, 2 * NUT_SIZE); + r.inset(1, 1); + GFX->getDrawUtil()->drawRectFill(r, nutColor); + r.inset(-1, -1); + GFX->getDrawUtil()->drawRect(r, outlineColor); +} + +//============================================================================= +// Selections. +//============================================================================= +// MARK: ---- Selections ---- + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::clearSelection(void) +{ + mSelectedControls.clear(); + if( isMethod( "onClearSelected" ) ) + Con::executef( this, "onClearSelected" ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::setSelection(GuiControl *ctrl, bool inclusive) +{ + //sanity check + if( !ctrl ) + return; + + if( mSelectedControls.size() == 1 && mSelectedControls[ 0 ] == ctrl ) + return; + + if( !inclusive ) + clearSelection(); + + if( mContentControl == ctrl ) + mCurrentAddSet = ctrl; + else + addSelection( ctrl ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::addSelection(S32 id) +{ + GuiControl * ctrl; + if( Sim::findObject( id, ctrl ) ) + addSelection( ctrl ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::addSelection( GuiControl* ctrl ) +{ + // Only add if this isn't the content control and the + // control isn't yet in the selection. + + if( ctrl != getContentControl() && !selectionContains( ctrl ) ) + { + mSelectedControls.push_back( ctrl ); + + if( mSelectedControls.size() == 1 ) + { + // Update the add set. + + if( ctrl->mIsContainer ) + setCurrentAddSet( ctrl, false ); + else + setCurrentAddSet( ctrl->getParent(), false ); + + // Notify script. + + Con::executef( this, "onSelect", Con::getIntArg( ctrl->getId() ) ); + } + else + { + // Notify script. + + Con::executef( this, "onAddSelected", Con::getIntArg( ctrl->getId() ) ); + } + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::removeSelection( S32 id ) +{ + GuiControl * ctrl; + if ( Sim::findObject( id, ctrl ) ) + removeSelection( ctrl ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::removeSelection( GuiControl* ctrl ) +{ + if( selectionContains( ctrl ) ) + { + Vector< GuiControl* >::iterator i = ::find( mSelectedControls.begin(), mSelectedControls.end(), ctrl ); + if ( i != mSelectedControls.end() ) + mSelectedControls.erase( i ); + + Con::executef( this, "onRemoveSelected", Con::getIntArg( ctrl->getId() ) ); + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::canHitSelectedControls( bool state ) +{ + for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i ) + mSelectedControls[ i ]->setCanHit( state ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::moveSelectionToCtrl(GuiControl *newParent) +{ + for(int i = 0; i < mSelectedControls.size(); i++) + { + GuiControl* ctrl = mSelectedControls[i]; + if( ctrl->getParent() == newParent + || ctrl->isLocked() + || selectionContainsParentOf( ctrl ) ) + continue; + + Point2I globalpos = ctrl->localToGlobalCoord(Point2I(0,0)); + newParent->addObject(ctrl); + Point2I newpos = ctrl->globalToLocalCoord(globalpos) + ctrl->getPosition(); + ctrl->setPosition(newpos); + } + + onHierarchyChanged(); +} + +//----------------------------------------------------------------------------- + +static Point2I snapPoint(Point2I point, Point2I delta, Point2I gridSnap) +{ + S32 snap; + if(gridSnap.x && delta.x) + { + snap = point.x % gridSnap.x; + point.x -= snap; + if(delta.x > 0 && snap != 0) + point.x += gridSnap.x; + } + if(gridSnap.y && delta.y) + { + snap = point.y % gridSnap.y; + point.y -= snap; + if(delta.y > 0 && snap != 0) + point.y += gridSnap.y; + } + return point; +} + +void GuiEditCtrl::moveAndSnapSelection(const Point2I &delta) +{ + // move / nudge gets a special callback so that multiple small moves can be + // coalesced into one large undo action. + // undo + Con::executef(this, "onPreSelectionNudged", Con::getIntArg(getSelectedSet().getId())); + + Vector::iterator i; + Point2I newPos; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + { + GuiControl* ctrl = *i; + if( !ctrl->isLocked() && !selectionContainsParentOf( ctrl ) ) + { + newPos = ctrl->getPosition() + delta; + newPos = snapPoint( newPos, delta, mGridSnap ); + ctrl->setPosition( newPos ); + } + } + + // undo + Con::executef(this, "onPostSelectionNudged", Con::getIntArg(getSelectedSet().getId())); + + // allow script to update the inspector + if (mSelectedControls.size() == 1) + Con::executef(this, "onSelectionMoved", Con::getIntArg(mSelectedControls[0]->getId())); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::moveSelection(const Point2I &delta) +{ + Vector::iterator i; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + { + GuiControl* ctrl = *i; + if( !ctrl->isLocked() && !selectionContainsParentOf( ctrl ) ) + ctrl->setPosition( ctrl->getPosition() + delta ); + } + + // allow script to update the inspector + if( mSelectedControls.size() == 1 ) + Con::executef(this, "onSelectionMoved", Con::getIntArg(mSelectedControls[0]->getId())); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::justifySelection(Justification j) +{ + S32 minX, maxX; + S32 minY, maxY; + S32 extentX, extentY; + + if (mSelectedControls.size() < 2) + return; + + Vector::iterator i = mSelectedControls.begin(); + minX = (*i)->getLeft(); + maxX = minX + (*i)->getWidth(); + minY = (*i)->getTop(); + maxY = minY + (*i)->getHeight(); + extentX = (*i)->getWidth(); + extentY = (*i)->getHeight(); + i++; + for(;i != mSelectedControls.end(); i++) + { + minX = getMin(minX, (*i)->getLeft()); + maxX = getMax(maxX, (*i)->getLeft() + (*i)->getWidth()); + minY = getMin(minY, (*i)->getTop()); + maxY = getMax(maxY, (*i)->getTop() + (*i)->getHeight()); + extentX += (*i)->getWidth(); + extentY += (*i)->getHeight(); + } + S32 deltaX = maxX - minX; + S32 deltaY = maxY - minY; + switch(j) + { + case JUSTIFY_LEFT: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if( !( *i )->isLocked() ) + (*i)->setLeft( minX ); + break; + case JUSTIFY_TOP: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if( !( *i )->isLocked() ) + (*i)->setTop( minY ); + break; + case JUSTIFY_RIGHT: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if( !( *i )->isLocked() ) + (*i)->setLeft( maxX - (*i)->getWidth() + 1 ); + break; + case JUSTIFY_BOTTOM: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if( !( *i )->isLocked() ) + (*i)->setTop( maxY - (*i)->getHeight() + 1 ); + break; + case JUSTIFY_CENTER_VERTICAL: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if( !( *i )->isLocked() ) + (*i)->setLeft( minX + ((deltaX - (*i)->getWidth()) >> 1 )); + break; + case JUSTIFY_CENTER_HORIZONTAL: + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if( !( *i )->isLocked() ) + (*i)->setTop( minY + ((deltaY - (*i)->getHeight()) >> 1 )); + break; + case SPACING_VERTICAL: + { + Vector sortedList; + Vector::iterator k; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + { + for(k = sortedList.begin(); k != sortedList.end(); k++) + { + if ((*i)->getTop() < (*k)->getTop()) + break; + } + sortedList.insert(k, *i); + } + S32 space = (deltaY - extentY) / (mSelectedControls.size() - 1); + S32 curY = minY; + for(k = sortedList.begin(); k != sortedList.end(); k++) + { + if( !( *k )->isLocked() ) + (*k)->setTop( curY ); + curY += (*k)->getHeight() + space; + } + } + break; + case SPACING_HORIZONTAL: + { + Vector sortedList; + Vector::iterator k; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + { + for(k = sortedList.begin(); k != sortedList.end(); k++) + { + if ((*i)->getLeft() < (*k)->getLeft()) + break; + } + sortedList.insert(k, *i); + } + S32 space = (deltaX - extentX) / (mSelectedControls.size() - 1); + S32 curX = minX; + for(k = sortedList.begin(); k != sortedList.end(); k++) + { + if( !( *k )->isLocked() ) + (*k)->setLeft( curX ); + curX += (*k)->getWidth() + space; + } + } + break; + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::deleteSelection(void) +{ + // Notify script for undo. + + Con::executef(this, "onTrashSelection", Con::getIntArg(getSelectedSet().getId())); + + // Move all objects in selection to trash. + + Vector< GuiControl* >::iterator i; + for( i = mSelectedControls.begin(); i != mSelectedControls.end(); i ++ ) + mTrash.addObject( *i ); + + clearSelection(); + + // Notify script it needs to update its views. + + onHierarchyChanged(); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::loadSelection(const char* filename) +{ + if (! mCurrentAddSet) + mCurrentAddSet = mContentControl; + + // Set redefine behavior to rename. + + const char* oldRedefineBehavior = Con::getVariable( "$Con::redefineBehavior" ); + Con::setVariable( "$Con::redefineBehavior", "renameNew" ); + + // Exec the file with the saved selection set. + + Con::executef( "exec", filename ); + SimSet* set; + if( !Sim::findObject( "guiClipboard", set ) ) + { + Con::errorf( "GuiEditCtrl::loadSelection() - could not find 'guiClipboard' in '%s'", filename ); + return; + } + + // Restore redefine behavior. + + Con::setVariable( "$Con::redefineBehavior", oldRedefineBehavior ); + + // Add the objects in the set. + + if( set->size() ) + { + clearSelection(); + for( U32 i = 0, num = set->size(); i < num; ++ i ) + { + GuiControl *ctrl = dynamic_cast< GuiControl* >( ( *set )[ i ] ); + if( ctrl ) + { + mCurrentAddSet->addObject( ctrl ); + addSelection( ctrl ); + } + } + + // Undo + Con::executef(this, "onAddNewCtrlSet", Con::getIntArg(getSelectedSet().getId())); + + // Notify the script it needs to update its treeview. + + onHierarchyChanged(); + } + set->deleteObject(); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::saveSelection(const char* filename) +{ + // If there are no selected objects, then don't save. + + if( mSelectedControls.size() == 0 ) + return; + + FileStream *stream; + if((stream = FileStream::createAndOpen( filename, Torque::FS::File::Write )) == NULL) + return; + + // Create a temporary SimSet. + + SimSet* clipboardSet = new SimSet; + clipboardSet->registerObject(); + Sim::getRootGroup()->addObject( clipboardSet, "guiClipboard" ); + + // Add the selected controls to the set. + + for( Vector< GuiControl* >::iterator i = mSelectedControls.begin(); + i != mSelectedControls.end(); ++ i ) + { + GuiControl* ctrl = *i; + if( !selectionContainsParentOf( ctrl ) ) + clipboardSet->addObject( ctrl ); + } + + // Write the SimSet. Use the IgnoreCanSave to ensure the controls + // get actually written out (also disables the default parent inheritance + // behavior for the flag). + + clipboardSet->write( *stream, 0, IgnoreCanSave ); + clipboardSet->deleteObject(); + + delete stream; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::selectAll() +{ + GuiControl::iterator i; + if (!mCurrentAddSet) + return; + + clearSelection(); + for(i = mCurrentAddSet->begin(); i != mCurrentAddSet->end(); i++) + { + GuiControl *ctrl = dynamic_cast(*i); + addSelection( ctrl ); + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::bringToFront() +{ + // undo + if (mSelectedControls.size() != 1) + return; + + GuiControl *ctrl = *(mSelectedControls.begin()); + mCurrentAddSet->pushObjectToBack(ctrl); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::pushToBack() +{ + // undo + if (mSelectedControls.size() != 1) + return; + + GuiControl *ctrl = *(mSelectedControls.begin()); + mCurrentAddSet->bringObjectToFront(ctrl); +} + +//----------------------------------------------------------------------------- + +RectI GuiEditCtrl::getSelectionBounds() const +{ + Vector::const_iterator i = mSelectedControls.begin(); + + Point2I minPos = (*i)->localToGlobalCoord( Point2I( 0, 0 ) ); + Point2I maxPos = minPos; + + for(; i != mSelectedControls.end(); i++) + { + Point2I iPos = (**i).localToGlobalCoord( Point2I( 0 , 0 ) ); + + minPos.x = getMin( iPos.x, minPos.x ); + minPos.y = getMin( iPos.y, minPos.y ); + + Point2I iExt = ( **i ).getExtent(); + + iPos.x += iExt.x; + iPos.y += iExt.y; + + maxPos.x = getMax( iPos.x, maxPos.x ); + maxPos.y = getMax( iPos.y, maxPos.y ); + } + + minPos = getContentControl()->globalToLocalCoord( minPos ); + maxPos = getContentControl()->globalToLocalCoord( maxPos ); + + return RectI( minPos.x, minPos.y, ( maxPos.x - minPos.x ), ( maxPos.y - minPos.y ) ); +} + +//----------------------------------------------------------------------------- + +RectI GuiEditCtrl::getSelectionGlobalBounds() const +{ + Point2I minb( S32_MAX, S32_MAX ); + Point2I maxb( S32_MIN, S32_MIN ); + + for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i ) + { + // Min. + + Point2I pos = mSelectedControls[ i ]->localToGlobalCoord( Point2I( 0, 0 ) ); + + minb.x = getMin( minb.x, pos.x ); + minb.y = getMin( minb.y, pos.y ); + + // Max. + + const Point2I extent = mSelectedControls[ i ]->getExtent(); + + maxb.x = getMax( maxb.x, pos.x + extent.x ); + maxb.y = getMax( maxb.y, pos.y + extent.y ); + } + + RectI bounds( minb.x, minb.y, maxb.x - minb.x, maxb.y - minb.y ); + return bounds; +} + +//----------------------------------------------------------------------------- + +bool GuiEditCtrl::selectionContains( GuiControl *ctrl ) +{ + Vector::iterator i; + for (i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + if (ctrl == *i) return true; + return false; +} + +//----------------------------------------------------------------------------- + +bool GuiEditCtrl::selectionContainsParentOf( GuiControl* ctrl ) +{ + GuiControl* parent = ctrl->getParent(); + while( parent && parent != getContentControl() ) + { + if( selectionContains( parent ) ) + return true; + + parent = parent->getParent(); + } + + return false; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::select( GuiControl* ctrl ) +{ + clearSelection(); + addSelection( ctrl ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::updateSelectedSet() +{ + mSelectedSet.clear(); + Vector::iterator i; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + { + mSelectedSet.addObject(*i); + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::addSelectControlsInRegion( const RectI& rect, U32 flags ) +{ + // Do a hit test on the content control. + + canHitSelectedControls( false ); + Vector< GuiControl* > hits; + + if( mFullBoxSelection ) + flags |= GuiControl::HIT_FullBoxOnly; + + getContentControl()->findHitControls( rect, hits, flags ); + canHitSelectedControls( true ); + + // Add all controls that got hit. + + for( U32 i = 0, num = hits.size(); i < num; ++ i ) + addSelection( hits[ i ] ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::addSelectControlAt( const Point2I& pos ) +{ + // Run a hit test. + + canHitSelectedControls( false ); + GuiControl* hit = getContentControl()->findHitControl( pos ); + canHitSelectedControls( true ); + + // Add to selection. + + if( hit ) + addSelection( hit ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::resizeControlsInSelectionBy( const Point2I& delta, U32 mode ) +{ + for( U32 i = 0, num = mSelectedControls.size(); i < num; ++ i ) + { + GuiControl *ctrl = mSelectedControls[ i ]; + if( ctrl->isLocked() ) + continue; + + Point2I minExtent = ctrl->getMinExtent(); + Point2I newPosition = ctrl->getPosition(); + Point2I newExtent = ctrl->getExtent(); + + if( mSizingMode & sizingLeft ) + { + newPosition.x += delta.x; + newExtent.x -= delta.x; + + if( newExtent.x < minExtent.x ) + { + newPosition.x -= minExtent.x - newExtent.x; + newExtent.x = minExtent.x; + } + } + else if( mSizingMode & sizingRight ) + { + newExtent.x += delta.x; + + if( newExtent.x < minExtent.x ) + newExtent.x = minExtent.x; + } + + if( mSizingMode & sizingTop ) + { + newPosition.y += delta.y; + newExtent.y -= delta.y; + + if( newExtent.y < minExtent.y ) + { + newPosition.y -= minExtent.y - newExtent.y; + newExtent.y = minExtent.y; + } + } + else if( mSizingMode & sizingBottom ) + { + newExtent.y += delta.y; + + if( newExtent.y < minExtent.y ) + newExtent.y = minExtent.y; + } + + ctrl->resize( newPosition, newExtent ); + } +} + +//============================================================================= +// Guides. +//============================================================================= +// MARK: ---- Guides ---- + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::readGuides( guideAxis axis, GuiControl* ctrl ) +{ + // Read the guide indices from the vector stored on the respective dynamic + // property of the control. + + const char* guideIndices = ctrl->getDataField( smGuidesPropertyName[ axis ], NULL ); + if( guideIndices && guideIndices[ 0 ] ) + { + U32 index = 0; + while( true ) + { + const char* posStr = StringUnit::getUnit( guideIndices, index, " \t" ); + if( !posStr[ 0 ] ) + break; + + mGuides[ axis ].push_back( dAtoi( posStr ) ); + + index ++; + } + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::writeGuides( guideAxis axis, GuiControl* ctrl ) +{ + // Store the guide indices of the given axis in a vector on the respective + // dynamic property of the control. + + StringBuilder str; + bool isFirst = true; + for( U32 i = 0, num = mGuides[ axis ].size(); i < num; ++ i ) + { + if( !isFirst ) + str.append( ' ' ); + + char buffer[ 32 ]; + dSprintf( buffer, sizeof( buffer ), "%i", mGuides[ axis ][ i ] ); + + str.append( buffer ); + + isFirst = false; + } + + String value = str.end(); + ctrl->setDataField( smGuidesPropertyName[ axis ], NULL, value ); +} + +//----------------------------------------------------------------------------- + +S32 GuiEditCtrl::findGuide( guideAxis axis, const Point2I& point, U32 tolerance ) +{ + const S32 p = ( point - localToGlobalCoord( Point2I( 0, 0 ) ) )[ axis ]; + + for( U32 i = 0, num = mGuides[ axis ].size(); i < num; ++ i ) + { + const S32 g = mGuides[ axis ][ i ]; + if( p >= ( g - tolerance ) && + p <= ( g + tolerance ) ) + return i; + } + + return -1; +} + +//============================================================================= +// Snapping. +//============================================================================= +// MARK: ---- Snapping ---- + +//----------------------------------------------------------------------------- + +RectI GuiEditCtrl::getSnapRegion( snappingAxis axis, const Point2I& center ) const +{ + RectI rootBounds = getContentControl()->getBounds(); + + RectI rect; + if( axis == SnapHorizontal ) + rect = RectI( rootBounds.point.x, + center.y - mSnapSensitivity, + rootBounds.extent.x, + mSnapSensitivity * 2 ); + else // SnapVertical + rect = RectI( center.x - mSnapSensitivity, + rootBounds.point.y, + mSnapSensitivity * 2, + rootBounds.extent.y ); + + // Clip against root bounds. + + rect.intersect( rootBounds ); + + return rect; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::registerSnap( snappingAxis axis, const Point2I& mousePoint, const Point2I& point, snappingEdges edge, GuiControl* ctrl ) +{ + bool takeNewSnap = false; + const Point2I globalPoint = getContentControl()->localToGlobalCoord( point ); + + // If we have no snap yet, just take this one. + + if( !mSnapped[ axis ] ) + takeNewSnap = true; + + // Otherwise see if this snap is the better one. + + else + { + // Compare deltas to pointer. + + S32 deltaCurrent = mAbs( mSnapOffset[ axis ] - mousePoint[ axis ] ); + S32 deltaNew = mAbs( globalPoint[ axis ] - mousePoint[ axis ] ); + + if( deltaCurrent > deltaNew ) + takeNewSnap = true; + } + + if( takeNewSnap ) + { + mSnapped[ axis ] = true; + mSnapOffset[ axis ] = globalPoint[ axis ]; + mSnapEdge[ axis ] = edge; + mSnapTargets[ axis ] = ctrl; + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::findSnaps( snappingAxis axis, const Point2I& mousePoint, const RectI& minRegion, const RectI& midRegion, const RectI& maxRegion ) +{ + // Find controls with edge in either minRegion, midRegion, or maxRegion + // (depending on snap settings). + + for( U32 i = 0, num = mSnapHits[ axis ].size(); i < num; ++ i ) + { + GuiControl* ctrl = mSnapHits[ axis ][ i ]; + if( ctrl == getContentControl() ) + continue; + + RectI bounds = ctrl->getGlobalBounds(); + bounds.point = getContentControl()->globalToLocalCoord( bounds.point ); + + // Compute points on min, mid, and max lines of control. + + Point2I min = bounds.point; + Point2I max = min + bounds.extent; + + Point2I mid = bounds.point; + mid.x += bounds.extent.x / 2; + mid.y += bounds.extent.y / 2; + + // Test edge snap cases. + + if( mSnapToEdges ) + { + // Min to min. + + if( minRegion.pointInRect( min ) ) + registerSnap( axis, mousePoint, min, SnapEdgeMin, ctrl ); + + // Max to max. + + if( maxRegion.pointInRect( max ) ) + registerSnap( axis, mousePoint, max, SnapEdgeMax, ctrl ); + + // Min to max. + + if( minRegion.pointInRect( max ) ) + registerSnap( axis, mousePoint, max, SnapEdgeMin, ctrl ); + + // Max to min. + + if( maxRegion.pointInRect( min ) ) + registerSnap( axis, mousePoint, min, SnapEdgeMax, ctrl ); + } + + // Test center snap cases. + + if( mSnapToCenters ) + { + // Mid to mid. + + if( midRegion.pointInRect( mid ) ) + registerSnap( axis, mousePoint, mid, SnapEdgeMid, ctrl ); + } + + // Test combined center+edge snap cases. + + if( mSnapToEdges && mSnapToCenters ) + { + // Min to mid. + + if( minRegion.pointInRect( mid ) ) + registerSnap( axis, mousePoint, mid, SnapEdgeMin, ctrl ); + + // Max to mid. + + if( maxRegion.pointInRect( mid ) ) + registerSnap( axis, mousePoint, mid, SnapEdgeMax, ctrl ); + + // Mid to min. + + if( midRegion.pointInRect( min ) ) + registerSnap( axis, mousePoint, min, SnapEdgeMid, ctrl ); + + // Mid to max. + + if( midRegion.pointInRect( max ) ) + registerSnap( axis, mousePoint, max, SnapEdgeMid, ctrl ); + } + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::doControlSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta ) +{ + if( !mSnapToControls || ( !mSnapToEdges && !mSnapToCenters ) ) + return; + + // Allow restricting to just vertical (ALT+SHIFT) or just horizontal (ALT+CTRL) + // snaps. + + bool snapAxisEnabled[ 2 ]; + + if( event.modifier & SI_PRIMARY_ALT && event.modifier & SI_SHIFT ) + snapAxisEnabled[ SnapHorizontal ] = false; + else + snapAxisEnabled[ SnapHorizontal ] = true; + + if( event.modifier & SI_PRIMARY_ALT && event.modifier & SI_CTRL ) + snapAxisEnabled[ SnapVertical ] = false; + else + snapAxisEnabled[ SnapVertical ] = true; + + // Compute snap regions. There is one region centered on and aligned with + // each of the selection bounds edges plus two regions aligned on the selection + // bounds center. For the selection bounds origin, we use the point that the + // selection would be at, if we had already done the mouse drag. + + RectI snapRegions[ 2 ][ 3 ]; + Point2I projectedOrigin( selectionBounds.point + delta ); + dMemset( snapRegions, 0, sizeof( snapRegions ) ); + + for( U32 axis = 0; axis < 2; ++ axis ) + { + if( !snapAxisEnabled[ axis ] ) + continue; + + if( mSizingMode == sizingNone || + ( axis == 0 && mSizingMode & sizingLeft ) || + ( axis == 1 && mSizingMode & sizingTop ) ) + snapRegions[ axis ][ 0 ] = getSnapRegion( ( snappingAxis ) axis, projectedOrigin ); + + if( mSizingMode == sizingNone ) + snapRegions[ axis ][ 1 ] = getSnapRegion( ( snappingAxis ) axis, projectedOrigin + Point2I( selectionBounds.extent.x / 2, selectionBounds.extent.y / 2 ) ); + + if( mSizingMode == sizingNone || + ( axis == 0 && mSizingMode & sizingRight ) || + ( axis == 1 && mSizingMode & sizingBottom ) ) + snapRegions[ axis ][ 2 ] = getSnapRegion( ( snappingAxis ) axis, projectedOrigin + selectionBounds.extent ); + } + + // Find hit controls. + + canHitSelectedControls( false ); + + if( mSnapToEdges ) + { + for( U32 axis = 0; axis < 2; ++ axis ) + if( snapAxisEnabled[ axis ] ) + { + getContentControl()->findHitControls( snapRegions[ axis ][ 0 ], mSnapHits[ axis ], HIT_NoCanHitNoRecurse ); + + getContentControl()->findHitControls( snapRegions[ axis ][ 2 ], mSnapHits[ axis ], HIT_NoCanHitNoRecurse ); + } + } + if( mSnapToCenters && mSizingMode == sizingNone ) + { + for( U32 axis = 0; axis < 2; ++ axis ) + if( snapAxisEnabled[ axis ] ) + getContentControl()->findHitControls( snapRegions[ axis ][ 1 ], mSnapHits[ axis ], HIT_NoCanHitNoRecurse ); + } + + canHitSelectedControls( true ); + + // Find snaps. + + for( U32 i = 0; i < 2; ++ i ) + if( snapAxisEnabled[ i ] ) + findSnaps( ( snappingAxis ) i, + event.mousePoint, + snapRegions[ i ][ 0 ], + snapRegions[ i ][ 1 ], + snapRegions[ i ][ 2 ] ); + + // Clean up. + + mSnapHits[ 0 ].clear(); + mSnapHits[ 1 ].clear(); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::doGridSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta ) +{ + delta += selectionBounds.point; + + if( mGridSnap.x ) + delta.x -= delta.x % mGridSnap.x; + if( mGridSnap.y ) + delta.y -= delta.y % mGridSnap.y; + + delta -= selectionBounds.point; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::doGuideSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta ) +{ + if( !mSnapToGuides ) + return; + + Point2I min = getContentControl()->localToGlobalCoord( selectionBounds.point + delta ); + Point2I mid = min + selectionBounds.extent / 2; + Point2I max = min + selectionBounds.extent; + + for( U32 axis = 0; axis < 2; ++ axis ) + { + if( mSnapToEdges ) + { + S32 guideMin = -1; + S32 guideMax = -1; + + if( mSizingMode == sizingNone || + ( axis == 0 && mSizingMode & sizingLeft ) || + ( axis == 1 && mSizingMode & sizingTop ) ) + guideMin = findGuide( ( guideAxis ) axis, min, mSnapSensitivity ); + if( mSizingMode == sizingNone || + ( axis == 0 && mSizingMode & sizingRight ) || + ( axis == 1 && mSizingMode & sizingBottom ) ) + guideMax = findGuide( ( guideAxis ) axis, max, mSnapSensitivity ); + + Point2I pos( 0, 0 ); + + if( guideMin != -1 ) + { + pos[ axis ] = mGuides[ axis ][ guideMin ]; + registerSnap( ( snappingAxis ) axis, event.mousePoint, pos, SnapEdgeMin ); + } + + if( guideMax != -1 ) + { + pos[ axis ] = mGuides[ axis ][ guideMax ]; + registerSnap( ( snappingAxis ) axis, event.mousePoint, pos, SnapEdgeMax ); + } + } + + if( mSnapToCenters && mSizingMode == sizingNone ) + { + const S32 guideMid = findGuide( ( guideAxis ) axis, mid, mSnapSensitivity ); + if( guideMid != -1 ) + { + Point2I pos( 0, 0 ); + pos[ axis ] = mGuides[ axis ][ guideMid ]; + registerSnap( ( snappingAxis ) axis, event.mousePoint, pos, SnapEdgeMid ); + } + } + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::doSnapping( const GuiEvent& event, const RectI& selectionBounds, Point2I& delta ) +{ + // Clear snapping. If we have snapping on, we want to find a new best snap. + + mSnapped[ SnapVertical ] = false; + mSnapped[ SnapHorizontal ] = false; + + // Compute global bounds. + + RectI selectionBoundsGlobal = selectionBounds; + selectionBoundsGlobal.point = getContentControl()->localToGlobalCoord( selectionBoundsGlobal.point ); + + // Apply the snaps. + + doGridSnap( event, selectionBounds, selectionBoundsGlobal, delta ); + doGuideSnap( event, selectionBounds, selectionBoundsGlobal, delta ); + doControlSnap( event, selectionBounds, selectionBoundsGlobal, delta ); + + // If we have a horizontal snap, compute a delta. + + if( mSnapped[ SnapVertical ] ) + snapDelta( SnapVertical, mSnapEdge[ SnapVertical ], mSnapOffset[ SnapVertical ], selectionBoundsGlobal, delta ); + + // If we have a vertical snap, compute a delta. + + if( mSnapped[ SnapHorizontal ] ) + snapDelta( SnapHorizontal, mSnapEdge[ SnapHorizontal ], mSnapOffset[ SnapHorizontal ], selectionBoundsGlobal, delta ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::snapToGrid( Point2I& point ) +{ + if( mGridSnap.x ) + point.x -= point.x % mGridSnap.x; + if( mGridSnap.y ) + point.y -= point.y % mGridSnap.y; +} + +//============================================================================= +// Misc. +//============================================================================= +// MARK: ---- Misc ---- + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::setContentControl(GuiControl *root) +{ + mContentControl = root; + if( root != NULL ) + root->mIsContainer = true; + + mCurrentAddSet = mContentControl; + clearSelection(); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::setEditMode(bool value) +{ + mActive = value; + + clearSelection(); + if (mActive && mAwake) + mCurrentAddSet = NULL; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::setCurrentAddSet(GuiControl *ctrl, bool doclearSelection) +{ + if (ctrl != mCurrentAddSet) + { + if(doclearSelection) + clearSelection(); + + mCurrentAddSet = ctrl; + } +} + +//----------------------------------------------------------------------------- + +const GuiControl* GuiEditCtrl::getCurrentAddSet() const +{ + return mCurrentAddSet ? mCurrentAddSet : mContentControl; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::addNewControl(GuiControl *ctrl) +{ + if (! mCurrentAddSet) + mCurrentAddSet = mContentControl; + + mCurrentAddSet->addObject(ctrl); + select( ctrl ); + + // undo + Con::executef(this, "onAddNewCtrl", Con::getIntArg(ctrl->getId())); +} + +//----------------------------------------------------------------------------- + +S32 GuiEditCtrl::getSizingHitKnobs(const Point2I &pt, const RectI &box) +{ + S32 lx = box.point.x, rx = box.point.x + box.extent.x - 1; + S32 cx = (lx + rx) >> 1; + S32 ty = box.point.y, by = box.point.y + box.extent.y - 1; + S32 cy = (ty + by) >> 1; + + // adjust nuts, so they dont straddle the controls + lx -= NUT_SIZE; + ty -= NUT_SIZE; + rx += NUT_SIZE; + by += NUT_SIZE; + + if (inNut(pt, lx, ty)) + return sizingLeft | sizingTop; + if (inNut(pt, cx, ty)) + return sizingTop; + if (inNut(pt, rx, ty)) + return sizingRight | sizingTop; + if (inNut(pt, lx, by)) + return sizingLeft | sizingBottom; + if (inNut(pt, cx, by)) + return sizingBottom; + if (inNut(pt, rx, by)) + return sizingRight | sizingBottom; + if (inNut(pt, lx, cy)) + return sizingLeft; + if (inNut(pt, rx, cy)) + return sizingRight; + return sizingNone; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::getDragRect(RectI &box) +{ + box.point.x = getMin(mLastMousePos.x, mSelectionAnchor.x); + box.extent.x = getMax(mLastMousePos.x, mSelectionAnchor.x) - box.point.x + 1; + box.point.y = getMin(mLastMousePos.y, mSelectionAnchor.y); + box.extent.y = getMax(mLastMousePos.y, mSelectionAnchor.y) - box.point.y + 1; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) +{ + GuiCanvas *pRoot = getRoot(); + if( !pRoot ) + return; + + showCursor = false; + cursor = NULL; + + Point2I ctOffset; + Point2I cext; + GuiControl *ctrl; + + Point2I mousePos = globalToLocalCoord(lastGuiEvent.mousePoint); + + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); + PlatformCursorController *pController = pWindow->getCursorController(); + AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); + + S32 desiredCursor = PlatformCursorController::curArrow; + + // first see if we hit a sizing knob on the currently selected control... + if (mSelectedControls.size() == 1 ) + { + ctrl = mSelectedControls.first(); + cext = ctrl->getExtent(); + ctOffset = globalToLocalCoord(ctrl->localToGlobalCoord(Point2I(0,0))); + RectI box(ctOffset.x,ctOffset.y,cext.x, cext.y); + + GuiEditCtrl::sizingModes sizeMode = (GuiEditCtrl::sizingModes)getSizingHitKnobs(mousePos, box); + + if( mMouseDownMode == SizingSelection ) + { + if ( ( mSizingMode == ( sizingBottom | sizingRight ) ) || ( mSizingMode == ( sizingTop | sizingLeft ) ) ) + desiredCursor = PlatformCursorController::curResizeNWSE; + else if ( ( mSizingMode == ( sizingBottom | sizingLeft ) ) || ( mSizingMode == ( sizingTop | sizingRight ) ) ) + desiredCursor = PlatformCursorController::curResizeNESW; + else if ( mSizingMode == sizingLeft || mSizingMode == sizingRight ) + desiredCursor = PlatformCursorController::curResizeVert; + else if (mSizingMode == sizingTop || mSizingMode == sizingBottom ) + desiredCursor = PlatformCursorController::curResizeHorz; + } + else + { + // Check for current mouse position after checking for actual sizing mode + if ( ( sizeMode == ( sizingBottom | sizingRight ) ) || ( sizeMode == ( sizingTop | sizingLeft ) ) ) + desiredCursor = PlatformCursorController::curResizeNWSE; + else if ( ( sizeMode == ( sizingBottom | sizingLeft ) ) || ( sizeMode == ( sizingTop | sizingRight ) ) ) + desiredCursor = PlatformCursorController::curResizeNESW; + else if (sizeMode == sizingLeft || sizeMode == sizingRight ) + desiredCursor = PlatformCursorController::curResizeVert; + else if (sizeMode == sizingTop || sizeMode == sizingBottom ) + desiredCursor = PlatformCursorController::curResizeHorz; + } + } + + if( mMouseDownMode == MovingSelection && cursor == NULL ) + desiredCursor = PlatformCursorController::curResizeAll; + + if( pRoot->mCursorChanged != desiredCursor ) + { + // We've already changed the cursor, + // so set it back before we change it again. + if(pRoot->mCursorChanged != -1) + pController->popCursor(); + + // Now change the cursor shape + pController->pushCursor(desiredCursor); + pRoot->mCursorChanged = desiredCursor; + } +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::setSnapToGrid(U32 gridsize) +{ + if( gridsize != 0 ) + gridsize = getMax( gridsize, ( U32 ) MIN_GRID_SIZE ); + mGridSnap.set( gridsize, gridsize ); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::controlInspectPreApply(GuiControl* object) +{ + // undo + Con::executef(this, "onControlInspectPreApply", Con::getIntArg(object->getId())); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::controlInspectPostApply(GuiControl* object) +{ + // undo + Con::executef(this, "onControlInspectPostApply", Con::getIntArg(object->getId())); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::startDragMove( const Point2I& startPoint ) +{ + // For calculating mouse delta + mDragBeginPoint = globalToLocalCoord( startPoint ); + + // Allocate enough space for our selected controls + mDragBeginPoints.reserve( mSelectedControls.size() ); + + // For snapping to origin + Vector::iterator i; + for(i = mSelectedControls.begin(); i != mSelectedControls.end(); i++) + mDragBeginPoints.push_back( (*i)->getPosition() ); + + // Set Mouse Mode + mMouseDownMode = MovingSelection; + + // undo + Con::executef(this, "onPreEdit", Con::getIntArg(getSelectedSet().getId())); +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::startDragRectangle( const Point2I& startPoint ) +{ + mSelectionAnchor = globalToLocalCoord( startPoint ); + mMouseDownMode = DragSelecting; +} + +//----------------------------------------------------------------------------- + +void GuiEditCtrl::startMouseGuideDrag( guideAxis axis, U32 guideIndex, bool lockMouse ) +{ + mDragGuideIndex[ axis ] = guideIndex; + mDragGuide[ axis ] = true; + + mMouseDownMode = DragGuide; + + // Grab the mouse. + + if( lockMouse ) + mouseLock(); +} + +//============================================================================= +// Console Methods. +//============================================================================= +// MARK: ---- Console Methods ---- + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, getContentControl, S32, 2, 2, "() - Return the toplevel control edited inside the GUI editor." ) +{ + GuiControl* ctrl = object->getContentControl(); + if( ctrl ) + return ctrl->getId(); + else + return 0; +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, setContentControl, void, 3, 3, "( GuiControl ctrl ) - Set the toplevel control to edit in the GUI editor." ) +{ + GuiControl *ctrl; + if(!Sim::findObject(argv[2], ctrl)) + return; + object->setContentControl(ctrl); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, addNewCtrl, void, 3, 3, "(GuiControl ctrl)") +{ + GuiControl *ctrl; + if(!Sim::findObject(argv[2], ctrl)) + return; + object->addNewControl(ctrl); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, addSelection, void, 3, 3, "selects a control.") +{ + S32 id = dAtoi(argv[2]); + object->addSelection(id); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, removeSelection, void, 3, 3, "deselects a control.") +{ + S32 id = dAtoi(argv[2]); + object->removeSelection(id); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, clearSelection, void, 2, 2, "Clear selected controls list.") +{ + object->clearSelection(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, select, void, 3, 3, "(GuiControl ctrl)") +{ + GuiControl *ctrl; + + if(!Sim::findObject(argv[2], ctrl)) + return; + + object->setSelection(ctrl, false); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, setCurrentAddSet, void, 3, 3, "(GuiControl ctrl)") +{ + GuiControl *addSet; + + if (!Sim::findObject(argv[2], addSet)) + { + Con::printf("%s(): Invalid control: %s", argv[0], argv[2]); + return; + } + object->setCurrentAddSet(addSet); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, getCurrentAddSet, S32, 2, 2, "Returns the set to which new controls will be added") +{ + const GuiControl* add = object->getCurrentAddSet(); + return add ? add->getId() : 0; +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, toggle, void, 2, 2, "Toggle activation.") +{ + object->setEditMode(! object->mActive); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, justify, void, 3, 3, "(int mode)" ) +{ + object->justifySelection((GuiEditCtrl::Justification)dAtoi(argv[2])); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, bringToFront, void, 2, 2, "") +{ + object->bringToFront(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, pushToBack, void, 2, 2, "") +{ + object->pushToBack(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, deleteSelection, void, 2, 2, "Delete the selected text.") +{ + object->deleteSelection(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, moveSelection, void, 4, 4, "(int deltax, int deltay)") +{ + object->moveAndSnapSelection(Point2I(dAtoi(argv[2]), dAtoi(argv[3]))); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, saveSelection, void, 3, 3, "(string fileName)") +{ + object->saveSelection(argv[2]); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, loadSelection, void, 3, 3, "(string fileName)") +{ + object->loadSelection(argv[2]); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, selectAll, void, 2, 2, "()") +{ + object->selectAll(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, getSelected, S32, 2, 2, "() - Gets the GUI control(s) the editor is currently selecting" ) +{ + return object->getSelectedSet().getId(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, getSelectionGlobalBounds, const char*, 2, 2, "() - Returns global bounds of current selection as vector 'x y width height'." ) +{ + RectI bounds = object->getSelectionGlobalBounds(); + String str = String::ToString( "%i %i %i %i", bounds.point.x, bounds.point.y, bounds.extent.x, bounds.extent.y ); + + char* buffer = Con::getReturnBuffer( str.length() ); + dStrcpy( buffer, str.c_str() ); + + return buffer; +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, getTrash, S32, 2, 2, "() - Gets the GUI controls(s) that are currently in the trash.") +{ + return object->getTrash().getId(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, getUndoManager, S32, 2, 2, "() - Gets the Gui Editor's UndoManager object") +{ + return object->getUndoManager().getId(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(GuiEditCtrl, setSnapToGrid, void, 3, 3, "GuiEditCtrl.setSnapToGrid(gridsize)") +{ + U32 gridsize = dAtoi(argv[2]); + object->setSnapToGrid(gridsize); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, readGuides, void, 3, 4, "( GuiControl ctrl [, int axis ] ) - Read the guides from the given control." ) +{ + // Find the control. + + GuiControl* ctrl; + if( !Sim::findObject( argv[ 2 ], ctrl ) ) + { + Con::errorf( "GuiEditCtrl::readGuides - no control '%s'", argv[ 2 ] ); + return; + } + + // Read the guides. + + if( argc > 3 ) + { + S32 axis = dAtoi( argv[ 3 ] ); + if( axis < 0 || axis > 1 ) + { + Con::errorf( "GuiEditCtrl::readGuides - invalid axis '%s'", argv[ 3 ] ); + return; + } + + object->readGuides( ( GuiEditCtrl::guideAxis ) axis, ctrl ); + } + else + { + object->readGuides( GuiEditCtrl::GuideHorizontal, ctrl ); + object->readGuides( GuiEditCtrl::GuideVertical, ctrl ); + } +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, writeGuides, void, 3, 4, "( GuiControl ctrl [, int axis ] ) - Write the guides to the given control." ) +{ + // Find the control. + + GuiControl* ctrl; + if( !Sim::findObject( argv[ 2 ], ctrl ) ) + { + Con::errorf( "GuiEditCtrl::writeGuides - no control '%i'", argv[ 2 ] ); + return; + } + + // Write the guides. + + if( argc > 3 ) + { + S32 axis = dAtoi( argv[ 3 ] ); + if( axis < 0 || axis > 1 ) + { + Con::errorf( "GuiEditCtrl::writeGuides - invalid axis '%s'", argv[ 3 ] ); + return; + } + + object->writeGuides( ( GuiEditCtrl::guideAxis ) axis, ctrl ); + } + else + { + object->writeGuides( GuiEditCtrl::GuideHorizontal, ctrl ); + object->writeGuides( GuiEditCtrl::GuideVertical, ctrl ); + } +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( GuiEditCtrl, clearGuides, void, 2, 3, "( [ int axis ] ) - Clear all currently set guide lines." ) +{ + if( argc > 2 ) + { + S32 axis = dAtoi( argv[ 2 ] ); + if( axis < 0 || axis > 1 ) + { + Con::errorf( "GuiEditCtrl::clearGuides - invalid axis '%i'", axis ); + return; + } + + object->clearGuides( ( GuiEditCtrl::guideAxis ) axis ); + } + else + { + object->clearGuides( GuiEditCtrl::GuideHorizontal ); + object->clearGuides( GuiEditCtrl::GuideVertical ); + } +} + +//============================================================================= +// GuiEditorRuler. +//============================================================================= + +class GuiEditorRuler : public GuiControl +{ + public: + + typedef GuiControl Parent; + + protected: + + String mRefCtrlName; + String mEditCtrlName; + + GuiScrollCtrl* mRefCtrl; + GuiEditCtrl* mEditCtrl; + + public: + + enum EOrientation + { + ORIENTATION_Horizontal, + ORIENTATION_Vertical + }; + + GuiEditorRuler() + : mRefCtrl( 0 ), + mEditCtrl( 0 ) + { + } + + EOrientation getOrientation() const + { + if( getWidth() > getHeight() ) + return ORIENTATION_Horizontal; + else + return ORIENTATION_Vertical; + } + + bool onWake() + { + if( !Parent::onWake() ) + return false; + + if( !mEditCtrlName.isEmpty() && !Sim::findObject( mEditCtrlName, mEditCtrl ) ) + Con::errorf( "GuiEditorRuler::onWake() - no GuiEditCtrl '%s'", mEditCtrlName.c_str() ); + + if( !mRefCtrlName.isEmpty() && !Sim::findObject( mRefCtrlName, mRefCtrl ) ) + Con::errorf( "GuiEditorRuler::onWake() - no GuiScrollCtrl '%s'", mRefCtrlName.c_str() ); + + return true; + } + + void onPreRender() + { + setUpdate(); + } + + void onMouseDown( const GuiEvent& event ) + { + if( !mEditCtrl ) + return; + + // Determine the guide axis. + + GuiEditCtrl::guideAxis axis; + if( getOrientation() == ORIENTATION_Horizontal ) + axis = GuiEditCtrl::GuideHorizontal; + else + axis = GuiEditCtrl::GuideVertical; + + // Start dragging a new guide out in the editor. + + U32 guideIndex = mEditCtrl->addGuide( axis, 0 ); + mEditCtrl->startMouseGuideDrag( axis, guideIndex ); + } + + void onRender(Point2I offset, const RectI &updateRect) + { + GFX->getDrawUtil()->drawRectFill(updateRect, ColorF(1,1,1,1)); + + Point2I choffset(0,0); + if( mRefCtrl != NULL ) + choffset = mRefCtrl->getChildPos(); + + if( getOrientation() == ORIENTATION_Horizontal ) + { + // it's horizontal. + for(U32 i = 0; i < getWidth(); i++) + { + S32 x = offset.x + i; + S32 pos = i - choffset.x; + if(!(pos % 10)) + { + S32 start = 6; + if(!(pos %20)) + start = 4; + if(!(pos % 100)) + start = 1; + GFX->getDrawUtil()->drawLine(x, offset.y + start, x, offset.y + 10, ColorF(0,0,0,1)); + } + } + } + else + { + // it's vertical. + for(U32 i = 0; i < getHeight(); i++) + { + S32 y = offset.y + i; + S32 pos = i - choffset.y; + if(!(pos % 10)) + { + S32 start = 6; + if(!(pos %20)) + start = 4; + if(!(pos % 100)) + start = 1; + GFX->getDrawUtil()->drawLine(offset.x + start, y, offset.x + 10, y, ColorF(0,0,0,1)); + } + } + } + } + static void initPersistFields() + { + addField( "refCtrl", TypeRealString, Offset( mRefCtrlName, GuiEditorRuler ) ); + addField( "editCtrl", TypeRealString, Offset( mEditCtrlName, GuiEditorRuler ) ); + + Parent::initPersistFields(); + } + + DECLARE_CONOBJECT(GuiEditorRuler); + DECLARE_CATEGORY( "Gui Editor" ); +}; + +IMPLEMENT_CONOBJECT(GuiEditorRuler); \ No newline at end of file diff --git a/gui/editor/guiEditCtrl.h b/gui/editor/guiEditCtrl.h new file mode 100644 index 0000000..fefc683 --- /dev/null +++ b/gui/editor/guiEditCtrl.h @@ -0,0 +1,292 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIEDITCTRL_H_ +#define _GUIEDITCTRL_H_ + +#ifndef _GUICONTROL_H_ + #include "gui/core/guiControl.h" +#endif +#ifndef _UNDO_H_ + #include "util/undo.h" +#endif +#ifndef _GFX_GFXDRAWER_H_ + #include "gfx/gfxDrawUtil.h" +#endif + + +/// Native side of the GUI editor. +class GuiEditCtrl : public GuiControl +{ + public: + + typedef GuiControl Parent; + friend class GuiEditorRuler; + + enum Justification + { + JUSTIFY_LEFT, + JUSTIFY_CENTER_VERTICAL, + JUSTIFY_RIGHT, + JUSTIFY_TOP, + JUSTIFY_BOTTOM, + SPACING_VERTICAL, + SPACING_HORIZONTAL, + JUSTIFY_CENTER_HORIZONTAL, + }; + + enum guideAxis { GuideVertical, GuideHorizontal }; + + protected: + + enum + { + NUT_SIZE = 4, + MIN_GRID_SIZE = 3 + }; + + typedef Vector< GuiControl* > GuiControlVector; + typedef SimObjectPtr< GuiControl > GuiControlPtr; + + enum mouseModes { Selecting, MovingSelection, SizingSelection, DragSelecting, DragGuide }; + enum sizingModes { sizingNone = 0, sizingLeft = 1, sizingRight = 2, sizingTop = 4, sizingBottom = 8 }; + + enum snappingAxis { SnapVertical, SnapHorizontal }; + enum snappingEdges { SnapEdgeMin, SnapEdgeMid, SnapEdgeMax }; + + bool mDrawBorderLines; + bool mDrawGuides; + bool mFullBoxSelection; + GuiControlVector mSelectedControls; + GuiControlPtr mCurrentAddSet; + GuiControlPtr mContentControl; + Point2I mLastMousePos; + Point2I mLastDragPos; + Point2I mSelectionAnchor; + Point2I mGridSnap; + Point2I mDragBeginPoint; + Vector mDragBeginPoints; + bool mDragAddSelection; + + // Guides. + + bool mSnapToGuides; ///< If true, edge and center snapping will work against guides. + bool mDragGuide[ 2 ]; + U32 mDragGuideIndex[ 2 ];///< Indices of the guide's being dragged in DragGuide mouse mode. + Vector< U32 > mGuides[ 2 ]; ///< The guides defined on the current content control. + + // Snapping + + bool mSnapToControls; ///< If true, edge and center snapping will work against controls. + bool mSnapToEdges; ///< If true, selection edges will snap to other control edges. + bool mSnapToCenters; ///< If true, selection centers will snap to other control centers. + S32 mSnapSensitivity; ///< Snap distance in pixels. + + bool mSnapped[ 2 ]; ///< Snap flag for each axis. If true, we are currently snapped in a drag. + S32 mSnapOffset[ 2 ]; ///< Reference point on snap line for each axis. + GuiControlVector mSnapHits[ 2 ]; ///< Hit testing lists for each axis. + snappingEdges mSnapEdge[ 2 ]; ///< Identification of edge being snapped for each axis. + GuiControlPtr mSnapTargets[ 2 ]; ///< Controls providing the snap reference lines on each axis. + + // Undo + UndoManager mUndoManager; + SimGroup mTrash; + SimSet mSelectedSet; + + // grid drawing + GFXVertexBufferHandle mDots; + GFXStateBlockRef mDotSB; + + mouseModes mMouseDownMode; + sizingModes mSizingMode; + + static StringTableEntry smGuidesPropertyName[ 2 ]; + + // private methods + void updateSelectedSet(); + + S32 findGuide( guideAxis axis, const Point2I& point, U32 tolerance = 0 ); + + RectI getSnapRegion( snappingAxis axis, const Point2I& center ) const; + void findSnaps( snappingAxis axis, const Point2I& mousePoint, const RectI& minRegion, const RectI& midRegion, const RectI& maxRegion ); + void registerSnap( snappingAxis axis, const Point2I& mousePoint, const Point2I& point, snappingEdges edge, GuiControl* ctrl = NULL ); + + void doSnapping( const GuiEvent& event, const RectI& selectionBounds, Point2I& delta ); + void doGuideSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta ); + void doControlSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta ); + void doGridSnap( const GuiEvent& event, const RectI& selectionBounds, const RectI& selectionBoundsGlobal, Point2I& delta ); + void snapToGrid( Point2I& point ); + + void startDragMove( const Point2I& startPoint ); + void startDragRectangle( const Point2I& startPoint ); + void startMouseGuideDrag( guideAxis axis, U32 index, bool lockMouse = true ); + + void onHierarchyChanged() + { + if( isMethod( "onHierarchyChanged" ) ) + Con::executef( this, "onHierarchyChanged" ); + } + + static bool inNut( const Point2I &pt, S32 x, S32 y ) + { + S32 dx = pt.x - x; + S32 dy = pt.y - y; + return dx <= NUT_SIZE && dx >= -NUT_SIZE && dy <= NUT_SIZE && dy >= -NUT_SIZE; + } + + static void drawCrossSection( U32 axis, S32 offset, const RectI& bounds, ColorI color, GFXDrawUtil* drawer ) + { + Point2I start; + Point2I end; + + if( axis == 0 ) + { + start.x = end.x = offset; + start.y = bounds.point.y; + end.y = bounds.point.y + bounds.extent.y; + } + else + { + start.y = end.y = offset; + start.x = bounds.point.x; + end.x = bounds.point.x + bounds.extent.x; + } + + drawer->drawLine( start, end, color ); + } + + static void snapDelta( snappingAxis axis, snappingEdges edge, S32 offset, const RectI& bounds, Point2I& delta ) + { + switch( axis ) + { + case SnapVertical: + switch( edge ) + { + case SnapEdgeMin: + delta.x = offset - bounds.point.x; + break; + + case SnapEdgeMid: + delta.x = offset - bounds.point.x - bounds.extent.x / 2; + break; + + case SnapEdgeMax: + delta.x = offset - bounds.point.x - bounds.extent.x; + break; + } + break; + + case SnapHorizontal: + switch( edge ) + { + case SnapEdgeMin: + delta.y = offset - bounds.point.y; + break; + + case SnapEdgeMid: + delta.y = offset - bounds.point.y - bounds.extent.y / 2; + break; + + case SnapEdgeMax: + delta.y = offset - bounds.point.y - bounds.extent.y; + break; + } + break; + } + } + + public: + + GuiEditCtrl(); + + DECLARE_CONOBJECT(GuiEditCtrl); + DECLARE_CATEGORY( "Gui Editor" ); + DECLARE_DESCRIPTION( "Implements the framework for the GUI editor." ); + + bool onWake(); + void onSleep(); + + static void initPersistFields(); + + GuiControl* getContentControl() const { return mContentControl; } + void setContentControl(GuiControl *ctrl); + void setEditMode(bool value); + S32 getSizingHitKnobs(const Point2I &pt, const RectI &box); + void getDragRect(RectI &b); + void drawNut(const Point2I &nut, ColorI &outlineColor, ColorI &nutColor); + void drawNuts(RectI &box, ColorI &outlineColor, ColorI &nutColor); + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + void addNewControl(GuiControl *ctrl); + void setCurrentAddSet(GuiControl *ctrl, bool clearSelection = true); + const GuiControl* getCurrentAddSet() const; + + // Selections. + + void select( GuiControl *ctrl ); + bool selectionContains( GuiControl *ctrl ); + bool selectionContainsParentOf( GuiControl* ctrl ); + void setSelection( GuiControl *ctrl, bool inclusive = false ); + RectI getSelectionBounds() const; + RectI getSelectionGlobalBounds() const; + void canHitSelectedControls( bool state = true ); + void justifySelection( Justification j ); + void moveSelection( const Point2I &delta ); + void moveAndSnapSelection( const Point2I &delta ); + void saveSelection( const char *filename ); + void loadSelection( const char *filename ); + void addSelection( S32 id ); + void addSelection( GuiControl* ctrl ); + void removeSelection( S32 id ); + void removeSelection( GuiControl* ctrl ); + void deleteSelection(); + void clearSelection(); + void selectAll(); + void bringToFront(); + void pushToBack(); + void moveSelectionToCtrl( GuiControl* ); + void addSelectControlsInRegion( const RectI& rect, U32 hitTestFlags = 0 ); + void addSelectControlAt( const Point2I& pos ); + void resizeControlsInSelectionBy( const Point2I& delta, U32 mode ); + + // Guides. + + U32 addGuide( guideAxis axis, U32 offset ) { U32 index = mGuides[ axis ].size(); mGuides[ axis ].push_back( offset ); return index; } + void readGuides( guideAxis axis, GuiControl* ctrl ); + void writeGuides( guideAxis axis, GuiControl* ctrl ); + void clearGuides( guideAxis axis ) { mGuides[ axis ].clear(); } + + // Undo Access. + + void undo(); + void redo(); + UndoManager& getUndoManager() { return mUndoManager; } + + // When a control is changed by the inspector + void controlInspectPreApply(GuiControl* object); + void controlInspectPostApply(GuiControl* object); + + // Sizing Cursors + void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + + U32 getSelectionSize() const { return mSelectedControls.size(); } + const Vector& getSelected() const { return mSelectedControls; } + const SimSet& getSelectedSet() { updateSelectedSet(); return mSelectedSet; } + const SimGroup& getTrash() { return mTrash; } + const GuiControl *getAddSet() const { return mCurrentAddSet; }; //JDD + + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onRightMouseDown(const GuiEvent &event); + + virtual bool onAdd(); + virtual void onRemove(); + + void setSnapToGrid(U32 gridsize); +}; + +#endif //_GUI_EDIT_CTRL_H diff --git a/gui/editor/guiFilterCtrl.cpp b/gui/editor/guiFilterCtrl.cpp new file mode 100644 index 0000000..ee341a7 --- /dev/null +++ b/gui/editor/guiFilterCtrl.cpp @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/guiFilterCtrl.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "guiFilterCtrl.h" +#include "math/mMath.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiFilterCtrl); + + +GuiFilterCtrl::GuiFilterCtrl() +{ + mControlPointRequest = 7; + mFilter.setSize(7); + mShowIdentity = true; + mIdentity.set( 0.0f, 1.0f ); + identity(); + mCurKnot = -1; +} + +void GuiFilterCtrl::initPersistFields() +{ + addField("controlPoints", TypeS32, Offset(mControlPointRequest, GuiFilterCtrl)); + addField("filter", TypeF32Vector, Offset(mFilter, GuiFilterCtrl)); + addField("showIdentity", TypeBool, Offset(mShowIdentity, GuiFilterCtrl)); + addField("identity", TypePoint2F, Offset(mIdentity, GuiFilterCtrl)); + + Parent::initPersistFields(); +} + +ConsoleMethod( GuiFilterCtrl, getValue, const char*, 2, 2, "Return a tuple containing all the values in the filter.") +{ + TORQUE_UNUSED(argv); + static char buffer[512]; + const Filter *filter = object->get(); + *buffer = 0; + + for (U32 i=0; i < filter->size(); i++) + { + char value[32]; + dSprintf(value, 31, "%1.5f ", *(filter->begin()+i) ); + dStrcat(buffer, value); + } + + return buffer; +} + +ConsoleMethod( GuiFilterCtrl, setValue, void, 3, 20, "(f1, f2, ...)" + "Reset the filter to use the specified points, spread equidistantly across the domain.") +{ + Filter filter; + + argc -= 2; + argv += 2; + + filter.set(argc, argv); + object->set(filter); +} + +ConsoleMethod( GuiFilterCtrl, identity, void, 2, 2, "Reset the filtering.") +{ + object->identity(); +} + +bool GuiFilterCtrl::onWake() +{ + if (!Parent::onWake()) + return false; + + if (U32(mControlPointRequest) != mFilter.size()) + { + mFilter.setSize(mControlPointRequest); + identity(); + } + + mCurKnot = -1; + + return true; +} + + +void GuiFilterCtrl::identity() +{ + S32 size = mFilter.size()-1; + for (U32 i=0; S32(i) <= size; i++) + { + F32 step = (F32)i/(F32)size; + mFilter[i] = mLerp( mIdentity.x, mIdentity.y, step ); + } +} + + +void GuiFilterCtrl::onMouseDown(const GuiEvent &event) +{ + mouseLock(); + setFirstResponder(); + + Point2I p = globalToLocalCoord(event.mousePoint); + + // determine which knot (offset same as in onRender) + F32 w = F32(getWidth()-4) / F32(mFilter.size()-1); + F32 val = (F32(p.x) + (w / 2.f)) / w; + mCurKnot = S32(val); + + mFilter[mCurKnot] = 1.0f - F32(getMin(getMax(0, p.y), getHeight())/(F32)getHeight()); + setUpdate(); +} + + +void GuiFilterCtrl::onMouseDragged(const GuiEvent &event) +{ + if ( mCurKnot < 0 ) + return; + + mouseLock(); + setFirstResponder(); + + Point2I p = globalToLocalCoord(event.mousePoint); + mFilter[mCurKnot] = 1.0f - F32(getMin(getMax(0, p.y), getHeight())/(F32)getHeight()); + setUpdate(); +} + +void GuiFilterCtrl::onMouseUp(const GuiEvent &) +{ + if ( mCurKnot < 0 ) + return; + + mouseUnlock(); + execConsoleCallback(); +} + +void GuiFilterCtrl::onPreRender() +{ + if(U32(mControlPointRequest) != mFilter.size()) + { + mFilter.setSize(mControlPointRequest); + identity(); + setUpdate(); + } +} + +void GuiFilterCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + Point2I pos = offset; + Point2I ext = getExtent(); + + RectI r(pos, ext); + GFX->getDrawUtil()->drawRectFill(r, ColorI(255,255,255)); + GFX->getDrawUtil()->drawRect(r, ColorI(0,0,0)); + + // shrink by 2 pixels + pos.x += 2; + pos.y += 2; + ext.x -= 4; + ext.y -= 4; + + // draw the identity line + if ( mShowIdentity ) + { + GFX->getDrawUtil()->drawLine( pos.x, pos.y + ( ext.y * ( 1.0f - mIdentity.x ) ), + pos.x + ext.x, pos.y + ( ext.y * ( 1.0f - mIdentity.y ) ), + ColorF( 0.9f, 0.9f, 0.9f ) ); + } + + // draw the curv + GFXVertexBufferHandle verts(GFX, ext.x, GFXBufferTypeVolatile); + + verts.lock(); + + F32 scale = 1.f / F32( ext.x ); + for ( U32 i = 0; i < ext.x; i++) + { + F32 index = F32(i) * scale; + S32 y = (S32)(ext.y*(1.0f-mFilter.getValue(index))); + + verts[i].point.set( (F32)(pos.x + i), (F32)(pos.y + y), 0.0f ); + verts[i].color = GFXVertexColor( ColorF( 0.4f, 0.4f, 0.4f ) ); + } + + verts.unlock(); + + GFX->setVertexBuffer( verts ); + GFX->drawPrimitive( GFXLineStrip, 0, ext.x - 1 ); + + // draw the knots + for (U32 k=0; k < mFilter.size(); k++) + { + RectI r; + r.point.x = (S32)(((F32)ext.x/(F32)(mFilter.size()-1)*(F32)k)); + r.point.y = (S32)(ext.y - ((F32)ext.y * mFilter[k])); + r.point += pos + Point2I(-2,-2); + r.extent = Point2I(5,5); + + GFX->getDrawUtil()->drawRectFill(r, ColorI(255,0,0)); + } + + renderChildControls(offset, updateRect); +} + + + +//-------------------------------------- +void Filter::set(S32 argc, const char *argv[]) +{ + setSize(0); + if (argc == 1) + { // in the form of one string "1.0 1.0 1.0" + char list[1024]; + dStrcpy(list, *argv); // strtok modifies the string so we need to copy it + char *value = dStrtok(list, " "); + while (value) + { + push_back(dAtof(value)); + value = dStrtok(NULL, " "); + } + } + else + { // in the form of seperate strings "1.0" "1.0" "1.0" + for (; argc ; argc--, argv++) + push_back(dAtof(*argv)); + } +} + + +//-------------------------------------- +F32 Filter::getValue(F32 x) const +{ + if (size() < 2) + return 0.0f; + + x = mClampF(x, 0.0f, 1.0f); + x *= F32(size()-1); + + F32 p0,p1,p2,p3; + S32 i1 = (S32)mFloor(x); + S32 i2 = i1+1; + F32 dt = x - F32(i1); + + p1 = *(begin()+i1); + p2 = *(begin()+i2); + + if (i1 == 0) + p0 = p1 + (p1 - p2); + else + p0 = *(begin()+i1-1); + + if (i2 == S32(size()-1)) + p3 = p2 + (p2 - p1); + else + p3 = *(begin()+i2+1); + + return mClampF( mCatmullrom(dt, p0, p1, p2, p3), 0.0f, 1.0f ); +} + + + + + + diff --git a/gui/editor/guiFilterCtrl.h b/gui/editor/guiFilterCtrl.h new file mode 100644 index 0000000..f642817 --- /dev/null +++ b/gui/editor/guiFilterCtrl.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIFILTERCTRL_H_ +#define _GUIFILTERCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + + +class Filter: public Vector +{ + public: + Filter() : Vector(__FILE__, __LINE__) { } + + void set(S32 argc, const char *argv[]); + F32 getValue(F32 t) const; +}; + + +class GuiFilterCtrl : public GuiControl +{ +protected: + + typedef GuiControl Parent; + + S32 mControlPointRequest; + + S32 mCurKnot; + + Filter mFilter; + + bool mShowIdentity; + + Point2F mIdentity; + +public: + + //creation methods + DECLARE_CONOBJECT(GuiFilterCtrl); + DECLARE_CATEGORY( "Gui Other" ); + + GuiFilterCtrl(); + static void initPersistFields(); + + //Parental methods + bool onWake(); + + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &); + + F32 getValue(S32 n); + const Filter* get() { return &mFilter; } + void set(const Filter &f); + S32 getNumControlPoints() {return mFilter.size(); } + void identity(); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect ); +}; + + +inline F32 GuiFilterCtrl::getValue(S32 n) +{ + S32 index = getMin(getMax(n,0), (S32)mFilter.size()-1); + return mFilter[index]; +} + + +inline void GuiFilterCtrl::set(const Filter &f) +{ + mControlPointRequest = f.size(); + mFilter = f; +} + +#endif diff --git a/gui/editor/guiGraphCtrl.cpp b/gui/editor/guiGraphCtrl.cpp new file mode 100644 index 0000000..524beee --- /dev/null +++ b/gui/editor/guiGraphCtrl.cpp @@ -0,0 +1,365 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/guiGraphCtrl.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/primBuilder.h" + +IMPLEMENT_CONOBJECT(GuiGraphCtrl); + +GuiGraphCtrl::GuiGraphCtrl() +{ + + for(int i = 0; i < MaxPlots; i++) + { + mPlots[i].mAutoPlot = NULL; + mPlots[i].mAutoPlotDelay = 0; + mPlots[i].mGraphColor = ColorF(1.0, 1.0, 1.0); + VECTOR_SET_ASSOCIATION(mPlots[i].mGraphData); + mPlots[i].mGraphMax = 1; + mPlots[i].mGraphType = Polyline; + for(int j = 0; j < MaxDataPoints; j++) + mPlots[i].mGraphData.push_front(0.0); + } + + AssertWarn(MaxPlots == 6, "Only 6 plot colors initialized. Update following code if you change MaxPlots."); + + mPlots[0].mGraphColor = ColorF(1.0, 1.0, 1.0); + mPlots[1].mGraphColor = ColorF(1.0, 0.0, 0.0); + mPlots[2].mGraphColor = ColorF(0.0, 1.0, 0.0); + mPlots[3].mGraphColor = ColorF(0.0, 0.0, 1.0); + mPlots[4].mGraphColor = ColorF(0.0, 1.0, 1.0); + mPlots[5].mGraphColor = ColorF(0.0, 0.0, 0.0); +} + +static const EnumTable::Enums enumGraphTypes[] = { + { GuiGraphCtrl::Bar, "bar" }, + { GuiGraphCtrl::Filled, "filled" }, + { GuiGraphCtrl::Point, "point" }, + { GuiGraphCtrl::Polyline , "polyline" }, +}; + +static const EnumTable gGraphTypeTable( 4, &enumGraphTypes[0] ); + +bool GuiGraphCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + setActive(true); + return true; +} + +void GuiGraphCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + + if( mProfile->mBorder ) + { + RectI rect( offset.x, offset.y, getWidth(), getHeight() ); + GFX->getDrawUtil()->drawRect( rect, mProfile->mBorderColor ); + } + + if (mBlendSB.isNull()) + { + GFXStateBlockDesc desc; + + desc.setBlend(true, GFXBlendSrcColor, GFXBlendInvSrcColor); + mBlendSB = GFX->createStateBlock(desc); + + desc.setBlend(false, GFXBlendOne, GFXBlendZero); + mSolidSB = GFX->createStateBlock(desc); + + } + + GFX->setStateBlock(mBlendSB); + + + ColorF color( 1.f, 1.f, 1.f, 0.5f ); + GFX->getDrawUtil()->drawRectFill( updateRect, color ); + + GFX->setStateBlock(mSolidSB); + + for (int k = 0; k < MaxPlots; k++) + { + // Check if there is an autoplot and the proper amount of time has passed, if so add datum. + if((mPlots[k].mAutoPlot!=NULL) && + (mPlots[k].mAutoPlotDelay < (Sim::getCurrentTime() - mPlots[k].mAutoPlotLastDisplay))) + { + mPlots[k].mAutoPlotLastDisplay = Sim::getCurrentTime(); + addDatum(k, Con::getFloatVariable(mPlots[k].mAutoPlot)); + Con::setIntVariable(mPlots[k].mAutoPlot, 0); + } + + // Adjust scale to max value + 5% so we can see high values + F32 Scale = (F32(getExtent().y) / (F32(mPlots[k].mGraphMax*1.05))); + + // Nothing to graph + if (mPlots[k].mGraphData.size() == 0) + continue; + + // Bar graph + if(mPlots[k].mGraphType == Bar) + { + PrimBuild::begin( GFXTriangleFan, getExtent().x * 4 ); // Max size correct? -pw + PrimBuild::color( mPlots[k].mGraphColor ); + + S32 temp1,temp2; + temp1 = 0; + + for (S32 sample = 0; sample < getExtent().x; sample++) + { + if(mPlots[k].mGraphData.size() >= getExtent().x) + temp2 = sample; + else + temp2 = (S32)(((F32)getExtent().x / (F32)mPlots[k].mGraphData.size()) * (F32)sample); + + PrimBuild::vertex2i(getPosition().x + temp1, + (getPosition().y + getExtent().y) - (S32)(getDatum(k, sample) * Scale)); + + PrimBuild::vertex2i(getPosition().x + temp2, + (getPosition().y + getExtent().y) - (S32)(getDatum(k, sample) * Scale)); + + PrimBuild::vertex2i(getPosition().x + temp2, + getPosition().y + getExtent().y); + + PrimBuild::vertex2i(getPosition().x + temp1, + getPosition().y + getExtent().y); + + temp1 = temp2; + } + + PrimBuild::end(); + } + + // Filled graph + else if(mPlots[k].mGraphType == Filled) + { + // Not getExtent().x - 1, because we are adding another 4 verts after this loop. -pw + PrimBuild::begin( GFXTriangleFan, getExtent().x * 4 ); // Max size correct? -pw + + PrimBuild::color( mPlots[k].mGraphColor ); + + S32 temp1,temp2; + temp1 = 0; + + for (S32 sample = 0; sample < (getExtent().x-1); sample++) + { + if(mPlots[k].mGraphData.size() >= getExtent().x) + temp2 = sample; + else + temp2 = (S32)(((F32)getExtent().x / (F32)mPlots[k].mGraphData.size()) * (F32)sample); + + PrimBuild::vertex2i(getPosition().x + temp1, + (getPosition().y + getExtent().y) - (S32)(getDatum(k, sample) * Scale)); + + PrimBuild::vertex2i(getPosition().x + temp2, + (getPosition().y + getExtent().y) - (S32)(getDatum(k, sample+1) * Scale)); + + PrimBuild::vertex2i(getPosition().x + temp2, + getPosition().y + getExtent().y); + + PrimBuild::vertex2i(getPosition().x + temp1, + getPosition().y + getExtent().y); + + temp1 = temp2; + } + + + // last point + S32 sample = getExtent().x; + + if(mPlots[k].mGraphData.size() >= getExtent().x) + temp2 = sample; + else + temp2 = (S32)(((F32)getExtent().x / (F32)mPlots[k].mGraphData.size()) * (F32)sample); + + PrimBuild::vertex2i(getPosition().x + temp1, + (getPosition().y + getExtent().y) - (S32)(getDatum(k, sample) * Scale)); + + PrimBuild::vertex2i(getPosition().x + temp2, + (getPosition().y + getExtent().y) - (S32)(getDatum(k, sample) * Scale)); + + PrimBuild::vertex2i(getPosition().x + temp2, + getPosition().y + getExtent().y); + + PrimBuild::vertex2i(getPosition().x + temp1, + getPosition().y + getExtent().y); + + PrimBuild::end(); + } + + + // Point or Polyline graph + else if((mPlots[k].mGraphType == Point) || (mPlots[k].mGraphType == Polyline)) + { + if(mPlots[k].mGraphType == Point) + PrimBuild::begin( GFXPointList, getExtent().x ); // Max size correct? -pw + else + PrimBuild::begin( GFXLineStrip, getExtent().x ); + + PrimBuild::color( mPlots[k].mGraphColor ); + + for (S32 sample = 0; sample < getExtent().x; sample++) + { + S32 temp; + if(mPlots[k].mGraphData.size() >= getExtent().x) + temp = sample; + else + temp = (S32)(((F32)getExtent().x / (F32)mPlots[k].mGraphData.size()) * (F32)sample); + + PrimBuild::vertex2i(getPosition().x + temp, + (getPosition().y + getExtent().y) - (S32)(getDatum(k, sample) * Scale)); + } + + PrimBuild::end(); + } + + } + +} + +void GuiGraphCtrl::addDatum(S32 plotID, F32 v) +{ + AssertFatal(plotID > -1 && plotID < MaxPlots, "Invalid plot specified!"); + + // Add the data and trim the vector... + mPlots[plotID].mGraphData.push_front( v ); + + if(mPlots[plotID].mGraphData.size() > MaxDataPoints) + mPlots[plotID].mGraphData.pop_back(); + + // Keep record of maximum data value for scaling purposes. + if(v > mPlots[plotID].mGraphMax) + mPlots[plotID].mGraphMax = v; +} + +float GuiGraphCtrl::getDatum( int plotID, int sample) +{ + AssertFatal(plotID > -1 && plotID < MaxPlots, "Invalid plot specified!"); + AssertFatal(sample > -1 && sample < MaxDataPoints, "Invalid sample specified!"); + return mPlots[plotID].mGraphData[sample]; +} + +void GuiGraphCtrl::addAutoPlot(S32 plotID, const char *variable, S32 update) +{ + mPlots[plotID].mAutoPlot = StringTable->insert(variable); + mPlots[plotID].mAutoPlotDelay = update; + Con::setIntVariable(mPlots[plotID].mAutoPlot, 0); +} + +void GuiGraphCtrl::removeAutoPlot(S32 plotID) +{ + mPlots[plotID].mAutoPlot = NULL; +} + +void GuiGraphCtrl::setGraphType(S32 plotID, const char *graphType) +{ + AssertFatal(plotID > -1 && plotID < MaxPlots, "Invalid plot specified!"); + if(!dStricmp(graphType,"Bar")) + mPlots[plotID].mGraphType = Bar; + else if(!dStricmp(graphType,"Filled")) + mPlots[plotID].mGraphType = Filled; + else if(!dStricmp(graphType,"Point")) + mPlots[plotID].mGraphType = Point; + else if(!dStricmp(graphType,"Polyline")) + mPlots[plotID].mGraphType = Polyline; + else AssertWarn(true, "Invalid graph type!"); +} + +ConsoleMethod(GuiGraphCtrl, addDatum, void, 4, 4, "(int plotID, float v)" + "Add a data point to the given plot.") +{ + S32 plotID = dAtoi(argv[2]); + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + object->addDatum( plotID, dAtof(argv[3])); +} + +ConsoleMethod(GuiGraphCtrl, getDatum, F32, 4, 4, "(int plotID, int samples)" + "Get a data point from the plot specified, samples from the start of the graph.") +{ + S32 plotID = dAtoi(argv[2]); + S32 samples = dAtoi(argv[3]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return -1; + } + if(samples > object->MaxDataPoints) + { + Con::errorf("Invalid sample."); + return -1; + } + + return object->getDatum(plotID, samples); +} + +ConsoleMethod(GuiGraphCtrl, addAutoPlot, void, 5, 5, "(int plotID, string variable, int update)" + "Adds a data point with value variable, every update ms.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->addAutoPlot(plotID,argv[3],dAtoi(argv[4])); +} + +ConsoleMethod(GuiGraphCtrl, removeAutoPlot, void, 3, 3, "(int plotID)" + "Stops automatic pointing over set interval.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->removeAutoPlot(plotID); +} + +ConsoleMethod(GuiGraphCtrl, setGraphType, void, 4, 4, "(int plotID, string graphType)" + "Change GraphType of plot plotID.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphType(dAtoi(argv[2]), argv[3]); +} + +ConsoleMethod(GuiGraphCtrl, matchScale, void, 3, GuiGraphCtrl::MaxPlots+2, "(int plotID, int plotID, ...)" + "Sets the scale of all specified plots to the maximum scale among them.") +{ + F32 Max = 0; + for(int i=0; i < (argc-2); i++) + { + if(dAtoi(argv[2+i]) > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + if (object->mPlots[dAtoi(argv[2+i])].mGraphMax > Max) + Max = object->mPlots[dAtoi(argv[2+i])].mGraphMax; + } + for(int i=0; i < (argc-2); i++) + object->mPlots[dAtoi(argv[2+i])].mGraphMax = Max; +} \ No newline at end of file diff --git a/gui/editor/guiGraphCtrl.h b/gui/editor/guiGraphCtrl.h new file mode 100644 index 0000000..eb3ad14 --- /dev/null +++ b/gui/editor/guiGraphCtrl.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIGRAPHCTRL_H_ +#define _GUIGRAPHCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +class GuiGraphCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + enum Constants { + MaxPlots = 6, + MaxDataPoints = 200 + }; + + enum GraphType { + Point, + Polyline, + Filled, + Bar + }; + + struct PlotInfo + { + const char *mAutoPlot; + U32 mAutoPlotDelay; + SimTime mAutoPlotLastDisplay; + ColorF mGraphColor; + Vector mGraphData; + F32 mGraphMax; + GraphType mGraphType; + }; + + PlotInfo mPlots[MaxPlots]; + + GFXStateBlockRef mSolidSB; + GFXStateBlockRef mBlendSB; + + //creation methods + DECLARE_CONOBJECT(GuiGraphCtrl); + DECLARE_CATEGORY( "Gui Other" ); + DECLARE_DESCRIPTION( "A control that allows to plot curve graphs." ); + + GuiGraphCtrl(); + + //Parental methods + bool onWake(); + + void onRender(Point2I offset, const RectI &updateRect); + + // Graph interface + void addDatum(S32 plotID, F32 v); + F32 getDatum(S32 plotID, S32 samples); + void addAutoPlot(S32 plotID, const char *variable, S32 update); + void removeAutoPlot(S32 plotID); + void setGraphType(S32 plotID, const char *graphType); +}; + +#endif diff --git a/gui/editor/guiImageList.cpp b/gui/editor/guiImageList.cpp new file mode 100644 index 0000000..98bd488 --- /dev/null +++ b/gui/editor/guiImageList.cpp @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// Torque 3D - ImageList Control +// Copyright (C) 2005 Justin DuJardin. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "gui/editor/guiImageList.h" + +IMPLEMENT_CONOBJECT(GuiImageList); + +GuiImageList::GuiImageList() +{ + VECTOR_SET_ASSOCIATION(mTextures); + mTextures.clear(); + mUniqueId = 0; +} + +U32 GuiImageList::Insert( const char* texturePath, GFXTextureProfile *Type ) +{ + TextureEntry *t = new TextureEntry; + + if ( ! t ) return -1; + + t->TexturePath = StringTable->insert(texturePath); + if ( *t->TexturePath ) + { + t->Handle = GFXTexHandle(t->TexturePath, Type, avar("%s() - t->Handle (line %d)", __FUNCTION__, __LINE__)); + + if ( t->Handle ) + { + t->id = ++mUniqueId; + + mTextures.push_back( t ); + + return t->id; + + } + } + + // Free Texture Entry. + delete t; + + // Return Failure. + return -1; + +} + +bool GuiImageList::Clear() +{ + while ( mTextures.size() ) + FreeTextureEntry( mTextures[0] ); + mTextures.clear(); + + mUniqueId = 0; + + return true; +} + +bool GuiImageList::FreeTextureEntry( U32 Index ) +{ + U32 Id = IndexFromId( Index ); + if ( Id != -1 ) + return FreeTextureEntry( mTextures[ Id ] ); + else + return false; +} + +bool GuiImageList::FreeTextureEntry( PTextureEntry Entry ) +{ + if ( ! Entry ) + return false; + + U32 id = IndexFromId( Entry->id ); + + delete Entry; + + mTextures.erase ( id ); + + return true; +} + +U32 GuiImageList::IndexFromId ( U32 Id ) +{ + if ( !mTextures.size() ) return -1; + Vector::iterator i = mTextures.begin(); + U32 j = 0; + for ( ; i != mTextures.end(); i++ ) + { + if ( i ) + { + if ( (*i)->id == Id ) + return j; + j++; + } + } + + return -1; +} + +U32 GuiImageList::IndexFromPath ( const char* Path ) +{ + if ( !mTextures.size() ) return -1; + Vector::iterator i = mTextures.begin(); + for ( ; i != mTextures.end(); i++ ) + { + if ( dStricmp( Path, (*i)->TexturePath ) == 0 ) + return (*i)->id; + } + + return -1; +} + +void GuiImageList::initPersistFields() +{ + Parent::initPersistFields(); +} + +ConsoleMethod(GuiImageList, getImage,const char *, 3, 3, "(int index) Get a path to the texture at the specified index") +{ + return object->GetTexturePath(dAtoi(argv[2])); +} + + +ConsoleMethod(GuiImageList, clear,bool, 2, 2, "clears the imagelist") +{ + return object->Clear(); +} + +ConsoleMethod(GuiImageList, count,S32, 2, 2, "gets the number of images in the list") +{ + return object->Count(); +} + + +ConsoleMethod(GuiImageList, remove, bool, 3,3, "(image index) removes an image from the list by index") +{ + return object->FreeTextureEntry( dAtoi(argv[2] ) ); +} + +ConsoleMethod(GuiImageList, getIndex, S32, 3,3, "(image path) retrieves the imageindex of a specified texture in the list") +{ + return object->IndexFromPath( argv[2] ); +} + + +ConsoleMethod(GuiImageList, insert, S32, 3, 3, "(image path) insert an image into imagelist- returns the image index or -1 for failure") +{ + return object->Insert( argv[2] ); +} + +GFXTexHandle GuiImageList::GetTextureHandle( U32 Index ) +{ + U32 ItemIndex = IndexFromId(Index); + if ( ItemIndex != -1 ) + return mTextures[ItemIndex]->Handle; + else + return NULL; + +} +GFXTexHandle GuiImageList::GetTextureHandle( const char* TexturePath ) +{ + Vector::iterator i = mTextures.begin(); + for ( ; i != mTextures.end(); i++ ) + { + if ( dStricmp( TexturePath, (*i)->TexturePath ) == 0 ) + return (*i)->Handle; + } + + return NULL; +} + + +const char *GuiImageList::GetTexturePath( U32 Index ) +{ + U32 ItemIndex = IndexFromId(Index); + if ( ItemIndex != -1 ) + return mTextures[ItemIndex]->TexturePath; + else + return ""; +} diff --git a/gui/editor/guiImageList.h b/gui/editor/guiImageList.h new file mode 100644 index 0000000..a7288ca --- /dev/null +++ b/gui/editor/guiImageList.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Torque 3D - ImageList Class for GUI's +// Copyright (C) 2005 Justin DuJardin. +//----------------------------------------------------------------------------- + +#ifndef _GUIIMAGELIST_H_ +#define _GUIIMAGELIST_H_ + +#include "console/simDatablock.h" + +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif + + +class GuiImageList : public SimObject +{ + private: + typedef SimObject Parent; + + public: + typedef struct tag_TextureEntry + { + StringTableEntry TexturePath; + GFXTexHandle Handle; + U32 id; + }TextureEntry,*PTextureEntry; + + Vector mTextures; + + protected: + + + U32 mUniqueId; + + public: + GuiImageList(); + + DECLARE_CONOBJECT(GuiImageList); + + static void initPersistFields(); + + // Image managing functions + bool Clear(); + inline U32 Count() { return (U32)mTextures.size(); }; + U32 Insert( const char* texturePath , GFXTextureProfile *Type = &GFXDefaultGUIProfile ); + + bool FreeTextureEntry( U32 Index ); + bool FreeTextureEntry( PTextureEntry Entry ); + + GFXTexHandle GetTextureHandle( U32 Index ); + GFXTexHandle GetTextureHandle( const char* TexturePath ); + + const char * GetTexturePath( U32 Index ); + + U32 IndexFromId ( U32 Id ); + U32 IndexFromPath ( const char* Path ); + +}; + +#endif //_GUIIMAGELISTCTRL_H_ diff --git a/gui/editor/guiInspector.cpp b/gui/editor/guiInspector.cpp new file mode 100644 index 0000000..f08b3f5 --- /dev/null +++ b/gui/editor/guiInspector.cpp @@ -0,0 +1,525 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/editor/guiInspector.h" +#include "gui/editor/inspector/field.h" +#include "gui/editor/inspector/group.h" +#include "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/inspector/dynamicGroup.h" +#include "gui/containers/guiScrollCtrl.h" +#include "gui/editor/inspector/customField.h" + + +GuiInspector::GuiInspector() + : mTarget( NULL ), + mDividerPos( 0.35f ), + mDividerMargin( 5 ), + mOverDivider( false ), + mMovingDivider( false ), + mHLField( NULL ) +{ + mPadding = 1; +} + +GuiInspector::~GuiInspector() +{ + clearGroups(); +} + +IMPLEMENT_CONOBJECT(GuiInspector); + + +// ConsoleObject + +bool GuiInspector::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + return true; +} + +void GuiInspector::initPersistFields() +{ + addField( "dividerMargin", TypeS32, Offset( mDividerMargin, GuiInspector ) ); + addField( "groupFilters", TypeRealString, Offset( mGroupFilters, GuiInspector ), "Specify groups that should be shown or not. Specifying 'shown' implicitly does 'not show' all other groups. Example string: +name -otherName" ); + + Parent::initPersistFields(); +} + +// SimObject + +void GuiInspector::onDeleteNotify( SimObject *object ) +{ + if ( object == mTarget ) + clearGroups(); +} + +// GuiControl + +void GuiInspector::parentResized(const RectI &oldParentRect, const RectI &newParentRect) +{ + GuiControl *parent = getParent(); + if ( parent && dynamic_cast(parent) != NULL ) + { + GuiScrollCtrl *scroll = dynamic_cast(parent); + setWidth( ( newParentRect.extent.x - ( scroll->scrollBarThickness() + 4 ) ) ); + } + else + Parent::parentResized(oldParentRect,newParentRect); +} + +bool GuiInspector::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + //F32 dividerPerc = (F32)getWidth() / (F32)mDividerPos; + + bool result = Parent::resize( newPosition, newExtent ); + + //mDividerPos = (F32)getWidth() * dividerPerc; + + updateDivider(); + + return result; +} + +GuiControl* GuiInspector::findHitControl( const Point2I &pt, S32 initialLayer ) +{ + if ( mOverDivider || mMovingDivider ) + return this; + + return Parent::findHitControl( pt, initialLayer ); +} + +void GuiInspector::getCursor( GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent ) +{ + GuiCanvas *pRoot = getRoot(); + if( !pRoot ) + return; + + S32 desiredCursor = mOverDivider ? PlatformCursorController::curResizeVert : PlatformCursorController::curArrow; + + // Bail if we're already at the desired cursor + if ( pRoot->mCursorChanged == desiredCursor ) + return; + + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); + PlatformCursorController *pController = pWindow->getCursorController(); + AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); + + // Now change the cursor shape + pController->popCursor(); + pController->pushCursor(desiredCursor); + pRoot->mCursorChanged = desiredCursor; +} + +void GuiInspector::onMouseMove(const GuiEvent &event) +{ + if ( collideDivider( globalToLocalCoord( event.mousePoint ) ) ) + mOverDivider = true; + else + mOverDivider = false; +} + +void GuiInspector::onMouseDown(const GuiEvent &event) +{ + if ( mOverDivider ) + { + mMovingDivider = true; + } +} + +void GuiInspector::onMouseUp(const GuiEvent &event) +{ + mMovingDivider = false; +} + +void GuiInspector::onMouseDragged(const GuiEvent &event) +{ + if ( !mMovingDivider ) + return; + + Point2I localPnt = globalToLocalCoord( event.mousePoint ); + + S32 inspectorWidth = getWidth(); + + // Distance from mouse/divider position in local space + // to the right edge of the inspector + mDividerPos = inspectorWidth - localPnt.x; + mDividerPos = mClamp( mDividerPos, 0, inspectorWidth ); + + // Divide that by the inspectorWidth to get a percentage + mDividerPos /= inspectorWidth; + + updateDivider(); +} + +bool GuiInspector::findExistentGroup( StringTableEntry groupName ) +{ + // If we have no groups, it couldn't possibly exist + if( mGroups.empty() ) + return false; + + // Attempt to find it in the group list + Vector::iterator i = mGroups.begin(); + + for( ; i != mGroups.end(); i++ ) + { + if( dStricmp( (*i)->getGroupName(), groupName ) == 0 ) + return true; + } + + return false; +} + +void GuiInspector::updateFieldValue( StringTableEntry fieldName, StringTableEntry arrayIdx ) +{ + // We don't know which group contains the field of this name, + // so ask each group in turn, and break when a group returns true + // signifying it contained and updated that field. + + Vector::iterator groupIter = mGroups.begin(); + + for( ; groupIter != mGroups.end(); groupIter++ ) + { + if ( (*groupIter)->updateFieldValue( fieldName, arrayIdx ) ) + break; + } +} + +void GuiInspector::clearGroups() +{ + // If we're clearing the groups, we want to clear our target too. + mTarget = NULL; + + // And the HL field + mHLField = NULL; + + // If we have no groups, there's nothing to clear! + if( mGroups.empty() ) + return; + + // Attempt to find it in the group list + Vector::iterator i = mGroups.begin(); + + freeze(true); + + // Delete Groups + for( ; i != mGroups.end(); i++ ) + { + if((*i) && (*i)->isProperlyAdded()) + (*i)->deleteObject(); + } + + mGroups.clear(); + + freeze(false); + updatePanes(); +} + +void GuiInspector::inspectObject( SimObject *object ) +{ + //GuiCanvas *guiCanvas = getRoot(); + //if( !guiCanvas ) + // return; + + //SimObjectPtr currResponder = guiCanvas->getFirstResponder(); + + // If our target is the same as our current target, just update the groups. + if( mTarget == object ) + { + Vector::iterator i = mGroups.begin(); + for ( ; i != mGroups.end(); i++ ) + (*i)->updateAllFields(); + + // Don't steal first responder + //if( !currResponder.isNull() ) + // guiCanvas->setFirstResponder( currResponder ); + + return; + } + + // Give users a chance to customize fields on this object + if( object->isMethod("onDefineFieldTypes") ) + Con::executef( object, "onDefineFieldTypes" ); + + // Clear our current groups + clearGroups(); + + // Set Target + if ( mTarget ) + clearNotify( mTarget ); + mTarget = object; + deleteNotify( mTarget ); + + // Special group for fields which should appear at the top of the + // list outside of a rollout control. + GuiInspectorGroup *ungroup = new GuiInspectorGroup( mTarget, "Ungrouped", this ); + ungroup->mHideHeader = true; + ungroup->mCanCollapse = false; + if( ungroup != NULL ) + { + ungroup->registerObject(); + mGroups.push_back( ungroup ); + addObject( ungroup ); + } + + // Put the 'transform' group first + GuiInspectorGroup *transform = new GuiInspectorGroup( mTarget, "Transform", this ); + if( transform != NULL ) + { + transform->registerObject(); + mGroups.push_back( transform ); + addObject( transform ); + } + + // Always create the 'general' group (for un-grouped fields) + GuiInspectorGroup *general = new GuiInspectorGroup( mTarget, "General", this ); + if( general != NULL ) + { + general->registerObject(); + mGroups.push_back( general ); + addObject( general ); + } + + // Grab this objects field list + AbstractClassRep::FieldList &fieldList = mTarget->getModifiableFieldList(); + AbstractClassRep::FieldList::iterator itr; + + // Iterate through, identifying the groups and create necessary GuiInspectorGroups + for(itr = fieldList.begin(); itr != fieldList.end(); itr++) + { + if ( itr->type == AbstractClassRep::StartGroupFieldType ) + { + if ( !findExistentGroup( itr->pGroupname ) && !isGroupFiltered( itr->pGroupname ) ) + { + GuiInspectorGroup *group = new GuiInspectorGroup( mTarget, itr->pGroupname, this ); + if( group != NULL ) + { + group->registerObject(); + mGroups.push_back( group ); + addObject( group ); + } + } + } + } + + // Deal with dynamic fields + if ( !isGroupFiltered( "Dynamic Fields" ) ) + { + GuiInspectorGroup *dynGroup = new GuiInspectorDynamicGroup( mTarget, "Dynamic Fields", this); + if( dynGroup != NULL ) + { + dynGroup->registerObject(); + mGroups.push_back( dynGroup ); + addObject( dynGroup ); + } + } + + // Add the SimObjectID field to the ungrouped group + GuiInspectorCustomField *field = new GuiInspectorCustomField(); + field->init(this, ungroup, object); + + if( field->registerObject() ) + { + ungroup->mChildren.push_back( field ); + ungroup->mStack->addObject( field ); + + StringTableEntry caption = StringTable->insert( String("Id"), true ); + field->setCaption( caption ); + + field->setData( StringTable->insert( object->getIdString() ) ); + + field->setDoc( StringTable->insert("SimObjectId of this object. [Read Only]")); + } + else + delete field; + + // Add the Source Class field to the ungrouped group + field = new GuiInspectorCustomField(); + field->init(this, ungroup, object); + + if( field->registerObject() ) + { + ungroup->mChildren.push_back( field ); + ungroup->mStack->addObject( field ); + + StringTableEntry caption = StringTable->insert( String("Source Class"), true ); + field->setCaption( caption ); + + if(object->getClassRep()) + { + field->setData( StringTable->insert( object->getClassRep()->getClassName(), true )); + + Namespace* ns = object->getClassRep()->getNameSpace(); + field->setToolTip( StringTable->insert( Con::getNamespaceList(ns), true )); + } + else + { + field->setData( StringTable->insert( String("") )); + } + + field->setDoc( StringTable->insert( "Source code class of this object. [Read Only]") ); + } + else + delete field; + + + // If the general group is still empty at this point ( or filtered ), kill it. + if ( isGroupFiltered( "General" ) || general->mStack->size() == 0 ) + { + for(S32 i=0; ideleteObject(); + updatePanes(); + + break; + } + } + } + + // If transform turns out to be empty or filtered, remove it + if( isGroupFiltered( "Transform" ) || transform->mStack->size() == 0 ) + { + for(S32 i=0; ideleteObject(); + updatePanes(); + + break; + } + } + } + +} + +void GuiInspector::setName( StringTableEntry newName ) +{ + if( mTarget == NULL ) + return; + + StringTableEntry name = StringTable->insert(newName); + + // Only assign a new name if we provide one + mTarget->assignName(name); +} + +bool GuiInspector::collideDivider( const Point2I &localPnt ) +{ + RectI divisorRect( getWidth() - getWidth() * mDividerPos - mDividerMargin, 0, mDividerMargin * 2, getHeight() ); + + if ( divisorRect.pointInRect( localPnt ) ) + return true; + + return false; +} + +void GuiInspector::updateDivider() +{ + for ( U32 i = 0; i < mGroups.size(); i++ ) + for ( U32 j = 0; j < mGroups[i]->mChildren.size(); j++ ) + mGroups[i]->mChildren[j]->updateRects(); + + //setUpdate(); +} + +void GuiInspector::getDivider( S32 &pos, S32 &margin ) +{ + pos = (F32)getWidth() * mDividerPos; + margin = mDividerMargin; +} + +void GuiInspector::setHighlightField( GuiInspectorField *field ) +{ + if ( mHLField == field ) + return; + + if ( mHLField.isValid() ) + mHLField->setHLEnabled( false ); + mHLField = field; + + // We could have been passed a null field, meaning, set no field highlighted. + if ( mHLField.isNull() ) + return; + + mHLField->setHLEnabled( true ); +} + +bool GuiInspector::isGroupFiltered( const char *groupName ) const +{ + // Internal and Ungrouped always filtered, we never show them. + if ( dStricmp( groupName, "Internal" ) == 0 || + dStricmp( groupName, "Ungrouped" ) == 0 || + dStricmp( groupName, "AdvCoordManipulation" ) == 0) + return true; + + // Normal case, determine if filtered by looking at the mGroupFilters string. + String searchStr; + + // Is this group explicitly show? Does it immediately follow a + char. + searchStr = String::ToString( "+%s", groupName ); + if ( mGroupFilters.find( searchStr ) != String::NPos ) + return false; + + // Were there any other + characters, if so, we are implicitly hidden. + if ( mGroupFilters.find( "+" ) != String::NPos ) + return true; + + // Is this group explicitly hidden? Does it immediately follow a - char. + searchStr = String::ToString( "-%s", groupName ); + if ( mGroupFilters.find( searchStr ) != String::NPos ) + return true; + + return false; +} + +ConsoleMethod( GuiInspector, inspect, void, 3, 3, "Inspect(Object)") +{ + SimObject * target = Sim::findObject(argv[2]); + if(!target) + { + if(dAtoi(argv[2]) > 0) + Con::warnf("%s::inspect(): invalid object: %s", argv[0], argv[2]); + + object->clearGroups(); + return; + } + + object->inspectObject(target); +} + +ConsoleMethod( GuiInspector, refresh, void, 2, 2, "Reinspect the currently selected object." ) +{ + SimObject *target = object->getInspectObject(); + if ( target ) + object->inspectObject( target ); +} + +ConsoleMethod( GuiInspector, getInspectObject, const char*, 2, 2, "getInspectObject() - Returns currently inspected object" ) +{ + SimObject *pSimObject = object->getInspectObject(); + if( pSimObject != NULL ) + return pSimObject->getIdString(); + + return ""; +} + +ConsoleMethod( GuiInspector, setName, void, 3, 3, "setName(NewObjectName)") +{ + object->setName(argv[2]); +} + +ConsoleMethod( GuiInspector, apply, void, 2, 2, "apply() - Force application of inspected object's attributes" ) +{ + SimObject *target = object->getInspectObject(); + if ( target ) + target->inspectPostApply(); +} diff --git a/gui/editor/guiInspector.h b/gui/editor/guiInspector.h new file mode 100644 index 0000000..8715010 --- /dev/null +++ b/gui/editor/guiInspector.h @@ -0,0 +1,99 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_H_ +#define _GUI_INSPECTOR_H_ + +#include "gui/containers/guiStackCtrl.h" + + +class GuiInspectorGroup; +class GuiInspectorField; +class GuiInspectorDatablockField; + +class GuiInspector : public GuiStackControl +{ + typedef GuiStackControl Parent; + +public: + + GuiInspector(); + virtual ~GuiInspector(); + + DECLARE_CONOBJECT(GuiInspector); + DECLARE_CATEGORY( "Gui Editor" ); + + // Console Object + + bool onAdd(); + static void initPersistFields(); + + // SimObject + virtual void onDeleteNotify( SimObject *object ); + + // GuiControl + + virtual void parentResized( const RectI &oldParentRect, const RectI &newParentRect ); + virtual bool resize( const Point2I &newPosition, const Point2I &newExtent ); + virtual GuiControl* findHitControl( const Point2I &pt, S32 initialLayer ); + virtual void getCursor( GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent ); + virtual void onMouseMove( const GuiEvent &event ); + virtual void onMouseDown( const GuiEvent &event ); + virtual void onMouseUp( const GuiEvent &event ); + virtual void onMouseDragged( const GuiEvent &event ); + + // GuiInspector + + /// Set the currently inspected object + virtual void inspectObject( SimObject *object ); + + /// Get the currently inspected object + SimObject* getInspectObject() { return mTarget; } + + /// Set the currently inspected object name + void setName( StringTableEntry newName ); + + /// Deletes all GuiInspectorGroups + void clearGroups(); + + /// Returns true if the named group exists + /// Helper for inspectObject + bool findExistentGroup( StringTableEntry groupName ); + + /// Should there be a GuiInspectorField associated with this fieldName, + /// update it to reflect actual/current value of that field in the inspected object. + /// Added to support UndoActions + void updateFieldValue( StringTableEntry fieldName, const char* arrayIdx ); + + /// Divider position is interpreted as an offset + /// from the right edge of the field control. + /// Divider margin is an offset on both left and right + /// sides of the divider in which it can be selected + /// with the mouse. + void getDivider( S32 &pos, S32 &margin ); + + void updateDivider(); + + bool collideDivider( const Point2I &localPnt ); + + void setHighlightField( GuiInspectorField *field ); + + // If returns true that group will not be inspected. + bool isGroupFiltered( const char *groupName ) const; + +protected: + + Vector mGroups; + + SimObject *mTarget; + F32 mDividerPos; + S32 mDividerMargin; + bool mOverDivider; + bool mMovingDivider; + SimObjectPtr mHLField; + String mGroupFilters; +}; + +#endif \ No newline at end of file diff --git a/gui/editor/guiInspectorTypes.cpp b/gui/editor/guiInspectorTypes.cpp new file mode 100644 index 0000000..2fead1a --- /dev/null +++ b/gui/editor/guiInspectorTypes.cpp @@ -0,0 +1,1095 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/guiInspectorTypes.h" + +#include "gui/editor/inspector/group.h" +#include "gui/controls/guiTextEditSliderCtrl.h" +#include "gui/controls/guiTextEditSliderBitmapCtrl.h" +#include "gui/buttons/guiSwatchButtonCtrl.h" +#include "gui/containers/guiDynamicCtrlArrayCtrl.h" +#include "core/strings/stringUnit.h" +#include "materials/materialDefinition.h" +#include "materials/materialManager.h" +#include "materials/customMaterialDefinition.h" +#include "gfx/gfxDrawUtil.h" + + +//----------------------------------------------------------------------------- +// GuiInspectorTypeMenuBase +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeMenuBase); + +GuiControl* GuiInspectorTypeMenuBase::constructEditControl() +{ + GuiControl* retCtrl = new GuiPopUpMenuCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + GuiPopUpMenuCtrl *menu = dynamic_cast(retCtrl); + + // Let's make it look pretty. + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiPopUpMenuProfile" ); + + menu->setField("text", getData()); + + _registerEditControl( retCtrl ); + + // Configure it to update our value when the popup is closed + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply( %d.getText() );", getId(), menu->getId() ); + menu->setField("Command", szBuffer ); + + //now add the entries, allow derived classes to override this + _populateMenu( menu ); + + return retCtrl; +} + +void GuiInspectorTypeMenuBase::setValue( StringTableEntry newValue ) +{ + GuiPopUpMenuCtrl *ctrl = dynamic_cast( mEdit ); + if ( ctrl != NULL ) + ctrl->setText( newValue ); +} + +void GuiInspectorTypeMenuBase::_populateMenu( GuiPopUpMenuCtrl *menu ) +{ + // do nothing, child classes override this. +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeEnum +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeEnum); + +void GuiInspectorTypeEnum::_populateMenu( GuiPopUpMenuCtrl *menu ) +{ + //now add the entries + for(S32 i = 0; i < mField->table->size; i++) + menu->addEntry(mField->table->table[i].label, mField->table->table[i].index); +} + +void GuiInspectorTypeEnum::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeEnum)->setInspectorFieldType("GuiInspectorTypeEnum"); +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeCubemapName +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeCubemapName); + +void GuiInspectorTypeCubemapName::_populateMenu( GuiPopUpMenuCtrl *menu ) +{ + PROFILE_SCOPE( GuiInspectorTypeCubemapName_populateMenu ); + + // This could be expensive looping through the whole RootGroup + // and performing string comparisons... Put a profile here + // to keep an eye on it. + + SimGroup *root = Sim::getRootGroup(); + + SimGroupIterator iter( root ); + for ( ; *iter; ++iter ) + { + if ( dStricmp( (*iter)->getClassName(), "CubemapData" ) == 0 ) + menu->addEntry( (*iter)->getName(), 0 ); + } + + menu->sort(); +} + +void GuiInspectorTypeCubemapName::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeCubemapName)->setInspectorFieldType("GuiInspectorTypeCubemapName"); +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeMaterialName +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiInspectorTypeMaterialName); +GuiInspectorTypeMaterialName::GuiInspectorTypeMaterialName() + : mBrowseButton( NULL ) +{ +} + +void GuiInspectorTypeMaterialName::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeMaterialName)->setInspectorFieldType("GuiInspectorTypeMaterialName"); +} + +GuiControl* GuiInspectorTypeMaterialName::constructEditControl() +{ + GuiControl* retCtrl = new GuiTextEditCtrl(); + + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + _registerEditControl( retCtrl ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() ); + retCtrl->setField("AltCommand", szBuffer ); + retCtrl->setField("Validate", szBuffer ); + + //return retCtrl; + mBrowseButton = new GuiBitmapButtonCtrl(); + + if ( mBrowseButton != NULL ) + { + RectI browseRect( Point2I( ( getLeft() + getWidth()) - 26, getTop() + 2), Point2I(20, getHeight() - 4) ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "materialSelector.showDialog(\"%d.apply\", \"name\");", getId()); + mBrowseButton->setField( "Command", szBuffer ); + + //temporary static button name + char bitmapName[512] = "tools/materialEditor/gui/change-material-btn"; + mBrowseButton->setBitmap( bitmapName ); + + mBrowseButton->setDataField( StringTable->insert("Profile"), NULL, "GuiButtonProfile" ); + mBrowseButton->registerObject(); + addObject( mBrowseButton ); + + // Position + mBrowseButton->resize( browseRect.point, browseRect.extent ); + } + + return retCtrl; +} + +bool GuiInspectorTypeMaterialName::updateRects() +{ + Point2I fieldPos = getPosition(); + Point2I fieldExtent = getExtent(); + S32 dividerPos, dividerMargin; + mInspector->getDivider( dividerPos, dividerMargin ); + + mCaptionRect.set( 0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y ); + // Icon extent 17 x 17 + mBrowseRect.set( fieldExtent.x - 20, 2, 17, fieldExtent.y - 1 ); + mEditCtrlRect.set( fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin - 29, fieldExtent.y ); + + bool editResize = mEdit->resize( mEditCtrlRect.point, mEditCtrlRect.extent ); + bool browseResize = false; + + if ( mBrowseButton != NULL ) + { + browseResize = mBrowseButton->resize( mBrowseRect.point, mBrowseRect.extent ); + } + + return ( editResize || browseResize ); +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeGuiProfile +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeGuiProfile); + +void GuiInspectorTypeGuiProfile::_populateMenu( GuiPopUpMenuCtrl *menu ) +{ + SimGroup *grp = Sim::getGuiDataGroup(); + SimSetIterator iter( grp ); + for ( ; *iter; ++iter ) + { + GuiControlProfile *profile = dynamic_cast(*iter); + if ( profile ) + menu->addEntry( profile->getName(), 0 ); + } + + menu->sort(); +} + +void GuiInspectorTypeGuiProfile::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeGuiProfile)->setInspectorFieldType("GuiInspectorTypeGuiProfile"); +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeCheckBox +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeCheckBox); + +GuiControl* GuiInspectorTypeCheckBox::constructEditControl() +{ + GuiControl* retCtrl = new GuiCheckBoxCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + GuiCheckBoxCtrl *check = dynamic_cast(retCtrl); + + // Let's make it look pretty. + retCtrl->setDataField( StringTable->insert("profile"), NULL, "InspectorTypeCheckboxProfile" ); + retCtrl->setField( "text", "" ); + + check->mIndent = 4; + + retCtrl->setScriptValue( getData() ); + + _registerEditControl( retCtrl ); + + // Configure it to update our value when the popup is closed + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getValue());",getId(),check->getId() ); + check->setField("Command", szBuffer ); + + return retCtrl; +} + + +void GuiInspectorTypeCheckBox::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeBool)->setInspectorFieldType("GuiInspectorTypeCheckBox"); +} + +void GuiInspectorTypeCheckBox::setValue( StringTableEntry newValue ) +{ + GuiButtonBaseCtrl *ctrl = dynamic_cast( mEdit ); + if ( ctrl != NULL ) + ctrl->setStateOn( dAtob(newValue) ); +} + +const char* GuiInspectorTypeCheckBox::getValue() +{ + GuiButtonBaseCtrl *ctrl = dynamic_cast( mEdit ); + if ( ctrl != NULL ) + return ctrl->getScriptValue(); + + return NULL; +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeFileName +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeFileName); + +void GuiInspectorTypeFileName::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeFilename)->setInspectorFieldType("GuiInspectorTypeFileName"); + ConsoleBaseType::getType(TypeStringFilename)->setInspectorFieldType("GuiInspectorTypeFileName"); +} + +GuiControl* GuiInspectorTypeFileName::constructEditControl() +{ + GuiControl* retCtrl = new GuiTextEditCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + // Let's make it look pretty. + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditRightProfile" ); + retCtrl->setDataField( StringTable->insert("tooltipprofile"), NULL, "GuiToolTipProfile" ); + retCtrl->setDataField( StringTable->insert("hovertime"), NULL, "1000" ); + + // Don't forget to register ourselves + _registerEditControl( retCtrl ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(),retCtrl->getId() ); + retCtrl->setField("AltCommand", szBuffer ); + retCtrl->setField("Validate", szBuffer ); + + mBrowseButton = new GuiButtonCtrl(); + + if( mBrowseButton != NULL ) + { + RectI browseRect( Point2I( ( getLeft() + getWidth()) - 26, getTop() + 2), Point2I(20, getHeight() - 4) ); + char szBuffer[512]; + dSprintf( szBuffer, 512, "getLoadFilename(\"*.*|*.*\", \"%d.apply\", %d.getData());", getId(), getId() ); + mBrowseButton->setField( "Command", szBuffer ); + mBrowseButton->setField( "text", "..." ); + mBrowseButton->setDataField( StringTable->insert("Profile"), NULL, "GuiInspectorButtonProfile" ); + mBrowseButton->registerObject(); + addObject( mBrowseButton ); + + // Position + mBrowseButton->resize( browseRect.point, browseRect.extent ); + } + + return retCtrl; +} + +bool GuiInspectorTypeFileName::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + if ( !Parent::resize( newPosition, newExtent ) ) + return false; + + if ( mEdit != NULL ) + { + return updateRects(); + } + + return false; +} + +bool GuiInspectorTypeFileName::updateRects() +{ + S32 dividerPos, dividerMargin; + mInspector->getDivider( dividerPos, dividerMargin ); + Point2I fieldExtent = getExtent(); + Point2I fieldPos = getPosition(); + + mCaptionRect.set( 0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y ); + mEditCtrlRect.set( fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin - 32, fieldExtent.y ); + + bool editResize = mEdit->resize( mEditCtrlRect.point, mEditCtrlRect.extent ); + bool browseResize = false; + + if ( mBrowseButton != NULL ) + { + mBrowseRect.set( fieldExtent.x - 20, 2, 14, fieldExtent.y - 4 ); + browseResize = mBrowseButton->resize( mBrowseRect.point, mBrowseRect.extent ); + } + + return ( editResize || browseResize ); +} + +void GuiInspectorTypeFileName::updateValue() +{ + if ( mTarget && mField ) + { + StringTableEntry data = StringTable->insert( mTarget->getDataField( mField->pFieldname, mFieldArrayIndex ), mField->type == TypeCaseString || mField->type == TypeRealString ? true : false ); + setValue( data ); + mEdit->setDataField( StringTable->insert("tooltip"), NULL, data ); + } +} + +ConsoleMethod( GuiInspectorTypeFileName, apply, void, 3,3, "apply(newValue);" ) +{ + String path( argv[2] ); + if ( path.isNotEmpty() ) + path = Platform::makeRelativePathName( path, Platform::getMainDotCsDir() ); + + object->setData( path.c_str() ); +} + + +//----------------------------------------------------------------------------- +// GuiInspectorTypeImageFileName +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeImageFileName); + +void GuiInspectorTypeImageFileName::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeImageFilename)->setInspectorFieldType("GuiInspectorTypeImageFileName"); +} + +GuiControl* GuiInspectorTypeImageFileName::constructEditControl() +{ + GuiControl *retCtrl = Parent::constructEditControl(); + + if ( retCtrl == NULL ) + return retCtrl; + + retCtrl->mRenderTooltipDelegate.bind( this, &GuiInspectorTypeImageFileName::renderTooltip ); + char szBuffer[512]; + + String extList = GBitmap::sGetExtensionList(); + U32 extCount = StringUnit::getUnitCount( extList, " " ); + + String fileSpec; + + // building the fileSpec string + + fileSpec += "All Image Files|"; + + for ( U32 i = 0; i < extCount; i++ ) + { + fileSpec += "*."; + fileSpec += StringUnit::getUnit( extList, i, " " ); + + if ( i < extCount - 1 ) + fileSpec += ";"; + } + + fileSpec += "|"; + + for ( U32 i = 0; i < extCount; i++ ) + { + String ext = StringUnit::getUnit( extList, i, " " ); + fileSpec += ext; + fileSpec += "|*."; + fileSpec += ext; + + if ( i != extCount - 1 ) + fileSpec += "|"; + } + + dSprintf( szBuffer, 512, "getLoadFilename(\"%s\", \"%d.apply\", %d.getData());", fileSpec.c_str(), getId(), getId() ); + mBrowseButton->setField( "Command", szBuffer ); + + return retCtrl; +} + +bool GuiInspectorTypeImageFileName::renderTooltip( const Point2I &hoverPos, const Point2I &cursorPos, const char *tipText ) +{ + if ( !mAwake ) + return false; + + GuiCanvas *root = getRoot(); + if ( !root ) + return false; + + StringTableEntry filename = getData(); + if ( !filename || !filename[0] ) + return false; + + GFXTexHandle texture( filename, &GFXDefaultStaticDiffuseProfile, avar("%s() - tooltip texture (line %d)", __FUNCTION__, __LINE__) ); + if ( texture.isNull() ) + return false; + + // Render image at a reasonable screen size while + // keeping its aspect ratio... + Point2I screensize = getRoot()->getWindowSize(); + Point2I offset = hoverPos; + Point2I tipBounds; + + U32 texWidth = texture.getWidth(); + U32 texHeight = texture.getHeight(); + F32 aspect = (F32)texHeight / (F32)texWidth; + + const F32 newWidth = 150.0f; + F32 newHeight = aspect * newWidth; + + // Offset below cursor image + offset.y += 20; // TODO: Attempt to fix?: root->getCursorExtent().y; + tipBounds.x = newWidth; + tipBounds.y = newHeight; + + // Make sure all of the tooltip will be rendered width the app window, + // 5 is given as a buffer against the edge + if ( screensize.x < offset.x + tipBounds.x + 5 ) + offset.x = screensize.x - tipBounds.x - 5; + if ( screensize.y < offset.y + tipBounds.y + 5 ) + offset.y = hoverPos.y - tipBounds.y - 5; + + RectI oldClip = GFX->getClipRect(); + RectI rect( offset, tipBounds ); + GFX->setClipRect( rect ); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch( texture, rect ); + + GFX->setClipRect( oldClip ); + + return true; +} + + + +//----------------------------------------------------------------------------- +// GuiInspectorTypeCommand +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeCommand); + +void GuiInspectorTypeCommand::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeCommand)->setInspectorFieldType("GuiInspectorTypeCommand"); +} + +GuiInspectorTypeCommand::GuiInspectorTypeCommand() +{ + mTextEditorCommand = StringTable->insert("TextPad"); +} + +GuiControl* GuiInspectorTypeCommand::constructEditControl() +{ + GuiButtonCtrl* retCtrl = new GuiButtonCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + // Let's make it look pretty. + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + // Don't forget to register ourselves + _registerEditControl( retCtrl ); + + _setCommand( retCtrl, getData() ); + + return retCtrl; +} + +void GuiInspectorTypeCommand::setValue( StringTableEntry newValue ) +{ + GuiButtonCtrl *ctrl = dynamic_cast( mEdit ); + _setCommand( ctrl, newValue ); +} + +void GuiInspectorTypeCommand::_setCommand( GuiButtonCtrl *ctrl, StringTableEntry command ) +{ + if( ctrl != NULL ) + { + ctrl->setField( "text", command ); + + // expandEscape isn't length-limited, so while this _should_ work + // in most circumstances, it may still fail if getData() has lots of + // non-printable characters + S32 len = 2 * dStrlen(command) + 512; + + FrameTemp szBuffer(len); + + S32 written = dSprintf( szBuffer, len, "%s(\"", mTextEditorCommand ); + expandEscape(szBuffer.address() + written, command); + written = strlen(szBuffer); + dSprintf( szBuffer.address() + written, len - written, "\", \"%d.apply\", %d.getRoot());", getId(), getId() ); + + ctrl->setField( "Command", szBuffer ); + } +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeColor (Base for ColorI/ColorF) +//----------------------------------------------------------------------------- +GuiInspectorTypeColor::GuiInspectorTypeColor() + : mBrowseButton( NULL ) +{ +} + +IMPLEMENT_CONOBJECT(GuiInspectorTypeColor); + +GuiControl* GuiInspectorTypeColor::constructEditControl() +{ + GuiControl* retCtrl = new GuiTextEditCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + // Let's make it look pretty. + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + // Don't forget to register ourselves + _registerEditControl( retCtrl ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() ); + retCtrl->setField("AltCommand", szBuffer ); + retCtrl->setField("Validate", szBuffer ); + + mBrowseButton = new GuiSwatchButtonCtrl(); + + if ( mBrowseButton != NULL ) + { + RectI browseRect( Point2I( ( getLeft() + getWidth()) - 26, getTop() + 2), Point2I(20, getHeight() - 4) ); + mBrowseButton->setDataField( StringTable->insert("Profile"), NULL, "GuiInspectorSwatchButtonProfile" ); + mBrowseButton->registerObject(); + addObject( mBrowseButton ); + + char szColor[512]; + if( _getColorConversionFunction() ) + dSprintf( szColor, 512, "%s( %d.color )", _getColorConversionFunction(), mBrowseButton->getId() ); + else + dSprintf( szColor, 512, "%d.color", mBrowseButton->getId() ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%s(%s, \"%d.apply\", %d.getRoot());", mColorFunction, szColor, getId(), getId() ); + + mBrowseButton->setField( "Command", szBuffer ); + + // Position + mBrowseButton->resize( browseRect.point, browseRect.extent ); + } + + return retCtrl; +} + +bool GuiInspectorTypeColor::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + if( !Parent::resize( newPosition, newExtent ) ) + return false; + + return false; +} + +bool GuiInspectorTypeColor::updateRects() +{ + Point2I fieldPos = getPosition(); + Point2I fieldExtent = getExtent(); + S32 dividerPos, dividerMargin; + mInspector->getDivider( dividerPos, dividerMargin ); + + mCaptionRect.set( 0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y ); + mBrowseRect.set( fieldExtent.x - 20, 2, 14, fieldExtent.y - 4 ); + mEditCtrlRect.set( fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin - 29, fieldExtent.y ); + + bool editResize = mEdit->resize( mEditCtrlRect.point, mEditCtrlRect.extent ); + bool browseResize = false; + + if ( mBrowseButton != NULL ) + { + browseResize = mBrowseButton->resize( mBrowseRect.point, mBrowseRect.extent ); + } + + return ( editResize || browseResize ); +} + + +//----------------------------------------------------------------------------- +// GuiInspectorTypeColorI +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeColorI); + +void GuiInspectorTypeColorI::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeColorI)->setInspectorFieldType("GuiInspectorTypeColorI"); +} + +GuiInspectorTypeColorI::GuiInspectorTypeColorI() +{ + mColorFunction = StringTable->insert("getColorI"); +} + +void GuiInspectorTypeColorI::setValue( StringTableEntry newValue ) +{ + // Allow parent to set the edit-ctrl text to the new value. + Parent::setValue( newValue ); + + // Now we also set our color swatch button to the new color value. + if ( mBrowseButton ) + { + ColorI color(255,0,255,255); + S32 r,g,b,a; + dSscanf( newValue, "%d %d %d %d", &r, &g, &b, &a ); + color.red = r; + color.green = g; + color.blue = b; + color.alpha = a; + mBrowseButton->setColor( color ); + } +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeColorF +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeColorF); + +void GuiInspectorTypeColorF::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeColorF)->setInspectorFieldType("GuiInspectorTypeColorF"); +} + +GuiInspectorTypeColorF::GuiInspectorTypeColorF() +{ + mColorFunction = StringTable->insert("getColorF"); +} + +void GuiInspectorTypeColorF::setValue( StringTableEntry newValue ) +{ + // Allow parent to set the edit-ctrl text to the new value. + Parent::setValue( newValue ); + + // Now we also set our color swatch button to the new color value. + if ( mBrowseButton ) + { + ColorF color(1,0,1,1); + dSscanf( newValue, "%f %f %f %f", &color.red, &color.green, &color.blue, &color.alpha ); + mBrowseButton->setColor( color ); + } +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeS32 +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeS32); + +void GuiInspectorTypeS32::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeS32)->setInspectorFieldType("GuiInspectorTypeS32"); +} + +GuiControl* GuiInspectorTypeS32::constructEditControl() +{ + GuiControl* retCtrl = new GuiTextEditSliderCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + // Don't forget to register ourselves + _registerEditControl( retCtrl ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() ); + retCtrl->setField("AltCommand", szBuffer ); + retCtrl->setField("Validate", szBuffer ); + retCtrl->setField("increment","1"); + retCtrl->setField("format","%d"); + retCtrl->setField("range","-2147483648 2147483647"); + + return retCtrl; +} + +void GuiInspectorTypeS32::setValue( StringTableEntry newValue ) +{ + GuiTextEditSliderCtrl *ctrl = dynamic_cast( mEdit ); + if( ctrl != NULL ) + ctrl->setText( newValue ); +} + +//----------------------------------------------------------------------------- +// GuiInspectorTypeS32Mask +//----------------------------------------------------------------------------- + +GuiInspectorTypeBitMask32::GuiInspectorTypeBitMask32() + : mRollout( NULL ), + mArrayCtrl( NULL ), + mHelper( NULL ) +{ +} + +IMPLEMENT_CONOBJECT( GuiInspectorTypeBitMask32 ); + +bool GuiInspectorTypeBitMask32::onAdd() +{ + // Skip our parent because we aren't using mEditCtrl + // and according to our parent that would be cause to fail onAdd. + if ( !Parent::Parent::onAdd() ) + return false; + + if ( !mTarget || !mInspector ) + return false; + + if ( !mField->table ) + return false; + + setDataField( StringTable->insert("profile"), NULL, "GuiInspectorFieldProfile" ); + setBounds(0,0,100,18); + + // Allocate our children controls... + + mRollout = new GuiRolloutCtrl(); + mRollout->mMargin.set( 14, 0, 0, 0 ); + mRollout->mCanCollapse = false; + mRollout->registerObject(); + addObject( mRollout ); + + mArrayCtrl = new GuiDynamicCtrlArrayControl(); + mArrayCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorBitMaskArrayProfile" ); + mArrayCtrl->setField( "autoCellSize", "true" ); + mArrayCtrl->setField( "fillRowFirst", "true" ); + mArrayCtrl->setField( "dynamicSize", "true" ); + mArrayCtrl->setField( "rowSpacing", "4" ); + mArrayCtrl->setField( "colSpacing", "1" ); + mArrayCtrl->setField( "frozen", "true" ); + mArrayCtrl->registerObject(); + + mRollout->addObject( mArrayCtrl ); + + GuiCheckBoxCtrl *pCheckBox = NULL; + + for ( S32 i = 0; i < mField->table->size; i++ ) + { + pCheckBox = new GuiCheckBoxCtrl(); + pCheckBox->setText( mField->table->table[i].label ); + pCheckBox->registerObject(); + mArrayCtrl->addObject( pCheckBox ); + + pCheckBox->autoSize(); + + // Override the normal script callbacks for GuiInspectorTypeCheckBox + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.applyBit();", getId() ); + pCheckBox->setField( "Command", szBuffer ); + } + + mArrayCtrl->setField( "frozen", "false" ); + mArrayCtrl->refresh(); + + mHelper = new GuiInspectorTypeBitMask32Helper(); + mHelper->init( mInspector, mParent, mTarget ); + mHelper->mParentRollout = mRollout; + mHelper->mParentField = this; + mHelper->setInspectorField( mField, mCaption, mFieldArrayIndex ); + mHelper->registerObject(); + mHelper->setExtent( pCheckBox->getExtent() ); + mHelper->setPosition( 0, 0 ); + mRollout->addObject( mHelper ); + + mRollout->sizeToContents(); + mRollout->instantCollapse(); + + updateValue(); + + return true; +} + +void GuiInspectorTypeBitMask32::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeBitMask32)->setInspectorFieldType("GuiInspectorTypeBitMask32"); +} + +void GuiInspectorTypeBitMask32::childResized( GuiControl *child ) +{ + setExtent( mRollout->getExtent() ); +} + +bool GuiInspectorTypeBitMask32::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if ( !Parent::resize( newPosition, newExtent ) ) + return false; + + // Hack... height of 18 is hardcoded + return mHelper->resize( Point2I(0,0), Point2I( newExtent.x, 18 ) ); +} + +bool GuiInspectorTypeBitMask32::updateRects() +{ + if ( !mRollout ) + return false; + + bool result = mRollout->setExtent( getExtent() ); + + for ( U32 i = 0; i < mArrayCtrl->size(); i++ ) + { + GuiInspectorField *pField = dynamic_cast( mArrayCtrl->at(i) ); + if ( pField ) + if ( pField->updateRects() ) + result = true; + } + + if ( mHelper && mHelper->updateRects() ) + result = true; + + return result; +} + +void GuiInspectorTypeBitMask32::setData( StringTableEntry data ) +{ + if ( mField == NULL || mTarget == NULL ) + return; + + mTarget->inspectPreApply(); + + // Callback on the inspector when the field is modified + // to allow creation of undo/redo actions. + const char *oldData = mTarget->getDataField( mField->pFieldname, mFieldArrayIndex); + if ( !oldData ) + oldData = ""; + if ( dStrcmp( oldData, data ) != 0 ) + Con::executef( mInspector, "onInspectorFieldModified", Con::getIntArg(mTarget->getId()), mField->pFieldname, oldData, data ); + + mTarget->setDataField( mField->pFieldname, mFieldArrayIndex, data ); + + // give the target a chance to validate + mTarget->inspectPostApply(); + + // Force our edit to update + updateValue(); +} + +StringTableEntry GuiInspectorTypeBitMask32::getValue() +{ + if ( !mRollout ) + return StringTable->insert( "" ); + + S32 mask = 0; + + for ( U32 i = 0; i < mArrayCtrl->size(); i++ ) + { + GuiCheckBoxCtrl *pCheckBox = dynamic_cast( mArrayCtrl->at(i) ); + + bool bit = pCheckBox->getStateOn(); + mask |= bit << i; + } + + return StringTable->insert( String::ToString(mask).c_str() ); +} + +void GuiInspectorTypeBitMask32::setValue( StringTableEntry value ) +{ + U32 mask = dAtoui( value, 0 ); + + for ( U32 i = 0; i < mArrayCtrl->size(); i++ ) + { + GuiCheckBoxCtrl *pCheckBox = dynamic_cast( mArrayCtrl->at(i) ); + + bool bit = mask & ( 1 << i ); + pCheckBox->setStateOn( bit ); + } + + mHelper->setValue( value ); +} + +void GuiInspectorTypeBitMask32::updateData() +{ + StringTableEntry data = getValue(); + setData( data ); +} + +ConsoleMethod( GuiInspectorTypeBitMask32, applyBit, void, 2,2, "apply();" ) +{ + object->updateData(); +} + +//------------------------------------------------------------------------------ +// GuiInspectorTypeS32MaskHelper +//------------------------------------------------------------------------------ + +GuiInspectorTypeBitMask32Helper::GuiInspectorTypeBitMask32Helper() +: mButton( NULL ), + mParentRollout( NULL ), + mParentField( NULL ) +{ +} + +IMPLEMENT_CONOBJECT( GuiInspectorTypeBitMask32Helper ); + +GuiControl* GuiInspectorTypeBitMask32Helper::constructEditControl() +{ + GuiControl *retCtrl = new GuiTextEditCtrl(); + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + retCtrl->setField( "hexDisplay", "true" ); + + _registerEditControl( retCtrl ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());", mParentField->getId(), retCtrl->getId() ); + retCtrl->setField( "AltCommand", szBuffer ); + retCtrl->setField( "Validate", szBuffer ); + + mButton = new GuiBitmapButtonCtrl(); + + RectI browseRect( Point2I( ( getLeft() + getWidth()) - 26, getTop() + 2), Point2I(20, getHeight() - 4) ); + dSprintf( szBuffer, 512, "%d.toggleExpanded(false);", mParentRollout->getId() ); + mButton->setField( "Command", szBuffer ); + mButton->setField( "buttonType", "ToggleButton" ); + mButton->setDataField( StringTable->insert("Profile"), NULL, "GuiInspectorButtonProfile" ); + mButton->setBitmap( "core/gui/images/arrowBtn" ); + mButton->setStateOn( true ); + mButton->setExtent( 16, 16 ); + mButton->registerObject(); + addObject( mButton ); + + mButton->resize( browseRect.point, browseRect.extent ); + + return retCtrl; +} + +bool GuiInspectorTypeBitMask32Helper::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if ( !Parent::resize( newPosition, newExtent ) ) + return false; + + if ( mEdit != NULL ) + { + return updateRects(); + } + + return false; +} + +bool GuiInspectorTypeBitMask32Helper::updateRects() +{ + S32 dividerPos, dividerMargin; + mInspector->getDivider( dividerPos, dividerMargin ); + Point2I fieldExtent = getExtent(); + Point2I fieldPos = getPosition(); + + mCaptionRect.set( 0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y ); + mEditCtrlRect.set( fieldExtent.x - dividerPos + dividerMargin, 1, dividerPos - dividerMargin - 32, fieldExtent.y ); + + bool editResize = mEdit->resize( mEditCtrlRect.point, mEditCtrlRect.extent ); + bool buttonResize = false; + + if ( mButton != NULL ) + { + mButtonRect.set( fieldExtent.x - 26, 2, 16, 16 ); + buttonResize = mButton->resize( mButtonRect.point, mButtonRect.extent ); + } + + return ( editResize || buttonResize ); +} + +void GuiInspectorTypeBitMask32Helper::setValue( StringTableEntry newValue ) +{ + GuiTextEditCtrl *edit = dynamic_cast(mEdit); + edit->setText( newValue ); +} + + +//----------------------------------------------------------------------------- +// GuiInspectorTypeName +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorTypeName); + +void GuiInspectorTypeName::consoleInit() +{ + Parent::consoleInit(); + + ConsoleBaseType::getType(TypeName)->setInspectorFieldType("GuiInspectorTypeName"); +} + +bool GuiInspectorTypeName::verifyData( StringTableEntry data ) +{ + if( !data || !data[ 0 ] ) + return true; + + bool isValidId = true; + if( !dIsalpha( data[ 0 ] ) && data[ 0 ] != '_' ) + isValidId = false; + else + { + for( U32 i = 1; data[ i ] != '\0'; ++ i ) + { + if( !dIsalnum( data[ i ] ) && data[ i ] != '_' ) + { + isValidId = false; + break; + } + } + } + + if( !isValidId ) + { + Platform::AlertOK( "Error", "Object name must be a valid TorqueScript identifier" ); + return false; + } + + SimObject *pTemp = NULL; + if ( Sim::findObject( data, pTemp ) && pTemp != mTarget ) + { + Platform::AlertOK( "Error", "Cannot assign name, object with that name already exists." ); + return false; + } + + return true; +} + diff --git a/gui/editor/guiInspectorTypes.h b/gui/editor/guiInspectorTypes.h new file mode 100644 index 0000000..4b5d17f --- /dev/null +++ b/gui/editor/guiInspectorTypes.h @@ -0,0 +1,393 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUI_INSPECTOR_TYPES_H_ +#define _GUI_INSPECTOR_TYPES_H_ + +#ifndef _GUI_INSPECTOR_H_ +#include "gui/editor/guiInspector.h" +#endif + +#ifndef _GUI_INSPECTOR_FIELD_H_ +#include "gui/editor/inspector/field.h" +#endif + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#ifndef _GUICHECKBOXCTRL_H_ +#include "gui/buttons/guiCheckBoxCtrl.h" +#endif + +#ifndef _GUIBITMAPBUTTON_H_ +#include "gui/buttons/guiBitmapButtonCtrl.h" +#endif + +class GuiPopUpMenuCtrl; + +/// A base class for other inspector field types which +/// wish to display a popup/dropdown menu. +class GuiInspectorTypeMenuBase : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; +public: + + DECLARE_CONOBJECT(GuiInspectorTypeMenuBase); + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + virtual void setValue( StringTableEntry newValue ); + virtual void _populateMenu( GuiPopUpMenuCtrl *menu ); +}; + +//----------------------------------------------------------------------------- +// TypeEnum GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeEnum : public GuiInspectorTypeMenuBase +{ +private: + typedef GuiInspectorTypeMenuBase Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeEnum); + static void consoleInit(); + + virtual void _populateMenu( GuiPopUpMenuCtrl *menu ); +}; + +//----------------------------------------------------------------------------- +// TypeCubemapName GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeCubemapName : public GuiInspectorTypeMenuBase +{ +private: + typedef GuiInspectorTypeMenuBase Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeCubemapName); + static void consoleInit(); + + virtual void _populateMenu( GuiPopUpMenuCtrl *menu ); +}; + +//-------------------------------------------------------------------------------- +// TypeMaterialName GuiInspectorField Class +//-------------------------------------------------------------------------------- +class GuiBitmapButtonCtrl; + +class GuiInspectorTypeMaterialName : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +public: + + GuiInspectorTypeMaterialName(); + + DECLARE_CONOBJECT(GuiInspectorTypeMaterialName); + static void consoleInit(); + + GuiBitmapButtonCtrl *mBrowseButton; + RectI mBrowseRect; + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + virtual bool updateRects(); +}; + +class GuiInspectorTypeRegularMaterialName : public GuiInspectorTypeMaterialName +{ + typedef GuiInspectorTypeMaterialName Parent; +public: + GuiInspectorTypeRegularMaterialName() {} + DECLARE_CONOBJECT(GuiInspectorTypeRegularMaterialName); + static void consoleInit(); + virtual void _populateMenu( GuiPopUpMenuCtrl *menu ); +}; + +//----------------------------------------------------------------------------- +// GuiInspectorTypeGuiProfile Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeGuiProfile : public GuiInspectorTypeMenuBase +{ +private: + typedef GuiInspectorTypeMenuBase Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeGuiProfile); + static void consoleInit(); + + virtual void _populateMenu( GuiPopUpMenuCtrl *menu ); +}; + +//----------------------------------------------------------------------------- +// GuiInspectorTypeCheckBox Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeCheckBox : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeCheckBox); + static void consoleInit(); + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + virtual void setValue( StringTableEntry newValue ); + virtual const char* getValue(); +}; + +//----------------------------------------------------------------------------- +// TypeCommand GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeCommand : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; + StringTableEntry mTextEditorCommand; + void _setCommand( GuiButtonCtrl *ctrl, StringTableEntry command ); +public: + DECLARE_CONOBJECT(GuiInspectorTypeCommand); + GuiInspectorTypeCommand(); + static void consoleInit(); + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + virtual void setValue( StringTableEntry data ); +}; + +//----------------------------------------------------------------------------- +// TypeFileName GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeFileName : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeFileName); + static void consoleInit(); + + SimObjectPtr mBrowseButton; + RectI mBrowseRect; + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + virtual bool resize(const Point2I &newPosition, const Point2I &newExtent); + virtual bool updateRects(); + virtual void updateValue(); +}; + + +//----------------------------------------------------------------------------- +// TypeImageFileName GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeImageFileName : public GuiInspectorTypeFileName +{ + typedef GuiInspectorTypeFileName Parent; +public: + + DECLARE_CONOBJECT(GuiInspectorTypeImageFileName); + static void consoleInit(); + + virtual GuiControl* constructEditControl(); + bool renderTooltip( const Point2I &hoverPos, const Point2I &cursorPos, const char *tipText = NULL ); +}; + + +//----------------------------------------------------------------------------- +// TypeColor GuiInspectorField Class (Base for ColorI/ColorF) +//----------------------------------------------------------------------------- + +class GuiSwatchButtonCtrl; + +class GuiInspectorTypeColor : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +protected: + + /// Return the name of a function that will be used to convert the + /// floating-point color of the swatch button to the form used by the + /// data field. + virtual const char* _getColorConversionFunction() const { return NULL; } + +public: + + GuiInspectorTypeColor(); + + DECLARE_CONOBJECT(GuiInspectorTypeColor); + + StringTableEntry mColorFunction; + GuiSwatchButtonCtrl *mBrowseButton; + RectI mBrowseRect; + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + virtual bool resize(const Point2I &newPosition, const Point2I &newExtent); + virtual bool updateRects(); +}; + +//----------------------------------------------------------------------------- +// TypeColorI GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeColorI : public GuiInspectorTypeColor +{ + typedef GuiInspectorTypeColor Parent; + +protected: + + virtual const char* _getColorConversionFunction() const { return "ColorFloatToInt"; } + +public: + + GuiInspectorTypeColorI(); + + DECLARE_CONOBJECT(GuiInspectorTypeColorI); + + static void consoleInit(); + void setValue( StringTableEntry newValue ); +}; + +//----------------------------------------------------------------------------- +// TypeColorF GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeColorF : public GuiInspectorTypeColor +{ + typedef GuiInspectorTypeColor Parent; + +public: + + GuiInspectorTypeColorF(); + + DECLARE_CONOBJECT(GuiInspectorTypeColorF); + + static void consoleInit(); + void setValue( StringTableEntry newValue ); +}; + +/* NOTE: Evidently this isn't used anywhere (or implemented) so i commented it out +//------------------------------------------------------------------------------ +// TypeString GuiInspectorField class +//------------------------------------------------------------------------------ +class GuiInspectorTypeString : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeString); + static void consoleInit(); + + SimObjectPtr mBrowseButton; + + virtual GuiControl* constructEditControl(); + virtual bool resize(const Point2I &newPosition, const Point2I &newExtent); + virtual bool updateRects(); +}; +*/ + + +//------------------------------------------------------------------------------ +// TypeS32 GuiInspectorField class +//------------------------------------------------------------------------------ +class GuiInspectorTypeS32 : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeS32); + static void consoleInit(); + + virtual GuiControl* constructEditControl(); + virtual void setValue( StringTableEntry newValue ); +}; + + +//------------------------------------------------------------------------------ +// TypeBitMask32 GuiInspectorField class +//------------------------------------------------------------------------------ +class GuiInspectorTypeBitMask32Helper; +class GuiDynamicCtrlArrayControl; + +class GuiInspectorTypeBitMask32 : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +public: + + GuiInspectorTypeBitMask32(); + virtual ~GuiInspectorTypeBitMask32() {} + + DECLARE_CONOBJECT( GuiInspectorTypeBitMask32 ); + + // ConsoleObject + bool onAdd(); + static void consoleInit(); + + // GuiInspectorField + virtual void childResized( GuiControl *child ); + virtual bool resize( const Point2I &newPosition, const Point2I &newExtent ); + virtual bool updateRects(); + virtual void updateData(); + virtual StringTableEntry getValue(); + virtual void setValue( StringTableEntry value ); + virtual void setData( StringTableEntry data ); + +protected: + + GuiInspectorTypeBitMask32Helper *mHelper; + GuiRolloutCtrl *mRollout; + GuiDynamicCtrlArrayControl *mArrayCtrl; + Vector mChildren; +}; + +class GuiInspectorTypeBitMask32Helper : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +public: + + GuiInspectorTypeBitMask32Helper(); + + DECLARE_CONOBJECT( GuiInspectorTypeBitMask32Helper ); + + GuiBitmapButtonCtrl *mButton; + GuiRolloutCtrl *mParentRollout; + GuiInspectorTypeBitMask32 *mParentField; + RectI mButtonRect; + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + virtual bool resize( const Point2I &newPosition, const Point2I &newExtent ); + virtual bool updateRects(); + virtual void setValue( StringTableEntry value ); +}; + + +//----------------------------------------------------------------------------- +// TypeName GuiInspectorField Class +//----------------------------------------------------------------------------- +class GuiInspectorTypeName : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; +public: + DECLARE_CONOBJECT(GuiInspectorTypeName); + static void consoleInit(); + + virtual bool verifyData( StringTableEntry data ); +}; + +#endif // _GUI_INSPECTOR_TYPES_H_ \ No newline at end of file diff --git a/gui/editor/guiMenuBar.cpp b/gui/editor/guiMenuBar.cpp new file mode 100644 index 0000000..12733a7 --- /dev/null +++ b/gui/editor/guiMenuBar.cpp @@ -0,0 +1,1572 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/guiMenuBar.h" + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" +#include "gui/controls/guiTextListCtrl.h" +#include "sim/actionMap.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/primBuilder.h" + +// menu bar: +// basic idea - fixed height control bar at the top of a window, placed and sized in gui editor +// menu text for menus or menu items should not begin with a digit +// all menus can be removed via the clearMenus() console command +// each menu is added via the addMenu(menuText, menuId) console command +// each menu is added with a menu id +// menu items are added to menus via that addMenuItem(menu, menuItemText, menuItemId, accelerator, checkGroup) console command +// each menu item is added with a menu item id and an optional accelerator +// menu items are initially enabled, but can be disabled/re-enabled via the setMenuItemEnable(menu,menuItem,bool) +// menu text can be set via the setMenuText(menu, newMenuText) console method +// menu item text can be set via the setMenuItemText console method +// menu items can be removed via the removeMenuItem(menu, menuItem) console command +// menu items can be cleared via the clearMenuItems(menu) console command +// menus can be hidden or shown via the setMenuVisible(menu, bool) console command +// menu items can be hidden or shown via the setMenuItemVisible(menu, menuItem, bool) console command +// menu items can be check'd via the setMenuItemChecked(menu, menuItem, bool) console command +// if the bool is true, any other items in that menu item's check group become unchecked. +// +// menu items can have a bitmap set on them via the setMenuItemBitmap(menu, menuItem, bitmapIndex) +// passing -1 for the bitmap index will result in no bitmap being shown +// the index paramater is an index into the bitmap array of the associated profile +// this can be used, for example, to display a check next to a selected menu item +// bitmap indices are actually multiplied by 3 when indexing into the bitmap +// since bitmaps have normal, selected and disabled states. +// +// menus can be removed via the removeMenu console command +// specification arguments for menus and menu items can be either the id or the text of the menu or menu item +// adding the menu item "-" will add an un-selectable seperator to the menu +// callbacks: +// when a menu is clicked, before it is displayed, the menu calls its onMenuSelect(menuId, menuText) method - +// this allows the callback to enable/disable menu items, or add menu items in a context-sensitive way +// when a menu item is clicked, the menu removes itself from display, then calls onMenuItemSelect(menuId, menuText, menuItemId, menuItemText) + +// the initial implementation does not support: +// hierarchal menus +// keyboard accelerators on menu text (i.e. via alt-key combos) + +//------------------------------------------------------------------------------ + +IMPLEMENT_CONOBJECT(GuiMenuBar); + +//------------------------------------------------------------------------------ +// console methods +//------------------------------------------------------------------------------ + +ConsoleMethod(GuiMenuBar, clearMenus, void, 2, 2, "() - clears all the menus from the menu bar.") +{ + object->clearMenus(); +} + +ConsoleMethod(GuiMenuBar, setMenuMargins, void, 5, 5, "(S32 horizontalMargin, S32 verticalMargin, S32 bitmapToTextSpacing) - Sets the menu rendering margins: horizontal, vertical, bitmap spacing.") +{ + object->mHorizontalMargin = dAtoi(argv[2]); + object->mVerticalMargin = dAtoi(argv[3]); + object->mBitmapMargin = dAtoi(argv[4]); +} + +ConsoleMethod(GuiMenuBar, addMenu, void, 4, 4, "(string menuText, int menuId) - adds a new menu to the menu bar.") +{ + if(dIsdigit(argv[2][0])) + { + Con::errorf("Cannot add menu %s (id = %s). First character of a menu's text cannot be a digit.", argv[2], argv[3]); + return; + } + object->addMenu(argv[2], dAtoi(argv[3])); +} + +ConsoleMethod(GuiMenuBar, addMenuItem, void, 5, 7, "(string menu, string menuItemText, int menuItemId, string accelerator = NULL, int checkGroup = -1) - adds a menu item to the specified menu. The menu argument can be either the text of a menu or its id.") +{ + if(dIsdigit(argv[3][0])) + { + Con::errorf("Cannot add menu item %s (id = %s). First character of a menu item's text cannot be a digit.", argv[3], argv[4]); + return; + } + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for addMenuItem.", argv[2]); + return; + } + object->addMenuItem(menu, argv[3], dAtoi(argv[4]), argc == 5 ? "" : argv[5], argc < 7 ? -1 : dAtoi(argv[6])); +} + +ConsoleMethod(GuiMenuBar, setMenuItemEnable, void, 5, 5, "(string menu, string menuItem, bool enabled) - sets the menu item to enabled or disabled based on the enable parameter. The specified menu and menu item can either be text or ids.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemEnable.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = object->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for setMenuItemEnable.", argv[3]); + return; + } + menuItem->enabled = dAtob(argv[4]); +} + +ConsoleMethod(GuiMenuBar, setCheckmarkBitmapIndex, void, 3, 3, "(S32 bitmapindex) - sets the menu bitmap index for the check mark image.") +{ + object->mCheckmarkBitmapIndex = dAtoi(argv[2]); +} + +ConsoleMethod(GuiMenuBar, setMenuItemChecked, void, 5, 5, "(string menu, string menuItem, bool checked) - sets the menu item bitmap to a check mark, which by default is the first element in the bitmap array (although this may be changed with setCheckmarkBitmapIndex()). Any other menu items in the menu with the same check group become unchecked if they are checked.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemChecked.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = object->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for setMenuItemChecked.", argv[3]); + return; + } + bool checked = dAtob(argv[4]); + if(checked && menuItem->checkGroup != -1) + { + // first, uncheck everything in the group: + for(GuiMenuBar::MenuItem *itemWalk = menu->firstMenuItem; itemWalk; itemWalk = itemWalk->nextMenuItem) + if(itemWalk->checkGroup == menuItem->checkGroup && itemWalk->bitmapIndex == object->mCheckmarkBitmapIndex) + itemWalk->bitmapIndex = -1; + } + menuItem->bitmapIndex = checked ? object->mCheckmarkBitmapIndex : -1; +} + +ConsoleMethod(GuiMenuBar, setMenuText, void, 4, 4, "(string menu, string newMenuText) - sets the text of the specified menu to the new string.") +{ + if(dIsdigit(argv[3][0])) + { + Con::errorf("Cannot name menu %s to %s. First character of a menu's text cannot be a digit.", argv[2], argv[3]); + return; + } + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuText.", argv[2]); + return; + } + dFree(menu->text); + menu->text = dStrdup(argv[3]); + object->menuBarDirty = true; +} +ConsoleMethod(GuiMenuBar, setMenuBitmapIndex, void, 6, 6, "(string menu, S32 bitmapindex, bool bitmaponly, bool drawborder) - sets the bitmap index for the menu and toggles rendering only the bitmap.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuBitmapIndex.", argv[2]); + return; + } + + menu->bitmapIndex = dAtoi(argv[3]); + menu->drawBitmapOnly = dAtob(argv[4]); + menu->drawBorder = dAtob(argv[5]); + + object->menuBarDirty = true; +} + +ConsoleMethod(GuiMenuBar, setMenuVisible, void, 4, 4, "(string menu, bool visible) - sets the whether or not to display the specified menu.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuVisible.", argv[2]); + return; + } + menu->visible = dAtob(argv[3]); + object->menuBarDirty = true; + object->setUpdate(); +} + +ConsoleMethod(GuiMenuBar, setMenuItemText, void, 5, 5, "(string menu, string menuItem, string newMenuItemText) - sets the text of the specified menu item to the new string.") +{ + if(dIsdigit(argv[4][0])) + { + Con::errorf("Cannot name menu item %s to %s. First character of a menu item's text cannot be a digit.", argv[3], argv[4]); + return; + } + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemText.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = object->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for setMenuItemText.", argv[3]); + return; + } + dFree(menuItem->text); + menuItem->text = dStrdup(argv[4]); +} + +ConsoleMethod(GuiMenuBar, setMenuItemVisible, void, 5, 5, "(string menu, string menuItem, bool isVisible) - sets the specified menu item to be either visible or not.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemVisible.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = object->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for setMenuItemVisible.", argv[3]); + return; + } + menuItem->visible = dAtob(argv[4]); +} + +ConsoleMethod(GuiMenuBar, setMenuItemBitmap, void, 5, 5, "(string menu, string menuItem, int bitmapIndex) - sets the specified menu item bitmap index in the bitmap array. Setting the item's index to -1 will remove any bitmap.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemBitmap.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = object->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for setMenuItemBitmap.", argv[3]); + return; + } + menuItem->bitmapIndex = dAtoi(argv[4]); +} + +ConsoleMethod(GuiMenuBar, removeMenuItem, void, 4, 4, "(string menu, string menuItem) - removes the specified menu item from the menu.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for removeMenuItem.", argv[2]); + return; + } + GuiMenuBar::MenuItem *menuItem = object->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for removeMenuItem.", argv[3]); + return; + } + object->removeMenuItem(menu, menuItem); +} + +ConsoleMethod(GuiMenuBar, clearMenuItems, void, 3, 3, "(string menu) - removes all the menu items from the specified menu.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + //Con::errorf("Cannot find menu %s for clearMenuItems.", argv[2]); + return; + } + object->clearMenuItems(menu); +} + +ConsoleMethod(GuiMenuBar, removeMenu, void, 3, 3, "(string menu) - removes the specified menu from the menu bar.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + //Con::errorf("Cannot find menu %s for removeMenu.", argv[2]); + return; + } + object->clearMenuItems(menu); + object->menuBarDirty = true; +} + +//------------------------------------------------------------------------------ +// Submenu console methods +//------------------------------------------------------------------------------ + +ConsoleMethod(GuiMenuBar, setMenuItemSubmenuState, void, 5, 5, "(string menu, string menuItem, bool isSubmenu) - Sets the given menu item to be a submenu") +{ + + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setMenuItemSubmenuState.", argv[2]); + return; + } + + GuiMenuBar::MenuItem *menuitem = object->findMenuItem(menu, argv[3]); + if(!menuitem) + { + Con::errorf("Cannot find menuitem %s for setMenuItemSubmenuState.", argv[3]); + return; + } + + menuitem->isSubmenu = dAtob(argv[4]); +} + +ConsoleMethod(GuiMenuBar, addSubmenuItem, void, 6, 8, "(string menu, string menuItem, string submenuItemText, int submenuItemId, string accelerator = NULL, int checkGroup = -1) - adds a menu item to the specified menu. The menu argument can be either the text of a menu or its id.") +{ + if(dIsdigit(argv[4][0])) + { + Con::errorf("Cannot add submenu item %s (id = %s). First character of a menu item's text cannot be a digit.", argv[4], argv[5]); + return; + } + + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for addMenuItem.", argv[2]); + return; + } + + GuiMenuBar::MenuItem *menuitem = object->findMenuItem(menu, argv[3]); + if(!menuitem) + { + Con::errorf("Cannot find menuitem %s for addSubmenuItem.", argv[3]); + return; + } + + object->addSubmenuItem(menu, menuitem, argv[4], dAtoi(argv[5]), argc == 6 ? "" : argv[6], argc < 8 ? -1 : dAtoi(argv[7])); +} + +ConsoleMethod(GuiMenuBar, clearSubmenuItems, void, 4, 4, "(string menu, string menuItem) - removes all the menu items from the specified submenu.") +{ + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for clearSubmenuItems.", argv[2]); + return; + } + + GuiMenuBar::MenuItem *menuitem = object->findMenuItem(menu, argv[3]); + if(!menuitem) + { + Con::errorf("Cannot find menuitem %s for clearSubmenuItems.", argv[3]); + return; + } + + object->clearSubmenuItems(menuitem); +} + +ConsoleMethod(GuiMenuBar, setSubmenuItemChecked, void, 6, 6, "(string menu, string menuItem, string submenuItemText, bool checked) - sets the menu item bitmap to a check mark, which by default is the first element in the bitmap array (although this may be changed with setCheckmarkBitmapIndex()). Any other menu items in the menu with the same check group become unchecked if they are checked.") +{ + // Find the parent menu + GuiMenuBar::Menu *menu = object->findMenu(argv[2]); + if(!menu) + { + Con::errorf("Cannot find menu %s for setSubmenuItemChecked.", argv[2]); + return; + } + + // Find the parent menu item + GuiMenuBar::MenuItem *menuItem = object->findMenuItem(menu, argv[3]); + if(!menuItem) + { + Con::errorf("Cannot find menu item %s for setSubmenuItemChecked.", argv[3]); + return; + } + + // Find the submenu item + GuiMenuBar::MenuItem *submenuItem = object->findSubmenuItem(menu, argv[3], argv[4]); + if(!submenuItem) + { + Con::errorf("Cannot find submenu item %s for setSubmenuItemChecked.", argv[4]); + return; + } + + bool checked = dAtob(argv[5]); + if(checked && submenuItem->checkGroup != -1) + { + // first, uncheck everything in the group: + for(GuiMenuBar::MenuItem *itemWalk = menuItem->firstSubmenuItem; itemWalk; itemWalk = itemWalk->nextMenuItem) + if(itemWalk->checkGroup == submenuItem->checkGroup && itemWalk->bitmapIndex == object->mCheckmarkBitmapIndex) + itemWalk->bitmapIndex = -1; + } + submenuItem->bitmapIndex = checked ? object->mCheckmarkBitmapIndex : -1; +} + +//------------------------------------------------------------------------------ +// menu management methods +//------------------------------------------------------------------------------ + +void GuiMenuBar::addMenu(const char *menuText, U32 menuId) +{ + // allocate the menu + Menu *newMenu = new Menu; + newMenu->text = dStrdup(menuText); + newMenu->id = menuId; + newMenu->nextMenu = NULL; + newMenu->firstMenuItem = NULL; + newMenu->visible = true; + + // Menu bitmap variables + newMenu->bitmapIndex = -1; + newMenu->drawBitmapOnly = false; + newMenu->drawBorder = true; + + // add it to the menu list + menuBarDirty = true; + Menu **walk; + for(walk = &menuList; *walk; walk = &(*walk)->nextMenu) + ; + *walk = newMenu; +} + +GuiMenuBar::Menu *GuiMenuBar::findMenu(const char *menu) +{ + if(dIsdigit(menu[0])) + { + U32 id = dAtoi(menu); + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + if(id == walk->id) + return walk; + return NULL; + } + else + { + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + if(!dStricmp(menu, walk->text)) + return walk; + return NULL; + } +} + +GuiMenuBar::MenuItem *GuiMenuBar::findMenuItem(Menu *menu, const char *menuItem) +{ + if(dIsdigit(menuItem[0])) + { + U32 id = dAtoi(menuItem); + for(MenuItem *walk = menu->firstMenuItem; walk; walk = walk->nextMenuItem) + if(id == walk->id) + return walk; + return NULL; + } + else + { + for(MenuItem *walk = menu->firstMenuItem; walk; walk = walk->nextMenuItem) + if(!dStricmp(menuItem, walk->text)) + return walk; + return NULL; + } +} + +void GuiMenuBar::removeMenu(Menu *menu) +{ + menuBarDirty = true; + clearMenuItems(menu); + for(Menu **walk = &menuList; *walk; walk = &(*walk)->nextMenu) + { + if(*walk == menu) + { + *walk = menu->nextMenu; + break; + } + } + dFree(menu->text); + delete menu; +} + +void GuiMenuBar::removeMenuItem(Menu *menu, MenuItem *menuItem) +{ + for(MenuItem **walk = &menu->firstMenuItem; *walk; walk = &(*walk)->nextMenuItem) + { + if(*walk == menuItem) + { + *walk = menuItem->nextMenuItem; + break; + } + } + + // If this is a submenu, then be sure to clear the submenu's items + if(menuItem->isSubmenu) + { + clearSubmenuItems(menuItem); + } + + dFree(menuItem->text); + dFree(menuItem->accelerator); + delete menuItem; +} + +void GuiMenuBar::addMenuItem(Menu *menu, const char *text, U32 id, const char *accelerator, S32 checkGroup) +{ + // allocate the new menu item + MenuItem *newMenuItem = new MenuItem; + newMenuItem->text = dStrdup(text); + if(accelerator[0]) + newMenuItem->accelerator = dStrdup(accelerator); + else + newMenuItem->accelerator = NULL; + newMenuItem->id = id; + newMenuItem->checkGroup = checkGroup; + newMenuItem->nextMenuItem = NULL; + newMenuItem->acceleratorIndex = 0; + newMenuItem->enabled = text[0] != '-'; + newMenuItem->visible = true; + newMenuItem->bitmapIndex = -1; + + // Default to not having a submenu + newMenuItem->isSubmenu = false; + newMenuItem->firstSubmenuItem = NULL; + newMenuItem->submenuParentMenu = NULL; + + // link it into the menu's menu item list + MenuItem **walk = &menu->firstMenuItem; + while(*walk) + walk = &(*walk)->nextMenuItem; + *walk = newMenuItem; + +} + +void GuiMenuBar::clearMenuItems(Menu *menu) +{ + while(menu->firstMenuItem) + removeMenuItem(menu, menu->firstMenuItem); +} + +void GuiMenuBar::clearMenus() +{ + while(menuList) + removeMenu(menuList); +} + +//------------------------------------------------------------------------------ +// Submenu methods +//------------------------------------------------------------------------------ + +// This method will return the MenuItem class of of a submenu's menu item given +// its parent menu and parent menuitem. If the menuitem ID is used, then the submenu +// ID must also be used. +GuiMenuBar::MenuItem *GuiMenuBar::findSubmenuItem(Menu *menu, const char *menuItem, const char *submenuItem) +{ + if(dIsdigit(menuItem[0])) + { + // Search by ID + U32 id = dAtoi(menuItem); + for(MenuItem *walk = menu->firstMenuItem; walk; walk = walk->nextMenuItem) + if(id == walk->id) + { + if(walk->isSubmenu) + { + U32 subid = dAtoi(submenuItem); + for(MenuItem *subwalk = walk->firstSubmenuItem; subwalk; subwalk = subwalk->nextMenuItem) + { + if(subid == walk->id) + { + return subwalk; + } + } + } + return NULL; + } + return NULL; + } + else + { + // Search by name + for(MenuItem *walk = menu->firstMenuItem; walk; walk = walk->nextMenuItem) + if(!dStricmp(menuItem, walk->text)) + { + if(walk->isSubmenu) + { + for(MenuItem *subwalk = walk->firstSubmenuItem; subwalk; subwalk = subwalk->nextMenuItem) + { + if(!dStricmp(submenuItem, subwalk->text)) + return subwalk; + } + } + return NULL; + } + return NULL; + } +} + +// Add a menuitem to the given submenu +void GuiMenuBar::addSubmenuItem(Menu *menu, MenuItem *submenu, const char *text, U32 id, const char *accelerator, S32 checkGroup) +{ + // Check that the given menu item supports a submenu + if(submenu && !submenu->isSubmenu) + { + Con::errorf("GuiMenuBar::addSubmenuItem: Attempting to add menuitem '%s' to an invalid submenu",text); + return; + } + + // allocate the new menu item + MenuItem *newMenuItem = new MenuItem; + newMenuItem->text = dStrdup(text); + if(accelerator[0]) + newMenuItem->accelerator = dStrdup(accelerator); + else + newMenuItem->accelerator = NULL; + newMenuItem->id = id; + newMenuItem->checkGroup = checkGroup; + newMenuItem->nextMenuItem = NULL; + newMenuItem->acceleratorIndex = 0; + newMenuItem->enabled = text[0] != '-'; + newMenuItem->visible = true; + newMenuItem->bitmapIndex = -1; + + // Default to not having a submenu + newMenuItem->isSubmenu = false; + newMenuItem->firstSubmenuItem = NULL; + + // Point back to the submenu's menu + newMenuItem->submenuParentMenu = menu; + + // link it into the menu's menu item list + MenuItem **walk = &submenu->firstSubmenuItem; + while(*walk) + walk = &(*walk)->nextMenuItem; + *walk = newMenuItem; + +} + +// Remove a submenu item +void GuiMenuBar::removeSubmenuItem(MenuItem *menuItem, MenuItem *submenuItem) +{ + // Check that the given menu item supports a submenu + if(menuItem && !menuItem->isSubmenu) + { + Con::errorf("GuiMenuBar::removeSubmenuItem: Attempting to remove submenuitem '%s' from an invalid submenu",submenuItem->text); + return; + } + + for(MenuItem **subwalk = &menuItem->firstSubmenuItem; *subwalk; subwalk = &(*subwalk)->nextMenuItem) + { + if(*subwalk == submenuItem) + { + *subwalk = submenuItem->nextMenuItem; + break; + } + } + dFree(submenuItem->text); + dFree(submenuItem->accelerator); + delete submenuItem; +} + +// Clear all menuitems from a submenu +void GuiMenuBar::clearSubmenuItems(MenuItem *menuitem) +{ + // Check that the given menu item supports a submenu + if(menuitem && !menuitem->isSubmenu) + { + Con::errorf("GuiMenuBar::clearSubmenuItems: Attempting to clear an invalid submenu"); + return; + } + + while(menuitem->firstSubmenuItem) + removeSubmenuItem(menuitem, menuitem->firstSubmenuItem); +} + +//------------------------------------------------------------------------------ +// initialization, input and render methods +//------------------------------------------------------------------------------ + +GuiMenuBar::GuiMenuBar() +{ + menuList = NULL; + menuBarDirty = true; + mouseDownMenu = NULL; + mouseOverMenu = NULL; + mCurAcceleratorIndex = 0; + mBackground = NULL; + mPadding = 0; + + mCheckmarkBitmapIndex = 0; // Default to the first image in the bitmap array for the check mark + + mHorizontalMargin = 6; // Default number of pixels on the left and right side of a manu's text + mVerticalMargin = 1; // Default number of pixels on the top and bottom of a menu's text + mBitmapMargin = 2; // Default number of pixels between a menu's bitmap and text + + // Added: + mouseDownSubmenu = NULL; + mouseOverSubmenu = NULL; + mSubmenuBackground = NULL; + mSubmenuTextList = NULL; + mMouseOverCounter = 0; + mCountMouseOver = false; + mMouseHoverAmount = 30; + setProcessTicks(false); +} + +void GuiMenuBar::initPersistFields() +{ + addField("Padding", TypeS32, Offset( mPadding, GuiMenuBar ) ); + + Parent::initPersistFields(); +} + +bool GuiMenuBar::onWake() +{ + if(!Parent::onWake()) + return false; + mProfile->constructBitmapArray(); // if a bitmap was specified... + maxBitmapSize.set(0,0); + S32 numBitmaps = mProfile->mBitmapArrayRects.size(); + if(numBitmaps) + { + RectI *bitmapBounds = mProfile->mBitmapArrayRects.address(); + for(S32 i = 0; i < numBitmaps; i++) + { + if(bitmapBounds[i].extent.x > maxBitmapSize.x) + maxBitmapSize.x = bitmapBounds[i].extent.x; + if(bitmapBounds[i].extent.y > maxBitmapSize.y) + maxBitmapSize.y = bitmapBounds[i].extent.y; + } + } + return true; +} + +GuiMenuBar::Menu *GuiMenuBar::findHitMenu(Point2I mousePoint) +{ + Point2I pos = globalToLocalCoord(mousePoint); + + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + if(walk->visible && walk->bounds.pointInRect(pos)) + return walk; + return NULL; +} + +void GuiMenuBar::onPreRender() +{ + Parent::onPreRender(); + if(menuBarDirty) + { + menuBarDirty = false; + U32 curX = mPadding; + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + { + if(!walk->visible) + continue; + + // Bounds depends on if there is a bitmap to be drawn or not + if(walk->bitmapIndex == -1) + { + // Text only + walk->bounds.set(curX, 0, mProfile->mFont->getStrWidth(walk->text) + (mHorizontalMargin * 2), getHeight() - (mVerticalMargin * 2)); + + } else + { + // Will the bitmap and text be draw? + if(!walk->drawBitmapOnly) + { + // Draw the bitmap and the text + RectI *bitmapBounds = mProfile->mBitmapArrayRects.address(); + walk->bounds.set(curX, 0, bitmapBounds[walk->bitmapIndex].extent.x + mProfile->mFont->getStrWidth(walk->text) + (mHorizontalMargin * 2), getHeight() + (mVerticalMargin * 2)); + + } else + { + // Only the bitmap will be drawn + RectI *bitmapBounds = mProfile->mBitmapArrayRects.address(); + walk->bounds.set(curX, 0, bitmapBounds[walk->bitmapIndex].extent.x + mBitmapMargin + (mHorizontalMargin * 2), getHeight() + (mVerticalMargin * 2)); + } + } + + curX += walk->bounds.extent.x; + } + mouseOverMenu = NULL; + mouseDownMenu = NULL; + } +} + +void GuiMenuBar::checkMenuMouseMove(const GuiEvent &event) +{ + Menu *hit = findHitMenu(event.mousePoint); + if(hit && hit != mouseDownMenu) + { + // gotta close out the current menu... + mTextList->setSelectedCell(Point2I(-1, -1)); + closeMenu(); + mouseOverMenu = mouseDownMenu = hit; + setUpdate(); + onAction(); + } +} + +void GuiMenuBar::onMouseMove(const GuiEvent &event) +{ + Menu *hit = findHitMenu(event.mousePoint); + if(hit != mouseOverMenu) + { + // If we need to, reset the mouse over menu counter and indicate + // that we should track it. + if(hit) + mMouseOverCounter = 0; + if(!mCountMouseOver) + { + // We've never started the counter, so start it. + if(hit) + mCountMouseOver = true; + } + + mouseOverMenu = hit; + setUpdate(); + } +} + +void GuiMenuBar::onMouseLeave(const GuiEvent &event) +{ + if(mouseOverMenu) + setUpdate(); + mouseOverMenu = NULL; + + // As we've left the control, don't track how long the mouse has been + // within it. + if(mCountMouseOver && mMouseOverCounter >= mMouseHoverAmount) + { + Con::executef( this, "onMouseInMenu", "0"); // Last parameter indicates if we've entered or left the menu + } + mCountMouseOver = false; + mMouseOverCounter = 0; +} + +void GuiMenuBar::onMouseDragged(const GuiEvent &event) +{ + Menu *hit = findHitMenu(event.mousePoint); + + if(hit != mouseOverMenu) + { + // If we need to, reset the mouse over menu counter and indicate + // that we should track it. + if(hit) + mMouseOverCounter = 0; + if(!mCountMouseOver) + { + // We've never started the counter, so start it. + if(hit) + mCountMouseOver = true; + } + + mouseOverMenu = hit; + mouseDownMenu = hit; + setUpdate(); + onAction(); + } +} + +void GuiMenuBar::onMouseDown(const GuiEvent &event) +{ + mouseDownMenu = mouseOverMenu = findHitMenu(event.mousePoint); + setUpdate(); + onAction(); +} + +void GuiMenuBar::onMouseUp(const GuiEvent &event) +{ + mouseDownMenu = NULL; + setUpdate(); +} + +void GuiMenuBar::onRender(Point2I offset, const RectI &updateRect) +{ + + RectI ctrlRect(offset, getExtent()); + + //if opaque, fill the update rect with the fill color + if (mProfile->mOpaque) + GFX->getDrawUtil()->drawRectFill(RectI(offset, getExtent()), mProfile->mFillColor); + + //if there's a border, draw the border + if (mProfile->mBorder) + renderBorder(ctrlRect, mProfile); + + for(Menu *walk = menuList; walk; walk = walk->nextMenu) + { + if(!walk->visible) + continue; + ColorI fontColor = mProfile->mFontColor; + RectI bounds = walk->bounds; + bounds.point += offset; + + Point2I start; + + start.x = walk->bounds.point.x + mHorizontalMargin; + start.y = walk->bounds.point.y + ( walk->bounds.extent.y - mProfile->mFont->getHeight() ) / 2; + + // Draw the border + if(walk->drawBorder) + { + RectI highlightBounds = bounds; + highlightBounds.inset(1,1); + if(walk == mouseDownMenu) + renderFilledBorder(highlightBounds, mProfile->mBorderColorHL, mProfile->mFillColorHL ); + else if(walk == mouseOverMenu && mouseDownMenu == NULL) + renderFilledBorder(highlightBounds, mProfile->mBorderColor, mProfile->mFillColor ); + } + + // Do we draw a bitmap? + if(walk->bitmapIndex != -1) + { + S32 index = walk->bitmapIndex * 3; + if(walk == mouseDownMenu) + ++index; + else if(walk == mouseOverMenu && mouseDownMenu == NULL) + index += 2; + + RectI rect = mProfile->mBitmapArrayRects[index]; + + Point2I bitmapstart(start); + bitmapstart.y = walk->bounds.point.y + ( walk->bounds.extent.y - rect.extent.y ) / 2; + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR( mProfile->mTextureObject, offset + bitmapstart, rect); + + // Should we also draw the text? + if(!walk->drawBitmapOnly) + { + start.x += mBitmapMargin; + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + GFX->getDrawUtil()->drawText( mProfile->mFont, start + offset, walk->text, mProfile->mFontColors ); + } + } else + { + GFX->getDrawUtil()->setBitmapModulation( fontColor ); + GFX->getDrawUtil()->drawText( mProfile->mFont, start + offset, walk->text, mProfile->mFontColors ); + } + } + + renderChildControls( offset, updateRect ); +} + +void GuiMenuBar::buildAcceleratorMap() +{ + Parent::buildAcceleratorMap(); + // ok, accelerator map is cleared... + // add all our keys: + mCurAcceleratorIndex = 1; + + for(Menu *menu = menuList; menu; menu = menu->nextMenu) + { + for(MenuItem *item = menu->firstMenuItem; item; item = item->nextMenuItem) + { + if(!item->accelerator) + { + item->accelerator = 0; + continue; + } + EventDescriptor accelEvent; + ActionMap::createEventDescriptor(item->accelerator, &accelEvent); + + //now we have a modifier, and a key, add them to the canvas + GuiCanvas *root = getRoot(); + if (root) + root->addAcceleratorKey(this, mCurAcceleratorIndex, accelEvent.eventCode, accelEvent.flags); + item->acceleratorIndex = mCurAcceleratorIndex; + mCurAcceleratorIndex++; + } + } +} + +void GuiMenuBar::acceleratorKeyPress(U32 index) +{ + // loop through all the menus + // and find the item that corresponds to the accelerator index + for(Menu *menu = menuList; menu; menu = menu->nextMenu) + { + if(!menu->visible) + continue; + + for(MenuItem *item = menu->firstMenuItem; item; item = item->nextMenuItem) + { + if(item->acceleratorIndex == index) + { + // first, call the script callback for menu selection: + Con::executef( this, "onMenuSelect", Con::getIntArg(menu->id), + menu->text); + if(item->visible) + menuItemSelected(menu, item); + return; + } + } + } +} + +//------------------------------------------------------------------------------ +// Menu display class methods +//------------------------------------------------------------------------------ + +GuiMenuBackgroundCtrl::GuiMenuBackgroundCtrl(GuiMenuBar *ctrl, GuiMenuTextListCtrl *textList) +{ + mMenuBarCtrl = ctrl; + mTextList = textList; +} + +void GuiMenuBackgroundCtrl::onMouseDown(const GuiEvent &event) +{ + mTextList->setSelectedCell(Point2I(-1,-1)); + mMenuBarCtrl->closeMenu(); +} + +void GuiMenuBackgroundCtrl::onMouseMove(const GuiEvent &event) +{ + GuiCanvas *root = getRoot(); + GuiControl *ctrlHit = root->findHitControl(event.mousePoint, mLayer - 1); + if(ctrlHit == mMenuBarCtrl) // see if the current mouse over menu is right... + mMenuBarCtrl->checkMenuMouseMove(event); +} + +void GuiMenuBackgroundCtrl::onMouseDragged(const GuiEvent &event) +{ + GuiCanvas *root = getRoot(); + GuiControl *ctrlHit = root->findHitControl(event.mousePoint, mLayer - 1); + if(ctrlHit == mMenuBarCtrl) // see if the current mouse over menu is right... + mMenuBarCtrl->checkMenuMouseMove(event); +} + +GuiMenuTextListCtrl::GuiMenuTextListCtrl(GuiMenuBar *ctrl) +{ + mMenuBarCtrl = ctrl; + isSubMenu = false; // Added +} + +void GuiMenuTextListCtrl::onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver) +{ + if(dStrcmp(mList[cell.y].text + 3, "-\t")) // Was: dStrcmp(mList[cell.y].text + 2, "-\t")) but has been changed to take into account the submenu flag + Parent::onRenderCell(offset, cell, selected, mouseOver); + else + { + S32 yp = offset.y + mCellSize.y / 2; + GFX->getDrawUtil()->drawLine(offset.x, yp, offset.x + mCellSize.x, yp, ColorI(128,128,128)); + GFX->getDrawUtil()->drawLine(offset.x, yp+1, offset.x + mCellSize.x, yp+1, ColorI(255,255,255)); + } + // now see if there's a bitmap... + U8 idx = mList[cell.y].text[0]; + if(idx != 1) + { + // there's a bitmap... + U32 index = U32(idx - 2) * 3; + if(!mList[cell.y].active) + index += 2; + else if(selected || mouseOver) + index ++; + + RectI rect = mProfile->mBitmapArrayRects[index]; + Point2I off = mMenuBarCtrl->maxBitmapSize - rect.extent; + off /= 2; + + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapSR(mProfile->mTextureObject, offset + off, rect); + } + + // Check if this is a submenu + idx = mList[cell.y].text[1]; + if(idx != 1) + { + // This is a submenu, so draw an arrow + S32 left = offset.x + mCellSize.x - 12; + S32 right = left + 8; + S32 top = mCellSize.y / 2 + offset.y - 4; + S32 bottom = top + 8; + S32 middle = top + 4; + + PrimBuild::begin( GFXTriangleList, 3 ); + if( selected || mouseOver ) + PrimBuild::color( mProfile->mFontColorHL ); + else + PrimBuild::color( mProfile->mFontColor ); + + PrimBuild::vertex2i( left, top ); + PrimBuild::vertex2i( right, middle ); + PrimBuild::vertex2i( left, bottom ); + PrimBuild::end(); + } +} + +bool GuiMenuTextListCtrl::onKeyDown(const GuiEvent &event) +{ + //if the control is a dead end, don't process the input: + if ( !mVisible || !mActive || !mAwake ) + return false; + + //see if the key down is a or not + if ( event.modifier == 0 ) + { + if ( event.keyCode == KEY_RETURN ) + { + mMenuBarCtrl->closeMenu(); + return true; + } + else if ( event.keyCode == KEY_ESCAPE ) + { + mSelectedCell.set( -1, -1 ); + mMenuBarCtrl->closeMenu(); + return true; + } + } + + //otherwise, pass the event to it's parent + return Parent::onKeyDown(event); +} + +void GuiMenuTextListCtrl::onMouseDown(const GuiEvent &event) +{ + Parent::onMouseDown(event); + mMenuBarCtrl->closeMenu(); +} + +void GuiMenuTextListCtrl::onMouseUp(const GuiEvent &event) +{ + // ok, this is kind of strange... but! + // here's the deal: if we get a mouse up in this control + // it means the mouse was dragged from the initial menu mouse click + // so: activate the menu result as though this event were, + // instead, a mouse down. + + onMouseDown(event); +} + +void GuiMenuTextListCtrl::onCellHighlighted(Point2I cell) +{ + // If this text list control is part of a submenu, then don't worry about + // passing this along + if(!isSubMenu) + { + RectI globalbounds(getBounds()); + Point2I globalpoint = localToGlobalCoord(globalbounds.point); + globalbounds.point = globalpoint; + mMenuBarCtrl->highlightedMenuItem(cell.y, globalbounds, mCellSize); + } +} + +//------------------------------------------------------------------------------ +// Submenu display class methods +//------------------------------------------------------------------------------ + +GuiSubmenuBackgroundCtrl::GuiSubmenuBackgroundCtrl(GuiMenuBar *ctrl, GuiMenuTextListCtrl *textList) : GuiMenuBackgroundCtrl(ctrl, textList) +{ +} + +void GuiSubmenuBackgroundCtrl::onMouseDown(const GuiEvent &event) +{ + mTextList->setSelectedCell(Point2I(-1,-1)); + mMenuBarCtrl->closeMenu(); +} + +bool GuiSubmenuBackgroundCtrl::pointInControl(const Point2I& parentCoordPoint) +{ + S32 xt = parentCoordPoint.x - getLeft(); + S32 yt = parentCoordPoint.y - getTop(); + + if(findHitControl(Point2I(xt,yt)) == this) + return false; + else + return true; +// return xt >= 0 && yt >= 0 && xt < getWidth() && yt < getHeight(); +} + +//------------------------------------------------------------------------------ + +void GuiMenuBar::menuItemSelected(GuiMenuBar::Menu *menu, GuiMenuBar::MenuItem *item) +{ + if(item->enabled) + Con::executef( this, "onMenuItemSelect", Con::getIntArg(menu->id), + menu->text, Con::getIntArg(item->id), item->text); +} + +void GuiMenuBar::onSleep() +{ + if(mBackground) // a menu is up? + { + mTextList->setSelectedCell(Point2I(-1, -1)); + closeMenu(); + } + Parent::onSleep(); +} + +void GuiMenuBar::closeMenu() +{ + // First close any open submenu + closeSubmenu(); + + // Get the selection from the text list: + S32 selectionIndex = mTextList->getSelectedCell().y; + + // Pop the background: + if( getRoot() ) + getRoot()->popDialogControl(mBackground); + else + return; + + // Kill the popup: + mBackground->deleteObject(); + mBackground = NULL; + + // Now perform the popup action: + if ( selectionIndex != -1 ) + { + MenuItem *list = mouseDownMenu->firstMenuItem; + + while(selectionIndex && list) + { + list = list->nextMenuItem; + selectionIndex--; + } + if(list) + menuItemSelected(mouseDownMenu, list); + } + mouseDownMenu = NULL; +} + +// Called when a menu item is highlighted by the mouse +void GuiMenuBar::highlightedMenuItem(S32 selectionIndex, RectI bounds, Point2I cellSize) +{ + S32 selstore = selectionIndex; + + // Now perform the popup action: + if ( selectionIndex != -1 ) + { + MenuItem *list = mouseDownMenu->firstMenuItem; + + while(selectionIndex && list) + { + list = list->nextMenuItem; + selectionIndex--; + } + + if(list) + { + // If the highlighted item has changed... + if(mouseOverSubmenu != list) + { + closeSubmenu(); + mouseOverSubmenu = NULL; + + // Check if this is a submenu. If so, open the submenu. + if(list->isSubmenu) + { + // If there are submenu items, then open the submenu + if(list->firstSubmenuItem) + { + mouseOverSubmenu = list; + onSubmenuAction(selstore, bounds, cellSize); + } + } + } + } + } +} + +//------------------------------------------------------------------------------ +void GuiMenuBar::onAction() +{ + if(!mouseDownMenu) + return; + + // first, call the script callback for menu selection: + Con::executef( this, "onMenuSelect", Con::getIntArg(mouseDownMenu->id), + mouseDownMenu->text); + + MenuItem *visWalk = mouseDownMenu->firstMenuItem; + while(visWalk) + { + if(visWalk->visible) + break; + visWalk = visWalk->nextMenuItem; + } + if(!visWalk) + { + mouseDownMenu = NULL; + return; + } + + mTextList = new GuiMenuTextListCtrl(this); + mTextList->setControlProfile(mProfile); + + mBackground = new GuiMenuBackgroundCtrl(this, mTextList); + + GuiCanvas *root = getRoot(); + Point2I windowExt = root->getExtent(); + + mBackground->resize( Point2I(0,0), root->getExtent()); + S32 textWidth = 0, width = 0; + S32 acceleratorWidth = 0; + + GFont *font = mProfile->mFont; + + for(MenuItem *walk = mouseDownMenu->firstMenuItem; walk; walk = walk->nextMenuItem) + { + if(!walk->visible) + continue; + + S32 iTextWidth = font->getStrWidth(walk->text); + S32 iAcceleratorWidth = walk->accelerator ? font->getStrWidth(walk->accelerator) : 0; + + if(iTextWidth > textWidth) + textWidth = iTextWidth; + if(iAcceleratorWidth > acceleratorWidth) + acceleratorWidth = iAcceleratorWidth; + } + width = textWidth + acceleratorWidth + maxBitmapSize.x * 2 + 2 + 4; + + mTextList->setCellSize(Point2I(width, font->getHeight()+2)); + mTextList->clearColumnOffsets(); + mTextList->addColumnOffset(-1); // add an empty column in for the bitmap index. + mTextList->addColumnOffset(maxBitmapSize.x + 1); + mTextList->addColumnOffset(maxBitmapSize.x + 1 + textWidth + 4); + + U32 entryCount = 0; + + for(MenuItem *walk = mouseDownMenu->firstMenuItem; walk; walk = walk->nextMenuItem) + { + if(!walk->visible) + continue; + + char buf[512]; + + // If this menu item is a submenu, then set the isSubmenu to 2 to indicate + // an arrow should be drawn. Otherwise set the isSubmenu normally. + char isSubmenu = 1; + if(walk->isSubmenu) + isSubmenu = 2; + + char bitmapIndex = 1; + if(walk->bitmapIndex >= 0 && (walk->bitmapIndex * 3 <= mProfile->mBitmapArrayRects.size())) + bitmapIndex = walk->bitmapIndex + 2; + dSprintf(buf, sizeof(buf), "%c%c\t%s\t%s", bitmapIndex, isSubmenu, walk->text, walk->accelerator ? walk->accelerator : ""); + mTextList->addEntry(entryCount, buf); + + if(!walk->enabled) + mTextList->setEntryActive(entryCount, false); + + entryCount++; + } + Point2I menuPoint = localToGlobalCoord(mouseDownMenu->bounds.point); + menuPoint.y += mouseDownMenu->bounds.extent.y; // Used to have this at the end: + 2; + + GuiControl *ctrl = new GuiControl; + RectI ctrlBounds( menuPoint, mTextList->getExtent() + Point2I(6, 6)); + + ctrl->setControlProfile(mProfile); + mTextList->setPosition( mTextList->getPosition() + Point2I(3,3) ); + + // Make sure the menu doesn't go beyond the Canvas' bottom edge. + if((ctrlBounds.point.y + ctrlBounds.extent.y) > windowExt.y) + { + // Pop the menu above the menu bar + Point2I menuBar = localToGlobalCoord(mouseDownMenu->bounds.point); + ctrlBounds.point.y = menuBar.y - ctrl->getHeight(); + } + + ctrl->resize(ctrlBounds.point, ctrlBounds.extent); + //mTextList->setPosition(Point2I(3,3)); + + mTextList->registerObject(); + mBackground->registerObject(); + ctrl->registerObject(); + + mBackground->addObject( ctrl ); + ctrl->addObject( mTextList ); + + root->pushDialogControl(mBackground, mLayer + 1); + mTextList->setFirstResponder(); +} + +//------------------------------------------------------------------------------ +// Performs an action when a menu item that is a submenu is selected/highlighted +void GuiMenuBar::onSubmenuAction(S32 selectionIndex, RectI bounds, Point2I cellSize) +{ + if(!mouseOverSubmenu) + return; + + // first, call the script callback for menu selection: + Con::executef( this, "onSubmenuSelect", Con::getIntArg(mouseOverSubmenu->id), + mouseOverSubmenu->text); + + MenuItem *visWalk = mouseOverSubmenu->firstSubmenuItem; + while(visWalk) + { + if(visWalk->visible) + break; + visWalk = visWalk->nextMenuItem; + } + if(!visWalk) + { + mouseOverSubmenu = NULL; + return; + } + + mSubmenuTextList = new GuiMenuTextListCtrl(this); + mSubmenuTextList->setControlProfile(mProfile); + mSubmenuTextList->isSubMenu = true; // Indicate that this text list is part of a submenu + + mSubmenuBackground = new GuiSubmenuBackgroundCtrl(this, mSubmenuTextList); + + GuiCanvas *root = getRoot(); + Point2I windowExt = root->getExtent(); + + mSubmenuBackground->resize( Point2I(0,0), root->getExtent()); + S32 textWidth = 0, width = 0; + S32 acceleratorWidth = 0; + + GFont *font = mProfile->mFont; + + for(MenuItem *walk = mouseOverSubmenu->firstSubmenuItem; walk; walk = walk->nextMenuItem) + { + if(!walk->visible) + continue; + + S32 iTextWidth = font->getStrWidth(walk->text); + S32 iAcceleratorWidth = walk->accelerator ? font->getStrWidth(walk->accelerator) : 0; + + if(iTextWidth > textWidth) + textWidth = iTextWidth; + if(iAcceleratorWidth > acceleratorWidth) + acceleratorWidth = iAcceleratorWidth; + } + width = textWidth + acceleratorWidth + maxBitmapSize.x * 2 + 2 + 4; + + mSubmenuTextList->setCellSize(Point2I(width, font->getHeight()+3)); + mSubmenuTextList->clearColumnOffsets(); + mSubmenuTextList->addColumnOffset(-1); // add an empty column in for the bitmap index. + mSubmenuTextList->addColumnOffset(maxBitmapSize.x + 1); + mSubmenuTextList->addColumnOffset(maxBitmapSize.x + 1 + textWidth + 4); + + U32 entryCount = 0; + + for(MenuItem *walk = mouseOverSubmenu->firstSubmenuItem; walk; walk = walk->nextMenuItem) + { + if(!walk->visible) + continue; + + char buf[512]; + + // Can't have submenus within submenus. + char isSubmenu = 1; + + char bitmapIndex = 1; + if(walk->bitmapIndex >= 0 && (walk->bitmapIndex * 3 <= mProfile->mBitmapArrayRects.size())) + bitmapIndex = walk->bitmapIndex + 2; + dSprintf(buf, sizeof(buf), "%c%c\t%s\t%s", bitmapIndex, isSubmenu, walk->text, walk->accelerator ? walk->accelerator : ""); + mSubmenuTextList->addEntry(entryCount, buf); + + if(!walk->enabled) + mSubmenuTextList->setEntryActive(entryCount, false); + + entryCount++; + } + Point2I menuPoint = bounds.point; //localToGlobalCoord(bounds.point); + menuPoint.x += bounds.extent.x; + menuPoint.y += cellSize.y * selectionIndex - 6; + + GuiControl *ctrl = new GuiControl; + RectI ctrlBounds(menuPoint, mSubmenuTextList->getExtent() + Point2I(6, 6)); + ctrl->setControlProfile(mProfile); + mSubmenuTextList->setPosition( getPosition() + Point2I(3,3)); + + // Make sure the menu doesn't go beyond the Canvas' bottom edge. + if((ctrlBounds.point.y + ctrlBounds.extent.y ) > windowExt.y) + { + // Pop the menu above the menu bar + ctrlBounds.point.y -= mSubmenuTextList->getHeight() - cellSize.y - 6 - 3; + } + + // And the same for the right edge + if((ctrlBounds.point.x + ctrlBounds.extent.x) > windowExt.x) + { + // Pop the submenu to the left of the menu + ctrlBounds.point.x -= mSubmenuTextList->getWidth() + cellSize.x + 6; + } + ctrl->resize(ctrlBounds.point, ctrlBounds.extent); + + //mSubmenuTextList->setPosition(Point2I(3,3)); + + mSubmenuTextList->registerObject(); + mSubmenuBackground->registerObject(); + ctrl->registerObject(); + + mSubmenuBackground->addObject( ctrl ); + ctrl->addObject( mSubmenuTextList ); + + root->pushDialogControl(mSubmenuBackground, mLayer + 1); + mSubmenuTextList->setFirstResponder(); +} + +// Close down the submenu controls +void GuiMenuBar::closeSubmenu() +{ + if(!mSubmenuBackground || !mSubmenuTextList) + return; + + // Get the selection from the text list: + S32 selectionIndex = mSubmenuTextList->getSelectedCell().y; + + // Pop the background: + if( getRoot() ) + getRoot()->popDialogControl(mSubmenuBackground); + + // Kill the popup: + mSubmenuBackground->deleteObject(); + mSubmenuBackground = NULL; + mSubmenuTextList = NULL; + + // Now perform the popup action: + if ( selectionIndex != -1 ) + { + MenuItem *list = NULL; + if(mouseOverSubmenu) + { + list = mouseOverSubmenu->firstSubmenuItem; + + while(selectionIndex && list) + { + list = list->nextMenuItem; + selectionIndex--; + } + } + if(list) + menuItemSelected(list->submenuParentMenu, list); + } + mouseOverSubmenu = NULL; +} + +// Find if the mouse pointer is within a menu item +GuiMenuBar::MenuItem *GuiMenuBar::findHitMenuItem(Point2I mousePoint) +{ + Point2I pos = globalToLocalCoord(mousePoint); + +// for(Menu *walk = menuList; walk; walk = walk->nextMenu) +// if(walk->visible && walk->bounds.pointInRect(pos)) +// return walk; + return NULL; +} + +// Checks if the mouse has been moved to a new menu item +void GuiMenuBar::checkSubmenuMouseMove(const GuiEvent &event) +{ + MenuItem *hit = findHitMenuItem(event.mousePoint); + if(hit && hit != mouseOverSubmenu) + { + // gotta close out the current menu... + mSubmenuTextList->setSelectedCell(Point2I(-1, -1)); +// closeSubmenu(); + setUpdate(); + } +} + +// Process a tick +void GuiMenuBar::processTick() +{ + // If we are to track a tick, then do so. + if(mCountMouseOver) + { + // If we're at a particular number of ticks, notify the script function + if(mMouseOverCounter < mMouseHoverAmount) + { + ++mMouseOverCounter; + + } else if(mMouseOverCounter == mMouseHoverAmount) + { + ++mMouseOverCounter; + Con::executef( this, "onMouseInMenu", "1"); // Last parameter indicates if we've entered or left the menu + } + } +} diff --git a/gui/editor/guiMenuBar.h b/gui/editor/guiMenuBar.h new file mode 100644 index 0000000..3b030d4 --- /dev/null +++ b/gui/editor/guiMenuBar.h @@ -0,0 +1,201 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMENUBAR_H_ +#define _GUIMENUBAR_H_ + +#ifndef _GUITEXTLISTCTRL_H_ +#include "gui/controls/guiTextListCtrl.h" +#endif +#ifndef _GUITICKCTRL_H_ +#include "gui/shiny/guiTickCtrl.h" +#endif + +class GuiMenuBar; +class GuiMenuTextListCtrl; + +class GuiMenuBackgroundCtrl : public GuiControl +{ + typedef GuiControl Parent; + +protected: + GuiMenuBar *mMenuBarCtrl; + GuiMenuTextListCtrl *mTextList; +public: + GuiMenuBackgroundCtrl(GuiMenuBar *ctrl, GuiMenuTextListCtrl* textList); + void onMouseDown(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); +}; + +class GuiSubmenuBackgroundCtrl : public GuiMenuBackgroundCtrl +{ + typedef GuiMenuBackgroundCtrl Parent; + +public: + GuiSubmenuBackgroundCtrl(GuiMenuBar *ctrl, GuiMenuTextListCtrl* textList); + bool pointInControl(const Point2I & parentCoordPoint); + void onMouseDown(const GuiEvent &event); +}; + +//------------------------------------------------------------------------------ + +class GuiMenuTextListCtrl : public GuiTextListCtrl +{ + private: + typedef GuiTextListCtrl Parent; + + protected: + GuiMenuBar *mMenuBarCtrl; + + public: + bool isSubMenu; // Indicates that this text list is in a submenu + + GuiMenuTextListCtrl(); // for inheritance + GuiMenuTextListCtrl(GuiMenuBar *ctrl); + + // GuiControl overloads: + bool onKeyDown(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + void onRenderCell(Point2I offset, Point2I cell, bool selected, bool mouseOver); + + virtual void onCellHighlighted(Point2I cell); // Added +}; + +//------------------------------------------------------------------------------ + +class GuiMenuBar : public GuiTickCtrl // Was: GuiControl +{ + typedef GuiTickCtrl Parent; // Was: GuiControl Parent; +public: + + struct Menu; + + struct MenuItem // an individual item in a pull-down menu + { + char *text; // the text of the menu item + U32 id; // a script-assigned identifier + char *accelerator; // the keyboard accelerator shortcut for the menu item + U32 acceleratorIndex; // index of this accelerator + bool enabled; // true if the menu item is selectable + bool visible; // true if the menu item is visible + S32 bitmapIndex; // index of the bitmap in the bitmap array + S32 checkGroup; // the group index of the item visa vi check marks - + // only one item in the group can be checked. + MenuItem *nextMenuItem; // next menu item in the linked list + + bool isSubmenu; // This menu item has a submenu that will be displayed + MenuItem *firstSubmenuItem; // The first menu item in the submenu + + Menu* submenuParentMenu; // For a submenu, this is the parent menu + }; + + struct Menu + { + char *text; + U32 id; + RectI bounds; + bool visible; + + S32 bitmapIndex; // Index of the bitmap in the bitmap array (-1 = no bitmap) + bool drawBitmapOnly; // Draw only the bitmap and not the text + bool drawBorder; // Should a border be drawn around this menu (usually if we only have a bitmap, we don't want a border) + + Menu *nextMenu; + MenuItem *firstMenuItem; + }; + + GuiMenuBackgroundCtrl *mBackground; + GuiMenuTextListCtrl *mTextList; + + GuiSubmenuBackgroundCtrl *mSubmenuBackground; // Background for a submenu + GuiMenuTextListCtrl *mSubmenuTextList; // Text list for a submenu + + Menu *menuList; + Menu *mouseDownMenu; + Menu *mouseOverMenu; + + MenuItem* mouseDownSubmenu; // Stores the menu item that is a submenu that has been selected + MenuItem* mouseOverSubmenu; // Stores the menu item that is a submenu that has been highlighted + + bool menuBarDirty; + U32 mCurAcceleratorIndex; + Point2I maxBitmapSize; + + S32 mCheckmarkBitmapIndex; // Index in the bitmap array to use for the check mark image + + S32 mPadding; + S32 mHorizontalMargin; // Left and right margin around the text of each menu + S32 mVerticalMargin; // Top and bottom margin around the text of each menu + S32 mBitmapMargin; // Margin between a menu's bitmap and text + + // Used to keep track of the amount of ticks that the mouse is hovering + // over a menu. + S32 mMouseOverCounter; + bool mCountMouseOver; + S32 mMouseHoverAmount; + + GuiMenuBar(); + bool onWake(); + void onSleep(); + + // internal menu handling functions + // these are used by the script manipulation functions to add/remove/change menu items + + void addMenu(const char *menuText, U32 menuId); + Menu *findMenu(const char *menu); // takes either a menu text or a string id + MenuItem *findMenuItem(Menu *menu, const char *menuItem); // takes either a menu text or a string id + void removeMenu(Menu *menu); + void removeMenuItem(Menu *menu, MenuItem *menuItem); + void addMenuItem(Menu *menu, const char *text, U32 id, const char *accelerator, S32 checkGroup); + void clearMenuItems(Menu *menu); + void clearMenus(); + + // Methods to deal with submenus + MenuItem* findSubmenuItem(Menu *menu, const char *menuItem, const char *submenuItem); + void addSubmenuItem(Menu *menu, MenuItem *submenu, const char *text, U32 id, const char *accelerator, S32 checkGroup); + void removeSubmenuItem(MenuItem *menuItem, MenuItem *submenuItem); + void clearSubmenuItems(MenuItem *menuitem); + void onSubmenuAction(S32 selectionIndex, RectI bounds, Point2I cellSize); + void closeSubmenu(); + void checkSubmenuMouseMove(const GuiEvent &event); + MenuItem *findHitMenuItem(Point2I mousePoint); + + void highlightedMenuItem(S32 selectionIndex, RectI bounds, Point2I cellSize); // Called whenever a menu item is highlighted by the mouse + + // display/mouse functions + + Menu *findHitMenu(Point2I mousePoint); + + // Called when the GUI theme changes and a bitmap arrary may need updating + // void onThemeChange(); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + + void checkMenuMouseMove(const GuiEvent &event); + void onMouseMove(const GuiEvent &event); + void onMouseLeave(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); + void onMouseDragged(const GuiEvent &event); + void onMouseUp(const GuiEvent &event); + + void onAction(); + void closeMenu(); + void buildAcceleratorMap(); + void acceleratorKeyPress(U32 index); + + void menuItemSelected(Menu *menu, MenuItem *item); + + // Added to support 'ticks' + void processTick(); + + static void initPersistFields(); + + DECLARE_CONOBJECT(GuiMenuBar); +}; + +#endif diff --git a/gui/editor/guiParticleGraphCtrl.cc b/gui/editor/guiParticleGraphCtrl.cc new file mode 100644 index 0000000..e4cfc37 --- /dev/null +++ b/gui/editor/guiParticleGraphCtrl.cc @@ -0,0 +1,1429 @@ +//----------------------------------------------------------------------------- +// Torque 2D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gfxInit.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" + +#include "gui/editor/guiParticleGraphCtrl.h" + +IMPLEMENT_CONOBJECT(GuiParticleGraphCtrl); + +GuiParticleGraphCtrl::GuiParticleGraphCtrl() +{ + + for(int i = 0; i < MaxPlots; i++) + { + mPlots[i].mGraphColor = ColorF(1.0, 1.0, 1.0); + VECTOR_SET_ASSOCIATION(mPlots[i].mGraphData); + mPlots[i].mGraphMax.x = 1; + mPlots[i].mGraphMax.y = 50; + mPlots[i].mGraphMin.x = 0; + mPlots[i].mGraphMin.y = 0; + mPlots[i].mGraphType = Polyline; + mPlots[i].mGraphName = StringTable->insert(""); + mPlots[i].mHidden = false; + mPlots[i].mGraphScale = 0.05f; + } + + mPlots[0].mGraphColor = ColorF(1.0f, 0.2f, 0.2f); + mPlots[1].mGraphColor = ColorF(1.0f, 0.5f, 0.5f); + mPlots[2].mGraphColor = ColorF(0.0f, 1.0f, 0.0f); + mPlots[3].mGraphColor = ColorF(0.0f, 0.0f, 1.0f); + mPlots[4].mGraphColor = ColorF(0.0f, 1.0f, 1.0f); + mPlots[5].mGraphColor = ColorF(0.0f, 0.0f, 0.0f); + mPlots[6].mGraphColor = ColorF(0.5f, 0.5f, 0.5f); + mPlots[7].mGraphColor = ColorF(0.5f, 0.0f, 0.0f); + mPlots[8].mGraphColor = ColorF(0.0f, 0.5f, 0.0f); + mPlots[9].mGraphColor = ColorF(0.0f, 0.0f, 0.5f); + mPlots[10].mGraphColor = ColorF(0.0f, 0.5f, 0.5f); + mPlots[11].mGraphColor = ColorF(0.25f, 0.25f, 0.25f); + mPlots[12].mGraphColor = ColorF(0.5f, 0.5f, 0.5f); + mPlots[13].mGraphColor = ColorF(0.5f, 0.0f, 0.0f); + mPlots[14].mGraphColor = ColorF(0.0f, 0.5f, 0.0f); + mPlots[15].mGraphColor = ColorF(0.0f, 0.0f, 0.5f); + mPlots[16].mGraphColor = ColorF(0.0f, 0.5f, 0.5f); + mPlots[17].mGraphColor = ColorF(0.25f, 0.25f, 0.25f); + mPlots[18].mGraphColor = ColorF(1.0f, 0.2f, 0.2f); + mPlots[19].mGraphColor = ColorF(1.0f, 0.5f, 0.5f); + mPlots[20].mGraphColor = ColorF(0.0f, 1.0f, 0.0f); + mPlots[21].mGraphColor = ColorF(0.0f, 0.0f, 1.0f); + mPlots[22].mGraphColor = ColorF(0.0f, 1.0f, 1.0f); + mPlots[23].mGraphColor = ColorF(0.0f, 0.0f, 0.0f); + mPlots[24].mGraphColor = ColorF(0.5f, 0.5f, 0.5f); + mPlots[25].mGraphColor = ColorF(0.5f, 0.0f, 0.0f); + mPlots[26].mGraphColor = ColorF(0.0f, 0.5f, 0.0f); + mPlots[27].mGraphColor = ColorF(0.0f, 0.0f, 0.5f); + mPlots[28].mGraphColor = ColorF(1.0f, 0.0f, 0.0f); + mPlots[29].mGraphColor = ColorF(0.0f, 1.0f, 0.0f); + mPlots[30].mGraphColor = ColorF(0.0f, 0.0f, 1.0f); + mPlots[31].mGraphColor = ColorF(0.5f, 0.0f, 0.0f); + + mVertexClickSize = 6; + mSelectedPlot = 0; + mSelectedPoint = -1; + mOriginalSelectedPoint = -1; + mLastSelectedPoint = -1; + mAddedPointIndex = -1; + mAutoMax = false; + mAutoRemove = false; + mRenderAllPoints = false; + mRenderGraphTooltip = true; + mPointWasAdded = false; + mPointXMovementClamped = false; + mOutlineColor = ColorI(1, 1, 1); + mCursorPos = Point2I(0, 0); +} + +static EnumTable::Enums enumGraphTypes[] = { + { GuiParticleGraphCtrl::Bar, "bar" }, + { GuiParticleGraphCtrl::Filled, "filled" }, + { GuiParticleGraphCtrl::Point, "point" }, + { GuiParticleGraphCtrl::Polyline , "polyline" }, +}; + +static EnumTable gGraphTypeTable( 4, &enumGraphTypes[0] ); + +bool GuiParticleGraphCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + setActive(true); + return true; +} + +void GuiParticleGraphCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + // Fetch Draw Utility. + GFXDrawUtil* pDrawUtil = GFX->getDrawUtil(); + + if (mProfile->mBorder) + { + const RectI bounds = getBounds(); + RectI rect(offset.x, offset.y, bounds.extent.x, bounds.extent.y); + pDrawUtil->drawRect(rect, mProfile->mBorderColor); + } + + GuiControlProfile* profile = dynamic_cast(Sim::findObject("GuiDefaultProfile")); + Resource font = profile->mFont; + GFXVideoMode videoMode = GFXInit::getDesktopResolution(); + + ColorF color(1.0f, 1.0f, 1.0f, 0.5f); + pDrawUtil->drawRectFill(updateRect, color); + + for (int k = 0; k < MaxPlots; k++) + { + // Nothing to graph + if ((mPlots[k].mGraphData.size() == 0) || (mPlots[k].mHidden == true)) + continue; + + Point2F graphExtent = getGraphExtent(k); + + // Adjust scale to max value + 5% so we can see high value + F32 ScaleX = (F32(getExtent().x) / (graphExtent.x*(1.00 + (mPlots[k].mGraphScale)))); + F32 ScaleY = (F32(getExtent().y) / (graphExtent.y*(1.00 + (mPlots[k].mGraphScale)))); + + if((mPlots[k].mGraphType == Point) || (mPlots[k].mGraphType == Polyline)) + { + S32 posX; + S32 posY; + S32 lastPosX = 0; + S32 lastPosY = 0; + Point2F plotPoint; + + S32 size = 32; + + for (S32 sample = 0; sample < mPlots[k].mGraphData.size(); sample++) + { + S32 temp; + + temp = (S32)(((F32)getExtent().x / (F32)mPlots[k].mGraphData.size()) * (F32)sample); + + // calculate the point positions + plotPoint = getPlotPoint(k, sample); + + posX = (S32)((plotPoint.x - mPlots[k].mGraphMin.x) * (ScaleX /(1.00 + mPlots[k].mGraphScale))); + posY = (getExtent().y) - (S32)((plotPoint.y - mPlots[k].mGraphMin.y) * ScaleY); + + posX += getExtent().x * (mPlots[k].mGraphScale); + posY /= (1.00 + (mPlots[k].mGraphScale)); + + posX = localToGlobalCoord(Point2I(posX, posY)).x; + posY = localToGlobalCoord(Point2I(posX, posY)).y; + + // check if this isn't our first loop through, if it is we won't have starting points + if(sample > 0) + { + pDrawUtil->drawLine( lastPosX, lastPosY , posX, posY , mPlots[k].mGraphColor ); + } else + { + mPlots[k].mNutList.clear(); + } + + mPlots[k].mNutList.push_back( Point2F(posX, posY) ); + + // store the last positions to be the starting points drawn into a line next loop + lastPosX = posX; + lastPosY = posY; + + //Con::printf("red %f green %f blue %f", mPlots[k].mGraphColor.red, mPlots[k].mGraphColor.green, mPlots[k].mGraphColor.blue); + + + + if(mSelectedPoint != -1) + { + mLastSelectedPoint = mSelectedPoint; + } + + ColorI nutColor (mPlots[k].mGraphColor); + + if(k == mSelectedPlot && sample == mLastSelectedPoint) + { + // grab the colors for the nut + F32 red = mPlots[k].mGraphColor.red; + F32 green = mPlots[k].mGraphColor.green; + F32 blue = mPlots[k].mGraphColor.blue; + + // invert the colors for the nut since this is a selected nut + red = 1.0 - red; + green = 1.0 - green; + blue = 1.0 - blue; + // nut color + + nutColor = ColorI(ColorF(red, green, blue)); + } + + // draw the seleciton nut + drawNut( Point2I(posX, posY), 3, mOutlineColor, nutColor ); + + if((mLastSelectedPoint != -1) || (mRenderAllPoints == true)) + { + if((k == mSelectedPlot && sample == mLastSelectedPoint) || (mRenderAllPoints == true)) + { + char number[32]; + + Point2I comparePos = localToGlobalCoord(Point2I(getPosition().x, getPosition().y)); + + dSprintf(number, 32, "%4.3f %4.3f", plotPoint.x, plotPoint.y); + + S32 textWidth = (S32)font->getStrWidth((const UTF8*)number);; + textWidth /= 2; + + if((((S32)posX - (textWidth/2)) < comparePos.x) || (((S32)posX - textWidth) <= 0)) + { + posX += (textWidth/1.5); + } else if((posX + (textWidth * 1.8)) > (comparePos.x + getExtent().x) || ((posX + textWidth) >= videoMode.resolution.x)) + { + posX -= (textWidth * 1.5); + } + + if((((S32)posY) < comparePos.y) || (((S32)posY - textWidth) <= 0)) + { + posY += 40; + } + + pDrawUtil->setBitmapModulation( profile->mFontColor ); + pDrawUtil->drawText( font, Point2I(posX, posY + 5) - Point2I(size >> 1, size), number ); + pDrawUtil->clearBitmapModulation(); + } + } + } + } + } + + if(mRenderNextGraphTooltip == true && mRenderGraphTooltip == true) + { + char argBuffer[1][32]; + + dSprintf(argBuffer[0], 32, "%s", getGraphName(mTooltipSelectedPlot)); + + renderGraphTooltip(mCursorPos, argBuffer[0]); + } +} + +S32 GuiParticleGraphCtrl::addPlotPoint(S32 plotID, Point2F v, bool setAdded) +{ + S32 mPlotIndex = 0; + bool plotChanged = false; + bool plotAdded = false; + + if(mAutoMax == false) + { + if(v.x < mPlots[plotID].mGraphMin.x) + { + v.x = mPlots[plotID].mGraphMin.x; + } + + if(v.x > mPlots[plotID].mGraphMax.x) + { + v.x = mPlots[plotID].mGraphMax.x; + } + + if(v.y < mPlots[plotID].mGraphMin.y) + { + v.y = mPlots[plotID].mGraphMin.y; + } + + if(v.y > mPlots[plotID].mGraphMax.y) + { + v.y = mPlots[plotID].mGraphMax.y; + } + } + + for(S32 i = 0; i < mPlots[plotID].mGraphData.size(); i++) + { + if(mFabs(v.x - mPlots[plotID].mGraphData[i].x) < 0.001) + { + if(mAutoRemove == true) + { + changePlotPoint(plotID, i, v); + plotChanged = true; + mPlotIndex = i; + } else + { + mPlotIndex = -1; + } + + plotAdded = true; + + break; + } else if(v.x < mPlots[plotID].mGraphData[i].x) + { + insertPlotPoint(plotID, i, v); + plotAdded = true; + mPlotIndex = i; + break; + } + } + + if(plotAdded == false) + { + mPlots[plotID].mGraphData.push_back( v ); + mPlotIndex = mPlots[plotID].mGraphData.size() - 1; + } + + if(mAutoMax == true) + { + // Keep record of maximum data value for scaling purposes. + if(v.y > mPlots[plotID].mGraphMax.y) + mPlots[plotID].mGraphMax.y = v.y; + + if(v.x > mPlots[plotID].mGraphMax.x) + mPlots[plotID].mGraphMax.x = v.x; + } + + if(plotChanged == true) + { + mPointWasAdded = false; + } else if(mPlotIndex != -1 && setAdded) + { + mPointWasAdded = true; + mAddedPoint = v; + mAddedPointIndex = mPlotIndex; + } + + return mPlotIndex; +} + +void GuiParticleGraphCtrl::insertPlotPoint(S32 plotID, S32 i, Point2F v) +{ + //AssertFatal(plotID > -1 && plotID < MaxPlots, "Invalid plot specified!"); + + // Add the data and trim the vector... + mPlots[plotID].mGraphData.insert( i ); + mPlots[plotID].mGraphData[ i ] = v; + + if(mAutoMax == true) + { + // Keep record of maximum data value for scaling purposes. + if(v.y > mPlots[plotID].mGraphMax.y) + mPlots[plotID].mGraphMax.y = v.y; + + if(v.x > mPlots[plotID].mGraphMax.x) + mPlots[plotID].mGraphMax.x = v.x; + } + + // Argument Buffer. + char argBuffer[2][32]; + + dSprintf(argBuffer[0], 32, "%d", plotID); + dSprintf(argBuffer[1], 32, "%f %f", v.x, v.y); + + // Call Scripts. + Con::executef(this, "onPlotPointInserted", argBuffer[0], argBuffer[1]); +} + +S32 GuiParticleGraphCtrl::changePlotPoint(S32 plotID, S32 i, Point2F v) +{ + //AssertFatal(plotID > -1 && plotID < MaxPlots, "Invalid plot specified!"); + + // Add the data and trim the vector... + S32 point = removePlotPoint(plotID, i); + + // Argument Buffer. + char argBuffer[3][32]; + + dSprintf(argBuffer[0], 32, "%d", mSelectedPlot); + dSprintf(argBuffer[1], 32, "%d", point); + dSprintf(argBuffer[2], 32, "%f %f", v.x, v.y); + + // Call Scripts. + Con::executef(this, "onPlotPointRemoved", argBuffer[0], argBuffer[1], argBuffer[2]); + + // call the insert function + S32 index = addPlotPoint(plotID, v); + + return index; +} + +S32 GuiParticleGraphCtrl::removePlotPoint(S32 plotID, S32 i) +{ + if((plotID < MaxPlots && plotID >= 0) && (i < mPlots[plotID].mGraphData.size())) + { + Point2F v = mPlots[plotID].mGraphData[i]; + + mPlots[plotID].mGraphData.erase(i); + + if(i == mPlots[plotID].mGraphData.size() && mSelectedPlot == plotID && mLastSelectedPoint == i) + { + setSelectedPoint(i-1); + } else if(i < mLastSelectedPoint) + { + setSelectedPoint(mLastSelectedPoint-1); + } + } + + return i; +} + +S32 GuiParticleGraphCtrl::getSelectedPlot() +{ + return mSelectedPlot; +} + +S32 GuiParticleGraphCtrl::getSelectedPoint() +{ + return mLastSelectedPoint; +} + +bool GuiParticleGraphCtrl::isExistingPoint(S32 plotID, S32 sample) +{ + if (((plotID < 0) || (plotID > MaxPlots)) || ((sample < 0) || (sample > MaxDataPoints)) || (sample >= mPlots[plotID].mGraphData.size())) + { + return false; + } else + { + return true; + } +} + +Point2F GuiParticleGraphCtrl::getPlotPoint(S32 plotID, S32 sample) +{ + Point2F val; + val.x = -1; + val.y = -1; + + if (((plotID < 0) || (plotID > MaxPlots)) || ((sample < 0) || (sample > MaxDataPoints))) + { + return val; + } + + return mPlots[plotID].mGraphData[sample]; +} + +S32 GuiParticleGraphCtrl::getPlotIndex(S32 plotID, F32 x, F32 y) +{ + if ((plotID < 0) || (plotID > MaxPlots)) + { + return -1; + } + + F32 compareX = 0.0; + F32 compareY = 0.0; + + for(S32 i = 0; i < mPlots[plotID].mGraphData.size(); i++) + { + compareX = mPlots[plotID].mGraphData[i].x; + compareY = mPlots[plotID].mGraphData[i].y; + + // + //if((x == compareX) && (y == compareY)) + if((mFabs(x - compareX) < 0.001) && (mFabs(y - compareY) < 0.001)) + return i; + } + + return -1; +} + +void GuiParticleGraphCtrl::setGraphType(S32 plotID, const char *graphType) +{ + AssertFatal(plotID > -1 && plotID < MaxPlots, "Invalid plot specified!"); + if(!dStricmp(graphType,"Bar")) + mPlots[plotID].mGraphType = Bar; + else if(!dStricmp(graphType,"Filled")) + mPlots[plotID].mGraphType = Filled; + else if(!dStricmp(graphType,"Point")) + mPlots[plotID].mGraphType = Point; + else if(!dStricmp(graphType,"Polyline")) + mPlots[plotID].mGraphType = Polyline; + else AssertWarn(true, "Invalid graph type!"); +} + +void GuiParticleGraphCtrl::setSelectedPlot(S32 plotID) +{ + mSelectedPlot = plotID; + + // Argument Buffer. + char argBuffer[32]; + + dSprintf(argBuffer, 32, "%d", plotID); + + // Call Scripts. + Con::executef(this, "onSetSelected", argBuffer); +} + +void GuiParticleGraphCtrl::setSelectedPoint(S32 point) +{ + if(point != mSelectedPoint && point < mPlots[mSelectedPlot].mGraphData.size() && point >= 0) + { + mSelectedPoint = point; + mLastSelectedPoint = point; + char argBuffer[32]; + + dSprintf(argBuffer, 32, "%d", point); + + // Call Scripts. + Con::executef(this, "onPlotPointSelected", argBuffer); + } +} + +void GuiParticleGraphCtrl::resetSelectedPoint() +{ + mSelectedPoint = -1; +} + +void GuiParticleGraphCtrl::setAutoGraphMax(bool autoMax) +{ + mAutoMax = autoMax; +} + +void GuiParticleGraphCtrl::setAutoRemove(bool autoRemove) +{ + mAutoRemove = autoRemove; +} + +void GuiParticleGraphCtrl::setGraphHidden(S32 plotID, bool isHidden) +{ + mPlots[plotID].mHidden = isHidden; +} + +void GuiParticleGraphCtrl::setRenderAll(bool renderAll) +{ + mRenderAllPoints = renderAll; +} + +void GuiParticleGraphCtrl::setPointXMovementClamped(bool clamped) +{ + mPointXMovementClamped = clamped; +} + +void GuiParticleGraphCtrl::setRenderGraphTooltip(bool renderGraphTooltip) +{ + mRenderGraphTooltip = renderGraphTooltip; +} + + +void GuiParticleGraphCtrl::drawNut(const Point2I &nut, S32 size, ColorI &outlineColor, ColorI &nutColor) +{ + // Fetch Draw Utility. + GFXDrawUtil* pDrawUtil = GFX->getDrawUtil(); + + //Con::printf("r %d g %d b %d", nutColor.red, nutColor.green, nutColor.blue); + S32 NUT_SIZE = size; + RectI r(nut.x - NUT_SIZE, nut.y - NUT_SIZE, 2 * NUT_SIZE + 1, 2 * NUT_SIZE + 1); + r.inset(1, 1); + pDrawUtil->drawRectFill(r, nutColor); + r.inset(-1, -1); + pDrawUtil->drawRect(r, outlineColor); +} + +bool GuiParticleGraphCtrl::inNut(const Point2I &pt, S32 x, S32 y) +{ + S32 dx = pt.x - x; + S32 dy = pt.y - y; + return dx <= mVertexClickSize && dx >= -mVertexClickSize && dy <= mVertexClickSize && dy >= -mVertexClickSize; +} + +Point2I GuiParticleGraphCtrl::findHitNut( Point2I hitPoint ) +{ + for(S32 i = 0; i < MaxPlots; i++) + { + if ( (mPlots[i].mGraphData.size() == 0) || (mPlots[i].mHidden == true)) + continue; + + for (S32 j = 0 ; j < mPlots[i].mNutList.size(); j++ ) + { + if( inNut (Point2I( mPlots[i].mNutList[j].x, mPlots[i].mNutList[j].y), hitPoint.x, hitPoint.y) ) + { + mTooltipSelectedPlot = i; + return Point2I(i,j); + } + } + } + + return Point2I(-1,-1); +} + +Point2F GuiParticleGraphCtrl::convertToGraphCoord(S32 plotID, Point2I v) +{ + Point2F resultV; + + v = globalToLocalCoord( v ); + v.y = getExtent().y - v.y; + resultV = Point2F(v.x, v.y); + resultV.x /= getExtent().x; + resultV.y /= getExtent().y; + resultV.x *= getGraphExtent(plotID).x; + resultV.y *= getGraphExtent(plotID).y; + resultV.x *= 1.00 + (mPlots[plotID].mGraphScale*2); + resultV.y *= 1.00 + (mPlots[plotID].mGraphScale*2); + resultV.x -= getGraphExtent(plotID).x * ((mPlots[plotID].mGraphScale)); + resultV.y -= getGraphExtent(plotID).y * ((mPlots[plotID].mGraphScale)); + resultV.x += mPlots[plotID].mGraphMin.x; + resultV.y += mPlots[plotID].mGraphMin.y; + + if(mAutoMax == false) + { + if(resultV.x < mPlots[plotID].mGraphMin.x) + { + resultV.x = mPlots[plotID].mGraphMin.x; + } + + if(resultV.x > mPlots[plotID].mGraphMax.x) + { + resultV.x = mPlots[plotID].mGraphMax.x; + } + + if(resultV.y < mPlots[plotID].mGraphMin.y) + { + resultV.y = mPlots[plotID].mGraphMin.y; + } + + if(resultV.y > mPlots[plotID].mGraphMax.y) + { + resultV.y = mPlots[plotID].mGraphMax.y; + } + } + + + + return resultV; +} + +Point2F GuiParticleGraphCtrl::getGraphExtent(S32 plotID) +{ + Point2F resultV; + + resultV.x = mPlots[plotID].mGraphMax.x - mPlots[plotID].mGraphMin.x; + resultV.y = mPlots[plotID].mGraphMax.y - mPlots[plotID].mGraphMin.y; + + return resultV; +} + +ColorF GuiParticleGraphCtrl::getGraphColor(S32 plotID) +{ + return mPlots[plotID].mGraphColor; +} + +Point2F GuiParticleGraphCtrl::getGraphMax(S32 plotID) +{ + return mPlots[plotID].mGraphMax; +} + +Point2F GuiParticleGraphCtrl::getGraphMin(S32 plotID) +{ + return mPlots[plotID].mGraphMin; +} + +void GuiParticleGraphCtrl::setGraphMin(S32 plotID, Point2F graphMin) +{ + mPlots[plotID].mGraphMin = graphMin; +} + +void GuiParticleGraphCtrl::setGraphMax(S32 plotID, Point2F graphMax) +{ + mPlots[plotID].mGraphMax = graphMax; +} + +void GuiParticleGraphCtrl::setGraphMinX(S32 plotID, F32 graphMinX) +{ + mPlots[plotID].mGraphMin.x = graphMinX; +} + +void GuiParticleGraphCtrl::setGraphMinY(S32 plotID, F32 graphMinY) +{ + mPlots[plotID].mGraphMin.y = graphMinY; +} + +void GuiParticleGraphCtrl::setGraphMaxX(S32 plotID, F32 graphMaxX) +{ + mPlots[plotID].mGraphMax.x = graphMaxX; +} + +void GuiParticleGraphCtrl::setGraphMaxY(S32 plotID, F32 graphMaxY) +{ + mPlots[plotID].mGraphMax.y = graphMaxY; +} + +void GuiParticleGraphCtrl::setGraphName(S32 plotID, StringTableEntry graphName) +{ + mPlots[plotID].mGraphName = StringTable->insert(graphName); +} + +StringTableEntry GuiParticleGraphCtrl::getGraphName(S32 plotID) +{ + return mPlots[plotID].mGraphName; +} + +void GuiParticleGraphCtrl::onMouseMove(const GuiEvent &event) +{ + mCursorPos = event.mousePoint; + + Point2I hitNut = findHitNut(event.mousePoint); + + if( hitNut != Point2I(-1,-1) ) + { + mRenderNextGraphTooltip = true; + } else + { + mRenderNextGraphTooltip = false; + } + + // Argument Buffer. + char argBuffer[32]; + + dSprintf(argBuffer, 32, "%f %f", convertToGraphCoord(mSelectedPlot, event.mousePoint).x, convertToGraphCoord(mSelectedPlot, event.mousePoint).y); + + + // Call Scripts. + Con::executef(this, "onMouseMove", argBuffer); +} + +void GuiParticleGraphCtrl::onMouseDown(const GuiEvent &event) +{ + Point2I hitNut = findHitNut(event.mousePoint); + + if( hitNut != Point2I(-1,-1) ) + { + if(event.mouseClickCount == 2) + { + Point2F plotPoint = getPlotPoint(hitNut.x, hitNut.y); + Point2F mousePos = convertToGraphCoord(mSelectedPlot, event.mousePoint); + S32 point = removePlotPoint(hitNut.x, hitNut.y); + + // Argument Buffer. + char argBuffer[3][32]; + + dSprintf(argBuffer[0], 32, "%d", mSelectedPlot); + dSprintf(argBuffer[1], 32, "%d", point); + dSprintf(argBuffer[2], 32, "%f %f", plotPoint.x, plotPoint.y); + + + + // Call Scripts. + Con::executef(this, "onPlotPointRemoved", argBuffer[0], argBuffer[1], argBuffer[2]); + } else + { + setSelectedPlot(hitNut.x); + setSelectedPoint(hitNut.y); + mOriginalSelectedPoint = hitNut.y; + + char argBuffer[32]; + + dSprintf(argBuffer, 32, "%d", hitNut.y); + + // Call Scripts. + Con::executef(this, "onPlotPointSelectedMouseDown", argBuffer); + } + } else if( mSelectedPlot != -1 ) + { + Point2F mousePos = convertToGraphCoord(mSelectedPlot, event.mousePoint); + mLastSelectedPoint = addPlotPoint(mSelectedPlot, mousePos); + + // Argument Buffer. + char argBuffer[32]; + + dSprintf(argBuffer, 32, "%f %f", convertToGraphCoord(mSelectedPlot, event.mousePoint).x, convertToGraphCoord(mSelectedPlot, event.mousePoint).y); + + // Call Scripts. + Con::executef(this, "onMouseDragged", argBuffer); + + return; + } +} + +void GuiParticleGraphCtrl::onMouseUp(const GuiEvent &event) +{ + if(mSelectedPoint != -1) + mLastSelectedPoint = mSelectedPoint; + + if(mPointWasAdded == true) + { + if(mSelectedPoint == -1) + { + // Argument Buffer. + char argBuffer[3][32]; + + dSprintf(argBuffer[0], 32, "%d", mSelectedPlot); + dSprintf(argBuffer[1], 32, "%f %f", mAddedPoint.x, mAddedPoint.y); + dSprintf(argBuffer[2], 32, "%d", mAddedPointIndex); + + // Call Scripts. + Con::executef(this, "onPlotPointAdded", argBuffer[0], argBuffer[1], argBuffer[2]); + } else + { + // Argument Buffer. + char argBuffer[4][32]; + + dSprintf(argBuffer[0], 32, "%d", mSelectedPlot); + dSprintf(argBuffer[1], 32, "%f %f", mAddedPoint.x, mAddedPoint.y); + dSprintf(argBuffer[2], 32, "%d", mOriginalSelectedPoint); + dSprintf(argBuffer[3], 32, "%d", mAddedPointIndex); + + // Call Scripts. + Con::executef(this, "onPlotPointChangedUp", argBuffer[0], argBuffer[1], argBuffer[2], argBuffer[3]); + } + } + + mPointWasAdded = false; + + mSelectedPoint = -1; +} + +void GuiParticleGraphCtrl::onMouseDragged(const GuiEvent &event) +{ + mRenderNextGraphTooltip = false; + + if(mSelectedPoint != -1) + { + Point2F mousePos = convertToGraphCoord(mSelectedPlot, event.mousePoint); + + if(mPointXMovementClamped == true) + { + F32 prevXPos = getPlotPoint(mSelectedPlot, mSelectedPoint).x; + if(mousePos.x != prevXPos) + { + mousePos.x = prevXPos; + } + } + + removePlotPoint(mSelectedPlot, mSelectedPoint); + S32 point = addPlotPoint(mSelectedPlot, mousePos); + if(point != -1) + { + setSelectedPoint(point); + mLastMousePos = mousePos; + + // Argument Buffer. + char argBuffer[3][32]; + + dSprintf(argBuffer[0], 32, "%d", mSelectedPlot); + dSprintf(argBuffer[1], 32, "%f %f", mAddedPoint.x, mAddedPoint.y); + dSprintf(argBuffer[2], 32, "%d", point); + + // Call Scripts. + Con::executef(this, "onPlotPointChangedMove", argBuffer[0], argBuffer[1], argBuffer[2]); + } else + { + point = addPlotPoint(mSelectedPlot, mLastMousePos); + } + } + + // Argument Buffer. + char argBuffer[32]; + + dSprintf(argBuffer, 32, "%f %f", convertToGraphCoord(mSelectedPlot, event.mousePoint).x, convertToGraphCoord(mSelectedPlot, event.mousePoint).y); + + // Call Scripts. + Con::executef(this, "onMouseDragged", argBuffer); +} + +void GuiParticleGraphCtrl::onRightMouseDown(const GuiEvent &event) +{ + Point2I hitNut = findHitNut(event.mousePoint); + + if( hitNut != Point2I(-1,-1) ) + { + Point2F plotPoint = getPlotPoint(hitNut.x, hitNut.y); + + Point2F mousePos = convertToGraphCoord(mSelectedPlot, event.mousePoint); + S32 point = removePlotPoint(hitNut.x, hitNut.y); + + // Argument Buffer. + char argBuffer[3][32]; + + dSprintf(argBuffer[0], 32, "%d", mSelectedPlot); + dSprintf(argBuffer[1], 32, "%d", point); + dSprintf(argBuffer[2], 32, "%f %f", plotPoint.x, plotPoint.y); + + // Call Scripts. + Con::executef(this, "onPlotPointRemoved", argBuffer[0], argBuffer[1], argBuffer[2]); + } +} + +void GuiParticleGraphCtrl::onRightMouseUp(const GuiEvent &event) +{ + +} + +void GuiParticleGraphCtrl::onRightMouseDragged(const GuiEvent &event) +{ + Point2I hitNut = findHitNut(event.mousePoint); + + if( hitNut != Point2I(-1,-1) ) + { + Point2F plotPoint = getPlotPoint(hitNut.x, hitNut.y); + + Point2F mousePos = convertToGraphCoord(mSelectedPlot, event.mousePoint); + S32 point = removePlotPoint(hitNut.x, hitNut.y); + + // Argument Buffer. + char argBuffer[3][32]; + + dSprintf(argBuffer[0], 32, "%d", mSelectedPlot); + dSprintf(argBuffer[1], 32, "%d", point); + dSprintf(argBuffer[2], 32, "%f %f", plotPoint.x, plotPoint.y); + + // Call Scripts. + Con::executef(this, "onPlotPointRemoved", argBuffer[0], argBuffer[1], argBuffer[2]); + } +} + +void GuiParticleGraphCtrl::clearAllGraphs() +{ + for(S32 i = 0;i < MaxPlots;i++) + { + clearGraph(i); + } +} + +void GuiParticleGraphCtrl::clearGraph(S32 plotID) +{ + mPlots[plotID].mGraphData.clear(); + mPlots[plotID].mNutList.clear(); +} + +bool GuiParticleGraphCtrl::renderGraphTooltip(Point2I cursorPos, StringTableEntry tooltip) +{ + // Short Circuit. + if (!mAwake) + return false; + if ( dStrlen( tooltip ) == 0 ) + return false; + + // Need to have root. + GuiCanvas *root = getRoot(); + if ( !root ) + return false; + + if (!mTooltipProfile) + mTooltipProfile = mProfile; + + GFont *font = mTooltipProfile->mFont; + + // Fetch Canvas. + GuiCanvas *pCanvas = getRoot(); + + //Vars used: + //Screensize (for position check) + //Offset to get position of cursor + //textBounds for text extent. + Point2I screensize = pCanvas->getWindowSize(); + Point2I offset = cursorPos; + Point2I textBounds; + S32 textWidth = font->getStrWidth(tooltip); + + //Offset below cursor image + offset.y -= root->getCursorExtent().y; + //Create text bounds. + textBounds.x = textWidth+8; + textBounds.y = font->getHeight() + 4; + + // Check position/width to make sure all of the tooltip will be rendered + // 5 is given as a buffer against the edge + if (screensize.x < offset.x + textBounds.x + 5) + offset.x = screensize.x - textBounds.x - 5; + + // And ditto for the height + if(screensize.y < offset.y + textBounds.y + 5) + offset.y = cursorPos.y - textBounds.y - 5; + + // Set rectangle for the box, and set the clip rectangle. + RectI rect(offset, textBounds); + GFX->setClipRect(rect); + + // Fetch Draw Utility. + GFXDrawUtil* pDrawUtil = GFX->getDrawUtil(); + + // Draw Filler bit, then border on top of that + pDrawUtil->drawRectFill(rect, ColorI(mTooltipProfile->mFillColor.red, mTooltipProfile->mFillColor.green, mTooltipProfile->mFillColor.blue, 200) ); + pDrawUtil->drawRect( rect, mTooltipProfile->mBorderColor ); + + // Draw the text centered in the tool tip box + pDrawUtil->setBitmapModulation( mTooltipProfile->mFontColor ); + Point2I start; + start.set( ( textBounds.x - textWidth) / 2, ( textBounds.y - font->getHeight() ) / 2 ); + pDrawUtil->drawText( font, start + offset, tooltip, mProfile->mFontColors ); + pDrawUtil->clearBitmapModulation(); + + return true; +} + +ConsoleMethod(GuiParticleGraphCtrl, setSelectedPoint, void, 3, 3, "(int point)" + "Set the selected point on the graph.\n" + "@return No return value") +{ + S32 point = dAtoi(argv[2]); + if(point >= object->mPlots[object->mSelectedPlot].mGraphData.size() || point < 0) + { + Con::errorf("Invalid point to select."); + return; + } + object->setSelectedPoint( point ); +} + +ConsoleMethod(GuiParticleGraphCtrl, setSelectedPlot, void, 3, 3, "(int plotID)" + "Set the selected plot (a.k.a. graph)." + "@return No return value" ) +{ + S32 plotID = dAtoi(argv[2]); + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + object->setSelectedPlot( plotID ); +} + +ConsoleMethod(GuiParticleGraphCtrl, clearGraph, void, 3, 3, "(int plotID)" + "Clear the graph of the given plot." + "@return No return value") +{ + S32 plotID = dAtoi(argv[2]); + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + object->clearGraph( plotID ); +} + +ConsoleMethod(GuiParticleGraphCtrl, clearAllGraphs, void, 2, 2, "()" + "Clear all of the graphs." + "@return No return value") +{ + object->clearAllGraphs(); +} + +ConsoleMethod(GuiParticleGraphCtrl, addPlotPoint, const char*, 5, 6, "(int plotID, float x, float y, bool setAdded = true;)" + "Add a data point to the given plot." + "@return") +{ + S32 plotID = dAtoi(argv[2]); + S32 pointAdded = 0; + char *retBuffer = Con::getReturnBuffer(32); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + dSprintf(retBuffer, 32, "%d", -2); + return retBuffer; + } + + if(argc == 5) + { + pointAdded = object->addPlotPoint( plotID, Point2F(dAtof(argv[3]), dAtof(argv[4]))); + } else if(argc == 6) + { + pointAdded = object->addPlotPoint( plotID, Point2F(dAtof(argv[3]), dAtof(argv[4])), dAtob(argv[5])); + } + + + dSprintf(retBuffer, 32, "%d", pointAdded); + + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, insertPlotPoint, void, 6, 6, "(int plotID, int i, float x, float y)\n" + "Insert a data point to the given plot and plot position.\n" + "@param plotID The plot you want to access\n" + "@param i The data point.\n" + "@param x,y The plot position.\n" + "@return No return value.") +{ + S32 plotID = dAtoi(argv[2]); + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + object->insertPlotPoint( plotID, dAtoi(argv[3]), Point2F(dAtof(argv[4]), dAtof(argv[5]))); +} + +ConsoleMethod(GuiParticleGraphCtrl, changePlotPoint, const char*, 6, 6, "(int plotID, int i, float x, float y)" + "Change a data point to the given plot and plot position.\n" + "@param plotID The plot you want to access\n" + "@param i The data point.\n" + "@param x,y The plot position.\n" + "@return No return value.") +{ + S32 plotID = dAtoi(argv[2]); + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + + char *retBuffer = Con::getReturnBuffer(64); + const S32 index = -1; + dSprintf(retBuffer, 64, "%d", index); + return retBuffer; + } + + char *retBuffer = Con::getReturnBuffer(64); + const S32 index = object->changePlotPoint( plotID, dAtoi(argv[3]), Point2F(dAtof(argv[4]), dAtof(argv[5]))); + dSprintf(retBuffer, 64, "%d", index); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getSelectedPlot, const char*, 2, 2, "() " + "Gets the selected Plot (a.k.a. graph).\n" + "@return The plot's ID.") +{ + char *retBuffer = Con::getReturnBuffer(32); + const S32 plot = object->getSelectedPlot(); + dSprintf(retBuffer, 32, "%d", plot); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getSelectedPoint, const char*, 2, 2, "()" + "Gets the selected Point on the Plot (a.k.a. graph)." + "@return The last selected point ID") +{ + char *retBuffer = Con::getReturnBuffer(32); + const S32 point = object->getSelectedPoint(); + dSprintf(retBuffer, 32, "%d", point); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, isExistingPoint, const char*, 4, 4, "(int plotID, int samples)" + "@return Returns true or false whether or not the point in the plot passed is an existing point.") +{ + S32 plotID = dAtoi(argv[2]); + S32 samples = dAtoi(argv[3]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + } + if(samples > object->MaxDataPoints) + { + Con::errorf("Invalid sample."); + } + + char *retBuffer = Con::getReturnBuffer(32); + const bool isPoint = object->isExistingPoint(plotID, samples); + dSprintf(retBuffer, 32, "%d", isPoint); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getPlotPoint, const char*, 4, 4, "(int plotID, int samples)" + "Get a data point from the plot specified, samples from the start of the graph." + "@return The data point ID") +{ + S32 plotID = dAtoi(argv[2]); + S32 samples = dAtoi(argv[3]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + } + if(samples > object->MaxDataPoints) + { + Con::errorf("Invalid sample."); + } + + char *retBuffer = Con::getReturnBuffer(64); + const Point2F &pos = object->getPlotPoint(plotID, samples); + dSprintf(retBuffer, 64, "%f %f", pos.x, pos.y); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getPlotIndex, const char*, 5, 5, "(int plotID, float x, float y)\n" + "Gets the index of the point passed on the plotID passed (graph ID).\n" + "@param plotID The plot you wish to check.\n" + "@param x,y The coordinates of the point to get.\n" + "@return Returns the index of the point.\n") +{ + S32 plotID = dAtoi(argv[2]); + F32 x = dAtof(argv[3]); + F32 y = dAtof(argv[4]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + } + + char *retBuffer = Con::getReturnBuffer(32); + const S32 &index = object->getPlotIndex(plotID, x, y); + dSprintf(retBuffer, 32, "%d", index); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getGraphColor, const char*, 3, 3, "(int plotID)" + "Get the color of the graph passed." + "@return Returns the color of the graph as a string of RGB values formatted as \"R G B\"") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + } + + char *retBuffer = Con::getReturnBuffer(64); + const ColorF &color = object->getGraphColor(plotID); + dSprintf(retBuffer, 64, "%f %f %f", color.red, color.green, color.blue); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getGraphMin, const char*, 3, 3, "(int plotID) " + "Get the minimum values of the graph ranges.\n" + "@return Returns the minimum of the range formatted as \"x-min y-min\"") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + } + + char *retBuffer = Con::getReturnBuffer(64); + const Point2F graphMin = object->getGraphMin(plotID); + dSprintf(retBuffer, 64, "%f %f", graphMin.x, graphMin.y); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getGraphMax, const char*, 3, 3, "(int plotID) " + "Get the maximum values of the graph ranges.\n" + "@return Returns the maximum of the range formatted as \"x-max y-max\"") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + } + + char *retBuffer = Con::getReturnBuffer(64); + const Point2F graphMax = object->getGraphMax(plotID); + dSprintf(retBuffer, 64, "%f %f", graphMax.x, graphMax.y); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, getGraphName, const char*, 3, 3, "(int plotID) " + "Get the name of the graph passed.\n" + "@return Returns the name of the plot") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + } + + char *retBuffer = Con::getReturnBuffer(64); + const StringTableEntry graphName = object->getGraphName(plotID); + dSprintf(retBuffer, 64, "%s", graphName); + return retBuffer; +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphMin, void, 5, 5, "(int plotID, float minX, float minY) " + "Set the min values of the graph of plotID.\n" + "@param plotID The plot to modify\n" + "@param minX,minY The minimum bound of the value range.\n" + "@return No return value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphMin(dAtoi(argv[2]), Point2F(dAtof(argv[3]), dAtof(argv[4]))); +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphMinX, void, 4, 4, "(int plotID, float minX) " + "Set the min X value of the graph of plotID.\n" + "@param plotID The plot to modify.\n" + "@param minX The minimum x value.\n" + "@return No return Value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphMinX(dAtoi(argv[2]), dAtof(argv[3])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphMinY, void, 4, 4, "(int plotID, float minY) " + "Set the min Y value of the graph of plotID." + "@param plotID The plot to modify.\n" + "@param minY The minimum y value.\n" + "@return No return Value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphMinY(dAtoi(argv[2]), dAtof(argv[3])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphMax, void, 5, 5, "(int plotID, float maxX, float maxY) " + "Set the max values of the graph of plotID." + "@param plotID The plot to modify\n" + "@param maxX,maxY The maximum bound of the value range.\n" + "@return No return value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphMax(dAtoi(argv[2]), Point2F(dAtof(argv[3]), dAtof(argv[4]))); +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphMaxX, void, 4, 4, "(int plotID, float maxX)" + "Set the max X value of the graph of plotID." + "@param plotID The plot to modify.\n" + "@param maxX The maximum x value.\n" + "@return No return Value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphMaxX(dAtoi(argv[2]), dAtof(argv[3])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphMaxY, void, 4, 4, "(int plotID, float maxY)" + "Set the max Y value of the graph of plotID." + "@param plotID The plot to modify.\n" + "@param maxY The maximum y value.\n" + "@return No return Value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphMaxY(dAtoi(argv[2]), dAtof(argv[3])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphHidden, void, 4, 4, "(int plotID, bool isHidden)" + "Set whether the graph number passed is hidden or not." + "@return No return value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphHidden(dAtoi(argv[2]), dAtob(argv[3])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setAutoGraphMax, void, 3, 3, "(bool autoMax) " + "Set whether the max will automatically be set when adding points " + "(ie if you add a value over the current max, the max is increased to that value).\n" + "@return No return value.") +{ + object->setAutoGraphMax(dAtob(argv[2])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setAutoRemove, void, 3, 3, "(bool autoRemove) " + "Set whether or not a point should be deleted when you drag another one over it." + "@return No return value.") +{ + object->setAutoRemove(dAtob(argv[2])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setRenderAll, void, 3, 3, "(bool renderAll)" + "Set whether or not a position should be rendered on every point or just the last selected." + "@return No return value.") +{ + object->setRenderAll(dAtob(argv[2])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setPointXMovementClamped, void, 3, 3, "(bool clamped)" + "Set whether the x position of the selected graph point should be clamped" + "@return No return value.") +{ + object->setPointXMovementClamped(dAtob(argv[2])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setRenderGraphTooltip, void, 3, 3, "(bool renderGraphTooltip)" + "Set whether or not to render the graph tooltip." + "@return No return value.") +{ + object->setRenderGraphTooltip(dAtob(argv[2])); +} + +ConsoleMethod(GuiParticleGraphCtrl, setGraphName, void, 4, 4, "(int plotID, string graphName) " + "Set the name of the given plot.\n" + "@param plotID The plot to modify.\n" + "@param graphName The name to set on the plot.\n" + "@return No return value.") +{ + S32 plotID = dAtoi(argv[2]); + + if(plotID > object->MaxPlots) + { + Con::errorf("Invalid plotID."); + return; + } + + object->setGraphName(dAtoi(argv[2]), argv[3]); +} + +ConsoleMethod(GuiParticleGraphCtrl, resetSelectedPoint, void, 2, 2, "()" + "This will reset the currently selected point to nothing." + "@return No return value.") +{ + object->resetSelectedPoint(); +} diff --git a/gui/editor/guiParticleGraphCtrl.h b/gui/editor/guiParticleGraphCtrl.h new file mode 100644 index 0000000..26817ac --- /dev/null +++ b/gui/editor/guiParticleGraphCtrl.h @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// Torque 2D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPARTICLEGRAPHCTRL_H_ +#define _GUIPARTICLEGRAPHCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _STRINGTABLE_H_ +#include "core/stringTable.h" +#endif + + +class GuiParticleGraphCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +protected: + /// The color of the nuts. + ColorI mNutColor; + /// The outline color of the nuts. + ColorI mOutlineColor; + /// The rectangle size to check for clicks on nuts (default 8) which is RectI( pointClick - 4, pointClick - 4, 8, 8 ) + S32 mVertexClickSize; + + Point2I findHitNut( Point2I hitPoint ); + +public: + enum Constants { + MaxPlots = 32, + MaxDataPoints = 200 + }; + + enum GraphType { + Point, + Polyline, + Filled, + Bar + }; + + struct PlotInfo + { + ColorF mGraphColor; + Vector mGraphData; + Vector mNutList; + Point2F mGraphMax; + Point2F mGraphMin; + GraphType mGraphType; + StringTableEntry mGraphName; + bool mHidden; + F32 mGraphScale; + }; + + S32 mSelectedPlot; + S32 mSelectedPoint; + S32 mOriginalSelectedPoint; + S32 mLastSelectedPoint; + S32 mTooltipSelectedPlot; + S32 mAddedPointIndex; + bool mAutoMax; + bool mAutoRemove; + bool mRenderAllPoints; + bool mPointWasAdded; + bool mRenderGraphTooltip; + bool mRenderNextGraphTooltip; + bool mPointXMovementClamped; + Point2F mAddedPoint; + Point2F mLastMousePos; + + Point2I mCursorPos; + + PlotInfo mPlots[MaxPlots]; + + //creation methods + DECLARE_CONOBJECT(GuiParticleGraphCtrl); + DECLARE_CATEGORY( "Gui Editor" ); + + GuiParticleGraphCtrl(); + virtual ~GuiParticleGraphCtrl() { }; + + void onMouseMove(const GuiEvent &event); + void onMouseDown( const GuiEvent &event ); + void onMouseUp( const GuiEvent &event ); + void onMouseDragged( const GuiEvent &event ); + void onRightMouseDown( const GuiEvent &event ); + void onRightMouseUp( const GuiEvent &event ); + void onRightMouseDragged( const GuiEvent &event ); + + //Parental methods + bool onWake(); + + void onRender(Point2I offset, const RectI &updateRect); + bool renderGraphTooltip(Point2I cursorPos, StringTableEntry tooltip); + + // Graph interface + S32 addPlotPoint(S32 plotID, Point2F v, bool setAdded = true); + void insertPlotPoint(S32 plotID, S32 i, Point2F v); + S32 changePlotPoint(S32 plotID, S32 i, Point2F v); + S32 removePlotPoint(S32 plotID, S32 i); + Point2F convertToGraphCoord(S32 plotID, Point2I v); + + // Set Graph specific functions + void setGraphType(S32 plotID, const char *graphType); + void setSelectedPlot(S32 plotID); + void setSelectedPoint(S32 point); + void resetSelectedPoint(); + void setGraphHidden(S32 plotID, bool isHidden); + + // Set Global Options + void setAutoGraphMax(bool autoMax); + void setAutoRemove(bool autoRemove); + void setRenderAll(bool renderAll); + void setRenderGraphTooltip(bool renderGraphTooltip); + void setPointXMovementClamped(bool clamped); + + // Get Functions + Point2F getGraphExtent(S32 plotID); + ColorF getGraphColor(S32 plotID); + S32 getSelectedPlot(); + S32 getSelectedPoint(); + Point2F getPlotPoint(S32 plotID, S32 samples); + S32 getPlotIndex(S32 plotID, F32 x, F32 y); + Point2F getGraphMax(S32 plotID); + Point2F getGraphMin(S32 plotID); + + bool isExistingPoint(S32 plotID, S32 point); + + StringTableEntry getGraphName(S32 plotID); + + // Set Graph Min and MaxFunctions + void setGraphMin(S32 plotID, Point2F graphMin); + void setGraphMinX(S32 plotID, F32 graphMinX); + void setGraphMinY(S32 plotID, F32 graphMinY); + void setGraphMax(S32 plotID, Point2F graphMax); + void setGraphMaxX(S32 plotID, F32 graphMaxX); + void setGraphMaxY(S32 plotID, F32 graphMaxY); + + // Other set functions for graphs + void setGraphName(S32 plotID, StringTableEntry graphName); + + + // Clear Functions + void clearGraph(S32 plotID); + void clearAllGraphs(); + + // Selection nuts. + bool inNut( const Point2I &pt, S32 x, S32 y ); + void drawNut( const Point2I &nut, S32 size, ColorI &outlineColor, ColorI &nutColor ); +}; + +#endif diff --git a/gui/editor/guiSeparatorCtrl.cpp b/gui/editor/guiSeparatorCtrl.cpp new file mode 100644 index 0000000..278a5be --- /dev/null +++ b/gui/editor/guiSeparatorCtrl.cpp @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/guiSeparatorCtrl.h" + +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/core/guiDefaultControlRender.h" + +IMPLEMENT_CONOBJECT(GuiSeparatorCtrl); + +static const EnumTable::Enums separatorTypeEnum[] = +{ + { GuiSeparatorCtrl::separatorTypeVertical, "Vertical" }, + { GuiSeparatorCtrl::separatorTypeHorizontal,"Horizontal" } +}; +static const EnumTable gSeparatorTypeTable(2, &separatorTypeEnum[0]); + + +//-------------------------------------------------------------------------- +GuiSeparatorCtrl::GuiSeparatorCtrl() : GuiControl() +{ + mInvisible = false; + mText = StringTable->insert(""); + mTextLeftMargin = 0; + mMargin = 2; + setExtent( 12, 35 ); + mSeparatorType = GuiSeparatorCtrl::separatorTypeVertical; +} + +//-------------------------------------------------------------------------- +void GuiSeparatorCtrl::initPersistFields() +{ + addField("Caption", TypeString, Offset(mText, GuiSeparatorCtrl)); + addField("Type", TypeEnum, Offset(mSeparatorType, GuiSeparatorCtrl), 1, &gSeparatorTypeTable); + addField("BorderMargin", TypeS32, Offset(mMargin, GuiSeparatorCtrl)); + addField("Invisible", TypeBool, Offset(mInvisible, GuiSeparatorCtrl)); + addField("LeftMargin", TypeS32, Offset(mTextLeftMargin, GuiSeparatorCtrl)); + + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +void GuiSeparatorCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + Parent::onRender( offset, updateRect ); + + if( mInvisible ) + return; + + if(mText != StringTable->lookup("") && !( mSeparatorType == separatorTypeVertical ) ) + { + // If text is present and we have a left margin, then draw some separator, then the + // text, and then the rest of the separator + S32 posx = offset.x + mMargin; + S32 fontheight = mProfile->mFont->getHeight(); + S32 seppos = (fontheight - 2) / 2 + offset.y; + if(mTextLeftMargin > 0) + { + RectI rect(Point2I(posx,seppos),Point2I(mTextLeftMargin,2)); + renderSlightlyLoweredBox(rect, mProfile); + posx += mTextLeftMargin; + } + + GFX->getDrawUtil()->setBitmapModulation( mProfile->mFontColor ); + posx += GFX->getDrawUtil()->drawText(mProfile->mFont, Point2I(posx,offset.y), mText, mProfile->mFontColors); + //posx += mProfile->mFont->getStrWidth(mText); + + RectI rect(Point2I(posx,seppos),Point2I(getWidth() - posx + offset.x,2)); + renderSlightlyLoweredBox(rect, mProfile); + + } else + { + if( mSeparatorType == separatorTypeHorizontal ) + { + S32 seppos = getHeight() / 2 + offset.y; + RectI rect(Point2I(offset.x + mMargin ,seppos),Point2I(getWidth() - (mMargin * 2),2)); + renderSlightlyLoweredBox(rect, mProfile); + } + else + { + S32 seppos = getWidth() / 2 + offset.x; + RectI rect(Point2I(seppos, offset.y + mMargin),Point2I(2, getHeight() - (mMargin * 2))); + renderSlightlyLoweredBox(rect, mProfile); + } + } + + renderChildControls(offset, updateRect); +} + + diff --git a/gui/editor/guiSeparatorCtrl.h b/gui/editor/guiSeparatorCtrl.h new file mode 100644 index 0000000..1c8f50a --- /dev/null +++ b/gui/editor/guiSeparatorCtrl.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _GUISEPARATORCTRL_H_ +#define _GUISEPARATORCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +/// Renders a separator line with optional text. +class GuiSeparatorCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + +public: + bool mInvisible; + StringTableEntry mText; + S32 mTextLeftMargin; + S32 mMargin; + S32 mSeparatorType; + + enum separatorTypeOptions + { + separatorTypeVertical = 0, ///< Draw Vertical Separator + separatorTypeHorizontal ///< Horizontal Separator + }; + + //creation methods + DECLARE_CONOBJECT(GuiSeparatorCtrl); + DECLARE_CATEGORY( "Gui Other" ); + GuiSeparatorCtrl(); + + static void initPersistFields(); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif diff --git a/gui/editor/guiShapeEdPreview.cpp b/gui/editor/guiShapeEdPreview.cpp new file mode 100644 index 0000000..d0a973c --- /dev/null +++ b/gui/editor/guiShapeEdPreview.cpp @@ -0,0 +1,793 @@ +//------------------------------------------------------------------------------ +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//------------------------------------------------------------------------------ + +#include "console/consoleTypes.h" +#include "console/console.h" +#include "gui/core/guiCanvas.h" +#include "gui/editor/guiShapeEdPreview.h" +#include "renderInstance/renderPassManager.h" +#include "lighting/lightManager.h" +#include "lighting/lightInfo.h" +#include "core/resourceManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" + +static const F32 sMoveScaler = 50.0f; +static const F32 sZoomScaler = 200.0f; +static const int sNodeRectSize = 16; + +//----------------------------------------------------------------------------- +// GuiShapeEdPreview +//----------------------------------------------------------------------------- + +GuiShapeEdPreview::GuiShapeEdPreview() +: mOrbitDist( 5.0f ), + mMoveSpeed (1.0f ), + mZoomSpeed (1.0f ), + mModel( NULL ), + mRenderGhost( false ), + mRenderNodes( false ), + mRenderBounds( false ), + mSelectedNode(-1 ), + mHoverNode(-1 ), + mUsingAxisGizmo( false ), + mSliderCtrl( NULL ), + mIsPlaying( false ), + mSeqIn( 0 ), + mSeqOut( 0 ), + mLastRenderTime( 0 ), + mAnimThread( 0 ), + mTimeScale( 1.0f ), + mAnimationSeq( 0 ), + mFakeSun( NULL ), + mCameraRot( 0, 0, 3.9f ), + mOrbitPos( 0, 0, 0 ) +{ + mActive = true; + + // By default don't do dynamic reflection + // updates for this viewport. + mReflectPriority = 0.0f; +} + +GuiShapeEdPreview::~GuiShapeEdPreview() +{ + if( mModel ) + SAFE_DELETE( mModel ); + if( mFakeSun ) + SAFE_DELETE( mFakeSun ); +} + +void GuiShapeEdPreview::initPersistFields() +{ + addField("renderGrid", TypeBool, Offset( mRenderGridPlane, EditTSCtrl )); + addField("renderNodes", TypeBool, Offset( mRenderNodes, GuiShapeEdPreview )); + addField("renderGhost", TypeBool, Offset( mRenderGhost, GuiShapeEdPreview )); + addField("renderBounds", TypeBool, Offset( mRenderBounds, GuiShapeEdPreview )); + addField("selectedNode", TypeS32, Offset( mSelectedNode, GuiShapeEdPreview )); + addField("isPlaying", TypeBool, Offset( mIsPlaying, GuiShapeEdPreview )); + addField("seqIn", TypeF32, Offset( mSeqIn, GuiShapeEdPreview )); + addField("seqOut", TypeF32, Offset( mSeqOut, GuiShapeEdPreview )); + + Parent::initPersistFields(); +} + +bool GuiShapeEdPreview::onWake() +{ + if (!Parent::onWake()) + return false; + + if (!mFakeSun ) + mFakeSun = gClientSceneGraph->getLightManager()->createLightInfo(); + + mFakeSun->setColor( ColorF( 1.0f, 1.0f, 1.0f ) ); + mFakeSun->setAmbient( ColorF( 0.5f, 0.5f, 0.5f ) ); + mFakeSun->setDirection( VectorF( 0.f, 0.707f, -0.707f ) ); + mFakeSun->setPosition( mFakeSun->getDirection() * -10000.0f ); + mFakeSun->setRange( 2000000.0f ); + + mGizmoProfile->mode = MoveMode; + + return( true ); +} + +void GuiShapeEdPreview::setOrbitDistance(F32 distance) +{ + mOrbitDist = distance; +} + +void GuiShapeEdPreview::setTimeScale(F32 scale) +{ + mTimeScale = scale; + if ( mModel && mAnimThread ) + mModel->setTimeScale( mAnimThread, mTimeScale ); +} + +void GuiShapeEdPreview::updateNodeTransforms() +{ + if ( mModel ) + mModel->mDirtyFlags[0] |= TSShapeInstance::TransformDirty; +} + +void GuiShapeEdPreview::setSliderCtrl(GuiSliderCtrl* ctrl) +{ + mSliderCtrl = ctrl; +} + +void GuiShapeEdPreview::get3DCursor( GuiCursor *&cursor, + bool &visible, + const Gui3DMouseEvent &event_ ) +{ + cursor = NULL; + visible = false; + + GuiCanvas *root = getRoot(); + if ( !root ) + return; + + S32 currCursor = PlatformCursorController::curArrow; + + if ( root->mCursorChanged == currCursor ) + return; + + PlatformWindow *window = root->getPlatformWindow(); + PlatformCursorController *controller = window->getCursorController(); + + // We've already changed the cursor, + // so set it back before we change it again. + if ( root->mCursorChanged != -1 ) + controller->popCursor(); + + // Now change the cursor shape + controller->pushCursor( currCursor ); + root->mCursorChanged = currCursor; +} + +void GuiShapeEdPreview::fitToShape() +{ + if ( !mModel ) + return; + + // Determine the shape bounding box given the current camera rotation + MatrixF camRotMatrix( smCamMatrix ); + camRotMatrix.setPosition( Point3F::Zero ); + camRotMatrix.inverse(); + + Box3F bounds = mModel->getShape()->bounds; + camRotMatrix.mul( bounds ); + + // Estimate the camera distance to fill the view by comparing the radii + // of the box and the viewport + F32 len_x = bounds.len_x(); + F32 len_z = bounds.len_z(); + F32 shapeRadius = mSqrt( len_x*len_x + len_z*len_z ) / 2; + F32 viewRadius = 0.45f * getMin( getExtent().x, getExtent().y ); + + mOrbitPos = mModel->getShape()->bounds.getCenter(); + mOrbitDist = ( shapeRadius / viewRadius ) * mSaveWorldToScreenScale.y; +} + +//----------------------------------------------------------------------------- +// Camera control and Node editing +// - moving the mouse over a node will highlight (but not select) it +// - left clicking on a node will select it, the gizmo will appear +// - left clicking on no node will unselect the current node +// - left dragging the gizmo will translate/rotate the node +// - middle drag translates the view +// - right drag rotates the view +// - mouse wheel zooms the view +// - holding shift while changing the view speeds them up + +bool GuiShapeEdPreview::onKeyDown(const GuiEvent& event) +{ + switch ( event.keyCode ) + { + case KEY_CONTROL: + // Holding CTRL puts the gizmo in rotate mode + mGizmoProfile->mode = RotateMode; + return true; + default: + break; + } + return false; +} + +bool GuiShapeEdPreview::onKeyUp(const GuiEvent& event) +{ + switch ( event.keyCode ) + { + case KEY_CONTROL: + // Releasing CTRL puts the gizmo in move mode + mGizmoProfile->mode = MoveMode; + return true; + default: + break; + } + return false; +} + +S32 GuiShapeEdPreview::collideNode(const Gui3DMouseEvent& event) const +{ + // Check if the given position is inside the screen rectangle of + // any shape node + S32 nodeIndex = -1; + F32 minZ = 0; + for ( S32 i = 0; i < mProjectedNodes.size(); i++) + { + const Point3F& pt = mProjectedNodes[i]; + if ( pt.z > 1.0f ) + continue; + + RectI rect( pt.x - sNodeRectSize/2, pt.y - sNodeRectSize/2, sNodeRectSize, sNodeRectSize ); + if ( rect.pointInRect( event.mousePoint ) ) + { + if ( ( nodeIndex == -1 ) || ( pt.z < minZ ) ) + { + nodeIndex = i; + minZ = pt.z; + } + } + } + + return nodeIndex; +} + +void GuiShapeEdPreview::handleMouseDown(const GuiEvent& event, Mode mode) +{ + if (!mActive || !mVisible || !mAwake ) + return; + + mouseLock(); + mLastMousePos = event.mousePoint; + + if ( mode == NoneMode ) + { + make3DMouseEvent( mLastEvent, event ); + + // Check gizmo first + mUsingAxisGizmo = false; + if ( mSelectedNode != -1 ) + { + mGizmo->on3DMouseDown( mLastEvent ); + if ( mGizmo->getSelection() != Gizmo::None ) + { + mUsingAxisGizmo = true; + return; + } + } + + // Check if we have clicked on a node + S32 selected = collideNode( mLastEvent ); + if ( selected != mSelectedNode ) + { + mSelectedNode = selected; + Con::executef( this, "onNodeSelected", Con::getIntArg( mSelectedNode )); + } + } +} + +void GuiShapeEdPreview::handleMouseUp(const GuiEvent& event, Mode mode) +{ + mouseUnlock(); + mUsingAxisGizmo = false; + + if ( mode == NoneMode ) + { + make3DMouseEvent( mLastEvent, event ); + mGizmo->on3DMouseUp( mLastEvent ); + } +} + +void GuiShapeEdPreview::handleMouseMove(const GuiEvent& event, Mode mode) +{ + if ( mode == NoneMode ) + { + make3DMouseEvent( mLastEvent, event ); + if ( mSelectedNode != -1 ) + { + // Check if the mouse is hovering over an axis + mGizmo->on3DMouseMove( mLastEvent ); + if ( mGizmo->getSelection() != Gizmo::None ) + return; + } + + // Check if we are over another node + mHoverNode = collideNode( mLastEvent ); + } +} + +void GuiShapeEdPreview::handleMouseDragged(const GuiEvent& event, Mode mode) +{ + if ( mode == NoneMode ) + { + make3DMouseEvent( mLastEvent, event ); + + if ( mUsingAxisGizmo ) + { + // Use gizmo to modify the transform of the selected node + mGizmo->on3DMouseDragged( mLastEvent ); + switch ( mGizmoProfile->mode ) + { + case MoveMode: + // Update node transform + if ( mSelectedNode != -1 ) + { + Point3F pos = mModel->mNodeTransforms[mSelectedNode].getPosition() + mGizmo->getOffset(); + mModel->mNodeTransforms[mSelectedNode].setPosition( pos ); + } + break; + + case RotateMode: + // Update node transform + if ( mSelectedNode != -1 ) + { + EulerF rot = mGizmo->getDeltaRot(); + mModel->mNodeTransforms[mSelectedNode].mul( MatrixF( rot ) ); + } + break; + default: + break; + } + + // Notify the change in node transform + const char* name = mModel->getShape()->getNodeName(mSelectedNode).c_str(); + const Point3F pos = mModel->mNodeTransforms[mSelectedNode].getPosition(); + AngAxisF aa(mModel->mNodeTransforms[mSelectedNode]); + char buffer[256]; + dSprintf(buffer, sizeof(buffer), "%g %g %g %g %g %g %g", + pos.x, pos.y, pos.z, aa.axis.x, aa.axis.y, aa.axis.z, aa.angle); + + Con::executef(this, "onEditNodeTransform", name, buffer); + } + } + else + { + Point2F delta( event.mousePoint.x - mLastMousePos.x, event.mousePoint.y - mLastMousePos.y ); + mLastMousePos = event.mousePoint; + + // Use shift to increase speed + delta.x *= ( event.modifier & SI_SHIFT ) ? 0.05f : 0.01f; + delta.y *= ( event.modifier & SI_SHIFT ) ? 0.05f : 0.01f; + + switch ( mode ) + { + case MoveMode: + { + VectorF offset(-delta.x, 0, delta.y ); + smCamMatrix.mulV( offset ); + mOrbitPos += offset * mMoveSpeed; + } + break; + + case RotateMode: + mCameraRot.x += delta.y; + mCameraRot.z += delta.x; + break; + default: + break; + } + } +} + +bool GuiShapeEdPreview::onMouseWheelUp(const GuiEvent& event) +{ + // Use shift to increase speed + setOrbitDistance( mOrbitDist - mFabs(event.fval) * mZoomSpeed * (( event.modifier & SI_SHIFT ) ? 1.0f : 0.25f) ); + return true; +} + +bool GuiShapeEdPreview::onMouseWheelDown(const GuiEvent& event) +{ + // Use shift to increase speed + setOrbitDistance( mOrbitDist + mFabs(event.fval) * mZoomSpeed * (( event.modifier & SI_SHIFT ) ? 1.0f : 0.25f) ); + return true; +} + +//----------------------------------------------------------------------------- +void GuiShapeEdPreview::setObjectModel(const char* modelName) +{ + if ( mModel ) + { + mModel->destroyThread( mAnimThread ); + mAnimThread = 0; + SAFE_DELETE( mModel ); + } + + if (modelName && modelName[0]) + { + Resource model = ResourceManager::get().load( modelName ); + if (! bool( model )) + { + Con::warnf( avar("GuiShapeEdPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName )); + return; + } + + mModel = new TSShapeInstance( model, true ); + AssertFatal( mModel, avar("GuiShapeEdPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName )); + + // Initialize camera values: + mOrbitPos = mModel->getShape()->center; + + // Set camera move and zoom speed according to model size + mMoveSpeed = mModel->getShape()->radius / sMoveScaler; + mZoomSpeed = mModel->getShape()->radius / sZoomScaler; + + // Reset node selection + mHoverNode = -1; + mSelectedNode = -1; + + // the first time recording + mLastRenderTime = Platform::getVirtualMilliseconds(); + } +} + +void GuiShapeEdPreview::setObjectAnimation(const char* seqName) +{ + if ( mModel ) + { + mAnimationSeq = mModel->getShape()->findSequence( seqName ); + if ( mAnimationSeq == -1 ) + { + mModel->destroyThread( mAnimThread ); + mAnimThread = 0; + } + else + { + if (!mAnimThread ) + { + mAnimThread = mModel->addThread(); + mModel->setTimeScale( mAnimThread, mTimeScale ); + } + + mModel->setSequence( mAnimThread, mAnimationSeq, 0.0f ); + } + } + else + { + mAnimationSeq = -1; + } +} + +//----------------------------------------------------------------------------- +bool GuiShapeEdPreview::processCameraQuery(CameraQuery* query) +{ + // Adjust the camera so that we are still facing the model: + Point3F vec; + MatrixF xRot, zRot; + xRot.set( EulerF( mCameraRot.x, 0.0f, 0.0f )); + zRot.set( EulerF( 0.0f, 0.0f, mCameraRot.z )); + + smCamMatrix.mul( zRot, xRot ); + smCamMatrix.getColumn( 1, &vec ); + vec *= mOrbitDist; + smCamPos = mOrbitPos - vec; + smCamMatrix.setColumn( 3, smCamPos ); + + query->farPlane = 1000000.0f; + query->nearPlane = 0.01f; + query->fov = 45.0f; + query->cameraMatrix = smCamMatrix; + + smCamOrtho = query->ortho; + smCamNearPlane = query->nearPlane; + + return true; +} + +void GuiShapeEdPreview::updateProjectedNodePoints() +{ + // @todo: When a node is added, we need to make sure to resize the nodeTransforms array as well + mModel->mNodeTransforms.setSize( mModel->getShape()->nodes.size() ); + mProjectedNodes.setSize( mModel->getShape()->nodes.size() ); + + for ( S32 i = 0; i < mModel->mNodeTransforms.size(); i++) + project( mModel->mNodeTransforms[i].getPosition(), &mProjectedNodes[i] ); +} + +void GuiShapeEdPreview::renderWorld(const RectI &updateRect) +{ + if ( !mModel ) + return; + + F32 left, right, top, bottom, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho ); + Frustum frust( false, left, right, top, bottom, nearPlane, farPlane, MatrixF::Identity ); + + SceneState state( + NULL, + gClientSceneGraph, + SPT_Diffuse, + 1, + frust, + GFX->getViewport(), + false, + false ); + + // Set up our TS render state here. + TSRenderState rdata; + rdata.setSceneState(&state); + + // Set up pass transforms + RenderPassManager *renderPass = state.getRenderPass(); + renderPass->assignSharedXform(RenderPassManager::View, MatrixF::Identity); + renderPass->assignSharedXform(RenderPassManager::Projection, GFX->getProjectionMatrix()); + + LightManager* lm = gClientSceneGraph->getLightManager(); + lm->unregisterAllLights(); + lm->setSpecialLight( LightManager::slSunLightType, mFakeSun ); + lm->setupLights(NULL, SphereF( Point3F::Zero, 1.0f ) ); + + // Determine time elapsed since last render (for animation playback) + S32 time = Platform::getVirtualMilliseconds(); + S32 dt = time - mLastRenderTime; + mLastRenderTime = time; + + if ( mModel ) + { + // Render the grid (auto-sized to the model) + if ( mRenderGridPlane ) + { + Box3F bounds = mModel->getShape()->bounds; + F32 dim = getMax( bounds.len_x(), bounds.len_y() ) * 2.0f; + Point2F size( dim, dim ); + Point2F majorStep( size / 6 ); + Point2F minorStep( majorStep / 10 ); + ColorF gray( 0.5f, 0.5f, 0.5f ); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, true ); + + GFX->getDrawUtil()->drawPlaneGrid( desc, Point3F::Zero, size, minorStep, gray ); + GFX->getDrawUtil()->drawPlaneGrid( desc, Point3F::Zero, size, majorStep, ColorF::BLACK ); + } + + // Update projected node points (for mouse picking) + updateProjectedNodePoints(); + + GFX->setStateBlock( mDefaultGuiSB ); + + // animate and render + if ( mAnimThread ) + { + F32 sliderRange = mSliderCtrl ? ( mSliderCtrl->getRange().y - mSliderCtrl->getRange().x ) : 0; + + // allow slider to change the thread position (even during playback) + F32 threadPos = mModel->getPos( mAnimThread ); + if ( sliderRange ) + { + F32 sliderPos = mSliderCtrl->getValue() / sliderRange; + if ( sliderPos != threadPos ) + { + threadPos = sliderPos; + mModel->setPos( mAnimThread, threadPos ); + } + } + + if ( mIsPlaying ) + { + if ( threadPos < mSeqIn ) + mModel->setPos( mAnimThread, mSeqIn ); + mModel->advanceTime(( F32 )dt/1000.f, mAnimThread ); + + // Ensure that position is within in/out values + threadPos = mModel->getPos( mAnimThread ); + if ( threadPos < mSeqIn ) + { + if ( mAnimThread->getSequence()->isCyclic()) + threadPos = mSeqIn + mFmod( threadPos, mSeqOut - mSeqIn ); + else + threadPos = mSeqIn; + mModel->setPos( mAnimThread, threadPos ); + } + else if ( threadPos > mSeqOut ) + { + if ( mAnimThread->getSequence()->isCyclic()) + threadPos = mSeqIn + mFmod( threadPos - mSeqOut, mSeqOut - mSeqIn ); + else + threadPos = mSeqOut; + mModel->setPos( mAnimThread, threadPos ); + } + + // update the slider value to match + if ( sliderRange ) + mSliderCtrl->setValue( threadPos * sliderRange ); + } + } + + mModel->animate(); + if ( mRenderGhost ) + rdata.setFadeOverride( 0.5f ); + mModel->render( rdata ); + + // Optionally render the shape bounding box + if ( mRenderBounds ) + { + Point3F boxSize = mModel->getShape()->bounds.maxExtents - mModel->getShape()->bounds.minExtents; + + GFXStateBlockDesc desc; + desc.fillMode = GFXFillWireframe; + GFX->getDrawUtil()->drawCube( desc, boxSize, mModel->getShape()->center, ColorF::WHITE ); + } + + gClientSceneGraph->getRenderPass()->render( &state ); + gClientSceneGraph->getRenderPass()->clear(); + + // render the nodes in the model + renderNodes( updateRect ); + } +} + +void GuiShapeEdPreview::renderNodes(const RectI& updateRect) const +{ + if ( mRenderNodes ) + { + // Render links between nodes + GFXStateBlockDesc desc; + desc.setZReadWrite( false, true ); + desc.setCullMode( GFXCullNone ); + GFX->setStateBlockByDesc( desc ); + + PrimBuild::color( ColorI::WHITE ); + PrimBuild::begin( GFXLineList, mModel->getShape()->nodes.size() * 2 ); + for ( S32 i = 0; i < mModel->getShape()->nodes.size(); i++) + { + const TSShape::Node& node = mModel->getShape()->nodes[i]; + const String& nodeName = mModel->getShape()->getName( node.nameIndex ); + if ( nodeName.compare( "__deleted_", 10 ) == 0 ) + continue; + + if (node.parentIndex >= 0) + { + Point3F start(mModel->mNodeTransforms[i].getPosition()); + Point3F end(mModel->mNodeTransforms[node.parentIndex].getPosition()); + + PrimBuild::vertex3f( start.x, start.y, start.z ); + PrimBuild::vertex3f( end.x, end.y, end.z ); + } + } + PrimBuild::end(); + + // Render the node axes + for ( S32 i = 0; i < mModel->getShape()->nodes.size(); i++) + { + // Render the selected and hover nodes last (so they are on top) + if ( ( i == mSelectedNode ) || ( i == mHoverNode ) ) + continue; + + renderNodeAxes( i, ColorF::WHITE ); + } + + // Render the hovered node + if ( mHoverNode != -1 ) + renderNodeAxes( mHoverNode, ColorF::GREEN ); + } + + // Render the selected node (even if mRenderNodes is false) + if ( mSelectedNode != -1 ) + { + renderNodeAxes( mSelectedNode, ColorF::GREEN ); + + const MatrixF& nodeMat = mModel->mNodeTransforms[mSelectedNode]; + mGizmo->set( nodeMat, nodeMat.getPosition(), Point3F( 1, 1, 1 )); + mGizmo->renderGizmo( smCamMatrix ); + } + + // Render the names of the hovered and selected nodes + GFX->setClipRect( updateRect ); + if ( mRenderNodes && mHoverNode != -1 ) + renderNodeName( mHoverNode, ColorF::WHITE ); + if ( mSelectedNode != -1 ) + renderNodeName( mSelectedNode, ColorF::WHITE ); +} + +void GuiShapeEdPreview::renderNodeAxes(S32 index, const ColorF& nodeColor) const +{ + // Ignore nodes marked for deletion + const TSShape::Node& node = mModel->getShape()->nodes[index]; + const String& nodeName = mModel->getShape()->getName( node.nameIndex ); + if ( nodeName.compare( "__deleted_", 10 ) == 0 ) + return; + + const Point3F xAxis( 1.0f, 0.15f, 0.15f ); + const Point3F yAxis( 0.15f, 1.0f, 0.15f ); + const Point3F zAxis( 0.15f, 0.15f, 1.0f ); + + GFXStateBlockDesc desc; + desc.setZReadWrite( false, true ); + desc.setCullMode( GFXCullNone ); + + // Render nodes the same size regardless of zoom + F32 scale = mOrbitDist / 60; + + GFX->pushWorldMatrix(); + GFX->multWorld( mModel->mNodeTransforms[index] ); + + GFX->getDrawUtil()->drawCube( desc, xAxis * scale, Point3F::Zero, nodeColor ); + GFX->getDrawUtil()->drawCube( desc, yAxis * scale, Point3F::Zero, nodeColor ); + GFX->getDrawUtil()->drawCube( desc, zAxis * scale, Point3F::Zero, nodeColor ); + + GFX->popWorldMatrix(); +} + +void GuiShapeEdPreview::renderNodeName(S32 index, const ColorF& textColor) const +{ + // Ignore nodes marked for deletion + const TSShape::Node& node = mModel->getShape()->nodes[index]; + const String& nodeName = mModel->getShape()->getName( node.nameIndex ); + if ( nodeName.compare( "__deleted_", 10 ) == 0 ) + return; + + Point2I pos( mProjectedNodes[index].x, mProjectedNodes[index].y + sNodeRectSize + 6 ); + + GFX->getDrawUtil()->setBitmapModulation( textColor ); + GFX->getDrawUtil()->drawText( mProfile->mFont, pos, nodeName.c_str() ); +} + +//----------------------------------------------------------------------------- +// Console methods (GuiShapeEdPreview) +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT( GuiShapeEdPreview ); + +ConsoleMethod( GuiShapeEdPreview, setSliderCtrl, void, 3, 3, + "( string ctrl_name )") +{ + TORQUE_UNUSED( argc ); + GuiSliderCtrl* ctrl = static_cast( Sim::findObject( argv[2] )); + if (!ctrl ) + Con::warnf("Could not find GuiSliderCtrl: %s", argv[2] ); + object->setSliderCtrl( ctrl ); +} + +ConsoleMethod( GuiShapeEdPreview, setModel, void, 3, 3, + "( string shapeName )\n" + "Sets the model to be displayed in this control\n\n" + "\\param shapeName Name of the model to display.\n") +{ + TORQUE_UNUSED( argc ); + object->setObjectModel( argv[2] ); +} + +ConsoleMethod( GuiShapeEdPreview, setSequence, void, 3, 3, + "( string sequence )\n" + "Sets the animation to play for the viewed object.\n\n" + "\\param sequence The name of the animation to play.") +{ + TORQUE_UNUSED( argc ); + object->setObjectAnimation( argv[2] ); +} + +ConsoleMethod( GuiShapeEdPreview, setOrbitDistance, void, 3, 3, + "( float distance )\n" + "Sets the distance at which the camera orbits the object. Clamped to the acceptable range defined in the class by min and max orbit distances.\n\n" + "\\param distance The distance to set the orbit to ( will be clamped ).") +{ + TORQUE_UNUSED( argc ); + object->setOrbitDistance( dAtof( argv[2] )); +} + +ConsoleMethod( GuiShapeEdPreview, setTimeScale, void, 3, 3, + "( float scale )") +{ + TORQUE_UNUSED( argc ); + object->setTimeScale( dAtof( argv[2] )); +} + +ConsoleMethod( GuiShapeEdPreview, fitToShape, void, 2, 2, + "()") +{ + TORQUE_UNUSED( argc ); + object->fitToShape(); +} + +ConsoleMethod( GuiShapeEdPreview, updateNodeTransforms, void, 2, 2, + "()") +{ + object->updateNodeTransforms(); +} diff --git a/gui/editor/guiShapeEdPreview.h b/gui/editor/guiShapeEdPreview.h new file mode 100644 index 0000000..b67e4ed --- /dev/null +++ b/gui/editor/guiShapeEdPreview.h @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUISHAPEEDPREVIEW_H_ +#define _GUISHAPEEDPREVIEW_H_ + +#include "gui/worldEditor/editTSCtrl.h" +#include "gui/controls/guiSliderCtrl.h" +#include "ts/tsShapeInstance.h" + +class LightInfo; + +class GuiShapeEdPreview : public EditTSCtrl +{ +private: + typedef EditTSCtrl Parent; + + // View and node selection + bool mUsingAxisGizmo; + + S32 mSelectedNode; + S32 mHoverNode; + Vector mProjectedNodes; + + // Camera + EulerF mCameraRot; + Point3F mOrbitPos; + F32 mOrbitDist; + F32 mMoveSpeed; + F32 mZoomSpeed; + + // Rendering + bool mRenderGhost; + bool mRenderNodes; + bool mRenderBounds; + TSShapeInstance* mModel; + + LightInfo* mFakeSun; + + // Animation and playback control + TSThread * mAnimThread; + S32 mLastRenderTime; + S32 mAnimationSeq; + F32 mTimeScale; + + bool mIsPlaying; + F32 mSeqIn; + F32 mSeqOut; + + GuiSliderCtrl *mSliderCtrl; + + // Generic mouse event handlers + void handleMouseDown(const GuiEvent& event, Mode mode); + void handleMouseUp(const GuiEvent& event, Mode mode); + void handleMouseMove(const GuiEvent& event, Mode mode); + void handleMouseDragged(const GuiEvent& event, Mode mode); + + S32 collideNode(const Gui3DMouseEvent& event) const; + void updateProjectedNodePoints(); + +public: + bool onWake(); + + // Keyboard and Mouse event handlers + bool onKeyDown(const GuiEvent& event); + bool onKeyUp(const GuiEvent& event); + + void onMouseDown(const GuiEvent& event) { handleMouseDown(event, NoneMode); } + void onMouseUp(const GuiEvent& event) { handleMouseUp(event, NoneMode); } + void onMouseMove(const GuiEvent& event) { handleMouseMove(event, NoneMode); } + void onMouseDragged(const GuiEvent& event) { handleMouseDragged(event, NoneMode); } + + void onMiddleMouseDown(const GuiEvent& event) { handleMouseDown(event, MoveMode); } + void onMiddleMouseUp(const GuiEvent& event) { handleMouseUp(event, MoveMode); } + void onMiddleMouseDragged(const GuiEvent& event) { handleMouseDragged(event, MoveMode); } + + void onRightMouseDown(const GuiEvent& event) { handleMouseDown(event, RotateMode); } + void onRightMouseUp(const GuiEvent& event) { handleMouseUp(event, RotateMode); } + void onRightMouseDragged(const GuiEvent& event) { handleMouseDragged(event, RotateMode); } + + bool onMouseWheelUp(const GuiEvent& event); + bool onMouseWheelDown(const GuiEvent& event); + + // Setters/Getters + TSShapeInstance* getModel() { return mModel; } + + void setEditMode(bool editMode); + void setObjectModel(const char * modelName); + void setObjectAnimation(const char * seqName); + void setSliderCtrl(GuiSliderCtrl* ctrl); + void setTimeScale(F32 scale); + void setOrbitDistance(F32 distance); + void updateNodeTransforms(); + + void get3DCursor(GuiCursor *& cursor, bool& visible, const Gui3DMouseEvent& event_); + + void fitToShape(); + + // Rendering + bool processCameraQuery(CameraQuery *query); + void renderWorld(const RectI& updateRect); + void renderNodes(const RectI& updateRect) const; + void renderNodeAxes(S32 index, const ColorF& nodeColor) const; + void renderNodeName(S32 index, const ColorF& textColor) const; + + DECLARE_CONOBJECT(GuiShapeEdPreview); + DECLARE_CATEGORY( "Gui Editor" ); + + static void initPersistFields(); + + GuiShapeEdPreview(); + ~GuiShapeEdPreview(); +}; + +#endif diff --git a/gui/editor/inspector/customField.cpp b/gui/editor/inspector/customField.cpp new file mode 100644 index 0000000..2781694 --- /dev/null +++ b/gui/editor/inspector/customField.cpp @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/editor/inspector/customField.h" +#include "gui/editor/guiInspector.h" + +//----------------------------------------------------------------------------- +// GuiInspectorCustomField - Child class of GuiInspectorField +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT( GuiInspectorCustomField ); + +GuiInspectorCustomField::GuiInspectorCustomField( GuiInspector *inspector, + GuiInspectorGroup* parent, + SimObjectPtr target, + SimFieldDictionary::Entry* field ) +{ + mInspector = inspector; + mParent = parent; + mTarget = target; + setBounds(0,0,100,20); + + mCustomValue = StringTable->insert(""); + mDoc = StringTable->insert(""); +} + +GuiInspectorCustomField::GuiInspectorCustomField() +{ + mInspector = NULL; + mParent = NULL; + mTarget = NULL; + mCustomValue = StringTable->insert(""); + mDoc = StringTable->insert(""); +} + +void GuiInspectorCustomField::setData( StringTableEntry data ) +{ + mCustomValue = data; + + // Force our edit to update + updateValue(); +} + +StringTableEntry GuiInspectorCustomField::getData() +{ + return mCustomValue; +} + +void GuiInspectorCustomField::updateValue() +{ + setValue( mCustomValue ); +} + +void GuiInspectorCustomField::setDoc( StringTableEntry data ) +{ + mDoc = data; +} + +void GuiInspectorCustomField::setToolTip( StringTableEntry data ) +{ + mEdit->setDataField( StringTable->insert("tooltipprofile"), NULL, "GuiToolTipProfile" ); + mEdit->setDataField( StringTable->insert("hovertime"), NULL, "1000" ); + mEdit->setDataField( StringTable->insert("tooltip"), NULL, data ); +} + +bool GuiInspectorCustomField::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + return true; +} + +void GuiInspectorCustomField::setInspectorField( AbstractClassRep::Field *field, + StringTableEntry caption, + const char*arrayIndex ) +{ + // Override the base just to be sure it doesn't get called. + // We don't use an AbstractClassRep::Field... + +// mField = field; +// mCaption = StringTable->EmptyString(); +// mRenameCtrl->setText( getFieldName() ); +} + +GuiControl* GuiInspectorCustomField::constructEditControl() +{ + GuiControl* retCtrl = new GuiTextCtrl(); + + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + // Register the object + retCtrl->registerObject(); + + return retCtrl; +} + +void GuiInspectorCustomField::setValue( StringTableEntry newValue ) +{ + GuiTextCtrl *ctrl = dynamic_cast( mEdit ); + if( ctrl != NULL ) + ctrl->setText( newValue ); +} + +void GuiInspectorCustomField::_executeSelectedCallback() +{ + Con::executef( mInspector, "onFieldSelected", mCaption, ConsoleBaseType::getType(TypeCaseString)->getTypeName(), mDoc ); +} diff --git a/gui/editor/inspector/customField.h b/gui/editor/inspector/customField.h new file mode 100644 index 0000000..a68a365 --- /dev/null +++ b/gui/editor/inspector/customField.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_CUSTOMFIELD_H_ +#define _GUI_INSPECTOR_CUSTOMFIELD_H_ + +#include "console/simFieldDictionary.h" +#include "gui/editor/inspector/field.h" + +class GuiInspectorCustomField : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +public: + + GuiInspectorCustomField( GuiInspector *inspector, GuiInspectorGroup* parent, SimObjectPtr target, SimFieldDictionary::Entry* field ); + GuiInspectorCustomField(); + ~GuiInspectorCustomField() {}; + + DECLARE_CONOBJECT( GuiInspectorCustomField ); + + virtual void setData( StringTableEntry data ); + virtual StringTableEntry getData(); + virtual void updateValue(); + virtual StringTableEntry getFieldName() { return StringTable->insert( "" ); } + + virtual void setDoc( StringTableEntry data ); + virtual void setToolTip( StringTableEntry data ); + + virtual bool onAdd(); + + virtual void setInspectorField( AbstractClassRep::Field *field, + StringTableEntry caption = NULL, + const char *arrayIndex = NULL ); + + virtual GuiControl* constructEditControl(); + + virtual void setValue( StringTableEntry newValue ); + +protected: + + virtual void _executeSelectedCallback(); + +protected: + + StringTableEntry mCustomValue; + StringTableEntry mDoc; +}; + +#endif // _GUI_INSPECTOR_DYNAMICFIELD_H_ diff --git a/gui/editor/inspector/datablockField.cpp b/gui/editor/inspector/datablockField.cpp new file mode 100644 index 0000000..10a0c93 --- /dev/null +++ b/gui/editor/inspector/datablockField.cpp @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/simBase.h" +#include "console/simDatablock.h" +#include "gui/editor/guiInspector.h" +#include "gui/editor/inspector/datablockField.h" +#include "gui/editor/inspector/group.h" +#include "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/inspector/datablockField.h" + +//----------------------------------------------------------------------------- +// GuiInspectorDatablockField +// Field construction for datablock types +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiInspectorDatablockField); + +static S32 QSORT_CALLBACK stringCompare(const void *a,const void *b) +{ + StringTableEntry sa = *(StringTableEntry*)a; + StringTableEntry sb = *(StringTableEntry*)b; + return(dStrnatcasecmp(sa, sb)); +} + +GuiInspectorDatablockField::GuiInspectorDatablockField( StringTableEntry className ) +{ + setClassName(className); +}; + +void GuiInspectorDatablockField::setClassName( StringTableEntry className ) +{ + // Walk the ACR list and find a matching class if any. + AbstractClassRep *walk = AbstractClassRep::getClassList(); + while(walk) + { + if(!dStricmp(walk->getClassName(), className)) + { + // Match! + mDesiredClass = walk; + return; + } + + walk = walk->getNextClass(); + } + + // No dice. + Con::warnf("GuiInspectorDatablockField::setClassName - no class '%s' found!", className); + return; +} + +GuiControl* GuiInspectorDatablockField::constructEditControl() +{ + GuiControl* retCtrl = new GuiPopUpMenuCtrl(); + + // If we couldn't construct the control, bail! + if( retCtrl == NULL ) + return retCtrl; + + GuiPopUpMenuCtrl *menu = dynamic_cast(retCtrl); + + // Let's make it look pretty. + retCtrl->setDataField( StringTable->insert("profile"), NULL, "InspectorTypeEnumProfile" ); + + menu->setField("text", getData()); + + _registerEditControl( retCtrl ); + + // Configure it to update our value when the popup is closed + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());%d.inspect(%d);",getId(), retCtrl->getId(),mParent->mParent->getId(), mTarget->getId() ); + //dSprintf( szBuffer, 512, "%d.%s = %d.getText();%d.inspect(%d);",mTarget->getId(), getFieldName(), menu->getId(), mParent->mParent->getId(), mTarget->getId() ); + menu->setField("Command", szBuffer ); + + Vector entries; + + SimDataBlockGroup * grp = Sim::getDataBlockGroup(); + for(SimDataBlockGroup::iterator i = grp->begin(); i != grp->end(); i++) + { + SimDataBlock * datablock = dynamic_cast(*i); + + // Skip non-datablocks if we somehow encounter them. + if(!datablock) + continue; + + // Ok, now we have to figure inheritance info. + if( datablock && datablock->getClassRep()->isClass(mDesiredClass) ) + entries.push_back(datablock->getName()); + } + + // sort the entries + dQsort(entries.address(), entries.size(), sizeof(StringTableEntry), stringCompare); + + // add them to our enum + for(U32 j = 0; j < entries.size(); j++) + menu->addEntry(entries[j], 0); + + return retCtrl; +} diff --git a/gui/editor/inspector/datablockField.h b/gui/editor/inspector/datablockField.h new file mode 100644 index 0000000..f47a7ed --- /dev/null +++ b/gui/editor/inspector/datablockField.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_DATABLOCKFIELD_H_ +#define _GUI_INSPECTOR_DATABLOCKFIELD_H_ + +#include "gui/editor/inspector/field.h" + + +//----------------------------------------------------------------------------- +// GuiInspectorDatablockField - custom field type for datablock enumeration +//----------------------------------------------------------------------------- +class GuiInspectorDatablockField : public GuiInspectorField +{ +private: + typedef GuiInspectorField Parent; + + AbstractClassRep *mDesiredClass; +public: + DECLARE_CONOBJECT(GuiInspectorDatablockField); + GuiInspectorDatablockField( StringTableEntry className ); + GuiInspectorDatablockField() { mDesiredClass = NULL; }; + + void setClassName( StringTableEntry className ); + + //----------------------------------------------------------------------------- + // Override able methods for custom edit fields (Both are REQUIRED) + //----------------------------------------------------------------------------- + virtual GuiControl* constructEditControl(); + +}; + +#endif \ No newline at end of file diff --git a/gui/editor/inspector/dynamicField.cpp b/gui/editor/inspector/dynamicField.cpp new file mode 100644 index 0000000..3d135ed --- /dev/null +++ b/gui/editor/inspector/dynamicField.cpp @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/editor/inspector/dynamicField.h" +#include "gui/editor/inspector/dynamicGroup.h" +#include "gui/editor/guiInspector.h" +#include "gui/buttons/guiIconButtonCtrl.h" + +//----------------------------------------------------------------------------- +// GuiInspectorDynamicField - Child class of GuiInspectorField +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT( GuiInspectorDynamicField ); + +GuiInspectorDynamicField::GuiInspectorDynamicField( GuiInspector *inspector, + GuiInspectorGroup* parent, + SimObjectPtr target, + SimFieldDictionary::Entry* field ) + : mRenameCtrl( NULL ), + mDeleteButton( NULL ) +{ + mInspector = inspector; + mParent = parent; + mTarget = target; + mDynField = field; + setBounds(0,0,100,20); +} + +void GuiInspectorDynamicField::setData( StringTableEntry data ) +{ + // SICKHEAD: add script callback to create an undo action here + // similar to GuiInspectorField to support UndoActions for + // dynamicFields + + if ( mTarget == NULL || mDynField == NULL ) + return; + + data = StringTable->insert( data, true ); + + mTarget->inspectPreApply(); + + // Callback on the inspector when the field is modified + // to allow creation of undo/redo actions. + const char *oldData = mTarget->getDataField( mDynField->slotName, NULL ); + if ( !oldData ) + oldData = ""; + if ( dStrcmp( oldData, data ) != 0 ) + Con::executef( mInspector, "onInspectorFieldModified", Con::getIntArg(mTarget->getId()), mDynField->slotName, oldData, data ); + + mTarget->setDataField( mDynField->slotName, NULL, data ); + + // give the target a chance to validate + mTarget->inspectPostApply(); + + // Force our edit to update + updateValue(); +} + +StringTableEntry GuiInspectorDynamicField::getData() +{ + if( mDynField == NULL || mTarget == NULL ) + return StringTable->insert( "" ); + + return mTarget->getDataField( mDynField->slotName, NULL ); +} + +void GuiInspectorDynamicField::updateValue() +{ + if ( mTarget && mDynField ) + setValue( StringTable->insert( mTarget->getDataField( mDynField->slotName, NULL ) ) ); +} + +void GuiInspectorDynamicField::renameField( StringTableEntry newFieldName ) +{ + // SICKHEAD: add script callback to create an undo action here + // similar to GuiInspectorField to support UndoActions for + // dynamicFields + + if ( mTarget == NULL || mDynField == NULL || mParent == NULL || mEdit == NULL ) + { + Con::warnf("GuiInspectorDynamicField::renameField - No target object or dynamic field data found!" ); + return; + } + + if ( !newFieldName ) + { + Con::warnf("GuiInspectorDynamicField::renameField - Invalid field name specified!" ); + return; + } + + // Only proceed if the name has changed + if ( dStricmp( newFieldName, getFieldName() ) == 0 ) + return; + + // Grab a pointer to our parent and cast it to GuiInspectorDynamicGroup + GuiInspectorDynamicGroup *group = dynamic_cast(mParent); + + if ( group == NULL ) + { + Con::warnf("GuiInspectorDynamicField::renameField - Unable to locate GuiInspectorDynamicGroup parent!" ); + return; + } + + char currentValue[512] = {0}; + + // Grab our current dynamic field value (we use a temporary buffer as this gets corrupted upon Con::eval) + dSprintf( currentValue, 512, "%s", getData() ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.%s = \"\";", mTarget->getId(), getFieldName() ); + Con::evaluate(szBuffer); + + + dSprintf( szBuffer, 512, "%d.%s = \"%s\";", mTarget->getId(), newFieldName, currentValue ); + Con::evaluate(szBuffer); + + // Configure our field to grab data from the new dynamic field + SimFieldDictionary::Entry *newEntry = group->findDynamicFieldInDictionary( newFieldName ); + + if( newEntry == NULL ) + { + Con::warnf("GuiInspectorDynamicField::renameField - Unable to find new field!" ); + return; + } + + // Assign our dynamic field pointer (where we retrieve field information from) to our new field pointer + mDynField = newEntry; + + // Lastly we need to reassign our Command and AltCommand fields for our value edit control + dSprintf( szBuffer, 512, "%d.%s = %d.getText();", mTarget->getId(), newFieldName, mEdit->getId() ); + mEdit->setField("AltCommand", szBuffer ); + mEdit->setField("Validate", szBuffer ); + + if ( mDeleteButton ) + { + dSprintf(szBuffer, 512, "%d.%s = \"\";%d.inspectGroup();", mTarget->getId(), newFieldName, group->getId()); + Con::printf(szBuffer); + mDeleteButton->setField("Command", szBuffer); + } +} + +bool GuiInspectorDynamicField::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + //pushObjectToBack(mEdit); + + // Create our renaming field + mRenameCtrl = new GuiTextEditCtrl(); + mRenameCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorDynamicFieldProfile" ); + + char szName[512]; + dSprintf( szName, 512, "IE_%s_%d_%s_Rename", mRenameCtrl->getClassName(), mTarget->getId(), getFieldName() ); + mRenameCtrl->registerObject( szName ); + + // Our command will evaluate to : + // + // if( (editCtrl).getText() !$= "" ) + // (field).renameField((editCtrl).getText()); + // + char szBuffer[512]; + dSprintf( szBuffer, 512, "if( %d.getText() !$= \"\" ) %d.renameField(%d.getText());", mRenameCtrl->getId(), getId(), mRenameCtrl->getId() ); + mRenameCtrl->setText( getFieldName() ); + mRenameCtrl->setField("AltCommand", szBuffer ); + mRenameCtrl->setField("Validate", szBuffer ); + addObject( mRenameCtrl ); + + // Resize the name control to fit in our caption rect + mRenameCtrl->resize( mCaptionRect.point, mCaptionRect.extent ); + + // Resize the value control to leave space for the delete button + mEdit->resize( mValueRect.point, mValueRect.extent); + + // Clear out any caption set from Parent::onAdd + // since we are rendering the fieldname with our 'rename' control. + mCaption = StringTable->insert( "" ); + + // Create delete button control + mDeleteButton = new GuiBitmapButtonCtrl(); + + SimObject* profilePtr = Sim::findObject("InspectorDynamicFieldButton"); + if( profilePtr != NULL ) + mDeleteButton->setControlProfile( dynamic_cast(profilePtr) ); + + dSprintf( szBuffer, 512, "%d.%s = \"\";%d.schedule(1,\"inspectGroup\");", mTarget->getId(), getFieldName(), mParent->getId() ); + + // FIXME Hardcoded image + mDeleteButton->setField( "Bitmap", "tools/gui/images/iconDelete" ); + mDeleteButton->setField( "Text", "X" ); + mDeleteButton->setField( "Command", szBuffer ); + mDeleteButton->setSizing( horizResizeLeft, vertResizeCenter ); + mDeleteButton->resize(Point2I(getWidth() - 20,2), Point2I(16, 16)); + mDeleteButton->registerObject(); + + addObject(mDeleteButton); + + return true; +} + +bool GuiInspectorDynamicField::updateRects() +{ + Point2I fieldExtent = getExtent(); + S32 dividerPos, dividerMargin; + mInspector->getDivider( dividerPos, dividerMargin ); + + S32 editWidth = dividerPos - dividerMargin; + + mEditCtrlRect.set( fieldExtent.x - dividerPos + dividerMargin, 1, editWidth, fieldExtent.y - 1 ); + mCaptionRect.set( 0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y ); + mValueRect.set( mEditCtrlRect.point, mEditCtrlRect.extent - Point2I( 20, 0 ) ); + mDeleteRect.set( fieldExtent.x - 20, 2, 16, fieldExtent.y - 4 ); + + // This is probably being called during Parent::onAdd + // so our special controls haven't been created yet but are just about to + // so we just need to calculate the extents. + if ( mRenameCtrl == NULL ) + return false; + + bool sized0 = mRenameCtrl->resize( mCaptionRect.point, mCaptionRect.extent ); + bool sized1 = mEdit->resize( mValueRect.point, mValueRect.extent ); + bool sized2 = mDeleteButton->resize(Point2I(getWidth() - 20,2), Point2I(16, 16)); + + return ( sized0 || sized1 || sized2 ); +} + +void GuiInspectorDynamicField::setInspectorField( AbstractClassRep::Field *field, + StringTableEntry caption, + const char*arrayIndex ) +{ + // Override the base just to be sure it doesn't get called. + // We don't use an AbstractClassRep::Field... + +// mField = field; +// mCaption = StringTable->EmptyString(); +// mRenameCtrl->setText( getFieldName() ); +} + +void GuiInspectorDynamicField::_executeSelectedCallback() +{ + ConsoleBaseType* type = mDynField->type; + if ( type ) + Con::executef( mInspector, "onFieldSelected", mDynField->slotName, type->getTypeName() ); + else + Con::executef( mInspector, "onFieldSelected", mDynField->slotName, "TypeDynamicField" ); +} + +ConsoleMethod( GuiInspectorDynamicField, renameField, void, 3,3, "field.renameField(newDynamicFieldName);" ) +{ + object->renameField( argv[2] ); +} diff --git a/gui/editor/inspector/dynamicField.h b/gui/editor/inspector/dynamicField.h new file mode 100644 index 0000000..0ad2de1 --- /dev/null +++ b/gui/editor/inspector/dynamicField.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_DYNAMICFIELD_H_ +#define _GUI_INSPECTOR_DYNAMICFIELD_H_ + +#include "console/simFieldDictionary.h" +#include "gui/editor/inspector/field.h" + + +class GuiInspectorDynamicField : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +public: + + GuiInspectorDynamicField( GuiInspector *inspector, GuiInspectorGroup* parent, SimObjectPtr target, SimFieldDictionary::Entry* field ); + GuiInspectorDynamicField() {}; + ~GuiInspectorDynamicField() {}; + + DECLARE_CONOBJECT( GuiInspectorDynamicField ); + + virtual void setData( StringTableEntry data ); + virtual StringTableEntry getData(); + virtual void updateValue(); + virtual StringTableEntry getFieldName() { return ( mDynField != NULL ) ? mDynField->slotName : StringTable->insert( "" ); } + + virtual bool onAdd(); + + void renameField( StringTableEntry newFieldName ); + GuiControl* constructRenameControl(); + + virtual bool updateRects(); + virtual void setInspectorField( AbstractClassRep::Field *field, + StringTableEntry caption = NULL, + const char *arrayIndex = NULL ); + +protected: + + virtual void _executeSelectedCallback(); + +protected: + + SimFieldDictionary::Entry* mDynField; + SimObjectPtr mRenameCtrl; + GuiBitmapButtonCtrl* mDeleteButton; + RectI mDeleteRect; + RectI mValueRect; +}; + +#endif // _GUI_INSPECTOR_DYNAMICFIELD_H_ diff --git a/gui/editor/inspector/dynamicGroup.cpp b/gui/editor/inspector/dynamicGroup.cpp new file mode 100644 index 0000000..518023d --- /dev/null +++ b/gui/editor/inspector/dynamicGroup.cpp @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/guiInspector.h" +#include "gui/editor/inspector/dynamicGroup.h" +#include "gui/editor/inspector/dynamicField.h" + +IMPLEMENT_CONOBJECT(GuiInspectorDynamicGroup); + +//----------------------------------------------------------------------------- +// GuiInspectorDynamicGroup - add custom controls +//----------------------------------------------------------------------------- +bool GuiInspectorDynamicGroup::createContent() +{ + if(!Parent::createContent()) + return false; + + // encapsulate the button in a dummy control. + GuiControl* shell = new GuiControl(); + shell->setDataField( StringTable->insert("profile"), NULL, "GuiTransparentProfile" ); + if( !shell->registerObject() ) + { + delete shell; + return false; + } + + // add a button that lets us add new dynamic fields. + GuiBitmapButtonCtrl* addFieldBtn = new GuiBitmapButtonCtrl(); + { + SimObject* profilePtr = Sim::findObject("InspectorDynamicFieldButton"); + if( profilePtr != NULL ) + addFieldBtn->setControlProfile( dynamic_cast(profilePtr) ); + + // FIXME Hardcoded image + addFieldBtn->setBitmap("tools/gui/images/iconAdd.png"); + + char commandBuf[64]; + dSprintf(commandBuf, 64, "%d.addDynamicField();", this->getId()); + addFieldBtn->setField("command", commandBuf); + addFieldBtn->setSizing(horizResizeLeft,vertResizeCenter); + //addFieldBtn->setField("buttonMargin", "2 2"); + addFieldBtn->resize(Point2I(getWidth() - 20,2), Point2I(16, 16)); + addFieldBtn->registerObject("zAddButton"); + } + + shell->resize(Point2I(0,0), Point2I(getWidth(), 28)); + shell->addObject(addFieldBtn); + + // save off the shell control, so we can push it to the bottom of the stack in inspectGroup() + mAddCtrl = shell; + mStack->addObject(shell); + + return true; +} + +static S32 QSORT_CALLBACK compareEntries(const void* a,const void* b) +{ + SimFieldDictionary::Entry *fa = *((SimFieldDictionary::Entry **)a); + SimFieldDictionary::Entry *fb = *((SimFieldDictionary::Entry **)b); + return dStricmp(fa->slotName, fb->slotName); +} + +//----------------------------------------------------------------------------- +// GuiInspectorDynamicGroup - inspectGroup override +//----------------------------------------------------------------------------- +bool GuiInspectorDynamicGroup::inspectGroup() +{ + // We can't inspect a group without a target! + if( !mTarget ) + return false; + + // clear the first responder if it's set + mStack->clearFirstResponder(); + GuiCanvas *canvas = mStack->getRoot(); + if(canvas != NULL) + canvas->mFirstResponder = NULL; + + // Clearing the fields and recreating them will more than likely be more + // efficient than looking up existent fields, updating them, and then iterating + // over existent fields and making sure they still exist, if not, deleting them. + clearFields(); + + // Create a vector of the fields + Vector flist; + + // Then populate with fields + SimFieldDictionary * fieldDictionary = mTarget->getFieldDictionary(); + for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + flist.push_back(*ditr); + } + dQsort(flist.address(),flist.size(),sizeof(SimFieldDictionary::Entry *),compareEntries); + + for(U32 i = 0; i < flist.size(); i++) + { + SimFieldDictionary::Entry * entry = flist[i]; + + // Create a dynamic field inspector. Can't reuse typed GuiInspectorFields as + // these rely on AbstractClassRep::Fields. + GuiInspectorDynamicField *field = new GuiInspectorDynamicField( mParent, this, mTarget, entry ); + + // Register the inspector field and add it to our lists + if( field->registerObject() ) + { + mChildren.push_back( field ); + mStack->addObject( field ); + } + else + delete field; + } + + mStack->pushObjectToBack(mAddCtrl); + + setUpdate(); + + return true; +} + +void GuiInspectorDynamicGroup::updateAllFields() +{ + // We overload this to just reinspect the group. + inspectGroup(); +} + +ConsoleMethod(GuiInspectorDynamicGroup, inspectGroup, bool, 2, 2, "Refreshes the dynamic fields in the inspector.") +{ + return object->inspectGroup(); +} + +void GuiInspectorDynamicGroup::clearFields() +{ + // save mAddCtrl + Sim::getGuiGroup()->addObject(mAddCtrl); + // delete everything else + mStack->clear(); + // clear the mChildren list. + mChildren.clear(); + // and restore. + mStack->addObject(mAddCtrl); +} + +SimFieldDictionary::Entry* GuiInspectorDynamicGroup::findDynamicFieldInDictionary( StringTableEntry fieldName ) +{ + if( !mTarget ) + return false; + + SimFieldDictionary * fieldDictionary = mTarget->getFieldDictionary(); + + for(SimFieldDictionaryIterator ditr(fieldDictionary); *ditr; ++ditr) + { + SimFieldDictionary::Entry * entry = (*ditr); + + if( dStricmp( entry->slotName, fieldName ) == 0 ) + return entry; + } + + return NULL; +} + +void GuiInspectorDynamicGroup::addDynamicField() +{ + // We can't add a field without a target + if( !mTarget || !mStack ) + { + Con::warnf("GuiInspectorDynamicGroup::addDynamicField - no target SimObject to add a dynamic field to."); + return; + } + + // find a field name that is not in use. + // But we wont try more than 100 times to find an available field. + U32 uid = 1; + char buf[64] = "dynamicField"; + SimFieldDictionary::Entry* entry = findDynamicFieldInDictionary(buf); + while(entry != NULL && uid < 100) + { + dSprintf(buf, sizeof(buf), "dynamicField%03d", uid++); + entry = findDynamicFieldInDictionary(buf); + } + + Con::evaluatef( "%d.%s = \"defaultValue\";", mTarget->getId(), buf ); + + // now we simply re-inspect the object, to see the new field. + inspectGroup(); + instantExpand(); +} + +ConsoleMethod( GuiInspectorDynamicGroup, addDynamicField, void, 2, 2, "obj.addDynamicField();" ) +{ + object->addDynamicField(); +} diff --git a/gui/editor/inspector/dynamicGroup.h b/gui/editor/inspector/dynamicGroup.h new file mode 100644 index 0000000..351ebb3 --- /dev/null +++ b/gui/editor/inspector/dynamicGroup.h @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_DYNAMICGROUP_H_ +#define _GUI_INSPECTOR_DYNAMICGROUP_H_ + +#include "gui/editor/inspector/group.h" + +#include "console/simFieldDictionary.h" + + +class GuiInspectorDynamicGroup : public GuiInspectorGroup +{ +private: + typedef GuiInspectorGroup Parent; + GuiControl* mAddCtrl; + + +public: + DECLARE_CONOBJECT(GuiInspectorDynamicGroup); + GuiInspectorDynamicGroup() { /*mNeedScroll=false;*/ }; + GuiInspectorDynamicGroup( SimObjectPtr target, StringTableEntry groupName, SimObjectPtr parent ) : GuiInspectorGroup( target, groupName, parent) { /*mNeedScroll=false;*/}; + + //----------------------------------------------------------------------------- + // inspectGroup is overridden in GuiInspectorDynamicGroup to inspect an + // objects FieldDictionary (dynamic fields) instead of regular persistent + // fields. + bool inspectGroup(); + virtual void updateAllFields(); + + // For scriptable dynamic field additions + void addDynamicField(); + + // Clear our fields (delete them) + void clearFields(); + + // Find an already existent field by name in the dictionary + virtual SimFieldDictionary::Entry* findDynamicFieldInDictionary( StringTableEntry fieldName ); +protected: + // create our inner controls when we add + virtual bool createContent(); + +}; + +#endif // _GUI_INSPECTOR_DYNAMICGROUP_H_ diff --git a/gui/editor/inspector/field.cpp b/gui/editor/inspector/field.cpp new file mode 100644 index 0000000..16fe6b6 --- /dev/null +++ b/gui/editor/inspector/field.cpp @@ -0,0 +1,404 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/inspector/field.h" + +#include "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/guiInspector.h" +#include "core/util/safeDelete.h" +#include "gfx/gfxDrawUtil.h" + +//----------------------------------------------------------------------------- +// GuiInspectorField +//----------------------------------------------------------------------------- +// The GuiInspectorField control is a representation of a single abstract +// field for a given ConsoleObject derived object. It handles creation +// getting and setting of it's fields data and editing control. +// +// Creation of custom edit controls is done through this class and is +// dependent upon the dynamic console type, which may be defined to be +// custom for different types. +// +// Note : GuiInspectorField controls must have a GuiInspectorGroup as their +// parent. +IMPLEMENT_CONOBJECT(GuiInspectorField); + + +GuiInspectorField::GuiInspectorField( GuiInspector* inspector, + GuiInspectorGroup* parent, + SimObjectPtr target, + AbstractClassRep::Field* field ) + : mInspector( inspector ), + mParent( parent ), + mTarget( target ), + mField( field ), + mFieldArrayIndex( NULL ), + mEdit( NULL ) +{ + if( field != NULL ) + mCaption = StringTable->insert( field->pFieldname ); + else + mCaption = StringTable->insert( "" ); + + mCanSave = false; + setBounds(0,0,100,18); +} + +GuiInspectorField::GuiInspectorField() + : mInspector( NULL ), + mParent( NULL ), + mTarget( NULL ), + mEdit( NULL ), + mField( NULL ), + mFieldArrayIndex( NULL ), + mCaption( StringTable->insert( "" ) ), + mHighlighted( false ) +{ + mCanSave = false; +} + +GuiInspectorField::~GuiInspectorField() +{ +} + +void GuiInspectorField::init( GuiInspector *inspector, GuiInspectorGroup *group, SimObjectPtr target ) +{ + mInspector = inspector; + mParent = group; + mTarget = target; +} + +bool GuiInspectorField::onAdd() +{ + setInspectorProfile(); + + if ( !Parent::onAdd() ) + return false; + + if ( !mTarget || !mInspector ) + return false; + + mEdit = constructEditControl(); + if ( mEdit == NULL ) + return false; + + setBounds(0,0,100,18); + + // Add our edit as a child + addObject( mEdit ); + + // Calculate Caption and EditCtrl Rects + updateRects(); + + // Force our editField to set it's value + updateValue(); + + return true; +} + +bool GuiInspectorField::resize( const Point2I &newPosition, const Point2I &newExtent ) +{ + if ( !Parent::resize( newPosition, newExtent ) ) + return false; + + return updateRects(); +} + +void GuiInspectorField::onRender( Point2I offset, const RectI &updateRect ) +{ + RectI ctrlRect(offset, getExtent()); + + // Render fillcolor... + if ( mProfile->mOpaque ) + GFX->getDrawUtil()->drawRectFill(ctrlRect, mProfile->mFillColor); + + // Render caption... + if ( mCaption && mCaption[0] ) + { + // Backup current ClipRect + RectI clipBackup = GFX->getClipRect(); + + RectI clipRect = updateRect; + + // The rect within this control in which our caption must fit. + RectI rect( offset + mCaptionRect.point + mProfile->mTextOffset, mCaptionRect.extent + Point2I(1,1) - Point2I(5,0) ); + + // Now clipRect is the amount of our caption rect that is actually visible. + bool hit = clipRect.intersect( rect ); + + if ( hit ) + { + GFX->setClipRect( clipRect ); + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + // Backup modulation color + ColorI currColor; + drawer->getBitmapModulation( &currColor ); + + // Draw caption background... + if ( mHighlighted ) + GFX->getDrawUtil()->drawRectFill( clipRect, mProfile->mFillColorHL ); + + // Draw caption text... + + drawer->setBitmapModulation( mHighlighted ? mProfile->mFontColorHL : mProfile->mFontColor ); + + // Clip text with '...' if too long to fit + String clippedText( mCaption ); + clipText( clippedText, clipRect.extent.x ); + + renderJustifiedText( offset + mProfile->mTextOffset, getExtent(), clippedText ); + + // Restore modulation color + drawer->setBitmapModulation( currColor ); + + // Restore previous ClipRect + GFX->setClipRect( clipBackup ); + } + } + + // Render Children... + renderChildControls(offset, updateRect); + + // Render border... + if ( mProfile->mBorder ) + renderBorder(ctrlRect, mProfile); + + // Render divider... + Point2I worldPnt = mEditCtrlRect.point + offset; + GFX->getDrawUtil()->drawLine( worldPnt.x - 5, + worldPnt.y, + worldPnt.x - 5, + worldPnt.y + getHeight(), + mProfile->mBorderColor ); +} + +void GuiInspectorField::setFirstResponder( GuiControl *firstResponder ) +{ + Parent::setFirstResponder( firstResponder ); + + if ( firstResponder == this || firstResponder == mEdit ) + { + mInspector->setHighlightField( this ); + } +} + +void GuiInspectorField::onMouseDown( const GuiEvent &event ) +{ + if ( mCaptionRect.pointInRect( globalToLocalCoord( event.mousePoint ) ) ) + { + if ( mEdit ) + //mEdit->onMouseDown( event ); + mInspector->setHighlightField( this ); + } + else + Parent::onMouseDown( event ); +} + +void GuiInspectorField::setData( StringTableEntry data ) +{ + if ( mField == NULL || mTarget == NULL ) + return; + + data = StringTable->insert( data, true ); + + if ( verifyData( data ) ) + { + mTarget->inspectPreApply(); + + // Callback on the inspector when the field is modified + // to allow creation of undo/redo actions. + String oldData = mTarget->getDataField( mField->pFieldname, mFieldArrayIndex); + if ( dStrcmp( oldData.c_str(), data ) != 0 ) + { + Con::executef( mInspector, "onInspectorFieldModified", + Con::getIntArg(mTarget->getId()), + mField->pFieldname, + mFieldArrayIndex ? mFieldArrayIndex : "(null)", + oldData.c_str(), + data ); + } + + mTarget->setDataField( mField->pFieldname, mFieldArrayIndex, data ); + + // give the target a chance to validate + mTarget->inspectPostApply(); + } + + // Force our edit to update + updateValue(); +} + +StringTableEntry GuiInspectorField::getData() +{ + if( mField == NULL || mTarget == NULL ) + return StringTable->insert( "" ); + + return StringTable->insert( mTarget->getDataField( mField->pFieldname, mFieldArrayIndex ) ); +} + +void GuiInspectorField::setInspectorField( AbstractClassRep::Field *field, StringTableEntry caption, const char*arrayIndex ) +{ + mField = field; + + if ( arrayIndex != NULL ) + mFieldArrayIndex = StringTable->insert( arrayIndex ); + + if ( !caption || !caption[0] ) + mCaption = getFieldName(); + else + mCaption = caption; +} + + +StringTableEntry GuiInspectorField::getFieldName() +{ + // Sanity + if ( mField == NULL ) + return StringTable->insert( "" ); + + // Array element? + if( mFieldArrayIndex != NULL ) + { + S32 frameTempSize = dStrlen( mField->pFieldname ) + 32; + FrameTemp valCopy( frameTempSize ); + dSprintf( (char *)valCopy, frameTempSize, "%s[%s]", mField->pFieldname, mFieldArrayIndex ); + + // Return formatted element + return StringTable->insert( valCopy ); + } + + // Plain field name. + return StringTable->insert( mField->pFieldname ); +}; + +GuiControl* GuiInspectorField::constructEditControl() +{ + GuiControl* retCtrl = new GuiTextEditCtrl(); + + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + _registerEditControl( retCtrl ); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() ); + retCtrl->setField("AltCommand", szBuffer ); + retCtrl->setField("Validate", szBuffer ); + + return retCtrl; +} + +void GuiInspectorField::setInspectorProfile() +{ + GuiControlProfile *profile = NULL; + if ( Sim::findObject( "GuiInspectorFieldProfile", profile ) ) + setControlProfile( profile ); + + // Use the same trick as GuiControl::onAdd + // to try finding a GuiControlProfile with the name scheme + // 'InspectorTypeClassName' + 'Profile' + +// String name = getClassName(); +// name += "Profile"; +// +// GuiControlProfile *profile = NULL; +// Sim::findObject( name, profile ); +// +// if ( !profile ) +// Sim::findObject( "GuiInspectorFieldProfile", profile ); +// +// if ( !profile ) +// Con::errorf( "GuiInspectorField::setInspectorProfile - no profile found! Tried (%s) and (GuiInspectorFieldProfile) !", name.c_str() ); +// else +// setControlProfile( profile ); +} + +void GuiInspectorField::setValue( StringTableEntry newValue ) +{ + GuiTextEditCtrl *ctrl = dynamic_cast( mEdit ); + if( ctrl != NULL ) + ctrl->setText( newValue ); +} + +bool GuiInspectorField::updateRects() +{ + S32 dividerPos, dividerMargin; + mInspector->getDivider( dividerPos, dividerMargin ); + + Point2I fieldExtent = getExtent(); + Point2I fieldPos = getPosition(); + + S32 editWidth = dividerPos - dividerMargin; + + mEditCtrlRect.set( fieldExtent.x - dividerPos + dividerMargin, 1, editWidth, fieldExtent.y - 1 ); + mCaptionRect.set( 0, 0, fieldExtent.x - dividerPos - dividerMargin, fieldExtent.y ); + + if ( !mEdit ) + return false; + + return mEdit->resize( mEditCtrlRect.point, mEditCtrlRect.extent ); +} + +void GuiInspectorField::updateValue() +{ + if ( mTarget && mField ) + setValue( StringTable->insert( mTarget->getDataField( mField->pFieldname, mFieldArrayIndex ), + mField->type == TypeCaseString || mField->type == TypeRealString ? true : false ) ); +} + +void GuiInspectorField::setHLEnabled( bool enabled ) +{ + mHighlighted = enabled; + if ( mHighlighted ) + { + if ( mEdit && !mEdit->isFirstResponder() ) + { + mEdit->setFirstResponder(); + GuiTextEditCtrl *edit = dynamic_cast( mEdit ); + if ( edit ) + { + mouseUnlock(); + edit->mouseLock(); + edit->setCursorPos(0); + } + } + _executeSelectedCallback(); + } +} + +void GuiInspectorField::_executeSelectedCallback() +{ + if( mField ) + { + if ( mField->pFieldDocs && mField->pFieldDocs[0] ) + Con::executef( mInspector, "onFieldSelected", mField->pFieldname, ConsoleBaseType::getType(mField->type)->getTypeName(), mField->pFieldDocs ); + else + Con::executef( mInspector, "onFieldSelected", mField->pFieldname, ConsoleBaseType::getType(mField->type)->getTypeName() ); + } +} + + +void GuiInspectorField::_registerEditControl( GuiControl *ctrl ) +{ + if ( !mTarget ) + return; + + char szName[512]; + dSprintf( szName, 512, "IE_%s_%d_%s_Field", ctrl->getClassName(), mTarget->getId(),mCaption); + + // Register the object + ctrl->registerObject( szName ); +} + +ConsoleMethod( GuiInspectorField, apply, void, 3,3, "apply(newValue);" ) +{ + object->setData( argv[2] ); +} + +ConsoleMethod( GuiInspectorField, getData, const char*, 2, 2, "getData();" ) +{ + return object->getData(); +} diff --git a/gui/editor/inspector/field.h b/gui/editor/inspector/field.h new file mode 100644 index 0000000..7edef1a --- /dev/null +++ b/gui/editor/inspector/field.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_FIELD_H_ +#define _GUI_INSPECTOR_FIELD_H_ + +#include "gui/core/guiCanvas.h" +#include "gui/shiny/guiTickCtrl.h" +#include "gui/controls/guiTextEditCtrl.h" +#include "gui/buttons/guiBitmapButtonCtrl.h" +#include "gui/controls/guiPopUpCtrl.h" + +#include "gui/containers/guiRolloutCtrl.h" + +class GuiInspectorGroup; +class GuiInspector; + + +class GuiInspectorField : public GuiControl +{ + friend class GuiInspectorGroup; + +public: + + typedef GuiControl Parent; + + + GuiInspectorField( GuiInspector *inspector, GuiInspectorGroup* parent, SimObjectPtr target, AbstractClassRep::Field* field ); + GuiInspectorField(); + virtual ~GuiInspectorField(); + + DECLARE_CONOBJECT(GuiInspectorField); + DECLARE_CATEGORY( "Gui Editor" ); + + // ConsoleObject + virtual bool onAdd(); + + // GuiControl + virtual bool resize(const Point2I &newPosition, const Point2I &newExtent); + virtual void onRender(Point2I offset, const RectI &updateRect); + virtual void setFirstResponder( GuiControl *firstResponder ); + virtual void onMouseDown( const GuiEvent &event ); + + // GuiInspectorField + virtual void init( GuiInspector *inspector, GuiInspectorGroup *group, SimObjectPtr target ); + virtual void setInspectorField( AbstractClassRep::Field *field, + StringTableEntry caption = NULL, + const char *arrayIndex = NULL ); + + virtual GuiControl* constructEditControl(); + + /// Sets the GuiControlProfile + virtual void setInspectorProfile(); + + /// Sets this control's caption text, usually set within setInspectorField, + /// this is exposed in case someone wants to override the normal caption. + virtual void setCaption( StringTableEntry caption ) { mCaption = caption; } + + /// Returns pointer to this InspectorField's edit ctrl. + virtual GuiControl* getEditCtrl() { return mEdit; } + + /// Sets the value of this GuiInspectorField (not the actual field) + /// This means the EditCtrl unless overridden. + virtual void setValue( StringTableEntry newValue ); + + /// Get the currently value of this control (not the actual field) + virtual const char* getValue() { return NULL; } + + /// Update this controls value to reflect that of the inspected field. + virtual void updateValue(); + + virtual StringTableEntry getFieldName(); + + /// Called from within setData to allow child classes + /// to perform their own verification. + virtual bool verifyData( StringTableEntry data ) { return true; } + + /// Set value of the field we are inspecting + virtual void setData( StringTableEntry data ); + + /// Get value of the field we are inspecting + virtual StringTableEntry getData(); + + /// Update the inspected field to match the value of this control. + virtual void updateData() {}; + + virtual bool updateRects(); + + virtual void setHLEnabled( bool enabled ); + +protected: + + virtual void _registerEditControl( GuiControl *ctrl ); + virtual void _executeSelectedCallback(); + +protected: + + StringTableEntry mCaption; + GuiInspectorGroup* mParent; + GuiInspector* mInspector; + SimObjectPtr mTarget; + AbstractClassRep::Field* mField; + StringTableEntry mFieldArrayIndex; + GuiControl* mEdit; + RectI mCaptionRect; + RectI mEditCtrlRect; + bool mHighlighted; +}; + +#endif // _GUI_INSPECTOR_FIELD_H_ diff --git a/gui/editor/inspector/group.cpp b/gui/editor/inspector/group.cpp new file mode 100644 index 0000000..a4e8508 --- /dev/null +++ b/gui/editor/inspector/group.cpp @@ -0,0 +1,492 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "gui/editor/guiInspector.h" +#include "gui/editor/inspector/group.h" +#include "gui/editor/inspector/dynamicField.h" +#include "gui/editor/inspector/datablockField.h" +#include "gui/buttons/guiIconButtonCtrl.h" + +//----------------------------------------------------------------------------- +// GuiInspectorGroup +//----------------------------------------------------------------------------- +// +// The GuiInspectorGroup control is a helper control that the inspector +// makes use of which houses a collapsible pane type control for separating +// inspected objects fields into groups. The content of the inspector is +// made up of zero or more GuiInspectorGroup controls inside of a GuiStackControl +// +// +// +IMPLEMENT_CONOBJECT(GuiInspectorGroup); + +GuiInspectorGroup::GuiInspectorGroup() + : mTarget( NULL ), + mParent( NULL ), + mStack(NULL) +{ + setBounds(0,0,200,20); + + mChildren.clear(); + + mCanSave = false; + + // Make sure we receive our ticks. + setProcessTicks(); + mMargin.set(0,0,5,0); +} + +GuiInspectorGroup::GuiInspectorGroup( SimObjectPtr target, + StringTableEntry groupName, + SimObjectPtr parent ) + : mTarget( target ), + mParent( parent ), + mStack(NULL) +{ + + setBounds(0,0,200,20); + + mCaption = StringTable->insert( groupName ); + mCanSave = false; + + mChildren.clear(); + mMargin.set(0,0,4,0); +} + +GuiInspectorGroup::~GuiInspectorGroup() +{ +} + +//----------------------------------------------------------------------------- +// Persistence +//----------------------------------------------------------------------------- +void GuiInspectorGroup::initPersistFields() +{ + addField("Caption", TypeString, Offset(mCaption, GuiInspectorGroup)); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// Scene Events +//----------------------------------------------------------------------------- +bool GuiInspectorGroup::onAdd() +{ + setDataField( StringTable->insert("profile"), NULL, "GuiInspectorGroupProfile" ); + + if( !Parent::onAdd() ) + return false; + + // Create our inner controls. Allow subclasses to provide other content. + if(!createContent()) + return false; + + inspectGroup(); + + return true; +} + +bool GuiInspectorGroup::createContent() +{ + // Create our field stack control + mStack = new GuiStackControl(); + + // Prefer GuiTransperantProfile for the stack. + mStack->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorStackProfile" ); + if( !mStack->registerObject() ) + { + SAFE_DELETE( mStack ); + return false; + } + + addObject( mStack ); + mStack->setField( "padding", "0" ); + return true; +} + +//----------------------------------------------------------------------------- +// Control Sizing Animation Functions +//----------------------------------------------------------------------------- +void GuiInspectorGroup::animateToContents() +{ + calculateHeights(); + if(size() > 0) + animateTo( mExpanded.extent.y ); + else + animateTo( mHeader.extent.y ); +} + +GuiInspectorField* GuiInspectorGroup::constructField( S32 fieldType ) +{ + // See if we can construct a field of this type + ConsoleBaseType *cbt = ConsoleBaseType::getType(fieldType); + if( !cbt ) + return NULL; + + // Alright, is it a datablock? + if(cbt->isDatablock()) + { + // Default to GameBaseData + StringTableEntry typeClassName = cbt->getTypeClassName(); + + if (mTarget && !dStricmp(typeClassName, "GameBaseData")) + { + // Try and setup the classname based on the object type + char className[256]; + dSprintf(className,256,"%sData",mTarget->getClassName()); + // Walk the ACR list and find a matching class if any. + AbstractClassRep *walk = AbstractClassRep::getClassList(); + while(walk) + { + if(!dStricmp(walk->getClassName(), className)) + break; + + walk = walk->getNextClass(); + } + + // We found a valid class + if (walk) + typeClassName = walk->getClassName(); + + } + + + GuiInspectorDatablockField *dbFieldClass = new GuiInspectorDatablockField( typeClassName ); + if( dbFieldClass != NULL ) + { + // return our new datablock field with correct datablock type enumeration info + return dbFieldClass; + } + } + + // Nope, not a datablock. So maybe it has a valid inspector field override we can use? + if(!cbt->getInspectorFieldType()) + // Nothing, so bail. + return NULL; + + // Otherwise try to make it! + ConsoleObject *co = create(cbt->getInspectorFieldType()); + GuiInspectorField *gif = dynamic_cast(co); + + if(!gif) + { + // Wasn't appropriate type, bail. + delete co; + return NULL; + } + + return gif; +} + +GuiInspectorField *GuiInspectorGroup::findField( const char *fieldName ) +{ + // If we don't have any field children we can't very well find one then can we? + if( mChildren.empty() ) + return NULL; + + Vector::iterator i = mChildren.begin(); + + for( ; i != mChildren.end(); i++ ) + { + if( (*i)->getFieldName() != NULL && dStricmp( (*i)->getFieldName(), fieldName ) == 0 ) + return (*i); + } + + return NULL; +} + +void GuiInspectorGroup::clearFields() +{ + // Deallocates all field related controls. + mStack->clear(); + + // Then just cleanup our vectors which also point to children + // that we keep for our own convenience. + mArrayCtrls.clear(); + mChildren.clear(); +} + +bool GuiInspectorGroup::inspectGroup() +{ + // We can't inspect a group without a target! + if( !mTarget ) + return false; + + // to prevent crazy resizing, we'll just freeze our stack for a sec.. + mStack->freeze(true); + + bool bNoGroup = false; + + // Un-grouped fields are all sorted into the 'general' group + if ( dStricmp( mCaption, "General" ) == 0 ) + bNoGroup = true; + + AbstractClassRep::FieldList &fieldList = mTarget->getModifiableFieldList(); + AbstractClassRep::FieldList::iterator itr; + + bool bGrabItems = false; + bool bNewItems = false; + bool bMakingArray = false; + GuiStackControl *pArrayStack = NULL; + GuiRolloutCtrl *pArrayRollout = NULL; + + // Just delete all fields and recreate them (like the dynamicGroup) + // because that makes creating controls for array fields a lot easier + clearFields(); + + for ( itr = fieldList.begin(); itr != fieldList.end(); itr++ ) + { + if( itr->type == AbstractClassRep::StartGroupFieldType ) + { + // If we're dealing with general fields, always set grabItems to true (to skip them) + if( bNoGroup == true ) + bGrabItems = true; + else if( itr->pGroupname != NULL && dStricmp( itr->pGroupname, mCaption ) == 0 ) + bGrabItems = true; + continue; + } + else if ( itr->type == AbstractClassRep::EndGroupFieldType ) + { + // If we're dealing with general fields, always set grabItems to false (to grab them) + if( bNoGroup == true ) + bGrabItems = false; + else if( itr->pGroupname != NULL && dStricmp( itr->pGroupname, mCaption ) == 0 ) + bGrabItems = false; + continue; + } + + if( ( bGrabItems == true || ( bNoGroup == true && bGrabItems == false ) ) && itr->type != AbstractClassRep::DeprecatedFieldType ) + { + if( bNoGroup == true && bGrabItems == true ) + continue; + + if ( itr->type == AbstractClassRep::StartArrayFieldType ) + { + // Starting an array... + // Create a rollout for the Array, give it the array's name. + GuiRolloutCtrl *arrayRollout = new GuiRolloutCtrl(); + GuiControlProfile *arrayRolloutProfile = dynamic_cast( Sim::findObject( "GuiInspectorRolloutProfile0" ) ); + + arrayRollout->setControlProfile(arrayRolloutProfile); + //arrayRollout->mCaption = StringTable->insert( String::ToString( "%s (%i)", itr->pGroupname, itr->elementCount ) ); + arrayRollout->mCaption = StringTable->insert( itr->pGroupname ); + arrayRollout->mMargin.set( 14, 0, 0, 0 ); + arrayRollout->registerObject(); + + GuiStackControl *arrayStack = new GuiStackControl(); + arrayStack->registerObject(); + arrayStack->freeze(true); + arrayRollout->addObject(arrayStack); + + // Allocate a rollout for each element-count in the array + // Give it the element count name. + for ( U32 i = 0; i < itr->elementCount; i++ ) + { + GuiRolloutCtrl *elementRollout = new GuiRolloutCtrl(); + GuiControlProfile *elementRolloutProfile = dynamic_cast( Sim::findObject( "GuiInspectorRolloutProfile0" ) ); + + char buf[256]; + dSprintf( buf, 256, " [%i]", i ); + + elementRollout->setControlProfile(elementRolloutProfile); + elementRollout->mCaption = StringTable->insert(buf); + elementRollout->mMargin.set( 14, 0, 0, 0 ); + elementRollout->registerObject(); + + GuiStackControl *elementStack = new GuiStackControl(); + elementStack->registerObject(); + elementRollout->addObject(elementStack); + elementRollout->instantCollapse(); + + arrayStack->addObject( elementRollout ); + } + + pArrayRollout = arrayRollout; + pArrayStack = arrayStack; + arrayStack->freeze(false); + pArrayRollout->instantCollapse(); + mStack->addObject(arrayRollout); + + bMakingArray = true; + continue; + } + else if ( itr->type == AbstractClassRep::EndArrayFieldType ) + { + bMakingArray = false; + continue; + } + + if ( bMakingArray ) + { + // Add a GuiInspectorField for this field, + // for every element in the array... + for ( U32 i = 0; i < pArrayStack->size(); i++ ) + { + FrameTemp intToStr( 64 ); + dSprintf( intToStr, 64, "%d", i ); + + // The array stack should have a rollout for each element + // as children... + GuiRolloutCtrl *pRollout = dynamic_cast(pArrayStack->at(i)); + // And the each of those rollouts should have a stack for + // fields... + GuiStackControl *pStack = dynamic_cast(pRollout->at(0)); + + // And we add a new GuiInspectorField to each of those stacks... + GuiInspectorField *field = constructField( itr->type ); + if ( field == NULL ) + field = new GuiInspectorField(); + + field->init( mParent, this, mTarget ); + StringTableEntry caption = StringTable->insert( itr->pFieldname ); + field->setInspectorField( itr, caption, intToStr ); + + if( field->registerObject() ) + { + mChildren.push_back( field ); + pStack->addObject( field ); + } + else + delete field; + } + + continue; + } + + // This is weird, but it should work for now. - JDD + // We are going to check to see if this item is an array + // if so, we're going to construct a field for each array element + if( itr->elementCount > 1 ) + { + // Make a rollout control for this array + // + GuiRolloutCtrl *rollout = new GuiRolloutCtrl(); + rollout->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorRolloutProfile0" ); + rollout->mCaption = StringTable->insert(String::ToString( "%s (%i)", itr->pFieldname, itr->elementCount)); + rollout->mMargin.set( 14, 0, 0, 0 ); + rollout->registerObject(); + mArrayCtrls.push_back(rollout); + + // Put a stack control within the rollout + // + GuiStackControl *stack = new GuiStackControl(); + stack->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorStackProfile" ); + stack->registerObject(); + stack->freeze(true); + rollout->addObject(stack); + + mStack->addObject(rollout); + + // Create each field and add it to the stack. + // + for (S32 nI = 0; nI < itr->elementCount; nI++) + { + FrameTemp intToStr( 64 ); + dSprintf( intToStr, 64, "%d", nI ); + + // Construct proper ValueName[nI] format which is "ValueName0" for index 0, etc. + + String fieldName = String::ToString( "%s%d", itr->pFieldname, nI ); + + // If the field already exists, just update it + GuiInspectorField *field = findField( fieldName ); + if( field != NULL ) + { + field->updateValue(); + continue; + } + + bNewItems = true; + + field = constructField( itr->type ); + if ( field == NULL ) + field = new GuiInspectorField(); + + field->init( mParent, this, mTarget ); + StringTableEntry caption = StringTable->insert( String::ToString(" [%i]",nI) ); + field->setInspectorField( itr, caption, intToStr ); + + if ( field->registerObject() ) + { + mChildren.push_back( field ); + stack->addObject( field ); + } + else + delete field; + } + + stack->freeze(false); + stack->updatePanes(); + rollout->instantCollapse(); + } + else + { + // If the field already exists, just update it + GuiInspectorField *field = findField( itr->pFieldname ); + if ( field != NULL ) + { + field->updateValue(); + continue; + } + + bNewItems = true; + + field = constructField( itr->type ); + if ( field == NULL ) + field = new GuiInspectorField(); + + field->init( mParent, this, mTarget ); + field->setInspectorField( itr ); + + if ( field->registerObject() ) + { + mChildren.push_back( field ); + mStack->addObject( field ); + } + else + delete field; + } + } + } + mStack->freeze(false); + mStack->updatePanes(); + + // If we've no new items, there's no need to resize anything! + if( bNewItems == false && !mChildren.empty() ) + return true; + + sizeToContents(); + + setUpdate(); + + return true; +} + +bool GuiInspectorGroup::updateFieldValue( StringTableEntry fieldName, StringTableEntry arrayIdx ) +{ + // Check if we contain a field of this name, + // if so update its value and return true. + Vector::iterator iter = mChildren.begin(); + + for( ; iter != mChildren.end(); iter++ ) + { + GuiInspectorField *field = (*iter); + if ( field->mField && + field->mField->pFieldname == fieldName && + field->mFieldArrayIndex == arrayIdx ) + { + field->updateValue(); + return true; + } + } + + return false; +} + +void GuiInspectorGroup::updateAllFields() +{ + Vector::iterator iter = mChildren.begin(); + for( ; iter != mChildren.end(); iter++ ) + (*iter)->updateValue(); +} diff --git a/gui/editor/inspector/group.h b/gui/editor/inspector/group.h new file mode 100644 index 0000000..cdaab7a --- /dev/null +++ b/gui/editor/inspector/group.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_GROUP_H_ +#define _GUI_INSPECTOR_GROUP_H_ + +#include "gui/core/guiCanvas.h" +#include "gui/controls/guiTextEditCtrl.h" +#include "gui/buttons/guiBitmapButtonCtrl.h" +#include "gui/containers/guiRolloutCtrl.h" + +// Forward refs +class GuiInspector; +class GuiInspectorField; + +class GuiInspectorGroup : public GuiRolloutCtrl +{ +private: + typedef GuiRolloutCtrl Parent; +public: + // Members + SimObjectPtr mTarget; + SimObjectPtr mParent; + Vector mChildren; + GuiStackControl* mStack; + Vector mArrayCtrls; + + // Constructor/Destructor/Conobject Declaration + GuiInspectorGroup(); + GuiInspectorGroup( SimObjectPtr target, StringTableEntry groupName, SimObjectPtr parent ); + virtual ~GuiInspectorGroup(); + + DECLARE_CONOBJECT(GuiInspectorGroup); + DECLARE_CATEGORY( "Gui Editor" ); + + // Persistence ( Inspector Exposed Fields ) + static void initPersistFields(); + + virtual GuiInspectorField* constructField( S32 fieldType ); + virtual GuiInspectorField* findField( const char *fieldName ); + + // Publicly Accessible Information about this group + StringTableEntry getGroupName() { return mCaption; }; + SimObjectPtr getGroupTarget() { return mTarget; }; + SimObjectPtr getContentCtrl() { return mParent; }; + + bool onAdd(); + virtual bool inspectGroup(); + + virtual void animateToContents(); + + void clearFields(); + + virtual bool updateFieldValue( StringTableEntry fieldName, const char *arrayIdx ); + virtual void updateAllFields(); + +protected: + // overridable method that creates our inner controls. + virtual bool createContent(); +}; + +#endif // _GUI_INSPECTOR_GROUP_H_ diff --git a/gui/editor/inspector/variableField.cpp b/gui/editor/inspector/variableField.cpp new file mode 100644 index 0000000..d492853 --- /dev/null +++ b/gui/editor/inspector/variableField.cpp @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/editor/inspector/variableField.h" + +#include "gui/buttons/guiIconButtonCtrl.h" +#include "gui/editor/guiInspector.h" +#include "core/util/safeDelete.h" +#include "gfx/gfxDrawUtil.h" + +//----------------------------------------------------------------------------- +// GuiInspectorVariableField +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(GuiInspectorVariableField); + + +GuiInspectorVariableField::GuiInspectorVariableField() +{ +} + +GuiInspectorVariableField::~GuiInspectorVariableField() +{ +} + +bool GuiInspectorVariableField::onAdd() +{ + setInspectorProfile(); + + // Hack: skip our immediate parent + if ( !Parent::Parent::onAdd() ) + return false; + + { + GuiTextEditCtrl *edit = new GuiTextEditCtrl(); + + edit->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + edit->registerObject(); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), edit->getId() ); + edit->setField("AltCommand", szBuffer ); + edit->setField("Validate", szBuffer ); + + mEdit = edit; + } + + setBounds(0,0,100,18); + + // Add our edit as a child + addObject( mEdit ); + + // Calculate Caption and EditCtrl Rects + updateRects(); + + // Force our editField to set it's value + updateValue(); + + return true; +} + +void GuiInspectorVariableField::setData( StringTableEntry data ) +{ + if ( !mCaption || mCaption[0] == 0 ) + return; + + Con::setVariable( mCaption, data ); + + // Force our edit to update + updateValue(); +} + +StringTableEntry GuiInspectorVariableField::getData() +{ + if ( !mCaption || mCaption[0] == 0 ) + return StringTable->insert( "" ); + + return StringTable->insert( Con::getVariable( mCaption ) ); +} + +void GuiInspectorVariableField::setValue( StringTableEntry newValue ) +{ + GuiTextEditCtrl *ctrl = dynamic_cast( mEdit ); + if( ctrl != NULL ) + ctrl->setText( newValue ); +} + +void GuiInspectorVariableField::updateValue() +{ + if ( !mCaption || mCaption[0] == 0 ) + return; + + setValue( getData() ); +} \ No newline at end of file diff --git a/gui/editor/inspector/variableField.h b/gui/editor/inspector/variableField.h new file mode 100644 index 0000000..22a2399 --- /dev/null +++ b/gui/editor/inspector/variableField.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_VARIABLEFIELD_H_ +#define _GUI_INSPECTOR_VARIABLEFIELD_H_ + +#ifndef _GUI_INSPECTOR_FIELD_H_ +#include "gui/editor/inspector/field.h" +#endif + +class GuiInspectorGroup; +class GuiInspector; + + +class GuiInspectorVariableField : public GuiInspectorField +{ + friend class GuiInspectorField; + +public: + + typedef GuiInspectorField Parent; + + GuiInspectorVariableField(); + virtual ~GuiInspectorVariableField(); + + DECLARE_CONOBJECT( GuiInspectorVariableField ); + DECLARE_CATEGORY( "Gui Editor" ); + + virtual bool onAdd(); + + + virtual void setValue( StringTableEntry newValue ); + virtual const char* getValue() { return NULL; } + virtual void updateValue(); + virtual void setData( StringTableEntry data ); + virtual StringTableEntry getData(); + virtual void updateData() {}; + +protected: + +}; + +#endif // _GUI_INSPECTOR_VARIABLEFIELD_H_ diff --git a/gui/editor/inspector/variableGroup.cpp b/gui/editor/inspector/variableGroup.cpp new file mode 100644 index 0000000..5de0c1f --- /dev/null +++ b/gui/editor/inspector/variableGroup.cpp @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "platform/platform.h" +#include "gui/editor/inspector/variableGroup.h" +#include "gui/editor/inspector/variableField.h" +#include "gui/editor/guiInspector.h" +#include "gui/buttons/guiIconButtonCtrl.h" +#include "console/consoleInternal.h" + +extern ExprEvalState gEvalState; + +//----------------------------------------------------------------------------- +// GuiInspectorVariableGroup +//----------------------------------------------------------------------------- +// +// +IMPLEMENT_CONOBJECT(GuiInspectorVariableGroup); + +GuiInspectorVariableGroup::GuiInspectorVariableGroup() +{ +} + +GuiInspectorVariableGroup::~GuiInspectorVariableGroup() +{ +} + +GuiInspectorField* GuiInspectorVariableGroup::constructField( S32 fieldType ) +{ + return NULL; +} + +bool GuiInspectorVariableGroup::inspectGroup() +{ + // to prevent crazy resizing, we'll just freeze our stack for a sec.. + mStack->freeze(true); + + clearFields(); + + Vector names; + + gEvalState.globalVars.exportVariables( mSearchString, &names, NULL ); + + bool bNewItems = false; + + for ( U32 i = 0; i < names.size(); i++ ) + { + const String &varName = names[i]; + + // If the field already exists, just update it + GuiInspectorVariableField *field = dynamic_cast( findField( varName ) ); + if ( field != NULL ) + { + field->updateValue(); + continue; + } + + bNewItems = true; + + field = new GuiInspectorVariableField(); + field->init( mParent, this, mTarget ); + field->setInspectorField( NULL, StringTable->insert( varName ) ); + + if ( field->registerObject() ) + { + mChildren.push_back( field ); + mStack->addObject( field ); + } + else + delete field; + } + + mStack->freeze(false); + mStack->updatePanes(); + + // If we've no new items, there's no need to resize anything! + if( bNewItems == false && !mChildren.empty() ) + return true; + + sizeToContents(); + + setUpdate(); + + return true; +} diff --git a/gui/editor/inspector/variableGroup.h b/gui/editor/inspector/variableGroup.h new file mode 100644 index 0000000..c0cd67b --- /dev/null +++ b/gui/editor/inspector/variableGroup.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_INSPECTOR_VARIABLEGROUP_H_ +#define _GUI_INSPECTOR_VARIABLEGROUP_H_ + +#ifndef _GUI_INSPECTOR_GROUP_H_ +#include "gui/editor/inspector/group.h" +#endif + +// Forward refs +class GuiInspector; +class GuiInspectorField; + +class GuiInspectorVariableGroup : public GuiInspectorGroup +{ +public: + + typedef GuiInspectorGroup Parent; + + String mSearchString; + + GuiInspectorVariableGroup(); + virtual ~GuiInspectorVariableGroup(); + + DECLARE_CONOBJECT(GuiInspectorVariableGroup); + DECLARE_CATEGORY( "Gui Editor" ); + + virtual GuiInspectorField* constructField( S32 fieldType ); + + virtual bool inspectGroup(); + +protected: +}; + +#endif // _GUI_INSPECTOR_VARIABLEGROUP_H_ diff --git a/gui/editor/inspector/variableInspector.cpp b/gui/editor/inspector/variableInspector.cpp new file mode 100644 index 0000000..1722b08 --- /dev/null +++ b/gui/editor/inspector/variableInspector.cpp @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/editor/inspector/variableInspector.h" +#include "gui/editor/inspector/variableGroup.h" + +GuiVariableInspector::GuiVariableInspector() +{ +} + +GuiVariableInspector::~GuiVariableInspector() +{ +} + +IMPLEMENT_CONOBJECT(GuiVariableInspector); + + +void GuiVariableInspector::loadVars( String searchStr ) +{ + clearGroups(); + + GuiInspectorVariableGroup *group = new GuiInspectorVariableGroup(); + + group->mHideHeader = true; + group->mCanCollapse = false; + group->mParent = this; + group->mCaption = StringTable->insert( "Global Variables" ); + group->mSearchString = searchStr; + + if( group != NULL ) + { + group->registerObject(); + mGroups.push_back( group ); + addObject( group ); + } + + //group->inspectGroup(); +} + +ConsoleMethod( GuiVariableInspector, loadVars, void, 3, 3, "loadVars( searchString )" ) +{ + object->loadVars( argv[2] ); +} \ No newline at end of file diff --git a/gui/editor/inspector/variableInspector.h b/gui/editor/inspector/variableInspector.h new file mode 100644 index 0000000..c786aad --- /dev/null +++ b/gui/editor/inspector/variableInspector.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUI_VARIABLEINSPECTOR_H_ +#define _GUI_VARIABLEINSPECTOR_H_ + +#ifndef _GUI_INSPECTOR_H_ +#include "gui/editor/guiInspector.h" +#endif + + +class GuiVariableInspector : public GuiInspector +{ + typedef GuiInspector Parent; + +public: + + GuiVariableInspector(); + virtual ~GuiVariableInspector(); + + DECLARE_CONOBJECT( GuiVariableInspector ); + DECLARE_CATEGORY( "Gui Editor" ); + + virtual void inspectObject( SimObject *object ) {} + + virtual void loadVars( String searchString ); + + +protected: + +}; + +#endif // _GUI_VARIABLEINSPECTOR_H_ \ No newline at end of file diff --git a/gui/game/guiAviBitmapCtrl.cpp b/gui/game/guiAviBitmapCtrl.cpp new file mode 100644 index 0000000..d833bc6 --- /dev/null +++ b/gui/game/guiAviBitmapCtrl.cpp @@ -0,0 +1,1373 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/core/guiControl.h" + +// This control currently disabled until streaming is +// added back into the audio library + +#if defined(TORQUE_OS_LINUX) || defined(TORQUE_OS_OPENBSD) +#if DEDICATED +#define ENABLE_AVI_GUI 0 +#define ENABLE_MPG_GUI 0 +#else +#define ENABLE_AVI_GUI 0 +#define ENABLE_MPG_GUI 0 // was 1 +#endif +#else +/* Windows */ +#define ENABLE_AVI_GUI 0 +#define ENABLE_MPG_GUI 0 +#endif + +#if ENABLE_AVI_GUI || ENABLE_MPG_GUI +#include "sfx/sfx.h" +#if ENABLE_AVI_GUI +#include +#include +#endif +#if ENABLE_MPG_GUI +#include "smpeg.h" +#endif +#endif /* Either AVI or MPG GUI */ + + +#include "gui/game/guiAviBitmapCtrl.h" +#include "console/consoleTypes.h" +//#include "gfx/gfxDevice.h" + +//---------------------------------------------------------------------------- + + +#if !ENABLE_AVI_GUI || !ENABLE_MPG_GUI +// Version for Loki which will compile (and do nothing)- +IMPLEMENT_CONOBJECT(GuiAviBitmapCtrl); +GuiAviBitmapCtrl::GuiAviBitmapCtrl() +{ + mDone = true; +} +GuiAviBitmapCtrl::~GuiAviBitmapCtrl() +{ +} + +void GuiAviBitmapCtrl::initPersistFields() +{ + addGroup("GuiAviBitmapCtrl"); + addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl)); + endGroup("GuiAviBitmapCtrl"); + Parent::initPersistFields(); +} +#else + +// Code common to both AVI and MPG players + +#define ALIGNULONG(bytes) ((((bytes) + 3) / 4) * 4) + +// Error codes +#define MOVERR_OK 0 +#define MOVERR_NOVIDEOSTREAM -1 +#define MOVERR_PLAYING -4 + + +//---------------------------------------------------------------------------- +ConsoleMethod( GuiAviBitmapCtrl, setFilename, void, 3, 3, "(string filename)") +{ + object->setFilename(argv[2]); +} + +//---------------------------------------------------------------------------- +ConsoleMethod( GuiAviBitmapCtrl, play, void, 2, 2, "Start playback.") +{ + object->movieStart(); +} + +//---------------------------------------------------------------------------- +ConsoleMethod( GuiAviBitmapCtrl, stop, void, 2, 2, "Stop playback.") +{ + object->movieStop(); +} + +//----------------------------------------------------------------------------- +// Audio Operations + +bool GuiAviBitmapCtrl::sndOpen() +{ + mAudioLatency = 0; + + #if 0 + // streaming disabled in this build + + // Open up the audio. Taking the easy way and using two separate streams. + // Don't treat as error if it doesn't open- + dSprintf(fileBuffer, sizeof(fileBuffer), "%s", mWavFilename); + Audio::Description desc; + desc.mIs3D = false; + desc.mVolume = 1.0f; + desc.mIsLooping = false; + desc.mType = Audio::MusicAudioType; + mWavHandle = alxCreateSource(&desc, fileBuffer, 0); + #endif + + mWavHandle = NULL_AUDIOHANDLE; + // alxGetContexti(ALC_BUFFER_LATENCY, &mAudioLatency); + + return (mWavHandle != NULL_AUDIOHANDLE); +} + +void GuiAviBitmapCtrl::sndStart() +{ + if (mWavHandle != NULL_AUDIOHANDLE) + alxPlay(mWavHandle); +} + +void GuiAviBitmapCtrl::sndStop() +{ + if (mWavHandle != NULL_AUDIOHANDLE) + { + alxStop(mWavHandle); + mWavHandle = NULL_AUDIOHANDLE; + } +} + + +#if ENABLE_AVI_GUI + +#define FOURCC_iv50 mmioFOURCC('i','v','5','0') +#define FOURCC_IV50 mmioFOURCC('I','V','5','0') + +// Error codes +#define VIDSERR_OK 0 +#define VIDSERR_NOVIDEO -20 +#define VIDSERR_VCM -21 +#define VIDSERR_NOTSTARTED -23 +#define VIDSERR_READ -24 + +#define VCMERR_OK 0 +#define VCMERR_INTERNAL -1 + +//---------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiAviBitmapCtrl); + +GuiAviBitmapCtrl::GuiAviBitmapCtrl() +{ + mAviFilename = StringTable->insert(""); + mTextureHandles = NULL; + + mPFile = NULL; + mPAviVideo = NULL; + mBPlaying = false; + mDone = false; + mLetterBox = false; + mTimePlayStart = -1; + mPVBuf = NULL; + mPBuf = NULL; + mPBiSrc = mPBiDst = NULL; + mHic = NULL; + mFccHandler = 0; + mSwapRB = true; + mSpeed = 1.0; + mWavHandle = NULL_AUDIOHANDLE; + + AVIFileInit(); +} + +//---------------------------------------------------------------------------- +GuiAviBitmapCtrl::~GuiAviBitmapCtrl() +{ + vcmClose(); + AVIFileExit(); +} + +//----------------------------------------------------------------------------- +// AVI File Operations + +// Open a file +// +S32 GuiAviBitmapCtrl::fileOpen() +{ + S32 rval; + if (!dStrcmp(mAviFilename,"")) + return MOVERR_NOVIDEOSTREAM; + + rval = AVIFileOpen(&mPFile, mAviFilename, OF_SHARE_DENY_WRITE, 0); + if (rval) + { + fileClose(); + return rval; + } + + return MOVERR_OK; +} + +// Close a file +// +S32 GuiAviBitmapCtrl::fileClose () +{ + if (mPFile) + { + AVIFileRelease(mPFile); + mPFile = NULL; + } + return MOVERR_OK; +} + +//----------------------------------------------------------------------------- +// Movie Operations + +// A movie must have a video stream +// Return codes should be properly done but for now, 0 is no error +// while !0 is error unless otherwise noted + +// Open a movie (video stream) from an open file +// +S32 GuiAviBitmapCtrl::movieOpen() +{ + S32 rval; + + // Open first video stream found + // Note that we don't handle multiple video streams + rval = AVIFileGetStream(mPFile, &mPAviVideo, streamtypeVIDEO, 0); + if (rval == AVIERR_NODATA) + { + mPAviVideo = NULL; + + return MOVERR_NOVIDEOSTREAM; + } + + // Get video stream info + AVISTREAMINFO avis; + + AVIStreamInfo(mPAviVideo, &avis, sizeof(avis)); + + mFrate = F32(avis.dwRate)/F32(avis.dwScale); + + // Open vids + rval = vidsVideoOpen(); + if (rval) + return rval; + + return MOVERR_OK; +} + +// Close movie stream +// +S32 GuiAviBitmapCtrl::movieClose() +{ + // Make sure movie is stopped + movieStop(); + + // Release streams + if (mPAviVideo) + AVIStreamRelease(mPAviVideo); + + mPAviVideo = 0; + + return MOVERR_OK; +} + +//----------------------------------------------------------------------------- +// Video Operations + +// Open and init VIDS +// +S32 GuiAviBitmapCtrl::vidsVideoOpen() +{ + if (!mPAviVideo) + return VIDSERR_NOVIDEO; + + mTimePlayStart = -1; + + // Get stream info + AVISTREAMINFO avis; + + AVIStreamInfo(mPAviVideo, &avis, sizeof(avis)); + + mVidsFirst = mVidsCurrent = avis.dwStart; + mVidsLast = avis.dwStart + avis.dwLength - 1; + + mVidsPrevious = mVidsCurrent-1; + + // This was not initialized and causing problems in RTEST- + mVidsPrevKey = -100000; + + // Read first frame to get source info + S32 cb, rval; + BITMAPINFOHEADER biFormat; + + AVIStreamFormatSize(mPAviVideo, 0, (long *) &cb); + if (cb != sizeof(BITMAPINFOHEADER)) + return -1; + rval = AVIStreamReadFormat(mPAviVideo, 0, &biFormat, (long *) &cb); + rval = rval; + + // Open VCM for this instance + if (vcmOpen(avis.fccHandler, &biFormat)) + { + vidsVideoClose(); + + return VIDSERR_VCM; + } + + // Create temporary buffer + mCBVBuf = biFormat.biHeight * ALIGNULONG(biFormat.biWidth) + * biFormat.biBitCount/8; + mPVBuf = (unsigned char *) dMalloc(mCBVBuf); + AssertFatal(mPVBuf, "Out of memory"); + dMemset(mPVBuf, 0, mCBVBuf); + + // Reset internal skip count + mPlayFSkipped = 0; + + return VIDSERR_OK; +} + +// Close VIDS +// +S32 GuiAviBitmapCtrl::vidsVideoClose() +{ + if (mPVBuf) + { + dFree(mPVBuf); + mPVBuf = NULL; + } + + vcmClose(); + + return VIDSERR_OK; +} + +// Start video, note that all decode/draw is done in vidsDraw() +// +// If fStart < 0, then start on current frame +// +S32 GuiAviBitmapCtrl::vidsVideoStart() +{ + // Begin VCM + if (vcmBegin()) + { + vidsVideoStop(); + + return VIDSERR_VCM; + } + + // Pass seperate start message to VCM only when playing + if (mBPlaying) + vcmDrawStart(); + + // Get start time + mTimePlayStart = getMilliseconds(); + mTimePlayStartPos = AVIStreamSampleToTime(mPAviVideo, mVidsCurrent); + + // Init play stats + mPlayFPrev = mVidsCurrent; + + return VIDSERR_OK; +} + +// Stop video +// +S32 GuiAviBitmapCtrl::vidsVideoStop() +{ + vcmDrawStop(); + vcmEnd(); + + mTimePlayStart = -1; + + return VIDSERR_OK; +} + +// Draw current frame of video +// +S32 GuiAviBitmapCtrl::vidsVideoDraw() +{ + // Check if started + if (mTimePlayStart < 0) + return VIDSERR_NOTSTARTED; + + if (mBPlaying) + { + S32 lTime = mTimePlayStartPos + (getMilliseconds() - mTimePlayStart); + + mVidsCurrent = vidsTimeToSample(lTime); + + if (mVidsCurrent > mVidsLast) + mVidsCurrent = mVidsLast; + + if (mVidsCurrent == mVidsPrevious) + // Going too fast! Should actually return a ms + // count so calling app can Sleep() if desired. + return VIDSERR_OK; + } + else + { + if (mVidsCurrent > mVidsLast) + mVidsCurrent = mVidsLast; + + if (mVidsCurrent == mVidsPrevious) + vidsResetDraw(); + } + + if (!vidsSync()) + return VIDSERR_OK; // don't draw this frame + + S32 rval = AVIStreamRead(mPAviVideo, + mVidsCurrent, + 1, + mPVBuf, + mCBVBuf, + NULL, + NULL); + if (rval) + return VIDSERR_READ; + + if (vcmDraw()) + return VIDSERR_VCM; + + mVidsPrevious = mVidsCurrent; + + if (mBPlaying) + { + mPlayFSkipped += mVidsCurrent-mPlayFPrev-1; + mPlayFPrev = mVidsCurrent; + + if (mVidsCurrent == mVidsLast) + { + mVidsCurrent = -1; + + return movieStop(); + } + } + + return VIDSERR_OK; +} + +// Convert ms to sample (frame) +// +S32 GuiAviBitmapCtrl::vidsTimeToSample(S32 lTime) +{ + S32 lSamp = AVIStreamTimeToSample(mPAviVideo, lTime); + + return lSamp; +} + +// TRUE if frame is KEY, if frame < 0 then check current frame +// +bool GuiAviBitmapCtrl::vidsIsKey(S32 frame /* = -1 */) +{ + if (!mPAviVideo) + return false; + + if (frame < 0) + frame = mVidsCurrent; + + return AVIStreamIsKeyFrame(mPAviVideo, frame); +} + +//----------------------------------------------------------------------------- +// Internal video routines + +// Synchronization and Keyframe Management: +// pretty simple plan, don't do anything too fancy. +// +bool GuiAviBitmapCtrl::vidsSync() +{ +#define dist(x,y) ((x)-(y)) +#define ABS_(x) (x<0 ? (-(x)) : (x)) + + if (mVidsCurrent < mVidsPrevious) // seeked back - reset draw + mVidsPrevious = -1; + + if (dist(mVidsCurrent, mVidsPrevious) == 1) + { + // normal situation + // fall thru and draw + } + else + { + // SKIPPED + if (AVIStreamIsKeyFrame(mPAviVideo, mVidsCurrent)) + { + // we are on KF boundry just reset and start here + mVidsPrevKey = mVidsCurrent; + mVidsNextKey = AVIStreamNextKeyFrame(mPAviVideo, mVidsCurrent); + // fall thru and draw + } + else + { + if (dist(mVidsCurrent, mVidsPrevious) == 2) + { + // one frame off - just draw + vidsCatchup(); + // fall thru and draw + } + else + { + // We are greater than one frame off: + // if we went past a K frame, update K frame info then: + // if we are closer to previous frame than catchup and draw + // if we are closer to next KEY frame than don't draw + if ((mVidsNextKey < mVidsCurrent) || (mVidsPrevKey > mVidsCurrent)) // seeked past previous key frame + { + // went past a K frame + mVidsPrevKey = AVIStreamPrevKeyFrame (mPAviVideo, mVidsCurrent); + mVidsNextKey = AVIStreamNextKeyFrame (mPAviVideo, mVidsCurrent); + } + + if (ABS_(dist(mVidsCurrent, mVidsPrevKey)) <= ABS_(dist(mVidsCurrent, mVidsNextKey))) + vidsCatchup(); + // fall thru and draw + else + if (mBPlaying) + return false; // m_vidsPrev NOT updated + else + vidsCatchup(); // if not playing than we want to + // draw the current frame + } + } + } + + return true; +} + +// Readies to draw (but doesn't draw) m_vidsCurrent. +// We just ICDECOMPRESS_HURRYUP frames from m_vidsPrevious or +// m_vidsPrevKey, whichever is closer. +// Updates m_vidsPrevious. +// +void GuiAviBitmapCtrl::vidsCatchup() +{ + if (mVidsPrevious < mVidsPrevKey) + mVidsPrevious = mVidsPrevKey-1; + + S32 catchup = mVidsPrevious+1; + + while (catchup < mVidsCurrent) + { + S32 rval = AVIStreamRead(mPAviVideo, + catchup, + 1, + mPVBuf, + mCBVBuf, + NULL, + NULL); + + if (rval) + break; + + if (!mBPlaying ) + vcmDrawIn(); + else + vcmDrawIn(ICDECOMPRESS_HURRYUP); + + mVidsPrevious = catchup++; + } +} + +// Note that between vcmOpen() and vcmClose(), the source information does not +// change. If it does, open a new one. +// +S32 GuiAviBitmapCtrl::vcmOpen (FOURCC fccHandler, BITMAPINFOHEADER * pbiSrc) +{ + if (fccHandler == 0) + mFccHandler = pbiSrc->biCompression; + else + mFccHandler = fccHandler; + + if (mFccHandler == FOURCC_IV50) + mFccHandler = FOURCC_iv50; + + // Open codec + mHic = ICLocate(ICTYPE_VIDEO, fccHandler, pbiSrc, 0, ICMODE_DECOMPRESS); + if (!mHic) return AVIERR_NOCOMPRESSOR; + + delete [] mPBiSrc; + mPBiSrc = (BITMAPINFOHEADER *) new char [sizeof (BITMAPINFOHEADER) + + 256 * sizeof (RGBQUAD)]; + + delete [] mPBiDst; + mPBiDst = (BITMAPINFOHEADER *) new char [sizeof (BITMAPINFOHEADER) + + 256 * sizeof (RGBQUAD)]; + AssertFatal(mPBiSrc && mPBiDst, "Out of memory"); + + dMemcpy(mPBiSrc, pbiSrc, sizeof(BITMAPINFOHEADER)); + + // Initialize destination bitmap header + dMemcpy (mPBiDst, mPBiSrc, sizeof(BITMAPINFOHEADER)); + // Default destination bitmap header + mPBiDst->biBitCount = 24; + mPBiDst->biCompression = BI_RGB; + mPBiDst->biSizeImage = mPBiDst->biHeight * ALIGNULONG(mPBiDst->biWidth)*mPBiDst->biBitCount/8; + + // Create temporary buffer + mCBuf = mPBiDst->biSizeImage; + mPBuf = (U8 *) dMalloc(mCBuf); + AssertFatal(mPBuf, "Out of memory"); + dMemset(mPBuf, 0, mCBuf); + + return VCMERR_OK; +} + +S32 GuiAviBitmapCtrl::vcmClose() +{ + if (mPBiSrc) delete [] mPBiSrc; + if (mPBiDst) delete [] mPBiDst; + mPBiSrc = mPBiDst = NULL; + + if (mPBuf) + { + dFree(mPBuf); + mPBuf = NULL; + } + + if (mHic) + { + ICClose(mHic); + mHic = NULL; + } + + mFccHandler = 0; + + return VCMERR_OK; +} + +// vcmBegin() and vcmEnd() (de)initializes a series of vcmDraw()'s +// The user must end and restart a sequence when the destination +// parameters change. +// Note that if the source information changes vcmOpen()/vcmClose() +// must be used (since fcc might be different). +// fInit initializes the sequence (do the first time and when resetting +// parameters +// +S32 GuiAviBitmapCtrl::vcmBegin() +{ + S32 rval; + + if (!mHic) + return VCMERR_INTERNAL; + + rval = ICDecompressExQuery(mHic, 0, + mPBiSrc, NULL, 0, 0, mBitmapWidth, mBitmapHeight, + mPBiDst, NULL, 0, 0, mBitmapWidth, mBitmapHeight); + + if (rval) return rval; + + rval = ICDecompressExBegin(mHic, 0, + mPBiSrc, NULL, 0, 0, mBitmapWidth, mBitmapHeight, + mPBiDst, NULL, 0, 0, mBitmapWidth, mBitmapHeight); + + if (rval) return rval; + + return AVIERR_OK; +} + +S32 GuiAviBitmapCtrl::vcmEnd() +{ + return VCMERR_OK; +} + +// vcmDrawStart/vcmDrawStop are not absolutely necessary but some codecs +// use them to do timing (to tell when playing real time) +// +S32 GuiAviBitmapCtrl::vcmDrawStart() +{ + // Send ICM_DRAW_BEGIN. + // this is only for telling the codec what our frame rate is - zero out all other members. + ICDrawBegin(mHic, + 0, 0, 0, 0, + 0, 0, 0, 0, + NULL, + 0, 0, 0, 0, + (DWORD) (1.0/mFrate * 1000.0 * 1000.0), // dwRate + (DWORD) (1000*1000)); // dwScale + + // Send ICM_DRAW_START. + ICDrawStart(mHic); + + return VCMERR_OK; +} + +S32 GuiAviBitmapCtrl::vcmDrawStop() +{ + // Send ICM_DRAW_STOP + ICDrawStop(mHic); + + // Send ICM_DRAW_END + ICDrawEnd(mHic); + + return VCMERR_OK; +} + +S32 GuiAviBitmapCtrl::vcmDraw(U64 dwICflags) +{ + S32 rval; + + rval = ICDecompressEx(mHic, dwICflags, + mPBiSrc, mPVBuf, 0, 0, mBitmapWidth, mBitmapHeight, + mPBiDst, mPBuf, 0, 0, mBitmapWidth, mBitmapHeight); + + if (rval) + // Normal in case of ICM_HURRYUP flag (rval = 1) + return rval; + + for (U32 j = 0; j < mHeightCount; j++) + { + U32 y = j * 256; + U32 height = getMin(mBitmapHeight - y, U32(256)); + + for (U32 i = 0; i < mWidthCount; i++) + { + U32 index = j * mWidthCount + i; + U32 x = i * 256; + U32 width = getMin(mBitmapWidth - x, U32(256)); + GBitmap *bmp = mTextureHandles[index].getBitmap(); + + for (U32 lp = 0; lp < height; lp++) + { + const U8 *src = &mPBuf[bmp->getBytesPerPixel()*((mBitmapHeight-(y+lp+1))*mBitmapAlignedWidth + x)]; + U8 *dest = bmp->getAddress(0, lp); + + // counting on the artist to switch the R & B channels so we don't have to in runtime + if (!mSwapRB) + dMemcpy(dest, src, width * bmp->getBytesPerPixel()); + else + Swizzles::bgr.ToBuffer( dest, src, width * bmp->getBytesPerPixel() ); + } + + mTextureHandles[index].refresh(); + } + } + + return VCMERR_OK; +} + +S32 GuiAviBitmapCtrl::vcmDrawIn(U64 dwICflags) +{ + // If we are not displaying frames, IVI still writes to the buffer + S32 rval = ICDecompressEx(mHic, dwICflags, + mPBiSrc, mPVBuf, 0, 0, mBitmapWidth, mBitmapHeight, + mPBiDst, mPBuf, 0, 0, mBitmapWidth, mBitmapHeight); + + if (rval) + // Normal in case of ICM_HURRYUP flag (rval = 1) + return rval; + + return VCMERR_OK; +} + +void GuiAviBitmapCtrl::initPersistFields() +{ + addGroup("Media"); + addField("aviFilename", TypeFilename, Offset(mAviFilename, GuiAviBitmapCtrl)); + addField("wavFilename", TypeFilename, Offset(mWavFilename, GuiAviBitmapCtrl)); + endGroup("Media"); + + addGroup("Misc"); + addField("swapRB", TypeBool, Offset(mSwapRB, GuiAviBitmapCtrl)); + addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl)); + addField("letterBox", TypeBool, Offset(mLetterBox, GuiAviBitmapCtrl)); + addField("speed", TypeF32, Offset(mSpeed, GuiAviBitmapCtrl)); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::setFilename(const char *filename) +{ + bool awake = mAwake; + + if (awake) + onSleep(); + + mAviFilename = StringTable->insert(filename); + + if (awake) + onWake(); +} + +// Start a movie, i.e. begin play from stopped state +// or restart from paused state +// +S32 GuiAviBitmapCtrl::movieStart() +{ + if (!mPAviVideo) + return MOVERR_NOVIDEOSTREAM; + + // Check if starting without stopping + if (mBPlaying) + return MOVERR_PLAYING; + + mBPlaying = true; + + sndStart(); + + // Start video, note only one state var, play or stop + vidsVideoStart(); + setUpdate(); + + return MOVERR_OK; +} + +// Stop playing a movie +// +S32 GuiAviBitmapCtrl::movieStop() +{ + mBPlaying = false; + vidsVideoStop(); + + sndStop(); + + // notify the script + // Con::executef(this,1,"movieStopped"); + + mDone = true; + + return MOVERR_OK; +} + +//---------------------------------------------------------------------------- +bool GuiAviBitmapCtrl::onWake() +{ + if (!Parent::onWake()) return false; + + if (fileOpen() || movieOpen()) + { + mDone = true; + + // we return TRUE here, or else the damn thing gets deleted and that's + // just plain bad. + + return true; + } + + sndOpen(); + + mBitmapWidth = mPBiSrc->biWidth; + mBitmapAlignedWidth = ALIGNULONG(mBitmapWidth); + mBitmapHeight = mPBiSrc->biHeight; + mWidthCount = mBitmapWidth / 256; + mHeightCount = mBitmapHeight / 256; + if (mBitmapWidth % 256) + mWidthCount++; + if (mBitmapHeight % 256) + mHeightCount++; + mNumTextures = mWidthCount * mHeightCount; + mTextureHandles = new GFXTexHandle[mNumTextures]; + for (U32 j = 0; j < mHeightCount; j++) + { + U32 y = j * 256; + U32 height = getMin(mBitmapWidth - y, U32(256)); + + for (U32 i = 0; i < mWidthCount; i++) + { + char nameBuffer[64]; + U32 index = j * mWidthCount + i; + + dSprintf(nameBuffer, sizeof(nameBuffer), "%s_#%d_#%d", mAviFilename, i, j); + mTextureHandles[index] = GFXTexHandle(nameBuffer, RegisteredTexture, true); + if (!bool(mTextureHandles[index])) + { + U32 x = i * 256; + U32 width = getMin(mBitmapWidth - x, U32(256)); + + const GBitmap *bmp = new GBitmap(width, height, false, GBitmap::RGB); + + mTextureHandles[index] = GFXTexHandle(nameBuffer, bmp, true); + } + } + } + + return true; +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::onSleep() +{ + movieClose(); + fileClose(); + + if (mTextureHandles) + { + delete [] mTextureHandles; + mTextureHandles = NULL; + } + + Parent::onSleep(); +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::onMouseDown(const GuiEvent&) +{ + // end the movie NOW! + movieStop(); +} + +//---------------------------------------------------------------------------- +bool GuiAviBitmapCtrl::onKeyDown(const GuiEvent&) +{ + // end the movie NOW! + movieStop(); + return true; +} + +//---------------------------------------------------------------------------- +// Playing with speed for debugging (glitch shows up when skipping occurs). + +S32 GuiAviBitmapCtrl::getMilliseconds() +{ + F32 ms; + + if (mSpeed > 0) + { + ms = Platform::getRealMilliseconds(); + ms *= mSpeed; + } + else + { + // Try to force the glitch (negative speed)- + static F32 deterministicClock = 0.0f; + ms = deterministicClock; + deterministicClock -= mSpeed; + } + + return S32(ms); +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + vidsVideoDraw(); + + if (mTextureHandles) + { + RectI displayRect(getBounds()); + S32 verticalDisplace = 0; + + if (mLetterBox) + { + // Our supplied picture is 3/4 height 640x360 for 640x480 res. But let's allow + // for a regular full-window version since it might come in handy elsewhere (so + // letterBox is public flag on the GUI). + verticalDisplace = (getHeight() >> 3); + displayRect.extent.y = (displayRect.extent.y * 3 >> 2); + displayRect.point.y += verticalDisplace; + RectI upperRect(offset.x, offset.y, displayRect.extent.x, verticalDisplace + 1); + GFX->drawRectFill(upperRect, mProfile->mFillColorHL); + } + + // Scale into the letterbox- + F32 widthScale = F32(displayRect.extent.x) / F32(mBitmapWidth); + F32 heightScale = F32(displayRect.extent.y) / F32(mBitmapHeight); + + offset.y += verticalDisplace; + + GFX->setBitmapModulation(ColorF(1,1,1)); + for (U32 i = 0; i < mWidthCount; i++) + { + for (U32 j = 0; j < mHeightCount; j++) + { + GFXTexHandle t = mTextureHandles[j * mWidthCount + i]; + RectI stretchRegion; + + stretchRegion.point.x = i * 256 * widthScale + offset.x; + stretchRegion.point.y = j * 256 * heightScale + offset.y; + stretchRegion.extent.x = (i * 256 + t.getWidth()) * widthScale + offset.x - stretchRegion.point.x; + stretchRegion.extent.y = (j * 256 + t.getHeight()) * heightScale + offset.y - stretchRegion.point.y; + GFX->drawBitmapStretch(t, stretchRegion); + } + } + + if (mLetterBox) + { + // For some reason the above loop draws white below, and this rect fill has to + // come after - got to look at that math... Also don't know why we need the + // extra width & height here... + RectI lowerRect(offset.x, getHeight() - verticalDisplace - 1, + getWidth() + 2, verticalDisplace + 2); + GFX->drawRectFill(lowerRect, mProfile->mFillColorHL); + } + + renderChildControls(offset, updateRect); + + if (mBPlaying) + setUpdate(); + } + else + Parent::onRender(offset, updateRect); +} + +#endif /* ENABLE_AVI_GUI */ + + +#if ENABLE_MPG_GUI + +//---------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiAviBitmapCtrl); + +GuiAviBitmapCtrl::GuiAviBitmapCtrl() +{ + mAviFilename = StringTable->insert(""); + mTextureHandles = NULL; + + mMPEG = NULL; + mBPlaying = false; + mDone = false; + mLetterBox = false; + mSurface = NULL; + mPBuf = NULL; + mDecodeLock = NULL; + mWavHandle = NULL_AUDIOHANDLE; +} + +//---------------------------------------------------------------------------- +GuiAviBitmapCtrl::~GuiAviBitmapCtrl() +{ +} + +//----------------------------------------------------------------------------- +// MPEG File Operations + +// Open a file +// +S32 GuiAviBitmapCtrl::fileOpen() +{ + char fileBuffer[256]; + + if (!dStrcmp(mAviFilename,"")) + return MOVERR_NOVIDEOSTREAM; + + // FixMe: Should convert this to have a dir attribute... + dSprintf(fileBuffer, sizeof(fileBuffer), "FixMe/textures/%s", mAviFilename); + + // Convert filename from .avi to .mpg + char *ext; + ext = dStrstr(static_cast(fileBuffer), ".avi"); + if (!ext) + ext = dStrstr(static_cast(fileBuffer), ".AVI"); + if (ext) + dStrcpy(ext, ".mpg"); + + mMPEG = SMPEG_new(fileBuffer, &mInfo, 0); + if (!mMPEG || (SMPEG_status(mMPEG) == SMPEG_ERROR)) + { + fileClose(); + return MOVERR_NOVIDEOSTREAM; + } + return MOVERR_OK; +} + +// Close a file +// +S32 GuiAviBitmapCtrl::fileClose () +{ + if (mMPEG) + { + SMPEG_delete(mMPEG); + mMPEG = NULL; + } + return MOVERR_OK; +} + +//----------------------------------------------------------------------------- +// Movie Operations + +// A movie must have a video stream +// Return codes should be properly done but for now, 0 is no error +// while !0 is error unless otherwise noted + +// Open a movie (video stream) from an open file +// +S32 GuiAviBitmapCtrl::movieOpen() +{ + // If the file was opened successfully, it's an MPEG video + return MOVERR_OK; +} + +// Close movie stream +// +S32 GuiAviBitmapCtrl::movieClose() +{ + // Make sure movie is stopped + movieStop(); + + return MOVERR_OK; +} + + +void GuiAviBitmapCtrl::initPersistFields() +{ + addGroup("Media"); + addField("aviFilename", TypeFilename, Offset(mAviFilename, GuiAviBitmapCtrl)); + addField("wavFilename", TypeFilename, Offset(mWavFilename, GuiAviBitmapCtrl)); + endGroup("Media"); + + addGroup("Misc"); + addField("done", TypeBool, Offset(mDone, GuiAviBitmapCtrl)); + addField("letterBox", TypeBool, Offset(mLetterBox, GuiAviBitmapCtrl)); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::setFilename(const char *filename) +{ + bool awake = mAwake; + + if (awake) + onSleep(); + + mAviFilename = StringTable->insert(filename); + + if (awake) + onWake(); +} + +// Start a movie, i.e. begin play from stopped state +// or restart from paused state +// +S32 GuiAviBitmapCtrl::movieStart() +{ + if (!mMPEG) + return MOVERR_NOVIDEOSTREAM; + + // Check if starting without stopping + if (mBPlaying) + return MOVERR_PLAYING; + + mBPlaying = true; + + sndStart(); + + // Start video, note only one state var, play or stop + SMPEG_play(mMPEG); + + return MOVERR_OK; +} + +// Stop playing a movie +// +S32 GuiAviBitmapCtrl::movieStop() +{ + mBPlaying = false; + + if (mMPEG) + { + SMPEG_stop(mMPEG); + } + sndStop(); + + // notify the script + // Con::executef(this,1,"movieStopped"); + + mDone = true; + + return MOVERR_OK; +} + +//---------------------------------------------------------------------------- +bool GuiAviBitmapCtrl::onWake() +{ + if (!Parent::onWake()) return false; + + if (fileOpen() || movieOpen()) + { + mDone = true; + // Never return false from onWake, the object will be freed, but + // not removed from the gui framework, so the game crashes later. + return true; + } + + sndOpen(); + + mBitmapWidth = mInfo.width; + mBitmapAlignedWidth = ALIGNULONG(mBitmapWidth); + AssertFatal(mBitmapAlignedWidth == mBitmapWidth, "Unaligned MPEG data"); + mBitmapHeight = mInfo.height; + mWidthCount = mBitmapWidth / 256; + mHeightCount = mBitmapHeight / 256; + if (mBitmapWidth % 256) + mWidthCount++; + if (mBitmapHeight % 256) + mHeightCount++; + mNumTextures = mWidthCount * mHeightCount; + mTextureHandles = new GFXTexHandle[mNumTextures]; + for (U32 j = 0; j < mHeightCount; j++) + { + U32 y = j * 256; + U32 height = getMin(mBitmapWidth - y, U32(256)); + + for (U32 i = 0; i < mWidthCount; i++) + { + char nameBuffer[64]; + U32 index = j * mWidthCount + i; + + dSprintf(nameBuffer, sizeof(nameBuffer), "%s_#%d_#%d", mAviFilename, i, j); + mTextureHandles[index] = GFXTexHandle(nameBuffer, RegisteredTexture, true); + if (!bool(mTextureHandles[index])) + { + U32 x = i * 256; + U32 width = getMin(mBitmapWidth - x, U32(256)); + + const GBitmap *bmp = new GBitmap(width, height, false, GBitmap::RGB); + + mTextureHandles[index] = GFXTexHandle(nameBuffer, bmp, true); + } + } + } + + // Allocate the SDL surface for the YUV decoding + // It shore wood be nice if SDL could decode YUV to GL textures... + mPBuf = (U8 *) dMalloc(mBitmapWidth*3*mBitmapHeight); + AssertFatal(mPBuf, "Out of memory for buffer."); + mSurface = SDL_CreateRGBSurfaceFrom(mPBuf, + mBitmapWidth, mBitmapHeight, 24, + mBitmapWidth*3, + 0x000000FF, 0x0000FF00, 0x00FF0000, 0); + AssertFatal(mSurface, "Out of memory for a surface!"); + + // Target the decoding to our surface + mDecodeLock = SDL_CreateMutex(); + AssertFatal(mDecodeLock, "Out of memory for a decode lock!"); + SMPEG_setdisplay(mMPEG, mSurface, mDecodeLock, NULL); + + return true; +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::onSleep() +{ + movieClose(); + fileClose(); + + if (mSurface) + { + SDL_FreeSurface(mSurface); + mSurface = NULL; + } + + if (mPBuf) + { + dFree(mPBuf); + mPBuf = NULL; + } + + if (mTextureHandles) + { + delete [] mTextureHandles; + mTextureHandles = NULL; + } + + Parent::onSleep(); +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::onMouseDown(const GuiEvent&) +{ + // end the movie NOW! + movieStop(); +} + +//---------------------------------------------------------------------------- +bool GuiAviBitmapCtrl::onKeyDown(const GuiEvent&) +{ + // end the movie NOW! + movieStop(); + return true; +} + +//---------------------------------------------------------------------------- +void GuiAviBitmapCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + if (mTextureHandles) + { + RectI displayRect(getBounds()); + S32 verticalDisplace = 0; + + // Get the converted RGB data from SMPEG + SDL_LockMutex(mDecodeLock); + for (U32 j = 0; j < mHeightCount; j++) + { + U32 y = j * 256; + U32 height = getMin(mBitmapHeight - y, U32(256)); + + for (U32 i = 0; i < mWidthCount; i++) + { + U32 index = j * mWidthCount + i; + U32 x = i * 256; + U32 width = getMin(mBitmapWidth - x, U32(256)); + GBitmap *bmp = mTextureHandles[index].getBitmap(); + + for (U32 lp = 0; lp < height; lp++) + { + const U8 *src = &mPBuf[bmp->getBytesPerPixel()*((y+lp)*mBitmapAlignedWidth + x)]; + U8 *dest = bmp->getAddress(0, lp); + + dMemcpy(dest, src, width * bmp->getBytesPerPixel()); + } + mTextureHandles[index].refresh(); + } + } + SDL_UnlockMutex(mDecodeLock); + + if (mLetterBox) + { + // Our supplied picture is 3/4 height 640x360 for 640x480 res. But let's allow + // for a regular full-window version since it might come in handy elsewhere (so + // letterBox is public flag on the GUI). + verticalDisplace = (getHeight() >> 3); + displayRect.extent.y = (displayRect.extent.y * 3 >> 2); + displayRect.point.y += verticalDisplace; + RectI upperRect(offset.x, offset.y, displayRect.extent.x, verticalDisplace + 1); + GFX->drawRectFill(upperRect, mProfile->mFillColorHL); + } + + // Scale into the letterbox- + F32 widthScale = F32(displayRect.extent.x) / F32(mBitmapWidth); + F32 heightScale = F32(displayRect.extent.y) / F32(mBitmapHeight); + + offset.y += verticalDisplace; + + GFX->setBitmapModulation(ColorF(1,1,1)); + for (U32 i = 0; i < mWidthCount; i++) + { + for (U32 j = 0; j < mHeightCount; j++) + { + GFXTexHandle t = mTextureHandles[j * mWidthCount + i]; + RectI stretchRegion; + + stretchRegion.point.x = i * 256 * widthScale + offset.x; + stretchRegion.point.y = j * 256 * heightScale + offset.y; + stretchRegion.extent.x = (i * 256 + t.getWidth()) * widthScale + offset.x - stretchRegion.point.x; + stretchRegion.extent.y = (j * 256 + t.getHeight()) * heightScale + offset.y - stretchRegion.point.y; + GFX->drawBitmapStretch(t, stretchRegion); + } + } + + if (mLetterBox) + { + // For some reason the above loop draws white below, and this rect fill has to + // come after - got to look at that math... Also don't know why we need the + // extra width & height here... + RectI lowerRect(offset.x, getHeight() - verticalDisplace - 1, + getWidth() + 2, verticalDisplace + 2); + GFX->drawRectFill(lowerRect, mProfile->mFillColorHL); + } + + renderChildControls(offset, updateRect); + + mBPlaying = (SMPEG_status(mMPEG) == SMPEG_PLAYING); + if (mBPlaying) + setUpdate(); + else + movieStop(); + } + else + Parent::onRender(offset, updateRect); +} + +#endif /* ENABLE_MPG_GUI */ + +#endif /* Enabled Movie GUI */ diff --git a/gui/game/guiAviBitmapCtrl.h b/gui/game/guiAviBitmapCtrl.h new file mode 100644 index 0000000..ffb0b50 --- /dev/null +++ b/gui/game/guiAviBitmapCtrl.h @@ -0,0 +1,186 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIAVIBITMAPCTRL_H_ +#define _GUIAVIBITMAPCTRL_H_ + +#if !ENABLE_AVI_GUI || !ENABLE_MPG_GUI + +class GuiAviBitmapCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + protected: + bool mDone; + + public: + DECLARE_CONOBJECT(GuiAviBitmapCtrl); + GuiAviBitmapCtrl(); + ~GuiAviBitmapCtrl(); + static void initPersistFields(); +}; + +#endif /* No movie control */ + + +#if ENABLE_AVI_GUI + +class GuiAviBitmapCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + protected: + StringTableEntry mAviFilename; + StringTableEntry mWavFilename; + U32 mNumTextures; + GFXTexHandle *mTextureHandles; + U32 mWidthCount; + U32 mHeightCount; + U32 mBitmapWidth; + U32 mBitmapAlignedWidth; + U32 mBitmapHeight; + + PAVIFILE mPFile; + PAVISTREAM mPAviVideo; // video stream to play + + AUDIOHANDLE mWavHandle; // music to play along with it + + bool mBPlaying; + bool mDone; + bool mLetterBox; + F32 mFrate; + F32 mSpeed; + S32 mTimePlayStart; + S32 mTimePlayStartPos; + S16 mPlayFPrev; + S16 mPlayFSkipped; + S32 mVidsCurrent; // attempted frame to draw + S32 mVidsPrevious; // last successfully decoded frame + S32 mVidsPrevKey, mVidsNextKey; + S32 mVidsFirst, mVidsLast; + S32 mCBVBuf; + U8 *mPVBuf; + + HIC mHic; + FOURCC mFccHandler; + BITMAPINFOHEADER *mPBiSrc; + BITMAPINFOHEADER *mPBiDst; + S32 mCBuf; + U8 *mPBuf; + bool mSwapRB; + ALint mAudioLatency; + + S32 fileOpen(); + S32 fileClose(); + S32 movieOpen(); + S32 movieClose(); + S32 vidsVideoOpen(); + S32 vidsVideoClose(); + S32 vidsVideoStart(); + S32 vidsVideoStop(); + S32 vidsVideoDraw(); + S32 vidsTimeToSample(S32 lTime); + bool vidsIsKey(S32 frame = -1); + void vidsResetDraw() { mVidsPrevious = -1; } + bool vidsSync(); + void vidsCatchup(); + S32 vcmOpen(FOURCC fccHandler, BITMAPINFOHEADER *pbiSrc); + S32 vcmClose(); + S32 vcmBegin(); + S32 vcmEnd(); + S32 vcmDrawStart(); + S32 vcmDrawStop(); + S32 vcmDraw(U64 dwICflags = 0); + S32 vcmDrawIn(U64 dwICflags = 0); + bool sndOpen(); + void sndStart(); + void sndStop(); + S32 getMilliseconds(); + + public: + DECLARE_CONOBJECT(GuiAviBitmapCtrl); + + GuiAviBitmapCtrl(); + ~GuiAviBitmapCtrl(); + + static void initPersistFields(); + + void setFilename(const char *filename); + S32 movieStart(); + S32 movieStop(); + + bool onWake(); + void onSleep(); + void onMouseDown(const GuiEvent&); + bool onKeyDown(const GuiEvent&); + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif /* ENABLE_AVI_GUI */ + +#if ENABLE_MPG_GUI + +class GuiAviBitmapCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + protected: + StringTableEntry mAviFilename; + StringTableEntry mWavFilename; + U32 mNumTextures; + GFXTexHandle *mTextureHandles; + U32 mWidthCount; + U32 mHeightCount; + U32 mBitmapWidth; + U32 mBitmapAlignedWidth; + U32 mBitmapHeight; + + SDL_Surface *mSurface; + U8 *mPBuf; + SDL_mutex *mDecodeLock; + ALint mAudioLatency; + + SMPEG *mMPEG; // video stream to play + + AUDIOHANDLE mWavHandle; // music to play along with it + + bool mBPlaying; + bool mDone; + bool mLetterBox; + SMPEG_Info mInfo; + + S32 fileOpen(); + S32 fileClose(); + S32 movieOpen(); + S32 movieClose(); + bool sndOpen(); + void sndStart(); + void sndStop(); + + public: + DECLARE_CONOBJECT(GuiAviBitmapCtrl); + + GuiAviBitmapCtrl(); + ~GuiAviBitmapCtrl(); + + static void initPersistFields(); + + void setFilename(const char *filename); + S32 movieStart(); + S32 movieStop(); + + bool onWake(); + void onSleep(); + void onMouseDown(const GuiEvent&); + bool onKeyDown(const GuiEvent&); + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif /* ENABLE_MPG_GUI */ + +#endif /* _GUIAVIBITMAPCTRL_H_ */ diff --git a/gui/game/guiChunkedBitmapCtrl.cpp b/gui/game/guiChunkedBitmapCtrl.cpp new file mode 100644 index 0000000..d3a2d3c --- /dev/null +++ b/gui/game/guiChunkedBitmapCtrl.cpp @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/bitmap/gBitmap.h" +#include "gui/core/guiControl.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTextureHandle.h" +#include "gfx/gfxDrawUtil.h" + + +class GuiChunkedBitmapCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + void renderRegion(const Point2I &offset, const Point2I &extent); + +protected: + StringTableEntry mBitmapName; + GFXTexHandle mTexHandle; + bool mUseVariable; + bool mTile; + +public: + //creation methods + DECLARE_CONOBJECT(GuiChunkedBitmapCtrl); + DECLARE_CATEGORY( "Gui Images" ); + + GuiChunkedBitmapCtrl(); + static void initPersistFields(); + + //Parental methods + bool onWake(); + void onSleep(); + + void setBitmap(const char *name); + + void onRender(Point2I offset, const RectI &updateRect); +}; + +IMPLEMENT_CONOBJECT(GuiChunkedBitmapCtrl); + +void GuiChunkedBitmapCtrl::initPersistFields() +{ + addGroup("GuiChunkedBitmapCtrl"); + addField( "bitmap", TypeFilename, Offset( mBitmapName, GuiChunkedBitmapCtrl ) ); + addField( "useVariable", TypeBool, Offset( mUseVariable, GuiChunkedBitmapCtrl ) ); + addField( "tile", TypeBool, Offset( mTile, GuiChunkedBitmapCtrl ) ); + endGroup("GuiChunkedBitmapCtrl"); + Parent::initPersistFields(); +} + +ConsoleMethod( GuiChunkedBitmapCtrl, setBitmap, void, 3, 3, "(string filename)" + "Set the bitmap contained in this control.") +{ + object->setBitmap( argv[2] ); +} + +GuiChunkedBitmapCtrl::GuiChunkedBitmapCtrl() +{ + mBitmapName = StringTable->insert(""); + mUseVariable = false; + mTile = false; +} + +void GuiChunkedBitmapCtrl::setBitmap(const char *name) +{ + bool awake = mAwake; + if(awake) + onSleep(); + + mBitmapName = StringTable->insert(name); + if(awake) + onWake(); + setUpdate(); +} + +bool GuiChunkedBitmapCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + + if( !mTexHandle + && ( ( mBitmapName && mBitmapName[ 0 ] ) + || ( mUseVariable && mConsoleVariable && mConsoleVariable[ 0 ] ) ) ) + { + if ( mUseVariable ) + mTexHandle.set( Con::getVariable( mConsoleVariable ), &GFXDefaultGUIProfile, avar("%s() - mTexHandle (line %d)", __FUNCTION__, __LINE__) ); + else + mTexHandle.set( mBitmapName, &GFXDefaultGUIProfile, avar("%s() - mTexHandle (line %d)", __FUNCTION__, __LINE__) ); + } + + return true; +} + +void GuiChunkedBitmapCtrl::onSleep() +{ + mTexHandle = NULL; + Parent::onSleep(); +} + +void GuiChunkedBitmapCtrl::renderRegion(const Point2I &offset, const Point2I &extent) +{ +/* + U32 widthCount = mTexHandle.getTextureCountWidth(); + U32 heightCount = mTexHandle.getTextureCountHeight(); + if(!widthCount || !heightCount) + return; + + F32 widthScale = F32(extent.x) / F32(mTexHandle.getWidth()); + F32 heightScale = F32(extent.y) / F32(mTexHandle.getHeight()); + GFX->setBitmapModulation(ColorF(1,1,1)); + for(U32 i = 0; i < widthCount; i++) + { + for(U32 j = 0; j < heightCount; j++) + { + GFXTexHandle t = mTexHandle.getSubTexture(i, j); + RectI stretchRegion; + stretchRegion.point.x = (S32)(i * 256 * widthScale + offset.x); + stretchRegion.point.y = (S32)(j * 256 * heightScale + offset.y); + if(i == widthCount - 1) + stretchRegion.extent.x = extent.x + offset.x - stretchRegion.point.x; + else + stretchRegion.extent.x = (S32)((i * 256 + t.getWidth() ) * widthScale + offset.x - stretchRegion.point.x); + if(j == heightCount - 1) + stretchRegion.extent.y = extent.y + offset.y - stretchRegion.point.y; + else + stretchRegion.extent.y = (S32)((j * 256 + t.getHeight()) * heightScale + offset.y - stretchRegion.point.y); + GFX->drawBitmapStretch(t, stretchRegion); + } + } +*/ +} + + +void GuiChunkedBitmapCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + + if( mTexHandle ) + { + RectI boundsRect( offset, getExtent()); + GFX->getDrawUtil()->drawBitmapStretch( mTexHandle, boundsRect, GFXBitmapFlip_None, GFXTextureFilterLinear ); + } + + renderChildControls(offset, updateRect); +} diff --git a/gui/game/guiFadeinBitmapCtrl.cpp b/gui/game/guiFadeinBitmapCtrl.cpp new file mode 100644 index 0000000..bcd1e38 --- /dev/null +++ b/gui/game/guiFadeinBitmapCtrl.cpp @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "gui/controls/guiBitmapCtrl.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + + +class GuiFadeinBitmapCtrl : public GuiBitmapCtrl +{ + typedef GuiBitmapCtrl Parent; +public: + DECLARE_CONOBJECT(GuiFadeinBitmapCtrl); + DECLARE_DESCRIPTION( "A control that shows a bitmap. It fades the bitmap in a set amount of time,\n" + "then waits a set amount of time, and finally fades the bitmap back out in\n" + "another set amount of time." ); + + U32 wakeTime; + bool done; + U32 fadeinTime; + U32 waitTime; + U32 fadeoutTime; + + GuiFadeinBitmapCtrl() + { + wakeTime = 0; + fadeinTime = 1000; + waitTime = 2000; + fadeoutTime = 1000; + done = false; + } + void onPreRender() + { + Parent::onPreRender(); + setUpdate(); + } + void onMouseDown(const GuiEvent &) + { + Con::executef(this, "click"); + } + bool onKeyDown(const GuiEvent &) + { + Con::executef(this, "click"); + return true; + } + bool onWake() + { + if(!Parent::onWake()) + return false; + wakeTime = Platform::getRealMilliseconds(); + return true; + } + void onRender(Point2I offset, const RectI &updateRect) + { + Parent::onRender(offset, updateRect); + U32 elapsed = Platform::getRealMilliseconds() - wakeTime; + + U32 alpha; + if (elapsed < fadeinTime) + { + // fade-in + alpha = (U32)(255.0f * (1.0f - (F32(elapsed) / F32(fadeinTime)))); + } + else if (elapsed < (fadeinTime+waitTime)) + { + // wait + alpha = 0; + } + else if (elapsed < (fadeinTime+waitTime+fadeoutTime)) + { + // fade out + elapsed -= (fadeinTime+waitTime); + alpha = (U32)(255.0f * F32(elapsed) / F32(fadeoutTime)); + } + else + { + // done state + alpha = fadeoutTime ? 255 : 0; + done = true; + + if (!Con::getBoolVariable("$InGuiEditor")) + Con::executef(this, "onDone"); + } + ColorI color(0,0,0,alpha); + GFX->getDrawUtil()->drawRectFill( offset, getExtent() + offset, color ); + } + static void initPersistFields() + { + addField("fadeinTime", TypeS32, Offset(fadeinTime, GuiFadeinBitmapCtrl)); + addField("waitTime", TypeS32, Offset(waitTime, GuiFadeinBitmapCtrl)); + addField("fadeoutTime", TypeS32, Offset(fadeoutTime, GuiFadeinBitmapCtrl)); + addField("done", TypeBool, Offset(done, GuiFadeinBitmapCtrl)); + Parent::initPersistFields(); + } +}; + +IMPLEMENT_CONOBJECT(GuiFadeinBitmapCtrl); diff --git a/gui/game/guiIdleCamFadeBitmapCtrl.cpp b/gui/game/guiIdleCamFadeBitmapCtrl.cpp new file mode 100644 index 0000000..75d575d --- /dev/null +++ b/gui/game/guiIdleCamFadeBitmapCtrl.cpp @@ -0,0 +1,164 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/controls/guiBitmapCtrl.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + + +class GuiIdleCamFadeBitmapCtrl : public GuiBitmapCtrl +{ + typedef GuiBitmapCtrl Parent; +public: + DECLARE_CONOBJECT(GuiIdleCamFadeBitmapCtrl); + DECLARE_CATEGORY( "Gui Images" ); + + U32 wakeTime; + bool done; + U32 fadeinTime; + U32 fadeoutTime; + bool doFadeIn; + bool doFadeOut; + + GuiIdleCamFadeBitmapCtrl() + { + wakeTime = 0; + fadeinTime = 1000; + fadeoutTime = 1000; + done = false; + + doFadeIn = false; + doFadeOut = false; + } + void onPreRender() + { + Parent::onPreRender(); + setUpdate(); + } + void onMouseDown(const GuiEvent &) + { + Con::executef(this, "click"); + } + bool onKeyDown(const GuiEvent &) + { + Con::executef(this, "click"); + return true; + } + bool onWake() + { + if(!Parent::onWake()) + return false; + wakeTime = Platform::getRealMilliseconds(); + return true; + } + + void fadeIn() + { + wakeTime = Platform::getRealMilliseconds(); + doFadeIn = true; + doFadeOut = false; + done = false; + } + + void fadeOut() + { + wakeTime = Platform::getRealMilliseconds(); + doFadeIn = false; + doFadeOut = true; + done = false; + } + + void onRender(Point2I offset, const RectI &updateRect) + { + U32 elapsed = Platform::getRealMilliseconds() - wakeTime; + + U32 alpha; + if (doFadeOut && elapsed < fadeoutTime) + { + // fade out + alpha = 255 - (255 * (F32(elapsed) / F32(fadeoutTime))); + } + else if (doFadeIn && elapsed < fadeinTime) + { + // fade in + alpha = 255 * F32(elapsed) / F32(fadeinTime); + } + else + { + // done state + alpha = doFadeIn ? 255 : 0; + done = true; + } + + ColorI color(255,255,255,alpha); + if (mTextureObject) + { + GFX->getDrawUtil()->setBitmapModulation(color); + + if(mWrap) + { + + GFXTextureObject* texture = mTextureObject; + RectI srcRegion; + RectI dstRegion; + float xdone = ((float)getExtent().x/(float)texture->mBitmapSize.x)+1; + float ydone = ((float)getExtent().y/(float)texture->mBitmapSize.y)+1; + + int xshift = mStartPoint.x%texture->mBitmapSize.x; + int yshift = mStartPoint.y%texture->mBitmapSize.y; + for(int y = 0; y < ydone; ++y) + for(int x = 0; x < xdone; ++x) + { + srcRegion.set(0,0,texture->mBitmapSize.x,texture->mBitmapSize.y); + dstRegion.set( ((texture->mBitmapSize.x*x)+offset.x)-xshift, + ((texture->mBitmapSize.y*y)+offset.y)-yshift, + texture->mBitmapSize.x, + texture->mBitmapSize.y); + GFX->getDrawUtil()->drawBitmapStretchSR(texture,dstRegion, srcRegion); + } + + } + else + { + RectI rect(offset, getExtent()); + GFX->getDrawUtil()->drawBitmapStretch(mTextureObject, rect); + } + } + + if (mProfile->mBorder || !mTextureObject) + { + RectI rect(offset.x, offset.y, getExtent().x, getExtent().y); + ColorI borderCol(mProfile->mBorderColor); + borderCol.alpha = alpha; + GFX->getDrawUtil()->drawRect(rect, borderCol); + } + + renderChildControls(offset, updateRect); + } + + static void initPersistFields() + { + addField("fadeinTime", TypeS32, Offset(fadeinTime, GuiIdleCamFadeBitmapCtrl)); + addField("fadeoutTime", TypeS32, Offset(fadeoutTime, GuiIdleCamFadeBitmapCtrl)); + addField("done", TypeBool, Offset(done, GuiIdleCamFadeBitmapCtrl)); + Parent::initPersistFields(); + } +}; + +IMPLEMENT_CONOBJECT(GuiIdleCamFadeBitmapCtrl); + +ConsoleMethod(GuiIdleCamFadeBitmapCtrl, fadeIn, void, 2, 2, "()") +{ + object->fadeIn(); +} + +ConsoleMethod(GuiIdleCamFadeBitmapCtrl, fadeOut, void, 2, 2, "()") +{ + object->fadeOut(); +} diff --git a/gui/game/guiMessageVectorCtrl.cpp b/gui/game/guiMessageVectorCtrl.cpp new file mode 100644 index 0000000..4be4a8b --- /dev/null +++ b/gui/game/guiMessageVectorCtrl.cpp @@ -0,0 +1,820 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/game/guiMessageVectorCtrl.h" + +#include "gui/utility/messageVector.h" +#include "console/consoleTypes.h" +#include "gui/containers/guiScrollCtrl.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiMessageVectorCtrl); + + +//-------------------------------------- Console functions +ConsoleMethod(GuiMessageVectorCtrl, attach, bool, 3, 3, "(MessageVector item)" + "Make this gui control display messages from the specified MessageVector") +{ + MessageVector* pMV = NULL; + Sim::findObject(argv[2], pMV); + if (pMV == NULL) { + Con::errorf(ConsoleLogEntry::General, "Could not find MessageVector: %s", argv[2]); + return false; + } + + return object->attach(pMV); +} + +ConsoleMethod(GuiMessageVectorCtrl, detach, void, 2, 2, "()" + "Stop listing messages from the MessageVector previously attached to, if any.") +{ + if (object->isAttached() == false) { + Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl: double detach"); + return; + } + + object->detach(); +} + +struct TempLineBreak +{ + S32 start; + S32 end; +}; + +//-------------------------------------------------------------------------- +// Callback for messageVector +void sMVCtrlCallback(void * spectatorKey, + const MessageVector::MessageCode code, + const U32 argument) +{ + GuiMessageVectorCtrl* pMVC = reinterpret_cast(spectatorKey); + pMVC->callbackRouter(code, argument); +} + + +//-------------------------------------------------------------------------- +GuiMessageVectorCtrl::GuiMessageVectorCtrl() +{ + VECTOR_SET_ASSOCIATION(mLineWrappings); + VECTOR_SET_ASSOCIATION(mSpecialMarkers); + VECTOR_SET_ASSOCIATION(mLineElements); + + mMessageVector = NULL; + mLineSpacingPixels = 0; + mLineContinuationIndent = 10; + + mMouseDown = false; + mMouseSpecialLine = -1; + mMouseSpecialRef = -1; + + for (U32 i = 0; i < 16; i++) + mAllowedMatches[i] = ""; + mSpecialColor.set(0, 0, 255); + + mMaxColorIndex = 9; +} + + +//-------------------------------------------------------------------------- +GuiMessageVectorCtrl::~GuiMessageVectorCtrl() +{ + AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); + AssertFatal(mSpecialMarkers.size() == 0, "Error, special markers not properly cleared!"); + AssertFatal(mLineElements.size() == 0, "Error, line elements not properly cleared!"); +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::initPersistFields() +{ + addField("lineSpacing", TypeS32, Offset(mLineSpacingPixels, GuiMessageVectorCtrl)); + addField("lineContinuedIndex", TypeS32, Offset(mLineContinuationIndent, GuiMessageVectorCtrl)); + addField("allowedMatches", TypeString, Offset(mAllowedMatches, GuiMessageVectorCtrl), 16); + addField("matchColor", TypeColorI, Offset(mSpecialColor, GuiMessageVectorCtrl)); + addField("maxColorIndex", TypeS32, Offset(mMaxColorIndex, GuiMessageVectorCtrl)); + Parent::initPersistFields(); +} + + +bool GuiMessageVectorCtrl::onAdd() +{ + return Parent::onAdd(); +} + + +void GuiMessageVectorCtrl::onRemove() +{ + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +bool GuiMessageVectorCtrl::isAttached() const +{ + return (mMessageVector != NULL); +} + + +//-------------------------------------------------------------------------- +bool GuiMessageVectorCtrl::attach(MessageVector* newAttachment) +{ + AssertFatal(newAttachment, "No attachment!"); + if (newAttachment == NULL || !isAwake()) + return false; + + if (isAttached()) { + Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl::attach: overriding attachment"); + detach(); + } + AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); + + mMessageVector = newAttachment; + mMessageVector->registerSpectator(sMVCtrlCallback, this); + + return true; +} + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::detach() +{ + if (isAttached() == false) { + Con::warnf(ConsoleLogEntry::General, "GuiMessageVectorCtrl::detach: not attached!"); + return; + } + + mMessageVector->unregisterSpectator(this); + mMessageVector = NULL; + AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::lineInserted(const U32 arg) +{ + AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!"); + + GuiScrollCtrl* pScroll = dynamic_cast(getParent()); + bool fullyScrolled = pScroll->isScrolledToBottom(); + + mSpecialMarkers.insert(arg); + createSpecialMarkers(mSpecialMarkers[arg], mMessageVector->getLine(arg).message); + + mLineWrappings.insert(arg); + createLineWrapping(mLineWrappings[arg], mMessageVector->getLine(arg).message); + + mLineElements.insert(arg); + createLineElement(mLineElements[arg], mLineWrappings[arg], mSpecialMarkers[arg]); + + U32 numLines = 0; + for (U32 i = 0; i < mLineWrappings.size(); i++) { + // We need to rebuild the physicalLineStart markers at the same time as + // we find out how many of them are left... + mLineElements[i].physicalLineStart = numLines; + + numLines += mLineWrappings[i].numLines; + } + + Point2I newExtent = getExtent(); + newExtent.y = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1)); + setExtent(newExtent); + if(fullyScrolled) + pScroll->scrollTo(0, 0x7FFFFFFF); +} + + +void GuiMessageVectorCtrl::lineDeleted(const U32 arg) +{ + AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!"); + AssertFatal(arg < mLineWrappings.size(), "Error, out of bounds line deleted!"); + + // It's a somewhat involved process to delete the lineelements... + LineElement& rElement = mLineElements[arg]; + + TextElement* walk = rElement.headLineElements; + while (walk != NULL) { + TextElement* lineWalk = walk->nextInLine; + while (lineWalk != NULL) { + TextElement* temp = lineWalk; + lineWalk = lineWalk->nextPhysicalLine; + delete temp; + } + + TextElement* temp = walk; + walk = walk->nextPhysicalLine; + delete temp; + } + rElement.headLineElements = NULL; + mLineElements.erase(arg); + + delete [] mLineWrappings[arg].startEndPairs; + mLineWrappings.erase(arg); + + delete [] mSpecialMarkers[arg].specials; + mSpecialMarkers.erase(arg); + + U32 numLines = 0; + for (U32 i = 0; i < mLineWrappings.size(); i++) { + // We need to rebuild the physicalLineStart markers at the same time as + // we find out how many of them are left... + mLineElements[i].physicalLineStart = numLines; + + numLines += mLineWrappings[i].numLines; + } + + U32 newHeight = (mProfile->mFont->getHeight() + mLineSpacingPixels) * getMax(numLines, U32(1)); + resize(getPosition(), Point2I(getWidth(), newHeight)); +} + + +void GuiMessageVectorCtrl::vectorDeleted() +{ + AssertFatal(mMessageVector != NULL, "Should not be here unless we're attached!"); + AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared out!"); + + mMessageVector = NULL; + U32 newHeight = mProfile->mFont->getHeight() + mLineSpacingPixels; + resize(getPosition(), Point2I(getWidth(), newHeight)); +} + + +void GuiMessageVectorCtrl::callbackRouter(const MessageVector::MessageCode code, + const U32 arg) +{ + switch (code) { + case MessageVector::LineInserted: + lineInserted(arg); + break; + case MessageVector::LineDeleted: + lineDeleted(arg); + break; + case MessageVector::VectorDeletion: + vectorDeleted(); + break; + } +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::createSpecialMarkers(SpecialMarkers& rSpecial, const char* string) +{ + // The first thing we need to do is create a version of the string with no uppercase + // chars for matching... + + String pLCCopyStr = String::ToLower( string ); + const char* pLCCopy = pLCCopyStr.c_str(); + + Vector tempSpecials(__FILE__, __LINE__); + Vector tempTypes(__FILE__, __LINE__); + + const char* pCurr = pLCCopy; + while (pCurr[0] != '\0') { + const char* pMinMatch = &pLCCopy[dStrlen(string)]; + U32 minMatchType = 0xFFFFFFFF; + AssertFatal(pMinMatch[0] == '\0', "Error, bad positioning of sentry..."); + + for (U32 i = 0; i < 16; i++) { + if (mAllowedMatches[i][0] == '\0') + continue; + + // Not the most efficient... + char matchBuffer[512]; + dStrncpy(matchBuffer, mAllowedMatches[i], 500); + matchBuffer[499] = '\0'; + dStrcat(matchBuffer, "://"); + + const char* pMatch = dStrstr(pCurr, (const char*)matchBuffer); + if (pMatch != NULL && pMatch < pMinMatch) { + pMinMatch = pMatch; + minMatchType = i; + } + } + + if (pMinMatch[0] != '\0') { + AssertFatal(minMatchType != 0xFFFFFFFF, "Hm, that's bad"); + // Found a match... + U32 start = pMinMatch - pLCCopy; + U32 j; + for (j = 0; pLCCopy[start + j] != '\0'; j++) { + if (pLCCopy[start + j] == '\n' || + pLCCopy[start + j] == ' ' || + pLCCopy[start + j] == '\t') + break; + } + AssertFatal(j > 0, "Error, j must be > 0 at this point!"); + U32 end = start + j - 1; + + tempSpecials.increment(); + tempSpecials.last().start = start; + tempSpecials.last().end = end; + tempTypes.push_back(minMatchType); + + pCurr = &pLCCopy[end + 1]; + } else { + // No match. This will cause the while loop to terminate... + pCurr = pMinMatch; + } + } + + if ((rSpecial.numSpecials = tempSpecials.size()) != 0) { + rSpecial.specials = new SpecialMarkers::Special[tempSpecials.size()]; + for (U32 i = 0; i < tempSpecials.size(); i++) { + rSpecial.specials[i].start = tempSpecials[i].start; + rSpecial.specials[i].end = tempSpecials[i].end; + rSpecial.specials[i].specialType = tempTypes[i]; + } + } else { + rSpecial.specials = NULL; + } +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::createLineWrapping(LineWrapping& rWrapping, const char* string) +{ + Vector tempBreaks(__FILE__, __LINE__); + + U32 i; + U32 currStart = 0; + if (dStrlen(string) != 0) { + for (i = 0; i < dStrlen(string); i++) { + if (string[i] == '\n') { + tempBreaks.increment(); + tempBreaks.last().start = currStart; + tempBreaks.last().end = i-1; + currStart = i+1; + } else if (i == dStrlen(string) - 1) { + tempBreaks.increment(); + tempBreaks.last().start = currStart; + tempBreaks.last().end = i; + currStart = i+1; + } + } + } else { + tempBreaks.increment(); + tempBreaks.last().start = 0; + tempBreaks.last().end = -1; + } + + U32 splitWidth = getWidth(); + U32 currLine = 0; + while (currLine < tempBreaks.size()) { + TempLineBreak& rLine = tempBreaks[currLine]; + if (rLine.start >= rLine.end) { + if (currLine == 0) + splitWidth -= mLineContinuationIndent; + currLine++; + continue; + } + + // Ok, there's some actual text in this line. How long is it? + U32 baseLength = mProfile->mFont->getStrNWidthPrecise((const UTF8 *)&string[rLine.start], rLine.end-rLine.start+1); + if (baseLength > splitWidth) { + // DMMNOTE: Replace with bin search eventually + U32 currPos = 0; + U32 breakPos = 0; + for (currPos = 0; currPos < rLine.end-rLine.start+1; currPos++) { + U32 currLength = mProfile->mFont->getStrNWidthPrecise((const UTF8 *)&string[rLine.start], currPos+1); + if (currLength > splitWidth) + { + // Make sure that the currPos has advanced, then set the breakPoint. + breakPos = currPos != 0 ? currPos - 1 : 0; + break; + } + } + if (currPos == rLine.end-rLine.start+1) { + AssertFatal(false, "Error, if the line must be broken, the position must be before this point!"); + currLine++; + continue; + } + + // Ok, the character at breakPos is the last valid char we can render. We + // want to scan back to the first whitespace character (which, in the bounds + // of the line, is guaranteed to be a space or a tab). + U32 originalBreak = breakPos; + while (true) { + if (string[rLine.start + breakPos] == ' ' || string[rLine.start + breakPos] == '\t') { + break; + } else { + AssertFatal(string[rLine.start + breakPos] != '\n', + "Bad characters in line range..."); + if (breakPos == 0) { + breakPos = originalBreak; + break; + } + breakPos--; + } + } + + // Ok, everything up to and including breakPos is in the currentLine. Insert + // a new line at this point, and put everything after in that line. + S32 oldStart = rLine.start; + S32 oldEnd = rLine.end; + rLine.end = rLine.start + breakPos; + + // Note that rLine is NOTNOTNOTNOT valid after this point! + tempBreaks.insert(currLine+1); + tempBreaks[currLine+1].start = oldStart + breakPos + 1; + tempBreaks[currLine+1].end = oldEnd; + } + + if (currLine == 0) + splitWidth -= mLineContinuationIndent; + currLine++; + } + + rWrapping.numLines = tempBreaks.size(); + rWrapping.startEndPairs = new LineWrapping::SEPair[tempBreaks.size()]; + for (i = 0; i < tempBreaks.size(); i++) { + rWrapping.startEndPairs[i].start = tempBreaks[i].start; + rWrapping.startEndPairs[i].end = tempBreaks[i].end; + } +} + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::createLineElement(LineElement& rElement, + LineWrapping& rWrapping, + SpecialMarkers& rSpecial) +{ + // First, do a straighforward translation of the wrapping... + TextElement** ppWalk = &rElement.headLineElements; + for (U32 i = 0; i < rWrapping.numLines; i++) { + *ppWalk = new TextElement; + + (*ppWalk)->nextInLine = NULL; + (*ppWalk)->nextPhysicalLine = NULL; + (*ppWalk)->specialReference = -1; + + (*ppWalk)->start = rWrapping.startEndPairs[i].start; + (*ppWalk)->end = rWrapping.startEndPairs[i].end; + + ppWalk = &((*ppWalk)->nextPhysicalLine); + } + + if (rSpecial.numSpecials != 0) { + // Ok. Now, walk down the lines, and split the elements into their contiuent parts... + // + TextElement* walk = rElement.headLineElements; + while (walk) { + TextElement* walkAcross = walk; + while (walkAcross) { + S32 specialMatch = -1; + for (U32 i = 0; i < rSpecial.numSpecials; i++) { + if (walkAcross->start <= rSpecial.specials[i].end && + walkAcross->end >= rSpecial.specials[i].start) { + specialMatch = i; + break; + } + } + + if (specialMatch != -1) { + // We have a match here. Break it into the appropriate number of segments. + // this will vary depending on how the overlap is occuring... + if (walkAcross->start >= rSpecial.specials[specialMatch].start) { + if (walkAcross->end <= rSpecial.specials[specialMatch].end) { + // The whole thing is part of the special + AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); + walkAcross->specialReference = specialMatch; + } else { + // The first part is in the special, the tail is out + AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); + walkAcross->nextInLine = new TextElement; + walkAcross->nextInLine->nextInLine = NULL; + walkAcross->nextInLine->nextPhysicalLine = NULL; + walkAcross->nextInLine->specialReference = -1; + + walkAcross->specialReference = specialMatch; + walkAcross->nextInLine->end = walkAcross->end; + walkAcross->end = rSpecial.specials[specialMatch].end; + walkAcross->nextInLine->start = rSpecial.specials[specialMatch].end + 1; + + AssertFatal(walkAcross->end >= walkAcross->start && walkAcross->nextInLine->end >= walkAcross->nextInLine->start, "Bad textelements generated!"); + } + + walkAcross = walkAcross->nextInLine; + } else { + if (walkAcross->end <= rSpecial.specials[specialMatch].end) { + // The first part is out of the special, the second part in. + AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); + walkAcross->nextInLine = new TextElement; + walkAcross->nextInLine->nextInLine = NULL; + walkAcross->nextInLine->nextPhysicalLine = NULL; + walkAcross->nextInLine->specialReference = specialMatch; + + walkAcross->specialReference = -1; + walkAcross->nextInLine->end = walkAcross->end; + walkAcross->end = rSpecial.specials[specialMatch].start - 1; + walkAcross->nextInLine->start = rSpecial.specials[specialMatch].start; + + AssertFatal(walkAcross->end >= walkAcross->start && walkAcross->nextInLine->end >= walkAcross->nextInLine->start, "Bad textelements generated!"); + walkAcross = walkAcross->nextInLine; + } else { + // First out, middle in, last out. Oy. + AssertFatal(walkAcross->nextInLine == NULL, "Bad assumption!"); + walkAcross->nextInLine = new TextElement; + walkAcross->nextInLine->nextInLine = NULL; + walkAcross->nextInLine->nextPhysicalLine = NULL; + walkAcross->nextInLine->specialReference = specialMatch; + + walkAcross->nextInLine->nextInLine = new TextElement; + walkAcross->nextInLine->nextInLine->nextInLine = NULL; + walkAcross->nextInLine->nextInLine->nextPhysicalLine = NULL; + walkAcross->nextInLine->nextInLine->specialReference = -1; + + walkAcross->nextInLine->start = rSpecial.specials[specialMatch].start; + walkAcross->nextInLine->end = rSpecial.specials[specialMatch].end; + walkAcross->nextInLine->nextInLine->start = rSpecial.specials[specialMatch].end+1; + walkAcross->nextInLine->nextInLine->end = walkAcross->end; + walkAcross->end = walkAcross->nextInLine->start - 1; + AssertFatal((walkAcross->end >= walkAcross->start && + walkAcross->nextInLine->end >= walkAcross->nextInLine->start && + walkAcross->nextInLine->nextInLine->end >= walkAcross->nextInLine->nextInLine->start), + "Bad textelements generated!"); + walkAcross = walkAcross->nextInLine->nextInLine; + } + } + } else { + walkAcross = walkAcross->nextInLine; + } + } + + walk = walk->nextPhysicalLine; + } + } +} + + +//-------------------------------------------------------------------------- +bool GuiMessageVectorCtrl::onWake() +{ + if (Parent::onWake() == false) + return false; + + if (bool(mProfile->mFont) == false) + return false; + + mMinSensibleWidth = 1; + + for (U32 i = 0; i < 256; i++) { + if (mProfile->mFont->isValidChar(U8(i))) { + if (mProfile->mFont->getCharWidth(U8(i)) > mMinSensibleWidth) + mMinSensibleWidth = mProfile->mFont->getCharWidth(U8(i)); + } + } + + AssertFatal(mLineWrappings.size() == 0, "Error, line wrappings not properly cleared!"); + return true; +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::onSleep() +{ + if (isAttached()) + detach(); + + Parent::onSleep(); +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::onRender(Point2I offset, + const RectI& updateRect) +{ + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + Parent::onRender(offset, updateRect); + if (isAttached()) { + U32 linePixels = mProfile->mFont->getHeight() + mLineSpacingPixels; + U32 currLine = 0; + for (U32 i = 0; i < mMessageVector->getNumLines(); i++) { + + TextElement* pElement = mLineElements[i].headLineElements; + ColorI lastColor = mProfile->mFontColor; + while (pElement != NULL) { + Point2I localStart(pElement == mLineElements[i].headLineElements ? 0 : mLineContinuationIndent, currLine * linePixels); + + Point2I globalCheck = localToGlobalCoord(localStart); + U32 globalRangeStart = globalCheck.y; + U32 globalRangeEnd = globalCheck.y + mProfile->mFont->getHeight(); + if (globalRangeStart > updateRect.point.y + updateRect.extent.y || + globalRangeEnd < updateRect.point.y) { + currLine++; + pElement = pElement->nextPhysicalLine; + continue; + } + + TextElement* walkAcross = pElement; + while (walkAcross) { + if (walkAcross->start > walkAcross->end) + break; + + Point2I globalStart = localToGlobalCoord(localStart); + + U32 strWidth; + if (walkAcross->specialReference == -1) { + drawer->setBitmapModulation(lastColor); + drawer->setTextAnchorColor(mProfile->mFontColor); + strWidth = drawer->drawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], + walkAcross->end - walkAcross->start + 1, mProfile->mFontColors, mMaxColorIndex); + } else { + drawer->getBitmapModulation( &lastColor ); + drawer->setBitmapModulation(mSpecialColor); + drawer->setTextAnchorColor(mProfile->mFontColor); + strWidth = drawer->drawTextN(mProfile->mFont, globalStart, &mMessageVector->getLine(i).message[walkAcross->start], + walkAcross->end - walkAcross->start + 1); + + // in case we have 2 in a row... + drawer->setBitmapModulation(lastColor); + } + + if (walkAcross->specialReference != -1) { + Point2I lineStart = localStart; + Point2I lineEnd = localStart; + lineStart.y += mProfile->mFont->getBaseline() + 1; + lineEnd.x += strWidth; + lineEnd.y += mProfile->mFont->getBaseline() + 1; + + drawer->drawLine(localToGlobalCoord(lineStart), + localToGlobalCoord(lineEnd), + mSpecialColor); + } + + localStart.x += strWidth; + walkAcross = walkAcross->nextInLine; + } + + currLine++; + pElement = pElement->nextPhysicalLine; + } + } + drawer->clearBitmapModulation(); + } +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::inspectPostApply() +{ + Parent::inspectPostApply(); +} + + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::parentResized(const RectI& oldParentRect, const RectI& newParentRect) +{ + Parent::parentResized(oldParentRect, newParentRect); + + // If we have a MesssageVector, detach/reattach so we can reflow the text. + if (mMessageVector) + { + MessageVector *reflowme = mMessageVector; + + detach(); + attach(reflowme); + } +} + +//-------------------------------------------------------------------------- +void GuiMessageVectorCtrl::findSpecialFromCoord(const Point2I& point, S32* specialLine, S32* specialRef) +{ + if (mLineElements.size() == 0) { + *specialLine = -1; + *specialRef = -1; + return; + } + + U32 linePixels = mProfile->mFont->getHeight() + mLineSpacingPixels; + + if ((point.x < 0 || point.x >= getWidth()) || + (point.y < 0 || point.y >= getHeight())) { + *specialLine = -1; + *specialRef = -1; + return; + } + + // Ok, we have real work to do here. Let's determine the physical line that it's on... + U32 physLine = point.y / linePixels; + AssertFatal(physLine >= 0, "Bad physical line!"); + + // And now we find the LineElement that contains that physicalLine... + U32 elemIndex; + for (elemIndex = 0; elemIndex < mLineElements.size(); elemIndex++) { + if (mLineElements[elemIndex].physicalLineStart > physLine) { + // We've passed it. + AssertFatal(elemIndex != 0, "Error, bad elemIndex, check assumptions."); + elemIndex--; + break; + } + } + if (elemIndex == mLineElements.size()) { + // On the last line... + elemIndex = mLineElements.size() - 1; + } + + TextElement* line = mLineElements[elemIndex].headLineElements; + for (U32 i = 0; i < physLine - mLineElements[elemIndex].physicalLineStart; i++) + { + if (line->nextPhysicalLine == NULL) + { + *specialLine = -1; + *specialRef = -1; + return; + } + line = line->nextPhysicalLine; + AssertFatal(line != NULL, "Error, moved too far!"); + } + + // Ok, line represents the current line. We now need to find out which textelement + // the points x coord falls in. + U32 currX = 0; + if ((physLine - mLineElements[elemIndex].physicalLineStart) != 0) { + currX = mLineContinuationIndent; + // First, if this isn't the first line in this wrapping, we have to make sure + // that the coord isn't in the margin... + if (point.x < mLineContinuationIndent) { + *specialLine = -1; + *specialRef = -1; + return; + } + } + if (line->start > line->end) { + // Empty line special case... + *specialLine = -1; + *specialRef = -1; + return; + } + + while (line) { + U32 newX = currX + mProfile->mFont->getStrNWidth((const UTF8 *)mMessageVector->getLine(elemIndex).message, + line->end - line->start + 1); + if (point.x < newX) { + // That's the one! + *specialLine = elemIndex; + *specialRef = line->specialReference; + return; + } + + currX = newX; + line = line->nextInLine; + } + + // Off to the right. Aw... + *specialLine = -1; + *specialRef = -1; +} + + +void GuiMessageVectorCtrl::onMouseDown(const GuiEvent& event) +{ + Parent::onMouseDown(event); + mouseUnlock(); + + mMouseDown = true; + + // Find the special we are in, if any... + findSpecialFromCoord(globalToLocalCoord(event.mousePoint), + &mMouseSpecialLine, &mMouseSpecialRef); +} + +void GuiMessageVectorCtrl::onMouseUp(const GuiEvent& event) +{ + Parent::onMouseUp(event); + mouseUnlock(); + + // Is this an up from a dragged click? + if (mMouseDown == false) + return; + + // Find the special we are in, if any... + + S32 currSpecialLine; + S32 currSpecialRef; + findSpecialFromCoord(globalToLocalCoord(event.mousePoint), &currSpecialLine, &currSpecialRef); + + if (currSpecialRef != -1 && + (currSpecialLine == mMouseSpecialLine && + currSpecialRef == mMouseSpecialRef)) { + // Execute the callback + SpecialMarkers& rSpecial = mSpecialMarkers[currSpecialLine]; + S32 specialStart = rSpecial.specials[currSpecialRef].start; + S32 specialEnd = rSpecial.specials[currSpecialRef].end; + + char* copyURL = new char[specialEnd - specialStart + 2]; + dStrncpy(copyURL, &mMessageVector->getLine(currSpecialLine).message[specialStart], specialEnd - specialStart + 1); + copyURL[specialEnd - specialStart + 1] = '\0'; + + Con::executef(this, "urlClickCallback", copyURL); + delete [] copyURL; + } + + mMouseDown = false; + mMouseSpecialLine = -1; + mMouseSpecialRef = -1; +} + diff --git a/gui/game/guiMessageVectorCtrl.h b/gui/game/guiMessageVectorCtrl.h new file mode 100644 index 0000000..e1122d0 --- /dev/null +++ b/gui/game/guiMessageVectorCtrl.h @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMESSAGEVECTORCTRL_H_ +#define _GUIMESSAGEVECTORCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _MESSAGEVECTOR_H_ +#include "gui/utility/messageVector.h" +#endif + +/// Render a MessageVector (chat HUD) +/// +/// This renders messages from a MessageVector; the important thing +/// here is that the MessageVector holds all the messages we care +/// about, while we can destroy and create these GUI controls as +/// needed. (For instance, Tribes 2 used seperate GuiMessageVectorCtrl +/// controls in the different HUD modes.) +class GuiMessageVectorCtrl : public GuiControl +{ + typedef GuiControl Parent; + + //-------------------------------------- Public interfaces... + public: + struct SpecialMarkers { + struct Special { + S32 specialType; + S32 start; + S32 end; + }; + + U32 numSpecials; + Special* specials; + }; + + GuiMessageVectorCtrl(); + ~GuiMessageVectorCtrl(); + + bool isAttached() const; + bool attach(MessageVector*); + void detach(); + + // Gui control overrides + protected: + bool onAdd(); + void onRemove(); + + bool onWake(); + void onSleep(); + void onRender(Point2I offset, const RectI &updateRect); + void inspectPostApply(); + void parentResized(const RectI& oldParentRect, const RectI& newParentRect); + + void onMouseUp(const GuiEvent &event); + void onMouseDown(const GuiEvent &event); +// void onMouseMove(const GuiEvent &event); + + // Overrideables + protected: + virtual void lineInserted(const U32); + virtual void lineDeleted(const U32); + virtual void vectorDeleted(); + + MessageVector* mMessageVector; + + // Font resource + protected: + U32 mMinSensibleWidth; + + U32 mLineSpacingPixels; + U32 mLineContinuationIndent; + + StringTableEntry mAllowedMatches[16]; + ColorI mSpecialColor; + + U32 mMaxColorIndex; + + bool mMouseDown; + S32 mMouseSpecialLine; + S32 mMouseSpecialRef; + + /// Derived classes must keep this set of variables consistent if they + /// use this classes onRender. Note that this has the fairly large + /// disadvantage of requiring lots of ugly, tiny mem allocs. A rewrite + /// to a more memory friendly version might be a good idea... + struct LineWrapping { + struct SEPair { + S32 start; + S32 end; + }; + + U32 numLines; + SEPair* startEndPairs; /// start[linex] = startEndPairs[linex*2+0] + /// end[linex] = startEndPairs[linex*2+1] + /// if end < start, line is empty... + }; + + struct TextElement { + TextElement* nextInLine; + TextElement* nextPhysicalLine; + + S32 specialReference; /// if (!= -1), indicates a special reference + + S32 start; + S32 end; + }; + struct LineElement { + U32 physicalLineStart; + + TextElement* headLineElements; + }; + + Vector mLineWrappings; + Vector mSpecialMarkers; + Vector mLineElements; + + void createLineWrapping(LineWrapping&, const char*); + void createSpecialMarkers(SpecialMarkers&, const char*); + void createLineElement(LineElement&, LineWrapping&, SpecialMarkers&); + + void findSpecialFromCoord(const Point2I&, S32*, S32*); + + public: + void callbackRouter(const MessageVector::MessageCode, const U32); + + public: + + DECLARE_CONOBJECT(GuiMessageVectorCtrl); + DECLARE_CATEGORY( "Gui Game" ); + + static void initPersistFields(); +}; + +#endif // _H_GUIMESSAGEVECTORCTRL_ diff --git a/gui/game/guiProgressBitmapCtrl.cpp b/gui/game/guiProgressBitmapCtrl.cpp new file mode 100644 index 0000000..6be1f4a --- /dev/null +++ b/gui/game/guiProgressBitmapCtrl.cpp @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/game/guiProgressBitmapCtrl.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiProgressBitmapCtrl); + +GuiProgressBitmapCtrl::GuiProgressBitmapCtrl() +{ + mProgress = 0.0f; + mBitmapName = StringTable->insert(""); + mUseVariable = false; + mTile = false; +} + +void GuiProgressBitmapCtrl::initPersistFields() +{ + addGroup("GuiProgressBitmapCtrl"); + addField( "bitmap", TypeFilename, Offset( mBitmapName, GuiProgressBitmapCtrl ) ); + endGroup("GuiProgressBitmapCtrl"); + Parent::initPersistFields(); +} + +void GuiProgressBitmapCtrl::setBitmap(const char *name) +{ + bool awake = mAwake; + if(awake) + onSleep(); + + mBitmapName = StringTable->insert(name); + if(awake) + onWake(); + setUpdate(); +} + +const char* GuiProgressBitmapCtrl::getScriptValue() +{ + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%g", mProgress); + return ret; +} + +void GuiProgressBitmapCtrl::setScriptValue(const char *value) +{ + //set the value + if (! value) + mProgress = 0.0f; + else + mProgress = dAtof(value); + + //validate the value + mProgress = mClampF(mProgress, 0.f, 1.f); + setUpdate(); +} + +void GuiProgressBitmapCtrl::onPreRender() +{ + const char * var = getVariable(); + if(var) + { + F32 value = mClampF(dAtof(var), 0.f, 1.f); + if(value != mProgress) + { + mProgress = value; + setUpdate(); + } + } +} + +void GuiProgressBitmapCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + RectI ctrlRect(offset, getExtent()); + + //grab lowest dimension + if(getHeight() <= getWidth()) + mDim = getHeight(); + else + mDim = getWidth(); + + GFX->getDrawUtil()->clearBitmapModulation(); + + if(mNumberOfBitmaps == 1) + { + //draw the progress with image + S32 width = (S32)((F32)(getWidth()) * mProgress); + if (width > 0) + { + //drawing stretch bitmap + RectI progressRect = ctrlRect; + progressRect.extent.x = width; + GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->mTextureObject, progressRect, mProfile->mBitmapArrayRects[0]); + } + } + else if(mNumberOfBitmaps >= 3) + { + //drawing left-end bitmap + RectI progressRectLeft(ctrlRect.point.x, ctrlRect.point.y, mDim, mDim); + GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->mTextureObject, progressRectLeft, mProfile->mBitmapArrayRects[0]); + + //draw the progress with image + S32 width = (S32)((F32)(getWidth()) * mProgress); + if (width > mDim) + { + //drawing stretch bitmap + RectI progressRect = ctrlRect; + progressRect.point.x += mDim; + progressRect.extent.x = (width - mDim - mDim); + if (progressRect.extent.x < 0) + progressRect.extent.x = 0; + GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->mTextureObject, progressRect, mProfile->mBitmapArrayRects[1]); + + //drawing right-end bitmap + RectI progressRectRight(progressRect.point.x + progressRect.extent.x, ctrlRect.point.y, mDim, mDim ); + GFX->getDrawUtil()->drawBitmapStretchSR(mProfile->mTextureObject, progressRectRight, mProfile->mBitmapArrayRects[2]); + } + } + else + Con::warnf("guiProgressBitmapCtrl only processes an array of bitmaps == 1 or >= 3"); + + //if there's a border, draw it + if (mProfile->mBorder) + GFX->getDrawUtil()->drawRect(ctrlRect, mProfile->mBorderColor); + + Parent::onRender( offset, updateRect ); + + //render the children + renderChildControls(offset, updateRect); + +} + +bool GuiProgressBitmapCtrl::onWake() +{ + if(!Parent::onWake()) + return false; + + mNumberOfBitmaps = mProfile->constructBitmapArray(); + + return true; +} +void GuiProgressBitmapCtrl::onSleep() +{ + Parent::onSleep(); +} + +ConsoleMethod( GuiProgressBitmapCtrl, setBitmap, void, 3, 3, "(string filename)" + "Set the bitmap contained in this control.") +{ + object->setBitmap( argv[2] ); +} \ No newline at end of file diff --git a/gui/game/guiProgressBitmapCtrl.h b/gui/game/guiProgressBitmapCtrl.h new file mode 100644 index 0000000..864c3a2 --- /dev/null +++ b/gui/game/guiProgressBitmapCtrl.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GuiProgressBitmapCtrl_H_ +#define _GuiProgressBitmapCtrl_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif + + +class GuiProgressBitmapCtrl : public GuiTextCtrl +{ +private: + typedef GuiTextCtrl Parent; + + F32 mProgress; + StringTableEntry mBitmapName; + bool mUseVariable; + bool mTile; + +public: + //creation methods + DECLARE_CONOBJECT(GuiProgressBitmapCtrl); + GuiProgressBitmapCtrl(); + + static void initPersistFields(); + void setBitmap(const char *name); + + //console related methods + virtual const char *getScriptValue(); + virtual void setScriptValue(const char *value); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); + bool onWake(); + void onSleep(); + + S32 mNumberOfBitmaps; + S32 mDim; +}; + +#endif diff --git a/gui/game/guiProgressCtrl.cpp b/gui/game/guiProgressCtrl.cpp new file mode 100644 index 0000000..0976b4f --- /dev/null +++ b/gui/game/guiProgressCtrl.cpp @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/game/guiProgressCtrl.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(GuiProgressCtrl); + +GuiProgressCtrl::GuiProgressCtrl() +{ + mProgress = 0.0f; +} + +const char* GuiProgressCtrl::getScriptValue() +{ + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%g", mProgress); + return ret; +} + +void GuiProgressCtrl::setScriptValue(const char *value) +{ + //set the value + if (! value) + mProgress = 0.0f; + else + mProgress = dAtof(value); + + //validate the value + mProgress = mClampF(mProgress, 0.f, 1.f); + setUpdate(); +} + +void GuiProgressCtrl::onPreRender() +{ + const char * var = getVariable(); + if(var) + { + F32 value = mClampF(dAtof(var), 0.f, 1.f); + if(value != mProgress) + { + mProgress = value; + setUpdate(); + } + } +} + +void GuiProgressCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + RectI ctrlRect(offset, getExtent()); + + //draw the progress + S32 width = (S32)((F32)(getWidth()) * mProgress); + if (width > 0) + { + RectI progressRect = ctrlRect; + progressRect.extent.x = width; + GFX->getDrawUtil()->drawRectFill(progressRect, mProfile->mFillColor); + } + + //now draw the border + if (mProfile->mBorder) + GFX->getDrawUtil()->drawRect(ctrlRect, mProfile->mBorderColor); + + Parent::onRender( offset, updateRect ); + + //render the children + renderChildControls(offset, updateRect); +} + diff --git a/gui/game/guiProgressCtrl.h b/gui/game/guiProgressCtrl.h new file mode 100644 index 0000000..4e40741 --- /dev/null +++ b/gui/game/guiProgressCtrl.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIPROGRESSCTRL_H_ +#define _GUIPROGRESSCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif + + +class GuiProgressCtrl : public GuiTextCtrl +{ +private: + typedef GuiTextCtrl Parent; + + F32 mProgress; + +public: + //creation methods + DECLARE_CONOBJECT(GuiProgressCtrl); + GuiProgressCtrl(); + + //console related methods + virtual const char *getScriptValue(); + virtual void setScriptValue(const char *value); + + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); +}; + +#endif diff --git a/gui/shiny/guiTheoraCtrl.cpp b/gui/shiny/guiTheoraCtrl.cpp new file mode 100644 index 0000000..c995d27 --- /dev/null +++ b/gui/shiny/guiTheoraCtrl.cpp @@ -0,0 +1,223 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifdef TORQUE_OGGTHEORA + +#include "gui/core/guiControl.h" +#include "gui/shiny/guiTheoraCtrl.h" +#include "console/consoleTypes.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxDrawUtil.h" + + +static const EnumTable::Enums transcoderEnums[] = +{ + { OggTheoraDecoder::TRANSCODER_Auto, "Auto" }, + { OggTheoraDecoder::TRANSCODER_Generic, "Generic" }, + { OggTheoraDecoder::TRANSCODER_SSE2420RGBA, "SSE2420RGBA" }, +}; +static const EnumTable gTranscoderTable( sizeof( transcoderEnums ) / sizeof( transcoderEnums[ 0 ] ), transcoderEnums ); + + +//---------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(GuiTheoraCtrl); + +GuiTheoraCtrl::GuiTheoraCtrl() +{ + mFilename = StringTable->insert(""); + mDone = false; + mStopOnSleep = false; + mMatchVideoSize = true; + mPlayOnWake = true; + mRenderDebugInfo = false; + mTranscoder = OggTheoraDecoder::TRANSCODER_Auto; + + mBackgroundColor.set( 0, 0, 0, 255); +} + +//---------------------------------------------------------------------------- +GuiTheoraCtrl::~GuiTheoraCtrl() +{ +} + +void GuiTheoraCtrl::initPersistFields() +{ + addGroup( "Playback"); + + addField( "theoraFile", TypeStringFilename, Offset( mFilename, GuiTheoraCtrl ), "Theora video file to play." ); + addField( "backgroundColor", TypeColorI, Offset( mBackgroundColor, GuiTheoraCtrl ), "Fill color when video is not playing." ); + addField( "playOnWake", TypeBool, Offset( mPlayOnWake, GuiTheoraCtrl ), "Start playing video when control is woken up." ); + addField( "stopOnSleep", TypeBool, Offset( mStopOnSleep, GuiTheoraCtrl ), "Stop video when control is set to sleep." ); + addField( "matchVideoSize", TypeBool, Offset( mMatchVideoSize, GuiTheoraCtrl ), "Match control extents with video size." ); + addField( "renderDebugInfo", TypeBool, Offset( mRenderDebugInfo, GuiTheoraCtrl ), "Render text information useful for debugging." ); + addField( "transcoder", TypeEnum, Offset( mTranscoder, GuiTheoraCtrl ), 1, &gTranscoderTable ); + + endGroup( "Playback" ); + + Parent::initPersistFields(); +} + +ConsoleMethod( GuiTheoraCtrl, setFile, void, 3, 3, "(string filename) Set an Ogg Theora file to play.") +{ + object->setFile(argv[2]); +} + +ConsoleMethod( GuiTheoraCtrl, play, void, 2, 2, "() - Start playback." ) +{ + object->play(); +} + +ConsoleMethod( GuiTheoraCtrl, pause, void, 2, 2, "() - Pause playback." ) +{ + object->pause(); +} + +ConsoleMethod( GuiTheoraCtrl, stop, void, 2, 2, "() - Stop playback.") +{ + object->stop(); +} + +ConsoleMethod( GuiTheoraCtrl, getCurrentTime, F32, 2, 2, "() - Return the time elapsed in playback, in seconds.") +{ + return object->getCurrentTime(); +} + +ConsoleMethod( GuiTheoraCtrl, isPlaybackDone, bool, 2, 2, "() - Return true if the video has finished playing." ) +{ + return object->isPlaybackDone(); +} + +void GuiTheoraCtrl::setFile( const String& filename ) +{ + mDone = false; + mFilename = filename; + mTheoraTexture.setFile( filename ); + + if( mMatchVideoSize && mTheoraTexture.isReady() ) + setExtent( Point2I( mTheoraTexture.getWidth(), mTheoraTexture.getHeight() ) ); + + OggTheoraDecoder* decoder = mTheoraTexture._getTheora(); + if( decoder != NULL ) + decoder->setTranscoder( mTranscoder ); +} + +void GuiTheoraCtrl::play() +{ + if( mFilename.isEmpty() ) + return; + + if( !mTheoraTexture.isPlaying() ) + { + mDone = false; + mTheoraTexture.play(); + } +} + +void GuiTheoraCtrl::pause() +{ + if( !mTheoraTexture.isPlaying() ) + { + Con::errorf( "GuiTheoraCtrl::pause - not playing" ); + return; + } + + mTheoraTexture.pause(); +} + +void GuiTheoraCtrl::stop() +{ + mTheoraTexture.stop(); + mDone = true; +} + +//---------------------------------------------------------------------------- +bool GuiTheoraCtrl::onWake() +{ + if( !Parent::onWake() ) + return false; + + if( !mTheoraTexture.isReady() ) + setFile( mFilename ); + + if( mPlayOnWake && !mTheoraTexture.isPlaying() ) + play(); + + return true; +} + +//---------------------------------------------------------------------------- +void GuiTheoraCtrl::onSleep() +{ + Parent::onSleep(); + + if( mStopOnSleep ) + stop(); + else + pause(); +} + +//---------------------------------------------------------------------------- +void GuiTheoraCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + const RectI rect(offset, getBounds().extent); + + if( mTheoraTexture.isReady() ) + { + mTheoraTexture.refresh(); + if( mTheoraTexture.isPlaying() + || mTheoraTexture.isPaused() ) + { + // Draw the frame. + + GFXDrawUtil* drawUtil = GFX->getDrawUtil(); + drawUtil->clearBitmapModulation(); + drawUtil->drawBitmapStretch( mTheoraTexture.getTexture(), rect ); + + // Draw frame info, if requested. + + if( mRenderDebugInfo ) + { + String info = String::ToString( "Frame Number: %i | Frame Time: %.2fs | Playback Time: %.2fs | Dropped: %i", + mTheoraTexture.getFrameNumber(), + mTheoraTexture.getFrameTime(), + F32( mTheoraTexture.getPosition() ) / 1000.f, + mTheoraTexture.getNumDroppedFrames() ); + + drawUtil->setBitmapModulation( mProfile->mFontColors[ 0 ] ); + drawUtil->drawText( mProfile->mFont, localToGlobalCoord( Point2I( 0, 0 ) ), info, mProfile->mFontColors ); + } + } + else + mDone = true; + } + else + GFX->getDrawUtil()->drawRectFill(rect, mBackgroundColor); // black rect + + renderChildControls(offset, updateRect); +} + +//---------------------------------------------------------------------------- +void GuiTheoraCtrl::inspectPostApply() +{ + if( !mTheoraTexture.getFilename().equal( mFilename, String::NoCase ) ) + { + stop(); + setFile( mFilename ); + + if( mPlayOnWake && !mTheoraTexture.isPlaying() ) + play(); + } + + if( mMatchVideoSize && mTheoraTexture.isReady() ) + setExtent( Point2I( mTheoraTexture.getWidth(), mTheoraTexture.getHeight() ) ); + + OggTheoraDecoder* decoder = mTheoraTexture._getTheora(); + if( decoder != NULL ) + decoder->setTranscoder( mTranscoder ); + + Parent::inspectPostApply(); +} + +#endif // TORQUE_OGGTHEORA diff --git a/gui/shiny/guiTheoraCtrl.h b/gui/shiny/guiTheoraCtrl.h new file mode 100644 index 0000000..fd70b5c --- /dev/null +++ b/gui/shiny/guiTheoraCtrl.h @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITHEORACTRL_H_ +#define _GUITHEORACTRL_H_ + +#ifdef TORQUE_OGGTHEORA + +#ifndef _THEORATEXTURE_H_ + #include "gfx/video/theoraTexture.h" +#endif +#ifndef _OGGTHEORADECODER_H_ + #include "core/ogg/oggTheoraDecoder.h" +#endif + + +/// Play back a Theora video file. +class GuiTheoraCtrl : public GuiControl +{ + public: + + typedef GuiControl Parent; + + protected: + + /// The Theora file we should play. + String mFilename; + + /// + TheoraTexture mTheoraTexture; + + /// If true, the control's extents will be matched to the video size. + bool mMatchVideoSize; + + /// If true, playback will start automatically when the control receives its + /// onWake(). + bool mPlayOnWake; + + /// Which transcoder to use on the Theora decoder. This is mostly + /// meant as a development aid. + OggTheoraDecoder::ETranscoder mTranscoder; + + /// If true, stop video playback when the control goes to sleep. Otherwise, + /// the video will be paused. + /// + /// @note We do not currently support to keep video running in the background + /// as the Theora decoder does not yet support skipping through bulks of + /// outdated data. This means that when the Theora texture gets its next + /// refresh, the decoder will frantically try to wade through a huge amount + /// of outdated ogg_packets which even though the actual decoding does not + /// take place takes a lot of time. + bool mStopOnSleep; + + /// Are we done with playback? + bool mDone; + + /// If true, renders some text information into the frame. + bool mRenderDebugInfo; + + /// Our background color. + ColorI mBackgroundColor; + + public: + + DECLARE_CONOBJECT( GuiTheoraCtrl ); + DECLARE_CATEGORY( "Gui Images" ); + DECLARE_DESCRIPTION( "A control for playing Theora videos." ); + + GuiTheoraCtrl(); + ~GuiTheoraCtrl(); + + static void initPersistFields(); + + void setFile( const String& filename ); + void play(); + void pause(); + void stop(); + + bool isPlaybackDone() const { return mDone; } + + // GuiControl. + virtual bool onWake(); + virtual void onSleep(); + virtual void onRender(Point2I offset, const RectI &updateRect); + virtual void inspectPostApply(); + + F32 getCurrentTime() + { + return F32( mTheoraTexture.getPosition() ) / 1000.f; + } +}; + +#endif // TORQUE_OGGTHEORA +#endif // !_GUITHEORACTRL_H_ diff --git a/gui/shiny/guiTickCtrl.cpp b/gui/shiny/guiTickCtrl.cpp new file mode 100644 index 0000000..62b4058 --- /dev/null +++ b/gui/shiny/guiTickCtrl.cpp @@ -0,0 +1,17 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "gui/shiny/guiTickCtrl.h" + +IMPLEMENT_CONOBJECT( GuiTickCtrl ); + +//------------------------------------------------------------------------------ + +ConsoleMethod( GuiTickCtrl, setProcessTicks, void, 2, 3, "( [tick = true] ) - This will set this object to either be processing ticks or not" ) +{ + if( argc == 3 ) + object->setProcessTicks( dAtob( argv[2] ) ); + else + object->setProcessTicks(); +} \ No newline at end of file diff --git a/gui/shiny/guiTickCtrl.h b/gui/shiny/guiTickCtrl.h new file mode 100644 index 0000000..b9b80da --- /dev/null +++ b/gui/shiny/guiTickCtrl.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITICKCTRL_H_ +#define _GUITICKCTRL_H_ + +#include "gui/core/guiControl.h" +#include "core/iTickable.h" + +/// This Gui Control is designed to be subclassed to let people create controls +/// which want to receive update ticks at a constant interval. This class was +/// created to be the Parent class of a control which used a DynamicTexture +/// along with a VectorField to create warping effects much like the ones found +/// in visualization displays for iTunes or Winamp. Those displays are updated +/// at the framerate frequency. This works fine for those effects, however for +/// an application of the same type of effects for things like Gui transitions +/// the framerate-driven update frequency is not desirable because it does not +/// allow the developer to be able to have any idea of a consistent user-experience. +/// +/// Enter the ITickable interface. This lets the Gui control, in this case, update +/// the dynamic texture at a constant rate of once per tick, even though it gets +/// rendered every frame, thus creating a framerate-independent update frequency +/// so that the effects are at a consistent speed regardless of the specifics +/// of the system the user is on. This means that the screen-transitions will +/// occur in the same time on a machine getting 300fps in the Gui shell as a +/// machine which gets 150fps in the Gui shell. +/// @see ITickable +class GuiTickCtrl : public GuiControl, public virtual ITickable +{ + typedef GuiControl Parent; + +private: + +protected: + + // So this can be instantiated and not be a pure virtual class + virtual void interpolateTick( F32 delta ) {}; + virtual void processTick() {}; + virtual void advanceTime( F32 timeDelta ) {}; + +public: + DECLARE_CONOBJECT( GuiTickCtrl ); + DECLARE_CATEGORY( "Gui Other" ); +}; + + +#endif \ No newline at end of file diff --git a/gui/utility/guiBubbleTextCtrl.cpp b/gui/utility/guiBubbleTextCtrl.cpp new file mode 100644 index 0000000..d17c778 --- /dev/null +++ b/gui/utility/guiBubbleTextCtrl.cpp @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/utility/guiBubbleTextCtrl.h" +#include "gui/core/guiCanvas.h" + +IMPLEMENT_CONOBJECT(GuiBubbleTextCtrl); + +//------------------------------------------------------------------------------ +void GuiBubbleTextCtrl::popBubble() +{ + // Release the mouse: + mInAction = false; + mouseUnlock(); + + // Pop the dialog + getRoot()->popDialogControl(mDlg); + + // Kill the popup + mDlg->removeObject(mPopup); + mPopup->removeObject(mMLText); + mMLText->deleteObject(); + mPopup->deleteObject(); + mDlg->deleteObject(); +} + +//------------------------------------------------------------------------------ +void GuiBubbleTextCtrl::onMouseDown(const GuiEvent &event) +{ + if (mInAction) + { + popBubble(); + + return; + } + + mDlg = new GuiControl(); + AssertFatal(mDlg, "Failed to create the GuiControl for the BubbleTextCtrl"); + mDlg->setDataField( StringTable->insert("profile"), NULL, "GuiModelessDialogProfile"); + mDlg->setField("horizSizing", "width"); + mDlg->setField("vertSizing", "height"); + mDlg->setField("extent", "640 480"); + + mPopup = new GuiControl(); + AssertFatal(mPopup, "Failed to create the GuiControl for the BubbleTextCtrl"); + mPopup->setDataField( StringTable->insert("profile"), NULL, "GuiBubblePopupProfile"); + + mMLText = new GuiMLTextCtrl(); + AssertFatal(mMLText, "Failed to create the GuiMLTextCtrl for the BubbleTextCtrl"); + mMLText->setDataField( StringTable->insert("profile"), NULL, "GuiBubbleTextProfile"); + mMLText->setField("position", "2 2"); + mMLText->setField("extent", "296 51"); + + mMLText->setText((char*)mText,dStrlen(mText)); + + mMLText->registerObject(); + mPopup->registerObject(); + mDlg->registerObject(); + + mPopup->addObject(mMLText); + mDlg->addObject(mPopup); + + mPopup->resize(event.mousePoint,Point2I(300,55)); + + getRoot()->pushDialogControl(mDlg,0); + mouseLock(); + + mInAction = true; +} diff --git a/gui/utility/guiBubbleTextCtrl.h b/gui/utility/guiBubbleTextCtrl.h new file mode 100644 index 0000000..92f2d30 --- /dev/null +++ b/gui/utility/guiBubbleTextCtrl.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIBUBBLETEXTCTRL_H_ +#define _GUIBUBBLETEXTCTRL_H_ + +#ifndef _GUITEXTCTRL_H_ +#include "gui/controls/guiTextCtrl.h" +#endif +#ifndef _GUIMLTEXTCTRL_H_ +#include "gui/controls/guiMLTextCtrl.h" +#endif + +class GuiBubbleTextCtrl : public GuiTextCtrl +{ + private: + typedef GuiTextCtrl Parent; + + protected: + bool mInAction; + GuiControl *mDlg; + GuiControl *mPopup; + GuiMLTextCtrl *mMLText; + + void popBubble(); + + public: + DECLARE_CONOBJECT(GuiBubbleTextCtrl); + + GuiBubbleTextCtrl() { mInAction = false; } + + virtual void onMouseDown(const GuiEvent &event); +}; + +#endif /* _GUI_BUBBLE_TEXT_CONTROL_H_ */ diff --git a/gui/utility/guiInputCtrl.cpp b/gui/utility/guiInputCtrl.cpp new file mode 100644 index 0000000..1166fdc --- /dev/null +++ b/gui/utility/guiInputCtrl.cpp @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/utility/guiInputCtrl.h" +#include "sim/actionMap.h" + +IMPLEMENT_CONOBJECT(GuiInputCtrl); + +//------------------------------------------------------------------------------ +bool GuiInputCtrl::onWake() +{ + // Set the default profile on start-up: + if ( !mProfile ) + { + SimObject *obj = Sim::findObject("GuiInputCtrlProfile"); + if ( obj ) + mProfile = dynamic_cast( obj ); + } + + if ( !Parent::onWake() ) + return( false ); + + if( !smDesignTime ) + mouseLock(); + + setFirstResponder(); + + return( true ); +} + + +//------------------------------------------------------------------------------ +void GuiInputCtrl::onSleep() +{ + Parent::onSleep(); + mouseUnlock(); + clearFirstResponder(); +} + + +//------------------------------------------------------------------------------ +static bool isModifierKey( U16 keyCode ) +{ + switch ( keyCode ) + { + case KEY_LCONTROL: + case KEY_RCONTROL: + case KEY_LALT: + case KEY_RALT: + case KEY_LSHIFT: + case KEY_RSHIFT: + return( true ); + } + + return( false ); +} + +//------------------------------------------------------------------------------ +bool GuiInputCtrl::onInputEvent( const InputEventInfo &event ) +{ + // TODO - add POV support... + if ( event.action == SI_MAKE ) + { + if ( event.objType == SI_BUTTON + || event.objType == SI_POV + || ( ( event.objType == SI_KEY ) && !isModifierKey( event.objInst ) ) ) + { + char deviceString[32]; + if ( !ActionMap::getDeviceName( event.deviceType, event.deviceInst, deviceString ) ) + return( false ); + + const char* actionString = ActionMap::buildActionString( &event ); + + Con::executef( this, "onInputEvent", deviceString, actionString, "1" ); + return( true ); + } + } + else if ( event.action == SI_BREAK ) + { + if ( ( event.objType == SI_KEY ) && isModifierKey( event.objInst ) ) + { + char keyString[32]; + if ( !ActionMap::getKeyString( event.objInst, keyString ) ) + return( false ); + + Con::executef( this, "onInputEvent", "keyboard", keyString, "0" ); + return( true ); + } + } + + return( false ); +} diff --git a/gui/utility/guiInputCtrl.h b/gui/utility/guiInputCtrl.h new file mode 100644 index 0000000..a7f9ddd --- /dev/null +++ b/gui/utility/guiInputCtrl.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIINPUTCTRL_H_ +#define _GUIINPUTCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +class GuiInputCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + + public: + DECLARE_CONOBJECT(GuiInputCtrl); + DECLARE_CATEGORY( "Gui Other Script" ); + DECLARE_DESCRIPTION( "A control that locks the mouse and reports all input events to script." ); + + bool onWake(); + void onSleep(); + + bool onInputEvent( const InputEventInfo &event ); +}; + +#endif // _GUI_INPUTCTRL_H diff --git a/gui/utility/guiMouseEventCtrl.cpp b/gui/utility/guiMouseEventCtrl.cpp new file mode 100644 index 0000000..9c443fe --- /dev/null +++ b/gui/utility/guiMouseEventCtrl.cpp @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/utility/guiMouseEventCtrl.h" +#include "console/consoleTypes.h" + +IMPLEMENT_CONOBJECT(GuiMouseEventCtrl); + +GuiMouseEventCtrl::GuiMouseEventCtrl() +{ + mLockMouse = false; +} + +//------------------------------------------------------------------------------ +void GuiMouseEventCtrl::sendMouseEvent(const char * name, const GuiEvent & event) +{ + char buf[3][32]; + dSprintf(buf[0], 32, "%d", event.modifier); + dSprintf(buf[1], 32, "%d %d", event.mousePoint.x, event.mousePoint.y); + dSprintf(buf[2], 32, "%d", event.mouseClickCount); + Con::executef(this, name, buf[0], buf[1], buf[2]); +} + +//------------------------------------------------------------------------------ +void GuiMouseEventCtrl::initPersistFields() +{ + addField("lockMouse", TypeBool, Offset(mLockMouse, GuiMouseEventCtrl)); + + Parent::initPersistFields(); + + Con::setIntVariable("$EventModifier::LSHIFT", SI_LSHIFT); + Con::setIntVariable("$EventModifier::RSHIFT", SI_RSHIFT); + Con::setIntVariable("$EventModifier::SHIFT", SI_SHIFT); + Con::setIntVariable("$EventModifier::LCTRL", SI_LCTRL); + Con::setIntVariable("$EventModifier::RCTRL", SI_RCTRL); + Con::setIntVariable("$EventModifier::CTRL", SI_CTRL); + Con::setIntVariable("$EventModifier::LALT", SI_LALT); + Con::setIntVariable("$EventModifier::RALT", SI_RALT); + Con::setIntVariable("$EventModifier::ALT", SI_ALT); +} + +//------------------------------------------------------------------------------ +void GuiMouseEventCtrl::onMouseDown(const GuiEvent & event) +{ + if(mLockMouse) + mouseLock(); + sendMouseEvent("onMouseDown", event); +} + +void GuiMouseEventCtrl::onMouseUp(const GuiEvent & event) +{ + if(mLockMouse) + mouseUnlock(); + sendMouseEvent("onMouseUp", event); +} + +void GuiMouseEventCtrl::onMouseMove(const GuiEvent & event) +{ + sendMouseEvent("onMouseMove", event); +} + +void GuiMouseEventCtrl::onMouseDragged(const GuiEvent & event) +{ + sendMouseEvent("onMouseDragged", event); +} + +void GuiMouseEventCtrl::onMouseEnter(const GuiEvent & event) +{ + sendMouseEvent("onMouseEnter", event); +} + +void GuiMouseEventCtrl::onMouseLeave(const GuiEvent & event) +{ + sendMouseEvent("onMouseLeave", event); +} + +void GuiMouseEventCtrl::onRightMouseDown(const GuiEvent & event) +{ + if(mLockMouse) + mouseLock(); + sendMouseEvent("onRightMouseDown", event); +} + +void GuiMouseEventCtrl::onRightMouseUp(const GuiEvent & event) +{ + if(mLockMouse) + mouseUnlock(); + sendMouseEvent("onRightMouseUp", event); +} + +void GuiMouseEventCtrl::onRightMouseDragged(const GuiEvent & event) +{ + sendMouseEvent("onRightMouseDragged", event); +} diff --git a/gui/utility/guiMouseEventCtrl.h b/gui/utility/guiMouseEventCtrl.h new file mode 100644 index 0000000..9e08284 --- /dev/null +++ b/gui/utility/guiMouseEventCtrl.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIMOUSEEVENTCTRL_H_ +#define _GUIMOUSEEVENTCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + + +class GuiMouseEventCtrl : public GuiControl +{ + private: + typedef GuiControl Parent; + void sendMouseEvent(const char * name, const GuiEvent &); + + // field info + bool mLockMouse; + + public: + + GuiMouseEventCtrl(); + + // GuiControl + void onMouseDown(const GuiEvent & event); + void onMouseUp(const GuiEvent & event); + void onMouseMove(const GuiEvent & event); + void onMouseDragged(const GuiEvent & event); + void onMouseEnter(const GuiEvent & event); + void onMouseLeave(const GuiEvent & event); + void onRightMouseDown(const GuiEvent & event); + void onRightMouseUp(const GuiEvent & event); + void onRightMouseDragged(const GuiEvent & event); + + static void initPersistFields(); + + DECLARE_CONOBJECT( GuiMouseEventCtrl ); + DECLARE_CATEGORY( "Gui Other Script" ); + DECLARE_DESCRIPTION( "A control that relays all mouse events to script." ); +}; + +#endif diff --git a/gui/utility/messageVector.cpp b/gui/utility/messageVector.cpp new file mode 100644 index 0000000..be669e4 --- /dev/null +++ b/gui/utility/messageVector.cpp @@ -0,0 +1,360 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/utility/messageVector.h" +#include "core/fileObject.h" + +IMPLEMENT_CONOBJECT(MessageVector); + +//-------------------------------------- Console functions +ConsoleMethod( MessageVector, clear, void, 2, 2, "Clear the message vector.") +{ + object->clear(); +} + +ConsoleMethod( MessageVector, pushBackLine, void, 3, 4, "(string msg, int tag=0)" + "Push a line onto the back of the list.") +{ + U32 tag = 0; + if (argc == 4) + tag = dAtoi(argv[3]); + + object->pushBackLine(argv[2], tag); +} + +ConsoleMethod( MessageVector, popBackLine, bool, 2, 2, "()" + "Pop a line from the back of the list; destroys the line.") +{ + if (object->getNumLines() == 0) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::popBackLine(): underflow"); + return false; + } + + object->popBackLine(); + return true; +} + +ConsoleMethod( MessageVector, pushFrontLine, void, 3, 4, "(string msg, int tag=0)" + "Push a line onto the front of the vector.") +{ + U32 tag = 0; + if (argc == 4) + tag = dAtoi(argv[3]); + + object->pushFrontLine(argv[2], tag); +} + +ConsoleMethod( MessageVector, popFrontLine, bool, 2, 2, + "Pop a line from the front of the vector, destroying the line.") +{ + if (object->getNumLines() == 0) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::popFrontLine(): underflow"); + return false; + } + + object->popFrontLine(); + return true; +} + + +ConsoleMethod( MessageVector, insertLine, bool, 4, 5, "(int insertPos, string msg, int tag=0)" + "Insert a new line into the vector at the specified position.") +{ + U32 pos = U32(dAtoi(argv[2])); + if (pos > object->getNumLines()) + return false; + + S32 tag = 0; + if (argc == 5) + tag = dAtoi(argv[4]); + + object->insertLine(pos, argv[3], tag); + return true; +} + +ConsoleMethod( MessageVector, deleteLine, bool, 3, 3, "(int deletePos)" + "Delete the line at the specified position.") +{ + U32 pos = U32(dAtoi(argv[2])); + if (pos >= object->getNumLines()) + return false; + + object->deleteLine(pos); + return true; +} + +ConsoleMethod( MessageVector, dump, void, 3, 4, "(string filename, string header=NULL)" + "Dump the message vector to a file, optionally prefixing a header.") +{ + + if ( argc == 4 ) + object->dump( argv[2], argv[3] ); + else + object->dump( argv[2] ); +} + +ConsoleMethod( MessageVector, getNumLines, S32, 2, 2, "Get the number of lines in the vector.") +{ + return object->getNumLines(); +} + +ConsoleMethod( MessageVector, getLineTextByTag, const char*, 3, 3, "(int tag)" + "Scan through the lines in the vector, returning the first line that has a matching tag.") +{ + U32 tag = dAtoi(argv[2]); + + for(U32 i = 0; i < object->getNumLines(); i++) + if(object->getLine(i).messageTag == tag) + return object->getLine(i).message; + return ""; +} + +ConsoleMethod( MessageVector, getLineIndexByTag, S32, 3, 3, "(int tag)" + "Scan through the vector, returning the line number of the first line that matches the specified tag; else returns -1 if no match was found.") +{ + U32 tag = dAtoi(argv[2]); + + for(U32 i = 0; i < object->getNumLines(); i++) + if(object->getLine(i).messageTag == tag) + return i; + return -1; +} + +ConsoleMethod( MessageVector, getLineText, const char*, 3, 3, "(int line)" + "Get the text at a specified line.") +{ + U32 pos = U32(dAtoi(argv[2])); + if (pos >= object->getNumLines()) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::getLineText(con): out of bounds line"); + return ""; + } + + return object->getLine(pos).message; +} + +ConsoleMethod( MessageVector, getLineTag, S32, 3, 3, "(int line)" + "Get the tag of a specified line.") +{ + U32 pos = U32(dAtoi(argv[2])); + if (pos >= object->getNumLines()) { + Con::errorf(ConsoleLogEntry::General, "MessageVector::getLineTag(con): out of bounds line"); + return 0; + } + + return object->getLine(pos).messageTag; +} + +//-------------------------------------------------------------------------- +MessageVector::MessageVector() +{ + VECTOR_SET_ASSOCIATION(mMessageLines); + VECTOR_SET_ASSOCIATION(mSpectators); +} + + +//-------------------------------------------------------------------------- +MessageVector::~MessageVector() +{ + for (U32 i = 0; i < mMessageLines.size(); i++) { + char* pDelete = const_cast(mMessageLines[i].message); + delete [] pDelete; + mMessageLines[i].message = 0; + mMessageLines[i].messageTag = 0xFFFFFFFF; + } + mMessageLines.clear(); +} + + +//-------------------------------------------------------------------------- +void MessageVector::initPersistFields() +{ + Parent::initPersistFields(); +} + + + +//-------------------------------------------------------------------------- +bool MessageVector::onAdd() +{ + return Parent::onAdd(); +} + + +//-------------------------------------------------------------------------- +void MessageVector::onRemove() +{ + // Delete all the lines from the observers, and then forcibly detatch ourselves + // + for (S32 i = mMessageLines.size() - 1; i >= 0; i--) + spectatorMessage(LineDeleted, i); + spectatorMessage(VectorDeletion, 0); + mSpectators.clear(); + + Parent::onRemove(); +} + + +//-------------------------------------------------------------------------- +void MessageVector::pushBackLine(const char* newMessage, const S32 newMessageTag) +{ + insertLine(mMessageLines.size(), newMessage, newMessageTag); +} + + +void MessageVector::popBackLine() +{ + AssertFatal(mMessageLines.size() != 0, "MessageVector::popBackLine: nothing to pop!"); + if (mMessageLines.size() == 0) + return; + + deleteLine(mMessageLines.size() - 1); +} + +void MessageVector::clear() +{ + while(mMessageLines.size()) + deleteLine(mMessageLines.size() - 1); +} + +//-------------------------------------------------------------------------- +void MessageVector::pushFrontLine(const char* newMessage, const S32 newMessageTag) +{ + insertLine(0, newMessage, newMessageTag); +} + + +void MessageVector::popFrontLine() +{ + AssertFatal(mMessageLines.size() != 0, "MessageVector::popBackLine: nothing to pop!"); + if (mMessageLines.size() == 0) + return; + + deleteLine(0); +} + + +//-------------------------------------------------------------------------- +void MessageVector::insertLine(const U32 position, + const char* newMessage, + const S32 newMessageTag) +{ + AssertFatal(position >= 0 && position <= mMessageLines.size(), "MessageVector::insertLine: out of range position!"); + AssertFatal(newMessage != NULL, "Error, no message to insert!"); + + U32 len = dStrlen(newMessage) + 1; + char* copy = new char[len]; + dStrcpy(copy, newMessage); + + mMessageLines.insert(position); + mMessageLines[position].message = copy; + mMessageLines[position].messageTag = newMessageTag; + + // Notify of insert + spectatorMessage(LineInserted, position); +} + + +//-------------------------------------------------------------------------- +void MessageVector::deleteLine(const U32 position) +{ + AssertFatal(position >= 0 && position < mMessageLines.size(), "MessageVector::deleteLine: out of range position!"); + + char* pDelete = const_cast(mMessageLines[position].message); + delete [] pDelete; + mMessageLines[position].message = NULL; + mMessageLines[position].messageTag = 0xFFFFFFFF; + + mMessageLines.erase(position); + + // Notify of delete + spectatorMessage(LineDeleted, position); +} + + +//-------------------------------------------------------------------------- +bool MessageVector::dump( const char* filename, const char* header ) +{ + Con::printf( "Dumping message vector %s to %s...", getName(), filename ); + FileObject file; + if ( !file.openForWrite( filename ) ) + return( false ); + + // If passed a header line, write it out first: + if ( header ) + file.writeLine( (const U8*) header ); + + // First write out the record count: + char* lineBuf = (char*) dMalloc( 10 ); + dSprintf( lineBuf, 10, "%d", mMessageLines.size() ); + file.writeLine( (const U8*) lineBuf ); + + // Write all of the lines of the message vector: + U32 len; + for ( U32 i = 0; i < mMessageLines.size(); i++ ) + { + len = ( dStrlen( mMessageLines[i].message ) * 2 ) + 10; + lineBuf = (char*) dRealloc( lineBuf, len ); + dSprintf( lineBuf, len, "%d ", mMessageLines[i].messageTag ); + expandEscape( lineBuf + dStrlen( lineBuf ), mMessageLines[i].message ); + file.writeLine( (const U8*) lineBuf ); + } + + file.close(); + return( true ); +} + + +//-------------------------------------------------------------------------- +void MessageVector::registerSpectator(SpectatorCallback callBack, void *spectatorKey) +{ + AssertFatal(callBack != NULL, "Error, must have a callback!"); + + // First, make sure that this hasn't been registered already... + U32 i; + for (i = 0; i < mSpectators.size(); i++) { + AssertFatal(mSpectators[i].key != spectatorKey, "Error, spectator key already registered!"); + if (mSpectators[i].key == spectatorKey) + return; + } + + mSpectators.increment(); + mSpectators.last().callback = callBack; + mSpectators.last().key = spectatorKey; + + // Need to message this spectator of all the lines currently inserted... + for (i = 0; i < mMessageLines.size(); i++) { + (*mSpectators.last().callback)(mSpectators.last().key, + LineInserted, i); + } +} + +void MessageVector::unregisterSpectator(void * spectatorKey) +{ + for (U32 i = 0; i < mSpectators.size(); i++) { + if (mSpectators[i].key == spectatorKey) { + // Need to message this spectator of all the lines currently inserted... + for (S32 j = mMessageLines.size() - 1; j >= 0 ; j--) { + (*mSpectators[i].callback)(mSpectators[i].key, + LineDeleted, j); + } + + mSpectators.erase(i); + return; + } + } + + AssertFatal(false, "MessageVector::unregisterSpectator: tried to unregister a spectator that isn't subscribed!"); + Con::errorf(ConsoleLogEntry::General, + "MessageVector::unregisterSpectator: tried to unregister a spectator that isn't subscribed!"); +} + +void MessageVector::spectatorMessage(MessageCode code, const U32 arg) +{ + for (U32 i = 0; i < mSpectators.size(); i++) { + (*mSpectators[i].callback)(mSpectators[i].key, + code, arg); + } +} + diff --git a/gui/utility/messageVector.h b/gui/utility/messageVector.h new file mode 100644 index 0000000..3aebd5e --- /dev/null +++ b/gui/utility/messageVector.h @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MESSAGEVECTOR_H_ +#define _MESSAGEVECTOR_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +/// Store a list of chat messages. +/// +/// This is responsible for managing messages which appear in the chat HUD. +/// +/// @see GuiMessageVectorCtrl for more details on how this is used. +class MessageVector : public SimObject +{ + typedef SimObject Parent; + + //-------------------------------------- Public interface... + public: + MessageVector(); + ~MessageVector(); + + public: + struct MessageLine { + char* message; + S32 messageTag; + }; + + + // Spectator registration... + public: + enum MessageCode { + LineInserted = 0, + LineDeleted = 1, + + VectorDeletion = 2 + }; + + typedef void (*SpectatorCallback)(void * spectatorKey, + const MessageCode code, + const U32 argument); + + void registerSpectator(SpectatorCallback, void * spectatorKey); + void unregisterSpectator(void *spectatorKey); + + // Query functions + public: + U32 getNumLines() const; + const MessageLine& getLine(const U32 line) const; + + // Mutators + public: + void pushBackLine(const char*, const S32); + void popBackLine(); + void pushFrontLine(const char*, const S32); + void popFrontLine(); + void clear(); + + virtual void insertLine(const U32 position, const char*, const S32); + virtual void deleteLine(const U32); + + bool dump( const char* filename, const char* header = NULL ); + + + //-------------------------------------- Internal interface + protected: + bool onAdd(); + void onRemove(); + + private: + struct SpectatorRef { + SpectatorCallback callback; + void * key; + }; + + Vector mMessageLines; + + Vector mSpectators; + void spectatorMessage(MessageCode, const U32 arg); + + public: + DECLARE_CONOBJECT(MessageVector); + static void initPersistFields(); +}; + + +//-------------------------------------------------------------------------- +inline U32 MessageVector::getNumLines() const +{ + return mMessageLines.size(); +} + +//-------------------------------------------------------------------------- +inline const MessageVector::MessageLine& MessageVector::getLine(const U32 line) const +{ + AssertFatal(line < mMessageLines.size(), "MessageVector::getLine: out of bounds line index"); + return mMessageLines[line]; +} + +#endif // _H_GUICHATVECTOR_ diff --git a/gui/worldEditor/creator.cpp b/gui/worldEditor/creator.cpp new file mode 100644 index 0000000..df2ea14 --- /dev/null +++ b/gui/worldEditor/creator.cpp @@ -0,0 +1,453 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/worldEditor/creator.h" + +#include "gfx/gfxDrawUtil.h" + + +IMPLEMENT_CONOBJECT(CreatorTree); + +//------------------------------------------------------------------------------ +// Class CreatorTree::Node +//------------------------------------------------------------------------------ + +CreatorTree::Node::Node() : + mFlags(0), + mParent(0), + mName(0), + mValue(0), + mId(0), + mTab(0) +{ + VECTOR_SET_ASSOCIATION(mChildren); +} + +CreatorTree::Node::~Node() +{ + for(U32 i = 0; i < mChildren.size(); i++) + delete mChildren[i]; +} + +//------------------------------------------------------------------------------ + +void CreatorTree::Node::expand(bool exp) +{ + if(exp) + { + if(mParent) + mParent->expand(exp); + mFlags.set(Node::Expanded); + } + else if(!isRoot()) + { + if(isGroup()) + for(U32 i = 0; i < mChildren.size(); i++) + mChildren[i]->expand(exp); + + mFlags.clear(Selected); + mFlags.clear(Expanded); + } +} + +//------------------------------------------------------------------------------ + +CreatorTree::Node * CreatorTree::Node::find(S32 id) +{ + if(mId == id) + return(this); + + if(!isGroup()) + return(0); + + for(U32 i = 0; i < mChildren.size(); i++) + { + Node * node = mChildren[i]->find(id); + if(node) + return(node); + } + + return(0); +} + +//------------------------------------------------------------------------------ + +bool CreatorTree::Node::isFirst() +{ + AssertFatal(!isRoot(), "CreatorTree::Node::isFirst - cannot call on root node"); + return(this == mParent->mChildren[0]); +} + +bool CreatorTree::Node::isLast() +{ + AssertFatal(!isRoot(), "CreatorTree::Node::isLast - cannot call on root node"); + return(this == mParent->mChildren[mParent->mChildren.size()-1]); +} + +bool CreatorTree::Node::hasChildItem() +{ + for(U32 i = 0; i < mChildren.size(); i++) + { + if(mChildren[i]->isGroup() && mChildren[i]->hasChildItem()) + return(true); + + if(!mChildren[i]->isGroup()) + return(true); + } + + return(false); +} + +S32 CreatorTree::Node::getSelected() +{ + for(U32 i = 0; i < mChildren.size(); i++) + { + if(mChildren[i]->isSelected()) + return(mChildren[i]->mId); + else if(mChildren[i]->isGroup()) + { + S32 ret = mChildren[i]->getSelected(); + if(ret != -1) + return(ret); + } + } + return(-1); +} + +//------------------------------------------------------------------------------ +// Class CreatorTree +//------------------------------------------------------------------------------ +CreatorTree::CreatorTree() : + mCurId(0), + mTxtOffset(5), + mRoot(0) +{ + VECTOR_SET_ASSOCIATION(mNodeList); + clear(); +} + +CreatorTree::~CreatorTree() +{ + delete mRoot; +} + +//------------------------------------------------------------------------------ + +CreatorTree::Node * CreatorTree::createNode(const char * name, const char * value, bool group, Node * parent) +{ + Node * node = new Node(); + node->mId = mCurId++; + node->mName = name ? StringTable->insert(name) : 0; + node->mValue = value ? StringTable->insert(value) : 0; + node->mFlags.set(Node::Group, group); + + // add to the parent group + if(parent) + { + node->mParent = parent; + if(!addNode(parent, node)) + { + delete node; + return(0); + } + } + + return(node); +} + +//------------------------------------------------------------------------------ + +void CreatorTree::clear() +{ + delete mRoot; + mCurId = 0; + mRoot = createNode(0, 0, true); + mRoot->mFlags.set(Node::Root | Node::Expanded); + mSize = Point2I(1,0); +} + +//------------------------------------------------------------------------------ + +bool CreatorTree::addNode(Node * parent, Node * node) +{ + if(!parent->isGroup()) + return(false); + + // + parent->mChildren.push_back(node); + return(true); +} + +//------------------------------------------------------------------------------ + +CreatorTree::Node * CreatorTree::findNode(S32 id) +{ + return(mRoot->find(id)); +} + +//------------------------------------------------------------------------------ + +void CreatorTree::sort() +{ + // groups then items by alpha +} + +//------------------------------------------------------------------------------ +ConsoleMethod( CreatorTree, addGroup, S32, 4, 4, "(string group, string name, string value)") +{ + CreatorTree::Node * grp = object->findNode(dAtoi(argv[2])); + + if(!grp || !grp->isGroup()) + return(-1); + + // return same named group if found... + for(U32 i = 0; i < grp->mChildren.size(); i++) + if(!dStricmp(argv[3], grp->mChildren[i]->mName)) + return(grp->mChildren[i]->mId); + + CreatorTree::Node * node = object->createNode(argv[3], 0, true, grp); + object->build(); + return(node ? node->getId() : -1); +} + +ConsoleMethod( CreatorTree, addItem, S32, 5, 5, "(Node group, string name, string value)") +{ + CreatorTree::Node * grp = object->findNode(dAtoi(argv[2])); + + if(!grp || !grp->isGroup()) + return -1; + + CreatorTree::Node * node = object->createNode(argv[3], argv[4], false, grp); + object->build(); + return(node ? node->getId() : -1); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( CreatorTree, fileNameMatch, bool, 5, 5, "(string world, string type, string filename)"){ + // argv[2] - world short + // argv[3] - type short + // argv[4] - filename + + // interior filenames + // 0 - world short ('b', 'x', ...) + // 1-> - type short ('towr', 'bunk', ...) + U32 typeLen = dStrlen(argv[3]); + if(dStrlen(argv[4]) < (typeLen + 1)) + return(false); + + // world + if(dToupper(argv[4][0]) != dToupper(argv[2][0])) + return(false); + + return(!dStrnicmp(argv[4]+1, argv[3], typeLen)); +} + +ConsoleMethod( CreatorTree, getSelected, S32, 2, 2, "Return a handle to the currently selected item.") +{ + return(object->getSelected()); +} + +ConsoleMethod( CreatorTree, isGroup, bool, 3, 3, "(Group g)") +{ + CreatorTree::Node * node = object->findNode(dAtoi(argv[2])); + if(node && node->isGroup()) + return(true); + return(false); +} + +ConsoleMethod( CreatorTree, getName, const char*, 3, 3, "(Node item)") +{ + CreatorTree::Node * node = object->findNode(dAtoi(argv[2])); + return(node ? node->mName : 0); +} + +ConsoleMethod( CreatorTree, getValue, const char*, 3, 3, "(Node n)") +{ + CreatorTree::Node * node = object->findNode(dAtoi(argv[2])); + return(node ? node->mValue : 0); +} + +ConsoleMethod( CreatorTree, clear, void, 2, 2, "Clear the tree.") +{ + object->clear(); +} + +ConsoleMethod( CreatorTree, getParent, S32, 3, 3, "(Node n)") +{ + CreatorTree::Node * node = object->findNode(dAtoi(argv[2])); + if(node && node->mParent) + return(node->mParent->getId()); + + return(-1); +} +//------------------------------------------------------------------------------ + +void CreatorTree::buildNode(Node * node, U32 tab) +{ + if(node->isExpanded()) + for(U32 i = 0; i < node->mChildren.size(); i++) + { + Node * child = node->mChildren[i]; + child->mTab = tab; + child->select(false); + mNodeList.push_back(child); + + // grab width + if(bool(mProfile->mFont) && child->mName) + { + S32 width = (tab + 1) * mTabSize + mProfile->mFont->getStrWidth(child->mName) + mTxtOffset; + if(width > mMaxWidth) + mMaxWidth = width; + } + + if(node->mChildren[i]->isGroup()) + buildNode(node->mChildren[i], tab+1); + } +} + +//------------------------------------------------------------------------------ + +void CreatorTree::build() +{ + mMaxWidth = 0; + mNodeList.clear(); + buildNode(mRoot, 0); + mCellSize.set( mMaxWidth + 1, 11 ); + setSize(Point2I(1, mNodeList.size())); +} + +//------------------------------------------------------------------------------ +bool CreatorTree::onWake() +{ + if(!Parent::onWake()) + return(false); + + mTabSize = 11; + + + // + build(); + mCellSize.set( mMaxWidth + 1, 11 ); + setSize(Point2I(1, mNodeList.size())); + return true; +} + +//------------------------------------------------------------------------------ + +void CreatorTree::onMouseUp(const GuiEvent & event) +{ + onAction(); +} + +void CreatorTree::onMouseDown(const GuiEvent & event) +{ + Point2I pos = globalToLocalCoord(event.mousePoint); + + bool dblClick = event.mouseClickCount > 1; + + // determine cell + Point2I cell(pos.x < 0 ? -1 : pos.x / mCellSize.x, pos.y < 0 ? -1 : pos.y / mCellSize.y); + if(cell.x >= 0 && cell.x < mSize.x && cell.y >= 0 && cell.y < mSize.y) + { + Node * node = mNodeList[cell.y]; + S32 offset = mTabSize * node->mTab; + if(node->isGroup() && node->mChildren.size() && pos.x >= offset && pos.x <= (offset + mTabSize)) + { + node->expand(!node->isExpanded()); + build(); + dblClick = false; + } + + if(pos.x >= offset) + { + if(dblClick) + node->expand(!node->isExpanded()); + build(); + node->select(true); + } + } +} + +//------------------------------------------------------------------------------ + +void CreatorTree::onMouseDragged(const GuiEvent & event) +{ + TORQUE_UNUSED(event); +} + +//------------------------------------------------------------------------------ +void CreatorTree::onRenderCell(Point2I offset, Point2I cell, bool, bool) +{ + Point2I cellOffset = offset; + + Node *node = mNodeList[cell.y]; + + // Get our points + Point2I boxStart( cellOffset.x + mTabSize * node->mTab, cellOffset.y ); + + boxStart.x += 2; + boxStart.y += 1; + + Point2I boxEnd = Point2I( boxStart ); + + boxEnd.x += 8; + boxEnd.y += 8; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + // Start drawing stuff + if( node->isGroup() ) + { + // If we need a box... + drawer->drawRectFill( boxStart, boxEnd, mProfile->mFillColor ); // Box background + drawer->drawRect( boxStart, boxEnd, mProfile->mFontColor ); // Border + + // Cross line + drawer->drawLine( boxStart.x + 2, boxStart.y + 4, boxStart.x + 7, boxStart.y + 4, mProfile->mFontColor ); + + if( !node->isExpanded() ) // If it's a [+] draw down line + drawer->drawLine( boxStart.x + 4, boxStart.y + 2, boxStart.x + 4, boxStart.y + 7, mProfile->mFontColor ); + } + else + { + // Draw horizontal line + drawer->drawLine( boxStart.x + 4, boxStart.y + 4, boxStart.x + 9, boxStart.y + 4, mProfile->mFontColor ); + + if( !node->isLast() ) // If it's a continuing one, draw a long down line + drawer->drawLine( boxStart.x + 4, boxStart.y - 6, boxStart.x + 4, boxStart.y + 10, mProfile->mFontColor ); + else // Otherwise, just a small one + drawer->drawLine( boxStart.x + 4, boxStart.y - 2, boxStart.x + 4, boxStart.y + 4, mProfile->mFontColor ); + } + + //draw in all the required continuation lines + Node *parent = node->mParent; + + while( !parent->isRoot() ) + { + if( !parent->isLast() ) + { + drawer->drawLine( cellOffset.x + ( parent->mTab * mTabSize ) + 6, + cellOffset.y - 2, + cellOffset.x + ( parent->mTab * mTabSize ) + 6, + cellOffset.y + 11, + mProfile->mFontColor ); + } + parent = parent->mParent; + } + + ColorI fontColor = mProfile->mFontColor; + if( node->isSelected() ) + fontColor = mProfile->mFontColorHL; + else if( node->isGroup() && node->hasChildItem() ) + fontColor.set( 128, 0, 0 ); + else if( !node->isGroup() ) + fontColor.set( 0, 0, 128 ); + + drawer->setBitmapModulation(fontColor); //node->isSelected() ? mProfile->mFontColorHL : mProfile->mFontColor); + drawer->drawText( mProfile->mFont, + Point2I( offset.x + mTxtOffset + mTabSize * ( node->mTab + 1 ), offset.y ), + node->mName); +} diff --git a/gui/worldEditor/creator.h b/gui/worldEditor/creator.h new file mode 100644 index 0000000..4e896de --- /dev/null +++ b/gui/worldEditor/creator.h @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CREATOR_H_ +#define _CREATOR_H_ + +#ifndef _GUIARRAYCTRL_H_ +#include "gui/core/guiArrayCtrl.h" +#endif + +class CreatorTree : public GuiArrayCtrl +{ + typedef GuiArrayCtrl Parent; + public: + + class Node + { + public: + Node(); + ~Node(); + + enum { + Group = BIT(0), + Expanded = BIT(1), + Selected = BIT(2), + Root = BIT(3) + }; + + BitSet32 mFlags; + S32 mId; + U32 mTab; + Node * mParent; + Vector mChildren; + StringTableEntry mName; + StringTableEntry mValue; + + void expand(bool exp); + void select(bool sel){mFlags.set(Selected, sel);} + + Node * find(S32 id); + + // + bool isGroup(){return(mFlags.test(Group));} + bool isExpanded(){return(mFlags.test(Expanded));} + bool isSelected(){return(mFlags.test(Selected));} + bool isRoot(){return(mFlags.test(Root));} + S32 getId(){return(mId);} + bool hasChildItem(); + S32 getSelected(); + + // + bool isFirst(); + bool isLast(); + }; + + CreatorTree(); + ~CreatorTree(); + + // + S32 mCurId; + Node * mRoot; + Vector mNodeList; + + // + void buildNode(Node * node, U32 tab); + void build(); + + // + bool addNode(Node * parent, Node * node); + Node * createNode(const char * name, const char * value, bool group = false, Node * parent = 0); + Node * findNode(S32 id); + S32 getSelected(){return(mRoot->getSelected());} + + // + void expandNode(Node * node, bool expand); + void selectNode(Node * node, bool select); + + // + void sort(); + void clear(); + + S32 mTabSize; + S32 mMaxWidth; + S32 mTxtOffset; + + // GuiControl + void onMouseDown(const GuiEvent & event); + void onMouseDragged(const GuiEvent & event); + void onMouseUp(const GuiEvent & event); + bool onWake(); + + // GuiArrayCtrl + void onRenderCell(Point2I offset, Point2I cell, bool, bool); + + DECLARE_CONOBJECT(CreatorTree); + DECLARE_CATEGORY( "Gui Editor" ); +}; + +#endif diff --git a/gui/worldEditor/editTSCtrl.cpp b/gui/worldEditor/editTSCtrl.cpp new file mode 100644 index 0000000..bbbbd48 --- /dev/null +++ b/gui/worldEditor/editTSCtrl.cpp @@ -0,0 +1,1208 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/consoleTypes.h" +#include "T3D/gameConnection.h" +#include "gui/worldEditor/editor.h" +#include "gui/worldEditor/editTSCtrl.h" +#include "gui/core/guiCanvas.h" +#include "terrain/terrData.h" +#include "T3D/sphere.h" +#include "T3D/missionArea.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "sceneGraph/sceneGraph.h" + + +IMPLEMENT_CONOBJECT(EditTSCtrl); + +Point3F EditTSCtrl::smCamPos; +MatrixF EditTSCtrl::smCamMatrix; +bool EditTSCtrl::smCamOrtho = false; +F32 EditTSCtrl::smCamNearPlane; +F32 EditTSCtrl::smVisibleDistanceScale = 1.0f; +U32 EditTSCtrl::smSceneBoundsMask = EnvironmentObjectType | TerrainObjectType | WaterObjectType | CameraObjectType; +Point3F EditTSCtrl::smMinSceneBounds = Point3F(500.0f, 500.0f, 500.0f); + +EditTSCtrl::EditTSCtrl() +{ + mGizmoProfile = NULL; + mGizmo = NULL; + + mRenderMissionArea = true; + mMissionAreaFillColor.set(255,0,0,20); + mMissionAreaFrameColor.set(255,0,0,128); + + mConsoleFrameColor.set(255,0,0,255); + mConsoleFillColor.set(255,0,0,120); + mConsoleSphereLevel = 1; + mConsoleCircleSegments = 32; + mConsoleLineWidth = 1; + mRightMousePassThru = true; + mMiddleMousePassThru = true; + + mConsoleRendering = false; + + mDisplayType = DisplayTypePerspective; + mOrthoFOV = 50.0f; + mOrthoCamTrans.set(0.0f, 0.0f, 0.0f); + + mIsoCamAngle = mDegToRad(45.0f); + mIsoCamRot = EulerF(0, 0, 0); + + mRenderGridPlane = true; + mGridPlaneOriginColor = ColorI(0, 0, 0, 255); + mGridPlaneColor = ColorI(0, 0, 0, 255); + mGridPlaneMinorTickColor = ColorI(102, 102, 102, 255); + mGridPlaneMinorTicks = 9; + mGridPlaneSize = 1.0f; + mGridPlaneSizePixelBias = 2.0f; + + mLastMousePos.set(0, 0); + + mAllowBorderMove = false; + mMouseMoveBorder = 20; + mMouseMoveSpeed = 0.1f; + mLastBorderMoveTime = 0; + mLeftMouseDown = false; + mRightMouseDown = false; + mMiddleMouseDown = false; + mMouseLeft = false; + + mBlendSB = NULL; + +} + +EditTSCtrl::~EditTSCtrl() +{ + mBlendSB = NULL; +} + +//------------------------------------------------------------------------------ + +bool EditTSCtrl::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // give all derived access to the fields + setModStaticFields(true); + + GFXStateBlockDesc blenddesc; + blenddesc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + mBlendSB = GFX->createStateBlock( blenddesc ); + + if ( !mGizmoProfile ) + { + Con::errorf( "EditTSCtrl::onadd - gizmoProfile was not assigned, cannot create control!" ); + return false; + } + + mGizmo = new Gizmo(); + mGizmo->setProfile( mGizmoProfile ); + mGizmo->registerObject(); + + return true; +} + +void EditTSCtrl::onRemove() +{ + Parent::onRemove(); + + if ( mGizmo ) + mGizmo->deleteObject(); +} + +void EditTSCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + // Perform possible mouse border move... + if(mAllowBorderMove && smCamOrtho && !mLeftMouseDown && !mRightMouseDown && !mMouseLeft) + { + Point2I ext = getExtent(); + + U32 current = Platform::getRealMilliseconds(); + bool update = false; + F32 movex = 0.0f; + F32 movey = 0.0f; + + Point2I localMouse = globalToLocalCoord(mLastMousePos); + + if(localMouse.x <= mMouseMoveBorder || localMouse.x >= ext.x - mMouseMoveBorder) + { + if(mLastBorderMoveTime != 0) + { + U32 dt = current - mLastBorderMoveTime; + if(localMouse.x <= mMouseMoveBorder) + { + movex = mMouseMoveSpeed * dt; + } + else + { + movex = -mMouseMoveSpeed * dt; + } + } + update = true; + } + + if(localMouse.y <= mMouseMoveBorder || localMouse.y >= ext.y - mMouseMoveBorder) + { + if(mLastBorderMoveTime != 0) + { + U32 dt = current - mLastBorderMoveTime; + if(localMouse.y <= mMouseMoveBorder) + { + movey = mMouseMoveSpeed * dt; + } + else + { + movey = -mMouseMoveSpeed * dt; + } + } + update = true; + } + + if(update) + { + mLastBorderMoveTime = current; + calcOrthoCamOffset(movex, movey); + } + else + { + mLastBorderMoveTime = 0; + } + } + + updateGuiInfo(); + Parent::onRender(offset, updateRect); + +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::initPersistFields() +{ + addGroup("Mission Area"); + addField("renderMissionArea", TypeBool, Offset(mRenderMissionArea, EditTSCtrl)); + addField("missionAreaFillColor", TypeColorI, Offset(mMissionAreaFillColor, EditTSCtrl)); + addField("missionAreaFrameColor", TypeColorI, Offset(mMissionAreaFrameColor, EditTSCtrl)); + endGroup("Mission Area"); + + addGroup("BorderMovement"); + addField("allowBorderMove", TypeBool, Offset(mAllowBorderMove, EditTSCtrl)); + addField("borderMovePixelSize", TypeS32, Offset(mMouseMoveBorder, EditTSCtrl)); + addField("borderMoveSpeed", TypeF32, Offset(mMouseMoveSpeed, EditTSCtrl)); + endGroup("BorderMovement"); + + addGroup("Misc"); + addField("consoleFrameColor", TypeColorI, Offset(mConsoleFrameColor, EditTSCtrl)); + addField("consoleFillColor", TypeColorI, Offset(mConsoleFillColor, EditTSCtrl)); + addField("consoleSphereLevel", TypeS32, Offset(mConsoleSphereLevel, EditTSCtrl)); + addField("consoleCircleSegments", TypeS32, Offset(mConsoleCircleSegments, EditTSCtrl)); + addField("consoleLineWidth", TypeS32, Offset(mConsoleLineWidth, EditTSCtrl)); + addField("gizmoProfile", TypeSimObjectPtr, Offset(mGizmoProfile, EditTSCtrl)); + endGroup("Misc"); + Parent::initPersistFields(); +} + +void EditTSCtrl::consoleInit() +{ + Con::addVariable("pref::WorldEditor::visibleDistanceScale", TypeF32, &EditTSCtrl::smVisibleDistanceScale); +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::make3DMouseEvent(Gui3DMouseEvent & gui3DMouseEvent, const GuiEvent & event) +{ + (GuiEvent&)(gui3DMouseEvent) = event; + gui3DMouseEvent.mousePoint = event.mousePoint; + + if(!smCamOrtho) + { + // get the eye pos and the mouse vec from that... + Point3F screenPoint((F32)gui3DMouseEvent.mousePoint.x, (F32)gui3DMouseEvent.mousePoint.y, 1.0f); + + Point3F wp; + unproject(screenPoint, &wp); + + gui3DMouseEvent.pos = smCamPos; + gui3DMouseEvent.vec = wp - smCamPos; + gui3DMouseEvent.vec.normalizeSafe(); + } + else + { + // get the eye pos and the mouse vec from that... + Point3F screenPoint((F32)gui3DMouseEvent.mousePoint.x, (F32)gui3DMouseEvent.mousePoint.y, 0.0f); + + Point3F np, fp; + unproject(screenPoint, &np); + + gui3DMouseEvent.pos = np; + smCamMatrix.getColumn( 1, &(gui3DMouseEvent.vec) ); + } +} + +//------------------------------------------------------------------------------ + +TerrainBlock* EditTSCtrl::getActiveTerrain() +{ + // Find a terrain block + SimSet* scopeAlwaysSet = Sim::getGhostAlwaysSet(); + for(SimSet::iterator itr = scopeAlwaysSet->begin(); itr != scopeAlwaysSet->end(); itr++) + { + TerrainBlock* block = dynamic_cast(*itr); + if( block ) + return block; + } + + return NULL; +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::getCursor(GuiCursor *&cursor, bool &visible, const GuiEvent &event) +{ + make3DMouseEvent(mLastEvent, event); + get3DCursor(cursor, visible, mLastEvent); +} + +void EditTSCtrl::get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event) +{ + TORQUE_UNUSED(event); + cursor = NULL; + visible = false; +} + +void EditTSCtrl::onMouseUp(const GuiEvent & event) +{ + mLeftMouseDown = false; + make3DMouseEvent(mLastEvent, event); + on3DMouseUp(mLastEvent); +} + +void EditTSCtrl::onMouseDown(const GuiEvent & event) +{ + mLeftMouseDown = true; + mLastBorderMoveTime = 0; + make3DMouseEvent(mLastEvent, event); + on3DMouseDown(mLastEvent); + + setFirstResponder(); +} + +void EditTSCtrl::onMouseMove(const GuiEvent & event) +{ + make3DMouseEvent(mLastEvent, event); + on3DMouseMove(mLastEvent); + + mLastMousePos = event.mousePoint; +} + +void EditTSCtrl::onMouseDragged(const GuiEvent & event) +{ + make3DMouseEvent(mLastEvent, event); + on3DMouseDragged(mLastEvent); +} + +void EditTSCtrl::onMouseEnter(const GuiEvent & event) +{ + mMouseLeft = false; + make3DMouseEvent(mLastEvent, event); + on3DMouseEnter(mLastEvent); +} + +void EditTSCtrl::onMouseLeave(const GuiEvent & event) +{ + mMouseLeft = true; + mLastBorderMoveTime = 0; + make3DMouseEvent(mLastEvent, event); + on3DMouseLeave(mLastEvent); +} + +void EditTSCtrl::onRightMouseDown(const GuiEvent & event) +{ + // always process the right mouse event first... + + mRightMouseDown = true; + mLastBorderMoveTime = 0; + + make3DMouseEvent(mLastEvent, event); + on3DRightMouseDown(mLastEvent); + + if(mRightMousePassThru && mProfile->mCanKeyFocus) + { + GuiCanvas *pCanvas = getRoot(); + if( !pCanvas ) + return; + + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + if( !pWindow ) + return; + + PlatformCursorController *pController = pWindow->getCursorController(); + if( !pController ) + return; + + // ok, gotta disable the mouse + // script functions are lockMouse(true); Canvas.cursorOff(); + pWindow->setMouseLocked(true); + pCanvas->setCursorON( false ); + + if(mDisplayType != DisplayTypePerspective) + { + mouseLock(); + mLastMousePos = event.mousePoint; + pCanvas->setForceMouseToGUI(true); + mLastMouseClamping = pCanvas->getClampTorqueCursor(); + pCanvas->setClampTorqueCursor(false); + } + + if(mDisplayType == DisplayTypeIsometric) + { + // Store the screen center point on the terrain for a possible rotation + TerrainBlock* activeTerrain = getActiveTerrain(); + if( activeTerrain ) + { + F32 extx, exty; + if(event.modifier & SI_SHIFT) + { + extx = F32(event.mousePoint.x); + exty = F32(event.mousePoint.y); + } + else + { + extx = getExtent().x * 0.5; + exty = getExtent().y * 0.5; + } + Point3F sp(extx, exty, 0.0f); // Near plane projection + Point3F start; + unproject(sp, &start); + + Point3F end = start + mLastEvent.vec * 4000.0f; + Point3F tStartPnt, tEndPnt; + activeTerrain->getTransform().mulP(start, &tStartPnt); + activeTerrain->getTransform().mulP(end, &tEndPnt); + + RayInfo info; + bool result = activeTerrain->castRay(tStartPnt, tEndPnt, &info); + if(result) + { + info.point.interpolate(start, end, info.t); + mIsoCamRotCenter = info.point; + } + else + { + mIsoCamRotCenter = start; + } + } + else + { + F32 extx = getExtent().x * 0.5; + F32 exty = getExtent().y * 0.5; + Point3F sp(extx, exty, 0.0f); // Near plane projection + unproject(sp, &mIsoCamRotCenter); + } + } + + setFirstResponder(); + } +} + +void EditTSCtrl::onRightMouseUp(const GuiEvent & event) +{ + mRightMouseDown = false; + make3DMouseEvent(mLastEvent, event); + on3DRightMouseUp(mLastEvent); +} + +void EditTSCtrl::onRightMouseDragged(const GuiEvent & event) +{ + make3DMouseEvent(mLastEvent, event); + on3DRightMouseDragged(mLastEvent); + + // Handle translation of orthographic views + if(mDisplayType != DisplayTypePerspective) + { + calcOrthoCamOffset((event.mousePoint.x - mLastMousePos.x), (event.mousePoint.y - mLastMousePos.y), event.modifier); + + mLastMousePos = event.mousePoint; + } +} + +void EditTSCtrl::onMiddleMouseDown(const GuiEvent & event) +{ + mMiddleMouseDown = true; + mLastBorderMoveTime = 0; + + if(mMiddleMousePassThru && mProfile->mCanKeyFocus) + { + GuiCanvas *pCanvas = getRoot(); + if( !pCanvas ) + return; + + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + if( !pWindow ) + return; + + PlatformCursorController *pController = pWindow->getCursorController(); + if( !pController ) + return; + + // ok, gotta disable the mouse + // script functions are lockMouse(true); Canvas.cursorOff(); + pWindow->setMouseLocked(true); + pCanvas->setCursorON( false ); + + // Trigger 2 is used by the camera + MoveManager::mTriggerCount[2]++; + + setFirstResponder(); + } +} + +void EditTSCtrl::onMiddleMouseUp(const GuiEvent & event) +{ + // Trigger 2 is used by the camera + MoveManager::mTriggerCount[2]++; + + mMiddleMouseDown = false; +} + +void EditTSCtrl::onMiddleMouseDragged(const GuiEvent & event) +{ +} + +bool EditTSCtrl::onMouseWheelUp( const GuiEvent &event ) +{ + if(mDisplayType != DisplayTypePerspective && !event.modifier) + { + mOrthoFOV -= 2.0f; + if(mOrthoFOV < 1.0f) + mOrthoFOV = 1.0f; + return true; + } + + make3DMouseEvent(mLastEvent, event); + on3DMouseWheelUp(mLastEvent); + + return false; +} + +bool EditTSCtrl::onMouseWheelDown( const GuiEvent &event ) +{ + if(mDisplayType != DisplayTypePerspective && !event.modifier) + { + mOrthoFOV += 2.0f; + return true; + } + + make3DMouseEvent(mLastEvent, event); + on3DMouseWheelDown(mLastEvent); + + return false; +} + + +bool EditTSCtrl::onInputEvent(const InputEventInfo & event) +{ + + if(mRightMousePassThru && event.deviceType == MouseDeviceType && + event.objInst == KEY_BUTTON1 && event.action == SI_BREAK) + { + // if the right mouse pass thru is enabled, + // we want to reactivate mouse on a right mouse button up + GuiCanvas *pCanvas = getRoot(); + if( !pCanvas ) + return false; + + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + if( !pWindow ) + return false; + + PlatformCursorController *pController = pWindow->getCursorController(); + if( !pController ) + return false; + + pWindow->setMouseLocked(false); + pCanvas->setCursorON( true ); + + if(mDisplayType != DisplayTypePerspective) + { + mouseUnlock(); + pCanvas->setForceMouseToGUI(false); + pCanvas->setClampTorqueCursor(mLastMouseClamping); + } + } + + if(mMiddleMousePassThru && event.deviceType == MouseDeviceType && + event.objInst == KEY_BUTTON2 && event.action == SI_BREAK) + { + // if the middle mouse pass thru is enabled, + // we want to reactivate mouse on a middle mouse button up + GuiCanvas *pCanvas = getRoot(); + if( !pCanvas ) + return false; + + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + if( !pWindow ) + return false; + + PlatformCursorController *pController = pWindow->getCursorController(); + if( !pController ) + return false; + + pWindow->setMouseLocked(false); + pCanvas->setCursorON( true ); + } + + // we return false so that the canvas can properly process the right mouse button up... + return false; +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::calcOrthoCamOffset(F32 mousex, F32 mousey, U8 modifier) +{ + F32 camScale = 0.01f; + + switch(mDisplayType) + { + case DisplayTypeTop: + mOrthoCamTrans.x -= mousex * mOrthoFOV * camScale; + mOrthoCamTrans.y += mousey * mOrthoFOV * camScale; + break; + + case DisplayTypeBottom: + mOrthoCamTrans.x -= mousex * mOrthoFOV * camScale; + mOrthoCamTrans.y -= mousey * mOrthoFOV * camScale; + break; + + case DisplayTypeFront: + mOrthoCamTrans.x += mousex * mOrthoFOV * camScale; + mOrthoCamTrans.z += mousey * mOrthoFOV * camScale; + break; + + case DisplayTypeBack: + mOrthoCamTrans.x -= mousex * mOrthoFOV * camScale; + mOrthoCamTrans.z += mousey * mOrthoFOV * camScale; + break; + + case DisplayTypeLeft: + mOrthoCamTrans.y += mousex * mOrthoFOV * camScale; + mOrthoCamTrans.z += mousey * mOrthoFOV * camScale; + break; + + case DisplayTypeRight: + mOrthoCamTrans.y -= mousex * mOrthoFOV * camScale; + mOrthoCamTrans.z += mousey * mOrthoFOV * camScale; + break; + + case DisplayTypeIsometric: + if(modifier & SI_PRIMARY_CTRL) + { + // NOTE: Maybe move the center of rotation code to right mouse down to avoid compound errors? + F32 rot = mDegToRad(mousex); + + Point3F campos = (mRawCamPos + mOrthoCamTrans) - mIsoCamRotCenter; + MatrixF mat(EulerF(0, 0, rot)); + mat.mulP(campos); + mOrthoCamTrans = (campos + mIsoCamRotCenter) - mRawCamPos; + mIsoCamRot.z += rot; + + } + else + { + mOrthoCamTrans.x -= mousex * mOrthoFOV * camScale * mCos(mIsoCamRot.z) - mousey * mOrthoFOV * camScale * mSin(mIsoCamRot.z); + mOrthoCamTrans.y += mousex * mOrthoFOV * camScale * mSin(mIsoCamRot.z) + mousey * mOrthoFOV * camScale * mCos(mIsoCamRot.z); + } + break; + } +} + +//------------------------------------------------------------------------------ + +void EditTSCtrl::renderWorld(const RectI & updateRect) +{ + gClientSceneGraph->setDisplayTargetResolution(getExtent()); + gClientSceneGraph->renderScene( SPT_Diffuse ); + + // render the mission area... + if ( mRenderMissionArea ) + renderMissionArea(); + + // render through console callbacks + SimSet * missionGroup = static_cast(Sim::findObject("MissionGroup")); + if(missionGroup) + { + mConsoleRendering = true; + + for(SimSetIterator itr(missionGroup); *itr; ++itr) + { + SimObject* object = *itr; + const bool renderEnabled = object->getClassRep()->isRenderEnabled(); + + if( renderEnabled ) + { + char buf[2][16]; + dSprintf(buf[0], 16, object->isSelected() ? "true" : "false"); + dSprintf(buf[1], 16, object->isExpanded() ? "true" : "false"); + + Con::executef( object, "onEditorRender", getIdString(), buf[0], buf[1] ); + } + } + + mConsoleRendering = false; + } + + // Draw the grid + if ( mRenderGridPlane ) + renderGrid(); + + // render the editor stuff + renderScene(updateRect); + + // Draw the camera axis + GFX->setClipRect(updateRect); + GFX->setStateBlock(mDefaultGuiSB); + renderCameraAxis(); +} + +void EditTSCtrl::renderMissionArea() +{ + // TODO: Fix me! + /* + MissionArea * obj = dynamic_cast(Sim::findObject("MissionAreaObject")); + if( obj == NULL ) + return; + + //EditTSCTRL has a pointer to the currently active terrain. Going to use it + //TerrainBlock * terrain = dynamic_cast(Sim::findObject("Terrain")); + + F32 height = 0.f; + + TerrainBlock* activeTerrain = getActiveTerrain(); + if( activeTerrain != NULL ) + { + GridSquare * gs = activeTerrain->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + height = F32(gs->maxHeight) * 0.03125f + 10.f; + } + + // + const RectI &area = obj->getArea(); + Box3F areaBox( area.point.x, + area.point.y, + 0, + area.point.x + area.extent.x, + area.point.y + area.extent.y, + height ); + + GFX->getDrawUtil()->drawSolidBox( areaBox, mMissionAreaFillColor ); + GFX->getDrawUtil()->drawWireBox( areaBox, mMissionAreaFrameColor ); + */ +} + +void EditTSCtrl::renderCameraAxis() +{ + static MatrixF sRotMat(EulerF( (M_PI_F / -2.0f), 0.0f, 0.0f)); + + MatrixF camMat = mLastCameraQuery.cameraMatrix; + camMat.mul(sRotMat); + camMat.inverse(); + + MatrixF axis; + axis.setColumn(0, Point3F(1, 0, 0)); + axis.setColumn(1, Point3F(0, 0, 1)); + axis.setColumn(2, Point3F(0, -1, 0)); + axis.mul(camMat); + + Point3F forwardVec, upVec, rightVec; + axis.getColumn( 2, &forwardVec ); + axis.getColumn( 1, &upVec ); + axis.getColumn( 0, &rightVec ); + + Point2I pos = getPosition(); + F32 offsetx = pos.x + 20.0; + F32 offsety = pos.y + getExtent().y - 42.0; // Take the status bar into account + F32 scale = 15.0; + + // Generate correct drawing order + ColorI c1(255,0,0); + ColorI c2(0,255,0); + ColorI c3(0,0,255); + ColorI tc; + Point3F *p1, *p2, *p3, *tp; + p1 = &rightVec; + p2 = &upVec; + p3 = &forwardVec; + if(p3->y > p2->y) + { + tp = p2; tc = c2; + p2 = p3; c2 = c3; + p3 = tp; c3 = tc; + } + if(p2->y > p1->y) + { + tp = p1; tc = c1; + p1 = p2; c1 = c2; + p2 = tp; c2 = tc; + } + + PrimBuild::begin( GFXLineList, 6 ); + //*** Axis 1 + PrimBuild::color(c1); + PrimBuild::vertex3f(offsetx, offsety, 0); + PrimBuild::vertex3f(offsetx+p1->x*scale, offsety-p1->z*scale, 0); + + //*** Axis 2 + PrimBuild::color(c2); + PrimBuild::vertex3f(offsetx, offsety, 0); + PrimBuild::vertex3f(offsetx+p2->x*scale, offsety-p2->z*scale, 0); + + //*** Axis 3 + PrimBuild::color(c3); + PrimBuild::vertex3f(offsetx, offsety, 0); + PrimBuild::vertex3f(offsetx+p3->x*scale, offsety-p3->z*scale, 0); + PrimBuild::end(); +} + +void EditTSCtrl::renderGrid() +{ + if ( mDisplayType == DisplayTypePerspective || + mDisplayType == DisplayTypeIsometric ) + return; + + // Calculate the displayed grid size based on view + F32 drawnGridSize = mGridPlaneSize; + F32 gridPixelSize = projectRadius(1.0f, mGridPlaneSize); + if(gridPixelSize < mGridPlaneSizePixelBias) + { + U32 counter = 1; + while(gridPixelSize < mGridPlaneSizePixelBias) + { + drawnGridSize = mGridPlaneSize * counter * 10.0f; + gridPixelSize = projectRadius(1.0f, drawnGridSize); + + ++counter; + + // No infinite loops here + if(counter > 1000) + break; + } + } + + F32 minorTickSize = 0; + F32 gridSize = drawnGridSize; + U32 minorTickMax = mGridPlaneMinorTicks + 1; + if(minorTickMax > 0) + { + minorTickSize = drawnGridSize; + gridSize = drawnGridSize * minorTickMax; + } + + // Build the view-based origin + VectorF dir; + smCamMatrix.getColumn( 1, &dir ); + + Point3F gridPlanePos = smCamPos + ( dir * smCamNearPlane ); + Point2F size(mOrthoWidth + 2 * gridSize, mOrthoHeight + 2 * gridSize); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + GFX->getDrawUtil()->drawPlaneGrid( desc, gridPlanePos, size, Point2F( minorTickSize, minorTickSize ), mGridPlaneMinorTickColor ); + GFX->getDrawUtil()->drawPlaneGrid( desc, gridPlanePos, size, Point2F( gridSize, gridSize ), mGridPlaneColor ); +} + +struct SceneBoundsInfo +{ + bool mValid; + Box3F mBounds; + + SceneBoundsInfo() + { + mValid = false; + mBounds.minExtents.set(1e10, 1e10, 1e10); + mBounds.maxExtents.set(-1e10, -1e10, -1e10); + } +}; + +static void sceneBoundsCalcCallback(SceneObject* obj, void *key) +{ + // Early out for those objects that slipped through the mask check + // because they belong to more than one type. + if((obj->getType() & EditTSCtrl::smSceneBoundsMask) != 0) + return; + + if(obj->isGlobalBounds()) + return; + + SceneBoundsInfo* bounds = (SceneBoundsInfo*)key; + + Point3F min = obj->getWorldBox().minExtents; + Point3F max = obj->getWorldBox().maxExtents; + + if (min.x <= -5000.0f || min.y <= -5000.0f || min.z <= -5000.0f || + min.x >= 5000.0f || min.y >= 5000.0f || min.z >= 5000.0f) + Con::errorf("SceneObject %d (%s : %s) has a bounds that could cause problems with a non-perspective view", obj->getId(), obj->getClassName(), obj->getName()); + + bounds->mBounds.minExtents.setMin(min); + bounds->mBounds.minExtents.setMin(max); + bounds->mBounds.maxExtents.setMax(min); + bounds->mBounds.maxExtents.setMax(max); + + bounds->mValid = true; +} + +bool EditTSCtrl::processCameraQuery(CameraQuery * query) +{ + if(mDisplayType == DisplayTypePerspective) + { + query->ortho = false; + } + else + { + query->ortho = true; + } + + GameConnection* connection = dynamic_cast(NetConnection::getConnectionToServer()); + if (connection) + { + if (connection->getControlCameraTransform(0.032f, &query->cameraMatrix)) + { + query->farPlane = gClientSceneGraph->getVisibleDistance() * smVisibleDistanceScale; + query->nearPlane = gClientSceneGraph->getNearClip(); + query->fov = mDegToRad(90.0f); + + if(query->ortho) + { + MatrixF camRot(true); + SceneBoundsInfo sceneBounds; + const F32 camBuffer = 1.0f; + Point3F camPos = query->cameraMatrix.getPosition(); + + F32 isocamplanedist = 0.0f; + if(mDisplayType == DisplayTypeIsometric) + { + const RectI& vp = GFX->getViewport(); + isocamplanedist = 0.25 * vp.extent.y * mSin(mIsoCamAngle); + } + + // Calculate the scene bounds + gClientContainer.findObjects(~(smSceneBoundsMask), sceneBoundsCalcCallback, &sceneBounds); + + if(!sceneBounds.mValid) + { + sceneBounds.mBounds.maxExtents = camPos + smMinSceneBounds; + sceneBounds.mBounds.minExtents = camPos - smMinSceneBounds; + } + else + { + query->farPlane = getMax(smMinSceneBounds.x * 2.0f, (sceneBounds.mBounds.maxExtents - sceneBounds.mBounds.minExtents).len() + camBuffer * 2.0f + isocamplanedist); + } + + mRawCamPos = camPos; + camPos += mOrthoCamTrans; + + switch(mDisplayType) + { + case DisplayTypeTop: + camRot.setColumn(0, Point3F(1.0, 0.0, 0.0)); + camRot.setColumn(1, Point3F(0.0, 0.0, -1.0)); + camRot.setColumn(2, Point3F(0.0, 1.0, 0.0)); + camPos.z = getMax(camPos.z + smMinSceneBounds.z, sceneBounds.mBounds.maxExtents.z + camBuffer); + break; + + case DisplayTypeBottom: + camRot.setColumn(0, Point3F(1.0, 0.0, 0.0)); + camRot.setColumn(1, Point3F(0.0, 0.0, 1.0)); + camRot.setColumn(2, Point3F(0.0, -1.0, 0.0)); + camPos.z = getMin(camPos.z - smMinSceneBounds.z, sceneBounds.mBounds.minExtents.z - camBuffer); + break; + + case DisplayTypeFront: + camRot.setColumn(0, Point3F(-1.0, 0.0, 0.0)); + camRot.setColumn(1, Point3F( 0.0, -1.0, 0.0)); + camRot.setColumn(2, Point3F( 0.0, 0.0, 1.0)); + camPos.y = getMax(camPos.z + smMinSceneBounds.z, sceneBounds.mBounds.maxExtents.y + camBuffer); + break; + + case DisplayTypeBack: + //camRot.setColumn(0, Point3F(1.0, 0.0, 0.0)); + //camRot.setColumn(1, Point3F(0.0, 1.0, 0.0)); + //camRot.setColumn(2, Point3F(0.0, 0.0, 1.0)); + camPos.y = getMin(camPos.y - smMinSceneBounds.y, sceneBounds.mBounds.minExtents.y - camBuffer); + break; + + case DisplayTypeLeft: + camRot.setColumn(0, Point3F( 0.0, -1.0, 0.0)); + camRot.setColumn(1, Point3F( 1.0, 0.0, 0.0)); + camRot.setColumn(2, Point3F( 0.0, 0.0, 1.0)); + camPos.x = getMin(camPos.x - smMinSceneBounds.x, sceneBounds.mBounds.minExtents.x - camBuffer); + break; + + case DisplayTypeRight: + camRot.setColumn(0, Point3F( 0.0, 1.0, 0.0)); + camRot.setColumn(1, Point3F(-1.0, 0.0, 0.0)); + camRot.setColumn(2, Point3F( 0.0, 0.0, 1.0)); + camPos.x = getMax(camPos.x + smMinSceneBounds.x, sceneBounds.mBounds.maxExtents.x + camBuffer); + break; + + case DisplayTypeIsometric: + camPos.z = sceneBounds.mBounds.maxExtents.z + camBuffer + isocamplanedist; + MatrixF angle(EulerF(mIsoCamAngle, 0, 0)); + MatrixF rot(mIsoCamRot); + camRot.mul(rot, angle); + break; + } + + query->cameraMatrix = camRot; + query->cameraMatrix.setPosition(camPos); + query->fov = mOrthoFOV; + } + + smCamMatrix = query->cameraMatrix; + smCamMatrix.getColumn(3,&smCamPos); + smCamOrtho = query->ortho; + smCamNearPlane = query->nearPlane; + + return true; + } + } + return false; + +} + +//------------------------------------------------------------------------------ +ConsoleMethod(EditTSCtrl, getDisplayType, S32, 2, 2, "") +{ + return object->getDisplayType(); +} + +ConsoleMethod(EditTSCtrl, setDisplayType, void, 3, 3, "(int displayType)") +{ + object->setDisplayType(dAtoi(argv[2])); +} + +ConsoleMethod( EditTSCtrl, renderBox, void, 4, 4, "( Point3F pos, Point3F size )" ) +{ + if( !object->mConsoleRendering || !object->mConsoleFillColor.alpha ) + return; + + Point3F pos; + Point3F size; + + dSscanf( argv[ 2 ], "%f %f %f", &pos.x, &pos.y, &pos.z ); + dSscanf( argv[ 3 ], "%f %f %f", &size.x, &size.y, &size.z ); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + + Box3F box; + box.set( size ); + box.setCenter( pos ); + + if( box.isContained( GFX->getWorldMatrix().getPosition() ) ) + desc.setCullMode( GFXCullNone ); + + GFX->getDrawUtil()->drawCube( desc, size, pos, object->mConsoleFillColor ); +} + +ConsoleMethod(EditTSCtrl, renderSphere, void, 4, 5, "(Point3F pos, float radius, int subdivisions=NULL)") +{ + if ( !object->mConsoleRendering || !object->mConsoleFillColor.alpha ) + return; + + // TODO: We need to support subdivision levels in GFXDrawUtil! + S32 sphereLevel = object->mConsoleSphereLevel; + if(argc == 5) + sphereLevel = dAtoi(argv[4]); + + Point3F pos; + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + + F32 radius = dAtof(argv[3]); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + + SphereF sphere( pos, radius ); + if( sphere.isContained( GFX->getWorldMatrix().getPosition() ) ) + desc.setCullMode( GFXCullNone ); + + GFX->getDrawUtil()->drawSphere( desc, radius, pos, object->mConsoleFillColor ); +} + +ConsoleMethod( EditTSCtrl, renderCircle, void, 5, 6, "(Point3F pos, Point3F normal, float radius, int segments=NULL)") +{ + if(!object->mConsoleRendering) + return; + + if(!object->mConsoleFrameColor.alpha && !object->mConsoleFillColor.alpha) + return; + + Point3F pos, normal; + dSscanf(argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z); + dSscanf(argv[3], "%g %g %g", &normal.x, &normal.y, &normal.z); + + F32 radius = dAtof(argv[4]); + + S32 segments = object->mConsoleCircleSegments; + if(argc == 6) + segments = dAtoi(argv[5]); + + normal.normalize(); + + AngAxisF aa; + mCross(normal, Point3F(0,0,1), &aa.axis); + aa.axis.normalizeSafe(); + aa.angle = mAcos(mClampF(mDot(normal, Point3F(0,0,1)), -1.f, 1.f)); + + if(aa.angle == 0.f) + aa.axis.set(0,0,1); + + MatrixF mat; + aa.setMatrix(&mat); + + F32 step = M_2PI / segments; + F32 angle = 0.f; + + Vector points; + segments--; + for(U32 i = 0; i < segments; i++) + { + Point3F pnt(mCos(angle), mSin(angle), 0.f); + + mat.mulP(pnt); + pnt *= radius; + pnt += pos; + + points.push_back(pnt); + angle += step; + } + + GFX->setStateBlock(object->mBlendSB); + //GFX->setAlphaBlendEnable( true ); + //GFX->setSrcBlend( GFXBlendSrcAlpha ); + //GFX->setDestBlend( GFXBlendInvSrcAlpha ); + + // framed + if(object->mConsoleFrameColor.alpha) + { + // TODO: Set GFX line width (when it exists) to the value of 'object->mConsoleLineWidth' + + PrimBuild::color( object->mConsoleFrameColor ); + + PrimBuild::begin( GFXLineStrip, points.size() + 1 ); + + for( int i = 0; i < points.size(); i++ ) + PrimBuild::vertex3fv( points[i] ); + + // GFX does not have a LineLoop primitive, so connect the last line + if( points.size() > 0 ) + PrimBuild::vertex3fv( points[0] ); + + PrimBuild::end(); + + // TODO: Reset GFX line width here + } + + // filled + if(object->mConsoleFillColor.alpha) + { + PrimBuild::color( object->mConsoleFillColor ); + + PrimBuild::begin( GFXTriangleFan, points.size() + 1 ); + + // Center point + PrimBuild::vertex3fv( pos ); + + // Edge verts + for( int i = 0; i < points.size(); i++ ) + PrimBuild::vertex3fv( points[i] ); + + PrimBuild::end(); + } + + //GFX->setAlphaBlendEnable( false ); +} + +ConsoleMethod( EditTSCtrl, renderTriangle, void, 5, 5, "(Point3F a, Point3F b, Point3F c)") +{ + + if(!object->mConsoleRendering) + return; + + if(!object->mConsoleFrameColor.alpha && !object->mConsoleFillColor.alpha) + return; + + Point3F pnts[3]; + for(U32 i = 0; i < 3; i++) + dSscanf(argv[i+2], "%f %f %f", &pnts[i].x, &pnts[i].y, &pnts[i].z); + + + GFX->setStateBlock(object->mBlendSB); + //GFX->setAlphaBlendEnable( true ); + //GFX->setSrcBlend( GFXBlendSrcAlpha ); + //GFX->setDestBlend( GFXBlendInvSrcAlpha ); + + // frame + if( object->mConsoleFrameColor.alpha ) + { + PrimBuild::color( object->mConsoleFrameColor ); + + // TODO: Set GFX line width (when it exists) to the value of 'object->mConsoleLineWidth' + + PrimBuild::begin( GFXLineStrip, 4 ); + PrimBuild::vertex3fv( pnts[0] ); + PrimBuild::vertex3fv( pnts[1] ); + PrimBuild::vertex3fv( pnts[2] ); + PrimBuild::vertex3fv( pnts[0] ); + PrimBuild::end(); + + // TODO: Reset GFX line width here + } + + // fill + if( object->mConsoleFillColor.alpha ) + { + PrimBuild::color( object->mConsoleFillColor ); + + PrimBuild::begin( GFXTriangleList, 3 ); + PrimBuild::vertex3fv( pnts[0] ); + PrimBuild::vertex3fv( pnts[1] ); + PrimBuild::vertex3fv( pnts[2] ); + PrimBuild::end(); + } + + // GFX->setAlphaBlendEnable( false ); +} + +ConsoleMethod( EditTSCtrl, renderLine, void, 4, 5, "(Point3F start, Point3F end, int width)") +{ + if ( !object->mConsoleRendering || !object->mConsoleFrameColor.alpha ) + return; + + Point3F start, end; + dSscanf(argv[2], "%f %f %f", &start.x, &start.y, &start.z); + dSscanf(argv[3], "%f %f %f", &end.x, &end.y, &end.z); + + // TODO: We don't support 3d lines with width... fix this! + S32 lineWidth = object->mConsoleLineWidth; + if ( argc == 5 ) + lineWidth = dAtoi(argv[4]); + + GFX->getDrawUtil()->drawLine( start, end, object->mConsoleFrameColor ); +} + +ConsoleMethod( EditTSCtrl, getGizmo, S32, 2, 2, "" ) +{ + return object->getGizmo()->getId(); +} + +ConsoleMethod(EditTSCtrl, isMiddleMouseDown, bool, 2, 2, "") +{ + return object->isMiddleMouseDown(); +} diff --git a/gui/worldEditor/editTSCtrl.h b/gui/worldEditor/editTSCtrl.h new file mode 100644 index 0000000..6b35bb6 --- /dev/null +++ b/gui/worldEditor/editTSCtrl.h @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EDITTSCTRL_H_ +#define _EDITTSCTRL_H_ + +#ifndef _GUITSCONTROL_H_ +#include "gui/3d/guiTSControl.h" +#endif +#ifndef _GIZMO_H_ +#include "gizmo.h" +#endif + +class TerrainBlock; +class Gizmo; +class EditManager; + +class EditTSCtrl : public GuiTSCtrl +{ + typedef GuiTSCtrl Parent; + + protected: + + void make3DMouseEvent(Gui3DMouseEvent & gui3Devent, const GuiEvent &event); + + // GuiControl + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + virtual void onMouseUp(const GuiEvent & event); + virtual void onMouseDown(const GuiEvent & event); + virtual void onMouseMove(const GuiEvent & event); + virtual void onMouseDragged(const GuiEvent & event); + virtual void onMouseEnter(const GuiEvent & event); + virtual void onMouseLeave(const GuiEvent & event); + virtual void onRightMouseDown(const GuiEvent & event); + virtual void onRightMouseUp(const GuiEvent & event); + virtual void onRightMouseDragged(const GuiEvent & event); + virtual void onMiddleMouseDown(const GuiEvent & event); + virtual void onMiddleMouseUp(const GuiEvent & event); + virtual void onMiddleMouseDragged(const GuiEvent & event); + virtual bool onInputEvent(const InputEventInfo & event); + virtual bool onMouseWheelUp(const GuiEvent &event); + virtual bool onMouseWheelDown(const GuiEvent &event); + + + virtual void updateGuiInfo() {}; + virtual void renderScene(const RectI &){}; + void renderMissionArea(); + virtual void renderCameraAxis(); + virtual void renderGrid(); + + // GuiTSCtrl + void renderWorld(const RectI & updateRect); + + protected: + enum DisplayType + { + DisplayTypeTop, + DisplayTypeBottom, + DisplayTypeFront, + DisplayTypeBack, + DisplayTypeLeft, + DisplayTypeRight, + DisplayTypePerspective, + DisplayTypeIsometric, + }; + + S32 mDisplayType; + F32 mOrthoFOV; + Point3F mOrthoCamTrans; + EulerF mIsoCamRot; + Point3F mIsoCamRotCenter; + F32 mIsoCamAngle; + Point3F mRawCamPos; + Point2I mLastMousePos; + bool mLastMouseClamping; + + bool mAllowBorderMove; + S32 mMouseMoveBorder; + F32 mMouseMoveSpeed; + U32 mLastBorderMoveTime; + + Gui3DMouseEvent mLastEvent; + bool mLeftMouseDown; + bool mRightMouseDown; + bool mMiddleMouseDown; + bool mMouseLeft; + + SimObjectPtr mGizmo; + GizmoProfile *mGizmoProfile; + + public: + + EditTSCtrl(); + ~EditTSCtrl(); + + // SimObject + bool onAdd(); + void onRemove(); + + // + bool mRenderMissionArea; + ColorI mMissionAreaFillColor; + ColorI mMissionAreaFrameColor; + + // + ColorI mConsoleFrameColor; + ColorI mConsoleFillColor; + S32 mConsoleSphereLevel; + S32 mConsoleCircleSegments; + S32 mConsoleLineWidth; + + static void initPersistFields(); + static void consoleInit(); + + // + bool mConsoleRendering; + bool mRightMousePassThru; + bool mMiddleMousePassThru; + + // all editors will share a camera + static Point3F smCamPos; + static MatrixF smCamMatrix; + static bool smCamOrtho; + static F32 smCamNearPlane; + static F32 smVisibleDistanceScale; + + static U32 smSceneBoundsMask; + static Point3F smMinSceneBounds; + + bool mRenderGridPlane; + ColorI mGridPlaneColor; + F32 mGridPlaneSize; + F32 mGridPlaneSizePixelBias; + S32 mGridPlaneMinorTicks; + ColorI mGridPlaneMinorTickColor; + ColorI mGridPlaneOriginColor; + + GFXStateBlockRef mBlendSB; + + // GuiTSCtrl + bool processCameraQuery(CameraQuery * query); + + // guiControl + virtual void onRender(Point2I offset, const RectI &updateRect); + virtual void on3DMouseUp(const Gui3DMouseEvent &){}; + virtual void on3DMouseDown(const Gui3DMouseEvent &){}; + virtual void on3DMouseMove(const Gui3DMouseEvent &){}; + virtual void on3DMouseDragged(const Gui3DMouseEvent &){}; + virtual void on3DMouseEnter(const Gui3DMouseEvent &){}; + virtual void on3DMouseLeave(const Gui3DMouseEvent &){}; + virtual void on3DRightMouseDown(const Gui3DMouseEvent &){}; + virtual void on3DRightMouseUp(const Gui3DMouseEvent &){}; + virtual void on3DRightMouseDragged(const Gui3DMouseEvent &){}; + virtual void on3DMouseWheelUp(const Gui3DMouseEvent &){}; + virtual void on3DMouseWheelDown(const Gui3DMouseEvent &){}; + virtual void get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &); + + virtual bool isMiddleMouseDown() {return mMiddleMouseDown;} + + virtual S32 getDisplayType() {return mDisplayType;} + virtual void setDisplayType(S32 type) {mDisplayType = type;} + + virtual TerrainBlock* getActiveTerrain(); + + virtual void calcOrthoCamOffset(F32 mousex, F32 mousey, U8 modifier=0); + + Gizmo* getGizmo() { return mGizmo; } + + DECLARE_CONOBJECT(EditTSCtrl); + DECLARE_CATEGORY( "Gui Editor" ); +}; + +#endif // _EDITTSCTRL_H_ diff --git a/gui/worldEditor/editor.cpp b/gui/worldEditor/editor.cpp new file mode 100644 index 0000000..03e7892 --- /dev/null +++ b/gui/worldEditor/editor.cpp @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/worldEditor/editor.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "gui/controls/guiTextListCtrl.h" +#include "T3D/shapeBase.h" +#include "T3D/gameConnection.h" + +#ifndef TORQUE_PLAYER +// See matching #ifdef in app/game.cpp +bool gEditingMission = false; +#endif + +//------------------------------------------------------------------------------ +// Class EditManager +//------------------------------------------------------------------------------ + +IMPLEMENT_CONOBJECT(EditManager); + +EditManager::EditManager() +{ + for(U32 i = 0; i < 10; i++) + mBookmarks[i] = MatrixF(true); +} + +EditManager::~EditManager() +{ +} + +//------------------------------------------------------------------------------ + +bool EditManager::onWake() +{ + if(!Parent::onWake()) + return(false); + + return(true); +} + +void EditManager::onSleep() +{ + Parent::onSleep(); +} + +//------------------------------------------------------------------------------ + +bool EditManager::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // hook the namespace + const char * name = getName(); + if(name && name[0] && getClassRep()) + { + Namespace * parent = getClassRep()->getNameSpace(); + Con::linkNamespaces(parent->mName, name); + mNameSpace = Con::lookupNamespace(name); + } + + return(true); +} + +//------------------------------------------------------------------------------ + +// NOTE: since EditManager is not actually used as a gui anymore, onWake/Sleep +// were never called, which broke anyone hooking into onEditorEnable/onEditorDisable +// and gEditingMission. So, moved these to happen in response to console methods +// which should be called at the appropriate time. +// +// This is a quick fix and this system is still "begging" for a remake. + +void EditManager::editorEnabled() +{ + for(SimGroupIterator itr(Sim::getRootGroup()); *itr; ++itr) + (*itr)->onEditorEnable(); + + gEditingMission = true; +} + +void EditManager::editorDisabled() +{ + for(SimGroupIterator itr(Sim::getRootGroup()); *itr; ++itr) + { + SimObject *so = *itr; + AssertFatal(so->isProperlyAdded() && !so->isRemoved(), "bad"); + so->onEditorDisable(); + } + + gEditingMission = false; +} + +//------------------------------------------------------------------------------ + +static GameBase * getControlObj() +{ + GameConnection * connection = GameConnection::getLocalClientConnection(); + ShapeBase* control = 0; + if(connection) + control = dynamic_cast(connection->getControlObject()); + return(control); +} + +ConsoleMethod( EditManager, setBookmark, void, 3, 3, "(int slot)") +{ + S32 val = dAtoi(argv[2]); + if(val < 0 || val > 9) + return; + + GameBase * control = getControlObj(); + if(control) + object->mBookmarks[val] = control->getTransform(); +} + +ConsoleMethod( EditManager, gotoBookmark, void, 3, 3, "(int slot)") +{ + S32 val = dAtoi(argv[2]); + if(val < 0 || val > 9) + return; + + GameBase * control = getControlObj(); + if(control) + control->setTransform(object->mBookmarks[val]); +} + +ConsoleMethod( EditManager, editorEnabled, void, 2, 2, "Perform the onEditorEnabled callback on all SimObjects and set gEditingMission true" ) +{ + object->editorEnabled(); +} + +ConsoleMethod( EditManager, editorDisabled, void, 2, 2, "Perform the onEditorDisabled callback on all SimObjects and set gEditingMission false" ) +{ + object->editorDisabled(); +} \ No newline at end of file diff --git a/gui/worldEditor/editor.h b/gui/worldEditor/editor.h new file mode 100644 index 0000000..1e674ee --- /dev/null +++ b/gui/worldEditor/editor.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EDITOR_H_ +#define _EDITOR_H_ + +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif + +class GameBase; + +//------------------------------------------------------------------------------ + +class EditManager : public GuiControl +{ + private: + typedef GuiControl Parent; + + public: + EditManager(); + ~EditManager(); + + bool onWake(); + void onSleep(); + + // SimObject + bool onAdd(); + + /// Perform the onEditorEnabled callback on all SimObjects + /// and set gEditingMission true. + void editorEnabled(); + + /// Perform the onEditorDisabled callback on all SimObjects + /// and set gEditingMission false. + void editorDisabled(); + + MatrixF mBookmarks[10]; + DECLARE_CONOBJECT(EditManager); + DECLARE_CATEGORY( "Gui Editor" ); +}; + +extern bool gEditingMission; + +//------------------------------------------------------------------------------ + +#endif diff --git a/gui/worldEditor/editorIconRegistry.cpp b/gui/worldEditor/editorIconRegistry.cpp new file mode 100644 index 0000000..688eec0 --- /dev/null +++ b/gui/worldEditor/editorIconRegistry.cpp @@ -0,0 +1,186 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/worldEditor/editorIconRegistry.h" + +#include "console/console.h" +#include "console/simBase.h" + + +EditorIconRegistry gEditorIcons; + + +EditorIconRegistry::EditorIconRegistry() +{ +} + +EditorIconRegistry::~EditorIconRegistry() +{ + clear(); +} + +void EditorIconRegistry::loadFromPath( const String &path, bool overwrite ) +{ + AbstractClassRep *classRep = AbstractClassRep::getClassList(); + while ( classRep ) + { + String iconFile = String::ToString( "%s%s", path.c_str(), classRep->getClassName() ); + add( classRep->getClassName(), iconFile.c_str(), overwrite ); + classRep = classRep->getNextClass(); + } + + String defaultIconFile = path + "default"; + + mDefaultIcon.set( defaultIconFile, + &GFXDefaultPersistentProfile, + avar("%s() - mIcons[] (line %d)", + __FUNCTION__, __LINE__) ); +} + +void EditorIconRegistry::add( const String &className, const String &imageFile, bool overwrite ) +{ + // First see if we can load the image. + GFXTexHandle icon( imageFile, &GFXDefaultPersistentProfile, + avar("%s() - mIcons[] (line %d)", __FUNCTION__, __LINE__) ); + if ( icon.isNull() ) + return; + + // Look it up in the map. + StringNoCase key( className ); + IconMap::Iterator iter = mIcons.find( key ); + if ( iter != mIcons.end() ) + { + if ( !overwrite ) + return; + + iter->value = icon; + } + else + mIcons.insertUnique( key, icon ); +} + +GFXTexHandle EditorIconRegistry::findIcon( AbstractClassRep *classRep ) +{ + while ( classRep ) + { + StringNoCase key( classRep->getClassName() ); + IconMap::Iterator icon = mIcons.find( key ); + + if ( icon != mIcons.end() && icon->value.isValid() ) + return icon->value; + + classRep = classRep->getParentClass(); + } + + return mDefaultIcon; +} + +GFXTexHandle EditorIconRegistry::findIcon( const SimObject *object ) +{ + if( object == NULL ) + return mDefaultIcon; + + AbstractClassRep *classRep = object->getClassRep(); + + return findIcon( classRep ); +} + +GFXTexHandle EditorIconRegistry::findIcon( const char *className ) +{ + // On the chance we have this className already in the map, + // check there first because its a lot faster... + + StringNoCase key( className ); + IconMap::Iterator icon = mIcons.find( key ); + + if ( icon != mIcons.end() && icon->value.isValid() ) + return icon->value; + + // Well, we could still have an icon for a parent class, + // so find the AbstractClassRep for the className. + // + // Unfortunately the only way to do this is looping through + // the AbstractClassRep linked list. + + bool found = false; + AbstractClassRep* pClassRep = AbstractClassRep::getClassList(); + + while ( pClassRep ) + { + if ( key.equal( pClassRep->getClassName(), String::NoCase ) ) + { + found = true; + break; + } + pClassRep = pClassRep->getNextClass(); + } + + if ( !found ) + { + Con::errorf( "EditorIconRegistry::findIcon, passed className %s was not an AbstractClassRep!", key.c_str() ); + return mDefaultIcon; + } + + // Now do a find by AbstractClassRep recursively up the class tree... + return findIcon( pClassRep ); +} + +void EditorIconRegistry::clear() +{ + mIcons.clear(); + mDefaultIcon.free(); +} + +ConsoleStaticMethod( EditorIconRegistry, add, void, 3, 4, "( String className, String imageFile [, bool overwrite = true] )" ) +{ + bool overwrite = true; + if ( argc > 3 ) + overwrite = dAtob( argv[3] ); + + gEditorIcons.add( argv[1], argv[2], overwrite ); +} + +ConsoleStaticMethod( EditorIconRegistry, loadFromPath, void, 2, 3, "( String imagePath [, bool overwrite = true] )" ) +{ + bool overwrite = true; + if ( argc > 2 ) + overwrite = dAtob( argv[2] ); + + gEditorIcons.loadFromPath( argv[1], overwrite ); +} + +ConsoleStaticMethod( EditorIconRegistry, clear, void, 1, 1, "" ) +{ + gEditorIcons.clear(); +} + +ConsoleStaticMethod( EditorIconRegistry, findIconByClassName, const char*, 2, 2, "( String className )\n" + "Returns the file path to the icon file if found." ) +{ + GFXTexHandle icon = gEditorIcons.findIcon( argv[1] ); + if ( icon.isNull() ) + return NULL; + + return icon->mPath; +} + +ConsoleStaticMethod( EditorIconRegistry, findIconBySimObject, const char*, 2, 2, "( SimObject )\n" + "Returns the file path to the icon file if found." ) +{ + SimObject *obj = NULL; + if ( !Sim::findObject( argv[1], obj ) ) + { + Con::warnf( "EditorIconRegistry::findIcon, parameter %d was not a SimObject!", argv[1] ); + return NULL; + } + + GFXTexHandle icon = gEditorIcons.findIcon( obj ); + if ( icon.isNull() ) + return NULL; + + return icon->mPath; +} + diff --git a/gui/worldEditor/editorIconRegistry.h b/gui/worldEditor/editorIconRegistry.h new file mode 100644 index 0000000..830db13 --- /dev/null +++ b/gui/worldEditor/editorIconRegistry.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _EDITORICONREGISTRY_H_ +#define _EDITORICONREGISTRY_H_ + +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +class SimObject; +class AbstractClassRep; + + +/// This class is used to find the correct icon file +/// path for different SimObject class types. It is +/// typically used by the editors. +class EditorIconRegistry +{ +public: + + EditorIconRegistry(); + ~EditorIconRegistry(); + + /// Loops thru all the AbstractClassReps looking for icons in the path. + void loadFromPath( const String &path, bool overwrite ); + + /// Adds a single icon to the registry. + void add( const String &className, const String &imageFile, bool overwrite ); + + /// Clears all the icons from the registry. + void clear(); + + /// Looks up an icon given an AbstractClassRep. + /// Other findIcon methods redirect to this. + GFXTexHandle findIcon( AbstractClassRep *classRep ); + + /// Looks up an icon given a SimObject. + GFXTexHandle findIcon( const SimObject *object ); + + /// Looks up an icon given a className. + GFXTexHandle findIcon( const char *className ); + +protected: + + typedef HashTable IconMap; + IconMap mIcons; + + /// The default icon returned when no matching icon is found. + GFXTexHandle mDefaultIcon; +}; + +/// The global registry of editor icons. +extern EditorIconRegistry gEditorIcons; + +#endif // _EDITORICONREGISTRY_H_ \ No newline at end of file diff --git a/gui/worldEditor/gizmo.cpp b/gui/worldEditor/gizmo.cpp new file mode 100644 index 0000000..fa6e845 --- /dev/null +++ b/gui/worldEditor/gizmo.cpp @@ -0,0 +1,1743 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/worldEditor/gizmo.h" + +#include "console/consoleTypes.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDrawUtil.h" +#include "T3D/gameConnection.h" +//#include "math/mathUtils.h" + +using namespace MathUtils; + + +// Developer Notes: + +// How to... Calculate the SelectionAxis index representing the normal +// of a plane, given a SelectionPlane index +// normal = axisVector[2 - (planeIdx - 3 )]; + +// How to... Get the two AxisVectors of a selected plane +// vec0 = mProjAxisVector[mAxisGizmoPlanarVectors[mSelectionIdx-3][0]]; +// vec1 = mProjAxisVector[mAxisGizmoPlanarVectors[mSelectionIdx-3][1]]; + + +//------------------------------------------------------------------------- +// Unnamed namespace for static data +//------------------------------------------------------------------------- + +namespace { + + static EnumTable::Enums gAlignmentEnums[] = { + { World, "World" }, + { Object, "Object" } + }; + + EnumTable gAlignmentEnumTable( AlignEnumCount, gAlignmentEnums ); + + static EnumTable::Enums gModeEnums[] = { + { NoneMode, "None" }, + { MoveMode, "Move" }, + { RotateMode, "Rotate" }, + { ScaleMode, "Scale" } + }; + + EnumTable gModeEnumTable( ModeEnumCount, gModeEnums ); + + static S32 sgAxisRemap[3][3] = { + {0, 1, 2}, + {2, 0, 1}, + {1, 2, 0}, + }; + + static VectorF sgAxisVectors[3] = { + VectorF(1.0f,0.0f,0.0f), + VectorF(0.0f,1.0f,0.0f), + VectorF(0.0f,0.0f,1.0f) + }; + + static U32 sgPlanarVectors[3][2] = { + { 0, 1 }, // XY + { 0, 2 }, // XZ + { 1, 2 } // YZ + }; + + static Point3F sgBoxPnts[] = { + Point3F(0.0f,0.0f,0.0f), + Point3F(0.0f,0.0f,1.0f), + Point3F(0.0f,1.0f,0.0f), + Point3F(0.0f,1.0f,1.0f), + Point3F(1.0f,0.0f,0.0f), + Point3F(1.0f,0.0f,1.0f), + Point3F(1.0f,1.0f,0.0f), + Point3F(1.0f,1.0f,1.0f) + }; + + static U32 sgBoxVerts[][4] = { + {0,2,3,1}, // -x + {7,6,4,5}, // +x + {0,1,5,4}, // -y + {3,2,6,7}, // +y + {0,4,6,2}, // -z + {3,7,5,1} // +z + }; + + static Point3F sgBoxNormals[] = { + Point3F(-1.0f, 0.0f, 0.0f), + Point3F( 1.0f, 0.0f, 0.0f), + Point3F( 0.0f,-1.0f, 0.0f), + Point3F( 0.0f, 1.0f, 0.0f), + Point3F( 0.0f, 0.0f,-1.0f), + Point3F( 0.0f, 0.0f, 1.0f) + }; + + static Point3F sgConePnts[] = { + Point3F(0.0f, 0.0f, 0.0f), + Point3F(-1.0f, 0.0f, -0.25f), + Point3F(-1.0f, -0.217f, -0.125f), + Point3F(-1.0f, -0.217f, 0.125f), + Point3F(-1.0f, 0.0f, 0.25f), + Point3F(-1.0f, 0.217f, 0.125f), + Point3F(-1.0f, 0.217f, -0.125f), + Point3F(-1.0f, 0.0f, 0.0f) + }; + + static U32 sgConeVerts[][3] = { + {0, 2, 1}, + {0, 3, 2}, + {0, 4, 3}, + {0, 5, 4}, + {0, 6, 5}, + {0, 1, 6}, + {7, 1, 6}, // Base + {7, 6, 5}, + {7, 5, 4}, + {7, 4, 3}, + {7, 3, 2}, + {7, 2, 1} + }; + + static Point3F sgCenterBoxPnts[] = { + Point3F(-0.5f, -0.5f, -0.5f), + Point3F(-0.5f, -0.5f, 0.5f), + Point3F(-0.5f, 0.5f, -0.5f), + Point3F(-0.5f, 0.5f, 0.5f), + Point3F( 0.5f, -0.5f, -0.5f), + Point3F( 0.5f, -0.5f, 0.5f), + Point3F( 0.5f, 0.5f, -0.5f), + Point3F( 0.5f, 0.5f, 0.5f) + }; + + static Point3F sgCirclePnts[] = { + Point3F(0.0f, 0.0f, -0.5f), + Point3F(0.0f, 0.354f, -0.354f), + Point3F(0.0f, 0.5f, 0), + Point3F(0.0f, 0.354f, 0.354f), + Point3F(0.0f, 0.0f, 0.5f), + Point3F(0.0f, -0.354f, 0.354f), + Point3F(0.0f, -0.5f, 0), + Point3F(0.0f, -0.354f, -0.354f), + Point3F(0.0f, 0.0f, -0.5f), + }; +} + + +//------------------------------------------------------------------------- +// GizmoProfile Class +//------------------------------------------------------------------------- + +GizmoProfile::GizmoProfile() +{ + mode = MoveMode; + alignment = World; + screenLen = 100; + renderPlane = true; + renderPlaneHashes = true; + gridColor.set(255,255,255,20); + planeDim = 500.0f; + + gridSize.set(10,10,10); + snapToGrid = false; + allowSnapRotations = true; + rotationSnap = 15.0f; + allowSnapScale = true; + scaleSnap = 0.1f; + + rotateScalar = 0.01f; + scaleScalar = 0.01f; + + axisColors[0].set( 255, 0, 0 ); + axisColors[1].set( 0, 255, 0 ); + axisColors[2].set( 0, 0, 255 ); + + activeColor.set( 237, 219, 0 ); + inActiveColor.set( 170, 170, 170 ); + + centroidColor.set( 255, 255, 255 ); + centroidHighlightColor.set( 255, 0, 255 ); + + flags = U32_MAX; +} + +IMPLEMENT_CONOBJECT( GizmoProfile ); + +bool GizmoProfile::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + const char* fontCacheDirectory = Con::getVariable("$GUI::fontCacheDirectory"); + font = GFont::create( "Arial", 10, fontCacheDirectory, TGE_ANSI_CHARSET); + if ( !font ) + { + Con::errorf( "GizmoProfile::onAdd - failed to load font!" ); + return false; + } + + return true; +} + +void GizmoProfile::initPersistFields() +{ + addField( "alignment", TypeEnum, Offset(alignment, GizmoProfile ), 1, &gAlignmentEnumTable ); + addField( "mode", TypeEnum, Offset(mode, GizmoProfile ), 1, &gModeEnumTable ); + + addField( "snapToGrid", TypeBool, Offset(snapToGrid, GizmoProfile) ); + addField( "allowSnapRotations", TypeBool, Offset(allowSnapRotations, GizmoProfile) ); + addField( "rotationSnap", TypeF32, Offset(rotationSnap, GizmoProfile) ); + addField( "allowSnapScale", TypeBool, Offset(allowSnapScale, GizmoProfile) ); + addField( "scaleSnap", TypeF32, Offset(scaleSnap, GizmoProfile) ); + addField( "renderPlane", TypeBool, Offset(renderPlane, GizmoProfile) ); + addField( "renderPlaneHashes", TypeBool, Offset(renderPlaneHashes, GizmoProfile) ); + addField( "gridColor", TypeColorI, Offset(gridColor, GizmoProfile) ); + addField( "planeDim", TypeF32, Offset(planeDim, GizmoProfile) ); + addField( "gridSize", TypePoint3F, Offset(gridSize, GizmoProfile) ); + addField( "screenLength", TypeS32, Offset(screenLen, GizmoProfile) ); + addField( "rotateScalar", TypeF32, Offset(rotateScalar, GizmoProfile) ); + addField( "scaleScalar", TypeF32, Offset(scaleScalar, GizmoProfile) ); + addField( "flags", TypeS32, Offset(flags, GizmoProfile) ); +} + + +//------------------------------------------------------------------------- +// Gizmo Class +//------------------------------------------------------------------------- + +F32 Gizmo::smProjectDistance = 20000.0f; + +Gizmo::Gizmo() +: mProfile( NULL ), + mSelectionIdx( -1 ), + mCameraMat( true ), + mTransform( true ), + mLastTransform( true ), + mSavedTransform( true ), + mSavedScale( 0,0,0 ), + mDeltaScale( 0,0,0 ), + mDeltaRot( 0,0,0 ), + mCurrentAlignment( World ), + mCurrentMode( MoveMode ), + mDirty( false ), + mMouseDownPos( -1,-1 ), + mMouseDown( false ), + mHighlightCentroidHandle( false ), + mLastWorldMat( true ), + mLastProjMat( true ), + mLastViewport( 0, 0, 10, 10 ), + mElipseCursorCollideVecSS( 1.0f, 0.0f, 0.0f ), + mElipseCursorCollidePntSS( 0.0f, 0.0f, 0.0f ) +{ + mUniformHandleEnabled = true; + mAxisEnabled[0] = mAxisEnabled[1] = mAxisEnabled[2] = true; +} + +Gizmo::~Gizmo() +{ +} + +IMPLEMENT_CONOBJECT( Gizmo ); + +// SimObject Methods... + +bool Gizmo::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + if ( !mProfile ) + return false; + + mCurrentAlignment = mProfile->alignment; + mCurrentMode = mProfile->mode; + + return true; +} + +void Gizmo::onRemove() +{ + Parent::onRemove(); +} + +void Gizmo::initPersistFields() +{ + Parent::initPersistFields(); + + //addField( "profile",) +} + +// Gizmo Accessors and Mutators... + +void Gizmo::set( const MatrixF &objMat, const Point3F &worldPos, const Point3F &objScale ) +{ + mCurrentAlignment = _filteredAlignment(); + + if ( mCurrentAlignment == World ) + { + mTransform.identity(); + mTransform.setPosition( worldPos ); + mScale = objScale; + mObjectMat = objMat; + } + else + { + mTransform = objMat; + mTransform.setPosition( worldPos ); + mScale = objScale; + mObjectMat.identity(); + } +} + +Point3F Gizmo::getScale() const +{ + //Point3F scale = mScale; + //mToObjectMat.mulV(scale); + //return scale; + return mScale; +} + +Gizmo::Selection Gizmo::getSelection() +{ + if ( mProfile->mode == NoneMode ) + return None; + + return (Selection)mSelectionIdx; +} + +VectorF Gizmo::selectionToAxisVector( Selection axis ) +{ + if ( axis < Axis_X || axis > Axis_Z ) + return VectorF(0,0,0); + + return sgAxisVectors[(U32)axis]; +} + +bool Gizmo::collideAxisGizmo( const Gui3DMouseEvent & event ) +{ + if ( mProfile->mode == NoneMode ) + return false; + + _calcAxisInfo(); + + // Early out if we are in a mode that is disabled. + if ( mProfile->mode == RotateMode && !(mProfile->flags & GizmoProfile::CanRotate ) ) + return false; + if ( mProfile->mode == MoveMode && !(mProfile->flags & GizmoProfile::CanTranslate ) ) + return false; + if ( mProfile->mode == ScaleMode && !(mProfile->flags & GizmoProfile::CanScale ) ) + return false; + + VectorF toGizmoVec; + + // get the projected size... + + toGizmoVec = mOrigin - mCameraPos; + toGizmoVec.normalizeSafe(); + + PlaneF clipPlane( mOrigin, toGizmoVec ); + + mSelectionIdx = -1; + Point3F end = mCameraPos + event.vec * smProjectDistance; + + if ( mProfile->mode == RotateMode ) + { + const Point3F mousePntSS( (F32)event.mousePoint.x, (F32)event.mousePoint.y, 0.0f ); + const F32 axisCollisionThresholdSS = 10.0f; + + + Point3F originSS; + MathUtils::mProjectWorldToScreen( mOrigin, &originSS, mLastViewport, mLastWorldMat, mLastProjMat ); + originSS.z = 0.0f; + + const F32 originDistSS = mAbs( ( mousePntSS - originSS ).len() ); + + // Check for camera facing axis rotation handle collision. + { + const F32 distSS = mAbs( ( (F32)mProfile->screenLen * 0.7f ) - originDistSS ); + + if ( distSS < axisCollisionThresholdSS ) + { + mSelectionIdx = 6; + + Point3F normal = mousePntSS - originSS; + normal.normalizeSafe(); + Point3F tangent = mCross( -normal, Point3F(0,0,1) ); + tangent.normalizeSafe(); + + mElipseCursorCollidePntSS = mousePntSS; + mElipseCursorCollideVecSS = tangent; + mElipseCursorCollideVecSS.z = 0.0f; + mElipseCursorCollideVecSS.normalizeSafe(); + + return true; + } + + } + + // Check for x/y/z axis ellipse handle collision. + // We do this as a screen-space pixel distance test between + // the cursor position and the ellipse handle projected to the screen + // as individual segments. + { + const F32 ellipseRadiusWS = mProjLen * 0.5f; + const U32 segments = 40; + const F32 stepRadians = mDegToRad(360.0f) / segments; + + U32 x,y,z; + F32 ang0, ang1, distSS; + Point3F temp, pnt0, pnt1, closestPntSS; + bool valid0, valid1; + + MatrixF worldToGizmo = mTransform; + worldToGizmo.inverse(); + PlaneF clipPlaneGS; // Clip plane in gizmo space. + mTransformPlane( worldToGizmo, Point3F(1,1,1), clipPlane, &clipPlaneGS ); + + for ( U32 i = 0; i < 3; i++ ) + { + if ( !mAxisEnabled[i] ) + continue; + + x = sgAxisRemap[i][0]; + y = sgAxisRemap[i][1]; + z = sgAxisRemap[i][2]; + + for ( U32 j = 1; j <= segments; j++ ) + { + ang0 = (j-1) * stepRadians; + ang1 = j * stepRadians; + + temp.x = 0.0f; + temp.y = mCos(ang0) * ellipseRadiusWS; + temp.z = mSin(ang0) * ellipseRadiusWS; + pnt0.set( temp[x], temp[y], temp[z] ); + + temp.x = 0.0f; + temp.y = mCos(ang1) * ellipseRadiusWS; + temp.z = mSin(ang1) * ellipseRadiusWS; + pnt1.set( temp[x], temp[y], temp[z] ); + + valid0 = ( clipPlaneGS.whichSide(pnt0) == PlaneF::Back ); + valid1 = ( clipPlaneGS.whichSide(pnt1) == PlaneF::Back ); + + if ( !valid0 || !valid1 ) + continue; + + // Transform points from gizmo space to world space. + + mTransform.mulP( pnt0 ); + mTransform.mulP( pnt1 ); + + // Transform points from gizmo space to screen space. + + valid0 = MathUtils::mProjectWorldToScreen( pnt0, &pnt0, mLastViewport, mLastWorldMat, mLastProjMat ); + valid1 = MathUtils::mProjectWorldToScreen( pnt1, &pnt1, mLastViewport, mLastWorldMat, mLastProjMat ); + + // Get distance from the cursor. + + closestPntSS = MathUtils::mClosestPointOnSegment( Point3F( pnt0.x, pnt0.y, 0.0f ), Point3F( pnt1.x, pnt1.y, 0.0f ), mousePntSS ); + distSS = ( closestPntSS - mousePntSS ).len(); + + if ( distSS < axisCollisionThresholdSS ) + { + mSelectionIdx = i; + mElipseCursorCollidePntSS = mousePntSS; + mElipseCursorCollideVecSS = pnt1 - pnt0; + mElipseCursorCollideVecSS.z = 0.0f; + mElipseCursorCollideVecSS.normalizeSafe(); + + return true; + } + } + } + } + + // Check for sphere surface collision + if ( originDistSS <= (F32)mProfile->screenLen * 0.5f ) + { + // If this style manipulation is desired it must also be implemented in onMouseDragged. + //mSelectionIdx = 7; + //return true; + } + + return false; + } + + // Check if we've hit the uniform scale handle... + if ( ( ( mProfile->mode == MoveMode ) && ( mProfile->flags & GizmoProfile::CanTranslateUniform ) ) || + ( ( mProfile->mode == ScaleMode ) && ( mProfile->flags & GizmoProfile::CanScaleUniform ) ) ) + { + F32 tipScale = mProjLen * 0.1f; + Point3F sp( tipScale, tipScale, tipScale ); + + Point3F min = mOrigin - sp; + Point3F max = mOrigin + sp; + Box3F uhandle(min, max); + if ( uhandle.collideLine( mCameraPos, end ) ) + { + mSelectionIdx = 6; + return true; + } + } + + // Check if we've hit the planar handles... + if ( ( mProfile->mode == MoveMode || mProfile->mode == ScaleMode ) && + ( mProfile->flags & GizmoProfile::PlanarHandlesOn ) ) + { + for ( U32 i = 0; i < 3; i++ ) + { + Point3F p1 = mProjAxisVector[sgPlanarVectors[i][0]]; + Point3F p2 = mProjAxisVector[sgPlanarVectors[i][1]]; + VectorF normal; + mCross(p1, p2, &normal); + + if(normal.isZero()) + continue; + + PlaneF plane(mOrigin, normal); + + p1 *= mProjLen * 0.5f; + p2 *= mProjLen * 0.5f; + + Point3F poly [] = { + Point3F(mOrigin + p1 + p2 * 0.75f), + Point3F(mOrigin + p1 + p2), + Point3F(mOrigin + p1 * 0.75f + p2), + Point3F(mOrigin + (p1 + p2) * 0.75f) + }; + + Point3F end = mCameraPos + event.vec * smProjectDistance; + F32 t = plane.intersect(mCameraPos, end); + if ( t >= 0 && t <= 1 ) + { + Point3F pos; + pos.interpolate(mCameraPos, end, t); + + // check if inside our 'poly' of this axisIdx vector... + bool inside = true; + for(U32 j = 0; inside && (j < 4); j++) + { + U32 k = (j+1) % 4; + VectorF vec1 = poly[k] - poly[j]; + VectorF vec2 = pos - poly[k]; + + if(mDot(vec1, vec2) > 0.f) + inside = false; + } + + // + if ( inside ) + { + mSelectionIdx = i+3; + //mAxisGizmoSelPlane = plane; + //mAxisGizmoSelPlaneIndex = i; + //mAxisGizmoSelPlanePoint = pos; + //mAxisGizmoSelStart = camPos; + return true; + } + } + } + } + + // Check if we've hit an axis... + for ( U32 i = 0; i < 3; i++ ) + { + if ( !mAxisEnabled[i] ) + continue; + + VectorF up, normal; + mCross(toGizmoVec, mProjAxisVector[i], &up); + mCross(up, mProjAxisVector[i], &normal); + + if ( normal.isZero() ) + continue; + + PlaneF plane( mOrigin, normal ); + + // width of the axisIdx poly is 1/10 the run + Point3F a = up * mProjLen / 10; + Point3F b = mProjAxisVector[i] * mProjLen; + + Point3F poly [] = { + Point3F(mOrigin + a), + Point3F(mOrigin + a + b), + Point3F(mOrigin - a + b), + Point3F(mOrigin - a) + }; + + F32 t = plane.intersect(mCameraPos, end); + if ( t >= 0 && t <= 1 ) + { + Point3F pos; + pos.interpolate(mCameraPos, end, t); + + // check if inside our 'poly' of this axisIdx vector... + bool inside = true; + for ( U32 j = 0; inside && (j < 4); j++ ) + { + U32 k = (j+1) % 4; + VectorF vec1 = poly[k] - poly[j]; + VectorF vec2 = pos - poly[k]; + + if ( mDot(vec1, vec2) > 0.f ) + inside = false; + } + + // + if(inside) + { + mSelectionIdx = i; + + return true; + } + } + } + + return false; +} + +void Gizmo::on3DMouseDown( const Gui3DMouseEvent & event ) +{ + _updateState(); + + mMouseDown = true; + + if ( mProfile->mode == NoneMode ) + return; + + // Save the current transforms, need this for some + // operations that occur on3DMouseDragged. + + mSavedTransform = mTransform; + mSavedScale = mScale; + mSavedRot = mTransform.toEuler(); + + mMouseDownPos = event.mousePoint; + mLastAngle = 0.0f; + mLastScale = mScale; + mLastMouseEvent = event; + mSign = 0.0f; + + _calcAxisInfo(); + + // Calculate mMouseCollideLine and mMouseDownProjPnt + // which are used in on3DMouseDragged. + + if ( mProfile->mode == MoveMode || mProfile->mode == ScaleMode ) + { + if ( mSelectionIdx >= 0 && mSelectionIdx <= 2 ) + { + MathUtils::Line clickLine; + clickLine.origin = event.pos; + clickLine.direction = event.vec; + + VectorF objectAxisVector = sgAxisVectors[mSelectionIdx]; + VectorF worldAxisVector = objectAxisVector; + mTransform.mulV( worldAxisVector ); + + MathUtils::Line axisLine; + axisLine.origin = mTransform.getPosition(); + axisLine.direction = worldAxisVector; + + mMouseCollideLine = axisLine; + + LineSegment segment; + mShortestSegmentBetweenLines( clickLine, axisLine, &segment ); + + mMouseDownProjPnt = segment.p1; + } + else if ( mSelectionIdx >= 3 && mSelectionIdx <= 5 ) + { + VectorF objectPlaneNormal = sgAxisVectors[2 - (mSelectionIdx - 3 )]; + VectorF worldPlaneNormal = objectPlaneNormal; + mTransform.mulV( worldPlaneNormal ); + + PlaneF plane( mTransform.getPosition(), worldPlaneNormal ); + + mMouseCollidePlane = plane; + + Point3F intersectPnt; + if ( plane.intersect( event.pos, event.vec, &intersectPnt ) ) + { + mMouseDownProjPnt = intersectPnt; + } + + // We also calculate the line to be used later. + + VectorF objectAxisVector(0,0,0); + objectAxisVector += sgAxisVectors[sgPlanarVectors[mSelectionIdx-3][0]]; + objectAxisVector += sgAxisVectors[sgPlanarVectors[mSelectionIdx-3][1]]; + objectAxisVector.normalize(); + + VectorF worldAxisVector = objectAxisVector; + mTransform.mulV( worldAxisVector ); + + MathUtils::Line axisLine; + axisLine.origin = mTransform.getPosition(); + axisLine.direction = worldAxisVector; + + mMouseCollideLine = axisLine; + } + else if ( mSelectionIdx == 6 ) + { + VectorF normal; + mCameraMat.getColumn(1,&normal); + normal = -normal; + + PlaneF plane( mOrigin, normal ); + + mMouseCollidePlane = plane; + + Point3F intersectPnt; + if ( plane.intersect( event.pos, event.vec, &intersectPnt ) ) + { + mMouseDownProjPnt = intersectPnt; + } + } + } + else if ( mProfile->mode == RotateMode ) + { + Point3F end = mCameraPos + event.vec * smProjectDistance; + + if ( 0 <= mSelectionIdx && mSelectionIdx <= 2 ) + { + // Nothing to do, we already have mElipseCursorCollidePntSS + // and mElipseCursorCollideVecSS set. + } + else if ( mSelectionIdx == 6 ) + { + // Nothing to do, we already have mElipseCursorCollidePntSS + // and mElipseCursorCollideVecSS set. + } + } +} + +void Gizmo::on3DMouseUp( const Gui3DMouseEvent &event ) +{ + _updateState(); + mMouseDown = false; +} + +void Gizmo::on3DMouseMove( const Gui3DMouseEvent & event ) +{ + _updateState( false ); + + if ( mProfile->mode == NoneMode ) + return; + + collideAxisGizmo( event ); + + mLastMouseEvent = event; +} + +void Gizmo::on3DMouseDragged( const Gui3DMouseEvent & event ) +{ + _updateState( false ); + + if ( !mProfile || mProfile->mode == NoneMode || mSelectionIdx == None ) + return; + + // If we got a dragged event without the mouseDown flag the drag operation + // must have been canceled by a mode change, ignore further dragged events. + if ( !mMouseDown ) + return; + + _calcAxisInfo(); + + if ( mProfile->mode == MoveMode || mProfile->mode == ScaleMode ) + { + Point3F projPnt = mOrigin; + + // Project the mouse position onto the line/plane of manipulation... + + if ( mSelectionIdx >= 0 && mSelectionIdx <= 2 ) + { + MathUtils::Line clickLine; + clickLine.origin = event.pos; + clickLine.direction = event.vec; + + LineSegment segment; + mShortestSegmentBetweenLines( clickLine, mMouseCollideLine, &segment ); + + projPnt = segment.p1; + + // snap to the selected axisIdx, if required + //Point3F snapPnt = _snapPoint(projPnt); + + //if ( mSelectionIdx < 3 ) + //{ + //projPnt[mSelectionIdx] = snapPnt[mSelectionIdx]; + //} + //else + //{ + //projPnt[sgPlanarVectors[mSelectionIdx-3][0]] = snapPnt[sgPlanarVectors[mSelectionIdx-3][0]]; + //projPnt[sgPlanarVectors[mSelectionIdx-3][1]] = snapPnt[sgPlanarVectors[mSelectionIdx-3][1]]; + //} + } + else if ( 3 <= mSelectionIdx && mSelectionIdx <= 5 ) + { + if ( mProfile->mode == MoveMode ) + { + Point3F intersectPnt; + if ( mMouseCollidePlane.intersect( event.pos, event.vec, &intersectPnt ) ) + { + projPnt = intersectPnt; + + // snap to the selected axisIdx, if required + //Point3F snapPnt = _snapPoint(projPnt); + //projPnt[sgPlanarVectors[mSelectionIdx-3][0]] = snapPnt[sgPlanarVectors[mSelectionIdx-3][0]]; + //projPnt[sgPlanarVectors[mSelectionIdx-3][1]] = snapPnt[sgPlanarVectors[mSelectionIdx-3][1]]; + } + } + else // ScaleMode + { + MathUtils::Line clickLine; + clickLine.origin = event.pos; + clickLine.direction = event.vec; + + LineSegment segment; + mShortestSegmentBetweenLines( clickLine, mMouseCollideLine, &segment ); + + projPnt = segment.p1; + } + } + else if ( mSelectionIdx == 6 ) + { + Point3F intersectPnt; + if ( mMouseCollidePlane.intersect( event.pos, event.vec, &intersectPnt ) ) + { + projPnt = intersectPnt; + } + } + + // Perform the manipulation... + + if ( mProfile->mode == MoveMode ) + { + // Clear deltas we aren't using... + mDeltaRot.zero(); + mDeltaScale.zero(); + + VectorF totalOffset = projPnt - mMouseDownProjPnt; + Point3F newPosition = mSavedTransform.getPosition() + totalOffset ; + mDeltaPos = newPosition - mTransform.getPosition(); + mTransform.setPosition( newPosition ); + } + else // ScaleMode + { + // This is the world-space axis we want to scale + //VectorF axis = sgAxisVectors[mSelectionIdx]; + + // Find its object-space components... + //MatrixF mat = mObjectMat; + //mat.inverse(); + //mat.mulV(axis); + + // Which needs to always be positive, this is a 'scale' transformation + // not really a 'vector' transformation. + //for ( U32 i = 0; i < 3; i++ ) + // axis[i] = mFabs(axis[i]); + + //axis.normalizeSafe(); + + // Clear deltas we aren't using... + mDeltaRot.zero(); + mDeltaPos.zero(); + + + // Calculate the deltaScale... + VectorF deltaScale(0,0,0); + + if ( 0 <= mSelectionIdx && mSelectionIdx <= 2 ) + { + // Are we above or below the starting position relative to this axis? + PlaneF plane( mMouseDownProjPnt, mProjAxisVector[mSelectionIdx] ); + F32 sign = ( plane.whichSide( projPnt ) == PlaneF::Front ) ? 1 : -1; + F32 diff = ( projPnt - mMouseDownProjPnt ).len(); + + deltaScale[mSelectionIdx] = diff * sign; + } + else if ( 3 <= mSelectionIdx && mSelectionIdx <= 5 ) + { + + PlaneF plane( mMouseDownProjPnt, mMouseCollideLine.direction ); + F32 sign = ( plane.whichSide( projPnt ) == PlaneF::Front ) ? 1 : -1; + F32 diff = ( projPnt - mMouseDownProjPnt ).len(); + + deltaScale[sgPlanarVectors[mSelectionIdx-3][0]] = diff * sign; + deltaScale[sgPlanarVectors[mSelectionIdx-3][1]] = diff * sign; + } + else // mSelectionIdx == 6 + { + // Are we above or below the starting position relative to the camera? + VectorF normal; + mCameraMat.getColumn( 2, &normal ); + + PlaneF plane( mMouseDownProjPnt, normal ); + + F32 sign = ( plane.whichSide( projPnt ) == PlaneF::Front ) ? 1 : -1; + F32 diff = ( projPnt - mMouseDownProjPnt ).len(); + deltaScale.set(1,1,1); + deltaScale = deltaScale * sign * diff; + } + + // Save current scale, then set mDeltaScale + // to the amount it changes during this call. + mDeltaScale = mScale; + + mScale = mSavedScale; + mScale += deltaScale * mProfile->scaleScalar; + + mDeltaScale = mScale - mDeltaScale; + + for ( U32 i = 0; i < 3; i++ ) + { + if ( mScale[i] < 0.01f ) + mScale[i] = 0.01f; + } + } + + mDirty = true; + } + else if ( mProfile->mode == RotateMode ) + { + // Clear deltas we aren't using... + mDeltaScale.zero(); + mDeltaPos.zero(); + + bool doScreenRot = ( mSelectionIdx == 6 ); + + U32 rotAxisIdx = ( doScreenRot ) ? 1 : mSelectionIdx; + + Point3F mousePntSS( event.mousePoint.x, event.mousePoint.y, 0.0f ); + + Point3F pntSS0 = mElipseCursorCollidePntSS + mElipseCursorCollideVecSS * 10000.0f; + Point3F pntSS1 = mElipseCursorCollidePntSS - mElipseCursorCollideVecSS * 10000.0f; + + Point3F closestPntSS = MathUtils::mClosestPointOnSegment( pntSS0, pntSS1, mousePntSS ); + + Point3F offsetDir = closestPntSS - mElipseCursorCollidePntSS; + F32 offsetDist = offsetDir.len(); + offsetDir.normalizeSafe(); + + F32 dot = mDot( mElipseCursorCollideVecSS, offsetDir ); + mSign = mIsZero( dot ) ? 0.0f : ( dot > 0.0f ) ? -1.0f : 1.0f; + + F32 offsetDistWS = ( offsetDist * ( mOrigin - mCameraPos ).len() ) / mLastWorldToScreenScale.y; + + // The angle that we will rotate the (saved) gizmo transform by to + // generate the current gizmo transform. + F32 angle = offsetDistWS * mSign * mProfile->rotateScalar; + mDeltaAngle = angle - mLastAngle; + mLastAngle = angle; + + mDeltaRot.set(0,0,0); + mDeltaRot[rotAxisIdx] = mDeltaAngle; + + EulerF totalRot(0,0,0); + totalRot[rotAxisIdx] = angle; + + if ( doScreenRot ) + { + // Transform the rotation around the y axis in a camera-relative + // space into world space. + + VectorF fvec( mOrigin - mCameraPos ); + fvec.normalizeSafe(); + VectorF rvec; + mCameraMat.getColumn( 0, &rvec ); + VectorF uvec = mCross( rvec, fvec ); + uvec.normalizeSafe(); + rvec = mCross( fvec, uvec ); + rvec.normalizeSafe(); + + MatrixF xfm( true ); + xfm.setColumn( 0, rvec ); + xfm.setColumn( 1, fvec ); + xfm.setColumn( 2, uvec ); + + xfm.mulV(totalRot); + xfm.mulV(mDeltaRot); + + // Transform the world space rotation into gizmo space. + + MatrixF xfm2( mSavedTransform ); + xfm2.inverse(); + + xfm2.mulV( totalRot ); + xfm2.mulV( mDeltaRot ); + } + + if ( mCurrentAlignment == World ) + { + MatrixF mat = mObjectMat; + mat.inverse(); + mat.mulV(totalRot); + mat.mulV(mDeltaRot); + } + + MatrixF rotMat( totalRot ); + mTransform = mSavedTransform * rotMat; + mTransform.setPosition( mSavedTransform.getPosition() ); + + mDirty = true; + } + + mLastMouseEvent = event; +} + +//------------------------------------------------------------------------------ + +void Gizmo::renderGizmo(const MatrixF &cameraTransform) +{ + mLastWorldMat = GFX->getWorldMatrix(); + mLastProjMat = GFX->getProjectionMatrix(); + mLastViewport = GFX->getViewport(); + mLastWorldToScreenScale = GFX->getWorldToScreenScale(); + + // Save the Camera transform matrix, used all over... + mCameraMat = cameraTransform; + mCameraPos = mCameraMat.getPosition(); + + // Change the far plane distance so that the gizmo is always visible. + F32 l, r, b, t, n, f; + bool ortho; + GFX->getFrustum( &l, &r, &b, &t, &n, &f, &ortho ); + GFX->setFrustum( l, r, b, t, n, 100000.0f ); + + _updateEnabledAxices(); + + _updateState(); + + _calcAxisInfo(); + + // Render plane (if set to render) behind the gizmo + if ( mProfile->mode != NoneMode ) + _renderPlane(); + + _setStateBlock(); + + // Special case for NoneMode, + // we only render the primary axis with no tips. + if ( mProfile->mode == NoneMode ) + { + _renderPrimaryAxis(); + return; + } + + if ( mProfile->mode == RotateMode ) + { + PrimBuild::begin( GFXLineList, 6 ); + + // Render the primary axisIdx + for(U32 i = 0; i < 3; i++) + { + PrimBuild::color( (i == mSelectionIdx) ? mProfile->axisColors[i] : mProfile->inActiveColor ); + PrimBuild::vertex3fv( mOrigin ); + PrimBuild::vertex3fv( mOrigin + mProjAxisVector[i] * mProjLen * 0.25f ); + } + + PrimBuild::end(); + + _renderAxisCircles(); + + return; + } + else + { + // Both Move and Scale modes render basis vectors as + // large stick lines. + _renderPrimaryAxis(); + + // Render the tips based on current operation. + + GFXTransformSaver saver( true, false ); + + GFX->multWorld(mTransform); + + if ( mProfile->mode == ScaleMode ) + { + _renderAxisBoxes(); + } + else if ( mProfile->mode == RotateMode ) + { + _renderAxisCircles(); + } + else if ( mProfile->mode == MoveMode ) + { + _renderAxisArrows(); + } + + saver.restore(); + } + + // Render Centroid Handle... + if ( mProfile->mode == MoveMode || mProfile->mode == ScaleMode ) + { + F32 tipScale = mProjLen * 0.075f; + GFXTransformSaver saver; + GFX->multWorld( mTransform ); + + if ( mHighlightCentroidHandle ) + PrimBuild::color( mProfile->centroidHighlightColor ); + else + PrimBuild::color( mProfile->centroidColor ); + + for(U32 j = 0; j < 6; j++) + { + PrimBuild::begin( GFXTriangleFan, 4 ); + + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][0]] * tipScale); + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][1]] * tipScale); + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][2]] * tipScale); + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][3]] * tipScale); + + PrimBuild::end(); + } + } + + // Render the planar handles... + Point3F midpnt[3]; + for(U32 i = 0; i < 3; i++) + midpnt[i] = mProjAxisVector[i] * mProjLen * 0.5f; + + PrimBuild::begin( GFXLineList, 12 ); + + for(U32 i = 0; i < 3; i++) + { + U32 axis0 = sgPlanarVectors[i][0]; + U32 axis1 = sgPlanarVectors[i][1]; + + const Point3F &p0 = midpnt[axis0]; + const Point3F &p1 = midpnt[axis1]; + + bool selected0 = false; + bool selected1 = false; + + if ( i + 3 == mSelectionIdx ) + selected0 = selected1 = true; + + ColorF color; + + if ( !mAxisEnabled[i] ) + PrimBuild::color( mProfile->inActiveColor ); + else + PrimBuild::color( selected0 ? mProfile->activeColor : mProfile->axisColors[axis0] ); + + PrimBuild::vertex3fv( mOrigin + p0 ); + PrimBuild::vertex3fv( mOrigin + p0 + p1 ); + + if ( !mAxisEnabled[i] ) + PrimBuild::color( mProfile->inActiveColor ); + else + PrimBuild::color( selected1 ? mProfile->activeColor : mProfile->axisColors[axis1] ); + + PrimBuild::vertex3fv( mOrigin + p1 ); + PrimBuild::vertex3fv( mOrigin + p0 + p1 ); + } + + PrimBuild::end(); + + if ( mUniformHandleEnabled && ( mProfile->mode == MoveMode || mProfile->mode == ScaleMode ) ) + { + if ( mSelectionIdx >= 3 && mSelectionIdx <= 5 ) + { + U32 idx0 = sgPlanarVectors[mSelectionIdx-3][0]; + U32 idx1 = sgPlanarVectors[mSelectionIdx-3][1]; + + Point3F& p0 = midpnt[idx0]; + Point3F& p1 = midpnt[idx1]; + + PrimBuild::color4i( 237, 219, 0, 150 ); + + PrimBuild::begin( GFXTriangleFan, 4 ); + PrimBuild::vertex3fv( mOrigin ); + PrimBuild::vertex3fv( mOrigin + p0 ); + PrimBuild::vertex3fv( mOrigin + p0 + p1 ); + PrimBuild::vertex3fv( mOrigin + p1 ); + PrimBuild::end(); + } + } + + // Restore frustum + if (!ortho) + GFX->setFrustum(l, r, b, t, n, f); + else + GFX->setOrtho(l, r, b, t, n, f); +} + +void Gizmo::renderText( const RectI &viewPort, const MatrixF &modelView, const MatrixF &projection ) +{ + if ( mProfile->mode == NoneMode ) + return; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + _setStateBlock(); + + char axisText[] = "xyz"; + + F32 projLen = mProjLen * 1.05f; + if ( mProfile->mode == RotateMode ) + projLen *= 0.28f; + + for ( U32 i = 0; i < 3; i++ ) + { + const Point3F & centroid = mOrigin; + Point3F pos(centroid.x + mProjAxisVector[i].x * projLen, + centroid.y + mProjAxisVector[i].y * projLen, + centroid.z + mProjAxisVector[i].z * projLen); + + Point3F sPos; + + if ( MathUtils::mProjectWorldToScreen( pos, &sPos, viewPort, modelView, projection ) ) + { + ColorI textColor = ColorI(170,170,170); + + if ( mProfile->mode == RotateMode ) + { + textColor.set(170,170,170); + if ( i == mSelectionIdx ) + textColor = mProfile->axisColors[i]; + } + else + { + if ( i == mSelectionIdx || !mAxisEnabled[i] ) + textColor = mProfile->inActiveColor; + else + textColor = mProfile->axisColors[i]; + } + + char buf[2]; + buf[0] = axisText[i]; buf[1] = '\0'; + drawer->setBitmapModulation(textColor); + drawer->drawText( mProfile->font, Point2I((S32)sPos.x, (S32)sPos.y), buf ); + } + } +} + +// Gizmo Internal Methods... + +void Gizmo::_calcAxisInfo() +{ + mOrigin = mTransform.getPosition(); + + for ( U32 i = 0; i < 3; i++ ) + { + VectorF tmp; + mTransform.mulV(sgAxisVectors[i], &tmp); + mProjAxisVector[i] = tmp; + mProjAxisVector[i].normalizeSafe(); + } + + // get the projected size... + + Point3F dir = mOrigin - mCameraPos; + F32 dist = dir.len(); + + mProjLen = ( (F32)mProfile->screenLen * dist ) / mLastWorldToScreenScale.y; +} + +void Gizmo::_renderPrimaryAxis() +{ + PrimBuild::begin( GFXLineList, 6 ); + + // Render the primary axis(s) + for(U32 i = 0; i < 3; i++) + { + ColorI color = mProfile->axisColors[i]; + + if ( !mAxisEnabled[i] ) + color = mProfile->inActiveColor; + else + { + if ( 0 <= mSelectionIdx && mSelectionIdx <= 2 ) + { + if ( i == mSelectionIdx ) + color = mProfile->activeColor; + } + else if ( 3 <= mSelectionIdx && mSelectionIdx <= 5 ) + { + if ( i == sgPlanarVectors[mSelectionIdx-3][0] || + i == sgPlanarVectors[mSelectionIdx-3][1] ) + color = mProfile->activeColor; + } + else if ( mSelectionIdx == 6 ) + color = mProfile->activeColor; + } + + PrimBuild::color( color ); + PrimBuild::vertex3fv( mOrigin ); + PrimBuild::vertex3fv( mOrigin + mProjAxisVector[i] * mProjLen ); + } + + PrimBuild::end(); +} + +void Gizmo::_renderAxisArrows() +{ + F32 tipScale = mProjLen * 0.25; + PrimBuild::begin( GFXTriangleList, 12*3*3 ); + + S32 x, y, z; + Point3F pnt; + + for ( U32 axisIdx = 0; axisIdx < 3; axisIdx++ ) + { + if ( !mAxisEnabled[axisIdx] ) + PrimBuild::color( mProfile->inActiveColor ); + else + PrimBuild::color( mProfile->axisColors[axisIdx] ); + + x = sgAxisRemap[axisIdx][0]; + y = sgAxisRemap[axisIdx][1]; + z = sgAxisRemap[axisIdx][2]; + + for ( U32 i = 0; i < sizeof(sgConeVerts) / (sizeof(U32)*3); ++i ) + { + const Point3F& conePnt0 = sgConePnts[sgConeVerts[i][0]]; + pnt.set(conePnt0[x], conePnt0[y], conePnt0[z]); + PrimBuild::vertex3fv(pnt * tipScale + sgAxisVectors[axisIdx] * mProjLen); + + const Point3F& conePnt1 = sgConePnts[sgConeVerts[i][1]]; + pnt.set(conePnt1[x], conePnt1[y], conePnt1[z]); + PrimBuild::vertex3fv(pnt * tipScale + sgAxisVectors[axisIdx] * mProjLen); + + const Point3F& conePnt2 = sgConePnts[sgConeVerts[i][2]]; + pnt.set(conePnt2[x], conePnt2[y], conePnt2[z]); + PrimBuild::vertex3fv(pnt * tipScale + sgAxisVectors[axisIdx] * mProjLen); + } + } + + PrimBuild::end(); +} + +void Gizmo::_renderAxisBoxes() +{ + F32 tipScale = mProjLen * 0.1; + F32 pos = mProjLen - 0.5 * tipScale; + + for( U32 axisIdx = 0; axisIdx < 3; ++axisIdx ) + { + if ( mAxisEnabled[axisIdx] ) + PrimBuild::color( mProfile->axisColors[axisIdx] ); + else + PrimBuild::color( mProfile->inActiveColor ); + + for(U32 j = 0; j < 6; j++) + { + PrimBuild::begin( GFXTriangleFan, 4 ); + + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][0]] * tipScale + sgAxisVectors[axisIdx] * pos ); + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][1]] * tipScale + sgAxisVectors[axisIdx] * pos ); + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][2]] * tipScale + sgAxisVectors[axisIdx] * pos ); + PrimBuild::vertex3fv( sgCenterBoxPnts[sgBoxVerts[j][3]] * tipScale + sgAxisVectors[axisIdx] * pos ); + + PrimBuild::end(); + } + } +} + +void Gizmo::_renderAxisCircles() +{ + // Setup the WorldMatrix for rendering in camera space. + // Honestly not sure exactly why this works but it does... + GFX->pushWorldMatrix(); + MatrixF cameraXfm = GFX->getWorldMatrix(); + cameraXfm.inverse(); + const Point3F cameraPos = cameraXfm.getPosition(); + cameraXfm.setPosition( mOrigin ); + GFX->multWorld(cameraXfm); + + // Render the ScreenSpace rotation circle... + { + F32 radius = mProjLen * 0.7f; + U32 segments = 40; + F32 step = mDegToRad(360.0f)/ segments; + Point3F pnt; + + PrimBuild::color( (mSelectionIdx == 6) ? mProfile->activeColor : mProfile->inActiveColor ); + PrimBuild::begin( GFXLineStrip, segments+1 ); + + for(U32 i = 0; i <= segments; i++) + { + F32 angle = i * step; + + pnt.x = mCos(angle) * radius; + pnt.y = 0.0f; + pnt.z = mSin(angle) * radius; + + PrimBuild::vertex3fv( pnt ); + } + + PrimBuild::end(); + } + + // Render the gizmo/sphere bounding circle... + { + F32 radius = mProjLen * 0.5f; + U32 segments = 40; + F32 step = mDegToRad(360.0f) / segments; + Point3F pnt; + + // Render as solid (with transparency) when the sphere is selected + if ( mSelectionIdx == 7 ) + { + ColorI color = mProfile->inActiveColor; + color.alpha = 100; + PrimBuild::color( color ); + PrimBuild::begin( GFXTriangleFan, segments+2 ); + + PrimBuild::vertex3fv( Point3F(0,0,0) ); + + for(U32 i = 0; i <= segments; i++) + { + F32 angle = i * step; + + pnt.x = mCos(angle) * radius; + pnt.y = 0.0f; + pnt.z = mSin(angle) * radius; + + PrimBuild::vertex3fv( pnt ); + } + + PrimBuild::end(); + } + else + { + PrimBuild::color( mProfile->inActiveColor ); + PrimBuild::begin( GFXLineStrip, segments+1 ); + + for(U32 i = 0; i <= segments; i++) + { + F32 angle = i * step; + + pnt.x = mCos(angle) * radius; + pnt.y = 0.0f; + pnt.z = mSin(angle) * radius; + + PrimBuild::vertex3fv( pnt ); + } + + PrimBuild::end(); + } + } + + // Done rendering in camera space. + GFX->popWorldMatrix(); + + // Setup WorldMatrix for Gizmo-Space rendering. + GFX->pushWorldMatrix(); + GFX->multWorld(mTransform); + + // Render the axis-manipulation ellipses... + { + F32 radius = mProjLen * 0.5f; + U32 segments = 40; + F32 step = mDegToRad(360.0f) / segments; + U32 x,y,z; + + VectorF planeNormal; + planeNormal = mOrigin - cameraPos; + planeNormal.normalize(); + PlaneF clipPlane( mOrigin, planeNormal ); + + MatrixF worldToGizmo = mTransform; + worldToGizmo.inverse(); + mTransformPlane( worldToGizmo, Point3F(1,1,1), clipPlane, &clipPlane ); + + for ( U32 axis = 0; axis < 3; axis++ ) + { + if ( mAxisEnabled[axis] ) + PrimBuild::color( (axis == mSelectionIdx) ? mProfile->activeColor : mProfile->axisColors[axis] ); + else + PrimBuild::color( mProfile->inActiveColor ); + + x = sgAxisRemap[axis][0]; + y = sgAxisRemap[axis][1]; + z = sgAxisRemap[axis][2]; + + PrimBuild::begin( GFXLineList, (segments+1) * 2 ); + + for ( U32 i = 1; i <= segments; i++ ) + { + F32 ang0 = (i-1) * step; + F32 ang1 = i * step; + + Point3F temp; + + temp.x = 0.0f; + temp.y = mCos(ang0) * radius; + temp.z = mSin(ang0) * radius; + Point3F pnt0( temp[x], temp[y], temp[z] ); + + temp.x = 0.0f; + temp.y = mCos(ang1) * radius; + temp.z = mSin(ang1) * radius; + Point3F pnt1( temp[x], temp[y], temp[z] ); + + bool valid0 = ( clipPlane.whichSide(pnt0) == PlaneF::Back ); + bool valid1 = ( clipPlane.whichSide(pnt1) == PlaneF::Back ); + + //if ( !valid0 && !valid1 ) + // continue; + + if ( !valid0 || !valid1 ) + continue; + + PrimBuild::vertex3fv( pnt0 ); + PrimBuild::vertex3fv( pnt1 ); + } + + PrimBuild::end(); + } + } + + // Done rendering in Gizmo-Space. + GFX->popWorldMatrix(); + + // Render hint-arrows... + /* + if ( mMouseDown && mSelectionIdx != -1 ) + { + PrimBuild::begin( GFXLineList, 4 ); + + F32 hintArrowScreenLength = mProfile->screenLen * 0.5f; + F32 hintArrowTipScreenLength = mProfile->screenLen * 0.25; + + F32 worldZDist = ( mMouseCollideLine.origin - mCameraPos ).len(); + F32 hintArrowLen = ( hintArrowScreenLength * worldZDist ) / mLastWorldToScreenScale.y; + F32 hintArrowTipLen = ( hintArrowTipScreenLength * worldZDist ) / mLastWorldToScreenScale.y; + + Point3F p0 = mMouseCollideLine.origin - mMouseCollideLine.direction * hintArrowLen; + Point3F p1 = mMouseCollideLine.origin; + Point3F p2 = mMouseCollideLine.origin + mMouseCollideLine.direction * hintArrowLen; + + // For whatever reason, the sign is actually negative if we are on the + // positive size of the MouseCollideLine direction. + ColorI color0 = ( mSign > 0.0f ) ? mProfile->activeColor : mProfile->inActiveColor; + ColorI color1 = ( mSign < 0.0f ) ? mProfile->activeColor : mProfile->inActiveColor; + + PrimBuild::color( color0 ); + PrimBuild::vertex3fv( p1 ); + PrimBuild::vertex3fv( p0 ); + + PrimBuild::color( color1 ); + PrimBuild::vertex3fv( p1 ); + PrimBuild::vertex3fv( p2 ); + PrimBuild::end(); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( false, false ); + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->drawCone( desc, p0, p0 - mMouseCollideLine.direction * hintArrowTipLen, hintArrowTipLen * 0.5f, color0 ); + drawer->drawCone( desc, p2, p2 + mMouseCollideLine.direction * hintArrowTipLen, hintArrowTipLen * 0.5f, color1 ); + } + */ +} + +void Gizmo::_renderPlane() +{ + Point2F size( mProfile->planeDim, mProfile->planeDim ); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + if ( mProfile->renderPlane ) + GFX->getDrawUtil()->drawSolidPlane( desc, mOrigin, size, mProfile->gridColor ); + + if ( mProfile->renderPlaneHashes ) + { + // TODO: This wasn't specified before... so it was + // rendering lines that were invisible. Maybe we need + // a new field for grid line color? + ColorI gridColor( mProfile->gridColor ); + gridColor.alpha *= 2; + + GFX->getDrawUtil()->drawPlaneGrid( desc, mOrigin, size, Point2F( mProfile->gridSize.x, mProfile->gridSize.y ), gridColor ); + } +} + + +void Gizmo::_setStateBlock() +{ + if ( !mStateBlock ) + { + GFXStateBlockDesc sb; + sb.blendDefined = true; + sb.blendEnable = true; + sb.blendSrc = GFXBlendSrcAlpha; + sb.blendDest = GFXBlendInvSrcAlpha; + sb.zDefined = true; + sb.zEnable = false; + sb.cullDefined = true; + sb.cullMode = GFXCullNone; + mStateBlock = GFX->createStateBlock(sb); + } + + GFX->setStateBlock( mStateBlock ); +} + +Point3F Gizmo::_snapPoint( const Point3F &pnt ) const +{ + if ( !mProfile->snapToGrid ) + return pnt; + + Point3F snap; + snap.x = _snapFloat( pnt.x, mProfile->gridSize.x ); + snap.y = _snapFloat( pnt.y, mProfile->gridSize.y ); + snap.z = _snapFloat( pnt.z, mProfile->gridSize.z ); + + return snap; +} + +F32 Gizmo::_snapFloat( const F32 &val, const F32 &snap ) const +{ + if ( snap == 0.0f ) + return val; + + F32 a = mFmod( val, snap ); + + F32 temp = val; + + if ( mFabs(a) > (snap / 2) ) + val < 0.0f ? temp -= snap : temp += snap; + + return(temp - a); +} + +Align Gizmo::_filteredAlignment() +{ + Align align = mProfile->alignment; + + // Special case in ScaleMode, always be in object. + if ( mProfile->mode == ScaleMode ) + align = Object; + + return align; +} + +void Gizmo::_updateState( bool collideGizmo ) +{ + if ( !mProfile ) + return; + + // Update mCurrentMode + + if ( mCurrentMode != mProfile->mode ) + { + // Changing the mode invalidates the prior selection since the gizmo + // has changed shape. + + mCurrentMode = mProfile->mode; + mSelectionIdx = -1; + + // Determine the new selection unless we have been told not to. + if ( collideGizmo ) + collideAxisGizmo( mLastMouseEvent ); + + // Also cancel any current dragging operation since it would only be + // valid if the mouse down event occurred first. + + mMouseDown = false; + } + + // Update mCurrentAlignment + + // Changing the alignment during a drag could be really bad. + // Haven't actually tested this though. + if ( mMouseDown ) + return; + + Align desired = _filteredAlignment(); + + if ( desired == World && + mCurrentAlignment == Object ) + { + mObjectMat = mTransform; + mTransform.identity(); + mTransform.setPosition( mObjectMat.getPosition() ); + } + else if ( desired == Object && + mCurrentAlignment == World ) + { + Point3F pos = mTransform.getPosition(); + mTransform = mObjectMat; + mTransform.setPosition( pos ); + mObjectMat.identity(); + mObjectMat.setPosition( pos ); + } + + mCurrentAlignment = desired; +} + +void Gizmo::_updateEnabledAxices() +{ + if ( ( mProfile->mode == ScaleMode && mProfile->flags & GizmoProfile::CanScaleUniform ) || + ( mProfile->mode == MoveMode && mProfile->flags & GizmoProfile::CanTranslateUniform ) ) + mUniformHandleEnabled = true; + else + mUniformHandleEnabled = false; + + // Early out if we are in a mode that is disabled. + if ( mProfile->mode == RotateMode && !(mProfile->flags & GizmoProfile::CanRotate ) ) + { + mAxisEnabled[0] = mAxisEnabled[1] = mAxisEnabled[2] = false; + return; + } + if ( mProfile->mode == MoveMode && !(mProfile->flags & GizmoProfile::CanTranslate ) ) + { + mAxisEnabled[0] = mAxisEnabled[1] = mAxisEnabled[2] = false; + return; + } + if ( mProfile->mode == ScaleMode && !(mProfile->flags & GizmoProfile::CanScale ) ) + { + mAxisEnabled[0] = mAxisEnabled[1] = mAxisEnabled[2] = false; + return; + } + + for ( U32 i = 0; i < 3; i++ ) + { + mAxisEnabled[i] = false; + + // Some tricky enum math... x/y/z are sequential in the enum + if ( ( mProfile->mode == RotateMode ) && + !( mProfile->flags & ( GizmoProfile::CanRotateX + i ) ) ) + continue; + if ( ( mProfile->mode == MoveMode ) && + !( mProfile->flags & ( GizmoProfile::CanTranslateX + i ) ) ) + continue; + if ( ( mProfile->mode == ScaleMode ) && + !( mProfile->flags & ( GizmoProfile::CanScaleX + i ) ) ) + continue; + + mAxisEnabled[i] = true; + } +} diff --git a/gui/worldEditor/gizmo.h b/gui/worldEditor/gizmo.h new file mode 100644 index 0000000..4c02c30 --- /dev/null +++ b/gui/worldEditor/gizmo.h @@ -0,0 +1,305 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#ifndef _GIZMO_H_ +#define _GIZMO_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/core/guiTypes.h" +#endif +#ifndef _MATHUTILS_H_ +#include "math/mathUtils.h" +#endif + + +enum Mode { + NoneMode = 0, + MoveMode, // 1 + RotateMode, // 2 + ScaleMode, // 3 + ModeEnumCount +}; + +enum Align { + World = 0, + Object, + AlignEnumCount +}; + + +// +class GizmoProfile : public SimObject +{ + typedef SimObject Parent; + +public: + + GizmoProfile(); + //virtual ~GizmoProfile(); + + DECLARE_CONOBJECT( GizmoProfile ); + + bool onAdd(); + + static void initPersistFields(); + + // Data Fields + + Mode mode; + Align alignment; + + F32 rotateScalar; + F32 scaleScalar; + U32 screenLen; + ColorI axisColors[3]; + ColorI activeColor; + ColorI inActiveColor; + ColorI centroidColor; + ColorI centroidHighlightColor; + Resource font; + + // new stuff... + + bool snapToGrid; + F32 scaleSnap; + bool allowSnapScale; + F32 rotationSnap; + bool allowSnapRotations; + + Point3F gridSize; + bool renderPlane; + bool renderPlaneHashes; + ColorI gridColor; + F32 planeDim; + + enum Flags { + CanRotate = 1 << 0, // 0 + CanRotateX = 1 << 1, + CanRotateY = 1 << 2, + CanRotateZ = 1 << 3, + CanRotateScreen = 1 << 4, + CanScale = 1 << 5, + CanScaleX = 1 << 6, + CanScaleY = 1 << 7, + CanScaleZ = 1 << 8, + CanScaleUniform = 1 << 9, + CanTranslate = 1 << 10, + CanTranslateX = 1 << 11, + CanTranslateY = 1 << 12, + CanTranslateZ = 1 << 13, + CanTranslateUniform = 1 << 14, + PlanarHandlesOn = 1 << 15 + }; + + S32 flags; +}; + + +// This class contains code for rendering and manipulating a 3D gizmo, it +// is usually used as a helper within a TSEdit-derived control. +// +// The Gizmo has a MatrixF transform and Point3F scale on which it will +// operate by passing it Gui3DMouseEvent(s). +// +// The idea is to set the Gizmo transform/scale to that of another 3D object +// which is being manipulated, pass mouse events into the Gizmo, read the +// new transform/scale out, and set it to onto the object. +// And of course the Gizmo can be rendered. +// +// Gizmo derives from SimObject only because this allows its properties +// to be initialized directly from script via fields. + +class Gizmo : public SimObject +{ + typedef SimObject Parent; + + friend class WorldEditor; + +public: + + enum Selection { + None = -1, + Axis_X = 0, + Axis_Y = 1, + Axis_Z = 2, + Plane_XY = 3, // Normal = Axis_Z + Plane_XZ = 4, // Normal = Axis_Y + Plane_YZ = 5, // Normal = Axis_X + Custom0 = 6, + Custom1 = 7 + }; + + Gizmo(); + ~Gizmo(); + + DECLARE_CONOBJECT( Gizmo ); + + // SimObject + bool onAdd(); + void onRemove(); + static void initPersistFields(); + + // Mutators + void set( const MatrixF &objMat, const Point3F &worldPos, const Point3F &objScale ); + void setProfile( GizmoProfile *profile ) { mProfile = profile; } + + // Accessors + const MatrixF& getTransform() const { return mTransform; } + Point3F getPosition() const { return mTransform.getPosition(); } + Point3F getScale() const; + GizmoProfile* getProfile() { return mProfile; } + Point3F getOffset() const { return mDeltaPos; } + Point3F getProjectPoint() const { return mProjPnt; } + Point3F getDeltaRot() const { return mDeltaRot; } + Point3F getDeltaScale() const { return mDeltaScale; } + + // Gizmo Interface methods... + + // Set the current highlight mode on the gizmo's centroid handle + void setCentroidHandleHighlight( bool state ) { mHighlightCentroidHandle = state; } + + // Must be called before on3DMouseDragged to save state + void on3DMouseDown( const Gui3DMouseEvent &event ); + + // So Gizmo knows the current mouse button state. + void on3DMouseUp( const Gui3DMouseEvent &event ); + + // Test Gizmo for collisions and set the Gizmo Selection (the part under the cursor) + void on3DMouseMove( const Gui3DMouseEvent &event ); + + // Make changes to the Gizmo transform/scale (depending on mode) + void on3DMouseDragged( const Gui3DMouseEvent &event ); + + // Returns an enum describing the part of the Gizmo that is Selected + // ( under the cursor ). This should be called AFTER calling onMouseMove + // or collideAxisGizmo + // + // -1 None + // 0 Axis_X + // 1 Axis_Y + // 2 Axis_Z + // 3 Plane_XY + // 4 Plane_XZ + // 5 Plane_YZ + Selection getSelection(); + void setSelection( Selection sel ) { mSelectionIdx = sel; } + + // Returns the object space vector corresponding to a Selection. + Point3F selectionToAxisVector( Selection axis ); + + // These provide the user an easy way to check if the Gizmo's transform + // or scale have changed by calling markClean prior to calling + // on3DMouseDragged, and calling isDirty after. + bool isDirty() { return mDirty; } + void markClean() { mDirty = false; } + + // Renders the 3D Gizmo in the scene, GFX must be setup for proper + // 3D rendering before calling this! + // Calling this will change the GFXStateBlock! + void renderGizmo(const MatrixF &cameraTransform); + + // Renders text associated with the Gizmo, GFX must be setup for proper + // 2D rendering before calling this! + // Calling this will change the GFXStateBlock! + void renderText( const RectI &viewPort, const MatrixF &modelView, const MatrixF &projection ); + + // Returns true if the mouse event collides with any part of the Gizmo + // and sets the Gizmo's current Selection. + // You can call this or on3DMouseMove, they are identical + bool collideAxisGizmo( const Gui3DMouseEvent & event ); + +protected: + + void _calcAxisInfo(); + void _setStateBlock(); + void _renderPrimaryAxis(); + void _renderAxisArrows(); + void _renderAxisBoxes(); + void _renderAxisCircles(); + void _renderAxisText(); + void _renderPlane(); + Point3F _snapPoint( const Point3F &pnt ) const; + F32 _snapFloat( const F32 &val, const F32 &snap ) const; + Align _filteredAlignment(); + void _updateState( bool collideGizmo = true ); + void _updateEnabledAxices(); + +protected: + + GizmoProfile *mProfile; + + MatrixF mObjectMat; + MatrixF mTransform; + MatrixF mLastTransform; + MatrixF mSavedTransform; + MatrixF *mRenderTransform; + + Align mCurrentAlignment; + Mode mCurrentMode; + + MatrixF mCameraMat; + Point3F mCameraPos; + + Point3F mScale; + Point3F mSavedScale; + Point3F mDeltaScale; + Point3F mLastScale; + Point3F mScaleInfluence; + + EulerF mRot; + EulerF mSavedRot; + EulerF mDeltaRot; + F32 mDeltaAngle; + F32 mLastAngle; + Point2I mMouseDownPos; + Point3F mMouseDownProjPnt; + Point3F mDeltaPos; + Point3F mProjPnt; + Point3F mOrigin; + Point3F mProjAxisVector[3]; + F32 mProjLen; + S32 mSelectionIdx; + bool mDirty; + Gui3DMouseEvent mLastMouseEvent; + GFXStateBlockRef mStateBlock; + + PlaneF mMouseCollidePlane; + MathUtils::Line mMouseCollideLine; + + bool mMouseDown; + + F32 mSign; + + bool mAxisEnabled[3]; + bool mUniformHandleEnabled; + + bool mHighlightCentroidHandle; + + // Initialized in renderGizmo and saved for later use when projecting + // to screen space for selection testing. + MatrixF mLastWorldMat; + MatrixF mLastProjMat; + RectI mLastViewport; + Point2F mLastWorldToScreenScale; + + // Screenspace cursor collision information used in rotation mode. + Point3F mElipseCursorCollidePntSS; + Point3F mElipseCursorCollideVecSS; + + /// A large hard coded distance used to test + /// gizmo axis selection. + static F32 smProjectDistance; + +}; + +#endif // _GIZMO_H_ \ No newline at end of file diff --git a/gui/worldEditor/guiDecalEditorCtrl.cpp b/gui/worldEditor/guiDecalEditorCtrl.cpp new file mode 100644 index 0000000..efa59af --- /dev/null +++ b/gui/worldEditor/guiDecalEditorCtrl.cpp @@ -0,0 +1,958 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef TORQUE_TGB_ONLY + +#include "guiDecalEditorCtrl.h" +#include "platform/platform.h" + +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "collision/collision.h" +#include "math/util/frustum.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxTextureHandle.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonCtrl.h" +#include "gui/worldEditor/gizmo.h" +#include "T3D/decal/decalManager.h" +#include "T3D/decal/decalInstance.h" +#include "gui/worldEditor/undoActions.h" + +IMPLEMENT_CONOBJECT(GuiDecalEditorCtrl); + +GuiDecalEditorCtrl::GuiDecalEditorCtrl() +{ + mSELDecal = NULL; + mHLDecal = NULL; + mCurrentDecalData = NULL; + mMode = "AddDecalMode"; +} + +GuiDecalEditorCtrl::~GuiDecalEditorCtrl() +{ + // nothing to do +} + +bool GuiDecalEditorCtrl::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + return true; +} + +void GuiDecalEditorCtrl::initPersistFields() +{ + addField( "currentDecalData", TypeSimObjectPtr, Offset( mCurrentDecalData, GuiDecalEditorCtrl ) ); + + Parent::initPersistFields(); +} + +void GuiDecalEditorCtrl::onEditorDisable() +{ + // Tools are not deleted/recreated between missions, but decals instances + // ARE. So we must release any references. + mSELDecal = NULL; + mHLDecal = NULL; +} + +bool GuiDecalEditorCtrl::onWake() +{ + if ( !Parent::onWake() ) + return false; + + + + return true; +} + +void GuiDecalEditorCtrl::onSleep() +{ + Parent::onSleep(); +} + +void GuiDecalEditorCtrl::get3DCursor( GuiCursor *&cursor, + bool &visible, + const Gui3DMouseEvent &event_ ) +{ + cursor = NULL; + visible = false; + + GuiCanvas *root = getRoot(); + if ( !root ) + return; + + S32 currCursor = PlatformCursorController::curArrow; + + if ( root->mCursorChanged == currCursor ) + return; + + PlatformWindow *window = root->getPlatformWindow(); + PlatformCursorController *controller = window->getCursorController(); + + // We've already changed the cursor, + // so set it back before we change it again. + if( root->mCursorChanged != -1) + controller->popCursor(); + + // Now change the cursor shape + controller->pushCursor(currCursor); + root->mCursorChanged = currCursor; +} + +void GuiDecalEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) +{ + if ( !isFirstResponder() ) + setFirstResponder(); + + bool dblClick = ( event.mouseClickCount > 1 ); + + // Gather selected decal information + RayInfo ri; + bool hit = getRayInfo( event, &ri ); + + Point3F start = event.pos; + Point3F end = start + event.vec * 300.0f; // use visible distance here?? + + DecalInstance *pDecal = gDecalManager->raycast( start, end ); + + if( mMode.compare("AddDecalMode") != 0 ) + { + if ( mSELDecal ) + { + // If our click hit the gizmo we are done. + mGizmo->on3DMouseDown( event ); + if ( mGizmo->getSelection() != Gizmo::None ) + { + char returnBuffer[256]; + dSprintf(returnBuffer, sizeof(returnBuffer), "%f %f %f %f %f %f %f", + mSELDecal->mPosition.x, mSELDecal->mPosition.y, mSELDecal->mPosition.z, + mSELDecal->mTangent.x, mSELDecal->mTangent.y, mSELDecal->mTangent.z, + mSELDecal->mSize); + + Con::executef( this, "prepGizmoTransform", Con::getIntArg(mSELDecal->mId), returnBuffer ); + + return; + } + } + + if ( mHLDecal && pDecal == mHLDecal ) + { + mHLDecal = NULL; + selectDecal( pDecal ); + + if ( isMethod( "onSelectInstance" ) ) + { + char idBuf[512]; + dSprintf(idBuf, 512, "%i", pDecal->mId); + Con::executef( this, "onSelectInstance", String(idBuf).c_str(), pDecal->mDataBlock->lookupName.c_str() ); + } + + return; + } + else if ( hit && !pDecal) + { + if ( dblClick ) + setMode( String("AddDecalMode"), true ); + + return; + } + } + else + { + // If we accidently hit a decal, then bail(probably an accident). If the use hits the decal twice, + // then boot them into selection mode and select the decal. + if ( mHLDecal && pDecal == mHLDecal ) + { + if ( dblClick ) + { + mHLDecal = NULL; + selectDecal( pDecal ); + + if ( isMethod( "onSelectInstance" ) ) + { + char idBuf[512]; + dSprintf(idBuf, 512, "%i", pDecal->mId); + Con::executef( this, "onSelectInstance", String(idBuf).c_str(), pDecal->mDataBlock->lookupName.c_str() ); + } + setMode( String("SelectDecalMode"), true ); + } + return; + } + + if ( hit && mCurrentDecalData ) // Create a new decal... + { + U8 flags = PermanentDecal | SaveDecal; + + DecalInstance *decalInst = gDecalManager->addDecal( ri.point, ri.normal, 0.0f, mCurrentDecalData, 1.0f, -1, flags ); + + if ( decalInst ) + { + // Give the decal an id + decalInst->mId = gDecalManager->mDecalInstanceVec.size(); + gDecalManager->mDecalInstanceVec.push_back(decalInst); + + selectDecal( decalInst ); + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + + // Create the UndoAction. + DICreateUndoAction *action = new DICreateUndoAction("Create Decal"); + action->addDecal( *decalInst ); + + action->mEditor = this; + // Submit it. + undoMan->addAction( action ); + + if ( isMethod( "onCreateInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", decalInst->mId); + Con::executef( this, "onCreateInstance", buffer, decalInst->mDataBlock->lookupName.c_str()); + } + } + + return; + } + } + + if ( !mSELDecal ) + return; +} + +void GuiDecalEditorCtrl::on3DRightMouseDown(const Gui3DMouseEvent & event) +{ + //mIsPanning = true; + //mGizmo->on3DRightMouseDown( event ); +} + +void GuiDecalEditorCtrl::on3DRightMouseUp(const Gui3DMouseEvent & event) +{ + //mIsPanning = false; +// mGizmo->on3DRightMouseUp( event ); +} + +void GuiDecalEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) +{ + if ( mSELDecal ) + { + if ( mGizmo->isDirty() ) + { + char returnBuffer[256]; + dSprintf(returnBuffer, sizeof(returnBuffer), "%f %f %f %f %f %f %f", + mSELDecal->mPosition.x, mSELDecal->mPosition.y, mSELDecal->mPosition.z, + mSELDecal->mTangent.x, mSELDecal->mTangent.y, mSELDecal->mTangent.z, + mSELDecal->mSize); + + Con::executef( this, "completeGizmoTransform", Con::getIntArg(mSELDecal->mId), returnBuffer ); + + mGizmo->markClean(); + } + + mGizmo->on3DMouseUp( event ); + } +} + +void GuiDecalEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) +{ + if ( mSELDecal ) + mGizmo->on3DMouseMove( event ); + + RayInfo ri; + if ( !getRayInfo( event, &ri ) ) + return; + + Point3F start = event.pos; + Point3F end = start + event.vec * 300.0f; // use visible distance here?? + + DecalInstance *pDecal = gDecalManager->raycast( start, end ); + + if ( pDecal && pDecal != mSELDecal ) + mHLDecal = pDecal; + else if ( !pDecal ) + mHLDecal = NULL; +} + +void GuiDecalEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + if ( !mSELDecal ) + return; + + // Update the Gizmo. + mGizmo->on3DMouseDragged( event ); + + // Pull out the Gizmo transform + // and position. + const MatrixF &gizmoMat = mGizmo->getTransform(); + const Point3F &gizmoPos = gizmoMat.getPosition(); + + // Get the new projection vector. + VectorF upVec, rightVec; + gizmoMat.getColumn( 0, &rightVec ); + gizmoMat.getColumn( 2, &upVec ); + + const Point3F &scale = mGizmo->getScale(); + + // Set the new decal position and projection vector. + mSELDecal->mSize = (scale.x + scale.y) * 0.5f; + mSELDecal->mPosition = gizmoPos; + mSELDecal->mNormal = upVec; + mSELDecal->mTangent = rightVec; + gDecalManager->notifyDecalModified( mSELDecal ); + + Con::executef( this, "syncNodeDetails" ); +} + +void GuiDecalEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +void GuiDecalEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) +{ + // nothing to do +} + +void GuiDecalEditorCtrl::updateGuiInfo() +{ + // nothing to do +} + +void GuiDecalEditorCtrl::onRender( Point2I offset, const RectI &updateRect ) +{ + PROFILE_SCOPE( GuiDecalEditorCtrl_OnRender ); + + Parent::onRender( offset, updateRect ); +} + +void GuiDecalEditorCtrl::forceRedraw( DecalInstance * decalInstance ) +{ + if ( !decalInstance ) + return; + + GFXDrawUtil *drawUtil = GFX->getDrawUtil(); + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + Vector verts; + verts.clear(); + if ( gDecalManager->clipDecal( decalInstance, &verts ) ) + _renderDecalEdge( verts, ColorI( 255, 255, 255, 255 ) ); + + const F32 &decalSize = decalInstance->mSize; + Point3F boxSize( decalSize, decalSize, decalSize ); + + MatrixF worldMat( true ); + decalInstance->getWorldMatrix( &worldMat, true ); + + drawUtil->drawObjectBox( desc, boxSize, decalInstance->mPosition, worldMat, ColorI( 255, 255, 255, 255 ) ); +} + +void GuiDecalEditorCtrl::_renderDecalEdge( const Vector &verts, const ColorI &color ) +{ + U32 vertCount = verts.size(); + + GFXTransformSaver saver; + + PrimBuild::color( color ); + + Point3F endPt( 0, 0, 0 ); + for ( U32 i = 0; i < vertCount; i++ ) + { + const Point3F &vert = verts[i]; + if ( i + 1 < vertCount ) + endPt = verts[i + 1]; + else + break; + + PrimBuild::begin( GFXLineList, 2 ); + + PrimBuild::vertex3f( vert.x, vert.y, vert.z ); + PrimBuild::vertex3f( endPt.x, endPt.y, endPt.z ); + + PrimBuild::end(); + } +} + +void GuiDecalEditorCtrl::renderScene(const RectI & updateRect) +{ + PROFILE_SCOPE( GuiDecalEditorCtrl_renderScene ); + + GFXTransformSaver saver; + + RectI bounds = getBounds(); + + ColorI hlColor(0,255,0,255); + ColorI regColor(255,0,0,255); + ColorI selColor(0,0,255,255); + ColorI color; + + GFXDrawUtil *drawUtil = GFX->getDrawUtil(); + + GFXStateBlockDesc desc; + desc.setBlend( true ); + desc.setZReadWrite( true, false ); + + // Draw 3D stuff here. + if ( mSELDecal ) + { + mGizmo->renderGizmo(mLastCameraQuery.cameraMatrix); + + mSELEdgeVerts.clear(); + if ( gDecalManager->clipDecal( mSELDecal, &mSELEdgeVerts ) ) + _renderDecalEdge( mSELEdgeVerts, ColorI( 255, 255, 255, 255 ) ); + + const F32 &decalSize = mSELDecal->mSize; + Point3F boxSize( decalSize, decalSize, decalSize ); + + MatrixF worldMat( true ); + mSELDecal->getWorldMatrix( &worldMat, true ); + + drawUtil->drawObjectBox( desc, boxSize, mSELDecal->mPosition, worldMat, ColorI( 255, 255, 255, 255 ) ); + } + + if ( mHLDecal ) + { + mHLEdgeVerts.clear(); + if ( gDecalManager->clipDecal( mHLDecal, &mHLEdgeVerts ) ) + _renderDecalEdge( mHLEdgeVerts, ColorI( 255, 255, 255, 255 ) ); + + const F32 &decalSize = mHLDecal->mSize; + Point3F boxSize( decalSize, decalSize, decalSize ); + + MatrixF worldMat( true ); + mHLDecal->getWorldMatrix( &worldMat, true ); + + drawUtil->drawObjectBox( desc, boxSize, mHLDecal->mPosition, worldMat, ColorI( 255, 255, 255, 255 ) ); + } + + gDecalManager->renderDecalSpheres(); +} + +bool GuiDecalEditorCtrl::getRayInfo( const Gui3DMouseEvent & event, RayInfo *rInfo ) +{ + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 100.0f; + + bool hit; + + hit = gServerContainer.castRayRendered( startPnt, endPnt, STATIC_COLLISION_MASK, rInfo ); + + return hit; +} + +void GuiDecalEditorCtrl::selectDecal( DecalInstance *decalInst ) +{ + // If id is zero or invalid we set the selected decal to null + // which is correct. + mSELDecal = decalInst; + + if ( decalInst ) + setGizmoFocus( decalInst ); +} + +void GuiDecalEditorCtrl::deleteSelectedDecal() +{ + if ( !mSELDecal ) + return; + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + + // Create the UndoAction. + DIDeleteUndoAction *action = new DIDeleteUndoAction("Delete Decal"); + action->deleteDecal( *mSELDecal ); + + action->mEditor = this; + // Submit it. + undoMan->addAction( action ); + + if ( isMethod( "onDeleteInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", mSELDecal->mId); + Con::executef( this, "onDeleteInstance", String(buffer).c_str(), mSELDecal->mDataBlock->lookupName.c_str() ); + } + + gDecalManager->removeDecal( mSELDecal ); + mSELDecal = NULL; +} + +void GuiDecalEditorCtrl::deleteDecalDatablock( String lookupName ) +{ + DecalData * datablock = dynamic_cast ( Sim::findObject(lookupName.c_str()) ); + if( !datablock ) + return; + + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "GuiMeshRoadEditorCtrl::on3DMouseDown() - EUndoManager not found!" ); + return; + } + + // Create the UndoAction. + DBDeleteUndoAction *action = new DBDeleteUndoAction("Delete Decal Datablock"); + action->mEditor = this; + action->mDatablockId = datablock->getId(); + + Vector mDecalQueue; + Vector::iterator iter; + mDecalQueue.clear(); + const Vector &grid = gDecalManager->getDecalDataFile()->getGrid(); + + for ( U32 i = 0; i < grid.size(); i++ ) + { + const DecalSphere *decalSphere = grid[i]; + mDecalQueue.merge( decalSphere->mItems ); + } + + for ( iter = mDecalQueue.begin();iter != mDecalQueue.end();iter++ ) + { + if( !(*iter) ) + continue; + + if( (*iter)->mDataBlock->lookupName.compare( lookupName ) == 0 ) + { + if( (*iter)->mId != -1 ) + { + //make sure to call onDeleteInstance as well + if ( isMethod( "onDeleteInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", (*iter)->mId); + Con::executef( this, "onDeleteInstance", String(buffer).c_str(), (*iter)->mDataBlock->lookupName.c_str() ); + } + + action->deleteDecal( *(*iter) ); + + if( mSELDecal == (*iter) ) + mSELDecal = NULL; + + if( mHLDecal == (*iter) ) + mHLDecal = NULL; + } + gDecalManager->removeDecal( (*iter) ); + } + } + + undoMan->addAction( action ); + + mCurrentDecalData = NULL; +} + +void GuiDecalEditorCtrl::setMode( String mode, bool sourceShortcut = false ) +{ + if( mode.compare("SelectDecalMode") == 0) + mGizmo->getProfile()->mode = NoneMode; + else if( mode.compare("AddDecalMode") == 0) + mGizmo->getProfile()->mode = NoneMode; + else if( mode.compare("MoveDecalMode") == 0) + mGizmo->getProfile()->mode = MoveMode; + else if( mode.compare("RotateDecalMode") == 0) + mGizmo->getProfile()->mode = RotateMode; + else if( mode.compare("ScaleDecalMode") == 0) + mGizmo->getProfile()->mode = ScaleMode; + + mMode = mode; + + if( sourceShortcut ) + Con::executef( this, "paletteSync", mMode ); +} + +ConsoleMethod( GuiDecalEditorCtrl, deleteSelectedDecal, void, 2, 2, "deleteSelectedDecal()" ) +{ + object->deleteSelectedDecal(); +} + +ConsoleMethod( GuiDecalEditorCtrl, deleteDecalDatablock, void, 3, 3, "deleteSelectedDecalDatablock( String datablock )" ) +{ + String lookupName( argv[2] ); + if( lookupName == String::EmptyString ) + return; + + object->deleteDecalDatablock( lookupName ); +} + +ConsoleMethod( GuiDecalEditorCtrl, setMode, void, 3, 3, "setMode( String mode )()" ) +{ + String newMode = ( argv[2] ); + object->setMode( newMode ); +} + +ConsoleMethod( GuiDecalEditorCtrl, getMode, const char*, 2, 2, "getMode()" ) +{ + return object->mMode; +} + +ConsoleMethod( GuiDecalEditorCtrl, getDecalCount, S32, 2, 2, "getDecalCount()" ) +{ + return gDecalManager->mDecalInstanceVec.size(); +} + +ConsoleMethod( GuiDecalEditorCtrl, getDecalTransform, const char*, 3, 3, "getDecalTransform()" ) +{ + DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[dAtoi(argv[2])]; + if( decalInstance == NULL ) + return ""; + + char* returnBuffer = Con::getReturnBuffer(256); + returnBuffer[0] = 0; + + if ( decalInstance ) + { + dSprintf(returnBuffer, 256, "%f %f %f %f %f %f %f", + decalInstance->mPosition.x, decalInstance->mPosition.y, decalInstance->mPosition.z, + decalInstance->mTangent.x, decalInstance->mTangent.y, decalInstance->mTangent.z, + decalInstance->mSize); + } + + return returnBuffer; +} + +ConsoleMethod( GuiDecalEditorCtrl, getDecalLookupName, const char*, 3, 3, "getDecalLookupName( S32 )()" ) +{ + DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[dAtoi(argv[2])]; + if( decalInstance == NULL ) + return "invalid"; + + return decalInstance->mDataBlock->lookupName; +} + +ConsoleMethod( GuiDecalEditorCtrl, selectDecal, void, 3, 3, "selectDecal( S32 )()" ) +{ + DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[dAtoi(argv[2])]; + if( decalInstance == NULL ) + return; + + object->selectDecal( decalInstance ); +} + +ConsoleMethod( GuiDecalEditorCtrl, editDecalDetails, void, 4, 4, "editDecalDetails( S32 )()" ) +{ + DecalInstance *decalInstance = gDecalManager->mDecalInstanceVec[ dAtoi(argv[2]) ]; + if( decalInstance == NULL ) + return; + + Point3F pos; + Point3F tan; + F32 size; + + S32 count = dSscanf( argv[3], "%f %f %f %f %f %f %f", + &pos.x, &pos.y, &pos.z, &tan.x, &tan.y, &tan.z, &size); + + if ( (count != 7) ) + { + Con::printf("Failed to parse decal information \"px py pz tx ty tz s\" from '%s'", argv[3]); + return; + } + + decalInstance->mPosition = pos; + decalInstance->mTangent = tan; + decalInstance->mSize = size; + + if ( decalInstance == object->mSELDecal ) + object->setGizmoFocus( decalInstance ); + + object->forceRedraw( decalInstance ); + + gDecalManager->notifyDecalModified( decalInstance ); +} + +void GuiDecalEditorCtrl::setGizmoFocus( DecalInstance * decalInstance ) +{ + const F32 &size = decalInstance->mSize; + MatrixF worldMat( true ); + decalInstance->getWorldMatrix( &worldMat, true ); + worldMat.setPosition( Point3F( 0, 0, 0 ) ); + mGizmo->set( worldMat, decalInstance->mPosition, Point3F( size, size, size ) ); +} + +//Decal Instance Create Undo Actions +IMPLEMENT_CONOBJECT( DICreateUndoAction ); + +DICreateUndoAction::DICreateUndoAction( const UTF8* actionName ) + : UndoAction( actionName ) +{ +} + +DICreateUndoAction::~DICreateUndoAction() +{ +} + +void DICreateUndoAction::initPersistFields() +{ + Parent::initPersistFields(); +} + +void DICreateUndoAction::addDecal( DecalInstance decal ) +{ + mDecalInstance = decal; + mDatablockId = decal.mDataBlock->getId(); +} + +void DICreateUndoAction::undo() +{ + Vector::iterator iter; + for(iter = gDecalManager->mDecalInstanceVec.begin();iter != gDecalManager->mDecalInstanceVec.end();iter++) + { + if( !(*iter) ) + continue; + + if( (*iter)->mId != mDecalInstance.mId ) + continue; + + if ( mEditor->isMethod( "onDeleteInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", (*iter)->mId); + Con::executef( mEditor, "onDeleteInstance", String(buffer).c_str(), (*iter)->mDataBlock->lookupName.c_str() ); + } + + // Decal manager handles clearing the vector if the decal contains a valid id + if( mEditor->mSELDecal == (*iter) ) + mEditor->mSELDecal = NULL; + + if( mEditor->mHLDecal == (*iter) ) + mEditor->mHLDecal = NULL; + + gDecalManager->removeDecal( (*iter) ); + break; + } +} + +void DICreateUndoAction::redo() +{ + //Reinstate the valid datablock pointer + mDecalInstance.mDataBlock = dynamic_cast( Sim::findObject( mDatablockId ) ); + + DecalInstance * decal = gDecalManager->addDecal( mDecalInstance.mPosition, + mDecalInstance.mNormal, + mDecalInstance.mTangent, + mDecalInstance.mDataBlock, + ( mDecalInstance.mSize / mDecalInstance.mDataBlock->size ), + mDecalInstance.mTextureRectIdx, + mDecalInstance.mFlags ); + + decal->mId = mDecalInstance.mId; + + // Override the rectIdx regardless of random decision in addDecal + decal->mTextureRectIdx = mDecalInstance.mTextureRectIdx; + + // We take care of filling in the vector space that was once there + gDecalManager->mDecalInstanceVec[decal->mId] = decal; + + if ( mEditor->isMethod( "onCreateInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", decal->mId); + Con::executef( mEditor, "onCreateInstance", buffer, decal->mDataBlock->lookupName.c_str()); + } + mEditor->selectDecal( decal ); +} + +//Decal Instance Delete Undo Actions +IMPLEMENT_CONOBJECT( DIDeleteUndoAction ); + +DIDeleteUndoAction::DIDeleteUndoAction( const UTF8 *actionName ) + : UndoAction( actionName ) +{ +} + +DIDeleteUndoAction::~DIDeleteUndoAction() +{ +} + +void DIDeleteUndoAction::initPersistFields() +{ + Parent::initPersistFields(); +} + +void DIDeleteUndoAction::deleteDecal( DecalInstance decal ) +{ + mDecalInstance = decal; + mDatablockId = decal.mDataBlock->getId(); +} + +void DIDeleteUndoAction::undo() +{ + //Reinstate the valid datablock pointer + mDecalInstance.mDataBlock = dynamic_cast( Sim::findObject( mDatablockId ) ); + + DecalInstance * decal = gDecalManager->addDecal( mDecalInstance.mPosition, + mDecalInstance.mNormal, + mDecalInstance.mTangent, + mDecalInstance.mDataBlock, + ( mDecalInstance.mSize / mDecalInstance.mDataBlock->size ), + mDecalInstance.mTextureRectIdx, + mDecalInstance.mFlags ); + + decal->mId = mDecalInstance.mId; + + // Override the rectIdx regardless of random decision in addDecal + decal->mTextureRectIdx = mDecalInstance.mTextureRectIdx; + + // We take care of filling in the vector space that was once there + gDecalManager->mDecalInstanceVec[decal->mId] = decal; + + if ( mEditor->isMethod( "onCreateInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", decal->mId); + Con::executef( mEditor, "onCreateInstance", buffer, decal->mDataBlock->lookupName.c_str()); + } + mEditor->selectDecal( decal ); +} + +void DIDeleteUndoAction::redo() +{ + Vector::iterator iter; + for(iter = gDecalManager->mDecalInstanceVec.begin();iter != gDecalManager->mDecalInstanceVec.end();iter++) + { + if( !(*iter) ) + continue; + + if( (*iter)->mId != mDecalInstance.mId ) + continue; + + if ( mEditor->isMethod( "onDeleteInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", (*iter)->mId); + Con::executef( mEditor, "onDeleteInstance", String(buffer).c_str(), (*iter)->mDataBlock->lookupName.c_str() ); + } + + // Decal manager handles clearing the vector if the decal contains a valid id + if( mEditor->mSELDecal == (*iter) ) + mEditor->mSELDecal = NULL; + + if( mEditor->mHLDecal == (*iter) ) + mEditor->mHLDecal = NULL; + + gDecalManager->removeDecal( (*iter) ); + break; + } +} + +//Decal Datablock Delete Undo Actions +IMPLEMENT_CONOBJECT( DBDeleteUndoAction ); + +DBDeleteUndoAction::DBDeleteUndoAction( const UTF8 *actionName ) + : UndoAction( actionName ) +{ +} + +DBDeleteUndoAction::~DBDeleteUndoAction() +{ +} + +void DBDeleteUndoAction::initPersistFields() +{ + Parent::initPersistFields(); +} + +void DBDeleteUndoAction::deleteDecal( DecalInstance decal ) +{ + mDecalInstanceVec.increment(); + mDecalInstanceVec.last() = decal; +} + +void DBDeleteUndoAction::undo() +{ + DecalData * datablock = dynamic_cast( Sim::findObject( mDatablockId ) ); + if ( mEditor->isMethod( "undoDeleteDecalDatablock" ) ) + Con::executef( mEditor, "undoDeleteDecalDatablock", datablock->lookupName.c_str()); + + // Create and restore the decal instances + for ( S32 i= mDecalInstanceVec.size()-1; i >= 0; i-- ) + { + DecalInstance vecInstance = mDecalInstanceVec[i]; + + //Reinstate the valid datablock pointer + vecInstance.mDataBlock = datablock; + + DecalInstance * decalInstance = gDecalManager->addDecal( vecInstance.mPosition, + vecInstance.mNormal, + vecInstance.mTangent, + vecInstance.mDataBlock, + ( vecInstance.mSize / vecInstance.mDataBlock->size ), + vecInstance.mTextureRectIdx, + vecInstance.mFlags ); + + decalInstance->mId = vecInstance.mId; + + // Override the rectIdx regardless of random decision in addDecal + decalInstance->mTextureRectIdx = vecInstance.mTextureRectIdx; + + // We take care of filling in the vector space that was once there + gDecalManager->mDecalInstanceVec[decalInstance->mId] = decalInstance; + + if ( mEditor->isMethod( "onCreateInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", decalInstance->mId); + Con::executef( mEditor, "onCreateInstance", buffer, decalInstance->mDataBlock->lookupName.c_str()); + } + } + +} + +void DBDeleteUndoAction::redo() +{ + for ( S32 i=0; i < mDecalInstanceVec.size(); i++ ) + { + DecalInstance vecInstance = mDecalInstanceVec[i]; + + Vector::iterator iter; + for(iter = gDecalManager->mDecalInstanceVec.begin();iter != gDecalManager->mDecalInstanceVec.end();iter++) + { + DecalInstance * decalInstance = (*iter); + if( !decalInstance ) + continue; + + if( decalInstance->mId != vecInstance.mId ) + continue; + + if ( mEditor->isMethod( "onDeleteInstance" ) ) + { + char buffer[512]; + dSprintf(buffer, 512, "%i", decalInstance->mId); + Con::executef( mEditor, "onDeleteInstance", String(buffer).c_str(), decalInstance->mDataBlock->lookupName.c_str() ); + } + + // Decal manager handles clearing the vector if the decal contains a valid id + if( mEditor->mSELDecal == decalInstance ) + mEditor->mSELDecal = NULL; + + if( mEditor->mHLDecal == decalInstance ) + mEditor->mHLDecal = NULL; + + gDecalManager->removeDecal( decalInstance ); + break; + } + } + + DecalData * datablock = dynamic_cast( Sim::findObject( mDatablockId ) ); + if ( mEditor->isMethod( "redoDeleteDecalDatablock" ) ) + Con::executef( mEditor, "redoDeleteDecalDatablock", datablock->lookupName.c_str()); +} +#endif \ No newline at end of file diff --git a/gui/worldEditor/guiDecalEditorCtrl.h b/gui/worldEditor/guiDecalEditorCtrl.h new file mode 100644 index 0000000..6f7ebab --- /dev/null +++ b/gui/worldEditor/guiDecalEditorCtrl.h @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUIDECALEDITORCTRL_H_ +#define _GUIDECALEDITORCTRL_H_ + +#ifndef _EDITTSCTRL_H_ +#include "gui/worldEditor/editTSCtrl.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _DECALINSTANCE_H_ +#include "T3D/decal/decalInstance.h" +#endif + +class GameBase; +class Gizmo; +struct RayInfo; +class DecalInstance; +class DecalData; + +class GuiDecalEditorCtrl : public EditTSCtrl +{ + typedef EditTSCtrl Parent; + + public: + + GuiDecalEditorCtrl(); + ~GuiDecalEditorCtrl(); + + DECLARE_CONOBJECT(GuiDecalEditorCtrl); + + // SimObject + bool onAdd(); + static void initPersistFields(); + void onEditorDisable(); + + // GuiControl + virtual bool onWake(); + virtual void onSleep(); + virtual void onRender(Point2I offset, const RectI &updateRect); + + // EditTSCtrl + void get3DCursor( GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_ ); + void on3DMouseDown(const Gui3DMouseEvent & event); + void on3DMouseUp(const Gui3DMouseEvent & event); + void on3DMouseMove(const Gui3DMouseEvent & event); + void on3DMouseDragged(const Gui3DMouseEvent & event); + void on3DMouseEnter(const Gui3DMouseEvent & event); + void on3DMouseLeave(const Gui3DMouseEvent & event); + void on3DRightMouseDown(const Gui3DMouseEvent & event); + void on3DRightMouseUp(const Gui3DMouseEvent & event); + void updateGuiInfo(); + void renderScene(const RectI & updateRect); + + /// Find clicked point on "static collision" objects. + bool getRayInfo( const Gui3DMouseEvent &event, RayInfo *rInfo ); + + void selectDecal( DecalInstance *inst ); + void deleteSelectedDecal(); + void deleteDecalDatablock( String lookupName ); + void setMode( String mode, bool sourceShortcut ); + + void forceRedraw( DecalInstance * decalInstance ); + void setGizmoFocus( DecalInstance * decalInstance ); + public: + String mMode; + DecalInstance *mSELDecal; + DecalInstance *mHLDecal; + + protected: + + DecalData *mCurrentDecalData; + + Vector mSELEdgeVerts; + Vector mHLEdgeVerts; + + void _renderDecalEdge( const Vector &verts, const ColorI &color ); +}; + +//Decal Instance Create Undo Actions +class DICreateUndoAction : public UndoAction +{ + typedef UndoAction Parent; + +public: + GuiDecalEditorCtrl *mEditor; + +protected: + + /// The captured object state. + DecalInstance mDecalInstance; + S32 mDatablockId; + +public: + + DECLARE_CONOBJECT( DICreateUndoAction ); + static void initPersistFields(); + + DICreateUndoAction( const UTF8* actionName = "Create Decal " ); + virtual ~DICreateUndoAction(); + + void addDecal( DecalInstance decal ); + + // UndoAction + virtual void undo(); + virtual void redo(); +}; + +//Decal Instance Delete Undo Actions +class DIDeleteUndoAction : public UndoAction +{ + typedef UndoAction Parent; + +public: + GuiDecalEditorCtrl *mEditor; + +protected: + + /// The captured object state. + DecalInstance mDecalInstance; + S32 mDatablockId; + +public: + + DECLARE_CONOBJECT( DIDeleteUndoAction ); + static void initPersistFields(); + + DIDeleteUndoAction( const UTF8* actionName = "Delete Decal" ); + virtual ~DIDeleteUndoAction(); + + /// + void deleteDecal( DecalInstance decal ); + + // UndoAction + virtual void undo(); + virtual void redo(); +}; + +//Decal Datablock Delete Undo Actions +class DBDeleteUndoAction : public UndoAction +{ + typedef UndoAction Parent; + +public: + GuiDecalEditorCtrl *mEditor; + S32 mDatablockId; + +protected: + + // The captured decalInstance states + Vector mDecalInstanceVec; + +public: + + DECLARE_CONOBJECT( DBDeleteUndoAction ); + static void initPersistFields(); + + DBDeleteUndoAction( const UTF8* actionName = "Delete Decal Datablock" ); + virtual ~DBDeleteUndoAction(); + + void deleteDecal( DecalInstance decal ); + + // UndoAction + virtual void undo(); + virtual void redo(); +}; + +#endif // _GUIDECALEDITORCTRL_H_ + + + diff --git a/gui/worldEditor/guiTerrPreviewCtrl.cpp b/gui/worldEditor/guiTerrPreviewCtrl.cpp new file mode 100644 index 0000000..09c7848 --- /dev/null +++ b/gui/worldEditor/guiTerrPreviewCtrl.cpp @@ -0,0 +1,343 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "terrain/terrData.h" +#include "gui/worldEditor/guiTerrPreviewCtrl.h" +#include "gfx/primBuilder.h" +#include "T3D/gameFunctions.h" + +IMPLEMENT_CONOBJECT(GuiTerrPreviewCtrl); + +GuiTerrPreviewCtrl::GuiTerrPreviewCtrl(void) : mTerrainEditor(NULL), mTerrainSize(2048.0f) +{ + mRoot.set( 0, 0 ); + mOrigin.set( 0, 0 ); + mWorldScreenCenter.set( mTerrainSize*0.5f, mTerrainSize*0.5f ); + mControlsStateBlock = NULL; + mTerrainBitmapStateBlock = NULL; +} + +bool GuiTerrPreviewCtrl::onAdd() +{ + if(Parent::onAdd() == false) + { + return false; + } + + SimObject* inTerrEditor = Sim::findObject("ETerrainEditor"); + if(!inTerrEditor) + { + Con::errorf(ConsoleLogEntry::General, "TerrainEditor::onAdd: failed to load Terrain Editor"); + return false; + } + + mTerrainEditor = dynamic_cast(inTerrEditor); + + GFXStateBlockDesc desc; + + desc.setBlend(false, GFXBlendOne, GFXBlendZero); + + desc.samplersDefined = true; + desc.samplers[0].addressModeU = GFXAddressWrap; + desc.samplers[0].addressModeV = GFXAddressWrap; + desc.samplers[0].textureColorOp = GFXTOPSelectARG1; + desc.samplers[0].colorArg1 = GFXTATexture; + desc.setCullMode(GFXCullNone); + desc.setZReadWrite(false); + + mTerrainBitmapStateBlock = GFX->createStateBlock(desc); + + desc.samplers[0].textureColorOp = GFXTOPDisable; + + mControlsStateBlock = GFX->createStateBlock(desc); + + return true; +} + +void GuiTerrPreviewCtrl::initPersistFields() +{ + Parent::initPersistFields(); +} + + +ConsoleMethod( GuiTerrPreviewCtrl, reset, void, 2, 2, "Reset the view of the terrain.") +{ + object->reset(); +} + +ConsoleMethod( GuiTerrPreviewCtrl, setRoot, void, 2, 2, "Add the origin to the root and reset the origin.") +{ + object->setRoot(); +} + +ConsoleMethod( GuiTerrPreviewCtrl, getRoot, const char *, 2, 2, "Return a Point2F representing the position of the root.") +{ + Point2F p = object->getRoot(); + + static char rootbuf[32]; + dSprintf(rootbuf,sizeof(rootbuf),"%g %g", p.x, -p.y); + return rootbuf; +} + +ConsoleMethod( GuiTerrPreviewCtrl, setOrigin, void, 4, 4, "(float x, float y)" + "Set the origin of the view.") +{ + object->setOrigin( Point2F( dAtof(argv[2]), -dAtof(argv[3]) ) ); +} + +ConsoleMethod( GuiTerrPreviewCtrl, getOrigin, const char*, 2, 2, "Return a Point2F containing the position of the origin.") +{ + Point2F p = object->getOrigin(); + + static char originbuf[32]; + dSprintf(originbuf,sizeof(originbuf),"%g %g", p.x, -p.y); + return originbuf; +} + +ConsoleMethod( GuiTerrPreviewCtrl, getValue, const char*, 2, 2, "Returns a 4-tuple containing: root_x root_y origin_x origin_y") +{ + Point2F r = object->getRoot(); + Point2F o = object->getOrigin(); + + static char valuebuf[64]; + dSprintf(valuebuf,sizeof(valuebuf),"%g %g %g %g", r.x, -r.y, o.x, -o.y); + return valuebuf; +} + +ConsoleMethod( GuiTerrPreviewCtrl, setValue, void, 3, 3, "Accepts a 4-tuple in the same form as getValue returns.\n\n" + "@see GuiTerrPreviewCtrl::getValue()") +{ + Point2F r,o; + dSscanf(argv[2],"%g %g %g %g", &r.x, &r.y, &o.x, &o.y); + r.y = -r.y; + o.y = -o.y; + object->reset(); + object->setRoot(r); + object->setOrigin(o); +} + +bool GuiTerrPreviewCtrl::onWake() +{ + if (! Parent::onWake()) + return false; + + return true; +} + +void GuiTerrPreviewCtrl::onSleep() +{ + Parent::onSleep(); +} + +void GuiTerrPreviewCtrl::setBitmap(const GFXTexHandle &handle) +{ + mTextureHandle = handle; +} + + +void GuiTerrPreviewCtrl::reset() +{ + mRoot.set(0,0); + mOrigin.set(0,0); +} + +void GuiTerrPreviewCtrl::setRoot() +{ + mRoot += mOrigin; + mOrigin.set(0,0); +} + +void GuiTerrPreviewCtrl::setRoot(const Point2F &p) +{ + mRoot = p; +} + +void GuiTerrPreviewCtrl::setOrigin(const Point2F &p) +{ + mOrigin = p; +} + + +Point2F& GuiTerrPreviewCtrl::wrap(const Point2F &p) +{ + static Point2F result; + result = p; + + while (result.x < 0.0f) + result.x += mTerrainSize; + while (result.x > mTerrainSize) + result.x -= mTerrainSize; + while (result.y < 0.0f) + result.y += mTerrainSize; + while (result.y > mTerrainSize) + result.y -= mTerrainSize; + + return result; +} + +Point2F& GuiTerrPreviewCtrl::worldToTexture(const Point2F &p) +{ + static Point2F result; + result = wrap( p + mRoot ) / mTerrainSize; + return result; +} + + +Point2F& GuiTerrPreviewCtrl::worldToCtrl(const Point2F &p) +{ + static Point2F result; + result = wrap( p - mCamera - mWorldScreenCenter ); + result *= getWidth() / mTerrainSize; + return result; +} + + +void GuiTerrPreviewCtrl::onPreRender() +{ + setUpdate(); +} + +void GuiTerrPreviewCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + CameraQuery query; + GameProcessCameraQuery(&query); + Point3F cameraRot; + TerrainBlock *terrBlock = NULL; + + MatrixF matrix = query.cameraMatrix; + matrix.getColumn(3,&cameraRot); // get Camera translation + mCamera.set(cameraRot.x, -cameraRot.y); + matrix.getRow(1,&cameraRot); // get camera rotation + + if (mTerrainEditor != NULL) + terrBlock = mTerrainEditor->getActiveTerrain(); + + if (!terrBlock) + return; + + for(U32 i = 0; i < GFX->getNumSamplers(); i++) + GFX->setTexture(i, NULL); + + GFX->disableShaders(); + + Point2F terrPos(terrBlock->getPosition().x, terrBlock->getPosition().y); + + mTerrainSize = terrBlock->getWorldBlockSize(); + + + //----------------------------------------- RENDER the Terrain Bitmap + if (mTextureHandle) + { + + GFXTextureObject *texture = (GFXTextureObject*)mTextureHandle; + if (texture) + { + //GFX->setLightingEnable(false); + GFX->setStateBlock(mTerrainBitmapStateBlock); + GFX->setTexture(0, texture); + + Point2F screenP1(offset.x - 0.5f, offset.y + 0.5f); + Point2F screenP2(offset.x + getWidth() - 0.5f, offset.y + getWidth() + 0.5f); + Point2F textureP1( worldToTexture( mCamera - terrPos ) - Point2F(0.5f, 0.5f)); + Point2F textureP2(textureP1 + Point2F(1.0f, 1.0f)); + + // the texture if flipped horz to reflect how the terrain is really drawn + PrimBuild::color3f(1.0f, 1.0f, 1.0f); + PrimBuild::begin(GFXTriangleFan, 4); + PrimBuild::texCoord2f(textureP1.x, textureP2.y); + PrimBuild::vertex2f(screenP1.x, screenP2.y); // left bottom + + + PrimBuild::texCoord2f(textureP2.x, textureP2.y); + PrimBuild::vertex2f(screenP2.x, screenP2.y); // right bottom + PrimBuild::texCoord2f(textureP2.x, textureP1.y); + PrimBuild::vertex2f(screenP2.x, screenP1.y); // right top + + PrimBuild::texCoord2f(textureP1.x, textureP1.y); + PrimBuild::vertex2f(screenP1.x, screenP1.y); // left top + PrimBuild::end(); + } + } + //Draw blank texture + else + { + RectI rect(offset.x, offset.y, getWidth(), getHeight()); + GFX->getDrawUtil()->drawRect(rect, ColorI(0,0,0)); + } + + GFX->setStateBlock(mControlsStateBlock); + + //----------------------------------------- RENDER the '+' at the center of the Block + + PrimBuild::color4f(1.0f, 1.0f, 1.0f, 1.0f); + Point2F center( worldToCtrl(terrPos + Point2F(mTerrainSize * 0.5f, mTerrainSize * 0.5f)) ); + S32 y; + for (y=-1; y<=1; y++) + { + F32 yoffset = offset.y + y*256.0f; + for (S32 x=-1; x<=1; x++) + { + F32 xoffset = offset.x + x*256.0f; + PrimBuild::begin(GFXLineList, 4); + PrimBuild::vertex2f(xoffset + center.x, yoffset + center.y-5); + PrimBuild::vertex2f(xoffset + center.x, yoffset + center.y+6); + PrimBuild::vertex2f(xoffset + center.x-5, yoffset + center.y); + PrimBuild::vertex2f(xoffset + center.x+6, yoffset + center.y); + PrimBuild::end(); + } + } + + //----------------------------------------- RENDER the Block Corners + Point2F cornerf( worldToCtrl(terrPos) + Point2F(0.125f, 0.125f)); + Point2I corner=Point2I((S32)cornerf.x,(S32)cornerf.y); + for (y=-1; y<=1; y++) + { + S32 yoffset = offset.y + y*256; + for (S32 x=-1; x<=1; x++) + { + S32 xoffset = offset.x + x*256; + PrimBuild::begin(GFXLineStrip, 3); + PrimBuild::color4f(1.0f, 1.0f, 1.0f, 0.3f); + PrimBuild::vertex2i(xoffset + corner.x, yoffset + corner.y-128); + PrimBuild::color4f(1.0f, 1.0f, 1.0f, 0.7f); + PrimBuild::vertex2i(xoffset + corner.x, yoffset + corner.y); + PrimBuild::color4f(1.0f, 1.0f, 1.0f, 0.3f); + PrimBuild::vertex2i(xoffset + corner.x+128, yoffset + corner.y); + PrimBuild::end(); + PrimBuild::begin(GFXLineStrip, 3); + PrimBuild::color4f(1.0f, 1.0f, 1.0f, 0.3f); + PrimBuild::vertex2i(xoffset + corner.x, yoffset + corner.y+128); + PrimBuild::color4f(1.0f, 1.0f, 1.0f, 0.7f); + PrimBuild::vertex2i(xoffset + corner.x, yoffset + corner.y); + PrimBuild::color4f(1.0f, 1.0f, 1.0f, 0.3f); + PrimBuild::vertex2i(xoffset + corner.x-128, yoffset + corner.y); + PrimBuild::end(); + } + } + + //----------------------------------------- RENDER the Viewcone + Point2F pointA(cameraRot.x * -40, cameraRot.y * -40); + Point2F pointB(-pointA.y, pointA.x); + + F32 tann = mTan(0.5f); + Point2F point1( pointA + pointB * tann ); + Point2F point2( pointA - pointB * tann ); + + center.set((F32)(offset.x + getWidth() / 2), (F32)(offset.y + getHeight() / 2 )); + PrimBuild::begin(GFXLineStrip, 3); + PrimBuild::color4f(1.0f, 0.0f, 0.0f, 0.7f); + PrimBuild::vertex2i((S32)(center.x + point1.x), (S32)(center.y + point1.y)); + PrimBuild::color4f(1.0f, 0.0f, 0.0f, 1.0f); + PrimBuild::vertex2i((S32)center.x,(S32)center.y); + PrimBuild::color4f(1.0f, 0.0f, 0.0f, 0.7f); + PrimBuild::vertex2i((S32)(center.x + point2.x), (S32)(center.y + point2.y)); + PrimBuild::end(); + + + renderChildControls(offset, updateRect); +} + diff --git a/gui/worldEditor/guiTerrPreviewCtrl.h b/gui/worldEditor/guiTerrPreviewCtrl.h new file mode 100644 index 0000000..6c40184 --- /dev/null +++ b/gui/worldEditor/guiTerrPreviewCtrl.h @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GUITERRPREVIEWCTRL_H_ +#define _GUITERRPREVIEWCTRL_H_ + +#ifndef _GUICONTROL_H_ +#include "gui/core/guiControl.h" +#endif +#ifndef _GUITSCONTROL_H_ +#include "gui/3d/guiTSControl.h" +#endif +#ifndef _GFX_GFXDRAWER_H_ +#include "gfx/gfxDrawUtil.h" +#endif +#ifndef _TERRAINEDITOR_H_ +#include "gui/worldEditor/terrainEditor.h" +#endif + + +class GuiTerrPreviewCtrl : public GuiControl +{ +private: + typedef GuiControl Parent; + GFXTexHandle mTextureHandle; + GFXStateBlockRef mTerrainBitmapStateBlock; + GFXStateBlockRef mControlsStateBlock; + Point2F mRoot; + Point2F mOrigin; + Point2F mWorldScreenCenter; + Point2F mCamera; + F32 mTerrainSize; + + TerrainEditor* mTerrainEditor; + + Point2F& wrap(const Point2F &p); + Point2F& worldToTexture(const Point2F &p); + Point2F& worldToCtrl(const Point2F &p); + + +public: + //creation methods + DECLARE_CONOBJECT(GuiTerrPreviewCtrl); + DECLARE_CATEGORY( "Gui Editor" ); + GuiTerrPreviewCtrl(); + static void initPersistFields(); + + //Parental methods + bool onWake(); + void onSleep(); + bool onAdd(); + + void setBitmap(const GFXTexHandle&); + + void reset(); + void setRoot(); + void setRoot(const Point2F &root); + void setOrigin(const Point2F &origin); + const Point2F& getRoot() { return mRoot; } + const Point2F& getOrigin() { return mOrigin; } + + //void setValue(const Point2F *center, const Point2F *camera); + //const char *getScriptValue(); + void onPreRender(); + void onRender(Point2I offset, const RectI &updateRect); +}; + + +#endif diff --git a/gui/worldEditor/missionAreaEditor.cpp b/gui/worldEditor/missionAreaEditor.cpp new file mode 100644 index 0000000..646113c --- /dev/null +++ b/gui/worldEditor/missionAreaEditor.cpp @@ -0,0 +1,1086 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gui/worldEditor/missionAreaEditor.h" +#include "terrain/terrData.h" +#include "sceneGraph/sceneObject.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "gui/3d/guiTSControl.h" +#include "T3D/gameConnection.h" +#include "T3D/shapeBase.h" +#include "T3D/gameFunctions.h" +#include "gfx/primBuilder.h" +#include "gfx/bitmap/gBitmap.h" +#include "gfx/gfxDrawUtil.h" + +IMPLEMENT_CONOBJECT(MissionAreaEditor); + +// unnamed namespace for static data +namespace { + static const Point3F BoxNormals[] = + { + Point3F( 1, 0, 0), + Point3F(-1, 0, 0), + Point3F( 0, 1, 0), + Point3F( 0,-1, 0), + Point3F( 0, 0, 1), + Point3F( 0, 0,-1) + }; + + static Point3F BoxPnts[] = { + Point3F(0,0,0), + Point3F(0,0,1), + Point3F(0,1,0), + Point3F(0,1,1), + Point3F(1,0,0), + Point3F(1,0,1), + Point3F(1,1,0), + Point3F(1,1,1) + }; + + F32 round_local(F32 val) + { + if(val >= 0.f) + { + F32 floor = mFloor(val); + if((val - floor) >= 0.5f) + return(floor + 1.f); + return(floor); + } + else + { + F32 ceil = mCeil(val); + if((val - ceil) <= -0.5f) + return(ceil - 1.f); + return(ceil); + } + } + + S32 clamp(S32 val, S32 resolution) + { + return(S32(round_local(F32(val) / F32(resolution))) * resolution); + } +} + + +//------------------------------------------------------------------------------ + +MissionAreaEditor::MissionAreaEditor() +{ + // + mMissionArea = 0; + mTerrainBlock = 0; + + // + mCurrentCursor = 0; + mLastHitMode = 0; + + // field data + mSquareBitmap = true; + mEnableEditing = true; + mRenderCamera = true; + + mHandleFrameColor.set(255,255,255); + mHandleFillColor.set(0,0,0); + mDefaultObjectColor.set(0,255,0,100); + mWaterObjectColor.set(0,0,255,100); + mMissionBoundsColor.set(255,0,0); + mCameraColor.set(255,0,0); + + mEnableMirroring = false; + mMirrorIndex = 0; + mMirrorLineColor.set(255,0,255,128); + mMirrorArrowColor.set(255,0,255,128); + + mBlendStateBlock = NULL; + mSolidStateBlock = NULL; +} + +//------------------------------------------------------------------------------ + +const RectI & MissionAreaEditor::getArea() +{ + AssertFatal(mMissionArea, "MissionAreaEditor::getArea: no MissionArea obj!"); + if(!bool(mMissionArea)) + return(MissionArea::smMissionArea); + + return(mMissionArea->getArea()); +} + +bool MissionAreaEditor::clampArea(RectI & area) +{ + if(!bool(mTerrainBlock)) + return(false); +// S32 res = mTerrainBlock->getSquareSize(); +// area.point.x = clamp(area.point.x, res); +// area.point.y = clamp(area.point.y, res); +// area.extent.x = clamp(area.extent.x, res << 1); +// area.extent.y = clamp(area.extent.y, res << 1); + return(true); +} + +void MissionAreaEditor::setArea(const RectI & area) +{ + AssertFatal(mMissionArea, "MissionAreaEditor::setArea: no MissionArea obj!"); + if(bool(mMissionArea)) + { + RectI clamped = area; + if(clampArea(clamped)) + { + mMissionArea->setArea(clamped); + onUpdate(); + } + } +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::getCursor(GuiCursor *&cursor, bool &visible, const GuiEvent &) +{ + cursor = mCurrentCursor; + visible = true; +} + +void MissionAreaEditor::setCursor(U32 cursor) +{ + AssertFatal(cursor < NumCursors, "MissionAreaEditor::setCursor: invalid cursor"); + mCurrentCursor = mCursors[cursor]; +} + +//------------------------------------------------------------------------------ + +bool MissionAreaEditor::grabCursors() +{ + struct { + U32 index; + const char * name; + } infos[] = { + {DefaultCursor, "DefaultCursor" }, + {HandCursor, "EditorHandCursor" }, + {GrabCursor, "EditorMoveCursor" }, + {VertResizeCursor, "EditorUpDownCursor" }, + {HorizResizeCursor, "EditorLeftRightCursor" }, + {DiagRightResizeCursor, "EditorDiagRightCursor" }, + {DiagLeftResizeCursor, "EditorDiagLeftCursor" } + }; + + for(U32 i = 0; i < (sizeof(infos) / sizeof(infos[0])); i++) + { + SimObject * obj = Sim::findObject(infos[i].name); + if(!obj) + { + Con::errorf(ConsoleLogEntry::Script, "MissionAreaEditor::grabCursors: failed to find cursor '%s'.", infos[i].name); + return(false); + } + + GuiCursor *cursor = dynamic_cast(obj); + if(!cursor) + { + Con::errorf(ConsoleLogEntry::Script, "MissionAreaEditor::grabCursors: object is not a cursor '%s'.", infos[i].name); + return(false); + } + mCursors[infos[i].index] = cursor; + } + + mCurrentCursor = mCursors[DefaultCursor]; + return(true); +} + +//------------------------------------------------------------------------------ + +TerrainBlock * MissionAreaEditor::getTerrainObj() +{ + SimSet * scopeAlwaysSet = Sim::getGhostAlwaysSet(); + for(SimSet::iterator itr = scopeAlwaysSet->begin(); itr != scopeAlwaysSet->end(); itr++) + { + TerrainBlock * terrain = dynamic_cast(*itr); + if(terrain) + return(terrain); + } + return(0); +} + +//------------------------------------------------------------------------------ + +GBitmap * MissionAreaEditor::createTerrainBitmap() +{ + // TODO: What is this for? + /* + GBitmap * bitmap = new GBitmap(TerrainBlock::BlockSize, TerrainBlock::BlockSize, false, GFXFormatR8G8B8 ); + if(!bitmap) + return(0); + + U8 * pBits = bitmap->getAddress(0,0); + + // get the min/max + GridSquare * gSquare = mTerrainBlock->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + + F32 min = fixedToFloat(gSquare->minHeight); + F32 max = fixedToFloat(gSquare->maxHeight); + F32 diff = max - min; + + // This method allocates it's bitmap above, and does all assignment + // in the following loop. It is not subject to 24-bit -> 32-bit conversion + // problems, because the texture handle creation is where the conversion would + // occur, if it occurs. Since the data in the texture is never read back, and + // the bitmap is deleted after texture-upload, this is not a problem. + for(U32 y = 0; y < TerrainBlock::BlockSize; y++) + for(U32 x = 0; x < TerrainBlock::BlockSize; x++) + { + F32 height = fixedToFloat(mTerrainBlock->getHeight(x, y)); + + U8 col = U8((height - min) / diff * 255.f); + *pBits++ = col; + *pBits++ = col; + *pBits++ = col; + } + + return(bitmap); + */ + + + return NULL; +} + +//------------------------------------------------------------------------------ + +bool MissionAreaEditor::onAdd() +{ + if(!Parent::onAdd()) + return(false); + if(!grabCursors()) + return(false); + + GFXStateBlockDesc desc; + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + mBlendStateBlock = GFX->createStateBlock( desc ); + + desc.setBlend(false, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + mSolidStateBlock = GFX->createStateBlock( desc ); + + return(true); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::updateTerrainBitmap() +{ + GBitmap * bitmap = createTerrainBitmap(); + if( bitmap ) + setBitmapHandle( GFXTexHandle( bitmap, &GFXDefaultGUIProfile, true, String("Terrain Bitmap Update") ) ); +} + +bool MissionAreaEditor::onWake() +{ + if(!Parent::onWake()) + return(false); + + mMissionArea = const_cast(MissionArea::getServerObject()); + if(!bool(mMissionArea)) + Con::warnf(ConsoleLogEntry::General, "MissionAreaEditor::onWake: no MissionArea object."); + + mTerrainBlock = getTerrainObj(); + if(!bool(mTerrainBlock)) + Con::warnf(ConsoleLogEntry::General, "MissionAreaEditor::onWake: no TerrainBlock object."); + + if ( !bool(mMissionArea) || !bool(mTerrainBlock) ) + return true; + + updateTerrainBitmap(); + + // make sure mission area is clamped + setArea(getArea()); + + onUpdate(); + setActive(true); + + return(true); +} + +void MissionAreaEditor::onSleep() +{ + mTextureObject = NULL; + mMissionArea = 0; + mTerrainBlock = 0; + + Parent::onSleep(); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::onUpdate() +{ + if(!bool(mMissionArea)) + return; + + char buf[48]; + + const RectI & area = mMissionArea->getArea(); + dSprintf(buf, sizeof(buf), "%d %d %d %d", area.point.x, area.point.y, area.extent.x, area.extent.y); + Con::executef(this, "onUpdate", buf); +} + +void MissionAreaEditor::parentResized(const Point2I & oldParentExtent, const Point2I & newParentExtent) +{ + static Point2I offset = (oldParentExtent - getPosition()) - getExtent(); + resize(getPosition(), newParentExtent - getPosition() - offset); +} + + +//------------------------------------------------------------------------------ + +Point2F MissionAreaEditor::worldToScreen(const Point2F & pos) +{ + return(Point2F(mCenterPos.x + (pos.x * mScale.x), mCenterPos.y + (pos.y * mScale.y))); +} + +Point2I MissionAreaEditor::worldToScreen(const Point2I &pos) +{ + return(Point2I(S32(mCenterPos.x + (pos.x * mScale.x)), S32(mCenterPos.y + (pos.y * mScale.y)))); +} + +Point2F MissionAreaEditor::screenToWorld(const Point2F & pos) +{ + return(Point2F((pos.x - mCenterPos.x) / mScale.x, (pos.y - mCenterPos.y) / mScale.y)); +} + +Point2I MissionAreaEditor::screenToWorld(const Point2I &pos) +{ + return(Point2I(S32((pos.x - mCenterPos.x) / mScale.x), S32((pos.y - mCenterPos.y) / mScale.y))); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::getScreenMissionArea(RectI & rect) +{ + RectI area = mMissionArea->getArea(); + Point2F pos = worldToScreen(Point2F(F32(area.point.x), F32(area.point.y))); + Point2F end = worldToScreen(Point2F(F32(area.point.x + area.extent.x), F32(area.point.y + area.extent.y))); + + // + rect.point.x = S32(round_local(pos.x)); + rect.point.y = S32(round_local(pos.y)); + rect.extent.x = S32(round_local(end.x - pos.x)); + rect.extent.y = S32(round_local(end.y - pos.y)); +} + +void MissionAreaEditor::getScreenMissionArea(RectF & rect) +{ + RectI area = mMissionArea->getArea(); + Point2F pos = worldToScreen(Point2F(F32(area.point.x), F32(area.point.y))); + Point2F end = worldToScreen(Point2F(F32(area.point.x + area.extent.x), F32(area.point.y + area.extent.y))); + + // + rect.point.x = pos.x; + rect.point.y = pos.y; + rect.extent.x = end.x - pos.x; + rect.extent.y = end.y - pos.y; +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::setupScreenTransform(const Point2I & offset) +{ + const MatrixF & terrMat = mTerrainBlock->getTransform(); + Point3F terrPos; + terrMat.getColumn(3, &terrPos); + terrPos.z = 0; + + F32 terrDim = mTerrainBlock->getWorldBlockSize(); + + const Point2I& extenti = getExtent( ); + Point2F extent( static_cast( extenti.x ), static_cast( extenti.y ) ); + + if(mSquareBitmap) + extent.x > extent.y ? extent.x = extent.y : extent.y = extent.x; + + // + mScale.set(extent.x / terrDim, extent.y / terrDim, 0); + + Point3F terrOffset = -terrPos; + terrOffset.convolve(mScale); + + // + mCenterPos.set(terrOffset.x + F32(offset.x), terrOffset.y + F32(offset.y)); +} + +//------------------------------------------------------------------------------ + +static void findObjectsCallback(SceneObject* obj, void * val) +{ + Vector * list = (Vector*)val; + list->push_back(obj); +} + +void MissionAreaEditor::onRender(Point2I offset, const RectI & updateRect) +{ + + RectI rect(offset, getExtent()); + + setUpdate(); + + GFX->setStateBlock(mSolidStateBlock); + + // draw an x + if(!bool(mMissionArea) || !bool(mTerrainBlock)) + { + PrimBuild::color3i( 0, 0, 0 ); + PrimBuild::begin( GFXLineList, 4 ); + + PrimBuild::vertex2f( rect.point.x + GFX->getFillConventionOffset(), updateRect.point.y + GFX->getFillConventionOffset() ); + PrimBuild::vertex2f( rect.point.x + updateRect.extent.x + GFX->getFillConventionOffset(), updateRect.point.y + updateRect.extent.y + GFX->getFillConventionOffset() ); + PrimBuild::vertex2f( rect.point.x + GFX->getFillConventionOffset(), updateRect.point.y + updateRect.extent.y + GFX->getFillConventionOffset() ); + PrimBuild::vertex2f( rect.point.x + updateRect.extent.x + GFX->getFillConventionOffset(), updateRect.point.y + GFX->getFillConventionOffset() ); + + PrimBuild::end(); + + return; + } + + // + setupScreenTransform(offset); + + // draw the terrain + if(mSquareBitmap) + rect.extent.x > rect.extent.y ? rect.extent.x = rect.extent.y : rect.extent.y = rect.extent.x; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + drawer->clearBitmapModulation(); + drawer->drawBitmapStretch(mTextureObject, rect); + + // draw all the objects + Vector objects; + U32 mask = PlayerObjectType | VehicleObjectType | StaticShapeObjectType | WaterObjectType | TriggerObjectType; + gServerContainer.findObjects(mask, findObjectsCallback, &objects); + + RectF area; + getScreenMissionArea(area); + + // render the mission area box + PrimBuild::color( mMissionBoundsColor ); + PrimBuild::begin( GFXLineStrip, 5 ); + PrimBuild::vertex2f(area.point.x + GFX->getFillConventionOffset(), area.point.y + GFX->getFillConventionOffset()); + PrimBuild::vertex2f(area.point.x + area.extent.x + GFX->getFillConventionOffset(), area.point.y + GFX->getFillConventionOffset()); + PrimBuild::vertex2f(area.point.x + area.extent.x + GFX->getFillConventionOffset(), area.point.y + area.extent.y + GFX->getFillConventionOffset()); + PrimBuild::vertex2f(area.point.x + GFX->getFillConventionOffset(), area.point.y + area.extent.y + GFX->getFillConventionOffset()); + PrimBuild::vertex2f(area.point.x + GFX->getFillConventionOffset(), area.point.y + GFX->getFillConventionOffset()); + PrimBuild::end(); + + // render the handles + RectI iArea; + getScreenMissionArea(iArea); + if(mEnableEditing && !mEnableMirroring) + drawNuts(iArea); + + // render the camera + if(mRenderCamera) + { + CameraQuery camera; + GameProcessCameraQuery(&camera); + + // farplane too far, 90' looks wrong... + camera.fov = mDegToRad(60.f); + camera.farPlane = 500.f; + + // + F32 rot = camera.fov / 2; + + // + VectorF ray; + VectorF projRayA, projRayB; + + ray.set(camera.farPlane * -mSin(rot), camera.farPlane * mCos(rot), 0); + camera.cameraMatrix.mulV(ray, &projRayA); + + ray.set(camera.farPlane * -mSin(-rot), camera.farPlane * mCos(-rot), 0); + camera.cameraMatrix.mulV(ray, &projRayB); + + Point3F camPos; + camera.cameraMatrix.getColumn(3, &camPos); + + Point2F s = worldToScreen(Point2F(camPos.x, camPos.y)); + Point2F e1 = worldToScreen(Point2F(camPos.x + projRayA.x, camPos.y + projRayA.y)); + Point2F e2 = worldToScreen(Point2F(camPos.x + projRayB.x, camPos.y + projRayB.y)); + + PrimBuild::color( mCameraColor ); + PrimBuild::begin( GFXLineList, 4 ); + PrimBuild::vertex2f( s.x + GFX->getFillConventionOffset(), s.y + GFX->getFillConventionOffset() ); + PrimBuild::vertex2f( e1.x + GFX->getFillConventionOffset(), e1.y + GFX->getFillConventionOffset() ); + PrimBuild::vertex2f( s.x + GFX->getFillConventionOffset(), s.y + GFX->getFillConventionOffset() ); + PrimBuild::vertex2f( e2.x + GFX->getFillConventionOffset(), e2.y + GFX->getFillConventionOffset() ); + PrimBuild::end(); + } + + // draw the mirroring info + if(mEnableMirroring) + { + // mirror index is cw octant of source + static Point2F octPoints[] = + { + Point2F(0.5, 0.0), + Point2F(1.0, 0.0), + Point2F(1.0, 0.5), + Point2F(1.0, 1.0), + Point2F(0.5, 1.0), + Point2F(0.0, 1.0), + Point2F(0.0, 0.5), + Point2F(0.0, 0.0) + }; + + // render the line + PrimBuild::color( mMirrorLineColor ); + PrimBuild::begin( GFXLineList, 2 ); + PrimBuild::vertex2f( + ( rect.point.x + octPoints[(mMirrorIndex+6)%8].x * rect.extent.x ) + GFX->getFillConventionOffset(), + ( rect.point.y + octPoints[(mMirrorIndex+6)%8].y * rect.extent.y ) + GFX->getFillConventionOffset() ); + PrimBuild::vertex2f( + ( rect.point.x + octPoints[(mMirrorIndex+2)%8].x * rect.extent.x ) + GFX->getFillConventionOffset(), + ( rect.point.y + octPoints[(mMirrorIndex+2)%8].y * rect.extent.y ) + GFX->getFillConventionOffset() ); + PrimBuild::end(); + + // render the arrow + static Point2F arrow[8] = // points up + { + Point2F(-0.375, 0), + Point2F(0, -0.375), + Point2F(0.375, 0), + Point2F(0.125, 0), + Point2F(0.125, 0.375), + Point2F(-0.125, 0.375), + Point2F(-0.125, 0), + Point2F(-0.375, 0) + }; + + static U32 arrow_tri[15] = // triangle verts + { + 0, 1, 6, + 6, 1, 3, + 3, 1, 2, + 6, 3, 5, + 3, 4, 5 + }; + + // rotate cw + F32 angle = -(M_PI * ((mMirrorIndex+6) % 8) / 4); + + F32 sin = mCos(angle); + F32 cos = mSin(angle); + + // rotate points.. + Point2F pnts[8]; + U32 i; + for(i = 0; i < 8; i++) + { + pnts[i].x = arrow[i].x * cos - arrow[i].y * sin; + pnts[i].y = arrow[i].x * sin + arrow[i].y * cos; + } + + // draw it + GFX->setStateBlock(mBlendStateBlock); + + PrimBuild::color( mMirrorArrowColor ); + PrimBuild::begin( GFXTriangleList, 15 ); + for(i = 0; i < 15; i++) + PrimBuild::vertex2f( + rect.point.x + pnts[arrow_tri[i]].x * rect.extent.x + (rect.extent.x / 2) + GFX->getFillConventionOffset(), + rect.point.y + pnts[arrow_tri[i]].y * rect.extent.y + (rect.extent.y / 2) + GFX->getFillConventionOffset() ); + PrimBuild::end(); + + // opaque + PrimBuild::color4i( mMirrorArrowColor.red, mMirrorArrowColor.green, mMirrorArrowColor.blue, 0xff ); + PrimBuild::begin( GFXLineStrip, 8 ); + for(i = 0; i < 8; i++) + PrimBuild::vertex2f( + rect.point.x + pnts[i].x * rect.extent.x + (rect.extent.x / 2) + GFX->getFillConventionOffset(), + rect.point.y + pnts[i].y * rect.extent.y + (rect.extent.y / 2) + GFX->getFillConventionOffset() ); + PrimBuild::end(); + } + + renderChildControls(offset, updateRect); +} + +//------------------------------------------------------------------------------ +// sometimes you feel like a..... +bool MissionAreaEditor::inNut(const Point2I & pt, S32 x, S32 y) +{ + S32 dx = pt.x - x; + S32 dy = pt.y - y; + return dx <= NUT_SIZE && dx >= -NUT_SIZE && dy <= NUT_SIZE && dy >= -NUT_SIZE; +} + +S32 MissionAreaEditor::getSizingHitKnobs(const Point2I & pt, const RectI & box) +{ + if(!mEnableEditing || mEnableMirroring) + return(nothing); + + S32 lx = box.point.x, rx = box.point.x + box.extent.x - 1; + S32 cx = (lx + rx) >> 1; + S32 ty = box.point.y, by = box.point.y + box.extent.y - 1; + S32 cy = (ty + by) >> 1; + + if (inNut(pt, lx, ty)) + return sizingLeft | sizingTop; + if (inNut(pt, cx, ty)) + return sizingTop; + if (inNut(pt, rx, ty)) + return sizingRight | sizingTop; + if (inNut(pt, lx, by)) + return sizingLeft | sizingBottom; + if (inNut(pt, cx, by)) + return sizingBottom; + if (inNut(pt, rx, by)) + return sizingRight | sizingBottom; + if (inNut(pt, lx, cy)) + return sizingLeft; + if (inNut(pt, rx, cy)) + return sizingRight; + if(pt.x >= box.point.x && pt.x < box.point.x + box.extent.x && + pt.y >= box.point.y && pt.y < box.point.y + box.extent.y) + return(moving); + return nothing; +} + +void MissionAreaEditor::drawNut(const Point2I & nut) +{ + RectI r(nut.x - NUT_SIZE, nut.y - NUT_SIZE, 2 * NUT_SIZE + 1, 2 * NUT_SIZE + 1); + GFX->getDrawUtil()->drawRect(r, mHandleFrameColor); + r.point += Point2I(1, 1); + r.extent -= Point2I(1, 1); + GFX->getDrawUtil()->drawRectFill(r, mHandleFillColor); +} + +void MissionAreaEditor::drawNuts(RectI & box) +{ + S32 lx = box.point.x, rx = box.point.x + box.extent.x - 1; + S32 cx = (lx + rx) >> 1; + S32 ty = box.point.y, by = box.point.y + box.extent.y - 1; + S32 cy = (ty + by) >> 1; + drawNut(Point2I(lx, ty)); + drawNut(Point2I(lx, cy)); + drawNut(Point2I(lx, by)); + drawNut(Point2I(rx, ty)); + drawNut(Point2I(rx, cy)); + drawNut(Point2I(rx, by)); + drawNut(Point2I(cx, ty)); + drawNut(Point2I(cx, by)); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::updateCursor(S32 hit) +{ + if(hit) + { + if(hit == sizingTop || hit == sizingBottom) + setCursor(VertResizeCursor); + else if(hit == sizingLeft || hit == sizingRight) + setCursor(HorizResizeCursor); + else if(hit & sizingTop) + { + if(hit & sizingLeft) + setCursor(DiagLeftResizeCursor); + else + setCursor(DiagRightResizeCursor); + } + else if(hit & sizingBottom) + { + if(hit & sizingLeft) + setCursor(DiagRightResizeCursor); + else + setCursor(DiagLeftResizeCursor); + } + else if(hit == moving) + setCursor(HandCursor); + } + else + setCursor(DefaultCursor); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::onMouseUp(const GuiEvent & event) +{ + if(!bool(mMissionArea)) + return; + + RectI box; + getScreenMissionArea(box); + S32 hit = getSizingHitKnobs(event.mousePoint, box); + + // set the current cursor + updateCursor(hit); + mLastHitMode = hit; +} + +void MissionAreaEditor::onMouseDown(const GuiEvent & event) +{ + if(!bool(mMissionArea)) + return; + + if(!mEnableEditing || mEnableMirroring) + { + Point2F pos = screenToWorld(Point2F(F32(event.mousePoint.x), F32(event.mousePoint.y))); + setControlObjPos(pos); + return; + } + + RectI box; + getScreenMissionArea(box); + + mLastHitMode = getSizingHitKnobs(event.mousePoint, box); + if(mLastHitMode == moving) + setCursor(GrabCursor); + mLastMousePoint = event.mousePoint; +} + +void MissionAreaEditor::onMouseMove(const GuiEvent & event) +{ + if(!bool(mMissionArea)) + return; + + RectI box; + getScreenMissionArea(box); + S32 hit = getSizingHitKnobs(event.mousePoint, box); + + // set the current cursor... + updateCursor(hit); + mLastHitMode = hit; +} + +// update the mission area here... +void MissionAreaEditor::onMouseDragged(const GuiEvent & event) +{ + if(!bool(mMissionArea)) + return; + + if(!mLastHitMode) + return; + + RectI box; + getScreenMissionArea(box); + Point2I mouseDiff(event.mousePoint.x - mLastMousePoint.x, + event.mousePoint.y - mLastMousePoint.y); + + // what we drag'n? + if(mLastHitMode == moving) + box.point += mouseDiff; + else + { + // dont allow the box to be < 1x1 'pixels' + if(mLastHitMode & sizingLeft) + { + if(mouseDiff.x >= box.extent.x) + mouseDiff.x = box.extent.x - 1; + + box.point.x += mouseDiff.x; + box.extent.x -= mouseDiff.x; + } + + if(mLastHitMode & sizingRight) + { + if(mouseDiff.x + box.extent.x <= 0) + mouseDiff.x = -(box.extent.x - 1); + box.extent.x += mouseDiff.x; + } + + if(mLastHitMode & sizingTop) + { + if(mouseDiff.y >= box.extent.y) + mouseDiff.y = box.extent.y - 1; + + box.point.y += mouseDiff.y; + box.extent.y -= mouseDiff.y; + } + + if(mLastHitMode & sizingBottom) + { + if(mouseDiff.y + box.extent.y <= 0) + mouseDiff.y = -(box.extent.y - 1); + box.extent.y += mouseDiff.y; + } + } + + // + Point2I min = screenToWorld(box.point); + Point2I max = screenToWorld(box.point + box.extent); + + RectI iBox(min.x, min.y, max.x - min.x, max.y - min.y); + setArea(iBox); + + mLastMousePoint = event.mousePoint; +} + +void MissionAreaEditor::onMouseEnter(const GuiEvent &) +{ + mLastHitMode = nothing; + setCursor(DefaultCursor); +} + +void MissionAreaEditor::onMouseLeave(const GuiEvent &) +{ + mLastHitMode = nothing; + setCursor(DefaultCursor); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::setControlObjPos(const Point2F & pos) +{ + GameConnection * connection = GameConnection::getLocalClientConnection(); + + ShapeBase * obj = 0; + if(connection) + obj = dynamic_cast(connection->getControlObject()); + + if(!obj) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::setControlObjPos: could not get a control object!"); + return; + } + + // move it + MatrixF mat = obj->getTransform(); + + Point3F current; + mat.getColumn(3, ¤t); + + // + if(bool(mTerrainBlock)) + { + F32 height; + mTerrainBlock->getHeight(pos, &height); + if(current.z < height) + current.z = height + 10.f; + } + + // + current.set(pos.x, pos.y, current.z); + mat.setColumn(3, current); + obj->setTransform(mat); +} + + +//------------------------------------------------------------------------------ +// the following globals allocations needed for cCenterWorld are allocated when first refd. +//static U16 *heights = NULL; +//static U8 *baseMaterials = NULL; +//static TerrainBlock::Material *materials = NULL; +//static bool cwAllocs = false; + +//------------------------------------------------------------------------------ +ConsoleMethod( MissionAreaEditor, centerWorld, void, 2, 2, "Realign the world so that the mission area is centered.\n\n" + "This method moves every SceneObject (including terrain) in the world so that the center of the world is " + "the center of the mission area.") +{ + // TODO: Fix me when terrain changes are finished! + /* + if(!object->missionAreaObjValid()) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cCenterWorld: no MissionArea obj!"); + return; + } + + // + SimSet * missionGroup = dynamic_cast(Sim::findObject("missionGroup")); + if(!missionGroup) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cCenterWorld: no mission group found!"); + return; + } + + // make sure we're allocated. + // !!!!!TBD -- NOTE THAT THIS IS LEAKED ON EXIT! + if (!cwAllocs) + { + U32 allocSize = TerrainBlock::BlockSize * TerrainBlock::BlockSize; + heights = new U16[allocSize]; + if (NULL==heights) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cCenterWorld: out of memory!"); + return; + } + baseMaterials = new U8[allocSize]; + if (NULL==baseMaterials) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cCenterWorld: out of memory!"); + return; + } + materials = new TerrainBlock::Material[allocSize]; + if (NULL==materials) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cCenterWorld: out of memory!"); + return; + } + cwAllocs = true; + } + + // make sure area is clamped to terrain square size! + object->setArea(object->getArea()); + RectI area = object->getArea(); + + // calc offset + Point2I offset(area.point.x + (area.extent.x >> 1), + area.point.y + (area.extent.y >> 1)); + + if(!offset.x || !offset.y) + return; + + Point3F offset3F(F32(offset.x), F32(offset.y), 0.0f); + + // update all the scene objects + for(SimSetIterator itr(missionGroup); *itr; ++itr) + { + SceneObject * obj = dynamic_cast(*itr); + if(!obj) + continue; + + // terrain is handled special like (mission area is forced to align to + // terrain square sizes because terrain cannot move) + TerrainBlock * terrain = dynamic_cast(*itr); + if(terrain) + { + // get the new start location in gridSquare space + Point2I start((offset.x / (S32) terrain->getSquareSize()) & TerrainBlock::BlockMask, + (offset.y / (S32) terrain->getSquareSize()) & TerrainBlock::BlockMask); + + // update the grid block + if(start.x || start.y) + { + for(U32 y = 0; y < TerrainBlock::BlockSize; y++) + for(U32 x = 0; x < TerrainBlock::BlockSize; x++) + { + Point2I pos((start.x + x) & TerrainBlock::BlockMask, + (start.y + y) & TerrainBlock::BlockMask); + + heights[x + y * TerrainBlock::BlockSize] = terrain->getHeight(pos.x, pos.y); + baseMaterials[x + y * TerrainBlock::BlockSize] = terrain->getBaseMaterial(pos.x, pos.y); + materials[x + y * TerrainBlock::BlockSize] = *terrain->getMaterial(pos.x, pos.y); + } + + U16 * heightsAddr = terrain->getHeightAddress(0,0); + U8 * baseMaterialsAddr = terrain->getBaseMaterialAddress(0,0); + TerrainBlock::Material * materialsAddr = terrain->getMaterial(0,0); + + dMemcpy(heightsAddr, heights, sizeof(heights)); + dMemcpy(baseMaterialsAddr, baseMaterials, sizeof(baseMaterials)); + dMemcpy(materialsAddr, materials, sizeof(materials)); + + terrain->buildGridMap(); + terrain->rebuildEmptyFlags(); + terrain->packEmptySquares(); + } + + object->updateTerrainBitmap(); + } + else + { + MatrixF objMat = obj->getTransform(); + Point3F pos; + objMat.getColumn(3, &pos); + + pos -= offset3F; + objMat.setColumn(3, pos); + + obj->setTransform(objMat); + } + } + + char buf[64]; + dSprintf(buf, sizeof(buf), "%g %g %g", -offset3F.x, -offset3F.y, -offset3F.z); + Con::executef(object, "onWorldOffset", buf); + + // move the mission area + area.point.x -= offset.x; + area.point.y -= offset.y; + + object->setArea(area); + */ +} + +//------------------------------------------------------------------------------ + +ConsoleMethod(MissionAreaEditor, getArea, const char *, 2, 2, "Return a 4-tuple: area_x area_y area_width are_height") +{ + if(!object->missionAreaObjValid()) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cGetArea: no MissionArea obj!"); + return(""); + } + + // + RectI area = object->getArea(); + char * ret = Con::getReturnBuffer(64); + dSprintf(ret, 64, "%d %d %d %d", area.point.x, area.point.y, area.extent.x, area.extent.y); + + return(ret); +} + +ConsoleMethod( MissionAreaEditor, setArea, void, 3, 6, "(int x, int y, int w, int h)" + "Set the mission area to the specified co-ordinates/extents.") +{ + if(!object->missionAreaObjValid()) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cSetArea: no MissionArea obj!"); + return; + } + + RectI area; + + // + if(argc == 3) + dSscanf(argv[2], "%d %d %d %d", &area.point.x, &area.point.y, &area.extent.x, &area.extent.y); + else if(argc == 6) + { + area.point.x = dAtoi(argv[2]); + area.point.y = dAtoi(argv[3]); + area.extent.x = dAtoi(argv[4]); + area.extent.y = dAtoi(argv[5]); + } + else + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cSetArea: invalid number of arguments!"); + + // + object->setArea(area); +} + +ConsoleMethod( MissionAreaEditor, updateTerrain, void, 2, 2, "Update the terrain bitmap that is rendered as background in the control.") +{ + // + if(!object->getTerrainObj()) + { + Con::errorf(ConsoleLogEntry::General, "MissionAreaEditor::cUpdateTerrain: no terrain found!"); + return; + } + + // + object->updateTerrainBitmap(); +} + +//------------------------------------------------------------------------------ + +void MissionAreaEditor::initPersistFields() +{ + addGroup("Mirror"); + addField("enableMirroring", TypeBool, Offset(mEnableMirroring, MissionAreaEditor)); + addField("mirrorIndex", TypeS32, Offset(mMirrorIndex, MissionAreaEditor)); + addField("mirrorLineColor", TypeColorI, Offset(mMirrorLineColor, MissionAreaEditor)); + addField("mirrorArrowColor", TypeColorI, Offset(mMirrorArrowColor, MissionAreaEditor)); + endGroup("Mirror"); + + addGroup("Misc"); + addField("handleFrameColor", TypeColorI, Offset(mHandleFrameColor, MissionAreaEditor)); + addField("handleFillColor", TypeColorI, Offset(mHandleFillColor, MissionAreaEditor)); + addField("defaultObjectColor", TypeColorI, Offset(mDefaultObjectColor, MissionAreaEditor)); + addField("waterObjectColor", TypeColorI, Offset(mWaterObjectColor, MissionAreaEditor)); + addField("missionBoundsColor", TypeColorI, Offset(mMissionBoundsColor, MissionAreaEditor)); + addField("cameraColor", TypeColorI, Offset(mCameraColor, MissionAreaEditor)); + addField("squareBitmap", TypeBool, Offset(mSquareBitmap, MissionAreaEditor)); + addField("enableEditing", TypeBool, Offset(mEnableEditing, MissionAreaEditor)); + addField("renderCamera", TypeBool, Offset(mRenderCamera, MissionAreaEditor)); + endGroup("Misc"); + + Parent::initPersistFields(); +} diff --git a/gui/worldEditor/missionAreaEditor.h b/gui/worldEditor/missionAreaEditor.h new file mode 100644 index 0000000..c7f56a5 --- /dev/null +++ b/gui/worldEditor/missionAreaEditor.h @@ -0,0 +1,152 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MISSIONAREAEDITOR_H_ +#define _MISSIONAREAEDITOR_H_ + +#ifndef _GUIBITMAPCTRL_H_ +#include "gui/controls/guiBitmapCtrl.h" +#endif +#ifndef _GUITYPES_H_ +#include "gui/guiTypes.h" +#endif +#ifndef _MISSIONAREA_H_ +#include "T3D/missionArea.h" +#endif + +class GBitmap; +class TerrainBlock; + +class MissionAreaEditor : public GuiBitmapCtrl +{ + private: + typedef GuiBitmapCtrl Parent; + + SimObjectPtr mMissionArea; + SimObjectPtr mTerrainBlock; + + GBitmap * createTerrainBitmap(); + void onUpdate(); + + void setControlObjPos(const Point2F & pos); + bool clampArea(RectI & area); + + // -------------------------------------------------- + // conversion + VectorF mScale; + Point2F mCenterPos; + + Point2F worldToScreen(const Point2F &); + Point2F screenToWorld(const Point2F &); + + Point2I worldToScreen(const Point2I &); + Point2I screenToWorld(const Point2I &); + + void getScreenMissionArea(RectI & rect); + void getScreenMissionArea(RectF & rect); + + void setupScreenTransform(const Point2I & offset); + + // -------------------------------------------------- + // mouse + enum { + DefaultCursor = 0, + HandCursor, + GrabCursor, + VertResizeCursor, + HorizResizeCursor, + DiagRightResizeCursor, + DiagLeftResizeCursor, + NumCursors + }; + + bool grabCursors(); + GuiCursor * mCurrentCursor; + GuiCursor * mCursors[NumCursors]; + void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + void setCursor(U32 cursor); + + S32 mLastHitMode; + Point2I mLastMousePoint; + + // -------------------------------------------------- + // mission area + enum { + NUT_SIZE = 3 + }; + + enum { + nothing = 0, + sizingLeft = BIT(0), + sizingRight = BIT(1), + sizingTop = BIT(2), + sizingBottom = BIT(3), + moving = BIT(4) + }; + + GFXStateBlockRef mBlendStateBlock; + GFXStateBlockRef mSolidStateBlock; + + void updateCursor(S32 hit); + bool inNut(const Point2I & pt, S32 x, S32 y); + S32 getSizingHitKnobs(const Point2I & pt, const RectI & box); + void drawNut(const Point2I & nut); + void drawNuts(RectI & box); + + public: + + MissionAreaEditor(); + + // + bool missionAreaObjValid() { return(!mMissionArea.isNull()); } + bool terrainObjValid() { return(!mTerrainBlock.isNull()); } + + TerrainBlock * getTerrainObj(); + + const RectI & getArea(); + void setArea(const RectI & area); + + void updateTerrainBitmap(); + + // GuiControl + void parentResized(const Point2I &oldParentExtent, const Point2I &newParentExtent); + void onRender(Point2I offset, const RectI &updateRect); + bool onWake(); + void onSleep(); + + void onMouseUp(const GuiEvent & event); + void onMouseDown(const GuiEvent & event); + void onMouseMove(const GuiEvent & event); + void onMouseDragged(const GuiEvent & event); + void onMouseEnter(const GuiEvent & event); + void onMouseLeave(const GuiEvent & event); + + // SimObject + bool onAdd(); + + // field data.. + bool mSquareBitmap; + bool mEnableEditing; + bool mRenderCamera; + + ColorI mHandleFrameColor; + ColorI mHandleFillColor; + ColorI mDefaultObjectColor; + ColorI mWaterObjectColor; + ColorI mMissionBoundsColor; + ColorI mCameraColor; + + bool mEnableMirroring; + S32 mMirrorIndex; + ColorI mMirrorLineColor; + ColorI mMirrorArrowColor; + + static void initPersistFields(); + + DECLARE_CONOBJECT(MissionAreaEditor); + DECLARE_CATEGORY( "Gui Editor" ); +}; + +#endif diff --git a/gui/worldEditor/terrainActions.cpp b/gui/worldEditor/terrainActions.cpp new file mode 100644 index 0000000..ad9ef80 --- /dev/null +++ b/gui/worldEditor/terrainActions.cpp @@ -0,0 +1,759 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/worldEditor/terrainActions.h" + +#include "gui/core/guiCanvas.h" + +//------------------------------------------------------------------------------ + +void SelectAction::process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type) +{ + if(sel == mTerrainEditor->getCurrentSel()) + return; + + if(type == Process) + return; + + if(selChanged) + { + if(event.modifier & SI_MULTISELECT) + { + for(U32 i = 0; i < sel->size(); i++) + mTerrainEditor->getCurrentSel()->remove((*sel)[i]); + } + else + { + for(U32 i = 0; i < sel->size(); i++) + { + GridInfo gInfo; + if(mTerrainEditor->getCurrentSel()->getInfo((*sel)[i].mGridPoint.gridPos, gInfo)) + { + if(!gInfo.mPrimarySelect) + gInfo.mPrimarySelect = (*sel)[i].mPrimarySelect; + + if(gInfo.mWeight < (*sel)[i].mWeight) + gInfo.mWeight = (*sel)[i].mWeight; + + mTerrainEditor->getCurrentSel()->setInfo(gInfo); + } + else + mTerrainEditor->getCurrentSel()->add((*sel)[i]); + } + } + } +} + +void DeselectAction::process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type) +{ + if(sel == mTerrainEditor->getCurrentSel()) + return; + + if(type == Process) + return; + + if(selChanged) + { + for(U32 i = 0; i < sel->size(); i++) + mTerrainEditor->getCurrentSel()->remove((*sel)[i]); + } +} + +//------------------------------------------------------------------------------ + +void SoftSelectAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type type) +{ + TerrainBlock *terrBlock = mTerrainEditor->getActiveTerrain(); + if ( !terrBlock ) + return; + + // allow process of current selection + Selection tmpSel; + if(sel == mTerrainEditor->getCurrentSel()) + { + tmpSel = *sel; + sel = &tmpSel; + } + + if(type == Begin || type == Process) + mFilter.set(1, &mTerrainEditor->mSoftSelectFilter); + + // + if(selChanged) + { + F32 radius = mTerrainEditor->mSoftSelectRadius; + if(radius == 0.f) + return; + + S32 squareSize = terrBlock->getSquareSize(); + U32 offset = U32(radius / F32(squareSize)) + 1; + + for(U32 i = 0; i < sel->size(); i++) + { + GridInfo & info = (*sel)[i]; + + info.mPrimarySelect = true; + info.mWeight = mFilter.getValue(0); + + if(!mTerrainEditor->getCurrentSel()->add(info)) + mTerrainEditor->getCurrentSel()->setInfo(info); + + Point2F infoPos((F32)info.mGridPoint.gridPos.x, (F32)info.mGridPoint.gridPos.y); + + // + for(S32 x = info.mGridPoint.gridPos.x - offset; x < info.mGridPoint.gridPos.x + (offset << 1); x++) + for(S32 y = info.mGridPoint.gridPos.y - offset; y < info.mGridPoint.gridPos.y + (offset << 1); y++) + { + // + Point2F pos((F32)x, (F32)y); + + F32 dist = Point2F(pos - infoPos).len() * F32(squareSize); + + if(dist > radius) + continue; + + F32 weight = mFilter.getValue(dist / radius); + + // + GridInfo gInfo; + GridPoint gridPoint = info.mGridPoint; + gridPoint.gridPos.set(x, y); + + if(mTerrainEditor->getCurrentSel()->getInfo(Point2I(x, y), gInfo)) + { + if(gInfo.mPrimarySelect) + continue; + + if(gInfo.mWeight < weight) + { + gInfo.mWeight = weight; + mTerrainEditor->getCurrentSel()->setInfo(gInfo); + } + } + else + { + Vector gInfos; + mTerrainEditor->getGridInfos(gridPoint, gInfos); + + for (U32 z = 0; z < gInfos.size(); z++) + { + gInfos[z].mWeight = weight; + gInfos[z].mPrimarySelect = false; + mTerrainEditor->getCurrentSel()->add(gInfos[z]); + } + } + } + } + } +} + +//------------------------------------------------------------------------------ + +void OutlineSelectAction::process(Selection * sel, const Gui3DMouseEvent & event, bool, Type type) +{ + TORQUE_UNUSED(sel); TORQUE_UNUSED(event); TORQUE_UNUSED(type); + switch(type) + { + case Begin: + if(event.modifier & SI_SHIFT) + break; + + mTerrainEditor->getCurrentSel()->reset(); + break; + + case End: + case Update: + + default: + return; + } + + mLastEvent = event; +} + +//------------------------------------------------------------------------------ + +void PaintMaterialAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + S32 mat = mTerrainEditor->getPaintMaterialIndex(); + if ( !selChanged || mat < 0 ) + return; + + const bool slopeLimit = mTerrainEditor->mSlopeMinAngle > 0.0f || mTerrainEditor->mSlopeMaxAngle < 90.0f; + const F32 minSlope = mSin( mDegToRad( 90.0f - mTerrainEditor->mSlopeMinAngle ) ); + const F32 maxSlope = mSin( mDegToRad( 90.0f - mTerrainEditor->mSlopeMaxAngle ) ); + + const TerrainBlock *terrain = mTerrainEditor->getActiveTerrain(); + const F32 squareSize = terrain->getSquareSize(); + + Point2F p; + Point3F norm; + + + for( U32 i = 0; i < sel->size(); i++ ) + { + GridInfo &inf = (*sel)[i]; + + if ( slopeLimit ) + { + p.x = inf.mGridPoint.gridPos.x * squareSize; + p.y = inf.mGridPoint.gridPos.y * squareSize; + if ( !terrain->getNormal( p, &norm, true ) ) + continue; + + if ( norm.z > minSlope || + norm.z < maxSlope ) + continue; + } + + if ( inf.mMaterial == mat ) + continue; + + inf.mMaterialChanged = true; + mTerrainEditor->getUndoSel()->add(inf); + + // Painting is really simple now... set the one mat index. + inf.mMaterial = mat; + mTerrainEditor->setGridInfo(inf, true); + } + + mTerrainEditor->scheduleMaterialUpdate(); +} + +//------------------------------------------------------------------------------ + +void ClearMaterialsAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if(selChanged) + { + for(U32 i = 0; i < sel->size(); i++) + { + GridInfo &inf = (*sel)[i]; + + mTerrainEditor->getUndoSel()->add(inf); + inf.mMaterialChanged = true; + + // Reset to the first texture layer. + inf.mMaterial = 0; + mTerrainEditor->setGridInfo(inf); + } + mTerrainEditor->scheduleMaterialUpdate(); + } +} + +//------------------------------------------------------------------------------ + +void RaiseHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + // ok the raise height action is our "dirt pour" action + // only works on brushes... + + Brush *brush = dynamic_cast(sel); + if(!brush) + return; + Point2I brushPos = brush->getPosition(); + Point2I brushSize = brush->getSize(); + GridPoint brushGridPoint = brush->getGridPoint(); + + Vector cur; // the height at the brush position + mTerrainEditor->getGridInfos(brushGridPoint, cur); + + if (cur.size() == 0) + return; + + // we get 30 process actions per second (at least) + F32 heightAdjust = mTerrainEditor->mAdjustHeightVal / 30; + // nothing can get higher than the current brush pos adjusted height + + F32 maxHeight = cur[0].mHeight + heightAdjust; + + for(U32 i = 0; i < sel->size(); i++) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + if((*sel)[i].mHeight < maxHeight) + { + (*sel)[i].mHeight += heightAdjust * (*sel)[i].mWeight; + if((*sel)[i].mHeight > maxHeight) + (*sel)[i].mHeight = maxHeight; + } + mTerrainEditor->setGridInfo((*sel)[i]); + } + + mTerrainEditor->scheduleGridUpdate(); +} + +//------------------------------------------------------------------------------ + +void LowerHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + // ok the lower height action is our "dirt dig" action + // only works on brushes... + + Brush *brush = dynamic_cast(sel); + if(!brush) + return; + Point2I brushPos = brush->getPosition(); + Point2I brushSize = brush->getSize(); + GridPoint brushGridPoint = brush->getGridPoint(); + + Vector cur; // the height at the brush position + mTerrainEditor->getGridInfos(brushGridPoint, cur); + + if (cur.size() == 0) + return; + + // we get 30 process actions per second (at least) + F32 heightAdjust = -mTerrainEditor->mAdjustHeightVal / 30; + // nothing can get higher than the current brush pos adjusted height + + F32 maxHeight = cur[0].mHeight + heightAdjust; + if(maxHeight < 0) + maxHeight = 0; + + for(U32 i = 0; i < sel->size(); i++) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + if((*sel)[i].mHeight > maxHeight) + { + (*sel)[i].mHeight += heightAdjust * (*sel)[i].mWeight; + if((*sel)[i].mHeight < maxHeight) + (*sel)[i].mHeight = maxHeight; + } + mTerrainEditor->setGridInfo((*sel)[i]); + } + + mTerrainEditor->scheduleGridUpdate(); +} + +//------------------------------------------------------------------------------ + +void SetHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if(selChanged) + { + for(U32 i = 0; i < sel->size(); i++) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + (*sel)[i].mHeight = mTerrainEditor->mSetHeightVal; + mTerrainEditor->setGridInfo((*sel)[i]); + } + mTerrainEditor->scheduleGridUpdate(); + } +} + +//------------------------------------------------------------------------------ + +void SetEmptyAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if ( !selChanged ) + return; + + mTerrainEditor->setMissionDirty(); + + for ( U32 i = 0; i < sel->size(); i++ ) + { + GridInfo &inf = (*sel)[i]; + + // Skip already empty blocks. + if ( inf.mMaterial == U8_MAX ) + continue; + + // The change flag needs to be set on the undo + // so that it knows to restore materials. + inf.mMaterialChanged = true; + mTerrainEditor->getUndoSel()->add( inf ); + + // Set the material to empty. + inf.mMaterial = -1; + mTerrainEditor->setGridInfo( inf ); + } + + mTerrainEditor->scheduleGridUpdate(); +} + +//------------------------------------------------------------------------------ + +void ClearEmptyAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if ( !selChanged ) + return; + + mTerrainEditor->setMissionDirty(); + + for ( U32 i = 0; i < sel->size(); i++ ) + { + GridInfo &inf = (*sel)[i]; + + // Skip if not empty. + if ( inf.mMaterial != U8_MAX ) + continue; + + // The change flag needs to be set on the undo + // so that it knows to restore materials. + inf.mMaterialChanged = true; + mTerrainEditor->getUndoSel()->add( inf ); + + // Set the material + inf.mMaterial = 0; + mTerrainEditor->setGridInfo( inf ); + } + + mTerrainEditor->scheduleGridUpdate(); +} + +//------------------------------------------------------------------------------ + +void ScaleHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if(selChanged) + { + for(U32 i = 0; i < sel->size(); i++) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + (*sel)[i].mHeight *= mTerrainEditor->mScaleVal; + mTerrainEditor->setGridInfo((*sel)[i]); + } + mTerrainEditor->scheduleGridUpdate(); + } +} + +void BrushAdjustHeightAction::process(Selection * sel, const Gui3DMouseEvent & event, bool, Type type) +{ + if(type == Process) + return; + + TerrainBlock *terrBlock = mTerrainEditor->getActiveTerrain(); + if ( !terrBlock ) + return; + + if(type == Begin) + { + mTerrainEditor->lockSelection(true); + mTerrainEditor->getRoot()->mouseLock(mTerrainEditor); + + // the way this works is: + // construct a plane that goes through the collision point + // with one axis up the terrain Z, and horizontally parallel to the + // plane of projection + + // the cross of the camera ffdv and the terrain up vector produces + // the cross plane vector. + + // all subsequent mouse actions are collided against the plane and the deltaZ + // from the previous position is used to delta the selection up and down. + Point3F cameraDir; + + EditTSCtrl::smCamMatrix.getColumn(1, &cameraDir); + terrBlock->getTransform().getColumn(2, &mTerrainUpVector); + + // ok, get the cross vector for the plane: + Point3F planeCross; + mCross(cameraDir, mTerrainUpVector, &planeCross); + + planeCross.normalize(); + Point3F planeNormal; + + Point3F intersectPoint; + mTerrainEditor->collide(event, intersectPoint); + + mCross(mTerrainUpVector, planeCross, &planeNormal); + mIntersectionPlane.set(intersectPoint, planeNormal); + + // ok, we have the intersection point... + // project the collision point onto the up vector of the terrain + + mPreviousZ = mDot(mTerrainUpVector, intersectPoint); + + // add to undo + // and record the starting heights + for(U32 i = 0; i < sel->size(); i++) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + (*sel)[i].mStartHeight = (*sel)[i].mHeight; + } + } + else if(type == Update) + { + // ok, collide the ray from the event with the intersection plane: + + Point3F intersectPoint; + Point3F start = event.pos; + Point3F end = start + event.vec * 1000; + + F32 t = mIntersectionPlane.intersect(start, end); + + m_point3F_interpolate( start, end, t, intersectPoint); + F32 currentZ = mDot(mTerrainUpVector, intersectPoint); + + F32 diff = currentZ - mPreviousZ; + + for(U32 i = 0; i < sel->size(); i++) + { + (*sel)[i].mHeight = (*sel)[i].mStartHeight + diff * (*sel)[i].mWeight; + + // clamp it + if((*sel)[i].mHeight < 0.f) + (*sel)[i].mHeight = 0.f; + if((*sel)[i].mHeight > 2047.f) + (*sel)[i].mHeight = 2047.f; + + mTerrainEditor->setGridInfoHeight((*sel)[i]); + } + mTerrainEditor->scheduleGridUpdate(); + } + else if(type == End) + { + mTerrainEditor->getRoot()->mouseUnlock(mTerrainEditor); + } +} + +//------------------------------------------------------------------------------ + +AdjustHeightAction::AdjustHeightAction(TerrainEditor * editor) : + BrushAdjustHeightAction(editor) +{ + mCursor = 0; +} + +void AdjustHeightAction::process(Selection *sel, const Gui3DMouseEvent & event, bool b, Type type) +{ + Selection * curSel = mTerrainEditor->getCurrentSel(); + BrushAdjustHeightAction::process(curSel, event, b, type); +} + +//------------------------------------------------------------------------------ +// flatten the primary selection then blend in the rest... + +void FlattenHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if(!sel->size()) + return; + + if(selChanged) + { + F32 average = 0.f; + + // get the average height + U32 cPrimary = 0; + for(U32 k = 0; k < sel->size(); k++) + if((*sel)[k].mPrimarySelect) + { + cPrimary++; + average += (*sel)[k].mHeight; + } + + average /= cPrimary; + + // set it + for(U32 i = 0; i < sel->size(); i++) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + + // + if((*sel)[i].mPrimarySelect) + (*sel)[i].mHeight = average; + else + { + F32 h = average - (*sel)[i].mHeight; + (*sel)[i].mHeight += (h * (*sel)[i].mWeight); + } + + mTerrainEditor->setGridInfo((*sel)[i]); + } + mTerrainEditor->scheduleGridUpdate(); + } +} + +//------------------------------------------------------------------------------ + +void SmoothHeightAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if(!sel->size()) + return; + + if(selChanged) + { + F32 avgHeight = 0.f; + for(U32 k = 0; k < sel->size(); k++) + { + mTerrainEditor->getUndoSel()->add((*sel)[k]); + avgHeight += (*sel)[k].mHeight; + } + + avgHeight /= sel->size(); + + // clamp the terrain smooth factor... + if(mTerrainEditor->mSmoothFactor < 0.f) + mTerrainEditor->mSmoothFactor = 0.f; + if(mTerrainEditor->mSmoothFactor > 1.f) + mTerrainEditor->mSmoothFactor = 1.f; + + // linear + for(U32 i = 0; i < sel->size(); i++) + { + (*sel)[i].mHeight += (avgHeight - (*sel)[i].mHeight) * mTerrainEditor->mSmoothFactor * (*sel)[i].mWeight; + mTerrainEditor->setGridInfo((*sel)[i]); + } + mTerrainEditor->scheduleGridUpdate(); + } +} + +void PaintNoiseAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type type) +{ + // If this is the ending + // mouse down event, then + // update the noise values. + if ( type == Begin ) + { + mNoise.setSeed( Sim::getCurrentTime() ); + mNoise.fBm( &mNoiseData, mNoiseSize, 12, 1.0f, 5.0f ); + mNoise.getMinMax( &mNoiseData, &mMinMaxNoise.x, &mMinMaxNoise.y, mNoiseSize ); + + mScale = 1.5f / ( mMinMaxNoise.x - mMinMaxNoise.y); + } + + if( selChanged ) + { + for( U32 i = 0; i < sel->size(); i++ ) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + + const Point2I &gridPos = (*sel)[i].mGridPoint.gridPos; + + const F32 noiseVal = mNoiseData[ ( gridPos.x % mNoiseSize ) + + ( ( gridPos.y % mNoiseSize ) * mNoiseSize ) ]; + + (*sel)[i].mHeight += (noiseVal - mMinMaxNoise.y * mScale) * (*sel)[i].mWeight * mTerrainEditor->mNoiseFactor; + + mTerrainEditor->setGridInfo((*sel)[i]); + } + + mTerrainEditor->scheduleGridUpdate(); + } +} +/* +void ThermalErosionAction::process(Selection * sel, const Gui3DMouseEvent &, bool selChanged, Type) +{ + if( selChanged ) + { + TerrainBlock *tblock = mTerrainEditor->getActiveTerrain(); + if ( !tblock ) + return; + + F32 height = 0; + F32 maxHeight = 0; + U32 shift = getBinLog2( TerrainBlock::BlockSize ); + + for ( U32 x = 0; x < TerrainBlock::BlockSize; x++ ) + { + for ( U32 y = 0; y < TerrainBlock::BlockSize; y++ ) + { + height = fixedToFloat( tblock->getHeight( x, y ) ); + mTerrainHeights[ x + (y << 8)] = height; + + if ( height > maxHeight ) + maxHeight = height; + } + } + + //mNoise.erodeThermal( &mTerrainHeights, &mNoiseData, 30.0f, 5.0f, 5, TerrainBlock::BlockSize, tblock->getSquareSize(), maxHeight ); + + mNoise.erodeHydraulic( &mTerrainHeights, &mNoiseData, 1, TerrainBlock::BlockSize ); + + F32 heightDiff = 0; + + for( U32 i = 0; i < sel->size(); i++ ) + { + mTerrainEditor->getUndoSel()->add((*sel)[i]); + + const Point2I &gridPos = (*sel)[i].mGridPoint.gridPos; + + // Need to get the height difference + // between the current height and the + // erosion height to properly apply the + // softness and pressure settings of the brush + // for this selection. + heightDiff = (*sel)[i].mHeight - mNoiseData[ gridPos.x + (gridPos.y << shift)]; + + (*sel)[i].mHeight -= (heightDiff * (*sel)[i].mWeight); + + mTerrainEditor->setGridInfo((*sel)[i]); + } + + mTerrainEditor->gridUpdateComplete(); + } +} +*/ + + +IMPLEMENT_CONOBJECT( TerrainSmoothAction ); + +TerrainSmoothAction::TerrainSmoothAction() + : UndoAction( "Terrain Smoothing" ) +{ +} + +void TerrainSmoothAction::initPersistFields() +{ + Parent::initPersistFields(); +} + +void TerrainSmoothAction::smooth( TerrainBlock *terrain, F32 factor, U32 steps ) +{ + AssertFatal( terrain, "TerrainSmoothAction::smooth() - Got null object!" ); + + // Store our input parameters. + mTerrainId = terrain->getId(); + mSteps = steps; + mFactor = factor; + + // The redo can do the rest. + redo(); +} + +ConsoleMethod( TerrainSmoothAction, smooth, void, 5, 5, "( TerrainBlock obj, F32 factor, U32 steps )") +{ + TerrainBlock *terrain = NULL; + if ( Sim::findObject( argv[2], terrain ) && terrain ) + object->smooth( terrain, dAtof( argv[3] ), mClamp( dAtoi( argv[4] ), 1, 13 ) ); +} + +void TerrainSmoothAction::undo() +{ + // First find the terrain from the id. + TerrainBlock *terrain; + if ( !Sim::findObject( mTerrainId, terrain ) || !terrain ) + return; + + // Get the terrain file. + TerrainFile *terrFile = terrain->getFile(); + + // Copy our stored heightmap to the file. + terrFile->setHeightMap( mUnsmoothedHeights, false ); + + // Tell the terrain to update itself. + terrain->updateGrid( Point2I::Zero, Point2I::Max, true ); +} + +void TerrainSmoothAction::redo() +{ + // First find the terrain from the id. + TerrainBlock *terrain; + if ( !Sim::findObject( mTerrainId, terrain ) || !terrain ) + return; + + // Get the terrain file. + TerrainFile *terrFile = terrain->getFile(); + + // First copy the heightmap state. + mUnsmoothedHeights = terrFile->getHeightMap(); + + // Do the smooth. + terrFile->smooth( mFactor, mSteps, false ); + + // Tell the terrain to update itself. + terrain->updateGrid( Point2I::Zero, Point2I::Max, true ); +} \ No newline at end of file diff --git a/gui/worldEditor/terrainActions.h b/gui/worldEditor/terrainActions.h new file mode 100644 index 0000000..914439a --- /dev/null +++ b/gui/worldEditor/terrainActions.h @@ -0,0 +1,334 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRAINACTIONS_H_ +#define _TERRAINACTIONS_H_ + +#ifndef _TERRAINEDITOR_H_ +#include "gui/worldEditor/terrainEditor.h" +#endif +#ifndef _GUIFILTERCTRL_H_ +#include "gui/editor/guiFilterCtrl.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _NOISE2D_H_ +#include "util/noise2d.h" +#endif + +class TerrainAction +{ + protected: + TerrainEditor * mTerrainEditor; + + public: + + virtual ~TerrainAction(){}; + TerrainAction(TerrainEditor * editor) : mTerrainEditor(editor){} + + virtual StringTableEntry getName() = 0; + + enum Type { + Begin = 0, + Update, + End, + Process + }; + + // + virtual void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type) = 0; + virtual bool useMouseBrush() { return(true); } +}; + +//------------------------------------------------------------------------------ + +class SelectAction : public TerrainAction +{ + public: + SelectAction(TerrainEditor * editor) : TerrainAction(editor){}; + StringTableEntry getName(){return("select");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +class DeselectAction : public TerrainAction +{ + public: + DeselectAction(TerrainEditor * editor) : TerrainAction(editor){}; + StringTableEntry getName(){return("deselect");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +class ClearAction : public TerrainAction +{ + public: + ClearAction(TerrainEditor * editor) : TerrainAction(editor){}; + StringTableEntry getName(){return("clear");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type) {}; + bool useMouseBrush() { mTerrainEditor->getCurrentSel()->reset(); return true; } +}; + + +class SoftSelectAction : public TerrainAction +{ + public: + SoftSelectAction(TerrainEditor * editor) : TerrainAction(editor){}; + StringTableEntry getName(){return("softSelect");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); + + Filter mFilter; +}; + +//------------------------------------------------------------------------------ + +class OutlineSelectAction : public TerrainAction +{ + public: + OutlineSelectAction(TerrainEditor * editor) : TerrainAction(editor){}; + StringTableEntry getName(){return("outlineSelect");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); + bool useMouseBrush() { return(false); } + + private: + + Gui3DMouseEvent mLastEvent; +}; + +//------------------------------------------------------------------------------ + +class PaintMaterialAction : public TerrainAction +{ + public: + PaintMaterialAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("paintMaterial");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class ClearMaterialsAction : public TerrainAction +{ +public: + ClearMaterialsAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("clearMaterials");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class RaiseHeightAction : public TerrainAction +{ + public: + RaiseHeightAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("raiseHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class LowerHeightAction : public TerrainAction +{ + public: + LowerHeightAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("lowerHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class SetHeightAction : public TerrainAction +{ + public: + SetHeightAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("setHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class SetEmptyAction : public TerrainAction +{ + public: + SetEmptyAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("setEmpty");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class ClearEmptyAction : public TerrainAction +{ + public: + ClearEmptyAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("clearEmpty");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class ScaleHeightAction : public TerrainAction +{ + public: + ScaleHeightAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("scaleHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +//------------------------------------------------------------------------------ + +class BrushAdjustHeightAction : public TerrainAction +{ + public: + BrushAdjustHeightAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("brushAdjustHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); + + private: + PlaneF mIntersectionPlane; + Point3F mTerrainUpVector; + F32 mPreviousZ; +}; + +class AdjustHeightAction : public BrushAdjustHeightAction +{ + public: + AdjustHeightAction(TerrainEditor * editor); + StringTableEntry getName(){return("adjustHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); + bool useMouseBrush() { return(false); } + + private: + // + Point3F mHitPos; + Point3F mLastPos; + SimObjectPtr mCursor; +}; + +//------------------------------------------------------------------------------ + +class FlattenHeightAction : public TerrainAction +{ + public: + FlattenHeightAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("flattenHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +class SmoothHeightAction : public TerrainAction +{ + public: + SmoothHeightAction(TerrainEditor * editor) : TerrainAction(editor){} + StringTableEntry getName(){return("smoothHeight");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); +}; + +class PaintNoiseAction : public TerrainAction +{ + public: + + PaintNoiseAction( TerrainEditor *editor ) + : TerrainAction( editor ), + mNoiseSize( 256 ) + { + mNoise.setSeed( 5342219 ); + mNoiseData.setSize( mNoiseSize * mNoiseSize ); + mNoise.fBm( &mNoiseData, mNoiseSize, 12, 1.0f, 5.0f ); + //Vector scratch = mNoiseData; + //mNoise.rigidMultiFractal( &mNoiseData, &scratch, TerrainBlock::BlockSize, 12, 1.0f, 5.0f ); + mNoise.getMinMax( &mNoiseData, &mMinMaxNoise.x, &mMinMaxNoise.y, mNoiseSize ); + + mScale = 1.5f / ( mMinMaxNoise.x - mMinMaxNoise.y); + } + + StringTableEntry getName() { return "paintNoise"; } + + void process( Selection *sel, const Gui3DMouseEvent &event, bool selChanged, Type type ); + + protected: + + const U32 mNoiseSize; + + Noise2D mNoise; + + Vector mNoiseData; + + Point2F mMinMaxNoise; + + F32 mScale; +}; + +/* +class ThermalErosionAction : public TerrainAction +{ + public: + ThermalErosionAction(TerrainEditor * editor) + : TerrainAction(editor) + { + mNoise.setSeed( 1 );//Sim::getCurrentTime() ); + mNoiseData.setSize( TerrainBlock::BlockSize * TerrainBlock::BlockSize ); + mTerrainHeights.setSize( TerrainBlock::BlockSize * TerrainBlock::BlockSize ); + } + + StringTableEntry getName(){return("thermalErode");} + + void process(Selection * sel, const Gui3DMouseEvent & event, bool selChanged, Type type); + + Noise2D mNoise; + Vector mNoiseData; + Vector mTerrainHeights; +}; +*/ + +/// An undo action used to perform terrain wide smoothing. +class TerrainSmoothAction : public UndoAction +{ + typedef UndoAction Parent; + +protected: + + SimObjectId mTerrainId; + + U32 mSteps; + + F32 mFactor; + + Vector mUnsmoothedHeights; + +public: + + TerrainSmoothAction(); + + // SimObject + DECLARE_CONOBJECT( TerrainSmoothAction ); + static void initPersistFields(); + + // UndoAction + virtual void undo(); + virtual void redo(); + + /// Performs the initial smoothing and stores + /// the heighfield state for later undo. + void smooth( TerrainBlock *terrain, F32 factor, U32 steps ); +}; + + +#endif // _TERRAINACTIONS_H_ diff --git a/gui/worldEditor/terrainEditor.cpp b/gui/worldEditor/terrainEditor.cpp new file mode 100644 index 0000000..aec84e1 --- /dev/null +++ b/gui/worldEditor/terrainEditor.cpp @@ -0,0 +1,2509 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/worldEditor/terrainEditor.h" + +#include "gui/core/guiCanvas.h" +#include "console/consoleTypes.h" +#include "gui/worldEditor/terrainActions.h" +#include "sim/netConnection.h" +#include "core/frameAllocator.h" +#include "gfx/primBuilder.h" +#include "console/simEvents.h" +#include "interior/interiorInstance.h" +#include "core/strings/stringUnit.h" +#include "terrain/terrMaterial.h" + + +IMPLEMENT_CONOBJECT(TerrainEditor); + +Selection::Selection() : + Vector(__FILE__, __LINE__), + mName(0), + mUndoFlags(0), + mHashListSize(1024) +{ + VECTOR_SET_ASSOCIATION(mHashLists); + + // clear the hash list + mHashLists.setSize(mHashListSize); + reset(); +} + +Selection::~Selection() +{ +} + +void Selection::reset() +{ + for(U32 i = 0; i < mHashListSize; i++) + mHashLists[i] = -1; + clear(); +} + +bool Selection::validate() +{ + // scan all the hashes and verify that the heads they point to point back to them + U32 hashesProcessed = 0; + for(U32 i = 0; i < mHashLists.size(); i++) + { + U32 entry = mHashLists[i]; + if(entry == -1) + continue; + + GridInfo info = (*this)[entry]; + U32 hashIndex = getHashIndex(info.mGridPoint.gridPos); + + if( entry != mHashLists[hashIndex] ) + { + AssertFatal(false, "Selection hash lists corrupted"); + return false; + } + hashesProcessed++; + } + + // scan all the entries and verify that anything w/ a prev == -1 is correctly in the hash table + U32 headsProcessed = 0; + for(U32 i = 0; i < size(); i++) + { + GridInfo info = (*this)[i]; + if(info.mPrev != -1) + continue; + + U32 hashIndex = getHashIndex(info.mGridPoint.gridPos); + + if(mHashLists[hashIndex] != i) + { + AssertFatal(false, "Selection list heads corrupted"); + return false; + } + headsProcessed++; + } + AssertFatal(headsProcessed == hashesProcessed, "Selection's number of hashes and number of list heads differ."); + return true; +} + +U32 Selection::getHashIndex(const Point2I & pos) +{ + Point2F pnt = Point2F((F32)pos.x, (F32)pos.y) + Point2F(1.3f,3.5f); + return( (U32)(mFloor(mHashLists.size() * mFmod(pnt.len() * 0.618f, 1.0f))) ); +} + +S32 Selection::lookup(const Point2I & pos) +{ + U32 index = getHashIndex(pos); + + S32 entry = mHashLists[index]; + + while(entry != -1) + { + if((*this)[entry].mGridPoint.gridPos == pos) + return(entry); + + entry = (*this)[entry].mNext; + } + + return(-1); +} + +void Selection::insert(GridInfo info) +{ + //validate(); + // get the index into the hash table + U32 index = getHashIndex(info.mGridPoint.gridPos); + + // if there is an existing linked list, make it our next + info.mNext = mHashLists[index]; + info.mPrev = -1; + + // if there is an existing linked list, make us it's prev + U32 indexOfNewEntry = size(); + if(info.mNext != -1) + (*this)[info.mNext].mPrev = indexOfNewEntry; + + // the hash table holds the heads of the linked lists. make us the head of this list. + mHashLists[index] = indexOfNewEntry; + + // copy us into the vector + push_back(info); + //validate(); +} + +bool Selection::remove(const GridInfo &info) +{ + if(size() < 1) + return false; + + //AssertFatal( validate(), "Selection hashLists corrupted before Selection.remove()"); + + U32 hashIndex = getHashIndex(info.mGridPoint.gridPos); + S32 listHead = mHashLists[hashIndex]; + //AssertFatal(listHead < size(), "A Selection's hash table is corrupt."); + + if(listHead == -1) + return(false); + + const S32 victimEntry = lookup(info.mGridPoint.gridPos); + if( victimEntry == -1 ) + return(false); + + const GridInfo victim = (*this)[victimEntry]; + const S32 vicPrev = victim.mPrev; + const S32 vicNext = victim.mNext; + + // remove us from the linked list, if there is one. + if(vicPrev != -1) + (*this)[vicPrev].mNext = vicNext; + if(vicNext != -1) + (*this)[vicNext].mPrev = vicPrev; + + // if we were the head of the list, make our next the new head in the hash table. + if(vicPrev == -1) + mHashLists[hashIndex] = vicNext; + + // if we're not the last element in the vector, copy the last element to our position. + if(victimEntry != size() - 1) + { + // copy last into victim, and re-cache next & prev + const GridInfo lastEntry = last(); + const S32 lastPrev = lastEntry.mPrev; + const S32 lastNext = lastEntry.mNext; + (*this)[victimEntry] = lastEntry; + + // update the new element's next and prev, to reestablish it in it's linked list. + if(lastPrev != -1) + (*this)[lastPrev].mNext = victimEntry; + if(lastNext != -1) + (*this)[lastNext].mPrev = victimEntry; + + // if it was the head of it's list, update the hash table with its new position. + if(lastPrev == -1) + { + const U32 lastHash = getHashIndex(lastEntry.mGridPoint.gridPos); + AssertFatal(mHashLists[lastHash] == size() - 1, "Selection hashLists corrupted during Selection.remove() (oldmsg)"); + mHashLists[lastHash] = victimEntry; + } + } + + // decrement the vector, we're done here + pop_back(); + //AssertFatal( validate(), "Selection hashLists corrupted after Selection.remove()"); + return true; +} + +// add unique grid info into the selection - test uniqueness by grid position +bool Selection::add(const GridInfo &info) +{ + S32 index = lookup(info.mGridPoint.gridPos); + if(index != -1) + return(false); + + insert(info); + return(true); +} + +bool Selection::getInfo(Point2I pos, GridInfo & info) +{ + + S32 index = lookup(pos); + if(index == -1) + return(false); + + info = (*this)[index]; + return(true); +} + +bool Selection::setInfo(GridInfo & info) +{ + S32 index = lookup(info.mGridPoint.gridPos); + if(index == -1) + return(false); + + S32 next = (*this)[index].mNext; + S32 prev = (*this)[index].mPrev; + + (*this)[index] = info; + (*this)[index].mNext = next; + (*this)[index].mPrev = prev; + + return(true); +} + +F32 Selection::getAvgHeight() +{ + if(!size()) + return(0); + + F32 avg = 0.f; + for(U32 i = 0; i < size(); i++) + avg += (*this)[i].mHeight; + + return(avg / size()); +} + +F32 Selection::getMinHeight() +{ + if(!size()) + return(0); + + F32 minHeight = (*this)[0].mHeight; + for(U32 i = 1; i < size(); i++) + minHeight = getMin(minHeight, (*this)[i].mHeight); + + return minHeight; +} + +F32 Selection::getMaxHeight() +{ + if(!size()) + return(0); + + F32 maxHeight = (*this)[0].mHeight; + for(U32 i = 1; i < size(); i++) + maxHeight = getMax(maxHeight, (*this)[i].mHeight); + + return maxHeight; +} + +//------------------------------------------------------------------------------ + +Brush::Brush(TerrainEditor * editor) : + mTerrainEditor(editor) +{ + mSize = mTerrainEditor->getBrushSize(); +} + +const Point2I & Brush::getPosition() +{ + return(mGridPoint.gridPos); +} + +const GridPoint & Brush::getGridPoint() +{ + return mGridPoint; +} + +void Brush::setPosition(const Point3F & pos) +{ + mTerrainEditor->worldToGrid(pos, mGridPoint); + update(); +} + +void Brush::setPosition(const Point2I & pos) +{ + mGridPoint.gridPos = pos; + update(); +} + +//------------------------------------------------------------------------------ + +void Brush::update() +{ + rebuild(); +} + +//------------------------------------------------------------------------------ + +void BoxBrush::rebuild() +{ + reset(); + Filter filter; + filter.set(1, &mTerrainEditor->mSoftSelectFilter); + // + // mSize should always be odd. + + S32 centerX = (mSize.x - 1) / 2; + S32 centerY = (mSize.y - 1) / 2; + + F32 xFactorScale = F32(centerX) / (F32(centerX) + 0.5); + F32 yFactorScale = F32(centerY) / (F32(centerY) + 0.5); + + const F32 softness = mTerrainEditor->getBrushSoftness(); + const F32 pressure = mTerrainEditor->getBrushPressure(); + + for(S32 x = 0; x < mSize.x; x++) + { + for(S32 y = 0; y < mSize.y; y++) + { + Vector infos; + GridPoint gridPoint = mGridPoint; + gridPoint.gridPos.set(mGridPoint.gridPos.x + x - centerX, mGridPoint.gridPos.y + y - centerY); + + mTerrainEditor->getGridInfos(gridPoint, infos); + + F32 xFactor = 0.0f; + if(centerX > 0) + xFactor = (mAbs(centerX - x) / F32(centerX)) * xFactorScale; + + F32 yFactor = 0.0f; + if(centerY > 0) + yFactor = (mAbs(centerY - y) / F32(centerY)) * yFactorScale; + + for (U32 z = 0; z < infos.size(); z++) + { + infos[z].mWeight = pressure * + mLerp( infos[z].mWeight, filter.getValue(xFactor > yFactor ? xFactor : yFactor), softness ); + + push_back(infos[z]); + } + } + } +} + +void BoxBrush::render(Vector & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone) const +{ + vertexBuffer.setSize(size() * 5); + + verts = 5; + elems = 4; + prims = size(); + + ColorF color; + U32 bindex; + F32 weight[4]; + U32 vindex = 0; + for(U32 i=0; igridToWorld((*this)[bindex].mGridPoint, verts[0].point); + weight[0] = (*this)[bindex].mWeight; + mTerrainEditor->gridToWorld((*this)[bindex+1].mGridPoint, verts[1].point); + weight[1] = (*this)[bindex+1].mWeight; + bindex = (i+1)*mSize.x + j; + mTerrainEditor->gridToWorld((*this)[bindex+1].mGridPoint, verts[2].point); + weight[2] = (*this)[bindex+1].mWeight; + mTerrainEditor->gridToWorld((*this)[bindex].mGridPoint, verts[3].point); + weight[3] = (*this)[bindex].mWeight; + + for(U32 k=0; k<4; ++k) + { + if( !mTerrainEditor->mRenderSolidBrush ) + { + if ( weight[k] < 0.f || weight[k] > 1.f ) + color = inColorFull; + else + color.interpolate(inColorNone, inColorFull, weight[k] ); + } + else + { + color = inColorFull; + } + + verts[k].color = color; + } + + verts[4].point = verts[0].point; + verts[4].color = verts[0].color; + + vindex += 5; + } + } +} + +//------------------------------------------------------------------------------ + +void EllipseBrush::rebuild() +{ + reset(); + mRenderList.setSize(mSize.x*mSize.y); + Point3F center(F32(mSize.x - 1) / 2, F32(mSize.y - 1) / 2, 0); + Filter filter; + filter.set(1, &mTerrainEditor->mSoftSelectFilter); + + // a point is in a circle if: + // x^2 + y^2 <= r^2 + // a point is in an ellipse if: + // (ax)^2 + (by)^2 <= 1 + // where a = 1/halfEllipseWidth and b = 1/halfEllipseHeight + + // for a soft-selected ellipse, + // the factor is simply the filtered: ((ax)^2 + (by)^2) + + F32 a = 1 / (F32(mSize.x) * 0.5); + F32 b = 1 / (F32(mSize.y) * 0.5); + + const F32 softness = mTerrainEditor->getBrushSoftness(); + const F32 pressure = mTerrainEditor->getBrushPressure(); + + for(S32 x = 0; x < mSize.x; x++) + { + for(S32 y = 0; y < mSize.y; y++) + { + F32 xp = center.x - x; + F32 yp = center.y - y; + + F32 factor = (a * a * xp * xp) + (b * b * yp * yp); + if(factor > 1) + { + mRenderList[x*mSize.x+y] = -1; + continue; + } + + Vector infos; + GridPoint gridPoint = mGridPoint; + gridPoint.gridPos.set((S32)(mGridPoint.gridPos.x + x - (S32)center.x), (S32)(mGridPoint.gridPos.y + y - (S32)center.y)); + + mTerrainEditor->getGridInfos(gridPoint, infos); + + for (U32 z = 0; z < infos.size(); z++) + { + infos[z].mWeight = pressure * mLerp( infos[z].mWeight, filter.getValue( factor ), softness ); + push_back(infos[z]); + } + + mRenderList[x*mSize.x+y] = size()-1; + } + } +} + +void EllipseBrush::render(Vector & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone) const +{ + vertexBuffer.setSize(size() * 5); + + verts = 5; + elems = 4; + prims = 0; + + ColorF color; + F32 weight[4]; + U32 vindex = 0; + for(U32 i=0; igridToWorld((*this)[mRenderList[i*mSize.x+j]].mGridPoint, verts[0].point); + weight[0] = (*this)[mRenderList[i*mSize.x+j]].mWeight; + + mTerrainEditor->gridToWorld((*this)[mRenderList[i*mSize.x+j+1]].mGridPoint, verts[1].point); + weight[1] = (*this)[mRenderList[i*mSize.x+j+1]].mWeight; + + mTerrainEditor->gridToWorld((*this)[mRenderList[(i+1)*mSize.x+j+1]].mGridPoint, verts[2].point); + weight[2] = (*this)[mRenderList[(i+1)*mSize.x+j+1]].mWeight; + + mTerrainEditor->gridToWorld((*this)[mRenderList[(i+1)*mSize.x+j]].mGridPoint, verts[3].point); + weight[3] = (*this)[mRenderList[(i+1)*mSize.x+j]].mWeight; + + for(U32 k=0; k<4; ++k) + { + if( !mTerrainEditor->mRenderSolidBrush ) + { + if ( weight[k] < 0.f || weight[k] > 1.f ) + color = inColorFull; + else + color.interpolate(inColorNone, inColorFull, weight[k] ); + } + else + { + color = inColorFull; + } + + verts[k].color = color; + } + + verts[4].point = verts[0].point; + verts[4].color = verts[0].color; + + vindex += 5; + ++prims; + } + } +} + +//------------------------------------------------------------------------------ + +SelectionBrush::SelectionBrush(TerrainEditor * editor) : + Brush(editor) +{ + //... grab the current selection +} + +void SelectionBrush::rebuild() +{ + reset(); + //... move the selection +} + +void SelectionBrush::render(Vector & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone) const +{ + //... render the selection +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +TerrainEditor::TerrainEditor() : + mActiveTerrain(0), + mMousePos(0,0,0), + mMouseBrush(0), + mInAction(false), + mUndoSel(0), + mGridUpdateMin( S32_MAX, S32_MAX ), + mGridUpdateMax( 0, 0 ), + mMaxBrushSize(48,48), + mNeedsGridUpdate( false ), + mNeedsMaterialUpdate( false ) +{ + VECTOR_SET_ASSOCIATION(mActions); + + // + resetCurrentSel(); + + // + mBrushPressure = 1.0f; + mBrushSize.set(1,1); + mBrushSoftness = 1.0f; + mBrushChanged = true; + mMouseBrush = new BoxBrush(this); + mMouseDownSeq = 0; + mIsDirty = false; + mIsMissionDirty = false; + mPaintIndex = -1; + + // add in all the actions here.. + mActions.push_back(new SelectAction(this)); + mActions.push_back(new DeselectAction(this)); + mActions.push_back(new ClearAction(this)); + mActions.push_back(new SoftSelectAction(this)); + mActions.push_back(new OutlineSelectAction(this)); + mActions.push_back(new PaintMaterialAction(this)); + mActions.push_back(new ClearMaterialsAction(this)); + mActions.push_back(new RaiseHeightAction(this)); + mActions.push_back(new LowerHeightAction(this)); + mActions.push_back(new SetHeightAction(this)); + mActions.push_back(new SetEmptyAction(this)); + mActions.push_back(new ClearEmptyAction(this)); + mActions.push_back(new ScaleHeightAction(this)); + mActions.push_back(new BrushAdjustHeightAction(this)); + mActions.push_back(new AdjustHeightAction(this)); + mActions.push_back(new FlattenHeightAction(this)); + mActions.push_back(new SmoothHeightAction(this)); + mActions.push_back(new PaintNoiseAction(this)); + //mActions.push_back(new ThermalErosionAction(this)); + + + // set the default action + mCurrentAction = mActions[0]; + mRenderBrush = mCurrentAction->useMouseBrush(); + + // persist data defaults + mRenderBorder = true; + mBorderHeight = 10; + mBorderFillColor.set(0,255,0,20); + mBorderFrameColor.set(0,255,0,128); + mBorderLineMode = false; + mSelectionHidden = false; + mRenderVertexSelection = false; + mRenderSolidBrush = false; + mProcessUsesBrush = false; + mCurrentCursor = NULL; + mCursorVisible = true; + + // + mAdjustHeightVal = 10; + mSetHeightVal = 100; + mScaleVal = 1; + mSmoothFactor = 0.1f; + mNoiseFactor = 1.0f; + mMaterialGroup = 0; + mSoftSelectRadius = 50.f; + mAdjustHeightMouseScale = 0.1f; + + mSoftSelectDefaultFilter = StringTable->insert("1.000000 0.833333 0.666667 0.500000 0.333333 0.166667 0.000000"); + mSoftSelectFilter = mSoftSelectDefaultFilter; + + mSlopeMinAngle = 0.0f; + mSlopeMaxAngle = 90.0f; +} + +TerrainEditor::~TerrainEditor() +{ + // mouse + delete mMouseBrush; + + // terrain actions + U32 i; + for(i = 0; i < mActions.size(); i++) + delete mActions[i]; + + // undo stuff + delete mUndoSel; +} + +//------------------------------------------------------------------------------ + +TerrainAction * TerrainEditor::lookupAction(const char * name) +{ + for(U32 i = 0; i < mActions.size(); i++) + if(!dStricmp(mActions[i]->getName(), name)) + return(mActions[i]); + return(0); +} + +//------------------------------------------------------------------------------ + +bool TerrainEditor::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + SimObject * obj = Sim::findObject("EditorArrowCursor"); + if(!obj) + { + Con::errorf(ConsoleLogEntry::General, "TerrainEditor::onAdd: failed to load cursor"); + return(false); + } + + mDefaultCursor = dynamic_cast(obj); + + GFXStateBlockDesc desc; + desc.setZReadWrite( false ); + desc.zWriteEnable = false; + desc.setCullMode( GFXCullNone ); + desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendDestAlpha ); + mStateBlock = GFX->createStateBlock( desc ); + + return true; +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::onDeleteNotify(SimObject * object) +{ + Parent::onDeleteNotify(object); + + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + if(mTerrainBlocks[i] != dynamic_cast(object)) + continue; + + if (mTerrainBlocks[i] == mActiveTerrain) + mActiveTerrain = NULL; + } +} + +void TerrainEditor::setCursor(GuiCursor * cursor) +{ + mCurrentCursor = cursor ? cursor : mDefaultCursor; +} + +//------------------------------------------------------------------------------ + +TerrainBlock* TerrainEditor::getClientTerrain( TerrainBlock *serverTerrain ) const +{ + if ( !serverTerrain && !mActiveTerrain ) + return NULL; + + serverTerrain = mActiveTerrain; // WEIRD STUFF! + + return dynamic_cast( serverTerrain->getClientObject() ); +} + +//------------------------------------------------------------------------------ + +bool TerrainEditor::isMainTile(const Point2I & gPos) const +{ + return true; + + // TODO: How importiant is this... is this to do with + // tiled terrains or to deal with megaterrain? + /* + Point2I testGridPos = gPos; + + if (!dStrcmp(getCurrentAction(),"paintMaterial")) + { + if (testGridPos.x == (1 << TerrainBlock::BlockShift)) + testGridPos.x--; + if (testGridPos.y == (1 << TerrainBlock::BlockShift)) + testGridPos.y--; + } + + return (!(testGridPos.x >> TerrainBlock::BlockShift || testGridPos.y >> TerrainBlock::BlockShift)); + */ +} + +TerrainBlock* TerrainEditor::getTerrainUnderWorldPoint(const Point3F & wPos) +{ + // Cast a ray straight down from the world position and see which + // Terrain is the closest to our starting point + Point3F startPnt = wPos; + Point3F endPnt = wPos + Point3F(0.0f, 0.0f, -1000.0f); + + S32 blockIndex = -1; + F32 nearT = 1.0f; + + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + Point3F tStartPnt, tEndPnt; + + mTerrainBlocks[i]->getWorldTransform().mulP(startPnt, &tStartPnt); + mTerrainBlocks[i]->getWorldTransform().mulP(endPnt, &tEndPnt); + + RayInfo ri; + if (mTerrainBlocks[i]->castRayI(tStartPnt, tEndPnt, &ri, true)) + { + if (ri.t < nearT) + { + blockIndex = i; + nearT = ri.t; + } + } + } + + if (blockIndex > -1) + return mTerrainBlocks[blockIndex]; + + return NULL; +} + +bool TerrainEditor::gridToWorld(const GridPoint & gPoint, Point3F & wPos) +{ + const MatrixF & mat = gPoint.terrainBlock->getTransform(); + Point3F origin; + mat.getColumn(3, &origin); + + wPos.x = gPoint.gridPos.x * gPoint.terrainBlock->getSquareSize() + origin.x; + wPos.y = gPoint.gridPos.y * gPoint.terrainBlock->getSquareSize() + origin.y; + wPos.z = getGridHeight(gPoint) + origin.z; + + return isMainTile(gPoint.gridPos); +} + +bool TerrainEditor::gridToWorld(const Point2I & gPos, Point3F & wPos, TerrainBlock* terrain) +{ + GridPoint gridPoint; + gridPoint.gridPos = gPos; + gridPoint.terrainBlock = terrain; + + return gridToWorld(gridPoint, wPos); +} + +bool TerrainEditor::worldToGrid(const Point3F & wPos, GridPoint & gPoint) +{ + // If the grid point TerrainBlock is NULL then find the closest Terrain underneath that + // point - pad a little upward in case our incoming point already lies exactly on the terrain + if (!gPoint.terrainBlock) + gPoint.terrainBlock = getTerrainUnderWorldPoint(wPos + Point3F(0.0f, 0.0f, 0.05f)); + + if (gPoint.terrainBlock == NULL) + return false; + + const MatrixF & worldMat = gPoint.terrainBlock->getWorldTransform(); + Point3F tPos = wPos; + worldMat.mulP(tPos); + + F32 squareSize = (F32) gPoint.terrainBlock->getSquareSize(); + F32 halfSquareSize = squareSize / 2; + + F32 x = (tPos.x + halfSquareSize) / squareSize; + F32 y = (tPos.y + halfSquareSize) / squareSize; + + gPoint.gridPos.x = (S32)mFloor(x); + gPoint.gridPos.y = (S32)mFloor(y); + + return isMainTile(gPoint.gridPos); +} + +bool TerrainEditor::worldToGrid(const Point3F & wPos, Point2I & gPos, TerrainBlock* terrain) +{ + GridPoint gridPoint; + gridPoint.terrainBlock = terrain; + + bool ret = worldToGrid(wPos, gridPoint); + + gPos = gridPoint.gridPos; + + return ret; +} + +bool TerrainEditor::gridToCenter(const Point2I & gPos, Point2I & cPos) const +{ + // TODO: What is this for... megaterrain or tiled terrains? + cPos.x = gPos.x; // & TerrainBlock::BlockMask; + cPos.y = gPos.y;// & TerrainBlock::BlockMask; + + //if (gPos.x == TerrainBlock::BlockSize) + // cPos.x = gPos.x; + //if (gPos.y == TerrainBlock::BlockSize) + // cPos.y = gPos.y; + + return isMainTile(gPos); +} + +//------------------------------------------------------------------------------ + +//bool TerrainEditor::getGridInfo(const Point3F & wPos, GridInfo & info) +//{ +// Point2I gPos; +// worldToGrid(wPos, gPos); +// return getGridInfo(gPos, info); +//} + +bool TerrainEditor::getGridInfo(const GridPoint & gPoint, GridInfo & info) +{ + // + info.mGridPoint = gPoint; + info.mMaterial = getGridMaterial(gPoint); + info.mHeight = getGridHeight(gPoint); + info.mWeight = 1.f; + info.mPrimarySelect = true; + info.mMaterialChanged = false; + + Point2I cPos; + gridToCenter(gPoint.gridPos, cPos); + + return isMainTile(gPoint.gridPos); +} + +bool TerrainEditor::getGridInfo(const Point2I & gPos, GridInfo & info, TerrainBlock* terrain) +{ + GridPoint gridPoint; + gridPoint.gridPos = gPos; + gridPoint.terrainBlock = terrain; + + return getGridInfo(gridPoint, info); +} + +void TerrainEditor::getGridInfos(const GridPoint & gPoint, Vector& infos) +{ + // First we test against the brush terrain so that we can + // favor it (this should be the same as the active terrain) + bool foundBrush = false; + + GridInfo baseInfo; + if (getGridInfo(gPoint, baseInfo)) + { + infos.push_back(baseInfo); + + foundBrush = true; + } + + // We are going to need the world position to test against + Point3F wPos; + gridToWorld(gPoint, wPos); + + // Now loop through our terrain blocks and decide which ones hit the point + // If we already found a hit against our brush terrain we only add points + // that are relatively close to the found point + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + // Skip if we've already found the point on the brush terrain + if (foundBrush && mTerrainBlocks[i] == baseInfo.mGridPoint.terrainBlock) + continue; + + // Get our grid position + Point2I gPos; + worldToGrid(wPos, gPos, mTerrainBlocks[i]); + + GridInfo info; + if (getGridInfo(gPos, info, mTerrainBlocks[i])) + { + // Skip adding this if we already found a GridInfo from the brush terrain + // and the resultant world point isn't equivalent + if (foundBrush) + { + // Convert back to world (since the height can be different) + // Possibly use getHeight() here? + Point3F testWorldPt; + gridToWorld(gPos, testWorldPt, mTerrainBlocks[i]); + + if (mFabs( wPos.z - testWorldPt.z ) > 4.0f ) + continue; + } + + infos.push_back(info); + } + } +} + +void TerrainEditor::setGridInfo(const GridInfo & info, bool checkActive) +{ + setGridHeight(info.mGridPoint, info.mHeight); + setGridMaterial(info.mGridPoint, info.mMaterial); +} + +F32 TerrainEditor::getGridHeight(const GridPoint & gPoint) +{ + Point2I cPos; + gridToCenter( gPoint.gridPos, cPos ); + const TerrainFile *file = gPoint.terrainBlock->getFile(); + return fixedToFloat( file->getHeight( cPos.x, cPos.y ) ); +} + +void TerrainEditor::gridUpdateComplete( bool materialChanged ) +{ + // TODO: This updates all terrains and not just the ones + // that were changed. We should keep track of the mGridUpdate + // in world space and transform it into terrain space. + + if(mGridUpdateMin.x <= mGridUpdateMax.x) + { + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + TerrainBlock *clientTerrain = getClientTerrain( mTerrainBlocks[i] ); + if ( materialChanged ) + clientTerrain->updateGridMaterials(mGridUpdateMin, mGridUpdateMax); + + mTerrainBlocks[i]->updateGrid(mGridUpdateMin, mGridUpdateMax); + clientTerrain->updateGrid(mGridUpdateMin, mGridUpdateMax); + } + } + + mGridUpdateMin.set( S32_MAX, S32_MAX ); + mGridUpdateMax.set( 0, 0 ); + mNeedsGridUpdate = false; +} + +void TerrainEditor::materialUpdateComplete() +{ + if(mGridUpdateMin.x <= mGridUpdateMax.x) + { + TerrainBlock * clientTerrain = getClientTerrain(mActiveTerrain); + clientTerrain->updateGridMaterials(mGridUpdateMin, mGridUpdateMax); + //mActiveTerrain->updateGrid(mGridUpdateMin, mGridUpdateMax); + } + mGridUpdateMin.set( S32_MAX, S32_MAX ); + mGridUpdateMax.set( 0, 0 ); + mNeedsMaterialUpdate = false; +} + +void TerrainEditor::setGridHeight(const GridPoint & gPoint, const F32 height) +{ + Point2I cPos; + gridToCenter(gPoint.gridPos, cPos); + + mGridUpdateMin.setMin( cPos ); + mGridUpdateMax.setMax( cPos ); + + gPoint.terrainBlock->setHeight(cPos, height); +} + +U8 TerrainEditor::getGridMaterial( const GridPoint &gPoint ) const +{ + Point2I cPos; + gridToCenter( gPoint.gridPos, cPos ); + const TerrainFile *file = gPoint.terrainBlock->getFile(); + return file->getLayerIndex( cPos.x, cPos.y ); +} + +void TerrainEditor::setGridMaterial( const GridPoint &gPoint, U8 index ) +{ + Point2I cPos; + gridToCenter( gPoint.gridPos, cPos ); + TerrainFile *file = gPoint.terrainBlock->getFile(); + + // If we changed the empty state then we need + // to do a grid update as well. + U8 currIndex = file->getLayerIndex( cPos.x, cPos.y ); + if ( ( currIndex == (U8)-1 && index != (U8)-1 ) || + ( currIndex != (U8)-1 && index == (U8)-1 ) ) + { + mGridUpdateMin.setMin( cPos ); + mGridUpdateMax.setMax( cPos ); + mNeedsGridUpdate = true; + } + + file->setLayerIndex( cPos.x, cPos.y, index ); +} + +//------------------------------------------------------------------------------ + +TerrainBlock* TerrainEditor::collide(const Gui3DMouseEvent & event, Point3F & pos) +{ + if (mTerrainBlocks.size() == 0) + return NULL; + + // call the terrain block's ray collision routine directly + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + S32 blockIndex = -1; + F32 nearT = 1.0f; + + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + Point3F tStartPnt, tEndPnt; + + mTerrainBlocks[i]->getWorldTransform().mulP(startPnt, &tStartPnt); + mTerrainBlocks[i]->getWorldTransform().mulP(endPnt, &tEndPnt); + + RayInfo ri; + if (mTerrainBlocks[i]->castRayI(tStartPnt, tEndPnt, &ri, true)) + { + if (ri.t < nearT) + { + blockIndex = i; + nearT = ri.t; + } + } + } + + if (blockIndex > -1) + { + pos.interpolate(startPnt, endPnt, nearT); + + return mTerrainBlocks[blockIndex]; + } + + return NULL; +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::updateGuiInfo() +{ + char buf[128]; + + // mouse num grids + // mouse avg height + // selection num grids + // selection avg height + dSprintf(buf, sizeof(buf), "%d %g %g %g %d %g", + mMouseBrush->size(), mMouseBrush->getMinHeight(), + mMouseBrush->getAvgHeight(), mMouseBrush->getMaxHeight(), + mDefaultSel.size(), mDefaultSel.getAvgHeight()); + Con::executef(this, "onGuiUpdate", buf); + + // If the brush setup has changed send out + // a notification of that! + if ( mBrushChanged && isMethod( "onBrushChanged" ) ) + { + mBrushChanged = false; + Con::executef( this, "onBrushChanged" ); + } +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::renderScene(const RectI &) +{ + if ( mNeedsGridUpdate ) + gridUpdateComplete( mNeedsMaterialUpdate ); + else if ( mNeedsMaterialUpdate ) + materialUpdateComplete(); + + if(mTerrainBlocks.size() == 0) + return; + + if(!mSelectionHidden) + renderSelection(mDefaultSel, ColorF(1,0,0), ColorF(0,1,0), ColorF(0,0,1), ColorF(0,0,1), true, false); + + if(mRenderBrush && mMouseBrush->size()) + renderBrush(*mMouseBrush, ColorF(0,1,0), ColorF(1,0,0), ColorF(0,0,1), ColorF(0,0,1), false, true); + + if(mRenderBorder) + renderBorder(); +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::renderSelection( const Selection & sel, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone, bool renderFill, bool renderFrame ) +{ + // Draw nothing if nothing selected. + if(sel.size() == 0) + return; + + Vector vertexBuffer; + ColorF color; + ColorI iColor; + + vertexBuffer.setSize(sel.size() * 5); + + F32 squareSize = ( mActiveTerrain ) ? mActiveTerrain->getSquareSize() : 1; + + // 'RenderVertexSelection' looks really bad so just always use the good one. + if( false && mRenderVertexSelection) + { + + for(U32 i = 0; i < sel.size(); i++) + { + Point3F wPos; + bool center = gridToWorld(sel[i].mGridPoint, wPos); + + F32 weight = sel[i].mWeight; + + if(center) + { + if ( weight < 0.f || weight > 1.f ) + color = inColorFull; + else + color.interpolate( inColorNone, inColorFull, weight ); + } + else + { + if ( weight < 0.f || weight > 1.f) + color = outColorFull; + else + color.interpolate( outColorFull, outColorNone, weight ); + } + // + iColor = color; + + GFXVertexPC *verts = &(vertexBuffer[i * 5]); + + verts[0].point = wPos + Point3F(-squareSize, -squareSize, 0); + verts[0].color = iColor; + verts[1].point = wPos + Point3F( squareSize, -squareSize, 0); + verts[1].color = iColor; + verts[2].point = wPos + Point3F( squareSize, squareSize, 0); + verts[2].color = iColor; + verts[3].point = wPos + Point3F(-squareSize, squareSize, 0); + verts[3].color = iColor; + verts[4].point = verts[0].point; + verts[4].color = iColor; + } + } + else + { + // walk the points in the selection + for(U32 i = 0; i < sel.size(); i++) + { + Point2I gPos = sel[i].mGridPoint.gridPos; + + GFXVertexPC *verts = &(vertexBuffer[i * 5]); + + bool center = gridToWorld(sel[i].mGridPoint, verts[0].point); + gridToWorld(Point2I(gPos.x + 1, gPos.y), verts[1].point, sel[i].mGridPoint.terrainBlock); + gridToWorld(Point2I(gPos.x + 1, gPos.y + 1), verts[2].point, sel[i].mGridPoint.terrainBlock); + gridToWorld(Point2I(gPos.x, gPos.y + 1), verts[3].point, sel[i].mGridPoint.terrainBlock); + verts[4].point = verts[0].point; + + F32 weight = sel[i].mWeight; + + if( !mRenderSolidBrush ) + { + if ( center ) + { + if ( weight < 0.f || weight > 1.f ) + color = inColorFull; + else + color.interpolate(inColorNone, inColorFull, weight ); + } + else + { + if( weight < 0.f || weight > 1.f ) + color = outColorFull; + else + color.interpolate(outColorFull, outColorNone, weight ); + } + + iColor = color; + } + else + { + if ( center ) + { + iColor = inColorNone; + } + else + { + iColor = outColorFull; + } + } + + verts[0].color = iColor; + verts[1].color = iColor; + verts[2].color = iColor; + verts[3].color = iColor; + verts[4].color = iColor; + } + } + + // Render this bad boy, by stuffing everything into a volatile buffer + // and rendering... + GFXVertexBufferHandle selectionVB(GFX, vertexBuffer.size(), GFXBufferTypeStatic); + + selectionVB.lock(0, vertexBuffer.size()); + + // Copy stuff + dMemcpy((void*)&selectionVB[0], (void*)&vertexBuffer[0], sizeof(GFXVertexPC) * vertexBuffer.size()); + + selectionVB.unlock(); + + GFX->setupGenericShaders(); + GFX->setStateBlock( mStateBlock ); + GFX->setVertexBuffer(selectionVB); + + if(renderFill) + for(U32 i=0; i < sel.size(); i++) + GFX->drawPrimitive( GFXTriangleFan, i*5, 4); + + if(renderFrame) + for(U32 i=0; i < sel.size(); i++) + GFX->drawPrimitive( GFXLineStrip , i*5, 4); + +} + +void TerrainEditor::renderBrush( const Brush & brush, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone, bool renderFill, bool renderFrame ) +{ + // Draw nothing if nothing selected. + if(brush.size() == 0) + return; + + S32 verticesPerPrimitive; + S32 elementsPerPrimitive; + S32 numPrimitives = 0; + + Vector vertexBuffer; + + if(brush.size() == 1) + { + Point2I gPos = brush[0].mGridPoint.gridPos; + + vertexBuffer.setSize(6); + GFXVertexPC *verts = &(vertexBuffer[0]); + + Point3F centerPos; + bool center = gridToWorld(brush[0].mGridPoint, centerPos); + + gridToWorld(Point2I(gPos.x - 1, gPos.y), verts[0].point, brush[0].mGridPoint.terrainBlock); + verts[1].point = centerPos; + gridToWorld(Point2I(gPos.x, gPos.y + 1), verts[2].point, brush[0].mGridPoint.terrainBlock); + + gridToWorld(Point2I(gPos.x + 1, gPos.y), verts[3].point, brush[0].mGridPoint.terrainBlock); + verts[4].point = centerPos; + gridToWorld(Point2I(gPos.x, gPos.y - 1), verts[5].point, brush[0].mGridPoint.terrainBlock); + + F32 weight = brush[0].mWeight; + + ColorF color; + ColorI iColor; + if ( center ) + { + if ( weight < 0.f || weight > 1.f ) + color = inColorFull; + else + color.interpolate(inColorNone, inColorFull, weight ); + } + else + { + if( weight < 0.f || weight > 1.f ) + color = outColorFull; + else + color.interpolate(outColorFull, outColorNone, weight ); + } + + iColor = color; + verts[1].color = iColor; + verts[4].color = iColor; + iColor = inColorNone; + verts[0].color = iColor; + verts[2].color = iColor; + verts[3].color = iColor; + verts[5].color = iColor; + + verticesPerPrimitive = 3; + elementsPerPrimitive = 2; + numPrimitives = 2; + } + else + { + brush.render(vertexBuffer, verticesPerPrimitive, elementsPerPrimitive, numPrimitives, inColorFull, inColorNone, outColorFull, outColorNone); + } + + // Render this bad boy, by stuffing everything into a volatile buffer + // and rendering... + GFXVertexBufferHandle selectionVB(GFX, vertexBuffer.size(), GFXBufferTypeStatic); + + selectionVB.lock(0, vertexBuffer.size()); + + // Copy stuff + dMemcpy((void*)&selectionVB[0], (void*)&vertexBuffer[0], sizeof(GFXVertexPC) * vertexBuffer.size()); + + selectionVB.unlock(); + + GFX->setupGenericShaders(); + GFX->setStateBlock( mStateBlock ); + GFX->setVertexBuffer(selectionVB); + + if(renderFill) + for(U32 i=0; i < numPrimitives; i++) + GFX->drawPrimitive( GFXTriangleFan, i*verticesPerPrimitive, elementsPerPrimitive); + + if(renderFrame) + for(U32 i=0; i < numPrimitives; i++) + GFX->drawPrimitive( GFXLineStrip , i*verticesPerPrimitive, elementsPerPrimitive); +} + +void TerrainEditor::renderBorder() +{ + // TODO: Disabled rendering the terrain borders... it was + // very annoying getting a fullscreen green tint on things. + // + // We should consider killing this all together or coming + // up with a new technique. + /* + Point2I pos(0,0); + Point2I dir[4] = { + Point2I(1,0), + Point2I(0,1), + Point2I(-1,0), + Point2I(0,-1) + }; + + GFX->setStateBlock( mStateBlock ); + + // + if(mBorderLineMode) + { + PrimBuild::color(mBorderFrameColor); + + PrimBuild::begin( GFXLineStrip, TerrainBlock::BlockSize * 4 + 1 ); + for(U32 i = 0; i < 4; i++) + { + for(U32 j = 0; j < TerrainBlock::BlockSize; j++) + { + Point3F wPos; + gridToWorld(pos, wPos, mActiveTerrain); + PrimBuild::vertex3fv( wPos ); + pos += dir[i]; + } + } + + Point3F wPos; + gridToWorld(Point2I(0,0), wPos, mActiveTerrain); + PrimBuild::vertex3fv( wPos ); + PrimBuild::end(); + } + else + { + GridSquare * gs = mActiveTerrain->findSquare(TerrainBlock::BlockShift, Point2I(0,0)); + F32 height = F32(gs->maxHeight) * 0.03125f + mBorderHeight; + + const MatrixF & mat = mActiveTerrain->getTransform(); + Point3F pos; + mat.getColumn(3, &pos); + + Point2F min(pos.x, pos.y); + Point2F max(pos.x + TerrainBlock::BlockSize * mActiveTerrain->getSquareSize(), + pos.y + TerrainBlock::BlockSize * mActiveTerrain->getSquareSize()); + + ColorI & a = mBorderFillColor; + ColorI & b = mBorderFrameColor; + + for(U32 i = 0; i < 2; i++) + { + // + if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); } + + PrimBuild::vertex3f(min.x, min.y, 0); + PrimBuild::vertex3f(max.x, min.y, 0); + PrimBuild::vertex3f(max.x, min.y, height); + PrimBuild::vertex3f(min.x, min.y, height); + if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f ); + PrimBuild::end(); + + // + if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); } + PrimBuild::vertex3f(min.x, max.y, 0); + PrimBuild::vertex3f(max.x, max.y, 0); + PrimBuild::vertex3f(max.x, max.y, height); + PrimBuild::vertex3f(min.x, max.y, height); + if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f ); + PrimBuild::end(); + + // + if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); } + PrimBuild::vertex3f(min.x, min.y, 0); + PrimBuild::vertex3f(min.x, max.y, 0); + PrimBuild::vertex3f(min.x, max.y, height); + PrimBuild::vertex3f(min.x, min.y, height); + if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f ); + PrimBuild::end(); + + // + if(i){ PrimBuild::color(a); PrimBuild::begin( GFXTriangleFan, 4 ); } else { PrimBuild::color(b); PrimBuild::begin( GFXLineStrip, 5 ); } + PrimBuild::vertex3f(max.x, min.y, 0); + PrimBuild::vertex3f(max.x, max.y, 0); + PrimBuild::vertex3f(max.x, max.y, height); + PrimBuild::vertex3f(max.x, min.y, height); + if(!i) PrimBuild::vertex3f( min.x, min.y, 0.f ); + PrimBuild::end(); + } + } + */ +} + +void TerrainEditor::submitUndo( Selection *sel ) +{ + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "TerrainEditor::submitUndo() - EUndoManager not found!" ); + return; + } + + // Create and submit the action. + TerrainEditorUndoAction *action = new TerrainEditorUndoAction( "Terrain Editor Action" ); + action->mSel = sel; + action->mTerrainEditor = this; + undoMan->addAction( action ); + + // Mark the editor as dirty! + setDirty(); +} + +void TerrainEditor::TerrainEditorUndoAction::undo() +{ + // NOTE: This function also handles TerrainEditorUndoAction::redo(). + + bool materialChanged = false; + + for (U32 i = 0; i < mSel->size(); i++) + { + // Grab the current grid info for this point. + GridInfo info; + mTerrainEditor->getGridInfo( (*mSel)[i].mGridPoint, info ); + info.mMaterialChanged = (*mSel)[i].mMaterialChanged; + + materialChanged |= info.mMaterialChanged; + + // Restore the previous grid info. + mTerrainEditor->setGridInfo( (*mSel)[i] ); + + // Save the old grid info so we can + // restore it later. + (*mSel)[i] = info; + } + + // Mark the editor as dirty! + mTerrainEditor->setDirty(); + mTerrainEditor->gridUpdateComplete( materialChanged ); +} + + +class TerrainProcessActionEvent : public SimEvent +{ + U32 mSequence; +public: + TerrainProcessActionEvent(U32 seq) + { + mSequence = seq; + } + void process(SimObject *object) + { + ((TerrainEditor *) object)->processActionTick(mSequence); + } +}; + +void TerrainEditor::processActionTick(U32 sequence) +{ + if(mMouseDownSeq == sequence) + { + Sim::postEvent(this, new TerrainProcessActionEvent(mMouseDownSeq), Sim::getCurrentTime() + 30); + mCurrentAction->process(mMouseBrush, mLastEvent, false, TerrainAction::Update); + } +} + +bool TerrainEditor::onInputEvent(const InputEventInfo & event) +{ + /* + if ( mRightMousePassThru && + event.deviceType == KeyboardDeviceType && + event.objType == SI_KEY && + event.objInst == KEY_TAB && + event.action == SI_MAKE ) + { + if ( isMethod( "onToggleToolWindows" ) ) + Con::executef( this, "onToggleToolWindows" ); + } + */ + + return Parent::onInputEvent( event ); +} + +void TerrainEditor::on3DMouseDown(const Gui3DMouseEvent & event) +{ + if(mTerrainBlocks.size() == 0) + return; + + if (!dStrcmp(getCurrentAction(),"paintMaterial")) + { + Point3F pos; + TerrainBlock* hitTerrain = collide(event, pos); + + if(!hitTerrain) + return; + + // Set the active terrain + bool changed = mActiveTerrain != hitTerrain; + mActiveTerrain = hitTerrain; + + if (changed) + { + Con::executef(this, "onActiveTerrainChange", Con::getIntArg(hitTerrain->getId())); + mMouseBrush->setTerrain(mActiveTerrain); + if(mRenderBrush) + mCursorVisible = false; + mMousePos = pos; + + mMouseBrush->setPosition(mMousePos); + return; + } + } + + + mSelectionLocked = false; + + mouseLock(); + mMouseDownSeq++; + mUndoSel = new Selection; + mCurrentAction->process(mMouseBrush, event, true, TerrainAction::Begin); + // process on ticks - every 30th of a second. + Sim::postEvent(this, new TerrainProcessActionEvent(mMouseDownSeq), Sim::getCurrentTime() + 30); +} + +void TerrainEditor::on3DMouseMove(const Gui3DMouseEvent & event) +{ + if(mTerrainBlocks.size() == 0) + return; + + //if ( !isFirstResponder() ) + //setFirstResponder(); + + Point3F pos; + TerrainBlock* hitTerrain = collide(event, pos); + + if(!hitTerrain) + { + mMouseBrush->reset(); + mCursorVisible = true; + } + else + { + // We do not change the active terrain as the mouse moves when + // in painting mode. This is because it causes the material + // window to change as you cursor over to it. + if ( dStrcmp(getCurrentAction(),"paintMaterial") != 0 ) + { + // Set the active terrain + bool changed = mActiveTerrain != hitTerrain; + mActiveTerrain = hitTerrain; + + if (changed) + Con::executef(this, "onActiveTerrainChange", Con::getIntArg(hitTerrain->getId())); + } + + // + if(mRenderBrush) + mCursorVisible = false; + mMousePos = pos; + + mMouseBrush->setTerrain(mActiveTerrain); + mMouseBrush->setPosition(mMousePos); + } +} + +void TerrainEditor::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + if(mTerrainBlocks.size() == 0) + return; + + if(!isMouseLocked()) + return; + + Point3F pos; + if(!mSelectionLocked) + { + if(!collide(event, pos)) + { + mMouseBrush->reset(); + return; + } + } + + // check if the mouse has actually moved in grid space + bool selChanged = false; + if(!mSelectionLocked) + { + Point2I gMouse; + Point2I gLastMouse; + worldToGrid(pos, gMouse); + worldToGrid(mMousePos, gLastMouse); + + // + mMousePos = pos; + mMouseBrush->setPosition(mMousePos); + + selChanged = gMouse != gLastMouse; + } + if(selChanged) + mCurrentAction->process(mMouseBrush, event, true, TerrainAction::Update); +} + +void TerrainEditor::on3DMouseUp(const Gui3DMouseEvent & event) +{ + if(mTerrainBlocks.size() == 0) + return; + + if(isMouseLocked()) + { + mouseUnlock(); + mMouseDownSeq++; + mCurrentAction->process(mMouseBrush, event, false, TerrainAction::End); + setCursor(0); + + if(mUndoSel->size()) + submitUndo( mUndoSel ); + else + delete mUndoSel; + + mUndoSel = 0; + mInAction = false; + } +} + +void TerrainEditor::on3DMouseWheelDown( const Gui3DMouseEvent & event ) +{ + if ( event.modifier & SI_PRIMARY_CTRL && event.modifier & SI_SHIFT ) + setBrushPressure( mBrushPressure - 0.1f ); + + else if ( event.modifier & SI_SHIFT ) + setBrushSoftness( mBrushSoftness + 0.05f ); + + else if ( event.modifier & SI_PRIMARY_CTRL ) + { + Point2I newBrush = getBrushSize() - Point2I(1,1); + setBrushSize( newBrush.x, newBrush.y ); + } + else if ( event.modifier & SI_SHIFT ) + setBrushSoftness( mBrushSoftness - 0.05f ); +} + +void TerrainEditor::on3DMouseWheelUp( const Gui3DMouseEvent & event ) +{ + if ( event.modifier & SI_PRIMARY_CTRL && event.modifier & SI_SHIFT ) + setBrushPressure( mBrushPressure + 0.1f ); + + else if ( event.modifier & SI_SHIFT ) + setBrushSoftness( mBrushSoftness - 0.05f ); + + else if( event.modifier & SI_PRIMARY_CTRL ) + { + Point2I newBrush = getBrushSize() + Point2I(1,1); + setBrushSize( newBrush.x, newBrush.y ); + } +} + +void TerrainEditor::getCursor(GuiCursor *&cursor, bool &visible, const GuiEvent &event) +{ + TORQUE_UNUSED(event); + cursor = mCurrentCursor; + visible = mCursorVisible; +} + +//------------------------------------------------------------------------------ +// any console function which depends on a terrainBlock attached to the editor +// should call this +bool checkTerrainBlock(TerrainEditor * object, const char * funcName) +{ + if(!object->terrainBlockValid()) + { + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::%s: not attached to a terrain block!", funcName); + return(false); + } + return(true); +} + +void TerrainEditor::attachTerrain(TerrainBlock *terrBlock) +{ + mActiveTerrain = terrBlock; + + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + if (mTerrainBlocks[i] == terrBlock) + return; + } + + mTerrainBlocks.push_back(terrBlock); +} + +void TerrainEditor::detachTerrain(TerrainBlock *terrBlock) +{ + if (mActiveTerrain == terrBlock) + mActiveTerrain = NULL; //do we want to set this to an existing terrain? + + if (mMouseBrush->getGridPoint().terrainBlock == terrBlock) + mMouseBrush->setTerrain(NULL); + + // reset the brush as its gridinfos may still have references to the old terrain + mMouseBrush->reset(); + + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + if (mTerrainBlocks[i] == terrBlock) + { + mTerrainBlocks.erase_fast(i); + break; + } + } +} + +void TerrainEditor::setBrushType( const char *type ) +{ + if ( mMouseBrush && dStrcmp( mMouseBrush->getType(), type ) == 0 ) + return; + + if(!dStricmp(type, "box")) + { + delete mMouseBrush; + mMouseBrush = new BoxBrush(this); + mBrushChanged = true; + } + else if(!dStricmp(type, "ellipse")) + { + delete mMouseBrush; + mMouseBrush = new EllipseBrush(this); + mBrushChanged = true; + } + else if(!dStricmp(type, "selection")) + { + delete mMouseBrush; + mMouseBrush = new SelectionBrush(this); + mBrushChanged = true; + } + else {} +} + +const char* TerrainEditor::getBrushType() const +{ + if ( mMouseBrush ) + return mMouseBrush->getType(); + + return ""; +} + +void TerrainEditor::setBrushSize( S32 w, S32 h ) +{ + w = mClamp( w, 1, mMaxBrushSize.x ); + h = mClamp( h, 1, mMaxBrushSize.y ); + + if ( w == mBrushSize.x && h == mBrushSize.y ) + return; + + mBrushSize.set( w, h ); + mBrushChanged = true; + + if ( mMouseBrush ) + { + mMouseBrush->setSize( mBrushSize ); + + if ( mMouseBrush->getGridPoint().terrainBlock ) + mMouseBrush->rebuild(); + } +} + +void TerrainEditor::setBrushPressure( F32 pressure ) +{ + pressure = mClampF( pressure, 0.01f, 1.0f ); + + if ( mBrushPressure == pressure ) + return; + + mBrushPressure = pressure; + mBrushChanged = true; + + if ( mMouseBrush && mMouseBrush->getGridPoint().terrainBlock ) + mMouseBrush->rebuild(); +} + +void TerrainEditor::setBrushSoftness( F32 softness ) +{ + softness = mClampF( softness, 0.01f, 1.0f ); + + if ( mBrushSoftness == softness ) + return; + + mBrushSoftness = softness; + mBrushChanged = true; + + if ( mMouseBrush && mMouseBrush->getGridPoint().terrainBlock ) + mMouseBrush->rebuild(); +} + +const char* TerrainEditor::getBrushPos() +{ + AssertFatal(mMouseBrush!=NULL, "TerrainEditor::getBrushPos: no mouse brush!"); + + Point2I pos = mMouseBrush->getPosition(); + char * ret = Con::getReturnBuffer(32); + dSprintf(ret, 32, "%d %d", pos.x, pos.y); + return(ret); +} + +void TerrainEditor::setBrushPos(Point2I pos) +{ + AssertFatal(mMouseBrush!=NULL, "TerrainEditor::setBrushPos: no mouse brush!"); + mMouseBrush->setPosition(pos); +} + +void TerrainEditor::setAction(const char* action) +{ + for(U32 i = 0; i < mActions.size(); i++) + { + if(!dStricmp(mActions[i]->getName(), action)) + { + mCurrentAction = mActions[i]; + + // + mRenderBrush = mCurrentAction->useMouseBrush(); + return; + } + } +} + +const char* TerrainEditor::getActionName(U32 index) +{ + if(index >= mActions.size()) + return(""); + return(mActions[index]->getName()); +} + +const char* TerrainEditor::getCurrentAction() const +{ + return(mCurrentAction->getName()); +} + +S32 TerrainEditor::getNumActions() +{ + return(mActions.size()); +} + +void TerrainEditor::resetSelWeights(bool clear) +{ + // + if(!clear) + { + for(U32 i = 0; i < mDefaultSel.size(); i++) + { + mDefaultSel[i].mPrimarySelect = false; + mDefaultSel[i].mWeight = 1.f; + } + return; + } + + Selection sel; + + U32 i; + for(i = 0; i < mDefaultSel.size(); i++) + { + if(mDefaultSel[i].mPrimarySelect) + { + mDefaultSel[i].mWeight = 1.f; + sel.add(mDefaultSel[i]); + } + } + + mDefaultSel.reset(); + + for(i = 0; i < sel.size(); i++) + mDefaultSel.add(sel[i]); +} + +void TerrainEditor::clearSelection() +{ + mDefaultSel.reset(); +} + +void TerrainEditor::processAction(const char* sAction) +{ + if(!checkTerrainBlock(this, "processAction")) + return; + + TerrainAction * action = mCurrentAction; + if (dStrcmp(sAction, "") != 0) + { + action = lookupAction(sAction); + + if(!action) + { + Con::errorf(ConsoleLogEntry::General, "TerrainEditor::cProcessAction: invalid action name '%s'.", sAction); + return; + } + } + + if(!getCurrentSel()->size() && !mProcessUsesBrush) + return; + + mUndoSel = new Selection; + + Gui3DMouseEvent event; + if(mProcessUsesBrush) + action->process(mMouseBrush, event, true, TerrainAction::Process); + else + action->process(getCurrentSel(), event, true, TerrainAction::Process); + + // check if should delete the undo + if(mUndoSel->size()) + submitUndo( mUndoSel ); + else + delete mUndoSel; + + mUndoSel = 0; +} + +S32 TerrainEditor::getNumTextures() +{ + if(!checkTerrainBlock(this, "getNumTextures")) + return(0); + + // walk all the possible material lists and count them.. + U32 count = 0; + for (U32 t = 0; t < mTerrainBlocks.size(); t++) + count += mTerrainBlocks[t]->getMaterialCount(); + + return count; +} + +void TerrainEditor::markEmptySquares() +{ + if(!checkTerrainBlock(this, "markEmptySquares")) + return; + + // TODO! + /* + // build a list of all the marked interiors + Vector interiors; + U32 mask = InteriorObjectType; + gServerContainer.findObjects(mask, findObjectsCallback, &interiors); + + // walk the terrains and empty any grid which clips to an interior + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + for(U32 x = 0; x < TerrainBlock::BlockSize; x++) + { + for(U32 y = 0; y < TerrainBlock::BlockSize; y++) + { + TerrainBlock::Material * material = mTerrainBlocks[i]->getMaterial(x,y); + material->flags |= ~(TerrainBlock::Material::Empty); + + Point3F a, b; + gridToWorld(Point2I(x,y), a, mTerrainBlocks[i]); + gridToWorld(Point2I(x+1,y+1), b, mTerrainBlocks[i]); + + Box3F box; + box.minExtents = a; + box.maxExtents = b; + + box.minExtents.setMin(b); + box.maxExtents.setMax(a); + + const MatrixF & terrOMat = mTerrainBlocks[i]->getTransform(); + const MatrixF & terrWMat = mTerrainBlocks[i]->getWorldTransform(); + + terrWMat.mulP(box.minExtents); + terrWMat.mulP(box.maxExtents); + + for(U32 i = 0; i < interiors.size(); i++) + { + MatrixF mat = interiors[i]->getWorldTransform(); + mat.scale(interiors[i]->getScale()); + mat.mul(terrOMat); + + U32 waterMark = FrameAllocator::getWaterMark(); + U16* zoneVector = (U16*)FrameAllocator::alloc(interiors[i]->getDetailLevel(0)->getNumZones()); + U32 numZones = 0; + interiors[i]->getDetailLevel(0)->scanZones(box, mat, + zoneVector, &numZones); + if (numZones != 0) + { + Con::printf("%d %d", x, y); + material->flags |= TerrainBlock::Material::Empty; + FrameAllocator::setWaterMark(waterMark); + break; + } + FrameAllocator::setWaterMark(waterMark); + } + } + } + } + + // rebuild stuff.. + for (U32 i = 0; i < mTerrainBlocks.size(); i++) + { + mTerrainBlocks[i]->buildGridMap(); + mTerrainBlocks[i]->rebuildEmptyFlags(); + mTerrainBlocks[i]->packEmptySquares(); + } + */ +} + +void TerrainEditor::mirrorTerrain(S32 mirrorIndex) +{ + if(!checkTerrainBlock(this, "mirrorTerrain")) + return; + + // TODO! + /* + TerrainBlock * terrain = mActiveTerrain; + setDirty(); + + // + enum { + top = BIT(0), + bottom = BIT(1), + left = BIT(2), + right = BIT(3) + }; + + U32 sides[8] = + { + bottom, + bottom | left, + left, + left | top, + top, + top | right, + right, + bottom | right + }; + + U32 n = TerrainBlock::BlockSize; + U32 side = sides[mirrorIndex % 8]; + bool diag = mirrorIndex & 0x01; + + Point2I src((side & right) ? (n - 1) : 0, (side & bottom) ? (n - 1) : 0); + Point2I dest((side & left) ? (n - 1) : 0, (side & top) ? (n - 1) : 0); + Point2I origSrc(src); + Point2I origDest(dest); + + // determine the run length + U32 minStride = ((side & top) || (side & bottom)) ? n : n / 2; + U32 majStride = ((side & left) || (side & right)) ? n : n / 2; + + Point2I srcStep((side & right) ? -1 : 1, (side & bottom) ? -1 : 1); + Point2I destStep((side & left) ? -1 : 1, (side & top) ? -1 : 1); + + // + U16 * heights = terrain->getHeightAddress(0,0); + U8 * baseMaterials = terrain->getBaseMaterialAddress(0,0); + TerrainBlock::Material * materials = terrain->getMaterial(0,0); + + // create an undo selection + Selection * undo = new Selection; + + // walk through all the positions + for(U32 i = 0; i < majStride; i++) + { + for(U32 j = 0; j < minStride; j++) + { + // skip the same position + if(src != dest) + { + U32 si = src.x + (src.y << TerrainBlock::BlockShift); + U32 di = dest.x + (dest.y << TerrainBlock::BlockShift); + + // add to undo selection + GridInfo info; + getGridInfo(dest, info, terrain); + undo->add(info); + + //... copy info... (height, basematerial, material) + heights[di] = heights[si]; + baseMaterials[di] = baseMaterials[si]; + materials[di] = materials[si]; + } + + // get to the new position + src.x += srcStep.x; + diag ? (dest.y += destStep.y) : (dest.x += destStep.x); + } + + // get the next position for a run + src.y += srcStep.y; + diag ? (dest.x += destStep.x) : (dest.y += destStep.y); + + // reset the minor run + src.x = origSrc.x; + diag ? (dest.y = origDest.y) : (dest.x = origDest.x); + + // shorten the run length for diag runs + if(diag) + minStride--; + } + + // rebuild stuff.. + terrain->buildGridMap(); + terrain->rebuildEmptyFlags(); + terrain->packEmptySquares(); + + // add undo selection + submitUndo( undo ); + */ +} + +//------------------------------------------------------------------------------ + +ConsoleMethod( TerrainEditor, attachTerrain, void, 2, 3, "(TerrainBlock terrain)") +{ + SimSet * missionGroup = dynamic_cast(Sim::findObject("MissionGroup")); + if (!missionGroup) + { + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no mission group found"); + return; + } + + VectorPtr terrains; + + // attach to first found terrainBlock + if (argc == 2) + { + for(SimSetIterator itr(missionGroup); *itr; ++itr) + { + TerrainBlock* terrBlock = dynamic_cast(*itr); + + if (terrBlock) + terrains.push_back(terrBlock); + } + + //if (terrains.size() == 0) + // Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: no TerrainBlock objects found!"); + } + else // attach to named object + { + TerrainBlock* terrBlock = dynamic_cast(Sim::findObject(argv[2])); + + if (terrBlock) + terrains.push_back(terrBlock); + + if(terrains.size() == 0) + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: failed to attach to object '%s'", argv[2]); + } + + if (terrains.size() > 0) + { + for (U32 i = 0; i < terrains.size(); i++) + { + if (!terrains[i]->isServerObject()) + { + terrains[i] = NULL; + + Con::errorf(ConsoleLogEntry::Script, "TerrainEditor::attach: cannot attach to client TerrainBlock"); + } + } + } + + for (U32 i = 0; i < terrains.size(); i++) + { + if (terrains[i]) + object->attachTerrain(terrains[i]); + } +} + +ConsoleMethod( TerrainEditor, setBrushType, void, 3, 3, "(string type)" + "One of box, ellipse, selection.") +{ + object->setBrushType(argv[2]); +} + +ConsoleMethod( TerrainEditor, getBrushType, const char*, 2, 2, "()") +{ + return object->getBrushType(); +} + +ConsoleMethod( TerrainEditor, setBrushSize, void, 3, 4, "(int w [, int h])") +{ + S32 w = dAtoi(argv[2]); + S32 h = argc > 3 ? dAtoi(argv[3]) : w; + object->setBrushSize( w, h ); +} + +ConsoleMethod( TerrainEditor, getBrushSize, const char*, 2, 2, "()") +{ + Point2I size = object->getBrushSize(); + + char * ret = Con::getReturnBuffer(32); + dSprintf(ret, 32, "%d %d", size.x, size.y); + return ret; +} + +ConsoleMethod( TerrainEditor, setBrushPressure, void, 3, 3, "(float pressure)") +{ + object->setBrushPressure( dAtof( argv[2] ) ); +} + +ConsoleMethod( TerrainEditor, getBrushPressure, F32, 2, 2, "()") +{ + return object->getBrushPressure(); +} + +ConsoleMethod( TerrainEditor, setBrushSoftness, void, 3, 3, "(float softness)") +{ + object->setBrushSoftness( dAtof( argv[2] ) ); +} + +ConsoleMethod( TerrainEditor, getBrushSoftness, F32, 2, 2, "()") +{ + return object->getBrushSoftness(); +} + +ConsoleMethod( TerrainEditor, getBrushPos, const char*, 2, 2, "Returns a Point2I.") +{ + return object->getBrushPos(); +} + +ConsoleMethod( TerrainEditor, setBrushPos, void, 3, 4, "(int x, int y)") +{ + // + Point2I pos; + if(argc == 3) + dSscanf(argv[2], "%d %d", &pos.x, &pos.y); + else + { + pos.x = dAtoi(argv[2]); + pos.y = dAtoi(argv[3]); + } + + object->setBrushPos(pos); +} + +ConsoleMethod( TerrainEditor, setAction, void, 3, 3, "(string action_name)") +{ + object->setAction(argv[2]); +} + +ConsoleMethod( TerrainEditor, getActionName, const char*, 3, 3, "(int num)") +{ + return (object->getActionName(dAtoi(argv[2]))); +} + +ConsoleMethod( TerrainEditor, getNumActions, S32, 2, 2, "") +{ + return(object->getNumActions()); +} + +ConsoleMethod( TerrainEditor, getCurrentAction, const char*, 2, 2, "") +{ + return object->getCurrentAction(); +} + +ConsoleMethod( TerrainEditor, resetSelWeights, void, 3, 3, "(bool clear)") +{ + object->resetSelWeights(dAtob(argv[2])); +} + +ConsoleMethod( TerrainEditor, clearSelection, void, 2, 2, "") +{ + object->clearSelection(); +} + +ConsoleMethod( TerrainEditor, processAction, void, 2, 3, "(string action=NULL)") +{ + if(argc == 3) + object->processAction(argv[2]); + else object->processAction(""); +} + +ConsoleMethod( TerrainEditor, getActiveTerrain, S32, 2, 2, "") +{ + S32 ret = 0; + + TerrainBlock* terrain = object->getActiveTerrain(); + + if (terrain) + ret = terrain->getId(); + + return ret; +} + +ConsoleMethod( TerrainEditor, getNumTextures, S32, 2, 2, "") +{ + return object->getNumTextures(); +} + +ConsoleMethod( TerrainEditor, markEmptySquares, void, 2, 2, "") +{ + object->markEmptySquares(); +} + +ConsoleMethod( TerrainEditor, mirrorTerrain, void, 3, 3, "") +{ + object->mirrorTerrain(dAtoi(argv[2])); +} + +ConsoleMethod(TerrainEditor, setTerraformOverlay, void, 3, 3, "(bool overlayEnable) - sets the terraformer current heightmap to draw as an overlay over the current terrain.") +{ + // XA: This one needs to be implemented :) +} + +ConsoleMethod(TerrainEditor, updateMaterial, bool, 4, 4, + "( int index, string matName )\n" + "Changes the material name at the index." ) +{ + TerrainBlock *terr = object->getClientTerrain(); + if ( !terr ) + return false; + + U32 index = dAtoi( argv[2] ); + if ( index >= terr->getMaterialCount() ) + return false; + + terr->updateMaterial( index, argv[3] ); + + object->setDirty(); + + return true; +} + +ConsoleMethod(TerrainEditor, addMaterial, S32, 3, 3, + "( string matName )\n" + "Adds a new material." ) +{ + TerrainBlock *terr = object->getClientTerrain(); + if ( !terr ) + return false; + + terr->addMaterial( argv[2] ); + + object->setDirty(); + + return true; +} + +ConsoleMethod(TerrainEditor, getMaterialCount, S32, 2, 2, + "Returns the current material count." ) +{ + TerrainBlock *terr = object->getClientTerrain(); + if ( terr ) + return terr->getMaterialCount(); + + return 0; +} + +ConsoleMethod(TerrainEditor, getMaterials, const char *, 2, 2, "() gets the list of current terrain materials.") +{ + TerrainBlock *terr = object->getClientTerrain(); + if ( !terr ) + return ""; + + char *ret = Con::getReturnBuffer(4096); + ret[0] = 0; + for(U32 i = 0; i < terr->getMaterialCount(); i++) + { + dStrcat( ret, terr->getMaterialName(i) ); + dStrcat( ret, "\n" ); + } + + return ret; +} + +ConsoleMethod(TerrainEditor, getTerrainUnderWorldPoint, S32, 3, 5, "(x/y/z) Gets the terrain block that is located under the given world point.\n" + "@param x/y/z The world coordinates (floating point values) you wish to query at. " + "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\n" + "@return Returns the ID of the requested terrain block (0 if not found).\n\n") +{ + TerrainEditor *tEditor = (TerrainEditor *) object; + if(tEditor == NULL) + return 0; + Point3F pos; + if(argc == 3) + dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z); + else if(argc == 5) + { + pos.x = dAtof(argv[2]); + pos.y = dAtof(argv[3]); + pos.z = dAtof(argv[4]); + } + + else + { + Con::errorf("TerrainEditor.getTerrainUnderWorldPoint(): Invalid argument count! Valid arguments are either \"x y z\" or x,y,z\n"); + return 0; + } + + TerrainBlock* terrain = tEditor->getTerrainUnderWorldPoint(pos); + if(terrain != NULL) + { + return terrain->getId(); + } + + return 0; +} + +//------------------------------------------------------------------------------ + +void TerrainEditor::initPersistFields() +{ + addGroup("Misc"); + addField("isDirty", TypeBool, Offset(mIsDirty, TerrainEditor)); + addField("isMissionDirty", TypeBool, Offset(mIsMissionDirty, TerrainEditor)); + addField("renderBorder", TypeBool, Offset(mRenderBorder, TerrainEditor)); ///< Not currently used + addField("borderHeight", TypeF32, Offset(mBorderHeight, TerrainEditor)); ///< Not currently used + addField("borderFillColor", TypeColorI, Offset(mBorderFillColor, TerrainEditor)); ///< Not currently used + addField("borderFrameColor", TypeColorI, Offset(mBorderFrameColor, TerrainEditor)); ///< Not currently used + addField("borderLineMode", TypeBool, Offset(mBorderLineMode, TerrainEditor)); ///< Not currently used + addField("selectionHidden", TypeBool, Offset(mSelectionHidden, TerrainEditor)); + addField("renderVertexSelection", TypeBool, Offset(mRenderVertexSelection, TerrainEditor)); ///< Not currently used + addField("renderSolidBrush", TypeBool, Offset(mRenderSolidBrush, TerrainEditor)); + addField("processUsesBrush", TypeBool, Offset(mProcessUsesBrush, TerrainEditor)); + addField("maxBrushSize", TypePoint2I, Offset(mMaxBrushSize, TerrainEditor)); + + // action values... + addField("adjustHeightVal", TypeF32, Offset(mAdjustHeightVal, TerrainEditor)); ///< RaiseHeightAction and LowerHeightAction + addField("setHeightVal", TypeF32, Offset(mSetHeightVal, TerrainEditor)); ///< SetHeightAction + addField("scaleVal", TypeF32, Offset(mScaleVal, TerrainEditor)); ///< ScaleHeightAction + addField("smoothFactor", TypeF32, Offset(mSmoothFactor, TerrainEditor)); ///< SmoothHeightAction + addField("noiseFactor", TypeF32, Offset(mNoiseFactor, TerrainEditor)); ///< PaintNoiseAction + addField("materialGroup", TypeS32, Offset(mMaterialGroup, TerrainEditor)); ///< Not currently used + addField("softSelectRadius", TypeF32, Offset(mSoftSelectRadius, TerrainEditor)); ///< SoftSelectAction + addField("softSelectFilter", TypeString, Offset(mSoftSelectFilter, TerrainEditor)); ///< SoftSelectAction brush filtering + addField("softSelectDefaultFilter", TypeString, Offset(mSoftSelectDefaultFilter, TerrainEditor)); ///< SoftSelectAction brush filtering + addField("adjustHeightMouseScale", TypeF32, Offset(mAdjustHeightMouseScale, TerrainEditor)); ///< Not currently used + addField("paintIndex", TypeS32, Offset(mPaintIndex, TerrainEditor)); ///< PaintMaterialAction + endGroup("Misc"); + + Parent::initPersistFields(); +} + +ConsoleMethod( TerrainEditor, getSlopeLimitMinAngle, F32, 2, 2, 0) +{ + return object->mSlopeMinAngle; +} + +ConsoleMethod( TerrainEditor, setSlopeLimitMinAngle, F32, 3, 3, 0) +{ + F32 angle = dAtof( argv[2] ); + if ( angle < 0.0f ) + angle = 0.0f; + if ( angle > object->mSlopeMaxAngle ) + angle = object->mSlopeMaxAngle; + + object->mSlopeMinAngle = angle; + return angle; +} + +ConsoleMethod( TerrainEditor, getSlopeLimitMaxAngle, F32, 2, 2, 0) +{ + return object->mSlopeMaxAngle; +} + +ConsoleMethod( TerrainEditor, setSlopeLimitMaxAngle, F32, 3, 3, 0) +{ + F32 angle = dAtof( argv[2] ); + if ( angle > 90.0f ) + angle = 90.0f; + if ( angle < object->mSlopeMinAngle ) + angle = object->mSlopeMinAngle; + + object->mSlopeMaxAngle = angle; + return angle; +} diff --git a/gui/worldEditor/terrainEditor.h b/gui/worldEditor/terrainEditor.h new file mode 100644 index 0000000..52f1494 --- /dev/null +++ b/gui/worldEditor/terrainEditor.h @@ -0,0 +1,429 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRAINEDITOR_H_ +#define _TERRAINEDITOR_H_ + +#ifndef _EDITTSCTRL_H_ +#include "gui/worldEditor/editTSCtrl.h" +#endif +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif + + +//------------------------------------------------------------------------------ + +// Each 2D grid position must be associated with a terrainBlock +struct GridPoint +{ + Point2I gridPos; + TerrainBlock* terrainBlock; + + GridPoint() { gridPos.set(0, 0); terrainBlock = NULL; }; +}; + +class GridInfo +{ + public: + GridPoint mGridPoint; + U8 mMaterial; + //TerrainBlock::Material mMaterial; + //U8 mMaterialAlpha[TerrainBlock::MaterialGroups]; + F32 mHeight; + //U8 mMaterialGroup; + F32 mWeight; + F32 mStartHeight; + + bool mPrimarySelect; + bool mMaterialChanged; + + // hash table + S32 mNext; + S32 mPrev; +}; + +//------------------------------------------------------------------------------ + +class Selection : public Vector +{ + private: + + StringTableEntry mName; + BitSet32 mUndoFlags; + + // hash table + S32 lookup(const Point2I & pos); + void insert(GridInfo info); + U32 getHashIndex(const Point2I & pos); + bool validate(); + + Vector mHashLists; + U32 mHashListSize; + + public: + + Selection(); + virtual ~Selection(); + + void reset(); + bool add(const GridInfo &info); + bool getInfo(Point2I pos, GridInfo & info); + bool setInfo(GridInfo & info); + bool remove(const GridInfo &info); + void setName(StringTableEntry name); + StringTableEntry getName(){return(mName);} + F32 getAvgHeight(); + F32 getMinHeight(); + F32 getMaxHeight(); +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +class TerrainEditor; +class Brush : public Selection +{ + protected: + TerrainEditor * mTerrainEditor; + Point2I mSize; + GridPoint mGridPoint; + + public: + + enum { + MaxBrushDim = 40 + }; + + Brush(TerrainEditor * editor); + virtual ~Brush(){}; + + virtual const char *getType() const = 0; + + // Brush appears to intentionally bypass Selection's hash table, so we + // override validate() here. + bool validate() { return true; } + void setPosition(const Point3F & pos); + void setPosition(const Point2I & pos); + const Point2I & getPosition(); + const GridPoint & getGridPoint(); + void setTerrain(TerrainBlock* terrain) { mGridPoint.terrainBlock = terrain; }; + + void update(); + virtual void rebuild() = 0; + + virtual void render(Vector & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone) const = 0; + + Point2I getSize() const {return(mSize);} + virtual void setSize(const Point2I & size){mSize = size;} +}; + +class BoxBrush : public Brush +{ + public: + BoxBrush(TerrainEditor * editor) : Brush(editor){} + + const char *getType() const { return "box"; } + void rebuild(); + void render(Vector & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone) const; +}; + +class EllipseBrush : public Brush +{ + protected: + Vector mRenderList; + + public: + EllipseBrush(TerrainEditor * editor) : Brush(editor){} + + const char *getType() const { return "ellipse"; } + void rebuild(); + void render(Vector & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone) const; +}; + +class SelectionBrush : public Brush +{ + public: + SelectionBrush(TerrainEditor * editor); + + const char *getType() const { return "selection"; } + void rebuild(); + void render(Vector & vertexBuffer, S32 & verts, S32 & elems, S32 & prims, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone) const; + void setSize(const Point2I &){} +}; +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + +class TerrainAction; + +class TerrainEditor : public EditTSCtrl +{ + // XA: This methods where added to replace the friend consoleMethods. + public: + void attachTerrain(TerrainBlock *terrBlock); + void detachTerrain(TerrainBlock *terrBlock); + + void setBrushType(const char* type); + const char* getBrushType() const; + + void setBrushSize(S32 w, S32 h); + const char* getBrushPos(); + void setBrushPos(Point2I pos); + + void setAction(const char* action); + const char* getActionName(U32 index); + const char* getCurrentAction() const; + S32 getNumActions(); + void processAction(const char* sAction); + + void resetSelWeights(bool clear); + void clearSelection(); + + S32 getNumTextures(); + + void markEmptySquares(); + + void mirrorTerrain(S32 mirrorIndex); + + TerrainBlock* getActiveTerrain() { return mActiveTerrain; }; + + void scheduleGridUpdate() { mNeedsGridUpdate = true; } + void scheduleMaterialUpdate() { mNeedsMaterialUpdate = true; } + + private: + + typedef EditTSCtrl Parent; + + TerrainBlock* mActiveTerrain; + + // A list of all of the TerrainBlocks this editor can edit + VectorPtr mTerrainBlocks; + + Point2I mGridUpdateMin; + Point2I mGridUpdateMax; + U32 mMouseDownSeq; + + /// If one of these flags when the terrainEditor goes to render + /// an appropriate update method will be called on the terrain. + /// This prevents unnecessary work from happening from directly + /// within an editor event's process method. + bool mNeedsGridUpdate; + bool mNeedsMaterialUpdate; + + Point3F mMousePos; + Brush * mMouseBrush; + bool mBrushChanged; + bool mRenderBrush; + F32 mBrushPressure; + Point2I mBrushSize; + F32 mBrushSoftness; + Vector mActions; + TerrainAction * mCurrentAction; + bool mInAction; + Selection mDefaultSel; + bool mSelectionLocked; + GuiCursor * mDefaultCursor; + GuiCursor * mCurrentCursor; + bool mCursorVisible; + + S32 mPaintIndex; + + Selection * mCurrentSel; + + class TerrainEditorUndoAction : public UndoAction + { + public: + + TerrainEditorUndoAction( const UTF8* actionName ) + : UndoAction( actionName ), + mTerrainEditor( NULL ), + mSel( NULL ) + { + } + + virtual ~TerrainEditorUndoAction() + { + delete mSel; + } + + TerrainEditor *mTerrainEditor; + + Selection *mSel; + + virtual void undo(); + virtual void redo() { undo(); } + }; + + void submitUndo( Selection *sel ); + + Selection *mUndoSel; + + bool mIsDirty; // dirty flag for writing terrain. + bool mIsMissionDirty; // dirty flag for writing mission. + + GFXStateBlockRef mStateBlock; + + public: + + TerrainEditor(); + ~TerrainEditor(); + + // conversion functions + // Returns true if the grid position is on the main tile + bool isMainTile(const Point2I & gPos) const; + + // Takes a world point and find the "highest" terrain underneath it + // Returns true if the returned GridPoint includes a valid terrain and grid position + TerrainBlock* getTerrainUnderWorldPoint(const Point3F & wPos); + + // Converts a GridPoint to a world position + bool gridToWorld(const GridPoint & gPoint, Point3F & wPos); + bool gridToWorld(const Point2I & gPos, Point3F & wPos, TerrainBlock* terrain); + + // Converts a world position to a grid point + // If the grid point does not have a TerrainBlock already it will find the nearest + // terrian under the world position + bool worldToGrid(const Point3F & wPos, GridPoint & gPoint); + bool worldToGrid(const Point3F & wPos, Point2I & gPos, TerrainBlock* terrain = NULL); + + // Converts any point that is off of the main tile to its equivalent on the main tile + // Returns true if the point was already on the main tile + bool gridToCenter(const Point2I & gPos, Point2I & cPos) const; + + //bool getGridInfo(const Point3F & wPos, GridInfo & info); + // Gets the grid info for a point on a TerrainBlock's grid + bool getGridInfo(const GridPoint & gPoint, GridInfo & info); + bool getGridInfo(const Point2I & gPos, GridInfo & info, TerrainBlock* terrain); + + // Returns a list of infos for all points on the terrain that are at that point in space + void getGridInfos(const GridPoint & gPoint, Vector& infos); + + void setGridInfo(const GridInfo & info, bool checkActive = false); + void setGridInfoHeight(const GridInfo & info); + void gridUpdateComplete( bool materialChanged = false ); + void materialUpdateComplete(); + void processActionTick(U32 sequence); + + TerrainBlock* collide(const Gui3DMouseEvent & event, Point3F & pos); + void lockSelection(bool lock) { mSelectionLocked = lock; }; + + Selection * getUndoSel(){return(mUndoSel);} + Selection * getCurrentSel(){return(mCurrentSel);} + void setCurrentSel(Selection * sel) { mCurrentSel = sel; } + void resetCurrentSel() {mCurrentSel = &mDefaultSel; } + + S32 getPaintMaterialIndex() const { return mPaintIndex; } + + void setBrushPressure( F32 pressure ); + F32 getBrushPressure() const { return mBrushPressure; } + + void setBrushSoftness( F32 softness ); + F32 getBrushSoftness() const { return mBrushSoftness; } + + Point2I getBrushSize() { return(mBrushSize); } + + TerrainBlock* getTerrainBlock() const { return mActiveTerrain; } + TerrainBlock* getClientTerrain( TerrainBlock *serverTerrain = NULL ) const; + bool terrainBlockValid() { return(mActiveTerrain ? true : false); } + void setCursor(GuiCursor * cursor); + void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + void setDirty() { mIsDirty = true; } + void setMissionDirty() { mIsMissionDirty = true; } + + TerrainAction * lookupAction(const char * name); + + private: + + + // terrain interface functions + // Returns the height at a grid point + F32 getGridHeight(const GridPoint & gPoint); + // Sets a height at a grid point + void setGridHeight(const GridPoint & gPoint, const F32 height); + + /// + U8 getGridMaterial( const GridPoint &gPoint ) const; + + /// + void setGridMaterial( const GridPoint & gPoint, U8 index ); + + // Gets the material group of a specific spot on a TerrainBlock's grid + U8 getGridMaterialGroup(const GridPoint & gPoint); + + // Sets a material group for a spot on a TerrainBlock's grid + void setGridMaterialGroup(const GridPoint & gPoint, U8 group); + + // + void updateBrush(Brush & brush, const Point2I & gPos); + + // + Point3F getMousePos(){return(mMousePos);}; + + // + void renderSelection(const Selection & sel, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone, bool renderFill, bool renderFrame); + void renderBrush(const Brush & brush, const ColorF & inColorFull, const ColorF & inColorNone, const ColorF & outColorFull, const ColorF & outColorNone, bool renderFill, bool renderFrame); + void renderBorder(); + + public: + + // persist field data - these are dynamic + bool mRenderBorder; + F32 mBorderHeight; + ColorI mBorderFillColor; + ColorI mBorderFrameColor; + bool mBorderLineMode; + bool mSelectionHidden; + bool mRenderVertexSelection; + bool mRenderSolidBrush; + bool mProcessUsesBrush; + + // + F32 mAdjustHeightVal; + F32 mSetHeightVal; + F32 mScaleVal; + F32 mSmoothFactor; + F32 mNoiseFactor; + S32 mMaterialGroup; + F32 mSoftSelectRadius; + StringTableEntry mSoftSelectFilter; + StringTableEntry mSoftSelectDefaultFilter; + F32 mAdjustHeightMouseScale; + Point2I mMaxBrushSize; + + F32 mSlopeMinAngle; + F32 mSlopeMaxAngle; + + public: + + // SimObject + bool onAdd(); + void onDeleteNotify(SimObject * object); + + static void initPersistFields(); + + // EditTSCtrl + virtual bool onInputEvent(const InputEventInfo & event); + virtual void on3DMouseUp(const Gui3DMouseEvent & event); + virtual void on3DMouseDown(const Gui3DMouseEvent & event); + virtual void on3DMouseMove(const Gui3DMouseEvent & event); + virtual void on3DMouseDragged(const Gui3DMouseEvent & event); + virtual void on3DMouseWheelUp(const Gui3DMouseEvent & event); + virtual void on3DMouseWheelDown(const Gui3DMouseEvent & event); + void updateGuiInfo(); + void renderScene(const RectI & updateRect); + + DECLARE_CONOBJECT(TerrainEditor); +}; + +inline void TerrainEditor::setGridInfoHeight(const GridInfo & info) +{ + setGridHeight(info.mGridPoint, info.mHeight); +} + +#endif diff --git a/gui/worldEditor/undoActions.cpp b/gui/worldEditor/undoActions.cpp new file mode 100644 index 0000000..223d283 --- /dev/null +++ b/gui/worldEditor/undoActions.cpp @@ -0,0 +1,225 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/worldEditor/undoActions.h" + +#include "gui/editor/inspector/field.h" +#include "gui/editor/guiInspector.h" +#include "console/consoleTypes.h" + + +IMPLEMENT_CONOBJECT( MECreateUndoAction ); + +MECreateUndoAction::MECreateUndoAction( const UTF8* actionName ) + : UndoAction( actionName ) +{ +} + +MECreateUndoAction::~MECreateUndoAction() +{ +} + +void MECreateUndoAction::initPersistFields() +{ + Parent::initPersistFields(); +} + +void MECreateUndoAction::addObject( SimObject *object ) +{ + AssertFatal( object, "MECreateUndoAction::addObject() - Got null object!" ); + + mObjects.increment(); + mObjects.last().id = object->getId(); +} + +ConsoleMethod( MECreateUndoAction, addObject, void, 3, 3, "( SimObject obj )") +{ + SimObject *obj = NULL; + if ( Sim::findObject( argv[2], obj ) && obj ) + object->addObject( obj ); +} + +void MECreateUndoAction::undo() +{ + for ( S32 i= mObjects.size()-1; i >= 0; i-- ) + { + ObjectState &state = mObjects[i]; + + SimObject *object = Sim::findObject( state.id ); + if ( !object ) + continue; + + // Save the state. + if ( !state.memento.hasState() ) + state.memento.save( object ); + + // Store the group. + SimGroup *group = object->getGroup(); + if ( group ) + state.groupId = group->getId(); + + // We got what we need... delete it. + object->deleteObject(); + } +} + +void MECreateUndoAction::redo() +{ + for ( S32 i=0; i < mObjects.size(); i++ ) + { + const ObjectState &state = mObjects[i]; + + // Create the object. + SimObject::setForcedId(state.id); // Restore the object's Id + SimObject *object = state.memento.restore(); + if ( !object ) + continue; + + // Now restore its group. + SimGroup *group; + if ( Sim::findObject( state.groupId, group ) ) + group->addObject( object ); + } +} + + +IMPLEMENT_CONOBJECT( MEDeleteUndoAction ); + +MEDeleteUndoAction::MEDeleteUndoAction( const UTF8 *actionName ) + : UndoAction( actionName ) +{ +} + +MEDeleteUndoAction::~MEDeleteUndoAction() +{ +} + +void MEDeleteUndoAction::initPersistFields() +{ + Parent::initPersistFields(); +} + +void MEDeleteUndoAction::deleteObject( SimObject *object ) +{ + AssertFatal( object, "MEDeleteUndoAction::deleteObject() - Got null object!" ); + AssertFatal( object->isProperlyAdded(), + "MEDeleteUndoAction::deleteObject() - Object should be registered!" ); + + mObjects.increment(); + ObjectState& state = mObjects.last(); + + // Capture the object id. + state.id = object->getId(); + + // Save the state. + state.memento.save( object ); + + // Store the group. + SimGroup *group = object->getGroup(); + if ( group ) + state.groupId = group->getId(); + + // Now delete the object. + object->deleteObject(); +} + +ConsoleMethod( MEDeleteUndoAction, deleteObject, void, 3, 3, "( SimObject obj )") +{ + SimObject *obj = NULL; + if ( Sim::findObject( argv[2], obj ) && obj ) + object->deleteObject( obj ); +} + +void MEDeleteUndoAction::undo() +{ + for ( S32 i= mObjects.size()-1; i >= 0; i-- ) + { + const ObjectState &state = mObjects[i]; + + // Create the object. + SimObject::setForcedId(state.id); // Restore the object's Id + SimObject *object = state.memento.restore(); + if ( !object ) + continue; + + // Now restore its group. + SimGroup *group; + if ( Sim::findObject( state.groupId, group ) ) + group->addObject( object ); + } +} + +void MEDeleteUndoAction::redo() +{ + for ( S32 i=0; i < mObjects.size(); i++ ) + { + const ObjectState& state = mObjects[i]; + SimObject *object = Sim::findObject( state.id ); + if ( object ) + object->deleteObject(); + } +} + +IMPLEMENT_CONOBJECT( InspectorFieldUndoAction ); + +InspectorFieldUndoAction::InspectorFieldUndoAction() +{ + mObjId = 0; + mField = NULL; + mSlotName = StringTable->insert(""); + mArrayIdx = StringTable->insert(""); +} + +InspectorFieldUndoAction::InspectorFieldUndoAction( const UTF8 *actionName ) +: UndoAction( actionName ) +{ + mInspector = NULL; + mObjId = 0; + mField = NULL; + mSlotName = StringTable->insert(""); + mArrayIdx = StringTable->insert(""); +} + +void InspectorFieldUndoAction::initPersistFields() +{ + addField( "inspectorGui", TypeSimObjectPtr, Offset( mInspector, InspectorFieldUndoAction ) ); + addField( "objectId", TypeS32, Offset( mObjId, InspectorFieldUndoAction ) ); + addField( "fieldName", TypeString, Offset( mSlotName, InspectorFieldUndoAction ) ); + addField( "fieldValue", TypeRealString, Offset( mData, InspectorFieldUndoAction ) ); + addField( "arrayIndex", TypeString, Offset( mArrayIdx, InspectorFieldUndoAction ) ); + + Parent::initPersistFields(); +} + +void InspectorFieldUndoAction::undo() +{ + SimObject *obj = NULL; + if ( !Sim::findObject( mObjId, obj ) ) + return; + + if ( mArrayIdx && dStricmp( mArrayIdx, "(null)" ) == 0 ) + mArrayIdx = NULL; + + // Grab the current data. + String data = obj->getDataField( mSlotName, mArrayIdx ); + + // Call this to mirror the way field changes are done through the inspector. + obj->inspectPreApply(); + + // Restore the data from the UndoAction + obj->setDataField( mSlotName, mArrayIdx, mData.c_str() ); + + // Call this to mirror the way field changes are done through the inspector. + obj->inspectPostApply(); + + // If the affected object is still being inspected, + // update the InspectorField to reflect the changed value. + if ( mInspector && mInspector->getInspectObject() == obj ) + mInspector->updateFieldValue( mSlotName, mArrayIdx ); + + // Now save the previous data in this UndoAction + // since an undo action must become a redo action and vice-versa + mData = data; +} diff --git a/gui/worldEditor/undoActions.h b/gui/worldEditor/undoActions.h new file mode 100644 index 0000000..627ab14 --- /dev/null +++ b/gui/worldEditor/undoActions.h @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#ifndef _GUI_WORLDEDITOR_UNDOACTIONS_H_ +#define _GUI_WORLDEDITOR_UNDOACTIONS_H_ + +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _CONSOLE_SIMOBJECTMEMENTO_H_ +#include "console/simObjectMemento.h" +#endif + +class GuiInspectorField; +class GuiInspector; + +class MECreateUndoAction : public UndoAction +{ + typedef UndoAction Parent; + +protected: + + struct ObjectState + { + /// The object we created and will delete in undo. + SimObjectId id; + + /// The captured object state. + SimObjectMemento memento; + + /// Keep track of the parent group. + SimObjectId groupId; + }; + + /// All the objects that were created. + Vector mObjects; + +public: + + DECLARE_CONOBJECT( MECreateUndoAction ); + static void initPersistFields(); + + MECreateUndoAction( const UTF8* actionName = " " ); + virtual ~MECreateUndoAction(); + + void addObject( SimObject *object ); + + // UndoAction + virtual void undo(); + virtual void redo(); +}; + + +class MEDeleteUndoAction : public UndoAction +{ + typedef UndoAction Parent; + +protected: + + struct ObjectState + { + /// The object we deleted and will restore in undo. + SimObjectId id; + + /// The captured object state. + SimObjectMemento memento; + + /// Keep track of the parent group. + SimObjectId groupId; + }; + + /// All the objects we're deleting. + Vector mObjects; + +public: + + DECLARE_CONOBJECT( MEDeleteUndoAction ); + static void initPersistFields(); + + MEDeleteUndoAction( const UTF8* actionName = "Delete Object" ); + virtual ~MEDeleteUndoAction(); + + /// + void deleteObject( SimObject *object ); + + // UndoAction + virtual void undo(); + virtual void redo(); +}; + +class InspectorFieldUndoAction : public UndoAction +{ + typedef UndoAction Parent; + +public: + + InspectorFieldUndoAction(); + InspectorFieldUndoAction( const UTF8* actionName ); + + DECLARE_CONOBJECT( InspectorFieldUndoAction ); + static void initPersistFields(); + + GuiInspector *mInspector; + SimObjectId mObjId; + SimObjectPtr mField; + StringTableEntry mSlotName; + StringTableEntry mArrayIdx; + String mData; + + // UndoAction + virtual void undo(); + virtual void redo() { undo(); } +}; + +#endif // _GUI_WORLDEDITOR_UNDOACTIONS_H_ diff --git a/gui/worldEditor/worldEditor.cpp b/gui/worldEditor/worldEditor.cpp new file mode 100644 index 0000000..1fbded9 --- /dev/null +++ b/gui/worldEditor/worldEditor.cpp @@ -0,0 +1,3547 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "gui/worldEditor/worldEditor.h" + +#include "gui/worldEditor/gizmo.h" +#include "gui/worldEditor/undoActions.h" +#include "gui/worldEditor/editorIconRegistry.h" +#include "core/stream/memStream.h" +#include "sceneGraph/simPath.h" +#include "gui/core/guiCanvas.h" +#include "T3D/gameConnection.h" +#include "collision/earlyOutPolyList.h" +#include "collision/concretePolyList.h" +#include "console/consoleInternal.h" +#include "T3D/shapeBase.h" +#include "T3D/cameraSpline.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDrawUtil.h" +#include "platform/typetraits.h" + + + +IMPLEMENT_CONOBJECT(WorldEditor); + +// unnamed namespace for static data +namespace { + + static VectorF axisVector[3] = { + VectorF(1.0f,0.0f,0.0f), + VectorF(0.0f,1.0f,0.0f), + VectorF(0.0f,0.0f,1.0f) + }; + + static Point3F BoxPnts[] = { + Point3F(0.0f,0.0f,0.0f), + Point3F(0.0f,0.0f,1.0f), + Point3F(0.0f,1.0f,0.0f), + Point3F(0.0f,1.0f,1.0f), + Point3F(1.0f,0.0f,0.0f), + Point3F(1.0f,0.0f,1.0f), + Point3F(1.0f,1.0f,0.0f), + Point3F(1.0f,1.0f,1.0f) + }; + + static U32 BoxVerts[][4] = { + {0,2,3,1}, // -x + {7,6,4,5}, // +x + {0,1,5,4}, // -y + {3,2,6,7}, // +y + {0,4,6,2}, // -z + {3,7,5,1} // +z + }; + + // + U32 getBoxNormalIndex(const VectorF & normal) + { + const F32 * pNormal = ((const F32 *)normal); + + F32 max = 0; + S32 index = -1; + + for(U32 i = 0; i < 3; i++) + if(mFabs(pNormal[i]) >= mFabs(max)) + { + max = pNormal[i]; + index = i*2; + } + + AssertFatal(index >= 0, "Failed to get best normal"); + if(max > 0.f) + index++; + + return(index); + } + + // + Point3F getBoundingBoxCenter(SceneObject * obj) + { + Box3F box = obj->getObjBox(); + MatrixF mat = obj->getTransform(); + VectorF scale = obj->getScale(); + + Point3F center(0,0,0); + Point3F projPnts[8]; + + for(U32 i = 0; i < 8; i++) + { + Point3F pnt; + pnt.set(BoxPnts[i].x ? box.maxExtents.x : box.minExtents.x, + BoxPnts[i].y ? box.maxExtents.y : box.minExtents.y, + BoxPnts[i].z ? box.maxExtents.z : box.minExtents.z); + + // scale it + pnt.convolve(scale); + mat.mulP(pnt, &projPnts[i]); + center += projPnts[i]; + } + + center /= 8; + return(center); + } + + // + const char * parseObjectFormat(SimObject * obj, const char * format) + { + static char buf[1024]; + + U32 curPos = 0; + U32 len = dStrlen(format); + + for(U32 i = 0; i < len; i++) + { + if(format[i] == '$') + { + U32 j; + for(j = i+1; j < len; j++) + if(format[j] == '$') + break; + + if(j == len) + break; + + char token[80]; + + AssertFatal((j - i) < (sizeof(token) - 1), "token too long"); + dStrncpy(token, &format[i+1], (j - i - 1)); + token[j-i-1] = 0; + + U32 remaining = sizeof(buf) - curPos - 1; + + // look at the token + if(!dStricmp(token, "id")) + curPos += dSprintf(buf + curPos, remaining, "%d", obj->getId()); + else if(!dStricmp(token, "name|internal")) + { + if( obj->getName() || !obj->getInternalName() ) + curPos += dSprintf(buf + curPos, remaining, "%s", obj->getName()); + else + curPos += dSprintf(buf + curPos, remaining, "[%s]", obj->getInternalName()); + } + else if(!dStricmp(token, "name")) + curPos += dSprintf(buf + curPos, remaining, "%s", obj->getName()); + else if(!dStricmp(token, "class")) + curPos += dSprintf(buf + curPos, remaining, "%s", obj->getClassName()); + else if(!dStricmp(token, "namespace") && obj->getNamespace()) + curPos += dSprintf(buf + curPos, remaining, "%s", obj->getNamespace()->mName); + + // + i = j; + } + else + buf[curPos++] = format[i]; + } + + buf[curPos] = 0; + return(buf); + } + + // + F32 snapFloat(F32 val, F32 snap) + { + if(snap == 0.f) + return(val); + + F32 a = mFmod(val, snap); + + if(mFabs(a) > (snap / 2)) + val < 0.f ? val -= snap : val += snap; + + return(val - a); + } + + // + EulerF extractEuler(const MatrixF & matrix) + { + const F32 * mat = (const F32*)matrix; + + EulerF r; + r.x = mAsin(mat[MatrixF::idx(2,1)]); + + if(mCos(r.x) != 0.f) + { + r.y = mAtan2(-mat[MatrixF::idx(2,0)], mat[MatrixF::idx(2,2)]); + r.z = mAtan2(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]); + } + else + { + r.y = 0.f; + r.z = mAtan2(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]); + } + + return(r); + } +} + +F32 WorldEditor::smProjectDistance = 20000.0f; + +//------------------------------------------------------------------------------ +// Class WorldEditor::Selection +//------------------------------------------------------------------------------ + +WorldEditor::Selection::Selection() : + mCentroidValid(false), + mAutoSelect(false), + mPrevCentroid(0.0f, 0.0f, 0.0f), + mContainsGlobalBounds(false) +{ + registerObject(); +} + +WorldEditor::Selection::~Selection() +{ + unregisterObject(); +} + +bool WorldEditor::Selection::objInSet( SimObject* obj ) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + if(mObjectList[i] == (SimObject*)obj) + return(true); + return(false); +} + +bool WorldEditor::Selection::addObject( SimObject* obj ) +{ + if(objInSet(obj)) + return(false); + + mCentroidValid = false; + + mObjectList.pushBack(obj); + deleteNotify(obj); + + if(mAutoSelect) + { + obj->setSelected(true); + + if( dynamic_cast< SceneObject* >( obj ) ) + { + SceneObject * clientObj = WorldEditor::getClientObj( static_cast< SceneObject* >( obj ) ); + if(clientObj) + clientObj->setSelected(true); + } + } + + return(true); +} + +bool WorldEditor::Selection::removeObject( SimObject* obj ) +{ + if(!objInSet(obj)) + return(false); + + mCentroidValid = false; + + mObjectList.remove(obj); + clearNotify(obj); + + if(mAutoSelect) + { + obj->setSelected(false); + + if( dynamic_cast< SceneObject* >( obj ) ) + { + SceneObject * clientObj = WorldEditor::getClientObj( static_cast< SceneObject* >( obj ) ); + if(clientObj) + clientObj->setSelected(false); + } + } + + return(true); +} + +void WorldEditor::Selection::clear() +{ + while(mObjectList.size()) + removeObject(static_cast(mObjectList[0])); +} + +void WorldEditor::Selection::onDeleteNotify(SimObject * obj) +{ + removeObject(static_cast(obj)); +} + +bool WorldEditor::Selection::containsGlobalBounds() +{ + updateCentroid(); + return mContainsGlobalBounds; +} + +void WorldEditor::Selection::updateCentroid() +{ + if(mCentroidValid) + return; + + //mCentroidValid = true; + + // + mCentroid.set(0,0,0); + mBoxCentroid = mCentroid; + mBoxBounds.minExtents.set(1e10, 1e10, 1e10); + mBoxBounds.maxExtents.set(-1e10, -1e10, -1e10); + + mContainsGlobalBounds = false; + + if(!mObjectList.size()) + return; + + // + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* obj = dynamic_cast(mObjectList[i]); + if( !obj ) + continue; + + const MatrixF & mat = obj->getTransform(); + Point3F wPos; + mat.getColumn(3, &wPos); + + // + mCentroid += wPos; + + // + const Box3F& bounds = obj->getWorldBox(); + mBoxBounds.minExtents.setMin(bounds.minExtents); + mBoxBounds.maxExtents.setMax(bounds.maxExtents); + + if(obj->isGlobalBounds()) + mContainsGlobalBounds = true; + } + + mCentroid /= (F32)mObjectList.size(); + mBoxCentroid = mBoxBounds.getCenter(); + + // Multi-selections always use mCentroid otherwise we + // break rotation. + if ( mObjectList.size() > 1 ) + mBoxCentroid = mCentroid; +} + +const Point3F & WorldEditor::Selection::getCentroid() +{ + updateCentroid(); + return(mCentroid); +} + +const Point3F & WorldEditor::Selection::getBoxCentroid() +{ + updateCentroid(); + return(mBoxCentroid); +} + +const Box3F & WorldEditor::Selection::getBoxBounds() +{ + updateCentroid(); + return(mBoxBounds); +} + +Point3F WorldEditor::Selection::getBoxBottomCenter() +{ + updateCentroid(); + + Point3F bottomCenter = mBoxCentroid; + bottomCenter.z -= mBoxBounds.len_z() * 0.5f; + + return bottomCenter; +} + +void WorldEditor::Selection::enableCollision() +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* object = dynamic_cast(mObjectList[i]); + if( object ) + object->enableCollision(); + } +} + +void WorldEditor::Selection::disableCollision() +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >(mObjectList[i]); + if( object ) + object->disableCollision(); + } +} + +void WorldEditor::Selection::offset(const Point3F & offset) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* obj = dynamic_cast(mObjectList[i]); + if( !obj ) + continue; + + MatrixF mat = obj->getTransform(); + Point3F wPos; + mat.getColumn(3, &wPos); + + // adjust + wPos += offset; + mat.setColumn(3, wPos); + obj->setTransform(mat); + } + + mCentroidValid = false; +} + +void WorldEditor::Selection::setPosition(const Point3F & pos) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* object = dynamic_cast(mObjectList[i]); + if( object ) + object->setPosition(pos); + } + + mCentroidValid = false; +} +void WorldEditor::Selection::setCentroidPosition(bool useBoxCenter, const Point3F & pos) +{ + Point3F centroid; + if( containsGlobalBounds() ) + { + centroid = getCentroid(); + } + else + { + centroid = useBoxCenter ? getBoxCentroid() : getCentroid(); + } + + offset(pos - centroid); +} + +void WorldEditor::Selection::orient(const MatrixF & rot, const Point3F & center) +{ + // Orient all the selected objects to the given rotation + for(U32 i = 0; i < size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + MatrixF mat = rot; + mat.setPosition( object->getPosition() ); + object->setTransform(mat); + } + + mCentroidValid = false; +} + +void WorldEditor::Selection::rotate(const EulerF &rot) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + MatrixF mat = object->getTransform(); + + MatrixF transform(rot); + mat.mul(transform); + + object->setTransform(mat); + } +} + +void WorldEditor::Selection::rotate(const EulerF & rot, const Point3F & center) +{ + // single selections will rotate around own axis, multiple about world + if(mObjectList.size() == 1) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ 0 ] ); + if( object ) + { + MatrixF mat = object->getTransform(); + + Point3F pos; + mat.getColumn(3, &pos); + + // get offset in obj space + Point3F offset = pos - center; + MatrixF wMat = object->getWorldTransform(); + wMat.mulV(offset); + + // + MatrixF transform(EulerF(0,0,0), -offset); + transform.mul(MatrixF(rot)); + transform.mul(MatrixF(EulerF(0,0,0), offset)); + mat.mul(transform); + + object->setTransform(mat); + } + } + else + { + for(U32 i = 0; i < size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + MatrixF mat = object->getTransform(); + + Point3F pos; + mat.getColumn(3, &pos); + + // get offset in obj space + Point3F offset = pos - center; + + MatrixF transform(rot); + Point3F wOffset; + transform.mulV(offset, &wOffset); + + MatrixF wMat = object->getWorldTransform(); + wMat.mulV(offset); + + // + transform.set(EulerF(0,0,0), -offset); + + mat.setColumn(3, Point3F(0,0,0)); + wMat.setColumn(3, Point3F(0,0,0)); + + transform.mul(wMat); + transform.mul(MatrixF(rot)); + transform.mul(mat); + mat.mul(transform); + + mat.normalize(); + mat.setColumn(3, wOffset + center); + + object->setTransform(mat); + } + } + + mCentroidValid = false; +} + +void WorldEditor::Selection::setRotate(const EulerF & rot) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + MatrixF mat = object->getTransform(); + Point3F pos; + mat.getColumn(3, &pos); + + MatrixF rmat(rot); + rmat.setPosition(pos); + + object->setTransform(rmat); + } +} + +void WorldEditor::Selection::scale(const VectorF & scale) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + VectorF current = object->getScale(); + current.convolve(scale); + object->setScale(current); + } + + mCentroidValid = false; +} + +void WorldEditor::Selection::scale(const VectorF & scale, const Point3F & center) +{ + for(U32 i = 0; i < size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + MatrixF mat = object->getTransform(); + + Point3F pos; + mat.getColumn(3, &pos); + + Point3F offset = pos - center; + offset *= scale; + + object->setPosition(offset + center); + + VectorF current = object->getScale(); + current.convolve(scale); + object->setScale(current); + } +} + +void WorldEditor::Selection::setScale(const VectorF & scale) +{ + for(U32 i = 0; i < mObjectList.size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( object ) + object->setScale( scale ); + } + + mCentroidValid = false; +} + +void WorldEditor::Selection::setScale(const VectorF & scale, const Point3F & center) +{ + for(U32 i = 0; i < size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + MatrixF mat = object->getTransform(); + + Point3F pos; + mat.getColumn(3, &pos); + + Point3F offset = pos - center; + offset *= scale; + + object->setPosition(offset + center); + object->setScale(scale); + } +} + +void WorldEditor::Selection::addSize(const VectorF & newsize) +{ + for(U32 i = 0; i < size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + if( object->isGlobalBounds() ) + continue; + + const Box3F& bounds = object->getObjBox(); + VectorF extent = bounds.getExtents(); + VectorF scaledextent = object->getScale() * extent; + + VectorF scale = (newsize + scaledextent) / scaledextent; + object->setScale( object->getScale() * scale ); + } +} + +void WorldEditor::Selection::setSize(const VectorF & newsize) +{ + for(U32 i = 0; i < size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( mObjectList[ i ] ); + if( !object ) + continue; + + if( object->isGlobalBounds() ) + continue; + + const Box3F& bounds = object->getObjBox(); + VectorF extent = bounds.getExtents(); + + VectorF scale = newsize / extent; + object->setScale( scale ); + } +} + +SceneObject* WorldEditor::getClientObj(SceneObject * obj) +{ + AssertFatal(obj->isServerObject(), "WorldEditor::getClientObj: not a server object!"); + + NetConnection * toServer = NetConnection::getConnectionToServer(); + NetConnection * toClient = NetConnection::getLocalClientConnection(); + if (!toServer || !toClient) + return NULL; + + S32 index = toClient->getGhostIndex(obj); + if(index == -1) + return(0); + + return(dynamic_cast(toServer->resolveGhost(index))); +} + +void WorldEditor::setClientObjInfo(SceneObject * obj, const MatrixF & mat, const VectorF & scale) +{ + SceneObject * clientObj = getClientObj(obj); + if(!clientObj) + return; + + clientObj->setTransform(mat); + clientObj->setScale(scale); +} + +void WorldEditor::updateClientTransforms(Selection & sel) +{ + + for(U32 i = 0; i < sel.size(); i++) + { + SceneObject* serverObj = dynamic_cast< SceneObject* >( sel[ i ] ); + if( !serverObj ) + continue; + + SceneObject* clientObj = getClientObj( serverObj ); + if(!clientObj) + continue; + + clientObj->setTransform(serverObj->getTransform()); + clientObj->setScale(serverObj->getScale()); + } +} + +void WorldEditor::submitUndo( Selection &sel, const UTF8* label ) +{ + // Grab the world editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "WorldEditor::createUndo() - EUndoManager not found!" ); + return; + } + + // Setup the action. + WorldEditorUndoAction *action = new WorldEditorUndoAction( label ); + for(U32 i = 0; i < sel.size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( sel[ i ] ); + if( !object ) + continue; + + WorldEditorUndoAction::Entry entry; + + entry.mMatrix = object->getTransform(); + entry.mScale = object->getScale(); + entry.mObjId = object->getId(); + action->mEntries.push_back( entry ); + } + + // Submit it. + action->mWorldEditor = this; + undoMan->addAction( action ); + + // Mark the world editor as dirty! + setDirty(); +} + +void WorldEditor::WorldEditorUndoAction::undo() +{ + // NOTE: This function also handles WorldEditorUndoAction::redo(). + + MatrixF oldMatrix; + VectorF oldScale; + for( U32 i = 0; i < mEntries.size(); i++ ) + { + SceneObject *obj; + if ( !Sim::findObject( mEntries[i].mObjId, obj ) ) + continue; + + mWorldEditor->setClientObjInfo( obj, mEntries[i].mMatrix, mEntries[i].mScale ); + + // Grab the current state. + oldMatrix = obj->getTransform(); + oldScale = obj->getScale(); + + // Restore the saved state. + obj->setTransform( mEntries[i].mMatrix ); + obj->setScale( mEntries[i].mScale ); + + // Store the previous state so the next time + // we're called we can restore it. + mEntries[i].mMatrix = oldMatrix; + mEntries[i].mScale = oldScale; + } + + // Mark the world editor as dirty! + mWorldEditor->setDirty(); + mWorldEditor->mSelected.invalidateCentroid(); + + // Let the script get a chance at it. + Con::executef( mWorldEditor, "onWorldEditorUndo" ); +} + +//------------------------------------------------------------------------------ +// edit stuff + +bool WorldEditor::cutSelection(Selection & sel) +{ + if ( !sel.size() ) + return false; + + // First copy the selection. + copySelection( sel ); + + // Grab the world editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "WorldEditor::cutSelection() - EUndoManager not found!" ); + return false; + } + + // Setup the action. + MEDeleteUndoAction *action = new MEDeleteUndoAction(); + while ( sel.size() ) + action->deleteObject( sel[0] ); + undoMan->addAction( action ); + + // Mark the world editor as dirty! + setDirty(); + + return true; +} + +bool WorldEditor::copySelection(Selection & sel) +{ + mCopyBuffer.clear(); + + for( U32 i = 0; i < sel.size(); i++ ) + { + mCopyBuffer.increment(); + mCopyBuffer.last().save( sel[i] ); + } + + return true; +} + +bool WorldEditor::pasteSelection( bool dropSel ) +{ + clearSelection(); + + // Grab the world editor undo manager. + UndoManager *undoMan = NULL; + if ( !Sim::findObject( "EUndoManager", undoMan ) ) + { + Con::errorf( "WorldEditor::pasteSelection() - EUndoManager not found!" ); + return false; + } + + SimGroup *missionGroup = NULL; + Sim::findObject( "MissionGroup", missionGroup ); + + // Setup the action. + MECreateUndoAction *action = new MECreateUndoAction( "Paste" ); + + for( U32 i = 0; i < mCopyBuffer.size(); i++ ) + { + SceneObject *obj = dynamic_cast( mCopyBuffer[i].restore() ); + if ( !obj ) + continue; + + if ( missionGroup ) + missionGroup->addObject( obj ); + + action->addObject( obj ); + + if ( !mSelectionLocked ) + { + mSelected.addObject( obj ); + Con::executef( this, "onSelect", obj->getIdString() ); + } + } + + // Its safe to submit the action before the selection + // is dropped below because the state of the objects + // are not stored until they are first undone. + undoMan->addAction( action ); + + // drop it ... + if ( dropSel ) + dropSelection( mSelected ); + + if ( mSelected.size() ) + { + if ( isMethod("onClick") ) + { + char buf[16]; + dSprintf( buf, sizeof(buf), "%d", mSelected[0]->getId() ); + + SimObject * obj = 0; + if(mRedirectID) + obj = Sim::findObject(mRedirectID); + Con::executef(obj ? obj : this, "onClick", buf); + } + } + + // Mark the world editor as dirty! + setDirty(); + + return true; +} + +//------------------------------------------------------------------------------ + +void WorldEditor::hideSelection(bool hide) +{ + // set server/client objects hide field + for(U32 i = 0; i < mSelected.size(); i++) + { + SceneObject* serverObj = dynamic_cast< SceneObject* >( mSelected[ i ] ); + if( !serverObj ) + continue; + + // client + SceneObject * clientObj = getClientObj( serverObj ); + if(!clientObj) + continue; + + clientObj->setHidden(hide); + + // server + serverObj->setHidden(hide); + } +} + +void WorldEditor::lockSelection(bool lock) +{ + // + for(U32 i = 0; i < mSelected.size(); i++) + mSelected[i]->setLocked(lock); +} + +//------------------------------------------------------------------------------ +// the centroid get's moved to the drop point... +void WorldEditor::dropSelection(Selection & sel) +{ + if(!sel.size()) + return; + + setDirty(); + + Point3F centroid = mObjectsUseBoxCenter ? sel.getBoxCentroid() : sel.getCentroid(); + + switch(mDropType) + { + case DropAtCentroid: + // already there + break; + + case DropAtOrigin: + { + if(mDropAtBounds && !sel.containsGlobalBounds()) + { + const Point3F& boxCenter = sel.getBoxCentroid(); + const Box3F& bounds = sel.getBoxBounds(); + Point3F offset = -boxCenter; + offset.z += bounds.len_z() * 0.5f; + + sel.offset(offset); + } + else + sel.offset(Point3F(-centroid)); + + break; + } + + case DropAtCameraWithRot: + { + Point3F center = centroid; + if(mDropAtBounds && !sel.containsGlobalBounds()) + center = sel.getBoxBottomCenter(); + + sel.offset(Point3F(smCamPos - center)); + sel.orient(smCamMatrix, center); + break; + } + + case DropAtCamera: + { + Point3F center = centroid; + if(mDropAtBounds && !sel.containsGlobalBounds()) + sel.getBoxBottomCenter(); + + sel.offset(Point3F(smCamPos - center)); + break; + } + + case DropBelowCamera: + { + Point3F center = centroid; + if(mDropAtBounds && !sel.containsGlobalBounds()) + center = sel.getBoxBottomCenter(); + + Point3F offset = smCamPos - center; + offset.z -= mDropBelowCameraOffset; + sel.offset(offset); + break; + } + + case DropAtScreenCenter: + { + // Use the center of the selection bounds + Point3F center = sel.getBoxCentroid(); + + Gui3DMouseEvent event; + event.pos = smCamPos; + + // Calculate the center of the sceen (in global screen coordinates) + Point2I offset = localToGlobalCoord(Point2I(0,0)); + Point3F sp(F32(offset.x + F32(getExtent().x / 2)), F32(offset.y + (getExtent().y / 2)), 1.0f); + + // Calculate the view distance to fit the selection + // within the camera's view. + const Box3F bounds = sel.getBoxBounds(); + F32 radius = bounds.len()*0.5f; + F32 viewdist = calculateViewDistance(radius) * mDropAtScreenCenterScalar; + + // Be careful of infinite sized objects, or just large ones in general. + if(viewdist > mDropAtScreenCenterMax) + viewdist = mDropAtScreenCenterMax; + + // Position the selection + Point3F wp; + unproject(sp, &wp); + event.vec = wp - smCamPos; + event.vec.normalizeSafe(); + event.vec *= viewdist; + sel.offset(Point3F(event.pos - center) += event.vec); + + break; + } + + case DropToTerrain: + { + terrainSnapSelection(sel, 0, mGizmo->getPosition(), true); + break; + } + + case DropBelowSelection: + { + dropBelowSelection(sel, centroid, mDropAtBounds); + break; + } + } + + // + updateClientTransforms(sel); +} + +void WorldEditor::dropBelowSelection(Selection & sel, const Point3F & centroid, bool useBottomBounds) +{ + if(!sel.size()) + return; + + Point3F start; + if(useBottomBounds && !sel.containsGlobalBounds()) + start = sel.getBoxBottomCenter(); + else + start = centroid; + + Point3F end = start; + end.z -= 4000.f; + + sel.disableCollision(); // Make sure we don't hit ourselves. + + RayInfo ri; + bool hit; + if(mBoundingBoxCollision) + hit = gServerContainer.collideBox(start, end, STATIC_COLLISION_MASK, &ri); + else + hit = gServerContainer.castRay(start, end, STATIC_COLLISION_MASK, &ri); + + sel.enableCollision(); + + if( hit ) + sel.offset(ri.point - start); +} + +//------------------------------------------------------------------------------ + +void WorldEditor::terrainSnapSelection(Selection& sel, U8 modifier, Point3F gizmoPos, bool forceStick) +{ + mStuckToGround = false; + + if ( !mStickToGround && !forceStick ) + return; + + if(!sel.size()) + return; + + if(sel.containsGlobalBounds()) + return; + + Point3F centroid; + if(mDropAtBounds && !sel.containsGlobalBounds()) + centroid = sel.getBoxBottomCenter(); + else + centroid = mObjectsUseBoxCenter ? sel.getBoxCentroid() : sel.getCentroid(); + + Point3F start = centroid; + Point3F end = start; + start.z -= 2000; + end.z += 2000.f; + + sel.disableCollision(); // Make sure we don't hit ourselves. + + RayInfo ri; + bool hit; + if(mBoundingBoxCollision) + hit = gServerContainer.collideBox(start, end, TerrainObjectType, &ri); + else + hit = gServerContainer.castRay(start, end, TerrainObjectType, &ri); + + sel.enableCollision(); + + if( hit ) + { + mStuckToGround = true; + + sel.offset(ri.point - centroid); + + if(mTerrainSnapAlignment != AlignNone) + { + EulerF rot(0.0f, 0.0f, 0.0f); // Equivalent to AlignPosY + switch(mTerrainSnapAlignment) + { + case AlignPosX: + rot.set(0.0f, 0.0f, mDegToRad(-90.0f)); + break; + + case AlignPosZ: + rot.set(mDegToRad(90.0f), 0.0f, mDegToRad(180.0f)); + break; + + case AlignNegX: + rot.set(0.0f, 0.0f, mDegToRad(90.0f)); + break; + + case AlignNegY: + rot.set(0.0f, 0.0f, mDegToRad(180.0f)); + break; + + case AlignNegZ: + rot.set(mDegToRad(-90.0f), 0.0f, mDegToRad(180.0f)); + break; + } + + MatrixF mat = MathUtils::createOrientFromDir(ri.normal); + MatrixF rotMat(rot); + + sel.orient(mat.mul(rotMat), Point3F::Zero); + } + } +} + +void WorldEditor::softSnapSelection(Selection& sel, U8 modifier, Point3F gizmoPos) +{ + mSoftSnapIsStuck = false; + mSoftSnapActivated = false; + + // If soft snap is activated, holding CTRL will temporarily deactivate it. + // Conversely, if soft snapping is deactivated, holding CTRL will activate it. + if( (mSoftSnap && (modifier & SI_PRIMARY_CTRL)) || (!mSoftSnap && !(modifier & SI_PRIMARY_CTRL)) ) + return; + + if(!sel.size()) + return; + + if(sel.containsGlobalBounds()) + return; + + mSoftSnapActivated = true; + + Point3F centroid = mObjectsUseBoxCenter ? sel.getBoxCentroid() : sel.getCentroid(); + + // Find objects we may stick against + Vector foundobjs; + + SceneObject *controlObj = getControlObject(); + if ( controlObj ) + controlObj->disableCollision(); + + sel.disableCollision(); + + if(mSoftSnapSizeByBounds) + { + mSoftSnapBounds = sel.getBoxBounds(); + mSoftSnapBounds.setCenter(centroid); + } + else + { + mSoftSnapBounds.set(Point3F(mSoftSnapSize, mSoftSnapSize, mSoftSnapSize)); + mSoftSnapBounds.setCenter(centroid); + } + + mSoftSnapPreBounds = mSoftSnapBounds; + mSoftSnapPreBounds.setCenter(gizmoPos); + + SphereF sphere(centroid, mSoftSnapBounds.len()*0.5f); + + gServerContainer.findObjectList(mSoftSnapBounds, 0xFFFFFFFF, &foundobjs); + + sel.enableCollision(); + + if ( controlObj ) + controlObj->enableCollision(); + + ConcretePolyList polys; + for(S32 i=0; igetTransform()), so->getScale()); + polys.setObject(so); + so->buildRenderedPolyList(&polys, mSoftSnapBounds, sphere); + } + + // Calculate sticky point + bool found = false; + F32 foundDist = 1e10; + Point3F foundPoint(0.0f, 0.0f, 0.0f); + PlaneF foundPlane; + MathUtils::IntersectInfo info; + + if(mSoftSnapDebugRender) + { + mSoftSnapDebugPoint.set(0.0f, 0.0f, 0.0f); + mSoftSnapDebugTriangles.clear(); + } + + F32 backfaceToleranceSize = mSoftSnapBackfaceTolerance*mSoftSnapSize; + for(S32 i=0; i= 3) + { + S32 vertind[3]; + vertind[0] = polys.mIndexList[p.vertexStart]; + vertind[1] = polys.mIndexList[p.vertexStart + 1]; + vertind[2] = polys.mIndexList[p.vertexStart + 2]; + + // Distance to the triangle + F32 d = MathUtils::mTriangleDistance(polys.mVertexList[vertind[0]], polys.mVertexList[vertind[1]], polys.mVertexList[vertind[2]], centroid, &info); + + // Cull backface polys that are not within tolerance + if(p.plane.whichSide(centroid) == PlaneF::Back && d > backfaceToleranceSize) + continue; + + bool changed = false; + if(d < foundDist) + { + changed = true; + found = true; + foundDist = d; + foundPoint = info.segment.p1; + foundPlane = p.plane; + + if(mSoftSnapRenderTriangle) + { + mSoftSnapTriangle.p0 = polys.mVertexList[vertind[0]]; + mSoftSnapTriangle.p1 = polys.mVertexList[vertind[1]]; + mSoftSnapTriangle.p2 = polys.mVertexList[vertind[2]]; + } + } + + if(mSoftSnapDebugRender) + { + Triangle debugTri; + debugTri.p0 = polys.mVertexList[vertind[0]]; + debugTri.p1 = polys.mVertexList[vertind[1]]; + debugTri.p2 = polys.mVertexList[vertind[2]]; + mSoftSnapDebugTriangles.push_back(debugTri); + + if(changed) + { + mSoftSnapDebugSnapTri = debugTri; + mSoftSnapDebugPoint = foundPoint; + } + } + } + } + + if(found) + { + sel.offset(foundPoint - centroid); + + if(mSoftSnapAlignment != AlignNone) + { + EulerF rot(0.0f, 0.0f, 0.0f); // Equivalent to AlignPosY + switch(mSoftSnapAlignment) + { + case AlignPosX: + rot.set(0.0f, 0.0f, mDegToRad(-90.0f)); + break; + + case AlignPosZ: + rot.set(mDegToRad(90.0f), 0.0f, mDegToRad(180.0f)); + break; + + case AlignNegX: + rot.set(0.0f, 0.0f, mDegToRad(90.0f)); + break; + + case AlignNegY: + rot.set(0.0f, 0.0f, mDegToRad(180.0f)); + break; + + case AlignNegZ: + rot.set(mDegToRad(-90.0f), 0.0f, mDegToRad(180.0f)); + break; + } + + MatrixF mat = MathUtils::createOrientFromDir(foundPlane.getNormal()); + MatrixF rotMat(rot); + + sel.orient(mat.mul(rotMat), Point3F::Zero); + } + } + + mSoftSnapIsStuck = found; +} + +//------------------------------------------------------------------------------ + +SceneObject * WorldEditor::getControlObject() +{ + GameConnection * connection = GameConnection::getLocalClientConnection(); + if(connection) + return(dynamic_cast(connection->getControlObject())); + return(0); +} + +bool WorldEditor::collide( const Gui3DMouseEvent &event, SceneObject **hitObj ) +{ + if ( mBoundingBoxCollision ) + { + // Raycast against sceneObject bounding boxes... + + SceneObject *controlObj = getControlObject(); + if ( controlObj ) + controlObj->disableCollision(); + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * smProjectDistance; + RayInfo ri; + + bool hit = gServerContainer.collideBox(startPnt, endPnt, 0xFFFFFFFF, &ri); + + if ( controlObj ) + controlObj->enableCollision(); + + if ( hit ) + *hitObj = ri.object; + + return hit; + } + + // Collide against the screen-space class icons... + + S32 collidedIconIdx = -1; + F32 collidedIconDist = F32_MAX; + + for ( U32 i = 0; i < mIcons.size(); i++ ) + { + const IconObject &icon = mIcons[i]; + + if ( icon.rect.pointInRect( event.mousePoint ) && + icon.dist < collidedIconDist ) + { + collidedIconIdx = i; + collidedIconDist = icon.dist; + } + } + + if ( collidedIconIdx != -1 ) + { + *hitObj = mIcons[collidedIconIdx].object; + return true; + } + + // No icon hit so check against the mesh + if ( mObjectMeshCollision ) + { + SceneObject *controlObj = getControlObject(); + if ( controlObj ) + controlObj->disableCollision(); + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * smProjectDistance; + RayInfo ri; + + bool hit = gServerContainer.castRayRendered(startPnt, endPnt, 0xFFFFFFFF, &ri); + if(hit && ri.object && ri.object->getTypeMask() & (TerrainObjectType)) + { + // We don't want to mesh select terrain + hit = false; + } + + if ( controlObj ) + controlObj->enableCollision(); + + if ( hit ) + *hitObj = ri.object; + + return hit; + } + + return false; +} + +//------------------------------------------------------------------------------ +// main render functions + +void WorldEditor::renderSelectionWorldBox(Selection & sel) +{ + + if(!mRenderSelectionBox) + return; + + // + if(!sel.size()) + return; + + // build the world bounds + Box3F selBox( TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, TypeTraits< F32 >::MAX, + TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN, TypeTraits< F32 >::MIN ); + + U32 i; + for(i = 1; i < sel.size(); i++) + { + SceneObject* object = dynamic_cast< SceneObject* >( sel[ i ] ); + if( !object ) + continue; + + const Box3F & wBox = object->getWorldBox(); + selBox.minExtents.setMin(wBox.minExtents); + selBox.maxExtents.setMax(wBox.maxExtents); + } + + // + + // GFX2_RENDER_MEGE + /* + PrimBuild::color( mSelectionBoxColor ); + + GFX->setCullMode( GFXCullNone ); + GFX->setAlphaBlendEnable( false ); + GFX->setTextureStageColorOp( 0, GFXTOPDisable ); + GFX->setZEnable( true ); + + // create the box points + Point3F projPnts[8]; + for(i = 0; i < 8; i++) + { + Point3F pnt; + pnt.set(BoxPnts[i].x ? selBox.maxExtents.x : selBox.minExtents.x, + BoxPnts[i].y ? selBox.maxExtents.y : selBox.minExtents.y, + BoxPnts[i].z ? selBox.maxExtents.z : selBox.minExtents.z); + projPnts[i] = pnt; + } + + // do the box + for(U32 j = 0; j < 6; j++) + { + PrimBuild::begin( GFXLineStrip, 4 ); + for(U32 k = 0; k < 4; k++) + { + PrimBuild::vertex3fv( projPnts[BoxVerts[j][k]] ); + } + PrimBuild::end(); + } + + GFX->setZEnable( true ); + */ +} + +void WorldEditor::renderObjectBox( SceneObject *obj, const ColorI &color ) +{ + if ( mRenderObjectBoxSB.isNull() ) + { + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setZReadWrite( true, true ); + mRenderObjectBoxSB = GFX->createStateBlock( desc ); + } + + GFX->setStateBlock(mRenderObjectBoxSB); + + GFXTransformSaver saver; + + Box3F objBox = obj->getObjBox(); + Point3F objScale = obj->getScale(); + Point3F boxScale = objBox.getExtents(); + Point3F boxCenter = obj->getWorldBox().getCenter(); + + MatrixF objMat = obj->getTransform(); + objMat.scale(objScale); + objMat.scale(boxScale); + objMat.setPosition(boxCenter); + + //GFX->multWorld( objMat ); + + PrimBuild::color( ColorI(255,255,255,255) ); + PrimBuild::begin( GFXLineList, 48 ); + + //Box3F objBox = obj->getObjBox(); + //Point3F size = objBox.getExtents(); + //Point3F halfSize = size * 0.5f; + + static const Point3F cubePoints[8] = + { + Point3F(-0.5, -0.5, -0.5), Point3F(-0.5, -0.5, 0.5), Point3F(-0.5, 0.5, -0.5), Point3F(-0.5, 0.5, 0.5), + Point3F( 0.5, -0.5, -0.5), Point3F( 0.5, -0.5, 0.5), Point3F( 0.5, 0.5, -0.5), Point3F( 0.5, 0.5, 0.5) + }; + + // 8 corner points of the box + for ( U32 i = 0; i < 8; i++ ) + { + //const Point3F &start = cubePoints[i]; + + // 3 lines per corner point + for ( U32 j = 0; j < 3; j++ ) + { + Point3F start = cubePoints[i]; + Point3F end = start; + end[j] *= 0.8f; + + objMat.mulP(start); + PrimBuild::vertex3fv(start); + objMat.mulP(end); + PrimBuild::vertex3fv(end); + } + } + + PrimBuild::end(); +} + +void WorldEditor::renderObjectFace(SceneObject * obj, const VectorF & normal, const ColorI & col) +{ + if ( mRenderObjectFaceSB.isNull() ) + { + GFXStateBlockDesc desc; + desc.setCullMode( GFXCullNone ); + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.setZReadWrite( false ); + mRenderObjectFaceSB = GFX->createStateBlock( desc ); + } + + GFX->setStateBlock(mRenderObjectFaceSB); + + // get the normal index + VectorF objNorm; + obj->getWorldTransform().mulV(normal, &objNorm); + + U32 normI = getBoxNormalIndex(objNorm); + + // + Box3F box = obj->getObjBox(); + MatrixF mat = obj->getTransform(); + VectorF scale = obj->getScale(); + + Point3F projPnts[4]; + for(U32 i = 0; i < 4; i++) + { + Point3F pnt; + pnt.set(BoxPnts[BoxVerts[normI][i]].x ? box.maxExtents.x : box.minExtents.x, + BoxPnts[BoxVerts[normI][i]].y ? box.maxExtents.y : box.minExtents.y, + BoxPnts[BoxVerts[normI][i]].z ? box.maxExtents.z : box.minExtents.z); + + // scale it + pnt.convolve(scale); + mat.mulP(pnt, &projPnts[i]); + } + + PrimBuild::color( col ); + + PrimBuild::begin( GFXTriangleFan, 4 ); + for(U32 k = 0; k < 4; k++) + { + PrimBuild::vertex3f(projPnts[k].x, projPnts[k].y, projPnts[k].z); + } + PrimBuild::end(); +} + +void WorldEditor::renderMousePopupInfo() +{ + if ( !mMouseDragged ) + return; + + + if ( mGizmoProfile->mode == NoneMode ) + return; + + char buf[256]; + + switch ( mGizmoProfile->mode ) + { + case MoveMode: + { + if ( !mSelected.size() ) + return; + + Point3F pos = getSelectionCentroid(); + dSprintf(buf, sizeof(buf), "x: %0.3f, y: %0.3f, z: %0.3f", pos.x, pos.y, pos.z); + + break; + } + + case RotateMode: + { + if ( !bool(mHitObject) || (mSelected.size() != 1) ) + return; + + // print out the angle-axis + AngAxisF aa(mHitObject->getTransform()); + + dSprintf(buf, sizeof(buf), "x: %0.3f, y: %0.3f, z: %0.3f, a: %0.3f", + aa.axis.x, aa.axis.y, aa.axis.z, mRadToDeg(aa.angle)); + + break; + } + + case ScaleMode: + { + if ( !bool(mHitObject) || (mSelected.size() != 1) ) + return; + + VectorF scale = mHitObject->getScale(); + + Box3F box = mHitObject->getObjBox(); + box.minExtents.convolve(scale); + box.maxExtents.convolve(scale); + + box.maxExtents -= box.minExtents; + dSprintf(buf, sizeof(buf), "w: %0.3f, h: %0.3f, d: %0.3f", box.maxExtents.x, box.maxExtents.y, box.maxExtents.z); + + break; + } + + default: + return; + } + + U32 width = mProfile->mFont->getStrWidth((const UTF8 *)buf); + Point2I posi( mLastMouseEvent.mousePoint.x, mLastMouseEvent.mousePoint.y + 12 ); + + if ( mRenderPopupBackground ) + { + Point2I minPt(posi.x - width / 2 - 2, posi.y - 1); + Point2I maxPt(posi.x + width / 2 + 2, posi.y + mProfile->mFont->getHeight() + 1); + + GFX->getDrawUtil()->drawRectFill(minPt, maxPt, mPopupBackgroundColor); + } + + GFX->getDrawUtil()->setBitmapModulation(mPopupTextColor); + GFX->getDrawUtil()->drawText(mProfile->mFont, Point2I(posi.x - width / 2, posi.y), buf); +} + +void WorldEditor::renderPaths(SimObject *obj) +{ + if (obj == NULL) + return; + bool selected = false; + + // Loop through subsets + if (SimSet *set = dynamic_cast(obj)) + for(SimSetIterator itr(set); *itr; ++itr) { + renderPaths(*itr); + if ((*itr)->isSelected()) + selected = true; + } + + // Render the path if it, or any of it's immediate sub-objects, is selected. + if (SimPath::Path *path = dynamic_cast(obj)) + if (selected || path->isSelected()) + renderSplinePath(path); +} + + +void WorldEditor::renderSplinePath(SimPath::Path *path) +{ + // at the time of writing the path properties are not part of the path object + // so we don't know to render it looping, splined, linear etc. + // for now we render all paths splined+looping + + Vector positions; + Vector rotations; + + path->sortMarkers(); + CameraSpline spline; + + for(SimSetIterator itr(path); *itr; ++itr) + { + Marker *pathmarker = dynamic_cast(*itr); + if (!pathmarker) + continue; + Point3F pos; + pathmarker->getTransform().getColumn(3, &pos); + + QuatF rot; + rot.set(pathmarker->getTransform()); + CameraSpline::Knot::Type type; + switch (pathmarker->mKnotType) + { + case Marker::KnotTypePositionOnly: type = CameraSpline::Knot::POSITION_ONLY; break; + case Marker::KnotTypeKink: type = CameraSpline::Knot::KINK; break; + case Marker::KnotTypeNormal: + default: type = CameraSpline::Knot::NORMAL; break; + + } + + CameraSpline::Knot::Path path; + switch (pathmarker->mSmoothingType) + { + case Marker::SmoothingTypeLinear: path = CameraSpline::Knot::LINEAR; break; + case Marker::SmoothingTypeSpline: + default: path = CameraSpline::Knot::SPLINE; break; + + } + + spline.push_back(new CameraSpline::Knot(pos, rot, 1.0f, type, path)); + } + + F32 t = 0.0f; + S32 size = spline.size(); + if (size <= 1) + return; + + // DEBUG + //spline.renderTimeMap(); + + if (mSplineSB.isNull()) + { + GFXStateBlockDesc desc; + + desc.setCullMode( GFXCullNone ); + desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + desc.samplersDefined = true; + desc.samplers[0].textureColorOp = GFXTOPDisable; + + mSplineSB = GFX->createStateBlock( desc ); + } + + GFX->setStateBlock(mSplineSB); + + + if (path->isLooping()) + { + CameraSpline::Knot *front = new CameraSpline::Knot(*spline.front()); + CameraSpline::Knot *back = new CameraSpline::Knot(*spline.back()); + spline.push_back(front); + spline.push_front(back); + t = 1.0f; + size += 2; + } + + VectorF a(-0.45f, -0.55f, 0.0f); + VectorF b( 0.0f, 0.55f, 0.0f); + VectorF c( 0.45f, -0.55f, 0.0f); + + U32 vCount=0; + + F32 tmpT = t; + while (tmpT < size - 1) + { + tmpT = spline.advanceDist(tmpT, 4.0f); + vCount++; + } + + // Build vertex buffer + + U32 batchSize = vCount; + + if(vCount > 4000) + batchSize = 4000; + + GFXVertexBufferHandle vb; + vb.set(GFX, 3*batchSize, GFXBufferTypeVolatile); + vb.lock(); + + U32 vIdx=0; + + while (t < size - 1) + { + CameraSpline::Knot k; + spline.value(t, &k); + t = spline.advanceDist(t, 4.0f); + + k.mRotation.mulP(a, &vb[vIdx+0].point); + k.mRotation.mulP(b, &vb[vIdx+1].point); + k.mRotation.mulP(c, &vb[vIdx+2].point); + + vb[vIdx+0].point += k.mPosition; + vb[vIdx+1].point += k.mPosition; + vb[vIdx+2].point += k.mPosition; + + vb[vIdx+0].color.set(0, 255, 0, 100); + vb[vIdx+1].color.set(0, 255, 0, 100); + vb[vIdx+2].color.set(0, 255, 0, 100); + + // vb[vIdx+3] = vb[vIdx+1]; + + vIdx+=3; + + // Do we have to knock it out? + if(vIdx > 3 * batchSize - 10) + { + vb.unlock(); + + // Render the buffer + GFX->setVertexBuffer(vb); + GFX->drawPrimitive(GFXTriangleList,0,vIdx/3); + + // Reset for next pass... + vIdx = 0; + vb.lock(); + } + } + + vb.unlock(); + + // Render the buffer + GFX->setVertexBuffer(vb); + //GFX->drawPrimitive(GFXLineStrip,0,3); + + if(vIdx) + GFX->drawPrimitive(GFXTriangleList,0,vIdx/3); +} + +void WorldEditor::renderScreenObj( SceneObject *obj, Point3F projPos ) +{ + // do not render control object stuff + if(obj == getControlObject() || obj->isHidden() ) + return; + + GFXDrawUtil *drawer = GFX->getDrawUtil(); + + // Lookup the ClassIcon - TextureHandle + GFXTexHandle classIcon = gEditorIcons.findIcon( obj ); + + if ( classIcon.isNull() ) + classIcon = mDefaultClassEntry.mDefaultHandle; + + U32 iconWidth = classIcon->getWidth(); + U32 iconHeight = classIcon->getHeight(); + + bool isHighlight = ( obj == mHitObject || mDragSelected.objInSet(obj) ); + + if ( isHighlight ) + { + iconWidth += 0; + iconHeight += 0; + } + + Point2I sPos( (S32)projPos.x, (S32)projPos.y ); + //if ( obj->isSelected() ) + // sPos.y += 4; + Point2I renderPos = sPos; + renderPos.x -= iconWidth / 2; + renderPos.y -= iconHeight / 2; + + Point2I iconSize( iconWidth, iconHeight ); + + RectI renderRect( renderPos, iconSize ); + + // + if ( mRenderObjHandle && !obj->isSelected() ) + { + if ( isHighlight ) + drawer->setBitmapModulation( ColorI(255,255,255,255) ); + else + drawer->setBitmapModulation( ColorI(255,255,255,125) ); + + drawer->drawBitmapStretch( classIcon, renderRect ); + drawer->clearBitmapModulation(); + + if ( obj->isLocked() ) + drawer->drawBitmap( mDefaultClassEntry.mLockedHandle, renderPos ); + + // Save an IconObject for performing icon-click testing later. + { + IconObject icon; + icon.object = obj; + icon.rect = renderRect; + icon.dist = projPos.z; + mIcons.push_back( icon ); + } + } + + // + if ( mRenderObjText && ( obj == mHitObject || obj->isSelected() ) ) + { + const char * str = parseObjectFormat(obj, mObjTextFormat); + + Point2I extent(mProfile->mFont->getStrWidth((const UTF8 *)str), mProfile->mFont->getHeight()); + + Point2I pos(sPos); + + if(mRenderObjHandle) + { + pos.x += (classIcon->getWidth() / 2) - (extent.x / 2); + pos.y += (classIcon->getHeight() / 2) + 3; + } + + + if(mGizmoProfile->mode == NoneMode){ + + drawer->drawBitmapStretch( classIcon, renderRect ); + drawer->setBitmapModulation( ColorI(255,255,255,255) ); + drawer->drawText(mProfile->mFont, pos, str); + if ( obj->isLocked() ) + drawer->drawBitmap( mDefaultClassEntry.mLockedHandle, renderPos ); + + // Save an IconObject for performing icon-click testing later. + { + IconObject icon; + icon.object = obj; + icon.rect = renderRect; + icon.dist = projPos.z; + mIcons.push_back( icon ); + } + }else{ + drawer->setBitmapModulation(mObjectTextColor); + drawer->drawText(mProfile->mFont, pos, str); + }; + } +} + +//------------------------------------------------------------------------------ +// ClassInfo stuff + +WorldEditor::ClassInfo::~ClassInfo() +{ + for(U32 i = 0; i < mEntries.size(); i++) + delete mEntries[i]; +} + +bool WorldEditor::objClassIgnored(const SimObject * obj) +{ + ClassInfo::Entry * entry = getClassEntry(obj); + if(mToggleIgnoreList) + return(!(entry ? entry->mIgnoreCollision : false)); + else + return(entry ? entry->mIgnoreCollision : false); +} + +WorldEditor::ClassInfo::Entry * WorldEditor::getClassEntry(StringTableEntry name) +{ + AssertFatal(name, "WorldEditor::getClassEntry - invalid args"); + for(U32 i = 0; i < mClassInfo.mEntries.size(); i++) + if(!dStricmp(name, mClassInfo.mEntries[i]->mName)) + return(mClassInfo.mEntries[i]); + return(0); +} + +WorldEditor::ClassInfo::Entry * WorldEditor::getClassEntry(const SimObject * obj) +{ + AssertFatal(obj, "WorldEditor::getClassEntry - invalid args"); + return(getClassEntry(obj->getClassName())); +} + +bool WorldEditor::addClassEntry(ClassInfo::Entry * entry) +{ + AssertFatal(entry, "WorldEditor::addClassEntry - invalid args"); + if(getClassEntry(entry->mName)) + return(false); + + mClassInfo.mEntries.push_back(entry); + return(true); +} + +//------------------------------------------------------------------------------ +// Mouse cursor stuff +void WorldEditor::setCursor(U32 cursor) +{ + mCurrentCursor = cursor; +} + +//------------------------------------------------------------------------------ +Signal WorldEditor::smRenderSceneSignal; + +WorldEditor::WorldEditor() : mCurrentCursor(PlatformCursorController::curArrow) +{ + // init the field data + mDropType = DropAtScreenCenter; + mBoundingBoxCollision = true; + mObjectMeshCollision = true; + mRenderPopupBackground = true; + mPopupBackgroundColor.set(100,100,100); + mPopupTextColor.set(255,255,0); + mSelectHandle = StringTable->insert("tools/worldEditor/images/SelectHandle"); + mDefaultHandle = StringTable->insert("tools/worldEditor/images/DefaultHandle"); + mLockedHandle = StringTable->insert("tools/worldEditor/images/LockedHandle"); + mObjectTextColor.set(255,255,255); + mObjectsUseBoxCenter = true; + + mObjSelectColor.set(255,0,0); + mObjMouseOverSelectColor.set(0,0,255); + mObjMouseOverColor.set(0,255,0); + mShowMousePopupInfo = true; + mDragRectColor.set(255,255,0); + mRenderObjText = true; + mRenderObjHandle = true; + mObjTextFormat = StringTable->insert("$id$: $name|internal$"); + mFaceSelectColor.set(0,0,100,100); + mRenderSelectionBox = true; + mSelectionBoxColor.set(255,255,0); + mSelectionLocked = false; + + mToggleIgnoreList = false; + + mIsDirty = false; + + mRedirectID = 0; + + // + mHitObject = NULL; + + // + //mDefaultMode = mCurrentMode = Move; + mMouseDown = false; + mDragSelect = false; + + mStickToGround = false; + mStuckToGround = false; + mTerrainSnapAlignment = AlignNone; + mDropAtBounds = false; + mDropBelowCameraOffset = 15.0f; + mDropAtScreenCenterScalar = 1.0f; + mDropAtScreenCenterMax = 100.0f; + + // + mSelected.autoSelect(true); + mDragSelected.autoSelect(false); + + // + mSoftSnap = false; + mSoftSnapActivated = false; + mSoftSnapIsStuck = false; + mSoftSnapAlignment = AlignNone; + mSoftSnapRender = true; + mSoftSnapRenderTriangle = false; + mSoftSnapSizeByBounds = false; + mSoftSnapSize = 2.0f; + mSoftSnapBackfaceTolerance = 0.5f; + mSoftSnapDebugRender = false; + mSoftSnapDebugPoint.set(0.0f, 0.0f, 0.0f); +} + +WorldEditor::~WorldEditor() +{ +} + +//------------------------------------------------------------------------------ + +bool WorldEditor::onAdd() +{ + if(!Parent::onAdd()) + return(false); + + // create the default class entry + mDefaultClassEntry.mName = 0; + mDefaultClassEntry.mIgnoreCollision = false; + mDefaultClassEntry.mDefaultHandle = GFXTexHandle(mDefaultHandle, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDefaultClassEntry.mDefaultHandle (line %d)", __FUNCTION__, __LINE__)); + mDefaultClassEntry.mSelectHandle = GFXTexHandle(mSelectHandle, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDefaultClassEntry.mSelectHandle (line %d)", __FUNCTION__, __LINE__)); + mDefaultClassEntry.mLockedHandle = GFXTexHandle(mLockedHandle, &GFXDefaultStaticDiffuseProfile, avar("%s() - mDefaultClassEntry.mLockedHandle (line %d)", __FUNCTION__, __LINE__)); + + if(!(mDefaultClassEntry.mDefaultHandle && mDefaultClassEntry.mSelectHandle && mDefaultClassEntry.mLockedHandle)) + return false; + + //mGizmo = new Gizmo(); + //mGizmo->registerObject("WorldEditorGizmo"); + mGizmo->assignName("WorldEditorGizmo"); + + return true; +} + +//------------------------------------------------------------------------------ + +void WorldEditor::onEditorEnable() +{ + // go through and copy the hidden field to the client objects... + for(SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr) + { + SceneObject * obj = dynamic_cast(*itr); + if(!obj) + continue; + + // only work with a server obj... + if(obj->isClientObject()) + continue; + + // grab the client object + SceneObject * clientObj = getClientObj(obj); + if(!clientObj) + continue; + + // + clientObj->setHidden(obj->isHidden()); + } +} + +//------------------------------------------------------------------------------ + +void WorldEditor::get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event) +{ + TORQUE_UNUSED(event); + cursor = NULL; + visible = false; + + GuiCanvas *pRoot = getRoot(); + if( !pRoot ) + return Parent::get3DCursor(cursor,visible,event); + + if(pRoot->mCursorChanged != mCurrentCursor) + { + PlatformWindow *pWindow = static_cast(getRoot())->getPlatformWindow(); + AssertFatal(pWindow != NULL,"GuiControl without owning platform window! This should not be possible."); + PlatformCursorController *pController = pWindow->getCursorController(); + AssertFatal(pController != NULL,"PlatformWindow without an owned CursorController!"); + + // We've already changed the cursor, + // so set it back before we change it again. + if(pRoot->mCursorChanged != -1) + pController->popCursor(); + + // Now change the cursor shape + pController->pushCursor(mCurrentCursor); + pRoot->mCursorChanged = mCurrentCursor; + } +} + +void WorldEditor::on3DMouseMove(const Gui3DMouseEvent & event) +{ + setCursor(PlatformCursorController::curArrow); + mHitObject = NULL; + + // + mUsingAxisGizmo = false; + + if ( mSelected.size() > 0 ) + { + mGizmo->on3DMouseMove( event ); + + if ( mGizmo->getSelection() != Gizmo::None ) + { + mUsingAxisGizmo = true; + mHitObject = dynamic_cast< SceneObject* >( mSelected[0] ); + } + } + + if ( !mHitObject ) + { + SceneObject *hitObj = NULL; + if ( collide(event, &hitObj) && !objClassIgnored(hitObj) ) + { + mHitObject = hitObj; + } + } + + mLastMouseEvent = event; +} + +void WorldEditor::on3DMouseDown(const Gui3DMouseEvent & event) +{ + mMouseDown = true; + mMouseDragged = false; + mPerformedDragCopy = false; + mLastMouseDownEvent = event; + + mouseLock(); + + // check gizmo first + mUsingAxisGizmo = false; + mNoMouseDrag = false; + + if ( mSelected.size() > 0 ) + { + mGizmo->on3DMouseDown( event ); + + if ( mGizmo->getSelection() != Gizmo::None ) + { + mUsingAxisGizmo = true; + mHitObject = dynamic_cast< SceneObject* >( mSelected[0] ); + + return; + } + } + + SceneObject *hitObj = NULL; + if ( collide( event, &hitObj ) && !objClassIgnored( hitObj ) ) + { + mPossibleHitObject = hitObj; + mNoMouseDrag = true; + } + else if ( !mSelectionLocked ) + { + if ( !(event.modifier & SI_SHIFT) ) + clearSelection(); + + mDragSelect = true; + mDragSelected.clear(); + mDragRect.set( Point2I(event.mousePoint), Point2I(0,0) ); + mDragStart = event.mousePoint; + } + + mLastMouseEvent = event; +} + +void WorldEditor::on3DMouseUp( const Gui3DMouseEvent &event ) +{ + mMouseDown = false; + mStuckToGround = false; + mSoftSnapIsStuck = false; + mSoftSnapActivated = false; + mUsingAxisGizmo = false; + mGizmo->on3DMouseUp(event); + + // check if selecting objects.... + if ( mDragSelect ) + { + mDragSelect = false; + mPossibleHitObject = NULL; + + // add all the objects from the drag selection into the normal selection + clearSelection(); + + for ( U32 i = 0; i < mDragSelected.size(); i++ ) + { + Con::executef( this, "onSelect", avar("%d", mDragSelected[i]->getId()) ); + mSelected.addObject( mDragSelected[i] ); + } + mDragSelected.clear(); + + if ( mSelected.size() ) + { + char buf[16]; + dSprintf( buf, sizeof(buf), "%d", mSelected[0]->getId() ); + + SimObject *obj = NULL; + if ( mRedirectID ) + obj = Sim::findObject( mRedirectID ); + Con::executef( obj ? obj : this, "onClick", buf ); + } + + mouseUnlock(); + return; + } + else if( mPossibleHitObject.isValid() ) + { + if ( !mSelectionLocked ) + { + if ( event.modifier & SI_SHIFT ) + { + mNoMouseDrag = true; + if ( mSelected.objInSet( mPossibleHitObject ) ) + { + mSelected.removeObject( mPossibleHitObject ); + mSelected.storeCurrentCentroid(); + Con::executef( this, "onUnSelect", avar("%d", mPossibleHitObject->getId()) ); + } + else + { + mSelected.addObject( mPossibleHitObject ); + mSelected.storeCurrentCentroid(); + Con::executef( this, "onSelect", avar("%d", mPossibleHitObject->getId()) ); + } + } + else + { + if ( !mSelected.objInSet( mPossibleHitObject ) ) + { + mNoMouseDrag = true; + for ( U32 i = 0; i < mSelected.size(); i++ ) + Con::executef( this, "onUnSelect", avar("%d", mSelected[i]->getId()) ); + + mSelected.clear(); + mSelected.addObject( mPossibleHitObject ); + mSelected.storeCurrentCentroid(); + Con::executef( this, "onSelect", avar("%d", mPossibleHitObject->getId()) ); + } + } + } + + if ( event.mouseClickCount > 1 ) + { + // + char buf[16]; + dSprintf(buf, sizeof(buf), "%d", mPossibleHitObject->getId()); + + SimObject *obj = NULL; + if ( mRedirectID ) + obj = Sim::findObject( mRedirectID ); + Con::executef( obj ? obj : this, "onDblClick", buf ); + } + else + { + char buf[16]; + dSprintf( buf, sizeof(buf), "%d", mPossibleHitObject->getId() ); + + SimObject *obj = NULL; + if ( mRedirectID ) + obj = Sim::findObject( mRedirectID ); + Con::executef( obj ? obj : this, "onClick", buf ); + } + + mHitObject = mPossibleHitObject; + } + + if(mSelected.hasCentroidChanged()) + { + Con::executef( this, "onSelectionCentroidChanged"); + } + + if ( mMouseDragged && mSelected.size() ) + { + if ( mSelected.size() ) + { + if ( isMethod("onEndDrag") ) + { + char buf[16]; + dSprintf( buf, sizeof(buf), "%d", mSelected[0]->getId() ); + + SimObject * obj = 0; + if ( mRedirectID ) + obj = Sim::findObject( mRedirectID ); + Con::executef( obj ? obj : this, "onEndDrag", buf ); + } + } + } + + //if ( mHitObject ) + // mHitObject->inspectPostApply(); + //mHitObject = NULL; + + // + //mHitObject = hitObj; + mouseUnlock(); +} + +void WorldEditor::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + if ( !mMouseDown ) + return; + + if ( mNoMouseDrag ) + { + // Perhaps we should start the drag after all + if( mAbs(mLastMouseDownEvent.mousePoint.x - event.mousePoint.x) > 2 || mAbs(mLastMouseDownEvent.mousePoint.y - event.mousePoint.y) > 2 ) + { + if ( !(event.modifier & SI_SHIFT) ) + clearSelection(); + + mDragSelect = true; + mDragSelected.clear(); + mDragRect.set( Point2I(mLastMouseDownEvent.mousePoint), Point2I(0,0) ); + mDragStart = mLastMouseDownEvent.mousePoint; + + mNoMouseDrag = false; + mHitObject = NULL; + } + else + { + return; + } + } + + // + if ( !mMouseDragged ) + { + if ( !mUsingAxisGizmo ) + { + // vert drag on new object.. reset hit offset + if ( mHitObject && !mSelected.objInSet( mHitObject ) ) + { + if ( !mSelectionLocked ) + mSelected.addObject( mHitObject ); + } + } + + // create and add an undo state + if ( !mDragSelect ) + submitUndo( mSelected ); + + mMouseDragged = true; + } + + // update the drag selection + if ( mDragSelect ) + { + // build the drag selection on the renderScene method - make sure no neg extent! + mDragRect.point.x = (event.mousePoint.x < mDragStart.x) ? event.mousePoint.x : mDragStart.x; + mDragRect.extent.x = (event.mousePoint.x > mDragStart.x) ? event.mousePoint.x - mDragStart.x : mDragStart.x - event.mousePoint.x; + mDragRect.point.y = (event.mousePoint.y < mDragStart.y) ? event.mousePoint.y : mDragStart.y; + mDragRect.extent.y = (event.mousePoint.y > mDragStart.y) ? event.mousePoint.y - mDragStart.y : mDragStart.y - event.mousePoint.y; + return; + } + + if ( !mUsingAxisGizmo && ( !mHitObject || !mSelected.objInSet( mHitObject ) ) ) + return; + + // anything locked? + for ( U32 i = 0; i < mSelected.size(); i++ ) + if ( mSelected[i]->isLocked() ) + return; + + if ( mUsingAxisGizmo ) + mGizmo->on3DMouseDragged(event); + + switch ( mGizmoProfile->mode ) + { + case MoveMode: + + // grabbed axis gizmo? + if ( mUsingAxisGizmo ) + { + // Check if a copy should be made + if ( event.modifier & SI_SHIFT && !mPerformedDragCopy ) + { + mPerformedDragCopy = true; + copySelection(mSelected); + pasteSelection(false); + } + + mSelected.offset( mGizmo->getOffset() ); + + // Handle various sticking + terrainSnapSelection( mSelected, event.modifier, mGizmo->getPosition() ); + softSnapSelection( mSelected, event.modifier, mGizmo->getPosition() ); + + updateClientTransforms( mSelected ); + } + break; + + case ScaleMode: + + // can scale only single selections + if ( mSelected.size() > 1 ) + break; + + if ( mUsingAxisGizmo ) + { + Point3F scale = mGizmo->getScale(); + + mSelected.setScale( scale ); + updateClientTransforms(mSelected); + } + + break; + + case RotateMode: + { + Point3F centroid = getSelectionCentroid(); + EulerF rot = mGizmo->getDeltaRot(); + + mSelected.rotate(rot, centroid); + updateClientTransforms(mSelected); + + break; + } + + default: + break; + } + + mLastMouseEvent = event; +} + +void WorldEditor::on3DMouseEnter(const Gui3DMouseEvent &) +{ +} + +void WorldEditor::on3DMouseLeave(const Gui3DMouseEvent &) +{ +} + +void WorldEditor::on3DRightMouseDown(const Gui3DMouseEvent & event) +{ +} + +void WorldEditor::on3DRightMouseUp(const Gui3DMouseEvent & event) +{ +} + +//------------------------------------------------------------------------------ + +void WorldEditor::updateGuiInfo() +{ + SimObject * obj = 0; + if ( mRedirectID ) + obj = Sim::findObject( mRedirectID ); + + char buf[] = ""; + Con::executef( obj ? obj : this, "onGuiUpdate", buf ); +} + +//------------------------------------------------------------------------------ + +static void findObjectsCallback( SceneObject *obj, void *val ) +{ + Vector * list = (Vector*)val; + list->push_back(obj); +} + +struct DragMeshCallbackData { + WorldEditor* mWorldEditor; + Box3F mBounds; + SphereF mSphereBounds; + Vector mObjects; + EarlyOutPolyList mPolyList; + MatrixF mStandardMat; + Point3F mStandardScale; + + DragMeshCallbackData(WorldEditor* we, Box3F &bounds, SphereF &sphereBounds) + { + mWorldEditor = we; + mBounds = bounds; + mSphereBounds = sphereBounds; + mStandardMat.identity(); + mStandardScale.set(1.0f, 1.0f, 1.0f); + } +}; + +static Frustum gDragFrustum; +static void findDragMeshCallback( SceneObject *obj, void *data ) +{ + DragMeshCallbackData* dragData = reinterpret_cast(data); + + if(dragData->mWorldEditor->objClassIgnored(obj) || (obj->getTypeMask() & (TerrainObjectType | ProjectileObjectType))) + return; + + // Reset the poly list for us + dragData->mPolyList.clear(); + dragData->mPolyList.setTransform(&(dragData->mStandardMat), dragData->mStandardScale); + + // Do the work + obj->buildRenderedPolyList(&(dragData->mPolyList), dragData->mBounds, dragData->mSphereBounds); + if (!dragData->mPolyList.isEmpty()) + { + dragData->mObjects.push_back(obj); + } +} + +void WorldEditor::renderScene( const RectI &updateRect ) +{ + smRenderSceneSignal.trigger(this); + + // Grab this before anything here changes it. + Frustum frustum; + { + F32 left, right, top, bottom, nearPlane, farPlane; + bool isOrtho = false; + GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho ); + + MatrixF cameraMat = GFX->getWorldMatrix(); + cameraMat.inverse(); + + frustum.set( isOrtho, left, right, top, bottom, nearPlane, farPlane, cameraMat ); + } + + // Render the paths + renderPaths(Sim::findObject("MissionGroup")); + + // walk selected + U32 i; + for ( i = 0; i < mSelected.size(); i++ ) + { + if ( (const SceneObject *)mHitObject == mSelected[i] ) + continue; + SceneObject* object = dynamic_cast< SceneObject* >( mSelected[ i ] ); + if( !object ) + continue; + renderObjectBox(object, mObjSelectColor); + } + + // do the drag selection + for ( i = 0; i < mDragSelected.size(); i++ ) + { + SceneObject* object = dynamic_cast< SceneObject* >( mDragSelected[ i ] ); + if( object ) + renderObjectBox(object, mObjSelectColor); + } + + // draw the mouse over obj + if ( bool(mHitObject) ) + { + ColorI & col = mSelected.objInSet(mHitObject) ? mObjMouseOverSelectColor : mObjMouseOverColor; + renderObjectBox(mHitObject, col); + } + + // stuff to do if there is a selection + if ( mSelected.size() ) + { + if ( mRenderSelectionBox ) + renderSelectionWorldBox(mSelected); + + SceneObject* singleSelectedSceneObject = NULL; + if ( mSelected.size() == 1 ) + singleSelectedSceneObject = dynamic_cast< SceneObject* >( mSelected[ 0 ] ); + + MatrixF objMat(true); + if( singleSelectedSceneObject ) + objMat = singleSelectedSceneObject->getTransform(); + + Point3F worldPos = getSelectionCentroid(); + + Point3F objScale = singleSelectedSceneObject ? singleSelectedSceneObject->getScale() : Point3F(1,1,1); + + mGizmo->set( objMat, worldPos, objScale ); + + // Change the gizmo's centroid highlight based on soft sticking state + if( mSoftSnapIsStuck || mStuckToGround ) + mGizmo->setCentroidHandleHighlight( true ); + + mGizmo->renderGizmo(mLastCameraQuery.cameraMatrix); + + // Reset any highlighting + if( mSoftSnapIsStuck || mStuckToGround ) + mGizmo->setCentroidHandleHighlight( false ); + + // Soft snap box rendering + if( (mSoftSnapRender || mSoftSnapRenderTriangle) && mSoftSnapActivated) + { + GFXDrawUtil *drawUtil = GFX->getDrawUtil(); + ColorI color; + + GFXStateBlockDesc desc; + + if(mSoftSnapRenderTriangle && mSoftSnapIsStuck) + { + desc.setBlend( false ); + desc.setZReadWrite( false, false ); + desc.fillMode = GFXFillWireframe; + desc.cullMode = GFXCullNone; + + color.set( 255, 255, 128, 255 ); + drawUtil->drawTriangle( desc, mSoftSnapTriangle.p0, mSoftSnapTriangle.p1, mSoftSnapTriangle.p2, color ); + } + + if(mSoftSnapRender) + { + desc.setBlend( true ); + desc.blendSrc = GFXBlendOne; + desc.blendDest = GFXBlendOne; + desc.blendOp = GFXBlendOpAdd; + desc.setZReadWrite( true, false ); + desc.cullMode = GFXCullCCW; + + color.set( 64, 64, 0, 255 ); + + desc.fillMode = GFXFillWireframe; + drawUtil->drawCube(desc, mSoftSnapPreBounds, color); + + desc.fillMode = GFXFillSolid; + drawUtil->drawSphere(desc, mSoftSnapPreBounds.len()*0.05f, mSoftSnapPreBounds.getCenter(), color); + } + } + } + + // Debug rendering of the soft stick + if(mSoftSnapDebugRender) + { + GFXDrawUtil *drawUtil = GFX->getDrawUtil(); + ColorI color( 255, 0, 0, 255 ); + + GFXStateBlockDesc desc; + desc.setBlend( false ); + desc.setZReadWrite( false, false ); + + if(mSoftSnapIsStuck) + { + drawUtil->drawArrow( desc, getSelectionCentroid(), mSoftSnapDebugPoint, color ); + + color.set(255, 255, 255); + desc.fillMode = GFXFillWireframe; + for(S32 i=0; idrawTriangle( desc, mSoftSnapDebugTriangles[i].p0, mSoftSnapDebugTriangles[i].p1, mSoftSnapDebugTriangles[i].p2, color ); + } + + color.set(255, 255, 0); + desc.fillMode = GFXFillSolid; + desc.cullMode = GFXCullNone; + drawUtil->drawTriangle( desc, mSoftSnapDebugSnapTri.p0, mSoftSnapDebugSnapTri.p1, mSoftSnapDebugSnapTri.p2, color ); + } + + } + + // Now do the 2D stuff... + // icons and text + GFX->setClipRect(updateRect); + + // update what is in the selection + if ( mDragSelect ) + mDragSelected.clear(); + + // Determine selected objects based on the drag box touching + // a mesh if a drag operation has begun. + if( mDragSelect && mDragRect.extent.x > 1 && mDragRect.extent.y > 1 ) + { + // Build the drag frustum based on the rect + F32 wwidth; + F32 wheight; + if(!mLastCameraQuery.ortho) + { + wwidth = mLastCameraQuery.nearPlane * mTan(mLastCameraQuery.fov / 2); + wheight = F32(getHeight()) / F32(getWidth()) * wwidth; + } + else + { + wwidth = mLastCameraQuery.fov; + wheight = F32(getHeight()) / F32(getWidth()) * wwidth; + } + + F32 hscale = wwidth * 2 / F32(getWidth()); + F32 vscale = wheight * 2 / F32(getHeight()); + + F32 left = (mDragRect.point.x - getPosition().x) * hscale - wwidth; + F32 right = (mDragRect.point.x - getPosition().x + mDragRect.extent.x) * hscale - wwidth; + F32 top = wheight - vscale * (mDragRect.point.y - getPosition().y); + F32 bottom = wheight - vscale * (mDragRect.point.y - getPosition().y + mDragRect.extent.y); + gDragFrustum.set(mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane, mLastCameraQuery.cameraMatrix ); + + // Create the search bounds and callback data + Box3F bounds = gDragFrustum.getBounds(); + SphereF sphere; + sphere.center = bounds.getCenter(); + sphere.radius = (bounds.maxExtents - sphere.center).len(); + DragMeshCallbackData data(this, bounds, sphere); + + // Set up the search normal and planes + Point3F vec; + mLastCameraQuery.cameraMatrix.getColumn(1,&vec); + vec.neg(); + data.mPolyList.mNormal.set(vec); + const PlaneF* planes = gDragFrustum.getPlanes(); + for(i=0; igetTypeMask() & (TerrainObjectType | ProjectileObjectType))) + continue; + + mDragSelected.addObject(obj); + } + } + + // Clear the vector of onscreen icons, will populate this below + // Necessary for performing click testing efficiently + mIcons.clear(); + + // Cull Objects and perform icon rendering + Vector objects; + gServerContainer.findObjects( frustum, 0xFFFFFFFF, findObjectsCallback, &objects); + for ( i = 0; i < objects.size(); i++ ) + { + SceneObject *obj = objects[i]; + if(objClassIgnored(obj)) + continue; + + Point3F wPos; + if(mObjectsUseBoxCenter) + wPos = getBoundingBoxCenter(obj); + else + obj->getTransform().getColumn(3, &wPos); + + Point3F sPos; + if ( project(wPos, &sPos) ) + { + Point2I sPosI( (S32)sPos.x,(S32)sPos.y ); + if ( !updateRect.pointInRect(sPosI) ) + continue; + + // check if object needs to be added into the regions select + + // Probably should test the entire icon screen-rect instead of just the centerpoint + // but would need to move some code from renderScreenObj to here. + if ( mDragSelect ) + if ( mDragRect.pointInRect(sPosI) && !mSelected.objInSet(obj) ) + mDragSelected.addObject(obj); + + // + renderScreenObj( obj, sPos ); + } + } + + //// Debug render rect around icons + //for ( U32 i = 0; i < mIcons.size(); i++ ) + // GFX->getDrawUtil()->drawRect( mIcons[i].rect, ColorI(255,255,255,255) ); + + if ( mShowMousePopupInfo && mMouseDown ) + renderMousePopupInfo(); + + if ( mDragSelect ) + GFX->getDrawUtil()->drawRect( mDragRect, mDragRectColor ); + + if ( mSelected.size() ) + mGizmo->renderText( mSaveViewport, mSaveModelview, mSaveProjection ); +} + +//------------------------------------------------------------------------------ +// Console stuff + +static EnumTable::Enums dropEnums[] = +{ + { WorldEditor::DropAtOrigin, "atOrigin" }, + { WorldEditor::DropAtCamera, "atCamera" }, + { WorldEditor::DropAtCameraWithRot, "atCameraRot" }, + { WorldEditor::DropBelowCamera, "belowCamera" }, + { WorldEditor::DropAtScreenCenter, "screenCenter" }, + { WorldEditor::DropAtCentroid, "atCentroid" }, + { WorldEditor::DropToTerrain, "toTerrain" }, + { WorldEditor::DropBelowSelection, "belowSelection" } +}; +static EnumTable gEditorDropTable(8, &dropEnums[0]); + +static EnumTable::Enums snapAlignEnums[] = +{ + { WorldEditor::AlignNone, "None" }, + { WorldEditor::AlignPosX, "+X" }, + { WorldEditor::AlignPosY, "+Y" }, + { WorldEditor::AlignPosZ, "+Z" }, + { WorldEditor::AlignNegX, "-X" }, + { WorldEditor::AlignNegY, "-Y" }, + { WorldEditor::AlignNegZ, "-Z" } +}; +static EnumTable gSnapAlignTable(7, &snapAlignEnums[0]); + +void WorldEditor::initPersistFields() +{ + addGroup( "Misc" ); + + addField( "isDirty", TypeBool, Offset(mIsDirty, WorldEditor) ); + addField( "stickToGround", TypeBool, Offset(mStickToGround, WorldEditor) ); + addField( "dropAtBounds", TypeBool, Offset(mDropAtBounds, WorldEditor) ); + addField( "dropBelowCameraOffset", TypeF32, Offset(mDropBelowCameraOffset, WorldEditor) ); + addField( "dropAtScreenCenterScalar",TypeF32, Offset(mDropAtScreenCenterScalar, WorldEditor) ); + addField( "dropAtScreenCenterMax", TypeF32, Offset(mDropAtScreenCenterMax, WorldEditor) ); + addField( "dropType", TypeEnum, Offset(mDropType, WorldEditor), 1, &gEditorDropTable); + addField( "boundingBoxCollision", TypeBool, Offset(mBoundingBoxCollision, WorldEditor) ); + addField( "objectMeshCollision", TypeBool, Offset(mObjectMeshCollision, WorldEditor) ); + addField( "renderPopupBackground", TypeBool, Offset(mRenderPopupBackground, WorldEditor) ); + addField( "popupBackgroundColor", TypeColorI, Offset(mPopupBackgroundColor, WorldEditor) ); + addField( "popupTextColor", TypeColorI, Offset(mPopupTextColor, WorldEditor) ); + addField( "objectTextColor", TypeColorI, Offset(mObjectTextColor, WorldEditor) ); + addProtectedField( "objectsUseBoxCenter", TypeBool, Offset(mObjectsUseBoxCenter, WorldEditor), &setObjectsUseBoxCenter, &defaultProtectedGetFn, "" ); + addField( "objSelectColor", TypeColorI, Offset(mObjSelectColor, WorldEditor) ); + addField( "objMouseOverSelectColor",TypeColorI, Offset(mObjMouseOverSelectColor, WorldEditor) ); + addField( "objMouseOverColor", TypeColorI, Offset(mObjMouseOverColor, WorldEditor) ); + addField( "showMousePopupInfo", TypeBool, Offset(mShowMousePopupInfo, WorldEditor) ); + addField( "dragRectColor", TypeColorI, Offset(mDragRectColor, WorldEditor) ); + addField( "renderObjText", TypeBool, Offset(mRenderObjText, WorldEditor) ); + addField( "renderObjHandle", TypeBool, Offset(mRenderObjHandle, WorldEditor) ); + addField( "objTextFormat", TypeString, Offset(mObjTextFormat, WorldEditor) ); + addField( "faceSelectColor", TypeColorI, Offset(mFaceSelectColor, WorldEditor) ); + addField( "renderSelectionBox", TypeBool, Offset(mRenderSelectionBox, WorldEditor) ); + addField( "selectionBoxColor", TypeColorI, Offset(mSelectionBoxColor, WorldEditor) ); + addField( "selectionLocked", TypeBool, Offset(mSelectionLocked, WorldEditor) ); + //addField("sameScaleAllAxis", TypeBool, Offset(mSameScaleAllAxis, WorldEditor)); + addField( "toggleIgnoreList", TypeBool, Offset(mToggleIgnoreList, WorldEditor) ); + addField( "selectHandle", TypeFilename, Offset(mSelectHandle, WorldEditor) ); + addField( "defaultHandle", TypeFilename, Offset(mDefaultHandle, WorldEditor) ); + addField( "lockedHandle", TypeFilename, Offset(mLockedHandle, WorldEditor) ); + + endGroup( "Misc" ); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +// These methods are needed for the console interfaces. + +void WorldEditor::ignoreObjClass( U32 argc, const char **argv ) +{ + for(S32 i = 2; i < argc; i++) + { + ClassInfo::Entry * entry = getClassEntry(argv[i]); + if(entry) + entry->mIgnoreCollision = true; + else + { + entry = new ClassInfo::Entry; + entry->mName = StringTable->insert(argv[i]); + entry->mIgnoreCollision = true; + if(!addClassEntry(entry)) + delete entry; + } + } +} + +void WorldEditor::clearIgnoreList() +{ + for(U32 i = 0; i < mClassInfo.mEntries.size(); i++) + mClassInfo.mEntries[i]->mIgnoreCollision = false; +} + +void WorldEditor::setObjectsUseBoxCenter(bool state) +{ + mObjectsUseBoxCenter = state; + if( isMethod( "onSelectionCentroidChanged" ) ) + Con::executef( this, "onSelectionCentroidChanged" ); +} + +void WorldEditor::clearSelection() +{ + if(mSelectionLocked) + return; + + // Give selected objects a chance at removing + // any custom editors. + for ( U32 i = 0; i < mSelected.size(); i++ ) + Con::executef(this, "onUnSelect", avar("%d", mSelected[i]->getId())); + + Con::executef(this, "onClearSelection"); + mSelected.clear(); +} + +void WorldEditor::selectObject( const char* obj ) +{ + if ( mSelectionLocked ) + return; + + SimObject* select; + + if ( Sim::findObject(obj, select) && !objClassIgnored(select) ) + { + Con::executef(this, "onSelect", avar("%d", select->getId())); + mSelected.addObject(select); + } +} + +void WorldEditor::unselectObject( const char *obj ) +{ + if(mSelectionLocked) + return; + + SimObject* select; + + if ( Sim::findObject(obj, select) && !objClassIgnored(select) ) + { + + if ( mSelected.objInSet(select) ) + { + mSelected.removeObject(select); + Con::executef(this, "onUnSelect", avar("%d", select->getId())); + } + } +} + +S32 WorldEditor::getSelectionSize() +{ + return mSelected.size(); +} + +S32 WorldEditor::getSelectObject(S32 index) +{ + // Return the object's id + return mSelected[index]->getId(); +} + +const Point3F& WorldEditor::getSelectionCentroid() +{ + return mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid(); +} + +const char* WorldEditor::getSelectionCentroidText() +{ + const Point3F & centroid = getSelectionCentroid(); + char * ret = Con::getReturnBuffer(100); + dSprintf(ret, 100, "%g %g %g", centroid.x, centroid.y, centroid.z); + return ret; +} + +const Box3F& WorldEditor::getSelectionBounds() +{ + return mSelected.getBoxBounds(); +} + +Point3F WorldEditor::getSelectionExtent() +{ + const Box3F& box = getSelectionBounds(); + return box.getExtents(); +} + +F32 WorldEditor::getSelectionRadius() +{ + const Box3F box = getSelectionBounds(); + return box.len() * 0.5f; +} + +void WorldEditor::dropCurrentSelection( bool skipUndo ) +{ + if ( !mSelected.size() ) + return; + + if ( !skipUndo ) + submitUndo( mSelected ); + + dropSelection( mSelected ); +} + +void WorldEditor::redirectConsole( S32 objID ) +{ + mRedirectID = objID; +} + +//------------------------------------------------------------------------------ + +bool WorldEditor::alignByBounds( S32 boundsAxis ) +{ + if(boundsAxis < 0 || boundsAxis > 5) + return false; + + if(mSelected.size() < 2) + return true; + + S32 axis = boundsAxis >= 3 ? boundsAxis-3 : boundsAxis; + bool useMax = boundsAxis >= 3 ? false : true; + + // Find out which selected object has its bounds the farthest out + F32 pos; + S32 baseObj = 0; + if(useMax) + pos = TypeTraits< F32 >::MIN; + else + pos = TypeTraits< F32 >::MAX; + + for(S32 i=1; i( mSelected[ i ] ); + if( !object ) + continue; + + const Box3F& bounds = object->getWorldBox(); + + if(useMax) + { + if(bounds.maxExtents[axis] > pos) + { + pos = bounds.maxExtents[axis]; + baseObj = i; + } + } + else + { + if(bounds.minExtents[axis] < pos) + { + pos = bounds.minExtents[axis]; + baseObj = i; + } + } + } + + submitUndo( mSelected, "Align By Bounds" ); + + // Move all selected objects to align with the calculated bounds + for(S32 i=0; i( mSelected[ i ] ); + if( !object ) + continue; + + const Box3F& bounds = object->getWorldBox(); + F32 delta; + if(useMax) + delta = pos - bounds.maxExtents[axis]; + else + delta = pos - bounds.minExtents[axis]; + + Point3F objPos = object->getPosition(); + objPos[axis] += delta; + object->setPosition(objPos); + } + + return true; +} + +bool WorldEditor::alignByAxis( S32 axis ) +{ + if(axis < 0 || axis > 2) + return false; + + if(mSelected.size() < 2) + return true; + + SceneObject* object = dynamic_cast< SceneObject* >( mSelected[ 0 ] ); + if( !object ) + return false; + + submitUndo( mSelected, "Align By Axis" ); + + // All objects will be repositioned to line up with the + // first selected object + Point3F pos = object->getPosition(); + + for(S32 i=0; i( mSelected[ i ] ); + if( !object ) + continue; + + Point3F objPos = object->getPosition(); + objPos[axis] = pos[axis]; + object->setPosition(objPos); + } + + return true; +} + +//------------------------------------------------------------------------------ + +void WorldEditor::transformSelection(bool position, Point3F& p, bool relativePos, bool rotate, EulerF& r, bool relativeRot, bool rotLocal, S32 scaleType, Point3F& s, bool sRelative, bool sLocal) +{ + if(mSelected.size() == 0) + return; + + submitUndo( mSelected, "Transform Selection" ); + + if( position ) + { + if( relativePos ) + { + mSelected.offset(p); + } + else + { + mSelected.setCentroidPosition(mObjectsUseBoxCenter, p); + } + } + + if( rotate ) + { + Point3F centroid; + if( mSelected.containsGlobalBounds() ) + { + centroid = mSelected.getCentroid(); + } + else + { + centroid = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid(); + } + + if( relativeRot ) + { + if( rotLocal ) + { + mSelected.rotate(r); + } + else + { + mSelected.rotate(r, centroid); + } + } + else if( rotLocal ) + { + // Can only do absolute rotation for multiple objects about + // object center + mSelected.setRotate(r); + } + } + + if( scaleType == 1 ) + { + // Scale + + Point3F centroid; + if( mSelected.containsGlobalBounds() ) + { + centroid = mSelected.getCentroid(); + } + else + { + centroid = mObjectsUseBoxCenter ? mSelected.getBoxCentroid() : mSelected.getCentroid(); + } + + if( sRelative ) + { + if( sLocal ) + { + mSelected.scale(s); + } + else + { + mSelected.scale(s, centroid); + } + } + else + { + if( sLocal ) + { + mSelected.setScale(s); + } + else + { + mSelected.setScale(s, centroid); + } + } + } + else if( scaleType == 2 ) + { + // Size + + if( mSelected.containsGlobalBounds() ) + return; + + if( sRelative ) + { + // Size is always local/object based + mSelected.addSize(s); + } + else + { + // Size is always local/object based + mSelected.setSize(s); + } + } + + updateClientTransforms(mSelected); + + if(mSelected.hasCentroidChanged()) + { + Con::executef( this, "onSelectionCentroidChanged"); + } + + if ( isMethod("onEndDrag") ) + { + char buf[16]; + dSprintf( buf, sizeof(buf), "%d", mSelected[0]->getId() ); + + SimObject * obj = 0; + if ( mRedirectID ) + obj = Sim::findObject( mRedirectID ); + Con::executef( obj ? obj : this, "onEndDrag", buf ); + } +} + +//------------------------------------------------------------------------------ + +void WorldEditor::resetSelectedRotation() +{ + for(S32 i=0; i( mSelected[ i ] ); + if( !object ) + continue; + + MatrixF mat(true); + mat.setPosition(object->getPosition()); + object->setTransform(mat); + } +} + +void WorldEditor::resetSelectedScale() +{ + for(S32 i=0; i( mSelected[ i ] ); + if( object ) + object->setScale(Point3F(1,1,1)); + } +} + +//------------------------------------------------------------------------------ + +ConsoleMethod( WorldEditor, ignoreObjClass, void, 3, 0, "(string class_name, ...)") +{ + object->ignoreObjClass(argc, argv); +} + +ConsoleMethod( WorldEditor, clearIgnoreList, void, 2, 2, "") +{ + object->clearIgnoreList(); +} + +ConsoleMethod( WorldEditor, clearSelection, void, 2, 2, "") +{ + object->clearSelection(); +} + +ConsoleMethod( WorldEditor, selectObject, void, 3, 3, "(SimObject obj)") +{ + object->selectObject(argv[2]); +} + +ConsoleMethod( WorldEditor, unselectObject, void, 3, 3, "(SimObject obj)") +{ + object->unselectObject(argv[2]); +} + +ConsoleMethod( WorldEditor, getSelectionSize, S32, 2, 2, "") +{ + return object->getSelectionSize(); +} + +ConsoleMethod( WorldEditor, getSelectedObject, S32, 3, 3, "(int index)") +{ + S32 index = dAtoi(argv[2]); + if(index < 0 || index >= object->getSelectionSize()) + { + Con::errorf(ConsoleLogEntry::General, "WorldEditor::getSelectedObject: invalid object index"); + return(-1); + } + + return(object->getSelectObject(index)); +} + +ConsoleMethod( WorldEditor, getSelectionRadius, F32, 2, 2, "") +{ + return object->getSelectionRadius(); +} + +ConsoleMethod( WorldEditor, getSelectionCentroid, const char *, 2, 2, "") +{ + return object->getSelectionCentroidText(); +} + +ConsoleMethod( WorldEditor, getSelectionExtent, const char *, 2, 2, "") +{ + Point3F bounds = object->getSelectionExtent(); + char * ret = Con::getReturnBuffer(100); + dSprintf(ret, 100, "%g %g %g", bounds.x, bounds.y, bounds.z); + return ret; +} + +ConsoleMethod( WorldEditor, dropSelection, void, 2, 3, "( bool skipUndo = false )") +{ + bool skipUndo = false; + if ( argc > 2 ) + skipUndo = dAtob( argv[2] ); + + object->dropCurrentSelection( skipUndo ); +} + +void WorldEditor::cutCurrentSelection() +{ + cutSelection(mSelected); +} + +void WorldEditor::copyCurrentSelection() +{ + copySelection(mSelected); +} + +ConsoleMethod( WorldEditor, cutSelection, void, 2, 2, "") +{ + object->cutCurrentSelection(); +} + +ConsoleMethod( WorldEditor, copySelection, void, 2, 2, "") +{ + object->copyCurrentSelection(); +} + +ConsoleMethod( WorldEditor, pasteSelection, void, 2, 2, "") +{ + object->pasteSelection(); +} + +bool WorldEditor::canPasteSelection() +{ + return mCopyBuffer.empty() != true; +} + +ConsoleMethod( WorldEditor, canPasteSelection, bool, 2, 2, "") +{ + return object->canPasteSelection(); +} + +ConsoleMethod( WorldEditor, hideSelection, void, 3, 3, "(bool hide)") +{ + object->hideSelection(dAtob(argv[2])); +} + +ConsoleMethod( WorldEditor, lockSelection, void, 3, 3, "(bool lock)") +{ + object->lockSelection(dAtob(argv[2])); +} + +ConsoleMethod( WorldEditor, alignByBounds, void, 3, 3, "(int boundsAxis)" + "Align all selected objects against the given bounds axis.") +{ + if(!object->alignByBounds(dAtoi(argv[2]))) + Con::warnf(ConsoleLogEntry::General, avar("worldEditor.alignByBounds: invalid bounds axis '%s'", argv[2])); +} + +ConsoleMethod( WorldEditor, alignByAxis, void, 3, 3, "(int axis)" + "Align all selected objects along the given axis.") +{ + if(!object->alignByAxis(dAtoi(argv[2]))) + Con::warnf(ConsoleLogEntry::General, avar("worldEditor.alignByAxis: invalid axis '%s'", argv[2])); +} + +ConsoleMethod( WorldEditor, resetSelectedRotation, void, 2, 2, "") +{ + object->resetSelectedRotation(); +} + +ConsoleMethod( WorldEditor, resetSelectedScale, void, 2, 2, "") +{ + object->resetSelectedScale(); +} + +ConsoleMethod( WorldEditor, redirectConsole, void, 3, 3, "( int objID )") +{ + object->redirectConsole(dAtoi(argv[2])); +} + +ConsoleMethod( WorldEditor, addUndoState, void, 2, 2, "") +{ + object->addUndoState(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( WorldEditor, getSoftSnap, bool, 2, 2, "getSoftSnap()\n" + "Is soft snapping always on?") +{ + return object->mSoftSnap; +} + +ConsoleMethod( WorldEditor, setSoftSnap, void, 3, 3, "setSoftSnap(bool)\n" + "Allow soft snapping all of the time.") +{ + object->mSoftSnap = dAtob(argv[2]); +} + +ConsoleMethod( WorldEditor, getSoftSnapSize, F32, 2, 2, "getSoftSnapSize()\n" + "Get the absolute size to trigger a soft snap.") +{ + return object->mSoftSnapSize; +} + +ConsoleMethod( WorldEditor, setSoftSnapSize, void, 3, 3, "setSoftSnapSize(F32)\n" + "Set the absolute size to trigger a soft snap.") +{ + object->mSoftSnapSize = dAtof(argv[2]); +} + +ConsoleMethod( WorldEditor, getSoftSnapAlignment, const char*, 2, 2, "getSoftSnapAlignment()\n" + "Get the soft snap alignment.") +{ + return gSnapAlignTable.table[object->mSoftSnapAlignment].label; +} + +ConsoleMethod( WorldEditor, setSoftSnapAlignment, void, 3, 3, "setSoftSnapAlignment(align)\n" + "Set the soft snap alignment.") +{ + S32 val = 0; + for (S32 i = 0; i < gSnapAlignTable.size; i++) + { + if (! dStricmp(argv[2], gSnapAlignTable.table[i].label)) + { + val = gSnapAlignTable.table[i].index; + break; + } + } + object->mSoftSnapAlignment = val; +} + +ConsoleMethod( WorldEditor, softSnapSizeByBounds, void, 3, 3, "softSnapSizeByBounds(bool)\n" + "Use selection bounds size as soft snap bounds.") +{ + object->mSoftSnapSizeByBounds = dAtob(argv[2]); +} + +ConsoleMethod( WorldEditor, getSoftSnapBackfaceTolerance, F32, 2, 2, "getSoftSnapBackfaceTolerance()\n" + "The fraction of the soft snap radius that backfaces may be included.") +{ + return object->mSoftSnapBackfaceTolerance; +} + +ConsoleMethod( WorldEditor, setSoftSnapBackfaceTolerance, void, 3, 3, "setSoftSnapBackfaceTolerance(F32 with range of 0..1)\n" + "The fraction of the soft snap radius that backfaces may be included.") +{ + object->mSoftSnapBackfaceTolerance = dAtof(argv[2]); +} + +ConsoleMethod( WorldEditor, softSnapRender, void, 3, 3, "softSnapRender(bool)\n" + "Render the soft snapping bounds.") +{ + object->mSoftSnapRender = dAtob(argv[2]); +} + +ConsoleMethod( WorldEditor, softSnapRenderTriangle, void, 3, 3, "softSnapRenderTriangle(bool)\n" + "Render the soft snapped triangle.") +{ + object->mSoftSnapRenderTriangle = dAtob(argv[2]); +} + +ConsoleMethod( WorldEditor, softSnapDebugRender, void, 3, 3, "softSnapDebugRender(bool)\n" + "Toggle soft snapping debug rendering.") +{ + object->mSoftSnapDebugRender = dAtob(argv[2]); +} + +ConsoleMethod( WorldEditor, getTerrainSnapAlignment, const char*, 2, 2, "getTerrainSnapAlignment()\n" + "Get the terrain snap alignment.") +{ + return gSnapAlignTable.table[object->mSoftSnapAlignment].label; +} + +ConsoleMethod( WorldEditor, setTerrainSnapAlignment, void, 3, 3, "setTerrainSnapAlignment(align)\n" + "Set the terrain snap alignment.") +{ + S32 val = 0; + for (S32 i = 0; i < gSnapAlignTable.size; i++) + { + if (! dStricmp(argv[2], gSnapAlignTable.table[i].label)) + { + val = gSnapAlignTable.table[i].index; + break; + } + } + object->mTerrainSnapAlignment = val; +} + +ConsoleMethod( WorldEditor, transformSelection, void, 13, 13, "transformSelection(...)\n" + "Transform selection by given parameters.") +{ + bool position = dAtob(argv[2]); + Point3F p(0.0f, 0.0f, 0.0f); + dSscanf(argv[3], "%g %g %g", &p.x, &p.y, &p.z); + bool relativePos = dAtob(argv[4]); + + bool rotate = dAtob(argv[5]); + EulerF r(0.0f, 0.0f, 0.0f); + dSscanf(argv[6], "%g %g %g", &r.x, &r.y, &r.z); + bool relativeRot = dAtob(argv[7]); + bool rotLocal = dAtob(argv[8]); + + S32 scaleType = dAtoi(argv[9]); + Point3F s(1.0f, 1.0f, 1.0f); + dSscanf(argv[10], "%g %g %g", &s.x, &s.y, &s.z); + bool sRelative = dAtob(argv[11]); + bool sLocal = dAtob(argv[12]); + + object->transformSelection(position, p, relativePos, rotate, r, relativeRot, rotLocal, scaleType, s, sRelative, sLocal); +} diff --git a/gui/worldEditor/worldEditor.h b/gui/worldEditor/worldEditor.h new file mode 100644 index 0000000..f1b91a1 --- /dev/null +++ b/gui/worldEditor/worldEditor.h @@ -0,0 +1,423 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WORLDEDITOR_H_ +#define _WORLDEDITOR_H_ + +#ifndef _EDITTSCTRL_H_ +#include "gui/worldEditor/editTSCtrl.h" +#endif +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _CONSOLE_SIMOBJECTMEMENTO_H_ +#include "console/simObjectMemento.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _SIMPATH_H_ +#include "sceneGraph/simPath.h" +#endif + +class SceneObject; + +class WorldEditor : public EditTSCtrl +{ + typedef EditTSCtrl Parent; + + public: + + struct Triangle + { + Point3F p0; + Point3F p1; + Point3F p2; + }; + + void ignoreObjClass(U32 argc, const char** argv); + void clearIgnoreList(); + + static bool setObjectsUseBoxCenter( void* obj, const char* data ) { static_cast(obj)->setObjectsUseBoxCenter( dAtob( data ) ); return false; }; + void setObjectsUseBoxCenter(bool state); + bool getObjectsUseBoxCenter() { return mObjectsUseBoxCenter; } + + void clearSelection(); + void selectObject(const char* obj); + void unselectObject(const char* obj); + + S32 getSelectionSize(); + S32 getSelectObject(S32 index); + const Point3F& getSelectionCentroid(); + const char* getSelectionCentroidText(); + const Box3F& getSelectionBounds(); + Point3F getSelectionExtent(); + F32 getSelectionRadius(); + + void dropCurrentSelection( bool skipUndo ); + void copyCurrentSelection(); + void cutCurrentSelection(); + bool canPasteSelection(); + + bool alignByBounds(S32 boundsAxis); + bool alignByAxis(S32 axis); + + void transformSelection(bool position, Point3F& p, bool relativePos, bool rotate, EulerF& r, bool relativeRot, bool rotLocal, S32 scaleType, Point3F& s, bool sRelative, bool sLocal); + + void resetSelectedRotation(); + void resetSelectedScale(); + + void addUndoState() { submitUndo( mSelected ); } + void redirectConsole(S32 objID); + + public: + + class Selection : public SimObject + { + typedef SimObject Parent; + + private: + + Point3F mCentroid; + Point3F mBoxCentroid; + Box3F mBoxBounds; + bool mCentroidValid; + bool mContainsGlobalBounds; // Does the selection contain at least one object with a global bounds + SimObjectList mObjectList; + bool mAutoSelect; + Point3F mPrevCentroid; + + void updateCentroid(); + + public: + + Selection(); + ~Selection(); + + // + U32 size() { return(mObjectList.size()); } + SimObject * operator[] (S32 index) { return ( ( SimObject* ) mObjectList[ index ] ); } + + bool objInSet( SimObject* ); + + bool addObject( SimObject* ); + bool removeObject( SimObject* ); + void clear(); + + void onDeleteNotify( SimObject* ); + + void storeCurrentCentroid() { mPrevCentroid = getCentroid(); } + bool hasCentroidChanged() { return (mPrevCentroid != getCentroid()); } + + bool containsGlobalBounds(); + + const Point3F & getCentroid(); + const Point3F & getBoxCentroid(); + const Box3F & getBoxBounds(); + Point3F getBoxBottomCenter(); + + void enableCollision(); + void disableCollision(); + + // + void autoSelect(bool b) { mAutoSelect = b; } + void invalidateCentroid() { mCentroidValid = false; } + + // + void offset(const Point3F &); + void setPosition(const Point3F & pos); + void setCentroidPosition(bool useBoxCenter, const Point3F & pos); + + void orient(const MatrixF &, const Point3F &); + void rotate(const EulerF &); + void rotate(const EulerF &, const Point3F &); + void setRotate(const EulerF &); + + void scale(const VectorF &); + void scale(const VectorF &, const Point3F &); + void setScale(const VectorF &); + void setScale(const VectorF &, const Point3F &); + + void addSize(const VectorF &); + void setSize(const VectorF &); + }; + + // + static SceneObject* getClientObj(SceneObject *); + static void setClientObjInfo(SceneObject *, const MatrixF &, const VectorF &); + static void updateClientTransforms(Selection &); + + protected: + + class WorldEditorUndoAction : public UndoAction + { + public: + + WorldEditorUndoAction( const UTF8* actionName ) : UndoAction( actionName ) + { + } + + WorldEditor *mWorldEditor; + + struct Entry + { + MatrixF mMatrix; + VectorF mScale; + + // validation + U32 mObjId; + U32 mObjNumber; + }; + + Vector mEntries; + + virtual void undo(); + virtual void redo() { undo(); } + }; + + void submitUndo( Selection &sel, const UTF8* label="World Editor Action" ); + + public: + + /// The objects currently in the copy buffer. + Vector mCopyBuffer; + + bool cutSelection(Selection &sel); + bool copySelection(Selection & sel); + bool pasteSelection(bool dropSel=true); + void dropSelection(Selection & sel); + void dropBelowSelection(Selection & sel, const Point3F & centroid, bool useBottomBounds=false); + + void terrainSnapSelection(Selection& sel, U8 modifier, Point3F gizmoPos, bool forceStick=false); + void softSnapSelection(Selection& sel, U8 modifier, Point3F gizmoPos); + + // work off of mSelected + void hideSelection(bool hide); + void lockSelection(bool lock); + + public: + bool objClassIgnored(const SimObject * obj); + void renderObjectBox(SceneObject * obj, const ColorI & col); + + private: + SceneObject * getControlObject(); + bool collide(const Gui3DMouseEvent & event, SceneObject **hitObj ); + + // gfx methods + //void renderObjectBox(SceneObject * obj, const ColorI & col); + void renderObjectFace(SceneObject * obj, const VectorF & normal, const ColorI & col); + void renderSelectionWorldBox(Selection & sel); + + void renderPlane(const Point3F & origin); + void renderMousePopupInfo(); + void renderScreenObj(SceneObject * obj, Point3F sPos); + + void renderPaths(SimObject *obj); + void renderSplinePath(SimPath::Path *path); + + // axis gizmo + bool mUsingAxisGizmo; + + GFXStateBlockRef mRenderObjectBoxSB; + GFXStateBlockRef mRenderObjectFaceSB; + GFXStateBlockRef mSplineSB; + + // + bool mIsDirty; + + // + bool mMouseDown; + Selection mSelected; + + Selection mDragSelected; + bool mDragSelect; + RectI mDragRect; + Point2I mDragStart; + + // modes for when dragging a selection + enum { + Move = 0, + Rotate, + Scale + }; + + // + //U32 mCurrentMode; + //U32 mDefaultMode; + + S32 mRedirectID; + + + struct IconObject + { + SceneObject *object; + F32 dist; + RectI rect; + }; + + Vector mIcons; + + SimObjectPtr mHitObject; + SimObjectPtr mPossibleHitObject; + bool mMouseDragged; + Gui3DMouseEvent mLastMouseEvent; + Gui3DMouseEvent mLastMouseDownEvent; + + // + class ClassInfo + { + public: + ~ClassInfo(); + + struct Entry + { + StringTableEntry mName; + bool mIgnoreCollision; + GFXTexHandle mDefaultHandle; + GFXTexHandle mSelectHandle; + GFXTexHandle mLockedHandle; + }; + + Vector mEntries; + }; + + + ClassInfo mClassInfo; + ClassInfo::Entry mDefaultClassEntry; + + ClassInfo::Entry * getClassEntry(StringTableEntry name); + ClassInfo::Entry * getClassEntry(const SimObject * obj); + bool addClassEntry(ClassInfo::Entry * entry); + + // persist field data + public: + + enum { + DropAtOrigin = 0, + DropAtCamera, + DropAtCameraWithRot, + DropBelowCamera, + DropAtScreenCenter, + DropAtCentroid, + DropToTerrain, + DropBelowSelection + }; + + // Snapping alignment mode + enum { + AlignNone = 0, + AlignPosX, + AlignPosY, + AlignPosZ, + AlignNegX, + AlignNegY, + AlignNegZ + }; + + /// A large hard coded distance used to test + /// object and icon selection. + static F32 smProjectDistance; + + S32 mDropType; + bool mBoundingBoxCollision; + bool mObjectMeshCollision; + bool mRenderPopupBackground; + ColorI mPopupBackgroundColor; + ColorI mPopupTextColor; + StringTableEntry mSelectHandle; + StringTableEntry mDefaultHandle; + StringTableEntry mLockedHandle; + ColorI mObjectTextColor; + bool mObjectsUseBoxCenter; + ColorI mObjSelectColor; + ColorI mObjMouseOverSelectColor; + ColorI mObjMouseOverColor; + bool mShowMousePopupInfo; + ColorI mDragRectColor; + bool mRenderObjText; + bool mRenderObjHandle; + StringTableEntry mObjTextFormat; + ColorI mFaceSelectColor; + bool mRenderSelectionBox; + ColorI mSelectionBoxColor; + bool mSelectionLocked; + bool mPerformedDragCopy; + bool mToggleIgnoreList; + bool mNoMouseDrag; + bool mDropAtBounds; + F32 mDropBelowCameraOffset; + F32 mDropAtScreenCenterScalar; + F32 mDropAtScreenCenterMax; + + bool mStickToGround; + bool mStuckToGround; ///< Selection is stuck to the ground + S32 mTerrainSnapAlignment; ///< How does the stickied object align to the terrain + + bool mSoftSnap; ///< Allow soft snapping all of the time + bool mSoftSnapActivated; ///< Soft snap has been activated by the user and allowed by the current rules + bool mSoftSnapIsStuck; ///< Are we snapping? + S32 mSoftSnapAlignment; ///< How does the snapped object align to the snapped surface + bool mSoftSnapRender; ///< Render the soft snapping bounds + bool mSoftSnapRenderTriangle; ///< Render the soft snapped triangle + Triangle mSoftSnapTriangle; ///< The triangle we are snapping to + bool mSoftSnapSizeByBounds; ///< Use the selection bounds for the size + F32 mSoftSnapSize; ///< If not the selection bounds, use this size + Box3F mSoftSnapBounds; ///< The actual bounds used for the soft snap + Box3F mSoftSnapPreBounds; ///< The bounds prior to any soft snapping (used when rendering the snap bounds) + F32 mSoftSnapBackfaceTolerance; ///< Fraction of mSoftSnapSize for backface polys to have an influence + + bool mSoftSnapDebugRender; ///< Activates debug rendering + Point3F mSoftSnapDebugPoint; ///< The point we're attempting to snap to + Triangle mSoftSnapDebugSnapTri; ///< The triangle we are snapping to + Vector mSoftSnapDebugTriangles; ///< The triangles that are considered for snapping + + protected: + + S32 mCurrentCursor; + void setCursor(U32 cursor); + void get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event); + + public: + + WorldEditor(); + ~WorldEditor(); + + // SimObject + bool onAdd(); + void onEditorEnable(); + void setDirty() { mIsDirty = true; } + + // EditTSCtrl + void on3DMouseMove(const Gui3DMouseEvent & event); + void on3DMouseDown(const Gui3DMouseEvent & event); + void on3DMouseUp(const Gui3DMouseEvent & event); + void on3DMouseDragged(const Gui3DMouseEvent & event); + void on3DMouseEnter(const Gui3DMouseEvent & event); + void on3DMouseLeave(const Gui3DMouseEvent & event); + void on3DRightMouseDown(const Gui3DMouseEvent & event); + void on3DRightMouseUp(const Gui3DMouseEvent & event); + + void updateGuiInfo(); + + // + void renderScene(const RectI & updateRect); + + Gizmo* getGizmo() { return mGizmo; }; + + static void initPersistFields(); + + DECLARE_CONOBJECT(WorldEditor); + + static Signal smRenderSceneSignal; +}; + +#endif // _WORLDEDITOR_H_ + diff --git a/i18n/i18n.cpp b/i18n/i18n.cpp new file mode 100644 index 0000000..4b0aa94 --- /dev/null +++ b/i18n/i18n.cpp @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/stream/stream.h" +#include "core/stream/fileStream.h" +#include "console/console.h" + +#include "i18n/i18n.h" +#include "i18n/lang.h" + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- + +// [tom, 3/17/2005] Note: This is created in script +static LangTable *gCoreLangTable = NULL; + +// [tom, 3/17/2005] Defined in CoreStringsDefaults.cc, which is generated by langc +//extern const UTF8 *gI18NDefaultStrings[]; + +// [tom, 5/2/2005] Note: Temporary kludge to keep this compilable while +// the core localization isn't finished. +static const UTF8 *gI18NDefaultStrings[] = +{ + NULL +}; + +//----------------------------------------------------------------------------- + +const UTF8 *getCoreString(S32 id) +{ + if(gCoreLangTable) + return gCoreLangTable->getString(id); + else + return gI18NDefaultStrings[id]; +} + +//----------------------------------------------------------------------------- + +ConsoleFunction(getCoreLangTable, S32, 1, 1, "()") +{ + if(gCoreLangTable) + return gCoreLangTable->getId(); + else + return 0; +} + +ConsoleFunction(setCoreLangTable, void, 2, 2, "(LangTable)") +{ + LangTable *lt; + + if(Sim::findObject(argv[1], lt)) + gCoreLangTable = lt; + else + Con::errorf("setCoreLangTable - Unable to find LanTable '%s'", argv[1]); +} + +//----------------------------------------------------------------------------- diff --git a/i18n/i18n.h b/i18n/i18n.h new file mode 100644 index 0000000..bfeb88e --- /dev/null +++ b/i18n/i18n.h @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#if 0 + +// Core strings are out of date so this code is currently disabled. + +#include "i18n/CoreStrings.h" + +#ifndef _I18N_H_ +#define _I18N_H_ + +#define _L(x) (getCoreString(x)) + +// Protos +extern const UTF8 *getCoreString(S32 id); + +#endif // _I18N_H_ + +#endif \ No newline at end of file diff --git a/i18n/lang.cpp b/i18n/lang.cpp new file mode 100644 index 0000000..7bcb1b2 --- /dev/null +++ b/i18n/lang.cpp @@ -0,0 +1,415 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/stream/stream.h" +#include "core/stream/fileStream.h" +#include "console/console.h" +#include "console/consoleInternal.h" +#include "console/ast.h" +#include "console/compiler.h" +#include "core/util/safeDelete.h" + +#include "i18n/lang.h" + +//----------------------------------------------------------------------------- +// LangFile Class +//----------------------------------------------------------------------------- + +LangFile::LangFile(const UTF8 *langName /* = NULL */) +{ + VECTOR_SET_ASSOCIATION( mStringTable ); + + if(langName) + { + mLangName = new UTF8 [dStrlen(langName) + 1]; + dStrcpy(mLangName, langName); + } + else + mLangName = NULL; + + mLangFile = NULL; +} + +LangFile::~LangFile() +{ + // [tom, 3/1/2005] Note: If this is freed in FreeTable() then when the file + // is loaded, the language name will be blitzed. + // Programming after 36 hours without sleep != good. + + SAFE_DELETE_ARRAY(mLangName); + SAFE_DELETE_ARRAY(mLangFile); + freeTable(); +} + +void LangFile::freeTable() +{ + U32 i; + for(i = 0;i < mStringTable.size();i++) + { + if(mStringTable[i]) + delete [] mStringTable[i]; + } + mStringTable.clear(); +} + +bool LangFile::load(const UTF8 *filename) +{ + FileStream * stream; + if((stream = FileStream::createAndOpen( filename, Torque::FS::File::Read )) == NULL) + return false; + + bool ret = load(stream); + delete stream; + return ret; +} + +bool LangFile::load(Stream *s) +{ + freeTable(); + + while(s->getStatus() != Stream::EOS) + { + char buf[256]; + s->readString(buf); + addString((const UTF8*)buf); + } + return true; +} + +bool LangFile::save(const UTF8 *filename) +{ + FileStream *fs; + + if(!isLoaded()) + return false; + + if((fs = FileStream::createAndOpen( filename, Torque::FS::File::Write )) == NULL) + return false; + + bool ret = save(fs); + delete fs; + + return ret; +} + +bool LangFile::save(Stream *s) +{ + if(!isLoaded()) + return false; + + U32 i; + for(i = 0;i < mStringTable.size();i++) + { + s->writeString((char*)mStringTable[i]); + } + return true; +} + +const UTF8 * LangFile::getString(U32 id) +{ + if(id == LANG_INVALID_ID || id >= mStringTable.size()) + return NULL; + return mStringTable[id]; +} + +U32 LangFile::addString(const UTF8 *str) +{ + UTF8 *newstr = new UTF8 [dStrlen(str) + 1]; + dStrcpy(newstr, str); + mStringTable.push_back(newstr); + return mStringTable.size() - 1; +} + +void LangFile::setString(U32 id, const UTF8 *str) +{ + if(id >= mStringTable.size()) + mStringTable.setSize(id+1); + + UTF8 *newstr = new UTF8 [dStrlen(str) + 1]; + dStrcpy(newstr, str); + mStringTable[id] = newstr; +} + +void LangFile::setLangName(const UTF8 *newName) +{ + if(mLangName) + delete [] mLangName; + + mLangName = new UTF8 [dStrlen(newName) + 1]; + dStrcpy(mLangName, newName); +} + +void LangFile::setLangFile(const UTF8 *langFile) +{ + if(mLangFile) + delete [] mLangFile; + + mLangFile = new UTF8 [dStrlen(langFile) + 1]; + dStrcpy(mLangFile, langFile); +} + +bool LangFile::activateLanguage() +{ + if(isLoaded()) + return true; + + if(mLangFile) + { + return load(mLangFile); + } + return false; +} + +void LangFile::deactivateLanguage() +{ + if(mLangFile && isLoaded()) + freeTable(); +} + +//----------------------------------------------------------------------------- +// LangTable Class +//----------------------------------------------------------------------------- + +IMPLEMENT_CONOBJECT(LangTable); + +LangTable::LangTable() : mDefaultLang(-1), mCurrentLang(-1) +{ + VECTOR_SET_ASSOCIATION( mLangTable ); +} + +LangTable::~LangTable() +{ + S32 i; + + for(i = 0;i < mLangTable.size();i++) + { + if(mLangTable[i]) + delete mLangTable[i]; + } + mLangTable.clear(); +} + +S32 LangTable::addLanguage(LangFile *lang, const UTF8 *name /* = NULL */) +{ + if(name) + lang->setLangName(name); + + mLangTable.push_back(lang); + + if(mDefaultLang == -1) + setDefaultLanguage(mLangTable.size() - 1); + if(mCurrentLang == -1) + setCurrentLanguage(mLangTable.size() - 1); + + return mLangTable.size() - 1; +} + +S32 LangTable::addLanguage(const UTF8 *filename, const UTF8 *name /* = NULL */) +{ + LangFile * lang = new LangFile(name); + if(lang != NULL) + { + if(Torque::FS::IsFile(filename)) + { + lang->setLangFile(filename); + + S32 ret = addLanguage(lang); + if(ret >= 0) + return ret; + } + delete lang; + } + + return -1; +} + +const UTF8 *LangTable::getString(const U32 id) const +{ + const UTF8 *s = NULL; + + if(mCurrentLang >= 0) + s = mLangTable[mCurrentLang]->getString(id); + if(s == NULL && mDefaultLang >= 0 && mDefaultLang != mCurrentLang) + s = mLangTable[mDefaultLang]->getString(id); + + return s; +} + +const U32 LangTable::getStringLength(const U32 id) const +{ + const UTF8 *s = getString(id); + if(s) + return dStrlen(s); + + return 0; +} + +void LangTable::setDefaultLanguage(S32 langid) +{ + if(langid >= 0 && langid < mLangTable.size()) + { + if(mLangTable[langid]->activateLanguage()) + { + if(mDefaultLang >= 0) + mLangTable[mDefaultLang]->deactivateLanguage(); + + mDefaultLang = langid; + } + } +} + +void LangTable::setCurrentLanguage(S32 langid) +{ + if(langid >= 0 && langid < mLangTable.size()) + { + if(mLangTable[langid]->activateLanguage()) + { + Con::printf("Language %s [%s] activated.", mLangTable[langid]->getLangName(), mLangTable[langid]->getLangFile()); + + if(mCurrentLang >= 0 && mCurrentLang != mDefaultLang) + { + mLangTable[mCurrentLang]->deactivateLanguage(); + Con::printf("Language %s [%s] deactivated.", mLangTable[mCurrentLang]->getLangName(), mLangTable[mCurrentLang]->getLangFile()); + } + mCurrentLang = langid; + } + } +} + +//----------------------------------------------------------------------------- +// LangTable Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(LangTable, addLanguage, S32, 3, 4, "(string filename, [string languageName])") +{ + UTF8 scriptFilenameBuffer[1024]; + + Con::expandScriptFilename((char*)scriptFilenameBuffer, sizeof(scriptFilenameBuffer), argv[2]); + return object->addLanguage(scriptFilenameBuffer, argc == 4 ? (const UTF8*)argv[3] : NULL); +} + +ConsoleMethod(LangTable, getString, const char *, 3, 3, "(string filename)") +{ + const char * str = (const char*)object->getString(dAtoi(argv[2])); + if(str != NULL) + { + char * ret = Con::getReturnBuffer(dStrlen(str) + 1); + dStrcpy(ret, str); + return ret; + } + + return ""; +} + +ConsoleMethod(LangTable, setDefaultLanguage, void, 3, 3, "(int language)") +{ + object->setDefaultLanguage(dAtoi(argv[2])); +} + +ConsoleMethod(LangTable, setCurrentLanguage, void, 3, 3, "(int language)") +{ + object->setCurrentLanguage(dAtoi(argv[2])); +} + +ConsoleMethod(LangTable, getCurrentLanguage, S32, 2, 2, "()") +{ + return object->getCurrentLanguage(); +} + +ConsoleMethod(LangTable, getLangName, const char *, 3, 3, "(int language)") +{ + const char * str = (const char*)object->getLangName(dAtoi(argv[2])); + if(str != NULL) + { + char * ret = Con::getReturnBuffer(dStrlen(str) + 1); + dStrcpy(ret, str); + return ret; + } + + return ""; +} + +ConsoleMethod(LangTable, getNumLang, S32, 2, 2, "()") +{ + return object->getNumLang(); +} + +//----------------------------------------------------------------------------- +// Support Functions +//----------------------------------------------------------------------------- + +UTF8 *sanitiseVarName(const UTF8 *varName, UTF8 *buffer, U32 bufsize) +{ + if(! varName || bufsize < 10) // [tom, 3/3/2005] bufsize check gives room to be lazy below + { + *buffer = 0; + return NULL; + } + + dStrcpy(buffer, (const UTF8*)"I18N::"); + + UTF8 *dptr = buffer + 6; + const UTF8 *sptr = varName; + while(*sptr) + { + if(dIsalnum(*sptr)) + *dptr++ = *sptr++; + else + { + if(*(dptr - 1) != '_') + *dptr++ = '_'; + sptr++; + } + + if((dptr - buffer) >= (bufsize - 1)) + break; + } + *dptr = 0; + + return buffer; +} + +UTF8 *getCurrentModVarName(UTF8 *buffer, U32 bufsize) +{ + char varName[256]; + StringTableEntry cbName = CodeBlock::getCurrentCodeBlockName(); + + const UTF8 *slash = (const UTF8*)dStrchr(cbName, '/'); + if (slash == NULL) + { + Con::errorf("Illegal CodeBlock path detected in sanitiseVarName() (no mod directory): %s", cbName); + return NULL; + } + + dStrncpy(varName, cbName, slash - (const UTF8*)cbName); + varName[slash - (const UTF8*)cbName] = 0; + + return sanitiseVarName((UTF8*)varName, buffer, bufsize); +} + +const LangTable *getCurrentModLangTable() +{ + UTF8 saneVarName[256]; + + if(getCurrentModVarName(saneVarName, sizeof(saneVarName))) + { + const LangTable *lt = dynamic_cast(Sim::findObject(Con::getIntVariable((const char*)saneVarName))); + return lt; + } + return NULL; +} + +const LangTable *getModLangTable(const UTF8 *mod) +{ + UTF8 saneVarName[256]; + + if(sanitiseVarName(mod, saneVarName, sizeof(saneVarName))) + { + const LangTable *lt = dynamic_cast(Sim::findObject(Con::getIntVariable((const char*)saneVarName))); + return lt; + } + return NULL; +} diff --git a/i18n/lang.h b/i18n/lang.h new file mode 100644 index 0000000..59e0e2c --- /dev/null +++ b/i18n/lang.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +/// \file lang.h +/// \brief Header for language support +//----------------------------------------------------------------------------- + +#include "console/simBase.h" +#include "core/util/tVector.h" + +#ifndef _LANG_H_ +#define _LANG_H_ + +#define LANG_INVALID_ID 0xffffffff ///!< Invalid ID. Used for returning failure + +//----------------------------------------------------------------------------- +/// \brief Class for working with language files +//----------------------------------------------------------------------------- +class LangFile +{ +protected: + Vector mStringTable; + UTF8 * mLangName; + UTF8 * mLangFile; + + void freeTable(); + +public: + LangFile(const UTF8 *langName = NULL); + virtual ~LangFile(); + + bool load(const UTF8 *filename); + bool save(const UTF8 *filename); + + bool load(Stream *s); + bool save(Stream *s); + + const UTF8 * getString(U32 id); + U32 addString(const UTF8 *str); + + // [tom, 4/22/2005] setString() added to help the language compiler a bit + void setString(U32 id, const UTF8 *str); + + void setLangName(const UTF8 *newName); + const UTF8 *getLangName(void) { return mLangName; } + const UTF8 *getLangFile(void) { return mLangFile; } + + void setLangFile(const UTF8 *langFile); + bool activateLanguage(void); + void deactivateLanguage(void); + + bool isLoaded(void) { return mStringTable.size() > 0; } + + S32 getNumStrings(void) { return mStringTable.size(); } +}; + +//----------------------------------------------------------------------------- +/// \brief Language file table +//----------------------------------------------------------------------------- +class LangTable : public SimObject +{ + typedef SimObject Parent; + +protected: + Vector mLangTable; + S32 mDefaultLang; + S32 mCurrentLang; + +public: + DECLARE_CONOBJECT(LangTable); + + LangTable(); + virtual ~LangTable(); + + S32 addLanguage(LangFile *lang, const UTF8 *name = NULL); + S32 addLanguage(const UTF8 *filename, const UTF8 *name = NULL); + + void setDefaultLanguage(S32 langid); + void setCurrentLanguage(S32 langid); + S32 getCurrentLanguage(void) { return mCurrentLang; } + + const UTF8 * getLangName(const S32 langid) const + { + if(langid < 0 || langid >= mLangTable.size()) + return NULL; + return mLangTable[langid]->getLangName(); + } + + const S32 getNumLang(void) const { return mLangTable.size(); } + + const UTF8 * getString(const U32 id) const; + const U32 getStringLength(const U32 id) const; +}; + +extern UTF8 *sanitiseVarName(const UTF8 *varName, UTF8 *buffer, U32 bufsize); +extern UTF8 *getCurrentModVarName(UTF8 *buffer, U32 bufsize); +extern const LangTable *getCurrentModLangTable(); +extern const LangTable *getModLangTable(const UTF8 *mod); + +#endif // _LANG_H_ diff --git a/interior/forceField.cpp b/interior/forceField.cpp new file mode 100644 index 0000000..bfb5c54 --- /dev/null +++ b/interior/forceField.cpp @@ -0,0 +1,450 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/forceField.h" +#include "core/stream/stream.h" +#include "math/mathIO.h" +#include "console/console.h" +#include "collision/abstractPolyList.h" +#include "sceneGraph/sceneObject.h" + +//-------------------------------------------------------------------------- +ForceField::ForceField() +{ + VECTOR_SET_ASSOCIATION( mTriggers ); + VECTOR_SET_ASSOCIATION( mPlanes ); + VECTOR_SET_ASSOCIATION( mPoints ); + VECTOR_SET_ASSOCIATION( mBSPNodes ); + VECTOR_SET_ASSOCIATION( mBSPSolidLeaves ); + VECTOR_SET_ASSOCIATION( mSolidLeafSurfaces ); + VECTOR_SET_ASSOCIATION( mWindings ); + VECTOR_SET_ASSOCIATION( mSurfaces ); + + mPreppedForRender = false; +} + +ForceField::~ForceField() +{ + mPreppedForRender = false; +} + + +bool ForceField::prepForRendering() +{ + if (mPreppedForRender == true) + return true; + + mPreppedForRender = true; + return true; +} + + +void ForceField::render(const ColorF& rColor, const F32 fade) +{ +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- Persistence interfaces +// +const U32 ForceField::smFileVersion = 0; + +bool ForceField::read(Stream& stream) +{ + AssertFatal(stream.hasCapability(Stream::StreamRead), "ForceField::read: non-read capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "ForceField::read: Error, stream in inconsistent state"); + + U32 i; + + // Version this stream + U32 fileVersion; + stream.read(&fileVersion); + if (fileVersion != smFileVersion) { + Con::errorf(ConsoleLogEntry::General, "ForceField::read: incompatible file version found."); + return false; + } + + mName = stream.readSTString(); + U32 numTriggers; + stream.read(&numTriggers); + mTriggers.setSize(numTriggers); + for (i = 0; i < mTriggers.size(); i++) + mTriggers[i] = stream.readSTString(); + + // Geometry factors... + mathRead(stream, &mBoundingBox); + mathRead(stream, &mBoundingSphere); + + // Now read in our data vectors. + U32 vectorSize; + // mPlanes + readPlaneVector(stream); + + // mPoints + stream.read(&vectorSize); + mPoints.setSize(vectorSize); + for (i = 0; i < mPoints.size(); i++) + mathRead(stream, &mPoints[i]); + + // mBSPNodes; + stream.read(&vectorSize); + mBSPNodes.setSize(vectorSize); + for (i = 0; i < mBSPNodes.size(); i++) { + stream.read(&mBSPNodes[i].planeIndex); + stream.read(&mBSPNodes[i].frontIndex); + stream.read(&mBSPNodes[i].backIndex); + } + + // mBSPSolidLeaves + stream.read(&vectorSize); + mBSPSolidLeaves.setSize(vectorSize); + for (i = 0; i < mBSPSolidLeaves.size(); i++) { + stream.read(&mBSPSolidLeaves[i].surfaceIndex); + stream.read(&mBSPSolidLeaves[i].surfaceCount); + } + + // mWindings + stream.read(&vectorSize); + mWindings.setSize(vectorSize); + for (i = 0; i < mWindings.size(); i++) { + stream.read(&mWindings[i]); + } + + // mSurfaces + stream.read(&vectorSize); + mSurfaces.setSize(vectorSize); + for (i = 0; i < mSurfaces.size(); i++) { + stream.read(&mSurfaces[i].windingStart); + stream.read(&mSurfaces[i].windingCount); + stream.read(&mSurfaces[i].planeIndex); + stream.read(&mSurfaces[i].surfaceFlags); + stream.read(&mSurfaces[i].fanMask); + } + + // mSolidLeafSurfaces + stream.read(&vectorSize); + mSolidLeafSurfaces.setSize(vectorSize); + for (i = 0; i < mSolidLeafSurfaces.size(); i++) { + stream.read(&mSolidLeafSurfaces[i]); + } + + stream.read(&mColor); + + return stream.getStatus() == Stream::Ok; +} + +bool ForceField::write(Stream& stream) const +{ + AssertFatal(stream.hasCapability(Stream::StreamWrite), "Interior::write: non-write capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::write: Error, stream in inconsistent state"); + + U32 i; + + // Version this stream + stream.write(smFileVersion); + + stream.writeString(mName); + stream.write(mTriggers.size()); + for (i = 0; i < mTriggers.size(); i++) + stream.writeString(mTriggers[i]); + + mathWrite(stream, mBoundingBox); + mathWrite(stream, mBoundingSphere); + + // Now write out our data vectors. Remember, for cross-platform capability, no + // structure writing is allowed... + + // mPlanes + writePlaneVector(stream); + + // mPoints + stream.write(mPoints.size()); + for (i = 0; i < mPoints.size(); i++) + mathWrite(stream, mPoints[i]); + + // mBSPNodes; + stream.write(mBSPNodes.size()); + for (i = 0; i < mBSPNodes.size(); i++) { + stream.write(mBSPNodes[i].planeIndex); + stream.write(mBSPNodes[i].frontIndex); + stream.write(mBSPNodes[i].backIndex); + } + + // mBSPSolidLeaves + stream.write(mBSPSolidLeaves.size()); + for (i = 0; i < mBSPSolidLeaves.size(); i++) { + stream.write(mBSPSolidLeaves[i].surfaceIndex); + stream.write(mBSPSolidLeaves[i].surfaceCount); + } + + // mWindings + stream.write(mWindings.size()); + for (i = 0; i < mWindings.size(); i++) { + stream.write(mWindings[i]); + } + + // mSurfaces + stream.write(mSurfaces.size()); + for (i = 0; i < mSurfaces.size(); i++) { + stream.write(mSurfaces[i].windingStart); + stream.write(mSurfaces[i].windingCount); + stream.write(mSurfaces[i].planeIndex); + stream.write(mSurfaces[i].surfaceFlags); + stream.write(mSurfaces[i].fanMask); + } + + // mSolidLeafSurfaces + stream.write(mSolidLeafSurfaces.size()); + for (i = 0; i < mSolidLeafSurfaces.size(); i++) { + stream.write(mSolidLeafSurfaces[i]); + } + + stream.write(mColor); + + return stream.getStatus() == Stream::Ok; +} + +bool ForceField::writePlaneVector(Stream& stream) const +{ + // This is pretty slow, but who cares? + // + Vector uniqueNormals(mPlanes.size()); + Vector uniqueIndices(mPlanes.size()); + + U32 i; + + for (i = 0; i < mPlanes.size(); i++) { + bool inserted = false; + for (U32 j = 0; j < uniqueNormals.size(); j++) { + if (mPlanes[i] == uniqueNormals[j]) { + // Hah! Already have this one... + uniqueIndices.push_back(j); + inserted = true; + break; + } + } + + if (inserted == false) { + // Gotta do it ourselves... + uniqueIndices.push_back(uniqueNormals.size()); + uniqueNormals.push_back(Point3F(mPlanes[i].x, mPlanes[i].y, mPlanes[i].z)); + } + } + + // Ok, what we have now, is a list of unique normals, a set of indices into + // that vector, and the distances that we still have to write out by hand. + // Hop to it! + stream.write(uniqueNormals.size()); + for (i = 0; i < uniqueNormals.size(); i++) + mathWrite(stream, uniqueNormals[i]); + + stream.write(mPlanes.size()); + for (i = 0; i < mPlanes.size(); i++) { + stream.write(uniqueIndices[i]); + stream.write(mPlanes[i].d); + } + + return (stream.getStatus() == Stream::Ok); +} + +bool ForceField::readPlaneVector(Stream& stream) +{ + Vector normals; + U32 vectorSize; + + stream.read(&vectorSize); + normals.setSize(vectorSize); + U32 i; + for (i = 0; i < normals.size(); i++) + mathRead(stream, &normals[i]); + + U16 index; + stream.read(&vectorSize); + mPlanes.setSize(vectorSize); + for (i = 0; i < mPlanes.size(); i++) { + stream.read(&index); + stream.read(&mPlanes[i].d); + mPlanes[i].x = normals[index].x; + mPlanes[i].y = normals[index].y; + mPlanes[i].z = normals[index].z; + } + + return (stream.getStatus() == Stream::Ok); +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- Collision support. Essentially +// copied from the interiorCollision +// +void ForceField::collisionFanFromSurface(const Surface& rSurface, U32* fanIndices, U32* numIndices) const +{ + U32 tempIndices[32]; + + tempIndices[0] = 0; + U32 idx = 1; + U32 i; + for (i = 1; i < rSurface.windingCount; i += 2) + tempIndices[idx++] = i; + for (i = ((rSurface.windingCount - 1) & (~0x1)); i > 0; i -= 2) + tempIndices[idx++] = i; + + idx = 0; + for (i = 0; i < rSurface.windingCount; i++) { + if (rSurface.fanMask & (1 << i)) { + fanIndices[idx++] = mWindings[rSurface.windingStart + tempIndices[i]]; + } + } + *numIndices = idx; +} + +bool ForceField::castRay(const Point3F& s, const Point3F& e, RayInfo* info) +{ + bool hit = castRay_r(0, s, e, info); + if (hit) { + Point3F vec = e - s; + F32 len = vec.len(); + vec /= len; + info->t = mDot(info->point - s, vec) / len; + } + + return hit; +} + +bool ForceField::castRay_r(const U16 node, + const Point3F& s, + const Point3F& e, + RayInfo* info) +{ + if (isBSPLeafIndex(node) == false) { + const IBSPNode& rNode = mBSPNodes[node]; + const PlaneF& rPlane = getPlane(rNode.planeIndex); + + PlaneF::Side sSide = rPlane.whichSide(s); + PlaneF::Side eSide = rPlane.whichSide(e); + + switch (PlaneSwitchCode(sSide, eSide)) { + case PlaneSwitchCode(PlaneF::Front, PlaneF::Front): + case PlaneSwitchCode(PlaneF::Front, PlaneF::On): + case PlaneSwitchCode(PlaneF::On, PlaneF::Front): + return castRay_r(rNode.frontIndex, s, e, info); + break; + + case PlaneSwitchCode(PlaneF::On, PlaneF::Back): + case PlaneSwitchCode(PlaneF::Back, PlaneF::On): + case PlaneSwitchCode(PlaneF::Back, PlaneF::Back): + return castRay_r(rNode.backIndex, s, e, info); + break; + + case PlaneSwitchCode(PlaneF::On, PlaneF::On): + // Line lies on the plane + if (isBSPLeafIndex(rNode.backIndex) == false) { + if (castRay_r(rNode.backIndex, s, e, info)) + return true; + } + if (isBSPLeafIndex(rNode.frontIndex) == false) { + if (castRay_r(rNode.frontIndex, s, e, info)) + return true; + } + return false; + break; + + case PlaneSwitchCode(PlaneF::Front, PlaneF::Back): { + Point3F ip; + F32 intersectT = rPlane.intersect(s, e); + AssertFatal(intersectT != PARALLEL_PLANE, "Error, this should never happen in this case!"); + ip.interpolate(s, e, intersectT); + if (castRay_r(rNode.frontIndex, s, ip, info)) + return true; + return castRay_r(rNode.backIndex, ip, e, info); + } + break; + + case PlaneSwitchCode(PlaneF::Back, PlaneF::Front): { + Point3F ip; + F32 intersectT = rPlane.intersect(s, e); + AssertFatal(intersectT != PARALLEL_PLANE, "Error, this should never happen in this case!"); + ip.interpolate(s, e, intersectT); + if (castRay_r(rNode.backIndex, s, ip, info)) + return true; + return castRay_r(rNode.frontIndex, ip, e, info); + } + break; + + default: + AssertFatal(false, "Misunderstood switchCode in ForceField::castRay_r"); + return false; + } + } + + if (isBSPSolidLeaf(node)) { + // DMM: Set material info here + info->point = s; + return true; + } + return false; +} + +void ForceField::buildPolyList_r(const U16 node, Vector& collPlanes, AbstractPolyList* list, SphereF& s) +{ + if (isBSPLeafIndex(node) == false) { + const IBSPNode& rNode = mBSPNodes[node]; + const PlaneF& rPlane = getPlane(rNode.planeIndex); + + F32 dist = rPlane.distToPlane(s.center); + if (mFabs(dist) <= s.radius) { + // Have to do both, and push the plane back on the list... + collPlanes.push_back(rNode.planeIndex); + buildPolyList_r(rNode.frontIndex, collPlanes, list, s); + buildPolyList_r(rNode.backIndex, collPlanes, list, s); + collPlanes.pop_back(); + } else if (dist > 0.0f) { + buildPolyList_r(rNode.frontIndex, collPlanes, list, s); + } else { + buildPolyList_r(rNode.backIndex, collPlanes, list, s); + } + return; + } + + if (isBSPSolidLeaf(node)) { + const IBSPLeafSolid& rLeaf = mBSPSolidLeaves[getBSPSolidLeafIndex(node)]; + for (U32 i = 0; i < rLeaf.surfaceCount; i++) { + U32 surfaceIndex = mSolidLeafSurfaces[rLeaf.surfaceIndex + i]; + const Surface& rSurface = mSurfaces[surfaceIndex]; + for (U32 j = 0; j < collPlanes.size(); j++) { + if (areEqualPlanes(rSurface.planeIndex, collPlanes[j]) == true) { + + U32 fanVerts[32]; + U32 numVerts; + collisionFanFromSurface(rSurface, fanVerts, &numVerts); + + // DMM: Material here + list->begin(0, rSurface.planeIndex); + + U32 vertStart = list->addPoint(mPoints[fanVerts[0]]); + list->vertex(vertStart); + for (U32 k = 1; k < numVerts; k++) { + list->addPoint(mPoints[fanVerts[k]]); + list->vertex(vertStart + k); + } + list->plane(vertStart, vertStart + 1, vertStart + 2); + list->end(); + + break; + } + } + + } + } +} + +bool ForceField::buildPolyList(AbstractPolyList* list, SphereF& sphere) +{ + Vector planes; + buildPolyList_r(0, planes, list, sphere); + AssertFatal(planes.size() == 0, "Error, unbalanced plane stack!"); + + return !list->isEmpty(); +} diff --git a/interior/forceField.h b/interior/forceField.h new file mode 100644 index 0000000..41faca8 --- /dev/null +++ b/interior/forceField.h @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FORCEFIELD_H_ +#define _FORCEFIELD_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif +#ifndef _MSPHERE_H_ +#include "math/mSphere.h" +#endif +#ifndef _MPLANE_H_ +#include "math/mPlane.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif + +//-------------------------------------- forward decls. +class EditGeometry; +class InteriorInstance; +class Stream; +class AbstractPolyList; +struct RayInfo; + +//-------------------------------------------------------------------------- +class ForceField +{ + static const U32 smFileVersion; + friend class EditGeometry; + friend class InteriorInstance; + + //-------------------------------------- Public interfaces + public: + ForceField(); + ~ForceField(); + + bool prepForRendering(); + void render(const ColorF& color, const F32 fadeLevel); + const Box3F& getBoundingBox() const; + + bool castRay(const Point3F&, const Point3F&, RayInfo*); + bool buildPolyList(AbstractPolyList*, SphereF&); + //-------------------------------------- Persistence interface + public: + bool read(Stream& stream); + bool write(Stream& stream) const; + + public: + static U16 getPlaneIndex(U16 index); + static bool planeIsFlipped(U16 index); + + //-------------------------------------- BSP Structures + private: + struct IBSPNode { + U16 planeIndex; + U16 frontIndex; + U16 backIndex; + U16 __padding__; + }; + struct IBSPLeafSolid { + U32 surfaceIndex; + U16 surfaceCount; + U16 __padding__; + }; + + bool isBSPLeafIndex(U16 index) const; + bool isBSPSolidLeaf(U16 index) const; + bool isBSPEmptyLeaf(U16 index) const; + U16 getBSPSolidLeafIndex(U16 index) const; + + bool writePlaneVector(Stream&) const; + bool readPlaneVector(Stream&); + + private: + const PlaneF& getPlane(U16 index) const; + bool areEqualPlanes(U16, U16) const; + + struct Surface { + U32 windingStart; + U32 fanMask; + + U16 planeIndex; + U8 windingCount; + U8 surfaceFlags; + }; + + protected: + StringTableEntry mName; + ColorF mColor; + Vector mTriggers; + + Box3F mBoundingBox; + SphereF mBoundingSphere; + Vector mPlanes; + Vector mPoints; + + Vector mBSPNodes; + Vector mBSPSolidLeaves; + Vector mSolidLeafSurfaces; + + bool mPreppedForRender; + + Vector mWindings; + Vector mSurfaces; + + protected: + bool castRay_r(const U16, const Point3F&, const Point3F&, RayInfo*); + void buildPolyList_r(const U16, Vector&, AbstractPolyList*, SphereF&); + void collisionFanFromSurface(const Surface&, U32* fan, U32* numIndices) const; +}; + +//------------------------------------------------------------------------------ +inline bool ForceField::isBSPLeafIndex(U16 index) const +{ + return (index & 0x8000) != 0; +} + +inline bool ForceField::isBSPSolidLeaf(U16 index) const +{ + AssertFatal(isBSPLeafIndex(index) == true, "Error, only call for leaves!"); + return (index & 0x4000) != 0; +} + +inline bool ForceField::isBSPEmptyLeaf(U16 index) const +{ + AssertFatal(isBSPLeafIndex(index) == true, "Error, only call for leaves!"); + return (index & 0x4000) == 0; +} + +inline U16 ForceField::getBSPSolidLeafIndex(U16 index) const +{ + AssertFatal(isBSPSolidLeaf(index) == true, "Error, only call for leaves!"); + return (index & ~0xC000); +} + +inline const PlaneF& ForceField::getPlane(U16 index) const +{ + AssertFatal(U32(index & ~0x8000) < mPlanes.size(), + "ForceField::getPlane: planeIndex out of range"); + + return mPlanes[index & ~0x8000]; +} + +inline U16 ForceField::getPlaneIndex(U16 index) +{ + return index & ~0x8000; +} + +inline bool ForceField::planeIsFlipped(U16 index) +{ + return (index & 0x8000) != 0; +} + +inline bool ForceField::areEqualPlanes(U16 o, U16 t) const +{ + return (o & ~0x8000) == (t & ~0x8000); +} + +inline const Box3F& ForceField::getBoundingBox() const +{ + return mBoundingBox; +} + +#endif // _H_FORCEFIELD_ + diff --git a/interior/interior.cpp b/interior/interior.cpp new file mode 100644 index 0000000..d83be9a --- /dev/null +++ b/interior/interior.cpp @@ -0,0 +1,2822 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" + +#include "gfx/bitmap/gBitmap.h" +#include "math/mMatrix.h" +#include "math/mRect.h" +#include "core/bitVector.h" +#include "core/frameAllocator.h" +#include "sceneGraph/sgUtil.h" +#include "platform/profiler.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTextureHandle.h" +#include "materials/materialList.h" +#include "materials/matInstance.h" +#include "materials/materialManager.h" +#include "renderInstance/renderPassManager.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" + +U32 Interior::smRenderMode = 0; +bool Interior::smFocusedDebug = false; +bool Interior::smRenderEnvironmentMaps = true; +bool Interior::smUseVertexLighting = false; +bool Interior::smUseTexturedFog = false; +bool Interior::smLockArrays = true; +bool Interior::smLightingCastRays = false; +bool Interior::smLightingBuildPolyList = false; + +// These are setup by setupActivePolyList +U16* sgActivePolyList = NULL; +U32 sgActivePolyListSize = 0; +U16* sgEnvironPolyList = NULL; +U32 sgEnvironPolyListSize = 0; +U16* sgFogPolyList = NULL; +U32 sgFogPolyListSize = 0; +bool sgFogActive = false; + +// Always the same size as the mPoints array +Point2F* sgFogTexCoords = NULL; + +class PlaneRange +{ +public: + U32 start; + U32 count; +}; + +namespace { + +struct PortalRenderInfo +{ + bool render; + + F64 frustum[4]; + RectI viewport; +}; + +//-------------------------------------- Rendering state variables. +Point3F sgCamPoint; +F64 sgStoredFrustum[6]; +RectI sgStoredViewport; + +Vector sgZoneRenderInfo(__FILE__, __LINE__); + +// Takes OS coords to clip space... +MatrixF sgWSToOSMatrix; +MatrixF sgProjMatrix; + +PlaneF sgOSPlaneFar; +PlaneF sgOSPlaneXMin; +PlaneF sgOSPlaneXMax; +PlaneF sgOSPlaneYMin; +PlaneF sgOSPlaneYMax; + +struct ZoneRect { + RectD rect; + bool active; +}; + +Vector sgZoneRects(__FILE__, __LINE__); + +//-------------------------------------- Little utility functions +RectD outlineRects(const Vector& rects) +{ + F64 minx = 1e10; + F64 maxx = -1e10; + F64 miny = 1e10; + F64 maxy = -1e10; + + for (S32 i = 0; i < rects.size(); i++) + { + if (rects[i].point.x < minx) + minx = rects[i].point.x; + if (rects[i].point.y < miny) + miny = rects[i].point.y; + + if (rects[i].point.x + rects[i].extent.x > maxx) + maxx = rects[i].point.x + rects[i].extent.x; + if (rects[i].point.y + rects[i].extent.y > maxy) + maxy = rects[i].point.y + rects[i].extent.y; + } + + return RectD(minx, miny, maxx - minx, maxy - miny); +} + +void insertZoneRects(ZoneRect& rZoneRect, const RectD* rects, const U32 numRects) +{ + F64 minx = 1e10; + F64 maxx = -1e10; + F64 miny = 1e10; + F64 maxy = -1e10; + + for (U32 i = 0; i < numRects; i++) { + if (rects[i].point.x < minx) + minx = rects[i].point.x; + if (rects[i].point.y < miny) + miny = rects[i].point.y; + + if (rects[i].point.x + rects[i].extent.x > maxx) + maxx = rects[i].point.x + rects[i].extent.x; + if (rects[i].point.y + rects[i].extent.y > maxy) + maxy = rects[i].point.y + rects[i].extent.y; + } + + if (rZoneRect.active == false && numRects != 0) { + rZoneRect.rect = RectD(minx, miny, maxx - minx, maxy - miny); + rZoneRect.active = true; + } else { + if (rZoneRect.rect.point.x < minx) + minx = rZoneRect.rect.point.x; + if (rZoneRect.rect.point.y < miny) + miny = rZoneRect.rect.point.y; + + if (rZoneRect.rect.point.x + rZoneRect.rect.extent.x > maxx) + maxx = rZoneRect.rect.point.x + rZoneRect.rect.extent.x; + if (rZoneRect.rect.point.y + rZoneRect.rect.extent.y > maxy) + maxy = rZoneRect.rect.point.y + rZoneRect.rect.extent.y; + + rZoneRect.rect = RectD(minx, miny, maxx - minx, maxy - miny); + } +} + + + +void fixupViewport(PortalRenderInfo& rInfo) +{ + F64 widthV = rInfo.frustum[1] - rInfo.frustum[0]; + F64 heightV = rInfo.frustum[3] - rInfo.frustum[2]; + + F64 fx0 = (rInfo.frustum[0] - sgStoredFrustum[0]) / (sgStoredFrustum[1] - sgStoredFrustum[0]); + F64 fx1 = (sgStoredFrustum[1] - rInfo.frustum[1]) / (sgStoredFrustum[1] - sgStoredFrustum[0]); + + F64 dV0 = F64(sgStoredViewport.point.x) + fx0 * F64(sgStoredViewport.extent.x); + F64 dV1 = F64(sgStoredViewport.point.x + + sgStoredViewport.extent.x) - fx1 * F64(sgStoredViewport.extent.x); + + F64 fdV0 = getMax(mFloorD(dV0), F64(sgStoredViewport.point.x)); + F64 cdV1 = getMin(mCeilD(dV1), F64(sgStoredViewport.point.x + sgStoredViewport.extent.x)); + + // If the width is 1 pixel, we need to widen it up a bit... + if ((cdV1 - fdV0) <= 1.0) + cdV1 = fdV0 + 1; + AssertFatal((fdV0 >= sgStoredViewport.point.x && + cdV1 <= sgStoredViewport.point.x + sgStoredViewport.extent.x), + "Out of bounds viewport bounds"); + + F64 new0 = rInfo.frustum[0] - ((dV0 - fdV0) * (widthV / F64(sgStoredViewport.extent.x))); + F64 new1 = rInfo.frustum[1] + ((cdV1 - dV1) * (widthV / F64(sgStoredViewport.extent.x))); + + rInfo.frustum[0] = new0; + rInfo.frustum[1] = new1; + + rInfo.viewport.point.x = S32(fdV0); + rInfo.viewport.extent.x = S32(cdV1) - rInfo.viewport.point.x; + + F64 fy0 = (sgStoredFrustum[3] - rInfo.frustum[3]) / (sgStoredFrustum[3] - sgStoredFrustum[2]); + F64 fy1 = (rInfo.frustum[2] - sgStoredFrustum[2]) / (sgStoredFrustum[3] - sgStoredFrustum[2]); + + dV0 = F64(sgStoredViewport.point.y) + fy0 * F64(sgStoredViewport.extent.y); + dV1 = F64(sgStoredViewport.point.y + + sgStoredViewport.extent.y) - fy1 * F64(sgStoredViewport.extent.y); + fdV0 = getMax(mFloorD(dV0), F64(sgStoredViewport.point.y)); + cdV1 = getMin(mCeilD(dV1), F64(sgStoredViewport.point.y + sgStoredViewport.extent.y)); + + // If the width is 1 pixel, we need to widen it up a bit... + if ((cdV1 - fdV0) <= 1.0) + cdV1 = fdV0 + 1; + // GFX2_RENDER_MERGE + // Need to fix this properly but for now *HACK* +#ifndef TORQUE_OS_MAC + AssertFatal((fdV0 >= sgStoredViewport.point.y && + cdV1 <= sgStoredViewport.point.y + sgStoredViewport.extent.y), + "Out of bounds viewport bounds"); +#endif + + new0 = rInfo.frustum[2] - ((cdV1 - dV1) * (heightV / F64(sgStoredViewport.extent.y))); + new1 = rInfo.frustum[3] + ((dV0 - fdV0) * (heightV / F64(sgStoredViewport.extent.y))); + rInfo.frustum[2] = new0; + rInfo.frustum[3] = new1; + + rInfo.viewport.point.y = S32(fdV0); + rInfo.viewport.extent.y = S32(cdV1) - rInfo.viewport.point.y; +} + +RectD convertToRectD(const F64 inResult[4]) +{ + F64 minx = ((inResult[0] + 1.0f) / 2.0f) * (sgStoredFrustum[1] - sgStoredFrustum[0]) + sgStoredFrustum[0]; + F64 maxx = ((inResult[2] + 1.0f) / 2.0f) * (sgStoredFrustum[1] - sgStoredFrustum[0]) + sgStoredFrustum[0]; + + F64 miny = ((inResult[1] + 1.0f) / 2.0f) * (sgStoredFrustum[3] - sgStoredFrustum[2]) + sgStoredFrustum[2]; + F64 maxy = ((inResult[3] + 1.0f) / 2.0f) * (sgStoredFrustum[3] - sgStoredFrustum[2]) + sgStoredFrustum[2]; + + return RectD(minx, miny, (maxx - minx), (maxy - miny)); +} + +void convertToFrustum(PortalRenderInfo& zrInfo, const RectD& finalRect) +{ + zrInfo.frustum[0] = finalRect.point.x; // left + zrInfo.frustum[1] = finalRect.point.x + finalRect.extent.x; // right + zrInfo.frustum[2] = finalRect.point.y; // bottom + zrInfo.frustum[3] = finalRect.point.y + finalRect.extent.y; // top + + fixupViewport(zrInfo); +} + +} // namespace {} + + +//------------------------------------------------------------------------------ +//-------------------------------------- IMPLEMENTATION +// +Interior::Interior() +{ + mMaterialList = NULL; + + mHasTranslucentMaterials = false; + + mLMHandle = LM_HANDLE(-1); + + // By default, no alarm state, no animated light states + mHasAlarmState = false; + + mNumLightStateEntries = 0; + mNumTriggerableLights = 0; + + mPreppedForRender = false;; + mSearchTag = 0; + + mLightMapBorderSize = 0; + +#ifndef TORQUE_SHIPPING + mDebugShader = NULL; + + mDebugShaderModelViewSC = NULL; + mDebugShaderShadeColorSC = NULL; +#endif + + // Bind our vectors + VECTOR_SET_ASSOCIATION(mPlanes); + VECTOR_SET_ASSOCIATION(mPoints); + VECTOR_SET_ASSOCIATION(mBSPNodes); + VECTOR_SET_ASSOCIATION(mBSPSolidLeaves); + VECTOR_SET_ASSOCIATION(mWindings); + VECTOR_SET_ASSOCIATION(mTexGenEQs); + VECTOR_SET_ASSOCIATION(mLMTexGenEQs); + VECTOR_SET_ASSOCIATION(mWindingIndices); + VECTOR_SET_ASSOCIATION(mSurfaces); + VECTOR_SET_ASSOCIATION(mNullSurfaces); + VECTOR_SET_ASSOCIATION(mSolidLeafSurfaces); + VECTOR_SET_ASSOCIATION(mZones); + VECTOR_SET_ASSOCIATION(mZonePlanes); + VECTOR_SET_ASSOCIATION(mZoneSurfaces); + VECTOR_SET_ASSOCIATION(mZonePortalList); + VECTOR_SET_ASSOCIATION(mPortals); + //VECTOR_SET_ASSOCIATION(mSubObjects); + VECTOR_SET_ASSOCIATION(mLightmaps); + VECTOR_SET_ASSOCIATION(mLightmapKeep); + VECTOR_SET_ASSOCIATION(mNormalLMapIndices); + VECTOR_SET_ASSOCIATION(mAlarmLMapIndices); + VECTOR_SET_ASSOCIATION(mAnimatedLights); + VECTOR_SET_ASSOCIATION(mLightStates); + VECTOR_SET_ASSOCIATION(mStateData); + VECTOR_SET_ASSOCIATION(mStateDataBuffer); + VECTOR_SET_ASSOCIATION(mNameBuffer); + VECTOR_SET_ASSOCIATION(mConvexHulls); + VECTOR_SET_ASSOCIATION(mConvexHullEmitStrings); + VECTOR_SET_ASSOCIATION(mHullIndices); + VECTOR_SET_ASSOCIATION(mHullEmitStringIndices); + VECTOR_SET_ASSOCIATION(mHullSurfaceIndices); + VECTOR_SET_ASSOCIATION(mHullPlaneIndices); + VECTOR_SET_ASSOCIATION(mPolyListPlanes); + VECTOR_SET_ASSOCIATION(mPolyListPoints); + VECTOR_SET_ASSOCIATION(mPolyListStrings); + VECTOR_SET_ASSOCIATION(mCoordBinIndices); + + VECTOR_SET_ASSOCIATION(mVehicleConvexHulls); + VECTOR_SET_ASSOCIATION(mVehicleConvexHullEmitStrings); + VECTOR_SET_ASSOCIATION(mVehicleHullIndices); + VECTOR_SET_ASSOCIATION(mVehicleHullEmitStringIndices); + VECTOR_SET_ASSOCIATION(mVehicleHullSurfaceIndices); + VECTOR_SET_ASSOCIATION(mVehicleHullPlaneIndices); + VECTOR_SET_ASSOCIATION(mVehiclePolyListPlanes); + VECTOR_SET_ASSOCIATION(mVehiclePolyListPoints); + VECTOR_SET_ASSOCIATION(mVehiclePolyListStrings); + VECTOR_SET_ASSOCIATION(mVehiclePoints); + VECTOR_SET_ASSOCIATION(mVehicleNullSurfaces); + VECTOR_SET_ASSOCIATION(mVehiclePlanes); +} + +Interior::~Interior() +{ + U32 i; + delete mMaterialList; + mMaterialList = NULL; + + if(mLMHandle != LM_HANDLE(-1)) + gInteriorLMManager.removeInterior(mLMHandle); + + for(i = 0; i < mLightmaps.size(); i++) + { + delete mLightmaps[i]; + mLightmaps[i] = NULL; + } + + for(i = 0; i < mMatInstCleanupList.size(); i++) + { + delete mMatInstCleanupList[i]; + mMatInstCleanupList[i] = NULL; + } + + for(S32 i=0; i &matNames = mMaterialList->getMaterialNameList(); + Vector originalNames = matNames; + + for (U32 i = 0; i < matNames.size(); i++) + { + if (matNames[i].equal("NULL", String::NoCase) || + matNames[i].equal("ORIGIN", String::NoCase) || + matNames[i].equal("TRIGGER", String::NoCase) || + matNames[i].equal("FORCEFIELD", String::NoCase) || + matNames[i].equal("EMITTER", String::NoCase) ) + { + mMaterialList->setMaterialName(i, String()); + } + } + + String relPath = Platform::makeRelativePathName(path, Platform::getCurrentDirectory()); + + // Load the material list + mMaterialList->load(relPath); + mMaterialList->mapMaterials(); + + // Grab all the static meshes and load any textures that didn't originate + // from inside the DIF. + for(S32 i=0; imaterialList->load(relPath); + + // Now restore the material names since someone later may + // count on the special texture names being present. + for (U32 i = 0; i < originalNames.size(); i++) + mMaterialList->setMaterialName(i, originalNames[i]); + + + fillSurfaceTexMats(); + createZoneVBs(); + cloneMatInstances(); + createReflectPlanes(); + initMatInstances(); + + // lightmap manager steals the lightmaps here... + gInteriorLMManager.addInterior(mLMHandle, mLightmaps.size(), this); + AssertFatal(!mLightmaps.size(), "Failed to process lightmaps"); + + for(U32 i=0; iprepForRendering(relPath); + + GFXStateBlockDesc sh; + +#ifndef TORQUE_SHIPPING + // First create a default state block with + // texturing turned off + mInteriorDebugNoneSB = GFX->createStateBlock(sh); + + // Create a state block for portal rendering that + // doesn't have backface culling enabled + sh.cullDefined = true; + sh.cullMode = GFXCullNone; + + mInteriorDebugPortalSB = GFX->createStateBlock(sh); + + // Reset our cull mode to the default + sh.cullMode = GFXCullCCW; +#endif + + // Next turn on the first texture channel + sh.samplersDefined = true; + sh.samplers[0].textureColorOp = GFXTOPModulate; + +#ifndef TORQUE_SHIPPING + mInteriorDebugTextureSB = GFX->createStateBlock(sh); + + sh.samplers[1].textureColorOp = GFXTOPModulate; + + mInteriorDebugTwoTextureSB = GFX->createStateBlock(sh); +#endif + + // Lastly create a standard rendering state block + sh.samplers[2].textureColorOp = GFXTOPModulate; + + sh.samplers[0].magFilter = GFXTextureFilterLinear; + sh.samplers[0].minFilter = GFXTextureFilterLinear; + + mInteriorSB = GFX->createStateBlock(sh); + + mPreppedForRender = true; + + return true; +} + + +void Interior::setupAveTexGenLength() +{ +/* + F32 len = 0; + for (U32 i = 0; i < mSurfaces.size(); i++) + { + // We're going to assume that most textures don't have separate scales for + // x and y... + F32 lenx = mTexGenEQs[mSurfaces[i].texGenIndex].planeX.len(); + len += F32((*mMaterialList)[mSurfaces[i].textureIndex].getWidth()) * lenx; + } + len /= F32(mSurfaces.size()); + mAveTexGenLength = len; +*/ +} + + +//-------------------------------------------------------------------------- +bool Interior::prepRender(SceneState* state, + S32 containingZone, + S32 baseZone, + U32 zoneOffset, + const MatrixF& OSToWS, + const Point3F& objScale, + const bool modifyBaseState, + const bool dontRestrictOutside, + const bool flipClipPlanes) +{ + // Store off the viewport and frustum + if(modifyBaseState || dontRestrictOutside ) + { + sgStoredViewport = state->getBaseZoneState().viewport; + sgStoredFrustum[0] = state->getBaseZoneState().frustum.getNearLeft(); + sgStoredFrustum[1] = state->getBaseZoneState().frustum.getNearRight(); + sgStoredFrustum[2] = state->getBaseZoneState().frustum.getNearBottom(); + sgStoredFrustum[3] = state->getBaseZoneState().frustum.getNearTop(); + sgStoredFrustum[4] = state->getNearPlane(); + sgStoredFrustum[5] = state->getFarPlane(); + } + else + { + sgStoredViewport = state->getZoneState(containingZone).viewport; + sgStoredFrustum[0] = state->getZoneState(containingZone).frustum.getNearLeft(); + sgStoredFrustum[1] = state->getZoneState(containingZone).frustum.getNearRight(); + sgStoredFrustum[2] = state->getZoneState(containingZone).frustum.getNearBottom(); + sgStoredFrustum[3] = state->getZoneState(containingZone).frustum.getNearTop(); + sgStoredFrustum[4] = state->getNearPlane(); + sgStoredFrustum[5] = state->getFarPlane(); + } + + + //Getting mirrors working, 2/15/2008 + /* + MatrixF proj = GFX->getProjectionMatrix(); + + GFX->setFrustum(sgStoredFrustum[0], sgStoredFrustum[1], + sgStoredFrustum[2], sgStoredFrustum[3], + sgStoredFrustum[4], sgStoredFrustum[5]); + */ + + sgProjMatrix = GFX->getProjectionMatrix(); + + //More getting mirrors working, 2/15/2008 + /* + GFX->setProjectionMatrix( proj ); + */ + + MatrixF finalModelView = GFX->getWorldMatrix(); + finalModelView.mul(OSToWS); + finalModelView.scale(Point3F(objScale.x, objScale.y, objScale.z)); + sgProjMatrix.mul(finalModelView); + + finalModelView.inverse(); + finalModelView.mulP(Point3F(0, 0, 0), &sgCamPoint); + sgWSToOSMatrix = finalModelView; + + // do the zone traversal + sgZoneRenderInfo.setSize(mZones.size()); + zoneTraversal(baseZone, flipClipPlanes); + + // Copy out the information for all zones but the outside zone. + for(U32 i = 1; i < mZones.size(); i++) + { + AssertFatal(zoneOffset != 0xFFFFFFFF, "Error, this should never happen!"); + U32 globalIndex = i + zoneOffset - 1; + + SceneState::ZoneState& rState = state->getZoneStateNC(globalIndex); + rState.render = sgZoneRenderInfo[i].render; + if(rState.render) + { + rState.frustum.set( rState.frustum.isOrtho(), + sgZoneRenderInfo[i].frustum[0], + sgZoneRenderInfo[i].frustum[1], + sgZoneRenderInfo[i].frustum[3], + sgZoneRenderInfo[i].frustum[2], + rState.frustum.getNearDist(), + rState.frustum.getFarDist() ); + + rState.viewport = sgZoneRenderInfo[i].viewport; + } + } + + if(modifyBaseState) + { + // Need to modify the state's baseZoneState based on the outside zone's (0), + // parameters. + if(sgZoneRenderInfo[0].render == true) + { + SceneState::ZoneState& rState = state->getBaseZoneStateNC(); + rState.frustum.set( rState.frustum.isOrtho(), + sgZoneRenderInfo[0].frustum[0], + sgZoneRenderInfo[0].frustum[1], + sgZoneRenderInfo[0].frustum[3], + sgZoneRenderInfo[0].frustum[2], + rState.frustum.getNearDist(), + rState.frustum.getFarDist() ); + + rState.viewport = sgZoneRenderInfo[0].viewport; + } + } + destroyZoneRectVectors(); + + // If zone 0 is rendered, then we return true... + return sgZoneRenderInfo[0].render; +} + + +void Interior::prepTempRender(SceneState* state, + S32 containingZone, + S32 baseZone, + const MatrixF& OSToWS, + const Point3F& objScale, + const bool flipClipPlanes) +{ +} + + +//------------------------------------------------------------------------------ +S32 Interior::getZoneForPoint(const Point3F& rPoint) const +{ + const IBSPNode* pNode = &mBSPNodes[0]; + + while (true) { + F32 dist = getPlane(pNode->planeIndex).distToPlane(rPoint); + if (planeIsFlipped(pNode->planeIndex)) + dist = -dist; + + U32 traverseIndex; + if (dist >= 0) + traverseIndex = pNode->frontIndex; + else + traverseIndex = pNode->backIndex; + + if (isBSPLeafIndex(traverseIndex)) { + if (isBSPSolidLeaf(traverseIndex)) { + return -1; + } else { + U16 zone = getBSPEmptyLeafZone(traverseIndex); + if (zone == 0x0FFF) + return -1; + else + return zone; + } + } + + pNode = &mBSPNodes[traverseIndex]; + } +} + + +//-------------------------------------------------------------------------- +static void itrClipToPlane(Point3F* points, U32& rNumPoints, const PlaneF& rPlane) +{ + S32 start = -1; + for(U32 i = 0; i < rNumPoints; i++) + { + if(rPlane.whichSide(points[i]) == PlaneF::Front) + { + start = i; + break; + } + } + + // Nothing was in front of the plane... + if(start == -1) + { + rNumPoints = 0; + return; + } + + static Point3F finalPoints[128]; + U32 numFinalPoints = 0; + + U32 baseStart = start; + U32 end = (start + 1) % rNumPoints; + + while(end != baseStart) + { + const Point3F& rStartPoint = points[start]; + const Point3F& rEndPoint = points[end]; + + PlaneF::Side fSide = rPlane.whichSide(rStartPoint); + PlaneF::Side eSide = rPlane.whichSide(rEndPoint); + + S32 code = fSide * 3 + eSide; + switch(code) + { + case 4: // f f + case 3: // f o + case 1: // o f + case 0: // o o + // No Clipping required + finalPoints[numFinalPoints++] = points[start]; + start = end; + end = (end + 1) % rNumPoints; + break; + + + case 2: // f b + { + // In this case, we emit the front point, Insert the intersection, + // and advancing to point to first point that is in front or on... + // + finalPoints[numFinalPoints++] = points[start]; + + Point3F vector = rEndPoint - rStartPoint; + F32 t = -(rPlane.distToPlane(rStartPoint) / mDot(rPlane, vector)); + + Point3F intersection = rStartPoint + (vector * t); + finalPoints[numFinalPoints++] = intersection; + + U32 endSeek = (end + 1) % rNumPoints; + while(rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + vector = rNewEndPoint - rNewStartPoint; + t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + } + break; + + case -1: // o b + { + // In this case, we emit the front point, and advance to point to first + // point that is in front or on... + // + finalPoints[numFinalPoints++] = points[start]; + + U32 endSeek = (end + 1) % rNumPoints; + while(rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + Point3F vector = rNewEndPoint - rNewStartPoint; + F32 t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + Point3F intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + } + break; + + case -2: // b f + case -3: // b o + case -4: // b b + // In the algorithm used here, this should never happen... + AssertISV(false, "CSGPlane::clipWindingToPlaneFront: error in polygon clipper"); + break; + + default: + AssertFatal(false, "CSGPlane::clipWindingToPlaneFront: bad outcode"); + break; + } + + } + + // Emit the last point. + finalPoints[numFinalPoints++] = points[start]; + AssertFatal(numFinalPoints >= 3, avar("Error, this shouldn't happen! Invalid winding in itrClipToPlane: %d", numFinalPoints)); + + // Copy the new rWinding, and we're set! + // + dMemcpy(points, finalPoints, numFinalPoints * sizeof(Point3F)); + rNumPoints = numFinalPoints; + AssertISV(rNumPoints <= 128, "Increase maxWindingPoints. Talk to DMoore"); +} + +bool Interior::projectClipAndBoundFan(U32 fanIndex, F64* pResult) +{ + const TriFan& rFan = mWindingIndices[fanIndex]; + + static Point3F windingPoints[128]; + U32 numPoints = rFan.windingCount; + U32 i; + for(i = 0; i < numPoints; i++) + windingPoints[i] = mPoints[mWindings[rFan.windingStart + i]].point; + + itrClipToPlane(windingPoints, numPoints, sgOSPlaneFar); + if(numPoints != 0) + itrClipToPlane(windingPoints, numPoints, sgOSPlaneXMin); + if(numPoints != 0) + itrClipToPlane(windingPoints, numPoints, sgOSPlaneXMax); + if(numPoints != 0) + itrClipToPlane(windingPoints, numPoints, sgOSPlaneYMin); + if(numPoints != 0) + itrClipToPlane(windingPoints, numPoints, sgOSPlaneYMax); + + if(numPoints == 0) + { + pResult[0] = + pResult[1] = + pResult[2] = + pResult[3] = 0.0f; + return false; + } + + F32 minX = 1e10; + F32 maxX = -1e10; + F32 minY = 1e10; + F32 maxY = -1e10; + static Point4F projPoints[128]; + for(i = 0; i < numPoints; i++) + { + projPoints[i].set(windingPoints[i].x, windingPoints[i].y, windingPoints[i].z, 1.0); + sgProjMatrix.mul(projPoints[i]); + + AssertFatal(projPoints[i].w != 0.0, "Error, that's bad!"); + projPoints[i].x /= projPoints[i].w; + projPoints[i].y /= projPoints[i].w; + + if(projPoints[i].x < minX) + minX = projPoints[i].x; + if(projPoints[i].x > maxX) + maxX = projPoints[i].x; + if(projPoints[i].y < minY) + minY = projPoints[i].y; + if(projPoints[i].y > maxY) + maxY = projPoints[i].y; + } + + if(minX < -1.0f) minX = -1.0f; + if(minY < -1.0f) minY = -1.0f; + if(maxX > 1.0f) maxX = 1.0f; + if(maxY > 1.0f) maxY = 1.0f; + + pResult[0] = minX; + pResult[1] = minY; + pResult[2] = maxX; + pResult[3] = maxY; + return true; +} + +void Interior::createZoneRectVectors() +{ + sgZoneRects.setSize(mZones.size()); + for(U32 i = 0; i < mZones.size(); i++) + sgZoneRects[i].active = false; +} + +void Interior::destroyZoneRectVectors() +{ + +} + +void Interior::traverseZone(const RectD* inputRects, const U32 numInputRects, U32 currZone, Vector& zoneStack) +{ + PROFILE_START(InteriorTraverseZone); + // First, we push onto our rect list all the inputRects... + insertZoneRects(sgZoneRects[currZone], inputRects, numInputRects); + + // A portal is a valid traversal if the camera point is on the + // same side of it's plane as the zone. It must then pass the + // clip/project test. + U32 i; + const Zone& rZone = mZones[currZone]; + for(i = rZone.portalStart; i < U32(rZone.portalStart + rZone.portalCount); i++) + { + const Portal& rPortal = mPortals[mZonePortalList[i]]; + AssertFatal(U32(rPortal.zoneFront) == currZone || U32(rPortal.zoneBack) == currZone, + "Portal doesn't reference this zone?"); + + S32 camSide = getPlane(rPortal.planeIndex).whichSide(sgCamPoint); + if(planeIsFlipped(rPortal.planeIndex)) + camSide = -camSide; + S32 zoneSide = (U32(rPortal.zoneFront) == currZone) ? 1 : -1; + U16 otherZone = (U32(rPortal.zoneFront) == currZone) ? rPortal.zoneBack : rPortal.zoneFront; + + // Make sure this isn't a free floating portal... + if(otherZone == currZone) + continue; + + // Make sure we haven't encountered this zone already in this traversal + bool onStack = false; + for(U32 i = 0; i < zoneStack.size(); i++) + { + if(otherZone == zoneStack[i]) + { + onStack = true; + break; + } + } + if(onStack == true) + continue; + + if(camSide == zoneSide) + { + // Can traverse. Note: special case PlaneF::On + // here to prevent possible w == 0 problems and infinite recursion +// Vector newRects; +// VECTOR_SET_ASSOCIATION(newRects); + + // We're abusing the heck out of the allocator here. + U32 waterMark = FrameAllocator::getWaterMark(); + RectD* newRects = (RectD*)FrameAllocator::alloc(1); + U32 numNewRects = 0; + + for(S32 j = 0; j < rPortal.triFanCount; j++) + { + F64 result[4]; + if(projectClipAndBoundFan(rPortal.triFanStart + j, result)) + { + // Have a good rect from this. + RectD possible = convertToRectD(result); + + for(U32 k = 0; k < numInputRects; k++) + { + RectD copy = possible; + if(copy.intersect(inputRects[k])) + newRects[numNewRects++] = copy; + } + } + } + + if(numNewRects != 0) + { + FrameAllocator::alloc((sizeof(RectD) * numNewRects) - 1); + + U32 prevStackSize = zoneStack.size(); + zoneStack.push_back(currZone); + traverseZone(newRects, numNewRects, otherZone, zoneStack); + zoneStack.pop_back(); + AssertFatal(zoneStack.size() == prevStackSize, "Error, stack size changed!"); + } + FrameAllocator::setWaterMark(waterMark); + } + else if(camSide == PlaneF::On) + { + U32 waterMark = FrameAllocator::getWaterMark(); + RectD* newRects = (RectD*)FrameAllocator::alloc(numInputRects * sizeof(RectD)); + dMemcpy(newRects, inputRects, sizeof(RectD) * numInputRects); + + U32 prevStackSize = zoneStack.size(); + zoneStack.push_back(currZone); + traverseZone(newRects, numInputRects, otherZone, zoneStack); + zoneStack.pop_back(); + AssertFatal(zoneStack.size() == prevStackSize, "Error, stack size changed!"); + FrameAllocator::setWaterMark(waterMark); + } + } + PROFILE_END(); +} + +void Interior::zoneTraversal(S32 baseZone, const bool flipClip) +{ + PROFILE_START(InteriorZoneTraversal); + // If we're in solid, render everything... + if(baseZone == -1) + { + for(U32 i = 0; i < mZones.size(); i++) + { + sgZoneRenderInfo[i].render = true; + + sgZoneRenderInfo[i].frustum[0] = sgStoredFrustum[0]; + sgZoneRenderInfo[i].frustum[1] = sgStoredFrustum[1]; + sgZoneRenderInfo[i].frustum[2] = sgStoredFrustum[2]; + sgZoneRenderInfo[i].frustum[3] = sgStoredFrustum[3]; + sgZoneRenderInfo[i].viewport = sgStoredViewport; + } + PROFILE_END(); + return; + } + + // Otherwise, we're going to have to do some work... + createZoneRectVectors(); + U32 i; + for(i = 0; i < mZones.size(); i++) + sgZoneRenderInfo[i].render = false; + + // Create the object space clipping planes... + sgComputeOSFrustumPlanes(sgStoredFrustum, + sgWSToOSMatrix, + sgCamPoint, + sgOSPlaneFar, + sgOSPlaneXMin, + sgOSPlaneXMax, + sgOSPlaneYMin, + sgOSPlaneYMax); + + if(flipClip == true) + { + sgOSPlaneXMin.neg(); + sgOSPlaneXMax.neg(); + sgOSPlaneYMin.neg(); + sgOSPlaneYMax.neg(); + } + // First, the current zone gets the full clipRect, and marked as rendering... + static const F64 fullResult[4] = { -1, -1, 1, 1}; + + static Vector zoneStack; + zoneStack.clear(); + VECTOR_SET_ASSOCIATION(zoneStack); + + RectD baseRect = convertToRectD(fullResult); + traverseZone(&baseRect, 1, baseZone, zoneStack); + + for(i = 0; i < mZones.size(); i++) + { + if(sgZoneRects[i].active == true) + { + sgZoneRenderInfo[i].render = true; + convertToFrustum(sgZoneRenderInfo[i], sgZoneRects[i].rect); + } + } + PROFILE_END(); +} + + +void mergeSurfaceVectors(const U16* from0, + const U32 size0, + const U16* from1, + const U32 size1, + U16* output, + U32* outputSize) +{ + U32 pos0 = 0; + U32 pos1 = 0; + U32 outputCount = 0; + while(pos0 < size0 && pos1 < size1) + { + if(from0[pos0] < from1[pos1]) + { + output[outputCount++] = from0[pos0++]; + } + else if(from0[pos0] == from1[pos1]) + { + // Equal, output one, and inc both counts + output[outputCount++] = from0[pos0++]; + pos1++; + } + else + { + output[outputCount++] = from1[pos1++]; + } + } + AssertFatal(pos0 == size0 || pos1 == size1, "Error, one of these must have reached the end!"); + + // Copy the dregs... + if(pos0 != size0) + { + dMemcpy(&output[outputCount], &from0[pos0], sizeof(U16) * (size0 - pos0)); + outputCount += size0 - pos0; + } + else if(pos1 != size1) + { + dMemcpy(&output[outputCount], &from1[pos1], sizeof(U16) * (size1 - pos1)); + outputCount += size1 - pos1; + } + + *outputSize = outputCount; +} + +struct ItrMergeStruct +{ + U16* array; + U32 size; +}; + + + + +void Interior::scopeZone(const U32 currZone, + bool* interiorScopingState, + const Point3F& interiorRoot, + Vector& zoneStack, + Vector& planeStack, + Vector& planeRangeStack) +{ + // First, if we're here, this zone is scoped... + interiorScopingState[currZone] = true; + + // A portal is a valid traversal if the camera point is on the + // same side of it's plane as the zone. It must then pass the + // clip/project test. + const Zone& rZone = mZones[currZone]; + for(S32 i = rZone.portalStart; i < U32(rZone.portalStart + rZone.portalCount); i++) + { + const Portal& rPortal = mPortals[mZonePortalList[i]]; + AssertFatal(U32(rPortal.zoneFront) == currZone || U32(rPortal.zoneBack) == currZone, + "Portal doesn't reference this zone?"); + + S32 camSide = getPlane(rPortal.planeIndex).whichSide(interiorRoot); + if(planeIsFlipped(rPortal.planeIndex)) + camSide = -camSide; + S32 zoneSide = (U32(rPortal.zoneFront) == currZone) ? PlaneF::Front : PlaneF::Back; + U16 otherZone = (U32(rPortal.zoneFront) == currZone) ? rPortal.zoneBack : rPortal.zoneFront; + + // Make sure this isn't a free floating portal... + if(otherZone == currZone) + continue; + + // Make sure we haven't encountered this zone already in this traversal + bool onStack = false; + for(U32 i = 0; i < zoneStack.size(); i++) + { + if(otherZone == zoneStack[i]) + { + onStack = true; + break; + } + } + if(onStack == true) + continue; + + if(camSide == zoneSide) + { + // Can traverse. Note: special case PlaneF::On + + // push ourselves onto the zonestack + zoneStack.push_back(currZone); + + for(S32 j = 0; j < rPortal.triFanCount; j++) + { + U32 k; + const TriFan& rFan = mWindingIndices[rPortal.triFanStart + j]; + + // Create the winding for this portal + // + static Point3F windingPoints[128]; + U32 numPoints = rFan.windingCount; + for(k = 0; k < numPoints; k++) + windingPoints[k] = mPoints[mWindings[rFan.windingStart + k]].point; + + // Clip the winding against the planes in the current range + for(k = 0; k < planeRangeStack.last().count; k++) + { + const PlaneF& rPlane = planeStack[planeRangeStack.last().start + k]; + itrClipToPlane(windingPoints, numPoints, rPlane); + if(numPoints == 0) + break; + } + // If the winding is now empty, bail + // + if(numPoints == 0) + continue; + + // create new planes and range from the winding that remains. There is one + // plane for each winding point. + // + planeRangeStack.increment(); + planeRangeStack.last().start = planeStack.size(); + planeRangeStack.last().count = numPoints; + planeStack.increment(numPoints); + for(k = 0; k < numPoints; k++) + { + U32 s = k; + U32 e = (k + 1) % numPoints; + + planeStack[planeRangeStack.last().start + k].set(interiorRoot, + windingPoints[e], + windingPoints[s]); + if(zoneSide == -1) + planeStack[planeRangeStack.last().start + k].neg(); + } + + // traverse into new zone + scopeZone(otherZone, + interiorScopingState, + interiorRoot, + zoneStack, + planeStack, + planeRangeStack); + + // pop off range, planes + planeStack.decrement(planeRangeStack.last().count); + planeRangeStack.pop_back(); + } + + // Pop ourselves off the stack + zoneStack.pop_back(); + } + else if(camSide == PlaneF::On) + { + // Special case. Have to drill down with the same frustums... + PlaneRange copy = planeRangeStack.last(); + copy.start += copy.count; + planeRangeStack.push_back(copy); + planeStack.increment(copy.count); + for(U32 i = 0; i < copy.count; i++) + planeStack[copy.start + i] = planeStack[copy.start + i - copy.count]; + + zoneStack.push_back(currZone); + scopeZone(otherZone, interiorScopingState, interiorRoot, zoneStack, planeStack, planeRangeStack); + if ( copy.count > 0 ) + planeStack.decrement(copy.count); + planeRangeStack.decrement(); + } + } +} + + +bool Interior::scopeZones(const S32 baseZone, + const Point3F& interiorRoot, + bool* interiorScopingState) +{ + // If we are in solid, scope everything, and return ourselves as continuing out... + if(baseZone == -1) + { + for(U32 i = 0; i < mZones.size(); i++) + interiorScopingState[i] = true; + return true; + } + + Vector zoneStack(64, __FILE__, __LINE__); + Vector planeStack(1024, __FILE__, __LINE__); + Vector planeRangeStack(64, __FILE__, __LINE__); + + PlaneRange initial; + initial.start = 0; + initial.count = 0; + planeRangeStack.push_back(initial); + scopeZone(baseZone, interiorScopingState, interiorRoot, zoneStack, planeStack, planeRangeStack); + + return interiorScopingState[0]; +} + + +// Remove any collision hulls, interval trees, etc... +// +void Interior::purgeLODData() +{ + mConvexHulls.clear(); + mHullIndices.clear(); + mHullEmitStringIndices.clear(); + mHullSurfaceIndices.clear(); + mCoordBinIndices.clear(); + mConvexHullEmitStrings.clear(); + for(U32 i = 0; i < NumCoordBins * NumCoordBins; i++) + { + mCoordBins[i].binStart = 0; + mCoordBins[i].binCount = 0; + } +} + +// Build an OptimizedPolyList that represents this Interior's mesh +void Interior::buildExportPolyList(OptimizedPolyList& polys, MatrixF* mat, Point3F* scale) +{ + MatrixF saveMat; + Point3F saveScale; + polys.getTransform(&saveMat, &saveScale); + + if (mat) + { + if (scale) + polys.setTransform(mat, *scale); + else + polys.setTransform(mat, Point3F(1.0f, 1.0f, 1.0f)); + } + + // Create one TSMesh per zone + for (U32 i = 0; i < mZones.size(); i++) + { + const Interior::Zone& zone = mZones[i]; + + // Gather some data + for (U32 j = 0; j < zone.surfaceCount; j++) + { + U32 sdx = mZoneSurfaces[zone.surfaceStart + j]; + + const Interior::Surface& surface = mSurfaces[sdx]; + + // Snag the MaterialInstance + BaseMatInstance *matInst = mMaterialList->getMaterialInst( surface.textureIndex ); + + // Start a poly + polys.begin(matInst, j, OptimizedPolyList::TriangleStrip); + + // Set its plane + PlaneF plane = getFlippedPlane(surface.planeIndex); + polys.plane(plane); + + // Get its texGen so that we can calculate uvs + Interior::TexGenPlanes texGens = mTexGenEQs[surface.texGenIndex]; + texGens.planeY.invert(); + + // Loop through and add the verts and uvs + for (U32 k = 0; k < surface.windingCount; k++) + { + // Get our point + U32 vdx = mWindings[surface.windingStart + k]; + const Point3F& pt = mPoints[vdx].point; + + // Get our uv + Point2F uv; + uv.x = texGens.planeX.distToPlane(pt); + uv.y = texGens.planeY.distToPlane(pt); + + Point3F normal = getPointNormal(sdx, k); + + polys.vertex(pt, normal, uv); + } + + polys.end(); + } + } + + polys.setTransform(&saveMat, saveScale); +} + + +struct TempProcSurface +{ + U32 numPoints; + U32 pointIndices[32]; + U16 planeIndex; + U8 mask; +}; + +struct PlaneGrouping +{ + U32 numPlanes; + U16 planeIndices[32]; + U8 mask; +}; + + +//-------------------------------------------------------------------------- +void Interior::processHullPolyLists() +{ + Vector planeIndices(256, __FILE__, __LINE__); + Vector pointIndices(256, __FILE__, __LINE__); + Vector pointMasks(256, __FILE__, __LINE__); + Vector planeMasks(256, __FILE__, __LINE__); + Vector tempSurfaces(128, __FILE__, __LINE__); + Vector planeGroups(32, __FILE__, __LINE__); + + // Reserve space in the vectors + { + mPolyListStrings.setSize(0); + mPolyListStrings.reserve(128 << 10); + + mPolyListPoints.setSize(0); + mPolyListPoints.reserve(32 << 10); + + mPolyListPlanes.setSize(0); + mPolyListPlanes.reserve(16 << 10); + } + + for(U32 i = 0; i < mConvexHulls.size(); i++) + { + U32 j, k, l, m; + + ConvexHull& rHull = mConvexHulls[i]; + + planeIndices.setSize(0); + pointIndices.setSize(0); + tempSurfaces.setSize(0); + + // Extract all the surfaces from this hull into our temporary processing format + { + for(j = 0; j < rHull.surfaceCount; j++) + { + tempSurfaces.increment(); + TempProcSurface& temp = tempSurfaces.last(); + + U32 surfaceIndex = mHullSurfaceIndices[j + rHull.surfaceStart]; + if(isNullSurfaceIndex(surfaceIndex)) + { + const NullSurface& rSurface = mNullSurfaces[getNullSurfaceIndex(surfaceIndex)]; + + temp.planeIndex = rSurface.planeIndex; + temp.numPoints = rSurface.windingCount; + for(k = 0; k < rSurface.windingCount; k++) + temp.pointIndices[k] = mWindings[rSurface.windingStart + k]; + } + else + { + const Surface& rSurface = mSurfaces[surfaceIndex]; + + temp.planeIndex = rSurface.planeIndex; + collisionFanFromSurface(rSurface, temp.pointIndices, &temp.numPoints); + } + } + } + + // First order of business: extract all unique planes and points from + // the list of surfaces... + { + for(j = 0; j < tempSurfaces.size(); j++) + { + const TempProcSurface& rSurface = tempSurfaces[j]; + + bool found = false; + for(k = 0; k < planeIndices.size() && !found; k++) + { + if(rSurface.planeIndex == planeIndices[k]) + found = true; + } + if(!found) + planeIndices.push_back(rSurface.planeIndex); + + for(k = 0; k < rSurface.numPoints; k++) + { + found = false; + for(l = 0; l < pointIndices.size(); l++) + { + if(pointIndices[l] == rSurface.pointIndices[k]) + found = true; + } + if(!found) + pointIndices.push_back(rSurface.pointIndices[k]); + } + } + } + + // Now that we have all the unique points and planes, remap the surfaces in + // terms of the offsets into the unique point list... + { + for(j = 0; j < tempSurfaces.size(); j++) + { + TempProcSurface& rSurface = tempSurfaces[j]; + + // Points + for(k = 0; k < rSurface.numPoints; k++) + { + bool found = false; + for(l = 0; l < pointIndices.size(); l++) + { + if(pointIndices[l] == rSurface.pointIndices[k]) + { + rSurface.pointIndices[k] = l; + found = true; + break; + } + } + AssertISV(found, "Error remapping point indices in interior collision processing"); + } + } + } + + // Ok, at this point, we have a list of unique points, unique planes, and the + // surfaces all remapped in those terms. We need to check our error conditions + // that will make sure that we can properly encode this hull: + { + AssertISV(planeIndices.size() < 256, "Error, > 256 planes on an interior hull"); + AssertISV(pointIndices.size() < 63356, "Error, > 65536 points on an interior hull"); + AssertISV(tempSurfaces.size() < 256, "Error, > 256 surfaces on an interior hull"); + } + + // Now we group the planes together, and merge the closest groups until we're left + // with <= 8 groups + { + planeGroups.setSize(planeIndices.size()); + for(j = 0; j < planeIndices.size(); j++) + { + planeGroups[j].numPlanes = 1; + planeGroups[j].planeIndices[0] = planeIndices[j]; + } + + while(planeGroups.size() > 8) + { + // Find the two closest groups. If mdp(i, j) is the value of the + // largest pairwise dot product that can be computed from the vectors + // of group i, and group j, then the closest group pair is the one + // with the smallest value of mdp. + F32 currmin = 2; + S32 firstGroup = -1; + S32 secondGroup = -1; + + for(j = 0; j < planeGroups.size(); j++) + { + PlaneGrouping& first = planeGroups[j]; + for(k = j + 1; k < planeGroups.size(); k++) + { + PlaneGrouping& second = planeGroups[k]; + + F32 max = -2; + for(l = 0; l < first.numPlanes; l++) + { + for(m = 0; m < second.numPlanes; m++) + { + Point3F firstNormal = getPlane(first.planeIndices[l]); + if(planeIsFlipped(first.planeIndices[l])) + firstNormal.neg(); + Point3F secondNormal = getPlane(second.planeIndices[m]); + if(planeIsFlipped(second.planeIndices[m])) + secondNormal.neg(); + + F32 dot = mDot(firstNormal, secondNormal); + if(dot > max) + max = dot; + } + } + + if(max < currmin) + { + currmin = max; + firstGroup = j; + secondGroup = k; + } + } + } + AssertFatal(firstGroup != -1 && secondGroup != -1, "Error, unable to find a suitable pairing?"); + + // Merge first and second + PlaneGrouping& to = planeGroups[firstGroup]; + PlaneGrouping& from = planeGroups[secondGroup]; + while(from.numPlanes != 0) + { + to.planeIndices[to.numPlanes++] = from.planeIndices[from.numPlanes - 1]; + from.numPlanes--; + } + + // And remove the merged group + planeGroups.erase(secondGroup); + } + AssertFatal(planeGroups.size() <= 8, "Error, too many plane groupings!"); + + + // Assign a mask to each of the plane groupings + for(j = 0; j < planeGroups.size(); j++) + planeGroups[j].mask = (1 << j); + } + + // Now, assign the mask to each of the temp polys + { + for(j = 0; j < tempSurfaces.size(); j++) + { + bool assigned = false; + for(k = 0; k < planeGroups.size() && !assigned; k++) + { + for(l = 0; l < planeGroups[k].numPlanes; l++) + { + if(planeGroups[k].planeIndices[l] == tempSurfaces[j].planeIndex) + { + tempSurfaces[j].mask = planeGroups[k].mask; + assigned = true; + break; + } + } + } + AssertFatal(assigned, "Error, missed a plane somewhere in the hull poly list!"); + } + } + + // Copy the appropriate group mask to the plane masks + { + planeMasks.setSize(planeIndices.size()); + dMemset(planeMasks.address(), 0, planeMasks.size() * sizeof(U8)); + + for(j = 0; j < planeIndices.size(); j++) + { + bool found = false; + for(k = 0; k < planeGroups.size() && !found; k++) + { + for(l = 0; l < planeGroups[k].numPlanes; l++) + { + if(planeGroups[k].planeIndices[l] == planeIndices[j]) + { + planeMasks[j] = planeGroups[k].mask; + found = true; + break; + } + } + } + AssertFatal(planeMasks[j] != 0, "Error, missing mask for plane!"); + } + } + + // And whip through the points, constructing the total mask for that point + { + pointMasks.setSize(pointIndices.size()); + dMemset(pointMasks.address(), 0, pointMasks.size() * sizeof(U8)); + + for(j = 0; j < pointIndices.size(); j++) + { + for(k = 0; k < tempSurfaces.size(); k++) + { + for(l = 0; l < tempSurfaces[k].numPoints; l++) + { + if(tempSurfaces[k].pointIndices[l] == j) + { + pointMasks[j] |= tempSurfaces[k].mask; + break; + } + } + } + AssertFatal(pointMasks[j] != 0, "Error, point must exist in at least one surface!"); + } + } + + // Create the emit strings, and we're done! + { + // Set the range of planes + rHull.polyListPlaneStart = mPolyListPlanes.size(); + mPolyListPlanes.setSize(rHull.polyListPlaneStart + planeIndices.size()); + for(j = 0; j < planeIndices.size(); j++) + mPolyListPlanes[j + rHull.polyListPlaneStart] = planeIndices[j]; + + // Set the range of points + rHull.polyListPointStart = mPolyListPoints.size(); + mPolyListPoints.setSize(rHull.polyListPointStart + pointIndices.size()); + for(j = 0; j < pointIndices.size(); j++) + mPolyListPoints[j + rHull.polyListPointStart] = pointIndices[j]; + + // Now the emit string. The emit string goes like: (all fields are bytes) + // NumPlanes (PLMask) * NumPlanes + // NumPointsHi NumPointsLo (PtMask) * NumPoints + // NumSurfaces + // (NumPoints SurfaceMask PlOffset (PtOffsetHi PtOffsetLo) * NumPoints) * NumSurfaces + // + U32 stringLen = 1 + planeIndices.size(); + stringLen += 2 + pointIndices.size(); + stringLen += 1; + for(j = 0; j < tempSurfaces.size(); j++) + stringLen += 1 + 1 + 1 + (tempSurfaces[j].numPoints * 2); + + rHull.polyListStringStart = mPolyListStrings.size(); + mPolyListStrings.setSize(rHull.polyListStringStart + stringLen); + + U8* pString = &mPolyListStrings[rHull.polyListStringStart]; + U32 currPos = 0; + + // Planes + pString[currPos++] = planeIndices.size(); + for(j = 0; j < planeIndices.size(); j++) + pString[currPos++] = planeMasks[j]; + + // Points + pString[currPos++] = (pointIndices.size() >> 8) & 0xFF; + pString[currPos++] = (pointIndices.size() >> 0) & 0xFF; + for(j = 0; j < pointIndices.size(); j++) + pString[currPos++] = pointMasks[j]; + + // Surfaces + pString[currPos++] = tempSurfaces.size(); + for(j = 0; j < tempSurfaces.size(); j++) + { + pString[currPos++] = tempSurfaces[j].numPoints; + pString[currPos++] = tempSurfaces[j].mask; + + bool found = false; + for(k = 0; k < planeIndices.size(); k++) + { + if(planeIndices[k] == tempSurfaces[j].planeIndex) + { + pString[currPos++] = k; + found = true; + break; + } + } + AssertFatal(found, "Error, missing planeindex!"); + + for(k = 0; k < tempSurfaces[j].numPoints; k++) + { + pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 8) & 0xFF; + pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 0) & 0xFF; + } + } + AssertFatal(currPos == stringLen, "Error, mismatched string length!"); + } + } // for (i = 0; i < mConvexHulls.size(); i++) + + // Compact the used vectors + { + mPolyListStrings.compact(); + mPolyListPoints.compact(); + mPolyListPlanes.compact(); + } +} + +//-------------------------------------------------------------------------- +void Interior::processVehicleHullPolyLists() +{ + Vector planeIndices(256, __FILE__, __LINE__); + Vector pointIndices(256, __FILE__, __LINE__); + Vector pointMasks(256, __FILE__, __LINE__); + Vector planeMasks(256, __FILE__, __LINE__); + Vector tempSurfaces(128, __FILE__, __LINE__); + Vector planeGroups(32, __FILE__, __LINE__); + + // Reserve space in the vectors + { + mVehiclePolyListStrings.setSize(0); + mVehiclePolyListStrings.reserve(128 << 10); + + mVehiclePolyListPoints.setSize(0); + mVehiclePolyListPoints.reserve(32 << 10); + + mVehiclePolyListPlanes.setSize(0); + mVehiclePolyListPlanes.reserve(16 << 10); + } + + for(U32 i = 0; i < mVehicleConvexHulls.size(); i++) + { + U32 j, k, l, m; + + ConvexHull& rHull = mVehicleConvexHulls[i]; + + planeIndices.setSize(0); + pointIndices.setSize(0); + tempSurfaces.setSize(0); + + // Extract all the surfaces from this hull into our temporary processing format + { + for(j = 0; j < rHull.surfaceCount; j++) + { + tempSurfaces.increment(); + TempProcSurface& temp = tempSurfaces.last(); + + U32 surfaceIndex = mVehicleHullSurfaceIndices[j + rHull.surfaceStart]; + const NullSurface& rSurface = mVehicleNullSurfaces[getVehicleNullSurfaceIndex(surfaceIndex)]; + + temp.planeIndex = rSurface.planeIndex; + temp.numPoints = rSurface.windingCount; + for(k = 0; k < rSurface.windingCount; k++) + temp.pointIndices[k] = mVehicleWindings[rSurface.windingStart + k]; + } + } + + // First order of business: extract all unique planes and points from + // the list of surfaces... + { + for(j = 0; j < tempSurfaces.size(); j++) + { + const TempProcSurface& rSurface = tempSurfaces[j]; + + bool found = false; + for(k = 0; k < planeIndices.size() && !found; k++) + { + if(rSurface.planeIndex == planeIndices[k]) + found = true; + } + if(!found) + planeIndices.push_back(rSurface.planeIndex); + + for(k = 0; k < rSurface.numPoints; k++) + { + found = false; + for(l = 0; l < pointIndices.size(); l++) + { + if(pointIndices[l] == rSurface.pointIndices[k]) + found = true; + } + if(!found) + pointIndices.push_back(rSurface.pointIndices[k]); + } + } + } + + // Now that we have all the unique points and planes, remap the surfaces in + // terms of the offsets into the unique point list... + { + for(j = 0; j < tempSurfaces.size(); j++) + { + TempProcSurface& rSurface = tempSurfaces[j]; + + // Points + for(k = 0; k < rSurface.numPoints; k++) + { + bool found = false; + for(l = 0; l < pointIndices.size(); l++) + { + if(pointIndices[l] == rSurface.pointIndices[k]) + { + rSurface.pointIndices[k] = l; + found = true; + break; + } + } + AssertISV(found, "Error remapping point indices in interior collision processing"); + } + } + } + + // Ok, at this point, we have a list of unique points, unique planes, and the + // surfaces all remapped in those terms. We need to check our error conditions + // that will make sure that we can properly encode this hull: + { + AssertISV(planeIndices.size() < 256, "Error, > 256 planes on an interior hull"); + AssertISV(pointIndices.size() < 63356, "Error, > 65536 points on an interior hull"); + AssertISV(tempSurfaces.size() < 256, "Error, > 256 surfaces on an interior hull"); + } + + // Now we group the planes together, and merge the closest groups until we're left + // with <= 8 groups + { + planeGroups.setSize(planeIndices.size()); + for(j = 0; j < planeIndices.size(); j++) + { + planeGroups[j].numPlanes = 1; + planeGroups[j].planeIndices[0] = planeIndices[j]; + } + + while(planeGroups.size() > 8) + { + // Find the two closest groups. If mdp(i, j) is the value of the + // largest pairwise dot product that can be computed from the vectors + // of group i, and group j, then the closest group pair is the one + // with the smallest value of mdp. + F32 currmin = 2; + S32 firstGroup = -1; + S32 secondGroup = -1; + + for(j = 0; j < planeGroups.size(); j++) + { + PlaneGrouping& first = planeGroups[j]; + for(k = j + 1; k < planeGroups.size(); k++) + { + PlaneGrouping& second = planeGroups[k]; + + F32 max = -2; + for(l = 0; l < first.numPlanes; l++) + { + for(m = 0; m < second.numPlanes; m++) + { + Point3F firstNormal = mVehiclePlanes[first.planeIndices[l]]; + Point3F secondNormal = mVehiclePlanes[second.planeIndices[m]]; + + F32 dot = mDot(firstNormal, secondNormal); + if(dot > max) + max = dot; + } + } + + if(max < currmin) + { + currmin = max; + firstGroup = j; + secondGroup = k; + } + } + } + AssertFatal(firstGroup != -1 && secondGroup != -1, "Error, unable to find a suitable pairing?"); + + // Merge first and second + PlaneGrouping& to = planeGroups[firstGroup]; + PlaneGrouping& from = planeGroups[secondGroup]; + while(from.numPlanes != 0) + { + to.planeIndices[to.numPlanes++] = from.planeIndices[from.numPlanes - 1]; + from.numPlanes--; + } + + // And remove the merged group + planeGroups.erase(secondGroup); + } + AssertFatal(planeGroups.size() <= 8, "Error, too many plane groupings!"); + + + // Assign a mask to each of the plane groupings + for(j = 0; j < planeGroups.size(); j++) + planeGroups[j].mask = (1 << j); + } + + // Now, assign the mask to each of the temp polys + { + for(j = 0; j < tempSurfaces.size(); j++) + { + bool assigned = false; + for(k = 0; k < planeGroups.size() && !assigned; k++) + { + for(l = 0; l < planeGroups[k].numPlanes; l++) + { + if(planeGroups[k].planeIndices[l] == tempSurfaces[j].planeIndex) + { + tempSurfaces[j].mask = planeGroups[k].mask; + assigned = true; + break; + } + } + } + AssertFatal(assigned, "Error, missed a plane somewhere in the hull poly list!"); + } + } + + // Copy the appropriate group mask to the plane masks + { + planeMasks.setSize(planeIndices.size()); + dMemset(planeMasks.address(), 0, planeMasks.size() * sizeof(U8)); + + for(j = 0; j < planeIndices.size(); j++) + { + bool found = false; + for(k = 0; k < planeGroups.size() && !found; k++) + { + for(l = 0; l < planeGroups[k].numPlanes; l++) + { + if(planeGroups[k].planeIndices[l] == planeIndices[j]) + { + planeMasks[j] = planeGroups[k].mask; + found = true; + break; + } + } + } + AssertFatal(planeMasks[j] != 0, "Error, missing mask for plane!"); + } + } + + // And whip through the points, constructing the total mask for that point + { + pointMasks.setSize(pointIndices.size()); + dMemset(pointMasks.address(), 0, pointMasks.size() * sizeof(U8)); + + for(j = 0; j < pointIndices.size(); j++) + { + for(k = 0; k < tempSurfaces.size(); k++) + { + for(l = 0; l < tempSurfaces[k].numPoints; l++) + { + if(tempSurfaces[k].pointIndices[l] == j) + { + pointMasks[j] |= tempSurfaces[k].mask; + break; + } + } + } + AssertFatal(pointMasks[j] != 0, "Error, point must exist in at least one surface!"); + } + } + + // Create the emit strings, and we're done! + { + // Set the range of planes + rHull.polyListPlaneStart = mVehiclePolyListPlanes.size(); + mVehiclePolyListPlanes.setSize(rHull.polyListPlaneStart + planeIndices.size()); + for(j = 0; j < planeIndices.size(); j++) + mVehiclePolyListPlanes[j + rHull.polyListPlaneStart] = planeIndices[j]; + + // Set the range of points + rHull.polyListPointStart = mVehiclePolyListPoints.size(); + mVehiclePolyListPoints.setSize(rHull.polyListPointStart + pointIndices.size()); + for(j = 0; j < pointIndices.size(); j++) + mVehiclePolyListPoints[j + rHull.polyListPointStart] = pointIndices[j]; + + // Now the emit string. The emit string goes like: (all fields are bytes) + // NumPlanes (PLMask) * NumPlanes + // NumPointsHi NumPointsLo (PtMask) * NumPoints + // NumSurfaces + // (NumPoints SurfaceMask PlOffset (PtOffsetHi PtOffsetLo) * NumPoints) * NumSurfaces + // + U32 stringLen = 1 + planeIndices.size(); + stringLen += 2 + pointIndices.size(); + stringLen += 1; + for(j = 0; j < tempSurfaces.size(); j++) + stringLen += 1 + 1 + 1 + (tempSurfaces[j].numPoints * 2); + + rHull.polyListStringStart = mVehiclePolyListStrings.size(); + mVehiclePolyListStrings.setSize(rHull.polyListStringStart + stringLen); + + U8* pString = &mVehiclePolyListStrings[rHull.polyListStringStart]; + U32 currPos = 0; + + // Planes + pString[currPos++] = planeIndices.size(); + for(j = 0; j < planeIndices.size(); j++) + pString[currPos++] = planeMasks[j]; + + // Points + pString[currPos++] = (pointIndices.size() >> 8) & 0xFF; + pString[currPos++] = (pointIndices.size() >> 0) & 0xFF; + for(j = 0; j < pointIndices.size(); j++) + pString[currPos++] = pointMasks[j]; + + // Surfaces + pString[currPos++] = tempSurfaces.size(); + for(j = 0; j < tempSurfaces.size(); j++) + { + pString[currPos++] = tempSurfaces[j].numPoints; + pString[currPos++] = tempSurfaces[j].mask; + + bool found = false; + for(k = 0; k < planeIndices.size(); k++) + { + if(planeIndices[k] == tempSurfaces[j].planeIndex) + { + pString[currPos++] = k; + found = true; + break; + } + } + AssertFatal(found, "Error, missing planeindex!"); + + for(k = 0; k < tempSurfaces[j].numPoints; k++) + { + pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 8) & 0xFF; + pString[currPos++] = (tempSurfaces[j].pointIndices[k] >> 0) & 0xFF; + } + } + AssertFatal(currPos == stringLen, "Error, mismatched string length!"); + } + } // for (i = 0; i < mConvexHulls.size(); i++) + + // Compact the used vectors + { + mVehiclePolyListStrings.compact(); + mVehiclePolyListPoints.compact(); + mVehiclePolyListPlanes.compact(); + } +} + + + + +//-------------------------------------------------------------------------- +void ZoneVisDeterminer::runFromState(SceneState* state, U32 offset, U32 parentZone) +{ + mMode = FromState; + mState = state; + mZoneRangeOffset = offset; + mParentZone = parentZone; +} + +void ZoneVisDeterminer::runFromRects(SceneState* state, U32 offset, U32 parentZone) +{ + mMode = FromRects; + mState = state; + mZoneRangeOffset = offset; + mParentZone = parentZone; +} + +bool ZoneVisDeterminer::isZoneVisible(const U32 zone) const +{ + if(zone == 0) + return mState->getZoneState(mParentZone).render; + + if(mMode == FromState) + { + return mState->getZoneState(zone + mZoneRangeOffset - 1).render; + } + else + { + return sgZoneRenderInfo[zone].render; + } +} + + + +//-------------------------------------------------------------------------- +// storeSurfaceVerts - +// Need to store the verts for every surface because the uv mapping changes +// per vertex per surface. +//-------------------------------------------------------------------------- +void Interior::storeSurfVerts( Vector &masterIndexList, + Vector &tempIndexList, + Vector &verts, + U32 numIndices, + Surface &surface, + U32 surfaceIndex ) +{ + U32 startIndex = tempIndexList.size() - numIndices; + U32 startVert = verts.size(); + + Vector vertMap; + + for( U32 i=0; i &primInfoList, + Vector &indexList, + Vector &verts, + U32 &startIndex, + U32 &startVert ) +{ + + GFXPrimitive pnfo; + + if( !node.matInst ) + { + String name = mMaterialList->getMaterialName( node.baseTexIndex ); + + if (!name.equal("NULL", String::NoCase) && + !name.equal("ORIGIN", String::NoCase) && + !name.equal("TRIGGER", String::NoCase) && + !name.equal("FORCEFIELD", String::NoCase) && + !name.equal("EMITTER", String::NoCase) ) + { + Con::errorf( "material unmapped: %s", name.c_str() ); + } + + node.matInst = MATMGR->getWarningMatInstance(); + } + + + // find min index + pnfo.minIndex = U32(-1); + + for( U32 i=startIndex; i 0 ) + { + primInfoList.push_back( pnfo ); + + node.primInfoIndex = primInfoList.size() - 1; + RNList.renderNodeList.push_back( node ); + } + +} + +//-------------------------------------------------------------------------- +// fill vertex +//-------------------------------------------------------------------------- +void Interior::fillVertex( GFXVertexPNTTB &vert, Surface &surface, U32 surfaceIndex ) +{ + TexGenPlanes texPlanes = mTexGenEQs[surface.texGenIndex]; + + vert.texCoord.x = texPlanes.planeX.x * vert.point.x + + texPlanes.planeX.y * vert.point.y + + texPlanes.planeX.z * vert.point.z + + texPlanes.planeX.d; + + + vert.texCoord.y = texPlanes.planeY.x * vert.point.x + + texPlanes.planeY.y * vert.point.y + + texPlanes.planeY.z * vert.point.z + + texPlanes.planeY.d; + + texPlanes = mLMTexGenEQs[surfaceIndex]; + + vert.texCoord2.x = texPlanes.planeX.x * vert.point.x + + texPlanes.planeX.y * vert.point.y + + texPlanes.planeX.z * vert.point.z + + texPlanes.planeX.d; + + + vert.texCoord2.y = texPlanes.planeY.x * vert.point.x + + texPlanes.planeY.y * vert.point.y + + texPlanes.planeY.z * vert.point.z + + texPlanes.planeY.d; + + // vert normal and N already set + vert.T = surface.T - vert.normal * mDot(vert.normal, surface.T); + vert.T.normalize(); + + mCross(vert.normal, vert.T, &vert.B); + vert.B *= (mDot(vert.B, surface.B) < 0.0F) ? -1.0F : 1.0F; +} + +//-------------------------------------------------------------------------- +// Create vertex (and index) buffers for each zone +//-------------------------------------------------------------------------- +void Interior::createZoneVBs() +{ + if( mVertBuff ) + { + return; + } + + // create one big-ass vertex buffer to contain all verts + // drawIndexedPrimitive() calls can render subsets of the big-ass buffer + + Vector verts; + Vector indices; + + U32 startIndex = 0; + U32 startVert = 0; + + + Vector primInfoList; + + + // fill index list first, then fill verts + for( U32 i=0; i tempIndices; + tempIndices.setSize(0); + + + + for( U32 j=0; jgetMaterialInst( surface.textureIndex ); + Material* pMat = dynamic_cast(matInst->getMaterial()); + if( pMat && pMat->mPlanarReflection ) continue; + + node.exterior = surface.surfaceFlags & SurfaceOutsideVisible; + + // fill in node info on first time through + if( j==0 ) + { + node.baseTexIndex = surface.textureIndex; + node.matInst = matInst; + curTexIndex = node.baseTexIndex; + node.lightMapIndex = mNormalLMapIndices[surfaceIndex]; + curLightMapIndex = node.lightMapIndex; + } + + // check for material change + if( surface.textureIndex != curTexIndex || + mNormalLMapIndices[surfaceIndex] != curLightMapIndex ) + { + storeRenderNode( node, RNList, primInfoList, indices, verts, startIndex, startVert ); + + tempIndices.setSize( 0 ); + + // set new material info + U16 baseTex = surface.textureIndex; + U8 lmIndex = mNormalLMapIndices[surfaceIndex]; + + if( baseTex != curTexIndex ) + { + node.baseTexIndex = baseTex; + node.matInst = mMaterialList->getMaterialInst( baseTex ); + } + else + { + node.baseTexIndex = NULL; + } + + + node.lightMapIndex = lmIndex; + + curTexIndex = baseTex; + curLightMapIndex = lmIndex; + + } + + + // NOTE, can put this in storeSurfVerts() + U32 tempStartIndex = tempIndices.size(); + + U32 nPrim = 0; + U32 last = 2; + while(last < surface.windingCount) + { + // First + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-2], getPointNormal(surfaceIndex, last-2)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-1], getPointNormal(surfaceIndex, last-1)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-0], getPointNormal(surfaceIndex, last)) ); + last++; + nPrim++; + + if(last == surface.windingCount) + break; + + // Second + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-1], getPointNormal(surfaceIndex, last-1)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-2], getPointNormal(surfaceIndex, last-2)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-0], getPointNormal(surfaceIndex, last)) ); + last++; + nPrim++; + } + + U32 dStartVert = verts.size(); + GFXPrimitive* p = &surface.surfaceInfo; + p->startIndex = indices.size(); //tempStartIndex; + + // Normal render info + storeSurfVerts( indices, tempIndices, verts, tempIndices.size() - tempStartIndex, + surface, surfaceIndex ); + + // Debug render info + p->type = GFXTriangleList; + p->numVertices = verts.size() - dStartVert; + p->numPrimitives = nPrim; + p->minIndex = indices[p->startIndex]; + for (U32 i = p->startIndex; i < p->startIndex + nPrim * 3; i++) { + if (indices[i] < p->minIndex) { + p->minIndex = indices[i]; + } + } + } + + // store remaining index list + storeRenderNode( node, RNList, primInfoList, indices, verts, startIndex, startVert ); + + mZoneRNList.push_back( RNList ); + } + + // It is possible that we have no zones or have no surfaces in our zones (static meshes only) + if (verts.size() == 0) + return; + + // create vertex buffer + mVertBuff.set(GFX, verts.size(), GFXBufferTypeStatic); + GFXVertexPNTTB *vbVerts = mVertBuff.lock(); + + dMemcpy( vbVerts, verts.address(), verts.size() * sizeof( GFXVertexPNTTB ) ); + + mVertBuff.unlock(); + + // create primitive buffer + U16 *ibIndices; + GFXPrimitive *piInput; + mPrimBuff.set(GFX, indices.size(), primInfoList.size(), GFXBufferTypeStatic); + mPrimBuff.lock(&ibIndices, &piInput); + + dMemcpy( ibIndices, indices.address(), indices.size() * sizeof(U16) ); + dMemcpy( piInput, primInfoList.address(), primInfoList.size() * sizeof(GFXPrimitive) ); + + mPrimBuff.unlock(); + +} + + +#define SMALL_FLOAT (1e-12) + +//-------------------------------------------------------------------------- +// Get the texture space matrice for a point on a surface +//-------------------------------------------------------------------------- +void Interior::getTexMat(U32 surfaceIndex, U32 pointOffset, Point3F& T, Point3F& N, Point3F& B) +{ + Surface& surface = mSurfaces[surfaceIndex]; + + if (mFileVersion >= 11) + { + // There is a one-to-one mapping of mWindings and mTexMatIndices + U32 texMatIndex = mTexMatIndices[surface.windingStart + pointOffset]; + TexMatrix& texMat = mTexMatrices[texMatIndex]; + + T = mNormals[texMat.T]; + N = mNormals[texMat.N]; + B = mNormals[texMat.B]; + } + else + { + T = surface.T - surface.N * mDot(surface.N, surface.T); + N = surface.N; + + mCross(surface.N, T, &B); + B *= (mDot(B, surface.B) < 0.0F) ? -1.0f : 1.0f; + } + + return; +} + +//-------------------------------------------------------------------------- +// Fill in texture space matrices for each surface +//-------------------------------------------------------------------------- +void Interior::fillSurfaceTexMats() +{ + for( U32 i=0; i SMALL_FLOAT ) + { + S.x = -cp.y / cp.x; + T.x = -cp.z / cp.x; + } + + edge1.set( pts[1].point.y - pts[0].point.y, pts[1].texCoord.x - pts[0].texCoord.x, pts[1].texCoord.y - pts[0].texCoord.y ); + edge2.set( pts[2].point.y - pts[0].point.y, pts[2].texCoord.x - pts[0].texCoord.x, pts[2].texCoord.y - pts[0].texCoord.y ); + + mCross( edge1, edge2, &cp ); + if( fabs(cp.x) > SMALL_FLOAT ) + { + S.y = -cp.y / cp.x; + T.y = -cp.z / cp.x; + } + + edge1.set( pts[1].point.z - pts[0].point.z, pts[1].texCoord.x - pts[0].texCoord.x, pts[1].texCoord.y - pts[0].texCoord.y ); + edge2.set( pts[2].point.z - pts[0].point.z, pts[2].texCoord.x - pts[0].texCoord.x, pts[2].texCoord.y - pts[0].texCoord.y ); + + mCross( edge1, edge2, &cp ); + if( fabs(cp.x) > SMALL_FLOAT ) + { + S.z = -cp.y / cp.x; + T.z = -cp.z / cp.x; + } + + S.normalizeSafe(); + T.normalizeSafe(); + mCross( S, T, &SxT ); + + + if( mDot( SxT, planeNorm ) < 0.0 ) + { + SxT = -SxT; + } + + surface.T = S; + surface.B = T; + surface.N = SxT; + surface.normal = planeNorm; + } + +} + +//-------------------------------------------------------------------------- +// Clone material instances - if a texture (material) exists on both the +// inside and outside of an interior, it needs to create two material +// instances - one for the inside, and one for the outside. The reason is +// that the light direction maps only exist on the inside of the interior. +//-------------------------------------------------------------------------- +void Interior::cloneMatInstances() +{ + Vector< BaseMatInstance *> outsideMats; + Vector< BaseMatInstance *> insideMats; + + // store pointers to mat lists + for( U32 i=0; i(outsideMats[i]->getMaterial()); + + if (mat) + { + BaseMatInstance *newMat = mat->createMatInstance(); + mMatInstCleanupList.push_back( newMat ); + + // go through and find the inside version and replace it + // with the new one. + for( U32 k=0; kgetFeaturesDelegate().bind( &Interior::_enableLightMapFeature ); + mat->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat()); + + // We need to know if we have non-zwrite translucent materials + // so that we can make the extra renderimage pass for them. + Material* pMat = dynamic_cast(mat->getMaterial()); + if ( pMat ) + mHasTranslucentMaterials |= pMat->mTranslucent && !pMat->mTranslucentZWrite; + } + + } + + for( U32 j=0; jinit( MATMGR->getDefaultFeatures(), getGFXVertexFormat()); + + // We need to know if we have non-zwrite translucent materials + // so that we can make the extra renderimage pass for them. + Material* pMat = dynamic_cast(mat->getMaterial()); + if ( pMat ) + mHasTranslucentMaterials |= pMat->mTranslucent && !pMat->mTranslucentZWrite; + } + } + } +} + +void Interior::_enableLightMapFeature( ProcessedMaterial *mat, + U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ) +{ + if ( mat->getMaterial() ) + { + fd.features.addFeature( MFT_LightMap ); + fd.features.removeFeature( MFT_ToneMap ); + } +} + +//-------------------------------------------------------------------------- +// Create the reflect plane list and the nodes of geometry necessary to +// render the reflective surfaces. +//-------------------------------------------------------------------------- +void Interior::createReflectPlanes() +{ + Vector verts; + Vector primInfoList; + Vector indices; + + U32 startIndex = 0; + U32 startVert = 0; + + + + for( U32 i=0; igetMaterialInst( surface.textureIndex ); + Material* pMat = dynamic_cast(matInst->getMaterial()); + if( !pMat || !pMat->mPlanarReflection ) continue; + + U32 *surfIndices = &mWindings[surface.windingStart]; + + // create / fill in GFXPrimitve, verts, indices + // going to need a new render node + ReflectRenderNode node; + node.exterior = surface.surfaceFlags & SurfaceOutsideVisible; + node.matInst = mMaterialList->getMaterialInst( surface.textureIndex ); + node.lightMapIndex = mNormalLMapIndices[surfaceIndex]; + + + PlaneF plane; + plane = getPlane( surface.planeIndex ); + if( planeIsFlipped( surface.planeIndex ) ) + { + plane.x = -plane.x; + plane.y = -plane.y; + plane.z = -plane.z; + plane.d = -plane.d; + } + + // check if coplanar with existing reflect plane + //-------------------------------------------------- + S32 rPlaneIdx = -1; + for( U32 a=0; a 0.999 ) + { + if( fabs( plane.d - mReflectPlanes[a].d ) < 0.001 ) + { + rPlaneIdx = a; + break; + } + } + } + + PlaneF refPlane; + refPlane = plane; + + if( rPlaneIdx < 0 ) + { + mReflectPlanes.push_back( refPlane ); + node.reflectPlaneIndex = mReflectPlanes.size() - 1; + } + else + { + node.reflectPlaneIndex = rPlaneIdx; + } + + // store the indices for the surface + //-------------------------------------------------- + Vector tempIndices; + tempIndices.setSize( 0 ); + + U32 tempStartIndex = tempIndices.size(); + + U32 last = 2; + while(last < surface.windingCount) + { + // First + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-2], getPointNormal(surfaceIndex, last-2)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-1], getPointNormal(surfaceIndex, last-1)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-0], getPointNormal(surfaceIndex, last)) ); + last++; + + if(last == surface.windingCount) + break; + + // Second + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-1], getPointNormal(surfaceIndex, last-1)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-2], getPointNormal(surfaceIndex, last-2)) ); + tempIndices.push_back( VertexBufferTempIndex(surfIndices[last-0], getPointNormal(surfaceIndex, last)) ); + last++; + } + + storeSurfVerts( indices, tempIndices, verts, tempIndices.size() - tempStartIndex, + surface, surfaceIndex ); + + + // store render node and GFXPrimitive + // each node is a different reflective surface + // --------------------------------------------------- + + // find min index + GFXPrimitive pnfo; + pnfo.minIndex = U32(-1); + for( U32 k=startIndex; kgetMaterialNameList().size(); + + if(mapToNameIndex < 0 || mapToNameIndex >= targetCount) + return String::EmptyString; + + return mMaterialList->getMaterialNameList()[mapToNameIndex]; +} + +S32 Interior::getTargetCount() const +{ + if(!this) + return -1; + + return mMaterialList->getMaterialNameList().size(); + +} diff --git a/interior/interior.h b/interior/interior.h new file mode 100644 index 0000000..6be06f5 --- /dev/null +++ b/interior/interior.h @@ -0,0 +1,1174 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIOR_H_ +#define _INTERIOR_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _COLLISION_H_ +#include "collision/collision.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MPLANE_H_ +#include "math/mPlane.h" +#endif +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif +#ifndef _MSPHERE_H_ +#include "math/mSphere.h" +#endif +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif +#ifndef _INTERIORLMMANAGER_H_ +#include "interior/interiorLMManager.h" +#endif +#ifndef _INTERIORSIMPLEMESH_H_ +#include "interior/interiorSimpleMesh.h" +#endif +#ifndef _OPTIMIZEDPOLYLIST_H_ +#include "collision/optimizedPolyList.h" +#endif + +#include "gfx/gfxDevice.h" +#include "materials/sceneData.h" + +//-------------------------------------- Forward declarations +class MatInstance; +class Stream; +class EditGeometry; +class InteriorInstance; +class GBitmap; +class RectD; +class SphereF; +class MatrixF; +class SceneState; +class MaterialList; +class AbstractPolyList; +class InteriorSubObject; +class TranslucentSubObject; +class BitVector; +struct RayInfo; +struct EdgeList; +class SurfaceHash; +class InteriorPolytope; +class LightInfo; +class PlaneRange; +class EditInteriorResource; +class GFXVertexBuffer; +class GFXPrimitiveBuffer; +struct RenderInst; +struct GFXVertexPNTTB; +struct GFXPrimitiveInfo; + +//-------------------------------------------------------------------------- +class InteriorConvex : public Convex +{ + typedef Convex Parent; + friend class Interior; + friend class InteriorInstance; + + protected: + Interior* pInterior; + public: + S32 hullId; + Box3F box; + + public: + InteriorConvex() { mType = InteriorConvexType; } + InteriorConvex(const InteriorConvex& cv) + { + mObject = cv.mObject; + pInterior = cv.pInterior; + hullId = cv.hullId; + box = box; + } + + Box3F getBoundingBox() const; + Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const; + Point3F support(const VectorF& v) const; + void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + void getPolyList(AbstractPolyList* list); +}; + +class ZoneVisDeterminer +{ + enum Mode + { + FromState, + FromRects + }; + + Mode mMode; + + SceneState* mState; + U32 mZoneRangeOffset; + U32 mParentZone; + + public: + ZoneVisDeterminer() : mMode(FromRects), mState(NULL) { } + + void runFromState(SceneState*, U32, U32); + void runFromRects(SceneState*, U32, U32); + + bool isZoneVisible(const U32) const; +}; + + +struct ItrPaddedPoint +{ + Point3F point; + union + { + F32 fogCoord; + U8 fogColor[4]; + }; +}; + + +//------------------------------------------------------------------------------ +//-------------------------------------- CLASS NOTES +// Interior: Base for all interior geometries. Contains all lighting, poly, +// portal zone, bsp info, etc. to render an interior. +// +// Internal Structure Notes: +// IBSPNode: +// planeIndex: Obv. +// frontIndex/backIndex: Top bit indicates if children are leaves. +// Next bit indicates if leaf children are solid. +// +// IBSPLeafSolid: +// planeIndex: obv. +// surfaceIndex/surfaceCount: Polys that are on the faces of this leaf. Only +// used for collision/surface info detection. +// +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +class Interior +{ + friend class EditGeometry; + friend class InteriorInstance; + friend class InteriorProxy; + friend class blInteriorProxy; + friend class TranslucentSubObject; + friend class MirrorSubObject; + friend class InteriorConvex; + friend class EditInteriorResource; + friend class InteriorLMManager; + friend class TSShape; + friend class SceneLighting; + + //-------------------------------------- Public interfaces + public: + Interior(); + ~Interior(); + + +private: + U32 mLightMapBorderSize; + +public: + Vector surfaceZones; + U32 getLightMapBorderSize() const {return mLightMapBorderSize;} + void setLightMapBorderSize(U32 value) {mLightMapBorderSize = value;} + void buildSurfaceZones(); + //void sgSetupLighting(InteriorInstance *intInst, SceneGraphData &sgData); + //bool sgRenderLights(InteriorInstance *intInst, SceneGraphData &sgData); + + // Interior Render StateBlock + GFXStateBlockRef mInteriorSB; + + MaterialList* mMaterialList; + + // Misc + U32 getDetailLevel() const; + U32 getMinPixels() const; + const Box3F& getBoundingBox() const; + S32 getNumZones() const; + + // Rendering + bool prepForRendering(const char* path); + + bool prepRender(SceneState* state, + S32 containingZone, + S32 baseZone, + U32 zoneOffset, + const MatrixF& OSToWS, + const Point3F& objScale, + const bool modifyBaseState, + const bool dontRestrictOutside, + const bool flipClipPlanes); + void prepTempRender(SceneState* state, + S32 containingZone, + S32 baseZone, + const MatrixF& OSToWS, + const Point3F& objScale, + const bool flipClipPlanes); + + void prepBatchRender( InteriorInstance *intInst, SceneState *state, const MatrixF* worldToCamera ); + + void setupRenderStates(); + + + bool scopeZones(const S32 baseZone, + const Point3F& interiorRoot, + bool* interiorScopingState); + + ZoneVisDeterminer setupZoneVis( InteriorInstance *intInst, SceneState *state ); + + SceneGraphData setupSceneGraphInfo( InteriorInstance *intInst, + SceneState *state ); + + void setupRender( InteriorInstance *intInst, + SceneState *state, + MeshRenderInst *coreRi, + const MatrixF* worldToCamera); + + + //-------------------------------------- Collision Interface and zone scans + public: + bool scanZones(const Box3F&, const MatrixF&, U16* zones, U32* numZones); + bool castRay(const Point3F&, const Point3F&, RayInfo*); + bool buildPolyList(AbstractPolyList*, const Box3F&, const MatrixF&, const Point3F&); + bool buildLightPolyList(U32* lightSurfaces, U32* numLightSurfaces, + const Box3F&, const MatrixF&, const Point3F&); + + bool getIntersectingHulls(const Box3F&, U16* hulls, U32* numHulls); + bool getIntersectingVehicleHulls(const Box3F&, U16* hulls, U32* numHulls); + +protected: + bool castRay_r(const U32, const U16, const Point3F&, const Point3F&, RayInfo*); + void buildPolyList_r(InteriorPolytope& polytope, + SurfaceHash& hash); + void scanZone_r(const U32 node, + const Point3F& center, + const Point3F& axisx, + const Point3F& axisy, + const Point3F& axisz, + U16* zones, + U32* numZones); + void scanZoneNew(InteriorPolytope& polytope, + U16* zones, + U32* numZones); + + void scopeZone(const U32 currZone, + bool* interiorScopingState, + const Point3F& interiorRoot, + Vector& zoneStack, + Vector& planeStack, + Vector& planeRangeStack); + + + /// @name Lightmap Support + /// @{ + static void _enableLightMapFeature( ProcessedMaterial *mat, + U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ); + /// @} + + //-------------------------------------- Global rendering control +public: + enum RenderModes + { + NormalRender = 0, + NormalRenderLines = 1, + ShowDetail = 2, + ShowAmbiguous = 3, + ShowOrphan = 4, + ShowLightmaps = 5, + ShowTexturesOnly = 6, + ShowPortalZones = 7, + ShowOutsideVisible = 8, + ShowCollisionFans = 9, + ShowStrips = 10, + ShowNullSurfaces = 11, + ShowLargeTextures = 12, + ShowHullSurfaces = 13, + ShowVehicleHullSurfaces = 14, + ShowVertexColors = 15, + ShowDetailLevel = 16 + }; + + enum Constants + { + NumCoordBins = 16, + + BinsXY = 0, + BinsXZ = 1, + BinsYZ = 2 + }; + + static U32 smRenderMode; + static bool smFocusedDebug; + static bool smRenderEnvironmentMaps; + static bool smUseVertexLighting; + static bool smUseTexturedFog; + static bool smLockArrays; + static U32 smFileVersion; + static bool smLightingCastRays; + static bool smLightingBuildPolyList; + + //-------------------------------------- Persistence interface + public: + bool read(Stream& stream); + bool write(Stream& stream) const; + + bool readVehicleCollision(Stream& stream); + bool writeVehicleCollision(Stream& stream) const; + + private: + bool writePlaneVector(Stream&) const; + bool readPlaneVector(Stream&); + bool readLMapTexGen(Stream&, PlaneF&, PlaneF&); + bool writeLMapTexGen(Stream&, const PlaneF&, const PlaneF&) const; + void setupTexCoords(); + void setupZonePlanes(); + + //-------------------------------------- For morian only... + public: + void processHullPolyLists(); + void processVehicleHullPolyLists(); + + //-------------------------------------- BSP Structures + private: + struct IBSPNode + { + U16 planeIndex; + U32 frontIndex; + U32 backIndex; + + U16 terminalZone; // if high bit set, then the lower 15 bits are the zone + // of any of the subsidiary nodes. Note that this is + // going to overestimate some, since an object could be + // completely contained in solid, but it's probably + // going to turn out alright. + }; + struct IBSPLeafSolid + { + U32 surfaceIndex; + U16 surfaceCount; + }; + + bool isBSPLeafIndex(U32 index) const; + bool isBSPSolidLeaf(U32 index) const; + bool isBSPEmptyLeaf(U32 index) const; + U16 getBSPSolidLeafIndex(U32 index) const; + U16 getBSPEmptyLeafZone(U32 index) const; + + void setupAveTexGenLength(); + + void truncateZoneTree(); + void truncateZoneNode(const U32); + bool getUnifiedZone(const U32, S32*); + + public: + static U16 getPlaneIndex(U16 index); + static bool planeIsFlipped(U16 index); + const PlaneF& getPlane(U16 index) const; + PlaneF getFlippedPlane(const U16 index) const; + + const Point3F getPointNormal(const U32 surfaceIndex, const U32 pointOffset) const; + + private: + bool areEqualPlanes(U16, U16) const; + + bool isNullSurfaceIndex(const U32 index) const; + bool isVehicleNullSurfaceIndex(const U32 index) const; + U32 getNullSurfaceIndex(const U32 index) const; + U32 getVehicleNullSurfaceIndex(const U32 index) const; + + //-------------------------------------- Portals and Zone structures + private: + struct Zone + { + U16 portalStart; + U16 portalCount; + + U32 surfaceStart; + U32 planeStart; + + U16 surfaceCount; + U16 planeCount; + + U32 staticMeshStart; + U32 staticMeshCount; + + U16 flags; + U16 zoneId; // This is ephemeral, not persisted out. + }; + + struct Portal + { + U16 planeIndex; + + U16 triFanCount; + U32 triFanStart; // portals can have multiple windings + + U16 zoneFront; + U16 zoneBack; + }; + + //-------------------------------------- Poly/Surface structures +public: + enum SurfaceFlags + { + SurfaceDetail = BIT(0), + SurfaceAmbiguous = BIT(1), + SurfaceOrphan = BIT(2), + SurfaceSharedLMaps = BIT(3), // Indicates that the alarm and normal states share a lightmap (for mission lighter) + SurfaceOutsideVisible = BIT(4), + SurfaceStaticMesh = BIT(5), // This surface belongs to a static mesh collision hull + SurfaceFlagMask = (SurfaceDetail | + SurfaceAmbiguous | + SurfaceOrphan | + SurfaceSharedLMaps | + SurfaceOutsideVisible | + SurfaceStaticMesh) + }; + enum ZoneFlags + { + ZoneInside = BIT(0) + }; + + const bool isSurfaceOutsideVisible(U32 surface) const; + + public: + struct TexMatrix + { + S32 T; + S32 N; + S32 B; + + TexMatrix() + : T( -1 ), + N( -1 ), + B( -1 ) + {}; + }; + + struct Edge + { + S32 vertexes[2]; + S32 faces[2]; + }; + + struct TexGenPlanes + { + PlaneF planeX; + PlaneF planeY; + }; + + struct TriFan + { + U32 windingStart; + U32 windingCount; + }; + + struct Surface + { + U32 windingStart; // 1 + + U16 planeIndex; // 2 + U16 textureIndex; + + U32 texGenIndex; // 3 + + U16 lightCount; // 4 + U8 surfaceFlags; + U32 windingCount; + + U32 fanMask; // 5 + + U32 lightStateInfoStart; // 6 + + U32 mapOffsetX; // 7 + U32 mapOffsetY; + U32 mapSizeX; + U32 mapSizeY; + + Point3F T,B,N; + Point3F normal; + + //U32 VBIndexStart; + //U32 primIndex; + GFXPrimitive surfaceInfo; + + bool unused; + }; + + struct NullSurface + { + U32 windingStart; + + U16 planeIndex; + U8 surfaceFlags; + U32 windingCount; + }; + + //-------------------------------------- Animated lighting structures + enum LightFlags + { + AnimationAmbient = BIT(0), + AnimationLoop = BIT(1), + AnimationFlicker = BIT(2), + AnimationTypeMask = BIT(3) - 1, + + AlarmLight = BIT(3) + }; + + enum LightType + { + AmbientLooping = AnimationAmbient | AnimationLoop, + AmbientFlicker = AnimationAmbient | AnimationFlicker, + + TriggerableLoop = AnimationLoop, + TriggerableFlicker = AnimationFlicker, + TriggerableRamp = 0 + }; + +private: + bool readSurface(Stream&, Surface&, TexGenPlanes&, const bool); + + public: + // this is public because tools/Morian needs this defination + struct AnimatedLight { + U32 nameIndex; // Light's name + U32 stateIndex; // start point in the state list + + U16 stateCount; // number of states in this light + U16 flags; // flags (Apply AnimationTypeMask to get type) + + U32 duration; // total duration of animation (ms) + }; + private: + struct LightState { + U8 red; // state's color + U8 green; + U8 blue; + U8 _color_padding_; + + U32 activeTime; // Time (ms) at which this state becomes active + + U32 dataIndex; // StateData count and index for this state + U16 dataCount; + + U16 __32bit_padding__; + }; + struct LightStateData { + U32 surfaceIndex; // Surface affected by this data + U32 mapIndex; // Index into StateDataBuffer (0xFFFFFFFF indicates none) + U16 lightStateIndex; // Entry to modify in InteriorInstance + U16 __32bit_padding__; + }; + + // convex hull collision structures... + protected: + struct ConvexHull + { + F32 minX; + F32 maxX; + F32 minY; + F32 maxY; + + F32 minZ; + F32 maxZ; + U32 hullStart; + U32 surfaceStart; + + U32 planeStart; + U16 hullCount; + U16 surfaceCount; + U32 polyListPlaneStart; + + U32 polyListPointStart; + U32 polyListStringStart; + U16 searchTag; + bool staticMesh; + }; + + struct CoordBin + { + U32 binStart; + U32 binCount; + }; + + struct RenderNode + { + bool exterior; + U16 baseTexIndex; + U8 lightMapIndex; + S32 primInfoIndex; + BaseMatInstance *matInst; + + RenderNode() + { + exterior = true; + baseTexIndex = 0; + lightMapIndex = U8(-1); + primInfoIndex = -1; + matInst = NULL; + } + }; + + + // public because InteriorInstance needs access + public: + Vector< PlaneF > mReflectPlanes; + + protected: + struct ReflectRenderNode + { + bool exterior; + S32 reflectPlaneIndex; + S32 primInfoIndex; + U8 lightMapIndex; + + BaseMatInstance *matInst; + + + ReflectRenderNode() + { + exterior = true; + reflectPlaneIndex = -1; + lightMapIndex = U8(-1); + primInfoIndex = -1; + matInst = NULL; + } + }; + + + struct ZoneRNList + { + Vector renderNodeList; + }; + + struct ZoneReflectRNList + { + Vector reflectList; + }; + + // needs to be exposed in order for on the fly changes + public: + Vector mZoneRNList; + + private: + + // reflective plane data + GFXVertexBufferHandle mReflectVertBuff; + GFXPrimitiveBufferHandle mReflectPrimBuff; + Vector mZoneReflectRNList; + + // standard interior data + GFXVertexBufferHandle mVertBuff; + GFXPrimitiveBufferHandle mPrimBuff; + + private: + LM_HANDLE mLMHandle; + public: + LM_HANDLE getLMHandle() {return(mLMHandle);} + + public: + + // SceneLighting::InteriorProxy interface + const Surface & getSurface(const U32 surface) const; + const U32 getSurfaceCount() const; + const U32 getNormalLMapIndex(const U32 surface) const; + const U32 getAlarmLMapIndex(const U32 surface) const; + const U32 getStaticMeshCount() const; + const InteriorSimpleMesh *getStaticMesh(const U32 index) const; + const U32 getWinding(const U32 index) const; + const Point3F & getPoint(const U32 index) const; + const TexGenPlanes & getLMTexGenEQ(const U32 index) const; + const TexGenPlanes & getTexGenEQ(const U32 index) const; + bool hasAlarmState() const; + const U32 getWindingCount() const; + S32 getTargetCount() const; + const String& getTargetName( S32 mapToNameIndex ) const; + + //-------------------------------------- Instance Data Members + private: + U32 mFileVersion; + U32 mDetailLevel; + U32 mMinPixels; + F32 mAveTexGenLength; // Set in Interior::read after loading the texgen planes. + Box3F mBoundingBox; + SphereF mBoundingSphere; + + Vector mPlanes; + Vector mPoints; + Vector mPointVisibility; + + Vector mNormals; + Vector mTexMatrices; + Vector mTexMatIndices; + + ColorF mBaseAmbient; + ColorF mAlarmAmbient; + + Vector mBSPNodes; + Vector mBSPSolidLeaves; + + bool mPreppedForRender; + + bool mHasTranslucentMaterials; + + Vector mWindings; + + Vector mTexGenEQs; + Vector mLMTexGenEQs; + + Vector mWindingIndices; + Vector mSurfaces; + Vector mNullSurfaces; + Vector mSolidLeafSurfaces; + + Vector mEdges; + + // Portals and zones + Vector mZones; + Vector mZonePlanes; + Vector mZoneSurfaces; + Vector mZonePortalList; + Vector mPortals; + Vector mZoneStaticMeshes; + + // Subobjects: Doors, translucencies, mirrors, etc. + Vector mSubObjects; + + // Lighting info + bool mHasAlarmState; + U32 mNumLightStateEntries; + + Vector mLightmaps; + Vector mLightmapKeep; + Vector mNormalLMapIndices; + Vector mAlarmLMapIndices; + + U32 mNumTriggerableLights; // Note: not persisted + + // Persistent animated light structures + Vector mAnimatedLights; + Vector mLightStates; + Vector mStateData; + Vector mStateDataBuffer; + + Vector mNameBuffer; + + Vector mConvexHulls; + Vector mConvexHullEmitStrings; + Vector mHullIndices; + Vector mHullEmitStringIndices; + Vector mHullSurfaceIndices; + Vector mHullPlaneIndices; + Vector mPolyListPlanes; + Vector mPolyListPoints; + Vector mPolyListStrings; + CoordBin mCoordBins[NumCoordBins * NumCoordBins]; + Vector mCoordBinIndices; + U32 mCoordBinMode; + + Vector mVehicleConvexHulls; + Vector mVehicleConvexHullEmitStrings; + Vector mVehicleHullIndices; + Vector mVehicleHullEmitStringIndices; + Vector mVehicleHullSurfaceIndices; + Vector mVehicleHullPlaneIndices; + Vector mVehiclePolyListPlanes; + Vector mVehiclePolyListPoints; + Vector mVehiclePolyListStrings; + Vector mVehiclePoints; + Vector mVehicleNullSurfaces; + Vector mVehiclePlanes; + Vector mVehicleWindings; + Vector mVehicleWindingIndices; + + VectorPtr mStaticMeshes; + + U16 mSearchTag; + Vector mMatInstCleanupList; + + //-------------------------------------- Private interface + private: + +#ifndef TORQUE_SHIPPING + GFXShaderRef mDebugShader; + GFXTexHandle mDebugTexture; + + GFXStateBlockRef mInteriorDebugNoneSB; + GFXStateBlockRef mInteriorDebugPortalSB; + GFXStateBlockRef mInteriorDebugTextureSB; + GFXStateBlockRef mInteriorDebugTwoTextureSB; + + // Debug Render ConstantBuffers + GFXShaderConstBufferRef mDebugShaderConsts; + + GFXShaderConstHandle* mDebugShaderModelViewSC; + GFXShaderConstHandle* mDebugShaderShadeColorSC; +#endif + + const char* getName(const U32 nameIndex) const; + static const char* getLightTypeString(const LightType); + S32 getZoneForPoint(const Point3F&) const; + +#ifndef TORQUE_SHIPPING + void debugRender(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst, MatrixF& modelview); + // render debug utility functions + void preDebugRender(); + void debugDefaultRender(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst); + // show all surfaces with flag set as color c + void debugShowSurfaceFlag(const ZoneVisDeterminer& zoneVis, const U32 flag, const ColorF& c); + // render brushes + void debugNormalRenderLines(const ZoneVisDeterminer& zoneVis); + // next 4 use debugShowSurfaceFlag to show surface info + void debugShowDetail(const ZoneVisDeterminer& zoneVis); + void debugShowAmbiguous(const ZoneVisDeterminer& zoneVis); + void debugShowOrphan(const ZoneVisDeterminer& zoneVis); + void debugShowOutsideVisible(const ZoneVisDeterminer& zoneVis); + void debugShowPortalZones(const ZoneVisDeterminer& zoneVis); + void debugRenderPortals(); + void debugShowCollisionFans(const ZoneVisDeterminer& zoneVis); + void debugShowStrips(const ZoneVisDeterminer& zoneVis); + void debugShowDetailLevel(const ZoneVisDeterminer& zoneVis); + void debugShowHullSurfaces(); + void debugShowNullSurfaces(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst); + void debugShowVehicleHullSurfaces(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst); + void debugShowTexturesOnly(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst); + void debugShowLightmaps(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst); + void debugShowLargeTextures(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst); +// void debugShowVertexColors(MaterialList* pMaterials); +#endif + + public: + void collisionFanFromSurface(const Surface&, U32* fan, U32* numIndices) const; + void fillSurfaceTexMats(); + void createZoneVBs(); + void cloneMatInstances(); + void initMatInstances(); + void createReflectPlanes(); + + private: + void fullWindingFromSurface(const Surface&, U32* fan, U32* numIndices) const; + bool projectClipAndBoundFan(U32 fanIndex, F64* pResult); + void zoneTraversal(S32 baseZone, const bool flipClipPlanes); + void createZoneRectVectors(); + void destroyZoneRectVectors(); + void traverseZone(const RectD* inRects, const U32 numInputRects, U32 currZone, Vector& zoneStack); + + void fillVertex( GFXVertexPNTTB &vert, Surface &surface, U32 surfaceIndex ); + void getTexMat(U32 surfaceIndex, U32 pointOffset, Point3F& T, Point3F& N, Point3F& B); + + + void storeRenderNode( RenderNode &node, + ZoneRNList &RNList, + Vector &primInfoList, + Vector &indexList, + Vector &verts, + U32 &startIndex, + U32 &startVert ); + + void renderZoneNode( SceneState *state, + RenderNode &node, + InteriorInstance *intInst, + SceneGraphData &sgData, + MeshRenderInst *coreRi ); + + void renderReflectNode( SceneState *state, + ReflectRenderNode &node, + InteriorInstance *intInst, + SceneGraphData &sgData, + MeshRenderInst *coreRi ); + + void renderLights(SceneState* state, InteriorInstance *intInst, SceneGraphData &sgData, MeshRenderInst *coreRi, const ZoneVisDeterminer &zonevis); + + + /// Used to maintain point and normal relationship (which use + /// dissimilar index lookups) for verts when building vertex buffer. + struct VertexBufferTempIndex + { + U16 index; + Point3F normal; + + VertexBufferTempIndex() + { + index = 0; + normal.set(0.0f, 0.0f, 0.0f); + } + VertexBufferTempIndex(const U16 ind, const Point3F &norm) + { + index = ind; + normal = norm; + } + }; + + void storeSurfVerts( Vector &masterIndexList, + Vector &tempIndexList, + Vector &verts, + U32 numIndices, + Surface &surface, + U32 surfaceIndex ); + + public: + void purgeLODData(); + + void buildExportPolyList(OptimizedPolyList& polys, MatrixF* mat = NULL, Point3F* scale = NULL); +}; + +//------------------------------------------------------------------------------ +inline bool Interior::isBSPLeafIndex(U32 index) const +{ + if (mFileVersion >= 14) + return (index & 0x80000) != 0; + else + return (index & 0x8000) != 0; +} + +inline bool Interior::isBSPSolidLeaf(U32 index) const +{ + AssertFatal(isBSPLeafIndex(index) == true, "Error, only call for leaves!"); + + if (mFileVersion >= 14) + return (index & 0x40000) != 0; + else + return (index & 0x4000) != 0; +} + +inline bool Interior::isBSPEmptyLeaf(U32 index) const +{ + AssertFatal(isBSPLeafIndex(index) == true, "Error, only call for leaves!"); + + if (mFileVersion >= 14) + return (index & 0x40000) == 0; + else + return (index & 0x4000) == 0; +} + +inline U16 Interior::getBSPSolidLeafIndex(U32 index) const +{ + AssertFatal(isBSPSolidLeaf(index) == true, "Error, only call for leaves!"); + + if (mFileVersion >= 14) + return U16(index & ~0xC0000); + else + return U16(index & ~0xC000); +} + +inline U16 Interior::getBSPEmptyLeafZone(U32 index) const +{ + AssertFatal(isBSPEmptyLeaf(index) == true, "Error, only call for leaves!"); + + if (mFileVersion >= 14) + return U16(index & ~0xC0000); + else + return U16(index & ~0xC000); +} + +inline const PlaneF& Interior::getPlane(U16 index) const +{ + AssertFatal(U32(index & ~0x8000) < mPlanes.size(), + "Interior::getPlane: planeIndex out of range"); + + return mPlanes[index & ~0x8000]; +} + +inline PlaneF Interior::getFlippedPlane(const U16 index) const +{ + PlaneF plane = getPlane(index); + if(Interior::planeIsFlipped(index)) + plane.neg(); + + return plane; +} + +inline U16 Interior::getPlaneIndex(U16 index) +{ + return U16(index & ~0x8000); +} + +inline bool Interior::planeIsFlipped(U16 index) +{ + return (index >> 15)!=0; +} + +inline bool Interior::areEqualPlanes(U16 o, U16 t) const +{ + return (o & ~0x8000) == (t & ~0x8000); +} + +inline const Point3F Interior::getPointNormal(const U32 surfaceIndex, const U32 pointOffset) const +{ + Surface rSurface = mSurfaces[surfaceIndex]; + + Point3F normal(0.0f, 0.0f, 0.0f); + + if (mFileVersion >= 11) + { + U32 texMatIndex = mTexMatIndices[rSurface.windingStart + pointOffset]; + TexMatrix texMat = mTexMatrices[texMatIndex]; + + if (texMat.N > -1) + normal = mNormals[texMat.N]; + } + else + normal = getFlippedPlane(rSurface.planeIndex); + + return normal; +} + +inline U32 Interior::getDetailLevel() const +{ + return mDetailLevel; +} + +inline U32 Interior::getMinPixels() const +{ + return mMinPixels; +} + +inline const Box3F& Interior::getBoundingBox() const +{ + return mBoundingBox; +} + +inline S32 Interior::getNumZones() const +{ + return mZones.size(); +} + +inline bool Interior::isNullSurfaceIndex(const U32 index) const +{ + return (index & 0x80000000) != 0; +} + +inline bool Interior::isVehicleNullSurfaceIndex(const U32 index) const +{ + return (index & 0x40000000) != 0; +} + +inline U32 Interior::getNullSurfaceIndex(const U32 index) const +{ + AssertFatal(isNullSurfaceIndex(index), "Not a proper index!"); + AssertFatal(!isVehicleNullSurfaceIndex(index), "Not a proper index"); + return (index & ~0x80000000); +} + +inline U32 Interior::getVehicleNullSurfaceIndex(const U32 index) const +{ + AssertFatal(isVehicleNullSurfaceIndex(index), "Not a proper index!"); + return (index & ~(0x80000000 | 0x40000000)); +} + +inline const char* Interior::getLightTypeString(const LightType type) +{ + switch (type) { + case AmbientLooping: + return "AmbientLooping"; + case AmbientFlicker: + return "AmbientFlicker"; + case TriggerableLoop: + return "TriggerableLoop"; + case TriggerableFlicker: + return "TriggerableFlicker"; + case TriggerableRamp: + return "TriggerableRamp"; + + default: + return ""; + } +} + +inline const char* Interior::getName(const U32 nameIndex) const +{ + return &mNameBuffer[nameIndex]; +} + +inline const U32 Interior::getSurfaceCount() const +{ + return(mSurfaces.size()); +} + +inline const Interior::Surface & Interior::getSurface(const U32 surface) const +{ + AssertFatal(surface < mSurfaces.size(), "invalid index"); + return(mSurfaces[surface]); +} + +inline const U32 Interior::getStaticMeshCount() const +{ + return mStaticMeshes.size(); +} + +inline const InteriorSimpleMesh *Interior::getStaticMesh(const U32 index) const +{ + AssertFatal(index < mStaticMeshes.size(), "invalid index"); + return mStaticMeshes[index]; +} + +inline const U32 Interior::getNormalLMapIndex(const U32 surface) const +{ + AssertFatal(surface < mNormalLMapIndices.size(), "invalid index"); + return(mNormalLMapIndices[surface]); +} + +inline const U32 Interior::getAlarmLMapIndex(const U32 surface) const +{ + AssertFatal(surface < mAlarmLMapIndices.size(), "invalid index"); + return(mAlarmLMapIndices[surface]); +} + +inline const U32 Interior::getWinding(const U32 index) const +{ + AssertFatal(index < mWindings.size(), "invalid index"); + return(mWindings[index]); +} + +inline const Point3F & Interior::getPoint(const U32 index) const +{ + AssertFatal(index < mPoints.size(), "invalid index"); + return(mPoints[index].point); +} + +inline const Interior::TexGenPlanes & Interior::getLMTexGenEQ(const U32 index) const +{ + AssertFatal(index < mLMTexGenEQs.size(), "invalid index"); + return(mLMTexGenEQs[index]); +} + +inline const Interior::TexGenPlanes & Interior::getTexGenEQ(const U32 index) const +{ + AssertFatal(index < mTexGenEQs.size(), "invalid index"); + return(mTexGenEQs[index]); +} + +inline bool Interior::hasAlarmState() const +{ + return(mHasAlarmState); +} + +inline const bool Interior::isSurfaceOutsideVisible(U32 surface) const +{ + AssertFatal(surface < mSurfaces.size(), "Interior::isSurfaceOutsideVisible: Invalid surface index"); + return ((mSurfaces[surface].surfaceFlags & SurfaceOutsideVisible)!=0); +} + +inline const U32 Interior::getWindingCount() const +{ + return(mWindings.size()); +} + +#endif //_INTERIOR_H_ diff --git a/interior/interiorCollision.cpp b/interior/interiorCollision.cpp new file mode 100644 index 0000000..550180a --- /dev/null +++ b/interior/interiorCollision.cpp @@ -0,0 +1,1784 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "math/mSphere.h" +#include "sceneGraph/sceneObject.h" +#include "collision/abstractPolyList.h" + +#include "core/frameAllocator.h" +#include "platform/profiler.h" + +namespace { + +//-------------------------------------- +// Custom on plane version to reduce numerical inaccuracies in the GJK stuff. +// copied from terrCollision.cc +inline bool isOnPlane(const Point3F& p, const PlaneF& plane) +{ + F32 dist = mDot(plane,p) + plane.d; + return mFabs(dist) < 0.1f; +} + +} // namespace {} + + +//-------------------------------------------------------------------------- +//-------------------------------------- SurfaceHash +const U32 csgHashSize = 4096; +class SurfaceHash +{ + private: + U32 hash(U32 i) { return i & (csgHashSize - 1); } + + public: + U32 mSurfaceArray[csgHashSize]; + U32 mNumSurfaces; + + U32 mHashTable[csgHashSize]; + + public: + SurfaceHash() + { + AssertFatal(isPow2(csgHashSize), "Error, size must be power of 2"); + } + + void clear() + { + dMemset(mHashTable, 0xFF, sizeof(mHashTable)); + mNumSurfaces = 0; + } + + void insert(U32 surface); +}; + +inline void SurfaceHash::insert(U32 surface) +{ + AssertFatal(surface != 0xFFFFFFFF, "Hm, bad assumption. 0xFFFFFFFF is a valid surface index?"); + if (mNumSurfaces >= csgHashSize) + { + AssertFatal(false, "Error, exceeded surfaceCount restriction!"); + return; + } + + U32 probe = hash(surface); + U32 initialProbe = probe; + while (mHashTable[probe] != 0xFFFFFFFF) + { + // If it's already in the table, bail. + if (mHashTable[probe] == surface) + return; + + probe = (probe + 1) % csgHashSize; + AssertFatal(probe != initialProbe, "Hm, wraparound?"); + } + + // If we're here, then the probe failed, and the index isn't in the hash + // table + mHashTable[probe] = surface; + mSurfaceArray[mNumSurfaces++] = surface; +} + + +class InteriorPolytope +{ + // Convex Polyhedron + public: + struct Vertex + { + Point3F point; + // Temp BSP clip info + S32 side; + }; + struct Edge + { + S32 vertex[2]; + S32 face[2]; + S32 next; + }; + struct Face + { + S32 original; + // Temp BSP clip info + S32 vertex; + }; + struct Volume + { + S32 edgeList; + }; + struct StackElement + { + S32 edgeList; + U32 popCount; + U32 nodeIndex; + }; + + typedef Vector EdgeList; + typedef Vector FaceList; + typedef Vector VertexList; + typedef Vector VolumeList; + typedef Vector VolumeStack; + + // + S32 sideCount; + EdgeList mEdgeList; + FaceList mFaceList; + VertexList mVertexList; + VolumeList mVolumeList; + + public: + // + InteriorPolytope(); + void clear(); + bool intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep); + void buildBox(const Box3F& box, const MatrixF& transform, const Point3F& scale); + inline bool didIntersect() { return mVolumeList.size() > 1; } +}; + +//---------------------------------------------------------------------------- +// Box should be axis aligned in the transform space provided. + +InteriorPolytope::InteriorPolytope() +{ + mVertexList.reserve(256); + mFaceList.reserve(200); + mEdgeList.reserve(100); + mVolumeList.reserve(20); + sideCount = 0; +} + +void InteriorPolytope::clear() +{ + mVertexList.clear(); + mFaceList.clear(); + mEdgeList.clear(); + mVolumeList.clear(); + sideCount = 0; +} + +void InteriorPolytope::buildBox(const Box3F& box, const MatrixF& transform, const Point3F&) +{ + // Initial vertices + mVertexList.setSize(8); + mVertexList[0].point = Point3F(box.minExtents.x, box.minExtents.y, box.minExtents.z); + mVertexList[1].point = Point3F(box.minExtents.x, box.maxExtents.y, box.minExtents.z); + mVertexList[2].point = Point3F(box.maxExtents.x, box.maxExtents.y, box.minExtents.z); + mVertexList[3].point = Point3F(box.maxExtents.x, box.minExtents.y, box.minExtents.z); + mVertexList[4].point = Point3F(box.minExtents.x, box.minExtents.y, box.maxExtents.z); + mVertexList[5].point = Point3F(box.minExtents.x, box.maxExtents.y, box.maxExtents.z); + mVertexList[6].point = Point3F(box.maxExtents.x, box.maxExtents.y, box.maxExtents.z); + mVertexList[7].point = Point3F(box.maxExtents.x, box.minExtents.y, box.maxExtents.z); + S32 i; + for (i = 0; i < 8; i++) + { + transform.mulP(mVertexList[i].point); + mVertexList[i].side = 0; + } + + // Initial faces + mFaceList.setSize(6); + for (S32 f = 0; f < 6; f++) + { + Face& face = mFaceList[f]; + face.original = true; + face.vertex = 0; + } + + // Initial edges + mEdgeList.setSize(12); + Edge* edge = mEdgeList.begin(); + S32 nextEdge = 0; + for (i = 0; i < 4; i++) + { + S32 n = (i == 3)? 0: i + 1; + S32 p = (i == 0)? 3: i - 1; + edge->vertex[0] = i; + edge->vertex[1] = n; + edge->face[0] = i; + edge->face[1] = 4; + edge->next = ++nextEdge; + edge++; + edge->vertex[0] = 4 + i; + edge->vertex[1] = 4 + n; + edge->face[0] = i; + edge->face[1] = 5; + edge->next = ++nextEdge; + edge++; + edge->vertex[0] = i; + edge->vertex[1] = 4 + i; + edge->face[0] = i; + edge->face[1] = p; + edge->next = ++nextEdge; + edge++; + } + edge[-1].next = -1; + + // Volume + mVolumeList.setSize(1); + Volume& volume = mVolumeList.last(); + volume.edgeList = 0; + sideCount = 0; +} + +bool InteriorPolytope::intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep) +{ + // If den == 0 then the line and plane are parallel. + F32 den; + Point3F dt = ep - sp; + if ((den = plane.x * dt.x + plane.y * dt.y + plane.z * dt.z) == 0.0f) + return false; + + // Save the values in sp since the memory may go away after the increment + F32 sp_x = sp.x; + F32 sp_y = sp.y; + F32 sp_z = sp.z; + + mVertexList.increment(); + Vertex& v = mVertexList.last(); + F32 s = -(plane.x * sp_x + plane.y * sp_y + plane.z * sp_z + plane.d) / den; + v.point.x = sp_x + dt.x * s; + v.point.y = sp_y + dt.y * s; + v.point.z = sp_z + dt.z * s; + v.side = 0; + return true; +} + + +//-------------------------------------------------------------------------- +void Interior::collisionFanFromSurface(const Surface& rSurface, U32* fanIndices, U32* numIndices) const +{ + U32 tempIndices[32]; + + tempIndices[0] = 0; + U32 idx = 1; + U32 i; + + for (i = 1; i < rSurface.windingCount; i += 2) + tempIndices[idx++] = i; + + for (i = ((rSurface.windingCount - 1) & (~0x1)); i > 0; i -= 2) + tempIndices[idx++] = i; + + idx = 0; + for (i = 0; i < rSurface.windingCount; i++) + { + if (rSurface.fanMask & (1 << i)) + { + fanIndices[idx++] = mWindings[rSurface.windingStart + tempIndices[i]]; + } + } + *numIndices = idx; +} + +void Interior::fullWindingFromSurface(const Surface& rSurface, U32* fanIndices, U32* numIndices) const +{ + U32 tempIndices[32]; + + tempIndices[0] = 0; + U32 idx = 1; + U32 i; + + for (i = 1; i < rSurface.windingCount; i += 2) + tempIndices[idx++] = i; + + for (i = ((rSurface.windingCount - 1) & (~0x1)); i > 0; i -= 2) + tempIndices[idx++] = i; + + idx = 0; + for (i = 0; i < rSurface.windingCount; i++) + fanIndices[idx++] = mWindings[rSurface.windingStart + tempIndices[i]]; + + *numIndices = idx; +} + + +bool Interior::castRay_r(const U32 node, + const U16 planeIndex, + const Point3F& s, + const Point3F& e, + RayInfo* info) +{ + if (isBSPLeafIndex(node) == false) + { + const IBSPNode& rNode = mBSPNodes[node]; + const PlaneF& rPlane = getPlane(rNode.planeIndex); + + const PlaneF::Side sSide = rPlane.whichSide(s); + const PlaneF::Side eSide = rPlane.whichSide(e); + + switch (PlaneSwitchCode(sSide, eSide)) + { + case PlaneSwitchCode(PlaneF::Front, PlaneF::Front): + case PlaneSwitchCode(PlaneF::Front, PlaneF::On): + case PlaneSwitchCode(PlaneF::On, PlaneF::Front): + return castRay_r(rNode.frontIndex, planeIndex, s, e, info); + break; + + case PlaneSwitchCode(PlaneF::On, PlaneF::Back): + case PlaneSwitchCode(PlaneF::Back, PlaneF::On): + case PlaneSwitchCode(PlaneF::Back, PlaneF::Back): + return castRay_r(rNode.backIndex, planeIndex, s, e, info); + break; + + case PlaneSwitchCode(PlaneF::On, PlaneF::On): + // Line lies on the plane + if (isBSPLeafIndex(rNode.backIndex) == false) + { + if (castRay_r(rNode.backIndex, planeIndex, s, e, info)) + return true; + } + + if (isBSPLeafIndex(rNode.frontIndex) == false) + { + if (castRay_r(rNode.frontIndex, planeIndex, s, e, info)) + return true; + } + + return false; + break; + + case PlaneSwitchCode(PlaneF::Front, PlaneF::Back): + { + Point3F ip; + F32 intersectT = rPlane.intersect(s, e); + AssertFatal(intersectT != PARALLEL_PLANE, "Error, this should never happen in this case!"); + ip.interpolate(s, e, intersectT); + if (castRay_r(rNode.frontIndex, planeIndex, s, ip, info)) + return true; + return castRay_r(rNode.backIndex, rNode.planeIndex, ip, e, info); + } + break; + + case PlaneSwitchCode(PlaneF::Back, PlaneF::Front): + { + Point3F ip; + F32 intersectT = rPlane.intersect(s, e); + AssertFatal(intersectT != PARALLEL_PLANE, "Error, this should never happen in this case!"); + ip.interpolate(s, e, intersectT); + if (castRay_r(rNode.backIndex, planeIndex, s, ip, info)) + return true; + return castRay_r(rNode.frontIndex, rNode.planeIndex, ip, e, info); + } + break; + + default: + AssertFatal(false, "Misunderstood switchCode in Interior::castRay_r"); + return false; + } + } + + if (isBSPSolidLeaf(node)) + { + // DMM: Set material info here.. material info? hahaha + + info->point = s; + + if (planeIndex != U16(-1)) + { + const PlaneF& rPlane = getPlane(planeIndex); + info->normal = rPlane; + if (planeIsFlipped(planeIndex)) + { + info->normal.neg(); + if (rPlane.whichSide(e) == PlaneF::Back) + info->normal.neg(); + } + else + { + if (rPlane.whichSide(e) == PlaneF::Front) + info->normal.neg(); + } + } + else + { + // Point started in solid; + if (s == e) + { + info->normal.set(0.0f, 0.0f, 1.0f); + } + else + { + info->normal = s - e; + info->normal.normalize(); + } + } + + // ok.. let's get it on! get the face that was hit + info->face = U32(-1); + + U32 numStaticSurfaces = 0; + + const IBSPLeafSolid & rLeaf = mBSPSolidLeaves[getBSPSolidLeafIndex(node)]; + for(U32 i = 0; i < rLeaf.surfaceCount; i++) + { + U32 surfaceIndex = mSolidLeafSurfaces[rLeaf.surfaceIndex + i]; + if(isNullSurfaceIndex(surfaceIndex)) + { + const NullSurface & rSurface = mNullSurfaces[getNullSurfaceIndex(surfaceIndex)]; + if (rSurface.surfaceFlags & SurfaceStaticMesh) + numStaticSurfaces++; + + continue; + } + + const Surface & rSurface = mSurfaces[surfaceIndex]; + if(!areEqualPlanes(rSurface.planeIndex, planeIndex)) + continue; + + PlaneF plane = getPlane(rSurface.planeIndex); + if(planeIsFlipped(rSurface.planeIndex)) + plane.neg(); + + // unfan this surface + U32 winding[32]; + U32 windingCount; + collisionFanFromSurface(rSurface, winding, &windingCount); + + // inside this surface? + bool inside = true; + for(U32 j = 0; inside && (j < windingCount); j++) + { + U32 k = (j+1) % windingCount; + + Point3F vec1 = mPoints[winding[k]].point - mPoints[winding[j]].point; + Point3F vec2 = info->point - mPoints[winding[j]].point; + + Point3F cross; + mCross(vec2, vec1, &cross); + + if(mDot(plane, cross) < 0.f) + inside = false; + } + + if(inside) + { + info->face = surfaceIndex; + info->material = mMaterialList->getMaterialInst( rSurface.textureIndex ); + break; + } + } + + if (Interior::smLightingCastRays && numStaticSurfaces == rLeaf.surfaceCount && numStaticSurfaces > 0) + return false; + + return true; + } + return false; +} + +bool Interior::castRay(const Point3F& s, const Point3F& e, RayInfo* info) +{ + // DMM: Going to need normal here eventually. + bool hit = castRay_r(0, U16(-1), s, e, info); + if (hit) + { + Point3F vec = e - s; + F32 len = vec.len(); + if (len < 1e-6f) + { + info->t = 0.0f; + } + else + { + vec /= len; + info->t = mDot(info->point - s, vec) / len; + } + } + + AssertFatal(!hit || (info->normal.z == info->normal.z), "NaN returned from castRay.\n\nPlease talk to DMM if you are running this in the debugger"); + + return hit; +} + +void Interior::buildPolyList_r(InteriorPolytope& polytope, + SurfaceHash& hash) +{ + // Submit the first volume of the poly tope to the bsp tree + static InteriorPolytope::VolumeStack stack; + stack.reserve(256); + stack.setSize(1); + stack.last().edgeList = polytope.mVolumeList[0].edgeList; + stack.last().nodeIndex = 0; + stack.last().popCount = 0; + + static Vector collPlanes; + collPlanes.reserve(64); + collPlanes.clear(); + + while (stack.empty() == false) + { + InteriorPolytope::StackElement volume = stack.last(); + stack.pop_back(); + + if (isBSPLeafIndex(volume.nodeIndex)) + { + if (isBSPSolidLeaf(volume.nodeIndex)) + { + // Export the polys from this node. + const IBSPLeafSolid& rLeaf = mBSPSolidLeaves[getBSPSolidLeafIndex(volume.nodeIndex)]; + for (U32 i = 0; i < rLeaf.surfaceCount; i++) + { + U32 surfaceIndex = mSolidLeafSurfaces[rLeaf.surfaceIndex + i]; + if (isNullSurfaceIndex(surfaceIndex)) + { + // Is a NULL surface + const NullSurface& rSurface = mNullSurfaces[getNullSurfaceIndex(surfaceIndex)]; + for (U32 j = 0; j < collPlanes.size(); j++) + { + if (areEqualPlanes(rSurface.planeIndex, collPlanes[j]) == true) + { + hash.insert(surfaceIndex); + break; + } + } + } + else + { + const Surface& rSurface = mSurfaces[surfaceIndex]; + for (U32 j = 0; j < collPlanes.size(); j++) + { + if (areEqualPlanes(rSurface.planeIndex, collPlanes[j]) == true) + { + hash.insert(surfaceIndex); + break; + } + } + } + } + } + + if (volume.popCount) + for (U32 i = 0; i < volume.popCount; i++) + collPlanes.pop_back(); + + continue; + } + + const IBSPNode& rNode = mBSPNodes[volume.nodeIndex]; + + // New front and back faces + polytope.mFaceList.increment(2); + InteriorPolytope::Face& frontFace = polytope.mFaceList[polytope.mFaceList.size() - 1]; + InteriorPolytope::Face& backFace = polytope.mFaceList[polytope.mFaceList.size() - 2]; + + backFace.original = frontFace.original = false; + backFace.vertex = frontFace.vertex = 0; + + // New front and back volumes + InteriorPolytope::StackElement frontVolume,backVolume; + frontVolume.edgeList = backVolume.edgeList = -1; + + PlaneF plane = getPlane(rNode.planeIndex); + if (planeIsFlipped(rNode.planeIndex)) + plane.neg(); + + S32 startVertex = polytope.mVertexList.size(); + + // Test & clip all the edges + S32 sideBase = ++polytope.sideCount << 1; + for (S32 i = volume.edgeList; i >= 0; i = polytope.mEdgeList[i].next) + { + + // Copy into tmp first to avoid problems with the array. + InteriorPolytope::Edge edge = polytope.mEdgeList[i]; + + InteriorPolytope::Vertex& v0 = polytope.mVertexList[edge.vertex[0]]; + if (v0.side < sideBase) + v0.side = sideBase + ((plane.distToPlane(v0.point) >= 0)? 0: 1); + + InteriorPolytope::Vertex& v1 = polytope.mVertexList[edge.vertex[1]]; + if (v1.side < sideBase) + v1.side = sideBase + ((plane.distToPlane(v1.point) >= 0)? 0: 1); + + if (v0.side != v1.side) + { + S32 s = v0.side - sideBase; + polytope.intersect(plane,v0.point,v1.point); + + // Split the edge into each volume + polytope.mEdgeList.increment(2); + InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); + e0.next = frontVolume.edgeList; + frontVolume.edgeList = polytope.mEdgeList.size() - 1; + + InteriorPolytope::Edge& e1 = *(&e0 - 1); + e1.next = backVolume.edgeList; + backVolume.edgeList = frontVolume.edgeList - 1; + + e0.vertex[0] = edge.vertex[s]; + e1.vertex[0] = edge.vertex[s ^ 1]; + e0.vertex[1] = e1.vertex[1] = polytope.mVertexList.size() - 1; + e0.face[0] = e1.face[0] = edge.face[0]; + e0.face[1] = e1.face[1] = edge.face[1]; + + // Add new edges on the plane, one to each volume + for (S32 f = 0; f < 2; f++) + { + InteriorPolytope::Face& face = polytope.mFaceList[edge.face[f]]; + if (face.vertex < startVertex) + { + face.vertex = polytope.mVertexList.size() - 1; + } + else + { + polytope.mEdgeList.increment(2); + InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); + e0.next = frontVolume.edgeList; + frontVolume.edgeList = polytope.mEdgeList.size() - 1; + + InteriorPolytope::Edge& e1 = *(&e0 - 1); + e1.next = backVolume.edgeList; + backVolume.edgeList = frontVolume.edgeList - 1; + + e1.vertex[0] = e0.vertex[0] = face.vertex; + e1.vertex[1] = e0.vertex[1] = polytope.mVertexList.size() - 1; + e1.face[0] = e0.face[0] = edge.face[f]; + e1.face[1] = polytope.mFaceList.size() - 1; + e0.face[1] = e1.face[1] - 1; + } + } + } + else + { + if (v0.side == sideBase) + { + polytope.mEdgeList.push_back(edge); + InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); + ne.next = frontVolume.edgeList; + frontVolume.edgeList = polytope.mEdgeList.size() - 1; + } + else + { + polytope.mEdgeList.push_back(edge); + InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); + ne.next = backVolume.edgeList; + backVolume.edgeList = polytope.mEdgeList.size() - 1; + } + } + } + + // Push the front and back nodes + if (frontVolume.edgeList >= 0 && backVolume.edgeList >= 0) + { + collPlanes.push_back(rNode.planeIndex); + + frontVolume.nodeIndex = rNode.frontIndex; + frontVolume.popCount = volume.popCount + 1; + stack.push_back(frontVolume); + + backVolume.nodeIndex = rNode.backIndex; + backVolume.popCount = 0; + stack.push_back(backVolume); + } + else if (frontVolume.edgeList >= 0) + { + frontVolume.nodeIndex = rNode.frontIndex; + frontVolume.popCount = volume.popCount; + stack.push_back(frontVolume); + } + else if (backVolume.edgeList >= 0) + { + backVolume.nodeIndex = rNode.backIndex; + backVolume.popCount = volume.popCount; + stack.push_back(backVolume); + } + else + { + // Pop off our own planes... + if (volume.popCount) + for (U32 i = 0; i < volume.popCount; i++) + collPlanes.pop_back(); + } + } + + AssertFatal(collPlanes.size() == 0, "Unbalanced stack!"); +} + +bool Interior::buildPolyList(AbstractPolyList* list, + const Box3F& box, + const MatrixF& transform, + const Point3F& scale) +{ + Box3F testBox; + MatrixF toItr; + if (!list->getMapping(&toItr,&testBox)) + { + // this list doesn't do this, use world space box and transform + testBox = box; + toItr = transform; + } + + // construct an interior space box from testBox and toItr + // source space may be world space, or may be something else... + // that's up to the list + // Note: transform maps to interior, but scale is itr -> world... + // that is why we divide by scale below... + F32 * f = toItr; + + F32 xx = mFabs(f[0]); F32 xy = mFabs(f[4]); F32 xz = mFabs(f[8]); + F32 yx = mFabs(f[1]); F32 yy = mFabs(f[5]); F32 yz = mFabs(f[9]); + F32 zx = mFabs(f[2]); F32 zy = mFabs(f[6]); F32 zz = mFabs(f[10]); + + F32 xlen = testBox.maxExtents.x - testBox.minExtents.x; + F32 ylen = testBox.maxExtents.y - testBox.minExtents.y; + F32 zlen = testBox.maxExtents.z - testBox.minExtents.z; + + F32 invScalex = 1.0f/scale.x; + F32 invScaley = 1.0f/scale.y; + F32 invScalez = 1.0f/scale.z; + + F32 xrad = (xx * xlen + yx * ylen + zx * zlen) * invScalex; + F32 yrad = (xy * xlen + yy * ylen + zy * zlen) * invScaley; + F32 zrad = (xz * xlen + yz * ylen + zz * zlen) * invScalez; + + Box3F interiorBox; + testBox.getCenter(&interiorBox.minExtents); + toItr.mulP(interiorBox.minExtents); + + interiorBox.minExtents.x *= invScalex; + interiorBox.minExtents.y *= invScaley; + interiorBox.minExtents.z *= invScalez; + + interiorBox.maxExtents = interiorBox.minExtents; + + interiorBox.minExtents.x -= xrad; + interiorBox.minExtents.y -= yrad; + interiorBox.minExtents.z -= zrad; + + interiorBox.maxExtents.x += xrad; + interiorBox.maxExtents.y += yrad; + interiorBox.maxExtents.z += zrad; + + U32 waterMark = FrameAllocator::getWaterMark(); + + U16* hulls = (U16*)FrameAllocator::alloc(mConvexHulls.size() * sizeof(U16)); + U32 numHulls = 0; + + getIntersectingHulls(interiorBox,hulls, &numHulls); + + if (numHulls == 0) + { + FrameAllocator::setWaterMark(waterMark); + return false; + } + + // we've found all the hulls that intersect the lists interior space bounding box... + // now cull out those hulls which don't intersect the oriented bounding box... + + Point3F radii = testBox.maxExtents - testBox.minExtents; + radii *= 0.5f; + radii.x *= invScalex; + radii.y *= invScaley; + radii.z *= invScalez; + + // adjust toItr transform so that origin of source space is box center + // Note: center of interior box will be = to transformed center of testBox + Point3F center = interiorBox.minExtents + interiorBox.maxExtents; + center *= 0.5f; + toItr.setColumn(3,center); // (0,0,0) now goes where box center used to... + + for (S32 i=0; ibegin(0, rSurface.planeIndex); + for (U32 k = 0; k < rSurface.windingCount; k++) + { + array[k] = list->addPoint(mPoints[mWindings[rSurface.windingStart + k]].point); + list->vertex(array[k]); + } + + list->plane(getFlippedPlane(rSurface.planeIndex)); + list->end(); + } + else + { + const Interior::Surface& rSurface = mSurfaces[surfaceIndex]; + U32 array[32]; + U32 fanVerts[32]; + U32 numVerts; + + collisionFanFromSurface(rSurface, fanVerts, &numVerts); + + list->begin(0, rSurface.planeIndex); + for (U32 k = 0; k < numVerts; k++) + { + array[k] = list->addPoint(mPoints[fanVerts[k]].point); + list->vertex(array[k]); + } + list->plane(getFlippedPlane(rSurface.planeIndex)); + list->end(); + } + } + } + + FrameAllocator::setWaterMark(waterMark); + return !list->isEmpty(); +} + +bool Interior::buildLightPolyList(U32* lightSurfaces, + U32* numLightSurfaces, + const Box3F& box, + const MatrixF& /*transform*/, + const Point3F& /*scale*/) +{ + static InteriorPolytope pTope; + pTope.buildBox(box, MatrixF(true), Point3F(1.0f, 1.0f, 1.0f)); + + static SurfaceHash hash; + hash.clear(); + buildPolyList_r(pTope, hash); + + for (U32 i = 0; i < hash.mNumSurfaces; i++) + { + U32 surfaceIndex = hash.mSurfaceArray[i]; + if (!isNullSurfaceIndex(surfaceIndex)) + { + lightSurfaces[*numLightSurfaces] = surfaceIndex; + (*numLightSurfaces)++; + } + } + + return *numLightSurfaces != 0; +} + + +//--------------------------------------------------------------------------------- +//-------------------------------------- Zone scan. Not really collision, but hey. +// +struct Edge +{ + U16 p1; + U16 p2; + Edge(U16 o, U16 t) : p1(o), p2(t) { } +}; + +struct EdgeList +{ + + Vector points; + Vector edges; +}; + +void Interior::scanZoneNew(InteriorPolytope& polytope, + U16* zones, + U32* numZones) +{ + PROFILE_START(InteriorScanZoneNew); + // Submit the first volume of the poly tope to the bsp tree + static InteriorPolytope::VolumeStack stack; + stack.reserve(128); + stack.setSize(1); + stack.last().edgeList = polytope.mVolumeList[0].edgeList; + stack.last().nodeIndex = 0; + stack.last().popCount = 0; + + static Vector collPlanes; + collPlanes.reserve(64); + collPlanes.clear(); + + while (stack.empty() == false) + { + + InteriorPolytope::StackElement volume = stack.last(); + stack.pop_back(); + + if (isBSPLeafIndex(volume.nodeIndex)) + { + if (isBSPEmptyLeaf(volume.nodeIndex)) + { + U16 zone = getBSPEmptyLeafZone(volume.nodeIndex); + if (zone != 0x0FFF) + { + bool insert = true; + for (U32 i = 0; i < *numZones; i++) + { + if (zones[i] == zone) + { + insert = false; + break; + } + } + if (insert) + { + zones[*numZones] = zone; + (*numZones)++; + } + } + } + + if (volume.popCount) + for (U32 i = 0; i < volume.popCount; i++) + collPlanes.pop_back(); + + continue; + } + + const IBSPNode& rNode = mBSPNodes[volume.nodeIndex]; + if ((rNode.terminalZone & U16(0x8000)) != 0) + { + // Hah! we don't need to search any further + U16 zone = rNode.terminalZone & (~0x8000); + if (zone != 0x7FFF && zone != 0x0FFF) + { + bool insert = true; + for (U32 i = 0; i < *numZones; i++) + { + if (zones[i] == zone) + { + insert = false; + break; + } + } + if (insert) + { + zones[*numZones] = zone; + (*numZones)++; + } + } + + if (volume.popCount) + for (U32 i = 0; i < volume.popCount; i++) + collPlanes.pop_back(); + + continue; + } + + // New front and back faces + polytope.mFaceList.increment(2); + InteriorPolytope::Face& frontFace = polytope.mFaceList.last(); + InteriorPolytope::Face& backFace = *(&frontFace - 1); + + backFace.original = frontFace.original = false; + backFace.vertex = frontFace.vertex = 0; + + // New front and back volumes + InteriorPolytope::StackElement frontVolume,backVolume; + frontVolume.edgeList = backVolume.edgeList = -1; + + PlaneF plane = getFlippedPlane(rNode.planeIndex); + + S32 startVertex = polytope.mVertexList.size(); + + // Test & clip all the edges + S32 sideBase = ++polytope.sideCount << 1; + AssertFatal(sideBase != 0, "Well, crap."); + for (S32 i = volume.edgeList; i >= 0; i = polytope.mEdgeList[i].next) + { + // Copy into tmp first to avoid problems with the array. + InteriorPolytope::Edge edge = polytope.mEdgeList[i]; + + InteriorPolytope::Vertex& v0 = polytope.mVertexList[edge.vertex[0]]; + if (v0.side < sideBase) + v0.side = sideBase + ((plane.distToPlane(v0.point) >= 0)? 0: 1); + InteriorPolytope::Vertex& v1 = polytope.mVertexList[edge.vertex[1]]; + if (v1.side < sideBase) + v1.side = sideBase + ((plane.distToPlane(v1.point) >= 0)? 0: 1); + + if (v0.side != v1.side) + { + S32 s = v0.side - sideBase; + polytope.intersect(plane,v0.point,v1.point); + + // Split the edge into each volume + polytope.mEdgeList.increment(2); + InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); + e0.next = frontVolume.edgeList; + frontVolume.edgeList = polytope.mEdgeList.size() - 1; + + InteriorPolytope::Edge& e1 = *(&e0 - 1); + e1.next = backVolume.edgeList; + backVolume.edgeList = frontVolume.edgeList - 1; + + e0.vertex[0] = edge.vertex[s]; + e1.vertex[0] = edge.vertex[s ^ 1]; + e0.vertex[1] = e1.vertex[1] = polytope.mVertexList.size() - 1; + e0.face[0] = e1.face[0] = edge.face[0]; + e0.face[1] = e1.face[1] = edge.face[1]; + + // Add new edges on the plane, one to each volume + for (S32 f = 0; f < 2; f++) + { + InteriorPolytope::Face& face = polytope.mFaceList[edge.face[f]]; + if (face.vertex < startVertex) + { + face.vertex = polytope.mVertexList.size() - 1; + } + else + { + polytope.mEdgeList.increment(2); + InteriorPolytope::Edge& e0 = polytope.mEdgeList.last(); + e0.next = frontVolume.edgeList; + frontVolume.edgeList = polytope.mEdgeList.size() - 1; + + InteriorPolytope::Edge& e1 = *(&e0 - 1); + e1.next = backVolume.edgeList; + backVolume.edgeList = frontVolume.edgeList - 1; + + e1.vertex[0] = e0.vertex[0] = face.vertex; + e1.vertex[1] = e0.vertex[1] = polytope.mVertexList.size() - 1; + e1.face[0] = e0.face[0] = edge.face[f]; + e1.face[1] = polytope.mFaceList.size() - 1; + e0.face[1] = e1.face[1] - 1; + } + } + } + else + { + if (v0.side == sideBase) + { + polytope.mEdgeList.push_back(edge); + InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); + ne.next = frontVolume.edgeList; + frontVolume.edgeList = polytope.mEdgeList.size() - 1; + } + else + { + polytope.mEdgeList.push_back(edge); + InteriorPolytope::Edge& ne = polytope.mEdgeList.last(); + ne.next = backVolume.edgeList; + backVolume.edgeList = polytope.mEdgeList.size() - 1; + } + } + } + + // Push the front and back nodes + if (frontVolume.edgeList >= 0 && backVolume.edgeList >= 0) + { + collPlanes.push_back(rNode.planeIndex); + + frontVolume.nodeIndex = rNode.frontIndex; + frontVolume.popCount = volume.popCount + 1; + stack.push_back(frontVolume); + + backVolume.nodeIndex = rNode.backIndex; + backVolume.popCount = 0; + stack.push_back(backVolume); + } + else if (frontVolume.edgeList >= 0) + { + frontVolume.nodeIndex = rNode.frontIndex; + frontVolume.popCount = volume.popCount; + stack.push_back(frontVolume); + } + else if (backVolume.edgeList >= 0) + { + backVolume.nodeIndex = rNode.backIndex; + backVolume.popCount = volume.popCount; + stack.push_back(backVolume); + } + else + { + // Pop off our own planes... + if (volume.popCount) + for (U32 i = 0; i < volume.popCount; i++) + collPlanes.pop_back(); + } + } + + AssertFatal(collPlanes.size() == 0, "Unbalanced stack!"); + PROFILE_END(); +} + +void Interior::scanZone_r(const U32 node, + const Point3F& center, + const Point3F& axisx, + const Point3F& axisy, + const Point3F& axisz, + U16* zones, + U32* numZones) +{ + if (isBSPLeafIndex(node) == false) + { + const IBSPNode& rNode = mBSPNodes[node]; + const PlaneF& rPlane = getPlane(rNode.planeIndex); + + PlaneF::Side side = rPlane.whichSideBox(center, axisx, axisy, axisz, Point3F(0.0f, 0.0f, 0.0f)); + if (planeIsFlipped(rNode.planeIndex)) + side = PlaneF::Side(-S32(side)); + + switch (side) + { + case PlaneF::Front: + scanZone_r(rNode.frontIndex, center, axisx, axisy, axisz, zones, numZones); + break; + + case PlaneF::Back: + scanZone_r(rNode.backIndex, center, axisx, axisy, axisz, zones, numZones); + break; + + case PlaneF::On: + scanZone_r(rNode.frontIndex, center, axisx, axisy, axisz, zones, numZones); + scanZone_r(rNode.backIndex, center, axisx, axisy, axisz, zones, numZones); + break; + + default: + AssertFatal(false, "Misunderstood switchCode in Interior::zoneRay_r"); + } + + return; + } + + if (isBSPEmptyLeaf(node)) + { + U16 zone = getBSPEmptyLeafZone(node); + if (zone != 0x0FFF) + { + for (U32 i = 0; i < *numZones; i++) + { + if (zones[i] == zone) + return; + } + + zones[*numZones] = zone; + (*numZones)++; + } + } +} + +bool Interior::scanZones(const Box3F& box, + const MatrixF& transform, + U16* zones, + U32* numZones) +{ + // We don't need an exact answer, so let's just blast a box through + // the planes and see what we intersect. scanZoneNew is good if you + // have an exact volume to clip. + + Point3F center; + box.getCenter(¢er); + + Point3F xRad((box.maxExtents.x - box.minExtents.x) * 0.5f, 0.0f, 0.0f); + Point3F yRad(0.0f, (box.maxExtents.y - box.minExtents.y) * 0.5f, 0.0f); + Point3F zRad(0.0f, 0.0f, (box.maxExtents.z - box.minExtents.z) * 0.5f); + + transform.mulP(center); + transform.mulV(xRad); + transform.mulV(yRad); + transform.mulV(zRad); + + scanZone_r(0, center, xRad, yRad, zRad, zones, numZones); + + bool outsideToo = false; + if (*numZones != 0) + { + for (U32 i = 0; i < *numZones; /**/) + { + if (zones[i]) + { + i++; + continue; + } + + outsideToo = true; + + zones[i] = zones[(*numZones) - 1]; + (*numZones)--; + } + } + else + { + // If it ain't in us, it's outside us. + outsideToo = true; + } + + return outsideToo; +} + + +bool Interior::getIntersectingHulls(const Box3F& query, U16* hulls, U32* numHulls) +{ + AssertFatal(*numHulls == 0, "Error, some stuff in the hull vector already!"); + + // This is paranoia, and I probably wouldn't do it if the tag was 32 bits, but + // a possible collision every 65k searches is just a little too small for comfort + // DMM + if (mSearchTag == 0) + { + for (U32 i = 0; i < mConvexHulls.size(); i++) + mConvexHulls[i].searchTag = 0; + mSearchTag = 1; + } + else + { + mSearchTag++; + } + + F32 xBinSize = mBoundingBox.len_x() / F32(NumCoordBins); + F32 yBinSize = mBoundingBox.len_y() / F32(NumCoordBins); + + F32 queryMinX = getMax(mBoundingBox.minExtents.x, query.minExtents.x); + F32 queryMinY = getMax(mBoundingBox.minExtents.y, query.minExtents.y); + F32 queryMaxX = getMin(mBoundingBox.maxExtents.x, query.maxExtents.x); + F32 queryMaxY = getMin(mBoundingBox.maxExtents.y, query.maxExtents.y); + + S32 startX = S32(mFloor((queryMinX - mBoundingBox.minExtents.x) / xBinSize)); + S32 endX = S32( mCeil((queryMaxX - mBoundingBox.minExtents.x) / xBinSize)); + S32 startY = S32(mFloor((queryMinY - mBoundingBox.minExtents.y) / yBinSize)); + S32 endY = S32( mCeil((queryMaxY - mBoundingBox.minExtents.y) / yBinSize)); + + AssertFatal(startX >= 0, "Error, bad startx"); + AssertFatal(startY >= 0, "Error, bad starty"); + AssertFatal(endX <= NumCoordBins, "Error, bad endX"); + AssertFatal(endY <= NumCoordBins, "Error, bad endY"); + + // Handle non-debug case + startX = getMax(startX, 0); + endX = getMin(endX, NumCoordBins); + startY = getMax(startY, 0); + endY = getMin(endY, NumCoordBins); + + for (S32 i = startX; i < endX; i++) + { + for (S32 j = startY; j < endY; j++) + { + const CoordBin& rBin = mCoordBins[i * NumCoordBins + j]; + for (U32 k = rBin.binStart; k < rBin.binStart + rBin.binCount; k++) + { + U16 hullIndex = mCoordBinIndices[k]; + ConvexHull& rHull = mConvexHulls[hullIndex]; + + // Don't check twice! (We're not Santa Claus.) + if (rHull.searchTag == mSearchTag) + continue; + rHull.searchTag = mSearchTag; + + Box3F qb(rHull.minX, rHull.minY, rHull.minZ, rHull.maxX, rHull.maxY, rHull.maxZ); + if (query.isOverlapped(qb)) + { + hulls[*numHulls] = hullIndex; + (*numHulls)++; + } + } + } + } + + return *numHulls != 0; +} + + +bool Interior::getIntersectingVehicleHulls(const Box3F& query, U16* hulls, U32* numHulls) +{ + AssertFatal(*numHulls == 0, "Error, some stuff in the hull vector already!"); + + for (U16 i = 0; i < mVehicleConvexHulls.size(); i++) + { + ConvexHull& rHull = mVehicleConvexHulls[i]; + Box3F qb(rHull.minX, rHull.minY, rHull.minZ, rHull.maxX, rHull.maxY, rHull.maxZ); + if (query.isOverlapped(qb)) + { + hulls[*numHulls] = i; + (*numHulls)++; + } + } + + return *numHulls != 0; +} + +//-------------------------------------------------------------------------- +Box3F InteriorConvex::getBoundingBox() const +{ + return getBoundingBox(mObject->getTransform(), mObject->getScale()); +} + +Box3F InteriorConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const +{ + Box3F newBox = box; + newBox.minExtents.convolve(scale); + newBox.maxExtents.convolve(scale); + mat.mul(newBox); + return newBox; +} + +Point3F InteriorConvex::support(const VectorF& v) const +{ + FrameAllocatorMarker fam; + + if (hullId >= 0) + { + AssertFatal(hullId < pInterior->mConvexHulls.size(), "Out of bounds hull!"); + + const Interior::ConvexHull& rHull = pInterior->mConvexHulls[hullId]; + + F32* pDots = (F32*)fam.alloc(sizeof(F32) * rHull.hullCount); + m_point3F_bulk_dot_indexed(&v.x, + &pInterior->mPoints[0].point.x, + rHull.hullCount, + sizeof(ItrPaddedPoint), + &pInterior->mHullIndices[rHull.hullStart], + pDots); + + U32 index = 0; + for (U32 i = 1; i < rHull.hullCount; i++) + { + if (pDots[i] > pDots[index]) + index = i; + } + + return pInterior->mPoints[pInterior->mHullIndices[rHull.hullStart + index]].point; + } + else + { + S32 actualId = -(hullId + 1); + AssertFatal(actualId < pInterior->mVehicleConvexHulls.size(), "Out of bounds hull!"); + + const Interior::ConvexHull& rHull = pInterior->mVehicleConvexHulls[actualId]; + + F32* pDots = (F32*)fam.alloc(sizeof(F32) * rHull.hullCount); + m_point3F_bulk_dot_indexed(&v.x, + &pInterior->mVehiclePoints[0].point.x, + rHull.hullCount, + sizeof(ItrPaddedPoint), + &pInterior->mVehicleHullIndices[rHull.hullStart], + pDots); + + U32 index = 0; + for (U32 i = 1; i < rHull.hullCount; i++) + { + if (pDots[i] > pDots[index]) + index = i; + } + + return pInterior->mVehiclePoints[pInterior->mVehicleHullIndices[rHull.hullStart + index]].point; + } +} + + +void InteriorConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf) +{ + S32 i; + + cf->material = 0; + cf->object = mObject; + + if (hullId >= 0) + { + // We find the support ourselves here since we want the index too... + const Interior::ConvexHull& rHull = pInterior->mConvexHulls[hullId]; + U32 spIndex = 0; + F32 maxSp = mDot(pInterior->mPoints[pInterior->mHullIndices[rHull.hullStart + spIndex]].point, n); + + for (i = 1; i < rHull.hullCount; i++) + { + U32 index = pInterior->mHullIndices[rHull.hullStart + i]; + const Point3F& rPoint = pInterior->mPoints[index].point; + + F32 dot = mDot(rPoint, n); + if (dot > maxSp) + { + spIndex = i; + maxSp = dot; + } + } + + // Ok, now we have the support point, let's extract the emission string for this + // vertex... + U32 currPos = 0; + const U8* pString = &pInterior->mConvexHullEmitStrings[pInterior->mHullEmitStringIndices[rHull.hullStart + spIndex]]; + + FrameAllocatorMarker fam; + U32* pRemaps = (U32*)fam.alloc(256 * sizeof(U32)); + + // Ok, this is a piece of cake. Lets dump the points first... + U32 numPoints = pString[currPos++]; + for (i = 0; i < numPoints; i++) + { + U32 index = pString[currPos++]; + + pRemaps[i] = cf->mVertexList.size(); + cf->mVertexList.increment(); + + const Point3F& rPoint = pInterior->mPoints[pInterior->mHullIndices[rHull.hullStart + index]].point; + mat.mulP(rPoint, &cf->mVertexList.last()); + } + + // Then the edges... + U32 numEdges = pString[currPos++]; + for (i = 0; i < numEdges; i++) + { + U32 index0 = pString[currPos++]; + U32 index1 = pString[currPos++]; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = pRemaps[index0]; + cf->mEdgeList.last().vertex[1] = pRemaps[index1]; + } + + // Then the polys... + U32 numPolys = pString[currPos++]; + for (i = 0; i < numPolys; i++) + { + U32 vertexCount = pString[currPos++]; + U32 planeIndex = pString[currPos++]; + + U32 verts[3]; + verts[0] = pString[currPos++]; + verts[1] = pString[currPos++]; + + AssertFatal( verts[0] < numPoints, "InteriorConvex::getFeatures verts out of range" ); + + for (U32 j = 2; j < vertexCount; j++) + { + verts[2] = pString[currPos++]; + + // Emit this poly + cf->mFaceList.increment(); + cf->mFaceList.last().normal = pInterior->getPlane(pInterior->mHullPlaneIndices[rHull.planeStart + planeIndex]); + + AssertFatal( verts[1] < numPoints, "InteriorConvex::getFeatures verts out of range" ); + AssertFatal( verts[2] < numPoints, "InteriorConvex::getFeatures verts out of range" ); + + cf->mFaceList.last().vertex[0] = pRemaps[verts[0]]; + cf->mFaceList.last().vertex[1] = pRemaps[verts[1]]; + cf->mFaceList.last().vertex[2] = pRemaps[verts[2]]; + PlaneF plane(cf->mVertexList[pRemaps[verts[0]]], + cf->mVertexList[pRemaps[verts[1]]], + cf->mVertexList[pRemaps[verts[2]]]); + cf->mFaceList.last().normal = plane; + + // Shift the fan over + verts[1] = verts[2]; + } + } + } + else + { + S32 actualId = -(hullId + 1); + // We find the support ourselves here since we want the index too... + const Interior::ConvexHull& rHull = pInterior->mVehicleConvexHulls[actualId]; + U32 spIndex = 0; + F32 maxSp = mDot(pInterior->mVehiclePoints[pInterior->mVehicleHullIndices[rHull.hullStart + spIndex]].point, n); + + for (i = 1; i < rHull.hullCount; i++) + { + U32 index = pInterior->mVehicleHullIndices[rHull.hullStart + i]; + const Point3F& rPoint = pInterior->mVehiclePoints[index].point; + + F32 dot = mDot(rPoint, n); + if (dot > maxSp) + { + spIndex = i; + maxSp = dot; + } + } + + // Ok, now we have the support point, let's extract the emission string for this + // vertex... + U32 currPos = 0; + const U8* pString = &pInterior->mVehicleConvexHullEmitStrings[ + pInterior->mVehicleHullEmitStringIndices[rHull.hullStart + spIndex] + ]; + + FrameAllocatorMarker fam; + U32* pRemaps = (U32*)fam.alloc(256 * sizeof(U32)); + + // Ok, this is a piece of cake. Lets dump the points first... + U32 numPoints = pString[currPos++]; + for (i = 0; i < numPoints; i++) + { + U32 index = pString[currPos++]; + + pRemaps[i] = cf->mVertexList.size(); + cf->mVertexList.increment(); + + const Point3F& rPoint = pInterior->mVehiclePoints[pInterior->mVehicleHullIndices[rHull.hullStart + index]].point; + mat.mulP(rPoint, &cf->mVertexList.last()); + } + + // Then the edges... + U32 numEdges = pString[currPos++]; + for (i = 0; i < numEdges; i++) + { + U32 index0 = pString[currPos++]; + U32 index1 = pString[currPos++]; + + cf->mEdgeList.increment(); + cf->mEdgeList.last().vertex[0] = pRemaps[index0]; + cf->mEdgeList.last().vertex[1] = pRemaps[index1]; + } + + // Then the polys... + U32 numPolys = pString[currPos++]; + for (i = 0; i < numPolys; i++) + { + U32 vertexCount = pString[currPos++]; + U32 planeIndex = pString[currPos++]; + + U32 verts[3]; + verts[0] = pString[currPos++]; + verts[1] = pString[currPos++]; + + for (U32 j = 2; j < vertexCount; j++) + { + verts[2] = pString[currPos++]; + + // Emit this poly + cf->mFaceList.increment(); + + cf->mFaceList.last().vertex[0] = pRemaps[verts[0]]; + cf->mFaceList.last().vertex[1] = pRemaps[verts[1]]; + cf->mFaceList.last().vertex[2] = pRemaps[verts[2]]; + cf->mFaceList.last().normal = pInterior->getPlane(planeIndex); + + // Shift the fan over + verts[1] = verts[2]; + } + } + } +} + +void InteriorConvex::getPolyList(AbstractPolyList* list) +{ + // Setup collision state data + { + list->setTransform(&mObject->getTransform(), mObject->getScale()); + list->setObject(mObject); + } + + if (hullId >= 0) + { + // Get our hull + const Interior::ConvexHull& rHull = pInterior->mConvexHulls[hullId]; + + // Build up the lists of points and strings + const U8* pString = &pInterior->mPolyListStrings[rHull.polyListStringStart]; + + U32 currPos = 0; + U32 numPlanes = pString[currPos++]; + + // It can happen that a hull has no collision surfaces. In that case, just bail out + // here... + if (numPlanes == 0) + return; + + const U8* planeString = &pString[currPos]; + currPos += numPlanes; + + U32 numPoints = pString[currPos++] << 8; + numPoints |= pString[currPos++]; + const U8* pointString = &pString[currPos]; + currPos += numPoints; + + U32 numSurfaces = pString[currPos++]; + + const U16* planeIndices = &pInterior->mPolyListPlanes[rHull.polyListPlaneStart]; + const U32* pointIndices = &pInterior->mPolyListPoints[rHull.polyListPointStart]; + + //-------------------------------------- + // At this point, currPos is pointing to the first surface in the string + //-------------------------------------- + + // First thing to do: build the interest mask, by seeing if the list is interested + // in our planes... + U8 interestMask = 0; + U32 remappedPlaneBase; + { + U16 planeIndex = planeIndices[0]; + PlaneF plane = pInterior->getPlane(planeIndex); + if (Interior::planeIsFlipped(planeIndex)) + plane.neg(); + + remappedPlaneBase = list->addPlane(plane); + + if (list->isInterestedInPlane(remappedPlaneBase)) + interestMask |= planeString[0]; + + for (U32 i = 1; i < numPlanes; i++) + { + planeIndex = planeIndices[i]; + plane = pInterior->getPlane(planeIndex); + if (Interior::planeIsFlipped(planeIndex)) + plane.neg(); + + list->addPlane(plane); + if (list->isInterestedInPlane(remappedPlaneBase + i)) + interestMask |= planeString[i]; + } + } + + // Now, whip through the points, and build up the remap table, adding only + // those points that the list is interested in. Note that we use the frameAllocator + // to get enoughMemory to deal with the variable sized remap array + FrameAllocatorMarker fam; + U32* pointRemapTable = reinterpret_cast(fam.alloc(numPoints * sizeof(U32))); + { + for (U32 i = 0; i < numPoints; i++) + { + if ((interestMask & pointString[i]) != 0) + { + const Point3F& rPoint = pInterior->mPoints[pointIndices[i]].point; + pointRemapTable[i] = list->addPoint(rPoint); + } + } + } + + // Now, whip through the surfaces, checking to make sure that we're interested in + // that poly as we go. At this point, currPos should point to the first surface. + // The format of the surface string can be found in interior.cc, in the + // processHullPolyLists function + { + for (U32 i = 0; i < numSurfaces; i++) + { + U32 snPoints = pString[currPos++]; + U32 sMask = pString[currPos++]; + U32 sPlane = pString[currPos++]; + + if ((interestMask & sMask) != 0) + { + // Add the poly + // + list->begin(0, planeIndices[sPlane]); + for (U32 j = 0; j < snPoints; j++) + { + U16 remappedIndex = pString[currPos++] << 8; + remappedIndex |= pString[currPos++]; + remappedIndex = pointRemapTable[remappedIndex]; + list->vertex(remappedIndex); + } + list->plane(remappedPlaneBase + sPlane); + + list->end(); + } + else + { + // Superflous poly, just skip past the points + currPos += snPoints * 2; + } + } + } + } + else + { + S32 actualId = -(hullId + 1); + + // Get our hull + const Interior::ConvexHull& rHull = pInterior->mVehicleConvexHulls[actualId]; + + // Build up the lists of points and strings + const U8* pString = &pInterior->mVehiclePolyListStrings[rHull.polyListStringStart]; + U32 currPos = 0; + + U32 numPlanes = pString[currPos++]; + // It can happen that a hull has no collision surfaces. In that case, just bail out + // here... + if (numPlanes == 0) + return; + + const U8* planeString = &pString[currPos]; + currPos += numPlanes; + + U32 numPoints = pString[currPos++] << 8; + numPoints |= pString[currPos++]; + const U8* pointString = &pString[currPos]; + currPos += numPoints; + + U32 numSurfaces = pString[currPos++]; + + const U16* planeIndices = &pInterior->mVehiclePolyListPlanes[rHull.polyListPlaneStart]; + const U32* pointIndices = &pInterior->mVehiclePolyListPoints[rHull.polyListPointStart]; + + //-------------------------------------- + // At this point, currPos is pointing to the first surface in the string + //-------------------------------------- + + // First thing to do: build the interest mask, by seeing if the list is interested + // in our planes... + U8 interestMask = 0; + U32 remappedPlaneBase; + { + U16 planeIndex = planeIndices[0]; + PlaneF plane = pInterior->getPlane(planeIndex); + if (Interior::planeIsFlipped(planeIndex)) + plane.neg(); + + remappedPlaneBase = list->addPlane(plane); + + if (list->isInterestedInPlane(remappedPlaneBase)) + interestMask |= planeString[0]; + + for (U32 i = 1; i < numPlanes; i++) + { + planeIndex = planeIndices[i]; + plane = pInterior->getPlane(planeIndex); + if (Interior::planeIsFlipped(planeIndex)) + plane.neg(); + + list->addPlane(plane); + if (list->isInterestedInPlane(remappedPlaneBase + i)) + interestMask |= planeString[i]; + } + } + + // Now, whip through the points, and build up the remap table, adding only + // those points that the list is interested in. Note that we use the frameAllocator + // to get enoughMemory to deal with the variable sized remap array + FrameAllocatorMarker fam; + U32* pointRemapTable = reinterpret_cast(fam.alloc(numPoints * sizeof(U32))); + { + for (U32 i = 0; i < numPoints; i++) + { + if ((interestMask & pointString[i]) != 0) + { + const Point3F& rPoint = pInterior->mVehiclePoints[pointIndices[i]].point; + pointRemapTable[i] = list->addPoint(rPoint); + } + } + } + + // Now, whip through the surfaces, checking to make sure that we're interested in + // that poly as we go. At this point, currPos should point to the first surface. + // The format of the surface string can be found in interior.cc, in the + // processHullPolyLists function + { + for (U32 i = 0; i < numSurfaces; i++) + { + U32 snPoints = pString[currPos++]; + U32 sMask = pString[currPos++]; + U32 sPlane = pString[currPos++]; + + if ((interestMask & sMask) != 0) + { + // Add the poly + // + list->begin(0, planeIndices[sPlane]); + for (U32 j = 0; j < snPoints; j++) + { + U16 remappedIndex = pString[currPos++] << 8; + remappedIndex |= pString[currPos++]; + remappedIndex = pointRemapTable[remappedIndex]; + list->vertex(remappedIndex); + } + list->plane(remappedPlaneBase + sPlane); + + list->end(); + } + else + { + // Superflous poly, just skip past the points + currPos += snPoints * 2; + } + } + } + } +} + + diff --git a/interior/interiorDebug.cpp b/interior/interiorDebug.cpp new file mode 100644 index 0000000..676771d --- /dev/null +++ b/interior/interiorDebug.cpp @@ -0,0 +1,699 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef TORQUE_SHIPPING + +#include "interior/interior.h" +#include "interior/interiorInstance.h" +#include "console/console.h" +#include "core/color.h" +#include "math/mMatrix.h" +#include "gfx/bitmap/gBitmap.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxShader.h" +#include "materials/matInstance.h" +#include "materials/materialList.h" +#include "materials/shaderData.h" +#include "renderInstance/renderPassManager.h" +#include "shaderGen/shaderGenVars.h" + +static U8 interiorDebugColors[14][3] = +{ + { 0xFF, 0xFF, 0xFF }, + { 0x00, 0x00, 0xFF }, + { 0x00, 0xFF, 0x00 }, + { 0xFF, 0x00, 0x00 }, + { 0xFF, 0xFF, 0x00 }, + { 0xFF, 0x00, 0xFF }, + { 0x00, 0xFF, 0xFF }, + { 0x80, 0x80, 0x80 }, + { 0xFF, 0x80, 0x80 }, + { 0x80, 0xFF, 0x80 }, + { 0x80, 0x80, 0xFF }, + { 0x80, 0xFF, 0xFF }, + { 0xFF, 0x80, 0xFF }, + { 0xFF, 0x80, 0x80 } +}; + + +namespace +{ + +void lineLoopFromStrip(Vector& points, + Vector& windings, + U32 windingStart, + U32 windingCount) +{ + PrimBuild::begin(GFXLineStrip, windingCount + 1); + PrimBuild::vertex3fv(points[windings[windingStart]].point); + S32 skip = windingStart + 1; + while (skip < (windingStart + windingCount)) + { + PrimBuild::vertex3fv(points[windings[skip]].point); + skip += 2; + } + + skip -= 1; + while (skip > windingStart) + { + if (skip < (windingStart + windingCount)) + PrimBuild::vertex3fv(points[windings[skip]].point); + + skip -= 2; + } + PrimBuild::vertex3fv(points[windings[windingStart]].point); + PrimBuild::end(); +} + +void lineStrip(Vector& points, + Vector& windings, + U32 windingStart, + U32 windingCount) +{ + U32 end = 2; + + while (end < windingCount) + { + // Even + PrimBuild::begin(GFXLineStrip, 4); + PrimBuild::vertex3fv(points[windings[windingStart + end - 2]].point); + PrimBuild::vertex3fv(points[windings[windingStart + end - 1]].point); + PrimBuild::vertex3fv(points[windings[windingStart + end - 0]].point); + PrimBuild::vertex3fv(points[windings[windingStart + end - 2]].point); + PrimBuild::end(); + + end++; + if (end >= windingCount) + break; + + // Odd + PrimBuild::begin(GFXLineStrip, 4); + PrimBuild::vertex3fv(points[windings[windingStart + end - 1]].point); + PrimBuild::vertex3fv(points[windings[windingStart + end - 2]].point); + PrimBuild::vertex3fv(points[windings[windingStart + end - 0]].point); + PrimBuild::vertex3fv(points[windings[windingStart + end - 1]].point); + PrimBuild::end(); + + end++; + } +} + +} // namespace {} + +void Interior::debugRender(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst, MatrixF& modelview) +{ + // We use this shader to color things + if ( mDebugShader == NULL ) + { + ShaderData *shaderData = NULL; + AssertFatal( Sim::findObject( "_DebugInterior_", shaderData ), "Unable to find ShaderData _DebugInterior_" ); + + mDebugShader = shaderData ? shaderData->getShader() : NULL; + + if ( mDebugShader ) + { + mDebugShaderConsts = mDebugShader->allocConstBuffer(); + shaderData->mapSamplerNames( mDebugShaderConsts ); + mDebugShaderModelViewSC = mDebugShader->getShaderConstHandle(ShaderGenVars::modelview); + mDebugShaderShadeColorSC = mDebugShader->getShaderConstHandle("$shadeColor"); + } + } + + // We use this to override the texture that the interior defines. + if (mDebugTexture.isNull()) + { + // Allocate a small white bitmap + GBitmap temp(16, 16); + mDebugTexture.set(&temp, &GFXDefaultStaticDiffuseProfile, false, "Blank Texture"); + } + + // Set up our buffers + GFX->setVertexBuffer( mVertBuff ); + GFX->setPrimitiveBuffer( mPrimBuff ); + + // Set the modelview matrix for the shaders + if (mDebugShaderModelViewSC->isValid()) + mDebugShaderConsts->set(mDebugShaderModelViewSC, modelview); + + GFX->disableShaders(); + + switch (smRenderMode) + { + case NormalRenderLines: + debugNormalRenderLines(zoneVis); + break; + + case ShowDetail: + debugShowDetail(zoneVis); + break; + + case ShowAmbiguous: + debugShowAmbiguous(zoneVis); + break; + + case ShowLightmaps: + debugShowLightmaps(zoneVis, sgData, intInst); + break; + + case ShowPortalZones: + debugShowPortalZones(zoneVis); + break; + + case ShowCollisionFans: + debugShowCollisionFans(zoneVis); + break; + + case ShowOrphan: + debugShowOrphan(zoneVis); + break; + + case ShowStrips: + debugShowStrips(zoneVis); + break; + + case ShowTexturesOnly: + debugShowTexturesOnly(zoneVis, sgData, intInst); + break; + + case ShowNullSurfaces: + debugShowNullSurfaces(zoneVis, sgData, intInst); + break; + + case ShowLargeTextures: + debugShowLargeTextures(zoneVis, sgData, intInst); + break; + + case ShowOutsideVisible: + debugShowOutsideVisible(zoneVis); + break; + + case ShowHullSurfaces: + debugShowHullSurfaces(); + break; + + case ShowVehicleHullSurfaces: + debugShowVehicleHullSurfaces(zoneVis, sgData, intInst); + break; + + case ShowDetailLevel: + debugShowDetailLevel(zoneVis); + break; + + case ShowVertexColors: +// debugShowVertexColors(pMaterials); + break; + + default: + AssertWarn(false, "Warning! Misunderstood debug render mode. Defaulting to ShowDetail"); + debugShowDetail(zoneVis); + break; + } +} + +void Interior::preDebugRender() +{ + // Set up our rendering states. + if( mDebugShader ) + { + // Set our shader + GFX->setShader( mDebugShader ); + + // Set a "blank" texture + GFX->setTexture( 0, mDebugTexture ); + + // Set a state block to enable our texture + GFX->setStateBlock(mInteriorDebugTextureSB); + } +} + +void Interior::debugNormalRenderLines(const ZoneVisDeterminer& zoneVis) +{ + // Set our "base debug states" (no texture) + GFX->setStateBlock(mInteriorDebugNoneSB); + + for (U32 i = 0; i < mZones.size(); i++) + { + if (zoneVis.isZoneVisible(i) == false) + continue; + + for( U32 j=0; jisValid()) + mDebugShaderConsts->set(mDebugShaderShadeColorSC, col); + + GFX->setShaderConstBuffer(mDebugShaderConsts); + + GFXPrimitive* info = &rSurface.surfaceInfo; + GFX->drawIndexedPrimitive( info->type, info->startVertex, info->minIndex, info->numVertices, info->startIndex, info->numPrimitives ); + } + } + + GFX->disableShaders(); + debugNormalRenderLines(zoneVis); +} + +void Interior::debugShowDetail(const ZoneVisDeterminer& zoneVis) +{ + debugShowSurfaceFlag(zoneVis, SurfaceDetail, ColorF(1.0f, 0.0f, 0.0f)); +} + +void Interior::debugShowAmbiguous(const ZoneVisDeterminer& zoneVis) +{ + debugShowSurfaceFlag(zoneVis, SurfaceAmbiguous, ColorF(0.0f, 1.0f, 0.0f)); +} + +void Interior::debugShowOrphan(const ZoneVisDeterminer& zoneVis) +{ + debugShowSurfaceFlag(zoneVis, SurfaceOrphan, ColorF(0.0f, 0.0f, 1.0f)); +} + +void Interior::debugShowOutsideVisible(const ZoneVisDeterminer& zoneVis) +{ + debugShowSurfaceFlag(zoneVis, SurfaceOutsideVisible, ColorF(1.0f, 0.0f, 0.0f)); +} + +void Interior::debugShowPortalZones(const ZoneVisDeterminer& zoneVis) +{ + preDebugRender(); + + for (U32 i = 0; i < mZones.size(); i++) + { + U8* color; + if (i == 0) + color = interiorDebugColors[0]; + else + color = interiorDebugColors[(i % 13) + 1]; + + for (U32 j = mZones[i].surfaceStart; j < mZones[i].surfaceStart + mZones[i].surfaceCount; j++) + { + Surface& rSurface = mSurfaces[mZoneSurfaces[j]]; + ColorF c((F32) color[0] / 255.0f, (F32) color[1] / 255.0f, (F32) color[2] / 255.0f); + + if (mDebugShaderShadeColorSC->isValid()) + mDebugShaderConsts->set(mDebugShaderShadeColorSC, c); + + GFX->setShaderConstBuffer(mDebugShaderConsts); + + GFXPrimitive* info = &rSurface.surfaceInfo; + GFX->drawIndexedPrimitive( info->type, info->startVertex, info->minIndex, info->numVertices, info->startIndex, info->numPrimitives ); + } + } + + GFX->disableShaders(); + debugRenderPortals(); + debugNormalRenderLines(zoneVis); +} + +// Render portals +void Interior::debugRenderPortals() +{ + // Set our portal rendering state block + GFX->setStateBlock(mInteriorDebugPortalSB); + + for (U32 i = 0; i < mPortals.size(); i++) + { + const Portal& rPortal = mPortals[i]; + + for (U16 j = 0; j < rPortal.triFanCount; j++) + { + const TriFan& rFan = mWindingIndices[rPortal.triFanStart + j]; + U32 k; + + PrimBuild::color4f(0.75f, 0.5f, 0.75f, 0.45f); + PrimBuild::begin(GFXTriangleFan, rFan.windingCount); + + for (k = 0; k < rFan.windingCount; k++) + PrimBuild::vertex3fv(mPoints[mWindings[rFan.windingStart + k]].point); + + PrimBuild::end(); + + PrimBuild::color4f(0, 0, 1, 1); + PrimBuild::begin(GFXLineStrip, rFan.windingCount+1); + + for (k = 0; k < rFan.windingCount; k++) + PrimBuild::vertex3fv(mPoints[mWindings[rFan.windingStart + k]].point); + + PrimBuild::vertex3fv(mPoints[mWindings[rFan.windingStart]].point); + + PrimBuild::end(); + } + } +} + +void Interior::debugShowCollisionFans(const ZoneVisDeterminer& zoneVis) +{ + // Set our "base debug states" (no texture) + GFX->setStateBlock(mInteriorDebugNoneSB); + + for (U32 i = 0; i < mZones.size(); i++) + { + if (zoneVis.isZoneVisible(i) == false) + continue; + + for( U32 j=0; j 0) + PrimBuild::vertex3fv(mPoints[fanIndices[0]].point); + PrimBuild::end(); + + // Normal + PrimBuild::color3f(1, 0, 0); + PrimBuild::begin(GFXLineList, numIndices * 2); + for (U32 j = 0; j < numIndices; j++) + { + Point3F up = mPoints[fanIndices[j]].point; + Point3F norm = getPlane(rSurface.planeIndex); + if (planeIsFlipped(rSurface.planeIndex)) + up -= norm * 0.4f; + else + up += norm * 0.4f; + + PrimBuild::vertex3fv(mPoints[fanIndices[j]].point); + PrimBuild::vertex3fv(up); + } + PrimBuild::end(); + } + } +} + +// This doesn't show strip (they don't go to the card that way) +// But it does show the batches of primitives we send. +void Interior::debugShowStrips(const ZoneVisDeterminer& zoneVis) +{ + // Set up our rendering states. + preDebugRender(); + + for (U32 i = 0; i < mZones.size(); i++) + { + if (zoneVis.isZoneVisible(i) == false) + continue; + + for( U32 j=0; jisValid()) + mDebugShaderConsts->set(mDebugShaderShadeColorSC, col); + + GFX->setShaderConstBuffer(mDebugShaderConsts); + + GFX->drawPrimitive(node.primInfoIndex); + } + } + + GFX->disableShaders(); + debugNormalRenderLines(zoneVis); +} + +void Interior::debugShowDetailLevel(const ZoneVisDeterminer& zoneVis) +{ + // Set up our rendering states. + preDebugRender(); + + U32 index = getDetailLevel(); + ColorF col((F32)interiorDebugColors[index][0] / 255.0f, (F32)interiorDebugColors[index][1] / 255.0f, + (F32)interiorDebugColors[index][2] / 255.0f); + + if (mDebugShaderShadeColorSC->isValid()) + mDebugShaderConsts->set(mDebugShaderShadeColorSC, col); + + GFX->setShaderConstBuffer(mDebugShaderConsts); + + for (U32 i = 0; i < mZones.size(); i++) + { + if (zoneVis.isZoneVisible(i) == false) + continue; + + for( U32 j=0; jdrawPrimitive(node.primInfoIndex); + } + } + + GFX->disableShaders(); + debugNormalRenderLines(zoneVis); +} + +void Interior::debugShowHullSurfaces() +{ + // Set our "base debug states" (no texture) + GFX->setStateBlock(mInteriorDebugNoneSB); + + for (U32 i = 0; i < mConvexHulls.size(); i++) + { + const ConvexHull& rHull = mConvexHulls[i]; + + for (U32 j = rHull.surfaceStart; j < rHull.surfaceCount + rHull.surfaceStart; j++) + { + U32 index = mHullSurfaceIndices[j]; + + if (!isNullSurfaceIndex(index)) + { + const Interior::Surface& rSurface = mSurfaces[index]; + U32 fanVerts[32]; + U32 numVerts; + collisionFanFromSurface(rSurface, fanVerts, &numVerts); + + PrimBuild::color3i(interiorDebugColors[(i%13)+1][0], interiorDebugColors[(i%13)+1][1], + interiorDebugColors[(i%13)+1][2]); + Point3F center(0, 0, 0); + PrimBuild::begin(GFXTriangleFan, numVerts); + for (U32 k = 0; k < numVerts; k++) + { + PrimBuild::vertex3fv(mPoints[fanVerts[k]].point); + center += mPoints[fanVerts[k]].point; + } + PrimBuild::end(); + center /= F32(numVerts); + PrimBuild::color3f(0, 0, 0); + lineLoopFromStrip(mPoints, mWindings, rSurface.windingStart, rSurface.windingCount); + + PlaneF plane; + plane.set(mPoints[fanVerts[0]].point, mPoints[fanVerts[1]].point, mPoints[fanVerts[2]].point); + PrimBuild::begin(GFXLineList, 2); + PrimBuild::vertex3fv(center); + PrimBuild::vertex3fv(center + (plane * 0.25)); + PrimBuild::end(); + } + } + } +} + +void Interior::debugDefaultRender(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst) +{ + // Set up a state block with two textures enabled + GFX->setStateBlock(mInteriorDebugTwoTextureSB); + + for( U32 i=0; igetLMHandle(), node.lightMapIndex ); + + GFX->setTexture( 0, mMaterialList->getMaterial( curBaseTexIndex )); + GFX->setTexture(1, sgData.lightmap); + GFX->drawPrimitive( node.primInfoIndex ); + } + } +} + +void Interior::debugShowNullSurfaces(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst) +{ + debugDefaultRender(zoneVis, sgData, intInst); + + PrimBuild::color3f(1.0f, 0.0f, 0.0f); + for (U32 i = 0; i < mNullSurfaces.size(); i++) + { + const NullSurface& rSurface = mNullSurfaces[i]; + + PrimBuild::begin(GFXTriangleFan, rSurface.windingCount); + for (U32 k = 0; k < rSurface.windingCount; k++) + { + PrimBuild::vertex3fv(mPoints[mWindings[rSurface.windingStart+k]].point); + } + PrimBuild::end(); + } +} + +void Interior::debugShowVehicleHullSurfaces(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, + InteriorInstance *intInst) +{ + debugDefaultRender(zoneVis, sgData, intInst); + + PrimBuild::color3f(1.0f, 0.0f, 0.0f); + for (U32 i = 0; i < mVehicleNullSurfaces.size(); i++) + { + const NullSurface& rSurface = mNullSurfaces[i]; + PrimBuild::begin(GFXTriangleFan, rSurface.windingCount); + for (U32 k = 0; k < rSurface.windingCount; k++) + { + PrimBuild::vertex3fv(mPoints[mWindings[rSurface.windingStart+k]].point); + } + } +} + +void Interior::debugShowTexturesOnly(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, + InteriorInstance *intInst) +{ + // Set up a state block with one texture unit enabled + GFX->setStateBlock(mInteriorDebugTextureSB); + + for( U32 i=0; isetTexture( 0, mMaterialList->getMaterial( curBaseTexIndex )); + GFX->drawPrimitive( node.primInfoIndex ); + } + } +} + +void Interior::debugShowLargeTextures(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, + InteriorInstance *intInst) +{ + preDebugRender(); + + for( U32 i=0; igetMaterial( curBaseTexIndex ); + ColorF texSizeColor(1.0f, 1.0f, 1.0f, 1.0f); + if (t) + { + U32 width = t.getWidth(); + U32 height = t.getHeight(); + + if (width <= 256 && height <= 256) + texSizeColor = ColorF(0.25f, 0.25f, 1.0f); // small texture + else if (width <= 512 && height <= 512) + texSizeColor = ColorF(0.25f, 1.0f, 0.25f); // medium texture + else + texSizeColor = ColorF(1.0f, 0.25f, 0.25f); // large texture + } + + if (mDebugShaderShadeColorSC->isValid()) + mDebugShaderConsts->set(mDebugShaderShadeColorSC, texSizeColor); + + GFX->setShaderConstBuffer(mDebugShaderConsts); + + GFX->setTexture( 0, t); + GFX->drawPrimitive( node.primInfoIndex ); + } + } +} + +void Interior::debugShowLightmaps(const ZoneVisDeterminer& zoneVis, SceneGraphData &sgData, InteriorInstance *intInst) +{ + GFX->setTexture(0, mDebugTexture); + + // Set up a state block with two textures enabled + GFX->setStateBlock(mInteriorDebugTwoTextureSB); + + for( U32 i=0; igetLMHandle(), node.lightMapIndex ); + + GFX->setTexture(1, sgData.lightmap); + GFX->drawPrimitive( node.primInfoIndex ); + } + } +} + +#endif diff --git a/interior/interiorIO.cpp b/interior/interiorIO.cpp new file mode 100644 index 0000000..7b65068 --- /dev/null +++ b/interior/interiorIO.cpp @@ -0,0 +1,1652 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "core/bitVector.h" +#include "core/stream/stream.h" +#include "math/mathIO.h" +#include "gfx/bitmap/gBitmap.h" +#include "interior/interiorSubObject.h" +#include "console/console.h" +#include "core/frameAllocator.h" +#include "materials/materialList.h" + +int QSORT_CALLBACK cmpU32(const void* p1, const void* p2) +{ + return S32(*((U32*)p1)) - S32(*((U32*)p2)); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- PERSISTENCE IMPLEMENTATION +// +U32 Interior::smFileVersion = 14; + +bool Interior::read(Stream& stream) +{ + AssertFatal(stream.hasCapability(Stream::StreamRead), "Interior::read: non-read capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::read: Error, stream in inconsistent state"); + + S32 i; + + // Version this stream. We only load stream of the current version + U32 fileVersion; + stream.read(&fileVersion); + + // We need to store the version in case there is any post processing that + // needs to take place that is dependent on the file version + mFileVersion = fileVersion; + + if (fileVersion > smFileVersion) + { + Con::errorf(ConsoleLogEntry::General, "Interior::read: incompatible file version found."); + return false; + } + + // Geometry factors... + stream.read(&mDetailLevel); + + stream.read(&mMinPixels); + mathRead(stream, &mBoundingBox); + mathRead(stream, &mBoundingSphere); + stream.read(&mHasAlarmState); + stream.read(&mNumLightStateEntries); + + // Now read in our data vectors. + S32 vectorSize; + + // mPlanes + readPlaneVector(stream); + + // mPoints + stream.read(&vectorSize); + mPoints.setSize(vectorSize); + for (i = 0; i < mPoints.size(); i++) + mathRead(stream, &mPoints[i].point); + + // mPointVisibility + stream.read(&vectorSize); + mPointVisibility.setSize(vectorSize); + stream.read(vectorSize, mPointVisibility.address()); + + // mTexGenEQs + stream.read(&vectorSize); + mTexGenEQs.setSize(vectorSize); + for(i = 0; i < mTexGenEQs.size(); i++) + { + mathRead(stream, &mTexGenEQs[i].planeX); + mathRead(stream, &mTexGenEQs[i].planeY); + } + + // mBSPNodes; + stream.read(&vectorSize); + mBSPNodes.setSize(vectorSize); + for(i = 0; i < mBSPNodes.size(); i++) + { + stream.read(&mBSPNodes[i].planeIndex); + + if (fileVersion >= 14) + { + stream.read(&mBSPNodes[i].frontIndex); + stream.read(&mBSPNodes[i].backIndex); + } + else + { + U16 frontIndex, backIndex; + stream.read(&frontIndex); + stream.read(&backIndex); + + mBSPNodes[i].frontIndex = U32(frontIndex); + mBSPNodes[i].backIndex = U32(backIndex); + } + } + + // mBSPSolidLeaves + stream.read(&vectorSize); + mBSPSolidLeaves.setSize(vectorSize); + for(i = 0; i < mBSPSolidLeaves.size(); i++) + { + stream.read(&mBSPSolidLeaves[i].surfaceIndex); + stream.read(&mBSPSolidLeaves[i].surfaceCount); + } + + // MaterialList + if(mMaterialList != NULL) + delete mMaterialList; + mMaterialList = new MaterialList; + mMaterialList->read(stream); + + + // mWindings + stream.read(&vectorSize); + mWindings.setSize(vectorSize); + for(i = 0; i < mWindings.size(); i++) + { + stream.read(&mWindings[i]); + } + + // mWindingIndices + stream.read(&vectorSize); + mWindingIndices.setSize(vectorSize); + for(i = 0; i < mWindingIndices.size(); i++) + { + stream.read(&mWindingIndices[i].windingStart); + stream.read(&mWindingIndices[i].windingCount); + } + + // mEdges + if (fileVersion >= 12) + { + stream.read(&vectorSize); + + mEdges.setSize(vectorSize); + for (i = 0; i < mEdges.size(); i++) + { + stream.read(&mEdges[i].vertexes[0]); + stream.read(&mEdges[i].vertexes[1]); + stream.read(&mEdges[i].faces[0]); + stream.read(&mEdges[i].faces[1]); + } + } + + // mZones + stream.read(&vectorSize); + mZones.setSize(vectorSize); + for(i = 0; i < mZones.size(); i++) + { + stream.read(&mZones[i].portalStart); + stream.read(&mZones[i].portalCount); + stream.read(&mZones[i].surfaceStart); + stream.read(&mZones[i].surfaceCount); + + if (fileVersion >= 12) + { + stream.read(&mZones[i].staticMeshStart); + stream.read(&mZones[i].staticMeshCount); + } + else + { + mZones[i].staticMeshStart = 0; + mZones[i].staticMeshCount = 0; + } + + stream.read(&mZones[i].flags); + mZones[i].zoneId = 0; + } + + // Zone surfaces + stream.read(&vectorSize); + mZoneSurfaces.setSize(vectorSize); + for(i = 0; i < mZoneSurfaces.size(); i++) + stream.read(&mZoneSurfaces[i]); + + // Zone static meshes + if (fileVersion >= 12) + { + stream.read(&vectorSize); + + mZoneStaticMeshes.setSize(vectorSize); + for (i = 0; i < mZoneStaticMeshes.size(); i++) + stream.read(&mZoneStaticMeshes[i]); + } + + // mZonePortalList; + stream.read(&vectorSize); + mZonePortalList.setSize(vectorSize); + for(i = 0; i < mZonePortalList.size(); i++) + stream.read(&mZonePortalList[i]); + + // mPortals + stream.read(&vectorSize); + mPortals.setSize(vectorSize); + for(i = 0; i < mPortals.size(); i++) + { + stream.read(&mPortals[i].planeIndex); + stream.read(&mPortals[i].triFanCount); + stream.read(&mPortals[i].triFanStart); + stream.read(&mPortals[i].zoneFront); + stream.read(&mPortals[i].zoneBack); + } + + // mSurfaces + stream.read(&vectorSize); + mSurfaces.setSize(vectorSize); + mLMTexGenEQs.setSize(vectorSize); + + // Couple of hoops to *attempt* to detect that we are loading + // a TGE version 0 Interior and not a TGEA verison 0 + U32 surfacePos = stream.getPosition(); + bool tgeInterior = false; + + // First attempt to read this as though it isn't a TGE version 0 Interior + for(i = 0; i < mSurfaces.size(); i++) + { + // If we end up reading any invalid data in this loop then odds + // are that we are no longer correctly reading from the stream + // and have gotten off because this is a TGE version 0 Interior + + Surface& surface = mSurfaces[i]; + + if (readSurface(stream, surface, mLMTexGenEQs[i], false) == false) + { + tgeInterior = true; + break; + } + } + + // If this is a version 0 Interior and we failed to read it as a + // TGEA version 0 Interior then attempt to read it as a TGE version 0 + if (fileVersion == 0 && tgeInterior) + { + // Set our stream position back to the start of the surfaces + stream.setPosition(surfacePos); + + // Try reading in the surfaces again + for(i = 0; i < mSurfaces.size(); i++) + { + Surface& surface = mSurfaces[i]; + + // If we fail on any of the surfaces then bail + if (readSurface(stream, surface, mLMTexGenEQs[i], true) == false) + return false; + } + } + // If we failed to read but this isn't a version 0 Interior + // then something has gone horribly wrong + else if (fileVersion != 0 && tgeInterior) + return false; + + // Edges + if (fileVersion == 5) + { + stream.read(&vectorSize); + mEdges.setSize(vectorSize); + for (i = 0; i < mEdges.size(); i++) + { + stream.read(&mEdges[i].vertexes[0]); + stream.read(&mEdges[i].vertexes[1]); + U32 normals[2]; + stream.read(&normals[0]); + stream.read(&normals[1]); + + if (fileVersion > 2) // version 3 is where surface id's get added + { + stream.read(&mEdges[i].faces[0]); + stream.read(&mEdges[i].faces[1]); + } + } + } + + // mNormals + if (fileVersion == 5) + { + stream.read(&vectorSize); + Vector normals; + normals.setSize(vectorSize); + for(i = 0; i < normals.size(); i++) + mathRead(stream, &normals[i]); + + // mNormalIndices + stream.read(&vectorSize); + Vector normalIndices; + normalIndices.setSize(vectorSize); + for (i = 0; i < normalIndices.size(); i++) + stream.read(&normalIndices[i]); + } + + // NormalLMapIndices + stream.read(&vectorSize); + mNormalLMapIndices.setSize(vectorSize); + for (U32 i = 0; i < mNormalLMapIndices.size(); i++) + { + if (fileVersion >= 13) + stream.read(&mNormalLMapIndices[i]); + else + { + U8 index = 0; + stream.read(&index); + + mNormalLMapIndices[i] = (U32)index; + } + } + + // AlarmLMapIndices + stream.read(&vectorSize); + mAlarmLMapIndices.setSize(vectorSize); + for (U32 i = 0; i < mAlarmLMapIndices.size(); i++) + { + if (fileVersion >= 13) + stream.read(&mAlarmLMapIndices[i]); + else + { + U8 index = 0; + stream.read(&index); + + mAlarmLMapIndices[i] = (U32)index; + } + } + + // mNullSurfaces + stream.read(&vectorSize); + mNullSurfaces.setSize(vectorSize); + for(i = 0; i < mNullSurfaces.size(); i++) + { + stream.read(&mNullSurfaces[i].windingStart); + stream.read(&mNullSurfaces[i].planeIndex); + stream.read(&mNullSurfaces[i].surfaceFlags); + + if (fileVersion >= 13) + stream.read(&mNullSurfaces[i].windingCount); + else + { + U8 count; + stream.read(&count); + mNullSurfaces[i].windingCount = (U32)count; + } + } + + // mLightmaps + stream.read(&vectorSize); + mLightmaps.setSize(vectorSize); + mLightmapKeep.setSize(vectorSize); + GBitmap dummyBmp; + for(i = 0; i < mLightmaps.size(); i++) + { + mLightmaps[i] = new GBitmap; + mLightmaps[i]->readBitmap("png",stream); + + if (!tgeInterior && (fileVersion == 0 || fileVersion == 5 || fileVersion >= 12)) + { + // The "light normal maps" or "light direction maps" were + // removed from Torque 3D... this just reads and throws + // them away. + dummyBmp.readBitmap("png",stream); + } + + stream.read(&mLightmapKeep[i]); + } + + // mSolidLeafSurfaces + stream.read(&vectorSize); + mSolidLeafSurfaces.setSize(vectorSize); + for(i = 0; i < mSolidLeafSurfaces.size(); i++) + { + stream.read(&mSolidLeafSurfaces[i]); + } + + // mAnimatedLights + mNumTriggerableLights = 0; + stream.read(&vectorSize); + mAnimatedLights.setSize(vectorSize); + for(i = 0; i < mAnimatedLights.size(); i++) + { + stream.read(&mAnimatedLights[i].nameIndex); + stream.read(&mAnimatedLights[i].stateIndex); + stream.read(&mAnimatedLights[i].stateCount); + stream.read(&mAnimatedLights[i].flags); + stream.read(&mAnimatedLights[i].duration); + + if((mAnimatedLights[i].flags & AnimationAmbient) == 0) + mNumTriggerableLights++; + } + + // mLightStates + stream.read(&vectorSize); + mLightStates.setSize(vectorSize); + for(i = 0; i < mLightStates.size(); i++) + { + stream.read(&mLightStates[i].red); + stream.read(&mLightStates[i].green); + stream.read(&mLightStates[i].blue); + stream.read(&mLightStates[i].activeTime); + stream.read(&mLightStates[i].dataIndex); + stream.read(&mLightStates[i].dataCount); + } + + // mStateData + stream.read(&vectorSize); + mStateData.setSize(vectorSize); + for(i = 0; i < mStateData.size(); i++) + { + stream.read(&mStateData[i].surfaceIndex); + stream.read(&mStateData[i].mapIndex); + stream.read(&mStateData[i].lightStateIndex); + } + + // mStateDataBuffer + stream.read(&vectorSize); + mStateDataBuffer.setSize(vectorSize); + U32 flags; + stream.read(&flags); + stream.read(mStateDataBuffer.size(), mStateDataBuffer.address()); + + // mNameBuffer + stream.read(&vectorSize); + mNameBuffer.setSize(vectorSize); + stream.read(mNameBuffer.size(), mNameBuffer.address()); + + // mSubObjects + stream.read(&vectorSize); + mSubObjects.setSize(vectorSize); + for (i = 0; i < mSubObjects.size(); i++) + { + InteriorSubObject* iso = InteriorSubObject::readISO(stream); + AssertFatal(iso != NULL, "Error, bad sub object in stream!"); + mSubObjects[i] = iso; + } + + // Convex hulls + stream.read(&vectorSize); + mConvexHulls.setSize(vectorSize); + for(i = 0; i < mConvexHulls.size(); i++) + { + stream.read(&mConvexHulls[i].hullStart); + stream.read(&mConvexHulls[i].hullCount); + stream.read(&mConvexHulls[i].minX); + stream.read(&mConvexHulls[i].maxX); + stream.read(&mConvexHulls[i].minY); + stream.read(&mConvexHulls[i].maxY); + stream.read(&mConvexHulls[i].minZ); + stream.read(&mConvexHulls[i].maxZ); + stream.read(&mConvexHulls[i].surfaceStart); + stream.read(&mConvexHulls[i].surfaceCount); + stream.read(&mConvexHulls[i].planeStart); + stream.read(&mConvexHulls[i].polyListPlaneStart); + stream.read(&mConvexHulls[i].polyListPointStart); + stream.read(&mConvexHulls[i].polyListStringStart); + + if (fileVersion >= 12) + stream.read(&mConvexHulls[i].staticMesh); + else + mConvexHulls[i].staticMesh = false; + } + + // Convex hull emit strings + stream.read(&vectorSize); + mConvexHullEmitStrings.setSize(vectorSize); + stream.read(mConvexHullEmitStrings.size(), mConvexHullEmitStrings.address()); + + // Hull indices + stream.read(&vectorSize); + mHullIndices.setSize(vectorSize); + for(i = 0; i < mHullIndices.size(); i++) + stream.read(&mHullIndices[i]); + + // Hull plane indices + stream.read(&vectorSize); + mHullPlaneIndices.setSize(vectorSize); + for(i = 0; i < mHullPlaneIndices.size(); i++) + stream.read(&mHullPlaneIndices[i]); + + // Hull emit string indices + stream.read(&vectorSize); + mHullEmitStringIndices.setSize(vectorSize); + for(i = 0; i < mHullEmitStringIndices.size(); i++) + stream.read(&mHullEmitStringIndices[i]); + + // Hull surface indices + stream.read(&vectorSize); + mHullSurfaceIndices.setSize(vectorSize); + for(i = 0; i < mHullSurfaceIndices.size(); i++) + stream.read(&mHullSurfaceIndices[i]); + + // PolyList planes + stream.read(&vectorSize); + mPolyListPlanes.setSize(vectorSize); + for(i = 0; i < mPolyListPlanes.size(); i++) + stream.read(&mPolyListPlanes[i]); + + // PolyList points + stream.read(&vectorSize); + mPolyListPoints.setSize(vectorSize); + for(i = 0; i < mPolyListPoints.size(); i++) + stream.read(&mPolyListPoints[i]); + + // PolyList strings + stream.read(&vectorSize); + mPolyListStrings.setSize(vectorSize); + for(i = 0; i < mPolyListStrings.size(); i++) + stream.read(&mPolyListStrings[i]); + + // Coord bins + for(i = 0; i < NumCoordBins * NumCoordBins; i++) + { + stream.read(&mCoordBins[i].binStart); + stream.read(&mCoordBins[i].binCount); + } + + // Coord bin indices + stream.read(&vectorSize); + mCoordBinIndices.setSize(vectorSize); + for(i = 0; i < mCoordBinIndices.size(); i++) + stream.read(&mCoordBinIndices[i]); + + // Coord bin mode + stream.read(&mCoordBinMode); + + // Ambient colors + stream.read(&mBaseAmbient); + stream.read(&mAlarmAmbient); + + if (fileVersion >= 10) + { + // Static meshes + stream.read(&vectorSize); + + mStaticMeshes.setSize(vectorSize); + for (i = 0; i < mStaticMeshes.size(); i++) + { + mStaticMeshes[i] = new InteriorSimpleMesh; + mStaticMeshes[i]->read(stream); + } + } + + if (fileVersion >= 11) + { + // Normals + stream.read(&vectorSize); + + mNormals.setSize(vectorSize); + for (i = 0; i < mNormals.size(); i++) + mathRead(stream, &mNormals[i]); + + // TexMatrices + stream.read(&vectorSize); + + mTexMatrices.setSize(vectorSize); + for (i = 0; i < mTexMatrices.size(); i++) + { + stream.read(&mTexMatrices[i].T); + stream.read(&mTexMatrices[i].N); + stream.read(&mTexMatrices[i].B); + } + + // TexMatIndices + stream.read(&vectorSize); + + mTexMatIndices.setSize(vectorSize); + for (i = 0; i < mTexMatIndices.size(); i++) + stream.read(&mTexMatIndices[i]); + } + + // For future expandability + U32 dummy; + if (fileVersion < 10) + { + stream.read(&dummy); if (dummy != 0) return false; + } + if (fileVersion < 11) + { + stream.read(&dummy); if (dummy != 0) return false; + stream.read(&dummy); if (dummy != 0) return false; + } + + // + // Support for interior light map border sizes. + // + U32 extendedlightmapdata; + stream.read(&extendedlightmapdata); + if(extendedlightmapdata == 1) + { + stream.read(&mLightMapBorderSize); + + //future expansion under current block (avoid using too + //many of the above expansion slots by allowing nested + //blocks)... + stream.read(&dummy); if (dummy != 0) return false; + } + + // Setup the zone planes + setupZonePlanes(); + truncateZoneTree(); + + buildSurfaceZones(); + + return (stream.getStatus() == Stream::Ok); +} + +bool Interior::write(Stream& stream) const +{ + AssertFatal(stream.hasCapability(Stream::StreamWrite), "Interior::write: non-write capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::write: Error, stream in inconsistent state"); + + U32 i; + + // Version this stream + stream.write(smFileVersion); + + stream.write(mDetailLevel); + stream.write(mMinPixels); + mathWrite(stream, mBoundingBox); + mathWrite(stream, mBoundingSphere); + + stream.write(mHasAlarmState); + stream.write(mNumLightStateEntries); + + // Now write out our data vectors. Remember, for cross-platform capability, no + // structure writing is allowed... + + // mPlanes + writePlaneVector(stream); + + // mPoints + stream.write(mPoints.size()); + for (i = 0; i < mPoints.size(); i++) + mathWrite(stream, mPoints[i].point); + + // mPointVisibility + stream.write(mPointVisibility.size()); + stream.write(mPointVisibility.size(), mPointVisibility.address()); + + // mTexGenEQs + stream.write(mTexGenEQs.size()); + for (i = 0; i < mTexGenEQs.size(); i++) + { + mathWrite(stream, mTexGenEQs[i].planeX); + mathWrite(stream, mTexGenEQs[i].planeY); + } + + // mBSPNodes; + stream.write(mBSPNodes.size()); + for (i = 0; i < mBSPNodes.size(); i++) + { + stream.write(mBSPNodes[i].planeIndex); + + if (smFileVersion < 14) + { + stream.write(U16(mBSPNodes[i].frontIndex)); + stream.write(U16(mBSPNodes[i].backIndex)); + } + else + { + stream.write(mBSPNodes[i].frontIndex); + stream.write(mBSPNodes[i].backIndex); + } + } + + // mBSPSolidLeaves + stream.write(mBSPSolidLeaves.size()); + for (i = 0; i < mBSPSolidLeaves.size(); i++) + { + stream.write(mBSPSolidLeaves[i].surfaceIndex); + stream.write(mBSPSolidLeaves[i].surfaceCount); + } + + // MaterialList + mMaterialList->write(stream); + + // mWindings + stream.write(mWindings.size()); + for (i = 0; i < mWindings.size(); i++) + { + stream.write(mWindings[i]); + } + + // mWindingIndices + stream.write(mWindingIndices.size()); + for (i = 0; i < mWindingIndices.size(); i++) + { + stream.write(mWindingIndices[i].windingStart); + stream.write(mWindingIndices[i].windingCount); + } + + // mEdges + if (smFileVersion >= 12) + { + stream.write(mEdges.size()); + for (i = 0; i < mEdges.size(); i++) + { + stream.write(mEdges[i].vertexes[0]); + stream.write(mEdges[i].vertexes[1]); + stream.write(mEdges[i].faces[0]); + stream.write(mEdges[i].faces[1]); + } + } + + // mZones + stream.write(mZones.size()); + for (i = 0; i < mZones.size(); i++) + { + stream.write(mZones[i].portalStart); + stream.write(mZones[i].portalCount); + stream.write(mZones[i].surfaceStart); + stream.write(mZones[i].surfaceCount); + + if (smFileVersion >= 12) + { + stream.write(mZones[i].staticMeshStart); + stream.write(mZones[i].staticMeshCount); + } + + stream.write(mZones[i].flags); + } + + // Zone surfaces + stream.write(mZoneSurfaces.size()); + for (i = 0; i < mZoneSurfaces.size(); i++) + stream.write(mZoneSurfaces[i]); + + // Zone static meshes + if (smFileVersion >= 12) + { + stream.write(mZoneStaticMeshes.size()); + for (i = 0; i < mZoneStaticMeshes.size(); i++) + stream.write(mZoneStaticMeshes[i]); + } + + // mZonePortalList; + stream.write(mZonePortalList.size()); + for (i = 0; i < mZonePortalList.size(); i++) + stream.write(mZonePortalList[i]); + + // mPortals + stream.write(mPortals.size()); + for (i = 0; i < mPortals.size(); i++) + { + stream.write(mPortals[i].planeIndex); + stream.write(mPortals[i].triFanCount); + stream.write(mPortals[i].triFanStart); + stream.write(mPortals[i].zoneFront); + stream.write(mPortals[i].zoneBack); + } + + // mSurfaces + stream.write(mSurfaces.size()); + for (i = 0; i < mSurfaces.size(); i++) + { + stream.write(mSurfaces[i].windingStart); + + if (smFileVersion >= 13) + stream.write(mSurfaces[i].windingCount); + else + { + U8 count = (U8)mSurfaces[i].windingCount; + stream.write(count); + } + + stream.write(mSurfaces[i].planeIndex); + stream.write(mSurfaces[i].textureIndex); + stream.write(mSurfaces[i].texGenIndex); + stream.write(mSurfaces[i].surfaceFlags); + stream.write(mSurfaces[i].fanMask); + writeLMapTexGen(stream, mLMTexGenEQs[i].planeX, mLMTexGenEQs[i].planeY); + + stream.write(mSurfaces[i].lightCount); + stream.write(mSurfaces[i].lightStateInfoStart); + + if (smFileVersion >= 13) + { + stream.write(mSurfaces[i].mapOffsetX); + stream.write(mSurfaces[i].mapOffsetY); + stream.write(mSurfaces[i].mapSizeX); + stream.write(mSurfaces[i].mapSizeY); + } + else + { + U8 offX, offY, sizeX, sizeY; + offX = (U8)mSurfaces[i].mapOffsetX; + offY = (U8)mSurfaces[i].mapOffsetY; + sizeX = (U8)mSurfaces[i].mapSizeX; + sizeY = (U8)mSurfaces[i].mapSizeY; + + stream.write(offX); + stream.write(offY); + stream.write(sizeX); + stream.write(sizeY); + } + + if (smFileVersion == 0 || smFileVersion >= 12) + stream.write(mSurfaces[i].unused); + } + // NormalLMapIndices + stream.write(mNormalLMapIndices.size()); + for (U32 i = 0; i < mNormalLMapIndices.size(); i++) + { + if (smFileVersion >= 13) + stream.write(mNormalLMapIndices[i]); + else + { + U8 index = (U8)mNormalLMapIndices[i]; + stream.write(index); + } + } + + // AlarmLMapIndices + stream.write(mAlarmLMapIndices.size()); + for (U32 i = 0; i < mAlarmLMapIndices.size(); i++) + { + if (smFileVersion >= 13) + stream.write(mAlarmLMapIndices[i]); + else + { + U8 index = (U8)mAlarmLMapIndices[i]; + stream.write(index); + } + } + + + // mNullSurfaces + stream.write(mNullSurfaces.size()); + for(i = 0; i < mNullSurfaces.size(); i++) + { + stream.write(mNullSurfaces[i].windingStart); + stream.write(mNullSurfaces[i].planeIndex); + stream.write(mNullSurfaces[i].surfaceFlags); + + if (smFileVersion >= 13) + stream.write(mNullSurfaces[i].windingCount); + else + { + U8 count = (U8)mNullSurfaces[i].windingCount; + stream.write(count); + } + } + + // mLightmaps + stream.write(mLightmaps.size()); + for(i = 0; i < mLightmaps.size(); i++) + { + mLightmaps[i]->writeBitmap("png",stream); + + if (smFileVersion == 0 || smFileVersion >= 12) + { + // The "light normal maps" or "light direction maps" were + // removed from Torque 3D... this just writes a dummy 2x2 + // texture so that the read/write functions don't change. + GBitmap dummyBmp( 2, 2 ); + dummyBmp.writeBitmap("png",stream); + } + + stream.write(mLightmapKeep[i]); + } + + // mSolidLeafSurfaces + stream.write(mSolidLeafSurfaces.size()); + for(i = 0; i < mSolidLeafSurfaces.size(); i++) + { + stream.write(mSolidLeafSurfaces[i]); + } + + + // Animated lights + stream.write(mAnimatedLights.size()); + for(i = 0; i < mAnimatedLights.size(); i++) + { + stream.write(mAnimatedLights[i].nameIndex); + stream.write(mAnimatedLights[i].stateIndex); + stream.write(mAnimatedLights[i].stateCount); + stream.write(mAnimatedLights[i].flags); + stream.write(mAnimatedLights[i].duration); + } + + stream.write(mLightStates.size()); + for(i = 0; i < mLightStates.size(); i++) + { + stream.write(mLightStates[i].red); + stream.write(mLightStates[i].green); + stream.write(mLightStates[i].blue); + stream.write(mLightStates[i].activeTime); + stream.write(mLightStates[i].dataIndex); + stream.write(mLightStates[i].dataCount); + } + + // mStateData + stream.write(mStateData.size()); + for(i = 0; i < mStateData.size(); i++) + { + stream.write(mStateData[i].surfaceIndex); + stream.write(mStateData[i].mapIndex); + stream.write(mStateData[i].lightStateIndex); + } + + // mStateDataBuffer: Note: superfluous 0 is for flags in future versions. + // that may add compression. This way, we can maintain + // compatability with previous versions. + stream.write(mStateDataBuffer.size()); + stream.write(U32(0)); + stream.write(mStateDataBuffer.size(), mStateDataBuffer.address()); + + // mNameBuffer + stream.write(mNameBuffer.size()); + stream.write(mNameBuffer.size(), mNameBuffer.address()); + + // mSubObjects + stream.write(mSubObjects.size()); + for (i = 0; i < mSubObjects.size(); i++) + { + bool writeSuccess = mSubObjects[i]->writeISO(stream); + AssertFatal(writeSuccess == true, "Error writing sub object to stream!"); + } + + // Convex hulls + stream.write(mConvexHulls.size()); + for(i = 0; i < mConvexHulls.size(); i++) + { + stream.write(mConvexHulls[i].hullStart); + stream.write(mConvexHulls[i].hullCount); + stream.write(mConvexHulls[i].minX); + stream.write(mConvexHulls[i].maxX); + stream.write(mConvexHulls[i].minY); + stream.write(mConvexHulls[i].maxY); + stream.write(mConvexHulls[i].minZ); + stream.write(mConvexHulls[i].maxZ); + stream.write(mConvexHulls[i].surfaceStart); + stream.write(mConvexHulls[i].surfaceCount); + stream.write(mConvexHulls[i].planeStart); + stream.write(mConvexHulls[i].polyListPlaneStart); + stream.write(mConvexHulls[i].polyListPointStart); + stream.write(mConvexHulls[i].polyListStringStart); + + if (smFileVersion >= 12) + stream.write(mConvexHulls[i].staticMesh); + } + + stream.write(mConvexHullEmitStrings.size()); + stream.write(mConvexHullEmitStrings.size(), mConvexHullEmitStrings.address()); + + stream.write(mHullIndices.size()); + for(i = 0; i < mHullIndices.size(); i++) + stream.write(mHullIndices[i]); + + stream.write(mHullPlaneIndices.size()); + for(i = 0; i < mHullPlaneIndices.size(); i++) + stream.write(mHullPlaneIndices[i]); + + stream.write(mHullEmitStringIndices.size()); + for(i = 0; i < mHullEmitStringIndices.size(); i++) + stream.write(mHullEmitStringIndices[i]); + + stream.write(mHullSurfaceIndices.size()); + for(i = 0; i < mHullSurfaceIndices.size(); i++) + stream.write(mHullSurfaceIndices[i]); + + stream.write(mPolyListPlanes.size()); + for(i = 0; i < mPolyListPlanes.size(); i++) + stream.write(mPolyListPlanes[i]); + + stream.write(mPolyListPoints.size()); + for(i = 0; i < mPolyListPoints.size(); i++) + stream.write(mPolyListPoints[i]); + + stream.write(mPolyListStrings.size()); + for(i = 0; i < mPolyListStrings.size(); i++) + stream.write(mPolyListStrings[i]); + + // Coord bins + for(i = 0; i < NumCoordBins * NumCoordBins; i++) + { + stream.write(mCoordBins[i].binStart); + stream.write(mCoordBins[i].binCount); + } + stream.write(mCoordBinIndices.size()); + for(i = 0; i < mCoordBinIndices.size(); i++) + stream.write(mCoordBinIndices[i]); + stream.write(mCoordBinMode); + + // Ambient colors... + stream.write(mBaseAmbient); + stream.write(mAlarmAmbient); + + if (smFileVersion >= 10) + { + // Static meshes + stream.write(mStaticMeshes.size()); + for (i = 0; i < mStaticMeshes.size(); i++) + mStaticMeshes[i]->write(stream); + } + else + stream.write(U32(0)); + + if (smFileVersion >= 11) + { + // Normals + stream.write(mNormals.size()); + for (i = 0; i < mNormals.size(); i++) + mathWrite(stream, mNormals[i]); + + // TexMatrices + stream.write(mTexMatrices.size()); + for (i = 0; i < mTexMatrices.size(); i++) + { + stream.write(mTexMatrices[i].T); + stream.write(mTexMatrices[i].N); + stream.write(mTexMatrices[i].B); + } + + // TexMatIndices + stream.write(mTexMatIndices.size()); + for (i = 0; i < mTexMatIndices.size(); i++) + stream.write(mTexMatIndices[i]); + } + else + { + stream.write(U32(0)); + stream.write(U32(0)); + } + + // + // Support for interior light map border sizes. + // + if(mLightMapBorderSize > 0) + { + stream.write(U32(1));//flag new block... + stream.write(U32(mLightMapBorderSize));//new block data.. + + //future expansion under current block (avoid using too + //many of the above expansion slots by allowing nested + //blocks)... + stream.write(U32(0)); + } + else + { + stream.write(U32(0)); + } + + return stream.getStatus() == Stream::Ok; +} + +bool Interior::readVehicleCollision(Stream& stream) +{ + AssertFatal(stream.hasCapability(Stream::StreamRead), "Interior::read: non-read capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::read: Error, stream in inconsistent state"); + + S32 i; + + // Version this stream. We only load stream of the current version + U32 fileVersion; + stream.read(&fileVersion); + if (fileVersion > smFileVersion) + { + Con::errorf(ConsoleLogEntry::General, "Interior::read: incompatible file version found."); + return false; + } + + U32 vectorSize; + + // Convex hulls + stream.read(&vectorSize); + mVehicleConvexHulls.setSize(vectorSize); + for(i = 0; i < mVehicleConvexHulls.size(); i++) + { + stream.read(&mVehicleConvexHulls[i].hullStart); + stream.read(&mVehicleConvexHulls[i].hullCount); + stream.read(&mVehicleConvexHulls[i].minX); + stream.read(&mVehicleConvexHulls[i].maxX); + stream.read(&mVehicleConvexHulls[i].minY); + stream.read(&mVehicleConvexHulls[i].maxY); + stream.read(&mVehicleConvexHulls[i].minZ); + stream.read(&mVehicleConvexHulls[i].maxZ); + stream.read(&mVehicleConvexHulls[i].surfaceStart); + stream.read(&mVehicleConvexHulls[i].surfaceCount); + stream.read(&mVehicleConvexHulls[i].planeStart); + stream.read(&mVehicleConvexHulls[i].polyListPlaneStart); + stream.read(&mVehicleConvexHulls[i].polyListPointStart); + stream.read(&mVehicleConvexHulls[i].polyListStringStart); + } + + stream.read(&vectorSize); + mVehicleConvexHullEmitStrings.setSize(vectorSize); + stream.read(mVehicleConvexHullEmitStrings.size(), mVehicleConvexHullEmitStrings.address()); + + stream.read(&vectorSize); + mVehicleHullIndices.setSize(vectorSize); + for(i = 0; i < mVehicleHullIndices.size(); i++) + stream.read(&mVehicleHullIndices[i]); + + stream.read(&vectorSize); + mVehicleHullPlaneIndices.setSize(vectorSize); + for(i = 0; i < mVehicleHullPlaneIndices.size(); i++) + stream.read(&mVehicleHullPlaneIndices[i]); + + stream.read(&vectorSize); + mVehicleHullEmitStringIndices.setSize(vectorSize); + for(i = 0; i < mVehicleHullEmitStringIndices.size(); i++) + stream.read(&mVehicleHullEmitStringIndices[i]); + + stream.read(&vectorSize); + mVehicleHullSurfaceIndices.setSize(vectorSize); + for(i = 0; i < mVehicleHullSurfaceIndices.size(); i++) + stream.read(&mVehicleHullSurfaceIndices[i]); + + stream.read(&vectorSize); + mVehiclePolyListPlanes.setSize(vectorSize); + for(i = 0; i < mVehiclePolyListPlanes.size(); i++) + stream.read(&mVehiclePolyListPlanes[i]); + + stream.read(&vectorSize); + mVehiclePolyListPoints.setSize(vectorSize); + for(i = 0; i < mVehiclePolyListPoints.size(); i++) + stream.read(&mVehiclePolyListPoints[i]); + + stream.read(&vectorSize); + mVehiclePolyListStrings.setSize(vectorSize); + for(i = 0; i < mVehiclePolyListStrings.size(); i++) + stream.read(&mVehiclePolyListStrings[i]); + + stream.read(&vectorSize); + mVehicleNullSurfaces.setSize(vectorSize); + for(i = 0; i < mVehicleNullSurfaces.size(); i++) + { + stream.read(&mVehicleNullSurfaces[i].windingStart); + stream.read(&mVehicleNullSurfaces[i].planeIndex); + stream.read(&mVehicleNullSurfaces[i].surfaceFlags); + stream.read(&mVehicleNullSurfaces[i].windingCount); + } + + stream.read(&vectorSize); + mVehiclePoints.setSize(vectorSize); + for(i = 0; i < mVehiclePoints.size(); i++) + mathRead(stream, &mVehiclePoints[i].point); + + stream.read(&vectorSize); + mVehiclePlanes.setSize(vectorSize); + for(i = 0; i < mVehiclePlanes.size(); i++) + mathRead(stream, &mVehiclePlanes[i]); + + stream.read(&vectorSize); + mVehicleWindings.setSize(vectorSize); + for(i = 0; i < mVehicleWindings.size(); i++) + { + stream.read(&mVehicleWindings[i]); + } + + stream.read(&vectorSize); + mVehicleWindingIndices.setSize(vectorSize); + for(i = 0; i < mVehicleWindingIndices.size(); i++) + { + stream.read(&mVehicleWindingIndices[i].windingStart); + stream.read(&mVehicleWindingIndices[i].windingCount); + } + + return true; +} + +bool Interior::writeVehicleCollision(Stream& stream) const +{ + AssertFatal(stream.hasCapability(Stream::StreamWrite), "Interior::write: non-write capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::write: Error, stream in inconsistent state"); + + U32 i; + + // Version this stream + stream.write(smFileVersion); + + // Convex hulls + stream.write(mVehicleConvexHulls.size()); + for(i = 0; i < mVehicleConvexHulls.size(); i++) + { + stream.write(mVehicleConvexHulls[i].hullStart); + stream.write(mVehicleConvexHulls[i].hullCount); + stream.write(mVehicleConvexHulls[i].minX); + stream.write(mVehicleConvexHulls[i].maxX); + stream.write(mVehicleConvexHulls[i].minY); + stream.write(mVehicleConvexHulls[i].maxY); + stream.write(mVehicleConvexHulls[i].minZ); + stream.write(mVehicleConvexHulls[i].maxZ); + stream.write(mVehicleConvexHulls[i].surfaceStart); + stream.write(mVehicleConvexHulls[i].surfaceCount); + stream.write(mVehicleConvexHulls[i].planeStart); + stream.write(mVehicleConvexHulls[i].polyListPlaneStart); + stream.write(mVehicleConvexHulls[i].polyListPointStart); + stream.write(mVehicleConvexHulls[i].polyListStringStart); + } + + stream.write(mVehicleConvexHullEmitStrings.size()); + stream.write(mVehicleConvexHullEmitStrings.size(), mVehicleConvexHullEmitStrings.address()); + + stream.write(mVehicleHullIndices.size()); + for(i = 0; i < mVehicleHullIndices.size(); i++) + stream.write(mVehicleHullIndices[i]); + + stream.write(mVehicleHullPlaneIndices.size()); + for(i = 0; i < mVehicleHullPlaneIndices.size(); i++) + stream.write(mVehicleHullPlaneIndices[i]); + + stream.write(mVehicleHullEmitStringIndices.size()); + for(i = 0; i < mVehicleHullEmitStringIndices.size(); i++) + stream.write(mVehicleHullEmitStringIndices[i]); + + stream.write(mVehicleHullSurfaceIndices.size()); + for(i = 0; i < mVehicleHullSurfaceIndices.size(); i++) + stream.write(mVehicleHullSurfaceIndices[i]); + + stream.write(mVehiclePolyListPlanes.size()); + for(i = 0; i < mVehiclePolyListPlanes.size(); i++) + stream.write(mVehiclePolyListPlanes[i]); + + stream.write(mVehiclePolyListPoints.size()); + for(i = 0; i < mVehiclePolyListPoints.size(); i++) + stream.write(mVehiclePolyListPoints[i]); + + stream.write(mVehiclePolyListStrings.size()); + for(i = 0; i < mVehiclePolyListStrings.size(); i++) + stream.write(mVehiclePolyListStrings[i]); + + stream.write(mVehicleNullSurfaces.size()); + for(i = 0; i < mVehicleNullSurfaces.size(); i++) + { + stream.write(mVehicleNullSurfaces[i].windingStart); + stream.write(mVehicleNullSurfaces[i].planeIndex); + stream.write(mVehicleNullSurfaces[i].surfaceFlags); + stream.write(mVehicleNullSurfaces[i].windingCount); + } + + stream.write(mVehiclePoints.size()); + for(i = 0; i < mVehiclePoints.size(); i++) + mathWrite(stream, mVehiclePoints[i].point); + + stream.write(mVehiclePlanes.size()); + for(i = 0; i < mVehiclePlanes.size(); i++) + mathWrite(stream, mVehiclePlanes[i]); + + stream.write(mVehicleWindings.size()); + for(i = 0; i < mVehicleWindings.size(); i++) + stream.write(mVehicleWindings[i]); + + stream.write(mVehicleWindingIndices.size()); + for(i = 0; i < mVehicleWindingIndices.size(); i++) + { + stream.write(mVehicleWindingIndices[i].windingStart); + stream.write(mVehicleWindingIndices[i].windingCount); + } + + return true; +} + +bool Interior::readSurface(Stream& stream, Surface& surface, TexGenPlanes& texgens, const bool tgeInterior) +{ + // If we end up reading any invalid data then odds are that we + // are no longer correctly reading from the stream and have gotten + // off because this is a TGE version 0 Interior so we bail. + // That is why you will see checks all the way through + stream.read(&surface.windingStart); + + if (surface.windingStart >= mWindings.size()) + return false; + + if (mFileVersion >= 13) + stream.read(&surface.windingCount); + else + { + U8 count; + stream.read(&count); + surface.windingCount = (U32)count; + } + + if (surface.windingStart + surface.windingCount > mWindings.size()) + return false; + + stream.read(&surface.planeIndex); + + if (U32(surface.planeIndex & ~0x8000) >= mPlanes.size()) + return false; + + stream.read(&surface.textureIndex); + + if (surface.textureIndex >= mMaterialList->getMaterialCount()) + return false; + + stream.read(&surface.texGenIndex); + + if (surface.texGenIndex >= mTexGenEQs.size()) + return false; + + stream.read(&surface.surfaceFlags); + stream.read(&surface.fanMask); + + // If reading the lightmap texgen fails then most likely this is a + // TGE version 0 Interior (it gets offset by the "unused" read below + if (readLMapTexGen(stream, texgens.planeX, texgens.planeY) == false) + return false; + + stream.read(&surface.lightCount); + stream.read(&surface.lightStateInfoStart); + + if (mFileVersion >= 13) + { + stream.read(&surface.mapOffsetX); + stream.read(&surface.mapOffsetY); + stream.read(&surface.mapSizeX); + stream.read(&surface.mapSizeY); + } + else + { + U8 offX, offY, sizeX, sizeY; + stream.read(&offX); + stream.read(&offY); + stream.read(&sizeX); + stream.read(&sizeY); + + surface.mapOffsetX = (U32)offX; + surface.mapOffsetY = (U32)offY; + surface.mapSizeX = (U32)sizeX; + surface.mapSizeY = (U32)sizeY; + } + + if (!tgeInterior && (mFileVersion == 0 || mFileVersion == 5 || mFileVersion >= 12)) + stream.read(&surface.unused); + + if (mFileVersion == 5) + { + U32 brushId; + stream.read(&brushId); + } + + return true; +} + +bool Interior::readLMapTexGen(Stream& stream, PlaneF& planeX, PlaneF& planeY) +{ + F32 genX[4]; + F32 genY[4]; + + for(U32 i = 0; i < 4; i++) + { + genX[i] = 0.0f; + genY[i] = 0.0f; + } + + U16 finalWord; + stream.read(&finalWord); + stream.read(&genX[3]); + stream.read(&genY[3]); + + // Unpack the final word. + U32 logScaleY = (finalWord >> 0) & ((1 << 6) - 1); + U32 logScaleX = (finalWord >> 6) & ((1 << 6) - 1); + U16 stEnc = (finalWord >> 13) & 7; + + S32 sc, tc; + switch(stEnc) + { + case 0: sc = 0; tc = 1; break; + case 1: sc = 0; tc = 2; break; + case 2: sc = 1; tc = 0; break; + case 3: sc = 1; tc = 2; break; + case 4: sc = 2; tc = 0; break; + case 5: sc = 2; tc = 1; break; + + default: + sc = tc = -1; + // This is potentially an invalid st coord encoding however *most* times + // this is caused by attempting to load a TGE version 0 Interior + return false; + } + + U32 invScaleX = 1 << logScaleX; + U32 invScaleY = 1 << logScaleY; + + genX[sc] = F32(1.0 / F64(invScaleX)); + genY[tc] = F32(1.0 / F64(invScaleY)); + + planeX.x = genX[0]; + planeX.y = genX[1]; + planeX.z = genX[2]; + planeX.d = genX[3]; + planeY.x = genY[0]; + planeY.y = genY[1]; + planeY.z = genY[2]; + planeY.d = genY[3]; + + return stream.getStatus() == Stream::Ok; +} + +bool Interior::writeLMapTexGen(Stream& stream, const PlaneF& planeX, const PlaneF& planeY) const +{ + F32 genX[4], genY[4]; + + genX[0] = planeX.x; + genX[1] = planeX.y; + genX[2] = planeX.z; + genX[3] = planeX.d; + genY[0] = planeY.x; + genY[1] = planeY.y; + genY[2] = planeY.z; + genY[3] = planeY.d; + + // The tex gen for lmaps is a special case. + // there are only 4 parameters that matter, + // an inverse power of 2 in the x and y, and the + // fp offsets in x and y. We can encode the + // scales completely in U16 and we'll just write out + // the offsets. First, determine which coords we're + // writing... + // + S32 sc = -1; + S32 tc = -1; + if(genX[0] != 0.0) sc = 0; + else if(genX[1] != 0.0) sc = 1; + else if(genX[2] != 0.0) sc = 2; + + if(genY[0] != 0.0) tc = 0; + else if(genY[1] != 0.0) tc = 1; + else if(genY[2] != 0.0) tc = 2; + AssertFatal(sc != -1 && tc != -1 && sc != tc, "Hm, something wrong here."); + + U32 invScaleX = U32((1.0f / genX[sc]) + 0.5); + U32 invScaleY = U32((1.0f / genY[tc]) + 0.5); + AssertISV(invScaleX && isPow2(invScaleX) && invScaleY && isPow2(invScaleY), "Not a power of 2? Something wrong"); + + U32 logScaleX = getBinLog2(invScaleX); + U32 logScaleY = getBinLog2(invScaleY); + AssertFatal(logScaleX < 63 && logScaleY < 63, "Error, you've set the lightmap scale WAAYYY to high!"); + + // We need 3 bits to encode sc and tc, which leaves us 6 bits for logScaleX + // and logScaleY + S16 stEnc = -1; + if(sc == 0 && tc == 1) stEnc = 0; + else if(sc == 0 && tc == 2) stEnc = 1; + else if(sc == 1 && tc == 0) stEnc = 2; + else if(sc == 1 && tc == 2) stEnc = 3; + else if(sc == 2 && tc == 0) stEnc = 4; + else if(sc == 2 && tc == 1) stEnc = 5; + AssertFatal(stEnc != -1, avar("Hm. This should never happen. (%d, %d)", sc, tc)); + + U16 finalWord = U16(stEnc) << 13; + finalWord |= logScaleX << 6; + finalWord |= logScaleY << 0; + + stream.write(finalWord); + stream.write(genX[3]); + stream.write(genY[3]); + + return stream.getStatus() == Stream::Ok; +} + +bool Interior::writePlaneVector(Stream& stream) const +{ + // This is pretty slow, but who cares? + // + Vector uniqueNormals(mPlanes.size()); + Vector uniqueIndices(mPlanes.size()); + + U32 i; + + for(i = 0; i < mPlanes.size(); i++) + { + bool inserted = false; + for(U32 j = 0; j < uniqueNormals.size(); j++) + { + if(mPlanes[i] == uniqueNormals[j]) + { + // Hah! Already have this one... + uniqueIndices.push_back(j); + inserted = true; + break; + } + } + + if(inserted == false) + { + // Gotta do it ourselves... + uniqueIndices.push_back(uniqueNormals.size()); + uniqueNormals.push_back(Point3F(mPlanes[i].x, mPlanes[i].y, mPlanes[i].z)); + } + } + + // Ok, what we have now, is a list of unique normals, a set of indices into + // that vector, and the distances that we still have to write out by hand. + // Hop to it! + stream.write(uniqueNormals.size()); + for(i = 0; i < uniqueNormals.size(); i++) + mathWrite(stream, uniqueNormals[i]); + + stream.write(mPlanes.size()); + for(i = 0; i < mPlanes.size(); i++) + { + stream.write(uniqueIndices[i]); + stream.write(mPlanes[i].d); + } + + return(stream.getStatus() == Stream::Ok); +} + +bool Interior::readPlaneVector(Stream& stream) +{ + U32 vectorSize; + + stream.read(&vectorSize); + Point3F* normals = new Point3F[vectorSize]; + U32 i; + for(i = 0; i < vectorSize; i++) + mathRead(stream, &normals[i]); + + U16 index; + stream.read(&vectorSize); + mPlanes.setSize(vectorSize); + for(i = 0; i < mPlanes.size(); i++) + { + stream.read(&index); + stream.read(&mPlanes[i].d); + mPlanes[i].x = normals[index].x; + mPlanes[i].y = normals[index].y; + mPlanes[i].z = normals[index].z; + } + + delete [] normals; + + return(stream.getStatus() == Stream::Ok); +} + + +bool Interior::getUnifiedZone(const U32 index, S32* zone) +{ + if(isBSPLeafIndex(index)) + { + if(isBSPSolidLeaf(index)) + *zone = -1; + else + *zone = S32(getBSPEmptyLeafZone(index)); + return true; + } + else + { + S32 frontZone, backZone; + bool frontUnified = getUnifiedZone(mBSPNodes[index].frontIndex, &frontZone); + bool backUnified = getUnifiedZone(mBSPNodes[index].backIndex, &backZone); + if(frontUnified && backUnified) + { + if(frontZone == backZone) + { + *zone = frontZone; + return true; + } + else + { + if(frontZone == -1 || backZone == -1) + { + // DMMFIX: Once the interior file format is able to distinguish + // between structural and detail nodes in the runtime bsp, + // we can make this work a little better. + return false; + } + else + { + // Not equal, and neither is -1, no unified zone possible + return false; + } + } + } + else + { + return false; + } + } +} + +void Interior::truncateZoneNode(const U32 index) +{ + S32 unifiedZone; + bool unified = getUnifiedZone(index, &unifiedZone); + if(unified) + { + // Aha! + if(isBSPLeafIndex(index)) + return; + + if(unifiedZone == -1) + mBSPNodes[index].terminalZone = U16(0xFFFF); + else + mBSPNodes[index].terminalZone = U16(0x8000) | U16(unifiedZone); + } + else + { + // Sigh. + if(isBSPLeafIndex(mBSPNodes[index].frontIndex) == false) + truncateZoneNode(mBSPNodes[index].frontIndex); + if(isBSPLeafIndex(mBSPNodes[index].backIndex) == false) + truncateZoneNode(mBSPNodes[index].backIndex); + } +} + +void Interior::truncateZoneTree() +{ + for(U32 i = 0; i < mBSPNodes.size(); i++) + { + mBSPNodes[i].terminalZone = 0; + } + + if(mBSPNodes.size() > 0) + truncateZoneNode(0); +} + + +void Interior::setupZonePlanes() +{ + U16* temp = new U16[mPlanes.size() * mZones.size()]; + U32 tempSize = 0; + + for(U32 i = 0; i < mZones.size(); i++) + { + Zone& rZone = mZones[i]; + + BitVector usedPlanes; + usedPlanes.setSize(mPlanes.size()); + usedPlanes.clear(); + + U32 j; + for(j = 0; j < rZone.surfaceCount; j++) + { + Surface& rSurface = mSurfaces[mZoneSurfaces[rZone.surfaceStart + j]]; + usedPlanes.set(getPlaneIndex(rSurface.planeIndex)); + } + + rZone.planeStart = tempSize; + for(j = 0; j < mPlanes.size(); j++) + { + if(usedPlanes.test(j)) + { + AssertFatal(tempSize < mPlanes.size() * mZones.size(), "Error, out of bounds plane list!"); + temp[tempSize++] = j; + } + } + rZone.planeCount = tempSize - rZone.planeStart; + } + + mZonePlanes.setSize(tempSize); + for(U32 j = 0; j < tempSize; j++) + mZonePlanes[j] = temp[j]; + + delete [] temp; +} diff --git a/interior/interiorInstance.cpp b/interior/interiorInstance.cpp new file mode 100644 index 0000000..619db42 --- /dev/null +++ b/interior/interiorInstance.cpp @@ -0,0 +1,1628 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interiorInstance.h" +#include "interior/interior.h" +//#include "interior/pathedInterior.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "core/stream/bitStream.h" +#include "gfx/bitmap/gBitmap.h" +#include "math/mathIO.h" +#include "gui/worldEditor/editor.h" +#include "interior/interiorResObjects.h" +//#include "T3D/trigger.h" +#include "sceneGraph/simPath.h" +#include "interior/forceField.h" +#include "lighting/lightManager.h" +#include "collision/convex.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxEnvironment.h" +#include "core/frameAllocator.h" +#include "sim/netConnection.h" +#include "platform/profiler.h" +#include "gui/3d/guiTSControl.h" +#include "math/mathUtils.h" +#include "renderInstance/renderPassManager.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsStatic.h" +#include "core/resourceManager.h" +#include "materials/materialManager.h" +#include "materials/materialFeatureTypes.h" + +#ifdef TORQUE_COLLADA +#include "ts/collada/colladaUtils.h" +#endif + +//-------------------------------------------------------------------------- +//-------------------------------------- Local classes, data, and functions +// +const U32 csgMaxZoneSize = 256; +bool sgScopeBoolArray[256]; + +//-------------------------------------------------------------------------- +//-------------------------------------- Static functions +// +IMPLEMENT_CO_NETOBJECT_V1(InteriorInstance); + +//------------------------------------------------------------------------------ +//-------------------------------------- InteriorInstance +// +bool InteriorInstance::smDontRestrictOutside = false; +F32 InteriorInstance::smDetailModification = 1.0f; + + +InteriorInstance::InteriorInstance() +{ + mAlarmState = false; + + mInteriorFileName = NULL; + mTypeMask = InteriorObjectType | StaticObjectType | + StaticRenderedObjectType | ShadowCasterObjectType; + mNetFlags.set(Ghostable | ScopeAlways); + + mShowTerrainInside = false; + mSmoothLighting = false; + + mSkinBase = StringTable->insert("base"); + mAudioProfile = 0; + mAudioEnvironment = 0; + mForcedDetailLevel = -1; + + mConvexList = new Convex; + mCRC = 0; + + mPhysicsRep = NULL; +} + +InteriorInstance::~InteriorInstance() +{ + delete mConvexList; + mConvexList = NULL; + + // GFX2_RENDER_MERGE + //for (U32 i = 0; i < mReflectPlanes.size(); i++) + // mReflectPlanes[i].clearTextures(); +} + + +void InteriorInstance::init() +{ + // Does nothing for the moment +} + + +void InteriorInstance::destroy() +{ + // Also does nothing for the moment +} + + +//-------------------------------------------------------------------------- +// Inspection +static SFXProfile * saveAudioProfile = 0; +static SFXEnvironment * saveAudioEnvironment = 0; +void InteriorInstance::inspectPreApply() +{ + saveAudioProfile = mAudioProfile; + saveAudioEnvironment = mAudioEnvironment; +} + +void InteriorInstance::inspectPostApply() +{ + if((mAudioProfile != saveAudioProfile) || (mAudioEnvironment != saveAudioEnvironment)) + setMaskBits(AudioMask); + + // Apply any transformations set in the editor + Parent::inspectPostApply(); + + // Update the Transform on Editor Apply. + setMaskBits(TransformMask); +} + +//-------------------------------------------------------------------------- +//-------------------------------------- Console functionality +// +void InteriorInstance::initPersistFields() +{ + addGroup("Media"); + addProtectedField("interiorFile", TypeFilename, Offset(mInteriorFileName, InteriorInstance), &setInteriorFile, &defaultProtectedGetFn, ""); + endGroup("Media"); + + addGroup("Audio"); + addField("sfxProfile", TypeSFXProfilePtr, Offset(mAudioProfile, InteriorInstance)); + addField("sfxEnvironment", TypeSFXEnvironmentPtr, Offset(mAudioEnvironment, InteriorInstance)); + endGroup("Audio"); + + addGroup("Misc"); + addField("showTerrainInside", TypeBool, Offset(mShowTerrainInside, InteriorInstance)); + addField("smoothLighting", TypeBool, Offset(mSmoothLighting, InteriorInstance)); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +void InteriorInstance::consoleInit() +{ + //-------------------------------------- Class level variables + Con::addVariable("pref::Interior::ShowEnvironmentMaps", TypeBool, &Interior::smRenderEnvironmentMaps); + Con::addVariable("pref::Interior::VertexLighting", TypeBool, &Interior::smUseVertexLighting); + Con::addVariable("pref::Interior::TexturedFog", TypeBool, &Interior::smUseTexturedFog); + Con::addVariable("pref::Interior::lockArrays", TypeBool, &Interior::smLockArrays); + + Con::addVariable("pref::Interior::detailAdjust", TypeF32, &InteriorInstance::smDetailModification); + + // DEBUG ONLY!!! +#ifndef TORQUE_SHIPPING + Con::addVariable("Interior::DontRestrictOutside", TypeBool, &smDontRestrictOutside); +#endif +} + +//-------------------------------------------------------------------------- +void InteriorInstance::renewOverlays() +{ +/* + StringTableEntry baseName = dStricmp(mSkinBase, "base") == 0 ? "blnd" : mSkinBase; + + for (U32 i = 0; i < mMaterialMaps.size(); i++) { + MaterialList* pMatList = mMaterialMaps[i]; + + for (U32 j = 0; j < pMatList->mMaterialNames.size(); j++) { + const char* pName = pMatList->mMaterialNames[j]; + const U32 len = dStrlen(pName); + if (len < 6) + continue; + + const char* possible = pName + (len - 5); + if (dStricmp(".blnd", possible) == 0) { + char newName[256]; + AssertFatal(len < 200, "InteriorInstance::renewOverlays: Error, len exceeds allowed name length"); + + dStrncpy(newName, pName, possible - pName); + newName[possible - pName] = '\0'; + dStrcat(newName, "."); + dStrcat(newName, baseName); + + TextureHandle test = TextureHandle(newName, MeshTexture, false); + if (test.getGLName() != 0) { + pMatList->mMaterials[j] = test; + } else { + pMatList->mMaterials[j] = TextureHandle(pName, MeshTexture, false); + } + } + } + } +*/ +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::setSkinBase(const char* newBase) +{ + if (dStricmp(mSkinBase, newBase) == 0) + return; + + mSkinBase = StringTable->insert(newBase); + + if (isServerObject()) + setMaskBits(SkinBaseMask); + else + renewOverlays(); +} + +#ifdef TORQUE_COLLADA +//-------------------------------------------------------------------------- +void InteriorInstance::exportToCollada(bool bakeTransform) +{ + if (mInteriorRes->getNumDetailLevels() == 0) + { + Con::errorf("InteriorInstance::exportToCollada() called an InteriorInstance with no Interior"); + return; + } + + // For now I am only worrying about the highest lod + Interior* pInterior = mInteriorRes->getDetailLevel(0); + + if (!pInterior) + { + Con::errorf("InteriorInstance::exportToCollada() called an InteriorInstance with an invalid Interior"); + return; + } + + // Get an optimized version of our mesh + OptimizedPolyList interiorMesh; + + if (bakeTransform) + { + MatrixF mat = getTransform(); + Point3F scale = getScale(); + + pInterior->buildExportPolyList(interiorMesh, &mat, &scale); + } + else + pInterior->buildExportPolyList(interiorMesh); + + // Get our export path + Torque::Path colladaFile = mInteriorRes.getPath(); + + // Make sure to set our Collada extension + colladaFile.setExtension("dae"); + + // Use the InteriorInstance name if possible + String meshName = getName(); + + // Otherwise use the DIF's file name + if (meshName.isEmpty()) + meshName = colladaFile.getFileName(); + + // If we are baking the transform then append + // a CRC version of the transform to the mesh/file name + if (bakeTransform) + { + F32 trans[19]; + + const MatrixF& mat = getTransform(); + const Point3F& scale = getScale(); + + // Copy in the transform + for (U32 i = 0; i < 4; i++) + { + for (U32 j = 0; j < 4; j++) + { + trans[i * 4 + j] = mat(i, j); + } + } + + // Copy in the scale + trans[16] = scale.x; + trans[17] = scale.y; + trans[18] = scale.z; + + U32 crc = CRC::calculateCRC(trans, sizeof(F32) * 19); + + meshName += String::ToString("_%x", crc); + } + + // Set the file name as the meshName + colladaFile.setFileName(meshName); + + // Use a ColladaUtils function to do the actual export to a Collada file + ColladaUtils::exportToCollada(colladaFile, interiorMesh, meshName); +} +#endif + +//-------------------------------------------------------------------------- +// from resManager.cc +extern U32 calculateCRC(void * buffer, S32 len, U32 crcVal ); + +bool InteriorInstance::onAdd() +{ + if(! loadInterior()) + return false; + + if(!Parent::onAdd()) + return false; + + addToScene(); + + if ( gPhysicsPlugin ) + mPhysicsRep = gPhysicsPlugin->createStatic( this ); + + return true; +} + + +void InteriorInstance::onRemove() +{ + SAFE_DELETE( mPhysicsRep ); + + unloadInterior(); + + removeFromScene(); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- + +bool InteriorInstance::loadInterior() +{ + U32 i; + + // Load resource + mInteriorRes = ResourceManager::get().load(mInteriorFileName); + if (bool(mInteriorRes) == false) { + Con::errorf(ConsoleLogEntry::General, "Unable to load interior: %s", mInteriorFileName); + NetConnection::setLastError("Unable to load interior: %s", mInteriorFileName); + return false; + } + if(isClientObject()) + { + if(mCRC != mInteriorRes.getChecksum()) + { + NetConnection::setLastError("Local interior file '%s' does not match version on server.", mInteriorFileName); + return false; + } + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + // ok, if the material list load failed... + // if this is a local connection, we'll assume that's ok + // and just have white textures... + // otherwise we want to return false. + Interior* pInterior = mInteriorRes->getDetailLevel(i); + if(!pInterior->prepForRendering(mInteriorRes.getPath().getFullPath().c_str()) ) + { + if(!bool(mServerObject)) + { + return false; + } + } + } + + // copy planar reflect list from top detail level - for now + Interior* pInterior = mInteriorRes->getDetailLevel(0); + if( pInterior->mReflectPlanes.size() ) + { + for ( i = 0; i < pInterior->mReflectPlanes.size(); i++ ) + { + mPlaneReflectors.increment(); + PlaneReflector &plane = mPlaneReflectors.last(); + + plane.refplane = pInterior->mReflectPlanes[i]; + plane.objectSpace = true; + plane.registerReflector( this, &mReflectorDesc ); + } + } + + } + else + mCRC = mInteriorRes.getChecksum(); + + // Ok, everything's groovy! Let's cache our hashed filename for renderimage sorting... + mInteriorFileHash = _StringTable::hashString(mInteriorFileName); + + // Setup bounding information + mObjBox = mInteriorRes->getDetailLevel(0)->getBoundingBox(); + resetWorldBox(); + setRenderTransform(mObjToWorld); + + + // Do any handle loading, etc. required. + + if (isClientObject()) { + + for (i = 0; i < mInteriorRes->getNumDetailLevels(); i++) { + Interior* pInterior = mInteriorRes->getDetailLevel(i); + + // Force the lightmap manager to download textures if we're + // running the mission editor. Normally they are only + // downloaded after the whole scene is lit. + gInteriorLMManager.addInstance(pInterior->getLMHandle(), mLMHandle, this); + if (gEditingMission) { + gInteriorLMManager.useBaseTextures(pInterior->getLMHandle(), mLMHandle); + gInteriorLMManager.downloadGLTextures(pInterior->getLMHandle()); + } + + // Install material list + // mMaterialMaps.push_back(new MaterialList(pInterior->mMaterialList)); + } + + renewOverlays(); + } else { + + } + + setMaskBits(0xffffffff); + return true; +} + +void InteriorInstance::unloadInterior() +{ + mConvexList->nukeList(); + delete mConvexList; + mConvexList = new Convex; + + if(isClientObject()) + { + if(bool(mInteriorRes) && mLMHandle != 0xFFFFFFFF) + { + for(U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) + { + Interior * pInterior = mInteriorRes->getDetailLevel(i); + if (pInterior->getLMHandle() != 0xFFFFFFFF) + gInteriorLMManager.removeInstance(pInterior->getLMHandle(), mLMHandle); + } + } + + if( mPlaneReflectors.size() ) + { + for ( U32 i = 0; i < mPlaneReflectors.size(); i++ ) + { + mPlaneReflectors[i].unregisterReflector(); + } + mPlaneReflectors.clear(); + } + } +} + +//-------------------------------------------------------------------------- +bool InteriorInstance::onSceneAdd(SceneGraph* pGraph) +{ + AssertFatal(mInteriorRes, "Error, should not have been added to the scene if there's no interior!"); + + if (Parent::onSceneAdd(pGraph) == false) + return false; + + U32 maxNumZones = 0; + + for (U32 i = 0; i < mInteriorRes->getNumDetailLevels(); i++) + { + if (mInteriorRes->getDetailLevel(i)->mZones.size() > maxNumZones) + maxNumZones = mInteriorRes->getDetailLevel(i)->mZones.size(); + } + + if (maxNumZones > 1) + { + AssertWarn(getNumCurrZones() == 1, "There should be one and only one zone for an interior that manages zones"); + mSceneManager->registerZones(this, (maxNumZones - 1)); + } + + return true; +} + + +//-------------------------------------------------------------------------- +void InteriorInstance::onSceneRemove() +{ + AssertFatal(mInteriorRes, "Error, should not have been added to the scene if there's no interior!"); + + if (isManagingZones()) + mSceneManager->unregisterZones(this); + + Parent::onSceneRemove(); +} + + +//-------------------------------------------------------------------------- +bool InteriorInstance::getOverlappingZones(SceneObject* obj, + U32* zones, + U32* numZones) +{ + MatrixF xForm(true); + Point3F invScale(1.0f / getScale().x, + 1.0f / getScale().y, + 1.0f / getScale().z); + xForm.scale(invScale); + xForm.mul(getWorldTransform()); + xForm.mul(obj->getTransform()); + xForm.scale(obj->getScale()); + + U32 waterMark = FrameAllocator::getWaterMark(); + + U16* zoneVector = (U16*)FrameAllocator::alloc(mInteriorRes->getDetailLevel(0)->mZones.size() * sizeof(U16)); + U32 numRetZones = 0; + + bool outsideToo = mInteriorRes->getDetailLevel(0)->scanZones(obj->getObjBox(), + xForm, + zoneVector, + &numRetZones); + if (numRetZones > SceneObject::MaxObjectZones) { + Con::warnf(ConsoleLogEntry::General, "Too many zones returned for query on %s. Returning first %d", + mInteriorFileName, SceneObject::MaxObjectZones); + } + + for (U32 i = 0; i < getMin(numRetZones, U32(SceneObject::MaxObjectZones)); i++) + zones[i] = zoneVector[i] + mZoneRangeStart - 1; + *numZones = numRetZones; + + FrameAllocator::setWaterMark(waterMark); + + return outsideToo; +} + + +//-------------------------------------------------------------------------- +U32 InteriorInstance::getPointZone(const Point3F& p) +{ + AssertFatal(mInteriorRes, "Error, no interior!"); + + Point3F osPoint = p; + mWorldToObj.mulP(osPoint); + osPoint.convolveInverse(mObjScale); + + S32 zone = mInteriorRes->getDetailLevel(0)->getZoneForPoint(osPoint); + + // If we're in solid (-1) or outside, we need to return 0 + if (zone == -1 || zone == 0) + return 0; + + return (zone-1) + mZoneRangeStart; +} + +// does a hack check to determine how much a point is 'inside'.. should have +// portals prebuilt with the transfer energy to each other portal in the zone +// from the neighboring zone.. these values can be used to determine the factor +// from within an individual zone.. also, each zone could be marked with +// average material property for eax environment audio +// ~0: outside -> 1: inside +bool InteriorInstance::getPointInsideScale(const Point3F & pos, F32 * pScale) +{ + AssertFatal(mInteriorRes, "InteriorInstance::getPointInsideScale: no interior"); + + Interior * interior = mInteriorRes->getDetailLevel(0); + + Point3F p = pos; + mWorldToObj.mulP(p); + p.convolveInverse(mObjScale); + + U32 zoneIndex = interior->getZoneForPoint(p); + if(zoneIndex == -1) // solid? + { + *pScale = 1.f; + return(true); + } + else if(zoneIndex == 0) // outside? + { + *pScale = 0.f; + return(true); + } + + U32 waterMark = FrameAllocator::getWaterMark(); + const Interior::Portal** portals = (const Interior::Portal**)FrameAllocator::alloc(256 * sizeof(const Interior::Portal*)); + U32 numPortals = 0; + + Interior::Zone & zone = interior->mZones[zoneIndex]; + + U32 i; + for(i = 0; i < zone.portalCount; i++) + { + const Interior::Portal & portal = interior->mPortals[interior->mZonePortalList[zone.portalStart + i]]; + if(portal.zoneBack == 0 || portal.zoneFront == 0) { + AssertFatal(numPortals < 256, "Error, overflow in temporary portal buffer!"); + portals[numPortals++] = &portal; + } + } + + // inside? + if(numPortals == 0) + { + *pScale = 1.f; + + FrameAllocator::setWaterMark(waterMark); + return(true); + } + + Point3F* portalCenters = (Point3F*)FrameAllocator::alloc(numPortals * sizeof(Point3F)); + U32 numPortalCenters = 0; + + // scale using the distances to the portals in this zone... + for(i = 0; i < numPortals; i++) + { + const Interior::Portal * portal = portals[i]; + if(!portal->triFanCount) + continue; + + Point3F center(0, 0, 0); + for(U32 j = 0; j < portal->triFanCount; j++) + { + const Interior::TriFan & fan = interior->mWindingIndices[portal->triFanStart + j]; + U32 numPoints = fan.windingCount; + + if(!numPoints) + continue; + + for(U32 k = 0; k < numPoints; k++) + { + const Point3F & a = interior->mPoints[interior->mWindings[fan.windingStart + k]].point; + center += a; + } + + center /= (F32)numPoints; + portalCenters[numPortalCenters++] = center; + } + } + + // 'magic' check here... + F32 magic = Con::getFloatVariable("Interior::insideDistanceFalloff", 10.f); + + F32 val = 0.f; + for(i = 0; i < numPortalCenters; i++) + val += 1.f - mClampF(Point3F(portalCenters[i] - p).len() / magic, 0.f, 1.f); + + *pScale = 1.f - mClampF(val, 0.f, 1.f); + + FrameAllocator::setWaterMark(waterMark); + return(true); +} + +//-------------------------------------------------------------------------- +// renderObject - this function is called pretty much only for debug rendering +//-------------------------------------------------------------------------- +void InteriorInstance::renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat ) +{ +#ifndef TORQUE_SHIPPING + if (Interior::smRenderMode == 0) + return; + + if (overrideMat) + return; + + if(gEditingMission && isHidden()) + return; + + U32 detailLevel = 0; + detailLevel = calcDetailLevel(state, state->getCameraPosition()); + + Interior* pInterior = mInteriorRes->getDetailLevel( detailLevel ); + + if (!pInterior) + return; + + PROFILE_START( IRO_DebugRender ); + + GFX->pushWorldMatrix(); + + // setup world matrix - for fixed function + MatrixF world = GFX->getWorldMatrix(); + world.mul( getRenderTransform() ); + world.scale( getScale() ); + GFX->setWorldMatrix( world ); + + // setup world matrix - for shaders + MatrixF proj = GFX->getProjectionMatrix(); + proj.mul(world); + + SceneGraphData sgData; + + sgData = pInterior->setupSceneGraphInfo( this, state ); + ZoneVisDeterminer zoneVis = pInterior->setupZoneVis( this, state ); + pInterior->debugRender( zoneVis, sgData, this, proj ); + + GFX->popWorldMatrix(); + + PROFILE_END(); +#endif +} + + +//-------------------------------------------------------------------------- +bool InteriorInstance::scopeObject(const Point3F& rootPosition, + const F32 /*rootDistance*/, + bool* zoneScopeState) +{ + AssertFatal(isManagingZones(), "Error, should be a zone manager if we are called on to scope the scene!"); + if (bool(mInteriorRes) == false) + return false; + + Interior* pInterior = getDetailLevel(0); + AssertFatal(pInterior->mZones.size() <= csgMaxZoneSize, "Error, too many zones! Increase max"); + bool* pInteriorScopingState = sgScopeBoolArray; + dMemset(pInteriorScopingState, 0, sizeof(bool) * pInterior->mZones.size()); + + // First, let's transform the point into the interior's space + Point3F interiorRoot = rootPosition; + getWorldTransform().mulP(interiorRoot); + interiorRoot.convolveInverse(getScale()); + + S32 realStartZone = getPointZone(rootPosition); + if (realStartZone != 0) + realStartZone = realStartZone - mZoneRangeStart + 1; + + bool continueOut = pInterior->scopeZones(realStartZone, + interiorRoot, + pInteriorScopingState); + + // Copy pInteriorScopingState to zoneScopeState + for (S32 i = 1; i < pInterior->mZones.size(); i++) + zoneScopeState[i + mZoneRangeStart - 1] = pInteriorScopingState[i]; + + return continueOut; +} + + +//-------------------------------------------------------------------------- +U32 InteriorInstance::calcDetailLevel(SceneState* state, const Point3F& wsPoint) +{ + AssertFatal(mInteriorRes, "Error, should not try to calculate the deatil level without a resource to work with!"); + AssertFatal(getNumCurrZones() > 0, "Error, must belong to a zone for this to work"); + + if (smDetailModification < 0.3f) + smDetailModification = 0.3f; + if (smDetailModification > 1.0f) + smDetailModification = 1.0f; + + // Early out for simple interiors + if (mInteriorRes->getNumDetailLevels() == 1) + return 0; + + if((mForcedDetailLevel >= 0) && (mForcedDetailLevel < mInteriorRes->getNumDetailLevels())) + return(mForcedDetailLevel); + + Point3F osPoint = wsPoint; + mRenderWorldToObj.mulP(osPoint); + osPoint.convolveInverse(mObjScale); + + // First, see if the point is in the object space bounding box of the highest detail + // If it is, then the detail level is zero. + if (mObjBox.isContained(osPoint)) + return 0; + + // Otherwise, we're going to have to do some ugly trickery to get the projection. + // I've stolen the worldToScreenScale from dglMatrix, we'll have to calculate the + // projection of the bounding sphere of the lowest detail level. + // worldToScreenScale = (near * view.extent.x) / (right - left) + RectI viewport; + F64 frustum[4] = { 1e10, -1e10, 1e10, -1e10 }; + + bool init = false; + SceneObjectRef* pWalk = mZoneRefHead; + AssertFatal(pWalk != NULL, "Error, object must exist in at least one zone to call this!"); + while (pWalk) { + const SceneState::ZoneState& rState = state->getZoneState(pWalk->zone); + if (rState.render == true) + { + // frustum + + const F32 left = rState.frustum.getNearLeft(); + const F32 right = rState.frustum.getNearRight(); + const F32 bottom = rState.frustum.getNearBottom(); + const F32 top = rState.frustum.getNearTop(); + + if (left < frustum[0]) frustum[0] = left; + if (right > frustum[1]) frustum[1] = right; + if (bottom < frustum[2]) frustum[2] = bottom; + if (top > frustum[3]) frustum[3] = top; + + // viewport + if (init == false) + viewport = rState.viewport; + else + viewport.unionRects(rState.viewport); + + init = true; + } + pWalk = pWalk->nextInObj; + } + AssertFatal(init, "Error, at least one zone must be rendered here!"); + + F32 worldToScreenScale = (state->getNearPlane() * viewport.extent.x) / (frustum[1] - frustum[0]); + const SphereF& lowSphere = mInteriorRes->getDetailLevel(mInteriorRes->getNumDetailLevels() - 1)->mBoundingSphere; + F32 dist = (lowSphere.center - osPoint).len(); + F32 projRadius = (lowSphere.radius / dist) * worldToScreenScale; + + // Scale the projRadius based on the objects maximum scale axis + projRadius *= getMax(mFabs(mObjScale.x), getMax(mFabs(mObjScale.y), mFabs(mObjScale.z))); + + // Multiply based on detail preference... + projRadius *= smDetailModification; + + // Ok, now we have the projected radius, we need to search through the interiors to + // find the largest interior that will support this projection. + U32 final = mInteriorRes->getNumDetailLevels() - 1; + for (U32 i = 0; i< mInteriorRes->getNumDetailLevels() - 1; i++) { + Interior* pDetail = mInteriorRes->getDetailLevel(i); + + if (pDetail->mMinPixels < projRadius) { + final = i; + break; + } + } + + // Ok, that's it. + return final; +} + + +//-------------------------------------------------------------------------- +bool InteriorInstance::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 startZone, const bool modifyBaseState) +{ + if (isLastState(state, stateKey)) + return false; + + if(gEditingMission && isHidden()) + return false; + + PROFILE_START(InteriorPrepRenderImage); + + setLastState(state, stateKey); + + U32 realStartZone; + if (startZone != 0xFFFFFFFF) { + AssertFatal(startZone != 0, "Hm. This really shouldn't happen. Should only get inside zones here"); + AssertFatal(isManagingZones(), "Must be managing zones if we're here..."); + + realStartZone = startZone - mZoneRangeStart + 1; + } else { + realStartZone = getPointZone(state->getCameraPosition()); + if (realStartZone != 0) + realStartZone = realStartZone - mZoneRangeStart + 1; + } + + if (modifyBaseState == false) + { + // Regular query. We only return a render zone if our parent zone is rendered. + // Otherwise, we always render + if (state->isObjectRendered(this) == false) + { + PROFILE_END(); + return false; + } + } + else + { + if (mShowTerrainInside == true) + state->enableTerrainOverride(); + } + + U32 detailLevel = 0; + if (startZone == 0xFFFFFFFF) + detailLevel = calcDetailLevel(state, state->getCameraPosition()); + + + U32 baseZoneForPrep = getCurrZone(0); + bool multipleZones = false; + if (getNumCurrZones() > 1) { + U32 numRenderedZones = 0; + baseZoneForPrep = 0xFFFFFFFF; + for (U32 i = 0; i < getNumCurrZones(); i++) { + if (state->getZoneState(getCurrZone(i)).render == true) { + numRenderedZones++; + if (baseZoneForPrep == 0xFFFFFFFF) + baseZoneForPrep = getCurrZone(i); + } + } + + if (numRenderedZones > 1) + multipleZones = true; + } + + bool continueOut = mInteriorRes->getDetailLevel(0)->prepRender(state, + baseZoneForPrep, + realStartZone, mZoneRangeStart, + mRenderObjToWorld, mObjScale, + modifyBaseState & !smDontRestrictOutside, + smDontRestrictOutside | multipleZones, + state->isInvertedCull() ); + if (smDontRestrictOutside) + continueOut = true; + + + // need to delay the batching because zone information is not complete until + // the entire scene tree is built. + SceneState::InteriorListElem elem; + elem.obj = this; + elem.stateKey = stateKey; + elem.startZone = 0xFFFFFFFF; + elem.detailLevel = detailLevel; + elem.worldXform = state->getRenderPass()->allocSharedXform(RenderPassManager::View); + + state->insertInterior( elem ); + + + PROFILE_END(); + return continueOut; +} + + +//-------------------------------------------------------------------------- +bool InteriorInstance::castRay(const Point3F& s, const Point3F& e, RayInfo* info) +{ + info->object = this; + return mInteriorRes->getDetailLevel(0)->castRay(s, e, info); +} + +//------------------------------------------------------------------------------ +void InteriorInstance::setTransform(const MatrixF & mat) +{ + Parent::setTransform(mat); + + // Since the interior is a static object, it's render transform changes 1 to 1 + // with it's collision transform + setRenderTransform(mat); + + if (isServerObject()) + setMaskBits(TransformMask); +} + + +//------------------------------------------------------------------------------ +bool InteriorInstance::buildPolyList(AbstractPolyList* list, const Box3F& wsBox, const SphereF&) +{ + if (bool(mInteriorRes) == false) + return false; + + // Setup collision state data + list->setTransform(&getTransform(), getScale()); + list->setObject(this); + + return mInteriorRes->getDetailLevel(0)->buildPolyList(list, wsBox, mWorldToObj, getScale()); +} + + + +void InteriorInstance::buildConvex(const Box3F& box, Convex* convex) +{ + if (bool(mInteriorRes) == false) + return; + + mConvexList->collectGarbage(); + + Box3F realBox = box; + mWorldToObj.mul(realBox); + realBox.minExtents.convolveInverse(mObjScale); + realBox.maxExtents.convolveInverse(mObjScale); + + if (realBox.isOverlapped(getObjBox()) == false) + return; + + U32 waterMark = FrameAllocator::getWaterMark(); + + if ((convex->getObject()->getType() & VehicleObjectType) && + mInteriorRes->getDetailLevel(0)->mVehicleConvexHulls.size() > 0) + { + // Can never have more hulls than there are hulls in the interior... + U16* hulls = (U16*)FrameAllocator::alloc(mInteriorRes->getDetailLevel(0)->mVehicleConvexHulls.size() * sizeof(U16)); + U32 numHulls = 0; + + Interior* pInterior = mInteriorRes->getDetailLevel(0); + if (pInterior->getIntersectingVehicleHulls(realBox, hulls, &numHulls) == false) { + FrameAllocator::setWaterMark(waterMark); + return; + } + + for (U32 i = 0; i < numHulls; i++) { + // See if this hull exists in the working set already... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { + if (itr->mConvex->getType() == InteriorConvexType && + (static_cast(itr->mConvex)->getObject() == this && + static_cast(itr->mConvex)->hullId == -S32(hulls[i] + 1))) { + cc = itr->mConvex; + break; + } + } + if (cc) + continue; + + // Create a new convex. + InteriorConvex* cp = new InteriorConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->mObject = this; + cp->pInterior = pInterior; + cp->hullId = -S32(hulls[i] + 1); + cp->box.minExtents.x = pInterior->mVehicleConvexHulls[hulls[i]].minX; + cp->box.minExtents.y = pInterior->mVehicleConvexHulls[hulls[i]].minY; + cp->box.minExtents.z = pInterior->mVehicleConvexHulls[hulls[i]].minZ; + cp->box.maxExtents.x = pInterior->mVehicleConvexHulls[hulls[i]].maxX; + cp->box.maxExtents.y = pInterior->mVehicleConvexHulls[hulls[i]].maxY; + cp->box.maxExtents.z = pInterior->mVehicleConvexHulls[hulls[i]].maxZ; + } + } + else + { + // Can never have more hulls than there are hulls in the interior... + U16* hulls = (U16*)FrameAllocator::alloc(mInteriorRes->getDetailLevel(0)->mConvexHulls.size() * sizeof(U16)); + U32 numHulls = 0; + + Interior* pInterior = mInteriorRes->getDetailLevel(0); + if (pInterior->getIntersectingHulls(realBox, hulls, &numHulls) == false) { + FrameAllocator::setWaterMark(waterMark); + return; + } + + for (U32 i = 0; i < numHulls; i++) { + // See if this hull exists in the working set already... + Convex* cc = 0; + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) { + if (itr->mConvex->getType() == InteriorConvexType && + (static_cast(itr->mConvex)->getObject() == this && + static_cast(itr->mConvex)->hullId == hulls[i])) { + cc = itr->mConvex; + break; + } + } + if (cc) + continue; + + // Create a new convex. + InteriorConvex* cp = new InteriorConvex; + mConvexList->registerObject(cp); + convex->addToWorkingList(cp); + cp->mObject = this; + cp->pInterior = pInterior; + cp->hullId = hulls[i]; + cp->box.minExtents.x = pInterior->mConvexHulls[hulls[i]].minX; + cp->box.minExtents.y = pInterior->mConvexHulls[hulls[i]].minY; + cp->box.minExtents.z = pInterior->mConvexHulls[hulls[i]].minZ; + cp->box.maxExtents.x = pInterior->mConvexHulls[hulls[i]].maxX; + cp->box.maxExtents.y = pInterior->mConvexHulls[hulls[i]].maxY; + cp->box.maxExtents.z = pInterior->mConvexHulls[hulls[i]].maxZ; + } + } + FrameAllocator::setWaterMark(waterMark); +} + + +//------------------------------------------------------------------------------ +U32 InteriorInstance::packUpdate(NetConnection* c, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(c, mask, stream); + + if (stream->writeFlag((mask & InitMask) != 0)) { + // Initial update, write the whole kit and kaboodle + stream->write(mCRC); + + stream->writeString(mInteriorFileName); + stream->writeFlag(mShowTerrainInside); + stream->writeFlag(mSmoothLighting); + + // Write the transform (do _not_ use writeAffineTransform. Since this is a static + // object, the transform must be RIGHT THE *&)*$&^ ON or it will goof up the + // syncronization between the client and the server. + mathWrite(*stream, mObjToWorld); + mathWrite(*stream, mObjScale); + + // Write the alarm state + stream->writeFlag(mAlarmState); + + // Write the skinbase + stream->writeString(mSkinBase); + + // audio profile + if(stream->writeFlag(mAudioProfile)) + stream->writeRangedU32(mAudioProfile->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + // audio environment: + if(stream->writeFlag(mAudioEnvironment)) + stream->writeRangedU32(mAudioEnvironment->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + } + else + { + if (stream->writeFlag((mask & TransformMask) != 0)) { + mathWrite(*stream, mObjToWorld); + mathWrite(*stream, mObjScale); + } + + stream->writeFlag(mAlarmState); + + + if (stream->writeFlag(mask & SkinBaseMask)) + stream->writeString(mSkinBase); + + // audio update: + if(stream->writeFlag(mask & AudioMask)) + { + // profile: + if(stream->writeFlag(mAudioProfile)) + stream->writeRangedU32(mAudioProfile->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + // environment: + if(stream->writeFlag(mAudioEnvironment)) + stream->writeRangedU32(mAudioEnvironment->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + } + } + + return retMask; +} + + +//------------------------------------------------------------------------------ +void InteriorInstance::unpackUpdate(NetConnection* c, BitStream* stream) +{ + Parent::unpackUpdate(c, stream); + + MatrixF temp; + Point3F tempScale; + + if (stream->readFlag()) { + bool isNewUpdate(mInteriorRes); + + if(isNewUpdate) + unloadInterior(); + + // Initial Update + // CRC + stream->read(&mCRC); + + // File + mInteriorFileName = stream->readSTString(); + + // Terrain flag + mShowTerrainInside = stream->readFlag(); + + //Smooth lighting flag + mSmoothLighting = stream->readFlag(); + + // Transform + mathRead(*stream, &temp); + mathRead(*stream, &tempScale); + setScale(tempScale); + setTransform(temp); + + // Alarm state: Note that we handle this ourselves on the initial update + // so that the state is always full on or full off... + mAlarmState = stream->readFlag(); + + mSkinBase = stream->readSTString(); + + // audio profile: + if(stream->readFlag()) + { + U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + mAudioProfile = dynamic_cast(Sim::findObject(profileId)); + } + else + mAudioProfile = 0; + + // audio environment: + if(stream->readFlag()) + { + U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + mAudioEnvironment = dynamic_cast( Sim::findObject( profileId ) ); + } + else + mAudioEnvironment = 0; + + if(isNewUpdate) + { + if(! loadInterior()) + Con::errorf("InteriorInstance::unpackUpdate - Unable to load new interior"); + } + } + else + { + // Normal update + if (stream->readFlag()) { + mathRead(*stream, &temp); + mathRead(*stream, &tempScale); + setScale(tempScale); + setTransform(temp); + } + + setAlarmMode(stream->readFlag()); + + + if (stream->readFlag()) { + mSkinBase = stream->readSTString(); + renewOverlays(); + } + + // audio update: + if(stream->readFlag()) + { + // profile: + if(stream->readFlag()) + { + U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + mAudioProfile = dynamic_cast( Sim::findObject( profileId ) ); + } + else + mAudioProfile = 0; + + // environment: + if(stream->readFlag()) + { + U32 profileId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + mAudioEnvironment = dynamic_cast( Sim::findObject( profileId ) ); + } + else + mAudioEnvironment = 0; + } + } +} + + +//------------------------------------------------------------------------------ +Interior* InteriorInstance::getDetailLevel(const U32 level) +{ + return mInteriorRes->getDetailLevel(level); +} + +U32 InteriorInstance::getNumDetailLevels() +{ + return mInteriorRes->getNumDetailLevels(); +} + +//-------------------------------------------------------------------------- +//-------------------------------------- Alarm functionality +// +void InteriorInstance::setAlarmMode(const bool alarm) +{ + if (mInteriorRes->getDetailLevel(0)->mHasAlarmState == false) + return; + + if (mAlarmState == alarm) + return; + + mAlarmState = alarm; + if (isServerObject()) + { + setMaskBits(AlarmMask); + } + else + { + // DMMTODO: Invalidate current light state + } +} + + + +void InteriorInstance::createTriggerTransform(const InteriorResTrigger* trigger, MatrixF* transform) +{ + Point3F offset; + MatrixF xform = getTransform(); + xform.getColumn(3, &offset); + + Point3F triggerOffset = trigger->mOffset; + triggerOffset.convolve(mObjScale); + getTransform().mulV(triggerOffset); + offset += triggerOffset; + xform.setColumn(3, offset); + + *transform = xform; +} + + +bool InteriorInstance::readLightmaps(GBitmap**** lightmaps) +{ + AssertFatal(mInteriorRes, "Error, no interior loaded!"); + AssertFatal(lightmaps, "Error, no lightmaps or numdetails result pointers"); + AssertFatal(*lightmaps == NULL, "Error, already have a pointer in the lightmaps result field!"); + + // Load resource + FileStream* pStream; + if((pStream = FileStream::createAndOpen( mInteriorFileName, Torque::FS::File::Read )) == NULL) + { + Con::errorf(ConsoleLogEntry::General, "Unable to load interior: %s", mInteriorFileName); + return false; + } + + InteriorResource* pResource = new InteriorResource; + bool success = pResource->read(*pStream); + delete pStream; + + if (success == false) + { + delete pResource; + return false; + } + AssertFatal(pResource->getNumDetailLevels() == mInteriorRes->getNumDetailLevels(), + "Mismatched detail levels!"); + + *lightmaps = new GBitmap**[mInteriorRes->getNumDetailLevels()]; + + for (U32 i = 0; i < pResource->getNumDetailLevels(); i++) + { + Interior* pInterior = pResource->getDetailLevel(i); + (*lightmaps)[i] = new GBitmap*[pInterior->mLightmaps.size()]; + for (U32 j = 0; j < pInterior->mLightmaps.size(); j++) + { + ((*lightmaps)[i])[j] = pInterior->mLightmaps[j]; + pInterior->mLightmaps[j] = NULL; + } + pInterior->mLightmaps.clear(); + } + + delete pResource; + return true; +} + +S32 InteriorInstance::getSurfaceZone(U32 surfaceindex, Interior *detail) +{ + AssertFatal(((surfaceindex >= 0) && (surfaceindex < detail->surfaceZones.size())), "Bad surface index!"); + S32 zone = detail->surfaceZones[surfaceindex]; + if(zone > -1) + return zone + mZoneRangeStart; + return getCurrZone(0); +} + +//----------------------------------------------------------------------------- +// Protected Field Accessors +//----------------------------------------------------------------------------- + +bool InteriorInstance::setInteriorFile(void* obj, const char* data) +{ + if(data == NULL) + return true; + + InteriorInstance *inst = static_cast(obj); + + if(inst->isProperlyAdded()) + inst->unloadInterior(); + + inst->mInteriorFileName = StringTable->insert(data); + + if(inst->isProperlyAdded()) + { + if(! inst->loadInterior()) + Con::errorf("InteriorInstance::setInteriorFile - Unable to load new interior"); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Console Functions / Methods +//----------------------------------------------------------------------------- + +ConsoleFunctionGroupBegin(Interiors, ""); + +#ifndef TORQUE_SHIPPING +ConsoleFunction( setInteriorRenderMode, void, 2, 2, "(int modeNum)") +{ + S32 mode = dAtoi(argv[1]); + if (mode < 0 || mode > Interior::ShowDetailLevel) + mode = 0; + + Interior::smRenderMode = mode; +} + +ConsoleFunction( setInteriorFocusedDebug, void, 2, 2, "(bool enable)") +{ + if (dAtob(argv[1])) { + Interior::smFocusedDebug = true; + } else { + Interior::smFocusedDebug = false; + } +} + +#endif + +ConsoleFunction( isPointInside, bool, 2, 4, "(Point3F pos) or (float x, float y, float z)") +{ + static bool lastValue = false; + + if(!(argc == 2 || argc == 4)) + { + Con::errorf(ConsoleLogEntry::General, "cIsPointInside: invalid parameters"); + return(lastValue); + } + + Point3F pos; + if(argc == 2) + dSscanf(argv[1], "%g %g %g", &pos.x, &pos.y, &pos.z); + else + { + pos.x = dAtof(argv[1]); + pos.y = dAtof(argv[2]); + pos.z = dAtof(argv[3]); + } + + RayInfo collision; + if(gClientContainer.castRay(pos, Point3F(pos.x, pos.y, pos.z - 2000.f), InteriorObjectType, &collision)) + { + if(collision.face == -1) + Con::errorf(ConsoleLogEntry::General, "cIsPointInside: failed to find hit face on interior"); + else + { + InteriorInstance * interior = dynamic_cast(collision.object); + if(interior) + lastValue = !interior->getDetailLevel(0)->isSurfaceOutsideVisible(collision.face); + else + Con::errorf(ConsoleLogEntry::General, "cIsPointInside: invalid interior on collision"); + } + } + + return(lastValue); +} + + +ConsoleFunctionGroupEnd(Interiors); + +#ifdef TORQUE_COLLADA +ConsoleMethod( InteriorInstance, exportToCollada, void, 2, 3, "([bool bakeTransform] exports the Interior to a Collada file)") +{ + if (argc == 3) + object->exportToCollada(dAtob(argv[2])); + else + object->exportToCollada(); +} +#endif + +ConsoleMethod( InteriorInstance, setAlarmMode, void, 3, 3, "(string mode) Mode is 'On' or 'Off'") +{ + AssertFatal(dynamic_cast(object) != NULL, + "Error, how did a non-interior get here?"); + + bool alarm; + if (dStricmp(argv[2], "On") == 0) + alarm = true; + else + alarm = false; + + InteriorInstance* interior = static_cast(object); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + interior->setAlarmMode(alarm); +} + + +ConsoleMethod( InteriorInstance, setSkinBase, void, 3, 3, "(string basename)") +{ + AssertFatal(dynamic_cast(object) != NULL, + "Error, how did a non-interior get here?"); + + InteriorInstance* interior = static_cast(object); + if (interior->isClientObject()) { + Con::errorf(ConsoleLogEntry::General, "InteriorInstance: client objects may not receive console commands. Ignored"); + return; + } + + interior->setSkinBase(argv[2]); +} + +ConsoleMethod( InteriorInstance, getNumDetailLevels, S32, 2, 2, "") +{ + InteriorInstance * instance = static_cast(object); + return(instance->getNumDetailLevels()); +} + +ConsoleMethod( InteriorInstance, setDetailLevel, void, 3, 3, "(int level)") +{ + InteriorInstance * instance = static_cast(object); + if(instance->isServerObject()) + { + NetConnection * toServer = NetConnection::getConnectionToServer(); + NetConnection * toClient = NetConnection::getLocalClientConnection(); + if(!toClient || !toServer) + return; + + S32 index = toClient->getGhostIndex(instance); + if(index == -1) + return; + + InteriorInstance * clientInstance = dynamic_cast(toServer->resolveGhost(index)); + if(clientInstance) + clientInstance->setDetailLevel(dAtoi(argv[2])); + } + else + instance->setDetailLevel(dAtoi(argv[2])); +} + +//------------------------------------------------------------------------ +//These functions are duplicated in tsStatic, shapeBase, and interiorInstance. +//They each function a little differently; but achieve the same purpose of gathering +//target names/counts without polluting simObject. + +ConsoleMethod( InteriorInstance, getTargetName, const char*, 4, 4, "(detailLevel, targetNum)") +{ + S32 detailLevel = dAtoi(argv[2]); + S32 idx = dAtoi(argv[3]); + + Interior* obj = object->getDetailLevel(detailLevel); + + if(obj) + return obj->getTargetName(idx); + + return ""; +} + +ConsoleMethod( InteriorInstance, getTargetCount, S32, 3, 3, "(detailLevel)") +{ + S32 detailLevel = dAtoi(argv[2]); + + Interior* obj = object->getDetailLevel(detailLevel); + if(obj) + return obj->getTargetCount(); + + return -1; +} + +// This method is able to change materials per map to with others. The material that is being replaced is being mapped to +// unmapped_mat as a part of this transition +ConsoleMethod( InteriorInstance, changeMaterial, void, 5, 5, "(mapTo, fromMaterial, ToMaterial)") +{ + // simple parsing through the interiors detail levels looking for the correct mapto. + // break when we find the correct detail level to depend on. + U32 level = -1; + for( U32 i = 0; i < object->getNumDetailLevels(); i++ ) + { + for( U32 j = 0; j < object->getDetailLevel(i)->getTargetCount(); j++ ) + { + if( object->getDetailLevel(i)->getTargetName(j).compare( argv[2] ) == 0 ) + { + level = i; + break; + } + } + + if( level != -1 ) + break; + } + + // kind of lame to do the same check after potentially breaking out early. needs to be done because + // we dont want to end up with the wrong detail level + if( level == -1 ) + return; + + // initilize server/client versions + Interior *serverObj = object->getDetailLevel( level ); + + InteriorInstance * instanceClientObj = dynamic_cast< InteriorInstance* > ( object->getClientObject() ); + Interior *clientObj = instanceClientObj->getDetailLevel( level ); + + if(serverObj) + { + // Lets get ready to switch out materials + Material *oldMat = dynamic_cast(Sim::findObject(argv[3])); + Material *newMat = dynamic_cast(Sim::findObject(argv[4])); + + // if no valid new material, theres no reason for doing this + if( !newMat ) + return; + + // Lets remap the old material off, so as to let room for our current material room to claim its spot + if( oldMat ) + oldMat->mMapTo = String("unmapped_mat"); + + newMat->mMapTo = argv[2]; + + // Map the material in the in the matmgr + MATMGR->mapMaterial( argv[2], argv[4] ); + + U32 i = 0; + // Replace instances with the new material being traded in. Lets make sure that we only + // target the specific targets per inst. This technically is only done here for interiors for + // safe keeping. The remapping that truly matters most (for on the fly changes) are done in the node lists + for (; i < serverObj->mMaterialList->getMaterialNameList().size(); i++) + { + if( String(argv[2]) == serverObj->mMaterialList->getMaterialName(i)) + { + delete [] clientObj->mMaterialList->mMatInstList[i]; + clientObj->mMaterialList->mMatInstList[i] = newMat->createMatInstance(); + + delete [] serverObj->mMaterialList->mMatInstList[i]; + serverObj->mMaterialList->mMatInstList[i] = newMat->createMatInstance(); + break; + } + } + + // Finishing the safekeeping + const GFXVertexFormat *flags = getGFXVertexFormat(); + FeatureSet features = MATMGR->getDefaultFeatures(); + clientObj->mMaterialList->getMaterialInst(i)->init( features, flags ); + serverObj->mMaterialList->getMaterialInst(i)->init( features, flags ); + + // These loops are referenced in interior.cpp's initMatInstances + // Made a couple of alterations to tailor specifically towards one changing one instance + for( U32 i=0; igetNumZones(); i++ ) + { + for( U32 j=0; jmZoneRNList[i].renderNodeList.size(); j++ ) + { + BaseMatInstance *matInst = clientObj->mZoneRNList[i].renderNodeList[j].matInst; + Material* refMat = dynamic_cast(matInst->getMaterial()); + + if(refMat == oldMat) + { + clientObj->mZoneRNList[i].renderNodeList[j].matInst = newMat->createMatInstance(); + clientObj->mZoneRNList[i].renderNodeList[j].matInst->init(MATMGR->getDefaultFeatures(), getGFXVertexFormat()); + + //if ( pMat ) + //mHasTranslucentMaterials |= pMat->mTranslucent && !pMat->mTranslucentZWrite; + } + } + } + + // Lets reset the clientObj settings in order to accomadate the new material + clientObj->fillSurfaceTexMats(); + clientObj->createZoneVBs(); + clientObj->cloneMatInstances(); + clientObj->createReflectPlanes(); + clientObj->initMatInstances(); + } +} + +ConsoleMethod( InteriorInstance, getModelFile, const char *, 2, 2, "getModelFile( String )") +{ + return object->getInteriorFileName(); +} \ No newline at end of file diff --git a/interior/interiorInstance.h b/interior/interiorInstance.h new file mode 100644 index 0000000..de34df4 --- /dev/null +++ b/interior/interiorInstance.h @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORINSTANCE_H_ +#define _INTERIORINSTANCE_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _INTERIORRES_H_ +#include "interior/interiorRes.h" +#endif +#ifndef _INTERIORLMMANAGER_H_ +#include "interior/interiorLMManager.h" +#endif +#ifndef _BITVECTOR_H_ +#include "core/bitVector.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _INTERIOR_H_ +#include "interior.h" +#endif +#ifndef _REFLECTOR_H_ +#include "sceneGraph/reflector.h" +#endif + + +class AbstractPolyList; +class InteriorSubObject; +class InteriorResTrigger; +class MaterialList; +class TextureObject; +class Convex; +class SFXProfile; +class SFXEnvironment; +class PhysicsStatic; + +//-------------------------------------------------------------------------- +class InteriorInstance : public SceneObject +{ + typedef SceneObject Parent; + + public: + InteriorInstance(); + ~InteriorInstance(); + + + S32 getSurfaceZone(U32 surfaceindex, Interior *detail); + + + static void init(); + static void destroy(); + + // Collision + public: + bool buildPolyList(AbstractPolyList *polyList, const Box3F &box, const SphereF &sphere); + bool castRay(const Point3F &start, const Point3F &end, RayInfo *info); + virtual void setTransform(const MatrixF &mat); + + void buildConvex(const Box3F& box,Convex* convex); + private: + Convex* mConvexList; + + public: + /// This returns true if the interior is in an alarm state. Alarm state + /// will put different lighting into the interior and also possibly + /// have an audio element also. + bool inAlarmState() {return(mAlarmState);} + + /// This returns true if the interior should be lit with smooth lighting (slower) + bool useSmoothLighting() {return(mSmoothLighting);} + + /// This sets the alarm mode of the interior. + /// @param alarm If true the interior will be in an alarm state next frame + void setAlarmMode(const bool alarm); + + public: + /// @name Subobject access interface + /// @{ + + /// Returns the number of detail levels for an object + U32 getNumDetailLevels(); + + /// Gets the interior associated with a particular detail level + /// @param level Detail level + Interior* getDetailLevel(const U32 level); + + /// Sets the detail level to render manually + /// @param level Detail level to force + void setDetailLevel(S32 level = -1) { mForcedDetailLevel = level; } + /// @} + + // Material management for overlays + public: + + /// Reloads material information if the interior skin changes + void renewOverlays(); + + /// Sets the interior skin to something different + /// @param newBase New base skin + void setSkinBase(const char *newBase); + + /// Exports the interior to a Collada file + /// @param bakeTransform Bakes the InteriorInstance's transform into the vertex positions + void exportToCollada(bool bakeTransform = false); + + public: + static bool smDontRestrictOutside; + static F32 smDetailModification; + + void renderObject( ObjectRenderInst *ri, SceneState *state, BaseMatInstance* ); + + DECLARE_CONOBJECT(InteriorInstance); + static void initPersistFields(); + static void consoleInit(); + + /// Reads the lightmaps of the interior into the provided pointer + /// @param lightmaps Lightmaps in the interior (out) + bool readLightmaps(GBitmap ****lightmaps); + + protected: + bool onAdd(); + void onRemove(); + + void inspectPreApply(); + void inspectPostApply(); + + bool onSceneAdd(SceneGraph *graph); + void onSceneRemove(); + U32 getPointZone(const Point3F& p); + bool getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones); + + U32 calcDetailLevel(SceneState*, const Point3F&); + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + bool scopeObject(const Point3F& rootPosition, + const F32 rootDistance, + bool* zoneScopeState); + + bool loadInterior(); + void unloadInterior(); + + private: + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + + enum UpdateMaskBits { + InitMask = BIT(0), + TransformMask = BIT(1), + AlarmMask = BIT(2), + + // Reserved for light updates (8 bits for now) + _lightupdate0 = BIT(3), + _lightupdate1 = BIT(4), + _lightupdate2 = BIT(5), + _lightupdate3 = BIT(6), + _lightupdate4 = BIT(7), + _lightupdate5 = BIT(8), + _lightupdate6 = BIT(9), + _lightupdate7 = BIT(10), + + SkinBaseMask = BIT(11), + AudioMask = BIT(12), + NextFreeMask = BIT(13) + }; + enum Constants { + LightUpdateBitStart = 3, + LightUpdateBitEnd = 10 + }; + + private: + StringTableEntry mInteriorFileName; ///< File name of the interior this instance encapuslates + U32 mInteriorFileHash; ///< Hash for interior file name, used for sorting + Resource mInteriorRes; ///< Interior managed by resource manager +// Vector mMaterialMaps; ///< Materials for this interior + StringTableEntry mSkinBase; ///< Skin for this interior + + bool mShowTerrainInside; ///< Enables or disables terrain showing through the interior + bool mSmoothLighting; ///< Enables or disables doing the longer smooth lighting calculations + SFXProfile * mAudioProfile; ///< Audio profile + SFXEnvironment * mAudioEnvironment; ///< Audio environment + S32 mForcedDetailLevel; ///< Forced LOD, if -1 auto LOD + U32 mCRC; ///< CRC for the interior + + LM_HANDLE mLMHandle; ///< Handle to the light manager + + PhysicsStatic *mPhysicsRep; + + public: + + /// Returns the Light Manager handle + LM_HANDLE getLMHandle() { return(mLMHandle); } + + /// Returns the audio profile + SFXProfile * getAudioProfile() { return(mAudioProfile); } + + /// Returns the audio environment + SFXEnvironment * getAudioEnvironment() const { return mAudioEnvironment; } + + /// This is used to determine just how 'inside' a point is in an interior. + /// This is used by the environmental audio code for audio properties and the + /// function always returns true. + /// @param pos Point to test + /// @param pScale How inside is the point 0 = totally outside, 1 = totally inside (out) + bool getPointInsideScale(const Point3F & pos, F32 * pScale); // ~0: outside -> 1: inside + + /// Returns the interior resource + Resource & getResource() {return(mInteriorRes);} // SceneLighting::InteriorProxy interface + + /// Returns the CRC for validation + U32 getCRC() { return(mCRC); } + + // Alarm state information + private: + enum AlarmState { + Normal = 0, + Alarm = 1 + }; + + bool mAlarmState; ///< Alarm state of the interior + + private: + + /// Creates a transform based on an trigger area + /// @param trigger Trigger to create a transform for + /// @param transform Transform generated (out) + void createTriggerTransform(const InteriorResTrigger *trigger, MatrixF *transform); + + /// Reflection related + public: + Vector< PlaneReflector > mPlaneReflectors; + ReflectorDesc mReflectorDesc; + + // Protected field accessors + static bool setInteriorFile(void* obj, const char* data); + + StringTableEntry getInteriorFileName() { return mInteriorFileName; } +}; + + + +#endif //_INTERIORBLOCK_H_ + diff --git a/interior/interiorLMManager.cpp b/interior/interiorLMManager.cpp new file mode 100644 index 0000000..767570c --- /dev/null +++ b/interior/interiorLMManager.cpp @@ -0,0 +1,367 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interiorLMManager.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/bitmap/gBitmap.h" +#include "interior/interiorRes.h" +#include "interior/interiorInstance.h" +#include "interior/interior.h" + +//------------------------------------------------------------------------------ +// Globals +InteriorLMManager gInteriorLMManager; + +//------------------------------------------------------------------------------ + +InteriorLMManager::InteriorLMManager() +{ + VECTOR_SET_ASSOCIATION( mInteriors ); +} + +InteriorLMManager::~InteriorLMManager() +{ + for(U32 i = 0; i < mInteriors.size(); i++) + removeInterior(LM_HANDLE(i)); +} + +//------------------------------------------------------------------------------ +void InteriorLMManager::addInterior(LM_HANDLE & interiorHandle, U32 numLightmaps, Interior * interior) +{ + interiorHandle = mInteriors.size(); + mInteriors.increment(); + mInteriors.last() = new InteriorLMInfo; + + mInteriors.last()->mInterior = interior; + mInteriors.last()->mHandlePtr = &interiorHandle; + mInteriors.last()->mNumLightmaps = numLightmaps; + + // create base instance + addInstance(interiorHandle, mInteriors.last()->mBaseInstanceHandle, 0); + AssertFatal(mInteriors.last()->mBaseInstanceHandle == LM_HANDLE(0), "InteriorLMManager::addInterior: invalid base instance handle"); + + // steal the lightmaps from the interior + Vector& texHandles = getHandles(interiorHandle, 0); + for(U32 i = 0; i < interior->mLightmaps.size(); i++) + { + AssertFatal(interior->mLightmaps[i], "InteriorLMManager::addInterior: interior missing lightmap"); + texHandles[i].set(interior->mLightmaps[i], &GFXDefaultPersistentProfile, true, String("Interior Lightmap")); + } + + interior->mLightmaps.clear(); +} + +void InteriorLMManager::removeInterior(LM_HANDLE interiorHandle) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::removeInterior: invalid interior handle"); + AssertFatal(mInteriors[interiorHandle]->mInstances.size() == 1, "InteriorLMManager::removeInterior: cannot remove base interior"); + + // remove base instance + removeInstance(interiorHandle, 0); + + *mInteriors[interiorHandle]->mHandlePtr = LM_HANDLE(-1); + + delete mInteriors[interiorHandle]; + + // last one? otherwise move it + if((mInteriors.size()-1) != interiorHandle) + { + mInteriors[interiorHandle] = mInteriors.last(); + *(mInteriors[interiorHandle]->mHandlePtr) = interiorHandle; + } + + mInteriors.decrement(); +} + +//------------------------------------------------------------------------------ +void InteriorLMManager::addInstance(LM_HANDLE interiorHandle, LM_HANDLE & instanceHandle, InteriorInstance * instance) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::addInstance: invalid interior handle"); + AssertFatal(interiorHandle == *(mInteriors[interiorHandle]->mHandlePtr), "InteriorLMManager::addInstance: invalid handle value"); + + InteriorLMInfo * interiorInfo = mInteriors[interiorHandle]; + + // create the instance info and fill + InstanceLMInfo * instanceInfo = new InstanceLMInfo; + + instanceInfo->mInstance = instance; + instanceInfo->mHandlePtr = &instanceHandle; + instanceHandle = interiorInfo->mInstances.size(); + + interiorInfo->mInstances.push_back(instanceInfo); + + // create/clear list + instanceInfo->mLightmapHandles.setSize(interiorInfo->mNumLightmaps); + + for(U32 i = 0; i < instanceInfo->mLightmapHandles.size(); i++) + instanceInfo->mLightmapHandles[i] = NULL; +} + +void InteriorLMManager::removeInstance(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::removeInstance: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::removeInstance: invalid instance handle"); + AssertFatal(!(instanceHandle == mInteriors[interiorHandle]->mBaseInstanceHandle && + mInteriors[interiorHandle]->mInstances.size() > 1), "InteriorLMManager::removeInstance: invalid base instance"); + + InteriorLMInfo * itrInfo = mInteriors[interiorHandle]; + + // kill it + InstanceLMInfo * instInfo = itrInfo->mInstances[instanceHandle]; + for(U32 i = 0; i < instInfo->mLightmapHandles.size(); i++) + instInfo->mLightmapHandles[i] = NULL; + + // reset on last instance removal only (multi detailed shapes share the same instance handle) + if(itrInfo->mInstances.size() == 1) + *instInfo->mHandlePtr = LM_HANDLE(-1); + + delete instInfo; + + // last one? otherwise move it + if((itrInfo->mInstances.size()-1) != instanceHandle) + { + itrInfo->mInstances[instanceHandle] = itrInfo->mInstances.last(); + *(itrInfo->mInstances[instanceHandle]->mHandlePtr) = instanceHandle; + } + + itrInfo->mInstances.decrement(); +} + +void InteriorLMManager::useBaseTextures(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::useBaseTextures: invalid interior handle"); + AssertFatal(interiorHandle == *(mInteriors[interiorHandle]->mHandlePtr), "InteriorLMManager::useBaseTextures: invalid handle value"); + + // Make sure the base light maps are loaded + loadBaseLightmaps(interiorHandle,instanceHandle); + + // Install base lightmaps for this instance... + Vector& baseHandles = getHandles(interiorHandle, 0); + Vector& texHandles = getHandles(interiorHandle, instanceHandle); + for(U32 i = 0; i < baseHandles.size(); i++) + texHandles[i] = baseHandles[i]; +} + +//------------------------------------------------------------------------------ +void InteriorLMManager::destroyBitmaps() +{ + for(S32 i = mInteriors.size() - 1; i >= 0; i--) + { + InteriorLMInfo * interiorInfo = mInteriors[i]; + for(S32 j = interiorInfo->mInstances.size() - 1; j >= 0; j--) + { + InstanceLMInfo * instanceInfo = interiorInfo->mInstances[j]; + for(S32 k = instanceInfo->mLightmapHandles.size() - 1; k >= 0; k--) + { + if(!instanceInfo->mLightmapHandles[k]) + continue; + + GFXTextureObject * texObj = instanceInfo->mLightmapHandles[k]; + if(!texObj || !texObj->mBitmap) + continue; + + // don't remove 'keep' bitmaps + if(!interiorInfo->mInterior->mLightmapKeep[k]) + { +// SAFE_DELETE(texObj->mBitmap); +// texObj->bitmap = 0; + } + } + } + } +} + +void InteriorLMManager::destroyTextures() +{ + for(S32 i = mInteriors.size() - 1; i >= 0; i--) + { + InteriorLMInfo * interiorInfo = mInteriors[i]; + for(S32 j = interiorInfo->mInstances.size() - 1; j >= 0; j--) + { + InstanceLMInfo * instanceInfo = interiorInfo->mInstances[j]; + for(S32 k = interiorInfo->mNumLightmaps - 1; k >= 0; k--) + { + // will want to remove the vector here eventually... so dont clear + instanceInfo->mLightmapHandles[k] = NULL; + } + } + } +} + +void InteriorLMManager::downloadGLTextures() +{ + for(S32 i = mInteriors.size() - 1; i >= 0; i--) + downloadGLTextures(i); +} + +void InteriorLMManager::downloadGLTextures(LM_HANDLE interiorHandle) +{ + + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::downloadGLTextures: invalid interior handle"); + InteriorLMInfo * interiorInfo = mInteriors[interiorHandle]; + + // The bit vector is used to keep track of which lightmap sets need + // to be loaded from the shared "base" instance. Every instance + // can have it's own lightmap set due to mission lighting. + BitVector needTexture; + needTexture.setSize(interiorInfo->mNumLightmaps); + needTexture.clear(); + + for(S32 j = interiorInfo->mInstances.size() - 1; j >= 0; j--) + { + InstanceLMInfo * instanceInfo = interiorInfo->mInstances[j]; + for(S32 k = instanceInfo->mLightmapHandles.size() - 1; k >= 0; k--) + { + // All instances can share the base instances static lightmaps. + // Test here to see if we need to load those lightmaps. + if ((j == 0) && !needTexture.test(k)) + continue; + if (!instanceInfo->mLightmapHandles[k]) + { + needTexture.set(k); + continue; + } + GFXTexHandle texObj = instanceInfo->mLightmapHandles[k]; + if (!texObj || !texObj->mBitmap) + { + needTexture.set(k); + continue; + } + + instanceInfo->mLightmapHandles[k].set( texObj->mBitmap, &GFXDefaultPersistentProfile, false, String("Interior Lightmap Handle") ); + } + } +} + +bool InteriorLMManager::loadBaseLightmaps(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::loadBaseLightmaps: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::loadBaseLightmaps: invalid instance handle"); + + // must use a valid instance handle + if(!instanceHandle) + return(false); + + InteriorLMInfo * interiorInfo = mInteriors[interiorHandle]; + if(!interiorInfo->mNumLightmaps) + return(false); + + InstanceLMInfo * baseInstanceInfo = interiorInfo->mInstances[0]; + + // already loaded? (if any bitmap is present, then assumed that all will be) + GFXTexHandle texture (baseInstanceInfo->mLightmapHandles[0]); + if(texture.isValid() && texture.getBitmap()) + return(true); + + InstanceLMInfo * instanceInfo = interiorInfo->mInstances[instanceHandle]; + + Resource & interiorRes = instanceInfo->mInstance->getResource(); + if(!bool(interiorRes)) + return(false); + + GBitmap *** pBitmaps = 0; + if(!instanceInfo->mInstance->readLightmaps(&pBitmaps)) + return(false); + + for(U32 i = 0; i < interiorRes->getNumDetailLevels(); i++) + { + Interior * interior = interiorRes->getDetailLevel(i); + AssertFatal(interior, "InteriorLMManager::loadBaseLightmaps: invalid detail level in resource"); + AssertFatal(interior->getLMHandle() != LM_HANDLE(-1), "InteriorLMManager::loadBaseLightmaps: interior not added to manager"); + AssertFatal(interior->getLMHandle() < mInteriors.size(), "InteriorLMManager::loadBaseLightmaps: invalid interior"); + + InteriorLMInfo * interiorInfo = mInteriors[interior->getLMHandle()]; + InstanceLMInfo * baseInstanceInfo = interiorInfo->mInstances[0]; + + for(U32 j = 0; j < interiorInfo->mNumLightmaps; j++) + { + AssertFatal(pBitmaps[i][j], "InteriorLMManager::loadBaseLightmaps: invalid bitmap"); + + if (baseInstanceInfo->mLightmapHandles[j]) + { + GFXTextureObject * texObj = baseInstanceInfo->mLightmapHandles[j]; + texObj->mBitmap = pBitmaps[i][j]; + } + else + baseInstanceInfo->mLightmapHandles[j].set( pBitmaps[i][j], &GFXDefaultPersistentProfile, false, String("Interior Lightmap Handle") ); + } + } + + delete [] pBitmaps; + return(true); +} + +//------------------------------------------------------------------------------ +GFXTexHandle &InteriorLMManager::getHandle(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::getHandle: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::getHandle: invalid instance handle"); + AssertFatal(index < mInteriors[interiorHandle]->mNumLightmaps, "InteriorLMManager::getHandle: invalid texture index"); + + // valid? if not, then get base lightmap handle + if(!mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]) + { + AssertFatal(mInteriors[interiorHandle]->mInstances[0]->mLightmapHandles[index], "InteriorLMManager::getHandle: invalid base texture handle"); + return(mInteriors[interiorHandle]->mInstances[0]->mLightmapHandles[index]); + } + return(mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]); +} + +GBitmap * InteriorLMManager::getBitmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::getBitmap: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::getBitmap: invalid instance handle"); + AssertFatal(index < mInteriors[interiorHandle]->mNumLightmaps, "InteriorLMManager::getBitmap: invalid texture index"); + + if(!mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]) + { + AssertFatal(mInteriors[interiorHandle]->mInstances[0]->mLightmapHandles[index], "InteriorLMManager::getBitmap: invalid base texture handle"); + return(mInteriors[interiorHandle]->mInstances[0]->mLightmapHandles[index]->getBitmap()); + } + + return(mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]->getBitmap()); +} + +Vector & InteriorLMManager::getHandles(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::getHandles: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::getHandles: invalid instance handle"); + return(mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles); +} + +//------------------------------------------------------------------------------ + +void InteriorLMManager::clearLightmaps(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::clearLightmaps: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::clearLightmaps: invalid instance handle"); + + for(U32 i = 0; i < mInteriors[interiorHandle]->mNumLightmaps; i++) + mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[i] = 0; +} + +//------------------------------------------------------------------------------ +GFXTexHandle &InteriorLMManager::duplicateBaseLightmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index) +{ + AssertFatal(interiorHandle < mInteriors.size(), "InteriorLMManager::duplicateBaseLightmap: invalid interior handle"); + AssertFatal(instanceHandle < mInteriors[interiorHandle]->mInstances.size(), "InteriorLMManager::duplicateBaseLightmap: invalid instance handle"); + AssertFatal(index < mInteriors[interiorHandle]->mNumLightmaps, "InteriorLMManager::duplicateBaseLightmap: invalid texture index"); + + // already exists? + GFXTexHandle texHandle = mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]; + if(texHandle && texHandle->getBitmap() ) + return mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]; + + AssertFatal(mInteriors[interiorHandle]->mInstances[0]->mLightmapHandles[index], "InteriorLMManager::duplicateBaseLightmap: invalid base handle"); + + // copy it + GBitmap * src = mInteriors[interiorHandle]->mInstances[0]->mLightmapHandles[index]->getBitmap(); + GBitmap * dest = new GBitmap(*src); + + // don't want this texture to be downloaded yet (SceneLighting will take care of that) + GFXTexHandle tHandle( dest, &GFXDefaultPersistentProfile, true, String("Interior Lightmap Handle 2") ); + mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index] = tHandle; + return mInteriors[interiorHandle]->mInstances[instanceHandle]->mLightmapHandles[index]; +} diff --git a/interior/interiorLMManager.h b/interior/interiorLMManager.h new file mode 100644 index 0000000..de3e30e --- /dev/null +++ b/interior/interiorLMManager.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORLMMANAGER_H_ +#define _INTERIORLMMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#include "gfx/gfxTextureHandle.h" + +class GBitmap; +class Interior; +class InteriorInstance; + +typedef U32 LM_HANDLE; + +class InteriorLMManager +{ + private: + + struct InstanceLMInfo { + InteriorInstance * mInstance; + LM_HANDLE * mHandlePtr; + Vector mLightmapHandles; + }; + + struct InteriorLMInfo { + Interior * mInterior; + LM_HANDLE * mHandlePtr; + U32 mNumLightmaps; + LM_HANDLE mBaseInstanceHandle; + Vector mInstances; + }; + + Vector mInteriors; + + public: + + InteriorLMManager(); + ~InteriorLMManager(); + + void destroyBitmaps(); + void destroyTextures(); + + void downloadGLTextures(); + void downloadGLTextures(LM_HANDLE interiorHandle); + bool loadBaseLightmaps(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle); + + void addInterior(LM_HANDLE & interiorHandle, U32 numLightmaps, Interior * interior); + void removeInterior(LM_HANDLE interiorHandle); + + void addInstance(LM_HANDLE interiorHandle, LM_HANDLE & instanceHandle, InteriorInstance * instance); + void removeInstance(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle); + void useBaseTextures(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle); + + void clearLightmaps(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle); + + GFXTexHandle &getHandle(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index); + Vector & getHandles(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle); + + // helper's + GFXTexHandle &duplicateBaseLightmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index); + GBitmap * getBitmap(LM_HANDLE interiorHandle, LM_HANDLE instanceHandle, U32 index); +}; + +extern InteriorLMManager gInteriorLMManager; + +#endif diff --git a/interior/interiorRender.cpp b/interior/interiorRender.cpp new file mode 100644 index 0000000..bf8ea60 --- /dev/null +++ b/interior/interiorRender.cpp @@ -0,0 +1,352 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interior.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightManager.h" + +#include "gfx/bitmap/gBitmap.h" +#include "math/mMatrix.h" +#include "math/mRect.h" +#include "core/bitVector.h" +#include "platform/profiler.h" +#include "gfx/gfxDevice.h" +#include "interior/interiorInstance.h" +#include "gfx/gfxTextureHandle.h" +#include "materials/materialList.h" +#include "materials/sceneData.h" +#include "materials/matInstance.h" +#include "materials/materialFeatureTypes.h" +#include "math/mathUtils.h" +#include "renderInstance/renderPassManager.h" +#include "core/frameAllocator.h" + +extern bool sgFogActive; +extern U16* sgActivePolyList; +extern U32 sgActivePolyListSize; +extern U16* sgFogPolyList; +extern U32 sgFogPolyListSize; +extern U16* sgEnvironPolyList; +extern U32 sgEnvironPolyListSize; + +Point3F sgOSCamPosition; + + +U32 sgRenderIndices[2048]; +U32 csgNumAllowedPoints = 256; + +extern "C" { + F32 texGen0[8]; + F32 texGen1[8]; + Point2F *fogCoordinatePointer; +} + + +//------------------------------------------------------------------------------ +// Set up render states for interor rendering +//------------------------------------------------------------------------------ +void Interior::setupRenderStates() +{ + // GFX2_RENDER_MERGE + // I suspect we don't need this anymore - MDF + GFX->setStateBlock(mInteriorSB); +} + +//------------------------------------------------------------------------------ +// Setup zone visibility +//------------------------------------------------------------------------------ +ZoneVisDeterminer Interior::setupZoneVis( InteriorInstance *intInst, SceneState *state ) +{ + + U32 zoneOffset = intInst->getZoneRangeStart() != 0xFFFFFFFF ? intInst->getZoneRangeStart() : 0; + + U32 baseZone = 0xFFFFFFFF; + + if (intInst->getNumCurrZones() == 1) + { + baseZone = intInst->getCurrZone(0); + } + else + { + for (U32 i = 0; i < intInst->getNumCurrZones(); i++) + { + if (state->getZoneState(intInst->getCurrZone(i)).render == true) + { + if (baseZone == 0xFFFFFFFF) { + baseZone = intInst->getCurrZone(i); + break; + } + } + } + if (baseZone == 0xFFFFFFFF) + { + baseZone = intInst->getCurrZone(0); + } + } + + ZoneVisDeterminer zoneVis; + zoneVis.runFromState(state, zoneOffset, baseZone); + return zoneVis; +} + +//------------------------------------------------------------------------------ +// Setup scenegraph data structure for materials +//------------------------------------------------------------------------------ +SceneGraphData Interior::setupSceneGraphInfo( InteriorInstance *intInst, + SceneState *state ) +{ + SceneGraphData sgData; + + // TODO: This sucks... interiors only get sunlight? + LightManager* lm = gClientSceneGraph->getLightManager(); + sgData.lights[0] = lm->getSpecialLight( LightManager::slSunLightType ); + + // fill in interior's transform + sgData.objTrans = intInst->getTransform(); + + // fog + sgData.setFogParams( gClientSceneGraph->getFogData() ); + + // misc + // NOTICE: SFXBB is removed and refraction is disabled! + //sgData.backBuffTex = GFX->getSfxBackBuffer(); + + return sgData; +} + +//------------------------------------------------------------------------------ +// Render zone RenderNode +//------------------------------------------------------------------------------ +void Interior::renderZoneNode( SceneState *state, + RenderNode &node, + InteriorInstance *intInst, + SceneGraphData &sgData, + MeshRenderInst *coreRi ) +{ + BaseMatInstance *matInst = node.matInst; + if ( !matInst ) + return; + + MeshRenderInst *ri = state->getRenderPass()->allocInst(); + *ri = *coreRi; + + LightManager* lm = gClientSceneGraph->getLightManager(); + ri->lights[0] = lm->getSpecialLight( LightManager::slSunLightType ); + + // setup lightmap + if (state->getLightManager() && + dStricmp(state->getLightManager()->getId(), "BLM") == 0) + { + if ( node.lightMapIndex != U8(-1) && + matInst->getFeatures().hasFeature( MFT_LightMap ) ) + ri->lightmap = gInteriorLMManager.getHandle(mLMHandle, intInst->getLMHandle(), node.lightMapIndex ); + } + + ri->matInst = matInst; + ri->primBuffIndex = node.primInfoIndex; + + if ( matInst->getMaterial()->isTranslucent() ) + { + ri->type = RenderPassManager::RIT_Translucent; + ri->translucentSort = true; + ri->sortDistSq = intInst->getRenderWorldBox().getSqDistanceToPoint( state->getCameraPosition() ); + } + + // Sort by the material then the normal map or vertex buffer! + ri->defaultKey = (U32)matInst; + ri->defaultKey2 = ri->lightmap ? (U32)ri->lightmap : (U32)ri->vertBuff; + + state->getRenderPass()->addInst( ri ); +} + +//------------------------------------------------------------------------------ +// Render zone RenderNode +//------------------------------------------------------------------------------ +void Interior::renderReflectNode( SceneState *state, + ReflectRenderNode &node, + InteriorInstance *intInst, + SceneGraphData &sgData, + MeshRenderInst *coreRi ) +{ + BaseMatInstance *matInst = node.matInst; + if ( !matInst ) + return; + + MeshRenderInst *ri = state->getRenderPass()->allocInst(); + *ri = *coreRi; + + ri->vertBuff = &mReflectVertBuff; + ri->primBuff = &mReflectPrimBuff; + + // use sgData.backBuffer to transfer the reflect texture to the materials + PlaneReflector *rp = &intInst->mPlaneReflectors[ node.reflectPlaneIndex ]; + ri->reflectTex = rp->reflectTex; + ri->reflective = true; + + // setup lightmap + if (state->getLightManager() && + dStricmp(state->getLightManager()->getId(), "BLM") == 0) + { + if ( node.lightMapIndex != U8(-1) && + matInst->getFeatures().hasFeature( MFT_LightMap ) ) + ri->lightmap = gInteriorLMManager.getHandle(mLMHandle, intInst->getLMHandle(), node.lightMapIndex ); + } + + ri->matInst = matInst; + ri->primBuffIndex = node.primInfoIndex; + + // Sort by the material then the normal map or vertex buffer! + ri->defaultKey = (U32)matInst; + ri->defaultKey2 = ri->lightmap ? (U32)ri->lightmap : (U32)ri->vertBuff; + + state->getRenderPass()->addInst( ri ); +} + + +//------------------------------------------------------------------------------ +// Setup the rendering +//------------------------------------------------------------------------------ +void Interior::setupRender( InteriorInstance *intInst, + SceneState *state, + MeshRenderInst *coreRi, const MatrixF* worldToCamera ) +{ + // Set the vertex and primitive buffers + coreRi->vertBuff = &mVertBuff; + coreRi->primBuff = &mPrimBuff; + + // Grab our render transform and scale it + MatrixF objectToWorld = intInst->getRenderTransform(); + objectToWorld.scale( intInst->getScale() ); + + coreRi->objectToWorld = state->getRenderPass()->allocUniqueXform(objectToWorld); + coreRi->worldToCamera = state->getRenderPass()->allocUniqueXform(*worldToCamera); // This is handed down from SceneState::renderCurrentImages() + coreRi->projection = state->getRenderPass()->allocSharedXform(RenderPassManager::Projection); + + coreRi->type = RenderPassManager::RIT_Interior; + + // NOTICE: SFXBB is removed and refraction is disabled! + //coreRi->backBuffTex = GFX->getSfxBackBuffer(); +} + + +//------------------------------------------------------------------------------ +// Render +//------------------------------------------------------------------------------ +void Interior::prepBatchRender( InteriorInstance *intInst, SceneState *state, const MatrixF* worldToCamera ) +{ + // coreRi - used as basis for subsequent interior ri allocations + MeshRenderInst *coreRi = state->getRenderPass()->allocInst(); + SceneGraphData sgData; + setupRender( intInst, state, coreRi, worldToCamera ); + ZoneVisDeterminer zoneVis = setupZoneVis( intInst, state ); + +// GFX2_RENDER_MERGE +#ifndef TORQUE_SHIPPING + if( smRenderMode != 0 ) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(intInst, &InteriorInstance::renderObject); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst( ri ); + return; + } +#endif + + // render zones + for( U32 i=0; iisReflectPass() ) + continue; + + for( U32 j=0; jrender( state, *coreRi, getLMHandle(), + intInst->getLMHandle(), intInst ); + + // render reflective surfaces + if ( !state->isReflectPass() ) + { + renderLights(state, intInst, sgData, coreRi, zoneVis); + + for( U32 i=0; iinteriorInstInit(intInst)) + return; + + // build the render instances... + for(U32 z=0; z -1) + zoneid += intInst->getZoneRangeStart();// only zone managers... + else + zoneid = intInst->getCurrZone(0);// if not what zone is it in... + + if (!smLightPlugin->zoneInit(zoneid)) + continue; + + static Vector sRenderList; + sRenderList.clear(); + + for(U32 j=0; jgetRenderPass()->allocInst(); + *ri = *coreRi; + ri->type = RenderPassManager::RIT_InteriorDynamicLighting; + ri->matInst = node.matInst; + ri->primBuffIndex = node.primInfoIndex; + + sRenderList.push_back(ri); + } + smLightPlugin->processRI(state, sRenderList); + } + */ +} + + diff --git a/interior/interiorRes.cpp b/interior/interiorRes.cpp new file mode 100644 index 0000000..d0bf92e --- /dev/null +++ b/interior/interiorRes.cpp @@ -0,0 +1,335 @@ +//----------------------------------------------------------------------------- +// Torque 3D Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "core/stream/stream.h" +#include "interior/interior.h" +#include "interior/interiorResObjects.h" +#include "gfx/bitmap/gBitmap.h" +#include "interior/forceField.h" + +#include "interior/interiorRes.h" + +const U32 InteriorResource::smFileVersion = 44; + +//-------------------------------------------------------------------------- +InteriorResource::InteriorResource() +{ + VECTOR_SET_ASSOCIATION(mDetailLevels); + VECTOR_SET_ASSOCIATION(mSubObjects); + VECTOR_SET_ASSOCIATION(mTriggers); + //VECTOR_SET_ASSOCIATION(mPaths); + VECTOR_SET_ASSOCIATION(mInteriorPathFollowers); + VECTOR_SET_ASSOCIATION(mForceFields); + VECTOR_SET_ASSOCIATION(mAISpecialNodes); + + mPreviewBitmap = NULL; +} + +InteriorResource::~InteriorResource() +{ + U32 i; + + for (i = 0; i < mDetailLevels.size(); i++) + delete mDetailLevels[i]; + for (i = 0; i < mSubObjects.size(); i++) + delete mSubObjects[i]; + for (i = 0; i < mTriggers.size(); i++) + delete mTriggers[i]; + for (i = 0; i < mInteriorPathFollowers.size(); i++) + delete mInteriorPathFollowers[i]; + for (i = 0; i < mForceFields.size(); i++) + delete mForceFields[i]; + for (i = 0; i < mAISpecialNodes.size(); i++) + delete mAISpecialNodes[i]; + for (i = 0; i < mGameEntities.size(); i++) + delete mGameEntities[i]; + + delete mPreviewBitmap; + mPreviewBitmap = NULL; +} + +bool InteriorResource::read(Stream& stream) +{ + AssertFatal(stream.hasCapability(Stream::StreamRead), "Interior::read: non-read capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::read: Error, stream in inconsistent state"); + + U32 i; + + // Version this stream + U32 fileVersion; + stream.read(&fileVersion); + if (fileVersion != smFileVersion) { + Con::errorf(ConsoleLogEntry::General, "InteriorResource::read: incompatible file version found."); + return false; + } + + // Handle preview + bool previewIncluded = false; + stream.read(&previewIncluded); + if (previewIncluded) { + GBitmap bmp; + bmp.readBitmap("png",stream); + } + + // Details + U32 numDetailLevels; + stream.read(&numDetailLevels); + mDetailLevels.setSize(numDetailLevels); + for (i = 0; i < mDetailLevels.size(); i++) + mDetailLevels[i] = NULL; + + for (i = 0; i < mDetailLevels.size(); i++) { + mDetailLevels[i] = new Interior; + if (mDetailLevels[i]->read(stream) == false) { + Con::errorf(ConsoleLogEntry::General, "Unable to read detail level %d in interior resource", i); + return false; + } + } + + // Subobjects: mirrors, translucencies + U32 numSubObjects; + stream.read(&numSubObjects); + mSubObjects.setSize(numSubObjects); + for (i = 0; i < mSubObjects.size(); i++) + mSubObjects[i] = NULL; + + for (i = 0; i < mSubObjects.size(); i++) { + mSubObjects[i] = new Interior; + if (mSubObjects[i]->read(stream) == false) { + AssertISV(false, avar("Unable to read subobject %d in interior resource", i)); + return false; + } + } + + // Triggers + U32 numTriggers; + stream.read(&numTriggers); + mTriggers.setSize(numTriggers); + for (i = 0; i < mTriggers.size(); i++) + mTriggers[i] = NULL; + + for (i = 0; i < mTriggers.size(); i++) { + mTriggers[i] = new InteriorResTrigger; + if (mTriggers[i]->read(stream) == false) { + AssertISV(false, avar("Unable to read trigger %d in interior resource", i)); + return false; + } + } + + U32 numChildren; + stream.read(&numChildren); + mInteriorPathFollowers.setSize(numChildren); + for (i = 0; i < mInteriorPathFollowers.size(); i++) + mInteriorPathFollowers[i] = NULL; + + for (i = 0; i < mInteriorPathFollowers.size(); i++) { + mInteriorPathFollowers[i] = new InteriorPathFollower; + if (mInteriorPathFollowers[i]->read(stream) == false) { + AssertISV(false, avar("Unable to read child %d in interior resource", i)); + return false; + } + } + + U32 numFields; + stream.read(&numFields); + mForceFields.setSize(numFields); + for (i = 0; i < mForceFields.size(); i++) + mForceFields[i] = NULL; + + for (i = 0; i < mForceFields.size(); i++) { + mForceFields[i] = new ForceField; + if (mForceFields[i]->read(stream) == false) { + AssertISV(false, avar("Unable to read field %d in interior resource", i)); + return false; + } + } + + U32 numSpecNodes; + stream.read(&numSpecNodes); + mAISpecialNodes.setSize(numSpecNodes); + for (i = 0; i < mAISpecialNodes.size(); i++) + mAISpecialNodes[i] = NULL; + + for (i = 0; i < mAISpecialNodes.size(); i++) { + mAISpecialNodes[i] = new AISpecialNode; + if (mAISpecialNodes[i]->read(stream) == false) { + AssertISV(false, avar("Unable to read SpecNode %d in interior resource", i)); + return false; + } + } + + U32 dummyInt; + stream.read(&dummyInt); + if (dummyInt == 1) + { + if (mDetailLevels.size() != 0) + getDetailLevel(0)->readVehicleCollision(stream); + } + + // For expansion purposes + stream.read(&dummyInt); + if(dummyInt == 2) + { + U32 numGameEnts; + stream.read(&numGameEnts); + mGameEntities.setSize(numGameEnts); + for (i = 0; i < numGameEnts; i++) + mGameEntities[i] = new ItrGameEntity; + + for (i = 0; i < numGameEnts; i++) { + if (mGameEntities[i]->read(stream) == false) { + AssertISV(false, avar("Unable to read SpecNode %d in interior resource", i)); + return false; + } + } + stream.read(&dummyInt); + } + + return (stream.getStatus() == Stream::Ok); +} + +bool InteriorResource::write(Stream& stream) const +{ + AssertFatal(stream.hasCapability(Stream::StreamWrite), "Interior::write: non-write capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::write: Error, stream in inconsistent state"); + + // Version the stream + stream.write(smFileVersion); + + // Handle preview + // + if (mPreviewBitmap != NULL) { + stream.write(bool(true)); + mPreviewBitmap->writeBitmap("png",stream); + } else { + stream.write(bool(false)); + } + + // Write out the interiors + stream.write(mDetailLevels.size()); + U32 i; + for (i = 0; i < mDetailLevels.size(); i++) { + if (mDetailLevels[i]->write(stream) == false) { + AssertISV(false, "Unable to write detail level to stream"); + return false; + } + } + + stream.write(mSubObjects.size()); + for (i = 0; i < mSubObjects.size(); i++) { + if (mSubObjects[i]->write(stream) == false) { + AssertISV(false, "Unable to write subobject to stream"); + return false; + } + } + + stream.write(mTriggers.size()); + for (i = 0; i < mTriggers.size(); i++) { + if (mTriggers[i]->write(stream) == false) { + AssertISV(false, "Unable to write trigger to stream"); + return false; + } + } + + stream.write(mInteriorPathFollowers.size()); + for (i = 0; i < mInteriorPathFollowers.size(); i++) { + if (mInteriorPathFollowers[i]->write(stream) == false) { + AssertISV(false, avar("Unable to write child %d in interior resource", i)); + return false; + } + } + + stream.write(mForceFields.size()); + for (i = 0; i < mForceFields.size(); i++) { + if (mForceFields[i]->write(stream) == false) { + AssertISV(false, avar("Unable to write field %d in interior resource", i)); + return false; + } + } + + stream.write(mAISpecialNodes.size()); + for (i = 0; i < mAISpecialNodes.size(); i++) { + if (mAISpecialNodes[i]->write(stream) == false) { + AssertISV(false, avar("Unable to write SpecNode %d in interior resource", i)); + return false; + } + } + + stream.write(U32(1)); + if (mDetailLevels.size() != 0) + const_cast(mDetailLevels[0])->writeVehicleCollision(stream); + + // For expansion purposes + if (mGameEntities.size()) + { + stream.write(U32(2)); + stream.write(mGameEntities.size()); + for(i = 0; i < mGameEntities.size(); i++) + { + if (mGameEntities[i]->write(stream) == false) { + AssertISV(false, avar("Unable to write GameEnt %d in interior resource", i)); + return false; + } + } + } + stream.write(U32(0)); + + return (stream.getStatus() == Stream::Ok); +} + +GBitmap* InteriorResource::extractPreview(Stream& stream) +{ + AssertFatal(stream.hasCapability(Stream::StreamRead), "Interior::read: non-read capable stream passed"); + AssertFatal(stream.getStatus() == Stream::Ok, "Interior::read: Error, stream in inconsistent state"); + + // Version this stream + U32 fileVersion; + stream.read(&fileVersion); + if (fileVersion != smFileVersion) { + Con::errorf(ConsoleLogEntry::General, "InteriorResource::read: incompatible file version found."); + return NULL; + } + + // Handle preview + bool previewIncluded = false; + stream.read(&previewIncluded); + if (previewIncluded) { + GBitmap* pBmp = new GBitmap; + if (pBmp->readBitmap("png",stream) == true) + return pBmp; + + delete pBmp; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Interior Resource constructor +template<> void *Resource::create(const Torque::Path &path) +{ + FileStream stream; + + stream.open( path.getFullPath(), Torque::FS::File::Read ); + + if ( stream.getStatus() != Stream::Ok ) + return NULL; + + InteriorResource* pResource = new InteriorResource; + + if (pResource->read(stream) == true) + return pResource; + else + { + delete pResource; + return NULL; + } +} + +template<> ResourceBase::Signature Resource::signature() +{ + return MakeFourCC('t','d','i','f'); +} diff --git a/interior/interiorRes.h b/interior/interiorRes.h new file mode 100644 index 0000000..6fbc504 --- /dev/null +++ b/interior/interiorRes.h @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORRES_H_ +#define _INTERIORRES_H_ + +class Stream; +class Interior; +class GBitmap; +class InteriorResTrigger; +class InteriorPath; +class InteriorPathFollower; +class ForceField; +class AISpecialNode; +class ItrGameEntity; + +class InteriorResource +{ + static const U32 smFileVersion; + + protected: + Vector mDetailLevels; + Vector mSubObjects; + Vector mTriggers; + Vector mInteriorPathFollowers; + Vector mForceFields; + Vector mAISpecialNodes; + Vector mGameEntities; + + GBitmap* mPreviewBitmap; + + public: + InteriorResource(); + ~InteriorResource(); + + bool read(Stream& stream); + bool write(Stream& stream) const; + static GBitmap* extractPreview(Stream&); + + S32 getNumDetailLevels() const; + S32 getNumSubObjects() const; + S32 getNumTriggers() const; + S32 getNumInteriorPathFollowers() const; + S32 getNumForceFields() const; + S32 getNumSpecialNodes() const; + S32 getNumGameEntities() const; + + Interior* getDetailLevel(const U32); + Interior* getSubObject(const U32); + InteriorResTrigger* getTrigger(const U32); + InteriorPathFollower* getInteriorPathFollower(const U32); + ForceField* getForceField(const U32); + AISpecialNode* getSpecialNode(const U32); + ItrGameEntity* getGameEntity(const U32); +}; + +//-------------------------------------------------------------------------- +inline S32 InteriorResource::getNumDetailLevels() const +{ + return mDetailLevels.size(); +} + +inline S32 InteriorResource::getNumSubObjects() const +{ + return mSubObjects.size(); +} + +inline S32 InteriorResource::getNumTriggers() const +{ + return mTriggers.size(); +} + +inline S32 InteriorResource::getNumSpecialNodes() const +{ + return mAISpecialNodes.size(); +} + +inline S32 InteriorResource::getNumGameEntities() const +{ + return mGameEntities.size(); +} + +inline S32 InteriorResource::getNumInteriorPathFollowers() const +{ + return mInteriorPathFollowers.size(); +} + +inline S32 InteriorResource::getNumForceFields() const +{ + return mForceFields.size(); +} + +inline Interior* InteriorResource::getDetailLevel(const U32 idx) +{ + AssertFatal(idx < getNumDetailLevels(), "Error, out of bounds detail level!"); + + if (idx < getNumDetailLevels()) + return mDetailLevels[idx]; + else + return NULL; +} + +inline Interior* InteriorResource::getSubObject(const U32 idx) +{ + AssertFatal(idx < getNumSubObjects(), "Error, out of bounds subObject!"); + + return mSubObjects[idx]; +} + +inline InteriorResTrigger* InteriorResource::getTrigger(const U32 idx) +{ + AssertFatal(idx < getNumTriggers(), "Error, out of bounds trigger!"); + + return mTriggers[idx]; +} + +inline InteriorPathFollower* InteriorResource::getInteriorPathFollower(const U32 idx) +{ + AssertFatal(idx < getNumInteriorPathFollowers(), "Error, out of bounds path follower!"); + + return mInteriorPathFollowers[idx]; +} + +inline ForceField* InteriorResource::getForceField(const U32 idx) +{ + AssertFatal(idx < getNumForceFields(), "Error, out of bounds force field!"); + + return mForceFields[idx]; +} + +inline AISpecialNode* InteriorResource::getSpecialNode(const U32 idx) +{ + AssertFatal(idx < getNumSpecialNodes(), "Error, out of bounds Special Nodes!"); + + return mAISpecialNodes[idx]; +} + +inline ItrGameEntity* InteriorResource::getGameEntity(const U32 idx) +{ + AssertFatal(idx < getNumGameEntities(), "Error, out of bounds Game ENts!"); + + return mGameEntities[idx]; +} + +#endif // _H_INTERIORRES_ + diff --git a/interior/interiorResObjects.cpp b/interior/interiorResObjects.cpp new file mode 100644 index 0000000..a08d9b5 --- /dev/null +++ b/interior/interiorResObjects.cpp @@ -0,0 +1,229 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/interiorResObjects.h" +#include "core/stream/stream.h" +#include "math/mathIO.h" + +//-------------------------------------------------------------------------- +//-------------------------------------- +// + +void InteriorDict::read(Stream &stream) +{ + U32 sz; + stream.read(&sz); + setSize(sz); + for(U32 i = 0; i < sz; i++) + { + InteriorDictEntry e; + stream.readString(e.name); + stream.readString(e.value); + (*this)[i] = e; + } +} + +void InteriorDict::write(Stream &stream) const +{ + U32 sz = size(); + stream.write(sz); + for(U32 i = 0; i < sz; i++) + { + stream.writeString((*this)[i].name); + stream.writeString((*this)[i].value); + } +} + +bool InteriorResTrigger::read(Stream& stream) +{ + U32 i, size; + stream.readString(mName); + mDataBlock = stream.readSTString(); + mDictionary.read(stream); + + // Read the polyhedron + stream.read(&size); + mPolyhedron.pointList.setSize(size); + for (i = 0; i < mPolyhedron.pointList.size(); i++) + mathRead(stream, &mPolyhedron.pointList[i]); + + stream.read(&size); + mPolyhedron.planeList.setSize(size); + for (i = 0; i < mPolyhedron.planeList.size(); i++) + mathRead(stream, &mPolyhedron.planeList[i]); + + stream.read(&size); + mPolyhedron.edgeList.setSize(size); + for (i = 0; i < mPolyhedron.edgeList.size(); i++) { + Polyhedron::Edge& rEdge = mPolyhedron.edgeList[i]; + + stream.read(&rEdge.face[0]); + stream.read(&rEdge.face[1]); + stream.read(&rEdge.vertex[0]); + stream.read(&rEdge.vertex[1]); + } + + // And the offset + mathRead(stream, &mOffset); + + return (stream.getStatus() == Stream::Ok); +} + +bool InteriorResTrigger::write(Stream& stream) const +{ + U32 i; + + stream.writeString(mName); + stream.writeString(mDataBlock); + mDictionary.write(stream); + + // Write the polyhedron + stream.write(mPolyhedron.pointList.size()); + for (i = 0; i < mPolyhedron.pointList.size(); i++) + mathWrite(stream, mPolyhedron.pointList[i]); + + stream.write(mPolyhedron.planeList.size()); + for (i = 0; i < mPolyhedron.planeList.size(); i++) + mathWrite(stream, mPolyhedron.planeList[i]); + + stream.write(mPolyhedron.edgeList.size()); + for (i = 0; i < mPolyhedron.edgeList.size(); i++) { + const Polyhedron::Edge& rEdge = mPolyhedron.edgeList[i]; + + stream.write(rEdge.face[0]); + stream.write(rEdge.face[1]); + stream.write(rEdge.vertex[0]); + stream.write(rEdge.vertex[1]); + } + + // And the offset + mathWrite(stream, mOffset); + + return (stream.getStatus() == Stream::Ok); +} + +InteriorPathFollower::InteriorPathFollower() +{ + VECTOR_SET_ASSOCIATION( mTriggerIds ); + VECTOR_SET_ASSOCIATION( mWayPoints ); + + mName = ""; + mPathIndex = 0; + mOffset.set(0, 0, 0); +} + +InteriorPathFollower::~InteriorPathFollower() +{ + +} + +bool InteriorPathFollower::read(Stream& stream) +{ + mName = stream.readSTString(); + mDataBlock = stream.readSTString(); + stream.read(&mInteriorResIndex); + mathRead(stream, &mOffset); + mDictionary.read(stream); + + U32 numTriggers; + stream.read(&numTriggers); + mTriggerIds.setSize(numTriggers); + for (U32 i = 0; i < mTriggerIds.size(); i++) + stream.read(&mTriggerIds[i]); + + U32 numWayPoints; + stream.read(&numWayPoints); + mWayPoints.setSize(numWayPoints); + for(U32 i = 0; i < numWayPoints; i++) + { + mathRead(stream, &mWayPoints[i].pos); + mathRead(stream, &mWayPoints[i].rot); + stream.read(&mWayPoints[i].msToNext); + stream.read(&mWayPoints[i].smoothingType); + } + stream.read(&mTotalMS); + return (stream.getStatus() == Stream::Ok); +} + +bool InteriorPathFollower::write(Stream& stream) const +{ + stream.writeString(mName); + stream.writeString(mDataBlock); + stream.write(mInteriorResIndex); + mathWrite(stream, mOffset); + mDictionary.write(stream); + + stream.write(mTriggerIds.size()); + for (U32 i = 0; i < mTriggerIds.size(); i++) + stream.write(mTriggerIds[i]); + + stream.write(U32(mWayPoints.size())); + for (U32 i = 0; i < mWayPoints.size(); i++) { + mathWrite(stream, mWayPoints[i].pos); + mathWrite(stream, mWayPoints[i].rot); + stream.write(mWayPoints[i].msToNext); + stream.write(mWayPoints[i].smoothingType); + } + stream.write(mTotalMS); + + return (stream.getStatus() == Stream::Ok); +} + +AISpecialNode::AISpecialNode() +{ + mName = ""; + mPos.set(0, 0, 0); +} + +AISpecialNode::~AISpecialNode() +{ +} + +bool AISpecialNode::read(Stream& stream) +{ + mName = stream.readSTString(); + mathRead(stream, &mPos); + + return (stream.getStatus() == Stream::Ok); +} + +bool AISpecialNode::write(Stream& stream) const +{ + stream.writeString(mName); + mathWrite(stream, mPos); + + return (stream.getStatus() == Stream::Ok); +} + +ItrGameEntity::ItrGameEntity() +{ + mDataBlock = ""; + mGameClass = ""; + mPos.set(0, 0, 0); +} + +ItrGameEntity::~ItrGameEntity() +{ +} + +bool ItrGameEntity::read(Stream& stream) +{ + mDataBlock = stream.readSTString(); + mGameClass = stream.readSTString(); + mathRead(stream, &mPos); + mDictionary.read(stream); + + return (stream.getStatus() == Stream::Ok); +} + +bool ItrGameEntity::write(Stream& stream) const +{ + stream.writeString(mDataBlock); + stream.writeString(mGameClass); + mathWrite(stream, mPos); + mDictionary.write(stream); + + return (stream.getStatus() == Stream::Ok); +} diff --git a/interior/interiorResObjects.h b/interior/interiorResObjects.h new file mode 100644 index 0000000..f13f85a --- /dev/null +++ b/interior/interiorResObjects.h @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORRESOBJECTS_H_ +#define _INTERIORRESOBJECTS_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _POLYHEDRON_H_ +#include "collision/polyhedron.h" +#endif + +class Stream; + +struct InteriorDictEntry +{ + char name[256]; + char value[256]; +}; + +class InteriorDict : public Vector +{ +public: + void read(Stream& stream); + void write(Stream& stream) const; +}; + +class InteriorResTrigger +{ + public: + enum Constants { + MaxNameChars = 255 + }; + + char mName[MaxNameChars+1]; + StringTableEntry mDataBlock; + InteriorDict mDictionary; + + Point3F mOffset; + Polyhedron mPolyhedron; + + public: + InteriorResTrigger() { } + + bool read(Stream& stream); + bool write(Stream& stream) const; +}; + +class InteriorPathFollower +{ + public: + struct WayPoint { + Point3F pos; + QuatF rot; + U32 msToNext; + U32 smoothingType; + }; + StringTableEntry mName; + StringTableEntry mDataBlock; + U32 mInteriorResIndex; + U32 mPathIndex; + Point3F mOffset; + Vector mTriggerIds; + Vector mWayPoints; + U32 mTotalMS; + InteriorDict mDictionary; + + public: + InteriorPathFollower(); + ~InteriorPathFollower(); + + bool read(Stream& stream); + bool write(Stream& stream) const; +}; + + +class AISpecialNode +{ + public: + enum + { + chute = 0, + }; + + public: + StringTableEntry mName; + Point3F mPos; + //U32 mType; + + public: + AISpecialNode(); + ~AISpecialNode(); + + bool read(Stream& stream); + bool write(Stream& stream) const; + +}; + +class ItrGameEntity +{ + public: + StringTableEntry mDataBlock; + StringTableEntry mGameClass; + Point3F mPos; + InteriorDict mDictionary; + + public: + ItrGameEntity(); + ~ItrGameEntity(); + + bool read(Stream& stream); + bool write(Stream& stream) const; + +}; + +#endif // _H_INTERIORRESOBJECTS_ diff --git a/interior/interiorSimpleMesh.cpp b/interior/interiorSimpleMesh.cpp new file mode 100644 index 0000000..9c4e367 --- /dev/null +++ b/interior/interiorSimpleMesh.cpp @@ -0,0 +1,623 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "interior/interiorSimpleMesh.h" + +#include "interior/interiorLMManager.h" +#include "interior/interior.h" +#include "console/console.h" +#include "sceneGraph/sceneObject.h" +#include "math/mathIO.h" +#include "materials/matInstance.h" +#include "materials/materialManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" + +Vector g_renderInstList; +Vector *InteriorSimpleMesh::renderInstList = &g_renderInstList;//new Vector(); + + +// Checks for polygon level collision with given planes +U32 _whichSide(PlaneF pln, Point3F* verts) +{ + Point3F currv, nextv; + S32 csd, nsd; + + // Find out which side the first vert is on + U32 side = PlaneF::On; + currv = verts[0]; + csd = pln.whichSide(currv); + if(csd != PlaneF::On) + side = csd; + + for(U32 k = 1; k < 3; k++) + { + nextv = verts[k]; + nsd = pln.whichSide(nextv); + if((csd == PlaneF::Back && nsd == PlaneF::Front) || + (csd == PlaneF::Front && nsd == PlaneF::Back)) + return 2; + else if (nsd != PlaneF::On) + side = nsd; + currv = nextv; + csd = nsd; + } + + // Loop back to the first vert + nextv = verts[0]; + nsd = pln.whichSide(nextv); + if((csd == PlaneF::Back && nsd == PlaneF::Front) || + (csd == PlaneF::Front && nsd == PlaneF::Back)) + return 2; + else if(nsd != PlaneF::On) + side = nsd; + return side; + +} + + +//bool InteriorSimpleMesh::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +//{ +// bool found = false; +// F32 best_t = F32_MAX; +// Point3F best_normal = Point3F(0, 0, 1); +// Point3F dir = end - start; +// +// for(U32 p=0; pt = best_t; +// info->normal = best_normal; +// info->material = 0; +// } +// +// return found; +//} + +bool InteriorSimpleMesh::castPlanes(PlaneF left, PlaneF right, PlaneF top, PlaneF bottom) +{ + for(U32 p=0; pmPrimitiveCount == packedPrimitives.size()), "Primitive mismatch"); + + renderInstList->clear(); + + for(S32 i=0; igetRenderPass()->allocInst(); + *inst = copyinst; + + inst->matInst = materialList->getMaterialInst(draw.diffuseIndex); + if(!inst->matInst) + inst->matInst = MATMGR->getWarningMatInstance(); + if(!inst->matInst) + continue; + + inst->primBuffIndex = i; + inst->primBuff = &primBuff; + inst->vertBuff = &vertBuff; + + if(draw.alpha) + { + inst->translucentSort = true; + inst->type = RenderPassManager::RIT_Translucent; + } + + inst->lightmap = gInteriorLMManager.getHandle(interiorlmhandle, instancelmhandle, draw.lightMapIndex); + + state->getRenderPass()->addInst(inst); + renderInstList->push_back(inst); + } + + if(lightingplugin && renderInstList->size() > 0) + { + if(lightingplugin->interiorInstInit(intInst, this)) + { + if(lightingplugin->allZoneInit()) + { + Vector &list = *renderInstList; + + // clone the origial instances to avoid damaging the originals' data + for(int i=0; isize(); i++) + { + MeshRenderInst *inst = state->getRenderPass()->allocInst(); + const MeshRenderInst *oldinst = list[i]; + *inst = *oldinst; + list[i] = inst; + } + + lightingplugin->processRI(state, list); + } + } + } + */ +} + +bool InteriorSimpleMesh::read(Stream& stream) +{ + // Simple serialization + S32 vectorSize = 0; + + // Primitives + stream.read(&vectorSize); + primitives.setSize(vectorSize); + for (U32 i = 0; i < primitives.size(); i++) + { + stream.read(&primitives[i].alpha); + stream.read(&primitives[i].texS); + stream.read(&primitives[i].texT); + stream.read(&primitives[i].diffuseIndex); + stream.read(&primitives[i].lightMapIndex); + stream.read(&primitives[i].start); + stream.read(&primitives[i].count); + + mathRead(stream, &primitives[i].lightMapEquationX); + mathRead(stream, &primitives[i].lightMapEquationY); + mathRead(stream, &primitives[i].lightMapOffset); + mathRead(stream, &primitives[i].lightMapSize); + } + + // Indices + stream.read(&vectorSize); + indices.setSize(vectorSize); + for (U32 i = 0; i < indices.size(); i++) + stream.read(&indices[i]); + + // Vertices + stream.read(&vectorSize); + verts.setSize(vectorSize); + for (U32 i = 0; i < verts.size(); i++) + mathRead(stream, &verts[i]); + + // Normals + stream.read(&vectorSize); + norms.setSize(vectorSize); + for (U32 i = 0; i < norms.size(); i++) + mathRead(stream, &norms[i]); + + // Diffuse UVs + stream.read(&vectorSize); + diffuseUVs.setSize(vectorSize); + for (U32 i = 0; i < diffuseUVs.size(); i++) + mathRead(stream, &diffuseUVs[i]); + + // Lightmap UVs + stream.read(&vectorSize); + lightmapUVs.setSize(vectorSize); + for (U32 i = 0; i < lightmapUVs.size(); i++) + mathRead(stream, &lightmapUVs[i]); + + // Material list + bool hasMaterialList = false; + stream.read(&hasMaterialList); + if (hasMaterialList) + { + // Since we are doing this externally to a TSShape read we need to + // make sure that our read version is the same as our write version. + // It is possible that it was changed along the way by a loaded TSShape. + TSShape::smReadVersion = 25; + + if (materialList) + delete materialList; + + materialList = new TSMaterialList; + materialList->read(stream); + } + else + materialList = NULL; + + // Diffuse bitmaps + stream.read(&vectorSize); + for (U32 i = 0; i < vectorSize; i++) + { + // need to read these + bool hasBitmap = false; + stream.read(&hasBitmap); + if(hasBitmap) + { + GBitmap* bitMap = new GBitmap; + bitMap->readBitmap("png",stream); + delete bitMap; + } + } + + // Misc data + stream.read(&hasSolid); + stream.read(&hasTranslucency); + mathRead(stream, &bounds); + mathRead(stream, &transform); + mathRead(stream, &scale); + + calculateBounds(); + buildBuffers(); + + return true; +} + +bool InteriorSimpleMesh::write(Stream& stream) const +{ + // Simple serialization + // Primitives + stream.write(primitives.size()); + for (U32 i = 0; i < primitives.size(); i++) + { + stream.write(primitives[i].alpha); + stream.write(primitives[i].texS); + stream.write(primitives[i].texT); + stream.write(primitives[i].diffuseIndex); + stream.write(primitives[i].lightMapIndex); + stream.write(primitives[i].start); + stream.write(primitives[i].count); + + mathWrite(stream, primitives[i].lightMapEquationX); + mathWrite(stream, primitives[i].lightMapEquationY); + mathWrite(stream, primitives[i].lightMapOffset); + mathWrite(stream, primitives[i].lightMapSize); + } + + // Indices + stream.write(indices.size()); + for (U32 i = 0; i < indices.size(); i++) + stream.write(indices[i]); + + // Vertices + stream.write(verts.size()); + for (U32 i = 0; i < verts.size(); i++) + mathWrite(stream, verts[i]); + + // Normals + stream.write(norms.size()); + for (U32 i = 0; i < norms.size(); i++) + mathWrite(stream, norms[i]); + + // Diffuse UVs + stream.write(diffuseUVs.size()); + for (U32 i = 0; i < diffuseUVs.size(); i++) + mathWrite(stream, diffuseUVs[i]); + + // Lightmap UVs + stream.write(lightmapUVs.size()); + for (U32 i = 0; i < lightmapUVs.size(); i++) + mathWrite(stream, lightmapUVs[i]); + + // Material list + if (materialList) + { + stream.write(true); + materialList->write(stream); + } + else + stream.write(false); + + // Diffuse bitmaps + if (!materialList) + stream.write(0); + else + { + stream.write(materialList->getMaterialCount()); + + for (U32 i = 0; i < materialList->getMaterialCount(); i++) + { + GFXTexHandle& handle = materialList->getMaterial(i); + + if (handle.isValid()) + { + GBitmap* bitMap = handle.getBitmap(); + + if (bitMap) + { + stream.write(true); + bitMap->writeBitmap("png",stream); + } + else + stream.write(false); + } + else + stream.write(false); + } + } + + // Misc data + stream.write(hasSolid); + stream.write(hasTranslucency); + mathWrite(stream, bounds); + mathWrite(stream, transform); + mathWrite(stream, scale); + + return true; +} + +void InteriorSimpleMesh::buildBuffers() +{ + bool flipped = false; + + MatrixF trans = transform; + trans.scale(scale); + + Point3F r0, r1, r2; + trans.getRow(0, &r0); + trans.getRow(1, &r1); + trans.getRow(2, &r2); + F32 det = r0.x * (r1.y * r2.z - r1.z * r2.y) - + r0.y * (r1.x * r2.z - r1.z * r2.x) + + r0.z * (r1.x * r2.y - r1.y * r2.x); + flipped = det < 0.0f; + + // setup the repack vectors + packedIndices.clear(); + packedPrimitives.clear(); + packedIndices.reserve(indices.size() * 2); + packedPrimitives.reserve(primitives.size()); + + Vector addedprim; + addedprim.setSize(primitives.size()); + dMemset(addedprim.address(), 0, (addedprim.size() * sizeof(bool))); + + Vector tang; + Vector binorm; + tang.setSize(verts.size()); + binorm.setSize(verts.size()); + dMemset(tang.address(), 0, (tang.size() * sizeof(Point3F))); + dMemset(binorm.address(), 0, (binorm.size() * sizeof(Point3F))); + + // fill the repack vectors + for(U32 p=0; p packedprims; + packedprims.setSize(packedPrimitives.size()); + + for(U32 i=0; i packedIndices[prim.start + ii]) + p.minIndex = packedIndices[prim.start + ii]; + if(maxindex < packedIndices[prim.start + ii]) + maxindex = packedIndices[prim.start + ii]; + } + + // D3D voodoo - not the actual numverts, only the max span (maxindex - minindex) - this needs a better variable name... + p.numVertices = (maxindex - p.minIndex) + 1; + } + + // create vb style sysmem buffer + Vector packedverts; + packedverts.setSize(verts.size()); + + // fill it + for(U32 i=0; i &tang, Vector &binorm) +{ + const Point3F& va = verts[i0]; + const Point3F& vb = verts[i1]; + const Point3F& vc = verts[i2]; + const Point2F& uva = diffuseUVs[i0]; + const Point2F& uvb = diffuseUVs[i1]; + const Point2F& uvc = diffuseUVs[i2]; + + float x1 = vb.x - va.x; + float x2 = vc.x - va.x; + float y1 = vb.y - va.y; + float y2 = vc.y - va.y; + float z1 = vb.z - va.z; + float z2 = vc.z - va.z; + float s1 = uvb.x - uva.x; + float s2 = uvc.x - uva.x; + float t1 = uvb.y - uva.y; + float t2 = uvc.y - uva.y; + + F32 denom = (s1 * t2 - s2 * t1); + if(fabs(denom) < 0.0001) + return; + + float r = 1.0F / denom; + Point3F s((t2 * x1 - t1 * x2) * r, + (t2 * y1 - t1 * y2) * r, + (t2 * z1 - t1 * z2) * r); + Point3F t((s1 * x2 - s2 * x1) * r, + (s1 * y2 - s2 * y1) * r, + (s1 * z2 - s2 * z1) * r); + + tang[i0] += s; + tang[i1] += s; + tang[i2] += s; + binorm[i0] += t; + binorm[i1] += t; + binorm[i2] += t; +} + +void InteriorSimpleMesh::packPrimitive(primitive &primnew, const primitive &primold, Vector &indicesnew, + bool flipped, Vector &tang, Vector &binorm) +{ + // convert from strip to list and add to primnew + for(U32 p=2; pload(InteriorTexture, path, false); + materialList->mapMaterials(); + + // GFX2_RENDER_MERGE + materialList->initMatInstances( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); + + return true; +} diff --git a/interior/interiorSimpleMesh.h b/interior/interiorSimpleMesh.h new file mode 100644 index 0000000..c7af1ae --- /dev/null +++ b/interior/interiorSimpleMesh.h @@ -0,0 +1,180 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORSIMPLEMESH_H_ +#define _INTERIORSIMPLEMESH_H_ + +#include "core/util/tVector.h" +//#include "dgl/dgl.h" +#include "math/mBox.h" +#include "core/stream/fileStream.h" +#include "ts/tsShapeInstance.h" +#include "renderInstance/renderPassManager.h" + + +class InteriorInstance; + + +class InteriorSimpleMesh +{ +public: + class primitive + { + public: + bool alpha; + U32 texS; + U32 texT; + S32 diffuseIndex; + S32 lightMapIndex; + U32 start; + U32 count; + + // used to relight the surface in-engine... + PlaneF lightMapEquationX; + PlaneF lightMapEquationY; + Point2I lightMapOffset; + Point2I lightMapSize; + + primitive() + { + alpha = false; + texS = GFXAddressWrap; + texT = GFXAddressWrap; + diffuseIndex = 0; + lightMapIndex = 0; + start = 0; + count = 0; + + lightMapEquationX = PlaneF(0, 0, 0, 0); + lightMapEquationY = PlaneF(0, 0, 0, 0); + lightMapOffset = Point2I(0, 0); + lightMapSize = Point2I(0, 0); + } + }; + + InteriorSimpleMesh() + { + VECTOR_SET_ASSOCIATION( packedIndices ); + VECTOR_SET_ASSOCIATION( packedPrimitives ); + VECTOR_SET_ASSOCIATION( indices ); + VECTOR_SET_ASSOCIATION( verts ); + VECTOR_SET_ASSOCIATION( norms ); + VECTOR_SET_ASSOCIATION( diffuseUVs ); + VECTOR_SET_ASSOCIATION( lightmapUVs ); + + materialList = NULL; + clear(); + } + ~InteriorSimpleMesh(){clear();} + void clear(bool wipeMaterials = true) + { + vertBuff = NULL; + primBuff = NULL; + packedIndices.clear(); + packedPrimitives.clear(); + + hasSolid = false; + hasTranslucency = false; + bounds = Box3F(-1, -1, -1, 1, 1, 1); + transform.identity(); + scale.set(1.0f, 1.0f, 1.0f); + + primitives.clear(); + indices.clear(); + verts.clear(); + norms.clear(); + diffuseUVs.clear(); + lightmapUVs.clear(); + + if(wipeMaterials && materialList) + delete materialList; + + if (wipeMaterials) + materialList = NULL; + } + + void render( SceneState* state, + const MeshRenderInst ©inst, + U32 interiorlmhandle, + U32 instancelmhandle, + InteriorInstance* intInst ); + + void calculateBounds() + { + bounds = Box3F(F32_MAX, F32_MAX, F32_MAX, -F32_MAX, -F32_MAX, -F32_MAX); + for(U32 i=0; i packedIndices; + Vector packedPrimitives;/// tri-list instead of strips + GFXVertexBufferHandle vertBuff; + GFXPrimitiveBufferHandle primBuff; + void buildBuffers(); + void buildTangent(U32 i0, U32 i1, U32 i2, Vector &tang, Vector &binorm); + void packPrimitive(primitive &primnew, const primitive &primold, Vector &indicesnew, + bool flipped, Vector &tang, Vector &binorm); + bool prepForRendering(const char *path); + + bool hasSolid; + bool hasTranslucency; + Box3F bounds; + MatrixF transform; + Point3F scale; + + Vector primitives; + + // same index relationship... + Vector indices; + Vector verts; + Vector norms; + Vector diffuseUVs; + Vector lightmapUVs; + + TSMaterialList *materialList; + + bool containsPrimitiveType(bool translucent) + { + for(U32 i=0; i *renderInstList; +}; + +#endif //_INTERIORSIMPLEMESH_H_ + diff --git a/interior/interiorSubObject.cpp b/interior/interiorSubObject.cpp new file mode 100644 index 0000000..cf68d7b --- /dev/null +++ b/interior/interiorSubObject.cpp @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "core/stream/stream.h" +#include "interior/interiorInstance.h" + +#include "interior/interiorSubObject.h" +#include "interior/mirrorSubObject.h" + + +InteriorSubObject::InteriorSubObject() +{ + mInteriorInstance = NULL; +} + +InteriorSubObject::~InteriorSubObject() +{ + mInteriorInstance = NULL; +} + +InteriorSubObject* InteriorSubObject::readISO(Stream& stream) +{ + U32 soKey; + stream.read(&soKey); + + InteriorSubObject* pObject = NULL; + switch (soKey) { + case MirrorSubObjectKey: + pObject = new MirrorSubObject; + break; + + default: + Con::errorf(ConsoleLogEntry::General, "Bad key in subObject stream!"); + return NULL; + }; + + if (pObject) { + bool readSuccess = pObject->_readISO(stream); + if (readSuccess == false) { + delete pObject; + pObject = NULL; + } + } + + return pObject; +} + +bool InteriorSubObject::writeISO(Stream& stream) const +{ + stream.write(getSubObjectKey()); + return _writeISO(stream); +} + +bool InteriorSubObject::_readISO(Stream& stream) +{ + return (stream.getStatus() == Stream::Ok); +} + +bool InteriorSubObject::_writeISO(Stream& stream) const +{ + return (stream.getStatus() == Stream::Ok); +} + +const MatrixF& InteriorSubObject::getSOTransform() const +{ + static const MatrixF csBadMatrix(true); + + if (mInteriorInstance != NULL) { + return mInteriorInstance->getTransform(); + } else { + AssertWarn(false, "Returning bad transform for subobject"); + return csBadMatrix; + } +} + +const Point3F& InteriorSubObject::getSOScale() const +{ + return mInteriorInstance->getScale(); +} + +InteriorInstance* InteriorSubObject::getInstance() +{ + return mInteriorInstance; +} + +void InteriorSubObject::noteTransformChange() +{ + // +} diff --git a/interior/interiorSubObject.h b/interior/interiorSubObject.h new file mode 100644 index 0000000..9b07fa6 --- /dev/null +++ b/interior/interiorSubObject.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _INTERIORSUBOBJECT_H_ +#define _INTERIORSUBOBJECT_H_ + +#ifndef _SCENESTATE_H_ +#include "sceneGraph/sceneState.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +class InteriorInstance; + +//class SubObjectRenderImage : public SceneRenderImage +//{ +// public: +// U32 mDetailLevel; +//}; + +class InteriorSubObject : public SceneObject +{ + typedef SceneObject Parent; + + protected: + InteriorInstance* mInteriorInstance; // Should NOT be set by derived except in clone + + protected: + enum SubObjectKeys { + TranslucentSubObjectKey = 0, + MirrorSubObjectKey = 1 + }; + + virtual U32 getSubObjectKey() const = 0; + virtual bool _readISO(Stream&); + virtual bool _writeISO(Stream&) const; + + InteriorInstance* getInstance(); + const MatrixF& getSOTransform() const; + const Point3F& getSOScale() const; + + public: + InteriorSubObject(); + virtual ~InteriorSubObject(); + + // Render control. A sub-object should return false from renderDetailDependant if + // it exists only at the level-0 detail level, ie, doors, elevators, etc., true + // if should only render at the interiors detail, ie, translucencies. + //virtual SubObjectRenderImage* getRenderImage(SceneState*, const Point3F& osPoint) = 0; + virtual bool renderDetailDependant() const = 0; + virtual U32 getZone() const = 0; + + virtual void noteTransformChange(); + virtual InteriorSubObject* clone(InteriorInstance*) const = 0; + + static InteriorSubObject* readISO(Stream&); + bool writeISO(Stream&) const; +}; + +#endif // _H_INTERIORSUBOBJECT_ diff --git a/interior/mirrorSubObject.cpp b/interior/mirrorSubObject.cpp new file mode 100644 index 0000000..0f438d8 --- /dev/null +++ b/interior/mirrorSubObject.cpp @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#include "interior/mirrorSubObject.h" +#include "interior/interiorInstance.h" +#include "interior/interior.h" +#include "materials/materialList.h" +#include "core/stream/stream.h" +#include "sceneGraph/sgUtil.h" + +IMPLEMENT_CONOBJECT(MirrorSubObject); + +//-------------------------------------------------------------------------- +MirrorSubObject::MirrorSubObject() +{ + mTypeMask = StaticObjectType; + + mInitialized = false; + mWhite = NULL; +} + +MirrorSubObject::~MirrorSubObject() +{ + delete mWhite; + mWhite = NULL; +} + +//-------------------------------------------------------------------------- +void MirrorSubObject::initPersistFields() +{ + Parent::initPersistFields(); + + // +} + +//-------------------------------------------------------------------------- +/* +void MirrorSubObject::renderObject(SceneState* state, SceneRenderImage* image) +{ +} +*/ + +//-------------------------------------------------------------------------- +void MirrorSubObject::transformModelview(const U32 portalIndex, const MatrixF& oldMV, MatrixF* pNewMV) +{ + AssertFatal(isInitialized() == true, "Error, we should have been initialized by this point!"); + AssertFatal(portalIndex == 0, "Error, we only have one portal!"); + + *pNewMV = oldMV; + pNewMV->mul(mReflectionMatrix); +} + + +//-------------------------------------------------------------------------- +void MirrorSubObject::transformPosition(const U32 portalIndex, Point3F& ioPosition) +{ + AssertFatal(isInitialized() == true, "Error, we should have been initialized by this point!"); + AssertFatal(portalIndex == 0, "Error, we only have one portal!"); + + mReflectionMatrix.mulP(ioPosition); +} + + +//-------------------------------------------------------------------------- +bool MirrorSubObject::computeNewFrustum(const U32 portalIndex, + const Frustum &oldFrustum, + const F64 nearPlane, + const F64 farPlane, + const RectI& oldViewport, + F64 *newFrustum, + RectI& newViewport, + const bool flippedMatrix) +{ + AssertFatal(isInitialized() == true, "Error, we should have been initialized by this point!"); + AssertFatal(portalIndex == 0, "Error, mirrortests only have one portal!"); + + Interior* interior = getInstance()->getDetailLevel(mDetailLevel); + + static Vector mirrorWindings; + mirrorWindings.setSize(surfaceCount); + + for (U32 i = 0; i < surfaceCount; i++) { + SGWinding& rSGWinding = mirrorWindings[i]; + const Interior::Surface& rSurface = interior->mSurfaces[surfaceStart + i]; + + U32 fanIndices[32]; + U32 numFanIndices = 0; + interior->collisionFanFromSurface(rSurface, fanIndices, &numFanIndices); + + for (U32 j = 0; j < numFanIndices; j++) + rSGWinding.points[j] = interior->mPoints[fanIndices[j]].point; + rSGWinding.numPoints = numFanIndices; + } + + MatrixF finalModelView; + finalModelView.mul(getSOTransform()); + finalModelView.scale(getSOScale()); + + return sgComputeNewFrustum(oldFrustum, nearPlane, farPlane, + oldViewport, + mirrorWindings.address(), mirrorWindings.size(), + finalModelView, + newFrustum, newViewport, + flippedMatrix); +} + + +//-------------------------------------------------------------------------- +void MirrorSubObject::openPortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState) +{ + +} + + +//-------------------------------------------------------------------------- +void MirrorSubObject::closePortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState) +{ +} + + +//-------------------------------------------------------------------------- +void MirrorSubObject::getWSPortalPlane(const U32 portalIndex, PlaneF* pPlane) +{ + AssertFatal(portalIndex == 0, "Error, mirrortests only have one portal!"); + + Interior* interior = getInstance()->getDetailLevel(mDetailLevel); + const Interior::Surface& rSurface = interior->mSurfaces[surfaceStart]; + + PlaneF temp = interior->getPlane(rSurface.planeIndex); + if (Interior::planeIsFlipped(rSurface.planeIndex)) + temp.neg(); + + mTransformPlane(getSOTransform(), getSOScale(), temp, pPlane); +} + + +//-------------------------------------------------------------------------- +U32 MirrorSubObject::getSubObjectKey() const +{ + return InteriorSubObject::MirrorSubObjectKey; +} + + +bool MirrorSubObject::_readISO(Stream& stream) +{ + AssertFatal(isInitialized() == false, "Error, should not be initialized here!"); + + if (Parent::_readISO(stream) == false) + return false; + + stream.read(&mDetailLevel); + stream.read(&mZone); + stream.read(&mAlphaLevel); + stream.read(&surfaceCount); + stream.read(&surfaceStart); + + stream.read(&mCentroid.x); + stream.read(&mCentroid.y); + stream.read(&mCentroid.z); + + return true; +} + + +bool MirrorSubObject::_writeISO(Stream& stream) const +{ + if (Parent::_writeISO(stream) == false) + return false; + + stream.write(mDetailLevel); + stream.write(mZone); + stream.write(mAlphaLevel); + stream.write(surfaceCount); + stream.write(surfaceStart); + + stream.write(mCentroid.x); + stream.write(mCentroid.y); + stream.write(mCentroid.z); + + return true; +} + +bool MirrorSubObject::renderDetailDependant() const +{ + return true; +} + + +U32 MirrorSubObject::getZone() const +{ + return mZone; +} + + +void MirrorSubObject::setupTransforms() +{ + mInitialized = true; + + // This is really bad, but it's just about the only good place for this... + if (getInstance()->isClientObject() && mWhite == NULL) + mWhite = new GFXTexHandle("special/whiteAlpha0", &GFXDefaultStaticDiffuseProfile, avar("%s() - mWhite (line %d)", __FUNCTION__, __LINE__)); + + Interior* interior = getInstance()->getDetailLevel(mDetailLevel); + const Interior::Surface& rSurface = interior->mSurfaces[surfaceStart]; + + PlaneF plane = interior->getPlane(rSurface.planeIndex); + if (Interior::planeIsFlipped(rSurface.planeIndex)) + plane.neg(); + + Point3F n(plane.x, plane.y, plane.z); + Point3F q = n; + q *= -plane.d; + + MatrixF t(true); + t.scale(getSOScale()); + t.mul(getSOTransform()); + + t.mulV(n); + t.mulP(q); + + F32* ra = mReflectionMatrix; + + ra[0] = 1.0f - 2.0f*(n.x*n.x); ra[1] = 0.0f - 2.0f*(n.x*n.y); ra[2] = 0.0f - 2.0f*(n.x*n.z); ra[3] = 0.0f; + ra[4] = 0.0f - 2.0f*(n.y*n.x); ra[5] = 1.0f - 2.0f*(n.y*n.y); ra[6] = 0.0f - 2.0f*(n.y*n.z); ra[7] = 0.0f; + ra[8] = 0.0f - 2.0f*(n.z*n.x); ra[9] = 0.0f - 2.0f*(n.z*n.y); ra[10] = 1.0f - 2.0f*(n.z*n.z); ra[11] = 0.0f; + + Point3F qnn = n * mDot(n, q); + + ra[12] = qnn.x * 2.0f; + ra[13] = qnn.y * 2.0f; + ra[14] = qnn.z * 2.0f; + ra[15] = 1.0f; + + // Now, the GGems series (as of v1) uses row vectors (arg) + mReflectionMatrix.transpose(); +} + +void MirrorSubObject::noteTransformChange() +{ + setupTransforms(); + Parent::noteTransformChange(); +} + +InteriorSubObject* MirrorSubObject::clone(InteriorInstance* instance) const +{ + MirrorSubObject* pClone = new MirrorSubObject; + + pClone->mDetailLevel = mDetailLevel; + pClone->mZone = mZone; + pClone->mAlphaLevel = mAlphaLevel; + pClone->mCentroid = mCentroid; + pClone->surfaceCount = surfaceCount; + pClone->surfaceStart = surfaceStart; + + pClone->mInteriorInstance = instance; + + return pClone; +} diff --git a/interior/mirrorSubObject.h b/interior/mirrorSubObject.h new file mode 100644 index 0000000..f898b5e --- /dev/null +++ b/interior/mirrorSubObject.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +// Portions Copyright (c) 2001 by Sierra Online, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MIRRORSUBOBJECT_H_ +#define _MIRRORSUBOBJECT_H_ + +#ifndef _INTERIORSUBOBJECT_H_ +#include "interior/interiorSubObject.h" +#endif + +#include "gfx/gfxTextureHandle.h" + +class TextureHandle; + +class MirrorSubObject : public InteriorSubObject +{ + typedef InteriorSubObject Parent; + + public: + U32 mDetailLevel; + U32 mZone; + + F32 mAlphaLevel; + Point3F mCentroid; + + U32 surfaceCount; + U32 surfaceStart; + + private: + bool mInitialized; + GFXTexHandle* mWhite; + MatrixF mReflectionMatrix; + + bool isInitialized() const { return mInitialized; } + void setupTransforms(); + + + // ISO overrides + protected: + U32 getSubObjectKey() const; + bool _readISO(Stream&); + bool _writeISO(Stream&) const; + + // Render control. A sub-object should return false from renderDetailDependant if + // it exists only at the level-0 detail level, ie, doors, elevators, etc., true + // if should only render at the interiors detail, ie, translucencies. + //SubObjectRenderImage* getRenderImage(SceneState*, const Point3F&); + bool renderDetailDependant() const; + U32 getZone() const; + void noteTransformChange(); + + InteriorSubObject* clone(InteriorInstance*) const; + + // Rendering + protected: + //void renderObject(SceneState*, SceneRenderImage*); + void transformModelview(const U32, const MatrixF&, MatrixF*); + void transformPosition(const U32, Point3F&); + bool computeNewFrustum( const U32 portalIndex, + const Frustum &oldFrustum, + const F64 nearPlane, + const F64 farPlane, + const RectI& oldViewport, + F64 *newFrustum, + RectI& newViewport, + const bool flippedMatrix ); + void openPortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState); + void closePortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState); + void getWSPortalPlane(const U32 portalIndex, PlaneF*); + + + public: + MirrorSubObject(); + ~MirrorSubObject(); + + DECLARE_CONOBJECT(MirrorSubObject); + static void initPersistFields(); +}; + +#endif // _H_MIRRORSUBOBJECT + diff --git a/interior/pathedInterior.cpp b/interior/pathedInterior.cpp new file mode 100644 index 0000000..5d55ff4 --- /dev/null +++ b/interior/pathedInterior.cpp @@ -0,0 +1,587 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "interior/pathedInterior.h" +#include "core/stream/stream.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneState.h" +#include "math/mathIO.h" +#include "core/stream/bitStream.h" +#include "interior/interior.h" +#include "sceneGraph/simPath.h" +#include "sceneGraph/pathManager.h" +#include "core/frameAllocator.h" +#include "sceneGraph/sceneGraph.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxProfile.h" +#include "sfx/sfxSource.h" +#include "core/resourceManager.h" + +IMPLEMENT_CO_NETOBJECT_V1(PathedInterior); +IMPLEMENT_CO_DATABLOCK_V1(PathedInteriorData); + +//-------------------------------------------------------------------------- + +PathedInteriorData::PathedInteriorData() +{ + for(U32 i = 0; i < MaxSounds; i++) + sound[i] = NULL; +} + +void PathedInteriorData::initPersistFields() +{ + addField("StartSound", TypeSFXProfilePtr, Offset(sound[StartSound], PathedInteriorData)); + addField("SustainSound", TypeSFXProfilePtr, Offset(sound[SustainSound], PathedInteriorData)); + addField("StopSound", TypeSFXProfilePtr, Offset(sound[StopSound], PathedInteriorData)); + + Parent::initPersistFields(); +} + +void PathedInteriorData::packData(BitStream *stream) +{ + for (S32 i = 0; i < MaxSounds; i++) + { + if (stream->writeFlag(sound[i])) + stream->writeRangedU32(packed? SimObjectId(sound[i]): + sound[i]->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast); + } + Parent::packData(stream); +} + +void PathedInteriorData::unpackData(BitStream* stream) +{ + for (S32 i = 0; i < MaxSounds; i++) { + sound[i] = NULL; + if (stream->readFlag()) + sound[i] = (SFXProfile*)stream->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast); + } + Parent::unpackData(stream); +} + +bool PathedInteriorData::preload(bool server, String &errorStr) +{ + if(!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + for (S32 i = 0; i < MaxSounds; i++) + if (sound[i]) + Sim::findObject(SimObjectId(sound[i]),sound[i]); + } + return true; +} + +PathedInterior::PathedInterior() +{ + mNetFlags.set(Ghostable); + mTypeMask = InteriorObjectType; + + mCurrentPosition = 0; + mTargetPosition = 0; + mPathKey = 0xFFFFFFFF; + mStopped = false; + + mSustainSound = NULL; +} + +PathedInterior::~PathedInterior() +{ + // +} + +PathedInterior *PathedInterior::mClientPathedInteriors = NULL; +//-------------------------------------------------------------------------- +void PathedInterior::initPersistFields() +{ + addField("interiorResource", TypeFilename, Offset(mInteriorResName, PathedInterior)); + addField("interiorIndex", TypeS32, Offset(mInteriorResIndex, PathedInterior)); + addField("basePosition", TypeMatrixPosition, Offset(mBaseTransform, PathedInterior)); + addField("baseRotation", TypeMatrixRotation, Offset(mBaseTransform, PathedInterior)); + addField("baseScale", TypePoint3F, Offset(mBaseScale, PathedInterior)); + + Parent::initPersistFields(); +} + + +//-------------------------------------------------------------------------- +bool PathedInterior::onAdd() +{ + if(!Parent::onAdd()) + return false; + + // Load the interior resource and extract the interior that is us. + mInteriorRes = ResourceManager::get().load(mInteriorResName); + if (bool(mInteriorRes) == false) + return false; + mInterior = mInteriorRes->getSubObject(mInteriorResIndex); + if (mInterior == NULL) + return false; + + // Setup bounding information + mObjBox = mInterior->getBoundingBox(); + resetWorldBox(); + + setScale(mBaseScale); + setTransform(mBaseTransform); + + if (isClientObject()) { + mNextClientPI = mClientPathedInteriors; + mClientPathedInteriors = this; + mInterior->prepForRendering(mInteriorRes.getPath().getFullPath().c_str()); +// gInteriorLMManager.addInstance(mInterior->getLMHandle(), mLMHandle, NULL, this); + } + + if(isClientObject()) + { + Point3F initialPos( 0.0, 0.0, 0.0 ); + mBaseTransform.getColumn(3, &initialPos); + Point3F pathPos( 0.0, 0.0, 0.0 ); + //gClientPathManager->getPathPosition(mPathKey, 0, pathPos); + mOffset = initialPos - pathPos; + //gClientPathManager->getPathPosition(mPathKey, mCurrentPosition, pathPos); + MatrixF mat = getTransform(); + mat.setColumn(3, pathPos + mOffset); + setTransform(mat); + } + + addToScene(); + + return true; +} + +bool PathedInterior::onSceneAdd(SceneGraph *g) +{ + if(!Parent::onSceneAdd(g)) + return false; + return true; +} + +void PathedInterior::onSceneRemove() +{ + Parent::onSceneRemove(); +} + +bool PathedInterior::onNewDataBlock(GameBaseData* dptr) +{ + mDataBlock = dynamic_cast(dptr); + + if ( isClientObject() ) + { + SFX_DELETE( mSustainSound ); + + if ( mDataBlock->sound[PathedInteriorData::SustainSound] ) + mSustainSound = SFX->createSource( mDataBlock->sound[PathedInteriorData::SustainSound], &getTransform() ); + + if ( mSustainSound ) + mSustainSound->play(); + } + + return Parent::onNewDataBlock(dptr); +} + +void PathedInterior::onRemove() +{ + if(isClientObject()) + { + SFX_DELETE( mSustainSound ); + + PathedInterior **walk = &mClientPathedInteriors; + while(*walk) + { + if(*walk == this) + { + *walk = mNextClientPI; + break; + } + walk = &((*walk)->mNextClientPI); + } +/* if(bool(mInteriorRes) && mLMHandle != 0xFFFFFFFF) + { + if (mInterior->getLMHandle() != 0xFFFFFFFF) + gInteriorLMManager.removeInstance(mInterior->getLMHandle(), mLMHandle); + }*/ + } + removeFromScene(); + Parent::onRemove(); +} + +//------------------------------------------------------------------------------ +bool PathedInterior::buildPolyList(AbstractPolyList* list, const Box3F& wsBox, const SphereF&) +{ + if (bool(mInteriorRes) == false) + return false; + + // Setup collision state data + list->setTransform(&getTransform(), getScale()); + list->setObject(this); + + return mInterior->buildPolyList(list, wsBox, mWorldToObj, getScale()); +} + + +//-------------------------------------------------------------------------- +bool PathedInterior::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + if (mPathKey == SimPath::Path::NoPathIndex) + return false; + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + bool render = false; + for (U32 i = 0; i < getNumCurrZones(); i++) + if (state->getZoneState(getCurrZone(i)).render == true) + render = true; + + if (render == true) { +/* + SceneRenderImage* image = new SceneRenderImage; + image->obj = this; + state->insertRenderImage(image); +*/ + } + + return false; +} + +extern ColorF gInteriorFogColor; + +void PathedInterior::renderObject(SceneState* state) +{ +} + +void PathedInterior::resolvePathKey() +{ + if(mPathKey == 0xFFFFFFFF && !isGhost()) + { + mPathKey = getPathKey(); + Point3F pathPos( 0.0, 0.0, 0.0 ); + Point3F initialPos( 0.0, 0.0, 0.0 ); + mBaseTransform.getColumn(3, &initialPos); + //gServerPathManager->getPathPosition(mPathKey, 0, pathPos); + mOffset = initialPos - pathPos; + } +} + + +//-------------------------------------------------------------------------- +U32 PathedInterior::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + resolvePathKey(); + + if (stream->writeFlag(mask & InitialUpdateMask)) + { + // Inital update... + stream->writeString(mInteriorResName); + stream->write(mInteriorResIndex); + + stream->writeAffineTransform(mBaseTransform); + mathWrite(*stream, mBaseScale); + + stream->write(mPathKey); + } + if(stream->writeFlag((mask & NewPositionMask) && mPathKey != SimPath::Path::NoPathIndex)) + stream->writeInt(S32(mCurrentPosition), gServerPathManager->getPathTimeBits(mPathKey)); + if(stream->writeFlag((mask & NewTargetMask) && mPathKey != SimPath::Path::NoPathIndex)) + { + if(stream->writeFlag(mTargetPosition < 0)) + { + stream->writeFlag(mTargetPosition == -1); + } + else + stream->writeInt(S32(mTargetPosition), gServerPathManager->getPathTimeBits(mPathKey)); + } + return retMask; +} + +void PathedInterior::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + MatrixF tempXForm; + Point3F tempScale; + + if (stream->readFlag()) + { + // Initial + mInteriorResName = stream->readSTString(); + stream->read(&mInteriorResIndex); + + stream->readAffineTransform(&tempXForm); + mathRead(*stream, &tempScale); + mBaseTransform = tempXForm; + mBaseScale = tempScale; + + stream->read(&mPathKey); + } + if(stream->readFlag()) + { + Point3F pathPos(0.0f, 0.0f, 0.0f); + mCurrentPosition = stream->readInt(gClientPathManager->getPathTimeBits(mPathKey)); + if(isProperlyAdded()) + { + //gClientPathManager->getPathPosition(mPathKey, mCurrentPosition, pathPos); + MatrixF mat = getTransform(); + mat.setColumn(3, pathPos + mOffset); + setTransform(mat); + } + } + if(stream->readFlag()) + { + if(stream->readFlag()) + { + mTargetPosition = stream->readFlag() ? -1 : -2; + } + else + mTargetPosition = stream->readInt(gClientPathManager->getPathTimeBits(mPathKey)); + } +} + +void PathedInterior::processTick(const Move* move) +{ + if(isServerObject()) + { + S32 timeMs = 32; + if(mCurrentPosition != mTargetPosition) + { + S32 delta; + if(mTargetPosition == -1) + delta = timeMs; + else if(mTargetPosition == -2) + delta = -timeMs; + else + { + delta = mTargetPosition - (S32)mCurrentPosition; + if(delta < -timeMs) + delta = -timeMs; + else if(delta > timeMs) + delta = timeMs; + } + mCurrentPosition += delta; + U32 totalTime = gClientPathManager->getPathTotalTime(mPathKey); + while(mCurrentPosition > totalTime) + mCurrentPosition -= totalTime; + while(mCurrentPosition < 0) + mCurrentPosition += totalTime; + } + } +} + +void PathedInterior::computeNextPathStep(U32 timeDelta) +{ + S32 timeMs = timeDelta; + mStopped = false; + + if(mCurrentPosition == mTargetPosition) + { + mExtrudedBox = getWorldBox(); + mCurrentVelocity.set(0,0,0); + } + else + { + S32 delta = 0; + if(mTargetPosition < 0) + { + if(mTargetPosition == -1) + delta = timeMs; + else if(mTargetPosition == -2) + delta = -timeMs; + + mCurrentPosition += delta; + + U32 totalTime = gClientPathManager->getPathTotalTime(mPathKey); + + while(mCurrentPosition >= totalTime) + mCurrentPosition -= totalTime; + + while(mCurrentPosition < 0) + mCurrentPosition += totalTime; + } + else + { + delta = mTargetPosition - (S32)mCurrentPosition; + if(delta < -timeMs) + delta = -timeMs; + else if(delta > timeMs) + delta = timeMs; + mCurrentPosition += delta; + } + + Point3F curPoint; + Point3F newPoint( 0.0, 0.0, 0.0 ); + MatrixF mat = getTransform(); + mat.getColumn(3, &curPoint); + //gClientPathManager->getPathPosition(mPathKey, mCurrentPosition, newPoint); + newPoint += mOffset; + + Point3F displaceDelta = newPoint - curPoint; + mExtrudedBox = getWorldBox(); + + if(displaceDelta.x < 0) + mExtrudedBox.minExtents.x += displaceDelta.x; + else + mExtrudedBox.maxExtents.x += displaceDelta.x; + if(displaceDelta.y < 0) + mExtrudedBox.minExtents.y += displaceDelta.y; + else + mExtrudedBox.maxExtents.y += displaceDelta.y; + if(displaceDelta.z < 0) + mExtrudedBox.minExtents.z += displaceDelta.z; + else + mExtrudedBox.maxExtents.z += displaceDelta.z; + + mCurrentVelocity = displaceDelta * 1000 / F32(timeDelta); + } + Point3F pos; + mExtrudedBox.getCenter(&pos); + MatrixF mat = getTransform(); + mat.setColumn(3, pos); + + if ( mSustainSound ) + { + mSustainSound->setTransform( mat ); + mSustainSound->setVelocity( getVelocity() ); + } +} + +Point3F PathedInterior::getVelocity() +{ + return mCurrentVelocity; +} + +void PathedInterior::advance(F64 timeDelta) +{ + if(mStopped) + return; + + if(mCurrentVelocity.len() == 0) + { +// if(mSustainHandle) +// { +// alxStop(mSustainHandle); +// mSustainHandle = 0; +// } + return; + } + MatrixF mat = getTransform(); + Point3F newPoint; + mat.getColumn(3, &newPoint); + newPoint += mCurrentVelocity * timeDelta / 1000.0f; + //gClientPathManager->getPathPosition(mPathKey, mCurrentPosition, newPoint); + mat.setColumn(3, newPoint);// + mOffset); + setTransform(mat); + setRenderTransform(mat); +} + +U32 PathedInterior::getPathKey() +{ + AssertFatal(isServerObject(), "Error, must be a server object to call this..."); + + SimGroup* myGroup = getGroup(); + AssertFatal(myGroup != NULL, "No group for this object?"); + + for (SimGroup::iterator itr = myGroup->begin(); itr != myGroup->end(); itr++) { + SimPath::Path* pPath = dynamic_cast(*itr); + if (pPath != NULL) { + U32 pathKey = pPath->getPathIndex(); + AssertFatal(pathKey != SimPath::Path::NoPathIndex, "Error, path must have event over at this point..."); + return pathKey; + } + } + + return SimPath::Path::NoPathIndex; +} + +void PathedInterior::setPathPosition(S32 newPosition) +{ + resolvePathKey(); + if(newPosition < 0) + newPosition = 0; + if(newPosition > S32(gServerPathManager->getPathTotalTime(mPathKey))) + newPosition = S32(gServerPathManager->getPathTotalTime(mPathKey)); + mCurrentPosition = mTargetPosition = newPosition; + setMaskBits(NewPositionMask | NewTargetMask); +} + +void PathedInterior::setTargetPosition(S32 newPosition) +{ + resolvePathKey(); + if(newPosition < -2) + newPosition = 0; + if(newPosition > S32(gServerPathManager->getPathTotalTime(mPathKey))) + newPosition = gServerPathManager->getPathTotalTime(mPathKey); + if(mTargetPosition != newPosition) + { + mTargetPosition = newPosition; + setMaskBits(NewTargetMask); + } +} + +ConsoleMethod(PathedInterior, setPathPosition, void, 3, 3, "") +{ + ((PathedInterior *) object)->setPathPosition(dAtoi(argv[2])); +} + +ConsoleMethod(PathedInterior, setTargetPosition, void, 3, 3, "") +{ + ((PathedInterior *) object)->setTargetPosition(dAtoi(argv[2])); +} + + +//-------------------------------------------------------------------------- +bool PathedInterior::readPI(Stream& stream) +{ + mName = stream.readSTString(); + mInteriorResName = stream.readSTString(); + stream.read(&mInteriorResIndex); + stream.read(&mPathIndex); + mathRead(stream, &mOffset); + + U32 numTriggers; + stream.read(&numTriggers); + mTriggers.setSize(numTriggers); + for (S32 i = 0; i < mTriggers.size(); i++) + mTriggers[i] = stream.readSTString(); + + return (stream.getStatus() == Stream::Ok); +} + +bool PathedInterior::writePI(Stream& stream) const +{ + stream.writeString(mName); + stream.writeString(mInteriorResName); + stream.write(mInteriorResIndex); + stream.write(mPathIndex); + mathWrite(stream, mOffset); + + stream.write(mTriggers.size()); + for (S32 i = 0; i < mTriggers.size(); i++) + stream.writeString(mTriggers[i]); + + return (stream.getStatus() == Stream::Ok); +} + +PathedInterior* PathedInterior::clone() const +{ + PathedInterior* pClone = new PathedInterior; + + pClone->mName = mName; + pClone->mInteriorResName = mInteriorResName; + pClone->mInteriorResIndex = mInteriorResIndex; + pClone->mPathIndex = mPathIndex; + pClone->mOffset = mOffset; + + return pClone; +} + + diff --git a/interior/pathedInterior.h b/interior/pathedInterior.h new file mode 100644 index 0000000..2a9162e --- /dev/null +++ b/interior/pathedInterior.h @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_PATHEDINTERIOR +#define _H_PATHEDINTERIOR + + +#include "interior/interior.h" +#include "T3D/gameBase.h" +#include "interior/interiorRes.h" + +class InteriorInstance; +class EditGeometry; +class EditInteriorResource; +class SFXProfile; +class SFXSource; + +struct PathedInteriorData : public GameBaseData { + typedef GameBaseData Parent; +public: + enum Sounds { + StartSound, + SustainSound, + StopSound, + MaxSounds + }; + SFXProfile *sound[MaxSounds]; + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + bool preload(bool server, String &errorStr); + PathedInteriorData(); + + DECLARE_CONOBJECT(PathedInteriorData); +}; + +class PathedInterior : public GameBase +{ + typedef GameBase Parent; + friend class InteriorInstance; + friend class EditGeometry; + friend class EditInteriorResource; + + PathedInteriorData *mDataBlock; + +public: + enum UpdateMasks { + NewTargetMask = Parent::NextFreeMask, + NewPositionMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2, + }; +private: + + U32 getPathKey(); // only used on the server + + // Persist fields + protected: + StringTableEntry mName; + S32 mPathIndex; + Vector mTriggers; + Point3F mOffset; + Box3F mExtrudedBox; + bool mStopped; + + // Loaded resources and fields + protected: + static PathedInterior *mClientPathedInteriors; + + SFXSource* mSustainSound; + + StringTableEntry mInteriorResName; + S32 mInteriorResIndex; + Resource mInteriorRes; + Interior* mInterior; + Vector mVertexColorsNormal; + Vector mVertexColorsAlarm; + + MatrixF mBaseTransform; + Point3F mBaseScale; + + U32 mPathKey; // only used on the client + F64 mCurrentPosition; + S32 mTargetPosition; + Point3F mCurrentVelocity; + + PathedInterior *mNextClientPI; + + // Rendering + protected: + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + void renderObject(SceneState *state); + void renderShadowVolumes(SceneState *state); + + protected: + bool onAdd(); + void onRemove(); + bool onSceneAdd(SceneGraph *graph); + void onSceneRemove(); + + public: + PathedInterior(); + ~PathedInterior(); + + PathedInterior *getNext() { return mNextClientPI; } + + static PathedInterior *getClientPathedInteriors() { return mClientPathedInteriors; } + + void processTick(const Move* move); + void setStopped() { mStopped = true; } + void resolvePathKey(); + + bool onNewDataBlock(GameBaseData* dptr); + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + bool readPI(Stream&); + bool writePI(Stream&) const; + PathedInterior* clone() const; + + DECLARE_CONOBJECT(PathedInterior); + static void initPersistFields(); + void setPathPosition(S32 newPosition); + void setTargetPosition(S32 targetPosition); + void computeNextPathStep(U32 timeDelta); + Box3F getExtrudedBox() { return mExtrudedBox; } + Point3F getVelocity(); + void advance(F64 timeDelta); + + U32 packUpdate(NetConnection *conn, U32 mask, BitStream* stream); + void unpackUpdate(NetConnection *conn, BitStream* stream); +}; + +#endif // _H_PATHEDINTERIOR + diff --git a/lighting/advanced/advancedLightBinManager.cpp b/lighting/advanced/advancedLightBinManager.cpp new file mode 100644 index 0000000..3174983 --- /dev/null +++ b/lighting/advanced/advancedLightBinManager.cpp @@ -0,0 +1,816 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/advancedLightBinManager.h" + +#include "lighting/advanced/advancedLightManager.h" +#include "lighting/advanced/advancedLightBufferConditioner.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "lighting/shadowMap/shadowMapPass.h" +#include "lighting/shadowMap/lightShadowMap.h" +#include "lighting/common/lightMapParams.h" +#include "renderInstance/renderPrePassMgr.h" +#include "gfx/gfxTransformSaver.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "materials/materialManager.h" +#include "materials/sceneData.h" +#include "core/util/safeDelete.h" +#include "core/util/rgb2luv.h" +#include "gfx/gfxDebugEvent.h" +#include "math/util/matrixSet.h" + +const RenderInstType AdvancedLightBinManager::RIT_LightInfo( "LightInfo" ); +const String AdvancedLightBinManager::smBufferName( "lightinfo" ); + +F32 AdvancedLightBinManager::smConstantSpecularPower = 12.0f; + +ShadowFilterMode AdvancedLightBinManager::smShadowFilterMode = ShadowFilterMode_SoftShadowHighQuality; + + +// NOTE: The order here matches that of the LightInfo::Type enum. +const String AdvancedLightBinManager::smLightMatNames[] = +{ + "AL_PointLightMaterial", // LightInfo::Point + "AL_SpotLightMaterial", // LightInfo::Spot + "AL_VectorLightMaterial", // LightInfo::Vector + "", // LightInfo::Ambient +}; + +// NOTE: The order here matches that of the LightInfo::Type enum. +const GFXVertexFormat* AdvancedLightBinManager::smLightMatVertex[] = +{ + getGFXVertexFormat(), // LightInfo::Point + getGFXVertexFormat(), // LightInfo::Spot + getGFXVertexFormat(), // LightInfo::Vector + NULL, // LightInfo::Ambient +}; + +// NOTE: The order here matches that of the ShadowType enum. +const String AdvancedLightBinManager::smShadowTypeMacro[] = +{ + "", // ShadowType_Spot + "", // ShadowType_PSSM, + "SHADOW_PARABOLOID", // ShadowType_Paraboloid, + "SHADOW_DUALPARABOLOID_SINGLE_PASS", // ShadowType_DualParaboloidSinglePass, + "SHADOW_DUALPARABOLOID", // ShadowType_DualParaboloid, + "SHADOW_CUBE", // ShadowType_CubeMap, +}; + + +IMPLEMENT_CONOBJECT(AdvancedLightBinManager); + +AdvancedLightBinManager::AdvancedLightBinManager( AdvancedLightManager *lm /* = NULL */, + ShadowMapManager *sm /* = NULL */, + GFXFormat lightBufferFormat /* = GFXFormatR8G8B8A8 */ ) + : RenderTexTargetBinManager( RIT_LightInfo, 1.0f, 1.0f, lightBufferFormat ), + mNumLightsCulled(0), + mLightManager(lm), + mShadowManager(sm), + mConditioner(NULL) +{ + // Create an RGB conditioner + mConditioner = new AdvancedLightBufferConditioner( getTargetFormat(), + AdvancedLightBufferConditioner::RGB ); + + // We want a full-resolution buffer + mTargetSizeType = RenderTexTargetBinManager::WindowSize; + + mMRTLightmapsDuringPrePass = false; + + MatTextureTarget::registerTarget( smBufferName, this ); +} + +//------------------------------------------------------------------------------ + +AdvancedLightBinManager::~AdvancedLightBinManager() +{ + MatTextureTarget::unregisterTarget( smBufferName, this ); + + deleteLightMaterials(); + + SAFE_DELETE(mConditioner); +} + +RenderBinManager::AddInstResult AdvancedLightBinManager::addElement(RenderInst *) +{ + return RenderBinManager::arSkipped; +} + +bool AdvancedLightBinManager::setTargetSize(const Point2I &newTargetSize) +{ + bool ret = Parent::setTargetSize( newTargetSize ); + + // We require the viewport to match the default. + mTargetViewport = GFX->getViewport(); + + return ret; +} + +ConditionerFeature *AdvancedLightBinManager::getTargetConditioner() const +{ + return const_cast(this)->mConditioner; +} + +RenderBinManager::AddInstResult AdvancedLightBinManager::addLight( LightInfo *light ) +{ + // Get the light type. + const LightInfo::Type lightType = light->getType(); + + AssertFatal( lightType == LightInfo::Point || + lightType == LightInfo::Spot, "Bogus light type." ); + + // Find a shadow map for this light, if it has one + LightShadowMap *lsm = light->getExtended()->getShadowMap(); + + // Get the right shadow type. + ShadowType shadowType = ShadowType_None; + if ( light->getCastShadows() && + lsm && lsm->hasShadowTex() && + !ShadowMapPass::smDisableShadows ) + shadowType = lsm->getShadowType(); + + // Add the entry + LightBinEntry lEntry; + lEntry.lightInfo = light; + lEntry.shadowMap = lsm; + lEntry.lightMaterial = _getLightMaterial( lightType, shadowType ); + + if( lightType == LightInfo::Spot ) + lEntry.vertBuffer = mLightManager->getConeMesh( lEntry.numPrims, lEntry.primBuffer ); + else + lEntry.vertBuffer = mLightManager->getSphereMesh( lEntry.numPrims, lEntry.primBuffer ); + + // If it's a point light, push front, spot + // light, push back. This helps batches. + Vector &curBin = mLightBin; + if ( light->getType() == LightInfo::Point ) + curBin.push_front( lEntry ); + else + curBin.push_back( lEntry ); + + return arAdded; +} + +void AdvancedLightBinManager::clear() +{ +#define BIN_DEBUG_SPEW +#ifdef BIN_DEBUG_SPEW + bool bSpewBinInfo = Con::getBoolVariable( "$spewBins" ); + + if( bSpewBinInfo ) + { + Con::printf( "Advanced Light Bin Spew:" ); + Con::printf( "%d lights.", mLightBin.size() ); + Con::printf( "%d lights culled.", mNumLightsCulled ); + + Con::setBoolVariable( "$spewBins", false ); + } +#endif + + mLightBin.clear(); + mNumLightsCulled = 0; +} + +void AdvancedLightBinManager::render(SceneState * state) +{ + PROFILE_SCOPE( AdvancedLightManager_Render ); + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + if( !mLightManager ) + return; + + // Get the sunlight. If there's no sun, and no lights in the bins, no draw + LightInfo *sunLight = mLightManager->getSpecialLight( LightManager::slSunLightType ); + if( !sunLight && mLightBin.empty() ) + return; + + GFXDEBUGEVENT_SCOPE( AdvancedLightBinManager_Render, ColorI::RED ); + + // Tell the superclass we're about to render + if ( !_onPreRender( state ) ) + return; + + // Clear as long as there isn't MRT population of light buffer with lightmap data + if ( !MRTLightmapsDuringPrePass() ) + GFX->clear(GFXClearTarget, ColorI(0, 0, 0, 0), 1.0f, 0); + + // Restore transforms + MatrixSet &matrixSet = getParentManager()->getMatrixSet(); + matrixSet.restoreSceneViewProjection(); + + const MatrixF &worldToCameraXfm = matrixSet.getWorldToCamera(); + + // Set up the SG Data + SceneGraphData sgData; + + // There are cases where shadow rendering is disabled. + const bool disableShadows = state->isReflectPass() || ShadowMapPass::smDisableShadows; + + // Pick the right material for rendering the sunlight... we only + // cast shadows when its enabled and we're not in a reflection. + LightMaterialInfo *vectorMatInfo; + if ( sunLight && + sunLight->getCastShadows() && + !disableShadows && + sunLight->getExtended() ) + vectorMatInfo = _getLightMaterial( LightInfo::Vector, ShadowType_PSSM ); + else + vectorMatInfo = _getLightMaterial( LightInfo::Vector, ShadowType_None ); + + // Initialize and set the per-frame parameters after getting + // the vector light material as we use lazy creation. + _setupPerFrameParameters( state->getCameraPosition() ); + + // Draw sunlight/ambient + if ( sunLight && vectorMatInfo ) + { + GFXDEBUGEVENT_SCOPE( AdvancedLightBinManager_Render_Sunlight, ColorI::RED ); + + // Set up SG data + setupSGData( sgData, sunLight ); + vectorMatInfo->setLightParameters( sunLight, worldToCameraXfm ); + + // Set light holds the active shadow map. + mShadowManager->setLightShadowMapForLight( sunLight ); + + // Set geometry + GFX->setVertexBuffer( mFarFrustumQuadVerts ); + GFX->setPrimitiveBuffer( NULL ); + + // Render the material passes + while( vectorMatInfo->matInstance->setupPass( state, sgData ) ) + { + vectorMatInfo->matInstance->setSceneInfo( state, sgData ); + vectorMatInfo->matInstance->setTransforms( matrixSet, state ); + GFX->drawPrimitive( GFXTriangleFan, 0, 2 ); + } + } + + // Blend the lights in the bin to the light buffer + for( LightBinIterator itr = mLightBin.begin(); itr != mLightBin.end(); itr++ ) + { + LightBinEntry& curEntry = *itr; + LightInfo *curLightInfo = curEntry.lightInfo; + LightMaterialInfo *curLightMat = curEntry.lightMaterial; + const U32 numPrims = curEntry.numPrims; + const U32 numVerts = curEntry.vertBuffer->mNumVerts; + + // Skip lights which won't affect the scene. + if ( !curLightMat || curLightInfo->getBrightness() <= 0.001f ) + continue; + + GFXDEBUGEVENT_SCOPE( AdvancedLightBinManager_Render_Light, ColorI::RED ); + + setupSGData( sgData, curLightInfo ); + curLightMat->setLightParameters( curLightInfo, worldToCameraXfm ); + mShadowManager->setLightShadowMap( curEntry.shadowMap ); + + // Let the shadow know we're about to render from it. + if ( curEntry.shadowMap ) + curEntry.shadowMap->preLightRender(); + + // Set geometry + GFX->setVertexBuffer( curEntry.vertBuffer ); + GFX->setPrimitiveBuffer( curEntry.primBuffer ); + + // Render the material passes + while( curLightMat->matInstance->setupPass( state, sgData ) ) + { + // Set transforms + matrixSet.setWorld(sgData.objTrans); + curLightMat->matInstance->setTransforms(matrixSet, state); + curLightMat->matInstance->setSceneInfo(state, sgData); + + if(curEntry.primBuffer) + GFX->drawIndexedPrimitive(GFXTriangleList, 0, 0, numVerts, 0, numPrims); + else + GFX->drawPrimitive(GFXTriangleList, 0, numPrims); + } + + // Tell it we're done rendering. + if ( curEntry.shadowMap ) + curEntry.shadowMap->postLightRender(); + } + + // Set NULL for active shadow map (so nothing gets confused) + mShadowManager->setLightShadowMap(NULL); + GFX->setVertexBuffer( NULL ); + GFX->setPrimitiveBuffer( NULL ); + + // Finish up the rendering + _onPostRender(); +} + +AdvancedLightBinManager::LightMaterialInfo* AdvancedLightBinManager::_getLightMaterial( LightInfo::Type lightType, ShadowType shadowType ) +{ + PROFILE_SCOPE( AdvancedLightBinManager_GetLightMaterial ); + + // Build the key. + const LightMatKey key( lightType, shadowType ); + + // See if we've already built this one. + LightMatTable::Iterator iter = mLightMaterials.find( key ); + if ( iter != mLightMaterials.end() ) + return iter->value; + + // If we got here we need to build a material for + // this light+shadow combination. + + LightMaterialInfo *info = NULL; + + // First get the light material name and make sure + // this light has a material in the first place. + const String &lightMatName = smLightMatNames[ lightType ]; + if ( lightMatName.isNotEmpty() ) + { + Vector shadowMacros; + + // Setup the shadow type macros for this material. + if ( shadowType == ShadowType_None ) + shadowMacros.push_back( GFXShaderMacro( "NO_SHADOW" ) ); + else + { + shadowMacros.push_back( GFXShaderMacro( smShadowTypeMacro[ shadowType ] ) ); + + // Do we need to do shadow filtering? + if ( smShadowFilterMode != ShadowFilterMode_None ) + { + shadowMacros.push_back( GFXShaderMacro( "SOFTSHADOW" ) ); + + const F32 SM = GFX->getPixelShaderVersion(); + if ( SM >= 3.0f && smShadowFilterMode == ShadowFilterMode_SoftShadowHighQuality ) + shadowMacros.push_back( GFXShaderMacro( "SOFTSHADOW_HIGH_QUALITY" ) ); + } + } + + // Now create the material info object. + info = new LightMaterialInfo( lightMatName, smLightMatVertex[ lightType ], shadowMacros ); + } + + // Push this into the map and return it. + mLightMaterials.insertUnique( key, info ); + return info; +} + +void AdvancedLightBinManager::deleteLightMaterials() +{ + LightMatTable::Iterator iter = mLightMaterials.begin(); + for ( ; iter != mLightMaterials.end(); iter++ ) + delete iter->value; + + mLightMaterials.clear(); +} + +void AdvancedLightBinManager::_setupPerFrameParameters( const Point3F &eyePos ) +{ + PROFILE_SCOPE( AdvancedLightBinManager_SetupPerFrameParameters ); + + F32 frustLeft, frustRight, frustBottom, frustTop, zNear, zFar; + bool isOrtho; + GFX->getFrustum( &frustLeft, &frustRight, &frustBottom, &frustTop, &zNear, &zFar, &isOrtho ); + + const F32 zFarOverNear = zFar / zNear; + + MatrixF camMat(GFX->getWorldMatrix()); + camMat.inverse(); + + mFrustum.set( isOrtho, frustLeft, frustRight, frustTop, frustBottom, zNear, zFar, camMat); + mViewSpaceFrustum.set( isOrtho, frustLeft, frustRight, frustTop, frustBottom, zNear, zFar, MatrixF::Identity ); + + const Point3F* wsFrutumPoints = mFrustum.getPoints(); + const Point3F* vsFrustumPoints = mViewSpaceFrustum.getPoints(); + + PlaneF farPlane(wsFrutumPoints[Frustum::FarBottomLeft], wsFrutumPoints[Frustum::FarTopLeft], wsFrutumPoints[Frustum::FarTopRight]); + PlaneF vsFarPlane(vsFrustumPoints[Frustum::FarBottomLeft], vsFrustumPoints[Frustum::FarTopLeft], vsFrustumPoints[Frustum::FarTopRight]); + + // Parameters calculated, assign them to the materials + LightMatTable::Iterator iter = mLightMaterials.begin(); + for ( ; iter != mLightMaterials.end(); iter++ ) + { + if ( iter->value ) + iter->value->setViewParameters(zNear, zFar, eyePos, farPlane, vsFarPlane); + } + + // Now build the quad for drawing full-screen vector light + // passes.... this is a volitile VB and updates every frame. + + //static const U32 sRemap[] = { 0, 1, 2, 3 }; // For normal culling + static const U32 sRemap[] = { 0, 3, 2, 1 }; // For reverse culling + + mFarFrustumQuadVerts.set( GFX, 4 ); + FarFrustumQuadVert *verts = mFarFrustumQuadVerts.lock(); + + verts[sRemap[0]].point.set( wsFrutumPoints[Frustum::FarBottomLeft] - eyePos ); + verts[sRemap[0]].normal.set( frustLeft * zFarOverNear, zFar, frustBottom * zFarOverNear ); + verts[sRemap[0]].texCoord.set( 0.0f, 0.0f ); + + verts[sRemap[1]].point.set( wsFrutumPoints[Frustum::FarTopLeft] - eyePos ); + verts[sRemap[1]].normal.set( frustLeft * zFarOverNear, zFar, frustTop * zFarOverNear ); + verts[sRemap[1]].texCoord.set( 0.0f, 1.0f ); + + verts[sRemap[2]].point.set( wsFrutumPoints[Frustum::FarTopRight] - eyePos ); + verts[sRemap[2]].normal.set( frustRight * zFarOverNear, zFar, frustTop * zFarOverNear ); + verts[sRemap[2]].texCoord.set( 1.0f, 1.0f ); + + verts[sRemap[3]].point.set( wsFrutumPoints[Frustum::FarBottomRight] - eyePos ); + verts[sRemap[3]].normal.set( frustRight * zFarOverNear, zFar, frustBottom * zFarOverNear); + verts[sRemap[3]].texCoord.set( 1.0f, 0.0f ); + + mFarFrustumQuadVerts.unlock(); +} + +void AdvancedLightBinManager::setupSGData( SceneGraphData &data, LightInfo *light ) +{ + data.reset(); + data.setFogParams(mParentManager->getSceneManager()->getFogData()); + + data.lights[0] = light; + + if ( light ) + { + if ( light->getType() == LightInfo::Point ) + { + data.objTrans = light->getTransform(); + + // The point light volume gets some flat spots along + // the perimiter mostly visible in the constant and + // quadradic falloff modes. + // + // To account for them slightly increase the scale + // instead of greatly increasing the polycount. + + data.objTrans.scale( light->getRange() * 1.01f ); + } + else if ( light->getType() == LightInfo::Spot ) + { + data.objTrans = light->getTransform(); + + // Rotate it to face down the -y axis. + MatrixF scaleRotateTranslate( EulerF( M_PI_F / -2.0f, 0.0f, 0.0f ) ); + + // Calculate the radius based on the range and angle. + F32 range = light->getRange().x; + F32 radius = range * mSin( mDegToRad( light->getOuterConeAngle() ) * 0.5f ); + + // NOTE: This fudge makes the cone a little bigger + // to remove the facet egde of the cone geometry. + radius *= 1.1f; + + // Use the scale to distort the cone to + // match our radius and range. + scaleRotateTranslate.scale( Point3F( radius, radius, range ) ); + + // Apply the transform and set the position. + data.objTrans *= scaleRotateTranslate; + data.objTrans.setPosition( light->getPosition() ); + } + else + data.objTrans.identity(); + } + else + data.objTrans.identity(); +} + +void AdvancedLightBinManager::MRTLightmapsDuringPrePass( bool val ) +{ + // Do not enable if the GFX device can't do MRT's + if(GFX->getNumRenderTargets() < 2) + val = false; + + if(mMRTLightmapsDuringPrePass != val) + { + mMRTLightmapsDuringPrePass = val; + + // Reload materials to cause a feature recalculation on prepass materials + if(mLightManager->isActive()) + MATMGR->flushAndReInitInstances(); + + // Force the pre-pass manager to update its targets + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(RenderPrePassMgr::BufferName); + if(texTarget != NULL) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + RenderPrePassMgr *prepass = static_cast(texTarget); + prepass->updateTargets(); + } + } +} + +AdvancedLightBinManager::LightMaterialInfo::LightMaterialInfo( const String &matName, + const GFXVertexFormat *vertexFormat, + const Vector ¯os ) +: matInstance(NULL), + zNearFarInvNearFar(NULL), + farPlane(NULL), + vsFarPlane(NULL), + negFarPlaneDotEye(NULL), + lightPosition(NULL), + lightDirection(NULL), + lightColor(NULL), + lightAttenuation(NULL), + lightRange(NULL), + lightAmbient(NULL), + lightTrilight(NULL), + lightSpotParams(NULL) +{ + Material *mat = MATMGR->getMaterialDefinitionByName( matName ); + if ( !mat ) + return; + + matInstance = new LightMatInstance( *mat ); + + for ( U32 i=0; i < macros.size(); i++ ) + matInstance->addShaderMacro( macros[i].name, macros[i].value ); + + matInstance->init( MATMGR->getDefaultFeatures(), vertexFormat ); + + lightDirection = matInstance->getMaterialParameterHandle("$lightDirection"); + lightAmbient = matInstance->getMaterialParameterHandle("$lightAmbient"); + lightTrilight = matInstance->getMaterialParameterHandle("$lightTrilight"); + lightSpotParams = matInstance->getMaterialParameterHandle("$lightSpotParams"); + lightAttenuation = matInstance->getMaterialParameterHandle("$lightAttenuation"); + lightRange = matInstance->getMaterialParameterHandle("$lightRange"); + lightPosition = matInstance->getMaterialParameterHandle("$lightPosition"); + farPlane = matInstance->getMaterialParameterHandle("$farPlane"); + vsFarPlane = matInstance->getMaterialParameterHandle("$vsFarPlane"); + negFarPlaneDotEye = matInstance->getMaterialParameterHandle("$negFarPlaneDotEye"); + zNearFarInvNearFar = matInstance->getMaterialParameterHandle("$zNearFarInvNearFar"); + lightColor = matInstance->getMaterialParameterHandle("$lightColor"); + lightBrightness = matInstance->getMaterialParameterHandle("$lightBrightness"); + constantSpecularPower = matInstance->getMaterialParameterHandle(AdvancedLightManager::ConstantSpecularPowerSC); +} + +AdvancedLightBinManager::LightMaterialInfo::~LightMaterialInfo() +{ + SAFE_DELETE(matInstance); +} + +void AdvancedLightBinManager::LightMaterialInfo::setViewParameters( const F32 _zNear, + const F32 _zFar, + const Point3F &_eyePos, + const PlaneF &_farPlane, + const PlaneF &_vsFarPlane) +{ + MaterialParameters *matParams = matInstance->getMaterialParameters(); + + if ( farPlane->isValid() ) + matParams->set( farPlane, *((const Point4F *)&_farPlane) ); + + if ( vsFarPlane->isValid() ) + matParams->set( vsFarPlane, *((const Point4F *)&_vsFarPlane) ); + + if ( negFarPlaneDotEye->isValid() ) + { + // -dot( farPlane, eyePos ) + const F32 negFarPlaneDotEyeVal = -( mDot( *((const Point3F *)&_farPlane), _eyePos ) + _farPlane.d ); + matParams->set( negFarPlaneDotEye, negFarPlaneDotEyeVal ); + } + + if ( zNearFarInvNearFar->isValid() ) + matParams->set( zNearFarInvNearFar, Point4F( _zNear, _zFar, 1.0f / _zNear, 1.0f / _zFar ) ); +} + +void AdvancedLightBinManager::LightMaterialInfo::setLightParameters( const LightInfo *lightInfo, const MatrixF &worldViewOnly ) +{ + MaterialParameters *matParams = matInstance->getMaterialParameters(); + + // Set color in the right format, set alpha to the luminance value for the color. + ColorF col = lightInfo->getColor(); + + // TODO: The specularity control of the light + // is being scaled by the overall lumiance. + // + // Not sure if this may be the source of our + // bad specularity results maybe? + // + + const Point3F colorToLumiance( 0.3576f, 0.7152f, 0.1192f ); + F32 lumiance = mDot(*((const Point3F *)&lightInfo->getColor()), colorToLumiance ); + col.alpha *= lumiance; + + matParams->set( lightColor, col ); + matParams->set( lightBrightness, lightInfo->getBrightness() ); + matParams->set( constantSpecularPower, AdvancedLightBinManager::getConstantSpecularPower() ); + + switch( lightInfo->getType() ) + { + case LightInfo::Vector: + { + VectorF lightDir = lightInfo->getDirection(); + worldViewOnly.mulV(lightDir); + lightDir.normalize(); + matParams->set( lightDirection, lightDir ); + + // Set small number for alpha since it represents existing specular in + // the vector light. This prevents a divide by zero. + ColorF ambientColor = lightInfo->getAmbient(); + ambientColor.alpha = 0.00001f; + matParams->set( lightAmbient, ambientColor ); + + // If no alt color is specified, set it to the average of + // the ambient and main color to avoid artifacts. + // + // TODO: Trilight disabled until we properly implement it + // in the light info! + // + //ColorF lightAlt = lightInfo->getAltColor(); + ColorF lightAlt( ColorF::BLACK ); // = lightInfo->getAltColor(); + if ( lightAlt.red == 0.0f && lightAlt.green == 0.0f && lightAlt.blue == 0.0f ) + lightAlt = (lightInfo->getColor() + lightInfo->getAmbient()) / 2.0f; + + ColorF trilightColor = lightAlt; + matParams->set(lightTrilight, trilightColor); + } + break; + + case LightInfo::Spot: + { + const F32 outerCone = lightInfo->getOuterConeAngle(); + const F32 innerCone = getMin( lightInfo->getInnerConeAngle(), outerCone ); + const F32 outerCos = mCos( mDegToRad( outerCone / 2.0f ) ); + const F32 innerCos = mCos( mDegToRad( innerCone / 2.0f ) ); + Point4F spotParams( outerCos, + innerCos - outerCos, + mCos( mDegToRad( outerCone ) ), + 0.0f ); + + matParams->set( lightSpotParams, spotParams ); + + VectorF lightDir = lightInfo->getDirection(); + worldViewOnly.mulV(lightDir); + lightDir.normalize(); + matParams->set( lightDirection, lightDir ); + } + // Fall through + + case LightInfo::Point: + { + const F32 radius = lightInfo->getRange().x; + matParams->set( lightRange, radius ); + + Point3F lightPos; + worldViewOnly.mulP(lightInfo->getPosition(), &lightPos); + matParams->set( lightPosition, lightPos ); + + // Get the attenuation falloff ratio and normalize it. + Point3F attenRatio = lightInfo->getExtended()->attenuationRatio; + F32 total = attenRatio.x + attenRatio.y + attenRatio.z; + if ( total > 0.0f ) + attenRatio /= total; + + Point2F attenParams( ( 1.0f / radius ) * attenRatio.y, + ( 1.0f / ( radius * radius ) ) * attenRatio.z ); + + matParams->set( lightAttenuation, attenParams ); + break; + } + + default: + AssertFatal( false, "Bad light type!" ); + break; + } +} + +bool LightMatInstance::setupPass( SceneState *state, const SceneGraphData &sgData ) +{ + // Go no further if the material failed to initialize properly. + if ( !mProcessedMaterial || + mProcessedMaterial->getNumPasses() == 0 ) + return false; + + // Fetch the lightmap params + const LightMapParams *lmParams = sgData.lights[0]->getExtended(); + + // If no Lightmap params, let parent handle it + if(lmParams == NULL) + return Parent::setupPass(state, sgData); + + // Defaults + bool bRetVal = true; + + // What render pass is this... + if(mCurPass == -1) + { + // First pass, reset this flag + mInternalPass = false; + + // Pass call to parent + bRetVal = Parent::setupPass(state, sgData); + } + else + { + // If this light is represented in a lightmap, it has already done it's + // job for non-lightmapped geometry. Now render the lightmapped geometry + // pass (specular + shadow-darkening) + if(!mInternalPass && lmParams->representedInLightmap) + mInternalPass = true; + else + return Parent::setupPass(state, sgData); + } + + // Set up the shader constants we need to... + if(mLightMapParamsSC->isValid()) + { + // If this is an internal pass, special case the parameters + if(mInternalPass) + { + AssertFatal( lmParams->shadowDarkenColor.alpha == -1.0f, "Assumption failed, check unpack code!" ); + getMaterialParameters()->set( mLightMapParamsSC, lmParams->shadowDarkenColor ); + } + else + getMaterialParameters()->set( mLightMapParamsSC, ColorF::WHITE ); + } + + // Now override stateblock with our own + if(!mInternalPass) + { + // If this is not an internal pass, and this light is represented in lightmaps + // than only effect non-lightmapped geometry for this pass + if(lmParams->representedInLightmap) + GFX->setStateBlock(mLitState[StaticLightNonLMGeometry]); + else // This is a normal, dynamic light. + GFX->setStateBlock(mLitState[DynamicLight]); + + } + else // Internal pass, this is the add-specular/multiply-darken-color pass + GFX->setStateBlock(mLitState[StaticLightLMGeometry]); + + return bRetVal; +} + +bool LightMatInstance::init( const FeatureSet &features, const GFXVertexFormat *vertexFormat ) +{ + bool success = Parent::init(features, vertexFormat); + + // If the initialization failed don't continue. + if ( !success || !mProcessedMaterial || mProcessedMaterial->getNumPasses() == 0 ) + return false; + + mLightMapParamsSC = getMaterialParameterHandle("$lightMapParams"); + + // Grab the state block for the first render pass (since this mat instance + // inserts a pass after the first pass) + AssertFatal(mProcessedMaterial->getNumPasses() > 0, "No passes created! Ohnoes"); + const RenderPassData *rpd = mProcessedMaterial->getPass(0); + AssertFatal(rpd, "No render pass data!"); + AssertFatal(rpd->mRenderStates[0], "No render state 0!"); + + // Get state block desc for normal (not wireframe, not translucent, not glow, etc) + // render state + GFXStateBlockDesc litState = rpd->mRenderStates[0]->getDesc(); + + // Create state blocks for each of the 3 possible combos in setupPass + + //DynamicLight State: This will effect lightmapped and non-lightmapped geometry + // in the same way. + litState.separateAlphaBlendDefined = true; + litState.separateAlphaBlendEnable = false; + litState.stencilMask = RenderPrePassMgr::OpaqueDynamicLitMask | RenderPrePassMgr::OpaqueStaticLitMask; + mLitState[DynamicLight] = GFX->createStateBlock(litState); + + // StaticLightNonLMGeometry State: This will treat non-lightmapped geometry + // in the usual way, but will not effect lightmapped geometry. + litState.separateAlphaBlendDefined = true; + litState.separateAlphaBlendEnable = false; + litState.stencilMask = RenderPrePassMgr::OpaqueDynamicLitMask; + mLitState[StaticLightNonLMGeometry] = GFX->createStateBlock(litState); + + // StaticLightLMGeometry State: This will add specular information (alpha) but + // multiply-darken color information. + litState.blendDest = GFXBlendSrcColor; + litState.blendSrc = GFXBlendZero; + litState.stencilMask = RenderPrePassMgr::OpaqueStaticLitMask; + litState.separateAlphaBlendDefined = true; + litState.separateAlphaBlendEnable = true; + litState.separateAlphaBlendSrc = GFXBlendOne; + litState.separateAlphaBlendDest = GFXBlendOne; + litState.separateAlphaBlendOp = GFXBlendOpAdd; + mLitState[StaticLightLMGeometry] = GFX->createStateBlock(litState); + + return true; +} + +ConsoleFunction( setShadowFilterMode, void, 2, 2, + "Sets the shadow filtering mode as None, SoftShadow, or SoftShadowHighQuality" ) +{ + ShadowFilterMode mode = ShadowFilterMode_None; + if ( dStricmp( argv[1], "SoftShadow" ) == 0 ) + mode = ShadowFilterMode_SoftShadow; + else if ( dStricmp( argv[1], "SoftShadowHighQuality" ) == 0 ) + mode = ShadowFilterMode_SoftShadowHighQuality; + + if ( mode != AdvancedLightBinManager::smShadowFilterMode ) + { + AdvancedLightBinManager::smShadowFilterMode = mode; + + AdvancedLightBinManager *lightBin = NULL; + if ( Sim::findObject( "AL_LightBinMgr", lightBin ) && lightBin ) + lightBin->deleteLightMaterials(); + } +} diff --git a/lighting/advanced/advancedLightBinManager.h b/lighting/advanced/advancedLightBinManager.h new file mode 100644 index 0000000..b1d10eb --- /dev/null +++ b/lighting/advanced/advancedLightBinManager.h @@ -0,0 +1,221 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ADVANCEDLIGHTBINMANAGER_H_ +#define _ADVANCEDLIGHTBINMANAGER_H_ + +#ifndef _TEXTARGETBIN_MGR_H_ +#include "renderInstance/renderTexTargetBinManager.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif +#ifndef _MATINSTANCE_H_ +#include "materials/matInstance.h" +#endif +#ifndef _SHADOW_COMMON_H_ +#include "lighting/shadowMap/shadowCommon.h" +#endif + + +class AdvancedLightManager; +class ShadowMapManager; +class LightShadowMap; +class AdvancedLightBufferConditioner; +class LightMapParams; + + +class LightMatInstance : public MatInstance +{ + typedef MatInstance Parent; +protected: + MaterialParameterHandle *mLightMapParamsSC; + bool mInternalPass; + + enum + { + DynamicLight = 0, + StaticLightNonLMGeometry, + StaticLightLMGeometry, + NUM_LIT_STATES + }; + GFXStateBlockRef mLitState[NUM_LIT_STATES]; + +public: + LightMatInstance(Material &mat) : Parent(mat), mLightMapParamsSC(NULL), mInternalPass(false) {} + + virtual bool init( const FeatureSet &features, const GFXVertexFormat *vertexFormat ); + virtual bool setupPass( SceneState *state, const SceneGraphData &sgData ); +}; + + +class AdvancedLightBinManager : public RenderTexTargetBinManager +{ + typedef RenderTexTargetBinManager Parent; + friend class AdvancedLightProbeManager; + +public: + + // Light info Render Inst Type + static const RenderInstType RIT_LightInfo; + + // registered buffer name + static const String smBufferName; + + /// The shadow filter mode to use on shadowed light materials. + /// @see setShadowFilterMode + static ShadowFilterMode smShadowFilterMode; + + // Used for console init + AdvancedLightBinManager( AdvancedLightManager *lm = NULL, + ShadowMapManager *sm = NULL, + GFXFormat lightBufferFormat = GFXFormatR8G8B8A8 ); + virtual ~AdvancedLightBinManager(); + + // RenderBinManager interface + virtual AddInstResult addElement(RenderInst *); + virtual void render(SceneState *); + virtual void clear(); + virtual void sort() {} + + // Expose a conditioner for light information + virtual ConditionerFeature *getTargetConditioner() const; + + // Add a light to the bins + virtual AddInstResult addLight( LightInfo *light ); + + virtual bool setTargetSize(const Point2I &newTargetSize); + + // ConsoleObject interface + DECLARE_CONOBJECT(AdvancedLightBinManager); + + /// Returns the constant specular power. + /// @see smConstantSpecularPower + static F32 getConstantSpecularPower() { return smConstantSpecularPower; } + + /// Sets the constant specular power. + /// @see smConstantSpecularPower + static void setConstantSpecularPower(F32 csp) { smConstantSpecularPower = csp; } + + bool MRTLightmapsDuringPrePass() const { return mMRTLightmapsDuringPrePass; } + void MRTLightmapsDuringPrePass(bool val); + + /// Frees all the currently allocated light materials. + void deleteLightMaterials(); + +protected: + + // Track a light material and associated data + struct LightMaterialInfo + { + LightMatInstance *matInstance; + + // { zNear, zFar, 1/zNear, 1/zFar } + MaterialParameterHandle *zNearFarInvNearFar; + + // Far frustum plane (World Space) + MaterialParameterHandle *farPlane; + + // Far frustum plane (View Space) + MaterialParameterHandle *vsFarPlane; + + // -dot( farPlane, eyePos ) + MaterialParameterHandle *negFarPlaneDotEye; + + // Light Parameters + MaterialParameterHandle *lightPosition; + MaterialParameterHandle *lightDirection; + MaterialParameterHandle *lightColor; + MaterialParameterHandle *lightBrightness; + MaterialParameterHandle *lightAttenuation; + MaterialParameterHandle *lightRange; + MaterialParameterHandle *lightAmbient; + MaterialParameterHandle *lightTrilight; + MaterialParameterHandle *lightSpotParams; + + // Constant specular power + MaterialParameterHandle *constantSpecularPower; + + LightMaterialInfo( const String &matName, + const GFXVertexFormat *vertexFormat, + const Vector ¯os = Vector() ); + + virtual ~LightMaterialInfo(); + + + void setViewParameters( const F32 zNear, + const F32 zFar, + const Point3F &eyePos, + const PlaneF &farPlane, + const PlaneF &_vsFarPlane ); + + void setLightParameters( const LightInfo *light, const MatrixF &worldViewOnly ); + }; + +protected: + + struct LightBinEntry + { + LightInfo* lightInfo; + LightShadowMap* shadowMap; + LightMaterialInfo* lightMaterial; + GFXPrimitiveBuffer* primBuffer; + GFXVertexBuffer* vertBuffer; + U32 numPrims; + }; + + Vector mLightBin; + typedef Vector::iterator LightBinIterator; + + bool mMRTLightmapsDuringPrePass; + + U32 mNumLightsCulled; + AdvancedLightManager *mLightManager; + ShadowMapManager *mShadowManager; + Frustum mFrustum; + Frustum mViewSpaceFrustum; + + static const String smLightMatNames[LightInfo::Count]; + + static const String smShadowTypeMacro[ShadowType_Count]; + + static const GFXVertexFormat* smLightMatVertex[LightInfo::Count]; + + typedef CompoundKey LightMatKey; + + typedef HashTable LightMatTable; + + /// The fixed table of light material info. + LightMatTable mLightMaterials; + + LightMaterialInfo* _getLightMaterial( LightInfo::Type lightType, ShadowType shadowType ); + + + AdvancedLightBufferConditioner *mConditioner; + + /// This value is used as a constant power to raise specular values to, before + /// storing them into the light info buffer. The per-material specular value is + /// then computer by using the integer identity of exponentiation: (a^m)^n = a^(m*n) + /// or: (specular^constSpecular)^(matSpecular/constSpecular) = specular^(matSpecular*constSpecular) + static F32 smConstantSpecularPower; + + typedef GFXVertexPNT FarFrustumQuadVert; + GFXVertexBufferHandle mFarFrustumQuadVerts; + + + //void _createMaterials(); + + void _setupPerFrameParameters( const Point3F &eyePos ); + + void setupSGData( SceneGraphData &data, LightInfo *light ); +}; + +#endif // _ADVANCEDLIGHTBINMANAGER_H_ diff --git a/lighting/advanced/advancedLightBufferConditioner.cpp b/lighting/advanced/advancedLightBufferConditioner.cpp new file mode 100644 index 0000000..f6fa78e --- /dev/null +++ b/lighting/advanced/advancedLightBufferConditioner.cpp @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/advancedLightBufferConditioner.h" + +#include "shaderGen/shaderOp.h" +#include "gfx/gfxDevice.h" +#include "core/util/safeDelete.h" + + +AdvancedLightBufferConditioner::~AdvancedLightBufferConditioner() +{ +} + +Var *AdvancedLightBufferConditioner::_conditionOutput( Var *unconditionedOutput, MultiLine *meta ) +{ + Var *conditionedOutput = new Var; + + if(GFX->getAdapterType() == OpenGL) + conditionedOutput->setType("vec4"); + else + conditionedOutput->setType("float4"); + + DecOp *outputDecl = new DecOp(conditionedOutput); + if(mColorFormat == RGB) + { + conditionedOutput->setName("rgbLightInfoOut"); + + // If this is a 16 bit integer format, scale up/down the values. All other + // formats just write out the full 0..1 + if(getBufferFormat() == GFXFormatR16G16B16A16) + meta->addStatement( new GenOp( " @ = max(4.0, (float4(lightColor, specular) * NL_att + float4(bufferSample.rgb, 0.0)) / 4.0);\r\n", outputDecl ) ); + else + meta->addStatement( new GenOp( " @ = float4(lightColor, specular) * NL_att + float4(bufferSample.rgb, 0.0);\r\n", outputDecl ) ); + } + else + { + // Input u'v' assumed to be scaled + conditionedOutput->setName("luvLightInfoOut"); + meta->addStatement( new GenOp( " @ = float4( lerp(bufferSample.xy, lightColor.xy, saturate(NL_att / bufferSample.z) * 0.5),\r\n", outputDecl ) ); + meta->addStatement( new GenOp( " bufferSample.z + NL_att, bufferSample.w + saturate(specular * NL_att) );\r\n" ) ); + } + + return conditionedOutput; +} + +Var *AdvancedLightBufferConditioner::_unconditionInput( Var *conditionedInput, MultiLine *meta ) +{ + if(mColorFormat == RGB) + { + if(getBufferFormat() == GFXFormatR16G16B16A16) + meta->addStatement( new GenOp( " lightColor = @.rgb * 4.0;\r\n", conditionedInput ) ); + else + meta->addStatement( new GenOp( " lightColor = @.rgb;\r\n", conditionedInput ) ); + meta->addStatement( new GenOp( " NL_att = dot(@.rgb, float3(0.3576, 0.7152, 0.1192));\r\n", conditionedInput ) ); + } + else + { + meta->addStatement( new GenOp( " // TODO: This clamps HDR values.\r\n" ) ); + meta->addStatement( new GenOp( " NL_att = @.b;\r\n", conditionedInput ) ); + meta->addStatement( new GenOp( " lightColor = DecodeLuv(float3(saturate(NL_att), @.rg * 0.62));\r\n", conditionedInput ) ); + } + meta->addStatement( new GenOp( " specular = max(@.a / NL_att, 0.00001f);\r\n", conditionedInput ) ); + + return NULL; +} + +Var *AdvancedLightBufferConditioner::printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ) +{ + Var *methodVar = new Var; + methodVar->setName(methodName); + DecOp *methodDecl = new DecOp(methodVar); + + Var *lightColor = new Var; + lightColor->setName("lightColor"); + DecOp *lightColorDecl = new DecOp(lightColor); + + Var *NLAtt = new Var; + NLAtt->setName("NL_att"); + DecOp *NLAttDecl = new DecOp(NLAtt); + + Var *specular = new Var; + specular->setName("specular"); + DecOp *specularDecl = new DecOp(specular); + + Var *bufferSample = new Var; + bufferSample->setName("bufferSample"); + DecOp *bufferSampleDecl = new DecOp(bufferSample); + + const bool isCondition = ( methodType == ConditionerFeature::ConditionMethod ); + + if(GFX->getAdapterType() == OpenGL) + { + methodVar->setType(avar("%s", isCondition ? "vec4" : "void")); + lightColor->setType(avar("%s vec3", isCondition ? "in" : "out")); + NLAtt->setType(avar("%s float", isCondition ? "in" : "out")); + specular->setType(avar("%s float", isCondition ? "in" : "out")); + bufferSample->setType("in vec4"); + } + else + { + methodVar->setType(avar("inline %s", isCondition ? "float4" : "void")); + lightColor->setType(avar("%s float3", isCondition ? "in" : "out")); + NLAtt->setType(avar("%s float", isCondition ? "in" : "out")); + specular->setType(avar("%s float", isCondition ? "in" : "out")); + bufferSample->setType("in float4"); + } + + // If this is LUV, print methods to convert RGB<->LUV as needed + if(mColorFormat == LUV) + { + if(!isCondition) + { + meta->addStatement( new GenOp( "float3 DecodeLuv(float3 Luv)\r\n{\r\n" ) ); + meta->addStatement( new GenOp( " float2 xy = float2(9.0f, 4.0f) * Luv.yz / (dot(Luv.yz, float2(6.0f, -16.0f)) + 12.0f);\r\n" ) ); + meta->addStatement( new GenOp( " float Ld = Luv.x;\r\n" ) ); + meta->addStatement( new GenOp( " float3 XYZ = float3(xy.x, Ld, 1.0f - xy.x - xy.y);\r\n" ) ); + meta->addStatement( new GenOp( " XYZ.xz = XYZ.xz * Ld / xy.y;\r\n" ) ); + meta->addStatement( new GenOp( " const float3x3 XYZ2RGB =\r\n" ) ); + meta->addStatement( new GenOp( " {\r\n" ) ); + meta->addStatement( new GenOp( " 2.5651f, -1.1665f, -0.3986f,\r\n" ) ); + meta->addStatement( new GenOp( " -1.0217f, 1.9777f, 0.0439f,\r\n" ) ); + meta->addStatement( new GenOp( " 0.0753f, -0.2543f, 1.1892f\r\n" ) ); + meta->addStatement( new GenOp( " };\r\n" ) ); + meta->addStatement( new GenOp( " return mul(XYZ2RGB, XYZ);\r\n" ) ); + meta->addStatement( new GenOp( "}\r\n\r\n" ) ); + } + else + { + // Shouldn't need this + } + } + + // Method header and opening bracket + if(isCondition) + { + // All parameters are input parameters, and the return value is float4. + // If this is an LUV buffer format, than the previous pixel value is needed + // for interpolation. + meta->addStatement( new GenOp( "@(@, @, @, @)\r\n", methodDecl, lightColorDecl, NLAttDecl, specularDecl, bufferSampleDecl ) ); + } + else + { + // Sample as input, parameters as output. Void return. + meta->addStatement( new GenOp( "@(@, @, @, @)\r\n", methodDecl, bufferSampleDecl, lightColorDecl, NLAttDecl, specularDecl ) ); + } + + meta->addStatement( new GenOp( "{\r\n" ) ); + + // We don't use this way of passing var's around, so this should cause a crash + // if something uses this improperly + return ( isCondition ? NULL : bufferSample ); +} + +void AdvancedLightBufferConditioner::printMethodFooter( ConditionerFeature::MethodType methodType, Var *retVar, Stream &stream, MultiLine *meta ) +{ + // Return and closing bracket + if(methodType == ConditionerFeature::ConditionMethod) + meta->addStatement( new GenOp( "\r\n return @;\r\n", retVar ) ); + + // Uncondition will assign output parameters + meta->addStatement( new GenOp( "}\r\n" ) ); +} \ No newline at end of file diff --git a/lighting/advanced/advancedLightBufferConditioner.h b/lighting/advanced/advancedLightBufferConditioner.h new file mode 100644 index 0000000..00fc361 --- /dev/null +++ b/lighting/advanced/advancedLightBufferConditioner.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _ADVANCED_LIGHTBUFFER_CONDITIONER_H_ +#define _ADVANCED_LIGHTBUFFER_CONDITIONER_H_ + +#ifndef _CONDITIONER_BASE_H_ +#include "shaderGen/conditionerFeature.h" +#endif + + +class AdvancedLightBufferConditioner : public ConditionerFeature +{ + typedef ConditionerFeature Parent; + +public: + enum ColorFormat + { + RGB, + LUV + }; + +public: + + AdvancedLightBufferConditioner(const GFXFormat bufferFormat, const ColorFormat colorFormat) + : Parent(bufferFormat), mColorFormat(colorFormat) + { + + } + + virtual ~AdvancedLightBufferConditioner(); + + virtual String getName() + { + return String("Light Buffer Conditioner ") + String( mColorFormat == RGB ? "[RGB]" : "[LUV]" ); + } + +protected: + ColorFormat mColorFormat; + + virtual Var *_conditionOutput( Var *unconditionedOutput, MultiLine *meta ); + virtual Var *_unconditionInput( Var *conditionedInput, MultiLine *meta ); + virtual Var *printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ); + virtual void printMethodFooter( MethodType methodType, Var *retVar, Stream &stream, MultiLine *meta ); +}; + +#endif // _ADVANCED_LIGHTBUFFER_CONDITIONER_H_ \ No newline at end of file diff --git a/lighting/advanced/advancedLightManager.cpp b/lighting/advanced/advancedLightManager.cpp new file mode 100644 index 0000000..0209f41 --- /dev/null +++ b/lighting/advanced/advancedLightManager.cpp @@ -0,0 +1,644 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/advancedLightManager.h" + +#include "lighting/advanced/advancedLightBinManager.h" +#include "lighting/advanced/advancedLightingFeatures.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "lighting/shadowMap/lightShadowMap.h" +#include "lighting/common/sceneLighting.h" +#include "lighting/common/lightMapParams.h" +#include "core/util/safeDelete.h" +#include "renderInstance/renderPrePassMgr.h" +#include "materials/materialManager.h" +#include "math/util/sphereMesh.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneState.h" + + +const String AdvancedLightManager::ConstantSpecularPowerSC( "$constantSpecularPower" ); + +AdvancedLightManager AdvancedLightManager::smSingleton; + + +AdvancedLightManager::AdvancedLightManager() + : LightManager( "Advanced Lighting", "ADVLM" ) +{ + mLightBinManager = NULL; + mLastShader = NULL; + mAvailableSLInterfaces = NULL; +} + +AdvancedLightManager::~AdvancedLightManager() +{ + mLastShader = NULL; + mLastConstants = NULL; + + for (LightConstantMap::Iterator i = mConstantLookup.begin(); i != mConstantLookup.end(); i++) + { + if (i->value) + SAFE_DELETE(i->value); + } + mConstantLookup.clear(); +} + +bool AdvancedLightManager::isCompatible() const +{ + // TODO: We need at least 3.0 shaders at the moment + // but this should be relaxed to 2.0 soon. + if ( GFX->getPixelShaderVersion() < 3.0 ) + return false; + + // TODO: Test for the necessary texture formats! + + return true; +} + +void AdvancedLightManager::activate( SceneGraph *sceneManager ) +{ + Parent::activate( sceneManager ); + + GFXShader::addGlobalMacro( "TORQUE_ADVANCED_LIGHTING" ); + + gClientSceneGraph->setPostEffectFog( true ); + + SHADOWMGR->activate(); + + // Find a target format that supports blending... + // we prefer the floating point format if it works. + Vector formats; + formats.push_back( GFXFormatR16G16B16A16F ); + formats.push_back( GFXFormatR16G16B16A16 ); + GFXFormat blendTargetFormat = GFX->selectSupportedFormat( &GFXDefaultRenderTargetProfile, + formats, + true, + true, + false ); + + mLightBinManager = new AdvancedLightBinManager( this, SHADOWMGR, blendTargetFormat ); + mLightBinManager->assignName( "AL_LightBinMgr" ); + + // First look for the prepass bin... + RenderPrePassMgr *prePassBin = NULL; + RenderPassManager *rpm = getSceneManager()->getRenderPass(); + for( U32 i = 0; i < rpm->getManagerCount(); i++ ) + { + RenderBinManager *bin = rpm->getManager(i); + if( bin->getRenderInstType() == RenderPrePassMgr::RIT_PrePass ) + { + prePassBin = (RenderPrePassMgr*)bin; + break; + } + } + + // If we didn't find the prepass bin then add one. + if ( !prePassBin ) + { + prePassBin = new RenderPrePassMgr( true, blendTargetFormat ); + prePassBin->registerObject(); + rpm->addManager( prePassBin ); + mPrePassRenderBin = prePassBin; + } + + // Tell the material manager that prepass is enabled. + MATMGR->setPrePassEnabled( true ); + + // Insert our light bin manager. + mLightBinManager->setRenderOrder( prePassBin->getRenderOrder() + 0.01f ); + rpm->addManager( mLightBinManager ); + + AdvancedLightingFeatures::registerFeatures(mPrePassRenderBin->getTargetFormat(), mLightBinManager->getTargetFormat()); + + // Last thing... let everyone know we're active. + smActivateSignal.trigger( getId(), true ); +} + +void AdvancedLightManager::deactivate() +{ + Parent::deactivate(); + + GFXShader::removeGlobalMacro( "TORQUE_ADVANCED_LIGHTING" ); + + MatTextureTarget::unregisterTarget( "AL_ShadowVizTexture", NULL ); + + // Release our bin manager... it will take care of + // removing itself from the render passes. + if( mLightBinManager ) + { + mLightBinManager->MRTLightmapsDuringPrePass(false); + mLightBinManager->deleteObject(); + } + mLightBinManager = NULL; + + if ( mPrePassRenderBin ) + mPrePassRenderBin->deleteObject(); + mPrePassRenderBin = NULL; + + SHADOWMGR->deactivate(); + + mLastShader = NULL; + mLastConstants = NULL; + + for (LightConstantMap::Iterator i = mConstantLookup.begin(); i != mConstantLookup.end(); i++) + { + if (i->value) + SAFE_DELETE(i->value); + } + mConstantLookup.clear(); + + mSphereGeometry = NULL; + mSphereIndices = NULL; + mConeGeometry = NULL; + mConeIndices = NULL; + + AdvancedLightingFeatures::unregisterFeatures(); + + // Now let everyone know we've deactivated. + smActivateSignal.trigger( getId(), false ); +} + +void AdvancedLightManager::_addLightInfoEx( LightInfo *lightInfo ) +{ + lightInfo->addExtended( new ShadowMapParams( lightInfo ) ); + lightInfo->addExtended( new LightMapParams( lightInfo ) ); +} + + +void AdvancedLightManager::_initLightFields() +{ + static EnumTable::Enums gShadowTypeEnums[] = + { + { ShadowType_Spot, "Spot" }, + { ShadowType_PSSM, "PSSM" }, + { ShadowType_Paraboloid, "Paraboloid" }, + { ShadowType_DualParaboloidSinglePass, "DualParaboloidSinglePass" }, + { ShadowType_DualParaboloid, "DualParaboloid" }, + { ShadowType_CubeMap, "CubeMap" }, + }; + + static EnumTable smShadowTypeTable( ShadowType_Count, gShadowTypeEnums ); + + + #define DEFINE_LIGHT_FIELD( var, type, enum_ ) \ + static inline const char* _get##var##Field( void *obj, const char *data ) \ + { \ + ShadowMapParams *p = _getShadowMapParams( obj ); \ + if ( p ) \ + return Con::getData( type, &p->var, 0, enum_ ); \ + else \ + return ""; \ + } \ + \ + static inline bool _set##var##Field( void *obj, const char *data ) \ + { \ + ShadowMapParams *p = _getShadowMapParams( obj ); \ + if ( p ) \ + { \ + Con::setData( type, &p->var, 0, 1, &data, enum_ ); \ + p->_validate(); \ + } \ + return false; \ + } \ + + #define DEFINE_LIGHTMAP_FIELD( var, type, enum_ ) \ + static inline const char* _get##var##Field( void *obj, const char *data ) \ + { \ + LightMapParams *p = _getLightMapParams( obj ); \ + if ( p ) \ + return Con::getData( type, &p->var, 0, enum_ ); \ + else \ + return ""; \ + } \ + \ + static inline bool _set##var##Field( void *obj, const char *data ) \ + { \ + LightMapParams *p = _getLightMapParams( obj ); \ + if ( p ) \ + { \ + Con::setData( type, &p->var, 0, 1, &data, enum_ ); \ + } \ + return false; \ + } \ + + #define ADD_LIGHT_FIELD( field, type, var, enum_, desc ) \ + ConsoleObject::addProtectedField( field, type, 0, \ + &Dummy::_set##var##Field, &Dummy::_get##var##Field, 1, enum_, desc ) \ + + // Our dummy adaptor class which we hide in here + // to keep from poluting the global namespace. + class Dummy + { + protected: + + static inline ShadowMapParams* _getShadowMapParams( void *obj ) + { + ISceneLight *sceneLight = dynamic_cast( (SimObject*)obj ); + if ( sceneLight ) + { + LightInfo *lightInfo = sceneLight->getLight(); + if ( lightInfo ) + return lightInfo->getExtended(); + } + return NULL; + } + + static inline LightMapParams* _getLightMapParams( void *obj ) + { + ISceneLight *sceneLight = dynamic_cast( (SimObject*)obj ); + if ( sceneLight ) + { + LightInfo *lightInfo = sceneLight->getLight(); + if ( lightInfo ) + return lightInfo->getExtended(); + } + return NULL; + } + + public: + + DEFINE_LIGHT_FIELD( attenuationRatio, TypePoint3F, NULL ); + DEFINE_LIGHT_FIELD( shadowType, TypeEnum, &smShadowTypeTable ); + DEFINE_LIGHT_FIELD( texSize, TypeS32, NULL ); + DEFINE_LIGHT_FIELD( numSplits, TypeS32, NULL ); + DEFINE_LIGHT_FIELD( logWeight, TypeF32, NULL ); + DEFINE_LIGHT_FIELD( overDarkFactor, TypePoint4F, NULL); + DEFINE_LIGHT_FIELD( shadowDistance, TypeF32, NULL ); + DEFINE_LIGHT_FIELD( shadowSoftness, TypeF32, NULL ); + DEFINE_LIGHT_FIELD( fadeStartDist, TypeF32, NULL ); + DEFINE_LIGHT_FIELD( lastSplitTerrainOnly, TypeBool, NULL ); + DEFINE_LIGHT_FIELD( splitFadeDistances, TypePoint4F, NULL ); + + DEFINE_LIGHTMAP_FIELD( representedInLightmap, TypeBool, NULL ); + DEFINE_LIGHTMAP_FIELD( shadowDarkenColor, TypeColorF, NULL ); + DEFINE_LIGHTMAP_FIELD( includeLightmappedGeometryInShadow, TypeBool, NULL ); + }; + + ConsoleObject::addGroup( "Advanced Lighting" ); + + ADD_LIGHT_FIELD( "attenuationRatio", TypePoint3F, attenuationRatio, NULL, + "The proportions of constant, linear, and quadratic attenuation to use for " + "the falloff for point and spot lights." ); + + ADD_LIGHT_FIELD( "shadowType", TypeEnum, shadowType, &smShadowTypeTable, + "The type of shadow to use on this light." ); + + ADD_LIGHT_FIELD( "texSize", TypeS32, texSize, NULL, + "The texture size of the shadow map." ); + + ADD_LIGHT_FIELD( "overDarkFactor", TypePoint4F, overDarkFactor, NULL, + "The ESM shadow darkening factor"); + + ADD_LIGHT_FIELD( "shadowDistance", TypeF32, shadowDistance, NULL, + "The distance from the camera to extend the PSSM shadow." ); + + ADD_LIGHT_FIELD( "shadowSoftness", TypeF32, shadowSoftness, NULL, + "" ); + + ADD_LIGHT_FIELD( "numSplits", TypeS32, numSplits, NULL, + "The logrithmic PSSM split distance factor." ); + + ADD_LIGHT_FIELD( "logWeight", TypeF32, logWeight, NULL, + "The logrithmic PSSM split distance factor." ); + + ADD_LIGHT_FIELD( "fadeStartDistance", TypeF32, fadeStartDist, NULL, + "Start fading shadows out at this distance. 0 = auto calculate this distance."); + + ADD_LIGHT_FIELD( "lastSplitTerrainOnly", TypeBool, lastSplitTerrainOnly, NULL, + "This toggles only terrain being rendered to the last split of a PSSM shadow map."); + + ADD_LIGHT_FIELD( "splitFadeDistances", TypePoint4F, splitFadeDistances, NULL, + "Distances (in world space units) that the shadows will fade between PSSM splits"); + + ConsoleObject::endGroup( "Advanced Lighting" ); + + ConsoleObject::addGroup( "Advanced Lighting Lightmap" ); + + ADD_LIGHT_FIELD( "representedInLightmap", TypeBool, representedInLightmap, NULL, + "This light is represented in lightmaps (static light, default: false)"); + + ADD_LIGHT_FIELD( "shadowDarkenColor", TypeColorF, shadowDarkenColor, NULL, + "The color that should be used to multiply-blend dynamic shadows onto lightmapped geometry (ignored if 'representedInLightmap' is false)"); + + ADD_LIGHT_FIELD( "includeLightmappedGeometryInShadow", TypeBool, includeLightmappedGeometryInShadow, NULL, + "This light should render lightmapped geometry during its shadow-map update (ignored if 'representedInLightmap' is false)"); + + ConsoleObject::endGroup( "Advanced Lighting Lightmap" ); + + #undef DEFINE_LIGHT_FIELD + #undef ADD_LIGHT_FIELD +} + +void AdvancedLightManager::setLightInfo( ProcessedMaterial *pmat, + const Material *mat, + const SceneGraphData &sgData, + const SceneState *state, + U32 pass, + GFXShaderConstBuffer *shaderConsts) +{ + // Skip this if we're rendering from the prepass bin. + if ( sgData.binType == SceneGraphData::PrePassBin ) + return; + + PROFILE_SCOPE(AdvancedLightManager_setLightInfo); + + LightingShaderConstants *lsc = getLightingShaderConstants(shaderConsts); + + LightShadowMap *lsm = SHADOWMGR->getCurrentShadowMap(); + + LightInfo *light; + if ( lsm ) + light = lsm->getLightInfo(); + else + { + light = sgData.lights[0]; + if ( !light ) + light = getDefaultLight(); + } + + // NOTE: If you encounter a crash from this point forward + // while setting a shader constant its probably because the + // mConstantLookup has bad shaders/constants in it. + // + // This is a known crash bug that can occur if materials/shaders + // are reloaded and the light manager is not reset. + // + // We should look to fix this by clearing the table. + + // Update the forward shading light constants. + _update4LightConsts( sgData, + lsc->mLightPositionSC, + lsc->mLightDiffuseSC, + lsc->mLightAmbientSC, + lsc->mLightInvRadiusSqSC, + lsc->mLightSpotDirSC, + lsc->mLightSpotAngleSC, + shaderConsts ); + + if ( lsc->mConstantSpecularPowerSC->isValid() ) + shaderConsts->set( lsc->mConstantSpecularPowerSC, AdvancedLightBinManager::getConstantSpecularPower() ); + + if ( lsm ) + { + if ( lsc->mWorldToLightProjSC->isValid() ) + shaderConsts->set( lsc->mWorldToLightProjSC, + lsm->getWorldToLightProj(), + lsc->mWorldToLightProjSC->getType() ); + + if ( lsc->mViewToLightProjSC->isValid() ) + { + // TODO: Should not do this mul here probably + shaderConsts->set( lsc->mViewToLightProjSC, + lsm->getWorldToLightProj() * state->getCameraTransform(), + lsc->mViewToLightProjSC->getType() ); + } + + if ( lsc->mShadowMapSizeSC->isValid() ) + shaderConsts->set( lsc->mShadowMapSizeSC, 1.0f / (F32)lsm->getTexSize() ); + + // Do this last so that overrides can properly override parameters previously set + lsm->setShaderParameters(shaderConsts, lsc); + } +} + +void AdvancedLightManager::registerGlobalLight(LightInfo *light, SimObject *obj) +{ + Parent::registerGlobalLight( light, obj ); + + // Pass the volume lights to the bin manager. + if ( mLightBinManager && + ( light->getType() == LightInfo::Point || + light->getType() == LightInfo::Spot ) ) + mLightBinManager->addLight( light ); +} + +void AdvancedLightManager::unregisterAllLights() +{ + Parent::unregisterAllLights(); + + if ( mLightBinManager ) + mLightBinManager->clear(); +} + +bool AdvancedLightManager::setTextureStage( const SceneGraphData &sgData, + const U32 currTexFlag, + const U32 textureSlot, + GFXShaderConstBuffer *shaderConsts, + ShaderConstHandles *handles ) +{ + LightShadowMap* lsm = SHADOWMGR->getCurrentShadowMap(); + + // Assign Shadowmap, if it exists + LightingShaderConstants* lsc = getLightingShaderConstants(shaderConsts); + if ( !lsc ) + return false; + + if(lsm) + return lsm->setTextureStage( sgData, currTexFlag, textureSlot, shaderConsts, lsc->mShadowMapSC ); + + // HACK: We should either be setting a 2x2 white texture + // or be using a shader that doesn't sample the shadow maps! + + switch (currTexFlag) + { + case Material::DynamicLight: + { + if(shaderConsts && lsc->mShadowMapSC->isValid()) + shaderConsts->set(lsc->mShadowMapSC, (S32)textureSlot); + GFX->setTexture( textureSlot, NULL ); + return true; + } + }; + + return false; +} + +LightingShaderConstants* AdvancedLightManager::getLightingShaderConstants(GFXShaderConstBuffer* buffer) +{ + if ( !buffer ) + return NULL; + + PROFILE_SCOPE( AdvancedLightManager_GetLightingShaderConstants ); + + GFXShader* shader = buffer->getShader(); + + // Check to see if this is the same shader, we'll get hit repeatedly by + // the same one due to the render bin loops. + if ( mLastShader.getPointer() != shader ) + { + LightConstantMap::Iterator iter = mConstantLookup.find(shader); + if ( iter != mConstantLookup.end() ) + { + mLastConstants = iter->value; + } + else + { + LightingShaderConstants* lsc = new LightingShaderConstants(); + mConstantLookup[shader] = lsc; + + mLastConstants = lsc; + } + + // Set our new shader + mLastShader = shader; + } + + // Make sure that our current lighting constants are initialized + if (!mLastConstants->mInit) + mLastConstants->init(shader); + + return mLastConstants; +} + +GFXVertexBufferHandle AdvancedLightManager::getSphereMesh(U32 &outNumPrimitives, GFXPrimitiveBuffer *&outPrimitives) +{ + static SphereMesh sSphereMesh; + + if( mSphereGeometry.isNull() ) + { + const SphereMesh::TriangleMesh * sphereMesh = sSphereMesh.getMesh(3); + S32 numPoly = sphereMesh->numPoly; + mSpherePrimitiveCount = 0; + mSphereGeometry.set(GFX, numPoly*3, GFXBufferTypeStatic); + mSphereGeometry.lock(); + S32 vertexIndex = 0; + + for (S32 i=0; ipoly[i].pnt[0]; + mSphereGeometry[vertexIndex].color = ColorI::WHITE; + vertexIndex++; + + mSphereGeometry[vertexIndex].point = sphereMesh->poly[i].pnt[1]; + mSphereGeometry[vertexIndex].color = ColorI::WHITE; + vertexIndex++; + + mSphereGeometry[vertexIndex].point = sphereMesh->poly[i].pnt[2]; + mSphereGeometry[vertexIndex].color = ColorI::WHITE; + vertexIndex++; + } + mSphereGeometry.unlock(); + } + + outNumPrimitives = mSpherePrimitiveCount; + outPrimitives = NULL; // For now + return mSphereGeometry; +} + +GFXVertexBufferHandle AdvancedLightManager::getConeMesh(U32 &outNumPrimitives, GFXPrimitiveBuffer *&outPrimitives ) +{ + static const Point2F circlePoints[] = + { + Point2F(0.707107f, 0.707107f), + Point2F(0.923880f, 0.382683f), + Point2F(1.000000f, 0.000000f), + Point2F(0.923880f, -0.382684f), + Point2F(0.707107f, -0.707107f), + Point2F(0.382683f, -0.923880f), + Point2F(0.000000f, -1.000000f), + Point2F(-0.382683f, -0.923880f), + Point2F(-0.707107f, -0.707107f), + Point2F(-0.923880f, -0.382684f), + Point2F(-1.000000f, 0.000000f), + Point2F(-0.923879f, 0.382684f), + Point2F(-0.707107f, 0.707107f), + Point2F(-0.382683f, 0.923880f), + Point2F(0.000000f, 1.000000f), + Point2F(0.382684f, 0.923879f) + }; + const S32 numPoints = sizeof(circlePoints)/sizeof(Point2F); + + if ( mConeGeometry.isNull() ) + { + mConeGeometry.set(GFX, numPoints + 1, GFXBufferTypeStatic); + mConeGeometry.lock(); + + mConeGeometry[0].point = Point3F(0.0f,0.0f,0.0f); + + for (S32 i=1; i numPoints ) ? 1 : i + 1; + } + + // Build the bottom of the cone (reverse winding order) + for( int i = 1; i < numPoints - 1; i++ ) + { + idx[idxIdx++] = 1; + idx[idxIdx++] = i + 2; + idx[idxIdx++] = i + 1; + } + mConeIndices.unlock(); + } + + outNumPrimitives = mConePrimitiveCount; + outPrimitives = mConeIndices.getPointer(); + return mConeGeometry; +} + +LightShadowMap* AdvancedLightManager::findShadowMapForObject( SimObject *object ) +{ + if ( !object ) + { + MatTextureTarget::unregisterTarget( "AL_ShadowVizTexture", NULL ); + return NULL; + } + + ISceneLight *sceneLight = dynamic_cast( object ); + if ( !sceneLight || !sceneLight->getLight() ) + return NULL; + + return sceneLight->getLight()->getExtended()->getShadowMap(); +} + +ConsoleFunction( setShadowVizLight, const char*, 2, 2, "" ) +{ + MatTextureTarget::unregisterTarget( "AL_ShadowVizTexture", NULL ); + + AdvancedLightManager *lm = dynamic_cast( gClientSceneGraph->getLightManager() ); + if ( lm ) + { + SimObject *object; + Sim::findObject( argv[1], object ); + LightShadowMap *lightShadowMap = lm->findShadowMapForObject( object ); + if ( lightShadowMap && lightShadowMap->getTargetTexture(0) ) + { + if ( MatTextureTarget::registerTarget( "AL_ShadowVizTexture", lightShadowMap ) ) + { + GFXTextureObject *texObject = lightShadowMap->getTargetTexture(0); + const Point3I &size = texObject->getSize(); + F32 aspect = (F32)size.x / (F32)size.y; + + char *result = Con::getReturnBuffer( 64 ); + dSprintf( result, 64, "%d %d %g", size.x, size.y, aspect ); + return result; + } + } + } + + return 0; +} + diff --git a/lighting/advanced/advancedLightManager.h b/lighting/advanced/advancedLightManager.h new file mode 100644 index 0000000..8c0e2fd --- /dev/null +++ b/lighting/advanced/advancedLightManager.h @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ADVANCEDLIGHTMANAGER_H_ +#define _ADVANCEDLIGHTMANAGER_H_ + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "lighting/lightManager.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _LIGHTSHADOWMAP_H_ +#include "lighting/shadowMap/lightShadowMap.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + + +class AvailableSLInterfaces; +class AdvancedLightBinManager; +class RenderPrePassMgr; +class BaseMatInstance; +class MaterialParameters; +class MaterialParameterHandle; +class GFXShader; +class GFXShaderConstHandle; +class ShadowMapManager; + + +class AdvancedLightManager : public LightManager +{ + typedef LightManager Parent; + +public: + + /// Return the lightBinManager for this light manager. + AdvancedLightBinManager* getLightBinManager() { return mLightBinManager; } + + // LightManager + virtual bool isCompatible() const; + virtual void activate( SceneGraph *sceneManager ); + virtual void deactivate(); + virtual void registerGlobalLight(LightInfo *light, SimObject *obj); + virtual void unregisterAllLights(); + virtual void setLightInfo( ProcessedMaterial *pmat, + const Material *mat, + const SceneGraphData &sgData, + const SceneState *state, + U32 pass, + GFXShaderConstBuffer *shaderConsts ); + virtual bool setTextureStage( const SceneGraphData &sgData, + const U32 currTexFlag, + const U32 textureSlot, + GFXShaderConstBuffer *shaderConsts, + ShaderConstHandles *handles ); + + // Constant Specular Power Shader Constant + const static String ConstantSpecularPowerSC; + + typedef GFXVertexPC LightVertex; + + GFXVertexBufferHandle getSphereMesh(U32 &outNumPrimitives, GFXPrimitiveBuffer *&outPrimitives ); + GFXVertexBufferHandle getConeMesh(U32 &outNumPrimitives, GFXPrimitiveBuffer *&outPrimitives ); + + LightShadowMap* findShadowMapForObject( SimObject *object ); + +protected: + + // LightManager + virtual void _addLightInfoEx( LightInfo *lightInfo ); + virtual void _initLightFields(); + + /// A simple protected singleton. Use LightManager::findByName() + /// to access this light manager. + /// @see LightManager::findByName() + static AdvancedLightManager smSingleton; + + // These are protected because we're a singleton and + // no one else should be creating us! + AdvancedLightManager(); + virtual ~AdvancedLightManager(); + + SimObjectPtr mLightBinManager; + + SimObjectPtr mPrePassRenderBin; + + LightConstantMap mConstantLookup; + + GFXShaderRef mLastShader; + + LightingShaderConstants* mLastConstants; + + // Convex geometry for lights + GFXVertexBufferHandle mSphereGeometry; + + GFXPrimitiveBufferHandle mSphereIndices; + + U32 mSpherePrimitiveCount; + + GFXVertexBufferHandle mConeGeometry; + + GFXPrimitiveBufferHandle mConeIndices; + + U32 mConePrimitiveCount; + + LightingShaderConstants* getLightingShaderConstants(GFXShaderConstBuffer* shader); + +}; + +#endif // _ADVANCEDLIGHTMANAGER_H_ diff --git a/lighting/advanced/advancedLightingFeatures.cpp b/lighting/advanced/advancedLightingFeatures.cpp new file mode 100644 index 0000000..d244d30 --- /dev/null +++ b/lighting/advanced/advancedLightingFeatures.cpp @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/advancedLightingFeatures.h" + +#include "shaderGen/featureMgr.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "materials/materialParameters.h" +#include "materials/materialFeatureTypes.h" +#include "gfx/gfxDevice.h" +#include "core/util/safeDelete.h" + +#ifndef TORQUE_OS_MAC +# include "lighting/advanced/hlsl/gBufferConditionerHLSL.h" +# include "lighting/advanced/hlsl/advancedLightingFeaturesHLSL.h" +#else +# include "lighting/advanced/glsl/gBufferConditionerGLSL.h" +# include "lighting/advanced/glsl/advancedLightingFeaturesGLSL.h" +#endif + + + +bool AdvancedLightingFeatures::smFeaturesRegistered = false; + +void AdvancedLightingFeatures::registerFeatures( const GFXFormat &prepassTargetFormat, const GFXFormat &lightInfoTargetFormat ) +{ + AssertFatal( !smFeaturesRegistered, "AdvancedLightingFeatures::registerFeatures() - Features already registered. Bad!" ); + + // If we ever need this... + TORQUE_UNUSED(lightInfoTargetFormat); + + if(GFX->getAdapterType() == OpenGL) + { +#ifdef TORQUE_OS_MAC + FEATUREMGR->registerFeature(MFT_PrePassConditioner, new GBufferConditionerGLSL( prepassTargetFormat )); + FEATUREMGR->registerFeature(MFT_RTLighting, new DeferredRTLightingFeatGLSL()); + FEATUREMGR->registerFeature(MFT_NormalMap, new DeferredBumpFeatGLSL()); + FEATUREMGR->registerFeature(MFT_PixSpecular, new DeferredPixelSpecularGLSL()); + FEATUREMGR->registerFeature(MFT_MinnaertShading, new DeferredMinnaertGLSL()); + FEATUREMGR->registerFeature(MFT_SubSurface, new DeferredSubSurfaceGLSL()); +#endif + } + else + { +#ifndef TORQUE_OS_MAC + FEATUREMGR->registerFeature(MFT_PrePassConditioner, + new GBufferConditionerHLSL( prepassTargetFormat, GBufferConditionerHLSL::ViewSpace )); + FEATUREMGR->registerFeature(MFT_RTLighting, new DeferredRTLightingFeatHLSL()); + FEATUREMGR->registerFeature(MFT_NormalMap, new DeferredBumpFeatHLSL()); + FEATUREMGR->registerFeature(MFT_PixSpecular, new DeferredPixelSpecularHLSL()); + FEATUREMGR->registerFeature(MFT_MinnaertShading, new DeferredMinnaertHLSL()); + FEATUREMGR->registerFeature(MFT_SubSurface, new DeferredSubSurfaceHLSL()); +#endif + } + + smFeaturesRegistered = true; +} + +void AdvancedLightingFeatures::unregisterFeatures() +{ + FEATUREMGR->unregisterFeature(MFT_PrePassConditioner); + FEATUREMGR->unregisterFeature(MFT_RTLighting); + FEATUREMGR->unregisterFeature(MFT_NormalMap); + FEATUREMGR->unregisterFeature(MFT_PixSpecular); + FEATUREMGR->unregisterFeature(MFT_MinnaertShading); + FEATUREMGR->unregisterFeature(MFT_SubSurface); + + smFeaturesRegistered = false; +} diff --git a/lighting/advanced/advancedLightingFeatures.h b/lighting/advanced/advancedLightingFeatures.h new file mode 100644 index 0000000..8ae738a --- /dev/null +++ b/lighting/advanced/advancedLightingFeatures.h @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _ADVANCEDLIGHTINGFEATURES_H_ +#define _ADVANCEDLIGHTINGFEATURES_H_ + +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif + + +class AdvancedLightingFeatures +{ +public: + + static void registerFeatures( const GFXFormat &prepassTargetFormat, const GFXFormat &lightInfoTargetFormat ); + static void unregisterFeatures(); + +private: + + static bool smFeaturesRegistered; +}; + +#endif // _ADVANCEDLIGHTINGFEATURES_H_ diff --git a/lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp b/lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp new file mode 100644 index 0000000..75c10bf --- /dev/null +++ b/lighting/advanced/glsl/advancedLightingFeaturesGLSL.cpp @@ -0,0 +1,708 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/glsl/advancedLightingFeaturesGLSL.h" + +#include "lighting/advanced/advancedLightBinManager.h" +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/conditionerFeature.h" +#include "renderInstance/renderPrePassMgr.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" + + +void DeferredRTLightingFeatGLSL::processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ) +{ + /// TODO: This needs to be done via some sort of material + /// feature and not just allow all translucent elements to + /// read from the light prepass. + /* + if ( fd.features[MFT_IsTranslucent] ) + { + Parent::processPixMacros( macros, fd ); + return; + } + */ + + // Pull in the uncondition method for the light info buffer + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( AdvancedLightBinManager::smBufferName ); + if ( texTarget && texTarget->getTargetConditioner() ) + { + ConditionerMethodDependency *unconditionMethod = texTarget->getTargetConditioner()->getConditionerMethodDependency(ConditionerFeature::UnconditionMethod); + unconditionMethod->createMethodMacro( String::ToLower( AdvancedLightBinManager::smBufferName ) + "Uncondition", macros ); + addDependency(unconditionMethod); + } +} + +void DeferredRTLightingFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + /// TODO: This needs to be done via some sort of material + /// feature and not just allow all translucent elements to + /// read from the light prepass. + /* + if ( fd.features[MFT_IsTranslucent] ) + { + Parent::processVert( componentList, fd ); + return; + } + */ + + // Pass screen space position to pixel shader to compute a full screen buffer uv + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *ssPos = connectComp->getElement( RT_TEXCOORD ); + ssPos->setName( "screenspacePos" ); + ssPos->setType( "vec4" ); + +// Var *outPosition = (Var*) LangElement::find( "hpos" ); +// AssertFatal( outPosition, "No hpos, ohnoes." ); + + output = new GenOp( " @ = gl_Position;\r\n", ssPos ); +} + +void DeferredRTLightingFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + /// TODO: This needs to be done via some sort of material + /// feature and not just allow all translucent elements to + /// read from the light prepass. + /* + if ( fd.features[MFT_IsTranslucent] ) + { + Parent::processPix( componentList, fd ); + return; + } + */ + + MultiLine *meta = new MultiLine; + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *ssPos = connectComp->getElement( RT_TEXCOORD ); + ssPos->setName( "screenspacePos" ); + ssPos->setType( "vec4" ); + + Var *uvScene = new Var; + uvScene->setType( "vec2" ); + uvScene->setName( "uvScene" ); + LangElement *uvSceneDecl = new DecOp( uvScene ); + + Var *rtParams = (Var*) LangElement::find( "renderTargetParams" ); + if( !rtParams ) + { + rtParams = new Var; + rtParams->setType( "vec4" ); + rtParams->setName( "renderTargetParams" ); + rtParams->uniform = true; + rtParams->constSortPos = cspPass; + } + + meta->addStatement( new GenOp( " @ = @.xy / @.w;\r\n", uvSceneDecl, ssPos, ssPos ) ); // get the screen coord... its -1 to +1 + meta->addStatement( new GenOp( " @ = ( @ + 1.0 ) / 2.0;\r\n", uvScene, uvScene ) ); // get the screen coord to 0 to 1 + meta->addStatement( new GenOp( " @ = ( @ * @.zw ) + @.xy;\r\n", uvScene, uvScene, rtParams, rtParams) ); // scale it down and offset it to the rt size + + Var *lightInfoSamp = new Var; + lightInfoSamp->setType( "vec4" ); + lightInfoSamp->setName( "lightInfoSample" ); + + // create texture var + Var *lightInfoBuffer = new Var; + lightInfoBuffer->setType( "sampler2D" ); + lightInfoBuffer->setName( "lightInfoBuffer" ); + lightInfoBuffer->uniform = true; + lightInfoBuffer->sampler = true; + lightInfoBuffer->constNum = Var::getTexUnitNum(); // used as texture unit num here + + String unconditionLightInfo = String::ToLower( AdvancedLightBinManager::smBufferName ) + "Uncondition"; + + meta->addStatement( new GenOp( " vec3 d_lightcolor;\r\n" ) ); + meta->addStatement( new GenOp( " float d_NL_Att;\r\n" ) ); + meta->addStatement( new GenOp( " float d_specular;\r\n" ) ); + meta->addStatement( new GenOp( avar( " %s(texture2D(@, @), d_lightcolor, d_NL_Att, d_specular);\r\n", unconditionLightInfo.c_str() ), + lightInfoBuffer, uvScene ) ); + + Var *rtShading = new Var; + rtShading->setType( "vec4" ); + rtShading->setName( "rtShading" ); + LangElement *rtShadingDecl = new DecOp( rtShading ); + meta->addStatement( new GenOp( " @ = vec4( d_lightcolor, 1.0 );\r\n", rtShadingDecl ) ); + + // This is kind of weak sauce + if( !fd.features[MFT_SubSurface] && !fd.features[MFT_ToneMap] && !fd.features[MFT_LightMap] ) + meta->addStatement( new GenOp( " @;\r\n", assignColor( rtShading, Material::Mul ) ) ); + + output = meta; +} + +ShaderFeature::Resources DeferredRTLightingFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + /// TODO: This needs to be done via some sort of material + /// feature and not just allow all translucent elements to + /// read from the light prepass. + /* + if( fd.features[MFT_IsTranslucent] ) + return Parent::getResources( fd ); + */ + + Resources res; + res.numTex = 1; + res.numTexReg = 1; + return res; +} + +void DeferredRTLightingFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + /// TODO: This needs to be done via some sort of material + /// feature and not just allow all translucent elements to + /// read from the light prepass. + /* + if( fd.features[MFT_IsTranslucent] ) + { + Parent::setTexData( stageDat, fd, passData, texIndex ); + return; + } + */ + + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( AdvancedLightBinManager::smBufferName ); + if( texTarget ) + { + passData.mTexType[ texIndex ] = Material::TexTarget; + passData.mTexSlot[ texIndex++ ].texTarget = texTarget; + } +} + + +void DeferredBumpFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_PrePassConditioner] ) + { + // There is an output conditioner active, so we need to supply a transform + // to the pixel shader. + MultiLine *meta = new MultiLine; + + // setup texture space matrix + Var *texSpaceMat = (Var*) LangElement::find( "objToTangentSpace" ); + if( !texSpaceMat ) + { + LangElement * texSpaceSetup = setupTexSpaceMat( componentList, &texSpaceMat ); + meta->addStatement( texSpaceSetup ); + texSpaceMat = (Var*) LangElement::find( "objToTangentSpace" ); + } + + // turn obj->tangent into world->tangent + Var *worldToTangent = new Var; + worldToTangent->setType( "mat3" ); + worldToTangent->setName( "worldToTangent" ); + LangElement *worldToTangentDecl = new DecOp( worldToTangent ); + + // Get the world->obj transform + Var *worldToObj = new Var; + worldToObj->setType( "mat4" ); + worldToObj->setName( "worldToObj" ); + worldToObj->uniform = true; + worldToObj->constSortPos = cspPrimitive; + + Var *mat3Conversion = new Var; + mat3Conversion->setType( "mat3" ); + mat3Conversion->setName( "worldToObjMat3" ); + LangElement* mat3Lang = new DecOp(mat3Conversion); + meta->addStatement( new GenOp( " @ = mat3(@[0].xyz, @[1].xyz, @[2].xyz);\r\n ", mat3Lang, worldToObj, worldToObj, worldToObj) ); + + // assign world->tangent transform + meta->addStatement( new GenOp( " @ = @ * @;\r\n", worldToTangentDecl, texSpaceMat, mat3Conversion ) ); + + // send transform to pixel shader + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *worldToTangentR1 = connectComp->getElement( RT_TEXCOORD ); + worldToTangentR1->setName( "worldToTangentR1" ); + worldToTangentR1->setType( "vec3" ); + meta->addStatement( new GenOp( " @ = @[0];\r\n", worldToTangentR1, worldToTangent ) ); + + Var *worldToTangentR2 = connectComp->getElement( RT_TEXCOORD ); + worldToTangentR2->setName( "worldToTangentR2" ); + worldToTangentR2->setType( "vec3" ); + meta->addStatement( new GenOp( " @ = @[1];\r\n", worldToTangentR2, worldToTangent ) ); + + Var *worldToTangentR3 = connectComp->getElement( RT_TEXCOORD ); + worldToTangentR3->setName( "worldToTangentR3" ); + worldToTangentR3->setType( "vec3" ); + meta->addStatement( new GenOp( " @ = @[2];\r\n", worldToTangentR3, worldToTangent ) ); + + // Make sure there are texcoords + if( !fd.features[MFT_DiffuseMap] ) + { + // find incoming texture var + Var *inTex = getVertTexCoord( "texCoord" ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "outTexCoord" ); + outTex->setType( "vec2" ); + outTex->mapsToSampler = true; + + if( fd.features[MFT_TexAnim] ) + { + inTex->setType( "vec4" ); + + // create texture mat var + Var *texMat = new Var; + texMat->setType( "mat4" ); + texMat->setName( "texMat" ); + texMat->uniform = true; + texMat->constSortPos = cspPotentialPrimitive; + + meta->addStatement( new GenOp( " @ = @ * @;\r\n", outTex, texMat, inTex ) ); + } + else + { + // setup language elements to output incoming tex coords to output + meta->addStatement( new GenOp( " @ = @;\r\n", outTex, inTex ) ); + } + } + + output = meta; + } + else if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + !fd.features[MFT_RTLighting] ) + { + Parent::processVert( componentList, fd ); + return; + } + else + { + output = NULL; + } +} + +void DeferredBumpFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // NULL output in case nothing gets handled + output = NULL; + + if( fd.features[MFT_PrePassConditioner] ) + { + MultiLine *meta = new MultiLine; + + // Pull the world->tangent transform from the vertex shader + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *worldToTangentR1 = connectComp->getElement( RT_TEXCOORD ); + worldToTangentR1->setName( "worldToTangentR1" ); + worldToTangentR1->setType( "vec3" ); + + Var *worldToTangentR2 = connectComp->getElement( RT_TEXCOORD ); + worldToTangentR2->setName( "worldToTangentR2" ); + worldToTangentR2->setType( "vec3" ); + + Var *worldToTangentR3 = connectComp->getElement( RT_TEXCOORD ); + worldToTangentR3->setName( "worldToTangentR3" ); + worldToTangentR3->setType( "vec3" ); + + Var *worldToTangent = new Var; + worldToTangent->setType( "mat3" ); + worldToTangent->setName( "worldToTangent" ); + LangElement *worldToTangentDecl = new DecOp( worldToTangent ); + + // Build world->tangent matrix + meta->addStatement( new GenOp( " @;\r\n", worldToTangentDecl ) ); + meta->addStatement( new GenOp( " @[0] = @;\r\n", worldToTangent, worldToTangentR1 ) ); + meta->addStatement( new GenOp( " @[1] = @;\r\n", worldToTangent, worldToTangentR2 ) ); + meta->addStatement( new GenOp( " @[2] = @;\r\n", worldToTangent, worldToTangentR3 ) ); + + // create texture var + Var *bumpMap = new Var; + bumpMap->setType( "sampler2D" ); + bumpMap->setName( "bumpMap" ); + bumpMap->uniform = true; + bumpMap->sampler = true; + bumpMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + Var *texCoord = (Var*) LangElement::find( "outTexCoord" ); + if( !texCoord ) + { + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + texCoord = connectComp->getElement( RT_TEXCOORD ); + texCoord->setName( "outTexCoord" ); + texCoord->setType( "vec2" ); + texCoord->mapsToSampler = true; + } + + LangElement * texOp = new GenOp( "texture2D(@, @)", bumpMap, texCoord ); + + // create bump normal + Var *bumpNorm = new Var; + bumpNorm->setName( "bumpNormal" ); + bumpNorm->setType( "vec4" ); + + LangElement *bumpNormDecl = new DecOp( bumpNorm ); + meta->addStatement( expandNormalMap( texOp, bumpNormDecl, bumpNorm, fd ) ); + + // This var is read from GBufferConditionerHLSL and + // used in the prepass output. + Var *gbNormal = new Var; + gbNormal->setName( "gbNormal" ); + gbNormal->setType( "vec3" ); + LangElement *gbNormalDecl = new DecOp( gbNormal ); + + // Normalize is done later... + // Note: The reverse mul order is intentional. Affine matrix. + meta->addStatement( new GenOp( " @ = @.xyz * @;\r\n", gbNormalDecl, bumpNorm, worldToTangent ) ); + + output = meta; + return; + } + else if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + !fd.features[MFT_RTLighting] ) + { + Parent::processPix( componentList, fd ); + return; + } + else if ( fd.features[MFT_PixSpecular] ) + { + Var *bumpSample = (Var *)LangElement::find( "bumpSample" ); + if( bumpSample == NULL ) + { + Var *texCoord = (Var*) LangElement::find( "outTexCoord" ); + if( !texCoord ) + { + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + texCoord = connectComp->getElement( RT_TEXCOORD ); + texCoord->setName( "outTexCoord" ); + texCoord->setType( "vec2" ); + texCoord->mapsToSampler = true; + } + + Var *bumpMap = new Var; + bumpMap->setType( "sampler2D" ); + bumpMap->setName( "bumpMap" ); + bumpMap->uniform = true; + bumpMap->sampler = true; + bumpMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + bumpSample = new Var; + bumpSample->setType( "vec4" ); + bumpSample->setName( "bumpSample" ); + LangElement *bumpSampleDecl = new DecOp( bumpSample ); + + output = new GenOp( " @ = texture2D(@, @);\r\n", bumpSampleDecl, bumpMap, texCoord ); + return; + } + } + + output = NULL; +} + +ShaderFeature::Resources DeferredBumpFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + fd.features[MFT_Parallax] || + !fd.features[MFT_RTLighting] ) + return Parent::getResources( fd ); + + Resources res; + if(!fd.features[MFT_SpecularMap]) + { + res.numTex = 1; + res.numTexReg = 1; + } + return res; +} + +void DeferredBumpFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + !fd.features[MFT_RTLighting] ) + { + Parent::setTexData( stageDat, fd, passData, texIndex ); + return; + } + + GFXTextureObject *normalMap = stageDat.getTex( MFT_NormalMap ); + if ( !fd.features[MFT_Parallax] && !fd.features[MFT_SpecularMap] && + ( fd.features[MFT_PrePassConditioner] || + fd.features[MFT_PixSpecular] ) && + normalMap ) + { + passData.mTexType[ texIndex ] = Material::Bump; + passData.mTexSlot[ texIndex++ ].texObject = normalMap; + } +} + + +void DeferredPixelSpecularGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + Parent::processVert( componentList, fd ); + return; + } + output = NULL; +} + +void DeferredPixelSpecularGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + Parent::processPix( componentList, fd ); + return; + } + + MultiLine *meta = new MultiLine; + + Var *specular = new Var; + specular->setType( "float" ); + specular->setName( "specular" ); + LangElement * specDecl = new DecOp( specular ); + + Var *specCol = (Var*)LangElement::find( "specularColor" ); + if(specCol == NULL) + { + specCol = new Var; + specCol->setType( "vec4" ); + specCol->setName( "specularColor" ); + specCol->uniform = true; + specCol->constSortPos = cspPotentialPrimitive; + } + + Var *specPow = new Var; + specPow->setType( "float" ); + specPow->setName( "specularPower" ); + + // If the gloss map flag is set, than the specular power is in the alpha + // channel of the specular map + if( fd.features[ MFT_GlossMap ] ) + meta->addStatement( new GenOp( " @ = @.a * 255;\r\n", new DecOp( specPow ), specCol ) ); + else + { + specPow->uniform = true; + specPow->constSortPos = cspPotentialPrimitive; + } + + Var *constSpecPow = new Var; + constSpecPow->setType( "float" ); + constSpecPow->setName( "constantSpecularPower" ); + constSpecPow->uniform = true; + constSpecPow->constSortPos = cspPass; + + Var *lightInfoSamp = (Var *)LangElement::find( "lightInfoSample" ); + AssertFatal( lightInfoSamp, "Something hosed the deferred features! Can't find lightInfoSample" ); + + // (a^m)^n = a^(m*n) + meta->addStatement( new GenOp( " @ = pow(d_specular, ceil(@ / @)) * d_NL_Att;\r\n", specDecl, specPow, constSpecPow ) ); + + LangElement *specMul = new GenOp( "@ * @", specCol, specular ); + LangElement *final = specMul; + + // We we have a normal map then mask the specular + if( !fd.features[MFT_SpecularMap] && fd.features[MFT_NormalMap] ) + { + Var *bumpSample = (Var*)LangElement::find( "bumpSample" ); + final = new GenOp( "@ * @.a", final, bumpSample ); + } + + // add to color + meta->addStatement( new GenOp( " @;\r\n", assignColor( final, Material::Add ) ) ); + + output = meta; +} + +ShaderFeature::Resources DeferredPixelSpecularGLSL::getResources( const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + return Parent::getResources( fd ); + + Resources res; + return res; +} + + +ShaderFeature::Resources DeferredMinnaertGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + if( !fd.features[MFT_IsTranslucent] && fd.features[MFT_RTLighting] ) + { + res.numTex = 1; + res.numTexReg = 1; + } + return res; +} + +void DeferredMinnaertGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + if( !fd.features[MFT_IsTranslucent] && fd.features[MFT_RTLighting] ) + { + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(RenderPrePassMgr::BufferName); + if ( texTarget ) + { + passData.mTexType[ texIndex ] = Material::TexTarget; + passData.mTexSlot[ texIndex++ ].texTarget = texTarget; + } + } +} + +void DeferredMinnaertGLSL::processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ) +{ + if( !fd.features[MFT_IsTranslucent] && fd.features[MFT_RTLighting] ) + { + // Pull in the uncondition method for the g buffer + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( RenderPrePassMgr::BufferName ); + if ( texTarget && texTarget->getTargetConditioner() ) + { + ConditionerMethodDependency *unconditionMethod = texTarget->getTargetConditioner()->getConditionerMethodDependency(ConditionerFeature::UnconditionMethod); + unconditionMethod->createMethodMacro( String::ToLower(RenderPrePassMgr::BufferName) + "Uncondition", macros ); + addDependency(unconditionMethod); + } + } +} + +void DeferredMinnaertGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If there is no deferred information, bail on this feature + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + output = NULL; + return; + } + + // grab incoming vert position + Var *inVertPos = (Var*) LangElement::find( "position" ); + AssertFatal( inVertPos, "Something went bad with ShaderGen. The vertex position should be already defined." ); + + // grab output for gbuffer normal + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outWSEyeVec= connectComp->getElement( RT_TEXCOORD ); + outWSEyeVec->setName( "outWSViewVec" ); + outWSEyeVec->setType( "vec4" ); + + // create objToWorld variable + Var *objToWorld = (Var*) LangElement::find( "objTrans" ); + if( !objToWorld ) + { + objToWorld = new Var; + objToWorld->setType( "mat4x4" ); + objToWorld->setName( "objTrans" ); + objToWorld->uniform = true; + objToWorld->constSortPos = cspPrimitive; + } + + // Eye Pos world + Var *eyePosWorld = (Var*) LangElement::find( "eyePosWorld" ); + if( !eyePosWorld ) + { + eyePosWorld = new Var; + eyePosWorld->setType( "vec3" ); + eyePosWorld->setName( "eyePosWorld" ); + eyePosWorld->uniform = true; + eyePosWorld->constSortPos = cspPass; + } + + // Kick out the world-space normal + LangElement *statement = new GenOp( " @ = vec4(@, @) - vec4(@, 0.0);\r\n", + outWSEyeVec, objToWorld, inVertPos, eyePosWorld ); + output = statement; +} + +void DeferredMinnaertGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If there is no deferred information, bail on this feature + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + output = NULL; + return; + } + + Var *minnaertConstant = new Var; + minnaertConstant->setType( "float" ); + minnaertConstant->setName( "minnaertConstant" ); + minnaertConstant->uniform = true; + minnaertConstant->constSortPos = cspPotentialPrimitive; + + // create texture var + Var *prepassBuffer = new Var; + prepassBuffer->setType( "sampler2D" ); + prepassBuffer->setName( "prepassBuffer" ); + prepassBuffer->uniform = true; + prepassBuffer->sampler = true; + prepassBuffer->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // Texture coord + Var *uvScene = (Var*) LangElement::find( "uvScene" ); + AssertFatal(uvScene != NULL, "Unable to find UVScene, no RTLighting feature?"); + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *wsViewVec = (Var*) LangElement::find( "wsPos" ); + if( !wsViewVec ) + { + wsViewVec = connectComp->getElement( RT_TEXCOORD ); + wsViewVec->setName( "outWSViewVec" ); + wsViewVec->setType( "vec4" ); + wsViewVec->mapsToSampler = false; + wsViewVec->uniform = false; + } + + String unconditionPrePassMethod = String::ToLower(RenderPrePassMgr::BufferName) + "Uncondition"; + + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( avar( " vec4 normalDepth = %s(texture2D(@, @));\r\n", unconditionPrePassMethod.c_str() ), prepassBuffer, uvScene ) ); + meta->addStatement( new GenOp( " vec3 worldViewVec = normalize(@.xyz / @.w);\r\n", wsViewVec, wsViewVec ) ); + meta->addStatement( new GenOp( " float vDotN = dot(normalDepth.xyz, worldViewVec);\r\n" ) ); + meta->addStatement( new GenOp( " float Minnaert = pow(d_NL_Att, @) * pow(vDotN, 1.0 - @);\r\n", minnaertConstant, minnaertConstant ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "vec4(Minnaert, Minnaert, Minnaert, 1.0)" ), Material::Mul ) ) ); + + output = meta; +} + + +void DeferredSubSurfaceGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If there is no deferred information, bail on this feature + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + output = NULL; + return; + } + + Var *subSurfaceParams = new Var; + subSurfaceParams->setType( "vec4" ); + subSurfaceParams->setName( "subSurfaceParams" ); + subSurfaceParams->uniform = true; + subSurfaceParams->constSortPos = cspPotentialPrimitive; + + Var *inColor = (Var*) LangElement::find( "rtShading" ); + + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( " float subLamb = smoothstep(-@.a, 1.0, d_NL_Att) - smoothstep(0.0, 1.0, d_NL_Att);\r\n", subSurfaceParams ) ); + meta->addStatement( new GenOp( " subLamb = max(0.0, subLamb);\r\n" ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "vec4(@.rgb + (subLamb * @.rgb), 1.0)", inColor, subSurfaceParams ), Material::Mul ) ) ); + + output = meta; +} diff --git a/lighting/advanced/glsl/advancedLightingFeaturesGLSL.h b/lighting/advanced/glsl/advancedLightingFeaturesGLSL.h new file mode 100644 index 0000000..905bbf3 --- /dev/null +++ b/lighting/advanced/glsl/advancedLightingFeaturesGLSL.h @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DEFERREDFEATURESGLSL_H_ +#define _DEFERREDFEATURESGLSL_H_ + +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#include "shaderGen/GLSL/bumpGLSL.h" +#include "shaderGen/GLSL/pixSpecularGLSL.h" + +class ConditionerMethodDependency; + + +/// Lights the pixel by sampling from the light prepass buffer. It will +/// fall back to default vertex lighting functionality if +class DeferredRTLightingFeatGLSL : public RTLightingFeatGLSL +{ + typedef RTLightingFeatGLSL Parent; + +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Deferred RT Lighting Feature"; + } +}; + + +/// Used to write the normals during the depth/normal prepass. +class DeferredBumpFeatGLSL : public BumpFeatGLSL +{ + typedef BumpFeatGLSL Parent; + +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Bumpmap [Deferred]"; + } +}; + + +/// Generates specular highlights in the forward pass +/// from the light prepass buffer. +class DeferredPixelSpecularGLSL : public PixelSpecularGLSL +{ + typedef PixelSpecularGLSL Parent; + +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Pixel Specular [Deferred]"; + } +}; + + +/// +class DeferredMinnaertGLSL : public ShaderFeatureGLSL +{ + typedef ShaderFeatureGLSL Parent; + +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Minnaert Shading [Deferred]"; + } +}; + + +/// +class DeferredSubSurfaceGLSL : public ShaderFeatureGLSL +{ + typedef ShaderFeatureGLSL Parent; + +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Sub-Surface Approximation [Deferred]"; + } +}; + +#endif // _DEFERREDFEATURESGLSL_H_ \ No newline at end of file diff --git a/lighting/advanced/glsl/gBufferConditionerGLSL.cpp b/lighting/advanced/glsl/gBufferConditionerGLSL.cpp new file mode 100644 index 0000000..6de8a2b --- /dev/null +++ b/lighting/advanced/glsl/gBufferConditionerGLSL.cpp @@ -0,0 +1,302 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/glsl/gBufferConditionerGLSL.h" + +#include "shaderGen/featureMgr.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "materials/materialFeatureTypes.h" + + +GBufferConditionerGLSL::GBufferConditionerGLSL( const GFXFormat bufferFormat ) : + Parent( bufferFormat ) +{ + // Figure out how we should store the normal data. These are the defaults. + mCanWriteNegativeValues = false; + mNormalStorageType = CartesianXYZ; + + // Note: We clear to a depth 1 (the w component) so + // that the unrendered parts of the scene end up + // farthest to the camera. + + switch(bufferFormat) + { + case GFXFormatR8G8B8A8: + // TODO: Some kind of logic here. Spherical is better, but is more + // expensive. + mNormalStorageType = Spherical; + mBitsPerChannel = 8; + break; + + case GFXFormatR16G16B16A16F: + // Floating point buffers don't need to encode negative values + mCanWriteNegativeValues = true; + mNormalStorageType = Spherical; + mBitsPerChannel = 16; + break; + + // Store a 32bit depth with a sperical normal in the + // integer 16 format. This gives us perfect depth + // precision and high quality normals within a 64bit + // buffer format. + case GFXFormatR16G16B16A16: + mNormalStorageType = Spherical; + mBitsPerChannel = 16; + break; + + case GFXFormatR32G32B32A32F: + mCanWriteNegativeValues = true; + mNormalStorageType = CartesianXYZ; + mBitsPerChannel = 32; + break; + + default: + AssertFatal(false, "Unsupported G-Buffer format"); + } +} + +GBufferConditionerGLSL::~GBufferConditionerGLSL() +{ +} + +void GBufferConditionerGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + output = NULL; + + if( !fd.features[MFT_NormalMap] ) + { + // grab incoming vert normal + Var *inNormal = (Var*) LangElement::find( "normal" ); + AssertFatal( inNormal, "Something went bad with ShaderGen. The normal should be already defined." ); + + // grab output for gbuffer normal + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outNormal = connectComp->getElement( RT_TEXCOORD ); + outNormal->setName( "gbNormal" ); + outNormal->setType( "vec3" ); + + // create objToWorld variable + Var *objToWorld = (Var*) LangElement::find( "objTrans" ); + if( !objToWorld ) + { + objToWorld = new Var; + objToWorld->setType( "mat4" ); + objToWorld->setName( "objTrans" ); + objToWorld->uniform = true; + objToWorld->constSortPos = cspPrimitive; + } + + // Kick out the world-space normal + LangElement *statement = new GenOp( " @ = vec3(@ * vec4(normalize(@), 0.0));\r\n", outNormal, objToWorld, inNormal ); + output = statement; + } +} + +void GBufferConditionerGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // sanity + AssertFatal( fd.features[MFT_EyeSpaceDepthOut], "No depth-out feature enabled! Bad news!" ); + + MultiLine *meta = new MultiLine; + + // grab connector normal + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *gbNormal = (Var*) LangElement::find( "gbNormal" ); + if( !gbNormal ) + { + gbNormal = connectComp->getElement( RT_TEXCOORD ); + gbNormal->setName( "gbNormal" ); + gbNormal->setType( "vec3" ); + gbNormal->mapsToSampler = false; + gbNormal->uniform = false; + } + + // find depth + ShaderFeature *depthFeat = FEATUREMGR->getByType( MFT_EyeSpaceDepthOut ); + AssertFatal( depthFeat != NULL, "No eye space depth feature found!" ); + + Var *depth = (Var*) LangElement::find(depthFeat->getOutputVarName()); + AssertFatal( depth, "Something went bad with ShaderGen. The depth should be already generated by the EyeSpaceDepthOut feature." ); + + + Var *unconditionedOut = new Var; + unconditionedOut->setType("vec4"); + unconditionedOut->setName("normal_depth"); + + LangElement *outputDecl = new DecOp( unconditionedOut ); + + // NOTE: We renormalize the normal here as they + // will not stay normalized during interpolation. + meta->addStatement( new GenOp(" @ = @;", outputDecl, new GenOp( "vec4(normalize(@), @)", gbNormal, depth ) ) ); + meta->addStatement( assignOutput( unconditionedOut ) ); + + output = meta; +} + +ShaderFeature::Resources GBufferConditionerGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // Passing from VS->PS: + // - world space normal (gbNormal) + res.numTexReg = 1; + + return res; +} + +Var* GBufferConditionerGLSL::printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ) +{ + const bool isCondition = ( methodType == ConditionerFeature::ConditionMethod ); + + Var *retVal = NULL; + + // The uncondition method inputs are changed + if( isCondition ) + retVal = Parent::printMethodHeader( methodType, methodName, stream, meta ); + else + { + Var *methodVar = new Var; + methodVar->setName(methodName); + methodVar->setType("vec4"); + DecOp *methodDecl = new DecOp(methodVar); + + Var *prepassSampler = new Var; + prepassSampler->setName("prepassSamplerVar"); + prepassSampler->setType("sampler2D"); + DecOp *prepassSamplerDecl = new DecOp(prepassSampler); + + Var *screenUV = new Var; + screenUV->setName("screenUVVar"); + screenUV->setType("vec2"); + DecOp *screenUVDecl = new DecOp(screenUV); + + Var *bufferSample = new Var; + bufferSample->setName("bufferSample"); + bufferSample->setType("vec4"); + DecOp *bufferSampleDecl = new DecOp(bufferSample); + + meta->addStatement( new GenOp( "@(@, @)\r\n", methodDecl, prepassSamplerDecl, screenUVDecl ) ); + + meta->addStatement( new GenOp( "{\r\n" ) ); + + meta->addStatement( new GenOp( " // Sampler g-buffer\r\n" ) ); + + // The gbuffer has no mipmaps, so use tex2dlod when + // so that the shader compiler can optimize. + meta->addStatement( new GenOp( " @ = texture2DLod(@, @, 0.0);\r\n", bufferSampleDecl, prepassSampler, screenUV ) ); + + // We don't use this way of passing var's around, so this should cause a crash + // if something uses this improperly + retVal = bufferSample; + } + + return retVal; +} + +GenOp* GBufferConditionerGLSL::_posnegEncode( GenOp *val ) +{ + return mCanWriteNegativeValues ? val : new GenOp("0.5 * (@ + 1.0)", val); +} + +GenOp* GBufferConditionerGLSL::_posnegDecode( GenOp *val ) +{ + return mCanWriteNegativeValues ? val : new GenOp("@ * 2.0 - 1.0", val); +} + +Var* GBufferConditionerGLSL::_conditionOutput( Var *unconditionedOutput, MultiLine *meta ) +{ + Var *retVar = new Var; + retVar->setType("vec4"); + retVar->setName("_gbConditionedOutput"); + LangElement *outputDecl = new DecOp( retVar ); + + switch(mNormalStorageType) + { + case CartesianXYZ: + meta->addStatement( new GenOp( " // g-buffer conditioner: vec4(normal.xyz, depth)\r\n" ) ); + meta->addStatement( new GenOp( " @ = vec4(@, @.a);\r\n", outputDecl, + _posnegEncode(new GenOp("@.xyz", unconditionedOutput)), unconditionedOutput ) ); + break; + + case CartesianXY: + meta->addStatement( new GenOp( " // g-buffer conditioner: vec4(normal.xy, depth Hi + z-sign, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " @ = vec4(@, @.a);", outputDecl, + _posnegEncode(new GenOp("vec3(@.xy, sign(@.z))", unconditionedOutput, unconditionedOutput)), unconditionedOutput ) ); + break; + + case Spherical: + meta->addStatement( new GenOp( " // g-buffer conditioner: vec4(normal.theta, normal.phi, depth Hi, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " @ = vec4(@, 0.0, @.a);\r\n", outputDecl, + _posnegEncode(new GenOp("vec2(atan2(@.y, @.x) / 3.14159265358979323846f, @.z)", unconditionedOutput, unconditionedOutput, unconditionedOutput ) ), + unconditionedOutput ) ); + break; + } + + // Encode depth into two channels + if(mNormalStorageType != CartesianXYZ) + { + const U64 maxValPerChannel = 1 << mBitsPerChannel; + const U64 extraVal = (maxValPerChannel * maxValPerChannel - 1) - (maxValPerChannel - 1) * 2; + meta->addStatement( new GenOp( " \r\n // Encode depth into hi/lo\r\n" ) ); + meta->addStatement( new GenOp( avar( " vec3 _tempDepth = fract(@.a * vec3(1.0, %llu.0, %llu.0));\r\n", maxValPerChannel - 1, extraVal ), + unconditionedOutput ) ); + meta->addStatement( new GenOp( avar( " @.zw = _tempDepth.xy - _tempDepth.yz * vec2(1.0/%llu.0, 1.0/%llu.0);\r\n\r\n", maxValPerChannel - 1, maxValPerChannel - 1 ), + retVar ) ); + } + + AssertFatal( retVar != NULL, avar( "Cannot condition output to buffer format: %s", GFXStringTextureFormat[getBufferFormat()] ) ); + return retVar; +} + +Var* GBufferConditionerGLSL::_unconditionInput( Var *conditionedInput, MultiLine *meta ) +{ + Var *retVar = new Var; + retVar->setType("vec4"); + retVar->setName("_gbUnconditionedInput"); + LangElement *outputDecl = new DecOp( retVar ); + + switch(mNormalStorageType) + { + case CartesianXYZ: + meta->addStatement( new GenOp( " // g-buffer unconditioner: vec4(normal.xyz, depth)\r\n" ) ); + meta->addStatement( new GenOp( " @ = vec4(@, @.a);\r\n", outputDecl, + _posnegDecode(new GenOp("@.xyz", conditionedInput)), conditionedInput ) ); + break; + + case CartesianXY: + meta->addStatement( new GenOp( " // g-buffer unconditioner: vec4(normal.xy, depth Hi + z-sign, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " @ = vec4(@, @.a);\r\n", outputDecl, + _posnegDecode(new GenOp("@.xyz", conditionedInput)), conditionedInput ) ); + meta->addStatement( new GenOp( " @.z *= sqrt(1.0 - dot(@.xy, @.xy));\r\n", retVar, retVar, retVar ) ); + break; + + case Spherical: + meta->addStatement( new GenOp( " // g-buffer unconditioner: vec4(normal.theta, normal.phi, depth Hi, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " vec2 spGPUAngles = @;\r\n", _posnegDecode(new GenOp("@.xy", conditionedInput)) ) ); + meta->addStatement( new GenOp( " vec2 sincosTheta;\r\n" ) ); + meta->addStatement( new GenOp( " sincosTheta.x = sin(spGPUAngles.x * 3.14159265358979323846);\r\n" ) ); + meta->addStatement( new GenOp( " sincosTheta.y = cos(spGPUAngles.x * 3.14159265358979323846);\r\n" ) ); + meta->addStatement( new GenOp( " vec2 sincosPhi = vec2(sqrt(1.0 - spGPUAngles.y * spGPUAngles.y), spGPUAngles.y);\r\n" ) ); + meta->addStatement( new GenOp( " @ = vec4(sincosTheta.y * sincosPhi.x, sincosTheta.x * sincosPhi.x, sincosPhi.y, @.a);\r\n", outputDecl, conditionedInput ) ); + break; + } + + // Recover depth from encoding + if(mNormalStorageType != CartesianXYZ) + { + const U64 maxValPerChannel = 1 << mBitsPerChannel; + meta->addStatement( new GenOp( " \r\n // Decode depth\r\n" ) ); + meta->addStatement( new GenOp( avar( " @.w = dot( @.zw, vec2(1.0, 1.0/%llu.0));\r\n", maxValPerChannel - 1 ), + retVar, conditionedInput ) ); + } + + + AssertFatal( retVar != NULL, avar( "Cannot uncondition input from buffer format: %s", GFXStringTextureFormat[getBufferFormat()] ) ); + return retVar; +} + diff --git a/lighting/advanced/glsl/gBufferConditionerGLSL.h b/lighting/advanced/glsl/gBufferConditionerGLSL.h new file mode 100644 index 0000000..4d3ba27 --- /dev/null +++ b/lighting/advanced/glsl/gBufferConditionerGLSL.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GBUFFER_CONDITIONER_GLSL_H_ +#define _GBUFFER_CONDITIONER_GLSL_H_ + +#ifndef _CONDITIONER_BASE_H_ +#include "shaderGen/conditionerFeature.h" +#endif +#ifndef _SHADEROP_H_ +#include "shaderGen/shaderOp.h" +#endif + + +/// +class GBufferConditionerGLSL : public ConditionerFeature +{ + typedef ConditionerFeature Parent; + +public: + enum NormalStorage + { + CartesianXYZ, + CartesianXY, + Spherical, + }; + +protected: + + NormalStorage mNormalStorageType; + bool mCanWriteNegativeValues; + U32 mBitsPerChannel; + +public: + + GBufferConditionerGLSL( const GFXFormat bufferFormat ); + virtual ~GBufferConditionerGLSL(); + + + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "GBuffer Conditioner"; } + +protected: + + virtual Var *printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ); + + virtual GenOp* _posnegEncode( GenOp *val ); + virtual GenOp* _posnegDecode( GenOp *val ); + virtual Var* _conditionOutput( Var *unconditionedOutput, MultiLine *meta ); + virtual Var* _unconditionInput( Var *conditionedInput, MultiLine *meta ); +}; + +#endif // _GBUFFER_CONDITIONER_GLSL_H_ diff --git a/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp b/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp new file mode 100644 index 0000000..656a134 --- /dev/null +++ b/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.cpp @@ -0,0 +1,557 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/hlsl/advancedLightingFeaturesHLSL.h" + +#include "lighting/advanced/advancedLightBinManager.h" +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/conditionerFeature.h" +#include "renderInstance/renderPrePassMgr.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" + + +void DeferredRTLightingFeatHLSL::processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ) +{ + // Anything that is translucent should use the + // forward rendering lighting feature. + if ( fd.features[MFT_IsTranslucent] ) + { + Parent::processPixMacros( macros, fd ); + return; + } + + // Pull in the uncondition method for the light info buffer + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( AdvancedLightBinManager::smBufferName ); + if ( texTarget && texTarget->getTargetConditioner() ) + { + ConditionerMethodDependency *unconditionMethod = texTarget->getTargetConditioner()->getConditionerMethodDependency(ConditionerFeature::UnconditionMethod); + unconditionMethod->createMethodMacro( String::ToLower( AdvancedLightBinManager::smBufferName ) + "Uncondition", macros ); + addDependency(unconditionMethod); + } +} + +void DeferredRTLightingFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Anything that is translucent should use the + // forward rendering lighting feature. + if ( fd.features[MFT_IsTranslucent] ) + { + Parent::processVert( componentList, fd ); + return; + } + + // Pass screen space position to pixel shader to compute a full screen buffer uv + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *ssPos = connectComp->getElement( RT_TEXCOORD ); + ssPos->setName( "screenspacePos" ); + ssPos->setStructName( "OUT" ); + ssPos->setType( "float4" ); + + Var *outPosition = (Var*) LangElement::find( "hpos" ); + AssertFatal( outPosition, "No hpos, ohnoes." ); + + output = new GenOp( " @ = @;\r\n", ssPos, outPosition ); +} + +void DeferredRTLightingFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Anything that is translucent should use the + // forward rendering lighting feature. + if ( fd.features[MFT_IsTranslucent] ) + { + Parent::processPix( componentList, fd ); + return; + } + + MultiLine *meta = new MultiLine; + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *ssPos = connectComp->getElement( RT_TEXCOORD ); + ssPos->setName( "screenspacePos" ); + ssPos->setStructName( "IN" ); + ssPos->setType( "float4" ); + + Var *uvScene = new Var; + uvScene->setType( "float2" ); + uvScene->setName( "uvScene" ); + LangElement *uvSceneDecl = new DecOp( uvScene ); + + String rtParamName = String::ToString( "rtParams%d", mLastTexIndex ); + Var *rtParams = (Var*) LangElement::find( rtParamName ); + if( !rtParams ) + { + rtParams = new Var; + rtParams->setType( "float4" ); + rtParams->setName( rtParamName ); + rtParams->uniform = true; + rtParams->constSortPos = cspPass; + } + + meta->addStatement( new GenOp( " @ = @.xy / @.w;\r\n", uvSceneDecl, ssPos, ssPos ) ); // get the screen coord... its -1 to +1 + meta->addStatement( new GenOp( " @ = ( @ + 1.0 ) / 2.0;\r\n", uvScene, uvScene ) ); // get the screen coord to 0 to 1 + meta->addStatement( new GenOp( " @.y = 1.0 - @.y;\r\n", uvScene, uvScene ) ); // flip the y axis + meta->addStatement( new GenOp( " @ = ( @ * @.zw ) + @.xy;\r\n", uvScene, uvScene, rtParams, rtParams) ); // scale it down and offset it to the rt size + + Var *lightInfoSamp = new Var; + lightInfoSamp->setType( "float4" ); + lightInfoSamp->setName( "lightInfoSample" ); + + // create texture var + Var *lightInfoBuffer = new Var; + lightInfoBuffer->setType( "sampler2D" ); + lightInfoBuffer->setName( "lightInfoBuffer" ); + lightInfoBuffer->uniform = true; + lightInfoBuffer->sampler = true; + lightInfoBuffer->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // Declare the RTLighting variables in this feature, they will either be assigned + // in this feature, or in the tonemap/lightmap feature + Var *d_lightcolor = new Var( "d_lightcolor", "float3" ); + meta->addStatement( new GenOp( " @;\r\n", new DecOp( d_lightcolor ) ) ); + + Var *d_NL_Att = new Var( "d_NL_Att", "float" ); + meta->addStatement( new GenOp( " @;\r\n", new DecOp( d_NL_Att ) ) ); + + Var *d_specular = new Var( "d_specular", "float" ); + meta->addStatement( new GenOp( " @;\r\n", new DecOp( d_specular ) ) ); + + + // Perform the uncondition here. + String unconditionLightInfo = String::ToLower( AdvancedLightBinManager::smBufferName ) + "Uncondition"; + meta->addStatement( new GenOp( avar( " %s(tex2D(@, @), @, @, @);\r\n", + unconditionLightInfo.c_str() ), lightInfoBuffer, uvScene, d_lightcolor, d_NL_Att, d_specular ) ); + + // This is kind of weak sauce + if( !fd.features[MFT_VertLit] && !fd.features[MFT_ToneMap] && !fd.features[MFT_LightMap] && !fd.features[MFT_SubSurface] ) + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "float4(@, 1.0)", d_lightcolor ), Material::Mul ) ) ); + + output = meta; +} + +ShaderFeature::Resources DeferredRTLightingFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + // Anything that is translucent should use the + // forward rendering lighting feature. + if ( fd.features[MFT_IsTranslucent] ) + return Parent::getResources( fd ); + + // HACK: See DeferredRTLightingFeatHLSL::setTexData. + mLastTexIndex = 0; + + Resources res; + res.numTex = 1; + res.numTexReg = 1; + return res; +} + +void DeferredRTLightingFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + // Anything that is translucent should use the + // forward rendering lighting feature. + if( fd.features[MFT_IsTranslucent] ) + { + Parent::setTexData( stageDat, fd, passData, texIndex ); + return; + } + + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( AdvancedLightBinManager::smBufferName ); + if( texTarget ) + { + // HACK: We store this for use in DeferredRTLightingFeatHLSL::processPix() + // which cannot deduce the texture unit itself. + mLastTexIndex = texIndex; + + passData.mTexType[ texIndex ] = Material::TexTarget; + passData.mTexSlot[ texIndex++ ].texTarget = texTarget; + } +} + + +void DeferredBumpFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_PrePassConditioner] ) + { + // There is an output conditioner active, so we need to supply a transform + // to the pixel shader. + MultiLine *meta = new MultiLine; + + // We need the view to tangent space transform in the pixel shader. + getOutViewToTangent( componentList, meta ); + + // Make sure there are texcoords + if( !fd.features[MFT_Parallax] && !fd.features[MFT_DiffuseMap] ) + getOutTexCoord( "texCoord", + "float2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + + output = meta; + } + else if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + !fd.features[MFT_RTLighting] ) + { + Parent::processVert( componentList, fd ); + return; + } + else + { + output = NULL; + } +} + +void DeferredBumpFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // NULL output in case nothing gets handled + output = NULL; + + if( fd.features[MFT_PrePassConditioner] ) + { + MultiLine *meta = new MultiLine; + + Var *viewToTangent = getInViewToTangent( componentList ); + + // create texture var + Var *bumpMap = getNormalMapTex(); + Var *texCoord = getInTexCoord( "texCoord", "float2", true, componentList ); + LangElement *texOp = new GenOp( "tex2D(@, @)", bumpMap, texCoord ); + + // create bump normal + Var *bumpNorm = new Var; + bumpNorm->setName( "bumpNormal" ); + bumpNorm->setType( "float4" ); + + LangElement *bumpNormDecl = new DecOp( bumpNorm ); + meta->addStatement( expandNormalMap( texOp, bumpNormDecl, bumpNorm, fd ) ); + + // This var is read from GBufferConditionerHLSL and + // used in the prepass output. + // + // By using the 'half' type here we get a bunch of partial + // precision optimized code on further operations on the normal + // which helps alot on older Geforce cards. + // + Var *gbNormal = new Var; + gbNormal->setName( "gbNormal" ); + gbNormal->setType( "half3" ); + LangElement *gbNormalDecl = new DecOp( gbNormal ); + + // Normalize is done later... + // Note: The reverse mul order is intentional. Affine matrix. + meta->addStatement( new GenOp( " @ = (half3)mul( @.xyz, @ );\r\n", gbNormalDecl, bumpNorm, viewToTangent ) ); + + output = meta; + return; + } + else if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + !fd.features[MFT_RTLighting] ) + { + Parent::processPix( componentList, fd ); + return; + } + else if ( fd.features[MFT_PixSpecular] && !fd.features[MFT_SpecularMap] ) + { + Var *bumpSample = (Var *)LangElement::find( "bumpSample" ); + if( bumpSample == NULL ) + { + Var *texCoord = getInTexCoord( "texCoord", "float2", true, componentList ); + + Var *bumpMap = getNormalMapTex(); + + bumpSample = new Var; + bumpSample->setType( "float4" ); + bumpSample->setName( "bumpSample" ); + LangElement *bumpSampleDecl = new DecOp( bumpSample ); + + output = new GenOp( " @ = tex2D(@, @);\r\n", bumpSampleDecl, bumpMap, texCoord ); + return; + } + } + + output = NULL; +} + +ShaderFeature::Resources DeferredBumpFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + fd.features[MFT_Parallax] || + !fd.features[MFT_RTLighting] ) + return Parent::getResources( fd ); + + Resources res; + if(!fd.features[MFT_SpecularMap]) + { + res.numTex = 1; + res.numTexReg = 1; + } + return res; +} + +void DeferredBumpFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + if ( fd.materialFeatures[MFT_NormalsOut] || + fd.features[MFT_IsTranslucent] || + !fd.features[MFT_RTLighting] ) + { + Parent::setTexData( stageDat, fd, passData, texIndex ); + return; + } + + GFXTextureObject *normalMap = stageDat.getTex( MFT_NormalMap ); + if ( !fd.features[MFT_Parallax] && !fd.features[MFT_SpecularMap] && + ( fd.features[MFT_PrePassConditioner] || + fd.features[MFT_PixSpecular] ) && + normalMap ) + { + passData.mTexType[ texIndex ] = Material::Bump; + passData.mTexSlot[ texIndex++ ].texObject = normalMap; + } +} + + +void DeferredPixelSpecularHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + Parent::processVert( componentList, fd ); + return; + } + output = NULL; +} + +void DeferredPixelSpecularHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + Parent::processPix( componentList, fd ); + return; + } + + MultiLine *meta = new MultiLine; + + Var *specular = new Var; + specular->setType( "float" ); + specular->setName( "specular" ); + LangElement * specDecl = new DecOp( specular ); + + Var *specCol = (Var*)LangElement::find( "specularColor" ); + if(specCol == NULL) + { + specCol = new Var; + specCol->setType( "float4" ); + specCol->setName( "specularColor" ); + specCol->uniform = true; + specCol->constSortPos = cspPotentialPrimitive; + } + + Var *specPow = new Var; + specPow->setType( "float" ); + specPow->setName( "specularPower" ); + + // If the gloss map flag is set, than the specular power is in the alpha + // channel of the specular map + if( fd.features[ MFT_GlossMap ] ) + meta->addStatement( new GenOp( " @ = @.a * 255;\r\n", new DecOp( specPow ), specCol ) ); + else + { + specPow->uniform = true; + specPow->constSortPos = cspPotentialPrimitive; + } + + Var *constSpecPow = new Var; + constSpecPow->setType( "float" ); + constSpecPow->setName( "constantSpecularPower" ); + constSpecPow->uniform = true; + constSpecPow->constSortPos = cspPass; + + Var *lightInfoSamp = (Var *)LangElement::find( "lightInfoSample" ); + Var *d_specular = (Var*)LangElement::find( "d_specular" ); + Var *d_NL_Att = (Var*)LangElement::find( "d_NL_Att" ); + + AssertFatal( lightInfoSamp && d_specular && d_NL_Att, + "DeferredPixelSpecularHLSL::processPix - Something hosed the deferred features!" ); + + // (a^m)^n = a^(m*n) + meta->addStatement( new GenOp( " @ = pow( @, ceil(@ / @)) * @;\r\n", + specDecl, d_specular, specPow, constSpecPow, d_NL_Att ) ); + + LangElement *specMul = new GenOp( "float4( @.rgb, 0 ) * @", specCol, specular ); + LangElement *final = specMul; + + // We we have a normal map then mask the specular + if( !fd.features[MFT_SpecularMap] && fd.features[MFT_NormalMap] ) + { + Var *bumpSample = (Var*)LangElement::find( "bumpSample" ); + final = new GenOp( "@ * @.a", final, bumpSample ); + } + + // add to color + meta->addStatement( new GenOp( " @;\r\n", assignColor( final, Material::Add ) ) ); + + output = meta; +} + +ShaderFeature::Resources DeferredPixelSpecularHLSL::getResources( const MaterialFeatureData &fd ) +{ + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + return Parent::getResources( fd ); + + Resources res; + return res; +} + + +ShaderFeature::Resources DeferredMinnaertHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + if( !fd.features[MFT_IsTranslucent] && fd.features[MFT_RTLighting] ) + { + res.numTex = 1; + res.numTexReg = 1; + } + return res; +} + +void DeferredMinnaertHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + if( !fd.features[MFT_IsTranslucent] && fd.features[MFT_RTLighting] ) + { + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(RenderPrePassMgr::BufferName); + if ( texTarget ) + { + passData.mTexType[ texIndex ] = Material::TexTarget; + passData.mTexSlot[ texIndex++ ].texTarget = texTarget; + } + } +} + +void DeferredMinnaertHLSL::processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ) +{ + if( !fd.features[MFT_IsTranslucent] && fd.features[MFT_RTLighting] ) + { + // Pull in the uncondition method for the g buffer + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( RenderPrePassMgr::BufferName ); + if ( texTarget && texTarget->getTargetConditioner() ) + { + ConditionerMethodDependency *unconditionMethod = texTarget->getTargetConditioner()->getConditionerMethodDependency(ConditionerFeature::UnconditionMethod); + unconditionMethod->createMethodMacro( String::ToLower(RenderPrePassMgr::BufferName) + "Uncondition", macros ); + addDependency(unconditionMethod); + } + } +} + +void DeferredMinnaertHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If there is no deferred information, bail on this feature + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + output = NULL; + return; + } + + // Make sure we pass the world space position to the + // pixel shader so we can calculate a view vector. + MultiLine *meta = new MultiLine; + addOutWsPosition( componentList, meta ); + output = meta; +} + +void DeferredMinnaertHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If there is no deferred information, bail on this feature + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + output = NULL; + return; + } + + Var *minnaertConstant = new Var; + minnaertConstant->setType( "float" ); + minnaertConstant->setName( "minnaertConstant" ); + minnaertConstant->uniform = true; + minnaertConstant->constSortPos = cspPotentialPrimitive; + + // create texture var + Var *prepassBuffer = new Var; + prepassBuffer->setType( "sampler2D" ); + prepassBuffer->setName( "prepassBuffer" ); + prepassBuffer->uniform = true; + prepassBuffer->sampler = true; + prepassBuffer->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // Texture coord + Var *uvScene = (Var*) LangElement::find( "uvScene" ); + AssertFatal(uvScene != NULL, "Unable to find UVScene, no RTLighting feature?"); + + MultiLine *meta = new MultiLine; + + // Get the world space view vector. + Var *wsViewVec = getWsView( getInWsPosition( componentList ), meta ); + + String unconditionPrePassMethod = String::ToLower(RenderPrePassMgr::BufferName) + "Uncondition"; + + Var *d_NL_Att = (Var*)LangElement::find( "d_NL_Att" ); + + meta->addStatement( new GenOp( avar( " float4 normalDepth = %s(@, @);\r\n", unconditionPrePassMethod.c_str() ), prepassBuffer, uvScene ) ); + meta->addStatement( new GenOp( " float vDotN = dot(normalDepth.xyz, @);\r\n", wsViewVec ) ); + meta->addStatement( new GenOp( " float Minnaert = pow( @, @) * pow(vDotN, 1.0 - @);\r\n", d_NL_Att, minnaertConstant, minnaertConstant ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "float4(Minnaert, Minnaert, Minnaert, 1.0)" ), Material::Mul ) ) ); + + output = meta; +} + + +void DeferredSubSurfaceHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If there is no deferred information, bail on this feature + if( fd.features[MFT_IsTranslucent] || !fd.features[MFT_RTLighting] ) + { + output = NULL; + return; + } + + Var *subSurfaceParams = new Var; + subSurfaceParams->setType( "float4" ); + subSurfaceParams->setName( "subSurfaceParams" ); + subSurfaceParams->uniform = true; + subSurfaceParams->constSortPos = cspPotentialPrimitive; + + Var *d_lightcolor = (Var*)LangElement::find( "d_lightcolor" ); + Var *d_NL_Att = (Var*)LangElement::find( "d_NL_Att" ); + + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( " float subLamb = smoothstep(-@.a, 1.0, @) - smoothstep(0.0, 1.0, @);\r\n", subSurfaceParams, d_NL_Att, d_NL_Att ) ); + meta->addStatement( new GenOp( " subLamb = max(0.0, subLamb);\r\n" ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "float4(@ + (subLamb * @.rgb), 1.0)", d_lightcolor, subSurfaceParams ), Material::Mul ) ) ); + + output = meta; +} diff --git a/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.h b/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.h new file mode 100644 index 0000000..5a79f1a --- /dev/null +++ b/lighting/advanced/hlsl/advancedLightingFeaturesHLSL.h @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DEFERREDFEATURESHLSL_H_ +#define _DEFERREDFEATURESHLSL_H_ + +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#include "shaderGen/HLSL/bumpHLSL.h" +#include "shaderGen/HLSL/pixSpecularHLSL.h" + +class ConditionerMethodDependency; + + +/// Lights the pixel by sampling from the light prepass +/// buffer. It will fall back to forward lighting +/// functionality for non-deferred rendered surfaces. +/// +/// Also note that this feature is only used in the +/// forward rendering pass. It is not used during the +/// prepass step. +/// +class DeferredRTLightingFeatHLSL : public RTLightingFeatHLSL +{ + typedef RTLightingFeatHLSL Parent; + +protected: + + /// @see DeferredRTLightingFeatHLSL::processPix() + U32 mLastTexIndex; + +public: + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Deferred RT Lighting"; + } +}; + + +/// This is used during the +class DeferredBumpFeatHLSL : public BumpFeatHLSL +{ + typedef BumpFeatHLSL Parent; + +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Bumpmap [Deferred]"; + } +}; + + +/// Generates specular highlights in the forward pass +/// from the light prepass buffer. +class DeferredPixelSpecularHLSL : public PixelSpecularHLSL +{ + typedef PixelSpecularHLSL Parent; + +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Pixel Specular [Deferred]"; + } +}; + + +/// +class DeferredMinnaertHLSL : public ShaderFeatureHLSL +{ + typedef ShaderFeatureHLSL Parent; + +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPixMacros( Vector ¯os, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Minnaert Shading [Deferred]"; + } +}; + + +/// +class DeferredSubSurfaceHLSL : public ShaderFeatureHLSL +{ + typedef ShaderFeatureHLSL Parent; + +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Sub-Surface Approximation [Deferred]"; + } +}; + +#endif // _DEFERREDFEATURESHLSL_H_ \ No newline at end of file diff --git a/lighting/advanced/hlsl/gBufferConditionerHLSL.cpp b/lighting/advanced/hlsl/gBufferConditionerHLSL.cpp new file mode 100644 index 0000000..496c4e4 --- /dev/null +++ b/lighting/advanced/hlsl/gBufferConditionerHLSL.cpp @@ -0,0 +1,364 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/advanced/hlsl/gBufferConditionerHLSL.h" + +#include "shaderGen/featureMgr.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "materials/materialFeatureTypes.h" + + +GBufferConditionerHLSL::GBufferConditionerHLSL( const GFXFormat bufferFormat, const NormalSpace nrmSpace ) : + Parent( bufferFormat ) +{ + // Figure out how we should store the normal data. These are the defaults. + mCanWriteNegativeValues = false; + mNormalStorageType = CartesianXYZ; + + // Note: We clear to a depth 1 (the w component) so + // that the unrendered parts of the scene end up + // farthest to the camera. + const NormalStorage &twoCmpNrmStorageType = ( nrmSpace == WorldSpace ? Spherical : LambertAzimuthal ); + switch(bufferFormat) + { + case GFXFormatR8G8B8A8: + mNormalStorageType = twoCmpNrmStorageType; + mBitsPerChannel = 8; + break; + + case GFXFormatR16G16B16A16F: + // Floating point buffers don't need to encode negative values + mCanWriteNegativeValues = true; + mNormalStorageType = twoCmpNrmStorageType; + mBitsPerChannel = 16; + break; + + // Store a 32bit depth with a sperical normal in the + // integer 16 format. This gives us perfect depth + // precision and high quality normals within a 64bit + // buffer format. + case GFXFormatR16G16B16A16: + mNormalStorageType = twoCmpNrmStorageType; + mBitsPerChannel = 16; + break; + + case GFXFormatR32G32B32A32F: + mCanWriteNegativeValues = true; + mNormalStorageType = CartesianXYZ; + mBitsPerChannel = 32; + break; + + default: + AssertFatal(false, "Unsupported G-Buffer format"); + } +} + +GBufferConditionerHLSL::~GBufferConditionerHLSL() +{ +} + +void GBufferConditionerHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + output = NULL; + + if( !fd.features[MFT_NormalMap] ) + { + // grab incoming vert normal + Var *inNormal = (Var*) LangElement::find( "normal" ); + AssertFatal( inNormal, "Something went bad with ShaderGen. The normal should be already defined." ); + + // grab output for gbuffer normal + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outNormal = connectComp->getElement( RT_TEXCOORD ); + outNormal->setName( "gbNormal" ); + outNormal->setStructName( "OUT" ); + outNormal->setType( "float3" ); + + Var *worldViewOnly = (Var*) LangElement::find( "worldViewOnly" ); + if( !worldViewOnly ) + { + worldViewOnly = new Var; + worldViewOnly->setType( "float4x4" ); + worldViewOnly->setName( "worldViewOnly" ); + worldViewOnly->uniform = true; + worldViewOnly->constSortPos = cspPrimitive; + } + + // Kick out the view-space normal + LangElement *statement = new GenOp( " @ = mul(@, float4( normalize(@), 0.0 ) ).xyz;\r\n", outNormal, worldViewOnly, inNormal ); + output = statement; + } +} + +void GBufferConditionerHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // sanity + AssertFatal( fd.features[MFT_EyeSpaceDepthOut], "No depth-out feature enabled! Bad news!" ); + + MultiLine *meta = new MultiLine; + + // grab connector normal + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *gbNormal = (Var*) LangElement::find( "gbNormal" ); + if( !gbNormal ) + { + gbNormal = connectComp->getElement( RT_TEXCOORD ); + gbNormal->setName( "gbNormal" ); + gbNormal->setStructName( "IN" ); + gbNormal->setType( "float3" ); + gbNormal->mapsToSampler = false; + gbNormal->uniform = false; + } + + // find depth + ShaderFeature *depthFeat = FEATUREMGR->getByType( MFT_EyeSpaceDepthOut ); + AssertFatal( depthFeat != NULL, "No eye space depth feature found!" ); + + Var *depth = (Var*) LangElement::find(depthFeat->getOutputVarName()); + AssertFatal( depth, "Something went bad with ShaderGen. The depth should be already generated by the EyeSpaceDepthOut feature." ); + + + Var *unconditionedOut = new Var; + unconditionedOut->setType("float4"); + unconditionedOut->setName("normal_depth"); + + LangElement *outputDecl = new DecOp( unconditionedOut ); + + // If we're doing prepass blending then we need + // to steal away the alpha channel before the + // conditioner stomps on it. + Var *alphaVal = NULL; + if ( fd.features[ MFT_IsTranslucentZWrite ] ) + { + alphaVal = new Var( "outAlpha", "float" ); + meta->addStatement( new GenOp( " @ = OUT.col.a; // MFT_IsTranslucentZWrite\r\n", new DecOp( alphaVal ) ) ); + } + + // NOTE: We renormalize the normal here as they + // will not stay normalized during interpolation. + meta->addStatement( new GenOp(" @ = @;", outputDecl, new GenOp( "float4(normalize(@), @)", gbNormal, depth ) ) ); + meta->addStatement( assignOutput( unconditionedOut ) ); + + // If we have an alpha var then we're doing prepass lerp blending. + if ( alphaVal ) + { + Var *outColor = (Var*)LangElement::find( getOutputTargetVarName( DefaultTarget ) ); + meta->addStatement( new GenOp( " @.ba = float2( 0, @ ); // MFT_IsTranslucentZWrite\r\n", outColor, alphaVal ) ); + } + + output = meta; +} + +ShaderFeature::Resources GBufferConditionerHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // Passing from VS->PS: + // - world space normal (gbNormal) + res.numTexReg = 1; + + return res; +} + +Var* GBufferConditionerHLSL::printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ) +{ + const bool isCondition = ( methodType == ConditionerFeature::ConditionMethod ); + + Var *retVal = NULL; + + // The uncondition method inputs are changed + if( isCondition ) + retVal = Parent::printMethodHeader( methodType, methodName, stream, meta ); + else + { + Var *methodVar = new Var; + methodVar->setName(methodName); + methodVar->setType("inline float4"); + DecOp *methodDecl = new DecOp(methodVar); + + Var *prepassSampler = new Var; + prepassSampler->setName("prepassSamplerVar"); + prepassSampler->setType("sampler2D"); + DecOp *prepassSamplerDecl = new DecOp(prepassSampler); + + Var *screenUV = new Var; + screenUV->setName("screenUVVar"); + screenUV->setType("float2"); + DecOp *screenUVDecl = new DecOp(screenUV); + + Var *bufferSample = new Var; + bufferSample->setName("bufferSample"); + bufferSample->setType("float4"); + DecOp *bufferSampleDecl = new DecOp(bufferSample); + + meta->addStatement( new GenOp( "@(@, @)\r\n", methodDecl, prepassSamplerDecl, screenUVDecl ) ); + + meta->addStatement( new GenOp( "{\r\n" ) ); + + meta->addStatement( new GenOp( " // Sampler g-buffer\r\n" ) ); + +#ifdef TORQUE_OS_XENON + meta->addStatement( new GenOp( " @;\r\n", bufferSampleDecl ) ); + meta->addStatement( new GenOp( " asm { tfetch2D @, @, @, MagFilter = point, MinFilter = point, MipFilter = point };\r\n", bufferSample, screenUV, prepassSampler ) ); +#else + // The gbuffer has no mipmaps, so use tex2dlod when + // possible so that the shader compiler can optimize. + meta->addStatement( new GenOp( " #if TORQUE_SM >= 30\r\n" ) ); + meta->addStatement( new GenOp( " @ = tex2Dlod(@, float4(@,0,0));\r\n", bufferSampleDecl, prepassSampler, screenUV ) ); + meta->addStatement( new GenOp( " #else\r\n" ) ); + meta->addStatement( new GenOp( " @ = tex2D(@, @);\r\n", bufferSampleDecl, prepassSampler, screenUV ) ); + meta->addStatement( new GenOp( " #endif\r\n\r\n" ) ); +#endif + + // We don't use this way of passing var's around, so this should cause a crash + // if something uses this improperly + retVal = bufferSample; + } + + return retVal; +} + +GenOp* GBufferConditionerHLSL::_posnegEncode( GenOp *val ) +{ + if(mNormalStorageType == LambertAzimuthal) + return mCanWriteNegativeValues ? val : new GenOp(avar("(%f * (@ + %f))", 1.0f/(M_SQRT2_F * 2.0f), M_SQRT2_F), val); + else + return mCanWriteNegativeValues ? val : new GenOp("(0.5 * (@ + 1.0))", val); +} + +GenOp* GBufferConditionerHLSL::_posnegDecode( GenOp *val ) +{ + if(mNormalStorageType == LambertAzimuthal) + return mCanWriteNegativeValues ? val : new GenOp(avar("(@ * %f - %f)", M_SQRT2_F * 2.0f, M_SQRT2_F), val); + else + return mCanWriteNegativeValues ? val : new GenOp("(@ * 2.0 - 1.0)", val); +} + +Var* GBufferConditionerHLSL::_conditionOutput( Var *unconditionedOutput, MultiLine *meta ) +{ + Var *retVar = new Var; + retVar->setType("float4"); + retVar->setName("_gbConditionedOutput"); + LangElement *outputDecl = new DecOp( retVar ); + + switch(mNormalStorageType) + { + case CartesianXYZ: + meta->addStatement( new GenOp( " // g-buffer conditioner: float4(normal.xyz, depth)\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4(@, @.a);\r\n", outputDecl, + _posnegEncode(new GenOp("@.xyz", unconditionedOutput)), unconditionedOutput ) ); + break; + + case CartesianXY: + meta->addStatement( new GenOp( " // g-buffer conditioner: float4(normal.xy, depth Hi + z-sign, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4(@, @.a);", outputDecl, + _posnegEncode(new GenOp("float3(@.xy, sign(@.z))", unconditionedOutput, unconditionedOutput)), unconditionedOutput ) ); + break; + + case Spherical: + meta->addStatement( new GenOp( " // g-buffer conditioner: float4(normal.theta, normal.phi, depth Hi, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4(@, 0.0, @.a);\r\n", outputDecl, + _posnegEncode(new GenOp("float2(atan2(@.y, @.x) / 3.14159265358979323846f, @.z)", unconditionedOutput, unconditionedOutput, unconditionedOutput ) ), + unconditionedOutput ) ); + + // HACK: This fixes the noise present when using a floating point + // gbuffer on Geforce cards and the "flat areas unlit" issues. + // + // We need work around atan2() above to fix this issue correctly + // without the extra overhead of this test. + // + meta->addStatement( new GenOp( " if ( abs( dot( @.xyz, float3( 0.0, 0.0, 1.0 ) ) ) > 0.999f ) @ = float4( 0, 1 * sign( @.z ), 0, @.a );\r\n", + unconditionedOutput, retVar, unconditionedOutput, unconditionedOutput ) ); + break; + + case LambertAzimuthal: + //http://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection + // + // Note we're casting to half to use partial precision + // sqrt which is much faster on older Geforces while + // still being acceptable for normals. + // + meta->addStatement( new GenOp( " // g-buffer conditioner: float4(normal.X, normal.Y, depth Hi, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4(@, 0.0, @.a);\r\n", outputDecl, + _posnegEncode(new GenOp("sqrt(half(2.0/(1.0 - @.y))) * half2(@.xz)", unconditionedOutput, unconditionedOutput)), + unconditionedOutput ) ); + break; + } + + // Encode depth into two channels + if(mNormalStorageType != CartesianXYZ) + { + const U64 maxValPerChannel = 1 << mBitsPerChannel; + meta->addStatement( new GenOp( " \r\n // Encode depth into hi/lo\r\n" ) ); + meta->addStatement( new GenOp( avar( " float2 _tempDepth = frac(@.a * float2(1.0, %llu.0));\r\n", maxValPerChannel - 1 ), + unconditionedOutput ) ); + meta->addStatement( new GenOp( avar( " @.zw = _tempDepth.xy - _tempDepth.yy * float2(1.0/%llu.0, 0.0);\r\n\r\n", maxValPerChannel - 1 ), + retVar ) ); + } + + AssertFatal( retVar != NULL, avar( "Cannot condition output to buffer format: %s", GFXStringTextureFormat[getBufferFormat()] ) ); + return retVar; +} + +Var* GBufferConditionerHLSL::_unconditionInput( Var *conditionedInput, MultiLine *meta ) +{ + Var *retVar = new Var; + retVar->setType("float4"); + retVar->setName("_gbUnconditionedInput"); + LangElement *outputDecl = new DecOp( retVar ); + + switch(mNormalStorageType) + { + case CartesianXYZ: + meta->addStatement( new GenOp( " // g-buffer unconditioner: float4(normal.xyz, depth)\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4(@, @.a);\r\n", outputDecl, + _posnegDecode(new GenOp("@.xyz", conditionedInput)), conditionedInput ) ); + break; + + case CartesianXY: + meta->addStatement( new GenOp( " // g-buffer unconditioner: float4(normal.xy, depth Hi + z-sign, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4(@, @.a);\r\n", outputDecl, + _posnegDecode(new GenOp("@.xyz", conditionedInput)), conditionedInput ) ); + meta->addStatement( new GenOp( " @.z *= sqrt(1.0 - dot(@.xy, @.xy));\r\n", retVar, retVar, retVar ) ); + break; + + case Spherical: + meta->addStatement( new GenOp( " // g-buffer unconditioner: float4(normal.theta, normal.phi, depth Hi, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " float2 spGPUAngles = @;\r\n", _posnegDecode(new GenOp("@.xy", conditionedInput)) ) ); + meta->addStatement( new GenOp( " float2 sincosTheta;\r\n" ) ); + meta->addStatement( new GenOp( " sincos(spGPUAngles.x * 3.14159265358979323846f, sincosTheta.x, sincosTheta.y);\r\n" ) ); + meta->addStatement( new GenOp( " float2 sincosPhi = float2(sqrt(1.0 - spGPUAngles.y * spGPUAngles.y), spGPUAngles.y);\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4(sincosTheta.y * sincosPhi.x, sincosTheta.x * sincosPhi.x, sincosPhi.y, @.a);\r\n", outputDecl, conditionedInput ) ); + break; + + case LambertAzimuthal: + // Note we're casting to half to use partial precision + // sqrt which is much faster on older Geforces while + // still being acceptable for normals. + // + meta->addStatement( new GenOp( " // g-buffer unconditioner: float4(normal.X, normal.Y, depth Hi, depth Lo)\r\n" ) ); + meta->addStatement( new GenOp( " float2 _inpXY = @;\r\n", _posnegDecode(new GenOp("@.xy", conditionedInput)) ) ); + meta->addStatement( new GenOp( " float _xySQ = dot(_inpXY, _inpXY);\r\n" ) ); + meta->addStatement( new GenOp( " @ = float4( sqrt(half(1.0 - (_xySQ / 4.0))) * _inpXY, -1.0 + (_xySQ / 2.0), @.a).xzyw;\r\n", outputDecl, conditionedInput ) ); + break; + } + + // Recover depth from encoding + if(mNormalStorageType != CartesianXYZ) + { + const U64 maxValPerChannel = 1 << mBitsPerChannel; + meta->addStatement( new GenOp( " \r\n // Decode depth\r\n" ) ); + meta->addStatement( new GenOp( avar( " @.w = dot( @.zw, float2(1.0, 1.0/%llu.0));\r\n", maxValPerChannel - 1 ), + retVar, conditionedInput ) ); + } + + + AssertFatal( retVar != NULL, avar( "Cannot uncondition input from buffer format: %s", GFXStringTextureFormat[getBufferFormat()] ) ); + return retVar; +} + diff --git a/lighting/advanced/hlsl/gBufferConditionerHLSL.h b/lighting/advanced/hlsl/gBufferConditionerHLSL.h new file mode 100644 index 0000000..db99411 --- /dev/null +++ b/lighting/advanced/hlsl/gBufferConditionerHLSL.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _GBUFFER_CONDITIONER_HLSL_H_ +#define _GBUFFER_CONDITIONER_HLSL_H_ + +#ifndef _CONDITIONER_BASE_H_ +#include "shaderGen/conditionerFeature.h" +#endif +#ifndef _SHADEROP_H_ +#include "shaderGen/shaderOp.h" +#endif + + +/// +class GBufferConditionerHLSL : public ConditionerFeature +{ + typedef ConditionerFeature Parent; + +public: + enum NormalStorage + { + CartesianXYZ, + CartesianXY, + Spherical, + LambertAzimuthal, + }; + + enum NormalSpace + { + WorldSpace, + ViewSpace, + }; + +protected: + + NormalStorage mNormalStorageType; + bool mCanWriteNegativeValues; + U32 mBitsPerChannel; + +public: + + GBufferConditionerHLSL( const GFXFormat bufferFormat, const NormalSpace nrmSpace ); + virtual ~GBufferConditionerHLSL(); + + + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "GBuffer Conditioner"; } + +protected: + + virtual Var *printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ); + + virtual GenOp* _posnegEncode( GenOp *val ); + virtual GenOp* _posnegDecode( GenOp *val ); + virtual Var* _conditionOutput( Var *unconditionedOutput, MultiLine *meta ); + virtual Var* _unconditionInput( Var *conditionedInput, MultiLine *meta ); +}; + +#endif // _GBUFFER_CONDITIONER_HLSL_H_ diff --git a/lighting/basic/basicLightManager.cpp b/lighting/basic/basicLightManager.cpp new file mode 100644 index 0000000..f4c76e4 --- /dev/null +++ b/lighting/basic/basicLightManager.cpp @@ -0,0 +1,383 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/basic/basicLightManager.h" + +#include "platform/platformTimer.h" +#include "console/simSet.h" +#include "console/consoleTypes.h" +#include "core/util/safeDelete.h" +#include "materials/processedMaterial.h" +#include "shaderGen/shaderFeature.h" +#include "lighting/basic/basicSceneObjectLightingPlugin.h" +#include "shaderGen/shaderGenVars.h" +#include "gfx/gfxShader.h" +#include "materials/sceneData.h" +#include "materials/materialParameters.h" +#include "materials/materialManager.h" +#include "materials/materialFeatureTypes.h" +#include "math/util/frustum.h" +#include "sceneGraph/sceneObject.h" +#include "renderInstance/renderPrePassMgr.h" +#include "shaderGen/featureMgr.h" +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#include "shaderGen/HLSL/bumpHLSL.h" +#include "shaderGen/HLSL/pixSpecularHLSL.h" +#include "lighting/shadowMap/shadowMatHook.h" + + +#ifdef TORQUE_OS_MAC +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#include "shaderGen/GLSL/bumpGLSL.h" +#include "shaderGen/GLSL/pixSpecularGLSL.h" +#endif + +U32 BasicLightManager::smActiveShadowPlugins = 0; +U32 BasicLightManager::smShadowsUpdated = 0; +U32 BasicLightManager::smElapsedUpdateMs = 0; + +F32 BasicLightManager::smProjectedShadowFilterDistance = 40.0f; + +// Dummy used to force initialization! +BasicLightManager *foo = BLM; + +static S32 QSORT_CALLBACK comparePluginScores( const void *a, const void *b ) +{ + const BasicSceneObjectLightingPlugin *A = *((BasicSceneObjectLightingPlugin**)a); + const BasicSceneObjectLightingPlugin *B = *((BasicSceneObjectLightingPlugin**)b); + + F32 dif = B->getScore() - A->getScore(); + return (S32)mFloor( dif ); +} + +BasicLightManager::BasicLightManager() + : LightManager( "Basic Lighting", "BLM" ), + mLastShader(NULL), + mLastConstants(NULL) +{ + mTimer = PlatformTimer::create(); +} + +BasicLightManager::~BasicLightManager() +{ + mLastShader = NULL; + mLastConstants = NULL; + + for (LightConstantMap::Iterator i = mConstantLookup.begin(); i != mConstantLookup.end(); i++) + { + if (i->value) + SAFE_DELETE(i->value); + } + mConstantLookup.clear(); + + if (mTimer) + SAFE_DELETE( mTimer ); +} + +bool BasicLightManager::isCompatible() const +{ + // As long as we have some shaders this works. + return GFX->getPixelShaderVersion() > 1.0; +} + +void BasicLightManager::activate( SceneGraph *sceneManager ) +{ + Parent::activate( sceneManager ); + + if( GFX->getAdapterType() == OpenGL ) + { + #ifdef TORQUE_OS_MAC + FEATUREMGR->registerFeature( MFT_LightMap, new LightmapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_ToneMap, new TonemapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_NormalMap, new BumpFeatGLSL ); + FEATUREMGR->registerFeature( MFT_RTLighting, new RTLightingFeatGLSL ); + FEATUREMGR->registerFeature( MFT_PixSpecular, new PixelSpecularGLSL ); + #endif + } + else + { + #ifndef TORQUE_OS_MAC + FEATUREMGR->registerFeature( MFT_LightMap, new LightmapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_ToneMap, new TonemapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_NormalMap, new BumpFeatHLSL ); + FEATUREMGR->registerFeature( MFT_RTLighting, new RTLightingFeatHLSL ); + FEATUREMGR->registerFeature( MFT_PixSpecular, new PixelSpecularHLSL ); + #endif + } + + FEATUREMGR->unregisterFeature( MFT_MinnaertShading ); + FEATUREMGR->unregisterFeature( MFT_SubSurface ); + + // First look for the prepass bin... + RenderPrePassMgr *prePassBin = NULL; + RenderPassManager *rpm = getSceneManager()->getRenderPass(); + for( U32 i = 0; i < rpm->getManagerCount(); i++ ) + { + RenderBinManager *bin = rpm->getManager(i); + if( bin->getRenderInstType() == RenderPrePassMgr::RIT_PrePass ) + { + prePassBin = (RenderPrePassMgr*)bin; + break; + } + } + + /* + // If you would like to use forward shading, and have a linear depth pre-pass + // than un-comment this code block. + if ( !prePassBin ) + { + Vector formats; + formats.push_back( GFXFormatR32F ); + formats.push_back( GFXFormatR16F ); + formats.push_back( GFXFormatR8G8B8A8 ); + GFXFormat linearDepthFormat = GFX->selectSupportedFormat( &GFXDefaultRenderTargetProfile, + formats, + true, + false ); + + // Uncomment this for a no-color-write z-fill pass. + //linearDepthFormat = GFXFormat_COUNT; + + prePassBin = new RenderPrePassMgr( linearDepthFormat != GFXFormat_COUNT, linearDepthFormat ); + prePassBin->registerObject(); + rpm->addManager( prePassBin ); + } + */ + mPrePassRenderBin = prePassBin; + + // If there is a prepass bin + MATMGR->setPrePassEnabled( mPrePassRenderBin.isValid() ); + gClientSceneGraph->setPostEffectFog( mPrePassRenderBin.isValid() && mPrePassRenderBin->getTargetChainLength() > 0 ); + + // Tell the material manager that we don't use prepass. + MATMGR->setPrePassEnabled( false ); + + GFXShader::addGlobalMacro( "TORQUE_BASIC_LIGHTING" ); + + // Hook into the SceneGraph prerender signal. + sceneManager->getPreRenderSignal().notify( this, &BasicLightManager::_onPreRender ); + + // Last thing... let everyone know we're active. + smActivateSignal.trigger( getId(), true ); + + Con::addVariable( "$BasicLightManagerStats::activePlugins", TypeS32, &smActiveShadowPlugins ); + Con::addVariable( "$BasicLightManagerStats::shadowsUpdated", TypeS32, &smShadowsUpdated ); + Con::addVariable( "$BasicLightManagerStats::elapsedUpdateMs", TypeS32, &smElapsedUpdateMs ); + + Con::addVariable( "$BasicLightManager::shadowFilterDistance", TypeF32, &smProjectedShadowFilterDistance ); + + // Get our BL projected shadow render pass manager + RenderPassManager *projectedShadowRPM = NULL; + if ( !Sim::findObject( "BL_ProjectedShadowRPM", projectedShadowRPM ) ) + return; + + // Get the first (and only) render bin + RenderBinManager *meshMgr = projectedShadowRPM->getManager( 0 ); + if ( !meshMgr ) + return; + + // Set up the material override delegate on the render bin + meshMgr->getMatOverrideDelegate().bind( this, &BasicLightManager::_shadowMaterialOverride ); +} + +void BasicLightManager::deactivate() +{ + Parent::deactivate(); + + mLastShader = NULL; + mLastConstants = NULL; + + for (LightConstantMap::Iterator i = mConstantLookup.begin(); i != mConstantLookup.end(); i++) + { + if (i->value) + SAFE_DELETE(i->value); + } + mConstantLookup.clear(); + + if ( mPrePassRenderBin ) + mPrePassRenderBin->deleteObject(); + mPrePassRenderBin = NULL; + + GFXShader::removeGlobalMacro( "TORQUE_BASIC_LIGHTING" ); + + // Remove us from the prerender signal. + getSceneManager()->getPreRenderSignal().remove( this, &BasicLightManager::_onPreRender ); + + // Now let everyone know we've deactivated. + smActivateSignal.trigger( getId(), false ); +} + +void BasicLightManager::_onPreRender( SceneGraph *sceneManger, const SceneState *state ) +{ + // Update all our shadow plugins here! + Vector *pluginInsts = BasicSceneObjectLightingPlugin::getPluginInstances(); + + Vector::const_iterator pluginIter = (*pluginInsts).begin(); + for ( ; pluginIter != (*pluginInsts).end(); pluginIter++ ) + { + BasicSceneObjectLightingPlugin *plugin = *pluginIter; + plugin->updateShadow( (SceneState*)state ); + } + + U32 pluginCount = (*pluginInsts).size(); + + // Sort them by the score. + dQsort( (*pluginInsts).address(), pluginCount, sizeof(BasicSceneObjectLightingPlugin*), comparePluginScores ); + + mTimer->getElapsedMs(); + mTimer->reset(); + U32 numUpdated = 0; + U32 targetMs = 5; + + S32 updateMs = 0; + + // NOTE: This is a hack to work around the state key + // system and allow prepRenderImage to be called directly + // on a SceneObject without going thru regular traversal. + // + // See ProjectedShadow::_renderToTexture. + // + sceneManger->incStateKey(); + + pluginIter = (*pluginInsts).begin(); + for ( ; pluginIter != (*pluginInsts).end(); pluginIter++ ) + { + BasicSceneObjectLightingPlugin *plugin = *pluginIter; + + // If we run out of update time then stop. + updateMs = mTimer->getElapsedMs(); + if ( updateMs >= targetMs ) + break; + + // NOTE! Fix this all up to past const SceneState! + plugin->renderShadow( (SceneState*)state ); + numUpdated++; + } + + smShadowsUpdated = numUpdated; + smActiveShadowPlugins = pluginCount; + smElapsedUpdateMs = updateMs; +} + +BaseMatInstance* BasicLightManager::_shadowMaterialOverride( BaseMatInstance *inMat ) +{ + // See if we have an existing material hook. + ShadowMaterialHook *hook = static_cast( inMat->getHook( ShadowMaterialHook::Type ) ); + if ( !hook ) + { + // Create a hook and initialize it using the incoming material. + hook = new ShadowMaterialHook; + hook->init( inMat ); + inMat->addHook( hook ); + } + + return hook->getShadowMat( ShadowType_Spot ); +} + +BasicLightManager::LightingShaderConstants::LightingShaderConstants() + : mInit( false ), + mShader( NULL ), + mLightPosition( NULL ), + mLightDiffuse( NULL ), + mLightAmbient( NULL ), + mLightInvRadiusSq( NULL ), + mLightSpotDir( NULL ), + mLightSpotAngle( NULL ) +{ +} + +BasicLightManager::LightingShaderConstants::~LightingShaderConstants() +{ + if (mShader.isValid()) + { + mShader->getReloadSignal().remove( this, &LightingShaderConstants::_onShaderReload ); + mShader = NULL; + } +} + +void BasicLightManager::LightingShaderConstants::init(GFXShader* shader) +{ + if (mShader.getPointer() != shader) + { + if (mShader.isValid()) + mShader->getReloadSignal().remove( this, &LightingShaderConstants::_onShaderReload ); + + mShader = shader; + mShader->getReloadSignal().notify( this, &LightingShaderConstants::_onShaderReload ); + } + + mLightPosition = shader->getShaderConstHandle( ShaderGenVars::lightPosition ); + mLightDiffuse = shader->getShaderConstHandle( ShaderGenVars::lightDiffuse); + mLightInvRadiusSq = shader->getShaderConstHandle( ShaderGenVars::lightInvRadiusSq ); + mLightAmbient = shader->getShaderConstHandle( ShaderGenVars::lightAmbient ); + mLightSpotDir = shader->getShaderConstHandle( ShaderGenVars::lightSpotDir ); + mLightSpotAngle = shader->getShaderConstHandle( ShaderGenVars::lightSpotAngle ); + + mInit = true; +} + +void BasicLightManager::LightingShaderConstants::_onShaderReload() +{ + if (mShader.isValid()) + init( mShader ); +} + +void BasicLightManager::setLightInfo( ProcessedMaterial* pmat, + const Material* mat, + const SceneGraphData& sgData, + const SceneState *state, + U32 pass, + GFXShaderConstBuffer* shaderConsts ) +{ + PROFILE_SCOPE( BasicLightManager_SetLightInfo ); + + GFXShader *shader = shaderConsts->getShader(); + + // Check to see if this is the same shader. Since we + // sort by material we should get hit repeatedly by the + // same one. This optimization should save us many + // hash table lookups. + if ( mLastShader.getPointer() != shader ) + { + LightConstantMap::Iterator iter = mConstantLookup.find(shader); + if ( iter != mConstantLookup.end() ) + { + mLastConstants = iter->value; + } + else + { + LightingShaderConstants* lsc = new LightingShaderConstants(); + mConstantLookup[shader] = lsc; + + mLastConstants = lsc; + } + + // Set our new shader + mLastShader = shader; + } + + // Make sure that our current lighting constants are initialized + if (!mLastConstants->mInit) + mLastConstants->init(shader); + + // NOTE: If you encounter a crash from this point forward + // while setting a shader constant its probably because the + // mConstantLookup has bad shaders/constants in it. + // + // This is a known crash bug that can occur if materials/shaders + // are reloaded and the light manager is not reset. + // + // We should look to fix this by clearing the table. + + _update4LightConsts( sgData, + mLastConstants->mLightPosition, + mLastConstants->mLightDiffuse, + mLastConstants->mLightAmbient, + mLastConstants->mLightInvRadiusSq, + mLastConstants->mLightSpotDir, + mLastConstants->mLightSpotAngle, + shaderConsts ); +} diff --git a/lighting/basic/basicLightManager.h b/lighting/basic/basicLightManager.h new file mode 100644 index 0000000..bbc8d6b --- /dev/null +++ b/lighting/basic/basicLightManager.h @@ -0,0 +1,109 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BASICLIGHTMANAGER_H_ +#define _BASICLIGHTMANAGER_H_ + +#ifndef _LIGHTMANAGER_H_ +#include "lighting/lightManager.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif + +class AvailableSLInterfaces; +class GFXShaderConstHandle; +class RenderPrePassMgr; +class PlatformTimer; +class BaseMatInstance; + +class BasicLightManager : public LightManager +{ + typedef LightManager Parent; + + // For access to protected constructor. + friend class Singleton; + +public: + + // LightManager + virtual bool isCompatible() const; + virtual void activate( SceneGraph *sceneManager ); + virtual void deactivate(); + virtual void setLightInfo(ProcessedMaterial* pmat, const Material* mat, const SceneGraphData& sgData, const SceneState *state, U32 pass, GFXShaderConstBuffer* shaderConsts); + virtual bool setTextureStage(const SceneGraphData& sgData, const U32 currTexFlag, const U32 textureSlot, GFXShaderConstBuffer* shaderConsts, ShaderConstHandles* handles) { return false; } + + static F32 getShadowFilterDistance() { return smProjectedShadowFilterDistance; } + +protected: + + // LightManager + virtual void _addLightInfoEx( LightInfo *lightInfo ) { } + virtual void _initLightFields() { } + + void _onPreRender( SceneGraph *sceneManger, const SceneState *state ); + + BaseMatInstance* _shadowMaterialOverride( BaseMatInstance *inMat ); + + // These are protected because we're a singleton and + // no one else should be creating us! + BasicLightManager(); + virtual ~BasicLightManager(); + + SimObjectPtr mPrePassRenderBin; + + struct LightingShaderConstants + { + bool mInit; + + GFXShaderRef mShader; + + GFXShaderConstHandle *mLightPosition; + GFXShaderConstHandle *mLightDiffuse; + GFXShaderConstHandle *mLightAmbient; + GFXShaderConstHandle *mLightInvRadiusSq; + GFXShaderConstHandle *mLightSpotDir; + GFXShaderConstHandle *mLightSpotAngle; + + LightingShaderConstants(); + ~LightingShaderConstants(); + + void init( GFXShader *shader ); + + void _onShaderReload(); + }; + + typedef Map LightConstantMap; + + LightConstantMap mConstantLookup; + GFXShaderRef mLastShader; + LightingShaderConstants* mLastConstants; + + /// Statics used for light manager/projected shadow metrics. + static U32 smActiveShadowPlugins; + static U32 smShadowsUpdated; + static U32 smElapsedUpdateMs; + + /// This is used to determine the distance + /// at which the shadow filtering PostEffect + /// will be enabled for ProjectedShadow. + static F32 smProjectedShadowFilterDistance; + + /// A timer used for tracking update time. + PlatformTimer *mTimer; +}; + +#define BLM Singleton::instance() + +#endif // _BASICLIGHTMANAGER_H_ diff --git a/lighting/basic/basicSceneObjectLightingPlugin.cpp b/lighting/basic/basicSceneObjectLightingPlugin.cpp new file mode 100644 index 0000000..c9594bd --- /dev/null +++ b/lighting/basic/basicSceneObjectLightingPlugin.cpp @@ -0,0 +1,164 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "lighting/basic/basicSceneObjectLightingPlugin.h" + +//#include "lighting/basic/basicLightManager.h" +#include "lighting/common/projectedShadow.h" +#include "T3D/shapeBase.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "ts/tsRenderState.h" +#include "gfx/sim/cubemapData.h" +#include "sceneGraph/reflector.h" +#include "T3D/decal/decalManager.h" + +static const U32 shadowObjectTypeMask = PlayerObjectType | CorpseObjectType | ItemObjectType | VehicleObjectType; +Vector BasicSceneObjectLightingPlugin::smPluginInstances; + +BasicSceneObjectLightingPlugin::BasicSceneObjectLightingPlugin(SceneObject* parent) + : mParentObject( parent ) +{ + mShadow = NULL; + + // Stick us on the list. + smPluginInstances.push_back( this ); +} + +BasicSceneObjectLightingPlugin::~BasicSceneObjectLightingPlugin() +{ + SAFE_DELETE( mShadow ); + + // Delete us from the list. + smPluginInstances.remove( this ); +} + +void BasicSceneObjectLightingPlugin::reset() +{ + SAFE_DELETE( mShadow ); +} + +void BasicSceneObjectLightingPlugin::cleanupPluginInstances() +{ + for (U32 i = 0; i < smPluginInstances.size(); i++) + { + BasicSceneObjectLightingPlugin *plug = smPluginInstances[i]; + smPluginInstances.remove( plug ); + delete plug; + i--; + } + + smPluginInstances.clear(); +} + +void BasicSceneObjectLightingPlugin::resetAll() +{ + for( U32 i = 0, num = smPluginInstances.size(); i < num; ++ i ) + smPluginInstances[ i ]->reset(); +} + +const F32 BasicSceneObjectLightingPlugin::getScore() const +{ + return mShadow ? mShadow->getScore() : 0.0f; +} + +void BasicSceneObjectLightingPlugin::updateShadow( SceneState *state ) +{ + if ( !mShadow ) + mShadow = new ProjectedShadow( mParentObject ); + + mShadow->update( state ); +} + +void BasicSceneObjectLightingPlugin::renderShadow( SceneState *state ) +{ + // hack until new scenegraph in place + GFXTransformSaver ts; + + TSRenderState rstate; + rstate.setSceneState(state); + + F32 camDist = (state->getCameraPosition() - mParentObject->getRenderPosition()).len(); + + // Make sure the shadow wants to be rendered + if( mShadow->shouldRender( state ) ) + { + // Render! (and note the time) + mShadow->render( camDist, rstate ); + } +} + +BasicSceneObjectPluginFactory p_BasicSceneObjectPluginFactory; + +BasicSceneObjectPluginFactory::BasicSceneObjectPluginFactory() +{ + LightManager::smActivateSignal.notify( this, &BasicSceneObjectPluginFactory::_onLMActivate ); +} + +void BasicSceneObjectPluginFactory::_onLMActivate( const char *lm, bool enable ) +{ + // Skip over signals that are not from + // the basic light manager. + if ( dStricmp( lm, "BLM" ) != 0 ) + return; + + if ( enable ) + { + SceneObject::smSceneObjectAdd.notify(this, &BasicSceneObjectPluginFactory::addLightPlugin); + SceneObject::smSceneObjectRemove.notify(this, &BasicSceneObjectPluginFactory::removeLightPlugin); + gDecalManager->getClearDataSignal().notify( this, &BasicSceneObjectPluginFactory::_onDecalManagerClear ); + addToExistingObjects(); + } + else + { + SceneObject::smSceneObjectAdd.remove(this, &BasicSceneObjectPluginFactory::addLightPlugin); + SceneObject::smSceneObjectRemove.remove(this, &BasicSceneObjectPluginFactory::removeLightPlugin); + gDecalManager->getClearDataSignal().remove( this, &BasicSceneObjectPluginFactory::_onDecalManagerClear ); + BasicSceneObjectLightingPlugin::cleanupPluginInstances(); + } +} + +void BasicSceneObjectPluginFactory::_onDecalManagerClear() +{ + BasicSceneObjectLightingPlugin::resetAll(); +} + +void BasicSceneObjectPluginFactory::removeLightPlugin( SceneObject *obj ) +{ + // Grab the plugin instance. + SceneObjectLightingPlugin *lightPlugin = obj->getLightingPlugin(); + + // Delete it, which will also remove it + // from the static list of plugin instances. + if ( lightPlugin ) + { + delete lightPlugin; + obj->setLightingPlugin( NULL ); + } +} + +void BasicSceneObjectPluginFactory::addLightPlugin(SceneObject* obj) +{ + bool serverObj = obj->isServerObject(); + bool isShadowType = (obj->getType() & shadowObjectTypeMask); + + if ( !isShadowType || serverObj ) + return; + + obj->setLightingPlugin(new BasicSceneObjectLightingPlugin(obj)); +} + +// Some objects may not get cleaned up during mission load/free, so add our +// plugin to existing scene objects +void BasicSceneObjectPluginFactory::addToExistingObjects() +{ + SimpleQueryList sql; + gClientContainer.findObjects( shadowObjectTypeMask, SimpleQueryList::insertionCallback, &sql); + for (SceneObject** i = sql.mList.begin(); i != sql.mList.end(); i++) + addLightPlugin(*i); +} + diff --git a/lighting/basic/basicSceneObjectLightingPlugin.h b/lighting/basic/basicSceneObjectLightingPlugin.h new file mode 100644 index 0000000..95a2605 --- /dev/null +++ b/lighting/basic/basicSceneObjectLightingPlugin.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +class ShadowBase; + +class BasicSceneObjectLightingPlugin : public SceneObjectLightingPlugin +{ +private: + + ShadowBase* mShadow; + SceneObject* mParentObject; + + static Vector smPluginInstances; + +public: + BasicSceneObjectLightingPlugin(SceneObject* parent); + ~BasicSceneObjectLightingPlugin(); + + static Vector* getPluginInstances() { return &smPluginInstances; } + + static void cleanupPluginInstances(); + static void resetAll(); + + const F32 getScore() const; + + // Called from BasicLightManager + virtual void updateShadow( SceneState *state ); + virtual void renderShadow( SceneState *state ); + + // Called by statics + virtual U32 packUpdate(SceneObject* obj, U32 checkMask, NetConnection *conn, U32 mask, BitStream *stream) { return 0; } + virtual void unpackUpdate(SceneObject* obj, NetConnection *conn, BitStream *stream) { } + + virtual void reset(); +}; + +class BasicSceneObjectPluginFactory +{ +protected: + + /// Called from the light manager on activation. + /// @see LightManager::addActivateCallback + void _onLMActivate( const char *lm, bool enable ); + + void _onDecalManagerClear(); + + void removeLightPlugin(SceneObject* obj); + void addLightPlugin(SceneObject* obj); + void addToExistingObjects(); +public: + BasicSceneObjectPluginFactory(); +}; diff --git a/lighting/basic/blInteriorSystem.cpp b/lighting/basic/blInteriorSystem.cpp new file mode 100644 index 0000000..5d2d9db --- /dev/null +++ b/lighting/basic/blInteriorSystem.cpp @@ -0,0 +1,1489 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "lighting/common/sceneLighting.h" +#include "lighting/lightingInterfaces.h" +#include "lighting/common/shadowVolumeBSP.h" +#include "interior/interiorInstance.h" +#include "lighting/common/sceneLightingGlobals.h" +#include "lighting/basic/basicLightManager.h" + +//#define SET_COLORS + +// +// Lighting system interface +// +class blInteriorSystem : public SceneLightingInterface +{ +protected: +public: + blInteriorSystem(); + static bool smUseVertexLighting; + + virtual SceneLighting::ObjectProxy* createObjectProxy(SceneObject* obj, SceneLighting::ObjectProxyList* sceneObjects); + virtual PersistInfo::PersistChunk* createPersistChunk(const U32 chunkType); + virtual bool createPersistChunkFromProxy(SceneLighting::ObjectProxy* objproxy, PersistInfo::PersistChunk **ret); + + virtual void init(); + virtual U32 addObjectType(); + virtual U32 addToClippingMask(); + + virtual void processLightingBegin(); + virtual void processLightingCompleted(bool success); + + // Given a ray, this will return the color from the lightmap of this object, return true if handled + virtual bool getColorFromRayInfo(RayInfo collision, ColorF& result); +}; + +bool blInteriorSystem::smUseVertexLighting = false; + +//------------------------------------------------------------------------------ +// Class SceneLighting::PersistInfo::InteriorChunk +//------------------------------------------------------------------------------ +struct blInteriorChunk : public PersistInfo::PersistChunk +{ + typedef PersistChunk Parent; + + blInteriorChunk(); + ~blInteriorChunk(); + + Vector sgNormalLightMaps; + + Vector mDetailLightmapCount; + Vector mDetailLightmapIndices; + Vector mLightmaps; + + bool mHasAlarmState; + Vector mDetailVertexCount; + Vector mVertexColorsNormal; + Vector mVertexColorsAlarm; + + bool read(Stream &); + bool write(Stream &); +}; + +blInteriorChunk::blInteriorChunk() +{ + mChunkType = PersistChunk::InteriorChunkType; +} + +blInteriorChunk::~blInteriorChunk() +{ + for(U32 i = 0; i < mLightmaps.size(); i++) + delete mLightmaps[i]; +} + +//------------------------------------------------------------------------------ +// - always read in vertex lighting, lightmaps may not be needed +bool blInteriorChunk::read(Stream & stream) +{ + if(!Parent::read(stream)) + return(false); + + U32 size; + U32 i; + + // lightmaps->vertex-info + // BTRTODO: FIX ME + if (true) + //if(!SceneLighting::smUseVertexLighting) + { + // size of this minichunk + if(!stream.read(&size)) + return(false); + + // lightmaps + stream.read(&size); + mDetailLightmapCount.setSize(size); + for(i = 0; i < size; i++) + if(!stream.read(&mDetailLightmapCount[i])) + return(false); + + stream.read(&size); + mDetailLightmapIndices.setSize(size); + for(i = 0; i < size; i++) + if(!stream.read(&mDetailLightmapIndices[i])) + return(false); + + if(!stream.read(&size)) + return(false); + mLightmaps.setSize(size); + + for(i = 0; i < size; i++) + { + mLightmaps[i] = new GBitmap; + if(!mLightmaps[i]->readBitmap("png",stream)) + return(false); + } + } + else + { + // step past the lightmaps + if(!stream.read(&size)) + return(false); + if(!stream.setPosition(stream.getPosition() + size)) + return(false); + } + + // size of the vertex lighting: need to reset stream position after zipStream reading + U32 zipStreamEnd; + if(!stream.read(&zipStreamEnd)) + return(false); + zipStreamEnd += stream.getPosition(); + + /* + // vertex lighting + ZipSubRStream zipStream; + if(!zipStream.attachStream(&stream)) + return(false); + + if(!zipStream.read(&size)) + return(false); + mHasAlarmState = bool(size); + + if(!zipStream.read(&size)) + return(false); + mDetailVertexCount.setSize(size); + for(i = 0; i < size; i++) + if(!zipStream.read(&mDetailVertexCount[i])) + return(false); + + size = 0; + for(i = 0; i < mDetailVertexCount.size(); i++) + size += mDetailVertexCount[i]; + + mVertexColorsNormal.setSize(size); + + if(mHasAlarmState) + mVertexColorsAlarm.setSize(size); + + U32 curPos = 0; + for(i = 0; i < mDetailVertexCount.size(); i++) + { + U32 count = mDetailVertexCount[i]; + for(U32 j = 0; j < count; j++) + if(!zipStream.read(&mVertexColorsNormal[curPos + j])) + return(false); + + // read in the alarm info + if(mHasAlarmState) + { + // same? + if(!zipStream.read(&size)) + return(false); + if(bool(size)) + dMemcpy(&mVertexColorsAlarm[curPos], &mVertexColorsNormal[curPos], count * sizeof(ColorI)); + else + { + for(U32 j = 0; j < count; j++) + if(!zipStream.read(&mVertexColorsAlarm[curPos + j])) + return(false); + } + } + + curPos += count; + } + + zipStream.detachStream(); + */ + + // since there is no resizeFilterStream the zipStream happily reads + // off the end of the compressed block... reset the position + stream.setPosition(zipStreamEnd); + + return(true); +} + +bool blInteriorChunk::write(Stream & stream) +{ + if(!Parent::write(stream)) + return(false); + + // lightmaps + U32 startPos = stream.getPosition(); + if(!stream.write(U32(0))) + return(false); + + U32 i; + if(!stream.write(U32(mDetailLightmapCount.size()))) + return(false); + for(i = 0; i < mDetailLightmapCount.size(); i++) + if(!stream.write(mDetailLightmapCount[i])) + return(false); + + if(!stream.write(U32(mDetailLightmapIndices.size()))) + return(false); + for(i = 0; i < mDetailLightmapIndices.size(); i++) + if(!stream.write(mDetailLightmapIndices[i])) + return(false); + + if(!stream.write(U32(mLightmaps.size()))) + return(false); + for(i = 0; i < mLightmaps.size(); i++) + { + AssertFatal(mLightmaps[i], "SceneLighting::blInteriorChunk::Write: Invalid bitmap!"); + if(!mLightmaps[i]->writeBitmap("png",stream)) + return(false); + } + + // write out the lightmap portions size + U32 endPos = stream.getPosition(); + if(!stream.setPosition(startPos)) + return(false); + + // don't include the offset in the size + if(!stream.write(U32(endPos - startPos - sizeof(U32)))) + return(false); + if(!stream.setPosition(endPos)) + return(false); + + + // vertex lighting: needs the size of the vertex info because the + // zip stream may read off the end of the chunk + startPos = stream.getPosition(); + if(!stream.write(U32(0))) + return(false); + + // write out the vertex lighting portions size + endPos = stream.getPosition(); + if(!stream.setPosition(startPos)) + return(false); + + // don't include the offset in the size + if(!stream.write(U32(endPos - startPos - sizeof(U32)))) + return(false); + if(!stream.setPosition(endPos)) + return(false); + + return(true); +} + +// +// InteriorProxy (definition) +// +class blInteriorProxy : public SceneLighting::ObjectProxy +{ +private: + typedef ObjectProxy Parent; + + bool isShadowedBy(blInteriorProxy *); + ShadowVolumeBSP::SVPoly * buildInteriorPoly(ShadowVolumeBSP * shadowVolumeBSP, + Interior * detail, U32 surfaceIndex, LightInfo * light, + bool createSurfaceInfo); +public: + + blInteriorProxy(SceneObject * obj); + ~blInteriorProxy(); + InteriorInstance * operator->() {return(static_cast(static_cast(mObj)));} + InteriorInstance * getObject() {return(static_cast(static_cast(mObj)));} + + // current light info + ShadowVolumeBSP * mBoxShadowBSP; + Vector mLitBoxSurfaces; + Vector mOppositeBoxPlanes; + Vector mTerrainTestPlanes; + + + struct sgSurfaceInfo + { + const Interior::Surface *sgSurface; + U32 sgIndex; + Interior *sgDetail; + bool sgHasAlarm; + }; + U32 sgCurrentSurfaceIndex; + U32 sgSurfacesPerPass; + InteriorInstance *sgInterior; + Vector sgLights; + Vector sgSurfaces; + + void sgAddLight(LightInfo *light, InteriorInstance *interior); + //void sgLightUniversalPoint(LightInfo *light); + void sgProcessSurface(const Interior::Surface &surface, U32 i, Interior *detail, bool hasAlarm); + + + // lighting interface + bool loadResources(); + void init(); + //bool tgePreLight(LightInfo* light); + bool preLight(LightInfo *); + void light(LightInfo *); + void postLight(bool lastLight); + + //virtual void processLightingStart(); + //virtual bool processStartObjectLightingEvent(SceneLighting::ObjectProxy* objproxy, U32 current, U32 max); + //virtual void processTGELightProcessEvent(U32 curr, U32 max, LightInfo*); + + virtual bool supportsShadowVolume(); + virtual void getClipPlanes(Vector& planes); + virtual void addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level); + + // persist + U32 getResourceCRC(); + bool setPersistInfo(PersistInfo::PersistChunk *); + bool getPersistInfo(PersistInfo::PersistChunk *); +}; + +//------------------------------------------------------------------------------ +// Class SceneLighting::InteriorProxy: +//------------------------------------------------------------------------------ +blInteriorProxy::blInteriorProxy(SceneObject * obj) : +Parent(obj) +{ + mBoxShadowBSP = 0; +} + +blInteriorProxy::~blInteriorProxy() +{ + delete mBoxShadowBSP; +} + +bool blInteriorProxy::loadResources() +{ + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + Resource & interiorRes = interior->getResource(); + if(!bool(interiorRes)) + return(false); + + return(true); +} + +void blInteriorProxy::init() +{ + InteriorInstance * interior = getObject(); + if(!interior) + return; +} + +bool blInteriorProxy::supportsShadowVolume() +{ + return true; +} + +void blInteriorProxy::getClipPlanes(Vector& planes) +{ + for(U32 i = 0; i < mLitBoxSurfaces.size(); i++) + planes.push_back(mLitBoxSurfaces[i]->mPlane); +} + +ShadowVolumeBSP::SVPoly * blInteriorProxy::buildInteriorPoly(ShadowVolumeBSP * shadowVolumeBSP, + Interior * detail, U32 surfaceIndex, LightInfo * light, + bool createSurfaceInfo) +{ + InteriorInstance* interior = dynamic_cast(getObject()); + if (!interior) + return NULL; + + // transform and add the points... + const MatrixF & transform = interior->getTransform(); + const VectorF & scale = interior->getScale(); + + const Interior::Surface & surface = detail->mSurfaces[surfaceIndex]; + + ShadowVolumeBSP::SVPoly * poly = shadowVolumeBSP->createPoly(); + + poly->mWindingCount = surface.windingCount; + + // project these points + for(U32 j = 0; j < poly->mWindingCount; j++) + { + Point3F iPnt = detail->mPoints[detail->mWindings[surface.windingStart + j]].point; + Point3F tPnt; + iPnt.convolve(scale); + transform.mulP(iPnt, &tPnt); + poly->mWinding[j] = tPnt; + } + + // convert from fan + U32 tmpIndices[ShadowVolumeBSP::SVPoly::MaxWinding]; + Point3F fanIndices[ShadowVolumeBSP::SVPoly::MaxWinding]; + + tmpIndices[0] = 0; + + U32 idx = 1; + U32 i; + for(i = 1; i < poly->mWindingCount; i += 2) + tmpIndices[idx++] = i; + for(i = ((poly->mWindingCount - 1) & (~0x1)); i > 0; i -= 2) + tmpIndices[idx++] = i; + + idx = 0; + for(i = 0; i < poly->mWindingCount; i++) + if(surface.fanMask & (1 << i)) + fanIndices[idx++] = poly->mWinding[tmpIndices[i]]; + + // set the data + poly->mWindingCount = idx; + for(i = 0; i < poly->mWindingCount; i++) + poly->mWinding[i] = fanIndices[i]; + + // flip the plane - shadow volumes face inwards + PlaneF plane = detail->getPlane(surface.planeIndex); + if(!Interior::planeIsFlipped(surface.planeIndex)) + plane.neg(); + + // transform the plane + mTransformPlane(transform, scale, plane, &poly->mPlane); + shadowVolumeBSP->buildPolyVolume(poly, light); + + // do surface info? + if(createSurfaceInfo) + { + ShadowVolumeBSP::SurfaceInfo * surfaceInfo = new ShadowVolumeBSP::SurfaceInfo; + shadowVolumeBSP->mSurfaces.push_back(surfaceInfo); + + // fill it + surfaceInfo->mSurfaceIndex = surfaceIndex; + surfaceInfo->mShadowVolume = shadowVolumeBSP->getShadowVolume(poly->mShadowVolume); + + // POLY and POLY node gets it too + ShadowVolumeBSP::SVNode * traverse = shadowVolumeBSP->getShadowVolume(poly->mShadowVolume); + while(traverse->mFront) + { + traverse->mSurfaceInfo = surfaceInfo; + traverse = traverse->mFront; + } + + // get some info from the poly node + poly->mSurfaceInfo = traverse->mSurfaceInfo = surfaceInfo; + surfaceInfo->mPlaneIndex = traverse->mPlaneIndex; + } + + return(poly); +} + + +void blInteriorProxy::addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level) +{ + if(light->getType() != LightInfo::Vector) + return; + + ColorF ambient = light->getAmbient(); + + bool shadowedTree = true; + + InteriorInstance* interior = dynamic_cast(getObject()); + if (!interior) + return; + Resource mInteriorRes = interior->getResource(); + + // check if just getting shadow detail + if(level == SceneLighting::SHADOW_DETAIL) + { + shadowedTree = false; + level = mInteriorRes->getNumDetailLevels() - 1; + } + + Interior * detail = mInteriorRes->getDetailLevel(level); + bool hasAlarm = detail->hasAlarmState(); + + // make sure surfaces do not get processed more than once + BitVector surfaceProcessed; + surfaceProcessed.setSize(detail->mSurfaces.size()); + surfaceProcessed.clear(); + + ColorI color = light->getAmbient(); + + // go through the zones of the interior and grab outside visible surfaces + for(U32 i = 0; i < detail->getNumZones(); i++) + { + Interior::Zone & zone = detail->mZones[i]; + for(U32 j = 0; j < zone.surfaceCount; j++) + { + U32 surfaceIndex = detail->mZoneSurfaces[zone.surfaceStart + j]; + + // dont reprocess a surface + if(surfaceProcessed.test(surfaceIndex)) + continue; + surfaceProcessed.set(surfaceIndex); + + Interior::Surface & surface = detail->mSurfaces[surfaceIndex]; + + // outside visible? + if(!(surface.surfaceFlags & Interior::SurfaceOutsideVisible)) + continue; + + // good surface? + PlaneF plane = detail->getPlane(surface.planeIndex); + if(Interior::planeIsFlipped(surface.planeIndex)) + plane.neg(); + + // project the plane + PlaneF projPlane; + mTransformPlane(interior->getTransform(), interior->getScale(), plane, &projPlane); + + // fill with ambient? (need to do here, because surface will not be + // added to the SVBSP tree) + F32 dot = mDot(projPlane, light->getDirection()); + if(dot > -gParellelVectorThresh)// && !(GFX->getPixelShaderVersion() > 0.0) ) + { + if(shadowedTree) + { + // alarm lighting + GFXTexHandle normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex)); + GFXTexHandle alarmHandle; + + GBitmap * normLightmap = normHandle->getBitmap(); + GBitmap * alarmLightmap = 0; + + // check if they share the lightmap + if(hasAlarm) + { + if(detail->getNormalLMapIndex(surfaceIndex) != detail->getAlarmLMapIndex(surfaceIndex)) + { + alarmHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getAlarmLMapIndex(surfaceIndex)); + alarmLightmap = alarmHandle->getBitmap(); + } + } + + // + // Support for interior light map border sizes. + // + U32 xlen, ylen, xoff, yoff; + U32 lmborder = detail->getLightMapBorderSize(); + xlen = surface.mapSizeX + (lmborder * 2); + ylen = surface.mapSizeY + (lmborder * 2); + xoff = surface.mapOffsetX - lmborder; + yoff = surface.mapOffsetY - lmborder; + + // attemp to light normal and alarm lighting + for(U32 c = 0; c < 2; c++) + { + GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap; + if(!lightmap) + continue; + + // fill it + for(U32 y = 0; y < ylen; y++) + { + for(U32 x = 0; x < xlen; x++) + { + ColorI outColor(255, 0, 0, 255); + +#ifndef SET_COLORS + ColorI lmColor(0, 0, 0, 255); + lightmap->getColor(xoff + x, yoff + y, lmColor); + + U32 _r = static_cast( color.red ) + static_cast( lmColor.red ); + U32 _g = static_cast( color.green ) + static_cast( lmColor.green ); + U32 _b = static_cast( color.blue ) + static_cast( lmColor.blue ); + + outColor.red = mClamp(_r, 0, 255); + outColor.green = mClamp(_g, 0, 255); + outColor.blue = mClamp(_b, 0, 255); +#endif + + lightmap->setColor(xoff + x, yoff + y, outColor); + } + } + } + } + continue; + } + + ShadowVolumeBSP::SVPoly * poly = buildInteriorPoly(shadowVolume, detail, + surfaceIndex, light, shadowedTree); + + // insert it into the SVBSP tree + shadowVolume->insertPoly(poly); + } + } +} + +bool blInteriorProxy::preLight(LightInfo * light) +{ + // create shadow volume of the bounding box of this object + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + if(light->getType() != LightInfo::Vector) + return(false); + + // reset + mLitBoxSurfaces.clear(); + + const Box3F & objBox = interior->getObjBox(); + const MatrixF & objTransform = interior->getTransform(); + const VectorF & objScale = interior->getScale(); + + // grab the surfaces which form the shadow volume + U32 numPlanes = 0; + PlaneF testPlanes[3]; + U32 planeIndices[3]; + + // grab the bounding planes which face the light + U32 i; + for(i = 0; (i < 6) && (numPlanes < 3); i++) + { + PlaneF plane; + plane.x = BoxNormals[i].x; + plane.y = BoxNormals[i].y; + plane.z = BoxNormals[i].z; + + if(i&1) + plane.d = (((const float*)objBox.minExtents)[(i-1)>>1]); + else + plane.d = -(((const float*)objBox.maxExtents)[i>>1]); + + // project + mTransformPlane(objTransform, objScale, plane, &testPlanes[numPlanes]); + + planeIndices[numPlanes] = i; + + if(mDot(testPlanes[numPlanes], light->getDirection()) < gParellelVectorThresh) + numPlanes++; + } + AssertFatal(numPlanes, "blInteriorProxy::preLight: no planes found"); + + // project the points + Point3F projPnts[8]; + for(i = 0; i < 8; i++) + { + Point3F pnt; + pnt.set(BoxPnts[i].x ? objBox.maxExtents.x : objBox.minExtents.x, + BoxPnts[i].y ? objBox.maxExtents.y : objBox.minExtents.y, + BoxPnts[i].z ? objBox.maxExtents.z : objBox.minExtents.z); + + // scale it + pnt.convolve(objScale); + objTransform.mulP(pnt, &projPnts[i]); + } + + mBoxShadowBSP = new ShadowVolumeBSP; + + // insert the shadow volumes for the surfaces + for(i = 0; i < numPlanes; i++) + { + ShadowVolumeBSP::SVPoly * poly = mBoxShadowBSP->createPoly(); + poly->mWindingCount = 4; + + U32 j; + for(j = 0; j < 4; j++) + poly->mWinding[j] = projPnts[BoxVerts[planeIndices[i]][j]]; + + testPlanes[i].neg(); + poly->mPlane = testPlanes[i]; + + mBoxShadowBSP->buildPolyVolume(poly, light); + mLitBoxSurfaces.push_back(mBoxShadowBSP->copyPoly(poly)); + mBoxShadowBSP->insertPoly(poly); + + // create the opposite planes for testing against terrain + Point3F pnts[3]; + for(j = 0; j < 3; j++) + pnts[j] = projPnts[BoxVerts[planeIndices[i]^1][j]]; + PlaneF plane(pnts[2], pnts[1], pnts[0]); + mOppositeBoxPlanes.push_back(plane); + } + + // grab the unique planes for terrain checks + for(i = 0; i < numPlanes; i++) + { + U32 mask = 0; + for(U32 j = 0; j < numPlanes; j++) + mask |= BoxSharedEdgeMask[planeIndices[i]][planeIndices[j]]; + + ShadowVolumeBSP::SVNode * traverse = mBoxShadowBSP->getShadowVolume(mLitBoxSurfaces[i]->mShadowVolume); + while(traverse->mFront) + { + if(!(mask & 1)) + mTerrainTestPlanes.push_back(mBoxShadowBSP->getPlane(traverse->mPlaneIndex)); + + mask >>= 1; + traverse = traverse->mFront; + } + } + + // there will be 2 duplicate node planes if there were only 2 planes lit + if(numPlanes == 2) + { + for(S32 i = 0; i < mTerrainTestPlanes.size(); i++) + for(U32 j = 0; j < mTerrainTestPlanes.size(); j++) + { + if(i == j) + continue; + + if((mDot(mTerrainTestPlanes[i], mTerrainTestPlanes[j]) > gPlaneNormThresh) && + (mFabs(mTerrainTestPlanes[i].d - mTerrainTestPlanes[j].d) < gPlaneDistThresh)) + { + mTerrainTestPlanes.erase(i); + i--; + break; + } + } + } + + return(true); +} + +bool blInteriorProxy::isShadowedBy(blInteriorProxy * test) +{ + // add if overlapping world box + if((*this)->getWorldBox().isOverlapped((*test)->getWorldBox())) + return(true); + + // test the box shadow volume + for(U32 i = 0; i < mLitBoxSurfaces.size(); i++) + { + ShadowVolumeBSP::SVPoly * poly = mBoxShadowBSP->copyPoly(mLitBoxSurfaces[i]); + if(test->mBoxShadowBSP->testPoly(poly)) + return(true); + } + + return(false); +} + +void blInteriorProxy::light(LightInfo * light) +{ + Platform::setMathControlStateKnown(); + + InteriorInstance * interior = getObject(); + if(!interior) + return; + + ColorF ambient = light->getAmbient(); + + S32 time = Platform::getRealMilliseconds(); + + // create own shadow volume + ShadowVolumeBSP shadowVolume; + + // add the other objects lit surfaces into shadow volume + for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++) + { + if(!(*itr)->getObject()) + continue; + + ObjectProxy* obj = *itr; + if (obj != this) + { + if (obj->supportsShadowVolume()) + obj->addToShadowVolume(&shadowVolume, light, SceneLighting::SHADOW_DETAIL); + } + +/* + // insert the terrain squares + if(gLighting->isTerrain((*itr)->mObj)) + { + TerrainProxy * terrain = static_cast(*itr); + + Vector clipPlanes; + clipPlanes = mTerrainTestPlanes; + for(U32 i = 0; i < mOppositeBoxPlanes.size(); i++) + clipPlanes.push_back(mOppositeBoxPlanes[i]); + + Vector shadowList; + if(terrain->getShadowedSquares(clipPlanes, shadowList)) + { + TerrainBlock * block = static_cast((*itr)->getObject()); + Point3F offset; + block->getTransform().getColumn(3, &offset); + + F32 squareSize = block->getSquareSize(); + + for(U32 j = 0; j < shadowList.size(); j++) + { + Point2I pos(shadowList[j] & TerrainBlock::BlockMask, shadowList[j] >> TerrainBlock::BlockShift); + Point2F wPos(pos.x * squareSize + offset.x, + pos.y * squareSize + offset.y); + + Point3F pnts[4]; + pnts[0].set(wPos.x, wPos.y, fixedToFloat(block->getHeight(pos.x, pos.y))); + pnts[1].set(wPos.x + squareSize, wPos.y, fixedToFloat(block->getHeight(pos.x + 1, pos.y))); + pnts[2].set(wPos.x + squareSize, wPos.y + squareSize, fixedToFloat(block->getHeight(pos.x + 1, pos.y + 1))); + pnts[3].set(wPos.x, wPos.y + squareSize, fixedToFloat(block->getHeight(pos.x, pos.y + 1))); + + GridSquare * gs = block->findSquare(0, pos); + + U32 squareIdx = (gs->flags & GridSquare::Split45) ? 0 : 2; + + for(U32 k = squareIdx; k < (squareIdx + 2); k++) + { + // face plane inwards + PlaneF plane(pnts[TerrainSquareIndices[k][2]], + pnts[TerrainSquareIndices[k][1]], + pnts[TerrainSquareIndices[k][0]]); + + if(mDot(plane, light->mDirection) > gParellelVectorThresh) + { + ShadowVolumeBSP::SVPoly * poly = shadowVolume.createPoly(); + poly->mWindingCount = 3; + + poly->mWinding[0] = pnts[TerrainSquareIndices[k][0]]; + poly->mWinding[1] = pnts[TerrainSquareIndices[k][1]]; + poly->mWinding[2] = pnts[TerrainSquareIndices[k][2]]; + poly->mPlane = plane; + + // create the shadow volume for this and insert + shadowVolume.buildPolyVolume(poly, light); + shadowVolume.insertPoly(poly); + } + } + } + } + }*/ + } + + // light all details + for(U32 i = 0; i < interior->getResource()->getNumDetailLevels(); i++) + { + // clear lightmaps + Interior * detail = interior->getResource()->getDetailLevel(i); + gInteriorLMManager.clearLightmaps(detail->getLMHandle(), interior->getLMHandle()); + + // clear out the last inserted interior + shadowVolume.removeLastInterior(); + + bool hasAlarm = detail->hasAlarmState(); + + addToShadowVolume(&shadowVolume, light, i); + + //gLighting->addInterior(&shadowVolume, *this, light, i); + + for(U32 j = 0; j < shadowVolume.mSurfaces.size(); j++) + { + ShadowVolumeBSP::SurfaceInfo * surfaceInfo = shadowVolume.mSurfaces[j]; + + U32 surfaceIndex = surfaceInfo->mSurfaceIndex; + + const Interior::Surface & surface = detail->getSurface(surfaceIndex); + + // alarm lighting + GFXTexHandle normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex)); + GFXTexHandle alarmHandle; + + GBitmap * normLightmap = normHandle->getBitmap(); + GBitmap * alarmLightmap = 0; + + // check if the lightmaps are shared + if(hasAlarm) + { + if(detail->getNormalLMapIndex(surfaceIndex) != detail->getAlarmLMapIndex(surfaceIndex)) + { + alarmHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getAlarmLMapIndex(surfaceIndex)); + alarmLightmap = alarmHandle->getBitmap(); + } + } + + // points right way? + PlaneF plane = detail->getPlane(surface.planeIndex); + if(Interior::planeIsFlipped(surface.planeIndex)) + plane.neg(); + + const MatrixF & transform = interior->getTransform(); + const Point3F & scale = interior->getScale(); + + // + PlaneF projPlane; + mTransformPlane(transform, scale, plane, &projPlane); + + F32 dot = mDot(projPlane, -light->getDirection()); + + // cancel out lambert dot product and ambient lighting on hardware + // with pixel shaders + if( GFX->getPixelShaderVersion() > 0.0 ) + { + dot = 1.0f; + ambient.set( 0.0f, 0.0f, 0.0f ); + } + + // shadowed? + if(!surfaceInfo->mShadowed.size()) + { + // calc the color and convert to U8 rep + ColorF tmp = (light->getColor() * dot) + ambient; + tmp.clamp(); + ColorI color = tmp; + + // attempt to light both the normal and the alarm states + for(U32 c = 0; c < 2; c++) + { + GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap; + if(!lightmap) + continue; + + // + // Support for interior light map border sizes. + // + U32 xlen, ylen, xoff, yoff; + U32 lmborder = detail->getLightMapBorderSize(); + xlen = surface.mapSizeX + (lmborder * 2); + ylen = surface.mapSizeY + (lmborder * 2); + xoff = surface.mapOffsetX - lmborder; + yoff = surface.mapOffsetY - lmborder; + + // fill it + for(U32 y = 0; y < ylen; y++) + { + for(U32 x = 0; x < xlen; x++) + { + ColorI outColor(0, 255, 0, 255); + +#ifndef SET_COLORS + ColorI lmColor(0, 0, 0, 255); + lightmap->getColor(xoff + x, yoff + y, lmColor); + + U32 _r = static_cast( color.red ) + static_cast( lmColor.red ); + U32 _g = static_cast( color.green ) + static_cast( lmColor.green ); + U32 _b = static_cast( color.blue ) + static_cast( lmColor.blue ); + + outColor.red = mClamp(_r, 0, 255); + outColor.green = mClamp(_g, 0, 255); + outColor.blue = mClamp(_b, 0, 255); +#endif + + lightmap->setColor(xoff + x, yoff + y, outColor); + } + } + } + if(!surfaceInfo->mShadowed.size()) + continue; + } + + // + // Support for interior light map border sizes. + // + U32 xlen, ylen, xoff, yoff; + U32 lmborder = detail->getLightMapBorderSize(); + xlen = surface.mapSizeX + (lmborder * 2); + ylen = surface.mapSizeY + (lmborder * 2); + xoff = surface.mapOffsetX - lmborder; + yoff = surface.mapOffsetY - lmborder; + + // get the lmagGen... + const Interior::TexGenPlanes & lmTexGenEQ = detail->getLMTexGenEQ(surfaceIndex); + + const F32 * const lGenX = lmTexGenEQ.planeX; + const F32 * const lGenY = lmTexGenEQ.planeY; + + AssertFatal((lGenX[0] * lGenX[1] == 0.f) && + (lGenX[0] * lGenX[2] == 0.f) && + (lGenX[1] * lGenX[2] == 0.f), "Bad lmTexGen!"); + AssertFatal((lGenY[0] * lGenY[1] == 0.f) && + (lGenY[0] * lGenY[2] == 0.f) && + (lGenY[1] * lGenY[2] == 0.f), "Bad lmTexGen!"); + + // get the axis index for the texgens (could be swapped) + S32 si=0; + S32 ti=0; + S32 axis = -1; + + // + if(lGenX[0] == 0.f && lGenY[0] == 0.f) // YZ + { + axis = 0; + if(lGenX[1] == 0.f) { // swapped? + si = 2; + ti = 1; + } else { + si = 1; + ti = 2; + } + } + else if(lGenX[1] == 0.f && lGenY[1] == 0.f) // XZ + { + axis = 1; + if(lGenX[0] == 0.f) { // swapped? + si = 2; + ti = 0; + } else { + si = 0; + ti = 2; + } + } + else if(lGenX[2] == 0.f && lGenY[2] == 0.f) // XY + { + axis = 2; + if(lGenX[0] == 0.f) { // swapped? + si = 1; + ti = 0; + } else { + si = 0; + ti = 1; + } + } + AssertFatal(!(axis == -1), "SceneLighting::lightInterior: bad TexGen!"); + + const F32 * pNormal = ((const F32*)plane); + + Point3F start; + F32 * pStart = ((F32*)start); + + F32 lumelScale = 1.0 / (lGenX[si] * normLightmap->getWidth()); + + // get the start point on the lightmap + pStart[si] = (((xoff * lumelScale) / (1.0 / lGenX[si])) - lGenX[3] ) / lGenX[si]; + pStart[ti] = (((yoff * lumelScale) / (1.0 / lGenY[ti])) - lGenY[3] ) / lGenY[ti]; + pStart[axis] = ((pNormal[si] * pStart[si]) + (pNormal[ti] * pStart[ti]) + plane.d) / -pNormal[axis]; + + start.convolve(scale); + transform.mulP(start); + + // get the s/t vecs oriented on the surface + Point3F sVec; + Point3F tVec; + + F32 * pSVec = ((F32*)sVec); + F32 * pTVec = ((F32*)tVec); + + F32 angle; + Point3F planeNormal; + + // s + pSVec[si] = 1.f; + pSVec[ti] = 0.f; + + planeNormal = plane; + ((F32*)planeNormal)[ti] = 0.f; + planeNormal.normalize(); + + angle = mAcos(mClampF(((F32*)planeNormal)[axis], -1.f, 1.f)); + pSVec[axis] = (((F32*)planeNormal)[si] < 0.f) ? mTan(angle) : -mTan(angle); + + // t + pTVec[ti] = 1.f; + pTVec[si] = 0.f; + + planeNormal = plane; + ((F32*)planeNormal)[si] = 0.f; + planeNormal.normalize(); + + angle = mAcos(mClampF(((F32*)planeNormal)[axis], -1.f, 1.f)); + pTVec[axis] = (((F32*)planeNormal)[ti] < 0.f) ? mTan(angle) : -mTan(angle); + + // scale the vectors + + sVec *= lumelScale; + tVec *= lumelScale; + + // project vecs + transform.mulV(sVec); + sVec.convolve(scale); + + transform.mulV(tVec); + tVec.convolve(scale); + + Point3F & curPos = start; + Point3F sRun = sVec * xlen; + + // get the lexel area + Point3F cross; + mCross(sVec, tVec, &cross); + F32 maxLexelArea = cross.len(); + + const PlaneF & surfacePlane = shadowVolume.getPlane(surfaceInfo->mPlaneIndex); + + // get the world coordinate for each lexel + for(U32 y = 0; y < ylen; y++) + { + for(U32 x = 0; x < xlen; x++) + { + ShadowVolumeBSP::SVPoly * poly = shadowVolume.createPoly(); + poly->mPlane = surfacePlane; + poly->mWindingCount = 4; + + // set the poly indices + poly->mWinding[0] = curPos; + poly->mWinding[1] = curPos + sVec; + poly->mWinding[2] = curPos + sVec + tVec; + poly->mWinding[3] = curPos + tVec; + + //// insert poly which has been clipped to own shadow volume + //ShadowVolumeBSP::SVPoly * store = 0; + //shadowVolume.clipToSelf(surfaceInfo->mShadowVolume, &store, poly); + + //if(!store) + // continue; + + //F32 lexelArea = shadowVolume.getPolySurfaceArea(store); + //F32 area = shadowVolume.getLitSurfaceArea(store, surfaceInfo); + + F32 area = shadowVolume.getLitSurfaceArea(poly, surfaceInfo); + F32 shadowScale = mClampF(area / maxLexelArea, 0.f, 1.f); + + // get the color into U8 + ColorF tmp = (light->getColor() * dot * shadowScale) + ambient; + tmp.clamp(); + ColorI color = tmp; + + // attempt to light both normal and alarm lightmaps + for(U32 c = 0; c < 2; c++) + { + GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap; + if(!lightmap) + continue; + + ColorI outColor(0, 0, 255, 255); + +#ifndef SET_COLORS + ColorI lmColor(0, 0, 0, 255); + lightmap->getColor(xoff + x, yoff + y, lmColor); + + U32 _r = static_cast( color.red ) + static_cast( lmColor.red ); + U32 _g = static_cast( color.green ) + static_cast( lmColor.green ); + U32 _b = static_cast( color.blue ) + static_cast( lmColor.blue ); + + outColor.red = mClamp(_r, 0, 255); + outColor.green = mClamp(_g, 0, 255); + outColor.blue = mClamp(_b, 0, 255); +#endif + + lightmap->setColor(xoff + x, yoff + y, outColor); + } + + curPos += sVec; + } + + curPos -= sRun; + curPos += tVec; + } + } + } + + Con::printf(" = interior lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f); +} + +void blInteriorProxy::postLight(bool lastLight) +{ + delete mBoxShadowBSP; + mBoxShadowBSP = 0; + + InteriorInstance * interior = getObject(); + if(!interior) + return; +} + +//------------------------------------------------------------------------------ +U32 blInteriorProxy::getResourceCRC() +{ + InteriorInstance * interior = getObject(); + if(!interior) + return(0); + return(interior->getCRC()); +} + +//------------------------------------------------------------------------------ +bool blInteriorProxy::setPersistInfo(PersistInfo::PersistChunk * info) +{ + + if(!Parent::setPersistInfo(info)) + return(false); + + blInteriorChunk * chunk = dynamic_cast(info); + AssertFatal(chunk, "blInteriorProxy::setPersistInfo: invalid info chunk!"); + + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + U32 numDetails = interior->getNumDetailLevels(); + + // check the lighting method +// BTRTODO: Restore +// AssertFatal(SceneLighting::smUseVertexLighting == Interior::smUseVertexLighting, "blInteriorProxy::setPersistInfo: invalid vertex lighting state"); +// if(SceneLighting::smUseVertexLighting != Interior::smUseVertexLighting) +// return(false); + + /* + // process the vertex lighting... + if(chunk->mDetailVertexCount.size() != numDetails) + return(false); + + AssertFatal(chunk->mVertexColorsNormal.size(), "blInteriorProxy::setPersistInfo: invalid chunk info"); + AssertFatal(!chunk->mHasAlarmState || chunk->mVertexColorsAlarm.size(), "blInteriorProxy::setPersistInfo: invalid chunk info"); + AssertFatal(!(chunk->mHasAlarmState ^ interior->getDetailLevel(0)->hasAlarmState()), "blInteriorProxy::setPersistInfo: invalid chunk info"); + + + U32 curPos = 0; + for(U32 i = 0; i < numDetails; i++) + { + U32 count = chunk->mDetailVertexCount[i]; + Vector* normal = interior->getVertexColorsNormal(i); + Vector* alarm = interior->getVertexColorsAlarm(i); + AssertFatal(normal != NULL && alarm != NULL, "Error, bad vectors returned!"); + + normal->setSize(count); + dMemcpy(normal->address(), &chunk->mVertexColorsNormal[curPos], count * sizeof(ColorI)); + + if(chunk->mHasAlarmState) + { + alarm->setSize(count); + dMemcpy(alarm->address(), &chunk->mVertexColorsAlarm[curPos], count * sizeof(ColorI)); + } + + curPos += count; + } + */ + // need lightmaps? + //if(!SceneLighting::smUseVertexLighting) + // BTRTODO: Fix me + if (!false) + { + if(chunk->mDetailLightmapCount.size() != numDetails) + return(false); + + LM_HANDLE instanceHandle = interior->getLMHandle(); + U32 idx = 0; + + for(U32 i = 0; i < numDetails; i++) + { + Interior * detail = interior->getDetailLevel(i); + + LM_HANDLE interiorHandle = detail->getLMHandle(); + Vector & baseHandles = gInteriorLMManager.getHandles(interiorHandle, 0); + + if(chunk->mDetailLightmapCount[i] > baseHandles.size()) + return(false); + + for(U32 j = 0; j < chunk->mDetailLightmapCount[i]; j++) + { + U32 baseIndex = chunk->mDetailLightmapIndices[idx]; + if(baseIndex >= baseHandles.size()) + return(false); + + AssertFatal(chunk->mLightmaps[idx], "blInteriorProxy::setPersistInfo: bunk bitmap!"); + if(chunk->mLightmaps[idx]->getWidth() != baseHandles[baseIndex]->getWidth() || + chunk->mLightmaps[idx]->getHeight() != baseHandles[baseIndex]->getHeight()) + return(false); + + GFXTexHandle tHandle = gInteriorLMManager.duplicateBaseLightmap(interiorHandle, instanceHandle, baseIndex); + + // create the diff bitmap + tHandle->getBitmap()->combine( baseHandles[baseIndex]->getBitmap(), + chunk->mLightmaps[idx], + GFXTOPAdd ); + + idx++; + } + } + } + + return(true); +} + +bool blInteriorProxy::getPersistInfo(PersistInfo::PersistChunk * info) +{ + if(!Parent::getPersistInfo(info)) + return(false); + + blInteriorChunk* chunk = dynamic_cast(info); + AssertFatal(chunk, "blInteriorProxy::getPersistInfo: invalid info chunk!"); + + InteriorInstance * interior = getObject(); + if(!interior) + return(false); + + LM_HANDLE instanceHandle = interior->getLMHandle(); + + AssertFatal(!chunk->mDetailLightmapCount.size(), "blInteriorProxy::getPersistInfo: invalid array!"); + AssertFatal(!chunk->mDetailLightmapIndices.size(), "blInteriorProxy::getPersistInfo: invalid array!"); + AssertFatal(!chunk->mLightmaps.size(), "blInteriorProxy::getPersistInfo: invalid array!"); + + U32 numDetails = interior->getNumDetailLevels(); + U32 i; + for(i = 0; i < numDetails; i++) + { + Interior * detail = interior->getDetailLevel(i); + LM_HANDLE interiorHandle = detail->getLMHandle(); + + Vector & baseHandles = gInteriorLMManager.getHandles(interiorHandle, 0); + Vector & instanceHandles = gInteriorLMManager.getHandles(interiorHandle, instanceHandle); + + U32 litCount = 0; + + // walk all the instance lightmaps and grab diff lighting from them + for(U32 j = 0; j < instanceHandles.size(); j++) + { + if(!instanceHandles[j]) + continue; + + litCount++; + chunk->mDetailLightmapIndices.push_back(j); + + GBitmap * baseBitmap = baseHandles[j]->getBitmap(); + GBitmap * instanceBitmap = instanceHandles[j]->getBitmap(); + + Point2I extent(baseBitmap->getWidth(), baseBitmap->getHeight()); + + GBitmap * diffLightmap = new GBitmap(extent.x, extent.y, false); + + // diffLightmap = instanceBitmap - baseBitmap + diffLightmap->combine( instanceBitmap, baseBitmap, GFXTOPSubtract ); + + chunk->mLightmaps.push_back(diffLightmap); + } + + chunk->mDetailLightmapCount.push_back(litCount); + } + + // process the vertex lighting... + AssertFatal(!chunk->mDetailVertexCount.size(), "blInteriorProxy::getPersistInfo: invalid chunk info"); + AssertFatal(!chunk->mVertexColorsNormal.size(), "blInteriorProxy::getPersistInfo: invalid chunk info"); + AssertFatal(!chunk->mVertexColorsAlarm.size(), "blInteriorProxy::getPersistInfo: invalid chunk info"); + + chunk->mHasAlarmState = interior->getDetailLevel(0)->hasAlarmState(); + chunk->mDetailVertexCount.setSize(numDetails); + + U32 size = 0; + for(i = 0; i < numDetails; i++) + { + Interior * detail = interior->getDetailLevel(i); + + U32 count = detail->getWindingCount(); + chunk->mDetailVertexCount[i] = count; + size += count; + } + + /* + chunk->mVertexColorsNormal.setSize(size); + if(chunk->mHasAlarmState) + chunk->mVertexColorsAlarm.setSize(size); + + U32 curPos = 0; + for(i = 0; i < numDetails; i++) + { + Vector* normal = interior->getVertexColorsNormal(i); + Vector* alarm = interior->getVertexColorsAlarm(i); + AssertFatal(normal != NULL && alarm != NULL, "Error, no normal or alarm vertex colors!"); + + U32 count = chunk->mDetailVertexCount[i]; + dMemcpy(&chunk->mVertexColorsNormal[curPos], normal->address(), count * sizeof(ColorI)); + + if(chunk->mHasAlarmState) + dMemcpy(&chunk->mVertexColorsAlarm[curPos], alarm->address(), count * sizeof(ColorI)); + + curPos += count; + } + */ + + return(true); +} + + + +blInteriorSystem::blInteriorSystem() +{ + BLM->getSceneLightingInterface()->registerSystem(this); +} + +SceneLighting::ObjectProxy* blInteriorSystem::createObjectProxy(SceneObject* obj, SceneLighting::ObjectProxyList* sceneObjects) +{ + if ((obj->getTypeMask() & InteriorObjectType) != 0) + { + return new blInteriorProxy(obj); + } else { + return NULL; + } +} + +PersistInfo::PersistChunk* blInteriorSystem::createPersistChunk(const U32 chunkType) +{ + if (chunkType == PersistInfo::PersistChunk::InteriorChunkType) + { + return new blInteriorChunk; + } else { + return NULL; + } +} + +bool blInteriorSystem::createPersistChunkFromProxy(SceneLighting::ObjectProxy* objproxy, PersistInfo::PersistChunk **ret) +{ + if ((objproxy->mObj->getTypeMask() & InteriorObjectType) != 0) + { + *ret = new blInteriorChunk; + return true; + } else { + return false; + } +} + +void blInteriorSystem::init() +{ + +} + +U32 blInteriorSystem::addObjectType() +{ + return InteriorObjectType; +} + +U32 blInteriorSystem::addToClippingMask() +{ + return InteriorObjectType; +} + +void blInteriorSystem::processLightingBegin() +{ + // Store the vertex lighting state when we being lighting, we compare this when we finish lighting + smUseVertexLighting = Interior::smUseVertexLighting; +} + +void blInteriorSystem::processLightingCompleted(bool success) +{ + if(success) + { + AssertFatal(smUseVertexLighting == Interior::smUseVertexLighting, "SceneLighting::completed: vertex lighting state changed during scene light"); + + // cannot do anything if vertex state has changed (since we only load in what is needed) + if(smUseVertexLighting == Interior::smUseVertexLighting) + { + if(!smUseVertexLighting) + { + gInteriorLMManager.downloadGLTextures(); + gInteriorLMManager.destroyBitmaps(); + } + else + gInteriorLMManager.destroyTextures(); + } + } +} + +// Given a ray, this will return the color from the lightmap of this object, return true if handled +bool blInteriorSystem::getColorFromRayInfo(RayInfo collision, ColorF& result) +{ + InteriorInstance* interior = dynamic_cast(collision.object); + if (interior == NULL) + return false; + + interior->getRenderWorldTransform().mulP(collision.point); + Interior *detail = interior->getDetailLevel(0); + AssertFatal((detail), "SceneObject::getLightingAmbientColor: invalid interior"); + if(collision.face < detail->getSurfaceCount()) + { + const Interior::Surface &surface = detail->getSurface(collision.face); + const Interior::TexGenPlanes &texgen = detail->getLMTexGenEQ(collision.face); + + GBitmap* lightmap = gInteriorLMManager.getHandle(detail->getLMHandle(), + interior->getLMHandle(), detail->getNormalLMapIndex(collision.face)).getBitmap(); + if (!lightmap) + return false; + + Point2F uv; + uv.x = mDot(texgen.planeX, collision.point) + texgen.planeX.d; + uv.y = mDot(texgen.planeY, collision.point) + texgen.planeY.d; + + U32 size = (U32)(uv.x * F32(lightmap->getWidth())); + size = mClamp(size, surface.mapOffsetX, (surface.mapOffsetX + surface.mapSizeX)); + uv.x = F32(size) / F32(lightmap->getWidth()); + + size = (U32)(uv.y * F32(lightmap->getHeight())); + size = mClamp(size, surface.mapOffsetY, (surface.mapOffsetY + surface.mapSizeY)); + uv.y = F32(size) / F32(lightmap->getHeight()); + + result = lightmap->sampleTexel(uv.x, uv.y); + return true; + } + return false; +} + + +// Static factory object registers with lighting system plug-in system +static blInteriorSystem p_blInteriorSystem; diff --git a/lighting/basic/blTerrainSystem.cpp b/lighting/basic/blTerrainSystem.cpp new file mode 100644 index 0000000..6d38b68 --- /dev/null +++ b/lighting/basic/blTerrainSystem.cpp @@ -0,0 +1,730 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +//----------------------------------------------- +// Synapse Gaming - Lighting System +// Copyright © Synapse Gaming 2003 +// Written by John Kabus +//----------------------------------------------- +// +// This file contains the interface between the lighting kit and the legacy terrain rendering system. + +#include "platform/platform.h" + +#include "core/bitVector.h" +#include "lighting/common/shadowVolumeBSP.h" +#include "lighting/lightingInterfaces.h" +#include "terrain/terrData.h" +#include "lighting/basic/basicLightManager.h" +#include "lighting/common/sceneLighting.h" +#include "gfx/bitmap/gBitmap.h" + +extern SceneLighting* gLighting; + +// +// Lighting system interface +// +class blTerrainSystem : public SceneLightingInterface +{ +public: + blTerrainSystem(); + virtual void init(); + virtual U32 addObjectType(); + virtual SceneLighting::ObjectProxy* createObjectProxy(SceneObject* obj, SceneLighting::ObjectProxyList* sceneObjects); + virtual PersistInfo::PersistChunk* createPersistChunk(const U32 chunkType); + virtual bool createPersistChunkFromProxy(SceneLighting::ObjectProxy* objproxy, PersistInfo::PersistChunk **ret); + + // Given a ray, this will return the color from the lightmap of this object, return true if handled + virtual bool getColorFromRayInfo(const RayInfo & collision, ColorF& result) const; +}; + +struct blTerrainChunk : public PersistInfo::PersistChunk +{ + typedef PersistInfo::PersistChunk Parent; + + blTerrainChunk(); + ~blTerrainChunk(); + + GBitmap *mLightmap; + + bool read(Stream &); + bool write(Stream &); +}; + +//------------------------------------------------------------------------------ +// Class SceneLighting::TerrainChunk +//------------------------------------------------------------------------------ +blTerrainChunk::blTerrainChunk() +{ + mChunkType = PersistChunk::TerrainChunkType; + mLightmap = NULL; +} + +blTerrainChunk::~blTerrainChunk() +{ + if(mLightmap) + delete mLightmap; +} + +//------------------------------------------------------------------------------ + +bool blTerrainChunk::read(Stream & stream) +{ + if(!Parent::read(stream)) + return(false); + + mLightmap = new GBitmap(); + return mLightmap->readBitmap("png",stream); +} + +bool blTerrainChunk::write(Stream & stream) +{ + if(!Parent::write(stream)) + return(false); + + if(!mLightmap) + return(false); + + if(!mLightmap->writeBitmap("png",stream)) + return(false); + + return(true); +} + +class blTerrainProxy : public SceneLighting::ObjectProxy +{ +protected: + + typedef ObjectProxy Parent; + + BitVector mShadowMask; + ShadowVolumeBSP * mShadowVolume; + ColorF * mLightmap; + + /// The dimension of the lightmap in pixels. + const U32 mLightMapSize; + + /// The dimension of the terrain height map sample array. + const U32 mTerrainBlockSize; + + ColorF *sgBakedLightmap; + Vector sgLights; + bool sgMarkStaticShadow(void *terrainproxy, SceneObject *sceneobject, LightInfo *light); + //void postLight(bool lastLight); + + void lightVector(LightInfo *); + + struct SquareStackNode + { + U8 mLevel; + U16 mClipFlags; + Point2I mPos; + }; + + S32 testSquare(const Point3F &, const Point3F &, S32, F32, const Vector &); + bool markObjectShadow(ObjectProxy *); + bool sgIsCorrectStaticObjectType(SceneObject *obj); + + inline ColorF _getValue( S32 row, S32 column ); + +public: + + blTerrainProxy(SceneObject * obj); + ~blTerrainProxy(); + TerrainBlock * operator->() {return(static_cast(static_cast(mObj)));} + TerrainBlock * getObject() {return(static_cast(static_cast(mObj)));} + + bool getShadowedSquares(const Vector &, Vector &); + + // lighting + void init(); + bool preLight(LightInfo *); + void light(LightInfo *); + + // persist + U32 getResourceCRC(); + bool setPersistInfo(PersistInfo::PersistChunk *); + bool getPersistInfo(PersistInfo::PersistChunk *); + + virtual bool supportsShadowVolume(); + virtual void getClipPlanes(Vector& planes); + virtual void addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level); + + // events + //virtual void processTGELightProcessEvent(U32 curr, U32 max, LightInfo* currlight); + //virtual void processSGObjectProcessEvent(LightInfo* currLight); +}; + +//------------------------------------------------------------------------------- +// Class SceneLighting::TerrainProxy: +//------------------------------------------------------------------------------- +blTerrainProxy::blTerrainProxy( SceneObject *obj ) : + Parent( obj ), + mLightMapSize( getObject()->getLightMapSize() ), + mTerrainBlockSize( getObject()->getBlockSize() ), + mLightmap( NULL ) +{ +} + +blTerrainProxy::~blTerrainProxy() +{ + delete [] mLightmap; +} + +//------------------------------------------------------------------------------- +void blTerrainProxy::init() +{ + mLightmap = new ColorF[ mLightMapSize * mLightMapSize ]; + dMemset(mLightmap, 0, mLightMapSize * mLightMapSize * sizeof(ColorF)); + mShadowMask.setSize( mTerrainBlockSize * mTerrainBlockSize ); +} + +bool blTerrainProxy::preLight(LightInfo * light) +{ + if(!bool(mObj)) + return(false); + + if(light->getType() != LightInfo::Vector) + return(false); + + mShadowMask.clear(); + return(true); +} + +inline ColorF blTerrainProxy::_getValue( S32 row, S32 column ) +{ + while( row < 0 ) + row += mLightMapSize; + row = row % mLightMapSize; + + while( column < 0 ) + column += mLightMapSize; + column = column % mLightMapSize; + + U32 offset = row * mLightMapSize + column; + + return mLightmap[offset]; +} + +bool blTerrainProxy::markObjectShadow(ObjectProxy * proxy) +{ + if (!proxy->supportsShadowVolume()) + return false; + + // setup the clip planes + Vector clipPlanes; + proxy->getClipPlanes(clipPlanes); + + Vector shadowList; + if(!getShadowedSquares(clipPlanes, shadowList)) + return(false); + + // set the correct bit + for(U32 i = 0; i < shadowList.size(); i++) + mShadowMask.set(shadowList[i]); + + return(true); +} + +void blTerrainProxy::light(LightInfo * light) +{ + // If we don't have terrain or its not a directional + // light then skip processing. + TerrainBlock * terrain = getObject(); + if ( !terrain || light->getType() != LightInfo::Vector ) + return; + + S32 time = Platform::getRealMilliseconds(); + + // reset + mShadowVolume = new ShadowVolumeBSP; + + // build interior shadow volume + for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++) + { + ObjectProxy* objproxy = *itr; + if (markObjectShadow(objproxy)) + objproxy->addToShadowVolume(mShadowVolume, light, SceneLighting::SHADOW_DETAIL); + } + + lightVector(light); + + // set the lightmap... + terrain->clearLightMap(); + + // Blur... + F32 kernel[3][3] = { {1, 2, 1}, + {2, 3, 2}, + {1, 2, 1} }; + + F32 modifier = 1; + F32 divisor = 0; + + + for( U32 i=0; i<3; i++ ) + { + for( U32 j=0; j<3; j++ ) + { + if( i==1 && j==1 ) + { + kernel[i][j] = 1 + kernel[i][j] * modifier; + } + else + { + kernel[i][j] = kernel[i][j] * modifier; + } + + divisor += kernel[i][j]; + } + } + + for( U32 i=0; i < mLightMapSize; i++ ) + { + for( U32 j=0; j < mLightMapSize; j++ ) + { + + ColorF val; + val = _getValue( i-1, j-1 ) * kernel[0][0]; + val += _getValue( i-1, j ) * kernel[0][1]; + val += _getValue( i-1, j+1 ) * kernel[0][2]; + val += _getValue( i, j-1 ) * kernel[1][0]; + val += _getValue( i, j ) * kernel[1][1]; + val += _getValue( i, j+1 ) * kernel[1][2]; + val += _getValue( i+1, j-1 ) * kernel[2][0]; + val += _getValue( i+1, j ) * kernel[2][1]; + val += _getValue( i+1, j+1 ) * kernel[2][2]; + + U32 edge = 0; + + if( j == 0 || j == mLightMapSize - 1 ) + edge++; + + if( i == 0 || i == mLightMapSize - 1 ) + edge++; + + if( !edge ) + val = val / divisor; + else + val = mLightmap[ i * mLightMapSize + j ]; + + // clamp values + mLightmap[ i * mLightMapSize + j ]= val; + } + } + + // And stuff it into the texture... + GBitmap *terrLightMap = terrain->getLightMap(); + for(U32 y = 0; y < mLightMapSize; y++) + { + for(U32 x = 0; x < mLightMapSize; x++) + { + ColorI color(255, 255, 255, 255); + + color.red = mLightmap[x + y * mLightMapSize].red * 255; + color.green = mLightmap[x + y * mLightMapSize].green * 255; + color.blue = mLightmap[x + y * mLightMapSize].blue * 255; + + terrLightMap->setColor(x, y, color); + } + } + + /* + // This handles matching up the outer edges of the terrain + // lightmap when it has neighbors + if (!terrain->isTiling()) + { + for (S32 y = 0; y < terrLightMap->getHeight(); y++) + { + ColorI c; + if (terrain->getFile()->mEdgeTerrainFiles[0]) + { + terrLightMap->getColor(terrLightMap->getWidth()-1,y,c); + terrLightMap->setColor(0,y,c); + terrLightMap->setColor(1,y,c); + } + else + { + terrLightMap->getColor(0,y,c); + terrLightMap->setColor(terrLightMap->getWidth()-1,y,c); + terrLightMap->setColor(terrLightMap->getWidth()-2,y,c); + } + } + + for (S32 x = 0; x < terrLightMap->getHeight(); x++) + { + ColorI c; + if (terrain->getFile()->mEdgeTerrainFiles[1]) + { + terrLightMap->getColor(x,terrLightMap->getHeight()-1,c); + terrLightMap->setColor(x,0,c); + terrLightMap->setColor(x,1,c); + } + else + { + terrLightMap->getColor(x,0,c); + terrLightMap->setColor(x,terrLightMap->getHeight()-1,c); + terrLightMap->setColor(x,terrLightMap->getHeight()-2,c); + } + } + } + */ + + delete mShadowVolume; + + Con::printf(" = terrain lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f); +} + +//------------------------------------------------------------------------------ +S32 blTerrainProxy::testSquare(const Point3F & min, const Point3F & max, S32 mask, F32 expand, const Vector & clipPlanes) +{ + expand = 0; + S32 retMask = 0; + Point3F minPoint, maxPoint; + for(S32 i = 0; i < clipPlanes.size(); i++) + { + if(mask & (1 << i)) + { + if(clipPlanes[i].x > 0) + { + maxPoint.x = max.x; + minPoint.x = min.x; + } + else + { + maxPoint.x = min.x; + minPoint.x = max.x; + } + if(clipPlanes[i].y > 0) + { + maxPoint.y = max.y; + minPoint.y = min.y; + } + else + { + maxPoint.y = min.y; + minPoint.y = max.y; + } + if(clipPlanes[i].z > 0) + { + maxPoint.z = max.z; + minPoint.z = min.z; + } + else + { + maxPoint.z = min.z; + minPoint.z = max.z; + } + F32 maxDot = mDot(maxPoint, clipPlanes[i]); + F32 minDot = mDot(minPoint, clipPlanes[i]); + F32 planeD = clipPlanes[i].d; + if(maxDot <= -(planeD + expand)) + return(U16(-1)); + if(minDot <= -planeD) + retMask |= (1 << i); + } + } + return(retMask); +} + +bool blTerrainProxy::getShadowedSquares(const Vector & clipPlanes, Vector & shadowList) +{ + TerrainBlock *terrain = getObject(); + if ( !terrain ) + return false; + + // TODO: Fix me for variable terrain sizes! + return true; + + /* + SquareStackNode stack[TerrainBlock::BlockShift * 4]; + + stack[0].mLevel = TerrainBlock::BlockShift; + stack[0].mClipFlags = 0xff; + stack[0].mPos.set(0,0); + + U32 stackSize = 1; + + Point3F blockPos; + terrain->getTransform().getColumn(3, &blockPos); + S32 squareSize = terrain->getSquareSize(); + F32 floatSquareSize = (F32)squareSize; + + bool marked = false; + + // push through all the levels of the quadtree + while(stackSize) + { + SquareStackNode * node = &stack[stackSize - 1]; + + S32 clipFlags = node->mClipFlags; + Point2I pos = node->mPos; + GridSquare * sq = terrain->findSquare(node->mLevel, pos); + + Point3F minPoint, maxPoint; + minPoint.set(squareSize * pos.x + blockPos.x, + squareSize * pos.y + blockPos.y, + fixedToFloat(sq->minHeight)); + maxPoint.set(minPoint.x + (squareSize << node->mLevel), + minPoint.y + (squareSize << node->mLevel), + fixedToFloat(sq->maxHeight)); + + // test the square against the current level + if(clipFlags) + { + clipFlags = testSquare(minPoint, maxPoint, clipFlags, floatSquareSize, clipPlanes); + if(clipFlags == U16(-1)) + { + stackSize--; + continue; + } + } + + // shadowed? + if(node->mLevel == 0) + { + marked = true; + shadowList.push_back(pos.x + (pos.y << TerrainBlock::BlockShift)); + stackSize--; + continue; + } + + // setup the next level of squares + U8 nextLevel = node->mLevel - 1; + S32 squareHalfSize = 1 << nextLevel; + + for(U32 i = 0; i < 4; i++) + { + node[i].mLevel = nextLevel; + node[i].mClipFlags = clipFlags; + } + + node[3].mPos = pos; + node[2].mPos.set(pos.x + squareHalfSize, pos.y); + node[1].mPos.set(pos.x, pos.y + squareHalfSize); + node[0].mPos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); + + stackSize += 3; + } + + return marked; + */ +} + +void blTerrainProxy::lightVector(LightInfo * light) +{ + // Grab our terrain object + TerrainBlock* terrain = getObject(); + if (!terrain) + return; + + // Get the direction to the light (the inverse of the direction + // the light is pointing) + Point3F lightDir = -light->getDirection(); + lightDir.normalize(); + + // Get the ratio between the light map pixel and world space (used below) + F32 lmTerrRatio = (F32)mTerrainBlockSize / (F32) mLightMapSize; + lmTerrRatio *= terrain->getSquareSize(); + + // Get the terrain position + Point3F terrPos( terrain->getTransform().getPosition() ); + + U32 i = 0; + for (U32 y = 0; y < mLightMapSize; y++) + { + for (U32 x = 0; x < mLightMapSize; x++) + { + // Get the relative pixel position and scale it + // by the ratio between lightmap and world space + Point2F pixelPos(x, y); + pixelPos *= lmTerrRatio; + + // Start with a default normal of straight up + Point3F normal(0.0f, 0.0f, 1.0f); + + // Try to get the actual normal from the terrain. + // Note: this won't change the default normal if + // it can't find a normal. + terrain->getNormal(pixelPos, &normal); + + // The terrain lightmap only contains shadows. + F32 shadowed = 0.0f; + + // Get the height at the lightmap pixel's position + F32 height = 0.0f; + terrain->getHeight(pixelPos, &height); + + // Calculate the 3D position of the pixel + Point3F pixelPos3F(pixelPos.x, pixelPos.y, height); + + // Translate that position by the terrain's transform + terrain->getTransform().mulP(pixelPos3F); + + // Offset slighting along the normal so that we don't + // raycast into ourself + pixelPos3F += (normal * 0.1f); + + // Calculate the light's position. + // If it is a vector light like the sun (no position + // just direction) then translate along that direction + // a reasonable distance to get a point sufficiently + // far away + Point3F lightPos = light->getPosition(); + if(light->getType() == LightInfo::Vector) + { + lightPos = 1000.f * lightDir; + lightPos = pixelPos3F + lightPos; + } + + // Cast a ray from the world space position of the lightmap pixel to the light source. + // If we hit something then we are in shadow. This allows us to be shadowed by anything + // that supports a castRay operation. + RayInfo info; + if(terrain->getContainer()->castRay(pixelPos3F, lightPos, STATIC_COLLISION_MASK, &info)) + { + // Shadow the pixel. + shadowed = 1.0f; + } + + // Set the final lightmap color. + mLightmap[i++] += ColorF::WHITE * mClampF( 1.0f - shadowed, 0.0f, 1.0f ); + } + } +} + +//-------------------------------------------------------------------------- +U32 blTerrainProxy::getResourceCRC() +{ + TerrainBlock * terrain = getObject(); + if(!terrain) + return(0); + return(terrain->getCRC()); +} + +//-------------------------------------------------------------------------- +bool blTerrainProxy::setPersistInfo(PersistInfo::PersistChunk * info) +{ + if(!Parent::setPersistInfo(info)) + return(false); + + blTerrainChunk * chunk = dynamic_cast(info); + AssertFatal(chunk, "blTerrainProxy::setPersistInfo: invalid info chunk!"); + + TerrainBlock * terrain = getObject(); + if(!terrain || !terrain->getLightMap()) + return(false); + + terrain->setLightMap( new GBitmap( *chunk->mLightmap) ); + + return(true); +} + +bool blTerrainProxy::getPersistInfo(PersistInfo::PersistChunk * info) +{ + if(!Parent::getPersistInfo(info)) + return(false); + + blTerrainChunk * chunk = dynamic_cast(info); + AssertFatal(chunk, "blTerrainProxy::getPersistInfo: invalid info chunk!"); + + TerrainBlock * terrain = getObject(); + if(!terrain || !terrain->getLightMap()) + return(false); + + if(chunk->mLightmap) delete chunk->mLightmap; + + chunk->mLightmap = new GBitmap(*terrain->getLightMap()); + + return(true); +} + +bool blTerrainProxy::supportsShadowVolume() +{ + return false; +} + +void blTerrainProxy::getClipPlanes(Vector& planes) +{ + +} + +void blTerrainProxy::addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level) +{ + +} + + +// +// blTerrainSystem +// +blTerrainSystem::blTerrainSystem() +{ + BLM->getSceneLightingInterface()->registerSystem( this ); +} + +void blTerrainSystem::init() +{ +} + +U32 blTerrainSystem::addObjectType() +{ + return TerrainObjectType; +} + +SceneLighting::ObjectProxy* blTerrainSystem::createObjectProxy(SceneObject* obj, SceneLighting::ObjectProxyList* sceneObjects) +{ + if ((obj->getTypeMask() & TerrainObjectType) != 0) + return new blTerrainProxy(obj); + else + return NULL; +} + +PersistInfo::PersistChunk* blTerrainSystem::createPersistChunk(const U32 chunkType) +{ + if (chunkType == PersistInfo::PersistChunk::TerrainChunkType) + return new blTerrainChunk(); + else + return NULL; +} + +bool blTerrainSystem::createPersistChunkFromProxy(SceneLighting::ObjectProxy* objproxy, PersistInfo::PersistChunk **ret) +{ + if (dynamic_cast(objproxy) != NULL) + { + *ret = new blTerrainChunk(); + return true; + } else { + return NULL; + } +} + +// Given a ray, this will return the color from the lightmap of this object, return true if handled +bool blTerrainSystem::getColorFromRayInfo(const RayInfo & collision, ColorF& result) const +{ + TerrainBlock *terrain = dynamic_cast(collision.object); + if (!terrain) + return false; + + Point2F uv; + F32 terrainlength = (F32)terrain->getBlockSize(); + Point3F pos = terrain->getPosition(); + uv.x = (collision.point.x - pos.x) / terrainlength; + uv.y = (collision.point.y - pos.y) / terrainlength; + + // similar to x = x & width... + uv.x = uv.x - F32(U32(uv.x)); + uv.y = uv.y - F32(U32(uv.y)); + const GBitmap* lightmap = terrain->getLightMap(); + if (!lightmap) + return false; + + result = lightmap->sampleTexel(uv.x, uv.y); + // terrain lighting is dim - look into this (same thing done in shaders)... + result *= 2.0f; + return true; +} + +// TODO: Review this... the BasicLightManager should probably +// be the one to create this thing! +static blTerrainSystem p_blTerrainSystem; \ No newline at end of file diff --git a/lighting/common/blobShadow.cpp b/lighting/common/blobShadow.cpp new file mode 100644 index 0000000..dd19df0 --- /dev/null +++ b/lighting/common/blobShadow.cpp @@ -0,0 +1,335 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/common/blobShadow.h" + +#include "gfx/primBuilder.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/bitmap/gBitmap.h" +#include "math/mathUtils.h" +#include "lighting/lightInfo.h" +#include "lighting/lightingInterfaces.h" +#include "T3D/shapeBase.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightManager.h" +#include "ts/tsMesh.h" + +DepthSortList BlobShadow::smDepthSortList; +GFXTexHandle BlobShadow::smGenericShadowTexture = NULL; +S32 BlobShadow::smGenericShadowDim = 32; +U32 BlobShadow::smShadowMask = TerrainObjectType | InteriorObjectType; +F32 BlobShadow::smGenericRadiusSkew = 0.4f; // shrink radius of shape when it always uses generic shadow... + +Box3F gBlobShadowBox; +SphereF gBlobShadowSphere; +Point3F gBlobShadowPoly[4]; + +//-------------------------------------------------------------- + +BlobShadow::BlobShadow(SceneObject* parentObject, LightInfo* light, TSShapeInstance* shapeInstance) +{ + mParentObject = parentObject; + mShapeBase = dynamic_cast(parentObject); + mParentLight = light; + mShapeInstance = shapeInstance; + mRadius = 0.0f; + mLastRenderTime = 0; + mDepthBias = -0.0002f; + + generateGenericShadowBitmap(smGenericShadowDim); + setupStateBlocks(); +} + +void BlobShadow::setupStateBlocks() +{ + GFXStateBlockDesc sh; + sh.cullDefined = true; + sh.cullMode = GFXCullNone; + sh.zDefined = true; + sh.zEnable = true; + sh.zWriteEnable = false; + + sh.zBias = mDepthBias; + sh.blendDefined = true; + sh.blendEnable = true; + sh.blendSrc = GFXBlendSrcAlpha; + sh.blendDest = GFXBlendInvSrcAlpha; + sh.alphaDefined = true; + sh.alphaTestEnable = true; + sh.alphaTestFunc = GFXCmpGreater; + sh.alphaTestRef = 0; + sh.samplersDefined = true; + sh.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + mShadowSB = GFX->createStateBlock(sh); +} + +BlobShadow::~BlobShadow() +{ + mShadowBuffer = NULL; +} + +bool BlobShadow::shouldRender(F32 camDist) +{ + Point3F lightDir; + + if (mShapeBase && mShapeBase->getFadeVal() < TSMesh::VISIBILITY_EPSILON) + return false; + + F32 shadowLen = 10.0f * mShapeInstance->getShape()->radius; + Point3F pos = mShapeInstance->getShape()->center; + + // this is a bit of a hack...move generic shadows towards feet/base of shape + pos *= 0.5f; + pos.convolve(mParentObject->getScale()); + mParentObject->getRenderTransform().mulP(pos); + if(mParentLight->getType() == LightInfo::Vector) + { + lightDir = mParentLight->getDirection(); + } + else + { + lightDir = pos - mParentLight->getPosition(); + lightDir.normalize(); + } + + // pos is where shadow will be centered (in world space) + setRadius(mShapeInstance, mParentObject->getScale()); + bool render = prepare(pos, lightDir, shadowLen); + return render; +} + +void BlobShadow::generateGenericShadowBitmap(S32 dim) +{ + if(smGenericShadowTexture) + return; + GBitmap * bitmap = new GBitmap(dim,dim,false,GFXFormatR8G8B8A8); + U8 * bits = bitmap->getWritableBits(); + dMemset(bits, 0, dim*dim*4); + S32 center = dim >> 1; + F32 invRadiusSq = 1.0f / (F32)(center*center); + F32 tmpF; + for (S32 i=0; i0.99f ? 0 : (U8)(180.0f*(1.0f-tmpF)); // 180 out of 255 max + bits[(i*dim*4)+(j*4)+3] = val; + } + } + + smGenericShadowTexture.set( bitmap, &GFXDefaultStaticDiffuseProfile, true, "BlobShadow" ); +} + +//-------------------------------------------------------------- + +void BlobShadow::setLightMatrices(const Point3F & lightDir, const Point3F & pos) +{ + AssertFatal(mDot(lightDir,lightDir)>0.0001f,"BlobShadow::setLightDir: light direction must be a non-zero vector."); + + // construct light matrix + Point3F x,z; + if (mFabs(lightDir.z)>0.001f) + { + // mCross(Point3F(1,0,0),lightDir,&z); + z.x = 0.0f; + z.y = lightDir.z; + z.z = -lightDir.y; + z.normalize(); + mCross(lightDir,z,&x); + } + else + { + mCross(lightDir,Point3F(0,0,1),&x); + x.normalize(); + mCross(x,lightDir,&z); + } + + mLightToWorld.identity(); + mLightToWorld.setColumn(0,x); + mLightToWorld.setColumn(1,lightDir); + mLightToWorld.setColumn(2,z); + mLightToWorld.setColumn(3,pos); + + mWorldToLight = mLightToWorld; + mWorldToLight.inverse(); +} + +void BlobShadow::setRadius(F32 radius) +{ + mRadius = radius; +} + +void BlobShadow::setRadius(TSShapeInstance * shapeInstance, const Point3F & scale) +{ + const Box3F & bounds = shapeInstance->getShape()->bounds; + F32 dx = 0.5f * (bounds.maxExtents.x-bounds.minExtents.x) * scale.x; + F32 dy = 0.5f * (bounds.maxExtents.y-bounds.minExtents.y) * scale.y; + F32 dz = 0.5f * (bounds.maxExtents.z-bounds.minExtents.z) * scale.z; + mRadius = mSqrt(dx*dx+dy*dy+dz*dz); +} + + +//-------------------------------------------------------------- + +bool BlobShadow::prepare(const Point3F & pos, Point3F lightDir, F32 shadowLen) +{ + if (mPartition.empty()) + { + // -------------------------------------- + // 1. + F32 dirMult = (1.0f) * (1.0f); + if (dirMult < 0.99f) + { + lightDir.z *= dirMult; + lightDir.z -= 1.0f - dirMult; + } + lightDir.normalize(); + shadowLen *= (1.0f) * (1.0f); + + // -------------------------------------- + // 2. get polys + F32 radius = mRadius; + radius *= smGenericRadiusSkew; + buildPartition(pos,lightDir,radius,shadowLen); + } + if (mPartition.empty()) + // no need to draw shadow if nothing to cast it onto + return false; + + return true; +} + +//-------------------------------------------------------------- + +void BlobShadow::buildPartition(const Point3F & p, const Point3F & lightDir, F32 radius, F32 shadowLen) +{ + setLightMatrices(lightDir,p); + + Point3F extent(2.0f*radius,shadowLen,2.0f*radius); + smDepthSortList.clear(); + smDepthSortList.set(mWorldToLight,extent); + smDepthSortList.setInterestNormal(lightDir); + + if (shadowLen<1.0f) + // no point in even this short of a shadow... + shadowLen = 1.0f; + mInvShadowDistance = 1.0f / shadowLen; + + // build world space box and sphere around shadow + + Point3F x,y,z; + mLightToWorld.getColumn(0,&x); + mLightToWorld.getColumn(1,&y); + mLightToWorld.getColumn(2,&z); + x *= radius; + y *= shadowLen; + z *= radius; + gBlobShadowBox.maxExtents.set(mFabs(x.x)+mFabs(y.x)+mFabs(z.x), + mFabs(x.y)+mFabs(y.y)+mFabs(z.y), + mFabs(x.z)+mFabs(y.z)+mFabs(z.z)); + y *= 0.5f; + gBlobShadowSphere.radius = gBlobShadowBox.maxExtents.len(); + gBlobShadowSphere.center = p + y; + gBlobShadowBox.minExtents = y + p - gBlobShadowBox.maxExtents; + gBlobShadowBox.maxExtents += y + p; + + // get polys + + gClientContainer.findObjects(STATIC_COLLISION_MASK, BlobShadow::collisionCallback, this); + + // setup partition list + gBlobShadowPoly[0].set(-radius,0,-radius); + gBlobShadowPoly[1].set(-radius,0, radius); + gBlobShadowPoly[2].set( radius,0, radius); + gBlobShadowPoly[3].set( radius,0,-radius); + + mPartition.clear(); + mPartitionVerts.clear(); + smDepthSortList.depthPartition(gBlobShadowPoly,4,mPartition,mPartitionVerts); + + if(mPartitionVerts.empty()) + return; + + // Find the rough distance of the shadow verts + // from the object position and use that to scale + // the visibleAlpha so that the shadow fades out + // the further away from you it gets + F32 dist = 0.0f; + + // Calculate the center of the partition verts + Point3F shadowCenter(0.0f, 0.0f, 0.0f); + for (U32 i = 0; i < mPartitionVerts.size(); i++) + shadowCenter += mPartitionVerts[i]; + + shadowCenter /= mPartitionVerts.size(); + + mLightToWorld.mulP(shadowCenter); + + dist = (p - shadowCenter).len(); + + // now set up tverts & colors + mShadowBuffer.set(GFX, mPartitionVerts.size(), GFXBufferTypeVolatile); + mShadowBuffer.lock(); + + F32 visibleAlpha = 255.0f; + if (mShapeBase && mShapeBase->getFadeVal()) + visibleAlpha = mClampF(255.0f * mShapeBase->getFadeVal(), 0, 255); + visibleAlpha *= 1.0f - (dist / gBlobShadowSphere.radius); + F32 invRadius = 1.0f / radius; + for (S32 i=0; igetWorldBox().isOverlapped(gBlobShadowBox)) + { + // only interiors clip... + ClippedPolyList::allowClipping = (obj->getTypeMask() & gClientSceneGraph->getLightManager()->getSceneLightingInterface()->mClippingMask) != 0; + obj->buildPolyList(&smDepthSortList,gBlobShadowBox,gBlobShadowSphere); + ClippedPolyList::allowClipping = true; + } +} + +//-------------------------------------------------------------- + +void BlobShadow::render( F32 camDist, const TSRenderState &rdata ) +{ + mLastRenderTime = Platform::getRealMilliseconds(); + GFX->pushWorldMatrix(); + MatrixF world = GFX->getWorldMatrix(); + world.mul(mLightToWorld); + GFX->setWorldMatrix(world); + + GFX->disableShaders(); + + GFX->setStateBlock(mShadowSB); + GFX->setTexture(0, smGenericShadowTexture); + GFX->setVertexBuffer(mShadowBuffer); + + for(U32 p=0; pdrawPrimitive(GFXTriangleFan, mPartition[p].vertexStart, (mPartition[p].vertexCount - 2)); + + // This is a bad nasty hack which forces the shadow to reconstruct itself every frame. + mPartition.clear(); + + GFX->popWorldMatrix(); +} + +void BlobShadow::deleteGenericShadowBitmap() +{ + smGenericShadowTexture = NULL; +} diff --git a/lighting/common/blobShadow.h b/lighting/common/blobShadow.h new file mode 100644 index 0000000..f56263a --- /dev/null +++ b/lighting/common/blobShadow.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BLOBSHADOW_H_ +#define _BLOBSHADOW_H_ + +#include "collision/depthSortList.h" +#include "sceneGraph/sceneObject.h" +#include "ts/tsShapeInstance.h" +#include "lighting/common/shadowBase.h" + +class ShapeBase; +class LightInfo; + +class BlobShadow : public ShadowBase +{ + F32 mRadius; + F32 mInvShadowDistance; + MatrixF mLightToWorld; + MatrixF mWorldToLight; + + Vector mPartition; + Vector mPartitionVerts; + GFXVertexBufferHandle mShadowBuffer; + + static U32 smShadowMask; + + static DepthSortList smDepthSortList; + static GFXTexHandle smGenericShadowTexture; + static F32 smGenericRadiusSkew; + static S32 smGenericShadowDim; + + U32 mLastRenderTime; + + static void collisionCallback(SceneObject*,void *); + +private: + SceneObject* mParentObject; + ShapeBase* mShapeBase; + LightInfo* mParentLight; + TSShapeInstance* mShapeInstance; + GFXStateBlockRef mShadowSB; + F32 mDepthBias; + + void setupStateBlocks(); + void setLightMatrices(const Point3F & lightDir, const Point3F & pos); + void buildPartition(const Point3F & p, const Point3F & lightDir, F32 radius, F32 shadowLen); +public: + + BlobShadow(SceneObject* parentobject, LightInfo* light, TSShapeInstance* shapeinstance); + ~BlobShadow(); + + void setRadius(F32 radius); + void setRadius(TSShapeInstance *, const Point3F & scale); + + bool prepare(const Point3F & pos, Point3F lightDir, F32 shadowLen); + + bool shouldRender(F32 camDist); + + void update( const SceneState *state ) {} + void render( F32 camDist, const TSRenderState &rdata ); + U32 getLastRenderTime() const { return mLastRenderTime; } + + static void generateGenericShadowBitmap(S32 dim); + static void deleteGenericShadowBitmap(); +}; + +#endif \ No newline at end of file diff --git a/lighting/common/lightMapParams.cpp b/lighting/common/lightMapParams.cpp new file mode 100644 index 0000000..eb1d0de --- /dev/null +++ b/lighting/common/lightMapParams.cpp @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "lighting/common/lightMapParams.h" +#include "core/stream/bitStream.h" + +const LightInfoExType LightMapParams::Type( "LightMapParams" ); + +LightMapParams::LightMapParams( LightInfo *light ) : + representedInLightmap(false), + includeLightmappedGeometryInShadow(false), + shadowDarkenColor(0.0f, 0.0f, 0.0f, -1.0f) +{ + +} + +LightMapParams::~LightMapParams() +{ + +} + +void LightMapParams::set( const LightInfoEx *ex ) +{ + // TODO: Do we even need this? +} + +void LightMapParams::packUpdate( BitStream *stream ) const +{ + stream->writeFlag(representedInLightmap); + stream->writeFlag(includeLightmappedGeometryInShadow); + stream->write(shadowDarkenColor); +} + +void LightMapParams::unpackUpdate( BitStream *stream ) +{ + representedInLightmap = stream->readFlag(); + includeLightmappedGeometryInShadow = stream->readFlag(); + stream->read(&shadowDarkenColor); + + // Always make sure that the alpha value of the shadowDarkenColor is -1.0 + shadowDarkenColor.alpha = -1.0f; +} \ No newline at end of file diff --git a/lighting/common/lightMapParams.h b/lighting/common/lightMapParams.h new file mode 100644 index 0000000..4285c61 --- /dev/null +++ b/lighting/common/lightMapParams.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTMAPPARAMS_H_ +#define _LIGHTMAPPARAMS_H_ + +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif + +class LightMapParams : public LightInfoEx +{ +public: + LightMapParams( LightInfo *light ); + virtual ~LightMapParams(); + + /// The LightInfoEx hook type. + static const LightInfoExType Type; + + // LightInfoEx + virtual void set( const LightInfoEx *ex ); + virtual const LightInfoExType& getType() const { return Type; } + virtual void packUpdate( BitStream *stream ) const; + virtual void unpackUpdate( BitStream *stream ); + +public: + // We're leaving these public for easy access + // for console protected fields. + + bool representedInLightmap; ///< This light is represented in lightmaps (static light, default: false) + ColorF shadowDarkenColor; ///< The color that should be used to multiply-blend dynamic shadows onto lightmapped geometry (ignored if 'representedInLightmap' is false) + bool includeLightmappedGeometryInShadow; ///< This light should render lightmapped geometry during its shadow-map update (ignored if 'representedInLightmap' is false) +}; + +#endif diff --git a/lighting/common/projectedShadow.cpp b/lighting/common/projectedShadow.cpp new file mode 100644 index 0000000..1ed9cf6 --- /dev/null +++ b/lighting/common/projectedShadow.cpp @@ -0,0 +1,536 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/common/projectedShadow.h" + +#include "gfx/primBuilder.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/bitmap/gBitmap.h" +#include "gfx/gfxDebugEvent.h" +#include "math/mathUtils.h" +#include "lighting/lightInfo.h" +#include "lighting/lightingInterfaces.h" +#include "T3D/shapeBase.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightManager.h" +//#include "lighting/shadowMap/lightShadowMap.h" +#include "ts/tsMesh.h" +#include "T3D/decal/decalManager.h" +#include "T3D/decal/decalInstance.h" +#include "renderInstance/renderPassManager.h" +#include "renderInstance/renderMeshMgr.h" +#include "gfx/gfxTransformSaver.h" +#include "materials/customMaterialDefinition.h" +#include "materials/materialFeatureTypes.h" +#include "console/console.h" +#include "postFx/postEffect.h" +#include "lighting/basic/basicLightManager.h" +#include "materials/materialManager.h" + +SimObjectPtr ProjectedShadow::smRenderPass = NULL; +F32 ProjectedShadow::smDepthAdjust = 10.0f; + + +GFX_ImplementTextureProfile( BLProjectedShadowProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::RenderTarget | + GFXTextureProfile::Pooled, + GFXTextureProfile::None ); + +GFX_ImplementTextureProfile( BLProjectedShadowZProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::ZTarget | + GFXTextureProfile::Pooled, + GFXTextureProfile::None ); + +//-------------------------------------------------------------- + +ProjectedShadow::ProjectedShadow( SceneObject *object ) +{ + mParentObject = object; + mShapeBase = dynamic_cast( object ); + + mRadius = 0; + mLastRenderTime = 0; + mUpdateTexture = false; + + mShadowLength = 10.0f; + + mDecalData = new DecalData; + mDecalData->startPixRadius = 200.0f; + mDecalData->endPixRadius = 35.0f; + + mDecalInstance = NULL; + + mLastLightDir.set( 0, 0, 0 ); + mLastObjectPosition.set( object->getRenderPosition() ); + mLastObjectScale.set( object->getScale() ); + + CustomMaterial *customMat = NULL; + Sim::findObject( "BL_ProjectedShadowMaterial", customMat ); + if ( customMat ) + { + mDecalData->material = customMat; + mDecalData->matInst = customMat->createMatInstance(); + } + else + mDecalData->matInst = MATMGR->createMatInstance( "WarningMaterial" ); + + mDecalData->matInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); + + mCasterPositionSC = NULL; + mShadowLengthSC = NULL; +} + +ProjectedShadow::~ProjectedShadow() +{ + if ( mDecalInstance ) + gDecalManager->removeDecal( mDecalInstance ); + + delete mDecalData; + + mShadowTexture = NULL; + mRenderTarget = NULL; +} + +bool ProjectedShadow::shouldRender( const SceneState *state ) +{ + // Don't render if our object has been removed from the + // scene graph. + + if( !mParentObject->getSceneGraph() ) + return false; + + // Don't render if the ShapeBase + // object's fade value is greater + // than the visibility epsilon. + bool shapeFade = mShapeBase && mShapeBase->getFadeVal() < TSMesh::VISIBILITY_EPSILON; + + // Get the shapebase datablock if we have one. + ShapeBaseData *data = NULL; + if ( mShapeBase ) + data = static_cast( mShapeBase->getDataBlock() ); + + // Also don't render if + // the camera distance is greater + // than the shadow length. + if ( shapeFade || !mDecalData || + ( mDecalInstance && + mDecalInstance->calcPixelRadius( state ) < mDecalInstance->calcEndPixRadius( state->getViewportExtent() ) ) ) + { + // Release our shadow texture + // so that others can grab it out + // of the pool. + mShadowTexture = NULL; + + return false; + } + + return true; +} + +bool ProjectedShadow::_updateDecal( const SceneState *state ) +{ + PROFILE_SCOPE( ProjectedShadow_UpdateDecal ); + + // Get the sunlight for the shadow projection. + // We want the LightManager to return NULL if it can't + // get the "real" sun, so we specify false for the useDefault parameter. + LightManager *lm = state->getLightManager(); + + LightInfo *lights[4] = {0}; + if ( !lm ) + return false; + + lm->setupLights( NULL, mParentObject->getWorldSphere(), 4 ); + lm->getBestLights( lights, 4 ); + lm->resetLights(); + + // Pull out the render transform + // for use in a couple of places later. + const MatrixF &renderTransform = mParentObject->getRenderTransform(); + Point3F pos = renderTransform.getPosition(); + + Point3F lightDir( 0, 0, 0 ); + Point3F tmp( 0, 0, 0 ); + F32 weight = 0; + F32 range = 0; + U32 lightCount = 0; + F32 dist = 0; + F32 fade = 0; + for ( U32 i = 0; i < 4; i++ ) + { + // If we got a NULL light, + // we're at the end of the list. + if ( !lights[i] ) + break; + + if ( !lights[i]->getCastShadows() ) + continue; + + if ( lights[i]->getType() != LightInfo::Point ) + tmp = lights[i]->getDirection(); + else + tmp = pos - lights[i]->getPosition(); + + range = lights[i]->getRange().x; + dist = ( (tmp.lenSquared()) / ((range * range) * 0.5f)); + weight = mClampF( 1.0f - ( tmp.lenSquared() / (range * range)), 0.00001f, 1.0f ); + + if ( lights[i]->getType() == LightInfo::Vector ) + fade = getMax( fade, 1.0f ); + else + fade = getMax( fade, mClampF( 1.0f - dist, 0.00001f, 1.0f ) ); + + lightDir += tmp * weight; + lightCount++; + } + + lightDir.normalize(); + + LightInfo *light = lights ? lights[0] : NULL; + + // No light... no shadow. + if ( !light ) + return false; + + const Box3F &objBox = mParentObject->getObjBox(); + Point3F boxCenter = objBox.getCenter(); + + // Has the light direction + // changed since last update? + bool lightDirChanged = !mLastLightDir.equal( lightDir ); + + // Has the parent object moved + // or scaled since the last update? + bool hasMoved = !mLastObjectPosition.equal( mParentObject->getRenderPosition() ); + bool hasScaled = !mLastObjectScale.equal( mParentObject->getScale() ); + + // Set the last light direction + // to the current light direction. + mLastLightDir = lightDir; + mLastObjectPosition = mParentObject->getRenderPosition(); + mLastObjectScale = mParentObject->getScale(); + + + // Temps used to generate + // tangent vector for DecalInstance below. + VectorF right( 0, 0, 0 ); + VectorF fwd( 0, 0, 0 ); + VectorF tmpFwd( 0, 0, 0 ); + + U32 idx = lightDir.getLeastComponentIndex(); + + tmpFwd[idx] = 1.0f; + + right = mCross( tmpFwd, lightDir ); + fwd = mCross( lightDir, right ); + right = mCross( fwd, lightDir ); + + right.normalize(); + + // Set up the world to light space + // matrix, along with proper position + // and rotation to be used as the world + // matrix for the render to texture later on. + static MatrixF sRotMat(EulerF( 0.0f, -(M_PI_F/2.0f), 0.0f)); + //mWorldToLight = light->getTransform(); + mWorldToLight.identity(); + MathUtils::getMatrixFromForwardVector( lightDir, &mWorldToLight ); + mWorldToLight.setPosition( ( pos + boxCenter ) - ( ( (mRadius * smDepthAdjust) + 0.001f ) * lightDir ) ); + mWorldToLight.mul( sRotMat ); + mWorldToLight.inverse(); + + // Set up the decal position. + // We use the object space box center + // multiplied by the render transform + // of the object to ensure we benefit + // from interpolation. + renderTransform.mulP( boxCenter ); + + // Get the shapebase datablock if we have one. + ShapeBaseData *data = NULL; + if ( mShapeBase ) + data = static_cast( mShapeBase->getDataBlock() ); + + // We use the object box's extents multiplied + // by the object's scale divided by 2 for the radius + // because the object's worldsphere radius is not + // rotationally invariant. + mRadius = (objBox.getExtents() * mParentObject->getScale()).len() * 0.5f; + + if ( data ) + mRadius *= data->shadowSphereAdjust; + + // Create the decal if we don't have one yet. + if ( !mDecalInstance ) + mDecalInstance = gDecalManager->addDecal( boxCenter, + lightDir, + right, + mDecalData, + 1.0f, + 0, + PermanentDecal | ClipDecal | CustomDecal ); + + if ( !mDecalInstance ) + return false; + + mDecalInstance->mVisibility = fade; + + // Setup decal parameters. + mDecalInstance->mSize = mRadius * 2.0f; + mDecalInstance->mNormal = -lightDir; + mDecalInstance->mTangent = -right; + mDecalInstance->mRotAroundNormal = 0; + mDecalInstance->mPosition = boxCenter; + mDecalInstance->mDataBlock = mDecalData; + + // If the position of the world + // space box center is the same + // as the decal's position, and + // the light direction has not + // changed, we don't need to clip. + bool shouldClip = !mDecalInstance->mPosition.equal( boxCenter ) || lightDirChanged || hasMoved || hasScaled; + + // Now, check and see if the object is visible. + const Frustum &frust = state->getFrustum(); + if ( !frust.sphereInFrustum( mDecalInstance->mPosition, mDecalInstance->mSize * mDecalInstance->mSize ) && !shouldClip ) + return false; + + F32 shadowLen = 10.0f; + if ( data ) + shadowLen = data->shadowProjectionDistance; + + const Point3F &boxExtents = objBox.getExtents(); + + + mShadowLength = shadowLen * mParentObject->getScale().z; + + // Set up clip depth, and box half + // offset for decal clipping. + Point2F clipParams( mShadowLength, (boxExtents.x + boxExtents.y) * 0.25f ); + + bool render = false; + bool clipSucceeded = true; + + // Clip! + if ( shouldClip ) + { + clipSucceeded = gDecalManager->clipDecal( mDecalInstance, + NULL, + &clipParams ); + } + + // If the clip failed, + // we'll return false in + // order to keep from + // unnecessarily rendering + // into the texture. If + // there was no reason to clip + // on this update, we'll assume we + // should update the texture. + render = clipSucceeded; + + // Tell the DecalManager we've changed this decal. + gDecalManager->notifyDecalModified( mDecalInstance ); + + return render; +} + +void ProjectedShadow::_calcScore( const SceneState *state ) +{ + if ( !mDecalInstance ) + return; + + F32 pixRadius = mDecalInstance->calcPixelRadius( state ); + + F32 pct = pixRadius / mDecalInstance->mDataBlock->startPixRadius; + + U32 msSinceLastRender = Platform::getVirtualMilliseconds() - getLastRenderTime(); + + ShapeBaseData *data = NULL; + if ( mShapeBase ) + data = static_cast( mShapeBase->getDataBlock() ); + + // For every 1s this shadow hasn't been + // updated we'll add 10 to the score. + F32 secs = mFloor( (F32)msSinceLastRender / 1000.0f ); + + mScore = pct + secs; + mClampF( mScore, 0.0f, 2000.0f ); +} + +void ProjectedShadow::update( const SceneState *state ) +{ + mUpdateTexture = true; + + // Update our decal before + // we render to texture. + // If it fails, something bad happened + // (no light to grab/failed clip) and we should return. + if ( !_updateDecal( state ) ) + { + // Release our shadow texture + // so that others can grab it out + // of the pool. + mShadowTexture = NULL; + + mUpdateTexture = false; + + return; + } + else + _calcScore( state ); + + if ( !mCasterPositionSC || !mCasterPositionSC->isValid() ) + mCasterPositionSC = mDecalData->matInst->getMaterialParameterHandle( "$shadowCasterPosition" ); + + if ( !mShadowLengthSC || !mShadowLengthSC->isValid() ) + mShadowLengthSC = mDecalData->matInst->getMaterialParameterHandle( "$shadowLength" ); + + MaterialParameters *matParams = mDecalData->matInst->getMaterialParameters(); + + matParams->set( mCasterPositionSC, mParentObject->getRenderPosition() ); + matParams->set( mShadowLengthSC, mShadowLength / 4.0f ); +} + +void ProjectedShadow::render( F32 camDist, const TSRenderState &rdata ) +{ + if ( !mUpdateTexture ) + return; + + // Do the render to texture, + // DecalManager handles rendering + // the shadow onto the world. + _renderToTexture( camDist, rdata ); +} + +void ProjectedShadow::_renderToTexture( F32 camDist, const TSRenderState &rdata ) +{ + PROFILE_SCOPE( ProjectedShadow_RenderToTexture ); + + GFXDEBUGEVENT_SCOPE( ProjectedShadow_RenderToTexture, ColorI( 255, 0, 0 ) ); + + RenderPassManager *renderPass = _getRenderPass(); + if ( !renderPass ) + return; + + GFXTransformSaver saver; + + // NOTE: GFXTransformSaver does not save/restore the frustum + // so we must save it here before we modify it. + F32 l, r, b, t, n, f; + bool ortho; + GFX->getFrustum( &l, &r, &b, &t, &n, &f, &ortho ); + + // Set the orthographic projection + // matrix up, to be based on the radius + // generated based on our shape. + GFX->setOrtho( -mRadius, mRadius, -mRadius, mRadius, 0.001f, (mRadius * 2) * smDepthAdjust, true ); + + // Set the world to light space + // matrix set up in shouldRender(). + GFX->setWorldMatrix( mWorldToLight ); + + // Get the shapebase datablock if we have one. + ShapeBaseData *data = NULL; + if ( mShapeBase ) + data = static_cast( mShapeBase->getDataBlock() ); + + // Init or update the shadow texture size. + if ( mShadowTexture.isNull() || ( data && data->shadowSize != mShadowTexture.getWidth() ) ) + { + U32 texSize = data ? data->shadowSize : 256; + mShadowTexture.set( texSize, texSize, GFXFormatR8G8B8A8, &PostFxTargetProfile, "BLShadow" ); + } + + GFX->pushActiveRenderTarget(); + + if ( !mRenderTarget ) + mRenderTarget = GFX->allocRenderToTextureTarget(); + + mRenderTarget->attachTexture( GFXTextureTarget::DepthStencil, _getDepthTarget( mShadowTexture->getWidth(), mShadowTexture->getHeight() ) ); + mRenderTarget->attachTexture( GFXTextureTarget::Color0, mShadowTexture ); + GFX->setActiveRenderTarget( mRenderTarget ); + + GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, ColorI( 0, 0, 0, 0 ), 1.0f, 0 ); + + gClientSceneGraph->pushRenderPass( renderPass ); + + SceneState *diffuseState = rdata.getSceneState(); + SceneGraph *sceneManager = diffuseState->getSceneManager(); + + SceneState *baseState = sceneManager->createBaseState( SPT_Shadow ); + baseState->setDiffuseCameraTransform( diffuseState->getCameraTransform() ); + baseState->setViewportExtent( diffuseState->getViewportExtent() ); + baseState->setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); + + // This is a tricky hack, + // in order to get ShapeBase mounted + // objects to render properly into + // our render target. + baseState->objectAlwaysRender( true ); + + mParentObject->prepRenderImage( baseState, gClientSceneGraph->getStateKey(), 0xFFFFFFFF ); + renderPass->renderPass( baseState ); + + baseState->objectAlwaysRender( false ); + + // Grab the ShadowFilterPFX object + // and call process on it with our target. + PostEffect *shadowFilterPfx = NULL; + if ( camDist < BasicLightManager::getShadowFilterDistance() && + Sim::findObject( "BL_ShadowFilterPostFx", shadowFilterPfx ) ) + shadowFilterPfx->process( baseState, mShadowTexture ); + + // Delete the SceneState we allocated. + delete baseState; + + gClientSceneGraph->popRenderPass(); + + mRenderTarget->resolve(); + + GFX->popActiveRenderTarget(); + + // Restore frustum + if (!ortho) + GFX->setFrustum(l, r, b, t, n, f); + else + GFX->setOrtho(l, r, b, t, n, f); + + // Set the last render time. + mLastRenderTime = Platform::getVirtualMilliseconds(); + + // HACK: Will remove in future release! + mDecalInstance->mCustomTex = &mShadowTexture; +} + +RenderPassManager* ProjectedShadow::_getRenderPass() +{ + if ( smRenderPass.isNull() ) + { + SimObject* renderPass = NULL; + + if ( !Sim::findObject( "BL_ProjectedShadowRPM", renderPass ) ) + Con::errorf( "ProjectedShadow::init() - 'BL_ProjectedShadowRPM' not initialized" ); + else + smRenderPass = dynamic_cast(renderPass); + } + + return smRenderPass; +} + +GFXTextureObject* ProjectedShadow::_getDepthTarget( U32 width, U32 height ) +{ + // Get a depth texture target from the pooled profile + // which is returned as a temporary. + GFXTexHandle depthTex( width, height, GFXFormatD24S8, &BLProjectedShadowZProfile, + "ProjectedShadow::_getDepthTarget()" ); + + return depthTex; +} diff --git a/lighting/common/projectedShadow.h b/lighting/common/projectedShadow.h new file mode 100644 index 0000000..0ed1095 --- /dev/null +++ b/lighting/common/projectedShadow.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROJECTEDSHADOW_H_ +#define _PROJECTEDSHADOW_H_ + +#include "collision/depthSortList.h" +#include "sceneGraph/sceneObject.h" +#include "ts/tsShapeInstance.h" +#include "lighting/common/shadowBase.h" + +class ShapeBase; +class LightInfo; +class DecalData; +class DecalInstance; +class RenderPassManager; +class RenderMeshMgr; +class CustomMaterial; +class BaseMatInstance; + +GFX_DeclareTextureProfile( BLProjectedShadowProfile ); +GFX_DeclareTextureProfile( BLProjectedShadowZProfile ); + +class ProjectedShadow : public ShadowBase +{ + +protected: + + + /// This parameter is used to + /// adjust the far plane out for our + /// orthographic render in order to + /// force our object towards one end of the + /// the eye space depth range. + static F32 smDepthAdjust; + + F32 mRadius; + MatrixF mWorldToLight; + U32 mLastRenderTime; + + F32 mShadowLength; + + F32 mScore; + bool mUpdateTexture; + + Point3F mLastObjectScale; + Point3F mLastObjectPosition; + VectorF mLastLightDir; + + DecalData *mDecalData; + DecalInstance *mDecalInstance; + + SceneObject *mParentObject; + ShapeBase *mShapeBase; + + MaterialParameterHandle *mCasterPositionSC; + MaterialParameterHandle *mShadowLengthSC; + + static SimObjectPtr smRenderPass; + + static RenderPassManager* _getRenderPass(); + + GFXTexHandle mShadowTexture; + GFXTextureTargetRef mRenderTarget; + + GFXTextureObject* _getDepthTarget( U32 width, U32 height ); + void _renderToTexture( F32 camDist, const TSRenderState &rdata ); + + bool _updateDecal( const SceneState *sceneState ); + + void _calcScore( const SceneState *state ); + +public: + + ProjectedShadow( SceneObject *object ); + virtual ~ProjectedShadow(); + + bool shouldRender( const SceneState *state ); + + void update( const SceneState *state ); + void render( F32 camDist, const TSRenderState &rdata ); + U32 getLastRenderTime() const { return mLastRenderTime; } + const F32 getScore() const { return mScore; } + +}; + +#endif // _PROJECTEDSHADOW_H_ \ No newline at end of file diff --git a/lighting/common/sceneLighting.cpp b/lighting/common/sceneLighting.cpp new file mode 100644 index 0000000..33ff245 --- /dev/null +++ b/lighting/common/sceneLighting.cpp @@ -0,0 +1,1094 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/common/sceneLighting.h" + +#include "T3D/gameConnection.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/common/shadowVolumeBSP.h" +#include "T3D/shapeBase.h" +#include "gui/core/guiCanvas.h" +#include "ts/tsShape.h" +#include "ts/tsShapeInstance.h" +#include "T3D/staticShape.h" +#include "T3D/tsStatic.h" +#include "collision/concretePolyList.h" +#include "lighting/lightingInterfaces.h" +#include "terrain/terrData.h" +#include "platform/platformVolume.h" +#include "core/stream/fileStream.h" +#include "core/crc.h" + +//#define DUMP_LIGHTMAPS + +#ifdef DUMP_LIGHTMAPS +#include "interior/interiorInstance.h" +#include "core/volume.h" +#endif + + +namespace +{ + bool gTerminateLighting = false; + F32 gLightingProgress = 0.0f; + char * gCompleteCallback = NULL; + U32 gConnectionMissionCRC = 0xffffffff; +} + +SceneLighting *gLighting = NULL; +F32 gParellelVectorThresh = 0.01f; +F32 gPlaneNormThresh = 0.999f; +F32 gPlaneDistThresh = 0.001f; + + +void SceneLighting::sgNewEvent(U32 light, S32 object, U32 event) +{ + Sim::postEvent(this, new sgSceneLightingProcessEvent(light, object, event), Sim::getTargetTime() + 1); + // Paint canvas here? +} + +//----------------------------------------------- +/* +* Called once per scenelighting - entry point for event system +*/ +void SceneLighting::sgLightingStartEvent() +{ + Con::printf(""); + Con::printf("Starting scene lighting..."); + + sgTimeTemp2 = Platform::getRealMilliseconds(); + + // clear interior light maps + for(ObjectProxy **proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + { + ObjectProxy* objprox; + objprox = *proxyItr; + // is there an object? + if(!objprox->getObject()) + { + AssertFatal(0, "SceneLighting:: missing sceneobject on light start"); + Con::errorf(ConsoleLogEntry::General, " SceneLighting:: missing sceneobject on light start"); + continue; + } + + objprox->processLightingStart(); + } + + + sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgTGEPassSetupEventType); + //sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgSGPassSetupEventType); +} + +/* +* Called once per scenelighting - exit from event system +*/ +void SceneLighting::sgLightingCompleteEvent() +{ + Vector terrBlocks; + + // initialize the objects for lighting + for(ObjectProxy ** proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + { + ObjectProxy* objprox = *proxyItr; + TerrainBlock *terr = dynamic_cast(objprox->getObject()); + if (terr) + terrBlocks.push_back(terr); + } + + for (S32 i = 0; i < terrBlocks.size(); i++) + terrBlocks[i]->postLight(terrBlocks); + + // save out the lighting? + if(Con::getBoolVariable("$pref::sceneLighting::cacheLighting", true)) + { + if(!savePersistInfo(mFileName)) + Con::errorf(ConsoleLogEntry::General, "SceneLighting::light: unable to persist lighting!"); + else + Con::printf("Successfully saved mission lighting file: '%s'", mFileName); + } + + Con::printf("Scene lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-sgTimeTemp2)/1000.f); + Con::printf("//-----------------------------------------------"); + Con::printf(""); + + + completed(true); + deleteObject(); +} + +//----------------------------------------------- +/* +* Called once per scenelighting - used for prepping the +* event system for TGE style scenelighting +*/ +void SceneLighting::sgTGEPassSetupEvent() +{ + Con::printf(" Starting TGE based scene lighting..."); + + + sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgTGELightStartEventType); +} + +/* +* Called once per light - used for calling preLight on all objects +* Only TGE lights call prelight and continue on to the process event +*/ +void SceneLighting::sgTGELightStartEvent(U32 light) +{ + // catch bad light index and jump to complete event + if(light >= mLights.size()) + { + sgNewEvent(light, 0, sgSceneLightingProcessEvent::sgTGELightCompleteEventType); + return; + } + + // can we use the light? + if(mLights[light]->getType() != LightInfo::Vector) + { + sgNewEvent((light+1), 0, sgSceneLightingProcessEvent::sgTGELightStartEventType); + return; + } + + // process pre-lighting + Con::printf(" Lighting with light #%d (TGE vector light)...", (light+1)); + LightInfo *lightobj = mLights[light]; + mLitObjects.clear(); + + for(ObjectProxy **proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + { + ObjectProxy* objprox = *proxyItr; + + // is there an object? + if(!objprox->getObject()) + { + AssertFatal(0, "SceneLighting:: missing sceneobject on light start"); + Con::errorf(ConsoleLogEntry::General, " SceneLighting:: missing sceneobject on light start"); + continue; + } + + if (objprox->tgePreLight(lightobj)) + mLitObjects.push_back(objprox); + } + + // kick off lighting + + sgNewEvent(light, 0, sgSceneLightingProcessEvent::sgTGELightProcessEventType); +} + +/* +* Called once for each TGE light and object - used for calling light on an object +*/ +void SceneLighting::sgTGELightProcessEvent(U32 light, S32 object) +{ + // catch bad light or object index + if((light >= mLights.size()) || (object >= mLitObjects.size())) + { + sgNewEvent(light, 0, sgSceneLightingProcessEvent::sgTGELightCompleteEventType); + return; + } + + //process object and light + S32 time = Platform::getRealMilliseconds(); + // light object + LightInfo* li = mLights[light]; + mLitObjects[object]->processTGELightProcessEvent(object, mLitObjects.size(), li); + + sgTGESetProgress(light, object); + Con::printf(" Object lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-time)/1000.f); + + + // kick off next object event + + sgNewEvent(light, (object+1), sgSceneLightingProcessEvent::sgTGELightProcessEventType); +} + +/* +* Called once per TGE light - used for calling postLight on all objects +*/ +void SceneLighting::sgTGELightCompleteEvent(U32 light) +{ + // catch bad light index and move to the next pass event + if(light >= mLights.size()) + { + sgTGESetProgress(mLights.size(), mLitObjects.size()); + Con::printf(" TGE based scene lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-sgTimeTemp2)/1000.f); + + sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgSGPassSetupEventType); + //sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgLightingCompleteEventType); + return; + } + + // process post-lighting + // don't do this, SG lighting events will copy terrain light map... + /*bool islast = (light == (mLights.size() - 1)); + for(U32 o=0; o(mLitObjects[o])) + mLitObjects[o]->postLight(islast); + }*/ + + // kick off next light event + + sgNewEvent((light+1), 0, sgSceneLightingProcessEvent::sgTGELightStartEventType); +} + +void SceneLighting::sgTGESetProgress(U32 light, S32 object) +{ + // TGE is light based... + F32 val = (F32)(light * mLitObjects.size()) + object; + F32 total = (F32)(mLights.size() * mLitObjects.size()); + + if(total == 0.0f) + return; + + val = getMin(val, total); + + // two passes... + total *= 2.0f; + + gLightingProgress = val / total; +} + +//----------------------------------------------- +/* +* Called once per scenelighting - used for prepping the +* event system for SG style scenelighting +*/ +void SceneLighting::sgSGPassSetupEvent() +{ + mLitObjects.clear(); + for(ObjectProxy **proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + { + // is there an object? + if(!(*proxyItr)->getObject()) + { + AssertFatal(0, "SceneLighting:: missing sceneobject on light start"); + Con::errorf(ConsoleLogEntry::General, " SceneLighting:: missing sceneobject on light start"); + continue; + } + + // add all lights + mLitObjects.push_back(*proxyItr); + } + + sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgSGObjectStartEventType); +} + +/* +* Called once per object - used for calling preLight on all SG lights +*/ +void SceneLighting::sgSGObjectStartEvent(S32 object) +{ + // catch bad light index and jump to complete event + if(object >= mLitObjects.size()) + { + sgNewEvent(0, object, sgSceneLightingProcessEvent::sgSGObjectCompleteEventType); + return; + } + + ObjectProxy *obj = mLitObjects[object]; + bool bHandled = obj->processStartObjectLightingEvent(object, mLitObjects.size()); + if (!bHandled) + { + Con::printf(" Lighting object %d of %d... %s: %s", (object+1), mLitObjects.size(), obj->getObject()->getClassName(), obj->getObject()->getName()); + } + + for(U32 i=0; imType == LightInfo::SGStaticPoint) || (lightobj->mType == LightInfo::SGStaticSpot)) + obj->preLight(lightobj); + } + + sgTimeTemp = Platform::getRealMilliseconds(); + + // kick off lighting + + + // this is slow with multiple objects... + //sgNewEvent(0, object, sgSceneLightingProcessEvent::sgSGObjectProcessEventType); + // jump right to the method... + sgSGObjectProcessEvent(0, object); +} + +/* +* Called once per object and SG light - used for calling light on an object +*/ +void SceneLighting::sgSGObjectProcessEvent(U32 light, S32 object) +{ + // catch bad light or object index + if((light >= mLights.size()) || (object >= mLitObjects.size())) + { + // this is slow with multiple objects... + //sgNewEvent(0, object, sgSceneLightingProcessEvent::sgSGObjectCompleteEventType); + // jump right to the method... + sgSGObjectCompleteEvent(object); + return; + } + + // avoid the event overhead... + // 80 lights == 0.6 seconds an interior without ANY lighting (events only)... + U32 time = Platform::getRealMilliseconds(); + ObjectProxy* objprox = mLitObjects[object]; + while((light < mLights.size()) && ((Platform::getRealMilliseconds() - time) < 500)) + { + // can we use the light? + LightInfo *lightobj = mLights[light]; + + objprox->processSGObjectProcessEvent(lightobj); + + sgSGSetProgress(light, object); + + light++; + } + + light--; + + // kick off next light event + + sgNewEvent((light+1), object, sgSceneLightingProcessEvent::sgSGObjectProcessEventType); +} + +/* +* Called once per object - used for calling postLight on all SG lights +*/ +void SceneLighting::sgSGObjectCompleteEvent(S32 object) +{ + // catch bad light index and move to the next pass event + if(object >= mLitObjects.size()) + { + sgSGSetProgress(mLights.size(), mLitObjects.size()); + + sgNewEvent(0, 0, sgSceneLightingProcessEvent::sgLightingCompleteEventType); + return; + } + + // process post-lighting + Con::printf(" Object lighting complete (%3.3f seconds)", (Platform::getRealMilliseconds()-sgTimeTemp)/1000.f); + + // in case Atlas turned off rendering... + GFX->setAllowRender(true); + + // only the last light does something + mLitObjects[object]->postLight(true); + +#ifdef DUMP_LIGHTMAPS + InteriorInstance *interiorinst = dynamic_cast(mLitObjects[object]->getObject()); + if(interiorinst) + { + Interior *detail = interiorinst->getDetailLevel(0); + for(U32 i=0; imNormalLMapIndices.size(); i++) + { + GFXTexHandle normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), + interiorinst->getLMHandle(), detail->getNormalLMapIndex(i)); + GBitmap *normLightmap = normHandle->getBitmap(); + + FileStream output; + output.open(avar("lightmaps/lm_%d_%d.png", object, i), Torque::FS::File::Write); + normLightmap->writeBitmap("png",output); + } + } +#endif + + /*ObjectProxy *obj = mLitObjects[object]; + for(U32 i=0; imType == LightInfo::SGStaticPoint) || (lightobj->mType == LightInfo::SGStaticSpot)) + obj->postLight((i == (mLights.size() - 1))); + }*/ + + // kick off next light event + + + // this is slow with multiple objects... + //sgNewEvent(0, (object+1), sgSceneLightingProcessEvent::sgSGObjectStartEventType); + // jump right to the method... + sgSGObjectStartEvent((object+1)); +} + +void SceneLighting::sgSGSetProgress(U32 light, S32 object) +{ + // SG is object based... + F32 val = (F32)((object * mLights.size()) + light); + F32 total = (F32)(mLights.size() * mLitObjects.size()); + + if(total == 0.0f) + return; + + val = getMin(val, total); + + // two passes... + total *= 2.0f; + + gLightingProgress = (val / total) + 0.5f; +} + +//----------------------------------------------- + +void SceneLighting::processEvent(U32 light, S32 object) +{ + sgNewEvent(light, object, sgSceneLightingProcessEvent::sgLightingStartEventType); +} + + +//----------------------------------------------- + + +SceneLighting::SceneLighting(AvailableSLInterfaces* lightingInterfaces) +{ + mLightingInterfaces = lightingInterfaces; + mStartTime = 0; + mFileName[0] = '\0'; + mSceneManager = NULL; + + // Registering vars more than once doesn't hurt anything. + Con::addVariable("SceneLighting::terminateLighting", TypeBool, &gTerminateLighting); + Con::addVariable("SceneLighting::lightingProgress", TypeF32, &gLightingProgress); + + mLightingInterfaces->initInterfaces(); +} + +SceneLighting::~SceneLighting() +{ + gLighting = NULL; + gLightingProgress = 0.0f; + + ObjectProxy ** proxyItr; + for(proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + delete *proxyItr; +} + +void SceneLighting::getMLName(const char* misName, const U32 missionCRC, const U32 buffSize, char* filenameBuffer) +{ + dSprintf(filenameBuffer, buffSize, "%s_%x.ml", misName, missionCRC); +} + +bool SceneLighting::light(BitSet32 flags) +{ + if(!mSceneManager) + return(false); + + mStartTime = Platform::getRealMilliseconds(); + + // register static lights + LightManager* lManager = gClientSceneGraph->getLightManager(); + if (!lManager) + return false; // This world doesn't need lighting. + + lManager->registerGlobalLights(NULL,true); + + // Notify each system factory that we are beginning to light + for(SceneLightingInterface** sitr = mLightingInterfaces->mAvailableSystemInterfaces.begin(); sitr != mLightingInterfaces->mAvailableSystemInterfaces.end(); sitr++) + { + SceneLightingInterface* si = (*sitr); + si->processLightingBegin(); + } + + // grab all the lights + mLights.clear(); + lManager->getAllUnsortedLights(mLights); + lManager->unregisterAllLights(); + + if(!mLights.size()) + return(false); + + // get all the objects and create proxy's for them + SimpleQueryList objects; + gClientContainer.findObjects(mLightingInterfaces->mAvailableObjectTypes, &SimpleQueryList::insertionCallback, &objects); + + for(SceneObject ** itr = objects.mList.begin(); itr != objects.mList.end(); itr++) + { + ObjectProxy * proxy = NULL; + SceneObject* obj = *itr; + if (!obj) + continue; + + // Create the right chunk for the system + for(SceneLightingInterface** sitr = mLightingInterfaces->mAvailableSystemInterfaces.begin(); + sitr != mLightingInterfaces->mAvailableSystemInterfaces.end() && proxy == NULL; sitr++) + { + SceneLightingInterface* si = (*sitr); + proxy = si->createObjectProxy(obj, &mSceneObjects); + } + + if (proxy) + { + if(!proxy->calcValidation()) + { + Con::errorf(ConsoleLogEntry::General, "Failed to calculate validation info for object. Skipped."); + delete proxy; + continue; + } + + if(!proxy->loadResources()) + { + Con::errorf(ConsoleLogEntry::General, "Failed to load resources for object. Skipped."); + delete proxy; + continue; + } + + mSceneObjects.push_back(proxy); + } + } + + if(!mSceneObjects.size()) + return(false); + + // grab the missions crc + U32 missionCRC = calcMissionCRC(); + + // remove the '.mis' extension from the mission name + char misName[256]; + dSprintf(misName, sizeof(misName), "%s", Con::getVariable("$Client::MissionFile")); + char * dot = dStrstr((const char*)misName, ".mis"); + if(dot) + *dot = '\0'; + + // get the mission name + getMLName(misName, missionCRC, 1023, mFileName); + + // check for some persisted data, check if being forced.. + if(!flags.test(ForceAlways|ForceWritable)) + { + if(loadPersistInfo(mFileName)) + { + Con::printf(" Successfully loaded mission lighting file: '%s'", mFileName); + + // touch this file... + if(!Platform::FS::Touch(mFileName)) + Con::warnf(" Failed to touch file '%s'. File may be read only.", mFileName); + + return(false); + } + + // texture manager must have lighting complete now + if(flags.test(LoadOnly)) + { + Con::errorf(ConsoleLogEntry::General, "Failed to load mission lighting!"); + return(false); + } + } + + // don't light if file is read-only? + if(!flags.test(ForceAlways)) + { + FileStream stream; + + stream.open( mFileName, Torque::FS::File::Write ); + + if(stream.getStatus() != Stream::Ok) + { + Con::errorf(ConsoleLogEntry::General, "SceneLighting::Light: Failed to light mission. File '%s' cannot be written to.", mFileName); + return(false); + } + } + + // initialize the objects for lighting + for(ObjectProxy ** proxyItr = mSceneObjects.begin(); proxyItr != mSceneObjects.end(); proxyItr++) + (*proxyItr)->init(); + + // get things started + Sim::postEvent(this, new sgSceneLightingProcessEvent(0, -1, + sgSceneLightingProcessEvent::sgLightingStartEventType), Sim::getTargetTime() + 1); + + return(true); +} + +void SceneLighting::completed(bool success) +{ + // process the cached lighting files + processCache(); + + // Notify each system factory that we are have lit! + for(SceneLightingInterface** sitr = mLightingInterfaces->mAvailableSystemInterfaces.begin(); sitr != mLightingInterfaces->mAvailableSystemInterfaces.end(); sitr++) + { + SceneLightingInterface* si = (*sitr); + si->processLightingCompleted(success); + } + + if(gCompleteCallback && gCompleteCallback[0]) + Con::executef(gCompleteCallback); + + dFree(gCompleteCallback); + gCompleteCallback = NULL; +} + +//------------------------------------------------------------------------------ +// Static access method: there can be only one SceneLighting object +bool SceneLighting::lightScene(const char * callback, BitSet32 flags) +{ + if(gLighting) + { + Con::errorf(ConsoleLogEntry::General, "Lighting is already in progress!"); + return false; + } + + // register the object + if(!registerObject()) + { + AssertFatal(0, "SceneLighting:: Unable to register SceneLighting object!"); + Con::errorf(ConsoleLogEntry::General, "SceneLighting:: Unable to register SceneLighting object!"); + delete this; + return(false); + } + + // could have interior resources but no instances (hey, got this far didnt we...) + GameConnection * con = dynamic_cast(NetConnection::getConnectionToServer()); + if(!con) + { + Con::errorf(ConsoleLogEntry::General, "SceneLighting:: no GameConnection"); + return(false); + } + con->addObject(this); + + // set the globals + gLighting = this; + gTerminateLighting = false; + gLightingProgress = 0.0f; + if (gCompleteCallback) + dFree(gCompleteCallback); + gCompleteCallback = dStrdup(callback); + gConnectionMissionCRC = con->getMissionCRC(); + + // assumes we are in the world that needs lighting... + mSceneManager = gClientSceneGraph; + + if(!light(flags)) + { + completed(true); + deleteObject(); + return(false); + } + return(true); +} + +bool SceneLighting::isLighting() +{ + return(bool(gLighting)); +} + +/// adds TSStatic objects as shadow casters. +void SceneLighting::addStatic(ShadowVolumeBSP *shadowVolume, + SceneObject *sceneobject, LightInfo *light, S32 level) +{ + if (!sceneobject) + return; + + if(light->getType() != LightInfo::Vector) + return; + + ConcretePolyList polylist; + const Box3F box; + const SphereF sphere; + sceneobject->buildPolyList(&polylist, box, sphere); + + // retrieve the poly list (uses the collision mesh)... + //sobj->sgAdvancedStaticOptionsData.sgBuildPolyList(sobj, &polylist); + + S32 i, count, vertind[3]; + ConcretePolyList::Poly *poly; + + count = polylist.mPolyList.size(); + + // add the polys to the shadow volume... + for(i=0; ivertexCount == 3), "Hmmm... vert count is greater than 3."); + + vertind[0] = polylist.mIndexList[poly->vertexStart]; + vertind[1] = polylist.mIndexList[poly->vertexStart + 1]; + vertind[2] = polylist.mIndexList[poly->vertexStart + 2]; + + if(mDot(PlaneF(polylist.mVertexList[vertind[0]], polylist.mVertexList[vertind[1]], + polylist.mVertexList[vertind[2]]), light->getDirection()) < gParellelVectorThresh) + { + ShadowVolumeBSP::SVPoly *svpoly = shadowVolume->createPoly(); + svpoly->mWindingCount = 3; + + svpoly->mWinding[0].set(polylist.mVertexList[vertind[0]]); + svpoly->mWinding[1].set(polylist.mVertexList[vertind[1]]); + svpoly->mWinding[2].set(polylist.mVertexList[vertind[2]]); + svpoly->mPlane = PlaneF(svpoly->mWinding[0], svpoly->mWinding[1], svpoly->mWinding[2]); + svpoly->mPlane.neg(); + + shadowVolume->buildPolyVolume(svpoly, light); + shadowVolume->insertPoly(svpoly); + } + } +} + +//------------------------------------------------------------------------------ +bool SceneLighting::verifyMissionInfo(PersistInfo::PersistChunk * chunk) +{ + PersistInfo::MissionChunk * info = dynamic_cast(chunk); + if(!info) + return(false); + + PersistInfo::MissionChunk curInfo; + if(!getMissionInfo(&curInfo)) + return(false); + + return(curInfo.mChunkCRC == info->mChunkCRC); +} + +bool SceneLighting::getMissionInfo(PersistInfo::PersistChunk * chunk) +{ + PersistInfo::MissionChunk * info = dynamic_cast(chunk); + if(!info) + return(false); + + info->mChunkCRC = gConnectionMissionCRC ^ PersistInfo::smFileVersion; + return(true); +} + +//------------------------------------------------------------------------------ +bool SceneLighting::loadPersistInfo(const char * fileName) +{ + FileStream stream; + + stream.open( fileName, Torque::FS::File::Read ); + + if(stream.getStatus() != Stream::Ok) + return false; + + PersistInfo persistInfo; + bool success = persistInfo.read(stream); + stream.close(); + if(!success) + return(false); + + // verify the mission chunk + if(!verifyMissionInfo(persistInfo.mChunks[0])) + return(false); + + // Create the right chunk for the system + for(SceneLightingInterface** sitr = mLightingInterfaces->mAvailableSystemInterfaces.begin(); sitr != mLightingInterfaces->mAvailableSystemInterfaces.end(); sitr++) + { + SceneLightingInterface* si = (*sitr); + if (!si->postProcessLoad(&persistInfo, &mSceneObjects)) + { + return false; + } + } + + if(mSceneObjects.size() != (persistInfo.mChunks.size() - 1)) + return(false); + + Vector chunks; + + // ensure that the scene objects are in the same order as the chunks + // - different instances will depend on this + U32 i; + for(i = 0; i < mSceneObjects.size(); i++) + { + // 0th chunk is the mission chunk + U32 chunkIdx = i+1; + if(chunkIdx >= persistInfo.mChunks.size()) + return(false); + + if(!mSceneObjects[i]->isValidChunk(persistInfo.mChunks[chunkIdx])) + return(false); + chunks.push_back(persistInfo.mChunks[chunkIdx]); + } + + // get the objects to load in the persisted chunks + for(i = 0; i < mSceneObjects.size(); i++) + if(!mSceneObjects[i]->setPersistInfo(chunks[i])) + return(false); + + return(true); +} + +bool SceneLighting::savePersistInfo(const char * fileName) +{ + // open the file + FileStream file; + file.open( fileName, Torque::FS::File::Write ); + + if(file.getStatus() != Stream::Ok) + return false; + + PersistInfo persistInfo; + + // add in the mission chunk + persistInfo.mChunks.push_back(new PersistInfo::MissionChunk); + + // get the mission info, will return false when there are 0 lights + if(!getMissionInfo(persistInfo.mChunks[0])) + return(false); + + // get all the persist chunks + bool bChunkFound; + for(U32 i = 0; i < mSceneObjects.size(); i++) + { + bChunkFound = false; + // Create the right chunk for the system + for(SceneLightingInterface** sitr = mLightingInterfaces->mAvailableSystemInterfaces.begin(); sitr != mLightingInterfaces->mAvailableSystemInterfaces.end() && !bChunkFound; sitr++) + { + SceneLightingInterface* si = (*sitr); + PersistInfo::PersistChunk* chunk; + if (si->createPersistChunkFromProxy(mSceneObjects[i], &chunk)) + { + if (chunk) + { + persistInfo.mChunks.push_back(chunk); + bChunkFound = true; + } + } + } + + // Make sure the chunk worked. + if (!mSceneObjects[i]->getPersistInfo(persistInfo.mChunks.last())) + return false; + } + + if(!persistInfo.write(file)) + return(false); + + file.close(); + + return(true); +} + +struct CacheEntry { + Torque::FS::FileNodeRef mFileObject; + const char *mFileName; + + CacheEntry() { + mFileName = 0; + }; +}; + +// object list sort methods: want list in reverse +static int QSORT_CALLBACK minSizeSort(const void * p1, const void * p2) +{ + const CacheEntry * entry1 = (const CacheEntry *)p1; + const CacheEntry * entry2 = (const CacheEntry *)p2; + + return(entry2->mFileObject->getSize() - entry1->mFileObject->getSize()); +} + +static int QSORT_CALLBACK maxSizeSort(const void * p1, const void * p2) +{ + const CacheEntry * entry1 = (const CacheEntry *)p1; + const CacheEntry * entry2 = (const CacheEntry *)p2; + + return(entry1->mFileObject->getSize() - entry2->mFileObject->getSize()); +} + +static int QSORT_CALLBACK lastCreatedSort(const void * p1, const void * p2) +{ + const CacheEntry * entry1 = (const CacheEntry *)p1; + const CacheEntry * entry2 = (const CacheEntry *)p2; + + FileTime create[2]; + FileTime modify; + + bool ret[2]; + + ret[0] = Platform::getFileTimes(entry1->mFileName, &create[0], &modify); + ret[1] = Platform::getFileTimes(entry2->mFileName, &create[1], &modify); + + // check return values + if(!ret[0] && !ret[1]) + return(0); + if(!ret[0]) + return(1); + if(!ret[1]) + return(-1); + + return(Platform::compareFileTimes(create[1], create[0])); +} + +static int QSORT_CALLBACK lastModifiedSort(const void * p1, const void * p2) +{ + const CacheEntry * entry1 = (const CacheEntry *)p1; + const CacheEntry * entry2 = (const CacheEntry *)p2; + + FileTime create; + FileTime modify[2]; + + bool ret[2]; + + ret[0] = Platform::getFileTimes(entry1->mFileName, &create, &modify[0]); + ret[1] = Platform::getFileTimes(entry2->mFileName, &create, &modify[1]); + + // check return values + if(!ret[0] && !ret[1]) + return(0); + if(!ret[0]) + return(1); + if(!ret[1]) + return(-1); + + return(Platform::compareFileTimes(modify[1], modify[0])); +} + +void SceneLighting::processCache() +{ + // get size in kb + S32 quota = Con::getIntVariable("$pref::sceneLighting::cacheSize", -1); + + Vector files; + + Vector fileNames; + Torque::FS::FindByPattern(Torque::Path(Platform::getMainDotCsDir()), "*.ml", true, fileNames); + + S32 curCacheSize = 0; + + for(S32 i = 0;i < fileNames.size();++i) + { + if(! Torque::FS::IsFile(fileNames[i])) + continue; + + Torque::FS::FileNodeRef fileNode = Torque::FS::GetFileNode(fileNames[i]); + if(fileNode == NULL) + continue; + + if(dStrstr(fileNames[i], mFileName) == 0) + { + // Don't allow the current file to be removed + CacheEntry entry; + entry.mFileObject = fileNode; + entry.mFileName = StringTable->insert(fileNames[i]); + files.push_back(entry); + } + else + curCacheSize += fileNode->getSize(); + } + + // remove old files + for(S32 i = files.size() - 1; i >= 0; i--) + { + FileStream *stream; + if((stream = FileStream::createAndOpen( files[i].mFileObject->getName(), Torque::FS::File::Read )) == NULL) + continue; + + // read in the version + U32 version; + bool ok = (stream->read(&version) && (version == PersistInfo::smFileVersion)); + delete stream; + + // ok? + if(ok) + continue; + + // no sneaky names + if(!dStrstr(files[i].mFileName, "..")) + { + Con::warnf("Removing old lighting file '%s'.", files[i].mFileName); + dFileDelete(files[i].mFileName); + } + + files.pop_back(); + } + + // no size restriction? + if(quota == -1 || !files.size()) + return; + + for(U32 i = 0; i < files.size(); i++) + curCacheSize += files[i].mFileObject->getSize(); + + // need to remove? + if(quota > (curCacheSize >> 10)) + return; + + // sort the entries by the correct method + const char * purgeMethod = Con::getVariable("$pref::sceneLighting::purgeMethod"); + if(!purgeMethod) + purgeMethod = ""; + + // determine the method (default to least recently used) + if(!dStricmp(purgeMethod, "minSize")) + dQsort(files.address(), files.size(), sizeof(CacheEntry), minSizeSort); + else if(!dStricmp(purgeMethod, "maxSize")) + dQsort(files.address(), files.size(), sizeof(CacheEntry), maxSizeSort); + else if(!dStricmp(purgeMethod, "lastCreated")) + dQsort(files.address(), files.size(), sizeof(CacheEntry), lastCreatedSort); + else + dQsort(files.address(), files.size(), sizeof(CacheEntry), lastModifiedSort); + + // go through and remove the best candidate first (sorted reverse) + while(((curCacheSize >> 10) > quota) && files.size()) + { + curCacheSize -= files.last().mFileObject->getSize(); + + // no sneaky names + if(!dStrstr(files.last().mFileName, "..")) + { + Con::warnf("Removing lighting file '%s'.", files.last().mFileName); + dFileDelete(files.last().mFileName); + } + + files.pop_back(); + } +} + +static S32 QSORT_CALLBACK compareS32(const void * a, const void * b) +{ + return(*((S32 *)a) - *((S32 *)b)); +} + +U32 SceneLighting::calcMissionCRC() +{ + // all the objects + mission chunk + Vector crc; + + // grab the object crcs + for(U32 i = 0; i < mSceneObjects.size(); i++) + crc.push_back( mSceneObjects[i]->mChunkCRC ); + + // grab the missions crc + PersistInfo::MissionChunk curInfo; + getMissionInfo(&curInfo); + crc.push_back(curInfo.mChunkCRC); + + // sort them (order may not have been preserved) + dQsort(crc.address(), crc.size(), sizeof(U32), compareS32); + +#ifdef TORQUE_BIG_ENDIAN + // calculateCRC operates on 8-bit chunks of memory. The memory is a vector + // of U32's, and so the result will be different on big/little endian hardware. + // To fix this, swap endians on the CRC's in the vector. This must be done + // _after_ the qsort. + for( int i = 0; i < crc.size(); i++ ) + crc[i] = endianSwap( crc[i] ); +#endif + + return(CRC::calculateCRC(crc.address(), sizeof(U32) * crc.size(), 0xffffffff)); +} + +bool SceneLighting::ObjectProxy::calcValidation() +{ + mChunkCRC = getResourceCRC(); + if(!mChunkCRC) + return(false); + + return(true); +} + +bool SceneLighting::ObjectProxy::isValidChunk(PersistInfo::PersistChunk * chunk) +{ + return(chunk->mChunkCRC == mChunkCRC); +} + +bool SceneLighting::ObjectProxy::getPersistInfo(PersistInfo::PersistChunk * chunk) +{ + chunk->mChunkCRC = mChunkCRC; + return(true); +} + +bool SceneLighting::ObjectProxy::setPersistInfo(PersistInfo::PersistChunk * chunk) +{ + mChunkCRC = chunk->mChunkCRC; + return(true); +} \ No newline at end of file diff --git a/lighting/common/sceneLighting.h b/lighting/common/sceneLighting.h new file mode 100644 index 0000000..8d15beb --- /dev/null +++ b/lighting/common/sceneLighting.h @@ -0,0 +1,237 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENELIGHTING_H_ +#define _SCENELIGHTING_H_ + +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _SGSCENEPERSIST_H_ +#include "lighting/common/scenePersist.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif + +class ShadowVolumeBSP; +class LightInfo; +class AvailableSLInterfaces; + +class SceneLighting : public SimObject +{ + typedef SimObject Parent; +protected: + AvailableSLInterfaces* mLightingInterfaces; + virtual void getMLName(const char* misName, const U32 missionCRC, const U32 buffSize, char* filenameBuffer); +public: + S32 sgTimeTemp; + S32 sgTimeTemp2; + + virtual void sgNewEvent(U32 light, S32 object, U32 event); + + virtual void sgLightingStartEvent(); + virtual void sgLightingCompleteEvent(); + + virtual void sgTGEPassSetupEvent(); + virtual void sgTGELightStartEvent(U32 light); + virtual void sgTGELightProcessEvent(U32 light, S32 object); + virtual void sgTGELightCompleteEvent(U32 light); + virtual void sgTGESetProgress(U32 light, S32 object); + + virtual void sgSGPassSetupEvent(); + virtual void sgSGObjectStartEvent(S32 object); + virtual void sgSGObjectProcessEvent(U32 light, S32 object); + virtual void sgSGObjectCompleteEvent(S32 object); + virtual void sgSGSetProgress(U32 light, S32 object); + + // 'sg' prefix omitted to conform with existing 'addInterior' method... + void addStatic(ShadowVolumeBSP *shadowVolume, SceneObject *sceneobject, LightInfo *light, S32 level); + + // persist objects moved to 'sgScenePersist.h' for clarity... + // everything below this line should be original code... + + U32 calcMissionCRC(); + + bool verifyMissionInfo(PersistInfo::PersistChunk *); + bool getMissionInfo(PersistInfo::PersistChunk *); + + bool loadPersistInfo(const char *); + bool savePersistInfo(const char *); + + class ObjectProxy; + + enum { + SHADOW_DETAIL = -1 + }; + + //------------------------------------------------------------------------------ + /// Create a proxy for each object to store data. + class ObjectProxy + { + public: + SimObjectPtr mObj; + U32 mChunkCRC; + + ObjectProxy(SceneObject * obj) : mObj(obj){mChunkCRC = 0;} + virtual ~ObjectProxy(){} + SceneObject * operator->() {return(mObj);} + SceneObject * getObject() {return(mObj);} + + /// @name Lighting Interface + /// @{ + virtual bool loadResources() {return(true);} + virtual void init() {} + virtual bool tgePreLight(LightInfo* light) { return preLight(light); } + virtual bool preLight(LightInfo *) {return(false);} + virtual void light(LightInfo *) {} + virtual void postLight(bool lastLight) {} + /// @} + + /// @name Lighting events + /// @{ + // Called when the lighting process begins + virtual void processLightingStart() {} + // Called when a TGELight event is started, return true if status has been reported to console + virtual void processTGELightProcessEvent(U32 curr, U32 max, LightInfo*) { Con::printf(" Lighting object %d of %d...", (curr+1), max); } + // Called for lighting kit lights + virtual bool processStartObjectLightingEvent(U32 current, U32 max) { Con::printf(" Lighting object %d of %d... %s: %s", (current+1), max, mObj->getClassName(), mObj->getName()); return true; } + // Called once per object and SG light - used for calling light on an object + virtual void processSGObjectProcessEvent(LightInfo* currLight) { light(currLight); }; + /// @} + + /// @name Persistence + /// + /// We cache lighting information to cut down on load times. + /// + /// There are flags such as ForceAlways and LoadOnly which allow you + /// to control this behaviour. + /// @{ + bool calcValidation(); + bool isValidChunk(PersistInfo::PersistChunk *); + + virtual U32 getResourceCRC() = 0; + virtual bool setPersistInfo(PersistInfo::PersistChunk *); + virtual bool getPersistInfo(PersistInfo::PersistChunk *); + /// @} + + // Called to figure out if this object should be added to the shadow volume + virtual bool supportsShadowVolume() { return false; } + // Called to retrieve the clip planes of the object. Currently used for terrain lighting, but could be used to speed up other + // lighting calculations. + virtual void getClipPlanes(Vector& planes) { } + // Called to add the object to the shadow volume + virtual void addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level) { } ; + }; + + typedef Vector ObjectProxyList; + + ObjectProxyList mSceneObjects; + ObjectProxyList mLitObjects; + + LightInfoList mLights; + + SceneLighting(AvailableSLInterfaces* lightingInterfaces); + ~SceneLighting(); + + enum Flags { + ForceAlways = BIT(0), ///< Regenerate the scene lighting no matter what. + ForceWritable = BIT(1), ///< Regenerate the scene lighting only if we can write to the lighting cache files. + LoadOnly = BIT(2), ///< Just load cached lighting data. + }; + bool lightScene(const char *, BitSet32 flags = 0); + bool isLighting(); + + S32 mStartTime; + char mFileName[1024]; + SceneGraph * mSceneManager; + + bool light(BitSet32); + void completed(bool success); + void processEvent(U32 light, S32 object); + void processCache(); +}; + +class sgSceneLightingProcessEvent : public SimEvent +{ +private: + U32 sgLightIndex; + S32 sgObjectIndex; + U32 sgEvent; + +public: + enum sgEventTypes + { + sgLightingStartEventType, + sgLightingCompleteEventType, + + sgSGPassSetupEventType, + sgSGObjectStartEventType, + sgSGObjectCompleteEventType, + sgSGObjectProcessEventType, + + sgTGEPassSetupEventType, + sgTGELightStartEventType, + sgTGELightCompleteEventType, + sgTGELightProcessEventType + }; + + sgSceneLightingProcessEvent(U32 lightIndex, S32 objectIndex, U32 event) + { + sgLightIndex = lightIndex; + sgObjectIndex = objectIndex; + sgEvent = event; + } + void process(SimObject * object) + { + AssertFatal(object, "SceneLightingProcessEvent:: null event object!"); + if(!object) + return; + + SceneLighting *sl = static_cast(object); + switch(sgEvent) + { + case sgLightingStartEventType: + sl->sgLightingStartEvent(); + break; + case sgLightingCompleteEventType: + sl->sgLightingCompleteEvent(); + break; + + case sgTGEPassSetupEventType: + sl->sgTGEPassSetupEvent(); + break; + case sgTGELightStartEventType: + sl->sgTGELightStartEvent(sgLightIndex); + break; + case sgTGELightProcessEventType: + sl->sgTGELightProcessEvent(sgLightIndex, sgObjectIndex); + break; + case sgTGELightCompleteEventType: + sl->sgTGELightCompleteEvent(sgLightIndex); + break; + + case sgSGPassSetupEventType: + sl->sgSGPassSetupEvent(); + break; + case sgSGObjectStartEventType: + sl->sgSGObjectStartEvent(sgObjectIndex); + break; + case sgSGObjectProcessEventType: + sl->sgSGObjectProcessEvent(sgLightIndex, sgObjectIndex); + break; + case sgSGObjectCompleteEventType: + sl->sgSGObjectCompleteEvent(sgObjectIndex); + break; + + default: + return; + }; + }; +}; + +extern SceneLighting *gLighting; + +#endif//_SGSCENELIGHTING_H_ diff --git a/lighting/common/sceneLightingGlobals.h b/lighting/common/sceneLightingGlobals.h new file mode 100644 index 0000000..e776cab --- /dev/null +++ b/lighting/common/sceneLightingGlobals.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +static const Point3F BoxNormals[] = +{ + Point3F( 1, 0, 0), + Point3F(-1, 0, 0), + Point3F( 0, 1, 0), + Point3F( 0,-1, 0), + Point3F( 0, 0, 1), + Point3F( 0, 0,-1) +}; + +static U32 BoxVerts[][4] = { + {7,6,4,5}, // +x + {0,2,3,1}, // -x + {7,3,2,6}, // +y + {0,1,5,4}, // -y + {7,5,1,3}, // +z + {0,4,6,2} // -z +}; + +static U32 BoxSharedEdgeMask[][6] = { + {0, 0, 1, 4, 8, 2}, + {0, 0, 2, 8, 4, 1}, + {8, 2, 0, 0, 1, 4}, + {4, 1, 0, 0, 2, 8}, + {1, 4, 8, 2, 0, 0}, + {2, 8, 4, 1, 0, 0} +}; + +static Point3F BoxPnts[] = { + Point3F(0,0,0), + Point3F(0,0,1), + Point3F(0,1,0), + Point3F(0,1,1), + Point3F(1,0,0), + Point3F(1,0,1), + Point3F(1,1,0), + Point3F(1,1,1) +}; + +extern SceneLighting *gLighting; +extern F32 gParellelVectorThresh; +extern F32 gPlaneNormThresh; +extern F32 gPlaneDistThresh; + diff --git a/lighting/common/scenePersist.cpp b/lighting/common/scenePersist.cpp new file mode 100644 index 0000000..bca2609 --- /dev/null +++ b/lighting/common/scenePersist.cpp @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/common/scenePersist.h" + +#include "lighting/lightingInterfaces.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/lightManager.h" + + +U32 PersistInfo::smFileVersion = 0x11; + +PersistInfo::~PersistInfo() +{ + for(U32 i = 0; i < mChunks.size(); i++) + delete mChunks[i]; +} + +//------------------------------------------------------------------------------ +bool PersistInfo::read(Stream & stream) +{ + U32 version; + if(!stream.read(&version) || version != smFileVersion) + return(false); + + U32 numChunks; + if(!stream.read(&numChunks)) + return(false); + + if(numChunks == 0) + return(false); + + // read in all the chunks + for(U32 i = 0; i < numChunks; i++) + { + U32 chunkType; + if(!stream.read(&chunkType)) + return(false); + + // MissionChunk must be first chunk + if(i == 0 && chunkType != PersistChunk::MissionChunkType) + return(false); + if(i != 0 && chunkType == PersistChunk::MissionChunkType) + return(false); + + // Create the right chunk for the system + bool bChunkFound = false; + SceneLightingInterfaces sli = gClientSceneGraph->getLightManager()->getSceneLightingInterface()->mAvailableSystemInterfaces; + for(SceneLightingInterface** itr = sli.begin(); itr != sli.end() && !bChunkFound; itr++) + { + PersistInfo::PersistChunk* pc = (*itr)->createPersistChunk(chunkType); + if (pc != NULL) + { + mChunks.push_back(pc); + bChunkFound = true; + } + } + + if (!bChunkFound) + { + // create the chunk + switch(chunkType) + { + case PersistChunk::MissionChunkType: + mChunks.push_back(new PersistInfo::MissionChunk); + break; + + default: + return(false); + break; + } + } + + // load the chunk info + if(!mChunks[i]->read(stream)) + return(false); + } + + return(true); +} + +bool PersistInfo::write(Stream & stream) +{ + if(!stream.write(smFileVersion)) + return(false); + + if(!stream.write((U32)mChunks.size())) + return(false); + + for(U32 i = 0; i < mChunks.size(); i++) + { + if(!stream.write(mChunks[i]->mChunkType)) + return(false); + if(!mChunks[i]->write(stream)) + return(false); + } + + return(true); +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::PersistInfo::PersistChunk +//------------------------------------------------------------------------------ +bool PersistInfo::PersistChunk::read(Stream & stream) +{ + if(!stream.read(&mChunkCRC)) + return(false); + return(true); +} + +bool PersistInfo::PersistChunk::write(Stream & stream) +{ + if(!stream.write(mChunkCRC)) + return(false); + return(true); +} + +//------------------------------------------------------------------------------ +// Class SceneLighting::PersistInfo::MissionChunk +//------------------------------------------------------------------------------ +PersistInfo::MissionChunk::MissionChunk() +{ + mChunkType = PersistChunk::MissionChunkType; +} \ No newline at end of file diff --git a/lighting/common/scenePersist.h b/lighting/common/scenePersist.h new file mode 100644 index 0000000..1922cdc --- /dev/null +++ b/lighting/common/scenePersist.h @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SGSCENEPERSIST_H_ +#define _SGSCENEPERSIST_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class Stream; + +struct PersistInfo +{ + struct PersistChunk + { + enum { + MissionChunkType = 0, + InteriorChunkType, + TerrainChunkType, + AtlasLightMapChunkType + }; + + U32 mChunkType; + U32 mChunkCRC; + + virtual ~PersistChunk() {} + + virtual bool read(Stream &); + virtual bool write(Stream &); + }; + + struct MissionChunk : public PersistChunk + { + typedef PersistChunk Parent; + MissionChunk(); + }; + + ~PersistInfo(); + + Vector mChunks; + static U32 smFileVersion; + + bool read(Stream &); + bool write(Stream &); +}; + + +#endif//_SGSCENEPERSIST_H_ diff --git a/lighting/common/shadowBase.h b/lighting/common/shadowBase.h new file mode 100644 index 0000000..d3f56c0 --- /dev/null +++ b/lighting/common/shadowBase.h @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTINGSYSTEM_SHADOWBASE_H_ +#define _LIGHTINGSYSTEM_SHADOWBASE_H_ + +class TSRenderState; + +class ShadowBase +{ +public: + virtual ~ShadowBase() {} + virtual bool shouldRender( const SceneState *state ) = 0; + + virtual void update( const SceneState *state ) = 0; + virtual void render(F32 camDist, const TSRenderState &rdata ) = 0; + virtual U32 getLastRenderTime() const = 0; + virtual const F32 getScore() const = 0; +}; + +#endif \ No newline at end of file diff --git a/lighting/common/shadowVolumeBSP.cpp b/lighting/common/shadowVolumeBSP.cpp new file mode 100644 index 0000000..4c165f6 --- /dev/null +++ b/lighting/common/shadowVolumeBSP.cpp @@ -0,0 +1,714 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/common/shadowVolumeBSP.h" + +#include "lighting/lightInfo.h" +#include "math/mPlane.h" + + +ShadowVolumeBSP::ShadowVolumeBSP() : + mSVRoot(0), + mNodeStore(0), + mPolyStore(0), + mFirstInteriorNode(0) +{ +} + +ShadowVolumeBSP::~ShadowVolumeBSP() +{ + for(U32 i = 0; i < mSurfaces.size(); i++) + delete mSurfaces[i]; +} + +void ShadowVolumeBSP::insertShadowVolume(SVNode ** root, U32 volume) +{ + SVNode * traverse = mShadowVolumes[volume]; + + // insert 'em + while(traverse) + { + // copy it + *root = createNode(); + (*root)->mPlaneIndex = traverse->mPlaneIndex; + (*root)->mSurfaceInfo = traverse->mSurfaceInfo; + (*root)->mShadowVolume = traverse->mShadowVolume; + + // do the next + root = &(*root)->mFront; + traverse = traverse->mFront; + } +} + +ShadowVolumeBSP::SVNode::Side ShadowVolumeBSP::whichSide(SVPoly * poly, const PlaneF & plane) const +{ + bool front = false; + bool back = false; + + for(U32 i = 0; i < poly->mWindingCount; i++) + { + switch(plane.whichSide(poly->mWinding[i])) + { + case PlaneF::Front: + if(back) + return(SVNode::Split); + front = true; + break; + + case PlaneF::Back: + if(front) + return(SVNode::Split); + back = true; + break; + + default: + break; + } + } + + AssertFatal(!(front && back), "ShadowVolumeBSP::whichSide - failed to classify poly"); + + if(!front && !back) + return(SVNode::On); + + return(front ? SVNode::Front : SVNode::Back); +} + +void ShadowVolumeBSP::splitPoly(SVPoly * poly, const PlaneF & plane, SVPoly ** front, SVPoly ** back) +{ + PlaneF::Side sides[SVPoly::MaxWinding]; + + U32 i; + for(i = 0; i < poly->mWindingCount; i++) + sides[i] = plane.whichSide(poly->mWinding[i]); + + // create the polys + (*front) = createPoly(); + (*back) = createPoly(); + + // copy the info + (*front)->mWindingCount = (*back)->mWindingCount = 0; + (*front)->mPlane = (*back)->mPlane = poly->mPlane; + (*front)->mTarget = (*back)->mTarget = poly->mTarget; + (*front)->mSurfaceInfo = (*back)->mSurfaceInfo = poly->mSurfaceInfo; + (*front)->mShadowVolume = (*back)->mShadowVolume = poly->mShadowVolume; + + // + for(i = 0; i < poly->mWindingCount; i++) + { + U32 j = (i+1) % poly->mWindingCount; + + if(sides[i] == PlaneF::On) + { + (*front)->mWinding[(*front)->mWindingCount++] = poly->mWinding[i]; + (*back)->mWinding[(*back)->mWindingCount++] = poly->mWinding[i]; + } + else if(sides[i] == PlaneF::Front) + { + (*front)->mWinding[(*front)->mWindingCount++] = poly->mWinding[i]; + + if(sides[j] == PlaneF::Back) + { + const Point3F & a = poly->mWinding[i]; + const Point3F & b = poly->mWinding[j]; + + F32 t = plane.intersect(a, b); + AssertFatal(t >=0 && t <= 1, "ShadowVolumeBSP::splitPoly - bad plane intersection"); + + Point3F pos; + pos.interpolate(a, b, t); + + // + (*front)->mWinding[(*front)->mWindingCount++] = + (*back)->mWinding[(*back)->mWindingCount++] = pos; + } + } + else if(sides[i] == PlaneF::Back) + { + (*back)->mWinding[(*back)->mWindingCount++] = poly->mWinding[i]; + + if(sides[j] == PlaneF::Front) + { + const Point3F & a = poly->mWinding[i]; + const Point3F & b = poly->mWinding[j]; + + F32 t = plane.intersect(a, b); + AssertFatal(t >=0 && t <= 1, "ShadowVolumeBSP::splitPoly - bad plane intersection"); + + Point3F pos; + pos.interpolate(a, b, t); + + (*front)->mWinding[(*front)->mWindingCount++] = + (*back)->mWinding[(*back)->mWindingCount++] = pos; + } + } + } + + AssertFatal((*front)->mWindingCount && (*back)->mWindingCount, "ShadowVolume::split - invalid split"); +} + +void ShadowVolumeBSP::addUniqueVolume(SurfaceInfo * surfaceInfo, U32 volume) +{ + if(!surfaceInfo) + return; + + for(U32 i = 0; i < surfaceInfo->mShadowed.size(); i++) + if(surfaceInfo->mShadowed[i] == volume) + return; + + // add it + surfaceInfo->mShadowed.push_back(volume); +} + +void ShadowVolumeBSP::insertPoly(SVNode ** root, SVPoly * poly) +{ + if(!(*root)) + { + insertShadowVolume(root, poly->mShadowVolume); + + if(poly->mSurfaceInfo && !mFirstInteriorNode) + mFirstInteriorNode = mShadowVolumes[poly->mShadowVolume]; + + if(poly->mTarget) + addUniqueVolume(poly->mTarget->mSurfaceInfo, poly->mShadowVolume); + + recyclePoly(poly); + return; + } + + const PlaneF & plane = getPlane((*root)->mPlaneIndex); + + // + switch(whichSide(poly, plane)) + { + case SVNode::On: + case SVNode::Front: + insertPolyFront(root, poly); + break; + + case SVNode::Back: + insertPolyBack(root, poly); + break; + + case SVNode::Split: + { + SVPoly * front; + SVPoly * back; + + splitPoly(poly, plane, &front, &back); + recyclePoly(poly); + + insertPolyFront(root, front); + insertPolyBack(root, back); + + break; + } + } +} + +void ShadowVolumeBSP::insertPolyFront(SVNode ** root, SVPoly * poly) +{ + // POLY type node? + if(!(*root)->mFront) + { + if(poly->mSurfaceInfo && !mFirstInteriorNode) + mFirstInteriorNode = mShadowVolumes[poly->mShadowVolume]; + addUniqueVolume(poly->mSurfaceInfo, (*root)->mShadowVolume); + recyclePoly(poly); + } + else + insertPoly(&(*root)->mFront, poly); +} + +void ShadowVolumeBSP::insertPolyBack(SVNode ** root, SVPoly * poly) +{ + // list of nodes where an interior has been added + if(poly->mSurfaceInfo && !(*root)->mSurfaceInfo && !(*root)->mBack) + { + if(!mFirstInteriorNode) + mFirstInteriorNode = mShadowVolumes[poly->mShadowVolume]; + mParentNodes.push_back(*root); + } + + // POLY type node? + if(!(*root)->mFront) + { + poly->mTarget = (*root); + insertPoly(&(*root)->mBack, poly); + } + else + insertPoly(&(*root)->mBack, poly); +} + +//------------------------------------------------------------------------------ + +ShadowVolumeBSP::SVNode * ShadowVolumeBSP::createNode() +{ + SVNode * node; + if(mNodeStore) + { + node = mNodeStore; + mNodeStore = mNodeStore->mFront; + } + else + node = mNodeChunker.alloc(); + + // + node->mFront = node->mBack = 0; + node->mShadowVolume = 0; + node->mSurfaceInfo = 0; + + return(node); +} + +void ShadowVolumeBSP::recycleNode(SVNode * node) +{ + if(!node) + return; + + recycleNode(node->mFront); + recycleNode(node->mBack); + + // + node->mFront = mNodeStore; + node->mBack = 0; + mNodeStore = node; +} + +ShadowVolumeBSP::SVPoly * ShadowVolumeBSP::createPoly() +{ + SVPoly * poly; + + if(mPolyStore) + { + poly = mPolyStore; + mPolyStore = mPolyStore->mNext; + } + else + poly = mPolyChunker.alloc(); + + // + poly->mNext = 0; + poly->mTarget = 0; + poly->mSurfaceInfo = 0; + poly->mShadowVolume = 0; + poly->mWindingCount = 0; + + for (U32 i = 0; i < SVPoly::MaxWinding; i++) + poly->mWinding[i] = Point3F(0.0f, 0.0f, 0.0f); + + return(poly); +} + +void ShadowVolumeBSP::recyclePoly(SVPoly * poly) +{ + if(!poly) + return; + + recyclePoly(poly->mNext); + + // + poly->mNext = mPolyStore; + mPolyStore = poly; +} + +U32 ShadowVolumeBSP::insertPlane(const PlaneF & plane) +{ + mPlanes.push_back(plane); + return(mPlanes.size() - 1); +} + +const PlaneF & ShadowVolumeBSP::getPlane(U32 index) const +{ + AssertFatal(index < mPlanes.size(), "ShadowVolumeBSP::getPlane - index out of range"); + return(mPlanes[index]); +} + +ShadowVolumeBSP::SVNode * ShadowVolumeBSP::getShadowVolume(U32 index) +{ + AssertFatal(index < mShadowVolumes.size(), "ShadowVolumeBSP::getShadowVolume - index out of range"); + return(mShadowVolumes[index]); +} + +bool ShadowVolumeBSP::testPoint(SVNode * root, const Point3F & pnt) +{ + const PlaneF & plane = getPlane(root->mPlaneIndex); + switch(plane.whichSide(pnt)) + { + case PlaneF::On: + + if(!root->mFront) + return(true); + else + { + if(testPoint(root->mFront, pnt)) + return(true); + else + { + if(!root->mBack) + return(false); + else + return(testPoint(root->mBack, pnt)); + } + } + break; + + // + case PlaneF::Front: + if(root->mFront) + return(testPoint(root->mFront, pnt)); + else + return(true); + break; + + // + case PlaneF::Back: + if(root->mBack) + return(testPoint(root->mBack, pnt)); + else + return(false); + break; + } + + return(false); +} + +//------------------------------------------------------------------------------ +bool ShadowVolumeBSP::testPoly(SVNode * root, SVPoly * poly) +{ + const PlaneF & plane = getPlane(root->mPlaneIndex); + switch(whichSide(poly, plane)) + { + case SVNode::On: + case SVNode::Front: + if(root->mFront) + return(testPoly(root->mFront, poly)); + + recyclePoly(poly); + return(true); + + case SVNode::Back: + if(root->mBack) + return(testPoly(root->mBack, poly)); + recyclePoly(poly); + break; + + case SVNode::Split: + { + if(!root->mFront) + { + recyclePoly(poly); + return(true); + } + + SVPoly * front; + SVPoly * back; + + splitPoly(poly, plane, &front, &back); + recyclePoly(poly); + + if(testPoly(root->mFront, front)) + { + recyclePoly(back); + return(true); + } + + if(root->mBack) + return(testPoly(root->mBack, back)); + + recyclePoly(back); + break; + } + } + return(false); +} + +//------------------------------------------------------------------------------ +void ShadowVolumeBSP::buildPolyVolume(SVPoly * poly, LightInfo * light) +{ + if(light->getType() != LightInfo::Vector) + return; + + // build the poly + Point3F pointOffset = light->getDirection() * 10.f; + + // create the shadow volume + mShadowVolumes.increment(); + SVNode ** traverse = &mShadowVolumes.last(); + U32 shadowVolumeIndex = mShadowVolumes.size() - 1; + + for(U32 i = 0; i < poly->mWindingCount; i++) + { + U32 j = (i + 1) % poly->mWindingCount; + if(poly->mWinding[i] == poly->mWinding[j]) + continue; + + (*traverse) = createNode(); + Point3F & a = poly->mWinding[i]; + Point3F & b = poly->mWinding[j]; + Point3F c = b + pointOffset; + + (*traverse)->mPlaneIndex = insertPlane(PlaneF(a,b,c)); + (*traverse)->mShadowVolume = shadowVolumeIndex; + + traverse = &(*traverse)->mFront; + } + + // do the poly node + (*traverse) = createNode(); + (*traverse)->mPlaneIndex = insertPlane(poly->mPlane); + (*traverse)->mShadowVolume = poly->mShadowVolume = shadowVolumeIndex; +} + +ShadowVolumeBSP::SVPoly * ShadowVolumeBSP::copyPoly(SVPoly * src) +{ + SVPoly * poly = createPoly(); + dMemcpy(poly, src, sizeof(SVPoly)); + + poly->mTarget = 0; + poly->mNext = 0; + + return(poly); +} + +//------------------------------------------------------------------------------ +void ShadowVolumeBSP::addToPolyList(SVPoly ** store, SVPoly * poly) const +{ + poly->mNext = *store; + *store = poly; +} + +//------------------------------------------------------------------------------ +void ShadowVolumeBSP::clipPoly(SVNode * root, SVPoly ** store, SVPoly * poly) +{ + if(!root) + { + recyclePoly(poly); + return; + } + + const PlaneF & plane = getPlane(root->mPlaneIndex); + + switch(whichSide(poly, plane)) + { + case SVNode::On: + case SVNode::Back: + if(root->mBack) + clipPoly(root->mBack, store, poly); + else + addToPolyList(store, poly); + break; + + case SVNode::Front: + // encountered POLY node? + if(!root->mFront) + { + recyclePoly(poly); + return; + } + else + clipPoly(root->mFront, store, poly); + break; + + case SVNode::Split: + { + SVPoly * front; + SVPoly * back; + + splitPoly(poly, plane, &front, &back); + AssertFatal(front && back, "ShadowVolumeBSP::clipPoly: invalid split"); + recyclePoly(poly); + + // front + if(!root->mFront) + { + recyclePoly(front); + return; + } + else + clipPoly(root->mFront, store, front); + + // back + if(root->mBack) + clipPoly(root->mBack, store, back); + else + addToPolyList(store, back); + break; + } + } +} + +// clip a poly to it's own shadow volume +void ShadowVolumeBSP::clipToSelf(SVNode * root, SVPoly ** store, SVPoly * poly) +{ + if(!root) + { + addToPolyList(store, poly); + return; + } + + const PlaneF & plane = getPlane(root->mPlaneIndex); + + switch(whichSide(poly, plane)) + { + case SVNode::Front: + clipToSelf(root->mFront, store, poly); + break; + + case SVNode::On: + addToPolyList(store, poly); + break; + + case SVNode::Back: + recyclePoly(poly); + break; + + case SVNode::Split: + { + SVPoly * front = 0; + SVPoly * back = 0; + splitPoly(poly, plane, &front, &back); + AssertFatal(front && back, "ShadowVolumeBSP::clipToSelf: invalid split"); + + recyclePoly(poly); + recyclePoly(back); + + clipToSelf(root->mFront, store, front); + break; + } + } +} + +//------------------------------------------------------------------------------ +F32 ShadowVolumeBSP::getPolySurfaceArea(SVPoly * poly) const +{ + if(!poly) + return(0.f); + + Point3F areaNorm(0,0,0); + for(U32 i = 0; i < poly->mWindingCount; i++) + { + U32 j = (i + 1) % poly->mWindingCount; + + Point3F tmp; + mCross(poly->mWinding[i], poly->mWinding[j], &tmp); + + areaNorm += tmp; + } + + F32 area = mDot(poly->mPlane, areaNorm); + + if(area < 0.f) + area *= -0.5f; + else + area *= 0.5f; + + if(poly->mNext) + area += getPolySurfaceArea(poly->mNext); + + return(area); +} + +//------------------------------------------------------------------------------ +F32 ShadowVolumeBSP::getClippedSurfaceArea(SVNode * root, SVPoly * poly) +{ + SVPoly * store = 0; + clipPoly(root, &store, poly); + + F32 area = getPolySurfaceArea(store); + recyclePoly(store); + return(area); +} + + +//------------------------------------------------------------------------------- +// Class SceneLighting::ShadowVolumeBSP +//------------------------------------------------------------------------------- + +void ShadowVolumeBSP::movePolyList(SVPoly ** dest, SVPoly * list) const +{ + while(list) + { + SVPoly * next = list->mNext; + addToPolyList(dest, list); + list = next; + } +} + + +F32 ShadowVolumeBSP::getLitSurfaceArea(SVPoly * poly, SurfaceInfo * surfaceInfo) +{ + // clip the poly to the shadow volumes + SVPoly * polyStore = poly; + + for(U32 i = 0; polyStore && (i < surfaceInfo->mShadowed.size()); i++) + { + SVPoly * polyList = 0; + SVPoly * traverse = polyStore; + + while(traverse) + { + SVPoly * next = traverse->mNext; + traverse->mNext = 0; + + SVPoly * currentStore = 0; + + clipPoly(mShadowVolumes[surfaceInfo->mShadowed[i]], ¤tStore, traverse); + + if(currentStore) + movePolyList(&polyList, currentStore); + + traverse = next; + } + polyStore = polyList; + } + + // get the lit area + F32 area = getPolySurfaceArea(polyStore); + recyclePoly(polyStore); + return(area); +} + +//------------------------------------------------------------------------------ +void ShadowVolumeBSP::removeLastInterior() +{ + if(!mSVRoot || !mFirstInteriorNode) + return; + + AssertFatal(mFirstInteriorNode->mSurfaceInfo, "No surface info for first interior node!"); + + // reset the planes + mPlanes.setSize(mFirstInteriorNode->mPlaneIndex); + + U32 i; + + // flush the shadow volumes + for(i = mFirstInteriorNode->mShadowVolume; i < mShadowVolumes.size(); i++) + recycleNode(mShadowVolumes[i]); + mShadowVolumes.setSize(mFirstInteriorNode->mShadowVolume); + + // flush the interior nodes + if(!mParentNodes.size() && (mFirstInteriorNode->mShadowVolume == mSVRoot->mShadowVolume)) + { + recycleNode(mSVRoot); + mSVRoot = 0; + } + else + { + for(i = 0; i < mParentNodes.size(); i++) + { + recycleNode(mParentNodes[i]->mBack); + mParentNodes[i]->mBack = 0; + } + } + + // flush the surfaces + for(i = 0; i < mSurfaces.size(); i++) + delete mSurfaces[i]; + mSurfaces.clear(); + + mFirstInteriorNode = 0; +} diff --git a/lighting/common/shadowVolumeBSP.h b/lighting/common/shadowVolumeBSP.h new file mode 100644 index 0000000..c781ebe --- /dev/null +++ b/lighting/common/shadowVolumeBSP.h @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOWVOLUMEBSP_H_ +#define _SHADOWVOLUMEBSP_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "lighting/lightManager.h" +#endif + +/// Used to calculate shadows. +class ShadowVolumeBSP +{ + public: + ShadowVolumeBSP(); + ~ShadowVolumeBSP(); + + struct SVNode; + struct SurfaceInfo + { + U32 mSurfaceIndex; + U32 mPlaneIndex; + Vector mShadowed; + SVNode * mShadowVolume; + }; + + struct SVNode + { + enum Side + { + Front = 0, + Back = 1, + On = 2, + Split = 3 + }; + + SVNode * mFront; + SVNode * mBack; + U32 mPlaneIndex; + U32 mShadowVolume; + + /// Used with shadowed interiors. + SurfaceInfo * mSurfaceInfo; + }; + + struct SVPoly + { + enum { + MaxWinding = 32 + }; + + U32 mWindingCount; + Point3F mWinding[MaxWinding]; + + PlaneF mPlane; + SVNode * mTarget; + U32 mShadowVolume; + SVPoly * mNext; + SurfaceInfo * mSurfaceInfo; + }; + + void insertPoly(SVNode **, SVPoly *); + void insertPolyFront(SVNode **, SVPoly *); + void insertPolyBack(SVNode **, SVPoly *); + + void splitPoly(SVPoly *, const PlaneF &, SVPoly **, SVPoly **); + void insertShadowVolume(SVNode **, U32); + void addUniqueVolume(SurfaceInfo *, U32); + + SVNode::Side whichSide(SVPoly *, const PlaneF &) const; + + // + bool testPoint(SVNode *, const Point3F &); + bool testPoly(SVNode *, SVPoly *); + void addToPolyList(SVPoly **, SVPoly *) const; + void clipPoly(SVNode *, SVPoly **, SVPoly *); + void clipToSelf(SVNode *, SVPoly **, SVPoly *); + F32 getPolySurfaceArea(SVPoly *) const; + F32 getClippedSurfaceArea(SVNode *, SVPoly *); + void movePolyList(SVPoly **, SVPoly *) const; + F32 getLitSurfaceArea(SVPoly *, SurfaceInfo *); + + Vector mSurfaces; + + Chunker mNodeChunker; + Chunker mPolyChunker; + + SVNode * createNode(); + void recycleNode(SVNode *); + + SVPoly * createPoly(); + void recyclePoly(SVPoly *); + + U32 insertPlane(const PlaneF &); + const PlaneF & getPlane(U32) const; + + // + SVNode * mSVRoot; + Vector mShadowVolumes; + SVNode * getShadowVolume(U32); + + Vector mPlanes; + SVNode * mNodeStore; + SVPoly * mPolyStore; + + // used to remove the last inserted interior from the tree + Vector mParentNodes; + SVNode * mFirstInteriorNode; + void removeLastInterior(); + + /// @name Access functions + /// @{ + void insertPoly(SVPoly * poly) {insertPoly(&mSVRoot, poly);} + bool testPoint(Point3F & pnt) {return(testPoint(mSVRoot, pnt));} + bool testPoly(SVPoly * poly) {return(testPoly(mSVRoot, poly));} + F32 getClippedSurfaceArea(SVPoly * poly) {return(getClippedSurfaceArea(mSVRoot, poly));} + /// @} + + /// @name Helpers + /// @{ + void buildPolyVolume(SVPoly *, LightInfo *); + SVPoly * copyPoly(SVPoly *); + /// @} +}; + +#endif diff --git a/lighting/lightInfo.cpp b/lighting/lightInfo.cpp new file mode 100644 index 0000000..31ac5f4 --- /dev/null +++ b/lighting/lightInfo.cpp @@ -0,0 +1,171 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/lightInfo.h" + +#include "math/mMath.h" +#include "core/color.h" +#include "gfx/gfxCubemap.h" +#include "console/simObject.h" +#include "math/mathUtils.h" + + +LightInfoExType::LightInfoExType( const char *type ) +{ + TypeMap::Iterator iter = getTypeMap().find( type ); + if ( iter == getTypeMap().end() ) + iter = getTypeMap().insertUnique( type, getTypeMap().size() ); + + mTypeIndex = iter->value; +} + + +LightInfo::LightInfo() + : mTransform( true ), + mColor( 0.0f, 0.0f, 0.0f, 1.0f ), + mBrightness( 1.0f ), + mAmbient( 0.0f, 0.0f, 0.0f, 1.0f ), + mRange( 1.0f, 1.0f, 1.0f ), + mInnerConeAngle( 90.0f ), + mOuterConeAngle( 90.0f ), + mType( Vector ), + mCastShadows( false ), + mPriority( 1.0f ), + mScore( 0.0f ) +{ +} + +LightInfo::~LightInfo() +{ + deleteAllLightInfoEx(); +} + +void LightInfo::set( const LightInfo *light ) +{ + mTransform = light->mTransform; + mColor = light->mColor; + mBrightness = light->mBrightness; + mAmbient = light->mAmbient; + mRange = light->mRange; + mInnerConeAngle = light->mInnerConeAngle; + mOuterConeAngle = light->mOuterConeAngle; + mType = light->mType; + mCastShadows = light->mCastShadows; + + for ( U32 i=0; i < mExtended.size(); i++ ) + { + LightInfoEx *ex = light->mExtended[ i ]; + if ( ex ) + mExtended[i]->set( ex ); + else + { + delete mExtended[i]; + mExtended[i] = NULL; + } + } +} + +void LightInfo::setGFXLight( GFXLightInfo *outLight ) +{ + switch( getType() ) + { + case LightInfo::Point : + outLight->mType = GFXLightInfo::Point; + break; + case LightInfo::Spot : + outLight->mType = GFXLightInfo::Spot; + break; + case LightInfo::Vector: + outLight->mType = GFXLightInfo::Vector; + break; + case LightInfo::Ambient: + outLight->mType = GFXLightInfo::Ambient; + break; + default: + break; + } + + outLight->mPos = getPosition(); + outLight->mDirection = getDirection(); + outLight->mColor = mColor * mBrightness; + outLight->mAmbient = mAmbient; + outLight->mRadius = mRange.x; + outLight->mInnerConeAngle = mInnerConeAngle; + outLight->mOuterConeAngle = mOuterConeAngle; +} + +void LightInfo::setDirection( const VectorF &dir ) +{ + MathUtils::getMatrixFromForwardVector( mNormalize( dir ), &mTransform ); +} + +void LightInfo::deleteAllLightInfoEx() +{ + for ( U32 i = 0; i < mExtended.size(); i++ ) + delete mExtended[ i ]; + + mExtended.clear(); +} + +LightInfoEx* LightInfo::getExtended( const LightInfoExType &type ) const +{ + if ( type >= mExtended.size() ) + return NULL; + + return mExtended[ type ]; +} + +void LightInfo::addExtended( LightInfoEx *lightInfoEx ) +{ + AssertFatal( lightInfoEx, "LightInfo::addExtended() - Got null extended light info!" ); + + const LightInfoExType &type = lightInfoEx->getType(); + + while ( mExtended.size() <= type ) + mExtended.push_back( NULL ); + + delete mExtended[type]; + mExtended[type] = lightInfoEx; +} + +void LightInfo::packExtended( BitStream *stream ) const +{ + for ( U32 i = 0; i < mExtended.size(); i++ ) + if ( mExtended[ i ] ) + mExtended[ i ]->packUpdate( stream ); +} + +void LightInfo::unpackExtended( BitStream *stream ) +{ + for ( U32 i = 0; i < mExtended.size(); i++ ) + if ( mExtended[ i ] ) + mExtended[ i ]->unpackUpdate( stream ); +} + +void LightInfoList::registerLight( LightInfo *light ) +{ + if(!light) + return; + // just add the light, we'll try to scan for dupes later... + push_back(light); +} + +void LightInfoList::unregisterLight( LightInfo *light ) +{ + // remove all of them... + LightInfoList &list = *this; + for(U32 i=0; i TypeMap; + + /// Returns the map of all the info types. We create + /// it as a method static so that its available to other + /// statics regardless of initialization order. + static inline TypeMap& getTypeMap() + { + static TypeMap smTypeMap; + return smTypeMap; + } + + /// The info type index for this type. + U32 mTypeIndex; + +public: + + LightInfoExType( const char *type ); + + inline LightInfoExType( const LightInfoExType &type ) + : mTypeIndex( type.mTypeIndex ) + { + } + + inline operator U32 () const { return mTypeIndex; } +}; + +/// This is the base class for extended lighting info +/// that lies outside of the normal info stored in LightInfo. +class LightInfoEx +{ +public: + + /// Basic destructor so we can delete the extended info + /// without knowing the concrete type. + virtual ~LightInfoEx() { } + + /// + virtual const LightInfoExType& getType() const = 0; + + /// Copy the values from the other LightInfoEx. + virtual void set( const LightInfoEx *ex ) {} + + /// + virtual void packUpdate( BitStream *stream ) const {} + + /// + virtual void unpackUpdate( BitStream *stream ) {} +}; + + +/// This is the base light information class that will be tracked by the +/// engine. Should basically contain a bounding volume and methods to interact +/// with the rest of the system (for example, setting GFX fixed function lights). +class LightInfo +{ +public: + + enum Type + { + Point = 0, + Spot = 1, + Vector = 2, + Ambient = 3, + Count = 4, + }; + +protected: + + Type mType; + + /// The primary light color. + ColorF mColor; + + + F32 mBrightness; + + ColorF mAmbient; + + MatrixF mTransform; + + Point3F mRange; + + F32 mInnerConeAngle; + + F32 mOuterConeAngle; + + bool mCastShadows; + + ::Vector mExtended; + + /// The priority of this light used for + /// light and shadow scoring. + F32 mPriority; + + /// A temporary which holds the score used + /// when prioritizing lights for rendering. + F32 mScore; + +public: + + LightInfo(); + ~LightInfo(); + + // Copies data passed in from light + void set( const LightInfo *light ); + + // Sets a fixed function GFXLight with our properties + void setGFXLight( GFXLightInfo *light ); + + // Accessors + Type getType() const { return mType; } + void setType( Type val ) { mType = val; } + + const MatrixF& getTransform() const { return mTransform; } + void setTransform( const MatrixF &xfm ) { mTransform = xfm; } + + Point3F getPosition() const { return mTransform.getPosition(); } + void setPosition( const Point3F &pos ) { mTransform.setPosition( pos ); } + + VectorF getDirection() const { return mTransform.getForwardVector(); } + void setDirection( const VectorF &val ); + + const ColorF& getColor() const { return mColor; } + void setColor( const ColorF &val ) { mColor = val; } + + F32 getBrightness() const { return mBrightness; } + void setBrightness( F32 val ) { mBrightness = val; } + + const ColorF& getAmbient() const { return mAmbient; } + void setAmbient( const ColorF &val ) { mAmbient = val; } + + const Point3F& getRange() const { return mRange; } + void setRange( const Point3F &range ) { mRange = range; } + void setRange( F32 range ) { mRange.set( range, range, range ); } + + F32 getInnerConeAngle() const { return mInnerConeAngle; } + void setInnerConeAngle( F32 val ) { mInnerConeAngle = val; } + + F32 getOuterConeAngle() const { return mOuterConeAngle; } + void setOuterConeAngle( F32 val ) { mOuterConeAngle = val; } + + bool getCastShadows() const { return mCastShadows; } + void setCastShadows( bool castShadows ) { mCastShadows = castShadows; } + + void setPriority( F32 priority ) { mPriority = priority; } + F32 getPriority() const { return mPriority; } + + void setScore( F32 score ) { mScore = score; } + F32 getScore() const { return mScore; } + + /// Helper function for getting the extended light info. + /// @see getExtended + template + inline ExClass* getExtended() const { return (ExClass*)getExtended( ExClass::Type ); } + + /// Returns the extended light info for the selected type. + LightInfoEx* getExtended( const LightInfoExType &type ) const; + + /// Adds the extended info to the light deleting the + /// existing extended info if it has one. + void addExtended( LightInfoEx *lightInfoEx ); + + /// + void deleteAllLightInfoEx(); + + /// + void packExtended( BitStream *stream ) const; + + /// + void unpackExtended( BitStream *stream ); +}; + + +/// +class LightInfoList : public Vector +{ +public: + void registerLight( LightInfo *light ); + void unregisterLight( LightInfo *light ); +}; + + +/// When the scene is queried for lights, the light manager will get +/// this interface to trigger a register light call. +class ISceneLight +{ +public: + + virtual ~ISceneLight() {} + + /// Submit lights to the light manager passed in. + virtual void submitLights( LightManager *lm, bool staticLighting ) = 0; + + /// + virtual LightInfo* getLight() = 0; +}; + +#endif // _LIGHTINFO_H_ diff --git a/lighting/lightManager.cpp b/lighting/lightManager.cpp new file mode 100644 index 0000000..7210c56 --- /dev/null +++ b/lighting/lightManager.cpp @@ -0,0 +1,609 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/lightManager.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "core/util/safeDelete.h" +#include "console/sim.h" +#include "console/simSet.h" +#include "sceneGraph/sceneGraph.h" +#include "materials/materialManager.h" +#include "materials/sceneData.h" +#include "lighting/lightInfo.h" +#include "lighting/lightingInterfaces.h" +#include "T3D/gameConnection.h" +#include "gfx/gfxStringEnumTranslate.h" + + +Signal LightManager::smActivateSignal; + + +LightManager::LightManager( const char *name, const char *id ) + : mName( name ), + mId( id ), + mIsActive( false ), + mSceneManager( NULL ), + mDefaultLight( NULL ), + mAvailableSLInterfaces( NULL ), + mCullPos( Point3F::Zero ) +{ + _getLightManagers().insert( mName, this ); + + dMemset( &mSpecialLights, 0, sizeof( mSpecialLights ) ); +} + +LightManager::~LightManager() +{ + _getLightManagers().erase( mName ); + SAFE_DELETE( mAvailableSLInterfaces ); + SAFE_DELETE( mDefaultLight ); +} + +LightManagerMap& LightManager::_getLightManagers() +{ + static LightManagerMap lightManagerMap; + return lightManagerMap; +} + +LightManager* LightManager::findByName( const char *name ) +{ + LightManagerMap &lightManagers = _getLightManagers(); + + LightManagerMap::Iterator iter = lightManagers.find( name ); + if ( iter != lightManagers.end() ) + return iter->value; + + return NULL; +} + +void LightManager::getLightManagerNames( String *outString ) +{ + LightManagerMap &lightManagers = _getLightManagers(); + LightManagerMap::Iterator iter = lightManagers.begin(); + for ( ; iter != lightManagers.end(); iter++ ) + *outString += iter->key + "\t"; + + // TODO! + //outString->rtrim(); +} + +LightInfo* LightManager::createLightInfo() +{ + LightInfo *outLight = new LightInfo; + + LightManagerMap &lightManagers = _getLightManagers(); + LightManagerMap::Iterator iter = lightManagers.begin(); + for ( ; iter != lightManagers.end(); iter++ ) + { + LightManager *lm = iter->value; + lm->_addLightInfoEx( outLight ); + } + + return outLight; +} + +void LightManager::initLightFields() +{ + LightManagerMap &lightManagers = _getLightManagers(); + + LightManagerMap::Iterator iter = lightManagers.begin(); + for ( ; iter != lightManagers.end(); iter++ ) + { + LightManager *lm = iter->value; + lm->_initLightFields(); + } +} + +void LightManager::activate( SceneGraph *sceneManager ) +{ + AssertFatal( sceneManager, "LightManager::activate() - Got null scene manager!" ); + AssertFatal( mIsActive == false, "LightManager::activate() - Already activated!" ); + + mIsActive = true; + mSceneManager = sceneManager; + + Con::executef("onLightManagerActivate", getName()); +} + +void LightManager::deactivate() +{ + AssertFatal( mIsActive == true, "LightManager::deactivate() - Already deactivated!" ); + + if( Sim::getRootGroup() ) // To protect against shutdown. + Con::executef("onLightManagerDeactivate", getName()); + + mIsActive = false; + mSceneManager = NULL; + + // Just in case... make sure we're all clear. + unregisterAllLights(); +} + +LightInfo* LightManager::getDefaultLight() +{ + // The sun is always our default light when + // when its registered. + if ( mSpecialLights[ LightManager::slSunLightType ] ) + return mSpecialLights[ LightManager::slSunLightType ]; + + // Else return a dummy special light. + if ( !mDefaultLight ) + mDefaultLight = createLightInfo(); + return mDefaultLight; +} + +LightInfo* LightManager::getSpecialLight( LightManager::SpecialLightTypesEnum type, bool useDefault ) +{ + if ( mSpecialLights[type] ) + return mSpecialLights[type]; + + if ( useDefault ) + return getDefaultLight(); + + return NULL; +} + +void LightManager::setSpecialLight( LightManager::SpecialLightTypesEnum type, LightInfo *light ) +{ + if ( light && type == slSunLightType ) + { + // The sun must be specially positioned and ranged + // so that it can be processed like a point light + // in the stock light shader used by Basic Lighting. + + light->setPosition( mCullPos - ( light->getDirection() * 10000.0f ) ); + light->setRange( 2000000.0f ); + } + + mSpecialLights[type] = light; + registerGlobalLight( light, NULL ); +} + +void LightManager::registerGlobalLights( const Frustum *frustum, bool staticLighting ) +{ + PROFILE_SCOPE( LightManager_RegisterGlobalLights ); + + // TODO: We need to work this out... + // + // 1. Why do we register and unregister lights on every + // render when they don't often change... shouldn't we + // just register once and keep them? + // + // 2. If we do culling of lights should this happen as part + // of registration or somewhere else? + // + + // Grab the lights to process. + Vector activeLights; + const U32 lightMask = LightObjectType; + + if ( staticLighting || !frustum ) + { + // We're processing static lighting or want all the lights + // in the container registerd... so no culling. + getSceneManager()->getContainer()->findObjectList( lightMask, &activeLights ); + } + else + { + // Cull the lights using the frustum. + getSceneManager()->getContainer()->findObjectList( *frustum, lightMask, &activeLights ); + + // Store the culling position for sun placement + // later... see setSpecialLight. + mCullPos = frustum->getPosition(); + + // HACK: Make sure the control object always gets + // processed as lights mounted to it don't change + // the shape bounds and can often get culled. + + GameConnection *conn = GameConnection::getConnectionToServer(); + if ( conn->getControlObject() ) + { + GameBase *conObject = conn->getControlObject(); + activeLights.push_back_unique( conObject ); + } + } + + // Let the lights register themselves. + for ( U32 i = 0; i < activeLights.size(); i++ ) + { + ISceneLight *lightInterface = dynamic_cast( activeLights[i] ); + if ( lightInterface ) + lightInterface->submitLights( this, staticLighting ); + } +} + +void LightManager::registerGlobalLight( LightInfo *light, SimObject *obj ) +{ + AssertFatal( !mRegisteredLights.contains( light ), + "LightManager::registerGlobalLight - This light is already registered!" ); + + mRegisteredLights.push_back( light ); +} + +void LightManager::unregisterGlobalLight( LightInfo *light ) +{ + mRegisteredLights.unregisterLight( light ); + + // If this is the sun... clear the special light too. + if ( light == mSpecialLights[slSunLightType] ) + dMemset( mSpecialLights, 0, sizeof( mSpecialLights ) ); +} + +void LightManager::registerLocalLight( LightInfo *light ) +{ + // TODO: What should we do here? +} + +void LightManager::unregisterLocalLight( LightInfo *light ) +{ + // TODO: What should we do here? +} + +void LightManager::unregisterAllLights() +{ + dMemset( mSpecialLights, 0, sizeof( mSpecialLights ) ); + mRegisteredLights.clear(); + mBestLights.clear(); +} + +void LightManager::getAllUnsortedLights( LightInfoList &list ) +{ + list.merge( mRegisteredLights ); +} + +void LightManager::setupLights( LightReceiver *obj, + const Point3F &camerapos, + const Point3F &cameradir, + F32 viewdist, + U32 maxLights ) +{ + SphereF bounds( camerapos, viewdist ); + _scoreLights( bounds ); + + // Limit the maximum. + if ( mBestLights.size() > maxLights ) + mBestLights.setSize( maxLights ); +} + +void LightManager::setupLights( LightReceiver *obj, + const SphereF &bounds, + U32 maxLights ) +{ + _scoreLights( bounds ); + + // Limit the maximum. + if ( mBestLights.size() > maxLights ) + mBestLights.setSize( maxLights ); +} + +void LightManager::getBestLights( LightInfo** outLights, U32 maxLights ) +{ + PROFILE_SCOPE( LightManager_GetBestLights ); + + // How many lights can we get... we don't allow + // more than 4 for forward lighting. + U32 lights = getMin( (U32)mBestLights.size(), getMin( (U32)4, maxLights ) ); + + // Copy them over. + for ( U32 i = 0; i < lights; i++ ) + { + LightInfo *light = mBestLights[i]; + + // If the score reaches zero then we got to + // the end of the valid lights for this object. + if ( light->getScore() <= 0.0f ) + break; + + outLights[i] = light; + } +} + +void LightManager::resetLights() +{ + mBestLights.clear(); +} + +void LightManager::_scoreLights( const SphereF &bounds ) +{ + PROFILE_SCOPE( LightManager_ScoreLights ); + + // Get all the lights. + mBestLights.clear(); + getAllUnsortedLights( mBestLights ); + + // Grab the sun to test for it. + LightInfo *sun = getSpecialLight( slSunLightType ); + + const Point3F lumDot( 0.2125f, 0.7154f, 0.0721f ); + + for ( U32 i=0; i < mBestLights.size(); i++ ) + { + // Get the light. + LightInfo *light = mBestLights[i]; + + F32 luminace = 0.0f; + F32 dist = 0.0f; + F32 weight = 0.0f; + + const bool isSpot = light->getType() == LightInfo::Spot; + const bool isPoint = light->getType() == LightInfo::Point; + + if ( isPoint || isSpot ) + { + // Get the luminocity. + luminace = mDot( light->getColor(), lumDot ) * light->getBrightness(); + + // Get the distance to the light... score it 1 to 0 near to far. + F32 lenSq = ( bounds.center - light->getPosition() ).lenSquared(); + + F32 radiusSq = light->getRange().x + bounds.radius; + radiusSq *= radiusSq; + + F32 distSq = radiusSq - lenSq; + + if ( distSq > 0.0f ) + dist = mClampF( distSq / ( 1000.0f * 1000.0f ), 0.0f, 1.0f ); + + // TODO: This culling is broken... it culls spotlights + // that are actually visible. + if ( false && isSpot && dist > 0.0f ) + { + // TODO: I cannot test to see if we're within + // the cone without a more detailed test... so + // just reject if we're behind the spot direction. + + Point3F toCenter = bounds.center - light->getPosition(); + F32 angDot = mDot( toCenter, light->getDirection() ); + if ( angDot < 0.0f ) + dist = 0.0f; + } + + weight = light->getPriority(); + } + else + { + // The sun always goes first + // regardless of the settings. + if ( light == sun ) + { + weight = F32_MAX; + dist = 1.0f; + luminace = 1.0f; + } + else + { + // TODO: When we have multiple directional + // lights we should score them here. + } + } + + // TODO: Manager ambient lights here too! + + light->setScore( luminace * weight * dist ); + } + + // Sort them! + mBestLights.sort( _lightScoreCmp ); +} + +S32 LightManager::_lightScoreCmp( LightInfo* const *a, LightInfo* const *b ) +{ + F32 diff = (*a)->getScore() - (*b)->getScore(); + return diff < 0 ? 1 : diff > 0 ? -1 : 0; +} + +void LightManager::_update4LightConsts( const SceneGraphData &sgData, + GFXShaderConstHandle *lightPositionSC, + GFXShaderConstHandle *lightDiffuseSC, + GFXShaderConstHandle *lightAmbientSC, + GFXShaderConstHandle *lightInvRadiusSqSC, + GFXShaderConstHandle *lightSpotDirSC, + GFXShaderConstHandle *lightSpotAngleSC, + GFXShaderConstBuffer *shaderConsts ) +{ + PROFILE_SCOPE( LightManager_Update4LightConsts ); + + // Skip over gathering lights if we don't have to! + if ( lightPositionSC->isValid() || + lightDiffuseSC->isValid() || + lightInvRadiusSqSC->isValid() || + lightSpotDirSC->isValid() || + lightSpotAngleSC->isValid() ) + { + // NOTE: We haven't ported the lighting shaders on OSX + // to the optimized HLSL versions. + #ifdef TORQUE_OS_MAC + static AlignedArray lightPositions( 4, sizeof( Point4F ) ); + #else + static AlignedArray lightPositions( 3, sizeof( Point4F ) ); + static AlignedArray lightSpotDirs( 3, sizeof( Point4F ) ); + #endif + static AlignedArray lightColors( 4, sizeof( Point4F ) ); + static Point4F lightInvRadiusSq; + static Point4F lightSpotAngle; + F32 range; + + // Need to clear the buffers so that we don't leak + // lights from previous passes or have NaNs. + dMemset( lightPositions.getBuffer(), 0, lightPositions.getBufferSize() ); + dMemset( lightColors.getBuffer(), 0, lightColors.getBufferSize() ); + lightInvRadiusSq = Point4F::Zero; + lightSpotAngle.set( -1.0f, -1.0f, -1.0f, -1.0f ); + + // Gather the data for the first 4 lights. + const LightInfo *light; + for ( U32 i=0; i < 4; i++ ) + { + light = sgData.lights[i]; + if ( !light ) + break; + + #ifdef TORQUE_OS_MAC + + lightPositions[i] = light->getPosition(); + + #else + + // The light positions and spot directions are + // in SoA order to make optimal use of the GPU. + const Point3F &lightPos = light->getPosition(); + lightPositions[0][i] = lightPos.x; + lightPositions[1][i] = lightPos.y; + lightPositions[2][i] = lightPos.z; + + const VectorF &lightDir = light->getDirection(); + lightSpotDirs[0][i] = lightDir.x; + lightSpotDirs[1][i] = lightDir.y; + lightSpotDirs[2][i] = lightDir.z; + + if ( light->getType() == LightInfo::Spot ) + lightSpotAngle[i] = mCos( mDegToRad( light->getOuterConeAngle() / 2.0f ) ); + + #endif + + // Prescale the light color by the brightness to + // avoid doing this in the shader. + lightColors[i] = Point4F(light->getColor()) * light->getBrightness(); + + // We need 1 over range^2 here. + range = light->getRange().x; + lightInvRadiusSq[i] = 1.0f / ( range * range ); + } + + shaderConsts->set( lightPositionSC, lightPositions ); + shaderConsts->set( lightDiffuseSC, lightColors ); + shaderConsts->set( lightInvRadiusSqSC, lightInvRadiusSq ); + + #ifndef TORQUE_OS_MAC + + shaderConsts->set( lightSpotDirSC, lightSpotDirs ); + shaderConsts->set( lightSpotAngleSC, lightSpotAngle ); + + #endif + } + + // Setup the ambient lighting from the first + // light which is the directional light if + // one exists at all in the scene. + if ( lightAmbientSC->isValid() ) + { + ColorF ambient( ColorF::BLACK ); + if ( sgData.lights[0] ) + ambient = sgData.lights[0]->getAmbient(); + shaderConsts->set( lightAmbientSC, ambient ); + } +} + +AvailableSLInterfaces* LightManager::getSceneLightingInterface() +{ + if ( !mAvailableSLInterfaces ) + mAvailableSLInterfaces = new AvailableSLInterfaces(); + + return mAvailableSLInterfaces; +} + +bool LightManager::lightScene( const char* callback, const char* param ) +{ + BitSet32 flags = 0; + + if ( param ) + { + if ( !dStricmp( param, "forceAlways" ) ) + flags.set( SceneLighting::ForceAlways ); + else if ( !dStricmp(param, "forceWritable" ) ) + flags.set( SceneLighting::ForceWritable ); + } + + // The SceneLighting object will delete itself + // once the lighting process is complete. + SceneLighting* sl = new SceneLighting( getSceneLightingInterface() ); + return sl->lightScene( callback, flags ); +} + +ConsoleFunctionGroupBegin( LightManager, "Functions for working with the light managers." ); + +ConsoleFunction( setLightManager, bool, 1, 3, "setLightManager( string lmName )\n" + "Finds and activates the named light manager." ) +{ + return gClientSceneGraph->setLightManager( argv[1] ); +} + +ConsoleFunction(lightScene, bool, 1, 3, "(script_function completeCallback=NULL, string mode=\"\")" + "Relight the scene.\n\n" + "If mode is \"forceAlways\", the lightmaps will be regenerated regardless of whether " + "lighting cache files can be written to. If mode is \"forceWritable\", then the lightmaps " + "will be regenerated only if the lighting cache files can be written.") +{ + LightManager * lm = gClientSceneGraph->getLightManager(); + + if (lm) + { + if (argc > 1) + return lm->lightScene(argv[1], argv[2]); + else + return lm->lightScene(argv[1], NULL); + } + else + return false; +} + +ConsoleFunction( getLightManagerNames, const char*, 1, 1, + "Returns a tab seperated list of light manager names." ) +{ + String names; + LightManager::getLightManagerNames( &names ); + + char *result = Con::getReturnBuffer( names.length() + 1 ); + dStrcpy( result, names.c_str() ); + return result; +} + +ConsoleFunction( getActiveLightManager, const char*, 1, 1, + "Returns the active light manager name." ) +{ + LightManager *lm = gClientSceneGraph->getLightManager(); + if ( !lm ) + return NULL; + + return lm->getName(); +} + +ConsoleFunction( resetLightManager, void, 1, 1, + "Deactivates and then activates the currently active light manager." ) +{ + LightManager *lm = gClientSceneGraph->getLightManager(); + if ( lm ) + { + lm->deactivate(); + lm->activate( gClientSceneGraph ); + } +} + +ConsoleFunction( getBestHDRFormat, const char*, 1, 1, + "Returns the best texture GFXFormat for storage of HDR data." ) +{ + // TODO: Maybe expose GFX::selectSupportedFormat() so that this + // specialized method can be moved to script. + + // Figure out the best HDR format. This is the smallest + // format which supports blending and filtering. + Vector formats; + formats.push_back( GFXFormatR10G10B10A2 ); + formats.push_back( GFXFormatR16G16B16A16F ); + formats.push_back( GFXFormatR16G16B16A16 ); + GFXFormat format = GFX->selectSupportedFormat( &GFXDefaultRenderTargetProfile, + formats, + true, + true, + true ); + + return Con::getData( TypeEnum, &format, 0, &gTextureFormatEnumTable ); +} + +ConsoleFunctionGroupEnd( LightManager ); diff --git a/lighting/lightManager.h b/lighting/lightManager.h new file mode 100644 index 0000000..61e9f00 --- /dev/null +++ b/lighting/lightManager.h @@ -0,0 +1,236 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTMANAGER_H_ +#define _LIGHTMANAGER_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/str.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif + +class SimObject; +class LightManager; +class Material; +class ProcessedMaterial; +class SceneGraph; +struct SceneGraphData; +class Point3F; +class SphereF; +class AvailableSLInterfaces; +struct LightReceiver; +class GFXShaderConstBuffer; +class GFXShaderConstHandle; +class ShaderConstHandles; +class SceneState; + +/// +typedef Map LightManagerMap; + + +class LightManager +{ +public: + + enum SpecialLightTypesEnum + { + slSunLightType, + slSpecialLightTypesCount + }; + + LightManager( const char *name, const char *id ); + + virtual ~LightManager(); + + /// + static void initLightFields(); + + /// + static LightInfo* createLightInfo(); + + /// + static LightManager* findByName( const char *name ); + + /// Returns a tab seperated list of available light managers. + static void getLightManagerNames( String *outString ); + + /// The light manager activation signal. + static Signal smActivateSignal; + + /// Return an id string used to load different versions of light manager + /// specific assets. It shoud be short, contain no spaces, and be safe + /// for filename use. + const char* getName() const { return mName.c_str(); } + + /// Return an id string used to load different versions of light manager + /// specific assets. It shoud be short, contain no spaces, and be safe + /// for filename use. + const char* getId() const { return mId.c_str(); } + + // Returns the scene manager passed at activation. + SceneGraph* getSceneManager() { return mSceneManager; } + + // Should return true if this light manager is compatible + // on the current platform and GFX device. + virtual bool isCompatible() const = 0; + + // Called when the lighting manager should become active + virtual void activate( SceneGraph *sceneManager ); + + // Called when we don't want the light manager active (should clean up) + virtual void deactivate(); + + // Returns the active scene lighting interface for this light manager. + virtual AvailableSLInterfaces* getSceneLightingInterface(); + + // Returns a "default" light info that callers should not free. Used for instances where we don't actually care about + // the light (for example, setting default data for SceneGraphData) + virtual LightInfo* getDefaultLight(); + + /// Returns the special light or the default light if useDefault is true. + /// @see getDefaultLight + virtual LightInfo* getSpecialLight( SpecialLightTypesEnum type, + bool useDefault = true ); + + /// Set a special light type. + virtual void setSpecialLight( SpecialLightTypesEnum type, LightInfo *light ); + + // registered before scene traversal... + virtual void registerGlobalLight( LightInfo *light, SimObject *obj ); + virtual void unregisterGlobalLight( LightInfo *light ); + + // registered per object... + virtual void registerLocalLight( LightInfo *light ); + virtual void unregisterLocalLight( LightInfo *light ); + + virtual void registerGlobalLights( const Frustum *frustum, bool staticlighting ); + virtual void unregisterAllLights(); + + /// Returns all unsorted and un-scored lights (both global and local). + virtual void getAllUnsortedLights( LightInfoList &list ); + + /// For the terrain. Finds the best + /// lights in the viewing area based on distance to camera. + virtual void setupLights( LightReceiver *obj, + const Point3F &camerapos, + const Point3F &cameradir, + F32 viewdist, + U32 maxLights = 4 ); + + /// Finds the best lights that overlap with the bounds. + /// @see getBestLights + virtual void setupLights( LightReceiver *obj, + const SphereF &bounds, + U32 maxLights = 4 ); + + /// This returns the best lights as gathered by the + /// previous setupLights() calls. + /// @see setupLights + virtual void getBestLights( LightInfo** outLights, U32 maxLights ); + + /// Clears the best lights list and all associated data. + virtual void resetLights(); + + /// Sets shader constants / textures for light infos + virtual void setLightInfo( ProcessedMaterial *pmat, + const Material *mat, + const SceneGraphData &sgData, + const SceneState *state, + U32 pass, + GFXShaderConstBuffer *shaderConsts ) = 0; + + /// Allows us to set textures during the Material::setTextureStage call, return true if we've done work. + virtual bool setTextureStage( const SceneGraphData &sgData, + const U32 currTexFlag, + const U32 textureSlot, + GFXShaderConstBuffer *shaderConsts, + ShaderConstHandles *handles ) = 0; + + /// Called when the static scene lighting (aka lightmaps) should be computed. + virtual bool lightScene( const char* callback, const char* param ); + + /// Returns true if this light manager is active + virtual bool isActive() const { return mIsActive; } + +protected: + + /// This helper function sets the shader constansts + /// for the stock 4 light forward lighting code. + static void _update4LightConsts( const SceneGraphData &sgData, + GFXShaderConstHandle *lightPositionSC, + GFXShaderConstHandle *lightDiffuseSC, + GFXShaderConstHandle *lightAmbientSC, + GFXShaderConstHandle *lightInvRadiusSqSC, + GFXShaderConstHandle *lightSpotDirSC, + GFXShaderConstHandle *lightSpotAngleSC, + GFXShaderConstBuffer *shaderConsts ); + + /// A dummy default light used when no lights + /// happen to be registered with the manager. + LightInfo *mDefaultLight; + + /// The list of global registered lights which is + /// initialized before the scene is rendered. + LightInfoList mRegisteredLights; + + /// The registered special light list. + LightInfo *mSpecialLights[slSpecialLightTypesCount]; + + /// The sorted list of the best lights. + /// @see setupLights, getBestLights + LightInfoList mBestLights; + + /// The root culling position used for + /// special sun light placement. + /// @see setSpecialLight + Point3F mCullPos; + + /// The scene lighting interfaces for + /// lightmap generation. + AvailableSLInterfaces *mAvailableSLInterfaces; + + /// + void _scoreLights( const SphereF &bounds ); + + /// + static S32 _lightScoreCmp( LightInfo* const *a, LightInfo* const *b ); + + /// Attaches any LightInfoEx data for this manager + /// to the light info object. + virtual void _addLightInfoEx( LightInfo *lightInfo ) = 0; + + /// + virtual void _initLightFields() = 0; + + /// Returns the static light manager map. + static LightManagerMap& _getLightManagers(); + + /// The constant light manager name initialized + /// in the constructor. + const String mName; + + /// The constant light manager identifier initialized + /// in the constructor. + const String mId; + + /// Is true if this light manager has been activated. + bool mIsActive; + + /// The scene graph the light manager is associated with. + SceneGraph *mSceneManager; +}; + +#endif // _LIGHTMANAGER_H_ diff --git a/lighting/lightReceiver.cpp b/lighting/lightReceiver.cpp new file mode 100644 index 0000000..05bae61 --- /dev/null +++ b/lighting/lightReceiver.cpp @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/lightReceiver.h" + +LightReceiver::LightReceiver() +{ + overrideOptions = true; + receiveLMLighting = true; + receiveSunLight = true; + useAdaptiveSelfIllumination = false; + useCustomAmbientLighting = false; + customAmbientForSelfIllumination = false; + customAmbientLighting = ColorF(0.0f, 0.0f, 0.0f); +} + +LightReceiver::~LightReceiver() +{ +} diff --git a/lighting/lightReceiver.h b/lighting/lightReceiver.h new file mode 100644 index 0000000..9edf19e --- /dev/null +++ b/lighting/lightReceiver.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTRECEIVER_H_ +#define _LIGHTRECEIVER_H_ + +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +struct LightReceiver +{ + LightReceiver(); + virtual ~LightReceiver(); + + bool overrideOptions; + bool receiveLMLighting; + bool receiveSunLight; + bool useAdaptiveSelfIllumination; + bool useCustomAmbientLighting; + bool customAmbientForSelfIllumination; + ColorF customAmbientLighting; + String lightGroupName; + +protected: +}; + +#endif // _LIGHTRECEIVER_H_ diff --git a/lighting/lightingInterfaces.cpp b/lighting/lightingInterfaces.cpp new file mode 100644 index 0000000..b5d058d --- /dev/null +++ b/lighting/lightingInterfaces.cpp @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/lightingInterfaces.h" + + +void AvailableSLInterfaces::registerSystem(SceneLightingInterface* si) +{ + mAvailableSystemInterfaces.push_back(si); + mDirty = true; +} + +void AvailableSLInterfaces::initInterfaces() +{ + if ( !mDirty ) + return; + + mAvailableObjectTypes = mClippingMask = mZoneLightSkipMask = 0; + + SceneLightingInterface** sitr = mAvailableSystemInterfaces.begin(); + for ( ; sitr != mAvailableSystemInterfaces.end(); sitr++ ) + { + SceneLightingInterface* si = (*sitr); + si->init(); + mAvailableObjectTypes |= si->addObjectType(); + mClippingMask |= si->addToClippingMask(); + mZoneLightSkipMask |= si->addToZoneLightSkipMask(); + } + + mDirty = false; +} \ No newline at end of file diff --git a/lighting/lightingInterfaces.h b/lighting/lightingInterfaces.h new file mode 100644 index 0000000..5f86d95 --- /dev/null +++ b/lighting/lightingInterfaces.h @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SG_SYSTEM_INTERFACE_H +#define _SG_SYSTEM_INTERFACE_H + +#ifndef _SGSCENEPERSIST_H_ +#include "lighting/common/scenePersist.h" +#endif + +#ifndef _SCENELIGHTING_H_ +#include "lighting/common/sceneLighting.h" +#endif + +class ObjectProxy; +class ObjectProxyList; +class SceneLightingInterface; + +template class Vector; +typedef Vector SceneLightingInterfaces; + +// List of available "systems" that the lighting kit can use +class AvailableSLInterfaces +{ +protected: + + bool mDirty; + +public: + AvailableSLInterfaces() + : mAvailableObjectTypes( 0 ), + mClippingMask( 0 ), + mZoneLightSkipMask( 0 ), + mDirty( true ) + { + VECTOR_SET_ASSOCIATION( mAvailableSystemInterfaces ); + } + + // Register a system + void registerSystem(SceneLightingInterface* si); + + // Init the interfaces + void initInterfaces(); + + // The actual list of SceneLightingInterfaces + SceneLightingInterfaces mAvailableSystemInterfaces; + + // Object types that are registered with the system + U32 mAvailableObjectTypes; + + // Clipping typemask + U32 mClippingMask; + + // Object types that we should skip zone lighting for + U32 mZoneLightSkipMask; +}; + +// This object is responsible for returning PersistChunk and ObjectProxy classes for the lighting system to use +// We may want to eventually split this into scene lighting vs. dynamic lighting. getColorFromRayInfo is a dynamic +// lighting thing. +class SceneLightingInterface +{ +public: + SceneLightingInterface() + { + } + virtual ~SceneLightingInterface() { } + + virtual void init() { } + + // + // Scene lighting methods + // + // Creates an object proxy for obj + virtual SceneLighting::ObjectProxy* createObjectProxy(SceneObject* obj, SceneLighting::ObjectProxyList* sceneObjects) = 0; + + // Creates a PersistChunk based on the chunkType flag + virtual PersistInfo::PersistChunk* createPersistChunk(const U32 chunkType) = 0; + + // Creates a PersistChunk if needed for a proxy, returns true if it's "handled" by the system and ret contains the PersistChunk if needed. + virtual bool createPersistChunkFromProxy(SceneLighting::ObjectProxy* proxy, PersistInfo::PersistChunk** ret) = 0; + + // Returns which object type flag this system supports (used to query scene graph for objects to light) + virtual U32 addObjectType() = 0; + + // Add an object type flag to the "allow clipping mask" (used for blob shadows) + virtual U32 addToClippingMask() { return 0; } + + // Add an object type flag to skip zone lighting + virtual U32 addToZoneLightSkipMask() { return 0; } + + // Allows for processing/validating of the scene list after loading cached persistant info, return false if a relight is required or true if the data looks good. + virtual bool postProcessLoad(PersistInfo* pi, SceneLighting::ObjectProxyList* sceneObjects) { return true; } + + virtual void processLightingBegin() { } + virtual void processLightingCompleted(bool success) { } + + // + // Runtime / dynamic methods + // + // Given a ray, this will return the color from the lightmap of this object, return true if handled + virtual bool getColorFromRayInfo(const RayInfo & collision, ColorF& result) const { return false; } +}; + +#endif diff --git a/lighting/shadowManager.cpp b/lighting/shadowManager.cpp new file mode 100644 index 0000000..4ebc699 --- /dev/null +++ b/lighting/shadowManager.cpp @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowManager.h" + +#include "sceneGraph/sceneGraph.h" +#include "materials/materialManager.h" + +const String ShadowManager::ManagerTypeName("ShadowManager"); + +//------------------------------------------------------------------------------ + +bool ShadowManager::canActivate() +{ + return true; +} + +//------------------------------------------------------------------------------ + +void ShadowManager::activate() +{ + mSceneManager = gClientSceneGraph; //;getWorld()->findWorldManager(); +} + +//------------------------------------------------------------------------------ + +SceneGraph* ShadowManager::getSceneManager() +{ + return mSceneManager; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +// Runtime switching of shadow systems. Requires correct world to be pushed at console. +ConsoleFunction( setShadowManager, bool, 1, 3, "string sShadowSystemName" ) +{ + /* + // Make sure this new one exists + ShadowManager * newSM = dynamic_cast(ConsoleObject::create(argv[1])); + if (!newSM) + return false; + + // Cleanup current + ShadowManager * currentSM = world->findWorldManager(); + if (currentSM) + { + currentSM->deactivate(); + world->removeWorldManager(currentSM); + delete currentSM; + } + + // Add to world and init. + world->addWorldManager(newSM); + newSM->activate(); + MaterialManager::get()->reInitInstances(); + */ + return true; +} \ No newline at end of file diff --git a/lighting/shadowManager.h b/lighting/shadowManager.h new file mode 100644 index 0000000..d3aa5d0 --- /dev/null +++ b/lighting/shadowManager.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOWMANAGER_H_ +#define _SHADOWMANAGER_H_ + +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +class SceneGraph; + +class ShadowManager +{ +public: + ShadowManager() : mSceneManager(NULL) {} + virtual ~ShadowManager() { } + + // Called when the shadow manager should become active + virtual void activate(); + + // Called when we don't want the shadow manager active (should clean up) + virtual void deactivate() { } + + // Return an "id" that other systems can use to load different versions of assets (custom shaders, etc.) + // Should be short and contain no spaces and safe for filename use. + //virtual const char* getId() const = 0; + + // Scene manager + virtual SceneGraph* getSceneManager(); + + // Called to find out if it is valid to activate this shadow system. If not, we should print out + // a console warning explaining why. + virtual bool canActivate(); + + // SimWorldManager + static const String ManagerTypeName; + const String & getManagerTypeName() const { return ManagerTypeName; } + +private: + SceneGraph* mSceneManager; +}; + +#endif diff --git a/lighting/shadowMap/blurTexture.cpp b/lighting/shadowMap/blurTexture.cpp new file mode 100644 index 0000000..c53b411 --- /dev/null +++ b/lighting/shadowMap/blurTexture.cpp @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/blurTexture.h" + +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "materials/shaderData.h" +#include "shaderGen/shaderGenVars.h" + + +BlurOp::BlurOp() +{ + mBlurShader = NULL; + mBlurConsts = NULL; + mBlurSB = NULL; +} + +BlurOp::~BlurOp() +{ + mBlurConsts = NULL; + mBlurSB = NULL; +} + +bool BlurOp::init(const String& shaderName, U32 texWidth, U32 texHeight) +{ + // Setup shader + ShaderData *blurShaderData; + if ( !Sim::findObject( shaderName, blurShaderData ) ) + { + Con::errorf( "Couldn't find blur shader data!"); + return false; + } + + mBlurShader = blurShaderData->getShader(); + if ( !mBlurShader ) + return false; + + mBlurConsts = mBlurShader->allocConstBuffer(); + mModelViewProjSC = mBlurShader->getShaderConstHandle(ShaderGenVars::modelview); + mTexSizeSC = mBlurShader->getShaderConstHandle("$texSize"); + mBlurDimensionSC = mBlurShader->getShaderConstHandle("$blurDimension"); + + // Setup state block + GFXStateBlockDesc desc; + desc.samplersDefined = true; + desc.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + desc.zDefined = true; + desc.zWriteEnable = false; + desc.zEnable = false; + mBlurSB = GFX->createStateBlock(desc); + + // Setup geometry + // 0.5 pixel offset - the render is spread from -1 to 1 ( 2 width ) + mTexDimensions.x = texWidth; + mTexDimensions.y = texHeight; + + mTarget = GFX->allocRenderToTextureTarget(); + + return true; +} + +void BlurOp::blur(GFXTextureObject* input, GFXTextureObject* scratch) +{ + if ( !mBlurShader ) + return; + + // Setup the VB. + GFXVertexBufferHandle vb( GFX, 4, GFXBufferTypeVolatile ); + { + F32 copyOffsetX = 1.0 / mTexDimensions.x; + F32 copyOffsetY = 1.0 / mTexDimensions.y; + GFXVertexPT *verts = vb.lock(); + + verts[0].point = Point3F( -1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 ); + verts[0].texCoord = Point2F( 0.0, 1.0 ); + + verts[1].point = Point3F( -1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 ); + verts[1].texCoord = Point2F( 0.0, 0.0 ); + + verts[2].point = Point3F( 1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 ); + verts[2].texCoord = Point2F( 1.0, 0.0 ); + + verts[3].point = Point3F( 1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 ); + verts[3].texCoord = Point2F( 1.0, 1.0 ); + + vb.unlock(); + } + + GFX->pushActiveRenderTarget(); + + // Set our ortho projection + GFXTransformSaver saver; + GFX->setWorldMatrix(MatrixF::Identity); + GFX->setProjectionMatrix(MatrixF::Identity); + mBlurConsts->set(mModelViewProjSC, MatrixF::Identity); + + mBlurConsts->set(mTexSizeSC, (F32) mTexDimensions.x); // BTRTODO: FIX ME + + // Set our shader stuff + GFX->setShader( mBlurShader ); + GFX->setShaderConstBuffer( mBlurConsts ); + GFX->setStateBlock( mBlurSB ); + GFX->setVertexBuffer( vb ); + + mTarget->attachTexture(GFXTextureTarget::Color0, scratch); + mBlurConsts->set(mBlurDimensionSC, Point2F(1.0f, 0.0f)); + GFX->setActiveRenderTarget(mTarget); + GFX->setTexture(0, input); + GFX->drawPrimitive(GFXTriangleFan, 0, 2); + + mTarget->resolve(); + + mTarget->attachTexture(GFXTextureTarget::Color0, input); + GFX->setActiveRenderTarget(mTarget); + mBlurConsts->set(mBlurDimensionSC, Point2F(0.0f, 1.0f)); + GFX->setTexture(0, scratch); + GFX->drawPrimitive(GFXTriangleFan, 0, 2); + + mTarget->resolve(); + + // Cleanup + GFX->setTexture( 0, NULL ); + GFX->setShaderConstBuffer(NULL); + GFX->popActiveRenderTarget(); +} diff --git a/lighting/shadowMap/blurTexture.h b/lighting/shadowMap/blurTexture.h new file mode 100644 index 0000000..342b449 --- /dev/null +++ b/lighting/shadowMap/blurTexture.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _BLURTEXTURE_H_ +#define _BLURTEXTURE_H_ + +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif + + +/// Simple two pass texture blurring. This may end up +/// in a more generic spot soon. +class BlurOp +{ +public: + + BlurOp(); + virtual ~BlurOp(); + + bool init(const String& shaderName, U32 texWidth, U32 texHeight); + virtual void blur(GFXTextureObject* input, GFXTextureObject* scratch); + GFXShaderConstBufferRef getBlurConsts() { return mBlurConsts; } + const Point2I& getTexDimensions() const { return mTexDimensions; } + +private: + + GFXShaderRef mBlurShader; + GFXShaderConstBufferRef mBlurConsts; + GFXShaderConstHandle* mModelViewProjSC; + GFXShaderConstHandle* mTexSizeSC; + GFXShaderConstHandle* mBlurDimensionSC; + + GFXStateBlockRef mBlurSB; + GFXTextureTargetRef mTarget; + Point2I mTexDimensions; +}; + +#endif diff --git a/lighting/shadowMap/cubeLightShadowMap.cpp b/lighting/shadowMap/cubeLightShadowMap.cpp new file mode 100644 index 0000000..f1ae1c1 --- /dev/null +++ b/lighting/shadowMap/cubeLightShadowMap.cpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/cubeLightShadowMap.h" + +#include "lighting/shadowMap/shadowMapManager.h" +#include "lighting/common/lightMapParams.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "renderInstance/renderPassManager.h" +#include "materials/materialDefinition.h" + + +CubeLightShadowMap::CubeLightShadowMap( LightInfo *light ) + : Parent( light ) +{ +} + +bool CubeLightShadowMap::setTextureStage( const SceneGraphData &sgData, + const U32 currTexFlag, + const U32 textureSlot, + GFXShaderConstBuffer *shaderConsts, + GFXShaderConstHandle *shadowMapSC ) +{ + switch (currTexFlag) + { + case Material::DynamicLight: + { + if(shaderConsts && shadowMapSC->isValid()) + shaderConsts->set(shadowMapSC, (S32)textureSlot); + GFX->setCubeTexture( textureSlot, mCubemap ); + return true; + } + break; + } + return false; +} + +void CubeLightShadowMap::setShaderParameters( GFXShaderConstBuffer *params, + LightingShaderConstants *lsc ) +{ + if ( lsc->mTapRotationTexSC->isValid() ) + GFX->setTexture( lsc->mTapRotationTexSC->getSamplerRegister(), + SHADOWMGR->getTapRotationTex() ); + + ShadowMapParams *p = mLight->getExtended(); + + if ( lsc->mLightParamsSC->isValid() ) + { + Point4F lightParams( mLight->getRange().x, + p->overDarkFactor.x, + 0.0f, + 0.0f ); + params->set(lsc->mLightParamsSC, lightParams); + } + + // The softness is a factor of the texel size. + if ( lsc->mShadowSoftnessConst->isValid() ) + params->set( lsc->mShadowSoftnessConst, p->shadowSoftness * ( 1.0f / mTexSize ) ); +} + +void CubeLightShadowMap::releaseTextures() +{ + Parent::releaseTextures(); + mCubemap = NULL; +} + +void CubeLightShadowMap::_render( SceneGraph *sceneManager, + const SceneState *diffuseState ) +{ + PROFILE_SCOPE( CubeLightShadowMap_Render ); + + const ShadowMapParams *p = mLight->getExtended(); + const LightMapParams *lmParams = mLight->getExtended(); + const bool bUseLightmappedGeometry = lmParams ? !lmParams->representedInLightmap || lmParams->includeLightmappedGeometryInShadow : true; + + if ( mCubemap.isNull() || + mTexSize != p->texSize ) + { + mTexSize = p->texSize; + mCubemap = GFX->createCubemap(); + mCubemap->initDynamic( mTexSize, LightShadowMap::ShadowMapFormat ); + } + + GFXTransformSaver saver; + F32 left, right, bottom, top, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + + // Set up frustum and visible distance + GFX->setFrustum( 90.0f, 1.0f, 0.1f, mLight->getRange().x ); + + // Render the shadowmap! + GFX->pushActiveRenderTarget(); + + for( U32 i = 0; i < 6; i++ ) + { + // Standard view that will be overridden below. + VectorF vLookatPt(0.0f, 0.0f, 0.0f), vUpVec(0.0f, 0.0f, 0.0f), vRight(0.0f, 0.0f, 0.0f); + + switch( i ) + { + case 0 : // D3DCUBEMAP_FACE_POSITIVE_X: + vLookatPt = VectorF(1.0f, 0.0f, 0.0f); + vUpVec = VectorF(0.0f, 1.0f, 0.0f); + break; + case 1 : // D3DCUBEMAP_FACE_NEGATIVE_X: + vLookatPt = VectorF(-1.0f, 0.0f, 0.0f); + vUpVec = VectorF(0.0f, 1.0f, 0.0f); + break; + case 2 : // D3DCUBEMAP_FACE_POSITIVE_Y: + vLookatPt = VectorF(0.0f, 1.0f, 0.0f); + vUpVec = VectorF(0.0f, 0.0f,-1.0f); + break; + case 3 : // D3DCUBEMAP_FACE_NEGATIVE_Y: + vLookatPt = VectorF(0.0f, -1.0f, 0.0f); + vUpVec = VectorF(0.0f, 0.0f, 1.0f); + break; + case 4 : // D3DCUBEMAP_FACE_POSITIVE_Z: + vLookatPt = VectorF(0.0f, 0.0f, 1.0f); + vUpVec = VectorF(0.0f, 1.0f, 0.0f); + break; + case 5: // D3DCUBEMAP_FACE_NEGATIVE_Z: + vLookatPt = VectorF(0.0f, 0.0f, -1.0f); + vUpVec = VectorF(0.0f, 1.0f, 0.0f); + break; + } + + GFXDEBUGEVENT_START( CubeLightShadowMap_Render_Face, ColorI::RED ); + + // create camera matrix + VectorF cross = mCross(vUpVec, vLookatPt); + cross.normalizeSafe(); + + MatrixF lightMatrix(true); + lightMatrix.setColumn(0, cross); + lightMatrix.setColumn(1, vLookatPt); + lightMatrix.setColumn(2, vUpVec); + lightMatrix.setPosition( mLight->getPosition() ); + lightMatrix.inverse(); + + GFX->setWorldMatrix( lightMatrix ); + + mTarget->attachTexture(GFXTextureTarget::Color0, mCubemap, i); + mTarget->attachTexture(GFXTextureTarget::DepthStencil, _getDepthTarget( mTexSize, mTexSize )); + GFX->setActiveRenderTarget(mTarget); + GFX->clear( GFXClearTarget | GFXClearStencil | GFXClearZBuffer, ColorI(255,255,255,255), 1.0f, 0 ); + + // Create scene state, prep it + SceneState* baseState = sceneManager->createBaseState( SPT_Shadow ); + baseState->mRenderNonLightmappedMeshes = true; + baseState->mRenderLightmappedMeshes = bUseLightmappedGeometry; + baseState->setDiffuseCameraTransform( diffuseState->getCameraTransform() ); + baseState->setViewportExtent( diffuseState->getViewportExtent() ); + baseState->setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); + + sceneManager->renderScene(baseState); + delete baseState; + + // Resolve this face + mTarget->resolve(); + + GFXDEBUGEVENT_END(); + } + GFX->popActiveRenderTarget(); + + // Restore frustum + if ( !isOrtho ) + GFX->setFrustum(left, right, bottom, top, nearPlane, farPlane); + else + GFX->setOrtho(left, right, bottom, top, nearPlane, farPlane); +} diff --git a/lighting/shadowMap/cubeLightShadowMap.h b/lighting/shadowMap/cubeLightShadowMap.h new file mode 100644 index 0000000..63e3931 --- /dev/null +++ b/lighting/shadowMap/cubeLightShadowMap.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _CUBELIGHTSHADOWMAP_H_ +#define _CUBELIGHTSHADOWMAP_H_ + +#ifndef _LIGHTSHADOWMAP_H_ +#include "lighting/shadowMap/lightShadowMap.h" +#endif +#ifndef _GFXCUBEMAP_H_ +#include "gfx/gfxCubemap.h" +#endif + + +class CubeLightShadowMap : public LightShadowMap +{ + typedef LightShadowMap Parent; + +public: + + CubeLightShadowMap( LightInfo *light ); + + // LightShadowMap + virtual bool hasShadowTex() const { return mCubemap.isValid(); } + virtual ShadowType getShadowType() const { return ShadowType_CubeMap; } + virtual void _render( SceneGraph *sceneManager, const SceneState *diffuseState ); + virtual void setShaderParameters( GFXShaderConstBuffer* params, LightingShaderConstants* lsc ); + virtual void releaseTextures(); + virtual bool setTextureStage( const SceneGraphData& sgData, + const U32 currTexFlag, + const U32 textureSlot, + GFXShaderConstBuffer* shaderConsts, + GFXShaderConstHandle* shadowMapSC ); + +protected: + + /// The shadow cubemap. + GFXCubemapHandle mCubemap; + +}; + +#endif // _CUBELIGHTSHADOWMAP_H_ diff --git a/lighting/shadowMap/dualParaboloidLightShadowMap.cpp b/lighting/shadowMap/dualParaboloidLightShadowMap.cpp new file mode 100644 index 0000000..890f2d6 --- /dev/null +++ b/lighting/shadowMap/dualParaboloidLightShadowMap.cpp @@ -0,0 +1,180 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/dualParaboloidLightShadowMap.h" +#include "lighting/common/lightMapParams.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "math/mathUtils.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" +#include "materials/materialDefinition.h" +#include "math/util/matrixSet.h" + +DualParaboloidLightShadowMap::DualParaboloidLightShadowMap( LightInfo *light ) + : Parent( light ) +{ +} + +bool DualParaboloidLightShadowMap::setTextureStage( const SceneGraphData &sgData, + const U32 currTexFlag, + const U32 textureSlot, + GFXShaderConstBuffer *shaderConsts, + GFXShaderConstHandle *shadowMapSC ) +{ + switch (currTexFlag) + { + case Material::DynamicLight: + { + GFX->setTexture(textureSlot, mShadowMapTex); + return true; + } + break; + } + + return false; +} + +void DualParaboloidLightShadowMap::_render( SceneGraph *sceneManager, + const SceneState *diffuseState ) +{ + PROFILE_SCOPE(DualParaboloidLightShadowMap_render); + + const ShadowMapParams *p = mLight->getExtended(); + const LightMapParams *lmParams = mLight->getExtended(); + const bool bUseLightmappedGeometry = lmParams ? !lmParams->representedInLightmap || lmParams->includeLightmappedGeometryInShadow : true; + + if ( mShadowMapTex.isNull() || + mTexSize != p->texSize ) + { + mTexSize = p->texSize; + + mShadowMapTex.set( mTexSize * 2, mTexSize, + ShadowMapFormat, &ShadowMapProfile, + "DualParaboloidLightShadowMap" ); + } + + GFXTransformSaver saver; + F32 left, right, bottom, top, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + + // Set and Clear target + GFX->pushActiveRenderTarget(); + + mTarget->attachTexture(GFXTextureTarget::Color0, mShadowMapTex); + mTarget->attachTexture( GFXTextureTarget::DepthStencil, + _getDepthTarget( mShadowMapTex->getWidth(), mShadowMapTex->getHeight() ) ); + GFX->setActiveRenderTarget(mTarget); + GFX->clear(GFXClearTarget | GFXClearStencil | GFXClearZBuffer, ColorI::WHITE, 1.0f, 0); + + const bool bUseSinglePassDPM = (p->shadowType == ShadowType_DualParaboloidSinglePass); + + // Set up matrix and visible distance + mWorldToLightProj = mLight->getTransform(); + mWorldToLightProj.inverse(); + + const F32 &lightRadius = mLight->getRange().x; + const F32 paraboloidNearPlane = 0.01f; + const F32 renderPosOffset = 0.01f; + + // Alter for creation of scene state if this is a single pass map + if(bUseSinglePassDPM) + { + VectorF camDir; + MatrixF temp = mLight->getTransform(); + temp.getColumn(1, &camDir); + temp.setPosition(mLight->getPosition() - camDir * (lightRadius + renderPosOffset)); + temp.inverse(); + GFX->setWorldMatrix(temp); + GFX->setOrtho(-lightRadius, lightRadius, -lightRadius, lightRadius, paraboloidNearPlane, 2.0f * lightRadius, true); + } + else + { + VectorF camDir; + MatrixF temp = mLight->getTransform(); + temp.getColumn(1, &camDir); + temp.setPosition(mLight->getPosition() - camDir * renderPosOffset); + temp.inverse(); + GFX->setWorldMatrix(temp); + + GFX->setOrtho(-lightRadius, lightRadius, -lightRadius, lightRadius, paraboloidNearPlane, lightRadius, true); + } + + // Set up a scene state + SceneState *baseState = sceneManager->createBaseState( SPT_Shadow ); + baseState->mRenderNonLightmappedMeshes = true; + baseState->mRenderLightmappedMeshes = bUseLightmappedGeometry; + baseState->setDiffuseCameraTransform( diffuseState->getCameraTransform() ); + baseState->setViewportExtent( diffuseState->getViewportExtent() ); + baseState->setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); + + if(bUseSinglePassDPM) + { + GFX->setWorldMatrix(mWorldToLightProj); + baseState->getRenderPass()->getMatrixSet().setSceneView(mWorldToLightProj); + GFX->setOrtho(-lightRadius, lightRadius, -lightRadius, lightRadius, paraboloidNearPlane, lightRadius, true); + } + + // Front map render + { + GFXDEBUGEVENT_SCOPE( DualParaboloidLightShadowMap_Render_FrontFacingParaboloid, ColorI::RED ); + mShadowMapScale.set(0.5f, 1.0f); + mShadowMapOffset.set(-0.5f, 0.0f); + sceneManager->renderScene(baseState); + } + + // Back map render + if(!bUseSinglePassDPM) + { + GFXDEBUGEVENT_SCOPE( DualParaboloidLightShadowMap_Render_BackFacingParaboloid, ColorI::RED ); + + mShadowMapScale.set(0.5f, 1.0f); + mShadowMapOffset.set(0.5f, 0.0f); + + // Invert direction on camera matrix + VectorF right, forward; + MatrixF temp = mLight->getTransform(); + temp.getColumn( 1, &forward ); + temp.getColumn( 0, &right ); + forward *= -1.0f; + right *= -1.0f; + temp.setColumn( 1, forward ); + temp.setColumn( 0, right ); + temp.setPosition(mLight->getPosition() - forward * -renderPosOffset); + temp.inverse(); + GFX->setWorldMatrix(temp); + + // Create an inverted scene state for the back-map + delete baseState; + baseState = sceneManager->createBaseState( SPT_Shadow ); + baseState->mRenderNonLightmappedMeshes = true; + baseState->mRenderLightmappedMeshes = bUseLightmappedGeometry; + baseState->setDiffuseCameraTransform( diffuseState->getCameraTransform() ); + baseState->setViewportExtent( diffuseState->getViewportExtent() ); + baseState->setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); + + baseState->getRenderPass()->getMatrixSet().setSceneView(temp); + + // Draw scene + sceneManager->renderScene(baseState); + } + + // Clean up + delete baseState; + + mTarget->resolve(); + GFX->popActiveRenderTarget(); + + // Restore frustum + if (!isOrtho) + GFX->setFrustum(left, right, bottom, top, nearPlane, farPlane); + else + GFX->setOrtho(left, right, bottom, top, nearPlane, farPlane); +} diff --git a/lighting/shadowMap/dualParaboloidLightShadowMap.h b/lighting/shadowMap/dualParaboloidLightShadowMap.h new file mode 100644 index 0000000..db16d5d --- /dev/null +++ b/lighting/shadowMap/dualParaboloidLightShadowMap.h @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _DUALPARABOLOIDLIGHTSHADOWMAP_H_ +#define _DUALPARABOLOIDLIGHTSHADOWMAP_H_ + +#ifndef _PARABOLOIDLIGHTSHADOWMAP_H_ +#include "lighting/shadowMap/paraboloidLightShadowMap.h" +#endif + +class DualParaboloidLightShadowMap : public ParaboloidLightShadowMap +{ + typedef ParaboloidLightShadowMap Parent; + +public: + DualParaboloidLightShadowMap( LightInfo *light ); + + virtual void _render( SceneGraph *sceneManager, const SceneState *diffuseState ); + virtual bool setTextureStage(const SceneGraphData& sgData, const U32 currTexFlag, + const U32 textureSlot, GFXShaderConstBuffer* shaderConsts, + GFXShaderConstHandle* shadowMapSC); +}; + +#endif // _DUALPARABOLOIDLIGHTSHADOWMAP_H_ \ No newline at end of file diff --git a/lighting/shadowMap/lightShadowMap.cpp b/lighting/shadowMap/lightShadowMap.cpp new file mode 100644 index 0000000..eb471a1 --- /dev/null +++ b/lighting/shadowMap/lightShadowMap.cpp @@ -0,0 +1,589 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "platform/platform.h" +#include "lighting/shadowMap/lightShadowMap.h" + +#include "lighting/shadowMap/blurTexture.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gfxOcclusionQuery.h" +#include "materials/materialDefinition.h" +#include "materials/baseMatInstance.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +//#include "sceneGraph/container/sceneContainer.h" +#include "lighting/lightManager.h" +#include "math/mathUtils.h" +#include "shaderGen/shaderGenVars.h" +#include "core/util/safeDelete.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" +#include "materials/shaderData.h" + +// Remove this when the shader constants are reworked better +#include "lighting/advanced/advancedLightManager.h" +#include "lighting/advanced/advancedLightBinManager.h" + +// TODO: Some cards (Justin's GeForce 7x series) barf on the integer format causing +// filtering artifacts. These can (sometimes) be resolved by switching the format +// to FP16 instead of Int16. +const GFXFormat LightShadowMap::ShadowMapFormat = GFXFormatR32F; // GFXFormatR8G8B8A8; + +Vector LightShadowMap::smUsedShadowMaps; +Vector LightShadowMap::smShadowMaps; + +GFX_ImplementTextureProfile( ShadowMapProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::RenderTarget | + GFXTextureProfile::Pooled, + GFXTextureProfile::None ); + +GFX_ImplementTextureProfile( ShadowMapZProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::NoMipmap | + GFXTextureProfile::ZTarget | + GFXTextureProfile::Pooled, + GFXTextureProfile::None ); + +// +// LightShadowMap, holds the shadow map and various other things for a light. +// +LightShadowMap::LightShadowMap( LightInfo *light ) + : mWorldToLightProj( true ), + mLight( light ), + mTexSize( 0 ), + mLastShader( NULL ), + mLastUpdate( 0 ), + mIsViewDependent( false ), + mVizQuery( NULL ), + mWasOccluded( false ), + mLastScreenSize( 0.0f ), + mLastPriority( 0.0f ) +{ + GFXTextureManager::addEventDelegate( this, &LightShadowMap::_onTextureEvent ); + + mTarget = GFX->allocRenderToTextureTarget(); + mVizQuery = GFX->createOcclusionQuery(); + + smShadowMaps.push_back( this ); +} + +LightShadowMap::~LightShadowMap() +{ + mTarget = NULL; + SAFE_DELETE( mVizQuery ); + + releaseTextures(); + + smShadowMaps.remove( this ); + smUsedShadowMaps.remove( this ); + + GFXTextureManager::removeEventDelegate( this, &LightShadowMap::_onTextureEvent ); +} + +void LightShadowMap::releaseAllTextures() +{ + PROFILE_SCOPE( LightShadowMap_ReleaseAllTextures ); + + for ( U32 i=0; i < smShadowMaps.size(); i++ ) + smShadowMaps[i]->releaseTextures(); +} + +void LightShadowMap::releaseUnusedTextures() +{ + PROFILE_SCOPE( LightShadowMap_ReleaseUnusedTextures ); + + const U32 currTime = Platform::getRealMilliseconds(); + const U32 purgeTime = 1000; + + for ( U32 i=0; i < smUsedShadowMaps.size(); ) + { + LightShadowMap *lsm = smUsedShadowMaps[i]; + + // If the shadow has not been updated for a while + // then release its textures. + if ( currTime > ( lsm->getLastUpdate() + purgeTime ) ) + { + // Internally this will remove the map from the used + // list, so don't increment the loop. + lsm->releaseTextures(); + continue; + } + + i++; + } +} + +void LightShadowMap::_onTextureEvent( GFXTexCallbackCode code ) +{ + if ( code == GFXZombify ) + releaseTextures(); + + // We don't initialize here as we want the textures + // to be reallocated when the shadow becomes visible. +} + +Frustum LightShadowMap::_getFrustum() const +{ + F32 left, right, bottom, top, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + + MatrixF camMat(GFX->getWorldMatrix() ); + camMat.inverse(); + + Frustum f; + f.set( isOrtho, left, right, top, bottom, nearPlane, farPlane, camMat ); + return f; +} + +void LightShadowMap::calcLightMatrices(MatrixF& outLightMatrix) +{ + // Create light matrix, set projection + + switch ( mLight->getType() ) + { + case LightInfo::Vector : + { + Frustum viewFrustum(_getFrustum()); + + const ShadowMapParams *p = mLight->getExtended(); + + // Calculate the bonding box of the shadowed area + // we're interested in... this is the shadow box + // transformed by the frustum transform. + Box3F viewBB( -p->shadowDistance, -p->shadowDistance, -p->shadowDistance, + p->shadowDistance, p->shadowDistance, p->shadowDistance ); + viewFrustum.getTransform().mul( viewBB ); + + // Calculate a light "projection" matrix. + MatrixF lightMatrix = MathUtils::createOrientFromDir(mLight->getDirection()); + outLightMatrix = lightMatrix; + static MatrixF rotMat(EulerF( (M_PI_F / 2.0f), 0.0f, 0.0f)); + lightMatrix.mul( rotMat ); + + // This is the box in lightspace + Box3F lightViewBB(viewBB); + lightMatrix.mul(lightViewBB); + + // Now, let's position our light based on the lightViewBB + Point3F newLightPos(viewBB.getCenter()); + F32 sceneDepth = lightViewBB.maxExtents.z - lightViewBB.minExtents.z; + newLightPos += mLight->getDirection() * ((-sceneDepth / 2.0f)-1.0f); // -1 for the nearplane + outLightMatrix.setPosition(newLightPos); + + // Update light info + mLight->setRange( sceneDepth ); + mLight->setPosition( newLightPos ); + + // Set our ortho projection + F32 width = (lightViewBB.maxExtents.x - lightViewBB.minExtents.x) / 2.0f; + F32 height = (lightViewBB.maxExtents.y - lightViewBB.minExtents.y) / 2.0f; + + width = getMax(width, height); + + GFX->setOrtho(-width, width, -width, width, 1.0f, sceneDepth, true); + + + // TODO: Width * 2... really isn't that pixels being used as + // meters? Is a real physical metric of scene depth better? + //sm->setVisibleDistance(width * 2.0f); + +#if 0 + DebugDrawer::get()->drawFrustum(viewFrustum, ColorF(1.0f, 0.0f, 0.0f)); + DebugDrawer::get()->drawBox(viewBB.minExtents, viewBB.maxExtents, ColorF(0.0f, 1.0f, 0.0f)); + DebugDrawer::get()->drawBox(lightViewBB.minExtents, lightViewBB.maxExtents, ColorF(0.0f, 0.0f, 1.0f)); + DebugDrawer::get()->drawBox(newLightPos - Point3F(1,1,1), newLightPos + Point3F(1,1,1), ColorF(1,1,0)); + DebugDrawer::get()->drawLine(newLightPos, newLightPos + mLight.mDirection*3.0f, ColorF(0,1,1)); + + Point3F a(newLightPos); + Point3F b(newLightPos); + Point3F offset(width, height,0.0f); + a -= offset; + b += offset; + DebugDrawer::get()->drawBox(a, b, ColorF(0.5f, 0.5f, 0.5f)); +#endif + } + break; + case LightInfo::Spot : + { + // Note: If a light is attached to a camera or something and mDirection is 0,0,-1 or 0,0,1 this transform won't match the camera because + // we don't have the up vector from the mLight structure (I don't think this is a big deal). + outLightMatrix = MathUtils::createOrientFromDir(mLight->getDirection()); + outLightMatrix.setPosition(mLight->getPosition()); + + F32 fov = mLight->getOuterConeAngle(); + F32 range = mLight->getRange().x; + GFX->setFrustum( fov, 1.0f, range * 0.01f, range ); + } + break; + default: + AssertFatal(false, "Unsupported light type!"); + } +} + +void LightShadowMap::releaseTextures() +{ + mShadowMapTex = NULL; + mLastUpdate = 0; + smUsedShadowMaps.remove( this ); +} + +GFXTextureObject* LightShadowMap::_getDepthTarget( U32 width, U32 height ) +{ + // Get a depth texture target from the pooled profile + // which is returned as a temporary. + GFXTexHandle depthTex( width, height, GFXFormatD24S8, &ShadowMapZProfile, + "LightShadowMap::_getDepthTarget()" ); + + return depthTex; +} + +bool LightShadowMap::setTextureStage( const SceneGraphData& sgData, + const U32 currTexFlag, + const U32 textureSlot, + GFXShaderConstBuffer* shaderConsts, + GFXShaderConstHandle* shadowMapSC ) +{ + switch (currTexFlag) + { + case Material::DynamicLight: + { + if(shaderConsts && shadowMapSC->isValid()) + shaderConsts->set(shadowMapSC, (S32)textureSlot); + GFX->setTexture(textureSlot, mShadowMapTex); + return true; + } + break; + } + return false; +} + +void LightShadowMap::render( SceneGraph *sceneManager, + const SceneState *diffuseState ) +{ + + _render( sceneManager, diffuseState ); + + // Add it to the used list unless we're been updated. + if ( !mLastUpdate ) + { + AssertFatal( !smUsedShadowMaps.contains( this ), "LightShadowMap::render - Used shadow map inserted twice!" ); + smUsedShadowMaps.push_back( this ); + } + + mLastUpdate = Platform::getRealMilliseconds(); +} + +void LightShadowMap::preLightRender() +{ + if ( mVizQuery ) + { + mWasOccluded = mVizQuery->getStatus( true ) == GFXOcclusionQuery::Occluded; + mVizQuery->begin(); + } +} + +void LightShadowMap::postLightRender() +{ + if ( mVizQuery ) + mVizQuery->end(); +} + +void LightShadowMap::updatePriority( const SceneState *state, U32 currTimeMs ) +{ + F32 dist = SphereF( mLight->getPosition(), mLight->getRange().x ).distanceTo( state->getCameraPosition() ); + mLastScreenSize = state->projectRadius( dist, mLight->getRange().x ); + + F32 timeSinceLastUpdate = currTimeMs - mLastUpdate; + mLastPriority = ( 1.0f - mClampF( mLastScreenSize / 600, 0.0f, 1.0f ) ) + + timeSinceLastUpdate; +} + +S32 QSORT_CALLBACK LightShadowMap::cmpPriority( LightShadowMap *const *lsm1, LightShadowMap *const *lsm2 ) +{ + F32 diff = (*lsm1)->getLastPriority() - (*lsm2)->getLastPriority(); + return diff > 0.0f ? -1 : diff < 0.0f ? 1 : 0; +} + + +LightingShaderConstants::LightingShaderConstants() + : mInit( false ), + mShader( NULL ), + mLightParamsSC(NULL), + mLightSpotParamsSC(NULL), + mLightPositionSC(NULL), + mLightDiffuseSC(NULL), + mLightAmbientSC(NULL), + mLightInvRadiusSqSC(NULL), + mLightSpotDirSC(NULL), + mLightSpotAngleSC(NULL), + mShadowMapSC(NULL), + mShadowMapSizeSC(NULL), + mRandomDirsConst(NULL), + mShadowSoftnessConst(NULL), + mWorldToLightProjSC(NULL), + mViewToLightProjSC(NULL), + mSplitStartSC(NULL), + mSplitEndSC(NULL), + mScaleXSC(NULL), + mScaleYSC(NULL), + mOffsetXSC(NULL), + mOffsetYSC(NULL), + mAtlasXOffsetSC(NULL), + mAtlasYOffsetSC(NULL), + mAtlasScaleSC(NULL), + mFadeStartLength(NULL), + mFarPlaneScalePSSM(NULL), + mOverDarkFactorPSSM(NULL), + mSplitFade(NULL), + mConstantSpecularPowerSC(NULL), + mTapRotationTexSC(NULL) +{ +} + +LightingShaderConstants::~LightingShaderConstants() +{ + if (mShader.isValid()) + { + mShader->getReloadSignal().remove( this, &LightingShaderConstants::_onShaderReload ); + mShader = NULL; + } +} + +void LightingShaderConstants::init(GFXShader* shader) +{ + if (mShader.getPointer() != shader) + { + if (mShader.isValid()) + mShader->getReloadSignal().remove( this, &LightingShaderConstants::_onShaderReload ); + + mShader = shader; + mShader->getReloadSignal().notify( this, &LightingShaderConstants::_onShaderReload ); + } + + mLightParamsSC = shader->getShaderConstHandle("$lightParams"); + mLightSpotParamsSC = shader->getShaderConstHandle("$lightSpotParams"); + + // NOTE: These are the shader constants used for doing lighting + // during the forward pass. Do not confuse these for the prepass + // lighting constants which are used from AdvancedLightBinManager. + mLightPositionSC = shader->getShaderConstHandle( ShaderGenVars::lightPosition ); + mLightDiffuseSC = shader->getShaderConstHandle( ShaderGenVars::lightDiffuse ); + mLightAmbientSC = shader->getShaderConstHandle( ShaderGenVars::lightAmbient ); + mLightInvRadiusSqSC = shader->getShaderConstHandle( ShaderGenVars::lightInvRadiusSq ); + mLightSpotDirSC = shader->getShaderConstHandle( ShaderGenVars::lightSpotDir ); + mLightSpotAngleSC = shader->getShaderConstHandle( ShaderGenVars::lightSpotAngle ); + + mShadowMapSC = shader->getShaderConstHandle("$shadowMap"); + mShadowMapSizeSC = shader->getShaderConstHandle("$shadowMapSize"); + + mShadowSoftnessConst = shader->getShaderConstHandle("$shadowSoftness"); + + mWorldToLightProjSC = shader->getShaderConstHandle("$worldToLightProj"); + mViewToLightProjSC = shader->getShaderConstHandle("$viewToLightProj"); + + mSplitStartSC = shader->getShaderConstHandle("$splitDistStart"); + mSplitEndSC = shader->getShaderConstHandle("$splitDistEnd"); + mScaleXSC = shader->getShaderConstHandle("$scaleX"); + mScaleYSC = shader->getShaderConstHandle("$scaleY"); + mOffsetXSC = shader->getShaderConstHandle("$offsetX"); + mOffsetYSC = shader->getShaderConstHandle("$offsetY"); + mAtlasXOffsetSC = shader->getShaderConstHandle("$atlasXOffset"); + mAtlasYOffsetSC = shader->getShaderConstHandle("$atlasYOffset"); + mAtlasScaleSC = shader->getShaderConstHandle("$atlasScale"); + + mFadeStartLength = shader->getShaderConstHandle("$fadeStartLength"); + mFarPlaneScalePSSM = shader->getShaderConstHandle("$farPlaneScalePSSM"); + + mOverDarkFactorPSSM = shader->getShaderConstHandle("$overDarkPSSM"); + mSplitFade = shader->getShaderConstHandle("$splitFade"); + + mConstantSpecularPowerSC = shader->getShaderConstHandle( AdvancedLightManager::ConstantSpecularPowerSC ); + + mTapRotationTexSC = shader->getShaderConstHandle( "$gTapRotationTex" ); + + mInit = true; +} + +void LightingShaderConstants::_onShaderReload() +{ + if (mShader.isValid()) + init( mShader ); +} + + +const LightInfoExType ShadowMapParams::Type( "ShadowMapParams" ); + +ShadowMapParams::ShadowMapParams( LightInfo *light ) + : mLight( light ), + mShadowMap( NULL ) +{ + attenuationRatio.set( 0.0f, 1.0f, 1.0f ); + shadowType = ShadowType_Spot; + overDarkFactor.set(2000.0f, 1000.0f, 500.0f, 100.0f); + numSplits = 4; + logWeight = 0.91f; + texSize = 512; + shadowDistance = 400.0f; + shadowSoftness = 0.15f; + fadeStartDist = 0.0f; + splitFadeDistances.set(10.0f, 20.0f, 30.0f, 40.0f); + lastSplitTerrainOnly = false; + _validate(); +} + +ShadowMapParams::~ShadowMapParams() +{ + SAFE_DELETE( mShadowMap ); +} + +void ShadowMapParams::_validate() +{ + switch ( mLight->getType() ) + { + case LightInfo::Spot: + shadowType = ShadowType_Spot; + break; + + case LightInfo::Vector: + shadowType = ShadowType_PSSM; + break; + + case LightInfo::Point: + if ( shadowType < ShadowType_Paraboloid ) + shadowType = ShadowType_DualParaboloidSinglePass; + break; + + default: + break; + } + + if ( mLight->getType() == LightInfo::Vector ) + { + numSplits = mClamp( numSplits, 1, 4 ); + + // Limit the total texture size for the PSSM to + // 4096... so use the split count to decide what + // the size of a single split can be. + U32 max = 4096; + if ( numSplits == 2 || numSplits == 4 ) + max = 2048; + if ( numSplits == 3 ) + max = 1024; + + texSize = mClamp( texSize, 32, max ); + } + else + { + texSize = mClamp( texSize, 32, 4096 ); + numSplits = 1; + } +} + +#include "lighting/shadowMap/singleLightShadowMap.h" +#include "lighting/shadowMap/pssmLightShadowMap.h" +#include "lighting/shadowMap/cubeLightShadowMap.h" +#include "lighting/shadowMap/dualParaboloidLightShadowMap.h" + +LightShadowMap* ShadowMapParams::getOrCreateShadowMap() +{ + if ( mShadowMap ) + return mShadowMap; + + if ( !mLight->getCastShadows() ) + return NULL; + + switch ( mLight->getType() ) + { + case LightInfo::Spot: + mShadowMap = new SingleLightShadowMap( mLight ); + break; + + case LightInfo::Vector: + mShadowMap = new PSSMLightShadowMap( mLight ); + break; + + case LightInfo::Point: + + if ( shadowType == ShadowType_CubeMap ) + mShadowMap = new CubeLightShadowMap( mLight ); + else if ( shadowType == ShadowType_Paraboloid ) + mShadowMap = new ParaboloidLightShadowMap( mLight ); + else + mShadowMap = new DualParaboloidLightShadowMap( mLight ); + break; + + default: + break; + } + + return mShadowMap; +} + +void ShadowMapParams::set( const LightInfoEx *ex ) +{ + // TODO: Do we even need this? +} + +void ShadowMapParams::packUpdate( BitStream *stream ) const +{ + // HACK: We need to work out proper parameter + // validation when any field changes on the light. + + ((ShadowMapParams*)this)->_validate(); + + stream->writeInt( shadowType, 8 ); + + mathWrite( *stream, attenuationRatio ); + + stream->write( texSize ); + + stream->write( numSplits ); + stream->write( logWeight ); + + mathWrite(*stream, overDarkFactor); + + stream->write( fadeStartDist ); + stream->writeFlag( lastSplitTerrainOnly ); + + mathWrite(*stream, splitFadeDistances); + + stream->write( shadowDistance ); + + stream->write( shadowSoftness ); +} + +void ShadowMapParams::unpackUpdate( BitStream *stream ) +{ + ShadowType newType = (ShadowType)stream->readInt( 8 ); + if ( shadowType != newType ) + { + // If the shadow type changes delete the shadow + // map so it can be reallocated on the next render. + shadowType = newType; + SAFE_DELETE( mShadowMap ); + } + + mathRead( *stream, &attenuationRatio ); + + stream->read( &texSize ); + + stream->read( &numSplits ); + stream->read( &logWeight ); + mathRead(*stream, &overDarkFactor); + + stream->read( &fadeStartDist ); + lastSplitTerrainOnly = stream->readFlag(); + + mathRead(*stream, &splitFadeDistances); + + stream->read( &shadowDistance ); + + stream->read( &shadowSoftness ); +} diff --git a/lighting/shadowMap/lightShadowMap.h b/lighting/shadowMap/lightShadowMap.h new file mode 100644 index 0000000..081e18f --- /dev/null +++ b/lighting/shadowMap/lightShadowMap.h @@ -0,0 +1,329 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LIGHTSHADOWMAP_H_ +#define _LIGHTSHADOWMAP_H_ + +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif +#ifndef _SHADOW_COMMON_H_ +#include "lighting/shadowMap/shadowCommon.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif + +class ShadowMapManager; +class SceneGraph; +class SceneState; +class BaseMatInstance; +class MaterialParameters; +class SharedShadowMapObjects; +struct SceneGraphData; +class GFXShaderConstBuffer; +class GFXShaderConstHandle; +class GFXShader; +class GFXOcclusionQuery; +class LightManager; + + +// Shader constant handle lookup +// This isn't broken up as much as it could be, we're mixing single light constants +// and pssm constants. +struct LightingShaderConstants +{ + bool mInit; + + GFXShaderRef mShader; + + GFXShaderConstHandle* mLightParamsSC; + GFXShaderConstHandle* mLightSpotParamsSC; + + // NOTE: These are the shader constants used for doing + // lighting during the forward pass. Do not confuse + // these for the prepass lighting constants which are + // used from AdvancedLightBinManager. + GFXShaderConstHandle *mLightPositionSC; + GFXShaderConstHandle *mLightDiffuseSC; + GFXShaderConstHandle *mLightAmbientSC; + GFXShaderConstHandle *mLightInvRadiusSqSC; + GFXShaderConstHandle *mLightSpotDirSC; + GFXShaderConstHandle *mLightSpotAngleSC; + + GFXShaderConstHandle* mShadowMapSC; + GFXShaderConstHandle* mShadowMapSizeSC; + + GFXShaderConstHandle* mRandomDirsConst; + GFXShaderConstHandle* mShadowSoftnessConst; + + GFXShaderConstHandle* mWorldToLightProjSC; + GFXShaderConstHandle* mViewToLightProjSC; + + GFXShaderConstHandle* mSplitStartSC; + GFXShaderConstHandle* mSplitEndSC; + GFXShaderConstHandle* mScaleXSC; + GFXShaderConstHandle* mScaleYSC; + GFXShaderConstHandle* mOffsetXSC; + GFXShaderConstHandle* mOffsetYSC; + GFXShaderConstHandle* mAtlasXOffsetSC; + GFXShaderConstHandle* mAtlasYOffsetSC; + GFXShaderConstHandle* mAtlasScaleSC; + + // fadeStartLength.x = Distance in eye space to start fading shadows + // fadeStartLength.y = 1 / Length of fade + GFXShaderConstHandle* mFadeStartLength; + GFXShaderConstHandle* mFarPlaneScalePSSM; + GFXShaderConstHandle* mOverDarkFactorPSSM; + GFXShaderConstHandle* mSplitFade; + + // This should be moved too. + GFXShaderConstHandle* mConstantSpecularPowerSC; + + GFXShaderConstHandle* mTapRotationTexSC; + + LightingShaderConstants(); + ~LightingShaderConstants(); + + void init(GFXShader* buffer); + + void _onShaderReload(); +}; + +typedef Map LightConstantMap; + + +/// This represents everything we need to render +/// the shadowmap for one light. +class LightShadowMap : public MatTextureTarget +{ +public: + + const static GFXFormat ShadowMapFormat; + +public: + + LightShadowMap( LightInfo *light ); + + virtual ~LightShadowMap(); + + void render( SceneGraph *sceneManager, + const SceneState *diffuseState ); + + U32 getLastUpdate() const { return mLastUpdate; } + + //U32 getLastVisible() const { return mLastVisible; } + + bool isViewDependent() const { return mIsViewDependent; } + + bool wasOccluded() const { return mWasOccluded; } + + void preLightRender(); + + void postLightRender(); + + void updatePriority( const SceneState *state, U32 currTimeMs ); + + F32 getLastScreenSize() const { return mLastScreenSize; } + + F32 getLastPriority() const { return mLastPriority; } + + virtual bool hasShadowTex() const { return mShadowMapTex.isValid(); } + + virtual bool setTextureStage(const SceneGraphData& sgData, const U32 currTexFlag, + const U32 textureSlot, GFXShaderConstBuffer* shaderConsts, + GFXShaderConstHandle* shadowMapSC); + + LightInfo* getLightInfo() { return mLight; } + + virtual void setShaderParameters(GFXShaderConstBuffer* params, LightingShaderConstants* lsc) = 0; + + U32 getTexSize() const { return mTexSize; } + + const MatrixF& getWorldToLightProj() const { return mWorldToLightProj; } + + static GFXTextureObject* _getDepthTarget( U32 width, U32 height ); + + virtual ShadowType getShadowType() const = 0; + + // Cleanup texture resources + virtual void releaseTextures(); + + // MatTextureTarget + virtual GFXTextureObject* getTargetTexture( U32 mrtIndex ) const { return mShadowMapTex.getPointer(); } + virtual const RectI& getTargetViewport() const { return RectI::One; } + virtual void setupSamplerState( GFXSamplerStateDesc *desc ) const { TORQUE_UNUSED( desc ); } + virtual ConditionerFeature* getTargetConditioner() const { return NULL; } + + static void releaseAllTextures(); + + static void releaseUnusedTextures(); + + /// + static S32 QSORT_CALLBACK cmpPriority( LightShadowMap *const *lsm1, LightShadowMap *const *lsm2 ); + +protected: + + /// All the shadow maps in the system. + static Vector smShadowMaps; + + /// All the shadow maps that have been reciently rendered to. + static Vector smUsedShadowMaps; + + virtual void _render( SceneGraph *sceneManager, + const SceneState *diffuseState ) = 0; + + /// If true the shadow is view dependent and cannot + /// be skipped if visible and within active range. + bool mIsViewDependent; + + /// The time this shadow was last updated. + U32 mLastUpdate; + + /// The time this shadow/light was last updated. + U32 mLastCull; + + /// The shadow occlusion query used when the light is + /// rendered to determin if any pixel of it is visible. + GFXOcclusionQuery *mVizQuery; + + /// If true the light was occluded by geometry the + /// last frame it was updated. + //the last frame. + bool mWasOccluded; + + F32 mLastScreenSize; + + F32 mLastPriority; + + MatrixF mWorldToLightProj; + + GFXTextureTargetRef mTarget; + U32 mTexSize; + GFXTexHandle mShadowMapTex; + + // The light we are rendering. + LightInfo *mLight; + + // Used for blur + GFXShader* mLastShader; + GFXShaderConstHandle* mBlurBoundaries; + + // Calculate view matrices and set proper projection with GFX + virtual void calcLightMatrices(MatrixF& outLightMatrix); + + Frustum _getFrustum() const; + + /// The callback used to get texture events. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ); +}; + +GFX_DeclareTextureProfile( ShadowMapProfile ); +GFX_DeclareTextureProfile( ShadowMapZProfile ); + + +class ShadowMapParams : public LightInfoEx +{ +public: + + ShadowMapParams( LightInfo *light ); + virtual ~ShadowMapParams(); + + /// The LightInfoEx hook type. + static const LightInfoExType Type; + + // LightInfoEx + virtual void set( const LightInfoEx *ex ); + virtual const LightInfoExType& getType() const { return Type; } + virtual void packUpdate( BitStream *stream ) const; + virtual void unpackUpdate( BitStream *stream ); + + LightShadowMap* getShadowMap() { return mShadowMap; } + + LightShadowMap* getOrCreateShadowMap(); + + // Validates the parameters after a field is changed. + void _validate(); + +protected: + + void _initShadowMap(); + + /// + LightShadowMap *mShadowMap; + + LightInfo *mLight; + +public: + + // We're leaving these public for easy access + // for console protected fields. + + /// @name Shadow Map + /// @{ + + /// + U32 texSize; + + /// @} + + Point3F attenuationRatio; + + /// @name Point Lights + /// @{ + + /// + ShadowType shadowType; + + /// @} + + /// @name Exponential Shadow Map Parameters + /// @{ + Point4F overDarkFactor; + /// @} + + /// @name Parallel Split Shadow Map + /// @{ + + /// + F32 shadowDistance; + + /// + F32 shadowSoftness; + + /// The number of splits in the shadow map. + U32 numSplits; + + /// + F32 logWeight; + + /// At what distance do we start fading the shadows out completely + F32 fadeStartDist; + + /// This toggles only terrain being visible in the last + /// split of a PSSM shadow map. + bool lastSplitTerrainOnly; + + /// Distances (in world space units) that the shadows will fade between + /// PSSM splits + Point4F splitFadeDistances; + + /// @} +}; + +#endif // _LIGHTSHADOWMAP_H_ diff --git a/lighting/shadowMap/paraboloidLightShadowMap.cpp b/lighting/shadowMap/paraboloidLightShadowMap.cpp new file mode 100644 index 0000000..1a72cf0 --- /dev/null +++ b/lighting/shadowMap/paraboloidLightShadowMap.cpp @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/paraboloidLightShadowMap.h" +#include "lighting/common/lightMapParams.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "math/mathUtils.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +//#include "scene/sceneReflectPass.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" +#include "materials/materialDefinition.h" +#include "gui/controls/guiBitmapCtrl.h" + +ParaboloidLightShadowMap::ParaboloidLightShadowMap( LightInfo *light ) + : Parent( light ), + mShadowMapScale( 1, 1 ), + mShadowMapOffset( 0, 0 ) +{ +} + +ParaboloidLightShadowMap::~ParaboloidLightShadowMap() +{ + releaseTextures(); +} + +ShadowType ParaboloidLightShadowMap::getShadowType() const +{ + const ShadowMapParams *params = mLight->getExtended(); + return params->shadowType; +} + +void ParaboloidLightShadowMap::setShaderParameters(GFXShaderConstBuffer* params, LightingShaderConstants* lsc) +{ + if ( lsc->mTapRotationTexSC->isValid() ) + GFX->setTexture( lsc->mTapRotationTexSC->getSamplerRegister(), + SHADOWMGR->getTapRotationTex() ); + + ShadowMapParams *p = mLight->getExtended(); + if ( lsc->mLightParamsSC->isValid() ) + { + Point4F lightParams( mLight->getRange().x, p->overDarkFactor.x, 0.0f, 0.0f); + params->set( lsc->mLightParamsSC, lightParams ); + } + + // Atlasing parameters (only used in the dual case, set here to use same shaders) + if ( lsc->mAtlasScaleSC->isValid() ) + params->set( lsc->mAtlasScaleSC, mShadowMapScale ); + + if( lsc->mAtlasXOffsetSC->isValid() ) + params->set( lsc->mAtlasXOffsetSC, mShadowMapOffset ); + + // The softness is a factor of the texel size. + if ( lsc->mShadowSoftnessConst->isValid() ) + params->set( lsc->mShadowSoftnessConst, p->shadowSoftness * ( 1.0f / mTexSize ) ); +} + +void ParaboloidLightShadowMap::_render( SceneGraph *sceneManager, + const SceneState *diffuseState ) +{ + PROFILE_SCOPE(ParaboloidLightShadowMap_render); + + const ShadowMapParams *p = mLight->getExtended(); + const LightMapParams *lmParams = mLight->getExtended(); + const bool bUseLightmappedGeometry = lmParams ? !lmParams->representedInLightmap || lmParams->includeLightmappedGeometryInShadow : true; + + if ( mShadowMapTex.isNull() || + mTexSize != p->texSize ) + { + mTexSize = p->texSize; + + mShadowMapTex.set( mTexSize, mTexSize, + ShadowMapFormat, &ShadowMapProfile, + "ParaboloidLightShadowMap" ); + } + + GFXTransformSaver saver; + F32 left, right, bottom, top, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + + // Render the shadowmap! + GFX->pushActiveRenderTarget(); + + // Calc matrix and set up visible distance + mWorldToLightProj = mLight->getTransform(); + mWorldToLightProj.inverse(); + GFX->setWorldMatrix(mWorldToLightProj); + + const F32 &lightRadius = mLight->getRange().x; + GFX->setOrtho(-lightRadius, lightRadius, -lightRadius, lightRadius, 1.0f, lightRadius, true); + + // Set up target + mTarget->attachTexture( GFXTextureTarget::Color0, mShadowMapTex ); + mTarget->attachTexture( GFXTextureTarget::DepthStencil, + _getDepthTarget( mShadowMapTex->getWidth(), mShadowMapTex->getHeight() ) ); + GFX->setActiveRenderTarget(mTarget); + GFX->clear(GFXClearTarget | GFXClearStencil | GFXClearZBuffer, ColorI(255,255,255,255), 1.0f, 0); + + // Create scene state, prep it + SceneState* baseState = sceneManager->createBaseState( SPT_Shadow ); + baseState->mRenderNonLightmappedMeshes = true; + baseState->mRenderLightmappedMeshes = bUseLightmappedGeometry; + baseState->setDiffuseCameraTransform( diffuseState->getCameraTransform() ); + baseState->setViewportExtent( diffuseState->getViewportExtent() ); + baseState->setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); + + sceneManager->renderScene(baseState); + + delete baseState; + + mTarget->resolve(); + GFX->popActiveRenderTarget(); + + // Restore frustum + if (!isOrtho) + GFX->setFrustum(left, right, bottom, top, nearPlane, farPlane); + else + GFX->setOrtho(left, right, bottom, top, nearPlane, farPlane); +} \ No newline at end of file diff --git a/lighting/shadowMap/paraboloidLightShadowMap.h b/lighting/shadowMap/paraboloidLightShadowMap.h new file mode 100644 index 0000000..63a67f1 --- /dev/null +++ b/lighting/shadowMap/paraboloidLightShadowMap.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PARABOLOIDLIGHTSHADOWMAP_H_ +#define _PARABOLOIDLIGHTSHADOWMAP_H_ + +#ifndef _LIGHTSHADOWMAP_H_ +#include "lighting/shadowMap/lightShadowMap.h" +#endif + + +class ParaboloidLightShadowMap : public LightShadowMap +{ + typedef LightShadowMap Parent; +public: + ParaboloidLightShadowMap( LightInfo *light ); + ~ParaboloidLightShadowMap(); + + // LightShadowMap + virtual ShadowType getShadowType() const; + virtual void _render( SceneGraph *sceneManager, const SceneState *diffuseState ); + virtual void setShaderParameters(GFXShaderConstBuffer* params, LightingShaderConstants* lsc); + +protected: + Point2F mShadowMapScale; + Point2F mShadowMapOffset; +}; + +#endif \ No newline at end of file diff --git a/lighting/shadowMap/pssmLightShadowMap.cpp b/lighting/shadowMap/pssmLightShadowMap.cpp new file mode 100644 index 0000000..bc49d7c --- /dev/null +++ b/lighting/shadowMap/pssmLightShadowMap.cpp @@ -0,0 +1,433 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/pssmLightShadowMap.h" +#include "lighting/common/lightMapParams.h" +#include "console/console.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "lighting/lightManager.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" +#include "gui/controls/guiBitmapCtrl.h" +#include "lighting/shadowMap/blurTexture.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "materials/shaderData.h" +#include "ts/tsShapeInstance.h" + +PSSMLightShadowMap::PSSMLightShadowMap( LightInfo *light ) + : LightShadowMap( light ), + mNumSplits( 0 ) +{ + mIsViewDependent = true; +} + +void PSSMLightShadowMap::_setNumSplits( U32 numSplits, U32 texSize ) +{ + AssertFatal( numSplits > 0 && numSplits <= MAX_SPLITS, + "PSSMLightShadowMap::_setNumSplits() - Splits must be between 1 and 4!" ); + + releaseTextures(); + + mNumSplits = numSplits; + mTexSize = texSize; + F32 texWidth, texHeight; + + // If the split count is less than 4 then do a + // 1xN layout of shadow maps... + if ( mNumSplits < 4 ) + { + texHeight = texSize; + texWidth = texSize * mNumSplits; + + for ( U32 i = 0; i < 4; i++ ) + { + mViewports[i].extent.set(texSize, texSize); + mViewports[i].point.set(texSize*i, 0); + } + } + else + { + // ... with 4 splits do a 2x2. + texWidth = texHeight = texSize * 2; + + for ( U32 i = 0; i < 4; i++ ) + { + F32 xOff = (i == 1 || i == 3) ? 0.5f : 0.0f; + F32 yOff = (i > 1) ? 0.5f : 0.0f; + mViewports[i].extent.set( texSize, texSize ); + mViewports[i].point.set( xOff * texWidth, yOff * texHeight ); + } + } + + mShadowMapTex.set( texWidth, texHeight, + ShadowMapFormat, &ShadowMapProfile, + "PSSMLightShadowMap" ); +} + +void PSSMLightShadowMap::_calcSplitPos(const Frustum& currFrustum) +{ + const F32 nearDist = 0.01f; //currFrustum.getNearDist(); + const F32 farDist = currFrustum.getFarDist(); + + // TODO: I like this split scheme alot... but it + // only looks good for 4 splits. + /* + mSplitDist[0] = near; + mSplitDist[mNumSplits] = far; + + F32 factor = 1.0f / ( mNumSplits - 1 ); + for ( S32 i=mNumSplits-1; i > 0; i-- ) + mSplitDist[i] = mSplitDist[i+1] * factor; + */ + + for ( U32 i = 1; i < mNumSplits; i++ ) + { + F32 step = (F32) i / (F32) mNumSplits; + F32 logSplit = nearDist * mPow(farDist / nearDist, step); + F32 linearSplit = nearDist + (farDist - nearDist) * step; + mSplitDist[i] = mLerp( linearSplit, logSplit, mClampF( mLogWeight, 0.0f, 1.0f ) ); + } + + mSplitDist[0] = nearDist; + mSplitDist[mNumSplits] = farDist; +} + +Box3F PSSMLightShadowMap::_calcClipSpaceAABB(const Frustum& f, const MatrixF& transform, F32 farDist) +{ + Box3F result; + for (U32 i = 0; i < 8; i++) + { + const Point3F& pt = f.getPoints()[i]; + // Lets just translate to lightspace + + // We need the Point4F so that we can project the w + Point4F xformed(pt.x, pt.y, pt.z, 1.0f); + transform.mul(xformed); + F32 absW = mFabs(xformed.w); + xformed.x /= absW; + xformed.y /= absW; + xformed.z /= absW; + Point3F xformed3(xformed.x, xformed.y, xformed.z); + if (i != 0) + { + result.minExtents.setMin(xformed3); + result.maxExtents.setMax(xformed3); + } else { + result.minExtents = xformed3; + result.maxExtents = xformed3; + } + } + + result.minExtents.x = mClampF(result.minExtents.x, -1.0f, 1.0f); + result.minExtents.y = mClampF(result.minExtents.y, -1.0f, 1.0f); + result.maxExtents.x = mClampF(result.maxExtents.x, -1.0f, 1.0f); + result.maxExtents.y = mClampF(result.maxExtents.y, -1.0f, 1.0f); + + return result; +} + +// This "rounds" the projection matrix to remove subtexel movement during shadow map +// rasterization. This is here to reduce shadow shimmering. +void PSSMLightShadowMap::_roundProjection(const MatrixF& lightMat, const MatrixF& cropMatrix, Point3F &offset, U32 splitNum) +{ + // Round to the nearest shadowmap texel, this helps reduce shimmering + MatrixF currentProj = GFX->getProjectionMatrix(); + currentProj = cropMatrix * currentProj * lightMat; + + // Project origin to screen. + Point4F originShadow4F(0,0,0,1); + currentProj.mul(originShadow4F); + Point2F originShadow(originShadow4F.x / originShadow4F.w, originShadow4F.y / originShadow4F.w); + + // Convert to texture space (0..shadowMapSize) + F32 t = mShadowMapTex->getWidth() / mNumSplits; + Point2F texelsToTexture(t / 2.0f, mShadowMapTex->getHeight() / 2.0f); + originShadow.convolve(texelsToTexture); + + // Clamp to texel boundary + Point2F originRounded; + originRounded.x = mFloor(originShadow.x + 0.5f); + originRounded.y = mFloor(originShadow.y + 0.5f); + + // Subtract origin to get an offset to recenter everything on texel boundaries + originRounded -= originShadow; + + // Convert back to texels (0..1) and offset + originRounded.convolveInverse(texelsToTexture); + offset.x += originRounded.x; + offset.y += originRounded.y; +} + +void PSSMLightShadowMap::_render( SceneGraph* sceneManager, + const SceneState *diffuseState ) +{ + PROFILE_SCOPE(PSSMLightShadowMap_render); + + const ShadowMapParams *params = mLight->getExtended(); + const LightMapParams *lmParams = mLight->getExtended(); + const bool bUseLightmappedGeometry = lmParams ? !lmParams->representedInLightmap || lmParams->includeLightmappedGeometryInShadow : true; + + if ( mShadowMapTex.isNull() || + mNumSplits != params->numSplits || + mTexSize != params->texSize ) + _setNumSplits( params->numSplits, params->texSize ); + mLogWeight = params->logWeight; + + Frustum fullFrustum(_getFrustum()); + fullFrustum.cropNearFar(fullFrustum.getNearDist(), params->shadowDistance); + + GFXTransformSaver saver; + F32 left, right, bottom, top, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + + // Set our render target + GFX->pushActiveRenderTarget(); + mTarget->attachTexture( GFXTextureTarget::Color0, mShadowMapTex ); + mTarget->attachTexture( GFXTextureTarget::DepthStencil, + _getDepthTarget( mShadowMapTex->getWidth(), mShadowMapTex->getHeight() ) ); + GFX->setActiveRenderTarget( mTarget ); + GFX->clear( GFXClearStencil | GFXClearZBuffer | GFXClearTarget, ColorI(255,255,255), 1.0f, 0 ); + + // Calculate our standard light matrices + MatrixF lightMatrix; + calcLightMatrices(lightMatrix); + lightMatrix.inverse(); + MatrixF lightViewProj = GFX->getProjectionMatrix() * lightMatrix; + + F32 pleft, pright, pbottom, ptop, pnear, pfar; + bool pisOrtho; + GFX->getFrustum(&pleft, &pright, &pbottom, &ptop, &pnear, &pfar,&pisOrtho); + + // Set our view up + GFX->setWorldMatrix(lightMatrix); + MatrixF toLightSpace = lightMatrix; // * invCurrentView; + + _calcSplitPos(fullFrustum); + + mWorldToLightProj = GFX->getProjectionMatrix() * toLightSpace; + + //TSShapeInstance::smSmallestVisiblePixelSize = 25.0f; + //F32 savedDetailAdjust = TSShapeInstance::smDetailAdjust; + + for (U32 i = 0; i < mNumSplits; i++) + { + GFXTransformSaver saver; + + // Calculate a sub-frustum + Frustum subFrustum(fullFrustum); + subFrustum.cropNearFar(mSplitDist[i], mSplitDist[i+1]); + + // Calculate our AABB in the light's clip space. + Box3F clipAABB = _calcClipSpaceAABB(subFrustum, lightViewProj, fullFrustum.getFarDist()); + + // Calculate our crop matrix + Point3F scale(2.0f / (clipAABB.maxExtents.x - clipAABB.minExtents.x), + 2.0f / (clipAABB.maxExtents.y - clipAABB.minExtents.y), + 1.0f); + + // TODO: This seems to produce less "pops" of the + // shadow resolution as the camera spins around and + // it should produce pixels that are closer to being + // square. + // + // Still is it the right thing to do? + // + scale.y = scale.x = ( getMin( scale.x, scale.y ) ); + //scale.x = mFloor(scale.x); + //scale.y = mFloor(scale.y); + + Point3F offset(-0.5f * (clipAABB.maxExtents.x + clipAABB.minExtents.x) * scale.x, + -0.5f * (clipAABB.maxExtents.y + clipAABB.minExtents.y) * scale.y, + 0.0f); + + MatrixF cropMatrix(true); + cropMatrix.scale(scale); + cropMatrix.setPosition(offset); + + _roundProjection(lightMatrix, cropMatrix, offset, i); + + cropMatrix.setPosition(offset); + + // Save scale/offset for shader computations + mScaleProj[i].set(scale); + mOffsetProj[i].set(offset); + + // Adjust the far plane to the max z we got (maybe add a little to deal with split overlap) + { + F32 left, right, bottom, top, nearDist, farDist; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearDist, &farDist,&isOrtho); + // BTRTODO: Fix me! + farDist = clipAABB.maxExtents.z; + if (!isOrtho) + GFX->setFrustum(left, right, bottom, top, nearDist, farDist); + + else + { + // Calculate a new far plane, add a fudge factor to avoid bringing + // the far plane in too close. + F32 newFar = pfar * clipAABB.maxExtents.z + 1.0f; + mFarPlaneScalePSSM[i] = (pfar - pnear) / (newFar - pnear); + GFX->setOrtho(left, right, bottom, top, pnear, newFar, true); + } + } + + // Crop matrix multiply needs to be post-projection. + MatrixF alightProj = GFX->getProjectionMatrix(); + alightProj = cropMatrix * alightProj; + + // Set our new projection + GFX->setProjectionMatrix(alightProj); + + // Render into the quad of the shadow map we are using. + GFX->setViewport(mViewports[i]); + + // Setup the scene state and use the diffuse state + // camera position and screen metrics values so that + // lod is done the same as in the diffuse pass. + SceneState *baseState = sceneManager->createBaseState( SPT_Shadow ); + baseState->mRenderNonLightmappedMeshes = true; + baseState->mRenderLightmappedMeshes = bUseLightmappedGeometry; + + /* + if ( i != 0 ) + { + // The idea here is to set a frustum optimal for culling + // out anything that isn't in this split and stuff behind + // the camera. + // + // So far it seems to not work well... things get cut out + // from a split when they shouldn't. + + const Frustum &diffuseFrust = diffuseState->getFrustum(); + MatrixF camXfm( diffuseFrust.getTransform() ); + camXfm.setPosition( camXfm.getPosition() - camXfm.getForwardVector() * 5.0f ); + + Frustum cullFrustum; + cullFrustum.set( diffuseFrust.isOrtho(), + diffuseFrust.getNearLeft(), + diffuseFrust.getNearRight(), + diffuseFrust.getNearTop(), + diffuseFrust.getNearBottom(), + diffuseFrust.getNearDist(), + mSplitDist[i+1] + 5.0f, + camXfm ); + + baseState->setFrustum( cullFrustum ); + } + */ + + baseState->setDiffuseCameraTransform( diffuseState->getCameraTransform() ); + baseState->setViewportExtent( diffuseState->getViewportExtent() ); + baseState->setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); + + U32 objectMask; + if ( i == mNumSplits-1 && params->lastSplitTerrainOnly ) + objectMask = TerrainObjectType; + else + objectMask = ShadowCasterObjectType | StaticRenderedObjectType | ShapeBaseObjectType; + + sceneManager->renderScene( baseState, objectMask ); + //TSShapeInstance::smDetailAdjust *= 0.75f; + + delete baseState; + } + + //TSShapeInstance::smSmallestVisiblePixelSize = -1; + //TSShapeInstance::smDetailAdjust = savedDetailAdjust; + + // Release our render target + mTarget->resolve(); + GFX->popActiveRenderTarget(); + + // Cleanup (restore frustum, clear render instance, kill scene state) + if (!isOrtho) + GFX->setFrustum(left, right, bottom, top, nearPlane, farPlane); + else + GFX->setOrtho(left, right, bottom, top, nearPlane, farPlane); +} + +void PSSMLightShadowMap::setShaderParameters(GFXShaderConstBuffer* params, LightingShaderConstants* lsc) +{ + if ( lsc->mTapRotationTexSC->isValid() ) + GFX->setTexture( lsc->mTapRotationTexSC->getSamplerRegister(), + SHADOWMGR->getTapRotationTex() ); + + const ShadowMapParams *p = mLight->getExtended(); + + Point2F shadowMapAtlas; + if (mNumSplits < 4) + { + shadowMapAtlas.x = 1.0f / (F32) mNumSplits; + shadowMapAtlas.y = 1.0f; + } + else + shadowMapAtlas.set(0.5f, 0.5f); + + // Split start, split end + Point4F sStart(Point4F::Zero), sEnd(Point4F::Zero), sx(Point4F::Zero), sy(Point4F::Zero); + Point4F ox(Point4F::Zero), oy(Point4F::Zero), aXOff(Point4F::Zero), aYOff(Point4F::Zero); + + for (U32 i = 0; i < mNumSplits; i++) + { + sStart[i] = mSplitDist[i]; + sEnd[i] = mSplitDist[i+1]; + sx[i] = mScaleProj[i].x; + sy[i] = mScaleProj[i].y; + ox[i] = mOffsetProj[i].x; + oy[i] = mOffsetProj[i].y; + } + + if (mNumSplits < 4) + { + // 1xmNumSplits + for (U32 i = 0; i < mNumSplits; i++) + aXOff[i] = (F32)i * shadowMapAtlas.x; + } else { + // 2x2 + for (U32 i = 0; i < mNumSplits; i++) + { + if (i == 1 || i == 3) + aXOff[i] = 0.5f; + if (i > 1) + aYOff[i] = 0.5f; + } + } + + params->set(lsc->mSplitStartSC, sStart); + params->set(lsc->mSplitEndSC, sEnd); + params->set(lsc->mScaleXSC, sx); + params->set(lsc->mScaleYSC, sy); + params->set(lsc->mOffsetXSC, ox); + params->set(lsc->mOffsetYSC, oy); + params->set(lsc->mAtlasXOffsetSC, aXOff); + params->set(lsc->mAtlasYOffsetSC, aYOff); + params->set(lsc->mAtlasScaleSC, shadowMapAtlas); + + Point4F lightParams( mLight->getRange().x, p->overDarkFactor.x, 0.0f, 0.0f ); + params->set( lsc->mLightParamsSC, lightParams ); + + params->set( lsc->mFarPlaneScalePSSM, mFarPlaneScalePSSM); + + Point2F fadeStartLength(p->fadeStartDist, 0.0f); + if (fadeStartLength.x == 0.0f) + { + // By default, lets fade the last half of the last split. + fadeStartLength.x = (mSplitDist[mNumSplits-1] + mSplitDist[mNumSplits]) / 2.0f; + } + fadeStartLength.y = 1.0f / (mSplitDist[mNumSplits] - fadeStartLength.x); + params->set( lsc->mFadeStartLength, fadeStartLength); + + params->set( lsc->mOverDarkFactorPSSM, p->overDarkFactor); + params->set( lsc->mSplitFade, p->splitFadeDistances); + + // The softness is a factor of the texel size. + if ( lsc->mShadowSoftnessConst ) + params->set( lsc->mShadowSoftnessConst, p->shadowSoftness * ( 1.0f / mTexSize ) ); +} diff --git a/lighting/shadowMap/pssmLightShadowMap.h b/lighting/shadowMap/pssmLightShadowMap.h new file mode 100644 index 0000000..73f62a1 --- /dev/null +++ b/lighting/shadowMap/pssmLightShadowMap.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _PSSMLIGHTSHADOWMAP_H_ +#define _PSSMLIGHTSHADOWMAP_H_ + +#ifndef _LIGHTSHADOWMAP_H_ +#include "lighting/shadowMap/lightShadowMap.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif + + +class PSSMLightShadowMap : public LightShadowMap +{ + typedef LightShadowMap Parent; +public: + PSSMLightShadowMap( LightInfo *light ); + + // LightShadowMap + virtual ShadowType getShadowType() const { return ShadowType_PSSM; } + virtual void _render( SceneGraph *sceneManager, const SceneState *diffuseState ); + virtual void setShaderParameters(GFXShaderConstBuffer* params, LightingShaderConstants* lsc); + +protected: + + void _setNumSplits( U32 numSplits, U32 texSize ); + void _calcSplitPos(const Frustum& currFrustum); + Box3F _calcClipSpaceAABB(const Frustum& f, const MatrixF& transform, F32 farDist); + void _roundProjection(const MatrixF& lightMat, const MatrixF& cropMatrix, Point3F &offset, U32 splitNum); + + static const int MAX_SPLITS = 4; + U32 mNumSplits; + F32 mSplitDist[MAX_SPLITS+1]; // +1 because we store a cap + RectI mViewports[MAX_SPLITS]; + Point3F mScaleProj[MAX_SPLITS]; + Point3F mOffsetProj[MAX_SPLITS]; + Point4F mFarPlaneScalePSSM; + F32 mLogWeight; +}; + +#endif diff --git a/lighting/shadowMap/shadowCommon.h b/lighting/shadowMap/shadowCommon.h new file mode 100644 index 0000000..de5ad04 --- /dev/null +++ b/lighting/shadowMap/shadowCommon.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOW_COMMON_H_ +#define _SHADOW_COMMON_H_ + +/// +enum ShadowType +{ + ShadowType_None = -1, + + ShadowType_Spot, + ShadowType_PSSM, + + ShadowType_Paraboloid, + ShadowType_DualParaboloidSinglePass, + ShadowType_DualParaboloid, + ShadowType_CubeMap, + + ShadowType_Count, +}; + + +/// The different shadow filter modes used when rendering +/// shadowed lights. +/// @see setShadowFilterMode +enum ShadowFilterMode +{ + ShadowFilterMode_None, + ShadowFilterMode_SoftShadow, + ShadowFilterMode_SoftShadowHighQuality +}; + +#endif // _SHADOW_COMMON_H_ diff --git a/lighting/shadowMap/shadowMapManager.cpp b/lighting/shadowMap/shadowMapManager.cpp new file mode 100644 index 0000000..5c7db85 --- /dev/null +++ b/lighting/shadowMap/shadowMapManager.cpp @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/shadowMapManager.h" + +#include "lighting/shadowMap/blurTexture.h" +#include "lighting/shadowMap/shadowMapPass.h" +#include "lighting/shadowMap/lightShadowMap.h" +#include "materials/materialManager.h" +#include "lighting/lightManager.h" +#include "core/util/safeDelete.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxTextureManager.h" + + +ShadowMapManager::ShadowMapManager() +: mShadowMapPass(NULL), + mCurrentShadowMap(NULL), + mIsActive(false) +{ +} + +ShadowMapManager::~ShadowMapManager() +{ +} + +void ShadowMapManager::setLightShadowMapForLight( LightInfo *light ) +{ + ShadowMapParams *params = light->getExtended(); + if ( params ) + mCurrentShadowMap = params->getShadowMap(); + else + mCurrentShadowMap = NULL; +} + +void ShadowMapManager::activate() +{ + ShadowManager::activate(); + + if (!getSceneManager()) + { + Con::errorf("This world has no scene manager! Shadow manager not activating!"); + return; + } + + LightManager *activeLM = getSceneManager()->getLightManager(); + + mShadowMapPass = new ShadowMapPass(activeLM, this); + + getSceneManager()->getPreRenderSignal().notify( this, &ShadowMapManager::_onPreRender, 0.01f ); + + mIsActive = true; +} + +void ShadowMapManager::deactivate() +{ + getSceneManager()->getPreRenderSignal().remove( this, &ShadowMapManager::_onPreRender ); + + SAFE_DELETE(mShadowMapPass); + mTapRotationTex = NULL; + + // Clean up our shadow texture memory. + LightShadowMap::releaseAllTextures(); + TEXMGR->cleanupPool(); + + mIsActive = false; + + ShadowManager::deactivate(); +} + +void ShadowMapManager::_onPreRender( SceneGraph *sg, const SceneState *state ) +{ + if ( mShadowMapPass && state->isDiffusePass() ) + mShadowMapPass->render( sg, state, (U32)-1 ); +} + +GFXTextureObject* ShadowMapManager::getTapRotationTex() +{ + if ( mTapRotationTex.isValid() ) + return mTapRotationTex; + + mTapRotationTex.set( 64, 64, GFXFormatR8G8B8A8, &GFXDefaultPersistentProfile, + "ShadowMapManager::getTapRotationTex" ); + + GFXLockedRect *rect = mTapRotationTex.lock(); + U8 *f = rect->bits; + F32 angle; + for( U32 i = 0; i < 64*64; i++, f += 4 ) + { + // We only pack the rotations into the red + // and green channels... the rest are empty. + angle = M_2PI_F * gRandGen.randF(); + f[0] = U8_MAX * ( ( 1.0f + mSin( angle ) ) * 0.5f ); + f[1] = U8_MAX * ( ( 1.0f + mCos( angle ) ) * 0.5f ); + f[2] = 0; + f[3] = 0; + } + + mTapRotationTex.unlock(); + + return mTapRotationTex; +} \ No newline at end of file diff --git a/lighting/shadowMap/shadowMapManager.h b/lighting/shadowMap/shadowMapManager.h new file mode 100644 index 0000000..87193ee --- /dev/null +++ b/lighting/shadowMap/shadowMapManager.h @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOWMAPMANAGER_H_ +#define _SHADOWMAPMANAGER_H_ + +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif +#ifndef _SHADOWMANAGER_H_ +#include "lighting/shadowManager.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _MPOINT4_H_ +#include "math/mPoint4.h" +#endif + +class LightShadowMap; +class ShadowMapPass; +class LightInfo; + +class SceneGraph; +class SceneState; + + +class ShadowMapManager : public ShadowManager +{ + typedef ShadowManager Parent; + + friend class ShadowMapPass; + +public: + + ShadowMapManager(); + virtual ~ShadowMapManager(); + + /// Sets the current shadowmap (used in setLightInfo/setTextureStage calls) + void setLightShadowMap( LightShadowMap *lm ) { mCurrentShadowMap = lm; } + + /// Looks up the shadow map for the light then sets it. + void setLightShadowMapForLight( LightInfo *light ); + + /// Return the current shadow map + LightShadowMap* getCurrentShadowMap() const { return mCurrentShadowMap; } + + ShadowMapPass* getShadowMapPass() const { return mShadowMapPass; } + + // Shadow manager + virtual void activate(); + virtual void deactivate(); + + GFXTextureObject* getTapRotationTex(); + +protected: + + void _onPreRender( SceneGraph *sg, const SceneState* state ); + + ShadowMapPass *mShadowMapPass; + LightShadowMap *mCurrentShadowMap; + + /// + GFXTexHandle mTapRotationTex; + + bool mIsActive; +}; + + +/// Returns the ShadowMapManager singleton. +#define SHADOWMGR Singleton::instance() + +#endif // _SHADOWMAPMANAGER_H_ \ No newline at end of file diff --git a/lighting/shadowMap/shadowMapPass.cpp b/lighting/shadowMap/shadowMapPass.cpp new file mode 100644 index 0000000..d7db7f4 --- /dev/null +++ b/lighting/shadowMap/shadowMapPass.cpp @@ -0,0 +1,242 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/shadowMapPass.h" + +#include "lighting/shadowMap/lightShadowMap.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "lighting/shadowMap/shadowMatHook.h" +#include "lighting/lightManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "renderInstance/renderPassManager.h" +#include "renderInstance/renderObjectMgr.h" +#include "renderInstance/renderMeshMgr.h" +#include "renderInstance/renderTerrainMgr.h" +#include "core/util/safeDelete.h" +#include "console/consoleTypes.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "platform/platformTimer.h" + + +const String ShadowMapPass::PassTypeName("ShadowMap"); + +U32 ShadowMapPass::smActiveShadowMaps = 0; +U32 ShadowMapPass::smUpdatedShadowMaps = 0; +U32 ShadowMapPass::smShadowMapsDrawCalls = 0; +U32 ShadowMapPass::smShadowMapPolyCount = 0; +U32 ShadowMapPass::smRenderTargetChanges = 0; +U32 ShadowMapPass::smShadowPoolTexturesCount = 0.; +F32 ShadowMapPass::smShadowPoolMemory = 0.0f; + +bool ShadowMapPass::smDisableShadows = false; + +/// We have a default 8ms render budget for shadow rendering. +U32 ShadowMapPass::smRenderBudgetMs = 8; + +ShadowMapPass::ShadowMapPass(LightManager* lightManager, ShadowMapManager* shadowManager) +{ + mLightManager = lightManager; + mShadowManager = shadowManager; + mShadowRPM = new ShadowRenderPassManager(); + mShadowRPM->assignName( "ShadowRenderPassManager" ); + mShadowRPM->registerObject(); + Sim::getRootGroup()->addObject( mShadowRPM ); + + // Setup our render pass manager + + RenderBinManager *renderBin = new RenderMeshMgr(RenderPassManager::RIT_Mesh, 0.3f, 0.3f); + renderBin->getMatOverrideDelegate().bind( this, &ShadowMapPass::_overrideDelegate ); + mShadowRPM->addManager( renderBin ); + + renderBin = new RenderMeshMgr( RenderPassManager::RIT_Interior, 0.4f, 0.4f ); + renderBin->getMatOverrideDelegate().bind( this, &ShadowMapPass::_overrideDelegate ); + mShadowRPM->addManager( renderBin ); + + //mRenderObjMgr = new RenderObjectMgr(); + //mRenderObjMgr->getMatOverrideDelegate().bind( this, &ShadowMapPass::_overrideDelegate ); + //mShadowRPM->addManager(mRenderObjMgr); + + renderBin = new RenderTerrainMgr(0.5f, 0.5f); + renderBin->getMatOverrideDelegate().bind( this, &ShadowMapPass::_overrideDelegate ); + mShadowRPM->addManager( renderBin ); + + mActiveLights = 0; + + mTimer = PlatformTimer::create(); + + Con::addVariable( "$ShadowStats::activeMaps", TypeS32, &smActiveShadowMaps ); + Con::addVariable( "$ShadowStats::updatedMaps", TypeS32, &smUpdatedShadowMaps ); + Con::addVariable( "$ShadowStats::drawCalls", TypeS32, &smShadowMapsDrawCalls ); + Con::addVariable( "$ShadowStats::polyCount", TypeS32, &smShadowMapPolyCount ); + Con::addVariable( "$ShadowStats::rtChanges", TypeS32, &smRenderTargetChanges ); + Con::addVariable( "$ShadowStats::poolTexCount", TypeS32, &smShadowPoolTexturesCount ); + Con::addVariable( "$ShadowStats::poolTexMemory", TypeF32, &smShadowPoolMemory ); + + Con::addVariable( "$ShadowMap::disableShadows", TypeBool, &smDisableShadows ); +} + +ShadowMapPass::~ShadowMapPass() +{ + SAFE_DELETE( mTimer ); + + if ( mShadowRPM ) + mShadowRPM->deleteObject(); +} + +void ShadowMapPass::render( SceneGraph *sceneManager, + const SceneState *diffuseState, + U32 objectMask ) +{ + PROFILE_SCOPE( ShadowMapPass_Render ); + + // Prep some shadow rendering stats. + smActiveShadowMaps = 0; + smUpdatedShadowMaps = 0; + GFXDeviceStatistics stats; + stats.start( GFX->getDeviceStatistics() ); + + // NOTE: The lights were already registered by SceneGraph. + + // Update mLights + mLights.clear(); + mLightManager->getAllUnsortedLights( mLights ); + mActiveLights = mLights.size(); + + // Start tracking time here so we can stop + // when we've gone over the shadow update + // frame budget. + const U32 currTime = Platform::getRealMilliseconds(); + + // First do a loop thru the lights setting up the shadow + // info array for this pass. + Vector shadowMaps; + shadowMaps.reserve( mActiveLights ); + for ( U32 i = 0; i < mActiveLights; i++ ) + { + ShadowMapParams *params = mLights[i]->getExtended(); + + // Before we do anything... skip lights without shadows. + if ( !mLights[i]->getCastShadows() || smDisableShadows ) + continue; + + LightShadowMap *lsm = params->getOrCreateShadowMap(); + + // First check the visiblity query... if it wasn't + // visible skip it. + if ( lsm->wasOccluded() ) + continue; + + // Any shadow that is visible is counted as being + // active regardless if we update it or not. + ++smActiveShadowMaps; + + lsm->updatePriority( diffuseState, currTime ); + + // Do lod... but only on view independent shadows. + if ( !lsm->isViewDependent() ) + { + F32 lodScale = lsm->getLastScreenSize() / 600.0f; + if ( lodScale < 0.25f ) + continue; + + U32 msDelta = mClampU( currTime - lsm->getLastUpdate(), 1, U32_MAX ); + if ( ( msDelta * mPow( lodScale, 2.0f ) ) < 2 ) + continue; + } + + shadowMaps.push_back( lsm ); + } + + // Now sort the shadow info by priority. + shadowMaps.sort( LightShadowMap::cmpPriority ); + + GFXDEBUGEVENT_SCOPE( ShadowMapPass_Render, ColorI::RED ); + + // Ok, let's render out the shadow maps. + sceneManager->pushRenderPass( mShadowRPM ); + + // Use a timer for tracking our shadow rendering + // budget to ensure a high precision results. + mTimer->getElapsedMs(); + mTimer->reset(); + + for ( U32 i = 0; i < shadowMaps.size(); i++ ) + { + LightShadowMap *lsm = shadowMaps[i]; + + // For material override delegate below. + mActiveShadowType = lsm->getShadowType(); + + { + GFXDEBUGEVENT_SCOPE( ShadowMapPass_Render_Shadow, ColorI::RED ); + + mShadowManager->setLightShadowMap( lsm ); + lsm->render( sceneManager, diffuseState ); + ++smUpdatedShadowMaps; + } + + // See if we're over our frame budget for shadow + // updates... give up completely in that case. + if ( mTimer->getElapsedMs() > smRenderBudgetMs ) + break; + } + + // Cleanup old unused textures. + LightShadowMap::releaseUnusedTextures(); + + // Update the stats. + stats.end( GFX->getDeviceStatistics() ); + smShadowMapsDrawCalls = stats.mDrawCalls; + smShadowMapPolyCount = stats.mPolyCount; + smRenderTargetChanges = stats.mRenderTargetChanges; + smShadowPoolTexturesCount = ShadowMapProfile.getStats().activeCount; + smShadowPoolMemory = ( ShadowMapProfile.getStats().activeBytes / 1024.0f ) / 1024.0f; + + // The NULL here is importaint as having it around + // will cause extra work in AdvancedLightManager::setLightInfo()/ + mShadowManager->setLightShadowMap( NULL ); + + sceneManager->popRenderPass(); +} + +BaseMatInstance* ShadowMapPass::_overrideDelegate( BaseMatInstance *inMat ) +{ + // See if we have an existing material hook. + ShadowMaterialHook *hook = static_cast( inMat->getHook( ShadowMaterialHook::Type ) ); + if ( !hook ) + { + // Create a hook and initialize it using the incoming material. + hook = new ShadowMaterialHook; + hook->init( inMat ); + inMat->addHook( hook ); + } + + return hook->getShadowMat( mActiveShadowType ); +} + +void ShadowRenderPassManager::addInst( RenderInst *inst ) +{ + if(inst->type == RIT_Mesh || inst->type == RIT_Interior ) + { + MeshRenderInst *meshRI = static_cast(inst); + if(meshRI != NULL && meshRI->matInst) + { + // TODO: Should castsShadows() override isTranslucent() ? + // This would mess some things up I think. + if(!meshRI->matInst->getMaterial()->castsShadows() || + meshRI->matInst->getMaterial()->isTranslucent()) + { + // Do not add this instance, return here and avoid the default behavior + // of calling up to Parent::addInst() + return; + } + } + } + + Parent::addInst(inst); +} \ No newline at end of file diff --git a/lighting/shadowMap/shadowMapPass.h b/lighting/shadowMap/shadowMapPass.h new file mode 100644 index 0000000..8dd3715 --- /dev/null +++ b/lighting/shadowMap/shadowMapPass.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOWMAPPASS_H_ +#define _SHADOWMAPPASS_H_ + +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _RENDERMESHMGR_H_ +#include "renderInstance/renderMeshMgr.h" +#endif +#ifndef _LIGHTINFO_H_ +#include "lighting/lightInfo.h" +#endif +#ifndef _SHADOW_COMMON_H_ +#include "lighting/shadowMap/shadowCommon.h" +#endif + +class RenderMeshMgr; +class LightShadowMap; +class LightManager; +class ShadowMapManager; +class BaseMatInstance; +class RenderObjectMgr; +class RenderTerrainMgr; +class PlatformTimer; +class ShadowRenderPassManager; + +/// ShadowMapPass, this is plugged into the SceneManager to generate +/// ShadowMaps for the scene. +class ShadowMapPass +{ +public: + + ShadowMapPass() {} // Only called by ConsoleSystem + ShadowMapPass(LightManager* LightManager, ShadowMapManager* ShadowManager); + virtual ~ShadowMapPass(); + + // + // SceneRenderPass interface + // + + /// Called to render a scene. + void render( SceneGraph *sceneGraph, + const SceneState *diffuseState, + U32 objectMask ); + + /// Return the type of pass this is + virtual const String& getPassType() const { return PassTypeName; }; + + /// Return our sort value. (Go first in order to have shadow maps available for RIT_Objects) + virtual F32 getSortValue() const { return 0.0f; } + + virtual bool geometryOnly() const { return true; } + + static const String PassTypeName; + + + /// Used to for debugging performance by disabling + /// shadow updates and rendering. + static bool smDisableShadows; + +private: + + static U32 smActiveShadowMaps; + static U32 smUpdatedShadowMaps; + static U32 smShadowMapsDrawCalls; + static U32 smShadowMapPolyCount; + static U32 smRenderTargetChanges; + static U32 smShadowPoolTexturesCount; + static F32 smShadowPoolMemory; + + /// The milliseconds alotted for shadow map updates + /// on a per frame basis. + static U32 smRenderBudgetMs; + + BaseMatInstance* _overrideDelegate( BaseMatInstance *inMat ); + + ShadowType mActiveShadowType; + + PlatformTimer *mTimer; + + LightInfoList mLights; + U32 mActiveLights; + SimObjectPtr mShadowRPM; + LightManager* mLightManager; + ShadowMapManager* mShadowManager; +}; + +class ShadowRenderPassManager : public RenderPassManager +{ + typedef RenderPassManager Parent; +public: + ShadowRenderPassManager() : Parent() {} + + /// Add a RenderInstance to the list + virtual void addInst( RenderInst *inst ); +}; + +#endif // _SHADOWMAPPASS_H_ diff --git a/lighting/shadowMap/shadowMatHook.cpp b/lighting/shadowMap/shadowMatHook.cpp new file mode 100644 index 0000000..20d0f48 --- /dev/null +++ b/lighting/shadowMap/shadowMatHook.cpp @@ -0,0 +1,174 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/shadowMatHook.h" + +#include "materials/materialManager.h" +#include "materials/customMaterialDefinition.h" +#include "materials/materialFeatureTypes.h" + +#include "sceneGraph/sceneState.h" + +const MatInstanceHookType ShadowMaterialHook::Type( "ShadowMap" ); + +ShadowMaterialHook::ShadowMaterialHook() +{ + dMemset( mShadowMat, 0, sizeof( mShadowMat ) ); +} + +ShadowMaterialHook::~ShadowMaterialHook() +{ + for ( U32 i = 0; i < ShadowType_Count; i++ ) + SAFE_DELETE( mShadowMat[i] ); +} + +void ShadowMaterialHook::init( BaseMatInstance *inMat ) +{ + // Tweak the feature data to include just what we need. + FeatureSet features; + features.addFeature( MFT_VertTransform ); + features.addFeature( MFT_DiffuseMap ); + features.addFeature( MFT_TexAnim ); + features.addFeature( MFT_AlphaTest ); + + Material *shadowMat = (Material*)inMat->getMaterial(); + if ( dynamic_cast( shadowMat ) ) + { + // This is a custom material... who knows what it really does, but + // if it wasn't already filtered out of the shadow render then just + // give it some default depth out material. + shadowMat = MATMGR->getMaterialDefinitionByName( "AL_DefaultShadowMaterial" ); + } + + // By default we want to disable some states + // that the material might enable for us. + GFXStateBlockDesc forced; + forced.setBlend( false ); + forced.setAlphaTest( false ); + + // We should force on zwrite as the prepass + // will disable it by default. + forced.setZReadWrite( true, true ); + + // TODO: Should we render backfaces for + // shadows or does the ESM take care of + // all our acne issues? + //forced.setCullMode( GFXCullCW ); + + // Vector, and spotlights use the same shadow material. + BaseMatInstance *newMat = new ShadowMatInstance( shadowMat ); + newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures ); + newMat->addStateBlockDesc( forced ); + newMat->init( features, inMat->getVertexFormat() ); + mShadowMat[ShadowType_Spot] = newMat; + + newMat = new ShadowMatInstance( shadowMat ); + newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures ); + forced.setCullMode( GFXCullCW ); + newMat->addStateBlockDesc( forced ); + forced.cullDefined = false; + newMat->addShaderMacro( "CUBE_SHADOW_MAP", "" ); + newMat->init( features, inMat->getVertexFormat() ); + mShadowMat[ShadowType_CubeMap] = newMat; + + // A dual paraboloid shadow rendered in a single draw call. + features.addFeature( MFT_ParaboloidVertTransform ); + features.addFeature( MFT_IsSinglePassParaboloid ); + features.removeFeature( MFT_VertTransform ); + newMat = new ShadowMatInstance( shadowMat ); + GFXStateBlockDesc noCull( forced ); + noCull.setCullMode( GFXCullNone ); + newMat->addStateBlockDesc( noCull ); + newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures ); + newMat->init( features, inMat->getVertexFormat() ); + mShadowMat[ShadowType_DualParaboloidSinglePass] = newMat; + + // Regular dual paraboloid shadow. + features.addFeature( MFT_ParaboloidVertTransform ); + features.removeFeature( MFT_IsSinglePassParaboloid ); + features.removeFeature( MFT_VertTransform ); + newMat = new ShadowMatInstance( shadowMat ); + newMat->addStateBlockDesc( forced ); + newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures ); + newMat->init( features, inMat->getVertexFormat() ); + mShadowMat[ShadowType_DualParaboloid] = newMat; + + /* + // A single paraboloid shadow. + newMat = new ShadowMatInstance( startMatInstance ); + GFXStateBlockDesc noCull; + noCull.setCullMode( GFXCullNone ); + newMat->addStateBlockDesc( noCull ); + newMat->getFeaturesDelegate().bind( &ShadowMaterialHook::_overrideFeatures ); + newMat->init( features, globalFeatures, inMat->getVertexFormat() ); + mShadowMat[ShadowType_DualParaboloidSinglePass] = newMat; + */ +} + +BaseMatInstance* ShadowMaterialHook::getShadowMat( ShadowType type ) const +{ + AssertFatal( type < ShadowType_Count, "ShadowMaterialHook::getShadowMat() - Bad light type!" ); + + // The cubemap and pssm shadows use the same + // spotlight material for shadows. + if ( type == ShadowType_Spot || + type == ShadowType_PSSM ) + return mShadowMat[ShadowType_Spot]; + + // Get the specialized shadow material. + return mShadowMat[type]; +} + +void ShadowMaterialHook::_overrideFeatures( ProcessedMaterial *mat, + U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ) +{ + if ( stageNum != 0 ) + { + fd.features.clear(); + return; + } + + // Disable the base texture if we don't + // have alpha test enabled. + if ( !fd.features[ MFT_AlphaTest ] ) + { + fd.features.removeFeature( MFT_TexAnim ); + fd.features.removeFeature( MFT_DiffuseMap ); + } + + // HACK: Need to figure out how to enable these + // suckers without this override call! + + fd.features.setFeature( MFT_ParaboloidVertTransform, + features.hasFeature( MFT_ParaboloidVertTransform ) ); + fd.features.setFeature( MFT_IsSinglePassParaboloid, + features.hasFeature( MFT_IsSinglePassParaboloid ) ); + + // The paraboloid transform outputs linear depth, so + // it needs to use the plain depth out feature. + if ( fd.features.hasFeature( MFT_ParaboloidVertTransform ) ) + fd.features.addFeature( MFT_DepthOut ); + else + fd.features.addFeature( MFT_EyeSpaceDepthOut ); +} + +ShadowMatInstance::ShadowMatInstance( Material *mat ) + : MatInstance( *mat ) +{ + mLightmappedMaterial = mMaterial->isLightmapped(); +} + +bool ShadowMatInstance::setupPass( SceneState *state, const SceneGraphData &sgData ) +{ + // Respect SceneState render flags + if( (mLightmappedMaterial && !state->mRenderLightmappedMeshes) || + (!mLightmappedMaterial && !state->mRenderNonLightmappedMeshes) ) + return false; + + return Parent::setupPass(state, sgData); +} diff --git a/lighting/shadowMap/shadowMatHook.h b/lighting/shadowMap/shadowMatHook.h new file mode 100644 index 0000000..594f1bf --- /dev/null +++ b/lighting/shadowMap/shadowMatHook.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADOWMATHOOK_H_ +#define _SHADOWMATHOOK_H_ + +#ifndef _MATINSTANCEHOOK_H_ +#include "materials/matInstanceHook.h" +#endif +#ifndef _MATINSTANCE_H_ +#include "materials/matInstance.h" +#endif + +// TODO: Move ShadowType enum to somewhere +// with less dependancies. +#ifndef _SHADOWMAPPASS_H_ +#include "lighting/shadowMap/shadowMapPass.h" +#endif + +class ShadowMatInstance : public MatInstance +{ + typedef MatInstance Parent; + + bool mLightmappedMaterial; +public: + ShadowMatInstance( Material *mat ); + virtual ~ShadowMatInstance() {} + + virtual bool setupPass( SceneState *state, const SceneGraphData &sgData ); +}; + +class ShadowMaterialHook : public MatInstanceHook +{ +public: + + ShadowMaterialHook(); + + // MatInstanceHook + virtual ~ShadowMaterialHook(); + virtual const MatInstanceHookType& getType() const { return Type; } + + /// The material hook type. + static const MatInstanceHookType Type; + + BaseMatInstance* getShadowMat( ShadowType type ) const; + + void init( BaseMatInstance *mat ); + +protected: + + static void _overrideFeatures( ProcessedMaterial *mat, + U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ); + + /// + BaseMatInstance* mShadowMat[ShadowType_Count]; + + +}; + +#endif // _SHADOWMATHOOK_H_ diff --git a/lighting/shadowMap/singleLightShadowMap.cpp b/lighting/shadowMap/singleLightShadowMap.cpp new file mode 100644 index 0000000..41e8e65 --- /dev/null +++ b/lighting/shadowMap/singleLightShadowMap.cpp @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "lighting/shadowMap/singleLightShadowMap.h" +#include "lighting/shadowMap/shadowMapManager.h" +#include "lighting/common/lightMapParams.h" +#include "console/console.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +//#include "scene/sceneReflectPass.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxTransformSaver.h" +#include "renderInstance/renderPassManager.h" + +SingleLightShadowMap::SingleLightShadowMap( LightInfo *light ) + : LightShadowMap( light ) +{ +} + +SingleLightShadowMap::~SingleLightShadowMap() +{ + releaseTextures(); +} + +void SingleLightShadowMap::_render( SceneGraph *sceneManager, + const SceneState *diffuseState ) +{ + PROFILE_SCOPE(SingleLightShadowMap_render); + + const ShadowMapParams *params = mLight->getExtended(); + const LightMapParams *lmParams = mLight->getExtended(); + const bool bUseLightmappedGeometry = lmParams ? !lmParams->representedInLightmap || lmParams->includeLightmappedGeometryInShadow : true; + + if ( mShadowMapTex.isNull() || + mTexSize != params->texSize ) + { + mTexSize = params->texSize; + + mShadowMapTex.set( mTexSize, mTexSize, + ShadowMapFormat, &ShadowMapProfile, + "SingleLightShadowMap" ); + } + + GFXTransformSaver saver; + F32 left, right, bottom, top, nearPlane, farPlane; + bool isOrtho; + GFX->getFrustum(&left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho); + + MatrixF lightMatrix; + calcLightMatrices(lightMatrix); + lightMatrix.inverse(); + GFX->setWorldMatrix(lightMatrix); + + const MatrixF& lightProj = GFX->getProjectionMatrix(); + mWorldToLightProj = lightProj * lightMatrix; + + // Render the shadowmap! + GFX->pushActiveRenderTarget(); + mTarget->attachTexture( GFXTextureTarget::Color0, mShadowMapTex ); + mTarget->attachTexture( GFXTextureTarget::DepthStencil, + _getDepthTarget( mShadowMapTex->getWidth(), mShadowMapTex->getHeight() ) ); + GFX->setActiveRenderTarget(mTarget); + GFX->clear(GFXClearStencil | GFXClearZBuffer | GFXClearTarget, ColorI(255,255,255), 1.0f, 0); + + SceneState* baseState = sceneManager->createBaseState( SPT_Shadow ); + baseState->mRenderNonLightmappedMeshes = true; + baseState->mRenderLightmappedMeshes = bUseLightmappedGeometry; + baseState->setDiffuseCameraTransform( diffuseState->getCameraTransform() ); + baseState->setViewportExtent( diffuseState->getViewportExtent() ); + baseState->setWorldToScreenScale( diffuseState->getWorldToScreenScale() ); + + sceneManager->renderScene(baseState); + + delete baseState; + + mTarget->resolve(); + GFX->popActiveRenderTarget(); + + // Restore frustum + if (!isOrtho) + GFX->setFrustum(left, right, bottom, top, nearPlane, farPlane); + else + GFX->setOrtho(left, right, bottom, top, nearPlane, farPlane); + + // showmap(mShadowMapTex); +} + +void SingleLightShadowMap::setShaderParameters(GFXShaderConstBuffer* params, LightingShaderConstants* lsc) +{ + if ( lsc->mTapRotationTexSC->isValid() ) + GFX->setTexture( lsc->mTapRotationTexSC->getSamplerRegister(), + SHADOWMGR->getTapRotationTex() ); + + ShadowMapParams *p = mLight->getExtended(); + + if ( lsc->mLightParamsSC->isValid() ) + { + Point4F lightParams( mLight->getRange().x, + p->overDarkFactor.x, + 0.0f, + 0.0f ); + params->set(lsc->mLightParamsSC, lightParams); + } + + // The softness is a factor of the texel size. + if ( lsc->mShadowSoftnessConst->isValid() ) + params->set( lsc->mShadowSoftnessConst, p->shadowSoftness * ( 1.0f / mTexSize ) ); +} diff --git a/lighting/shadowMap/singleLightShadowMap.h b/lighting/shadowMap/singleLightShadowMap.h new file mode 100644 index 0000000..825f87f --- /dev/null +++ b/lighting/shadowMap/singleLightShadowMap.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SINGLELIGHTSHADOWMAP_H_ +#define _SINGLELIGHTSHADOWMAP_H_ + +#ifndef _LIGHTSHADOWMAP_H_ +#include "lighting/shadowMap/lightShadowMap.h" +#endif + +// +// SingleLightShadowMap, holds the shadow map and various other things for a light. +// +// This represents everything we need to render the shadowmap for one light. +class SingleLightShadowMap : public LightShadowMap +{ +public: + SingleLightShadowMap( LightInfo *light ); + ~SingleLightShadowMap(); + + // LightShadowMap + virtual ShadowType getShadowType() const { return ShadowType_Spot; } + virtual void _render( SceneGraph *sceneManager, const SceneState *diffuseState ); + virtual void setShaderParameters(GFXShaderConstBuffer* params, LightingShaderConstants* lsc); +}; + + +#endif // _SINGLELIGHTSHADOWMAP_H_ \ No newline at end of file diff --git a/main/main.cpp b/main/main.cpp new file mode 100644 index 0000000..7b74343 --- /dev/null +++ b/main/main.cpp @@ -0,0 +1,292 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifdef TORQUE_SHARED + +#ifdef WIN32 + +#include +#include + +#include +#pragma comment (lib,"DBGCrash.lib") + +extern "C" +{ + int (*torque_winmain)( HINSTANCE hInstance, HINSTANCE h, LPSTR lpszCmdLine, int nShow) = NULL; +}; + +int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCommandShow) +{ + char filename[4096]; + char gameLib[4096]; + + GetModuleFileNameA(NULL, filename, 4096); + filename[strlen(filename)-4] = 0; + sprintf(gameLib, "%s.dll", filename); + + HMODULE hGame = LoadLibraryA(gameLib); + + if (hGame) + torque_winmain = (int (*)(HINSTANCE hInstance, HINSTANCE h, LPSTR lpszCmdLine, int nShow))GetProcAddress(hGame, "torque_winmain"); + + char error[4096]; + if (!hGame) + { + sprintf(error, "Unable to load game library: %s. Please make sure it exists and the latest DirectX is installed.", gameLib); + MessageBoxA(NULL, error, "Error", MB_OK|MB_ICONWARNING); + return -1; + } + + if (!torque_winmain) + { + sprintf(error, "Missing torque_winmain export in game library: %s. Please make sure that it exists and the latest DirectX is installed.", gameLib); + MessageBoxA(NULL, error, "Error", MB_OK|MB_ICONWARNING); + return -1; + } + + int ret = -1; + __try + { + ret = torque_winmain(hInstance, hPrevInstance, lpszCmdLine, nCommandShow ); + } + __except(RecordExceptionInfo(GetExceptionInformation(), "main thread")) + { + } + + FreeLibrary(hGame); + + return ret; + +} +#endif // WIN32 + + +#ifdef __MACOSX__ + +#include +#include +#include +#include + + +extern "C" { + + int (*torque_macmain)(int argc, const char **argv) = 0; + +} + +void GetBasePath(const char** cpath, const char** cname) +{ + static char path[2049]; + static char name[2049]; + + ProcessSerialNumber PSN; + ProcessInfoRec pinfo; + FSSpec pspec; + FSRef fsr; + OSStatus err; + + path[0] = 0; + name[0] = 0; + + *cpath = path; + *cname = name; + + // set up process serial number + PSN.highLongOfPSN = 0; + PSN.lowLongOfPSN = kCurrentProcess; + + // set up info block + pinfo.processInfoLength = sizeof(pinfo); + pinfo.processName = NULL; + pinfo.processAppSpec = &pspec; + + // grab the vrefnum and directory + err = GetProcessInformation(&PSN, &pinfo); + if (! err ) { + + FSSpec fss2; + + strcpy(name, &pspec.name[1]); + + err = FSMakeFSSpec(pspec.vRefNum, pspec.parID, 0, &fss2); + + if ( ! err ) { + err = FSpMakeFSRef(&fss2, &fsr); + if ( ! err ) { + err = (OSErr)FSRefMakePath(&fsr, (UInt8*)path, 2048); + } + } + } +} + +int main(int argc, const char **argv) +{ + void *gameBundle = 0; + char gameBundleFilename[2049]; + + const char* basePath; + const char* appName; + + // Get the path to our app binary and the app name + + GetBasePath(&basePath, &appName); + + if (!basePath[0] || !appName[0]) + return; + + char appNameNoDebug[2049]; + + strcpy(appNameNoDebug, appName); + + int i = strlen(appName); + while (i > 0) + { + if (!strcmp(&appName[i], "_DEBUG")) + { + appNameNoDebug[i] = 0; + break; + } + + i--; + } + + sprintf(gameBundleFilename, "%s.app/Contents/Frameworks/%s Bundle.bundle/Contents/MacOS/%s Bundle", appName, appNameNoDebug, appNameNoDebug); + + // first see if the current directory is set properly + gameBundle = dlopen(gameBundleFilename, RTLD_LAZY | RTLD_LOCAL); + + if (!gameBundle) + { + // Couldn't load the game bundle... so, using the path to the bundle binary fix up the cwd + + if (basePath[0]) { + chdir( basePath ); + chdir( "../../../" ); + } + + // and try again + gameBundle = dlopen( gameBundleFilename, RTLD_LAZY | RTLD_LOCAL); + } + + if (!gameBundle) + return -1; + + torque_macmain = (int (*)(int argc, const char **argv)) dlsym(gameBundle, "torque_macmain"); + + if (!torque_macmain) + return -1; + + return torque_macmain(argc, argv); +} + +#endif // __MACOSX + +#ifdef __linux__ + +#include +#include +#include +#include + +extern "C" +{ + int (*torque_unixmain)(int argc, const char **argv) = NULL; + void(*setExePathName)(const char *exePathName) = NULL; +} + +int main(int argc, const char **argv) +{ + // assume bin name is in argv[0] + int len = strlen(argv[0]); + char *libName = new char[len+4]; // len + .so + NUL + + strcpy(libName, argv[0]); + strcat(libName, ".so"); + + // try to load the game lib + void *gameLib = dlopen(libName, RTLD_LAZY | RTLD_LOCAL); + delete libName; + + if(gameLib == NULL) + { + printf("%s\n", dlerror()); + return -1; + } + + // set the filename of the exe image + setExePathName = (void(*)(const char *)) dlsym(gameLib, "setExePathName"); + if(setExePathName == NULL) + { + printf("%s\n", dlerror()); + return -1; + } + setExePathName(argv[0]); + + // try to load the lib entry point + torque_unixmain = (int(*)(int argc, const char **argv)) dlsym(gameLib, "torque_unixmain"); + + if(torque_unixmain == NULL) + { + printf("%s\n", dlerror()); + return -1; + } + + // Go! + return torque_unixmain(argc, argv); +} +#endif // __linux__ + + +#else //static exe build + +#include "platform/platform.h" +#include "app/mainLoop.h" +#include "T3D/gameFunctions.h" + +// Entry point for your game. +// +// This is build by default using the "StandardMainLoop" toolkit. Feel free +// to bring code over directly as you need to modify or extend things. You +// will need to merge against future changes to the SML code if you do this. +S32 TorqueMain(S32 argc, const char **argv) +{ + // Some handy debugging code: + // if (argc == 1) { + // static const char* argvFake[] = { "dtest.exe", "-jload", "test.jrn" }; + // argc = 3; + // argv = argvFake; + // } + + // Memory::enableLogging("testMem.log"); + // Memory::setBreakAlloc(104717); + + // Initialize the subsystems. + StandardMainLoop::init(); + + // Handle any command line args. + if(!StandardMainLoop::handleCommandLine(argc, argv)) + { + Platform::AlertOK("Error", "Failed to initialize game, shutting down."); + + return 1; + } + + // Main loop + while(StandardMainLoop::doMainLoop()); + + // Clean everything up. + StandardMainLoop::shutdown(); + + // Do we need to restart? + if( StandardMainLoop::requiresRestart() ) + Platform::restartInstance(); + + // Return. + return 0; +} + +#endif //TORQUE_SHARED diff --git a/materials/baseMatInstance.cpp b/materials/baseMatInstance.cpp new file mode 100644 index 0000000..0935fc7 --- /dev/null +++ b/materials/baseMatInstance.cpp @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/baseMatInstance.h" + +#include "core/util/safeDelete.h" + + +BaseMatInstance::~BaseMatInstance() +{ + deleteAllHooks(); +} + +void BaseMatInstance::addHook( MatInstanceHook *hook ) +{ + AssertFatal( hook, "BaseMatInstance::addHook() - Got null hook!" ); + + const MatInstanceHookType &type = hook->getType(); + + while ( mHooks.size() <= type ) + mHooks.push_back( NULL ); + + delete mHooks[type]; + mHooks[type] = hook; +} + +MatInstanceHook* BaseMatInstance::getHook( const MatInstanceHookType &type ) const +{ + if ( type >= mHooks.size() ) + return NULL; + + return mHooks[ type ]; +} + +void BaseMatInstance::deleteHook( const MatInstanceHookType &type ) +{ + if ( type >= mHooks.size() ) + return; + + delete mHooks[ type ]; + mHooks[ type ] = NULL; +} + +U32 BaseMatInstance::deleteAllHooks() +{ + U32 deleteCount = 0; + + Vector::iterator iter = mHooks.begin(); + for ( ; iter != mHooks.end(); iter++ ) + { + if ( *iter ) + { + delete (*iter); + (*iter) = NULL; + ++deleteCount; + } + } + + return deleteCount; +} \ No newline at end of file diff --git a/materials/baseMatInstance.h b/materials/baseMatInstance.h new file mode 100644 index 0000000..9663d91 --- /dev/null +++ b/materials/baseMatInstance.h @@ -0,0 +1,198 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _BASEMATINSTANCE_H_ +#define _BASEMATINSTANCE_H_ + +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _BASEMATERIALDEFINITION_H_ +#include "materials/baseMaterialDefinition.h" +#endif +#ifndef _MATERIALPARAMETERS_H_ +#include "materials/materialParameters.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _GFXENUMS_H_ +#include "gfx/gfxEnums.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _MATERIALFEATUREDATA_H_ +#include "materials/materialFeatureData.h" +#endif +#ifndef _MATINSTANCEHOOK_H_ +#include "materials/matInstanceHook.h" +#endif + +struct RenderPassData; +class GFXVertexBufferHandleBase; +class GFXPrimitiveBufferHandle; +struct SceneGraphData; +class SceneState; +struct GFXStateBlockDesc; +class GFXVertexFormat; +class MatrixSet; + +/// +class BaseMatInstance +{ +protected: + + /// The array of active material hooks indexed + /// by a MatInstanceHookType. + Vector mHooks; + + /// + MatFeaturesDelegate mFeaturesDelegate; + + /// + String mMatNameStr; + + /// Should be true if init has been called and it succeeded. + /// It is up to the derived class to set this variable appropriately. + bool mIsValid; + +public: + + virtual ~BaseMatInstance(); + + /// @param features The features you want to allow for this material. + /// + /// @param vertexFormat The vertex format on which this material will be rendered. + /// + /// @see GFXVertexFormat + /// @see FeatureSet + virtual bool init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat ) = 0; + + /// Reinitializes the material using the previous + /// initialization parameters. + /// @see init + virtual bool reInit() = 0; + + /// Returns true if init has been successfully called. + /// It is up to the derived class to set this value properly. + bool isValid() { return mIsValid; } + + /// Adds this stateblock to the base state block + /// used during initialization. + /// @see init + virtual void addStateBlockDesc(const GFXStateBlockDesc& desc) = 0; + + /// Adds a shader macro which will be passed to the shader + /// during initialization. + /// @see init + virtual void addShaderMacro( const String &name, const String &value ) = 0; + + /// Get a MaterialParameters block for this BaseMatInstance, + /// caller is responsible for freeing it. + virtual MaterialParameters* allocMaterialParameters() = 0; + + /// Set the current parameters for this BaseMatInstance + virtual void setMaterialParameters(MaterialParameters* param) = 0; + + /// Get the current parameters for this BaseMatInstance (BaseMatInstances are created with a default active + /// MaterialParameters which is managed by BaseMatInstance. + virtual MaterialParameters* getMaterialParameters() = 0; + + /// Returns a MaterialParameterHandle for name. + virtual MaterialParameterHandle* getMaterialParameterHandle(const String& name) = 0; + + /// Sets up the next rendering pass for this material. It is + /// typically called like so... + /// + ///@code + /// while( mat->setupPass( state, sgData ) ) + /// { + /// mat->setTransforms(...); + /// mat->setSceneInfo(...); + /// ... + /// GFX->drawPrimitive(); + /// } + ///@endcode + /// + virtual bool setupPass( SceneState *state, const SceneGraphData &sgData ) = 0; + + /// This initializes the material transforms and should be + /// called after setupPass() within the pass loop. + /// @see setupPass + virtual void setTransforms( const MatrixSet &matrixSet, SceneState *state ) = 0; + + /// This initializes various material scene state settings and + /// should be called after setupPass() within the pass loop. + /// @see setupPass + virtual void setSceneInfo( SceneState *state, const SceneGraphData &sgData ) = 0; + + /// This is normally called from within setupPass() automatically, so its + /// unnessasary to do so manually unless a texture stage has changed. If + /// so it should be called after setupPass() within the pass loop. + /// @see setupPass + virtual void setTextureStages(SceneState *, const SceneGraphData &sgData ) = 0; + + virtual void setBuffers( GFXVertexBufferHandleBase *vertBuffer, GFXPrimitiveBufferHandle *primBuffer ) = 0; + + /// Returns the material this instance is based on. + virtual BaseMaterialDefinition* getMaterial() = 0; + + // BTRTODO: This stuff below should probably not be in BaseMatInstance + virtual bool hasGlow() = 0; + + virtual U32 getCurPass() = 0; + + virtual U32 getCurStageNum() = 0; + + virtual RenderPassData *getPass(U32 pass) = 0; + + /// Returns the active features in use by this material. + /// @see getRequestedFeatures + virtual const FeatureSet& getFeatures() const = 0; + + /// Returns the features that were requested at material + /// creation time which may differ from the active features. + /// @see getFeatures + virtual const FeatureSet& getRequestedFeatures() const = 0; + + virtual const GFXVertexFormat* getVertexFormat() const = 0; + + virtual void dumpShaderInfo() const = 0; + + /// + MatFeaturesDelegate& getFeaturesDelegate() { return mFeaturesDelegate; } + + /// @name Material Hook functions + /// @{ + + /// + void addHook( MatInstanceHook *hook ); + + /// Helper function for getting a hook. + /// @see getHook + template + inline HOOK* getHook() { return (HOOK*)getHook( HOOK::Type ); } + + /// + MatInstanceHook* getHook( const MatInstanceHookType &type ) const; + + /// + void deleteHook( const MatInstanceHookType &type ); + + /// + U32 deleteAllHooks(); + + /// @} + +}; + +#endif /// _BASEMATINSTANCE_H_ + + + + + + diff --git a/materials/baseMaterialDefinition.h b/materials/baseMaterialDefinition.h new file mode 100644 index 0000000..73d5ea4 --- /dev/null +++ b/materials/baseMaterialDefinition.h @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _BASEMATERIALDEFINITION_H_ +#define _BASEMATERIALDEFINITION_H_ + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + +class BaseMatInstance; + +class BaseMaterialDefinition : public SimObject +{ + typedef SimObject Parent; +public: + virtual BaseMatInstance* createMatInstance() = 0; + virtual bool isIFL() const = 0; + virtual bool isTranslucent() const = 0; + virtual bool isDoubleSided() const = 0; + virtual bool isLightmapped() const = 0; + virtual bool castsShadows() const = 0; +}; + +#endif diff --git a/materials/customMaterialDefinition.cpp b/materials/customMaterialDefinition.cpp new file mode 100644 index 0000000..eac6433 --- /dev/null +++ b/materials/customMaterialDefinition.cpp @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "materials/customMaterialDefinition.h" +#include "materials/materialManager.h" +#include "console/consoleTypes.h" +#include "materials/shaderData.h" +#include "gfx/sim/cubemapData.h" +#include "gfx/gfxCubemap.h" +#include "gfx/sim/gfxStateBlockData.h" + + +//**************************************************************************** +// Custom Material +//**************************************************************************** +IMPLEMENT_CONOBJECT(CustomMaterial); + +//---------------------------------------------------------------------------- +// Constructor +//---------------------------------------------------------------------------- +CustomMaterial::CustomMaterial() +{ + mFallback = NULL; + mMaxTex = 0; + mVersion = 1.1f; + mTranslucent = false; + dMemset( mFlags, 0, sizeof( mFlags ) ); + mShaderData = NULL; + mRefract = false; + mStateBlockData = NULL; +} + +//-------------------------------------------------------------------------- +// Init fields +//-------------------------------------------------------------------------- +void CustomMaterial::initPersistFields() +{ + addField("texture", TypeStringFilename, Offset(mTexFilename, CustomMaterial), MAX_TEX_PER_PASS); + addField("version", TypeF32, Offset(mVersion, CustomMaterial)); + addField("fallback", TypeSimObjectPtr, Offset(mFallback, CustomMaterial)); + addField("shader", TypeRealString, Offset(mShaderDataName, CustomMaterial)); + addField("stateBlock", TypeSimObjectPtr, Offset(mStateBlockData, CustomMaterial)); + addField("target", TypeRealString, Offset(mOutputTarget, CustomMaterial)); + + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +// On add - verify data settings +//-------------------------------------------------------------------------- +bool CustomMaterial::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + mShaderData = dynamic_cast(Sim::findObject( mShaderDataName ) ); + if(mShaderDataName.isNotEmpty() && mShaderData == NULL) + { + logError("Failed to find ShaderData %s", mShaderDataName.c_str()); + return false; + } + + return true; +} + +//-------------------------------------------------------------------------- +// On remove +//-------------------------------------------------------------------------- +void CustomMaterial::onRemove() +{ + Parent::onRemove(); +} + +//-------------------------------------------------------------------------- +// Map this material to the texture specified in the "mapTo" data variable +//-------------------------------------------------------------------------- +void CustomMaterial::_mapMaterial() +{ + if( String(getName()).isEmpty() ) + { + Con::warnf( "Unnamed Material! Could not map to: %s", mMapTo.c_str() ); + return; + } + + if( mMapTo.isEmpty() ) + return; + + MATMGR->mapMaterial(mMapTo, getName()); +} + +const GFXStateBlockData* CustomMaterial::getStateBlockData() const +{ + return mStateBlockData; +} diff --git a/materials/customMaterialDefinition.h b/materials/customMaterialDefinition.h new file mode 100644 index 0000000..74397a4 --- /dev/null +++ b/materials/customMaterialDefinition.h @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _CUSTOMMATERIALDEFINITION_H_ +#define _CUSTOMMATERIALDEFINITION_H_ + +#ifndef _MATERIALDEFINITION_H_ +#include "materials/materialDefinition.h" +#endif + +class ShaderData; +class GFXStateBlockData; + +//************************************************************************** +// Custom Material +//************************************************************************** +class CustomMaterial : public Material +{ + typedef Material Parent; +public: + enum CustomConsts + { + MAX_PASSES = 8, + NUM_FALLBACK_VERSIONS = 2, + }; + + FileName mTexFilename[MAX_TEX_PER_PASS]; + String mOutputTarget; + Material* mFallback; + + F32 mVersion; // 0 = legacy, 1 = DX 8.1, 2 = DX 9.0 + bool mRefract; + ShaderData* mShaderData; + + CustomMaterial(); + const GFXStateBlockData* getStateBlockData() const; + + // + // SimObject interface + // + virtual bool onAdd(); + virtual void onRemove(); + + // + // ConsoleObject interface + // + static void initPersistFields(); + DECLARE_CONOBJECT(CustomMaterial); +protected: + U32 mMaxTex; + String mShaderDataName; + U32 mFlags[MAX_TEX_PER_PASS]; + GFXStateBlockData* mStateBlockData; + + virtual void _mapMaterial(); +}; + +#endif diff --git a/materials/matInstance.cpp b/materials/matInstance.cpp new file mode 100644 index 0000000..b4e7e51 --- /dev/null +++ b/materials/matInstance.cpp @@ -0,0 +1,495 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/matInstance.h" + +#include "materials/materialManager.h" +#include "materials/customMaterialDefinition.h" +#include "materials/processedMaterial.h" +#include "materials/processedFFMaterial.h" +#include "materials/processedShaderMaterial.h" +#include "materials/processedCustomMaterial.h" +#include "shaderGen/featureMgr.h" +#include "gfx/gfxDevice.h" +#include "gfx/sim/cubemapData.h" +#include "gfx/gfxCubemap.h" +#include "core/util/safeDelete.h" + +class MatInstParameters; + +class MatInstanceParameterHandle : public MaterialParameterHandle +{ +public: + virtual ~MatInstanceParameterHandle() {} + MatInstanceParameterHandle(const String& name); + + void loadHandle(ProcessedMaterial* pmat); + + // MaterialParameterHandle interface + const String& getName() const { return mName; } + virtual bool isValid() const; + virtual S32 getSamplerRegister( U32 pass ) const; +private: + friend class MatInstParameters; + String mName; + MaterialParameterHandle* mProcessedHandle; +}; + +MatInstanceParameterHandle::MatInstanceParameterHandle(const String& name) +{ + mName = name; + mProcessedHandle = NULL; +} + +bool MatInstanceParameterHandle::isValid() const +{ + return mProcessedHandle && mProcessedHandle->isValid(); +} + +S32 MatInstanceParameterHandle::getSamplerRegister( U32 pass ) const +{ + if ( !mProcessedHandle ) + return -1; + return mProcessedHandle->getSamplerRegister( pass ); +} + +void MatInstanceParameterHandle::loadHandle(ProcessedMaterial* pmat) +{ + mProcessedHandle = pmat->getMaterialParameterHandle(mName); +} + +MatInstParameters::MatInstParameters() +{ + mOwnParameters = false; + mParameters = NULL; +} + +MatInstParameters::MatInstParameters(MaterialParameters* matParams) +{ + mOwnParameters = false; + mParameters = matParams; +} + +void MatInstParameters::loadParameters(ProcessedMaterial* pmat) +{ + mOwnParameters = true; + mParameters = pmat->allocMaterialParameters(); +} + +MatInstParameters::~MatInstParameters() +{ + if (mOwnParameters) + SAFE_DELETE(mParameters); +} + +const Vector& MatInstParameters::getShaderConstDesc() const +{ + return mParameters->getShaderConstDesc(); +} + +U32 MatInstParameters::getAlignmentValue(const GFXShaderConstType constType) +{ + return mParameters->getAlignmentValue(constType); +} + +#define MATINSTPARAMSET(handle, f) \ + if (!mParameters) \ + return; \ + AssertFatal(dynamic_cast(handle), "Invalid handle type!"); \ + MatInstanceParameterHandle* mph = static_cast(handle); \ + mParameters->set(mph->mProcessedHandle, f); \ + +void MatInstParameters::set(MaterialParameterHandle* handle, const F32 f) +{ + MATINSTPARAMSET(handle, f); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const Point2F& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const Point3F& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const Point4F& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const ColorF& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const S32 f) +{ + MATINSTPARAMSET(handle, f); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const Point2I& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const Point3I& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const Point4I& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + MATINSTPARAMSET(handle, fv); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType) +{ + AssertFatal(dynamic_cast(handle), "Invalid handle type!"); + MatInstanceParameterHandle* mph = static_cast(handle); + mParameters->set(mph->mProcessedHandle, mat, matrixType); +} + +void MatInstParameters::set(MaterialParameterHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType) +{ + AssertFatal(dynamic_cast(handle), "Invalid handle type!"); + MatInstanceParameterHandle* mph = static_cast(handle); + mParameters->set(mph->mProcessedHandle, mat, arraySize, matrixType); +} +#undef MATINSTPARAMSET + +//**************************************************************************** +// Material Instance +//**************************************************************************** +MatInstance::MatInstance( Material &mat ) +{ + VECTOR_SET_ASSOCIATION( mCurrentHandles ); + VECTOR_SET_ASSOCIATION( mCurrentParameters ); + + mMaterial = &mat; + + construct(); +} + +//---------------------------------------------------------------------------- +// Construct +//---------------------------------------------------------------------------- +void MatInstance::construct() +{ + mCurPass = -1; + mProcessedMaterial = false; + mVertexFormat = NULL; + mMaxStages = 1; + mMatNameStr = "Unknown"; + mActiveParameters = NULL; + mDefaultParameters = NULL; + + MATMGR->_track(this); +} + +//---------------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------------- +MatInstance::~MatInstance() +{ + SAFE_DELETE(mProcessedMaterial); + SAFE_DELETE(mDefaultParameters); + for (U32 i = 0; i < mCurrentHandles.size(); i++) + SAFE_DELETE(mCurrentHandles[i]); + + MATMGR->_untrack(this); +} + +//---------------------------------------------------------------------------- +// Init +//---------------------------------------------------------------------------- +bool MatInstance::init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat ) +{ + mFeatureList = features; + mVertexFormat = vertexFormat; + + SAFE_DELETE(mProcessedMaterial); + mIsValid = processMaterial(); + + return mIsValid; +} + + +//---------------------------------------------------------------------------- +// reInitialize +//---------------------------------------------------------------------------- +bool MatInstance::reInit() +{ + SAFE_DELETE(mProcessedMaterial); + mIsValid = processMaterial(); + + if ( mIsValid ) + { + for (U32 i = 0; i < mCurrentHandles.size(); i++) + mCurrentHandles[i]->loadHandle(mProcessedMaterial); + + for (U32 i = 0; i < mCurrentParameters.size(); i++) + mCurrentParameters[i]->loadParameters(mProcessedMaterial); + } + + return mIsValid; +} + +//---------------------------------------------------------------------------- +// Process stages +//---------------------------------------------------------------------------- +bool MatInstance::processMaterial() +{ + AssertFatal( mMaterial, "Material is not valid!" ); + if ( !mMaterial ) + return false; + + SAFE_DELETE(mDefaultParameters); + + if( dynamic_cast(mMaterial) ) + { + F32 pixVersion = GFX->getPixelShaderVersion(); + CustomMaterial* custMat = static_cast(mMaterial); + if ((custMat->mVersion > pixVersion) || (custMat->mVersion == 0.0)) + { + if(custMat->mFallback) + { + mMaterial = custMat->mFallback; + return processMaterial(); + } + else + { + AssertWarn(custMat->mVersion == 0.0f, avar("Can't load CustomMaterial %s for %s, using generic FF fallback", + String(mMaterial->getName()).isEmpty() ? "Unknown" : mMaterial->getName(), custMat->mMapTo.c_str())); + mProcessedMaterial = new ProcessedFFMaterial(*mMaterial); + } + } + else + mProcessedMaterial = new ProcessedCustomMaterial(*mMaterial); + } + else if(GFX->getPixelShaderVersion() > 0.001) + mProcessedMaterial = getShaderMaterial(); + else + mProcessedMaterial = new ProcessedFFMaterial(*mMaterial); + + if (mProcessedMaterial) + { + mProcessedMaterial->addStateBlockDesc( mUserDefinedState ); + mProcessedMaterial->setShaderMacros( mUserMacros ); + + FeatureSet features( mFeatureList ); + features.exclude( MATMGR->getExclusionFeatures() ); + + if( !mProcessedMaterial->init(features, mVertexFormat, mFeaturesDelegate) ) + { + Con::errorf( "Failed to initialize material '%s'", getMaterial()->getName() ); + SAFE_DELETE( mProcessedMaterial ); + return false; + } + + mDefaultParameters = new MatInstParameters(mProcessedMaterial->getDefaultMaterialParameters()); + mActiveParameters = mDefaultParameters; + + return true; + } + + return false; +} + +ProcessedMaterial* MatInstance::getShaderMaterial() +{ + return new ProcessedShaderMaterial(*mMaterial); +} + +void MatInstance::addStateBlockDesc(const GFXStateBlockDesc& desc) +{ + mUserDefinedState = desc; +} + +void MatInstance::addShaderMacro( const String &name, const String &value ) +{ + // Check to see if we already have this macro. + Vector::iterator iter = mUserMacros.begin(); + for ( ; iter != mUserMacros.end(); iter++ ) + { + if ( iter->name == name ) + { + iter->value = value; + return; + } + } + + // Add a new macro. + mUserMacros.increment(); + mUserMacros.last().name = name; + mUserMacros.last().value = value; +} + +//---------------------------------------------------------------------------- +// Setup pass - needs scenegraph data because the lightmap will change across +// several materials. +//---------------------------------------------------------------------------- +bool MatInstance::setupPass(SceneState * state, const SceneGraphData &sgData ) +{ + PROFILE_SCOPE( MatInstance_SetupPass ); + + if( !mProcessedMaterial ) + return false; + + ++mCurPass; + + if ( !mProcessedMaterial->setupPass( state, sgData, mCurPass ) ) + { + mProcessedMaterial->cleanup(mCurPass - 1); + mCurPass = -1; + return false; + } + return true; +} + +void MatInstance::setTransforms(const MatrixSet &matrixSet, SceneState *state) +{ + PROFILE_SCOPE(MatInstance_setTransforms); + mProcessedMaterial->setTransforms(matrixSet, state, getCurPass()); +} + +void MatInstance::setSceneInfo(SceneState * state, const SceneGraphData& sgData) +{ + mProcessedMaterial->setSceneInfo(state, sgData, getCurPass()); +} + +void MatInstance::setBuffers(GFXVertexBufferHandleBase* vertBuffer, GFXPrimitiveBufferHandle* primBuffer) +{ + mProcessedMaterial->setBuffers(vertBuffer, primBuffer); +} + +void MatInstance::setTextureStages(SceneState * state, const SceneGraphData &sgData ) +{ + mProcessedMaterial->setTextureStages(state, sgData, getCurPass()); +} + +U32 MatInstance::getCurStageNum() +{ + return mProcessedMaterial->getStageFromPass(getCurPass()); +} + +RenderPassData* MatInstance::getPass(U32 pass) +{ + return mProcessedMaterial->getPass(pass); +} + +bool MatInstance::hasGlow() +{ + if( mProcessedMaterial ) + return mProcessedMaterial->hasGlow(); + else + return false; +} + +const FeatureSet& MatInstance::getFeatures() const +{ + return mProcessedMaterial->getFeatures(); +} + +MaterialParameterHandle* MatInstance::getMaterialParameterHandle(const String& name) +{ + AssertFatal(mProcessedMaterial, "Not init'ed!"); + for (U32 i = 0; i < mCurrentHandles.size(); i++) + { + if (mCurrentHandles[i]->getName().equal(name)) + { + return mCurrentHandles[i]; + } + } + MatInstanceParameterHandle* mph = new MatInstanceParameterHandle(name); + mph->loadHandle(mProcessedMaterial); + mCurrentHandles.push_back(mph); + return mph; +} + +MaterialParameters* MatInstance::allocMaterialParameters() +{ + AssertFatal(mProcessedMaterial, "Not init'ed!"); + MatInstParameters* mip = new MatInstParameters(); + mip->loadParameters(mProcessedMaterial); + mCurrentParameters.push_back(mip); + return mip; +} + +void MatInstance::setMaterialParameters(MaterialParameters* param) +{ + AssertFatal(mProcessedMaterial, "Not init'ed!"); + mProcessedMaterial->setMaterialParameters(param, mCurPass); + AssertFatal(dynamic_cast(param), "Incorrect param type!"); + mActiveParameters = static_cast(param); +} + +MaterialParameters* MatInstance::getMaterialParameters() +{ + AssertFatal(mProcessedMaterial, "Not init'ed!"); + return mActiveParameters; +} + +void MatInstance::dumpShaderInfo() const +{ + if ( mMaterial == NULL ) + { + Con::errorf( "Trying to get Material information on an invalid MatInstance" ); + return; + } + + Con::printf( "Material Info for object %s - %s", mMaterial->getName(), mMaterial->mMapTo.c_str() ); + + if ( mProcessedMaterial == NULL ) + { + Con::printf( " [no processed material!]" ); + return; + } + + mProcessedMaterial->dumpMaterialInfo(); +} diff --git a/materials/matInstance.h b/materials/matInstance.h new file mode 100644 index 0000000..70f3998 --- /dev/null +++ b/materials/matInstance.h @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATINSTANCE_H_ +#define _MATINSTANCE_H_ + +#ifndef _MATERIALDEFINITION_H_ +#include "materials/materialDefinition.h" +#endif +#ifndef _BASEMATINSTANCE_H_ +#include "materials/baseMatInstance.h" +#endif +#ifndef _SCENEDATA_H_ +#include "materials/sceneData.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + +class GFXShader; +class GFXCubemap; +class ShaderFeature; +class MatInstanceParameterHandle; +class MatInstParameters; +class ProcessedMaterial; + + +/// +class MatInstance : public BaseMatInstance +{ +public: + virtual ~MatInstance(); + + // BaseMatInstance + virtual bool init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat ); + virtual bool reInit(); + virtual void addStateBlockDesc(const GFXStateBlockDesc& desc); + virtual void addShaderMacro( const String &name, const String &value ); + virtual MaterialParameters* allocMaterialParameters(); + virtual void setMaterialParameters(MaterialParameters* param); + virtual MaterialParameters* getMaterialParameters(); + virtual MaterialParameterHandle* getMaterialParameterHandle(const String& name); + virtual bool setupPass(SceneState *, const SceneGraphData &sgData ); + virtual void setTransforms(const MatrixSet &matrixSet, SceneState *state); + virtual void setSceneInfo(SceneState *, const SceneGraphData& sgData); + virtual void setTextureStages(SceneState * state, const SceneGraphData &sgData ); + virtual void setBuffers(GFXVertexBufferHandleBase* vertBuffer, GFXPrimitiveBufferHandle* primBuffer); + virtual Material *getMaterial() { return mMaterial; } + virtual bool hasGlow(); + virtual U32 getCurPass() { return getMax( mCurPass, 0 ); } + virtual U32 getCurStageNum(); + virtual RenderPassData *getPass(U32 pass); + virtual const GFXVertexFormat* getVertexFormat() const { return mVertexFormat; } + virtual const FeatureSet& getFeatures() const; + virtual const FeatureSet& getRequestedFeatures() const { return mFeatureList; } + virtual void dumpShaderInfo() const; + + ProcessedMaterial *getProcessedMaterial() const { return mProcessedMaterial; } + +protected: + + friend class Material; + + /// Create a material instance by reference to a Material. + MatInstance( Material &mat ); + + virtual bool processMaterial(); + virtual ProcessedMaterial* getShaderMaterial(); + + Material* mMaterial; + ProcessedMaterial* mProcessedMaterial; + + /// The features requested at material creation time. + FeatureSet mFeatureList; + + /// The vertex format on which this material will render. + const GFXVertexFormat *mVertexFormat; + + S32 mCurPass; + U32 mMaxStages; + + GFXStateBlockDesc mUserDefinedState; + + Vector mUserMacros; + + Vector mCurrentHandles; + Vector mCurrentParameters; + MatInstParameters* mActiveParameters; + MatInstParameters* mDefaultParameters; +private: + void construct(); +}; + +// +// MatInstParameters +// +class MatInstParameters : public MaterialParameters +{ +public: + MatInstParameters(); + MatInstParameters(MaterialParameters* matParams); + virtual ~MatInstParameters(); + + void loadParameters(ProcessedMaterial* pmat); + + /// Returns our list of shader constants, the material can get this and just set the constants it knows about + virtual const Vector& getShaderConstDesc() const; + + /// @name Set shader constant values + /// @{ + /// Actually set shader constant values + /// @param name Name of the constant, this should be a name contained in the array returned in getShaderConstDesc, + /// if an invalid name is used, it is ignored. + virtual void set(MaterialParameterHandle* handle, const F32 f); + virtual void set(MaterialParameterHandle* handle, const Point2F& fv); + virtual void set(MaterialParameterHandle* handle, const Point3F& fv); + virtual void set(MaterialParameterHandle* handle, const Point4F& fv); + virtual void set(MaterialParameterHandle* handle, const ColorF& fv); + virtual void set(MaterialParameterHandle* handle, const S32 f); + virtual void set(MaterialParameterHandle* handle, const Point2I& fv); + virtual void set(MaterialParameterHandle* handle, const Point3I& fv); + virtual void set(MaterialParameterHandle* handle, const Point4I& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType = GFXSCT_Float4x4); + virtual void set(MaterialParameterHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType = GFXSCT_Float4x4); + virtual U32 getAlignmentValue(const GFXShaderConstType constType); +private: + MaterialParameters* mParameters; + bool mOwnParameters; +}; + + +#endif // _MATINSTANCE_H_ diff --git a/materials/matInstanceHook.cpp b/materials/matInstanceHook.cpp new file mode 100644 index 0000000..d36211a --- /dev/null +++ b/materials/matInstanceHook.cpp @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/matInstanceHook.h" + + +MatInstanceHookType::MatInstanceHookType( const char *type ) +{ + TypeMap::Iterator iter = getTypeMap().find( type ); + if ( iter == getTypeMap().end() ) + iter = getTypeMap().insertUnique( type, getTypeMap().size() ); + + mTypeIndex = iter->value; +} + diff --git a/materials/matInstanceHook.h b/materials/matInstanceHook.h new file mode 100644 index 0000000..7dbe7f9 --- /dev/null +++ b/materials/matInstanceHook.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATINSTANCEHOOK_H_ +#define _MATINSTANCEHOOK_H_ + +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + + +/// The hook type wrapper object +class MatInstanceHookType +{ +protected: + + typedef HashTable TypeMap; + + /// Returns the map of all the hook types. We create + /// it as a method static so that its available to other + /// statics regardless of initialization order. + static inline TypeMap& getTypeMap() + { + static TypeMap smTypeMap; + return smTypeMap; + } + + /// The hook type index for this type. + U32 mTypeIndex; + +public: + + MatInstanceHookType( const char *type ); + + inline MatInstanceHookType( const MatInstanceHookType &type ) + : mTypeIndex( type.mTypeIndex ) + { + } + + inline operator U32 () const { return mTypeIndex; } +}; + + +/// This class is used to define hook objects attached to +/// material instances and provide a registration system +/// for different hook types. +/// +/// @see BaseMatInstance +/// @see MaterialManager +/// +class MatInstanceHook +{ +public: + + /// + virtual ~MatInstanceHook() {} + + /// + virtual const MatInstanceHookType& getType() const = 0; +}; + +#endif // _MATINSTANCEHOOK_H_ + + + + + + diff --git a/materials/matTextureTarget.cpp b/materials/matTextureTarget.cpp new file mode 100644 index 0000000..cf73754 --- /dev/null +++ b/materials/matTextureTarget.cpp @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/matTextureTarget.h" + +#include "console/console.h" +#include "platform/profiler.h" +#include "shaderGen/conditionerFeature.h" +#include "gfx/gfxTextureObject.h" +#include "gfx/gfxStructs.h" + +MatTextureTarget::TexTargetMap MatTextureTarget::smRegisteredTargets; + + +bool MatTextureTarget::registerTarget( const String &name, MatTextureTarget *target ) +{ + // Don't register targets to names already taken. + if ( smRegisteredTargets.contains( name ) ) + return false; + + target->mRegTargetName = name; + smRegisteredTargets.insert( name, target ); + return true; +} + +void MatTextureTarget::unregisterTarget( const String &name, MatTextureTarget *target ) +{ + TexTargetMap::Iterator iter = smRegisteredTargets.find( name ); + if ( iter == smRegisteredTargets.end() || ( target != NULL && iter->value != target ) ) + return; + + smRegisteredTargets.erase( iter ); +} + +MatTextureTarget* MatTextureTarget::findTargetByName( const String &name ) +{ + PROFILE_SCOPE( MatTextureTarget_FindTargetByName ); + + TexTargetMap::Iterator iter = smRegisteredTargets.find( name ); + if ( iter != smRegisteredTargets.end() ) + return iter->value; + else + return NULL; +} + +MatTextureTarget::~MatTextureTarget() +{ + // Remove ourselves from the registered targets. + TexTargetMap::Iterator iter = smRegisteredTargets.begin(); + for ( ; iter != smRegisteredTargets.end(); iter++ ) + { + if ( iter->value == this ) + { + TexTargetMap::Iterator prev = iter++; + smRegisteredTargets.erase( prev ); + } + } +} + +void MatTextureTarget::getTargetShaderMacros( Vector *outMacros ) +{ + ConditionerFeature *cond = getTargetConditioner(); + if ( !cond ) + return; + + // TODO: No check for duplicates is + // going on here which might be a problem? + + String targetName = String::ToLower( mRegTargetName ); + + // Add both the condition and uncondition macros. + const String &condMethod = cond->getShaderMethodName( ConditionerFeature::ConditionMethod ); + if ( condMethod.isNotEmpty() ) + { + GFXShaderMacro macro; + macro.name = targetName + "Condition"; + macro.value = condMethod; + outMacros->push_back( macro ); + } + + const String &uncondMethod = cond->getShaderMethodName( ConditionerFeature::UnconditionMethod ); + if ( uncondMethod.isNotEmpty() ) + { + GFXShaderMacro macro; + macro.name = targetName + "Uncondition"; + macro.value = uncondMethod; + outMacros->push_back( macro ); + } +} \ No newline at end of file diff --git a/materials/matTextureTarget.h b/materials/matTextureTarget.h new file mode 100644 index 0000000..934142a --- /dev/null +++ b/materials/matTextureTarget.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATTEXTURETARGET_H_ +#define _MATTEXTURETARGET_H_ + +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _REFBASE_H_ +#include "core/util/refBase.h" +#endif + +class RectI; +class GFXTextureObject; +struct GFXSamplerStateDesc; +struct GFXShaderMacro; +class ConditionerFeature; + + +/// +class MatTextureTarget : public WeakRefBase +{ +protected: + + typedef Map TexTargetMap; + + /// + static TexTargetMap smRegisteredTargets; + + /// The target name we were registered with. + String mRegTargetName; + +public: + + /// @name Registration and Management + /// @{ + + /// + static bool registerTarget( const String &name, MatTextureTarget *target ); + + /// + static void unregisterTarget( const String &name, MatTextureTarget *target ); + + /// + static MatTextureTarget* findTargetByName( const String &name ); + + /// @} + +public: + + virtual ~MatTextureTarget(); + + /// + virtual GFXTextureObject* getTargetTexture( U32 mrtIndex ) const = 0; + + /// + //virtual const Point2I &getTargetSize() const = 0; + + /// + virtual const RectI& getTargetViewport() const = 0; + + /// + virtual void setupSamplerState( GFXSamplerStateDesc *desc ) const = 0; + + /// Returns the conditioner feature for the target. + virtual ConditionerFeature* getTargetConditioner() const = 0; + + /// Adds the condition and uncondition shader macros + /// from the ConditionerFeature to the incoming vector. + void getTargetShaderMacros( Vector *outMacros ); + +}; + +/// A weak reference to a texture target. +typedef WeakRefPtr MatTextureTargetRef; + +#endif // _MATTEXTURETARGET_H_ diff --git a/materials/materialDefinition.cpp b/materials/materialDefinition.cpp new file mode 100644 index 0000000..6235313 --- /dev/null +++ b/materials/materialDefinition.cpp @@ -0,0 +1,460 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/materialDefinition.h" + +#include "console/consoleTypes.h" +#include "math/mathTypes.h" +#include "materials/materialManager.h" +#include "sceneData.h" +#include "gfx/sim/cubemapData.h" +#include "gfx/gfxCubemap.h" +#include "math/mathIO.h" +#include "materials/matInstance.h" +#include "sfx/sfxProfile.h" +#include "core/util/safeDelete.h" + + +GFXCubemap * Material::GetNormalizeCube() +{ + if(smNormalizeCube) + return smNormalizeCube; + smNormalizeCube = GFX->createCubemap(); + smNormalizeCube->initNormalize(64); + return smNormalizeCube; +} + + +IMPLEMENT_CONOBJECT(Material); + +static EnumTable::Enums gAnimFlagEnums[] = +{ + { Material::Scroll, "Scroll" }, + { Material::Rotate, "Rotate" }, + { Material::Wave, "Wave" }, + { Material::Scale, "Scale" }, + { Material::Sequence, "Sequence" } +}; +EnumTable Material::mAnimFlagTable( 5, gAnimFlagEnums ); + +static EnumTable::Enums gBlendOpEnums[] = +{ + { Material::None, "None" }, + { Material::Mul, "Mul" }, + { Material::Add, "Add" }, + { Material::AddAlpha, "AddAlpha" }, + { Material::Sub, "Sub" }, + { Material::LerpAlpha, "LerpAlpha" } +}; +EnumTable Material::mBlendOpTable( 6, gBlendOpEnums ); + +static EnumTable::Enums gWaveTypeEnums[] = +{ + { Material::Sin, "Sin" }, + { Material::Triangle, "Triangle" }, + { Material::Square, "Square" }, +}; +EnumTable Material::mWaveTypeTable( 3, gWaveTypeEnums ); + +GFXCubemapHandle Material::smNormalizeCube; + +Material::Material() +{ + for( U32 i=0; i(Sim::findObject( mCubemapName ) ); + + if( mTranslucentBlendOp >= NumBlendTypes || mTranslucentBlendOp < 0 ) + { + Con::errorf( "Invalid blend op in material: %s", getName() ); + mTranslucentBlendOp = LerpAlpha; + } + + SimSet *matSet = MATMGR->getMaterialSet(); + if( matSet ) + matSet->addObject( (SimObject*)this ); + + // save the current script path for texture lookup later + const String scriptFile = Con::getVariable("$Con::File"); // current script file - local materials.cs + + String::SizeType slash = scriptFile.find( '/', scriptFile.length(), String::Right ); + + AssertFatal( slash != String::NPos, "Invalid value for $Con::File - missing '/'" ); + + mPath = scriptFile.substr( 0, slash + 1 ); + + _mapMaterial(); + + return true; +} + +void Material::onRemove() +{ + smNormalizeCube = NULL; + Parent::onRemove(); +} + +void Material::inspectPostApply() +{ + Parent::inspectPostApply(); + + // Reload the material instances which + // use this material. + if ( isProperlyAdded() ) + reload(); +} + + +bool Material::isLightmapped() const +{ + bool ret = false; + for( U32 i=0; igetLastUpdateTime(); + F32 dt = MATMGR->getDeltaTime(); + if (mLastUpdateTime != lastTime) + { + for (U32 i = 0; i < MAX_STAGES; i++) + { + mScrollOffset[i] += mScrollDir[i] * mScrollSpeed[i] * dt; + mRotPos[i] += mRotSpeed[i] * dt; + mWavePos[i] += mWaveFreq[i] * dt; + } + mLastUpdateTime = lastTime; + } +} + +void Material::_mapMaterial() +{ + if( String(getName()).isEmpty() ) + { + Con::warnf( "[Material::mapMaterial] - Cannot map unnamed Material" ); + return; + } + + // If mapTo not defined in script, try to use the base texture name instead + if( mMapTo.isEmpty() ) + { + mIsIFL = false; + + if( mDiffuseMapFilename[0].isEmpty() && mBaseTexFilename[0].isEmpty() ) + { + return; + } + else + { + // extract filename from base texture + if ( mDiffuseMapFilename[0].isNotEmpty() ) + { + U32 slashPos = mDiffuseMapFilename[0].find('/',0,String::Right); + if (slashPos == String::NPos) + // no '/' character, must be no path, just the filename + mMapTo = mDiffuseMapFilename[0]; + else + // use everything after the last slash + mMapTo = mDiffuseMapFilename[0].substr(slashPos+1, mDiffuseMapFilename[0].length() - slashPos - 1); + } + else if ( mBaseTexFilename[0].isNotEmpty() ) + { + U32 slashPos = mBaseTexFilename[0].find('/',0,String::Right); + if (slashPos == String::NPos) + // no '/' character, must be no path, just the filename + mMapTo = mBaseTexFilename[0]; + else + // use everything after the last slash + mMapTo = mBaseTexFilename[0].substr(slashPos+1, mBaseTexFilename[0].length() - slashPos - 1); + } + } + } + else + { + mIsIFL = String::NPos != mMapTo.find(".ifl", String::NoCase); + } + + // add mapping + MATMGR->mapMaterial(mMapTo,getName()); +} + +BaseMatInstance* Material::createMatInstance() +{ + return new MatInstance(*this); +} + +void Material::flush() +{ + MATMGR->flushInstance( this ); +} + +void Material::reload() +{ + MATMGR->reInitInstance( this ); +} + +ConsoleMethod( Material, flush, void, 2, 2, + "Flushes all material instances that use this material." ) +{ + object->flush(); +} + +ConsoleMethod( Material, reload, void, 2, 2, + "Reloads all material instances that use this material." ) +{ + object->reload(); +} + +ConsoleMethod( Material, dumpInstances, void, 2, 2, + "Dumps a formatted list of the currently allocated material instances for this material to the console." ) +{ + MATMGR->dumpMaterialInstances( object ); +} + +ConsoleMethod( Material, getAnimFlags, const char*, 3, 3, "" ) +{ + char * animFlags = Con::getReturnBuffer(512); + + if(object->mAnimFlags[ dAtoi(argv[2]) ] & Material::Scroll) + { + if(dStrcmp( animFlags, "" ) == 0) + dStrcpy( animFlags, "$Scroll" ); + } + if(object->mAnimFlags[ dAtoi(argv[2]) ] & Material::Rotate) + { + if(dStrcmp( animFlags, "" ) == 0) + dStrcpy( animFlags, "$Rotate" ); + else + dStrcat( animFlags, " | $Rotate"); + } + if(object->mAnimFlags[ dAtoi(argv[2]) ] & Material::Wave) + { + if(dStrcmp( animFlags, "" ) == 0) + dStrcpy( animFlags, "$Wave" ); + else + dStrcat( animFlags, " | $Wave"); + } + if(object->mAnimFlags[ dAtoi(argv[2]) ] & Material::Scale) + { + if(dStrcmp( animFlags, "" ) == 0) + dStrcpy( animFlags, "$Scale" ); + else + dStrcat( animFlags, " | $Scale"); + } + if(object->mAnimFlags[ dAtoi(argv[2]) ] & Material::Sequence) + { + if(dStrcmp( animFlags, "" ) == 0) + dStrcpy( animFlags, "$Sequence" ); + else + dStrcat( animFlags, " | $Sequence"); + } + + return animFlags; +} + +ConsoleMethod(Material, getFilename, const char*, 2, 2, "Get filename of material") +{ + SimObject *material = static_cast(object); + return material->getFilename(); +} + +ConsoleMethod( Material, isAutoGenerated, bool, 2, 2, + "Returns true if this Material was automatically generated by MaterialList::mapMaterials()" ) +{ + return object->isAutoGenerated(); +} + +ConsoleMethod( Material, setAutoGenerated, void, 3, 3, + "setAutoGenerated(bool isAutoGenerated): Set whether or not the Material is autogenerated." ) +{ + object->setAutoGenerated(dAtob(argv[2])); +} \ No newline at end of file diff --git a/materials/materialDefinition.h b/materials/materialDefinition.h new file mode 100644 index 0000000..5ea1c87 --- /dev/null +++ b/materials/materialDefinition.h @@ -0,0 +1,380 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATERIALDEFINITION_H_ +#define _MATERIALDEFINITION_H_ + +#ifndef _BASEMATERIALDEFINITION_H_ +#include "materials/baseMaterialDefinition.h" +#endif +#ifndef _MATERIALFEATUREDATA_H_ +#include "materials/materialFeatureData.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXSTRUCTS_H_ +#include "gfx/gfxStructs.h" +#endif +#ifndef _GFXCUBEMAP_H_ +#include "gfx/gfxCubemap.h" +#endif + +class CubemapData; +class SFXProfile; +struct SceneGraphData; + +class MaterialSoundProfile; +class MaterialPhysicsProfile; + +//************************************************************************** +// Basic Material +//************************************************************************** +class Material : public BaseMaterialDefinition +{ + typedef BaseMaterialDefinition Parent; +public: + static GFXCubemap *GetNormalizeCube(); + + //----------------------------------------------------------------------- + // Enums + //----------------------------------------------------------------------- + enum Constants + { + MAX_TEX_PER_PASS = 8, ///< Number of textures per pass + MAX_STAGES = 4, + NUM_EFFECT_COLOR_STAGES = 2, ///< Number of effect color definitions for transitioning effects. + }; + + enum TexType + { + NoTexture = 0, + Standard = 1, + Detail, + Bump, + Env, + Cube, + SGCube, // scene graph cube - probably dynamic + Lightmap, + ToneMapTex, + Mask, + BackBuff, + ReflectBuff, + Misc, + DynamicLight, + DynamicLight2, + DynamicLight3, + DynamicLight4, + DynamicLightSecondary, + DynamicLightMask, + NormalizeCube, + TexTarget, + }; + + enum BlendOp + { + None = 0, + Mul, + Add, + AddAlpha, // add modulated with alpha channel + Sub, + LerpAlpha, // linear interpolation modulated with alpha channel + ToneMap, + NumBlendTypes + }; + + enum AnimType + { + Scroll = 1, + Rotate = 2, + Wave = 4, + Scale = 8, + Sequence = 16, + }; + + enum WaveType + { + Sin = 0, + Triangle, + Square, + }; + + class StageData + { + protected: + + /// + typedef HashTable TextureTable; + + /// The sparse table of textures by feature index. + /// @see getTex + /// @see setTex + TextureTable mTextures; + + /// The cubemap for this stage. + GFXCubemap *mCubemap; + + public: + + StageData() + : mCubemap( NULL ) + { + } + + /// Returns the texture object or NULL if there is no + /// texture entry for that feature type in the table. + inline GFXTextureObject* getTex( const FeatureType &type ) const + { + TextureTable::ConstIterator iter = mTextures.find( &type ); + if ( iter == mTextures.end() ) + return NULL; + + return iter->value.getPointer(); + } + + /// Assigns a texture object by feature type. + inline void setTex( const FeatureType &type, GFXTextureObject *tex ) + { + if ( !tex ) + { + TextureTable::Iterator iter = mTextures.find( &type ); + if ( iter != mTextures.end() ) + mTextures.erase( iter ); + + return; + } + + TextureTable::Iterator iter = mTextures.findOrInsert( &type ); + iter->value = tex; + } + + /// Returns true if we have a valid texture assigned to + /// any feature in the texture table. + inline bool hasValidTex() const + { + TextureTable::ConstIterator iter = mTextures.begin(); + for ( ; iter != mTextures.end(); iter++ ) + { + if ( iter->value.isValid() ) + return true; + } + + return false; + } + + /// Returns the active texture features. + inline FeatureSet getFeatureSet() const + { + FeatureSet result; + + TextureTable::ConstIterator iter = mTextures.begin(); + for ( ; iter != mTextures.end(); iter++ ) + { + if ( iter->value.isValid() ) + result.addFeature( *iter->key ); + } + + return result; + } + + /// Returns the stage cubemap. + GFXCubemap* getCubemap() const { return mCubemap; } + + /// Set the stage cubemap. + void setCubemap( GFXCubemap *cubemap ) { mCubemap = cubemap; } + + }; + +public: + + //----------------------------------------------------------------------- + // Data + //----------------------------------------------------------------------- + FileName mDiffuseMapFilename[MAX_STAGES]; + FileName mBaseTexFilename[MAX_STAGES]; + + FileName mOverlayMapFilename[MAX_STAGES]; + FileName mOverlayTexFilename[MAX_STAGES]; + + FileName mLightMapFilename[MAX_STAGES]; + + FileName mToneMapFilename[MAX_STAGES]; + + FileName mDetailMapFilename[MAX_STAGES]; + FileName mDetailTexFilename[MAX_STAGES]; + + FileName mNormalMapFilename[MAX_STAGES]; + FileName mBumpTexFilename[MAX_STAGES]; + FileName mSpecularMapFilename[MAX_STAGES]; + + FileName mEnvMapFilename[MAX_STAGES]; + FileName mEnvTexFilename[MAX_STAGES]; + + StageData mStages[MAX_STAGES]; + + /// This is the color used if there is no diffuse + /// texture map and the alpha value is not zero. + ColorF mDiffuse[MAX_STAGES]; + + ColorF mSpecular[MAX_STAGES]; + + /// This is not really a color multiplication. This does a + /// lerp between the diffuse color/tex and this color based + /// on its alpha channel. + ColorF mColorMultiply[MAX_STAGES]; + + F32 mSpecularPower[MAX_STAGES]; + bool mPixelSpecular[MAX_STAGES]; + + bool mVertLit[MAX_STAGES]; + + F32 mParallaxScale[MAX_STAGES]; + + F32 mMinnaertConstant[MAX_STAGES]; + bool mSubSurface[MAX_STAGES]; + ColorF mSubSurfaceColor[MAX_STAGES]; + F32 mSubSurfaceRolloff[MAX_STAGES]; + + /// The repetition scale of the detail texture + /// over the base texture. + Point2F mDetailScale[MAX_STAGES]; + + // yes this should be U32 - we test for 2 or 4... + U32 mExposure[MAX_STAGES]; + + U32 mAnimFlags[MAX_STAGES]; + Point2F mScrollDir[MAX_STAGES]; + F32 mScrollSpeed[MAX_STAGES]; + Point2F mScrollOffset[MAX_STAGES]; + + F32 mRotSpeed[MAX_STAGES]; + Point2F mRotPivotOffset[MAX_STAGES]; + F32 mRotPos[MAX_STAGES]; + + F32 mWavePos[MAX_STAGES]; + F32 mWaveFreq[MAX_STAGES]; + F32 mWaveAmp[MAX_STAGES]; + U32 mWaveType[MAX_STAGES]; + + F32 mSeqFramePerSec[MAX_STAGES]; + F32 mSeqSegSize[MAX_STAGES]; + + bool mGlow[MAX_STAGES]; // entire stage glows + bool mEmissive[MAX_STAGES]; + + bool mDoubleSided; + + String mCubemapName; + CubemapData* mCubemapData; + bool mDynamicCubemap; + + bool mTranslucent; + BlendOp mTranslucentBlendOp; + bool mTranslucentZWrite; + + /// A generic setting which tells the system to skip + /// generation of shadows from this material. + bool mCastShadows; + + bool mAlphaTest; + U32 mAlphaRef; + + bool mPlanarReflection; + + bool mAutoGenerated; + + ///@{ + /// Behavioral properties. + + bool mShowFootprints; ///< If true, show footprints when walking on surface with this material. Defaults to false. + bool mShowDust; ///< If true, show dust emitters (footpuffs, hover trails, etc) when on surface with this material. Defaults to false. + + /// Color to use for particle effects and such when located on this material. + ColorF mEffectColor[ NUM_EFFECT_COLOR_STAGES ]; + + /// Footstep sound to play when walking on surface with this material. + /// Numeric ID of footstep sound defined on player datablock (0 == soft, + /// 1 == hard, 2 == metal, 3 == snow). + /// Defaults to -1 which deactivates default sound. + /// @see mFootstepSoundCustom + S32 mFootstepSoundId; + S32 mImpactSoundId; + + /// Sound effect to play when walking on surface with this material. + /// If defined, overrides mFootstepSoundId. + /// @see mFootstepSoundCustom + SFXProfile* mFootstepSoundCustom; + SFXProfile* mImpactSoundCustom; + + F32 mFriction; ///< Friction coefficient when moving along surface. + + ///@} + + String mMapTo; // map Material to this texture name + + /// + /// Material interface + /// + Material(); + + /// Allocates and returns a BaseMatInstance for this material. Caller is responsible + /// for freeing the instance + virtual BaseMatInstance* createMatInstance(); + virtual bool isIFL() const { return mIsIFL; } + void setIsIFL(bool isIFL) { mIsIFL = isIFL; } + virtual bool isTranslucent() const { return mTranslucent && mTranslucentBlendOp != Material::None; } + virtual bool isDoubleSided() const { return mDoubleSided; } + virtual bool isAutoGenerated() const { return mAutoGenerated; } + virtual void setAutoGenerated(bool isAutoGenerated) { mAutoGenerated = isAutoGenerated; } + virtual bool isLightmapped() const; + virtual bool castsShadows() const { return mCastShadows; } + const String &getPath() const { return mPath; } + + void flush(); + + /// Re-initializes all the material instances + /// that use this material. + void reload(); + + /// Called to update time based parameters for a material. Ensures + /// that it only happens once per tick. + void updateTimeBasedParams(); + + /// + /// SimObject interface + /// + virtual bool onAdd(); + virtual void onRemove(); + virtual void inspectPostApply(); + + // + // ConsoleObject interface + // + static void initPersistFields(); + + DECLARE_CONOBJECT(Material); +protected: + + // Per material animation parameters + U32 mLastUpdateTime; + + bool mIsIFL; + String mPath; + + static EnumTable mAnimFlagTable; + static EnumTable mBlendOpTable; + static EnumTable mWaveTypeTable; + + /// Map this material to the texture specified + /// in the "mapTo" data variable. + virtual void _mapMaterial(); + +private: + static GFXCubemapHandle smNormalizeCube; +}; + +#endif // _MATERIALDEFINITION_H_ diff --git a/materials/materialFeatureData.cpp b/materials/materialFeatureData.cpp new file mode 100644 index 0000000..5e34ab2 --- /dev/null +++ b/materials/materialFeatureData.cpp @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/materialFeatureData.h" + + +MaterialFeatureData::MaterialFeatureData() +{ +} + +MaterialFeatureData::MaterialFeatureData( const MaterialFeatureData &data ) + : features( data.features ), + materialFeatures( data.materialFeatures ) +{ +} + +MaterialFeatureData::MaterialFeatureData( const FeatureSet &handle ) + : features( handle ) +{ +} + +void MaterialFeatureData::clear() +{ + features.clear(); + materialFeatures.clear(); +} diff --git a/materials/materialFeatureData.h b/materials/materialFeatureData.h new file mode 100644 index 0000000..b1598c7 --- /dev/null +++ b/materials/materialFeatureData.h @@ -0,0 +1,56 @@ +#ifndef _MATERIALFEATUREDATA_H_ +#define _MATERIALFEATUREDATA_H_ + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif +#ifndef _UTIL_DELEGATE_H_ +#include "core/util/delegate.h" +#endif +#ifndef _FEATURESET_H_ +#include "shaderGen/featureSet.h" +#endif + + +class ProcessedMaterial; + + +//----------------------------------------------------------------------------- +// MaterialFeatureData, this is basically a series of flags which a material can +// ask for. Shader processed materials will use the shadergen system to accomplish this, +// FF processed materials will do the best they can. +//----------------------------------------------------------------------------- +struct MaterialFeatureData +{ +public: + + // General feature data for a pass or for other purposes. + FeatureSet features; + + // This is to give hints to shader creation code. It contains + // all the features that are in a material stage instead of just + // the current pass. + FeatureSet materialFeatures; + +public: + + MaterialFeatureData(); + + MaterialFeatureData( const MaterialFeatureData &data ); + + MaterialFeatureData( const FeatureSet &handle ); + + void clear(); + + const FeatureSet& codify() const { return features; } + +}; + + +/// +typedef Delegate< void( ProcessedMaterial *mat, + U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features) > MatFeaturesDelegate; + +#endif // _MATERIALFEATUREDATA_H_ diff --git a/materials/materialFeatureTypes.cpp b/materials/materialFeatureTypes.cpp new file mode 100644 index 0000000..5c1cf0f --- /dev/null +++ b/materials/materialFeatureTypes.cpp @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/materialFeatureTypes.h" + + +ImplementFeatureType( MFT_VertTransform, MFG_Transform, 0, true ); + +ImplementFeatureType( MFT_TexAnim, MFG_PreTexture, 1.0f, true ); +ImplementFeatureType( MFT_Parallax, MFG_PreTexture, 2.0f, true ); + +ImplementFeatureType( MFT_DiffuseMap, MFG_Texture, 2.0f, true ); +ImplementFeatureType( MFT_OverlayMap, MFG_Texture, 3.0f, true ); +ImplementFeatureType( MFT_DetailMap, MFG_Texture, 4.0f, true ); +ImplementFeatureType( MFT_DiffuseColor, MFG_Texture, 5.0f, true ); +ImplementFeatureType( MFT_ColorMultiply, MFG_Texture, 6.0f, true ); +ImplementFeatureType( MFT_AlphaTest, MFG_Texture, 7.0f, true ); +ImplementFeatureType( MFT_SpecularMap, MFG_Texture, 8.0f, true ); + +ImplementFeatureType( MFT_NormalMap, MFG_Lighting, 1.0f, true ); +ImplementFeatureType( MFT_RTLighting, MFG_Lighting, 2.0f, true ); +ImplementFeatureType( MFT_SubSurface, MFG_Lighting, 3.0f, true ); +ImplementFeatureType( MFT_LightMap, MFG_Lighting, 4.0f, true ); +ImplementFeatureType( MFT_ToneMap, MFG_Lighting, 5.0f, true ); +ImplementFeatureType( MFT_VertLitTone, MFG_Lighting, 6.0f, false ); +ImplementFeatureType( MFT_VertLit, MFG_Lighting, 7.0f, true ); +ImplementFeatureType( MFT_EnvMap, MFG_Lighting, 8.0f, true ); +ImplementFeatureType( MFT_CubeMap, MFG_Lighting, 9.0f, true ); +ImplementFeatureType( MFT_PixSpecular, MFG_Lighting, 10.0f, true ); +ImplementFeatureType( MFT_MinnaertShading, MFG_Lighting, 12.0f, true ); + +ImplementFeatureType( MFT_GlowMask, MFG_PostLighting, 1.0f, true ); +ImplementFeatureType( MFT_Visibility, MFG_PostLighting, 2.0f, true ); +ImplementFeatureType( MFT_Fog, MFG_PostProcess, 3.0f, true ); + +ImplementFeatureType( MFT_HDROut, MFG_PostProcess, 999.0f, true ); + +ImplementFeatureType( MFT_IsDXTnm, U32(-1), -1, true ); +ImplementFeatureType( MFT_IsExposureX2, U32(-1), -1, true ); +ImplementFeatureType( MFT_IsExposureX4, U32(-1), -1, true ); +ImplementFeatureType( MFT_IsTranslucent, U32(-1), -1, true ); +ImplementFeatureType( MFT_IsTranslucentZWrite, U32(-1), -1, true ); +ImplementFeatureType( MFT_IsEmissive, U32(-1), -1, true ); +ImplementFeatureType( MFT_GlossMap, U32(-1), -1, true ); + +ImplementFeatureType( MFT_ParaboloidVertTransform, MFG_Transform, -1, false ); +ImplementFeatureType( MFT_IsSinglePassParaboloid, U32(-1), -1, false ); +ImplementFeatureType( MFT_EyeSpaceDepthOut, MFG_PostLighting, 2.0f, false ); +ImplementFeatureType( MFT_DepthOut, MFG_PostLighting, 3.0f, false ); +ImplementFeatureType( MFT_PrePassConditioner, MFG_PostProcess, 1.0f, false ); +ImplementFeatureType( MFT_NormalsOut, MFG_PreLighting, 1.0f, false ); + +ImplementFeatureType( MFT_LightbufferMRT, MFG_PreLighting, 1.0f, false ); +ImplementFeatureType( MFT_RenderTarget1_Zero, MFG_PreTexture, 1.0f, false ); + +ImplementFeatureType( MFT_Foliage, MFG_PreTransform, 1.0f, false ); \ No newline at end of file diff --git a/materials/materialFeatureTypes.h b/materials/materialFeatureTypes.h new file mode 100644 index 0000000..9ca5035 --- /dev/null +++ b/materials/materialFeatureTypes.h @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATERIALFEATURETYPES_H_ +#define _MATERIALFEATURETYPES_H_ + +#ifndef _FEATURETYPE_H_ +#include "shaderGen/featureType.h" +#endif + + +/// +enum MaterialFeatureGroup +{ + /// One or more pre-transform features are + /// allowed at any one time and are executed + /// in order to each other. + MFG_PreTransform, + + /// Only one transform feature is allowed at + /// any one time. + MFG_Transform, + + /// + MFG_PostTransform, + + /// The features that need to occur before texturing + /// takes place. Usually these are features that will + /// manipulate or generate texture coords. + MFG_PreTexture, + + /// The different diffuse color features including + /// textures and colors. + MFG_Texture, + + /// + MFG_PreLighting, + + /// + MFG_Lighting, + + /// + MFG_PostLighting, + + /// Final features like fogging. + MFG_PostProcess, + + /// Miscellaneous features that require no specialized + /// ShaderFeature object and are just queried as flags. + MFG_Misc = -1, +}; + + +/// The standard vertex transform. +DeclareFeatureType( MFT_VertTransform ); + +/// A special transform with paraboloid warp used +/// in shadow and reflection rendering. +DeclareFeatureType( MFT_ParaboloidVertTransform ); + +/// This feature is queried from the MFT_ParaboloidVertTransform +/// feature to detect if it needs to generate a single pass. +DeclareFeatureType( MFT_IsSinglePassParaboloid ); + +/// This feature does normal map decompression for DXT1/5. +DeclareFeatureType( MFT_IsDXTnm ); + +DeclareFeatureType( MFT_TexAnim ); +DeclareFeatureType( MFT_Parallax ); + +DeclareFeatureType( MFT_DiffuseMap ); +DeclareFeatureType( MFT_OverlayMap ); +DeclareFeatureType( MFT_DetailMap ); +DeclareFeatureType( MFT_DiffuseColor ); +DeclareFeatureType( MFT_ColorMultiply ); + +/// This feature is used to do alpha test clipping in +/// the shader which can be faster on SM3 and is needed +/// when the render state alpha test is not available. +DeclareFeatureType( MFT_AlphaTest ); + +/// Stock Basic Lighting features. +//DeclareFeatureType( MFT_DynamicLight ); +//DeclareFeatureType( MFT_DynamicLightDual ); +//DeclareFeatureType( MFT_DynamicLightAttenuateBackFace ); +/* +DeclareFeatureType( MFT_DynamicLightMask ); +DeclareFeatureType( MFT_CustomShadowCoord ); +DeclareFeatureType( MFT_CustomShadowCoord2 ); +DeclareFeatureType( MFT_CustomShadowCompare ); +DeclareFeatureType( MFT_CustomShadowCompare2 ); +DeclareFeatureType( MFT_CustomShadow ); +DeclareFeatureType( MFT_CustomShadow2 ); +*/ + +DeclareFeatureType( MFT_NormalMap ); +DeclareFeatureType( MFT_RTLighting ); +/* +DeclareFeatureType( MFT_CustomLighting ); +DeclareFeatureType( MFT_CustomLighting2 ); +*/ + +DeclareFeatureType( MFT_IsEmissive ); +DeclareFeatureType( MFT_SubSurface ); +DeclareFeatureType( MFT_LightMap ); +DeclareFeatureType( MFT_ToneMap ); +DeclareFeatureType( MFT_VertLit ); +DeclareFeatureType( MFT_VertLitTone ); + +DeclareFeatureType( MFT_IsExposureX2 ); +DeclareFeatureType( MFT_IsExposureX4 ); +DeclareFeatureType( MFT_EnvMap ); +DeclareFeatureType( MFT_CubeMap ); +DeclareFeatureType( MFT_PixSpecular ); +DeclareFeatureType( MFT_SpecularMap ); +DeclareFeatureType( MFT_GlossMap ); + +/// This feature is only used to detect alpha transparency +/// and does not have any code associtated with it. +DeclareFeatureType( MFT_IsTranslucent ); + +/// +DeclareFeatureType( MFT_IsTranslucentZWrite ); + +/// This feature causes MFT_NormalMap to set the world +/// space normal vector to the output color rgb. +DeclareFeatureType( MFT_NormalsOut ); + +DeclareFeatureType( MFT_MinnaertShading ); +DeclareFeatureType( MFT_GlowMask ); +DeclareFeatureType( MFT_Visibility ); +DeclareFeatureType( MFT_EyeSpaceDepthOut ); +DeclareFeatureType( MFT_DepthOut ); +DeclareFeatureType( MFT_Fog ); + +/// This should be the last feature of any material that +/// renders to a HDR render target. It converts the high +/// dynamic range color into the correct HDR encoded format. +DeclareFeatureType( MFT_HDROut ); + +/// +DeclareFeatureType( MFT_PrePassConditioner ); + +/// This feature causes MFT_ToneMap and MFT_LightMap to output their light color +/// to the second render-target +DeclareFeatureType( MFT_LightbufferMRT ); + +/// This feature outputs black to RenderTarget1 +DeclareFeatureType( MFT_RenderTarget1_Zero ); + +DeclareFeatureType( MFT_Foliage ); + + +#endif // _MATERIALFEATURETYPES_H_ diff --git a/materials/materialList.cpp b/materials/materialList.cpp new file mode 100644 index 0000000..78c1d22 --- /dev/null +++ b/materials/materialList.cpp @@ -0,0 +1,475 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/materialList.h" + +#include "materials/matInstance.h" +#include "materials/materialManager.h" +#include "materials/materialFeatureTypes.h" +#include "core/volume.h" +#include "console/simSet.h" + + +MaterialList::MaterialList() +{ + VECTOR_SET_ASSOCIATION(mMaterialNames); + VECTOR_SET_ASSOCIATION(mMaterials); +} + +MaterialList::MaterialList(const MaterialList* pCopy) +{ + VECTOR_SET_ASSOCIATION(mMaterialNames); + VECTOR_SET_ASSOCIATION(mMaterials); + + mMaterialNames.setSize(pCopy->mMaterialNames.size()); + S32 i; + for (i = 0; i < mMaterialNames.size(); i++) + { + mMaterialNames[i] = pCopy->mMaterialNames[i]; + } + + mMaterials.setSize(pCopy->mMaterials.size()); + for (i = 0; i < mMaterials.size(); i++) { + constructInPlace(&mMaterials[i]); + mMaterials[i] = pCopy->mMaterials[i]; + } + + clearMatInstList(); + mMatInstList.setSize(pCopy->mMaterials.size()); + for( i = 0; i < mMatInstList.size(); i++ ) + { + if( i < pCopy->mMatInstList.size() && pCopy->mMatInstList[i] ) + { + mMatInstList[i] = pCopy->mMatInstList[i]->getMaterial()->createMatInstance(); + } + else + { + mMatInstList[i] = NULL; + } + } + +} + + + +MaterialList::MaterialList(U32 materialCount, const char **materialNames) +{ + VECTOR_SET_ASSOCIATION(mMaterialNames); + VECTOR_SET_ASSOCIATION(mMaterials); + + set(materialCount, materialNames); +} + + +//-------------------------------------- +void MaterialList::set(U32 materialCount, const char **materialNames) +{ + free(); + mMaterials.setSize(materialCount); + mMaterialNames.setSize(materialCount); + clearMatInstList(); + mMatInstList.setSize(materialCount); + for(U32 i = 0; i < materialCount; i++) + { + // vectors DO NOT initialize classes so manually call the constructor + constructInPlace(&mMaterials[i]); + mMaterialNames[i] = materialNames[i]; + mMatInstList[i] = NULL; + } +} + + +//-------------------------------------- +MaterialList::~MaterialList() +{ + free(); +} + +//-------------------------------------- +void MaterialList::setMaterialName(U32 index, String name) +{ + if (index < mMaterialNames.size()) + mMaterialNames[index] = name; +} + + +//-------------------------------------- +void MaterialList::load(U32 index, const String &path) +{ + AssertFatal(index < size(), "MaterialList:: index out of range."); + if (index < size()) + { + GFXTexHandle &handle = mMaterials[index]; + if (handle.isNull()) + { + const String &name = mMaterialNames[index]; + if (name.isNotEmpty()) + { + if (path.isNotEmpty()) + { + // Should we strip off the extension of the path here before trying + // to load the texture? + String fullPath = String::ToString("%s/%s", path.c_str(), name.c_str()); + handle.set(fullPath, &GFXDefaultStaticDiffuseProfile, avar("%s() - handle (line %d)", __FUNCTION__, __LINE__)); + } + else + { + handle.set(name, &GFXDefaultStaticDiffuseProfile, avar("%s() - handle (line %d)", __FUNCTION__, __LINE__)); + } + } + } + } +} + + +//-------------------------------------- +bool MaterialList::load(const String &path) +{ + AssertFatal(mMaterials.size() == mMaterials.size(), "MaterialList::load: internal vectors out of sync."); + + for(S32 i=0; i < mMaterials.size(); i++) + load(i,path); + + for(S32 i=0; i < mMaterials.size(); i++) + { + // TSMaterialList nulls out the names of IFL materials, so + // we need to ignore empty names. + const String &name = mMaterialNames[i]; + if (name.isNotEmpty() && !mMaterials[i]) + return false; + } + return true; +} + +//-------------------------------------- +void MaterialList::free() +{ + AssertFatal(mMaterials.size() == mMaterialNames.size(), "MaterialList::free: internal vectors out of sync."); + for(S32 i=0; i < mMaterials.size(); i++) + { + mMaterialNames[i] = String(); + mMaterials[i] = NULL; + } + clearMatInstList(); + mMatInstList.setSize(0); + mMaterialNames.setSize(0); + mMaterials.setSize(0); +} + + +//-------------------------------------- +U32 MaterialList::push_back(GFXTexHandle textureHandle, const String &filename) +{ + mMaterials.increment(); + mMaterialNames.increment(); + mMatInstList.push_back(NULL); + + // vectors DO NOT initialize classes so manually call the constructor + constructInPlace(&mMaterials.last()); + mMaterials.last() = textureHandle; + mMaterialNames.last() = filename; + + // return the index + return mMaterials.size()-1; +} + +//-------------------------------------- +U32 MaterialList::push_back(const String &filename, Material* material) +{ + mMaterials.increment(); + mMaterialNames.increment(); + if (material) + mMatInstList.push_back(material->createMatInstance()); + else + mMatInstList.push_back(NULL); + + // vectors DO NOT initialize classes so manually call the constructor + constructInPlace(&mMaterials.last()); + mMaterialNames.last() = filename; + + // return the index + return mMaterials.size()-1; +} + +//-------------------------------------- +bool MaterialList::read(Stream &stream) +{ + free(); + + // check the stream version + U8 version; + if ( stream.read(&version) && version != BINARY_FILE_VERSION) + return readText(stream,version); + + // how many materials? + U32 count; + if ( !stream.read(&count) ) + return false; + + // pre-size the vectors for efficiency + mMaterials.reserve(count); + mMaterialNames.reserve(count); + + // read in the materials + for (U32 i=0; inot be initialized. + +void MaterialList::mapMaterial( U32 i ) +{ + AssertFatal( i < mMaterials.size(), "MaterialList::mapMaterialList - index out of bounds" ); + + if( mMatInstList[i] != NULL ) + return; + + // lookup a material property entry + const String &matName = getMaterialName(i); + + // JMQ: this code assumes that all materials have names, but ifls don't (they are nuked by tsmateriallist loader code). what to do? + if( matName.isEmpty() ) + { + mMatInstList[i] = NULL; + return; + } + + String materialName = MATMGR->getMapEntry(matName); + + // IF we didn't find it, then look for a PolyStatic generated Material + // [a little cheesy, but we need to allow for user override of generated Materials] + if ( materialName.isEmpty() ) + materialName = MATMGR->getMapEntry(String::ToString( "polyMat_%s", matName.c_str()) ); + + if(materialName.isNotEmpty()) + { + Material * mat = MATMGR->getMaterialDefinitionByName(materialName); + if( mat ) + mMatInstList[i] = mat->createMatInstance(); + else + mMatInstList[i] = MATMGR->createWarningMatInstance(); + } + else + { + if( mMaterials[i].isValid() ) + { + if (Con::getBoolVariable("$Materials::createMissing", true)) + { + // No Material found, create new "default" material with just a diffuseMap + //static U32 defaultMatCount = 0; + String newMatName = Sim::getUniqueName( "DefaultMaterial" ); + + Material *newMat = MATMGR->allocateAndRegister( newMatName, mMaterialNames[i] ); + + // Flag this as an autogenerated Material + newMat->mAutoGenerated = true; + + // Overwrite diffuseMap in new material - hackish, but works + newMat->mStages[0].setTex( MFT_DiffuseMap, mMaterials[i] ); + newMat->mDiffuseMapFilename[0] = mMaterials[i]->mTextureLookupName; + + // Set up some defaults for transparent textures + if (mMaterials[i]->mHasTransparency) + { + newMat->mTranslucent = true; + newMat->mTranslucentBlendOp = Material::LerpAlpha; + newMat->mTranslucentZWrite = true; + newMat->mAlphaRef = 20; + } + + // create a MatInstance for the new material + mMatInstList[i] = newMat->createMatInstance(); + +#ifndef TORQUE_SHIPPING + Con::warnf("[MaterialList::mapMaterials] Creating missing material for texture: %s", mMaterials[i]->mTextureLookupName.c_str() ); +#endif + } + else + { + Con::errorf("[MaterialList::mapMaterials] Unable to find material for texture: %s", mMaterials[i]->mTextureLookupName.c_str() ); + mMatInstList[i] = MATMGR->createWarningMatInstance(); + } + } + else + { + mMatInstList[i] = MATMGR->createWarningMatInstance(); + } + } +} + +void MaterialList::initMatInstances( const FeatureSet &features, + const GFXVertexFormat *vertexFormat ) +{ + mFeatures = features; + mVertexFormat = vertexFormat; + + for( U32 i=0; iinit( mFeatures, mVertexFormat ); + } +} + +//-------------------------------------------------------------------------- +// Get material instance +//-------------------------------------------------------------------------- +BaseMatInstance * MaterialList::getMaterialInst( U32 texIndex ) const +{ + if ( mMatInstList.size() == 0 ) + return NULL; + + return mMatInstList[texIndex]; +} + +//-------------------------------------------------------------------------- +// Set material instance +//-------------------------------------------------------------------------- +void MaterialList::setMaterialInst( BaseMatInstance *matInst, U32 texIndex ) +{ + if( texIndex >= mMatInstList.size() ) + { + AssertFatal( false, "Index out of range" ); + } + mMatInstList[texIndex] = matInst; +} + diff --git a/materials/materialList.h b/materials/materialList.h new file mode 100644 index 0000000..d2e53d2 --- /dev/null +++ b/materials/materialList.h @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATERIALLIST_H_ +#define _MATERIALLIST_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _MATERIALFEATUREDATA_H_ +#include "materials/materialFeatureData.h" +#endif + +class Material; +class BaseMatInstance; +class Stream; +class GFXVertexFormat; + + +class MaterialList +{ +public: + MaterialList(); + MaterialList(U32 materialCount, const char **materialNames); + virtual ~MaterialList(); + + /// Note: this is not to be confused with MaterialList(const MaterialList&). Copying + /// a material list in the middle of it's lifetime is not a good thing, so we force + /// it to copy at construction time by restricting the copy syntax to + /// ML* pML = new ML(©); + explicit MaterialList(const MaterialList*); + + U32 getMaterialCount() const { return (U32)mMaterials.size(); } + + const Vector &getMaterialNameList() const { return mMaterialNames; } + const String &getMaterialName(U32 index) const { return mMaterialNames[index]; } + GFXTexHandle &getMaterial(U32 index) + { + AssertFatal(index < (U32) mMaterials.size(), "MaterialList::getMaterial: index lookup out of range."); + return mMaterials[index]; + } + + void setMaterialName(U32 index, String name); + + void set(U32 materialCount, const char **materialNames); + U32 push_back(GFXTexHandle textureHandle, const String &filename); + U32 push_back(const String &filename, Material* = 0); + + void load(U32 index, const String &path); + bool load(const String &path); + virtual void free(); + void clearMatInstList(); + + typedef Vector::iterator iterator; + typedef Vector::value_type value; + + GFXTexHandle& front() { return mMaterials.front(); } + GFXTexHandle& first() { return mMaterials.first(); } + GFXTexHandle& last() { return mMaterials.last(); } + bool empty() const { return mMaterials.empty(); } + U32 size() const { return (U32)mMaterials.size(); } + iterator begin() { return mMaterials.begin(); } + iterator end() { return mMaterials.end(); } + value operator[] (S32 index) { return getMaterial(U32(index)); } + + bool read(Stream &stream); + bool write(Stream &stream); + + bool readText(Stream &stream, U8 firstByte); + bool readText(Stream &stream); + bool writeText(Stream &stream); + + void mapMaterials(); + + /// Initialize material instances in material list. + void initMatInstances( const FeatureSet &features, + const GFXVertexFormat *vertexFormat ); + + BaseMatInstance * getMaterialInst( U32 texIndex ) const; + void setMaterialInst( BaseMatInstance *matInst, U32 texIndex ); + + // Needs to be accessible if were going to freely edit instances + Vector mMatInstList; + +protected: + Vector mMaterialNames; + Vector mMaterials; + + + + FeatureSet mFeatures; + + /// The vertex format on which the materials will render. + const GFXVertexFormat *mVertexFormat; + + virtual void mapMaterial( U32 index ); + +private: + enum Constants { BINARY_FILE_VERSION = 1 }; +}; + +#endif diff --git a/materials/materialManager.cpp b/materials/materialManager.cpp new file mode 100644 index 0000000..0abd146 --- /dev/null +++ b/materials/materialManager.cpp @@ -0,0 +1,401 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/materialManager.h" + +#include "materials/matInstance.h" +#include "materials/materialFeatureTypes.h" +#include "lighting/lightManager.h" +#include "core/util/safeDelete.h" +#include "shaderGen/shaderGen.h" + + +MaterialManager::MaterialManager() +{ + VECTOR_SET_ASSOCIATION( mMatInstanceList ); + + mDt = 0.0f; + mAccumTime = 0.0f; + mLastTime = 0; + mWarningInst = NULL; + + GFXDevice::getDeviceEventSignal().notify( this, &MaterialManager::_handleGFXEvent ); + + // Make sure we get activation signals + // and that we're the last to get them. + LightManager::smActivateSignal.notify( this, &MaterialManager::_onLMActivate, 9999 ); + + mMaterialSet = NULL; + + mUsingPrePass = false; +} + +MaterialManager::~MaterialManager() +{ + GFXDevice::getDeviceEventSignal().remove( this, &MaterialManager::_handleGFXEvent ); + LightManager::smActivateSignal.remove( this, &MaterialManager::_onLMActivate ); + + SAFE_DELETE( mWarningInst ); + +#ifndef TORQUE_SHIPPING + DebugMaterialMap::Iterator itr = mMeshDebugMaterialInsts.begin(); + + for ( ; itr != mMeshDebugMaterialInsts.end(); itr++ ) + delete itr->value; +#endif +} + +void MaterialManager::_onLMActivate( const char *lm, bool activate ) +{ + if ( !activate ) + return; + + // Since the light manager usually swaps shadergen features + // and changes system wide shader defines we need to completely + // flush and rebuild all the material instances. + + flushAndReInitInstances(); +} + +Material * MaterialManager::allocateAndRegister(const String &objectName, const String &mapToName) +{ + Material *newMat = new Material(); + + if ( mapToName.isNotEmpty() ) + newMat->mMapTo = mapToName; + + bool registered = newMat->registerObject(objectName ); + AssertFatal( registered, "Unable to register material" ); + + if (registered) + Sim::getRootGroup()->addObject( newMat ); + else + { + delete newMat; + newMat = NULL; + } + + return newMat; +} + +Material * MaterialManager::getMaterialDefinitionByName(const String &matName) +{ + // Get the material + Material * foundMat; + + if(!Sim::findObject(matName, foundMat)) + { + Con::errorf("MaterialManager: Unable to find material '%s'", matName.c_str()); + return NULL; + } + + return foundMat; +} + +BaseMatInstance* MaterialManager::createMatInstance(const String &matName) +{ + BaseMaterialDefinition* mat = NULL; + if (Sim::findObject(matName, mat)) + return mat->createMatInstance(); + + return NULL; +} + +BaseMatInstance* MaterialManager::createMatInstance( const String &matName, + const GFXVertexFormat *vertexFormat ) +{ + return createMatInstance( matName, getDefaultFeatures(), vertexFormat ); +} + +BaseMatInstance* MaterialManager::createMatInstance( const String &matName, + const FeatureSet& features, + const GFXVertexFormat *vertexFormat ) +{ + BaseMatInstance* mat = createMatInstance(matName); + if (mat) + { + mat->init( features, vertexFormat ); + return mat; + } + + return NULL; +} + +BaseMatInstance * MaterialManager::createWarningMatInstance() +{ + Material *warnMat = static_cast(Sim::findObject("WarningMaterial")); + + BaseMatInstance *warnMatInstance = NULL; + + if( warnMat != NULL ) + { + warnMatInstance = warnMat->createMatInstance(); + + GFXStateBlockDesc desc; + desc.setCullMode(GFXCullNone); + warnMatInstance->addStateBlockDesc(desc); + + warnMatInstance->init( getDefaultFeatures(), + getGFXVertexFormat() ); + } + + return warnMatInstance; +} + +// Gets the global warning material instance, callers should not free this copy +BaseMatInstance * MaterialManager::getWarningMatInstance() +{ + if (!mWarningInst) + mWarningInst = createWarningMatInstance(); + + return mWarningInst; +} + +#ifndef TORQUE_SHIPPING +BaseMatInstance * MaterialManager::createMeshDebugMatInstance(const ColorF &meshColor) +{ + String meshDebugStr = String::ToString( "Torque_MeshDebug_%d", meshColor.getRGBAPack() ); + + Material *debugMat; + if (!Sim::findObject(meshDebugStr,debugMat)) + { + debugMat = allocateAndRegister( meshDebugStr ); + + debugMat->mDiffuse[0] = meshColor; + debugMat->mEmissive[0] = true; + } + + BaseMatInstance *debugMatInstance = NULL; + + if( debugMat != NULL ) + { + debugMatInstance = debugMat->createMatInstance(); + + GFXStateBlockDesc desc; + desc.setCullMode(GFXCullNone); + desc.fillMode = GFXFillWireframe; + debugMatInstance->addStateBlockDesc(desc); + + // Disable fog and other stuff. + FeatureSet debugFeatures; + debugFeatures.addFeature( MFT_DiffuseColor ); + debugMatInstance->init( debugFeatures, getGFXVertexFormat() ); + } + + return debugMatInstance; +} + +// Gets the global material instance for a given color, callers should not free this copy +BaseMatInstance *MaterialManager::getMeshDebugMatInstance(const ColorF &meshColor) +{ + DebugMaterialMap::Iterator itr = mMeshDebugMaterialInsts.find( meshColor.getRGBAPack() ); + + BaseMatInstance *inst = NULL; + + if ( itr == mMeshDebugMaterialInsts.end() ) + inst = createMeshDebugMatInstance( meshColor ); + else + inst = itr->value; + + mMeshDebugMaterialInsts.insert( meshColor.getRGBAPack(), inst ); + + return inst; +} +#endif + +void MaterialManager::mapMaterial(const String & textureName, const String & materialName) +{ + if (getMapEntry(textureName).isNotEmpty()) + { + if (!textureName.equal("unmapped_mat", String::NoCase)) + Con::warnf(ConsoleLogEntry::General, "Warning, overwriting material for: %s", textureName.c_str()); + } + + mMaterialMap[String::ToLower(textureName)] = materialName; +} + +String MaterialManager::getMapEntry(const String & textureName) const +{ + MaterialMap::ConstIterator iter = mMaterialMap.find(String::ToLower(textureName)); + if ( iter == mMaterialMap.end() ) + return String(); + return iter->value; +} + +void MaterialManager::flushAndReInitInstances() +{ + // First we flush all the shader gen shaders which will + // invalidate all GFXShader* to them. + SHADERGEN->flushProceduralShaders(); + + // First do a pass deleting all hooks as they can contain + // materials themselves. This means we have to restart the + // loop every time we delete any hooks... lame. + Vector::iterator iter = mMatInstanceList.begin(); + while ( iter != mMatInstanceList.end() ) + { + if ( (*iter)->deleteAllHooks() != 0 ) + { + // Restart the loop. + iter = mMatInstanceList.begin(); + continue; + } + + iter++; + } + + // Now do a pass re-initializing materials. + iter = mMatInstanceList.begin(); + for ( ; iter != mMatInstanceList.end(); iter++ ) + (*iter)->reInit(); +} + +// Used in the materialEditor. This flushes the material preview object so it can be reloaded easily. +void MaterialManager::flushInstance( BaseMaterialDefinition *target ) +{ + Vector::iterator iter = mMatInstanceList.begin(); + while ( iter != mMatInstanceList.end() ) + { + if ( (*iter)->getMaterial() == target ) + { + (*iter)->deleteAllHooks(); + return; + } + iter++; + } +} + +void MaterialManager::reInitInstance( BaseMaterialDefinition *target ) +{ + Vector::iterator iter = mMatInstanceList.begin(); + for ( ; iter != mMatInstanceList.end(); iter++ ) + { + if ( (*iter)->getMaterial() == target ) + (*iter)->reInit(); + } +} + +void MaterialManager::updateTime() +{ + U32 curTime = Sim::getCurrentTime(); + if(curTime > mLastTime) + { + mDt = (curTime - mLastTime) / 1000.0f; + mLastTime = curTime; + mAccumTime += mDt; + } + else + mDt = 0.0f; +} + +SimSet * MaterialManager::getMaterialSet() +{ + if(!mMaterialSet) + mMaterialSet = static_cast( Sim::findObject( "MaterialSet" ) ); + + AssertFatal( mMaterialSet, "MaterialSet not found" ); + return mMaterialSet; +} + +void MaterialManager::dumpMaterialInstances( BaseMaterialDefinition *target ) const +{ + if ( !mMatInstanceList.size() ) + return; + + if ( target ) + Con::printf( "--------------------- %s MatInstances ---------------------", target->getName() ); + else + Con::printf( "--------------------- MatInstances %d ---------------------", mMatInstanceList.size() ); + + for( U32 i=0; igetMaterial() != target ) + continue; + + inst->dumpShaderInfo(); + } + + Con::printf( "---------------------- Dump complete ----------------------"); +} + +void MaterialManager::_track( MatInstance *matInstance ) +{ + mMatInstanceList.push_back( matInstance ); +} + +void MaterialManager::_untrack( MatInstance *matInstance ) +{ + mMatInstanceList.remove( matInstance ); +} + +void MaterialManager::recalcFeaturesFromPrefs() +{ + mDefaultFeatures.clear(); + FeatureType::addDefaultTypes( &mDefaultFeatures ); + + mExclusionFeatures.setFeature( MFT_NormalMap, + Con::getBoolVariable( "$pref::Video::disableNormalmapping", false ) ); + + mExclusionFeatures.setFeature( MFT_PixSpecular, + Con::getBoolVariable( "$pref::Video::disablePixSpecular", false ) ); + + mExclusionFeatures.setFeature( MFT_CubeMap, + Con::getBoolVariable( "$pref::Video::disableCubemapping", false ) ); +} + +bool MaterialManager::_handleGFXEvent(GFXDevice::GFXDeviceEventType event) +{ + switch (event) + { + case GFXDevice::deInit : + recalcFeaturesFromPrefs(); + break; + case GFXDevice::deDestroy : + SAFE_DELETE(mWarningInst); + break; + default : + break; + } + return true; +} + +ConsoleFunction( reInitMaterials, void, 1, 1, + "Flushes all the procedural shaders and re-initializes all the active materials instances." ) +{ + MATMGR->flushAndReInitInstances(); +} + +ConsoleFunction( addMaterialMapping, void, 3, 3, "(string texName, string matName)\n" + "Set up a material to texture mapping.") +{ + MATMGR->mapMaterial(argv[1],argv[2]); +} + +ConsoleFunction( recalcFeaturesFromPrefs, void, 1, 1, + "Enables/disable shader features based on pref settings.") +{ + MATMGR->recalcFeaturesFromPrefs(); +} + +ConsoleFunction( getMaterialMapping, const char*, 2, 2, "(string texName)\n" + "Gets the name of the material mapped to this texture.") +{ + return MATMGR->getMapEntry(argv[1]).c_str(); +} + +ConsoleFunction( dumpMaterialInstances, void, 1, 1, + "Dumps a formatted list of currently allocated material instances to the console." ) +{ + MATMGR->dumpMaterialInstances(); +} + +ConsoleFunction( getMapEntry, const char *, 2, 2, + "getMapEntry( String ) Returns the material name via the materialList mapTo entry" ) +{ + return MATMGR->getMapEntry( String(argv[1]) ); +} \ No newline at end of file diff --git a/materials/materialManager.h b/materials/materialManager.h new file mode 100644 index 0000000..e8538a9 --- /dev/null +++ b/materials/materialManager.h @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATERIAL_MGR_H_ +#define _MATERIAL_MGR_H_ + +#ifndef _MATERIALDEFINITION_H_ +#include "materials/materialDefinition.h" +#endif +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif + +class SimSet; +class MatInstance; + +class MaterialManager : public ManagedSingleton +{ +public: + MaterialManager(); + ~MaterialManager(); + + // ManagedSingleton + static const char* getSingletonName() { return "MaterialManager"; } + + Material * allocateAndRegister(const String &objectName, const String &mapToName = String()); + Material * getMaterialDefinitionByName(const String &matName); + SimSet * getMaterialSet(); + + // map textures to materials + void mapMaterial(const String & textureName, const String & materialName); + String getMapEntry(const String & textureName) const; + + // Return instance of named material caller is responsible for memory + BaseMatInstance * createMatInstance( const String &matName ); + + // Create a BaseMatInstance with the default feature flags. + BaseMatInstance * createMatInstance( const String &matName, const GFXVertexFormat *vertexFormat ); + BaseMatInstance * createMatInstance( const String &matName, const FeatureSet &features, const GFXVertexFormat *vertexFormat ); + + /// The default feature set for materials. + const FeatureSet& getDefaultFeatures() const { return mDefaultFeatures; } + + /// The feature exclusion list for disabling features. + const FeatureSet& getExclusionFeatures() const { return mExclusionFeatures; } + + void recalcFeaturesFromPrefs(); + + /// Allocate and return an instance of special materials. Caller is responsible for the memory. + BaseMatInstance * createWarningMatInstance(); + + /// Gets the global warning material instance, callers should not free this copy + BaseMatInstance * getWarningMatInstance(); + + /// Set the prepass enabled state. + void setPrePassEnabled( bool enabled ) { mUsingPrePass = enabled; } + + /// Get the prepass enabled state. + bool getPrePassEnabled() const { return mUsingPrePass; } + +#ifndef TORQUE_SHIPPING + + // Allocate and return an instance of mesh debugging materials. Caller is responsible for the memory. + BaseMatInstance * createMeshDebugMatInstance(const ColorF &meshColor); + + // Gets the global material instance for a given color, callers should not free this copy + BaseMatInstance * getMeshDebugMatInstance(const ColorF &meshColor); + +#endif + + void dumpMaterialInstances( BaseMaterialDefinition *target = NULL ) const; + + void updateTime(); + F32 getTotalTime() const { return mAccumTime; } + F32 getDeltaTime() const { return mDt; } + U32 getLastUpdateTime() const { return mLastTime; } + + /// Flushes all the procedural shaders and re-initializes all + /// the active materials instances. + void flushAndReInitInstances(); + + // Flush the instance + void flushInstance( BaseMaterialDefinition *target ); + /// Re-initializes the material instances for a specific target material. + void reInitInstance( BaseMaterialDefinition *target ); + +protected: + + // MatInstance tracks it's instances here + friend class MatInstance; + void _track(MatInstance*); + void _untrack(MatInstance*); + + /// @see LightManager::smActivateSignal + void _onLMActivate( const char *lm, bool activate ); + + bool _handleGFXEvent(GFXDevice::GFXDeviceEventType event); + + SimSet* mMaterialSet; + Vector mMatInstanceList; + + /// The default material features. + FeatureSet mDefaultFeatures; + + /// The feature exclusion set. + FeatureSet mExclusionFeatures; + + // material map + typedef Map MaterialMap; + MaterialMap mMaterialMap; + + bool mUsingPrePass; + + // time tracking + F32 mDt; + F32 mAccumTime; + U32 mLastTime; + + BaseMatInstance* mWarningInst; + +#ifndef TORQUE_SHIPPING + typedef Map DebugMaterialMap; + DebugMaterialMap mMeshDebugMaterialInsts; +#endif + +}; + +/// Helper for accessing MaterialManager singleton. +#define MATMGR MaterialManager::instance() + +#endif // _MATERIAL_MGR_H_ diff --git a/materials/materialParameters.h b/materials/materialParameters.h new file mode 100644 index 0000000..334b365 --- /dev/null +++ b/materials/materialParameters.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATERIALPARAMETERS_H_ +#define _MATERIALPARAMETERS_H_ + +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif + +/// +/// Similar class to GFXShaderConsts, but this is to describe generic material parameters. +/// +class MaterialParameterHandle +{ +public: + virtual ~MaterialParameterHandle() {} + virtual const String& getName() const = 0; + // This is similar to GFXShaderConstHandle, but a Material will always return a handle when + // asked for one. Even if it doesn't have that material parameter. This is because after a + // reInitMaterials call, the constants can change underneath the MatInstance. + // Note: GFXShaderConstHandle actually works this way too, now. + virtual bool isValid() const = 0; + /// Returns -1 if this handle does not point to a Sampler. + virtual S32 getSamplerRegister( U32 pass ) const = 0; +}; + +class MaterialParameters +{ +public: + MaterialParameters() + { + VECTOR_SET_ASSOCIATION( mShaderConstDesc ); + } + virtual ~MaterialParameters() {} + + /// Returns our list of shader constants, the material can get this and just set the constants it knows about + virtual const Vector& getShaderConstDesc() const { return mShaderConstDesc; } + + /// @name Set shader constant values + /// @{ + /// Actually set shader constant values + /// @param name Name of the constant, this should be a name contained in the array returned in getShaderConstDesc, + /// if an invalid name is used, it is ignored. + virtual void set(MaterialParameterHandle* handle, const F32 f) {} + virtual void set(MaterialParameterHandle* handle, const Point2F& fv) {} + virtual void set(MaterialParameterHandle* handle, const Point3F& fv) {} + virtual void set(MaterialParameterHandle* handle, const Point4F& fv) {} + virtual void set(MaterialParameterHandle* handle, const ColorF& fv) {} + virtual void set(MaterialParameterHandle* handle, const S32 f) {} + virtual void set(MaterialParameterHandle* handle, const Point2I& fv) {} + virtual void set(MaterialParameterHandle* handle, const Point3I& fv) {} + virtual void set(MaterialParameterHandle* handle, const Point4I& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv) {} + virtual void set(MaterialParameterHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType = GFXSCT_Float4x4) {} + virtual void set(MaterialParameterHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType = GFXSCT_Float4x4) {} + + /// Returns the alignment value for the constType in bytes. + virtual U32 getAlignmentValue(const GFXShaderConstType constType) { return 0; } + +protected: + Vector mShaderConstDesc; +}; + +#endif diff --git a/materials/miscShdrDat.h b/materials/miscShdrDat.h new file mode 100644 index 0000000..5e76d75 --- /dev/null +++ b/materials/miscShdrDat.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MISCSHDRDAT_H_ +#define _MISCSHDRDAT_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +//************************************************************************** +// This file is an attempt to keep certain classes from having to know about +// the ShaderGen class +//************************************************************************** + + +//----------------------------------------------------------------------- +// Enums +//----------------------------------------------------------------------- +enum RegisterType +{ + RT_POSITION = 0, + RT_NORMAL, + RT_BINORMAL, + RT_TANGENT, + RT_COLOR, + RT_TEXCOORD, +}; + +enum Components +{ + C_VERT_STRUCT = 0, + C_CONNECTOR, + C_VERT_MAIN, + C_PIX_MAIN, +}; + +#endif // _MISCSHDRDAT_H_ diff --git a/materials/processedCustomMaterial.cpp b/materials/processedCustomMaterial.cpp new file mode 100644 index 0000000..8af1129 --- /dev/null +++ b/materials/processedCustomMaterial.cpp @@ -0,0 +1,487 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/processedCustomMaterial.h" + +#include "gfx/sim/cubemapData.h" +#include "materials/sceneData.h" +#include "shaderGen/shaderGenVars.h" +#include "sceneGraph/sceneState.h" +#include "materials/customMaterialDefinition.h" +#include "materials/shaderData.h" +#include "materials/materialManager.h" +#include "materials/matTextureTarget.h" +#include "materials/materialFeatureTypes.h" +#include "gfx/sim/gfxStateBlockData.h" +#include "core/util/safeDelete.h" +#include "gfx/genericConstBuffer.h" +#include "console/simFieldDictionary.h" +#include "console/propertyParsing.h" +#include "gfx/util/screenspace.h" + + +ProcessedCustomMaterial::ProcessedCustomMaterial(Material &mat) +{ + mMaterial = &mat; + AssertFatal(dynamic_cast(mMaterial), "Incompatible Material type!"); + mCustomMaterial = static_cast(mMaterial); + mHasSetStageData = false; + mHasGlow = false; + mMaxStages = 0; + mMaxTex = 0; +} + +ProcessedCustomMaterial::~ProcessedCustomMaterial() +{ +} + +void ProcessedCustomMaterial::_setStageData() +{ + // Only do this once + if ( mHasSetStageData ) + return; + mHasSetStageData = true; + + ShaderRenderPassData* rpd = _getRPD(0); + mConditionerMacros.clear(); + + // Loop through all the possible textures, set the right flags, and load them if needed + for(U32 i=0; imTexType[i] = Material::NoTexture; // Set none as the default in case none of the cases below catch it. + String filename = mCustomMaterial->mTexFilename[i]; + + if(filename.isEmpty()) + continue; + + if(filename.equal(String("$dynamiclight"), String::NoCase)) + { + rpd->mTexType[i] = Material::DynamicLight; + mMaxTex = i+1; + continue; + } + + if(filename.equal(String("$dynamiclightmask"), String::NoCase)) + { + rpd->mTexType[i] = Material::DynamicLightMask; + mMaxTex = i+1; + continue; + } + + if(filename.equal(String("$lightmap"), String::NoCase)) + { + rpd->mTexType[i] = Material::Lightmap; + mMaxTex = i+1; + continue; + } + + if(filename.equal(String("$cubemap"), String::NoCase)) + { + if( mCustomMaterial->mCubemapData ) + { + rpd->mTexType[i] = Material::Cube; + mMaxTex = i+1; + } + else + { + mCustomMaterial->logError( "Could not find CubemapData - %s", mCustomMaterial->mCubemapName.c_str()); + } + continue; + } + + if(filename.equal(String("$dynamicCubemap"), String::NoCase)) + { + rpd->mTexType[i] = Material::SGCube; + mMaxTex = i+1; + continue; + } + + if(filename.equal(String("$backbuff"), String::NoCase)) + { + rpd->mTexType[i] = Material::BackBuff; + mMaxTex = i+1; + continue; + } + + if(filename.equal(String("$reflectbuff"), String::NoCase)) + { + rpd->mTexType[i] = Material::ReflectBuff; + mMaxTex = i+1; + continue; + } + + if(filename.equal(String("$miscbuff"), String::NoCase)) + { + rpd->mTexType[i] = Material::Misc; + mMaxTex = i+1; + continue; + } + + // Check for a RenderTexTargetBin assignment + if (filename.substr( 0, 1 ).equal("#")) + { + String texTargetBufferName = filename.substr(1, filename.length() - 1); + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( texTargetBufferName ); + rpd->mTexSlot[i].texTarget = texTarget; + + // Get the conditioner macros. + if ( texTarget ) + texTarget->getTargetShaderMacros( &mConditionerMacros ); + + rpd->mTexType[i] = Material::TexTarget; + mMaxTex = i+1; + continue; + } + + rpd->mTexSlot[i].texObject = _createTexture( filename, &GFXDefaultStaticDiffuseProfile ); + if ( !rpd->mTexSlot[i].texObject ) + { + mMaterial->logError("Failed to load texture %s", _getTexturePath(filename).c_str()); + continue; + } + rpd->mTexType[i] = Material::Standard; + mMaxTex = i+1; + } + + // We only get one cubemap + if( mCustomMaterial->mCubemapData ) + { + mCustomMaterial->mCubemapData->createMap(); + rpd->mCubeMap = mMaterial->mCubemapData->mCubemap; // BTRTODO ? + if ( !rpd->mCubeMap ) + mMaterial->logError("Failed to load cubemap"); + } + + // If this has a output target defined, it may be writing + // to a tex target bin with a conditioner, so search for + // one and add its macros. + if ( mCustomMaterial->mOutputTarget.isNotEmpty() ) + { + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( mCustomMaterial->mOutputTarget ); + if ( texTarget ) + texTarget->getTargetShaderMacros( &mConditionerMacros ); + } + + // Copy the glow state over. + mHasGlow = mCustomMaterial->mGlow[0]; +} + +bool ProcessedCustomMaterial::init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat, + const MatFeaturesDelegate &featuresDelegate ) +{ + // If we don't have a shader data... we have nothing to do. + if ( !mCustomMaterial->mShaderData ) + return true; + + // Custom materials only do one pass at the moment... so + // add one for the stage data to fill in. + ShaderRenderPassData *rpd = new ShaderRenderPassData(); + mPasses.push_back( rpd ); + + _setStageData(); + _initPassStateBlocks(); + + // Note: We don't use the vertex format in a custom + // material at all right now. + // + // Maybe we can add some required semantics and + // validate that the format fits the shader? + + // Build a composite list of shader macros from + // the conditioner and the user defined lists. + Vector macros; + macros.merge( mConditionerMacros ); + macros.merge( mUserMacros ); + + // Ask the shader data to give us a shader instance. + rpd->shader = mCustomMaterial->mShaderData->getShader( macros ); + if ( !rpd->shader ) + { + delete rpd; + mPasses.clear(); + return false; + } + + rpd->shaderHandles.init( rpd->shader, mCustomMaterial->mShaderData ); + _initMaterialParameters(); + mDefaultParameters = allocMaterialParameters(); + setMaterialParameters( mDefaultParameters, 0 ); + + return true; +} + +/// Does the base render state block setting, normally per pass +void ProcessedCustomMaterial::_initPassStateBlock(const Material::BlendOp blendOp, U32 numTex, const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockDesc& result) +{ + Parent::_initPassStateBlock(blendOp, numTex, texFlags, result); + if (mCustomMaterial->getStateBlockData()) + { + result.addDesc(mCustomMaterial->getStateBlockData()->getState()); + } +} + +void ProcessedCustomMaterial::_initPassStateBlocks() +{ + AssertFatal(mHasSetStageData, "State data must be set before initializing state block!"); + ShaderRenderPassData* rpd = _getRPD(0); + _initRenderStateStateBlocks(Material::None, mMaxTex, rpd->mTexType, rpd->mRenderStates); +} + +bool ProcessedCustomMaterial::_hasCubemap(U32 pass) +{ + // If the material doesn't have a cubemap, we don't + if( mMaterial->mCubemapData ) return true; + else return false; +} + +bool ProcessedCustomMaterial::setupPass( SceneState *state, const SceneGraphData& sgData, U32 pass ) +{ + PROFILE_SCOPE( ProcessedCustomMaterial_SetupPass ); + + // Make sure we have a pass. + if ( pass >= mPasses.size() ) + return false; + + ShaderRenderPassData* rpd = _getRPD( pass ); + U32 currState = _getRenderStateIndex( state, sgData ); + GFX->setStateBlock(rpd->mRenderStates[currState]); + + // activate shader + if ( rpd->shader ) + GFX->setShader( rpd->shader ); + else + GFX->disableShaders(); + + // Set our textures + setTextureStages( state, sgData, pass ); + + GFXShaderConstBuffer* shaderConsts = _getShaderConstBuffer(pass); + GFX->setShaderConstBuffer(shaderConsts); + + // Set our shader constants. + _setTextureTransforms(pass); + _setShaderConstants(state, sgData, pass); + + LightManager* lm = state ? state->getLightManager() : NULL; + if (lm) + lm->setLightInfo(this, NULL, sgData, state, pass, shaderConsts); + + if (rpd->shaderHandles.mAccumTimeSC->isValid()) + shaderConsts->set(rpd->shaderHandles.mAccumTimeSC, MATMGR->getTotalTime()); + + return true; +} + +void ProcessedCustomMaterial::setTextureStages( SceneState *state, const SceneGraphData &sgData, U32 pass ) +{ + LightManager* lm = state ? state->getLightManager() : NULL; + ShaderRenderPassData* rpd = _getRPD(pass); + ShaderConstHandles* handles = _getShaderConstHandles(pass); + GFXShaderConstBuffer* shaderConsts = _getShaderConstBuffer(pass); + + const MatTextureTarget *texTarget; + GFXTextureObject *texObject; + + for( U32 i=0; imTexType[i]; + if ( !lm || !lm->setTextureStage(sgData, currTexFlag, i, shaderConsts, handles ) ) + { + if ( handles->mTexHandlesSC[i] ) + shaderConsts->set(handles->mTexHandlesSC[i], (S32)i); + + switch( currTexFlag ) + { + case 0: + default: + break; + + case Material::Mask: + case Material::Standard: + case Material::Bump: + case Material::Detail: + { + GFX->setTexture( i, rpd->mTexSlot[i].texObject ); + break; + } + + case Material::Lightmap: + { + GFX->setTexture( i, sgData.lightmap ); + break; + } + case Material::Cube: + { + GFX->setCubeTexture( i, rpd->mCubeMap ); + break; + } + case Material::SGCube: + { + GFX->setCubeTexture( i, sgData.cubemap ); + break; + } + case Material::BackBuff: + { + GFX->setTexture( i, sgData.backBuffTex ); + break; + } + case Material::ReflectBuff: + { + GFX->setTexture( i, sgData.reflectTex ); + break; + } + case Material::Misc: + { + GFX->setTexture( i, sgData.miscTex ); + break; + } + case Material::TexTarget: + { + texTarget = rpd->mTexSlot[i].texTarget; + if ( !texTarget ) + { + GFX->setTexture( i, NULL ); + break; + } + + texObject = texTarget->getTargetTexture( 0 ); + + // If no texture is available then map the default 2x2 + // black texture to it. This at least will ensure that + // we get consistant behavior across GPUs and platforms. + if ( !texObject ) + texObject = GFXTexHandle::ZERO; + + if ( handles->mRTParamsSC[i]->isValid() && texObject ) + { + const Point3I &targetSz = texObject->getSize(); + const RectI &targetVp = texTarget->getTargetViewport(); + Point4F rtParams; + + ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams); + shaderConsts->set(handles->mRTParamsSC[i], rtParams); + } + + GFX->setTexture( i, texObject ); + break; + } + } + } + } +} + +void ProcessedCustomMaterial::cleanup(U32 pass) +{ + // Cleanup is dumb... we waste time clearing stuff + // that will be re-applied on the next draw when we + // sort by material. + + Parent::cleanup(pass); +} + +template +void ProcessedCustomMaterial::setMaterialParameter(MaterialParameters* param, + MaterialParameterHandle* handle, + const String& value) +{ + T typedValue; + if (PropertyInfo::default_scan(value, typedValue)) + { + param->set(handle, typedValue); + } else { + Con::errorf("Error setting %s, parse error: %s", handle->getName().c_str(), value.c_str()); + } +} + +void ProcessedCustomMaterial::setMatrixParameter(MaterialParameters* param, + MaterialParameterHandle* handle, + const String& value, GFXShaderConstType matrixType) +{ + MatrixF result(true); + F32* m = result; + switch (matrixType) + { + case GFXSCT_Float2x2 : + dSscanf(value.c_str(),"%g %g %g %g", + m[result.idx(0,0)], m[result.idx(0,1)], + m[result.idx(1,0)], m[result.idx(1,1)]); + break; + case GFXSCT_Float3x3 : + dSscanf(value.c_str(),"%g %g %g %g %g %g %g %g %g", + m[result.idx(0,0)], m[result.idx(0,1)], m[result.idx(0,2)], + m[result.idx(1,0)], m[result.idx(1,1)], m[result.idx(1,2)], + m[result.idx(2,0)], m[result.idx(2,1)], m[result.idx(2,2)]); + break; + default: + AssertFatal(false, "Invalid type!"); + break; + } +} + +// BTRTODO: Support arrays!? +MaterialParameters* ProcessedCustomMaterial::allocMaterialParameters() +{ + MaterialParameters* ret = Parent::allocMaterialParameters(); + // See if any of the dynamic fields match up with shader constants we have. + SimFieldDictionary* fields = mMaterial->getFieldDictionary(); + if (!fields || fields->getNumFields() == 0) + return ret; + + const Vector& consts = ret->getShaderConstDesc(); + for (U32 i = 0; i < consts.size(); i++) + { + // strip the dollar sign from the front. + String stripped(consts[i].name); + stripped.erase(0, 1); + + SimFieldDictionary::Entry* field = fields->findDynamicField(stripped); + if (field) + { + MaterialParameterHandle* handle = getMaterialParameterHandle(consts[i].name); + switch (consts[i].constType) + { + case GFXSCT_Float : + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Float2: + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Float3: + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Float4: + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Float2x2: + case GFXSCT_Float3x3: + setMatrixParameter(ret, handle, field->value, consts[i].constType); + break; + case GFXSCT_Float4x4: + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Int: + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Int2: + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Int3: + setMaterialParameter(ret, handle, field->value); + break; + case GFXSCT_Int4: + setMaterialParameter(ret, handle, field->value); + break; + // Do we want to ignore these? + case GFXSCT_Sampler: + case GFXSCT_SamplerCube: + default: + break; + } + } + } + return ret; +} \ No newline at end of file diff --git a/materials/processedCustomMaterial.h b/materials/processedCustomMaterial.h new file mode 100644 index 0000000..28b2d4e --- /dev/null +++ b/materials/processedCustomMaterial.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATERIALS_PROCESSEDCUSTOMMATERIAL_H_ +#define _MATERIALS_PROCESSEDCUSTOMMATERIAL_H_ + +#ifndef _MATERIALS_PROCESSEDSHADERMATERIAL_H_ +#include "materials/processedShaderMaterial.h" +#endif +#ifndef _CUSTOMMATERIALDEFINITION_H_ +#include "materials/customMaterialDefinition.h" +#endif + + +/// +class ProcessedCustomMaterial : public ProcessedShaderMaterial +{ + typedef ProcessedShaderMaterial Parent; +public: + ProcessedCustomMaterial(Material &mat); + ~ProcessedCustomMaterial(); + + virtual bool setupPass(SceneState *, const SceneGraphData& sgData, U32 pass); + virtual bool init( const FeatureSet &features, const GFXVertexFormat *vertexFormat, const MatFeaturesDelegate &featuresDelegate ); + virtual void setTextureStages(SceneState *, const SceneGraphData &sgData, U32 pass ); + virtual void cleanup(U32 pass); + virtual MaterialParameters* allocMaterialParameters(); + +protected: + + virtual void _setStageData(); + virtual bool _hasCubemap(U32 pass); + void _initPassStateBlock(const Material::BlendOp blendOp, U32 numTex, + const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockDesc& result); + virtual void _initPassStateBlocks(); + +private: + + CustomMaterial* mCustomMaterial; + + /// The conditioner macros passed to the + /// shader on construction. + Vector mConditionerMacros; + + /// How many texture slots are we using. + U32 mMaxTex; + + template + void setMaterialParameter(MaterialParameters* param, MaterialParameterHandle* handle, + const String& value); + void setMatrixParameter(MaterialParameters* param, + MaterialParameterHandle* handle, const String& value, GFXShaderConstType matrixType); +}; + +#endif // _MATERIALS_PROCESSEDCUSTOMMATERIAL_H_ diff --git a/materials/processedFFMaterial.cpp b/materials/processedFFMaterial.cpp new file mode 100644 index 0000000..9037a06 --- /dev/null +++ b/materials/processedFFMaterial.cpp @@ -0,0 +1,381 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/processedFFMaterial.h" + +#include "gfx/sim/cubemapData.h" +#include "materials/sceneData.h" +#include "materials/customMaterialDefinition.h" +#include "materials/materialFeatureTypes.h" +#include "gfx/sim/gfxStateBlockData.h" +#include "gfx/gfxDevice.h" +#include "gfx/genericConstBuffer.h" +#include "materials/materialParameters.h" +#include "lighting/lightInfo.h" +#include "sceneGraph/sceneState.h" +#include "core/util/safeDelete.h" +#include "math/util/matrixSet.h" + +class FFMaterialParameterHandle : public MaterialParameterHandle +{ +public: + virtual ~FFMaterialParameterHandle() {} + virtual const String& getName() const { return mName; } + virtual bool isValid() const { return false; } + virtual S32 getSamplerRegister( U32 pass ) const { return -1; } +private: + String mName; +}; + +ProcessedFFMaterial::ProcessedFFMaterial() +{ + VECTOR_SET_ASSOCIATION( mParamDesc ); + + _construct(); +} + +ProcessedFFMaterial::ProcessedFFMaterial(Material &mat, const bool isLightingMaterial) +{ + VECTOR_SET_ASSOCIATION( mParamDesc ); + + _construct(); + mMaterial = &mat; + mIsLightingMaterial = isLightingMaterial; +} + +void ProcessedFFMaterial::_construct() +{ + mHasSetStageData = false; + mHasGlow = false; + mIsLightingMaterial = false; + mDefaultHandle = new FFMaterialParameterHandle(); + mDefaultParameters = new MaterialParameters(); + mCurrentParams = mDefaultParameters; +} + +ProcessedFFMaterial::~ProcessedFFMaterial() +{ + SAFE_DELETE(mDefaultParameters); + SAFE_DELETE( mDefaultHandle ); +} + +void ProcessedFFMaterial::_createPasses( U32 stageNum, const FeatureSet &features ) +{ + FixedFuncFeatureData featData; + _determineFeatures(stageNum, featData, features); + // Just create a simple pass! + _addPass(0, featData); + + mFeatures.clear(); + if ( featData.features[FixedFuncFeatureData::DiffuseMap] ) + mFeatures.addFeature( MFT_DiffuseMap ); + if ( featData.features[FixedFuncFeatureData::LightMap] ) + mFeatures.addFeature( MFT_LightMap ); + if ( featData.features[FixedFuncFeatureData::ToneMap] ) + mFeatures.addFeature( MFT_ToneMap ); + +} + +void ProcessedFFMaterial::_determineFeatures( U32 stageNum, + FixedFuncFeatureData& featData, + const FeatureSet &features ) +{ + if ( mStages[stageNum].getTex( MFT_DiffuseMap ) ) + featData.features[FixedFuncFeatureData::DiffuseMap] = true; + + if ( features.hasFeature( MFT_LightMap ) ) + featData.features[FixedFuncFeatureData::LightMap] = true; + if ( features.hasFeature( MFT_ToneMap )) + featData.features[FixedFuncFeatureData::ToneMap] = true; +} + +U32 ProcessedFFMaterial::getNumStages() +{ + // Loops through all stages to determine how many stages we actually use + U32 numStages = 0; + + U32 i; + for( i=0; imCubemapData || mMaterial->mDynamicCubemap ) + { + numStages++; + continue; + } + } + + // If we have a texture for the a feature the + // stage is active. + if ( mStages[i].hasValidTex() ) + stageActive = true; + + // If this stage has specular lighting, it's active + if ( mMaterial->mPixelSpecular[i] ) + stageActive = true; + + // If we have a Material that is vertex lit + // then it may not have a texture + if( mMaterial->mVertLit[i] ) + { + stageActive = true; + } + + // Increment the number of active stages + numStages += stageActive; + } + + + return numStages; +} + +void ProcessedFFMaterial::cleanup(U32 pass) +{ + // Cleanup is dumb... we waste time clearing stuff + // that will be re-applied on the next draw when we + // sort by material. + + Parent::cleanup(pass); +} + +bool ProcessedFFMaterial::setupPass( SceneState *state, const SceneGraphData &sgData, U32 pass ) +{ + PROFILE_SCOPE( ProcessedFFMaterial_SetupPass ); + + // Make sure we have a pass + if(pass >= mPasses.size()) + return false; + + _setRenderState( state, sgData, pass ); + + // Bind our textures + setTextureStages( state, sgData, pass ); + return true; +} + +void ProcessedFFMaterial::setTextureStages(SceneState * state, const SceneGraphData& sgData, U32 pass) +{ + // We may need to do some trickery in here for fixed function, this is just copy/paste from MatInstance +#ifdef TORQUE_DEBUG + AssertFatal( passgetLightManager(); + + RenderPassData *rpd = mPasses[pass]; + for( U32 i=0; imNumTex; i++ ) + { + U32 currTexFlag = rpd->mTexType[i]; + if (!lm || !lm->setTextureStage(sgData, currTexFlag, i, NULL, NULL)) + { + switch( currTexFlag ) + { + case Material::NoTexture: + if( mMaterial->isIFL() && sgData.miscTex ) + GFX->setTexture( i, sgData.miscTex ); + else + { + if (rpd->mTexSlot[i].texObject) + GFX->setTexture( i, rpd->mTexSlot[i].texObject ); + } + break; + + case Material::NormalizeCube: + GFX->setCubeTexture(i, Material::GetNormalizeCube()); + break; + + case Material::Lightmap: + GFX->setTexture( i, sgData.lightmap ); + break; + + case Material::Cube: + // TODO: Is this right? + GFX->setTexture( i, rpd->mTexSlot[0].texObject ); + break; + + case Material::SGCube: + // No cubemap support just yet + //GFX->setCubeTexture( i, sgData.cubemap ); + GFX->setTexture( i, rpd->mTexSlot[0].texObject ); + break; + + case Material::BackBuff: + GFX->setTexture( i, sgData.backBuffTex ); + break; + } + } + } +} + +MaterialParameters* ProcessedFFMaterial::allocMaterialParameters() +{ + return new MaterialParameters(); +} + +MaterialParameters* ProcessedFFMaterial::getDefaultMaterialParameters() +{ + return mDefaultParameters; +} + +MaterialParameterHandle* ProcessedFFMaterial::getMaterialParameterHandle(const String& name) +{ + return mDefaultHandle; +} + +void ProcessedFFMaterial::setTransforms(const MatrixSet &matrixSet, SceneState *state, const U32 pass) +{ + GFX->setWorldMatrix(matrixSet.getObjectToWorld()); + GFX->setViewMatrix(matrixSet.getWorldToCamera()); + GFX->setProjectionMatrix(matrixSet.getCameraToScreen()); +} + +void ProcessedFFMaterial::setSceneInfo(SceneState * state, const SceneGraphData& sgData, U32 pass) +{ + _setPrimaryLightInfo(sgData.objTrans, sgData.lights[0], pass); + _setSecondaryLightInfo(sgData.objTrans, sgData.lights[1]); +} + +void ProcessedFFMaterial::_setPrimaryLightInfo(const MatrixF &_objTrans, LightInfo* light, U32 pass) +{ + // Just in case + GFX->setGlobalAmbientColor(ColorF(0.0f, 0.0f, 0.0f, 1.0f)); + if ( light->getType() == LightInfo::Ambient ) + { + // Ambient light + GFX->setGlobalAmbientColor( light->getAmbient() ); + return; + } + + GFX->setLight(0, NULL); + GFX->setLight(1, NULL); + // This is a quick hack that lets us use FF lights + GFXLightMaterial lightMat; + lightMat.ambient = ColorF(1.0f, 1.0f, 1.0f, 1.0f); + lightMat.diffuse = ColorF(1.0f, 1.0f, 1.0f, 1.0f); + lightMat.emissive = ColorF(0.0f, 0.0f, 0.0f, 0.0f); + lightMat.specular = ColorF(0.0f, 0.0f, 0.0f, 0.0f); + lightMat.shininess = 128.0f; + GFX->setLightMaterial(lightMat); + + // set object transform + MatrixF objTrans = _objTrans; + objTrans.inverse(); + + // fill in primary light + //------------------------- + GFXLightInfo xlatedLight; + light->setGFXLight(&xlatedLight); + Point3F lightPos = light->getPosition(); + Point3F lightDir = light->getDirection(); + objTrans.mulP(lightPos); + objTrans.mulV(lightDir); + + xlatedLight.mPos = lightPos; + xlatedLight.mDirection = lightDir; + + GFX->setLight(0, &xlatedLight); +} + +void ProcessedFFMaterial::_setSecondaryLightInfo(const MatrixF &_objTrans, LightInfo* light) +{ + // set object transform + MatrixF objTrans = _objTrans; + objTrans.inverse(); + + // fill in secondary light + //------------------------- + GFXLightInfo xlatedLight; + light->setGFXLight(&xlatedLight); + + Point3F lightPos = light->getPosition(); + Point3F lightDir = light->getDirection(); + objTrans.mulP(lightPos); + objTrans.mulV(lightDir); + + xlatedLight.mPos = lightPos; + xlatedLight.mDirection = lightDir; + + GFX->setLight(1, &xlatedLight); +} + +bool ProcessedFFMaterial::init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat, + const MatFeaturesDelegate &featuresDelegate ) +{ + TORQUE_UNUSED( vertexFormat ); + TORQUE_UNUSED( featuresDelegate ); + + _setStageData(); + + // Just create a simple pass + _createPasses(0, features); + _initRenderPassDataStateBlocks(); + + return true; +} + +void ProcessedFFMaterial::_addPass(U32 stageNum, FixedFuncFeatureData& featData) +{ + U32 numTex = 0; + + // Just creates a simple pass, but it can still glow! + RenderPassData rpd; + + // Base texture, texunit 0 + if(featData.features[FixedFuncFeatureData::DiffuseMap]) + { + rpd.mTexSlot[0].texObject = mStages[stageNum].getTex( MFT_DiffuseMap ); + rpd.mTexType[0] = Material::NoTexture; + numTex++; + } + + // lightmap, texunit 1 + if(featData.features[FixedFuncFeatureData::LightMap]) + { + rpd.mTexType[1] = Material::Lightmap; + numTex++; + } + + rpd.mNumTex = numTex; + rpd.mStageNum = stageNum; + rpd.mGlow = false; + + mPasses.push_back( new RenderPassData(rpd) ); +} + +void ProcessedFFMaterial::_setPassBlendOp() +{ + +} + +/// Does the base render state block setting, normally per pass +void ProcessedFFMaterial::_initPassStateBlock(const Material::BlendOp blendOp, U32 numTex, const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockDesc& result) +{ + Parent::_initPassStateBlock(blendOp, numTex, texFlags, result); + if (mIsLightingMaterial) + { + result.ffLighting = true; + result.blendDefined = true; + result.blendEnable = true; + result.blendSrc = GFXBlendOne; + result.blendSrc = GFXBlendOne; + } + + // This is here for generic FF shader fallbacks. + CustomMaterial* custmat = dynamic_cast(mMaterial); + if (custmat) + { + if (custmat->getStateBlockData()) + { + result.addDesc(custmat->getStateBlockData()->getState()); + } + } +} diff --git a/materials/processedFFMaterial.h b/materials/processedFFMaterial.h new file mode 100644 index 0000000..b892605 --- /dev/null +++ b/materials/processedFFMaterial.h @@ -0,0 +1,110 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATERIALS_PROCESSEDFFMATERIAL_H_ +#define _MATERIALS_PROCESSEDFFMATERIAL_H_ + +#ifndef _MATERIALS_PROCESSEDMATERIAL_H_ +#include "materials/processedMaterial.h" +#endif + +class LightInfo; +struct GFXShaderConstDesc; + + +/// Fixed function rendering. Does not load or use shaders. Does not rely on GFXMaterialFeatureData. +/// Tries very hard to not rely on anything possibly related to shaders. +/// +/// @note Does not always succeed. +class ProcessedFFMaterial : public ProcessedMaterial +{ + typedef ProcessedMaterial Parent; +public: + ProcessedFFMaterial(); + ProcessedFFMaterial(Material &mat, const bool isLightingMaterial = false); + ~ProcessedFFMaterial(); + /// @name Render state setting + /// + /// @{ + + /// Sets necessary textures and texture ops for rendering + virtual void setTextureStages(SceneState *, const SceneGraphData &sgData, U32 pass ); + + virtual MaterialParameters* allocMaterialParameters(); + virtual MaterialParameterHandle* getMaterialParameterHandle(const String& name); + virtual MaterialParameters* getDefaultMaterialParameters(); + + virtual void setTransforms(const MatrixSet &matrixSet, SceneState *state, const U32 pass); + + virtual void setSceneInfo(SceneState *, const SceneGraphData& sgData, U32 pass); + + /// @} + + virtual bool init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat, + const MatFeaturesDelegate &featuresDelegate ); + + /// Sets up the given pass + /// + /// @returns false if the pass could not be set up + virtual bool setupPass(SceneState *, const SceneGraphData& sgData, U32 pass); + + /// Returns the number of stages we're using (not to be confused with the number of passes) + virtual U32 getNumStages(); + + /// Undoes all state changes for the given pass (or will anyways) + virtual void cleanup(U32 pass); +protected: + MaterialParameterHandle* mDefaultHandle; + MaterialParameters* mDefaultParameters; + + struct FixedFuncFeatureData + { + enum + { + DiffuseMap, + LightMap, + ToneMap, + NumFeatures + }; + bool features[NumFeatures]; + }; + + bool mIsLightingMaterial; + + Vector mParamDesc; + + /// @name Internal functions + /// + /// @{ + + /// Adds a pass for the given stage + virtual void _addPass(U32 stageNum, FixedFuncFeatureData& featData); + + /// Chooses a blend op for the pass during pass creation + virtual void _setPassBlendOp(); + + /// Creates all necessary passes for the given stage + void _createPasses( U32 stageNum, const FeatureSet &features ); + + /// Determine what features we need + void _determineFeatures( U32 stageNum, + FixedFuncFeatureData &featData, + const FeatureSet &features); + + /// Sets light info for the first light + virtual void _setPrimaryLightInfo(const MatrixF &objTrans, LightInfo* light, U32 pass); + + /// Sets light info for the second light + virtual void _setSecondaryLightInfo(const MatrixF &objTrans, LightInfo* light); + + /// Does the base render state block setting, normally per pass + virtual void _initPassStateBlock(const Material::BlendOp blendOp, U32 numTex, + const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockDesc& result); + /// @} + + void _construct(); +}; + +#endif diff --git a/materials/processedMaterial.cpp b/materials/processedMaterial.cpp new file mode 100644 index 0000000..0b8fb9d --- /dev/null +++ b/materials/processedMaterial.cpp @@ -0,0 +1,421 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/processedMaterial.h" + +#include "materials/sceneData.h" +#include "materials/materialParameters.h" +#include "materials/matTextureTarget.h" +#include "materials/materialFeatureTypes.h" +#include "materials/materialManager.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/sim/cubemapData.h" + + +//----------------------------------------------------------------------------- +// RenderPassData +//----------------------------------------------------------------------------- +RenderPassData::RenderPassData() +{ + reset(); +} + +void RenderPassData::reset() +{ + for( U32 i = 0; i < Material::MAX_TEX_PER_PASS; ++ i ) + destructInPlace( &mTexSlot[ i ] ); + + dMemset( &mTexSlot, 0, sizeof(mTexSlot) ); + dMemset( &mTexType, 0, sizeof(mTexType) ); + + mCubeMap = NULL; + mNumTex = mNumTexReg = mStageNum = 0; + mGlow = false; + mBlendOp = Material::None; + + mFeatureData.clear(); + + for (U32 i = 0; i < STATE_MAX; i++) + mRenderStates[i] = NULL; +} + +//----------------------------------------------------------------------------- +// ProcessedMaterial +//----------------------------------------------------------------------------- +ProcessedMaterial::ProcessedMaterial() +: mMaterial( NULL ), + mCurrentParams( NULL ), + mHasSetStageData( false ), + mHasGlow( false ), + mMaxStages( 0 ), + mVertexFormat( NULL ) +{ + VECTOR_SET_ASSOCIATION( mPasses ); +} + +ProcessedMaterial::~ProcessedMaterial() +{ + for_each( mPasses.begin(), mPasses.end(), delete_pointer() ); +} + +void ProcessedMaterial::_setBlendState(Material::BlendOp blendOp, GFXStateBlockDesc& desc ) +{ + switch( blendOp ) + { + case Material::Add: + { + desc.blendSrc = GFXBlendOne; + desc.blendDest = GFXBlendOne; + break; + } + case Material::AddAlpha: + { + desc.blendSrc = GFXBlendSrcAlpha; + desc.blendDest = GFXBlendOne; + break; + } + case Material::Mul: + { + desc.blendSrc = GFXBlendDestColor; + desc.blendDest = GFXBlendZero; + break; + } + case Material::LerpAlpha: + { + desc.blendSrc = GFXBlendSrcAlpha; + desc.blendDest = GFXBlendInvSrcAlpha; + break; + } + + default: + { + // default to LerpAlpha + desc.blendSrc = GFXBlendSrcAlpha; + desc.blendDest = GFXBlendInvSrcAlpha; + break; + } + } +} + +void ProcessedMaterial::setBuffers(GFXVertexBufferHandleBase* vertBuffer, GFXPrimitiveBufferHandle* primBuffer) +{ + GFX->setVertexBuffer( *vertBuffer ); + GFX->setPrimitiveBuffer( *primBuffer ); +} + +String ProcessedMaterial::_getTexturePath(const String& filename) +{ + // if '/', then path is specified, use it. + if( filename.find('/') != String::NPos ) + { + return filename; + } + + // otherwise, construct path + return mMaterial->getPath() + filename; +} + +GFXTexHandle ProcessedMaterial::_createTexture( const char* filename, GFXTextureProfile *profile) +{ + return GFXTexHandle( _getTexturePath(filename), profile, avar("%s() - NA (line %d)", __FUNCTION__, __LINE__) ); +} + +void ProcessedMaterial::addStateBlockDesc(const GFXStateBlockDesc& sb) +{ + mUserDefined = sb; +} + +/// Creates the default state block templates, used by initStateBlocks +void ProcessedMaterial::_initStateBlockTemplates(GFXStateBlockDesc& stateTranslucent, GFXStateBlockDesc& stateGlow, GFXStateBlockDesc& stateReflect) +{ + // Translucency + stateTranslucent.blendDefined = true; + stateTranslucent.blendEnable = mMaterial->mTranslucentBlendOp != Material::None; + _setBlendState(mMaterial->mTranslucentBlendOp, stateTranslucent); + stateTranslucent.zDefined = true; + stateTranslucent.zWriteEnable = mMaterial->mTranslucentZWrite; + stateTranslucent.alphaDefined = true; + stateTranslucent.alphaTestEnable = mMaterial->mAlphaTest; + stateTranslucent.alphaTestRef = mMaterial->mAlphaRef; + stateTranslucent.alphaTestFunc = GFXCmpGreaterEqual; + stateTranslucent.samplersDefined = true; + stateTranslucent.samplers[0].textureColorOp = GFXTOPModulate; + stateTranslucent.samplers[0].alphaOp = GFXTOPModulate; + stateTranslucent.samplers[0].alphaArg1 = GFXTATexture; + stateTranslucent.samplers[0].alphaArg2 = GFXTADiffuse; + + // Glow + stateGlow.zDefined = true; + stateGlow.zWriteEnable = false; + + // Reflect + stateReflect.cullDefined = true; + stateReflect.cullMode = mMaterial->mDoubleSided ? GFXCullNone : GFXCullCW; +} + +/// Creates the default state blocks for each RenderPassData item +void ProcessedMaterial::_initRenderPassDataStateBlocks() +{ + for (U32 pass = 0; pass < mPasses.size(); pass++) + { + RenderPassData* rpd = mPasses[pass]; + _initRenderStateStateBlocks(rpd->mBlendOp, rpd->mNumTex, rpd->mTexType, rpd->mRenderStates); + } +} + +/// Does the base render state block setting, normally per pass +void ProcessedMaterial::_initPassStateBlock(const Material::BlendOp blendOp, U32 numTex, const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockDesc& result) +{ + if (blendOp != Material::None) + { + result.blendDefined = true; + result.blendEnable = true; + _setBlendState(blendOp, result); + } + + if (mMaterial->isDoubleSided()) + { + result.cullDefined = true; + result.cullMode = GFXCullNone; + } + + if(mMaterial->mAlphaTest) + { + result.alphaDefined = true; + result.alphaTestEnable = mMaterial->mAlphaTest; + result.alphaTestRef = mMaterial->mAlphaRef; + result.alphaTestFunc = GFXCmpGreaterEqual; + } + + result.samplersDefined = true; + MatTextureTarget *texTarget; + + for( U32 i=0; i < numTex; i++ ) + { + U32 currTexFlag = texFlags[i]; + + switch( currTexFlag ) + { + case 0: + result.samplers[i].textureColorOp = GFXTOPModulate; + result.samplers[i].addressModeU = GFXAddressWrap; + result.samplers[i].addressModeV = GFXAddressWrap; + break; + + case Material::TexTarget: + { + texTarget = mPasses[0]->mTexSlot[i].texTarget; + if ( texTarget ) + texTarget->setupSamplerState( &result.samplers[i] ); + break; + } + } + } + + // The prepass will take care of writing to the + // zbuffer, so we don't have to by default. + // The prepass can't write to the backbuffer's zbuffer in OpenGL. + if ( MATMGR->getPrePassEnabled() && !GFX->getAdapterType() == OpenGL ) + result.setZReadWrite( result.zEnable, false ); + + result.addDesc(mUserDefined); +} + +/// Creates the default state blocks for a list of render states +void ProcessedMaterial::_initRenderStateStateBlocks(const Material::BlendOp blendOp, U32 numTex, const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockRef renderStates[RenderPassData::STATE_MAX]) +{ + GFXStateBlockDesc stateTranslucent; + GFXStateBlockDesc stateGlow; + GFXStateBlockDesc stateReflect; + GFXStateBlockDesc statePass; + + _initStateBlockTemplates(stateTranslucent, stateGlow, stateReflect); + _initPassStateBlock(blendOp, numTex, texFlags, statePass); + + // Ok, we've got our templates set up, let's combine them together based on state and + // create our state blocks. + for (U32 i = 0; i < RenderPassData::STATE_MAX; i++) + { + GFXStateBlockDesc stateFinal; + + if (i & RenderPassData::STATE_REFLECT) + stateFinal.addDesc(stateReflect); + if (i & RenderPassData::STATE_TRANSLUCENT) + stateFinal.addDesc(stateTranslucent); + if (i & RenderPassData::STATE_GLOW) + stateFinal.addDesc(stateGlow); + + stateFinal.addDesc(statePass); + + if (i & RenderPassData::STATE_WIREFRAME) + stateFinal.fillMode = GFXFillWireframe; + + GFXStateBlockRef sb = GFX->createStateBlock(stateFinal); + renderStates[i] = sb; + } +} + +U32 ProcessedMaterial::_getRenderStateIndex( const SceneState *sceneState, + const SceneGraphData &sgData ) +{ + // Based on what the state of the world is, get our render state block + U32 currState = 0; + + if ( sgData.binType == SceneGraphData::GlowBin ) + currState |= RenderPassData::STATE_GLOW; + + if ( sceneState && sceneState->isReflectPass() ) + currState |= RenderPassData::STATE_REFLECT; + + if( mMaterial->isTranslucent() || sgData.visibility < 1.0f ) + currState |= RenderPassData::STATE_TRANSLUCENT; + + if ( sgData.wireframe ) + currState |= RenderPassData::STATE_WIREFRAME; + + return currState; +} + +void ProcessedMaterial::_setRenderState( const SceneState *state, + const SceneGraphData& sgData, + U32 pass ) +{ + // Make sure we have the pass + if ( pass >= mPasses.size() ) + return; + + U32 currState = _getRenderStateIndex( state, sgData ); + + GFX->setStateBlock(mPasses[pass]->mRenderStates[currState]); +} + + +void ProcessedMaterial::_setStageData() +{ + // Only do this once + if ( mHasSetStageData ) + return; + mHasSetStageData = true; + + U32 i; + + // Load up all the textures for every possible stage + for( i=0; imDiffuseMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_DiffuseMap, _createTexture( mMaterial->mDiffuseMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_DiffuseMap )) + mMaterial->logError("Failed to load diffuse map %s for stage %i", _getTexturePath(mMaterial->mDiffuseMapFilename[i]).c_str(), i); + } + else if( mMaterial->mBaseTexFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_DiffuseMap, _createTexture( mMaterial->mBaseTexFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_DiffuseMap )) + mMaterial->logError("Failed to load diffuse map %s for stage %i", _getTexturePath(mMaterial->mBaseTexFilename[i]).c_str(), i); + } + + // OverlayMap + if( mMaterial->mOverlayMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_OverlayMap, _createTexture( mMaterial->mOverlayMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_OverlayMap )) + mMaterial->logError("Failed to load overlay map %s for stage %i", _getTexturePath(mMaterial->mOverlayMapFilename[i]).c_str(), i); + } + else if( mMaterial->mOverlayTexFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_OverlayMap, _createTexture( mMaterial->mOverlayTexFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_OverlayMap )) + mMaterial->logError("Failed to load overlay map %s for stage %i", _getTexturePath(mMaterial->mOverlayTexFilename[i]).c_str(), i); + } + + // LightMap + if( mMaterial->mLightMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_LightMap, _createTexture( mMaterial->mLightMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_LightMap )) + mMaterial->logError("Failed to load light map %s for stage %i", _getTexturePath(mMaterial->mLightMapFilename[i]).c_str(), i); + } + + // ToneMap + if( mMaterial->mToneMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_ToneMap, _createTexture( mMaterial->mToneMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_ToneMap )) + mMaterial->logError("Failed to load tone map %s for stage %i", _getTexturePath(mMaterial->mToneMapFilename[i]).c_str(), i); + } + + // DetailMap + if( mMaterial->mDetailMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_DetailMap, _createTexture( mMaterial->mDetailMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_DetailMap )) + mMaterial->logError("Failed to load detail map %s for stage %i", _getTexturePath(mMaterial->mDetailMapFilename[i]).c_str(), i); + } + else if( mMaterial->mDetailTexFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_DetailMap, _createTexture( mMaterial->mDetailTexFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_DetailMap )) + mMaterial->logError("Failed to load detail map %s for stage %i", _getTexturePath(mMaterial->mDetailTexFilename[i]).c_str(), i); + } + + // NormalMap + if( mMaterial->mNormalMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_NormalMap, _createTexture( mMaterial->mNormalMapFilename[i], &GFXDefaultStaticNormalMapProfile ) ); + if(!mStages[i].getTex( MFT_NormalMap )) + mMaterial->logError("Failed to load normal map %s for stage %i", _getTexturePath(mMaterial->mNormalMapFilename[i]).c_str(), i); + } + else if( mMaterial->mBumpTexFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_NormalMap, _createTexture( mMaterial->mBumpTexFilename[i], &GFXDefaultStaticNormalMapProfile ) ); + if(!mStages[i].getTex( MFT_NormalMap )) + mMaterial->logError("Failed to load normal map %s for stage %i", _getTexturePath(mMaterial->mBumpTexFilename[i]).c_str(), i); + } + + // SpecularMap + if( mMaterial->mSpecularMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_SpecularMap, _createTexture( mMaterial->mSpecularMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_SpecularMap )) + mMaterial->logError("Failed to load specular map %s for stage %i", _getTexturePath(mMaterial->mSpecularMapFilename[i]).c_str(), i); + } + + // EnironmentMap + if( mMaterial->mEnvMapFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_EnvMap, _createTexture( mMaterial->mEnvMapFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_EnvMap )) + mMaterial->logError("Failed to load environment map %s for stage %i", _getTexturePath(mMaterial->mEnvMapFilename[i]).c_str(), i); + } + else if( mMaterial->mEnvTexFilename[i].isNotEmpty() ) + { + mStages[i].setTex( MFT_EnvMap, _createTexture( mMaterial->mEnvTexFilename[i], &GFXDefaultStaticDiffuseProfile ) ); + if(!mStages[i].getTex( MFT_EnvMap )) + mMaterial->logError("Failed to load environment map %s for stage %i", _getTexturePath(mMaterial->mEnvTexFilename[i]).c_str(), i); + } + } + + mMaterial->mCubemapData = dynamic_cast(Sim::findObject( mMaterial->mCubemapName )); + if( !mMaterial->mCubemapData ) + mMaterial->mCubemapData = NULL; + + + // If we have a cubemap put it on stage 0 (cubemaps only supported on stage 0) + if( mMaterial->mCubemapData ) + { + mMaterial->mCubemapData->createMap(); + mStages[0].setCubemap( mMaterial->mCubemapData->mCubemap ); + if ( !mStages[0].getCubemap() ) + mMaterial->logError("Failed to load cubemap"); + } +} + +void ProcessedMaterial::cleanup(U32 pass) +{ +} diff --git a/materials/processedMaterial.h b/materials/processedMaterial.h new file mode 100644 index 0000000..e9ecff6 --- /dev/null +++ b/materials/processedMaterial.h @@ -0,0 +1,263 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATERIALS_PROCESSEDMATERIAL_H_ +#define _MATERIALS_PROCESSEDMATERIAL_H_ + +#ifndef _MATERIALDEFINITION_H_ +#include "materials/materialDefinition.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif + +class ShaderFeature; +class MaterialParameters; +class MaterialParameterHandle; +class SceneState; +class GFXVertexBufferHandleBase; +class GFXPrimitiveBufferHandle; +class MatrixSet; + + +/// This contains the common data needed to render a pass. +struct RenderPassData +{ +public: + + struct TexSlotT + { + /// This is the default type of texture which + /// is valid with most texture types. + /// @see mTexType + GFXTexHandle texObject; + + /// Only valid when the texture type is set + /// to Material::TexTarget. + /// @see mTexType + MatTextureTargetRef texTarget; + + } mTexSlot[Material::MAX_TEX_PER_PASS]; + + U32 mTexType[Material::MAX_TEX_PER_PASS]; + + /// The cubemap to use when the texture type is + /// set to Material::Cube. + /// @see mTexType + GFXCubemap *mCubeMap; + + U32 mNumTex; + + U32 mNumTexReg; + + MaterialFeatureData mFeatureData; + + bool mGlow; + + Material::BlendOp mBlendOp; + + U32 mStageNum; + + /// State permutations, used to index into + /// the render states array. + /// @see mRenderStates + enum + { + STATE_REFLECT = 1, + STATE_TRANSLUCENT = 2, + STATE_GLOW = 4, + STATE_WIREFRAME = 8, + STATE_MAX = 16 + }; + + /// + GFXStateBlockRef mRenderStates[STATE_MAX]; + + RenderPassData(); + + virtual ~RenderPassData() { reset(); } + + virtual void reset(); +}; + +/// This is an abstract base class which provides the external +/// interface all subclasses must implement. This interface +/// primarily consists of setting state. Pass creation +/// is implementation specific, and internal, thus it is +/// not in this base class. +class ProcessedMaterial +{ +public: + ProcessedMaterial(); + virtual ~ProcessedMaterial(); + + /// @name State setting functions + /// + /// @{ + + virtual void addStateBlockDesc(const GFXStateBlockDesc& sb); + + /// Set the user defined shader macros. + virtual void setShaderMacros( const Vector ¯os ) { mUserMacros = macros; } + + /// Sets the textures needed for rendering the current pass + virtual void setTextureStages(SceneState *, const SceneGraphData &sgData, U32 pass ) = 0; + + /// Sets the transformation matrix, i.e. Model * View * Projection + virtual void setTransforms(const MatrixSet &matrixSet, SceneState *state, const U32 pass) = 0; + + /// Sets the scene info like lights for the given pass. + virtual void setSceneInfo(SceneState *, const SceneGraphData& sgData, U32 pass) = 0; + + /// Sets the given vertex and primitive buffers so we can render geometry + virtual void setBuffers(GFXVertexBufferHandleBase* vertBuffer, GFXPrimitiveBufferHandle* primBuffer); + + /// @} + + /// Initializes us (eg. loads textures, creates passes, generates shaders) + virtual bool init( const FeatureSet& features, + const GFXVertexFormat *vertexFormat, + const MatFeaturesDelegate &featuresDelegate ) = 0; + + /// Sets up the given pass. Returns true if the pass was set up, false if there was an error or if + /// the specified pass is out of bounds. + virtual bool setupPass(SceneState *, const SceneGraphData& sgData, U32 pass) = 0; + + // Material parameter methods + virtual MaterialParameters* allocMaterialParameters() = 0; + virtual MaterialParameters* getDefaultMaterialParameters() = 0; + virtual void setMaterialParameters(MaterialParameters* param, S32 pass) { mCurrentParams = param; }; + virtual MaterialParameters* getMaterialParameters() { return mCurrentParams; } + virtual MaterialParameterHandle* getMaterialParameterHandle(const String& name) = 0; + + /// Cleans up the state and resources set by the given pass. + virtual void cleanup(U32 pass); + + /// Returns the pass data for the given pass. + RenderPassData* getPass(U32 pass) + { + if(pass >= mPasses.size()) + return NULL; + return mPasses[pass]; + } + + /// Returns the pass data for the given pass (const version) + const RenderPassData* getPass(U32 pass) const + { + return getPass(pass); + } + + /// Returns the number of stages we're rendering (not to be confused with the number of passes). + virtual U32 getNumStages() = 0; + + /// Returns the number of passes we are rendering (not to be confused with the number of stages). + U32 getNumPasses() + { + return mPasses.size(); + } + + /// Returns true if any pass glows + bool hasGlow() + { + return mHasGlow; + } + + /// Gets the stage number for a pass + U32 getStageFromPass(U32 pass) const + { + if(pass >= mPasses.size()) + return 0; + return mPasses[pass]->mStageNum; + } + + /// Returns the active features in use by this material. + /// @see BaseMatInstance::getFeatures + const FeatureSet& getFeatures() const { return mFeatures; } + + /// Dump shader info, or FF texture info? + virtual void dumpMaterialInfo() { } + + /// Returns the source material. + Material* getMaterial() const { return mMaterial; } + +protected: + + /// Our passes. + Vector mPasses; + + /// The active features in use by this material. + FeatureSet mFeatures; + + /// The material which we are processing. + Material* mMaterial; + + MaterialParameters* mCurrentParams; + + /// Material::StageData is used here because the shader + /// generator throws a fit if it's passed anything else. + Material::StageData mStages[Material::MAX_STAGES]; + + /// If we've already loaded the stage data + bool mHasSetStageData; + + /// If we glow + bool mHasGlow; + + /// Number of stages (not to be confused with number of passes) + U32 mMaxStages; + + /// The vertex format on which this material will render. + const GFXVertexFormat *mVertexFormat; + + /// Set by addStateBlockDesc, should be considered + /// when initPassStateBlock is called. + GFXStateBlockDesc mUserDefined; + + /// The user defined macros to pass to the + /// shader initialization. + Vector mUserMacros; + + /// Loads all the textures for all of the stages in the Material + virtual void _setStageData(); + + /// Sets the blend state for rendering + void _setBlendState(Material::BlendOp blendOp, GFXStateBlockDesc& desc ); + + /// Returns the path the material will attempt to load for a given texture filename. + String _getTexturePath(const String& filename); + + /// Loads the texture located at _getTexturePath(filename) and gives it the specified profile + GFXTexHandle _createTexture( const char *filename, GFXTextureProfile *profile ); + + /// @name State blocks + /// + /// @{ + + /// Creates the default state block templates, used by initStateBlocks + virtual void _initStateBlockTemplates(GFXStateBlockDesc& stateTranslucent, GFXStateBlockDesc& stateGlow, GFXStateBlockDesc& stateReflect); + + /// Does the base render state block setting, normally per pass + virtual void _initPassStateBlock(const Material::BlendOp blendOp, U32 numTex, const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockDesc& result); + + /// Creates the default state blocks for a list of render states + virtual void _initRenderStateStateBlocks(const Material::BlendOp blendOp, U32 numTex, const U32 texFlags[Material::MAX_TEX_PER_PASS], GFXStateBlockRef renderStates[RenderPassData::STATE_MAX]); + + /// Creates the default state blocks for each RenderPassData item + virtual void _initRenderPassDataStateBlocks(); + + /// This returns the index into the renderState array based on the sgData passed in. + virtual U32 _getRenderStateIndex( const SceneState *state, + const SceneGraphData &sgData ); + + /// Activates the correct mPasses[currPass].renderState based on scene graph info + virtual void _setRenderState( const SceneState *state, + const SceneGraphData &sgData, + U32 pass ); + /// @ +}; + +#endif diff --git a/materials/processedShaderMaterial.cpp b/materials/processedShaderMaterial.cpp new file mode 100644 index 0000000..b75b59c --- /dev/null +++ b/materials/processedShaderMaterial.cpp @@ -0,0 +1,1077 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/processedShaderMaterial.h" + +#include "core/util/safeDelete.h" +#include "gfx/sim/cubemapData.h" +#include "gfx/gfxShader.h" +#include "gfx/genericConstBuffer.h" +#include "sceneGraph/sceneState.h" +#include "shaderGen/shaderFeature.h" +#include "shaderGen/shaderGenVars.h" +#include "shaderGen/featureMgr.h" +#include "shaderGen/shaderGen.h" +#include "materials/shaderData.h" +#include "materials/sceneData.h" +#include "materials/materialFeatureTypes.h" +#include "materials/materialManager.h" +#include "materials/shaderMaterialParameters.h" +#include "materials/matTextureTarget.h" +#include "gfx/util/screenspace.h" +#include "math/util/matrixSet.h" + +/// +/// ShaderConstHandles +/// +void ShaderConstHandles::init( GFXShader *shader, ShaderData* sd /*=NULL*/ ) +{ + mDiffuseColorSC = shader->getShaderConstHandle("$diffuseMaterialColor"); + mBumpMapTexSC = shader->getShaderConstHandle(ShaderGenVars::bumpMap); + mLightMapTexSC = shader->getShaderConstHandle(ShaderGenVars::lightMap); + mLightNormMapTexSC = shader->getShaderConstHandle(ShaderGenVars::lightNormMap); + mCubeMapTexSC = shader->getShaderConstHandle(ShaderGenVars::cubeMap); + mTexMatSC = shader->getShaderConstHandle(ShaderGenVars::texMat); + mToneMapTexSC = shader->getShaderConstHandle(ShaderGenVars::toneMap); + mSpecularColorSC = shader->getShaderConstHandle(ShaderGenVars::specularColor); + mSpecularPowerSC = shader->getShaderConstHandle(ShaderGenVars::specularPower); + mParallaxInfoSC = shader->getShaderConstHandle("$parallaxInfo"); + mFogDataSC = shader->getShaderConstHandle(ShaderGenVars::fogData); + mFogColorSC = shader->getShaderConstHandle(ShaderGenVars::fogColor); + mDetailScaleSC = shader->getShaderConstHandle(ShaderGenVars::detailScale); + mVisiblitySC = shader->getShaderConstHandle(ShaderGenVars::visibility); + mColorMultiplySC = shader->getShaderConstHandle(ShaderGenVars::colorMultiply); + mAlphaTestValueSC = shader->getShaderConstHandle(ShaderGenVars::alphaTestValue); + mModelViewProjSC = shader->getShaderConstHandle(ShaderGenVars::modelview); + mWorldViewOnlySC = shader->getShaderConstHandle(ShaderGenVars::worldViewOnly); + mWorldToCameraSC = shader->getShaderConstHandle(ShaderGenVars::worldToCamera); + mWorldToObjSC = shader->getShaderConstHandle(ShaderGenVars::worldToObj); + mViewToObjSC = shader->getShaderConstHandle(ShaderGenVars::viewToObj); + mCubeTransSC = shader->getShaderConstHandle(ShaderGenVars::cubeTrans); + mObjTransSC = shader->getShaderConstHandle(ShaderGenVars::objTrans); + mCubeEyePosSC = shader->getShaderConstHandle(ShaderGenVars::cubeEyePos); + mEyePosSC = shader->getShaderConstHandle(ShaderGenVars::eyePos); + mEyePosWorldSC = shader->getShaderConstHandle(ShaderGenVars::eyePosWorld); + m_vEyeSC = shader->getShaderConstHandle(ShaderGenVars::vEye); + mEyeMatSC = shader->getShaderConstHandle(ShaderGenVars::eyeMat); + mOneOverFarplane = shader->getShaderConstHandle(ShaderGenVars::oneOverFarplane); + mAccumTimeSC = shader->getShaderConstHandle(ShaderGenVars::accumTime); + mMinnaertConstantSC = shader->getShaderConstHandle(ShaderGenVars::minnaertConstant); + mSubSurfaceParamsSC = shader->getShaderConstHandle(ShaderGenVars::subSurfaceParams); + + for (S32 i = 0; i < TEXTURE_STAGE_COUNT; ++i) + mRTParamsSC[i] = shader->getShaderConstHandle( String::ToString( "$rtParams%d", i ) ); + + // Clear any existing texture handles. + dMemset( mTexHandlesSC, 0, sizeof( mTexHandlesSC ) ); + + if(sd) + { + for (S32 i = 0; i < TEXTURE_STAGE_COUNT; ++i) + { + mTexHandlesSC[i] = shader->getShaderConstHandle(sd->getSamplerName(i)); + } + } +} + +/// +/// ShaderRenderPassData +/// +void ShaderRenderPassData::reset() +{ + Parent::reset(); + + shader = NULL; + + for ( U32 i=0; i < featureShaderHandles.size(); i++ ) + delete featureShaderHandles[i]; + + featureShaderHandles.clear(); +} + +/// +/// ProcessedShaderMaterial +/// +ProcessedShaderMaterial::ProcessedShaderMaterial() + : mDefaultParameters( NULL ) +{ + VECTOR_SET_ASSOCIATION( mShaderConstDesc ); + VECTOR_SET_ASSOCIATION( mParameterHandles ); +} + +ProcessedShaderMaterial::ProcessedShaderMaterial(Material &mat) + : mDefaultParameters( NULL ) +{ + VECTOR_SET_ASSOCIATION( mShaderConstDesc ); + VECTOR_SET_ASSOCIATION( mParameterHandles ); + mMaterial = &mat; +} + +ProcessedShaderMaterial::~ProcessedShaderMaterial() +{ + SAFE_DELETE(mDefaultParameters); + for (U32 i = 0; i < mParameterHandles.size(); i++) + SAFE_DELETE(mParameterHandles[i]); +} + +// +// Material init +// +bool ProcessedShaderMaterial::init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat, + const MatFeaturesDelegate &featuresDelegate ) +{ + // Load our textures + _setStageData(); + + // Determine how many stages we use + mMaxStages = getNumStages(); + mVertexFormat = vertexFormat; + mFeatures.clear(); + + for( U32 i=0; imCubemapData || mMaterial->mDynamicCubemap ) + { + numStages++; + continue; + } + } + + // If we have a texture for the a feature the + // stage is active. + if ( mStages[i].hasValidTex() ) + stageActive = true; + + // If this stage has specular lighting, it's active + if ( mMaterial->mPixelSpecular[i] ) + stageActive = true; + + // If this stage has diffuse color, it's active + if (mMaterial->mDiffuse[i].alpha > 0) + stageActive = true; + + // If we have a Material that is vertex lit + // then it may not have a texture + if( mMaterial->mVertLit[i] ) + stageActive = true; + + // Increment the number of active stages + numStages += stageActive; + } + + return numStages; +} + +void ProcessedShaderMaterial::_determineFeatures( U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ) +{ + PROFILE_SCOPE( ProcessedShaderMaterial_DetermineFeatures ); + + AssertFatal(GFX->getPixelShaderVersion() > 0.0 , "Cannot create a shader material if we don't support shaders"); + + bool lastStage = stageNum == (mMaxStages-1); + + // First we add all the features which the + // material has defined. + + if ( mMaterial->isTranslucent() ) + { + // Note: This is for decal blending into the prepass + // for AL... it probably needs to be made clearer. + if ( mMaterial->mTranslucentBlendOp == Material::LerpAlpha && + mMaterial->mTranslucentZWrite ) + fd.features.addFeature( MFT_IsTranslucentZWrite ); + else + fd.features.addFeature( MFT_IsTranslucent ); + } + + if ( mMaterial->mAlphaTest ) + fd.features.addFeature( MFT_AlphaTest ); + + if ( mMaterial->mEmissive[stageNum] ) + fd.features.addFeature( MFT_IsEmissive ); + + if ( mMaterial->mExposure[stageNum] == 2 ) + fd.features.addFeature( MFT_IsExposureX2 ); + + if ( mMaterial->mExposure[stageNum] == 4 ) + fd.features.addFeature( MFT_IsExposureX4 ); + + if ( mMaterial->mAnimFlags[stageNum] ) + fd.features.addFeature( MFT_TexAnim ); + + if ( !mMaterial->mEmissive[stageNum] ) + { + fd.features.addFeature( MFT_RTLighting ); + + // Only allow pixel specular if we have + // realtime lighting enabled. + if ( mMaterial->mPixelSpecular[stageNum] ) + fd.features.addFeature( MFT_PixSpecular ); + } + + if ( mMaterial->mVertLit[stageNum] ) + fd.features.addFeature( MFT_VertLit ); + + // cubemaps only available on stage 0 for now - bramage + if ( stageNum < 1 && + ( ( mMaterial->mCubemapData && mMaterial->mCubemapData->mCubemap ) || + mMaterial->mDynamicCubemap ) ) + fd.features.addFeature( MFT_CubeMap ); + + fd.features.addFeature( MFT_Visibility ); + + if ( mMaterial->mColorMultiply[stageNum].alpha > 0 ) + fd.features.addFeature( MFT_ColorMultiply ); + + if ( lastStage && + ( !gClientSceneGraph->usePostEffectFog() || + fd.features.hasFeature( MFT_IsTranslucent ) ) ) + fd.features.addFeature( MFT_Fog ); + + if ( mMaterial->mMinnaertConstant[stageNum] > 0.0f ) + fd.features.addFeature( MFT_MinnaertShading ); + + if ( mMaterial->mSubSurface[stageNum] ) + fd.features.addFeature( MFT_SubSurface ); + + // Grab other features like normal maps, base texture, etc.. + fd.features.merge( mStages[stageNum].getFeatureSet() ); + + if ( fd.features[ MFT_NormalMap ] ) + { + // If we have bump we gotta have a normal + // and tangent in our vertex format. + if ( !mVertexFormat->hasNormalAndTangent() ) + fd.features.removeFeature( MFT_NormalMap ); + else + { + // If we have a DXT5 texture we can only assume its a DXTnm if + // per-pixel specular is disabled... else we get bad results. + if ( !fd.features[MFT_PixSpecular] && + mStages[stageNum].getTex( MFT_NormalMap )->mFormat == GFXFormatDXT5 ) + fd.features.addFeature( MFT_IsDXTnm ); + } + } + + // If specular map is enabled, make sure that per-pixel specular is as well + if( !fd.features[ MFT_RTLighting ] ) + fd.features.removeFeature( MFT_SpecularMap ); + + if( fd.features[ MFT_SpecularMap ] ) + { + fd.features.addFeature( MFT_PixSpecular ); + + // Check for an alpha channel on the specular map. If it has one (and it + // has values less than 255) than the artist has put the gloss map into + // the alpha channel. + if( mStages[stageNum].getTex( MFT_SpecularMap )->mHasTransparency ) + fd.features.addFeature( MFT_GlossMap ); + } + + // Only allow parallax if we have a normal map, + // we're not using DXTnm, and we're above SM 2.0. + if ( mMaterial->mParallaxScale[stageNum] > 0.0f && + fd.features[ MFT_NormalMap ] && + !fd.features[ MFT_IsDXTnm ] && + GFX->getPixelShaderVersion() >= 2.0f ) + fd.features.addFeature( MFT_Parallax ); + + // Without a base texture try using diffuse color. + if (!fd.features[MFT_DiffuseMap]) + { + if ( mMaterial->mDiffuse[stageNum].alpha > 0 ) + fd.features.addFeature( MFT_DiffuseColor ); + + fd.features.removeFeature( MFT_OverlayMap ); + } + + // If lightmaps or tonemaps are enabled or we + // don't have a second UV set then we cannot + // use the overlay texture. + if ( fd.features[MFT_LightMap] || + fd.features[MFT_ToneMap] || + mVertexFormat->getTexCoordCount() < 2 ) + fd.features.removeFeature( MFT_OverlayMap ); + + // If tonemaps are enabled don't use lightmap + if ( fd.features[MFT_ToneMap] || mVertexFormat->getTexCoordCount() < 2 ) + fd.features.removeFeature( MFT_LightMap ); + + // Don't allow tonemaps if we don't have a second UV set + if ( mVertexFormat->getTexCoordCount() < 2 ) + fd.features.removeFeature( MFT_ToneMap ); + + // Always add the HDR output feature. + // + // It will be filtered out if it was disabled + // for this material creation below. + // + // Also the shader code will evaluate to a nop + // if HDR is not enabled in the scene. + // + fd.features.addFeature( MFT_HDROut ); + + // Allow features to add themselves. + for ( U32 i = 0; i < FEATUREMGR->getFeatureCount(); i++ ) + { + const FeatureInfo &info = FEATUREMGR->getAt( i ); + info.feature->determineFeature( mMaterial, + mVertexFormat, + stageNum, + *info.type, + features, + &fd ); + } + + // Now disable any features that were + // not part of the input feature handle. + fd.features.filter( features ); +} + +bool ProcessedShaderMaterial::_createPasses( MaterialFeatureData &stageFeatures, U32 stageNum, const FeatureSet &features ) +{ + // Creates passes for the given stage + ShaderRenderPassData passData; + U32 texIndex = 0; + + for( U32 i=0; i < FEATUREMGR->getFeatureCount(); i++ ) + { + const FeatureInfo &info = FEATUREMGR->getAt( i ); + if ( !stageFeatures.features.hasFeature( *info.type ) ) + continue; + + U32 numTexReg = info.feature->getResources( passData.mFeatureData ).numTexReg; + + // adds pass if blend op changes for feature + _setPassBlendOp( info.feature, passData, texIndex, stageFeatures, stageNum, features ); + + // Add pass if num tex reg is going to be too high + if( passData.mNumTexReg + numTexReg > GFX->getNumSamplers() ) + { + if( !_addPass( passData, texIndex, stageFeatures, stageNum, features ) ) + return false; + _setPassBlendOp( info.feature, passData, texIndex, stageFeatures, stageNum, features ); + } + + passData.mNumTexReg += numTexReg; + passData.mFeatureData.features.addFeature( *info.type ); + info.feature->setTexData( mStages[stageNum], stageFeatures, passData, texIndex ); + + // Add pass if tex units are maxed out + if( texIndex > GFX->getNumSamplers() ) + { + if( !_addPass( passData, texIndex, stageFeatures, stageNum, features ) ) + return false; + _setPassBlendOp( info.feature, passData, texIndex, stageFeatures, stageNum, features ); + } + } + + const FeatureSet &passFeatures = passData.mFeatureData.codify(); + if ( passFeatures.isNotEmpty() ) + { + mFeatures.merge( passFeatures ); + if( !_addPass( passData, texIndex, stageFeatures, stageNum, features ) ) + { + mFeatures.clear(); + return false; + } + } + + return true; +} + +void ProcessedShaderMaterial::_initMaterialParameters() +{ + // Cleanup anything left first. + SAFE_DELETE( mDefaultParameters ); + for ( U32 i = 0; i < mParameterHandles.size(); i++ ) + SAFE_DELETE( mParameterHandles[i] ); + + // Gather the shaders as they all need to be + // passed to the ShaderMaterialParameterHandles. + Vector shaders; + shaders.setSize( mPasses.size() ); + for ( U32 i = 0; i < mPasses.size(); i++ ) + shaders[i] = _getRPD(i)->shader; + + // Run through each shader and prepare its constants. + for ( U32 i = 0; i < mPasses.size(); i++ ) + { + const Vector& desc = shaders[i]->getShaderConstDesc(); + + Vector::const_iterator p = desc.begin(); + for ( ; p != desc.end(); p++ ) + { + // Add this to our list of shader constants + GFXShaderConstDesc d(*p); + mShaderConstDesc.push_back(d); + + ShaderMaterialParameterHandle* smph = new ShaderMaterialParameterHandle(d.name, shaders); + mParameterHandles.push_back(smph); + } + } +} + +bool ProcessedShaderMaterial::_addPass( ShaderRenderPassData &rpd, + U32 &texIndex, + MaterialFeatureData &fd, + U32 stageNum, + const FeatureSet &features ) +{ + // Set number of textures, stage, glow, etc. + rpd.mNumTex = texIndex; + rpd.mStageNum = stageNum; + rpd.mGlow |= mMaterial->mGlow[stageNum]; + + // Copy over features + rpd.mFeatureData.materialFeatures = fd.features; + + // Generate shader + GFXShader::setLogging( true, true ); + rpd.shader = SHADERGEN->getShader( rpd.mFeatureData, mVertexFormat, &mUserMacros ); + if( !rpd.shader ) + return false; + rpd.shaderHandles.init( rpd.shader ); + + // If a pass glows, we glow + if( rpd.mGlow ) + mHasGlow = true; + + ShaderRenderPassData *newPass = new ShaderRenderPassData( rpd ); + mPasses.push_back( newPass ); + + // Give each active feature a chance to create specialized shader consts. + for( U32 i=0; i < FEATUREMGR->getFeatureCount(); i++ ) + { + const FeatureInfo &info = FEATUREMGR->getAt( i ); + if ( !fd.features.hasFeature( *info.type ) ) + continue; + + ShaderFeatureConstHandles *fh = info.feature->createConstHandles( rpd.shader ); + if ( fh ) + newPass->featureShaderHandles.push_back( fh ); + } + + rpd.reset(); + texIndex = 0; + + return true; +} + +void ProcessedShaderMaterial::_setPassBlendOp( ShaderFeature *sf, + ShaderRenderPassData &passData, + U32 &texIndex, + MaterialFeatureData &stageFeatures, + U32 stageNum, + const FeatureSet &features ) +{ + if( sf->getBlendOp() == Material::None ) + { + return; + } + + // set up the current blend operation for multi-pass materials + if( mPasses.size() > 0) + { + // If passData.numTexReg is 0, this is a brand new pass, so set the + // blend operation to the first feature. + if( passData.mNumTexReg == 0 ) + { + passData.mBlendOp = sf->getBlendOp(); + } + else + { + // numTegReg is more than zero, if this feature + // doesn't have the same blend operation, then + // we need to create yet another pass + if( sf->getBlendOp() != passData.mBlendOp && mPasses[mPasses.size()-1]->mStageNum == stageNum) + { + _addPass( passData, texIndex, stageFeatures, stageNum, features ); + passData.mBlendOp = sf->getBlendOp(); + } + } + } +} + +// +// Runtime / rendering +// +bool ProcessedShaderMaterial::setupPass( SceneState *state, const SceneGraphData &sgData, U32 pass ) +{ + PROFILE_SCOPE( ProcessedShaderMaterial_SetupPass ); + + // Make sure we have the pass + if(pass >= mPasses.size()) + return false; + + _setRenderState( state, sgData, pass ); + + // Set shaders + ShaderRenderPassData* rpd = _getRPD(pass); + if( rpd->shader ) + { + GFX->setShader( rpd->shader ); + GFX->setShaderConstBuffer(_getShaderConstBuffer(pass)); + _setShaderConstants(state, sgData, pass); + } + else + { + GFX->disableShaders(); + GFX->setShaderConstBuffer(NULL); + } + + // Set our textures + setTextureStages( state, sgData, pass ); + _setTextureTransforms(pass); + + return true; +} + +void ProcessedShaderMaterial::cleanup(U32 pass) +{ + // Cleanup is dumb... we waste time clearing stuff + // that will be re-applied on the next draw when we + // sort by material. + + Parent::cleanup(pass); +} + +void ProcessedShaderMaterial::setTextureStages( SceneState *state, const SceneGraphData &sgData, U32 pass ) +{ + PROFILE_SCOPE( ProcessedShaderMaterial_SetTextureStages ); + + LightManager *lm = state->getLightManager(); + ShaderConstHandles *handles = _getShaderConstHandles(pass); + + // Set all of the textures we need to render the give pass. +#ifdef TORQUE_DEBUG + AssertFatal( passmNumTex; i++ ) + { + U32 currTexFlag = rpd->mTexType[i]; + if (!lm || !lm->setTextureStage(sgData, currTexFlag, i, shaderConsts, handles)) + { + switch( currTexFlag ) + { + // If the flag is unset then assume its just + // a regular texture to set... nothing special. + case 0: + if( mMaterial->isIFL() && sgData.miscTex ) + { + GFX->setTexture( i, sgData.miscTex ); + break; + } + // Fall thru to the next case. + + default: + GFX->setTexture(i, rpd->mTexSlot[i].texObject); + break; + + case Material::NormalizeCube: + GFX->setCubeTexture(i, Material::GetNormalizeCube()); + break; + + case Material::Lightmap: + GFX->setTexture( i, sgData.lightmap ); + break; + + case Material::ToneMapTex: + shaderConsts->set(handles->mToneMapTexSC, (S32)i); + GFX->setTexture(i, rpd->mTexSlot[i].texObject); + break; + + case Material::Cube: + //shaderConsts->set(handles->mCubeMapTexSC, (S32)i); + GFX->setCubeTexture( i, rpd->mCubeMap ); + break; + + case Material::SGCube: + GFX->setCubeTexture( i, sgData.cubemap ); + break; + + case Material::BackBuff: + GFX->setTexture( i, sgData.backBuffTex ); + break; + + case Material::TexTarget: + { + texTarget = rpd->mTexSlot[i].texTarget; + if ( !texTarget ) + { + GFX->setTexture( i, NULL ); + break; + } + + texObject = texTarget->getTargetTexture( 0 ); + + // If no texture is available then map the default 2x2 + // black texture to it. This at least will ensure that + // we get consistant behavior across GPUs and platforms. + if ( !texObject ) + texObject = GFXTexHandle::ZERO; + + if ( handles->mRTParamsSC[i]->isValid() && texObject ) + { + const Point3I &targetSz = texObject->getSize(); + const RectI &targetVp = texTarget->getTargetViewport(); + Point4F rtParams; + + ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams); + + shaderConsts->set(handles->mRTParamsSC[i], rtParams); + } + + GFX->setTexture( i, texObject ); + break; + } + } + } + } +} + +void ProcessedShaderMaterial::_setTextureTransforms(const U32 pass) +{ + PROFILE_SCOPE( ProcessedShaderMaterial_SetTextureTransforms ); + + ShaderConstHandles* handles = _getShaderConstHandles(pass); + if (handles->mTexMatSC->isValid()) + { + MatrixF texMat( true ); + + mMaterial->updateTimeBasedParams(); + F32 waveOffset = _getWaveOffset( pass ); // offset is between 0.0 and 1.0 + + // handle scroll anim type + if( mMaterial->mAnimFlags[pass] & Material::Scroll ) + { + if( mMaterial->mAnimFlags[pass] & Material::Wave ) + { + Point3F scrollOffset; + scrollOffset.x = mMaterial->mScrollDir[pass].x * waveOffset; + scrollOffset.y = mMaterial->mScrollDir[pass].y * waveOffset; + scrollOffset.z = 1.0; + + texMat.setColumn( 3, scrollOffset ); + } + else + { + Point3F offset( mMaterial->mScrollOffset[pass].x, + mMaterial->mScrollOffset[pass].y, + 1.0 ); + + texMat.setColumn( 3, offset ); + } + + } + + // handle rotation + if( mMaterial->mAnimFlags[pass] & Material::Rotate ) + { + if( mMaterial->mAnimFlags[pass] & Material::Wave ) + { + F32 rotPos = waveOffset * M_2PI; + texMat.set( EulerF( 0.0, 0.0, rotPos ) ); + texMat.setColumn( 3, Point3F( 0.5, 0.5, 0.0 ) ); + + MatrixF test( true ); + test.setColumn( 3, Point3F( mMaterial->mRotPivotOffset[pass].x, + mMaterial->mRotPivotOffset[pass].y, + 0.0 ) ); + texMat.mul( test ); + } + else + { + texMat.set( EulerF( 0.0, 0.0, mMaterial->mRotPos[pass] ) ); + + texMat.setColumn( 3, Point3F( 0.5, 0.5, 0.0 ) ); + + MatrixF test( true ); + test.setColumn( 3, Point3F( mMaterial->mRotPivotOffset[pass].x, + mMaterial->mRotPivotOffset[pass].y, + 0.0 ) ); + texMat.mul( test ); + } + } + + // Handle scale + wave offset + if( mMaterial->mAnimFlags[pass] & Material::Scale && + mMaterial->mAnimFlags[pass] & Material::Wave ) + { + F32 wOffset = fabs( waveOffset ); + + texMat.setColumn( 3, Point3F( 0.5, 0.5, 0.0 ) ); + + MatrixF temp( true ); + temp.setRow( 0, Point3F( wOffset, 0.0, 0.0 ) ); + temp.setRow( 1, Point3F( 0.0, wOffset, 0.0 ) ); + temp.setRow( 2, Point3F( 0.0, 0.0, wOffset ) ); + temp.setColumn( 3, Point3F( -wOffset * 0.5, -wOffset * 0.5, 0.0 ) ); + texMat.mul( temp ); + } + + // handle sequence + if( mMaterial->mAnimFlags[pass] & Material::Sequence ) + { + U32 frameNum = (U32)(MATMGR->getTotalTime() * mMaterial->mSeqFramePerSec[pass]); + F32 offset = frameNum * mMaterial->mSeqSegSize[pass]; + + Point3F texOffset = texMat.getPosition(); + texOffset.x += offset; + texMat.setPosition( texOffset ); + } + + GFXShaderConstBuffer* shaderConsts = _getShaderConstBuffer(pass); + shaderConsts->set(handles->mTexMatSC, texMat); + } +} + +//-------------------------------------------------------------------------- +// Get wave offset for texture animations using a wave transform +//-------------------------------------------------------------------------- +F32 ProcessedShaderMaterial::_getWaveOffset( U32 stage ) +{ + switch( mMaterial->mWaveType[stage] ) + { + case Material::Sin: + { + return mMaterial->mWaveAmp[stage] * mSin( M_2PI * mMaterial->mWavePos[stage] ); + break; + } + + case Material::Triangle: + { + F32 frac = mMaterial->mWavePos[stage] - mFloor( mMaterial->mWavePos[stage] ); + if( frac > 0.0 && frac <= 0.25 ) + { + return mMaterial->mWaveAmp[stage] * frac * 4.0; + } + + if( frac > 0.25 && frac <= 0.5 ) + { + return mMaterial->mWaveAmp[stage] * ( 1.0 - ((frac-0.25)*4.0) ); + } + + if( frac > 0.5 && frac <= 0.75 ) + { + return mMaterial->mWaveAmp[stage] * (frac-0.5) * -4.0; + } + + if( frac > 0.75 && frac <= 1.0 ) + { + return -mMaterial->mWaveAmp[stage] * ( 1.0 - ((frac-0.75)*4.0) ); + } + + break; + } + + case Material::Square: + { + F32 frac = mMaterial->mWavePos[stage] - mFloor( mMaterial->mWavePos[stage] ); + if( frac > 0.0 && frac <= 0.5 ) + { + return 0.0; + } + else + { + return mMaterial->mWaveAmp[stage]; + } + break; + } + + } + + return 0.0; +} + +void ProcessedShaderMaterial::_setShaderConstants(SceneState * state, const SceneGraphData &sgData, U32 pass) +{ + PROFILE_SCOPE( ProcessedShaderMaterial_SetShaderConstants ); + + GFXShaderConstBuffer* shaderConsts = _getShaderConstBuffer(pass); + ShaderConstHandles* handles = _getShaderConstHandles(pass); + U32 stageNum = getStageFromPass(pass); + + // this is OK for now, will need to change later to support different + // specular values per pass in custom materials + //------------------------- + if ( handles->mSpecularColorSC->isValid() ) + shaderConsts->set(handles->mSpecularColorSC, mMaterial->mSpecular[stageNum]); + + if ( handles->mSpecularPowerSC->isValid() ) + shaderConsts->set(handles->mSpecularPowerSC, mMaterial->mSpecularPower[stageNum]); + + if ( handles->mParallaxInfoSC->isValid() ) + shaderConsts->set(handles->mParallaxInfoSC, mMaterial->mParallaxScale[stageNum]); + + if ( handles->mMinnaertConstantSC->isValid() ) + shaderConsts->set(handles->mMinnaertConstantSC, mMaterial->mMinnaertConstant[stageNum]); + + if ( handles->mSubSurfaceParamsSC->isValid() ) + { + Point4F subSurfParams; + dMemcpy( &subSurfParams, &mMaterial->mSubSurfaceColor[stageNum], sizeof(ColorF) ); + subSurfParams.w = mMaterial->mSubSurfaceRolloff[stageNum]; + shaderConsts->set(handles->mSubSurfaceParamsSC, subSurfParams); + } + + // fog + if ( handles->mFogDataSC->isValid() ) + { + Point3F fogData; + fogData.x = sgData.fogDensity; + fogData.y = sgData.fogDensityOffset; + fogData.z = sgData.fogHeightFalloff; + shaderConsts->set( handles->mFogDataSC, fogData ); + } + if ( handles->mFogColorSC->isValid() ) + shaderConsts->set(handles->mFogColorSC, sgData.fogColor); + + // set detail scale + if ( handles->mDetailScaleSC->isValid() ) + shaderConsts->set(handles->mDetailScaleSC, mMaterial->mDetailScale[stageNum]); + + // Visibility + if ( handles->mVisiblitySC->isValid() ) + shaderConsts->set(handles->mVisiblitySC, sgData.visibility); + + // Diffuse + if ( handles->mDiffuseColorSC->isValid() ) + shaderConsts->set(handles->mDiffuseColorSC, mMaterial->mDiffuse[stageNum]); + + // Color multiply + if ( handles->mColorMultiplySC->isValid() ) + if (mMaterial->mColorMultiply[stageNum].alpha > 0.0f) + shaderConsts->set(handles->mColorMultiplySC, mMaterial->mColorMultiply[stageNum]); + + if ( handles->mAlphaTestValueSC->isValid() ) + shaderConsts->set( handles->mAlphaTestValueSC, mClampF( (F32)mMaterial->mAlphaRef / 255.0f, 0.0f, 1.0f ) ); + + if( handles->mOneOverFarplane->isValid() ) + { + const F32 &invfp = 1.0f / state->getFarPlane(); + Point4F oneOverFP(invfp, invfp, invfp, invfp); + shaderConsts->set( handles->mOneOverFarplane, oneOverFP ); + } + + if ( handles->mAccumTimeSC->isValid() ) + shaderConsts->set( handles->mAccumTimeSC, MATMGR->getTotalTime() ); +} + +bool ProcessedShaderMaterial::_hasCubemap(U32 pass) +{ + // Only support cubemap on the first stage + if( mPasses[pass]->mStageNum > 0 ) + return false; + + if( mPasses[pass]->mCubeMap ) + return true; + + return false; +} + +void ProcessedShaderMaterial::setTransforms(const MatrixSet &matrixSet, SceneState *state, const U32 pass) +{ + GFXShaderConstBuffer* shaderConsts = _getShaderConstBuffer(pass); + ShaderConstHandles* handles = _getShaderConstHandles(pass); + + if ( handles->mModelViewProjSC->isValid() ) + shaderConsts->set(handles->mModelViewProjSC, matrixSet.getWorldViewProjection()); + + if ( handles->mCubeTransSC->isValid() ) + { + if( _hasCubemap(pass) || mMaterial->mDynamicCubemap) + { + MatrixF cubeTrans = matrixSet.getObjectToWorld(); + cubeTrans.setPosition( Point3F( 0.0, 0.0, 0.0 ) ); + shaderConsts->set(handles->mCubeTransSC, cubeTrans, GFXSCT_Float3x3); + } + } + + if ( handles->mObjTransSC->isValid() ) + shaderConsts->set(handles->mObjTransSC, matrixSet.getObjectToWorld()); + + if ( handles->mWorldToObjSC->isValid() ) + shaderConsts->set(handles->mWorldToObjSC, matrixSet.getWorldToObject()); + + if ( handles->mWorldToCameraSC->isValid() ) + shaderConsts->set( handles->mWorldToCameraSC, matrixSet.getWorldToCamera() ); + + if ( handles->mWorldViewOnlySC->isValid() ) + shaderConsts->set( handles->mWorldViewOnlySC, matrixSet.getObjectToCamera() ); + + if ( handles->mViewToObjSC->isValid() ) + shaderConsts->set( handles->mViewToObjSC, matrixSet.getCameraToObject() ); + + + // vEye + if( handles->m_vEyeSC->isValid() ) + { + // vEye is the direction the camera is pointing, with length 1 / zFar + Point3F vEye; + matrixSet.getCameraToWorld().getColumn( 1, &vEye ); + vEye.normalize( 1.0f / state->getFarPlane() ); + shaderConsts->set( handles->m_vEyeSC, vEye ); + } +} + +void ProcessedShaderMaterial::setSceneInfo(SceneState * state, const SceneGraphData& sgData, U32 pass) +{ + GFXShaderConstBuffer* shaderConsts = _getShaderConstBuffer(pass); + ShaderConstHandles* handles = _getShaderConstHandles(pass); + + // Set cubemap stuff here (it's convenient!) + const Point3F &eyePosWorld = state->getCameraPosition(); + if ( handles->mCubeEyePosSC->isValid() ) + { + if(_hasCubemap(pass) || mMaterial->mDynamicCubemap) + { + Point3F cubeEyePos = eyePosWorld - sgData.objTrans.getPosition(); + shaderConsts->set(handles->mCubeEyePosSC, cubeEyePos); + } + } + + shaderConsts->set(handles->mEyePosWorldSC, eyePosWorld); + + if ( handles->mEyePosSC->isValid() ) + { + MatrixF tempMat = sgData.objTrans; + tempMat.inverse(); + Point3F eyepos; + tempMat.mulP( eyePosWorld, &eyepos ); + shaderConsts->set(handles->mEyePosSC, eyepos); + } + + if ( handles->mEyeMatSC->isValid() ) + shaderConsts->set(handles->mEyeMatSC, state->getCameraTransform()); + + // Now give the features a chance. + ShaderRenderPassData *rpd = _getRPD( pass ); + for ( U32 i=0; i < rpd->featureShaderHandles.size(); i++ ) + rpd->featureShaderHandles[i]->setConsts( state, sgData, shaderConsts ); + + LightManager* lm = state ? state->getLightManager() : NULL; + if (lm) + lm->setLightInfo(this, mMaterial, sgData, state, pass, shaderConsts); +} + +MaterialParameters* ProcessedShaderMaterial::allocMaterialParameters() +{ + ShaderMaterialParameters* smp = new ShaderMaterialParameters(); + Vector buffers( __FILE__, __LINE__ ); + buffers.setSize(mPasses.size()); + for (U32 i = 0; i < mPasses.size(); i++) + buffers[i] = _getRPD(i)->shader->allocConstBuffer(); + // smp now owns these buffers. + smp->setBuffers(mShaderConstDesc, buffers); + return smp; +} + +MaterialParameterHandle* ProcessedShaderMaterial::getMaterialParameterHandle(const String& name) +{ + // Search our list + for (U32 i = 0; i < mParameterHandles.size(); i++) + { + if (mParameterHandles[i]->getName().equal(name)) + return mParameterHandles[i]; + } + + // If we didn't find it, we have to add it to support shader reloading. + + Vector shaders; + shaders.setSize(mPasses.size()); + for (U32 i = 0; i < mPasses.size(); i++) + shaders[i] = _getRPD(i)->shader; + + ShaderMaterialParameterHandle* smph = new ShaderMaterialParameterHandle( name, shaders ); + mParameterHandles.push_back(smph); + + return smph; +} + +/// This is here to deal with the differences between ProcessedCustomMaterials and ProcessedShaderMaterials. +GFXShaderConstBuffer* ProcessedShaderMaterial::_getShaderConstBuffer( const U32 pass ) +{ + if (pass < mPasses.size()) + { + return static_cast(mCurrentParams)->getBuffer(pass); + } + return NULL; +} + +ShaderConstHandles* ProcessedShaderMaterial::_getShaderConstHandles(const U32 pass) +{ + if (pass < mPasses.size()) + { + return &_getRPD(pass)->shaderHandles; + } + return NULL; +} + +void ProcessedShaderMaterial::dumpMaterialInfo() +{ + for ( U32 i = 0; i < getNumPasses(); i++ ) + { + const ShaderRenderPassData *passData = _getRPD( i ); + + if ( passData == NULL ) + continue; + + const GFXShader *shader = passData->shader; + + if ( shader == NULL ) + Con::printf( " [%i] [NULL shader]", i ); + else + Con::printf( " [%i] %s", i, shader->describeSelf().c_str() ); + } +} diff --git a/materials/processedShaderMaterial.h b/materials/processedShaderMaterial.h new file mode 100644 index 0000000..67a06db --- /dev/null +++ b/materials/processedShaderMaterial.h @@ -0,0 +1,185 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATERIALS_PROCESSEDSHADERMATERIAL_H_ +#define _MATERIALS_PROCESSEDSHADERMATERIAL_H_ + +#ifndef _MATERIALS_PROCESSEDMATERIAL_H_ +#include "processedMaterial.h" +#endif +#ifndef _BASEMATINSTANCE_H_ +#include "materials/baseMatInstance.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif + +class GenericConstBufferLayout; +class ShaderData; +class LightInfo; +class ShaderMaterialParameterHandle; +class ShaderFeatureConstHandles; + + +class ShaderConstHandles +{ +public: + GFXShaderConstHandle* mDiffuseColorSC; + GFXShaderConstHandle* mBumpMapTexSC; + GFXShaderConstHandle* mLightMapTexSC; + GFXShaderConstHandle* mLightNormMapTexSC; + GFXShaderConstHandle* mCubeMapTexSC; + GFXShaderConstHandle* mFogMapTexSC; + GFXShaderConstHandle* mToneMapTexSC; + GFXShaderConstHandle* mTexMatSC; + GFXShaderConstHandle* mSpecularColorSC; + GFXShaderConstHandle* mSpecularPowerSC; + GFXShaderConstHandle* mParallaxInfoSC; + GFXShaderConstHandle* mFogDataSC; + GFXShaderConstHandle* mFogColorSC; + GFXShaderConstHandle* mDetailScaleSC; + GFXShaderConstHandle* mVisiblitySC; + GFXShaderConstHandle* mColorMultiplySC; + GFXShaderConstHandle* mAlphaTestValueSC; + GFXShaderConstHandle* mModelViewProjSC; + GFXShaderConstHandle* mWorldViewOnlySC; + GFXShaderConstHandle* mWorldToCameraSC; + GFXShaderConstHandle* mWorldToObjSC; + GFXShaderConstHandle* mViewToObjSC; + GFXShaderConstHandle* mCubeTransSC; + GFXShaderConstHandle* mObjTransSC; + GFXShaderConstHandle* mCubeEyePosSC; + GFXShaderConstHandle* mEyePosSC; + GFXShaderConstHandle* mEyePosWorldSC; + GFXShaderConstHandle* m_vEyeSC; + GFXShaderConstHandle* mEyeMatSC; + GFXShaderConstHandle* mOneOverFarplane; + GFXShaderConstHandle* mAccumTimeSC; + GFXShaderConstHandle* mMinnaertConstantSC; + GFXShaderConstHandle* mSubSurfaceParamsSC; + + GFXShaderConstHandle* mTexHandlesSC[TEXTURE_STAGE_COUNT]; + GFXShaderConstHandle* mRTParamsSC[TEXTURE_STAGE_COUNT]; + + void init( GFXShader* shader, ShaderData* sd = NULL ); +}; + +class ShaderRenderPassData : public RenderPassData +{ + typedef RenderPassData Parent; + +public: + + GFXShaderRef shader; + ShaderConstHandles shaderHandles; + Vector featureShaderHandles; + + virtual void reset(); +}; + +class ProcessedShaderMaterial : public ProcessedMaterial +{ + typedef ProcessedMaterial Parent; +public: + + ProcessedShaderMaterial(); + ProcessedShaderMaterial(Material &mat); + ~ProcessedShaderMaterial(); + + virtual void dumpMaterialInfo(); + + /// @name Render state setting + /// + /// @{ + + /// Binds all of the necessary textures for the given pass + virtual void setTextureStages(SceneState *, const SceneGraphData &sgData, U32 pass ); + + /// Sets the transformation matrices + virtual void setTransforms(const MatrixSet &matrixSet, SceneState *state, const U32 pass); + + /// Sets the scene info for a given pass + virtual void setSceneInfo(SceneState *, const SceneGraphData& sgData, U32 pass); + + /// @} + + virtual bool init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat, + const MatFeaturesDelegate &featuresDelegate ); + + /// Sets up the given pass for rendering + virtual bool setupPass(SceneState *, const SceneGraphData& sgData, U32 pass); + + /// Cleans up the given pass (or will anyways). + virtual void cleanup(U32 pass); + + /// Returns a shader constant block + virtual MaterialParameters* allocMaterialParameters(); + virtual MaterialParameters* getDefaultMaterialParameters() { return mDefaultParameters; } + + virtual MaterialParameterHandle* getMaterialParameterHandle(const String& name); + + /// Gets the number of stages we're using (not to be confused with the number of passes!) + virtual U32 getNumStages(); + +protected: + Vector mShaderConstDesc; + MaterialParameters* mDefaultParameters; + Vector mParameterHandles; + + /// @name Internal functions + /// + /// @{ + + /// Adds a pass for the given stage. + virtual bool _addPass( ShaderRenderPassData &rpd, + U32 &texIndex, + MaterialFeatureData &fd, + U32 stageNum, + const FeatureSet &features); + + /// Chooses a blend op for the given pass + virtual void _setPassBlendOp( ShaderFeature *sf, + ShaderRenderPassData &passData, + U32 &texIndex, + MaterialFeatureData &stageFeatures, + U32 stageNum, + const FeatureSet &features); + + /// Creates passes for the given stage + virtual bool _createPasses( MaterialFeatureData &fd, U32 stageNum, const FeatureSet &features ); + + /// Fills in the MaterialFeatureData for the given stage + virtual void _determineFeatures( U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ); + + /// Do we have a cubemap on pass? + virtual bool _hasCubemap(U32 pass); + + /// Used by setTextureTransforms + F32 _getWaveOffset( U32 stage ); + + /// Sets texture transformation matrices for texture animations such as scale and wave + virtual void _setTextureTransforms(const U32 pass); + + /// Sets all of the necessary shader constants for the given pass + virtual void _setShaderConstants(SceneState *, const SceneGraphData &sgData, U32 pass); + + /// @} + + void _setPrimaryLightConst(const LightInfo* light, const MatrixF& objTrans, const U32 stageNum); + + /// This is here to deal with the differences between ProcessedCustomMaterials and ProcessedShaderMaterials. + virtual GFXShaderConstBuffer* _getShaderConstBuffer(const U32 pass); + virtual ShaderConstHandles* _getShaderConstHandles(const U32 pass); + + /// + virtual void _initMaterialParameters(); + + ShaderRenderPassData* _getRPD(const U32 pass) { return static_cast(mPasses[pass]); } +}; + +#endif diff --git a/materials/sceneData.h b/materials/sceneData.h new file mode 100644 index 0000000..defc8e4 --- /dev/null +++ b/materials/sceneData.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SCENEDATA_H_ +#define _SCENEDATA_H_ + +#ifndef _SCENEGRAPH_H_ +#include "sceneGraph/sceneGraph.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "lighting/lightManager.h" +#endif + +struct VertexData; +class GFXTexHandle; +class GFXCubemap; + +//************************************************************************** +// Scene graph data - temp - simulates data scenegraph will provide +// +// CodeReview [btr, 7/31/2007] I'm not sure how temporary this struct is. But +// it keeps the material system separate from the SceneGraph and RenderInst +// systems. Which is kind of nice. I think eventually the RenderInst should +// get rid of the duplicate variables and just contain a SceneGraphData. +//************************************************************************** +struct SceneGraphData +{ + // textures + GFXTextureObject * lightmap; + GFXTextureObject * backBuffTex; + GFXTextureObject * reflectTex; + GFXTextureObject * miscTex; + + /// The current lights to use in rendering + /// in order of the light importance. + LightInfo* lights[8]; + + // fog + F32 fogDensity; + F32 fogDensityOffset; + F32 fogHeightFalloff; + ColorF fogColor; + + /// The special bin types. + enum BinType + { + /// A render bin that isn't one of the + /// special bins we care about. + OtherBin = 0, + + /// The glow render bin. + /// @see RenderGlowMgr + GlowBin, + + /// The prepass render bin. + /// @RenderPrePassMgr + PrePassBin, + }; + + /// This defines when we're rendering a special bin + /// type that the material or lighting system needs + /// to know about. + BinType binType; + + // misc + MatrixF objTrans; + VertexData * vertData; + GFXCubemap * cubemap; + F32 visibility; + + /// Enables wireframe rendering for the object. + bool wireframe; + + /// A generic hint value passed from the game + /// code down to the material for use by shader + /// features. + void *materialHint; + + //----------------------------------------------------------------------- + // Constructor + //----------------------------------------------------------------------- + SceneGraphData() : lightmap() + { + reset(); + } + + inline void reset() + { + dMemset( this, 0, sizeof( SceneGraphData ) ); + visibility = 1.0f; + } + + inline void setFogParams( const FogData &data ) + { + // Fogging... + fogDensity = data.density; + fogDensityOffset = data.densityOffset; + if ( !mIsZero( data.atmosphereHeight ) ) + fogHeightFalloff = 1.0f / data.atmosphereHeight; + else + fogHeightFalloff = 0.0f; + + fogColor = data.color; + } +}; + +#endif // _SCENEDATA_H_ diff --git a/materials/shaderData.cpp b/materials/shaderData.cpp new file mode 100644 index 0000000..af0dd36 --- /dev/null +++ b/materials/shaderData.cpp @@ -0,0 +1,224 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "materials/shaderData.h" + +#include "console/consoleTypes.h" +#include "gfx/gfxDevice.h" +#include "core/strings/stringUnit.h" +#include "lighting/lightManager.h" + +using namespace Torque; + + +Vector ShaderData::smAllShaderData; + + +IMPLEMENT_CONOBJECT( ShaderData ); + +ShaderData::ShaderData() +{ + VECTOR_SET_ASSOCIATION( mShaderMacros ); + + mUseDevicePixVersion = false; + mPixVersion = 1.0; +} + +void ShaderData::initPersistFields() +{ + addField("DXVertexShaderFile", TypeStringFilename, Offset(mDXVertexShaderName, ShaderData)); + addField("DXPixelShaderFile", TypeStringFilename, Offset(mDXPixelShaderName, ShaderData)); + + addField("OGLVertexShaderFile", TypeStringFilename, Offset(mOGLVertexShaderName, ShaderData)); + addField("OGLPixelShaderFile", TypeStringFilename, Offset(mOGLPixelShaderName, ShaderData)); + + addField("samplerNames", TypeRealString, Offset(mSamplerNames, ShaderData), TEXTURE_STAGE_COUNT); + + addField("useDevicePixVersion", TypeBool, Offset(mUseDevicePixVersion, ShaderData)); + addField("pixVersion", TypeF32, Offset(mPixVersion, ShaderData)); + addField("defines", TypeRealString, Offset(mDefines, ShaderData)); + + Parent::initPersistFields(); + + // Make sure we get activation signals. + LightManager::smActivateSignal.notify( &ShaderData::_onLMActivate ); +} + +bool ShaderData::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + mShaderMacros.clear(); + + // Keep track of it. + smAllShaderData.push_back( this ); + + // NOTE: We initialize the shader on request. + + return true; +} + +void ShaderData::onRemove() +{ + // Remove it from the all shaders list. + smAllShaderData.remove( this ); + + Parent::onRemove(); +} + +const Vector& ShaderData::_getMacros() +{ + // If they have already been processed then + // return the cached result. + if ( mShaderMacros.size() != 0 || mDefines.isEmpty() ) + return mShaderMacros; + + mShaderMacros.clear(); + GFXShaderMacro macro; + const U32 defineCount = StringUnit::getUnitCount( mDefines, ";\n\t" ); + for ( U32 i=0; i < defineCount; i++ ) + { + String define = StringUnit::getUnit( mDefines, i, ";\n\t" ); + + macro.name = StringUnit::getUnit( define, 0, "=" ); + macro.value = StringUnit::getUnit( define, 1, "=" ); + mShaderMacros.push_back( macro ); + } + + return mShaderMacros; +} + +GFXShader* ShaderData::getShader( const Vector ¯os ) +{ + PROFILE_SCOPE( ShaderData_GetShader ); + + // Combine the dynamic macros with our script defined macros. + Vector finalMacros; + finalMacros.merge( _getMacros() ); + finalMacros.merge( macros ); + + // Convert the final macro list to a string. + String cacheKey; + GFXShaderMacro::stringize( macros, &cacheKey ); + + // Lookup the shader for this instance. + ShaderCache::Iterator iter = mShaders.find( cacheKey ); + if ( iter != mShaders.end() ) + return iter->value; + + // Create the shader instance... if it fails then + // bail out and return nothing to the caller. + GFXShader *shader = _createShader( finalMacros ); + if ( !shader ) + return NULL; + + // Store the shader in the cache and return it. + mShaders.insertUnique( cacheKey, shader ); + return shader; +} + +void ShaderData::mapSamplerNames( GFXShaderConstBufferRef constBuffer ) +{ + if ( constBuffer.isNull() ) + return; + + GFXShader *shader = constBuffer->getShader(); + + for ( U32 i = 0; i < TEXTURE_STAGE_COUNT; i++ ) + { + GFXShaderConstHandle *handle = shader->getShaderConstHandle( mSamplerNames[i] ); + if ( handle->isValid() ) + constBuffer->set( handle, (S32)i ); + } +} + +GFXShader* ShaderData::_createShader( const Vector ¯os ) +{ + F32 pixver = mPixVersion; + if ( mUseDevicePixVersion ) + pixver = getMax( pixver, GFX->getPixelShaderVersion() ); + + // Enable shader error logging. + GFXShader::setLogging( true, true ); + + GFXShader *shader = GFX->createShader(); + bool success = false; + + // Initialize the right shader type. + switch( GFX->getAdapterType() ) + { + case Direct3D9_360: + case Direct3D9: + { + success = shader->init( mDXVertexShaderName, + mDXPixelShaderName, + pixver, + macros ); + break; + } + + case OpenGL: + { + success = shader->init( mOGLVertexShaderName, + mOGLPixelShaderName, + pixver, + macros ); + break; + } + + default: + // Other device types are assumed to not support shaders. + success = false; + break; + } + + // If we failed to load the shader then + // cleanup and return NULL. + if ( !success ) + SAFE_DELETE( shader ); + + return shader; +} + +void ShaderData::reloadShaders() +{ + ShaderCache::Iterator iter = mShaders.begin(); + for ( ; iter != mShaders.end(); iter++ ) + iter->value->reload(); +} + +void ShaderData::reloadAllShaders() +{ + Vector::iterator iter = smAllShaderData.begin(); + for ( ; iter != smAllShaderData.end(); iter++ ) + (*iter)->reloadShaders(); +} + +void ShaderData::_onLMActivate( const char *lm, bool activate ) +{ + // Only on activations do we do anything. + if ( !activate ) + return; + + // Since the light manager usually swaps shadergen features + // and changes system wide shader defines we need to completely + // flush and rebuild all shaders. + + reloadAllShaders(); +} + +const String& ShaderData::getSamplerName(U32 idx) +{ + AssertFatal(idx < TEXTURE_STAGE_COUNT, "ShaderData::getSamplerName - idx out of range"); + return mSamplerNames[idx]; +} + +ConsoleMethod(ShaderData, reload, void, 2, 2, + "Rebuilds all the vertex and pixel shaders instances created from this ShaderData.") +{ + object->reloadShaders(); +} diff --git a/materials/shaderData.h b/materials/shaderData.h new file mode 100644 index 0000000..cecd2da --- /dev/null +++ b/materials/shaderData.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERTDATA_H_ +#define _SHADERTDATA_H_ + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif + +class GFXShader; +class ShaderData; +struct GFXShaderMacro; + + +/// +class ShaderData : public SimObject +{ + typedef SimObject Parent; + +protected: + + /// + static Vector smAllShaderData; + + typedef HashTable ShaderCache; + + ShaderCache mShaders; + + bool mUseDevicePixVersion; + + F32 mPixVersion; + + FileName mDXVertexShaderName; + + FileName mDXPixelShaderName; + + FileName mOGLVertexShaderName; + + FileName mOGLPixelShaderName; + + String mSamplerNames[TEXTURE_STAGE_COUNT]; + + /// A semicolon, tab, or newline delimited string of case + /// sensitive defines that are passed to the shader compiler. + /// + /// For example: + /// + /// SAMPLE_TAPS=10;USE_TEXKILL;USE_TORQUE_FOG=1 + /// + String mDefines; + + /// The shader macros built from mDefines. + /// @see _getMacros() + Vector mShaderMacros; + + /// Returns the shader macros taking care to rebuild + /// them if the content has changed. + const Vector& _getMacros(); + + /// Helper for converting an array of macros + /// into a formatted string. + void _stringizeMacros( const Vector ¯os, + String *outString ); + + /// Creates a new shader returning NULL on error. + GFXShader* _createShader( const Vector ¯os ); + + /// @see LightManager::smActivateSignal + static void _onLMActivate( const char *lm, bool activate ); + +public: + + + ShaderData(); + + /// Returns an initialized shader instance or NULL + /// if the shader failed to be created. + GFXShader* getShader( const Vector ¯os = Vector() ); + + /// Forces a reinitialization of all the instanced shaders. + void reloadShaders(); + + /// Forces a reinitialization of the instanced shaders for + /// all loaded ShaderData objects in the system. + static void reloadAllShaders(); + + void mapSamplerNames( GFXShaderConstBufferRef constBuffer ); + + const String& getSamplerName(U32 idx); + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + + // ConsoleObject + static void initPersistFields(); + DECLARE_CONOBJECT(ShaderData); +}; + +#endif // _SHADERTDATA_H_ \ No newline at end of file diff --git a/materials/shaderMaterialParameters.cpp b/materials/shaderMaterialParameters.cpp new file mode 100644 index 0000000..8f8103f --- /dev/null +++ b/materials/shaderMaterialParameters.cpp @@ -0,0 +1,210 @@ +#include "materials/shaderMaterialParameters.h" +#include "console/console.h" + +// +// ShaderMaterialParameters +// +ShaderMaterialParameterHandle::ShaderMaterialParameterHandle(const String& name) +{ + VECTOR_SET_ASSOCIATION( mHandles ); + + mName = name; + mValid = false; +} + +ShaderMaterialParameterHandle::ShaderMaterialParameterHandle(const String& name, Vector& shaders) +{ + VECTOR_SET_ASSOCIATION( mHandles ); + + mName = name; + mValid = false; + mHandles.setSize(shaders.size()); + + for (U32 i = 0; i < shaders.size(); i++) + { + mHandles[i] = shaders[i]->getShaderConstHandle(name); + mValid |= mHandles[i]->isValid(); + } +} + +ShaderMaterialParameterHandle::~ShaderMaterialParameterHandle() +{ +} + +S32 ShaderMaterialParameterHandle::getSamplerRegister( U32 pass ) const +{ + AssertFatal( mHandles.size() > pass, "ShaderMaterialParameterHandle::getSamplerRegister - out of bounds" ); + return mHandles[pass]->getSamplerRegister(); +} + +// +// ShaderMaterialParameters +// +ShaderMaterialParameters::ShaderMaterialParameters() +: MaterialParameters() +{ + VECTOR_SET_ASSOCIATION( mBuffers ); +} + +ShaderMaterialParameters::~ShaderMaterialParameters() +{ + releaseBuffers(); +} + +void ShaderMaterialParameters::setBuffers(Vector& constDesc, Vector& buffers) +{ + mShaderConstDesc = constDesc; + mBuffers = buffers; +} + +void ShaderMaterialParameters::releaseBuffers() +{ + for (U32 i = 0; i < mBuffers.size(); i++) + { + mBuffers[i] = NULL; + } + mBuffers.setSize(0); +} + +U32 ShaderMaterialParameters::getAlignmentValue(const GFXShaderConstType constType) +{ + if (mBuffers.size() > 0) + return mBuffers[0]->getShader()->getAlignmentValue(constType); + else + return 0; +} + +#define SHADERMATPARAM_SET(handle, f) \ + if ((!handle) || !handle->isValid()) \ + return; \ + AssertFatal(dynamic_cast(handle), "Invalid handle type!"); \ + ShaderMaterialParameterHandle* h = static_cast(handle); \ + AssertFatal(h->mHandles.size() == mBuffers.size(), "Handle length differs from buffer length!"); \ + for (U32 i = 0; i < h->mHandles.size(); i++) \ +{ \ + GFXShaderConstHandle* shaderHandle = h->mHandles[i]; \ + if (shaderHandle->isValid()) \ + mBuffers[i]->set(shaderHandle, f); \ +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const F32 f) +{ + SHADERMATPARAM_SET(handle, f); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const Point2F& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const Point3F& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const Point4F& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const PlaneF& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const ColorF& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const S32 f) +{ + SHADERMATPARAM_SET(handle, f); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const Point2I& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const Point3I& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const Point4I& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const AlignedArray& fv) +{ + SHADERMATPARAM_SET(handle, fv); +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType) +{ + if ((!handle) || !handle->isValid()) + return; + AssertFatal(dynamic_cast(handle), "Invalid handle type!"); + ShaderMaterialParameterHandle* h = static_cast(handle); + AssertFatal(h->mHandles.size() == mBuffers.size(), "Handle length differs from buffer length!"); + for (U32 i = 0; i < h->mHandles.size(); i++) + { + GFXShaderConstHandle* shaderHandle = h->mHandles[i]; + if (shaderHandle && shaderHandle->isValid()) + mBuffers[i]->set(shaderHandle, mat, matrixType); + } +} + +void ShaderMaterialParameters::set(MaterialParameterHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType) +{ + if ((!handle) || !handle->isValid()) + return; + AssertFatal(dynamic_cast(handle), "Invalid handle type!"); + ShaderMaterialParameterHandle* h = static_cast(handle); + AssertFatal(h->mHandles.size() == mBuffers.size(), "Handle length differs from buffer length!"); + for (U32 i = 0; i < h->mHandles.size(); i++) + { + GFXShaderConstHandle* shaderHandle = h->mHandles[i]; + if (shaderHandle && shaderHandle->isValid()) + mBuffers[i]->set(shaderHandle, mat, arraySize, matrixType); + } +} + +#undef SHADERMATPARAM_SET diff --git a/materials/shaderMaterialParameters.h b/materials/shaderMaterialParameters.h new file mode 100644 index 0000000..2df5926 --- /dev/null +++ b/materials/shaderMaterialParameters.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERMATERIALPARAMETERS_H_ +#define _SHADERMATERIALPARAMETERS_H_ + +#ifndef _MATERIALPARAMETERS_H_ +#include "materials/materialParameters.h" +#endif + +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif + +class ShaderMaterialParameterHandle : public MaterialParameterHandle +{ + friend class ShaderMaterialParameters; +public: + virtual ~ShaderMaterialParameterHandle(); + + ShaderMaterialParameterHandle(const String& name); + ShaderMaterialParameterHandle(const String& name, Vector& shaders); + + virtual const String& getName() const { return mName; } + virtual bool isValid() const { return mValid; }; + virtual S32 getSamplerRegister( U32 pass ) const; + +protected: + Vector< GFXShaderConstHandle* > mHandles; + bool mValid; + String mName; +}; + +// This is the union of all of the shaders contained in a material +class ShaderMaterialParameters : public MaterialParameters +{ +public: + ShaderMaterialParameters(); + virtual ~ShaderMaterialParameters(); + + void setBuffers(Vector& constDesc, Vector& buffers); + GFXShaderConstBuffer* getBuffer(U32 i) { return mBuffers[i]; } + + /// + /// MaterialParameter interface + /// + + /// Returns the material parameter handle for name. + virtual void set(MaterialParameterHandle* handle, const F32 f); + virtual void set(MaterialParameterHandle* handle, const Point2F& fv); + virtual void set(MaterialParameterHandle* handle, const Point3F& fv); + virtual void set(MaterialParameterHandle* handle, const Point4F& fv); + virtual void set(MaterialParameterHandle* handle, const PlaneF& fv); + virtual void set(MaterialParameterHandle* handle, const ColorF& fv); + virtual void set(MaterialParameterHandle* handle, const S32 f); + virtual void set(MaterialParameterHandle* handle, const Point2I& fv); + virtual void set(MaterialParameterHandle* handle, const Point3I& fv); + virtual void set(MaterialParameterHandle* handle, const Point4I& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const AlignedArray& fv); + virtual void set(MaterialParameterHandle* handle, const MatrixF& mat, const GFXShaderConstType matrixType = GFXSCT_Float4x4); + virtual void set(MaterialParameterHandle* handle, const MatrixF* mat, const U32 arraySize, const GFXShaderConstType matrixType = GFXSCT_Float4x4); + + virtual U32 getAlignmentValue(const GFXShaderConstType constType); + +private: + Vector mBuffers; + + void releaseBuffers(); +}; + +#endif diff --git a/math/mAngAxis.cpp b/math/mAngAxis.cpp new file mode 100644 index 0000000..d3ac2d1 --- /dev/null +++ b/math/mAngAxis.cpp @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mAngAxis.h" +#include "math/mQuat.h" +#include "math/mMatrix.h" + +AngAxisF & AngAxisF::set( const QuatF & q ) +{ + angle = 2.0f * mAcos( q.w ); + F32 sinHalfAngle = mSqrt(q.x * q.x + q.y * q.y + q.z * q.z); + if (sinHalfAngle != 0.0f) + axis.set( q.x / sinHalfAngle, q.y / sinHalfAngle, q.z / sinHalfAngle ); + else + axis.set(1.0f,0.0f,0.0f); + return *this; +} + +AngAxisF & AngAxisF::set( const MatrixF & mat ) +{ + QuatF q( mat ); + set( q ); + return *this; +} + +MatrixF * AngAxisF::setMatrix( MatrixF * mat ) const +{ + QuatF q( *this ); + return q.setMatrix( mat ); +} + +void AngAxisF::RotateX(F32 angle, MatrixF * mat) +{ + // for now...do it the easy way + AngAxisF rotX(Point3F(1.0f,0.0f,0.0f),angle); + rotX.setMatrix(mat); +} + +void AngAxisF::RotateY(F32 angle, MatrixF * mat) +{ + // for now...do it the easy way + AngAxisF rotY(Point3F(0.0f,1.0f,0.0f),angle); + rotY.setMatrix(mat); +} + +void AngAxisF::RotateZ(F32 angle, MatrixF * mat) +{ + // for now...do it the easy way + AngAxisF rotZ(Point3F(0.0f,0.0f,1.0f),angle); + rotZ.setMatrix(mat); +} + +void AngAxisF::RotateX(F32 angle, const Point3F & from, Point3F * to) +{ + // for now...do it the easy way + MatrixF mat; + AngAxisF::RotateX(angle,&mat); + mat.mulV(from,to); +} + +void AngAxisF::RotateY(F32 angle, const Point3F & from, Point3F * to) +{ + // for now...do it the easy way + MatrixF mat; + AngAxisF::RotateY(angle,&mat); + mat.mulV(from,to); +} + +void AngAxisF::RotateZ(F32 angle, const Point3F & from, Point3F * to) +{ + // for now...do it the easy way + MatrixF mat; + AngAxisF::RotateZ(angle,&mat); + mat.mulV(from,to); +} + diff --git a/math/mAngAxis.h b/math/mAngAxis.h new file mode 100644 index 0000000..94bcbcb --- /dev/null +++ b/math/mAngAxis.h @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MANGAXIS_H_ +#define _MANGAXIS_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +class MatrixF; +class QuatF; + +//---------------------------------------------------------------------------- +// rotation about an arbitrary axis through the origin: + +class AngAxisF +{ + public: + Point3F axis; + F32 angle; + + AngAxisF(); + AngAxisF( const Point3F & _axis, F32 _angle ); + explicit AngAxisF( const MatrixF &m ); + explicit AngAxisF( const QuatF &q ); + + AngAxisF& set( const Point3F & _axis, F32 _angle ); + AngAxisF& set( const MatrixF & m ); + AngAxisF& set( const QuatF & q ); + + bool operator ==( const AngAxisF & c ) const; + bool operator !=( const AngAxisF & c ) const; + + MatrixF * setMatrix( MatrixF * mat ) const; + + static void RotateX(F32 angle, MatrixF * mat); + static void RotateY(F32 angle, MatrixF * mat); + static void RotateZ(F32 angle, MatrixF * mat); + + static void RotateX(F32 angle, const Point3F & from, Point3F * to); + static void RotateY(F32 angle, const Point3F & from, Point3F * to); + static void RotateZ(F32 angle, const Point3F & from, Point3F * to); +}; + +//---------------------------------------------------------------------------- +// AngAxisF implementation: + +inline AngAxisF::AngAxisF() +{ +} + +inline AngAxisF::AngAxisF( const Point3F & _axis, F32 _angle ) +{ + set(_axis,_angle); +} + +inline AngAxisF::AngAxisF( const MatrixF & mat ) +{ + set(mat); +} + +inline AngAxisF::AngAxisF( const QuatF & quat ) +{ + set(quat); +} + +inline AngAxisF& AngAxisF::set( const Point3F & _axis, F32 _angle ) +{ + axis = _axis; + angle = _angle; + return *this; +} + +inline bool AngAxisF::operator ==( const AngAxisF & c ) const +{ + return mFabs(angle-c.angle) < 0.0001f && (axis == c.axis); +} + +inline bool AngAxisF::operator !=( const AngAxisF & c ) const +{ + return !(*this == c); +} + +#endif // _MANGAXIS_H_ diff --git a/math/mBox.cpp b/math/mBox.cpp new file mode 100644 index 0000000..b28fd78 --- /dev/null +++ b/math/mBox.cpp @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMatrix.h" + +const Box3F Box3F::Invalid( F32_MAX, F32_MAX, F32_MAX, -F32_MAX, -F32_MAX, -F32_MAX ); +const Box3F Box3F::Max( -F32_MAX, -F32_MAX, -F32_MAX, F32_MAX, F32_MAX, F32_MAX ); +const Box3F Box3F::Zero( 0, 0, 0, 0, 0, 0 ); + +bool Box3F::collideLine(const Point3F& start, const Point3F& end, F32* t, Point3F* n) const +{ + // Collide against bounding box. Need at least this for the editor. + F32 st,et; + F32 fst = 0; + F32 fet = 1; + const F32* bmin = &minExtents.x; + const F32* bmax = &maxExtents.x; + const F32* si = &start.x; + const F32* ei = &end.x; + + static const Point3F na[3] = { Point3F(1.0f, 0.0f, 0.0f), Point3F(0.0f, 1.0f, 0.0f), Point3F(0.0f, 0.0f, 1.0f) }; + Point3F finalNormal(0.0f, 0.0f, 0.0f); + + for (int i = 0; i < 3; i++) { + bool n_neg = false; + if (si[i] < ei[i]) { + if (si[i] > bmax[i] || ei[i] < bmin[i]) + return false; + F32 di = ei[i] - si[i]; + st = (si[i] < bmin[i]) ? (bmin[i] - si[i]) / di : 0.0f; + et = (ei[i] > bmax[i]) ? (bmax[i] - si[i]) / di : 1.0f; + n_neg = true; + } + else { + if (ei[i] > bmax[i] || si[i] < bmin[i]) + return false; + F32 di = ei[i] - si[i]; + st = (si[i] > bmax[i]) ? (bmax[i] - si[i]) / di : 0.0f; + et = (ei[i] < bmin[i]) ? (bmin[i] - si[i]) / di : 1.0f; + } + if (st > fst) { + fst = st; + finalNormal = na[i]; + if ( n_neg ) + finalNormal.neg(); + } + if (et < fet) + fet = et; + + if (fet < fst) + return false; + } + + *t = fst; + *n = finalNormal; + return true; +} + + +bool Box3F::collideLine(const Point3F& start, const Point3F& end) const +{ + F32 t; + Point3F normal; + return collideLine(start, end, &t, &normal); +} + +// returns true if "oriented" box collides with us +// radiiB is dimension of incoming box (half x,y,z length +// toA is transform that takes incoming box into our space +// assumes incoming box is centered at origin of source space +bool Box3F::collideOrientedBox(const Point3F & bRadii, const MatrixF & toA) const +{ + Point3F p; + toA.getColumn(3,&p); + Point3F aCenter = minExtents + maxExtents; + aCenter *= 0.5f; + p -= aCenter; // this essentially puts origin of toA target space on the center of the current box + Point3F aRadii = maxExtents - minExtents; + aRadii *= 0.5f; + + F32 absXX,absXY,absXZ; + F32 absYX,absYY,absYZ; + F32 absZX,absZY,absZZ; + + const F32 * f = toA; + + absXX = mFabs(f[0]); + absYX = mFabs(f[1]); + absZX = mFabs(f[2]); + + if (aRadii.x + bRadii.x * absXX + bRadii.y * absYX + bRadii.z * absZX - mFabs(p.x)<0.0f) + return false; + + absXY = mFabs(f[4]); + absYY = mFabs(f[5]); + absZY = mFabs(f[6]); + if (aRadii.y + bRadii.x * absXY + bRadii.y * absYY + bRadii.z * absZY - mFabs(p.y)<0.0f) + return false; + + absXZ = mFabs(f[8]); + absYZ = mFabs(f[9]); + absZZ = mFabs(f[10]); + if (aRadii.z + bRadii.x * absXZ + bRadii.y * absYZ + bRadii.z * absZZ - mFabs(p.z)<0.0f) + return false; + + if (aRadii.x*absXX + aRadii.y*absXY + aRadii.z*absXZ + bRadii.x - mFabs(p.x*f[0] + p.y*f[4] + p.z*f[8])<0.0f) + return false; + + if (aRadii.x*absYX + aRadii.y*absYY + aRadii.z*absYZ + bRadii.y - mFabs(p.x*f[1] + p.y*f[5] + p.z*f[9])<0.0f) + return false; + + if (aRadii.x*absZX + aRadii.y*absZY + aRadii.z*absZZ + bRadii.z - mFabs(p.x*f[2] + p.y*f[6] + p.z*f[10])<0.0f) + return false; + + if (mFabs(p.z*f[4] - p.y*f[8]) > + aRadii.y * absXZ + aRadii.z * absXY + + bRadii.y * absZX + bRadii.z * absYX) + return false; + + if (mFabs(p.z*f[5] - p.y*f[9]) > + aRadii.y * absYZ + aRadii.z * absYY + + bRadii.x * absZX + bRadii.z * absXX) + return false; + + if (mFabs(p.z*f[6] - p.y*f[10]) > + aRadii.y * absZZ + aRadii.z * absZY + + bRadii.x * absYX + bRadii.y * absXX) + return false; + + if (mFabs(p.x*f[8] - p.z*f[0]) > + aRadii.x * absXZ + aRadii.z * absXX + + bRadii.y * absZY + bRadii.z * absYY) + return false; + + if (mFabs(p.x*f[9] - p.z*f[1]) > + aRadii.x * absYZ + aRadii.z * absYX + + bRadii.x * absZY + bRadii.z * absXY) + return false; + + if (mFabs(p.x*f[10] - p.z*f[2]) > + aRadii.x * absZZ + aRadii.z * absZX + + bRadii.x * absYY + bRadii.y * absXY) + return false; + + if (mFabs(p.y*f[0] - p.x*f[4]) > + aRadii.x * absXY + aRadii.y * absXX + + bRadii.y * absZZ + bRadii.z * absYZ) + return false; + + if (mFabs(p.y*f[1] - p.x*f[5]) > + aRadii.x * absYY + aRadii.y * absYX + + bRadii.x * absZZ + bRadii.z * absXZ) + return false; + + if (mFabs(p.y*f[2] - p.x*f[6]) > + aRadii.x * absZY + aRadii.y * absZX + + bRadii.x * absYZ + bRadii.y * absXZ) + return false; + + return true; +} + + + diff --git a/math/mBox.h b/math/mBox.h new file mode 100644 index 0000000..0bee4cc --- /dev/null +++ b/math/mBox.h @@ -0,0 +1,625 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MBOX_H_ +#define _MBOX_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif + +class MatrixF; + +//------------------------------------------------------------------------------ +/// Bounding Box +/// +/// A helper class for working with boxes. It runs at F32 precision. +/// +/// @see Box3D +class Box3F +{ + public: + + Point3F minExtents; ///< Minimum extents of box + Point3F maxExtents; ///< Maximum extents of box + + public: + Box3F() { } + + /// Create a box from two points. + /// + /// Normally, this function will compensate for mismatched + /// min/max values. If you know your values are valid, you + /// can set in_overrideCheck to true and skip this. + /// + /// @param in_rMin Minimum extents of box. + /// @param in_rMax Maximum extents of box. + /// @param in_overrideCheck Pass true to skip check of extents. + Box3F(const Point3F& in_rMin, const Point3F& in_rMax, const bool in_overrideCheck = false); + + /// Create a box from six extent values. + /// + /// No checking is performed as to the validity of these + /// extents, unlike the other constructor. + Box3F(F32 xmin, F32 ymin, F32 zmin, F32 max, F32 ymax, F32 zmax); + + Box3F(F32 cubeSize); + + void set(const Point3F& in_rMin, const Point3F& in_rMax); + + /// Create box around origin given lengths + void set(const Point3F& in_Length); + + /// Recenter the box + void setCenter(const Point3F& center); + + /// Check to see if a point is contained in this box. + bool isContained(const Point3F& in_rContained) const; + + /// Check if the Point2F is within the box xy extents. + bool isContained( const Point2F &pt ) const; + + /// Check to see if another box overlaps this box. + bool isOverlapped(const Box3F& in_rOverlap) const; + + /// Check to see if another box is contained in this box. + bool isContained(const Box3F& in_rContained) const; + + F32 len_x() const; + F32 len_y() const; + F32 len_z() const; + F32 len() const { return (maxExtents - minExtents).len(); } + F32 len(S32 axis) const { return ((F32*)&maxExtents.x)[axis] - ((F32*)&minExtents.x)[axis]; } + + /// Perform an intersection operation with another box + /// and store the results in this box. + void intersect(const Box3F& in_rIntersect); + void intersect(const Point3F& in_rIntersect); + + /// Get the center of this box. + /// + /// This is the average of min and max. + void getCenter(Point3F* center) const; + Point3F getCenter() const; + + /// Returns the max minus the min extents. + Point3F getExtents() const; + + /// Collide a line against the box. + /// + /// @param start Start of line. + /// @param end End of line. + /// @param t Value from 0.0-1.0, indicating position + /// along line of collision. + /// @param n Normal of collision. + bool collideLine(const Point3F& start, const Point3F& end, F32*t, Point3F*n) const; + + /// Collide a line against the box. + /// + /// Returns true on collision. + bool collideLine(const Point3F& start, const Point3F& end) const; + + /// Collide an oriented box against the box. + /// + /// Returns true if "oriented" box collides with us. + /// Assumes incoming box is centered at origin of source space. + /// + /// @param radii The dimension of incoming box (half x,y,z length). + /// @param toUs A transform that takes incoming box into our space. + bool collideOrientedBox(const Point3F & radii, const MatrixF & toUs) const; + + /// Check that the box is valid. + /// + /// Currently, this just means that min < max. + bool isValidBox() const { return (minExtents.x <= maxExtents.x) && + (minExtents.y <= maxExtents.y) && + (minExtents.z <= maxExtents.z); } + + /// Return the closest point of the box, relative to the passed point. + Point3F getClosestPoint(const Point3F& refPt) const; + + /// Return distance of closest point on box to refPt. + F32 getDistanceToPoint(const Point3F& refPt) const; + + /// Return the squared distance to closest point on box to refPt. + F32 getSqDistanceToPoint(const Point3F& refPt) const; + + /// Extend the box to include point. + /// @see Invalid + void extend(const Point3F & p); + + /// Scale the box by a Point3F or F32 + void scale( const Point3F &amt ); + void scale( F32 amt ); + + /// Equality operator. + bool operator ==( const Box3F &b ) const; + + /// Inequality operator. + bool operator !=( const Box3F &b ) const; + + public: + + /// An inverted bounds where the minimum point is positive + /// and the maximum point is negative. Should be used with + /// extend() to construct a minimum volume box. + /// @see extend + static const Box3F Invalid; + + /// A box that covers the entire floating point range. + static const Box3F Max; + + /// A null box of zero size. + static const Box3F Zero; +}; + +inline Box3F::Box3F(const Point3F& in_rMin, const Point3F& in_rMax, const bool in_overrideCheck) + : minExtents(in_rMin), + maxExtents(in_rMax) +{ + if (in_overrideCheck == false) { + minExtents.setMin(in_rMax); + maxExtents.setMax(in_rMin); + } +} + +inline Box3F::Box3F(F32 xMin, F32 yMin, F32 zMin, F32 xMax, F32 yMax, F32 zMax) + : minExtents(xMin,yMin,zMin), + maxExtents(xMax,yMax,zMax) +{ +} + +inline Box3F::Box3F(F32 cubeSize) + : minExtents(-0.5f * cubeSize, -0.5f * cubeSize, -0.5f * cubeSize), + maxExtents(0.5f * cubeSize, 0.5f * cubeSize, 0.5f * cubeSize) +{ +} + +inline void Box3F::set(const Point3F& in_rMin, const Point3F& in_rMax) +{ + minExtents.set(in_rMin); + maxExtents.set(in_rMax); +} + +inline void Box3F::set(const Point3F& in_Length) +{ + minExtents.set(-in_Length.x * 0.5f, -in_Length.y * 0.5f, -in_Length.z * 0.5f); + maxExtents.set( in_Length.x * 0.5f, in_Length.y * 0.5f, in_Length.z * 0.5f); +} + +inline void Box3F::setCenter(const Point3F& center) +{ + F32 halflenx = len_x() * 0.5f; + F32 halfleny = len_y() * 0.5f; + F32 halflenz = len_z() * 0.5f; + + minExtents.set(center.x-halflenx, center.y-halfleny, center.z-halflenz); + maxExtents.set(center.x+halflenx, center.y+halfleny, center.z+halflenz); +} + +inline bool Box3F::isContained(const Point3F& in_rContained) const +{ + return (in_rContained.x >= minExtents.x && in_rContained.x < maxExtents.x) && + (in_rContained.y >= minExtents.y && in_rContained.y < maxExtents.y) && + (in_rContained.z >= minExtents.z && in_rContained.z < maxExtents.z); +} + +inline bool Box3F::isContained( const Point2F &pt ) const +{ + return ( pt.x >= minExtents.x && pt.x < maxExtents.x ) && + ( pt.y >= minExtents.y && pt.y < maxExtents.y ); +} + +inline bool Box3F::isOverlapped(const Box3F& in_rOverlap) const +{ + if (in_rOverlap.minExtents.x > maxExtents.x || + in_rOverlap.minExtents.y > maxExtents.y || + in_rOverlap.minExtents.z > maxExtents.z) + return false; + if (in_rOverlap.maxExtents.x < minExtents.x || + in_rOverlap.maxExtents.y < minExtents.y || + in_rOverlap.maxExtents.z < minExtents.z) + return false; + return true; +} + +inline bool Box3F::isContained(const Box3F& in_rContained) const +{ + return (minExtents.x <= in_rContained.minExtents.x) && + (minExtents.y <= in_rContained.minExtents.y) && + (minExtents.z <= in_rContained.minExtents.z) && + (maxExtents.x >= in_rContained.maxExtents.x) && + (maxExtents.y >= in_rContained.maxExtents.y) && + (maxExtents.z >= in_rContained.maxExtents.z); +} + +inline F32 Box3F::len_x() const +{ + return maxExtents.x - minExtents.x; +} + +inline F32 Box3F::len_y() const +{ + return maxExtents.y - minExtents.y; +} + +inline F32 Box3F::len_z() const +{ + return maxExtents.z - minExtents.z; +} + +inline Point3F Box3F::getExtents() const +{ + return maxExtents - minExtents; +} + +inline void Box3F::intersect(const Box3F& in_rIntersect) +{ + minExtents.setMin(in_rIntersect.minExtents); + maxExtents.setMax(in_rIntersect.maxExtents); +} + +inline void Box3F::intersect(const Point3F& in_rIntersect) +{ + minExtents.setMin(in_rIntersect); + maxExtents.setMax(in_rIntersect); +} + +inline void Box3F::getCenter(Point3F* center) const +{ + center->x = (minExtents.x + maxExtents.x) * 0.5f; + center->y = (minExtents.y + maxExtents.y) * 0.5f; + center->z = (minExtents.z + maxExtents.z) * 0.5f; +} + +inline Point3F Box3F::getCenter() const +{ + Point3F center; + center.x = (minExtents.x + maxExtents.x) * 0.5f; + center.y = (minExtents.y + maxExtents.y) * 0.5f; + center.z = (minExtents.z + maxExtents.z) * 0.5f; + return center; +} + +inline Point3F Box3F::getClosestPoint(const Point3F& refPt) const +{ + Point3F closest; + if (refPt.x <= minExtents.x) closest.x = minExtents.x; + else if (refPt.x > maxExtents.x) closest.x = maxExtents.x; + else closest.x = refPt.x; + + if (refPt.y <= minExtents.y) closest.y = minExtents.y; + else if (refPt.y > maxExtents.y) closest.y = maxExtents.y; + else closest.y = refPt.y; + + if (refPt.z <= minExtents.z) closest.z = minExtents.z; + else if (refPt.z > maxExtents.z) closest.z = maxExtents.z; + else closest.z = refPt.z; + + return closest; +} + +inline F32 Box3F::getDistanceToPoint(const Point3F& refPt) const +{ + return mSqrt( getSqDistanceToPoint( refPt ) ); +} + +inline F32 Box3F::getSqDistanceToPoint( const Point3F &refPt ) const +{ + F32 sqDist = 0.0f; + + for ( U32 i=0; i < 3; i++ ) + { + const F32 v = refPt[i]; + if ( v < minExtents[i] ) + sqDist += ( minExtents[i] - v ) * ( minExtents[i] - v ); + if ( v > maxExtents[i] ) + sqDist += ( v - maxExtents[i] ) * ( v - maxExtents[i] ); + } + + return sqDist; +} + +inline void Box3F::extend(const Point3F & p) +{ +#define EXTEND_AXIS(AXIS) \ +if (p.AXIS < minExtents.AXIS) \ + minExtents.AXIS = p.AXIS; \ +else if (p.AXIS > maxExtents.AXIS) \ + maxExtents.AXIS = p.AXIS; + + EXTEND_AXIS(x) + EXTEND_AXIS(y) + EXTEND_AXIS(z) + +#undef EXTEND_AXIS +} + +inline void Box3F::scale( const Point3F &amt ) +{ + minExtents *= amt; + maxExtents *= amt; +} + +inline void Box3F::scale( F32 amt ) +{ + minExtents *= amt; + maxExtents *= amt; +} + +inline bool Box3F::operator ==( const Box3F &b ) const +{ + return minExtents.equal( b.minExtents ) && maxExtents.equal( b.maxExtents ); +} + +inline bool Box3F::operator !=( const Box3F &b ) const +{ + return !minExtents.equal( b.minExtents ) || !maxExtents.equal( b.maxExtents ); +} + +//------------------------------------------------------------------------------ +/// Clone of Box3F, using 3D types. +/// +/// 3D types use F64. +/// +/// @see Box3F +class Box3D +{ + public: + Point3D minExtents; + Point3D maxExtents; + + public: + Box3D() { } + Box3D(const Point3D& in_rMin, const Point3D& in_rMax, const bool in_overrideCheck = false); + + bool isContained(const Point3D& in_rContained) const; + bool isOverlapped(const Box3D& in_rOverlap) const; + + F64 len_x() const; + F64 len_y() const; + F64 len_z() const; + + void intersect(const Box3D& in_rIntersect); + void getCenter(Point3D* center) const; + + void extend(const Point3D & p); +}; + +inline Box3D::Box3D(const Point3D& in_rMin, const Point3D& in_rMax, const bool in_overrideCheck) + : minExtents(in_rMin), + maxExtents(in_rMax) +{ + if (in_overrideCheck == false) { + minExtents.setMin(in_rMax); + maxExtents.setMax(in_rMin); + } +} + +inline bool Box3D::isContained(const Point3D& in_rContained) const +{ + return (in_rContained.x >= minExtents.x && in_rContained.x < maxExtents.x) && + (in_rContained.y >= minExtents.y && in_rContained.y < maxExtents.y) && + (in_rContained.z >= minExtents.z && in_rContained.z < maxExtents.z); +} + +inline bool Box3D::isOverlapped(const Box3D& in_rOverlap) const +{ + if (in_rOverlap.minExtents.x > maxExtents.x || + in_rOverlap.minExtents.y > maxExtents.y || + in_rOverlap.minExtents.z > maxExtents.z) + return false; + if (in_rOverlap.maxExtents.x < minExtents.x || + in_rOverlap.maxExtents.y < minExtents.y || + in_rOverlap.maxExtents.z < minExtents.z) + return false; + return true; +} + +inline F64 Box3D::len_x() const +{ + return maxExtents.x - minExtents.x; +} + +inline F64 Box3D::len_y() const +{ + return maxExtents.y - minExtents.y; +} + +inline F64 Box3D::len_z() const +{ + return maxExtents.z - minExtents.z; +} + +inline void Box3D::intersect(const Box3D& in_rIntersect) +{ + minExtents.setMin(in_rIntersect.minExtents); + maxExtents.setMax(in_rIntersect.maxExtents); +} + +inline void Box3D::getCenter(Point3D* center) const +{ + center->x = (minExtents.x + maxExtents.x) * 0.5; + center->y = (minExtents.y + maxExtents.y) * 0.5; + center->z = (minExtents.z + maxExtents.z) * 0.5; +} + +inline void Box3D::extend(const Point3D & p) +{ +#define EXTEND_AXIS(AXIS) \ +if (p.AXIS < minExtents.AXIS) \ + minExtents.AXIS = p.AXIS; \ +else if (p.AXIS > maxExtents.AXIS) \ + maxExtents.AXIS = p.AXIS; + + EXTEND_AXIS(x) + EXTEND_AXIS(y) + EXTEND_AXIS(z) + +#undef EXTEND_AXIS +} + + +/// Bounding Box +/// +/// A helper class for working with boxes. It runs at F32 precision. +/// +/// @see Box3D +class Box3I +{ +public: + Point3I minExtents; ///< Minimum extents of box + Point3I maxExtents; ///< Maximum extents of box + +public: + Box3I() { } + + /// Create a box from two points. + /// + /// Normally, this function will compensate for mismatched + /// min/max values. If you know your values are valid, you + /// can set in_overrideCheck to true and skip this. + /// + /// @param in_rMin Minimum extents of box. + /// @param in_rMax Maximum extents of box. + /// @param in_overrideCheck Pass true to skip check of extents. + Box3I(const Point3I& in_rMin, const Point3I& in_rMax, const bool in_overrideCheck = false); + + /// Create a box from six extent values. + /// + /// No checking is performed as to the validity of these + /// extents, unlike the other constructor. + Box3I(S32 xmin, S32 ymin, S32 zmin, S32 max, S32 ymax, S32 zmax); + + /// Check to see if a point is contained in this box. + bool isContained(const Point3I& in_rContained) const; + + /// Check to see if another box overlaps this box. + bool isOverlapped(const Box3I& in_rOverlap) const; + + /// Check to see if another box is contained in this box. + bool isContained(const Box3I& in_rContained) const; + + S32 len_x() const; + S32 len_y() const; + S32 len_z() const; + + /// Perform an intersection operation with another box + /// and store the results in this box. + void intersect(const Box3I& in_rIntersect); + + /// Get the center of this box. + /// + /// This is the average of min and max. + void getCenter(Point3I* center) const; + + /// Check that the box is valid. + /// + /// Currently, this just means that min < max. + bool isValidBox() const + { + return (minExtents.x <= maxExtents.x) && + (minExtents.y <= maxExtents.y) && + (minExtents.z <= maxExtents.z); + } + + void extend(const Point3I & p); +}; + +inline Box3I::Box3I(const Point3I& in_rMin, const Point3I& in_rMax, const bool in_overrideCheck) +: minExtents(in_rMin), +maxExtents(in_rMax) +{ + if (in_overrideCheck == false) + { + minExtents.setMin(in_rMax); + maxExtents.setMax(in_rMin); + } +} + +inline Box3I::Box3I(S32 xMin, S32 yMin, S32 zMin, S32 xMax, S32 yMax, S32 zMax) +: minExtents(xMin,yMin,zMin), +maxExtents(xMax,yMax,zMax) +{ +} + +inline bool Box3I::isContained(const Point3I& in_rContained) const +{ + return (in_rContained.x >= minExtents.x && in_rContained.x < maxExtents.x) && + (in_rContained.y >= minExtents.y && in_rContained.y < maxExtents.y) && + (in_rContained.z >= minExtents.z && in_rContained.z < maxExtents.z); +} + +inline bool Box3I::isOverlapped(const Box3I& in_rOverlap) const +{ + if (in_rOverlap.minExtents.x > maxExtents.x || + in_rOverlap.minExtents.y > maxExtents.y || + in_rOverlap.minExtents.z > maxExtents.z) + return false; + if (in_rOverlap.maxExtents.x < minExtents.x || + in_rOverlap.maxExtents.y < minExtents.y || + in_rOverlap.maxExtents.z < minExtents.z) + return false; + return true; +} + +inline bool Box3I::isContained(const Box3I& in_rContained) const +{ + return (minExtents.x <= in_rContained.minExtents.x) && + (minExtents.y <= in_rContained.minExtents.y) && + (minExtents.z <= in_rContained.minExtents.z) && + (maxExtents.x >= in_rContained.maxExtents.x) && + (maxExtents.y >= in_rContained.maxExtents.y) && + (maxExtents.z >= in_rContained.maxExtents.z); +} + +inline S32 Box3I::len_x() const +{ + return maxExtents.x - minExtents.x; +} + +inline S32 Box3I::len_y() const +{ + return maxExtents.y - minExtents.y; +} + +inline S32 Box3I::len_z() const +{ + return maxExtents.z - minExtents.z; +} + +inline void Box3I::intersect(const Box3I& in_rIntersect) +{ + minExtents.setMin(in_rIntersect.minExtents); + maxExtents.setMax(in_rIntersect.maxExtents); +} + +inline void Box3I::getCenter(Point3I* center) const +{ + center->x = (minExtents.x + maxExtents.x) >> 1; + center->y = (minExtents.y + maxExtents.y) >> 1; + center->z = (minExtents.z + maxExtents.z) >> 1; +} + +inline void Box3I::extend(const Point3I & p) +{ +#define EXTEND_AXIS(AXIS) \ +if (p.AXIS < minExtents.AXIS) \ + minExtents.AXIS = p.AXIS; \ +else if (p.AXIS > maxExtents.AXIS) \ + maxExtents.AXIS = p.AXIS; + + EXTEND_AXIS(x) + EXTEND_AXIS(y) + EXTEND_AXIS(z) + +#undef EXTEND_AXIS +} + + +#endif // _DBOX_H_ diff --git a/math/mConsoleFunctions.cpp b/math/mConsoleFunctions.cpp new file mode 100644 index 0000000..5433533 --- /dev/null +++ b/math/mConsoleFunctions.cpp @@ -0,0 +1,202 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" + +#include "console/console.h" +#include "math/mMathFn.h" +#include "math/mRandom.h" + + +ConsoleFunctionGroupBegin( GeneralMath, "General math functions. Use these whenever possible, as they'll run much faster than script equivalents."); + +ConsoleFunction( mSolveQuadratic, const char *, 4, 4, "(float a, float b, float c)" + "Solve a quadratic equation of form a*x^2 + b*x + c = 0.\n\n" + "@returns A triple, contanining: sol x0 x1. sol is the number of" + " solutions (being 0, 1, or 2), and x0 and x1 are the solutions, if any." + " Unused x's are undefined.") +{ + char * retBuffer = Con::getReturnBuffer(256); + F32 x[2]; + U32 sol = mSolveQuadratic(dAtof(argv[1]), dAtof(argv[2]), dAtof(argv[3]), x); + dSprintf(retBuffer, 256, "%d %g %g", sol, x[0], x[1]); + return retBuffer; +} + +ConsoleFunction( mSolveCubic, const char *, 5, 5, "(float a, float b, float c, float d)" + "Solve a cubic equation of form a*x^3 + b*x^2 + c*x + d = 0.\n\n" + "@returns A 4-tuple, contanining: sol x0 x1 x2. sol is the number of" + " solutions (being 0, 1, 2, or 3), and x0, x1, x2 are the solutions, if any." + " Unused x's are undefined.") +{ + char * retBuffer = Con::getReturnBuffer(256); + F32 x[3]; + U32 sol = mSolveCubic(dAtof(argv[1]), dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]), x); + dSprintf(retBuffer, 256, "%d %g %g %g", sol, x[0], x[1], x[2]); + return retBuffer; +} + +ConsoleFunction( mSolveQuartic, const char *, 6, 6, "(float a, float b, float c, float d, float e)" + "Solve a quartic equation of form a*x^4 + b*x^3 + c*x^2 + d*x + e = 0.\n\n" + "@returns A 5-tuple, contanining: sol x0 x1 x2 x3. sol is the number of" + " solutions (ranging from 0-4), and x0, x1, x2 and x3 are the solutions, if any." + " Unused x's are undefined.") +{ + char * retBuffer = Con::getReturnBuffer(256); + F32 x[4]; + U32 sol = mSolveQuartic(dAtof(argv[1]), dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]), dAtof(argv[5]), x); + dSprintf(retBuffer, 256, "%d %g %g %g %g", sol, x[0], x[1], x[2], x[3]); + return retBuffer; +} + +ConsoleFunction( mFloor, S32, 2, 2, "(float v) Round v down to the nearest whole number.") +{ + return (S32)mFloor(dAtof(argv[1])); +} + +ConsoleFunction( mRound, S32, 2, 2, "(float v) Rounds a number") +{ + F32 num = dAtof(argv[1]); + return (S32)mFloor(num + 0.5f); +} + +ConsoleFunction( mCeil, S32, 2, 2, "(float v) Round v up to the nearest whole number.") +{ + return (S32)mCeil(dAtof(argv[1])); +} + +ConsoleFunction( mFloatLength, const char *, 3, 3, "(float v, int numDecimals)" + "Return a string containing v formatted with the specified number of decimal places.") +{ + char * outBuffer = Con::getReturnBuffer(256); + char fmtString[8] = "%.0f"; + U32 precision = dAtoi(argv[2]); + if (precision > 9) + precision = 9; + fmtString[2] = '0' + precision; + + dSprintf(outBuffer, 255, fmtString, dAtof(argv[1])); + return outBuffer; +} + +//------------------------------------------------------------------------------ +ConsoleFunction( mAbs, F32, 2, 2, "(float v) Returns the absolute value of the argument.") +{ + return(mFabs(dAtof(argv[1]))); +} + +ConsoleFunction( mFmod, F32, 3, 3, "( float v, float d ) Returns the floating point remainder of v/d.") +{ + return mFmod( dAtof( argv[1] ), dAtof( argv[2] ) ); +} + +ConsoleFunction( mSqrt, F32, 2, 2, "(float v) Returns the square root of the argument.") +{ + return(mSqrt(dAtof(argv[1]))); +} + +ConsoleFunction( mPow, F32, 3, 3, "(float b, float p) Returns the b raised to the pth power.") +{ + return(mPow(dAtof(argv[1]), dAtof(argv[2]))); +} + +ConsoleFunction( mLog, F32, 2, 2, "(float v) Returns the natural logarithm of the argument.") +{ + return(mLog(dAtof(argv[1]))); +} + +ConsoleFunction( mSin, F32, 2, 2, "(float th) Returns the sine of th, which is in radians.") +{ + return(mSin(dAtof(argv[1]))); +} + +ConsoleFunction( mCos, F32, 2, 2, "(float th) Returns the cosine of th, which is in radians.") +{ + return(mCos(dAtof(argv[1]))); +} + +ConsoleFunction( mTan, F32, 2, 2, "(float th) Returns the tangent of th, which is in radians.") +{ + return(mTan(dAtof(argv[1]))); +} + +ConsoleFunction( mAsin, F32, 2, 2, "(float th) Returns the arc-sine of th, which is in radians.") +{ + return(mAsin(dAtof(argv[1]))); +} + +ConsoleFunction( mAcos, F32, 2, 2, "(float th) Returns the arc-cosine of th, which is in radians.") +{ + return(mAcos(dAtof(argv[1]))); +} + +ConsoleFunction( mAtan, F32, 3, 3, "(float rise, float run) Returns the slope in radians (the arc-tangent) of a line with the given rise and run.") +{ + return(mAtan2(dAtof(argv[1]), dAtof(argv[2]))); +} + +ConsoleFunction( mRadToDeg, F32, 2, 2, "(float radians) Converts a measure in radians to degrees.") +{ + return(mRadToDeg(dAtof(argv[1]))); +} + +ConsoleFunction( mDegToRad, F32, 2, 2, "(float degrees) Convert a measure in degrees to radians.") +{ + return(mDegToRad(dAtof(argv[1]))); +} + +ConsoleFunction( mClamp, F32, 4, 4, "(float number, float min, float max) Clamp a value between two other values.") +{ + F32 value = dAtof( argv[1] ); + F32 min = dAtof( argv[2] ); + F32 max = dAtof( argv[3] ); + return mClampF( value, min, max ); +} + +ConsoleFunction( mSaturate, F32, 2, 2, "(float number) Clamp between 0 and 1" ) +{ + F32 val = dAtof( argv[1] ); + return mClampF( val, 0.0f, 1.0f ); +} + +ConsoleFunction( getMax, F32, 3, 3, "(float number, float number) Return the greater number." ) +{ + F32 v0 = dAtof( argv[1] ); + F32 v1 = dAtof( argv[2] ); + return getMax( v0, v1 ); +} + +ConsoleFunction( getMin, F32, 3, 3, "(float number, float number) Return the lesser number." ) +{ + F32 v0 = dAtof( argv[1] ); + F32 v1 = dAtof( argv[2] ); + return getMin( v0, v1 ); +} + +ConsoleFunction( mLerp, F32, 4, 4, "(float f0, float f1, float t) Linearly interpolate between f0 and f1 given time t.") +{ + F32 f0 = dAtof( argv[1] ); + F32 f1 = dAtof( argv[2] ); + F32 t = dAtof( argv[3] ); + return mLerp( f0, f1, t ); +} + +ConsoleFunction( mPi, F32, 1, 1, "() Returns the value of Pi") +{ + return M_PI_F; +} + +ConsoleFunction( m2Pi, F32, 1, 1, "() Returns the value of 2*Pi") +{ + return M_2PI_F; +} + +ConsoleFunction( mIsPow2, bool, 2, 2, "( int value )" + "Returns true if the value is power of two in size." ) +{ + return isPow2( dAtoi( argv[1] ) ); +} + +ConsoleFunctionGroupEnd( GeneralMath ); diff --git a/math/mConstants.h b/math/mConstants.h new file mode 100644 index 0000000..ab0a947 --- /dev/null +++ b/math/mConstants.h @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MCONSTANTS_H_ +#define _MCONSTANTS_H_ + +#undef M_PI +#undef M_SQRT2 + +#define M_PI 3.14159265358979323846 +#define M_SQRT2 1.41421356237309504880 + +#define M_2PI (3.1415926535897932384626433 * 2.0) +#define M_SQRTHALF 0.7071067811865475244008443 + +#define M_PI_F 3.14159265358979323846f +#define M_SQRT2_F 1.41421356237309504880f + +#define M_2PI_F (3.1415926535897932384626433f * 2.0f) +#define M_SQRTHALF_F 0.7071067811865475244008443f + +#define M_CONST_E_F 2.7182818284590452353602874f + +#define POINT_EPSILON (1e-4) ///< Epsilon for point types. + +#endif diff --git a/math/mMath.h b/math/mMath.h new file mode 100644 index 0000000..60646e1 --- /dev/null +++ b/math/mMath.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MMATH_H_ +#define _MMATH_H_ + + +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _MQUAT_H_ +#include "math/mQuat.h" +#endif +#ifndef _MANGAXIS_H_ +#include "math/mAngAxis.h" +#endif +#ifndef _MSPHERE_H_ +#include "math/mSphere.h" +#endif +#ifndef _MRANDOM_H_ +#include "math/mRandom.h" +#endif + +#endif //_MMATH_H_ diff --git a/math/mMathAMD.cpp b/math/mMathAMD.cpp new file mode 100644 index 0000000..3de4360 --- /dev/null +++ b/math/mMathAMD.cpp @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMathFn.h" +#include "math/mPlane.h" +#include "math/mMatrix.h" + + +// extern void (*m_matF_x_point3F)(const F32 *m, const F32 *p, F32 *presult); +// extern void (*m_matF_x_vectorF)(const F32 *m, const F32 *v, F32 *vresult); + +/* not currently implemented. +void Athlon_MatrixF_x_Point3F(const F32 *m, const F32 *p, F32 *presult) +{ + m; + p; + presult; +} +*/ + +//============================================================ +// Here's the C code for MatF_x_MatF: +// note that the code below does it in a different order (optimal asm, after all!) +// +// r[0] = a[0]*b[0] + a[1]*b[4] + a[2]*b[8] + a[3]*b[12]; +// r[1] = a[0]*b[1] + a[1]*b[5] + a[2]*b[9] + a[3]*b[13]; +// r[2] = a[0]*b[2] + a[1]*b[6] + a[2]*b[10] + a[3]*b[14]; +// r[3] = a[0]*b[3] + a[1]*b[7] + a[2]*b[11] + a[3]*b[15]; +// +// r[4] = a[4]*b[0] + a[5]*b[4] + a[6]*b[8] + a[7]*b[12]; +// r[5] = a[4]*b[1] + a[5]*b[5] + a[6]*b[9] + a[7]*b[13]; +// r[6] = a[4]*b[2] + a[5]*b[6] + a[6]*b[10] + a[7]*b[14]; +// r[7] = a[4]*b[3] + a[5]*b[7] + a[6]*b[11] + a[7]*b[15]; +// +// r[8] = a[8]*b[0] + a[9]*b[4] + a[10]*b[8] + a[11]*b[12]; +// r[9] = a[8]*b[1] + a[9]*b[5] + a[10]*b[9] + a[11]*b[13]; +// r[10]= a[8]*b[2] + a[9]*b[6] + a[10]*b[10]+ a[11]*b[14]; +// r[11]= a[8]*b[3] + a[9]*b[7] + a[10]*b[11]+ a[11]*b[15]; +// +// r[12]= a[12]*b[0]+ a[13]*b[4]+ a[14]*b[8] + a[15]*b[12]; +// r[13]= a[12]*b[1]+ a[13]*b[5]+ a[14]*b[9] + a[15]*b[13]; +// r[14]= a[12]*b[2]+ a[13]*b[6]+ a[14]*b[10]+ a[15]*b[14]; +// r[15]= a[12]*b[3]+ a[13]*b[7]+ a[14]*b[11]+ a[15]*b[15]; +//============================================================ + +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) +#define ADD_3DNOW_FUNCS +// inlined version here. +void Athlon_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) +{ + __asm + { + femms + + mov ecx, matA + mov edx, matB + mov eax, result + + prefetch [ecx+32] ;// These may help - + prefetch [edx+32] ;// and probably don't hurt + + movq mm0,[ecx] ;// a21 | a11 + movq mm1,[ecx+8] ;// a41 | a31 + movq mm4,[edx] ;// b21 | b11 + punpckhdq mm2,mm0 ;// a21 | + movq mm5,[edx+16] ;// b22 | b12 + punpckhdq mm3,mm1 ;// a41 | + movq mm6,[edx+32] ;// b23 | b13 + punpckldq mm0,mm0 ;// a11 | a11 + punpckldq mm1,mm1 ;// a31 | a31 + pfmul mm4,mm0 ;// a11*b21 | a11*b11 + punpckhdq mm2,mm2 ;// a21 | a21 + pfmul mm0,[edx+8] ;// a11*b41 | a11*b31 + movq mm7,[edx+48] ;// b24 | b14 + pfmul mm5,mm2 ;// a21*b22 | a21*b12 + punpckhdq mm3,mm3 ;// a41 | a41 + pfmul mm2,[edx+24] ;// a21*b42 | a21*b32 + pfmul mm6,mm1 ;// a31*b23 | a31*b13 + pfadd mm5,mm4 ;// a21*b22 + a11*b21 | a21*b12 + a11*b11 + pfmul mm1,[edx+40] ;// a31*b43 | a31*b33 + pfadd mm2,mm0 ;// a21*b42 + a11*b41 | a21*b32 + a11*b31 + pfmul mm7,mm3 ;// a41*b24 | a41*b14 + pfadd mm6,mm5 ;// a21*b22 + a11*b21 + a31*b23 | a21*b12 + a11*b11 + a31*b13 + pfmul mm3,[edx+56] ;// a41*b44 | a41*b34 + pfadd mm2,mm1 ;// a21*b42 + a11*b41 + a31*b43 | a21*b32 + a11*b31 + a31*b33 + pfadd mm7,mm6 ;// a41*b24 + a21*b22 + a11*b21 + a31*b23 | a41*b14 + a21*b12 + a11*b11 + a31*b13 + movq mm0,[ecx+16] ;// a22 | a12 + pfadd mm3,mm2 ;// a41*b44 + a21*b42 + a11*b41 + a31*b43 | a41*b34 + a21*b32 + a11*b31 + a31*b33 + movq mm1,[ecx+24] ;// a42 | a32 + movq [eax],mm7 ;// r21 | r11 + movq mm4,[edx] ;// b21 | b11 + movq [eax+8],mm3 ;// r41 | r31 + + punpckhdq mm2,mm0 ;// a22 | XXX + movq mm5,[edx+16] ;// b22 | b12 + punpckhdq mm3,mm1 ;// a42 | XXX + movq mm6,[edx+32] ;// b23 | b13 + punpckldq mm0,mm0 ;// a12 | a12 + punpckldq mm1,mm1 ;// a32 | a32 + pfmul mm4,mm0 ;// a12*b21 | a12*b11 + punpckhdq mm2,mm2 ;// a22 | a22 + pfmul mm0,[edx+8] ;// a12*b41 | a12*b31 + movq mm7,[edx+48] ;// b24 | b14 + pfmul mm5,mm2 ;// a22*b22 | a22*b12 + punpckhdq mm3,mm3 ;// a42 | a42 + pfmul mm2,[edx+24] ;// a22*b42 | a22*b32 + pfmul mm6,mm1 ;// a32*b23 | a32*b13 + pfadd mm5,mm4 ;// a12*b21 + a22*b22 | a12*b11 + a22*b12 + pfmul mm1,[edx+40] ;// a32*b43 | a32*b33 + pfadd mm2,mm0 ;// a12*b41 + a22*b42 | a12*b11 + a22*b32 + pfmul mm7,mm3 ;// a42*b24 | a42*b14 + pfadd mm6,mm5 ;// a32*b23 + a12*b21 + a22*b22 | a32*b13 + a12*b11 + a22*b12 + pfmul mm3,[edx+56] ;// a42*b44 | a42*b34 + pfadd mm2,mm1 ;// a32*b43 + a12*b41 + a22*b42 | a32*b33 + a12*b11 + a22*b32 + pfadd mm7,mm6 ;// a42*b24 + a32*b23 + a12*b21 + a22*b22 | a42*b14 + a32*b13 + a12*b11 + a22*b12 + movq mm0,[ecx+32] ;// a23 | a13 + pfadd mm3,mm2 ;// a42*b44 + a32*b43 + a12*b41 + a22*b42 | a42*b34 + a32*b33 + a12*b11 + a22*b32 + movq mm1,[ecx+40] ;// a43 | a33 + movq [eax+16],mm7 ;// r22 | r12 + movq mm4,[edx] ;// b21 | b11 + movq [eax+24],mm3 ;// r42 | r32 + + punpckhdq mm2,mm0 ;// a23 | XXX + movq mm5,[edx+16] ;// b22 | b12 + punpckhdq mm3,mm1 ;// a43 | XXX + movq mm6,[edx+32] ;// b23 | b13 + punpckldq mm0,mm0 ;// a13 | a13 + punpckldq mm1,mm1 ;// a33 | a33 + pfmul mm4,mm0 ;// a13*b21 | a13*b11 + punpckhdq mm2,mm2 ;// a23 | a23 + pfmul mm0,[edx+8] ;// a13*b41 | a13*b31 + movq mm7,[edx+48] ;// b24 | b14 + pfmul mm5,mm2 ;// a23*b22 | a23*b12 + punpckhdq mm3,mm3 ;// a43 | a43 + pfmul mm2,[edx+24] ;// a23*b42 | a23*b32 + pfmul mm6,mm1 ;// a33*b23 | a33*b13 + pfadd mm5,mm4 ;// a23*b22 + a13*b21 | a23*b12 + a13*b11 + pfmul mm1,[edx+40] ;// a33*b43 | a33*b33 + pfadd mm2,mm0 ;// a13*b41 + a23*b42 | a13*b31 + a23*b32 + pfmul mm7,mm3 ;// a43*b24 | a43*b14 + pfadd mm6,mm5 ;// a33*b23 + a23*b22 + a13*b21 | a33*b13 + a23*b12 + a13*b11 + pfmul mm3,[edx+56] ;// a43*b44 | a43*b34 + pfadd mm2,mm1 ;// a33*b43*a13*b41 + a23*b42 | a33*b33 + a13*b31 + a23*b32 + pfadd mm7,mm6 ;// a43*b24 + a33*b23 + a23*b22 + a13*b21 | a43*b14 + a33*b13 + a23*b12 + a13*b11 + movq mm0,[ecx+48] ;// a24 | a14 + pfadd mm3,mm2 ;// a43*b44 + a33*b43*a13*b41 + a23*b42 | a43*b34 + a33*b33 + a13*b31 + a23*b32 + movq mm1,[ecx+56] ;// a44 | a34 + movq [eax+32],mm7 ;// r23 | r13 + movq mm4,[edx] ;// b21 | b11 + movq [eax+40],mm3 ;// r43 | r33 + + punpckhdq mm2,mm0 ;// a24 | XXX + movq mm5,[edx+16] ;// b22 | b12 + punpckhdq mm3,mm1 ;// a44 | XXX + movq mm6,[edx+32] ;// b23 | b13 + punpckldq mm0,mm0 ;// a14 | a14 + punpckldq mm1,mm1 ;// a34 | a34 + pfmul mm4,mm0 ;// a14*b21 | a14*b11 + punpckhdq mm2,mm2 ;// a24 | a24 + pfmul mm0,[edx+8] ;// a14*b41 | a14*b31 + movq mm7,[edx+48] ;// b24 | b14 + pfmul mm5,mm2 ;// a24*b22 | a24*b12 + punpckhdq mm3,mm3 ;// a44 | a44 + pfmul mm2,[edx+24] ;// a24*b 42 | a24*b32 + pfmul mm6,mm1 ;// a34*b23 | a34*b13 + pfadd mm5,mm4 ;// a14*b21 + a24*b22 | a14*b11 + a24*b12 + pfmul mm1,[edx+40] ;// a34*b43 | a34*b33 + pfadd mm2,mm0 ;// a14*b41 + a24*b 42 | a14*b31 + a24*b32 + pfmul mm7,mm3 ;// a44*b24 | a44*b14 + pfadd mm6,mm5 ;// a34*b23 + a14*b21 + a24*b22 | a34*b13 + a14*b11 + a24*b12 + pfmul mm3,[edx+56] ;// a44*b44 | a44*b34 + pfadd mm2,mm1 ;// a34*b43 + a14*b41 + a24*b 42 | a34*b33 + a14*b31 + a24*b32 + pfadd mm7,mm6 ;// a44*b24 + a14*b23 + a24*b 42 | a44*b14 + a14*b31 + a24*b32 + pfadd mm3,mm2 ;// a44*b44 + a34*b43 + a14*b41 + a24*b42 | a44*b34 + a34*b33 + a14*b31 + a24*b32 + movq [eax+48],mm7 ;// r24 | r14 + movq [eax+56],mm3 ;// r44 | r34 + femms + } +} +#elif defined(TORQUE_SUPPORTS_NASM) +#define ADD_3DNOW_FUNCS +extern "C" +{ + void Athlon_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result); +} + +#endif + +void mInstall_AMD_Math() +{ +#if defined(ADD_3DNOW_FUNCS) + m_matF_x_matF = Athlon_MatrixF_x_MatrixF; +#endif + // m_matF_x_point3F = Athlon_MatrixF_x_Point3F; + // m_matF_x_vectorF = Athlon_MatrixF_x_VectorF; +} + diff --git a/math/mMathAMD_ASM.asm b/math/mMathAMD_ASM.asm new file mode 100644 index 0000000..74cff7d --- /dev/null +++ b/math/mMathAMD_ASM.asm @@ -0,0 +1,160 @@ +;----------------------------------------------------------------------------- +; Torque Game Engine +; Copyright (C) GarageGames.com, Inc. +;----------------------------------------------------------------------------- + + +segment .data + +matA dd 0 +result dd 0 +matB dd 0 + +segment .text + +%macro export_fn 1 + %ifidn __OUTPUT_FORMAT__, elf + ; No underscore needed for ELF object files + global %1 + %1: + %else + global _%1 + _%1: + %endif +%endmacro + + +%define arg(x) [esp+(x*4)] + + + +;void Athlon_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) + +export_fn Athlon_MatrixF_x_MatrixF + + mov ecx, arg(1) + mov edx, arg(2) + mov eax, arg(3) + + femms + prefetch [ecx+32] ; These may help - + prefetch [edx+32] ; and probably don't hurt + + movq mm0,[ecx] ; a21 | a11 + movq mm1,[ecx+8] ; a41 | a31 + movq mm4,[edx] ; b21 | b11 + punpckhdq mm2,mm0 ; a21 | + movq mm5,[edx+16] ; b22 | b12 + punpckhdq mm3,mm1 ; a41 | + movq mm6,[edx+32] ; b23 | b13 + punpckldq mm0,mm0 ; a11 | a11 + punpckldq mm1,mm1 ; a31 | a31 + pfmul mm4,mm0 ; a11*b21 | a11*b11 + punpckhdq mm2,mm2 ; a21 | a21 + pfmul mm0,[edx+8] ; a11*b41 | a11*b31 + movq mm7,[edx+48] ; b24 | b14 + pfmul mm5,mm2 ; a21*b22 | a21*b12 + punpckhdq mm3,mm3 ; a41 | a41 + pfmul mm2,[edx+24] ; a21*b42 | a21*b32 + pfmul mm6,mm1 ; a31*b23 | a31*b13 + pfadd mm5,mm4 ; a21*b22 + a11*b21 | a21*b12 + a11*b11 + pfmul mm1,[edx+40] ; a31*b43 | a31*b33 + pfadd mm2,mm0 ; a21*b42 + a11*b41 | a21*b32 + a11*b31 + pfmul mm7,mm3 ; a41*b24 | a41*b14 + pfadd mm6,mm5 ; a21*b22 + a11*b21 + a31*b23 | a21*b12 + a11*b11 + a31*b13 + pfmul mm3,[edx+56] ; a41*b44 | a41*b34 + pfadd mm2,mm1 ; a21*b42 + a11*b41 + a31*b43 | a21*b32 + a11*b31 + a31*b33 + pfadd mm7,mm6 ; a41*b24 + a21*b22 + a11*b21 + a31*b23 | a41*b14 + a21*b12 + a11*b11 + a31*b13 + movq mm0,[ecx+16] ; a22 | a12 + pfadd mm3,mm2 ; a41*b44 + a21*b42 + a11*b41 + a31*b43 | a41*b34 + a21*b32 + a11*b31 + a31*b33 + movq mm1,[ecx+24] ; a42 | a32 + movq [eax],mm7 ; r21 | r11 + movq mm4,[edx] ; b21 | b11 + movq [eax+8],mm3 ; r41 | r31 + + punpckhdq mm2,mm0 ; a22 | XXX + movq mm5,[edx+16] ; b22 | b12 + punpckhdq mm3,mm1 ; a42 | XXX + movq mm6,[edx+32] ; b23 | b13 + punpckldq mm0,mm0 ; a12 | a12 + punpckldq mm1,mm1 ; a32 | a32 + pfmul mm4,mm0 ; a12*b21 | a12*b11 + punpckhdq mm2,mm2 ; a22 | a22 + pfmul mm0,[edx+8] ; a12*b41 | a12*b31 + movq mm7,[edx+48] ; b24 | b14 + pfmul mm5,mm2 ; a22*b22 | a22*b12 + punpckhdq mm3,mm3 ; a42 | a42 + pfmul mm2,[edx+24] ; a22*b42 | a22*b32 + pfmul mm6,mm1 ; a32*b23 | a32*b13 + pfadd mm5,mm4 ; a12*b21 + a22*b22 | a12*b11 + a22*b12 + pfmul mm1,[edx+40] ; a32*b43 | a32*b33 + pfadd mm2,mm0 ; a12*b41 + a22*b42 | a12*b11 + a22*b32 + pfmul mm7,mm3 ; a42*b24 | a42*b14 + pfadd mm6,mm5 ; a32*b23 + a12*b21 + a22*b22 | a32*b13 + a12*b11 + a22*b12 + pfmul mm3,[edx+56] ; a42*b44 | a42*b34 + pfadd mm2,mm1 ; a32*b43 + a12*b41 + a22*b42 | a32*b33 + a12*b11 + a22*b32 + pfadd mm7,mm6 ; a42*b24 + a32*b23 + a12*b21 + a22*b22 | a42*b14 + a32*b13 + a12*b11 + a22*b12 + movq mm0,[ecx+32] ; a23 | a13 + pfadd mm3,mm2 ; a42*b44 + a32*b43 + a12*b41 + a22*b42 | a42*b34 + a32*b33 + a12*b11 + a22*b32 + movq mm1,[ecx+40] ; a43 | a33 + movq [eax+16],mm7 ; r22 | r12 + movq mm4,[edx] ; b21 | b11 + movq [eax+24],mm3 ; r42 | r32 + + punpckhdq mm2,mm0 ; a23 | XXX + movq mm5,[edx+16] ; b22 | b12 + punpckhdq mm3,mm1 ; a43 | XXX + movq mm6,[edx+32] ; b23 | b13 + punpckldq mm0,mm0 ; a13 | a13 + punpckldq mm1,mm1 ; a33 | a33 + pfmul mm4,mm0 ; a13*b21 | a13*b11 + punpckhdq mm2,mm2 ; a23 | a23 + pfmul mm0,[edx+8] ; a13*b41 | a13*b31 + movq mm7,[edx+48] ; b24 | b14 + pfmul mm5,mm2 ; a23*b22 | a23*b12 + punpckhdq mm3,mm3 ; a43 | a43 + pfmul mm2,[edx+24] ; a23*b42 | a23*b32 + pfmul mm6,mm1 ; a33*b23 | a33*b13 + pfadd mm5,mm4 ; a23*b22 + a13*b21 | a23*b12 + a13*b11 + pfmul mm1,[edx+40] ; a33*b43 | a33*b33 + pfadd mm2,mm0 ; a13*b41 + a23*b42 | a13*b31 + a23*b32 + pfmul mm7,mm3 ; a43*b24 | a43*b14 + pfadd mm6,mm5 ; a33*b23 + a23*b22 + a13*b21 | a33*b13 + a23*b12 + a13*b11 + pfmul mm3,[edx+56] ; a43*b44 | a43*b34 + pfadd mm2,mm1 ; a33*b43*a13*b41 + a23*b42 | a33*b33 + a13*b31 + a23*b32 + pfadd mm7,mm6 ; a43*b24 + a33*b23 + a23*b22 + a13*b21 | a43*b14 + a33*b13 + a23*b12 + a13*b11 + movq mm0,[ecx+48] ; a24 | a14 + pfadd mm3,mm2 ; a43*b44 + a33*b43*a13*b41 + a23*b42 | a43*b34 + a33*b33 + a13*b31 + a23*b32 + movq mm1,[ecx+56] ; a44 | a34 + movq [eax+32],mm7 ; r23 | r13 + movq mm4,[edx] ; b21 | b11 + movq [eax+40],mm3 ; r43 | r33 + + punpckhdq mm2,mm0 ; a24 | XXX + movq mm5,[edx+16] ; b22 | b12 + punpckhdq mm3,mm1 ; a44 | XXX + movq mm6,[edx+32] ; b23 | b13 + punpckldq mm0,mm0 ; a14 | a14 + punpckldq mm1,mm1 ; a34 | a34 + pfmul mm4,mm0 ; a14*b21 | a14*b11 + punpckhdq mm2,mm2 ; a24 | a24 + pfmul mm0,[edx+8] ; a14*b41 | a14*b31 + movq mm7,[edx+48] ; b24 | b14 + pfmul mm5,mm2 ; a24*b22 | a24*b12 + punpckhdq mm3,mm3 ; a44 | a44 + pfmul mm2,[edx+24] ; a24*b 42 | a24*b32 + pfmul mm6,mm1 ; a34*b23 | a34*b13 + pfadd mm5,mm4 ; a14*b21 + a24*b22 | a14*b11 + a24*b12 + pfmul mm1,[edx+40] ; a34*b43 | a34*b33 + pfadd mm2,mm0 ; a14*b41 + a24*b 42 | a14*b31 + a24*b32 + pfmul mm7,mm3 ; a44*b24 | a44*b14 + pfadd mm6,mm5 ; a34*b23 + a14*b21 + a24*b22 | a34*b13 + a14*b11 + a24*b12 + pfmul mm3,[edx+56] ; a44*b44 | a44*b34 + pfadd mm2,mm1 ; a34*b43 + a14*b41 + a24*b 42 | a34*b33 + a14*b31 + a24*b32 + pfadd mm7,mm6 ; a44*b24 + a14*b23 + a24*b 42 | a44*b14 + a14*b31 + a24*b32 + pfadd mm3,mm2 ; a44*b44 + a34*b43 + a14*b41 + a24*b42 | a44*b34 + a34*b33 + a14*b31 + a24*b32 + movq [eax+48],mm7 ; r24 | r14 + movq [eax+56],mm3 ; r44 | r34 + femms + + ret diff --git a/math/mMathAltivec.cpp b/math/mMathAltivec.cpp new file mode 100644 index 0000000..e299841 --- /dev/null +++ b/math/mMathAltivec.cpp @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// This file is Mac specific. +#if defined( __APPLE__ ) + +#include +#include "math/mMathFn.h" +#include "console/console.h" +#include "platform/profiler.h" + +#if defined( __VEC__ ) + +// tests show BLAS to be about 4x slower than aligned altivec, 3x slower than unaligned altivec code below. + +/// Altivec 4x4 Matrix multiplication. +/// Most of our time is spent moving data in & out of the vector registers. +/// Alignment of the matrix data to 16-byte boundaries is very important, +/// because we get a much better speed gain if we can assume the data is aligned. +void vec_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) +{ + vector float A[4][1]; + vector float B[4][1]; + vector float C[4][1]; + + /// If the incoming pointers are not 16-byte aligned, we have to load & store the slow way. + if((int)matA & 0xF || (int)matB & 0xF || (int)result & 0xF) + { + F32 *loader; + loader = (F32*) &A; + loader[0] = matA[0]; + loader[1] = matA[1]; + loader[2] = matA[2]; + loader[3] = matA[3]; + loader[4] = matA[4]; + loader[5] = matA[5]; + loader[6] = matA[6]; + loader[7] = matA[7]; + loader[8] = matA[8]; + loader[9] = matA[9]; + loader[10] = matA[10]; + loader[11] = matA[11]; + loader[12] = matA[12]; + loader[13] = matA[13]; + loader[14] = matA[14]; + loader[15] = matA[15]; + loader = (F32*) &B; + loader[0] = matB[0]; + loader[1] = matB[1]; + loader[2] = matB[2]; + loader[3] = matB[3]; + loader[4] = matB[4]; + loader[5] = matB[5]; + loader[6] = matB[6]; + loader[7] = matB[7]; + loader[8] = matB[8]; + loader[9] = matB[9]; + loader[10] = matB[10]; + loader[11] = matB[11]; + loader[12] = matB[12]; + loader[13] = matB[13]; + loader[14] = matB[14]; + loader[15] = matB[15]; + + vMultMatMat_4x4( A, B, C); + + loader = (F32*) &C; + + result[0] = loader[0]; + result[1] = loader[1]; + result[2] = loader[2]; + result[3] = loader[3]; + result[4] = loader[4]; + result[5] = loader[5]; + result[6] = loader[6]; + result[7] = loader[7]; + result[8] = loader[8]; + result[9] = loader[9]; + result[10] = loader[10]; + result[11] = loader[11]; + result[12] = loader[12]; + result[13] = loader[13]; + result[14] = loader[14]; + result[15] = loader[15]; + } + else + { + A[0][0] = vec_ld(0, matA); + A[1][0] = vec_ld(16, matA); + A[2][0] = vec_ld(32, matA); + A[3][0] = vec_ld(48, matA); + B[0][0] = vec_ld(0, matB); + B[1][0] = vec_ld(16, matB); + B[2][0] = vec_ld(32, matB); + B[3][0] = vec_ld(48, matB); + + vMultMatMat_4x4( A, B, C); + + vec_st(C[0][0], 0, result); + vec_st(C[1][0], 16, result); + vec_st(C[2][0], 32, result); + vec_st(C[3][0], 48, result); + } +} + +void mInstallLibrary_Vec() +{ + m_matF_x_matF = vec_MatrixF_x_MatrixF; +} +#else // defined(__VEC__) +void mInstallLibrary_Vec() +{ + Con::warnf("Cannot use altivec math, this build does not support altivec."); +} +#endif// defined(__VEC__) + +#endif// defined(__APPLE__) \ No newline at end of file diff --git a/math/mMathFn.h b/math/mMathFn.h new file mode 100644 index 0000000..3bf6818 --- /dev/null +++ b/math/mMathFn.h @@ -0,0 +1,419 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MMATHFN_H_ +#define _MMATHFN_H_ + +#include +#include + +#ifndef _MCONSTANTS_H_ +#include "math/mConstants.h" +#endif +#ifndef _PLATFORMASSERT_H_ +#include "platform/platformAssert.h" +#endif + + +extern void MathConsoleInit(); + +//-------------------------------------- +// Installable Library Prototypes +extern S32 (*m_mulDivS32)(S32 a, S32 b, S32 c); +extern U32 (*m_mulDivU32)(S32 a, S32 b, U32 c); + +extern F32 (*m_catmullrom)(F32 t, F32 p0, F32 p1, F32 p2, F32 p3); + +extern void (*m_point2F_normalize)(F32 *p); +extern void (*m_point2F_normalize_f)(F32 *p, F32 len); +extern void (*m_point2D_normalize)(F64 *p); +extern void (*m_point2D_normalize_f)(F64 *p, F64 len); +extern void (*m_point3F_normalize)(F32 *p); +extern void (*m_point3F_normalize_f)(F32 *p, F32 len); +extern void (*m_point3F_interpolate)(const F32 *from, const F32 *to, F32 factor, F32 *result); + +extern void (*m_point3D_normalize)(F64 *p); +extern void (*m_point3D_normalize_f)(F64 *p, F64 len); +extern void (*m_point3D_interpolate)(const F64 *from, const F64 *to, F64 factor, F64 *result); + +extern void (*m_point3F_bulk_dot)(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + F32* output); +extern void (*m_point3F_bulk_dot_indexed)(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + const U32* pointIndices, + F32* output); + +extern void (*m_quatF_set_matF)( F32 x, F32 y, F32 z, F32 w, F32* m ); + +extern void (*m_matF_set_euler)(const F32 *e, F32 *result); +extern void (*m_matF_set_euler_point)(const F32 *e, const F32 *p, F32 *result); +extern void (*m_matF_identity)(F32 *m); +extern void (*m_matF_inverse)(F32 *m); +extern void (*m_matF_invert_to)(const F32 *m, F32 *d); +extern void (*m_matF_affineInverse)(F32 *m); +extern void (*m_matF_transpose)(F32 *m); +extern void (*m_matF_scale)(F32 *m,const F32* p); +extern void (*m_matF_normalize)(F32 *m); +extern F32 (*m_matF_determinant)(const F32 *m); +extern void (*m_matF_x_matF)(const F32 *a, const F32 *b, F32 *mresult); +extern void (*m_matF_x_matF_aligned)(const F32 *a, const F32 *b, F32 *mresult); +// extern void (*m_matF_x_point3F)(const F32 *m, const F32 *p, F32 *presult); +// extern void (*m_matF_x_vectorF)(const F32 *m, const F32 *v, F32 *vresult); +extern void (*m_matF_x_point4F)(const F32 *m, const F32 *p, F32 *presult); +extern void (*m_matF_x_scale_x_planeF)(const F32 *m, const F32* s, const F32 *p, F32 *presult); +extern void (*m_matF_x_box3F)(const F32 *m, F32 *min, F32 *max); + +// Note that x must point to at least 4 values for quartics, and 3 for cubics +extern U32 (*mSolveQuadratic)(F32 a, F32 b, F32 c, F32* x); +extern U32 (*mSolveCubic)(F32 a, F32 b, F32 c, F32 d, F32* x); +extern U32 (*mSolveQuartic)(F32 a, F32 b, F32 c, F32 d, F32 e, F32* x); + +// Note that the plane list provided MUST match the one in Frustum +extern bool (*m_planeF_intersect_box3F)(const F32 *planes, const F32 *bounds); + +extern S32 mRandI(S32 i1, S32 i2); // random # from i1 to i2 inclusive +extern F32 mRandF(F32 f1, F32 f2); // random # from f1 to f2 inclusive +extern F32 mRandF(); // random # from 0.0 to 1.0 inclusive + + +inline void m_matF_x_point3F(const F32 *m, const F32 *p, F32 *presult) +{ + AssertFatal(p != presult, "Error, aliasing matrix mul pointers not allowed here!"); + +#ifdef TORQUE_COMPILER_GCC + const F32 p0 = p[0], p1 = p[1], p2 = p[2]; + const F32 m0 = m[0], m1 = m[1], m2 = m[2]; + const F32 m3 = m[3], m4 = m[4], m5 = m[5]; + const F32 m6 = m[6], m7 = m[7], m8 = m[8]; + const F32 m9 = m[9], m10 = m[10], m11 = m[11]; + + presult[0] = m0*p0 + m1*p1 + m2*p2 + m3; + presult[1] = m4*p0 + m5*p1 + m6*p2 + m7; + presult[2] = m8*p0 + m9*p1 + m10*p2 + m11; +#else + presult[0] = m[0]*p[0] + m[1]*p[1] + m[2]*p[2] + m[3]; + presult[1] = m[4]*p[0] + m[5]*p[1] + m[6]*p[2] + m[7]; + presult[2] = m[8]*p[0] + m[9]*p[1] + m[10]*p[2] + m[11]; +#endif +} + + +//-------------------------------------- +inline void m_matF_x_vectorF(const F32 *m, const F32 *v, F32 *vresult) +{ + AssertFatal(v != vresult, "Error, aliasing matrix mul pointers not allowed here!"); + +#ifdef TORQUE_COMPILER_GCC + const F32 v0 = v[0], v1 = v[1], v2 = v[2]; + const F32 m0 = m[0], m1 = m[1], m2 = m[2]; + const F32 m4 = m[4], m5 = m[5], m6 = m[6]; + const F32 m8 = m[8], m9 = m[9], m10 = m[10]; + + vresult[0] = m0*v0 + m1*v1 + m2*v2; + vresult[1] = m4*v0 + m5*v1 + m6*v2; + vresult[2] = m8*v0 + m9*v1 + m10*v2; +#else + vresult[0] = m[0]*v[0] + m[1]*v[1] + m[2]*v[2]; + vresult[1] = m[4]*v[0] + m[5]*v[1] + m[6]*v[2]; + vresult[2] = m[8]*v[0] + m[9]*v[1] + m[10]*v[2]; +#endif +} + + +//-------------------------------------- +// Inlines + +inline bool mIsEqual( F32 a, F32 b, const F32 epsilon = __EQUAL_CONST_F ) +{ + F32 diff = a - b; + return diff > -epsilon && diff < epsilon; +} + +inline bool mIsZero(const F32 val, const F32 epsilon = __EQUAL_CONST_F ) +{ + return (val > -epsilon) && (val < epsilon); +} + +inline F32 mClampToZero(F32& input) +{ + if (input < __EQUAL_CONST_F && input > -__EQUAL_CONST_F) + input = 0.0f; + + return input; +} + + +inline F32 mMax(const F32 x, const F32 y) +{ + if (x > y) + return x; + return y; +} + +inline F32 mFloor(const F32 val) +{ + return (F32) floor(val); +} + +inline F32 mCeil(const F32 val) +{ + return (F32) ceil(val); +} + +inline F32 mFabs(const F32 val) +{ + return (F32) fabs(val); +} + +inline F64 mFabs(const F64 val) +{ + return fabs(val); +} + +inline F32 mFmod(const F32 val, const F32 mod) +{ + return fmod(val, mod); +} + +inline S32 mAbs(const S32 val) +{ + return abs(val); +} + +inline S32 mClamp(S32 val, S32 low, S32 high) +{ + return getMax(getMin(val, high), low); +} + +inline U32 mClampU(U32 val, U32 low, U32 high) +{ + return getMax(getMin(val, high), low); +} + +inline F32 mClampF(F32 val, F32 low, F32 high) +{ + return (F32) getMax(getMin(val, high), low); +} + +inline F32 mLerp( F32 v1, F32 v2, F32 factor ) +{ + return ( v1 * ( 1.0f - factor ) ) + ( v2 * factor ); +} + +inline S32 mMulDiv(S32 a, S32 b, S32 c) +{ + return m_mulDivS32(a, b, c); +} + +inline U32 mMulDiv(S32 a, S32 b, U32 c) +{ + return m_mulDivU32(a, b, c); +} + +inline F32 mSin(const F32 angle) +{ + return (F32) sin(angle); +} + +inline F32 mCos(const F32 angle) +{ + return (F32) cos(angle); +} + +inline F32 mTan(const F32 angle) +{ + return (F32) tan(angle); +} + +inline F32 mAsin(const F32 val) +{ + return (F32) asin(val); +} + +inline F32 mAcos(const F32 val) +{ + return (F32) acos(val); +} + +inline F32 mAtan( const F32 x ) +{ + return (F32) atan( x ); +} + +inline F32 mAtan2(const F32 x, const F32 y) +{ + return (F32) atan2(x, y); +} + +inline void mSinCos(const F32 angle, F32 &s, F32 &c) +{ + s = mSin(angle); + c = mCos(angle); +} + +inline F32 mTanh(const F32 angle) +{ + return (F32) tanh(angle); +} + +inline F32 mSqrt(const F32 val) +{ + return (F32) sqrt(val); +} + +inline F64 mSqrt(const F64 val) +{ + return (F64) sqrt(val); +} + +inline F32 mPow(const F32 x, const F32 y) +{ + return (F32) pow(x, y); +} + +inline F32 mLog(const F32 val) +{ + return (F32) log(val); +} + +inline F32 mExp(const F32 val) +{ + return (F32) exp(val); +} + +inline F64 mSin(const F64 angle) +{ + return (F64) sin(angle); +} + +inline F64 mCos(const F64 angle) +{ + return (F64) cos(angle); +} + +inline F64 mTan(const F64 angle) +{ + return (F64) tan(angle); +} + +inline F64 mAsin(const F64 val) +{ + return (F64) asin(val); +} + +inline F64 mAcos(const F64 val) +{ + return (F64) acos(val); +} + +inline F64 mAtan( const F64 x ) +{ + return (F64) atan( x ); +} + +inline F64 mAtan2(const F64 x, const F64 y) +{ + return (F64) atan2(x, y); +} + +inline void mSinCos(const F64 angle, F64 &sin, F64 &cos) +{ + sin = mSin(angle); + cos = mCos(angle); +} + +inline F64 mTanh(const F64 angle) +{ + return (F64) tanh(angle); +} + +inline F64 mPow(const F64 x, const F64 y) +{ + return (F64) pow(x, y); +} + +inline F64 mLog(const F64 val) +{ + return (F64) log(val); +} + + +inline F32 mCatmullrom(F32 t, F32 p0, F32 p1, F32 p2, F32 p3) +{ + return m_catmullrom(t, p0, p1, p2, p3); +} + + +inline F64 mFabsD(const F64 val) +{ + return (F64) fabs(val); +} + +inline F64 mFmodD(const F64 val, const F64 mod) +{ + return (F64) fmod(val, mod); +} + +inline F64 mSqrtD(const F64 val) +{ + return (F64) sqrt(val); +} + +inline F64 mFloorD(const F64 val) +{ + return (F64) floor(val); +} + +inline F64 mCeilD(const F64 val) +{ + return (F64) ceil(val); +} + +/// +template< typename A, typename B > +inline A mAlignToMultiple( A val, B mul ) +{ + A rem = val % mul; + return ( rem ? val + mul - rem : val ); +} + +//-------------------------------------- +inline F32 mDegToRad(F32 d) +{ + return((d * M_PI_F) / 180.0f); +} + +inline F32 mRadToDeg(F32 r) +{ + return((r * 180.0f) / M_PI_F); +} + +inline F64 mDegToRad(F64 d) +{ + return (d * M_PI) / 180.0; +} + +inline F64 mRadToDeg(F64 r) +{ + return (r * 180.0) / M_PI; +} + +//------------------------------------------------------------------------------ + +inline bool mIsNaN_F( const F32 x ) +{ + // If x is a floating point variable, then (x != x) will be TRUE if x has the value NaN. + // This is only going to work if the compiler is IEEE 748 compliant. + // + // Tested and working on VC2k5 + return ( x != x ); +} + +#endif //_MMATHFN_H_ diff --git a/math/mMathSSE.cpp b/math/mMathSSE.cpp new file mode 100644 index 0000000..9ac2431 --- /dev/null +++ b/math/mMathSSE.cpp @@ -0,0 +1,365 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMathFn.h" +#include "math/mPlane.h" +#include "math/mMatrix.h" + + +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) +#define ADD_SSE_FN +// inlined version here. +void SSE_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) +{ + __asm + { + mov edx, matA + mov ecx, matB + mov eax, result + + movss xmm0, [edx] + movups xmm1, [ecx] + shufps xmm0, xmm0, 0 + movss xmm2, [edx+4] + mulps xmm0, xmm1 + shufps xmm2, xmm2, 0 + movups xmm3, [ecx+10h] + movss xmm7, [edx+8] + mulps xmm2, xmm3 + shufps xmm7, xmm7, 0 + addps xmm0, xmm2 + movups xmm4, [ecx+20h] + movss xmm2, [edx+0Ch] + mulps xmm7, xmm4 + shufps xmm2, xmm2, 0 + addps xmm0, xmm7 + movups xmm5, [ecx+30h] + movss xmm6, [edx+10h] + mulps xmm2, xmm5 + movss xmm7, [edx+14h] + shufps xmm6, xmm6, 0 + addps xmm0, xmm2 + shufps xmm7, xmm7, 0 + movlps [eax], xmm0 + movhps [eax+8], xmm0 + mulps xmm7, xmm3 + movss xmm0, [edx+18h] + mulps xmm6, xmm1 + shufps xmm0, xmm0, 0 + addps xmm6, xmm7 + mulps xmm0, xmm4 + movss xmm2, [edx+24h] + addps xmm6, xmm0 + movss xmm0, [edx+1Ch] + movss xmm7, [edx+20h] + shufps xmm0, xmm0, 0 + shufps xmm7, xmm7, 0 + mulps xmm0, xmm5 + mulps xmm7, xmm1 + addps xmm6, xmm0 + shufps xmm2, xmm2, 0 + movlps [eax+10h], xmm6 + movhps [eax+18h], xmm6 + mulps xmm2, xmm3 + movss xmm6, [edx+28h] + addps xmm7, xmm2 + shufps xmm6, xmm6, 0 + movss xmm2, [edx+2Ch] + mulps xmm6, xmm4 + shufps xmm2, xmm2, 0 + addps xmm7, xmm6 + mulps xmm2, xmm5 + movss xmm0, [edx+34h] + addps xmm7, xmm2 + shufps xmm0, xmm0, 0 + movlps [eax+20h], xmm7 + movss xmm2, [edx+30h] + movhps [eax+28h], xmm7 + mulps xmm0, xmm3 + shufps xmm2, xmm2, 0 + movss xmm6, [edx+38h] + mulps xmm2, xmm1 + shufps xmm6, xmm6, 0 + addps xmm2, xmm0 + mulps xmm6, xmm4 + movss xmm7, [edx+3Ch] + shufps xmm7, xmm7, 0 + addps xmm2, xmm6 + mulps xmm7, xmm5 + addps xmm2, xmm7 + movups [eax+30h], xmm2 + } +} +void SSE_MatrixF_x_MatrixF_Aligned(const F32 *matA, const F32 *matB, F32 *result) +{ + __asm + { + mov edx, matA + mov ecx, matB + mov eax, result + + movss xmm0, [edx] + movaps xmm1, [ecx] + shufps xmm0, xmm0, 0 + movss xmm2, [edx+4] + mulps xmm0, xmm1 + shufps xmm2, xmm2, 0 + movaps xmm3, [ecx+10h] + movss xmm7, [edx+8] + mulps xmm2, xmm3 + shufps xmm7, xmm7, 0 + addps xmm0, xmm2 + movaps xmm4, [ecx+20h] + movss xmm2, [edx+0Ch] + mulps xmm7, xmm4 + shufps xmm2, xmm2, 0 + addps xmm0, xmm7 + movaps xmm5, [ecx+30h] + movss xmm6, [edx+10h] + mulps xmm2, xmm5 + movss xmm7, [edx+14h] + shufps xmm6, xmm6, 0 + addps xmm0, xmm2 + shufps xmm7, xmm7, 0 + movlps [eax], xmm0 + movhps [eax+8], xmm0 + mulps xmm7, xmm3 + movss xmm0, [edx+18h] + mulps xmm6, xmm1 + shufps xmm0, xmm0, 0 + addps xmm6, xmm7 + mulps xmm0, xmm4 + movss xmm2, [edx+24h] + addps xmm6, xmm0 + movss xmm0, [edx+1Ch] + movss xmm7, [edx+20h] + shufps xmm0, xmm0, 0 + shufps xmm7, xmm7, 0 + mulps xmm0, xmm5 + mulps xmm7, xmm1 + addps xmm6, xmm0 + shufps xmm2, xmm2, 0 + movlps [eax+10h], xmm6 + movhps [eax+18h], xmm6 + mulps xmm2, xmm3 + movss xmm6, [edx+28h] + addps xmm7, xmm2 + shufps xmm6, xmm6, 0 + movss xmm2, [edx+2Ch] + mulps xmm6, xmm4 + shufps xmm2, xmm2, 0 + addps xmm7, xmm6 + mulps xmm2, xmm5 + movss xmm0, [edx+34h] + addps xmm7, xmm2 + shufps xmm0, xmm0, 0 + movlps [eax+20h], xmm7 + movss xmm2, [edx+30h] + movhps [eax+28h], xmm7 + mulps xmm0, xmm3 + shufps xmm2, xmm2, 0 + movss xmm6, [edx+38h] + mulps xmm2, xmm1 + shufps xmm6, xmm6, 0 + addps xmm2, xmm0 + mulps xmm6, xmm4 + movss xmm7, [edx+3Ch] + shufps xmm7, xmm7, 0 + addps xmm2, xmm6 + mulps xmm7, xmm5 + addps xmm2, xmm7 + movaps [eax+30h], xmm2 + } +} +// if we set our flag, we always try to build the inlined asm. +// EXCEPT if we're in an old version of Codewarrior that can't handle SSE code. +#elif defined(TORQUE_SUPPORTS_NASM) +#define ADD_SSE_FN +extern "C" +{ + void SSE_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result); + void SSE_MatrixF_x_MatrixF_Aligned(const F32 *matA, const F32 *matB, F32 *result); +} + +#elif defined( TORQUE_COMPILER_GCC ) && defined( TORQUE_CPU_X86 ) +#define ADD_SSE_FN + +void SSE_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) +{ + asm + ( + "movss (%%edx),%%xmm0\n" + "movups (%%ecx),%%xmm1\n" + "shufps $0,%%xmm0,%%xmm0\n" + "movss 4(%%edx),%%xmm2\n" + "mulps %%xmm1,%%xmm0\n" + "shufps $0,%%xmm2,%%xmm2\n" + "movups 0x10(%%ecx),%%xmm3\n" + "movss 8(%%edx),%%xmm7\n" + "mulps %%xmm3,%%xmm2\n" + "shufps $0,%%xmm7,%%xmm7\n" + "addps %%xmm2,%%xmm0\n" + "movups 0x20(%%ecx),%%xmm4\n" + "movss 0x0c(%%edx),%%xmm2\n" + "mulps %%xmm4,%%xmm7\n" + "shufps $0,%%xmm2,%%xmm2\n" + "addps %%xmm7,%%xmm0\n" + "movups 0x30(%%ecx),%%xmm5\n" + "movss 0x10(%%edx),%%xmm6\n" + "mulps %%xmm5,%%xmm2\n" + "movss 0x14(%%edx),%%xmm7\n" + "shufps $0,%%xmm6,%%xmm6\n" + "addps %%xmm2,%%xmm0\n" + "shufps $0,%%xmm7,%%xmm7\n" + "movlps %%xmm0,(%%eax)\n" + "movhps %%xmm0,8(%%eax)\n" + "mulps %%xmm3,%%xmm7\n" + "movss 0x18(%%edx),%%xmm0\n" + "mulps %%xmm1,%%xmm6\n" + "shufps $0,%%xmm0,%%xmm0\n" + "addps %%xmm7,%%xmm6\n" + "mulps %%xmm4,%%xmm0\n" + "movss 0x24(%%edx),%%xmm2\n" + "addps %%xmm0,%%xmm6\n" + "movss 0x1c(%%edx),%%xmm0\n" + "movss 0x20(%%edx),%%xmm7\n" + "shufps $0,%%xmm0,%%xmm0\n" + "shufps $0,%%xmm7,%%xmm7\n" + "mulps %%xmm5,%%xmm0\n" + "mulps %%xmm1,%%xmm7\n" + "addps %%xmm0,%%xmm6\n" + "shufps $0,%%xmm2,%%xmm2\n" + "movlps %%xmm6,0x10(%%eax)\n" + "movhps %%xmm6,0x18(%%eax)\n" + "mulps %%xmm3,%%xmm2\n" + "movss 0x28(%%edx),%%xmm6\n" + "addps %%xmm2,%%xmm7\n" + "shufps $0,%%xmm6,%%xmm6\n" + "movss 0x2c(%%edx),%%xmm2\n" + "mulps %%xmm4,%%xmm6\n" + "shufps $0,%%xmm2,%%xmm2\n" + "addps %%xmm6,%%xmm7\n" + "mulps %%xmm5,%%xmm2\n" + "movss 0x34(%%edx),%%xmm0\n" + "addps %%xmm2,%%xmm7\n" + "shufps $0,%%xmm0,%%xmm0\n" + "movlps %%xmm7,0x20(%%eax)\n" + "movss 0x30(%%edx),%%xmm2\n" + "movhps %%xmm7,0x28(%%eax)\n" + "mulps %%xmm3,%%xmm0\n" + "shufps $0,%%xmm2,%%xmm2\n" + "movss 0x38(%%edx),%%xmm6\n" + "mulps %%xmm1,%%xmm2\n" + "shufps $0,%%xmm6,%%xmm6\n" + "addps %%xmm0,%%xmm2\n" + "mulps %%xmm4,%%xmm6\n" + "movss 0x3c(%%edx),%%xmm7\n" + "shufps $0,%%xmm7,%%xmm7\n" + "addps %%xmm6,%%xmm2\n" + "mulps %%xmm5,%%xmm7\n" + "addps %%xmm7,%%xmm2\n" + "movups %%xmm2,0x30(%%eax)\n" + + : + : "d" ( matA ), + "c" ( matB ), + "a" ( result ) + ); +} + +void SSE_MatrixF_x_MatrixF_Aligned(const F32 *matA, const F32 *matB, F32 *result) +{ + asm + ( + "movss (%%edx),%%xmm0\n" + "movaps (%%ecx),%%xmm1\n" + "shufps $0,%%xmm0,%%xmm0\n" + "movss 4(%%edx),%%xmm2\n" + "mulps %%xmm1,%%xmm0\n" + "shufps $0,%%xmm2,%%xmm2\n" + "movaps 0x10(%%ecx),%%xmm3\n" + "movss 8(%%edx),%%xmm7\n" + "mulps %%xmm3,%%xmm2\n" + "shufps $0,%%xmm7,%%xmm7\n" + "addps %%xmm2,%%xmm0\n" + "movaps 0x20(%%ecx),%%xmm4\n" + "movss 0x0c(%%edx),%%xmm2\n" + "mulps %%xmm4,%%xmm7\n" + "shufps $0,%%xmm2,%%xmm2\n" + "addps %%xmm7,%%xmm0\n" + "movaps 0x30(%%ecx),%%xmm5\n" + "movss 0x10(%%edx),%%xmm6\n" + "mulps %%xmm5,%%xmm2\n" + "movss 0x14(%%edx),%%xmm7\n" + "shufps $0,%%xmm6,%%xmm6\n" + "addps %%xmm2,%%xmm0\n" + "shufps $0,%%xmm7,%%xmm7\n" + "movlps %%xmm0,(%%eax)\n" + "movhps %%xmm0,8(%%eax)\n" + "mulps %%xmm3,%%xmm7\n" + "movss 0x18(%%edx),%%xmm0\n" + "mulps %%xmm1,%%xmm6\n" + "shufps $0,%%xmm0,%%xmm0\n" + "addps %%xmm7,%%xmm6\n" + "mulps %%xmm4,%%xmm0\n" + "movss 0x24(%%edx),%%xmm2\n" + "addps %%xmm0,%%xmm6\n" + "movss 0x1c(%%edx),%%xmm0\n" + "movss 0x20(%%edx),%%xmm7\n" + "shufps $0,%%xmm0,%%xmm0\n" + "shufps $0,%%xmm7,%%xmm7\n" + "mulps %%xmm5,%%xmm0\n" + "mulps %%xmm1,%%xmm7\n" + "addps %%xmm0,%%xmm6\n" + "shufps $0,%%xmm2,%%xmm2\n" + "movlps %%xmm6,0x10(%%eax)\n" + "movhps %%xmm6,0x18(%%eax)\n" + "mulps %%xmm3,%%xmm2\n" + "movss 0x28(%%edx),%%xmm6\n" + "addps %%xmm2,%%xmm7\n" + "shufps $0,%%xmm6,%%xmm6\n" + "movss 0x2c(%%edx),%%xmm2\n" + "mulps %%xmm4,%%xmm6\n" + "shufps $0,%%xmm2,%%xmm2\n" + "addps %%xmm6,%%xmm7\n" + "mulps %%xmm5,%%xmm2\n" + "movss 0x34(%%edx),%%xmm0\n" + "addps %%xmm2,%%xmm7\n" + "shufps $0,%%xmm0,%%xmm0\n" + "movlps %%xmm7,0x20(%%eax)\n" + "movss 0x30(%%edx),%%xmm2\n" + "movhps %%xmm7,0x28(%%eax)\n" + "mulps %%xmm3,%%xmm0\n" + "shufps $0,%%xmm2,%%xmm2\n" + "movss 0x38(%%edx),%%xmm6\n" + "mulps %%xmm1,%%xmm2\n" + "shufps $0,%%xmm6,%%xmm6\n" + "addps %%xmm0,%%xmm2\n" + "mulps %%xmm4,%%xmm6\n" + "movss 0x3c(%%edx),%%xmm7\n" + "shufps $0,%%xmm7,%%xmm7\n" + "addps %%xmm6,%%xmm2\n" + "mulps %%xmm5,%%xmm7\n" + "addps %%xmm7,%%xmm2\n" + "movaps %%xmm2,0x30(%%eax)\n" + + : + : "d" ( matA ), + "c" ( matB ), + "a" ( result ) + ); +} + +#endif + +void mInstall_Library_SSE() +{ +#if defined(ADD_SSE_FN) + m_matF_x_matF = SSE_MatrixF_x_MatrixF; + m_matF_x_matF_aligned = SSE_MatrixF_x_MatrixF_Aligned; + // m_matF_x_point3F = Athlon_MatrixF_x_Point3F; + // m_matF_x_vectorF = Athlon_MatrixF_x_VectorF; +#endif +} diff --git a/math/mMathSSE_ASM.asm b/math/mMathSSE_ASM.asm new file mode 100644 index 0000000..cd9dce5 --- /dev/null +++ b/math/mMathSSE_ASM.asm @@ -0,0 +1,111 @@ +;----------------------------------------------------------------------------- +; Torque Game Engine +; Copyright (C) GarageGames.com, Inc. +;----------------------------------------------------------------------------- + + +segment .data + +matA dd 0 +result dd 0 +matB dd 0 + +segment .text + +%macro export_fn 1 + %ifidn __OUTPUT_FORMAT__, elf + ; No underscore needed for ELF object files + global %1 + %1: + %else + global _%1 + _%1: + %endif +%endmacro + + +%define arg(x) [esp+(x*4)] + + + +;void SSE_MatrixF_x_MatrixF(const F32 *matA, const F32 *matB, F32 *result) + +export_fn SSE_MatrixF_x_MatrixF + + mov edx, arg(1) + mov ecx, arg(2) + mov eax, arg(3) + + movss xmm0, [edx] + movups xmm1, [ecx] + shufps xmm0, xmm0, 0 + movss xmm2, [edx+4] + mulps xmm0, xmm1 + shufps xmm2, xmm2, 0 + movups xmm3, [ecx+10h] + movss xmm7, [edx+8] + mulps xmm2, xmm3 + shufps xmm7, xmm7, 0 + addps xmm0, xmm2 + movups xmm4, [ecx+20h] + movss xmm2, [edx+0Ch] + mulps xmm7, xmm4 + shufps xmm2, xmm2, 0 + addps xmm0, xmm7 + movups xmm5, [ecx+30h] + movss xmm6, [edx+10h] + mulps xmm2, xmm5 + movss xmm7, [edx+14h] + shufps xmm6, xmm6, 0 + addps xmm0, xmm2 + shufps xmm7, xmm7, 0 + movlps [eax], xmm0 + movhps [eax+8], xmm0 + mulps xmm7, xmm3 + movss xmm0, [edx+18h] + mulps xmm6, xmm1 + shufps xmm0, xmm0, 0 + addps xmm6, xmm7 + mulps xmm0, xmm4 + movss xmm2, [edx+24h] + addps xmm6, xmm0 + movss xmm0, [edx+1Ch] + movss xmm7, [edx+20h] + shufps xmm0, xmm0, 0 + shufps xmm7, xmm7, 0 + mulps xmm0, xmm5 + mulps xmm7, xmm1 + addps xmm6, xmm0 + shufps xmm2, xmm2, 0 + movlps [eax+10h], xmm6 + movhps [eax+18h], xmm6 + mulps xmm2, xmm3 + movss xmm6, [edx+28h] + addps xmm7, xmm2 + shufps xmm6, xmm6, 0 + movss xmm2, [edx+2Ch] + mulps xmm6, xmm4 + shufps xmm2, xmm2, 0 + addps xmm7, xmm6 + mulps xmm2, xmm5 + movss xmm0, [edx+34h] + addps xmm7, xmm2 + shufps xmm0, xmm0, 0 + movlps [eax+20h], xmm7 + movss xmm2, [edx+30h] + movhps [eax+28h], xmm7 + mulps xmm0, xmm3 + shufps xmm2, xmm2, 0 + movss xmm6, [edx+38h] + mulps xmm2, xmm1 + shufps xmm6, xmm6, 0 + addps xmm2, xmm0 + mulps xmm6, xmm4 + movss xmm7, [edx+3Ch] + shufps xmm7, xmm7, 0 + addps xmm2, xmm6 + mulps xmm7, xmm5 + addps xmm2, xmm7 + movups [eax+30h], xmm2 + + ret diff --git a/math/mMath_ASM.asm b/math/mMath_ASM.asm new file mode 100644 index 0000000..bdd4dee --- /dev/null +++ b/math/mMath_ASM.asm @@ -0,0 +1,221 @@ +;----------------------------------------------------------------------------- +; Torque Game Engine +; Copyright (C) GarageGames.com, Inc. +;----------------------------------------------------------------------------- +; +; NASM version of optimized funcs in mMath_C +; + +; The following funcs are included: +; m_ceil_ASM, m_ceilD_ASM, m_floor_ASM, m_floorD_ASM +; m_fmod_ASM, m_fmodD_ASM, m_mulDivS32_ASM, m_mulDivU32_ASM +; m_sincos_ASM, m_sincosD_ASM + +; The other funcs from mMath_C were determined to compile into fast +; code using MSVC --Paul Bowman + + +segment .data + + +temp_int64 dq 0.0 +const_0pt5_D dq 0.4999999999995 +temp_int32 dd 0 +const_0pt5 dd 0.49999995 +const_neg1 dd -1.0 + + +segment .text + +%macro export_fn 1 + %ifidn __OUTPUT_FORMAT__, elf + ; No underscore needed for ELF object files + global %1 + %1: + %else + global _%1 + _%1: + %endif +%endmacro + +%define rnd_adjD qword [const_0pt5_D] +%define rnd_adj dword [const_0pt5] + + +%define val dword [esp+4] +%define val64 qword [esp+4] +; +; static F32 m_ceil_ASM(F32 val) +; +export_fn m_ceil_ASM + fld val + fadd rnd_adj + fistp qword [temp_int64] + fild qword [temp_int64] + ret + +; +; static F64 m_ceilD_ASM(F64 val64) +; +export_fn m_ceilD_ASM + fld val64 + fadd rnd_adjD + fistp qword [temp_int64] + fild qword [temp_int64] + ret + +; +; static F32 m_floor_ASM(F32 val) +; +export_fn m_floor_ASM + fld val + fsub rnd_adj + fistp qword [temp_int64] + fild qword [temp_int64] + ret + + +; +; static F32 m_floorD_ASM( F64 val64 ) +; +export_fn m_floorD_ASM + fld val64 + fsub rnd_adjD + fistp qword [temp_int64] + fild qword [temp_int64] + ret + + + +%define arg_a dword [esp+4] +%define arg_b dword [esp+8] +%define arg_c dword [esp+12] + +; +; static S32 m_mulDivS32_ASM( S32 a, S32 b, S32 c ) +; +; // Note: this returns different (but correct) values than the C +; // version. C code must be overflowing...returns -727 +; // if a b and c are 1 million, for instance. This version returns +; // 1 million. +; return (S32) ((S64)a*(S64)b) / (S64)c; +; +export_fn m_mulDivS32_ASM + mov eax, arg_a + imul arg_b + idiv arg_c + ret + +; +; static U32 m_mulDivU32_ASM( U32 a, U32 b, U32 c ) +; +; // Note: again, C version overflows +; +export_fn m_mulDivU32_ASM + mov eax, arg_a + mul arg_b + div arg_c + ret + + + +; val is already defined above to be esp+4 +%define modulo dword [esp+8] + + +; +; static F32 m_fmod_ASM(F32 val, F32 modulo) +; +export_fn m_fmod_ASM + mov eax, val + fld modulo + fabs + fld val + fabs + fdiv st0, st1 + fld st0 + fsub rnd_adj + fistp qword [temp_int64] + fild qword [temp_int64] + fsubp st1, st0 + fmulp st1, st0 + +; // sign bit can be read as integer high bit, +; // as long as # isn't 0x80000000 + cmp eax, 0x80000000 + jbe notneg + + fmul dword [const_neg1] + +notneg: + ret + + +%define val64hi dword [esp+8] +%define val64 qword [esp+4] +%define modulo64 qword [esp+12] + +; +; static F32 m_fmodD_ASM(F64 val, F64 modulo) +; +export_fn m_fmodD_ASM + mov eax, val64hi + fld modulo64 + fabs + fld val64 + fabs + fdiv st0, st1 + fld st0 + fsub rnd_adjD + fistp qword [temp_int64] + fild qword [temp_int64] + fsubp st1, st0 + fmulp st1, st0 + +; // sign bit can be read as integer high bit, +; // as long as # isn't 0x80000000 + cmp eax, 0x80000000 + jbe notnegD + + fmul dword [const_neg1] + +notnegD: + ret + + + +%define angle dword [esp+4] +%define res_sin dword [esp+8] +%define res_cos dword [esp+12] + +; +;static void m_sincos_ASM( F32 angle, F32 *s, F32 *c ) +; +export_fn m_sincos_ASM + mov eax, res_cos + fld angle + fsincos + fstp dword [eax] + mov eax, res_sin + fstp dword [eax] + ret + + + +%define angle64 qword [esp+4] +%define res_sin64 dword [esp+12] +%define res_cos64 dword [esp+16] +; +;static void m_sincosD_ASM( F64 angle, F64 *s, F64 *c ) +; +export_fn m_sincosD_ASM + mov eax, res_cos64 + fld angle64 + fsincos + fstp qword [eax] + mov eax, res_sin64 + fstp qword [eax] + ret + + + diff --git a/math/mMath_C.cpp b/math/mMath_C.cpp new file mode 100644 index 0000000..cc40f87 --- /dev/null +++ b/math/mMath_C.cpp @@ -0,0 +1,958 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMath.h" +#include // Caution!!! Possible platform specific include + + +//------------------------------------------------------------------------------ +// C version of Math Library + +// asm externals +extern "C" { + + S32 m_mulDivS32_ASM( S32 a, S32 b, S32 c ); + U32 m_mulDivU32_ASM( U32 a, U32 b, U32 c ); + F32 m_fmod_ASM(F32 val, F32 modulo); + F32 m_fmodD_ASM(F64 val, F64 modulo); + void m_sincos_ASM( F32 angle, F32 *s, F32 *c ); + void m_sincosD_ASM( F64 angle, F64 *s, F64 *c ); + +} +//-------------------------------------- + +static S32 m_mulDivS32_C(S32 a, S32 b, S32 c) +{ + // S64/U64 support in most 32-bit compilers generate + // horrible code, the C version are here just for porting + // assembly implementation is recommended + return (S32) ((S64)a*(S64)b) / (S64)c; +} + +static U32 m_mulDivU32_C(S32 a, S32 b, U32 c) +{ + return (U32) ((S64)a*(S64)b) / (U64)c; +} + + +//-------------------------------------- +static F32 m_catmullrom_C(F32 t, F32 p0, F32 p1, F32 p2, F32 p3) +{ + return 0.5f * ((3.0f*p1 - 3.0f*p2 + p3 - p0)*t*t*t + + (2.0f*p0 - 5.0f*p1 + 4.0f*p2 - p3)*t*t + + (p2-p0)*t + + 2.0f*p1); +} + + +//-------------------------------------- +static void m_point2F_normalize_C(F32 *p) +{ + F32 factor = 1.0f / mSqrt(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +static void m_point2F_normalize_f_C(F32 *p, F32 val) +{ + F32 factor = val / mSqrt(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +static void m_point2D_normalize_C(F64 *p) +{ + F64 factor = 1.0f / mSqrtD(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +static void m_point2D_normalize_f_C(F64 *p, F64 val) +{ + F64 factor = val / mSqrtD(p[0]*p[0] + p[1]*p[1] ); + p[0] *= factor; + p[1] *= factor; +} + +//-------------------------------------- +static void m_point3D_normalize_f_C(F64 *p, F64 val) +{ + F64 factor = val / mSqrtD(p[0]*p[0] + p[1]*p[1] + p[2]*p[2]); + p[0] *= factor; + p[1] *= factor; + p[2] *= factor; +} + +//-------------------------------------- +static void m_point3F_normalize_C(F32 *p) +{ + F32 squared = p[0]*p[0] + p[1]*p[1] + p[2]*p[2]; + // This can happen in Container::castRay -> ForceFieldBare::castRay + //AssertFatal(squared != 0.0, "Error, zero length vector normalized!"); + if (squared != 0.0f) { + F32 factor = 1.0f / mSqrt(squared); + p[0] *= factor; + p[1] *= factor; + p[2] *= factor; + } else { + p[0] = 0.0f; + p[1] = 0.0f; + p[2] = 1.0f; + } +} + +//-------------------------------------- +static void m_point3F_normalize_f_C(F32 *p, F32 val) +{ + F32 factor = val / mSqrt(p[0]*p[0] + p[1]*p[1] + p[2]*p[2] ); + p[0] *= factor; + p[1] *= factor; + p[2] *= factor; +} + +//-------------------------------------- +static void m_point3F_interpolate_C(const F32 *from, const F32 *to, F32 factor, F32 *result ) +{ +#ifdef TORQUE_COMPILER_GCC +// remove possibility of aliases + const F32 inverse = 1.0f - factor; + const F32 from0 = from[0], from1 = from[1], from2 = from[2]; + const F32 to0 = to[0], to1 = to[1], to2 = to[2]; + + result[0] = from0 * inverse + to0 * factor; + result[1] = from1 * inverse + to1 * factor; + result[2] = from2 * inverse + to2 * factor; +#else + F32 inverse = 1.0f - factor; + result[0] = from[0] * inverse + to[0] * factor; + result[1] = from[1] * inverse + to[1] * factor; + result[2] = from[2] * inverse + to[2] * factor; +#endif +} + +//-------------------------------------- +static void m_point3D_normalize_C(F64 *p) +{ + F64 factor = 1.0f / mSqrtD(p[0]*p[0] + p[1]*p[1] + p[2]*p[2] ); + p[0] *= factor; + p[1] *= factor; + p[2] *= factor; +} + + +//-------------------------------------- +static void m_point3D_interpolate_C(const F64 *from, const F64 *to, F64 factor, F64 *result ) +{ +#ifdef TORQUE_COMPILER_GCC +// remove possibility of aliases + const F64 inverse = 1.0f - factor; + const F64 from0 = from[0], from1 = from[1], from2 = from[2]; + const F64 to0 = to[0], to1 = to[1], to2 = to[2]; + + result[0] = from0 * inverse + to0 * factor; + result[1] = from1 * inverse + to1 * factor; + result[2] = from2 * inverse + to2 * factor; +#else + F64 inverse = 1.0f - factor; + result[0] = from[0] * inverse + to[0] * factor; + result[1] = from[1] * inverse + to[1] * factor; + result[2] = from[2] * inverse + to[2] * factor; +#endif +} + + +static void m_quatF_set_matF_C( F32 x, F32 y, F32 z, F32 w, F32* m ) +{ +#define qidx(r,c) (r*4 + c) + F32 xs = x * 2.0f; + F32 ys = y * 2.0f; + F32 zs = z * 2.0f; + F32 wx = w * xs; + F32 wy = w * ys; + F32 wz = w * zs; + F32 xx = x * xs; + F32 xy = x * ys; + F32 xz = x * zs; + F32 yy = y * ys; + F32 yz = y * zs; + F32 zz = z * zs; + m[qidx(0,0)] = 1.0f - (yy + zz); + m[qidx(1,0)] = xy - wz; + m[qidx(2,0)] = xz + wy; + m[qidx(3,0)] = 0.0f; + m[qidx(0,1)] = xy + wz; + m[qidx(1,1)] = 1.0f - (xx + zz); + m[qidx(2,1)] = yz - wx; + m[qidx(3,1)] = 0.0f; + m[qidx(0,2)] = xz - wy; + m[qidx(1,2)] = yz + wx; + m[qidx(2,2)] = 1.0f - (xx + yy); + m[qidx(3,2)] = 0.0f; + + m[qidx(0,3)] = 0.0f; + m[qidx(1,3)] = 0.0f; + m[qidx(2,3)] = 0.0f; + m[qidx(3,3)] = 1.0f; +#undef qidx +} + + +//-------------------------------------- +static void m_matF_set_euler_C(const F32 *e, F32 *result) +{ + enum { + AXIS_X = (1<<0), + AXIS_Y = (1<<1), + AXIS_Z = (1<<2) + }; + + U32 axis = 0; + if (e[0] != 0.0f) axis |= AXIS_X; + if (e[1] != 0.0f) axis |= AXIS_Y; + if (e[2] != 0.0f) axis |= AXIS_Z; + + switch (axis) + { + case 0: + m_matF_identity(result); + break; + + case AXIS_X: + { + F32 cx,sx; + mSinCos( e[0], sx, cx ); + + result[0] = 1.0f; + result[1] = 0.0f; + result[2] = 0.0f; + result[3] = 0.0f; + + result[4] = 0.0f; + result[5] = cx; + result[6] = sx; + result[7] = 0.0f; + + result[8] = 0.0f; + result[9] = -sx; + result[10]= cx; + result[11]= 0.0f; + + result[12]= 0.0f; + result[13]= 0.0f; + result[14]= 0.0f; + result[15]= 1.0f; + break; + } + + case AXIS_Y: + { + F32 cy,sy; + mSinCos( e[1], sy, cy ); + + result[0] = cy; + result[1] = 0.0f; + result[2] = -sy; + result[3] = 0.0f; + + result[4] = 0.0f; + result[5] = 1.0f; + result[6] = 0.0f; + result[7] = 0.0f; + + result[8] = sy; + result[9] = 0.0f; + result[10]= cy; + result[11]= 0.0f; + + result[12]= 0.0f; + result[13]= 0.0f; + result[14]= 0.0f; + result[15]= 1.0f; + break; + } + + case AXIS_Z: + { + // the matrix looks like this: + // r1 - (r4 * sin(x)) r2 + (r3 * sin(x)) -cos(x) * sin(y) + // -cos(x) * sin(z) cos(x) * cos(z) sin(x) + // r3 + (r2 * sin(x)) r4 - (r1 * sin(x)) cos(x) * cos(y) + // + // where: + // r1 = cos(y) * cos(z) + // r2 = cos(y) * sin(z) + // r3 = sin(y) * cos(z) + // r4 = sin(y) * sin(z) + F32 cz,sz; + mSinCos( e[2], sz, cz ); + + result[0] = cz; + result[1] = sz; + result[2] = 0.0f; + result[3] = 0.0f; + + result[4] = -sz; + result[5] = cz; + result[6] = 0.0f; + result[7] = 0.0f; + + result[8] = 0.0f; + result[9] = 0.0f; + result[10]= 1.0f; + result[11]= 0.0f; + + result[12]= 0.0f; + result[13]= 0.0f; + result[14]= 0.0f; + result[15]= 1.0f; + break; + } + + default: + // the matrix looks like this: + // r1 - (r4 * sin(x)) r2 + (r3 * sin(x)) -cos(x) * sin(y) + // -cos(x) * sin(z) cos(x) * cos(z) sin(x) + // r3 + (r2 * sin(x)) r4 - (r1 * sin(x)) cos(x) * cos(y) + // + // where: + // r1 = cos(y) * cos(z) + // r2 = cos(y) * sin(z) + // r3 = sin(y) * cos(z) + // r4 = sin(y) * sin(z) + F32 cx,sx; + mSinCos( e[0], sx, cx ); + F32 cy,sy; + mSinCos( e[1], sy, cy ); + F32 cz,sz; + mSinCos( e[2], sz, cz ); + F32 r1 = cy * cz; + F32 r2 = cy * sz; + F32 r3 = sy * cz; + F32 r4 = sy * sz; + + result[0] = r1 - (r4 * sx); + result[1] = r2 + (r3 * sx); + result[2] = -cx * sy; + result[3] = 0.0f; + + result[4] = -cx * sz; + result[5] = cx * cz; + result[6] = sx; + result[7] = 0.0f; + + result[8] = r3 + (r2 * sx); + result[9] = r4 - (r1 * sx); + result[10]= cx * cy; + result[11]= 0.0f; + + result[12]= 0.0f; + result[13]= 0.0f; + result[14]= 0.0f; + result[15]= 1.0f; + break; + } +} + + +//-------------------------------------- +static void m_matF_set_euler_point_C(const F32 *e, const F32 *p, F32 *result) +{ + m_matF_set_euler(e, result); + result[3] = p[0]; + result[7] = p[1]; + result[11]= p[2]; +} + +//-------------------------------------- +static void m_matF_identity_C(F32 *m) +{ + *m++ = 1.0f; + *m++ = 0.0f; + *m++ = 0.0f; + *m++ = 0.0f; + + *m++ = 0.0f; + *m++ = 1.0f; + *m++ = 0.0f; + *m++ = 0.0f; + + *m++ = 0.0f; + *m++ = 0.0f; + *m++ = 1.0f; + *m++ = 0.0f; + + *m++ = 0.0f; + *m++ = 0.0f; + *m++ = 0.0f; + *m = 1.0f; +} + +#if 0 +// Compile this out till we hook it up. It's a more efficient matrix +// inverse than what we have (it uses intermediate results of determinant +// to same about 1/4 of the operations. +static void affineInvertTo(const F32 * m, F32 * out) +{ +#define idx(r,c) (r*4 + c) + F32 d1 = m[idx(2,2)] * m[idx(1,1)] - m[idx(2,1)] * m[idx(1,2)]; + F32 d2 = m[idx(2,0)] * m[idx(1,2)] - m[idx(2,2)] * m[idx(1,0)]; + F32 d3 = m[idx(2,1)] * m[idx(1,0)] - m[idx(2,0)] * m[idx(1,1)]; + + F32 invDet = 1.0f / (m[idx(0,0)] * d1 + m[idx(0,1)] * d2 + m[idx(0,2)] * d3); + + F32 m00 = m[idx(0,0)] * invDet; + F32 m01 = m[idx(0,1)] * invDet; + F32 m02 = m[idx(0,2)] * invDet; + + F32 * result = out; + *out++ = d1 * invDet; + *out++ = m02 * m[idx(2,1)] - m01 * m[idx(2,2)]; + *out++ = m01 * m[idx(1,2)] - m02 * m[idx(1,1)]; + *out++ = 0.0f; + + *out++ = d2 * invDet; + *out++ = m00 * m[idx(2,2)] - m02 * m[idx(2,0)]; + *out++ = m02 * m[idx(1,0)] - m00 * m[idx(1,2)]; + *out++ = 0.0f; + + *out++ = d3 * invDet; + *out++ = m01 * m[idx(2,0)] - m00 * m[idx(2,1)]; + *out++ = m00 * m[idx(1,1)] - m01 * m[idx(1,0)]; + *out++ = 0.0f; + + *out++ = -result[idx(0,0)] * m[idx(0,3)] - result[idx(0,1)] * m[idx(1,3)] - result[idx(0,2)] * m[idx(2,3)]; + *out++ = -result[idx(1,0)] * m[idx(0,3)] - result[idx(1,1)] * m[idx(1,3)] - result[idx(1,2)] * m[idx(2,3)]; + *out++ = -result[idx(2,0)] * m[idx(0,3)] - result[idx(2,1)] * m[idx(1,3)] - result[idx(2,2)] * m[idx(2,3)]; + *out++ = 1.0f; +#undef idx +} +#endif + +//-------------------------------------- +static void m_matF_inverse_C(F32 *m) +{ + // using Cramers Rule find the Inverse + // Minv = (1/det(M)) * adjoint(M) + F32 det = m_matF_determinant( m ); + AssertFatal( det != 0.0f, "MatrixF::inverse: non-singular matrix, no inverse."); + + F32 invDet = 1.0f/det; + F32 temp[16]; + + temp[0] = (m[5] * m[10]- m[6] * m[9]) * invDet; + temp[1] = (m[9] * m[2] - m[10]* m[1]) * invDet; + temp[2] = (m[1] * m[6] - m[2] * m[5]) * invDet; + + temp[4] = (m[6] * m[8] - m[4] * m[10])* invDet; + temp[5] = (m[10]* m[0] - m[8] * m[2]) * invDet; + temp[6] = (m[2] * m[4] - m[0] * m[6]) * invDet; + + temp[8] = (m[4] * m[9] - m[5] * m[8]) * invDet; + temp[9] = (m[8] * m[1] - m[9] * m[0]) * invDet; + temp[10]= (m[0] * m[5] - m[1] * m[4]) * invDet; + + m[0] = temp[0]; + m[1] = temp[1]; + m[2] = temp[2]; + + m[4] = temp[4]; + m[5] = temp[5]; + m[6] = temp[6]; + + m[8] = temp[8]; + m[9] = temp[9]; + m[10] = temp[10]; + + // invert the translation + temp[0] = -m[3]; + temp[1] = -m[7]; + temp[2] = -m[11]; + m_matF_x_vectorF(m, temp, &temp[4]); + m[3] = temp[4]; + m[7] = temp[5]; + m[11]= temp[6]; +} + +static void m_matF_invert_to_C(const F32 *m, F32 *d) +{ + // using Cramers Rule find the Inverse + // Minv = (1/det(M)) * adjoint(M) + F32 det = m_matF_determinant( m ); + AssertFatal( det != 0.0f, "MatrixF::inverse: non-singular matrix, no inverse."); + + F32 invDet = 1.0f/det; + + d[0] = (m[5] * m[10]- m[6] * m[9]) * invDet; + d[1] = (m[9] * m[2] - m[10]* m[1]) * invDet; + d[2] = (m[1] * m[6] - m[2] * m[5]) * invDet; + + d[4] = (m[6] * m[8] - m[4] * m[10])* invDet; + d[5] = (m[10]* m[0] - m[8] * m[2]) * invDet; + d[6] = (m[2] * m[4] - m[0] * m[6]) * invDet; + + d[8] = (m[4] * m[9] - m[5] * m[8]) * invDet; + d[9] = (m[8] * m[1] - m[9] * m[0]) * invDet; + d[10]= (m[0] * m[5] - m[1] * m[4]) * invDet; + + // invert the translation + F32 temp[6]; + temp[0] = -m[3]; + temp[1] = -m[7]; + temp[2] = -m[11]; + m_matF_x_vectorF(m, temp, &temp[3]); + d[3] = temp[3]; + d[7] = temp[4]; + d[11]= temp[5]; +} + +//-------------------------------------- +static void m_matF_affineInverse_C(F32 *m) +{ + // Matrix class checks to make sure this is an affine transform before calling + // this function, so we can proceed assuming it is... + F32 temp[16]; + dMemcpy(temp, m, 16 * sizeof(F32)); + + // Transpose rotation + m[1] = temp[4]; + m[4] = temp[1]; + m[2] = temp[8]; + m[8] = temp[2]; + m[6] = temp[9]; + m[9] = temp[6]; + + m[3] = -(temp[0]*temp[3] + temp[4]*temp[7] + temp[8]*temp[11]); + m[7] = -(temp[1]*temp[3] + temp[5]*temp[7] + temp[9]*temp[11]); + m[11] = -(temp[2]*temp[3] + temp[6]*temp[7] + temp[10]*temp[11]); +} + +inline void swap(F32 &a, F32 &b) +{ + F32 temp = a; + a = b; + b = temp; +} + +//-------------------------------------- +static void m_matF_transpose_C(F32 *m) +{ + swap(m[1], m[4]); + swap(m[2], m[8]); + swap(m[3], m[12]); + swap(m[6], m[9]); + swap(m[7], m[13]); + swap(m[11],m[14]); +} + +//-------------------------------------- +static void m_matF_scale_C(F32 *m,const F32 *p) +{ + // Note, doesn't allow scaling w... + + m[0] *= p[0]; m[1] *= p[1]; m[2] *= p[2]; + m[4] *= p[0]; m[5] *= p[1]; m[6] *= p[2]; + m[8] *= p[0]; m[9] *= p[1]; m[10] *= p[2]; + m[12] *= p[0]; m[13] *= p[1]; m[14] *= p[2]; +} + +//-------------------------------------- +static void m_matF_normalize_C(F32 *m) +{ + F32 col0[3], col1[3], col2[3]; + // extract columns 0 and 1 + col0[0] = m[0]; + col0[1] = m[4]; + col0[2] = m[8]; + + col1[0] = m[1]; + col1[1] = m[5]; + col1[2] = m[9]; + + // assure their relationships to one another + mCross(*(Point3F*)col0, *(Point3F*)col1, (Point3F*)col2); + mCross(*(Point3F*)col2, *(Point3F*)col0, (Point3F*)col1); + + // assure their length is 1.0f + m_point3F_normalize( col0 ); + m_point3F_normalize( col1 ); + m_point3F_normalize( col2 ); + + // store the normalized columns + m[0] = col0[0]; + m[4] = col0[1]; + m[8] = col0[2]; + + m[1] = col1[0]; + m[5] = col1[1]; + m[9] = col1[2]; + + m[2] = col2[0]; + m[6] = col2[1]; + m[10]= col2[2]; +} + + +//-------------------------------------- +static F32 m_matF_determinant_C(const F32 *m) +{ + return m[0] * (m[5] * m[10] - m[6] * m[9]) + + m[4] * (m[2] * m[9] - m[1] * m[10]) + + m[8] * (m[1] * m[6] - m[2] * m[5]) ; +} + + +//-------------------------------------- +// Removed static in order to write benchmarking code (that compares against +// specialized SSE/AMD versions) elsewhere. +void default_matF_x_matF_C(const F32 *a, const F32 *b, F32 *mresult) +{ + mresult[0] = a[0]*b[0] + a[1]*b[4] + a[2]*b[8] + a[3]*b[12]; + mresult[1] = a[0]*b[1] + a[1]*b[5] + a[2]*b[9] + a[3]*b[13]; + mresult[2] = a[0]*b[2] + a[1]*b[6] + a[2]*b[10] + a[3]*b[14]; + mresult[3] = a[0]*b[3] + a[1]*b[7] + a[2]*b[11] + a[3]*b[15]; + + mresult[4] = a[4]*b[0] + a[5]*b[4] + a[6]*b[8] + a[7]*b[12]; + mresult[5] = a[4]*b[1] + a[5]*b[5] + a[6]*b[9] + a[7]*b[13]; + mresult[6] = a[4]*b[2] + a[5]*b[6] + a[6]*b[10] + a[7]*b[14]; + mresult[7] = a[4]*b[3] + a[5]*b[7] + a[6]*b[11] + a[7]*b[15]; + + mresult[8] = a[8]*b[0] + a[9]*b[4] + a[10]*b[8] + a[11]*b[12]; + mresult[9] = a[8]*b[1] + a[9]*b[5] + a[10]*b[9] + a[11]*b[13]; + mresult[10]= a[8]*b[2] + a[9]*b[6] + a[10]*b[10]+ a[11]*b[14]; + mresult[11]= a[8]*b[3] + a[9]*b[7] + a[10]*b[11]+ a[11]*b[15]; + + mresult[12]= a[12]*b[0]+ a[13]*b[4]+ a[14]*b[8] + a[15]*b[12]; + mresult[13]= a[12]*b[1]+ a[13]*b[5]+ a[14]*b[9] + a[15]*b[13]; + mresult[14]= a[12]*b[2]+ a[13]*b[6]+ a[14]*b[10]+ a[15]*b[14]; + mresult[15]= a[12]*b[3]+ a[13]*b[7]+ a[14]*b[11]+ a[15]*b[15]; +} + + +// //-------------------------------------- +// static void m_matF_x_point3F_C(const F32 *m, const F32 *p, F32 *presult) +// { +// AssertFatal(p != presult, "Error, aliasing matrix mul pointers not allowed here!"); +// presult[0] = m[0]*p[0] + m[1]*p[1] + m[2]*p[2] + m[3]; +// presult[1] = m[4]*p[0] + m[5]*p[1] + m[6]*p[2] + m[7]; +// presult[2] = m[8]*p[0] + m[9]*p[1] + m[10]*p[2] + m[11]; +// } + + +// //-------------------------------------- +// static void m_matF_x_vectorF_C(const F32 *m, const F32 *v, F32 *vresult) +// { +// AssertFatal(v != vresult, "Error, aliasing matrix mul pointers not allowed here!"); +// vresult[0] = m[0]*v[0] + m[1]*v[1] + m[2]*v[2]; +// vresult[1] = m[4]*v[0] + m[5]*v[1] + m[6]*v[2]; +// vresult[2] = m[8]*v[0] + m[9]*v[1] + m[10]*v[2]; +// } + + +//-------------------------------------- +static void m_matF_x_point4F_C(const F32 *m, const F32 *p, F32 *presult) +{ + AssertFatal(p != presult, "Error, aliasing matrix mul pointers not allowed here!"); + presult[0] = m[0]*p[0] + m[1]*p[1] + m[2]*p[2] + m[3]*p[3]; + presult[1] = m[4]*p[0] + m[5]*p[1] + m[6]*p[2] + m[7]*p[3]; + presult[2] = m[8]*p[0] + m[9]*p[1] + m[10]*p[2] + m[11]*p[3]; + presult[3] = m[12]*p[0]+ m[13]*p[1]+ m[14]*p[2] + m[15]*p[3]; +} + +//-------------------------------------- +static void m_matF_x_scale_x_planeF_C(const F32* m, const F32* s, const F32* p, F32* presult) +{ + // We take in a matrix, a scale factor, and a plane equation. We want to output + // the resultant normal + // We have T = m*s + // To multiply the normal, we want Inv(Tr(m*s)) + // Inv(Tr(ms)) = Inv(Tr(s) * Tr(m)) + // = Inv(Tr(m)) * Inv(Tr(s)) + // + // Inv(Tr(s)) = Inv(s) = [ 1/x 0 0 0] + // [ 0 1/y 0 0] + // [ 0 0 1/z 0] + // [ 0 0 0 1] + // + // Since m is an affine matrix, + // Tr(m) = [ [ ] 0 ] + // [ [ R ] 0 ] + // [ [ ] 0 ] + // [ [ x y z ] 1 ] + // + // Inv(Tr(m)) = [ [ -1 ] 0 ] + // [ [ R ] 0 ] + // [ [ ] 0 ] + // [ [ A B C ] 1 ] + // Where: + // + // P = (x, y, z) + // A = -(Row(0, r) * P); + // B = -(Row(1, r) * P); + // C = -(Row(2, r) * P); + + MatrixF invScale(true); + F32* pScaleElems = invScale; + pScaleElems[MatrixF::idx(0, 0)] = 1.0f / s[0]; + pScaleElems[MatrixF::idx(1, 1)] = 1.0f / s[1]; + pScaleElems[MatrixF::idx(2, 2)] = 1.0f / s[2]; + + const Point3F shear( m[MatrixF::idx(3, 0)], m[MatrixF::idx(3, 1)], m[MatrixF::idx(3, 2)] ); + + const Point3F row0(m[MatrixF::idx(0, 0)], m[MatrixF::idx(0, 1)], m[MatrixF::idx(0, 2)]); + const Point3F row1(m[MatrixF::idx(1, 0)], m[MatrixF::idx(1, 1)], m[MatrixF::idx(1, 2)]); + const Point3F row2(m[MatrixF::idx(2, 0)], m[MatrixF::idx(2, 1)], m[MatrixF::idx(2, 2)]); + + const F32 A = -mDot(row0, shear); + const F32 B = -mDot(row1, shear); + const F32 C = -mDot(row2, shear); + + MatrixF invTrMatrix(true); + F32* destMat = invTrMatrix; + destMat[MatrixF::idx(0, 0)] = m[MatrixF::idx(0, 0)]; + destMat[MatrixF::idx(1, 0)] = m[MatrixF::idx(1, 0)]; + destMat[MatrixF::idx(2, 0)] = m[MatrixF::idx(2, 0)]; + destMat[MatrixF::idx(0, 1)] = m[MatrixF::idx(0, 1)]; + destMat[MatrixF::idx(1, 1)] = m[MatrixF::idx(1, 1)]; + destMat[MatrixF::idx(2, 1)] = m[MatrixF::idx(2, 1)]; + destMat[MatrixF::idx(0, 2)] = m[MatrixF::idx(0, 2)]; + destMat[MatrixF::idx(1, 2)] = m[MatrixF::idx(1, 2)]; + destMat[MatrixF::idx(2, 2)] = m[MatrixF::idx(2, 2)]; + destMat[MatrixF::idx(0, 3)] = A; + destMat[MatrixF::idx(1, 3)] = B; + destMat[MatrixF::idx(2, 3)] = C; + invTrMatrix.mul(invScale); + + Point3F norm(p[0], p[1], p[2]); + Point3F point = norm * -p[3]; + invTrMatrix.mulP(norm); + norm.normalize(); + + MatrixF temp; + dMemcpy(temp, m, sizeof(F32) * 16); + point.x *= s[0]; + point.y *= s[1]; + point.z *= s[2]; + temp.mulP(point); + + PlaneF resultPlane(point, norm); + presult[0] = resultPlane.x; + presult[1] = resultPlane.y; + presult[2] = resultPlane.z; + presult[3] = resultPlane.d; +} + +static void m_matF_x_box3F_C(const F32 *m, F32* min, F32* max) +{ + // Algorithm for axis aligned bounding box adapted from + // Graphic Gems I, pp 548-550 + // + F32 originalMin[3]; + F32 originalMax[3]; + originalMin[0] = min[0]; + originalMin[1] = min[1]; + originalMin[2] = min[2]; + originalMax[0] = max[0]; + originalMax[1] = max[1]; + originalMax[2] = max[2]; + + min[0] = max[0] = m[3]; + min[1] = max[1] = m[7]; + min[2] = max[2] = m[11]; + + const F32 * row = &m[0]; + for (U32 i = 0; i < 3; i++) + { + #define Do_One_Row(j) { \ + F32 a = (row[j] * originalMin[j]); \ + F32 b = (row[j] * originalMax[j]); \ + if (a < b) { *min += a; *max += b; } \ + else { *min += b; *max += a; } } + + // Simpler addressing (avoiding things like [ecx+edi*4]) might be worthwhile (LH): + Do_One_Row(0); + Do_One_Row(1); + Do_One_Row(2); + row += 4; + min++; + max++; + } +} + + +void m_point3F_bulk_dot_C(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + F32* output) +{ + for (U32 i = 0; i < numPoints; i++) + { + F32* pPoint = (F32*)(((U8*)dotPoints) + (pointStride * i)); + output[i] = ((refVector[0] * pPoint[0]) + + (refVector[1] * pPoint[1]) + + (refVector[2] * pPoint[2])); + } +} + +void m_point3F_bulk_dot_indexed_C(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + const U32* pointIndices, + F32* output) +{ + for (U32 i = 0; i < numPoints; i++) + { + F32* pPoint = (F32*)(((U8*)dotPoints) + (pointStride * pointIndices[i])); + output[i] = ((refVector[0] * pPoint[0]) + + (refVector[1] * pPoint[1]) + + (refVector[2] * pPoint[2])); + } +} + +bool m_planeF_intersect_box3F_C(const F32 *planes, const F32 *box) +{ + // This is based on the paper "A Faster Overlap Test for a Plane and a Bounding Box" + // by Kenny Hoff. See http://www.cs.unc.edu/~hoff/research/vfculler/boxplane.html + + const Box3F &bounds = *reinterpret_cast(box); + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + const PlaneF *plane = reinterpret_cast(planes); + const PlaneF *endPlane = plane + Frustum::PlaneCount; + + Point3F maxPoint; + F32 maxDot; + + while( plane < endPlane ) + { + const PlaneF &curPlane = *plane; + // This is pretty much as optimal as you can + // get for a plane vs AABB test... + // + // 4 comparisions + // 3 multiplies + // 2 adds + // 1 negation + // + // It will early out as soon as it detects the + // bounds is outside one of the planes. + maxPoint.x = ( curPlane.x > 0.0f ) ? bounds.maxExtents.x : bounds.minExtents.x; + maxPoint.y = ( curPlane.y > 0.0f ) ? bounds.maxExtents.y : bounds.minExtents.y; + maxPoint.z = ( curPlane.z > 0.0f ) ? bounds.maxExtents.z : bounds.minExtents.z; + + maxDot = mDot( maxPoint, curPlane ); + + if ( maxDot <= -curPlane.d ) + return false; + plane++; + } + + return true; +} + +//------------------------------------------------------------------------------ +// Math function pointer declarations + +S32 (*m_mulDivS32)(S32 a, S32 b, S32 c) = m_mulDivS32_C; +U32 (*m_mulDivU32)(S32 a, S32 b, U32 c) = m_mulDivU32_C; + +F32 (*m_catmullrom)(F32 t, F32 p0, F32 p1, F32 p2, F32 p3) = m_catmullrom_C; + +void (*m_point2F_normalize)(F32 *p) = m_point2F_normalize_C; +void (*m_point2F_normalize_f)(F32 *p, F32 val) = m_point2F_normalize_f_C; +void (*m_point2D_normalize)(F64 *p) = m_point2D_normalize_C; +void (*m_point2D_normalize_f)(F64 *p, F64 val) = m_point2D_normalize_f_C; +void (*m_point3D_normalize_f)(F64 *p, F64 val) = m_point3D_normalize_f_C; +void (*m_point3F_normalize)(F32 *p) = m_point3F_normalize_C; +void (*m_point3F_normalize_f)(F32 *p, F32 len) = m_point3F_normalize_f_C; +void (*m_point3F_interpolate)(const F32 *from, const F32 *to, F32 factor, F32 *result) = m_point3F_interpolate_C; + +void (*m_point3D_normalize)(F64 *p) = m_point3D_normalize_C; +void (*m_point3D_interpolate)(const F64 *from, const F64 *to, F64 factor, F64 *result) = m_point3D_interpolate_C; + +void (*m_point3F_bulk_dot)(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + F32* output) = m_point3F_bulk_dot_C; +void (*m_point3F_bulk_dot_indexed)(const F32* refVector, + const F32* dotPoints, + const U32 numPoints, + const U32 pointStride, + const U32* pointIndices, + F32* output) = m_point3F_bulk_dot_indexed_C; + +void (*m_quatF_set_matF)( F32 x, F32 y, F32 z, F32 w, F32* m ) = m_quatF_set_matF_C; + +void (*m_matF_set_euler)(const F32 *e, F32 *result) = m_matF_set_euler_C; +void (*m_matF_set_euler_point)(const F32 *e, const F32 *p, F32 *result) = m_matF_set_euler_point_C; +void (*m_matF_identity)(F32 *m) = m_matF_identity_C; +void (*m_matF_inverse)(F32 *m) = m_matF_inverse_C; +void (*m_matF_affineInverse)(F32 *m) = m_matF_affineInverse_C; +void (*m_matF_invert_to)(const F32 *m, F32 *d) = m_matF_invert_to_C; +void (*m_matF_transpose)(F32 *m) = m_matF_transpose_C; +void (*m_matF_scale)(F32 *m,const F32* p) = m_matF_scale_C; +void (*m_matF_normalize)(F32 *m) = m_matF_normalize_C; +F32 (*m_matF_determinant)(const F32 *m) = m_matF_determinant_C; +void (*m_matF_x_matF)(const F32 *a, const F32 *b, F32 *mresult) = default_matF_x_matF_C; +void (*m_matF_x_matF_aligned)(const F32 *a, const F32 *b, F32 *mresult) = default_matF_x_matF_C; +// void (*m_matF_x_point3F)(const F32 *m, const F32 *p, F32 *presult) = m_matF_x_point3F_C; +// void (*m_matF_x_vectorF)(const F32 *m, const F32 *v, F32 *vresult) = m_matF_x_vectorF_C; +void (*m_matF_x_point4F)(const F32 *m, const F32 *p, F32 *presult) = m_matF_x_point4F_C; +void (*m_matF_x_scale_x_planeF)(const F32 *m, const F32* s, const F32 *p, F32 *presult) = m_matF_x_scale_x_planeF_C; +void (*m_matF_x_box3F)(const F32 *m, F32 *min, F32 *max) = m_matF_x_box3F_C; +bool (*m_planeF_intersect_box3F)(const F32 *planes, const F32 *bounds) = m_planeF_intersect_box3F_C; + +//------------------------------------------------------------------------------ +void mInstallLibrary_C() +{ + m_mulDivS32 = m_mulDivS32_C; + m_mulDivU32 = m_mulDivU32_C; + + m_catmullrom = m_catmullrom_C; + + m_point2F_normalize = m_point2F_normalize_C; + m_point2F_normalize_f = m_point2F_normalize_f_C; + m_point2D_normalize = m_point2D_normalize_C; + m_point2D_normalize_f = m_point2D_normalize_f_C; + m_point3F_normalize = m_point3F_normalize_C; + m_point3F_normalize_f = m_point3F_normalize_f_C; + m_point3F_interpolate = m_point3F_interpolate_C; + + m_point3D_normalize = m_point3D_normalize_C; + m_point3D_interpolate = m_point3D_interpolate_C; + + m_point3F_bulk_dot = m_point3F_bulk_dot_C; + m_point3F_bulk_dot_indexed = m_point3F_bulk_dot_indexed_C; + + m_quatF_set_matF = m_quatF_set_matF_C; + + m_matF_set_euler = m_matF_set_euler_C; + m_matF_set_euler_point = m_matF_set_euler_point_C; + m_matF_identity = m_matF_identity_C; + m_matF_inverse = m_matF_inverse_C; + m_matF_affineInverse = m_matF_affineInverse_C; + m_matF_invert_to = m_matF_invert_to_C; + m_matF_transpose = m_matF_transpose_C; + m_matF_scale = m_matF_scale_C; + m_matF_normalize = m_matF_normalize_C; + m_matF_determinant = m_matF_determinant_C; + m_matF_x_matF = default_matF_x_matF_C; + m_matF_x_matF_aligned = default_matF_x_matF_C; +// m_matF_x_point3F = m_matF_x_point3F_C; +// m_matF_x_vectorF = m_matF_x_vectorF_C; + m_matF_x_point4F = m_matF_x_point4F_C; + m_matF_x_scale_x_planeF = m_matF_x_scale_x_planeF_C; + m_matF_x_box3F = m_matF_x_box3F_C; + m_planeF_intersect_box3F = m_planeF_intersect_box3F_C; +} + diff --git a/math/mMatrix.cpp b/math/mMatrix.cpp new file mode 100644 index 0000000..e30d08c --- /dev/null +++ b/math/mMatrix.cpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "core/frameAllocator.h" + +#include "math/mMatrix.h" +#include "console/console.h" + + +const MatrixF MatrixF::Identity( true ); + +// idx(i,j) is index to element in column i, row j + +void MatrixF::transposeTo(F32 *matrix) const +{ + matrix[idx(0,0)] = m[idx(0,0)]; + matrix[idx(0,1)] = m[idx(1,0)]; + matrix[idx(0,2)] = m[idx(2,0)]; + matrix[idx(0,3)] = m[idx(3,0)]; + matrix[idx(1,0)] = m[idx(0,1)]; + matrix[idx(1,1)] = m[idx(1,1)]; + matrix[idx(1,2)] = m[idx(2,1)]; + matrix[idx(1,3)] = m[idx(3,1)]; + matrix[idx(2,0)] = m[idx(0,2)]; + matrix[idx(2,1)] = m[idx(1,2)]; + matrix[idx(2,2)] = m[idx(2,2)]; + matrix[idx(2,3)] = m[idx(3,2)]; + matrix[idx(3,0)] = m[idx(0,3)]; + matrix[idx(3,1)] = m[idx(1,3)]; + matrix[idx(3,2)] = m[idx(2,3)]; + matrix[idx(3,3)] = m[idx(3,3)]; +} + +bool MatrixF::isAffine() const +{ + // An affine transform is defined by the following structure + // + // [ X X X P ] + // [ X X X P ] + // [ X X X P ] + // [ 0 0 0 1 ] + // + // Where X is an orthonormal 3x3 submatrix and P is an arbitrary translation + // We'll check in the following order: + // 1: [3][3] must be 1 + // 2: Shear portion must be zero + // 3: Dot products of rows and columns must be zero + // 4: Length of rows and columns must be 1 + // + if (m[idx(3,3)] != 1.0f) + return false; + + if (m[idx(0,3)] != 0.0f || + m[idx(1,3)] != 0.0f || + m[idx(2,3)] != 0.0f) + return false; + + Point3F one, two, three; + getColumn(0, &one); + getColumn(1, &two); + getColumn(2, &three); + if (mDot(one, two) > 0.0001f || + mDot(one, three) > 0.0001f || + mDot(two, three) > 0.0001f) + return false; + + if (mFabs(1.0f - one.lenSquared()) > 0.0001f || + mFabs(1.0f - two.lenSquared()) > 0.0001f || + mFabs(1.0f - three.lenSquared()) > 0.0001f) + return false; + + getRow(0, &one); + getRow(1, &two); + getRow(2, &three); + if (mDot(one, two) > 0.0001f || + mDot(one, three) > 0.0001f || + mDot(two, three) > 0.0001f) + return false; + + if (mFabs(1.0f - one.lenSquared()) > 0.0001f || + mFabs(1.0f - two.lenSquared()) > 0.0001f || + mFabs(1.0f - three.lenSquared()) > 0.0001f) + return false; + + // We're ok. + return true; +} + +// Perform inverse on full 4x4 matrix. Used in special cases only, so not at all optimized. +bool MatrixF::fullInverse() +{ + Point4F a,b,c,d; + getRow(0,&a); + getRow(1,&b); + getRow(2,&c); + getRow(3,&d); + + // det = a0*b1*c2*d3 - a0*b1*c3*d2 - a0*c1*b2*d3 + a0*c1*b3*d2 + a0*d1*b2*c3 - a0*d1*b3*c2 - + // b0*a1*c2*d3 + b0*a1*c3*d2 + b0*c1*a2*d3 - b0*c1*a3*d2 - b0*d1*a2*c3 + b0*d1*a3*c2 + + // c0*a1*b2*d3 - c0*a1*b3*d2 - c0*b1*a2*d3 + c0*b1*a3*d2 + c0*d1*a2*b3 - c0*d1*a3*b2 - + // d0*a1*b2*c3 + d0*a1*b3*c2 + d0*b1*a2*c3 - d0*b1*a3*c2 - d0*c1*a2*b3 + d0*c1*a3*b2 + F32 det = a.x*b.y*c.z*d.w - a.x*b.y*c.w*d.z - a.x*c.y*b.z*d.w + a.x*c.y*b.w*d.z + a.x*d.y*b.z*c.w - a.x*d.y*b.w*c.z + - b.x*a.y*c.z*d.w + b.x*a.y*c.w*d.z + b.x*c.y*a.z*d.w - b.x*c.y*a.w*d.z - b.x*d.y*a.z*c.w + b.x*d.y*a.w*c.z + + c.x*a.y*b.z*d.w - c.x*a.y*b.w*d.z - c.x*b.y*a.z*d.w + c.x*b.y*a.w*d.z + c.x*d.y*a.z*b.w - c.x*d.y*a.w*b.z + - d.x*a.y*b.z*c.w + d.x*a.y*b.w*c.z + d.x*b.y*a.z*c.w - d.x*b.y*a.w*c.z - d.x*c.y*a.z*b.w + d.x*c.y*a.w*b.z; + + if (mFabs(det)<0.00001f) + return false; + + Point4F aa,bb,cc,dd; + aa.x = b.y*c.z*d.w - b.y*c.w*d.z - c.y*b.z*d.w + c.y*b.w*d.z + d.y*b.z*c.w - d.y*b.w*c.z; + aa.y = -a.y*c.z*d.w + a.y*c.w*d.z + c.y*a.z*d.w - c.y*a.w*d.z - d.y*a.z*c.w + d.y*a.w*c.z; + aa.z = a.y*b.z*d.w - a.y*b.w*d.z - b.y*a.z*d.w + b.y*a.w*d.z + d.y*a.z*b.w - d.y*a.w*b.z; + aa.w = -a.y*b.z*c.w + a.y*b.w*c.z + b.y*a.z*c.w - b.y*a.w*c.z - c.y*a.z*b.w + c.y*a.w*b.z; + + bb.x = -b.x*c.z*d.w + b.x*c.w*d.z + c.x*b.z*d.w - c.x*b.w*d.z - d.x*b.z*c.w + d.x*b.w*c.z; + bb.y = a.x*c.z*d.w - a.x*c.w*d.z - c.x*a.z*d.w + c.x*a.w*d.z + d.x*a.z*c.w - d.x*a.w*c.z; + bb.z = -a.x*b.z*d.w + a.x*b.w*d.z + b.x*a.z*d.w - b.x*a.w*d.z - d.x*a.z*b.w + d.x*a.w*b.z; + bb.w = a.x*b.z*c.w - a.x*b.w*c.z - b.x*a.z*c.w + b.x*a.w*c.z + c.x*a.z*b.w - c.x*a.w*b.z; + + cc.x = b.x*c.y*d.w - b.x*c.w*d.y - c.x*b.y*d.w + c.x*b.w*d.y + d.x*b.y*c.w - d.x*b.w*c.y; + cc.y = -a.x*c.y*d.w + a.x*c.w*d.y + c.x*a.y*d.w - c.x*a.w*d.y - d.x*a.y*c.w + d.x*a.w*c.y; + cc.z = a.x*b.y*d.w - a.x*b.w*d.y - b.x*a.y*d.w + b.x*a.w*d.y + d.x*a.y*b.w - d.x*a.w*b.y; + cc.w = -a.x*b.y*c.w + a.x*b.w*c.y + b.x*a.y*c.w - b.x*a.w*c.y - c.x*a.y*b.w + c.x*a.w*b.y; + + dd.x = -b.x*c.y*d.z + b.x*c.z*d.y + c.x*b.y*d.z - c.x*b.z*d.y - d.x*b.y*c.z + d.x*b.z*c.y; + dd.y = a.x*c.y*d.z - a.x*c.z*d.y - c.x*a.y*d.z + c.x*a.z*d.y + d.x*a.y*c.z - d.x*a.z*c.y; + dd.z = -a.x*b.y*d.z + a.x*b.z*d.y + b.x*a.y*d.z - b.x*a.z*d.y - d.x*a.y*b.z + d.x*a.z*b.y; + dd.w = a.x*b.y*c.z - a.x*b.z*c.y - b.x*a.y*c.z + b.x*a.z*c.y + c.x*a.y*b.z - c.x*a.z*b.y; + + setRow(0,aa); + setRow(1,bb); + setRow(2,cc); + setRow(3,dd); + + mul(1.0f/det); + + return true; +} + +EulerF MatrixF::toEuler() const +{ + const F32 * mat = m; + + EulerF r; + r.x = mAsin(mat[MatrixF::idx(2,1)]); + + if(mCos(r.x) != 0.f) + { + r.y = mAtan2(-mat[MatrixF::idx(2,0)], mat[MatrixF::idx(2,2)]); + r.z = mAtan2(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]); + } + else + { + r.y = 0.f; + r.z = mAtan2(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]); + } + + return r; +} + +void MatrixF::dumpMatrix(const char *caption /* =NULL */) const +{ + U32 size = dStrlen(caption); + FrameTemp spacer(size+1); + char *spacerRef = spacer; + + dMemset(spacerRef, ' ', size); + spacerRef[size] = 0; + + Con::printf("%s = | %-8.4f %-8.4f %-8.4f %-8.4f |", caption, m[idx(0,0)], m[idx(0, 1)], m[idx(0, 2)], m[idx(0, 3)]); + Con::printf("%s | %-8.4f %-8.4f %-8.4f %-8.4f |", spacerRef, m[idx(1,0)], m[idx(1, 1)], m[idx(1, 2)], m[idx(1, 3)]); + Con::printf("%s | %-8.4f %-8.4f %-8.4f %-8.4f |", spacerRef, m[idx(2,0)], m[idx(2, 1)], m[idx(2, 2)], m[idx(2, 3)]); + Con::printf("%s | %-8.4f %-8.4f %-8.4f %-8.4f |", spacerRef, m[idx(3,0)], m[idx(3, 1)], m[idx(3, 2)], m[idx(3, 3)]); +} \ No newline at end of file diff --git a/math/mMatrix.h b/math/mMatrix.h new file mode 100644 index 0000000..6a62251 --- /dev/null +++ b/math/mMatrix.h @@ -0,0 +1,546 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MMATRIX_H_ +#define _MMATRIX_H_ + +#ifndef _MPLANE_H_ +#include "math/mPlane.h" +#endif + +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif + +#ifndef _MPOINT4_H_ +#include "math/mPoint4.h" +#endif + +/// 4x4 Matrix Class +/// +/// This runs at F32 precision. +#if defined(TORQUE_OS_XENON) +__declspec(align(16)) +#endif +class MatrixF +{ +private: + F32 m[16]; ///< Note: Torque uses column major matrices + +public: + /// Create an uninitialized matrix. + /// + /// @param identity If true, initialize to the identity matrix. + explicit MatrixF(bool identity=false); + + /// Create a matrix to rotate about origin by e. + /// @see set + explicit MatrixF( const EulerF &e); + + /// Create a matrix to rotate about p by e. + /// @see set + MatrixF( const EulerF &e, const Point3F& p); + + /// Get the index in m to element in column i, row j + /// + /// This is necessary as we have m as a one dimensional array. + /// + /// @param i Column desired. + /// @param j Row desired. + static U32 idx(U32 i, U32 j) { return (i + j*4); } + + /// Initialize matrix to rotate about origin by e. + MatrixF& set( const EulerF &e); + + /// Initialize matrix to rotate about p by e. + MatrixF& set( const EulerF &e, const Point3F& p); + + /// Initialize matrix with a cross product of p. + MatrixF& setCrossProduct( const Point3F &p); + + /// Initialize matrix with a tensor product of p. + MatrixF& setTensorProduct( const Point3F &p, const Point3F& q); + + operator F32*() { return (m); } ///< Allow people to get at m. + operator const F32*() const { return (F32*)(m); } ///< Allow people to get at m. + + bool isAffine() const; ///< Check to see if this is an affine matrix. + bool isIdentity() const; ///< Checks for identity matrix. + + /// Make this an identity matrix. + MatrixF& identity(); + + /// Invert m. + MatrixF& inverse(); + + /// Take inverse of matrix assuming it is affine (rotation, + /// scale, sheer, translation only). + MatrixF& affineInverse(); + + /// Swap rows and columns. + MatrixF& transpose(); + + /// M * Matrix(p) -> M + MatrixF& scale( const Point3F &s ); + MatrixF& scale( F32 s ) { return scale( Point3F( s, s, s ) ); } + + /// Return scale assuming scale was applied via mat.scale(s). + Point3F getScale() const; + + EulerF toEuler() const; + + /// Compute the inverse of the matrix. + /// + /// Computes inverse of full 4x4 matrix. Returns false and performs no inverse if + /// the determinant is 0. + /// + /// Note: In most cases you want to use the normal inverse function. This method should + /// be used if the matrix has something other than (0,0,0,1) in the bottom row. + bool fullInverse(); + + /// Swaps rows and columns into matrix. + void transposeTo(F32 *matrix) const; + + /// Normalize the matrix. + void normalize(); + + /// Copy the requested column into a Point4F. + void getColumn(S32 col, Point4F *cptr) const; + Point4F getColumn4F(S32 col) const { Point4F ret; getColumn(col,&ret); return ret; } + + /// Copy the requested column into a Point3F. + /// + /// This drops the bottom-most row. + void getColumn(S32 col, Point3F *cptr) const; + Point3F getColumn3F(S32 col) const { Point3F ret; getColumn(col,&ret); return ret; } + + /// Set the specified column from a Point4F. + void setColumn(S32 col, const Point4F& cptr); + + /// Set the specified column from a Point3F. + /// + /// The bottom-most row is not set. + void setColumn(S32 col, const Point3F& cptr); + + /// Copy the specified row into a Point4F. + void getRow(S32 row, Point4F *cptr) const; + Point4F getRow4F(S32 row) const { Point4F ret; getRow(row,&ret); return ret; } + + /// Copy the specified row into a Point3F. + /// + /// Right-most item is dropped. + void getRow(S32 row, Point3F *cptr) const; + Point3F getRow3F(S32 row) const { Point3F ret; getRow(row,&ret); return ret; } + + /// Set the specified row from a Point4F. + void setRow(S32 row, const Point4F& cptr); + + /// Set the specified row from a Point3F. + /// + /// The right-most item is not set. + void setRow(S32 row, const Point3F& cptr); + + /// Get the position of the matrix. + /// + /// This is the 4th column of the matrix. + Point3F getPosition() const; + + /// Set the position of the matrix. + /// + /// This is the 4th column of the matrix. + void setPosition( const Point3F &pos ) { setColumn( 3, pos ); } + + /// Get the y axis of the matrix. + /// + /// This is the 2nd column of the matrix and is + /// normally considered the forward vector. + VectorF getForwardVector() const; + + /// Get the x axis of the matrix. + /// + /// This is the 1st column of the matrix and is + /// normally considered the right vector. + VectorF getRightVector() const; + + /// Get the z axis of the matrix. + /// + /// This is the 3rd column of the matrix and is + /// normally considered the up vector. + VectorF getUpVector() const; + + MatrixF& mul(const MatrixF &a); ///< M * a -> M + MatrixF& mul(const MatrixF &a, const MatrixF &b); ///< a * b -> M + + // Scalar multiplies + MatrixF& mul(const F32 a); ///< M * a -> M + MatrixF& mul(const MatrixF &a, const F32 b); ///< a * b -> M + + + void mul( Point4F& p ) const; ///< M * p -> p (full [4x4] * [1x4]) + void mulP( Point3F& p ) const; ///< M * p -> p (assume w = 1.0f) + void mulP( const Point3F &p, Point3F *d) const; ///< M * p -> d (assume w = 1.0f) + void mulV( VectorF& p ) const; ///< M * v -> v (assume w = 0.0f) + void mulV( const VectorF &p, Point3F *d) const; ///< M * v -> d (assume w = 0.0f) + + void mul(Box3F& b) const; ///< Axial box -> Axial Box + + /// Convenience function to allow people to treat this like an array. + F32& operator ()(S32 row, S32 col) { return m[idx(col,row)]; } + F32 operator ()(S32 row, S32 col) const { return m[idx(col,row)]; } + + void dumpMatrix(const char *caption=NULL) const; + + // Math operator overloads + //------------------------------------ + friend MatrixF operator * ( const MatrixF &m1, const MatrixF &m2 ); + MatrixF& operator *= ( const MatrixF &m ); + + // Static identity matrix + const static MatrixF Identity; + +} +#if defined(TORQUE_COMPILER_GCC) +__attribute__ ((aligned (16))) +#endif +; + +//-------------------------------------- +// Inline Functions + +inline MatrixF::MatrixF(bool _identity) +{ + if (_identity) + identity(); +} + +inline MatrixF::MatrixF( const EulerF &e ) +{ + set(e); +} + +inline MatrixF::MatrixF( const EulerF &e, const Point3F& p ) +{ + set(e,p); +} + +inline MatrixF& MatrixF::set( const EulerF &e) +{ + m_matF_set_euler( e, *this ); + return (*this); +} + + +inline MatrixF& MatrixF::set( const EulerF &e, const Point3F& p) +{ + m_matF_set_euler_point( e, p, *this ); + return (*this); +} + +inline MatrixF& MatrixF::setCrossProduct( const Point3F &p) +{ + m[1] = -(m[4] = p.z); + m[8] = -(m[2] = p.y); + m[6] = -(m[9] = p.x); + m[0] = m[3] = m[5] = m[7] = m[10] = m[11] = + m[12] = m[13] = m[14] = 0.0f; + m[15] = 1; + return (*this); +} + +inline MatrixF& MatrixF::setTensorProduct( const Point3F &p, const Point3F &q) +{ + m[0] = p.x * q.x; + m[1] = p.x * q.y; + m[2] = p.x * q.z; + m[4] = p.y * q.x; + m[5] = p.y * q.y; + m[6] = p.y * q.z; + m[8] = p.z * q.x; + m[9] = p.z * q.y; + m[10] = p.z * q.z; + m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0.0f; + m[15] = 1.0f; + return (*this); +} + +inline bool MatrixF::isIdentity() const +{ + return + m[0] == 1.0f && + m[1] == 0.0f && + m[2] == 0.0f && + m[3] == 0.0f && + m[4] == 0.0f && + m[5] == 1.0f && + m[6] == 0.0f && + m[7] == 0.0f && + m[8] == 0.0f && + m[9] == 0.0f && + m[10] == 1.0f && + m[11] == 0.0f && + m[12] == 0.0f && + m[13] == 0.0f && + m[14] == 0.0f && + m[15] == 1.0f; +} + +inline MatrixF& MatrixF::identity() +{ + m[0] = 1.0f; + m[1] = 0.0f; + m[2] = 0.0f; + m[3] = 0.0f; + m[4] = 0.0f; + m[5] = 1.0f; + m[6] = 0.0f; + m[7] = 0.0f; + m[8] = 0.0f; + m[9] = 0.0f; + m[10] = 1.0f; + m[11] = 0.0f; + m[12] = 0.0f; + m[13] = 0.0f; + m[14] = 0.0f; + m[15] = 1.0f; + return (*this); +} + + +inline MatrixF& MatrixF::inverse() +{ + m_matF_inverse(m); + return (*this); +} + +inline MatrixF& MatrixF::affineInverse() +{ +// AssertFatal(isAffine() == true, "Error, this matrix is not an affine transform"); + m_matF_affineInverse(m); + return (*this); +} + +inline MatrixF& MatrixF::transpose() +{ + m_matF_transpose(m); + return (*this); +} + +inline MatrixF& MatrixF::scale(const Point3F& p) +{ + m_matF_scale(m,p); + return *this; +} + +inline Point3F MatrixF::getScale() const +{ + Point3F scale; + scale.x = mSqrt(m[0]*m[0] + m[4] * m[4] + m[8] * m[8]); + scale.y = mSqrt(m[1]*m[1] + m[5] * m[5] + m[9] * m[9]); + scale.z = mSqrt(m[2]*m[2] + m[6] * m[6] + m[10] * m[10]); + return scale; +} + +inline void MatrixF::normalize() +{ + m_matF_normalize(m); +} + +inline MatrixF& MatrixF::mul( const MatrixF &a ) +{ // M * a -> M + AssertFatal(&a != this, "MatrixF::mul - a.mul(a) is invalid!"); + + MatrixF tempThis(*this); + m_matF_x_matF(tempThis, a, *this); + return (*this); +} + + +inline MatrixF& MatrixF::mul( const MatrixF &a, const MatrixF &b ) +{ // a * b -> M + AssertFatal((&a != this) && (&b != this), "MatrixF::mul - a.mul(a, b) a.mul(b, a) a.mul(a, a) is invalid!"); + + m_matF_x_matF(a, b, *this); + return (*this); +} + + +inline MatrixF& MatrixF::mul(const F32 a) +{ + for (U32 i = 0; i < 16; i++) + m[i] *= a; + + return *this; +} + + +inline MatrixF& MatrixF::mul(const MatrixF &a, const F32 b) +{ + *this = a; + mul(b); + + return *this; +} + +inline void MatrixF::mul( Point4F& p ) const +{ + Point4F temp; + m_matF_x_point4F(*this, &p.x, &temp.x); + p = temp; +} + +inline void MatrixF::mulP( Point3F& p) const +{ + // M * p -> d + Point3F d; + m_matF_x_point3F(*this, &p.x, &d.x); + p = d; +} + +inline void MatrixF::mulP( const Point3F &p, Point3F *d) const +{ + // M * p -> d + m_matF_x_point3F(*this, &p.x, &d->x); +} + +inline void MatrixF::mulV( VectorF& v) const +{ + // M * v -> v + VectorF temp; + m_matF_x_vectorF(*this, &v.x, &temp.x); + v = temp; +} + +inline void MatrixF::mulV( const VectorF &v, Point3F *d) const +{ + // M * v -> d + m_matF_x_vectorF(*this, &v.x, &d->x); +} + +inline void MatrixF::mul(Box3F& b) const +{ + m_matF_x_box3F(*this, &b.minExtents.x, &b.maxExtents.x); +} + +inline void MatrixF::getColumn(S32 col, Point4F *cptr) const +{ + cptr->x = m[col]; + cptr->y = m[col+4]; + cptr->z = m[col+8]; + cptr->w = m[col+12]; +} + +inline void MatrixF::getColumn(S32 col, Point3F *cptr) const +{ + cptr->x = m[col]; + cptr->y = m[col+4]; + cptr->z = m[col+8]; +} + +inline void MatrixF::setColumn(S32 col, const Point4F &cptr) +{ + m[col] = cptr.x; + m[col+4] = cptr.y; + m[col+8] = cptr.z; + m[col+12]= cptr.w; +} + +inline void MatrixF::setColumn(S32 col, const Point3F &cptr) +{ + m[col] = cptr.x; + m[col+4] = cptr.y; + m[col+8] = cptr.z; +} + + +inline void MatrixF::getRow(S32 col, Point4F *cptr) const +{ + col *= 4; + cptr->x = m[col++]; + cptr->y = m[col++]; + cptr->z = m[col++]; + cptr->w = m[col]; +} + +inline void MatrixF::getRow(S32 col, Point3F *cptr) const +{ + col *= 4; + cptr->x = m[col++]; + cptr->y = m[col++]; + cptr->z = m[col]; +} + +inline void MatrixF::setRow(S32 col, const Point4F &cptr) +{ + col *= 4; + m[col++] = cptr.x; + m[col++] = cptr.y; + m[col++] = cptr.z; + m[col] = cptr.w; +} + +inline void MatrixF::setRow(S32 col, const Point3F &cptr) +{ + col *= 4; + m[col++] = cptr.x; + m[col++] = cptr.y; + m[col] = cptr.z; +} + +inline Point3F MatrixF::getPosition() const +{ + Point3F pos; + getColumn( 3, &pos ); + return pos; +} + +inline VectorF MatrixF::getForwardVector() const +{ + VectorF vec; + getColumn( 1, &vec ); + return vec; +} + +inline VectorF MatrixF::getRightVector() const +{ + VectorF vec; + getColumn( 0, &vec ); + return vec; +} + +inline VectorF MatrixF::getUpVector() const +{ + VectorF vec; + getColumn( 2, &vec ); + return vec; +} + +//------------------------------------ +// Math operator overloads +//------------------------------------ +inline MatrixF operator * ( const MatrixF &m1, const MatrixF &m2 ) +{ + // temp = m1 * m2 + MatrixF temp; + m_matF_x_matF(m1, m2, temp); + return temp; +} + +inline MatrixF& MatrixF::operator *= ( const MatrixF &m ) +{ + MatrixF tempThis(*this); + m_matF_x_matF(tempThis, m, *this); + return (*this); +} + +//------------------------------------ +// Non-member methods +//------------------------------------ + +inline void mTransformPlane(const MatrixF& mat, const Point3F& scale, const PlaneF& plane, PlaneF * result) +{ + m_matF_x_scale_x_planeF(mat, &scale.x, &plane.x, &result->x); +} + +#endif //_MMATRIX_H_ diff --git a/math/mPlane.cpp b/math/mPlane.cpp new file mode 100644 index 0000000..ec3075d --- /dev/null +++ b/math/mPlane.cpp @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mPlane.h" + + +bool mIntersect( const PlaneF &p1, const PlaneF &p2, + Point3F *outLinePt, VectorF *outLineDir ) +{ + // Compute direction of intersection line. + *outLineDir = mCross( p1, p2 ); + + // If d is zero, the planes are parallel (and separated) + // or coincident, so they're not considered intersecting + F32 denom = mDot( *outLineDir, *outLineDir ); + if ( denom < 0.00001f ) + return false; + + // Compute point on intersection line + *outLinePt = mCross( p1.d * p2 - p2.d * p1, + *outLineDir ) / denom; + + return true; +} diff --git a/math/mPlane.h b/math/mPlane.h new file mode 100644 index 0000000..a8e8017 --- /dev/null +++ b/math/mPlane.h @@ -0,0 +1,599 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPLANE_H_ +#define _MPLANE_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + + +class PlaneF : public Point3F +{ +public: + F32 d; + + PlaneF(); + PlaneF( const Point3F& p, const Point3F& n ); + PlaneF( F32 _x, F32 _y, F32 _z, F32 _d); + PlaneF( const Point3F& j, const Point3F& k, const Point3F& l ); + + // Comparison operators + bool operator==(const PlaneF&a) const + { + return (a.x == x && a.y == y && a.z == z && a.d == d); + } + + bool operator!=(const PlaneF&a) const + { + return (a.x != x || a.y != y || a.z != z || a.d == d); + } + + // Methods + void set( F32 _x, F32 _y, F32 _z ); + void set( F32 _x, F32 _y, F32 _z, F32 _d ); + void set( const Point3F& p, const Point3F& n); + void set( const Point3F& k, const Point3F& j, const Point3F& l ); + void setPoint(const Point3F &p); // assumes the x,y,z fields are already set + // creates an un-normalized plane + + void setXY(F32 zz); + void setYZ(F32 xx); + void setXZ(F32 yy); + void setXY(const Point3F& P, F32 dir); + void setYZ(const Point3F& P, F32 dir); + void setXZ(const Point3F& P, F32 dir); + void shiftX(F32 xx); + void shiftY(F32 yy); + void shiftZ(F32 zz); + void invert(); + void neg(); + Point3F project(const Point3F &pt) const; // projects the point onto the plane. + + bool isOnPlane( const Point3F& cp ) const; + F32 distToPlane( const Point3F& cp ) const; + + const Point3F& getNormal() const { return *this; } + + enum Side + { + Front = 1, + On = 0, + Back = -1 + }; + + Side whichSide(const Point3F& cp) const; + + F32 intersect( const Point3F &start, const Point3F &end ) const; + + bool intersect( const Point3F &start, const Point3F &dir, Point3F *outHit ) const; + + bool isHorizontal() const; + bool isVertical() const; + + Side whichSideBox(const Point3F& center, + const Point3F& axisx, + const Point3F& axisy, + const Point3F& axisz, + const Point3F& offset) const; +}; +#define PARALLEL_PLANE 1e20f + +#define PlaneSwitchCode(s, e) (s * 3 + e) + + +//--------------------------------------------------------------------------- + +inline PlaneF::PlaneF() +{ +} + +inline PlaneF:: + PlaneF( F32 _x, F32 _y, F32 _z, F32 _d ) +{ + set( _x, _y, _z, _d ); +} + +inline PlaneF::PlaneF( const Point3F& p, const Point3F& n ) +{ + set(p,n); +} + +inline PlaneF::PlaneF( const Point3F& j, const Point3F& k, const Point3F& l ) +{ + set(j,k,l); +} + +inline void PlaneF::setXY( F32 zz ) +{ + x = y = 0.0f; z = 1.0f; d = -zz; +} + +inline void PlaneF::setYZ( F32 xx ) +{ + x = 1.0f; z = y = 0.0f; d = -xx; +} + +inline void PlaneF::setXZ( F32 yy ) +{ + x = z = 0.0f; y = 1.0f; d = -yy; +} + +inline void PlaneF::setXY(const Point3F& point, F32 dir) // Normal = (0, 0, -1|1) +{ + x = y = 0.0f; + d = -((z = dir) * point.z); +} + +inline void PlaneF::setYZ(const Point3F& point, F32 dir) // Normal = (-1|1, 0, 0) +{ + z = y = 0.0f; + d = -((x = dir) * point.x); +} + +inline void PlaneF::setXZ(const Point3F& point, F32 dir) // Normal = (0, -1|1, 0) +{ + x = z = 0.0f; + d = -((y = dir) * point.y); +} + +inline void PlaneF::shiftX( F32 xx ) +{ + d -= xx * x; +} + +inline void PlaneF::shiftY( F32 yy ) +{ + d -= yy * y; +} + +inline void PlaneF::shiftZ( F32 zz ) +{ + d -= zz * z; +} + +inline bool PlaneF::isHorizontal() const +{ + return (x == 0.0f && y == 0.0f) ? true : false; +} + +inline bool PlaneF::isVertical() const +{ + return ((x != 0.0f || y != 0.0f) && z == 0.0f) ? true : false; +} + +inline Point3F PlaneF::project(const Point3F &pt) const +{ + F32 dist = distToPlane(pt); + return Point3F(pt.x - x * dist, pt.y - y * dist, pt.z - z * dist); +} + +inline F32 PlaneF::distToPlane( const Point3F& cp ) const +{ + // return mDot(*this,cp) + d; + return (x * cp.x + y * cp.y + z * cp.z) + d; +} + +inline PlaneF::Side PlaneF::whichSide(const Point3F& cp) const +{ + F32 dist = distToPlane(cp); + if (dist >= 0.005f) // if (mFabs(dist) < 0.005f) + return Front; // return On; + else if (dist <= -0.005f) // else if (dist > 0.0f) + return Back; // return Front; + else // else + return On; // return Back; +} + +inline bool PlaneF::isOnPlane( const Point3F& cp ) const +{ + return (whichSide(cp) == On); +} + +inline void PlaneF::set( F32 _x, F32 _y, F32 _z ) +{ + Point3F::set(_x,_y,_z); +} + +inline void PlaneF::set( F32 _x, F32 _y, F32 _z, F32 _d ) +{ + Point3F::set(_x,_y,_z); + d = _d; +} + +//--------------------------------------------------------------------------- +/// Calculate the coefficients of the plane passing through +/// a point with the given normal. +inline void PlaneF::setPoint(const Point3F &p) +{ + d = -(p.x * x + p.y * y + p.z * z); +} + +inline void PlaneF::set( const Point3F& p, const Point3F& n ) +{ + x = n.x; y = n.y; z = n.z; + normalize(); + + // Calculate the last plane coefficient. + + d = -(p.x * x + p.y * y + p.z * z); +} + +//--------------------------------------------------------------------------- +// Calculate the coefficients of the plane passing through +// three points. Basically it calculates the normal to the three +// points then calculates a plane through the middle point with that +// normal. +inline void PlaneF::set( const Point3F& k, const Point3F& j, const Point3F& l ) +{ + // Point3F kj, lj, pv; + // kj = k - j; + // lj = l - j; + // mCross( kj, lj, &pv ); + // set(j, pv); + + // Inline for speed... + const F32 ax = k.x-j.x; + const F32 ay = k.y-j.y; + const F32 az = k.z-j.z; + const F32 bx = l.x-j.x; + const F32 by = l.y-j.y; + const F32 bz = l.z-j.z; + x = ay*bz - az*by; + y = az*bx - ax*bz; + z = ax*by - ay*bx; + + m_point3F_normalize( (F32 *)(&x) ); + d = -(j.x * x + j.y * y + j.z * z); +} + +inline void PlaneF::invert() +{ + x = -x; + y = -y; + z = -z; + d = -d; +} + +inline void PlaneF::neg() +{ + invert(); +} + +inline F32 PlaneF::intersect(const Point3F &p1, const Point3F &p2) const +{ + const F32 den = mDot(p2 - p1, *this); + if(den == 0.0f) + return PARALLEL_PLANE; + return -distToPlane(p1) / den; +} + +inline bool PlaneF::intersect( const Point3F &start, const Point3F &dir, Point3F *outHit ) const +{ + const F32 den = mDot( dir, *this ); + if ( mIsZero( den ) ) + return false; + + F32 dist = -distToPlane( start ) / den; + *outHit = start + dir * dist; + return true; +} + +inline PlaneF::Side PlaneF::whichSideBox(const Point3F& center, + const Point3F& axisx, + const Point3F& axisy, + const Point3F& axisz, + const Point3F& /*offset*/) const +{ + F32 baseDist = distToPlane(center); + + F32 compDist = mFabs(mDot(axisx, *this)) + + mFabs(mDot(axisy, *this)) + + mFabs(mDot(axisz, *this)); + + if (baseDist >= compDist) + return Front; + else if (baseDist <= -compDist) + return Back; + else + return On; +} + +class PlaneD: public Point3D +{ +public: + F64 d; + + PlaneD(); + PlaneD( const PlaneF& copy); + PlaneD( const Point3D& p, const Point3D& n ); + PlaneD( F64 _x, F64 _y, F64 _z, F64 _d); + PlaneD( const Point3D& j, const Point3D& k, const Point3D& l ); + + // Methods + //using Point3D::set; + void set(const F64 _x, const F64 _y, const F64 _z); + + void set( const Point3D& p, const Point3D& n); + void set( const Point3D& k, const Point3D& j, const Point3D& l ); + void setPoint(const Point3D &p); // assumes the x,y,z fields are already set + // creates an un-normalized plane + + void setXY(F64 zz); + void setYZ(F64 xx); + void setXZ(F64 yy); + void setXY(const Point3D& P, F64 dir); + void setYZ(const Point3D& P, F64 dir); + void setXZ(const Point3D& P, F64 dir); + void shiftX(F64 xx); + void shiftY(F64 yy); + void shiftZ(F64 zz); + void invert(); + void neg(); + Point3D project(const Point3D &pt) const; // projects the point onto the plane. + + F64 distToPlane( const Point3D& cp ) const; + + enum Side { + Front = 1, + On = 0, + Back = -1 + }; + + Side whichSide(const Point3D& cp) const; + F64 intersect(const Point3D &start, const Point3D &end) const; + //DLLAPI bool split( const Poly3F& poly, Poly3F* front, Poly3F* back ); + + bool isHorizontal() const; + bool isVertical() const; + + Side whichSideBox(const Point3D& center, + const Point3D& axisx, + const Point3D& axisy, + const Point3D& axisz, + const Point3D& offset) const; +}; +//#define PARALLEL_PLANE 1e20f + +//#define PlaneSwitchCode(s, e) (s * 3 + e) + + +//--------------------------------------------------------------------------- + +inline PlaneD::PlaneD() +{ +} + +inline PlaneD:: + PlaneD( F64 _x, F64 _y, F64 _z, F64 _d ) +{ + x = _x; y = _y; z = _z; d = _d; +} + +inline PlaneD::PlaneD( const PlaneF& copy) +{ + x = copy.x; y = copy.y; z = copy.z; d = copy.d; +} + +inline PlaneD::PlaneD( const Point3D& p, const Point3D& n ) +{ + set(p,n); +} + +inline PlaneD::PlaneD( const Point3D& j, const Point3D& k, const Point3D& l ) +{ + set(j,k,l); +} + +inline void PlaneD::setXY( F64 zz ) +{ + x = y = 0; z = 1; d = -zz; +} + +inline void PlaneD::setYZ( F64 xx ) +{ + x = 1; z = y = 0; d = -xx; +} + +inline void PlaneD::setXZ( F64 yy ) +{ + x = z = 0; y = 1; d = -yy; +} + +inline void PlaneD::setXY(const Point3D& point, F64 dir) // Normal = (0, 0, -1|1) +{ + x = y = 0; + d = -((z = dir) * point.z); +} + +inline void PlaneD::setYZ(const Point3D& point, F64 dir) // Normal = (-1|1, 0, 0) +{ + z = y = 0; + d = -((x = dir) * point.x); +} + +inline void PlaneD::setXZ(const Point3D& point, F64 dir) // Normal = (0, -1|1, 0) +{ + x = z = 0; + d = -((y = dir) * point.y); +} + +inline void PlaneD::shiftX( F64 xx ) +{ + d -= xx * x; +} + +inline void PlaneD::shiftY( F64 yy ) +{ + d -= yy * y; +} + +inline void PlaneD::shiftZ( F64 zz ) +{ + d -= zz * z; +} + +inline bool PlaneD::isHorizontal() const +{ + return (x == 0 && y == 0) ? true : false; +} + +inline bool PlaneD::isVertical() const +{ + return ((x != 0 || y != 0) && z == 0) ? true : false; +} + +inline Point3D PlaneD::project(const Point3D &pt) const +{ + F64 dist = distToPlane(pt); + return Point3D(pt.x - x * dist, pt.y - y * dist, pt.z - z * dist); +} + +inline F64 PlaneD::distToPlane( const Point3D& cp ) const +{ + // return mDot(*this,cp) + d; + return (x * cp.x + y * cp.y + z * cp.z) + d; +} + +inline PlaneD::Side PlaneD::whichSide(const Point3D& cp) const +{ + F64 dist = distToPlane(cp); + if (dist >= 0.005f) // if (mFabs(dist) < 0.005f) + return Front; // return On; + else if (dist <= -0.005f) // else if (dist > 0.0f) + return Back; // return Front; + else // else + return On; // return Back; +} + +inline void PlaneD::set(const F64 _x, const F64 _y, const F64 _z) +{ + Point3D::set(_x,_y,_z); +} + +//--------------------------------------------------------------------------- +// Calculate the coefficients of the plane passing through +// a point with the given normal. + +////inline void PlaneD::set( const Point3D& p, const Point3D& n ) +inline void PlaneD::setPoint(const Point3D &p) +{ + d = -(p.x * x + p.y * y + p.z * z); +} + +inline void PlaneD::set( const Point3D& p, const Point3D& n ) +{ + x = n.x; y = n.y; z = n.z; + normalize(); + + // Calculate the last plane coefficient. + + d = -(p.x * x + p.y * y + p.z * z); +} + +//--------------------------------------------------------------------------- +// Calculate the coefficients of the plane passing through +// three points. Basically it calculates the normal to the three +// points then calculates a plane through the middle point with that +// normal. + +inline void PlaneD::set( const Point3D& k, const Point3D& j, const Point3D& l ) +{ +// Point3D kj,lj,pv; +// kj = k; +// kj -= j; +// lj = l; +// lj -= j; +// mCross( kj, lj, &pv ); +// set(j,pv); + +// Above ends up making function calls up the %*#... +// This is called often enough to be a little more direct +// about it (sqrt should become intrinsic in the future)... + F64 ax = k.x-j.x; + F64 ay = k.y-j.y; + F64 az = k.z-j.z; + F64 bx = l.x-j.x; + F64 by = l.y-j.y; + F64 bz = l.z-j.z; + x = ay*bz - az*by; + y = az*bx - ax*bz; + z = ax*by - ay*bx; + F64 squared = x*x + y*y + z*z; + AssertFatal(squared != 0.0, "Error, no plane possible!"); + + // In non-debug mode + if (squared != 0) + { + F64 invSqrt = 1.0 / (F64)mSqrt(x*x + y*y + z*z); + x *= invSqrt; + y *= invSqrt; + z *= invSqrt; + d = -(j.x * x + j.y * y + j.z * z); + } + else + { + x = 0; + y = 0; + z = 1; + d = -(j.x * x + j.y * y + j.z * z); + } +} + +inline void PlaneD::invert() +{ + x = -x; + y = -y; + z = -z; + d = -d; +} + +inline void PlaneD::neg() +{ + invert(); +} + +inline F64 PlaneD::intersect(const Point3D &p1, const Point3D &p2) const +{ + F64 den = mDot(p2 - p1, *this); + if(den == 0) + return PARALLEL_PLANE; + return -distToPlane(p1) / den; +} + +inline PlaneD::Side PlaneD::whichSideBox(const Point3D& center, + const Point3D& axisx, + const Point3D& axisy, + const Point3D& axisz, + const Point3D& /*offset*/) const +{ + F64 baseDist = distToPlane(center); + + F64 compDist = mFabs(mDot(axisx, *this)) + + mFabs(mDot(axisy, *this)) + + mFabs(mDot(axisz, *this)); + + // Intersects + if (baseDist >= compDist) + return Front; + else if (baseDist <= -compDist) + return Back; + else + return On; + +// if (compDist > mFabs(baseDist)) +// return On; +// else +// return baseDist < 0.0 ? Back : Front; +} + + +/// Returns the line of intersection between two planes or +/// false when they are coplanar. +bool mIntersect( const PlaneF &p1, const PlaneF &p2, + Point3F *outLinePt, VectorF *outLineDir ); + +#endif // _MPLANE_H_ diff --git a/math/mPlaneTransformer.cpp b/math/mPlaneTransformer.cpp new file mode 100644 index 0000000..b4cb894 --- /dev/null +++ b/math/mPlaneTransformer.cpp @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mPlaneTransformer.h" +#include "math/mMathFn.h" + +void PlaneTransformer::set(const MatrixF& xform, const Point3F& scale) +{ + mTransform = xform; + mScale = scale; + + MatrixF scaleMat(true); + F32* m = scaleMat; + m[MatrixF::idx(0, 0)] = scale.x; + m[MatrixF::idx(1, 1)] = scale.y; + m[MatrixF::idx(2, 2)] = scale.z; + + mTransposeInverse = xform; + mTransposeInverse.mul(scaleMat); + mTransposeInverse.transpose(); + mTransposeInverse.inverse(); +} + +void PlaneTransformer::transform(const PlaneF& plane, PlaneF& result) +{ + Point3F point = plane; + point *= -plane.d; + point.convolve(mScale); + mTransform.mulP(point); + + Point3F normal = plane; + mTransposeInverse.mulV(normal); + + result.set(point, normal); +// mTransformPlane(mTransform, mScale, plane, &result); +} + +void PlaneTransformer::setIdentity() +{ + static struct MakeIdentity + { + PlaneTransformer transformer; + MakeIdentity() + { + MatrixF defMat(true); + Point3F defScale(1.0f, 1.0f, 1.0f); + transformer.set(defMat, defScale); + } + } sMakeIdentity; + + *this = sMakeIdentity.transformer; +} diff --git a/math/mPlaneTransformer.h b/math/mPlaneTransformer.h new file mode 100644 index 0000000..17a1d9c --- /dev/null +++ b/math/mPlaneTransformer.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPLANETRANSFORMER_H_ +#define _MPLANETRANSFORMER_H_ + +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MPLANE_H_ +#include "math/mPlane.h" +#endif + +// ========================================================= +class PlaneTransformer +{ + MatrixF mTransform; + MatrixF mTransposeInverse; + Point3F mScale; + + public: + void set(const MatrixF& xform, const Point3F& scale); + void transform(const PlaneF& plane, PlaneF& result); + void setIdentity(); +}; + +#endif diff --git a/math/mPoint.cpp b/math/mPoint.cpp new file mode 100644 index 0000000..5203488 --- /dev/null +++ b/math/mPoint.cpp @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mPoint2.h" +#include "math/mPoint3.h" +#include "math/mPoint4.h" + + +const Point2I Point2I::One(1, 1); +const Point2I Point2I::Zero(0, 0); +const Point2I Point2I::Min(S32_MIN, S32_MIN); +const Point2I Point2I::Max(S32_MAX, S32_MAX); + +const Point2F Point2F::One(1.0f, 1.0f); +const Point2F Point2F::Zero(0.0f, 0.0f); +const Point2F Point2F::Min(F32_MIN, F32_MIN); +const Point2F Point2F::Max(F32_MAX, F32_MAX); + +const Point2D Point2D::One(1.0, 1.0); +const Point2D Point2D::Zero(0.0, 0.0); + +const Point3I Point3I::One(1, 1, 1); +const Point3I Point3I::Zero(0, 0, 0); + +const Point3F Point3F::One(1.0f, 1.0f, 1.0f); +const Point3F Point3F::Zero(0.0f, 0.0f, 0.0f); +const Point3F Point3F::Min(F32_MIN, F32_MIN, F32_MIN); +const Point3F Point3F::Max(F32_MAX, F32_MAX, F32_MAX); + +const Point3D Point3D::One(1.0, 1.0, 1.0); +const Point3D Point3D::Zero(0.0, 0.0, 0.0); + +const Point4I Point4I::One(1, 1, 1, 1); +const Point4I Point4I::Zero(0, 0, 0, 0); + +const Point4F Point4F::One(1.0f, 1.0f, 1.0f, 1.0f); +const Point4F Point4F::Zero(0.0f, 0.0f, 0.0f, 0.0f); + + +Point3F mPerp( const Point3F &inVec ) +{ + AssertFatal( inVec.len() > 0.0f, "mPerp() - zero length vector has no perp!" ); + AssertFatal( inVec.isUnitLength(), "mPerp() - passed vector must be normalized!" ); + + U32 idx = inVec.getLeastComponentIndex(); + + Point3F vec( 0.0f, 0.0f, 0.0f ); + vec[idx] = 1.0f; + + Point3F outVec = mCross( inVec, vec ); + outVec.normalize(); + + //AssertFatal( mIsZero( mDot( inVec, outVec ) ), "mPerp, failed to generate perpendicular" ); + + return outVec; +} + diff --git a/math/mPoint2.h b/math/mPoint2.h new file mode 100644 index 0000000..77716c8 --- /dev/null +++ b/math/mPoint2.h @@ -0,0 +1,837 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPOINT2_H_ +#define _MPOINT2_H_ + +#ifndef _MMATHFN_H_ +#include "math/mMathFn.h" +#endif + +//------------------------------------------------------------------------------ +/// 2D integer point +/// +/// Uses S32 internally. +class Point2I +{ + //-------------------------------------- Public data + public: + S32 x; ///< X position + S32 y; ///< Y position + + //-------------------------------------- Public interface + public: + Point2I(); ///< Create an uninitialized point. + Point2I(const Point2I&); ///< Copy constructor + Point2I(S32 in_x, S32 in_y); ///< Create point from two co-ordinates. + + //-------------------------------------- Non-math mutators and misc functions + void set(S32 in_x, S32 in_y); ///< Set (x,y) position + void setMin(const Point2I&); ///< Store lesser co-ordinates from parameter in this point. + void setMax(const Point2I&); ///< Store greater co-ordinates from parameter in this point. + + //-------------------------------------- Math mutators + void neg(); ///< Invert sign of point's co-ordinates. + void convolve(const Point2I&); ///< Convolve this point by parameter. + + //-------------------------------------- Queries + bool isZero() const; ///< Is this point at the origin? (No epsilon used) + F32 len() const; ///< Get the length of the point + S32 lenSquared() const; ///< Get the length-squared of the point + + //-------------------------------------- Overloaded operators + public: + operator S32*() { return &x; } + operator const S32*() const { return &x; } + + // Comparison operators + bool operator==(const Point2I&) const; + bool operator!=(const Point2I&) const; + + // Arithmetic w/ other points + Point2I operator+(const Point2I&) const; + Point2I operator-(const Point2I&) const; + Point2I& operator+=(const Point2I&); + Point2I& operator-=(const Point2I&); + + // Arithmetic w/ scalars + Point2I operator*(S32) const; + Point2I& operator*=(S32); + Point2I operator/(S32) const; + Point2I& operator/=(S32); + + // Unary operators + Point2I operator-() const; + + //-------------------------------------- Public static constants + public: + const static Point2I One; + const static Point2I Zero; + const static Point2I Min; + const static Point2I Max; +}; + +//------------------------------------------------------------------------------ +/// 2D floating-point point. +class Point2F +{ + //-------------------------------------- Public data + public: + F32 x; + F32 y; + + public: + Point2F(); ///< Create uninitialized point. + Point2F(const Point2F&); ///< Copy constructor + Point2F(F32 _x, F32 _y); ///< Create point from co-ordinates. + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(F32 _x, F32 _y); ///< Set point's co-ordinates. + + void setMin(const Point2F&); ///< Store lesser co-ordinates. + void setMax(const Point2F&); ///< Store greater co-ordinates. + + /// Interpolate from a to b, based on c. + /// + /// @param a Starting point. + /// @param b Ending point. + /// @param c Interpolation factor (0.0 .. 1.0). + void interpolate(const Point2F& a, const Point2F& b, const F32 c); + + operator F32*() { return &x; } + operator const F32*() const { return &x; } + + //-------------------------------------- Queries + public: + bool isZero() const; ///< Check for zero coordinates. (No epsilon.) + F32 len() const; ///< Get length. + F32 lenSquared() const; ///< Get squared length (one sqrt less than len()). + bool equal( const Point2F &compare ) const; ///< Is compare within POINT_EPSILON of all of the component of this point + F32 magnitudeSafe() const; + + //-------------------------------------- Mathematical mutators + public: + void neg(); ///< Invert signs of co-ordinates. + void normalize(); ///< Normalize vector. + void normalize(F32 val); ///< Normalize, scaling by val. + void normalizeSafe(); + void convolve(const Point2F&); ///< Convolve by parameter. + void convolveInverse(const Point2F&); ///< Inversely convolute by parameter. (ie, divide) + void rotate( F32 radians ); ///< Rotate vector around origin. + + //-------------------------------------- Overloaded operators + public: + // Comparison operators + bool operator==(const Point2F&) const; + bool operator!=(const Point2F&) const; + + // Arithmetic w/ other points + Point2F operator+(const Point2F&) const; + Point2F operator-(const Point2F&) const; + Point2F& operator+=(const Point2F&); + Point2F& operator-=(const Point2F&); + + // Arithmetic w/ scalars + Point2F operator*(F32) const; + Point2F operator/(F32) const; + Point2F& operator*=(F32); + Point2F& operator/=(F32); + + Point2F operator*(const Point2F&) const; + Point2F& operator*=(const Point2F&); + Point2F operator/(const Point2F&) const; + Point2F& operator/=(const Point2F&); + + // Unary operators + Point2F operator-() const; + + //-------------------------------------- Public static constants + public: + const static Point2F One; + const static Point2F Zero; + const static Point2F Min; + const static Point2F Max; +}; + + +//------------------------------------------------------------------------------ +/// 2D high-precision point. +/// +/// Uses F64 internally. +class Point2D +{ + //-------------------------------------- Public data + public: + F64 x; ///< X co-ordinate. + F64 y; ///< Y co-ordinate. + + public: + Point2D(); ///< Create uninitialized point. + Point2D(const Point2D&); ///< Copy constructor + Point2D(F64 _x, F64 _y); ///< Create point from coordinates. + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(F64 _x, F64 _y); ///< Set point's coordinates. + + void setMin(const Point2D&); ///< Store lesser co-ordinates. + void setMax(const Point2D&); ///< Store greater co-ordinates. + + /// Interpolate from a to b, based on c. + /// + /// @param a Starting point. + /// @param b Ending point. + /// @param c Interpolation factor (0.0 .. 1.0). + void interpolate(const Point2D &a, const Point2D &b, const F64 c); + + operator F64*() { return &x; } + operator const F64*() const { return &x; } + + //-------------------------------------- Queries + public: + bool isZero() const; + F64 len() const; + F64 lenSquared() const; + + //-------------------------------------- Mathematical mutators + public: + void neg(); + void normalize(); + void normalize(F64 val); + void convolve(const Point2D&); + void convolveInverse(const Point2D&); + + //-------------------------------------- Overloaded operators + public: + // Comparison operators + bool operator==(const Point2D&) const; + bool operator!=(const Point2D&) const; + + // Arithmetic w/ other points + Point2D operator+(const Point2D&) const; + Point2D operator-(const Point2D&) const; + Point2D& operator+=(const Point2D&); + Point2D& operator-=(const Point2D&); + + // Arithmetic w/ scalars + Point2D operator*(F64) const; + Point2D operator/(F64) const; + Point2D& operator*=(F64); + Point2D& operator/=(F64); + + // Unary operators + Point2D operator-() const; + + //-------------------------------------- Public static constants + public: + const static Point2D One; + const static Point2D Zero; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- Point2I +// +inline Point2I::Point2I() +{ + // +} + + +inline Point2I::Point2I(const Point2I& _copy) + : x(_copy.x), y(_copy.y) +{ + // +} + + +inline Point2I::Point2I(S32 _x, S32 _y) + : x(_x), y(_y) +{ + // +} + + +inline void Point2I::set(S32 _x, S32 _y) +{ + x = _x; + y = _y; +} + + +inline void Point2I::setMin(const Point2I& _test) +{ + x = (_test.x < x) ? _test.x : x; + y = (_test.y < y) ? _test.y : y; +} + + +inline void Point2I::setMax(const Point2I& _test) +{ + x = (_test.x > x) ? _test.x : x; + y = (_test.y > y) ? _test.y : y; +} + + +inline void Point2I::neg() +{ + x = -x; + y = -y; +} + +inline void Point2I::convolve(const Point2I& c) +{ + x *= c.x; + y *= c.y; +} + +inline bool Point2I::isZero() const +{ + return ((x == 0) && (y == 0)); +} + + +inline F32 Point2I::len() const +{ + return mSqrt(F32(x*x + y*y)); +} + +inline S32 Point2I::lenSquared() const +{ + return x*x + y*y; +} + +inline bool Point2I::operator==(const Point2I& _test) const +{ + return ((x == _test.x) && (y == _test.y)); +} + + +inline bool Point2I::operator!=(const Point2I& _test) const +{ + return (operator==(_test) == false); +} + + +inline Point2I Point2I::operator+(const Point2I& _add) const +{ + return Point2I(x + _add.x, y + _add.y); +} + + +inline Point2I Point2I::operator-(const Point2I& _rSub) const +{ + return Point2I(x - _rSub.x, y - _rSub.y); +} + + +inline Point2I& Point2I::operator+=(const Point2I& _add) +{ + x += _add.x; + y += _add.y; + + return *this; +} + + +inline Point2I& Point2I::operator-=(const Point2I& _rSub) +{ + x -= _rSub.x; + y -= _rSub.y; + + return *this; +} + + +inline Point2I Point2I::operator-() const +{ + return Point2I(-x, -y); +} + + +inline Point2I Point2I::operator*(S32 mul) const +{ + return Point2I(x * mul, y * mul); +} + +inline Point2I Point2I::operator/(S32 div) const +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + return Point2I(x/div, y/div); +} + + +inline Point2I& Point2I::operator*=(S32 mul) +{ + x *= mul; + y *= mul; + + return *this; +} + + +inline Point2I& Point2I::operator/=(S32 div) +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + + x /= div; + y /= div; + + return *this; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Point2F +// +inline Point2F::Point2F() +{ + // +} + + +inline Point2F::Point2F(const Point2F& _copy) + : x(_copy.x), y(_copy.y) +{ + // +} + + +inline Point2F::Point2F(F32 _x, F32 _y) + : x(_x), y(_y) +{ +} + + +inline void Point2F::set(F32 _x, F32 _y) +{ + x = _x; + y = _y; +} + + +inline void Point2F::setMin(const Point2F& _test) +{ + x = (_test.x < x) ? _test.x : x; + y = (_test.y < y) ? _test.y : y; +} + + +inline void Point2F::setMax(const Point2F& _test) +{ + x = (_test.x > x) ? _test.x : x; + y = (_test.y > y) ? _test.y : y; +} + + +inline void Point2F::interpolate(const Point2F& _rFrom, const Point2F& _to, const F32 _factor) +{ + AssertFatal(_factor >= 0.0f && _factor <= 1.0f, "Out of bound interpolation factor"); + x = (_rFrom.x * (1.0f - _factor)) + (_to.x * _factor); + y = (_rFrom.y * (1.0f - _factor)) + (_to.y * _factor); +} + + +inline bool Point2F::isZero() const +{ + return (x == 0.0f) && (y == 0.0f); +} + + +inline F32 Point2F::lenSquared() const +{ + return (x * x) + (y * y); +} + + +inline bool Point2F::equal( const Point2F &compare ) const +{ + return( ( mFabs( x - compare.x ) < POINT_EPSILON ) && + ( mFabs( y - compare.y ) < POINT_EPSILON ) ); +} + + +inline void Point2F::neg() +{ + x = -x; + y = -y; +} + +inline void Point2F::convolve(const Point2F& c) +{ + x *= c.x; + y *= c.y; +} + + +inline void Point2F::convolveInverse(const Point2F& c) +{ + x /= c.x; + y /= c.y; +} + +inline void Point2F::rotate( F32 radians ) +{ + const F32 sinTheta = mSin( radians ); + const F32 cosTheta = mCos( radians ); + set( cosTheta * x - sinTheta * y, + sinTheta * x + cosTheta * y ); +} + +inline bool Point2F::operator==(const Point2F& _test) const +{ + return (x == _test.x) && (y == _test.y); +} + + +inline bool Point2F::operator!=(const Point2F& _test) const +{ + return operator==(_test) == false; +} + + +inline Point2F Point2F::operator+(const Point2F& _add) const +{ + return Point2F(x + _add.x, y + _add.y); +} + + +inline Point2F Point2F::operator-(const Point2F& _rSub) const +{ + return Point2F(x - _rSub.x, y - _rSub.y); +} + + +inline Point2F& Point2F::operator+=(const Point2F& _add) +{ + x += _add.x; + y += _add.y; + + return *this; +} + + +inline Point2F& Point2F::operator-=(const Point2F& _rSub) +{ + x -= _rSub.x; + y -= _rSub.y; + + return *this; +} + + +inline Point2F Point2F::operator*(F32 _mul) const +{ + return Point2F(x * _mul, y * _mul); +} + + +inline Point2F Point2F::operator/(F32 _div) const +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F32 inv = 1.0f / _div; + + return Point2F(x * inv, y * inv); +} + + +inline Point2F& Point2F::operator*=(F32 _mul) +{ + x *= _mul; + y *= _mul; + + return *this; +} + + +inline Point2F& Point2F::operator/=(F32 _div) +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F32 inv = 1.0f / _div; + + x *= inv; + y *= inv; + + return *this; +} + +inline Point2F Point2F::operator*(const Point2F &_vec) const +{ + return Point2F(x * _vec.x, y * _vec.y); +} + +inline Point2F& Point2F::operator*=(const Point2F &_vec) +{ + x *= _vec.x; + y *= _vec.y; + return *this; +} + +inline Point2F Point2F::operator/(const Point2F &_vec) const +{ + return Point2F(x / _vec.x, y / _vec.y); +} + +inline Point2F& Point2F::operator/=(const Point2F &_vec) +{ + x /= _vec.x; + y /= _vec.y; + return *this; +} + +inline Point2F Point2F::operator-() const +{ + return Point2F(-x, -y); +} + +inline F32 Point2F::len() const +{ + return mSqrt(x*x + y*y); +} + +inline void Point2F::normalize() +{ + m_point2F_normalize(*this); +} + +inline void Point2F::normalize(F32 val) +{ + m_point2F_normalize_f(*this, val); +} + +inline F32 Point2F::magnitudeSafe() const +{ + if( isZero() ) + return 0.0f; + else + return len(); +} + +inline void Point2F::normalizeSafe() +{ + F32 vmag = magnitudeSafe(); + + if( vmag > POINT_EPSILON ) + *this *= F32(1.0 / vmag); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Point2D +// +inline Point2D::Point2D() +{ + // +} + + +inline Point2D::Point2D(const Point2D& _copy) + : x(_copy.x), y(_copy.y) +{ + // +} + + +inline Point2D::Point2D(F64 _x, F64 _y) + : x(_x), y(_y) +{ +} + + +inline void Point2D::set(F64 _x, F64 _y) +{ + x = _x; + y = _y; +} + + +inline void Point2D::setMin(const Point2D& _test) +{ + x = (_test.x < x) ? _test.x : x; + y = (_test.y < y) ? _test.y : y; +} + + +inline void Point2D::setMax(const Point2D& _test) +{ + x = (_test.x > x) ? _test.x : x; + y = (_test.y > y) ? _test.y : y; +} + + +inline void Point2D::interpolate(const Point2D& _rFrom, const Point2D& _to, const F64 _factor) +{ + AssertFatal(_factor >= 0.0f && _factor <= 1.0f, "Out of bound interpolation factor"); + x = (_rFrom.x * (1.0f - _factor)) + (_to.x * _factor); + y = (_rFrom.y * (1.0f - _factor)) + (_to.y * _factor); +} + + +inline bool Point2D::isZero() const +{ + return (x == 0.0f) && (y == 0.0f); +} + + +inline F64 Point2D::lenSquared() const +{ + return (x * x) + (y * y); +} + + +inline void Point2D::neg() +{ + x = -x; + y = -y; +} + +inline void Point2D::convolve(const Point2D& c) +{ + x *= c.x; + y *= c.y; +} + +inline void Point2D::convolveInverse(const Point2D& c) +{ + x /= c.x; + y /= c.y; +} + +inline bool Point2D::operator==(const Point2D& _test) const +{ + return (x == _test.x) && (y == _test.y); +} + + +inline bool Point2D::operator!=(const Point2D& _test) const +{ + return operator==(_test) == false; +} + + +inline Point2D Point2D::operator+(const Point2D& _add) const +{ + return Point2D(x + _add.x, y + _add.y); +} + + +inline Point2D Point2D::operator-(const Point2D& _rSub) const +{ + return Point2D(x - _rSub.x, y - _rSub.y); +} + + +inline Point2D& Point2D::operator+=(const Point2D& _add) +{ + x += _add.x; + y += _add.y; + + return *this; +} + + +inline Point2D& Point2D::operator-=(const Point2D& _rSub) +{ + x -= _rSub.x; + y -= _rSub.y; + + return *this; +} + + +inline Point2D Point2D::operator*(F64 _mul) const +{ + return Point2D(x * _mul, y * _mul); +} + + +inline Point2D Point2D::operator/(F64 _div) const +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F64 inv = 1.0f / _div; + + return Point2D(x * inv, y * inv); +} + + +inline Point2D& Point2D::operator*=(F64 _mul) +{ + x *= _mul; + y *= _mul; + + return *this; +} + + +inline Point2D& Point2D::operator/=(F64 _div) +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F64 inv = 1.0f / _div; + + x *= inv; + y *= inv; + + return *this; +} + + +inline Point2D Point2D::operator-() const +{ + return Point2D(-x, -y); +} + +inline F64 Point2D::len() const +{ + return mSqrtD(x*x + y*y); +} + +inline void Point2D::normalize() +{ + m_point2D_normalize(*this); +} + +inline void Point2D::normalize(F64 val) +{ + m_point2D_normalize_f(*this, val); +} + + +//------------------------------------------------------------------- +// Non-Member Operators +//------------------------------------------------------------------- + +inline Point2I operator*(S32 mul, const Point2I& multiplicand) +{ + return multiplicand * mul; +} + +inline Point2F operator*(F32 mul, const Point2F& multiplicand) +{ + return multiplicand * mul; +} + +inline Point2D operator*(F64 mul, const Point2D& multiplicand) +{ + return multiplicand * mul; +} + +inline F32 mDot(const Point2F &p1, const Point2F &p2) +{ + return (p1.x*p2.x + p1.y*p2.y); +} + +inline F32 mDotPerp(const Point2F &p1, const Point2F &p2) +{ + return p1.x*p2.y - p2.x*p1.y; +} + +inline bool mIsNaN( const Point2F &p ) +{ + return mIsNaN_F( p.x ) || mIsNaN_F( p.y ); +} + +#endif // _MPOINT2_H_ diff --git a/math/mPoint3.h b/math/mPoint3.h new file mode 100644 index 0000000..a157fbd --- /dev/null +++ b/math/mPoint3.h @@ -0,0 +1,954 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPOINT3_H_ +#define _MPOINT3_H_ + +#ifndef _MMATHFN_H_ +#include "math/mMathFn.h" +#endif + + +//------------------------------------------------------------------------------ +/// 3D integer point +/// +/// Uses S32 internally. +class Point3I +{ + //-------------------------------------- Public data + public: + S32 x; ///< X co-ordinate + S32 y; ///< Y co-ordinate + S32 z; ///< Z co-ordinate + + //-------------------------------------- Public interface + public: + Point3I(); ///< Create an uninitialized point. + Point3I(const Point3I&); ///< Copy constructor. + Point3I(S32 in_x, S32 in_y, S32 in_z); ///< Create a point from co-ordinates. + + //-------------------------------------- Non-math mutators and misc functions + void set(S32 in_x, S32 in_y, S32 in_z); ///< Set co-ordinates. + void setMin(const Point3I&); ///< Store lesser co-ordinates in this point. + void setMax(const Point3I&); ///< Store greater co-ordinates in this point. + + //-------------------------------------- Math mutators + void neg(); ///< Invert co-ordinate's signs. + void convolve(const Point3I&); ///< Convolve by parameter. + + //-------------------------------------- Queries + bool isZero() const; ///< Check for point at origin. (No epsilon.) + F32 len() const; ///< Get length. + + //-------------------------------------- Overloaded operators + public: + operator S32*() { return &x; } + operator const S32*() const { return &x; } + + // Comparison operators + bool operator==(const Point3I&) const; + bool operator!=(const Point3I&) const; + + // Arithmetic w/ other points + Point3I operator+(const Point3I&) const; + Point3I operator-(const Point3I&) const; + Point3I& operator+=(const Point3I&); + Point3I& operator-=(const Point3I&); + + // Arithmetic w/ scalars + Point3I operator*(S32) const; + Point3I& operator*=(S32); + Point3I operator/(S32) const; + Point3I& operator/=(S32); + + // Unary operators + Point3I operator-() const; + + //-------------------------------------- Public static constants +public: + const static Point3I One; + const static Point3I Zero; +}; + +class Point3D; + +//------------------------------------------------------------------------------ +class Point3F +{ + //-------------------------------------- Public data + public: + F32 x; + F32 y; + F32 z; + + public: + Point3F(); + Point3F(const Point3F&); + Point3F(F32 _x, F32 _y, F32 _z); + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(F32 _x, F32 _y, F32 _z); + void set(const Point3F&); + + void setMin(const Point3F&); + void setMax(const Point3F&); + + void interpolate(const Point3F&, const Point3F&, F32); + void zero(); + + /// Returns the smallest absolute value. + F32 least() const; + + operator F32*() { return &x; } + operator const F32*() const { return &x; } + + //-------------------------------------- Queries + public: + bool isZero() const; + bool isUnitLength() const; + F32 len() const; + F32 lenSquared() const; + F32 magnitudeSafe() const; + bool equal( const Point3F &compare ) const; + U32 getLeastComponentIndex() const; + U32 getGreatestComponentIndex() const; + + //-------------------------------------- Mathematical mutators + public: + void neg(); + void normalize(); + void normalizeSafe(); + void normalize(F32 val); + void convolve(const Point3F&); + void convolveInverse(const Point3F&); + + //-------------------------------------- Overloaded operators + public: + // Comparison operators + bool operator==(const Point3F&) const; + bool operator!=(const Point3F&) const; + + // Arithmetic w/ other points + Point3F operator+(const Point3F&) const; + Point3F operator-(const Point3F&) const; + Point3F& operator+=(const Point3F&); + Point3F& operator-=(const Point3F&); + + // Arithmetic w/ scalars + Point3F operator*(F32) const; + Point3F operator/(F32) const; + Point3F& operator*=(F32); + Point3F& operator/=(F32); + + Point3F operator*(const Point3F&) const; + Point3F& operator*=(const Point3F&); + Point3F operator/(const Point3F&) const; + Point3F& operator/=(const Point3F&); + + // Unary operators + Point3F operator-() const; + + Point3F& operator=(const Point3D&); + + //-------------------------------------- Public static constants +public: + const static Point3F One; + const static Point3F Zero; + const static Point3F Max; + const static Point3F Min; +}; + +typedef Point3F VectorF; +typedef Point3F EulerF; + + +//------------------------------------------------------------------------------ +class Point3D +{ + //-------------------------------------- Public data + public: + F64 x; + F64 y; + F64 z; + + public: + Point3D(); + Point3D(const Point3D&); + Point3D(const Point3F&); + Point3D(F64 _x, F64 _y, F64 _z); + + //-------------------------------------- Non-math mutators and misc functions + public: + void set(F64 _x, F64 _y, F64 _z); + + void setMin(const Point3D&); + void setMax(const Point3D&); + + void interpolate(const Point3D&, const Point3D&, F64); + + operator F64*() { return (&x); } + operator const F64*() const { return &x; } + + //-------------------------------------- Queries + public: + bool isZero() const; + F64 len() const; + F64 lenSquared() const; + + //-------------------------------------- Mathematical mutators + public: + void neg(); + void normalize(); + void normalize(F64 val); + void convolve(const Point3D&); + void convolveInverse(const Point3D&); + + //-------------------------------------- Overloaded operators + public: + Point3F toPoint3F() const; + // Comparison operators + bool operator==(const Point3D&) const; + bool operator!=(const Point3D&) const; + + // Arithmetic w/ other points + Point3D operator+(const Point3D&) const; + Point3D operator-(const Point3D&) const; + Point3D& operator+=(const Point3D&); + Point3D& operator-=(const Point3D&); + + // Arithmetic w/ scalars + Point3D operator*(F64) const; + Point3D operator/(F64) const; + Point3D& operator*=(F64); + Point3D& operator/=(F64); + + // Unary operators + Point3D operator-() const; + + //-------------------------------------- Public static constants +public: + const static Point3D One; + const static Point3D Zero; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- Point3I +// +inline Point3I::Point3I() +{ + // +} + +inline Point3I::Point3I(const Point3I& _copy) + : x(_copy.x), y(_copy.y), z(_copy.z) +{ + // +} + +inline Point3I::Point3I(S32 _x, S32 _y, S32 _z) + : x(_x), y(_y), z(_z) +{ + // +} + +inline void Point3I::set(S32 _x, S32 _y, S32 _z) +{ + x = _x; + y = _y; + z = _z; +} + +inline void Point3I::setMin(const Point3I& _test) +{ + x = (_test.x < x) ? _test.x : x; + y = (_test.y < y) ? _test.y : y; + z = (_test.z < z) ? _test.z : z; +} + +inline void Point3I::setMax(const Point3I& _test) +{ + x = (_test.x > x) ? _test.x : x; + y = (_test.y > y) ? _test.y : y; + z = (_test.z > z) ? _test.z : z; +} + +inline void Point3I::neg() +{ + x = -x; + y = -y; + z = -z; +} + +inline F32 Point3I::len() const +{ + return mSqrt(F32(x*x + y*y + z*z)); +} + +inline void Point3I::convolve(const Point3I& c) +{ + x *= c.x; + y *= c.y; + z *= c.z; +} + +inline bool Point3I::isZero() const +{ + return ((x == 0) && (y == 0) && (z == 0)); +} + +inline bool Point3I::operator==(const Point3I& _test) const +{ + return ((x == _test.x) && (y == _test.y) && (z == _test.z)); +} + +inline bool Point3I::operator!=(const Point3I& _test) const +{ + return (operator==(_test) == false); +} + +inline Point3I Point3I::operator+(const Point3I& _add) const +{ + return Point3I(x + _add.x, y + _add.y, z + _add.z); +} + +inline Point3I Point3I::operator-(const Point3I& _rSub) const +{ + return Point3I(x - _rSub.x, y - _rSub.y, z - _rSub.z); +} + +inline Point3I& Point3I::operator+=(const Point3I& _add) +{ + x += _add.x; + y += _add.y; + z += _add.z; + + return *this; +} + +inline Point3I& Point3I::operator-=(const Point3I& _rSub) +{ + x -= _rSub.x; + y -= _rSub.y; + z -= _rSub.z; + + return *this; +} + +inline Point3I Point3I::operator-() const +{ + return Point3I(-x, -y, -z); +} + +inline Point3I Point3I::operator*(S32 mul) const +{ + return Point3I(x * mul, y * mul, z * mul); +} + +inline Point3I Point3I::operator/(S32 div) const +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + return Point3I(x/div, y/div, z/div); +} + +inline Point3I& Point3I::operator*=(S32 mul) +{ + x *= mul; + y *= mul; + z *= mul; + + return *this; +} + +inline Point3I& Point3I::operator/=(S32 div) +{ + AssertFatal(div != 0, "Error, div by zero attempted"); + + x /= div; + y /= div; + z /= div; + + return *this; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Point3F +// +inline Point3F::Point3F() +#if defined(TORQUE_OS_LINUX) + : x(0.f), y(0.f), z(0.f) +#endif +{ +// Uninitialized points are definitely a problem. +// Enable the following code to see how often they crop up. +#ifdef DEBUG_MATH + *(U32 *)&x = 0x7FFFFFFA; + *(U32 *)&y = 0x7FFFFFFB; + *(U32 *)&z = 0x7FFFFFFC; +#endif +} + + +inline Point3F::Point3F(const Point3F& _copy) + : x(_copy.x), y(_copy.y), z(_copy.z) +{ + // +} + +inline Point3F::Point3F(F32 _x, F32 _y, F32 _z) + : x(_x), y(_y), z(_z) +{ + // +} + +inline void Point3F::set(F32 _x, F32 _y, F32 _z) +{ + x = _x; + y = _y; + z = _z; +} + +inline void Point3F::set(const Point3F& copy) +{ + x = copy.x; + y = copy.y; + z = copy.z; +} + +inline void Point3F::setMin(const Point3F& _test) +{ + x = (_test.x < x) ? _test.x : x; + y = (_test.y < y) ? _test.y : y; + z = (_test.z < z) ? _test.z : z; +} + +inline void Point3F::setMax(const Point3F& _test) +{ + x = (_test.x > x) ? _test.x : x; + y = (_test.y > y) ? _test.y : y; + z = (_test.z > z) ? _test.z : z; +} + +inline void Point3F::interpolate(const Point3F& _from, const Point3F& _to, F32 _factor) +{ + AssertFatal(_factor >= 0.0f && _factor <= 1.0f, "Out of bound interpolation factor"); + m_point3F_interpolate( _from, _to, _factor, *this); +} + +inline void Point3F::zero() +{ + x = y = z = 0.0f; +} + +inline bool Point3F::isZero() const +{ + return ((x*x) <= POINT_EPSILON) && ((y*y) <= POINT_EPSILON) && ((z*z) <= POINT_EPSILON ); +} + +inline bool Point3F::isUnitLength() const +{ + return ( mFabs( 1.0f - ( x*x + y*y + z*z ) ) < POINT_EPSILON ); +} + +inline bool Point3F::equal( const Point3F &compare ) const +{ + return( ( mFabs( x - compare.x ) < POINT_EPSILON ) && + ( mFabs( y - compare.y ) < POINT_EPSILON ) && + ( mFabs( z - compare.z ) < POINT_EPSILON ) ); +} + +inline U32 Point3F::getLeastComponentIndex() const +{ + U32 idx; + + if ( mFabs( x ) < mFabs( y ) ) + { + if ( mFabs( x ) < mFabs( z ) ) + idx = 0; + else + idx = 2; + } + else + { + if ( mFabs( y ) < mFabs( z ) ) + idx = 1; + else + idx = 2; + } + + return idx; +} + +inline U32 Point3F::getGreatestComponentIndex() const +{ + U32 idx; + + if ( mFabs( x ) > mFabs( y ) ) + { + if ( mFabs( x ) > mFabs( z ) ) + idx = 0; + else + idx = 2; + } + else + { + if ( mFabs( y ) > mFabs( z ) ) + idx = 1; + else + idx = 2; + } + + return idx; +} + +inline F32 Point3F::least() const +{ + if ( mFabs( x ) < mFabs( y ) ) + { + if ( mFabs( x ) < mFabs( z ) ) + return x; + else + return z; + } + + if ( mFabs( y ) < mFabs( z ) ) + return y; + else + return z; +} + +inline void Point3F::neg() +{ + x = -x; + y = -y; + z = -z; +} + +inline void Point3F::convolve(const Point3F& c) +{ + x *= c.x; + y *= c.y; + z *= c.z; +} + +inline void Point3F::convolveInverse(const Point3F& c) +{ + x /= c.x; + y /= c.y; + z /= c.z; +} + +inline F32 Point3F::lenSquared() const +{ + return (x * x) + (y * y) + (z * z); +} + +inline F32 Point3F::len() const +{ + return mSqrt(x*x + y*y + z*z); +} + +inline void Point3F::normalize() +{ + m_point3F_normalize(*this); +} + +inline F32 Point3F::magnitudeSafe() const +{ + if( isZero() ) + { + return 0.0f; + } + else + { + return len(); + } +} + +inline void Point3F::normalizeSafe() +{ + F32 vmag = magnitudeSafe(); + + if( vmag > POINT_EPSILON ) + { + *this *= F32(1.0 / vmag); + } +} + + +inline void Point3F::normalize(F32 val) +{ + m_point3F_normalize_f(*this, val); +} + +inline bool Point3F::operator==(const Point3F& _test) const +{ + return (x == _test.x) && (y == _test.y) && (z == _test.z); +} + +inline bool Point3F::operator!=(const Point3F& _test) const +{ + return operator==(_test) == false; +} + +inline Point3F Point3F::operator+(const Point3F& _add) const +{ + return Point3F(x + _add.x, y + _add.y, z + _add.z); +} + +inline Point3F Point3F::operator-(const Point3F& _rSub) const +{ + return Point3F(x - _rSub.x, y - _rSub.y, z - _rSub.z); +} + +inline Point3F& Point3F::operator+=(const Point3F& _add) +{ + x += _add.x; + y += _add.y; + z += _add.z; + + return *this; +} + +inline Point3F& Point3F::operator-=(const Point3F& _rSub) +{ + x -= _rSub.x; + y -= _rSub.y; + z -= _rSub.z; + + return *this; +} + +inline Point3F Point3F::operator*(F32 _mul) const +{ + return Point3F(x * _mul, y * _mul, z * _mul); +} + +inline Point3F Point3F::operator/(F32 _div) const +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F32 inv = 1.0f / _div; + + return Point3F(x * inv, y * inv, z * inv); +} + +inline Point3F& Point3F::operator*=(F32 _mul) +{ + x *= _mul; + y *= _mul; + z *= _mul; + + return *this; +} + +inline Point3F& Point3F::operator/=(F32 _div) +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F32 inv = 1.0f / _div; + x *= inv; + y *= inv; + z *= inv; + + return *this; +} + +inline Point3F Point3F::operator*(const Point3F &_vec) const +{ + return Point3F(x * _vec.x, y * _vec.y, z * _vec.z); +} + +inline Point3F& Point3F::operator*=(const Point3F &_vec) +{ + x *= _vec.x; + y *= _vec.y; + z *= _vec.z; + return *this; +} + +inline Point3F Point3F::operator/(const Point3F &_vec) const +{ + return Point3F(x / _vec.x, y / _vec.y, z / _vec.z); +} + +inline Point3F& Point3F::operator/=(const Point3F &_vec) +{ + x /= _vec.x; + y /= _vec.y; + z /= _vec.z; + return *this; +} + +inline Point3F Point3F::operator-() const +{ + return Point3F(-x, -y, -z); +} + + +inline Point3F& Point3F::operator=(const Point3D &_vec) +{ + x = (F32)_vec.x; + y = (F32)_vec.y; + z = (F32)_vec.z; + return *this; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Point3D +// +inline Point3D::Point3D() +{ + // +} + +inline Point3D::Point3D(const Point3D& _copy) + : x(_copy.x), y(_copy.y), z(_copy.z) +{ + // +} + +inline Point3D::Point3D(const Point3F& _copy) + : x(_copy.x), y(_copy.y), z(_copy.z) +{ + // +} + +inline Point3D::Point3D(F64 _x, F64 _y, F64 _z) + : x(_x), y(_y), z(_z) +{ + // +} + +inline void Point3D::set(F64 _x, F64 _y, F64 _z) +{ + x = _x; + y = _y; + z = _z; +} + +inline void Point3D::setMin(const Point3D& _test) +{ + x = (_test.x < x) ? _test.x : x; + y = (_test.y < y) ? _test.y : y; + z = (_test.z < z) ? _test.z : z; +} + +inline void Point3D::setMax(const Point3D& _test) +{ + x = (_test.x > x) ? _test.x : x; + y = (_test.y > y) ? _test.y : y; + z = (_test.z > z) ? _test.z : z; +} + +inline void Point3D::interpolate(const Point3D& _from, const Point3D& _to, F64 _factor) +{ + AssertFatal(_factor >= 0.0f && _factor <= 1.0f, "Out of bound interpolation factor"); + m_point3D_interpolate( _from, _to, _factor, *this); +} + +inline bool Point3D::isZero() const +{ + return (x == 0.0f) && (y == 0.0f) && (z == 0.0f); +} + +inline void Point3D::neg() +{ + x = -x; + y = -y; + z = -z; +} + +inline void Point3D::convolve(const Point3D& c) +{ + x *= c.x; + y *= c.y; + z *= c.z; +} + +inline void Point3D::convolveInverse(const Point3D& c) +{ + x /= c.x; + y /= c.y; + z /= c.z; +} + +inline F64 Point3D::lenSquared() const +{ + return (x * x) + (y * y) + (z * z); +} + +inline F64 Point3D::len() const +{ + return mSqrtD(x*x + y*y + z*z); +} + +inline void Point3D::normalize() +{ + m_point3D_normalize(*this); +} + +inline void Point3D::normalize(F64 val) +{ + m_point3D_normalize_f(*this, val); +} + +inline bool Point3D::operator==(const Point3D& _test) const +{ + return (x == _test.x) && (y == _test.y) && (z == _test.z); +} + +inline bool Point3D::operator!=(const Point3D& _test) const +{ + return operator==(_test) == false; +} + +inline Point3D Point3D::operator+(const Point3D& _add) const +{ + return Point3D(x + _add.x, y + _add.y, z + _add.z); +} + + +inline Point3D Point3D::operator-(const Point3D& _rSub) const +{ + return Point3D(x - _rSub.x, y - _rSub.y, z - _rSub.z); +} + +inline Point3D& Point3D::operator+=(const Point3D& _add) +{ + x += _add.x; + y += _add.y; + z += _add.z; + + return *this; +} + +inline Point3D& Point3D::operator-=(const Point3D& _rSub) +{ + x -= _rSub.x; + y -= _rSub.y; + z -= _rSub.z; + + return *this; +} + +inline Point3D Point3D::operator*(F64 _mul) const +{ + return Point3D(x * _mul, y * _mul, z * _mul); +} + +inline Point3D Point3D::operator/(F64 _div) const +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F64 inv = 1.0f / _div; + + return Point3D(x * inv, y * inv, z * inv); +} + +inline Point3D& Point3D::operator*=(F64 _mul) +{ + x *= _mul; + y *= _mul; + z *= _mul; + + return *this; +} + +inline Point3D& Point3D::operator/=(F64 _div) +{ + AssertFatal(_div != 0.0f, "Error, div by zero attempted"); + + F64 inv = 1.0f / _div; + x *= inv; + y *= inv; + z *= inv; + + return *this; +} + +inline Point3D Point3D::operator-() const +{ + return Point3D(-x, -y, -z); +} + +inline Point3F Point3D::toPoint3F() const +{ + return Point3F((F32)x,(F32)y,(F32)z); +} + +//------------------------------------------------------------------- +// Non-Member Operators +//------------------------------------------------------------------- + +inline Point3I operator*(S32 mul, const Point3I& multiplicand) +{ + return multiplicand * mul; +} + +inline Point3F operator*(F32 mul, const Point3F& multiplicand) +{ + return multiplicand * mul; +} + +inline Point3D operator*(F64 mul, const Point3D& multiplicand) +{ + return multiplicand * mul; +} + +inline F32 mDot(const Point3F &p1, const Point3F &p2) +{ + return (p1.x*p2.x + p1.y*p2.y + p1.z*p2.z); +} + +inline F64 mDot(const Point3D &p1, const Point3D &p2) +{ + return (p1.x*p2.x + p1.y*p2.y + p1.z*p2.z); +} + +inline Point3F mCross(const Point3F &a, const Point3F &b) +{ + return Point3F((a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x)); +} + +inline Point3D mCross(const Point3D &a, const Point3D &b) +{ + return Point3D((a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x)); +} + +inline void mCross(const Point3F &a, const Point3F &b, Point3F *res) +{ + *res = mCross(a, b); +} + +inline void mCross(const Point3D &a, const Point3D &b, Point3D *res) +{ + *res = mCross(a, b); +} + +/// Returns the vector normalized. +inline Point3F mNormalize( const Point3F &vec ) +{ + Point3F out( vec ); + out.normalize(); + return out; +} + +/// Returns true if the point is NaN. +inline bool mIsNaN( const Point3F &p ) +{ + return mIsNaN_F( p.x ) || mIsNaN_F( p.y ) || mIsNaN_F( p.z ); +} + +/// Returns a copy of the vector reflected by a normal +inline Point3F mReflect( const Point3F &v, const Point3F &n ) +{ + return v - 2 * n * mDot( v, n ); +} + +/// Returns a perpendicular vector to the unit length input vector. +extern Point3F mPerp( const Point3F &normal ); + +#endif // _MPOINT3_H_ diff --git a/math/mPoint4.h b/math/mPoint4.h new file mode 100644 index 0000000..4a4690b --- /dev/null +++ b/math/mPoint4.h @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MPOINT4_H_ +#define _MPOINT4_H_ + +#ifndef _MMATHFN_H_ +#include "math/mMathFn.h" +#endif + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + + +//------------------------------------------------------------------------------ +/// 4D integer point +/// +/// Uses S32 internally. Currently storage only. +class Point4I +{ + public: + Point4I() {} + Point4I(S32 _x, S32 _y, S32 _z, S32 _w); + + S32 x; + S32 y; + S32 z; + S32 w; + + //-------------------------------------- Public static constants + public: + const static Point4I One; + const static Point4I Zero; +}; + +//------------------------------------------------------------------------------ +/// 4D floating-point point. +/// +/// Uses F32 internally. +/// +/// Useful for representing quaternions and other 4d beasties. +class Point4F +{ + //-------------------------------------- Public data + public: + F32 x; ///< X co-ordinate. + F32 y; ///< Y co-ordinate. + F32 z; ///< Z co-ordinate. + F32 w; ///< W co-ordinate. + + public: + Point4F(); ///< Create an uninitialized point. + Point4F(const Point4F&); ///< Copy constructor. + + /// Create point from coordinates. + Point4F(F32 _x, F32 _y, F32 _z, F32 _w); + + /// Set point's coordinates. + void set(F32 _x, F32 _y, F32 _z, F32 _w); + + /// Interpolate from _pt1 to _pt2, based on _factor. + /// + /// @param _pt1 Starting point. + /// @param _pt2 Ending point. + /// @param _factor Interpolation factor (0.0 .. 1.0). + void interpolate(const Point4F& _pt1, const Point4F& _pt2, F32 _factor); + + operator F32*() { return (&x); } + operator const F32*() const { return &x; } + + F32 len() const; + + Point4F operator/(F32) const; + + Point4F operator*(F32) const; + Point4F operator+(const Point4F&) const; + Point4F& operator+=(const Point4F&); + Point4F operator-(const Point4F&) const; + Point4F operator*(const Point4F&) const; + Point4F& operator*=(const Point4F&); + Point4F& operator=(const Point3F&); + Point4F& operator=(const Point4F&); + + Point3F asPoint3F() const { return Point3F(x,y,z); } + + //-------------------------------------- Public static constants + public: + const static Point4F One; + const static Point4F Zero; +}; + +typedef Point4F Vector4F; ///< Points can be vectors! + +//------------------------------------------------------------------------------ +//-------------------------------------- Point4F +// +inline Point4F::Point4F() +{ +} + +inline Point4F::Point4F(const Point4F& _copy) + : x(_copy.x), y(_copy.y), z(_copy.z), w(_copy.w) +{ +} + +inline Point4F::Point4F(F32 _x, F32 _y, F32 _z, F32 _w) + : x(_x), y(_y), z(_z), w(_w) +{ +} + +inline void Point4F::set(F32 _x, F32 _y, F32 _z, F32 _w) +{ + x = _x; + y = _y; + z = _z; + w = _w; +} + +inline F32 Point4F::len() const +{ + return mSqrt(x*x + y*y + z*z + w*w); +} + +inline void Point4F::interpolate(const Point4F& _from, const Point4F& _to, F32 _factor) +{ + x = (_from.x * (1.0f - _factor)) + (_to.x * _factor); + y = (_from.y * (1.0f - _factor)) + (_to.y * _factor); + z = (_from.z * (1.0f - _factor)) + (_to.z * _factor); + w = (_from.w * (1.0f - _factor)) + (_to.w * _factor); +} + +inline Point4F& Point4F::operator=(const Point3F &_vec) +{ + x = _vec.x; + y = _vec.y; + z = _vec.z; + w = 1.0f; + return *this; +} + +inline Point4F& Point4F::operator=(const Point4F &_vec) +{ + x = _vec.x; + y = _vec.y; + z = _vec.z; + w = _vec.w; + + return *this; +} + +inline Point4F Point4F::operator+(const Point4F& _add) const +{ + return Point4F( x + _add.x, y + _add.y, z + _add.z, w + _add.w ); +} + +inline Point4F& Point4F::operator+=(const Point4F& _add) +{ + x += _add.x; + y += _add.y; + z += _add.z; + w += _add.w; + + return *this; +} + +inline Point4F Point4F::operator-(const Point4F& _rSub) const +{ + return Point4F( x - _rSub.x, y - _rSub.y, z - _rSub.z, w - _rSub.w ); +} + +inline Point4F Point4F::operator*(const Point4F &_vec) const +{ + return Point4F(x * _vec.x, y * _vec.y, z * _vec.z, w * _vec.w); +} + +inline Point4F Point4F::operator*(F32 _mul) const +{ + return Point4F(x * _mul, y * _mul, z * _mul, w * _mul); +} + +inline Point4F Point4F::operator /(F32 t) const +{ + F32 f = 1.0f / t; + return Point4F( x * f, y * f, z * f, w * f ); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- Point4F + +inline Point4I::Point4I(S32 _x, S32 _y, S32 _z, S32 _w) : x(_x), y(_y), z(_z), w(_w) +{ +} + +//------------------------------------------------------------------- +// Non-Member Operators +//------------------------------------------------------------------- + +inline Point4F operator*(F32 mul, const Point4F& multiplicand) +{ + return multiplicand * mul; +} + +inline bool mIsNaN( const Point4F &p ) +{ + return mIsNaN_F( p.x ) || mIsNaN_F( p.y ) || mIsNaN_F( p.z ) || mIsNaN_F( p.w ); +} + +#endif // _MPOINT4_H_ diff --git a/math/mQuadPatch.cpp b/math/mQuadPatch.cpp new file mode 100644 index 0000000..6d63b9e --- /dev/null +++ b/math/mQuadPatch.cpp @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mQuadPatch.h" + + +//****************************************************************************** +// Quadratic spline patch +//****************************************************************************** +QuadPatch::QuadPatch() +{ + setNumReqControlPoints(3); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void QuadPatch::calcABC( const Point3F *points ) +{ + a = points[2] - points[1]; + b = points[1] - points[0]; + c = points[0]; +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void QuadPatch::submitControlPoints( SplCtrlPts &points ) +{ + Parent::submitControlPoints( points ); + calcABC( points.getPoint(0) ); +}; + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void QuadPatch::setControlPoint( Point3F &point, int index ) +{ + ( (SplCtrlPts*) getControlPoints() )->setPoint( point, index ); + calcABC( getControlPoint(0) ); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void QuadPatch::calc( F32 t, Point3F &result ) +{ + F32 t2 = t*t; + result = a*t2 + b*t + c; +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void QuadPatch::calc( Point3F *points, F32 t, Point3F &result ) +{ + calcABC( points ); + calc( t, result ); +} diff --git a/math/mQuadPatch.h b/math/mQuadPatch.h new file mode 100644 index 0000000..284bb97 --- /dev/null +++ b/math/mQuadPatch.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MQUADPATCH_H_ +#define _MQUADPATCH_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MSPLINEPATCH_H_ +#include "math/mSplinePatch.h" +#endif + +//------------------------------------------------------------------------------ +/// Quadratic spline patch. This is a special type of spline that only had 3 control points. +/// @see SplinePatch +class QuadPatch : public SplinePatch +{ + typedef SplinePatch Parent; + +private: + Point3F a, b, c; + + void calcABC( const Point3F *points ); + +public: + + QuadPatch(); + + virtual void calc( F32 t, Point3F &result ); + virtual void calc( Point3F *points, F32 t, Point3F &result ); + virtual void setControlPoint( Point3F &point, int index ); + virtual void submitControlPoints( SplCtrlPts &points ); + + +}; + + + +#endif diff --git a/math/mQuat.cpp b/math/mQuat.cpp new file mode 100644 index 0000000..ad947ee --- /dev/null +++ b/math/mQuat.cpp @@ -0,0 +1,338 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mQuat.h" +#include "math/mAngAxis.h" +#include "math/mMatrix.h" + +const QuatF QuatF::Identity(0.0f,0.0f,0.0f,1.0f); + +QuatF& QuatF::set( const EulerF & e ) +{ + F32 cx, sx; + F32 cy, sy; + F32 cz, sz; + mSinCos( -e.x * 0.5f, sx, cx ); + mSinCos( -e.y * 0.5f, sy, cy ); + mSinCos( -e.z * 0.5f, sz, cz ); + + // Qyaw(z) = [ (0, 0, sin z/2), cos z/2 ] + // Qpitch(x) = [ (sin x/2, 0, 0), cos x/2 ] + // Qroll(y) = [ (0, sin y/2, 0), cos y/2 ] + // this = Qresult = Qyaw*Qpitch*Qroll ZXY + // + // The code that folows is a simplification of: + // roll*=pitch; + // roll*=yaw; + // *this = roll; + F32 cycz, sysz, sycz, cysz; + cycz = cy*cz; + sysz = sy*sz; + sycz = sy*cz; + cysz = cy*sz; + w = cycz*cx + sysz*sx; + x = cycz*sx + sysz*cx; + y = sycz*cx - cysz*sx; + z = cysz*cx - sycz*sx; + + return *this; +} + +QuatF& QuatF::operator *=( const QuatF & b ) +{ + QuatF prod; + prod.w = w * b.w - x * b.x - y * b.y - z * b.z; + prod.x = w * b.x + x * b.w + y * b.z - z * b.y; + prod.y = w * b.y + y * b.w + z * b.x - x * b.z; + prod.z = w * b.z + z * b.w + x * b.y - y * b.x; + *this = prod; + return (*this); +} + +QuatF& QuatF::operator /=( const QuatF & c ) +{ + QuatF temp = c; + return ( (*this) *= temp.inverse() ); +} + +QuatF& QuatF::operator +=( const QuatF & c ) +{ + x += c.x; + y += c.y; + z += c.z; + w += c.w; + return *this; +} + +QuatF& QuatF::operator -=( const QuatF & c ) +{ + x -= c.x; + y -= c.y; + z -= c.z; + w -= c.w; + return *this; +} + +QuatF& QuatF::operator *=( F32 a ) +{ + x *= a; + y *= a; + z *= a; + w *= a; + return *this; +} + +QuatF& QuatF::operator /=( F32 a ) +{ + x /= a; + y /= a; + z /= a; + w /= a; + return *this; +} + +QuatF& QuatF::square() +{ + F32 t = w*2.0f; + w = (w*w) - (x*x + y*y + z*z); + x *= t; + y *= t; + z *= t; + return *this; +} + +QuatF& QuatF::inverse() +{ + F32 magnitude = w*w + x*x + y*y + z*z; + F32 invMagnitude; + if( magnitude == 1.0f ) // special case unit quaternion + { + x = -x; + y = -y; + z = -z; + } + else // else scale + { + if( magnitude == 0.0f ) + invMagnitude = 1.0f; + else + invMagnitude = 1.0f / magnitude; + w *= invMagnitude; + x *= -invMagnitude; + y *= -invMagnitude; + z *= -invMagnitude; + } + return *this; +} + +QuatF & QuatF::set( const AngAxisF & a ) +{ + return set( a.axis, a.angle ); +} + +QuatF & QuatF::set( const Point3F &axis, F32 angle ) +{ + F32 sinHalfAngle, cosHalfAngle; + mSinCos( angle * 0.5f, sinHalfAngle, cosHalfAngle ); + x = axis.x * sinHalfAngle; + y = axis.y * sinHalfAngle; + z = axis.z * sinHalfAngle; + w = cosHalfAngle; + return *this; +} + +QuatF & QuatF::normalize() +{ + F32 l = mSqrt( x*x + y*y + z*z + w*w ); + if( l == 0.0f ) + identity(); + else + { + x /= l; + y /= l; + z /= l; + w /= l; + } + return *this; +} + +#define idx(r,c) (r*4 + c) + +QuatF & QuatF::set( const MatrixF & mat ) +{ + F32 const *m = mat; + + F32 trace = m[idx(0, 0)] + m[idx(1, 1)] + m[idx(2, 2)]; + if (trace > 0.0f) + { + F32 s = mSqrt(trace + F32(1)); + w = s * 0.5f; + s = 0.5f / s; + x = (m[idx(1,2)] - m[idx(2,1)]) * s; + y = (m[idx(2,0)] - m[idx(0,2)]) * s; + z = (m[idx(0,1)] - m[idx(1,0)]) * s; + } + else + { + F32* q = &x; + U32 i = 0; + if (m[idx(1, 1)] > m[idx(0, 0)]) i = 1; + if (m[idx(2, 2)] > m[idx(i, i)]) i = 2; + U32 j = (i + 1) % 3; + U32 k = (j + 1) % 3; + + F32 s = mSqrt((m[idx(i, i)] - (m[idx(j, j)] + m[idx(k, k)])) + 1.0f); + q[i] = s * 0.5f; + s = 0.5f / s; + q[j] = (m[idx(i,j)] + m[idx(j,i)]) * s; + q[k] = (m[idx(i,k)] + m[idx(k, i)]) * s; + w = (m[idx(j,k)] - m[idx(k, j)]) * s; + } + + // Added to resolve issue #2230 + normalize(); + + return *this; +} + +MatrixF * QuatF::setMatrix( MatrixF * mat ) const +{ + if( x*x + y*y + z*z < 10E-20f) // isIdentity() -- substituted code a little more stringent but a lot faster + mat->identity(); + else + m_quatF_set_matF( x, y, z, w, *mat ); + return mat; +} + +QuatF & QuatF::slerp( const QuatF & q, F32 t ) +{ + return interpolate( *this, q, t ); +} + +QuatF & QuatF::extrapolate( const QuatF & q1, const QuatF & q2, F32 t ) +{ + // assert t >= 0 && t <= 1 + // q1 is value at time = 0 + // q2 is value at time = t + // Computes quaternion at time = 1 + F64 flip,cos = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; + if (cos < 0.0) + { + cos = -cos; + flip = -1.0; + } + else + flip = 1.0; + + F64 s1,s2; + if ((1.0 - cos) > 0.00001) + { + F64 om = mAcos(cos) / t; + F64 sd = 1.0 / mSin(t * om); + s1 = flip * mSin(om) * sd; + s2 = mSin((1.0 - t) * om) * sd; + } + else + { + // If quats are very close, do linear interpolation + s1 = flip / t; + s2 = (1.0 - t) / t; + } + + x = F32(s1 * q2.x - s2 * q1.x); + y = F32(s1 * q2.y - s2 * q1.y); + z = F32(s1 * q2.z - s2 * q1.z); + w = F32(s1 * q2.w - s2 * q1.w); + + return *this; +} + +QuatF & QuatF::interpolate( const QuatF & q1, const QuatF & q2, F32 t ) +{ + //----------------------------------- + // Calculate the cosine of the angle: + + double cosOmega = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w; + + //----------------------------------- + // adjust signs if necessary: + + F32 sign2; + if ( cosOmega < 0.0 ) + { + cosOmega = -cosOmega; + sign2 = -1.0f; + } + else + sign2 = 1.0f; + + //----------------------------------- + // calculate interpolating coeffs: + + double scale1, scale2; + if ( (1.0 - cosOmega) > 0.00001 ) + { + // standard case + double omega = mAcos(cosOmega); + double sinOmega = mSin(omega); + scale1 = mSin((1.0 - t) * omega) / sinOmega; + scale2 = sign2 * mSin(t * omega) / sinOmega; + } + else + { + // if quats are very close, just do linear interpolation + scale1 = 1.0 - t; + scale2 = sign2 * t; + } + + + //----------------------------------- + // actually do the interpolation: + + x = F32(scale1 * q1.x + scale2 * q2.x); + y = F32(scale1 * q1.y + scale2 * q2.y); + z = F32(scale1 * q1.z + scale2 * q2.z); + w = F32(scale1 * q1.w + scale2 * q2.w); + return *this; +} + +Point3F & QuatF::mulP(const Point3F& p, Point3F* r) +{ + QuatF qq; + QuatF qi = *this; + QuatF qv( p.x, p.y, p.z, 0.0f); + + qi.inverse(); + qq.mul(qi, qv); + qv.mul(qq, *this); + r->set(qv.x, qv.y, qv.z); + return *r; +} + +QuatF & QuatF::mul( const QuatF &a, const QuatF &b) +{ + AssertFatal( &a != this && &b != this, "QuatF::mul: dest should not be same as source" ); + w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z; + x = a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y; + y = a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z; + z = a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x; + return *this; +} + +QuatF & QuatF::shortestArc( const VectorF &a, const VectorF &b ) +{ + // From Game Programming Gems pg. 217 + VectorF c = mCross( a, b ); + F32 d = mDot( a, b ); + F32 s = mSqrt( ( 1 + d ) * 2 ); + + x = c.x / s; + y = c.y / s; + z = c.z / s; + w = s / 2.f; + + return *this; +} + diff --git a/math/mQuat.h b/math/mQuat.h new file mode 100644 index 0000000..f0d2c62 --- /dev/null +++ b/math/mQuat.h @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MQUAT_H_ +#define _MQUAT_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +class MatrixF; +class AngAxisF; + +//---------------------------------------------------------------------------- +// unit quaternion class: + +class QuatF +{ + //-------------------------------------- Public static constants +public: + const static QuatF Identity; + + public: + F32 x,y,z,w; + + QuatF() {} // no init constructor + QuatF( F32 _x, F32 _y, F32 _z, F32 w ); + QuatF( const Point3F &axis, F32 angle ); + QuatF( const MatrixF & m ); + QuatF( const AngAxisF & a ); + QuatF( const EulerF & e ); + + QuatF& set( F32 _x, F32 _y, F32 _z, F32 _w ); + QuatF& set( const Point3F &axis, F32 angle ); + QuatF& set( const MatrixF & m ); + QuatF& set( const AngAxisF & a ); + QuatF& set( const EulerF & e ); + + int operator ==( const QuatF & c ) const; + int operator !=( const QuatF & c ) const; + QuatF& operator *=( const QuatF & c ); + QuatF& operator /=( const QuatF & c ); + QuatF& operator +=( const QuatF & c ); + QuatF& operator -=( const QuatF & c ); + QuatF& operator *=( F32 a ); + QuatF& operator /=( F32 a ); + + QuatF& square(); + QuatF& neg(); + F32 dot( const QuatF &q ) const; + + MatrixF* setMatrix( MatrixF * mat ) const; + QuatF& normalize(); + QuatF& inverse(); + QuatF& identity(); + int isIdentity() const; + QuatF& slerp( const QuatF & q, F32 t ); + QuatF& extrapolate( const QuatF & q1, const QuatF & q2, F32 t ); + QuatF& interpolate( const QuatF & q1, const QuatF & q2, F32 t ); + F32 angleBetween( const QuatF & q ); + + Point3F& mulP(const Point3F& a, Point3F* b); // r = p * this + QuatF& mul(const QuatF& a, const QuatF& b); // This = a * b + + // Vectors passed in must be normalized + QuatF& shortestArc( const VectorF &normalizedA, const VectorF &normalizedB ); +}; + +// a couple simple utility methods +inline F32 QuatIsEqual(F32 a,F32 b,F32 epsilon = 0.0001f) { return mFabs(a-b) < epsilon; } +inline F32 QuatIsZero(F32 a,F32 epsilon = 0.0001f) { return mFabs(a) < epsilon; } + +//---------------------------------------------------------------------------- +// quaternion implementation: + +inline QuatF::QuatF( F32 _x, F32 _y, F32 _z, F32 _w ) +{ + set( _x, _y, _z, _w ); +} + +inline QuatF::QuatF( const Point3F &axis, F32 angle ) +{ + set( axis, angle ); +} + +inline QuatF::QuatF( const AngAxisF & a ) +{ + set( a ); +} + +inline QuatF::QuatF( const EulerF & e ) +{ + set( e ); +} + +inline QuatF& QuatF::set( F32 _x, F32 _y, F32 _z, F32 _w ) +{ + x = _x; + y = _y; + z = _z; + w = _w; + return *this; +} + +inline int QuatF::operator ==( const QuatF & c ) const +{ + QuatF a = *this; + QuatF b = c; + a.normalize(); + b.normalize(); + b.inverse(); + a *= b; + return a.isIdentity(); +} + +inline int QuatF::isIdentity() const +{ + return QuatIsZero( x ) && QuatIsZero( y ) && QuatIsZero( z ); +} + +inline QuatF& QuatF::identity() +{ + x = 0.0f; + y = 0.0f; + z = 0.0f; + w = 1.0f; + return *this; +} + +inline int QuatF::operator !=( const QuatF & c ) const +{ + return !operator==( c ); +} + +inline QuatF::QuatF( const MatrixF & m ) +{ + set( m ); +} + +inline QuatF& QuatF::neg() +{ + x = -x; + y = -y; + z = -z; + w = -w; + return *this; +} + +inline F32 QuatF::dot( const QuatF &q ) const +{ + return (w*q.w + x*q.x + y*q.y + z*q.z); +} + +inline F32 QuatF::angleBetween( const QuatF & q ) +{ + // angle between to quaternions + return mAcos(x * q.x + y * q.y + z * q.z + w * q.w); +} + +#endif diff --git a/math/mRandom.cpp b/math/mRandom.cpp new file mode 100644 index 0000000..715ea72 --- /dev/null +++ b/math/mRandom.cpp @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mRandom.h" +#include "core/util/journal/journal.h" + +MRandomLCG gRandGen; +U32 gRandGenSeed = 1376312589; + +void MRandomLCG::setGlobalRandSeed(U32 seed) +{ + if (Journal::IsPlaying()) + Journal::Read(&gRandGenSeed); + else + { + gRandGenSeed = seed; + if (Journal::IsRecording()) + Journal::Write(gRandGenSeed); + } + + //now actually set the seed + gRandGen.setSeed(gRandGenSeed); +} + +static U32 msSeed = 1376312589; + +inline U32 generateSeed() +{ + // A very, VERY crude LCG but good enough to generate + // a nice range of seed values + msSeed = (msSeed * 0x015a4e35L) + 1; + msSeed = (msSeed>>16)&0x7fff; + return (msSeed); +} + +//-------------------------------------- +void MRandomGenerator::setSeed() +{ + setSeed(generateSeed()); +} + + +//-------------------------------------- +const S32 MRandomLCG::msQuotient = S32_MAX / 16807L; +const S32 MRandomLCG::msRemainder = S32_MAX % 16807L; + + +//-------------------------------------- +MRandomLCG::MRandomLCG() +{ + setSeed(generateSeed()); +} + +MRandomLCG::MRandomLCG(S32 s) +{ + setSeed(s); +} + + +//-------------------------------------- +void MRandomLCG::setSeed(S32 s) +{ + mSeed = s; +} + + +//-------------------------------------- +U32 MRandomLCG::randI() +{ + if ( mSeed <= msQuotient ) + mSeed = (mSeed * 16807L) % S32_MAX; + else + { + S32 high_part = mSeed / msQuotient; + S32 low_part = mSeed % msQuotient; + + S32 test = (16807L * low_part) - (msRemainder * high_part); + + if ( test > 0 ) + mSeed = test; + else + mSeed = test + S32_MAX; + + } + return mSeed; +} + + + +//-------------------------------------- +MRandomR250::MRandomR250() +{ + setSeed(generateSeed()); +} + +MRandomR250::MRandomR250(S32 s) +{ + setSeed(s); +} + + +//-------------------------------------- +void MRandomR250::setSeed(S32 s) +{ + mSeed = s; + MRandomLCG lcg( s ); + mIndex = 0; + + S32 j; + for (j = 0; j < 250; j++) // fill r250 buffer with bit values + mBuffer[j] = lcg.randI(); + + for (j = 0; j < 250; j++) // set some MSBs to 1 + if ( lcg.randI() > 0x40000000L ) + mBuffer[j] |= 0x80000000L; + + + U32 msb = 0x80000000; // turn on diagonal bit + U32 mask = 0xffffffff; // turn off the leftmost bits + + for (j = 0; j < 32; j++) + { + S32 k = 7 * j + 3; // select a word to operate on + mBuffer[k] &= mask; // turn off bits left of the diagonal + mBuffer[k] |= msb; // turn on the diagonal bit + mask >>= 1; + msb >>= 1; + } +} + + +//-------------------------------------- +U32 MRandomR250::randI() +{ + S32 j; + + // wrap pointer around + if ( mIndex >= 147 ) j = mIndex - 147; + else j = mIndex + 103; + + U32 new_rand = mBuffer[ mIndex ] ^ mBuffer[ j ]; + mBuffer[ mIndex ] = new_rand; + + // increment pointer for next time + if ( mIndex >= 249 ) mIndex = 0; + else mIndex++; + + return new_rand >> 1; +} + + diff --git a/math/mRandom.h b/math/mRandom.h new file mode 100644 index 0000000..49fd27b --- /dev/null +++ b/math/mRandom.h @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MRANDOM_H_ +#define _MRANDOM_H_ + +#ifndef _PLATFORMASSERT_H_ +#include "platform/platformAssert.h" +#endif + + +//-------------------------------------- +/// Base class for random number generators +class MRandomGenerator +{ +protected: + MRandomGenerator() {} + S32 mSeed; + +public: + virtual ~MRandomGenerator() {} + + void setSeed(); + S32 getSeed() { return mSeed; } + virtual void setSeed(S32 s) = 0; + + virtual U32 randI( void ) = 0; ///< 0..2^31 random number generator + virtual F32 randF( void ); ///< 0.0 .. 1.0 F32 random number generator + S32 randI(S32 i, S32 n); ///< i..n integer random number generator + F32 randF(F32 i, F32 n); ///< i..n F32 random number generator +}; + + +//-------------------------------------- +inline F32 MRandomGenerator::randF() +{ + // default: multiply by 1/(2^31) + return F32(randI()) * (1.0f/2147483647.0f); +} + +inline S32 MRandomGenerator::randI(S32 i, S32 n) +{ + AssertFatal(i<=n, "MRandomGenerator::randi: inverted range."); + return (S32)(i + (randI() % (n - i + 1)) ); +} + +inline F32 MRandomGenerator::randF(F32 i, F32 n) +{ + AssertFatal(i<=n, "MRandomGenerator::randf: inverted range."); + return (i + (n - i) * randF()); +} + + +//-------------------------------------- +/// Linear Congruential Method, the "minimal standard generator" +/// +/// Fast, farly good random numbers (better than using rand) +/// +/// @author Park & Miller, 1988, Comm of the ACM, 31(10), pp. 1192-1201 +class MRandomLCG : public MRandomGenerator +{ +protected: + static const S32 msQuotient; + static const S32 msRemainder; + +public: + MRandomLCG(); + MRandomLCG(S32 s); + virtual ~MRandomLCG() {} + + static void setGlobalRandSeed(U32 seed); + + void setSeed(S32 s); +// using MRandomGenerator::randI; + S32 randI(S32 i, S32 n); ///< i..n integer generator + + U32 randI( void ); + +}; + +// Solution to "using" problem. +inline S32 MRandomLCG::randI(S32 i, S32 n) +{ + return( MRandomGenerator::randI(i,n) ); +} + + +//-------------------------------------- +/// Fast, very good random numbers +/// +/// Period = 2^249 +/// +/// Kirkpatrick, S., and E. Stoll, 1981; A Very Fast Shift-Register +/// Sequence Random Number Generator, Journal of Computational Physics, +/// V. 40. +/// +/// Maier, W.L., 1991; A Fast Pseudo Random Number Generator, +/// Dr. Dobb's Journal, May, pp. 152 - 157 +class MRandomR250: public MRandomGenerator +{ +private: + U32 mBuffer[250]; + S32 mIndex; + +public: + MRandomR250(); + MRandomR250(S32 s); + virtual ~MRandomR250() {} + + void setSeed(S32 s); +// using MRandomGenerator::randI; + U32 randI(); +}; + + +typedef MRandomLCG MRandom; + +extern MRandomLCG gRandGen; + + +#endif //_MRANDOM_H_ diff --git a/math/mRect.cpp b/math/mRect.cpp new file mode 100644 index 0000000..2a31f2e --- /dev/null +++ b/math/mRect.cpp @@ -0,0 +1,11 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mRect.h" + + +const RectI RectI::Zero( 0, 0, 0, 0 ); +const RectI RectI::One( 0, 0, 1, 1 ); diff --git a/math/mRect.h b/math/mRect.h new file mode 100644 index 0000000..80efb4e --- /dev/null +++ b/math/mRect.h @@ -0,0 +1,473 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MRECT_H_ +#define _MRECT_H_ + +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif + + +class RectI +{ + public: + Point2I point; + Point2I extent; + + public: + RectI() { } + RectI(const Point2I& in_rMin, + const Point2I& in_rExtent); + RectI(const S32 in_left, const S32 in_top, + const S32 in_width, const S32 in_height); + + void set(const Point2I& in_rMin, const Point2I& in_rExtent); + void set(const S32 in_left, const S32 in_top, + const S32 in_width, const S32 in_height); + + bool intersect(const RectI& clipRect); + bool pointInRect(const Point2I& pt) const; + bool overlaps(RectI R) const; + bool contains(const RectI& R) const; + void inset(S32 x, S32 y); + + void unionRects(const RectI&); + + S32 len_x() const; + S32 len_y() const; + + bool operator==(const RectI&) const; + bool operator!=(const RectI&) const; + + bool isValidRect() const { return (extent.x > 0 && extent.y > 0); } + +public: + + /// A rect of zero extent. + static const RectI Zero; + + /// A rect of 1,1 extent. + static const RectI One; + +}; + +class RectF +{ + public: + Point2F point; + Point2F extent; + + public: + RectF() { } + RectF(const Point2F& in_rMin, + const Point2F& in_rExtent); + RectF(const F32 in_left, const F32 in_top, + const F32 in_width, const F32 in_height); + + void set(const Point2F& in_rMin, const Point2F& in_rExtent); + void set(const F32 in_left, const F32 in_top, + const F32 in_width, const F32 in_height); + + void inset(F32 x, F32 y); + + bool intersect(const RectF& clipRect); + bool pointInRect(const Point2F& pt) const; + bool overlaps(const RectF&) const; + bool contains(const Point2F &p) const + { + Point2F minkowskiP = p - point; + + // If we're beyond origin... + if(minkowskiP.x < 0 || minkowskiP.y < 0) + return false; + + // Or past extent... + if(minkowskiP.x > extent.x || minkowskiP.y > extent.y) + return false; + + // Otherwise we're ok. + return true; + } + bool contains(const RectF& R) const; + + void unionRects( const RectF &rect ); + + F32 len_x() const; + F32 len_y() const; + + bool isValidRect() const { return (extent.x > 0.0f && extent.y > 0.0f); } + inline bool intersectTriangle(const Point2F &a, const Point2F &b, const Point2F &c); + +}; + +class RectD +{ + public: + Point2D point; + Point2D extent; + + public: + RectD() { } + RectD(const Point2D& in_rMin, + const Point2D& in_rExtent); + RectD(const F64 in_left, const F64 in_top, + const F64 in_width, const F64 in_height); + void inset(F64 x, F64 y); + + bool intersect(const RectD& clipRect); + F64 len_x() const; + F64 len_y() const; + + bool isValidRect() const { return (extent.x > 0 && extent.y > 0); } +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- INLINES (RectI) +// +inline +RectI::RectI(const Point2I& in_rMin, + const Point2I& in_rExtent) + : point(in_rMin), + extent(in_rExtent) +{ + // +} + +inline +RectI::RectI(const S32 in_left, const S32 in_top, + const S32 in_width, const S32 in_height) + : point(in_left, in_top), + extent(in_width, in_height) +{ + // +} + +inline void RectI::set(const Point2I& in_rMin, const Point2I& in_rExtent) +{ + point = in_rMin; + extent = in_rExtent; +} + +inline void RectI::set(const S32 in_left, const S32 in_top, + const S32 in_width, const S32 in_height) +{ + point.set(in_left, in_top); + extent.set(in_width, in_height); +} + +inline bool RectI::intersect(const RectI& clipRect) +{ + Point2I bottomL; + bottomL.x = getMin(point.x + extent.x - 1, clipRect.point.x + clipRect.extent.x - 1); + bottomL.y = getMin(point.y + extent.y - 1, clipRect.point.y + clipRect.extent.y - 1); + + point.x = getMax(point.x, clipRect.point.x); + point.y = getMax(point.y, clipRect.point.y); + + extent.x = bottomL.x - point.x + 1; + extent.y = bottomL.y - point.y + 1; + + return isValidRect(); +} + +inline bool RectI::pointInRect(const Point2I &pt) const +{ + return (pt.x >= point.x && pt.x < point.x + extent.x && pt.y >= point.y && pt.y < point.y + extent.y); +} + +inline bool RectI::contains(const RectI& R) const +{ + if (point.x <= R.point.x && point.y <= R.point.y) + if (R.point.x + R.extent.x <= point.x + extent.x) + if (R.point.y + R.extent.y <= point.y + extent.y) + return true; + return false; +} + +inline bool RectI::overlaps(RectI R) const +{ + return R.intersect (* this); +} + +inline void RectI::inset(S32 x, S32 y) +{ + point.x += x; + point.y += y; + extent.x -= 2 * x; + extent.y -= 2 * y; +} + +inline void RectF::inset(F32 x, F32 y) +{ + point.x += x; + point.y += y; + extent.x -= 2.0f * x; + extent.y -= 2.0f * y; +} + +inline void RectD::inset(F64 x, F64 y) +{ + point.x += x; + point.y += y; + extent.x -= 2.0 * x; + extent.y -= 2.0 * y; +} + + +inline void RectI::unionRects(const RectI& u) +{ + S32 minx = point.x < u.point.x ? point.x : u.point.x; + S32 miny = point.y < u.point.y ? point.y : u.point.y; + S32 maxx = (point.x + extent.x) > (u.point.x + u.extent.x) ? (point.x + extent.x) : (u.point.x + u.extent.x); + S32 maxy = (point.y + extent.y) > (u.point.y + u.extent.y) ? (point.y + extent.y) : (u.point.y + u.extent.y); + + point.x = minx; + point.y = miny; + extent.x = maxx - minx; + extent.y = maxy - miny; +} + +inline S32 +RectI::len_x() const +{ + return extent.x; +} + +inline S32 +RectI::len_y() const +{ + return extent.y; +} + +inline bool +RectI::operator==(const RectI& in_rCompare) const +{ + return (point == in_rCompare.point) && (extent == in_rCompare.extent); +} + +inline bool +RectI::operator!=(const RectI& in_rCompare) const +{ + return (operator==(in_rCompare) == false); +} + +//------------------------------------------------------------------------------ +//-------------------------------------- INLINES (RectF) +// +inline +RectF::RectF(const Point2F& in_rMin, + const Point2F& in_rExtent) + : point(in_rMin), + extent(in_rExtent) +{ + // +} + +inline +RectF::RectF(const F32 in_left, const F32 in_top, + const F32 in_width, const F32 in_height) + : point(in_left, in_top), + extent(in_width, in_height) +{ + // +} + +inline F32 +RectF::len_x() const +{ + return extent.x; +} + +inline F32 +RectF::len_y() const +{ + return extent.y; +} + +inline void RectF::set(const Point2F& in_rMin, const Point2F& in_rExtent) +{ + point = in_rMin; + extent = in_rExtent; +} + +inline void RectF::set(const F32 in_left, const F32 in_top, + const F32 in_width, const F32 in_height) +{ + point.set(in_left, in_top); + extent.set(in_width, in_height); +} + +inline bool RectF::intersect(const RectF& clipRect) +{ + Point2F bottomL; + bottomL.x = getMin(point.x + extent.x, clipRect.point.x + clipRect.extent.x); + bottomL.y = getMin(point.y + extent.y, clipRect.point.y + clipRect.extent.y); + + point.x = getMax(point.x, clipRect.point.x); + point.y = getMax(point.y, clipRect.point.y); + + extent.x = bottomL.x - point.x; + extent.y = bottomL.y - point.y; + + return isValidRect(); +} + +inline bool RectF::pointInRect(const Point2F &pt) const +{ + return (pt.x >= point.x && pt.x < point.x + extent.x && pt.y >= point.y && pt.y < point.y + extent.y); +} + +inline bool RectF::overlaps(const RectF& clipRect) const +{ + RectF test = *this; + return test.intersect(clipRect); +} + +inline bool lineToLineIntersect(const Point2F & a1, const Point2F & a2, const Point2F & b1, const Point2F & b2) +{ + const F32 ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x); + const F32 ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x); + const F32 u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + + if(u_b != 0) + { + const F32 ua = ua_t / u_b; + const F32 ub = ub_t / u_b; + + return ( 0.0f <= ua && ua <= 1.0f && 0.0f <= ub && ub <= 1.0f ); + } + else + { + return ( ua_t == 0 || ub_t == 0 ); + } +} + +inline bool RectF::intersectTriangle(const Point2F &a, const Point2F &b, const Point2F &c) +{ + const Point2F topLeft = point; + const Point2F topRight = Point2F( point.x + extent.x, point.y ); + const Point2F bottomLeft = Point2F( point.x, point.y + extent.y ); + const Point2F bottomRight = point + extent; + + // 3 point plus 12 edge tests. + + // Check each triangle point to see if it's in us. + if(contains(a) || contains(b) || contains(b)) + return true; + + // Check a-b against the rect. + if(lineToLineIntersect(topLeft, topRight, a, b)) + return true; + + if(lineToLineIntersect(topRight, bottomRight, a, b)) + return true; + + if(lineToLineIntersect(bottomRight, bottomLeft, a, b)) + return true; + + if(lineToLineIntersect(bottomLeft, topLeft, a, b)) + return true; + + // Check b-c + if(lineToLineIntersect(topLeft, topRight, b, c)) + return true; + + if(lineToLineIntersect(topRight, bottomRight, b, c)) + return true; + + if(lineToLineIntersect(bottomRight, bottomLeft, b, c)) + return true; + + if(lineToLineIntersect(bottomLeft, topLeft, b, c)) + return true; + + // Check c-a + if(lineToLineIntersect(topLeft, topRight, c, a)) + return true; + + if(lineToLineIntersect(topRight, bottomRight, c, a)) + return true; + + if(lineToLineIntersect(bottomRight, bottomLeft, c, a)) + return true; + + if(lineToLineIntersect(bottomLeft, topLeft, c, a)) + return true; + + return false; +} + +inline bool RectF::contains(const RectF& R) const +{ + if (point.x <= R.point.x && point.y <= R.point.y) + if (R.point.x + R.extent.x <= point.x + extent.x) + if (R.point.y + R.extent.y <= point.y + extent.y) + return true; + return false; +} + +inline void RectF::unionRects( const RectF &r ) +{ + F32 minx = point.x < r.point.x ? point.x : r.point.x; + F32 miny = point.y < r.point.y ? point.y : r.point.y; + F32 maxx = (point.x + extent.x) > (r.point.x + r.extent.x) ? (point.x + extent.x) : (r.point.x + r.extent.x); + F32 maxy = (point.y + extent.y) > (r.point.y + r.extent.y) ? (point.y + extent.y) : (r.point.y + r.extent.y); + + point.x = minx; + point.y = miny; + extent.x = maxx - minx; + extent.y = maxy - miny; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- INLINES (RectD) +// +inline +RectD::RectD(const Point2D& in_rMin, + const Point2D& in_rExtent) + : point(in_rMin), + extent(in_rExtent) +{ + // +} + +inline +RectD::RectD(const F64 in_left, const F64 in_top, + const F64 in_width, const F64 in_height) + : point(in_left, in_top), + extent(in_width, in_height) +{ + // +} + +inline F64 +RectD::len_x() const +{ + return extent.x; +} + +inline F64 +RectD::len_y() const +{ + return extent.y; +} + +inline bool RectD::intersect(const RectD& clipRect) +{ + Point2D bottomL; + bottomL.x = getMin(point.x + extent.x, clipRect.point.x + clipRect.extent.x); + bottomL.y = getMin(point.y + extent.y, clipRect.point.y + clipRect.extent.y); + + point.x = getMax(point.x, clipRect.point.x); + point.y = getMax(point.y, clipRect.point.y); + + extent.x = bottomL.x - point.x; + extent.y = bottomL.y - point.y; + + return isValidRect(); +} + +#endif //_RECT_H_ diff --git a/math/mSolver.cpp b/math/mSolver.cpp new file mode 100644 index 0000000..edbb477 --- /dev/null +++ b/math/mSolver.cpp @@ -0,0 +1,236 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mMathFn.h" + +//-------------------------------------------------------------------------- +#define EQN_EPSILON (1e-8) + +static inline void swap(F32 & a, F32 & b) +{ + F32 t = b; + b = a; + a = t; +} + +static inline F32 mCbrt(F32 val) +{ + if(val < 0.f) + return(-mPow(-val, F32(1.f/3.f))); + else + return(mPow(val, F32(1.f/3.f))); +} + +static inline U32 mSolveLinear(F32 a, F32 b, F32 * x) +{ + if(mIsZero(a)) + return(0); + + x[0] = -b/a; + return(1); +} + +static U32 mSolveQuadratic_c(F32 a, F32 b, F32 c, F32 * x) +{ + // really linear? + if(mIsZero(a)) + return(mSolveLinear(b, c, x)); + + // get the descriminant: (b^2 - 4ac) + F32 desc = (b * b) - (4.f * a * c); + + // solutions: + // desc < 0: two imaginary solutions + // desc > 0: two real solutions (b +- sqrt(desc)) / 2a + // desc = 0: one real solution (b / 2a) + if(mIsZero(desc)) + { + x[0] = b / (2.f * a); + return(1); + } + else if(desc > 0.f) + { + F32 sqrdesc = mSqrt(desc); + F32 den = (2.f * a); + x[0] = (-b + sqrdesc) / den; + x[1] = (-b - sqrdesc) / den; + + if(x[1] < x[0]) + swap(x[0], x[1]); + + return(2); + } + else + return(0); +} + +//-------------------------------------------------------------------------- +// from Graphics Gems I: pp 738-742 +U32 mSolveCubic_c(F32 a, F32 b, F32 c, F32 d, F32 * x) +{ + if(mIsZero(a)) + return(mSolveQuadratic(b, c, d, x)); + + // normal form: x^3 + Ax^2 + BX + C = 0 + F32 A = b / a; + F32 B = c / a; + F32 C = d / a; + + // substitute x = y - A/3 to eliminate quadric term and depress + // the cubic equation to (x^3 + px + q = 0) + F32 A2 = A * A; + F32 A3 = A2 * A; + + F32 p = (1.f/3.f) * (((-1.f/3.f) * A2) + B); + F32 q = (1.f/2.f) * (((2.f/27.f) * A3) - ((1.f/3.f) * A * B) + C); + + // use Cardano's fomula to solve the depressed cubic + F32 p3 = p * p * p; + F32 q2 = q * q; + + F32 D = q2 + p3; + + U32 num = 0; + + if(mIsZero(D)) // 1 or 2 solutions + { + if(mIsZero(q)) // 1 triple solution + { + x[0] = 0.f; + num = 1; + } + else // 1 single and 1 double + { + F32 u = mCbrt(-q); + x[0] = 2.f * u; + x[1] = -u; + num = 2; + } + } + else if(D < 0.f) // 3 solutions: casus irreducibilis + { + F32 phi = (1.f/3.f) * mAcos(-q / mSqrt(-p3)); + F32 t = 2.f * mSqrt(-p); + + x[0] = t * mCos(phi); + x[1] = -t * mCos(phi + (M_PI / 3.f)); + x[2] = -t * mCos(phi - (M_PI / 3.f)); + num = 3; + } + else // 1 solution + { + F32 sqrtD = mSqrt(D); + F32 u = mCbrt(sqrtD - q); + F32 v = -mCbrt(sqrtD + q); + + x[0] = u + v; + num = 1; + } + + // resubstitute + F32 sub = (1.f/3.f) * A; + for(U32 i = 0; i < num; i++) + x[i] -= sub; + + // sort the roots + for(S32 j = 0; j < (num - 1); j++) + for(S32 k = j + 1; k < num; k++) + if(x[k] < x[j]) + swap(x[k], x[j]); + + return(num); +} + +//-------------------------------------------------------------------------- +// from Graphics Gems I: pp 738-742 +U32 mSolveQuartic_c(F32 a, F32 b, F32 c, F32 d, F32 e, F32 * x) +{ + if(mIsZero(a)) + return(mSolveCubic(b, c, d, e, x)); + + // normal form: x^4 + ax^3 + bx^2 + cx + d = 0 + F32 A = b / a; + F32 B = c / a; + F32 C = d / a; + F32 D = e / a; + + // substitue x = y - A/4 to eliminate cubic term: + // x^4 + px^2 + qx + r = 0 + F32 A2 = A * A; + F32 A3 = A2 * A; + F32 A4 = A2 * A2; + + F32 p = ((-3.f/8.f) * A2) + B; + F32 q = ((1.f/8.f) * A3) - ((1.f/2.f) * A * B) + C; + F32 r = ((-3.f/256.f) * A4) + ((1.f/16.f) * A2 * B) - ((1.f/4.f) * A * C) + D; + + U32 num = 0; + if(mIsZero(r)) // no absolute term: y(y^3 + py + q) = 0 + { + num = mSolveCubic(1.f, 0.f, p, q, x); + x[num++] = 0.f; + } + else + { + // solve the resolvent cubic + F32 q2 = q * q; + + a = 1.f; + b = (-1.f/2.f) * p; + c = -r; + d = ((1.f/2.f) * r * p) - ((1.f/8.f) * q2); + + mSolveCubic(a, b, c, d, x); + + F32 z = x[0]; + + // build 2 quadratic equations from the one solution + F32 u = (z * z) - r; + F32 v = (2.f * z) - p; + + if(mIsZero(u)) + u = 0.f; + else if(u > 0.f) + u = mSqrt(u); + else + return(0); + + if(mIsZero(v)) + v = 0.f; + else if(v > 0.f) + v = mSqrt(v); + else + return(0); + + // solve the two quadratics + a = 1.f; + b = v; + c = z - u; + num = mSolveQuadratic(a, b, c, x); + + a = 1.f; + b = -v; + c = z + u; + num += mSolveQuadratic(a, b, c, x + num); + } + + // resubstitute + F32 sub = (1.f/4.f) * A; + for(U32 i = 0; i < num; i++) + x[i] -= sub; + + // sort the roots + for(S32 j = 0; j < (num - 1); j++) + for(S32 k = j + 1; k < num; k++) + if(x[k] < x[j]) + swap(x[k], x[j]); + + return(num); +} + +U32 (*mSolveQuadratic)( F32 a, F32 b, F32 c, F32* x ) = mSolveQuadratic_c; +U32 (*mSolveCubic)( F32 a, F32 b, F32 c, F32 d, F32* x ) = mSolveCubic_c; +U32 (*mSolveQuartic)( F32 a, F32 b, F32 c, F32 d, F32 e, F32* x ) = mSolveQuartic_c; diff --git a/math/mSphere.h b/math/mSphere.h new file mode 100644 index 0000000..07038f6 --- /dev/null +++ b/math/mSphere.h @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MSPHERE_H_ +#define _MSPHERE_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +#ifndef _MATHUTILS_H_ +#include "math/mathUtils.h" +#endif + + +class SphereF +{ +public: + Point3F center; + F32 radius; + +public: + SphereF() { } + SphereF( const Point3F& in_rPosition, const F32 in_rRadius ) + : center(in_rPosition), + radius(in_rRadius) + { + if ( radius < 0.0f ) + radius = 0.0f; + } + + bool isContained( const Point3F& in_rContain ) const; + bool isContained( const SphereF& in_rContain ) const; + bool isIntersecting( const SphereF& in_rIntersect ) const; + bool intersectsRay( const Point3F &start, const Point3F &end ) const; + + F32 distanceTo( const Point3F &pt ) const; + F32 squareDistanceTo( const Point3F &pt ) const; +}; + +//-------------------------------------- INLINES +// +inline bool SphereF::isContained( const Point3F& in_rContain ) const +{ + F32 distSq = (center - in_rContain).lenSquared(); + + return (distSq <= (radius * radius)); +} + +inline bool SphereF::isContained( const SphereF& in_rContain ) const +{ + if (radius < in_rContain.radius) + return false; + + // Since our radius is guaranteed to be >= other's, we + // can dodge the sqrt() here. + // + F32 dist = (in_rContain.center - center).lenSquared(); + + return (dist <= ((radius - in_rContain.radius) * + (radius - in_rContain.radius))); +} + +inline bool SphereF::isIntersecting( const SphereF& in_rIntersect ) const +{ + F32 distSq = (in_rIntersect.center - center).lenSquared(); + + return (distSq <= ((in_rIntersect.radius + radius) * + (in_rIntersect.radius + radius))); +} + +inline bool SphereF::intersectsRay( const Point3F &start, const Point3F &end ) const +{ + MatrixF worldToObj( true ); + worldToObj.setPosition( center ); + worldToObj.inverse(); + + VectorF dir = end - start; + dir.normalize(); + + Point3F tmpStart = start; + worldToObj.mulP( tmpStart ); + + //Compute A, B and C coefficients + F32 a = mDot(dir, dir); + F32 b = 2 * mDot(dir, tmpStart); + F32 c = mDot(tmpStart, tmpStart) - (radius * radius); + + //Find discriminant + F32 disc = b * b - 4 * a * c; + + // if discriminant is negative there are no real roots, so return + // false as ray misses sphere + if ( disc < 0 ) + return false; + + // compute q as described above + F32 distSqrt = mSqrt( disc ); + F32 q; + if ( b < 0 ) + q = (-b - distSqrt)/2.0; + else + q = (-b + distSqrt)/2.0; + + // compute t0 and t1 + F32 t0 = q / a; + F32 t1 = c / q; + + // make sure t0 is smaller than t1 + if ( t0 > t1 ) + { + // if t0 is bigger than t1 swap them around + F32 temp = t0; + t0 = t1; + t1 = temp; + } + + // This function doesn't use it + // but t would be the interpolant + // value for getting the exact + // intersection point, by interpolating + // start to end by t. + F32 t = 0; + TORQUE_UNUSED(t); + + // if t1 is less than zero, the object is in the ray's negative direction + // and consequently the ray misses the sphere + if ( t1 < 0 ) + return false; + + // if t0 is less than zero, the intersection point is at t1 + if ( t0 < 0 ) // t = t1; + return true; + else // else the intersection point is at t0 + return true; // t = t0; +} + +inline F32 SphereF::distanceTo( const Point3F &toPt ) const +{ + return (center - toPt).len() - radius; +} + +#endif //_SPHERE_H_ diff --git a/math/mSplinePatch.cpp b/math/mSplinePatch.cpp new file mode 100644 index 0000000..de692c6 --- /dev/null +++ b/math/mSplinePatch.cpp @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mSplinePatch.h" + + +//****************************************************************************** +// Spline control points +//****************************************************************************** +SplCtrlPts::SplCtrlPts() +{ +} + +//------------------------------------------------------------------------------ +// Destructor +//------------------------------------------------------------------------------ +SplCtrlPts::~SplCtrlPts() +{ +} + +//------------------------------------------------------------------------------ +// Get point +//------------------------------------------------------------------------------ +const Point3F * SplCtrlPts::getPoint( U32 pointNum ) +{ + return &mPoints[pointNum]; +} + +//------------------------------------------------------------------------------ +// Set point +//------------------------------------------------------------------------------ +void SplCtrlPts::setPoint( Point3F &point, U32 pointNum ) +{ + mPoints[pointNum] = point; +} + +//------------------------------------------------------------------------------ +// Add point +//------------------------------------------------------------------------------ +void SplCtrlPts::addPoint( Point3F &point ) +{ + mPoints.push_back( point ); +} + +//------------------------------------------------------------------------------ +// Submit control points +//------------------------------------------------------------------------------ +void SplCtrlPts::submitPoints( Point3F *pts, U32 num ) +{ + mPoints.clear(); + + for( int i=0; i mPoints; + +public: + + SplCtrlPts(); + virtual ~SplCtrlPts(); + + /// Gets the number of points in the spline + U32 getNumPoints(){ return mPoints.size(); } + /// Gets the point at the given index + /// @param pointNum index of the point in question + const Point3F * getPoint( U32 pointNum ); + /// Sets a point at the given index to the point given + /// @param point New value for the given point + /// @param pointNum index of the given point + void setPoint( Point3F &point, U32 pointNum ); + /// Adds a point to the end of the spline + /// @param point New point to be added + void addPoint( Point3F &point ); + /// Clears existing points and enters new points + /// @param pts List of points to be added + /// @param num Number of points to be added + void submitPoints( Point3F *pts, U32 num ); +}; + +//------------------------------------------------------------------------------ +// Base class for spline patches +//------------------------------------------------------------------------------ + +/// Base class for spline patches. The only child of this class is QuadPatch. +/// +/// Spline utility class for drawing nice pretty splines. In order to draw a spline, +/// you need to create a SplCtrlPts data structure, which contains all control +/// points on the spline. See SplCtrlPts for more information on how to submit +/// points to the spline utility. Next, submit the SplCtrlPts structure to the +/// spline utility. +/// @code +/// SplinePatch patch; +/// patch.submitControlPoints(ctrlPts); +/// @endcode +/// Next, use the SplineUtil namespace to draw your spline. +/// @code +/// SplineUtil::drawSplineBeam(camPos, numSegments, width, patch[, uvOffset, numTexRep]); +/// @endcode +/// +/// You can also create a SplineBeamInfo structure (SplineUtil::SplineBeamInfo) +/// and just pass the SplineBeamInfo structure to the SplineUtil::drawSplineBeam function. +/// @see SplCtrlPts +/// @see SplineUtil +class SplinePatch +{ +private: + U32 mNumReqControlPoints; + SplCtrlPts mControlPoints; + +protected: + void setNumReqControlPoints( U32 numPts ){ mNumReqControlPoints = numPts; } + +public: + + SplinePatch(); + + U32 getNumReqControlPoints(){ return mNumReqControlPoints; } + const SplCtrlPts * getControlPoints(){ return &mControlPoints; } + const Point3F * getControlPoint( U32 index ){ return mControlPoints.getPoint( index ); } + + // virtuals + virtual void setControlPoint( Point3F &point, int index ); + /// If you have a preconstructed "SplCtrlPts" class, submit it with this function. + /// @see SplCtrlPts + virtual void submitControlPoints( SplCtrlPts &points ){ mControlPoints = points; } + /// Recalc function. Do not call this ever - only SplineUtil needs this. + /// @see SplineUtil + virtual void calc( F32 t, Point3F &result) = 0; + /// Recalc function. Do not call this ever - only SplineUtil needs this. + /// @see SplineUtil + virtual void calc( Point3F *points, F32 t, Point3F &result ) = 0; + +}; + + +#endif diff --git a/math/mTrig.h b/math/mTrig.h new file mode 100644 index 0000000..e9f28ca --- /dev/null +++ b/math/mTrig.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MTRIG_H_ +#define _MTRIG_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MMATHFN_H_ +#include "math/mMathFn.h" +#endif + +//-------------------------------------- External assembly helpers... +// +//#ifdef WIN32 +// +//extern "C" { +// F32 m_reduceAngle_asm(const F32*); +//} +// +//inline F32 +//m_reduceAngle(const F32 in_angle) +//{ +// F32 initial = m_reduceAngle_asm(&in_angle); +// if (initial < 0.0) +// initial += Float_2Pi; +// return initial; +//} +// +//#else +// +//F32 m_reduceAngle(const F32 in_angle); +// +//#endif + + + +#endif //_TRIG_H_ diff --git a/math/mathIO.h b/math/mathIO.h new file mode 100644 index 0000000..5d4f0c1 --- /dev/null +++ b/math/mathIO.h @@ -0,0 +1,231 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATHIO_H_ +#define _MATHIO_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif + +//------------------------------------------------------------------------------ +//-------------------------------------- READING +// +inline bool mathRead(Stream& stream, Point2I* p) +{ + bool success = stream.read(&p->x); + success &= stream.read(&p->y); + return success; +} + +inline bool mathRead(Stream& stream, Point3I* p) +{ + bool success = stream.read(&p->x); + success &= stream.read(&p->y); + success &= stream.read(&p->z); + return success; +} + +inline bool mathRead(Stream& stream, Point2F* p) +{ + bool success = stream.read(&p->x); + success &= stream.read(&p->y); + return success; +} + +inline bool mathRead(Stream& stream, Point3F* p) +{ + bool success = stream.read(&p->x); + success &= stream.read(&p->y); + success &= stream.read(&p->z); + return success; +} + +inline bool mathRead(Stream& stream, Point4F* p) +{ + bool success = stream.read(&p->x); + success &= stream.read(&p->y); + success &= stream.read(&p->z); + success &= stream.read(&p->w); + return success; +} + +inline bool mathRead(Stream& stream, Point3D* p) +{ + bool success = stream.read(&p->x); + success &= stream.read(&p->y); + success &= stream.read(&p->z); + return success; +} + +inline bool mathRead(Stream& stream, PlaneF* p) +{ + bool success = stream.read(&p->x); + success &= stream.read(&p->y); + success &= stream.read(&p->z); + success &= stream.read(&p->d); + return success; +} + +inline bool mathRead(Stream& stream, Box3F* b) +{ + bool success = mathRead(stream, &b->minExtents); + success &= mathRead(stream, &b->maxExtents); + return success; +} + +inline bool mathRead(Stream& stream, SphereF* s) +{ + bool success = mathRead(stream, &s->center); + success &= stream.read(&s->radius); + return success; +} + +inline bool mathRead(Stream& stream, RectI* r) +{ + bool success = mathRead(stream, &r->point); + success &= mathRead(stream, &r->extent); + return success; +} + +inline bool mathRead(Stream& stream, RectF* r) +{ + bool success = mathRead(stream, &r->point); + success &= mathRead(stream, &r->extent); + return success; +} + +inline bool mathRead(Stream& stream, MatrixF* m) +{ + bool success = true; + F32* pm = *m; + for (U32 i = 0; i < 16; i++) + success &= stream.read(&pm[i]); + return success; +} + +inline bool mathRead(Stream& stream, QuatF* q) +{ + bool success = stream.read(&q->x); + success &= stream.read(&q->y); + success &= stream.read(&q->z); + success &= stream.read(&q->w); + return success; +} + +//------------------------------------------------------------------------------ +//-------------------------------------- WRITING +// +inline bool mathWrite(Stream& stream, const Point2I& p) +{ + bool success = stream.write(p.x); + success &= stream.write(p.y); + return success; +} + +inline bool mathWrite(Stream& stream, const Point3I& p) +{ + bool success = stream.write(p.x); + success &= stream.write(p.y); + success &= stream.write(p.z); + return success; +} + +inline bool mathWrite(Stream& stream, const Point2F& p) +{ + bool success = stream.write(p.x); + success &= stream.write(p.y); + return success; +} + +inline bool mathWrite(Stream& stream, const Point3F& p) +{ + bool success = stream.write(p.x); + success &= stream.write(p.y); + success &= stream.write(p.z); + return success; +} + +inline bool mathWrite(Stream& stream, const Point4F& p) +{ + bool success = stream.write(p.x); + success &= stream.write(p.y); + success &= stream.write(p.z); + success &= stream.write(p.w); + return success; +} + +inline bool mathWrite(Stream& stream, const Point3D& p) +{ + bool success = stream.write(p.x); + success &= stream.write(p.y); + success &= stream.write(p.z); + return success; +} + +inline bool mathWrite(Stream& stream, const PlaneF& p) +{ + bool success = stream.write(p.x); + success &= stream.write(p.y); + success &= stream.write(p.z); + success &= stream.write(p.d); + return success; +} + +inline bool mathWrite(Stream& stream, const Box3F& b) +{ + bool success = mathWrite(stream, b.minExtents); + success &= mathWrite(stream, b.maxExtents); + return success; +} + +inline bool mathWrite(Stream& stream, const SphereF& s) +{ + bool success = mathWrite(stream, s.center); + success &= stream.write(s.radius); + return success; +} + +inline bool mathWrite(Stream& stream, const RectI& r) +{ + bool success = mathWrite(stream, r.point); + success &= mathWrite(stream, r.extent); + return success; +} + +inline bool mathWrite(Stream& stream, const RectF& r) +{ + bool success = mathWrite(stream, r.point); + success &= mathWrite(stream, r.extent); + return success; +} + +inline bool mathWrite(Stream& stream, const MatrixF& m) +{ + bool success = true; + const F32* pm = m; + for (U32 i = 0; i < 16; i++) + success &= stream.write(pm[i]); + return success; +} + +inline bool mathWrite(Stream& stream, const QuatF& q) +{ + bool success = stream.write(q.x); + success &= stream.write(q.y); + success &= stream.write(q.z); + success &= stream.write(q.w); + return success; +} + +#endif //_MATHIO_H_ + diff --git a/math/mathTypes.cpp b/math/mathTypes.cpp new file mode 100644 index 0000000..0e6c858 --- /dev/null +++ b/math/mathTypes.cpp @@ -0,0 +1,580 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "console/consoleTypes.h" +#include "console/console.h" +#include "math/mPoint2.h" +#include "math/mPoint3.h" +#include "math/mMatrix.h" +#include "math/mQuat.h" +#include "math/mRect.h" +#include "math/mBox.h" +#include "math/mAngAxis.h" +#include "math/mathTypes.h" +#include "math/mRandom.h" + +//----------------------------------------------------------------------------- +// TypePoint2I +//----------------------------------------------------------------------------- +ConsoleType( Point2I, TypePoint2I, Point2I ) + +ConsoleGetType( TypePoint2I ) +{ + Point2I *pt = (Point2I *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d %d", pt->x, pt->y); + return returnBuffer; +} + +ConsoleSetType( TypePoint2I ) +{ + if(argc == 1) + dSscanf(argv[0], "%d %d", &((Point2I *) dptr)->x, &((Point2I *) dptr)->y); + else if(argc == 2) + *((Point2I *) dptr) = Point2I(dAtoi(argv[0]), dAtoi(argv[1])); + else + Con::printf("Point2I must be set as { x, y } or \"x y\""); +} + +//----------------------------------------------------------------------------- +// TypePoint2F +//----------------------------------------------------------------------------- +ConsoleType( Point2F, TypePoint2F, Point2F ) + +ConsoleGetType( TypePoint2F ) +{ + Point2F *pt = (Point2F *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%.3g %.3g", pt->x, pt->y); + return returnBuffer; +} + +ConsoleSetType( TypePoint2F ) +{ + if(argc == 1) + dSscanf(argv[0], "%g %g", &((Point2F *) dptr)->x, &((Point2F *) dptr)->y); + else if(argc == 2) + *((Point2F *) dptr) = Point2F(dAtof(argv[0]), dAtof(argv[1])); + else + Con::printf("Point2F must be set as { x, y } or \"x y\""); +} + +//----------------------------------------------------------------------------- +// TypePoint3F +//----------------------------------------------------------------------------- +ConsoleType( Point3F, TypePoint3F, Point3F ) +ImplementConsoleTypeCasters(TypePoint3F, Point3F) + +ConsoleGetType( TypePoint3F ) +{ + Point3F *pt = (Point3F *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g", pt->x, pt->y, pt->z); + return returnBuffer; +} + +ConsoleSetType( TypePoint3F ) +{ + if(argc == 1) + dSscanf(argv[0], "%g %g %g", &((Point3F *) dptr)->x, &((Point3F *) dptr)->y, &((Point3F *) dptr)->z); + else if(argc == 3) + *((Point3F *) dptr) = Point3F(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2])); + else + Con::printf("Point3F must be set as { x, y, z } or \"x y z\""); +} + +//----------------------------------------------------------------------------- +// TypePoint4F +//----------------------------------------------------------------------------- +ConsoleType( Point4F, TypePoint4F, Point4F ) + +ConsoleGetType( TypePoint4F ) +{ + Point4F *pt = (Point4F *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g %g", pt->x, pt->y, pt->z, pt->w); + return returnBuffer; +} + +ConsoleSetType( TypePoint4F ) +{ + if(argc == 1) + dSscanf(argv[0], "%g %g %g %g", &((Point4F *) dptr)->x, &((Point4F *) dptr)->y, &((Point4F *) dptr)->z, &((Point4F *) dptr)->w); + else if(argc == 4) + *((Point4F *) dptr) = Point4F(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2]), dAtof(argv[3])); + else + Con::printf("Point4F must be set as { x, y, z, w } or \"x y z w\""); +} + +//----------------------------------------------------------------------------- +// TypeRectI +//----------------------------------------------------------------------------- +ConsoleType( RectI, TypeRectI, RectI ) + +ConsoleGetType( TypeRectI ) +{ + RectI *rect = (RectI *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%d %d %d %d", rect->point.x, rect->point.y, + rect->extent.x, rect->extent.y); + return returnBuffer; +} + +ConsoleSetType( TypeRectI ) +{ + if(argc == 1) + dSscanf(argv[0], "%d %d %d %d", &((RectI *) dptr)->point.x, &((RectI *) dptr)->point.y, + &((RectI *) dptr)->extent.x, &((RectI *) dptr)->extent.y); + else if(argc == 4) + *((RectI *) dptr) = RectI(dAtoi(argv[0]), dAtoi(argv[1]), dAtoi(argv[2]), dAtoi(argv[3])); + else + Con::printf("RectI must be set as { x, y, w, h } or \"x y w h\""); +} + +//----------------------------------------------------------------------------- +// TypeRectF +//----------------------------------------------------------------------------- +ConsoleType( RectF, TypeRectF, RectF ) + +ConsoleGetType( TypeRectF ) +{ + RectF *rect = (RectF *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g %g", rect->point.x, rect->point.y, + rect->extent.x, rect->extent.y); + return returnBuffer; +} + +ConsoleSetType( TypeRectF ) +{ + if(argc == 1) + dSscanf(argv[0], "%g %g %g %g", &((RectF *) dptr)->point.x, &((RectF *) dptr)->point.y, + &((RectF *) dptr)->extent.x, &((RectF *) dptr)->extent.y); + else if(argc == 4) + *((RectF *) dptr) = RectF(dAtof(argv[0]), dAtof(argv[1]), dAtof(argv[2]), dAtof(argv[3])); + else + Con::printf("RectF must be set as { x, y, w, h } or \"x y w h\""); +} + +//----------------------------------------------------------------------------- +// TypeMatrixPosition +//----------------------------------------------------------------------------- +ConsoleType( MatrixPosition, TypeMatrixPosition, MatrixF ) + +ConsoleGetType( TypeMatrixPosition ) +{ + F32 *col = (F32 *) dptr + 3; + char* returnBuffer = Con::getReturnBuffer(256); + if(col[12] == 1.f) + dSprintf(returnBuffer, 256, "%g %g %g", col[0], col[4], col[8]); + else + dSprintf(returnBuffer, 256, "%g %g %g %g", col[0], col[4], col[8], col[12]); + return returnBuffer; +} + +ConsoleSetType( TypeMatrixPosition ) +{ + F32 *col = ((F32 *) dptr) + 3; + if (argc == 1) + { + col[0] = col[4] = col[8] = 0.f; + col[12] = 1.f; + dSscanf(argv[0], "%g %g %g %g", &col[0], &col[4], &col[8], &col[12]); + } + else if (argc <= 4) + { + for (S32 i = 0; i < argc; i++) + col[i << 2] = dAtof(argv[i]); + } + else + Con::printf("Matrix position must be set as { x, y, z, w } or \"x y z w\""); +} + +//----------------------------------------------------------------------------- +// TypeMatrixRotation +//----------------------------------------------------------------------------- +ConsoleType( MatrixRotation, TypeMatrixRotation, MatrixF ) + +ConsoleGetType( TypeMatrixRotation ) +{ + AngAxisF aa(*(MatrixF *) dptr); + aa.axis.normalize(); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g %g",aa.axis.x,aa.axis.y,aa.axis.z,mRadToDeg(aa.angle)); + return returnBuffer; +} + +ConsoleSetType( TypeMatrixRotation ) +{ + // DMM: Note that this will ONLY SET the ULeft 3x3 submatrix. + // + AngAxisF aa(Point3F(0,0,0),0); + if (argc == 1) + { + dSscanf(argv[0], "%g %g %g %g", &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle); + aa.angle = mDegToRad(aa.angle); + } + else if (argc == 4) + { + for (S32 i = 0; i < argc; i++) + ((F32*)&aa)[i] = dAtof(argv[i]); + aa.angle = mDegToRad(aa.angle); + } + else + Con::printf("Matrix rotation must be set as { x, y, z, angle } or \"x y z angle\""); + + // + MatrixF temp; + aa.setMatrix(&temp); + + F32* pDst = *(MatrixF *)dptr; + const F32* pSrc = temp; + for (U32 i = 0; i < 3; i++) + for (U32 j = 0; j < 3; j++) + pDst[i*4 + j] = pSrc[i*4 + j]; +} + + + +//----------------------------------------------------------------------------- +// TypeBox3F +//----------------------------------------------------------------------------- +ConsoleType( Box3F, TypeBox3F, Box3F ) + +ConsoleGetType( TypeBox3F ) +{ + const Box3F* pBox = (const Box3F*)dptr; + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g %g %g %g", + pBox->minExtents.x, pBox->minExtents.y, pBox->minExtents.z, + pBox->maxExtents.x, pBox->maxExtents.y, pBox->maxExtents.z); + + return returnBuffer; +} + +ConsoleSetType( TypeBox3F ) +{ + Box3F* pDst = (Box3F*)dptr; + + if (argc == 1) + { + U32 args = dSscanf(argv[0], "%g %g %g %g %g %g", + &pDst->minExtents.x, &pDst->minExtents.y, &pDst->minExtents.z, + &pDst->maxExtents.x, &pDst->maxExtents.y, &pDst->maxExtents.z); + AssertWarn(args == 6, "Warning, box probably not read properly"); + } + else + { + Con::printf("Box3F must be set as \"xMin yMin zMin xMax yMax zMax\""); + } +} + + +//---------------------------------------------------------------------------- + +ConsoleFunctionGroupBegin( VectorMath, "Vector manipulation functions."); + +ConsoleFunction( VectorAdd, const char*, 3, 3, "(Vector3F a, Vector3F b) Returns a+b.") +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%g %g %g",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%g %g %g",&v2.x,&v2.y,&v2.z); + VectorF v; + v = v1 + v2; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g",v.x,v.y,v.z); + return returnBuffer; +} + +ConsoleFunction( VectorSub, const char*, 3, 3, "(Vector3F a, Vector3F b) Returns a-b.") +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%g %g %g",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%g %g %g",&v2.x,&v2.y,&v2.z); + VectorF v; + v = v1 - v2; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g",v.x,v.y,v.z); + return returnBuffer; +} + +ConsoleFunction( VectorScale, const char*, 3, 3, "(Vector3F a, float scalar) Returns a scaled by scalar (ie, a*scalar).") +{ + VectorF v(0,0,0); + dSscanf(argv[1],"%g %g %g",&v.x,&v.y,&v.z); + v *= dAtof(argv[2]); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g",v.x,v.y,v.z); + return returnBuffer; +} + +ConsoleFunction( VectorNormalize, const char*, 2, 2, "(Vector3F a) Returns a scaled such that length(a) = 1.") +{ + VectorF v(0,0,0); + dSscanf(argv[1],"%g %g %g",&v.x,&v.y,&v.z); + if (v.len() != 0) + v.normalize(); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g",v.x,v.y,v.z); + return returnBuffer; +} + +ConsoleFunction( VectorDot, F32, 3, 3, "(Vector3F a, Vector3F b) Calculate the dot product of a and b.") +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%g %g %g",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%g %g %g",&v2.x,&v2.y,&v2.z); + return mDot(v1,v2); +} + +ConsoleFunction(VectorCross, const char*, 3, 3, "(Vector3F a, Vector3F b) Calculate the cross product of a and b.") +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%g %g %g",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%g %g %g",&v2.x,&v2.y,&v2.z); + VectorF v; + mCross(v1,v2,&v); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g",v.x,v.y,v.z); + return returnBuffer; +} + +ConsoleFunction(VectorDist, F32, 3, 3, "(Vector3F a, Vector3F b) Calculate the distance between a and b.") +{ + VectorF v1(0,0,0),v2(0,0,0); + dSscanf(argv[1],"%g %g %g",&v1.x,&v1.y,&v1.z); + dSscanf(argv[2],"%g %g %g",&v2.x,&v2.y,&v2.z); + VectorF v = v2 - v1; + return mSqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); +} + +ConsoleFunction(VectorLen, F32, 2, 2, "(Vector3F v) Calculate the length of a vector.") +{ + VectorF v(0,0,0); + dSscanf(argv[1],"%g %g %g",&v.x,&v.y,&v.z); + return mSqrt((v.x * v.x) + (v.y * v.y) + (v.z * v.z)); +} + +ConsoleFunction( VectorOrthoBasis, const char*, 2, 2, "(AngAxisF aaf) Create an orthogonal basis from the given vector. Return a matrix.") +{ + AngAxisF aa; + dSscanf(argv[1],"%g %g %g %g", &aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); + MatrixF mat; + aa.setMatrix(&mat); + Point3F col0, col1, col2; + mat.getColumn(0, &col0); + mat.getColumn(1, &col1); + mat.getColumn(2, &col2); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g %g %g %g %g %g %g", + col0.x, col0.y, col0.z, col1.x, col1.y, col1.z, col2.x, col2.y, col2.z); + return returnBuffer; +} + +ConsoleFunction( VectorLerp, const char*, 4, 4, "(Vector3F a, Vector3F b, F32 t ) Return vector interpolated between a / b with time t." ) +{ + VectorF a,b,c; + dSscanf( argv[1], "%g %g %g", &a.x, &a.y, &a.z ); + dSscanf( argv[2], "%g %g %g", &b.x, &b.y, &b.z ); + + c.interpolate( a, b, dAtof( argv[3] ) ); + char *ret = Con::getReturnBuffer(256); + dSprintf( ret, 256, "%g %g %g", c.x, c.y, c.z ); + + return ret; +} + +ConsoleFunctionGroupEnd(VectorMath); + +ConsoleFunctionGroupBegin(MatrixMath, "Matrix manipulation functions."); + +ConsoleFunction( MatrixCreate, const char*, 3, 3, "(Vector3F pos, Vector3F rot) Create a matrix representing the given translation and rotation.") +{ + Point3F pos; + dSscanf(argv[1], "%g %g %g", &pos.x, &pos.y, &pos.z); + + AngAxisF aa(Point3F(0,0,0),0); + dSscanf(argv[2], "%g %g %g %g", &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle); + aa.angle = mDegToRad(aa.angle); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 255, "%g %g %g %g %g %g %g", + pos.x, pos.y, pos.z, + aa.axis.x, aa.axis.y, aa.axis.z, + aa.angle); + return returnBuffer; +} + +ConsoleFunction( MatrixCreateFromEuler, const char*, 2, 2, "(Vector3F e) Create a matrix from the given rotations.") +{ + EulerF rot; + dSscanf(argv[1], "%g %g %g", &rot.x, &rot.y, &rot.z); + + QuatF rotQ(rot); + AngAxisF aa; + aa.set(rotQ); + + char* ret = Con::getReturnBuffer(256); + dSprintf(ret, 255, "0 0 0 %g %g %g %g",aa.axis.x,aa.axis.y,aa.axis.z,aa.angle); + return ret; +} + + +ConsoleFunction( MatrixMultiply, const char*, 3, 3, "(Matrix4F left, Matrix4F right) Multiply the two matrices.") +{ + Point3F pos1; + AngAxisF aa1(Point3F(0,0,0),0); + dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle); + + MatrixF temp1(true); + aa1.setMatrix(&temp1); + temp1.setColumn(3, pos1); + + Point3F pos2; + AngAxisF aa2(Point3F(0,0,0),0); + dSscanf(argv[2], "%g %g %g %g %g %g %g", &pos2.x, &pos2.y, &pos2.z, &aa2.axis.x, &aa2.axis.y, &aa2.axis.z, &aa2.angle); + + MatrixF temp2(true); + aa2.setMatrix(&temp2); + temp2.setColumn(3, pos2); + + temp1.mul(temp2); + + + Point3F pos; + AngAxisF aa(temp1); + + aa.axis.normalize(); + temp1.getColumn(3, &pos); + + char* ret = Con::getReturnBuffer(256); + dSprintf(ret, 255, "%g %g %g %g %g %g %g", + pos.x, pos.y, pos.z, + aa.axis.x, aa.axis.y, aa.axis.z, + aa.angle); + return ret; +} + + +ConsoleFunction( MatrixMulVector, const char*, 3, 3, "(MatrixF xfrm, Point3F vector) Multiply the vector by the transform.") +{ + Point3F pos1; + AngAxisF aa1(Point3F(0,0,0),0); + dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle); + + MatrixF temp1(true); + aa1.setMatrix(&temp1); + temp1.setColumn(3, pos1); + + Point3F vec1; + dSscanf(argv[2], "%g %g %g", &vec1.x, &vec1.y, &vec1.z); + + Point3F result; + temp1.mulV(vec1, &result); + + char* ret = Con::getReturnBuffer(256); + dSprintf(ret, 255, "%g %g %g", result.x, result.y, result.z); + return ret; +} + +ConsoleFunction( MatrixMulPoint, const char*, 3, 3, "(MatrixF xfrm, Point3F pnt) Multiply pnt by xfrm.") +{ + Point3F pos1; + AngAxisF aa1(Point3F(0,0,0),0); + dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle); + + MatrixF temp1(true); + aa1.setMatrix(&temp1); + temp1.setColumn(3, pos1); + + Point3F vec1; + dSscanf(argv[2], "%g %g %g", &vec1.x, &vec1.y, &vec1.z); + + Point3F result; + temp1.mulP(vec1, &result); + + char* ret = Con::getReturnBuffer(256); + dSprintf(ret, 255, "%g %g %g", result.x, result.y, result.z); + return ret; +} + +ConsoleFunctionGroupEnd(MatrixMath); + +//------------------------------------------------------------------------------ + +ConsoleFunction( getBoxCenter, const char*, 2, 2, "(Box b) Get the center point of a box.") +{ + Box3F box; + box.minExtents.set(0,0,0); + box.maxExtents.set(0,0,0); + dSscanf(argv[1],"%g %g %g %g %g %g", + &box.minExtents.x,&box.minExtents.y,&box.minExtents.z, + &box.maxExtents.x,&box.maxExtents.y,&box.maxExtents.z); + Point3F p; + box.getCenter(&p); + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer,256,"%g %g %g",p.x,p.y,p.z); + return returnBuffer; +} + + +//------------------------------------------------------------------------------ +ConsoleFunctionGroupBegin(RandomNumbers, "Functions relating to the generation of random numbers."); + +ConsoleFunction(setRandomSeed, void, 1, 2, "(int seed=-1) Set the current random seed. If no seed is provided, then the current time in ms is used.") +{ + U32 seed = Platform::getRealMilliseconds(); + if (argc == 2) + seed = dAtoi(argv[1]); + MRandomLCG::setGlobalRandSeed(seed); +} + +ConsoleFunction(getRandomSeed, S32, 1, 1, "Return the current random seed.") +{ + return gRandGen.getSeed(); +} + +S32 mRandI(S32 i1, S32 i2) +{ + return gRandGen.randI(i1, i2); +} + +F32 mRandF(F32 f1, F32 f2) +{ + return gRandGen.randF(f1, f2); +} + +F32 mRandF() +{ + return gRandGen.randF(); +} + +ConsoleFunction(getRandom, F32, 1, 3, "(int a=1, int b=0)" + "Get a random number between a and b.") +{ + if (argc == 2) + return F32(gRandGen.randI(0,getMax( dAtoi(argv[1]), 0 ))); + else + { + if (argc == 3) + { + S32 min = dAtoi(argv[1]); + S32 max = dAtoi(argv[2]); + if (min > max) + { + S32 t = min; + min = max; + max = t; + } + return F32(gRandGen.randI(min,max)); + } + } + return gRandGen.randF(); +} + +ConsoleFunctionGroupEnd(RandomNumbers); +//------------------------------------------------------------------------------ \ No newline at end of file diff --git a/math/mathTypes.h b/math/mathTypes.h new file mode 100644 index 0000000..2b34388 --- /dev/null +++ b/math/mathTypes.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATHTYPES_H_ +#define _MATHTYPES_H_ + +#ifndef _DYNAMIC_CONSOLETYPES_H_ +#include "console/dynamicTypes.h" +#endif + +void RegisterMathFunctions(void); + +class Point2I; +class Point2F; +class Point3F; +class Point4F; +class RectI; +class RectF; +class MatrixF; +class Box3F; + +DefineConsoleType( TypePoint2I, Point2I ) +DefineConsoleType( TypePoint2F, Point2F ) +DefineConsoleType( TypePoint3F, Point3F ) +DefineConsoleType( TypePoint4F, Point4F ) +DefineConsoleType( TypeRectI, RectI ) +DefineConsoleType( TypeRectF, RectF ) +DefineConsoleType( TypeMatrixPosition, MatrixF) +DefineConsoleType( TypeMatrixRotation, MatrixF ) +DefineConsoleType( TypeBox3F, Box3F ) + + +#endif diff --git a/math/mathUtils.cpp b/math/mathUtils.cpp new file mode 100644 index 0000000..ed02d40 --- /dev/null +++ b/math/mathUtils.cpp @@ -0,0 +1,979 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/mathUtils.h" + +#include "math/mMath.h" +#include "math/mRandom.h" + +namespace MathUtils +{ + +MRandomLCG sgRandom(0xdeadbeef); ///< Our random number generator. + +// Collide two capsules (sphere swept lines) against each other, reporting only if they intersect or not. +// Based on routine from "Real Time Collision Detection" by Christer Ericson pp 114. +bool capsuleCapsuleOverlap(const Point3F & a1, const Point3F & b1, F32 rad1, const Point3F & a2, const Point3F & b2, F32 rad2) +{ + F32 s,t; + Point3F c1,c2; + F32 dist = segmentSegmentNearest(a1,b1,a2,b2,s,t,c1,c2); + return dist <= (rad1+rad2)*(rad1+rad2); +} + +// Intersect two line segments (p1,q1) and (p2,q2), returning points on lines (c1 & c2) and line parameters (s,t). +// Based on routine from "Real Time Collision Detection" by Christer Ericson pp 149. +F32 segmentSegmentNearest(const Point3F & p1, const Point3F & q1, const Point3F & p2, const Point3F & q2, F32 & s, F32 & t, Point3F & c1, Point3F & c2) +{ + Point3F d1 = q1-p1; + Point3F d2 = q2-p2; + Point3F r = p1-p2; + F32 a = mDot(d1,d1); + F32 e = mDot(d2,d2); + F32 f = mDot(d2,r); + + const F32 EPSILON = 0.001f; + + if (a <= EPSILON && e <= EPSILON) + { + s = t = 0.0f; + c1 = p1; + c2 = p2; + return mDot(c1-c2,c1-c2); + } + + if (a <= EPSILON) + { + s = 0.0f; + t = mClampF(f/e,0.0f,1.0f); + } + else + { + F32 c = mDot(d1,r); + if (e <= EPSILON) + { + t = 0.0f; + s = mClampF(-c/a,0.0f,1.0f); + } + else + { + F32 b = mDot(d1,d2); + F32 denom = a*e-b*b; + if (denom != 0.0f) + s = mClampF((b*f-c*e)/denom,0.0f,1.0f); + else + s = 0.0f; + F32 tnom = b*s+f; + if (tnom < 0.0f) + { + t = 0.0f; + s = mClampF(-c/a,0.0f,1.0f); + } + else if (tnom>e) + { + t = 1.0f; + s = mClampF((b-c)/a,0.0f,1.0f); + } + else + t = tnom/e; + } + } + + c1 = p1 + d1*s; + c2 = p2 + d2*t; + return mDot(c1-c2,c1-c2); +} + +//------------------------------------------------------------------------------ +// Return capsule-sphere overlap. Returns time of first overlap, where time +// is viewed as a sphere of radius radA moving from point A0 to A1. +//------------------------------------------------------------------------------ +bool capsuleSphereNearestOverlap(const Point3F & A0, const Point3F A1, F32 radA, const Point3F & B, F32 radB, F32 & t) +{ + Point3F V = A1-A0; + Point3F A0B = A0-B; + F32 d1 = mDot(A0B,V); + F32 d2 = mDot(A0B,A0B); + F32 d3 = mDot(V,V); + F32 R2 = (radA+radB)*(radA+radB); + if (d20 && t1<1.0f) + { + t=t1; + return true; + } + F32 t2 = (-d1+b24ac)/d3; + if (t2>0 && t2<1.0f) + { + t=t2; + return true; + } + if (t1<0 && t2>0) + { + t=0; + return true; + } + return false; +} + +void getZBiasProjectionMatrix( F32 bias, const Frustum &frustum, MatrixF *outMat, bool rotate ) +{ + + F32 currNearDist = frustum.getNearDist(); + F32 currLeft = frustum.getNearLeft(); + F32 currBottom = frustum.getNearBottom(); + + // Extract the fov and aspect ratio. + F32 fovInRadians = ( mAtan2( currNearDist, currLeft ) * 2.0f ) - M_PI_F; + F32 aspectRatio = currLeft / currBottom; + + F32 left = -(currNearDist + bias) * mTan( fovInRadians / 2.0f ); + F32 right = -left; + F32 bottom = left / aspectRatio; + F32 top = -bottom; + + F32 nearDist, farDist; + + nearDist = currNearDist + bias; + farDist = frustum.getFarDist(); + + Point4F row; + row.x = 2.0 * nearDist / (right-left); + row.y = 0.0; + row.z = 0.0; + row.w = 0.0; + outMat->setRow( 0, row ); + + row.x = 0.0; + row.y = 2.0 * nearDist / (top-bottom); + row.z = 0.0; + row.w = 0.0; + outMat->setRow( 1, row ); + + row.x = (left+right) / (right-left); + row.y = (top+bottom) / (top-bottom); + row.z = farDist / (nearDist-farDist); + row.w = -1.0; + outMat->setRow( 2, row ); + + row.x = 0.0; + row.y = 0.0; + row.z = nearDist * farDist / (nearDist-farDist); + row.w = 0.0; + outMat->setRow( 3, row ); + + outMat->transpose(); + + if ( rotate ) + { + MatrixF rotMat(EulerF( (M_PI_F / 2.0f), 0.0f, 0.0f)); + outMat->mul( rotMat ); + } +} + +MatrixF createOrientFromDir( const Point3F &direction ) +{ + Point3F j = direction; + Point3F k(0.0f, 0.0f, 1.0f); + Point3F i; + + mCross( j, k, &i ); + + if( i.magnitudeSafe() == 0.0f ) + { + i.set( 0.0f, -1.0f, 0.0f ); + } + + i.normalizeSafe(); + mCross( i, j, &k ); + + MatrixF mat( true ); + mat.setColumn( 0, i ); + mat.setColumn( 1, j ); + mat.setColumn( 2, k ); + + return mat; +} + +void getMatrixFromUpVector( const VectorF &up, MatrixF *outMat ) +{ + AssertFatal( up.isUnitLength(), "MathUtils::getMatrixFromUpVector() - Up vector was not normalized!" ); + AssertFatal( outMat, "MathUtils::getMatrixFromUpVector() - Got null output matrix!" ); + AssertFatal( outMat->isAffine(), "MathUtils::getMatrixFromUpVector() - Got uninitialized matrix!" ); + + VectorF forward = mPerp( up ); + VectorF right = mCross( forward, up ); + right.normalize(); + forward = mCross( up, right ); + forward.normalize(); + + outMat->setColumn( 0, right ); + outMat->setColumn( 1, forward ); + outMat->setColumn( 2, up ); +} + +void getMatrixFromForwardVector( const VectorF &forward, MatrixF *outMat ) +{ + AssertFatal( forward.isUnitLength(), "MathUtils::getMatrixFromForwardVector() - Forward vector was not normalized!" ); + AssertFatal( outMat, "MathUtils::getMatrixFromForwardVector() - Got null output matrix!" ); + AssertFatal( outMat->isAffine(), "MathUtils::getMatrixFromForwardVector() - Got uninitialized matrix!" ); + + VectorF up = mPerp( forward ); + VectorF right = mCross( forward, up ); + right.normalize(); + up = mCross( right, forward ); + up.normalize(); + + outMat->setColumn( 0, right ); + outMat->setColumn( 1, forward ); + outMat->setColumn( 2, up ); +} + + +//------------------------------------------------------------------------------ +// Creates random direction given angle parameters similar to the particle system. +// The angles are relative to the specified axis. +//------------------------------------------------------------------------------ +Point3F randomDir( const Point3F &axis, F32 thetaAngleMin, F32 thetaAngleMax, + F32 phiAngleMin, F32 phiAngleMax ) +{ + MatrixF orient = createOrientFromDir( axis ); + Point3F axisx; + orient.getColumn( 0, &axisx ); + + F32 theta = (thetaAngleMax - thetaAngleMin) * sgRandom.randF() + thetaAngleMin; + F32 phi = (phiAngleMax - phiAngleMin) * sgRandom.randF() + phiAngleMin; + + // Both phi and theta are in degs. Create axis angles out of them, and create the + // appropriate rotation matrix... + AngAxisF thetaRot(axisx, theta * (M_PI_F / 180.0f)); + AngAxisF phiRot(axis, phi * (M_PI_F / 180.0f)); + + Point3F ejectionAxis = axis; + + MatrixF temp(true); + thetaRot.setMatrix(&temp); + temp.mulP(ejectionAxis); + phiRot.setMatrix(&temp); + temp.mulP(ejectionAxis); + + return ejectionAxis; +} + + +//------------------------------------------------------------------------------ +// Returns yaw and pitch angles from a given vector. Angles are in RADIANS. +// Assumes north is (0.0, 1.0, 0.0), the degrees move upwards clockwise. +// The range of yaw is 0 - 2PI. The range of pitch is -PI/2 - PI/2. +// ASSUMES Z AXIS IS UP +//------------------------------------------------------------------------------ +void getAnglesFromVector( const VectorF &vec, F32 &yawAng, F32 &pitchAng ) +{ + yawAng = mAtan2( vec.x, vec.y ); + if( yawAng < 0.0f ) + yawAng += M_2PI_F; + + if( mFabs(vec.x) > mFabs(vec.y) ) + pitchAng = mAtan2( mFabs(vec.z), mFabs(vec.x) ); + else + pitchAng = mAtan2( mFabs(vec.z), mFabs(vec.y) ); + if( vec.z < 0.0f ) + pitchAng = -pitchAng; +} + + +//------------------------------------------------------------------------------ +// Returns vector from given yaw and pitch angles. Angles are in RADIANS. +// Assumes north is (0.0, 1.0, 0.0), the degrees move upwards clockwise. +// The range of yaw is 0 - 2PI. The range of pitch is -PI/2 - PI/2. +// ASSUMES Z AXIS IS UP +//------------------------------------------------------------------------------ +void getVectorFromAngles( VectorF &vec, F32 yawAng, F32 pitchAng ) +{ + VectorF pnt( 0.0f, 1.0f, 0.0f ); + + EulerF rot( -pitchAng, 0.0f, 0.0f ); + MatrixF mat( rot ); + + rot.set( 0.0f, 0.0f, yawAng ); + MatrixF mat2( rot ); + + mat.mulV( pnt ); + mat2.mulV( pnt ); + + vec = pnt; +} + + +// transform bounding box making sure to keep original box entirely contained - JK... +void transformBoundingBox(const Box3F &sbox, const MatrixF &mat, const Point3F scale, Box3F &dbox) +{ + Point3F center; + + // set transformed center... + sbox.getCenter(¢er); + center.convolve(scale); + mat.mulP(center); + dbox.minExtents = center; + dbox.maxExtents = center; + + Point3F val; + for(U32 ix=0; ix<2; ix++) + { + if(ix & 0x1) + val.x = sbox.minExtents.x; + else + val.x = sbox.maxExtents.x; + + for(U32 iy=0; iy<2; iy++) + { + if(iy & 0x1) + val.y = sbox.minExtents.y; + else + val.y = sbox.maxExtents.y; + + for(U32 iz=0; iz<2; iz++) + { + if(iz & 0x1) + val.z = sbox.minExtents.z; + else + val.z = sbox.maxExtents.z; + + Point3F v1, v2; + v1 = val; + v1.convolve(scale); + mat.mulP(v1, &v2); + dbox.minExtents.setMin(v2); + dbox.maxExtents.setMax(v2); + } + } + } +} + +bool mProjectWorldToScreen( const Point3F &in, + Point3F *out, + const RectI &view, + const MatrixF &world, + const MatrixF &projection ) +{ + MatrixF worldProjection = projection; + worldProjection.mul(world); + + Point4F temp(in.x,in.y,in.z,1.0f); + worldProjection.mul(temp); + temp.x /= temp.w; + temp.y /= temp.w; + temp.z /= temp.w; + + out->x = (temp.x + 1.0f) / 2.0f * view.extent.x + view.point.x; + out->y = (1.0f - temp.y) / 2.0f * view.extent.y + view.point.y; + out->z = temp.z; + + if ( out->z < 0.0f || out->z > 1.0f || + out->x < (F32)view.point.x || out->x > (F32)view.point.x + (F32)view.extent.x || + out->y < (F32)view.point.y || out->y > (F32)view.point.y + (F32)view.extent.y ) + return false; + + return true; +} + +void mProjectScreenToWorld( const Point3F &in, + Point3F *out, + const RectI &view, + const MatrixF &world, + const MatrixF &projection, + F32 zfar, + F32 znear ) +{ + MatrixF invWorldProjection = projection; + invWorldProjection.mul(world); + invWorldProjection.inverse(); + + Point3F vec; + vec.x = (in.x - view.point.x) * 2.0f / view.extent.x - 1.0f; + vec.y = -(in.y - view.point.y) * 2.0f / view.extent.y + 1.0f; + vec.z = (znear + in.z * (zfar - znear))/zfar; + + invWorldProjection.mulV(vec); + vec *= 1.0f + in.z * zfar; + + invWorldProjection.getColumn(3, out); + (*out) += vec; +} + +bool pointInPolygon( const Point2F *verts, U32 vertCount, const Point2F &testPt ) +{ + U32 i, j, c = 0; + for ( i = 0, j = vertCount-1; i < vertCount; j = i++ ) + { + if ( ( ( verts[i].y > testPt.y ) != ( verts[j].y > testPt.y ) ) && + ( testPt.x < ( verts[j].x - verts[i].x ) * + ( testPt.y - verts[i].y ) / + ( verts[j].y - verts[i].y ) + verts[i].x ) ) + c = !c; + } + + return c != 0; +} + +F32 mTriangleDistance( const Point3F &A, const Point3F &B, const Point3F &C, const Point3F &P, IntersectInfo* info ) +{ + Point3F diff = A - P; + Point3F edge0 = B - A; + Point3F edge1 = C - A; + F32 a00 = edge0.lenSquared(); + F32 a01 = mDot( edge0, edge1 ); + F32 a11 = edge1.lenSquared(); + F32 b0 = mDot( diff, edge0 ); + F32 b1 = mDot( diff, edge1 ); + F32 c = diff.lenSquared(); + F32 det = mFabs(a00*a11-a01*a01); + F32 s = a01*b1-a11*b0; + F32 t = a01*b0-a00*b1; + F32 sqrDistance; + + if (s + t <= det) + { + if (s < 0.0f) + { + if (t < 0.0f) // region 4 + { + if (b0 < 0.0f) + { + t = 0.0f; + if (-b0 >= a00) + { + s = 1.0f; + sqrDistance = a00 + (2.0f)*b0 + c; + } + else + { + s = -b0/a00; + sqrDistance = b0*s + c; + } + } + else + { + s = 0.0f; + if (b1 >= 0.0f) + { + t = 0.0f; + sqrDistance = c; + } + else if (-b1 >= a11) + { + t = 1.0f; + sqrDistance = a11 + 2.0f*b1 + c; + } + else + { + t = -b1/a11; + sqrDistance = b1*t + c; + } + } + } + else // region 3 + { + s = 0.0f; + if (b1 >= 0.0f) + { + t = 0.0f; + sqrDistance = c; + } + else if (-b1 >= a11) + { + t = 1.0f; + sqrDistance = a11 + 2.0f*b1 + c; + } + else + { + t = -b1/a11; + sqrDistance = b1*t + c; + } + } + } + else if (t < 0.0f) // region 5 + { + t = 0.0f; + if (b0 >= 0.0f) + { + s = 0.0f; + sqrDistance = c; + } + else if (-b0 >= a00) + { + s = 1.0f; + sqrDistance = a00 + 2.0f*b0 + c; + } + else + { + s = -b0/a00; + sqrDistance = b0*s + c; + } + } + else // region 0 + { + // minimum at interior point + F32 invDet = 1.0f / det; + s *= invDet; + t *= invDet; + sqrDistance = s * (a00*s + a01*t + 2.0f*b0) + + t * (a01*s + a11*t + 2.0f*b1) + c; + } + } + else + { + F32 tmp0, tmp1, numer, denom; + + if (s < 0.0f) // region 2 + { + tmp0 = a01 + b0; + tmp1 = a11 + b1; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - 2.0f*a01 + a11; + if (numer >= denom) + { + s = 1.0f; + t = 0.0f; + sqrDistance = a00 + 2.0f*b0 + c; + } + else + { + s = numer/denom; + t = 1.0f - s; + sqrDistance = s * (a00*s + a01*t + 2.0f*b0) + + t * (a01*s + a11*t + 2.0f*b1) + c; + } + } + else + { + s = 0.0f; + if (tmp1 <= 0.0f) + { + t = 1.0f; + sqrDistance = a11 + 2.0f*b1 + c; + } + else if (b1 >= 0.0f) + { + t = 0.0f; + sqrDistance = c; + } + else + { + t = -b1/a11; + sqrDistance = b1*t + c; + } + } + } + else if (t < 0.0f) // region 6 + { + tmp0 = a01 + b1; + tmp1 = a00 + b0; + if (tmp1 > tmp0) + { + numer = tmp1 - tmp0; + denom = a00 - 2.0f*a01 + a11; + if (numer >= denom) + { + t = 1.0f; + s = 0.0f; + sqrDistance = a11 + 2.0f*b1 + c; + } + else + { + t = numer/denom; + s = 1.0f - t; + sqrDistance = s * (a00*s + a01*t + 2.0f*b0) + + t * (a01*s + a11*t + 2.0f*b1) + c; + } + } + else + { + t = 0.0f; + if (tmp1 <= 0.0f) + { + s = 1.0f; + sqrDistance = a00 + 2.0f*b0 + c; + } + else if (b0 >= 0.0f) + { + s = 0.0f; + sqrDistance = c; + } + else + { + s = -b0/a00; + sqrDistance = b0*s + c; + } + } + } + else // region 1 + { + numer = a11 + b1 - a01 - b0; + if (numer <= 0.0f) + { + s = 0.0f; + t = 1.0f; + sqrDistance = a11 + 2.0f*b1 + c; + } + else + { + denom = a00 - 2.0f*a01 + a11; + if (numer >= denom) + { + s = 1.0f; + t = 0.0f; + sqrDistance = a00 + 2.0f*b0 + c; + } + else + { + s = numer/denom; + t = 1.0f - s; + sqrDistance = s * (a00*s + a01*t + 2.0f*b0) + + t * (a01*s + a11*t + 2.0f*b1) + c; + } + } + } + } + + // account for numerical round-off error + if (sqrDistance < 0.0f) + sqrDistance = 0.0f; + + // This also calculates the barycentric coordinates and the closest point! + //m_kClosestPoint0 = P; + //m_kClosestPoint1 = A + s*edge0 + t*edge1; + //m_afTriangleBary[1] = s; + //m_afTriangleBary[2] = t; + //m_afTriangleBary[0] = (Real)1.0 - fS - fT; + if(info) + { + info->segment.p0 = P; + info->segment.p1 = A + s*edge0 + t*edge1; + info->bary.x = s; + info->bary.y = t; + info->bary.z = 1.0f - s - t; + } + + return sqrDistance; +} + +Point3F mClosestPointOnSegment( const Point3F &a, const Point3F &b, const Point3F &p ) +{ + Point3F c = p - a; // Vector from a to Point + Point3F v = (b - a); + F32 d = v.len(); // Length of the line segment + v.normalize(); // Unit Vector from a to b + F32 t = mDot( v, c ); // Intersection point Distance from a + + // Check to see if the point is on the line + // if not then return the endpoint + if(t < 0) return a; + if(t > d) return b; + + // get the distance to move from point a + v *= t; + + // move from point a to the nearest point on the segment + return a + v; +} + +void mShortestSegmentBetweenLines( const Line &line0, const Line &line1, LineSegment *outSegment ) +{ + // compute intermediate parameters + Point3F w0 = line0.origin - line1.origin; + F32 a = mDot( line0.direction, line0.direction ); + F32 b = mDot( line0.direction, line1.direction ); + F32 c = mDot( line1.direction, line1.direction ); + F32 d = mDot( line0.direction, w0 ); + F32 e = mDot( line1.direction, w0 ); + + F32 denom = a*c - b*b; + + if ( denom > -0.001f && denom < 0.001f ) + { + outSegment->p0 = line0.origin; + outSegment->p1 = line1.origin + (e/c)*line1.direction; + } + else + { + outSegment->p0 = line0.origin + ((b*e - c*d)/denom)*line0.direction; + outSegment->p1 = line1.origin + ((a*e - b*d)/denom)*line1.direction; + } +} + +U32 greatestCommonDivisor( U32 u, U32 v ) +{ + // http://en.wikipedia.org/wiki/Binary_GCD_algorithm + + int shift; + + /* GCD(0,x) := x */ + if (u == 0 || v == 0) + return u | v; + + /* Left shift := lg K, where K is the greatest power of 2 + dividing both u and v. */ + for (shift = 0; ((u | v) & 1) == 0; ++shift) { + u >>= 1; + v >>= 1; + } + + while ((u & 1) == 0) + u >>= 1; + + /* From here on, u is always odd. */ + do { + while ((v & 1) == 0) /* Loop X */ + v >>= 1; + + /* Now u and v are both odd, so diff(u, v) is even. + Let u = min(u, v), v = diff(u, v)/2. */ + if (u < v) { + v -= u; + } else { + unsigned int diff = u - v; + u = v; + v = diff; + } + v >>= 1; + } while (v != 0); + + return u << shift; +} + + +bool mLineTriangleCollide( const Point3F &p1, const Point3F &p2, + const Point3F &t1, const Point3F &t2, const Point3F &t3, + Point3F *outUVW, F32 *outT ) +{ + VectorF ab = t2 - t1; + VectorF ac = t3 - t1; + VectorF qp = p1 - p2; + + // Compute triangle normal. Can be precalculated or cached if + // intersecting multiple segments against the same triangle + VectorF n = mCross( ab, ac ); + + // Compute denominator d. If d <= 0, segment is parallel to or points + // away from triangle, so exit early + F32 d = mDot( qp, n ); + if ( d <= 0.0f ) + return false; + + // Compute intersection t value of pq with plane of triangle. A ray + // intersects if 0 <= t. Segment intersects iff 0 <= t <= 1. Delay + // dividing by d until intersection has been found to pierce triangle + VectorF ap = p1 - t1; + F32 t = mDot( ap, n ); + if ( t < 0.0f ) + return false; + if ( t > d ) + return false; // For segment; exclude this code line for a ray test + + // Compute barycentric coordinate components and test if within bounds + VectorF e = mCross( qp, ap ); + F32 v = mDot( ac, e ); + if ( v < 0.0f || v > d ) + return false; + F32 w = -mDot( ab, e ); + if ( w < 0.0f || v + w > d ) + return false; + + // Segment/ray intersects triangle. Perform delayed division and + // compute the last barycentric coordinate component + const F32 ood = 1.0f / d; + + if ( outT ) + *outT = t * ood; + + if ( outUVW ) + { + v *= ood; + w *= ood; + outUVW->set( 1.0f - v - w, v, w ); + } + + return true; +} + +bool mRayQuadCollide( const Quad &quad, + const Ray &ray, + Point2F *outUV, + F32 *outT ) +{ + static const F32 eps = F32(10e-6); + + // Rejects rays that are parallel to Q, and rays that intersect the plane of + // Q either on the left of the line V00V01 or on the right of the line V00V10. + + // p01-----eXX-----p11 + // ^ . ^ | + // | . | + // e03 e02 eXX + // | . | + // | . | + // p00-----e01---->p10 + + VectorF e01 = quad.p10 - quad.p00; + VectorF e03 = quad.p01 - quad.p00; + + // If the ray is perfectly perpendicular to e03, which + // represents the entire planes tangent, then the + // result of this cross product (P) will equal e01 + // If it is parallel it will result in a vector opposite e01. + + // If the ray is heading DOWN the cross product will point to the RIGHT + // If the ray is heading UP the cross product will point to the LEFT + // We do not reject based on this though... + // + // In either case cross product will be more parallel to e01 the more + // perpendicular the ray is to e03, and it will be more perpendicular to + // e01 the more parallel it is to e03. + VectorF P = mCross(ray.direction, e03); + + // det can be seen as 'the amount of vector e01 in the direction P' + F32 det = mDot(e01, P); + + // Take a Abs of the dot because we do not care if the ray is heading up or down, + // but if it is perfectly parallel to the quad we want to reject it. + if ( mFabs(det) < eps ) + return false; + + F32 inv_det = 1.0f / det; + + VectorF T = ray.origin - quad.p00; + + // alpha can be seen as 'the amount of vector T in the direction P' + // T is a vector up from the quads corner point 00 to the ray's origin. + // P is the cross product of the ray and e01, which should be "roughly" + // parallel with e03 but might be of either positive or negative magnitude. + F32 alpha = mDot(T, P) * inv_det; + if ( alpha < 0.0f ) + return false; + + // if (alpha > real(1.0)) return false; // Uncomment if VR is used. + + // The cross product of T and e01 should be roughly parallel to e03 + // and of either positive or negative magnitude. + VectorF Q = mCross(T, e01); + F32 beta = mDot(ray.direction, Q) * inv_det; + if ( beta < 0.0f ) + return false; + + // if (beta > real(1.0)) return false; // Uncomment if VR is used. + + if ( alpha + beta > 1.0f ) + //if ( false ) + { + // Rejects rays that intersect the plane of Q either on the + // left of the line V11V10 or on the right of the line V11V01. + + VectorF e23 = quad.p01 - quad.p11; + VectorF e21 = quad.p10 - quad.p11; + VectorF P_prime = mCross(ray.direction, e21); + F32 det_prime = mDot(e23, P_prime); + if ( mFabs(det_prime) < eps) + return false; + F32 inv_det_prime = 1.0f / det_prime; + VectorF T_prime = ray.origin - quad.p11; + F32 alpha_prime = mDot(T_prime, P_prime) * inv_det_prime; + if (alpha_prime < 0.0f) + return false; + VectorF Q_prime = mCross(T_prime, e23); + F32 beta_prime = mDot(ray.direction, Q_prime) * inv_det_prime; + if (beta_prime < 0.0f) + return false; + } + + // Compute the ray parameter of the intersection point, and + // reject the ray if it does not hit Q. + + F32 t = mDot(e03, Q) * inv_det; + if ( t < 0.0f ) + return false; + + + // Compute the barycentric coordinates of the fourth vertex. + // These do not depend on the ray, and can be precomputed + // and stored with the quadrilateral. + + F32 alpha_11, beta_11; + VectorF e02 = quad.p11 - quad.p00; + VectorF n = mCross(e01, e03); + + if ( mFabs(n.x) >= mFabs(n.y) && + mFabs(n.x) >= mFabs(n.z) ) + { + alpha_11 = ( e02.y * e03.z - e02.z * e03.y ) / n.x; + beta_11 = ( e01.y * e02.z - e01.z * e02.y ) / n.x; + } + else if ( mFabs(n.y) >= mFabs(n.x) && + mFabs(n.y) >= mFabs(n.z) ) + { + alpha_11 = ((e02.z * e03.x) - (e02.x * e03.z)) / n.y; + beta_11 = ((e01.z * e02.x) - (e01.x * e02.z)) / n.y; + } + else + { + alpha_11 = ((e02.x * e03.y) - (e02.y * e03.x)) / n.z; + beta_11 = ((e01.x * e02.y) - (e01.y * e02.x)) / n.z; + } + + // Compute the bilinear coordinates of the intersection point. + + F32 u,v; + + if ( mFabs(alpha_11 - 1.0f) < eps) + { + // Q is a trapezium. + u = alpha; + if ( mFabs(beta_11 - 1.0f) < eps) + v = beta; // Q is a parallelogram. + else + v = beta / ((u * (beta_11 - 1.0f)) + 1.0f); // Q is a trapezium. + } + else if ( mFabs(beta_11 - 1.0f) < eps) + { + // Q is a trapezium. + v = beta; + u = alpha / ((v * (alpha_11 - 1.0f)) + 1.0f); + } + else + { + F32 A = 1.0f - beta_11; + F32 B = (alpha * (beta_11 - 1.0f)) + - (beta * (alpha_11 - 1.0f)) - 1.0f; + F32 C = alpha; + F32 D = (B * B) - (4.0f * A * C); + F32 Q = -0.5f * (B + (B < 0.0f ? -1.0f : 1.0f) ) * mSqrt(D); + u = Q / A; + if ((u < 0.0f) || (u > 1.0f)) u = C / Q; + v = beta / ((u * (beta_11 - 1.0f)) + 1.0f); + } + + if ( outUV ) + outUV->set( u, v ); + if ( outT ) + *outT = t; + + return true; +} + +} // end namespace MathUtils diff --git a/math/mathUtils.h b/math/mathUtils.h new file mode 100644 index 0000000..be240b2 --- /dev/null +++ b/math/mathUtils.h @@ -0,0 +1,204 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATHUTILS_H_ +#define _MATHUTILS_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif + +class Box3F; +class RectI; + +/// Miscellaneous math utility functions. +namespace MathUtils +{ + /// A simple helper struct to define a line. + struct Line + { + Point3F origin; + VectorF direction; + }; + + /// A ray is also a line. + typedef Line Ray; + + /// A simple helper struct to define a line segment. + struct LineSegment + { + Point3F p0; + Point3F p1; + }; + + /// A simple helper struct to define a clockwise + /// winding quad. + struct Quad + { + Point3F p00; + Point3F p01; + Point3F p10; + Point3F p11; + }; + + /// Used by mTriangleDistance() to pass along collision info + struct IntersectInfo + { + LineSegment segment; // Starts at given point, ends at collision + Point3F bary; // Barycentric coords for collision + }; + + /// Generates a projection matrix with the near plane + /// moved forward by the bias amount. This function is a helper primarily + /// for working around z-fighting issues. + /// + /// @param bias The amount to move the near plane forward. + /// @param frustum The frustum to generate the new projection matrix from. + /// @param outMat The resulting z-biased projection matrix. Note: It must be initialized before the call. + /// @param rotate Optional parameter specifying whether to rotate the projection matrix similarly to GFXDevice. + /// + void getZBiasProjectionMatrix( F32 bias, const Frustum &frustum, MatrixF *outMat, bool rotate = true ); + + /// Creates orientation matrix from a direction vector. Assumes ( 0 0 1 ) is up. + MatrixF createOrientFromDir( const Point3F &direction ); + + /// Creates an orthonormal basis matrix with the unit length + /// input vector in column 2 (up vector). + /// + /// @param up The non-zero unit length up vector. + /// @param outMat The output matrix which must be initialized prior to the call. + /// + void getMatrixFromUpVector( const VectorF &up, MatrixF *outMat ); + + /// Creates an orthonormal basis matrix with the unit length + /// input vector in column 1 (forward vector). + /// + /// @param forward The non-zero unit length forward vector. + /// @param outMat The output matrix which must be initialized prior to the call. + /// + void getMatrixFromForwardVector( const VectorF &forward, MatrixF *outMat ); + + /// Creates random direction given angle parameters similar to the particle system. + /// + /// The angles are relative to the specified axis. Both phi and theta are in degrees. + Point3F randomDir( const Point3F &axis, F32 thetaAngleMin, F32 thetaAngleMax, F32 phiAngleMin = 0.0, F32 phiAngleMax = 360.0 ); + + /// Returns yaw and pitch angles from a given vector. + /// + /// Angles are in RADIANS. + /// + /// Assumes north is (0.0, 1.0, 0.0), the degrees move upwards clockwise. + /// + /// The range of yaw is 0 - 2PI. The range of pitch is -PI/2 - PI/2. + /// + /// ASSUMES Z AXIS IS UP + void getAnglesFromVector( const VectorF &vec, F32 &yawAng, F32 &pitchAng ); + + /// Returns vector from given yaw and pitch angles. + /// + /// Angles are in RADIANS. + /// + /// Assumes north is (0.0, 1.0, 0.0), the degrees move upwards clockwise. + /// + /// The range of yaw is 0 - 2PI. The range of pitch is -PI/2 - PI/2. + /// + /// ASSUMES Z AXIS IS UP + void getVectorFromAngles( VectorF &vec, F32 yawAng, F32 pitchAng ); + + /// Simple reflection equation - pass in a vector and a normal to reflect off of + inline Point3F reflect( Point3F &inVec, Point3F &norm ) + { + return inVec - norm * ( mDot( inVec, norm ) * 2.0f ); + } + + bool capsuleCapsuleOverlap(const Point3F & a1, const Point3F & b1, F32 radius1, const Point3F & a2, const Point3F & b2, F32 radius2); + bool capsuleSphereNearestOverlap(const Point3F & A0, const Point3F A1, F32 radA, const Point3F & B, F32 radB, F32 & t); + F32 segmentSegmentNearest(const Point3F & p1, const Point3F & q1, const Point3F & p2, const Point3F & q2, F32 & s, F32 & t, Point3F & c1, Point3F & c2); + + // transform bounding box making sure to keep original box entirely contained - JK... + void transformBoundingBox(const Box3F &sbox, const MatrixF &mat, const Point3F scale, Box3F &dbox); + + bool mProjectWorldToScreen( const Point3F &in, + Point3F *out, + const RectI &view, + const MatrixF &world, + const MatrixF &projection ); + + void mProjectScreenToWorld( const Point3F &in, + Point3F *out, + const RectI &view, + const MatrixF &world, + const MatrixF &projection, + F32 far, + F32 near); + + /// Returns true if the test point is within the polygon. + /// @param verts The array of points which forms the polygon. + /// @param vertCount The number of points in the polygon. + /// @param testPt The point to test. + bool pointInPolygon( const Point2F *verts, U32 vertCount, const Point2F &testPt ); + + + /// Calculates the shortest line segment between two lines. + /// + /// @param outSegment The result where .p0 is the point on line0 and .p1 is the point on line1. + /// + void mShortestSegmentBetweenLines( const Line &line0, const Line &line1, LineSegment *outSegment ); + + /// Returns the greatest common divisor of two positive integers. + U32 greatestCommonDivisor( U32 u, U32 v ); + + /// Returns the barycentric coordinates and time of intersection between + /// a line segment and a triangle. + /// + /// @param p1 The first point of the line segment. + /// @param p2 The second point of the line segment. + /// @param t1 The first point of the triangle. + /// @param t2 The second point of the triangle. + /// @param t2 The third point of the triangle. + /// @param outUVW The optional output barycentric coords. + /// @param outT The optional output time of intersection. + /// + /// @return Returns true if a collision occurs. + /// + bool mLineTriangleCollide( const Point3F &p1, const Point3F &p2, + const Point3F &t1, const Point3F &t2, const Point3F &t3, + Point3F *outUVW = NULL, + F32 *outT = NULL ); + + /// Returns the uv coords and time of intersection between + /// a ray and a quad. + /// + /// @param quad The quad. + /// @param ray The ray. + /// @param outUV The optional output UV coords of the intersection. + /// @param outT The optional output time of intersection. + /// + /// @return Returns true if a collision occurs. + /// + bool mRayQuadCollide( const Quad &quad, + const Ray &ray, + Point2F *outUV = NULL, + F32 *outT = NULL ); + + /// Returns the distance between a point and triangle 'abc'. + F32 mTriangleDistance( const Point3F &a, const Point3F &b, const Point3F &c, const Point3F &p, IntersectInfo* info=NULL ); + + /// Returns the closest point on the segment defined by + /// points a, b to the point p. + Point3F mClosestPointOnSegment( const Point3F &a, + const Point3F &b, + const Point3F &p ); + + +} // namespace MathUtils + +#endif // _MATHUTILS_H_ diff --git a/math/util/frustum.cpp b/math/util/frustum.cpp new file mode 100644 index 0000000..a4ab797 --- /dev/null +++ b/math/util/frustum.cpp @@ -0,0 +1,713 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/util/frustum.h" + +#include "math/mMathFn.h" +#include "platform/profiler.h" + +const U32 Frustum::smEdgeIndices[12][2] = +{ + {NearTopLeft, NearTopRight}, + {NearBottomLeft, NearBottomRight}, + {NearTopLeft, NearBottomLeft}, + {NearTopRight, NearBottomRight}, + {FarTopLeft, FarTopRight}, + {FarBottomLeft, FarBottomRight}, + {NearTopLeft, FarTopLeft}, + {NearTopRight, FarTopRight}, + {NearBottomLeft, FarBottomLeft}, + {NearBottomRight, FarBottomRight}, + {FarTopLeft, FarBottomLeft}, + {FarTopRight, FarBottomRight} +}; + +const U32 Frustum::smOBBFaceIndices[6][4] = +{ + {0, 1, 2, 3}, + {4, 5, 6, 7}, + {0, 1, 5, 4}, + {3, 2, 6, 7}, + {0, 4, 7, 3}, + {1, 5, 6, 2} +}; + + +Frustum::Frustum( bool isOrtho, + F32 nearLeft, + F32 nearRight, + F32 nearTop, + F32 nearBottom, + F32 nearDist, + F32 farDist, + const MatrixF &transform ) +{ + mTransform = transform; + + mNearLeft = nearLeft; + mNearRight = nearRight; + mNearTop = nearTop; + mNearBottom = nearBottom; + mNearDist = nearDist; + mFarDist = farDist; + mIsOrtho = isOrtho; + + _updatePlanes(); +} + +Frustum::Frustum( const Frustum& frustum ) +{ + set( frustum ); +} + +Frustum& Frustum::operator =( const Frustum& frustum ) +{ + set( frustum ); + return *this; +} + +void Frustum::set( const Frustum& frustum ) +{ + mNearLeft = frustum.mNearLeft; + mNearRight = frustum.mNearRight; + mNearTop = frustum.mNearTop; + mNearBottom = frustum.mNearBottom; + mNearDist = frustum.mNearDist; + mFarDist = frustum.mFarDist; + mIsOrtho = frustum.mIsOrtho; + + mTransform = frustum.mTransform; + mBounds = frustum.mBounds; + + dMemcpy( mPoints, frustum.mPoints, sizeof( mPoints ) ); + dMemcpy( mPlanes, frustum.mPlanes, sizeof( mPlanes ) ); +} + +void Frustum::set( bool isOrtho, + F32 fovInRadians, + F32 aspectRatio, + F32 nearDist, + F32 farDist, + const MatrixF &transform ) +{ + F32 left = -nearDist * mTan( fovInRadians / 2.0f ); + F32 right = -left; + F32 bottom = left / aspectRatio; + F32 top = -bottom; + + set( isOrtho, left, right, top, bottom, nearDist, farDist, transform ); +} + +void Frustum::set( bool isOrtho, + F32 nearLeft, + F32 nearRight, + F32 nearTop, + F32 nearBottom, + F32 nearDist, + F32 farDist, + const MatrixF &transform ) +{ + mTransform = transform; + + mNearLeft = nearLeft; + mNearRight = nearRight; + mNearTop = nearTop; + mNearBottom = nearBottom; + mNearDist = nearDist; + mFarDist = farDist; + mIsOrtho = isOrtho; + + _updatePlanes(); +} + +void Frustum::set( const MatrixF &projMat, bool normalize ) +{ + // From "Fast Extraction of Viewing Frustum Planes from the World-View-Projection Matrix" + // by Gil Gribb and Klaus Hartmann. + // + // http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf + + // Right clipping plane. + mPlanes[ PlaneRight ].set( projMat[3] - projMat[0], + projMat[7] - projMat[4], + projMat[11] - projMat[8], + projMat[15] - projMat[12] ); + + // Left clipping plane. + mPlanes[ PlaneLeft ].set( projMat[3] + projMat[0], + projMat[7] + projMat[4], + projMat[11] + projMat[8], + projMat[15] + projMat[12] ); + + // Bottom clipping plane. + mPlanes[ PlaneBottom ].set( projMat[3] + projMat[1], + projMat[7] + projMat[5], + projMat[11] + projMat[9], + projMat[15] + projMat[13] ); + + // Top clipping plane. + mPlanes[ PlaneTop ].set( projMat[3] - projMat[1], + projMat[7] - projMat[5], + projMat[11] - projMat[9], + projMat[15] - projMat[13] ); + + // Near clipping plane + mPlanes[ PlaneNear ].set( projMat[3] + projMat[2], + projMat[7] + projMat[6], + projMat[11] + projMat[10], + projMat[15] + projMat[14] ); + + // Far clipping plane. + mPlanes[ PlaneFar ].set( projMat[3] - projMat[2], + projMat[7] - projMat[6], + projMat[11] - projMat[10], + projMat[15] - projMat[14] ); + + if ( normalize ) + { + for ( S32 i=0; i < PlaneCount; i++ ) + mPlanes[i].normalize(); + } + + /* // Create the corner points via plane intersections. + mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneLeft ], &mPoints[ NearTopLeft ] ); + mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneRight ], &mPoints[ NearTopRight ] ); + mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneLeft ], &mPoints[ NearBottomLeft ] ); + mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneRight ], &mPoints[ NearBottomRight ] ); + mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneLeft ], &mPoints[ FarTopLeft ] ); + mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneRight ], &mPoints[ FarTopRight ] ); + mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneLeft ], &mPoints[ FarBottomLeft ] ); + mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneRight ], &mPoints[ FarBottomRight ] ); + */ + // Update the axis aligned bounding box. + _updateBounds(); +} + +void Frustum::setNearDist( F32 nearDist ) +{ + setNearFarDist( nearDist, mFarDist ); +} + +void Frustum::setFarDist( F32 farDist ) +{ + setNearFarDist( mNearDist, farDist ); +} + +void Frustum::setNearFarDist( F32 nearDist, F32 farDist ) +{ + // Extract the fov and aspect ratio. + F32 fovInRadians = ( mAtan2( mNearDist, mNearLeft ) * 2.0f ) - M_PI_F; + F32 aspectRatio = mNearLeft / mNearBottom; + + // Store the inverted state. + bool wasInverted = isInverted(); + + // Recalculate the frustum. + MatrixF xfm( mTransform ); + set( mIsOrtho, fovInRadians, aspectRatio, nearDist, farDist, xfm ); + + // If the cull does not match then we need to invert. + if ( wasInverted != isInverted() ) + invert(); +} + +void Frustum::cropNearFar(F32 newNearDist, F32 newFarDist) +{ + const F32 newOverOld = newNearDist / mNearDist; + + set( mIsOrtho, mNearLeft * newOverOld, mNearRight * newOverOld, mNearTop * newOverOld, mNearBottom * newOverOld, + newNearDist, newFarDist, mTransform); +} + +void Frustum::_updatePlanes() +{ + PROFILE_SCOPE( Frustum_UpdatePlanes ); + + // Build the frustum points in camera space first. + + if ( mIsOrtho ) + { + mPoints[ CameraPosition ].zero(); + mPoints[ NearTopLeft ].set( mNearLeft, mNearDist, mNearTop ); + mPoints[ NearTopRight ].set( mNearRight, mNearDist, mNearTop ); + mPoints[ NearBottomLeft ].set( mNearLeft, mNearDist, mNearBottom ); + mPoints[ NearBottomRight ].set( mNearRight, mNearDist, mNearBottom ); + mPoints[ FarTopLeft ].set( mNearLeft, mFarDist, mNearTop ); + mPoints[ FarTopRight ].set( mNearRight, mFarDist, mNearTop ); + mPoints[ FarBottomLeft ].set( mNearLeft, mFarDist, mNearBottom ); + mPoints[ FarBottomRight ].set( mNearRight, mFarDist, mNearBottom ); + } + else + { + const F32 farOverNear = mFarDist / mNearDist; + + mPoints[ CameraPosition ].zero(); + mPoints[ NearTopLeft ].set( mNearLeft, mNearDist, mNearTop ); + mPoints[ NearTopRight ].set( mNearRight, mNearDist, mNearTop ); + mPoints[ NearBottomLeft ].set( mNearLeft, mNearDist, mNearBottom ); + mPoints[ NearBottomRight ].set( mNearRight, mNearDist, mNearBottom ); + mPoints[ FarTopLeft ].set( mNearLeft * farOverNear, mFarDist, mNearTop * farOverNear ); + mPoints[ FarTopRight ].set( mNearRight * farOverNear, mFarDist, mNearTop * farOverNear ); + mPoints[ FarBottomLeft ].set( mNearLeft * farOverNear, mFarDist, mNearBottom * farOverNear ); + mPoints[ FarBottomRight ].set( mNearRight * farOverNear, mFarDist, mNearBottom * farOverNear ); + } + + // Transform the points into the desired culling space. + for ( S32 i=0; i < PlaneLeftCenter; i++ ) + mTransform.mulP( mPoints[i] ); + + // Update the axis aligned bounding box from + // the newly transformed points. + _updateBounds(); + + // Finally build the planes. + if ( mIsOrtho ) + { + mPlanes[ PlaneLeft ].set( mPoints[ NearBottomLeft ], + mPoints[ FarTopLeft ], + mPoints[ FarBottomLeft ] ); + + mPlanes[ PlaneRight ].set( mPoints[ NearTopRight ], + mPoints[ FarBottomRight ], + mPoints[ FarTopRight ] ); + + mPlanes[ PlaneTop ].set( mPoints[ FarTopRight ], + mPoints[ NearTopLeft ], + mPoints[ NearTopRight ] ); + + mPlanes[ PlaneBottom ].set( mPoints[ NearBottomRight ], + mPoints[ FarBottomLeft ], + mPoints[ FarBottomRight ] ); + + mPlanes[ PlaneNear ].set( mPoints[ NearTopLeft ], + mPoints[ NearBottomLeft ], + mPoints[ NearTopRight ] ); + + mPlanes[ PlaneFar ].set( mPoints[ FarTopLeft ], + mPoints[ FarTopRight ], + mPoints[ FarBottomLeft ] ); + } + else + { + mPlanes[ PlaneLeft ].set( mPoints[ CameraPosition ], + mPoints[ NearTopLeft ], + mPoints[ NearBottomLeft ] ); + + mPlanes[ PlaneRight ].set( mPoints[ CameraPosition ], + mPoints[ NearBottomRight ], + mPoints[ NearTopRight ] ); + + mPlanes[ PlaneTop ].set( mPoints[ CameraPosition ], + mPoints[ NearTopRight ], + mPoints[ NearTopLeft ] ); + + mPlanes[ PlaneBottom ].set( mPoints[ CameraPosition ], + mPoints[ NearBottomLeft ], + mPoints[ NearBottomRight ] ); + + mPlanes[ PlaneNear ].set( mPoints[ NearTopLeft ], + mPoints[ NearBottomLeft ], + mPoints[ NearTopRight ] ); + + mPlanes[ PlaneFar ].set( mPoints[ FarTopLeft ], + mPoints[ FarTopRight ], + mPoints[ FarBottomLeft ] ); + } + + // And now the center points... mostly just used in debug rendering. + mPoints[ PlaneLeftCenter ] = ( mPoints[ NearTopLeft ] + + mPoints[ NearBottomLeft ] + + mPoints[ FarTopLeft ] + + mPoints[ FarBottomLeft ] ) / 4.0f; + + mPoints[ PlaneRightCenter ] = ( mPoints[ NearTopRight ] + + mPoints[ NearBottomRight ] + + mPoints[ FarTopRight ] + + mPoints[ FarBottomRight ] ) / 4.0f; + + mPoints[ PlaneTopCenter ] = ( mPoints[ NearTopLeft ] + + mPoints[ NearTopRight ] + + mPoints[ FarTopLeft ] + + mPoints[ FarTopRight ] ) / 4.0f; + + mPoints[ PlaneBottomCenter ] = ( mPoints[ NearBottomLeft ] + + mPoints[ NearBottomRight ] + + mPoints[ FarBottomLeft ] + + mPoints[ FarBottomRight ] ) / 4.0f; + + mPoints[ PlaneNearCenter ] = ( mPoints[ NearTopLeft ] + + mPoints[ NearTopRight ] + + mPoints[ NearBottomLeft ] + + mPoints[ NearBottomRight ] ) / 4.0f; + + mPoints[ PlaneFarCenter ] = ( mPoints[ FarTopLeft ] + + mPoints[ FarTopRight ] + + mPoints[ FarBottomLeft ] + + mPoints[ FarBottomRight ] ) / 4.0f; +} + +void Frustum::_updateBounds() +{ + // Note this code depends on the order of the + // enum in the header... don't change it. + + mBounds.minExtents.set( mPoints[FirstCornerPoint] ); + mBounds.maxExtents.set( mPoints[FirstCornerPoint] ); + + for ( S32 i=FirstCornerPoint + 1; i <= LastCornerPoint; i++ ) + mBounds.extend( mPoints[i] ); +} + +void Frustum::invert() +{ + for( U32 i = 0; i < PlaneCount; i++ ) + mPlanes[i].invert(); +} + +bool Frustum::isInverted() const +{ + Point3F position; + mTransform.getColumn( 3, &position ); + return mPlanes[ PlaneNear ].whichSide( position ) != PlaneF::Back; +} + +void Frustum::scaleFromCenter( F32 scale ) +{ + // Extract the fov and aspect ratio. + F32 fovInRadians = ( mAtan2( mNearDist, mNearLeft ) * 2.0f ) - M_PI_F; + F32 aspectRatio = mNearLeft / mNearBottom; + + // Now move the near and far planes out. + F32 halfDist = ( mFarDist - mNearDist ) / 2.0f; + mNearDist -= halfDist * ( scale - 1.0f ); + mFarDist += halfDist * ( scale - 1.0f ); + + // Setup the new scaled frustum. + set( mIsOrtho, fovInRadians, aspectRatio, mNearDist, mFarDist, mTransform ); +} + +U32 Frustum::testPlanes( const Box3F &bounds, U32 planeMask, F32 expand ) const +{ + PROFILE_SCOPE( Frustum_TestPlanes ); + + // This is based on the paper "A Faster Overlap Test for a Plane and a Bounding Box" + // by Kenny Hoff. See http://www.cs.unc.edu/~hoff/research/vfculler/boxplane.html + + U32 retMask = 0; + + Point3F minPoint, maxPoint; + F32 maxDot, minDot; + U32 mask; + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + for ( S32 i = 0; i < PlaneCount; i++ ) + { + mask = ( 1 << i ); + + if ( !( planeMask & mask ) ) + continue; + + const PlaneF& plane = mPlanes[i]; + + if ( plane.x > 0 ) + { + maxPoint.x = bounds.maxExtents.x; + minPoint.x = bounds.minExtents.x; + } + else + { + maxPoint.x = bounds.minExtents.x; + minPoint.x = bounds.maxExtents.x; + } + + if ( plane.y > 0 ) + { + maxPoint.y = bounds.maxExtents.y; + minPoint.y = bounds.minExtents.y; + } + else + { + maxPoint.y = bounds.minExtents.y; + minPoint.y = bounds.maxExtents.y; + } + + if ( plane.z > 0 ) + { + maxPoint.z = bounds.maxExtents.z; + minPoint.z = bounds.minExtents.z; + } + else + { + maxPoint.z = bounds.minExtents.z; + minPoint.z = bounds.maxExtents.z; + } + + maxDot = mDot( maxPoint, plane ); + + if ( maxDot <= -( plane.d + expand ) ) + return -1; + + minDot = mDot( minPoint, plane ); + + if ( ( minDot + plane.d ) < 0.0f ) + retMask |= mask; + } + + return retMask; +} + +bool Frustum::edgeFaceIntersect( const Point3F &edgeA, const Point3F &edgeB, + const Point3F &faceA, const Point3F &faceB, const Point3F &faceC, const Point3F &faceD, Point3F *intersection ) const +{ + VectorF edgeAB = edgeB - edgeA; + VectorF edgeAFaceA = faceA - edgeA; + VectorF edgeAFaceB = faceB - edgeA; + VectorF edgeAFaceC = faceC - edgeA; + + VectorF m = mCross( edgeAFaceC, edgeAB ); + F32 v = mDot( edgeAFaceA, m ); + if ( v >= 0.0f ) + { + F32 u = -mDot( edgeAFaceB, m ); + if ( u < 0.0f ) + return false; + + VectorF tmp = mCross( edgeAFaceB, edgeAB ); + F32 w = mDot( edgeAFaceA, tmp ); + if ( w < 0.0f ) + return false; + + F32 denom = 1.0f / (u + v + w ); + u *= denom; + v *= denom; + w *= denom; + + (*intersection) = u * faceA + v * faceB + w * faceC; + } + else + { + VectorF edgeAFaceD = faceD - edgeA; + F32 u = mDot( edgeAFaceD, m ); + if ( u < 0.0f ) + return false; + + VectorF tmp = mCross( edgeAFaceA, edgeAB ); + F32 w = mDot( edgeAFaceD, tmp ); + if ( w < 0.0f ) + return false; + + v = -v; + + F32 denom = 1.0f / ( u + v + w ); + u *= denom; + v *= denom; + w *= denom; + + (*intersection) = u * faceA + v * faceD + w * faceC; + } + + return true; +} + +bool Frustum::intersectOBB( const Point3F *points ) const +{ + U32 bitMask[8] = { 0 }; + + F32 maxDot; + + PROFILE_SCOPE( Frustum_OBB_Intersects ); + + // For each of eight points + // loop through each plane of + // the frustum (near, left, right, bottom, top, far). + for ( U32 j = 0; j < 8; j++ ) + { + for ( S32 i = 0; i < PlaneCount; i++ ) + { + const PlaneF &plane = mPlanes[ i ]; + + // For each plane, test the plane equation + // for the current point. Set the appropriate + // bit for the current point and plane. + maxDot = mDot( points[j], plane ) + plane.d; + if ( maxDot < 0.0f ) + bitMask[j] = bitMask[j] | BIT( i ); + } + } + + // If the logical AND of all + // eight 6-bit sequences is + // not zero, the box is rejected. + if ( (bitMask[0] & + bitMask[1] & + bitMask[2] & + bitMask[3] & + bitMask[4] & + bitMask[5] & + bitMask[6] & + bitMask[7]) != 0 ) + return false; + + // If the box has not been rejected, + // loop through the sequences. + // If any are 0, accept. + for ( U32 i = 0; i < 8; i++ ) + { + if ( !bitMask[i] ) + return true; + } + + // Special cases below. + + // First, check each of the 12 + // frustum edges against the 6 + // OBB faces. On the first occurrance + // of an edge-face intersection, accept. + Point3F intersection( 0, 0, 0 ); + + for ( U32 i = 0; i < 12; i++ ) + { + for ( U32 j = 0; j < 6; j++ ) + { + if ( edgeFaceIntersect( mPoints[smEdgeIndices[i][0]], mPoints[smEdgeIndices[i][1]], + points[smOBBFaceIndices[j][0]], + points[smOBBFaceIndices[j][1]], + points[smOBBFaceIndices[j][2]], + points[smOBBFaceIndices[j][3]], &intersection ) ) + return true; + } + } + + // If all edge-face intersections fail, + // check to see if the frustum origin + // is contained in the OBB. + + // If both special cases fail, reject. + return false; +} + +bool Frustum::pointInFrustum( const Point3F &point ) const +{ + PROFILE_SCOPE( Frustum_PointInFrustum ); + + F32 maxDot; + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + for ( S32 i = 0; i < PlaneCount; i++ ) + { + const PlaneF &plane = mPlanes[ i ]; + + // This is pretty much as optimal as you can + // get for a plane vs point test... + // + // 1 comparision + // 2 multiplies + // 1 adds + // + // It will early out as soon as it detects the + // point is outside one of the planes. + + maxDot = mDot( point, plane ) + plane.d; + if ( maxDot < 0.0f ) + return false; + } + + return true; +} + +bool Frustum::sphereInFrustum( const Point3F ¢er, F32 radius ) const +{ + PROFILE_SCOPE( Frustum_SphereInFrustum ); + + F32 maxDot; + + // Note the planes are ordered left, right, near, + // far, top, bottom for getting early rejections + // from the typical horizontal scene. + for ( S32 i = 0; i < PlaneCount; i++ ) + { + const PlaneF &plane = mPlanes[ i ]; + + // This is pretty much as optimal as you can + // get for a plane vs point test... + // + // 1 comparision + // 2 multiplies + // 1 adds + // 1 negation + // + // It will early out as soon as it detects the + // point is outside one of the planes. + + maxDot = mDot( center, plane ) + plane.d; + if ( maxDot < -radius ) + return false; + } + + return true; +} + +void Frustum::getCenterPoint( Point3F *center ) const +{ + center->set( mPoints[ FirstCornerPoint ] ); + + for ( U32 i = FirstCornerPoint+1; i <= LastCornerPoint; i++ ) + *center += mPoints[ i ]; + + *center /= (F32)CornerPointCount; +} + +void Frustum::mul( const MatrixF& mat ) +{ + mTransform.mul( mat ); + _updatePlanes(); +} + +void Frustum::mulL( const MatrixF& mat ) +{ + MatrixF last( mTransform ); + mTransform.mul( mat, last ); + + _updatePlanes(); +} + +void Frustum::getProjectionMatrix( MatrixF *proj ) const +{ + Point4F row; + row.x = 2.0*mNearDist / (mNearRight-mNearLeft); + row.y = 0.0; + row.z = 0.0; + row.w = 0.0; + proj->setRow( 0, row ); + + row.x = 0.0; + row.y = 2.0 * mNearDist / (mNearTop-mNearBottom); + row.z = 0.0; + row.w = 0.0; + proj->setRow( 1, row ); + + row.x = (mNearLeft+mNearRight) / (mNearRight-mNearLeft); + row.y = (mNearTop+mNearBottom) / (mNearTop-mNearBottom); + row.z = mFarDist / (mNearDist-mFarDist); + row.w = -1.0; + proj->setRow( 2, row ); + + row.x = 0.0; + row.y = 0.0; + row.z = mNearDist * mFarDist / (mNearDist-mFarDist); + row.w = 0.0; + proj->setRow( 3, row ); + + proj->transpose(); + + MatrixF rotMat(EulerF( (M_PI_F / 2.0f), 0.0f, 0.0f)); + proj->mul( rotMat ); +} + diff --git a/math/util/frustum.h b/math/util/frustum.h new file mode 100644 index 0000000..9bfa49e --- /dev/null +++ b/math/util/frustum.h @@ -0,0 +1,391 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MATHUTIL_FRUSTUM_H_ +#define _MATHUTIL_FRUSTUM_H_ + +#ifndef _MBOX_H_ +#include "math/mBox.h" +#endif +#ifndef _MPLANE_H_ +#include "math/mPlane.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _MQUAT_H_ +#include "math/mQuat.h" +#endif + + +/// This class implements a view frustum for use in culling +/// scene objects and rendering the scene graph. +class Frustum +{ + public: + + static const U32 smOBBFaceIndices[6][4]; + static const U32 smEdgeIndices[12][2]; + + /// Used to index into point array. + enum + { + /// The corner points of the frustum. + NearTopLeft, + NearTopRight, + NearBottomLeft, + NearBottomRight, + FarTopLeft, + FarTopRight, + FarBottomLeft, + FarBottomRight, + + /// The apex of the frustum. + CameraPosition, + + /// The center points of the frustum planes. + PlaneLeftCenter, + PlaneRightCenter, + PlaneTopCenter, + PlaneBottomCenter, + PlaneNearCenter, + PlaneFarCenter, + + /// The total number of frustum points. + PointCount, + + FirstCornerPoint = NearTopLeft, + LastCornerPoint = FarBottomRight, + CornerPointCount = 8 + }; + + /// Used to index into the plane array. + /// + /// Note that these are ordered for optimal early + /// rejection. By culling with the left and right + /// planes first you cull most of the objects in + /// the typical horizontal scene. + enum + { + PlaneLeft, + PlaneRight, + PlaneNear, + PlaneFar, + PlaneTop, + PlaneBottom, + + /// The total number of frustum planes. + PlaneCount + }; + + /// Used to mask out planes for testing. + enum + { + PlaneMaskLeft = ( 1 << PlaneLeft ), + PlaneMaskRight = ( 1 << PlaneRight ), + PlaneMaskTop = ( 1 << PlaneTop ), + PlaneMaskBottom = ( 1 << PlaneBottom ), + PlaneMaskNear = ( 1 << PlaneNear ), + PlaneMaskFar = ( 1 << PlaneFar ), + + PlaneMaskAll = 0xFFFFFFFF, + }; + + protected: + /// The clipping planes used during culling. +#ifdef TORQUE_COMPILER_VISUALC + __declspec(align(16)) +#endif + PlaneF mPlanes[ PlaneCount ] +#if defined(TORQUE_COMPILER_GCC) + __attribute__ ((aligned (16))) +#endif + ; + + /// The points of the frustum that make up + /// the the clipping planes. + Point3F mPoints[ PointCount ]; + + /// Determines whether this Frustum + /// is orthographic or perspective. + bool mIsOrtho; + + /// The axis aligned bounding box which contains + /// the extents of the frustum. + Box3F mBounds; + + /// Used to transform the frustum points from camera + /// space into the desired clipping space. + MatrixF mTransform; + + /// The size of the near plane used to generate + /// the frustum points and planes. + F32 mNearLeft; + F32 mNearRight; + F32 mNearTop; + F32 mNearBottom; + F32 mNearDist; + F32 mFarDist; + + /// Called to initialize the planes after frustum + /// settings are changed. + void _updatePlanes(); + + /// Called to recalculate the bounds from the frustum + /// points when the planes are updated or transformed. + void _updateBounds(); + + public: + + /// @name Constructors + /// + /// @{ + + /// + Frustum( bool orthographic = false, + F32 nearLeft = -1.0f, + F32 nearRight = 1.0f, + F32 nearTop = -1.0f, + F32 nearBottom = 1.0f, + F32 nearDist = 0.1f, + F32 farDist = 1.0f, + const MatrixF &transform = MatrixF( true ) ); + + /// Copy constructor. + Frustum( const Frustum &frustum ); + + /// @} + + + /// @name Operators + /// + /// @{ + + /// Convenience operator for copying frustums. + Frustum& operator =( const Frustum& frustum ); + + // Comparison operators + bool operator==( const Frustum& frustum ) const; + bool operator!=( const Frustum& frustum ) const; + + /// @} + + + /// @name Initialization + /// + /// Functions used to initialize the frustum. + /// + /// @{ + + /// Set the frustum via a copy. + void set( const Frustum &frustum ); + + /// Sets the frustum from the field of view, screen aspect + /// ratio, and the near and far distances. You can pass an + /// matrix to transform the frustum. + void set( bool isOrtho, + F32 fovInRadians, + F32 aspectRatio, + F32 nearDist, + F32 farDist, + const MatrixF &mat = MatrixF( true ) ); + + /// Sets the frustum from the near plane dimensions and + /// near and far distances. + void set( bool isOrtho, + F32 nearLeft, + F32 nearRight, + F32 nearTop, + F32 nearBottom, + F32 nearDist, + F32 farDist, + const MatrixF &transform = MatrixF( true ) ); + + /// Sets the frustum by extracting the planes from a projection, + /// view-projection, or world-view-projection matrix. + void set( const MatrixF& projMatrix, bool normalize ); + + /// Changes the near distance of the frustum. + void setNearDist( F32 nearDist ); + + /// Changes the far distance of the frustum. + void setFarDist( F32 farDist ); + + /// Changes the near and far distance of the frustum. + void setNearFarDist( F32 nearDist, F32 farDist ); + + /// + void cropNearFar(F32 newNearDist, F32 newFarDist); + + /// Returns the far clip distance used to create + /// the frustum planes. + F32 getFarDist() const { return mFarDist; } + + /// Returns the far clip distance used to create + /// the frustum planes. + F32 getNearDist() const { return mNearDist; } + + /// + F32 getNearLeft() const { return mNearLeft; } + + /// + F32 getNearRight() const { return mNearRight; } + + /// + F32 getNearTop() const { return mNearTop; } + + /// + F32 getNearBottom() const { return mNearBottom; } + + /// @} + + + /// @name Transformation + /// + /// These functions for transforming the frustum from + /// one space to another. + /// + /// @{ + + /// Sets a new transform for the frustum. + void setTransform( const MatrixF &transform ); + + /// Returns the current transform matrix for the frustum. + const MatrixF& getTransform() const; + + /// Scales up the frustum from its center point. + void scaleFromCenter( F32 scale ); + + /// Transforms the frustum by F = F * mat. + void mul( const MatrixF &mat ); + + /// Transforms the frustum by F = mat * F. + void mulL( const MatrixF &mat ); + + /// Flip the plane normals which has the result + /// of reversing the culling results. + void invert(); + + /// Returns true if the frustum planes point outwards. + bool isInverted() const; + + + /// Returns the origin point of the frustum. + const Point3F& getPosition() const; + + /// Returns the axis aligned bounding box of the frustum + /// points typically used for early rejection. + const Box3F& getBounds() const; + + /// Generates a projection matrix from the frustum. + void getProjectionMatrix( MatrixF *proj ) const; + + /// @} + + + /// @name Culling + /// + /// Various functions used to cull shapes against the view + /// frustum. For best results always perform an overlap test + /// against the frustum bounds before using these routines. + /// + /// @{ + + /// Returns true if the box is completely within or intersecting + /// one or more of the frustum planes. + bool intersects( const Box3F &bounds ) const { return m_planeF_intersect_box3F(reinterpret_cast(&mPlanes), reinterpret_cast(&bounds)); } + bool intersectOBB( const Point3F *points ) const; + bool edgeFaceIntersect( const Point3F &edgeA, const Point3F &edgeB, + const Point3F &faceA, const Point3F &faceB, const Point3F &faceC, const Point3F &faceD, Point3F *intersection ) const; + + /// Returns true if the point is completely within the frustum planes. + bool pointInFrustum( const Point3F &point ) const; + + /// Returns true if the center point of the sphere + /// is not less than radius distance from one of the frustum planes. + bool sphereInFrustum( const Point3F ¢er, F32 radius ) const; + + /// Returns the bitmask of what planes were hit. + U32 testPlanes( const Box3F &bounds, U32 planeMask, F32 expand = 0.0f ) const; + + /// @} + + + /// @name Points and Planes + /// + /// @{ + + /// Returns a reference to a frustum point. + const Point3F& getPoint( U32 index ) const; + + /// Returns a pointer to the array of frustum + /// points of PointCount size. + const Point3F* getPoints() const; + + /// Returns a pointer to the array of frustum + /// planes of PlaneCount size. + const PlaneF* getPlanes() const; + + /// Returns the center point of the frustum by + /// averaging all the corner points. + void getCenterPoint( Point3F *center ) const; + + /// @} + + /// @name Projection Type + /// + /// @{ + bool isOrtho() const { return mIsOrtho; } + /// @} + +}; + + +inline const MatrixF& Frustum::getTransform() const +{ + return mTransform; +} + +inline const Box3F& Frustum::getBounds() const +{ + return mBounds; +} + +inline const Point3F& Frustum::getPosition() const +{ + return mPoints[ CameraPosition ]; +} + +inline const Point3F& Frustum::getPoint( U32 index ) const +{ + AssertFatal( index >= 0 && index < PointCount, "Bad index!" ); + return mPoints[ index ]; +} + +inline const Point3F* Frustum::getPoints() const +{ + return mPoints; +} + +inline const PlaneF* Frustum::getPlanes() const +{ + return mPlanes; +} + +inline bool Frustum::operator==(const Frustum& _test) const +{ + return ( ( mNearLeft == _test.mNearLeft ) && + ( mNearTop == _test.mNearTop ) && + ( mNearBottom == _test.mNearBottom ) && + ( mNearDist == _test.mNearDist ) && + ( mFarDist == _test.mFarDist ) ); +} + +inline bool Frustum::operator!=(const Frustum& _test) const +{ + return (operator==(_test) == false); +} + +#endif // _MATHUTIL_FRUSTUM_H_ \ No newline at end of file diff --git a/math/util/matrixSet.cpp b/math/util/matrixSet.cpp new file mode 100644 index 0000000..1b0e05d --- /dev/null +++ b/math/util/matrixSet.cpp @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/util/matrixSet.h" + +MatrixSet::MatrixSet() +{ + // [9/4/2009 Pat] Until we get better control over heap allocations in Torque + // this class will provide a place where aligned/specalized matrix math can take place. + // We should be able to plug in any kind of platform-specific optimization + // behind the delgates. + AssertFatal( ((int)this & 0xF) == 0, "MatrixSet has been allocated off a 16-byte boundary!" ); + + // Must be initialized by name, not a for(), it's macro magic + MATRIX_SET_BIND_VALUE(ObjectToWorld); + MATRIX_SET_BIND_VALUE(WorldToCamera); + MATRIX_SET_BIND_VALUE(CameraToScreen); + MATRIX_SET_BIND_VALUE(ObjectToCamera); + MATRIX_SET_BIND_VALUE(WorldToObject); + MATRIX_SET_BIND_VALUE(CameraToWorld); + MATRIX_SET_BIND_VALUE(ObjectToScreen); + MATRIX_SET_BIND_VALUE(CameraToObject); + MATRIX_SET_BIND_VALUE(WorldToScreen); + MATRIX_SET_BIND_VALUE(SceneView); + MATRIX_SET_BIND_VALUE(SceneProjection); +} \ No newline at end of file diff --git a/math/util/matrixSet.h b/math/util/matrixSet.h new file mode 100644 index 0000000..b932d4f --- /dev/null +++ b/math/util/matrixSet.h @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATRIXSET_H_ +#define _MATRIXSET_H_ + +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _UTIL_DELEGATE_H_ +#include "core/util/delegate.h" +#endif +#ifndef _MATRIXSETDELEGATES_H_ +#include "math/util/matrixSetDelegateMethods.h" +#endif + +#ifdef TORQUE_COMPILER_VISUALC +__declspec(align(16)) +#endif +class MatrixSet +{ + typedef Delegate MatrixEvalDelegate; + enum _Transforms + { + ObjectToWorld = 0, // World + WorldToCamera, // View + CameraToScreen, // Projection + ObjectToScreen, // World * View * Proj + ObjectToCamera, // World * View + WorldToObject, // World^-1 + CameraToWorld, // View^-1 + CameraToObject, // (World * View)^-1 + WorldToScreen, // View * Projection + SceneView, // The View matrix for the SceneState + SceneProjection, // The Projection matrix for the SceneState + NumTransforms, + }; + + MatrixF mTransform[NumTransforms]; + MatrixEvalDelegate mEvalDelegate[NumTransforms]; + + const MatrixF *mViewSource; + const MatrixF *mProjectionSource; + + MATRIX_SET_GET_VALUE(ObjectToWorld); + MATRIX_SET_GET_VALUE(WorldToCamera); + MATRIX_SET_GET_VALUE(CameraToScreen); + MATRIX_SET_GET_VALUE(ObjectToCamera); + MATRIX_SET_GET_VALUE(WorldToObject); + MATRIX_SET_GET_VALUE(CameraToWorld); + MATRIX_SET_GET_VALUE(ObjectToScreen); + MATRIX_SET_GET_VALUE(CameraToObject); + MATRIX_SET_GET_VALUE(WorldToScreen); + MATRIX_SET_GET_VALUE(SceneView); + MATRIX_SET_GET_VALUE(SceneProjection); + + MATRIX_SET_IS_INVERSE_OF(WorldToObject, ObjectToWorld); + MATRIX_SET_IS_INVERSE_OF(CameraToWorld, WorldToCamera); + + MATRIX_SET_MULT_ASSIGN(WorldToCamera, ObjectToWorld, ObjectToCamera); + MATRIX_SET_IS_INVERSE_OF(CameraToObject, ObjectToCamera); + + MATRIX_SET_MULT_ASSIGN(CameraToScreen, WorldToCamera, WorldToScreen); + + MATRIX_SET_MULT_ASSIGN(WorldToScreen, ObjectToWorld, ObjectToScreen); + +public: + MatrixSet(); + + // Direct accessors + inline const MatrixF &getObjectToWorld() const { return mTransform[ObjectToWorld]; } + inline const MatrixF &getWorldToCamera() const { return mTransform[WorldToCamera]; } + inline const MatrixF &getCameraToScreen() const { return mTransform[CameraToScreen]; } + + // Delegate driven, lazy-evaluation accessors + inline const MatrixF &getWorldViewProjection() const { return mEvalDelegate[ObjectToScreen](); } + inline const MatrixF &getWorldToObject() const { return mEvalDelegate[WorldToObject](); } + inline const MatrixF &getCameraToWorld() const { return mEvalDelegate[CameraToWorld](); } + inline const MatrixF &getObjectToCamera() const { return mEvalDelegate[ObjectToCamera](); } + inline const MatrixF &getCameraToObject() const { return mEvalDelegate[CameraToObject](); } + + // Assignment for the world/view/projection matrices + inline void setWorld(const MatrixF &world) + { + mTransform[ObjectToWorld] = world; + mEvalDelegate[WorldToObject].bind(this, &MatrixSet::MATRIX_SET_IS_INVERSE_OF_FN(WorldToObject, ObjectToWorld)); + mEvalDelegate[ObjectToScreen].bind(this, &MatrixSet::MATRIX_SET_MULT_ASSIGN_FN(WorldToScreen, ObjectToWorld, ObjectToScreen)); + mEvalDelegate[ObjectToCamera].bind(this, &MatrixSet::MATRIX_SET_MULT_ASSIGN_FN(WorldToCamera, ObjectToWorld, ObjectToCamera)); + mEvalDelegate[CameraToObject].bind(this, &MatrixSet::MATRIX_SET_IS_INVERSE_OF_FN(CameraToObject, ObjectToCamera)); + } + + inline void setView(const MatrixF &view) + { + if(&view == mViewSource) + return; + + mViewSource = &view; + mTransform[WorldToCamera] = view; + mEvalDelegate[CameraToWorld].bind(this, &MatrixSet::MATRIX_SET_IS_INVERSE_OF_FN(CameraToWorld, WorldToCamera)); + mEvalDelegate[ObjectToScreen].bind(this, &MatrixSet::MATRIX_SET_MULT_ASSIGN_FN(WorldToScreen, ObjectToWorld, ObjectToScreen)); + mEvalDelegate[WorldToScreen].bind(this, &MatrixSet::MATRIX_SET_MULT_ASSIGN_FN(CameraToScreen, WorldToCamera, WorldToScreen)); + mEvalDelegate[ObjectToCamera].bind(this, &MatrixSet::MATRIX_SET_MULT_ASSIGN_FN(WorldToCamera, ObjectToWorld, ObjectToCamera)); + mEvalDelegate[CameraToObject].bind(this, &MatrixSet::MATRIX_SET_IS_INVERSE_OF_FN(CameraToObject, ObjectToCamera)); + } + + inline void setProjection(const MatrixF &projection) + { + if(&projection == mProjectionSource) + return; + + mProjectionSource = &projection; + mTransform[CameraToScreen] = projection; + mEvalDelegate[ObjectToScreen].bind(this, &MatrixSet::MATRIX_SET_MULT_ASSIGN_FN(WorldToScreen, ObjectToWorld, ObjectToScreen)); + mEvalDelegate[WorldToScreen].bind(this, &MatrixSet::MATRIX_SET_MULT_ASSIGN_FN(CameraToScreen, WorldToCamera, WorldToScreen)); + } + + void setSceneView(const MatrixF &view) + { + mViewSource = NULL; + setView(view); + mViewSource = &mTransform[WorldToCamera]; + mTransform[SceneView] = view; + } + + void setSceneProjection(const MatrixF &projection) + { + mProjectionSource = NULL; + setProjection(projection); + mProjectionSource = &mTransform[CameraToScreen]; + mTransform[SceneProjection] = projection; + } + + void restoreSceneViewProjection() + { + mViewSource = NULL; + mProjectionSource = NULL; + setView(mTransform[SceneView]); + setProjection(mTransform[SceneProjection]); + mViewSource = &mTransform[WorldToCamera]; + mProjectionSource = &mTransform[CameraToScreen]; + } +} +#if defined(TORQUE_COMPILER_GCC) +__attribute__ ((aligned (16))) +#endif +; + +#endif // _MATRIXSET_H_ diff --git a/math/util/matrixSetDelegateMethods.h b/math/util/matrixSetDelegateMethods.h new file mode 100644 index 0000000..992f1f6 --- /dev/null +++ b/math/util/matrixSetDelegateMethods.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _MATRIXSETDELEGATES_H_ +#define _MATRIXSETDELEGATES_H_ + + // Access to the direct value +#define MATRIX_SET_GET_VALUE_FN(xfm) _transform_##xfm +#define MATRIX_SET_GET_VALUE(xfm) inline const MatrixF &MATRIX_SET_GET_VALUE_FN(xfm)() { return mTransform[xfm]; } +#define MATRIX_SET_BIND_VALUE(xfm) mEvalDelegate[xfm].bind(this, &MatrixSet::MATRIX_SET_GET_VALUE_FN(xfm)) + +#define MATRIX_SET_IS_INVERSE_OF_FN(inv_xfm, src_xfm) _##inv_xfm##_is_inverse_of_##src_xfm +#define MATRIX_SET_IS_INVERSE_OF(inv_xfm, src_xfm) inline const MatrixF &MATRIX_SET_IS_INVERSE_OF_FN(inv_xfm, src_xfm)() \ + { \ + m_matF_invert_to(mEvalDelegate[src_xfm](), mTransform[inv_xfm]); \ + MATRIX_SET_BIND_VALUE(inv_xfm); \ + return mTransform[inv_xfm]; \ + } + + +#define MATRIX_SET_MULT_ASSIGN_FN(matA, matB, matC) _##matC##_is_##matA##_x_##matB +#define MATRIX_SET_MULT_ASSIGN(matA, matB, matC) inline const MatrixF &MATRIX_SET_MULT_ASSIGN_FN(matA, matB, matC)() \ + { \ + m_matF_x_matF_aligned(mEvalDelegate[matA](), mEvalDelegate[matB](), mTransform[matC]); \ + MATRIX_SET_BIND_VALUE(matC); \ + return mTransform[matC]; \ + } + + +#endif // _MATRIXSETDELEGATES_H_ diff --git a/math/util/quadTransforms.cpp b/math/util/quadTransforms.cpp new file mode 100644 index 0000000..5f030a4 --- /dev/null +++ b/math/util/quadTransforms.cpp @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "math/util/quadTransforms.h" + + +BiQuadToSqr::BiQuadToSqr( const Point2F &p00, + const Point2F &p10, + const Point2F &p11, + const Point2F &p01 ) + : m_kP00( p00 ) +{ + m_kB = p10 - p00; // width + m_kC = p01 - p00; // height + m_kD = p11 + p00 - p10 - p01; // diagonal dist + + m_fBC = mDotPerp( m_kB, m_kC ); + m_fBD = mDotPerp( m_kB, m_kD ); + m_fCD = mDotPerp( m_kC, m_kD ); +} + +Point2F BiQuadToSqr::transform( const Point2F &p ) const +{ + Point2F kA = m_kP00 - p; + + F32 fAB = mDotPerp( kA, m_kB ); + F32 fAC = mDotPerp( kA, m_kC); + + const F32 smEpsilon = 0.001f; + + // 0 = ac*bc+(bc^2+ac*bd-ab*cd)*s+bc*bd*s^2 = k0 + k1*s + k2*s^2 + F32 fK0 = fAC*m_fBC; + F32 fK1 = m_fBC*m_fBC + fAC*m_fBD - fAB*m_fCD; + F32 fK2 = m_fBC*m_fBD; + + if (mFabs(fK2) >= smEpsilon) + { + // s-equation is quadratic + F32 fInv = ((F32)0.5)/fK2; + F32 fDiscr = fK1*fK1 - ((F32)4.0)*fK0*fK2; + F32 fRoot = mSqrt( mFabs(fDiscr) ); + + Point2F kResult0( 0, 0 ); + kResult0.x = (-fK1 - fRoot)*fInv; + kResult0.y = fAB/(m_fBC + m_fBD*kResult0.x); + F32 fDeviation0 = deviation(kResult0); + if ( fDeviation0 == 0.0f ) + return kResult0; + + Point2F kResult1( 0, 0 ); + kResult1.x = (-fK1 + fRoot)*fInv; + kResult1.y = fAB/(m_fBC + m_fBD*kResult1.x); + F32 fDeviation1 = deviation(kResult1); + if ( fDeviation1 == 0.0f ) + return kResult1; + + if (fDeviation0 <= fDeviation1) + { + if ( fDeviation0 <= smEpsilon ) + return kResult0; + } + else + { + if ( fDeviation1 <= smEpsilon ) + return kResult1; + } + } + else + { + // s-equation is linear + Point2F kResult( 0, 0 ); + + kResult.x = -fK0/fK1; + kResult.y = fAB/(m_fBC + m_fBD*kResult.x); + F32 fDeviation = deviation(kResult); + if ( fDeviation <= smEpsilon ) + return kResult; + } + + // point is outside the quadrilateral, return invalid + return Point2F(F32_MAX,F32_MAX); +} + +F32 BiQuadToSqr::deviation( const Point2F &sp ) +{ + // deviation is the squared distance of the point from the unit square + F32 fDeviation = (F32)0.0; + F32 fDelta; + + if (sp.x < (F32)0.0) + { + fDeviation += sp.x*sp.x; + } + else if (sp.x > (F32)1.0) + { + fDelta = sp.x - (F32)1.0; + fDeviation += fDelta*fDelta; + } + + if (sp.y < (F32)0.0) + { + fDeviation += sp.y*sp.y; + } + else if (sp.y > (F32)1.0) + { + fDelta = sp.y - (F32)1.0; + fDeviation += fDelta*fDelta; + } + + return fDeviation; +} + +/* +BiSqrToQuad::BiSqrToQuad (const Point2F& rkP00, + const Point2F& rkP10, const Point2F& rkP11, + const Point2F& rkP01) +{ + m_kS00 = rkP00; + m_kS10 = rkP10; + m_kS11 = rkP11; + m_kS01 = rkP01; +} + +Point2F BiSqrToQuad::Transform (const Point2F& rkP) +{ + Point2F kOmP((F32)1.0-rkP.x,(F32)1.0-rkP.y); + Point2F kResult; + kResult.x = kOmP.y*(kOmP.x*m_kS00.x + rkP.x*m_kS10.x) + + rkP.y*(kOmP.x*m_kS01.x + rkP.x*m_kS11.x); + kResult.y = kOmP.y*(kOmP.x*m_kS00.y + rkP.x*m_kS10.y) + + rkP.y*(kOmP.x*m_kS01.y + rkP.x*m_kS11.y); + return kResult; +} +*/ + + +BiSqrToQuad3D::BiSqrToQuad3D( const Point3F& pnt00, + const Point3F& pnt10, + const Point3F& pnt11, + const Point3F& pnt01) +{ + p00 = pnt00; + p10 = pnt10; + p11 = pnt11; + p01 = pnt01; +} + +Point3F BiSqrToQuad3D::transform( const Point2F &p ) const +{ + //Let p00, p10, p01, and p11 be your 3-tuples that are the quad's + //vertices. You can parameterize the quad as follows. + + //q(s,t) = (1-s)*((1-t)*p00 + t*p01) + s*((1-t)*p10 + t*p11) + + //for 0 <= s <= 1 and 0 <= t <= 1. Notice that q(0,0) = p00, + //q(1,0) = p10, q(0,1) = p01, and q(1,1) = p11, so the parameter + //"square" whose points are (s,t) will be mapped to the quad. + + const F32 &s = p.x; + const F32 &t = p.y; + + Point3F result = (1.0f-s)*((1.0f-t)*p00 + t*p01) + s*((1.0f-t)*p10 + t*p11); + return result; +} + diff --git a/math/util/quadTransforms.h b/math/util/quadTransforms.h new file mode 100644 index 0000000..63c8283 --- /dev/null +++ b/math/util/quadTransforms.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _QUADTOQUADTRANSFORMS_H_ +#define _QUADTOQUADTRANSFORMS_H_ + +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif + +// NOTE: The code in these classes originate from the Wild Magic Source Code +// library by David Eberly and is used with permission. + + +/// This class does bilinear mapping of quadrilateral to a square. +class BiQuadToSqr +{ +public: + + /// Constructs the transform class from the quadrilateral + /// points in counter clockwise order. + BiQuadToSqr( const Point2F &p00, + const Point2F &p10, + const Point2F &p11, + const Point2F &p01 ); + + /// Transforms the point. + Point2F transform( const Point2F &p ) const; + +protected: + + static F32 deviation( const Point2F &sp ); + + Point2F m_kP00, m_kB, m_kC, m_kD; + + F32 m_fBC, m_fBD, m_fCD; + +}; + + +class BiSqrToQuad3D +{ +public: + + BiSqrToQuad3D( const Point3F &pnt00, + const Point3F &pnt10, + const Point3F &pnt11, + const Point3F &pnt01 ); + + Point3F transform( const Point2F &pnt ) const; + +protected: + + Point3F p00, p01, p10, p11; +}; + +#endif // _QUADTOQUADTRANSFORMS_H_ diff --git a/math/util/sphereMesh.cpp b/math/util/sphereMesh.cpp new file mode 100644 index 0000000..aa47d65 --- /dev/null +++ b/math/util/sphereMesh.cpp @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/util/sphereMesh.h" + +SphereMesh::SphereMesh(U32 baseType) +{ + VECTOR_SET_ASSOCIATION(mDetails); + + switch(baseType) + { + case Tetrahedron: + mDetails.push_back(createTetrahedron()); + break; + + case Octahedron: + mDetails.push_back(createOctahedron()); + break; + + case Icosahedron: + mDetails.push_back(createIcosahedron()); + break; + } + calcNormals(mDetails[0]); +} + +//------------------------------------------------------------------------------ + +SphereMesh::TriangleMesh * SphereMesh::createTetrahedron() +{ + const F32 sqrt3 = 0.5773502692f; + + static Point3F spherePnts[] = { + Point3F( sqrt3, sqrt3, sqrt3 ), + Point3F(-sqrt3,-sqrt3, sqrt3 ), + Point3F(-sqrt3, sqrt3,-sqrt3 ), + Point3F( sqrt3,-sqrt3,-sqrt3 ) + }; + + static Triangle tetrahedron[] = { + Triangle(spherePnts[0], spherePnts[1], spherePnts[2]), + Triangle(spherePnts[0], spherePnts[3], spherePnts[1]), + Triangle(spherePnts[2], spherePnts[1], spherePnts[3]), + Triangle(spherePnts[3], spherePnts[0], spherePnts[2]), + }; + + static TriangleMesh tetrahedronMesh = { + Tetrahedron, + &tetrahedron[0] + }; + + return(&tetrahedronMesh); +} + +//------------------------------------------------------------------------------ + +SphereMesh::TriangleMesh * SphereMesh::createOctahedron() +{ + // + static Point3F spherePnts[] = { + Point3F( 1, 0, 0), + Point3F(-1, 0, 0), + Point3F( 0, 1, 0), + Point3F( 0,-1, 0), + Point3F( 0, 0, 1), + Point3F( 0, 0,-1) + }; + + // + static Triangle octahedron[] = { + Triangle(spherePnts[0], spherePnts[4], spherePnts[2]), + Triangle(spherePnts[2], spherePnts[4], spherePnts[1]), + Triangle(spherePnts[1], spherePnts[4], spherePnts[3]), + Triangle(spherePnts[3], spherePnts[4], spherePnts[0]), + Triangle(spherePnts[0], spherePnts[2], spherePnts[5]), + Triangle(spherePnts[2], spherePnts[1], spherePnts[5]), + Triangle(spherePnts[1], spherePnts[3], spherePnts[5]), + Triangle(spherePnts[3], spherePnts[0], spherePnts[5]) + }; + + // + static TriangleMesh octahedronMesh = { + Octahedron, + &octahedron[0] + }; + + return(&octahedronMesh); +} + +SphereMesh::TriangleMesh * SphereMesh::createIcosahedron() +{ + const F32 tau = 0.8506508084f; + const F32 one = 0.5257311121f; + + static Point3F spherePnts[] = { + Point3F( tau, one, 0), + Point3F(-tau, one, 0), + Point3F(-tau,-one, 0), + Point3F( tau,-one, 0), + Point3F( one, 0, tau), + Point3F( one, 0,-tau), + Point3F(-one, 0,-tau), + Point3F(-one, 0, tau), + Point3F( 0, tau, one), + Point3F( 0,-tau, one), + Point3F( 0,-tau,-one), + Point3F( 0, tau,-one), + }; + + static Triangle icosahedron[] = { + Triangle(spherePnts[4], spherePnts[8], spherePnts[7]), + Triangle(spherePnts[4], spherePnts[7], spherePnts[9]), + Triangle(spherePnts[5], spherePnts[6], spherePnts[11]), + Triangle(spherePnts[5], spherePnts[10], spherePnts[6]), + Triangle(spherePnts[0], spherePnts[4], spherePnts[3]), + Triangle(spherePnts[0], spherePnts[3], spherePnts[5]), + Triangle(spherePnts[2], spherePnts[7], spherePnts[1]), + Triangle(spherePnts[2], spherePnts[1], spherePnts[6]), + Triangle(spherePnts[8], spherePnts[0], spherePnts[11]), + Triangle(spherePnts[8], spherePnts[11], spherePnts[1]), + Triangle(spherePnts[9], spherePnts[10], spherePnts[3]), + Triangle(spherePnts[9], spherePnts[2], spherePnts[10]), + Triangle(spherePnts[8], spherePnts[4], spherePnts[0]), + Triangle(spherePnts[11], spherePnts[0], spherePnts[5]), + Triangle(spherePnts[4], spherePnts[9], spherePnts[3]), + Triangle(spherePnts[5], spherePnts[3], spherePnts[10]), + Triangle(spherePnts[7], spherePnts[8], spherePnts[1]), + Triangle(spherePnts[6], spherePnts[1], spherePnts[11]), + Triangle(spherePnts[7], spherePnts[2], spherePnts[9]), + Triangle(spherePnts[6], spherePnts[10], spherePnts[2]), + }; + + static TriangleMesh icosahedronMesh = { + Icosahedron, + &icosahedron[0] + }; + + return(&icosahedronMesh); +} + +//------------------------------------------------------------------------------ + +void SphereMesh::calcNormals(TriangleMesh * mesh) +{ + for(U32 i = 0; i < mesh->numPoly; i++) + { + Triangle & tri = mesh->poly[i]; + mCross(tri.pnt[1] - tri.pnt[0], tri.pnt[2] - tri.pnt[0], &tri.normal); + } +} + +//------------------------------------------------------------------------------ + +SphereMesh::~SphereMesh() +{ + // level 0 is static data + for(U32 i = 1; i < mDetails.size(); i++) + { + delete [] mDetails[i]->poly; + delete mDetails[i]; + } +} + +//------------------------------------------------------------------------------ + +const SphereMesh::TriangleMesh * SphereMesh::getMesh(U32 level) +{ + AssertFatal(mDetails.size(), "SphereMesh::getMesh: no details!"); + + if(level > MaxLevel) + level = MaxLevel; + + // + while(mDetails.size() <= level) + mDetails.push_back(subdivideMesh(mDetails.last())); + + return(mDetails[level]); +} + +SphereMesh::TriangleMesh * SphereMesh::subdivideMesh(TriangleMesh * prevMesh) +{ + AssertFatal(prevMesh, "SphereMesh::subdivideMesh: invalid previous mesh level!"); + + // + TriangleMesh * mesh = new TriangleMesh; + + mesh->numPoly = prevMesh->numPoly * 4; + mesh->poly = new Triangle [mesh->numPoly]; + + // + for(U32 i = 0; i < prevMesh->numPoly; i++) + { + Triangle * pt = &prevMesh->poly[i]; + Triangle * nt = &mesh->poly[i*4]; + + Point3F a = (pt->pnt[0] + pt->pnt[2]) / 2; + Point3F b = (pt->pnt[0] + pt->pnt[1]) / 2; + Point3F c = (pt->pnt[1] + pt->pnt[2]) / 2; + + // force the point onto the unit sphere surface + a.normalize(); + b.normalize(); + c.normalize(); + + // + nt->pnt[0] = pt->pnt[0]; + nt->pnt[1] = b; + nt->pnt[2] = a; + nt++; + + // + nt->pnt[0] = b; + nt->pnt[1] = pt->pnt[1]; + nt->pnt[2] = c; + nt++; + + // + nt->pnt[0] = a; + nt->pnt[1] = b; + nt->pnt[2] = c; + nt++; + + // + nt->pnt[0] = a; + nt->pnt[1] = c; + nt->pnt[2] = pt->pnt[2]; + } + + calcNormals(mesh); + return(mesh); +} diff --git a/math/util/sphereMesh.h b/math/util/sphereMesh.h new file mode 100644 index 0000000..ade99d8 --- /dev/null +++ b/math/util/sphereMesh.h @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SPHEREMESH_H_ +#define _SPHEREMESH_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +//------------------------------------------------------------------------------ +// Class: SphereMesh +//------------------------------------------------------------------------------ +// * ctor takes type of base polyhedron that is subdivided to create sphere +// * getMesh(...) will subdivide the current mesh to the desired level where +// (each level has 4 times the polys of the previous level) + +class SphereMesh +{ + public: + + // regular polyhedra with triangle face polygons (num of faces) + enum { + Tetrahedron = 4, + Octahedron = 8, + Icosahedron = 20, + + MaxLevel = 5 + }; + + struct Triangle { + Triangle() {} + Triangle(const Point3F &a, const Point3F &b, const Point3F &c) + { + pnt[0] = a; + pnt[1] = b; + pnt[2] = c; + } + + Point3F pnt[3]; + Point3F normal; + }; + + struct TriangleMesh { + U32 numPoly; + Triangle * poly; + }; + + SphereMesh(U32 baseType = Octahedron); + ~SphereMesh(); + + const TriangleMesh * getMesh(U32 level = 0); + + private: + + TriangleMesh * createTetrahedron(); + TriangleMesh * createOctahedron(); + TriangleMesh * createIcosahedron(); + + Vector mDetails; + + void calcNormals(TriangleMesh *); + TriangleMesh * subdivideMesh(TriangleMesh*); +}; + +#endif diff --git a/math/util/tResponseCurve.cpp b/math/util/tResponseCurve.cpp new file mode 100644 index 0000000..56e6692 --- /dev/null +++ b/math/util/tResponseCurve.cpp @@ -0,0 +1,52 @@ + +#include "tResponseCurve.h" + +IMPLEMENT_CONOBJECT( SimResponseCurve ); + +SimResponseCurve::SimResponseCurve() +{ + +} + +bool SimResponseCurve::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + return true; +} + +void SimResponseCurve::onRemove() +{ + Parent::onRemove(); +} + +void SimResponseCurve::addPoint(F32 value, F32 time) +{ + mCurve.addPoint( value, time ); +} + +F32 SimResponseCurve::getValue(F32 time) +{ + return mCurve.getVal( time ); +} + +void SimResponseCurve::clear() +{ + mCurve.clear(); +} + +ConsoleMethod( SimResponseCurve, addPoint, void, 4, 4, "addPoint( F32 value, F32 time )" ) +{ + object->addPoint( dAtof(argv[2]), dAtof(argv[3]) ); +} + +ConsoleMethod( SimResponseCurve, getValue, F32, 3, 3, "getValue( F32 time )" ) +{ + return object->getValue( dAtof(argv[2]) ); +} + +ConsoleMethod( SimResponseCurve, clear, void, 2, 2, "clear()" ) +{ + object->clear(); +} \ No newline at end of file diff --git a/math/util/tResponseCurve.h b/math/util/tResponseCurve.h new file mode 100644 index 0000000..e362c1b --- /dev/null +++ b/math/util/tResponseCurve.h @@ -0,0 +1,230 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// Notice: +// Some of this code originates from an article in AI Game Programming Wisdom +// by Dave Mark. + +#ifndef _TRESPONSECURVE_H_ +#define _TRESPONSECURVE_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _MMATHFN_H_ +#include "math/mMathFn.h" +#endif + +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif + + +// ------------------------------- +// Represents a sigmoid function +// Note: not used by ResponseCurve +// ------------------------------- + +class Sigmoid +{ +public: + Sigmoid( F32 s, F32 m) { _s = s; _m = m; } + ~Sigmoid() {}; + + inline F32 get( F32 x ) + { + F32 pow = -2.0f * ( ( x - _m ) / _s ); + F32 y = 1.0f / mPow( 1 + M_CONST_E_F, pow ); + return y; + } + + F32 _s; + F32 _m; +}; + +// ----------------------------------------------------------------------------- +// Represents a response curve, can query values +// ----------------------------------------------------------------------------- + +template< class T > +class ResponseCurve +{ +public: + + struct Sample + { + Sample() {} + Sample( F32 f, const T &val ) : mF(f), mVal(val) {} + + F32 mF; + T mVal; + }; + + typedef Vector< Sample > SampleList; + SampleList mSamples; + + const SampleList& getSamples() { return mSamples; } + + ResponseCurve() {} + ResponseCurve( U32 numSamples ) { mSamples.reserve(numSamples); } + + void clear() { mSamples.clear(); } + void addPoint( F32 f, const T &val ); + //void addPoints( U32 count, F32 f[], const T &val[] ); + T getVal( F32 f ) const; + S32 setPoint( S32 idx, F32 f, const T &val ); + void removePoint( S32 idx ); + S32 getSampleCount() const { return mSamples.size(); } +}; + +//----------------------------------------------------------------------------- +// Adds a new value to the Response Curve, at the position f +//----------------------------------------------------------------------------- +template< class T > +inline void ResponseCurve::addPoint( F32 f, const T &val ) +{ + typename SampleList::iterator iter = mSamples.begin(); + for ( ; iter != mSamples.end(); iter++ ) + { + if ( iter->mF == f ) + { + Con::warnf( "Warn: ResponseCurve::AddPoint, Duplicate values are not allowed." ); + return; + } + + if ( iter->mF > f ) + break; + } + + mSamples.insert( iter, Sample( f, val ) ); +} + +//----------------------------------------------------------------------------- +// Finds the right value at position f, interpolating between the previous +// and the following values +//----------------------------------------------------------------------------- +template< class T > +inline T ResponseCurve::getVal( F32 f ) const +{ + T retVal; + + if ( mSamples.empty() ) + { + retVal = T(); + } + else + { + U32 nSamples = mSamples.size(); + if ( nSamples == 1 || f <= mSamples[0].mF ) + { + retVal = mSamples[0].mVal; + } + else if ( f >= mSamples[nSamples-1].mF ) + { + retVal = mSamples[nSamples-1].mVal; + } + else + { + U32 i = 1; + while ( i < (nSamples-1) && mSamples[i].mF < f ) + ++i; + + // Interpolate between m_Samples[i-1] and m_Samples[i] + F32 fSampleMin = mSamples[i-1].mF; + F32 fSampleMax = mSamples[i].mF; + AssertWarn(fSampleMin != fSampleMax, "fSampleMin should not equal fSampleMax" ); + + F32 t = (f - fSampleMin) / (fSampleMax - fSampleMin); + retVal = mSamples[i-1].mVal + ( mSamples[i].mVal - mSamples[i-1].mVal) * t; + } + } + + return retVal; +} + +template< class T > +inline S32 ResponseCurve< T >::setPoint( S32 idx, F32 f, const T &val ) +{ + mSamples.erase( idx ); + + typename SampleList::iterator iter = mSamples.begin(); + for ( ; iter != mSamples.end(); iter++ ) + { + if ( iter->mF == f ) + { + Con::warnf( "Warn: ResponseCurve::AddPoint, Duplicate values are not allowed." ); + return -1; + } + + if ( iter->mF > f ) + break; + } + + mSamples.insert( iter, Sample( f, val ) ); + + return (S32)( iter - mSamples.begin() ); +} + + +class FloatCurve : public ResponseCurve +{ +public: + FloatCurve() {} +}; + + +// ----------------------------------------------- +// A ResponseCurve wrapped as a SimObject +// ----------------------------------------------- + +class SimResponseCurve : public SimObject +{ + typedef SimObject Parent; + +public: + + SimResponseCurve(); + //~SimResponseCurve(); + + DECLARE_CONOBJECT( SimResponseCurve ); + + virtual bool onAdd(); + virtual void onRemove(); + + void addPoint( F32 value, F32 time ); + F32 getValue( F32 time ); + void clear(); + + ResponseCurve mCurve; +}; + + +// A networked-datablock version of ResponseCurve +/* +class ResponseCurveData : public SimDataBlock +{ + typedef SimDataBlock Parent; + +public: + + ResponseCurveData(); + //~ResponseCurveData(); + + DECLARE_CONOBJECT( ResponseCurveData ); + + virtual bool onAdd(); + virtual void onRemove(); + + + void addPoint( F32 value, F32 time ); + F32 getValue( F32 time ); + void clear(); + + ResponseCurve mCurve; +}; +*/ + +#endif diff --git a/platform/async/asyncBufferedStream.h b/platform/async/asyncBufferedStream.h new file mode 100644 index 0000000..ff398fc --- /dev/null +++ b/platform/async/asyncBufferedStream.h @@ -0,0 +1,400 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ASYNCBUFFEREDSTREAM_H_ +#define _ASYNCBUFFEREDSTREAM_H_ + +#ifndef _TSTREAM_H_ + #include "core/stream/tStream.h" +#endif +#ifndef _THREADPOOL_H_ + #include "platform/threads/threadPool.h" +#endif +#ifndef _THREADSAFEDEQUE_H_ + #include "platform/threads/threadSafeDeque.h" +#endif + + +// Disable nonsense warning about unreferenced +// local function on VC. +#ifdef TORQUE_COMPILER_VISUALC + #pragma warning( disable: 4505 ) +#endif + + +template< typename T, class Stream > +class AsyncBufferedReadItem; + + + +//============================================================================= +// AsyncBufferedInputStream. +//============================================================================= + + +/// +template< typename T, class Stream = IInputStream< T >* > +class AsyncBufferedInputStream : public IInputStreamFilter< T, Stream >, + public ThreadSafeRefCount< AsyncBufferedInputStream< T, Stream > > +{ + public: + + typedef IInputStreamFilter< T, Stream > Parent; + + /// Type of elements read, buffered, and returned by this stream. + typedef typename Parent::ElementType ElementType; + + /// Type of the source stream being read by this stream. + typedef typename Parent::SourceStreamType SourceStreamType; + + /// Type of elements being read from the source stream. + /// + /// @note This does not need to correspond to the type of elements buffered + /// in this stream. + typedef typename Parent::SourceElementType SourceElementType; + + enum + { + /// The number of elements to buffer in advance by default. + DEFAULT_STREAM_LOOKAHEAD = 3 + }; + + friend class AsyncBufferedReadItem< T, Stream >; // _onArrival + + protected: + + /// Stream elements are kept on deques that can be concurrently + /// accessed by multiple threads. + typedef ThreadSafeDeque< ElementType > ElementList; + + /// If true, the stream will restart over from the beginning once + /// it has been read in entirety. + bool mIsLooping; + + /// If true, no further requests should be issued on this stream. + /// @note This in itself doesn't say anything about pending requests. + bool mIsStopped; + + /// Number of source elements remaining in the source stream. + U32 mNumRemainingSourceElements; + + /// Number of elements currently on buffer list. + U32 mNumBufferedElements; + + /// Maximum number of elements allowed on buffer list. + U32 mMaxBufferedElements; + + /// List of buffered elements. + ElementList mBufferedElements; + + /// The thread pool to which read items are queued. + ThreadPool* mThreadPool; + + /// The thread context used for prioritizing read items in the pool. + ThreadContext* mThreadContext; + + /// Request the next element from the underlying stream. + virtual void _requestNext() = 0; + + /// Called when an element read has been completed on the underlying stream. + virtual void _onArrival( const ElementType& element ); + + public: + + /// Construct a new buffered stream reading from "source". + /// + /// @param stream The source stream from which to read the actual data elements. + /// @param numSourceElementsToRead Total number of elements to read from "stream". + /// @param numReadAhead Number of packets to read and buffer in advance. + /// @param isLooping If true, the packet stream will loop infinitely over the source stream. + /// @param pool The ThreadPool to use for asynchronous packet reads. + /// @param context The ThreadContext to place asynchronous packet reads in. + AsyncBufferedInputStream( const Stream& stream, + U32 numSourceElementsToRead = 0, + U32 numReadAhead = DEFAULT_STREAM_LOOKAHEAD, + bool isLooping = false, + ThreadPool* pool = &ThreadPool::GLOBAL(), + ThreadContext* context = ThreadContext::ROOT_CONTEXT() ); + + virtual ~AsyncBufferedInputStream(); + + /// @return true if the stream is looping infinitely. + bool isLooping() const { return mIsLooping; } + + /// @return the number of elements that will be read and buffered in advance. + U32 getReadAhead() const { return mMaxBufferedElements; } + + /// Initiate the request chain of the element stream. + void start() { _requestNext(); } + + /// Call for the request chain of the element stream to stop at the next + /// synchronization point. + void stop() { mIsStopped = true; } + + // IInputStream. + virtual U32 read( ElementType* buffer, U32 num ); +}; + +//----------------------------------------------------------------------------- + +template< typename T, typename Stream > +AsyncBufferedInputStream< T, Stream >::AsyncBufferedInputStream + ( const Stream& stream, + U32 numSourceElementsToRead, + U32 numReadAhead, + bool isLooping, + ThreadPool* threadPool, + ThreadContext* threadContext ) + : Parent( stream ), + mIsStopped( false ), + mIsLooping( isLooping ), + mNumRemainingSourceElements( numSourceElementsToRead ), + mNumBufferedElements( 0 ), + mMaxBufferedElements( numReadAhead ), + mThreadPool( threadPool ), + mThreadContext( threadContext ) +{ + if( mIsLooping ) + { + // Stream is looping so we don't count down source elements. + + mNumRemainingSourceElements = 0; + } + else if( !mNumRemainingSourceElements ) + { + // If not given number of elements to read, see if the source + // stream is sizeable. If so, take its size as the number of elements. + + if( dynamic_cast< ISizeable<>* >( &Deref( stream ) ) ) + mNumRemainingSourceElements = ( ( ISizeable<>* ) &Deref( stream ) )->getSize(); + else + { + // Can't tell how many source elements there are. + + mNumRemainingSourceElements = U32_MAX; + } + } +} + +//----------------------------------------------------------------------------- + +template< typename T, typename Stream > +AsyncBufferedInputStream< T, Stream >::~AsyncBufferedInputStream() +{ + ElementType element; + while( mBufferedElements.tryPopFront( element ) ) + destructSingle( element ); +} + +//----------------------------------------------------------------------------- + +template< typename T, typename Stream > +void AsyncBufferedInputStream< T, Stream >::_onArrival( const ElementType& element ) +{ + mBufferedElements.pushBack( element ); + + // Adjust buffer count. + + while( 1 ) + { + S32 numBuffered = mNumBufferedElements; + if( dCompareAndSwap( mNumBufferedElements, numBuffered, numBuffered + 1 ) ) + { + // If we haven't run against the lookahead limit and haven't reached + // the end of the stream, immediately trigger a new request. + + if( !mIsStopped && ( numBuffered + 1 ) < mMaxBufferedElements ) + _requestNext(); + + break; + } + } +} + +//----------------------------------------------------------------------------- + +template< typename T, typename Stream > +U32 AsyncBufferedInputStream< T, Stream >::read( ElementType* buffer, U32 num ) +{ + if( !num ) + return 0; + + U32 numRead = 0; + for( U32 i = 0; i < num; ++ i ) + { + // Try to pop a element off the buffered element list. + + ElementType element; + if( mBufferedElements.tryPopFront( element ) ) + { + buffer[ i ] = element; + numRead ++; + } + else + break; + } + + // Get the request chain going again, if it has stopped. + + while( 1 ) + { + U32 numBuffered = mNumBufferedElements; + U32 newNumBuffered = numBuffered - numRead; + + if( dCompareAndSwap( mNumBufferedElements, numBuffered, newNumBuffered ) ) + { + if( numBuffered == mMaxBufferedElements ) + _requestNext(); + + break; + } + } + + return numRead; +} + + +//============================================================================= +// AsyncSingleBufferedInputStream. +//============================================================================= + + +/// Asynchronous work item for reading an element from the source stream. +template< typename T, typename Stream = IInputStream< T >* > +class AsyncBufferedReadItem : public ThreadWorkItem +{ + public: + + typedef ThreadWorkItem Parent; + typedef ThreadSafeRef< AsyncBufferedInputStream< T, Stream > > AsyncStreamRef; + + protected: + + /// The issueing async state. + AsyncStreamRef mAsyncStream; + + /// + Stream mSourceStream; + + /// The element read from the stream. + T mElement; + + // WorkItem + virtual void execute() + { + if( Deref( mSourceStream ).read( &mElement, 1 ) ) + { + // Buffer the element. + + if( this->cancellationPoint() ) return; + mAsyncStream->_onArrival( mElement ); + } + } + virtual void onCancelled() + { + Parent::onCancelled(); + destructSingle( mElement ); + mAsyncStream = NULL; + } + + public: + + /// + AsyncBufferedReadItem( + const AsyncStreamRef& asyncStream, + ThreadPool::Context* context = NULL + ) + : Parent( context ), + mAsyncStream( asyncStream ), + mSourceStream( asyncStream->getSourceStream() ) + { + } + +}; + + +/// A stream filter that performs background read-aheads on its source stream +/// and buffers the results. +/// +/// As each element is read in an independent threaded operation, reading an +/// element should invole a certain amount of work for using this class to +/// make sense. +/// +/// @note For looping streams, the stream must implement the IResettable interface. +/// +template< typename T, typename Stream = IInputStream< T >*, class ReadItem = AsyncBufferedReadItem< T, Stream > > +class AsyncSingleBufferedInputStream : public AsyncBufferedInputStream< T, Stream > +{ + public: + + typedef AsyncBufferedInputStream< T, Stream > Parent; + typedef typename Parent::ElementType ElementType; + typedef typename Parent::SourceElementType SourceElementType; + typedef typename Parent::SourceStreamType SourceStreamType; + + protected: + + // AsyncBufferedInputStream. + virtual void _requestNext(); + + /// Create a new work item that reads the next element. + virtual void _newReadItem( ThreadSafeRef< ThreadWorkItem >& outRef ) + { + outRef = new ReadItem( this, this->mThreadContext ); + } + + public: + + /// Construct a new buffered stream reading from "source". + /// + /// @param stream The source stream from which to read the actual data elements. + /// @param numSourceElementsToRead Total number of elements to read from "stream". + /// @param numReadAhead Number of packets to read and buffer in advance. + /// @param isLooping If true, the packet stream will loop infinitely over the source stream. + /// @param pool The ThreadPool to use for asynchronous packet reads. + /// @param context The ThreadContext to place asynchronous packet reads in. + AsyncSingleBufferedInputStream( const Stream& stream, + U32 numSourceElementsToRead = 0, + U32 numReadAhead = Parent::DEFAULT_STREAM_LOOKAHEAD, + bool isLooping = false, + ThreadPool* pool = &ThreadPool::GLOBAL(), + ThreadContext* context = ThreadContext::ROOT_CONTEXT() ) + : Parent( stream, + numSourceElementsToRead, + numReadAhead, + isLooping, + pool, + context ) {} +}; + +//----------------------------------------------------------------------------- + +template< typename T, typename Stream, class ReadItem > +void AsyncSingleBufferedInputStream< T, Stream, ReadItem >::_requestNext() +{ + Stream& stream = this->getSourceStream(); + bool isEOS = !this->mNumRemainingSourceElements; + if( isEOS && this->mIsLooping ) + { + SourceStreamType* s = &Deref( stream ); + dynamic_cast< IResettable* >( s )->reset(); + isEOS = false; + } + else if( isEOS ) + return; + + //TODO: could scale priority depending on feed status + + // Queue a stream packet work item. + + if( !this->mIsLooping && this->mNumRemainingSourceElements != U32_MAX ) + -- this->mNumRemainingSourceElements; + + ThreadSafeRef< ThreadWorkItem > workItem; + _newReadItem( workItem ); + this->mThreadPool->queueWorkItem( workItem ); +} + +#endif // !_ASYNCBUFFEREDSTREAM_H_ diff --git a/platform/async/asyncPacketQueue.h b/platform/async/asyncPacketQueue.h new file mode 100644 index 0000000..49da716 --- /dev/null +++ b/platform/async/asyncPacketQueue.h @@ -0,0 +1,260 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ASYNCPACKETQUEUE_H_ +#define _ASYNCPACKETQUEUE_H_ + +#ifndef _TFIXEDSIZEQUEUE_H_ +# include "core/util/tFixedSizeDeque.h" +#endif +#ifndef _TSTREAM_H_ +# include "core/stream/tStream.h" +#endif +#ifndef _TYPETRAITS_H_ +# include "platform/typetraits.h" +#endif + + +//#define DEBUG_SPEW + + +/// @file +/// Time-based packet streaming. +/// +/// The classes contained in this file can be used for any kind +/// of continuous playback that depends on discrete samplings of +/// a source stream (i.e. any kind of digital media streaming). + + + +//-------------------------------------------------------------------------- +// Async packet queue. +//-------------------------------------------------------------------------- + +/// Time-based packet stream queue. +/// +/// Be aware that using single item queues for synchronizing to a timer +/// will usually result in bad timing behavior when packet uploading takes +/// any non-trivial amount of time. +/// +/// @note While the queue associates a variable tick count with each +/// individual packet, the queue fill status is measured in number of +/// packets rather than in total tick time. +/// +/// @param Packet Value type of packets passed through this queue. +/// @param TimeSource Value type for time tick source to which the queue +/// is synchronized. +/// @param Consumer Value type of stream to which the packets are written. +template< typename Packet, typename TimeSource = IPositionable< U32 >*, typename Consumer = IOutputStream< Packet >*, typename Tick = U32 > +class AsyncPacketQueue +{ + public: + + typedef void Parent; + + /// The type of data packets being streamed through this queue. + typedef typename TypeTraits< Packet >::BaseType PacketType; + + /// The type of consumer that receives the packets from this queue. + typedef typename TypeTraits< Consumer >::BaseType ConsumerType; + + /// + typedef typename TypeTraits< TimeSource >::BaseType TimeSourceType; + + /// + typedef Tick TickType; + + protected: + + /// Information about the time slice covered by an + /// individual packet currently on the queue. + struct QueuedPacket + { + /// + TickType mStartTick; + + /// + TickType mEndTick; + + QueuedPacket( TickType start, TickType end ) + : mStartTick( start ), mEndTick( end ) {} + + /// + TickType getNumTicks() const + { + return ( mEndTick - mStartTick ); + } + }; + + typedef FixedSizeDeque< QueuedPacket > PacketQueue; + + /// If true, packets that have missed their proper queuing timeframe + /// will be dropped. If false, they will be queued nonetheless. + bool mDropPackets; + + /// Total number of ticks spanned by the total queue playback time. + /// If this is zero, the total queue time is considered to be infinite. + TickType mTotalTicks; + + /// + TickType mTotalQueuedTicks; + + /// + PacketQueue mPacketQueue; + + /// The time source to which we are sync'ing. + TimeSource mTimeSource; + + /// The output stream that this queue feeds into. + Consumer mConsumer; + + /// Total number of packets queued so far. + U32 mTotalQueuedPackets; + + public: + + /// + AsyncPacketQueue( U32 maxQueuedPackets, + TimeSource timeSource, + Consumer consumer, + TickType totalTicks = 0, + bool dropPackets = false ) + : mTotalTicks( totalTicks ), + mTotalQueuedTicks( 0 ), + mPacketQueue( maxQueuedPackets ), + mTimeSource( timeSource ), + mConsumer( consumer ), + mDropPackets( dropPackets ) + { + #ifdef TORQUE_DEBUG + mTotalQueuedPackets = 0; + #endif + } + + /// @return true if there are currently + bool isEmpty() const { return mPacketQueue.isEmpty(); } + + /// @return true if all packets have been streamed. + bool isAtEnd() const; + + /// @return true if the queue needs one or more new packets to be submitted. + bool needPacket(); + + /// + bool submitPacket( Packet packet, + TickType packetTicks, + bool isLast = false, + TickType packetPos = TypeTraits< TickType >::MAX ); + + /// + TickType getCurrentTick() const { return Deref( mTimeSource ).getPosition(); } + + /// + TickType getTotalQueuedTicks() const { return mTotalQueuedTicks; } + + /// + U32 getTotalQueuedPackets() const { return mTotalQueuedPackets; } +}; + +template< typename Packet, typename TimeSource, typename Consumer, typename Tick > +inline bool AsyncPacketQueue< Packet, TimeSource, Consumer, Tick >::isAtEnd() const +{ + if( !mTotalTicks ) + return false; + else + return ( getCurrentTick() >= mTotalTicks + && ( mDropPackets || mTotalQueuedTicks >= mTotalTicks ) ); +} + +template< typename Packet, typename TimeSource, typename Consumer, typename Tick > +bool AsyncPacketQueue< Packet, TimeSource, Consumer, Tick >::needPacket() +{ + if( mPacketQueue.capacity() != 0 ) + return true; + else + { + // Unqueue packets that have expired their playtime. + + TickType currentTick = getCurrentTick(); + while( mPacketQueue.size() && currentTick >= mPacketQueue.front().mEndTick ) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[AsyncPacketQueue] expired packet #%i: %i-%i (tick: %i; queue: %i)", + mTotalQueuedPackets - mPacketQueue.size(), + U32( mPacketQueue.front().mStartTick ), + U32( mPacketQueue.front().mEndTick ), + U32( currentTick ), + mPacketQueue.size() ); + #endif + + mPacketQueue.popFront(); + } + + // Need more packets if the queue isn't full anymore. + + return ( mPacketQueue.capacity() != 0 ); + } +} + +template< typename Packet, typename TimeSource, typename Consumer, typename Tick > +bool AsyncPacketQueue< Packet, TimeSource, Consumer, Tick >::submitPacket( Packet packet, TickType packetTicks, bool isLast, TickType packetPos ) +{ + AssertFatal( mPacketQueue.capacity() != 0, + "AsyncPacketQueue::submitPacket() - Queue is full!" ); + + TickType packetStartPos; + TickType packetEndPos; + + if( packetPos != TypeTraits< TickType >::MAX ) + { + packetStartPos = packetPos; + packetEndPos = packetPos + packetTicks; + } + else + { + packetStartPos = mTotalQueuedTicks; + packetEndPos = mTotalQueuedTicks + packetTicks; + } + + // Check whether the packet is outdated, if enabled. + + bool dropPacket = false; + if( mDropPackets ) + { + TickType currentTick = getCurrentTick(); + if( currentTick >= packetEndPos ) + dropPacket = true; + } + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[AsyncPacketQueue] new packet #%i: %i-%i (ticks: %i, current: %i, queue: %i)%s", + mTotalQueuedPackets, + U32( mTotalQueuedTicks ), + U32( packetEndPos ), + U32( packetTicks ), + U32( getCurrentTick() ), + mPacketQueue.size(), + dropPacket ? " !! DROPPED !!" : "" ); + #endif + + // Queue the packet. + + if( !dropPacket ) + { + mPacketQueue.pushBack( QueuedPacket( packetStartPos, packetEndPos ) ); + Deref( mConsumer ).write( &packet, 1 ); + } + + mTotalQueuedTicks = packetEndPos; + if( isLast && !mTotalTicks ) + mTotalTicks = mTotalQueuedTicks; + + mTotalQueuedPackets ++; + + return !dropPacket; +} + +#undef DEBUG_SPEW +#endif // _ASYNCPACKETQUEUE_H_ diff --git a/platform/async/asyncPacketStream.h b/platform/async/asyncPacketStream.h new file mode 100644 index 0000000..e0c7381 --- /dev/null +++ b/platform/async/asyncPacketStream.h @@ -0,0 +1,309 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ASYNCPACKETSTREAM_H_ +#define _ASYNCPACKETSTREAM_H_ + +#ifndef _ASYNCBUFFEREDSTREAM_H_ + #include "platform/async/asyncBufferedStream.h" +#endif +#ifndef _RAWDATA_H_ + #include "core/util/rawData.h" +#endif +#ifndef _THREADPOOLASYNCIO_H_ + #include "platform/threads/threadPoolAsyncIO.h" +#endif + + +//#define DEBUG_SPEW + + +/// @file +/// Input stream filter definitions for turning linear streams into +/// streams that yield data in discrete packets using background +/// reads. + + +//-------------------------------------------------------------------------- +// Async stream packets. +//-------------------------------------------------------------------------- + +/// Stream packet read by an asynchronous packet stream. +template< typename T > +class AsyncPacket : public RawDataT< T > +{ + public: + + typedef RawDataT< T > Parent; + + AsyncPacket() + : mIndex( 0 ), mIsLast( false ), mSizeActual( 0 ) {} + AsyncPacket( T* data, U32 size, bool ownMemory = false ) + : Parent( data, size, ownMemory ), + mIndex( 0 ), mIsLast( false ), mSizeActual( 0 ) {} + + /// Running number in stream. + U32 mIndex; + + /// Number of items that have actually been read into the packet. + /// This may be less than "size" for end-of-stream packets in non-looping + /// streams. + /// + /// @note Extraneous space at the end of the packet will be cleared using + /// constructArray() calls. + U32 mSizeActual; + + /// If true this is the last packet in the stream. + bool mIsLast; +}; + +//-------------------------------------------------------------------------- +// Async packet streams. +//-------------------------------------------------------------------------- + +/// A packet stream turns a continuous stream of elements into a +/// stream of discrete packets of elements. +/// +/// All packets are of the exact same size even if, for end-of-stream +/// packets, they actually contain less data than their actual size. +/// Extraneous space is cleared. +/// +/// @note For looping streams, the stream must implement the +/// IResettable interface. +template< typename Stream, class Packet = AsyncPacket< typename TypeTraits< Stream >::BaseType::ElementType > > +class AsyncPacketBufferedInputStream : public AsyncBufferedInputStream< Packet*, Stream > +{ + public: + + typedef AsyncBufferedInputStream< Packet*, Stream > Parent; + typedef Packet PacketType; + typedef typename TypeTraits< Stream >::BaseType StreamType; + + protected: + + class PacketReadItem; + friend class PacketReadItem; // _onArrival + + /// Asynchronous work item for reading a packet from the source stream. + class PacketReadItem : public AsyncReadItem< typename Parent::SourceElementType, StreamType > + { + public: + + typedef AsyncReadItem< typename AsyncPacketBufferedInputStream< Stream, Packet >::SourceElementType, StreamType > Parent; + + PacketReadItem( const ThreadSafeRef< AsyncPacketBufferedInputStream< Stream, Packet > >& asyncStream, + PacketType* packet, + U32 numElements, + ThreadPool::Context* context = NULL ) + : Parent( asyncStream->getSourceStream(), numElements, 0, *packet, false, 0, context ), + mAsyncStream( asyncStream ), + mPacket( packet ) {} + + protected: + + typedef ThreadSafeRef< AsyncPacketBufferedInputStream< Stream, Packet > > AsyncPacketStreamPtr; + + /// The issueing async state. + AsyncPacketStreamPtr mAsyncStream; + + /// The packet that receives the data. + PacketType* mPacket; + + // WorkItem + virtual void execute() + { + Parent::execute(); + mPacket->mSizeActual += this->mNumElementsRead; + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[AsyncPacketStream] read %i elements into packet #%i with size %i", + this->mNumElementsRead, mPacket->mIndex, mPacket->size ); + #endif + + // Handle extraneous space at end of packet. + + if( this->cancellationPoint() ) return; + U32 numExtraElements = mPacket->size - this->mNumElementsRead; + if( numExtraElements ) + { + if( mAsyncStream->mIsLooping + && dynamic_cast< IResettable* >( &Deref( this->getStream() ) ) ) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[AsyncPacketStream] resetting stream and reading %i more elements", numExtraElements ); + #endif + + // Wrap around and start re-reading from beginning of stream. + + dynamic_cast< IResettable* >( &Deref( this->getStream() ) )->reset(); + + this->mOffsetInBuffer += this->mNumElementsRead; + this->mOffsetInStream = 0; + this->mNumElements = numExtraElements; + + this->_prep(); + Parent::execute(); + + mPacket->mSizeActual += this->mNumElementsRead; + } + else + constructArray( &mPacket->data[ this->mNumElementsRead ], numExtraElements ); + } + + // Buffer the packet. + + if( this->cancellationPoint() ) return; + mAsyncStream->_onArrival( mPacket ); + } + virtual void onCancelled() + { + Parent::onCancelled(); + destructSingle< PacketType* >( mPacket ); + mAsyncStream = NULL; + } + }; + + typedef ThreadSafeRef< PacketReadItem > PacketReadItemRef; + + /// Number of elements to read per packet. + U32 mPacketSize; + + /// Running number of next stream packet. + U32 mNextPacketIndex; + + /// Total number of elements in the source stream. + U32 mNumTotalSourceElements; + + /// Create a new stream packet of the given size. + virtual PacketType* _newPacket( U32 packetSize ) { return constructSingle< PacketType* >( packetSize ); } + + /// Request the next packet from the underlying stream. + virtual void _requestNext(); + + /// Create a new work item that reads "numElements" into "packet". + virtual void _newReadItem( PacketReadItemRef& outRef, + PacketType* packet, + U32 numElements ) + { + outRef = new PacketReadItem( this, packet, numElements, this->mThreadContext ); + } + + public: + + /// Construct a new packet stream reading from "stream". + /// + /// @note If looping is used and "stream" is not read from the beginning, "stream" should + /// implement IPositionable or ISizeable so the async stream can tell how many elements + /// there actually are in the stream after resetting. + /// + /// @param stream The source stream from which to read the actual data elements. + /// @param packetSize Size of stream packets returned by the stream in number of elements. + /// @param numSourceElementsToRead Number of elements to read from "stream". + /// @param numReadAhead Number of packets to read and buffer in advance. + /// @param isLooping If true, the packet stream will loop infinitely over the source stream. + /// @param pool The ThreadPool to use for asynchronous packet reads. + /// @param context The ThreadContext to place asynchronous packet reads in. + AsyncPacketBufferedInputStream( const Stream& stream, + U32 packetSize, + U32 numSourceElementsToRead = 0, + U32 numReadAhead = Parent::DEFAULT_STREAM_LOOKAHEAD, + bool isLooping = false, + ThreadPool* pool = &ThreadPool::GLOBAL(), + ThreadContext* context = ThreadContext::ROOT_CONTEXT() ); + + /// @return the size of stream packets returned by this stream in number of elements. + U32 getPacketSize() const { return mPacketSize; } +}; + +template< typename Stream, class Packet > +AsyncPacketBufferedInputStream< Stream, Packet >::AsyncPacketBufferedInputStream + ( const Stream& stream, + U32 packetSize, + U32 numSourceElementsToRead, + U32 numReadAhead, + bool isLooping, + ThreadPool* threadPool, + ThreadContext* threadContext ) + : Parent( stream, numSourceElementsToRead, numReadAhead, isLooping, threadPool, threadContext ), + mPacketSize( packetSize ), + mNumTotalSourceElements( numSourceElementsToRead ), + mNextPacketIndex( 0 ) +{ + AssertFatal( mPacketSize > 0, + "AsyncPacketStream::AsyncPacketStream() - packet size cannot be zero" ); + + // Determine total number of elements in stream, if possible. + + IPositionable< U32 >* positionable = dynamic_cast< IPositionable< U32 >* >( &Deref( stream ) ); + if( positionable ) + mNumTotalSourceElements += positionable->getPosition(); + else + { + ISizeable< U32 >* sizeable = dynamic_cast< ISizeable< U32 >* >( &Deref( stream ) ); + if( sizeable ) + mNumTotalSourceElements = sizeable->getSize(); + } + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[AsyncPacketStream] %i remaining, %i total (%i packets)", + this->mNumRemainingSourceElements, mNumTotalSourceElements, + ( this->mNumRemainingSourceElements / mPacketSize ) + ( this->mNumRemainingSourceElements % mPacketSize ? 1 : 0 ) ); + #endif +} + +template< typename Stream, class Packet > +void AsyncPacketBufferedInputStream< Stream, Packet >::_requestNext() +{ + Stream& stream = this->getSourceStream(); + bool isEOS = !this->mNumRemainingSourceElements; + if( isEOS && this->mIsLooping ) + { + StreamType* s = &Deref( stream ); + if( dynamic_cast< IResettable* >( s ) ) + { + reinterpret_cast< IResettable* >( s )->reset(); + isEOS = false; + this->mNumRemainingSourceElements = mNumTotalSourceElements; + } + } + else if( isEOS ) + return; + + //TODO: scale priority depending on feed status + + // Allocate a packet. + + U32 numElements = mPacketSize; + PacketType* packet = _newPacket( numElements ); + packet->mIndex = mNextPacketIndex; + mNextPacketIndex ++; + + // Queue a stream packet work item. + + if( numElements >= this->mNumRemainingSourceElements ) + { + if( !this->mIsLooping ) + { + this->mNumRemainingSourceElements = 0; + packet->mIsLast = true; + } + else + this->mNumRemainingSourceElements = ( this->mNumTotalSourceElements - numElements + this->mNumRemainingSourceElements ); + } + else + this->mNumRemainingSourceElements -= numElements; + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[AsyncPacketStream] packet %i, %i remaining, %i total", + packet->mIndex, this->mNumRemainingSourceElements, mNumTotalSourceElements ); + #endif + + ThreadSafeRef< PacketReadItem > workItem; + _newReadItem( workItem, packet, numElements ); + this->mThreadPool->queueWorkItem( workItem ); +} + +#undef DEBUG_SPEW +#endif // !_ASYNCPACKETSTREAM_H_ diff --git a/platform/async/asyncUpdate.cpp b/platform/async/asyncUpdate.cpp new file mode 100644 index 0000000..a228681 --- /dev/null +++ b/platform/async/asyncUpdate.cpp @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/async/asyncUpdate.h" +#include "core/stream/tStream.h" + + +//----------------------------------------------------------------------------- +// AsyncUpdateList implementation. +//----------------------------------------------------------------------------- + +void AsyncUpdateList::process( S32 timeOut ) +{ + U32 endTime = 0; + if( timeOut != -1 ) + endTime = Platform::getRealMilliseconds() + timeOut; + + // Flush the process list. + + IPolled* ptr; + IPolled* firstProcessedPtr = 0; + + while( mUpdateList.tryPopFront( ptr ) ) + { + if( ptr == firstProcessedPtr ) + { + // We've wrapped around. Stop. + + mUpdateList.pushFront( ptr ); + break; + } + + if( ptr->update() ) + { + mUpdateList.pushBack( ptr ); + + if( !firstProcessedPtr ) + firstProcessedPtr = ptr; + } + + // Stop if we have exceeded our processing time budget. + + if( timeOut != -1 + && Platform::getRealMilliseconds() >= endTime ) + break; + } +} + +//-------------------------------------------------------------------------- +// AsyncUpdateThread implementation. +//-------------------------------------------------------------------------- + +void AsyncUpdateThread::run( void* ) +{ + _setName( getName() ); + + while( !checkForStop() ) + { + _waitForEventAndReset(); + + if( !checkForStop() ) + mUpdateList->process(); + } +} diff --git a/platform/async/asyncUpdate.h b/platform/async/asyncUpdate.h new file mode 100644 index 0000000..ec5f17c --- /dev/null +++ b/platform/async/asyncUpdate.h @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ASYNCUPDATE_H_ +#define _ASYNCUPDATE_H_ + +#ifndef _PLATFORM_THREADS_THREAD_H_ +# include "platform/threads/thread.h" +#endif +#ifndef _THREADSAFEREFCOUNT_H_ +# include "platform/threads/threadSafeRefCount.h" +#endif +#ifndef _THREADSAFEDEQUE_H_ +# include "platform/threads/threadSafeDeque.h" +#endif + + +class IPolled; + +//-------------------------------------------------------------------------- +// Async update list. +//-------------------------------------------------------------------------- + +/// This structure keeps track of the objects that need +/// updating. +class AsyncUpdateList : public ThreadSafeRefCount< AsyncUpdateList > +{ + protected: + + typedef ThreadSafeDeque< IPolled* > UpdateList; + + /// List of structures currently in the update loop. + UpdateList mUpdateList; + + public: + + virtual ~AsyncUpdateList() {} + + /// Update the structures currently on the processing list. + /// + /// @param timeOut Soft limit in milliseconds on the time + /// spent on flushing the list. Default of -1 means no + /// limit and function will only return, if update list + /// has been fully flushed. + virtual void process( S32 timeOut = -1 ); + + /// Add the structure to the update list. It will stay + /// on this list, until its update() method returns false. + /// + /// @note This can be called on different threads. + virtual void add( IPolled* ptr ) + { + mUpdateList.pushBack( ptr ); + } +}; + +//-------------------------------------------------------------------------- +// Async update thread. +//-------------------------------------------------------------------------- + +/// Abstract baseclass for async update threads. +class AsyncUpdateThread : public Thread, public ThreadSafeRefCount< AsyncUpdateThread > +{ + public: + + typedef Thread Parent; + + protected: + + /// Name of this thread. + String mName; + + /// Platform-dependent event data. + void* mUpdateEvent; + + /// The update list processed on this thread. + ThreadSafeRef< AsyncUpdateList > mUpdateList; + + /// Wait for an update event being triggered and + /// immediately reset the event. + /// + /// @note Note that this must be an atomic operation to avoid + /// a race condition. Immediately resetting the event shields + /// us from event releases happening during us updating getting + /// ignored. + virtual void _waitForEventAndReset(); + + public: + + /// Create the update thread. + /// The thread won't immediately start (we have virtual functions + /// so construction needs to finish first) and will not auto-delete + /// itself. + AsyncUpdateThread( String name, AsyncUpdateList* updateList ); + + virtual ~AsyncUpdateThread(); + + virtual void run( void* ); + + /// Trigger the update event to notify the thread about + /// pending updates. + virtual void triggerUpdate(); + + /// + const String& getName() const { return mName; } + + /// + void* getUpdateEvent() const { return mUpdateEvent; } +}; + +/// Extension to update thread that also does automatic +/// periodic updates. +class AsyncPeriodicUpdateThread : public AsyncUpdateThread +{ + typedef AsyncUpdateThread Parent; + + protected: + + /// Platform-dependent timer event. + void* mUpdateTimer; + + /// Time between periodic updates in milliseconds. + U32 mIntervalMS; + + virtual void _waitForEventAndReset(); + + public: + + enum + { + /// Default interval between periodic updates in milliseconds. + DEFAULT_UPDATE_INTERVAL = 4000 + }; + + /// + AsyncPeriodicUpdateThread( String name, + AsyncUpdateList* updateList, + U32 intervalMS = DEFAULT_UPDATE_INTERVAL ); + + virtual ~AsyncPeriodicUpdateThread(); +}; + +#endif // _TORQUE_CORE_ASYNC_ASYNCUPDATE_H_ diff --git a/platform/event.h b/platform/event.h new file mode 100644 index 0000000..5878a38 --- /dev/null +++ b/platform/event.h @@ -0,0 +1,370 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +/// @file +/// Library-wide input events +/// +/// All external events are converted into system events, which are defined +/// in this file. + +/// +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#include "platform/types.h" +#include "core/util/journal/journaledSignal.h" + +/// @defgroup input_constants Input system constants +/// @{ + +/// Input event constants: +enum InputObjectInstances +{ + KEY_NULL = 0x000, ///< Invalid KeyCode + KEY_BACKSPACE = 0x001, + KEY_TAB = 0x002, + KEY_RETURN = 0x003, + KEY_CONTROL = 0x004, + KEY_ALT = 0x005, + KEY_SHIFT = 0x006, + KEY_PAUSE = 0x007, + KEY_CAPSLOCK = 0x008, + KEY_ESCAPE = 0x009, + KEY_SPACE = 0x00a, + KEY_PAGE_DOWN = 0x00b, + KEY_PAGE_UP = 0x00c, + KEY_END = 0x00d, + KEY_HOME = 0x00e, + KEY_LEFT = 0x00f, + KEY_UP = 0x010, + KEY_RIGHT = 0x011, + KEY_DOWN = 0x012, + KEY_PRINT = 0x013, + KEY_INSERT = 0x014, + KEY_DELETE = 0x015, + KEY_HELP = 0x016, + + KEY_0 = 0x017, + KEY_1 = 0x018, + KEY_2 = 0x019, + KEY_3 = 0x01a, + KEY_4 = 0x01b, + KEY_5 = 0x01c, + KEY_6 = 0x01d, + KEY_7 = 0x01e, + KEY_8 = 0x01f, + KEY_9 = 0x020, + + KEY_A = 0x021, + KEY_B = 0x022, + KEY_C = 0x023, + KEY_D = 0x024, + KEY_E = 0x025, + KEY_F = 0x026, + KEY_G = 0x027, + KEY_H = 0x028, + KEY_I = 0x029, + KEY_J = 0x02a, + KEY_K = 0x02b, + KEY_L = 0x02c, + KEY_M = 0x02d, + KEY_N = 0x02e, + KEY_O = 0x02f, + KEY_P = 0x030, + KEY_Q = 0x031, + KEY_R = 0x032, + KEY_S = 0x033, + KEY_T = 0x034, + KEY_U = 0x035, + KEY_V = 0x036, + KEY_W = 0x037, + KEY_X = 0x038, + KEY_Y = 0x039, + KEY_Z = 0x03a, + + KEY_TILDE = 0x03b, + KEY_MINUS = 0x03c, + KEY_EQUALS = 0x03d, + KEY_LBRACKET = 0x03e, + KEY_RBRACKET = 0x03f, + KEY_BACKSLASH = 0x040, + KEY_SEMICOLON = 0x041, + KEY_APOSTROPHE = 0x042, + KEY_COMMA = 0x043, + KEY_PERIOD = 0x044, + KEY_SLASH = 0x045, + KEY_NUMPAD0 = 0x046, + KEY_NUMPAD1 = 0x047, + KEY_NUMPAD2 = 0x048, + KEY_NUMPAD3 = 0x049, + KEY_NUMPAD4 = 0x04a, + KEY_NUMPAD5 = 0x04b, + KEY_NUMPAD6 = 0x04c, + KEY_NUMPAD7 = 0x04d, + KEY_NUMPAD8 = 0x04e, + KEY_NUMPAD9 = 0x04f, + KEY_MULTIPLY = 0x050, + KEY_ADD = 0x051, + KEY_SEPARATOR = 0x052, + KEY_SUBTRACT = 0x053, + KEY_DECIMAL = 0x054, + KEY_DIVIDE = 0x055, + KEY_NUMPADENTER = 0x056, + + KEY_F1 = 0x057, + KEY_F2 = 0x058, + KEY_F3 = 0x059, + KEY_F4 = 0x05a, + KEY_F5 = 0x05b, + KEY_F6 = 0x05c, + KEY_F7 = 0x05d, + KEY_F8 = 0x05e, + KEY_F9 = 0x05f, + KEY_F10 = 0x060, + KEY_F11 = 0x061, + KEY_F12 = 0x062, + KEY_F13 = 0x063, + KEY_F14 = 0x064, + KEY_F15 = 0x065, + KEY_F16 = 0x066, + KEY_F17 = 0x067, + KEY_F18 = 0x068, + KEY_F19 = 0x069, + KEY_F20 = 0x06a, + KEY_F21 = 0x06b, + KEY_F22 = 0x06c, + KEY_F23 = 0x06d, + KEY_F24 = 0x06e, + + KEY_NUMLOCK = 0x06f, + KEY_SCROLLLOCK = 0x070, + KEY_LCONTROL = 0x071, + KEY_RCONTROL = 0x072, + KEY_LALT = 0x073, + KEY_RALT = 0x074, + KEY_LSHIFT = 0x075, + KEY_RSHIFT = 0x076, + KEY_WIN_LWINDOW = 0x077, + KEY_WIN_RWINDOW = 0x078, + KEY_WIN_APPS = 0x079, + KEY_OEM_102 = 0x080, + + KEY_MAC_OPT = 0x090, + KEY_MAC_LOPT = 0x091, + KEY_MAC_ROPT = 0x092, + + KEY_BUTTON0 = 0x0100, + KEY_BUTTON1 = 0x0101, + KEY_BUTTON2 = 0x0102, + KEY_BUTTON3 = 0x0103, + KEY_BUTTON4 = 0x0104, + KEY_BUTTON5 = 0x0105, + KEY_BUTTON6 = 0x0106, + KEY_BUTTON7 = 0x0107, + KEY_BUTTON8 = 0x0108, + KEY_BUTTON9 = 0x0109, + KEY_BUTTON10 = 0x010A, + KEY_BUTTON11 = 0x010B, + KEY_BUTTON12 = 0x010C, + KEY_BUTTON13 = 0x010D, + KEY_BUTTON14 = 0x010E, + KEY_BUTTON15 = 0x010F, + KEY_BUTTON16 = 0x0110, + KEY_BUTTON17 = 0x0111, + KEY_BUTTON18 = 0x0112, + KEY_BUTTON19 = 0x0113, + KEY_BUTTON20 = 0x0114, + KEY_BUTTON21 = 0x0115, + KEY_BUTTON22 = 0x0116, + KEY_BUTTON23 = 0x0117, + KEY_BUTTON24 = 0x0118, + KEY_BUTTON25 = 0x0119, + KEY_BUTTON26 = 0x011A, + KEY_BUTTON27 = 0x011B, + KEY_BUTTON28 = 0x011C, + KEY_BUTTON29 = 0x011D, + KEY_BUTTON30 = 0x011E, + KEY_BUTTON31 = 0x011F, + KEY_ANYKEY = 0xfffe, + + /// Joystick event codes. + SI_XPOV = 0x204, + SI_YPOV = 0x205, + SI_UPOV = 0x206, + SI_DPOV = 0x207, + SI_LPOV = 0x208, + SI_RPOV = 0x209, + SI_XAXIS = 0x20B, + SI_YAXIS = 0x20C, + SI_ZAXIS = 0x20D, + SI_RXAXIS = 0x20E, + SI_RYAXIS = 0x20F, + SI_RZAXIS = 0x210, + SI_SLIDER = 0x211, + SI_XPOV2 = 0x212, + SI_YPOV2 = 0x213, + SI_UPOV2 = 0x214, + SI_DPOV2 = 0x215, + SI_LPOV2 = 0x216, + SI_RPOV2 = 0x217, + + XI_CONNECT = 0x300, + XI_THUMBLX = 0x301, + XI_THUMBLY = 0x302, + XI_THUMBRX = 0x303, + XI_THUMBRY = 0x304, + XI_LEFT_TRIGGER = 0x305, + XI_RIGHT_TRIGGER = 0x306, + + XI_START = 0x311, + XI_BACK = 0x312, + XI_LEFT_THUMB = 0x313, + XI_RIGHT_THUMB = 0x314, + XI_LEFT_SHOULDER = 0x315, + XI_RIGHT_SHOULDER = 0x316, + + XI_A = 0x317, + XI_B = 0x318, + XI_X = 0x319, + XI_Y = 0x320, +}; + +/// Input device types +enum InputDeviceTypes +{ + UnknownDeviceType, + MouseDeviceType, + KeyboardDeviceType, + JoystickDeviceType, + GamepadDeviceType, + XInputDeviceType, + + NUM_INPUT_DEVICE_TYPES +}; + +/// Device Event Action Types +enum InputActionType +{ + /// Button was depressed. + SI_MAKE = 0x01, + + /// Button was released. + SI_BREAK = 0x02, + + /// An axis moved. + SI_MOVE = 0x03, + + /// A key repeat occurred. Happens in between a SI_MAKE and SI_BREAK. + SI_REPEAT = 0x04, +}; + +///Device Event Types +enum InputEventType +{ + SI_UNKNOWN = 0x01, + SI_BUTTON = 0x02, + SI_POV = 0x03, + SI_AXIS = 0x04, + SI_KEY = 0x0A, +}; + +/// Wildcard match used by the input system. +#define SI_ANY 0xff + +// Modifier Keys +enum InputModifiers +{ + /// shift and ctrl are the same between platforms. + SI_LSHIFT = BIT(0), + SI_RSHIFT = BIT(1), + SI_SHIFT = (SI_LSHIFT|SI_RSHIFT), + SI_LCTRL = BIT(2), + SI_RCTRL = BIT(3), + SI_CTRL = (SI_LCTRL|SI_RCTRL), + + /// win altkey, mapped to mac cmdkey. + SI_LALT = BIT(4), + SI_RALT = BIT(5), + SI_ALT = (SI_LALT|SI_RALT), + + /// mac optionkey + SI_MAC_LOPT = BIT(6), + SI_MAC_ROPT = BIT(7), + SI_MAC_OPT = (SI_MAC_LOPT|SI_MAC_ROPT), + + /// modifier keys used for common operations +#if defined(TORQUE_OS_MAC) + SI_COPYPASTE = SI_ALT, + SI_MULTISELECT = SI_ALT, + SI_RANGESELECT = SI_SHIFT, + SI_PRIMARY_ALT = SI_MAC_OPT, ///< Primary key used for toggling into alternates of commands. + SI_PRIMARY_CTRL = SI_ALT, ///< Primary key used for triggering commands. +#else + SI_COPYPASTE = SI_CTRL, + SI_MULTISELECT = SI_CTRL, + SI_RANGESELECT = SI_SHIFT, + SI_PRIMARY_ALT = SI_ALT, + SI_PRIMARY_CTRL = SI_CTRL, +#endif + /// modfier key used in conjunction w/ arrow keys to move cursor to next word +#if defined(TORQUE_OS_MAC) + SI_WORDJUMP = SI_MAC_OPT, +#else + SI_WORDJUMP = SI_CTRL, +#endif + /// modifier key used in conjunction w/ arrow keys to move cursor to beginning / end of line + SI_LINEJUMP = SI_ALT, + + /// modifier key used in conjunction w/ home & end to jump to the top or bottom of a document +#if defined(TORQUE_OS_MAC) + SI_DOCJUMP = SI_ANY, +#else + SI_DOCJUMP = SI_CTRL, +#endif +}; + +/// @} + + +/// Generic input event. +struct InputEventInfo +{ + InputEventInfo() + { + deviceInst = 0; + fValue = 0.f; + deviceType = (InputDeviceTypes)0; + objType = (InputEventType)0; + ascii = 0; + objInst = (InputObjectInstances)0; + action = (InputActionType)0; + modifier = (InputModifiers)0; + } + + /// Device instance: joystick0, joystick1, etc + U32 deviceInst; + + /// Value ranges from -1.0 to 1.0 + F32 fValue; + + /// What was the action? (MAKE/BREAK/MOVE) + InputActionType action; + InputDeviceTypes deviceType; + InputEventType objType; + InputObjectInstances objInst; + + /// ASCII character code if this is a keyboard event. + U16 ascii; + + /// Modifiers to action: SI_LSHIFT, SI_LCTRL, etc. + InputModifiers modifier; + + inline void postToSignal(InputEvent &ie) + { + ie.trigger(deviceInst, fValue, deviceType, objType, ascii, objInst, action, modifier); + } +}; + + +#endif diff --git a/platform/menus/menuBar.cpp b/platform/menus/menuBar.cpp new file mode 100644 index 0000000..5bd22fe --- /dev/null +++ b/platform/menus/menuBar.cpp @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/menus/menuBar.h" +#include "platform/menus/popupMenu.h" +#include "gui/core/guiCanvas.h" + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +MenuBar::MenuBar() +{ + createPlatformPopupMenuData(); + + mCanvas = NULL; + + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +MenuBar::~MenuBar() +{ + removeFromCanvas(); + + deletePlatformPopupMenuData(); +} + +IMPLEMENT_CONOBJECT(MenuBar); + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +void MenuBar::addObject(SimObject *obj) +{ + Parent::addObject(obj); + updateMenuBar(dynamic_cast(obj)); +} + +void MenuBar::removeObject(SimObject *obj) +{ + Parent::removeObject(obj); + updateMenuBar(dynamic_cast(obj)); +} + +void MenuBar::insertObject(SimObject *obj, S32 pos) +{ + Parent::addObject(obj); + + if(pos >= size()) + pos = size() - 1; + + if(pos < size()) + { + if(pos < 0) pos = 0; + Parent::reOrder(obj, at(pos)); + } + updateMenuBar(dynamic_cast(obj)); +} + +void MenuBar::pushObject(SimObject *obj) +{ + Parent::pushObject(obj); + updateMenuBar(dynamic_cast(obj)); +} + +void MenuBar::popObject() +{ + Parent::popObject(); + updateMenuBar(); +} + +bool MenuBar::reOrder(SimObject *obj, SimObject *target /*= 0*/) +{ + bool ret = Parent::reOrder(obj, target); + if(ret) + updateMenuBar(dynamic_cast(obj)); + return ret; +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(MenuBar, attachToCanvas, void, 4, 4, "(GuiCanvas, pos)") +{ + object->attachToCanvas(dynamic_cast(Sim::findObject(argv[2])), dAtoi(argv[3])); +} + +ConsoleMethod(MenuBar, removeFromCanvas, void, 2, 2, "()") +{ + object->removeFromCanvas(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(MenuBar, insert, void, 4, 4,"(object, pos) insert object at position") +{ + SimObject* pObject = Sim::findObject(argv[2]); + + if(pObject) + object->insertObject(pObject, dAtoi(argv[3])); +} diff --git a/platform/menus/menuBar.h b/platform/menus/menuBar.h new file mode 100644 index 0000000..b73125b --- /dev/null +++ b/platform/menus/menuBar.h @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/simBase.h" + +#ifndef _MENUBAR_H_ +#define _MENUBAR_H_ + +// Forward Refs +class PlatformMenuBarData; +class PopupMenu; +class GuiCanvas; + +class MenuBar : public SimSet +{ + typedef SimSet Parent; + +protected: + PlatformMenuBarData *mData; + GuiCanvas *mCanvas; + + /// Update the native menu bar to ensure consistency with the set + void updateMenuBar(PopupMenu *menu = NULL); + + void createPlatformPopupMenuData(); + void deletePlatformPopupMenuData(); + +public: + MenuBar(); + virtual ~MenuBar(); + DECLARE_CONOBJECT(MenuBar); + + /// Attach this menu bar to the native menu bar + void attachToCanvas(GuiCanvas *owner, S32 pos); + /// Remove this menu bar from the native menu bar + void removeFromCanvas(); + + /// Returns true if this menu is attached to the menu bar + bool isAttachedToCanvas() { return mCanvas != NULL; } + + virtual void insertObject(SimObject *obj, S32 pos); + + // Overridden SimSet methods to ensure menu bar consistency when attached + virtual void addObject(SimObject *obj); + virtual void removeObject(SimObject *obj); + virtual void pushObject(SimObject *obj); + virtual void popObject(); + + virtual bool reOrder(SimObject *obj, SimObject *target = 0); +}; + +#endif // _MENUBAR_H_ diff --git a/platform/menus/popupMenu.cpp b/platform/menus/popupMenu.cpp new file mode 100644 index 0000000..5b96eeb --- /dev/null +++ b/platform/menus/popupMenu.cpp @@ -0,0 +1,213 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/menus/popupMenu.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "core/util/safeDelete.h" + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +PopupMenu::PopupMenu() : mCanvas(NULL) +{ + createPlatformPopupMenuData(); + + mSubmenus = new SimSet; + mSubmenus->registerObject(); + + mBarTitle = StringTable->insert(""); + mIsPopup = false; + + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +PopupMenu::~PopupMenu() +{ + // This searches the menu bar so is safe to call for menus + // that aren't on it, since nothing will happen. + removeFromMenuBar(); + + SimSet::iterator i; + while((i = mSubmenus->begin()) != mSubmenus->end()) + { + (*i)->deleteObject(); + } + + mSubmenus->deleteObject(); + deletePlatformPopupMenuData(); +} + +IMPLEMENT_CONOBJECT(PopupMenu); + +//----------------------------------------------------------------------------- + +void PopupMenu::initPersistFields() +{ + addField("isPopup", TypeBool, Offset(mIsPopup, PopupMenu), "true if this is a pop-up/context menu. defaults to false."); + addField("barTitle", TypeCaseString, Offset(mBarTitle, PopupMenu), "the title of this menu when attached to a menu bar"); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- + +bool PopupMenu::onAdd() +{ + if(! Parent::onAdd()) + return false; + + createPlatformMenu(); + + Con::executef(this, "onAdd"); + return true; +} + +void PopupMenu::onRemove() +{ + Con::executef(this, "onRemove"); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- + +void PopupMenu::onMenuSelect() +{ + Con::executef(this, "onMenuSelect"); +} + +//----------------------------------------------------------------------------- + +void PopupMenu::onAttachToMenuBar(GuiCanvas *canvas, S32 pos, const char *title) +{ + mCanvas = canvas; + + // Pass on to sub menus + for(SimSet::iterator i = mSubmenus->begin();i != mSubmenus->end();++i) + { + PopupMenu *mnu = dynamic_cast(*i); + if(mnu == NULL) + continue; + + mnu->onAttachToMenuBar(canvas, pos, title); + } + + // Call script + if(isProperlyAdded()) + Con::executef(this, "onAttachToMenuBar", Con::getIntArg(canvas ? canvas->getId() : 0), Con::getIntArg(pos), title); +} + +void PopupMenu::onRemoveFromMenuBar(GuiCanvas *canvas) +{ + mCanvas = NULL; + + // Pass on to sub menus + for(SimSet::iterator i = mSubmenus->begin();i != mSubmenus->end();++i) + { + PopupMenu *mnu = dynamic_cast(*i); + if(mnu == NULL) + continue; + + mnu->onRemoveFromMenuBar(canvas); + } + + // Call script + if(isProperlyAdded()) + Con::executef(this, "onRemoveFromMenuBar", Con::getIntArg(canvas ? canvas->getId() : 0)); +} + +//----------------------------------------------------------------------------- + +bool PopupMenu::onMessageReceived(StringTableEntry queue, const char* event, const char* data) +{ + return Con::executef(this, "onMessageReceived", queue, event, data); +} + + +bool PopupMenu::onMessageObjectReceived(StringTableEntry queue, Message *msg ) +{ + return Con::executef(this, "onMessageReceived", queue, Con::getIntArg(msg->getId())); +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(PopupMenu, insertItem, S32, 3, 5, "(pos[, title][, accelerator])") +{ + return object->insertItem(dAtoi(argv[2]), argc < 4 ? NULL : argv[3], argc < 5 ? "" : argv[4]); +} + +ConsoleMethod(PopupMenu, removeItem, void, 3, 3, "(pos)") +{ + object->removeItem(dAtoi(argv[2])); +} + +ConsoleMethod(PopupMenu, insertSubMenu, S32, 5, 5, "(pos, title, subMenu)") +{ + PopupMenu *mnu = dynamic_cast(Sim::findObject(argv[4])); + if(mnu == NULL) + { + Con::errorf("PopupMenu::insertSubMenu - Invalid PopupMenu object specified for submenu"); + return -1; + } + return object->insertSubMenu(dAtoi(argv[2]), argv[3], mnu); +} + +ConsoleMethod(PopupMenu, setItem, bool, 4, 5, "(pos, title[, accelerator])") +{ + return object->setItem(dAtoi(argv[2]), argv[3], argc < 5 ? "" : argv[4]); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(PopupMenu, enableItem, void, 4, 4, "(pos, enabled)") +{ + object->enableItem(dAtoi(argv[2]), dAtob(argv[3])); +} + +ConsoleMethod(PopupMenu, checkItem, void, 4, 4, "(pos, checked)") +{ + object->checkItem(dAtoi(argv[2]), dAtob(argv[3])); +} + +ConsoleMethod(PopupMenu, checkRadioItem, void, 5, 5, "(firstPos, lastPos, checkPos)") +{ + object->checkRadioItem(dAtoi(argv[2]), dAtoi(argv[3]), dAtoi(argv[4])); +} + +ConsoleMethod(PopupMenu, isItemChecked, bool, 3, 3, "(pos)") +{ + return object->isItemChecked(dAtoi(argv[2])); +} + +ConsoleMethod(PopupMenu, getItemCount, S32, 2, 2, "()") +{ + return object->getItemCount(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(PopupMenu, attachToMenuBar, void, 5, 5, "(GuiCanvas, pos, title)") +{ + object->attachToMenuBar(dynamic_cast(Sim::findObject(argv[2])),dAtoi(argv[3]), argv[4]); +} + +ConsoleMethod(PopupMenu, removeFromMenuBar, void, 2, 2, "()") +{ + object->removeFromMenuBar(); +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(PopupMenu, showPopup, void, 3, 5, "(Canvas,[x, y])") +{ + GuiCanvas *pCanvas = dynamic_cast(Sim::findObject(argv[2])); + S32 x = argc >= 4 ? dAtoi(argv[3]) : -1; + S32 y = argc >= 5 ? dAtoi(argv[4]) : -1; + object->showPopup(pCanvas, x, y); +} diff --git a/platform/menus/popupMenu.h b/platform/menus/popupMenu.h new file mode 100644 index 0000000..f4aa756 --- /dev/null +++ b/platform/menus/popupMenu.h @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "console/simBase.h" +#include "core/util/tVector.h" +#include "util/messaging/dispatcher.h" +#include "gui/core/guiCanvas.h" + +#ifndef _POPUPMENU_H_ +#define _POPUPMENU_H_ + +// Forward ref used by the platform code +struct PlatformPopupMenuData; +class MenuBar; + +// PopupMenu represents a menu. +// You can add menu items to the menu, but there is no torque object associated +// with these menu items, they exist only in a platform specific manner. +class PopupMenu : public SimObject, public virtual Dispatcher::IMessageListener +{ + typedef SimObject Parent; + + friend class MenuBar; + +private: + /// Used by MenuBar to attach the menu to the menu bar. Do not use anywhere else. + void attachToMenuBar(GuiCanvas *owner, S32 pos); + +protected: + PlatformPopupMenuData *mData; + + SimSet *mSubmenus; + SimObjectPtr mCanvas; + + StringTableEntry mBarTitle; + + bool mIsPopup; + +public: + PopupMenu(); + virtual ~PopupMenu(); + void createPlatformPopupMenuData(); + void deletePlatformPopupMenuData(); + + DECLARE_CONOBJECT(PopupMenu); + + static void initPersistFields(); + + virtual bool onAdd(); + virtual void onRemove(); + + /// Creates the platform specific menu object, a peer to this object. + /// The platform menu *must* exist before calling any method that manipulates + /// menu items or displays the menu. + /// implementd on a per-platform basis. + void createPlatformMenu(); + + void setBarTitle(const char * val) { mBarTitle = StringTable->insert(val, true); } + StringTableEntry getBarTitle() const { return mBarTitle; } + + /// pass NULL for @p title to insert a separator + /// returns the menu item's ID, or -1 on failure. + /// implementd on a per-platform basis. + /// TODO: factor out common code + S32 insertItem(S32 pos, const char *title, const char* accelerator); + + /// Sets the name title and accelerator for + /// an existing item. + bool setItem(S32 pos, const char *title, const char* accelerator); + + /// pass NULL for @p title to insert a separator + /// returns the menu item's ID, or -1 on failure. + /// adds the submenu to the mSubmenus vector. + /// implemented on a per-platform basis. + /// TODO: factor out common code + S32 insertSubMenu(S32 pos, const char *title, PopupMenu *submenu); + + /// remove the menu item at @p itemPos + /// if the item has a submenu, it is removed from the mSubmenus list. + /// implemented on a per-platform basis. + /// TODO: factor out common code + void removeItem(S32 itemPos); + + /// implemented on a per-platform basis. + void enableItem(S32 pos, bool enable); + /// implemented on a per-platform basis. + void checkItem(S32 pos, bool checked); + + /// All items at positions firstPos through lastPos are unchecked, and the + /// item at checkPos is checked. + /// implemented on a per-platform basis. + void checkRadioItem(S32 firstPos, S32 lastPos, S32 checkPos); + bool isItemChecked(S32 pos); + + /// Returns the number of items in the menu. + U32 getItemCount(); + + //----------------------------------------------------------------------------- + // New code should not use these methods directly, use the menu bar instead. + // + // They remain for compatibility with old code and will be changing/going away + // once the existing code is moved over to the menu bar. + //----------------------------------------------------------------------------- + + /// Places this menu in the menu bar of the application's main window. + /// @param owner The GuiCanvas that owns the PlatformWindow that this call is associated with + /// @param pos The relative position at which to place the menu. + /// @param title The name of the menu + void attachToMenuBar(GuiCanvas *owner, S32 pos, const char *title); + + /// Removes this menu from the menu bar. + void removeFromMenuBar(); + + //----------------------------------------------------------------------------- + + /// Called when the menu has been attached to the menu bar + void onAttachToMenuBar(GuiCanvas *canvas, S32 pos, const char *title); + + /// Called when the menu has been removed from the menu bar + void onRemoveFromMenuBar(GuiCanvas *canvas); + + /// Returns the position index of this menu on the bar. + S32 getPosOnMenuBar(); + + /// Returns true if this menu is attached to the menu bar + bool isAttachedToMenuBar() { return mCanvas != NULL; } + + /// Displays this menu as a popup menu and blocks until the user has selected + /// an item. + /// @param canvas the owner to show this popup associated with + /// @param x window local x coordinate at which to display the popup menu + /// @param y window local y coordinate at which to display the popup menu + /// implemented on a per-platform basis. + void showPopup(GuiCanvas *owner, S32 x = -1, S32 y = -1); + + /// Returns true iff this menu contains an item that matches @p iD. + /// implemented on a per-platform basis. + /// TODO: factor out common code + bool canHandleID(U32 iD); + + /// A menu item in this menu has been selected by id. + /// Submenus are given a chance to respond to the command first. + /// If no submenu can handle the command id, this menu handles it. + /// The script callback this::onSelectItem( position, text) is called. + /// If @p text is null, then the text arg passed to script is the text of + /// the selected menu item. + /// implemented on a per-platform basis. + /// TODO: factor out common code + bool handleSelect(U32 command, const char *text = NULL); + + void onMenuSelect(); + + virtual bool onMessageReceived(StringTableEntry queue, const char* event, const char* data ); + virtual bool onMessageObjectReceived(StringTableEntry queue, Message *msg ); +}; + +#endif // _POPUPMENU_H_ diff --git a/platform/nativeDialogs/fileDialog.h b/platform/nativeDialogs/fileDialog.h new file mode 100644 index 0000000..3c53405 --- /dev/null +++ b/platform/nativeDialogs/fileDialog.h @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _FILEDIALOG_H_ +#define _FILEDIALOG_H_ +#include "console/simBase.h" + +// [03/14/07] The file dialogs need refactoring, and will be refactored in Jugg. +// Things that might need to change: +// - The interface is not in fact platform agnotsic, it is win32 oriented. +// - Filter format is highly windows specific, and is a little fragile, both for +// win32 and for other platforms. +// - Platform specific path strings are exposed to the console, because the +// protected validators save them as such. +// - Several of the FDS_XXX values are not options we want to give the user, such +// as NOT warning on file overwrite. The values FDS_OVERWRITEPROMPT, +// FDS_MUSTEXIST, and FDS_CHANGEPATH are not good things to give the user. +// - The Execute method is virtual for good reason. It should be implemented for +// each subclass. If common behavior is needed for Execute(), it can be +// factored out in hidden platform specific code. + + +/// @defgroup SystemDialogs Using System Dialogs + +/// @ingroup SystemDialogs +/// FileDialogOpaqueData is both defined and implemented on a platform specific +/// basis. +class FileDialogOpaqueData; + +/// @ingroup SystemDialogs +/// @internal +/// Platform Agnostic Structure for holding information about a file dialog. +struct FileDialogData +{ + +public: + FileDialogData(); + ~FileDialogData(); + + enum DialogStyle + { + FDS_OPEN = BIT(0),///< This is an open dialog. + FDS_SAVE = BIT(1),///< This is a save dialog. + FDS_OVERWRITEPROMPT = BIT(2),///< Can only be used in conjunction with style SaveDialog: prompt for a confirmation if a file will be overwritten. + FDS_MUSTEXIST = BIT(3),///< The user may only select files that actually exist. + FDS_MULTIPLEFILES = BIT(4),///< Can only be used in conjunction with style OpenDialog: allows selecting multiple files. + FDS_CHANGEPATH = BIT(5),///< Change the current working path to the directory where the file(s) chosen by the user are. + FDS_BROWSEFOLDER = BIT(6) ///< Select folders instead of files + }; + U8 mStyle; ///< Specifies the Style of the File Dialog @see DialogStyle + + StringTableEntry mFilters; ///< List of Filters pipe separated e.g. "BMP Files (*.bmp)|*.bmp|JPG Files (*.jpg)|*.jpg" + //StringTableEntry mFiles; // this is never used ///< Should only be referenced when using dialogStyle OpenDialog AND MultipleFiles: List of Files returned pipe separated + StringTableEntry mFile; ///< Should be referenced when dialogStyle MultipleFiles is NOT used: the file path of the user selected file. + StringTableEntry mDefaultPath; ///< Default path of dialog + StringTableEntry mDefaultFile; ///< Default selected file of dialog + StringTableEntry mTitle; ///< Title to display in file dialog + + FileDialogOpaqueData *mOpaqueData; ///< Stores platform specific info about the dialog + +}; + +/// @ingroup SystemDialogs +/// FileDialog is a platform agnostic dialog interface for querying the user for +/// file locations. It is designed to be used through the exposed +/// scripting interface. +/// +/// FileDialog is the base class for Native File Dialog controls in Torque. It provides these +/// basic areas of functionality: +/// +/// - Inherits from SimObject and is exposed to the scripting interface +/// - Provides blocking interface to allow instant return to script execution +/// - Simple object configuration makes practical use easy and effective +/// +/// @attention +/// FileDialog is *NOT* intended to be used directly in script and is only exposed to script +/// to expose generic file dialog attributes. +/// @see OpenFileDialog for a practical example on opening a file +/// @see SaveFileDialog for a practical example of saving a file +/// +/// +/// @{ +class FileDialog : public SimObject +{ + typedef SimObject Parent; + +protected: + FileDialogData mData; ///< Stores platform agnostic information about the dialogs properties + bool mChangePath; ///< Exposed ChangePath Property + bool mBoolTranslator; ///< Internally used to translate boolean values into their respective bits of dialog style +public: + + FileDialog(); + virtual ~FileDialog(); + DECLARE_CONOBJECT(FileDialog); + + static void initPersistFields(); + + virtual bool Execute(); + + FileDialogData &getData() { return mData; }; +protected: + /// @name FileDialog Properties + /// @{ + /// @@property DefaultPath (String) : Path to use as the default when the dialog is shown. + /// @code %fd.DefaultPath = "/source/myGameProject/data/images"; @endcode + /// + /// @li @b ChangePath (bool) : Will change the working path of the tools to the selected path when not canceled + /// @code %fd.ChangePath = true; // Change Working Path on Success @endcode + /// @internal + static bool setDefaultPath(void* obj, const char* data); + static bool setDefaultFile(void* obj, const char* data); + static bool setFilters(void* obj, const char* data); + static bool setChangePath(void* obj, const char* data); + static const char* getChangePath(void* obj, const char* data); + /// + /// @} + + static bool setFile(void* obj, const char* data); +}; +/// @} + +class OpenFileDialog : public FileDialog +{ + typedef FileDialog Parent; + + /// Field Values + /// @{ + /// @internal + bool mMustExist; ///< Corresponds to FDS_MUSTEXIST flag on the PlatformFileDlgData structure + bool mMultipleFiles; ///< Corresponds to the FDS_MULTIPLEFILES flag on the PlatformFileDlgData structure + /// @} + +public: + + OpenFileDialog(); + virtual ~OpenFileDialog(); + + DECLARE_CONOBJECT(OpenFileDialog); /// @internal + + static void initPersistFields(); + +protected: + /// + /// @} + + /// Must Exist Property + static bool setMustExist(void* obj, const char* data); + static const char*getMustExist(void* obj, const char* data); + + /// Multiple Files Property + static bool setMultipleFiles(void* obj, const char* data); + static const char* getMultipleFiles(void* obj, const char* data); +}; + +class OpenFolderDialog : public OpenFileDialog +{ + typedef OpenFileDialog Parent; + +public: + StringTableEntry mMustExistInDir; + + OpenFolderDialog(); + DECLARE_CONOBJECT(OpenFolderDialog); + + static void initPersistFields(); +}; + +class SaveFileDialog : public FileDialog +{ + typedef FileDialog Parent; + +public: + + SaveFileDialog(); + virtual ~SaveFileDialog(); + DECLARE_CONOBJECT(SaveFileDialog); + + bool mOverwritePrompt; + + static void initPersistFields(); + +protected: + // Overwrite Prompt Property + static bool setOverwritePrompt(void* obj, const char* data); + static const char* getOverwritePrompt(void* obj, const char* data); + +}; + +#endif // _FILEDIALOG_H_ diff --git a/platform/nativeDialogs/msgBox.cpp b/platform/nativeDialogs/msgBox.cpp new file mode 100644 index 0000000..eb6e25f --- /dev/null +++ b/platform/nativeDialogs/msgBox.cpp @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" + +#include "console/console.h" + +#include "platform/nativeDialogs/msgBox.h" + +// these are the return values for message box dialog buttons +void initMessageBoxVars() +{ + Con::setIntVariable("$MROk", MROk); + Con::setIntVariable("$MRCancel", MRCancel); + Con::setIntVariable("$MRRetry", MRRetry); + Con::setIntVariable("$MRDontSave", MRDontSave); +} + +//----------------------------------------------------------------------------- + +static EnumTable::Enums sgButtonEnums[] = +{ + { MBOk, "Ok" }, + { MBOkCancel, "OkCancel" }, + { MBRetryCancel, "RetryCancel" }, + { MBSaveDontSave, "SaveDontSave" }, // maps to yes/no on win, to save/discard on mac. + { MBSaveDontSaveCancel, "SaveDontSaveCancel" }, // maps to yes/no/cancel on win, to save/cancel/don'tsave on mac. + { 0, NULL } +}; + +static EnumTable::Enums sgIconEnums[] = +{ + { MIInformation, "Information" },// win: blue i, mac: app icon or talking head + { MIWarning, "Warning" }, // win & mac: yellow triangle with exclamation pt + { MIStop, "Stop" }, // win: red x, mac: app icon or stop icon, depending on version + { MIQuestion, "Question" }, // win: blue ?, mac: app icon + { 0, NULL } +}; + +//----------------------------------------------------------------------------- + +static S32 getIDFromName(const EnumTable::Enums *table, const char *name, S32 def = -1) +{ + for(S32 i = 0;table[i].label != NULL;++i) + { + if(dStricmp(table[i].label, name) == 0) + return table[i].index; + } + AssertWarn(false,"getIDFromName(): didn't find that name" ); + return def; +} + +//----------------------------------------------------------------------------- + +ConsoleFunction(messageBox, S32, 3, 5, "(title, message[, buttons[, icon]])") +{ + S32 btns = MBOkCancel; + S32 icns = MIInformation; + + if(argc > 3) + btns = getIDFromName(sgButtonEnums, argv[3], btns); + if(argc > 4) + icns = getIDFromName(sgIconEnums, argv[4], icns); + + return Platform::messageBox(argv[1], argv[2], (MBButtons)btns, (MBIcons)icns); +} diff --git a/platform/nativeDialogs/msgBox.h b/platform/nativeDialogs/msgBox.h new file mode 100644 index 0000000..7f5bfc7 --- /dev/null +++ b/platform/nativeDialogs/msgBox.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MSGBOX_H_ +#define _MSGBOX_H_ + +// [tom, 10/17/2006] Note: If you change either of these enums, make sure you +// update the relevant code in the all the platform layers. + +// [pauls, 3/20/2007] Reduced the available types of dialog boxes in order to +// maintain a consistent but platform - appropriate look and feel in Torque. + +enum MBButtons +{ + MBOk, + MBOkCancel, + MBRetryCancel, + MBSaveDontSave, + MBSaveDontSaveCancel, +}; + +enum MBIcons +{ + MIWarning, + MIInformation, + MIQuestion, + MIStop, +}; + +enum MBReturnVal +{ + MROk = 1, // Start from 1 to allow use of 0 for errors + MRCancel, + MRRetry, + MRDontSave, +}; + +extern void initMessageBoxVars(); + +#endif // _MSGBOX_H_ diff --git a/platform/platform.cpp b/platform/platform.cpp new file mode 100644 index 0000000..84476ca --- /dev/null +++ b/platform/platform.cpp @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "platform/threads/mutex.h" +#include "app/mainLoop.h" +#include "platform/event.h" +#include "platform/typetraits.h" + + +const F32 TypeTraits< F32 >::MIN = - F32_MAX; +const F32 TypeTraits< F32 >::MAX = F32_MAX; +const F32 TypeTraits< F32 >::ZERO = 0; + +// The tools prefer to allow the CPU time to process +#ifndef TORQUE_TOOLS +S32 sgBackgroundProcessSleepTime = 25; +#else +S32 sgBackgroundProcessSleepTime = 200; +#endif +S32 sgTimeManagerProcessInterval = 1; + +Vector gKeyboardExclusionList; +bool gInitKeyboardExclusionList = false; +static bool gWebDeployment = false; + +void Platform::initConsole() +{ + Con::addVariable("Pref::backgroundSleepTime", TypeS32, &sgBackgroundProcessSleepTime); + Con::addVariable("Pref::timeManagerProcessInterval", TypeS32, &sgTimeManagerProcessInterval); +} + +S32 Platform::getBackgroundSleepTime() +{ + return sgBackgroundProcessSleepTime; +} + +ConsoleToolFunction(restartInstance, void, 1, 1, "restartInstance()") +{ + StandardMainLoop::setRestart(true); + Platform::postQuitMessage( 0 ); +} + +void Platform::clearKeyboardInputExclusion() +{ + gKeyboardExclusionList.clear(); + gInitKeyboardExclusionList = true; +} + +void Platform::addKeyboardInputExclusion(const KeyboardInputExclusion &kie) +{ + gKeyboardExclusionList.push_back(kie); +} + +const bool Platform::checkKeyboardInputExclusion(const InputEventInfo *info) +{ + // Do one-time initialization of platform defaults. + if(!gInitKeyboardExclusionList) + { + gInitKeyboardExclusionList = true; + + // CodeReview Looks like we don't even need to do #ifdefs here since + // things like cmd-tab don't appear on windows, and alt-tab is an unlikely + // desired bind on other platforms - might be best to simply have a + // global exclusion list and keep it standard on all platforms. + // This might not be so, but it's the current assumption. [bjg 5/4/07] + + // Alt-tab + { + KeyboardInputExclusion kie; + kie.key = KEY_TAB; + kie.orModifierMask = SI_ALT; + addKeyboardInputExclusion(kie); + } + + // ... others go here... + } + + // Walk the list and look for matches. + for(S32 i=0; iobjType != SI_KEY) + return false; + + if(info->objInst != key) + return false; + + if((info->modifier & andModifierMask) != andModifierMask) + return false; + + if(!(info->modifier & orModifierMask)) + return false; + + return true; +} + +S32 Platform::compareModifiedTimes( const char *firstPath, const char *secondPath ) +{ + FileTime firstModTime; + if ( !getFileTimes( firstPath, NULL, &firstModTime ) ) + return -1; + + FileTime secondModTime; + if ( !getFileTimes( secondPath, NULL, &secondModTime ) ) + return -1; + + return compareFileTimes( firstModTime, secondModTime ); +} + +bool Platform::getWebDeployment() +{ + return gWebDeployment; +} + +void Platform::setWebDeployment(bool v) +{ + gWebDeployment = v; +} + + diff --git a/platform/platform.h b/platform/platform.h new file mode 100644 index 0000000..5e57751 --- /dev/null +++ b/platform/platform.h @@ -0,0 +1,579 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORM_H_ +#define _PLATFORM_H_ + +#include + +#ifndef _TORQUECONFIG_H_ +#include "torqueConfig.h" +#endif +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif +#ifndef _PLATFORMASSERT_H_ +#include "platform/platformAssert.h" +#endif +#ifndef _MSGBOX_H_ +#include "platform/nativeDialogs/msgBox.h" +#endif +#ifndef _VERSION_H_ +#include "app/version.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +#include + +/// Global processor identifiers. +/// +/// @note These enums must be globally scoped so that they work with the inline assembly +enum ProcessorType +{ + // x86 + CPU_X86Compatible, + CPU_Intel_Unknown, + CPU_Intel_486, + CPU_Intel_Pentium, + CPU_Intel_PentiumMMX, + CPU_Intel_PentiumPro, + CPU_Intel_PentiumII, + CPU_Intel_PentiumCeleron, + CPU_Intel_PentiumIII, + CPU_Intel_Pentium4, + CPU_Intel_PentiumM, + CPU_Intel_Core, + CPU_Intel_Core2, + CPU_Intel_Corei7Xeon, // Core i7 or Xeon + CPU_AMD_K6, + CPU_AMD_K6_2, + CPU_AMD_K6_3, + CPU_AMD_Athlon, + CPU_AMD_Unknown, + CPU_Cyrix_6x86, + CPU_Cyrix_MediaGX, + CPU_Cyrix_6x86MX, + CPU_Cyrix_GXm, ///< Media GX w/ MMX + CPU_Cyrix_Unknown, + + // PowerPC + CPU_PowerPC_Unknown, + CPU_PowerPC_601, + CPU_PowerPC_603, + CPU_PowerPC_603e, + CPU_PowerPC_603ev, + CPU_PowerPC_604, + CPU_PowerPC_604e, + CPU_PowerPC_604ev, + CPU_PowerPC_G3, + CPU_PowerPC_G4, + CPU_PowerPC_G4_7450, + CPU_PowerPC_G4_7455, + CPU_PowerPC_G4_7447, + CPU_PowerPC_G5, + + // Xenon + CPU_Xenon, + +}; + +/// Properties for CPU. +enum ProcessorProperties +{ + CPU_PROP_C = (1<<0), ///< We should use C fallback math functions. + CPU_PROP_FPU = (1<<1), ///< Has an FPU. (It better!) + CPU_PROP_MMX = (1<<2), ///< Supports MMX instruction set extension. + CPU_PROP_3DNOW = (1<<3), ///< Supports AMD 3dNow! instruction set extension. + CPU_PROP_SSE = (1<<4), ///< Supports SSE instruction set extension. + CPU_PROP_RDTSC = (1<<5), ///< Supports Read Time Stamp Counter op. + CPU_PROP_SSE2 = (1<<6), ///< Supports SSE2 instruction set extension. + CPU_PROP_SSE3 = (1<<7), ///< Supports SSE3 instruction set extension. + CPU_PROP_SSE3xt = (1<<8), ///< Supports extended SSE3 instruction set + CPU_PROP_SSE4_1 = (1<<9), ///< Supports SSE4_1 instruction set extension. + CPU_PROP_SSE4_2 = (1<<10), ///< Supports SSE4_2 instruction set extension. + CPU_PROP_MP = (1<<11), ///< This is a multi-processor system. + CPU_PROP_LE = (1<<12), ///< This processor is LITTLE ENDIAN. + CPU_PROP_64bit = (1<<13), ///< This processor is 64-bit capable + CPU_PROP_ALTIVEC = (1<<14), ///< Supports AltiVec instruction set extension (PPC only). +}; + +/// Processor info manager. +struct Processor +{ + /// Gather processor state information. + static void init(); +}; + +#if defined(TORQUE_SUPPORTS_GCC_INLINE_X86_ASM) +#define TORQUE_DEBUGBREAK() { asm ( "int 3"); } +#elif defined (TORQUE_SUPPORTS_VC_INLINE_X86_ASM) // put this test second so that the __asm syntax doesn't break the Visual Studio Intellisense parser +#define TORQUE_DEBUGBREAK() { __asm { int 3 }; } +#else +/// Macro to do in-line debug breaks, used for asserts. Does inline assembly when possible. +#define TORQUE_DEBUGBREAK() Platform::debugBreak(); +#endif + +/// Physical type of a drive. +enum DriveType +{ + DRIVETYPE_FIXED = 0, ///< Non-removable fixed drive. + DRIVETYPE_REMOVABLE = 1, ///< Removable drive. + DRIVETYPE_REMOTE = 2, ///< Networked/remote drive. + DRIVETYPE_CDROM = 3, ///< CD-Rom. + DRIVETYPE_RAMDISK = 4, ///< A ramdisk! + DRIVETYPE_UNKNOWN = 5 ///< Don't know. +}; + +// Some forward declares for later. +class Point2I; +template class Vector; +template class Signal; +struct InputEventInfo; + +namespace Platform +{ + // Time + struct LocalTime + { + U8 sec; ///< Seconds after minute (0-59) + U8 min; ///< Minutes after hour (0-59) + U8 hour; ///< Hours after midnight (0-23) + U8 month; ///< Month (0-11; 0=january) + U8 monthday; ///< Day of the month (1-31) + U8 weekday; ///< Day of the week (0-6, 6=sunday) + U16 year; ///< Current year minus 1900 + U16 yearday; ///< Day of year (0-365) + bool isdst; ///< True if daylight savings time is active + }; + + void getLocalTime(LocalTime &); + + /// Converts the local time to a formatted string appropriate + /// for the current platform. + String localTimeToString( const LocalTime < ); + + U32 getTime(); + U32 getVirtualMilliseconds(); + + /// Returns the milliseconds since the system was started. You should + /// not depend on this for high precision timing. + /// @see PlatformTimer + U32 getRealMilliseconds(); + + void advanceTime(U32 delta); + S32 getBackgroundSleepTime(); + + // Platform control + void init(); + void initConsole(); + void shutdown(); + void process(); + + // Math control state + U32 getMathControlState(); + void setMathControlState(U32 state); + void setMathControlStateKnown(); + + // Process control + void sleep(U32 ms); + bool excludeOtherInstances(const char *string); + bool checkOtherInstances(const char *string); + void restartInstance(); + void postQuitMessage(const U32 in_quitVal); + void forceShutdown(S32 returnValue); + + // Debug + void outputDebugString(const char *string, ...); + void debugBreak(); + + // Random + float getRandom(); + + // Window state + void setWindowLocked(bool locked); + void minimizeWindow(); + //const Point2I &getWindowSize(); + void setWindowSize( U32 newWidth, U32 newHeight, bool fullScreen ); + void closeWindow(); + + // File stuff + bool doCDCheck(); + StringTableEntry createPlatformFriendlyFilename(const char *filename); + struct FileInfo + { + const char* pFullPath; + const char* pFileName; + U32 fileSize; + }; + bool cdFileExists(const char *filePath, const char *volumeName, S32 serialNum); + void fileToLocalTime(const FileTime &ft, LocalTime *lt); + /// compare file times returns < 0 if a is earlier than b, >0 if b is earlier than a + S32 compareFileTimes(const FileTime &a, const FileTime &b); + bool stringToFileTime(const char * string, FileTime * time); + bool fileTimeToString(FileTime * time, char * string, U32 strLen); + + /// Compares the last modified time between two file paths. Returns < 0 if + /// the first file is earlier than the second, > 0 if the second file is earlier + /// than the first, and 0 if the files are equal. + /// + /// If either of the files doesn't exist it returns -1. + S32 compareModifiedTimes( const char *firstPath, const char *secondPath ); + + // Directory functions. Dump path returns false iff the directory cannot be + // opened. + + StringTableEntry getCurrentDirectory(); + bool setCurrentDirectory(StringTableEntry newDir); + + StringTableEntry getTemporaryDirectory(); + StringTableEntry getTemporaryFileName(); + + /// Returns the filename of the torque executable. + /// On Win32, this is the .exe file. + /// On Mac, this is the .app/ directory bundle. + StringTableEntry getExecutableName(); + /// Returns full pathname of the torque executable without filename + StringTableEntry getExecutablePath(); + + /// Returns the full path to the directory that contains main.cs. + /// Tools scripts are validated as such if they are in this directory or a + /// subdirectory of this directory. + StringTableEntry getMainDotCsDir(); + + /// Set main.cs directory. Used in runEntryScript() + void setMainDotCsDir(const char *dir); + + StringTableEntry getPrefsPath(const char *file = NULL); + + char *makeFullPathName(const char *path, char *buffer, U32 size, const char *cwd = NULL); + StringTableEntry stripBasePath(const char *path); + bool isFullPath(const char *path); + StringTableEntry makeRelativePathName(const char *path, const char *to); + + String stripExtension( String fileName, Vector< String >& validExtensions ); + + bool dumpPath(const char *in_pBasePath, Vector& out_rFileVector, S32 recurseDepth = -1); + bool dumpDirectories( const char *path, Vector &directoryVector, S32 depth = 0, bool noBasePath = false ); + bool hasSubDirectory( const char *pPath ); + bool getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime); + bool isFile(const char *pFilePath); + S32 getFileSize(const char *pFilePath); + bool isDirectory(const char *pDirPath); + bool isSubDirectory(const char *pParent, const char *pDir); + + void addExcludedDirectory(const char *pDir); + void clearExcludedDirectories(); + bool isExcludedDirectory(const char *pDir); + + /// Given a directory path, create all necessary directories for that path to exist. + bool createPath(const char *path); // create a directory path + + // Alerts + void AlertOK(const char *windowTitle, const char *message); + bool AlertOKCancel(const char *windowTitle, const char *message); + bool AlertRetry(const char *windowTitle, const char *message); + + // Volumes + struct VolumeInformation + { + StringTableEntry RootPath; + StringTableEntry Name; + StringTableEntry FileSystem; + U32 SerialNumber; + U32 Type; + bool ReadOnly; + }; + extern struct VolumeInformation *PVolumeInformation; + + // Volume functions. + void getVolumeNamesList( Vector& out_rNameVector, bool bOnlyFixedDrives = false ); + void getVolumeInformationList( Vector& out_rVolumeInfoVector, bool bOnlyFixedDrives = false ); + + struct SystemInfo_struct + { + struct Processor + { + ProcessorType type; + const char* name; + U32 mhz; + bool isMultiCore; + bool isHyperThreaded; + U32 numLogicalProcessors; + U32 numPhysicalProcessors; + U32 numAvailableCores; + U32 properties; // CPU type specific enum + } processor; + }; + extern Signal SystemInfoReady; + extern SystemInfo_struct SystemInfo; + + // Web page launch function: + bool openWebBrowser( const char* webAddress ); + + // display Splash Window + bool displaySplashWindow( ); + + void openFolder( const char* path ); + + // Open file at the OS level, according to registered file-types. + void openFile( const char* path ); + + const char* getLoginPassword(); + bool setLoginPassword( const char* password ); + + const char* getClipboard(); + bool setClipboard(const char *text); + + // User Specific Functions + StringTableEntry getUserHomeDirectory(); + StringTableEntry getUserDataDirectory(); + bool getUserIsAdministrator(); + + // Displays a fancy platform specific message box + S32 messageBox(const UTF8 *title, const UTF8 *message, MBButtons buttons = MBOkCancel, MBIcons icon = MIInformation); + + /// Description of a keyboard input we want to ignore. + struct KeyboardInputExclusion + { + KeyboardInputExclusion() + { + key = 0; + orModifierMask = 0; + andModifierMask = 0; + } + + /// The key code to ignore, e.g. KEY_TAB. If this and the other + /// conditions match, ignore the key. + S32 key; + + /// if(modifiers | orModifierMask) and the other conditions match, + /// ignore the key. + U32 orModifierMask; + + /// if((modifiers & andModifierMask) == andModifierMask) and the + /// other conditions match, ignore the key stroke. + U32 andModifierMask; + + /// Based on the values above, determine if a given input event + /// matchs this exclusion rule. + const bool checkAgainstInput(const InputEventInfo *info) const; + }; + + /// Reset the keyboard input exclusion list. + void clearKeyboardInputExclusion(); + + /// Add a new keyboard exclusion. + void addKeyboardInputExclusion(const KeyboardInputExclusion &kie); + + /// Check if a given input event should be excluded. + const bool checkKeyboardInputExclusion(const InputEventInfo *info); + + + /// Set/Get whether this is a web deployment + bool getWebDeployment(); + void setWebDeployment(bool v); + +}; + +//------------------------------------------------------------------------------ +// Unicode string conversions +// UNICODE is a windows platform API switching flag. Don't define it on other platforms. +#ifdef UNICODE +#define dT(s) L##s +#else +#define dT(s) s +#endif + +//------------------------------------------------------------------------------ +// Misc StdLib functions +#define QSORT_CALLBACK FN_CDECL +inline void dQsort(void *base, U32 nelem, U32 width, int (QSORT_CALLBACK *fcmp)(const void *, const void *)) +{ + qsort(base, nelem, width, fcmp); +} + +//-------------------------------------- Some all-around useful inlines and globals +// + +///@defgroup ObjTrickery Object Management Trickery +/// +/// These functions are to construct and destruct objects in memory +/// without causing a free or malloc call to occur. This is so that +/// we don't have to worry about allocating, say, space for a hundred +/// NetAddresses with a single malloc call, calling delete on a single +/// NetAdress, and having it try to free memory out from under us. +/// +/// @{ + +/// Constructs an object that already has memory allocated for it. +template +inline T* constructInPlace(T* p) +{ + return new ( p ) T; +} +template< class T > +inline T* constructArrayInPlace( T* p, U32 num ) +{ + return new ( p ) T[ num ]; +} + +/// Copy constructs an object that already has memory allocated for it. +template +inline T* constructInPlace(T* p, const T* copy) +{ + return new ( p ) T( *copy ); +} + +template inline T* constructInPlace(T* ptr, T2 t2) +{ + return new ( ptr ) T( t2 ); +} + +template inline T* constructInPlace(T* ptr, T2 t2, T3 t3) +{ + return new ( ptr ) T( t2, t3 ); +} + +template inline T* constructInPlace(T* ptr, T2 t2, T3 t3, T4 t4) +{ + return new ( ptr ) T( t2, t3, t4 ); +} + +template inline T* constructInPlace(T* ptr, T2 t2, T3 t3, T4 t4, T5 t5) +{ + return new ( ptr ) T( t2, t3, t4, t5 ); +} + +/// Destructs an object without freeing the memory associated with it. +template +inline void destructInPlace(T* p) +{ + p->~T(); +} + +/// This macro is used to execute code during static initialization +/// when the executable is starting up. +/// +/// OnTorqueStartup +/// { +/// // Your code here! +/// } +/// +#define OnTorqueStartup \ + namespace { struct _StaticStartup { _StaticStartup(); }; } \ + static const _StaticStartup _staticStartup; \ + _StaticStartup::_StaticStartup() + + +/// This macro is used to execute code during static destruction +/// when the executable is shutting down. +/// +/// OnTorqueShutdown +/// { +/// // Your code here! +/// } +/// +#define OnTorqueShutdown \ + namespace { struct _StaticShutdown { ~_StaticShutdown(); }; } \ + static const _StaticShutdown _staticShutdown; \ + _StaticShutdown::~_StaticShutdown() + + +//------------------------------------------------------------------------------ +/// Memory functions + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +# define TORQUE_TMM_ARGS_DECL , const char* fileName, const U32 lineNum +# define TORQUE_TMM_ARGS , fileName, lineNum +# define TORQUE_TMM_LOC , __FILE__, __LINE__ + extern void* FN_CDECL operator new(dsize_t size, const char*, const U32); + extern void* FN_CDECL operator new[](dsize_t size, const char*, const U32); + extern void FN_CDECL operator delete(void* ptr); + extern void FN_CDECL operator delete[](void* ptr); +# define _new new(__FILE__, __LINE__) +# define new _new +#else +# define TORQUE_TMM_ARGS_DECL +# define TORQUE_TMM_ARGS +# define TORQUE_TMM_LOC +#endif + +#define dMalloc(x) dMalloc_r(x, __FILE__, __LINE__) +#define dRealloc(x, y) dRealloc_r(x, y, __FILE__, __LINE__) + +extern void setBreakAlloc(dsize_t); +extern void setMinimumAllocUnit(U32); +extern void* dMalloc_r(dsize_t in_size, const char*, const dsize_t); +extern void dFree(void* in_pFree); +extern void* dRealloc_r(void* in_pResize, dsize_t in_size, const char*, const dsize_t); +extern void* dRealMalloc(dsize_t); +extern void dRealFree(void*); + +extern void *dAligned_malloc(dsize_t in_size, dsize_t alignment); +extern void dAligned_free(void*); + +inline void dFree( const void* p ) +{ + dFree( ( void* ) p ); +} + +// Helper function to copy one array into another of different type +template void dCopyArray(T *dst, const S *src, dsize_t size) +{ + for (dsize_t i = 0; i < size; i++) + dst[i] = (T)src[i]; +} + +// Special case of the above function when the arrays are the same type (use memcpy) +template void dCopyArray(T *dst, const T *src, dsize_t size) +{ + dMemcpy(dst, src, size * sizeof(T)); +} + +extern void* dMemcpy(void *dst, const void *src, dsize_t size); +extern void* dMemmove(void *dst, const void *src, dsize_t size); +extern void* dMemset(void *dst, int c, dsize_t size); +extern int dMemcmp(const void *ptr1, const void *ptr2, dsize_t size); + +//------------------------------------------------------------------------------ +// FileIO functions +extern bool dFileDelete(const char *name); +extern bool dFileRename(const char *oldName, const char *newName); +extern bool dFileTouch(const char *name); +extern bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite = true); + +typedef void* FILE_HANDLE; +enum DFILE_STATUS +{ + DFILE_OK = 1 +}; + +extern FILE_HANDLE dOpenFileRead(const char *name, DFILE_STATUS &error); +extern FILE_HANDLE dOpenFileReadWrite(const char *name, bool append, DFILE_STATUS &error); +extern int dFileRead(FILE_HANDLE handle, U32 bytes, char *dst, DFILE_STATUS &error); +extern int dFileWrite(FILE_HANDLE handle, U32 bytes, const char *dst, DFILE_STATUS &error); +extern void dFileClose(FILE_HANDLE handle); + +extern StringTableEntry osGetTemporaryDirectory(); + +//------------------------------------------------------------------------------ +struct Math +{ + /// Initialize the math library with the appropriate libraries + /// to support hardware acceleration features. + /// + /// @param properties Leave zero to detect available hardware. Otherwise, + /// pass CPU instruction set flags that you want to load + /// support for. + static void init(U32 properties = 0); +}; + +/// @} + +#endif + + diff --git a/platform/platformAssert.cpp b/platform/platformAssert.cpp new file mode 100644 index 0000000..228de69 --- /dev/null +++ b/platform/platformAssert.cpp @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include + +#include "core/strings/stringFunctions.h" +#include "console/console.h" + + +//-------------------------------------- STATIC Declaration +PlatformAssert *PlatformAssert::platformAssert = NULL; + +//-------------------------------------- +PlatformAssert::PlatformAssert() +{ + processing = false; +} + +//-------------------------------------- +PlatformAssert::~PlatformAssert() +{ +} + +//-------------------------------------- +void PlatformAssert::create( PlatformAssert* newAssertClass ) +{ + if (!platformAssert) + platformAssert = newAssertClass ? newAssertClass : new PlatformAssert; +} + + +//-------------------------------------- +void PlatformAssert::destroy() +{ + if (platformAssert) + delete platformAssert; + platformAssert = NULL; +} + + +//-------------------------------------- +bool PlatformAssert::displayMessageBox(const char *title, const char *message, bool retry) +{ + if (retry) + return Platform::AlertRetry(title, message); + + Platform::AlertOK(title, message); + return false; +} + +static const char *typeName[] = { "Unknown", "Fatal-ISV", "Fatal", "Warning" }; +//------------------------------------------------------------------------------ +static bool askToEnterDebugger(const char* message ) +{ + static bool haveAsked = false; + static bool useDebugger = true; + if(!haveAsked ) + { + static char tempBuff[1024]; + dSprintf( tempBuff, 1024, "Torque has encountered an assertion with message\n\n" + "%s\n\n" + "Would you like to use the debugger? If you cancel, you won't be asked" + " again until you restart Torque.", message); + + useDebugger = Platform::AlertOKCancel("Use debugger?", tempBuff ); + haveAsked = true; + } + return useDebugger; +} + +//-------------------------------------- + +bool PlatformAssert::process(Type assertType, + const char *filename, + U32 lineNumber, + const char *message) +{ + // If we're somehow recursing, just die. + if(processing) + Platform::debugBreak(); + + processing = true; + bool ret = true; + + // always dump to the Assert to the Console + if (Con::isActive()) + { + if (assertType == Warning) + Con::warnf(ConsoleLogEntry::Assert, "%s(%ld) : %s - %s", filename, lineNumber, typeName[assertType], message); + else + Con::errorf(ConsoleLogEntry::Assert, "%s(%ld) : %s - %s", filename, lineNumber, typeName[assertType], message); + } + + // if not a WARNING pop-up a dialog box + if (assertType != Warning) + { + // used for processing navGraphs (an assert won't botch the whole build) + if(Con::getBoolVariable("$FP::DisableAsserts", false) == true) + Platform::forceShutdown(1); + + char buffer[2048]; + dSprintf(buffer, 2048, "%s(%ld) : %s", filename, lineNumber, typeName[assertType] ); + +#ifdef TORQUE_DEBUG + // In debug versions, allow a retry even for ISVs... + bool retry = displayMessageBox(buffer, message, true); +#else + bool retry = displayMessageBox(buffer, message, ((assertType == Fatal) ? true : false) ); +#endif + if(!retry) + Platform::forceShutdown(1); + + ret = askToEnterDebugger(message); + } + + processing = false; + + return ret; +} + +bool PlatformAssert::processingAssert() +{ + return platformAssert ? platformAssert->processing : false; +} + +//-------------------------------------- +bool PlatformAssert::processAssert(Type assertType, + const char *filename, + U32 lineNumber, + const char *message) +{ + if (platformAssert) + return platformAssert->process(assertType, filename, lineNumber, message); + else // when platAssert NULL (during _start/_exit) try direct output... + dPrintf("\n%s: (%s @ %ld) %s\n", typeName[assertType], filename, lineNumber, message); + + // this could also be platform-specific: OutputDebugString on PC, DebugStr on Mac. + // Will raw printfs do the job? In the worst case, it's a break-pointable line of code. + // would have preferred Con but due to race conditions, it might not be around... + // Con::errorf(ConsoleLogEntry::Assert, "%s: (%s @ %ld) %s", typeName[assertType], filename, lineNumber, message); + + return true; +} + +//-------------------------------------- +const char* avar(const char *message, ...) +{ + static char buffer[4096]; + va_list args; + va_start(args, message); + dVsprintf(buffer, sizeof(buffer), message, args); + return( buffer ); +} diff --git a/platform/platformAssert.h b/platform/platformAssert.h new file mode 100644 index 0000000..c1c39b4 --- /dev/null +++ b/platform/platformAssert.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMASSERT_H_ +#define _PLATFORMASSERT_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +class PlatformAssert +{ +public: + enum Type + { + Warning = 3, + Fatal = 2, + Fatal_ISV = 1 + }; + +private: + static PlatformAssert *platformAssert; + bool processing; + + virtual bool displayMessageBox(const char *title, const char *message, bool retry); + virtual bool process(Type assertType, + const char* filename, + U32 lineNumber, + const char* message); + + PlatformAssert(); + virtual ~PlatformAssert(); + +public: + static void create( PlatformAssert* newAssertClass = NULL ); + static void destroy(); + static bool processAssert(Type assertType, + const char* filename, + U32 lineNumber, + const char* message); + static char *message(const char *message, ...); + static bool processingAssert(); +}; + + +#ifdef TORQUE_ENABLE_ASSERTS + /*! + Assert that the statement x is true, and continue processing. + + If the statment x is true, continue processing. + + If the statement x is false, log the file and line where the assert occured, + the message y and continue processing. + + These asserts are only present in DEBUG builds. + */ + #define AssertWarn(x, y) \ + { if ((x)==0) \ + ::PlatformAssert::processAssert(::PlatformAssert::Warning, __FILE__, __LINE__, y); } + + /*! + Assert that the statement x is true, otherwise halt. + + If the statement x is true, continue processing. + + If the statement x is false, log the file and line where the assert occured, + the message y and displaying a dialog containing the message y. The user then + has the option to halt or continue causing the debugger to break. + + These asserts are only present in DEBUG builds. + + This assert is very useful for verifying data as well as function entry and + exit conditions. + */ + #define AssertFatal(x, y) \ + { if (((bool)(x))==(bool)0) \ + { if ( ::PlatformAssert::processAssert(::PlatformAssert::Fatal, __FILE__, __LINE__, y) ) { ::Platform::debugBreak(); } } } + +#else + #define AssertFatal(x, y) { (void)sizeof(x); (void)sizeof(y); } + #define AssertWarn(x, y) { (void)sizeof(x); (void)sizeof(y); } +#endif + +/*! + Assert (In Shipping Version) that the statement x is true, otherwise halt. + + If the statement x is true, continue processing. + + If the statement x is false, log the file and line where the assert occurred, + the message y and exit the program displaying a dialog containing the message y. + These asserts are present in both OPTIMIZED and DEBUG builds. + + This assert should only be used for rare conditions where the application cannot continue + execution without seg-faulting and you want to display a nice exit message. + */ +#define AssertISV(x, y) \ + { if ((x)==0) \ +{ if ( ::PlatformAssert::processAssert(::PlatformAssert::Fatal_ISV, __FILE__, __LINE__, y) ) { ::Platform::debugBreak(); } } } + + +/*! + Sprintf style string formating into a fixed temporary buffer. + @param in_msg sprintf style format string + @returns pointer to fixed buffer containing formatted string + + \b Example: + \code + U8 a = 5; + S16 b = -10; + char *output = avar("hello %s! a=%u, b=%d", "world"); + ouput = "hello world! a=5, b=-10" + \endcode + + @warning avar uses a static fixed buffer. Treat the buffer as volatile data + and use it immediately. Other functions my use avar too and clobber the buffer. + */ +const char* avar(const char *in_msg, ...); + + + +#endif // _PLATFORM_ASSERT_H_ + diff --git a/platform/platformCPU.cpp b/platform/platformCPU.cpp new file mode 100644 index 0000000..a3017d5 --- /dev/null +++ b/platform/platformCPU.cpp @@ -0,0 +1,241 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformCPUCount.h" +#include "core/strings/stringFunctions.h" +#include "core/stringTable.h" +#include "core/util/tSignal.h" + +Signal Platform::SystemInfoReady; + +enum CPUFlags +{ + BIT_FPU = BIT(0), + BIT_RDTSC = BIT(4), + BIT_MMX = BIT(23), + BIT_SSE = BIT(25), + BIT_SSE2 = BIT(26), + BIT_3DNOW = BIT(31), +}; + +// fill the specified structure with information obtained from asm code +void SetProcessorInfo(Platform::SystemInfo_struct::Processor& pInfo, + char* vendor, U32 processor, U32 properties) +{ + Platform::SystemInfo.processor.properties |= (properties & BIT_FPU) ? CPU_PROP_FPU : 0; + Platform::SystemInfo.processor.properties |= (properties & BIT_RDTSC) ? CPU_PROP_RDTSC : 0; + Platform::SystemInfo.processor.properties |= (properties & BIT_MMX) ? CPU_PROP_MMX : 0; + + if (dStricmp(vendor, "GenuineIntel") == 0) + { + pInfo.properties |= (properties & BIT_SSE) ? CPU_PROP_SSE : 0; + pInfo.properties |= (properties & BIT_SSE2) ? CPU_PROP_SSE2 : 0; + pInfo.type = CPU_Intel_Unknown; + // switch on processor family code + switch ((processor >> 8) & 0x0f) + { + case 4: + pInfo.type = CPU_Intel_486; + pInfo.name = StringTable->insert("Intel 486 class"); + break; + + // Pentium Family + case 5: + // switch on processor model code + switch ((processor >> 4) & 0xf) + { + case 1: + case 2: + case 3: + pInfo.type = CPU_Intel_Pentium; + pInfo.name = StringTable->insert("Intel Pentium"); + break; + case 4: + pInfo.type = CPU_Intel_PentiumMMX; + pInfo.name = StringTable->insert("Intel Pentium MMX"); + break; + default: + pInfo.type = CPU_Intel_Pentium; + pInfo.name = StringTable->insert( "Intel (unknown)" ); + break; + } + break; + + // Pentium Pro/II/II family + case 6: + { + U32 extendedModel = ( processor & 0xf0000 ) >> 16; + // switch on processor model code + switch ((processor >> 4) & 0xf) + { + case 1: + pInfo.type = CPU_Intel_PentiumPro; + pInfo.name = StringTable->insert("Intel Pentium Pro"); + break; + case 3: + case 5: + pInfo.type = CPU_Intel_PentiumII; + pInfo.name = StringTable->insert("Intel Pentium II"); + break; + case 6: + pInfo.type = CPU_Intel_PentiumCeleron; + pInfo.name = StringTable->insert("Intel Pentium Celeron"); + break; + case 7: + case 8: + case 11: + pInfo.type = CPU_Intel_PentiumIII; + pInfo.name = StringTable->insert("Intel Pentium III"); + break; + case 0xA: + if( extendedModel == 1) + { + pInfo.type = CPU_Intel_Corei7Xeon; + pInfo.name = StringTable->insert( "Intel Core i7 / Xeon" ); + } + else + { + pInfo.type = CPU_Intel_PentiumIII; + pInfo.name = StringTable->insert( "Intel Pentium III Xeon" ); + } + break; + case 0xD: + if( extendedModel == 1 ) + { + pInfo.type = CPU_Intel_Corei7Xeon; + pInfo.name = StringTable->insert( "Intel Core i7 / Xeon" ); + } + else + { + pInfo.type = CPU_Intel_PentiumM; + pInfo.name = StringTable->insert( "Intel Pentium/Celeron M" ); + } + break; + case 0xE: + pInfo.type = CPU_Intel_Core; + pInfo.name = StringTable->insert( "Intel Core" ); + break; + case 0xF: + pInfo.type = CPU_Intel_Core2; + pInfo.name = StringTable->insert( "Intel Core 2" ); + break; + default: + pInfo.type = CPU_Intel_PentiumPro; + pInfo.name = StringTable->insert( "Intel (unknown)" ); + break; + } + break; + } + + // Pentium4 Family + case 0xf: + pInfo.type = CPU_Intel_Pentium4; + pInfo.name = StringTable->insert( "Intel Pentium 4" ); + break; + + default: + pInfo.type = CPU_Intel_Unknown; + pInfo.name = StringTable->insert( "Intel (unknown)" ); + break; + } + } + //-------------------------------------- + else + if (dStricmp(vendor, "AuthenticAMD") == 0) + { + // AthlonXP processors support SSE + pInfo.properties |= (properties & BIT_SSE) ? CPU_PROP_SSE : 0; + pInfo.properties |= ( properties & BIT_SSE2 ) ? CPU_PROP_SSE2 : 0; + pInfo.properties |= (properties & BIT_3DNOW) ? CPU_PROP_3DNOW : 0; + // switch on processor family code + switch ((processor >> 8) & 0xf) + { + // K6 Family + case 5: + // switch on processor model code + switch ((processor >> 4) & 0xf) + { + case 0: + case 1: + case 2: + case 3: + pInfo.type = CPU_AMD_K6_3; + pInfo.name = StringTable->insert("AMD K5"); + break; + case 4: + case 5: + case 6: + case 7: + pInfo.type = CPU_AMD_K6; + pInfo.name = StringTable->insert("AMD K6"); + break; + case 8: + pInfo.type = CPU_AMD_K6_2; + pInfo.name = StringTable->insert("AMD K6-2"); + break; + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + pInfo.type = CPU_AMD_K6_3; + pInfo.name = StringTable->insert("AMD K6-3"); + break; + } + break; + + // Athlon Family + case 6: + pInfo.type = CPU_AMD_Athlon; + pInfo.name = StringTable->insert("AMD Athlon"); + break; + + default: + pInfo.type = CPU_AMD_Unknown; + pInfo.name = StringTable->insert("AMD (unknown)"); + break; + } + } + //-------------------------------------- + else + if (dStricmp(vendor, "CyrixInstead") == 0) + { + switch (processor) + { + case 0x520: + pInfo.type = CPU_Cyrix_6x86; + pInfo.name = StringTable->insert("Cyrix 6x86"); + break; + case 0x440: + pInfo.type = CPU_Cyrix_MediaGX; + pInfo.name = StringTable->insert("Cyrix Media GX"); + break; + case 0x600: + pInfo.type = CPU_Cyrix_6x86MX; + pInfo.name = StringTable->insert("Cyrix 6x86mx/MII"); + break; + case 0x540: + pInfo.type = CPU_Cyrix_GXm; + pInfo.name = StringTable->insert("Cyrix GXm"); + break; + default: + pInfo.type = CPU_Cyrix_Unknown; + pInfo.name = StringTable->insert("Cyrix (unknown)"); + break; + } + } + + // Get multithreading caps. + + CPUInfo::EConfig config = CPUInfo::CPUCount( pInfo.numLogicalProcessors, pInfo.numAvailableCores, pInfo.numPhysicalProcessors ); + pInfo.isHyperThreaded = CPUInfo::isHyperThreaded( config ); + pInfo.isMultiCore = CPUInfo::isMultiCore( config ); + + // Trigger the signal + Platform::SystemInfoReady.trigger(); +} diff --git a/platform/platformCPUCount.cpp b/platform/platformCPUCount.cpp new file mode 100644 index 0000000..0583a6c --- /dev/null +++ b/platform/platformCPUCount.cpp @@ -0,0 +1,730 @@ +// Original code is: +// Copyright (c) 2005 Intel Corporation +// All Rights Reserved +// +// CPUCount.cpp : Detects three forms of hardware multi-threading support across IA-32 platform +// The three forms of HW multithreading are: Multi-processor, Multi-core, and +// HyperThreading Technology. +// This application enumerates all the logical processors enabled by OS and BIOS, +// determine the HW topology of these enabled logical processors in the system +// using information provided by CPUID instruction. +// A multi-processing system can support any combination of the three forms of HW +// multi-threading support. The relevant topology can be identified using a +// three level decomposition of the "initial APIC ID" into +// Package_id, core_id, and SMT_id. Such decomposition provides a three-level map of +// the topology of hardware resources and +// allow multi-threaded software to manage shared hardware resources in +// the platform to reduce resource contention + +// Multicore detection algorithm for processor and cache topology requires +// all leaf functions of CPUID instructions be available. System administrator +// must ensure BIOS settings is not configured to restrict CPUID functionalities. +//------------------------------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformCPUCount.h" + +// Consoles don't need this +#if defined(TORQUE_OS_XENON) || defined(TORQUE_OS_PS3) +namespace CPUInfo +{ + +EConfig CPUCount(U32& TotAvailLogical, U32& TotAvailCore, U32& PhysicalNum) +{ + TotAvailLogical = 6; + TotAvailCore = 6; + PhysicalNum = 3; + + return CONFIG_MultiCoreAndHTEnabled; +} + +}; // namespace +#else + +#ifdef TORQUE_OS_LINUX +// The Linux source code listing can be compiled using Linux kernel verison 2.6 +// or higher (e.g. RH 4AS-2.8 using GCC 3.4.4). +// Due to syntax variances of Linux affinity APIs with earlier kernel versions +// and dependence on glibc library versions, compilation on Linux environment +// with older kernels and compilers may require kernel patches or compiler upgrades. + +#include +#include +#include +#include +#define DWORD unsigned long +#elif defined( TORQUE_OS_WIN32 ) +#include +#elif defined( TORQUE_OS_MAC ) +# include +# include +#else +#error Not implemented on platform. +#endif +#include +#include + +namespace CPUInfo { + +#define HWD_MT_BIT 0x10000000 // EDX[28] Bit 28 is set if HT or multi-core is supported +#define NUM_LOGICAL_BITS 0x00FF0000 // EBX[23:16] Bit 16-23 in ebx contains the number of logical + // processors per physical processor when execute cpuid with + // eax set to 1 +#define NUM_CORE_BITS 0xFC000000 // EAX[31:26] Bit 26-31 in eax contains the number of cores minus one + // per physical processor when execute cpuid with + // eax set to 4. + + +#define INITIAL_APIC_ID_BITS 0xFF000000 // EBX[31:24] Bits 24-31 (8 bits) return the 8-bit unique + // initial APIC ID for the processor this code is running on. + + + #ifndef TORQUE_OS_MAC + static unsigned int CpuIDSupported(void); + static unsigned int find_maskwidth(unsigned int); + static unsigned int HWD_MTSupported(void); + static unsigned int MaxLogicalProcPerPhysicalProc(void); + static unsigned int MaxCorePerPhysicalProc(void); + static unsigned char GetAPIC_ID(void); + static unsigned char GetNzbSubID(unsigned char, unsigned char, unsigned char); + static unsigned int GenuineIntel(void); + #endif + + static char g_s3Levels[2048]; + +#ifndef TORQUE_OS_MAC + + // + // CpuIDSupported will return 0 if CPUID instruction is unavailable. Otherwise, it will return + // the maximum supported standard function. + // + static unsigned int CpuIDSupported(void) + { + unsigned int MaxInputValue; + // If CPUID instruction is supported +#ifdef TORQUE_COMPILER_GCC + try + { + MaxInputValue = 0; + // call cpuid with eax = 0 + asm + ( + "pushl %%ebx\n\t" + "xorl %%eax,%%eax\n\t" + "cpuid\n\t" + "popl %%ebx\n\t" + : "=a" (MaxInputValue) + : + : "%ecx", "%edx" + ); + } + catch (...) + { + return(0); // cpuid instruction is unavailable + } +#elif defined( TORQUE_COMPILER_VISUALC ) + try + { + MaxInputValue = 0; + // call cpuid with eax = 0 + __asm + { + xor eax, eax + cpuid + mov MaxInputValue, eax + } + } + catch (...) + { + return(0); // cpuid instruction is unavailable + } +#else +# error Not implemented. +#endif + + return MaxInputValue; + + } + + + // + // GenuineIntel will return 0 if the processor is not a Genuine Intel Processor + // + static unsigned int GenuineIntel(void) + { +#ifdef TORQUE_COMPILER_GCC + unsigned int VendorIDb = 0,VendorIDd = 0, VendorIDc = 0; + + try + // If CPUID instruction is supported + { + // Get vendor id string + asm + ( + //get the vendor string + // call cpuid with eax = 0 + "xorl %%eax, %%eax\n\t" + "cpuid\n\t" + : "=b" (VendorIDb), + "=d" (VendorIDd), + "=c" (VendorIDc) + : + : "%eax" + ); + } + + catch(...) + { + return(0); // cpuid instruction is unavailable + } + + return ( (VendorIDb == 'uneG') && + (VendorIDd == 'Ieni') && + (VendorIDc == 'letn')); + +#elif defined( TORQUE_COMPILER_VISUALC ) + unsigned int VendorID[3] = {0, 0, 0}; + try // If CPUID instruction is supported + { + __asm + { + xor eax, eax // call cpuid with eax = 0 + cpuid // Get vendor id string + mov VendorID, ebx + mov VendorID + 4, edx + mov VendorID + 8, ecx + } + } + catch (...) + { + return(0); + // cpuid instruction is unavailable + } + return ( (VendorID[0] == 'uneG') && + (VendorID[1] == 'Ieni') && + (VendorID[2] == 'letn')); +#else +# error Not implemented. +#endif + } + + + + // + // Function returns the maximum cores per physical package. Note that the number of + // AVAILABLE cores per physical to be used by an application might be less than this + // maximum value. + // + + static unsigned int MaxCorePerPhysicalProc(void) + { + + unsigned int Regeax = 0; + + if (!HWD_MTSupported()) return (unsigned int) 1; // Single core +#ifdef TORQUE_COMPILER_GCC + { + asm + ( + "pushl %ebx\n\t" + "xorl %eax, %eax\n\t" + "cpuid\n\t" + "cmpl $4, %eax\n\t" // check if cpuid supports leaf 4 + "jl .single_core\n\t" // Single core + "movl $4, %eax\n\t" + "movl $0, %ecx\n\t" // start with index = 0; Leaf 4 reports + "popl %ebx\n\t" + ); // at least one valid cache level + asm + ( + "cpuid" + : "=a" (Regeax) + : + : "%ecx", "%edx" + ); + asm + ( + "jmp .multi_core\n" + ".single_core:\n\t" + "xor %eax, %eax\n" + ".multi_core:" + ); + } +#elif defined( TORQUE_COMPILER_VISUALC ) + __asm + { + xor eax, eax + cpuid + cmp eax, 4 // check if cpuid supports leaf 4 + jl single_core // Single core + mov eax, 4 + mov ecx, 0 // start with index = 0; Leaf 4 reports + cpuid // at least one valid cache level + mov Regeax, eax + jmp multi_core + +single_core: + xor eax, eax + +multi_core: + + } +#else +# error Not implemented. +#endif + return (unsigned int)((Regeax & NUM_CORE_BITS) >> 26)+1; + + } + + + + // + // The function returns 0 when the hardware multi-threaded bit is not set. + // + static unsigned int HWD_MTSupported(void) + { + + + unsigned int Regedx = 0; + + + if ((CpuIDSupported() >= 1))// && GenuineIntel()) + { +#ifdef TORQUE_COMPILER_GCC + asm + ( + "pushl %%ebx\n\t" + "movl $1,%%eax\n\t" + "cpuid\n\t" + "popl %%ebx\n\t" + : "=d" (Regedx) + : + : "%eax","%ecx" + ); +#elif defined( TORQUE_COMPILER_VISUALC ) + __asm + { + mov eax, 1 + cpuid + mov Regedx, edx + } +#else +# error Not implemented. +#endif + } + + return (Regedx & HWD_MT_BIT); + + + } + + + + // + // Function returns the maximum logical processors per physical package. Note that the number of + // AVAILABLE logical processors per physical to be used by an application might be less than this + // maximum value. + // + static unsigned int MaxLogicalProcPerPhysicalProc(void) + { + + unsigned int Regebx = 0; + + if (!HWD_MTSupported()) return (unsigned int) 1; +#ifdef TORQUE_COMPILER_GCC + asm + ( + "movl $1,%%eax\n\t" + "cpuid" + : "=b" (Regebx) + : + : "%eax","%ecx","%edx" + ); +#elif defined( TORQUE_COMPILER_VISUALC ) + __asm + { + mov eax, 1 + cpuid + mov Regebx, ebx + } +#else +# error Not implemented. +#endif + return (unsigned int) ((Regebx & NUM_LOGICAL_BITS) >> 16); + + } + + + static unsigned char GetAPIC_ID(void) + { + + unsigned int Regebx = 0; +#ifdef TORQUE_COMPILER_GCC + asm + ( + "movl $1, %%eax\n\t" + "cpuid" + : "=b" (Regebx) + : + : "%eax","%ecx","%edx" + ); + +#elif defined( TORQUE_COMPILER_VISUALC ) + __asm + { + mov eax, 1 + cpuid + mov Regebx, ebx + } +#else +# error Not implemented. +#endif + + return (unsigned char) ((Regebx & INITIAL_APIC_ID_BITS) >> 24); + + } + + // + // Determine the width of the bit field that can represent the value count_item. + // + unsigned int find_maskwidth(unsigned int CountItem) + { + unsigned int MaskWidth, + count = CountItem; +#ifdef TORQUE_COMPILER_GCC + asm + ( +#ifdef __x86_64__ // define constant to compile + "push %%rcx\n\t" // under 64-bit Linux + "push %%rax\n\t" +#else + "pushl %%ecx\n\t" + "pushl %%eax\n\t" +#endif + // "movl $count, %%eax\n\t" //done by Assembler below + "xorl %%ecx, %%ecx" + // "movl %%ecx, MaskWidth\n\t" //done by Assembler below + : "=c" (MaskWidth) + : "a" (count) + // : "%ecx", "%eax" We don't list these as clobbered because we don't want the assembler + //to put them back when we are done + ); + asm + ( + "decl %%eax\n\t" + "bsrw %%ax,%%cx\n\t" + "jz next\n\t" + "incw %%cx\n\t" + // "movl %%ecx, MaskWidth\n" //done by Assembler below + : "=c" (MaskWidth) + : + ); + asm + ( + "next:\n\t" +#ifdef __x86_64__ + "pop %rax\n\t" + "pop %rcx" +#else + "popl %eax\n\t" + "popl %ecx" +#endif + ); + +#elif defined( TORQUE_COMPILER_VISUALC ) + __asm + { + mov eax, count + mov ecx, 0 + mov MaskWidth, ecx + dec eax + bsr cx, ax + jz next + inc cx + mov MaskWidth, ecx +next: + + } +#else +# error Not implemented. +#endif + return MaskWidth; + } + + + // + // Extract the subset of bit field from the 8-bit value FullID. It returns the 8-bit sub ID value + // + static unsigned char GetNzbSubID(unsigned char FullID, + unsigned char MaxSubIDValue, + unsigned char ShiftCount) + { + unsigned int MaskWidth; + unsigned char MaskBits; + + MaskWidth = find_maskwidth((unsigned int) MaxSubIDValue); + MaskBits = (0xff << ShiftCount) ^ + ((unsigned char) (0xff << (ShiftCount + MaskWidth))); + + return (FullID & MaskBits); + } + +#endif + + + // + // + // + EConfig CPUCount(U32& TotAvailLogical, U32& TotAvailCore, U32& PhysicalNum) + { + EConfig StatusFlag = CONFIG_UserConfigIssue; + + g_s3Levels[0] = 0; + TotAvailCore = 1; + PhysicalNum = 1; + + unsigned int numLPEnabled = 0; + int MaxLPPerCore = 1; + +#ifdef TORQUE_OS_MAC + + //FIXME: This isn't a proper port but more or less just some sneaky cheating + // to get around having to mess with yet another crap UNIX-style API. Seems + // like there isn't a way to do this that's working across all OSX incarnations + // and machine configurations anyway. + + int numCPUs; + int numPackages; + + // Get the number of CPUs. + + size_t len = sizeof( numCPUs ); + if( sysctlbyname( "hw.ncpu", &numCPUs, &len, 0, 0 ) == -1 ) + return CONFIG_UserConfigIssue; + + // Get the number of packages. + len = sizeof( numPackages ); + if( sysctlbyname( "hw.packages", &numPackages, &len, 0, 0 ) == -1 ) + return CONFIG_UserConfigIssue; + + TotAvailCore = numCPUs; + TotAvailLogical = numCPUs; + PhysicalNum = numPackages; +#else + + U32 dwAffinityMask; + int j = 0; + unsigned char apicID, PackageIDMask; + unsigned char tblPkgID[256], tblCoreID[256], tblSMTID[256]; + char tmp[256]; + +#ifdef TORQUE_OS_LINUX + //we need to make sure that this process is allowed to run on + //all of the logical processors that the OS itself can run on. + //A process could acquire/inherit affinity settings that restricts the + // current process to run on a subset of all logical processor visible to OS. + + // Linux doesn't easily allow us to look at the Affinity Bitmask directly, + // but it does provide an API to test affinity maskbits of the current process + // against each logical processor visible under OS. + int sysNumProcs = sysconf(_SC_NPROCESSORS_CONF); //This will tell us how many + //CPUs are currently enabled. + + //this will tell us which processors this process can run on. + cpu_set_t allowedCPUs; + sched_getaffinity(0, sizeof(allowedCPUs), &allowedCPUs); + + for (int i = 0; i < sysNumProcs; i++ ) + { + if ( CPU_ISSET(i, &allowedCPUs) == 0 ) + return CONFIG_UserConfigIssue; + } +#elif defined( TORQUE_OS_WIN32 ) + DWORD dwProcessAffinity, dwSystemAffinity; + GetProcessAffinityMask(GetCurrentProcess(), + &dwProcessAffinity, + &dwSystemAffinity); + if (dwProcessAffinity != dwSystemAffinity) // not all CPUs are enabled + return CONFIG_UserConfigIssue; +#else +# error Not implemented. +#endif + + // Assume that cores within a package have the SAME number of + // logical processors. Also, values returned by + // MaxLogicalProcPerPhysicalProc and MaxCorePerPhysicalProc do not have + // to be power of 2. + + MaxLPPerCore = MaxLogicalProcPerPhysicalProc() / MaxCorePerPhysicalProc(); + dwAffinityMask = 1; + +#ifdef TORQUE_OS_LINUX + cpu_set_t currentCPU; + while ( j < sysNumProcs ) + { + CPU_ZERO(¤tCPU); + CPU_SET(j, ¤tCPU); + if ( sched_setaffinity (0, sizeof(currentCPU), ¤tCPU) == 0 ) + { + sleep(0); // Ensure system to switch to the right CPU +#elif defined( TORQUE_OS_WIN32 ) + while (dwAffinityMask && dwAffinityMask <= dwSystemAffinity) + { + if (SetThreadAffinityMask(GetCurrentThread(), dwAffinityMask)) + { + Sleep(0); // Ensure system to switch to the right CPU +#else +# error Not implemented. +#endif + apicID = GetAPIC_ID(); + + + // Store SMT ID and core ID of each logical processor + // Shift vlaue for SMT ID is 0 + // Shift value for core ID is the mask width for maximum logical + // processors per core + + tblSMTID[j] = GetNzbSubID(apicID, MaxLPPerCore, 0); + tblCoreID[j] = GetNzbSubID(apicID, + MaxCorePerPhysicalProc(), + (unsigned char) find_maskwidth(MaxLPPerCore)); + + // Extract package ID, assume single cluster. + // Shift value is the mask width for max Logical per package + + PackageIDMask = (unsigned char) (0xff << + find_maskwidth(MaxLogicalProcPerPhysicalProc())); + + tblPkgID[j] = apicID & PackageIDMask; + sprintf(tmp," AffinityMask = %d; Initial APIC = %d; Physical ID = %d, Core ID = %d, SMT ID = %d\n", + dwAffinityMask, apicID, tblPkgID[j], tblCoreID[j], tblSMTID[j]); + strcat(g_s3Levels, tmp); + + numLPEnabled ++; // Number of available logical processors in the system. + + } // if + + j++; + dwAffinityMask = 1 << j; + } // while + + // restore the affinity setting to its original state +#ifdef TORQUE_OS_LINUX + sched_setaffinity (0, sizeof(allowedCPUs), &allowedCPUs); + sleep(0); +#elif defined( TORQUE_OS_WIN32 ) + SetThreadAffinityMask(GetCurrentThread(), dwProcessAffinity); + Sleep(0); +#else +# error Not implemented. +#endif + TotAvailLogical = numLPEnabled; + + // + // Count available cores (TotAvailCore) in the system + // + unsigned char CoreIDBucket[256]; + DWORD ProcessorMask, pCoreMask[256]; + unsigned int i, ProcessorNum; + + CoreIDBucket[0] = tblPkgID[0] | tblCoreID[0]; + ProcessorMask = 1; + pCoreMask[0] = ProcessorMask; + + for (ProcessorNum = 1; ProcessorNum < numLPEnabled; ProcessorNum++) + { + ProcessorMask <<= 1; + for (i = 0; i < TotAvailCore; i++) + { + // Comparing bit-fields of logical processors residing in different packages + // Assuming the bit-masks are the same on all processors in the system. + if ((tblPkgID[ProcessorNum] | tblCoreID[ProcessorNum]) == CoreIDBucket[i]) + { + pCoreMask[i] |= ProcessorMask; + break; + } + + } // for i + + if (i == TotAvailCore) // did not match any bucket. Start a new one. + { + CoreIDBucket[i] = tblPkgID[ProcessorNum] | tblCoreID[ProcessorNum]; + pCoreMask[i] = ProcessorMask; + + TotAvailCore++; // Number of available cores in the system + + } + + } // for ProcessorNum + + + // + // Count physical processor (PhysicalNum) in the system + // + unsigned char PackageIDBucket[256]; + DWORD pPackageMask[256]; + + PackageIDBucket[0] = tblPkgID[0]; + ProcessorMask = 1; + pPackageMask[0] = ProcessorMask; + + for (ProcessorNum = 1; ProcessorNum < numLPEnabled; ProcessorNum++) + { + ProcessorMask <<= 1; + for (i = 0; i < PhysicalNum; i++) + { + // Comparing bit-fields of logical processors residing in different packages + // Assuming the bit-masks are the same on all processors in the system. + if (tblPkgID[ProcessorNum]== PackageIDBucket[i]) + { + pPackageMask[i] |= ProcessorMask; + break; + } + + } // for i + + if (i == PhysicalNum) // did not match any bucket. Start a new one. + { + PackageIDBucket[i] = tblPkgID[ProcessorNum]; + pPackageMask[i] = ProcessorMask; + + PhysicalNum++; // Total number of physical processors in the system + + } + + } // for ProcessorNum +#endif + + // + // Check to see if the system is multi-core + // Check if the system is hyper-threading + // + if (TotAvailCore > PhysicalNum) + { + // Multi-core + if (MaxLPPerCore == 1) + StatusFlag = CONFIG_MultiCoreAndHTNotCapable; + else if (numLPEnabled > TotAvailCore) + StatusFlag = CONFIG_MultiCoreAndHTEnabled; + else StatusFlag = CONFIG_MultiCoreAndHTDisabled; + + } + else + { + // Single-core + if (MaxLPPerCore == 1) + StatusFlag = CONFIG_SingleCoreAndHTNotCapable; + else if (numLPEnabled > TotAvailCore) + StatusFlag = CONFIG_SingleCoreHTEnabled; + else StatusFlag = CONFIG_SingleCoreHTDisabled; + + + } + + + + return StatusFlag; + } + +} // namespace CPUInfo +#endif \ No newline at end of file diff --git a/platform/platformCPUCount.h b/platform/platformCPUCount.h new file mode 100644 index 0000000..3030736 --- /dev/null +++ b/platform/platformCPUCount.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_PLATFORM_PLATFORMCPUCOUNT_H_ +#define _TORQUE_PLATFORM_PLATFORMCPUCOUNT_H_ + +#include "platform/platform.h" + +namespace CPUInfo +{ + enum EConfig + { + CONFIG_UserConfigIssue, + CONFIG_SingleCoreHTEnabled, + CONFIG_SingleCoreHTDisabled, + CONFIG_SingleCoreAndHTNotCapable, + CONFIG_MultiCoreAndHTNotCapable, + CONFIG_MultiCoreAndHTEnabled, + CONFIG_MultiCoreAndHTDisabled, + }; + + inline bool isMultiCore( EConfig config ) + { + switch( config ) + { + case CONFIG_MultiCoreAndHTNotCapable: + case CONFIG_MultiCoreAndHTEnabled: + case CONFIG_MultiCoreAndHTDisabled: + return true; + + default: + return false; + } + } + + inline bool isHyperThreaded( EConfig config ) + { + switch( config ) + { + case CONFIG_SingleCoreHTEnabled: + case CONFIG_MultiCoreAndHTEnabled: + return true; + + default: + return false; + } + } + + EConfig CPUCount( U32& totalAvailableLogical, + U32& totalAvailableCores, + U32& numPhysical ); + +} // namespace CPUInfo + +#endif // _TORQUE_PLATFORM_PLATFORMCOUNT_H_ + diff --git a/platform/platformCPUInfo.asm b/platform/platformCPUInfo.asm new file mode 100644 index 0000000..2b5885e --- /dev/null +++ b/platform/platformCPUInfo.asm @@ -0,0 +1,111 @@ +;----------------------------------------------------------------------------- +; Torque Game Engine +; Copyright (C) GarageGames.com, Inc. +;----------------------------------------------------------------------------- + + +segment .text + +; syntax: export_fn +%macro export_fn 1 + %ifidn __OUTPUT_FORMAT__, elf + ; No underscore needed for ELF object files + global %1 + %1: + %else + global _%1 + _%1: + %endif +%endmacro + +; push registers +%macro pushreg 0 +; pushad + push ebx + push ebp + push esi + push edi +%endmacro + +; pop registers +%macro popreg 0 + pop edi + pop esi + pop ebp + pop ebx +; popad +%endmacro + +; void detectX86CPUInfo(char *vendor, U32 *processor, U32 *properties); +export_fn detectX86CPUInfo + push ebp + mov ebp, esp + + pushreg + + push edx + push ecx + pushfd + pushfd ; save EFLAGS to stack + pop eax ; move EFLAGS into EAX + mov ebx, eax + xor eax, 0x200000 ; flip bit 21 + push eax + popfd ; restore EFLAGS + pushfd + pop eax + cmp eax, ebx + jz EXIT ; doesn't support CPUID instruction + + ; + ; get vendor information using CPUID eax == 0 + xor eax, eax + cpuid + + ; store the vendor tag (12 bytes in ebx, edx, ecx) in the first parameter, + ; which should be a char[13] + push eax ; save eax + mov eax, [ebp+8] ; store the char* address in eax + mov [eax], ebx ; move ebx into the first 4 bytes + add eax, 4 ; advance the char* 4 bytes + mov [eax], edx ; move edx into the next 4 bytes + add eax, 4 ; advance the char* 4 bytes + mov [eax], ecx ; move ecx into the last 4 bytes + pop eax ; restore eax + + ; get generic extended CPUID info + mov eax, 1 + cpuid ; eax=1, so cpuid queries feature information + + and eax, 0x0fff3fff + push ecx + mov ecx, [ebp+12] + mov [ecx], eax ; just store the model bits in processor param + mov ecx, [ebp+16] + mov [ecx], edx ; set properties param + pop ecx + + ; want to check for 3DNow(tm). + ; need to see if extended cpuid functions present. + mov eax, 0x80000000 + cpuid + cmp eax, 0x80000000 + jbe MAYBE_3DLATER + mov eax, 0x80000001 + cpuid + ; 3DNow if bit 31 set -> put bit in our properties + and edx, 0x80000000 + push eax + mov eax, [ebp+16] + or [eax], edx + pop eax +MAYBE_3DLATER: +EXIT: + popfd + pop ecx + pop edx + + popreg + + pop ebp + ret diff --git a/platform/platformDlibrary.h b/platform/platformDlibrary.h new file mode 100644 index 0000000..f07e3e7 --- /dev/null +++ b/platform/platformDlibrary.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef OS_DLIBRARY_H +#define OS_DLIBRARY_H + +#include "core/util/refBase.h" + +// DLLs use the standard calling convention +#define DLL_CALL __stdcall +#define DLL_EXPORT_CALL __declspec(dllexport) +#define DLL_IMPORT_CALL __declspec(dllimport) + +// Export functions from the DLL +#if defined(DLL_CODE) + #define DLL_DECL DLL_EXPORT_CALL +#else + #define DLL_DECL DLL_IMPORT_CALL +#endif + + +//----------------------------------------------------------------------------- + +///@defgroup KernelDLL Loadable Libraries +/// Basic DLL handling and symbol resolving. When a library is first loaded +/// it's "open" function will be called, and it's "close" function is called +/// right before the library is unloaded. +///@ingroup OsModule +///@{ + +/// Dynamic Library +/// Interface for library objects loaded using the loadLibrary() function. +class DLibrary: public StrongRefBase +{ +public: + virtual ~DLibrary() {} + virtual void *bind( const char *name ) = 0; +}; +typedef StrongRefPtr DLibraryRef; + +/// Load a library +/// Returns 0 if the library fails to load. Symbols are +/// resolved through the DLibrary interface. +DLibraryRef OsLoadLibrary( const char *file ); + +///@} + +//----------------------------------------------------------------------------- + + + +#endif + diff --git a/platform/platformFileIO.cpp b/platform/platformFileIO.cpp new file mode 100644 index 0000000..8a791b7 --- /dev/null +++ b/platform/platformFileIO.cpp @@ -0,0 +1,491 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "util/tempAlloc.h" +#include "console/console.h" +#include "core/stringTable.h" + +//----------------------------------------------------------------------------- + +StringTableEntry Platform::getTemporaryDirectory() +{ + StringTableEntry path = osGetTemporaryDirectory(); + + if(! Platform::isDirectory(path)) + path = Platform::getCurrentDirectory(); + + return path; +} + +ConsoleFunction(getTemporaryDirectory, const char *, 1, 1, "()") +{ + return Platform::getTemporaryDirectory(); +} + +StringTableEntry Platform::getTemporaryFileName() +{ + char buf[512]; + StringTableEntry path = Platform::getTemporaryDirectory(); + + dSprintf(buf, sizeof(buf), "%s/tgb.%08x.%02x.tmp", path, Platform::getRealMilliseconds(), U32(Platform::getRandom() * 255)); + + // [tom, 9/7/2006] This shouldn't be needed, but just in case + if(Platform::isFile(buf)) + return Platform::getTemporaryFileName(); + + return StringTable->insert(buf); +} + +ConsoleFunction(getTemporaryFileName, const char *, 1, 1, "()") +{ + return Platform::getTemporaryFileName(); +} + +//----------------------------------------------------------------------------- + +static StringTableEntry sgMainCSDir = NULL; + +StringTableEntry Platform::getMainDotCsDir() +{ + if(sgMainCSDir == NULL) + sgMainCSDir = Platform::getExecutablePath(); + + return sgMainCSDir; +} + +void Platform::setMainDotCsDir(const char *dir) +{ + sgMainCSDir = StringTable->insert(dir); +} + +//----------------------------------------------------------------------------- + +typedef Vector CharVector; +static CharVector gPlatformDirectoryExcludeList( __FILE__, __LINE__ ); + +void Platform::addExcludedDirectory(const char *pDir) +{ + gPlatformDirectoryExcludeList.push_back(dStrdup(pDir)); +} + +void Platform::clearExcludedDirectories() +{ + while(gPlatformDirectoryExcludeList.size()) + { + dFree(gPlatformDirectoryExcludeList.last()); + gPlatformDirectoryExcludeList.pop_back(); + } +} + +bool Platform::isExcludedDirectory(const char *pDir) +{ + for(CharVector::iterator i=gPlatformDirectoryExcludeList.begin(); i!=gPlatformDirectoryExcludeList.end(); i++) + if(!dStrcmp(pDir, *i)) + return true; + + return false; +} + +//----------------------------------------------------------------------------- + +inline void catPath(char *dst, const char *src, U32 len) +{ + if(*dst != '/') + { + ++dst; --len; + *dst = '/'; + } + + ++dst; --len; + + dStrncpy(dst, src, len); + dst[len - 1] = 0; +} + +// converts the posix root path "/" to "c:/" for win32 +// FIXME: this is not ideal. the c: drive is not guaranteed to exist. +#if defined(TORQUE_OS_WIN32) +static inline void _resolveLeadingSlash(char* buf, U32 size) +{ + if(buf[0] != '/') + return; + + AssertFatal(dStrlen(buf) + 2 < size, "Expanded path would be too long"); + dMemmove(buf + 2, buf, dStrlen(buf)); + buf[0] = 'c'; + buf[1] = ':'; +} +#endif + +static void makeCleanPathInPlace( char* path ) +{ + U32 pathDepth = 0; + char* fromPtr = path; + char* toPtr = path; + + bool isAbsolute = false; + if( *fromPtr == '/' ) + { + fromPtr ++; + toPtr ++; + isAbsolute = true; + } + else if( fromPtr[ 0 ] != '\0' && fromPtr[ 1 ] == ':' ) + { + toPtr += 3; + fromPtr += 3; + isAbsolute = true; + } + + while( *fromPtr ) + { + if( fromPtr[ 0 ] == '.' && fromPtr[ 1 ] == '.' && fromPtr[ 2 ] == '/' ) + { + // Back up from '../' + + if( pathDepth > 0 ) + { + pathDepth --; + toPtr -= 2; + while( toPtr >= path && *toPtr != '/' ) + toPtr --; + toPtr ++; + } + else if( !isAbsolute ) + { + dMemcpy( toPtr, fromPtr, 3 ); + toPtr += 3; + } + + fromPtr += 3; + } + else if( fromPtr[ 0 ] == '.' && fromPtr[ 1 ] == '/' ) + { + // Ignore. + fromPtr += 2; + } + else + { + if( fromPtr[ 0 ] == '/' ) + pathDepth ++; + + *toPtr ++ = *fromPtr ++; + } + } + + *toPtr = '\0'; +} + +char * Platform::makeFullPathName(const char *path, char *buffer, U32 size, const char *cwd /* = NULL */) +{ + char bspath[1024]; + dStrncpy(bspath, path, sizeof(bspath)); + bspath[sizeof(bspath)-1] = 0; + + for(S32 i = 0;i < dStrlen(bspath);++i) + { + if(bspath[i] == '\\') + bspath[i] = '/'; + } + + if(Platform::isFullPath(bspath)) + { + // Already a full path + #if defined(TORQUE_OS_WIN32) + _resolveLeadingSlash(bspath, sizeof(bspath)); + #endif + dStrncpy(buffer, bspath, size); + buffer[size-1] = 0; + return buffer; + } + + // [rene, 05/05/2008] Based on overall file handling in Torque, it does not seem to make + // that much sense to me to base things off the current working directory here. + + if(cwd == NULL) + cwd = Con::isCurrentScriptToolScript() ? Platform::getMainDotCsDir() : Platform::getCurrentDirectory(); + + dStrncpy(buffer, cwd, size); + buffer[size-1] = 0; + + const char* defaultDir = Con::getVariable("defaultGame"); + + char *ptr = bspath; + char *slash = NULL; + char *endptr = buffer + dStrlen(buffer) - 1; + + do + { + slash = dStrchr(ptr, '/'); + if(slash) + { + *slash = 0; + + // Directory + + if(dStrcmp(ptr, "..") == 0) + { + // Parent + endptr = dStrrchr(buffer, '/'); + } + else if(dStrcmp(ptr, ".") == 0) + { + // Current dir + } + else if(dStrcmp(ptr, "~") == 0) + { + catPath(endptr, defaultDir, size - (endptr - buffer)); + endptr += dStrlen(endptr) - 1; + } + else if(endptr) + { + catPath(endptr, ptr, size - (endptr - buffer)); + endptr += dStrlen(endptr) - 1; + } + + ptr = slash + 1; + } + else if(endptr) + { + // File + + catPath(endptr, ptr, size - (endptr - buffer)); + endptr += dStrlen(endptr) - 1; + } + + } while(slash); + + return buffer; +} + +bool Platform::isFullPath(const char *path) +{ + // Quick way out + if(path[0] == '/' || path[1] == ':') + return true; + + return false; +} + +//----------------------------------------------------------------------------- + +/// Return "fileName" stripped of its extension. Only extensions contained +/// in "validExtensions" will be stripped from the filename. +/// +/// @note Extensions in "validExtension" should include the dot. +String Platform::stripExtension( String fileName, Vector< String >& validExtensions ) +{ + // See if we have a valid extension to strip off + String ext; + S32 dotPos = fileName.find( '.', 0, String::Right ); + if( dotPos != String::NPos ) + ext = fileName.substr( dotPos ); + + U32 numValidExt = validExtensions.size(); + if( ext.isNotEmpty() && numValidExt ) + { + bool validExt = false; + for( U32 i = 0; i < numValidExt; i++ ) + { + if( ext.equal( validExtensions[ i ], String::NoCase ) ) + { + validExt = true; + break; + } + } + + if( !validExt ) + ext = String::EmptyString; + } + + if( ext.isEmpty() ) + return fileName; + else + return fileName.substr( 0, fileName.length() - ext.length() ); +} + +//----------------------------------------------------------------------------- +// TODO: wow really shouldn't be adding everything to the string table, use the string class! +StringTableEntry Platform::makeRelativePathName(const char *path, const char *to) +{ + // Make sure 'to' is a proper absolute path terminated with a forward slash. + + char buffer[ 1024 ]; + if( !Platform::isFullPath( to ) ) + { + dSprintf( buffer, sizeof( buffer ), "%s/%s/", Platform::getMainDotCsDir(), to ); + makeCleanPathInPlace( buffer ); + to = buffer; + } + else if( to[ dStrlen( to ) - 1 ] != '/' ) + { + U32 length = getMin( (U32)dStrlen( to ), sizeof( buffer ) - 2 ); + dMemcpy( buffer, to, length ); + buffer[ length ] = '/'; + buffer[ length + 1 ] = '\0'; + to = buffer; + } + + // If 'path' isn't absolute, make it now. Let's us use a single + // absolute/absolute merge path from here on. + + char buffer2[ 1024 ]; + if( !Platform::isFullPath( path ) ) + { + dSprintf( buffer2, sizeof( buffer2 ), "%s/%s", Platform::getMainDotCsDir(), path ); + makeCleanPathInPlace( buffer2 ); + path = buffer2; + } + + // First, find the common prefix and see where 'path' branches off from 'to'. + + const char *pathPtr, *toPtr, *branch = path; + for(pathPtr = path, toPtr = to;*pathPtr && *toPtr && dTolower(*pathPtr) == dTolower(*toPtr);++pathPtr, ++toPtr) + { + if(*pathPtr == '/') + branch = pathPtr; + } + + // If there's no common part, the two paths are on different drives and + // there's nothing we can do. + + if( pathPtr == path ) + return StringTable->insert( path ); + + // If 'path' and 'to' are identical (minus trailing slash or so), we can just return './'. + + else if((*pathPtr == 0 || (*pathPtr == '/' && *(pathPtr + 1) == 0)) && + (*toPtr == 0 || (*toPtr == '/' && *(toPtr + 1) == 0))) + { + char* bufPtr = buffer; + *bufPtr ++ = '.'; + + if(*pathPtr == '/' || *(pathPtr - 1) == '/') + *bufPtr++ = '/'; + + *bufPtr = 0; + return StringTable->insert(buffer); + } + + // If 'to' is a proper prefix of 'path', the remainder of 'path' is our relative path. + + else if( *toPtr == '\0' && toPtr[ -1 ] == '/' ) + return StringTable->insert( pathPtr ); + + // Otherwise have to step up the remaining directories in 'to' and then + // append the remainder of 'path'. + + else + { + if((*pathPtr == 0 && *toPtr == '/') || (*toPtr == '/' && *pathPtr == 0)) + branch = pathPtr; + + // Allocate a new temp so we aren't prone to buffer overruns. + + TempAlloc< char > temp( dStrlen( toPtr ) + dStrlen( branch ) + 1 ); + char* bufPtr = temp; + + // Figure out parent dirs + + for(toPtr = to + (branch - path);*toPtr;++toPtr) + { + if(*toPtr == '/' && *(toPtr + 1) != 0) + { + *bufPtr++ = '.'; + *bufPtr++ = '.'; + *bufPtr++ = '/'; + } + } + *bufPtr = 0; + + // Copy the rest + if(*branch) + dStrcpy(bufPtr, branch + 1); + else + *--bufPtr = 0; + + return StringTable->insert( temp ); + } +} + +//----------------------------------------------------------------------------- + +static StringTableEntry tryStripBasePath(const char *path, const char *base) +{ + U32 len = dStrlen(base); + if(dStrnicmp(path, base, len) == 0) + { + if(*(path + len) == '/') ++len; + return StringTable->insert(path + len, true); + } + return NULL; +} + +StringTableEntry Platform::stripBasePath(const char *path) +{ + if(path == NULL) + return NULL; + + StringTableEntry str = tryStripBasePath(path, Platform::getMainDotCsDir()); + + if(str != NULL ) + return str; + + str = tryStripBasePath(path, Platform::getCurrentDirectory()); + if(str != NULL ) + return str; + + str = tryStripBasePath(path, Platform::getPrefsPath()); + if(str != NULL ) + return str; + + return path; +} + +//----------------------------------------------------------------------------- + +StringTableEntry Platform::getPrefsPath(const char *file /* = NULL */) +{ +#ifndef TORQUE2D_TOOLS_FIXME + return StringTable->insert(file ? file : ""); +#else + char buf[1024]; + const char *company = Con::getVariable("$Game::CompanyName"); + if(company == NULL || *company == 0) + company = "GarageGames"; + + const char *appName = Con::getVariable("$Game::GameName"); + if(appName == NULL || *appName == 0) + appName = TORQUE_APP_NAME; + + if(file) + { + if(dStrstr(file, "..")) + { + Con::errorf("getPrefsPath - filename cannot be relative"); + return NULL; + } + + dSprintf(buf, sizeof(buf), "%s/%s/%s/%s", Platform::getUserDataDirectory(), company, appName, file); + } + else + dSprintf(buf, sizeof(buf), "%s/%s/%s", Platform::getUserDataDirectory(), company, appName); + + return StringTable->insert(buf, true); +#endif +} + +//----------------------------------------------------------------------------- + +ConsoleFunction(getUserDataDirectory, const char*, 1, 1, "getUserDataDirectory()") +{ + return Platform::getUserDataDirectory(); +} + +ConsoleFunction(getUserHomeDirectory, const char*, 1, 1, "getUserHomeDirectory()") +{ + return Platform::getUserHomeDirectory(); +} diff --git a/platform/platformFont.cpp b/platform/platformFont.cpp new file mode 100644 index 0000000..b174fef --- /dev/null +++ b/platform/platformFont.cpp @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformFont.h" + + +const char *getCharSetName(const U32 charSet) +{ + switch(charSet) + { + case TGE_ANSI_CHARSET: return "ansi"; + case TGE_SYMBOL_CHARSET: return "symbol"; + case TGE_SHIFTJIS_CHARSET: return "shiftjis"; + case TGE_HANGEUL_CHARSET: return "hangeul"; + case TGE_HANGUL_CHARSET: return "hangul"; + case TGE_GB2312_CHARSET: return "gb2312"; + case TGE_CHINESEBIG5_CHARSET: return "chinesebig5"; + case TGE_OEM_CHARSET: return "oem"; + case TGE_JOHAB_CHARSET: return "johab"; + case TGE_HEBREW_CHARSET: return "hebrew"; + case TGE_ARABIC_CHARSET: return "arabic"; + case TGE_GREEK_CHARSET: return "greek"; + case TGE_TURKISH_CHARSET: return "turkish"; + case TGE_VIETNAMESE_CHARSET: return "vietnamese"; + case TGE_THAI_CHARSET: return "thai"; + case TGE_EASTEUROPE_CHARSET: return "easteurope"; + case TGE_RUSSIAN_CHARSET: return "russian"; + case TGE_MAC_CHARSET: return "mac"; + case TGE_BALTIC_CHARSET: return "baltic"; + } + + AssertISV(false, "getCharSetName - unknown charset! Update table in platformString.cc!"); + return ""; +} \ No newline at end of file diff --git a/platform/platformFont.h b/platform/platformFont.h new file mode 100644 index 0000000..ba76e02 --- /dev/null +++ b/platform/platformFont.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +#ifndef _PLATFORMFONT_H_ +#define _PLATFORMFONT_H_ + +// Charsets for fonts + +// [tom, 7/27/2005] These are intended to map to their Win32 equivalents. This +// enumeration may require changes to accommodate other platforms. +enum FontCharset +{ + TGE_ANSI_CHARSET = 0, + TGE_SYMBOL_CHARSET, + TGE_SHIFTJIS_CHARSET, + TGE_HANGEUL_CHARSET, + TGE_HANGUL_CHARSET, + TGE_GB2312_CHARSET, + TGE_CHINESEBIG5_CHARSET, + TGE_OEM_CHARSET, + TGE_JOHAB_CHARSET, + TGE_HEBREW_CHARSET, + TGE_ARABIC_CHARSET, + TGE_GREEK_CHARSET, + TGE_TURKISH_CHARSET, + TGE_VIETNAMESE_CHARSET, + TGE_THAI_CHARSET, + TGE_EASTEUROPE_CHARSET, + TGE_RUSSIAN_CHARSET, + TGE_MAC_CHARSET, + TGE_BALTIC_CHARSET +}; + +extern const char *getCharSetName(const U32 charSet); + +class PlatformFont +{ +public: + struct CharInfo + { + S16 bitmapIndex; ///< @note -1 indicates character is NOT to be + /// rendered, i.e., \n, \r, etc. + U32 xOffset; ///< x offset into bitmap sheet + U32 yOffset; ///< y offset into bitmap sheet + U32 width; ///< width of character (pixels) + U32 height; ///< height of character (pixels) + S32 xOrigin; + S32 yOrigin; + S32 xIncrement; + U8 *bitmapData; ///< temp storage for bitmap data + }; + + virtual ~PlatformFont() {} + + /// Is the specified character valid for rendering? + virtual bool isValidChar(const UTF16 ch) const = 0; + virtual bool isValidChar(const UTF8 *str) const = 0; + + virtual U32 getFontHeight() const = 0; + virtual U32 getFontBaseLine() const = 0; + + virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const = 0; + virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const = 0; + + /// This is just for createPlatformFont to call. + /// + /// @todo Rethink this so we don't have a private public. + virtual bool create(const char *name, U32 size, U32 charset = TGE_ANSI_CHARSET) = 0; + static void enumeratePlatformFonts( Vector& fonts, UTF16* fontFamily = NULL ); +}; + +extern PlatformFont *createPlatformFont(const char *name, U32 size, U32 charset = TGE_ANSI_CHARSET); + +#endif // _PLATFORMFONT_H_ diff --git a/platform/platformInput.h b/platform/platformInput.h new file mode 100644 index 0000000..2bce3d7 --- /dev/null +++ b/platform/platformInput.h @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMINPUT_H_ +#define _PLATFORMINPUT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +#include "platform/event.h" + +//------------------------------------------------------------------------------ +U8 TranslateOSKeyCode( U8 vcode ); +U8 TranslateKeyCodeToOS(U8 keycode); + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +class InputDevice : public SimObject +{ +protected: + char mName[30]; + +public: + struct ObjInfo + { + InputEventType mType; + InputObjectInstances mInst; + S32 mMin, mMax; + }; + + inline const char* getDeviceName() + { + return mName; + } + + virtual bool process() = 0; +}; + +//------------------------------------------------------------------------------ + +class InputManager : public SimGroup +{ +protected: + bool mEnabled; + +public: + inline bool isEnabled() + { + return mEnabled; + } + + virtual bool enable() = 0; + virtual void disable() = 0; + virtual void process() = 0; +}; + +enum KEY_STATE +{ + STATE_LOWER, + STATE_UPPER, + STATE_GOOFY +}; + +//------------------------------------------------------------------------------ +class Input +{ +protected: + static InputManager* smManager; + + static bool smActive; + + /// Current modifier keys. + static U8 smModifierKeys; + + static bool smLastKeyboardActivated; + static bool smLastMouseActivated; + static bool smLastJoystickActivated; + +public: + static void init(); + static void destroy(); + + static bool enable(); + static void disable(); + + static void activate(); + static void deactivate(); + + static U16 getAscii( U16 keyCode, KEY_STATE keyState ); + static U16 getKeyCode( U16 asciiCode ); + + static bool isEnabled(); + static bool isActive(); + + static void process(); + + static InputManager* getManager(); + + static U8 getModifierKeys() {return smModifierKeys;} + static void setModifierKeys(U8 mod) {smModifierKeys = mod;} +#ifdef LOG_INPUT + static void log( const char* format, ... ); +#endif + +#ifdef TORQUE_OS_XENON + static S32 getLockedController(); +#endif + + /// Global input routing JournaledSignal; post input events here for + /// processing. + static InputEvent smInputEvent; +}; + +#endif // _H_PLATFORMINPUT_ diff --git a/platform/platformIntrinsics.gcc.h b/platform/platformIntrinsics.gcc.h new file mode 100644 index 0000000..67364e7 --- /dev/null +++ b/platform/platformIntrinsics.gcc.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_PLATFORM_PLATFORMINTRINSICS_GCC_H_ +#define _TORQUE_PLATFORM_PLATFORMINTRINSICS_GCC_H_ + +/// @file +/// Compiler intrinsics for GCC. + +#ifdef TORQUE_OS_MAC +#include +#elif defined(TORQUE_OS_PS3) +#include +#endif + +// Fetch-And-Add + +inline void dFetchAndAdd( volatile U32& ref, U32 val ) +{ + #if defined(TORQUE_OS_PS3) + cellAtomicAdd32( (std::uint32_t *)&ref, val ); + #elif !defined(TORQUE_OS_MAC) + __sync_fetch_and_add( ( volatile long* ) &ref, val ); + #else + OSAtomicAdd32( val, (int32_t* ) &ref); + #endif +} +inline void dFetchAndAdd( volatile S32& ref, S32 val ) +{ + #if defined(TORQUE_OS_PS3) + cellAtomicAdd32( (std::uint32_t *)&ref, val ); + #elif !defined(TORQUE_OS_MAC) + __sync_fetch_and_add( ( volatile long* ) &ref, val ); + #else + OSAtomicAdd32( val, (int32_t* ) &ref); + #endif +} + +// Compare-And-Swap + +inline bool dCompareAndSwap( volatile U32& ref, U32 oldVal, U32 newVal ) +{ + // bool + //OSAtomicCompareAndSwap32(int32_t oldValue, int32_t newValue, volatile int32_t *theValue); + #if defined(TORQUE_OS_PS3) + return ( cellAtomicCompareAndSwap32( (std::uint32_t *)&ref, newVal, oldVal ) == oldVal ); + #elif !defined(TORQUE_OS_MAC) + return ( __sync_val_compare_and_swap( ( volatile long* ) &ref, oldVal, newVal ) == oldVal ); + #else + return OSAtomicCompareAndSwap32(oldVal, newVal, (int32_t *) &ref); + #endif +} +inline bool dCompareAndSwap( volatile U64& ref, U64 oldVal, U64 newVal ) +{ + #if defined(TORQUE_OS_PS3) + return ( cellAtomicCompareAndSwap32( (std::uint32_t *)&ref, newVal, oldVal ) == oldVal ); + #elif !defined(TORQUE_OS_MAC) + return ( __sync_val_compare_and_swap( ( volatile long long* ) &ref, oldVal, newVal ) == oldVal ); + #else + return OSAtomicCompareAndSwap64(oldVal, newVal, (int64_t *) &ref); + #endif + +} + +#endif // _TORQUE_PLATFORM_PLATFORMINTRINSICS_GCC_H_ diff --git a/platform/platformIntrinsics.h b/platform/platformIntrinsics.h new file mode 100644 index 0000000..ee3a2a8 --- /dev/null +++ b/platform/platformIntrinsics.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMINTRINSICS_H_ +#define _PLATFORMINTRINSICS_H_ + +#ifndef _TORQUE_TYPES_H_ +# include "platform/types.h" +#endif + +#if defined( TORQUE_COMPILER_VISUALC ) +# include "platform/platformIntrinsics.visualc.h" +#elif defined ( TORQUE_COMPILER_GCC ) +# include "platform/platformIntrinsics.gcc.h" +#else +# error No intrinsics implemented for compiler. +#endif + +//TODO: 64bit safe + +template< typename T > +inline bool dCompareAndSwap( T* volatile& refPtr, T* oldPtr, T* newPtr ) +{ + return dCompareAndSwap( *reinterpret_cast< volatile U32* >( &refPtr ), ( U32 ) oldPtr, ( U32 ) newPtr ); +} + +// Test-And-Set + +inline bool dTestAndSet( volatile U32& ref ) +{ + return dCompareAndSwap( ref, 0, 1 ); +} +inline bool dTestAndSet( volatile U64& ref ) +{ + return dCompareAndSwap( ref, 0, 1 ); +} + +#endif // _PLATFORMINTRINSICS_H_ diff --git a/platform/platformIntrinsics.visualc.h b/platform/platformIntrinsics.visualc.h new file mode 100644 index 0000000..73d8372 --- /dev/null +++ b/platform/platformIntrinsics.visualc.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_PLATFORM_PLATFORMINTRINSICS_VISUALC_H_ +#define _TORQUE_PLATFORM_PLATFORMINTRINSICS_VISUALC_H_ + +/// @file +/// Compiler intrinsics for Visual C++. + +#if defined(TORQUE_OS_XENON) +# include +# define _InterlockedExchangeAdd InterlockedExchangeAdd +# define _InterlockedExchangeAdd64 InterlockedExchangeAdd64 +#else +# include +#endif + +// Fetch-And-Add + +inline void dFetchAndAdd( volatile U32& ref, U32 val ) +{ + _InterlockedExchangeAdd( ( volatile long* ) &ref, val ); +} +inline void dFetchAndAdd( volatile S32& ref, S32 val ) +{ + _InterlockedExchangeAdd( ( volatile long* ) &ref, val ); +} + +#if defined(TORQUE_OS_XENON) +// Not available on x86 +inline void dFetchAndAdd( volatile U64& ref, U64 val ) +{ + _InterlockedExchangeAdd64( ( volatile __int64* ) &ref, val ); +} +#endif + +// Compare-And-Swap + +inline bool dCompareAndSwap( volatile U32& ref, U32 oldVal, U32 newVal ) +{ + return ( _InterlockedCompareExchange( ( volatile long* ) &ref, newVal, oldVal ) == oldVal ); +} +inline bool dCompareAndSwap( volatile U64& ref, U64 oldVal, U64 newVal ) +{ + return ( _InterlockedCompareExchange64( ( volatile __int64* ) &ref, newVal, oldVal ) == oldVal ); +} + +#endif // _TORQUE_PLATFORM_PLATFORMINTRINSICS_VISUALC_H_ diff --git a/platform/platformMemory.cpp b/platform/platformMemory.cpp new file mode 100644 index 0000000..0ee777d --- /dev/null +++ b/platform/platformMemory.cpp @@ -0,0 +1,1666 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformMemory.h" +#include "core/stream/fileStream.h" +#include "core/strings/stringFunctions.h" +#include "console/console.h" +#include "platform/profiler.h" +#include "platform/threads/mutex.h" + +// If profile paths are enabled, disable profiling of the +// memory manager as that would cause a cyclic dependency +// through the string table's allocation stuff used by the +// profiler (talk about a long sentence...) + +#ifdef TORQUE_ENABLE_PROFILE_PATH +# undef PROFILE_START +# undef PROFILE_END +# undef PROFILE_SCOPE +# define PROFILE_START( x ) +# define PROFILE_END() +# define PROFILE_SCOPE( x ) +#endif + +#ifdef TORQUE_MULTITHREAD +void * gMemMutex = NULL; +#endif + +//-------------------------------------- Make sure we don't have the define set +#ifdef new +#undef new +#endif + +enum MemConstants { + Allocated = BIT(0), + Array = BIT(1), + DebugFlag = BIT(2), + Reallocated = BIT(3), /// This flag is set if the memory has been allocated, then 'realloc' is called + GlobalFlag = BIT(4), + StaticFlag = BIT(5), + AllocatedGuard = 0xCEDEFEDE, + FreeGuard = 0x5555FFFF, + MaxAllocationAmount = 0xFFFFFFFF, + TreeNodeAllocCount = 2048, +}; + +inline U32 flagToBit( Memory::EFlag flag ) +{ + using namespace Memory; + + U32 bit = 0; + switch( flag ) + { + case FLAG_Debug: bit = DebugFlag; break; + case FLAG_Global: bit = GlobalFlag; break; + case FLAG_Static: bit = StaticFlag; break; + } + return bit; +} + +enum RedBlackTokens { + Red = 0, + Black = 1 +}; + +static U32 MinPageSize = 8 * 1024 * 1024; + +#if !defined(TORQUE_SHIPPING) && defined(TORQUE_DEBUG_GUARD) +#define LOG_PAGE_ALLOCS +#endif + +U32 gNewNewTotal = 0; +U32 gImageAlloc = 0; +//--------------------------------------------------------------------------- + +namespace Memory +{ + +ConsoleFunctionGroupBegin( Memory, "Memory manager utility functions."); + +struct FreeHeader; + +/// Red/Black Tree Node - used to store queues of free blocks +struct TreeNode +{ + U32 size; + TreeNode *parent; + TreeNode *left; + TreeNode *right; + U32 color; + FreeHeader *queueHead; + FreeHeader *queueTail; + U32 unused; +}; + +struct Header +{ + // doubly linked list of allocated and free blocks - + // contiguous in memory. +#ifdef TORQUE_DEBUG_GUARD + U32 preguard[4]; +#endif + Header *next; + Header *prev; + dsize_t size; + U32 flags; +#ifdef TORQUE_DEBUG_GUARD + #ifdef TORQUE_ENABLE_PROFILE_PATH + U32 unused[5]; + #else + U32 unused[4]; + #endif + U32 postguard[4]; +#endif +}; + +struct AllocatedHeader +{ +#ifdef TORQUE_DEBUG_GUARD + U32 preguard[4]; +#endif + Header *next; + Header *prev; + dsize_t size; + U32 flags; + +#ifdef TORQUE_DEBUG_GUARD + // an allocated header will only have this stuff if TORQUE_DEBUG_GUARD + U32 line; + U32 allocNum; + const char *fileName; + #ifdef TORQUE_ENABLE_PROFILE_PATH + const char * profilePath; + #endif + U32 realSize; + U32 postguard[4]; +#endif + + void* getUserPtr() + { + return ( this + 1 ); + } +}; + +struct FreeHeader +{ +#ifdef TORQUE_DEBUG_GUARD + U32 preguard[4]; +#endif + Header *next; + Header *prev; + dsize_t size; + U32 flags; + +// since a free header has at least one cache line (16 bytes) +// we can tag some more stuff on: + + FreeHeader *nextQueue; // of the same size + FreeHeader *prevQueue; // doubly linked + TreeNode *treeNode; // which tree node we're coming off of. + U32 guard; +#ifdef TORQUE_DEBUG_GUARD + #ifdef TORQUE_ENABLE_PROFILE_PATH + U32 unused; + #endif + U32 postguard[4]; +#endif +}; + +struct PageRecord +{ + dsize_t allocSize; + PageRecord *prevPage; + Header *headerList; // if headerList is NULL, this is a treeNode page + void *basePtr; + U32 unused[4]; // even out the record to 32 bytes... + // so if we're on a 32-byte cache-line comp, the tree nodes + // will cache better +}; + +PageRecord *gPageList = NULL; +TreeNode nil; +TreeNode *NIL = &nil; +TreeNode *gFreeTreeRoot = &nil; +TreeNode *gTreeFreeList = NULL; + +U32 gInsertCount = 0; +U32 gRemoveCount = 0; +U32 gBreakAlloc = 0xFFFFFFFF; +U32 gCurrAlloc = 0; +char gLogFilename[256] = "memlog.txt"; +bool gEnableLogging = false; +bool gNeverLogLeaks = 0; +bool gAlwaysLogLeaks = 0; +U32 gBytesAllocated = 0; +U32 gPageBytesAllocated = 0; + +struct HeapIterator +{ + PageRecord* mCurrentPage; + Header* mCurrentHeader; + bool mAllocatedOnly; + + HeapIterator( bool allocatedOnly = true ) + : mCurrentPage( gPageList ), + mAllocatedOnly( allocatedOnly ), + mCurrentHeader( NULL ) + { + if( mCurrentPage ) + { + mCurrentHeader = mCurrentPage->headerList; + while( !mCurrentHeader && mCurrentPage ) + { + mCurrentPage = mCurrentPage->prevPage; + mCurrentHeader = mCurrentPage->headerList; + } + + if( mCurrentHeader && mAllocatedOnly && !( mCurrentHeader->flags & Allocated ) ) + ++ ( *this ); // Advance to first allocated record. + } + } + + bool isValid() const + { + return ( mCurrentHeader != NULL ); + } + HeapIterator& operator ++() + { + do + { + if( !mCurrentHeader ) + { + if( mCurrentPage ) + mCurrentPage = mCurrentPage->prevPage; + + if( !mCurrentPage ) + break; + mCurrentHeader = mCurrentPage->headerList; + } + else + mCurrentHeader = mCurrentHeader->next; + } + while( !mCurrentHeader || ( mAllocatedOnly && !( mCurrentHeader->flags & Allocated ) ) ); + + return *this; + } + operator Header*() const + { + return mCurrentHeader; + } + Header* operator *() const + { + return mCurrentHeader; + } + Header* operator ->() const + { + return mCurrentHeader; + } +}; + +#ifdef TORQUE_DEBUG_GUARD + +static bool checkGuard(Header *header, bool alloc) +{ + U32 guardVal = alloc ? AllocatedGuard : FreeGuard; + for(U32 i = 0; i < 4; i++) + if(header->preguard[i] != guardVal || header->postguard[i] != guardVal) + { + Platform::debugBreak(); + return false; + } + + return true; +} + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void setGuard(Header *header, bool alloc) +{ + U32 guardVal = alloc ? AllocatedGuard : FreeGuard; + for(U32 i = 0; i < 4; i++) + header->preguard[i] = header->postguard[i] = guardVal; +} +#endif // !defined(TORQUE_DISABLE_MEMORY_MANAGER) + +#endif // TORQUE_DEBUG_GUARD + +static void memoryError() +{ + // free all the pages + PageRecord *walk = gPageList; + while(walk) { + PageRecord *prev = walk->prevPage; + dRealFree(walk); + walk = prev; + } + AssertFatal(false, "Error allocating memory! Shutting down."); + Platform::AlertOK("Torque Memory Error", "Error allocating memory. Shutting down.\n"); + Platform::forceShutdown(-1); +} + +PageRecord *allocPage(dsize_t pageSize) +{ + pageSize += sizeof(PageRecord); + void* base = dRealMalloc(pageSize); + if (base == NULL) + memoryError(); + + PageRecord *rec = (PageRecord *) base; + rec->basePtr = (void *) (rec + 1); + rec->allocSize = pageSize; + rec->prevPage = gPageList; + gPageList = rec; + rec->headerList = NULL; + return rec; +} + +TreeNode *allocTreeNode() +{ + if(!gTreeFreeList) + { + PageRecord *newPage = allocPage(TreeNodeAllocCount * sizeof(TreeNode)); + TreeNode *walk = (TreeNode *) newPage->basePtr; + U32 i; + gTreeFreeList = walk; + for(i = 0; i < TreeNodeAllocCount - 1; i++, walk++) + walk->parent = walk + 1; + walk->parent = NULL; + } + TreeNode *ret = gTreeFreeList; + gTreeFreeList = ret->parent; + return ret; +} + +void freeTreeNode(TreeNode *tn) +{ + tn->parent = gTreeFreeList; + gTreeFreeList = tn; +} + + +static U32 validateTreeRecurse(TreeNode *tree) +{ + if(tree == NIL) + return 1; + // check my left tree + int lcount, rcount, nc = 0; + + if(tree->color == Red) + { + if(tree->left->color == Red || tree->right->color == Red) + Platform::debugBreak(); + } + else + nc = 1; + + FreeHeader *walk = tree->queueHead; + if(!walk) + Platform::debugBreak(); + + FreeHeader *prev = NULL; + while(walk) + { + if(walk->prevQueue != prev) + Platform::debugBreak(); + if(walk->treeNode != tree) + Platform::debugBreak(); + if(walk->size != tree->size) + Platform::debugBreak(); + if(!walk->nextQueue && walk != tree->queueTail) + Platform::debugBreak(); + prev = walk; + walk = walk->nextQueue; + } + + lcount = validateTreeRecurse(tree->left); + rcount = validateTreeRecurse(tree->right); + if(lcount != rcount) + Platform::debugBreak(); + return lcount + nc; +} + +static void validateParentageRecurse(TreeNode *tree) +{ + if(tree->left != NIL) + { + if(tree->left->parent != tree) + Platform::debugBreak(); + + if(tree->left->size > tree->size) + Platform::debugBreak(); + validateParentageRecurse(tree->left); + } + if(tree->right != NIL) + { + if(tree->right->parent != tree) + Platform::debugBreak(); + + if(tree->right->size < tree->size) + Platform::debugBreak(); + validateParentageRecurse(tree->right); + } +} + +static void validateTree() +{ + if(gFreeTreeRoot == NIL) + return; + validateParentageRecurse(gFreeTreeRoot); + validateTreeRecurse(gFreeTreeRoot); +} + +void validate() +{ +#ifdef TORQUE_MULTITHREAD + if(!gMemMutex) + gMemMutex = Mutex::createMutex(); + + Mutex::lockMutex(gMemMutex); +#endif + + + // first validate the free tree: + validateTree(); + // now validate all blocks: + for(PageRecord *list = gPageList; list; list = list->prevPage) + { + Header *prev = NULL; + for(Header *walk = list->headerList; walk; walk = walk->next) + { +#ifdef TORQUE_DEBUG_GUARD + checkGuard(walk, walk->flags & Allocated); +#endif + if(walk->prev != prev) + Platform::debugBreak(); + prev = walk; + if(walk->next && ((const char *)(walk->next) != (const char *)(walk) + sizeof(Header) + walk->size)) + Platform::debugBreak(); + } + } + +#ifdef TORQUE_MULTITHREAD + Mutex::unlockMutex(gMemMutex); +#endif +} + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void rotateLeft(TreeNode *hdr) +{ + TreeNode *temp = hdr->right; + hdr->right = temp->left; + if(temp->left != NIL) + temp->left->parent = hdr; + temp->parent = hdr->parent; + if(temp->parent == NIL) + gFreeTreeRoot = temp; + else if(hdr == hdr->parent->left) + hdr->parent->left = temp; + else + hdr->parent->right = temp; + temp->left = hdr; + hdr->parent = temp; +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void rotateRight(TreeNode *hdr) +{ + TreeNode *temp = hdr->left; + hdr->left = temp->right; + if(temp->right != NIL) + temp->right->parent = hdr; + temp->parent = hdr->parent; + if(temp->parent == NIL) + gFreeTreeRoot = temp; + else if(hdr == hdr->parent->left) + hdr->parent->left = temp; + else + hdr->parent->right = temp; + temp->right = hdr; + hdr->parent = temp; +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void treeInsert(FreeHeader *fhdr) +{ +#ifdef TORQUE_DEBUG_GUARD + checkGuard((Header *) fhdr, true); // check to see that it's got allocated guards + setGuard((Header *) fhdr, false); +#endif + //gInsertCount++; + + TreeNode *newParent = NIL; + TreeNode *walk = gFreeTreeRoot; + while(walk != NIL) + { + newParent = walk; + if(fhdr->size < walk->size) + walk = walk->left; + else if(fhdr->size > walk->size) + walk = walk->right; + else // tag it on the end of the queue... + { + // insert it on this header... + walk->queueTail->nextQueue = fhdr; + fhdr->prevQueue = walk->queueTail; + walk->queueTail = fhdr; + fhdr->nextQueue = NULL; + fhdr->treeNode = walk; + return; + } + } + TreeNode *hdr = allocTreeNode(); + hdr->size = fhdr->size; + hdr->queueHead = hdr->queueTail = fhdr; + fhdr->nextQueue = fhdr->prevQueue = NULL; + fhdr->treeNode = hdr; + + hdr->left = NIL; + hdr->right = NIL; + + hdr->parent = newParent; + + if(newParent == NIL) + gFreeTreeRoot = hdr; + else if(hdr->size < newParent->size) + newParent->left = hdr; + else + newParent->right = hdr; + + // do red/black rotations + hdr->color = Red; + while(hdr != gFreeTreeRoot && (hdr->parent->color == Red)) + { + TreeNode *parent = hdr->parent; + TreeNode *pparent = hdr->parent->parent; + + if(parent == pparent->left) + { + TreeNode *temp = pparent->right; + if(temp->color == Red) + { + parent->color = Black; + temp->color = Black; + pparent->color = Red; + hdr = pparent; + } + else + { + if(hdr == parent->right) + { + hdr = parent; + rotateLeft(hdr); + parent = hdr->parent; + pparent = hdr->parent->parent; + } + parent->color = Black; + pparent->color = Red; + rotateRight(pparent); + } + } + else + { + TreeNode *temp = pparent->left; + if(temp->color == Red) + { + parent->color = Black; + temp->color = Black; + pparent->color = Red; + hdr = pparent; + } + else + { + if(hdr == parent->left) + { + hdr = parent; + rotateRight(hdr); + parent = hdr->parent; + pparent = hdr->parent->parent; + } + parent->color = Black; + pparent->color = Red; + rotateLeft(pparent); + } + } + } + gFreeTreeRoot->color = Black; + //validateTree(); +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void treeRemove(FreeHeader *hdr) +{ +#ifdef TORQUE_DEBUG_GUARD + checkGuard((Header *) hdr, false); + setGuard((Header *) hdr, true); +#endif + //validateTree(); + //gRemoveCount++; + + FreeHeader *prev = hdr->prevQueue; + FreeHeader *next = hdr->nextQueue; + + if(prev) + prev->nextQueue = next; + else + hdr->treeNode->queueHead = next; + + if(next) + next->prevQueue = prev; + else + hdr->treeNode->queueTail = prev; + + if(prev || next) + return; + + TreeNode *z = hdr->treeNode; + + nil.color = Black; + + TreeNode *y, *x; + if(z->left == NIL || z->right == NIL) + y = z; + else + { + y = z->right; + while(y->left != NIL) + y = y->left; + } + if(y->left != NIL) + x = y->left; + else + x = y->right; + + x->parent = y->parent; + if(y->parent == NIL) + gFreeTreeRoot = x; + else if(y == y->parent->left) + y->parent->left = x; + else + y->parent->right = x; + + U32 yColor = y->color; + if(y != z) + { + // copy y's important fields into z (since we're going to free y) + if(z->parent->left == z) + z->parent->left = y; + else if(z->parent->right == z) + z->parent->right = y; + y->left = z->left; + y->right = z->right; + if(y->left != NIL) + y->left->parent = y; + if(y->right != NIL) + y->right->parent = y; + y->parent = z->parent; + if(z->parent == NIL) + gFreeTreeRoot = y; + y->color = z->color; + if(x->parent == z) + x->parent = y; + //validateTree(); + } + freeTreeNode(z); + + if(yColor == Black) + { + while(x != gFreeTreeRoot && x->color == Black) + { + TreeNode *w; + if(x == x->parent->left) + { + w = x->parent->right; + if(w->color == Red) + { + w->color = Black; + x->parent->color = Red; + rotateLeft(x->parent); + w = x->parent->right; + } + if(w->left->color == Black && w->right->color == Black) + { + w->color = Red; + x = x->parent; + } + else + { + if(w->right->color == Black) + { + w->left->color = Black; + rotateRight(w); + w = x->parent->right; + } + w->color = x->parent->color; + x->parent->color = Black; + w->right->color = Black; + rotateLeft(x->parent); + x = gFreeTreeRoot; + } + } + else + { + w = x->parent->left; + if(w->color == Red) + { + w->color = Black; + x->parent->color = Red; + rotateRight(x->parent); + w = x->parent->left; + } + if(w->left->color == Black && w->right->color == Black) + { + w->color = Red; + x = x->parent; + } + else + { + if(w->left->color == Black) + { + w->right->color = Black; + rotateLeft(w); + w = x->parent->left; + } + w->color = x->parent->color; + x->parent->color = Black; + w->left->color = Black; + rotateRight(x->parent); + x = gFreeTreeRoot; + } + } + } + x->color = Black; + } + //validateTree(); +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static FreeHeader *treeFindSmallestGreaterThan(dsize_t size) +{ + TreeNode *bestMatch = NIL; + TreeNode *walk = gFreeTreeRoot; + while(walk != NIL) + { + if(size == walk->size) + return walk->queueHead; + else if(size > walk->size) + walk = walk->right; + else // size < walk->size + { + bestMatch = walk; + walk = walk->left; + } + } + //validateTree(); + if(bestMatch != NIL) + return bestMatch->queueHead; + + return NULL; +} +#endif + +/// Trigger a breakpoint if ptr is not a valid heap pointer or if its memory guards +/// have been destroyed (only if TORQUE_DEBUG_GUARD is enabled). +/// +/// @note This function does not allow interior pointers! + +void checkPtr( void* ptr ) +{ + for( HeapIterator iter; iter.isValid(); ++ iter ) + { + AllocatedHeader* header = ( AllocatedHeader* ) *iter; + if( header->getUserPtr() == ptr ) + { + char buffer[ 1024 ]; + +#ifdef TORQUE_DEBUG_GUARD + if( !checkGuard( *iter, true ) ) + { + dSprintf( buffer, sizeof( buffer ), "0x%x is a valid heap pointer but has its guards corrupted", ptr ); + Platform::outputDebugString( buffer ); + return; + } +#endif + + dSprintf( buffer, sizeof( buffer ), "0x%x is a valid heap pointer", ptr ); + Platform::outputDebugString( buffer ); + return; + } + } + + char buffer[ 1024 ]; + dSprintf( buffer, sizeof( buffer ), "0x%x is not a valid heap pointer", ptr ); + Platform::outputDebugString( buffer ); + + Platform::debugBreak(); +} + +/// Dump info on all memory blocks that are still allocated. +/// @note Only works if TORQUE_DISABLE_MEMORY_MANAGER is not defined; otherwise this is a NOP. + +void ensureAllFreed() +{ +#ifndef TORQUE_DISABLE_MEMORY_MANAGER + + U32 numLeaks = 0; + U32 bytesLeaked = 0; + + for( HeapIterator iter; iter.isValid(); ++ iter ) + { + AllocatedHeader* header = ( AllocatedHeader* ) *iter; + if( !( header->flags & GlobalFlag ) ) + { + // Note: can't spill profile paths here since they by + // now are all invalid (they're on the now freed string table) + +#ifdef TORQUE_DEBUG_GUARD + Platform::outputDebugString( "MEMORY LEAKED: %i %s %s:%i = %i", + header->allocNum, + ( header->flags & StaticFlag ? "(static)" : "" ), + header->fileName, header->line, header->size ); + numLeaks ++; + bytesLeaked += header->size; +#endif + } + } + + if( numLeaks ) + Platform::outputDebugString( "NUM LEAKS: %i (%i bytes)", numLeaks, bytesLeaked ); +#endif +} + +struct MemDumpLog +{ + U32 size; + U32 count; + U32 depthTotal; + U32 maxDepth; + U32 minDepth; +}; + +void logDumpTraverse(MemDumpLog *sizes, TreeNode *header, U32 depth) +{ + if(header == NIL) + return; + MemDumpLog *mySize = sizes; + while(mySize->size < header->size) + mySize++; + + U32 cnt = 0; + for(FreeHeader *walk = header->queueHead; walk; walk = walk->nextQueue) + cnt++; + mySize->count += cnt; + mySize->depthTotal += depth * cnt; + mySize->maxDepth = depth > mySize->maxDepth ? depth : mySize->maxDepth; + mySize->minDepth = depth < mySize->minDepth ? depth : mySize->minDepth; + logDumpTraverse(sizes, header->left, depth + 1); + logDumpTraverse(sizes, header->right, depth + 1); +} + +#ifdef TORQUE_DEBUG +ConsoleFunction(ValidateMemory, void, 1, 1, "ValidateMemory();") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + validate(); +} +#endif + +ConsoleFunction(FreeMemoryDump, void, 1, 1, "FreeMemoryDump();") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + U32 startSize = 16; + MemDumpLog memSizes[20]; + U32 i; + for(i = 0; i < 20; i++) + { + memSizes[i].size = startSize << i; + memSizes[i].count = 0; + memSizes[i].depthTotal = 0; + memSizes[i].maxDepth = 0; + memSizes[i].minDepth = 1000; + } + memSizes[19].size = MaxAllocationAmount; + logDumpTraverse(memSizes, gFreeTreeRoot, 1); + MemDumpLog fullMem; + fullMem.count = 0; + fullMem.depthTotal = 0; + fullMem.maxDepth = 0; + fullMem.minDepth = 1000; + + for(i = 0; i < 20; i++) + { + if(memSizes[i].count) + Con::printf("Size: %d - Free blocks: %d Max Depth: %d Min Depth: %d Average Depth: %g", + memSizes[i].size, memSizes[i].count, memSizes[i].maxDepth, memSizes[i].minDepth, + F32(memSizes[i].depthTotal) / F32(memSizes[i].count)); + + fullMem.count += memSizes[i].count; + fullMem.depthTotal += memSizes[i].depthTotal; + fullMem.maxDepth = memSizes[i].maxDepth > fullMem.maxDepth ? memSizes[i].maxDepth : fullMem.maxDepth; + fullMem.minDepth = memSizes[i].minDepth < fullMem.minDepth ? memSizes[i].minDepth : fullMem.minDepth; + } + Con::printf("Total free blocks: %d Max Depth: %d Min Depth: %d Average Depth: %g", + fullMem.count, fullMem.maxDepth, fullMem.minDepth, F32(fullMem.depthTotal) / F32(fullMem.count)); +} + +#ifdef TORQUE_DEBUG_GUARD + +void flagCurrentAllocs( EFlag flag ) +{ +#ifdef TORQUE_ENABLE_PROFILE_PATH + if (gProfiler && !gProfiler->isEnabled()) + { + gProfiler->enable(true); + // warm it up + //gProfiler->dumpToConsole(); + } +#endif + + U32 bit = flagToBit( flag ); + for( HeapIterator iter; iter.isValid(); ++ iter ) + iter->flags |= bit; +} + +ConsoleFunction(FlagCurrentAllocs, void, 1, 1, "FlagCurrentAllocs();") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + flagCurrentAllocs(); +} + +void dumpUnflaggedAllocs(const char *file, EFlag flag) +{ + countUnflaggedAllocs(file, NULL, flag); +} + +S32 countUnflaggedAllocs(const char * filename, S32 *outUnflaggedRealloc, EFlag flag) +{ + S32 unflaggedAllocCount = 0; + S32 unflaggedReAllocCount = 0; + + FileStream fws; + bool useFile = filename && *filename;; + if (useFile) + useFile = fws.open(filename, Torque::FS::File::Write); + char buffer[1024]; + + U32 bit = flagToBit( flag ); + + PageRecord* walk; + for (walk = gPageList; walk; walk = walk->prevPage) + { + for(Header *probe = walk->headerList; probe; probe = probe->next) + { + if (probe->flags & Allocated) + { + AllocatedHeader* pah = (AllocatedHeader*)probe; + if (!(pah->flags & bit)) + { + // If you want to extract further information from an unflagged + // memory allocation, do the following: + // U8 *foo = (U8 *)pah; + // foo += sizeof(Header); + // FooObject *obj = (FooObject *)foo; + dSprintf(buffer, 1023, "%s%s\t%d\t%d\t%d\r\n", + pah->flags & Reallocated ? "[R] " : "", + pah->fileName != NULL ? pah->fileName : "Undetermined", + pah->line, pah->realSize, pah->allocNum); + + if( pah->flags & Reallocated ) + unflaggedReAllocCount++; + else + unflaggedAllocCount++; + + if (useFile) + { + fws.write(dStrlen(buffer), buffer); + fws.write(2, "\r\n"); + } + else + { + if( pah->flags & Reallocated ) + Con::warnf(buffer); + else + Con::errorf(buffer); + } + +#ifdef TORQUE_ENABLE_PROFILE_PATH + static char line[4096]; + dSprintf(line, sizeof(line), " %s\r\nreal size=%d", + pah->profilePath ? pah->profilePath : "unknown", + pah->realSize); + + if (useFile) + { + fws.write(dStrlen(line), line); + fws.write(2, "\r\n"); + } + else + { + if( pah->flags & Reallocated ) + Con::warnf(line); + else + Con::errorf(line); + } +#endif + + } + } + } + } + + if (useFile) + fws.close(); + + if( outUnflaggedRealloc != NULL ) + *outUnflaggedRealloc = unflaggedReAllocCount; + + return unflaggedAllocCount; +} + +ConsoleFunction(DumpUnflaggedAllocs, void, 1, 2, "DumpUnflaggedAllocs(filename [optional, if not specified dumps to console]);") +{ + TORQUE_UNUSED(argc); + dumpUnflaggedAllocs(argc==2 ? argv[1] : NULL); +} + +static void initLog() +{ + static const char* sInitString = " --- INIT MEMORY LOG (ACTION): (FILE) (LINE) (SIZE) (ALLOCNUMBER) ---\r\n"; + + FileStream fws; + fws.open(gLogFilename, Torque::FS::File::Write); + fws.write(dStrlen(sInitString), sInitString); + fws.close(); +} + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void logAlloc(const AllocatedHeader* hdr, S32 memSize) +{ + FileStream fws; + fws.open(gLogFilename, Torque::FS::File::ReadWrite); + fws.setPosition(fws.getStreamSize()); + + char buffer[1024]; + dSprintf(buffer, 1023, "alloc: %s %d %d %d\r\n", + hdr->fileName != NULL ? hdr->fileName : "Undetermined", + hdr->line, memSize, hdr->allocNum); + fws.write(dStrlen(buffer), buffer); + fws.close(); +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void logRealloc(const AllocatedHeader* hdr, S32 memSize) +{ + FileStream fws; + fws.open(gLogFilename, Torque::FS::File::ReadWrite); + fws.setPosition(fws.getStreamSize()); + + char buffer[1024]; + dSprintf(buffer, 1023, "realloc: %s %d %d %d\r\n", + hdr->fileName != NULL ? hdr->fileName : "Undetermined", + hdr->line, memSize, hdr->allocNum); + fws.write(dStrlen(buffer), buffer); + fws.close(); +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void logFree(const AllocatedHeader* hdr) +{ + FileStream fws; + fws.open(gLogFilename, Torque::FS::File::ReadWrite); + fws.setPosition(fws.getStreamSize()); + + char buffer[1024]; + dSprintf(buffer, 1023, "free: %s %d %d\r\n", + hdr->fileName != NULL ? hdr->fileName : "Undetermined", + hdr->line, hdr->allocNum); + fws.write(dStrlen(buffer), buffer); + fws.close(); +} +#endif // !defined(TORQUE_DISABLE_MEMORY_MANAGER) + +#endif + +void enableLogging(const char* fileName) +{ + dStrcpy(gLogFilename, fileName); + if (!gEnableLogging) + { + gEnableLogging = true; +#ifdef TORQUE_DEBUG_GUARD + initLog(); +#endif + } +} + +void disableLogging() +{ + gLogFilename[0] = '\0'; + gEnableLogging = false; +} + +// CodeReview - this is never called so commented out to save warning. +// Do we want to re-enable it? Might be nice to get leak tracking on +// exit...or maybe that is just a problematical feature we shouldn't +// worry about. +//static void shutdown() +//{ +//#ifdef TORQUE_MULTITHREAD +// Mutex::destroyMutex(gMemMutex); +// gMemMutex = NULL; +//#endif +// +//#ifdef TORQUE_DEBUG_GUARD +// +// // write out leaks and such +// const U32 maxNumLeaks = 1024; +// U32 numLeaks = 0; +// +// AllocatedHeader* pLeaks[maxNumLeaks]; +// for (PageRecord * walk = gPageList; walk; walk = walk->prevPage) +// for(Header *probe = walk->headerList; probe; probe = probe->next) +// if ((probe->flags & Allocated) && ((AllocatedHeader *)probe)->fileName != NULL) +// pLeaks[numLeaks++] = (AllocatedHeader *) probe; +// +// if (numLeaks && !gNeverLogLeaks) +// { +// if (gAlwaysLogLeaks || Platform::AlertOKCancel("Memory Status", "Memory leaks detected. Write to memoryLeaks.log?") == true) +// { +// char buffer[1024]; +// FileStream logFile; +// logFile.open("memoryLeaks.log", Torque::FS::File::Write); +// +// for (U32 i = 0; i < numLeaks; i++) +// { +// dSprintf(buffer, 1023, "Leak in %s: %d (%d)\r\n", pLeaks[i]->fileName, pLeaks[i]->line, pLeaks[i]->allocNum); +// logFile.write(dStrlen(buffer), buffer); +// } +// logFile.close(); +// } +// } +//#endif +// +// // then free all the memory pages +// for (PageRecord * walk = gPageList; walk; ) +// { +// PageRecord *prev = walk->prevPage; +// dRealFree(walk); +// walk = prev; +// } +//} + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static Header *allocMemPage(dsize_t pageSize) +{ + pageSize += sizeof(Header); + if(pageSize < MinPageSize) + pageSize = MinPageSize; + PageRecord *base = allocPage(pageSize); + + Header* rec = (Header*)base->basePtr; + base->headerList = rec; + + rec->size = pageSize - sizeof(Header); + rec->next = NULL; + rec->prev = NULL; + rec->flags = 0; +#ifdef TORQUE_DEBUG_GUARD + setGuard(rec, true); +#endif + +#ifdef LOG_PAGE_ALLOCS + gPageBytesAllocated += pageSize; + // total bytes allocated so far will be 0 when TORQUE_DEBUG_GUARD is disabled, so convert that into more meaningful string + const U32 StrSize = 256; + char strBytesAllocated[StrSize]; + if (gBytesAllocated > 0) + dSprintf(strBytesAllocated, sizeof(strBytesAllocated), "%i", gBytesAllocated); + else + dStrncpy(strBytesAllocated,"unknown - enable TORQUE_DEBUG_GUARD", StrSize); + +#ifndef TORQUE_MULTITHREAD // May deadlock. + // NOTE: This code may be called within Con::_printf, and if that is the case + // this will infinitly recurse. This is the reason for the code in Con::_printf + // that sets Con::active to false. -patw + if (Con::isActive()) + Con::errorf("PlatformMemory: allocating new page, total bytes allocated so far: %s (total bytes in all pages=%i)",strBytesAllocated,gPageBytesAllocated); +#endif +#endif + return rec; +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void checkUnusedAlloc(FreeHeader *header, U32 size) +{ + //validate(); + if(header->size >= size + sizeof(Header) + 16) + { + U8 *basePtr = (U8 *) header; + basePtr += sizeof(Header); + FreeHeader *newHeader = (FreeHeader *) (basePtr + size); + newHeader->next = header->next; + newHeader->prev = (Header *) header; + header->next = (Header *) newHeader; + if(newHeader->next) + newHeader->next->prev = (Header *) newHeader; + newHeader->flags = 0; + newHeader->size = header->size - size - sizeof(Header); + header->size = size; +#ifdef TORQUE_DEBUG_GUARD + setGuard((Header *) newHeader, true); +#endif + treeInsert(newHeader); + } +} +#endif + +#if defined(TORQUE_MULTITHREAD) && !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static bool gReentrantGuard = false; +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void* alloc(dsize_t size, bool array, const char* fileName, const U32 line) +{ + AssertFatal(size < MaxAllocationAmount, "Memory::alloc - tried to allocate > MaxAllocationAmount!"); + +#ifdef TORQUE_MULTITHREAD + if(!gMemMutex && !gReentrantGuard) + { + gReentrantGuard = true; + gMemMutex = Mutex::createMutex(); + gReentrantGuard = false; + } + + if(!gReentrantGuard) + Mutex::lockMutex(gMemMutex); + +#endif + + AssertFatal(size < MaxAllocationAmount, "Size error."); + //validate(); + if (size == 0) + { +#ifdef TORQUE_MULTITHREAD + if(!gReentrantGuard) + Mutex::unlockMutex(gMemMutex); +#endif + return NULL; + } + +#ifndef TORQUE_ENABLE_PROFILE_PATH + // Note: will cause crash if profile path is on + PROFILE_START(MemoryAlloc); +#endif + +#ifdef TORQUE_DEBUG_GUARD + // if we're guarding, round up to the nearest DWORD + size = ((size + 3) & ~0x3); +#else + // round up size to nearest 16 byte boundary (cache lines and all...) + size = ((size + 15) & ~0xF); +#endif + + FreeHeader *header = treeFindSmallestGreaterThan(size); + if(header) + treeRemove(header); + else + header = (FreeHeader *) allocMemPage(size); + + // ok, see if there's enough room in the block to make another block + // for this to happen it has to have enough room for a header + // and 16 more bytes. + + U8 *basePtr = (U8 *) header; + basePtr += sizeof(Header); + + checkUnusedAlloc(header, size); + + AllocatedHeader *retHeader = (AllocatedHeader *) header; + retHeader->flags = array ? (Allocated | Array) : Allocated; + +#ifdef TORQUE_DEBUG_GUARD + retHeader->line = line; + retHeader->fileName = fileName; + retHeader->allocNum = gCurrAlloc; + retHeader->realSize = size; +#ifdef TORQUE_ENABLE_PROFILE_PATH + retHeader->profilePath = gProfiler ? gProfiler->getProfilePath() : "pre"; +#endif + gBytesAllocated += size; + //static U32 skip = 0; + //if ((++skip % 1000) == 0) + // Con::printf("new=%i, newnew=%i, imagenew=%i",gBytesAllocated,gNewNewTotal,gImageAlloc); + if (gEnableLogging) + logAlloc(retHeader, size); +#endif + if(gCurrAlloc == gBreakAlloc && gBreakAlloc != 0xFFFFFFFF) + Platform::debugBreak(); + gCurrAlloc++; +#ifndef TORQUE_ENABLE_PROFILE_PATH + PROFILE_END(); +#endif + //validate(); + +#ifdef TORQUE_DEBUG + // fill the block with the fill value. although this is done in free(), that won't fill + // newly allocated MM memory (which hasn't been freed yet). We use a different fill value + // to diffentiate filled freed memory from filled new memory; this may aid debugging. + #ifndef TORQUE_ENABLE_PROFILE_PATH + PROFILE_START(stompMem1); + #endif + dMemset(basePtr, 0xCF, size); + #ifndef TORQUE_ENABLE_PROFILE_PATH + PROFILE_END(); + #endif +#endif + + if(gCurrAlloc == gBreakAlloc && gBreakAlloc != 0xFFFFFFFF) + Platform::debugBreak(); + + gCurrAlloc++; + +#ifdef TORQUE_MULTITHREAD + if(!gReentrantGuard) + Mutex::unlockMutex(gMemMutex); +#endif + + return basePtr; +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void free(void* mem, bool array) +{ + // validate(); + + if (!mem) + return; + +#ifdef TORQUE_MULTITHREAD + if(!gMemMutex) + gMemMutex = Mutex::createMutex(); + + if( mem != gMemMutex ) + Mutex::lockMutex(gMemMutex); + else + gMemMutex = NULL; +#endif + + PROFILE_START(MemoryFree); + AllocatedHeader *hdr = ((AllocatedHeader *)mem) - 1; + + AssertFatal(hdr->flags & Allocated, avar("Not an allocated block!")); + AssertFatal(((bool)((hdr->flags & Array)==Array))==array, avar("Array alloc mismatch. ")); + +#ifdef TORQUE_DEBUG_GUARD + gBytesAllocated -= hdr->realSize; + if (gEnableLogging) + logFree(hdr); +#endif + + hdr->flags = 0; + + // fill the block with the fill value + +#ifdef TORQUE_DEBUG + #ifndef TORQUE_ENABLE_PROFILE_PATH + PROFILE_START(stompMem2); + #endif + dMemset(mem, 0xCE, hdr->size); + #ifndef TORQUE_ENABLE_PROFILE_PATH + PROFILE_END(); + #endif +#endif + + // see if we can merge hdr with the block after it. + + Header* next = hdr->next; + if (next && next->flags == 0) + { + treeRemove((FreeHeader *) next); + hdr->size += next->size + sizeof(Header); + hdr->next = next->next; + if(next->next) + next->next->prev = (Header *) hdr; + } + + // see if we can merge hdr with the block before it. + Header* prev = hdr->prev; + + if (prev && prev->flags == 0) + { + treeRemove((FreeHeader *) prev); + prev->size += hdr->size + sizeof(Header); + prev->next = hdr->next; + if (hdr->next) + hdr->next->prev = prev; + + hdr = (AllocatedHeader *) prev; + } + + // throw this puppy into the tree! + treeInsert((FreeHeader *) hdr); + PROFILE_END(); + +// validate(); + +#ifdef TORQUE_MULTITHREAD + Mutex::unlockMutex(gMemMutex); +#endif +} +#endif + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) +static void* realloc(void* mem, dsize_t size, const char* fileName, const U32 line) +{ + //validate(); + if (!size) { + free(mem, false); + return NULL; + } + if(!mem) + return alloc(size, false, fileName, line); + +#ifdef TORQUE_MULTITHREAD + if(!gMemMutex) + gMemMutex = Mutex::createMutex(); + + Mutex::lockMutex(gMemMutex); +#endif + + AllocatedHeader* hdr = ((AllocatedHeader *)mem) - 1; +#ifdef TORQUE_DEBUG_GUARD + checkGuard( ( Header* ) hdr, true ); +#endif + + AssertFatal((hdr->flags & Allocated) == Allocated, "Bad block flags."); + + size = (size + 0xF) & ~0xF; + + U32 oldSize = hdr->size; + if(oldSize == size) + { +#ifdef TORQUE_MULTITHREAD + Mutex::unlockMutex(gMemMutex); +#endif + return mem; + } + PROFILE_START(MemoryRealloc); + + FreeHeader *next = (FreeHeader *) hdr->next; + +#ifdef TORQUE_DEBUG_GUARD + // adjust header size and allocated bytes size + hdr->realSize += size - oldSize; + gBytesAllocated += size - oldSize; + if (gEnableLogging) + logRealloc(hdr, size); + + // Add reallocated flag, note header changes will not persist if the realloc + // decides tofree, and then perform a fresh allocation for the memory. The flag will + // be manually set again after this takes place, down at the bottom of this fxn. + hdr->flags |= Reallocated; + + // Note on Above ^ + // A more useful/robust implementation can be accomplished by storing an additional + // AllocatedHeader* in DEBUG_GUARD builds inside the AllocatedHeader structure + // itself to create a sort of reallocation history. This will be, essentially, + // a allocation header stack for each allocation. Each time the memory is reallocated + // it should use dRealMalloc (IMPORTANT!!) to allocate a AllocatedHeader* and chain + // it to the reallocation history chain, and the dump output changed to display + // reallocation history. It is also important to clean up this chain during 'free' + // using dRealFree (Since memory for the chain was allocated via dRealMalloc). + // + // See patw for details. +#endif + if (next && !(next->flags & Allocated) && next->size + hdr->size + sizeof(Header) >= size) + { + // we can merge with the next dude. + treeRemove(next); + hdr->size += sizeof(Header) + next->size; + hdr->next = next->next; + if(next->next) + next->next->prev = (Header *) hdr; + + checkUnusedAlloc((FreeHeader *) hdr, size); + //validate(); + PROFILE_END(); +#ifdef TORQUE_MULTITHREAD + Mutex::unlockMutex(gMemMutex); +#endif + return mem; + } + else if(size < oldSize) + { + checkUnusedAlloc((FreeHeader *) hdr, size); + PROFILE_END(); +#ifdef TORQUE_MULTITHREAD + Mutex::unlockMutex(gMemMutex); +#endif + return mem; + } +#ifdef TORQUE_DEBUG_GUARD + // undo above adjustment because we're going though alloc instead + hdr->realSize -= size - oldSize; + gBytesAllocated -= size - oldSize; +#endif + void* ret = alloc(size, false, fileName, line); + dMemcpy(ret, mem, oldSize); + free(mem, false); + PROFILE_END(); + + // Re-enable the 'Reallocated' flag so that this allocation can be ignored by + // a non-strict run of the flag/dumpunflagged. + hdr = ((AllocatedHeader *)ret) - 1; + hdr->flags |= Reallocated; + +#ifdef TORQUE_MULTITHREAD + Mutex::unlockMutex(gMemMutex); +#endif + return ret; +} +#endif + +dsize_t getMemoryUsed() +{ + U32 size = 0; + + PageRecord* walk; + for (walk = gPageList; walk; walk = walk->prevPage) { + for(Header *probe = walk->headerList; probe; probe = probe->next) + if (probe->flags & Allocated) { + size += probe->size; + } + } + + return size; +} + +#ifdef TORQUE_DEBUG_GUARD +ConsoleFunction(dumpMemSnapshot,void,2,2,"(string fileName) Dump a snapshot of current memory to a file.") +{ + TORQUE_UNUSED(argc); + FileStream fws; + fws.open(argv[1], Torque::FS::File::Write); + char buffer[1024]; + + PageRecord* walk; + for (walk = gPageList; walk; walk = walk->prevPage) { + for(Header *probe = walk->headerList; probe; probe = probe->next) + if (probe->flags & Allocated) { + AllocatedHeader* pah = (AllocatedHeader*)probe; + dSprintf(buffer, 1023, "%s%s\t%d\t%d\t%d\r\n", + pah->flags & Reallocated ? "[R] " : "", + pah->fileName != NULL ? pah->fileName : "Undetermined", + pah->line, pah->realSize, pah->allocNum); + fws.write(dStrlen(buffer), buffer); + } + } + + Con::errorf("total memory used: %d",getMemoryUsed()); + fws.close(); +} +#endif + +dsize_t getMemoryAllocated() +{ + return 0; +} + +void setBreakAlloc(U32 breakAlloc) +{ + gBreakAlloc = breakAlloc; +} + +ConsoleFunctionGroupEnd( Memory ); + +} // namespace Memory + +void setMinimumAllocUnit(U32 allocUnit) +{ + AssertFatal(isPow2(allocUnit) && allocUnit > (2 << 20), + "Error, allocunit must be a power of two, and greater than 2 megs"); + + MinPageSize = allocUnit; +} + +//--------------------------------------------------------------------------- + +//--------------------------------------------------------------------------- + +#if !defined(TORQUE_DISABLE_MEMORY_MANAGER) + +// Manage our own memory, add overloaded memory operators and functions + +void* FN_CDECL operator new(dsize_t size, const char* fileName, const U32 line) +{ + return Memory::alloc(size, false, fileName, line); +} + +void* FN_CDECL operator new[](dsize_t size, const char* fileName, const U32 line) +{ + return Memory::alloc(size, true, fileName, line); +} + +void* FN_CDECL operator new(dsize_t size) +{ + return Memory::alloc(size, false, NULL, 0); +} + +void* FN_CDECL operator new[](dsize_t size) +{ + return Memory::alloc(size, true, NULL, 0); +} + +void FN_CDECL operator delete(void* mem) +{ + Memory::free(mem, false); +} + +void FN_CDECL operator delete[](void* mem) +{ + Memory::free(mem, true); +} + +void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line) +{ + return Memory::alloc(in_size, false, fileName, line); +} + +void dFree(void* in_pFree) +{ + Memory::free(in_pFree, false); +} + +void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line) +{ + return Memory::realloc(in_pResize, in_size, fileName, line); +} + +#else + +// Don't manage our own memory +void* dMalloc_r(dsize_t in_size, const char* fileName, const dsize_t line) +{ + return malloc(in_size); +} + +void dFree(void* in_pFree) +{ + free(in_pFree); +} + +void* dRealloc_r(void* in_pResize, dsize_t in_size, const char* fileName, const dsize_t line) +{ + return realloc(in_pResize,in_size); +} + +#endif diff --git a/platform/platformMemory.h b/platform/platformMemory.h new file mode 100644 index 0000000..d42ff4b --- /dev/null +++ b/platform/platformMemory.h @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_PLATFORM_PLATFORMMEMORY_H_ +#define _TORQUE_PLATFORM_PLATFORMMEMORY_H_ + +#include "platform/platform.h" + +namespace Memory +{ + enum EFlag + { + FLAG_Debug, + FLAG_Global, + FLAG_Static + }; + + void checkPtr( void* ptr ); + void flagCurrentAllocs( EFlag flag = FLAG_Debug ); + void ensureAllFreed(); + void dumpUnflaggedAllocs(const char *file, EFlag flag = FLAG_Debug ); + S32 countUnflaggedAllocs(const char *file, S32 *outUnflaggedRealloc = NULL, EFlag flag = FLAG_Debug ); + dsize_t getMemoryUsed(); + dsize_t getMemoryAllocated(); + void validate(); +} + +#endif // _TORQUE_PLATFORM_PLATFORMMEMORY_H_ diff --git a/platform/platformNet.cpp b/platform/platformNet.cpp new file mode 100644 index 0000000..b04bd0e --- /dev/null +++ b/platform/platformNet.cpp @@ -0,0 +1,1018 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformNet.h" +#include "platform/event.h" +#include "core/strings/stringFunctions.h" + +#if defined (TORQUE_OS_WIN32) +#define TORQUE_USE_WINSOCK +#include +#include +#define EINPROGRESS WSAEINPROGRESS +#define ioctl ioctlsocket + +typedef int socklen_t; + +#elif defined ( TORQUE_OS_MAC ) + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef sockaddr_in SOCKADDR_IN; +typedef sockaddr * PSOCKADDR; +typedef sockaddr SOCKADDR; +typedef in_addr IN_ADDR; + +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 + +#define closesocket close + +#elif defined TORQUE_OS_LINUX + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef sockaddr_in SOCKADDR_IN; +typedef sockaddr * PSOCKADDR; +typedef sockaddr SOCKADDR; +typedef in_addr IN_ADDR; + +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 + +#define closesocket close + +#elif defined( TORQUE_OS_XENON ) + +#include +#include + +#define TORQUE_USE_WINSOCK +#define EINPROGRESS WSAEINPROGRESS +#define ioctl ioctlsocket +typedef int socklen_t; + +DWORD _getLastErrorAndClear() +{ + DWORD err = WSAGetLastError(); + WSASetLastError( 0 ); + + return err; +} + +#else + +#endif + +#if defined(TORQUE_USE_WINSOCK) +static const char* strerror_wsa( int code ) +{ + switch( code ) + { +#define E( name ) case name: return #name; + E( WSANOTINITIALISED ); + E( WSAENETDOWN ); + E( WSAEADDRINUSE ); + E( WSAEINPROGRESS ); + E( WSAEALREADY ); + E( WSAEADDRNOTAVAIL ); + E( WSAEAFNOSUPPORT ); + E( WSAEFAULT ); + E( WSAEINVAL ); + E( WSAEISCONN ); + E( WSAENETUNREACH ); + E( WSAEHOSTUNREACH ); + E( WSAENOBUFS ); + E( WSAENOTSOCK ); + E( WSAETIMEDOUT ); + E( WSAEWOULDBLOCK ); + E( WSAEACCES ); +#undef E + default: + return "Unknown"; + } +} +#endif + +#include "core/util/tVector.h" +#include "platform/platformNetAsync.h" +#include "console/console.h" +#include "core/util/journal/process.h" +#include "core/util/journal/journal.h" + +#include "../add/global/GlobalStatic.h" +#include "../add/wllib/wlmgr.h" + +static Net::Error getLastError(); +static S32 defaultPort = 28000; +static S32 netPort = 0; +int udpSocket = InvalidSocket; + +ConnectionNotifyEvent Net::smConnectionNotify; +ConnectionAcceptedEvent Net::smConnectionAccept; +ConnectionReceiveEvent Net::smConnectionReceive; +PacketReceiveEvent Net::smPacketReceive; + +// local enum for socket states for polled sockets +enum SocketState +{ + InvalidState, + Connected, + ConnectionPending, + Listening, + NameLookupRequired +}; + +// the Socket structure helps us keep track of the +// above states +struct Socket +{ + Socket() + { + fd = InvalidSocket; + state = InvalidState; + remoteAddr[0] = 0; + remotePort = -1; + } + + NetSocket fd; + S32 state; + char remoteAddr[256]; + S32 remotePort; +}; + +// list of polled sockets +static Vector gPolledSockets( __FILE__, __LINE__ ); + +static Socket* addPolledSocket(NetSocket& fd, S32 state, + char* remoteAddr = NULL, S32 port = -1) +{ + Socket* sock = new Socket(); + sock->fd = fd; + sock->state = state; + if (remoteAddr) + dStrcpy(sock->remoteAddr, remoteAddr); + if (port != -1) + sock->remotePort = port; + gPolledSockets.push_back(sock); + return sock; +} + +enum { + MaxConnections = 1024, +}; + + +bool netSocketWaitForWritable(NetSocket fd, S32 timeoutMs) +{ + fd_set writefds; + timeval timeout; + + FD_ZERO(&writefds); + FD_SET( fd, &writefds ); + + timeout.tv_sec = timeoutMs / 1000; + timeout.tv_usec = ( timeoutMs % 1000 ) * 1000; + + if( select(fd + 1, NULL, &writefds, NULL, &timeout) > 0 ) + return true; + + return false; +} + +static S32 initCount = 0; + +bool Net::init() +{ +#if defined(TORQUE_USE_WINSOCK) + if(!initCount) + { +#ifdef TORQUE_OS_XENON + // Configure startup parameters + XNetStartupParams xnsp; + memset( &xnsp, 0, sizeof( xnsp ) ); + xnsp.cfgSizeOfStruct = sizeof( XNetStartupParams ); + +#ifdef TORQUE_DEBUG + xnsp.cfgFlags = XNET_STARTUP_BYPASS_SECURITY; +#endif + AssertISV( !XNetStartup( &xnsp ), "Net::init - failed to init XNet" ); +#endif + + WSADATA stWSAData; + AssertISV( !WSAStartup( 0x0101, &stWSAData ), "Net::init - failed to init WinSock!" ); + + //logprintf("Winsock initialization %s", success ? "succeeded." : "failed!"); + } +#endif + initCount++; + + Process::notify(&Net::process, PROCESS_NET_ORDER); + + return(true); +} + +void Net::shutdown() +{ + Process::remove(&Net::process); + + while (gPolledSockets.size() > 0) + closeConnectTo(gPolledSockets[0]->fd); + + closePort(); + initCount--; + +#if defined(TORQUE_USE_WINSOCK) + if(!initCount) + { + WSACleanup(); + +#ifdef TORQUE_OS_XENON + XNetCleanup(); +#endif + } +#endif +} + +Net::Error getLastError() +{ +#if defined(TORQUE_USE_WINSOCK) + S32 err = WSAGetLastError(); + switch(err) + { + case 0: + return Net::NoError; + case WSAEWOULDBLOCK: + return Net::WouldBlock; + default: + return Net::UnknownError; + } +#else + if (errno == EAGAIN) + return Net::WouldBlock; + if (errno == 0) + return Net::NoError; + return Net::UnknownError; +#endif +} + +void netToIPSocketAddress(const NetAddress *address, struct sockaddr_in *sockAddr) +{ + dMemset(sockAddr, 0, sizeof(struct sockaddr_in)); + sockAddr->sin_family = AF_INET; + sockAddr->sin_port = htons(address->port); + char tAddr[20]; + dSprintf(tAddr, 20, "%d.%d.%d.%d\n", address->netNum[0], address->netNum[1], address->netNum[2], address->netNum[3]); + //fprintf(stdout,"netToIPSocketAddress(): %s\n",tAddr);fflush(NULL); + sockAddr->sin_addr.s_addr = inet_addr(tAddr); +} + +void IPSocketToNetAddress(const struct sockaddr_in *sockAddr, NetAddress *address) +{ + address->type = NetAddress::IPAddress; + address->port = htons(sockAddr->sin_port); +#ifndef TORQUE_OS_XENON + char *tAddr; + tAddr = inet_ntoa(sockAddr->sin_addr); + //fprintf(stdout,"IPSocketToNetAddress(): %s\n",tAddr);fflush(NULL); + U8 nets[4]; + nets[0] = atoi(strtok(tAddr, ".")); + nets[1] = atoi(strtok(NULL, ".")); + nets[2] = atoi(strtok(NULL, ".")); + nets[3] = atoi(strtok(NULL, ".")); + //fprintf(stdout,"0 = %d, 1 = %d, 2 = %d, 3 = %d\n", nets[0], nets[1], nets[2], nets[3]); + address->netNum[0] = nets[0]; + address->netNum[1] = nets[1]; + address->netNum[2] = nets[2]; + address->netNum[3] = nets[3]; +#else + address->netNum[0] = sockAddr->sin_addr.s_net; + address->netNum[1] = sockAddr->sin_addr.s_host; + address->netNum[2] = sockAddr->sin_addr.s_lh; + address->netNum[3] = sockAddr->sin_addr.s_impno; +#endif +} + +NetSocket Net::openListenPort(U16 port) +{ + if(Journal::IsPlaying()) + { + U32 ret; + Journal::Read(&ret); + return NetSocket(ret); + } + + NetSocket sock = openSocket(); + if (sock == InvalidSocket) + { + Con::errorf("Unable to open listen socket: %s", strerror(errno)); + return InvalidSocket; + } + + if (bind(sock, port) != NoError) + { + Con::errorf("Unable to bind port %d: %s", port, strerror(errno)); + ::closesocket(sock); + return InvalidSocket; + } + if (listen(sock, 4) != NoError) + { + Con::errorf("Unable to listen on port %d: %s", port, strerror(errno)); + ::closesocket(sock); + return InvalidSocket; + } + + setBlocking(sock, false); + addPolledSocket(sock, Listening); + + if(Journal::IsRecording()) + Journal::Write(U32(sock)); + + return sock; +} + +NetSocket Net::openConnectTo(const char *addressString) +{ + if(!dStrnicmp(addressString, "ipx:", 4)) + // ipx support deprecated + return InvalidSocket; + if(!dStrnicmp(addressString, "ip:", 3)) + addressString += 3; // eat off the ip: + char remoteAddr[256]; + dStrcpy(remoteAddr, addressString); + + char *portString = dStrchr(remoteAddr, ':'); + + U16 port; + if(portString) + { + *portString++ = 0; + port = htons(dAtoi(portString)); + } + else + port = htons(defaultPort); + + if(!dStricmp(remoteAddr, "broadcast")) + return InvalidSocket; + + if(Journal::IsPlaying()) + { + U32 ret; + Journal::Read(&ret); + return NetSocket(ret); + } + NetSocket sock = openSocket(); + setBlocking(sock, false); + + sockaddr_in ipAddr; + dMemset(&ipAddr, 0, sizeof(ipAddr)); + ipAddr.sin_addr.s_addr = inet_addr(remoteAddr); + + if(ipAddr.sin_addr.s_addr != INADDR_NONE) + { + ipAddr.sin_port = port; + ipAddr.sin_family = AF_INET; + if(::connect(sock, (struct sockaddr *)&ipAddr, sizeof(ipAddr)) == -1) + { + S32 err = getLastError(); + if(err != Net::WouldBlock) + { + Con::errorf("Error connecting %s: %s", + addressString, strerror(err)); + ::closesocket(sock); + sock = InvalidSocket; + } + } + if(sock != InvalidSocket) + { + // add this socket to our list of polled sockets + addPolledSocket(sock, ConnectionPending); + } + } + else + { + // need to do an asynchronous name lookup. first, add the socket + // to the polled list + addPolledSocket(sock, NameLookupRequired, remoteAddr, port); + // queue the lookup + gNetAsync.queueLookup(remoteAddr, sock); + } + if(Journal::IsRecording()) + Journal::Write(U32(sock)); + return sock; +} + +void Net::closeConnectTo(NetSocket sock) +{ + if(Journal::IsPlaying()) + return; + + // if this socket is in the list of polled sockets, remove it + for (int i = 0; i < gPolledSockets.size(); ++i) + { + if (gPolledSockets[i]->fd == sock) + { + delete gPolledSockets[i]; + gPolledSockets.erase(i); + break; + } + } + + closeSocket(sock); +} + +Net::Error Net::sendtoSocket(NetSocket socket, const U8 *buffer, int bufferSize) +{ + if(Journal::IsPlaying()) + { + U32 e; + Journal::Read(&e); + + return (Net::Error) e; + } + + Net::Error e = send(socket, buffer, bufferSize); + + if(Journal::IsRecording()) + Journal::Write(U32(e)); + + return e; +} + +bool Net::openPort(S32 port, bool doBind) +{ + if(udpSocket != InvalidSocket) + ::closesocket(udpSocket); + + udpSocket = socket(AF_INET, SOCK_DGRAM, 0); + + if(udpSocket != InvalidSocket) + { + Net::Error error = NoError; + if (doBind) + { + error = bind(udpSocket, port); + } + + if(error == NoError) + error = setBufferSize(udpSocket, 32768); + + if(error == NoError) + error = setBroadcast(udpSocket, true); + + if(error == NoError) + error = setBlocking(udpSocket, true); + + if(error == NoError) + Con::printf("UDP initialized on port %d", port); + else + { + ::closesocket(udpSocket); + udpSocket = InvalidSocket; + Con::printf("Unable to initialize UDP - error %d", error); + } + } + netPort = port; + return udpSocket != InvalidSocket; +} + +NetSocket Net::getPort() + +{ + + return udpSocket; + +} + + +void Net::closePort() +{ + if(udpSocket != InvalidSocket) + ::closesocket(udpSocket); +} + +Net::Error Net::sendto(const NetAddress *address, const U8 *buffer, S32 bufferSize) +{ + if(Journal::IsPlaying()) + return NoError; + + PKTSEND * pkt = CGlobalStatic::allocPktSend((const char *)buffer,bufferSize); + dMemcpy(&(pkt->address),address,sizeof(NetAddress)); + CWLMgr::getInstance()->getStack_SendPkt()->push(pkt); + SetEvent(CGlobalStatic::g_hEventSend); + /* + if(address->type == NetAddress::IPAddress) + { + sockaddr_in ipAddr; + netToIPSocketAddress(address, &ipAddr); + if(::sendto(udpSocket, (const char*)buffer, bufferSize, 0, + (sockaddr *) &ipAddr, sizeof(sockaddr_in)) == SOCKET_ERROR) + return getLastError(); + else + return NoError; + } + else + { + SOCKADDR_IN ipAddr; + netToIPSocketAddress(address, &ipAddr); + if(::sendto(udpSocket, (const char*)buffer, bufferSize, 0, + (PSOCKADDR) &ipAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) + return getLastError(); + else + return NoError; + } + */ +} + +void Net::process() +{ + WL::CLockFreeQueue * pktList = CWLMgr::getInstance()->getStack_RecvPkt(); + PKT * pkt = NULL; + while (pktList->size()) + { + pkt = (PKT*)(pktList->pop()); + if (pkt && pkt->bytesRead > 0) + { + Net::smPacketReceive.trigger(pkt->address, (U32)&(pkt->data[0]),pkt->bytesRead); + CGlobalStatic::freePkt(pkt); + } + } + /* + sockaddr sa; + sa.sa_family = AF_UNSPEC; + NetAddress srcAddress; + RawData tmpBuffer; + tmpBuffer.alloc(MaxPacketDataSize); + + for(;;) + { + socklen_t addrLen = sizeof(sa); + S32 bytesRead = -1; + + if(udpSocket != InvalidSocket) + bytesRead = recvfrom(udpSocket, (char *) tmpBuffer.data, MaxPacketDataSize, 0, &sa, &addrLen); + + if(bytesRead == -1) + break; + + if(sa.sa_family == AF_INET) + IPSocketToNetAddress((sockaddr_in *) &sa, &srcAddress); + else + continue; + + if(bytesRead <= 0) + continue; + + if(srcAddress.type == NetAddress::IPAddress && + srcAddress.netNum[0] == 127 && + srcAddress.netNum[1] == 0 && + srcAddress.netNum[2] == 0 && + srcAddress.netNum[3] == 1 && + srcAddress.port == netPort) + continue; + + tmpBuffer.size = bytesRead; + + Net::smPacketReceive.trigger(srcAddress, tmpBuffer); + } + */ + // process the polled sockets. This blob of code performs functions + // similar to WinsockProc in winNet.cc + + if (gPolledSockets.size() == 0) + return; + + S32 optval; + socklen_t optlen = sizeof(S32); + S32 bytesRead; + Net::Error err; + bool removeSock = false; + Socket *currentSock = NULL; + sockaddr_in ipAddr; + NetSocket incoming = InvalidSocket; + char out_h_addr[1024]; + int out_h_length = 0; + RawData readBuff; + + for (S32 i = 0; i < gPolledSockets.size(); + /* no increment, this is done at end of loop body */) + { + removeSock = false; + currentSock = gPolledSockets[i]; + switch (currentSock->state) + { + case ::InvalidState: + Con::errorf("Error, InvalidState socket in polled sockets list"); + break; + case ::ConnectionPending: + // see if it is now connected +#ifdef TORQUE_OS_XENON + // WSASetLastError has no return value, however part of the SO_ERROR behavior + // is to clear the last error, so this needs to be done here. + if( ( optval = _getLastErrorAndClear() ) == -1 ) +#else + if (getsockopt(currentSock->fd, SOL_SOCKET, SO_ERROR, + (char*)&optval, &optlen) == -1) +#endif + { + Con::errorf("Error getting socket options: %s", strerror(errno)); + + Net::smConnectionNotify.trigger(currentSock->fd, Net::ConnectFailed); + removeSock = true; + } + else + { + if (optval == EINPROGRESS) + // still connecting... + break; + + if (optval == 0) + { + // poll for writable status to be sure we're connected. + bool ready = netSocketWaitForWritable(currentSock->fd,0); + if(!ready) + break; + + currentSock->state = ::Connected; + Net::smConnectionNotify.trigger(currentSock->fd, Net::Connected); + } + else + { + // some kind of error + Con::errorf("Error connecting: %s", strerror(errno)); + Net::smConnectionNotify.trigger(currentSock->fd, Net::ConnectFailed); + removeSock = true; + } + } + break; + case ::Connected: + + // try to get some data + bytesRead = 0; + readBuff.alloc(MaxPacketDataSize); + err = Net::recv(currentSock->fd, (U8*)readBuff.data, MaxPacketDataSize, &bytesRead); + if(err == Net::NoError) + { + if (bytesRead > 0) + { + // got some data, post it + readBuff.size = bytesRead; + Net::smConnectionReceive.trigger(currentSock->fd, readBuff); + } + else + { + // ack! this shouldn't happen + if (bytesRead < 0) + Con::errorf("Unexpected error on socket: %s", strerror(errno)); + + // zero bytes read means EOF + Net::smConnectionNotify.trigger(currentSock->fd, Net::Disconnected); + + removeSock = true; + } + } + else if (err != Net::NoError && err != Net::WouldBlock) + { + Con::errorf("Error reading from socket: %s", strerror(errno)); + Net::smConnectionNotify.trigger(currentSock->fd, Net::Disconnected); + removeSock = true; + } + break; + case ::NameLookupRequired: + // is the lookup complete? + if (!gNetAsync.checkLookup( + currentSock->fd, out_h_addr, &out_h_length, + sizeof(out_h_addr))) + break; + + U32 newState; + if (out_h_length == -1) + { + Con::errorf("DNS lookup failed: %s", currentSock->remoteAddr); + newState = Net::DNSFailed; + removeSock = true; + } + else + { + // try to connect + dMemcpy(&(ipAddr.sin_addr.s_addr), out_h_addr, out_h_length); + ipAddr.sin_port = currentSock->remotePort; + ipAddr.sin_family = AF_INET; + if(::connect(currentSock->fd, (struct sockaddr *)&ipAddr, + sizeof(ipAddr)) == -1) + { + int errorCode; +#if defined(TORQUE_USE_WINSOCK) + errorCode = WSAGetLastError(); + if( errorCode == WSAEINPROGRESS || errorCode == WSAEWOULDBLOCK ) +#else + errorCode = errno; + if (errno == EINPROGRESS) +#endif + { + newState = Net::DNSResolved; + currentSock->state = ::ConnectionPending; + } + else + { + const char* errorString; +#if defined(TORQUE_USE_WINSOCK) + errorString = strerror_wsa( errorCode ); +#else + errorString = strerror( errorCode ); +#endif + Con::errorf("Error connecting to %s: %s (%i)", + currentSock->remoteAddr, errorString, errorCode); + newState = Net::ConnectFailed; + removeSock = true; + } + } + else + { + newState = Net::Connected; + currentSock->state = Net::Connected; + } + } + + Net::smConnectionNotify.trigger(currentSock->fd, newState); + break; + case ::Listening: + NetAddress incomingAddy; + + incoming = Net::accept(currentSock->fd, &incomingAddy); + if(incoming != InvalidSocket) + { + setBlocking(incoming, false); + addPolledSocket(incoming, Connected); + Net::smConnectionAccept.trigger(currentSock->fd, incoming, incomingAddy); + } + break; + } + + // only increment index if we're not removing the connection, since + // the removal will shift the indices down by one + if (removeSock) + closeConnectTo(currentSock->fd); + else + i++; + } +} + +NetSocket Net::openSocket() +{ + int retSocket; + retSocket = socket(AF_INET, SOCK_STREAM, 0); + + if(retSocket == InvalidSocket) + return InvalidSocket; + else + return retSocket; +} + +Net::Error Net::closeSocket(NetSocket socket) +{ + if(socket != InvalidSocket) + { + if(!closesocket(socket)) + return NoError; + else + return getLastError(); + } + else + return NotASocket; +} + +Net::Error Net::connect(NetSocket socket, const NetAddress *address) +{ + if(address->type != NetAddress::IPAddress) + return WrongProtocolType; + sockaddr_in socketAddress; + netToIPSocketAddress(address, &socketAddress); + if(!::connect(socket, (sockaddr *) &socketAddress, sizeof(socketAddress))) + return NoError; + return getLastError(); +} + +Net::Error Net::listen(NetSocket socket, S32 backlog) +{ + if(!::listen(socket, backlog)) + return NoError; + return getLastError(); +} + +NetSocket Net::accept(NetSocket acceptSocket, NetAddress *remoteAddress) +{ + sockaddr_in socketAddress; + socklen_t addrLen = sizeof(socketAddress); + + int retVal = ::accept(acceptSocket, (sockaddr *) &socketAddress, &addrLen); + if(retVal != InvalidSocket) + { + IPSocketToNetAddress(&socketAddress, remoteAddress); + return retVal; + } + return InvalidSocket; +} + +Net::Error Net::bind(NetSocket socket, U16 port) +{ + S32 error; + + sockaddr_in socketAddress; + dMemset((char *)&socketAddress, 0, sizeof(socketAddress)); + socketAddress.sin_family = AF_INET; + // It's entirely possible that there are two NIC cards. + // We let the user specify which one the server runs on. + + // thanks to [TPG]P1aGu3 for the name + const char* serverIP = Con::getVariable( "Pref::Net::BindAddress" ); + // serverIP is guaranteed to be non-0. + AssertFatal( serverIP, "serverIP is NULL!" ); + + if( serverIP[0] != '\0' ) { + // we're not empty + socketAddress.sin_addr.s_addr = inet_addr( serverIP ); + + if( socketAddress.sin_addr.s_addr != INADDR_NONE ) { + Con::printf( "Binding server port to %s", serverIP ); + } else { + Con::warnf( ConsoleLogEntry::General, + "inet_addr() failed for %s while binding!", + serverIP ); + socketAddress.sin_addr.s_addr = INADDR_ANY; + } + + } else { + Con::printf( "Binding server port to default IP" ); + socketAddress.sin_addr.s_addr = INADDR_ANY; + } + + socketAddress.sin_port = htons(port); + error = ::bind(socket, (sockaddr *) &socketAddress, sizeof(socketAddress)); + + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::setBufferSize(NetSocket socket, S32 bufferSize) +{ + S32 error; + error = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *) &bufferSize, sizeof(bufferSize)); + if(!error) + error = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *) &bufferSize, sizeof(bufferSize)); + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::setBroadcast(NetSocket socket, bool broadcast) +{ + S32 bc = broadcast; + S32 error = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&bc, sizeof(bc)); + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::setBlocking(NetSocket socket, bool blockingIO) +{ + unsigned long notblock = !blockingIO; + S32 error = ioctl(socket, FIONBIO, ¬block); + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::send(NetSocket socket, const U8 *buffer, S32 bufferSize) +{ + errno = 0; + S32 bytesWritten = ::send(socket, (const char*)buffer, bufferSize, 0); + if(bytesWritten == -1) +#if defined(TORQUE_USE_WINSOCK) + Con::errorf("Could not write to socket. Error: %s",strerror_wsa( WSAGetLastError() )); +#else + Con::errorf("Could not write to socket. Error: %s",strerror(errno)); +#endif + + return getLastError(); +} + +Net::Error Net::recv(NetSocket socket, U8 *buffer, S32 bufferSize, S32 *bytesRead) +{ + *bytesRead = ::recv(socket, (char*)buffer, bufferSize, 0); + if(*bytesRead == -1) + return getLastError(); + return NoError; +} + +bool Net::compareAddresses(const NetAddress *a1, const NetAddress *a2) +{ + if((a1->type != a2->type) || + (*((U32 *)a1->netNum) != *((U32 *)a2->netNum)) || + (a1->port != a2->port)) + return false; + + if(a1->type == NetAddress::IPAddress) + return true; + for(S32 i = 0; i < 6; i++) + if(a1->nodeNum[i] != a2->nodeNum[i]) + return false; + return true; +} + +bool Net::stringToAddress(const char *addressString, NetAddress *address) +{ + if(!dStrnicmp(addressString, "ipx:", 4)) + // ipx support deprecated + return false; + + if(!dStrnicmp(addressString, "ip:", 3)) + addressString += 3; // eat off the ip: + + sockaddr_in ipAddr; + char remoteAddr[256]; + if(strlen(addressString) > 255) + return false; + + dStrcpy(remoteAddr, addressString); + + char *portString = dStrchr(remoteAddr, ':'); + if(portString) + *portString++ = '\0'; + + struct hostent *hp; + if(!dStricmp(remoteAddr, "broadcast")) + ipAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + else + { + ipAddr.sin_addr.s_addr = inet_addr(remoteAddr); + + if (ipAddr.sin_addr.s_addr == INADDR_NONE) // error + { +#ifndef TORQUE_OS_XENON + if((hp = gethostbyname(remoteAddr)) == 0) + return false; + else + memcpy(&ipAddr.sin_addr.s_addr, hp->h_addr, sizeof(in_addr)); +#else + return false; +#endif + } + } + if(portString) + ipAddr.sin_port = htons(dAtoi(portString)); + else + ipAddr.sin_port = htons(defaultPort); + ipAddr.sin_family = AF_INET; + IPSocketToNetAddress(&ipAddr, address); + return true; +} + +void Net::addressToString(const NetAddress *address, char addressString[256]) +{ + if(address->type == NetAddress::IPAddress) + { + sockaddr_in ipAddr; + netToIPSocketAddress(address, &ipAddr); + + if(ipAddr.sin_addr.s_addr == htonl(INADDR_BROADCAST)) + dSprintf(addressString, 256, "IP:Broadcast:%d", ntohs(ipAddr.sin_port)); + else + { +#ifndef TORQUE_OS_XENON + dSprintf(addressString, 256, "IP:%s:%d", inet_ntoa(ipAddr.sin_addr), + ntohs(ipAddr.sin_port)); +#else + dSprintf(addressString, 256, "IP:%d.%d.%d.%d:%d", ipAddr.sin_addr.s_net, + ipAddr.sin_addr.s_host, ipAddr.sin_addr.s_lh, + ipAddr.sin_addr.s_impno, ntohs( ipAddr.sin_port ) ); + +#endif + } + } + else + { + *addressString = 0; + return; + } +} + diff --git a/platform/platformNet.h b/platform/platformNet.h new file mode 100644 index 0000000..bdd8627 --- /dev/null +++ b/platform/platformNet.h @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORM_PLATFORMNET_H_ +#define _PLATFORM_PLATFORMNET_H_ + +#include "platform/platform.h" +#include "core/util/rawData.h" +#include "core/util/journal/journaledSignal.h" + +#ifndef MAXPACKETSIZE +#define MAXPACKETSIZE 1500 +#endif + +typedef int NetConnectionId; + +/// Generic network address +/// +/// This is used to represent IP addresses. +struct NetAddress +{ + int type; ///< Type of address (IPAddress currently) + + /// Acceptable NetAddress types. + enum + { + IPAddress, + }; + + U8 netNum[4]; ///< For IP: sin_addr
+ U8 nodeNum[6]; ///< For IP: Not used.
+ U16 port; ///< For IP: sin_port
+}; + +typedef S32 NetSocket; +const NetSocket InvalidSocket = -1; + +/// void event(NetSocket sock, U32 state) +typedef JournaledSignal ConnectionNotifyEvent; + +/// void event(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress) +typedef JournaledSignal ConnectionAcceptedEvent; + +/// void event(NetSocket connection, RawData incomingData) +typedef JournaledSignal ConnectionReceiveEvent; + +/// void event(NetAddress originator, RawData incomingData) +typedef JournaledSignal PacketReceiveEvent; + +/// Platform-specific network operations. +struct Net +{ + enum Error + { + NoError, + WrongProtocolType, + InvalidPacketProtocol, + WouldBlock, + NotASocket, + UnknownError + }; + + enum ConnectionState { + DNSResolved, + DNSFailed, + Connected, + ConnectFailed, + Disconnected + }; + + enum Protocol + { + UDPProtocol, + TCPProtocol + }; + + static const int MaxPacketDataSize = MAXPACKETSIZE; + + static ConnectionNotifyEvent smConnectionNotify; + static ConnectionAcceptedEvent smConnectionAccept; + static ConnectionReceiveEvent smConnectionReceive; + static PacketReceiveEvent smPacketReceive; + + static bool init(); + static void shutdown(); + + // Unreliable net functions (UDP) + // sendto is for sending data + // all incoming data comes in on packetReceiveEventType + // App can only open one unreliable port... who needs more? ;) + + static bool openPort(S32 connectPort, bool doBind = true); + static NetSocket getPort(); + + static void closePort(); + static Error sendto(const NetAddress *address, const U8 *buffer, S32 bufferSize); + + // Reliable net functions (TCP) + // all incoming messages come in on the Connected* events + static NetSocket openListenPort(U16 port); + static NetSocket openConnectTo(const char *stringAddress); // does the DNS resolve etc. + static void closeConnectTo(NetSocket socket); + static Error sendtoSocket(NetSocket socket, const U8 *buffer, S32 bufferSize); + + static bool compareAddresses(const NetAddress *a1, const NetAddress *a2); + static bool stringToAddress(const char *addressString, NetAddress *address); + static void addressToString(const NetAddress *address, char addressString[256]); + + // lower level socked based network functions + static NetSocket openSocket(); + static Error closeSocket(NetSocket socket); + + static Error send(NetSocket socket, const U8 *buffer, S32 bufferSize); + static Error recv(NetSocket socket, U8 *buffer, S32 bufferSize, S32 *bytesRead); + + static Error connect(NetSocket socket, const NetAddress *address); + static Error listen(NetSocket socket, S32 maxConcurrentListens); + static NetSocket accept(NetSocket acceptSocket, NetAddress *remoteAddress); + + static Error bind(NetSocket socket, U16 port); + static Error setBufferSize(NetSocket socket, S32 bufferSize); + static Error setBroadcast(NetSocket socket, bool broadcastEnable); + static Error setBlocking(NetSocket socket, bool blockingIO); + + +private: + static void process(); + +}; + +#endif \ No newline at end of file diff --git a/platform/platformNetAsync.cpp b/platform/platformNetAsync.cpp new file mode 100644 index 0000000..8399b01 --- /dev/null +++ b/platform/platformNetAsync.cpp @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformNetAsync.h" +#include "core/strings/stringFunctions.h" +#include "platform/threads/threadPool.h" +#include "console/console.h" + +#if defined(TORQUE_OS_WIN32) +# include +#elif defined(TORQUE_OS_XENON) +# include +#else +# include +# include +#endif + +#include + +NetAsync gNetAsync; + +//-------------------------------------------------------------------------- +// NetAsync::NameLookupRequest. +//-------------------------------------------------------------------------- + +// internal structure for storing information about a name lookup request +struct NetAsync::NameLookupRequest +{ + NetSocket sock; + char remoteAddr[4096]; + char out_h_addr[4096]; + int out_h_length; + bool complete; + + NameLookupRequest() + { + sock = InvalidSocket; + remoteAddr[0] = 0; + out_h_addr[0] = 0; + out_h_length = -1; + complete = false; + } +}; + +//-------------------------------------------------------------------------- +// NetAsync::NameLookupWorkItem. +//-------------------------------------------------------------------------- + +/// Work item issued to the thread pool for each lookup request. + +struct NetAsync::NameLookupWorkItem : public ThreadPool::WorkItem +{ + typedef ThreadPool::WorkItem Parent; + + NameLookupWorkItem( NameLookupRequest& request, ThreadPool::Context* context = 0 ) + : Parent( context ), + mRequest( request ) + { + } + +protected: + virtual void execute() + { +#ifndef TORQUE_OS_XENON + // do it + struct hostent* hostent = gethostbyname(mRequest.remoteAddr); + if (hostent == NULL) + { + // oh well! leave the lookup data unmodified (h_length) should + // still be -1 from initialization + mRequest.complete = true; + } + else + { + // copy the stuff we need from the hostent + dMemset(mRequest.out_h_addr, 0, + sizeof(mRequest.out_h_addr)); + dMemcpy(mRequest.out_h_addr, hostent->h_addr, hostent->h_length); + + mRequest.out_h_length = hostent->h_length; + mRequest.complete = true; + } +#else + AssertFatal( false, "NetAsync not supported on Xenon" ); +#endif + } + +private: + NameLookupRequest& mRequest; +}; + +//-------------------------------------------------------------------------- +// NetAsync. +//-------------------------------------------------------------------------- + +NetAsync::NetAsync() +{ + VECTOR_SET_ASSOCIATION( mLookupRequests ); +} + +void NetAsync::queueLookup(const char* remoteAddr, NetSocket socket) +{ + // do we have it already? + + unsigned int i = 0; + for (i = 0; i < mLookupRequests.size(); ++i) + { + if (mLookupRequests[i].sock == socket) + // found it. ignore more than one lookup at a time for a socket. + return; + } + + // not found, so add it + + mLookupRequests.increment(); + NameLookupRequest& lookupRequest = mLookupRequests.last(); + lookupRequest.sock = socket; + dStrncpy(lookupRequest.remoteAddr, remoteAddr, sizeof(lookupRequest.remoteAddr)); + + ThreadSafeRef< NameLookupWorkItem > workItem( new NameLookupWorkItem( lookupRequest ) ); + ThreadPool::GLOBAL().queueWorkItem( workItem ); +} + +bool NetAsync::checkLookup(NetSocket socket, char* out_h_addr, + int* out_h_length, int out_h_addr_size) +{ + bool found = false; + + // search for the socket + RequestIterator iter; + for (iter = mLookupRequests.begin(); + iter != mLookupRequests.end(); + ++iter) + // if we found it and it is complete... + if (socket == iter->sock && iter->complete) + { + // copy the lookup data to the callers parameters + dMemcpy(out_h_addr, iter->out_h_addr, out_h_addr_size); + *out_h_length = iter->out_h_length; + found = true; + break; + } + + // we found the socket, so we are done with it. erase. + if (found) + mLookupRequests.erase(iter); + + return found; +} diff --git a/platform/platformNetAsync.h b/platform/platformNetAsync.h new file mode 100644 index 0000000..7b9890f --- /dev/null +++ b/platform/platformNetAsync.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef PLATFORM_NET_ASYNC_H +#define PLATFORM_NET_ASYNC_H + +#include "platform/platform.h" +#include "platform/platformNet.h" +#include "core/util/tVector.h" + +// class for doing asynchronous network operations on unix (linux and +// hopefully osx) platforms. right now it only implements dns lookups +class NetAsync +{ + private: + struct NameLookupRequest; + struct NameLookupWorkItem; + + typedef Vector< NameLookupRequest > RequestVector; + typedef RequestVector::iterator RequestIterator; + + RequestVector mLookupRequests; + + public: + NetAsync(); + + // queue a DNS lookup. only one dns lookup can be queued per socket at + // a time. subsequent queue request for the socket are ignored. use + // checkLookup() to check the status of a request. + void queueLookup(const char* remoteAddr, NetSocket socket); + + // check on the status of a dns lookup for a socket. if the lookup is + // not yet complete, the function will return false. if it is + // complete, the function will return true, and out_h_addr and + // out_h_length will be set appropriately. if out_h_length is -1, then + // name could not be resolved. otherwise, it provides the number of + // address bytes copied into out_h_addr. + bool checkLookup(NetSocket socket, char* out_h_addr, int* out_h_length, int out_h_addr_size); +}; + +// the global net async object +extern NetAsync gNetAsync; + +#endif diff --git a/platform/platformRedBook.cpp b/platform/platformRedBook.cpp new file mode 100644 index 0000000..eff851a --- /dev/null +++ b/platform/platformRedBook.cpp @@ -0,0 +1,254 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "console/console.h" +#include "platform/platformRedBook.h" + +//------------------------------------------------------------------------------ +// Class: RedBookDevice +//------------------------------------------------------------------------------ +RedBookDevice::RedBookDevice() +{ + mAcquired = false; + mDeviceName = 0; +} + +RedBookDevice::~RedBookDevice() +{ + delete [] mDeviceName; +} + +//------------------------------------------------------------------------------ +// Class: RedBook +//------------------------------------------------------------------------------ +Vector RedBook::smDeviceList(__FILE__, __LINE__); +RedBookDevice * RedBook::smCurrentDevice; +char RedBook::smLastError[1024]; + +//------------------------------------------------------------------------------ +void RedBook::init() +{ +} + +void RedBook::destroy() +{ + close(); + + for( Vector::iterator i = smDeviceList.begin( ); i != smDeviceList.end( ); i++ ) { + delete *i; + } + + smDeviceList.clear( ); +} + +//------------------------------------------------------------------------------ +void RedBook::installDevice(RedBookDevice * device) +{ + smDeviceList.push_back(device); +} + +RedBookDevice * RedBook::getCurrentDevice() +{ + return(smCurrentDevice); +} + +U32 RedBook::getDeviceCount() +{ + return(smDeviceList.size()); +} + +const char * RedBook::getDeviceName(U32 idx) +{ + if(idx >= getDeviceCount()) + { + setLastError("Invalid device index"); + return(""); + } + return(smDeviceList[idx]->mDeviceName); +} + +void RedBook::setLastError(const char * error) +{ + if(!error || dStrlen(error) >= sizeof(smLastError)) + setLastError("Invalid error string passed"); + else + dStrcpy(smLastError, error); +} + +const char * RedBook::getLastError() +{ + return(smLastError); +} + +void RedBook::handleCallback(U32 type) +{ + switch(type) + { + case PlayFinished: + Con::executef("RedBookCallback", "PlayFinished"); + break; + } +} + +//------------------------------------------------------------------------------ +bool RedBook::open(const char * deviceName) +{ + if(!deviceName) + { + setLastError("Invalid device name"); + return(false); + } + + for(U32 i = 0; i < smDeviceList.size(); i++) + if(!dStricmp(deviceName, smDeviceList[i]->mDeviceName)) + return(open(smDeviceList[i])); + + setLastError("Failed to find device"); + return(false); +} + +bool RedBook::open(RedBookDevice * device) +{ + if(!device) + { + setLastError("Invalid device passed"); + return(false); + } + + close(); + smCurrentDevice = device; + return(smCurrentDevice->open()); +} + +bool RedBook::close() +{ + if(smCurrentDevice) + { + bool ret = smCurrentDevice->close(); + smCurrentDevice = 0; + return(ret); + } + + setLastError("No device is currently open"); + return(false); +} + +bool RedBook::play(U32 track) +{ + if(!smCurrentDevice) + { + setLastError("No device is currently open"); + return(false); + } + return(smCurrentDevice->play(track)); +} + +bool RedBook::stop() +{ + if(!smCurrentDevice) + { + setLastError("No device is currently open"); + return(false); + } + return(smCurrentDevice->stop()); +} + +bool RedBook::getTrackCount(U32 * trackCount) +{ + if(!smCurrentDevice) + { + setLastError("No device is currently open"); + return(false); + } + return(smCurrentDevice->getTrackCount(trackCount)); +} + +bool RedBook::getVolume(F32 * volume) +{ + if(!smCurrentDevice) + { + setLastError("No device is currently open"); + return(false); + } + return(smCurrentDevice->getVolume(volume)); +} + +bool RedBook::setVolume(F32 volume) +{ + if(!smCurrentDevice) + { + setLastError("No device is currently open"); + return(false); + } + return(smCurrentDevice->setVolume(volume)); +} + +//------------------------------------------------------------------------------ +// console methods +//------------------------------------------------------------------------------ + +ConsoleFunctionGroupBegin( Redbook, "Control functions for Redbook audio (ie, CD audio)."); + +ConsoleFunction(redbookOpen, bool, 1, 2, "(string device=NULL)") +{ + if(argc == 1) + return(RedBook::open(RedBook::getDeviceName(0))); + else + return(RedBook::open(argv[1])); +} + +ConsoleFunction(redbookClose, bool, 1, 1, "Close the current Redbook device.") +{ + return(RedBook::close()); +} + +ConsoleFunction( redbookPlay, bool, 2, 2, "(int track) Play the selected track.") +{ + return(RedBook::play(dAtoi(argv[1]))); +} + +ConsoleFunction( redbookStop, bool, 1, 1, "Stop playing.") +{ + return(RedBook::stop()); +} +ConsoleFunction(redbookGetTrackCount, S32, 1, 1, "Return the number of tracks.") +{ + U32 trackCount; + if(!RedBook::getTrackCount(&trackCount)) + return(0); + return(trackCount); +} + +ConsoleFunction(redbookGetVolume, F32, 1, 1, "Get the volume.") +{ + F32 vol; + if(!RedBook::getVolume(&vol)) + return(0.f); + else + return(vol); +} + +ConsoleFunction(redbookSetVolume, bool, 2, 2, "(float volume) Set playback volume.") +{ + return(RedBook::setVolume(dAtof(argv[1]))); +} + +ConsoleFunction( redbookGetDeviceCount, S32, 1, 1, "get the number of redbook devices.") +{ + return(RedBook::getDeviceCount()); +} + +ConsoleFunction( redbookGetDeviceName, const char *, 2, 2, "(int index) Get name of specified Redbook device.") +{ + return(RedBook::getDeviceName(dAtoi(argv[1]))); +} + +ConsoleFunction( redbookGetLastError, const char*, 1, 1, "Get a string explaining the last redbook error.") +{ + return(RedBook::getLastError()); +} + +ConsoleFunctionGroupEnd( Redbook ); diff --git a/platform/platformRedBook.h b/platform/platformRedBook.h new file mode 100644 index 0000000..addd7a3 --- /dev/null +++ b/platform/platformRedBook.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMREDBOOK_H_ +#define _PLATFORMREDBOOK_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class RedBookDevice +{ + public: + RedBookDevice(); + virtual ~RedBookDevice(); + + bool mAcquired; + char * mDeviceName; + + virtual bool open() = 0; + virtual bool close() = 0; + virtual bool play(U32) = 0; + virtual bool stop() = 0; + virtual bool getTrackCount(U32 *) = 0; + virtual bool getVolume(F32 *) = 0; + virtual bool setVolume(F32) = 0; +}; + +class RedBook +{ + private: + static Vector smDeviceList; + static RedBookDevice * smCurrentDevice; + static char smLastError[]; + + public: + enum { + PlayFinished = 0, + }; + static void handleCallback(U32); + + static void init(); + static void destroy(); + + static void installDevice(RedBookDevice *); + static U32 getDeviceCount(); + static const char * getDeviceName(U32); + static RedBookDevice * getCurrentDevice(); + + static void setLastError(const char *); + static const char * getLastError(); + + static bool open(const char *); + static bool open(RedBookDevice *); + static bool close(); + static bool play(U32); + static bool stop(); + static bool getTrackCount(U32 *); + static bool getVolume(F32 *); + static bool setVolume(F32); +}; + +//------------------------------------------------------------------------------ + +#endif diff --git a/platform/platformTLS.h b/platform/platformTLS.h new file mode 100644 index 0000000..5c5e8b6 --- /dev/null +++ b/platform/platformTLS.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMTLS_H_ +#define _PLATFORMTLS_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +struct PlatformThreadStorage; + +/// Platform independent per-thread storage class. +class ThreadStorage +{ + enum + { + PlatformThreadStorageStorageSize = 32, + }; + + PlatformThreadStorage *mThreadStorage; + U8 mStorage[PlatformThreadStorageStorageSize]; +public: + /// ThreadStorage constructor. + ThreadStorage(); + /// ThreadStorage destructor. + ~ThreadStorage(); + + /// returns the per-thread stored void pointer for this ThreadStorage. The default value is NULL. + void *get(); + /// sets the per-thread stored void pointer for this ThreadStorage object. + void set(void *data); +}; + + +#endif diff --git a/platform/platformTimer.cpp b/platform/platformTimer.cpp new file mode 100644 index 0000000..e6f0cde --- /dev/null +++ b/platform/platformTimer.cpp @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformTimer.h" +#include "core/util/journal/process.h" + +void TimeManager::_updateTime() +{ + // Calculate & filter time delta since last event. + + // How long since last update? + S32 delta = mTimer->getElapsedMs(); + + // Now - we want to try to sleep until the time threshold will hit. + S32 msTillThresh = (mBackground ? mBackgroundThreshold : mForegroundThreshold) - delta; + + if(msTillThresh > 0) + { + // There's some time to go, so let's sleep. + Platform::sleep( msTillThresh ); + } + + // Ok - let's grab the new elapsed and send that out. + S32 finalDelta = mTimer->getElapsedMs(); + mTimer->reset(); + + timeEvent.trigger(finalDelta); +} + +TimeManager::TimeManager() +{ + mBackground = false; + mTimer = PlatformTimer::create(); + Process::notify(this, &TimeManager::_updateTime, PROCESS_TIME_ORDER); + + mForegroundThreshold = 5; + mBackgroundThreshold = 10; +} + +TimeManager::~TimeManager() +{ + Process::remove(this, &TimeManager::_updateTime); + delete mTimer; +} + +void TimeManager::setForegroundThreshold(const S32 msInterval) +{ + AssertFatal(msInterval > 0, "TimeManager::setForegroundThreshold - should have at least 1 ms between time events to avoid math problems!"); + mForegroundThreshold = msInterval; +} + +const S32 TimeManager::getForegroundThreshold() const +{ + return mForegroundThreshold; +} + +void TimeManager::setBackgroundThreshold(const S32 msInterval) +{ + AssertFatal(msInterval > 0, "TimeManager::setBackgroundThreshold - should have at least 1 ms between time events to avoid math problems!"); + mBackgroundThreshold = msInterval; +} + +const S32 TimeManager::getBackgroundThreshold() const +{ + return mBackgroundThreshold; +} + +//---------------------------------------------------------------------------------- + +#pragma message("Mac/Lunix will need to implement this or get unresolved externals.") +#pragma message(" It was previously defined here with a Win32 ifdef which goes against") +#pragma message(" how torque implements its platform agnostic systems - JDD") +//PlatformTimer *PlatformTimer::create() +//{ +// return new DefaultPlatformTimer(); +//} + +PlatformTimer::PlatformTimer() +{ +} + +PlatformTimer::~PlatformTimer() +{ +} + + +// Exposes PlatformTimer to script for when high precision is needed. + +#include "core/util/tDictionary.h" +#include "console/console.h" + +class ScriptTimerMan +{ +public: + + ScriptTimerMan(); + ~ScriptTimerMan(); + + S32 startTimer(); + S32 stopTimer( S32 id ); + +protected: + + static S32 smNextId; + + typedef Map TimerMap; + + TimerMap mTimers; +}; + +S32 ScriptTimerMan::smNextId = 1; + +ScriptTimerMan::ScriptTimerMan() +{ +} + +ScriptTimerMan::~ScriptTimerMan() +{ + TimerMap::Iterator itr = mTimers.begin(); + + for ( ; itr != mTimers.end(); itr++ ) + delete itr->value; + + mTimers.clear(); +} + +S32 ScriptTimerMan::startTimer() +{ + PlatformTimer *timer = PlatformTimer::create(); + mTimers.insert( smNextId, timer ); + smNextId++; + return ( smNextId - 1 ); +} + +S32 ScriptTimerMan::stopTimer( S32 id ) +{ + TimerMap::Iterator itr = mTimers.find( id ); + if ( itr == mTimers.end() ) + return -1; + + PlatformTimer *timer = itr->value; + S32 elapsed = timer->getElapsedMs(); + + mTimers.erase( itr ); + delete timer; + + return elapsed; +} + +ScriptTimerMan gScriptTimerMan; + +ConsoleFunction( startPrecisionTimer, S32, 1, 1, "startPrecisionTimer() - Create and start a high resolution platform timer. Returns the timer id." ) +{ + return gScriptTimerMan.startTimer(); +} + +ConsoleFunction( stopPrecisionTimer, S32, 2, 2, "stopPrecisionTimer( S32 id ) - Stop and destroy timer with the passed id. Returns the elapsed milliseconds." ) +{ + return gScriptTimerMan.stopTimer( dAtoi( argv[1] ) ); +} \ No newline at end of file diff --git a/platform/platformTimer.h b/platform/platformTimer.h new file mode 100644 index 0000000..4f12a85 --- /dev/null +++ b/platform/platformTimer.h @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORM_PLATFORMTIMER_H_ +#define _PLATFORM_PLATFORMTIMER_H_ + +#include "platform/platform.h" +#include "core/util/journal/journaledSignal.h" + +/// Platform-specific timer class. +/// +/// This exists primarily as support for the TimeManager, but may be useful +/// elsewhere. +class PlatformTimer +{ +protected: + PlatformTimer(); +public: + virtual ~PlatformTimer(); + + /// Get the number of MS that have elapsed since creation or the last + /// reset call. + virtual const S32 getElapsedMs()=0; + + /// Reset elapsed ms back to zero. + virtual void reset()=0; + + /// Create a new PlatformTimer. + static PlatformTimer *create(); +}; + +/// Utility class to fire journalled time-delta events at regular intervals. +/// +/// Most games and simulations need the ability to update their state based on +/// a time-delta. However, tracking time accurately and sending out well-conditioned +/// events (for instance, allowing no events with delta=0) tends to be platform +/// specific. This class provides an abstraction around this platform mojo. +/// +/// In addition, a well behaved application may want to alter how frequently +/// it processes time advancement depending on its execution state. For instance, +/// a game running in the background can significantly reduce CPU usage +/// by only updating every 100ms, instead of trying to maintain a 1ms update +/// update rate. +class TimeManager +{ + PlatformTimer *mTimer; + S32 mForegroundThreshold, mBackgroundThreshold; + bool mBackground; + + void _updateTime(); + +public: + + TimeManagerEvent timeEvent; + + TimeManager(); + ~TimeManager(); + + void setForegroundThreshold(const S32 msInterval); + const S32 getForegroundThreshold() const; + + void setBackgroundThreshold(const S32 msInterval); + const S32 getBackgroundThreshold() const; + + void setBackground(const bool isBackground) { mBackground = isBackground; }; + const bool getBackground() const { return mBackground; }; + +}; + +class DefaultPlatformTimer : public PlatformTimer +{ + S32 mLastTime, mNextTime; + +public: + DefaultPlatformTimer() + { + mLastTime = mNextTime = Platform::getRealMilliseconds(); + } + + const S32 getElapsedMs() + { + mNextTime = Platform::getRealMilliseconds(); + return (mNextTime - mLastTime); + } + + void reset() + { + mLastTime = mNextTime; + } +}; + +#endif \ No newline at end of file diff --git a/platform/platformVFS.h b/platform/platformVFS.h new file mode 100644 index 0000000..eb44349 --- /dev/null +++ b/platform/platformVFS.h @@ -0,0 +1,17 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMVFS_H_ +#define _PLATFORMVFS_H_ + +namespace Zip +{ + class ZipArchive; +} + +extern Zip::ZipArchive *openEmbeddedVFSArchive(); +extern void closeEmbeddedVFSArchive(); + +#endif // _PLATFORMVFS_H_ diff --git a/platform/platformVideoInfo.cpp b/platform/platformVideoInfo.cpp new file mode 100644 index 0000000..5fbf2f7 --- /dev/null +++ b/platform/platformVideoInfo.cpp @@ -0,0 +1,77 @@ +#include "platform/platformVideoInfo.h" +#include "core/strings/stringFunctions.h" + +//------------------------------------------------------------------------------ + +PlatformVideoInfo::PlatformVideoInfo() +{ + VECTOR_SET_ASSOCIATION( mAdapters ); +} + +//------------------------------------------------------------------------------ + +PlatformVideoInfo::~PlatformVideoInfo() +{ + +} + +//------------------------------------------------------------------------------ + + +bool PlatformVideoInfo::profileAdapters() +{ + // Initialize the child class + if( !_initialize() ) + return false; + + mAdapters.clear(); + + // Query the number of adapters + String tempString; + + mAdapters.increment( 1 ); + //if( !_queryProperty( PVI_NumAdapters, 0, &tempString ) ) + // return false; + + //mAdapters.increment( dAtoi( tempString ) ); + + U32 adapterNum = 0; + for( Vector::iterator itr = mAdapters.begin(); itr != mAdapters.end(); itr++ ) + { + PVIAdapter &adapter = *itr; + + + U32 querySuccessFlags = U32_MAX; + AssertFatal( PVI_QueryCount < sizeof( querySuccessFlags ) * 8, "Not enough bits in query success mask." ); + querySuccessFlags -= ( ( 1 << PVI_QueryCount ) - 1 ); + + // Fill in adapter information +#define _QUERY_MASK_HELPER( querytype, outstringaddr ) \ + querySuccessFlags |= ( _queryProperty( querytype, adapterNum, outstringaddr ) ? 1 << querytype : 0 ) + + _QUERY_MASK_HELPER( PVI_NumDevices, &tempString ); + adapter.numDevices = dAtoi( tempString ); + + _QUERY_MASK_HELPER( PVI_VRAM, &tempString ); + adapter.vram = dAtoi( tempString ); + + _QUERY_MASK_HELPER( PVI_Description, &adapter.description ); + _QUERY_MASK_HELPER( PVI_Name, &adapter.name ); + _QUERY_MASK_HELPER( PVI_ChipSet, &adapter.chipSet ); + _QUERY_MASK_HELPER( PVI_DriverVersion, &adapter.driverVersion ); + +#undef _QUERY_MASK_HELPER + + // Test flags here for success + } + + return true; +} + +//------------------------------------------------------------------------------ + +const PlatformVideoInfo::PVIAdapter &PlatformVideoInfo::getAdapterInformation( const U32 adapterIndex ) const +{ + AssertFatal( adapterIndex < mAdapters.size(), "Not that many adapters" ); + return mAdapters[adapterIndex]; +} \ No newline at end of file diff --git a/platform/platformVideoInfo.h b/platform/platformVideoInfo.h new file mode 100644 index 0000000..4c8f11e --- /dev/null +++ b/platform/platformVideoInfo.h @@ -0,0 +1,77 @@ +#ifndef _PLATFORM_VIDEOINFO_H_ +#define _PLATFORM_VIDEOINFO_H_ + +#include "platform/platform.h" +#include "core/util/str.h" +#include "core/util/tVector.h" + +// The purpose of this class is to abstract the gathering of video adapter information. +// This information is not specific to the API being used to do the rendering, or +// the capabilities of that renderer. That information is queried in a different +// class. + +class PlatformVideoInfo +{ + // # of devices + // description + // manufacturer + // chip set + // driver version + // VRAM +public: + enum PVIQueryType + { + PVI_QueryStart = 0, ///< Start of the enum for looping + + // The NumAdapters query is the only non-adapter specific query, the following + // queries are all specific to an adapter. + PVI_NumDevices = 0, ///< Number of sub adapters + PVI_Description, ///< String description of the adapter + PVI_Name, ///< Card name string + PVI_ChipSet, ///< Chipset string + PVI_DriverVersion, ///< Driver version string + PVI_VRAM, ///< Dedicated video memory in megabytes + + // Please add query types above this value + PVI_QueryCount, ///< Counter so that this enum can be looped over + PVI_NumAdapters, ///< Number of adapters on the system + }; + + struct PVIAdapter + { + U32 numDevices; + String description; + String name; + String chipSet; + String driverVersion; + U32 vram; + }; + +private: + Vector mAdapters; ///< Vector of adapters + + /// Signal handling method for GFX signals + // bool processGFXSignal( GFXDevice::GFXDeviceEventType gfxEvent ); + +protected: + /// This method will be called before any queries are made. All initialization, + /// for example Win32 COM startup, should be done in this method. If the return + /// value is false, no querys will be made. + virtual bool _initialize() = 0; + + /// This is the query method which subclasses must implement. The querys made + /// are all found in the PVIQueryType enum. If NULL is specified for outValue, + /// the sub class should simply return true if the query type is supported, and + /// false otherwise. + virtual bool _queryProperty( const PVIQueryType queryType, const U32 adapterId, String *outValue ) = 0; + +public: + PlatformVideoInfo(); + virtual ~PlatformVideoInfo(); + + bool profileAdapters(); + + const PVIAdapter &getAdapterInformation( const U32 adapterIndex ) const; +}; + +#endif \ No newline at end of file diff --git a/platform/platformVolume.cpp b/platform/platformVolume.cpp new file mode 100644 index 0000000..398d1ae --- /dev/null +++ b/platform/platformVolume.cpp @@ -0,0 +1,72 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "platform/types.h" + +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) +#include +#else +#include +#endif + +#include "platform/platformVolume.h" +#include "core/util/zip/zipVolume.h" + +using namespace Torque; +using namespace Torque::FS; + +namespace Platform +{ +namespace FS +{ + +bool MountDefaults() +{ + String path = getAssetDir(); + + bool mounted = Mount( "game", createNativeFS( path )); + + if ( !mounted ) + return false; + + // [8/31/2009 tomb] Disabling this for T3D 1.0 due to issues with script namespace corruption when running from zips + // Note that the VirtualMountSystem must be enabled in volume.cpp for zip support to work. + //return MountZips("game"); + return true; +} + +bool MountZips(const String &root) +{ + Path basePath; + basePath.setRoot(root); + Vector outList; + + S32 num = FindByPattern(basePath, "*.zip", true, outList); + if(num == 0) + return true; // not an error + + S32 mounted = 0; + for(S32 i = 0;i < outList.size();++i) + { + String &zipfile = outList[i]; + mounted += (S32)Mount(root, new ZipFileSystem(zipfile, true)); + } + + return mounted == outList.size(); +} + +//----------------------------------------------------------------------------- + +bool Touch( const Path &path ) +{ +#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XBOX) || defined(TORQUE_OS_XENON) + return( utime( path.getFullPath(), 0 ) != -1 ); +#else + return( utimes( path.getFullPath(), NULL) == 0 ); // utimes returns 0 on success. +#endif +} + +} // namespace FS +} // namespace Platform diff --git a/platform/platformVolume.h b/platform/platformVolume.h new file mode 100644 index 0000000..bbf3aac --- /dev/null +++ b/platform/platformVolume.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMVOLUME_H_ +#define _PLATFORMVOLUME_H_ + +#include "core/volume.h" + + +namespace Platform +{ +namespace FS +{ + using namespace Torque; + using namespace Torque::FS; + + FileSystemRef createNativeFS( const String &volume ); + + String getAssetDir(); + + /// Mount default OS file systems. + /// On POSIX environment this means mounting a root FileSystem "/", mounting + /// the $HOME environment variable as the "home:/" file system and setting the + /// current working directory to the current OS working directory. + bool InstallFileSystems(); + + bool MountDefaults(); + bool MountZips(const String &root); + + bool Touch( const Path &path ); + +} // Namespace FS +} // Namespace Platform + +#endif + diff --git a/platform/profiler.cpp b/platform/profiler.cpp new file mode 100644 index 0000000..1eff707 --- /dev/null +++ b/platform/profiler.cpp @@ -0,0 +1,714 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/types.h" + +#if defined(TORQUE_OS_WIN32) +#include // for SetThreadAffinityMask +#endif + +#if defined(TORQUE_OS_MAC) +#include // For high resolution timer +#endif + +#include "core/stream/fileStream.h" +#include "core/frameAllocator.h" +#include "core/strings/stringFunctions.h" +#include "core/stringTable.h" + +#include "platform/profiler.h" +#include "platform/threads/thread.h" + +#include "console/console.h" + +#ifdef TORQUE_ENABLE_PROFILER +ProfilerRootData *ProfilerRootData::sRootList = NULL; +Profiler *gProfiler = NULL; + +// Uncomment the following line to enable a debugging aid for mismatched profiler blocks. +//#define TORQUE_PROFILER_DEBUG + +// Machinery to record the stack of node names, as a debugging aid to find +// mismatched PROFILE_START and PROFILE_END blocks. We profile from the +// beginning to catch profile block errors that occur when torque is starting up. +#ifdef TORQUE_PROFILER_DEBUG +Vector gProfilerNodeStack; +#define TORQUE_PROFILE_AT_ENGINE_START true +#define PROFILER_DEBUG_PUSH_NODE( nodename ) \ + gProfilerNodeStack.push_back( nodename ); +#define PROFILER_DEBUG_POP_NODE() \ + gProfilerNodeStack.pop_back(); +#else +#define TORQUE_PROFILE_AT_ENGINE_START false +#define PROFILER_DEBUG_PUSH_NODE( nodename ) ; +#define PROFILER_DEBUG_POP_NODE() ; +#endif + +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) +// platform specific get hires times... +void startHighResolutionTimer(U32 time[2]) +{ + //time[0] = Platform::getRealMilliseconds(); + + __asm + { + push eax + push edx + push ecx + rdtsc + mov ecx, time + mov DWORD PTR [ecx], eax + mov DWORD PTR [ecx + 4], edx + pop ecx + pop edx + pop eax + } +} + +U32 endHighResolutionTimer(U32 time[2]) +{ + U32 ticks; + //ticks = Platform::getRealMilliseconds() - time[0]; + //return ticks; + + __asm + { + push eax + push edx + push ecx + //db 0fh, 31h + rdtsc + mov ecx, time + sub edx, DWORD PTR [ecx+4] + sbb eax, DWORD PTR [ecx] + mov DWORD PTR ticks, eax + pop ecx + pop edx + pop eax + } + return ticks; +} + +#elif defined(TORQUE_SUPPORTS_GCC_INLINE_X86_ASM) + +// platform specific get hires times... +void startHighResolutionTimer(U32 time[2]) +{ + __asm__ __volatile__( + "rdtsc\n" + : "=a" (time[0]), "=d" (time[1]) + ); +} + +U32 endHighResolutionTimer(U32 time[2]) +{ + U32 ticks; + __asm__ __volatile__( + "rdtsc\n" + "sub 0x4(%%ecx), %%edx\n" + "sbb (%%ecx), %%eax\n" + : "=a" (ticks) : "c" (time) + ); + return ticks; +} + +#elif defined(TORQUE_OS_MAC) + + +void startHighResolutionTimer(U32 time[2]) { + UnsignedWide t; + Microseconds(&t); + time[0] = t.lo; + time[1] = t.hi; +} + +U32 endHighResolutionTimer(U32 time[2]) { + UnsignedWide t; + Microseconds(&t); + return t.lo - time[0]; + // given that we're returning a 32 bit integer, and this is unsigned subtraction... + // it will just wrap around, we don't need the upper word of the time. + // NOTE: the code assumes that more than 3 hrs will not go by between calls to startHighResolutionTimer() and endHighResolutionTimer(). + // I mean... that damn well better not happen anyway. +} + +#else + +void startHighResolutionTimer(U32 time[2]) +{ +} + +U32 endHighResolutionTimer(U32 time[2]) +{ + return 1; +} + +#endif + +Profiler::Profiler() +{ + mMaxStackDepth = MaxStackDepth; + mCurrentHash = 0; + + mCurrentProfilerData = (ProfilerData *) malloc(sizeof(ProfilerData)); + mCurrentProfilerData->mRoot = NULL; + mCurrentProfilerData->mNextForRoot = NULL; + mCurrentProfilerData->mNextProfilerData = NULL; + mCurrentProfilerData->mNextHash = NULL; + mCurrentProfilerData->mParent = NULL; + mCurrentProfilerData->mNextSibling = NULL; + mCurrentProfilerData->mFirstChild = NULL; + mCurrentProfilerData->mLastSeenProfiler = NULL; + mCurrentProfilerData->mHash = 0; + mCurrentProfilerData->mSubDepth = 0; + mCurrentProfilerData->mInvokeCount = 0; + mCurrentProfilerData->mTotalTime = 0; + mCurrentProfilerData->mSubTime = 0; +#ifdef TORQUE_ENABLE_PROFILE_PATH + mCurrentProfilerData->mPath = ""; +#endif + mRootProfilerData = mCurrentProfilerData; + + for(U32 i = 0; i < ProfilerData::HashTableSize; i++) + mCurrentProfilerData->mChildHash[i] = 0; + + mProfileList = NULL; + + mEnabled = TORQUE_PROFILE_AT_ENGINE_START; + mNextEnable = TORQUE_PROFILE_AT_ENGINE_START; + mStackDepth = 0; + gProfiler = this; + mDumpToConsole = false; + mDumpToFile = false; + mDumpFileName[0] = '\0'; +} + +Profiler::~Profiler() +{ + reset(); + free(mRootProfilerData); + gProfiler = NULL; +} + +void Profiler::reset() +{ + mEnabled = false; // in case we're in a profiler call. + while(mProfileList) + { + free(mProfileList); + mProfileList = NULL; + } + for(ProfilerRootData *walk = ProfilerRootData::sRootList; walk; walk = walk->mNextRoot) + { + walk->mFirstProfilerData = 0; + walk->mTotalTime = 0; + walk->mSubTime = 0; + walk->mTotalInvokeCount = 0; + } + mCurrentProfilerData = mRootProfilerData; + mCurrentProfilerData->mNextForRoot = 0; + mCurrentProfilerData->mFirstChild = 0; + for(U32 i = 0; i < ProfilerData::HashTableSize; i++) + mCurrentProfilerData->mChildHash[i] = 0; + mCurrentProfilerData->mInvokeCount = 0; + mCurrentProfilerData->mTotalTime = 0; + mCurrentProfilerData->mSubTime = 0; + mCurrentProfilerData->mSubDepth = 0; + mCurrentProfilerData->mLastSeenProfiler = 0; +} + +static Profiler aProfiler; // allocate the global profiler + +ProfilerRootData::ProfilerRootData(const char *name) +{ + for(ProfilerRootData *walk = sRootList; walk; walk = walk->mNextRoot) + if(!dStrcmp(walk->mName, name)) + Platform::debugBreak(); + + mName = name; + mNameHash = _StringTable::hashString(name); + mNextRoot = sRootList; + sRootList = this; + mTotalTime = 0; + mTotalInvokeCount = 0; + mFirstProfilerData = NULL; + mEnabled = true; +} + +void Profiler::validate() +{ + for(ProfilerRootData *walk = ProfilerRootData::sRootList; walk; walk = walk->mNextRoot) + { + for(ProfilerData *dp = walk->mFirstProfilerData; dp; dp = dp->mNextForRoot) + { + if(dp->mRoot != walk) + Platform::debugBreak(); + // check if it's in the parent's list... + ProfilerData *wk; + for(wk = dp->mParent->mFirstChild; wk; wk = wk->mNextSibling) + if(wk == dp) + break; + if(!wk) + Platform::debugBreak(); + for(wk = dp->mParent->mChildHash[walk->mNameHash & (ProfilerData::HashTableSize - 1)] ; + wk; wk = wk->mNextHash) + if(wk == dp) + break; + if(!wk) + Platform::debugBreak(); + } + } +} + +#ifdef TORQUE_ENABLE_PROFILE_PATH +const char * Profiler::getProfilePath() +{ +#ifdef TORQUE_MULTITHREAD + // Ignore non-main-thread profiler activity. + if( !ThreadManager::isMainThread() ) + return "[non-main thread]"; +#endif + + return (mEnabled && mCurrentProfilerData) ? mCurrentProfilerData->mPath : "na"; +} +#endif + +#ifdef TORQUE_ENABLE_PROFILE_PATH +const char * Profiler::constructProfilePath(ProfilerData * pd) +{ + if (pd->mParent) + { + const bool saveEnable = gProfiler->mEnabled; + gProfiler->mEnabled = false; + + const char * connector = " -> "; + U32 len = dStrlen(pd->mParent->mPath); + if (!len) + connector = ""; + len += dStrlen(connector); + len += dStrlen(pd->mRoot->mName); + + U32 mark = FrameAllocator::getWaterMark(); + char * buf = (char*)FrameAllocator::alloc(len+1); + dStrcpy(buf,pd->mParent->mPath); + dStrcat(buf,connector); + dStrcat(buf,pd->mRoot->mName); + const char * ret = StringTable->insert(buf); + FrameAllocator::setWaterMark(mark); + + gProfiler->mEnabled = saveEnable; + + return ret; + } + return "root"; +} +#endif +void Profiler::hashPush(ProfilerRootData *root) +{ +#ifdef TORQUE_MULTITHREAD + // Ignore non-main-thread profiler activity. + if( !ThreadManager::isMainThread() ) + return; +#endif + + mStackDepth++; + PROFILER_DEBUG_PUSH_NODE(root->mName); + AssertFatal(mStackDepth <= mMaxStackDepth, + "Stack overflow in profiler. You may have mismatched PROFILE_START and PROFILE_ENDs"); + if(!mEnabled) + return; + + ProfilerData *nextProfiler = NULL; + if(!root->mEnabled || mCurrentProfilerData->mRoot == root) + { + mCurrentProfilerData->mSubDepth++; + return; + } + + if(mCurrentProfilerData->mLastSeenProfiler && + mCurrentProfilerData->mLastSeenProfiler->mRoot == root) + nextProfiler = mCurrentProfilerData->mLastSeenProfiler; + + if(!nextProfiler) + { + // first see if it's in the hash table... + U32 index = root->mNameHash & (ProfilerData::HashTableSize - 1); + nextProfiler = mCurrentProfilerData->mChildHash[index]; + while(nextProfiler) + { + if(nextProfiler->mRoot == root) + break; + nextProfiler = nextProfiler->mNextHash; + } + if(!nextProfiler) + { + nextProfiler = (ProfilerData *) malloc(sizeof(ProfilerData)); + for(U32 i = 0; i < ProfilerData::HashTableSize; i++) + nextProfiler->mChildHash[i] = 0; + + nextProfiler->mRoot = root; + nextProfiler->mNextForRoot = root->mFirstProfilerData; + root->mFirstProfilerData = nextProfiler; + + nextProfiler->mNextProfilerData = mProfileList; + mProfileList = nextProfiler; + + nextProfiler->mNextHash = mCurrentProfilerData->mChildHash[index]; + mCurrentProfilerData->mChildHash[index] = nextProfiler; + + nextProfiler->mParent = mCurrentProfilerData; + nextProfiler->mNextSibling = mCurrentProfilerData->mFirstChild; + mCurrentProfilerData->mFirstChild = nextProfiler; + nextProfiler->mFirstChild = NULL; + nextProfiler->mLastSeenProfiler = NULL; + nextProfiler->mHash = root->mNameHash; + nextProfiler->mInvokeCount = 0; + nextProfiler->mTotalTime = 0; + nextProfiler->mSubTime = 0; + nextProfiler->mSubDepth = 0; +#ifdef TORQUE_ENABLE_PROFILE_PATH + nextProfiler->mPath = constructProfilePath(nextProfiler); +#endif + } + } + root->mTotalInvokeCount++; + nextProfiler->mInvokeCount++; + startHighResolutionTimer(nextProfiler->mStartTime); + mCurrentProfilerData->mLastSeenProfiler = nextProfiler; + mCurrentProfilerData = nextProfiler; +} + +void Profiler::enable(bool enabled) +{ + mNextEnable = enabled; +} + +void Profiler::dumpToConsole() +{ + mDumpToConsole = true; + mDumpToFile = false; + mDumpFileName[0] = '\0'; +} + +void Profiler::dumpToFile(const char* fileName) +{ + AssertFatal(dStrlen(fileName) < DumpFileNameLength, "Error, dump filename too long"); + mDumpToFile = true; + mDumpToConsole = false; + dStrcpy(mDumpFileName, fileName); +} + +void Profiler::hashPop(ProfilerRootData *expected) +{ +#ifdef TORQUE_MULTITHREAD + // Ignore non-main-thread profiler activity. + if( !ThreadManager::isMainThread() ) + return; +#endif + + mStackDepth--; + PROFILER_DEBUG_POP_NODE(); + AssertFatal(mStackDepth >= 0, "Stack underflow in profiler. You may have mismatched PROFILE_START and PROFILE_ENDs"); + if(mEnabled) + { + if(mCurrentProfilerData->mSubDepth) + { + mCurrentProfilerData->mSubDepth--; + return; + } + + if(expected) + { + AssertISV(expected == mCurrentProfilerData->mRoot, "Profiler::hashPop - didn't get expected ProfilerRoot!"); + } + + F64 fElapsed = endHighResolutionTimer(mCurrentProfilerData->mStartTime); + + mCurrentProfilerData->mTotalTime += fElapsed; + mCurrentProfilerData->mParent->mSubTime += fElapsed; // mark it in the parent as well... + mCurrentProfilerData->mRoot->mTotalTime += fElapsed; + if(mCurrentProfilerData->mParent->mRoot) + mCurrentProfilerData->mParent->mRoot->mSubTime += fElapsed; // mark it in the parent as well... + + mCurrentProfilerData = mCurrentProfilerData->mParent; + } + if(mStackDepth == 0) + { + // apply the next enable... + if(mDumpToConsole || mDumpToFile) + { + dump(); + startHighResolutionTimer(mCurrentProfilerData->mStartTime); + } + if(!mEnabled && mNextEnable) + startHighResolutionTimer(mCurrentProfilerData->mStartTime); + +#if defined(TORQUE_OS_WIN32) + // The high performance counters under win32 are unreliable when running on multiple + // processors. When the profiler is enabled, we restrict Torque to a single processor. + if(mNextEnable != mEnabled) + { + + if(mNextEnable) + { + Con::warnf("Warning: forcing the Torque profiler thread to run only on cpu 1."); + SetThreadAffinityMask(GetCurrentThread(), 1); + } + else + { + Con::warnf("Warning: the Torque profiler thread may now run on any cpu."); + DWORD procMask; + DWORD sysMask; + GetProcessAffinityMask( GetCurrentProcess(), &procMask, &sysMask); + SetThreadAffinityMask( GetCurrentThread(), procMask); + } + } +#endif + + mEnabled = mNextEnable; + } +} + +static S32 QSORT_CALLBACK rootDataCompare(const void *s1, const void *s2) +{ + const ProfilerRootData *r1 = *((ProfilerRootData **) s1); + const ProfilerRootData *r2 = *((ProfilerRootData **) s2); + if((r2->mTotalTime - r2->mSubTime) > (r1->mTotalTime - r1->mSubTime)) + return 1; + return -1; +} + +static void profilerDataDumpRecurse(ProfilerData *data, char *buffer, U32 bufferLen, F64 totalTime) +{ + // dump out this one: + Con::printf("%7.3f %7.3f %8d %s%s", + 100 * data->mTotalTime / totalTime, + 100 * (data->mTotalTime - data->mSubTime) / totalTime, + data->mInvokeCount, + buffer, + data->mRoot ? data->mRoot->mName : "ROOT" ); + data->mTotalTime = 0; + data->mSubTime = 0; + data->mInvokeCount = 0; + + buffer[bufferLen] = ' '; + buffer[bufferLen+1] = ' '; + buffer[bufferLen+2] = 0; + // sort data's children... + ProfilerData *list = NULL; + while(data->mFirstChild) + { + ProfilerData *ins = data->mFirstChild; + data->mFirstChild = ins->mNextSibling; + ProfilerData **walk = &list; + while(*walk && (*walk)->mTotalTime > ins->mTotalTime) + walk = &(*walk)->mNextSibling; + ins->mNextSibling = *walk; + *walk = ins; + } + data->mFirstChild = list; + while(list) + { + if(list->mInvokeCount) + profilerDataDumpRecurse(list, buffer, bufferLen + 2, totalTime); + list = list->mNextSibling; + } + buffer[bufferLen] = 0; +} + +static void profilerDataDumpRecurseFile(ProfilerData *data, char *buffer, U32 bufferLen, F64 totalTime, FileStream& fws) +{ + char pbuffer[256]; + dSprintf(pbuffer, 255, "%7.3f %7.3f %8d %s%s\n", + 100 * data->mTotalTime / totalTime, + 100 * (data->mTotalTime - data->mSubTime) / totalTime, + data->mInvokeCount, + buffer, + data->mRoot ? data->mRoot->mName : "ROOT" ); + fws.write(dStrlen(pbuffer), pbuffer); + data->mTotalTime = 0; + data->mSubTime = 0; + data->mInvokeCount = 0; + + buffer[bufferLen] = ' '; + buffer[bufferLen+1] = ' '; + buffer[bufferLen+2] = 0; + // sort data's children... + ProfilerData *list = NULL; + while(data->mFirstChild) + { + ProfilerData *ins = data->mFirstChild; + data->mFirstChild = ins->mNextSibling; + ProfilerData **walk = &list; + while(*walk && (*walk)->mTotalTime > ins->mTotalTime) + walk = &(*walk)->mNextSibling; + ins->mNextSibling = *walk; + *walk = ins; + } + data->mFirstChild = list; + while(list) + { + if(list->mInvokeCount) + profilerDataDumpRecurseFile(list, buffer, bufferLen + 2, totalTime, fws); + list = list->mNextSibling; + } + buffer[bufferLen] = 0; +} + +void Profiler::dump() +{ + bool enableSave = mEnabled; + mEnabled = false; + mStackDepth++; + // may have some profiled calls... gotta turn em off. + + Vector rootVector; + F64 totalTime = 0; + for(ProfilerRootData *walk = ProfilerRootData::sRootList; walk; walk = walk->mNextRoot) + { + totalTime += walk->mTotalTime - walk->mSubTime; + rootVector.push_back(walk); + } + dQsort((void *) &rootVector[0], rootVector.size(), sizeof(ProfilerRootData *), rootDataCompare); + + + if (mDumpToConsole == true) + { + Con::printf("Profiler Data Dump:"); + Con::printf("Ordered by non-sub total time -"); + Con::printf("%%NSTime %% Time Invoke # Name"); + for(U32 i = 0; i < rootVector.size(); i++) + { + Con::printf("%7.3f %7.3f %8d %s", + 100 * (rootVector[i]->mTotalTime - rootVector[i]->mSubTime) / totalTime, + 100 * rootVector[i]->mTotalTime / totalTime, + rootVector[i]->mTotalInvokeCount, + rootVector[i]->mName); + rootVector[i]->mTotalInvokeCount = 0; + rootVector[i]->mTotalTime = 0; + rootVector[i]->mSubTime = 0; + } + Con::printf(""); + Con::printf("Ordered by stack trace total time -"); + Con::printf("%% Time %% NSTime Invoke # Name"); + + mCurrentProfilerData->mTotalTime = endHighResolutionTimer(mCurrentProfilerData->mStartTime); + + char depthBuffer[MaxStackDepth * 2 + 1]; + depthBuffer[0] = 0; + profilerDataDumpRecurse(mCurrentProfilerData, depthBuffer, 0, totalTime); + mEnabled = enableSave; + mStackDepth--; + } + else if (mDumpToFile == true && mDumpFileName[0] != '\0') + { + FileStream fws; + bool success = fws.open(mDumpFileName, Torque::FS::File::Write); + AssertFatal(success, "Nuts! Cannot write profile dump to specified file!"); + char buffer[1024]; + + dStrcpy(buffer, "Profiler Data Dump:\n"); + fws.write(dStrlen(buffer), buffer); + dStrcpy(buffer, "Ordered by non-sub total time -\n"); + fws.write(dStrlen(buffer), buffer); + dStrcpy(buffer, "%%NSTime %% Time Invoke # Name\n"); + fws.write(dStrlen(buffer), buffer); + + for(U32 i = 0; i < rootVector.size(); i++) + { + dSprintf(buffer, 1023, "%7.3f %7.3f %8d %s\n", + 100 * (rootVector[i]->mTotalTime - rootVector[i]->mSubTime) / totalTime, + 100 * rootVector[i]->mTotalTime / totalTime, + rootVector[i]->mTotalInvokeCount, + rootVector[i]->mName); + fws.write(dStrlen(buffer), buffer); + + rootVector[i]->mTotalInvokeCount = 0; + rootVector[i]->mTotalTime = 0; + rootVector[i]->mSubTime = 0; + } + dStrcpy(buffer, "\nOrdered by non-sub total time -\n"); + fws.write(dStrlen(buffer), buffer); + dStrcpy(buffer, "%%NSTime %% Time Invoke # Name\n"); + fws.write(dStrlen(buffer), buffer); + + mCurrentProfilerData->mTotalTime = endHighResolutionTimer(mCurrentProfilerData->mStartTime); + + char depthBuffer[MaxStackDepth * 2 + 1]; + depthBuffer[0] = 0; + profilerDataDumpRecurseFile(mCurrentProfilerData, depthBuffer, 0, totalTime, fws); + mEnabled = enableSave; + mStackDepth--; + + fws.close(); + } + + mDumpToConsole = false; + mDumpToFile = false; + mDumpFileName[0] = '\0'; +} + +void Profiler::enableMarker(const char *marker, bool enable) +{ + reset(); + U32 markerLen = dStrlen(marker); + if(markerLen == 0) + return; + bool sn = marker[markerLen - 1] == '*'; + for(ProfilerRootData *data = ProfilerRootData::sRootList; data; data = data->mNextRoot) + { + if(sn) + { + if(!dStrncmp(marker, data->mName, markerLen - 1)) + data->mEnabled = enable; + } + else + { + if(!dStrcmp(marker, data->mName)) + data->mEnabled = enable; + } + } +} + +ConsoleFunctionGroupBegin( Profiler, "Profiler functionality."); + +ConsoleFunction(profilerMarkerEnable, void, 3, 3, "(string markerName, bool enable)") +{ + TORQUE_UNUSED(argc); + if(gProfiler) + gProfiler->enableMarker(argv[1], dAtob(argv[2])); +} + +ConsoleFunction(profilerEnable, void, 2, 2, "(bool enable);") +{ + TORQUE_UNUSED(argc); + if(gProfiler) + gProfiler->enable(dAtob(argv[1])); +} + +ConsoleFunction(profilerDump, void, 1, 1, "Dump the current state of the profiler.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + if(gProfiler) + gProfiler->dumpToConsole(); +} + +ConsoleFunction(profilerDumpToFile, void, 2, 2, "(string filename) Dump profiling stats to a file.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + if(gProfiler) + gProfiler->dumpToFile(argv[1]); +} + +ConsoleFunction(profilerReset, void, 1, 1, "Resets the profiler, clearing all of its data.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + if(gProfiler) + gProfiler->reset(); +} + +ConsoleFunctionGroupEnd( Profiler ); + +#endif diff --git a/platform/profiler.h b/platform/profiler.h new file mode 100644 index 0000000..3df3e8b --- /dev/null +++ b/platform/profiler.h @@ -0,0 +1,177 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROFILER_H_ +#define _PROFILER_H_ + +#ifndef _TORQUECONFIG_H_ +#include "torqueConfig.h" +#endif + +#ifdef TORQUE_ENABLE_PROFILER + +struct ProfilerData; +struct ProfilerRootData; +/// The Profiler is used to see how long a specific chunk of code takes to execute. +/// All values outputted by the profiler are percentages of the time that it takes +/// to run entire main loop. +/// +/// First, you must #define TORQUE_ENABLE_PROFILER in profiler.h in order to +/// active it. Examples of script use: +/// @code +/// //enables or disables profiling. Data is only gathered when the profiler is enabled. +/// profilerEnable(bool enable); +/// profilerReset(); //resets the data gathered by the profiler +/// profilerDump(); //dumps all profiler data to the console +/// profilerDumpToFile(string filename); //dumps all profiler data to a given file +/// profilerMarkerEnable((string markerName, bool enable); //enables or disables a given profile tag +/// @endcode +/// +/// The C++ code side of the profiler uses pairs of PROFILE_START() and PROFILE_END(). +/// +/// When using these macros, make sure there is a PROFILE_END() for every PROFILE_START +/// and a PROFILE_START() for every PROFILE_END(). It is fine to nest these macros, however, +/// you must make sure that no matter what execution path the code takes, the PROFILE macros +/// will be balanced. +/// +/// The profiler can be used to locate areas of code that are slow or should be considered for +/// optimization. Since it tracks the relative time of execution of that code to the execution +/// of the main loop, it is possible to benchmark any given code to see if changes made will +/// actually improve performance. +/// +/// Here are some examples: +/// @code +/// PROFILE_START(TerrainRender); +/// //some code here +/// PROFILE_START(TerrainRenderGridSquare); +/// //some code here +/// PROFILE_END(); +/// //possibly some code here +/// PROFILE_END(); +/// @endcode +class Profiler +{ + enum { + MaxStackDepth = 256, + DumpFileNameLength = 256 + }; + U32 mCurrentHash; + + ProfilerData *mCurrentProfilerData; + ProfilerData *mProfileList; + ProfilerData *mRootProfilerData; + + bool mEnabled; + S32 mStackDepth; + bool mNextEnable; + U32 mMaxStackDepth; + bool mDumpToConsole; + bool mDumpToFile; + char mDumpFileName[DumpFileNameLength]; + void dump(); + void validate(); +public: + Profiler(); + ~Profiler(); + + /// Reset the data in the profiler + void reset(); + /// Dumps the profile to console + void dumpToConsole(); + /// Dumps the profile data to a file + /// @param fileName filename to dump data to + void dumpToFile(const char *fileName); + /// Enable profiling + void enable(bool enabled); + bool isEnabled() { return mNextEnable; } + /// Helper function for macro definition PROFILE_START + void hashPush(ProfilerRootData *data); + /// Helper function for macro definition PROFILE_END + void hashPop(ProfilerRootData *expected=NULL); + /// Enable a profiler marker + void enableMarker(const char *marker, bool enabled); +#ifdef TORQUE_ENABLE_PROFILE_PATH + /// Get current profile path + const char * getProfilePath(); + /// Construct profile path of given profiler data + const char * constructProfilePath(ProfilerData * pd); +#endif +}; + +extern Profiler *gProfiler; + +struct ProfilerRootData +{ + const char *mName; + U32 mNameHash; + ProfilerData *mFirstProfilerData; + ProfilerRootData *mNextRoot; + F64 mTotalTime; + F64 mSubTime; + U32 mTotalInvokeCount; + bool mEnabled; + + static ProfilerRootData *sRootList; + + ProfilerRootData(const char *name); +}; + +struct ProfilerData +{ + ProfilerRootData *mRoot; ///< link to root node. + ProfilerData *mNextForRoot; ///< links together all ProfilerData's for a particular root + ProfilerData *mNextProfilerData; ///< links all the profilerDatas + ProfilerData *mNextHash; + ProfilerData *mParent; + ProfilerData *mNextSibling; + ProfilerData *mFirstChild; + enum { + HashTableSize = 32, + }; + ProfilerData *mChildHash[HashTableSize]; + ProfilerData *mLastSeenProfiler; + + U32 mHash; + U32 mSubDepth; + U32 mInvokeCount; + U32 mStartTime[2]; + F64 mTotalTime; + F64 mSubTime; +#ifdef TORQUE_ENABLE_PROFILE_PATH + const char * mPath; +#endif +}; + + +#define PROFILE_START(name) \ +static ProfilerRootData pdata##name##obj (#name); \ +if(gProfiler) gProfiler->hashPush(& pdata##name##obj ) + +#define PROFILE_END() if(gProfiler) gProfiler->hashPop() + +#define PROFILE_END_NAMED(name) if(gProfiler) gProfiler->hashPop(& pdata##name##obj) + +class ScopedProfiler { +public: + ScopedProfiler(ProfilerRootData *data) { + if (gProfiler) gProfiler->hashPush(data); + } + ~ScopedProfiler() { + if (gProfiler) gProfiler->hashPop(); + } +}; + +#define PROFILE_SCOPE(name) \ + static ProfilerRootData pdata##name##obj (#name); \ + static ScopedProfiler scopedProfiler##name##obj(&pdata##name##obj); + +#else +#define PROFILE_START(x) +#define PROFILE_END() +#define PROFILE_SCOPE(x) +#define PROFILE_END_NAMED(x) +#endif + +#endif diff --git a/platform/test/testAlerts.cpp b/platform/test/testAlerts.cpp new file mode 100644 index 0000000..309f706 --- /dev/null +++ b/platform/test/testAlerts.cpp @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "unit/test.h" + +using namespace UnitTesting; + +CreateInteractiveTest(CheckPlatformAlerts, "Platform/Alerts") +{ + void run() + { + // Run through all the alert types. + Platform::AlertOK("Test #1 - AlertOK", "This is a test of Platform::AlertOK. I am a blocking dialog with an OK button. Please hit OK to continue."); + test(true, "AlertOK should return when the user clicks on it..."); // <-- gratuitous test point. + + bool res; + + res = Platform::AlertOKCancel("Test #2 - AlertOKCancel", "This is a test of Platform::alertOKCancel. I am a blocking dialog with an OK and a Cancel button. Please hit Cancel to continue."); + test(res==false,"AlertOKCancel - Didn't get cancel. User error, or just bad code?"); + + res = Platform::AlertOKCancel("Test #3 - AlertOKCancel", "This is a test of Platform::alertOKCancel. I am a blocking dialog with an OK and a Cancel button. Please hit OK to continue."); + test(res==true,"AlertOKCancel - Didn't get ok. User error, or just bad code?"); + + res = Platform::AlertRetry("Test #4 - AlertRetry", "This is a test of Platform::AlertRetry. I am a blocking dialog with an Retry and a Cancel button. Please hit Retry to continue."); + test(res==true,"AlertRetry - Didn't get retry. User error, or just bad code?"); + + res = Platform::AlertRetry("Test #5 - AlertRetry", "This is a test of Platform::AlertRetry. I am a blocking dialog with an Retry and a Cancel button. Please hit Cancel to continue."); + test(res==false,"AlertRetry - Didn't get cancel. User error, or just bad code?"); + } +}; \ No newline at end of file diff --git a/platform/test/testAsyncPacketQueue.cpp b/platform/test/testAsyncPacketQueue.cpp new file mode 100644 index 0000000..4895ebb --- /dev/null +++ b/platform/test/testAsyncPacketQueue.cpp @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "platform/async/asyncPacketQueue.h" +#include "console/console.h" +#include "core/util/tVector.h" + +#ifndef TORQUE_SHIPPING + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) + +CreateUnitTest( TestAsyncPacketQueue, "Platform/AsyncPacketQueue" ) +{ + struct Packet + { + typedef void Parent; + + StringChar mChar; + U32 mDuration; + S32 mWriteIndex; + U32 mWriteTime; + + Packet() {} + Packet( StringChar ch, U32 duration ) + : mChar( ch ), mDuration( duration ), mWriteIndex( -2 ), mWriteTime( 0 ) {} + }; + + struct TimeSource + { + typedef void Parent; + + U32 mStartTime; + + TimeSource() + : mStartTime( Platform::getRealMilliseconds() ) {} + + U32 getPosition() + { + return ( Platform::getRealMilliseconds() - mStartTime ); + } + }; + + struct Consumer : public IOutputStream< Packet* > + { + typedef IOutputStream< Packet* > Parent; + + U32 mIndex; + + Consumer() + : mIndex( 0 ) {} + + virtual void write( Packet* const* packets, U32 num ) + { + for( U32 i = 0; i < num; ++ i ) + { + Packet* p = packets[ i ]; + + Con::printf( "%c", p->mChar ); + + p->mWriteTime = Platform::getRealMilliseconds(); + p->mWriteIndex = mIndex; + mIndex ++; + } + } + }; + + void test1( bool dropPackets, U32 queueLength ) + { + F32 factor = Con::getFloatVariable( "$testAsyncPacketQueue::timeFactor", 100.0f ); + String str = Con::getVariable( "$testAsyncPacketQueue::string" ); + if( str.isEmpty() ) + str = "This is a test string"; + + Vector< Packet > packets; + for( U32 i = 0; i < str.size(); ++ i ) + packets.push_back( Packet( str[ i ], U32( Platform::getRandom() * factor ) ) ); + + U32 totalTime = 0; + for( U32 i = 0; i < packets.size(); ++ i ) + totalTime += packets[ i ].mDuration; + + TimeSource timeSource; + Consumer consumer; + AsyncPacketQueue< Packet*, TimeSource* > queue( queueLength, &timeSource, &consumer, totalTime, dropPackets ); + + U32 index = 0; + while( !queue.isAtEnd() ) + { + if( queue.needPacket() + && index < packets.size() ) + { + + Packet* packet = &packets[ index ]; + index ++; + + queue.submitPacket( packet, packet->mDuration ); + } + } + + U32 time = timeSource.mStartTime; + S32 lastIndex = -1; + for( U32 i = 0; i < packets.size(); ++ i ) + { + TEST( ( packets[ i ].mWriteIndex == -2 && dropPackets ) // not written = dropped + || packets[ i ].mWriteIndex == lastIndex + 1 ); + + if( packets[ i ].mWriteIndex != -2 ) + lastIndex ++; + + if( queueLength == 1 ) + TEST( packets[ i ].mWriteTime >= time || dropPackets ); // start time okay + + time += packets[ i ].mDuration; + if( dropPackets ) + TEST( packets[ i ].mWriteTime < time ); // end time okay (if not dropping) + } + } + + void run() + { + test1( false, 1 ); + test1( true, 1 ); + + test1( false, 4 ); + test1( true, 4 ); + } +}; + +#endif // !TORQUE_SHIPPING diff --git a/platform/test/testBasicTypes.cpp b/platform/test/testBasicTypes.cpp new file mode 100644 index 0000000..7cf280a --- /dev/null +++ b/platform/test/testBasicTypes.cpp @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/util/endian.h" +#include "unit/test.h" + +using namespace UnitTesting; + +CreateUnitTest(CheckTypeSizes, "Platform/Types/Sizes") +{ + void run() + { + // Run through all the types and ensure they're the right size. + +#define CheckType(typeName, expectedSize) \ + test( sizeof(typeName) == expectedSize, "Wrong size for a " #typeName ", expected " #expectedSize); + + // One byte types. + CheckType(bool, 1); + CheckType(U8, 1); + CheckType(S8, 1); + CheckType(UTF8, 1); + + // Two byte types. + CheckType(U16, 2); + CheckType(S16, 2); + CheckType(UTF16, 2); + + // Four byte types. + CheckType(U32, 4); + CheckType(S32, 4); + CheckType(F32, 4); + CheckType(UTF32, 4); + + // Eight byte types. + CheckType(U64, 8); + CheckType(S64, 8); + CheckType(F64, 8); + + // 16 byte (128bit) types will go here, when we get some. +#undef CheckType + } +}; + +CreateUnitTest(CheckEndianConversion, "Platform/Types/EndianRoundTrip") +{ + void run() + { + // Convenient and non-palindrome byte patterns to test with. + const U16 U16Test = 0xA1B2; + const S16 S16Test = 0x52A1; + + const U32 U32Test = 0xA1B2C3D4; + const S32 S32Test = 0xD4C3B2A1; + const F32 F32Test = 1234.5678f; + + //const U64 U64Test = 0xA1B2C3D4E3F2E10A; + //const S64 S64Test = 0x1A2B3C4D3E2F1EA0; + const F64 F64Test = 12345678.9101112131415; + + // Run through all the conversions - bump stuff from host to little or big + // endian and back again. +#define CheckEndianRoundTrip(type, b_or_l) \ + test( type##Test == convert##b_or_l##EndianToHost(convertHostTo##b_or_l##Endian(type##Test)), "Failed to convert the " #type " test value to " #b_or_l " endian and back to host endian order."); + +#define CheckTypeBothWays(type) \ + CheckEndianRoundTrip(type, B); \ + CheckEndianRoundTrip(type, L); + +#define CheckIntsForBitSize(bits) \ + CheckTypeBothWays( U##bits ); \ + CheckTypeBothWays( S##bits ); + + // Don't check 8-bit types - they aren't affected by endian issues. + + // Check the >1 byte int types, though. + CheckIntsForBitSize(16); + CheckIntsForBitSize(32); + // CheckIntsForBitSize(64); // don't have convertHostToLEndian(const U64/S64) so this doesn't work + + // And check the float types. + CheckTypeBothWays(F32); + CheckTypeBothWays(F64); + + // We'd check 128bit types here, if we had any. + +#undef CheckIntsForBitSize +#undef CheckTypeBothWays +#undef CheckEndianRoundTrip + } +}; + +CreateUnitTest(CheckEndianSwap, "Platform/Types/EndianSwap") +{ + void run() + { + U32 swap32 = 0xABCDEF12; + U16 swap16 = 0xABCD; + + test(endianSwap(swap32) == 0x12EFCDAB, "32 bit endianSwap should reverse byte order, but didn't."); + test(endianSwap(swap16) == 0xCDAB, "16 bit endianSwap should reverse byte order, but didn't."); + } +}; + diff --git a/platform/test/testFile.cpp b/platform/test/testFile.cpp new file mode 100644 index 0000000..4a90a88 --- /dev/null +++ b/platform/test/testFile.cpp @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/fileio.h" +#include "unit/test.h" +#include "core/util/tVector.h" +#include "console/console.h" + +using namespace UnitTesting; + +CreateUnitTest(CheckFileListingAndExclusion, "File/ListDirectoryAndExclusions") +{ + void run() + { + // Just dump everything under the current directory. We should + // find at least one file. + + // Exclude .svn and CVS + Platform::clearExcludedDirectories(); + Platform::addExcludedDirectory(".svn"); + Platform::addExcludedDirectory("CVS"); + + test(Platform::isExcludedDirectory("foo") == false, "Doesn't match list, shouldn't be excluded."); + test(Platform::isExcludedDirectory(".svn") == true, "On list, should be excluded."); + test(Platform::isExcludedDirectory("CVS") == true, "On list, should be excluded."); + test(Platform::isExcludedDirectory(".svnCVS") == false, "Looks like a duck, but it shouldn't be excluded cuz it's distinct from all entries on the exclusion list."); + + // Ok, now our exclusion list is setup, so let's dump some paths. + Vector < Platform::FileInfo > pathInfo; + Platform::dumpPath (Platform::getCurrentDirectory(), pathInfo, 2); + + Con::printf("Dump of files in '%s', up to 2 levels deep...", Platform::getCurrentDirectory()); + for(S32 i=0; i 0, "Should find at least SOMETHING in the current directory!"); + + // This'll nuke info if we run it in a live situation... so don't run unit + // tests in a live situation. ;) + Platform::clearExcludedDirectories(); + } +}; + +CreateUnitTest(CheckFileTouchAndTime, "File/TouchAndTime") +{ + void run() + { + FileTime create[2], modify[2]; + + // Create a file and sleep for a second. + File f; + f.open("testTouch.file", File::WriteAppend); + f.close(); + + Platform::sleep(2000); + + // Touch a file and note its last-modified. + dFileTouch("testTouch.file"); + test(Platform::isFile("testTouch.file"), "We just touched this file - it should exist."); + test(Platform::getFileTimes("testTouch.file", &create[0], &modify[0]), "Failed to get filetimes for a file we just created."); + + // Sleep for a few seconds... + Platform::sleep(5000); + + // Touch it again, and compare the last-modifieds. + test(Platform::isFile("testTouch.file"), "We just touched this file - it should exist."); + dFileTouch("testTouch.file"); + test(Platform::isFile("testTouch.file"), "We just touched this file - it should exist."); + test(Platform::getFileTimes("testTouch.file", &create[1], &modify[1]), "Failed to get filetimes for a file we just created."); + + // Now compare the times... + test(Platform::compareFileTimes(modify[0], modify[1]) < 0, "Timestamps are wrong - modify[0] should be before modify[1]!"); + + // This seems to fail even on a valid case... + // test(Platform::compareFileTimes(create[0], create[1]) == 0, "Create timestamps should match - we didn't delete the file during this test."); + + // Clean up.. + dFileDelete("testTouch.file"); + test(!Platform::isFile("testTouch.file"), "Somehow failed to delete our test file."); + } +}; + +// Mac has no implementations for these functions, so we 'def it out for now. +#if 0 +CreateUnitTest(CheckVolumes, "File/Volumes") +{ + void run() + { + Con::printf("Dumping volumes by name:"); + + Vector names; + Platform::getVolumeNamesList(names); + + test(names.size() > 0, "We should have at least one volume..."); + + for(S32 i=0; i info; + Platform::getVolumeInformationList(info); + + test(names.size() == info.size(), "Got inconsistent number of volumes back from info vs. name list functions!"); + + for(S32 i=0; i 0, "Didn't get any data back!"); + } +}; + +CreateUnitTest( TestTCPRequestJournal, "Platform/Net/JournalTCPRequest") +{ + NetSocket mSocket; + S32 mDataRecved; + + void handleNotify(NetSocket sock, U32 state) + { + // Only consider our own socket. + if(mSocket != sock) + return; + + // Ok - what's the state? We do some dumb responses to given states + // in order to fulfill the request. + if(state == Net::Connected) + { + U8 reqBuffer[] = { + "GET / HTTP/1.0\nUser-Agent: Torque/1.0\n\n" + }; + + Net::Error e = Net::sendtoSocket(mSocket, reqBuffer, sizeof(reqBuffer)); + + test(e == Net::NoError, "Got an error sending our HTTP request!"); + } + else if(state == Net::Disconnected) + { + Process::requestShutdown(); + mSocket = NULL; + } + } + + void handleReceive(NetSocket sock, RawData incomingData) + { + // Only consider our own socket. + if(mSocket != sock) + return; + + char buff[4096]; + dMemcpy(buff, incomingData.data, incomingData.size); + buff[incomingData.size] = 0; + + UnitPrint("Got a message...\n"); + UnitPrint(buff); + UnitPrint("------\n"); + + mDataRecved += incomingData.size; + } + + void makeRequest() + { + mSocket = InvalidSocket; + mDataRecved = 0; + + // Initialize networking - done by initLibraries currently + //test(Net::init(), "Failed to initialize networking!"); + + // Hook into the signals. + Net::smConnectionNotify. notify(this, &TestTCPRequestJournal::handleNotify); + Net::smConnectionReceive.notify(this, &TestTCPRequestJournal::handleReceive); + + // Open a TCP connection to garagegames.com + mSocket = Net::openConnectTo("ip:72.246.107.193:80"); + + // Let the callbacks enable things to process. + while(Process::processEvents()) + ; + + // Unhook from the signals. + Net::smConnectionNotify. remove(this, &TestTCPRequestJournal::handleNotify); + Net::smConnectionReceive.remove(this, &TestTCPRequestJournal::handleReceive); + + test(mDataRecved > 0, "Didn't get any data back!"); + } + + void run() + { + Journal::Record("journalTCP.jrn"); + + makeRequest(); + + S32 bytesRead = mDataRecved; + + Journal::Stop(); + + Journal::Play("journalTCP.jrn"); + + makeRequest(); + + Journal::Stop(); + + test(bytesRead == mDataRecved, "Didn't get same data back from journal playback."); + + } +}; \ No newline at end of file diff --git a/platform/test/testThreadPool.cpp b/platform/test/testThreadPool.cpp new file mode 100644 index 0000000..0e95021 --- /dev/null +++ b/platform/test/testThreadPool.cpp @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "platform/threads/threadPool.h" +#include "console/console.h" +#include "core/util/tVector.h" + +#ifndef TORQUE_SHIPPING + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) + +// Simple test that creates and verifies an array of numbers using +// thread pool work items. + +CreateUnitTest( TestThreadPool, "Platform/ThreadPool/Simple" ) +{ + enum { DEFAULT_NUM_ITEMS = 4000 }; + + static Vector< U32 > results; + + struct TestItem : public ThreadPool::WorkItem + { + typedef ThreadPool::WorkItem Parent; + + U32 mIndex; + + TestItem( U32 index ) + : mIndex( index ) {} + + protected: + virtual void execute() + { + results[ mIndex ] = mIndex; + } + }; + + void run() + { + U32 numItems = Con::getIntVariable( "$testThreadPool::numValues", DEFAULT_NUM_ITEMS ); + ThreadPool* pool = &ThreadPool::GLOBAL(); + results.setSize( numItems ); + + for( U32 i = 0; i < numItems; ++ i ) + results[ i ] = U32( -1 ); + + for( U32 i = 0; i < numItems; ++ i ) + { + ThreadSafeRef< TestItem > item( new TestItem( i ) ); + pool->queueWorkItem( item ); + } + + pool->flushWorkItems(); + + for( U32 i = 0; i < numItems; ++ i ) + test( results[ i ] == i, "result mismatch" ); + + results.clear(); + } +}; + +Vector< U32 > TestThreadPool::results( __FILE__, __LINE__ ); + +#endif // !TORQUE_SHIPPING diff --git a/platform/test/testThreadSafeDeque.cpp b/platform/test/testThreadSafeDeque.cpp new file mode 100644 index 0000000..4d85e83 --- /dev/null +++ b/platform/test/testThreadSafeDeque.cpp @@ -0,0 +1,386 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "platform/threads/threadSafeDeque.h" +#include "platform/threads/thread.h" +#include "core/util/tVector.h" +#include "console/console.h" + + +#ifndef TORQUE_SHIPPING + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) +#define XTEST( t, x ) t->test( ( x ), "FAIL: " #x ) + + +// Test deque without concurrency. + +CreateUnitTest( TestThreadSafeDequeSerial, "Platform/ThreadSafeDeque/Serial" ) +{ + void test1() + { + ThreadSafeDeque< char > deque; + String str = "teststring"; + + for( U32 i = 0; i < str.length(); ++ i ) + deque.pushBack( str[ i ] ); + + TEST( !deque.isEmpty() ); + + for( U32 i = 0; i < str.length(); ++ i ) + { + char ch; + TEST( deque.tryPopFront( ch ) && ch == str[ i ] ); + } + } + + void test2() + { + ThreadSafeDeque< char > deque; + String str = "teststring"; + + const char* p1 = str.c_str() + 4; + const char* p2 = p1 + 1; + while( *p2 ) + { + deque.pushFront( *p1 ); + deque.pushBack( *p2 ); + + -- p1; + ++ p2; + } + +#ifdef TORQUE_DEBUG + deque.dumpDebug(); +#endif + + for( U32 i = 0; i < str.length(); ++ i ) + { + char ch; + TEST( deque.tryPopFront( ch ) && ch == str[ i ] ); + } + } + + void test3() + { + ThreadSafeDeque< char > deque; + String str = "teststring"; + + const char* p1 = str.c_str() + 4; + const char* p2 = p1 + 1; + while( *p2 ) + { + deque.pushFront( *p1 ); + deque.pushBack( *p2 ); + + -- p1; + ++ p2; + } + +#ifdef TORQUE_DEBUG + deque.dumpDebug(); +#endif + + for( S32 i = ( str.length() - 1 ); i >= 0; -- i ) + { + char ch; + TEST( deque.tryPopBack( ch ) && ch == str[ i ] ); + } + } + + void test4() + { + ThreadSafeDeque< char > deque; + char ch; + + TEST( deque.isEmpty() ); + + deque.pushFront( 'a' ); + TEST( !deque.isEmpty() ); + TEST( deque.tryPopFront( ch ) ); + TEST( ch == 'a' ); + + deque.pushBack( 'a' ); + TEST( !deque.isEmpty() ); + TEST( deque.tryPopFront( ch ) ); + TEST( ch == 'a' ); + + deque.pushBack( 'a' ); + TEST( !deque.isEmpty() ); + TEST( deque.tryPopBack( ch ) ); + TEST( ch == 'a' ); + + deque.pushFront( 'a' ); + TEST( !deque.isEmpty() ); + TEST( deque.tryPopBack( ch ) ); + TEST( ch == 'a' ); + } + + void run() + { + test1(); + test2(); + test3(); + test4(); + } +}; + +// Test deque in a concurrent setting. + +CreateUnitTest( TestThreadSafeDequeConcurrentSimple, "Platform/ThreadSafeDeque/ConcurrentSimple" ) +{ +public: + typedef TestThreadSafeDequeConcurrentSimple TestType; + + enum + { + DEFAULT_NUM_VALUES = 100000, + }; + + struct Value : public ThreadSafeRefCount< Value > + { + U32 mIndex; + U32 mTick; + + Value() {} + Value( U32 index, U32 tick ) + : mIndex( index ), mTick( tick ) {} + }; + + typedef ThreadSafeRef< Value > ValueRef; + + struct Deque : public ThreadSafeDeque< ValueRef > + { + typedef ThreadSafeDeque Parent; + + U32 mPushIndex; + U32 mPopIndex; + + Deque() + : mPushIndex( 0 ), mPopIndex( 0 ) {} + + void pushBack( const ValueRef& value ) + { + AssertFatal( value->mIndex == mPushIndex, "index out of line" ); + mPushIndex ++; + Parent::pushBack( value ); + } + bool tryPopFront( ValueRef& outValue ) + { + if( Parent::tryPopFront( outValue ) ) + { + AssertFatal( outValue->mIndex == mPopIndex, "index out of line" ); + mPopIndex ++; + return true; + } + else + return false; + } + }; + + Deque mDeque; + Vector< U32 > mValues; + + struct ProducerThread : public Thread + { + ProducerThread( TestType* test ) + : Thread( 0, test ) {} + + virtual void run( void* arg ) + { + _setName( "ProducerThread" ); + Platform::outputDebugString( "Starting ProducerThread" ); + + TestType* test = ( TestType* ) arg; + + for( U32 i = 0; i < test->mValues.size(); ++ i ) + { + U32 tick = Platform::getRealMilliseconds(); + test->mValues[ i ] = tick; + + ValueRef val = new Value( i, tick ); + test->mDeque.pushBack( val ); + } + Platform::outputDebugString( "Stopping ProducerThread" ); + } + }; + struct ConsumerThread : public Thread + { + ConsumerThread( TestType* test ) + : Thread( 0, test ) {} + + virtual void run( void* arg ) + { + _setName( "ConsumerThread" ); + Platform::outputDebugString( "Starting CosumerThread" ); + TestType* t = ( TestType* ) arg; + + for( U32 i = 0; i < t->mValues.size(); ++ i ) + { + ValueRef value; + while( !t->mDeque.tryPopFront( value ) ); + + XTEST( t, value->mIndex == i ); + XTEST( t, t->mValues[ i ] == value->mTick ); + } + Platform::outputDebugString( "Stopping ConsumerThread" ); + } + }; + + void run() + { + U32 numValues = Con::getIntVariable( "$testThreadSafeDeque::numValues", DEFAULT_NUM_VALUES ); + mValues.setSize( numValues ); + + ProducerThread pThread( this ); + ConsumerThread cThread( this ); + + pThread.start(); + cThread.start(); + + pThread.join(); + cThread.join(); + + mValues.clear(); + } +}; + +CreateUnitTest( TestThreadSafeDequeConcurrent, "Platform/ThreadSafeDeque/Concurrent" ) +{ +public: + typedef TestThreadSafeDequeConcurrent TestType; + + enum + { + DEFAULT_NUM_VALUES = 100000, + DEFAULT_NUM_CONSUMERS = 10, + DEFAULT_NUM_PRODUCERS = 10 + }; + + struct Value : public ThreadSafeRefCount< Value > + { + U32 mIndex; + U32 mTick; + + Value() {} + Value( U32 index, U32 tick ) + : mIndex( index ), mTick( tick ) {} + }; + + typedef ThreadSafeRef< Value > ValueRef; + + U32 mProducerIndex; + U32 mConsumerIndex; + ThreadSafeDeque< ValueRef > mDeque; + Vector< U32 > mValues; + + struct ProducerThread : public Thread + { + ProducerThread( TestType* test ) + : Thread( 0, test ) {} + + virtual void run( void* arg ) + { + _setName( "ProducerThread" ); + Platform::outputDebugString( "Starting ProducerThread" ); + TestType* test = ( TestType* ) arg; + + while( 1 ) + { + U32 index = test->mProducerIndex; + if( index == test->mValues.size() ) + break; + + if( dCompareAndSwap( test->mProducerIndex, index, index + 1 ) ) + { + U32 tick = Platform::getRealMilliseconds(); + test->mValues[ index ] = tick; + + ValueRef val = new Value( index, tick ); + test->mDeque.pushBack( val ); + } + } + Platform::outputDebugString( "Stopping ProducerThread" ); + } + }; + struct ConsumerThread : public Thread + { + ConsumerThread( TestType* test ) + : Thread( 0, test ) {} + + virtual void run( void* arg ) + { + _setName( "ConsumerThread" ); + Platform::outputDebugString( "Starting ConsumerThread" ); + TestType* t = ( TestType* ) arg; + + while( t->mConsumerIndex < t->mValues.size() ) + { + ValueRef value; + if( t->mDeque.tryPopFront( value ) ) + { + dFetchAndAdd( t->mConsumerIndex, 1 ); + XTEST( t, t->mValues[ value->mIndex ] == value->mTick ); + t->mValues[ value->mIndex ] = 0; + } + } + + Platform::outputDebugString( "Stopping ConsumerThread" ); + } + }; + + void run() + { + U32 numValues = Con::getIntVariable( "$testThreadSafeDeque::numValues", DEFAULT_NUM_VALUES ); + U32 numConsumers = Con::getIntVariable( "$testThreadSafeDeque::numConsumers", DEFAULT_NUM_CONSUMERS ); + U32 numProducers = Con::getIntVariable( "$testThreadSafeDeque::numProducers", DEFAULT_NUM_PRODUCERS ); + + mProducerIndex = 0; + mConsumerIndex = 0; + mValues.setSize( numValues ); + + U32 tick = Platform::getRealMilliseconds(); + for( U32 i = 0; i < numValues; ++ i ) + mValues[ i ] = tick; + + Vector< ProducerThread* > producers; + Vector< ConsumerThread* > consumers; + + producers.setSize( numProducers ); + consumers.setSize( numConsumers ); + + for( U32 i = 0; i < numProducers; ++ i ) + { + producers[ i ] = new ProducerThread( this ); + producers[ i ]->start(); + } + for( U32 i = 0; i < numConsumers; ++ i ) + { + consumers[ i ] = new ConsumerThread( this ); + consumers[ i ]->start(); + } + + for( U32 i = 0; i < numProducers; ++ i ) + { + producers[ i ]->join(); + delete producers[ i ]; + } + for( U32 i = 0; i < numConsumers; ++ i ) + { + consumers[ i ]->join(); + delete consumers[ i ]; + } + + for( U32 i = 0; i < mValues.size(); ++ i ) + TEST( mValues[ i ] == 0 ); + + mValues.clear(); + } +}; + +#endif // !TORQUE_SHIPPING diff --git a/platform/test/testThreadSafePriorityQueue.cpp b/platform/test/testThreadSafePriorityQueue.cpp new file mode 100644 index 0000000..f25b842 --- /dev/null +++ b/platform/test/testThreadSafePriorityQueue.cpp @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "platform/threads/threadSafePriorityQueue.h" +#include "platform/threads/thread.h" +#include "core/util/tVector.h" +#include "console/console.h" + + +#ifndef TORQUE_SHIPPING + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) +#define XTEST( t, x ) t->test( ( x ), "FAIL: " #x ) + + +// Test queue without concurrency. + +CreateUnitTest( TestThreadSafePriorityQueueSerial, "Platform/ThreadSafePriorityQueue/Serial" ) +{ + struct Value + { + F32 mPriority; + U32 mIndex; + + Value() {} + Value( F32 priority, U32 index ) + : mPriority( priority ), mIndex( index ) {} + }; + + template< bool SORT_MIN_TO_MAX > + void test1() + { + Vector< Value > values; + + values.push_back( Value( 0.2f, 2 ) ); + values.push_back( Value( 0.7f, 7 ) ); + values.push_back( Value( 0.4f, 4 ) ); + values.push_back( Value( 0.6f, 6 ) ); + values.push_back( Value( 0.1f, 1 ) ); + values.push_back( Value( 0.5f, 5 ) ); + values.push_back( Value( 0.3f, 3 ) ); + values.push_back( Value( 0.8f, 8 ) ); + values.push_back( Value( 0.6f, 6 ) ); + values.push_back( Value( 0.9f, 9 ) ); + values.push_back( Value( 0.0f, 0 ) ); + + const S32 min = 0; + const S32 max = 9; + + ThreadSafePriorityQueue< U32, F32, SORT_MIN_TO_MAX > queue; + + for( U32 i = 0; i < values.size(); ++ i ) + queue.insert( values[ i ].mPriority, values[ i ].mIndex ); + + TEST( !queue.isEmpty() ); + + S32 index; + if( SORT_MIN_TO_MAX ) + index = min - 1; + else + index = max + 1; + + for( U32 i = 0; i < values.size(); ++ i ) + { + U32 value; + TEST( queue.takeNext( value ) ); + + if( value != index ) + { + if( SORT_MIN_TO_MAX ) + index ++; + else + index --; + } + + TEST( value == index ); + } + } + + void run() + { + test1< true >(); + test1< false >(); + } +}; + +// Test queue with concurrency. + +CreateUnitTest( TestThreadSafePriorityQueueConcurrent, "Platform/ThreadSafePriorityQueue/Concurrent" ) +{ +public: + typedef TestThreadSafePriorityQueueConcurrent TestType; + + enum + { + DEFAULT_NUM_VALUES = 100000, + DEFAULT_NUM_CONSUMERS = 10, + DEFAULT_NUM_PRODUCERS = 10 + }; + + struct Value : public ThreadSafeRefCount< Value > + { + U32 mIndex; + F32 mPriority; + bool mCheck; + + Value() : mCheck( false ) {} + Value( U32 index, F32 priority ) + : mIndex( index ), mPriority( priority ), mCheck( false ) {} + }; + + typedef ThreadSafeRef< Value > ValueRef; + + U32 mProducerIndex; + U32 mConsumerIndex; + ThreadSafePriorityQueue< ValueRef > mQueue; + Vector< ValueRef > mValues; + + struct ProducerThread : public Thread + { + ProducerThread( TestType* test ) + : Thread( 0, test ) {} + + virtual void run( void* arg ) + { + _setName( "ProducerThread" ); + Platform::outputDebugString( "Starting ProducerThread" ); + TestType* test = ( TestType* ) arg; + + while( 1 ) + { + U32 index = test->mProducerIndex; + if( index == test->mValues.size() ) + break; + + if( dCompareAndSwap( test->mProducerIndex, index, index + 1 ) ) + { + F32 priority = Platform::getRandom(); + ValueRef val = new Value( index, priority ); + test->mValues[ index ] = val; + test->mQueue.insert( priority, val ); + } + } + Platform::outputDebugString( "Stopping ProducerThread" ); + } + }; + struct ConsumerThread : public Thread + { + ConsumerThread( TestType* test ) + : Thread( 0, test ) {} + + virtual void run( void* arg ) + { + _setName( "ConsumerThread" ); + Platform::outputDebugString( "Starting ConsumerThread" ); + TestType* t = ( TestType* ) arg; + + while( t->mConsumerIndex < t->mValues.size() ) + { + ValueRef value; + if( t->mQueue.takeNext( value ) ) + { + dFetchAndAdd( t->mConsumerIndex, 1 ); + XTEST( t, t->mValues[ value->mIndex ] == value ); + value->mCheck = true; + } + else + Platform::sleep( 5 ); + } + Platform::outputDebugString( "Stopping ConsumerThread" ); + } + }; + + void run() + { + U32 numValues = Con::getIntVariable( "$testThreadSafePriorityQueue::numValues", DEFAULT_NUM_VALUES ); + U32 numConsumers = Con::getIntVariable( "$testThreadSafePriorityQueue::numConsumers", DEFAULT_NUM_CONSUMERS ); + U32 numProducers = Con::getIntVariable( "$testThreadSafePriorityQueue::numProducers", DEFAULT_NUM_PRODUCERS ); + + mProducerIndex = 0; + mConsumerIndex = 0; + mValues.setSize( numValues ); + + Vector< ProducerThread* > producers; + Vector< ConsumerThread* > consumers; + + producers.setSize( numProducers ); + consumers.setSize( numConsumers ); + + for( U32 i = 0; i < numProducers; ++ i ) + { + producers[ i ] = new ProducerThread( this ); + producers[ i ]->start(); + } + for( U32 i = 0; i < numConsumers; ++ i ) + { + consumers[ i ] = new ConsumerThread( this ); + consumers[ i ]->start(); + } + + for( U32 i = 0; i < numProducers; ++ i ) + { + producers[ i ]->join(); + delete producers[ i ]; + } + for( U32 i = 0; i < numConsumers; ++ i ) + { + consumers[ i ]->join(); + delete consumers[ i ]; + } + + for( U32 i = 0; i < mValues.size(); ++ i ) + { + TEST( mValues[ i ] != NULL ); + if( mValues[ i ] != NULL ) + TEST( mValues[ i ]->mCheck ); + } + + mValues.clear(); + } +}; + +#endif // !TORQUE_SHIPPING diff --git a/platform/test/testThreadSafeRefCount.cpp b/platform/test/testThreadSafeRefCount.cpp new file mode 100644 index 0000000..c33097d --- /dev/null +++ b/platform/test/testThreadSafeRefCount.cpp @@ -0,0 +1,210 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "unit/test.h" +#include "platform/threads/threadSafeRefCount.h" +#include "platform/threads/thread.h" +#include "core/util/tVector.h" +#include "console/console.h" + +#ifndef TORQUE_SHIPPING + +using namespace UnitTesting; + +#define TEST( x ) test( ( x ), "FAIL: " #x ) + +CreateUnitTest( TestThreadSafeRefCountSerial, "Platform/ThreadSafeRefCount/Serial" ) +{ + struct TestObject : public ThreadSafeRefCount< TestObject > + { + static bool smDeleted; + + TestObject() + { + smDeleted = false; + } + ~TestObject() + { + smDeleted = true; + } + }; + + typedef ThreadSafeRef< TestObject > TestObjectRef; + + void run() + { + TestObjectRef ref1 = new TestObject; + TEST( !ref1->isShared() ); + TEST( ref1 != NULL ); + + TestObjectRef ref2 = ref1; + TEST( ref1->isShared() ); + TEST( ref2->isShared() ); + TEST( ref1 == ref2 ); + + ref1 = NULL; + TEST( !ref2->isShared() ); + + ref2 = NULL; + TEST( TestObject::smDeleted ); + } +}; + +bool TestThreadSafeRefCountSerial::TestObject::smDeleted; + +CreateUnitTest( TestThreadSafeRefCountConcurrent, "Platform/ThreadSafeRefCount/Concurrent" ) +{ +public: + typedef TestThreadSafeRefCountConcurrent TestType; + enum + { + NUM_ADD_REFS_PER_THREAD = 1000, + NUM_EXTRA_REFS_PER_THREAD = 1000, + NUM_THREADS = 10 + }; + + class TestObject : public ThreadSafeRefCount< TestObject > + { + public: + }; + + ThreadSafeRef< TestObject > mRef; + + class TestThread : public Thread + { + public: + TestType* mTest; + Vector< ThreadSafeRef< TestObject > > mExtraRefs; + + TestThread( TestType* test ) + : mTest( test ) {} + + void run( void* arg ) + { + if( !arg ) + { + for( U32 i = 0; i < NUM_ADD_REFS_PER_THREAD; ++ i ) + mTest->mRef->addRef(); + + mExtraRefs.setSize( NUM_EXTRA_REFS_PER_THREAD ); + for( U32 i = 0; i < NUM_EXTRA_REFS_PER_THREAD; ++ i ) + mExtraRefs[ i ] = mTest->mRef; + } + else + { + mExtraRefs.clear(); + + for( U32 i = 0; i < NUM_ADD_REFS_PER_THREAD; ++ i ) + mTest->mRef->release(); + } + } + }; + + void run() + { + mRef = new TestObject; + TEST( mRef->getRefCount() == 2 ); // increments of 2 + + Vector< TestThread* > threads; + threads.setSize( NUM_THREADS ); + + // Create threads. + for( U32 i = 0; i < NUM_THREADS; ++ i ) + threads[ i ] = new TestThread( this ); + + // Run phase 1: create references. + for( U32 i = 0; i < NUM_THREADS; ++ i ) + threads[ i ]->start( NULL ); + + // Wait for completion. + for( U32 i = 0; i < NUM_THREADS; ++ i ) + threads[ i ]->join(); + + Con::printf( "REF: %i", mRef->getRefCount() ); + TEST( mRef->getRefCount() == 2 + ( ( NUM_ADD_REFS_PER_THREAD + NUM_EXTRA_REFS_PER_THREAD ) * NUM_THREADS * 2 ) ); + + // Run phase 2: release references. + for( U32 i = 0; i < NUM_THREADS; ++ i ) + threads[ i ]->start( ( void* ) 1 ); + + // Wait for completion. + for( U32 i = 0; i < NUM_THREADS; ++ i ) + { + threads[ i ]->join(); + delete threads[ i ]; + } + + TEST( mRef->getRefCount() == 2 ); // increments of two + + mRef = NULL; + } +}; + +CreateUnitTest( TestThreadSafeRefCountTagging, "Platform/ThreadSafeRefCount/Tagging" ) +{ + struct TestObject : public ThreadSafeRefCount< TestObject > {}; + + typedef ThreadSafeRef< TestObject > TestObjectRef; + + void run() + { + TestObjectRef ref; + + TEST( !ref.isTagged() ); + TEST( !ref ); + TEST( !ref.ptr() ); + + TEST( ref.trySetFromTo( ref, NULL ) ); + TEST( !ref.isTagged() ); + + TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Set ) ); + TEST( ref.isTagged() ); + TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Set ) ); + TEST( ref.isTagged() ); + + TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Unset ) ); + TEST( !ref.isTagged() ); + TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_Unset ) ); + TEST( !ref.isTagged() ); + + TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_SetOrFail ) ); + TEST( ref.isTagged() ); + TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_SetOrFail ) ); + TEST( ref.isTagged() ); + TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_FailIfSet ) ); + + TEST( ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_UnsetOrFail ) ); + TEST( !ref.isTagged() ); + TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_UnsetOrFail ) ); + TEST( !ref.isTagged() ); + TEST( !ref.trySetFromTo( ref, NULL, TestObjectRef::TAG_FailIfUnset ) ); + + TestObjectRef objectA = new TestObject; + TestObjectRef objectB = new TestObject; + + TEST( !objectA->isShared() ); + TEST( !objectB->isShared() ); + + ref = objectA; + TEST( !ref.isTagged() ); + TEST( ref == objectA ); + TEST( ref == objectA.ptr() ); + TEST( objectA->isShared() ); + + TEST( ref.trySetFromTo( objectA, objectB, TestObjectRef::TAG_Set ) ); + TEST( ref.isTagged() ); + TEST( ref == objectB ); + TEST( ref == objectB.ptr() ); + TEST( objectB->isShared() ); + TEST( !objectA->isShared() ); + + TEST( ref.trySetFromTo( ref, objectA ) ); + TEST( ref.isTagged() ); + TEST( ref == objectA ); + TEST( ref == objectA.ptr() ); + } +}; + +#endif // !TORQUE_SHIPPING diff --git a/platform/test/testThreading.cpp b/platform/test/testThreading.cpp new file mode 100644 index 0000000..47052fa --- /dev/null +++ b/platform/test/testThreading.cpp @@ -0,0 +1,400 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/threads/thread.h" +#include "platform/threads/semaphore.h" +#include "platform/threads/mutex.h" +#include "unit/test.h" +#include "core/util/tVector.h" +#include "console/console.h" + +using namespace UnitTesting; + +class ThreadTestHarness +{ + U32 mStartTime, mEndTime, mCleanupTime; + void (*mThreadBody)(void*); + S32 mThreadCount; + Thread **mThreads; + +public: + ThreadTestHarness() + { + mStartTime = mEndTime = mCleanupTime = 0; + mThreadBody = NULL; + mThreadCount = 1; + mThreads = NULL; + } + + void startThreads(void (*threadBody)(void*), void *arg, U32 threadCount) + { + mThreadCount = threadCount; + mThreadBody = threadBody; + + // Start up threadCount threads... + mThreads = new Thread*[threadCount]; + + mStartTime = Platform::getRealMilliseconds(); + + //Con::printf(" Running with %d threads...", threadCount); + for(S32 i=0; istart(); + } + } + + void waitForThreadExit(U32 checkFrequencyMs) + { + // And wait for them to complete. + bool someAlive = true; + S32 liveCount = mThreadCount; + + while(someAlive) + { + //Con::printf(" - Sleeping for %dms with %d live threads.", checkFrequencyMs, liveCount); + Platform::sleep(checkFrequencyMs); + + someAlive = false; + liveCount = 0; + + for(S32 i=0; iisAlive()) + continue; + + someAlive = true; + liveCount++; + } + + } + + mEndTime = Platform::getRealMilliseconds(); + + // Clean up memory at this point. + for(S32 i=0; iacquire(false), "Should succeed at acquiring a new semaphore with count 1."); + test(sem2->acquire(false), "This one should succeed too, see previous test."); + + // Test that we can do non-blocking acquires that fail. + test(sem1->acquire(false)==false, "Should failed, as we've already got the sem."); + sem1->release(); + test(sem2->acquire(false)==false, "Should also fail."); + sem2->release(); + + // Test that we can do blocking acquires that succeed. + test(sem1->acquire(true)==true, "Should succeed as we just released."); + test(sem2->acquire(true)==true, "Should succeed as we just released."); + + // Can't test blocking acquires that never happen... :) + + // Clean up. + delete sem1; + delete sem2; + } +}; + +CreateUnitTest( SemaphoreWaitTest, "Platform/Threads/SemaphoreWaitTest") +{ + static void threadBody(void *self) + { + SemaphoreWaitTest *me = (SemaphoreWaitTest*)self; + + // Wait for the semaphore to get released. + me->mSemaphore->acquire(); + + // Increment the counter. + Mutex::lockMutex(me->mMutex); + me->mDoneCount++; + Mutex::unlockMutex(me->mMutex); + + // Signal back to the main thread we're done. + me->mPostbackSemaphore->release(); + } + + Semaphore *mSemaphore; + Semaphore *mPostbackSemaphore; + void *mMutex; + U32 mDoneCount; + + const static int csmThreadCount = 10; + + void run() + { + ThreadTestHarness tth; + + mDoneCount = 0; + mSemaphore = new Semaphore(0); + mPostbackSemaphore = new Semaphore(0); + mMutex = Mutex::createMutex(); + + tth.startThreads(&threadBody, this, csmThreadCount); + + Platform::sleep(500); + + Mutex::lockMutex(mMutex); + test(mDoneCount == 0, "no threads should have touched the counter yet."); + Mutex::unlockMutex(mMutex); + + // Let 500 come out. + for(S32 i=0; irelease(); + + // And wait for 500 postbacks. + for(S32 i=0; iacquire(); + + Mutex::lockMutex(mMutex); + test(mDoneCount == csmThreadCount / 2, "Didn't get expected number of done threads! (a)"); + Mutex::unlockMutex(mMutex); + + // Ok, now do the rest. + // Let 500 come out. + for(S32 i=0; irelease(); + + // And wait for 500 postbacks. + for(S32 i=0; iacquire(); + + Mutex::lockMutex(mMutex); + test(mDoneCount == csmThreadCount, "Didn't get expected number of done threads! (b)"); + Mutex::unlockMutex(mMutex); + + // Wait for the threads to exit - shouldn't have to wait ever though. + tth.waitForThreadExit(10); + + // Make sure no one touched our data after shutdown time. + Mutex::lockMutex(mMutex); + test(mDoneCount == csmThreadCount, "Didn't get expected number of done threads! (c)"); + Mutex::unlockMutex(mMutex); + } +}; + +CreateUnitTest( MutexWaitTest, "Platform/Threads/MutexWaitTest") +{ + static void threadBody(void *self) + { + MutexWaitTest *me = (MutexWaitTest*)self; + + // Increment the counter. We'll block until the mutex + // is open. + Mutex::lockMutex(me->mMutex); + me->mDoneCount++; + Mutex::unlockMutex(me->mMutex); + } + + void *mMutex; + U32 mDoneCount; + + const static int csmThreadCount = 10; + + void run() + { + mMutex = Mutex::createMutex(); + mDoneCount = 0; + + // We lock the mutex before we create any threads, so that all the threads + // block on the mutex. Then we unlock it and let them all work their way + // through the increment. + Mutex::lockMutex(mMutex); + + ThreadTestHarness tth; + tth.startThreads(&threadBody, this, csmThreadCount); + + Platform::sleep(5000); + + // Check count is still zero. + test(mDoneCount == 0, "Uh oh - a thread somehow didn't get blocked by the locked mutex!"); + + // Open the flood gates... + Mutex::unlockMutex(mMutex); + + // Wait for the threads to all finish executing. + tth.waitForThreadExit(10); + + Mutex::lockMutex(mMutex); + test(mDoneCount == csmThreadCount, "Hmm - all threads reported done, but we didn't get the expected count."); + Mutex::unlockMutex(mMutex); + + // Kill the mutex. + Mutex::destroyMutex(mMutex); + } +}; \ No newline at end of file diff --git a/platform/test/testTimeManager.cpp b/platform/test/testTimeManager.cpp new file mode 100644 index 0000000..37de313 --- /dev/null +++ b/platform/test/testTimeManager.cpp @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformTimer.h" +#include "core/util/journal/journaledSignal.h" +#include "core/util/journal/process.h" +#include "math/mMath.h" +#include "console/console.h" + +#include "unit/test.h" +using namespace UnitTesting; + +CreateUnitTest(Check_advanceTime, "Platform/Time/advanceTime") +{ + void run() + { + U32 time = Platform::getVirtualMilliseconds(); + Platform::advanceTime(10); + U32 newTime = Platform::getVirtualMilliseconds(); + + test(newTime - time == 10, "Platform::advanceTime is borked, we advanced 10ms but didn't get a 10ms delta!"); + } +}; + +CreateUnitTest(Check_platformSleep, "Platform/Time/Sleep") +{ + const static S32 sleepTimeMs = 500; + void run() + { + U32 start = Platform::getRealMilliseconds(); + Platform::sleep(sleepTimeMs); + U32 end = Platform::getRealMilliseconds(); + + test(end - start >= sleepTimeMs, "We didn't sleep at least as long as we requested!"); + } +}; + +CreateUnitTest(Check_timeManager, "Platform/Time/Manager") +{ + void handleTimeEvent(S32 timeDelta) + { + mElapsedTime += timeDelta; + mNumberCalls++; + + if(mElapsedTime >= 1000) + Process::requestShutdown(); + } + + S32 mElapsedTime; + S32 mNumberCalls; + + void run() + { + mElapsedTime = mNumberCalls = 0; + + // Initialize the time manager... + TimeManager time; + time.timeEvent.notify(this, &Check_timeManager::handleTimeEvent); + + // Event loop till at least one second has passed. + const U32 start = Platform::getRealMilliseconds(); + + while(Process::processEvents()) + { + // If we go too long, kill it off... + if(Platform::getRealMilliseconds() - start > 30*1000) + { + test(false, "Terminated process loop due to watchdog, not due to time manager event, after 30 seconds."); + Process::requestShutdown(); + } + } + + const U32 end = Platform::getRealMilliseconds(); + + // Now, confirm we have approximately similar elapsed times. + S32 elapsedRealTime = end - start; + test(mAbs(elapsedRealTime - mElapsedTime) < 50, "Failed to elapse time to within the desired tolerance."); + + test(mNumberCalls > 0, "Somehow got no event callbacks from TimeManager?"); + + Con::printf(" Got %d time events, and elapsed %dms from TimeManager, " + "%dms according to Platform::getRealMilliseconds()", + mNumberCalls, mElapsedTime, elapsedRealTime); + } +}; \ No newline at end of file diff --git a/platform/threads/mutex.h b/platform/threads/mutex.h new file mode 100644 index 0000000..a28ead9 --- /dev/null +++ b/platform/threads/mutex.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/types.h" +#include "platform/platformAssert.h" + +#ifndef _PLATFORM_THREADS_MUTEX_H_ +#define _PLATFORM_THREADS_MUTEX_H_ + +// Forward ref used by platform code +struct PlatformMutexData; + +class Mutex +{ +protected: + PlatformMutexData *mData; + +public: + Mutex(); + virtual ~Mutex(); + + virtual bool lock(bool block = true); + virtual void unlock(); + + // Old API so that we don't have to change a load of code + static void *createMutex() + { + Mutex *mutex = new Mutex; + return (void *)mutex; + } + + static void destroyMutex(void *mutex) + { + Mutex *realMutex = reinterpret_cast(mutex); + delete realMutex; + } + + static bool lockMutex(void *mutex, bool block = true) + { + Mutex *realMutex = reinterpret_cast(mutex); + return realMutex->lock(block); + } + + static void unlockMutex(void *mutex) + { + Mutex *realMutex = reinterpret_cast(mutex); + realMutex->unlock(); + } +}; + +/// Helper for simplifying mutex locking code. +/// +/// This class will automatically unlock a mutex that you've +/// locked through it, saving you from managing a lot of complex +/// exit cases. For instance: +/// +/// @code +/// MutexHandle handle; +/// handle.lock(myMutex); +/// +/// if(error1) +/// return; // Auto-unlocked by handle if we leave here - normally would +/// // leave the mutex locked, causing much pain later. +/// +/// handle.unlock(); +/// @endcode +class MutexHandle +{ +private: + void *mMutexPtr; + +public: + MutexHandle() + : mMutexPtr(NULL) + { + } + + ~MutexHandle() + { + if(mMutexPtr) + unlock(); + } + + bool lock(void *mutex, bool blocking=false) + { + AssertFatal(!mMutexPtr, "MutexHandle::lock - shouldn't be locking things twice!"); + + bool ret = Mutex::lockMutex(mutex, blocking); + + if(ret) + { + // We succeeded, do book-keeping. + mMutexPtr = mutex; + } + + return ret; + } + + void unlock() + { + if(mMutexPtr) + { + Mutex::unlockMutex(mMutexPtr); + mMutexPtr = NULL; + } + } + +}; + +#endif // _PLATFORM_THREADS_MUTEX_H_ diff --git a/platform/threads/semaphore.h b/platform/threads/semaphore.h new file mode 100644 index 0000000..ae2e63a --- /dev/null +++ b/platform/threads/semaphore.h @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORM_THREAD_SEMAPHORE_H_ +#define _PLATFORM_THREAD_SEMAPHORE_H_ + +#ifndef _TORQUE_TYPES_H_ +#include "platform/types.h" +#endif + +// Forward ref used by platform code +class PlatformSemaphore; + +class Semaphore +{ +protected: + PlatformSemaphore *mData; + +public: + /// Create a semaphore. initialCount defaults to 1. + Semaphore(S32 initialCount = 1); + /// Delete a semaphore, ignoring it's count. + ~Semaphore(); + + /// Acquire the semaphore, decrementing its count. + /// if the initial count is less than 1, block until it goes above 1, then acquire. + /// Returns true if the semaphore was acquired, false if the semaphore could + /// not be acquired and block was false. + bool acquire(bool block = true); + + /// Release the semaphore, incrementing its count. + /// Never blocks. + void release(); +}; + +#endif diff --git a/platform/threads/thread.h b/platform/threads/thread.h new file mode 100644 index 0000000..4c15896 --- /dev/null +++ b/platform/threads/thread.h @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/types.h" +#include "core/util/tVector.h" +#include "platform/threads/mutex.h" + +#ifndef _PLATFORM_THREADS_THREAD_H_ +#define _PLATFORM_THREADS_THREAD_H_ + +// Forward ref used by platform code +class PlatformThreadData; + +// Typedefs +typedef void (*ThreadRunFunction)(void *data); + +class Thread +{ +public: + typedef void Parent; + +protected: + PlatformThreadData* mData; + + /// Used to signal threads need to stop. + /// Threads set this flag to false in start() + U32 shouldStop; + + /// Set the name of this thread for identification in debuggers. + /// Maybe a NOP on platforms that do not support this. Always a NOP + /// in non-debug builds. + void _setName( const char* name ); + +public: + /// If set, the thread will delete itself once it has finished running. + bool autoDelete; + + /// Create a thread. + /// @param func The starting function for the thread. + /// @param arg Data to be passed to func, when the thread starts. + /// @param start_thread Supported for compatibility. Must be false. Starting threads from + /// within the constructor is not allowed anymore as the run() method is virtual. + Thread(ThreadRunFunction func = 0, void *arg = 0, bool start_thread = false, bool autodelete = false); + + /// Destroy a thread. + /// The thread MUST be allowed to exit before it is destroyed. + virtual ~Thread(); + + /// Start a thread. + /// Sets shouldStop to false and calls run() in a new thread of execution. + void start( void* arg = 0 ); + + /// Ask a thread to stop running. + void stop() + { + shouldStop = true; + } + + /// Block until the thread stops running. + /// @note Don't use this in combination with auto-deletion as otherwise the thread will kill + /// itself while still executing the join() method on the waiting thread. + bool join(); + + /// Threads may call checkForStop() periodically to check if they've been + /// asked to stop. As soon as checkForStop() returns true, the thread should + /// clean up and return. + bool checkForStop() + { + return shouldStop; + } + + /// Run the Thread's entry point function. + /// Override this method in a subclass of Thread to create threaded code in + /// an object oriented way, and without passing a function ptr to Thread(). + /// Also, you can call this method directly to execute the thread's + /// code in a non-threaded way. + virtual void run(void *arg = 0); + + /// Returns true if the thread is running. + bool isAlive(); + + /// Returns the platform specific thread id for this thread. + U32 getId(); +}; + +class ThreadManager +{ + static ThreadManager* singleton() + { + static ThreadManager man;// = NULL; +// if(!man) man = new ThreadManager; +// AssertISV(man, "Thread manager doesn't exist."); + return &man; + } + + Vector threadPool; + Mutex poolLock; + + struct MainThreadId + { + U32 mId; + MainThreadId() + { + mId = ThreadManager::getCurrentThreadId(); + } + U32 get() + { + // Okay, this is a bit soso. The main thread ID may get queried during + // global ctor phase before MainThreadId's ctor ran. Since global + // ctors will/should all run on the main thread, we can sort of safely + // assume here that we can just query the current thread's ID. + + if( !mId ) + mId = ThreadManager::getCurrentThreadId(); + return mId; + } + }; + + static MainThreadId smMainThreadId; + +public: + ThreadManager() + { + VECTOR_SET_ASSOCIATION( threadPool ); + } + + /// Return true if the caller is running on the main thread. + static bool isMainThread(); + + /// Returns true if threadId is the same as the calling thread's id. + static bool isCurrentThread(U32 threadId); + + /// Returns true if the 2 thread ids represent the same thread. Some thread + /// APIs return an opaque object as a thread id, so the == operator cannot + /// reliably compare thread ids. + // this comparator is needed by pthreads and ThreadManager. + static bool compare(U32 threadId_1, U32 threadId_2); + + /// Returns the platform specific thread id of the calling thread. Some + /// platforms do not guarantee that this ID stays the same over the life of + /// the thread, so use ThreadManager::compare() to compare thread ids. + static U32 getCurrentThreadId(); + + /// Returns the platform specific thread id ot the main thread. + static U32 getMainThreadId() { return smMainThreadId.get(); } + + /// Each thread should add itself to the thread pool the first time it runs. + static void addThread(Thread* thread) + { + ThreadManager &manager = *singleton(); + manager.poolLock.lock(); + Thread *alreadyAdded = getThreadById(thread->getId()); + if(!alreadyAdded) + manager.threadPool.push_back(thread); + manager.poolLock.unlock(); + } + + static void removeThread(Thread* thread) + { + ThreadManager &manager = *singleton(); + manager.poolLock.lock(); + + U32 threadID = thread->getId(); + for(U32 i = 0;i < manager.threadPool.size();++i) + { + if(manager.threadPool[i]->getId() == threadID) + { + manager.threadPool.erase(i); + break; + } + } + + manager.poolLock.unlock(); + } + + /// Searches the pool of known threads for a thread whose id is equivalent to + /// the given threadid. Compares thread ids with ThreadManager::compare(). + static Thread* getThreadById(U32 threadid) + { + AssertFatal(threadid != 0, "ThreadManager::getThreadById() Searching for a bad thread id."); + Thread* ret = NULL; + + singleton()->poolLock.lock(); + Vector &pool = singleton()->threadPool; + for( S32 i = pool.size() - 1; i >= 0; i--) + { + Thread* p = pool[i]; + if(compare(p->getId(), threadid)) + { + ret = p; + break; + } + } + singleton()->poolLock.unlock(); + return ret; + } + + static Thread* getCurrentThread() + { + return getThreadById(ThreadManager::getCurrentThreadId()); + } +}; + +inline bool ThreadManager::isMainThread() +{ + return compare( ThreadManager::getCurrentThreadId(), smMainThreadId.get() ); +} + +inline bool ThreadManager::isCurrentThread(U32 threadId) +{ + U32 current = getCurrentThreadId(); + return compare(current, threadId); +} + +#endif // _PLATFORM_THREADS_THREAD_H_ diff --git a/platform/threads/threadPool.cpp b/platform/threads/threadPool.cpp new file mode 100644 index 0000000..1f198ab --- /dev/null +++ b/platform/threads/threadPool.cpp @@ -0,0 +1,451 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/threads/threadPool.h" +#include "platform/threads/thread.h" +#include "platform/platformCPUCount.h" +#include "core/strings/stringFunctions.h" + + +//#define DEBUG_SPEW + + +//============================================================================= +// ThreadPool::Context. +//============================================================================= + +ThreadPool::Context ThreadPool::Context::smRootContext( "ROOT", NULL, 1.0 ); + +//-------------------------------------------------------------------------- + +ThreadPool::Context::Context( const char* name, ThreadPool::Context* parent, F32 priorityBias ) + : mName( name ), + mParent( parent ), + mSibling( 0 ), + mChildren( 0 ), + mPriorityBias( priorityBias ), + mAccumulatedPriorityBias( 0.0 ) +{ + if( parent ) + { + mSibling = mParent->mChildren; + mParent->mChildren = this; + } +} + +//-------------------------------------------------------------------------- + +ThreadPool::Context::~Context() +{ + if( mParent ) + for( Context* context = mParent->mChildren, *prev = 0; context != 0; prev = context, context = context->mSibling ) + if( context == this ) + { + if( !prev ) + mParent->mChildren = this->mSibling; + else + prev->mSibling = this->mSibling; + } +} + +//-------------------------------------------------------------------------- + +ThreadPool::Context* ThreadPool::Context::getChild( const char* name ) +{ + for( Context* child = getChildren(); child != 0; child = child->getSibling() ) + if( dStricmp( child->getName(), name ) == 0 ) + return child; + return 0; +} + +//-------------------------------------------------------------------------- + +F32 ThreadPool::Context::getAccumulatedPriorityBias() +{ + if( !mAccumulatedPriorityBias ) + updateAccumulatedPriorityBiases(); + return mAccumulatedPriorityBias; +} + +//-------------------------------------------------------------------------- + +void ThreadPool::Context::setPriorityBias( F32 value ) +{ + mPriorityBias = value; + mAccumulatedPriorityBias = 0.0; +} + +//-------------------------------------------------------------------------- + +void ThreadPool::Context::updateAccumulatedPriorityBiases() +{ + // Update our own priority bias. + + mAccumulatedPriorityBias = mPriorityBias; + for( Context* context = getParent(); context != 0; context = context->getParent() ) + mAccumulatedPriorityBias *= context->getPriorityBias(); + + // Update our children. + + for( Context* child = getChildren(); child != 0; child = child->getSibling() ) + child->updateAccumulatedPriorityBiases(); +} + +//============================================================================= +// ThreadPool::WorkItem. +//============================================================================= + +//-------------------------------------------------------------------------- + +void ThreadPool::WorkItem::process() +{ + execute(); +} + +//-------------------------------------------------------------------------- + +bool ThreadPool::WorkItem::isCancellationRequested() +{ + return false; +} + +//-------------------------------------------------------------------------- + +bool ThreadPool::WorkItem::cancellationPoint() +{ + if( isCancellationRequested() ) + { + onCancelled(); + return true; + } + else + return false; +} + +//-------------------------------------------------------------------------- + +F32 ThreadPool::WorkItem::getPriority() +{ + return 1.0; +} + +//============================================================================= +// ThreadPool::WorkItemWrapper. +//============================================================================= + +/// Value wrapper for work items while placed on priority queue. +/// Conforms to interface dictated by ThreadSafePriorityQueueWithUpdate. +/// +/// @see ThreadSafePriorityQueueWithUpdate +/// @see ThreadPool::WorkItem +/// +struct ThreadPool::WorkItemWrapper : public ThreadSafeRef< WorkItem > +{ + typedef ThreadSafeRef< WorkItem > Parent; + + WorkItemWrapper() {} + WorkItemWrapper( WorkItem* item ) + : Parent( item ) {} + + bool isAlive(); + F32 getPriority(); +}; + +inline bool ThreadPool::WorkItemWrapper::isAlive() +{ + WorkItem* item = ptr(); + if( !item ) + return false; + else if( item->isCancellationRequested() ) + { + ( *this ) = 0; + return false; + } + else + return true; +} + +inline F32 ThreadPool::WorkItemWrapper::getPriority() +{ + WorkItem* item = ptr(); + AssertFatal( item != 0, "ThreadPool::WorkItemWrapper::getPriority - called on dead item" ); + + // Compute a scaled priority value based on the item's context. + return ( item->getContext()->getAccumulatedPriorityBias() * item->getPriority() ); +} + +//============================================================================= +// ThreadPool::WorkerThread. +//============================================================================= + +/// +/// +struct ThreadPool::WorkerThread : public Thread +{ + WorkerThread( ThreadPool* pool, U32 index ); + + WorkerThread* getNext(); + virtual void run( void* arg = 0 ); + +private: + U32 mIndex; + ThreadPool* mPool; + WorkerThread* mNext; +}; + +ThreadPool::WorkerThread::WorkerThread( ThreadPool* pool, U32 index ) + : mPool( pool ), + mIndex( index ) +{ + // Link us to the pool's thread list. + + mNext = pool->mThreads; + pool->mThreads = this; +} + +inline ThreadPool::WorkerThread* ThreadPool::WorkerThread::getNext() +{ + return mNext; +} + +void ThreadPool::WorkerThread::run( void* arg ) +{ + #ifdef TORQUE_DEBUG + { + // Set the thread's name for debugging. + char buffer[ 2048 ]; + dSprintf( buffer, sizeof( buffer ), "ThreadPool(%s) WorkerThread %i", mPool->mName.c_str(), mIndex ); + _setName( buffer ); + } + #endif + + while( 1 ) + { + if( checkForStop() ) + { +#ifdef DEBUG_SPEW + Platform::outputDebugString( "[ThreadPool::WorkerThread] thread '%i' exits", getId() ); +#endif + dFetchAndAdd( mPool->mNumThreads, ( U32 ) -1 ); + return; + } + + // Mark us as potentially blocking. + dFetchAndAdd( mPool->mNumThreadsReady, ( U32 ) -1 ); + + bool waitForSignal = false; + { + // Try to take an item from the queue. Do + // this in a separate block, so we'll be + // releasing the item after we have finished. + + WorkItemWrapper workItem; + if( mPool->mWorkItemQueue.takeNext( workItem ) ) + { + // Mark us as non-blocking as this loop definitely + // won't wait on the semaphore. + dFetchAndAdd( mPool->mNumThreadsReady, 1 ); + +#ifdef DEBUG_SPEW + Platform::outputDebugString( "[ThreadPool::WorkerThread] thread '%i' takes item '0x%x'", getId(), *workItem ); +#endif + workItem->process(); + } + else + waitForSignal = true; + } + + if( waitForSignal ) + { + dFetchAndAdd( mPool->mNumThreadsAwake, ( U32 ) -1 ); + +#ifdef DEBUG_SPEW + Platform::outputDebugString( "[ThreadPool::WorkerThread] thread '%i' going to sleep", getId() ); +#endif + mPool->mSemaphore.acquire(); +#ifdef DEBUG_SPEW + Platform::outputDebugString( "[ThreadPool::WorkerThread] thread '%i' waking up", getId() ); +#endif + + dFetchAndAdd( mPool->mNumThreadsAwake, 1 ); + dFetchAndAdd( mPool->mNumThreadsReady, 1 ); + } + } +} + +//============================================================================= +// ThreadPool. +//============================================================================= + +bool ThreadPool::smForceAllMainThread; +U32 ThreadPool::smMainThreadTimeMS; +ThreadPool ThreadPool::smGlobalPool( "GLOBAL" ); +ThreadPool::QueueType ThreadPool::smMainThreadQueue; + +//-------------------------------------------------------------------------- + +ThreadPool::ThreadPool( const char* name, U32 numThreads ) + : mName( name ), + mNumThreads( numThreads ), + mNumThreadsAwake( 0 ), + mThreads( 0 ), + mSemaphore( 0 ) +{ + // Number of worker threads to create. + + if( !mNumThreads ) + { + // Use platformCPUInfo directly as in the case of the global pool, + // Platform::SystemInfo will not yet have been initialized. + + U32 numLogical; + U32 numPhysical; + U32 numCores; + + CPUInfo::CPUCount( numLogical, numCores, numPhysical ); + + const U32 baseCount = getMax( numLogical, numCores ); + if( baseCount ) + mNumThreads = baseCount; + else + mNumThreads = 2; + } + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[ThreadPool] spawning %i threads", mNumThreads ); + #endif + + // Create the threads. + + mNumThreadsAwake = mNumThreads; + mNumThreadsReady = mNumThreads; + for( U32 i = 0; i < mNumThreads; i ++ ) + { + WorkerThread* thread = new WorkerThread( this, i ); + thread->start(); + } +} + +//-------------------------------------------------------------------------- + +ThreadPool::~ThreadPool() +{ + shutdown(); +} + +//-------------------------------------------------------------------------- + +void ThreadPool::shutdown() +{ + const U32 numThreads = mNumThreads; + + // Tell our worker threads to stop. + + for( WorkerThread* thread = mThreads; thread != 0; thread = thread->getNext() ) + thread->stop(); + + // Release the semaphore as many times as there are threads. + // Doing this separately guarantees we're not waking a thread + // that hasn't been set its stop flag yet. + + for( U32 n = 0; n < numThreads; ++ n ) + mSemaphore.release(); + + // Delete each worker thread. Wait until death as we're prone to + // running into issues with decomposing work item lists otherwise. + + for( WorkerThread* thread = mThreads; thread != 0; ) + { + WorkerThread* next = thread->getNext(); + thread->join(); + delete thread; + thread = next; + } + + mThreads = NULL; + mNumThreads = 0; +} + +//-------------------------------------------------------------------------- + +void ThreadPool::queueWorkItem( WorkItem* item ) +{ + bool executeRightAway = ( getForceAllMainThread() ); +#ifdef DEBUG_SPEW + Platform::outputDebugString( "[ThreadPool] %s work item '0x%x'", + ( executeRightAway ? "executing" : "queuing" ), + item ); +#endif + + if( executeRightAway ) + item->process(); + else + { + // Put the item in the queue. + + mWorkItemQueue.insert( item->getPriority(), item ); + + // Wake up some thread, if we need to. + // Use the ready count here as the wake count does + // not correctly protect the critical section in the + // thread's run function. This may lead us to release + // the semaphore more often than necessary, but it avoids + // a race condition. + + if( !dCompareAndSwap( mNumThreadsReady, mNumThreads, mNumThreads ) ) + mSemaphore.release(); + } +} + +//-------------------------------------------------------------------------- + +void ThreadPool::flushWorkItems( S32 timeOut ) +{ + AssertFatal( mNumThreads, "ThreadPool::flushWorkItems() - no worker threads in pool" ); + + U32 endTime = 0; + if( timeOut != -1 ) + endTime = Platform::getRealMilliseconds() + timeOut; + + // Spinlock until the queue is empty. + + while( !mWorkItemQueue.isEmpty() ) + { + Platform::sleep( 25 ); + + // Stop if we have exceeded our processing time budget. + + if( timeOut != -1 + && Platform::getRealMilliseconds() >= endTime ) + break; + } +} + +//-------------------------------------------------------------------------- + +void ThreadPool::queueWorkItemOnMainThread( WorkItem* item ) +{ + smMainThreadQueue.insert( item->getPriority(), item ); +} + +//-------------------------------------------------------------------------- + +void ThreadPool::processMainThreadWorkItems() +{ + AssertFatal( ThreadManager::isMainThread(), + "ThreadPool::processMainThreadWorkItems - this function must only be called on the main thread" ); + + U32 timeLimit = ( Platform::getRealMilliseconds() + getMainThreadThresholdTimeMS() ); + + do + { + WorkItemWrapper item; + if( !smMainThreadQueue.takeNext( item ) ) + break; + else + item->process(); + } + while( Platform::getRealMilliseconds() < timeLimit ); +} diff --git a/platform/threads/threadPool.h b/platform/threads/threadPool.h new file mode 100644 index 0000000..916fa7d --- /dev/null +++ b/platform/threads/threadPool.h @@ -0,0 +1,366 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _THREADPOOL_H_ +#define _THREADPOOL_H_ + +#ifndef _THREADSAFEREFCOUNT_H_ + #include "platform/threads/threadSafeRefCount.h" +#endif +#ifndef _THREADSAFEPRIORITYQUEUE_H_ + #include "platform/threads/threadSafePriorityQueue.h" +#endif +#ifndef _PLATFORM_THREAD_SEMAPHORE_H_ + #include "platform/threads/semaphore.h" +#endif + + +/// @file +/// Interface for an asynchronous work manager. + + +/// Asynchronous work manager. +/// +/// Thread pooling allows to submit work items for background execution. +/// Each work item will be placed on a queue and, based on a total priority +/// ordering, executed when it has the highest priority and a worker thread +/// becomes available. +/// +/// @note The global pool maintains the invariant that only the main thread +/// may submit items in order to be able to flush the item queue reliably +/// from the main thread itself. If other threads were issuing items to +/// the queue, the queue may never empty out and the main thread will +/// deadlock. +/// +/// Flushing is the simplest method to guarantee that no asynchronous +/// operation is pending in a specific case (deletion of the target object +/// being the most common case). However, when possible, avoid this +/// situation and design your work items to operate independently, +/// e.g. by having only a single point of access to data that may have +/// disappeared in the meantime and putting a check around that single +/// access so that the item will silently die when its target object has +/// disappeared. +/// +/// The cleanest safe solution to this is to create a separate concurrently +/// reference-counted structure that holds all interfacing state and +/// functionality shared between a work item and its issueing code. This way +/// the host object can safely disappear with the interfacing structure +/// automatically being released once the last concurrent work item has been +/// processed or discarded. +/// +class ThreadPool +{ + public: + + /// A ThreadPool context defines a logical context in which WorkItems are + /// being executed. Their primary use is for biasing priorities of + /// WorkItems. + /// + /// Contexts are arranged in a tree hierarchy. Each parent node's priority + /// bias scales all the priority biases underneath it. + /// + /// Note that instances of this class are meant to be instantiated + /// globally only. + /// + class Context + { + protected: + + /// Superordinate context; scales this context's priority bias. + Context* mParent; + + /// First child. + Context* mChildren; + + /// Next sibling in child chain. + Context* mSibling; + + /// Name of this context. Should be unique in parent namespace. + const char* mName; + + /// Priority scale factor of this context. + F32 mPriorityBias; + + /// Accumulated scale factor. + F32 mAccumulatedPriorityBias; + + /// The root context; does not modify priorities. All contexts should be direct or indirect children of this one. + static Context smRootContext; + + /// Recursively update cached accumulated priority biases. + void updateAccumulatedPriorityBiases(); + + public: + + Context( const char* name, Context* parent, F32 priorityBias ); + ~Context(); + + /// Return the name of the worker threading context. + const char* getName() const + { + return mName; + } + + /// Return the context's own work item priority bias. + F32 getPriorityBias() const + { + return mPriorityBias; + } + + /// Return the superordinate node to the current context. + Context* getParent() const + { + return mParent; + } + + /// Return the next sibling to the current context. + Context* getSibling() const + { + return mSibling; + } + + /// Return the first child context. + Context* getChildren() const + { + return mChildren; + } + + /// Return the root context. + static Context* ROOT_CONTEXT() + { + return &smRootContext; + } + + /// + F32 getAccumulatedPriorityBias(); + + /// + Context* getChild( const char* name ); + + /// + void setPriorityBias( F32 value ); + }; + + /// An action to execute on a worker thread from the pool. + /// + /// Work items are concurrently reference-counted and will be + /// automatically released once the last reference disappears. + /// + class WorkItem : public ThreadSafeRefCount< WorkItem > + { + public: + + typedef ThreadSafeRefCount< WorkItem > Parent; + + protected: + + /// The work context of this item. + Context* mContext; + + /// Mark a point in a work item's execution where the item can + /// be safely cancelled. + /// + /// This method should be called by subclasses' execute() methods + /// whenever an item can be safely cancelled. When it returns true, + /// the work item should exit from its execute() method. + bool cancellationPoint(); + + /// Called when the item has been cancelled. + virtual void onCancelled() {} + + /// Execute the actions associated with this work item. + /// This is the primary function to implement by subclasses. + virtual void execute() = 0; + + public: + + /// Construct a new work item. + /// + /// @param context The work context in which the item should be placed. + /// If NULL, the root context will be used. + WorkItem( Context* context = 0 ) + : mContext( context ? context : Context::ROOT_CONTEXT() ) + { + } + + virtual ~WorkItem() {} + + /// Return the work context associated with the work item. + inline Context* getContext() const + { + return mContext; + } + + /// Process the work item. + void process(); + + /// Return true if the work item should be cancelled. + /// + /// This method can be overridden by subclasses. It's value will be + /// checked each time cancellationPoint() is called. When it returns + /// true, the item's process() method will exit automatically. + /// + /// @return true, if item should be cancelled; default is false. + /// @see ThreadPool::WorkItem::cancellationPoint + virtual bool isCancellationRequested(); + + /// Return the item's base priority value. + /// @return item priority; defaults to 1.0. + virtual F32 getPriority(); + }; + + typedef ThreadSafeRef< WorkItem > WorkItemPtr; + + protected: + + struct WorkItemWrapper; + struct WorkerThread; + + friend struct WorkerThread; // mSemaphore, mNumThreadsAwake, mThreads + + typedef ThreadSafePriorityQueueWithUpdate< WorkItemWrapper, F32 > QueueType; + + /// Name of this pool. Mainly for debugging. Used to name worker threads. + String mName; + + /// Number of worker threads spawned by the pool. + U32 mNumThreads; + + /// Number of worker threads in non-sleeping state. + U32 mNumThreadsAwake; + + /// Number of worker threads guaranteed to be non-blocking. + U32 mNumThreadsReady; + + /// Semaphore used to wake up threads, if necessary. + Semaphore mSemaphore; + + /// Threaded priority queue for concurrent access by worker threads. + QueueType mWorkItemQueue; + + /// List of worker threads. + WorkerThread* mThreads; + + /// Force all work items to execute on main thread; + /// turns this into a single-threaded system. + /// Primarily useful to find whether malfunctions are caused + /// by parallel execution or not. + static bool smForceAllMainThread; + + /// + static U32 smMainThreadTimeMS; + + /// The global pool singleton. + static ThreadPool smGlobalPool; + + /// Work queue for main thread; can be used to ping back work items to + /// main thread that need processing that can only happen on main thread. + static QueueType smMainThreadQueue; + + public: + + /// Create a new thread pool with the given number of worker threads. + /// + /// If numThreads is zero (the default), the number of threads created + /// will be based on the number of CPU cores available. + /// + /// @param numThreads Number of threads to create or zero for default. + ThreadPool( const char* name, U32 numThreads = 0 ); + + ~ThreadPool(); + + /// Manually shutdown threads outside of static destructors. + void shutdown(); + + /// + void queueWorkItem( WorkItem* item ); + + /// + /// For the global pool, it is very important to only ever call + /// this function on the main thread and to let work items only ever + /// come from the main thread. Otherwise this function has the potential + /// of dead-locking as new work items may constantly be fed to the queue + /// without it ever getting empty. + /// + /// @param timeOut Soft limit on the number of milliseconds to wait for + /// the queue to flush out. -1 = infinite. + void flushWorkItems( S32 timeOut = -1 ); + + /// Add a work item to the main thread's work queue. + /// + /// The main thread's work queue will be processed each frame using + /// a set timeout to limit the work being done. Nonetheless, work + /// items will not be suspended in-midst of processing, so make sure + /// that whatever work you issue to the main thread is light work + /// or you may see short hangs in gameplay. + /// + /// To reiterate this: any code executed through this interface directly + /// adds to frame processing time on the main thread. + /// + /// This method *may* (and is meant to) be called from threads + /// other than the main thread. + static void queueWorkItemOnMainThread( WorkItem* item ); + + /// Process work items waiting on the main thread's work queue. + /// + /// There is a soft limit imposed on the time this method is allowed + /// to run so as to balance frame-to-frame load. However, work + /// items, once their processing is initiated, will not be suspended + /// and will run for as long as they take to complete, so make sure + /// individual items perform as little work as necessary. + /// + /// @see ThreadPool::getMainThreadThesholdTimeMS + static void processMainThreadWorkItems(); + + /// Return the interval in which item priorities are updated on the queue. + /// @return update interval in milliseconds. + U32 getQueueUpdateInterval() const + { + return mWorkItemQueue.getUpdateInterval(); + } + + /// Return the priority increment applied to work items on each interval. + F32 getQueueTimeBasedPriorityBoost() const + { + return mWorkItemQueue.getTimeBasedPriorityBoost(); + } + + /// Set the update interval of the work item queue to the given value. + /// @param milliSeconds Time between updates in milliseconds. + void setQueueUpdateInterval( U32 milliSeconds ) + { + mWorkItemQueue.setUpdateInterval( milliSeconds ); + } + + /// Set the priority increment applied to work items on each update interval. + /// @param value Priority increment. Set to zero to deactivate. + void setQueueTimeBasedPriorityBoost( F32 value ) + { + mWorkItemQueue.setTimeBasedPriorityBoost( value ); + } + + /// + static U32& getMainThreadThresholdTimeMS() + { + return smMainThreadTimeMS; + } + + /// + static bool& getForceAllMainThread() + { + return smForceAllMainThread; + } + + /// Return the global thread pool singleton. + static ThreadPool& GLOBAL() + { + return smGlobalPool; + } +}; + +typedef ThreadPool::Context ThreadContext; +typedef ThreadPool::WorkItem ThreadWorkItem; + +#endif // _THREADPOOL_H_ diff --git a/platform/threads/threadPoolAsyncIO.h b/platform/threads/threadPoolAsyncIO.h new file mode 100644 index 0000000..92c5f2d --- /dev/null +++ b/platform/threads/threadPoolAsyncIO.h @@ -0,0 +1,340 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _THREADPOOLASYNCIO_H_ +#define _THREADPOOLASYNCIO_H_ + +#ifndef _THREADPOOL_H_ +# include "platform/threads/threadPool.h" +#endif +#ifndef _RAWDATA_H_ +# include "core/util/rawData.h" +#endif +#ifndef _TSTREAM_H_ +# include "core/stream/tStream.h" +#endif + + +//RDTODO: I/O error handling + +/// @file +/// Thread pool work items for asynchronous stream I/O. +/// Through the use of stream filters, this can be basically used for any +/// type of asynchronous stream processing. + +//-------------------------------------------------------------------------- +// AsyncIOItem. +//-------------------------------------------------------------------------- + +/// Abstract superclass of async I/O work items. +/// +/// Supports both offset-based stream I/O as well as I/O on streams with +/// implicit positions. Note that if you use the latter type, make sure +/// that no other thread is messing with the stream at the same time or +/// chaos will ensue. +/// +/// @param T Type of elements being streamed. +template< typename T, class Stream > +class AsyncIOItem : public ThreadPool::WorkItem +{ + public: + + typedef WorkItem Parent; + typedef T ValueType; + typedef RawDataT< ValueType > BufferType; + typedef U32 OffsetType; + typedef Stream StreamType; + + protected: + + /// Buffer keeping/receiving the data elements. + BufferType mBuffer; + + /// The stream to read from/write to. + StreamType* mStream; + + /// Number of elements to read from/write to the stream. + U32 mNumElements; + + /// Offset in "mBuffer" from where to read/where to start writing to. + U32 mOffsetInBuffer; + + /// Offset in stream from where to read/where to write to. + /// @note This is only meaningful if the stream is an offset I/O + /// stream. For a stream that is can do both types of I/O, + /// explicit offsets are preferred and this value is used. + OffsetType mOffsetInStream; + + /// + ValueType* getBufferPtr() + { + return &getBuffer().data[ getOffsetInBuffer() ]; + } + + public: + + /// + /// If the stream uses implicit positioning, then the supplied "offsetInStream" + /// is meaningless and ignored. + AsyncIOItem( StreamType* stream, U32 numElements, OffsetType offsetInStream, + ThreadContext* context = 0 ) + : Parent( context ), + mStream( stream ), + mNumElements( numElements ), + mOffsetInStream( offsetInStream ), + mOffsetInBuffer( 0 ) {} + + /// Construct a read item on "stream" that stores data into the given "buffer". + /// + AsyncIOItem( StreamType* stream, BufferType& buffer, U32 offsetInBuffer, + U32 numElements, OffsetType offsetInStream, bool takeOwnershipOfBuffer = true, + ThreadContext* context = 0 ) + : Parent( context ), + mStream( stream ), + mBuffer( buffer ), + mNumElements( numElements ), + mOffsetInStream( offsetInStream ), + mOffsetInBuffer( offsetInBuffer ) + { + if( takeOwnershipOfBuffer ) + mBuffer.ownMemory = true; + } + + /// Return the stream being written to/read from. + StreamType* getStream() + { + return mStream; + } + + /// Return the data buffer being written to/read from. + /// @note This may not yet have been allocated. + BufferType& getBuffer() + { + return mBuffer; + + } + + /// Return the number of elements involved in the transfer. + U32 getNumElements() + { + return mNumElements; + } + + /// Return the position in the data buffer at which to start the transfer. + U32 getOffsetInBuffer() + { + return mOffsetInBuffer; + } + + /// Return the position in the stream at which to start the transfer. + /// @note Only meaningful for streams that support offset I/O. + OffsetType getOffsetInStream() + { + return mOffsetInStream; + } +}; + +//-------------------------------------------------------------------------- +// AsyncReadItem. +//-------------------------------------------------------------------------- + +//RDTODO: error handling +/// Work item to asynchronously read from a stream. +/// +/// The given stream type may implement any of the input stream +/// interfaces. Preference is given to IAsyncInputStream, then to +/// IOffsetInputStream, and only if none of these are implemented +/// IInputStream is used. +/// +/// For IAsyncInputStreams, the async read operation is issued immediately +/// on the constructing thread and then picked up on the worker thread. +/// This ensures optimal use of concurrency. + +template< typename T, class Stream = IOffsetInputStream< T > > +class AsyncReadItem : public AsyncIOItem< T, Stream > +{ + public: + + typedef AsyncIOItem< T, Stream > Parent; + typedef typename Parent::StreamType StreamType; + typedef typename Parent::OffsetType OffsetType; + typedef typename Parent::BufferType BufferType; + typedef typename Parent::ValueType ValueType; + + /// Construct a read item that reads "numElements" at "offsetInStream" + /// from "stream". + /// + /// Since with this constructor no data buffer is supplied, it will be + /// dynamically allocated by the read() method. Note that this only makes + /// sense if this class is subclassed and processing is done on the buffer + /// after it has been read. + /// + /// @param stream The stream to read from. + /// @param numElement The number of elements to read from the stream. + /// @param offsetInStream The offset at which to read from the stream; + /// ignored if the stream uses implicit positioning + /// @param context The tread pool context to place the item into. + AsyncReadItem( StreamType* stream, U32 numElements, OffsetType offsetInStream, + ThreadContext* context = 0 ) + : Parent( stream, numElements, offsetInStream, context ) + { + _prep(); + } + + AsyncReadItem( StreamType* stream, U32 numElements, OffsetType offsetInStream, + BufferType& buffer, bool takeOwnershipOfBuffer = false, + U32 offsetInBuffer = 0, ThreadContext* context = 0 ) + : Parent( stream, buffer, offsetInBuffer, numElements, offsetInStream, takeOwnershipOfBuffer, context ) + { + _prep(); + } + + /// @return The number of elements actually read from the stream. + U32 getNumElementsRead() + { + return mNumElementsRead; + } + + protected: + + /// Handle of asynchronous stream read, if we are using an async interface. + void* mAsyncHandle; + + /// After the read operation has completed, this holds the number of + /// elements actually read from the stream. + U32 mNumElementsRead; + + virtual void execute(); + + void _allocBuffer() + { + if( !this->getBuffer().data ) + this->getBuffer().alloc( this->getNumElements() ); + } + + void _prep() + { + IAsyncInputStream< T >* s = dynamic_cast< IAsyncInputStream< T >* >( this->getStream() ); + if( s ) + { + _allocBuffer(); + mAsyncHandle = s->issueReadAt( this->getOffsetInStream(), this->getBufferPtr(), this->getNumElements() ); + } + } + + // Helper functions to differentiate between stream types. + + void _read( IInputStream< T >* stream ) + { + mNumElementsRead = stream->read( this->getBufferPtr(), this->getNumElements() ); + } + void _read( IOffsetInputStream< T >* stream ) + { + mNumElementsRead = stream->readAt( this->getOffsetInStream(), this->getBufferPtr(), this->getNumElements() ); + } + void _read( IAsyncInputStream< T >* stream ) + { + stream->tryCompleteReadAt( mAsyncHandle, mNumElementsRead, true ); + } +}; + +template< typename T, class Stream > +void AsyncReadItem< T, Stream >::execute() +{ + _allocBuffer(); + + // Read the data. Do a dynamic cast for any of the + // interfaces we prefer. + + if( this->cancellationPoint() ) return; + StreamType* stream = this->getStream(); + if( dynamic_cast< IAsyncInputStream< T >* >( stream ) ) + _read( ( IAsyncInputStream< T >* ) stream ); + else if( dynamic_cast< IOffsetInputStream< T >* >( stream ) ) + _read( ( IOffsetInputStream< T >* ) stream ); + else + _read( stream ); +} + +//-------------------------------------------------------------------------- +// AsyncWriteItem. +//-------------------------------------------------------------------------- + +/// Work item for writing to an output stream. +/// +/// The stream being written to may implement any of the given output stream +/// interfaces. Preference is given to IAsyncOutputStream, then to +/// IOffsetOutputStream, and only if none of these is implemented IOutputStream +/// is used. +/// +/// A useful feature is to yield ownership of the data buffer to the +/// write item. This way, this can be pretty much used in a fire-and-forget +/// manner where after submission, no further synchronization happens +/// between the client and the work item. +/// +/// @note Be aware that if writing to an output stream that has an implicit +/// position property, multiple concurrent writes will interfere with each other. +template< typename T, class Stream = IOffsetOutputStream< T > > +class AsyncWriteItem : public AsyncIOItem< T, Stream > +{ + public: + + typedef AsyncIOItem< T, Stream > Parent; + typedef typename Parent::StreamType StreamType; + typedef typename Parent::OffsetType OffsetType; + typedef typename Parent::BufferType BufferType; + typedef typename Parent::ValueType ValueType; + + AsyncWriteItem( StreamType* stream, U32 numElements, OffsetType offsetInStream, + BufferType& buffer, bool takeOwnershipOfBuffer = true, + U32 offsetInBuffer = 0, ThreadContext* context = 0 ) + : Parent( stream, buffer, offsetInBuffer, numElements, offsetInStream, takeOwnershipOfBuffer, context ) + { + _prep( stream ); + } + + protected: + + /// Handle of asynchronous write operation, if the stream implements IAsyncOutputStream. + void* mAsyncHandle; + + virtual void execute(); + + void _prep( StreamType* stream ) + { + IAsyncOutputStream< T >* s = dynamic_cast< IAsyncOutputStream< T >* >( stream ); + if( s ) + mAsyncHandle = s->issueWriteAt( this->getOffset(), this->getBufferPtr(), this->getNumElements() ); + } + + void _write( IOutputStream< T >* stream ) + { + stream->write( this->getBufferPtr(), this->getNumElements() ); + } + void _write( IOffsetOutputStream< T >* stream ) + { + stream->writeAt( this->getOffsetInStream(), this->getBufferPtr(), this->getNumElements() ); + } + void _write( IAsyncOutputStream< T >* stream ) + { + stream->tryCompleteWriteAt( mAsyncHandle, true ); + } +}; + +template< typename T, class Stream > +void AsyncWriteItem< T, Stream >::execute() +{ + if( this->cancellationPoint() ) return; + + StreamType* stream = this->getStream(); + if( dynamic_cast< IAsyncOutputStream< T >* >( stream ) ) + _write( ( IAsyncOutputStream< T >* ) stream ); + if( dynamic_cast< IOffsetOutputStream< T >* >( stream ) ) + _write( ( IOffsetOutputStream< T >* ) stream ); + else + _write( stream ); +} + +#endif // _THREADPOOLASYNCIO_H_ diff --git a/platform/threads/threadSafeDeque.h b/platform/threads/threadSafeDeque.h new file mode 100644 index 0000000..5208825 --- /dev/null +++ b/platform/threads/threadSafeDeque.h @@ -0,0 +1,457 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _THREADSAFEDEQUE_H_ +#define _THREADSAFEDEQUE_H_ + +#ifndef _THREADSAFEFREELIST_H_ +# include "platform/threads/threadSafeFreeList.h" +#endif + +#include "platform/tmm_off.h" + + +/// Fast, lock-free double-ended queue for concurrent access. +/// +/// @param T Type of list elements; must have default contructor. +template< typename T > +class ThreadSafeDeque +{ + // Lock-free deques using just single-word atomic writes are + // very tricky as each pointer update must immediately result + // in a fully valid list state. The idea here is to maintain the + // deque's head and tail pointers unreliably but otherwise keep a + // regular double-linked list (since we never insert nodes in the + // middle, single-word writes are all we need). + // + // Deletions are a bit less straightforward and require the threads + // to work cooperatively. Since failure of a pointer update depends + // on the deletion state, the deletion flag has to be encoded into + // the link fields. However, as there are two link fields this creates + // two independent deletion flags for each single node, one on the + // next link and one on the prev link. + // + // This will not lead to a problem, though, as it only becomes relevant + // when there is only a single value in the list which, even if the + // respective node gets both deleted and appended/prepended a new node, + // will result in a valid list state. + + + public: + + typedef T ValueType; + + protected: + + class Node; + class DeleteNode; + typedef ThreadSafeRef< Node > NodeRef; + + /// List node. + class Node : public ThreadSafeFreeListNode< Node, DeleteNode > + { + public: + + friend class DeleteNode; // mFreeList; + typedef typename ThreadSafeDeque< T >::ValueType ValueType; + + /// Thread claim flag. This is to prevent two threads who concurrently + /// do a tryPopFront() and tryPopBack() respectively on a deque with just + /// a single node to both claim and return the same value (which would happen + /// without the flag as otherwise both threads would use two different + /// deletion bits for claiming the node). + U32 mIsClaimed; + + /// Link to the freelist that the node has been + /// allocated from. + ThreadSafeFreeList< Node >& mFreeList; + + /// Value contained in the node. + ValueType mValue; + + /// Reference to next node and deletion bit. + NodeRef mNext; + + /// Reference to previous node and deletion bit. + NodeRef mPrev; + + /// Construct an unlinked node allocated from "freeList". + Node( ThreadSafeFreeList< Node >& freeList, const ValueType& value ) + : mIsClaimed( 0 ), mFreeList( freeList ), mValue( value ) {} + }; + + class DeleteNode + { + public: + template< typename N > + static void destroy( N* ptr ) + { + AssertFatal( ptr->mIsClaimed, + "ThreadSafeDeque::DeleteNode::destroy() - deleting unclaimed node" ); + destructInPlace( ptr ); + ptr->mFreeList.free( ptr ); + } + }; + + #ifdef TORQUE_DEBUG + S32 mNumValues; + #endif + + /// Reference to the head node. + NodeRef mHead; + + /// + NodeRef mTail; + + /// Free list for list nodes. + ThreadSafeFreeList< Node > mFreeList; + + /// @return the leftmost node in the list. + /// @note Updates the list state and may purge deleted nodes. + NodeRef getHead(); + + /// @return the rightmost node in the list. + /// @note Updates the list state and may purge deleted nodes. + NodeRef getTail(); + + public: + + /// Construct an empty deque. + ThreadSafeDeque() + { + #ifdef TORQUE_DEBUG + mNumValues = 0; + #endif + } + + ~ThreadSafeDeque() + { + ValueType value; + while( tryPopFront( value ) ); + AssertFatal( isEmpty(), "ThreadSafeDeque::~ThreadSafeDeque() - not empty" ); + } + + /// @return true if the queue is empty. + bool isEmpty() + { + return ( !getHead() && !getTail() ); + } + + /// Prepend the given value to the list. + void pushFront( const ValueType& value ); + + /// Append the given value to the list. + void pushBack( const ValueType& value ); + + /// Try to take the leftmost value from the deque. + /// Fails if the deque is empty at the time the method tries to + /// take a node from the list. + bool tryPopFront( ValueType& outValue ); + + /// Try to take the rightmost value from the deque. + /// Fails if the deque is empty at the time the method tries to + /// take a node from the list. + bool tryPopBack( ValueType& outValue ); + + void dumpDebug() + { + #ifdef TORQUE_DEBUG + Platform::outputDebugString( "[ThreadSafeDeque] numValues=%i", mNumValues ); + mFreeList.dumpDebug(); + #endif + } +}; + +// The getHead() and getTail() code here is pretty much brute-force in order +// to keep the complexities of synchronizing it bounded. We just let each +// thread work as if it is the only thread but require each one to start from +// scratch on each iteration. + +template< typename T > +typename ThreadSafeDeque< T >::NodeRef ThreadSafeDeque< T >::getHead() +{ + // Find leftmost node. + + NodeRef result; + while( 1 ) + { + // Iterate through to leftmost node. + + { + NodeRef head = mHead; + while( head != NULL ) + { + NodeRef prev = head->mPrev; + if( prev != NULL ) + mHead.trySetFromTo( head, prev, NodeRef::TAG_Unset ); + else + break; + + head = mHead; + } + } + + // Clear out dead nodes at front of list. + + { + NodeRef head = mHead; + if( head && head->mPrev.isTagged() ) + { + NodeRef next = head->mNext; + + mHead.trySetFromTo( head, next, NodeRef::TAG_Unset ); + mTail.trySetFromTo( head, next, NodeRef::TAG_Unset ); + + if( next != NULL ) + next->mPrev.trySetFromTo( head, NULL ); + + head->mNext.trySetFromTo( next, NULL, NodeRef::TAG_Set ); + + continue; // Restart. + } + } + + // Try head. + + NodeRef head = mHead; + if( head != NULL && !head->mPrev.isTagged() ) + { + result = head; + break; + } + + // Try tail. + + if( !head ) + { + head = mTail; + if( !head ) + break; + } + + // Update head. + + NodeRef prev = head->mPrev; + if( head->mPrev != NULL ) + { + if( !mHead.trySetFromTo( head, prev, NodeRef::TAG_Unset ) ) + mHead.trySetFromTo( NULL, prev ); + } + else + mHead.trySetFromTo( NULL, head ); + } + + AssertFatal( !result.isTagged(), "ThreadSafeDeque::getHead() - head got tagged" ); + return result; +} + +template< typename T > +typename ThreadSafeDeque< T >::NodeRef ThreadSafeDeque< T >::getTail() +{ + // Find rightmost node. + + NodeRef result; + while( 1 ) + { + // Iterate through to rightmost node. + + { + NodeRef tail = mTail; + while( tail != NULL ) + { + NodeRef next = tail->mNext; + if( next != NULL ) + mTail.trySetFromTo( tail, next, NodeRef::TAG_Unset ); + else + break; + + tail = mTail; + } + } + + // Clear out dead nodes at tail of list. + + { + NodeRef tail = mTail; + if( tail != NULL && tail->mNext.isTagged() ) + { + NodeRef prev = tail->mPrev; + + mHead.trySetFromTo( tail, prev, NodeRef::TAG_Unset ); + mTail.trySetFromTo( tail, prev, NodeRef::TAG_Unset ); + + if( prev != NULL ) + prev->mNext.trySetFromTo( tail, NULL ); + + tail->mPrev.trySetFromTo( prev, NULL, NodeRef::TAG_Set ); + + continue; // Restart. + } + } + + // Try tail. + + NodeRef tail = mTail; + if( tail != NULL && !tail->mNext.isTagged() ) + { + result = tail; + break; + } + + // Try head. + + if( !tail ) + { + tail = mHead; + if( !tail ) + break; + } + + // Update tail. + + NodeRef next = tail->mNext; + if( next != NULL ) + { + if( !mTail.trySetFromTo( tail, next, NodeRef::TAG_Unset ) ) + mTail.trySetFromTo( NULL, next ); + } + else + mTail.trySetFromTo( NULL, tail ); + } + + AssertFatal( !result.isTagged(), "ThreadSafeDeque::getTail() - tail got tagged" ); + return result; +} + +template< typename T > +void ThreadSafeDeque< T >::pushFront( const ValueType& value ) +{ + NodeRef nextNode; + NodeRef newNode; + + NodeRef::unsafeWrite( newNode, new ( mFreeList ) Node( mFreeList, value ) ); + + while( 1 ) + { + nextNode = getHead(); + if( !nextNode ) + { + newNode->mNext = NULL; + if( mHead.trySetFromTo( NULL, newNode ) ) + break; + } + else + { + newNode->mNext = nextNode; + if( nextNode->mPrev.trySetFromTo( NULL, newNode, NodeRef::TAG_FailIfSet ) ) + break; + } + } + +#ifdef TORQUE_DEBUG + dFetchAndAdd( mNumValues, 1 ); +#endif +} + +template< typename T > +void ThreadSafeDeque< T >::pushBack( const ValueType& value ) +{ + NodeRef prevNode; + NodeRef newNode; + + NodeRef::unsafeWrite( newNode, new ( mFreeList ) Node( mFreeList, value ) ); + + while( 1 ) + { + prevNode = getTail(); + if( !prevNode ) + { + newNode->mPrev = NULL; + if( mHead.trySetFromTo( NULL, newNode ) ) // use head so we synchronize with pushFront + break; + } + else + { + newNode->mPrev = prevNode; + if( prevNode->mNext.trySetFromTo( NULL, newNode, NodeRef::TAG_FailIfSet ) ) + break; + } + } + +#ifdef TORQUE_DEBUG + dFetchAndAdd( mNumValues, 1 ); +#endif +} + +template< typename T > +bool ThreadSafeDeque< T >::tryPopFront( ValueType& outValue ) +{ + NodeRef oldHead; + + while( 1 ) + { + oldHead = getHead(); + if( !oldHead ) + return false; + + // Try to claim the node. + + if( oldHead->mPrev.trySetFromTo( NULL, NULL, NodeRef::TAG_SetOrFail ) ) + { + if( dCompareAndSwap( oldHead->mIsClaimed, 0, 1 ) ) + break; + else + continue; + } + } + + outValue = oldHead->mValue; + oldHead = NULL; + + // Cleanup. + getHead(); + +#ifdef TORQUE_DEBUG + dFetchAndAdd( mNumValues, -1 ); +#endif + return true; +} + +template< typename T > +bool ThreadSafeDeque< T >::tryPopBack( ValueType& outValue ) +{ + NodeRef oldTail; + + while( 1 ) + { + oldTail = getTail(); + if( !oldTail ) + return false; + + // Try to claim the node. + + if( oldTail->mNext.trySetFromTo( NULL, NULL, NodeRef::TAG_SetOrFail ) ) + { + if( dCompareAndSwap( oldTail->mIsClaimed, 0, 1 ) ) + break; + } + } + + outValue = oldTail->mValue; + oldTail = NULL; + + // Cleanup. + getTail(); + +#ifdef TORQUE_DEBUG + dFetchAndAdd( mNumValues, -1 ); +#endif + return true; +} + + +#include "platform/tmm_on.h" + +#endif // _THREADSAFEDEQUE_H_ diff --git a/platform/threads/threadSafeFreeList.h b/platform/threads/threadSafeFreeList.h new file mode 100644 index 0000000..b4b31d6 --- /dev/null +++ b/platform/threads/threadSafeFreeList.h @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _THREADSAFEFREELIST_H_ +#define _THREADSAFEFREELIST_H_ + +#ifndef _THREADSAFEREFCOUNT_H_ +# include "platform/threads/threadSafeRefCount.h" +#endif +#ifndef _PLATFORMINTRINSICS_H_ +# include "platform/platformIntrinsics.h" +#endif + +#include "platform/tmm_off.h" + + +/// @file +/// Lock-free freelists for concurrent access. + + +/// Freelist for re-using allocations in a concurrent setting. +/// +/// @note Make sure that there are no more allocations in use +/// when the free list is destructed. +/// @note Allocated instances come with a reference already counted +/// on the instance. +/// +/// @param T Type of elements to allocate; must be derived from +/// ThreadSafeRefCount and have at least define one additional +/// pointer-sized field. +template< class T > +class ThreadSafeFreeList +{ + protected: + + T* mFreeList; + + #ifdef TORQUE_DEBUG + S32 mNumNodesTotal; + S32 mNumNodesFree; + #endif + + T*& getNext( T* ptr ) + { + return *( ( T** ) &( ( U8* ) ptr )[ sizeof( T ) - sizeof( T* ) ] ); + } + + public: + + /// Create the freelist. + /// + /// @param numPreAlloc Number of instances to pre-allocate. + ThreadSafeFreeList( U32 numPreAlloc = 0 ) + : mFreeList( 0 ) + { + #ifdef TORQUE_DEBUG + mNumNodesTotal = 0; + mNumNodesFree = 0; + #endif + + for( U32 i = 0; i < numPreAlloc; ++ i ) + free( alloc() ); + } + + ~ThreadSafeFreeList() + { + #ifdef TORQUE_DEBUG + AssertWarn( mNumNodesTotal == mNumNodesFree, + "ThreadSafeFreeList::~ThreadSafeFreeList() - still got live instances" ); + #endif + + // Destroy remaining nodes. Not synchronized. We assume all + // concurrent processing to have finished. + + while( mFreeList ) + { + T* next = getNext( mFreeList ); + dFree( mFreeList ); + mFreeList = next; + } + } + + /// Return memory for a new instance. + void* alloc() + { + T* ptr; + while( 1 ) + { + ptr = ThreadSafeRef< T >::safeRead( mFreeList ); + if( !ptr ) + { + ptr = ( T* ) dMalloc( sizeof( T ) ); + dMemset( ptr, 0, sizeof( T ) ); + + #ifdef TORQUE_DEBUG + dFetchAndAdd( mNumNodesTotal, 1 ); + #endif + + ptr->addRef(); + break; + } + else if( dCompareAndSwap( mFreeList, ptr, getNext( ptr ) ) ) + { + #ifdef TORQUE_DEBUG + dFetchAndAdd( mNumNodesFree, -1 ); + #endif + + ptr->clearLowestBit(); + break; + } + else + ptr->release(); + } + + return ptr; + } + + /// Return the memory allocated to the given instance to the freelist. + void free( void* ptr ) + { + AssertFatal( ptr, "ThreadSafeFreeList::free() - got a NULL pointer" ); + T* node = ( T* ) ptr; + + while( 1 ) + { + T* list = mFreeList; + getNext( node ) = list; + if( dCompareAndSwap( mFreeList, list, node ) ) + break; + } + + #ifdef TORQUE_DEBUG + dFetchAndAdd( mNumNodesFree, 1 ); + #endif + } + + void dumpDebug() + { + #ifdef TORQUE_DEBUG + Platform::outputDebugString( "[ThreadSafeFreeList] total=%i, free=%i", + mNumNodesTotal, mNumNodesFree ); + #endif + } +}; + +/// Baseclass for objects allocated from ThreadSafeFreeLists. +template< class T, class DeletePolicy = DeleteSingle > +class ThreadSafeFreeListNode : public ThreadSafeRefCount< T, DeletePolicy > +{ + public: + + typedef ThreadSafeRefCount< T, DeletePolicy > Parent; + + ThreadSafeFreeListNode() + : Parent( false ) {} + + static void* operator new( size_t size, ThreadSafeFreeList< T >& freeList ) + { + AssertFatal( size <= sizeof( T ), + "ThreadSafeFreeListNode::new() - size exceeds limit of freelist" ); + TORQUE_UNUSED( size ); + return freeList.alloc(); + } + static void operator delete( void* ptr, ThreadSafeFreeList< T >& freeList ) + { + freeList.free( ptr ); + } +}; + + +#include "platform/tmm_on.h" + +#endif // _THREADSAFEFREELIST_H_ diff --git a/platform/threads/threadSafePriorityQueue.h b/platform/threads/threadSafePriorityQueue.h new file mode 100644 index 0000000..4dad500 --- /dev/null +++ b/platform/threads/threadSafePriorityQueue.h @@ -0,0 +1,714 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _THREADSAFEPRIORITYQUEUE_H_ +#define _THREADSAFEPRIORITYQUEUE_H_ + +#ifndef _PLATFORMINTRINSICS_H_ +# include "platform/platformIntrinsics.h" +#endif +#ifndef _THREADSAFEREFCOUNT_H_ +# include "platform/threads/threadSafeRefCount.h" +#endif +#ifndef _TYPETRAITS_H_ +# include "platform/typetraits.h" +#endif + +// Disable TMM's new operator grabbing. +#include "platform/tmm_off.h" + +/// @file +/// Template code for an efficient thread-safe priority queue +/// implementation. There are two alternative implementations to +/// choose from: ThreadSafePriorityQueue and ThreadSafePriorityQueueWithUpdate +/// where the latter adds concurrent status updates of queue items to +/// the former implementation. + +//-------------------------------------------------------------------------- +// ThreadSafePriorityQueue. +//-------------------------------------------------------------------------- + +/// Fast, lock-free priority queue implementation for concurrent access. +/// +/// Equal priorities are allowed and are placed before existing items of +/// identical priority in the queue. +/// +/// Based on (but with significant deviations from) "Fast and Lock-Free Concurrent +/// Priority Queues for Multi-Thread Systems" by Hakan Sundell and Philippas Tsigas. +/// Parts of the skiplist code is based on work by William Pugh. +/// +/// @param T The item value type. Must have a default constructor. +/// @param K The priority key type. Must be comparable, have a default constructor, +/// and be a valid template parameter to TypeTraits. +/// @param SORT_MIN_TO_MAX If true, the queue sorts from minimum to maximum priority or +/// the reverse if false. +/// @param MAX_LEVEL The number of levels a node can have at most. +/// @param PROBABILISTIC_BIAS The probabilistic level distribution factor for +/// the skiplist. Multiplied by 100 and turned into int to conform to restrictions +/// on non-type template parameters. +/// +/// @see TypeTraits + +template< typename T, typename K = F32, bool SORT_MIN_TO_MAX = false, U32 MAX_LEVEL = 4, U32 PROBABILISTIC_BIAS = 50 > +struct ThreadSafePriorityQueue +{ + typedef T ValueType; + typedef K KeyType; + + enum { MAX_LEVEL_CONST = MAX_LEVEL }; + + ThreadSafePriorityQueue(); + + bool isEmpty(); + void insert( KeyType priority, const T& value ); + bool takeNext( T& outValue, KeyType upToPriority = ( SORT_MIN_TO_MAX ? TypeTraits< KeyType >::MAX : TypeTraits< KeyType >::MIN ) ); + +protected: + struct Node; + typedef ThreadSafeRef< Node > NodePtr; + friend class ThreadSafeRefCount< Node >; + friend struct DeleteSingle; + + /// A queue node. + /// + /// Nodes are reference-counted to coordinate memory management + /// between the different threads. Reclamation happens on the + /// thread that releases the last reference. + /// + /// Reference-counting and deletion requests are kept separate. + /// A given node is marked for deletion and will then have its references + /// progressively disappear and eventually be reclaimed once the + /// reference count drops to zero. + /// + /// Note that 'Next' references are released by the destructor which + /// is only called when the reference count to the node itself drops to + /// zero. This is to avoid threads getting trapped in a node with no + /// link out. + + struct Node : public ThreadSafeRefCount< Node > + { + typedef ThreadSafeRefCount< Node > Parent; + + Node( KeyType priority, const ValueType& value ); + ~Node(); + + KeyType getPriority() { return mPriority; } + ValueType& getValue() { return mValue; } + U32 getLevel(); + NodePtr& getNext( U32 level ); + + bool isMarkedForDeletion(); + bool tryMarkForDeletion(); + + void clearValue() { mValue = ValueType(); } + + static U32 randomLevel(); + + void* operator new( size_t size, S32 level = -1 ); + void operator delete( void* ptr ); + + private: + KeyType mPriority; ///< Priority key. + U32 mLevel; ///< Level count and deletion bit (highest). + ValueType mValue; + Node* mNext[ 1 ]; ///< Variable-sized array of next pointers. + + struct FreeList + { + bool mDestroyed; + Node* mNodes; + + ~FreeList(); + }; + + static FreeList smFreeLists[ MAX_LEVEL ]; + }; + + NodePtr mHead; ///< Artificial head node. + NodePtr mTail; ///< Artificial tail node. + + void readNext( NodePtr& refPrev, NodePtr& refNext, U32 level ); + void scan( NodePtr& refPrev, NodePtr& refNext, U32 level, KeyType priority ); + void scanFromHead( NodePtr& refPrev, NodePtr& refNext, U32 level, KeyType priority ); + void insert( KeyType priority, const T& value, NodePtr& outResult ); + void helpDelete(); +}; + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +typename ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::FreeList ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::smFreeLists[ MAX_LEVEL ]; + +/// Construct an empty queue. +/// +/// Internally, this creates a head node with maximal priority and a tail node with minimal priority, +/// both at maximum level. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::ThreadSafePriorityQueue() +{ + NodePtr::unsafeWrite( mHead, new ( MAX_LEVEL - 1 ) + Node( SORT_MIN_TO_MAX ? TypeTraits< KeyType >::MIN : TypeTraits< KeyType >::MAX, ValueType() ) ); + NodePtr::unsafeWrite( mTail, new ( MAX_LEVEL - 1 ) + Node( SORT_MIN_TO_MAX ? TypeTraits< KeyType >::MAX : TypeTraits< KeyType >::MIN, ValueType() ) ); + + for( U32 level = 0; level < MAX_LEVEL; level ++ ) + mHead->getNext( level ) = mTail; +} + +/// Return true if the queue does not currently contain an item. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +bool ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::isEmpty() +{ + return ( mHead->getNext( 0 ) == mTail ); +} + +/// Insert the given value into the queue at the place determined by the given priority. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +inline void ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::insert( KeyType priority, const ValueType& value ) +{ + NodePtr result; + insert( priority, value, result ); +} + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::insert( KeyType priority, const ValueType& value, NodePtr& outResult ) +{ + // Create a new node at a random level. + + outResult = NULL; + NodePtr::unsafeWrite( outResult, new Node( priority, value ) ); + U32 resultNodeLevel = outResult->getLevel(); + + // Link up all the levels. Do this bottom-up instead of + // top-down (as would be the right way for a skiplist) so + // that our list state always remains valid. If going top-down, + // we'll insert nodes with NULL pointers at their lower levels. + + U32 currentLevel = 0; + do + { + while( 1 ) + { + NodePtr nextNode; + NodePtr prevNode; + + scanFromHead( prevNode, nextNode, currentLevel, priority ); + + outResult->getNext( currentLevel ) = nextNode; + if( prevNode->getNext( currentLevel ).trySetFromTo( nextNode, outResult, NodePtr::TAG_FailIfSet ) ) + break; + else + outResult->getNext( currentLevel ) = 0; + } + + currentLevel ++; + } + while( currentLevel <= resultNodeLevel ); +} + +/// Take the item with the highest priority from the queue. +/// +/// @param outValue Reference to where the resulting value should be stored. +/// @param upToPriority Priority limit (inclusive) up to which items are taken from the queue. +/// @return true if there was a matching item in the queue. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +bool ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::takeNext( T& outValue, KeyType upToPriority ) +{ + // Iterate through to the first unmarked node. + + NodePtr prevNode = mHead; + while( 1 ) + { + NodePtr node; + readNext( prevNode, node, 0 ); + + if( node == mTail ) + return false; // End reached. + + bool priorityLimitReached = SORT_MIN_TO_MAX + ? ( upToPriority >= node->getPriority() ) + : ( upToPriority <= node->getPriority() ); + + if( !priorityLimitReached ) + return false; + else + { + // Try to mark the node for deletion. Only if that succeeds, taking the + // node was a success and we can return. If it fails, spin and try again. + + if( node->tryMarkForDeletion() ) + { + helpDelete(); + + // Node is now off the list and will disappear as soon as + // all references held by threads (including this one) + // go out of scope. + + outValue = node->getValue(); + node->clearValue(); + + return true; + } + } + } +} + +/// Update the given references to the next non-deleted node at the given level. +/// refPrev will be updated to reference the immediate predecessor of the next +/// node returned. Note that this can be a node in deleted state. +/// +/// @param refPrev Reference to a node of which the successor node should be +/// returned. Updated to immediate predecessor of refNext on return. +/// @param refNext Reference to update to refer to next non-deleted node on +/// the given level. +/// @param level Skiplist level to operate on. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +inline void ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::readNext( NodePtr& refPrev, NodePtr& refNext, U32 level ) +{ + while( 1 ) + { + refNext = refPrev->getNext( level ); + AssertFatal( refNext != NULL, "ThreadSafePriorityQueue::readNext() - next is NULL" ); + if( !refNext->isMarkedForDeletion() || refNext == mTail ) + break; + + refPrev = refNext; + } +} + +/// Scan for the position at which to insert a node of the given priority. +/// Upon return, the position between refPrev and refNext is the one to insert at. +/// +/// @param refPrev position at which to start scanning; updated to match insert position. +/// @param refNext + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::scan( NodePtr& refPrev, NodePtr& refNext, U32 level, KeyType priority ) +{ + while( 1 ) + { + readNext( refPrev, refNext, level ); + if( refNext == mTail + || ( SORT_MIN_TO_MAX + ? ( refNext->getPriority() > priority ) + : ( refNext->getPriority() < priority ) ) ) + break; + + refPrev = refNext; + } +} + +/// + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::scanFromHead( NodePtr& refPrev, NodePtr& refNext, U32 level, KeyType priority ) +{ + // Purge dead nodes at left end of queue so + // we don't get stuck hitting the same node + // in deletable state over and over again. + helpDelete(); + + S32 currentLevel = MAX_LEVEL - 1; + refPrev = mHead; + do + { + scan( refPrev, refNext, currentLevel, priority ); + currentLevel --; + } + while( currentLevel >= S32( level ) ); +} + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::helpDelete() +{ + // Clean out all the references from head. + // Spin over a given reference on each level until head + // clearly refers to a node in non-deletable state. This + // makes this code work cooperatively with other threads + // doing takeNexts on prior or later nodes while also + // guaranteeing that all next pointers to us will eventually + // disappear. + // + // Note that this is *the only place* where we will be cleaning + // out our lists. + + S32 level = MAX_LEVEL - 1; + do + { + while( 1 ) + { + NodePtr ptr = mHead->getNext( level ); + if( !ptr->isMarkedForDeletion() ) + break; + else + { + NodePtr& next = ptr->getNext( level ); + next.setTag(); + mHead->getNext( level ).trySetFromTo( ptr, next, NodePtr::TAG_Unset ); + AssertFatal( next->getRefCount() >= 2, "ThreadSafePriorityQueue::helpDelete() - invalid refcount" ); + } + } + + level --; + } + while( level >= 0 ); +} + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +inline ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::Node( KeyType priority, const ValueType& value ) + : Parent( false ), + mPriority( priority ), + mValue( value ) +{ + dMemset( mNext, 0, sizeof( Node* ) * ( getLevel() + 1 ) ); + + // Level is already set by the allocation routines. +} + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::~Node() +{ + for( U32 level = 0; level < ( getLevel() + 1 ); level ++ ) + getNext( level ) = NULL; +} + +/// Return the skip list level the node is at. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +inline U32 ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::getLevel() +{ + // Mask out the deletion request bit. + + return ( mLevel & 0x7FFFFFFF ); +} + +/// Return the successor node at the given level. +/// @param level The level of the desired successor node; must be within the node's level bounds. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +inline typename ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::NodePtr& ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::getNext( U32 level ) +{ + return *reinterpret_cast< NodePtr* >( &mNext[ level ] ); +} + +/// Return true if the node is marked to be deleted. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +inline bool ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::isMarkedForDeletion() +{ + return ( mLevel & 0x80000000 ); +} + +/// Attempt to mark the node for deletion. If the mark bit has not yet been set +/// and setting it on the current thread succeeds, returns true. +/// +/// @return true, if the marking succeeded. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +inline bool ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::tryMarkForDeletion() +{ + U32 oldVal = mLevel & 0x7FFFFFFF; + U32 newVal = oldVal | 0x80000000; + + return ( dCompareAndSwap( mLevel, oldVal, newVal ) ); +} + +/// Choose a random level. +/// +/// The chosen level depends on the given PROBABILISTIC_BIAS and MAX_LEVEL, +/// but is not affected by the actual number of nodes in a queue. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +U32 ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::randomLevel() +{ + U32 level = 0; + while( Platform::getRandom() < ( ( ( F32 ) PROBABILISTIC_BIAS ) / 100 ) && level < ( MAX_LEVEL - 1 ) ) + level ++; + return level; +} + +/// Allocate a new node. +/// The node comes with a reference count of one and its level already set. +/// +/// @param level The level to allocate the node at. If this is -1, a random level is chosen. +/// @return a new node. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void* ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::operator new( size_t size, S32 level ) +{ + if( level == -1 ) + level = randomLevel(); + + Node* node = 0; + while( 1 ) + { + // Try to take a node from the freelist. If there's none, + // allocate a new one. + + if( !smFreeLists[ level ].mDestroyed ) + node = Node::safeRead( smFreeLists[ level ].mNodes ); + + if( !node ) + { + node = ( Node* ) dMalloc( sizeof( Node ) + sizeof( Node* ) * level ); + dMemset( node, 0, sizeof( Node ) ); + node->mLevel = level; + node->addRef(); + break; + } + else if( dCompareAndSwap( smFreeLists[ level ].mNodes, node, node->mNext[ 0 ] ) ) + { + node->clearLowestBit(); + break; + } + else + node->release(); // Other thread was quicker than us; release. + } + + AssertFatal( node->getRefCount() != 0, "ThreadSafePriorityQueue::new Node() - invalid refcount" ); + AssertFatal( ( node->getRefCount() % 2 ) == 0, "ThreadSafePriorityQueue::new Node() - invalid refcount" ); + return node; +} + +/// Reclaim a node. +/// +/// @param node The node to reclaim. Must refer to a Node instance. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::operator delete( void* ptr ) +{ + //TODO: limit number of nodes kept + + Node* node = ( Node* ) ptr; + U32 level = node->getLevel(); + node->mLevel = level; // Reset the node's deletion bit. + + while( !smFreeLists[ level ].mDestroyed ) + { + // Put the node on the freelist. + + Node* freeList = smFreeLists[ level ].mNodes; + node->mNext[ 0 ] = freeList; + + if( dCompareAndSwap( smFreeLists[ level ].mNodes, freeList, node ) ) + { + node = NULL; + break; + } + } + + if( node ) + dFree( node ); +} + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::Node::FreeList::~FreeList() +{ + mDestroyed = true; + while( mNodes ) + { + //FIXME: could leak some bytes under unfortunate circumstances (this in + // combination with mDestroyed is a dependent write) + + Node* next = mNodes; + if( dCompareAndSwap( mNodes, next, next->mNext[ 0 ] ) ) + dFree( next ); + } +} + +//-------------------------------------------------------------------------- +// ThreadSafePriorityQueueWithUpdate. +//-------------------------------------------------------------------------- + +/// Fast, lock-free priority queue implementation for concurrent access that +/// performs dynamic re-prioritization of items. +/// +/// Within the bounds of a set update interval UPDATE_INTERVAL, the takeNext +/// method is guaranteed to always return the item that has the highest priority +/// at the time the method is called rather than at the time items were inserted +/// into the queue. +/// +/// Values placed on the queue must implement the following interface: +/// +/// @code +/// template< typename K > +/// struct IThreadSafePriorityQueueItem +/// { +/// // Default constructor. +/// IThreadSafePriorityQueueItem(); +/// +/// // Return the current priority. +/// // This must run normally even if the item is already dead. +/// K getPriority(); +/// +/// // Return true if the item is still meant to be waiting in the queue. +/// bool isAlive(); +/// }; +/// @endcode + +template< typename T, typename K, bool SORT_MIN_TO_MAX = false, U32 MAX_LEVEL = 4, U32 PROBABILISTIC_BIAS = 50 > +struct ThreadSafePriorityQueueWithUpdate : public ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS > +{ + + typedef T ValueType; + typedef K KeyType; + + enum { DEFAULT_UPDATE_INTERVAL = 256 }; + + ThreadSafePriorityQueueWithUpdate( U32 updateInterval = DEFAULT_UPDATE_INTERVAL ); + + void insert( KeyType priority, const T& value ); + bool takeNext( T& outValue, KeyType upToPriority = ( SORT_MIN_TO_MAX ? TypeTraits< KeyType >::MAX : TypeTraits< KeyType >::MIN ) ); + + U32 getUpdateInterval() const; + void setUpdateInterval( U32 value ); + + KeyType getTimeBasedPriorityBoost() const; + void setTimeBasedPriorityBoost( KeyType value ); + + void updatePriorities(); + +protected: + typedef ThreadSafePriorityQueue< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS > Parent; + typedef U32 TickType; + typedef typename Parent::NodePtr NodePtr; + + U32 mUpdateInterval; + KeyType mPriorityBoost; ///< If this is non-zero, priorities will be boosted by this amount each update. This can be used to prevent constant high-priority inserts to starve low-priority items already in the queue. + + /// Work queue for node updates. + ThreadSafePriorityQueue< NodePtr, TickType, true, MAX_LEVEL, PROBABILISTIC_BIAS > mUpdateQueue; + + TickType getTick() { return Platform::getRealMilliseconds(); } +}; + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::ThreadSafePriorityQueueWithUpdate( U32 updateInterval ) + : mUpdateInterval( updateInterval ), + mPriorityBoost( TypeTraits< KeyType >::ZERO ) +{ +} + +/// Return the current update interval in milliseconds. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +U32 ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::getUpdateInterval() const +{ + return mUpdateInterval; +} + +/// Set update interval of queue to given value. +/// +/// Call this method on the main thread only. +/// +/// @param value Time between priority updates in milliseconds. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::setUpdateInterval( U32 value ) +{ + mUpdateInterval = value; +} + +/// Return the delta to apply to priorities on each update. +/// Set to zero to deactivate time-based priority adjustments. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +K ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::getTimeBasedPriorityBoost() const +{ + return mPriorityBoost; +} + +/// Set the delta for time-based priority adjustments to the given value. +/// +/// Call this method on the main thread only. +/// +/// @param value The new priority adjustment value. + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::setTimeBasedPriorityBoost( KeyType value ) +{ + mPriorityBoost = value; +} + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::insert( KeyType priority, const ValueType& value ) +{ + NodePtr node; + Parent::insert( priority, value, node ); + mUpdateQueue.insert( getTick() + getUpdateInterval(), node ); +} + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +bool ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::takeNext( T& outValue, KeyType upToPriority ) +{ + updatePriorities(); + + bool result = false; + do + { + result = Parent::takeNext( outValue, upToPriority ); + } + while( result && !outValue.isAlive() ); + + return result; +} + +/// + +template< typename T, typename K, bool SORT_MIN_TO_MAX, U32 MAX_LEVEL, U32 PROBABILISTIC_BIAS > +void ThreadSafePriorityQueueWithUpdate< T, K, SORT_MIN_TO_MAX, MAX_LEVEL, PROBABILISTIC_BIAS >::updatePriorities() +{ + TickType currentTime = getTick(); + U32 numNodesUpdated = 0; + U32 numNodesDead = 0; + U32 numNodesChanged = 0; + + NodePtr node; + while( mUpdateQueue.takeNext( node, currentTime ) ) + { + numNodesUpdated ++; + + // Since we're updating nodes on the update queue only periodically, + // their associated values or main queue nodes may have died in the + // meantime. If so, we just discard them here. + + if( node->getValue().isAlive() + && !node->isMarkedForDeletion() ) + { + KeyType newPriority = node->getValue().getPriority() + getTimeBasedPriorityBoost(); + if( newPriority != node->getPriority() ) + { + // Node is outdated. Reinsert with new priority and mark the + // old node for deletion. + + insert( newPriority, node->getValue() ); + node->tryMarkForDeletion(); + numNodesChanged ++; + } + else + { + // Node is still current. Just move to end. + + mUpdateQueue.insert( currentTime + getUpdateInterval(), node ); + } + } + else + numNodesDead ++; + } + +#if 0 //defined( TORQUE_DEBUG ) + if( numNodesUpdated ) + Platform::outputDebugString( "[ThreadSafePriorityQueueWithUpdate] updated %i nodes (%i changed, %i dead)", + numNodesUpdated, numNodesChanged, numNodesDead ); +#endif +} + +// Re-enable TMM if necessary. +#include "platform/tmm_on.h" + +#endif // _THREADSAFEPRIORITYQUEUE_H_ diff --git a/platform/threads/threadSafeRefCount.h b/platform/threads/threadSafeRefCount.h new file mode 100644 index 0000000..018cb75 --- /dev/null +++ b/platform/threads/threadSafeRefCount.h @@ -0,0 +1,363 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _THREADSAFEREFCOUNT_H_ +#define _THREADSAFEREFCOUNT_H_ + +#ifndef _PLATFORMINTRINSICS_H_ +# include "platform/platformIntrinsics.h" +#endif +#ifndef _TYPETRAITS_H_ +# include "platform/typetraits.h" +#endif + + +/// @file +/// Templated code for concurrent reference-counting. +/// +/// Part of this code is based on work by J.D. Valois, Michael M. Maged, +/// and Scott L. Michael. + + +//-------------------------------------------------------------------------- +// ThreadSafeRefCount. +//-------------------------------------------------------------------------- + +/// Baseclass for concurrently reference-counted objects. +/// +/// @note NOTE that freshly instantiated objects start out with a reference +/// count of ZERO! Depending on how this class is used, this may not +/// be desirable, so override this behavior in constructors if necessary. +/// +/// @param T the class being reference counted; this is passed to this class, +/// so it can call the correct destructor without having to force users +/// to have virtual methods + +template< class T, class DeletePolicy = DeleteSingle > +class ThreadSafeRefCount +{ + public: + + typedef void Parent; + + ThreadSafeRefCount() + : mRefCount( 0 ) {} + ThreadSafeRefCount( bool noSet ) {} + + bool isShared() const; + U32 getRefCount() const; + void addRef(); + void release(); + void clearLowestBit(); + static T* safeRead( T* const volatile& refPtr ); + + protected: + + U32 mRefCount; ///< Reference count and claim bit. Note that this increments in steps of two. + + static U32 decrementAndTestAndSet( U32& refCount ); +}; + +/// @return true if the object is referenced by more than a single +/// reference. + +template< class T, class DeletePolicy > +inline bool ThreadSafeRefCount< T, DeletePolicy >::isShared() const +{ + return ( mRefCount > 3 ); +} + +/// Get the current reference count. This method is mostly meant for +/// debugging and should not normally be used. + +template< class T, class DeletePolicy > +inline U32 ThreadSafeRefCount< T, DeletePolicy >::getRefCount() const +{ + return mRefCount; +} + +/// Increase the reference count on the object. + +template< class T, class DeletePolicy > +inline void ThreadSafeRefCount< T, DeletePolicy >::addRef() +{ + dFetchAndAdd( mRefCount, 2 ); +} + +/// Decrease the object's reference count and delete the object, if the count +/// drops to zero and claiming the object by the current thread succeeds. + +template< class T, class DeletePolicy > +inline void ThreadSafeRefCount< T, DeletePolicy >::release() +{ + AssertFatal( mRefCount != 0, "ThreadSafeRefCount::release() - refcount of zero" ); + if( decrementAndTestAndSet( mRefCount ) != 0 ) + DeletePolicy::destroy( ( T* ) this ); +} + +/// Dereference a reference-counted pointer in a multi-thread safe way. + +template< class T, class DeletePolicy > +T* ThreadSafeRefCount< T, DeletePolicy >::safeRead( T* const volatile& refPtr ) +{ + while( 1 ) + { + // Support tagged pointers here. + + T* ptr = TypeTraits< T* >::getUntaggedPtr( refPtr ); + if( !ptr ) + return 0; + + ptr->addRef(); + if( ptr == TypeTraits< T* >::getUntaggedPtr( refPtr ) ) + return ptr; + else + ptr->release(); + } +} + +/// Decrement the given reference count. Return 1 if the count dropped to zero +/// and the claim bit has been successfully set; return 0 otherwise. + +template< class T, class DeletePolicy > +U32 ThreadSafeRefCount< T, DeletePolicy >::decrementAndTestAndSet( U32& refCount ) +{ + U32 oldVal; + U32 newVal; + + do + { + oldVal = refCount; + newVal = oldVal - 2; + + AssertFatal( oldVal >= 2, + "ThreadSafeRefCount::decrementAndTestAndSet() - invalid refcount" ); + + if( newVal == 0 ) + newVal = 1; + } + while( !dCompareAndSwap( refCount, oldVal, newVal ) ); + + return ( ( oldVal - newVal ) & 1 ); +} + +/// + +template< class T, class DeletePolicy > +inline void ThreadSafeRefCount< T, DeletePolicy >::clearLowestBit() +{ + AssertFatal( mRefCount % 2 != 0, "ThreadSafeRefCount::clearLowestBit() - invalid refcount" ); + + U32 oldVal; + U32 newVal; + + do + { + oldVal = mRefCount; + newVal = oldVal - 1; + } + while( !dCompareAndSwap( mRefCount, oldVal, newVal ) ); +} + +//-------------------------------------------------------------------------- +// ThreadSafeRef. +//-------------------------------------------------------------------------- + +/// Reference to a concurrently reference-counted object. +/// +/// This class takes care of the reference-counting as well as protecting +/// the reference itself from concurrent operations. +/// +/// Tagging allows the pointer contained in the reference to be flagged. +/// Tag state is preserved through updates to the reference. +/// +/// @note If you directly assign a freshly created object with a reference +/// count of zero to a ThreadSafeRef, make absolutely sure the ThreadSafeRef +/// is accessed only by a single thread. Otherwise there's a risk of the +/// object being released and freed in midst of trying to set the reference. +template< class T > +class ThreadSafeRef +{ + public: + + enum ETag + { + TAG_PreserveOld, ///< Preserve existing tagging state when changing pointer. + TAG_PreserveNew, ///< Preserve tagging state of new pointer when changing pointer. + TAG_Set, ///< Set tag when changing pointer; okay if already set. + TAG_Unset, ///< Unset tag when changing pointer; okay if already unset. + TAG_SetOrFail, ///< Set tag when changing pointer; fail if already set. + TAG_UnsetOrFail, ///< Unset tag when changing pointer; fail if already unset. + TAG_FailIfSet, ///< Fail changing pointer when currently tagged. + TAG_FailIfUnset ///< Fail changing pointer when currently untagged. + }; + + typedef ThreadSafeRef< T > ThisType; + + ThreadSafeRef() : mPtr( 0 ) {} + ThreadSafeRef( T* ptr ) : mPtr( ThreadSafeRefCount< T >::safeRead( ptr ) ) {} + ThreadSafeRef( const ThisType& ref ) : mPtr( ThreadSafeRefCount< T >::safeRead( ref.mPtr ) ) {} + ~ThreadSafeRef() + { + T* ptr = NULL; + while( !trySetFromTo( mPtr, ptr ) ); + } + + T* ptr() const { return getUntaggedPtr( mPtr ) ; } + void setTag() { while( !trySetFromTo( mPtr, mPtr, TAG_Set ) ); } + bool isTagged() const { return isTaggedPtr( mPtr ); } + bool trySetFromTo( T* oldVal, T* const volatile& newVal, ETag tag = TAG_PreserveOld ); + bool trySetFromTo( T* oldVal, const ThisType& newVal, ETag tag = TAG_PreserveOld ); + bool trySetFromTo( const ThisType& oldVal, const ThisType& newVal, ETag tag = TAG_PreserveOld ); + static void unsafeWrite( ThisType& ref, T* ptr ); + static T* safeRead( T* const volatile& refPtr ) { return ThreadSafeRefCount< T >::safeRead( refPtr ); } + + bool operator ==( T* ptr ) const; + bool operator ==( const ThisType& ref ) const; + bool operator !=( T* ptr ) const { return !( *this == ptr ); } + bool operator !=( const ThisType& ref ) const { return !( *this == ref ); } + ThisType& operator =( T* ptr ); + ThisType& operator =( const ThisType& ref ); + + bool operator !() const { return ( ptr() == 0 ); } + T& operator *() const { return *ptr(); } + T* operator ->() const { return ptr(); } + operator T*() const { return ptr(); } + + protected: + + T* volatile mPtr; + + static bool isTaggedPtr( T* ptr ) { return TypeTraits< T* >::isTaggedPtr( ptr ); } + static T* getTaggedPtr( T* ptr ) { return TypeTraits< T* >::getTaggedPtr( ptr ); } + static T* getUntaggedPtr( T* ptr ) { return TypeTraits< T* >::getUntaggedPtr( ptr ); } +}; + +/// Update the reference from pointing to oldVal to point to newVal. +/// Do so in a thread-safe way. +/// +/// This operation will only succeed, if, when doing the pointer-swapping, +/// the reference still points to oldVal. If, however, the reference +/// has been changed in the meantime by another thread, the operation will +/// fail. +/// +/// @param oldVal The pointer assumed to currently be contained in this ThreadSafeRef. +/// @param newVal The pointer to store in this ThreadSafeRef. +/// @param tag Operation to perform on the reference's tag field. +/// +/// @return true, if the reference now points to newVal. + +template< class T > +bool ThreadSafeRef< T >::trySetFromTo( T* oldVal, T* const volatile& newVal, ETag tag ) +{ + bool setTag = false; + bool getTag = false; + bool isTagged = isTaggedPtr( oldVal ); + + switch( tag ) + { + case TAG_PreserveOld: setTag = isTaggedPtr( oldVal ); break; + case TAG_PreserveNew: setTag = isTaggedPtr( newVal ); break; + case TAG_Set: setTag = true; break; + case TAG_Unset: setTag = false; break; + case TAG_SetOrFail: setTag = true; getTag = true; break; + case TAG_UnsetOrFail: setTag = false; getTag = true; break; + case TAG_FailIfSet: if( isTagged ) return false; break; + case TAG_FailIfUnset: if( !isTagged ) return false; break; + } + + T* newValPtr = ( setTag + ? getTaggedPtr( ThreadSafeRefCount< T >::safeRead( newVal ) ) + : getUntaggedPtr( ThreadSafeRefCount< T >::safeRead( newVal ) ) ); + + if( dCompareAndSwap( mPtr, + ( getTag + ? ( setTag + ? getUntaggedPtr( oldVal ) + : getTaggedPtr( oldVal ) ) + : oldVal ), + newValPtr ) ) + { + if( getUntaggedPtr( oldVal ) ) + getUntaggedPtr( oldVal )->release(); + return true; + } + else + { + if( getUntaggedPtr( newValPtr ) ) + getUntaggedPtr( newValPtr )->release(); + return false; + } +} + +template< class T > +inline bool ThreadSafeRef< T >::trySetFromTo( T* oldVal, const ThisType& newVal, ETag tag ) +{ + return trySetFromTo( oldVal, newVal.mPtr, tag ); +} + +template< class T > +inline bool ThreadSafeRef< T >::trySetFromTo( const ThisType& oldVal, const ThisType& newVal, ETag tag ) +{ + return trySetFromTo( oldVal.mPtr, newVal.mPtr, tag ); +} + +/// Update ref to point to ptr but do not release an existing +/// reference held by ref nor do the operation in a thread-safe way. +/// +/// This method is only for when you absolutely know that your +/// thread is the only thread operating on a reference and you +/// are keeping track of reference counts yourself. +/// +/// @param ref The reference to update. +/// @param ptr The new pointer to store in ref. + +template< class T > +inline void ThreadSafeRef< T >::unsafeWrite( ThisType& ref, T* ptr ) +{ + ref.mPtr = ptr; +} + +template< class T > +inline bool ThreadSafeRef< T >::operator ==( T* p ) const +{ + return ( ptr() == p ); +} + +template< class T > +inline bool ThreadSafeRef< T >::operator ==( const ThisType& ref ) const +{ + return ( ptr() == ref.ptr() ); +} + +template< class T > +inline ThreadSafeRef< T >& ThreadSafeRef< T >::operator =( T* ptr ) +{ + while( !trySetFromTo( mPtr, ptr, TAG_PreserveNew ) ); + return *this; +} + +template< class T > +inline ThreadSafeRef< T >& ThreadSafeRef< T >::operator =( const ThisType& ref ) +{ + while( !trySetFromTo( mPtr, ref, TAG_PreserveNew ) ); + return *this; +} + + +template< typename T > +struct TypeTraits< ThreadSafeRef< T > > : public TypeTraits< T* > {}; +template< typename T > +inline T& Deref( ThreadSafeRef< T >& ref ) +{ + return *ref; +} +template< typename T > +inline T& Deref( const ThreadSafeRef< T >& ref ) +{ + return *ref; +} + +#endif // _THREADSAFEREFCOUNT_H_ diff --git a/platform/tmm_off.h b/platform/tmm_off.h new file mode 100644 index 0000000..8e0a12d --- /dev/null +++ b/platform/tmm_off.h @@ -0,0 +1 @@ +#undef new diff --git a/platform/tmm_on.h b/platform/tmm_on.h new file mode 100644 index 0000000..d2118eb --- /dev/null +++ b/platform/tmm_on.h @@ -0,0 +1,3 @@ +#ifndef TORQUE_DISABLE_MEMORY_MANAGER +# define new _new +#endif diff --git a/platform/types.codewarrior.h b/platform/types.codewarrior.h new file mode 100644 index 0000000..e33b9c2 --- /dev/null +++ b/platform/types.codewarrior.h @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef INCLUDED_TYPES_CODEWARRIOR_H +#define INCLUDED_TYPES_CODEWARRIOR_H + +#pragma once + +// If using the IDE detect if DEBUG build was requested +#if __ide_target("Torque-W32-Debug") + #define TORQUE_DEBUG +#elif __ide_target("Torque-MacCarb-Debug") + #define TORQUE_DEBUG +#elif __ide_target("Torque-MacX-Debug") + #define TORQUE_DEBUG +#endif + + +//-------------------------------------- +// Types +typedef signed long long S64; ///< Compiler independent Signed 64-bit integer +typedef unsigned long long U64; ///< Compiler independent Unsigned 64-bit integer + + + +//-------------------------------------- +// Compiler Version +#define TORQUE_COMPILER_CODEWARRIOR __MWERKS__ + +#define TORQUE_COMPILER_STRING "CODEWARRIOR" + + +//-------------------------------------- +// Identify the Operating System +#if defined(_WIN32) +# define TORQUE_OS_STRING "Win32" +# define TORQUE_OS_WIN32 +# include "platform/types.win32.h" + +#elif defined(macintosh) || defined(__APPLE__) +# define TORQUE_OS_STRING "Mac" +# define TORQUE_OS_MAC +# if defined(__MACH__) +# define TORQUE_OS_MAC +# endif +# include "platform/types.ppc.h" +#else +# error "CW: Unsupported Operating System" +#endif + + +//-------------------------------------- +// Identify the CPU +#if defined(_M_IX86) +# define TORQUE_CPU_STRING "x86" +# define TORQUE_CPU_X86 +# define TORQUE_LITTLE_ENDIAN +# define TORQUE_SUPPORTS_NASM +# define TORQUE_SUPPORTS_VC_INLINE_X86_ASM + + // Compiling with the CW IDE we cannot use NASM :( +# if __ide_target("Torque-W32-Debug") +# undef TORQUE_SUPPORTS_NASM +# elif __ide_target("Torque-W32-Release") +# undef TORQUE_SUPPORTS_NASM +# endif + +#elif defined(__POWERPC__) +# define TORQUE_CPU_STRING "PowerPC" +# define TORQUE_CPU_PPC +# define TORQUE_BIG_ENDIAN + +#else +# error "CW: Unsupported Target CPU" +#endif + + +#endif // INCLUDED_TYPES_CODEWARRIOR_H + diff --git a/platform/types.gcc.h b/platform/types.gcc.h new file mode 100644 index 0000000..353b2d0 --- /dev/null +++ b/platform/types.gcc.h @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESGCC_H +#define _TYPESGCC_H + + +// For additional information on GCC predefined macros +// http://gcc.gnu.org/onlinedocs/gcc-3.0.2/cpp.html + + +//-------------------------------------- +// Types +typedef signed long long S64; +typedef unsigned long long U64; + + +//-------------------------------------- +// Compiler Version +#define TORQUE_COMPILER_GCC (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) + + +//-------------------------------------- +// Identify the compiler string + +#if defined(__MINGW32__) +# define TORQUE_COMPILER_STRING "GCC (MinGW)" +# define TORQUE_COMPILER_MINGW +#elif defined(__CYGWIN__) +# define TORQUE_COMPILER_STRING "GCC (Cygwin)" +# define TORQUE_COMPILER_MINGW +#else +# define TORQUE_COMPILER_STRING "GCC " +#endif + + +//-------------------------------------- +// Identify the Operating System +#if defined(__WIN32__) || defined(_WIN32) +# define TORQUE_OS_STRING "Win32" +# define TORQUE_OS_WIN32 +# define TORQUE_SUPPORTS_NASM +# define TORQUE_SUPPORTS_GCC_INLINE_X86_ASM +# include "platform/types.win32.h" + +#elif defined(SN_TARGET_PS3) +# define TORQUE_OS_STRING "PS3" +# define TORQUE_OS_PS3 +# include "platform/types.posix.h" + +#elif defined(linux) +# define TORQUE_OS_STRING "Linux" +# define TORQUE_OS_LINUX +# define TORQUE_SUPPORTS_NASM +# define TORQUE_SUPPORTS_GCC_INLINE_X86_ASM +# include "platform/types.posix.h" + +#elif defined(__OpenBSD__) +# define TORQUE_OS_STRING "OpenBSD" +# define TORQUE_OS_OPENBSD +# define TORQUE_SUPPORTS_NASM +# define TORQUE_SUPPORTS_GCC_INLINE_X86_ASM +# include "platform/types.posix.h" + +#elif defined(__FreeBSD__) +# define TORQUE_OS_STRING "FreeBSD" +# define TORQUE_OS_FREEBSD +# define TORQUE_SUPPORTS_NASM +# define TORQUE_SUPPORTS_GCC_INLINE_X86_ASM +# include "platform/types.posix.h" + +#elif defined(__APPLE__) +# define TORQUE_OS_STRING "MacOS X" +# define TORQUE_OS_MAC +# include "platform/types.mac.h" +# if defined(i386) +// Disabling ASM on XCode for shared library build code relocation issues +// This could be reconfigured for static builds, though minimal impact +//# define TORQUE_SUPPORTS_NASM +# endif +#else +# error "GCC: Unsupported Operating System" +#endif + + +//-------------------------------------- +// Identify the CPU +#if defined(i386) +# define TORQUE_CPU_STRING "Intel x86" +# define TORQUE_CPU_X86 +# define TORQUE_LITTLE_ENDIAN + +#elif defined(__ppc__) +# define TORQUE_CPU_STRING "PowerPC" +# define TORQUE_CPU_PPC +# define TORQUE_BIG_ENDIAN + +#elif defined(SN_TARGET_PS3) +# define TORQUE_CPU_STRING "PowerPC" +# define TORQUE_CPU_PPC +# define TORQUE_BIG_ENDIAN + +#else +# error "GCC: Unsupported Target CPU" +#endif + +#ifndef Offset +/// Offset macro: +/// Calculates the location in memory of a given member x of class cls from the +/// start of the class. Need several definitions to account for various +/// flavors of GCC. + +// now, for each compiler type, define the Offset macros that should be used. +// The Engine code usually uses the Offset macro, but OffsetNonConst is needed +// when a variable is used in the indexing of the member field (see +// TSShapeConstructor::initPersistFields for an example) + +// compiler is non-GCC, or gcc < 3 +#if (__GNUC__ < 3) +#define Offset(x, cls) _Offset_Normal(x, cls) +#define OffsetNonConst(x, cls) _Offset_Normal(x, cls) + +// compiler is GCC 3 with minor version less than 4 +#elif defined(TORQUE_COMPILER_GCC) && (__GNUC__ == 3) && (__GNUC_MINOR__ < 4) +#define Offset(x, cls) _Offset_Variant_1(x, cls) +#define OffsetNonConst(x, cls) _Offset_Variant_1(x, cls) + +// compiler is GCC 3 with minor version greater than 4 +#elif defined(TORQUE_COMPILER_GCC) && (__GNUC__ == 3) && (__GNUC_MINOR__ >= 4) +#include +#define Offset(x, cls) _Offset_Variant_2(x, cls) +#define OffsetNonConst(x, cls) _Offset_Variant_1(x, cls) + +// compiler is GCC 4 +#elif defined(TORQUE_COMPILER_GCC) && (__GNUC__ == 4) +#include +#define Offset(x, cls) _Offset_Normal(x, cls) +#define OffsetNonConst(x, cls) _Offset_Variant_1(x, cls) + +#endif +#endif + +#endif // INCLUDED_TYPES_GCC_H + diff --git a/platform/types.h b/platform/types.h new file mode 100644 index 0000000..09d1813 --- /dev/null +++ b/platform/types.h @@ -0,0 +1,275 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TORQUE_TYPES_H_ +#define _TORQUE_TYPES_H_ + +//------------------------------------------------------------------------------ +//-------------------------------------- Basic Types... + +typedef signed char S8; ///< Compiler independent Signed Char +typedef unsigned char U8; ///< Compiler independent Unsigned Char + +typedef signed short S16; ///< Compiler independent Signed 16-bit short +typedef unsigned short U16; ///< Compiler independent Unsigned 16-bit short + +typedef signed int S32; ///< Compiler independent Signed 32-bit integer +typedef unsigned int U32; ///< Compiler independent Unsigned 32-bit integer + +typedef float F32; ///< Compiler independent 32-bit float +typedef double F64; ///< Compiler independent 64-bit float + +struct EmptyType {}; ///< "Null" type used by templates + +#define TORQUE_UNUSED(var) (void)var + +//------------------------------------------------------------------------------ +//------------------------------------- String Types + +typedef char UTF8; ///< Compiler independent 8 bit Unicode encoded character +typedef unsigned short UTF16; ///< Compiler independent 16 bit Unicode encoded character +typedef unsigned int UTF32; ///< Compiler independent 32 bit Unicode encoded character + +typedef const char* StringTableEntry; + +//------------------------------------------------------------------------------ +//-------------------------------------- Type constants... +#define __EQUAL_CONST_F F32(0.000001) ///< Constant float epsilon used for F32 comparisons + +static const F32 Float_One = F32(1.0); ///< Constant float 1.0 +static const F32 Float_Half = F32(0.5); ///< Constant float 0.5 +static const F32 Float_Zero = F32(0.0); ///< Constant float 0.0 +static const F32 Float_Pi = F32(3.14159265358979323846); ///< Constant float PI +static const F32 Float_2Pi = F32(2.0 * 3.14159265358979323846); ///< Constant float 2*PI +static const F32 Float_InversePi = F32(1.0 / 3.14159265358979323846); ///< Constant float 1 / PI +static const F32 Float_HalfPi = F32(0.5 * 3.14159265358979323846); ///< Constant float 1/2 * PI +static const F32 Float_2InversePi = F32(2.0 / 3.14159265358979323846);///< Constant float 2 / PI +static const F32 Float_Inverse2Pi = F32(0.5 / 3.14159265358979323846);///< Constant float 0.5 / PI + +static const F32 Float_Sqrt2 = F32(1.41421356237309504880f); ///< Constant float sqrt(2) +static const F32 Float_SqrtHalf = F32(0.7071067811865475244008443f); ///< Constant float sqrt(0.5) + +static const S8 S8_MIN = S8(-128); ///< Constant Min Limit S8 +static const S8 S8_MAX = S8(127); ///< Constant Max Limit S8 +static const U8 U8_MAX = U8(255); ///< Constant Max Limit U8 + +static const S16 S16_MIN = S16(-32768); ///< Constant Min Limit S16 +static const S16 S16_MAX = S16(32767); ///< Constant Max Limit S16 +static const U16 U16_MAX = U16(65535); ///< Constant Max Limit U16 + +static const S32 S32_MIN = S32(-2147483647 - 1); ///< Constant Min Limit S32 +static const S32 S32_MAX = S32(2147483647); ///< Constant Max Limit S32 +static const U32 U32_MAX = U32(0xffffffff); ///< Constant Max Limit U32 + +static const F32 F32_MIN = F32(1.175494351e-38F); ///< Constant Min Limit F32 +static const F32 F32_MAX = F32(3.402823466e+38F); ///< Constant Max Limit F32 + +// define all the variants of Offset that we might use +#define _Offset_Normal(x, cls) ((dsize_t)((const char *)&(((cls *)1)->x)-(const char *)1)) +#define _Offset_Variant_1(x, cls) ((int)(&((cls *)1)->x) - 1) +#define _Offset_Variant_2(x, cls) offsetof(cls, x) // also requires #include + +//-------------------------------------- +// Identify the compiler being used + +// PC-lint +#if defined(_lint) +# include "platform/types.lint.h" +// Metrowerks CodeWarrior +#elif defined(__MWERKS__) +# include "platform/types.codewarrior.h" +// Microsoft Visual C++/Visual.NET +#elif defined(_MSC_VER) +# include "platform/types.visualc.h" +// GNU GCC +#elif defined(__GNUC__) +# include "platform/types.gcc.h" +#else +# error "Unknown Compiler" +#endif + + +//-------------------------------------- Some all-around useful inlines and globals +// + +/// Returns power of 2 number which is as small as possible but +/// still greater than or equal to input number. Note: returns 0 +/// for an input of 0 even though that is not a power of 2. +/// @param num Any U32 +inline U32 getNextPow2(U32 num) +{ + // Taken from: http://graphics.stanford.edu/~seander/bithacks.html + + num--; + num |= num >> 1; + num |= num >> 2; + num |= num >> 4; + num |= num >> 8; + num |= num >> 16; + num++; + + return num; +} + +/// Return integer log2 of input number (rounding down). So, e.g., +/// getBinLog2(7) == 2 whereas getBinLog2(8) == 3. If known +/// @param num Any U32 +/// @param knownPow2 Is num a known power of 2? +inline U32 getBinLog2(U32 num, bool knownPow2 = false) +{ + // Taken from: http://graphics.stanford.edu/~seander/bithacks.html + + static const U32 MultiplyDeBruijnBitPosition[32] = + { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 + }; + + if (!knownPow2) + { + num |= num >> 1; // first round down to power of 2 + num |= num >> 2; + num |= num >> 4; + num |= num >> 8; + num |= num >> 16; + num = (num >> 1) + 1; + } + + return MultiplyDeBruijnBitPosition[(num * 0x077CB531UL) >> 27]; +} + +/// Determines if the given U32 is some 2^n +/// @param num Any U32 +/// @returns true if in_num is a power of two, otherwise false +inline bool isPow2(const U32 num) +{ + return (num & (num - 1)) == 0; +} + +/// Determines the binary logarithm of the next greater power of two of the input number. +inline U32 getNextBinLog2(U32 number) +{ + return getBinLog2(number) + (isPow2(number) ? 0 : 1); +} + +//----------------Many versions of min and max------------- +//---not using template functions because MS VC++ chokes--- + +/// Returns the lesser of the two parameters: a & b. +inline U32 getMin(U32 a, U32 b) +{ + return a>b ? b : a; +} + +/// Returns the lesser of the two parameters: a & b. +inline U16 getMin(U16 a, U16 b) +{ + return a>b ? b : a; +} + +/// Returns the lesser of the two parameters: a & b. +inline U8 getMin(U8 a, U8 b) +{ + return a>b ? b : a; +} + +/// Returns the lesser of the two parameters: a & b. +inline S32 getMin(S32 a, S32 b) +{ + return a>b ? b : a; +} + +/// Returns the lesser of the two parameters: a & b. +inline S16 getMin(S16 a, S16 b) +{ + return a>b ? b : a; +} + +/// Returns the lesser of the two parameters: a & b. +inline S8 getMin(S8 a, S8 b) +{ + return a>b ? b : a; +} + +/// Returns the lesser of the two parameters: a & b. +inline float getMin(float a, float b) +{ + return a>b ? b : a; +} + +/// Returns the lesser of the two parameters: a & b. +inline double getMin(double a, double b) +{ + return a>b ? b : a; +} + +/// Returns the greater of the two parameters: a & b. +inline U32 getMax(U32 a, U32 b) +{ + return a>b ? a : b; +} + +/// Returns the greater of the two parameters: a & b. +inline U16 getMax(U16 a, U16 b) +{ + return a>b ? a : b; +} + +/// Returns the greater of the two parameters: a & b. +inline U8 getMax(U8 a, U8 b) +{ + return a>b ? a : b; +} + +/// Returns the greater of the two parameters: a & b. +inline S32 getMax(S32 a, S32 b) +{ + return a>b ? a : b; +} + +/// Returns the greater of the two parameters: a & b. +inline S16 getMax(S16 a, S16 b) +{ + return a>b ? a : b; +} + +/// Returns the greater of the two parameters: a & b. +inline S8 getMax(S8 a, S8 b) +{ + return a>b ? a : b; +} + +/// Returns the greater of the two parameters: a & b. +inline float getMax(float a, float b) +{ + return a>b ? a : b; +} + +/// Returns the greater of the two parameters: a & b. +inline double getMax(double a, double b) +{ + return a>b ? a : b; +} + +//-------------------------------------- Use this instead of Win32 FOURCC() +// macro... +// +#define makeFourCCTag(ch0, ch1, ch2, ch3) \ + (((U32(ch0) & 0xFF) << 0) | \ + ((U32(ch1) & 0xFF) << 8) | \ + ((U32(ch2) & 0xFF) << 16) | \ + ((U32(ch3) & 0xFF) << 24) ) + +#define makeFourCCString(ch0, ch1, ch2, ch3) { ch0, ch1, ch2, ch3 } + +#define BIT(x) (1 << (x)) ///< Returns value with bit x set (2^x) + +#if defined(TORQUE_OS_WIN32) +#define STDCALL __stdcall +#else +#define STDCALL +#endif + +#endif //_TORQUE_TYPES_H_ diff --git a/platform/types.lint.h b/platform/types.lint.h new file mode 100644 index 0000000..6a6ec0a --- /dev/null +++ b/platform/types.lint.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef TORQUE_TYPES_LINT_H_ +#define TORQUE_TYPES_LINT_H_ + +typedef signed long long S64; +typedef unsigned long long U64; + +typedef unsigned int dsize_t; + +struct FileTime +{ + U32 v1; + U32 v2; +}; + +#define TORQUE_OS_STRING "Lint" +#define TORQUE_CPU_STRING "x86" +#define TORQUE_LITTLE_ENDIAN +#define TORQUE_SUPPORTS_NASM +#define TORQUE_SUPPORTS_VC_INLINE_X86_ASM +#define TORQUE_OS_WIN32 +#define TORQUE_COMPILER_VISUALC 1500 + +#ifndef FN_CDECL +#define FN_CDECL +#endif + +#ifndef Offset +#define Offset(x, cls) _Offset_Normal(x, cls) +#define OffsetNonConst(x, cls) _Offset_Normal(x, cls) +#endif + +#ifndef NULL +#define NULL 0 +#endif + +#endif diff --git a/platform/types.mac.h b/platform/types.mac.h new file mode 100644 index 0000000..9a9fbc3 --- /dev/null +++ b/platform/types.mac.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESPPC_H_ +#define _TYPESPPC_H_ + +///< Calling convention +#define FN_CDECL +#define STDCALL + +// size_t is needed to overload new +// size_t tends to be OS and compiler specific and may need to +// be if/def'ed in the future +typedef unsigned long dsize_t; + + +/** Platform dependent file date-time structure. The defination of this structure + * will likely be different for each OS platform. + * On the PPC is a 64-bit structure for storing the date/time for a file + */ + +// 64-bit structure for storing the date/time for a file +// The date and time, specified in seconds since the unix epoch. +// NOTE: currently, this is only 32-bits in value, so the upper 32 are all zeroes. +typedef U64 FileTime; + +#ifndef NULL +# define NULL (0) +#endif + + +#endif //_TYPESPPC_H_ diff --git a/platform/types.posix.h b/platform/types.posix.h new file mode 100644 index 0000000..81f0a15 --- /dev/null +++ b/platform/types.posix.h @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESPOSIX_H_ +#define _TYPESPOSIX_H_ + + +#define FN_CDECL ///< Calling convention + +// size_t is needed to overload new +// size_t tends to be OS and compiler specific and may need to +// be if/def'ed in the future +typedef unsigned int dsize_t; + + +/** Platform dependent file date-time structure. The defination of this structure + * will likely be different for each OS platform. + */ +typedef S32 FileTime; + + +#ifndef NULL +# define NULL (0) +#endif + + +#endif //_TYPESPOSIX_H_ diff --git a/platform/types.ppc.h b/platform/types.ppc.h new file mode 100644 index 0000000..7b4b76d --- /dev/null +++ b/platform/types.ppc.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESPPC_H_ +#define _TYPESPPC_H_ + +///< Calling convention +#define FN_CDECL + +// size_t is needed to overload new +// size_t tends to be OS and compiler specific and may need to +// be if/def'ed in the future +typedef unsigned long dsize_t; + + +/** Platform dependent file date-time structure. The defination of this structure + * will likely be different for each OS platform. + * On the PPC is a 64-bit structure for storing the date/time for a file + */ + +// 64-bit structure for storing the date/time for a file +// The date and time, specified in seconds since the unix epoch. +// NOTE: currently, this is only 32-bits in value, so the upper 32 are all zeroes. +typedef U64 FileTime; + +#ifndef NULL +# define NULL (0) +#endif + + +#endif //_TYPESPPC_H_ diff --git a/platform/types.visualc.h b/platform/types.visualc.h new file mode 100644 index 0000000..69b258f --- /dev/null +++ b/platform/types.visualc.h @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef INCLUDED_TYPES_VISUALC_H +#define INCLUDED_TYPES_VISUALC_H + + +// For more information on VisualC++ predefined macros +// http://support.microsoft.com/default.aspx?scid=kb;EN-US;q65472 + +//-------------------------------------- +// Types +typedef signed _int64 S64; +typedef unsigned _int64 U64; + + +//-------------------------------------- +// Compiler Version +#define TORQUE_COMPILER_VISUALC _MSC_VER + +//-------------------------------------- +// Identify the compiler string +#if _MSC_VER < 1200 + // No support for old compilers +# error "VC: Minimum VisualC++ 6.0 or newer required" +#else _MSC_VER >= 1200 +# define TORQUE_COMPILER_STRING "VisualC++" +#endif + + +//-------------------------------------- +// Identify the Operating System +#if _XBOX_VER >= 200 +# define TORQUE_OS_STRING "Xenon" +# ifndef TORQUE_OS_XENON +# define TORQUE_OS_XENON +# endif +# include "platform/types.xenon.h" +#elif defined( _XBOX_VER ) +# define TORQUE_OS_STRING "Xbox" +# define TORQUE_OS_XBOX +# include "platform/types.win32.h" +#elif defined(_WIN32) +# define TORQUE_OS_STRING "Win32" +# define TORQUE_OS_WIN32 +# include "platform/types.win32.h" +#else +# error "VC: Unsupported Operating System" +#endif + + +//-------------------------------------- +// Identify the CPU +#if defined(_M_IX86) +# define TORQUE_CPU_STRING "x86" +# define TORQUE_CPU_X86 +# define TORQUE_LITTLE_ENDIAN +# define TORQUE_SUPPORTS_NASM +# define TORQUE_SUPPORTS_VC_INLINE_X86_ASM +#elif defined(TORQUE_OS_XENON) +# define TORQUE_CPU_STRING "ppc" +# define TORQUE_CPU_PPC +# define TORQUE_BIG_ENDIAN +#else +# error "VC: Unsupported Target CPU" +#endif + +#ifndef FN_CDECL +# define FN_CDECL __cdecl ///< Calling convention +#endif + +#define for if(false) {} else for ///< Hack to work around Microsoft VC's non-C++ compliance on variable scoping + +// disable warning caused by memory layer +// see msdn.microsoft.com "Compiler Warning (level 1) C4291" for more details +#pragma warning(disable: 4291) + + +#endif // INCLUDED_TYPES_VISUALC_H + diff --git a/platform/types.win32.h b/platform/types.win32.h new file mode 100644 index 0000000..47dcd5c --- /dev/null +++ b/platform/types.win32.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESWIN32_H_ +#define _TYPESWIN32_H_ + + +#define FN_CDECL __cdecl ///< Calling convention + +// size_t is needed to overload new +// size_t tends to be OS and compiler specific and may need to +// be if/def'ed in the future +typedef unsigned int dsize_t; + + +/// Platform dependent file date-time structure. The definition of this structure +/// will likely be different for each OS platform. +struct FileTime +{ + U32 v1; + U32 v2; +}; + + +#ifndef NULL +# define NULL 0 +#endif + + +#endif //_TYPESWIN32_H_ diff --git a/platform/types.xenon.h b/platform/types.xenon.h new file mode 100644 index 0000000..b5f4b5a --- /dev/null +++ b/platform/types.xenon.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESXENON_H_ +#define _TYPESXENON_H_ + +///< Calling convention +#ifdef FN_CDECL +# undef FN_CDECL +#endif +#define FN_CDECL __cdecl + +// size_t is needed to overload new +// size_t tends to be OS and compiler specific and may need to +// be if/def'ed in the future +typedef size_t dsize_t; + +struct FileTime +{ + U32 v1; + U32 v2; +}; + + +#ifndef NULL +# define NULL (0) +#endif + + +#endif //_TYPESXENON_H_ \ No newline at end of file diff --git a/platform/typesLinux.h b/platform/typesLinux.h new file mode 100644 index 0000000..887fff2 --- /dev/null +++ b/platform/typesLinux.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESLINUX_H_ +#define _TYPESLINUX_H_ + +/* eek. */ +#ifndef NULL +#define NULL 0 +#endif + +#define PLATFORM_LITTLE_ENDIAN + +#define FN_CDECL + +typedef signed char S8; +typedef unsigned char U8; + +typedef signed short S16; +typedef unsigned short U16; + +typedef signed int S32; +typedef unsigned int U32; + +typedef signed long long S64; +typedef unsigned long long U64; + +typedef float F32; +typedef double F64; + +typedef unsigned int dsize_t; + +typedef const char* StringTableEntry; + +typedef S32 FileTime; + +#define __EQUAL_CONST_F F32(0.000001) + +static const F32 Float_One = F32(1.0); +static const F32 Float_Half = F32(0.5); +static const F32 Float_Zero = F32(0.0); +static const F32 Float_Pi = F32(3.14159265358979323846); +static const F32 Float_2Pi = F32(2.0 * 3.14159265358979323846); + +static const S8 S8_MIN = S8(-128); +static const S8 S8_MAX = S8(127); +static const U8 U8_MAX = U8(255); + +static const S16 S16_MIN = S16(-32768); +static const S16 S16_MAX = S16(32767); +static const U16 U16_MAX = U16(65535); + +static const S32 S32_MIN = S32(-2147483647 - 1); +static const S32 S32_MAX = S32(2147483647); +static const U32 U32_MAX = U32(0xffffffff); + +static const F32 F32_MAX = F32(3.402823466e+38F); +static const F32 F32_MIN = F32(1.175494351e-38F); + + +#endif diff --git a/platform/typesPPC.h b/platform/typesPPC.h new file mode 100644 index 0000000..d744256 --- /dev/null +++ b/platform/typesPPC.h @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPES_PPC_H_ +#define _TYPES_PPC_H_ + +// We have to check this. Since every file will eventually wind up including +// this header, but not every header includes a windows or system header... +// +#ifndef NULL +#define NULL 0 +#endif + +// Let's just have this in a nice central location. Again, since every file +// will wind up including this file, we can affect compilation most effectively +// from this location. +// +#define PLATFORM_BIG_ENDIAN + +#define FN_CDECL + + +//------------------------------------------------------------------------------ +//-------------------------------------- Basic Types... + +typedef signed char S8; +typedef unsigned char U8; + +typedef signed short S16; +typedef unsigned short U16; + +typedef signed int S32; +typedef unsigned int U32; + +typedef signed long long S64; +typedef unsigned long long U64; + +typedef float F32; +typedef double F64; + +// size_t is needed to overload new +// size_t tends to be OS and compiler specific and may need to +// be if/def'ed in the future +typedef unsigned long dsize_t; + +typedef const char* StringTableEntry; + +// 64-bit structure for storing the date/time for a file +// The date and time, specified in seconds since the unix epoch. +// NOTE: currently, this is only 32-bits in value, so the upper 32 are all zeroes. +typedef U64 FileTime; + + +//------------------------------------------------------------------------------ +//-------------------------------------- Type constants... +#define __EQUAL_CONST_F F32(0.000001) + +static const F32 Float_One = F32(1.0); +static const F32 Float_Half = F32(0.5); +static const F32 Float_Zero = F32(0.0); +static const F32 Float_Pi = F32(3.14159265358979323846); +static const F32 Float_2Pi = F32(2.0 * 3.14159265358979323846); + +static const S8 S8_MIN = S8(-128); +static const S8 S8_MAX = S8(127); +static const U8 U8_MAX = U8(255); + +static const S16 S16_MIN = S16(-32768); +static const S16 S16_MAX = S16(32767); +static const U16 U16_MAX = U16(65535); + +static const S32 S32_MIN = S32(-2147483647 - 1); +static const S32 S32_MAX = S32(2147483647); +static const U32 U32_MAX = U32(0xffffffff); + +static const F32 F32_MAX = F32(3.402823466e+38F); +static const F32 F32_MIN = F32(1.175494351e-38F); + + +#endif //_TYPES_PPC_H_ diff --git a/platform/typesWin32.h b/platform/typesWin32.h new file mode 100644 index 0000000..35d988c --- /dev/null +++ b/platform/typesWin32.h @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESWIN32_H_ +#define _TYPESWIN32_H_ + +// We have to check this. Since every file will eventually wind up including +// this header, but not every header includes a windows or system header... +// +#ifndef NULL +#define NULL 0 +#endif + +// Let's just have this in a nice central location. Again, since every file +// will wind up including this file, we can affect compilation most effectively +// from this location. +// +#define PLATFORM_LITTLE_ENDIAN ///< Signals this platfrom is Little Endian + + +#define FN_CDECL __cdecl ///< Calling convention + +//------------------------------------------------------------------------------ +//-------------------------------------- Basic Types... + +typedef signed char S8; ///< Compiler independent Signed Char +typedef unsigned char U8; ///< Compiler independent Unsigned Char + +typedef signed short S16; ///< Compiler independent Signed 16-bit short +typedef unsigned short U16; ///< Compiler independent Unsigned 16-bit short + +typedef signed int S32; ///< Compiler independent Signed 32-bit integer +typedef unsigned int U32; ///< Compiler independent Unsigned 32-bit integer + +#ifdef __BORLANDC__ +typedef signed __int64 S64; ///< Compiler independent Signed 64-bit integer +typedef unsigned __int64 U64; ///< Compiler independent Unsigned 64-bit integer + +#elif defined(__MWERKS__) // This has to go before MSC_VER since CodeWarrior defines MSC_VER too +typedef signed long long S64; ///< Compiler independent Signed 64-bit integer +typedef unsigned long long U64; ///< Compiler independent Unsigned 64-bit integer + +#elif defined(_MSC_VER) +typedef signed _int64 S64; ///< Compiler independent Signed 64-bit integer +typedef unsigned _int64 U64; ///< Compiler independent Unsigned 64-bit integer +#pragma warning(disable: 4291) // disable warning caused by memory layer... +#pragma warning(disable: 4996) // turn off "deprecation" warnings + +#else +typedef signed long long S64; ///< Compiler independent Signed 64-bit integer +typedef unsigned long long U64; ///< Compiler independent Unsigned 64-bit integer +#endif + +typedef float F32; ///< Compiler independent 32-bit float +typedef double F64; ///< Compiler independent 64-bit float + +// size_t is needed to overload new +// size_t tends to be OS and compiler specific and may need to +// be if/def'ed in the future +typedef unsigned int dsize_t; + +typedef const char* StringTableEntry; + +/* Platform dependent file date-time structure. The defination of this structure + * will likely be different for each OS platform. + */ +struct FileTime +{ + U32 v1; + U32 v2; +}; + +//------------------------------------------------------------------------------ +//-------------------------------------- Type constants... +#define __EQUAL_CONST_F F32(0.000001) ///< Constant float epsilon used for F32 comparisons + +static const F32 Float_One = F32(1.0); ///< Constant float 1.0 +static const F32 Float_Half = F32(0.5); ///< Constant float 0.5 +static const F32 Float_Zero = F32(0.0); ///< Constant float 0.0 +static const F32 Float_Pi = F32(3.14159265358979323846); ///< Constant float PI +static const F32 Float_2Pi = F32(2.0 * 3.14159265358979323846); ///< Constant float 2*PI + +static const S8 S8_MIN = S8(-128); ///< Constant Min Limit S8 +static const S8 S8_MAX = S8(127); ///< Constant Max Limit S8 +static const U8 U8_MAX = U8(255); ///< Constant Max Limit U8 + +static const S16 S16_MIN = S16(-32768); ///< Constant Min Limit S16 +static const S16 S16_MAX = S16(32767); ///< Constant Max Limit S16 +static const U16 U16_MAX = U16(65535); ///< Constant Max Limit U16 + +static const S32 S32_MIN = S32(-2147483647 - 1); ///< Constant Min Limit S32 +static const S32 S32_MAX = S32(2147483647); ///< Constant Max Limit S32 +static const U32 U32_MAX = U32(0xffffffff); ///< Constant Max Limit U32 + +static const F32 F32_MIN = F32(1.175494351e-38F); ///< Constant Min Limit F32 +static const F32 F32_MAX = F32(3.402823466e+38F); ///< Constant Max Limit F32 + + +#ifdef _MSC_VER +#define for if(false) {} else for ///< Hack to work around Microsoft VC's non-C++ compliance on variable scoping +#endif + + +#endif //_NTYPES_H_ diff --git a/platform/typesX86UNIX.h b/platform/typesX86UNIX.h new file mode 100644 index 0000000..163367e --- /dev/null +++ b/platform/typesX86UNIX.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPESX86UNIX_H_ +#define _TYPESX86UNIX_H_ + +/* eek. */ +#ifndef NULL +#define NULL 0 +#endif + +#define PLATFORM_LITTLE_ENDIAN + +#define FN_CDECL + +typedef signed char S8; +typedef unsigned char U8; + +typedef signed short S16; +typedef unsigned short U16; + +typedef signed int S32; +typedef unsigned int U32; + +typedef signed long long S64; +typedef unsigned long long U64; + +typedef float F32; +typedef double F64; + +typedef unsigned int dsize_t; + +typedef const char* StringTableEntry; + +typedef S32 FileTime; + +#define __EQUAL_CONST_F F32(0.000001) + +static const F32 Float_One = F32(1.0); +static const F32 Float_Half = F32(0.5); +static const F32 Float_Zero = F32(0.0); +static const F32 Float_Pi = F32(3.14159265358979323846); +static const F32 Float_2Pi = F32(2.0 * 3.14159265358979323846); + +static const S8 S8_MIN = S8(-128); +static const S8 S8_MAX = S8(127); +static const U8 U8_MAX = U8(255); + +static const S16 S16_MIN = S16(-32768); +static const S16 S16_MAX = S16(32767); +static const U16 U16_MAX = U16(65535); + +static const S32 S32_MIN = S32(-2147483647 - 1); +static const S32 S32_MAX = S32(2147483647); +static const U32 U32_MAX = U32(0xffffffff); + +static const F32 F32_MAX = F32(3.402823466e+38F); +static const F32 F32_MIN = F32(1.175494351e-38F); + + +#endif diff --git a/platform/typetraits.h b/platform/typetraits.h new file mode 100644 index 0000000..1ef280a --- /dev/null +++ b/platform/typetraits.h @@ -0,0 +1,346 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TYPETRAITS_H_ +#define _TYPETRAITS_H_ + +#ifndef _PLATFORM_H_ +# include "platform/platform.h" +#endif + + +/// @file +/// Template definitions for introspecting type properties. + + +//-------------------------------------------------------------------------- +// Construct. +//-------------------------------------------------------------------------- + +struct _ConstructDefault +{ + template< typename T > + static T single() + { + return T(); + } + template< typename T, typename A > + static T single( A a ) + { + return T( a ); + } + template< typename T, typename A, typename B > + static T single( A a, B b ) + { + return T( a, b ); + } + template< typename T > + static void array( T* ptr, U32 num ) + { + constructArrayInPlace< T >( ptr, num ); + } + template< typename T, typename A > + static void array( T* ptr, U32 num, A a ) + { + for( U32 i = 0; i < num; ++ i ) + ptr[ i ] = single< T >( a ); + } +}; +struct _ConstructPrim +{ + template< typename T > + static T single() + { + return 0; + } + template< typename T, typename A > + static T single( T a ) + { + return a; + } + template< typename T > + static void array( T* ptr, U32 num ) + { + dMemset( ptr, 0, num * sizeof( T ) ); + } + template< typename T, typename A > + static void array( T* ptr, U32 num, T a ) + { + for( U32 i = 0; i < num; ++ i ) + ptr[ i ] = a; + } +}; +struct _ConstructPtr +{ + template< typename T > + static T* single() + { + return new T; + } + template< typename T, typename A > + static T* single( A a ) + { + return new T( a ); + } + template< typename T, typename A, typename B > + static T* single( A a, B b ) + { + return new T( a, b ); + } + template< typename T > + static void array( T** ptr, U32 num ) + { + for( U32 i = 0; i < num; ++ i ) + ptr[ i ] = single< T >(); + } + template< typename T, typename A > + static void array( T** ptr, U32 num, A a ) + { + for( U32 i = 0; i < num; ++ i ) + ptr[ i ] = single< T >( a ); + } +}; + +//-------------------------------------------------------------------------- +// Destruct. +//-------------------------------------------------------------------------- + +struct _DestructDefault +{ + template< typename T > + static void single( T& val ) + { + val.~T(); + } + template< typename T > + static void array( T* ptr, U32 num ) + { + for( U32 i = 0; i < num; ++ i ) + single< T >( ptr[ i ] ); + } +}; +struct _DestructPrim +{ + template< typename T > + static void single( T& val ) {} + template< typename T > + static void array( T* ptr, U32 num ) {} +}; +struct _DestructPtr +{ + template< typename T > + static void single( T*& val ) + { + delete val; + val = NULL; + } + template< typename T > + static void array( T* ptr, U32 num ) + { + for( U32 i = 0; i < num; ++ i ) + single< T >( ptr[ i ] ); + } +}; + +//-------------------------------------------------------------------------- +// TypeTraits. +//-------------------------------------------------------------------------- + +template< typename T > +struct _TypeTraits +{ + typedef T BaseType; + typedef _ConstructDefault Construct; + typedef _DestructDefault Destruct; +}; +template< typename T > +struct _TypeTraits< T* > +{ + typedef T BaseType; + typedef _ConstructPtr Construct; + typedef _DestructPtr Destruct; + + template< typename A > + static bool isTaggedPtr( A* ptr ) { return ( U32( ptr ) & 0x1 ); } //TODO: 64bits + template< typename A > + static A* getTaggedPtr( A* ptr ) { return ( A* ) ( U32( ptr ) | 0x1 ); } //TODO: 64bits + template< typename A > + static A* getUntaggedPtr( A* ptr ) { return ( A* ) ( U32( ptr ) & 0xFFFFFFFE ); } //TODO: 64bit +}; + +template< typename T > +struct TypeTraits : public TypeTraits< typename T::Parent > +{ + typedef T BaseType; +}; +template< typename T > +struct TypeTraits< T* > : public TypeTraits< typename T::Parent* > +{ + typedef T BaseType; +}; +template< typename T > +struct TypeTraits< T* const > : public TypeTraits< typename T::Parent* > +{ + typedef T BaseType; +}; +template<> +struct TypeTraits< void > : public _TypeTraits< void > {}; +template<> +struct TypeTraits< void* > : public _TypeTraits< void* > {}; +template<> +struct TypeTraits< void* const > : public _TypeTraits< void* > {}; + +// Type traits for primitive types. + +template<> +struct TypeTraits< bool > : public _TypeTraits< bool > +{ + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; +template<> +struct TypeTraits< S8 > : public _TypeTraits< S8 > +{ + static const S8 MIN = S8_MIN; + static const S8 MAX = S8_MAX; + static const S8 ZERO = 0; + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; +template<> +struct TypeTraits< U8 > : public _TypeTraits< U8 > +{ + static const U8 MIN = 0; + static const U8 MAX = U8_MAX; + static const U8 ZERO = 0; + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; +template<> +struct TypeTraits< S16 > : public _TypeTraits< S16 > +{ + static const S16 MIN = S16_MIN; + static const S16 MAX = S16_MAX; + static const S16 ZERO = 0; + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; +template<> +struct TypeTraits< U16 > : public _TypeTraits< U16 > +{ + static const U16 MIN = 0; + static const U16 MAX = U16_MAX; + static const U16 ZERO = 0; + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; +template<> +struct TypeTraits< S32 > : public _TypeTraits< S32 > +{ + static const S32 MIN = S32_MIN; + static const S32 MAX = S32_MAX; + static const S32 ZERO = 0; + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; +template<> +struct TypeTraits< U32 > : public _TypeTraits< U32 > +{ + static const U32 MIN = 0; + static const U32 MAX = U32_MAX; + static const U32 ZERO = 0; + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; +template<> +struct TypeTraits< F32 > : public _TypeTraits< F32 > +{ + static const F32 MIN; + static const F32 MAX; + static const F32 ZERO; + typedef _ConstructPrim Construct; + typedef _DestructPrim Destruct; +}; + +//-------------------------------------------------------------------------- +// Utilities. +//-------------------------------------------------------------------------- + +template< typename T > +inline T constructSingle() +{ + typedef typename TypeTraits< T >::BaseType Type; + typedef typename TypeTraits< T >::Construct Construct; + return Construct::template single< Type >(); +} +template< typename T, typename A > +inline T constructSingle( A a ) +{ + typedef typename TypeTraits< T >::BaseType BaseType; + typedef typename TypeTraits< T >::Construct Construct; + return Construct::template single< BaseType >( a ); +} +template< typename T, typename A, typename B > +inline T constructSingle( A a, B b ) +{ + typedef typename TypeTraits< T >::BaseType BaseType; + typedef typename TypeTraits< T >::Construct Construct; + return Construct::template single< BaseType >( a, b ); +} +template< typename T > +inline void constructArray( T* ptr, U32 num ) +{ + typedef typename TypeTraits< T >::BaseType BaseType; + typedef typename TypeTraits< T >::Construct Construct; + Construct::template array< BaseType >( ptr, num ); +} +template< typename T, typename A > +inline void constructArray( T* ptr, U32 num, A a ) +{ + typedef typename TypeTraits< T >::BaseType BaseType; + typedef typename TypeTraits< T >::Construct Construct; + Construct::template array< BaseType >( ptr, num, a ); +} +template< typename T > +inline void destructSingle( T& val ) +{ + typedef typename TypeTraits< T >::BaseType BaseType; + typedef typename TypeTraits< T >::Destruct Destruct; + Destruct::template single< BaseType >( val ); +} +template< typename T > +inline void destructArray( T* ptr, U32 num ) +{ + typedef typename TypeTraits< T >::BaseType BaseType; + typedef typename TypeTraits< T >::Destruct Destruct; + Destruct::template array< BaseType >( ptr, num ); +} + +template< typename T> +inline T& Deref( T& val ) +{ + return val; +} +template< typename T > +inline T& Deref( T* ptr ) +{ + return *ptr; +} + +/// Delete a single object policy. +struct DeleteSingle +{ + template + static void destroy(T *ptr) { delete ptr; } +}; + +/// Delete an array of objects policy. +struct DeleteArray +{ + template + static void destroy(T *ptr) { delete [] ptr; } +}; + +#endif // _TYPETRAITS_H_ diff --git a/platformMac/cursors/resizeNESW.png b/platformMac/cursors/resizeNESW.png new file mode 100644 index 0000000000000000000000000000000000000000..cd9e83a00a74335c2963ee6e17ab8c656460c348 GIT binary patch literal 630 zcmV-+0*U>JP)}9YPDS2k8wC4npXV!Ki!2(*Gp5q)we%NFbeE z3>c?SiSI(-#Dv~!a`AGnL*Io{nlJpe_x*l<-{1Sf0#quMY#4@DQp$-CVj_f?x~>~f z5QgFK@bK{UBEAV^Tdh_;2!f&GILb85`D8K~C4>m}_xB5n1PmYxtkr5YM@l)eZ94)K zpiI+LLWui9p|F=)AfSK{FiNFT*Ymt~sZ>&rAC1T30ep7*ucY^q!?ry7lh>U28iqtQq@j-zgFZl?Ks{u7l-C9Csh zfwUI0Zf|cNkS{JS8o)uhT>dPj^nKr-7K_C_!Z5sQwOV;#4afmclFrR$Q(azOeg{74 z`B1G^kA2_&wY$6f7AfT<2!dg)R&#*uUa$8p>8!~`;2p37yaKk@*Vngz=LjJtj^ijP z<>>nQ`sbs}KN94Zz%$^9-gFIEMF=r9O;g#n9i5$>&0=!9-EQcWWAanL(l<)a@8-Jh z)HF>Mm!w{=&pMsXIdGr_cC@ple2VK`U@vGJ9F|e&Or~lVBBMiggU@*ukrBa4r5N~8zmIB<(&CNo;-}e9s zOtkYHOD88M@9*yJR)@pk@_0O6j-qHOolehIR#yBV2nPLrKS;b!0kfpQUlLtSjc|Qy QjsO4v07*qoM6N<$g6WnlVE_OC literal 0 HcmV?d00001 diff --git a/platformMac/cursors/resizeNWSE.png b/platformMac/cursors/resizeNWSE.png new file mode 100644 index 0000000000000000000000000000000000000000..b4bc196bdfca250a7e50f0b2284a135c114d9294 GIT binary patch literal 707 zcmV;!0zCbRP)0K=rfD0arDzWt1c@Fr=HR6ekWgr;pt<@(lwPW#29e$p&?s9vvM$>s7}{r>gZ`uaK%jYcmck;taU zcpx>Y_=n8jtBOo4+k5nqv zH;qQ~20k00AB;NFwm<{)di~gLw~yU!Hz$!u5CVbVlUA$UWgr#M&&l>_K&@6Ix3;!s z4-O8xXJ=>MTdmfL*=!~-L@XA6RIAlq2GU`?7_qh3c+~B7NrVvT^?C!a=l0Rj(eqBH zqhlcwi6pC3Dt8zk&Ow*B&}cNyrPJvFB1y6k4u^f`rO%0GxqCi@J&XlyncKNJd4+uPd)#Qu>nX26)lXTbLW zvI~%>7y?D3{r!EE&1O-lRO&k%4l7~`xlAD&5*+IV+y&@Cu~>YY&*vWkgpNBzoG6#e zgN=<12O_2q480F~e&TdG-$U_yCX+!d06G|0t=1Pe0eIl{+Un}+2aCnBkV>Vl7kE4Z zjPFD6Hm3m)ob!A#nfyvolnio#@GXVBAojM3L?RN>`)nEjw?FIi`6O^lMUd9J?RHzx p>-G4f(P#$7ki+3z93CE){sMxqGm-_$X0reQ002ovPDHLkV1ks9M=AgS literal 0 HcmV?d00001 diff --git a/platformMac/cursors/resizeall.png b/platformMac/cursors/resizeall.png new file mode 100644 index 0000000000000000000000000000000000000000..f1950da509ed7a35673977c1c21f02c1975237d3 GIT binary patch literal 307 zcmV-30nGl1P)yl-viq?bB zimIa_uat`69>{&l92EkP7(yExV+gWBm`g|}z!%pm1Ze@f!~(Kp$g2fl@dAlAuH6ZF zpCI1B&#^7(O&p-UnQz&^A4CsoaibgykPUP7^_&qzAqdvow=Cpu0Q?j|@nZ=I?MAQ| z2Uo8_u3=xAnBbANO&sf!53z}X&$varXK@9)xX;Lt84}W-K_USSQ3fj-g9OMBZi2)t zFv@gu4oi8E-mOs< + + +@interface macApplication : NSApplication +{ +} + +@end diff --git a/platformMac/macApplication.mm b/platformMac/macApplication.mm new file mode 100644 index 0000000..aab40f4 --- /dev/null +++ b/platformMac/macApplication.mm @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "macApplication.h" +#include "windowManager/mac/macWindow.h" +#include "windowManager/mac/macView.h" +#include "console/console.h" + +@implementation macApplication + +- (void)sendEvent:(NSEvent*)theEvent +{ + if([theEvent type] == NSKeyUp) + { + if([theEvent modifierFlags] & NSCommandKeyMask) + { + // These will normally be blocked, but we wants them! + [[self delegate] keyUp:theEvent]; + return; + } + } + + MacWindow* window = [(GGMacView*)[self delegate] torqueWindow]; + if(window && window->isFullscreen()) + { + switch([theEvent type]) + { + case NSLeftMouseDown: + [[self delegate] mouseDown:theEvent]; + return; + case NSRightMouseDown: + [[self delegate] rightMouseDown:theEvent]; + return; + case NSLeftMouseUp: + [[self delegate] mouseUp:theEvent]; + return; + case NSRightMouseUp: + [[self delegate] rightMouseUp:theEvent]; + return; + case NSMouseMoved: + [[self delegate] mouseMoved:theEvent]; + return; + case NSLeftMouseDragged: + [[self delegate] mouseDragged:theEvent]; + return; + case NSRightMouseDragged: + [[self delegate] rightMouseDragged:theEvent]; + return; + case NSScrollWheel: + [[self delegate] scrollWheel:theEvent]; + return; + default: + break; + } + } + + [super sendEvent:theEvent]; +} + +@end diff --git a/platformMac/macCarbAsync.cpp b/platformMac/macCarbAsync.cpp new file mode 100644 index 0000000..5a06bdc --- /dev/null +++ b/platformMac/macCarbAsync.cpp @@ -0,0 +1,47 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "platform/async/asyncUpdate.h" + + +AsyncUpdateThread::AsyncUpdateThread( String name, AsyncUpdateList* updateList ) + : Parent( 0, 0, false, false ), + mUpdateList( updateList ), + mName( name ) +{ + MPCreateEvent( ( MPEventID* ) &mUpdateEvent ); +} + +AsyncUpdateThread::~AsyncUpdateThread() +{ + MPDeleteEvent( *( ( MPEventID* ) &mUpdateEvent ) ) ; +} + +void AsyncUpdateThread::_waitForEventAndReset() +{ + MPWaitForEvent( *( ( MPEventID* ) &mUpdateEvent ), NULL, kDurationForever ); +} + +void AsyncUpdateThread::triggerUpdate() +{ + MPSetEvent( *( ( MPEventID* ) &mUpdateEvent ), 1 ); +} + +AsyncPeriodicUpdateThread::AsyncPeriodicUpdateThread + ( String name, AsyncUpdateList* updateList, U32 intervalMS ) + : Parent( name, updateList ), + mIntervalMS( intervalMS ) +{ +} + +AsyncPeriodicUpdateThread::~AsyncPeriodicUpdateThread() +{ +} + +void AsyncPeriodicUpdateThread::_waitForEventAndReset() +{ + MPWaitForEvent( *( ( MPEventID* ) &mUpdateEvent ), NULL, kDurationMillisecond * mIntervalMS ); +} diff --git a/platformMac/macCarbCPUInfo.cpp b/platformMac/macCarbCPUInfo.cpp new file mode 100644 index 0000000..a9c63ed --- /dev/null +++ b/platformMac/macCarbCPUInfo.cpp @@ -0,0 +1,265 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include + +#include "platformMac/platformMacCarb.h" +#include "platform/platformAssert.h" +#include "console/console.h" +#include "core/stringTable.h" + + +// Original code by Sean O'Brien (http://www.garagegames.com/community/forums/viewthread/81815). + + +// Reads sysctl() string value into buffer at DEST with maximum length MAXLEN +// Return: 0 on success, non-zero is error in accordance with stdlib and +int _getSysCTLstring(const char key[], char * dest, size_t maxlen) { + size_t len = 0; + int err; + // Call with NULL for 'dest' to have the required size stored in 'len'. If the 'key' + // doesn't exist, 'err' will be -1 and if all goes well, it will be 0. + err = sysctlbyname(key, NULL, &len, NULL, 0); + if (err == 0) { + AssertWarn((len <= maxlen), ("Insufficient buffer length for SYSCTL() read. Truncating.\n")); + if (len > maxlen) + len = maxlen; + // Call with actual pointers to 'dest' and clamped 'len' fields to perform the read. + err = sysctlbyname(key, dest, &len, NULL, 0); + } + return err; +} + +// TEMPLATED Reads sysctl() integer value into variable DEST of type T +// The two predominant types used are unsigned longs and unsiged long longs +// and the size of the argument is on a case-by-case value. As a "guide" the +// resources at Apple claim that any "byte count" or "frequency" values will +// be returned as ULL's and most everything else will be UL's. +// Return: 0 on success, non-zero is error in accordance with stdlib and +template +int _getSysCTLvalue(const char key[], T * dest) { + size_t len = 0; + int err; + // Call with NULL for 'dest' to get the size. If the 'key' doesn't exist, the + // 'err' returned will be -1, so 0 indicates success. + err = sysctlbyname(key, NULL, &len, NULL, 0); + if (err == 0) { + AssertFatal((len == sizeof(T)), "Mis-matched destination type for SYSCTL() read.\n"); + // We're just double-checking that we're being called with the correct type of + // pointer for 'dest' so we don't clobber anything nearby when writing back. + err = sysctlbyname(key, dest, &len, NULL, 0); + } + return err; +} + +Platform::SystemInfo_struct Platform::SystemInfo; + +#define BASE_MHZ_SPEED 0 + +void Processor::init() +{ + U32 procflags; + int err, cpufam, cputype, cpusub; + char buf[20]; + unsigned long lraw; + unsigned long long llraw; + + Con::printf( "System & Processor Information:" ); + + SInt32 MacVersion; + if( Gestalt( gestaltSystemVersion, &MacVersion ) == noErr ) + { + U32 revision = MacVersion & 0xf; + U32 minorVersion = ( MacVersion & 0xf0 ) >> 4; + U32 majorVersion = ( MacVersion & 0xff00 ) >> 8; + + Con::printf( " OSX Version: %x.%x.%x", majorVersion, minorVersion, revision ); + } + + err = _getSysCTLstring("kern.ostype", buf, sizeof(buf)); + if (err) + Con::printf( " Unable to determine OS type\n" ); + else + Con::printf( " Mac OS Kernel name: %s", buf); + + err = _getSysCTLstring("kern.osrelease", buf, sizeof(buf)); + if (err) + Con::printf( " Unable to determine OS release number\n" ); + else + Con::printf( " Mac OS Kernel version: %s", buf ); + + err = _getSysCTLvalue("hw.memsize", &llraw); + if (err) + Con::printf( " Unable to determine amount of physical RAM\n" ); + else + Con::printf( " Physical memory installed: %d MB", (llraw >> 20)); + + err = _getSysCTLvalue("hw.usermem", &lraw); + if (err) + Con::printf( " Unable to determine available user address space\n"); + else + Con::printf( " Addressable user memory: %d MB", (lraw >> 20)); + + //////////////////////////////// + // Values for the Family Type, CPU Type and CPU Subtype are defined in the + // SDK files for the Mach Kernel ==> mach/machine.h + //////////////////////////////// + + // CPU Family, Type, and Subtype + cpufam = 0; + cputype = 0; + cpusub = 0; + err = _getSysCTLvalue("hw.cpufamily", &lraw); + if (err) + Con::printf( " Unable to determine 'family' of CPU\n"); + else { + cpufam = (int) lraw; + err = _getSysCTLvalue("hw.cputype", &lraw); + if (err) + Con::printf( " Unable to determine CPU type\n"); + else { + cputype = (int) lraw; + err = _getSysCTLvalue("hw.cpusubtype", &lraw); + if (err) + Con::printf( " Unable to determine CPU subtype\n"); + else + cpusub = (int) lraw; + // If we've made it this far, + Con::printf( " Installed processor ID: Family 0x%08x Type %d Subtype %d",cpufam, cputype,cpusub); + } + } + + // The Gestalt version was known to have issues with some Processor Upgrade cards + // but it is uncertain whether this version has similar issues. + err = _getSysCTLvalue("hw.cpufrequency", &llraw); + if (err) { + llraw = BASE_MHZ_SPEED; + Con::printf( " Unable to determine CPU Frequency. Defaulting to %d MHz\n", llraw); + } else { + llraw /= 1000000; + Con::printf( " Installed processor clock frequency: %d MHz", llraw); + } + Platform::SystemInfo.processor.mhz = (unsigned int)llraw; + + // Here's one that the original version of this routine couldn't do -- number + // of processors (cores) + U32 ncpu = 1; + err = _getSysCTLvalue("hw.ncpu", &lraw); + if (err) + Con::printf( " Unable to determine number of processor cores\n"); + else + { + ncpu = lraw; + Con::printf( " Installed/available processor cores: %d", lraw); + } + + // Now use CPUFAM to determine and then store the processor type + // and 'friendly name' in GG-accessible structure. Note that since + // we have access to the Family code, the Type and Subtypes are useless. + // + // NOTE: Even this level of detail is almost assuredly not needed anymore + // and the Optional Capability flags (further down) should be more than enough. + switch(cpufam) + { + case CPUFAMILY_POWERPC_G3: + Platform::SystemInfo.processor.type = CPU_PowerPC_G3; + Platform::SystemInfo.processor.name = StringTable->insert("PowerPC G3"); + break; + case CPUFAMILY_POWERPC_G4: + Platform::SystemInfo.processor.type = CPU_PowerPC_G3; + Platform::SystemInfo.processor.name = StringTable->insert("PowerPC G4"); + break; + case CPUFAMILY_POWERPC_G5: + Platform::SystemInfo.processor.type = CPU_PowerPC_G3; + Platform::SystemInfo.processor.name = StringTable->insert("PowerPC G5"); + break; + case CPUFAMILY_INTEL_6_14: + Platform::SystemInfo.processor.type = CPU_Intel_Core; + if( ncpu == 2 ) + Platform::SystemInfo.processor.name = StringTable->insert("Intel Core Duo"); + else + Platform::SystemInfo.processor.name = StringTable->insert("Intel Core"); + break; + #ifdef CPUFAMILY_INTEL_6_23 + case CPUFAMILY_INTEL_6_23: + #endif + case CPUFAMILY_INTEL_6_15: + Platform::SystemInfo.processor.type = CPU_Intel_Core2; + if( ncpu == 4 ) + Platform::SystemInfo.processor.name = StringTable->insert("Intel Core 2 Quad"); + else + Platform::SystemInfo.processor.name = StringTable->insert("Intel Core 2 Duo"); + break; + + #ifdef CPUFAMILY_INTEL_6_26 + case CPUFAMILY_INTEL_6_26: + Platform::SystemInfo.processor.type = CPU_Intel_Core2; + Platform::SystemInfo.processor.name = StringTable->insert( "Intel 'Nehalem' Core Processor" ); + break; + #endif + + default: + // explain why we can't get the processor type. + Con::warnf( " Unknown Processor (family, type, subtype): 0x%x\t%d %d", cpufam, cputype, cpusub); + // for now, identify it as an x86 processor, because Apple is moving to Intel chips... + Platform::SystemInfo.processor.type = CPU_X86Compatible; + Platform::SystemInfo.processor.name = StringTable->insert("Unknown Processor, assuming x86 Compatible"); + break; + } + // Now we can directly query the system about a litany of "Optional" processor capabilities + // and determine the status by using BOTH the 'err' value and the 'lraw' value. If we request + // a non-existant feature from SYSCTL(), the 'err' result will be -1; 0 denotes it exists + // >>>> BUT <<<<< + // it may not be supported, only defined. Thus we need to check 'lraw' to determine if it's + // actually supported/implemented by the processor: 0 = no, 1 = yes, others are undefined. + procflags = 0; + // Seriously this one should be an Assert() + err = _getSysCTLvalue("hw.optional.floatingpoint", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_FPU; + // List of chip-specific features + err = _getSysCTLvalue("hw.optional.mmx", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_MMX; + err = _getSysCTLvalue("hw.optional.sse", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_SSE; + err = _getSysCTLvalue("hw.optional.sse2", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_SSE2; + err = _getSysCTLvalue("hw.optional.sse3", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_SSE3; + err = _getSysCTLvalue("hw.optional.supplementalsse3", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_SSE3xt; + err = _getSysCTLvalue("hw.optional.sse4_1", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_SSE4_1; + err = _getSysCTLvalue("hw.optional.sse4_2", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_SSE4_2; + err = _getSysCTLvalue("hw.optional.altivec", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_ALTIVEC; + // Finally some architecture-wide settings + err = _getSysCTLvalue("hw.ncpu", &lraw); + if ((err==0)&&(lraw>1)) procflags |= CPU_PROP_MP; + err = _getSysCTLvalue("hw.cpu64bit_capable", &lraw); + if ((err==0)&&(lraw==1)) procflags |= CPU_PROP_64bit; + err = _getSysCTLvalue("hw.byteorder", &lraw); + if ((err==0)&&(lraw==1234)) procflags |= CPU_PROP_LE; + + Platform::SystemInfo.processor.properties = procflags; + + Con::printf( "%s, %2.2f GHz", Platform::SystemInfo.processor.name, F32( Platform::SystemInfo.processor.mhz ) / 1000.0 ); + if (Platform::SystemInfo.processor.properties & CPU_PROP_MMX) + Con::printf( " MMX detected"); + if (Platform::SystemInfo.processor.properties & CPU_PROP_SSE) + Con::printf( " SSE detected"); + if (Platform::SystemInfo.processor.properties & CPU_PROP_SSE2) + Con::printf( " SSE2 detected"); + if (Platform::SystemInfo.processor.properties & CPU_PROP_SSE3) + Con::printf( " SSE3 detected"); + if (Platform::SystemInfo.processor.properties & CPU_PROP_ALTIVEC) + Con::printf( " AltiVec detected"); + + Con::printf( "" ); +} diff --git a/platformMac/macCarbFileio.mm b/platformMac/macCarbFileio.mm new file mode 100644 index 0000000..b95016b --- /dev/null +++ b/platformMac/macCarbFileio.mm @@ -0,0 +1,914 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +// Get our GL header included before Apple's +#include "platformMac/platformMacCarb.h" +// Don't include Apple's +#define __gl_h_ + +#include "platform/tmm_off.h" +#include +#include "platform/tmm_on.h" + +#include "core/fileio.h" +#include "core/util/tVector.h" +#include "core/stringTable.h" +#include "core/strings/stringFunctions.h" +#include "console/console.h" +#include "platform/profiler.h" +#include "cinterface/cinterface.h"; + +//TODO: file io still needs some work... + +#define MAX_MAC_PATH_LONG 2048 + +//----------------------------------------------------------------------------- +#if defined(TORQUE_OS_MAC) +#include +#else +#include +#endif + +//----------------------------------------------------------------------------- +bool dFileDelete(const char * name) +{ + if(!name ) + return(false); + + if (dStrlen(name) > MAX_MAC_PATH_LONG) + Con::warnf("dFileDelete: Filename length is pretty long..."); + + return(remove(name) == 0); // remove returns 0 on success +} + + +//----------------------------------------------------------------------------- +bool dFileTouch(const char *path) +{ + if (!path || !*path) + return false; + + // set file at path's modification and access times to now. + return( utimes( path, NULL) == 0); // utimes returns 0 on success. +} + +//----------------------------------------------------------------------------- +// Constructors & Destructor +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// After construction, the currentStatus will be Closed and the capabilities +// will be 0. +//----------------------------------------------------------------------------- +File::File() +: currentStatus(Closed), capability(0) +{ + handle = NULL; +} + +//----------------------------------------------------------------------------- +// insert a copy constructor here... (currently disabled) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +File::~File() +{ + close(); + handle = NULL; +} + + +//----------------------------------------------------------------------------- +// Open a file in the mode specified by openMode (Read, Write, or ReadWrite). +// Truncate the file if the mode is either Write or ReadWrite and truncate is +// true. +// +// Sets capability appropriate to the openMode. +// Returns the currentStatus of the file. +//----------------------------------------------------------------------------- +File::Status File::open(const char *filename, const AccessMode openMode) +{ + if (dStrlen(filename) > MAX_MAC_PATH_LONG) + Con::warnf("File::open: Filename length is pretty long..."); + + // Close the file if it was already open... + if (currentStatus != Closed) + close(); + + // create the appropriate type of file... + switch (openMode) + { + case Read: + handle = (void *)fopen(filename, "rb"); // read only + break; + case Write: + handle = (void *)fopen(filename, "wb"); // write only + break; + case ReadWrite: + handle = (void *)fopen(filename, "ab+"); // write(append) and read + break; + case WriteAppend: + handle = (void *)fopen(filename, "ab"); // write(append) only + break; + default: + AssertFatal(false, "File::open: bad access mode"); + } + + // handle not created successfully + if (handle == NULL) + return setStatus(); + + // successfully created file, so set the file capabilities... + switch (openMode) + { + case Read: + capability = FileRead; + break; + case Write: + case WriteAppend: + capability = FileWrite; + break; + case ReadWrite: + capability = FileRead | FileWrite; + break; + default: + AssertFatal(false, "File::open: bad access mode"); + } + + // must set the file status before setting the position. + currentStatus = Ok; + + if (openMode == ReadWrite) + setPosition(0); + + // success! + return currentStatus; +} + +//----------------------------------------------------------------------------- +// Get the current position of the file pointer. +//----------------------------------------------------------------------------- +U32 File::getPosition() const +{ + AssertFatal(currentStatus != Closed , "File::getPosition: file closed"); + AssertFatal(handle != NULL, "File::getPosition: invalid file handle"); + + return ftell((FILE*)handle); +} + +//----------------------------------------------------------------------------- +// Set the position of the file pointer. +// Absolute and relative positioning is supported via the absolutePos +// parameter. +// +// If positioning absolutely, position MUST be positive - an IOError results if +// position is negative. +// Position can be negative if positioning relatively, however positioning +// before the start of the file is an IOError. +// +// Returns the currentStatus of the file. +//----------------------------------------------------------------------------- +File::Status File::setPosition(S32 position, bool absolutePos) +{ + AssertFatal(Closed != currentStatus, "File::setPosition: file closed"); + AssertFatal(handle != NULL, "File::setPosition: invalid file handle"); + + if (currentStatus != Ok && currentStatus != EOS ) + return currentStatus; + + U32 finalPos; + if(absolutePos) + { + // absolute position + AssertFatal(0 <= position, "File::setPosition: negative absolute position"); + // position beyond EOS is OK + fseek((FILE*)handle, position, SEEK_SET); + finalPos = ftell((FILE*)handle); + } + else + { + // relative position + AssertFatal((getPosition() + position) >= 0, "File::setPosition: negative relative position"); + // position beyond EOS is OK + fseek((FILE*)handle, position, SEEK_CUR); + finalPos = ftell((FILE*)handle); + } + + // ftell returns -1 on error. set error status + if (0xffffffff == finalPos) + return setStatus(); + + // success, at end of file + else if (finalPos >= getSize()) + return currentStatus = EOS; + + // success! + else + return currentStatus = Ok; +} + +//----------------------------------------------------------------------------- +// Get the size of the file in bytes. +// It is an error to query the file size for a Closed file, or for one with an +// error status. +//----------------------------------------------------------------------------- +U32 File::getSize() const +{ + AssertWarn(Closed != currentStatus, "File::getSize: file closed"); + AssertFatal(handle != NULL, "File::getSize: invalid file handle"); + + if (Ok == currentStatus || EOS == currentStatus) + { + struct stat statData; + + if(fstat(fileno((FILE*)handle), &statData) != 0) + return 0; + + // return the size in bytes + return statData.st_size; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Flush the file. +// It is an error to flush a read-only file. +// Returns the currentStatus of the file. +//----------------------------------------------------------------------------- +File::Status File::flush() +{ + AssertFatal(Closed != currentStatus, "File::flush: file closed"); + AssertFatal(handle != NULL, "File::flush: invalid file handle"); + AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file"); + + if (fflush((FILE*)handle) != 0) + return setStatus(); + else + return currentStatus = Ok; +} + +//----------------------------------------------------------------------------- +// Close the File. +// +// Returns the currentStatus +//----------------------------------------------------------------------------- +File::Status File::close() +{ + // check if it's already closed... + if (Closed == currentStatus) + return currentStatus; + + // it's not, so close it... + if (handle != NULL) + { + if (fclose((FILE*)handle) != 0) + return setStatus(); + } + handle = NULL; + return currentStatus = Closed; +} + +//----------------------------------------------------------------------------- +// Self-explanatory. +//----------------------------------------------------------------------------- +File::Status File::getStatus() const +{ + return currentStatus; +} + +//----------------------------------------------------------------------------- +// Sets and returns the currentStatus when an error has been encountered. +//----------------------------------------------------------------------------- +File::Status File::setStatus() +{ + switch (errno) + { + case EACCES: // permission denied + currentStatus = IOError; + break; + case EBADF: // Bad File Pointer + case EINVAL: // Invalid argument + case ENOENT: // file not found + case ENAMETOOLONG: + default: + currentStatus = UnknownError; + } + + return currentStatus; +} + +//----------------------------------------------------------------------------- +// Sets and returns the currentStatus to status. +//----------------------------------------------------------------------------- +File::Status File::setStatus(File::Status status) +{ + return currentStatus = status; +} + +//----------------------------------------------------------------------------- +// Read from a file. +// The number of bytes to read is passed in size, the data is returned in src. +// The number of bytes read is available in bytesRead if a non-Null pointer is +// provided. +//----------------------------------------------------------------------------- +File::Status File::read(U32 size, char *dst, U32 *bytesRead) +{ + AssertFatal(Closed != currentStatus, "File::read: file closed"); + AssertFatal(handle != NULL, "File::read: invalid file handle"); + AssertFatal(NULL != dst, "File::read: NULL destination pointer"); + AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability"); + AssertWarn(0 != size, "File::read: size of zero"); + + if (Ok != currentStatus || 0 == size) + return currentStatus; + + // read from stream + U32 nBytes = fread(dst, 1, size, (FILE*)handle); + + // did we hit the end of the stream? + if( nBytes != size) + currentStatus = EOS; + + // if bytesRead is a valid pointer, send number of bytes read there. + if(bytesRead) + *bytesRead = nBytes; + + // successfully read size bytes + return currentStatus; +} + +//----------------------------------------------------------------------------- +// Write to a file. +// The number of bytes to write is passed in size, the data is passed in src. +// The number of bytes written is available in bytesWritten if a non-Null +// pointer is provided. +//----------------------------------------------------------------------------- +File::Status File::write(U32 size, const char *src, U32 *bytesWritten) +{ + AssertFatal(Closed != currentStatus, "File::write: file closed"); + AssertFatal(handle != NULL, "File::write: invalid file handle"); + AssertFatal(NULL != src, "File::write: NULL source pointer"); + AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability"); + AssertWarn(0 != size, "File::write: size of zero"); + + if ((Ok != currentStatus && EOS != currentStatus) || 0 == size) + return currentStatus; + + // write bytes to the stream + U32 nBytes = fwrite(src, 1, size,(FILE*)handle); + + // if we couldn't write everything, we've got a problem. set error status. + if(nBytes != size) + setStatus(); + + // if bytesWritten is a valid pointer, put number of bytes read there. + if(bytesWritten) + *bytesWritten = nBytes; + + // return current File status, whether good or ill. + return currentStatus; +} + + +//----------------------------------------------------------------------------- +// Self-explanatory. +//----------------------------------------------------------------------------- +bool File::hasCapability(Capability cap) const +{ + return (0 != (U32(cap) & capability)); +} + +//----------------------------------------------------------------------------- +S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b) +{ + if(a > b) + return 1; + if(a < b) + return -1; + return 0; +} + + +//----------------------------------------------------------------------------- +// either time param COULD be null. +//----------------------------------------------------------------------------- +bool Platform::getFileTimes(const char *path, FileTime *createTime, FileTime *modifyTime) +{ + // MacOSX is NOT guaranteed to be running off a HFS volume, + // and UNIX does not keep a record of a file's creation time anywhere. + // So instead of creation time we return changed time, + // just like the Linux platform impl does. + + if (!path || !*path) + return false; + + struct stat statData; + + if (stat(path, &statData) == -1) + return false; + + if(createTime) + *createTime = statData.st_ctime; + + if(modifyTime) + *modifyTime = statData.st_mtime; + + return true; +} + + +//----------------------------------------------------------------------------- +bool Platform::createPath(const char *file) +{ + // if the path exists, we're done. + struct stat statData; + if( stat(file, &statData) == 0 ) + { + return true; // exists, rejoice. + } + + Con::warnf( "creating path %s", file ); + + // get the parent path. + // we're not using basename because it's not thread safe. + U32 len = dStrlen(file); + char parent[len]; + bool isDirPath = false; + + dStrncpy(parent,file,len); + parent[len] = '\0'; + if(parent[len - 1] == '/') + { + parent[len - 1] = '\0'; // cut off the trailing slash, if there is one + isDirPath = true; // we got a trailing slash, so file is a directory. + } + + // recusively create the parent path. + // only recurse if newpath has a slash that isn't a leading slash. + char *slash = dStrrchr(parent,'/'); + if( slash && slash != parent) + { + // snip the path just after the last slash. + slash[1] = '\0'; + // recusively create the parent path. fail if parent path creation failed. + if(!Platform::createPath(parent)) + return false; + } + + // create *file if it is a directory path. + if(isDirPath) + { + // try to create the directory + if( mkdir(file, 0777) != 0) // app may reside in global apps dir, and so must be writable to all. + return false; + } + + return true; +} + + +//----------------------------------------------------------------------------- +bool Platform::cdFileExists(const char *filePath, const char *volumeName, S32 serialNum) +{ + return true; +} + +#pragma mark ---- Directories ---- +//----------------------------------------------------------------------------- +StringTableEntry Platform::getCurrentDirectory() +{ + // get the current directory, the one that would be opened if we did a fopen(".") + char* cwd = getcwd(NULL, 0); + StringTableEntry ret = StringTable->insert(cwd); + free(cwd); + return ret; +} + +//----------------------------------------------------------------------------- +bool Platform::setCurrentDirectory(StringTableEntry newDir) +{ + return (chdir(newDir) == 0); +} + +//----------------------------------------------------------------------------- +void Platform::openFolder(const char* path ) +{ + // TODO: users can still run applications by calling openfolder on an app bundle. + // this may be a bad thing. + if(!Platform::isDirectory(path)) + { + Con::errorf(avar("Error: not a directory: %s",path)); + return; + } + + const char* arg = avar("open '%s'", path); + U32 ret = system(arg); + if(ret != 0) + Con::printf(strerror(errno)); +} + +void Platform::openFile(const char* path ) +{ + // Implement me! +} + +// helper func for getWorkingDirectory +bool isMainDotCsPresent(NSString* dir) +{ + return [[NSFileManager defaultManager] fileExistsAtPath:[dir stringByAppendingPathComponent:@"main.cs"]] == YES; +} + +//----------------------------------------------------------------------------- +/// Finds and sets the current working directory. +/// Torque tries to automatically detect whether you have placed the game files +/// inside or outside the application's bundle. It checks for the presence of +/// the file 'main.cs'. If it finds it, Torque will assume that the other game +/// files are there too. If Torque does not see 'main.cs' inside its bundle, it +/// will assume the files are outside the bundle. +/// Since you probably don't want to copy the game files into the app every time +/// you build, you will want to leave them outside the bundle for development. +/// +/// Placing all content inside the application bundle gives a much better user +/// experience when you distribute your app. +StringTableEntry Platform::getExecutablePath() +{ + static const char* cwd = NULL; + + // this isn't actually being used due to some static constructors at bundle load time + // calling this method (before there is a chance to set it) + // for instance, FMOD sound provider (this should be fixed in FMOD as it is with windows) + if (!cwd && torque_getexecutablepath()) + { + // we're in a plugin using the cinterface + cwd = torque_getexecutablepath(); + chdir(cwd); + } + else if(!cwd) + { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + //first check the cwd for main.cs + static char buf[4096]; + NSString* currentDir = [[NSString alloc ] initWithCString:getcwd(buf,(4096 * sizeof(char))) ]; + + if (isMainDotCsPresent(currentDir)) + { + cwd = buf; + [pool release]; + return cwd; + } + + NSString* string = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"cs"]; + if(!string) + string = [[NSBundle mainBundle] bundlePath]; + + string = [string stringByDeletingLastPathComponent]; + AssertISV(isMainDotCsPresent(string), "Platform::getExecutablePath - Failed to find main.cs!"); + cwd = dStrdup([string UTF8String]); + chdir(cwd); + [pool release]; + } + + return cwd; +} + +//----------------------------------------------------------------------------- +StringTableEntry Platform::getExecutableName() +{ + static const char* name = NULL; + if(!name) + name = [[[[NSBundle mainBundle] bundlePath] lastPathComponent] UTF8String]; + + return name; +} + +//----------------------------------------------------------------------------- +bool Platform::isFile(const char *path) +{ + if (!path || !*path) + return false; + + // make sure we can stat the file + struct stat statData; + if( stat(path, &statData) < 0 ) + return false; + + // now see if it's a regular file + if( (statData.st_mode & S_IFMT) == S_IFREG) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +bool Platform::isDirectory(const char *path) +{ + if (!path || !*path) + return false; + + // make sure we can stat the file + struct stat statData; + if( stat(path, &statData) < 0 ) + return false; + + // now see if it's a directory + if( (statData.st_mode & S_IFMT) == S_IFDIR) + return true; + + return false; +} + + +S32 Platform::getFileSize(const char* pFilePath) +{ + if (!pFilePath || !*pFilePath) + return 0; + + struct stat statData; + if( stat(pFilePath, &statData) < 0 ) + return 0; + + // and return it's size in bytes + return (S32)statData.st_size; +} + + +//----------------------------------------------------------------------------- +bool Platform::isSubDirectory(const char *pathParent, const char *pathSub) +{ + char fullpath[MAX_MAC_PATH_LONG]; + dStrcpyl(fullpath, MAX_MAC_PATH_LONG, pathParent, "/", pathSub, NULL); + return isDirectory((const char *)fullpath); +} + +//----------------------------------------------------------------------------- +// utility for platform::hasSubDirectory() and platform::dumpDirectories() +// ensures that the entry is a directory, and isnt on the ignore lists. +inline bool isGoodDirectory(dirent* entry) +{ + return (entry->d_type == DT_DIR // is a dir + && dStrcmp(entry->d_name,".") != 0 // not here + && dStrcmp(entry->d_name,"..") != 0 // not parent + && !Platform::isExcludedDirectory(entry->d_name)); // not excluded +} + +//----------------------------------------------------------------------------- +bool Platform::hasSubDirectory(const char *path) +{ + DIR *dir; + dirent *entry; + + dir = opendir(path); + if(!dir) + return false; // we got a bad path, so no, it has no subdirectory. + + while( (entry = readdir(dir)) ) + { + if(isGoodDirectory(entry) ) + { + closedir(dir); + return true; // we have a subdirectory, that isnt on the exclude list. + } + } + + closedir(dir); + return false; // either this dir had no subdirectories, or they were all on the exclude list. +} + +//----------------------------------------------------------------------------- +bool recurseDumpDirectories(const char *basePath, const char *path, Vector &directoryVector, S32 depth, bool noBasePath) +{ + DIR *dir; + dirent *entry; + U32 len = dStrlen(basePath) + dStrlen(path) + 2; + char pathbuf[len]; + + // construct the file path + dSprintf(pathbuf, len, "%s/%s", basePath, path); + pathbuf[len] = '\0'; + + // be sure it opens. + dir = opendir(pathbuf); + if(!dir) + return false; + + // look inside the current directory + while( (entry = readdir(dir)) ) + { + // we just want directories. + if(!isGoodDirectory(entry)) + continue; + + // TODO: better unicode file name handling + // // Apple's file system stores unicode file names in decomposed form. + // // ATSUI will not reliably draw out just the accent character by itself, + // // so our text renderer has no chance of rendering decomposed form unicode. + // // We have to convert the entry name to precomposed normalized form. + // CFStringRef cfdname = CFStringCreateWithCString(NULL,entry->d_name,kCFStringEncodingUTF8); + // CFMutableStringRef cfentryName = CFStringCreateMutableCopy(NULL,0,cfdname); + // CFStringNormalize(cfentryName,kCFStringNormalizationFormC); + // + // U32 entryNameLen = CFStringGetLength(cfentryName) * 4 + 1; + // char entryName[entryNameLen]; + // CFStringGetCString(cfentryName, entryName, entryNameLen, kCFStringEncodingUTF8); + // entryName[entryNameLen-1] = NULL; // sometimes, CFStringGetCString() doesn't null terminate. + // CFRelease(cfentryName); + // CFRelease(cfdname); + + // construct the new path string, we'll need this below. + U32 newpathlen = dStrlen(path) + dStrlen(entry->d_name) + 2; + char newpath[newpathlen]; + if(dStrlen(path) > 0) // prevent extra slashes in the path + dSprintf(newpath, newpathlen,"%s/%s",path,entry->d_name); + else + dStrncpy(newpath,entry->d_name, newpathlen); + newpath[newpathlen] = '\0'; + + // we have a directory, add it to the list. + if( noBasePath ) + directoryVector.push_back(StringTable->insert(newpath)); + else { + U32 fullpathlen = dStrlen(basePath) + dStrlen(newpath) + 2; + char fullpath[fullpathlen]; + dSprintf(fullpath,fullpathlen,"%s/%s",basePath,newpath); + fullpath[fullpathlen] = '\0'; + + directoryVector.push_back(StringTable->insert(fullpath)); + } + + // and recurse into it, unless we've run out of depth + if( depth != 0) // passing a val of -1 as the recurse depth means go forever + recurseDumpDirectories(basePath, newpath, directoryVector, depth-1, noBasePath); + } + closedir(dir); + return true; +} + +//----------------------------------------------------------------------------- +bool Platform::dumpDirectories(const char *path, Vector &directoryVector, S32 depth, bool noBasePath) +{ + PROFILE_START(dumpDirectories); + + int len = dStrlen(path); + char newpath[len]; + + dStrncpy(newpath,path,len); + newpath[len] = '\0'; + if(newpath[len - 1] == '/') + newpath[len - 1] = '\0'; // cut off the trailing slash, if there is one + + bool ret = recurseDumpDirectories(newpath, "", directoryVector, depth, noBasePath); + PROFILE_END(); + + return ret; +} + +//----------------------------------------------------------------------------- +static bool recurseDumpPath(const char* curPath, Vector& fileVector, U32 depth) +{ + DIR *dir; + dirent *entry; + + // be sure it opens. + dir = opendir(curPath); + if(!dir) + return false; + + // look inside the current directory + while( (entry = readdir(dir)) ) + { + // construct the full file path. we need this to get the file size and to recurse + U32 len = dStrlen(curPath) + entry->d_namlen + 2; + char pathbuf[len]; + dSprintf( pathbuf, len, "%s/%s", curPath, entry->d_name); + pathbuf[len] = '\0'; + + // ok, deal with directories and files seperately. + if( entry->d_type == DT_DIR ) + { + if( depth == 0) + continue; + + // filter out dirs we dont want. + if( !isGoodDirectory(entry) ) + continue; + + // recurse into the dir + recurseDumpPath( pathbuf, fileVector, depth-1); + } + else + { + //add the file entry to the list + // unlike recurseDumpDirectories(), we need to return more complex info here. + U32 fileSize = Platform::getFileSize(pathbuf); + fileVector.increment(); + Platform::FileInfo& rInfo = fileVector.last(); + rInfo.pFullPath = StringTable->insert(curPath); + rInfo.pFileName = StringTable->insert(entry->d_name); + rInfo.fileSize = fileSize; + } + } + closedir(dir); + return true; + +} + + +//----------------------------------------------------------------------------- +bool Platform::dumpPath(const char *path, Vector& fileVector, S32 depth) +{ + PROFILE_START(dumpPath); + int len = dStrlen(path); + char newpath[len+1]; + + dStrncpy(newpath,path,len); + newpath[len] = '\0'; // null terminate + if(newpath[len - 1] == '/') + newpath[len - 1] = '\0'; // cut off the trailing slash, if there is one + + bool ret = recurseDumpPath( newpath, fileVector, depth); + PROFILE_END(); + + return ret; +} + +// TODO: implement stringToFileTime() +bool Platform::stringToFileTime(const char * string, FileTime * time) { return false;} +// TODO: implement fileTimeToString() +bool Platform::fileTimeToString(FileTime * time, char * string, U32 strLen) { return false;} + +//----------------------------------------------------------------------------- +#if defined(TORQUE_DEBUG) +ConsoleFunction(testHasSubdir,void,2,2,"tests platform::hasSubDirectory") { + Con::printf("testing %s",argv[1]); + Platform::addExcludedDirectory(".svn"); + if(Platform::hasSubDirectory(argv[1])) + Con::printf(" has subdir"); + else + Con::printf(" does not have subdir"); +} + +ConsoleFunction(testDumpDirectories,void,4,4,"testDumpDirectories('path', int depth, bool noBasePath)") { + Vector paths; + const S32 depth = dAtoi(argv[2]); + const bool noBasePath = dAtob(argv[3]); + + Platform::addExcludedDirectory(".svn"); + + Platform::dumpDirectories(argv[1], paths, depth, noBasePath); + + Con::printf("Dumping directories starting from %s with depth %i", argv[1],depth); + + for(Vector::iterator itr = paths.begin(); itr != paths.end(); itr++) { + Con::printf(*itr); + } + +} + +ConsoleFunction(testDumpPaths, void, 3, 3, "testDumpPaths('path', int depth)") +{ + Vector files; + S32 depth = dAtoi(argv[2]); + + Platform::addExcludedDirectory(".svn"); + + Platform::dumpPath(argv[1], files, depth); + + for(Vector::iterator itr = files.begin(); itr != files.end(); itr++) { + Con::printf("%s/%s",itr->pFullPath, itr->pFileName); + } +} + +//----------------------------------------------------------------------------- +ConsoleFunction(testFileTouch, bool , 2,2, "testFileTouch('path')") +{ + return dFileTouch(argv[1]); +} + +ConsoleFunction(testGetFileTimes, bool, 2,2, "testGetFileTimes('path')") +{ + FileTime create, modify; + bool ok = Platform::getFileTimes(argv[1], &create, &modify); + Con::printf("%s Platform::getFileTimes %i, %i", ok ? "+OK" : "-FAIL", create, modify); + return ok; +} + +#endif diff --git a/platformMac/macCarbFont.cpp b/platformMac/macCarbFont.cpp new file mode 100644 index 0000000..f6b2c7e --- /dev/null +++ b/platformMac/macCarbFont.cpp @@ -0,0 +1,432 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformMac/macCarbFont.h" +#include "platformMac/platformMacCarb.h" +#include "math/mRect.h" +#include "console/console.h" +#include "core/strings/unicode.h" +#include "core/stringTable.h" +#include "core/strings/stringFunctions.h" + + +//------------------------------------------------------------------------------ +// New Unicode capable font class. +PlatformFont *createPlatformFont(const char *name, U32 size, U32 charset /* = TGE_ANSI_CHARSET */) +{ + PlatformFont *retFont = new MacCarbFont; + + if(retFont->create(name, size, charset)) + return retFont; + + delete retFont; + return NULL; +} + +//------------------------------------------------------------------------------ +MacCarbFont::MacCarbFont() +{ + mStyle = NULL; + mLayout = NULL; + mColorSpace = NULL; +} + +MacCarbFont::~MacCarbFont() +{ + // apple docs say we should dispose the layout first. + ATSUDisposeTextLayout(mLayout); + ATSUDisposeStyle(mStyle); + CGColorSpaceRelease(mColorSpace); +} + +//------------------------------------------------------------------------------ +bool MacCarbFont::create( const char* name, U32 size, U32 charset) +{ + String nameStr = name; + nameStr = nameStr.trim(); + + // create and cache the style and layout. + // based on apple sample code at http://developer.apple.com/qa/qa2001/qa1027.html + + // note: charset is ignored on mac. -- we don't need it to get the right chars. + // But do we need it to translate encodings? hmm... + + CFStringRef cfsName; + ATSUFontID atsuFontID; + ATSFontRef atsFontRef; + Fixed atsuSize; + ATSURGBAlphaColor black; + ATSFontMetrics fontMetrics; + U32 scaledSize; + + bool isBold = false; + bool isItalic = false; + + bool haveModifier; + do + { + haveModifier = false; + if( nameStr.compare( "Bold", 4, String::NoCase | String::Right ) == 0 ) + { + isBold = true; + nameStr = nameStr.substr( 0, nameStr.length() - 4 ).trim(); + haveModifier = true; + } + if( nameStr.compare( "Italic", 6, String::NoCase | String::Right ) == 0 ) + { + isItalic = true; + nameStr = nameStr.substr( 0, nameStr.length() - 6 ).trim(); + haveModifier = true; + } + } + while( haveModifier ); + + // Look up the font. We need it in 2 differnt formats, for differnt Apple APIs. + cfsName = CFStringCreateWithCString( kCFAllocatorDefault, nameStr.c_str(), kCFStringEncodingUTF8); + if(!cfsName) + Con::errorf("Error: could not make a cfstring out of \"%s\" ",nameStr.c_str()); + + atsFontRef = ATSFontFindFromName( cfsName, kATSOptionFlagsDefault); + atsuFontID = FMGetFontFromATSFontRef( atsFontRef); + + // make sure we found it. ATSFontFindFromName() appears to return 0 if it cant find anything. Apple docs contain no info on error returns. + if( !atsFontRef || !atsuFontID ) + { + Con::errorf("MacCarbFont::create - could not load font -%s-",name); + return false; + } + + // adjust the size. win dpi = 96, mac dpi = 72. 72/96 = .75 + // Interestingly enough, 0.75 is not what makes things the right size. + scaledSize = size - 2 - (int)((float)size * 0.1); + mSize = scaledSize; + + // Set up the size and color. We send these to ATSUSetAttributes(). + atsuSize = IntToFixed(scaledSize); + black.red = black.green = black.blue = black.alpha = 1.0; + + // Three parrallel arrays for setting up font, size, and color attributes. + ATSUAttributeTag theTags[] = { kATSUFontTag, kATSUSizeTag, kATSURGBAlphaColorTag}; + ByteCount theSizes[] = { sizeof(ATSUFontID), sizeof(Fixed), sizeof(ATSURGBAlphaColor) }; + ATSUAttributeValuePtr theValues[] = { &atsuFontID, &atsuSize, &black }; + + // create and configure the style object. + ATSUCreateStyle(&mStyle); + ATSUSetAttributes( mStyle, 3, theTags, theSizes, theValues ); + + if( isBold ) + { + ATSUAttributeTag tag = kATSUQDBoldfaceTag; + ByteCount size = sizeof( Boolean ); + Boolean value = true; + ATSUAttributeValuePtr valuePtr = &value; + ATSUSetAttributes( mStyle, 1, &tag, &size, &valuePtr ); + } + + if( isItalic ) + { + ATSUAttributeTag tag = kATSUQDItalicTag; + ByteCount size = sizeof( Boolean ); + Boolean value = true; + ATSUAttributeValuePtr valuePtr = &value; + ATSUSetAttributes( mStyle, 1, &tag, &size, &valuePtr ); + } + + // create the layout object, + ATSUCreateTextLayout(&mLayout); + // we'll bind the layout to a bitmap context when we actually draw. + // ATSUSetTextPointerLocation() - will set the text buffer + // ATSUSetLayoutControls() - will set the cg context. + + // get font metrics, save our baseline and height + ATSFontGetHorizontalMetrics(atsFontRef, kATSOptionFlagsDefault, &fontMetrics); + mBaseline = scaledSize * fontMetrics.ascent; + mHeight = scaledSize * ( fontMetrics.ascent - fontMetrics.descent + fontMetrics.leading ) + 1; + + // cache our grey color space, so we dont have to re create it every time. + mColorSpace = CGColorSpaceCreateDeviceGray(); + + // and finally cache the font's name. We use this to cheat some antialiasing options below. + mName = StringTable->insert(name); + + return true; +} + +//------------------------------------------------------------------------------ +bool MacCarbFont::isValidChar(const UTF8 *str) const +{ + // since only low order characters are invalid, and since those characters + // are single codeunits in UTF8, we can safely cast here. + return isValidChar((UTF16)*str); +} + +bool MacCarbFont::isValidChar( const UTF16 ch) const +{ + // We cut out the ASCII control chars here. Only printable characters are valid. + // 0x20 == 32 == space + if( ch < 0x20 ) + return false; + + return true; +} + +PlatformFont::CharInfo& MacCarbFont::getCharInfo(const UTF8 *str) const +{ + return getCharInfo(oneUTF32toUTF16(oneUTF8toUTF32(str,NULL))); +} + +PlatformFont::CharInfo& MacCarbFont::getCharInfo(const UTF16 ch) const +{ + // We use some static data here to avoid re allocating the same variable in a loop. + // this func is primarily called by GFont::loadCharInfo(), + Rect imageRect; + CGContextRef imageCtx; + U32 bitmapDataSize; + ATSUTextMeasurement tbefore, tafter, tascent, tdescent; + OSStatus err; + + // 16 bit character buffer for the ATUSI calls. + // -- hey... could we cache this at the class level, set style and loc *once*, + // then just write to this buffer and clear the layout cache, to speed up drawing? + static UniChar chUniChar[1]; + chUniChar[0] = ch; + + // Declare and clear out the CharInfo that will be returned. + static PlatformFont::CharInfo c; + dMemset(&c, 0, sizeof(c)); + + // prep values for GFont::addBitmap() + c.bitmapIndex = 0; + c.xOffset = 0; + c.yOffset = 0; + + // put the text in the layout. + // we've hardcoded a string length of 1 here, but this could work for longer strings... (hint hint) + // note: ATSUSetTextPointerLocation() also clears the previous cached layout information. + ATSUSetTextPointerLocation( mLayout, chUniChar, 0, 1, 1); + ATSUSetRunStyle( mLayout, mStyle, 0,1); + + // get the typographic bounds. this tells us how characters are placed relative to other characters. + ATSUGetUnjustifiedBounds( mLayout, 0, 1, &tbefore, &tafter, &tascent, &tdescent); + c.xIncrement = FixedToInt(tafter); + + // find out how big of a bitmap we'll need. + // as a bonus, we also get the origin where we should draw, encoded in the Rect. + ATSUMeasureTextImage( mLayout, 0, 1, 0, 0, &imageRect); + U32 xFudge = 2; + U32 yFudge = 1; + c.width = imageRect.right - imageRect.left + xFudge; // add 2 because small fonts don't always have enough room + c.height = imageRect.bottom - imageRect.top + yFudge; + c.xOrigin = imageRect.left; // dist x0 -> center line + c.yOrigin = -imageRect.top; // dist y0 -> base line + + // kick out early if the character is undrawable + if( c.width == xFudge || c.height == yFudge) + return c; + + // allocate a greyscale bitmap and clear it. + bitmapDataSize = c.width * c.height; + c.bitmapData = new U8[bitmapDataSize]; + dMemset(c.bitmapData,0x00,bitmapDataSize); + + // get a graphics context on the bitmap + imageCtx = CGBitmapContextCreate( c.bitmapData, c.width, c.height, 8, c.width, mColorSpace, kCGImageAlphaNone); + if(!imageCtx) { + Con::errorf("Error: failed to create a graphics context on the CharInfo bitmap! Drawing a blank block."); + c.xIncrement = c.width; + dMemset(c.bitmapData,0x0F,bitmapDataSize); + return c; + } + + // Turn off antialiasing for monospaced console fonts. yes, this is cheating. + if(mSize < 12 && ( dStrstr(mName,"Monaco")!=NULL || dStrstr(mName,"Courier")!=NULL )) + CGContextSetShouldAntialias(imageCtx, false); + + // Set up drawing options for the context. + // Since we're not going straight to the screen, we need to adjust accordingly + CGContextSetShouldSmoothFonts(imageCtx, false); + CGContextSetRenderingIntent(imageCtx, kCGRenderingIntentAbsoluteColorimetric); + CGContextSetInterpolationQuality( imageCtx, kCGInterpolationNone); + CGContextSetGrayFillColor( imageCtx, 1.0, 1.0); + CGContextSetTextDrawingMode( imageCtx, kCGTextFill); + + // tell ATSUI to substitute fonts as needed for missing glyphs + ATSUSetTransientFontMatching(mLayout, true); + + // set up three parrallel arrays for setting up attributes. + // this is how most options in ATSUI are set, by passing arrays of options. + ATSUAttributeTag theTags[] = { kATSUCGContextTag }; + ByteCount theSizes[] = { sizeof(CGContextRef) }; + ATSUAttributeValuePtr theValues[] = { &imageCtx }; + + // bind the layout to the context. + ATSUSetLayoutControls( mLayout, 1, theTags, theSizes, theValues ); + + // Draw the character! + int yoff = c.height < 3 ? 1 : 0; // kludge for 1 pixel high characters, such as '-' and '_' + int xoff = 1; + err = ATSUDrawText( mLayout, 0, 1, IntToFixed(-imageRect.left + xoff), IntToFixed(imageRect.bottom + yoff ) ); + CGContextRelease(imageCtx); + + if(err != noErr) { + Con::errorf("Error: could not draw the character! Drawing a blank box."); + dMemset(c.bitmapData,0x0F,bitmapDataSize); + } + + +#if TORQUE_DEBUG +// Con::printf("Font Metrics: Rect = %2i %2i %2i %2i Char= %C, 0x%x Size= %i, Baseline= %i, Height= %i",imageRect.top, imageRect.bottom, imageRect.left, imageRect.right,ch,ch, mSize,mBaseline, mHeight); +// Con::printf("Font Bounds: left= %2i right= %2i Char= %C, 0x%x Size= %i",FixedToInt(tbefore), FixedToInt(tafter), ch,ch, mSize); +#endif + + return c; +} + +void PlatformFont::enumeratePlatformFonts( Vector< StringTableEntry >& fonts, UTF16* fontFamily ) +{ + if( fontFamily ) + { + // Determine the font ID from the family name. + + ATSUFontID fontID; + if( ATSUFindFontFromName( + fontFamily, + dStrlen( fontFamily ) * 2, + kFontFamilyName, + kFontMicrosoftPlatform, + kFontNoScriptCode, + kFontNoLanguageCode, &fontID ) != kATSUInvalidFontErr ) + { + // Get the number of fonts in the family. + + ItemCount numFonts; + ATSUCountFontNames( fontID, &numFonts ); + + // Read out font names. + + U32 bufferSize = 512; + char* buffer = ( char* ) dMalloc( bufferSize ); + + for( U32 i = 0; i < numFonts; ++ i ) + { + for( U32 n = 0; n < 2; ++ n ) + { + ByteCount actualNameLength; + FontNameCode fontNameCode; + FontPlatformCode fontPlatformCode; + FontScriptCode fontScriptCode; + FontLanguageCode fontLanguageCode; + + if( ATSUGetIndFontName( + fontID, + i, + bufferSize - 2, + buffer, + &actualNameLength, + &fontNameCode, + &fontPlatformCode, + &fontScriptCode, + &fontLanguageCode ) == noErr ) + { + *( ( UTF16* ) &buffer[ actualNameLength ] ) = '\0'; + char* utf8 = convertUTF16toUTF8( ( UTF16* ) buffer ); + fonts.push_back( StringTable->insert( utf8 ) ); + delete [] utf8; + break; + } + + // Allocate larger buffer. + + bufferSize = actualNameLength + 2; + buffer = ( char* ) dRealloc( buffer, bufferSize ); + } + } + + dFree( buffer ); + } + } + else + { + // Get the number of installed fonts. + + ItemCount numFonts; + ATSUFontCount( &numFonts ); + + // Get all the font IDs. + + ATSUFontID* fontIDs = new ATSUFontID[ numFonts ]; + if( ATSUGetFontIDs( fontIDs, numFonts, &numFonts ) == noErr ) + { + U32 bufferSize = 512; + char* buffer = ( char* ) dMalloc( bufferSize ); + + // Read all family names. + + for( U32 i = 0; i < numFonts; ++ i ) + { + for( U32 n = 0; n < 2; ++ n ) + { + ByteCount actualNameLength; + ItemCount fontIndex; + + OSStatus result = ATSUFindFontName( + fontIDs[ i ], + kFontFamilyName, + kFontMicrosoftPlatform, + kFontNoScriptCode, + kFontNoLanguageCode, + bufferSize - 2, + buffer, + &actualNameLength, + &fontIndex ); + + if( result == kATSUNoFontNameErr ) + break; + else if( result == noErr ) + { + *( ( UTF16* ) &buffer[ actualNameLength ] ) = '\0'; + char* utf8 = convertUTF16toUTF8( ( UTF16* ) buffer ); + StringTableEntry name = StringTable->insert( utf8 ); + delete [] utf8; + + // Avoid duplicates. + + bool duplicate = false; + for( U32 i = 0, num = fonts.size(); i < num; ++ i ) + if( fonts[ i ] == name ) + { + duplicate = true; + break; + } + + if( !duplicate ) + fonts.push_back( name ); + + break; + } + + // Allocate larger buffer. + + bufferSize = actualNameLength + 2; + buffer = ( char* ) dRealloc( buffer, bufferSize ); + } + } + + dFree( buffer ); + } + + delete [] fontIDs; + } +} + +//----------------------------------------------------------------------------- +// The following code snippet demonstrates how to get the elusive GlyphIDs, +// which are needed when you want to do various complex and arcane things +// with ATSUI and CoreGraphics. +// +// ATSUGlyphInfoArray glyphinfoArr; +// ATSUGetGlyphInfo( mLayout, kATSUFromTextBeginning, kATSUToTextEnd,sizeof(ATSUGlyphInfoArray), &glyphinfoArr); +// ATSUGlyphInfo glyphinfo = glyphinfoArr.glyphs[0]; +// Con::printf(" Glyphinfo: screenX= %i, idealX=%f, deltaY=%f", glyphinfo.screenX, glyphinfo.idealX, glyphinfo.deltaY); diff --git a/platformMac/macCarbFont.h b/platformMac/macCarbFont.h new file mode 100644 index 0000000..ad47cbc --- /dev/null +++ b/platformMac/macCarbFont.h @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "platform/platformFont.h" + + +class MacCarbFont : public PlatformFont +{ +private: + // Caches style, layout and colorspace data to speed up character drawing. + // TODO: style colors + ATSUStyle mStyle; + ATSUTextLayout mLayout; + CGColorSpaceRef mColorSpace; + + // Cache the baseline and height for the getter methods below. + U32 mHeight; // distance between lines + U32 mBaseline; // distance from drawing point to typographic baseline, + // think of the drawing point as the upper left corner of a text box. + // note: 'baseline' is synonymous with 'ascent' in Torque. + + // Cache the size and name requested in create() + U32 mSize; + StringTableEntry mName; + +public: + MacCarbFont(); + virtual ~MacCarbFont(); + + /// Look up the requested font, cache style, layout, colorspace, and some metrics. + virtual bool create( const char* name, U32 size, U32 charset = TGE_ANSI_CHARSET); + + /// Determine if the character requested is a drawable character, or if it should be ignored. + virtual bool isValidChar( const UTF16 ch) const; + virtual bool isValidChar( const UTF8 *str) const; + + /// Get some vertical data on the font at large. Useful for drawing multiline text, and sizing text boxes. + virtual U32 getFontHeight() const; + virtual U32 getFontBaseLine() const; + + // Draw the character to a temporary bitmap, and fill the CharInfo with various text metrics. + virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const; + virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const; +}; + +inline U32 MacCarbFont::getFontHeight() const +{ + return mHeight; +} + +inline U32 MacCarbFont::getFontBaseLine() const +{ + return mBaseline; +} \ No newline at end of file diff --git a/platformMac/macCarbInput.cpp b/platformMac/macCarbInput.cpp new file mode 100644 index 0000000..90f2d41 --- /dev/null +++ b/platformMac/macCarbInput.cpp @@ -0,0 +1,483 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include + +#include "platform/platformInput.h" +#include "console/console.h" +#include "core/strings/unicode.h" +#include "core/util/tVector.h" + +// Static class variables: +InputManager* Input::smManager; +bool Input::smActive; +U8 Input::smModifierKeys; +InputEvent Input::smInputEvent; + +//----------------------------------------------------------------------------- +// Keycode mapping. + +struct KeyCode +{ + U32 mKeyCode; + UniChar mCharLower; + UniChar mCharUpper; + + KeyCode( U32 keyCode ) + : mKeyCode( keyCode ) {} +}; + +static KeyCode sOSToKeyCode[] = +{ + KEY_A, // 0x00 + KEY_S, // 0x01 + KEY_D, // 0x02 + KEY_F, // 0x03 + KEY_H, // 0x04 + KEY_G, // 0x05 + KEY_Z, // 0x06 + KEY_X, // 0x07 + KEY_C, // 0x08 + KEY_V, // 0x09 + 0, // 0x0A + KEY_B, // 0x0B + KEY_Q, // 0x0C + KEY_W, // 0x0D + KEY_E, // 0x0E + KEY_R, // 0x0F + KEY_Y, // 0x10 + KEY_T, // 0x11 + KEY_1, // 0x12 + KEY_2, // 0x13 + KEY_3, // 0x14 + KEY_4, // 0x15 + KEY_6, // 0x16 + KEY_5, // 0x17 + KEY_EQUALS, // 0x18 + KEY_9, // 0x19 + KEY_7, // 0x1A + KEY_MINUS, // 0x1B + KEY_8, // 0x1C + KEY_0, // 0x1D + KEY_RBRACKET, // 0x1E + KEY_O, // 0x1F + KEY_U, // 0x20 + KEY_LBRACKET, // 0x21 + KEY_I, // 0x22 + KEY_P, // 0x23 + KEY_RETURN, // 0x24 + KEY_L, // 0x25 + KEY_J, // 0x26 + KEY_APOSTROPHE, // 0x27 + KEY_K, // 0x28 + KEY_SEMICOLON, // 0x29 + KEY_BACKSLASH, // 0x2A + KEY_COMMA, // 0x2B + KEY_SLASH, // 0x2C + KEY_N, // 0x2D + KEY_M, // 0x2E + KEY_PERIOD, // 0x2F + KEY_TAB, // 0x30 + KEY_SPACE, // 0x31 + KEY_TILDE, // 0x32 + KEY_BACKSPACE, // 0x33 + 0, // 0x34 + KEY_ESCAPE, // 0x35 + KEY_RALT, // 0x36 + KEY_LALT, // 0x37 + KEY_LSHIFT, // 0x38 + KEY_CAPSLOCK, // 0x39 + KEY_MAC_LOPT, // 0x3A + KEY_LCONTROL, // 0x3B + KEY_RSHIFT, // 0x3C + KEY_MAC_ROPT, // 0x3D + KEY_RCONTROL, // 0x3E + 0, // 0x3F + 0, // 0x40 + KEY_DECIMAL, // 0x41 + 0, // 0x42 + KEY_MULTIPLY, // 0x43 + 0, // 0x44 + KEY_ADD, // 0x45 + 0, // 0x46 + KEY_NUMLOCK, // 0x47 + 0, // 0x48 + 0, // 0x49 + 0, // 0x4A + KEY_DIVIDE, // 0x4B + KEY_NUMPADENTER, // 0x4C + 0, // 0x4D + KEY_SUBTRACT, // 0x4E + 0, // 0x4F + 0, // 0x50 + KEY_SEPARATOR, // 0x51 + KEY_NUMPAD0, // 0x52 + KEY_NUMPAD1, // 0x53 + KEY_NUMPAD2, // 0x54 + KEY_NUMPAD3, // 0x55 + KEY_NUMPAD4, // 0x56 + KEY_NUMPAD5, // 0x57 + KEY_NUMPAD6, // 0x58 + KEY_NUMPAD7, // 0x59 + 0, // 0x5A + KEY_NUMPAD8, // 0x5B + KEY_NUMPAD9, // 0x5C + 0, // 0x5D + 0, // 0x5E + 0, // 0x5F + KEY_F5, // 0x60 + KEY_F6, // 0x61 + KEY_F7, // 0x62 + KEY_F3, // 0x63 + KEY_F8, // 0x64 + KEY_F9, // 0x65 + 0, // 0x66 + KEY_F11, // 0x67 + 0, // 0x68 + KEY_F13, // 0x69 + KEY_F16, // 0x6A + KEY_F14, // 0x6B + 0, // 0x6C + KEY_F10, // 0x6D + 0, // 0x6E + KEY_F12, // 0x6F + 0, // 0x70 + KEY_F15, // 0x71 + KEY_INSERT, // 0x72 + KEY_HOME, // 0x73 + KEY_PAGE_UP, // 0x74 + KEY_DELETE, // 0x75 + KEY_F4, // 0x76 + KEY_END, // 0x77 + KEY_F2, // 0x78 + KEY_PAGE_DOWN, // 0x79 + KEY_F1, // 0x7A + KEY_LEFT, // 0x7B + KEY_RIGHT, // 0x7C + KEY_DOWN, // 0x7D + KEY_UP, // 0x7E +}; + +static Vector< U8 > sKeyCodeToOS( __FILE__, __LINE__ ); + +#define NSShiftKeyMask ( 1 << 17 ) + +static KeyboardLayoutRef sKeyLayout; +static SInt32 sKeyLayoutKind = -1; +static SInt32 sKeyLayoutID = -1; +static SInt32 sLastKeyLayoutID = -1; + +static void GetKeyboardLayout() +{ + KLGetCurrentKeyboardLayout( &sKeyLayout ); + KLGetKeyboardLayoutProperty( sKeyLayout, kKLKind, ( const void** ) &sKeyLayoutKind ); + KLGetKeyboardLayoutProperty( sKeyLayout, kKLIdentifier, ( const void** ) &sKeyLayoutID ); +} + +static bool KeyboardLayoutHasChanged() +{ + GetKeyboardLayout(); + return ( sKeyLayoutID != sLastKeyLayoutID ); +} + +static UniChar OSKeyCodeToUnicode( UInt16 osKeyCode, bool shift = false ) +{ + // Translate the key code. + + UniChar uniChar = 0; + if( sKeyLayoutKind == kKLKCHRKind ) + { + // KCHR mapping. + + void* KCHRData; + KLGetKeyboardLayoutProperty( sKeyLayout, kKLKCHRData, ( const void** ) & KCHRData ); + + UInt16 key = ( osKeyCode & 0x7f ); + if( shift ) + key |= NSShiftKeyMask; + + UInt32 keyTranslateState = 0; + UInt32 charCode = KeyTranslate( KCHRData, key, &keyTranslateState ); + charCode &= 0xff; + + if( keyTranslateState == 0 && charCode ) + uniChar = charCode; + } + else + { + // UCHR mapping. + + UCKeyboardLayout* uchrData; + KLGetKeyboardLayoutProperty( sKeyLayout, kKLuchrData, ( const void** ) &uchrData ); + + UInt32 deadKeyState; + UniCharCount actualStringLength; + UniChar unicodeString[ 4 ]; + UCKeyTranslate( uchrData, + osKeyCode, + kUCKeyActionDown, + ( shift ? 0x02 : 0 ), // Oh yeah... Apple docs are fun... + LMGetKbdType(), + 0, + &deadKeyState, + sizeof( unicodeString ) / sizeof( unicodeString[ 0 ] ), + &actualStringLength, + unicodeString ); + + if( actualStringLength ) + uniChar = unicodeString[ 0 ]; // Well, Unicode is something else, but... + } + + return uniChar; +} + +static void InitKeyCodeMapping() +{ + const U32 numOSKeyCodes = sizeof( sOSToKeyCode ) / sizeof( sOSToKeyCode[ 0 ] ); + GetKeyboardLayout(); + sLastKeyLayoutID = sKeyLayoutID; + + U32 maxKeyCode = 0; + for( U32 i = 0; i < numOSKeyCodes; ++ i ) + { + sOSToKeyCode[ i ].mCharLower = OSKeyCodeToUnicode( i, false ); + sOSToKeyCode[ i ].mCharUpper = OSKeyCodeToUnicode( i, true ); + + if( sOSToKeyCode[ i ].mKeyCode > maxKeyCode ) + maxKeyCode = sOSToKeyCode[ i ].mKeyCode; + } + + if( !sKeyCodeToOS.size() ) + { + sKeyCodeToOS.setSize( maxKeyCode + 1 ); + dMemset( sKeyCodeToOS.address(), 0, sKeyCodeToOS.size() ); + for( U32 i = 0; i < numOSKeyCodes; ++ i ) + sKeyCodeToOS[ sOSToKeyCode[ i ].mKeyCode ] = i; + } +} + +U8 TranslateOSKeyCode(U8 macKeycode) +{ + AssertWarn(macKeycode < sizeof(sOSToKeyCode) / sizeof(sOSToKeyCode[0]), avar("TranslateOSKeyCode - could not translate code %i", macKeycode)); + if(macKeycode >= sizeof(sOSToKeyCode) / sizeof(sOSToKeyCode[0])) + return KEY_NULL; + + return sOSToKeyCode[ macKeycode ].mKeyCode; +} + +U8 TranslateKeyCodeToOS( U8 keycode ) +{ + return sKeyCodeToOS[ keycode ]; +} + +#pragma mark ---- Clipboard functions ---- +//----------------------------------------------------------------------------- +const char* Platform::getClipboard() +{ + // mac clipboards can contain multiple items, + // and each item can be in several differnt flavors, + // such as unicode or plaintext or pdf, etc. + // scan through the clipboard, and return the 1st piece of actual text. + ScrapRef clip; + char *retBuf = ""; + OSStatus err = noErr; + char *dataBuf = ""; + + // get a local ref to the system clipboard + GetScrapByName( kScrapClipboardScrap, kScrapGetNamedScrap, &clip ); + + + // First try to get unicode data, then try to get plain text data. + Size dataSize = 0; + bool plaintext = false; + err = GetScrapFlavorSize(clip, kScrapFlavorTypeUnicode, &dataSize); + if( err != noErr || dataSize <= 0) + { + Con::errorf("some error getting unicode clip"); + plaintext = true; + err = GetScrapFlavorSize(clip, kScrapFlavorTypeText, &dataSize); + } + + // kick out if we don't have any data. + if( err != noErr || dataSize <= 0) + { + Con::errorf("no data, kicking out. size = %i",dataSize); + return ""; + } + + if( err == noErr && dataSize > 0 ) + { + // ok, we've got something! allocate a buffer and copy it in. + char buf[dataSize+1]; + dMemset(buf, 0, dataSize+1); + dataBuf = buf; + // plain text needs no conversion. + // unicode data needs to be converted to normalized utf-8 format. + if(plaintext) + { + GetScrapFlavorData(clip, kScrapFlavorTypeText, &dataSize, &buf); + retBuf = Con::getReturnBuffer(dataSize + 1); + dMemcpy(retBuf,buf,dataSize); + } + else + { + GetScrapFlavorData(clip, kScrapFlavorTypeUnicode, &dataSize, &buf); + + // normalize + CFStringRef cfBuf = CFStringCreateWithBytes(NULL, (const UInt8*)buf, dataSize, kCFStringEncodingUnicode, false); + CFMutableStringRef normBuf = CFStringCreateMutableCopy(NULL, 0, cfBuf); + CFStringNormalize(normBuf, kCFStringNormalizationFormC); + + // convert to utf-8 + U32 normBufLen = CFStringGetLength(normBuf); + U32 retBufLen = CFStringGetMaximumSizeForEncoding(normBufLen,kCFStringEncodingUTF8) + 1; // +1 for the null terminator + retBuf = Con::getReturnBuffer(retBufLen); + CFStringGetCString( normBuf, retBuf, retBufLen, kCFStringEncodingUTF8); + dataSize = retBufLen; + } + + // manually null terminate, just in case. + retBuf[dataSize] = 0; + } + + // return the data, or the empty string if we did not find any data. + return retBuf; +} + +//----------------------------------------------------------------------------- +bool Platform::setClipboard(const char *text) +{ + ScrapRef clip; + U32 textSize; + OSStatus err = noErr; + + // make sure we have something to copy + textSize = dStrlen(text); + if(textSize == 0) + return false; + + // get a local ref to the system clipboard + GetScrapByName( kScrapClipboardScrap, kScrapClearNamedScrap, &clip ); + + // put the data on the clipboard as text + err = PutScrapFlavor( clip, kScrapFlavorTypeText, kScrapFlavorMaskNone, textSize, text); + + // put the data on the clipboard as unicode + const UTF16 *utf16Data = convertUTF8toUTF16(text); + err |= PutScrapFlavor( clip, kScrapFlavorTypeUnicode, kScrapFlavorMaskNone, + dStrlen(utf16Data) * sizeof(UTF16), utf16Data); + delete utf16Data; + + // and see if we were successful. + if( err == noErr ) + return true; + else + return false; +} + +void Input::init() +{ + smManager = NULL; + smActive = false; + + InitKeyCodeMapping(); +} + +U16 Input::getKeyCode( U16 asciiCode ) +{ + if( KeyboardLayoutHasChanged() ) + InitKeyCodeMapping(); + + for( U32 i = 0; i < ( sizeof( sOSToKeyCode ) / sizeof( sOSToKeyCode[ 0 ] ) ); ++ i ) + if( sOSToKeyCode[ i ].mCharLower == asciiCode + || sOSToKeyCode[ i ].mCharUpper == asciiCode ) + return sOSToKeyCode[ i ].mKeyCode; + + return 0; +} + +U16 Input::getAscii( U16 keyCode, KEY_STATE keyState ) +{ + GetKeyboardLayout(); + return OSKeyCodeToUnicode( TranslateKeyCodeToOS( keyCode ), ( keyState == STATE_UPPER ? true : false ) ); +} + +void Input::destroy() +{ +} + +bool Input::enable() +{ + return true; +} + +void Input::disable() +{ +} + +void Input::activate() +{ +} + +void Input::deactivate() +{ +} + +bool Input::isEnabled() +{ + return true; +} + +bool Input::isActive() +{ + return true; +} + +void Input::process() +{ +} + +InputManager* Input::getManager() +{ + return smManager; +} + +ConsoleFunction( enableMouse, bool, 1, 1, "enableMouse()" ) +{ + return true; +} + +ConsoleFunction( disableMouse, void, 1, 1, "disableMouse()" ) +{ +} + +ConsoleFunction( echoInputState, void, 1, 1, "echoInputState()" ) +{ +} + +ConsoleFunction( toggleInputState, void, 1, 1, "toggleInputState()" ) +{ +} + +ConsoleFunction( isJoystickDetected, bool, 1, 1, "Always false on the MAC." ) +{ + return false; +} + +ConsoleFunction( getJoystickAxes, const char*, 2, 2, "(handle instance)" ) +{ + return ""; +} + +ConsoleFunction( deactivateKeyboard, void, 1, 1, "deactivateKeyboard();") +{ + // these are only useful on the windows side. They deal with some vagaries of win32 DirectInput. +} + +ConsoleFunction( activateKeyboard, void, 1, 1, "activateKeyboard();") +{ + // these are only useful on the windows side. They deal with some vagaries of win32 DirectInput. +} diff --git a/platformMac/macCarbMath.cpp b/platformMac/macCarbMath.cpp new file mode 100644 index 0000000..002196b --- /dev/null +++ b/platformMac/macCarbMath.cpp @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformMac/platformMacCarb.h" +#include "platform/platform.h" +#include "console/console.h" +#include "math/mMath.h" +#include "core/strings/stringFunctions.h" + +extern void mInstallLibrary_C(); +extern void mInstallLibrary_Vec(); +extern void mInstall_Library_SSE(); + +static MRandomLCG sgPlatRandom; + +U32 Platform::getMathControlState() +{ + return 0; +} + +void Platform::setMathControlStateKnown() +{ + +} + +void Platform::setMathControlState(U32 state) +{ + +} + +//-------------------------------------- +ConsoleFunction( MathInit, void, 1, 10, "(DETECT|C|VEC|SSE)") +{ + U32 properties = CPU_PROP_C; // C entensions are always used + + if (argc == 1) + { + Math::init(0); + return; + } + for (argc--, argv++; argc; argc--, argv++) + { + if (dStricmp(*argv, "DETECT") == 0) { + Math::init(0); + return; + } + if (dStricmp(*argv, "C") == 0) { + properties |= CPU_PROP_C; + continue; + } + if (dStricmp(*argv, "VEC") == 0) { + properties |= CPU_PROP_ALTIVEC; + continue; + } + if( dStricmp( *argv, "SSE" ) == 0 ) + { + properties |= CPU_PROP_SSE; + continue; + } + Con::printf("Error: MathInit(): ignoring unknown math extension '%s'", *argv); + } + Math::init(properties); +} + + + +//------------------------------------------------------------------------------ +void Math::init(U32 properties) +{ + if (!properties) + // detect what's available + properties = Platform::SystemInfo.processor.properties; + else + // Make sure we're not asking for anything that's not supported + properties &= Platform::SystemInfo.processor.properties; + + Con::printf("Math Init:"); + Con::printf(" Installing Standard C extensions"); + mInstallLibrary_C(); + + #if defined(__VEC__) + if (properties & CPU_PROP_ALTIVEC) + { + Con::printf(" Installing Altivec extensions"); + mInstallLibrary_Vec(); + } + #endif + #ifdef TORQUE_CPU_X86 + if( properties & CPU_PROP_SSE ) + { + Con::printf( " Installing SSE extensions" ); + mInstall_Library_SSE(); + } + #endif + + Con::printf(" "); +} + +//------------------------------------------------------------------------------ +F32 Platform::getRandom() +{ + return sgPlatRandom.randF(); +} + diff --git a/platformMac/macCarbMemory.cpp b/platformMac/macCarbMemory.cpp new file mode 100644 index 0000000..0061512 --- /dev/null +++ b/platformMac/macCarbMemory.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include +#include +#include + +//-------------------------------------- +void* dRealMalloc(dsize_t in_size) +{ + return malloc(in_size); +} + + +//-------------------------------------- +void dRealFree(void* in_pFree) +{ + free(in_pFree); +} + +void *dAligned_malloc(dsize_t in_size, dsize_t alignment) +{ + return _mm_malloc(in_size, alignment); +} + +void dAligned_free(void* p) +{ + return _mm_free(p); +} + +void* dMemcpy(void *dst, const void *src, dsize_t size) +{ + return memcpy(dst,src,size); +} + + +//-------------------------------------- +void* dMemmove(void *dst, const void *src, dsize_t size) +{ + return memmove(dst,src,size); +} + +//-------------------------------------- +void* dMemset(void *dst, int c, dsize_t size) +{ + return memset(dst,c,size); +} + +//-------------------------------------- +int dMemcmp(const void *ptr1, const void *ptr2, dsize_t len) +{ + return(memcmp(ptr1, ptr2, len)); +} diff --git a/platformMac/macCarbMutex.cpp b/platformMac/macCarbMutex.cpp new file mode 100644 index 0000000..f625649 --- /dev/null +++ b/platformMac/macCarbMutex.cpp @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include "platform/platform.h" +#include "platform/threads/mutex.h" +#include "platform/threads/thread.h" +// TODO: examine & dump errno if pthread_* funcs fail. ( only in debug build ) + +class PlatformMutexData +{ +public: + pthread_mutex_t mMutex; + bool locked; + U32 lockedByThread; +}; + +Mutex::Mutex(void) +{ + int ok; + mData = new PlatformMutexData; + pthread_mutexattr_t attr; + ok = pthread_mutexattr_init(&attr); + ok = pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE); + ok = pthread_mutex_init(&(mData->mMutex),&attr); + AssertFatal(ok == 0, "Mutex() failed: pthread_mutex_init() failed."); + + mData->locked = false; + mData->lockedByThread = 0; +} + +Mutex::~Mutex() +{ + int ok; + ok = pthread_mutex_destroy( &(mData->mMutex) ); + AssertFatal(ok == 0, "~Mutex() failed: pthread_mutex_destroy() failed."); + + delete mData; +} + +bool Mutex::lock( bool block) +{ + int ok; + + if(block) + { + ok = pthread_mutex_lock( &(mData->mMutex) ); + AssertFatal( ok != EINVAL, "Mutex::lockMutex() failed: invalid mutex."); + AssertFatal( ok != EDEADLK, "Mutex::lockMutex() failed: system detected a deadlock!"); + AssertFatal( ok == 0, "Mutex::lockMutex() failed: pthread_mutex_lock() failed -- unknown reason."); + } + else { + ok = pthread_mutex_trylock( &(mData->mMutex) ); + // returns EBUSY if mutex was locked by another thread, + // returns EINVAL if mutex was not a valid mutex pointer, + // returns 0 if lock succeeded. + AssertFatal( ok != EINVAL, "Mutex::lockMutex(non blocking) failed: invalid mutex."); + if( ok != 0 ) + return false; + + AssertFatal( ok == 0, "Mutex::lockMutex(non blocking) failed: pthread_mutex_trylock() failed -- unknown reason."); + } + + mData->locked = true; + mData->lockedByThread = ThreadManager::getCurrentThreadId(); + return true; +} + +void Mutex::unlock() +{ + int ok; + ok = pthread_mutex_unlock( &(mData->mMutex) ); + AssertFatal( ok == 0, "Mutex::unlockMutex() failed: pthread_mutex_unlock() failed."); + mData->locked = false; + mData->lockedByThread = 0; +} diff --git a/platformMac/macCarbProcessControl.cpp b/platformMac/macCarbProcessControl.cpp new file mode 100644 index 0000000..dd143ef --- /dev/null +++ b/platformMac/macCarbProcessControl.cpp @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformMac/platformMacCarb.h" +#include "platform/event.h" +#include "core/util/journal/process.h" +#include "console/console.h" + +void Platform::postQuitMessage(const U32 in_quitVal) +{ + Process::requestShutdown(); +} + +void Platform::debugBreak() +{ + DebugStr("\pDEBUG_BREAK!"); +} + +void Platform::forceShutdown(S32 returnValue) +{ + exit(returnValue); +} + +void Platform::restartInstance() +{ + // execl() leaves open file descriptors open, that's the main reason it's not + // used here. We want to start fresh. + // get the path to the torque executable + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFURLRef execURL = CFBundleCopyExecutableURL(mainBundle); + CFStringRef execString = CFURLCopyFileSystemPath(execURL, kCFURLPOSIXPathStyle); + + // append ampersand so that we can launch without blocking. + // encase in quotes so that spaces in the path are accepted. + CFMutableStringRef mut = CFStringCreateMutableCopy(NULL, 0, execString); + CFStringInsert(mut, 0, CFSTR("\"")); + CFStringAppend(mut, CFSTR("\" & ")); + + U32 len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(mut), kCFStringEncodingUTF8); + char *execCString = new char[len+1]; + CFStringGetCString(mut, execCString, len, kCFStringEncodingUTF8); + execCString[len] = '\0'; + + Con::printf("---- %s -----",execCString); + system(execCString); +} diff --git a/platformMac/macCarbSemaphore.cpp b/platformMac/macCarbSemaphore.cpp new file mode 100644 index 0000000..063b96f --- /dev/null +++ b/platformMac/macCarbSemaphore.cpp @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "platform/platform.h" +#include "platform/threads/semaphore.h" + +class PlatformSemaphore +{ +public: + MPSemaphoreID mSemaphore; + + PlatformSemaphore(S32 initialCount) + { + OSStatus err = MPCreateSemaphore(S32_MAX - 1, initialCount, &mSemaphore); + AssertFatal(err == noErr, "Failed to allocate semaphore!"); + } + + ~PlatformSemaphore() + { + OSStatus err = MPDeleteSemaphore(mSemaphore); + AssertFatal(err == noErr, "Failed to destroy semaphore!"); + } +}; + +Semaphore::Semaphore(S32 initialCount) +{ + mData = new PlatformSemaphore(initialCount); +} + +Semaphore::~Semaphore() +{ + AssertFatal(mData && mData->mSemaphore, "Semaphore::destroySemaphore: invalid semaphore"); + delete mData; +} + +bool Semaphore::acquire(bool block) +{ + AssertFatal(mData && mData->mSemaphore, "Semaphore::acquireSemaphore: invalid semaphore"); + OSStatus err = MPWaitOnSemaphore(mData->mSemaphore, block ? kDurationForever : kDurationImmediate); + return(err == noErr); +} + +void Semaphore::release() +{ + AssertFatal(mData && mData->mSemaphore, "Semaphore::releaseSemaphore: invalid semaphore"); + OSStatus err = MPSignalSemaphore(mData->mSemaphore); + AssertFatal(err == noErr, "Failed to release semaphore!"); +} diff --git a/platformMac/macCarbStrings.cpp b/platformMac/macCarbStrings.cpp new file mode 100644 index 0000000..e9d8526 --- /dev/null +++ b/platformMac/macCarbStrings.cpp @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include +#include +#include +#include +#include +#include "core/strings/stringFunctions.h" + +char *dStrnew(const char *src) +{ + char *buffer = new char[dStrlen(src) + 1]; + dStrcpy(buffer, src); + return buffer; +} + +char* dStrstr(char *str1, char *str2) +{ + return strstr(str1,str2); +} + +int dSprintf(char *buffer, dsize_t /*bufferSize*/, const char *format, ...) +{ + va_list args; + va_start(args, format); + S32 len = vsprintf(buffer, format, args); + + return (len); +} + + +int dVsprintf(char *buffer, dsize_t /*bufferSize*/, const char *format, void *arglist) +{ + S32 len = vsprintf(buffer, format, (char*)arglist); + + return (len); +} + +int dFflushStdout() +{ + return fflush(stdout); +} + +int dFflushStderr() +{ + return fflush(stderr); +} + diff --git a/platformMac/macCarbThread.cpp b/platformMac/macCarbThread.cpp new file mode 100644 index 0000000..4656a45 --- /dev/null +++ b/platformMac/macCarbThread.cpp @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "platform/threads/thread.h" +#include "platform/threads/semaphore.h" +#include "platform/threads/mutex.h" +#include + +class PlatformThreadData +{ +public: + ThreadRunFunction mRunFunc; + void* mRunArg; + Thread* mThread; + Semaphore mGateway; // default count is 1 + U32 mThreadID; + bool mDead; +}; + +ThreadManager::MainThreadId ThreadManager::smMainThreadId; + +//----------------------------------------------------------------------------- +// Function: ThreadRunHandler +// Summary: Calls Thread::run() with the thread's specified run argument. +// Neccesary because Thread::run() is provided as a non-threaded +// way to execute the thread's run function. So we have to keep +// track of the thread's lock here. +static void *ThreadRunHandler(void * arg) +{ + PlatformThreadData *mData = reinterpret_cast(arg); + Thread *thread = mData->mThread; + + // mThreadID is filled in twice, once here and once in pthread_create(). + // We fill in mThreadID here as well as in pthread_create() because addThread() + // can execute before pthread_create() returns and sets mThreadID. + // The value from pthread_create() and pthread_self() are guaranteed to be equivalent (but not identical) + mData->mThreadID = ThreadManager::getCurrentThreadId(); + + ThreadManager::addThread(thread); + thread->run(mData->mRunArg); + ThreadManager::removeThread(thread); + + bool autoDelete = thread->autoDelete; + + mData->mThreadID = 0; + mData->mDead = true; + mData->mGateway.release(); + + if( autoDelete ) + delete thread; + + // return value for pthread lib's benefit + return NULL; + // the end of this function is where the created pthread will die. +} + +//----------------------------------------------------------------------------- +Thread::Thread(ThreadRunFunction func, void* arg, bool start_thread, bool autodelete) +{ + AssertFatal( !start_thread, "Thread::Thread() - auto-starting threads from ctor has been disallowed since the run() method is virtual" ); + + mData = new PlatformThreadData; + mData->mRunFunc = func; + mData->mRunArg = arg; + mData->mThread = this; + mData->mThreadID = 0; + mData->mDead = false; + autoDelete = autodelete; +} + +Thread::~Thread() +{ + stop(); + if( isAlive() ) + join(); + + delete mData; +} + +void Thread::start( void* arg ) +{ + // cause start to block out other pthreads from using this Thread, + // at least until ThreadRunHandler exits. + mData->mGateway.acquire(); + + // reset the shouldStop flag, so we'll know when someone asks us to stop. + shouldStop = false; + + mData->mDead = false; + + if( !mData->mRunArg ) + mData->mRunArg = arg; + + pthread_create((pthread_t*)(&mData->mThreadID), NULL, ThreadRunHandler, mData); +} + +bool Thread::join() +{ + // not using pthread_join here because pthread_join cannot deal + // with multiple simultaneous calls. + + mData->mGateway.acquire(); + AssertFatal( !isAlive(), "Thread::join() - thread not dead after join()" ); + mData->mGateway.release(); + + return true; +} + +void Thread::run(void* arg) +{ + if(mData->mRunFunc) + mData->mRunFunc(arg); +} + +bool Thread::isAlive() +{ + return ( !mData->mDead ); +} + +U32 Thread::getId() +{ + return mData->mThreadID; +} + +void Thread::_setName( const char* ) +{ + // Not supported. Wading through endless lists of Thread-1, Thread-2, Thread-3, ... trying to find + // that one thread you are looking for is just so much fun. +} + +U32 ThreadManager::getCurrentThreadId() +{ + return (U32)pthread_self(); +} + +bool ThreadManager::compare(U32 threadId_1, U32 threadId_2) +{ + return pthread_equal((_opaque_pthread_t*)threadId_1, (_opaque_pthread_t*)threadId_2); +} diff --git a/platformMac/macCarbTime.cpp b/platformMac/macCarbTime.cpp new file mode 100644 index 0000000..36abfea --- /dev/null +++ b/platformMac/macCarbTime.cpp @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "platform/platformTimer.h" +#include +#include + +//-------------------------------------- + +static U32 sgCurrentTime = 0; + +//-------------------------------------- +void Platform::getLocalTime(LocalTime <) +{ + struct tm systime; + time_t long_time; + + /// Get time as long integer. + time( &long_time ); + /// Convert to local time, thread safe. + localtime_r( &long_time, &systime ); + + /// Fill the return struct + lt.sec = systime.tm_sec; + lt.min = systime.tm_min; + lt.hour = systime.tm_hour; + lt.month = systime.tm_mon; + lt.monthday = systime.tm_mday; + lt.weekday = systime.tm_wday; + lt.year = systime.tm_year; + lt.yearday = systime.tm_yday; + lt.isdst = systime.tm_isdst; +} + +String Platform::localTimeToString( const LocalTime < ) +{ + tm systime; + + systime.tm_sec = lt.sec; + systime.tm_min = lt.min; + systime.tm_hour = lt.hour; + systime.tm_mon = lt.month; + systime.tm_mday = lt.monthday; + systime.tm_wday = lt.weekday; + systime.tm_year = lt.year; + systime.tm_yday = lt.yearday; + systime.tm_isdst = lt.isdst; + + return asctime( &systime ); +} + +/// Gets the time in seconds since the Epoch +U32 Platform::getTime() +{ + time_t epoch_time; + time( &epoch_time ); + return epoch_time; +} + +/// Gets the time in milliseconds since some epoch. In this case, system start time. +/// Storing milliseconds in a U32 overflows every 49.71 days +U32 Platform::getRealMilliseconds() +{ + // Duration is a S32 value. + // if negative, it is in microseconds. + // if positive, it is in milliseconds. + Duration durTime = AbsoluteToDuration(UpTime()); + U32 ret; + if( durTime < 0 ) + ret = durTime / -1000; + else + ret = durTime; + + return ret; +} + +U32 Platform::getVirtualMilliseconds() +{ + return sgCurrentTime; +} + +void Platform::advanceTime(U32 delta) +{ + sgCurrentTime += delta; +} + +/// Asks the operating system to put the process to sleep for at least ms milliseconds +void Platform::sleep(U32 ms) +{ + // note: this will overflow if you want to sleep for more than 49 days. just so ye know. + usleep( ms * 1000 ); +} + +//---------------------------------------------------------------------------------- +PlatformTimer* PlatformTimer::create() +{ + return new DefaultPlatformTimer; +} + +void Platform::fileToLocalTime(const FileTime & ft, LocalTime * lt) +{ + if(!lt) + return; + + time_t long_time = ft; + + struct tm systime; + + /// Convert to local time, thread safe. + localtime_r( &long_time, &systime ); + + /// Fill the return struct + lt->sec = systime.tm_sec; + lt->min = systime.tm_min; + lt->hour = systime.tm_hour; + lt->month = systime.tm_mon; + lt->monthday = systime.tm_mday; + lt->weekday = systime.tm_wday; + lt->year = systime.tm_year; + lt->yearday = systime.tm_yday; + lt->isdst = systime.tm_isdst; +} + diff --git a/platformMac/macCarbUtil.cpp b/platformMac/macCarbUtil.cpp new file mode 100644 index 0000000..2f5bb57 --- /dev/null +++ b/platformMac/macCarbUtil.cpp @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include + +#include "platform/platform.h" +#include "core/strings/stringFunctions.h" + +void Platform::outputDebugString( const char *string, ... ) +{ +#ifdef TORQUE_DEBUG + char buffer[ 2048 ]; + + va_list args; + va_start( args, string ); + + dVsprintf( buffer, sizeof( buffer ), string, args ); + va_end( args ); + + U32 length = strlen( buffer ); + if( length == ( sizeof( buffer ) - 1 ) ) + length --; + + buffer[ length ] = '\n'; + buffer[ length + 1 ] = '\0'; + + fputs( buffer, stderr ); + fflush(stderr); +#endif +} + +#pragma mark ---- Platform utility funcs ---- +//-------------------------------------- +// Web browser function: +//-------------------------------------- +bool Platform::openWebBrowser( const char* webAddress ) +{ + OSStatus err; + CFURLRef url = CFURLCreateWithBytes(NULL,(UInt8*)webAddress,dStrlen(webAddress),kCFStringEncodingASCII,NULL); + err = LSOpenCFURLRef(url,NULL); + CFRelease(url); + + return(err==noErr); +} + diff --git a/platformMac/macCarbVolume.cpp b/platformMac/macCarbVolume.cpp new file mode 100644 index 0000000..8391a5e --- /dev/null +++ b/platformMac/macCarbVolume.cpp @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include + + +#include "platformMac/macCarbVolume.h" +#include "platform/platformVolume.h" +#include "console/console.h" + + +//#define DEBUG_SPEW + + +//----------------------------------------------------------------------------- +// Change notifications. +//----------------------------------------------------------------------------- + + +// http://developer.apple.com/documentation/Darwin/Conceptual/FSEvents_ProgGuide/KernelQueues/KernelQueues.html + + +MacFileSystemChangeNotifier::MacFileSystemChangeNotifier( MacFileSystem* fs ) + : Parent( fs ) +{ + VECTOR_SET_ASSOCIATION( mDirs ); + VECTOR_SET_ASSOCIATION( mEvents ); + + mQueue = kqueue(); + if( mQueue < 0 ) + Con::errorf( "MacFileSystemChangeNotifier - could not create kqueue" ); +} + +MacFileSystemChangeNotifier::~MacFileSystemChangeNotifier() +{ + for( U32 i = 0, num = mEvents.size(); i < num; ++ i ) + close( mEvents[ i ].ident ); +} + +void MacFileSystemChangeNotifier::internalProcessOnce() +{ + if( mQueue < 0 ) + return; + + // Allocate temporary event data space. + + const U32 numEvents = mEvents.size(); + struct kevent* eventData = ( struct kevent* ) alloca( numEvents * sizeof( struct kevent ) ); + + // Check for events. + + struct timespec timeout; + dMemset( &timeout, 0, sizeof( struct timespec ) ); + + const S32 numChanges = kevent( mQueue, mEvents.address(), numEvents, eventData, numEvents, &timeout ); + if( numChanges > 0 ) + { + for( U32 i = 0; i < numChanges; ++ i ) + { + U32 index = ( U32 ) eventData[ i ].udata; + + // Signal the change. + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[MacFileSystemChangeNotifier] changed dir %i: '%s'", index, mDirs[ index ].getFullPath().c_str() ); + #endif + + internalNotifyDirChanged( mDirs[ index ] ); + } + } +} + +bool MacFileSystemChangeNotifier::internalAddNotification( const Torque::Path& dir ) +{ + if( mQueue < 0 ) + return false; + + for( U32 i = 0, num = mDirs.size(); i < num; ++ i ) + if( mDirs[ i ] == dir ) + return false; + + // Map the path. + + Torque::Path fullFSPath = mFS->mapTo( dir ); + String osPath = PathToOS( fullFSPath ); + + // Open the path. + + int handle = open( osPath.c_str(), O_EVTONLY ); + if( handle <= 0 ) + return false; + + // Create the event. + + const U32 index = mEvents.size(); + mDirs.push_back( dir ); + mEvents.increment(); + struct kevent& event = mEvents.last(); + + EV_SET( &event, handle, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE | NOTE_RENAME | NOTE_ATTRIB, 0, ( void* ) index ); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[MacFileSystemChangeNotifier] added change notification %i to '%s' (full path: %s)", index, dir.getFullPath().c_str(), osPath.c_str() ); + #endif + + return true; +} + +bool MacFileSystemChangeNotifier::internalRemoveNotification( const Torque::Path& dir ) +{ + if( mQueue < 0 ) + return false; + + for( U32 i = 0, num = mDirs.size(); i < num; ++ i ) + if( mDirs[ i ] == dir ) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[MacFileSystemChangeNotifier] removing change notification %i from '%s'", i, dir.getFullPath().c_str() ); + #endif + + close( mEvents[ i ].ident ); + + // Adjust indices. + + for( U32 n = i + 1; n < mEvents.size(); ++ n ) + mEvents[ n ].udata = ( void* ) ( ( ( U32 ) mEvents[ n ].udata ) - 1 ); + + // Erase notification. + + mEvents.erase( i ); + mDirs.erase( i ); + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Platform API. +//----------------------------------------------------------------------------- + +Torque::FS::FileSystemRef Platform::FS::createNativeFS( const String &volume ) +{ + return new MacFileSystem( volume ); +} + +bool Torque::FS::VerifyWriteAccess(const Torque::Path &path) +{ + return true; +} diff --git a/platformMac/macCarbVolume.h b/platformMac/macCarbVolume.h new file mode 100644 index 0000000..8b80b86 --- /dev/null +++ b/platformMac/macCarbVolume.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MACCARBVOLUME_H_ +#define _MACCARBVOLUME_H_ + +#include +#include +#include + + +#ifndef _POSIXVOLUME_H_ + #include "platformPOSIX/posixVolume.h" +#endif +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif + + +class MacFileSystem; + + +/// File system change notifications on Mac. +class MacFileSystemChangeNotifier : public Torque::FS::FileSystemChangeNotifier +{ + public: + + typedef Torque::FS::FileSystemChangeNotifier Parent; + + protected: + + /// The kqueue handle. + int mQueue; + + /// + Vector< struct kevent > mEvents; + + /// + Vector< Torque::Path > mDirs; + + // FileSystemChangeNotifier. + virtual void internalProcessOnce(); + virtual bool internalAddNotification( const Torque::Path& dir ); + virtual bool internalRemoveNotification( const Torque::Path& dir ); + + public: + + MacFileSystemChangeNotifier( MacFileSystem* fs ); + + virtual ~MacFileSystemChangeNotifier(); +}; + +/// Mac filesystem. +class MacFileSystem : public Torque::Posix::PosixFileSystem +{ + public: + + typedef Torque::Posix::PosixFileSystem Parent; + + MacFileSystem( String volume ) + : Parent( volume ) + { + mChangeNotifier = new MacFileSystemChangeNotifier( this ); + } +}; + +#endif // !_MACCARBVOLUME_H_ diff --git a/platformMac/macCocoaDialogs.mm b/platformMac/macCocoaDialogs.mm new file mode 100644 index 0000000..157de7e --- /dev/null +++ b/platformMac/macCocoaDialogs.mm @@ -0,0 +1,643 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// Get our GL header included before Apple's +#include "platformMac/platformMacCarb.h" +// Don't include Apple's +#define __gl_h_ + +#include "platform/tmm_off.h" +#include +#include "platform/tmm_on.h" + +#include "console/simBase.h" +#include "platform/nativeDialogs/fileDialog.h" +#include "platform/threads/mutex.h" +#include "core/util/safeDelete.h" +#include "math/mMath.h" +#include "core/strings/unicode.h" +#include "console/consoleTypes.h" +#include "platform/threads/thread.h" +#include "platform/threads/semaphore.h" + +class FileDialogOpaqueData +{ +public: + Semaphore *sem; + FileDialogOpaqueData() { sem = new Semaphore(0); } + ~FileDialogOpaqueData() { delete sem; } +}; + +class FileDialogFileExtList +{ +public: + Vector list; + UTF8* data; + + FileDialogFileExtList(const char* exts) { data = dStrdup(exts); } + ~FileDialogFileExtList() { SAFE_DELETE(data); } +}; + +class FileDialogFileTypeList +{ +public: + UTF8* filterData; + Vector names; + Vector exts; + bool any; + + FileDialogFileTypeList(const char* filter) { filterData = dStrdup(filter); any = false;} + ~FileDialogFileTypeList() + { + SAFE_DELETE(filterData); + for(U32 i = 0; i < exts.size(); i++) + delete exts[i]; + } +}; + +#undef new + +//----------------------------------------------------------------------------- +// Overwrite confirmation. +//----------------------------------------------------------------------------- + +@interface OverwriteConfirmation : NSObject + - ( NSString* ) panel: ( id ) sender userEnteredFilename: ( NSString* ) filename confirmed: ( BOOL ) okFlag; +@end + +@implementation OverwriteConfirmation + +- ( NSString* ) panel: ( id) sender userEnteredFilename: ( NSString* ) filename confirmed: ( BOOL ) okFlag +{ + // We want the real filename with the absolute path *and* the extension. + const char* file = [ [ sender filename ] UTF8String ]; + + if( okFlag && Platform::isFile( file ) ) + { + String message = String::ToString( "Do you really want to overwrite '%s'.", file ); + if( !Platform::AlertOKCancel( "Confirm overwrite", message ) ) + return nil; + } + + return filename; +} + +@end + +//----------------------------------------------------------------------------- +// PlatformFileDlgData Implementation +//----------------------------------------------------------------------------- +FileDialogData::FileDialogData() +{ + // Default Path + // + // Try to provide consistent experience by recalling the last file path + // - else + // Default to Working Directory if last path is not set or is invalid + mDefaultPath = StringTable->insert( Con::getVariable("Tools::FileDialogs::LastFilePath") ); + if( mDefaultPath == StringTable->lookup("") || !Platform::isDirectory( mDefaultPath ) ) + mDefaultPath = Platform::getCurrentDirectory(); + + mDefaultFile = StringTable->insert(""); + mFilters = StringTable->insert(""); + mFile = StringTable->insert(""); + mTitle = StringTable->insert(""); + + mStyle = 0; + + mOpaqueData = new FileDialogOpaqueData(); + +} +FileDialogData::~FileDialogData() +{ + delete mOpaqueData; +} + +//----------------------------------------------------------------------------- +// FileDialog Implementation +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(FileDialog); + +FileDialog::FileDialog() : mData() +{ + // Default to File Must Exist Open Dialog style + mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST; + mChangePath = false; +} + +FileDialog::~FileDialog() +{ +} + +void FileDialog::initPersistFields() +{ + // why is this stuff buried in another class? + addProtectedField("DefaultPath", TypeString, Offset(mData.mDefaultPath, FileDialog), &setDefaultPath, &defaultProtectedGetFn, "Default Path when Dialog is shown"); + addProtectedField("DefaultFile", TypeString, Offset(mData.mDefaultFile, FileDialog), &setDefaultFile, &defaultProtectedGetFn, "Default File when Dialog is shown"); + addProtectedField("FileName", TypeString, Offset(mData.mFile, FileDialog), &setFile, &defaultProtectedGetFn, "Default File when Dialog is shown"); + addProtectedField("Filters", TypeString, Offset(mData.mFilters, FileDialog), &setFilters, &defaultProtectedGetFn, "Default File when Dialog is shown"); + addField("Title", TypeString, Offset(mData.mTitle, FileDialog), "Default File when Dialog is shown"); + addProtectedField("ChangePath", TypeBool, Offset(mChangePath, FileDialog), &setChangePath, &getChangePath, "True/False whether to set the working directory to the directory returned by the dialog" ); + Parent::initPersistFields(); +} + + +static FileDialogFileExtList* _MacCarbGetFileExtensionsFromString(const char* filter) +{ + FileDialogFileExtList* list = new FileDialogFileExtList(filter); + + char* token = list->data; + char* place = list->data; + + for( ; *place; place++) + { + if(*place != ';') + continue; + + *place = '\0'; + + list->list.push_back(token); + + ++place; + token = place; + } + // last token + list->list.push_back(token); + + return list; + +} + +static FileDialogFileTypeList* _MacCarbGetFileTypesFromString(const char* filter) +{ + FileDialogFileTypeList &list = *(new FileDialogFileTypeList(filter)); + + char* token = list.filterData; + char* place = list.filterData; + + // scan the filter list until we hit a null. + // when we see the separator '|', replace it with a null, and save the token + // format is description|extension|description|extension + bool isDesc = true; + for( ; *place; place++) + { + if(*place != '|') + continue; + + *place = '\0'; + + if(isDesc) + list.names.push_back(token); + else + { + // detect *.* + if(dStrstr((const char*)token, "*.*")) + list.any = true; + + list.exts.push_back(_MacCarbGetFileExtensionsFromString(token)); + } + + + isDesc = !isDesc; + ++place; + token = place; + } + list.exts.push_back(_MacCarbGetFileExtensionsFromString(token)); + + return &list; +} + +static NSArray* _MacCocoaCreateAndRunSavePanel(FileDialogData &mData) +{ + NSSavePanel* panel = [NSSavePanel savePanel]; + + // User freedom niceties + [panel setCanCreateDirectories:YES]; + [panel setCanSelectHiddenExtension:YES]; + [panel setTreatsFilePackagesAsDirectories:YES]; + + NSString *initialFile = [[NSString stringWithUTF8String:mData.mDefaultFile] lastPathComponent]; + + // we only use mDefaultDir if mDefault path is not set. + NSString *dir; + if(dStrlen(mData.mDefaultPath) < 1) + dir = [[NSString stringWithUTF8String:mData.mDefaultFile] stringByDeletingLastPathComponent]; + else + dir = [NSString stringWithUTF8String: mData.mDefaultPath]; + [panel setDirectory:dir]; + + // todo: move file type handling to an accessory view. + // parse file types + FileDialogFileTypeList *fTypes = _MacCarbGetFileTypesFromString(mData.mFilters); + + // fill an array with the possible file types + NSMutableArray* types = [NSMutableArray arrayWithCapacity:10]; + for(U32 i = 0; i < fTypes->exts.size(); i++) + { + for(U32 j = 0; j < fTypes->exts[i]->list.size(); j++) + { + char* ext = fTypes->exts[i]->list[j]; + if(ext) + { + if(dStrncmp(ext, "*.", 2) == 0) + ext+=2; + + [types addObject:[NSString stringWithUTF8String:ext]]; + } + } + } + if([types count] > 0) + [panel setAllowedFileTypes:types]; + + // if any file type was *.*, user may select any file type. + [panel setAllowsOtherFileTypes:fTypes->any]; + + // Create delegate. + + OverwriteConfirmation* delegate = nil; + if( mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT ) + { + delegate = [ OverwriteConfirmation new ]; + [ panel setDelegate: delegate ]; + } + + //--------------------------------------------------------------------------- + // Display the panel, enter a modal loop. This blocks. + //--------------------------------------------------------------------------- + U32 button = [panel runModalForDirectory:dir file:initialFile]; + + // return the file name + NSMutableArray *array = [NSMutableArray arrayWithCapacity:10]; + if(button != NSFileHandlingPanelCancelButton) + [array addObject:[panel filename]]; + + if( delegate ) + { + [ panel setDelegate: nil ]; + [ delegate release ]; + } + + delete fTypes; + + return array; + + // TODO: paxorr: show as sheet + // crashes when we try to display the window as a sheet. Not sure why. + // the sheet is instantly dismissed, and crashes as it's dismissing itself. + // here's the code snippet to get an nswindow from our carbon WindowRef + //NSWindow *nsAppWindow = [[NSWindow alloc] initWithWindowRef:platState.appWindow]; +} + +NSArray* _MacCocoaCreateAndRunOpenPanel(FileDialogData &mData) +{ + NSOpenPanel* panel = [NSOpenPanel openPanel]; + + // User freedom niceties + [panel setCanCreateDirectories:YES]; + [panel setCanSelectHiddenExtension:YES]; + [panel setTreatsFilePackagesAsDirectories:YES]; + + [panel setAllowsMultipleSelection:(mData.mStyle & FileDialogData::FDS_MULTIPLEFILES)]; + + // + bool chooseDir = (mData.mStyle & FileDialogData::FDS_BROWSEFOLDER); + [panel setCanChooseFiles: !chooseDir ]; + [panel setCanChooseDirectories: chooseDir ]; + if(chooseDir) + { + [panel setPrompt:@"Choose"]; + [panel setTitle:@"Choose Folder"]; + } + + NSString *initialFile = [[NSString stringWithUTF8String:mData.mDefaultFile] lastPathComponent]; + + // we only use mDefaultDir if mDefault path is not set. + NSString *dir; + if(dStrlen(mData.mDefaultPath) < 1) + dir = [[NSString stringWithUTF8String:mData.mDefaultFile] stringByDeletingLastPathComponent]; + else + dir = [NSString stringWithUTF8String: mData.mDefaultPath]; + [panel setDirectory:dir]; + + // todo: move file type handling to an accessory view. + // parse file types + FileDialogFileTypeList *fTypes = _MacCarbGetFileTypesFromString(mData.mFilters); + + // fill an array with the possible file types + NSMutableArray* types = [NSMutableArray arrayWithCapacity:10]; + for(U32 i = 0; i < fTypes->exts.size(); i++) + { + for(U32 j = 0; j < fTypes->exts[i]->list.size(); j++) + { + char* ext = fTypes->exts[i]->list[j]; + if(ext) + { + if(dStrncmp(ext, "*.", 2) == 0) + ext+=2; + + [types addObject:[NSString stringWithUTF8String:ext]]; + } + } + } + if([types count] > 0) + [panel setAllowedFileTypes:types]; + + // if any file type was *.*, user may select any file type. + [panel setAllowsOtherFileTypes:fTypes->any]; + + + //--------------------------------------------------------------------------- + // Display the panel, enter a modal loop. This blocks. + //--------------------------------------------------------------------------- + U32 button = [panel runModalForDirectory:dir file:initialFile ]; + + // return the file name + NSMutableArray *array = [NSMutableArray arrayWithCapacity:10]; + if(button != NSFileHandlingPanelCancelButton) + [array addObject:[panel filename]]; + + delete fTypes; + + return array; +} + +void MacCarbShowDialog(void* dialog) +{ + FileDialog* d = static_cast(dialog); + d->Execute(); +} +// +// Execute Method +// +bool FileDialog::Execute() +{ +// if(! ThreadManager::isCurrentThread(platState.firstThreadId)) +// { +// MacCarbSendTorqueEventToMain(kEventTorqueModalDialog,this); +// mData.mOpaqueData->sem->acquire(); +// return; +// } + + NSArray* nsFileArray; + if(mData.mStyle & FileDialogData::FDS_OPEN) + nsFileArray = _MacCocoaCreateAndRunOpenPanel(mData); + else if(mData.mStyle & FileDialogData::FDS_SAVE) + nsFileArray = _MacCocoaCreateAndRunSavePanel(mData); + else + { + Con::errorf("Bad File Dialog Setup."); + mData.mOpaqueData->sem->release(); + return false; + } + + if([nsFileArray count] == 0) + return false; + + if(! (mData.mStyle & FileDialogData::FDS_MULTIPLEFILES) && [nsFileArray count] >= 1) + { + const UTF8* f = [(NSString*)[nsFileArray objectAtIndex:0] UTF8String]; + mData.mFile = StringTable->insert(f); + } + else + { + for(U32 i = 0; i < [nsFileArray count]; i++) + { + const UTF8* f = [(NSString*)[nsFileArray objectAtIndex:i] UTF8String]; + setDataField(StringTable->insert("files"), Con::getIntArg(i), StringTable->insert(f)); + } + setDataField(StringTable->insert("fileCount"), NULL, Con::getIntArg([nsFileArray count])); + } + mData.mOpaqueData->sem->release(); + + + return true; + +} + +ConsoleMethod( FileDialog, Execute, bool, 2, 2, "%fileDialog.Execute();" ) +{ + return object->Execute(); +} + +//----------------------------------------------------------------------------- +// Dialog Filters +//----------------------------------------------------------------------------- +bool FileDialog::setFilters(void* obj, const char* data) +{ + // Will do validate on write at some point. + if( !data ) + return true; + + return true; + +}; + + +//----------------------------------------------------------------------------- +// Default Path Property - String Validated on Write +//----------------------------------------------------------------------------- +bool FileDialog::setDefaultPath(void* obj, const char* data) +{ + + if( !data ) + return true; + + return true; + +}; + +//----------------------------------------------------------------------------- +// Default File Property - String Validated on Write +//----------------------------------------------------------------------------- +bool FileDialog::setDefaultFile(void* obj, const char* data) +{ + if( !data ) + return true; + + return true; +}; + +//----------------------------------------------------------------------------- +// ChangePath Property - Change working path on successful file selection +//----------------------------------------------------------------------------- +bool FileDialog::setChangePath(void* obj, const char* data) +{ + bool bChangePath = dAtob( data ); + + FileDialog *pDlg = static_cast( obj ); + + if( bChangePath ) + pDlg->mData.mStyle |= FileDialogData::FDS_CHANGEPATH; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_CHANGEPATH; + + return true; +}; + +const char* FileDialog::getChangePath(void* obj, const char* data) +{ + FileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_CHANGEPATH ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +bool FileDialog::setFile(void* obj, const char* data) +{ + return false; +}; + +//----------------------------------------------------------------------------- +// OpenFileDialog Implementation +//----------------------------------------------------------------------------- +OpenFileDialog::OpenFileDialog() +{ + // Default File Must Exist + mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST; +} + +OpenFileDialog::~OpenFileDialog() +{ + mMustExist = true; + mMultipleFiles = false; +} + +IMPLEMENT_CONOBJECT(OpenFileDialog); + +//----------------------------------------------------------------------------- +// Console Properties +//----------------------------------------------------------------------------- +void OpenFileDialog::initPersistFields() +{ + addProtectedField("MustExist", TypeBool, Offset(mMustExist, OpenFileDialog), &setMustExist, &getMustExist, "True/False whether the file returned must exist or not" ); + addProtectedField("MultipleFiles", TypeBool, Offset(mMultipleFiles, OpenFileDialog), &setMultipleFiles, &getMultipleFiles, "True/False whether multiple files may be selected and returned or not" ); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// File Must Exist - Boolean +//----------------------------------------------------------------------------- +bool OpenFileDialog::setMustExist(void* obj, const char* data) +{ + bool bMustExist = dAtob( data ); + + OpenFileDialog *pDlg = static_cast( obj ); + + if( bMustExist ) + pDlg->mData.mStyle |= FileDialogData::FDS_MUSTEXIST; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_MUSTEXIST; + + return true; +}; + +const char* OpenFileDialog::getMustExist(void* obj, const char* data) +{ + OpenFileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_MUSTEXIST ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +//----------------------------------------------------------------------------- +// Can Select Multiple Files - Boolean +//----------------------------------------------------------------------------- +bool OpenFileDialog::setMultipleFiles(void* obj, const char* data) +{ + bool bMustExist = dAtob( data ); + + OpenFileDialog *pDlg = static_cast( obj ); + + if( bMustExist ) + pDlg->mData.mStyle |= FileDialogData::FDS_MULTIPLEFILES; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_MULTIPLEFILES; + + return true; +}; + +const char* OpenFileDialog::getMultipleFiles(void* obj, const char* data) +{ + OpenFileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +//----------------------------------------------------------------------------- +// SaveFileDialog Implementation +//----------------------------------------------------------------------------- +SaveFileDialog::SaveFileDialog() +{ + // Default File Must Exist + mData.mStyle = FileDialogData::FDS_SAVE | FileDialogData::FDS_OVERWRITEPROMPT; + mOverwritePrompt = true; +} + +SaveFileDialog::~SaveFileDialog() +{ +} + +IMPLEMENT_CONOBJECT(SaveFileDialog); + +//----------------------------------------------------------------------------- +// Console Properties +//----------------------------------------------------------------------------- +void SaveFileDialog::initPersistFields() +{ + addProtectedField("OverwritePrompt", TypeBool, Offset(mOverwritePrompt, SaveFileDialog), &setOverwritePrompt, &getOverwritePrompt, "True/False whether the dialog should prompt before accepting an existing file name" ); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// Prompt on Overwrite - Boolean +//----------------------------------------------------------------------------- +bool SaveFileDialog::setOverwritePrompt(void* obj, const char* data) +{ + bool bOverwrite = dAtob( data ); + + SaveFileDialog *pDlg = static_cast( obj ); + + if( bOverwrite ) + pDlg->mData.mStyle |= FileDialogData::FDS_OVERWRITEPROMPT; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_OVERWRITEPROMPT; + + return true; +}; + +const char* SaveFileDialog::getOverwritePrompt(void* obj, const char* data) +{ + SaveFileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +//----------------------------------------------------------------------------- +// OpenFolderDialog Implementation +//----------------------------------------------------------------------------- + +OpenFolderDialog::OpenFolderDialog() +{ + mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_OVERWRITEPROMPT | FileDialogData::FDS_BROWSEFOLDER; + + mMustExistInDir = ""; +} + +IMPLEMENT_CONOBJECT(OpenFolderDialog); + +void OpenFolderDialog::initPersistFields() +{ + addField("fileMustExist", TypeFilename, Offset(mMustExistInDir, OpenFolderDialog), "File that must in selected folder for it to be valid"); + + Parent::initPersistFields(); +} diff --git a/platformMac/macCocoaPlatform.mm b/platformMac/macCocoaPlatform.mm new file mode 100644 index 0000000..b9fab4b --- /dev/null +++ b/platformMac/macCocoaPlatform.mm @@ -0,0 +1,222 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#import +#import +#include +#include "platform/platform.h" +#include "console/console.h" +#include "core/stringTable.h" +#include "platform/platformInput.h" +#include "platform/threads/thread.h" + +#pragma mark ---- Various Directories ---- +//----------------------------------------------------------------------------- +const char* Platform::getUserDataDirectory() +{ + // application support directory is most in line with the current usages of this function. + // this may change with later usage + // perhaps the user data directory should be pref-controlled? + NSString *nsDataDir = [@"~/Library/Application Support/" stringByStandardizingPath]; + return StringTable->insert([nsDataDir UTF8String]); +} + +//----------------------------------------------------------------------------- +const char* Platform::getUserHomeDirectory() +{ + return StringTable->insert([[@"~/" stringByStandardizingPath] UTF8String]); +} + +//----------------------------------------------------------------------------- +StringTableEntry osGetTemporaryDirectory() +{ + NSString *tdir = NSTemporaryDirectory(); + const char *path = [tdir UTF8String]; + return StringTable->insert(path); +} + +#pragma mark ---- Administrator ---- +//----------------------------------------------------------------------------- +bool Platform::getUserIsAdministrator() +{ + // if we can write to /Library, we're probably an admin + // HACK: this is not really very good, because people can chmod Library. + return (access("/Library", W_OK) == 0); +} + +#pragma mark ---- Cosmetic ---- +//----------------------------------------------------------------------------- +bool Platform::displaySplashWindow() +{ + return false; +} + +#pragma mark ---- File IO ---- +//----------------------------------------------------------------------------- +bool dPathCopy(const char* source, const char* dest, bool nooverwrite) +{ + NSFileManager *manager = [NSFileManager defaultManager]; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *nsource = [[NSString stringWithUTF8String:source] stringByStandardizingPath]; + NSString *ndest = [[NSString stringWithUTF8String:dest] stringByStandardizingPath]; + NSString *ndestFolder = [ndest stringByDeletingLastPathComponent]; + + if(! [manager fileExistsAtPath:nsource]) + { + Con::errorf("dPathCopy: no file exists at %s",source); + return false; + } + + if( [manager fileExistsAtPath:ndest] ) + { + if(nooverwrite) + { + Con::errorf("dPathCopy: file already exists at %s",dest); + return false; + } + Con::warnf("Deleting files at path: %s", dest); + bool deleted = [manager removeFileAtPath:ndest handler:nil]; + if(!deleted) + { + Con::errorf("Copy failed! Could not delete files at path: %s", dest); + return false; + } + } + + if([manager fileExistsAtPath:ndestFolder] == NO) + { + ndestFolder = [ndestFolder stringByAppendingString:@"/"]; // createpath requires a trailing slash + Platform::createPath([ndestFolder UTF8String]); + } + + bool ret = [manager copyPath:nsource toPath:ndest handler:nil]; + + [pool release]; + return ret; + +} + +//----------------------------------------------------------------------------- +bool dFileRename(const char *source, const char *dest) +{ + if(source == NULL || dest == NULL) + return false; + + NSFileManager *manager = [NSFileManager defaultManager]; + + NSString *nsource = [manager stringWithFileSystemRepresentation:source length:dStrlen(source)]; + NSString *ndest = [manager stringWithFileSystemRepresentation:dest length:dStrlen(dest)]; + + if(! [manager fileExistsAtPath:nsource]) + { + Con::errorf("dFileRename: no file exists at %s",source); + return false; + } + + if( [manager fileExistsAtPath:ndest] ) + { + Con::warnf("dFileRename: Deleting files at path: %s", dest); + } + + bool ret = [manager movePath:nsource toPath:ndest handler:nil]; + + return ret; +} + +#pragma mark - +#pragma mark ---- ShellExecute ---- +class ExecuteThread : public Thread +{ + const char* zargs; + const char* directory; + const char* executable; +public: + ExecuteThread(const char *_executable, const char *_args /* = NULL */, const char *_directory /* = NULL */) : Thread(0, NULL, false, true) + { + zargs = dStrdup(_args); + directory = dStrdup(_directory); + executable = dStrdup(_executable); + start(); + } + + virtual void run(void* arg); +}; + +static char* _unDoubleQuote(char* arg) +{ + U32 len = dStrlen(arg); + if(!len) + return arg; + + if(arg[0] == '"' && arg[len-1] == '"') + { + arg[len - 1] = '\0'; + return arg + 1; + } + return arg; +} + +void ExecuteThread::run(void* arg) +{ +// 2k should be enough. if it's not, bail. +// char buf[2048]; +// U32 len = dSprintf(buf, sizeof(buf), "%s %s -workingDir %s", executable, args, directory); +// if( len >= sizeof(buf)) +// { +// Con::errorf("shellExecute(): the command was too long, and won't be run."); +// return; +// } +// // calls sh with the string and blocks until the command returns. +// system(buf); + + // FIXME: there is absolutely no error checking in here. + printf("creating nstask\n"); + NSTask *aTask = [[NSTask alloc] init]; + NSMutableArray *array = [NSMutableArray array]; + + // scan the args list, breaking it up, space delimited, backslash escaped. + U32 len = dStrlen(zargs); + char args[len+1]; + dStrncpy(args, zargs, len+1); + char *lastarg = args; + bool escaping = false; + for(int i = 0; i< len; i++) + { + char c = args[i]; + // a backslash escapes the next character + if(escaping) + continue; + if(c == '\\') + escaping = true; + + if(c == ' ') + { + args[i] = '\0'; + if(*lastarg) + [array addObject:[NSString stringWithUTF8String: _unDoubleQuote(lastarg)]]; + lastarg = args + i + 1; + } + } + if(*lastarg) + [array addObject:[NSString stringWithUTF8String: _unDoubleQuote(lastarg)]]; + + [aTask setArguments: array]; + + [aTask setCurrentDirectoryPath:[NSString stringWithUTF8String: this->directory]]; + [aTask setLaunchPath:[NSString stringWithUTF8String:executable]]; + [aTask launch]; + [aTask waitUntilExit]; + U32 ret = [aTask terminationStatus]; + Con::executef("onExecuteDone", Con::getIntArg(ret)); + printf("done nstask\n"); +} + +ConsoleFunction(shellExecute, bool, 2, 4, "(executable, [args], [directory])") +{ + ExecuteThread *et = new ExecuteThread(argv[1], argc > 2 ? argv[2] : NULL, argc > 3 ? argv[3] : NULL); + TORQUE_UNUSED(et); + return true; // Bug: BPNC error: need feedback on whether the command was sucessful +} diff --git a/platformMac/macDLibrary.cpp b/platformMac/macDLibrary.cpp new file mode 100644 index 0000000..34e3a1b --- /dev/null +++ b/platformMac/macDLibrary.cpp @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include "platform/types.h" +#include "platform/platformDlibrary.h" +#include + +class MacDLibrary : public DLibrary +{ + void* _handle; +public: + MacDLibrary(); + ~MacDLibrary(); + bool open(const char* file); + void close(); + void* bind(const char* name); +}; + +MacDLibrary::MacDLibrary() +{ + _handle = NULL; +} + +MacDLibrary::~MacDLibrary() +{ + close(); +} + +bool MacDLibrary::open(const char* file) +{ + Platform::getExecutablePath(); + _handle = dlopen(file, RTLD_LAZY | RTLD_LOCAL); + return _handle != NULL; +} + +void* MacDLibrary::bind(const char* name) +{ + return _handle ? dlsym(_handle, name) : NULL; +} + +void MacDLibrary::close() +{ + if(_handle) + dlclose(_handle); + + _handle = NULL; +} + +DLibraryRef OsLoadLibrary(const char* file) +{ + MacDLibrary* library = new MacDLibrary(); + if(!library->open(file)) + { + delete library; + return NULL; + } + + return library; +} diff --git a/platformMac/macGLUtils.h b/platformMac/macGLUtils.h new file mode 100644 index 0000000..b4adf3b --- /dev/null +++ b/platformMac/macGLUtils.h @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _MACGLUTILS_H_ +#define _MACGLUTILS_H_ + +static Vector _beginPixelFormatAttributesForDisplay(CGDirectDisplayID display) +{ + Vector attributes; + attributes.reserve(16); // Most attribute lists won't exceed this + + attributes.push_back(NSOpenGLPFAScreenMask); + attributes.push_back((NSOpenGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(display)); + attributes.push_back(NSOpenGLPFANoRecovery); + attributes.push_back(NSOpenGLPFADoubleBuffer); + attributes.push_back(NSOpenGLPFAAccelerated); + attributes.push_back(NSOpenGLPFAAuxBuffers); + attributes.push_back((NSOpenGLPixelFormatAttribute)1); + return attributes; +} + +static void _addColorAlphaDepthStencilAttributes(Vector& attributes, U32 color, U32 alpha, U32 depth, U32 stencil) +{ + attributes.push_back(NSOpenGLPFAColorSize); attributes.push_back((NSOpenGLPixelFormatAttribute)color); + attributes.push_back(NSOpenGLPFAAlphaSize); attributes.push_back((NSOpenGLPixelFormatAttribute)alpha); + attributes.push_back(NSOpenGLPFADepthSize); attributes.push_back((NSOpenGLPixelFormatAttribute)depth); + attributes.push_back(NSOpenGLPFAStencilSize); attributes.push_back((NSOpenGLPixelFormatAttribute)stencil); +} + +static void _addFullscreenAttributes(Vector& attributes) +{ + attributes.push_back(NSOpenGLPFAFullScreen); +} + +static void _endAttributeList(Vector& attributes) +{ + attributes.push_back((NSOpenGLPixelFormatAttribute)0); +} + +static Vector _createStandardPixelFormatAttributesForDisplay(CGDirectDisplayID display) +{ + Vector attributes = _beginPixelFormatAttributesForDisplay(display); + _addColorAlphaDepthStencilAttributes(attributes, 24, 8, 24, 8); + _endAttributeList(attributes); + + return attributes; +} + +/// returns an opengl pixel format suitable for creating shared opengl contexts. +static NSOpenGLPixelFormat* _createStandardPixelFormat() +{ + Vector attributes = _createStandardPixelFormatAttributesForDisplay(kCGDirectMainDisplay); + + NSOpenGLPixelFormat* fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes.address()]; + + return fmt; +} + +#endif \ No newline at end of file diff --git a/platformMac/macMain.mm b/platformMac/macMain.mm new file mode 100644 index 0000000..d74748b --- /dev/null +++ b/platformMac/macMain.mm @@ -0,0 +1,177 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "app/mainLoop.h" +#include "platform/platformInput.h" +#include +#include +#include "console/console.h" +#include "platform/threads/thread.h" + +// TODO: let the mainLoop's sleep time happen via rescheduling the timer every run-through. +extern S32 sgTimeManagerProcessInterval; + +@interface MainLoopTimerHandler : NSObject +{ + U32 argc; + const char** argv; + NSTimeInterval interval; +} + +(id)startTimerWithintervalMs:(U32)intervalMs argc:(U32)_argc argv:(const char**)_argv; + -(void)firstFire:(NSTimer*)theTimer; + -(void)fireTimer:(NSTimer*)theTimer; +@end +@implementation MainLoopTimerHandler + -(void)firstFire:(NSTimer*)theTimer + { + StandardMainLoop::init(); + StandardMainLoop::handleCommandLine(argc, argv); + [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(fireTimer:) userInfo:nil repeats:YES]; + } + -(void)fireTimer:(NSTimer*)theTimer + { + if(!StandardMainLoop::doMainLoop()) + { + StandardMainLoop::shutdown(); + [theTimer invalidate]; + [NSApp setDelegate:nil]; + [NSApp terminate:self]; + } +// if(!mainLoop || !mainLoop->mainLoop()) +// { +// // stop the timer from firing again +// if(mainLoop) +// mainLoop->shutdown(); +// +// [theTimer invalidate]; +// [NSApp setDelegate:nil]; +// [NSApp terminate:self]; +// } + } + +(id)startTimerWithintervalMs:(U32)intervalMs argc:(U32)_argc argv:(const char**)_argv + { + MainLoopTimerHandler* handler = [[[MainLoopTimerHandler alloc] init] autorelease]; + handler->argc = _argc; + handler->argv = _argv; + handler->interval = intervalMs / 1000.0; // interval in milliseconds + [NSTimer scheduledTimerWithTimeInterval:handler->interval target:handler selector:@selector(firstFire:) userInfo:nil repeats:NO]; + return handler; + } +@end + +#pragma mark - + +#ifndef TORQUE_SHARED +//----------------------------------------------------------------------------- +// main() - the real one - this is the actual program entry point. +//----------------------------------------------------------------------------- +S32 main(S32 argc, const char **argv) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + // get command line and text file args, filter them + + // now, we prepare to hand off execution to torque & macosx. + U32 appReturn = 0; + printf("installing torque main loop timer\n"); + [MainLoopTimerHandler startTimerWithintervalMs:1 argc:argc argv:argv]; + printf("starting NSApplicationMain\n"); + appReturn = NSApplicationMain(argc, argv); + printf("NSApplicationMain exited\n"); + + // shut down the engine + + [pool release]; + + return appReturn; +} + +#endif + +static NSApplication *app = NULL; +static NSAutoreleasePool* pool = NULL; + +void torque_mac_engineinit(S32 argc, const char **argv) +{ + + if (!Platform::getWebDeployment()) + { + pool = [[NSAutoreleasePool alloc] init]; + app = [NSApplication sharedApplication]; + } + +} + +void torque_mac_enginetick() +{ + + if (!Platform::getWebDeployment()) + { + + NSEvent *e = [app nextEventMatchingMask: NSAnyEventMask + untilDate: [NSDate distantPast] + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (e) + [app sendEvent: e]; + + } +} + +void torque_mac_engineshutdown() +{ + if (!Platform::getWebDeployment()) + { + [pool release]; + } +} + + +extern "C" { + +//----------------------------------------------------------------------------- +// torque_macmain() - entry point for application using bundle +//----------------------------------------------------------------------------- +S32 torque_macmain(S32 argc, const char **argv) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + // get command line and text file args, filter them + + // now, we prepare to hand off execution to torque & macosx. + U32 appReturn = 0; + printf("installing torque main loop timer\n"); + [MainLoopTimerHandler startTimerWithintervalMs:1 argc:argc argv:argv]; + printf("starting NSApplicationMain\n"); + appReturn = NSApplicationMain(argc, argv); + printf("NSApplicationMain exited\n"); + + // shut down the engine + + [pool release]; + + return appReturn; +} + +} // extern "C" + + + +#pragma mark ---- Init funcs ---- +//------------------------------------------------------------------------------ +void Platform::init() +{ + // Set the platform variable for the scripts + Con::setVariable( "$platform", "macos" ); + + Input::init(); +} + +//------------------------------------------------------------------------------ +void Platform::shutdown() +{ + Input::destroy(); +} diff --git a/platformMac/macMsgBox.mm b/platformMac/macMsgBox.mm new file mode 100644 index 0000000..76ae537 --- /dev/null +++ b/platformMac/macMsgBox.mm @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#import +#include "platform/nativeDialogs/msgBox.h" +#include "console/console.h" + +void Platform::AlertOK(const char *windowTitle, const char *message) +{ + Platform::messageBox(windowTitle, message, MBOk, MIInformation); +} + +//-------------------------------------- +bool Platform::AlertOKCancel(const char *windowTitle, const char *message) +{ + return ( Platform::messageBox(windowTitle, message, MBOkCancel, MIInformation) == MROk ); +} + +//-------------------------------------- +bool Platform::AlertRetry(const char *windowTitle, const char *message) +{ + return ( Platform::messageBox(windowTitle, message, MBRetryCancel, MIInformation) == MRRetry ); +} + +namespace MsgBoxMac +{ + struct _NSStringMap + { + S32 num; + NSString* ok; + NSString* cancel; + NSString* third; + }; + + static _NSStringMap sgButtonTextMap[] = + { + { MBOk, @"OK", nil, nil }, + { MBOkCancel, @"OK", @"Cancel", nil }, + { MBRetryCancel, @"Retry", @"Cancel", nil }, + { MBSaveDontSave, @"Yes", @"No", nil }, + { MBSaveDontSaveCancel, @"Yes", @"No", @"Cancel" }, + { -1, nil, nil, nil } + }; + + struct _NSAlertResultMap + { + S32 num; + S32 ok; + S32 cancel; + S32 third; + }; + + static _NSAlertResultMap sgAlertResultMap[] = + { + { MBOk, MROk, 0, 0 }, + { MBOkCancel, MROk, MRCancel, 0 }, + { MBRetryCancel, MRRetry, MRCancel, 0 }, + { MBSaveDontSave, MROk, MRDontSave, 0 }, + { MBSaveDontSaveCancel, MROk, MRDontSave, MRCancel }, + { -1, nil, nil, nil } + }; +} // end MsgBoxMac namespace + +//----------------------------------------------------------------------------- +S32 Platform::messageBox(const UTF8 *title, const UTF8 *message, MBButtons buttons, MBIcons icon) +{ +// TODO: put this on the main thread + + // determine the button text + NSString *okBtn = nil; + NSString *cancelBtn = nil; + NSString *thirdBtn = nil; + U32 i; + for(i = 0; MsgBoxMac::sgButtonTextMap[i].num != -1; i++) + { + if(MsgBoxMac::sgButtonTextMap[i].num != buttons) + continue; + + okBtn = MsgBoxMac::sgButtonTextMap[i].ok; + cancelBtn = MsgBoxMac::sgButtonTextMap[i].cancel; + thirdBtn = MsgBoxMac::sgButtonTextMap[i].third; + break; + } + if(MsgBoxMac::sgButtonTextMap[i].num == -1) + Con::errorf("Unknown message box button set requested. Mac Platform::messageBox() probably needs to be updated."); + + // convert title and message to NSStrings + NSString *nsTitle = [NSString stringWithUTF8String:title]; + NSString *nsMessage = [NSString stringWithUTF8String:message]; + // TODO: ensure that the cursor is the expected shape + // show the alert + S32 result = -2; + + NSAlert *alert = [NSAlert alertWithMessageText:nsTitle + defaultButton:okBtn + alternateButton:thirdBtn + otherButton:cancelBtn + informativeTextWithFormat:nsMessage]; + + switch(icon) + { + // TODO: + // Currently, NSAlert only provides two alert icon options. + // NSWarningAlertStyle and NSInformationalAlertStyle are identical and + // display the application icon, while NSCriticalAlertStyle displays + // a shrunken app icon badge on a yellow-triangle-with-a-bang icon. + // If custom icons were created, they could be used here with the + // message [alert setIcon:foo] + case MIWarning: // MIWarning = 0 + + case MIQuestion: // MIquestion = 3 + [alert setAlertStyle:NSWarningAlertStyle]; + break; + + case MIInformation: // MIInformation = 1 + [alert setAlertStyle:NSInformationalAlertStyle]; + break; + + case MIStop: // MIStop = 3 + [alert setAlertStyle:NSCriticalAlertStyle]; + break; + + default: + Con::errorf("Unknown message box icon requested. Mac Platform::messageBox() probably needs to be updated."); + } + + id appDelegate = [NSApp delegate]; + [NSApp setDelegate: nil]; + + U32 cursorDepth = 0; + + while(!CGCursorIsVisible()) + { + CGDisplayShowCursor(kCGDirectMainDisplay); + cursorDepth++; + } + + CGAssociateMouseAndMouseCursorPosition(true); + result = [alert runModal]; + + [NSApp setDelegate: appDelegate]; + + S32 ret = 0; + for(U32 i = 0; MsgBoxMac::sgAlertResultMap[i].num != -1; i++) + { + if(MsgBoxMac::sgAlertResultMap[i].num != buttons) + continue; + + switch(result) + { + case NSAlertDefaultReturn: + ret = MsgBoxMac::sgAlertResultMap[i].ok; break; + case NSAlertOtherReturn: + ret = MsgBoxMac::sgAlertResultMap[i].cancel; break; + case NSAlertAlternateReturn: + ret = MsgBoxMac::sgAlertResultMap[i].third; break; + } + } + + return ret; +} \ No newline at end of file diff --git a/platformMac/menus/mainMenu.nib/classes.nib b/platformMac/menus/mainMenu.nib/classes.nib new file mode 100644 index 0000000..bf6aa25 --- /dev/null +++ b/platformMac/menus/mainMenu.nib/classes.nib @@ -0,0 +1,30 @@ + + + + + IBClasses + + + ACTIONS + + toggleAutomaticLinkDetection + id + toggleAutomaticQuoteSubstitution + id + toggleGrammarChecking + id + toggleSmartInsertDelete + id + + CLASS + FirstResponder + LANGUAGE + ObjC + SUPERCLASS + NSObject + + + IBVersion + 1 + + diff --git a/platformMac/menus/mainMenu.nib/info.nib b/platformMac/menus/mainMenu.nib/info.nib new file mode 100644 index 0000000..439f912 --- /dev/null +++ b/platformMac/menus/mainMenu.nib/info.nib @@ -0,0 +1,20 @@ + + + + + IBFramework Version + 629 + IBLastKnownRelativeProjectPath + ../../../../../GameExamples/T3D/buildFiles/Xcode/T3D.xcodeproj + IBOldestOS + 5 + IBOpenObjects + + 29 + + IBSystem Version + 9F33 + targetFramework + IBCocoaFramework + + diff --git a/platformMac/menus/mainMenu.nib/keyedobjects.nib b/platformMac/menus/mainMenu.nib/keyedobjects.nib new file mode 100644 index 0000000000000000000000000000000000000000..01a177f254388bb4dfee6cfd351aa431a223ac39 GIT binary patch literal 17142 zcmbVz349aP^Z0w4>~7OE?IvlPG)I#*X?g%nvI}wx2MoAeUSsatMM7BBuz7+$e&82P&fSpWU>OV)=i*pI@O(_U*ozc{B6o+&7@OAiuQC z<2j2kqL2b95set6LZLmJ!+j;C`Tn9TXPLjar!%J{cW^!o^>F$J4DscbmFf_FedQ^{ zqL4mqo23Q@^K!~^MBI;yNY8BV8|BMOtC+M|i|-*d3PWC02fc(Ep~ff;wM0Wu2^x>4 zps8pYnuTVgIcPpwjFzC~Xa!o0HlWRD3)+gdp*xmh0o_Lr(9h@>^gBjaiD}GW7OSxi8?X__U@Law1nj~oSip60JzO6*z%S!gxHWEr z+v1M6EAEN2aWC8(_s4_qV4RPK;37O6kH91G7(4;Lh9~0J@l-q;FT{)RV!Q;e!0YgO zyaB(3H{$p4cDw`c#Cz~T{4qX+593eqm-r+;h0oz{@g@8nzKpNqAMqW07eBy%;K%rH z{0~J@Dk_vxQxQ}&WuVMdJmsJgsZ`2Mc_=SchiXJMre3C+P|c}!RC}re)tTx}^`LrE z*;HT3M-8L~Q3X^fRYsLlqbQMjjhaZkPEDewQFExd)I4fFwV3*e`h)tDdZb_#;fe?a zr!XjtiWo&a^{2w2NK_;#QWSL*jTDU)FDqIqS}9s9(iNQ)Jrp^Le0XArqDWDy7_E3k zF-9>#@tR_y;&sJT#WclI#ahLCaDS6xr(!>R9#9-roP^I)it~yq@Of2nOK}fA?<@XM zGD?jyTp6J>D&wd>mGR0%Ws0(fvbM6Z@?~WUWlLonWm{!OWx6s`nXT-l9H7jFr-mxa zl;z4X%CSljt|r6pEcjidT%x>3H>VfTi|HlwQhFJ^oL)h{Nw1_=(W~h-^jdlyy`J7c zzeT@IzeB%EZ=~O&H_@BvE%a7;8~r}Lo!&w3q<7J~=?~~V^oR6b`XhQDy`MfnAEZB~ z57CF|Pv|4`QTkK*Gx~G-7=4`nf<8fiNuQ)o(WmJ%^jZ2G{S|$l{+hl(U!=dGzojqH z-_e)pEA&7VEa%pm4F<}!1IxyoE)t}{27o6Ifdd*%n` zHuEEMhq=rA#N1=3M+ znq^p)4PjMmD63|}SPdJ_Mz9>KWp%8cjbx+PXx6|Q*%;Qunpq2LWn) z*#y?bCbCIvGMmEIU~96q*i_cddRQ;ZvjSV2t;4>=)@AFl_1Ok&L$(pyn0=XT!Zu~o z*k){VwguagZN;``+pulfc5HjL1KW{JXFIW-*$lP|+m-FX_Uh^^DlaIw%;}H@g`)_> zAuZA&J&Ht8C>j}%5yc=AG9wGJqF7`@b`*!=kpnqV0&<~5l!TH|3aWu>qFN{wxsj(^ zdS(~DzpR@xw;-pqv`>0wr-0D`t5PbPm*&#Aue4iELAeh`V$w6ya&vv9rTGK$3-ZfG zb;{4HxCe8a`-_UeVu39y?UkOHo>S;6mEgelRwZyT!e25}dMW@>x|0p~0D$xrZpaf} zgs1|xzeIYnmA|O0V@^@dAYVy8j!Vz%mJjq4_`2s8<@rYh=&o2tB{)MhbW3P^0$Sh5 zGN7V&fR?}nks|mfaVNp01m6`y2#ytLf=dW4BiKgpk0KHof~^GK(c(b_m!Ae{@JK+l zVXgA|q_v1jQ31rJ6@%%|&5_u08r4PhP<_;(M}XhH((J&OB6xf?YJeK{h56;BW&Xkd z{XHrL0?TaMthqnepYxQJ63&-V6Vw#ASm72*MRsKXM>EuX4Qh^BpoXxnj^$-Jz@AKB zS=aPT_#2qn3bht@Z06!o8`KuHL+w!q)DfklPN*}=KwVHK%0gXHH`E>VKs`}5>VyY(IwUpJ>pAZL$Qh2Qfw!77Q2c)#lB*m zm@gKIrQ!&2j3|l|#VO(&;!JU#xL8~+t`^se?}%H@!g_|H0#t~KKy!=HFyLJaUuju> zk<2?_MW(;JB-htHJ+nj30AGO^EmC5r81*&GD@A3f91TY!&`2~2jYhAaF=#A$6^%n8 zA}}EfSRp>_kvZ5u!k$)8(D@8ZXvtw!@uLwPd<^rsR?Fz`j91WhkLB|(vwm06Zk<_myX zik7WL%TPmLN+rGhxajht!TEVUAT&skOB)1cyopw>K`YTJ33U`KHh%!@)3OqO!PD&u z>j{iXmcd_ARmkhmdRdA_(OjJr-$w7CcPpg`ERWHFfkUDx!0ldbfGm5bvcV8OWef&F zB{Jp9Duh)@#Xr8%3Any%M0PNGzj-O^cCPv6jQ3heIW?|CoZY z=r8mb{au0M3GC}EzrP?WzZkG-L_vpipi;GEr2JDhU<$VA5plP;r%z`v0d_E&<;AI~ zsTrO7mXsGsLmhHP`OC|mk%3h>6v(J6HmGKqSo4hWSPO{jS1`U_kkD}?j>6I65rTCD zM-mMCRM)ITVm$0Udzs(fsswlqCLpJzB&TSQuWgY$5JZ9*TY!W{;>*>MV0%Uak~rPC zQua>;-5Q)U*c5yB{IbDMr4IWlAn1y_{}nJ!!pVTPsn|jiKov`jYoO7%78-+71MJ6M z%!3Qc${7R%G!vW4TuMJR0;VbY3Z+qMxcL1#I#ZJ{@?F3jmS76P=S%9^pm@Z@Oz_HIb z&>i;x4qO*+0_VGyN~%{7h^g!Y{U!ER5aMKjCo>fLO5nOsy6&7)JjsU7Hv9OMMw5`c%M|FN2^K$ZPEOqBRzy$9UM&HA-%> zyV#>*jfy@JvG&d-zTx>`E;3q6YnBZr9*tiCTC>GI)zSJY&^oRXCP3^Z_6|Sny;UBMy+U z_Lp}oo+BAcJP*&u3qTkFf7xSRR`{uAaK6}xCs^D#y>M2kMLihboa%d`(#2f zNiyO&L&1aefsCTK@}fbNh=@WI29%Wex;Xi~Y`=rzXiE7kiUx#}DiEssagk{Q!2LZJ z4uKT>C3(IQ*)Ks{S(KMkk|#eXo2X}OqcpM^nfd}VLTNz`l?FN!O9pycg@NwiL##Lr>WzNh#nnXbF@HI5JC|Ta6H?WHg3K z2~v^S;+%krK;BlCF)}MRI4Gb5sy2X{FD|MMR9yg7F91sR$P2`UGN_~?NWKOmKV&m@ z_*M*QRssBzRz1k>%4h%~Kq=%7tsx;VeV(ytR5L)hL|j%K!j?e_B<fDXx^4zz>8hvP34i3`j2SRt$kmP_#t# zruqQXHR8JJp!N@`xH5to3DDmaH&+K6vQKJEfGgBEp#ME_lgyQkPd6$xo|*s~ z^^*7-o{r@oYADGBx31n5Yj2sR6x2_++H2h>F6;vBS z68H$DZPWs4;TmcowMecvR8$AjAgUY%iKhH1R|u#j)KY>io4E{X8MT~Rfp?>I)G7#c zM^kI5b@&}B0mL{C&%#&HEefIEsLj+CYAcnFE>YWYUuq|{iyA^{s6Et&crqf?K59R8 zfI0}_VkUK%N~4ZYBe0hGjQSiMqmENk@b9=0K7-CtG1M9AEOibS;APa;)CKAyJ_7sb z66}LR)D`L~dVz=NI*@nM!Y4S5dVU3wgbTt;t~S! z`nzI1f};qgM2Gkz@P9mnmLnm@nl2s_FN){IZlaH1EdEO%d@qCDeoqXA?C+fThsZ-T zo<^`yJStkn_5_>2wjUI)fv=>S*;)+>suOlrXv2UDD6;>#U1a0%+>;LZh z`Dm2PfMe*yGX|h1ia1!W19ITNdU^3suTI6jB6}eDtyrldLENzl9TMXqT<#~;PO2ov zQqC+_Diq0R)M_XVH3Zxe;}tbfxuRxJ#b_+}i1}zN{06Eb_?h^5;Nh&yoZ-HV((2|kMUNE~P7g_Q zj*DLe<_rcWkOQRpz#XKaQs7|be>}CK?Nd)J&Hb{HXW%Qk_@J9qCP&GDJMVvHK-E?L zkJIEh=NWVQSF7pN6-C}NK`RCWrgP$%3X;!uYXx~tj_i-+l7u2pQ3$s3jQCZh$cx1t ztEqF;S=f3PtK(!jaDR9}j?@(Z{`E2$JprcFZN)_n)0D2PCjw+%50C6IC z=RoxcqCtB_4WSa>$KsB)lz?ubbJz@KvnH6-MlZkxm4iA}aD555(x6aKiR*5k;95Xw zC5kiRjy2#{lJF6**-gc=7lHpBKdJ)$GJqcsfpjJK2i;&HzTCl;aIZ@$*c;yi|3a$O zaYUd5{Im$I_zB>t#a}Dn{nodt&##2~vt+dqwVk?39i%=4?>7XkL&e}BcYyc$9sJ2( z;$z9vYydYGD)}QTtml6+X9Vy++m`BTbYB^Q#wb-4)=zr;Pw|ng`|1KYmXm7zK8WeS zhC}V&C)=T#|3~yMkOFl{M_R0>@RG1c;)h*vQqH@)pCB583 z?SqI0Vwvj1+!ej^)Hv=Eq0Sf)AR3tKhn{3} z=)p+!KR@(v@*$v+999-w`5}8504}V=?y7dLKnOYS8D!-Uka~adawTZ)f0UVASbe7B z^|}Sxft8#Y0mcUJmf}vVf;dqQ?CSOf=2mnKEBP+B@ub#WwWLzJlVhX(MC|6^P7oSfgA_Em|$b@$xFE zCCX)ifE^lWPk^ghutGLKo{65w*R$~N)NSf8o&~0x1jeK%sr~Ymjk<^k1n0Zp?<@%9 zcfoT{;JcT)D?`x&MK|GjLfrw_v#3j8lmU_i9Rl+!;B02Wy$|78FO0%>2<%tE$TCO> zj!SqX=y3OUi5AGrc#v{Eu!|#*^gl4oDi* zkU}JZ3zl#J0{9#c-}+ZF_JPdUF4Qv6At@J9!P3?RWkTb?8&sPKK^C+VBJd%wD>Y9u zp#xI(1G;z+GNJvzxFisjP0EFUQOD$5=nQUDT`oikkPSgDL|uVw=qiZg2?)VV-9|eg z&Ts!T7xK&5P+eG-F(?<>2;QpNTu9boC1eB}q)cck@TMXcIuS^JmWY2zBDn=@X$ryu zA;{U#j}V6QT&)2-;% zbQ`)Y-HvWgcc44c>2xQ$Go3+qp)=_$x+~p{?oRihd(zo-FS<9~hwe-Fqx;i2^Z+`S z&ZB+wKza~8n9iq%&_n40x{xlS{d6%sj4q)|=`y;U9!`&-N7AF{(ex|y7hY?1m_SufZ$w$^9c44JdjAQ zG7Kg-pWq<`4<)#O;6j3n2=)_POz<#hjYw9~E;X|4NmBNi3=v+?P-WQ{h9iQ8A=pXo z^)!{LRTr9vL3jV#S=RZAxWIv~cR~w|jr5rp`Tkudm z@KAP?^eibw03>*EQkwSMkW?Utq2Pr<$uGH=j5)tqs>%nYECC)2USOaa z{M_uS{ZF_lH-Ia6UtRST9*GIUUZrmt%>Ft-qNrDu;;K<8@J;suz6BNcB+i)wJg6?# z560}R;FT1xB=@KcYM_l1M7G>;0+6<#v1;mZ@PskwiQvHH|0C|FrDKWcV8yKr5n(u3 z3#7&k3`GT&_aE-WzJNRbLmc#sYk|%5T#}yL^XYb}%8IHq$hE@fXqL*i!A$3?E~!HO zf|aRyu?5yFrH6o^3a^72v&nq|j|F0=(G(YR;!tKPM+g0~kl@}f0$$n-#XFiq}edUi<#56xF^I0T_bKM1}1SHd)oYEEFuBm-=z>L4vQo&ktCF zm7q#T5AFr))Qh)^yn!ntth*3l!3%V{AgfMiC-@81C(_Bd1wdO$J17di*;5O#of3-eKKWjP<^;UiEB z?gnA&O_T%K#Vx6TPc4U3v0osdMtvZA7yxylzEVp8oOFL`C+be^LD`U95R?h6gaZ&f zk3d}@Y(56%k)FsGXds}rf%XB)kQxYRu-rH}6lfcuHd2@@1<5E~90Ae3)GkBRw=ogkb#%cykXzurMu?{> z8Ul+U1P+CFAR)Lj!b^{75cfg{0OIq`;>3#Bd<;b8Gokz?%KeF##3|635Ft!c!z+`I zAsK;aPXmSeCD4dy3E?+HoD{rHxm=FDTfvK$fj&eNxfAh@+<&O-KBxkrwiIZ4D&)>U zcqfUP8Nv)@3YbEsi19PU%rK^eDP_u-a%MO)f*Hw-Vn#EsFk_gp%&W{e2HtjM#xoO` z*O-aS>&zr(GBbsl%1mS4V5T!On3>EhW;Qd2naj*$<}(YJh0G#mF|&kO$}D4+Gb@-k znU%~cW;L^hS<9?r)-xNJx0tt?cbIpXjm&$@CT26Uh1tq%W8P=BGdq}_%r0g(^8vGm z`H{VgUrXwA?7gi33G%w%6!Uv#(d5kV~#UlFejKVnUl;Z<}`DLIm?`5 zzGBWZUo#h&i_AC7x6CDihZ8)4;E@E6B6u{xuMj+j;IRb1O7J*>MS=;z;|ZQX@M{E5 zB=~iLClNfEV35O9f~OJu2Eo$_oKm;ExF2NAP}v4-kBi;ExGDMDSsPKOy)C z!AA-Hl;F<@{+!@r1Rp2(3xZD&{3XFB2|h*eX@buXe3syI1b;>Fd4j(t_yWNf3I2xQ zZwUszeMj(Rg0B#KmEdaxUnlqm!8ZxMMez3o|3L6R+jGq}@S8?HTfhx>xN!F|SE=h|^6xJ2$}&dOcU;$$v?o50oQe$(O7sAPm zO>q9=eK>is8%|t&1ZOQihEo=w!s&_=aIWGsoT&I3PEvdarzU=-AJTs?G!xDkn0O|U zd5LMlbYXfi1DU~0K5YI`u(98O?Yj`pR9u1c6gS{3#Sd_f;x3$_cmU@oeuJ|Uf5N$m zznOpG!~_MWB^Wp<5elay!r_F34o*iz!^sE}oQjBr6A|%n-XRT6F?3B#J?v5T1bdP_&7Nm3u-~y)*lX+!_96RsNJvO%NLWZjh&IF&Vh%|PsT)#1 zq+v+okd`6cL;8pKLPmy644D)%C1hI2ijWN<8$))5oD8`TawX(X6{|9;Oe%{iR%KVE zshX==s@kbKsM1xPRb5nlRsB_?R8v)NsAi~UsphEOSM5;kQhlKMQ1y{&zv`Uoyy}|j zzUpVyud0Wk(V@mrQ>Z00Hq;&(A1Z{_39TDiKeSQuEy-9(+H?yT;k?x)UC z=c*^G=cwnY7pNDhm#g1YuTrm3uTyVRZ&&YB?^YjCf2uyOzM;OQ{z3g?m^w@o77?Zm z(}zWc8N!mnQo?G5dBTLSHeuOegTnH|hK3b}%?O(nHYaRe*n+S{VN1exhn))hF6>I! zwXho+l_o-?)#x=*8iOWAW7b$TDVmy^REy;kNLa;i=)Ca6Y_!c*pQT;bq~& z!$*dX4qqC+Jp9e@RpD#G*M)Bge=mG<_($QNhaV3=5q>iKVfY{6kHQ~^{}cWs0!Jt! zv=RCUdqjMMGr|?oI-+ev`-qMaogy+KG9v~>s%nM;wee6!A&K z(TL9?E=K$o@q5Id5sxAsbBLq35H1SLP7G(}8gVakO}S=V3$7Jx{YNxSwIb$;Dg-89`Y z-3r}G-D=%h-9gH3-a+4{Nq`TB+WRr*c(E&6Tx?fP^2^ZE<=Z}gY+m-ScmH}&7^@9H1v zAM5|oKZ&$Q#z#6MU6DzVDUmfJ+eWsJ>=QX8vLLc3vN&>f>ZH?`X9gUrg8OBUw591(Xv9ZKB#W>A4-8j=Y z+c?)a-?-4Y*tpcV-1w&PfbnDFVdD|wr^e5X$BieAr;WcF9~%EKJ~BQw{$qR+6B-j1 zQzxcwO#PUKF^yxI#H7VEk7*f`5tAL$JEmVuPR!_-2{Dsnw#Mv_IUaK&=48yNm@_eF zW4?;{I_6@`Bv~(@xVa(+8$KroE_<=U9H`%J*~a0eXRYgIaa@Q zq;<5FSSMH~S|?ezShrcXTX$M_TlZM^TK8EGSdUsyT2EWgTQ69@iAAwgtTL8~4T%kn z4T}wrwZ^()lVY31ro}doZ5i7-wry;O*!0-Wv0Y-j#&(YtW5>t77CSL^Qtaf|sj+Xw z&WPO{yEXRx*d4LEVn2xeF!pThSFulQ*ru>4ZH$ezscdSS#`cn}o~?nck?mz$Q(H4z z3tKB&CtH8p09&4IkS*U>CZ*!J4?*}k{kw%xJawcWGb zxBYDU)%MU%*$ws>yTxv^+wBf}E&EIMdiMJEmi8|8EPHo*w!NQyuziT#Z!fWz+ZWgu z*_Yav+uyXWvahvowV$@1wV$_NuzzE}WWN%}#?_2Vjq}9uakb-KimMmbFfK1{VBFxi z{J5cU1#v}j#c?HZqvFQLy%sk;Zf4xEl z?uU3yd_=r9J~BQ!-WYF+x5hV%ZxP=rzD<0)_zv;u@fq>i@%`cl#}9RI4xJ;?5$!NK zOb&}9)?s(VJDd)eBgv8CsO9iDct>r=OOASu298FKmmN(V%^WQptsHF}?HwH*og7^p zS&nXw9*$m)K8}8l97nFh=NRP3cMNqDI*J`7jxxt^$4JL$#~8<}4$(2*@tWgx$7IJ; z#~Y3rj#-X5j(Ls+jzx|oj%AJ&j+Ktpj^RP9&U4IGb=j;bOw2gewWx6K*BkPPm(JKjD{zhY5csJWlx6gk4&gT%1eqigv}gEH0ZX-j(1=a@BC9y1cI1uDY%UuEwsWuI8>*uC}fYu1>BluCA^g zu3oObt{hjMYmjS*tI$>KDs>HajdG1~jdP86O>|9mO>@m~&34UmEp#n$EqASSt#Pe) zz3tlQ+U(lq+Tq&m`p~t{b4L^5haEt)=F%a*gJ7d;;O{$iANJJB;HJ-lERZ5 zNllY-lg1@YOInz;J?WdIN6B=uHrbS%n%p^gX!5M&4as|wPp4>8+$r@_dZiSlj82)J zvL$72%K4O=DZixrTO+iFt46~b={0)SD6TQH#s@V{)%d$+bWL~7_BHcr7S|kKbAHX^ zHE-2?TuWEWUaNksf?A~3^jd3cov3v+RgoHzT06CK>X6j2sjsI_Nu8QHH+4bk!qk?{@ET?{)8Y zA9Npbf8svs{>**M{e}Ca`;7aX`)l___qXov+*jP!+&A2}+&{Q~bl-K~b3btZ>VD|{ z)BTtGZ}-0*9j z)bZ5y)b}*>H1;(0H1o9ZwDPp^wDWZEbn;|)GCf^A-90@$y*zz9{X99IT#wH)$dm6G z>M8X2J;OYuo^sC!&uGsW&#NBMGu|`JGs8Q|JH|WCJKj6dJJ~zUJHtEMJI}k&yTrTP zyVASHyWab@ccXW+cbj*IcenRL?>_HA?_uvz@8{kxyeGY9ykB`Qc)#^t_FnVe^#0(z zze|W?zc!pQ;VSEIymMenuOpenCount ++; + else + { + AssertWarn( mbData->mMenuOpenCount > 0, "Unbalanced menu open/close events in _OnMenuEvent" ); + if( mbData->mMenuOpenCount ) + mbData->mMenuOpenCount --; + } + + OSStatus err = eventNotHandledErr; + PopupMenu *torqueMenu; + if( CountMenuItems( menu ) > 0 ) + { + // I don't know of a way to get the Torque PopupMenu object from a Carbon MenuRef + // other than going through its first menu item + err = GetMenuItemProperty(menu, 1, 'GG2d', 'ownr', sizeof(PopupMenu*), NULL, &torqueMenu); + if( err == noErr && torqueMenu != NULL ) + { + torqueMenu->onMenuSelect(); + } + } + + return err; +} + +//----------------------------------------------------------------------------- + +#pragma mark - +#pragma mark ---- menu command event handler ---- +static bool MacCarbHandleMenuCommand( void* hiCommand, PlatformMenuBarData* mbData ) +{ + HICommand *cmd = (HICommand*)hiCommand; + + if(cmd->commandID != kHICommandTorque) + return false; + + MenuRef menu = cmd->menu.menuRef; + MenuItemIndex item = cmd->menu.menuItemIndex; + + // If this command event came about without a menu open, then it was (probably) + // triggered by a hotkey. As we don't want hotkeys to trigger when they are disabled, + // don't handle the event. + + if( !mbData->mMenuOpenCount ) + { + PlatformWindow* window = PlatformWindowManager::get()->getFocusedWindow(); + if( !window || !window->getAcceleratorsEnabled() ) + return false; + } + + // Run the command handler. + + PopupMenu* torqueMenu; + OSStatus err = GetMenuItemProperty(menu, item, 'GG2d', 'ownr', sizeof(PopupMenu*), NULL, &torqueMenu); + AssertFatal(err == noErr, "Could not resolve the PopupMenu stored on a native menu item"); + + UInt32 command; + err = GetMenuItemRefCon(menu, item, &command); + AssertFatal(err == noErr, "Could not find the tag of a native menu item"); + + if(!torqueMenu->canHandleID(command)) + Con::errorf("menu claims it cannot handle that id. how odd."); + + // un-highlight currently selected menu + HiliteMenu( 0 ); + + return torqueMenu->handleSelect(command,NULL); +} + +//----------------------------------------------------------------------------- + +#pragma mark - +#pragma mark ---- Command Events ---- + +static OSStatus _OnCommandEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void* userData) +{ + PlatformMenuBarData* mbData = ( PlatformMenuBarData* ) userData; + + HICommand commandStruct; + + OSStatus result = eventNotHandledErr; + + GetEventParameter(theEvent, kEventParamDirectObject, typeHICommand, NULL, sizeof(HICommand), NULL, &commandStruct); + + // pass menu command events to a more specific handler. + if(commandStruct.attributes & kHICommandFromMenu) + if(MacCarbHandleMenuCommand(&commandStruct, mbData)) + result = noErr; + + return result; +} + + + +//----------------------------------------------------------------------------- +// MenuBar Methods +//----------------------------------------------------------------------------- + +void MenuBar::createPlatformPopupMenuData() +{ + + mData = new PlatformMenuBarData; + +} + +void MenuBar::deletePlatformPopupMenuData() +{ + + SAFE_DELETE(mData); +} + +//----------------------------------------------------------------------------- + +void MenuBar::attachToCanvas(GuiCanvas *owner, S32 pos) +{ + if(owner == NULL || isAttachedToCanvas()) + return; + + mCanvas = owner; + + + // Add the items + for(S32 i = 0;i < size();++i) + { + PopupMenu *mnu = dynamic_cast(at(i)); + if(mnu == NULL) + { + Con::warnf("MenuBar::attachToMenuBar - Non-PopupMenu object in set"); + continue; + } + + if(mnu->isAttachedToMenuBar()) + mnu->removeFromMenuBar(); + + mnu->attachToMenuBar(owner, pos + i, mnu->getBarTitle()); + } + + // register as listener for menu opening events + static EventTypeSpec menuEventTypes[ 2 ]; + + menuEventTypes[ 0 ].eventClass = kEventClassMenu; + menuEventTypes[ 0 ].eventKind = kEventMenuOpening; + menuEventTypes[ 1 ].eventClass = kEventClassMenu; + menuEventTypes[ 1 ].eventKind = kEventMenuClosed; + + EventHandlerUPP menuEventHandlerUPP; + menuEventHandlerUPP = NewEventHandlerUPP(_OnMenuEvent); + InstallEventHandler(GetApplicationEventTarget(), menuEventHandlerUPP, 2, menuEventTypes, mData, &mData->mMenuEventHandlerRef); + + // register as listener for process command events + static EventTypeSpec comEventTypes[1]; + comEventTypes[0].eventClass = kEventClassCommand; + comEventTypes[0].eventKind = kEventCommandProcess; + + EventHandlerUPP commandEventHandlerUPP; + commandEventHandlerUPP = NewEventHandlerUPP(_OnCommandEvent); + InstallEventHandler(GetApplicationEventTarget(), commandEventHandlerUPP, 1, comEventTypes, mData, &mData->mCommandEventHandlerRef); +} + +//----------------------------------------------------------------------------- + +void MenuBar::removeFromCanvas() +{ + if(mCanvas == NULL || ! isAttachedToCanvas()) + return; + + if(mData->mCommandEventHandlerRef != NULL) + RemoveEventHandler( mData->mCommandEventHandlerRef ); + mData->mCommandEventHandlerRef = NULL; + + if(mData->mMenuEventHandlerRef != NULL) + RemoveEventHandler( mData->mMenuEventHandlerRef ); + mData->mMenuEventHandlerRef = NULL; + + // Add the items + for(S32 i = 0;i < size();++i) + { + PopupMenu *mnu = dynamic_cast(at(i)); + if(mnu == NULL) + { + Con::warnf("MenuBar::removeFromMenuBar - Non-PopupMenu object in set"); + continue; + } + + mnu->removeFromMenuBar(); + } + + mCanvas = NULL; +} + +//----------------------------------------------------------------------------- + +void MenuBar::updateMenuBar(PopupMenu* menu) +{ + if(! isAttachedToCanvas()) + return; + + menu->removeFromMenuBar(); + SimSet::iterator itr = find(begin(), end(), menu); + if(itr == end()) + return; + + // Get the item currently at the position we want to add to + S32 pos = itr - begin(); + S16 posID = 0; + + PopupMenu *nextMenu = NULL; + for(S32 i = pos + 1; i < size(); i++) + { + PopupMenu *testMenu = dynamic_cast(at(i)); + if (testMenu && testMenu->isAttachedToMenuBar()) + { + nextMenu = testMenu; + break; + } + } + + if(nextMenu) + posID = GetMenuID(nextMenu->mData->mMenu); + + menu->attachToMenuBar(mCanvas, posID, menu->mBarTitle); +} \ No newline at end of file diff --git a/platformMac/menus/popupMenu.cpp b/platformMac/menus/popupMenu.cpp new file mode 100644 index 0000000..a3a387b --- /dev/null +++ b/platformMac/menus/popupMenu.cpp @@ -0,0 +1,422 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformMac/platformMacCarb.h" +#include "platform/menus/popupmenu.h" +#include "core/util/safeDelete.h" +#include "gui/core/guiCanvas.h" + +void PopupMenu::createPlatformPopupMenuData() +{ + mData = new PlatformPopupMenuData; +} + +void PopupMenu::deletePlatformPopupMenuData() +{ + SAFE_DELETE(mData); +} + +void PopupMenu::createPlatformMenu() +{ + OSStatus err = CreateNewMenu( mData->tag, kMenuAttrAutoDisable,&(mData->mMenu)); + CFRetain(mData->mMenu); + AssertFatal(err == noErr, "Could not create Carbon MenuRef"); +} + +static int _getModifierMask(const char* accel) +{ + int ret = 0; + if(dStrstr(accel, "ctrl")) + ret |= kMenuControlModifier; + if(dStrstr(accel, "shift")) + ret |= kMenuShiftModifier; + if(dStrstr(accel, "alt")) + ret |= kMenuOptionModifier; + if(!(dStrstr(accel, "cmd") || dStrstr(accel, "command"))) + ret |= kMenuNoCommandModifier; + + return ret; +} + +static void _assignCommandKeys(const char* accel, MenuRef menu, MenuItemIndex item) +{ + if(!(accel && *accel)) + return; + + // get the modifier keys + String _accel = String::ToLower( accel ); + int mods = _getModifierMask(_accel); + + // accel is space or dash delimted. + // the modifier key is either the last token in accel, or the first char in accel. + const char* key = dStrrchr(_accel, ' '); + if(!key) + key = dStrrchr(_accel, '-'); + if(!key) + key = _accel; + else + key++; + + if(dStrlen(key) <= 1) + { + char k = dToupper( key[0] ); + SetMenuItemCommandKey( menu, item, false, k ); + } + else + { + SInt16 glyph = kMenuNullGlyph; + + //*** A lot of these mappings came from a listing at http://developer.apple.com/releasenotes/Carbon/HIToolboxOlderNotes.html + if(!dStricmp(key, "DELETE")) + glyph = kMenuDeleteRightGlyph; + + else if(!dStricmp(key, "HOME")) + glyph = kMenuNorthwestArrowGlyph; + + else if(!dStricmp(key, "END")) + glyph = kMenuSoutheastArrowGlyph; + + else if(!dStricmp(key, "BACKSPACE")) + glyph = kMenuDeleteLeftGlyph; + + else if(!dStricmp(key, "TAB")) + glyph = kMenuTabRightGlyph; + + else if(!dStricmp(key, "RETURN")) + glyph = kMenuReturnGlyph; + + else if(!dStricmp(key, "ENTER")) + glyph = kMenuEnterGlyph; + + else if(!dStricmp(key, "PG UP")) + glyph = kMenuPageUpGlyph; + + else if(!dStricmp(key, "PG DOWN")) + glyph = kMenuPageDownGlyph; + + else if(!dStricmp(key, "ESC")) + glyph = kMenuEscapeGlyph; + + else if(!dStricmp(key, "LEFT")) + glyph = kMenuLeftArrowGlyph; + + else if(!dStricmp(key, "RIGHT")) + glyph = kMenuRightArrowGlyph; + + else if(!dStricmp(key, "UP")) + glyph = kMenuUpArrowGlyph; + + else if(!dStricmp(key, "DOWN")) + glyph = kMenuDownArrowGlyph; + + else if(!dStricmp(key, "SPACE")) + glyph = kMenuSpaceGlyph; + + else if(!dStricmp(key, "F1")) + glyph = kMenuF1Glyph; + + else if(!dStricmp(key, "F2")) + glyph = kMenuF2Glyph; + + else if(!dStricmp(key, "F3")) + glyph = kMenuF3Glyph; + + else if(!dStricmp(key, "F4")) + glyph = kMenuF4Glyph; + + else if(!dStricmp(key, "F5")) + glyph = kMenuF5Glyph; + + else if(!dStricmp(key, "F6")) + glyph = kMenuF6Glyph; + + else if(!dStricmp(key, "F7")) + glyph = kMenuF7Glyph; + + else if(!dStricmp(key, "F8")) + glyph = kMenuF8Glyph; + + else if(!dStricmp(key, "F9")) + glyph = kMenuF9Glyph; + + else if(!dStricmp(key, "F10")) + glyph = kMenuF10Glyph; + + else if(!dStricmp(key, "F11")) + glyph = kMenuF11Glyph; + + else if(!dStricmp(key, "F12")) + glyph = kMenuF12Glyph; + + else if(!dStricmp(key, "F13")) + glyph = kMenuF13Glyph; + + else if(!dStricmp(key, "F14")) + glyph = kMenuF14Glyph; + + else if(!dStricmp(key, "F15")) + glyph = kMenuF15Glyph; + + SetMenuItemKeyGlyph(menu, item, glyph); + } + + SetMenuItemModifiers(menu, item, mods); +} + + +S32 PopupMenu::insertItem(S32 pos, const char *title, const char* accel) +{ + MenuItemIndex item; + CFStringRef cftitle; + MenuItemAttributes attr = 0; + + bool needRelease = false; + if(title && *title) + { + cftitle = CFStringCreateWithCString(NULL,title,kCFStringEncodingUTF8); + needRelease = true; + } + else + { + cftitle = CFSTR("-"); + attr = kMenuItemAttrSeparator; + } + + InsertMenuItemTextWithCFString(mData->mMenu, cftitle, pos, attr, kHICommandTorque + 1); + if( needRelease ) + CFRelease( cftitle ); + + // ensure that we have the correct index for the new menu item + MenuRef outref; + GetIndMenuItemWithCommandID(mData->mMenu, kHICommandTorque+1, 1, &outref, &item); + SetMenuItemCommandID(mData->mMenu, item, kHICommandTorque); + + // save a ref to the PopupMenu that owns this item. + PopupMenu* thisMenu = this; + SetMenuItemProperty(mData->mMenu, item, 'GG2d', 'ownr', sizeof(PopupMenu*), &thisMenu); + + // construct the accelerator keys + _assignCommandKeys(accel, mData->mMenu, item); + + S32 tag = PlatformPopupMenuData::getTag(); + SetMenuItemRefCon(mData->mMenu, item, tag); + + return tag; +} + +S32 PopupMenu::insertSubMenu(S32 pos, const char *title, PopupMenu *submenu) +{ + for(S32 i = 0;i < mSubmenus->size();i++) + { + if(submenu == (*mSubmenus)[i]) + { + Con::errorf("PopupMenu::insertSubMenu - Attempting to add submenu twice"); + return -1; + } + } + + CFStringRef cftitle = CFStringCreateWithCString(NULL,title,kCFStringEncodingUTF8); + InsertMenuItemTextWithCFString(mData->mMenu, cftitle, pos, 0, kHICommandTorque + 1); + CFRelease( cftitle ); + + // ensure that we have the correct index for the new menu item + MenuRef outref; + MenuItemIndex item; + GetIndMenuItemWithCommandID(mData->mMenu, kHICommandTorque+1, 1, &outref, &item); + SetMenuItemCommandID(mData->mMenu, item, 0); + + S32 tag = PlatformPopupMenuData::getTag(); + SetMenuItemRefCon( mData->mMenu, item, tag); + + // store a pointer to the PopupMenu this item represents. See PopupMenu::removeItem() + SetMenuItemProperty(mData->mMenu, item, 'GG2d', 'subm', sizeof(PopupMenu*), submenu); + + SetMenuItemHierarchicalMenu( mData->mMenu, item, submenu->mData->mMenu); + mSubmenus->addObject(submenu); + + return tag; +} + +void PopupMenu::removeItem(S32 itemPos) +{ + PopupMenu* submenu; + itemPos++; // adjust torque -> mac menu index + + OSStatus err = GetMenuItemProperty(mData->mMenu, itemPos, 'GG2d', 'subm', sizeof(PopupMenu*),NULL,&submenu); + if(err == noErr) + mSubmenus->removeObject(submenu); + + // deleting the item decrements the ref count on the mac submenu. + DeleteMenuItem(mData->mMenu, itemPos); +} + +////////////////////////////////////////////////////////////////////////// + +void PopupMenu::enableItem(S32 pos, bool enable) +{ + pos++; // adjust torque -> mac menu index. + if(enable) + EnableMenuItem(mData->mMenu, pos); + else + DisableMenuItem(mData->mMenu, pos); +} + +void PopupMenu::checkItem(S32 pos, bool checked) +{ + pos++; + CheckMenuItem(mData->mMenu, pos, checked); +} + +void PopupMenu::checkRadioItem(S32 firstPos, S32 lastPos, S32 checkPos) +{ + // uncheck items + for(int i = firstPos; i <= lastPos; i++) + checkItem( i, false); + + // check the selected item + checkItem( checkPos, true); +} + +bool PopupMenu::isItemChecked(S32 pos) +{ + CharParameter mark; + GetItemMark(mData->mMenu, pos, &mark); + return (mark == checkMark); +} + +////////////////////////////////////////////////////////////////////////// + +// this method really isn't necessary for the mac implementation +bool PopupMenu::canHandleID(U32 iD) +{ + for(S32 i = 0;i < mSubmenus->size();i++) + { + PopupMenu *subM = dynamic_cast((*mSubmenus)[i]); + if(subM == NULL) + continue; + + if(subM->canHandleID(iD)) + return true; + } + + UInt32 refcon; + U32 nItems = CountMenuItems(mData->mMenu); + for(int i = 1; i <= nItems; i++) + { + GetMenuItemRefCon(mData->mMenu, i, &refcon); + if(refcon == iD) + return true; + } + + return false; +} + +bool PopupMenu::handleSelect(U32 command, const char *text /* = NULL */) +{ + // [tom, 8/20/2006] Pass off to a sub menu if it's for them + for(S32 i = 0;i < mSubmenus->size();i++) + { + PopupMenu *subM = dynamic_cast((*mSubmenus)[i]); + if(subM == NULL) + continue; + + if(subM->canHandleID(command)) + { + return subM->handleSelect(command, text); + } + } + + // ensure that this menu actually has an item with the specificed command / refcon. + // this is not strictly necessary, we're just doing it here to keep the behavior + // in line with the windows implementation. + UInt32 refcon; + U32 nItems = CountMenuItems(mData->mMenu); + S32 pos = -1; + for(int i = 1; i <= nItems; i++) + { + GetMenuItemRefCon(mData->mMenu, i, &refcon); + if(refcon == command) + pos = i; + } + if(pos == -1) + { + Con::errorf("PopupMenu::handleSelect - Could not find menu item position for ID %d ... this shouldn't happen!", command); + return false; + } + + char textbuf[1024]; + if(!text) + { + CFStringRef cfstr; + CopyMenuItemTextAsCFString(mData->mMenu, pos, &cfstr); + CFStringGetCString(cfstr,textbuf,sizeof(textbuf) - 1,kCFStringEncodingUTF8); + CFRelease( cfstr ); + text = textbuf; + } + + // [tom, 8/20/2006] Wasn't handled by a submenu, pass off to script + return dAtob(Con::executef(this, "onSelectItem", Con::getIntArg(pos - 1), text ? text : "")); +} + +////////////////////////////////////////////////////////////////////////// + +void PopupMenu::showPopup(GuiCanvas* canvas, S32 x /* = -1 */, S32 y /* = -1 */) +{ + if(x < 0 || y < 0) + { + Point2I p = canvas->getCursorPos(); + x = p.x; + y = p.y; + } + + PopUpMenuSelect(mData->mMenu, y, x, 0); +} + +////////////////////////////////////////////////////////////////////////// + +void PopupMenu::attachToMenuBar(GuiCanvas* canvas, S32 pos, const char *title) +{ + CFStringRef cftitle = CFStringCreateWithCString(NULL,title,kCFStringEncodingUTF8); + SetMenuTitleWithCFString(mData->mMenu, cftitle); + CFRelease( cftitle ); + InsertMenu(mData->mMenu, pos); + + onAttachToMenuBar(canvas, pos, title); +} + +void PopupMenu::removeFromMenuBar() +{ + DeleteMenu(mData->tag); + + onRemoveFromMenuBar(mCanvas); +} + +U32 PopupMenu::getItemCount() +{ + return CountMenuItems( mData->mMenu ); +} + +bool PopupMenu::setItem(S32 pos, const char *title, const char *accelerator) +{ + //TODO: update accelerator? + + pos += 1; // Torque to mac index + + CFStringRef cftitle = CFStringCreateWithCString( NULL, title, kCFStringEncodingUTF8 ); + SetMenuItemTextWithCFString( mData->mMenu, pos, cftitle ); + CFRelease( cftitle ); + + return true; +} + +S32 PopupMenu::getPosOnMenuBar() +{ + return -1; +} + +void PopupMenu::attachToMenuBar(GuiCanvas *owner, S32 pos) +{ + +} diff --git a/platformMac/platformMacCarb.h b/platformMac/platformMacCarb.h new file mode 100644 index 0000000..347877f --- /dev/null +++ b/platformMac/platformMacCarb.h @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMMACCARB_H_ +#define _PLATFORMMACCARB_H_ + +/// NOTE: Placing system headers before Torque's platform.h will work around the Torque-Redefines-New problems. +#include +#include +#include "platform/platform.h" +#include "math/mMath.h" + +#include "gfx/gl/ggl/ggl.h" +#define __gl_h_ +#include + +class MacCarbPlatState +{ +public: + GDHandle hDisplay; + CGDirectDisplayID cgDisplay; + + bool captureDisplay; + bool fadeWindows; + + WindowPtr appWindow; + char appWindowTitle[256]; + WindowGroupRef torqueWindowGroup; + + bool quit; + + AGLContext ctx; + bool ctxNeedsUpdate; + + S32 desktopBitsPixel; + S32 desktopWidth; + S32 desktopHeight; + U32 currentTime; + bool isFullScreen; + + U32 osVersion; + + TSMDocumentID tsmDoc; + bool tsmActive; + + U32 firstThreadId; + + void* alertSemaphore; + S32 alertHit; + DialogRef alertDlg; + EventQueueRef mainEventQueue; + + MRandomLCG platRandom; + + bool mouseLocked; + bool backgrounded; + + U32 sleepTicks; + + Point2I windowSize; + + U32 appReturn; + + U32 argc; + char** argv; + + U32 lastTimeTick; + + MacCarbPlatState(); +}; + +/// Global singleton that encapsulates a lot of mac platform state & globals. +extern MacCarbPlatState platState; + +/// @name Misc Mac Plat Functions +/// Functions that are used by multiple files in the mac plat, but too trivial +/// to require their own header file. +/// @{ +/// Fills gGLState with info about this gl renderer's capabilities. +void getGLCapabilities(void); + +/// Creates a new mac window, of a particular size, centered on the screen. +/// If a fullScreen window is requested, then the window is created without +/// decoration, in front of all other normal windows AND BEHIND asian text input methods. +/// This path to a fullScreen window allows asian text input methods to work +/// in full screen mode, because it avoids capturing the display. +WindowPtr MacCarbCreateOpenGLWindow( GDHandle hDevice, U32 width, U32 height, bool fullScreen ); + +/// Asnychronously fade a window into existence, and set menu bar visibility. +/// The fading can be turned off via the preference $pref::mac::fadeWindows. +/// It also sends itself to the main thread if it is called on any other thread. +void MacCarbFadeInWindow( WindowPtr window ); + +/// Asnychronously fade a window out of existence. The window will be destroyed +/// when the fade is complete. +/// The fading can be turned off via the preference $pref::mac::fadeWindows. +/// It also sends itself to the main thread if it is called on any other thread. +void MacCarbFadeAndReleaseWindow( WindowPtr window ); + +/// Translates a Mac keycode to a Torque keycode +U8 TranslateOSKeyCode(U8 vcode); +/// @} + +/// @name Misc Mac Plat constants +/// @{ + +/// earlier versions of OSX don't have these convinience macros, so manually stick them here. +#ifndef IntToFixed +#define IntToFixed(a) ((Fixed)(a) <<16) +#define FixedToInt(a) ((short)(((Fixed)(a) + fixed1/2) >> 16)) +#endif + +/// window level constants +const U32 kTAlertWindowLevel = CGShieldingWindowLevel() - 1; +const U32 kTUtilityWindowLevel = CGShieldingWindowLevel() - 2; +const U32 kTFullscreenWindowLevel = CGShieldingWindowLevel() - 3; + +/// mouse wheel sensitivity factor +const S32 kTMouseWheelMagnificationFactor = 25; + +/// Torque Menu Command ID +const U32 kHICommandTorque = 'TORQ'; + +/// @} + +//----------------------------------------------------------------------------- +// Platform Menu Data +//----------------------------------------------------------------------------- +class PlatformPopupMenuData + { + public: + // We assign each new menu item an arbitrary integer tag. + static S32 getTag() + { + static S32 lastTag = 'TORQ'; + return ++lastTag; + } + + MenuRef mMenu; + S32 tag; + PlatformPopupMenuData() + { + mMenu = NULL; + tag = getTag(); + } + + ~PlatformPopupMenuData() + { + if(mMenu) + CFRelease(mMenu); + mMenu = NULL; + } +}; + +#endif //_PLATFORMMACCARB_H_ + diff --git a/platformMac/t2d.icns b/platformMac/t2d.icns new file mode 100644 index 0000000000000000000000000000000000000000..400aa0baee99ffdaacf5c49f952921c003ce6537 GIT binary patch literal 32047 zcmeHv2Yggj+V{CLX(Yjbh#;tlv8^tOfQVQ?Sq--Jvtq%v=qelAt|+<|rk5lTYG?sM z5;{mppUDIS0YRjQk_IFYdJRTG2qDk=KX>NdxpQYy#k{}o`#kfTd(V0Lc}_1gBS(Dj zF=Jod@#v7hM>8gb(f@K_JZN={h2k5v#`7&l8sDf&#s<|eM((dr{~2R-_c8V*zIB7v zFe3gf7o=|;as*PX=gUHYE;~n5H<}{5-W?hldM-4w2fkN_@;62Hp!$*T_3gu0P<;Xk zgC3qi+W9prJ4~hEu0Ifc6El@UmeQFYms-N7P-swLsUypkd_SULx!HLR*9vPAh2Ff< z>2SDa+%}ooUAA;bJ_nEOHi06-Qi|(oTeidDoI7DWwV%A1=(nGpc}_=uR$Rw%bXm94 zL2sPhK}S&*2;h$el}1QjXr*ji}3}s_LXf3K>kbvyJu$ z9fiTZ^bXYiS|N@?X3@!U77;pusK~M5;I{U06uzGJ7;Sd*lqI&UCSn8Z6xO+te$?CT zdZ(4x8l-0vt=8MEUw><)po=xXp4erlP7b2w2HVu6wZ9#$DBiLTV+62<}JVD2F_MqEH6C_Na&@`f52D|-6_-D<@Q>(u=>N_Wrm4(yo zV|MJ=zI})JR+`9!UdK!%lxQZf2;pNC-&0RLwvni_jp;~_ujaW;-{~kw`x?kukWN0x>!BZ zBR-JqhQkjWt2%Cq2ys1Jek?I@AO~Kur*sAfP4)0xA_EOwi-a zrf@8e35anPCL{r8;E>52XJ9&b0}(R^jR(%gBjb=iyi9noaRBO=Nzii1<4DV(LWdL) zz)VOa4FGyUhioE-XPgYtvmntRH^d}Zm|esfpoIy(aX1gOf{_^o8?@_1fE(SAi4qp? z046H1t-(v|DBH&5bH$AjG>msEknsL8+<4`a(|Usv!{y)hZ&iGbIK(yh{?*!$u%Jb2!ol$S(kt5#4Y)~^)hWr@`=mC%C`S6Xvr>uvbzvKF5gWj420F3Q^_UnlUw0k53&W)mPp}q}5ekT^WUm`F;G^qmDnv zh)=5I@k+*@oE}ebx}T^={V<+j#F0b$?Q&EZX`gYwGAYM*_I*H(DKF_C%vVK9KuX6#!4`o2CLH8O6bF|M?kCj08Yq^R*nDL9kXwbtjHkbp!Hj2Xu_Buu40nH8 zfVu-SA>PX_#Q;~ApoH>!b1PoQiY%%YN(NT)uAT?!O~@KY$Z0p4cuDe5DWyGcNEyUE zm!g6@`u2tDG%sB2Snyc9oDrBn&rRb`bU27bxxEbe+#g=J8{8KVAGlaUd39FF0X)qk z*=>yycGb!LH^QXiwdI)OLo8Nj|nJ$a5O?;Z0X>@3jCxL7e*HqmlT(l`e8&{)Kcn; zqKiRZQc{|sMu};)l(M2?cZe+wkT-w-5M+y=*iy0J)k@Uz&z6^X6a>zJnxC;)kA{-n zW&TJ%8#9Uvr5u#TmicM-l`x{LgnPfpqXAj=3rY#)@I5|~{NmWsazFhF0kMUm@EFNM zC4q9_m0!kXM}yiMV)r&8zZlff(Z#zSm>A9xTe>?owv1RA*Ao*< zGTx&}wx2Reig?A`UAkK=*fLb>=;Cr2?};e*%VXf1-KC|d2!NIsL0hq3EmiQBaq~g$ zt~j7aN$h4+URI*eqL5!Awkyz!Fe-ukLgWE@V!c!f`K1uZ#d8Ja>`LIElJ0_SSItzkA=J#k$QJ2_l@!s<1e4My)j%q zzOv}T!lEJ+WB~c)TwV#6UlNNhh?9Y?qy(v0MO&5)+r^ z5+0H5M{O=FNHV?qE<#H z@{zaEP|n8|`BPM`lJCcxfkG(Uvu7_pym@6bA%Dak$S!vKY!CG8^^-5UL{uZj%J=Tg zD2LVMCcXQm!EEgJ%*j>Z~nQ6f@WIIrB7c#!Cw~;Ez!>>A*CFf?n{i* z(kX0dEm7_MbkjVJZ8$av1WYSLiYi>0* z?{{W{#`wJ=$2V-nMT45HF^r$ zwg87NPFH@u&Y^Km8ZeEnSV70n6XhA^7Zl{p{o6v9BhR_aHkGby|I>D74beQKbx!hM z1nRmx*YVx<=|M>pp&7L3CrnevTkY0%dJ5@Z^xH*R)omIDcP}FdtmrS5lnfCYdf%5)T3gh+=w=3h0D*g{Y(HtOsK5;UuTI{R(G&SSNdJduK@5S^c5 z(c6PNQTrZuueFk}7N-IsK_x^d<7_xTvBl*bsj-uAu{XfcFm|66V2yR`$pm66aDb44 zBHC&8!geH*5VQ>%zc=XYT8k;6(n#&<>wY_NXwNU3zsmu(^BEm=$}-q(@$+X*KXBsE zfwEm&zs*~|Xr6A4FmqBOUDcZq__4Vy%4j*i-&O0e4P zHce=^+i$vRGs8!{H({%q zy<=b@A2Nz~=Z)`RwH?YUg)9gi*vZSl*| zXp2)`X2N8&#m{hZA`C@a6tJs=V58u2_t8=+KYucD z7!#vL9QR-~$qJZQQ~SZ+_wb__6HH*9YC4YDH5&A`vsgUNsqoTkjQApoTKP0i#85cs z!F3!W)e3lmR~?Z`Ssx+Q#_4$7lqw?Q@dOBdX+m4QhrM59e0!uZ+R=zt$IU1NV-p8Er*vAQI z&cA-YIU`5B&+Ehh-$|!8C9DzFXxBsy4*4jwGNkvQy50vm9q4pJryB5KcaDAUp^T39 zL;&LWmFVo0*ELn7xiOtjXRD`u zecc&ckNH1)45Y1@X?&p)(_~W(uG`=m&(~c~k9F zSLQff$!5F#UF{(fu0Ls}$o+Y%9JqG0D$Z&*-wfd33()`DrWHAut_O>>FCQZIA6R&y zID3`J5#q`Osp;ASm}RExDd@zS<+)C?Be;Nzed+*(wa2`bxfZ+W`U=uS#TSeNdv=1&{xU=Q z%A|Y|ZMGY(0(->cE3-Iz4)BkcQ)tNjOE1vRE1Y8;p#^DX`{;o~!%A_{=Z;xejcDsP zuXH9jLi2Ott;Y3-dzFwTT7MF~L+#oO4tJ%PO(`edWQC;Z{^=eI3fGy5tc5zW$68Fg zQoH{`nk!z}&S$cY+ypo#)nZsy@W@WmM!vYAKAwc1S3-z0XRJAK*(dF`lQwurR$UzK zT^(GXojcYMQZUzSn;3uBR+!T-r3Q%FJ*zXa5=9mZ&)pW%{3X5`h_i)xD|04-BON|` zuia);sZS2aQSh0cvRCFhCpki#d05owyKEvMWDelJZq8bj=S+gv=7Hf(fNwq;M_R`u zr)wH~lQ-UCe_|7{SK}#Yk#4%f74Kju#Gr!tX8W7pL&iefFsq(!HciSZuwn`4%6H`3 z9Kp`K36@cj8*u~c5PH#i%(#ywqJoWGzS`&rbGix~Q-?pf!ew&=tzKX;Ujw?~Xb_r{ z8xNbPOPLjQ)?#0{+KENn>iqN%?zx=kkvtc~=Q|Ruy*E%;$Rescv?=?WQ@Aa*1rI5xYMvyXUbEB63(Vb0f`fO_yel%W?f+u19)vDSVo)->N3ZpSm? z`s*ARv_3SG=5(S3_@{x7Z~*h2VTcjk)YW$l*Dov249~-=ClgeAEjCa+462a%Hi|6M z52KJyeV+Mb$*$inVEC)P1BBNcg(ce!7CXBebehTNi_T0Q_hIr6r;tbXF(@9r2vMu` zaS(OySYk%#yZGyQSnX<`OYAE%X)iA3SdISaj`76)h4shTJ~<>r=78_)R9scmd@(o# z?JBh69aCnI@L!fq!cE73JQi<>vkULvf>ruuw+{V$X<;>%F&jDp{bD`PB_>>8VS-#ii$HJK!I`2oO89S2%C@RzOt_OFJJ3+V<9 zwN?On;jv6&@1W>@onjJ!2j-J7S-Xma;50xF*sL6CI|yibcR;)T4CoGn4N$B7@B(5_ z0(k#9sB-DX0v2~P4SoFibJep~qFhE{2Vj{SeryJ{zgT;&@>oUb&L7qmxLipf2*un- zb3WN32Co?l6Gdnl8;8W1|EC>DZ1dG)kmNedK zvDmP2V5KhgwP(*9uPENS`TKRQoXl0LGPARD9r-Sot{~Wz?{wr11M5?~e(pA+^n2Ru zEAv;RC5<)3pP>siRmTtP*|lxMx2qjsg(}=f&DXg)03@)Z@)M6l`Z(d`3aOD?fX|?C-zN&&@=0%z{pXv!f#~`p@aI#cZ{AdKc5L&};5`;q$3W zm!v08j{Ew(XSCZj)hCBAyel(Q@Xp8kJ2A&>*boaert)7IgnqgcM?&r<>pcRq? zYw!JNAvQFAz0EF|Zrq15&Oy?pLsHir$?H#mxPA#of7x*#vB#jPc7_Sb|F+vlq1Kw3 z3l}fe*JDn58e@|^#A-2p{ly!>`>0#*A&))x+PkCljok#D^4W6v7SuFBv>Cx#JxW+~p#dJEK znU1cRg^is^1IiWtcOlDt5 zCH9h@7Gipq&L_8{EEIZrk>R~JhrRHmAtrd}b1(kWR)Rj{eFcpjolVWt$x|JkgD%vFy%K*Zp2QU#UAP+Jb$eq{)V5vabIC28nC1e_qB^=2)zvRe?__{fT z;j@iXH8a1!&0JvsdOG&05z527X5VcTwM|gRXvob@ud0dqBF_FvJJ3i5-#9!i?GM>;9BQj=^}eBdr^ukmzSujfFr3vqGYe3! zlZAV0umCBVEti)txE5sPBc{HkdHc0UoeXC~v1L_roeFU3S{>@rScpVPbBcg~`6wlk z;!zL9Jormysp`RN=XepI>A(NjoFkfJ#aHVnO4l z1XSRHy=WPact|)C!#P3{-g;I_)7=y$Gy@SGX0zDV;^zKBDTpw(6ZZ34$0Be$TxkGs zD~d$~Y!R_j+=Dh=Zl<2b7hZ<{X<)3jzSJ%O;8x&{lNJAxiPWjF6HCMjHeqxNX5x3E>4S zrN?5wJ6ELEVQ!a<1*DC|p+qbwh-}0Ycn)~a%M)XTMJx-(CkhK$ru??C@nV|47qL}< zyWeP%V%q&)2{=zH-*(70d00F{AzLqiJ&6bq{7hPUWPE z5e%g`o&UA~=I{|s9Ko0{i$fG67)wFJQjB0sNZ}OnaD_ol4dUA|so+wKPWah^4x8wx z;TB(nz<=<-#}Mq$doZ4n$&JC_!w~`>02ITPKhoQyat6rsaKZ{@7Yz>pr&Q?0&T)4? zZVSC&{LlkUDqghC3!Mm)(a@9J&hWx9Iw70McqqpqaR=VxTJM-8W_vaSt41f5ES8IE zbb>Cv=Z_-vYQva=pRj1GXz{9{$n6EdRf7<=-?+)DL5M?{pcsVQ0CYj#@dsrJ#sC0$ z{E;a90P^@FQOM(u1dzrbT&Y7A{~UY%9D7g={v3Ne@WQ;~g%>6*1^9HDPneZM#~eOURP?-4 zF#k}dC(<0B^mxokd`AGDU1i@tSJmyOV9n|6*~dYiZ%;o<)x}Rxb?r7)r*385{(PRI z-&V(^-kQUeajDbhaH+rMajC23ape+Wl=#EA!C`!BzjdMOEO7;%7u-7b-^(@RNB0Im z&3(UDQfv6bbicXP6E+#zKHR=a(vC|uz1m#=@Pl6U{7A2D!^K-iXm`e{lV>F;(bu>Rw1)CWBgSUvIGrM3vhhV+-UO&^rgkbe50Ewax( z#J$qMVSC&3LHfYjaXaXVHgSNkuptAo+eAN$`nCZB&OO*R23+$?0|qR{BDgKWZHEIJ zP<2OJXlGacrvd%gH2vd4`w>3}&OM;}hJW1QkF`MitaPWV0V7`cL-pUg@?ryeCGa0v z2m7SUYaU6R-@CH^ALW4S6HfSP3jm1wWAt}5_$+w>DE5GM{}JP0#QXe?U-LpkKg?~t zUo^PG)BLdLQul%n=QdwCC^vU(eNF>#_Da9dBK7p^Z2_dT8gy9$^W8>l8s2JU0T_n{ zTJAKW94H*GUb+z1xUN9hOF0i|zcrAC#$hF0FJ1Nr99{42+k=xX=z3E@qjp`$IHJTY-pctEYiL^8JsM@!(rtOCQ^1f#T zeoUwgTP|4-sJfW#RZ{hUGLKz1dNGI{xk`!rJ@Kn8M|~vAiHci%#NJvgV-PtX_>XUV zIGUS?o`_#Y_=h6Za{C=0lK(VUdUNP*n~XmJQHQNp__99hLx4k$OBpu!2p1FIh5IUa z$Y)1%=OtZ=_70H$990$nt4}|5_6A?;eqZWc|LoHel}OdLjB*x!MgM^+;+7=^_c4dB z5z|yME-&-Jk8z_r-|>QJ*^Zh$omJE=N?sml>ZjgvkMNzLeZwMee&(~(pYea9JUrJT zg(~8^1I+;5Q4s|@eDZ%}#`X&_xyyJL-_9=Mk-*2UMruD7Z3Yjg6I_1ei3k~UY zq(yofB0kyLt)W*NlGsA{CoRmkL_Vp7dKw~M)4GFhRlnMh^rqp}{Qt*V9(pxv&!cJe zM59}-92{>qM|87jer%0?A@FHavlOa`_P0j=Wex8&s>C;$bhJkQeN|1$Z7#v2*69DT zxzYpDU93KR@RzkK=O%pe+LI4;YlVKT=ilxEN^b~w;mqF6xr=7lM!o*TJvVg>YK2;v z1;4OtW~kf$%f{@~iMo$of3(llmxuo@t+IL+)45|SIzpU@`iBRv z|8k4uw3axfA^qoKS}U?8d{K)3FYvYGOzY-SEm02I2O8*niH@q)ek~N-O!AL?Gp$G+ zytF4A4cL>X&?rai>&~4#l%?8yYCyp61V8t|Xnn%e)a3QDCn*E}$G#`LB`vUI!X=I0G*xtn*D9rX?Sihp#akCqqNk0m!Ca@y3S zDHF$4F7HBUbsxwj?ywQHN9zfkNOo2j3EMo-@C z{NLW3nsTvU3-V9&VGHyRYex2o#JNdx^G{oX-G52iq-YmopU6a&i;eI9V8ueyz;EM4Laix0_4ep%oJFZt#R{$pEDIR6ayu}_GW`99}xS%2NU z@sku`a?-Tf3)dW%1mXRZj~K=>WZYLO44W#U4g?r~GbNnm8T!Mz-#O&D5&zaro}aVf=Y6LY=R92Klg-hO zKPvGAv}>Qxl>WOuS-kU9jY_8^4Da8rcto+}Qm>PIr z-1d@3R#X1h%J)hZ`e<*w{1v~sQdv-6`)f&-IVM69)|7s4`%95nJnu&GAMUdtzHoYf z;SZ}b(kB~6y*=!)dwNDHJEf-d-}sx9fho<`-&4McQ91tAA$Q(*^%d) zN$U9@h4_R<^4~`?`uwv#ECCYU{9hjc%N~+PYl>Cj8_|E?d5_h_Djxr&tpEH=1MX3T zf%iV}$n&GV!CzoY;b|3fBl;hdHYE1F^>%~dpRe;7xU8Sf{dQIns;gC9XHEa+>wi?T zxzYD|zB%I6-zY8qqw=y#t^ah7mgraC<7jOBZC?Icq`&q~RrcFh|Ceg@jp=`^iL!K~ zTatgPpL+b|j1-J&Wb^zlP5_#7ZdJ22&_&q=r#uV$Q=XyBYsK9a$9)(5fl|jyt^vn~ dl9WIsfbCv8ck}~)z=4g|*gs=`dZ1Yz_#dqqLSFy? literal 0 HcmV?d00001 diff --git a/platformMac/torqueDemo.icns b/platformMac/torqueDemo.icns new file mode 100644 index 0000000000000000000000000000000000000000..6695aaf34a77592884892d2075ddac3c6f4a50fa GIT binary patch literal 39457 zcmch9d30M>x$i!MX3Lg5O1350ZIcd(?RbbCk~o$%+GQ$G?rnk2l+reX9SXES+kK8C zkD2Gxa?8+`vCx5*Lg8__?_P!rrBI-;JLSIh)?06_d)ND?@AvH^c}VOebz&?X z>FAujzv1_ddrQfK_ig?HV^38+_(**nW1n&}{3Y1lD2r)0lF`D@`fy8Y%ZAqG;JQ$A zFt{$bzIFZQz8lMp#=@~^Bo>WF@g9rzv7V?Ji$-IyUC|=U*`gE=)O&nNEFZRh=bmu_&_f_QcNGk6{s~!EDq&ZY@d+6 z1oCWXg<$K0MDjyFd3WmE)Wq(Aj|@gxBpJ=QU}9EjdM89v(NZYgIs>ws_mlIBV;PUudU( z5b295F^I~ed09Hm!B77F9H{71%D#d0!-IUK=RK{yh@a~8_Nhx$Mx0Y4(Y=#JVl z!hP`7cT*sB?%hM_^x%En(5pYLkdnP|+l&*Enfjs}{$~;Xx8Z?dKE=;zQ}Vf~zw-e( z-5-INkzG+ogj_a4-Vy->eM|>eg0>3>!OWvJGIQuDV0tb!Wj%N9?}O=qfx+9K4_j%D za5I{iz+mH$V&Vidg>^%p{LQ&@d`bnP1gZHyF+V+^^N7iCHb4P~lkq_puRReP^q)m} z8z^rV@ex}0#5nnfAaxFq{{5LWh^0TV3%;^DZWYtW8YIxJj0QbX`R142O-v~?PZM&S z+t1U=K>BOZ=VBJ(l%1jmTUvratwjz7TX>MSD8ZIsix$*dOgM=jIk$aEefRfY>lox| z+rU#d!+7-UkEmq8a2HI)9BO`y>ET+{;g z(;%wr)~;K(wrd?*%f(SZAJ?rFuj~4S+EdV5Jva64FTSL8S{~j#04xu*Jr^~T3&Mp+ zbJm<%&zk#ibgg%+eVK4Ko*W>I^l#BRtsR|TA~U8x|9r?a!~W?F%DTUjkr?^@4?8+L zc&ERkvvc!KVzY}f8V-6GPkQGuhjGcc$e~blh9V1Ttx71=+S(OjttlLONct$OK!BLw z#cydHdZ*L@KISh9f$n-fW)X0JXdipJ)ztAWWQRD`iDx!-c0!nrL}&h{og{o8#$-i- z;uOZwL=4`9qbH_e|=Q%bU`cT@=$Q}6!fE4)L- zPz_ocheH6e1yZEbo1c$K#_%3wISog*$aZS*Ur!$ZGZ|uQU}MmL>ApY_y}#{1Oom`d zps&T$jF9P8Ir7R@4n7GSoFpWT2~7KzK8oJ^DWU2iTR22nmR>a^T%2(vSYmI+B}3zZ zcD1eje)3Azk?b@J^U){X!EjhQw~`C(2t5~uZ3ykIHYwi5+vT=)D0?j06%1 zBSXW~v~XJ5ni53uVwh5k>Bv?*t_zbYDBN)ov?v-#m{bkdBCl+3W4G{*9YRq=>y}=4 z$Kb8cg=HkGKGq%0rCwxRxV1SJY>9wGSDPvRNw}O)%1p+@gG|diEggV);PGfwhr9M5 zH&cA@m`b4|&ij~*I7&wIiRRjHusPPUuBD|p9Er8JCEAs?&vXK9*3sAL7S7$dr5p5~ z`Y3?~rxaeQ&GJ&H6oYp3CPbyS2^|8FT^|fKw+5SALZO%r+uWNnJhlhaHG+)%=u0R& zXdhUNN>P6l)g%Qn^g)XTvZUfI>s!!$1<_G8w}yhzI86Ki9GX0XQ$k4r2o1K_(#;1< zhuWf%)(G`my*T%Z0mx-^&!Yc1WCwXB>s@d5Co-xr*K~iFo>JBJRFM! z(Jo~&58jNbX%xXemW*Bld{I1MreHG!hNz$~1cv16H{3k%#%9Q95pJBZmfmR+mP_#g zbx&JtLxl7uFpQ3ZN-SBw0^NWhD^pJp+z&Q)bP)&?XDMWtEx>i~zEE>K zQ<$l&%Lu7{YEvO3G9Fmy(Ngk0Lt1ixX*7b54Ni=lYlxgA#nrmbaBh0KFNf0_P_2wO9i99o>TM1+JWLO>Gk(%jl|^V4Uv zN%Pssvk(yyTHgj8avFG_m7Ip;g}+!k5)kw0|2*{Z-4L^X8gN%uVRDa_=Gfj*KB<7( z+0-N&hF@>y9Y`rVI~^Szn;_A_x1RfQYv}3L3nU38hB0T7pOq&k&n6*<>y^!db5;b( zNXYOyO!n;Shufm)r^x;k3Efd=)<iVQiVPmRnN^& zrPKRg8~8lTMjn)mDfGr;IieubMY1T7cNpYb*GC`xx5;e~Pkuw+q;=|`M&Jilu(isqDWDn zwWYOV^HT!@`*#d}e1F*58leRfvJy#U7Gys5-PD=Bv!==8-`d(C5{3vGQAMdVP$uDM z75An;7upw=S|Uj?*8+{&4vwCg=suG=t4#gn&?XJllvEKj$&GqI4bp5ntfkqb`@(W- zFrqGbrT6$5KGAbVKWm=)?KWy|dNQI5GDaoQsaJcF68QGeo`^jJLPn7i z{-|vI>%@3+LOL_ibw-`|nYOWGn-CjGgrG2d8xe;Oof{9s`cJhRDPI%F@~D}D4m7Ff zfVZP$wwO1(H8DOhF`k-8oXHvc;iFqHo8X;67RD09jrWAn#ykPz4c-pfsAcJnnd0HF zpi9;xveKPr`FLW2jq4MRGiN5w{PK%Lm>d(BCy)i{1FrPL-!>hBw{O}NF^3~0oFEAr z&FoNOyl=vK=C#2mH`#Pq8Am<;Gf3F z$HvF_xB_i~q3P_m2GTv7l9>eDaZmuMZag?Juy=Q>MG$~?8fe(4O`&e(zB7q&Wo&$W zf{zyfF`#z#03_d?flO3QvcVowuk&%VnIF_bSp0>gFuEH@IMmV_&RCS_vSw`qZ)O7= zQn4{@Tq3y~$M;cHJh+uqBCJVq`1qFlKPx;0j>bE4goW_3@nCxJ&h6pcmX_8C;SZbA z6V<3$UjO!}Hl{-93DB1zt~O!&kHeIQ(*s}GLUQuy_O+4;qhn*EW8-fh`q#14xH>WZMtXo&O6hcB6B$HEYJcFi z_E=ja+SdM%h!i#7Z$h18J09-r)F;NElXT`=V&O=7NxL>85kB+@$|#UIj%KO7?GszO03*5O z0|SG%^hTxD4ZH=3hfDz#I0{K0<3Sth^e0Ehj(lB&=zu>e|XAKMRTc$yD5VD25%Fp|uIa3rp^d*YvkrSYKW#wWk|TSVyDbb4zz!owNJTS*n0 z0TWq+q8bbK@WY#vozkXb+L$y7_p*-v7HNjeAaqFgwS-|$gHXF0=HI0V;3LjeYC^L%hT|`B-vHc>^r8bfMst*257| zabSrktetGL-@K`F(_@4GHa>OsEJu=6#$O*iphXIeC3^&1gvpHv(Fw119yfpN^Q8W> z8tCVZf6oXEp-Xz;t6I<&f*J;cRGa#-AN_R-1y=`g%lPjP4c>yDJsEanDU&k^JG9%# zL1MQ))VcY0NdRP?pe>7mbaFsVe;Hd-yv>rK8ceNIBB;4KB-SR(Yrn-$OQWZgqwHTa&{ZgW zd79lxoizI0cq(EFM_S*U1kR9I18sHer|H8nEo?#N35NwN8J7fz!X>d@K`e%NtJLzA zPSo{L>9w>DUm>vv|Cx4~XupZA>s~82{T!spKlr#AZuPrEgP+tw7BuRiNDDb4AQzFF zAhBUy>9*0tX+CN?Jv#Or!OYSmw>0>%?zj@&uy$=?or2w(A83N6Hf|alL-Kx*w_1bi zT0m3C3p7Hl8)gb_PpceiP4t{rM#qVyoiH|@?OiBXKY6(K@_0pZAsZ-i%_s@jT1`;^k8Hs7poKQF#To2-{tu5=< z>K~A{PLlPCsgjSQ^o)-F_VC~x;ZHnz@QovSZKL?YO{1iK>osves!P6EaZpg<=p9qJsy&elius|ez zf?z{%H{U*G_@guiCd%p2(_{P;KgmyM0E&+&M~@DFL<=%30eXT``-pZ@5^9W6kDv{d z8kSB!^<`{*g^4<$1s4W&9yVp$Ou0wjcED?kZzSW zK{0lMpR@y}Q>Wh9M}&3wyAB=)K2MUv5@hBO0TiMX6(55SaRCky)*y6-uzpl}y;+0z zbi;d|I?;VnId$sa4(n+dfan9|L_59xQ**^dht>MeUpg&flZ_!ooYn*mM3RqaN9Ez8 zw`!3-W?Hv?9iDu>yzT?P)=tPLPn^(Bnoga3ZE#?qA4V-s^A8)M!j{czZ~nsGpN~wQ zB}KPi0&bd55t z4U~}Z^nB_IZRFLrK6>ECMf&vlb-D)uXpHZ-5=ULQ3m% z$B!pYFxUdf?BQuQa*N0>hS0=@4_MRBVjmGpN7No9T5=F@(BL1Y8DNC_0T_~eXcxB0 zA$g1X)^RA6JmEZf;*Zq%8jpcQItQoX)4=l}%`-(!Mwi1yqi~v?(&1nUM5rdvFbxg; z8Z9-N;1)fog8XeK_2c}6{Wvf?@e6^mMj3|E47Tp6zmT4PmrqJ(QPC)S8G)&dBBWtV z1O^;I_zGcd9vV6_d~XQ$)SFF6V9lu$$>SEX$MF*{0cUeYbWQX}Lh|(Y2y&`xx*&bza5LaeHB0MW(vP!a8U&Y5ev3pTcx7PCg|lk|CW7Vrg0ObR zid0UNM-hvHaz^SbCfbm4zgdI%|6WQUHZV^Sod-3kZ2tQ9YbD$GztA8WX;wy%6mr60JyNu zG1JJ%(UGG^6T{Nbkak4+I^q#~?YlIPlz(#wswdJ3Q6w48rSywLN&}uY0q5x0=}|$C zLgQpMG;}yHCu(nDg_~@VntzcxmN+iIim!~)s;~?hDdT8(d&!6kNt-a8ni!=aYL=op`!(Jm>%@UYu zMc%2?CrAGH%U8ei)XvnGw`|#X%u#BJCo94{z$+_yxX;>^JxmBOkcVoE<;@w_m^ZgRk#Od}YhS8@Gt|o=*}Uno z?n6(%c6RdI~U`Y;gf$C@wI>Fn72&;$3~{h8Ztz2!q~arDO6TaCxC zIjV1vTmCrAK~y?^NK5A$7?Tb0o!VyD!_l$vk^aG*PkeF9#*L5?`ypZro0fVB?744# z`17B=`;OcH>6Q<--%M>T)(m2coukzv7VTisBHey0c{Dj94e#X{4ulIm`-I}OXPwOM zPc7TpxnKk|u>ez-kObYpF8kh>kL&Mc#L!ANKlMQ10lf+AQ4 z29*sj9|c^}@Gx@0i1ah^7##T8jl4eRtI_xeZ@uGzEnCTq$<7rW4}Yci(9^raSsh|% zZQJJX$|!|z$vL)$r*+acD3QMm5BD9leqVS!0Lvz8*B4r8Ss#sm__n(r>cHM0EKz+#)~In%S<@aPp}A7=E=t8sU}F zE;|x43efWqz%!ZP~%F*Fp z9ik*}q&Eq3D!ZXkHRzqK1&3^&L)9Pv@mh!5&R>hRF;@K9n{ z`8M#DlLmxqD4P2-P9N0&+pbcL1SV@K#ymkgYI_L5HSw=Dx!uPiLN|E?a z$E&8$0E-eE*E+8 z2w-DJ5<}MEp%<`P5tbnb$3DmtVf>l|TcClA9tbCci^u}AxbUb8=0VyP2?2@#@0kFS zgLe*VM>x0`hjaKj!b(_3u!HYCmTF&qGeU+~h_nC~AZBew5Y2XP{Gx}L0YEUCow|{| z(5`IgO`siudBDwbwA&8BPvvj>OuBkY|N za2xtbI^Bh(m~e#j;HUbdmKowsBLifElh2`^$!fffWwanighAL|qRnw6kC?tA0MZ7M z26Q$CbVUrD*xt>Kn zEKGucnW@xCmSFo8SytpOMZv4GO=h;83X}KX*s1ifuPZ9uRT478VO9$KW(@BVBj`p- zG09?P5`&rChZ`vzJ-Cd!vchFm(8ar@titcyhns1-f#E$k?-$IuK}A-2XjU$-kOO(P z6j+0FhMqyUAJeugk}Mc;MW^8z9;On#LdkWjasp(#arENQGe;8d8s_fCd%vPVnUpNq zeTfP=&uyohFW@G}I}M=sgczg=-3n4P%=H~y?!_<-?*mG=EL%z|Qx*0+m)j}=m43v`0rHKlqvZA6=$7DAqvm66-(p!j5RwD|q z6MmLR1|vhu&IC9nD^!~6)?G|}9vsL<1P$pksK{zDWTH_@WuBnzAThp+n;3u{9@uUV zIhCyVc3@1I-Kx7Ct_&1JgX~Ul6L?*jSmI@B&mTUk+- zqtje^*xVpsp`iLEa7rOqNel&H3(lA#kwGQd-d!OD-2E0Qmkj4jh%xdrm`3X2w|%mb z5A!4{B#*6Eh-P!UxSN?cd;x~!LIMr%W+IFy0WEwRk|DG_$qI`U7BhIlM(Pis$zcqF^rlL!LMqSUMA4#yrWmSO zBq!9W(9Li|7g7isd-$~FtnA`2=)c{8O9lHJJbM%v!1>;sb|u!V9i=kpl^ zjZhZZO~y2JrvQ$FclzV36tNG2u9*bkg%E3f#_3IC~^RYp^k&xh^$DH zD0!y{$!v6qoGrs7y^3TazAz;)#6!Hm7jqCN>VN?NP3^`ha3FAW0TAe$b>X=gFCY)L zYA%H`hG9Y&E}OSR3Ct2m7$zdmzC-EiTeE0Z8CD}3 z&}2^GAY^jpl;%>B5-Eu!p=w6@;F+Yvg-`afR0YeOB}1RZOliBMqKNFy6}OloCK*%7 zzm2&H6r>v|P|Ji2-IT1JZnPE-Yi~v(L>C}(?Hg3FLe2OKyx7b98w zZQC%ji=d^b_WB*)z+lWJ$)QFjMeFog&EJlEg$!uIXv{E@Q3e5*yV7N%UZ@B2X>?L? z8SJvJsw=Rwf|d3fHX#4Y9LRQRSphte?4A+_U?kooHsZ!`CRxe#?Ws_Tz&a(INu+Xw z0J$ToRY9@q>)_oD5~xqIqpYGL5ZF~=DYH?W8AeN3)J)7+5a;f^VwaUdPZP*c;B^TK z!sd#tG+Ds{+bYZ@IYa^aQrsKPV|D9$*z( zK&lX$=@n9GE>$Fh1_9HDU=T8i1gd5H4b(IWal9Luk=jjC3^F>(x&vwjB&*=!y(Fg_ z6p5-4Tm(8j@KL4W>B=SzQq71E8zNDDF*!rjGe8o(eGNC#3ufJ=)55X#?4f)~3T86s5Yc3aHE z&_TJ(DFVp|JZMd`@PU$B*^Yo20_UUF?7$>W9=+G%$aA^!Xar5+U_9L`WoiPUiS%Uc zhSptV5Oi4Fx+@zBgjsiB0<1Fr+mK_P)pI1`FjHP z!Yn11v&*7Ta!V;)LJ`^Sa%EN?I?{-WTa=loTu?bs$nX!WcX z6Y7)7gT+l8v6>!U+QwH)SMW8O!NRcc>@4Z;j*P1P(d(B zEU(-NEM468KG92HrHjO;2NXZh?gB*Q9(dC1K1EV3x?c+T^?=onwV4n_cUiFh6@zGb zKou#FM6k&&7Z%opUUEi&J`8mDksEWN4;}#+m8?ZIjl9xgxJr-AXb}sANSZ`IE^uL; z!ah)4$GZqSvKGEP*u zA3Xh1F|4n-85TW?8N+m;MK2aMhD<3$0m9h?bkOu9>BVqJ-AEpkNX(EZ$50VDi)12{ zU1~3OI9L%jDk%LX17^U8as)Rdc`|PzZ2-wG(l@t<1k-6Utzg_;LR{=VumTj7Kt*@J zgye~l-Ec}c|Zf*4x#E2^Ti@Xw70o>MGtf+hQE;xCUZ`{$LCM_q;g<`UpArP3iJ}5 z+~uU{f`&U6Y8kFci%2Yba;VZm6Q|$rb-B&BkY7^n_xJjkgO&tjZX_|$Ik@`#GJ=2t z3UH80In?=3N+5&cO_TuH#w&UuncwFsDk)Ebl)1A2J15UY6~epZQXLwH(__NUPa=m?w4)sMF)1K}$?o0a9HEU*Njk$BKyz zJSxA>Z-)PqGAYV6*uBU#Q#BH9m07Bqb7-pKN7!}wB_BYM%FDTrnP?%}BP(8?--ihB zCH&@MBe-=00i`X~odc)la*ohgRvj*nuL1z~k&VhV(8(o@(bJ)K@*%!`GN9M}wt{J+ zCq;Uvk9H6;DX?)dVqB#r1fX<58x`P|d#H_t^HU9jwmF(l^@AdanMph7s3XV?PcrgG zp4DCA??R@ifD2Isr~I zeV_%9djzQ-A1^n93Z8jH2wIkbi0-rbiB>6g0D(B-ki^nJK#1yg<@bvG)dgE2tjGyL z14O(WfKTs_N@jn;mje}vSc%XqfqIDDF(C>lvw|K9brfCLZrXIdRJn|vS2R|+DbUw_ zQW2&hk>Wul$Z)`@pckeR9@7U)`I6Ce#8*K%6tZ0AV2r7Nw9dg?*uh008=y>pmHHc9l!{pk(9#=IZxZkVC{ng9b#8 zfG`Ow7j+p(^dJP``x@ks$}pK$uvi@`C5Uj83GgIi;*muY#wfy$DrD$`dnqs#C~zo2 z{Fz;}YD%w9fe8zHjNF7~jvn+ZN(%4n4I~j=8W4mP2*gx5D-cud;OmEkq`AmQlqR#LjvT4W5Z+!^T2h$r%5~T*W+Ry(p}4pCknq8libW3I4H580NEDM2703b< zC*A%YpS8?eT$rDiV>kL>(6ImuvWU0er$>wzDI;h{Y_T#`(h6La$ZOc$5#$KYCvILGU zpBphTEs9XnN$r1Tu|~YH{1DS=i;}jMMDD~Y2ek?bE9H~Dd8jB-#+O|==00Nqr(3v< zsnjCVv0pGoXF{%(^5vI6*383rbD?v!s3vM$X@6F=JM-Lmv=v5;CLOfcq^=w-xl#tn zsVZD6o>1 z1Xj68Q>o?{Uu|UPDK03@%Oz4=%0mPohvC&mc7z1*?VZHvdl^}&GP9CIxeBa(qK;t! zfldokbkST`NTmHZzJY^!5iaMWOo;~>NIX3lPFYKEAx0)Jppq|8r3!3El7LuLLy91W zplt+}%dJwG1A-8XHd>#fk6=W#J|79fr$M^%@&Y6Dl5k9jC(1Bh$a3^;$8JG`K2e_z zoxDlROoAby?s9iS8MjMpAf|~C*^7YFF&eqgv>hW2N7E@h2{vl=47CYm+OH?|F*s*? zx#Z33rU)TOUAZJajd>OaE!?u-=!wN=VNZDu!BsAnuaHLID_-5s7chFK5IsY?v^$HF zSmGKb`HU^86kxfkzLb0s-)&G zCMHH1?IR{J8;Sp`=`lsn5Q|Dz!fi3AxvWI!J48S!+g@%%rzKz|8lq5JI5sfJV!|*# z$3SKSqv9TNBGMUOu4UHMzoMJ29py8vkt^v|Fv#>f+))cWrDbJ(<(Vq#0GqsAXO6>R zw^_+(WMc9X17q3{Hu~Z|=0)cP_J~lc-R{V7=DG6iVuQ(qFtNbN@K6lzCqiW0?E-qz zeW#LSKVvNC|1m|XXt4Y@Q|;AF4f+4Yuyk#bR~Os=FSDuFuUXU7)L7(^{|}S$>iHT| z)0(DYuk(L1RSK+WTBA29#oiL>e=(D_3U}7zZg%vWa~_^Rj}oAnF4@4Wq2MUh46ByygjJwN{Z7f}Khy zr5NoVXHsF~_9n}kGK}(gl+&=&GQLJ51{h1S$ZOV=aIfs~~6Q?Wf zCXMdPCz;uqonX0~`q6Z0`9=dA$)iQV#lC&;dtk+1@Yt>!GIAxew zETq_)Rk?d<7`xRr+?ZsH-s!&FRm;(`mG zOC(THJ>1Le;HCJNKZRJqr=oY5N%A(VSyOSCSyjm5u`(w8nOO=Aw<$f$EH1DrC|Fte z7BH(KN6J6MED8y_e9bj)GTDCPnyx0PG3OA&ev?-zX{t8=2eVk0!Kd*|Hc9oCgT@5U zjrDoIWwI&I2p^%F@YY7HNvcsWAQxhmuJI`Uj-9Ns)nYCU0qGUe0a_G-bhR({e=@UF zT#MV7$Q!$x*fp4&2g!ZQN~B*g$z82AnwlC7)3&D+|M$TlkhEk6+vFZS1S#xS7VOc z*tj~?C@ph7j~q(&DS4H#D$Tf^Sgkk8t1GPEWhR7(*K2x($+pIZ)M^J!UX7`Zjn`Yh zmzinD-EyMIaHtIps~Z}$)oSDF>%7kIG1UpTaJ|BmDqN=_+G-hR`f5{Sx!3EKzJt6# zS>$Erv^O-=i`ihY8slsj*H{AfMG8idAo5EH5{%Lty1{65T`>fgO3dFjXjooiQgwZ@ zK_c>oEGtU99(beu+e~pF&|hS-gJxT*JvRtWiPdbm?o}aTp7b12Wr^4QBFpjBH`LcR z+*BaR^$n|egAEEnpbGd&!YdWX&w+}k7{kTYA3F+yl4mwO0Ku$2m>XR*}* zv44mFt8ZB4RW-&uT@7X^2*Erlq6V{>3a+gw@>upD0uC|7v!YN+FxT}22E@SI>e^zA z=3X6nr(lrT9AzsSLBi{G{*a+_WMy)l7-x*WsRWn_N1)gI!_29;E8)0}WV(RY{SziR z8#JJx)Yo6<)jTr11Ifm9fT>pRO^r=@qnSo7^Auxbd7WOLXs`x=kf2E!(VMpy8Mm@# zO;fTFSi_|Nv+^2KZ>p=WuS?V`4Gm?aK8dVgo^vl81p$$0v=ZUf^+jImtBA(>dace{ z57F@`f(SI0pmXeFazhh{D>RxnAcyC^%8FbVN(|_=p@#edYQiGSx{pbL{+rd780ny6RL&Qd^Yp7*zvlbU(u`j)At7yrRC|?;nh-= zoJN;WS6f$`tdnc&uH#-41ja*D*~0xuaY%gHYKdZ^7$Vf&MDfJaCMIxB%DoJBZVf12b$|vc`C1~uG4DuRa%XyTBwm`%Cg$p zIu0RNndUKLHlYT$$lK2p8{Ak&Vj!1+0Cn&ny;fR<>Aac|bM+}iTutpNy*5#YRtIrN zKA2I$DeZ;ZHNrwtLp^Um86`!*OIlUS0RpS$HHm6gfEwl0YS}7YYatD45u98k2@l*_ zg*YX)JoY8X$s9dnQYBRf-_+* z^x*Yem`$tIR+(y6!I+RjuBtK}X6(kkTHCc55?)U?>Ecp)Sf1)Cy8)F_#54q0&Ff{L zp#u$-3;|`5)x3%op*E@YsrB3fD`(YzaYRm9RPoR@K#d z6)QQ&)DDc5sAmuaR+GgR{uBC7wQ#sN0Lw1YwNi#3?xep{!r6!q5~5 zC@WX2bRetSSLro2BlK6TsxA^bC7_|?1=_ISH9AP?FdL$s{4*1+yhbARq^gx#mAZ1} ziWOyhkup+{h8$PD5&ZxJ5pI+qPpxc3^&mf@WI1gy0HxO`a7jc9pkB#WNHZdJ{SWtG?_$g#-JGG=+jh0M8ZAjMJYim|q=PxbFwV9+BP?zNQ<7kXvdvvmdL1?FtLv(k zSC$v%IaKLi7`v&zTCE`$G^z)(8*wY_V%XNp_qZKq`FH5oYqnKO#Y8F_$t?dU>ixEA z(+xBiy&dIxuWJunN5PL(K)7;SwL-a^dmO?f2I0Kj_~O9s_IQhai6zmBY8bSq+Uh4z zX#l76N)IQhrzr=k2uF2QU$tCQ3OZP|2_Vn}*@r2Fs*=@G4HBP~F5_Y387uBxn?kOw z(yPrYg+qWYg#!htWeAJwl|5B?)!@Bc2ruSl^k=3a#&S~#Fue-VR_RT^ju5{M>ZsmYf2R@qioRaG0*2r5|FQ-V9D9aT~hAcC02qZlhwcU9HOmAzHU z4aF3k+-no65vKx3=v%R^%6zS%ZZEo3TB_cN;Own3iC8R2fI9649D#9=3z91nRjdrj zm53qZih$3>&7zgs3W)+xuQFG|nFU6|R^TalazMCpCB{%f;VaEmE2~xrfs%rb*JFDJ zoNvO-#7YJ#t|~xAN()Lvji^}OVX6aRzEUS%e5Gl{%Bo7TJOfC!%wVtAjA5P?D^`LU z;1w(_SQQDyyC_Y0)pwZLxBP}1Z(0Ff3Put!F~5|19Uf0fVSy_T`_?u9UWQ-QO0o$* zfXl^RQE^F`(}_(ci%F5Nuoz~Ktl9K1V?nI5ui!6gzJkeDJXPiI-F?MG7EfT=oA15% zma_QyS2k0+XX-utJ#b}I7CqbcGI7V>HH#*3B@;tu4DRo}_r#TuS>#;x$t?Hx-kZ9z z2wIE;*TV5;{vKTni7S{R-6b4vT7WEJ(xzjWtZ$D0vg`9DGc%TqH9&pSKx58!XQ(D7!ZurIv; zH_dRn@a$_#&;RZVxz1dfT>3ubhcn#fIlE`60g(PQ57(JXZ^=t6fNh4?yl2rR<{z4u z>&(SJEO8RPe}>ikXE!b_0NHkHe&)D}rTG&6$!{;nID6x|C1lT>SuP8|^QGh;U6^n7 z&XjEl1(>()XE|ryzp{k59PaPE_s6A6!nTR? zc+LAUykr2dWs~!AoO=;pwJq7-r3!$M0L+QVJ0G^L#qbxwe$6am(YEPJ?IEr2;_x^5vv}_u}|=e+fj8 z+b*{LnPq}E+y3y_Rp&2-=4`plVmIIEpRV?f$n}>~erHJ%xvBtMyYRwy7SGwI_!Y%f z`?EheoAPCzFS}ao&0o1Jr$1jTes%aOcb`3<^5uQLDrr!PzIA!r<{9;+D+gf3@8_X= zxtFeZJ-lr2a#_tk?#i~LDi6(1_42RYaRmmjUPhg{lqC46Q#r=JH9%9)`3n(SclREHYyT$z-$TFMf{k8>2 z&bK$T$ZrKN+5{9Vz<<6JaKi#)FL60<9<`@0EtFMXm>w~6vdA9nqF=Qx^exAHrR#$4 z#jQ8p_oGXv<%{k&oR|Bjp}coS?dh{W&+5TU*WY>Yr*!9%Uz8~q+hzO3ERxf_>Ew=S zuCq>z@8y+cpE)q&o|jyk&zH)kX)d^~xqW)btdo%Jj6Ct<^Ym$(Blm?L9-Bqrg3|@N zn&1UPap6yYndj=Ic83;f=me=BT_~?{<)T3|SBi`OT4KITK5PDg1%B^kn>FSVr*|); zgSdXFFFGdgnKuMXmtZ>U_G=gAKVKlvnvBywhAto=J3ljb%tb$4d%^r!7hb>URO4oP z76r*3`s&N?Z_D6f8^n&wV2>e>)A}CKb^nhp{hh!C6#uwT5&_N2#qsPt_q}xKFXb;_ zZCtp>?$j0UJT1U-&gF&IW|#3pgE^EI_u)@-)-SW1k1byu!S_3B!C#Bq^L{BUZv29+ z;RV00TpayN&6=Dyhv8qRomZ=tFFnI`<}7@5d0dRzyhZrQi_N&Y_vUlJtG4-k8KT}j zgZlJY&SlKIT0<`EVHW7&uNK1$owr|q<%P%Zz2mm|)}~8Ud#8T>>W`kh|E}9_%(c_M z{#ocxet%umC4hPRwHIIb{N10vt-xVgu-eX*>xt5D5I+yMA zr}>tUbCrDS#f2dH^QTt0@DC)XX)WetzAc2g{5k%~{HGp2e?doMetu=FN!-g=L1eZx z6|ALyHe0e^{Pg83=YJWs_ug5s+FC#Rz`b|ez5@TCbP=|mMJRiE8DBkpIy-hL-hVP1 z(fQ9?mx6DG=H)D4-Al+nI={+uuI4Tw|BG!pW`$T_?SD0-y6C23mj41>^VQ&=o8@tl z_p6X(78Lc)i}20fT5$7gL8_O3<40L07kR(b&ei2}yI|x+HyyJg;s5tps=phuJTCUW zg#Xs^a=v +#include +#include + +#include "core/crc.h" +#include "core/frameAllocator.h" + +#include "core/util/str.h" +#include "core/strings/stringFunctions.h" + +#include "platform/platformVolume.h" +#include "platformPOSIX/posixVolume.h" + +#ifndef PATH_MAX +#include +#endif + +#ifndef NGROUPS_UMAX + #define NGROUPS_UMAX 32 +#endif + + +//#define DEBUG_SPEW + + +namespace Torque +{ +namespace Posix +{ + +//----------------------------------------------------------------------------- + +static String buildFileName(const String& prefix,const Path& path) +{ + // Need to join the path (minus the root) with our + // internal path name. + String file = prefix; + file = Path::Join(file,'/',path.getPath()); + file = Path::Join(file,'/',path.getFileName()); + file = Path::Join(file,'.',path.getExtension()); + return file; +} + +/* +static bool isFile(const String& file) +{ + struct stat info; + if (stat(file.c_str(),&info) == 0) + return S_ISREG(info.st_mode); + return false; +} + +static bool isDirectory(const String& file) +{ + struct stat info; + if (stat(file.c_str(),&info) == 0) + return S_ISDIR(info.st_mode); + return false; +} +*/ + +//----------------------------------------------------------------------------- + +static uid_t _Uid; // Current user id +static int _GroupCount; // Number of groups in the table +static gid_t _Groups[NGROUPS_UMAX+1]; // Table of all the user groups + +static void copyStatAttributes(const struct stat& info, FileNode::Attributes* attr) +{ + // We need to user and group id's in order to determin file + // read-only access permission. This information is only retrieved + // once per execution. + if (!_Uid) + { + _Uid = getuid(); + _GroupCount = getgroups(NGROUPS_UMAX,_Groups); + _Groups[_GroupCount++] = getegid(); + } + + // Fill in the return struct. The read-only flag is determined + // by comparing file user and group ownership. + attr->flags = 0; + if (S_ISDIR(info.st_mode)) + attr->flags |= FileNode::Directory; + + if (S_ISREG(info.st_mode)) + attr->flags |= FileNode::File; + + if (info.st_uid == _Uid) + { + if (!(info.st_mode & S_IWUSR)) + attr->flags |= FileNode::ReadOnly; + } + else + { + S32 i = 0; + for (; i < _GroupCount; i++) + { + if (_Groups[i] == info.st_gid) + break; + } + if (i != _GroupCount) + { + if (!(info.st_mode & S_IWGRP)) + attr->flags |= FileNode::ReadOnly; + } + else + { + if (!(info.st_mode & S_IWOTH)) + attr->flags |= FileNode::ReadOnly; + } + } + + attr->size = info.st_size; + attr->mtime = UnixTimeToTime(info.st_mtime); + attr->atime = UnixTimeToTime(info.st_atime); +} + + +//----------------------------------------------------------------------------- + +PosixFileSystem::PosixFileSystem(String volume) +{ + _volume = volume; +} + +PosixFileSystem::~PosixFileSystem() +{ +} + +FileNodeRef PosixFileSystem::resolve(const Path& path) +{ + String file = buildFileName(_volume,path); + struct stat info; + if (stat(file.c_str(),&info) == 0) + { + // Construct the appropriate object + if (S_ISREG(info.st_mode)) + return new PosixFile(path,file); + + if (S_ISDIR(info.st_mode)) + return new PosixDirectory(path,file); + } + + return 0; +} + +FileNodeRef PosixFileSystem::create(const Path& path, FileNode::Mode mode) +{ + // The file will be created on disk when it's opened. + if (mode & FileNode::File) + return new PosixFile(path,buildFileName(_volume,path)); + + // Default permissions are read/write/search/executate by everyone, + // though this will be modified by the current umask + if (mode & FileNode::Directory) + { + String file = buildFileName(_volume,path); + + if (mkdir(file.c_str(),S_IRWXU | S_IRWXG | S_IRWXO)) + return new PosixDirectory(path,file); + } + + return 0; +} + +bool PosixFileSystem::remove(const Path& path) +{ + // Should probably check for outstanding files or directory objects. + String file = buildFileName(_volume,path); + + struct stat info; + int error = stat(file.c_str(),&info); + if (error < 0) + return false; + + if (S_ISDIR(info.st_mode)) + return !rmdir(file); + + return !unlink(file); +} + +bool PosixFileSystem::rename(const Path& from,const Path& to) +{ + String fa = buildFileName(_volume,from); + String fb = buildFileName(_volume,to); + + if (!rename(fa.c_str(),fb.c_str())) + return true; + + return false; +} + +Path PosixFileSystem::mapTo(const Path& path) +{ + return buildFileName(_volume,path); +} + + +Path PosixFileSystem::mapFrom(const Path& path) +{ + const String::SizeType volumePathLen = _volume.length(); + + String pathStr = path.getFullPath(); + + if ( _volume.compare( pathStr, volumePathLen, String::NoCase )) + return Path(); + + return pathStr.substr( volumePathLen, pathStr.length() - volumePathLen ); +} + +//----------------------------------------------------------------------------- + +PosixFile::PosixFile(const Path& path,String name) +{ + _path = path; + _name = name; + _status = Closed; + _handle = 0; +} + +PosixFile::~PosixFile() +{ + if (_handle) + close(); +} + +Path PosixFile::getName() const +{ + return _path; +} + +FileNode::Status PosixFile::getStatus() const +{ + return _status; +} + +bool PosixFile::getAttributes(Attributes* attr) +{ + struct stat info; + int error = _handle? fstat(fileno(_handle),&info): stat(_name.c_str(),&info); + + if (error < 0) + { + _updateStatus(); + return false; + } + + copyStatAttributes(info,attr); + attr->name = _path; + + return true; +} + +U32 PosixFile::calculateChecksum() +{ + if (!open( Read )) + return 0; + + U64 fileSize = getSize(); + + FrameTemp buf( fileSize ); + + U32 bytesRead = read(buf, fileSize); + + close(); + + if ( bytesRead != fileSize ) + return 0; + + return CRC::calculateCRC(buf, fileSize); +} + +bool PosixFile::open(AccessMode mode) +{ + close(); + + if (_name.isEmpty()) + { + return _status; + } + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[PosixFile] opening '%s'", _name.c_str() ); + #endif + + const char* fmode = "r"; + switch (mode) + { + case Read: fmode = "r"; break; + case Write: fmode = "w"; break; + case ReadWrite: + { + fmode = "r+"; + // Ensure the file exists. + FILE* temp = fopen( _name.c_str(), "a+" ); + fclose( temp ); + break; + } + case WriteAppend: fmode = "a"; break; + default: break; + } + + if (!(_handle = fopen(_name.c_str(), fmode))) + { + _updateStatus(); + return false; + } + + _status = Open; + return true; +} + +bool PosixFile::close() +{ + if (_handle) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[PosixFile] closing '%s'", _name.c_str() ); + #endif + + fflush(_handle); + fclose(_handle); + _handle = 0; + } + + _status = Closed; + return true; +} + +U32 PosixFile::getPosition() +{ + if (_status == Open || _status == EndOfFile) + return ftell(_handle); + + return 0; +} + +U32 PosixFile::setPosition(U32 delta, SeekMode mode) +{ + if (_status != Open && _status != EndOfFile) + return 0; + + S32 fmode = 0; + switch (mode) + { + case Begin: fmode = SEEK_SET; break; + case Current: fmode = SEEK_CUR; break; + case End: fmode = SEEK_END; break; + default: break; + } + + if (fseek(_handle, delta, fmode)) + { + _status = UnknownError; + return 0; + } + + _status = Open; + + return ftell(_handle); +} + +U32 PosixFile::read(void* dst, U32 size) +{ + if (_status != Open && _status != EndOfFile) + return 0; + + U32 bytesRead = fread(dst, 1, size, _handle); + + if (bytesRead != size) + { + if (feof(_handle)) + _status = EndOfFile; + else + _updateStatus(); + } + + return bytesRead; +} + +U32 PosixFile::write(const void* src, U32 size) +{ + if ((_status != Open && _status != EndOfFile) || !size) + return 0; + + U32 bytesWritten = fwrite(src, 1, size, _handle); + + if (bytesWritten != size) + _updateStatus(); + + return bytesWritten; +} + +void PosixFile::_updateStatus() +{ + switch (errno) + { + case EACCES: _status = AccessDenied; break; + case ENOSPC: _status = FileSystemFull; break; + case ENOTDIR: _status = NoSuchFile; break; + case ENOENT: _status = NoSuchFile; break; + case EISDIR: _status = AccessDenied; break; + case EROFS: _status = AccessDenied; break; + default: _status = UnknownError; break; + } +} + +//----------------------------------------------------------------------------- + +PosixDirectory::PosixDirectory(const Path& path,String name) +{ + _path = path; + _name = name; + _status = Closed; + _handle = 0; +} + +PosixDirectory::~PosixDirectory() +{ + if (_handle) + close(); +} + +Path PosixDirectory::getName() const +{ + return _path; +} + +bool PosixDirectory::open() +{ + if ((_handle = opendir(_name)) == 0) + { + _updateStatus(); + return false; + } + + _status = Open; + return true; +} + +bool PosixDirectory::close() +{ + if (_handle) + { + closedir(_handle); + _handle = NULL; + return true; + } + + return false; +} + +bool PosixDirectory::read(Attributes* entry) +{ + if (_status != Open) + return false; + + struct dirent* de = readdir(_handle); + + if (!de) + { + _status = EndOfFile; + return false; + } + + // Skip "." and ".." entries + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || + (de->d_name[1] == '.' && de->d_name[2] == '\0'))) + return read(entry); + + // The dirent structure doesn't actually return much beside + // the name, so we must call stat for more info. + struct stat info; + String file = _name + "/" + de->d_name; + + int error = stat(file.c_str(),&info); + + if (error < 0) + { + _updateStatus(); + return false; + } + copyStatAttributes(info,entry); + entry->name = de->d_name; + return true; +} + +U32 PosixDirectory::calculateChecksum() +{ + // Return checksum of current entry + return 0; +} + +bool PosixDirectory::getAttributes(Attributes* attr) +{ + struct stat info; + if (stat(_name.c_str(),&info)) + { + _updateStatus(); + return false; + } + + copyStatAttributes(info,attr); + attr->name = _path; + return true; +} + +FileNode::Status PosixDirectory::getStatus() const +{ + return _status; +} + +void PosixDirectory::_updateStatus() +{ + switch (errno) + { + case EACCES: _status = AccessDenied; break; + case ENOTDIR: _status = NoSuchFile; break; + case ENOENT: _status = NoSuchFile; break; + default: _status = UnknownError; break; + } +} + +} // Namespace POSIX + +} // Namespace Torque + + +//----------------------------------------------------------------------------- + +#ifndef TORQUE_OS_MAC // Mac has its own native FS build on top of the POSIX one. + +Torque::FS::FileSystemRef Platform::FS::createNativeFS( const String &volume ) +{ + return new Posix::PosixFileSystem( volume ); +} + +#endif + +String Platform::FS::getAssetDir() +{ + return Platform::getExecutablePath(); +} + +/// Function invoked by the kernel layer to install OS specific +/// file systems. +bool Platform::FS::InstallFileSystems() +{ + Platform::FS::Mount( String(), Platform::FS::createNativeFS( String() ) ); + + // Setup the current working dir. + char buffer[PATH_MAX]; + if (::getcwd(buffer,sizeof(buffer))) + { + // add trailing '/' if it isn't there + if (buffer[dStrlen(buffer) - 1] != '/') + dStrcat(buffer, "/"); + + Platform::FS::SetCwd(buffer); + } + + // Mount the home directory + if (char* home = getenv("HOME")) + Platform::FS::Mount( "home", Platform::FS::createNativeFS(home) ); + + return true; +} diff --git a/platformPOSIX/posixVolume.h b/platformPOSIX/posixVolume.h new file mode 100644 index 0000000..21f8c5f --- /dev/null +++ b/platformPOSIX/posixVolume.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _POSIXVOLUME_H_ +#define _POSIXVOLUME_H_ + +#ifndef _VOLUME_H_ +#include "core/volume.h" +#endif + +#include +#include +#include + +namespace Torque +{ +using namespace FS; + +namespace Posix +{ + +//----------------------------------------------------------------------------- + +class PosixFileSystem: public FileSystem +{ + String _volume; + +public: + PosixFileSystem(String volume); + ~PosixFileSystem(); + + String getTypeStr() const { return "POSIX"; } + + FileNodeRef resolve(const Path& path); + FileNodeRef create(const Path& path,FileNode::Mode); + bool remove(const Path& path); + bool rename(const Path& from,const Path& to); + Path mapTo(const Path& path); + Path mapFrom(const Path& path); +}; + + +//----------------------------------------------------------------------------- +/// Posix stdio file access. +/// This class makes use the fopen, fread and fwrite for buffered io. +class PosixFile: public File +{ + friend class PosixFileSystem; + Path _path; + String _name; + FILE* _handle; + Status _status; + + PosixFile(const Path& path,String name); + bool _updateInfo(); + void _updateStatus(); + +public: + ~PosixFile(); + + Path getName() const; + Status getStatus() const; + bool getAttributes(Attributes*); + + U32 getPosition(); + U32 setPosition(U32,SeekMode); + + bool open(AccessMode); + bool close(); + + U32 read(void* dst, U32 size); + U32 write(const void* src, U32 size); + +private: + U32 calculateChecksum(); +}; + + +//----------------------------------------------------------------------------- + +class PosixDirectory: public Directory +{ + friend class PosixFileSystem; + Path _path; + String _name; + DIR* _handle; + Status _status; + + PosixDirectory(const Path& path,String name); + void _updateStatus(); + +public: + ~PosixDirectory(); + + Path getName() const; + Status getStatus() const; + bool getAttributes(Attributes*); + + bool open(); + bool close(); + bool read(Attributes*); + +private: + U32 calculateChecksum(); +}; + + +} // Namespace +} // Namespace +#endif diff --git a/platformWin32/VFSRes.h b/platformWin32/VFSRes.h new file mode 100644 index 0000000..888f512 --- /dev/null +++ b/platformWin32/VFSRes.h @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by zipCat.rc +// +#define IDR_ZIPFILE 500 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 501 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/platformWin32/cardProfile.cpp b/platformWin32/cardProfile.cpp new file mode 100644 index 0000000..4a54ddb --- /dev/null +++ b/platformWin32/cardProfile.cpp @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "console/console.h" +#include "platformWin32/platformWin32.h" + + +void initDisplayDeviceInfo() +{ + Con::printf( "Reading Display Device information..." ); + + U8 i = 0; + + DISPLAY_DEVICEA ddData; + ddData.cb = sizeof( DISPLAY_DEVICEA ); + + // Search for the primary display adapter, because that is what the rendering + // context will get created on. + while( EnumDisplayDevicesA( NULL, i, &ddData, 0 ) != 0 ) + { + // If we find the primary display adapter, break out + if( ddData.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE ) + break; + + i++; + } + + Con::printf( " Primary Display Device Found:" ); + + // Ok, now we have the primary display device. Parse the device information. + char ven[9]; + char dev[9]; + + ven[8] = dev[8] = '\0'; + + // It may seem a bit silly here to cast, but there are two implimentations in Platform.h + // This usage is the "const" version... + char *pos = dStrstr( ddData.DeviceID, (const char *)"VEN_"); + + dStrncpy( ven, ( pos ) ? pos : "VEN_0000", 8 ); + + Con::printf( " Vendor Id: %s", ven ); + + pos = dStrstr( ddData.DeviceID, (const char *)"DEV_" ); + + dStrncpy( dev, ( pos ) ? pos : "DEV_0000", 8 ); + + Con::printf( " Device Id: %s", dev ); + + // We now have the information, set them to console variables so we can parse + // the file etc in script using getField and so on. + Con::setVariable( "$PCI_VEN", ven ); + Con::setVariable( "$PCI_DEV", dev ); +} + +ConsoleFunction( initDisplayDeviceInfo, void, 1, 1, "()" ) +{ + initDisplayDeviceInfo(); +} \ No newline at end of file diff --git a/platformWin32/menus/menuBarWin32.cpp b/platformWin32/menus/menuBarWin32.cpp new file mode 100644 index 0000000..5393d1a --- /dev/null +++ b/platformWin32/menus/menuBarWin32.cpp @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "platform/menus/menuBar.h" +#include "platform/menus/popupMenu.h" +#include "gui/core/guiCanvas.h" +#include "windowManager/platformWindowMgr.h" +#include "windowManager/win32/win32Window.h" +#include "core/util/safeDelete.h" + +//----------------------------------------------------------------------------- +// Platform Data +//----------------------------------------------------------------------------- + +// class PlatformMenuBarData +// { +// +// }; + +//----------------------------------------------------------------------------- +// MenuBar Methods +//----------------------------------------------------------------------------- + +void MenuBar::createPlatformPopupMenuData() +{ +// mData = new PlatformMenuBarData; + + // [tom, 6/4/2007] Nothing currently needed for win32 + mData = NULL; +} + +void MenuBar::deletePlatformPopupMenuData() +{ +// SAFE_DELETE(mData); +} + +//----------------------------------------------------------------------------- + +void MenuBar::updateMenuBar(PopupMenu *menu /* = NULL */) +{ + if(! isAttachedToCanvas()) + return; + + if(menu == NULL) + { + // [tom, 6/4/2007] Kludgetastic + GuiCanvas *oldCanvas = mCanvas; + S32 pos = -1; + PopupMenu *mnu = dynamic_cast(at(0)); + if(mnu) + pos = mnu->getPosOnMenuBar(); + + removeFromCanvas(); + attachToCanvas(oldCanvas, pos); + + return; + } + + menu->removeFromMenuBar(); + SimSet::iterator itr = find(begin(), end(), menu); + if(itr == end()) + return; + + menu->attachToMenuBar(mCanvas, itr - begin()); + + Win32Window *pWindow = dynamic_cast(mCanvas->getPlatformWindow()); + if(pWindow == NULL) + return; + + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); +} + +//----------------------------------------------------------------------------- + +void MenuBar::attachToCanvas(GuiCanvas *owner, S32 pos) +{ + if(owner == NULL || isAttachedToCanvas()) + return; + + // This is set for popup menus in the onAttachToMenuBar() callback + mCanvas = owner; + + Win32Window *pWindow = dynamic_cast(owner->getPlatformWindow()); + if(pWindow == NULL) + return; + + // Setup the native menu bar + HMENU hWindowMenu = pWindow->getMenuHandle(); + if(hWindowMenu == NULL) + { + hWindowMenu = CreateMenu(); + if(hWindowMenu) + { + SetMenu(pWindow->getHWND(), hWindowMenu); + pWindow->setMenuHandle( hWindowMenu); + } + } + + // Add the items + for(S32 i = 0;i < size();++i) + { + PopupMenu *mnu = dynamic_cast(at(i)); + if(mnu == NULL) + { + Con::warnf("MenuBar::attachToMenuBar - Non-PopupMenu object in set"); + continue; + } + + if(mnu->isAttachedToMenuBar()) + mnu->removeFromMenuBar(); + + mnu->attachToMenuBar(owner, pos + i); + } + + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); + +} + +void MenuBar::removeFromCanvas() +{ + if(mCanvas == NULL || ! isAttachedToCanvas()) + return; + + Win32Window *pWindow = dynamic_cast(mCanvas->getPlatformWindow()); + if(pWindow == NULL) + return; + + // Setup the native menu bar + HMENU hWindowMenu = pWindow->getMenuHandle(); + if(hWindowMenu == NULL) + return; + + // Add the items + for(S32 i = 0;i < size();++i) + { + PopupMenu *mnu = dynamic_cast(at(i)); + if(mnu == NULL) + { + Con::warnf("MenuBar::removeFromMenuBar - Non-PopupMenu object in set"); + continue; + } + + mnu->removeFromMenuBar(); + } + + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); + + mCanvas = NULL; +} diff --git a/platformWin32/menus/popupMenuWin32.cpp b/platformWin32/menus/popupMenuWin32.cpp new file mode 100644 index 0000000..73acb17 --- /dev/null +++ b/platformWin32/menus/popupMenuWin32.cpp @@ -0,0 +1,658 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/menus/popupMenu.h" +#include "platformWin32/platformWin32.h" +#include "console/consoleTypes.h" +#include "gui/core/guiCanvas.h" +#include "windowManager/platformWindowMgr.h" +#include "windowManager/win32/win32Window.h" +#include "core/util/safeDelete.h" + +#include "sim/actionMap.h" +#include "platform/platformInput.h" + +////////////////////////////////////////////////////////////////////////// +// Platform Menu Data +////////////////////////////////////////////////////////////////////////// + +struct PlatformPopupMenuData +{ + static U32 mLastPopupMenuID; + static const U32 PopupMenuIDRange; + + HMENU mMenu; + U32 mMenuID; + U32 mLastID; + + Win32Window::AcceleratorList mAccelerators; + + PlatformPopupMenuData() + { + mMenu = NULL; + mMenuID = mLastPopupMenuID++; + mLastID = 0; + } + + ~PlatformPopupMenuData() + { + if(mMenu) + DestroyMenu(mMenu); + } + + void insertAccelerator(EventDescriptor &desc, U32 id); + void removeAccelerator(U32 id); +}; + +U32 PlatformPopupMenuData::mLastPopupMenuID = 0; +const U32 PlatformPopupMenuData::PopupMenuIDRange = 100; + +////////////////////////////////////////////////////////////////////////// + +void PlatformPopupMenuData::insertAccelerator(EventDescriptor &desc, U32 id) +{ + if(desc.eventType != SI_KEY) + return; + + Win32Window::AcceleratorList::iterator i; + for(i = mAccelerators.begin();i != mAccelerators.end();++i) + { + if(i->mID == id) + { + // Update existing entry + i->mDescriptor.eventType = desc.eventType; + i->mDescriptor.eventCode = desc.eventCode; + i->mDescriptor.flags = desc.flags; + return; + } + + if(i->mDescriptor.eventType == desc.eventType && i->mDescriptor.eventCode == desc.eventCode && i->mDescriptor.flags == desc.flags) + { + // Already have a matching accelerator, don't add another one + return; + } + } + + Win32Window::Accelerator accel; + accel.mDescriptor = desc; + accel.mID = id; + mAccelerators.push_back(accel); +} + +void PlatformPopupMenuData::removeAccelerator(U32 id) +{ + Win32Window::AcceleratorList::iterator i; + for(i = mAccelerators.begin();i != mAccelerators.end();++i) + { + if(i->mID == id) + { + mAccelerators.erase(i); + return; + } + } +} + +////////////////////////////////////////////////////////////////////////// + +void PopupMenu::createPlatformPopupMenuData() +{ + mData = new PlatformPopupMenuData; +} + +void PopupMenu::deletePlatformPopupMenuData() +{ + SAFE_DELETE(mData); +} +void PopupMenu::createPlatformMenu() +{ + mData->mMenu = mIsPopup ? CreatePopupMenu() : CreateMenu(); + AssertFatal(mData->mMenu, "Unable to create menu"); + + MENUINFO mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIM_MENUDATA; + mi.dwMenuData = (ULONG_PTR)this; + SetMenuInfo(mData->mMenu, &mi); +} + +////////////////////////////////////////////////////////////////////////// +// Public Methods +////////////////////////////////////////////////////////////////////////// + +S32 PopupMenu::insertItem(S32 pos, const char *title, const char* accelerator) +{ + Win32Window *pWindow = mCanvas ? dynamic_cast(mCanvas->getPlatformWindow()) : NULL; + bool isAttached = isAttachedToMenuBar(); + if(isAttached && pWindow == NULL) + return -1; + + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_ID|MIIM_TYPE; + mi.wID = (mData->mMenuID * PlatformPopupMenuData::PopupMenuIDRange) + mData->mLastID + 1; + mData->mLastID++; + if(title && *title) + mi.fType = MFT_STRING; + else + mi.fType = MFT_SEPARATOR; + + char buf[1024]; + if(accelerator && *accelerator) + { + dSprintf(buf, sizeof(buf), "%s\t%s", title, accelerator); + + if(isAttached) + pWindow->removeAccelerators(mData->mAccelerators); + + // Build entry for accelerator table + EventDescriptor accelDesc; + if(ActionMap::createEventDescriptor(accelerator, &accelDesc)) + mData->insertAccelerator(accelDesc, mi.wID); + else + Con::errorf("PopupMenu::insertItem - Could not create event descriptor for accelerator \"%s\"", accelerator); + + if(isAttached) + pWindow->addAccelerators(mData->mAccelerators); + } + else + dSprintf(buf, sizeof(buf), "%s", title); + + mi.dwTypeData = (LPSTR)buf; + + if(InsertMenuItemA(mData->mMenu, pos, TRUE, &mi)) + { + if(isAttached) + { + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); + } + return mi.wID; + } + + return -1; +} + +S32 PopupMenu::insertSubMenu(S32 pos, const char *title, PopupMenu *submenu) +{ + Win32Window *pWindow = mCanvas ? dynamic_cast(mCanvas->getPlatformWindow()) : NULL; + bool isAttached = isAttachedToMenuBar(); + if(isAttached && pWindow == NULL) + return -1; + + for(S32 i = 0;i < mSubmenus->size();i++) + { + if(submenu == (*mSubmenus)[i]) + { + Con::errorf("PopupMenu::insertSubMenu - Attempting to add submenu twice"); + return -1; + } + } + + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_ID|MIIM_TYPE|MIIM_SUBMENU|MIIM_DATA; + mi.wID = (mData->mMenuID * PlatformPopupMenuData::PopupMenuIDRange) + mData->mLastID + 1; + if(title && *title) + mi.fType = MFT_STRING; + else + mi.fType = MFT_SEPARATOR; + mi.dwTypeData = (LPSTR)title; + mi.hSubMenu = submenu->mData->mMenu; + mi.dwItemData = (ULONG_PTR)submenu; + if(InsertMenuItemA(mData->mMenu, pos, TRUE, &mi)) + { + mSubmenus->addObject(submenu); + + if(isAttached) + { + pWindow->addAccelerators(submenu->mData->mAccelerators); + + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); + } + return mi.wID; + } + + return -1; +} + +bool PopupMenu::setItem(S32 pos, const char *title, const char* accelerator) +{ + Win32Window *pWindow = mCanvas ? dynamic_cast(mCanvas->getPlatformWindow()) : NULL; + bool isAttached = isAttachedToMenuBar(); + if(isAttached && pWindow == NULL) + return false; + + // Are we out of range? + if ( pos >= getItemCount() ) + return false; + + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_TYPE; + + if(title && *title) + mi.fType = MFT_STRING; + else + mi.fType = MFT_SEPARATOR; + + char buf[1024]; + if(accelerator && *accelerator) + { + dSprintf(buf, sizeof(buf), "%s\t%s", title, accelerator); + + if(isAttached) + pWindow->removeAccelerators(mData->mAccelerators); + + // Build entry for accelerator table + EventDescriptor accelDesc; + if(ActionMap::createEventDescriptor(accelerator, &accelDesc)) + mData->insertAccelerator(accelDesc, pos); + else + Con::errorf("PopupMenu::setItem - Could not create event descriptor for accelerator \"%s\"", accelerator); + + if(isAttached) + pWindow->addAccelerators(mData->mAccelerators); + } + else + dSprintf(buf, sizeof(buf), "%s", title); + + mi.dwTypeData = (LPSTR)buf; + + if(SetMenuItemInfoA(mData->mMenu, pos, TRUE, &mi)) + { + if(isAttached) + { + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); + } + + return true; + } + + return false; +} + +void PopupMenu::removeItem(S32 itemPos) +{ + Win32Window *pWindow = mCanvas ? dynamic_cast(mCanvas->getPlatformWindow()) : NULL; + bool isAttached = isAttachedToMenuBar(); + if(isAttached && pWindow == NULL) + return; + + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_DATA|MIIM_ID; + if(GetMenuItemInfoA(mData->mMenu, itemPos, TRUE, &mi)) + { + bool submenu = false; + + // Update list of submenus if this is a submenu + if(mi.fMask & MIIM_DATA) + { + PopupMenu *mnu = (PopupMenu *)mi.dwItemData; + if( mnu != NULL ) + { + if(isAttached) + pWindow->removeAccelerators(mnu->mData->mAccelerators); + mSubmenus->removeObject(mnu); + + submenu = true; + } + } + + if(! submenu) + { + // Update accelerators if this has an accelerator and wasn't a sub menu + for(S32 i = 0;i < mData->mAccelerators.size();++i) + { + if(mData->mAccelerators[i].mID == mi.wID) + { + if(isAttached) + pWindow->removeAccelerators(mData->mAccelerators); + + mData->mAccelerators.erase(i); + + if(isAttached) + pWindow->addAccelerators(mData->mAccelerators); + + break; + } + } + } + } + else + return; + + RemoveMenu(mData->mMenu, itemPos, MF_BYPOSITION); + + if(isAttached) + { + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); + } +} + +////////////////////////////////////////////////////////////////////////// + +void PopupMenu::enableItem(S32 pos, bool enable) +{ + U32 flags = enable ? MF_ENABLED : MF_GRAYED; + EnableMenuItem(mData->mMenu, pos, MF_BYPOSITION|flags); +} + +void PopupMenu::checkItem(S32 pos, bool checked) +{ +// U32 flags = checked ? MF_CHECKED : MF_UNCHECKED; +// CheckMenuItem(mData->mMenu, pos, MF_BYPOSITION|flags); + + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_STATE; + mi.fState = checked ? MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfoA(mData->mMenu, pos, TRUE, &mi); +} + +void PopupMenu::checkRadioItem(S32 firstPos, S32 lastPos, S32 checkPos) +{ + CheckMenuRadioItem(mData->mMenu, firstPos, lastPos, checkPos, MF_BYPOSITION); +} + +bool PopupMenu::isItemChecked(S32 pos) +{ + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_STATE; + if(GetMenuItemInfoA(mData->mMenu, pos, TRUE, &mi) && (mi.fState & MFS_CHECKED)) + return true; + return false; +} + +U32 PopupMenu::getItemCount() +{ + return GetMenuItemCount( mData->mMenu ); +} + +////////////////////////////////////////////////////////////////////////// + +bool PopupMenu::canHandleID(U32 id) +{ + for(S32 i = 0;i < mSubmenus->size();i++) + { + PopupMenu *subM = dynamic_cast((*mSubmenus)[i]); + if(subM == NULL) + continue; + + if(subM->canHandleID(id)) + return true; + } + + if(id >= mData->mMenuID * PlatformPopupMenuData::PopupMenuIDRange && + id < (mData->mMenuID+1) * PlatformPopupMenuData::PopupMenuIDRange) + { + return true; + } + + return false; +} + +bool PopupMenu::handleSelect(U32 command, const char *text /* = NULL */) +{ + // [tom, 8/20/2006] Pass off to a sub menu if it's for them + for(S32 i = 0;i < mSubmenus->size();i++) + { + PopupMenu *subM = dynamic_cast((*mSubmenus)[i]); + if(subM == NULL) + continue; + + if(subM->canHandleID(command)) + { + return subM->handleSelect(command, text); + } + } + + // [tom, 8/21/2006] Cheesey hack to find the position based on ID + char buf[512]; + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.dwTypeData = NULL; + + S32 numItems = GetMenuItemCount(mData->mMenu); + S32 pos = -1; + for(S32 i = 0;i < numItems;i++) + { + mi.fMask = MIIM_ID|MIIM_STRING|MIIM_STATE; + if(GetMenuItemInfoA(mData->mMenu, i, TRUE, &mi)) + { + if(mi.wID == command) + { + if(text == NULL) + { + mi.dwTypeData = buf; + mi.cch++; + GetMenuItemInfoA(mData->mMenu, i, TRUE, &mi); + + // [tom, 5/11/2007] Don't do anything if the menu item is disabled + if(mi.fState & MFS_DISABLED) + return false; + + text = StringTable->insert(mi.dwTypeData); + } + pos = i; + break; + } + } + } + + if(pos == -1) + { + Con::errorf("PopupMenu::handleSelect - Could not find menu item position for ID %d ... this shouldn't happen!", command); + return false; + } + + // [tom, 8/20/2006] Wasn't handled by a submenu, pass off to script + return dAtob(Con::executef(this, "onSelectItem", Con::getIntArg(pos), text ? text : "")); +} + +////////////////////////////////////////////////////////////////////////// + +void PopupMenu::showPopup(GuiCanvas *owner, S32 x /* = -1 */, S32 y /* = -1 */) +{ + if( owner == NULL ) + { + Con::warnf("PopupMenu::showPopup - Invalid canvas supplied!"); + return; + } + + // [tom, 6/4/2007] showPopup() blocks until the menu is closed by the user, + // so the canvas pointer is not needed beyond the scope of this function + // when working with context menus. Setting mCanvas here will cause undesired + // behavior in relation to the menu bar. + + Win32Window *pWindow = dynamic_cast(owner->getPlatformWindow()); + if(pWindow == NULL) + return; + HWND hWindow = pWindow->getHWND(); + POINT p; + if(x == -1 && y == -1) + GetCursorPos(&p); + else + { + p.x = x; + p.y = y; + ClientToScreen(hWindow, &p); + } + + winState.renderThreadBlocked = true; + U32 opt = (int)TrackPopupMenu(mData->mMenu, TPM_NONOTIFY|TPM_RETURNCMD, p.x, p.y, 0, hWindow, NULL); + if(opt > 0) + handleSelect(opt, NULL); + winState.renderThreadBlocked = false; +} + +////////////////////////////////////////////////////////////////////////// + +void PopupMenu::attachToMenuBar(GuiCanvas *owner, S32 pos, const char *title) +{ + if(owner == NULL || isAttachedToMenuBar()) + return; + + // This is set for sub-menus in the onAttachToMenuBar() callback + mCanvas = owner; + + Win32Window *pWindow = dynamic_cast(owner->getPlatformWindow()); + if(pWindow == NULL) + return; + + HMENU hWindowMenu = pWindow->getMenuHandle(); + if(hWindowMenu == NULL) + { + hWindowMenu = CreateMenu(); + if(hWindowMenu) + { + SetMenu(pWindow->getHWND(), hWindowMenu); + pWindow->setMenuHandle( hWindowMenu); + } + } + + MENUITEMINFOA mii; + + mii.cbSize = sizeof(MENUITEMINFOA); + + mii.fMask = MIIM_STRING|MIIM_DATA; + mii.dwTypeData = (LPSTR)title; + mii.fMask |= MIIM_ID; + mii.wID = mData->mMenuID; + mii.fMask |= MIIM_SUBMENU; + mii.hSubMenu = mData->mMenu; + mii.dwItemData = (ULONG_PTR)this; + + InsertMenuItemA(hWindowMenu, pos, TRUE, &mii); + + HWND hWindow = pWindow->getHWND(); + DrawMenuBar(hWindow); + + pWindow->addAccelerators(mData->mAccelerators); + + // Add accelerators for sub menus + for(SimSet::iterator i = mSubmenus->begin();i != mSubmenus->end();++i) + { + PopupMenu *submenu = dynamic_cast(*i); + if(submenu == NULL) + continue; + + pWindow->addAccelerators(submenu->mData->mAccelerators); + } + + onAttachToMenuBar(owner, pos, title); +} + +// New version of above for use by MenuBar class. Do not use yet. +void PopupMenu::attachToMenuBar(GuiCanvas *owner, S32 pos) +{ + Win32Window *pWindow = dynamic_cast(owner->getPlatformWindow()); + if(pWindow == NULL) + return; + + HMENU hWindowMenu = pWindow->getMenuHandle(); + + MENUITEMINFOA mii; + + mii.cbSize = sizeof(MENUITEMINFOA); + + mii.fMask = MIIM_STRING|MIIM_DATA; + mii.dwTypeData = (LPSTR)mBarTitle; + mii.fMask |= MIIM_ID; + mii.wID = mData->mMenuID; + mii.fMask |= MIIM_SUBMENU; + mii.hSubMenu = mData->mMenu; + mii.dwItemData = (ULONG_PTR)this; + + InsertMenuItemA(hWindowMenu, pos, TRUE, &mii); + + pWindow->addAccelerators(mData->mAccelerators); + + // Add accelerators for sub menus (have to do this here as it's platform specific) + for(SimSet::iterator i = mSubmenus->begin();i != mSubmenus->end();++i) + { + PopupMenu *submenu = dynamic_cast(*i); + if(submenu == NULL) + continue; + + pWindow->addAccelerators(submenu->mData->mAccelerators); + } + + onAttachToMenuBar(owner, pos, mBarTitle); +} + +void PopupMenu::removeFromMenuBar() +{ + S32 pos = getPosOnMenuBar(); + if(pos == -1) + return; + + Win32Window *pWindow = mCanvas ? dynamic_cast(mCanvas->getPlatformWindow()) : NULL; + if(pWindow == NULL) + return; + + HMENU hMenuHandle = pWindow->getMenuHandle(); + if(!hMenuHandle) + return; + + RemoveMenu(hMenuHandle, pos, MF_BYPOSITION); + + HWND hWindow = pWindow->getHWND(); + + DrawMenuBar(hWindow); + + pWindow->removeAccelerators(mData->mAccelerators); + + // Remove accelerators for sub menus + for(SimSet::iterator i = mSubmenus->begin();i != mSubmenus->end();++i) + { + PopupMenu *submenu = dynamic_cast(*i); + if(submenu == NULL) + continue; + + pWindow->removeAccelerators(submenu->mData->mAccelerators); + } + + onRemoveFromMenuBar(mCanvas); +} + +S32 PopupMenu::getPosOnMenuBar() +{ + if(mCanvas == NULL) + return -1; + + Win32Window *pWindow = mCanvas ? dynamic_cast(mCanvas->getPlatformWindow()) : NULL; + if(pWindow == NULL) + return -1; + + HMENU hMenuHandle = pWindow->getMenuHandle(); + S32 numItems = GetMenuItemCount(hMenuHandle); + S32 pos = -1; + for(S32 i = 0;i < numItems;i++) + { + MENUITEMINFOA mi; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_DATA; + if(GetMenuItemInfoA(hMenuHandle, i, TRUE, &mi)) + { + if(mi.fMask & MIIM_DATA) + { + PopupMenu *mnu = (PopupMenu *)mi.dwItemData; + if(mnu == this) + { + pos = i; + break; + } + } + } + } + + return pos; +} + diff --git a/platformWin32/nativeDialogs/fileDialog.cpp b/platformWin32/nativeDialogs/fileDialog.cpp new file mode 100644 index 0000000..2a76390 --- /dev/null +++ b/platformWin32/nativeDialogs/fileDialog.cpp @@ -0,0 +1,678 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/simBase.h" +#include "platform/nativeDialogs/fileDialog.h" +#include "platform/threads/mutex.h" +#include "platformWin32/platformWin32.h" +#include "core/util/safeDelete.h" +#include "math/mMath.h" +#include "core/strings/unicode.h" +#include "console/consoleTypes.h" +#include "platform/profiler.h" +#include +#include + +#ifdef TORQUE_TOOLS +//----------------------------------------------------------------------------- +// PlatformFileDlgData Implementation +//----------------------------------------------------------------------------- +FileDialogData::FileDialogData() +{ + // Default Path + // + // Try to provide consistent experience by recalling the last file path + // - else + // Default to Working Directory if last path is not set or is invalid + mDefaultPath = StringTable->insert( Con::getVariable("Tools::FileDialogs::LastFilePath") ); + if( mDefaultPath == StringTable->lookup("") || !Platform::isDirectory( mDefaultPath ) ) + mDefaultPath = Platform::getCurrentDirectory(); + + mDefaultFile = StringTable->insert(""); + mFilters = StringTable->insert(""); + mFile = StringTable->insert(""); + mTitle = StringTable->insert(""); + + mStyle = 0; + +} +FileDialogData::~FileDialogData() +{ + +} + +static LRESULT PASCAL OKBtnFolderHackProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + WNDPROC oldProc = (WNDPROC)GetProp(hWnd, dT("OldWndProc")); + + switch(uMsg) + { + case WM_COMMAND: + if(LOWORD(wParam) == IDOK) + { + LPOPENFILENAME ofn = (LPOPENFILENAME)GetProp(hWnd, dT("OFN")); + if(ofn == NULL) + break; + + SendMessage(hWnd, CDM_GETFILEPATH, ofn->nMaxFile, (LPARAM)ofn->lpstrFile); + + char *filePath; +#ifdef UNICODE + char fileBuf[MAX_PATH]; + convertUTF16toUTF8(ofn->lpstrFile, fileBuf, sizeof(fileBuf)); + filePath = fileBuf; +#else + filePath = ofn->lpstrFile; +#endif + + if(Platform::isDirectory(filePath)) + { + // Got a directory + EndDialog(hWnd, IDOK); + } + } + break; + } + + if(oldProc) + return CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + else + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +static UINT_PTR CALLBACK FolderHookProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam){ + HWND hParent = GetParent(hdlg); + + switch(uMsg) + { + case WM_INITDIALOG: + { + LPOPENFILENAME lpofn = (LPOPENFILENAME)lParam; + + SendMessage(hParent, CDM_SETCONTROLTEXT, stc3, (LPARAM)dT("Folder name:")); + SendMessage(hParent, CDM_HIDECONTROL, cmb1, 0); + SendMessage(hParent, CDM_HIDECONTROL, stc2, 0); + + LONG oldProc = SetWindowLong(hParent, GWL_WNDPROC, (LONG)OKBtnFolderHackProc); + SetProp(hParent, dT("OldWndProc"), (HANDLE)oldProc); + SetProp(hParent, dT("OFN"), (HANDLE)lpofn); + } + break; + + case WM_NOTIFY: + { + LPNMHDR nmhdr = (LPNMHDR)lParam; + switch(nmhdr->code) + { + case CDN_FOLDERCHANGE: + { + LPOFNOTIFY lpofn = (LPOFNOTIFY)lParam; + + OpenFolderDialog *ofd = (OpenFolderDialog *)lpofn->lpOFN->lCustData; + +#ifdef UNICODE + UTF16 buf[MAX_PATH]; +#else + char buf[MAX_PATH]; +#endif + + SendMessage(hParent, CDM_GETFOLDERPATH, sizeof(buf), (LPARAM)buf); + + char filePath[MAX_PATH]; +#ifdef UNICODE + convertUTF16toUTF8(buf, filePath, sizeof(filePath)); +#else + dStrcpy( filePath, buf ); +#endif + + // [tom, 12/8/2006] Hack to remove files from the list because + // CDN_INCLUDEITEM doesn't work for regular files and folders. + HWND shellView = GetDlgItem(hParent, lst2); + HWND listView = FindWindowEx(shellView, 0, WC_LISTVIEW, NULL); + if(listView) + { + S32 count = ListView_GetItemCount(listView); + for(S32 i = count - 1;i >= 0;--i) + { + ListView_GetItemText(listView, i, 0, buf, sizeof(buf)); + +#ifdef UNICODE + char buf2[MAX_PATH]; + convertUTF16toUTF8(buf, buf2, sizeof(buf2)); +#else + char *buf2 = buf; +#endif + char full[MAX_PATH]; + dSprintf(full, sizeof(full), "%s\\%s", filePath, buf2); + + if(!Platform::isDirectory(full)) + { + ListView_DeleteItem(listView, i); + } + } + } + + if(ofd->mMustExistInDir == NULL || *ofd->mMustExistInDir == 0) + break; + + HWND hOK = GetDlgItem(hParent, IDOK); + if(hOK == NULL) + break; + + char checkPath[MAX_PATH]; + dSprintf(checkPath, sizeof(checkPath), "%s\\%s", filePath, ofd->mMustExistInDir); + + EnableWindow(hOK, Platform::isFile(checkPath)); + } + break; + } + } + break; + } + return 0; +} + +//----------------------------------------------------------------------------- +// FileDialog Implementation +//----------------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(FileDialog); + +FileDialog::FileDialog() : mData() +{ + // Default to File Must Exist Open Dialog style + mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST; + mChangePath = false; +} + +FileDialog::~FileDialog() +{ +} + +void FileDialog::initPersistFields() +{ + addProtectedField( "defaultPath", TypeString, Offset(mData.mDefaultPath, FileDialog), &setDefaultPath, &defaultProtectedGetFn, + "The default directory path when the dialog is shown." ); + + addProtectedField( "defaultFile", TypeString, Offset(mData.mDefaultFile, FileDialog), &setDefaultFile, &defaultProtectedGetFn, + "The default file path when the dialog is shown." ); + + addProtectedField( "fileName", TypeString, Offset(mData.mFile, FileDialog), &setFile, &defaultProtectedGetFn, + "The default file name when the dialog is shown." ); + + addProtectedField( "filters", TypeString, Offset(mData.mFilters, FileDialog), &setFilters, &defaultProtectedGetFn, + "The filter string for limiting the types of files visible in the dialog. For example:\n\n" + "\t 'All Files|*.*'\n" + "\t 'Image Files|*.png;*.jpg|Png Files|*.png|Jepg Files|*.jpg'" ); + + addField( "title", TypeString, Offset(mData.mTitle, FileDialog), + "The title for the dialog." ); + + addProtectedField( "changePath", TypeBool, Offset(mChangePath, FileDialog), &setChangePath, &getChangePath, + "True/False whether to set the working directory to the directory returned by the dialog." ); + + Parent::initPersistFields(); +} + +static const U32 convertUTF16toUTF8DoubleNULL( const UTF16 *unistring, UTF8 *outbuffer, U32 len) +{ + AssertFatal(len >= 1, "Buffer for unicode conversion must be large enough to hold at least the null terminator."); + PROFILE_START(convertUTF16toUTF8DoubleNULL); + U32 walked, nCodeunits, codeunitLen; + UTF32 middleman; + + nCodeunits=0; + while( ! (*unistring == '\0' && *(unistring + 1) == '\0') && nCodeunits + 3 < len ) + { + walked = 1; + middleman = oneUTF16toUTF32(unistring,&walked); + codeunitLen = oneUTF32toUTF8(middleman, &outbuffer[nCodeunits]); + unistring += walked; + nCodeunits += codeunitLen; + } + + nCodeunits = getMin(nCodeunits,len - 1); + outbuffer[nCodeunits] = '\0'; + outbuffer[nCodeunits+1] = '\0'; + + PROFILE_END(); + return nCodeunits; +} + +// +// Execute Method +// +bool FileDialog::Execute() +{ + static char pszResult[MAX_PATH]; +#ifdef UNICODE + UTF16 pszFile[MAX_PATH]; + UTF16 pszInitialDir[MAX_PATH]; + UTF16 pszTitle[MAX_PATH]; + UTF16 pszFilter[1024]; + UTF16 pszFileTitle[MAX_PATH]; + // Convert parameters to UTF16*'s + convertUTF8toUTF16((UTF8 *)mData.mDefaultFile, pszFile, sizeof(pszFile)); + convertUTF8toUTF16((UTF8 *)mData.mDefaultPath, pszInitialDir, sizeof(pszInitialDir)); + convertUTF8toUTF16((UTF8 *)mData.mTitle, pszTitle, sizeof(pszTitle)); + convertUTF8toUTF16((UTF8 *)mData.mFilters, pszFilter, sizeof(pszFilter) ); +#else + // Not Unicode, All char*'s! + char pszFile[MAX_PATH]; + char pszFilter[1024]; + char pszFileTitle[MAX_PATH]; + dStrcpy( pszFile, mData.mDefaultFile ); + dStrcpy( pszFilter, mData.mFilters ); + const char* pszInitialDir = mData.mDefaultPath; + const char* pszTitle = mData.mTitle; + +#endif + + pszFileTitle[0] = 0; + + // Convert Filters + U32 filterLen = dStrlen( pszFilter ); + for( U32 i = 0; i < filterLen; i++ ) + { + if( pszFilter[i] == '|' ) + pszFilter[i] = '\0'; + } + // Add second NULL terminator at the end + pszFilter[ filterLen + 1 ] = '\0'; + + OPENFILENAME ofn; + dMemset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = getWin32WindowHandle(); + ofn.lpstrFile = pszFile; + + + if( !dStrncmp( mData.mDefaultFile, "", 1 ) ) + ofn.lpstrFile[0] = '\0'; + + + ofn.nMaxFile = sizeof(pszFile); + ofn.lpstrFilter = pszFilter; + ofn.nFilterIndex = 1; + ofn.lpstrInitialDir = pszInitialDir; + ofn.lCustData = (LPARAM)this; + ofn.lpstrFileTitle = pszFileTitle; + ofn.nMaxFileTitle = sizeof(pszFileTitle); + + if( mData.mTitle != StringTable->lookup("") ) + ofn.lpstrTitle = pszTitle; + + // Build Proper Flags. + ofn.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_HIDEREADONLY; + + if(mData.mStyle & FileDialogData::FDS_BROWSEFOLDER) + { + ofn.lpfnHook = FolderHookProc; + ofn.Flags |= OFN_ENABLEHOOK; + } + + if( !(mData.mStyle & FileDialogData::FDS_CHANGEPATH) ) + ofn.Flags |= OFN_NOCHANGEDIR; + + if( mData.mStyle & FileDialogData::FDS_MUSTEXIST ) + ofn.Flags |= OFN_FILEMUSTEXIST; + + if( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ) + ofn.Flags |= OFN_ALLOWMULTISELECT; + + if( mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT ) + ofn.Flags |= OFN_OVERWRITEPROMPT; + + + // Flag we're showing file browser so we can do some render hacking + winState.renderThreadBlocked = true; + + // Get the current working directory, so we can back up to it once Windows has + // done its craziness and messed with it. + StringTableEntry cwd = Platform::getCurrentDirectory(); + + // Execute Dialog (Blocking Call) + bool dialogSuccess = false; + if( mData.mStyle & FileDialogData::FDS_OPEN ) + dialogSuccess = GetOpenFileName(&ofn); + else if( mData.mStyle & FileDialogData::FDS_SAVE ) + dialogSuccess = GetSaveFileName(&ofn); + + // Dialog is gone. + winState.renderThreadBlocked = false; + + // Restore the working directory. + Platform::setCurrentDirectory( cwd ); + + // Did we select a file? + if( !dialogSuccess ) + return false; + + // Handle Result Properly for Unicode as well as ANSI +#ifdef UNICODE + if(pszFileTitle[0] || ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )) + convertUTF16toUTF8( (UTF16*)pszFile, (UTF8*)pszResult, sizeof(pszResult)); + else + convertUTF16toUTF8DoubleNULL( (UTF16*)pszFile, (UTF8*)pszResult, sizeof(pszResult)); +#else + if(pszFileTitle[0] || ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )) + dStrcpy(pszResult,pszFile); + else + { + // [tom, 1/4/2007] pszResult is a double-NULL terminated, NULL separated list in this case so we can't just dSstrcpy() + char *sptr = pszFile, *dptr = pszResult; + while(! (*sptr == 0 && *(sptr+1) == 0)) + *dptr++ = *sptr++; + *dptr++ = 0; + } +#endif + + forwardslash(pszResult); + + // [tom, 1/5/2007] Windows is ridiculously dumb. If you select a single file in a multiple + // select file dialog then it will return the file the same way as it would in a single + // select dialog. The only difference is pszFileTitle is empty if multiple files + // are selected. + + // Store the result on our object + if( mData.mStyle & FileDialogData::FDS_BROWSEFOLDER || ( pszFileTitle[0] && ! ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ))) + { + // Single file selection, do it the easy way + mData.mFile = StringTable->insert( pszResult ); + } + else if(pszFileTitle[0] && ( mData.mStyle & FileDialogData::FDS_OPEN && mData.mStyle & FileDialogData::FDS_MULTIPLEFILES )) + { + // Single file selection in a multiple file selection dialog + setDataField(StringTable->insert("files"), "0", pszResult); + setDataField(StringTable->insert("fileCount"), NULL, "1"); + } + else + { + // Multiple file selection, break out into an array + S32 numFiles = 0; + const char *dir = pszResult; + const char *file = dir + dStrlen(dir) + 1; + char buffer[1024]; + + while(*file) + { + Platform::makeFullPathName(file, buffer, sizeof(buffer), dir); + setDataField(StringTable->insert("files"), Con::getIntArg(numFiles++), buffer); + + file = file + dStrlen(file) + 1; + } + + setDataField(StringTable->insert("fileCount"), NULL, Con::getIntArg(numFiles)); + } + + // Return success. + return true; + +} + +ConsoleMethod( FileDialog, Execute, bool, 2, 2, "%fileDialog.Execute();" ) +{ + return object->Execute(); +} + +//----------------------------------------------------------------------------- +// Dialog Filters +//----------------------------------------------------------------------------- +bool FileDialog::setFilters(void* obj, const char* data) +{ + // Will do validate on write at some point. + if( !data ) + return true; + + return true; + +}; + + +//----------------------------------------------------------------------------- +// Default Path Property - String Validated on Write +//----------------------------------------------------------------------------- +bool FileDialog::setDefaultPath(void* obj, const char* data) +{ + + if( !data || !dStrncmp( data, "", 1 ) ) + return true; + + // Copy and Backslash the path (Windows dialogs are VERY picky about this format) + static char szPathValidate[512]; + dStrcpy( szPathValidate, data ); + + Platform::makeFullPathName( data,szPathValidate, sizeof(szPathValidate)); + backslash( szPathValidate ); + + // Remove any trailing \'s + S8 validateLen = dStrlen( szPathValidate ); + if( szPathValidate[ validateLen - 1 ] == '\\' ) + szPathValidate[ validateLen - 1 ] = '\0'; + + // Now check + if( Platform::isDirectory( szPathValidate ) ) + { + // Finally, assign in proper format. + FileDialog *pDlg = static_cast( obj ); + pDlg->mData.mDefaultPath = StringTable->insert( szPathValidate ); + } +#ifdef TORQUE_DEBUG + else + Con::errorf(ConsoleLogEntry::GUI, "FileDialog - Invalid Default Path Specified!"); +#endif + + return false; + +}; + +//----------------------------------------------------------------------------- +// Default File Property - String Validated on Write +//----------------------------------------------------------------------------- +bool FileDialog::setDefaultFile(void* obj, const char* data) +{ + if( !data || !dStrncmp( data, "", 1 ) ) + return true; + + // Copy and Backslash the path (Windows dialogs are VERY picky about this format) + static char szPathValidate[512]; + Platform::makeFullPathName( data,szPathValidate, sizeof(szPathValidate) ); + backslash( szPathValidate ); + + // Remove any trailing \'s + S8 validateLen = dStrlen( szPathValidate ); + if( szPathValidate[ validateLen - 1 ] == '\\' ) + szPathValidate[ validateLen - 1 ] = '\0'; + + // Finally, assign in proper format. + FileDialog *pDlg = static_cast( obj ); + pDlg->mData.mDefaultFile = StringTable->insert( szPathValidate ); + + return false; +}; + +//----------------------------------------------------------------------------- +// ChangePath Property - Change working path on successful file selection +//----------------------------------------------------------------------------- +bool FileDialog::setChangePath(void* obj, const char* data) +{ + bool bMustExist = dAtob( data ); + + FileDialog *pDlg = static_cast( obj ); + + if( bMustExist ) + pDlg->mData.mStyle |= FileDialogData::FDS_CHANGEPATH; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_CHANGEPATH; + + return true; +}; + +const char* FileDialog::getChangePath(void* obj, const char* data) +{ + FileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_CHANGEPATH ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +bool FileDialog::setFile(void* obj, const char* data) +{ + return false; +}; + +//----------------------------------------------------------------------------- +// OpenFileDialog Implementation +//----------------------------------------------------------------------------- +OpenFileDialog::OpenFileDialog() +{ + // Default File Must Exist + mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_MUSTEXIST; +} + +OpenFileDialog::~OpenFileDialog() +{ + mMustExist = true; + mMultipleFiles = false; +} + +IMPLEMENT_CONOBJECT(OpenFileDialog); + +//----------------------------------------------------------------------------- +// Console Properties +//----------------------------------------------------------------------------- +void OpenFileDialog::initPersistFields() +{ + addProtectedField("MustExist", TypeBool, Offset(mMustExist, OpenFileDialog), &setMustExist, &getMustExist, "True/False whether the file returned must exist or not" ); + addProtectedField("MultipleFiles", TypeBool, Offset(mMultipleFiles, OpenFileDialog), &setMultipleFiles, &getMultipleFiles, "True/False whether multiple files may be selected and returned or not" ); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// File Must Exist - Boolean +//----------------------------------------------------------------------------- +bool OpenFileDialog::setMustExist(void* obj, const char* data) +{ + bool bMustExist = dAtob( data ); + + OpenFileDialog *pDlg = static_cast( obj ); + + if( bMustExist ) + pDlg->mData.mStyle |= FileDialogData::FDS_MUSTEXIST; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_MUSTEXIST; + + return true; +}; + +const char* OpenFileDialog::getMustExist(void* obj, const char* data) +{ + OpenFileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_MUSTEXIST ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +//----------------------------------------------------------------------------- +// Can Select Multiple Files - Boolean +//----------------------------------------------------------------------------- +bool OpenFileDialog::setMultipleFiles(void* obj, const char* data) +{ + bool bMustExist = dAtob( data ); + + OpenFileDialog *pDlg = static_cast( obj ); + + if( bMustExist ) + pDlg->mData.mStyle |= FileDialogData::FDS_MULTIPLEFILES; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_MULTIPLEFILES; + + return true; +}; + +const char* OpenFileDialog::getMultipleFiles(void* obj, const char* data) +{ + OpenFileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_MULTIPLEFILES ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +//----------------------------------------------------------------------------- +// SaveFileDialog Implementation +//----------------------------------------------------------------------------- +SaveFileDialog::SaveFileDialog() +{ + // Default File Must Exist + mData.mStyle = FileDialogData::FDS_SAVE | FileDialogData::FDS_OVERWRITEPROMPT; + mOverwritePrompt = true; +} + +SaveFileDialog::~SaveFileDialog() +{ +} + +IMPLEMENT_CONOBJECT(SaveFileDialog); + +//----------------------------------------------------------------------------- +// Console Properties +//----------------------------------------------------------------------------- +void SaveFileDialog::initPersistFields() +{ + addProtectedField("OverwritePrompt", TypeBool, Offset(mOverwritePrompt, SaveFileDialog), &setOverwritePrompt, &getOverwritePrompt, "True/False whether the dialog should prompt before accepting an existing file name" ); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// Prompt on Overwrite - Boolean +//----------------------------------------------------------------------------- +bool SaveFileDialog::setOverwritePrompt(void* obj, const char* data) +{ + bool bMustExist = dAtob( data ); + + SaveFileDialog *pDlg = static_cast( obj ); + + if( bMustExist ) + pDlg->mData.mStyle |= FileDialogData::FDS_OVERWRITEPROMPT; + else + pDlg->mData.mStyle &= ~FileDialogData::FDS_OVERWRITEPROMPT; + + return true; +}; + +const char* SaveFileDialog::getOverwritePrompt(void* obj, const char* data) +{ + SaveFileDialog *pDlg = static_cast( obj ); + if( pDlg->mData.mStyle & FileDialogData::FDS_OVERWRITEPROMPT ) + return StringTable->insert("true"); + else + return StringTable->insert("false"); +} + +//----------------------------------------------------------------------------- +// OpenFolderDialog Implementation +//----------------------------------------------------------------------------- + +OpenFolderDialog::OpenFolderDialog() +{ + mData.mStyle = FileDialogData::FDS_OPEN | FileDialogData::FDS_OVERWRITEPROMPT | FileDialogData::FDS_BROWSEFOLDER; + + mMustExistInDir = ""; +} + +IMPLEMENT_CONOBJECT(OpenFolderDialog); + +void OpenFolderDialog::initPersistFields() +{ + addField("fileMustExist", TypeFilename, Offset(mMustExistInDir, OpenFolderDialog), "File that must in selected folder for it to be valid"); + + Parent::initPersistFields(); +} + +#endif \ No newline at end of file diff --git a/platformWin32/nativeDialogs/win32MsgBox.cpp b/platformWin32/nativeDialogs/win32MsgBox.cpp new file mode 100644 index 0000000..a6cb857 --- /dev/null +++ b/platformWin32/nativeDialogs/win32MsgBox.cpp @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platformWin32/platformWin32.h" +#include "platform/platformInput.h" +#include "platform/nativeDialogs/msgBox.h" + +#include "console/console.h" +#include "core/strings/unicode.h" + +#include "windowManager/platformWindowMgr.h" +#include "windowManager/win32/win32Window.h" + +struct _FlagMap +{ + S32 num; + U32 flag; +}; + +static _FlagMap sgButtonMap[] = +{ + { MBOk, MB_OK }, + { MBOkCancel, MB_OKCANCEL }, + { MBRetryCancel, MB_RETRYCANCEL }, + { MBSaveDontSave, MB_YESNO }, + { MBSaveDontSaveCancel, MB_YESNOCANCEL }, + { 0xffffffff, 0xffffffff } +}; + +static _FlagMap sgIconMap[] = +{ + { MIWarning, MB_ICONWARNING }, + { MIInformation, MB_ICONINFORMATION }, + { MIQuestion, MB_ICONQUESTION }, + { MIStop, MB_ICONSTOP }, + { 0xffffffff, 0xffffffff } +}; + +static _FlagMap sgMsgBoxRetMap[] = +{ + { IDCANCEL, MRCancel }, + { IDNO, MRDontSave }, + { IDOK, MROk}, + { IDRETRY, MRRetry }, + { IDYES, MROk }, + { 0xffffffff, 0xffffffff } +}; + +//----------------------------------------------------------------------------- + +static U32 getMaskFromID(_FlagMap *map, S32 id) +{ + for(S32 i = 0;map[i].num != 0xffffffff && map[i].flag != 0xffffffff;++i) + { + if(map[i].num == id) + return map[i].flag; + } + + return 0; +} + +//----------------------------------------------------------------------------- + +S32 Platform::messageBox(const UTF8 *title, const UTF8 *message, MBButtons buttons, MBIcons icon) +{ + PlatformWindow *pWindow = WindowManager->getFirstWindow(); + + // Get us rendering while we're blocking. + winState.renderThreadBlocked = true; + + // We don't keep a locked mouse or else we're going + // to end up possibly locking our mouse out of the + // message box area + bool cursorLocked = pWindow && pWindow->isMouseLocked(); + if( cursorLocked ) + pWindow->setMouseLocked( false ); + + // Need a visible cursor to click stuff accurately + bool cursorVisible = !pWindow || pWindow->isCursorVisible(); + if( !cursorVisible ) + pWindow->setCursorVisible(true); + +#ifdef UNICODE + const UTF16 *msg = convertUTF8toUTF16(message); + const UTF16 *t = convertUTF8toUTF16(title); +#else + const UTF8 *msg = message; + const UTF8 *t = title; +#endif + + HWND parent = pWindow ? static_cast(pWindow)->getHWND() : NULL; + S32 ret = ::MessageBox( parent, msg, t, getMaskFromID(sgButtonMap, buttons) | getMaskFromID(sgIconMap, icon)); + +#ifdef UNICODE + delete [] msg; + delete [] t; +#endif + + // Dialog is gone. + winState.renderThreadBlocked = false; + + if( cursorVisible == false ) + pWindow->setCursorVisible( false ); + + if( cursorLocked == true ) + pWindow->setMouseLocked( true ); + + return getMaskFromID(sgMsgBoxRetMap, ret); +} diff --git a/platformWin32/platformWin32.h b/platformWin32/platformWin32.h new file mode 100644 index 0000000..8a412f6 --- /dev/null +++ b/platformWin32/platformWin32.h @@ -0,0 +1,121 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMWIN32_H_ +#define _PLATFORMWIN32_H_ + +// Sanity check for UNICODE +#ifdef TORQUE_UNICODE +# ifndef UNICODE +# error "ERROR: You must have UNICODE defined in your preprocessor settings (ie, /DUNICODE) if you have TORQUE_UNICODE enabled in torqueConfig.h!" +# endif +#endif + +#include +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif + +#if defined(TORQUE_COMPILER_CODEWARRIOR) +# include +# include +# include +#else +# include +# include +#endif + +#ifdef _MSC_VER +#pragma warning(disable: 4996) // turn off "deprecation" warnings +#endif + +#define NOMINMAX + +// Hack to get a correct HWND instead of using global state. +extern HWND getWin32WindowHandle(); + +struct Win32PlatState +{ + FILE *log_fp; + HINSTANCE hinstOpenGL; + HINSTANCE hinstGLU; + HINSTANCE hinstOpenAL; + HWND appWindow; + HDC appDC; + HINSTANCE appInstance; + HGLRC hGLRC; + DWORD processId; + bool renderThreadBlocked; + S32 nMessagesPerFrame; ///< The max number of messages to dispatch per frame + HMENU appMenu; ///< The menu bar for the window +#ifdef UNICODE + //HIMC imeHandle; +#endif + + S32 desktopBitsPixel; + S32 desktopWidth; + S32 desktopHeight; + S32 desktopClientWidth; + S32 desktopClientHeight; + U32 currentTime; + + // minimum time per frame + U32 sleepTicks; + // are we in the background? + bool backgrounded; + + Win32PlatState(); +}; + +extern Win32PlatState winState; + +extern void setModifierKeys( S32 modKeys ); + +//-------------------------------------- Helper Functions + +template< typename T > +inline void forwardslashT( T *str ) +{ + while(*str) + { + if(*str == '\\') + *str = '/'; + str++; + } +} + +inline void forwardslash( char* str ) +{ + forwardslashT< char >( str ); +} +inline void forwardslash( WCHAR* str ) +{ + forwardslashT< WCHAR >( str ); +} + +template< typename T > +inline void backslashT( T *str ) +{ + while(*str) + { + if(*str == '/') + *str = '\\'; + str++; + } +} + +inline void backslash( char* str ) +{ + backslashT< char >( str ); +} +inline void backslash( WCHAR* str ) +{ + backslashT< WCHAR >( str ); +} + +#endif //_PLATFORMWIN32_H_ diff --git a/platformWin32/threads/mutex.cpp b/platformWin32/threads/mutex.cpp new file mode 100644 index 0000000..beb52e3 --- /dev/null +++ b/platformWin32/threads/mutex.cpp @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/threads/mutex.h" +#include "platformWin32/platformWin32.h" +#include "core/util/safeDelete.h" + +//----------------------------------------------------------------------------- +// Mutex Data +//----------------------------------------------------------------------------- + +struct PlatformMutexData +{ + HANDLE mMutex; + + PlatformMutexData() + { + mMutex = NULL; + } +}; + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +Mutex::Mutex() +{ + mData = new PlatformMutexData; + + mData->mMutex = CreateMutex(NULL, FALSE, NULL); +} + +Mutex::~Mutex() +{ + if(mData && mData->mMutex) + CloseHandle(mData->mMutex); + + SAFE_DELETE(mData); +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +bool Mutex::lock(bool block /* = true */) +{ + if(mData == NULL || mData->mMutex == NULL) + return false; + + return (bool)WaitForSingleObject(mData->mMutex, block ? INFINITE : 0) == WAIT_OBJECT_0; +} + +void Mutex::unlock() +{ + if(mData == NULL || mData->mMutex == NULL) + return; + + ReleaseMutex(mData->mMutex); +} + +//void Mutex::set( void*data ) +//{ +// if(mData && mData->mMutex) +// CloseHandle(mData->mMutex); +// +// if( mData == NULL ) +// mData = new PlatformMutexData; +// +// mData->mMutex = (HANDLE)data; +// +//} \ No newline at end of file diff --git a/platformWin32/threads/thread.cpp b/platformWin32/threads/thread.cpp new file mode 100644 index 0000000..b01eb33 --- /dev/null +++ b/platformWin32/threads/thread.cpp @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef TORQUE_OS_XENON +#include "platformWin32/platformWin32.h" +#endif +#include "platform/threads/thread.h" +#include "platform/threads/semaphore.h" +#include "platform/platformIntrinsics.h" +#include "core/util/safeDelete.h" + +#include // [tom, 4/20/2006] for _beginthread() + +ThreadManager::MainThreadId ThreadManager::smMainThreadId; + +//----------------------------------------------------------------------------- +// Thread data +//----------------------------------------------------------------------------- + +class PlatformThreadData +{ +public: + ThreadRunFunction mRunFunc; + void* mRunArg; + Thread* mThread; + HANDLE mThreadHnd; + Semaphore mGateway; + U32 mThreadID; + U32 mDead; + + PlatformThreadData() + { + mRunFunc = NULL; + mRunArg = 0; + mThread = 0; + mThreadHnd = 0; + mDead = false; + }; +}; + +//----------------------------------------------------------------------------- +// Static Functions/Methods +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Function: ThreadRunHandler +// Summary: Calls Thread::run() with the thread's specified run argument. +// Neccesary because Thread::run() is provided as a non-threaded +// way to execute the thread's run function. So we have to keep +// track of the thread's lock here. +static unsigned int __stdcall ThreadRunHandler(void * arg) +{ + PlatformThreadData* mData = reinterpret_cast(arg); + mData->mThreadID = GetCurrentThreadId(); + + ThreadManager::addThread(mData->mThread); + mData->mThread->run(mData->mRunArg); + ThreadManager::removeThread(mData->mThread); + + bool autoDelete = mData->mThread->autoDelete; + + mData->mThreadHnd = NULL; // mark as dead + dCompareAndSwap( mData->mDead, false, true ); + mData->mGateway.release(); // don't access data after this. + + if( autoDelete ) + delete mData->mThread; // Safe as we own the data. + + _endthreadex( 0 ); + return 0; +} + +//----------------------------------------------------------------------------- +// Constructor/Destructor +//----------------------------------------------------------------------------- + +Thread::Thread(ThreadRunFunction func /* = 0 */, void *arg /* = 0 */, bool start_thread /* = true */, bool autodelete /*= false*/) + : autoDelete( autodelete ) +{ + AssertFatal( !start_thread, "Thread::Thread() - auto-starting threads from ctor has been disallowed since the run() method is virtual" ); + + mData = new PlatformThreadData; + mData->mRunFunc = func; + mData->mRunArg = arg; + mData->mThread = this; +} + +Thread::~Thread() +{ + stop(); + if( isAlive() ) + join(); + + SAFE_DELETE(mData); +} + +//----------------------------------------------------------------------------- +// Public Methods +//----------------------------------------------------------------------------- + +void Thread::start( void* arg ) +{ + AssertFatal( !mData->mThreadHnd, + "Thread::start() - thread already started" ); + + // cause start to block out other pthreads from using this Thread, + // at least until ThreadRunHandler exits. + mData->mGateway.acquire(); + + // reset the shouldStop flag, so we'll know when someone asks us to stop. + shouldStop = false; + + mData->mDead = false; + + if( !mData->mRunArg ) + mData->mRunArg = arg; + + mData->mThreadHnd = (HANDLE)_beginthreadex(0, 0, ThreadRunHandler, mData, 0, 0); +} + +bool Thread::join() +{ + mData->mGateway.acquire(); + AssertFatal( !isAlive(), "Thread::join() - thread still alive after join" ); + mData->mGateway.release(); // release for further joins + return true; +} + +void Thread::run(void *arg /* = 0 */) +{ + if(mData->mRunFunc) + mData->mRunFunc(arg); +} + +bool Thread::isAlive() +{ + return ( !mData->mDead ); +} + +U32 Thread::getId() +{ + return mData->mThreadID; +} + +void Thread::_setName( const char* name ) +{ +#if defined( TORQUE_DEBUG ) && defined( TORQUE_COMPILER_VISUALC ) && defined( TORQUE_OS_WIN32 ) + + // See http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx + + #define MS_VC_EXCEPTION 0x406D1388 + + #pragma pack(push,8) + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + } THREADNAME_INFO; + #pragma pack(pop) + + Sleep(10); + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = getId(); + info.dwFlags = 0; + + __try + { + RaiseException( MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info ); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + } +#endif +} + +U32 ThreadManager::getCurrentThreadId() +{ + return GetCurrentThreadId(); +} + +bool ThreadManager::compare(U32 threadId_1, U32 threadId_2) +{ + return (threadId_1 == threadId_2); +} diff --git a/platformWin32/videoInfo/wmiVideoInfo.cpp b/platformWin32/videoInfo/wmiVideoInfo.cpp new file mode 100644 index 0000000..d9e4413 --- /dev/null +++ b/platformWin32/videoInfo/wmiVideoInfo.cpp @@ -0,0 +1,566 @@ +#define _WIN32_DCOM + +//#include +#include +//#include +#pragma comment(lib, "comsuppw.lib") +#pragma comment(lib, "wbemuuid.lib") + +#include "platformWin32/videoInfo/wmiVideoInfo.h" +#include "core/util/safeRelease.h" +#include "console/console.h" + +// http://www.spectranaut.net/sourcecode/WMI.cpp + +// Add constructor to GUID. +struct MYGUID : public GUID +{ + MYGUID( DWORD a, SHORT b, SHORT c, BYTE d, BYTE e, BYTE f, BYTE g, BYTE h, BYTE i, BYTE j, BYTE k ) + { + Data1 = a; + Data2 = b; + Data3 = c; + Data4[ 0 ] = d; + Data4[ 1 ] = e; + Data4[ 2 ] = f; + Data4[ 3 ] = g; + Data4[ 4 ] = h; + Data4[ 5 ] = i; + Data4[ 6 ] = j; + Data4[ 7 ] = k; + } +}; + +//------------------------------------------------------------------------------ +// DXGI decls for retrieving device info on Vista. We manually declare that +// stuff here, so we don't depend on headers and compile on any setup. At +// run-time, it depends on whether we can successfully load the DXGI DLL; if +// not, nothing of this here will be used. + +struct IDXGIObject; +struct IDXGIFactory; +struct IDXGIAdapter; +struct IDXGIOutput; + +struct DXGI_SWAP_CHAIN_DESC; +struct DXGI_ADAPTER_DESC; + +struct IDXGIObject : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE SetPrivateData( REFGUID, UINT, const void* ) = 0; + virtual HRESULT STDMETHODCALLTYPE SetPrivateDataInterface( REFGUID, const IUnknown* ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetPrivateData( REFGUID, UINT*, void* ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetParent( REFIID, void** ) = 0; +}; + +struct IDXGIFactory : public IDXGIObject +{ + virtual HRESULT STDMETHODCALLTYPE EnumAdapters( UINT, IDXGIAdapter** ) = 0; + virtual HRESULT STDMETHODCALLTYPE MakeWindowAssociation( HWND, UINT ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetWindowAssociation( HWND ) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateSwapChain( IUnknown*, DXGI_SWAP_CHAIN_DESC* ) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateSoftwareAdapter( HMODULE, IDXGIAdapter** ) = 0; +}; + +struct IDXGIAdapter : public IDXGIObject +{ + virtual HRESULT STDMETHODCALLTYPE EnumOutputs( UINT, IDXGIOutput** ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetDesc( DXGI_ADAPTER_DESC* ) = 0; + virtual HRESULT STDMETHODCALLTYPE CheckInterfaceSupport( REFGUID, LARGE_INTEGER* ) = 0; +}; + +struct DXGI_ADAPTER_DESC +{ + WCHAR Description[ 128 ]; + UINT VendorId; + UINT DeviceId; + UINT SubSysId; + UINT Revision; + SIZE_T DedicatedVideoMemory; + SIZE_T DedicatedSystemMemory; + SIZE_T SharedSystemMemory; + LUID AdapterLuid; +}; + +static MYGUID IID_IDXGIFactory( 0x7b7166ec, 0x21c7, 0x44ae, 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69 ); + +//------------------------------------------------------------------------------ +// DXDIAG declarations. + +struct DXDIAG_INIT_PARAMS +{ + DWORD dwSize; + DWORD dwDxDiagHeaderVersion; + BOOL bAllowWHQLChecks; + LPVOID pReserved; +}; + +struct IDxDiagContainer : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE GetNumberOfChildContaiiners( DWORD* pdwCount ) = 0; + virtual HRESULT STDMETHODCALLTYPE EnumChildContainerNames( DWORD dwIndex, LPWSTR pwszContainer, DWORD cchContainer ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetChildContainer( LPCWSTR pwszContainer, IDxDiagContainer** ppInstance ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetNumberOfProps( DWORD* pdwCount ) = 0; + virtual HRESULT STDMETHODCALLTYPE EnumPropNames( DWORD dwIndex, LPWSTR pwszPropName, DWORD cchPropName ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetProp( LPCWSTR pwszPropName, VARIANT* pvarProp ) = 0; +}; + +struct IDxDiagProvider : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE Initialize( DXDIAG_INIT_PARAMS* pParams ) = 0; + virtual HRESULT STDMETHODCALLTYPE GetRootContainer( IDxDiagContainer** ppInstance ) = 0; +}; + +static MYGUID CLSID_DxDiagProvider( 0xA65B8071, 0x3BFE, 0x4213, 0x9A, 0x5B, 0x49, 0x1D, 0xA4, 0x46, 0x1C, 0xA7 ); +static MYGUID IID_IDxDiagProvider( 0x9C6B4CB0, 0x23F8, 0x49CC, 0xA3, 0xED, 0x45, 0xA5, 0x50, 0x00, 0xA6, 0xD2 ); +static MYGUID IID_IDxDiagContainer( 0x7D0F462F, 0x4064, 0x4862, 0xBC, 0x7F, 0x93, 0x3E, 0x50, 0x58, 0xC1, 0x0F ); + +//------------------------------------------------------------------------------ + +WCHAR *WMIVideoInfo::smPVIQueryTypeToWMIString [] = +{ + L"MaxNumberControlled", //PVI_NumDevices + L"Description", //PVI_Description + L"Name", //PVI_Name + L"VideoProcessor", //PVI_ChipSet + L"DriverVersion", //PVI_DriverVersion + L"AdapterRAM", //PVI_VRAM +}; + +//------------------------------------------------------------------------------ + +WMIVideoInfo::WMIVideoInfo() + : PlatformVideoInfo(), + mLocator( NULL ), + mServices( NULL ), + mComInitialized( false ), + mDXGIModule( NULL ), + mDXGIFactory( NULL ), + mDxDiagProvider( NULL ) +{ + +} + +//------------------------------------------------------------------------------ + +WMIVideoInfo::~WMIVideoInfo() +{ + SAFE_RELEASE( mLocator ); + SAFE_RELEASE( mServices ); + + if( mDxDiagProvider ) + SAFE_RELEASE( mDxDiagProvider ); + + if( mDXGIFactory ) + SAFE_RELEASE( mDXGIFactory ); + if( mDXGIModule ) + FreeLibrary( ( HMODULE ) mDXGIModule ); + + if( mComInitialized ) + CoUninitialize(); +} + +//------------------------------------------------------------------------------ + +bool WMIVideoInfo::_initialize() +{ + // Init COM + HRESULT hr = CoInitialize( NULL ); + mComInitialized = SUCCEEDED( hr ); + + if( !mComInitialized ) + return false; + + bool success = false; + + success |= _initializeDXGI(); + success |= _initializeDxDiag(); + success |= _initializeWMI(); + + return success; +} + +bool WMIVideoInfo::_initializeWMI() +{ + //// Set security levels + //hr = CoInitializeSecurity( + // NULL, + // -1, // COM authentication + // NULL, // Authentication services + // NULL, // Reserved + // RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication + // RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation + // NULL, // Authentication info + // EOAC_NONE, // Additional capabilities + // NULL // Reserved + // ); + + //if( FAILED( hr ) ) + //{ + // Con::errorf( "WMIVideoInfo: Failed to initialize com security." ); + // return false; + //} + + // Obtain the locator to WMI + HRESULT hr = CoCreateInstance( + CLSID_WbemLocator, + 0, + CLSCTX_INPROC_SERVER, + IID_IWbemLocator, + (void**)&mLocator + ); + + if( FAILED( hr ) ) + { + Con::errorf( "WMIVideoInfo: Failed to create instance of IID_IWbemLocator." ); + return false; + } + + // Connect to the root\cimv2 namespace with + // the current user and obtain pointer pSvc + // to make IWbemServices calls. + hr = mLocator->ConnectServer( + BSTR(L"ROOT\\CIMV2"), // Object path of WMI namespace + NULL, // User name. NULL = current user + NULL, // User password. NULL = current + 0, // Locale. NULL indicates current + NULL, // Security flags. + 0, // Authority (e.g. Kerberos) + 0, // Context object + &mServices // pointer to IWbemServices proxy + ); + + if( FAILED( hr ) ) + { + Con::errorf( "WMIVideoInfo: Connect server failed." ); + return false; + } + + + // Set security levels on the proxy + hr = CoSetProxyBlanket( + mServices, // Indicates the proxy to set + RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx + RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx + NULL, // Server principal name + RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx + RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx + NULL, // client identity + EOAC_NONE // proxy capabilities + ); + + if( FAILED( hr ) ) + { + Con::errorf( "WMIVideoInfo: CoSetProxyBlanket failed" ); + return false; + } + + return true; +} + +bool WMIVideoInfo::_initializeDXGI() +{ + // DXGI stuff works, but loading dxgi.dll seems to cause crashes in the DX rendering code. + // Deactivated for now. + +#if 0 + // Try going for DXGI. Will only succeed on Vista. + + mDXGIModule = ( HMODULE ) LoadLibrary( L"dxgi.dll" ); + if( mDXGIModule != 0 ) + { + typedef HRESULT (* CreateDXGIFactoryFuncType )( REFIID, void** ); + CreateDXGIFactoryFuncType factoryFunction = + ( CreateDXGIFactoryFuncType ) GetProcAddress( ( HMODULE ) mDXGIModule, "CreateDXGIFactory" ); + + if( factoryFunction && factoryFunction( IID_IDXGIFactory, ( void** ) &mDXGIFactory ) == S_OK ) + return true; + else + { + FreeLibrary( ( HMODULE ) mDXGIModule ); + mDXGIModule = 0; + } + } +#endif + return false; +} + +bool WMIVideoInfo::_initializeDxDiag() +{ + if( CoCreateInstance( CLSID_DxDiagProvider, NULL, CLSCTX_INPROC_SERVER, IID_IDxDiagProvider, ( void** ) &mDxDiagProvider ) == S_OK ) + { + DXDIAG_INIT_PARAMS params; + dMemset( ¶ms, 0, sizeof( DXDIAG_INIT_PARAMS ) ); + + params.dwSize = sizeof( DXDIAG_INIT_PARAMS ); + params.dwDxDiagHeaderVersion = 111; + params.bAllowWHQLChecks = false; + + HRESULT result = mDxDiagProvider->Initialize( ¶ms ); + if( result != S_OK ) + { + Con::errorf( "WMIVideoInfo: DxDiag initialization failed (%i)", result ); + SAFE_RELEASE( mDxDiagProvider ); + return false; + } + else + { + Con::printf( "WMIVideoInfo: DxDiag initialized" ); + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------ +// http://msdn2.microsoft.com/en-us/library/aa394512.aspx +// +// The Win32_VideoController WMI class represents the capabilities and management capacity of the +// video controller on a computer system running Windows. +// +// Starting with Windows Vista, hardware that is not compatible with Windows Display Driver Model (WDDM) +// returns inaccurate property values for instances of this class. +// +// Windows Server 2003, Windows XP, Windows 2000, and Windows NT 4.0: This class is supported. +//------------------------------------------------------------------------------ + +bool WMIVideoInfo::_queryProperty( const PVIQueryType queryType, const U32 adapterId, String *outValue ) +{ + if( _queryPropertyDXGI( queryType, adapterId, outValue ) ) + return true; + else if( _queryPropertyDxDiag( queryType, adapterId, outValue ) ) + return true; + else + return _queryPropertyWMI( queryType, adapterId, outValue ); +} + +bool WMIVideoInfo::_queryPropertyDxDiag( const PVIQueryType queryType, const U32 adapterId, String *outValue ) +{ + if( mDxDiagProvider != 0 ) + { + IDxDiagContainer* rootContainer = 0; + IDxDiagContainer* displayDevicesContainer = 0; + IDxDiagContainer* deviceContainer = 0; + + WCHAR adapterIdString[ 2 ]; + adapterIdString[ 0 ] = L'0' + adapterId; + adapterIdString[ 1 ] = L'\0'; + + String value; + if( mDxDiagProvider->GetRootContainer( &rootContainer ) == S_OK + && rootContainer->GetChildContainer( L"DxDiag_DisplayDevices", &displayDevicesContainer ) == S_OK + && displayDevicesContainer->GetChildContainer( adapterIdString, &deviceContainer ) == S_OK ) + { + const WCHAR* propertyName = 0; + + switch( queryType ) + { + case PVI_Description: + propertyName = L"szDescription"; + break; + + case PVI_Name: + propertyName = L"szDeviceName"; + break; + + case PVI_ChipSet: + propertyName = L"szChipType"; + break; + + case PVI_DriverVersion: + propertyName = L"szDriverVersion"; + break; + + // Don't get VRAM via DxDiag as that won't tell us about the actual amount of dedicated + // video memory but rather some dedicated+shared RAM value. + } + + if( propertyName ) + { + VARIANT val; + if( deviceContainer->GetProp( propertyName, &val ) == S_OK ) + switch( val.vt ) + { + case VT_BSTR: + value = String( val.bstrVal ); + break; + + default: + AssertWarn( false, avar( "WMIVideoInfo: property type '%i' not implemented", val.vt ) ); + } + } + } + + if( rootContainer ) + SAFE_RELEASE( rootContainer ); + if( displayDevicesContainer ) + SAFE_RELEASE( displayDevicesContainer ); + if( deviceContainer ) + SAFE_RELEASE( deviceContainer ); + + if( value.isNotEmpty() ) + { + // Try to get the DxDiag data into some canonical form. Otherwise, we + // won't be giving the card profiler much opportunity for matching up + // its data with profile scripts. + + switch( queryType ) + { + case PVI_ChipSet: + if( value.compare( "ATI", 3, String::NoCase ) == 0 ) + value = "ATI Technologies Inc."; + else if( value.compare( "NVIDIA", 6, String::NoCase ) == 0 ) + value = "NVIDIA"; + else if( value.compare( "INTEL", 5, String::NoCase ) == 0 ) + value = "INTEL"; + else if( value.compare( "MATROX", 6, String::NoCase ) == 0 ) + value = "MATROX"; + break; + + case PVI_Description: + if( value.compare( "ATI ", 4, String::NoCase ) == 0 ) + { + value = value.substr( 4, value.length() - 4 ); + if( value.compare( " Series", 7, String::NoCase | String::Right ) == 0 ) + value = value.substr( 0, value.length() - 7 ); + } + else if( value.compare( "NVIDIA ", 7, String::NoCase ) == 0 ) + value = value.substr( 7, value.length() - 7 ); + else if( value.compare( "INTEL ", 6, String::NoCase ) == 0 ) + value = value.substr( 6, value.length() - 6 ); + else if( value.compare( "MATROX ", 7, String::NoCase ) == 0 ) + value = value.substr( 7, value.length() - 7 ); + break; + } + + *outValue = value; + return true; + } + } + return false; +} + +bool WMIVideoInfo::_queryPropertyDXGI( const PVIQueryType queryType, const U32 adapterId, String *outValue ) +{ +#if 0 + if( mDXGIFactory ) + { + IDXGIAdapter* adapter; + if( mDXGIFactory->EnumAdapters( adapterId, &adapter ) != S_OK ) + return false; + + DXGI_ADAPTER_DESC desc; + if( adapter->GetDesc( &desc ) != S_OK ) + { + adapter->Release(); + return false; + } + + String value; + switch( queryType ) + { + case PVI_Description: + value = String( desc.Description ); + break; + + case PVI_Name: + value = String( avar( "%i", desc.DeviceId ) ); + break; + + case PVI_VRAM: + value = String( avar( "%i", desc.DedicatedVideoMemory ) ); + break; + + //RDTODO + } + + adapter->Release(); + *outValue = value; + return true; + } +#endif + return false; +} + +bool WMIVideoInfo::_queryPropertyWMI( const PVIQueryType queryType, const U32 adapterId, String *outValue ) +{ + if( mServices == NULL ) + return false; + + // Use the IWbemServices pointer to make requests of WMI + IEnumWbemClassObject *enumerator; + HRESULT hr = mServices->ExecQuery( + BSTR("WQL"), + BSTR("SELECT * FROM Win32_VideoController"), + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &enumerator + ); + + if( FAILED( hr ) ) + return false; + + // Uh I think this is right...get the proper adapter + IWbemClassObject *adapter = NULL; + for( int i = 0; i < adapterId + 1; i++ ) + { + ULONG count = 0; + HRESULT hr = enumerator->Next( WBEM_INFINITE, 1, &adapter, &count ); + + if( FAILED( hr ) || count == 0 ) + { + enumerator->Release(); + return false; + } + } + + // Now get the property + VARIANT v; + hr = adapter->Get( smPVIQueryTypeToWMIString[queryType], 0, &v, 0, 0 ); + + if( SUCCEEDED( hr ) ) + { + switch( v.vt ) + { + case VT_I4: + { + LONG longVal = v.lVal; + + if( queryType == PVI_VRAM ) + longVal = longVal >> 20; // Convert to megabytes + + *outValue = String::ToString( (S32)longVal ); + break; + } + + case VT_UI4: + { + *outValue = String::ToString( (U32)v.ulVal ); + break; + } + + case VT_BSTR: + *outValue = String( v.bstrVal ); + break; + case VT_LPSTR: + case VT_LPWSTR: + break; + } + + VariantClear( &v ); + } + else + { + adapter->Release(); + enumerator->Release(); + return false; + } + + adapter->Release(); + + // Cleanup + enumerator->Release(); + + return true; +} \ No newline at end of file diff --git a/platformWin32/videoInfo/wmiVideoInfo.h b/platformWin32/videoInfo/wmiVideoInfo.h new file mode 100644 index 0000000..1ba5f57 --- /dev/null +++ b/platformWin32/videoInfo/wmiVideoInfo.h @@ -0,0 +1,41 @@ +#ifndef _WMI_CARDINFO_H_ +#define _WMI_CARDINFO_H_ + +#include "platform/platformVideoInfo.h" + +struct IWbemLocator; +struct IWbemServices; + +struct IDXGIFactory; +struct IDxDiagProvider; + +class WMIVideoInfo : public PlatformVideoInfo +{ +private: + IWbemLocator *mLocator; + IWbemServices *mServices; + bool mComInitialized; + + void* mDXGIModule; + IDXGIFactory* mDXGIFactory; + IDxDiagProvider* mDxDiagProvider; + + bool _initializeDXGI(); + bool _initializeDxDiag(); + bool _initializeWMI(); + + bool _queryPropertyDXGI( const PVIQueryType queryType, const U32 adapterId, String *outValue ); + bool _queryPropertyDxDiag( const PVIQueryType queryType, const U32 adapterId, String *outValue ); + bool _queryPropertyWMI( const PVIQueryType queryType, const U32 adapterId, String *outValue ); + +protected: + static WCHAR *smPVIQueryTypeToWMIString []; + bool _queryProperty( const PVIQueryType queryType, const U32 adapterId, String *outValue ); + bool _initialize(); + +public: + WMIVideoInfo(); + ~WMIVideoInfo(); +}; + +#endif \ No newline at end of file diff --git a/platformWin32/winAsmBlit.cpp b/platformWin32/winAsmBlit.cpp new file mode 100644 index 0000000..0bd8764 --- /dev/null +++ b/platformWin32/winAsmBlit.cpp @@ -0,0 +1,190 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMath.h" +#include "gfx/bitmap/gBitmap.h" +#include "gfx/bitmap/bitmapUtils.h" + +#if !defined(__MWERKS__) && defined(_MSC_VER) +#define asm _asm +#endif + +//-------------------------------------------------------------------------- +void bitmapExtrude5551_asm(const void *srcMip, void *mip, U32 height, U32 width) +{ + const U16 *src = (const U16 *) srcMip; + U16 *dst = (U16 *) mip; + U32 stride = width << 1; + + for(U32 y = 0; y < height; y++) + { + for(U32 x = 0; x < width; x++) + { + U32 a = src[0]; + U32 b = src[1]; + U32 c = src[stride]; + U32 d = src[stride+1]; + dst[x] = ((((a >> 11) + (b >> 11) + (c >> 11) + (d >> 11)) >> 2) << 11) | + ((( ((a >> 6) & 0x1f) + ((b >> 6) & 0x1f) + ((c >> 6) & 0x1f) + ((d >> 6) & 0x1F) ) >> 2) << 6) | + ((( ((a >> 1) & 0x1F) + ((b >> 1) & 0x1F) + ((c >> 1) & 0x1f) + ((d >> 1) & 0x1f)) >> 2) << 1); + src += 2; + } + src += stride; + dst += width; + } +} + + +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) + +//-------------------------------------------------------------------------- +void bitmapExtrudeRGB_mmx(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) +{ + if (srcHeight == 1 || srcWidth == 1) { + bitmapExtrudeRGB_c(srcMip, mip, srcHeight, srcWidth); + return; + } + + U32 width = srcWidth >> 1; + U32 height = srcHeight >> 1; + + if (width <= 1) + { + bitmapExtrudeRGB_c(srcMip, mip, srcHeight, srcWidth); + return; + } + + U64 ZERO = 0x0000000000000000; + const U8 *src = (const U8 *) srcMip; + U8 *dst = (U8 *) mip; + U32 srcStride = (width << 1) * 3; + U32 dstStride = width * 3; + + for(U32 y = 0; y < height; y++) + { + asm + { + mov eax, src + mov ebx, eax + add ebx, srcStride + mov ecx, dst + mov edx, width + + //-------------------------------------- + row_loop: + + punpcklbw mm0, [eax] + psrlw mm0, 8 + + punpcklbw mm1, [eax+3] + psrlw mm1, 8 + paddw mm0, mm1 + + punpcklbw mm1, [ebx] + psrlw mm1, 8 + paddw mm0, mm1 + + punpcklbw mm1, [ebx+3] + psrlw mm1, 8 + paddw mm0, mm1 + + psrlw mm0, 2 + //pxor mm1, mm1 + packuswb mm0, ZERO // mm1 + + movd [ecx], mm0 + add eax, 6 + add ebx, 6 + add ecx, 3 + dec edx + jnz row_loop + } + src += srcStride + srcStride; // advance to next line + dst += dstStride; + } + asm + { + emms + } +} + + +//-------------------------------------------------------------------------- +void bitmapConvertRGB_to_5551_mmx(U8 *src, U32 pixels) +{ + U64 MULFACT = 0x0008200000082000; // RGB quad word multiplier + U64 REDBLUE = 0x00f800f800f800f8; // Red-Blue mask + U64 GREEN = 0x0000f8000000f800; // Green mask + U64 ALPHA = 0x0000000000010001; // 100% Alpha mask + U64 ZERO = 0x0000000000000000; + + U32 evenPixels = pixels >> 1; // the MMX loop can only do an even number + U32 oddPixels = pixels & 1; // of pixels since it processes 2 at a time + + U16 *dst = (U16*)src; + + if (evenPixels) + { + asm + { + mov eax, src // YES, src = dst at start + mov ebx, dst // convert image in place + mov edx, evenPixels + + pixel_loop2: + movd mm0, [eax] // get first 24-bit pixel + movd mm1, [eax+3] // get second 24-bit pixel + punpckldq mm0, mm1 // put second in high dword + movq mm1, mm0 // save the original data + pand mm0, REDBLUE // mask out all but the 5MSBits of red and blue + pmaddwd mm0, MULFACT // multiply each word by + // 2**13, 2**3, 2**13, 2**3 and add results + pand mm1, GREEN // mask out all but the 5MSBits of green + por mm0, mm1 // combine the red, green, and blue bits + psrld mm0, 6 // shift into position + packssdw mm0, ZERO // pack into single dword + pslld mm0, 1 // shift into final position + por mm0, ALPHA // add the alpha bit + movd [ebx], mm0 + + add eax, 6 + add ebx, 4 + dec edx + jnz pixel_loop2 + + mov src, eax + mov dst, ebx + emms + } + } + + if (oddPixels) + { + U32 r = src[0] >> 3; + U32 g = src[1] >> 3; + U32 b = src[2] >> 3; + + *dst = (b << 1) | (g << 6) | (r << 11) | 1; + } +} + +#endif + + + +//-------------------------------------------------------------------------- +void PlatformBlitInit() +{ + bitmapExtrude5551 = bitmapExtrude5551_asm; + bitmapExtrudeRGB = bitmapExtrudeRGB_c; + + if (Platform::SystemInfo.processor.properties & CPU_PROP_MMX) + { +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) + bitmapExtrudeRGB = bitmapExtrudeRGB_mmx; + bitmapConvertRGB_to_5551 = bitmapConvertRGB_to_5551_mmx; +#endif + } +} diff --git a/platformWin32/winAsync.cpp b/platformWin32/winAsync.cpp new file mode 100644 index 0000000..9b4de95 --- /dev/null +++ b/platformWin32/winAsync.cpp @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// For VS2005. +#define _WIN32_WINNT 0x501 +#ifndef TORQUE_OS_XENON +#include "platformWin32/platformWin32.h" +#endif +#include "platform/async/asyncUpdate.h" + + +AsyncUpdateThread::AsyncUpdateThread( String name, AsyncUpdateList* updateList ) + : Parent( 0, 0, false, false ), + mUpdateList( updateList ), + mName( name ) +{ + // Create an auto-reset event in non-signaled state. + mUpdateEvent = CreateEvent( NULL, false, false, NULL ); +} + +AsyncUpdateThread::~AsyncUpdateThread() +{ + CloseHandle( ( HANDLE ) mUpdateEvent ); +} + +void AsyncUpdateThread::_waitForEventAndReset() +{ + WaitForSingleObject( ( HANDLE ) mUpdateEvent, INFINITE ); +} + +void AsyncUpdateThread::triggerUpdate() +{ + SetEvent( ( HANDLE ) mUpdateEvent ); +} + +AsyncPeriodicUpdateThread::AsyncPeriodicUpdateThread( String name, + AsyncUpdateList* updateList, + U32 intervalMS ) + : Parent( name, updateList ) +{ + mUpdateTimer = CreateWaitableTimer( NULL, FALSE, NULL ); + + // This is a bit contrived. The 'dueTime' is in 100 nanosecond intervals + // and relative if it is negative. The period is in milliseconds. + + LARGE_INTEGER deltaTime; + deltaTime.QuadPart = - LONGLONG( intervalMS * 10 /* micro */ * 1000 /* milli */ ); + + SetWaitableTimer( ( HANDLE ) mUpdateTimer, &deltaTime, intervalMS, NULL, NULL, FALSE ); +} + +AsyncPeriodicUpdateThread::~AsyncPeriodicUpdateThread() +{ + CloseHandle( ( HANDLE ) mUpdateTimer ); +} + +void AsyncPeriodicUpdateThread::_waitForEventAndReset() +{ + HANDLE handles[ 2 ]; + + handles[ 0 ] = ( HANDLE ) mUpdateEvent; + handles[ 1 ] = ( HANDLE ) mUpdateTimer; + + WaitForMultipleObjects( 2, handles, FALSE, INFINITE ); +} diff --git a/platformWin32/winCPUInfo.cpp b/platformWin32/winCPUInfo.cpp new file mode 100644 index 0000000..11142bf --- /dev/null +++ b/platformWin32/winCPUInfo.cpp @@ -0,0 +1,178 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platformWin32/platformWin32.h" +#include "console/console.h" +#include "core/stringTable.h" +#include + +Platform::SystemInfo_struct Platform::SystemInfo; +extern void PlatformBlitInit(); +extern void SetProcessorInfo(Platform::SystemInfo_struct::Processor& pInfo, + char* vendor, U32 processor, U32 properties); // platform/platformCPU.cc + + +#if defined(TORQUE_SUPPORTS_NASM) +// asm cpu detection routine from platform code +extern "C" +{ + void detectX86CPUInfo(char *vendor, U32 *processor, U32 *properties); +} +#endif + + +void Processor::init() +{ + // Reference: + // www.cyrix.com + // www.amd.com + // www.intel.com + // http://developer.intel.com/design/PentiumII/manuals/24512701.pdf + + Con::printf("Processor Init:"); + + Platform::SystemInfo.processor.type = CPU_X86Compatible; + Platform::SystemInfo.processor.name = StringTable->insert("Unknown x86 Compatible"); + Platform::SystemInfo.processor.mhz = 0; + Platform::SystemInfo.processor.properties = CPU_PROP_C | CPU_PROP_LE; + + char vendor[13] = {0,}; + U32 properties = 0; + U32 processor = 0; + +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) + __asm + { + //-------------------------------------- + // is CPUID supported + push ebx + push edx + push ecx + pushfd + pushfd // save EFLAGS to stack + pop eax // move EFLAGS into EAX + mov ebx, eax + xor eax, 0x200000 // flip bit 21 + push eax + popfd // restore EFLAGS + pushfd + pop eax + cmp eax, ebx + jz EXIT // doesn't support CPUID instruction + + //-------------------------------------- + // Get Vendor Informaion using CPUID eax==0 + xor eax, eax + cpuid + + mov DWORD PTR vendor, ebx + mov DWORD PTR vendor+4, edx + mov DWORD PTR vendor+8, ecx + + // get Generic Extended CPUID info + mov eax, 1 + cpuid // eax=1, so cpuid queries feature information + + and eax, 0x0fff3fff + mov processor, eax // just store the model bits + mov properties, edx + + // Want to check for 3DNow(tm). Need to see if extended cpuid functions present. + mov eax, 0x80000000 + cpuid + cmp eax, 0x80000000 + jbe MAYBE_3DLATER + mov eax, 0x80000001 + cpuid + and edx, 0x80000000 // 3DNow if bit 31 set -> put bit in our properties + or properties, edx + MAYBE_3DLATER: + + + EXIT: + popfd + pop ecx + pop edx + pop ebx + } +#elif defined(TORQUE_SUPPORTS_NASM) + + detectX86CPUInfo(vendor, &processor, &properties); + +#endif + + SetProcessorInfo(Platform::SystemInfo.processor, vendor, processor, properties); + +// now calculate speed of processor... + U32 nearmhz = 0; // nearest rounded mhz + U32 mhz = 0; // calculated value. + + LONG result; + DWORD data = 0; + DWORD dataSize = 4; + HKEY hKey; + + result = ::RegOpenKeyExA (HKEY_LOCAL_MACHINE,"Hardware\\Description\\System\\CentralProcessor\\0", 0, KEY_QUERY_VALUE, &hKey); + + if (result == ERROR_SUCCESS) + { + result = ::RegQueryValueExA (hKey, "~MHz",NULL, NULL,(LPBYTE)&data, &dataSize); + + if (result == ERROR_SUCCESS) + nearmhz = mhz = data; + + ::RegCloseKey(hKey); + } + + Platform::SystemInfo.processor.mhz = mhz; + + if (mhz==0) + { + Con::printf(" %s, (Unknown) Mhz", Platform::SystemInfo.processor.name); + // stick SOMETHING in so it isn't ZERO. + Platform::SystemInfo.processor.mhz = 200; // seems a decent value. + } + else + { + if (nearmhz >= 1000) + Con::printf(" %s, ~%.2f Ghz", Platform::SystemInfo.processor.name, ((float)nearmhz)/1000.0f); + else + Con::printf(" %s, ~%d Mhz", Platform::SystemInfo.processor.name, nearmhz); + if (nearmhz != mhz) + { + if (mhz >= 1000) + Con::printf(" (timed at roughly %.2f Ghz)", ((float)mhz)/1000.0f); + else + Con::printf(" (timed at roughly %d Mhz)", mhz); + } + } + + if( Platform::SystemInfo.processor.numAvailableCores > 0 + || Platform::SystemInfo.processor.numPhysicalProcessors > 0 + || Platform::SystemInfo.processor.isHyperThreaded ) + Platform::SystemInfo.processor.properties |= CPU_PROP_MP; + + if (Platform::SystemInfo.processor.properties & CPU_PROP_FPU) + Con::printf( " FPU detected" ); + if (Platform::SystemInfo.processor.properties & CPU_PROP_MMX) + Con::printf( " MMX detected" ); + if (Platform::SystemInfo.processor.properties & CPU_PROP_3DNOW) + Con::printf( " 3DNow detected" ); + if (Platform::SystemInfo.processor.properties & CPU_PROP_SSE) + Con::printf( " SSE detected" ); + if( Platform::SystemInfo.processor.properties & CPU_PROP_SSE2 ) + Con::printf( " SSE2 detected" ); + if( Platform::SystemInfo.processor.isHyperThreaded ) + Con::printf( " HT detected" ); + if( Platform::SystemInfo.processor.properties & CPU_PROP_MP ) + Con::printf( " MP detected [%i cores, %i logical, %i physical]", + Platform::SystemInfo.processor.numAvailableCores, + Platform::SystemInfo.processor.numLogicalProcessors, + Platform::SystemInfo.processor.numPhysicalProcessors ); + Con::printf(" "); + + PlatformBlitInit(); +} diff --git a/platformWin32/winConsole.cpp b/platformWin32/winConsole.cpp new file mode 100644 index 0000000..4098c41 --- /dev/null +++ b/platformWin32/winConsole.cpp @@ -0,0 +1,324 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/util/rawData.h" +#include "core/strings/stringFunctions.h" +#include "core/strings/unicode.h" + +#include "platformWin32/platformWin32.h" +#include "platformWin32/winConsole.h" +#include "console/consoleTypes.h" +#include "core/util/journal/process.h" + + +WinConsole *WindowsConsole = NULL; + +namespace Con +{ + extern bool alwaysUseDebugOutput; +} + +ConsoleFunction(enableWinConsole, void, 2, 2, "enableWinConsole(bool);") +{ + argc; + WindowsConsole->enable(dAtob(argv[1])); +} + +void WinConsole::create() +{ + if( !WindowsConsole ) + WindowsConsole = new WinConsole(); +} + +void WinConsole::destroy() +{ + if( WindowsConsole ) + delete WindowsConsole; + WindowsConsole = NULL; +} + +void WinConsole::enable(bool enabled) +{ + winConsoleEnabled = enabled; + if(winConsoleEnabled) + { + AllocConsole(); + const char *title = Con::getVariable("Con::WindowTitle"); + if (title && *title) + { +#ifdef UNICODE + UTF16 buf[512]; + convertUTF8toUTF16((UTF8 *)title, buf, sizeof(buf)); + SetConsoleTitle(buf); +#else + SetConsoleTitle(title); +#endif + } + stdOut = GetStdHandle(STD_OUTPUT_HANDLE); + stdIn = GetStdHandle(STD_INPUT_HANDLE); + stdErr = GetStdHandle(STD_ERROR_HANDLE); + + printf("%s", Con::getVariable("Con::Prompt")); + } +} + +bool WinConsole::isEnabled() +{ + if ( WindowsConsole ) + return WindowsConsole->winConsoleEnabled; + + return false; +} + +static void winConsoleConsumer(ConsoleLogEntry::Level level, const char *line) +{ + if (WindowsConsole) + { + WindowsConsole->processConsoleLine(line); +#ifndef TORQUE_SHIPPING + // see console.cpp for a description of Con::alwaysUseDebugOutput + if( level == ConsoleLogEntry::Error || Con::alwaysUseDebugOutput) + { + // [rene, 04/05/2008] This is incorrect. Should do conversion from UTF8 here. + // Skipping for the sake of speed. Not meant to be seen by user anyway. + OutputDebugStringA( line ); + OutputDebugStringA( "\n" ); + } +#endif + } +} + +WinConsole::WinConsole() +{ + for (S32 iIndex = 0; iIndex < MAX_CMDS; iIndex ++) + rgCmds[iIndex][0] = '\0'; + + iCmdIndex = 0; + winConsoleEnabled = false; + Con::addConsumer(winConsoleConsumer); + inpos = 0; + lineOutput = false; + + Process::notify(this, &WinConsole::process, PROCESS_LAST_ORDER); +} + +WinConsole::~WinConsole() +{ + Process::remove(this, &WinConsole::process); + Con::removeConsumer(winConsoleConsumer); +} + +void WinConsole::printf(const char *s, ...) +{ + // Get the line into a buffer. + static const int BufSize = 4096; + static char buffer[4096]; + DWORD bytes; + va_list args; + va_start(args, s); + _vsnprintf(buffer, BufSize, s, args); + // Replace tabs with carats, like the "real" console does. + char *pos = buffer; + while (*pos) { + if (*pos == '\t') { + *pos = '^'; + } + pos++; + } + // Axe the color characters. + Con::stripColorChars(buffer); + // Print it. + WriteFile(stdOut, buffer, strlen(buffer), &bytes, NULL); + FlushFileBuffers( stdOut ); +} + +void WinConsole::processConsoleLine(const char *consoleLine) +{ + if(winConsoleEnabled) + { + inbuf[inpos] = 0; + if(lineOutput) + printf("%s\n", consoleLine); + else + printf("%c%s\n%s%s", '\r', consoleLine, Con::getVariable("Con::Prompt"), inbuf); + } +} + +void WinConsole::process() +{ + if(winConsoleEnabled) + { + DWORD numEvents; + GetNumberOfConsoleInputEvents(stdIn, &numEvents); + if(numEvents) + { + INPUT_RECORD rec[20]; + char outbuf[512]; + S32 outpos = 0; + + ReadConsoleInput(stdIn, rec, 20, &numEvents); + DWORD i; + for(i = 0; i < numEvents; i++) + { + if(rec[i].EventType == KEY_EVENT) + { + KEY_EVENT_RECORD *ke = &(rec[i].Event.KeyEvent); + if(ke->bKeyDown) + { + switch (ke->uChar.AsciiChar) + { + // If no ASCII char, check if it's a handled virtual key + case 0: + switch (ke->wVirtualKeyCode) + { + // UP ARROW + case 0x26 : + // Go to the previous command in the cyclic array + if ((-- iCmdIndex) < 0) + iCmdIndex = MAX_CMDS - 1; + + // If this command isn't empty ... + if (rgCmds[iCmdIndex][0] != '\0') + { + // Obliterate current displayed text + for (S32 i = outpos = 0; i < inpos; i ++) + { + outbuf[outpos ++] = '\b'; + outbuf[outpos ++] = ' '; + outbuf[outpos ++] = '\b'; + } + + // Copy command into command and display buffers + for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos ++, outpos ++) + { + outbuf[outpos] = rgCmds[iCmdIndex][inpos]; + inbuf [inpos ] = rgCmds[iCmdIndex][inpos]; + } + } + // If previous is empty, stay on current command + else if ((++ iCmdIndex) >= MAX_CMDS) + { + iCmdIndex = 0; + } + + break; + + // DOWN ARROW + case 0x28 : { + // Go to the next command in the command array, if + // it isn't empty + if (rgCmds[iCmdIndex][0] != '\0' && (++ iCmdIndex) >= MAX_CMDS) + iCmdIndex = 0; + + // Obliterate current displayed text + for (S32 i = outpos = 0; i < inpos; i ++) + { + outbuf[outpos ++] = '\b'; + outbuf[outpos ++] = ' '; + outbuf[outpos ++] = '\b'; + } + + // Copy command into command and display buffers + for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos ++, outpos ++) + { + outbuf[outpos] = rgCmds[iCmdIndex][inpos]; + inbuf [inpos ] = rgCmds[iCmdIndex][inpos]; + } + } + break; + + // LEFT ARROW + case 0x25 : + break; + + // RIGHT ARROW + case 0x27 : + break; + + default : + break; + } + break; + case '\b': + if(inpos > 0) + { + outbuf[outpos++] = '\b'; + outbuf[outpos++] = ' '; + outbuf[outpos++] = '\b'; + inpos--; + } + break; + case '\t': + // In the output buffer, we're going to have to erase the current line (in case + // we're cycling through various completions) and write out the whole input + // buffer, so (inpos * 3) + complen <= 512. Should be OK. The input buffer is + // also 512 chars long so that constraint will also be fine for the input buffer. + { + // Erase the current line. + U32 i; + for (i = 0; i < inpos; i++) { + outbuf[outpos++] = '\b'; + outbuf[outpos++] = ' '; + outbuf[outpos++] = '\b'; + } + // Modify the input buffer with the completion. + U32 maxlen = 512 - (inpos * 3); + if (ke->dwControlKeyState & SHIFT_PRESSED) { + inpos = Con::tabComplete(inbuf, inpos, maxlen, false); + } + else { + inpos = Con::tabComplete(inbuf, inpos, maxlen, true); + } + // Copy the input buffer to the output. + for (i = 0; i < inpos; i++) { + outbuf[outpos++] = inbuf[i]; + } + } + break; + case '\n': + case '\r': + outbuf[outpos++] = '\r'; + outbuf[outpos++] = '\n'; + + inbuf[inpos] = 0; + outbuf[outpos] = 0; + printf("%s", outbuf); + + // Pass the line along to the console for execution. + { + RawData rd; + rd.size = inpos + 1; + rd.data = ( S8* ) inbuf; + + Con::smConsoleInput.trigger(rd); + } + + // If we've gone off the end of our array, wrap + // back to the beginning + if (iCmdIndex >= MAX_CMDS) + iCmdIndex %= MAX_CMDS; + + // Put the new command into the array + strcpy(rgCmds[iCmdIndex ++], inbuf); + + printf("%s", Con::getVariable("Con::Prompt")); + inpos = outpos = 0; + break; + default: + inbuf[inpos++] = ke->uChar.AsciiChar; + outbuf[outpos++] = ke->uChar.AsciiChar; + break; + } + } + } + } + if(outpos) + { + outbuf[outpos] = 0; + printf("%s", outbuf); + } + } + } +} diff --git a/platformWin32/winConsole.h b/platformWin32/winConsole.h new file mode 100644 index 0000000..b2b823b --- /dev/null +++ b/platformWin32/winConsole.h @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WINCONSOLE_H_ +#define _WINCONSOLE_H_ + +#define MAX_CMDS 10 +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +class WinConsole +{ + bool winConsoleEnabled; + + HANDLE stdOut; + HANDLE stdIn; + HANDLE stdErr; + char inbuf[512]; + S32 inpos; + bool lineOutput; + char curTabComplete[512]; + S32 tabCompleteStart; + char rgCmds[MAX_CMDS][512]; + S32 iCmdIndex; + + void printf(const char *s, ...); + +public: + WinConsole(); + ~WinConsole(); + + void process(); + void enable(bool); + void processConsoleLine(const char *consoleLine); + static void create(); + static void destroy(); + static bool isEnabled(); +}; + +extern WinConsole *WindowsConsole; + +#endif diff --git a/platformWin32/winDInputDevice.cpp b/platformWin32/winDInputDevice.cpp new file mode 100644 index 0000000..9e4beaa --- /dev/null +++ b/platformWin32/winDInputDevice.cpp @@ -0,0 +1,1584 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef INITGUID +#define INITGUID +#endif + +#include "math/mMath.h" +#include "platformWin32/winDInputDevice.h" +#include "console/console.h" +#include "core/strings/unicode.h" +#include "windowManager/platformWindowMgr.h" +#include "windowManager/win32/win32Window.h" + +// Static class data: +LPDIRECTINPUT8 DInputDevice::smDInputInterface; +U8 DInputDevice::smDeviceCount[ NUM_INPUT_DEVICE_TYPES ]; +bool DInputDevice::smInitialized = false; + +#ifdef LOG_INPUT +const char* getKeyName( U16 key ); +#endif + +//------------------------------------------------------------------------------ +DInputDevice::DInputDevice( const DIDEVICEINSTANCE* dii ) +{ + mDeviceInstance = *dii; + mDevice = NULL; + mAcquired = false; + mNeedSync = false; + mObjInstance = NULL; + mObjFormat = NULL; + mObjInfo = NULL; + mObjBuffer1 = NULL; + mObjBuffer2 = NULL; + mPrevObjBuffer = NULL; + + mForceFeedbackEffect = NULL; + mNumForceFeedbackAxes = 0; + mForceFeedbackAxes[0] = 0; + mForceFeedbackAxes[1] = 0; + + const char* deviceTypeName = "unknown"; + U8 deviceType = UnknownDeviceType; + + switch ( GET_DIDEVICE_TYPE( mDeviceInstance.dwDevType ) ) + { + // [rene, 12/09/2008] why do we turn a gamepad into a joystick here? + + case DI8DEVTYPE_GAMEPAD: + case DI8DEVTYPE_JOYSTICK: + deviceTypeName = "joystick"; + deviceType = JoystickDeviceType; + break; + + case DI8DEVTYPE_KEYBOARD: + deviceTypeName = "keyboard"; + deviceType = KeyboardDeviceType; + break; + + case DI8DEVTYPE_MOUSE: + deviceTypeName = "mouse"; + deviceType = MouseDeviceType; + break; + } + + mDeviceType = deviceType; + mDeviceID = smDeviceCount[ deviceType ] ++; + + dSprintf( mName, 29, "%s%d", deviceTypeName, mDeviceID ); +} + +//------------------------------------------------------------------------------ +DInputDevice::~DInputDevice() +{ + destroy(); +} + +//------------------------------------------------------------------------------ +void DInputDevice::init() +{ + // Reset all of the static variables: + smDInputInterface = NULL; + dMemset( smDeviceCount, 0, sizeof( smDeviceCount ) ); +} + +//------------------------------------------------------------------------------ +bool DInputDevice::create() +{ + HRESULT result; + + if ( smDInputInterface ) + { + result = smDInputInterface->CreateDevice( mDeviceInstance.guidInstance, &mDevice, NULL ); + if ( result == DI_OK ) + { + mDeviceCaps.dwSize = sizeof( DIDEVCAPS ); + if ( FAILED( mDevice->GetCapabilities( &mDeviceCaps ) ) ) + { + Con::errorf( " Failed to get the capabilities of the %s input device.", mName ); +#ifdef LOG_INPUT + Input::log( "Failed to get the capabilities of &s!\n", mName ); +#endif + return false; + } + +#ifdef LOG_INPUT + Input::log( "%s detected, created as %s (%s).\n", mDeviceInstance.tszProductName, mName, ( isPolled() ? "polled" : "asynchronous" ) ); +#endif + + if ( enumerateObjects() ) + { + // Set the device's data format: + DIDATAFORMAT dataFormat; + dMemset( &dataFormat, 0, sizeof( DIDATAFORMAT ) ); + dataFormat.dwSize = sizeof( DIDATAFORMAT ); + dataFormat.dwObjSize = sizeof( DIOBJECTDATAFORMAT ); + dataFormat.dwFlags = ( mDeviceType == MouseDeviceType ) ? DIDF_RELAXIS : DIDF_ABSAXIS; + dataFormat.dwDataSize = mObjBufferSize; + dataFormat.dwNumObjs = mObjCount; + dataFormat.rgodf = mObjFormat; + + result = mDevice->SetDataFormat( &dataFormat ); + if ( FAILED( result ) ) + { + Con::errorf( " Failed to set the data format for the %s input device.", mName ); +#ifdef LOG_INPUT + Input::log( "Failed to set the data format for %s!\n", mName ); +#endif + return false; + } + + // Set up the data buffer for buffered input: + DIPROPDWORD prop; + prop.diph.dwSize = sizeof( DIPROPDWORD ); + prop.diph.dwHeaderSize = sizeof( DIPROPHEADER ); + prop.diph.dwObj = 0; + prop.diph.dwHow = DIPH_DEVICE; + if ( isPolled() ) + prop.dwData = mObjBufferSize; + else + prop.dwData = QUEUED_BUFFER_SIZE; + + result = mDevice->SetProperty( DIPROP_BUFFERSIZE, &prop.diph ); + if ( FAILED( result ) ) + { + Con::errorf( " Failed to set the buffer size property for the %s input device.", mName ); +#ifdef LOG_INPUT + Input::log( "Failed to set the buffer size property for %s!\n", mName ); +#endif + return false; + } + + // If this device is a mouse, set it to relative axis mode: + if ( mDeviceType == MouseDeviceType ) + { + prop.diph.dwObj = 0; + prop.diph.dwHow = DIPH_DEVICE; + prop.dwData = DIPROPAXISMODE_REL; + + result = mDevice->SetProperty( DIPROP_AXISMODE, &prop.diph ); + if ( FAILED( result ) ) + { + Con::errorf( " Failed to set relative axis mode for the %s input device.", mName ); +#ifdef LOG_INPUT + Input::log( "Failed to set relative axis mode for %s!\n", mName ); +#endif + return false; + } + } + } + } + else + { +#ifdef LOG_INPUT + switch ( result ) + { + case DIERR_DEVICENOTREG: + Input::log( "CreateDevice failed -- The device or device instance is not registered with DirectInput.\n" ); + break; + + case DIERR_INVALIDPARAM: + Input::log( "CreateDevice failed -- Invalid parameter.\n" ); + break; + + case DIERR_NOINTERFACE: + Input::log( "CreateDevice failed -- The specified interface is not supported by the object.\n" ); + break; + + case DIERR_OUTOFMEMORY: + Input::log( "CreateDevice failed -- Out of memory.\n" ); + break; + + default: + Input::log( "CreateDevice failed -- Unknown error.\n" ); + break; + }; +#endif // LOG_INPUT + Con::printf( " CreateDevice failed for the %s input device!", mName ); + return false; + } + } + + Con::printf( " %s input device created.", mName ); + return true; +} + +//------------------------------------------------------------------------------ +void DInputDevice::destroy() +{ + if ( mDevice ) + { + unacquire(); + + // Tear down our forcefeedback. + if (mForceFeedbackEffect) + { + mForceFeedbackEffect->Release(); + mForceFeedbackEffect = NULL; + mNumForceFeedbackAxes = 0; +#ifdef LOG_INPUT + Input::log("DInputDevice::destroy - releasing constant force feeback effect\n"); +#endif + } + + mDevice->Release(); + mDevice = NULL; + + delete [] mObjInstance; + delete [] mObjFormat; + delete [] mObjInfo; + delete [] mObjBuffer1; + delete [] mObjBuffer2; + + mObjInstance = NULL; + mObjFormat = NULL; + mObjInfo = NULL; + mObjBuffer1 = NULL; + mObjBuffer2 = NULL; + mPrevObjBuffer = NULL; + mName[0] = 0; + } +} + +//------------------------------------------------------------------------------ +bool DInputDevice::acquire() +{ + if ( mDevice ) + { + if ( mAcquired ) + return( true ); + + bool result = false; + // Set the cooperative level: + // (do this here so that we have a valid app window) + DWORD coopLevel = DISCL_BACKGROUND; + if ( mDeviceType == JoystickDeviceType +// #ifndef DEBUG +// || mDeviceType == MouseDeviceType +// #endif + ) + // Exclusive access is required in order to perform force feedback + coopLevel = DISCL_EXCLUSIVE | DISCL_FOREGROUND; + else + coopLevel |= DISCL_NONEXCLUSIVE; + + result = mDevice->SetCooperativeLevel( getWin32WindowHandle(), coopLevel ); + if ( FAILED( result ) ) + { + Con::errorf( "Failed to set the cooperative level for the %s input device.", mName ); +#ifdef LOG_INPUT + Input::log( "Failed to set the cooperative level for %s!\n", mName ); +#endif + return false; + } + + // Enumerate joystick axes to enable force feedback + if ( NULL == mForceFeedbackEffect && JoystickDeviceType == mDeviceType) + { + // Since we will be playing force feedback effects, we should disable the auto-centering spring. + DIPROPDWORD dipdw; + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + dipdw.diph.dwObj = 0; + dipdw.diph.dwHow = DIPH_DEVICE; + dipdw.dwData = FALSE; + + if( FAILED( result = mDevice->SetProperty( DIPROP_AUTOCENTER, &dipdw.diph ) ) ) + return false; + } + + S32 code = mDevice->Acquire(); + result = SUCCEEDED( code ); + if ( result ) + { + Con::printf( "%s input device acquired.", mName ); +#ifdef LOG_INPUT + Input::log( "%s acquired.\n", mName ); +#endif + mAcquired = true; + + // If we were previously playing a force feedback effect, before + // losing acquisition, we do not automatically restart it. This is + // where you could call mForceFeedbackEffect->Start( INFINITE, 0 ); + // if you want that behavior. + + // Update all of the key states: + if ( !isPolled() ) + mNeedSync = true; + } + else + { + const char* reason; + switch ( code ) + { + case DIERR_INVALIDPARAM: reason = "Invalid parameter" ; break; + case DIERR_NOTINITIALIZED: reason = "Not initialized"; break; + case DIERR_OTHERAPPHASPRIO: reason = "Other app has priority"; break; + case S_FALSE: reason = "Already acquired"; break; + default: reason = "Unknown error"; break; + } + Con::warnf( "%s input device NOT acquired: %s", mName, reason ); +#ifdef LOG_INPUT + Input::log( "Failed to acquire %s: %s\n", mName, reason ); +#endif + } + + return( result ); + } + + return( false ); +} + +//------------------------------------------------------------------------------ +bool DInputDevice::unacquire() +{ + if ( mDevice ) + { + if ( !mAcquired ) + return( true ); + + bool result = false; + result = SUCCEEDED( mDevice->Unacquire() ); + if ( result ) + { + Con::printf( "%s input device unacquired.", mName ); +#ifdef LOG_INPUT + Input::log( "%s unacquired.\n", mName ); +#endif + mAcquired = false; + } + else + { + Con::warnf( ConsoleLogEntry::General, "%s input device NOT unacquired.", mName ); +#ifdef LOG_INPUT + Input::log( "Failed to unacquire %s!\n", mName ); +#endif + } + + return( result ); + } + + return( false ); +} + +//------------------------------------------------------------------------------ +BOOL CALLBACK DInputDevice::EnumObjectsProc( const DIDEVICEOBJECTINSTANCE* doi, LPVOID pvRef ) +{ + // Don't enumerate unknown types: + if ( doi->guidType == GUID_Unknown ) + return (DIENUM_CONTINUE); + + // Reduce a couple pointers: + DInputDevice* diDevice = (DInputDevice*) pvRef; + DIDEVICEOBJECTINSTANCE* objInstance = &diDevice->mObjInstance[diDevice->mObjEnumCount]; + DIOBJECTDATAFORMAT* objFormat = &diDevice->mObjFormat[diDevice->mObjEnumCount]; + + // Fill in the object instance structure: + *objInstance = *doi; + + // DWORD objects must be DWORD aligned: + if ( !(objInstance->dwType & DIDFT_BUTTON ) ) + diDevice->mObjBufferOfs = ( diDevice->mObjBufferOfs + 3 ) & ~3; + + objInstance->dwOfs = diDevice->mObjBufferOfs; + + // Fill in the object data format structure: + objFormat->pguid = &objInstance->guidType; + objFormat->dwType = objInstance->dwType; + objFormat->dwFlags= 0; + objFormat->dwOfs = diDevice->mObjBufferOfs; + + // Advance the enumeration counters: + if ( objFormat->dwType & DIDFT_BUTTON ) + diDevice->mObjBufferOfs += SIZEOF_BUTTON; + else + diDevice->mObjBufferOfs += SIZEOF_AXIS; + diDevice->mObjEnumCount++; + + return (DIENUM_CONTINUE); +} + +//------------------------------------------------------------------------------ +bool DInputDevice::enumerateObjects() +{ + if ( !mDevice ) + return false; + + // Calculate the needed buffer sizes and allocate them: + mObjCount = ( mDeviceCaps.dwAxes + mDeviceCaps.dwButtons + mDeviceCaps.dwPOVs ); + mObjBufferSize = mObjCount * sizeof( DWORD ); + + mObjInstance = new DIDEVICEOBJECTINSTANCE[mObjCount]; + mObjFormat = new DIOBJECTDATAFORMAT[mObjCount]; + mObjInfo = new ObjInfo[mObjCount]; + + if ( isPolled() ) + { + mObjBuffer1 = new U8[mObjBufferSize]; + dMemset( mObjBuffer1, 0, mObjBufferSize ); + mObjBuffer2 = new U8[mObjBufferSize]; + dMemset( mObjBuffer2, 0, mObjBufferSize ); + } + mObjEnumCount = 0; + mObjBufferOfs = 0; + + // We are about to enumerate, clear the axes we claim to know about + mNumForceFeedbackAxes = 0; + + // Enumerate all of the 'objects' detected on the device: + if ( FAILED( mDevice->EnumObjects( EnumObjectsProc, this, DIDFT_ALL ) ) ) + return false; + + // We only supports one or two axis joysticks + if( mNumForceFeedbackAxes > 2 ) + mNumForceFeedbackAxes = 2; + + // if we enumerated fewer objects than are supposedly available, reset the + // object count + if (mObjEnumCount < mObjCount) + mObjCount = mObjEnumCount; + + mObjBufferSize = ( mObjBufferSize + 3 ) & ~3; // Fill in the actual size to nearest DWORD + + U32 buttonCount = 0; + U32 povCount = 0; + U32 keyCount = 0; + U32 unknownCount = 0; + + // Fill in the device object's info structure: + for ( U32 i = 0; i < mObjCount; i++ ) + { + if ( mObjInstance[i].guidType == GUID_Button ) + { + mObjInfo[i].mType = SI_BUTTON; + mObjInfo[i].mInst = (InputObjectInstances)(KEY_BUTTON0 + buttonCount++); + } + else if ( mObjInstance[i].guidType == GUID_POV ) + { + // This is actually intentional - the POV handling code lower down + // takes the instance number and converts everything to button events. + mObjInfo[i].mType = SI_POV; + mObjInfo[i].mInst = (InputObjectInstances)povCount++; + } + else if ( mObjInstance[i].guidType == GUID_XAxis ) + { + mObjInfo[i].mType = SI_AXIS; + mObjInfo[i].mInst = SI_XAXIS; + + if (mObjInstance[i].dwFFMaxForce > 0) + mForceFeedbackAxes[mNumForceFeedbackAxes++] = mObjInstance[i].dwOfs; + } + else if ( mObjInstance[i].guidType == GUID_YAxis ) + { + mObjInfo[i].mType = SI_AXIS; + mObjInfo[i].mInst = SI_YAXIS; + + if (mObjInstance[i].dwFFMaxForce > 0) + mForceFeedbackAxes[mNumForceFeedbackAxes++] = mObjInstance[i].dwOfs; + } + else if ( mObjInstance[i].guidType == GUID_ZAxis ) + { + mObjInfo[i].mType = SI_AXIS; + mObjInfo[i].mInst = SI_ZAXIS; + } + else if ( mObjInstance[i].guidType == GUID_RxAxis ) + { + mObjInfo[i].mType = SI_AXIS; + mObjInfo[i].mInst = SI_RXAXIS; + } + else if ( mObjInstance[i].guidType == GUID_RyAxis ) + { + mObjInfo[i].mType = SI_AXIS; + mObjInfo[i].mInst = SI_RYAXIS; + } + else if ( mObjInstance[i].guidType == GUID_RzAxis ) + { + mObjInfo[i].mType = SI_AXIS; + mObjInfo[i].mInst = SI_RZAXIS; + } + else if ( mObjInstance[i].guidType == GUID_Slider ) + { + mObjInfo[i].mType = SI_AXIS; + mObjInfo[i].mInst = SI_SLIDER; + } + else if ( mObjInstance[i].guidType == GUID_Key ) + { + mObjInfo[i].mType = SI_KEY; + mObjInfo[i].mInst = DIK_to_Key( DIDFT_GETINSTANCE( mObjFormat[i].dwType ) ); + keyCount++; + } + else + { + mObjInfo[i].mType = SI_UNKNOWN; + mObjInfo[i].mInst = (InputObjectInstances)unknownCount++; + } + + // Set the device object's min and max values: + if ( mObjInstance[i].guidType == GUID_Button + || mObjInstance[i].guidType == GUID_Key + || mObjInstance[i].guidType == GUID_POV ) + { + mObjInfo[i].mMin = DIPROPRANGE_NOMIN; + mObjInfo[i].mMax = DIPROPRANGE_NOMAX; + } + else + { + // This is an axis or a slider, so find out its range: + DIPROPRANGE pr; + pr.diph.dwSize = sizeof( pr ); + pr.diph.dwHeaderSize = sizeof( pr.diph ); + pr.diph.dwHow = DIPH_BYID; + pr.diph.dwObj = mObjFormat[i].dwType; + + if ( SUCCEEDED( mDevice->GetProperty( DIPROP_RANGE, &pr.diph ) ) ) + { + mObjInfo[i].mMin = pr.lMin; + mObjInfo[i].mMax = pr.lMax; + } + else + { + mObjInfo[i].mMin = DIPROPRANGE_NOMIN; + mObjInfo[i].mMax = DIPROPRANGE_NOMAX; + } + } + } + +#ifdef LOG_INPUT + Input::log( " %d total objects detected.\n", mObjCount ); + if ( buttonCount ) + Input::log( " %d buttons.\n", buttonCount ); + if ( povCount ) + Input::log( " %d POVs.\n", povCount ); + if ( xAxisCount ) + Input::log( " %d x-axis.\n", xAxisCount ); + if ( yAxisCount ) + Input::log( " %d y-axis.\n", yAxisCount ); + if ( zAxisCount ) + Input::log( " %d z-axis.\n", zAxisCount ); + if ( rAxisCount ) + Input::log( " %d r-axis.\n", rAxisCount ); + if ( uAxisCount ) + Input::log( " %d u-axis.\n", uAxisCount ); + if ( vAxisCount ) + Input::log( " %d v-axis.\n", vAxisCount ); + if ( sliderCount ) + Input::log( " %d sliders.\n", sliderCount ); + if ( keyCount ) + Input::log( " %d keys.\n", keyCount ); + if ( unknownCount ) + Input::log( " %d unknown objects.\n", unknownCount ); + Input::log( "\n" ); +#endif + + return true; +} + +//------------------------------------------------------------------------------ +const char* DInputDevice::getName() +{ +#ifdef UNICODE + static UTF8 buf[512]; + convertUTF16toUTF8(mDeviceInstance.tszInstanceName, buf, sizeof(buf)); + return (const char *)buf; +#else + return mDeviceInstance.tszInstanceName; +#endif +} + +//------------------------------------------------------------------------------ +const char* DInputDevice::getProductName() +{ +#ifdef UNICODE + static UTF8 buf[512]; + convertUTF16toUTF8(mDeviceInstance.tszProductName, buf, sizeof(buf)); + return (const char *)buf; +#else + return mDeviceInstance.tszProductName; +#endif +} + +//------------------------------------------------------------------------------ +bool DInputDevice::process() +{ + if ( mAcquired ) + { + if ( isPolled() ) + return processImmediate(); + else + return processAsync(); + } + + return false; +} + +//------------------------------------------------------------------------------ +bool DInputDevice::processAsync() +{ + DIDEVICEOBJECTDATA eventBuffer[QUEUED_BUFFER_SIZE]; + DWORD numEvents = QUEUED_BUFFER_SIZE; + HRESULT result; + + if ( !mDevice ) + return false; + + do + { + result = mDevice->GetDeviceData( sizeof( DIDEVICEOBJECTDATA ), eventBuffer, &numEvents, 0 ); + + if ( !SUCCEEDED( result ) ) + { + switch ( result ) + { + case DIERR_INPUTLOST: + // Data stream was interrupted, so try to reacquire the device: + mAcquired = false; + acquire(); + break; + + case DIERR_INVALIDPARAM: + Con::errorf( "DInputDevice::processAsync -- Invalid parameter passed to GetDeviceData of the %s input device!", mName ); +#ifdef LOG_INPUT + Input::log( "Invalid parameter passed to GetDeviceData for %s!\n", mName ); +#endif + break; + + case DIERR_NOTACQUIRED: + // We can't get the device, so quit: + mAcquired = false; + // Don't error out - this is actually a natural occurrence... + //Con::errorf( "DInputDevice::processAsync -- GetDeviceData called when %s input device is not acquired!", mName ); +#ifdef LOG_INPUT + Input::log( "GetDeviceData called when %s is not acquired!\n", mName ); +#endif + break; + } + + return false; + } + + // We have buffered input, so act on it: + for ( DWORD i = 0; i < numEvents; i++ ) + buildEvent( findObjInstance( eventBuffer[i].dwOfs ), eventBuffer[i].dwData, eventBuffer[i].dwData ); + + // Check for buffer overflow: + if ( result == DI_BUFFEROVERFLOW ) + { + // This is a problem, but we can keep going... + Con::errorf( "DInputDevice::processAsync -- %s input device's event buffer overflowed!", mName ); +#ifdef LOG_INPUT + Input::log( "%s event buffer overflowed!\n", mName ); +#endif + mNeedSync = true; // Let it know to resync next time through... + } + } + while ( numEvents ); + + return true; +} + +//------------------------------------------------------------------------------ +bool DInputDevice::processImmediate() +{ + if ( !mDevice ) + return false; + + mDevice->Poll(); + + U8* buffer = ( mPrevObjBuffer == mObjBuffer1 ) ? mObjBuffer2 : mObjBuffer1; + HRESULT result = mDevice->GetDeviceState( mObjBufferSize, buffer ); + if ( !SUCCEEDED( result ) ) + { + switch ( result ) + { + case DIERR_INPUTLOST: + // Data stream was interrupted, so try to reacquire the device: + mAcquired = false; + acquire(); + break; + + case DIERR_INVALIDPARAM: + Con::errorf( "DInputDevice::processPolled -- invalid parameter passed to GetDeviceState on %s input device!", mName ); +#ifdef LOG_INPUT + Input::log( "Invalid parameter passed to GetDeviceState on %s.\n", mName ); +#endif + break; + + case DIERR_NOTACQUIRED: + Con::errorf( "DInputDevice::processPolled -- GetDeviceState called when %s input device is not acquired!", mName ); +#ifdef LOG_INPUT + Input::log( "GetDeviceState called when %s is not acquired!\n", mName ); +#endif + break; + + case E_PENDING: + Con::warnf( "DInputDevice::processPolled -- Data not yet available for the %s input device!", mName ); +#ifdef LOG_INPUT + Input::log( "Data pending for %s.", mName ); +#endif + break; + } + + return false; + } + + // Loop through all of the objects and produce events where + // the states have changed: + + // (oldData = 0 prevents a crashing bug in Torque. There is a case where + // Torque accessed oldData without it ever being set.) + S32 newData, oldData = 0; + for ( DWORD i = 0; i < mObjCount; i++ ) + { + if ( mObjFormat[i].dwType & DIDFT_BUTTON ) + { + if ( mPrevObjBuffer ) + { + newData = *( (U8*) ( buffer + mObjFormat[i].dwOfs ) ); + oldData = *( (U8*) ( mPrevObjBuffer + mObjFormat[i].dwOfs ) ); + if ( newData == oldData ) + continue; + } + else + continue; + } + else if ( mObjFormat[i].dwType & DIDFT_POV ) + { + if ( mPrevObjBuffer ) + { + newData = *( (S32*) ( buffer + mObjFormat[i].dwOfs ) ); + oldData = *( (S32*) ( mPrevObjBuffer + mObjFormat[i].dwOfs ) ); + if ( LOWORD( newData ) == LOWORD( oldData ) ) + continue; + } + else + continue; + } + else + { + // report normal axes every time through the loop: + newData = *( (S32*) ( buffer + mObjFormat[i].dwOfs ) ); + } + + // Build an event: + buildEvent( i, newData, oldData ); + } + mPrevObjBuffer = buffer; + + return true; +} + +//------------------------------------------------------------------------------ +DWORD DInputDevice::findObjInstance( DWORD offset ) +{ + DIDEVICEOBJECTINSTANCE *inst = mObjInstance; + for ( U32 i = 0; i < mObjCount; i++, inst++ ) + { + if ( inst->dwOfs == offset ) + return i; + } + + AssertFatal( false, "DInputDevice::findObjInstance -- failed to locate object instance." ); + return 0; +} + +//------------------------------------------------------------------------------ +enum Win32POVDirBits +{ + POV_up = 1 << 0, + POV_right = 1 << 1, + POV_down = 1 << 2, + POV_left = 1 << 3, +}; + +enum Win32POVDirsInQuadrant +{ + POVq_center = 0, + POVq_up = POV_up, + POVq_upright = POV_up | POV_right, + POVq_right = POV_right, + POVq_downright = POV_down | POV_right, + POVq_down = POV_down, + POVq_downleft = POV_down | POV_left, + POVq_left = POV_left, + POVq_upleft = POV_up | POV_left, +}; + +static const U32 Win32POVQuadrantMap[] = +{ + POVq_up, POVq_upright, + POVq_right, POVq_downright, + POVq_down, POVq_downleft, + POVq_left, POVq_upleft +}; + +static U32 _Win32GetPOVDirs(U32 data) +{ + U32 quadrant = (data / 4500) % 8; + U32 dirs = (data == 0xffff) ? POVq_center : Win32POVQuadrantMap[quadrant]; + return dirs; +} + +#if defined(LOG_INPUT) +static void _Win32LogPOVInput(InputEvent &newEvent) +{ + U32 inst = 0xffff; + const char* sstate = ( newEvent.action == SI_MAKE ) ? "pressed" : "released"; + const char* dir = ""; + switch( newEvent.objInst ) + { + case SI_UPOV: + case SI_UPOV2: + dir = "Up"; inst = (newEvent.objInst == SI_UPOV) ? 1 : 2; break; + case SI_RPOV: + case SI_RPOV2: + dir = "Right"; inst = (newEvent.objInst == SI_RPOV) ? 1 : 2; break; + case SI_DPOV: + case SI_DPOV2: + dir = "Down"; inst = (newEvent.objInst == SI_DPOV) ? 1 : 2; break; + case SI_LPOV: + case SI_LPOV2: + dir = "Left"; inst = (newEvent.objInst == SI_LPOV) ? 1 : 2; break; + } + Con::printf( "EVENT (DInput): %s POV %d %s.\n", dir, inst, sstate); +} +#else +#define _Win32LogPOVInput( a ) +#endif + +//------------------------------------------------------------------------------ +bool DInputDevice::buildEvent( DWORD offset, S32 newData, S32 oldData ) +{ + ObjInfo &objInfo = mObjInfo[offset]; + + if ( objInfo.mType == SI_UNKNOWN ) + return false; + + InputEventInfo newEvent; + newEvent.deviceType = (InputDeviceTypes)mDeviceType; + newEvent.deviceInst = mDeviceID; + newEvent.objType = objInfo.mType; + newEvent.objInst = objInfo.mInst; + newEvent.modifier = (InputModifiers)0; + + switch ( newEvent.objType ) + { + case SI_AXIS: + newEvent.action = SI_MOVE; + if ( newEvent.deviceType == MouseDeviceType ) + { + newEvent.fValue = float( newData ); + +#ifdef LOG_INPUT +#ifdef LOG_MOUSEMOVE + if ( newEvent.objInst == SI_XAXIS ) + Input::log( "EVENT (DInput): %s move (%.1f, 0.0).\n", mName, newEvent.fValue ); + else if ( newEvent.objInst == SI_YAXIS ) + Input::log( "EVENT (DInput): %s move (0.0, %.1f).\n", mName, newEvent.fValue ); + else +#endif + if ( newEvent.objInst == SI_ZAXIS ) + Input::log( "EVENT (DInput): %s wheel move %.1f.\n", mName, newEvent.fValue ); +#endif + } + else // Joystick or other device: + { + // Scale to the range -1.0 to 1.0: + if ( objInfo.mMin != DIPROPRANGE_NOMIN && objInfo.mMax != DIPROPRANGE_NOMAX ) + { + float range = float( objInfo.mMax - objInfo.mMin ); + newEvent.fValue = float( ( 2 * newData ) - objInfo.mMax - objInfo.mMin ) / range; + } + else + newEvent.fValue = (F32)newData; + +#ifdef LOG_INPUT + // Keep this commented unless you REALLY want these messages for something-- + // they come once per each iteration of the main game loop. + //switch ( newEvent.objType ) + //{ + //case SI_XAXIS: + //if ( newEvent.fValue < -0.01f || newEvent.fValue > 0.01f ) + //Input::log( "EVENT (DInput): %s X-axis move %.2f.\n", mName, newEvent.fValue ); + //break; + //case SI_YAXIS: + //if ( newEvent.fValue < -0.01f || newEvent.fValue > 0.01f ) + //Input::log( "EVENT (DInput): %s Y-axis move %.2f.\n", mName, newEvent.fValue ); + //break; + //case SI_ZAXIS: + //Input::log( "EVENT (DInput): %s Z-axis move %.1f.\n", mName, newEvent.fValue ); + //break; + //case SI_RXAXIS: + //Input::log( "EVENT (DInput): %s R-axis move %.1f.\n", mName, newEvent.fValue ); + //break; + //case SI_RYAXIS: + //Input::log( "EVENT (DInput): %s U-axis move %.1f.\n", mName, newEvent.fValue ); + //break; + //case SI_RZAXIS: + //Input::log( "EVENT (DInput): %s V-axis move %.1f.\n", mName, newEvent.fValue ); + //break; + //case SI_SLIDER: + //Input::log( "EVENT (DInput): %s slider move %.1f.\n", mName, newEvent.fValue ); + //break; + //}; +#endif + } + + newEvent.postToSignal(Input::smInputEvent); + break; + + case SI_BUTTON: + newEvent.action = ( newData & 0x80 ) ? SI_MAKE : SI_BREAK; + newEvent.fValue = ( newEvent.action == SI_MAKE ) ? 1.0f : 0.0f; + +#ifdef LOG_INPUT + if ( newEvent.action == SI_MAKE ) + Input::log( "EVENT (DInput): %s button%d pressed. MODS:%c%c%c\n", + mName, newEvent.objInst - KEY_BUTTON0, ( smModifierKeys & SI_SHIFT ? 'S' : '.' ), ( smModifierKeys & SI_CTRL ? 'C' : '.' ), ( smModifierKeys & SI_ALT ? 'A' : '.' ) ); + else + Input::log( "EVENT (DInput): %s button%d released.\n", mName, newEvent.objInst - KEY_BUTTON0 ); +#endif + + newEvent.postToSignal(Input::smInputEvent); + break; + + case SI_POV: + // Handle artificial POV up/down/left/right buttons + + // If we're not a polling device, oldData and newData are the same, so "fake" transitions + if(!isPolled()) { + oldData = mPrevPOVPos; + mPrevPOVPos = newData; + } + + newData = LOWORD( newData ); + oldData = LOWORD( oldData ); + + newData = _Win32GetPOVDirs(newData); + oldData = _Win32GetPOVDirs(oldData); + + U32 setkeys = newData & (~oldData); + U32 clearkeys = oldData & (~newData); + U32 objInst = newEvent.objInst; + + if ( setkeys || clearkeys ) + { + if ( clearkeys ) + { + newEvent.action = SI_BREAK; + newEvent.fValue = 0.0f; + // post events for all buttons that need to be cleared. + if( clearkeys & POV_up) + { + newEvent.objInst = ( objInst == 0 ) ? SI_UPOV : SI_UPOV2; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + + } + if( clearkeys & POV_right) + { + newEvent.objInst = ( objInst == 0 ) ? SI_RPOV : SI_RPOV2; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + } + if( clearkeys & POV_down) + { + newEvent.objInst = ( objInst == 0 ) ? SI_DPOV : SI_DPOV; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + } + if( clearkeys & POV_left) + { + newEvent.objInst = ( objInst == 0 ) ? SI_LPOV : SI_LPOV2; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + } + } // clear keys + + if ( setkeys ) + { + newEvent.action = SI_MAKE; + newEvent.fValue = 1.0f; + // post events for all buttons that need to be set. + if( setkeys & POV_up) + { + newEvent.objInst = ( objInst == 0 ) ? SI_UPOV : SI_UPOV2; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + } + if( setkeys & POV_right) + { + newEvent.objInst = ( objInst == 0 ) ? SI_RPOV : SI_RPOV2; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + } + if( setkeys & POV_down) + { + newEvent.objInst = ( objInst == 0 ) ? SI_DPOV : SI_DPOV; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + } + if( setkeys & POV_left) + { + newEvent.objInst = ( objInst == 0 ) ? SI_LPOV : SI_LPOV2; + _Win32LogPOVInput(newEvent); + newEvent.postToSignal(Input::smInputEvent); + } + } // set keys + } + break; + } + + return true; +} + +void DInputDevice::rumble(float x, float y) +{ + LONG rglDirection[2] = { 0, 0 }; + DICONSTANTFORCE cf = { 0 }; + HRESULT result; + + // Now set the new parameters and start the effect immediately. + if (!mForceFeedbackEffect) + { +#ifdef LOG_INPUT + Input::log("DInputDevice::rumbleJoystick - creating constant force feeback effect\n"); +#endif + DIEFFECT eff; + ZeroMemory( &eff, sizeof(eff) ); + eff.dwSize = sizeof(DIEFFECT); + eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + eff.dwDuration = INFINITE; + eff.dwSamplePeriod = 0; + eff.dwGain = DI_FFNOMINALMAX; + eff.dwTriggerButton = DIEB_NOTRIGGER; + eff.dwTriggerRepeatInterval = 0; + eff.cAxes = mNumForceFeedbackAxes; + eff.rgdwAxes = mForceFeedbackAxes; + eff.rglDirection = rglDirection; + eff.lpEnvelope = 0; + eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); + eff.lpvTypeSpecificParams = &cf; + eff.dwStartDelay = 0; + + // Create the prepared effect + if ( FAILED( result = mDevice->CreateEffect( GUID_ConstantForce, &eff, &mForceFeedbackEffect, NULL ) ) ) + { +#ifdef LOG_INPUT + Input::log( "DInputDevice::rumbleJoystick - %s does not support force feedback.\n", mName ); +#endif + Con::errorf( "DInputDevice::rumbleJoystick - %s does not support force feedback.\n", mName ); + return; + } + else + { +#ifdef LOG_INPUT + Input::log( "DInputDevice::rumbleJoystick - %s supports force feedback.\n", mName ); +#endif + Con::printf( "DInputDevice::rumbleJoystick - %s supports force feedback.\n", mName ); + } + } + + // Clamp the input floats to [0 - 1] + x = max(0, min(1, x)); + y = max(0, min(1, y)); + + if ( 1 == mNumForceFeedbackAxes ) + { + cf.lMagnitude = (DWORD)( x * DI_FFNOMINALMAX ); + } + else + { + rglDirection[0] = (DWORD)( x * DI_FFNOMINALMAX ); + rglDirection[1] = (DWORD)( y * DI_FFNOMINALMAX ); + cf.lMagnitude = (DWORD)sqrt( (double)(x * x * DI_FFNOMINALMAX * DI_FFNOMINALMAX + y * y * DI_FFNOMINALMAX * DI_FFNOMINALMAX) ); + } + + DIEFFECT eff; + ZeroMemory( &eff, sizeof(eff) ); + eff.dwSize = sizeof(DIEFFECT); + eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; + eff.dwDuration = INFINITE; + eff.dwSamplePeriod = 0; + eff.dwGain = DI_FFNOMINALMAX; + eff.dwTriggerButton = DIEB_NOTRIGGER; + eff.dwTriggerRepeatInterval = 0; + eff.cAxes = mNumForceFeedbackAxes; + eff.rglDirection = rglDirection; + eff.lpEnvelope = 0; + eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); + eff.lpvTypeSpecificParams = &cf; + eff.dwStartDelay = 0; + + if ( FAILED( result = mForceFeedbackEffect->SetParameters( &eff, DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS | DIEP_START ) ) ) + { + const char* errorString = NULL; + switch ( result ) + { + case DIERR_INPUTLOST: + errorString = "DIERR_INPUTLOST"; + break; + + case DIERR_INVALIDPARAM: + errorString = "DIERR_INVALIDPARAM"; + break; + + case DIERR_NOTACQUIRED: + errorString = "DIERR_NOTACQUIRED"; + break; + + default: + errorString = "Unknown Error"; + } + +#ifdef LOG_INPUT + Input::log( "DInputDevice::rumbleJoystick - %s - Failed to start rumble effect\n", errorString ); +#endif + Con::errorf( "DInputDevice::rumbleJoystick - %s - Failed to start rumble effect\n", errorString ); + } +} + +//------------------------------------------------------------------------------ +// +// This function translates the DirectInput scan code to the associated +// internal key code (as defined in event.h). +// +//------------------------------------------------------------------------------ +InputObjectInstances DIK_to_Key( U8 dikCode ) +{ + switch ( dikCode ) + { + case DIK_ESCAPE: return KEY_ESCAPE; + + case DIK_1: return KEY_1; + case DIK_2: return KEY_2; + case DIK_3: return KEY_3; + case DIK_4: return KEY_4; + case DIK_5: return KEY_5; + case DIK_6: return KEY_6; + case DIK_7: return KEY_7; + case DIK_8: return KEY_8; + case DIK_9: return KEY_9; + case DIK_0: return KEY_0; + + case DIK_MINUS: return KEY_MINUS; + case DIK_EQUALS: return KEY_EQUALS; + case DIK_BACK: return KEY_BACKSPACE; + case DIK_TAB: return KEY_TAB; + + case DIK_Q: return KEY_Q; + case DIK_W: return KEY_W; + case DIK_E: return KEY_E; + case DIK_R: return KEY_R; + case DIK_T: return KEY_T; + case DIK_Y: return KEY_Y; + case DIK_U: return KEY_U; + case DIK_I: return KEY_I; + case DIK_O: return KEY_O; + case DIK_P: return KEY_P; + + case DIK_LBRACKET: return KEY_LBRACKET; + case DIK_RBRACKET: return KEY_RBRACKET; + case DIK_RETURN: return KEY_RETURN; + case DIK_LCONTROL: return KEY_LCONTROL; + + case DIK_A: return KEY_A; + case DIK_S: return KEY_S; + case DIK_D: return KEY_D; + case DIK_F: return KEY_F; + case DIK_G: return KEY_G; + case DIK_H: return KEY_H; + case DIK_J: return KEY_J; + case DIK_K: return KEY_K; + case DIK_L: return KEY_L; + + case DIK_SEMICOLON: return KEY_SEMICOLON; + case DIK_APOSTROPHE: return KEY_APOSTROPHE; + case DIK_GRAVE: return KEY_TILDE; + case DIK_LSHIFT: return KEY_LSHIFT; + case DIK_BACKSLASH: return KEY_BACKSLASH; + + case DIK_Z: return KEY_Z; + case DIK_X: return KEY_X; + case DIK_C: return KEY_C; + case DIK_V: return KEY_V; + case DIK_B: return KEY_B; + case DIK_N: return KEY_N; + case DIK_M: return KEY_M; + + case DIK_COMMA: return KEY_COMMA; + case DIK_PERIOD: return KEY_PERIOD; + case DIK_SLASH: return KEY_SLASH; + case DIK_RSHIFT: return KEY_RSHIFT; + case DIK_MULTIPLY: return KEY_MULTIPLY; + case DIK_LALT: return KEY_LALT; + case DIK_SPACE: return KEY_SPACE; + case DIK_CAPSLOCK: return KEY_CAPSLOCK; + + case DIK_F1: return KEY_F1; + case DIK_F2: return KEY_F2; + case DIK_F3: return KEY_F3; + case DIK_F4: return KEY_F4; + case DIK_F5: return KEY_F5; + case DIK_F6: return KEY_F6; + case DIK_F7: return KEY_F7; + case DIK_F8: return KEY_F8; + case DIK_F9: return KEY_F9; + case DIK_F10: return KEY_F10; + + case DIK_NUMLOCK: return KEY_NUMLOCK; + case DIK_SCROLL: return KEY_SCROLLLOCK; + + case DIK_NUMPAD7: return KEY_NUMPAD7; + case DIK_NUMPAD8: return KEY_NUMPAD8; + case DIK_NUMPAD9: return KEY_NUMPAD9; + case DIK_SUBTRACT: return KEY_SUBTRACT; + + case DIK_NUMPAD4: return KEY_NUMPAD4; + case DIK_NUMPAD5: return KEY_NUMPAD5; + case DIK_NUMPAD6: return KEY_NUMPAD6; + case DIK_ADD: return KEY_ADD; + + case DIK_NUMPAD1: return KEY_NUMPAD1; + case DIK_NUMPAD2: return KEY_NUMPAD2; + case DIK_NUMPAD3: return KEY_NUMPAD3; + case DIK_NUMPAD0: return KEY_NUMPAD0; + case DIK_DECIMAL: return KEY_DECIMAL; + + case DIK_F11: return KEY_F11; + case DIK_F12: return KEY_F12; + case DIK_F13: return KEY_F13; + case DIK_F14: return KEY_F14; + case DIK_F15: return KEY_F15; + + case DIK_KANA: return KEY_NULL; + case DIK_CONVERT: return KEY_NULL; + case DIK_NOCONVERT: return KEY_NULL; + case DIK_YEN: return KEY_NULL; + case DIK_NUMPADEQUALS: return KEY_NULL; + case DIK_CIRCUMFLEX: return KEY_NULL; + case DIK_AT: return KEY_NULL; + case DIK_COLON: return KEY_NULL; + case DIK_UNDERLINE: return KEY_NULL; + case DIK_KANJI: return KEY_NULL; + case DIK_STOP: return KEY_NULL; + case DIK_AX: return KEY_NULL; + case DIK_UNLABELED: return KEY_NULL; + + case DIK_NUMPADENTER: return KEY_NUMPADENTER; + case DIK_RCONTROL: return KEY_RCONTROL; + case DIK_NUMPADCOMMA: return KEY_SEPARATOR; + case DIK_DIVIDE: return KEY_DIVIDE; + case DIK_SYSRQ: return KEY_PRINT; + case DIK_RALT: return KEY_RALT; + case DIK_PAUSE: return KEY_PAUSE; + + case DIK_HOME: return KEY_HOME; + case DIK_UP: return KEY_UP; + case DIK_PGUP: return KEY_PAGE_UP; + case DIK_LEFT: return KEY_LEFT; + case DIK_RIGHT: return KEY_RIGHT; + case DIK_END: return KEY_END; + case DIK_DOWN: return KEY_DOWN; + case DIK_PGDN: return KEY_PAGE_DOWN; + case DIK_INSERT: return KEY_INSERT; + case DIK_DELETE: return KEY_DELETE; + + case DIK_LWIN: return KEY_WIN_LWINDOW; + case DIK_RWIN: return KEY_WIN_RWINDOW; + case DIK_APPS: return KEY_WIN_APPS; + case DIK_OEM_102: return KEY_OEM_102; + } + + return KEY_NULL; +} + +//------------------------------------------------------------------------------ +// +// This function translates an internal key code to the associated +// DirectInput scan code +// +//------------------------------------------------------------------------------ +U8 Key_to_DIK( U16 keyCode ) +{ + switch ( keyCode ) + { + case KEY_BACKSPACE: return DIK_BACK; + case KEY_TAB: return DIK_TAB; + case KEY_RETURN: return DIK_RETURN; + //KEY_CONTROL: + //KEY_ALT: + //KEY_SHIFT: + case KEY_PAUSE: return DIK_PAUSE; + case KEY_CAPSLOCK: return DIK_CAPSLOCK; + case KEY_ESCAPE: return DIK_ESCAPE; + + case KEY_SPACE: return DIK_SPACE; + case KEY_PAGE_DOWN: return DIK_PGDN; + case KEY_PAGE_UP: return DIK_PGUP; + case KEY_END: return DIK_END; + case KEY_HOME: return DIK_HOME; + case KEY_LEFT: return DIK_LEFT; + case KEY_UP: return DIK_UP; + case KEY_RIGHT: return DIK_RIGHT; + case KEY_DOWN: return DIK_DOWN; + case KEY_PRINT: return DIK_SYSRQ; + case KEY_INSERT: return DIK_INSERT; + case KEY_DELETE: return DIK_DELETE; + case KEY_HELP: return 0; + + case KEY_0: return DIK_0; + case KEY_1: return DIK_1; + case KEY_2: return DIK_2; + case KEY_3: return DIK_3; + case KEY_4: return DIK_4; + case KEY_5: return DIK_5; + case KEY_6: return DIK_6; + case KEY_7: return DIK_7; + case KEY_8: return DIK_8; + case KEY_9: return DIK_9; + + case KEY_A: return DIK_A; + case KEY_B: return DIK_B; + case KEY_C: return DIK_C; + case KEY_D: return DIK_D; + case KEY_E: return DIK_E; + case KEY_F: return DIK_F; + case KEY_G: return DIK_G; + case KEY_H: return DIK_H; + case KEY_I: return DIK_I; + case KEY_J: return DIK_J; + case KEY_K: return DIK_K; + case KEY_L: return DIK_L; + case KEY_M: return DIK_M; + case KEY_N: return DIK_N; + case KEY_O: return DIK_O; + case KEY_P: return DIK_P; + case KEY_Q: return DIK_Q; + case KEY_R: return DIK_R; + case KEY_S: return DIK_S; + case KEY_T: return DIK_T; + case KEY_U: return DIK_U; + case KEY_V: return DIK_V; + case KEY_W: return DIK_W; + case KEY_X: return DIK_X; + case KEY_Y: return DIK_Y; + case KEY_Z: return DIK_Z; + + case KEY_TILDE: return DIK_GRAVE; + case KEY_MINUS: return DIK_MINUS; + case KEY_EQUALS: return DIK_EQUALS; + case KEY_LBRACKET: return DIK_LBRACKET; + case KEY_RBRACKET: return DIK_RBRACKET; + case KEY_BACKSLASH: return DIK_BACKSLASH; + case KEY_SEMICOLON: return DIK_SEMICOLON; + case KEY_APOSTROPHE: return DIK_APOSTROPHE; + case KEY_COMMA: return DIK_COMMA; + case KEY_PERIOD: return DIK_PERIOD; + case KEY_SLASH: return DIK_SLASH; + + case KEY_NUMPAD0: return DIK_NUMPAD0; + case KEY_NUMPAD1: return DIK_NUMPAD1; + case KEY_NUMPAD2: return DIK_NUMPAD2; + case KEY_NUMPAD3: return DIK_NUMPAD3; + case KEY_NUMPAD4: return DIK_NUMPAD4; + case KEY_NUMPAD5: return DIK_NUMPAD5; + case KEY_NUMPAD6: return DIK_NUMPAD6; + case KEY_NUMPAD7: return DIK_NUMPAD7; + case KEY_NUMPAD8: return DIK_NUMPAD8; + case KEY_NUMPAD9: return DIK_NUMPAD9; + case KEY_MULTIPLY: return DIK_MULTIPLY; + case KEY_ADD: return DIK_ADD; + case KEY_SEPARATOR: return DIK_NUMPADCOMMA; + case KEY_SUBTRACT: return DIK_SUBTRACT; + case KEY_DECIMAL: return DIK_DECIMAL; + case KEY_DIVIDE: return DIK_DIVIDE; + case KEY_NUMPADENTER: return DIK_NUMPADENTER; + + case KEY_F1: return DIK_F1; + case KEY_F2: return DIK_F2; + case KEY_F3: return DIK_F3; + case KEY_F4: return DIK_F4; + case KEY_F5: return DIK_F5; + case KEY_F6: return DIK_F6; + case KEY_F7: return DIK_F7; + case KEY_F8: return DIK_F8; + case KEY_F9: return DIK_F9; + case KEY_F10: return DIK_F10; + case KEY_F11: return DIK_F11; + case KEY_F12: return DIK_F12; + case KEY_F13: return DIK_F13; + case KEY_F14: return DIK_F14; + case KEY_F15: return DIK_F15; + case KEY_F16: + case KEY_F17: + case KEY_F18: + case KEY_F19: + case KEY_F20: + case KEY_F21: + case KEY_F22: + case KEY_F23: + case KEY_F24: return 0; + + case KEY_NUMLOCK: return DIK_NUMLOCK; + case KEY_SCROLLLOCK: return DIK_SCROLL; + case KEY_LCONTROL: return DIK_LCONTROL; + case KEY_RCONTROL: return DIK_RCONTROL; + case KEY_LALT: return DIK_LALT; + case KEY_RALT: return DIK_RALT; + case KEY_LSHIFT: return DIK_LSHIFT; + case KEY_RSHIFT: return DIK_RSHIFT; + + case KEY_WIN_LWINDOW: return DIK_LWIN; + case KEY_WIN_RWINDOW: return DIK_RWIN; + case KEY_WIN_APPS: return DIK_APPS; + case KEY_OEM_102: return DIK_OEM_102; + + }; + + return 0; +} + +#ifdef LOG_INPUT +//------------------------------------------------------------------------------ +const char* getKeyName( U16 key ) +{ + switch ( key ) + { + case KEY_BACKSPACE: return "Backspace"; + case KEY_TAB: return "Tab"; + case KEY_RETURN: return "Return"; + case KEY_PAUSE: return "Pause"; + case KEY_CAPSLOCK: return "CapsLock"; + case KEY_ESCAPE: return "Esc"; + + case KEY_SPACE: return "SpaceBar"; + case KEY_PAGE_DOWN: return "PageDown"; + case KEY_PAGE_UP: return "PageUp"; + case KEY_END: return "End"; + case KEY_HOME: return "Home"; + case KEY_LEFT: return "Left"; + case KEY_UP: return "Up"; + case KEY_RIGHT: return "Right"; + case KEY_DOWN: return "Down"; + case KEY_PRINT: return "PrintScreen"; + case KEY_INSERT: return "Insert"; + case KEY_DELETE: return "Delete"; + case KEY_HELP: return "Help"; + + case KEY_NUMPAD0: return "Numpad 0"; + case KEY_NUMPAD1: return "Numpad 1"; + case KEY_NUMPAD2: return "Numpad 2"; + case KEY_NUMPAD3: return "Numpad 3"; + case KEY_NUMPAD4: return "Numpad 4"; + case KEY_NUMPAD5: return "Numpad 5"; + case KEY_NUMPAD6: return "Numpad 6"; + case KEY_NUMPAD7: return "Numpad 7"; + case KEY_NUMPAD8: return "Numpad 8"; + case KEY_NUMPAD9: return "Numpad 9"; + case KEY_MULTIPLY: return "Multiply"; + case KEY_ADD: return "Add"; + case KEY_SEPARATOR: return "Separator"; + case KEY_SUBTRACT: return "Subtract"; + case KEY_DECIMAL: return "Decimal"; + case KEY_DIVIDE: return "Divide"; + case KEY_NUMPADENTER: return "Numpad Enter"; + + case KEY_F1: return "F1"; + case KEY_F2: return "F2"; + case KEY_F3: return "F3"; + case KEY_F4: return "F4"; + case KEY_F5: return "F5"; + case KEY_F6: return "F6"; + case KEY_F7: return "F7"; + case KEY_F8: return "F8"; + case KEY_F9: return "F9"; + case KEY_F10: return "F10"; + case KEY_F11: return "F11"; + case KEY_F12: return "F12"; + case KEY_F13: return "F13"; + case KEY_F14: return "F14"; + case KEY_F15: return "F15"; + case KEY_F16: return "F16"; + case KEY_F17: return "F17"; + case KEY_F18: return "F18"; + case KEY_F19: return "F19"; + case KEY_F20: return "F20"; + case KEY_F21: return "F21"; + case KEY_F22: return "F22"; + case KEY_F23: return "F23"; + case KEY_F24: return "F24"; + + case KEY_NUMLOCK: return "NumLock"; + case KEY_SCROLLLOCK: return "ScrollLock"; + case KEY_LCONTROL: return "LCtrl"; + case KEY_RCONTROL: return "RCtrl"; + case KEY_LALT: return "LAlt"; + case KEY_RALT: return "RAlt"; + case KEY_LSHIFT: return "LShift"; + case KEY_RSHIFT: return "RShift"; + + case KEY_WIN_LWINDOW: return "LWin"; + case KEY_WIN_RWINDOW: return "RWin"; + case KEY_WIN_APPS: return "Apps"; + } + + static char returnString[5]; + dSprintf( returnString, sizeof( returnString ), "%c", Input::getAscii( key, STATE_UPPER ) ); + return returnString; +} +#endif // LOG_INPUT + +//------------------------------------------------------------------------------ +const char* DInputDevice::getJoystickAxesString() +{ + if ( mDeviceType != JoystickDeviceType ) + return( "" ); + + U32 axisCount = mDeviceCaps.dwAxes; + char buf[64]; + dSprintf( buf, sizeof( buf ), "%d", axisCount ); + for ( U32 i = 0; i < mObjCount; i++ ) + { + switch ( mObjInfo[i].mInst ) + { + case SI_XAXIS: + dStrcat( buf, "\tX" ); + break; + case SI_YAXIS: + dStrcat( buf, "\tY" ); + break; + case SI_ZAXIS: + dStrcat( buf, "\tZ" ); + break; + case SI_RXAXIS: + dStrcat( buf, "\tR" ); + break; + case SI_RYAXIS: + dStrcat( buf, "\tU" ); + break; + case SI_RZAXIS: + dStrcat( buf, "\tV" ); + break; + case SI_SLIDER: + dStrcat( buf, "\tS" ); + break; + } + } + + char* returnString = Con::getReturnBuffer( dStrlen( buf ) + 1 ); + dStrcpy( returnString, buf ); + return( returnString ); +} + +//------------------------------------------------------------------------------ +bool DInputDevice::joystickDetected() +{ + return( smDeviceCount[ JoystickDeviceType ] > 0 ); +} + + + diff --git a/platformWin32/winDInputDevice.h b/platformWin32/winDInputDevice.h new file mode 100644 index 0000000..f29fdc9 --- /dev/null +++ b/platformWin32/winDInputDevice.h @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WINDINPUTDEVICE_H_ +#define _WINDINPUTDEVICE_H_ + +#ifndef _PLATFORMWIN32_H_ +#include "platformWin32/platformWin32.h" +#endif +#ifndef _PLATFORMINPUT_H_ +#include "platform/platformInput.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +#define DIRECTINPUT_VERSION 0x0800 +#include + + +class DInputDevice : public InputDevice +{ + public: + static LPDIRECTINPUT8 smDInputInterface; + + protected: + enum Constants + { + QUEUED_BUFFER_SIZE = 128, + + SIZEOF_BUTTON = 1, // size of an object's data in bytes + SIZEOF_KEY = 1, + SIZEOF_AXIS = 4, + SIZEOF_POV = 4, + }; + + static U8 smDeviceCount[ NUM_INPUT_DEVICE_TYPES ]; + static bool smInitialized; + + /// Are we an XInput device? + bool mIsXInput; + + //-------------------------------------- + LPDIRECTINPUTDEVICE8 mDevice; + DIDEVICEINSTANCE mDeviceInstance; + DIDEVCAPS mDeviceCaps; + U8 mDeviceType; + U8 mDeviceID; + + bool mAcquired; + bool mNeedSync; + + LPDIRECTINPUTEFFECT mForceFeedbackEffect; ///< Holds our DirectInput FF Effect + DWORD mNumForceFeedbackAxes; ///< # axes (we only support 0, 1, or 2 + DWORD mForceFeedbackAxes[2]; ///< Force Feedback axes offsets into DIOBJECTFORMAT + + //-------------------------------------- + DIDEVICEOBJECTINSTANCE* mObjInstance; + DIOBJECTDATAFORMAT* mObjFormat; + ObjInfo* mObjInfo; + U8* mObjBuffer1; // polled device input buffers + U8* mObjBuffer2; + U8* mPrevObjBuffer; // points to buffer 1 or 2 + + // Hack for POV + S32 mPrevPOVPos; + + U32 mObjBufferSize; // size of objBuffer* + U32 mObjCount; // number of objects on this device + U32 mObjEnumCount; // used during enumeration ONLY + U32 mObjBufferOfs; // used during enumeration ONLY + + static BOOL CALLBACK EnumObjectsProc( const DIDEVICEOBJECTINSTANCE *doi, LPVOID pvRef ); + + bool enumerateObjects(); + bool processAsync(); + bool processImmediate(); + + DWORD findObjInstance( DWORD offset ); + bool buildEvent( DWORD offset, S32 newData, S32 oldData ); + + public: + DInputDevice( const DIDEVICEINSTANCE* deviceInst ); + ~DInputDevice(); + + static void init(); + + bool create(); + void destroy(); + + bool acquire(); + bool unacquire(); + + bool isAcquired(); + bool isPolled(); + + U8 getDeviceType(); + U8 getDeviceID(); + + const char* getName(); + const char* getProductName(); + + // Constant Effect Force Feedback + void rumble( float x, float y ); + + // Console interface functions: + const char* getJoystickAxesString(); + static bool joystickDetected(); + // + + bool process(); +}; + +//------------------------------------------------------------------------------ +inline bool DInputDevice::isAcquired() +{ + return mAcquired; +} + +//------------------------------------------------------------------------------ +inline bool DInputDevice::isPolled() +{ + //return true; + return ( mDeviceCaps.dwFlags & DIDC_POLLEDDEVICE ) != 0; +} + +//------------------------------------------------------------------------------ +inline U8 DInputDevice::getDeviceType() +{ + return mDeviceType; +} + +//------------------------------------------------------------------------------ +inline U8 DInputDevice::getDeviceID() +{ + return mDeviceID; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +InputObjectInstances DIK_to_Key( U8 dikCode ); +U8 Key_to_DIK( U16 keyCode ); +#endif // _H_WINDINPUTDEVICE_ diff --git a/platformWin32/winDirectInput.cpp b/platformWin32/winDirectInput.cpp new file mode 100644 index 0000000..b8716e1 --- /dev/null +++ b/platformWin32/winDirectInput.cpp @@ -0,0 +1,880 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "platformWin32/winDirectInput.h" +#include "platformWin32/winDInputDevice.h" +#include "platform/event.h" +#include "console/console.h" +#include "console/consoleTypes.h" +#include "sim/actionMap.h" + +#include + +//------------------------------------------------------------------------------ +// Static class variables: +bool DInputManager::smJoystickEnabled = true; +bool DInputManager::smXInputEnabled = true; + +// Type definitions: +typedef HRESULT (WINAPI* FN_DirectInputCreate)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID *ppvOut, LPUNKNOWN punkOuter); + +//------------------------------------------------------------------------------ +DInputManager::DInputManager() +{ + mEnabled = false; + mDInputLib = NULL; + mDInputInterface = NULL; + mJoystickActive = mXInputActive = true; + mXInputLib = NULL; + + for(S32 i=0; i<4; i++) + mLastDisconnectTime[i] = -1; +} + +//------------------------------------------------------------------------------ +void DInputManager::init() +{ + Con::addVariable( "pref::Input::JoystickEnabled", TypeBool, &smJoystickEnabled ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::enable() +{ + FN_DirectInputCreate fnDInputCreate; + + disable(); + + // Dynamically load the XInput 9 DLL and cache function pointers to the + // two APIs we use +#ifdef LOG_INPUT + Input::log( "Enabling XInput...\n" ); +#endif + mXInputLib = LoadLibrary( dT("xinput9_1_0.dll") ); + if ( mXInputLib ) + { + mfnXInputGetState = (FN_XInputGetState) GetProcAddress( mXInputLib, "XInputGetState" ); + mfnXInputSetState = (FN_XInputSetState) GetProcAddress( mXInputLib, "XInputSetState" ); + if ( mfnXInputGetState && mfnXInputSetState ) + { +#ifdef LOG_INPUT + Input::log( "XInput detected.\n" ); +#endif + mXInputStateReset = true; + mXInputDeadZoneOn = true; + smXInputEnabled = true; + } + } + else + { +#ifdef LOG_INPUT + Input::log( "XInput was not found.\n" ); + mXInputStateReset = false; + mXInputDeadZoneOn = false; +#endif + } + +#ifdef LOG_INPUT + Input::log( "Enabling DirectInput...\n" ); +#endif + mDInputLib = LoadLibrary( dT("DInput8.dll") ); + if ( mDInputLib ) + { + fnDInputCreate = (FN_DirectInputCreate) GetProcAddress( mDInputLib, "DirectInput8Create" ); + if ( fnDInputCreate ) + { + bool result = SUCCEEDED( fnDInputCreate( winState.appInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, reinterpret_cast(&mDInputInterface), NULL )); + if ( result ) + { +#ifdef LOG_INPUT + Input::log( "DirectX 8 or greater detected.\n" ); +#endif + } + + if ( result ) + { + enumerateDevices(); + mEnabled = true; + return true; + } + } + } + + disable(); + +#ifdef LOG_INPUT + Input::log( "Failed to enable DirectInput.\n" ); +#endif + + return false; +} + +//------------------------------------------------------------------------------ + +void DInputManager::disable() +{ + unacquire( SI_ANY, SI_ANY ); + + DInputDevice* dptr; + iterator ptr = begin(); + while ( ptr != end() ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr ) + { + removeObject( dptr ); + //if ( dptr->getManager() ) + //dptr->getManager()->unregisterObject( dptr ); + delete dptr; + ptr = begin(); + } + else + ptr++; + } + + if ( mDInputInterface ) + { + mDInputInterface->Release(); + mDInputInterface = NULL; + } + + if ( mDInputLib ) + { + FreeLibrary( mDInputLib ); + mDInputLib = NULL; + } + + if ( mfnXInputGetState ) + { + mXInputStateReset = true; + mfnXInputGetState = NULL; + mfnXInputSetState = NULL; + } + + if ( mXInputLib ) + { + FreeLibrary( mXInputLib ); + mXInputLib = NULL; + } + + mEnabled = false; +} + +//------------------------------------------------------------------------------ +void DInputManager::onDeleteNotify( SimObject* object ) +{ + Parent::onDeleteNotify( object ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + acquire( SI_ANY, SI_ANY ); + return true; +} + +//------------------------------------------------------------------------------ +void DInputManager::onRemove() +{ + unacquire( SI_ANY, SI_ANY ); + Parent::onRemove(); +} + +//------------------------------------------------------------------------------ +bool DInputManager::acquire( U8 deviceType, U8 deviceID ) +{ + bool anyActive = false; + DInputDevice* dptr; + for ( iterator ptr = begin(); ptr != end(); ptr++ ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr + && ( ( deviceType == SI_ANY ) || ( dptr->getDeviceType() == deviceType ) ) + && ( ( deviceID == SI_ANY ) || ( dptr->getDeviceID() == deviceID ) ) ) + { + if ( dptr->acquire() ) + anyActive = true; + } + } + + return anyActive; +} + +//------------------------------------------------------------------------------ +void DInputManager::unacquire( U8 deviceType, U8 deviceID ) +{ + DInputDevice* dptr; + for ( iterator ptr = begin(); ptr != end(); ptr++ ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr + && ( ( deviceType == SI_ANY ) || ( dptr->getDeviceType() == deviceType ) ) + && ( ( deviceID == SI_ANY ) || ( dptr->getDeviceID() == deviceID ) ) ) + dptr->unacquire(); + } +} + +//------------------------------------------------------------------------------ +void DInputManager::process() +{ + // Because the XInput APIs manage everything for all four controllers trivially, + // we don't need the abstraction of a DInputDevice for an unknown number of devices + // with varying buttons & whistles... nice and easy! + if ( isXInputActive() ) + processXInput(); + + DInputDevice* dptr; + for ( iterator ptr = begin(); ptr != end(); ptr++ ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr ) + dptr->process(); + } +} + +//------------------------------------------------------------------------------ +void DInputManager::enumerateDevices() +{ + if ( mDInputInterface ) + { +#ifdef LOG_INPUT + Input::log( "Enumerating input devices...\n" ); +#endif + + DInputDevice::init(); + DInputDevice::smDInputInterface = mDInputInterface; + mDInputInterface->EnumDevices( DI8DEVTYPE_KEYBOARD, EnumDevicesProc, this, DIEDFL_ATTACHEDONLY ); + mDInputInterface->EnumDevices( DI8DEVTYPE_MOUSE, EnumDevicesProc, this, DIEDFL_ATTACHEDONLY ); + mDInputInterface->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumDevicesProc, this, DIEDFL_ATTACHEDONLY ); + } +} + +//------------------------------------------------------------------------------ +BOOL CALLBACK DInputManager::EnumDevicesProc( const DIDEVICEINSTANCE* pddi, LPVOID pvRef ) +{ + DInputManager* manager = (DInputManager*) pvRef; + DInputDevice* newDevice = new DInputDevice( pddi ); + manager->addObject( newDevice ); + if ( !newDevice->create() ) + { + manager->removeObject( newDevice ); + delete newDevice; + } + + return (DIENUM_CONTINUE); +} + +//------------------------------------------------------------------------------ +bool DInputManager::enableJoystick() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled() ) + return( false ); + + if ( smJoystickEnabled && mgr->isJoystickActive() ) + return( true ); + + smJoystickEnabled = true; + if ( Input::isActive() ) + smJoystickEnabled = mgr->activateJoystick(); + + if ( smJoystickEnabled ) + { + Con::printf( "DirectInput joystick enabled." ); +#ifdef LOG_INPUT + Input::log( "Joystick enabled.\n" ); +#endif + } + else + { + Con::warnf( "DirectInput joystick failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Joystick failed to enable!\n" ); +#endif + } + + return( smJoystickEnabled ); +} + +//------------------------------------------------------------------------------ +void DInputManager::disableJoystick() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled() || !smJoystickEnabled ) + return; + + mgr->deactivateJoystick(); + smJoystickEnabled = false; + Con::printf( "DirectInput joystick disabled." ); +#ifdef LOG_INPUT + Input::log( "Joystick disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool DInputManager::isJoystickEnabled() +{ + return( smJoystickEnabled ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::activateJoystick() +{ + if ( !mEnabled || !Input::isActive() || !smJoystickEnabled ) + return( false ); + + mJoystickActive = acquire( JoystickDeviceType, SI_ANY ); +#ifdef LOG_INPUT + Input::log( mJoystickActive ? "Joystick activated.\n" : "Joystick failed to activate!\n" ); +#endif + return( mJoystickActive ); +} + +//------------------------------------------------------------------------------ +void DInputManager::deactivateJoystick() +{ + if ( mEnabled && mJoystickActive ) + { + unacquire( JoystickDeviceType, SI_ANY ); + mJoystickActive = false; +#ifdef LOG_INPUT + Input::log( "Joystick deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +const char* DInputManager::getJoystickAxesString( U32 deviceID ) +{ + DInputDevice* dptr; + for ( iterator ptr = begin(); ptr != end(); ptr++ ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr && ( dptr->getDeviceType() == JoystickDeviceType ) && ( dptr->getDeviceID() == deviceID ) ) + return( dptr->getJoystickAxesString() ); + } + + return( "" ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::enableXInput() +{ + // Largely, this series of functions is identical to the Joystick versions, + // except that XInput cannot be "activated" or "deactivated". You either have + // the DLL or you don't. Beyond that, you have up to four controllers + // connected at any given time + + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled() ) + return( false ); + + if ( mgr->isXInputActive() ) + return( true ); + + if ( Input::isActive() ) + mgr->activateXInput(); + + if ( smXInputEnabled ) + { + Con::printf( "XInput enabled." ); +#ifdef LOG_INPUT + Input::log( "XInput enabled.\n" ); +#endif + } + else + { + Con::warnf( "XInput failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "XInput failed to enable!\n" ); +#endif + } + + return( smXInputEnabled ); +} + +//------------------------------------------------------------------------------ +void DInputManager::disableXInput() +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( !mgr || !mgr->isEnabled()) + return; + + mgr->deactivateXInput(); + Con::printf( "XInput disabled." ); +#ifdef LOG_INPUT + Input::log( "XInput disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool DInputManager::isXInputEnabled() +{ + return( smXInputEnabled ); +} + +//------------------------------------------------------------------------------ +bool DInputManager::isXInputConnected(int controllerID) +{ + return( mXInputStateNew[controllerID].bConnected ); +} + +int DInputManager::getXInputState(int controllerID, int property, bool current) +{ + int retVal; + + switch(property) + { +#define CHECK_PROP_ANALOG(prop, stateTest) \ + case prop: (current) ? retVal = mXInputStateNew[controllerID].state.Gamepad.##stateTest : retVal = mXInputStateOld[controllerID].state.Gamepad.##stateTest; return retVal; + + CHECK_PROP_ANALOG(XI_THUMBLX, sThumbLX) + CHECK_PROP_ANALOG(XI_THUMBLY, sThumbLY) + CHECK_PROP_ANALOG(XI_THUMBRX, sThumbRX) + CHECK_PROP_ANALOG(XI_THUMBRY, sThumbRY) + CHECK_PROP_ANALOG(XI_LEFT_TRIGGER, bLeftTrigger) + CHECK_PROP_ANALOG(XI_RIGHT_TRIGGER, bRightTrigger) + +#undef CHECK_PROP_ANALOG + +#define CHECK_PROP_DIGITAL(prop, stateTest) \ + case prop: (current) ? retVal = (( mXInputStateNew[controllerID].state.Gamepad.wButtons & stateTest ) != 0 ) : retVal = (( mXInputStateOld[controllerID].state.Gamepad.wButtons & stateTest ) != 0 ); return retVal; + + CHECK_PROP_DIGITAL(SI_UPOV, XINPUT_GAMEPAD_DPAD_UP) + CHECK_PROP_DIGITAL(SI_DPOV, XINPUT_GAMEPAD_DPAD_DOWN) + CHECK_PROP_DIGITAL(SI_LPOV, XINPUT_GAMEPAD_DPAD_LEFT) + CHECK_PROP_DIGITAL(SI_RPOV, XINPUT_GAMEPAD_DPAD_RIGHT) + CHECK_PROP_DIGITAL(XI_START, XINPUT_GAMEPAD_START) + CHECK_PROP_DIGITAL(XI_BACK, XINPUT_GAMEPAD_BACK) + CHECK_PROP_DIGITAL(XI_LEFT_THUMB, XINPUT_GAMEPAD_LEFT_THUMB) + CHECK_PROP_DIGITAL(XI_RIGHT_THUMB, XINPUT_GAMEPAD_RIGHT_THUMB) + CHECK_PROP_DIGITAL(XI_LEFT_SHOULDER, XINPUT_GAMEPAD_LEFT_SHOULDER) + CHECK_PROP_DIGITAL(XI_RIGHT_SHOULDER, XINPUT_GAMEPAD_RIGHT_SHOULDER) + CHECK_PROP_DIGITAL(XI_A, XINPUT_GAMEPAD_A) + CHECK_PROP_DIGITAL(XI_B, XINPUT_GAMEPAD_B) + CHECK_PROP_DIGITAL(XI_X, XINPUT_GAMEPAD_X) + CHECK_PROP_DIGITAL(XI_Y, XINPUT_GAMEPAD_Y) + +#undef CHECK_PROP_DIGITAL + } + + return -1; +} + +//------------------------------------------------------------------------------ +bool DInputManager::activateXInput() +{ + if ( !mEnabled || !Input::isActive()) + return( false ); + + mXInputActive = true; //acquire( GamepadDeviceType, SI_ANY ); +#ifdef LOG_INPUT + Input::log( mXInputActive ? "XInput activated.\n" : "XInput failed to activate!\n" ); +#endif + return( mXInputActive ); +} + +//------------------------------------------------------------------------------ +void DInputManager::deactivateXInput() +{ + if ( mEnabled && mXInputActive ) + { + unacquire( GamepadDeviceType, SI_ANY ); + mXInputActive = false; +#ifdef LOG_INPUT + Input::log( "XInput deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +bool DInputManager::rumble( const char *pDeviceName, float x, float y ) +{ + // Determine the device + U32 deviceType; + U32 deviceInst; + + // Find the requested device + if ( !ActionMap::getDeviceTypeAndInstance(pDeviceName, deviceType, deviceInst) ) + { + Con::printf("DInputManager::rumble: unknown device: %s", pDeviceName); + return false; + } + + // clamp (x, y) to the range of [0 ... 1] each + x = mClampF(x, 0.f, 1.f); + y = mClampF(y, 0.f, 1.f); + + // Easy path for xinput devices. + if(deviceType == GamepadDeviceType) + { + XINPUT_VIBRATION vibration; + vibration.wLeftMotorSpeed = static_cast(x * 65535); + vibration.wRightMotorSpeed = static_cast(y * 65535); + return ( mfnXInputSetState(deviceInst, &vibration) == ERROR_SUCCESS ); + } + + switch ( deviceType ) + { + case JoystickDeviceType: + + // Find the device and shake it! + DInputDevice* dptr; + for ( iterator ptr = begin(); ptr != end(); ptr++ ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr ) + { + if (deviceType == dptr->getDeviceType() && deviceInst == dptr->getDeviceID()) + { + dptr->rumble(x, y); + return true; + } + } + } + + // We should never get here... something's really messed up + Con::errorf( "DInputManager::rumbleJoystick - Couldn't find device to rumble! This should never happen!\n" ); + return false; + + default: + Con::printf("DInputManager::rumble - only supports joystick and xinput/gamepad devices"); + return false; + } +} + +void DInputManager::buildXInputEvent( U32 deviceInst, InputEventType objType, InputObjectInstances objInst, InputActionType action, float fValue ) +{ + InputEventInfo newEvent; + + newEvent.deviceType = GamepadDeviceType; + newEvent.deviceInst = deviceInst; + newEvent.objType = objType; + newEvent.objInst = objInst; + newEvent.action = action; + newEvent.fValue = fValue; + + newEvent.postToSignal(Input::smInputEvent); +} + +// The next three functions: fireXInputConnectEvent, fireXInputMoveEvent, and fireXInputButtonEvent +// determine whether a "delta" has occurred between the last captured controller state and the +// currently captured controller state and only if so, do we fire an event. The shortcutter +// "mXInputStateReset" is the exception and is true whenever DirectInput gets reset (because +// the user ALT-TABBED away, for example). That means that after every context switch, +// you will get a full set of updates on the "true" state of the controller. +inline void DInputManager::fireXInputConnectEvent( int controllerID, bool condition, bool connected ) +{ + if ( mXInputStateReset || condition ) + { +#ifdef LOG_INPUT + Input::log( "EVENT (XInput): xinput%d CONNECT %s\n", controllerID, connected ? "make" : "break" ); +#endif + buildXInputEvent( controllerID, SI_BUTTON, XI_CONNECT, connected ? SI_MAKE : SI_BREAK, 0); + } +} + +inline void DInputManager::fireXInputMoveEvent( int controllerID, bool condition, InputObjectInstances objInst, float fValue ) +{ + if ( mXInputStateReset || condition ) + { +#ifdef LOG_INPUT + char *objName; + switch (objType) + { + case XI_THUMBLX: objName = "THUMBLX"; break; + case XI_THUMBLY: objName = "THUMBLY"; break; + case XI_THUMBRX: objName = "THUMBRX"; break; + case XI_THUMBRY: objName = "THUMBRY"; break; + case XI_LEFT_TRIGGER: objName = "LEFT_TRIGGER"; break; + case XI_RIGHT_TRIGGER: objName = "RIGHT_TRIGGER"; break; + default: objName = "UNKNOWN"; break; + } + + Input::log( "EVENT (XInput): xinput%d %s MOVE %.1f.\n", controllerID, objName, fValue ); +#endif + buildXInputEvent( controllerID, SI_AXIS, objInst, SI_MOVE, fValue ); + } +} + +inline void DInputManager::fireXInputButtonEvent( int controllerID, bool forceFire, int button, InputObjectInstances objInst ) +{ + if ( mXInputStateReset || forceFire || ((mXInputStateNew[controllerID].state.Gamepad.wButtons & button) != (mXInputStateOld[controllerID].state.Gamepad.wButtons & button)) ) + { +#ifdef LOG_INPUT + char *objName; + switch (objType) + { + case XI_DPAD_UP: objName = "DPAD_UP"; break; + case XI_DPAD_DOWN: objName = "DPAD_DOWN"; break; + case XI_DPAD_LEFT: objName = "DPAD_LEFT"; break; + case XI_DPAD_RIGHT: objName = "DPAD_RIGHT"; break; + case XI_START: objName = "START"; break; + case XI_BACK: objName = "BACK"; break; + case XI_LEFT_THUMB: objName = "LEFT_THUMB"; break; + case XI_RIGHT_THUMB: objName = "RIGHT_THUMB"; break; + case XI_LEFT_SHOULDER: objName = "LEFT_SHOULDER"; break; + case XI_RIGHT_SHOULDER: objName = "RIGHT_SHOULDER"; break; + case XI_A: objName = "A"; break; + case XI_B: objName = "B"; break; + case XI_X: objName = "X"; break; + case XI_Y: objName = "Y"; break; + default: objName = "UNKNOWN"; break; + } + + Input::log( "EVENT (XInput): xinput%d %s %s\n", controllerID, objName, ((mXInputStateNew[controllerID].state.Gamepad.wButtons & button) != 0) ? "make" : "break" ); +#endif + InputActionType action = ((mXInputStateNew[controllerID].state.Gamepad.wButtons & button) != 0) ? SI_MAKE : SI_BREAK; + buildXInputEvent( controllerID, SI_BUTTON, objInst, action, ( action == SI_MAKE ? 1 : 0 ) ); + } +} + +void DInputManager::processXInput( void ) +{ + const U32 curTime = Platform::getRealMilliseconds(); + + // We only want to check one disconnected device per frame. + bool foundDisconnected = false; + + if ( mfnXInputGetState ) + { + for ( int i=0; i<4; i++ ) + { + // Calling XInputGetState on a disconnected controller takes a fair + // amount of time (probably because it tries to locate it), so we + // add a delay - only check every 250ms or so. + if(mLastDisconnectTime[i] != -1) + { + // If it's not -1, then it was disconnected list time we checked. + // So skip until it's time. + if((curTime-mLastDisconnectTime[i]) < csmDisconnectedSkipDelay) + { + continue; + } + + // If we already checked a disconnected controller, don't try any + // further potentially disconnected devices. + if(foundDisconnected) + { + // If we're skipping this, defer it out by the skip delay + // so we don't get clumped checks. + mLastDisconnectTime[i] += csmDisconnectedSkipDelay; + continue; + } + } + + mXInputStateOld[i] = mXInputStateNew[i]; + mXInputStateNew[i].bConnected = ( mfnXInputGetState( i, &mXInputStateNew[i].state ) == ERROR_SUCCESS ); + + // Update the last connected time. + if(mXInputStateNew[i].bConnected) + mLastDisconnectTime[i] = -1; + else + { + foundDisconnected = true; + mLastDisconnectTime[i] = curTime; + } + + // trim the controller's thumbsticks to zero if they are within the deadzone + if( mXInputDeadZoneOn ) + { + // Zero value if thumbsticks are within the dead zone + if( (mXInputStateNew[i].state.Gamepad.sThumbLX < XINPUT_DEADZONE && mXInputStateNew[i].state.Gamepad.sThumbLX > -XINPUT_DEADZONE) && + (mXInputStateNew[i].state.Gamepad.sThumbLY < XINPUT_DEADZONE && mXInputStateNew[i].state.Gamepad.sThumbLY > -XINPUT_DEADZONE) ) + { + mXInputStateNew[i].state.Gamepad.sThumbLX = 0; + mXInputStateNew[i].state.Gamepad.sThumbLY = 0; + } + + if( (mXInputStateNew[i].state.Gamepad.sThumbRX < XINPUT_DEADZONE && mXInputStateNew[i].state.Gamepad.sThumbRX > -XINPUT_DEADZONE) && + (mXInputStateNew[i].state.Gamepad.sThumbRY < XINPUT_DEADZONE && mXInputStateNew[i].state.Gamepad.sThumbRY > -XINPUT_DEADZONE) ) + { + mXInputStateNew[i].state.Gamepad.sThumbRX = 0; + mXInputStateNew[i].state.Gamepad.sThumbRY = 0; + } + } + + // this controller was connected or disconnected + bool bJustConnected = ( ( mXInputStateOld[i].bConnected != mXInputStateNew[i].bConnected ) && ( mXInputStateNew[i].bConnected ) ); + fireXInputConnectEvent( i, (mXInputStateOld[i].bConnected != mXInputStateNew[i].bConnected), mXInputStateNew[i].bConnected ); + + // If this controller is disconnected, stop reporting events for it + if ( !mXInputStateNew[i].bConnected ) + continue; + + // == LEFT THUMBSTICK == + fireXInputMoveEvent( i, ( bJustConnected ) || (mXInputStateNew[i].state.Gamepad.sThumbLX != mXInputStateOld[i].state.Gamepad.sThumbLX), XI_THUMBLX, (mXInputStateNew[i].state.Gamepad.sThumbLX / 32768.0f) ); + fireXInputMoveEvent( i, ( bJustConnected ) || (mXInputStateNew[i].state.Gamepad.sThumbLY != mXInputStateOld[i].state.Gamepad.sThumbLY), XI_THUMBLY, (mXInputStateNew[i].state.Gamepad.sThumbLY / 32768.0f) ); + + // == RIGHT THUMBSTICK == + fireXInputMoveEvent( i, ( bJustConnected ) || (mXInputStateNew[i].state.Gamepad.sThumbRX != mXInputStateOld[i].state.Gamepad.sThumbRX), XI_THUMBRX, (mXInputStateNew[i].state.Gamepad.sThumbRX / 32768.0f) ); + fireXInputMoveEvent( i, ( bJustConnected ) || (mXInputStateNew[i].state.Gamepad.sThumbRY != mXInputStateOld[i].state.Gamepad.sThumbRY), XI_THUMBRY, (mXInputStateNew[i].state.Gamepad.sThumbRY / 32768.0f) ); + + // == LEFT & RIGHT REAR TRIGGERS == + fireXInputMoveEvent( i, ( bJustConnected ) || (mXInputStateNew[i].state.Gamepad.bLeftTrigger != mXInputStateOld[i].state.Gamepad.bLeftTrigger), XI_LEFT_TRIGGER, (mXInputStateNew[i].state.Gamepad.bLeftTrigger / 255.0f) ); + fireXInputMoveEvent( i, ( bJustConnected ) || (mXInputStateNew[i].state.Gamepad.bRightTrigger != mXInputStateOld[i].state.Gamepad.bRightTrigger), XI_RIGHT_TRIGGER, (mXInputStateNew[i].state.Gamepad.bRightTrigger / 255.0f) ); + + // == BUTTONS: DPAD == + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_DPAD_UP, SI_UPOV ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_DPAD_DOWN, SI_DPOV ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_DPAD_LEFT, SI_LPOV ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_DPAD_RIGHT, SI_RPOV ); + + // == BUTTONS: START & BACK == + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_START, XI_START ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_BACK, XI_BACK ); + + // == BUTTONS: LEFT AND RIGHT THUMBSTICK == + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_LEFT_THUMB, XI_LEFT_THUMB ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_RIGHT_THUMB, XI_RIGHT_THUMB ); + + // == BUTTONS: LEFT AND RIGHT SHOULDERS (formerly WHITE and BLACK on Xbox 1) == + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_LEFT_SHOULDER, XI_LEFT_SHOULDER ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_RIGHT_SHOULDER, XI_RIGHT_SHOULDER ); + + // == BUTTONS: A, B, X, and Y == + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_A, XI_A ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_B, XI_B ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_X, XI_X ); + fireXInputButtonEvent( i, bJustConnected, XINPUT_GAMEPAD_Y, XI_Y ); + } + + if ( mXInputStateReset ) + mXInputStateReset = false; + } +} +ConsoleFunction( enableJoystick, bool, 1, 1, "enableJoystick()" ) +{ + argc; argv; + return( DInputManager::enableJoystick() ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableJoystick, void, 1, 1, "disableJoystick()" ) +{ + argc; argv; + DInputManager::disableJoystick(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( isJoystickEnabled, bool, 1, 1, "()" ) +{ + argc; argv; + return DInputManager::isJoystickEnabled(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( enableXInput, bool, 1, 1, "enableXInput()" ) +{ + // Although I said above that you couldn't change the "activation" of XInput, + // you can enable and disable it. It gets enabled by default if you have the + // DLL. You would want to disable it if you have 360 controllers and want to + // read them as joysticks... why you'd want to do that is beyond me + + argc; argv; + return( DInputManager::enableXInput() ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableXInput, void, 1, 1, "disableXInput()" ) +{ + argc; argv; + DInputManager::disableXInput(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( resetXInput, void, 1, 1, "resetXInput()" ) +{ + // This function requests a full "refresh" of events for all controllers the + // next time we go through the input processing loop. This is useful to call + // at the beginning of your game code after your actionMap is set up to hook + // all of the appropriate events + + argc; argv; + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr && mgr->isEnabled() ) + mgr->resetXInput(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( isXInputConnected, bool, 2, 2, "( int controllerID )" ) +{ + argc; argv; + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr && mgr->isEnabled() ) return mgr->isXInputConnected( atoi( argv[1] ) ); + return false; +} + +//------------------------------------------------------------------------------ +ConsoleFunction( getXInputState, int, 3, 4, "( int controllerID, string property, bool current )" ) +{ + argc; argv; + DInputManager* mgr = dynamic_cast( Input::getManager() ); + + if ( !mgr || !mgr->isEnabled() ) + return -1; + + // Use a little bit of macro magic to simplify this otherwise monolothic + // block of code. +#define GET_XI_STATE(constName) \ + if (!dStricmp(argv[2], #constName)) \ + return mgr->getXInputState( dAtoi( argv[1] ), constName, ( dAtoi ( argv[3] ) == 1) ); + + GET_XI_STATE(XI_THUMBLX); + GET_XI_STATE(XI_THUMBLY); + GET_XI_STATE(XI_THUMBRX); + GET_XI_STATE(XI_THUMBRY); + GET_XI_STATE(XI_LEFT_TRIGGER); + GET_XI_STATE(XI_RIGHT_TRIGGER); + GET_XI_STATE(SI_UPOV); + GET_XI_STATE(SI_DPOV); + GET_XI_STATE(SI_LPOV); + GET_XI_STATE(SI_RPOV); + GET_XI_STATE(XI_START); + GET_XI_STATE(XI_BACK); + GET_XI_STATE(XI_LEFT_THUMB); + GET_XI_STATE(XI_RIGHT_THUMB); + GET_XI_STATE(XI_LEFT_SHOULDER); + GET_XI_STATE(XI_RIGHT_SHOULDER); + GET_XI_STATE(XI_A); + GET_XI_STATE(XI_B); + GET_XI_STATE(XI_X); + GET_XI_STATE(XI_Y); +#undef GET_XI_STATE + + return -1; +} + +//------------------------------------------------------------------------------ +ConsoleFunction( echoInputState, void, 1, 1, "echoInputState()" ) +{ + argc; argv; + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr && mgr->isEnabled() ) + { + Con::printf( "DirectInput is enabled %s.", Input::isActive() ? "and active" : "but inactive" ); + Con::printf( "- Joystick is %sabled and %sactive.", + mgr->isJoystickEnabled() ? "en" : "dis", + mgr->isJoystickActive() ? "" : "in" ); + } + else + Con::printf( "DirectInput is not enabled." ); +} + +ConsoleFunction( rumble, void, 4, 4, "(string device, float xRumble, float yRumble) Rumbles the specified controller with constant force feedback in the (x, y) direction.\nValid inputs for xRumble/yRumble are [0 - 1].") +{ + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr && mgr->isEnabled() ) + { + mgr->rumble(argv[1], dAtof(argv[2]), dAtof(argv[3])); + } + else + { + Con::printf( "DirectInput/XInput is not enabled." ); + } +} diff --git a/platformWin32/winDirectInput.h b/platformWin32/winDirectInput.h new file mode 100644 index 0000000..e8d7636 --- /dev/null +++ b/platformWin32/winDirectInput.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WINDIRECTINPUT_H_ +#define _WINDIRECTINPUT_H_ + +#ifndef _PLATFORMWIN32_H_ +#include "platformWin32/platformWin32.h" +#endif +#ifndef _PLATFORMINPUT_H_ +#include "platform/platformInput.h" +#endif +#ifndef _WINDINPUTDEVICE_H_ +#include "platformWin32/winDInputDevice.h" +#endif + +#define DIRECTINPUT_VERSION 0x0800 +#include +#include + +// XInput related definitions +typedef DWORD (WINAPI* FN_XInputGetState)(DWORD dwUserIndex, XINPUT_STATE* pState); +typedef DWORD (WINAPI* FN_XInputSetState)(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration); +#define XINPUT_MAX_CONTROLLERS 4 // XInput handles up to 4 controllers +#define XINPUT_DEADZONE ( 0.24f * FLOAT(0x7FFF) ) // Default to 24% of the +/- 32767 range. This is a reasonable default value but can be altered if needed. +struct XINPUT_CONTROLLER_STATE +{ + XINPUT_STATE state; + bool bConnected; +}; + +//------------------------------------------------------------------------------ +class DInputManager : public InputManager +{ + private: + typedef SimGroup Parent; + + // XInput state + HMODULE mXInputLib; + FN_XInputGetState mfnXInputGetState; + FN_XInputSetState mfnXInputSetState; + XINPUT_CONTROLLER_STATE mXInputStateOld[XINPUT_MAX_CONTROLLERS]; + XINPUT_CONTROLLER_STATE mXInputStateNew[XINPUT_MAX_CONTROLLERS]; + U32 mLastDisconnectTime[XINPUT_MAX_CONTROLLERS]; + bool mXInputStateReset; + bool mXInputDeadZoneOn; + + /// Number of milliseconds to skip checking an xinput device if it was + /// disconnected on last check. + const static U32 csmDisconnectedSkipDelay = 250; + + HMODULE mDInputLib; + LPDIRECTINPUT8 mDInputInterface; + + static bool smJoystickEnabled; + static bool smXInputEnabled; + + bool mJoystickActive; + bool mXInputActive; + + void enumerateDevices(); + + static BOOL CALLBACK EnumDevicesProc( const DIDEVICEINSTANCE *pddi, LPVOID pvRef ); + + bool acquire( U8 deviceType, U8 deviceID ); + void unacquire( U8 deviceType, U8 deviceID ); + + // XInput worker functions + void buildXInputEvent( U32 deviceInst, InputEventType objType, InputObjectInstances objInst, InputActionType action, float fValue ); + void fireXInputConnectEvent( int controllerID, bool condition, bool connected ); + void fireXInputMoveEvent( int controllerID, bool condition, InputObjectInstances objInst, float fValue ); + void fireXInputButtonEvent( int controllerID, bool forceFire, int button, InputObjectInstances objInst ); + void processXInput(); + + public: + DInputManager(); + + bool enable(); + void disable(); + + void onDeleteNotify( SimObject* object ); + bool onAdd(); + void onRemove(); + + void process(); + + // DirectInput functions: + static void init(); + + static bool enableJoystick(); + static void disableJoystick(); + static bool isJoystickEnabled(); + bool activateJoystick(); + void deactivateJoystick(); + bool isJoystickActive() { return( mJoystickActive ); } + + static bool enableXInput(); + static void disableXInput(); + static bool isXInputEnabled(); + bool activateXInput(); + void deactivateXInput(); + bool isXInputActive() { return( mXInputActive ); } + void resetXInput() { mXInputStateReset = true; } + bool isXInputConnected(int controllerID); + int getXInputState(int controllerID, int property, bool current); + + // Console interface: + const char* getJoystickAxesString( U32 deviceID ); + + bool rumble( const char *pDeviceName, float x, float y ); +}; + +#endif // _H_WINDIRECTINPUT_ diff --git a/platformWin32/winDlibrary.cpp b/platformWin32/winDlibrary.cpp new file mode 100644 index 0000000..d7d7fff --- /dev/null +++ b/platformWin32/winDlibrary.cpp @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include + +#include "platform/types.h" +#include "platform/platformDlibrary.h" + +class Win32DLibrary: public DLibrary +{ + HMODULE _handle; +public: + Win32DLibrary(); + ~Win32DLibrary(); + bool open(const char* file); + void close(); + void *bind(const char *name); +}; + +Win32DLibrary::Win32DLibrary() +{ + _handle = 0; +} + +Win32DLibrary::~Win32DLibrary() +{ + close(); +} + +bool Win32DLibrary::open(const char* file) +{ + // dlopen should also include the RTLD_LOCAL flag, but it seems to be + // missing from cygwin + _handle = LoadLibraryA(file); + if (!_handle) + return false; + bool (*open)() = (bool(*)())bind("dllopen"); + if (open && !(*open)()) { + FreeLibrary(_handle); + _handle = 0; + return false; + } + return true; +} + +void Win32DLibrary::close() +{ + if (_handle) { + void (*close)() = (void(*)())bind("dllclose"); + if (close) + (*close)(); + FreeLibrary(_handle); + _handle = 0; + } +} + +void* Win32DLibrary::bind(const char *name) +{ + return _handle? (void*)GetProcAddress(_handle,name): 0; +} + +DLibraryRef OsLoadLibrary(const char* file) +{ + Win32DLibrary* library = new Win32DLibrary(); + if (!library->open(file)) { + delete library; + return 0; + } + return library; +} + diff --git a/platformWin32/winExec.cpp b/platformWin32/winExec.cpp new file mode 100644 index 0000000..7c714f9 --- /dev/null +++ b/platformWin32/winExec.cpp @@ -0,0 +1,160 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "console/console.h" +#include "console/simBase.h" +#include "core/strings/unicode.h" +#include "platform/threads/thread.h" +#include "platform/threads/mutex.h" +#include "core/util/safeDelete.h" +#include "util/tempAlloc.h" + +//----------------------------------------------------------------------------- +// Thread for executing in +//----------------------------------------------------------------------------- + +class ExecuteThread : public Thread +{ + // [tom, 12/14/2006] mProcess is only used in the constructor before the thread + // is started and in the thread itself so we should be OK without a mutex. + HANDLE mProcess; + +public: + ExecuteThread(const char *executable, const char *args = NULL, const char *directory = NULL); + + virtual void run(void *arg = 0); +}; + +//----------------------------------------------------------------------------- +// Event for cleanup +//----------------------------------------------------------------------------- + +class ExecuteCleanupEvent : public SimEvent +{ + ExecuteThread *mThread; + bool mOK; + +public: + ExecuteCleanupEvent(ExecuteThread *thread, bool ok) + { + mThread = thread; + mOK = ok; + } + + virtual void process(SimObject *object) + { + Con::executef("onExecuteDone", Con::getIntArg(mOK)); + SAFE_DELETE(mThread); + } +}; + +//----------------------------------------------------------------------------- + +ExecuteThread::ExecuteThread(const char *executable, const char *args /* = NULL */, const char *directory /* = NULL */) : Thread(0, NULL, false) +{ + SHELLEXECUTEINFO shl; + dMemset(&shl, 0, sizeof(shl)); + + shl.cbSize = sizeof(shl); + shl.fMask = SEE_MASK_NOCLOSEPROCESS; + + char exeBuf[1024]; + Platform::makeFullPathName(executable, exeBuf, sizeof(exeBuf)); + + TempAlloc< TCHAR > dirBuf( dStrlen( directory ) + 1 ); + +#ifdef UNICODE + WCHAR exe[ 1024 ]; + convertUTF8toUTF16( exeBuf, exe, sizeof( exe ) / sizeof( exe[ 0 ] ) ); + + TempAlloc< WCHAR > argsBuf( dStrlen( args ) + 1 ); + + convertUTF8toUTF16( args, argsBuf, argsBuf.size ); + convertUTF8toUTF16( directory, dirBuf, dirBuf.size ); +#else + char* exe = exeBuf; + char* argsBuf = args; + dStrpcy( dirBuf, directory ); +#endif + + backslash( exe ); + backslash( dirBuf ); + + shl.lpVerb = TEXT( "open" ); + shl.lpFile = exe; + shl.lpParameters = argsBuf; + shl.lpDirectory = dirBuf; + + shl.nShow = SW_SHOWNORMAL; + + if(ShellExecuteEx(&shl) && shl.hProcess) + { + mProcess = shl.hProcess; + start(); + } +} + +void ExecuteThread::run(void *arg /* = 0 */) +{ + if(mProcess == NULL) + return; + + DWORD wait = WAIT_OBJECT_0 - 1; // i.e., not WAIT_OBJECT_0 + while(! checkForStop() && (wait = WaitForSingleObject(mProcess, 200)) != WAIT_OBJECT_0) ; + + Sim::postEvent(Sim::getRootGroup(), new ExecuteCleanupEvent(this, wait == WAIT_OBJECT_0), -1); +} + +//----------------------------------------------------------------------------- +// Console Functions +//----------------------------------------------------------------------------- + +ConsoleFunction(shellExecute, bool, 2, 4, "(executable, [args], [directory])") +{ + ExecuteThread *et = new ExecuteThread(argv[1], argc > 2 ? argv[2] : NULL, argc > 3 ? argv[3] : NULL); + if(! et->isAlive()) + { + delete et; + return false; + } + + return true; +} + +void Platform::openFolder(const char* path ) +{ + char filePath[1024]; + Platform::makeFullPathName(path, filePath, sizeof(filePath)); + +#ifdef UNICODE + WCHAR p[ 1024 ]; + convertUTF8toUTF16( filePath, p, sizeof( p ) / sizeof( p[ 0 ] ) ); +#else + char* p = filePath; +#endif + + backslash( p ); + + ::ShellExecute( NULL,TEXT("explore"),p, NULL, NULL, SW_SHOWNORMAL); +} + +void Platform::openFile(const char* path ) +{ + char filePath[1024]; + Platform::makeFullPathName(path, filePath, sizeof(filePath)); + +#ifdef UNICODE + WCHAR p[ 1024 ]; + convertUTF8toUTF16( filePath, p, sizeof( p ) / sizeof( p[ 0 ] ) ); +#else + char* p = filePath; +#endif + + backslash( p ); + + ::ShellExecute( NULL,TEXT("open"),p, NULL, NULL, SW_SHOWNORMAL); +} + diff --git a/platformWin32/winFileio.cpp b/platformWin32/winFileio.cpp new file mode 100644 index 0000000..d6ac087 --- /dev/null +++ b/platformWin32/winFileio.cpp @@ -0,0 +1,1460 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "platformWin32/platformWin32.h" +#include "core/fileio.h" +#include "core/util/tVector.h" +#include "core/stringTable.h" +#include "console/console.h" +#include "core/strings/unicode.h" +#include "util/tempAlloc.h" +#include "core/util/safeDelete.h" + +// Microsoft VC++ has this POSIX header in the wrong directory +#if defined(TORQUE_COMPILER_VISUALC) +#include +#elif defined (TORQUE_COMPILER_GCC) +#include +#include +#else +#include +#endif + +StringTableEntry Platform::createPlatformFriendlyFilename( const char *filename ) +{ + return StringTable->insert( filename ); +} + +//----------------------------------------------------------------------------- +bool dFileDelete(const char * name) +{ + AssertFatal( name != NULL, "dFileDelete - NULL file name" ); + + TempAlloc< TCHAR > buf( dStrlen( name ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( name, buf, buf.size ); +#else + dStrcpy( buf, name ); +#endif + + backslash( buf ); + if( Platform::isFile( name ) ) + return DeleteFile( buf ); + else + return RemoveDirectory( buf ); +} + +bool dFileRename(const char *oldName, const char *newName) +{ + AssertFatal( oldName != NULL && newName != NULL, "dFileRename - NULL file name" ); + + TempAlloc< TCHAR > oldf( dStrlen( oldName ) + 1 ); + TempAlloc< TCHAR > newf( dStrlen( newName ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( oldName, oldf, oldf.size ); + convertUTF8toUTF16( newName, newf, newf.size ); +#else + dStrcpy(oldf, oldName); + dStrcpy(newf, newName); +#endif + backslash(oldf); + backslash(newf); + + return MoveFile( oldf, newf ); +} + +bool dFileTouch(const char * name) +{ + AssertFatal( name != NULL, "dFileTouch - NULL file name" ); + + TempAlloc< TCHAR > buf( dStrlen( name ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( name, buf, buf.size ); +#else + dStrcpy( buf, name ); +#endif + + backslash( buf ); + FILETIME ftime; + GetSystemTimeAsFileTime( &ftime ); + HANDLE handle = CreateFile( buf, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, 0, NULL ); + if( handle == INVALID_HANDLE_VALUE ) + return false; + bool result = SetFileTime( handle, NULL, NULL, &ftime ); + CloseHandle( handle ); + + return result; +}; + +bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite) +{ + AssertFatal( fromName != NULL && toName != NULL, "dPathCopy - NULL file name" ); + + TempAlloc< TCHAR > from( dStrlen( fromName ) + 1 ); + TempAlloc< TCHAR > to( dStrlen( toName ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( fromName, from, from.size ); + convertUTF8toUTF16( toName, to, to.size ); +#else + dStrcpy( from, fromName ); + dStrcpy( to, toName ); +#endif + + backslash( from ); + backslash( to ); + + // Copy File + if (Platform::isFile(fromName)) + return CopyFile( from, to, nooverwrite ); + // Copy Path + else if (Platform::isDirectory(fromName)) + { + // If the destination path exists and we don't want to overwrite, return. + if ((Platform::isDirectory(toName) || Platform::isFile(toName)) && nooverwrite) + return false; + + Vector directoryInfo; + Platform::dumpDirectories(fromName, directoryInfo, -1); + + Vector fileInfo; + Platform::dumpPath(fromName, fileInfo); + + Platform::clearExcludedDirectories(); + + TempAlloc< char > tempBuf( to.size * 3 + MAX_PATH * 3 ); + + // Create all the directories. + for (S32 i = 0; i < directoryInfo.size(); i++) + { + const char* fromDir = directoryInfo[i]; + + char* toDir = tempBuf; + Platform::makeFullPathName(fromDir + dStrlen(fromName) + (dStricmp(fromDir, fromName) ? 1 : 0), tempBuf, tempBuf.size, toName); + if(*(toDir + dStrlen(toDir) - 1) != '/') + dStrcat(toDir, "/"); + forwardslash(toDir); + + if (!Platform::createPath(toDir)) + { + //TODO: New directory should be deleted here. + return false; + } + } + + TempAlloc< char > tempBuf1( from.size * 3 + MAX_PATH * 3 ); +#ifdef UNICODE + TempAlloc< WCHAR > wtempBuf( tempBuf.size / 3 ); + TempAlloc< WCHAR > wtempBuf1( tempBuf1.size / 3 ); +#endif + + for (S32 i = 0; i < fileInfo.size(); i++) + { + char* fromFile = tempBuf1; + dSprintf( tempBuf1, tempBuf1.size, "%s/%s", fileInfo[i].pFullPath, fileInfo[i].pFileName); + + char* toFile = tempBuf; + Platform::makeFullPathName(fileInfo[i].pFullPath + dStrlen(fromName) + (dStricmp(fileInfo[i].pFullPath, fromName) ? 1 : 0), tempBuf, tempBuf.size, toName); + dStrcat(toFile, "/"); + dStrcat(toFile, fileInfo[i].pFileName); + + backslash(fromFile); + backslash(toFile); + +#ifdef UNICODE + convertUTF8toUTF16( tempBuf, wtempBuf, wtempBuf.size ); + convertUTF8toUTF16( tempBuf1, wtempBuf1, wtempBuf1.size ); + WCHAR* f = wtempBuf1; + WCHAR* t = wtempBuf; +#else + char *f = (char*)fromFile; + char *t = (char*)toFile; +#endif + + if (!::CopyFile(f, t, nooverwrite)) + { + // New directory should be deleted here. + return false; + } + + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Constructors & Destructor +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// After construction, the currentStatus will be Closed and the capabilities +// will be 0. +//----------------------------------------------------------------------------- +File::File() +: currentStatus(Closed), capability(0) +{ + AssertFatal(sizeof(HANDLE) == sizeof(void *), "File::File: cannot cast void* to HANDLE"); + + handle = (void *)INVALID_HANDLE_VALUE; +} + +//----------------------------------------------------------------------------- +// insert a copy constructor here... (currently disabled) +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +File::~File() +{ + close(); + handle = (void *)INVALID_HANDLE_VALUE; +} + + +//----------------------------------------------------------------------------- +// Open a file in the mode specified by openMode (Read, Write, or ReadWrite). +// Truncate the file if the mode is either Write or ReadWrite and truncate is +// true. +// +// Sets capability appropriate to the openMode. +// Returns the currentStatus of the file. +//----------------------------------------------------------------------------- +File::Status File::open(const char *filename, const AccessMode openMode) +{ + AssertFatal(NULL != filename, "File::open: NULL fname"); + AssertWarn(INVALID_HANDLE_VALUE == (HANDLE)handle, "File::open: handle already valid"); + + TempAlloc< TCHAR > fname( dStrlen( filename ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( filename, fname, fname.size ); +#else + dStrcpy(fname, filename); +#endif + backslash( fname ); + + // Close the file if it was already open... + if (Closed != currentStatus) + close(); + + // create the appropriate type of file... + switch (openMode) + { + case Read: + handle = (void *)CreateFile(fname, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + break; + case Write: + handle = (void *)CreateFile(fname, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + break; + case ReadWrite: + handle = (void *)CreateFile(fname, + GENERIC_WRITE | GENERIC_READ, + 0, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + break; + case WriteAppend: + handle = (void *)CreateFile(fname, + GENERIC_WRITE, + 0, + NULL, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + break; + + default: + AssertFatal(false, "File::open: bad access mode"); // impossible + } + + if (INVALID_HANDLE_VALUE == (HANDLE)handle) // handle not created successfully + { + return setStatus(); + } + else + { + // successfully created file, so set the file capabilities... + switch (openMode) + { + case Read: + capability = U32(FileRead); + break; + case Write: + case WriteAppend: + capability = U32(FileWrite); + break; + case ReadWrite: + capability = U32(FileRead) | + U32(FileWrite); + break; + default: + AssertFatal(false, "File::open: bad access mode"); + } + return currentStatus = Ok; // success! + } +} + +//----------------------------------------------------------------------------- +// Get the current position of the file pointer. +//----------------------------------------------------------------------------- +U32 File::getPosition() const +{ + AssertFatal(Closed != currentStatus, "File::getPosition: file closed"); + AssertFatal(INVALID_HANDLE_VALUE != (HANDLE)handle, "File::getPosition: invalid file handle"); + + return SetFilePointer((HANDLE)handle, + 0, // how far to move + NULL, // pointer to high word + FILE_CURRENT); // from what point +} + +//----------------------------------------------------------------------------- +// Set the position of the file pointer. +// Absolute and relative positioning is supported via the absolutePos +// parameter. +// +// If positioning absolutely, position MUST be positive - an IOError results if +// position is negative. +// Position can be negative if positioning relatively, however positioning +// before the start of the file is an IOError. +// +// Returns the currentStatus of the file. +//----------------------------------------------------------------------------- +File::Status File::setPosition(S32 position, bool absolutePos) +{ + AssertFatal(Closed != currentStatus, "File::setPosition: file closed"); + AssertFatal(INVALID_HANDLE_VALUE != (HANDLE)handle, "File::setPosition: invalid file handle"); + + if (Ok != currentStatus && EOS != currentStatus) + return currentStatus; + + U32 finalPos; + if (absolutePos) + { + AssertFatal(0 <= position, "File::setPosition: negative absolute position"); + + // position beyond EOS is OK + finalPos = SetFilePointer((HANDLE)handle, + position, + NULL, + FILE_BEGIN); + } + else + { + AssertFatal((getPosition() >= (U32)abs(position) && 0 > position) || 0 <= position, "File::setPosition: negative relative position"); + + // position beyond EOS is OK + finalPos = SetFilePointer((HANDLE)handle, + position, + NULL, + FILE_CURRENT); + } + + if (0xffffffff == finalPos) + return setStatus(); // unsuccessful + else if (finalPos >= getSize()) + return currentStatus = EOS; // success, at end of file + else + return currentStatus = Ok; // success! +} + +//----------------------------------------------------------------------------- +// Get the size of the file in bytes. +// It is an error to query the file size for a Closed file, or for one with an +// error status. +//----------------------------------------------------------------------------- +U32 File::getSize() const +{ + AssertWarn(Closed != currentStatus, "File::getSize: file closed"); + AssertFatal(INVALID_HANDLE_VALUE != (HANDLE)handle, "File::getSize: invalid file handle"); + + if (Ok == currentStatus || EOS == currentStatus) + { + DWORD high; + return GetFileSize((HANDLE)handle, &high); // success! + } + else + return 0; // unsuccessful +} + +//----------------------------------------------------------------------------- +// Flush the file. +// It is an error to flush a read-only file. +// Returns the currentStatus of the file. +//----------------------------------------------------------------------------- +File::Status File::flush() +{ + AssertFatal(Closed != currentStatus, "File::flush: file closed"); + AssertFatal(INVALID_HANDLE_VALUE != (HANDLE)handle, "File::flush: invalid file handle"); + AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file"); + + if (0 != FlushFileBuffers((HANDLE)handle)) + return setStatus(); // unsuccessful + else + return currentStatus = Ok; // success! +} + +//----------------------------------------------------------------------------- +// Close the File. +// +// Returns the currentStatus +//----------------------------------------------------------------------------- +File::Status File::close() +{ + // check if it's already closed... + if (Closed == currentStatus) + return currentStatus; + + // it's not, so close it... + if (INVALID_HANDLE_VALUE != (HANDLE)handle) + { + if (0 == CloseHandle((HANDLE)handle)) + return setStatus(); // unsuccessful + } + handle = (void *)INVALID_HANDLE_VALUE; + return currentStatus = Closed; +} + +//----------------------------------------------------------------------------- +// Self-explanatory. +//----------------------------------------------------------------------------- +File::Status File::getStatus() const +{ + return currentStatus; +} + +//----------------------------------------------------------------------------- +// Sets and returns the currentStatus when an error has been encountered. +//----------------------------------------------------------------------------- +File::Status File::setStatus() +{ + switch (GetLastError()) + { + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_ACCESS: + case ERROR_TOO_MANY_OPEN_FILES: + case ERROR_FILE_NOT_FOUND: + case ERROR_SHARING_VIOLATION: + case ERROR_HANDLE_DISK_FULL: + return currentStatus = IOError; + + default: + return currentStatus = UnknownError; + } +} + +//----------------------------------------------------------------------------- +// Sets and returns the currentStatus to status. +//----------------------------------------------------------------------------- +File::Status File::setStatus(File::Status status) +{ + return currentStatus = status; +} + +//----------------------------------------------------------------------------- +// Read from a file. +// The number of bytes to read is passed in size, the data is returned in src. +// The number of bytes read is available in bytesRead if a non-Null pointer is +// provided. +//----------------------------------------------------------------------------- +File::Status File::read(U32 size, char *dst, U32 *bytesRead) +{ + AssertFatal(Closed != currentStatus, "File::read: file closed"); + AssertFatal(INVALID_HANDLE_VALUE != (HANDLE)handle, "File::read: invalid file handle"); + AssertFatal(NULL != dst, "File::read: NULL destination pointer"); + AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability"); + AssertWarn(0 != size, "File::read: size of zero"); + + if (Ok != currentStatus || 0 == size) + return currentStatus; + else + { + DWORD lastBytes; + DWORD *bytes = (NULL == bytesRead) ? &lastBytes : (DWORD *)bytesRead; + if (0 != ReadFile((HANDLE)handle, dst, size, bytes, NULL)) + { + if(*((U32 *)bytes) != size) + return currentStatus = EOS; // end of stream + } + else + return setStatus(); // unsuccessful + } + return currentStatus = Ok; // successfully read size bytes +} + +//----------------------------------------------------------------------------- +// Write to a file. +// The number of bytes to write is passed in size, the data is passed in src. +// The number of bytes written is available in bytesWritten if a non-Null +// pointer is provided. +//----------------------------------------------------------------------------- +File::Status File::write(U32 size, const char *src, U32 *bytesWritten) +{ + AssertFatal(Closed != currentStatus, "File::write: file closed"); + AssertFatal(INVALID_HANDLE_VALUE != (HANDLE)handle, "File::write: invalid file handle"); + AssertFatal(NULL != src, "File::write: NULL source pointer"); + AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability"); + AssertWarn(0 != size, "File::write: size of zero"); + + if ((Ok != currentStatus && EOS != currentStatus) || 0 == size) + return currentStatus; + else + { + DWORD lastBytes; + DWORD *bytes = (NULL == bytesWritten) ? &lastBytes : (DWORD *)bytesWritten; + if (0 != WriteFile((HANDLE)handle, src, size, bytes, NULL)) + return currentStatus = Ok; // success! + else + return setStatus(); // unsuccessful + } +} + +//----------------------------------------------------------------------------- +// Self-explanatory. +//----------------------------------------------------------------------------- +bool File::hasCapability(Capability cap) const +{ + return (0 != (U32(cap) & capability)); +} + +S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b) +{ + if(a.v2 > b.v2) + return 1; + if(a.v2 < b.v2) + return -1; + if(a.v1 > b.v1) + return 1; + if(a.v1 < b.v1) + return -1; + return 0; +} + +static bool recurseDumpPath(const char *path, const char *pattern, Vector &fileVector, S32 recurseDepth ) +{ + WIN32_FIND_DATA findData; + + TempAlloc< char > fullPath( dStrlen( path ) * 3 + MAX_PATH * 3 + 1 ); + Platform::makeFullPathName( path, fullPath, fullPath.size ); + + U32 lenFullPath = dStrlen( fullPath ); + TempAlloc< char > buf( lenFullPath + MAX_PATH * 3 + 2 ); + dSprintf( buf, buf.size, "%s/%s", fullPath.ptr, pattern ); + +#ifdef UNICODE + TempAlloc< WCHAR > searchBuf( buf.size ); + convertUTF8toUTF16( buf, searchBuf, searchBuf.size ); + WCHAR* search = searchBuf; +#else + char *search = buf; +#endif + + backslash( search ); + + HANDLE handle = FindFirstFile(search, &findData); + if (handle == INVALID_HANDLE_VALUE) + return false; + + do + { +#ifdef UNICODE + convertUTF16toUTF8( findData.cFileName, buf, buf.size ); + char* fnbuf = buf; +#else + char *fnbuf = findData.cFileName; +#endif + + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // make sure it is a directory + if (findData.dwFileAttributes & (FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_SYSTEM) ) + continue; + + // skip . and .. directories + if (dStrcmp( findData.cFileName, TEXT( "." ) ) == 0 || dStrcmp( findData.cFileName, TEXT( ".." ) ) == 0) + continue; + + // Skip excluded directores + if(Platform::isExcludedDirectory(fnbuf)) + continue; + + dSprintf( fullPath, fullPath.size, "%s/%s", path, fnbuf); + char* child = fullPath; + if( recurseDepth > 0 ) + recurseDumpPath(child, pattern, fileVector, recurseDepth - 1); + else if (recurseDepth == -1) + recurseDumpPath(child, pattern, fileVector, -1); + } + else + { + // make sure it is the kind of file we're looking for + if (findData.dwFileAttributes & + (FILE_ATTRIBUTE_DIRECTORY| + FILE_ATTRIBUTE_OFFLINE| + FILE_ATTRIBUTE_SYSTEM| + FILE_ATTRIBUTE_TEMPORARY) ) + continue; + + // add it to the list + fileVector.increment(); + Platform::FileInfo& rInfo = fileVector.last(); + + forwardslash( fnbuf ); + + rInfo.pFullPath = StringTable->insert(path); + rInfo.pFileName = StringTable->insert(fnbuf); + rInfo.fileSize = findData.nFileSizeLow; + } + + }while(FindNextFile(handle, &findData)); + + FindClose(handle); + return true; +} + + +//-------------------------------------- + +bool Platform::getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime) +{ + WIN32_FIND_DATA findData; + + TempAlloc< TCHAR > fp( dStrlen( filePath ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( filePath, fp, fp.size ); +#else + dStrcpy( fp, filePath ); +#endif + + backslash( fp ); + + HANDLE h = FindFirstFile(fp, &findData); + if(h == INVALID_HANDLE_VALUE) + return false; + + if(createTime) + { + createTime->v1 = findData.ftCreationTime.dwLowDateTime; + createTime->v2 = findData.ftCreationTime.dwHighDateTime; + } + if(modifyTime) + { + modifyTime->v1 = findData.ftLastWriteTime.dwLowDateTime; + modifyTime->v2 = findData.ftLastWriteTime.dwHighDateTime; + } + FindClose(h); + return true; +} + +//-------------------------------------- +bool Platform::createPath(const char *file) +{ + TempAlloc< TCHAR > pathbuf( dStrlen( file ) + 1 ); + +#ifdef UNICODE + TempAlloc< WCHAR > fileBuf( pathbuf.size ); + convertUTF8toUTF16( file, fileBuf, fileBuf.size ); + const WCHAR* fileName = fileBuf; + const WCHAR* dir; +#else + const char* fileName = file; + const char* dir; +#endif + + pathbuf[ 0 ] = 0; + U32 pathLen = 0; + + while((dir = dStrchr(fileName, '/')) != NULL) + { + TCHAR* pathptr = pathbuf; + dMemcpy( pathptr + pathLen, fileName, ( dir - fileName ) * sizeof( TCHAR ) ); + pathbuf[pathLen + dir-fileName] = 0; + + // ignore return value because we are fine with already existing directory + CreateDirectory(pathbuf, NULL); + + pathLen += dir - fileName; + pathbuf[pathLen++] = '\\'; + fileName = dir + 1; + } + return true; +} + +// [rene, 04/05/2008] Not used currently so did not bother updating. +#if 0 +// [tom, 7/12/2005] Rather then converting this to unicode, just using the ANSI +// versions of the Win32 API as its quicker for testing. +bool Platform::cdFileExists(const char *filePath, const char *volumeName, S32 serialNum) +{ + if (!filePath || !filePath[0]) + return true; + + //first find the CD device... + char fileBuf[1024]; + char drivesBuf[256]; + S32 length = GetLogicalDriveStringsA(256, drivesBuf); + char *drivePtr = drivesBuf; + while (S32(drivePtr - drivesBuf) < length) + { + char driveVolume[256], driveFileSystem[256]; + U32 driveSerial, driveFNLength, driveFlags; + if ((dStricmp(drivePtr, "A:\\") != 0 && dStricmp(drivePtr, "B:\\") != 0) && + GetVolumeInformationA((const char*)drivePtr, &driveVolume[0], (unsigned long)255, + (unsigned long*)&driveSerial, (unsigned long*)&driveFNLength, + (unsigned long*)&driveFlags, &driveFileSystem[0], (unsigned long)255)) + { +#if defined (TORQUE_DEBUG) || !defined (TORQUE_SHIPPING) + Con::printf("Found Drive: %s, vol: %s, serial: %d", drivePtr, driveVolume, driveSerial); +#endif + //see if the volume and serial number match + if (!dStricmp(volumeName, driveVolume) && (!serialNum || (serialNum == driveSerial))) + { + //see if the file exists on this volume + if(dStrlen(drivePtr) == 3 && drivePtr[2] == '\\' && filePath[0] == '\\') + dSprintf(fileBuf, sizeof(fileBuf), "%s%s", drivePtr, filePath + 1); + else + dSprintf(fileBuf, sizeof(fileBuf), "%s%s", drivePtr, filePath); +#if defined (TORQUE_DEBUG) || !defined (TORQUE_SHIPPING) + Con::printf("Looking for file: %s on %s", fileBuf, driveVolume); +#endif + WIN32_FIND_DATAA findData; + HANDLE h = FindFirstFileA(fileBuf, &findData); + if(h != INVALID_HANDLE_VALUE) + { + FindClose(h); + return true; + } + FindClose(h); + } + } + + //check the next drive + drivePtr += dStrlen(drivePtr) + 1; + } + + return false; +} +#endif + +//-------------------------------------- +bool Platform::dumpPath(const char *path, Vector &fileVector, S32 recurseDepth) +{ + return recurseDumpPath(path, "*", fileVector, recurseDepth ); +} + + +//-------------------------------------- + +//StringTableEntry Platform::getWorkingDirectory() +//{ +// return getCurrentDirectory(); +//} + +StringTableEntry Platform::getCurrentDirectory() +{ + TempAlloc< TCHAR > buf( 2048 ); + + GetCurrentDirectory( buf.size, buf ); + forwardslash( buf ); + +#ifdef UNICODE + char* utf8 = convertUTF16toUTF8( buf ); + StringTableEntry result = StringTable->insert( utf8 ); + SAFE_DELETE_ARRAY( utf8 ); + return result; +#else + return StringTable->insert( buf ); +#endif +} + +bool Platform::setCurrentDirectory(StringTableEntry newDir) +{ + + if (Platform::getWebDeployment()) + return true; + + TempAlloc< TCHAR > buf( dStrlen( newDir ) + 2 ); + +#ifdef UNICODE + convertUTF8toUTF16( newDir, buf, buf.size - 1 ); +#else + dStrcpy( buf, newDir ); +#endif + + backslash( buf ); + return SetCurrentDirectory( buf ); +} + +#ifdef UNICODE +static void getExecutableInfo( StringTableEntry* path, StringTableEntry* exe ) +{ + static StringTableEntry pathEntry = NULL; + static StringTableEntry exeEntry = NULL; + + if( !pathEntry ) + { + if (!Platform::getWebDeployment()) + { + WCHAR cen_buf[ 2048 ]; + GetModuleFileNameW( NULL, cen_buf, sizeof( cen_buf ) / sizeof( cen_buf[ 0 ] ) ); + forwardslash( cen_buf ); + + WCHAR* delimiter = dStrrchr( cen_buf, '/' ); + if( delimiter ) + *delimiter = '\0'; + + char* pathBuf = convertUTF16toUTF8( cen_buf ); + char* exeBuf = convertUTF16toUTF8( delimiter + 1 ); + + pathEntry = StringTable->insert( pathBuf ); + exeEntry = StringTable->insert( exeBuf ); + + SAFE_DELETE_ARRAY( pathBuf ); + SAFE_DELETE_ARRAY( exeBuf ); + } + else + { + char cdir[4096]; + GetCurrentDirectoryA(4096, cdir); + pathEntry = StringTable->insert(cdir); + exeEntry = StringTable->insert("WebGameCtrl.exe"); + } + } + + if( path ) + *path = pathEntry; + if( exe ) + *exe = exeEntry; +} +#endif + +StringTableEntry Platform::getExecutableName() +{ +#ifdef UNICODE + StringTableEntry exe; + getExecutableInfo( NULL, &exe ); + return exe; +#else + static StringTableEntry cen = NULL; + if (!cen) + { + char cen_buf[2048]; + GetModuleFileNameA( NULL, cen_buf, 2047); + forwardslash(cen_buf); + + char *delimiter = dStrrchr( cen_buf, '/' ); + + if( delimiter != NULL ) + { + *delimiter = 0x00; + delimiter++; + cen = StringTable->insert(delimiter); + } + else + cen = StringTable->insert(cen_buf); + } + return cen; +#endif +} + +StringTableEntry Platform::getExecutablePath() +{ +#ifdef UNICODE + StringTableEntry path; + getExecutableInfo( &path, NULL ); + return path; +#else + static StringTableEntry cen = NULL; + if (!cen) + { + char cen_buf[2048]; + GetModuleFileNameA( NULL, cen_buf, 2047); + forwardslash(cen_buf); + + char *delimiter = dStrrchr( cen_buf, '/' ); + + if( delimiter != NULL ) + *delimiter = 0x00; + + cen = StringTable->insert(cen_buf); + } + return cen; +#endif +} + +//-------------------------------------- +bool Platform::isFile(const char *pFilePath) +{ + if (!pFilePath || !*pFilePath) + return false; + + TempAlloc< TCHAR > buf( dStrlen( pFilePath ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( pFilePath, buf, buf.size ); +#else + dStrcpy( buf, pFilePath ); +#endif + backslash( buf ); + + // Get file info + WIN32_FIND_DATA findData; + HANDLE handle = FindFirstFile(buf, &findData); + FindClose(handle); + + if(handle == INVALID_HANDLE_VALUE) + return false; + + // if the file is a Directory, Offline, System or Temporary then FALSE + if (findData.dwFileAttributes & + (FILE_ATTRIBUTE_DIRECTORY| + FILE_ATTRIBUTE_OFFLINE| + FILE_ATTRIBUTE_SYSTEM| + FILE_ATTRIBUTE_TEMPORARY) ) + return false; + + // must be a real file then + return true; +} + +//-------------------------------------- +S32 Platform::getFileSize(const char *pFilePath) +{ + if (!pFilePath || !*pFilePath) + return -1; + + TempAlloc< TCHAR > buf( dStrlen( pFilePath ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( pFilePath, buf, buf.size ); +#else + dStrcpy( buf, pFilePath ); +#endif + backslash( buf ); + + // Get file info + WIN32_FIND_DATA findData; + HANDLE handle = FindFirstFile(buf, &findData); + + if(handle == INVALID_HANDLE_VALUE) + return -1; + + FindClose(handle); + + // if the file is a Directory, Offline, System or Temporary then FALSE + if (findData.dwFileAttributes & + (FILE_ATTRIBUTE_DIRECTORY| + FILE_ATTRIBUTE_OFFLINE| + FILE_ATTRIBUTE_SYSTEM| + FILE_ATTRIBUTE_TEMPORARY) ) + return -1; + + // must be a real file then + return findData.nFileSizeLow; +} + + +//-------------------------------------- +bool Platform::isDirectory(const char *pDirPath) +{ + if (!pDirPath || !*pDirPath) + return false; + + TempAlloc< TCHAR > buf( dStrlen( pDirPath ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( pDirPath, buf, buf.size ); +#else + dStrcpy( buf, pDirPath ); +#endif + backslash( buf ); + + // Get file info + WIN32_FIND_DATA findData; + HANDLE handle = FindFirstFile(buf, &findData); + + // [neo, 5/15/2007] + // This check was AFTER FindClose for some reason - this is most probably the + // original intent. + if(handle == INVALID_HANDLE_VALUE) + return false; + + FindClose(handle); + + // if the file is a Directory, Offline, System or Temporary then FALSE + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // make sure it's a valid game directory + if (findData.dwFileAttributes & (FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_SYSTEM) ) + return false; + + // must be a directory + return true; + } + + return false; +} + + + +//-------------------------------------- +bool Platform::isSubDirectory(const char *pParent, const char *pDir) +{ + if (!pParent || !*pDir) + return false; + + const char* fileName = avar("%s/*", pParent); + + TempAlloc< TCHAR > file( dStrlen( fileName ) + 1 ); + TempAlloc< TCHAR > dir( dStrlen( pDir ) + 1 ); + +#ifdef UNICODE + convertUTF8toUTF16( fileName, file, file.size ); + convertUTF8toUTF16( pDir, dir, dir.size ); +#else + dStrcpy( file, fileName ); + dStrcpy( dir, pDir ); +#endif + + backslash( file ); + backslash( dir ); + + // this is somewhat of a brute force method but we need to be 100% sure + // that the user cannot enter things like ../dir or /dir etc,... + WIN32_FIND_DATA findData; + HANDLE handle = FindFirstFile(file, &findData); + if (handle == INVALID_HANDLE_VALUE) + return false; + do + { + // if it is a directory... + if (findData.dwFileAttributes & + (FILE_ATTRIBUTE_DIRECTORY| + FILE_ATTRIBUTE_OFFLINE| + FILE_ATTRIBUTE_SYSTEM| + FILE_ATTRIBUTE_TEMPORARY) ) + { + //FIXME: this has to be dStrcasecmp but there's no implementation for Unicode + + // and the names match + if (dStrcmp(dir, findData.cFileName ) == 0) + { + // then we have a real sub directory + FindClose(handle); + return true; + } + } + }while(FindNextFile(handle, &findData)); + + FindClose(handle); + return false; +} + +//------------------------------------------------------------------------------ + +bool Platform::fileTimeToString(FileTime * time, char * string, U32 strLen) +{ + if(!time || !string) + return(false); + + dSprintf(string, strLen, "%d:%d", time->v2, time->v1); + return(true); +} + +bool Platform::stringToFileTime(const char * string, FileTime * time) +{ + if(!time || !string) + return(false); + + char buf[80]; + dSprintf(buf, sizeof(buf), (char *)string); + + char * sep = (char *)dStrstr((const char *)buf, (const char *)":"); + if(!sep) + return(false); + + *sep = 0; + sep++; + + time->v2 = dAtoi(buf); + time->v1 = dAtoi(sep); + + return(true); +} + +// Volume Functions + +void Platform::getVolumeNamesList( Vector& out_rNameVector, bool bOnlyFixedDrives ) +{ + DWORD dwDrives = GetLogicalDrives(); + DWORD dwMask = 1; + char driveLetter[12]; + + out_rNameVector.clear(); + + for(int i = 0; i < 32; i++ ) + { + dMemset(driveLetter,0,12); + if( dwDrives & dwMask ) + { + dSprintf(driveLetter, 12, "%c:", (i + 'A')); + + if( bOnlyFixedDrives && GetDriveTypeA(driveLetter) == DRIVE_FIXED ) + out_rNameVector.push_back( StringTable->insert( driveLetter ) ); + else if ( !bOnlyFixedDrives ) + out_rNameVector.push_back( StringTable->insert( driveLetter ) ); + } + dwMask <<= 1; + } +} + +void Platform::getVolumeInformationList( Vector& out_rVolumeInfoVector, bool bOnlyFixedDrives ) +{ + Vector drives; + + getVolumeNamesList( drives, bOnlyFixedDrives ); + + if( ! drives.empty() ) + { + Vector::iterator i; + for( i = drives.begin(); i != drives.end(); i++ ) + { + VolumeInformation info; + TCHAR lpszVolumeName[ 256 ]; + TCHAR lpszFileSystem[ 256 ]; + DWORD dwSerial = 0; + DWORD dwMaxComponentLength = 0; + DWORD dwFileSystemFlags = 0; + + dMemset( lpszVolumeName, 0, sizeof( lpszVolumeName ) ); + dMemset( lpszFileSystem, 0, sizeof( lpszFileSystem ) ); + dMemset( &info, 0, sizeof( VolumeInformation ) ); + + // More volume information + UINT uDriveType = GetDriveTypeA( (*i) ); + if( uDriveType == DRIVE_UNKNOWN ) + info.Type = DRIVETYPE_UNKNOWN; + else if( uDriveType == DRIVE_REMOVABLE ) + info.Type = DRIVETYPE_REMOVABLE; + else if( uDriveType == DRIVE_FIXED ) + info.Type = DRIVETYPE_FIXED; + else if( uDriveType == DRIVE_CDROM ) + info.Type = DRIVETYPE_CDROM; + else if( uDriveType == DRIVE_RAMDISK ) + info.Type = DRIVETYPE_RAMDISK; + else if( uDriveType == DRIVE_REMOTE ) + info.Type = DRIVETYPE_REMOTE; + + info.RootPath = StringTable->insert( (*i) ); + + // We don't retrieve drive volume info for removable drives, because it's loud :( + if( info.Type != DRIVETYPE_REMOVABLE ) + { +#ifdef UNICODE + WCHAR ibuf[ 3 ]; + ibuf[ 0 ] = ( *i )[ 0 ]; + ibuf[ 1 ] = ':'; + ibuf[ 2 ] = '\0'; +#else + char* ibuf = *i; +#endif + // Standard volume information + GetVolumeInformation( ibuf, lpszVolumeName, sizeof( lpszVolumeName ) / sizeof( lpszVolumeName[ 0 ] ), + &dwSerial, &dwMaxComponentLength, &dwFileSystemFlags, lpszFileSystem, + sizeof( lpszFileSystem ) / sizeof( lpszFileSystem[ 0 ] ) ); + +#ifdef UNICODE + char buf[ sizeof( lpszFileSystem ) / sizeof( lpszFileSystem[ 0 ] ) * 3 + 1 ]; + convertUTF16toUTF8( lpszFileSystem, buf, sizeof( buf ) / sizeof( buf[ 0 ] ) ); + info.FileSystem = StringTable->insert( buf ); + + convertUTF16toUTF8( lpszVolumeName, buf, sizeof( buf ) / sizeof( buf[ 0 ] ) ); + info.Name = StringTable->insert( buf ); +#else + info.FileSystem = StringTable->insert( lpszFileSystem ); + info.Name = StringTable->insert( lpszVolumeName ); +#endif + info.SerialNumber = dwSerial; + // Won't compile on something prior to XP. + info.ReadOnly = dwFileSystemFlags & FILE_READ_ONLY_VOLUME; + } + out_rVolumeInfoVector.push_back( info ); + + // I opted not to get free disk space because of the overhead of the calculations required for it + + } + } +} + + +bool Platform::hasSubDirectory(const char *pPath) +{ + if( !pPath ) + return false; + + char searchBuf[1024]; + + // Compose our search string - Format : ([path]/[subpath]/*) + char trail = pPath[ dStrlen(pPath) - 1 ]; + if( trail == '/' ) + dStrcpy( searchBuf, pPath ); + else + dSprintf(searchBuf, 1024, "%s/*", pPath ); + +#ifdef UNICODE + WCHAR buf[ 1024 ]; + convertUTF8toUTF16( searchBuf, buf, sizeof( buf ) / sizeof( buf[ 0 ] ) ); + WCHAR* search = buf; +#else + char* search = searchBuf; +#endif + + backslash( search ); + + // See if we get any hits + WIN32_FIND_DATA findData; + HANDLE handle = FindFirstFile(search, &findData); + if (handle == INVALID_HANDLE_VALUE) + return false; + + bool result = false; + do + { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // skip . and .. directories + if (dStrcmp(findData.cFileName, TEXT( "." ) ) == 0 || dStrcmp(findData.cFileName, TEXT( ".." ) ) == 0) + continue; + +#ifdef UNICODE + char fileName[ 1024 ]; + convertUTF16toUTF8( findData.cFileName, fileName, sizeof( fileName ) / sizeof( fileName[ 0 ] ) ); +#else + char* fileName = findData.cFileName; +#endif + + if( Platform::isExcludedDirectory( fileName ) ) + continue; + + result = true; + break; + } + } + while(FindNextFile(handle, &findData)); + + FindClose(handle); + + Platform::clearExcludedDirectories(); + + return result; +} + + +static bool recurseDumpDirectories(const char *basePath, const char *subPath, Vector &directoryVector, S32 currentDepth, S32 recurseDepth, bool noBasePath) +{ + TempAlloc< char > search( 1024 ); + + //----------------------------------------------------------------------------- + // Compose our search string - Format : ([path]/[subpath]/*) + //----------------------------------------------------------------------------- + + char trail = basePath[ dStrlen(basePath) - 1 ]; + char subTrail = subPath ? subPath[ dStrlen(subPath) - 1 ] : '\0'; + + if( trail == '/' ) + { + // we have a sub path and it's not an empty string + if( subPath && ( dStrncmp( subPath, "", 1 ) != 0 ) ) + { + if( subTrail == '/' ) + dSprintf(search, search.size, "%s%s*", basePath,subPath ); + else + dSprintf(search, search.size, "%s%s/*", basePath,subPath ); + } + else + dSprintf( search, search.size, "%s*", basePath ); + } + else + { + if( subPath && ( dStrncmp( subPath, "", 1 ) != 0 ) ) + if( subTrail == '/' ) + dSprintf(search, search.size, "%s%s*", basePath,subPath ); + else + dSprintf(search, search.size, "%s%s/*", basePath,subPath ); + else + dSprintf(search, search.size, "%s/*", basePath ); + } + +#ifdef UNICODE + TempAlloc< WCHAR > searchStr( dStrlen( search ) + 1 ); + convertUTF8toUTF16( search, searchStr, searchStr.size ); +#else + char* searchStr = search; +#endif + + backslash( searchStr ); + + //----------------------------------------------------------------------------- + // See if we get any hits + //----------------------------------------------------------------------------- + + WIN32_FIND_DATA findData; + HANDLE handle = FindFirstFile(searchStr, &findData); + if (handle == INVALID_HANDLE_VALUE) + return false; + + //----------------------------------------------------------------------------- + // add path to our return list ( provided it is valid ) + //----------------------------------------------------------------------------- + if( !Platform::isExcludedDirectory( subPath ) ) + { + + if( noBasePath ) + { + // We have a path and it's not an empty string or an excluded directory + if( ( subPath && ( dStrncmp( subPath, "", 1 ) != 0 ) ) ) + directoryVector.push_back( StringTable->insert( subPath ) ); + } + else + { + if( ( subPath && ( dStrncmp( subPath, "", 1 ) != 0 ) ) ) + { + char szPath [ 1024 ]; + dMemset( szPath, 0, 1024 ); + if( trail != '/' ) + dSprintf( szPath, 1024, "%s%s", basePath, subPath ); + else + dSprintf( szPath, 1024, "%s%s", basePath, &subPath[1] ); + directoryVector.push_back( StringTable->insert( szPath ) ); + } + else + directoryVector.push_back( StringTable->insert( basePath ) ); + } + } + + //----------------------------------------------------------------------------- + // Iterate through and grab valid directories + //----------------------------------------------------------------------------- + +#ifdef UNICODE + TempAlloc< char > fileName( 1024 ); +#endif + + do + { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // skip . and .. directories + if (dStrcmp(findData.cFileName, TEXT( "." )) == 0 || dStrcmp(findData.cFileName, TEXT( ".." )) == 0) + continue; + +#ifdef UNICODE + convertUTF16toUTF8( findData.cFileName, fileName, fileName.size ); +#else + char* fileName = findData.cFileName; +#endif + + // skip excluded directories + if( Platform::isExcludedDirectory( fileName ) ) + continue; + + if( ( subPath && ( dStrncmp( subPath, "", 1 ) != 0 ) )) + { + if( subTrail == '/' ) + dSprintf(search, search.size, "%s%s", subPath, fileName); + else + dSprintf(search, search.size, "%s/%s", subPath, fileName); + char* child = search; + + if( currentDepth < recurseDepth || recurseDepth == -1 ) + recurseDumpDirectories(basePath, child, directoryVector, currentDepth+1, recurseDepth, noBasePath ); + + } + else + { + char* child; + if( trail == '/' ) + child = fileName; + else + { + dSprintf(search, search.size, "/%s", fileName); + child = search; + } + + if( currentDepth < recurseDepth || recurseDepth == -1 ) + recurseDumpDirectories(basePath, child, directoryVector, currentDepth+1, recurseDepth, noBasePath ); + } + } + } + while(FindNextFile(handle, &findData)); + + FindClose(handle); + return true; +} + +bool Platform::dumpDirectories( const char *path, Vector &directoryVector, S32 depth, bool noBasePath ) +{ + bool retVal = recurseDumpDirectories( path, "", directoryVector, -1, depth, noBasePath ); + + clearExcludedDirectories(); + + return retVal; +} + +//----------------------------------------------------------------------------- + +StringTableEntry osGetTemporaryDirectory() +{ + TCHAR buf[ 1024 ]; + const U32 bufSize = sizeof( buf ) / sizeof( buf[ 0 ] ); + DWORD len = GetTempPath( sizeof( buf ) / sizeof( buf[ 0 ] ), buf ); + + TempAlloc< TCHAR > temp; + TCHAR* buffer = buf; + if( len > bufSize - 1 ) + { + temp = TempAlloc< TCHAR >( len + 1 ); + buffer = temp; + GetTempPath( len + 1, buffer ); + } + + // Remove the trailing slash + buffer[len-1] = 0; + +#ifdef UNICODE + TempAlloc< char > dirBuffer( len * 3 + 1 ); + char* dir = dirBuffer; + convertUTF16toUTF8( buffer, dir, dirBuffer.size ); +#else + char* dir = buf; +#endif + + forwardslash(dir); + return StringTable->insert(dir); +} diff --git a/platformWin32/winFont.cpp b/platformWin32/winFont.cpp new file mode 100644 index 0000000..045afe4 --- /dev/null +++ b/platformWin32/winFont.cpp @@ -0,0 +1,294 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "platformWin32/winFont.h" +#include "gfx/gFont.h" +#include "gfx/gFont.h" +#include "gfx/bitmap/gBitmap.h" +#include "math/mRect.h" +#include "console/console.h" +#include "core/strings/unicode.h" +#include "core/strings/stringFunctions.h" +#include "core/stringTable.h" + +static HDC fontHDC = NULL; +static HBITMAP fontBMP = NULL; + +static U32 charsetMap[]= +{ + ANSI_CHARSET, + SYMBOL_CHARSET, + SHIFTJIS_CHARSET, + HANGEUL_CHARSET, + HANGUL_CHARSET, + GB2312_CHARSET, + CHINESEBIG5_CHARSET, + OEM_CHARSET, + JOHAB_CHARSET, + HEBREW_CHARSET, + ARABIC_CHARSET, + GREEK_CHARSET, + TURKISH_CHARSET, + VIETNAMESE_CHARSET, + THAI_CHARSET, + EASTEUROPE_CHARSET, + RUSSIAN_CHARSET, + MAC_CHARSET, + BALTIC_CHARSET, +}; +#define NUMCHARSETMAP (sizeof(charsetMap) / sizeof(U32)) + +void createFontInit(void); +void createFontShutdown(void); +void CopyCharToBitmap(GBitmap *pDstBMP, HDC hSrcHDC, const RectI &r); + +void createFontInit() +{ + //shared library sets the appInstance here + winState.appInstance = GetModuleHandle(NULL); + fontHDC = CreateCompatibleDC(NULL); + fontBMP = CreateCompatibleBitmap(fontHDC, 256, 256); +} + +void createFontShutdown() +{ + DeleteObject(fontBMP); + DeleteObject(fontHDC); +} + +void CopyCharToBitmap(GBitmap *pDstBMP, HDC hSrcHDC, const RectI &r) +{ + for (S32 i = r.point.y; i < r.point.y + r.extent.y; i++) + { + for (S32 j = r.point.x; j < r.point.x + r.extent.x; j++) + { + COLORREF color = GetPixel(hSrcHDC, j, i); + if (color) + *pDstBMP->getAddress(j, i) = 255; + else + *pDstBMP->getAddress(j, i) = 0; + } + } +} + +//----------------------------------------------------------------------------- +// WinFont class +//----------------------------------------------------------------------------- + +BOOL CALLBACK EnumFamCallBack(LPLOGFONT logFont, LPNEWTEXTMETRIC textMetric, DWORD fontType, LPARAM lParam) +{ + if( !( fontType & TRUETYPE_FONTTYPE ) ) + return true; + + Vector* fonts = (Vector< StringTableEntry>*)lParam; + + const U32 len = dStrlen( logFont->lfFaceName ) * 3 + 1; + FrameTemp buffer( len ); + convertUTF16toUTF8( logFont->lfFaceName, buffer, len ); + + fonts->push_back( StringTable->insert( buffer ) ); + + return true; +} + +void PlatformFont::enumeratePlatformFonts( Vector& fonts, UTF16* fontFamily ) +{ + EnumFontFamilies( fontHDC, fontFamily, (FONTENUMPROC)EnumFamCallBack, (LPARAM)&fonts ); +} + +PlatformFont *createPlatformFont(const char *name, U32 size, U32 charset /* = TGE_ANSI_CHARSET */) +{ + PlatformFont *retFont = new WinFont; + + if(retFont->create(name, size, charset)) + return retFont; + + delete retFont; + return NULL; +} + +WinFont::WinFont() : mFont(NULL) +{ +} + +WinFont::~WinFont() +{ + if(mFont) + { + DeleteObject(mFont); + } +} + +bool WinFont::create(const char *name, U32 size, U32 charset /* = TGE_ANSI_CHARSET */) +{ + if(name == NULL || size < 1) + return false; + + if(charset > NUMCHARSETMAP) + charset = TGE_ANSI_CHARSET; + + U32 weight = 0; + U32 doItalic = 0; + + String nameStr = name; + nameStr = nameStr.trim(); + + bool haveModifier; + do + { + haveModifier = false; + if( nameStr.compare( "Bold", 4, String::NoCase | String::Right ) == 0 ) + { + weight = 700; + nameStr = nameStr.substr( 0, nameStr.length() - 4 ).trim(); + haveModifier = true; + } + if( nameStr.compare( "Italic", 6, String::NoCase | String::Right ) == 0 ) + { + doItalic = 1; + nameStr = nameStr.substr( 0, nameStr.length() - 6 ).trim(); + haveModifier = true; + } + } + while( haveModifier ); + +#ifdef UNICODE + const UTF16* n = nameStr.utf16(); + mFont = CreateFont(size,0,0,0,weight,doItalic,0,0,DEFAULT_CHARSET,OUT_TT_PRECIS,0,PROOF_QUALITY,0,n); +#else + mFont = CreateFont(size,0,0,0,weight,doItalic,0,0,charsetMap[charset],OUT_TT_PRECIS,0,PROOF_QUALITY,0,name); +#endif + if(mFont == NULL) + return false; + + SelectObject(fontHDC, fontBMP); + SelectObject(fontHDC, mFont); + GetTextMetrics(fontHDC, &mTextMetric); + + return true; +} + +bool WinFont::isValidChar(const UTF16 ch) const +{ + return ch != 0 /* && (ch >= mTextMetric.tmFirstChar && ch <= mTextMetric.tmLastChar)*/; +} + +bool WinFont::isValidChar(const UTF8 *str) const +{ + return isValidChar(oneUTF8toUTF32(str)); +} + + +PlatformFont::CharInfo &WinFont::getCharInfo(const UTF16 ch) const +{ + static PlatformFont::CharInfo c; + + dMemset(&c, 0, sizeof(c)); + c.bitmapIndex = -1; + + static U8 scratchPad[65536]; + + COLORREF backgroundColorRef = RGB( 0, 0, 0); + COLORREF foregroundColorRef = RGB(255, 255, 255); + SelectObject(fontHDC, fontBMP); + SelectObject(fontHDC, mFont); + SetBkColor(fontHDC, backgroundColorRef); + SetTextColor(fontHDC, foregroundColorRef); + + MAT2 matrix; + GLYPHMETRICS metrics; + RectI clip; + + FIXED zero; + zero.fract = 0; + zero.value = 0; + FIXED one; + one.fract = 0; + one.value = 1; + + matrix.eM11 = one; + matrix.eM12 = zero; + matrix.eM21 = zero; + matrix.eM22 = one; + + + if(GetGlyphOutline( + fontHDC, // handle of device context + ch, // character to query + GGO_GRAY8_BITMAP, // format of data to return + &metrics, // address of structure for metrics + sizeof(scratchPad), // size of buffer for data + scratchPad, // address of buffer for data + &matrix // address of transformation matrix structure + ) != GDI_ERROR) + { + U32 rowStride = (metrics.gmBlackBoxX + 3) & ~3; // DWORD aligned + U32 size = rowStride * metrics.gmBlackBoxY; + + // [neo, 5/7/2007 - #3055] + // If we get large font sizes rowStride * metrics.gmBlackBoxY will + // be larger than scratch pad size and so overwrite mem, boom! + // Added range check < scratchPad for now but we need to review what + // to do here - do we want to call GetGlyphOutline() first with null + // values and get the real size to alloc buffer? + //if( size > sizeof( scratchPad ) ) + // DebugBreak(); + + for(U32 j = 0; j < size && j < sizeof(scratchPad); j++) + { + U32 pad = U32(scratchPad[j]) << 2; + if(pad > 255) + pad = 255; + scratchPad[j] = pad; + } + S32 inc = metrics.gmCellIncX; + if(inc < 0) + inc = -inc; + + c.xOffset = 0; + c.yOffset = 0; + c.width = metrics.gmBlackBoxX; + c.height = metrics.gmBlackBoxY; + c.xOrigin = metrics.gmptGlyphOrigin.x; + c.yOrigin = metrics.gmptGlyphOrigin.y; + c.xIncrement = metrics.gmCellIncX; + + c.bitmapData = new U8[c.width * c.height]; + AssertFatal( c.bitmapData != NULL, "Could not allocate memory for font bitmap data!"); + for(U32 y = 0; S32(y) < c.height; y++) + { + U32 x; + for(x = 0; x < c.width; x++) + { + // [neo, 5/7/2007 - #3055] + // See comments above about scratchPad overrun + S32 spi = y * rowStride + x; + + if( spi >= sizeof(scratchPad) ) + return c; + + c.bitmapData[y * c.width + x] = scratchPad[spi]; + } + } + } + else + { + SIZE size; + GetTextExtentPoint32W(fontHDC, &ch, 1, &size); + if(size.cx) + { + c.xIncrement = size.cx; + c.bitmapIndex = 0; + } + } + + return c; +} + +PlatformFont::CharInfo &WinFont::getCharInfo(const UTF8 *str) const +{ + return getCharInfo(oneUTF8toUTF32(str)); +} diff --git a/platformWin32/winFont.h b/platformWin32/winFont.h new file mode 100644 index 0000000..4e27190 --- /dev/null +++ b/platformWin32/winFont.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMFONT_H_ +#include "platform/platformFont.h" +#endif + +#ifndef _WINFONT_H_ +#define _WINFONT_H_ + +class WinFont : public PlatformFont +{ +private: + HFONT mFont; + TEXTMETRIC mTextMetric; + +public: + WinFont(); + virtual ~WinFont(); + + // PlatformFont virtual methods + virtual bool isValidChar(const UTF16 ch) const; + virtual bool isValidChar(const UTF8 *str) const; + + inline virtual U32 getFontHeight() const + { + return mTextMetric.tmHeight; + } + + inline virtual U32 getFontBaseLine() const + { + return mTextMetric.tmAscent; + } + + virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const; + virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const; + + virtual bool create(const char *name, dsize_t size, U32 charset = TGE_ANSI_CHARSET); +}; + +#endif // _WINFONT_H_ diff --git a/platformWin32/winInput.cpp b/platformWin32/winInput.cpp new file mode 100644 index 0000000..0e532ad --- /dev/null +++ b/platformWin32/winInput.cpp @@ -0,0 +1,852 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "platform/platformInput.h" +#include "gfx/gfxDevice.h" +#include "platformWin32/winDirectInput.h" +#include "platform/event.h" +#include "console/console.h" +#include "gfx/gfxDevice.h" +#include "core/util/journal/process.h" +#include "windowManager/platformWindowMgr.h" + +#ifdef LOG_INPUT +#include +#include +#endif + +// Static class variables: +InputManager* Input::smManager; +bool Input::smActive; +U8 Input::smModifierKeys; +bool Input::smLastKeyboardActivated; +bool Input::smLastMouseActivated; +bool Input::smLastJoystickActivated; +InputEvent Input::smInputEvent; + +#ifdef LOG_INPUT +static HANDLE gInputLog; +#endif + +static void fillAsciiTable(); + +//------------------------------------------------------------------------------ +// +// This function gets the standard ASCII code corresponding to our key code +// and the existing modifier key state. +// +//------------------------------------------------------------------------------ +struct AsciiData +{ + struct KeyData + { + U16 ascii; + bool isDeadChar; + }; + + KeyData upper; + KeyData lower; + KeyData goofy; +}; + + +#define NUM_KEYS ( KEY_OEM_102 + 1 ) +#define KEY_FIRST KEY_ESCAPE +static AsciiData AsciiTable[NUM_KEYS]; + +//------------------------------------------------------------------------------ +void Input::init() +{ + Con::printf( "Input Init:" ); + + destroy(); + +#ifdef LOG_INPUT + struct tm* newTime; + time_t aclock; + time( &aclock ); + newTime = localtime( &aclock ); + asctime( newTime ); + + gInputLog = CreateFile( L"input.log", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + log( "Input log opened at %s\n", asctime( newTime ) ); +#endif + + smActive = false; + smLastKeyboardActivated = true; + smLastMouseActivated = true; + smLastJoystickActivated = true; + + OSVERSIONINFO OSVersionInfo; + dMemset( &OSVersionInfo, 0, sizeof( OSVERSIONINFO ) ); + OSVersionInfo.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + if ( GetVersionEx( &OSVersionInfo ) ) + { +#ifdef LOG_INPUT + log( "Operating System:\n" ); + switch ( OSVersionInfo.dwPlatformId ) + { + case VER_PLATFORM_WIN32s: + log( " Win32s on Windows 3.1 version %d.%d\n", OSVersionInfo.dwMajorVersion, OSVersionInfo.dwMinorVersion ); + break; + + case VER_PLATFORM_WIN32_WINDOWS: + log( " Windows 95 version %d.%d\n", OSVersionInfo.dwMajorVersion, OSVersionInfo.dwMinorVersion ); + log( " Build number %d\n", LOWORD( OSVersionInfo.dwBuildNumber ) ); + break; + + case VER_PLATFORM_WIN32_NT: + log( " WinNT version %d.%d\n", OSVersionInfo.dwMajorVersion, OSVersionInfo.dwMinorVersion ); + log( " Build number %d\n", OSVersionInfo.dwBuildNumber ); + break; + } + + if ( OSVersionInfo.szCSDVersion != NULL ) + log( " %s\n", OSVersionInfo.szCSDVersion ); + + log( "\n" ); +#endif + + if ( !( OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && OSVersionInfo.dwMajorVersion < 5 ) ) + { + smManager = new DInputManager; + if ( !smManager->enable() ) + { + Con::printf( " DirectInput not enabled." ); + delete smManager; + smManager = NULL; + } + else + { + DInputManager::init(); + Con::printf( " DirectInput enabled." ); + } + } + else + Con::printf( " WinNT detected -- DirectInput not enabled." ); + } + + // Init the current modifier keys + setModifierKeys(0); + fillAsciiTable(); + Con::printf( "" ); + + // Set ourselves to participate in per-frame processing. + Process::notify(Input::process, PROCESS_INPUT_ORDER); + +} + +//------------------------------------------------------------------------------ +ConsoleFunction( isJoystickDetected, bool, 1, 1, "isJoystickDetected()" ) +{ + argc; argv; + return( DInputDevice::joystickDetected() ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( getJoystickAxes, const char*, 2, 2, "getJoystickAxes( instance )" ) +{ + argc; + DInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + return( mgr->getJoystickAxesString( dAtoi( argv[1] ) ) ); + + return( "" ); +} + +//------------------------------------------------------------------------------ +static void fillAsciiTable() +{ +#ifdef LOG_INPUT + char buf[256]; + Input::log( "--- Filling the ASCII table! ---\n" ); +#endif + + //HKL layout = GetKeyboardLayout( 0 ); + U8 state[256]; + U16 ascii[2]; + U32 dikCode, vKeyCode, keyCode; + S32 result; + + dMemset( &AsciiTable, 0, sizeof( AsciiTable ) ); + dMemset( &state, 0, sizeof( state ) ); + + for ( keyCode = KEY_FIRST; keyCode < NUM_KEYS; keyCode++ ) + { + ascii[0] = ascii[1] = 0; + dikCode = Key_to_DIK( keyCode ); + + // This is a special case for numpad keys. + // + // The KEY_NUMPAD# torque types represent the event generated when a + // numpad key is pressed WITH NUMLOCK ON. Therefore it does have an ascii + // value, but to get it from windows we must specify in the keyboard state + // that numlock is on. + if ( KEY_NUMPAD0 <= keyCode && keyCode <= KEY_NUMPAD9 ) + { + state[VK_NUMLOCK] = 0x80; + + // Also, numpad keys return completely different keycodes when + // numlock is not pressed (home,insert,etc) and MapVirtualKey + // appears to always return those values. + // + // So I'm using TranslateKeyCodeToOS instead. Really it looks + // like we could be using this method for all of them and + // cut out the Key_to_DIK middleman. + // + vKeyCode = TranslateKeyCodeToOS( keyCode ); + + result = ToAscii( vKeyCode, dikCode, state, ascii, 0 ); + + AsciiTable[keyCode].lower.ascii = ascii[0]; + AsciiTable[keyCode].upper.ascii = 0; + AsciiTable[keyCode].goofy.ascii = 0; + + state[VK_NUMLOCK] = 0; + + continue; + } + + if ( dikCode ) + { + //vKeyCode = MapVirtualKeyEx( dikCode, 1, layout ); + vKeyCode = MapVirtualKey( dikCode, 1 ); +#ifdef LOG_INPUT + dSprintf( buf, sizeof( buf ), "KC: %#04X DK: %#04X VK: %#04X\n", + keyCode, dikCode, vKeyCode ); + Input::log( buf ); +#endif + + // Lower case: + ascii[0] = ascii[1] = 0; + //result = ToAsciiEx( vKeyCode, dikCode, state, ascii, 0, layout ); + result = ToAscii( vKeyCode, dikCode, state, ascii, 0 ); +#ifdef LOG_INPUT + dSprintf( buf, sizeof( buf ), " LOWER- R: %d A[0]: %#06X A[1]: %#06X\n", + result, ascii[0], ascii[1] ); + Input::log( buf ); +#endif + if ( result == 2 ) + AsciiTable[keyCode].lower.ascii = ascii[1] ? ascii[1] : ( ascii[0] >> 8 ); + else if ( result == 1 ) + AsciiTable[keyCode].lower.ascii = ascii[0]; + else if ( result < 0 ) + { + AsciiTable[keyCode].lower.ascii = ascii[0]; + AsciiTable[keyCode].lower.isDeadChar = true; + // Need to clear the dead character from the keyboard layout: + //ToAsciiEx( vKeyCode, dikCode, state, ascii, 0, layout ); + ToAscii( vKeyCode, dikCode, state, ascii, 0 ); + } + + // Upper case: + ascii[0] = ascii[1] = 0; + state[VK_SHIFT] = 0x80; + //result = ToAsciiEx( vKeyCode, dikCode, state, ascii, 0, layout ); + result = ToAscii( vKeyCode, dikCode, state, ascii, 0 ); +#ifdef LOG_INPUT + dSprintf( buf, sizeof( buf ), " UPPER- R: %d A[0]: %#06X A[1]: %#06X\n", + result, ascii[0], ascii[1] ); + Input::log( buf ); +#endif + if ( result == 2 ) + AsciiTable[keyCode].upper.ascii = ascii[1] ? ascii[1] : ( ascii[0] >> 8 ); + else if ( result == 1 ) + AsciiTable[keyCode].upper.ascii = ascii[0]; + else if ( result < 0 ) + { + AsciiTable[keyCode].upper.ascii = ascii[0]; + AsciiTable[keyCode].upper.isDeadChar = true; + // Need to clear the dead character from the keyboard layout: + //ToAsciiEx( vKeyCode, dikCode, state, ascii, 0, layout ); + ToAscii( vKeyCode, dikCode, state, ascii, 0 ); + } + state[VK_SHIFT] = 0; + + // Foreign mod case: + ascii[0] = ascii[1] = 0; + state[VK_CONTROL] = 0x80; + state[VK_MENU] = 0x80; + //result = ToAsciiEx( vKeyCode, dikCode, state, ascii, 0, layout ); + result = ToAscii( vKeyCode, dikCode, state, ascii, 0 ); +#ifdef LOG_INPUT + dSprintf( buf, sizeof( buf ), " GOOFY- R: %d A[0]: %#06X A[1]: %#06X\n", + result, ascii[0], ascii[1] ); + Input::log( buf ); +#endif + if ( result == 2 ) + AsciiTable[keyCode].goofy.ascii = ascii[1] ? ascii[1] : ( ascii[0] >> 8 ); + else if ( result == 1 ) + AsciiTable[keyCode].goofy.ascii = ascii[0]; + else if ( result < 0 ) + { + AsciiTable[keyCode].goofy.ascii = ascii[0]; + AsciiTable[keyCode].goofy.isDeadChar = true; + // Need to clear the dead character from the keyboard layout: + //ToAsciiEx( vKeyCode, dikCode, state, ascii, 0, layout ); + ToAscii( vKeyCode, dikCode, state, ascii, 0 ); + } + state[VK_CONTROL] = 0; + state[VK_MENU] = 0; + } + } + +#ifdef LOG_INPUT + Input::log( "--- Finished filling the ASCII table! ---\n\n" ); +#endif +} + +//------------------------------------------------------------------------------ +U16 Input::getKeyCode( U16 asciiCode ) +{ + U16 keyCode = 0; + U16 i; + + // This is done three times so the lowerkey will always + // be found first. Some foreign keyboards have duplicate + // chars on some keys. + for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) + { + if ( AsciiTable[i].lower.ascii == asciiCode ) + { + keyCode = i; + break; + }; + } + + for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) + { + if ( AsciiTable[i].upper.ascii == asciiCode ) + { + keyCode = i; + break; + }; + } + + for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) + { + if ( AsciiTable[i].goofy.ascii == asciiCode ) + { + keyCode = i; + break; + }; + } + + return( keyCode ); +} + +//------------------------------------------------------------------------------ +U16 Input::getAscii( U16 keyCode, KEY_STATE keyState ) +{ + if ( keyCode >= NUM_KEYS ) + return 0; + + switch ( keyState ) + { + case STATE_LOWER: + return AsciiTable[keyCode].lower.ascii; + case STATE_UPPER: + return AsciiTable[keyCode].upper.ascii; + case STATE_GOOFY: + return AsciiTable[keyCode].goofy.ascii; + default: + return(0); + + } +} + +//------------------------------------------------------------------------------ +void Input::destroy() +{ + Process::remove(Input::process); + +#ifdef LOG_INPUT + if ( gInputLog ) + { + log( "*** CLOSING LOG ***\n" ); + CloseHandle( gInputLog ); + gInputLog = NULL; + } +#endif + + if ( smManager && smManager->isEnabled() ) + { + smManager->disable(); + delete smManager; + smManager = NULL; + } +} + +//------------------------------------------------------------------------------ +bool Input::enable() +{ + if ( smManager && !smManager->isEnabled() ) + return( smManager->enable() ); + + return( false ); +} + +//------------------------------------------------------------------------------ +void Input::disable() +{ + if ( smManager && smManager->isEnabled() ) + smManager->disable(); +} + +//------------------------------------------------------------------------------ + +void Input::activate() +{ +#ifdef UNICODE + //winState.imeHandle = ImmGetContext( getWin32WindowHandle() ); + //ImmReleaseContext( getWin32WindowHandle(), winState.imeHandle ); +#endif + + if ( !Con::getBoolVariable( "$enableDirectInput" ) ) + return; + + if ( smManager && smManager->isEnabled() && !smActive ) + { + Con::printf( "Activating DirectInput..." ); +#ifdef LOG_INPUT + Input::log( "Activating DirectInput...\n" ); +#endif + smActive = true; + DInputManager* dInputManager = dynamic_cast( smManager ); + if ( dInputManager ) + { + if ( dInputManager->isJoystickEnabled() && smLastJoystickActivated ) + dInputManager->activateJoystick(); + } + } +} + +//------------------------------------------------------------------------------ +void Input::deactivate() +{ + if ( smManager && smManager->isEnabled() && smActive ) + { +#ifdef LOG_INPUT + Input::log( "Deactivating DirectInput...\n" ); +#endif + DInputManager* dInputManager = dynamic_cast( smManager ); + + if ( dInputManager ) + { + smLastJoystickActivated = dInputManager->isJoystickActive(); + dInputManager->deactivateJoystick(); + } + + smActive = false; + Con::printf( "DirectInput deactivated." ); + } +} + +//------------------------------------------------------------------------------ +bool Input::isEnabled() +{ + if ( smManager ) + return smManager->isEnabled(); + return false; +} + +//------------------------------------------------------------------------------ +bool Input::isActive() +{ + return smActive; +} + +//------------------------------------------------------------------------------ +void Input::process() +{ + if ( smManager && smManager->isEnabled() && smActive ) + smManager->process(); +} + +//------------------------------------------------------------------------------ +InputManager* Input::getManager() +{ + return( smManager ); +} + +#ifdef LOG_INPUT +//------------------------------------------------------------------------------ +void Input::log( const char* format, ... ) +{ + if ( !gInputLog ) + return; + + va_list argptr; + va_start( argptr, format ); + + char buffer[512]; + dVsprintf( buffer, 511, format, argptr ); + DWORD bytes; + WriteFile( gInputLog, buffer, dStrlen( buffer ), &bytes, NULL ); + + va_end( argptr ); +} + +ConsoleFunction( inputLog, void, 2, 2, "inputLog( string )" ) +{ + argc; + Input::log( "%s\n", argv[1] ); +} +#endif // LOG_INPUT + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +static U8 VcodeRemap[256] = +{ +0, // 0x00 +0, // 0x01 VK_LBUTTON +0, // 0x02 VK_RBUTTON +0, // 0x03 VK_CANCEL +0, // 0x04 VK_MBUTTON +0, // 0x05 +0, // 0x06 +0, // 0x07 +KEY_BACKSPACE, // 0x08 VK_BACK +KEY_TAB, // 0x09 VK_TAB +0, // 0x0A +0, // 0x0B +0, // 0x0C VK_CLEAR +KEY_RETURN, // 0x0D VK_RETURN +0, // 0x0E +0, // 0x0F +KEY_SHIFT, // 0x10 VK_SHIFT +KEY_CONTROL, // 0x11 VK_CONTROL +KEY_ALT, // 0x12 VK_MENU +KEY_PAUSE, // 0x13 VK_PAUSE +KEY_CAPSLOCK, // 0x14 VK_CAPITAL +0, // 0x15 VK_KANA, VK_HANGEUL, VK_HANGUL +0, // 0x16 +0, // 0x17 VK_JUNJA +0, // 0x18 VK_FINAL +0, // 0x19 VK_HANJA, VK_KANJI +0, // 0x1A +KEY_ESCAPE, // 0x1B VK_ESCAPE + +0, // 0x1C VK_CONVERT +0, // 0x1D VK_NONCONVERT +0, // 0x1E VK_ACCEPT +0, // 0x1F VK_MODECHANGE + +KEY_SPACE, // 0x20 VK_SPACE +KEY_PAGE_UP, // 0x21 VK_PRIOR +KEY_PAGE_DOWN, // 0x22 VK_NEXT +KEY_END, // 0x23 VK_END +KEY_HOME, // 0x24 VK_HOME +KEY_LEFT, // 0x25 VK_LEFT +KEY_UP, // 0x26 VK_UP +KEY_RIGHT, // 0x27 VK_RIGHT +KEY_DOWN, // 0x28 VK_DOWN +0, // 0x29 VK_SELECT +KEY_PRINT, // 0x2A VK_PRINT +0, // 0x2B VK_EXECUTE +0, // 0x2C VK_SNAPSHOT +KEY_INSERT, // 0x2D VK_INSERT +KEY_DELETE, // 0x2E VK_DELETE +KEY_HELP, // 0x2F VK_HELP + +KEY_0, // 0x30 VK_0 VK_0 thru VK_9 are the same as ASCII '0' thru '9' (// 0x30 - // 0x39) +KEY_1, // 0x31 VK_1 +KEY_2, // 0x32 VK_2 +KEY_3, // 0x33 VK_3 +KEY_4, // 0x34 VK_4 +KEY_5, // 0x35 VK_5 +KEY_6, // 0x36 VK_6 +KEY_7, // 0x37 VK_7 +KEY_8, // 0x38 VK_8 +KEY_9, // 0x39 VK_9 +0, // 0x3A +0, // 0x3B +0, // 0x3C +0, // 0x3D +0, // 0x3E +0, // 0x3F +0, // 0x40 + +KEY_A, // 0x41 VK_A VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (// 0x41 - // 0x5A) +KEY_B, // 0x42 VK_B +KEY_C, // 0x43 VK_C +KEY_D, // 0x44 VK_D +KEY_E, // 0x45 VK_E +KEY_F, // 0x46 VK_F +KEY_G, // 0x47 VK_G +KEY_H, // 0x48 VK_H +KEY_I, // 0x49 VK_I +KEY_J, // 0x4A VK_J +KEY_K, // 0x4B VK_K +KEY_L, // 0x4C VK_L +KEY_M, // 0x4D VK_M +KEY_N, // 0x4E VK_N +KEY_O, // 0x4F VK_O +KEY_P, // 0x50 VK_P +KEY_Q, // 0x51 VK_Q +KEY_R, // 0x52 VK_R +KEY_S, // 0x53 VK_S +KEY_T, // 0x54 VK_T +KEY_U, // 0x55 VK_U +KEY_V, // 0x56 VK_V +KEY_W, // 0x57 VK_W +KEY_X, // 0x58 VK_X +KEY_Y, // 0x59 VK_Y +KEY_Z, // 0x5A VK_Z + + +KEY_WIN_LWINDOW, // 0x5B VK_LWIN +KEY_WIN_RWINDOW, // 0x5C VK_RWIN +KEY_WIN_APPS, // 0x5D VK_APPS +0, // 0x5E +0, // 0x5F + +KEY_NUMPAD0, // 0x60 VK_NUMPAD0 +KEY_NUMPAD1, // 0x61 VK_NUMPAD1 +KEY_NUMPAD2, // 0x62 VK_NUMPAD2 +KEY_NUMPAD3, // 0x63 VK_NUMPAD3 +KEY_NUMPAD4, // 0x64 VK_NUMPAD4 +KEY_NUMPAD5, // 0x65 VK_NUMPAD5 +KEY_NUMPAD6, // 0x66 VK_NUMPAD6 +KEY_NUMPAD7, // 0x67 VK_NUMPAD7 +KEY_NUMPAD8, // 0x68 VK_NUMPAD8 +KEY_NUMPAD9, // 0x69 VK_NUMPAD9 +KEY_MULTIPLY, // 0x6A VK_MULTIPLY +KEY_ADD, // 0x6B VK_ADD +KEY_SEPARATOR, // 0x6C VK_SEPARATOR +KEY_SUBTRACT, // 0x6D VK_SUBTRACT +KEY_DECIMAL, // 0x6E VK_DECIMAL +KEY_DIVIDE, // 0x6F VK_DIVIDE +KEY_F1, // 0x70 VK_F1 +KEY_F2, // 0x71 VK_F2 +KEY_F3, // 0x72 VK_F3 +KEY_F4, // 0x73 VK_F4 +KEY_F5, // 0x74 VK_F5 +KEY_F6, // 0x75 VK_F6 +KEY_F7, // 0x76 VK_F7 +KEY_F8, // 0x77 VK_F8 +KEY_F9, // 0x78 VK_F9 +KEY_F10, // 0x79 VK_F10 +KEY_F11, // 0x7A VK_F11 +KEY_F12, // 0x7B VK_F12 +KEY_F13, // 0x7C VK_F13 +KEY_F14, // 0x7D VK_F14 +KEY_F15, // 0x7E VK_F15 +KEY_F16, // 0x7F VK_F16 +KEY_F17, // 0x80 VK_F17 +KEY_F18, // 0x81 VK_F18 +KEY_F19, // 0x82 VK_F19 +KEY_F20, // 0x83 VK_F20 +KEY_F21, // 0x84 VK_F21 +KEY_F22, // 0x85 VK_F22 +KEY_F23, // 0x86 VK_F23 +KEY_F24, // 0x87 VK_F24 +0, // 0x88 +0, // 0x89 +0, // 0x8A +0, // 0x8B +0, // 0x8C +0, // 0x8D +0, // 0x8E +0, // 0x8F + +KEY_NUMLOCK, // 0x90 VK_NUMLOCK +KEY_SCROLLLOCK, // 0x91 VK_OEM_SCROLL +0, // 0x92 +0, // 0x93 +0, // 0x94 +0, // 0x95 +0, // 0x96 +0, // 0x97 +0, // 0x98 +0, // 0x99 +0, // 0x9A +0, // 0x9B +0, // 0x9C +0, // 0x9D +0, // 0x9E +0, // 0x9F + +KEY_LSHIFT, // 0xA0 VK_LSHIFT +KEY_RSHIFT, // 0xA1 VK_RSHIFT +KEY_LCONTROL, // 0xA2 VK_LCONTROL +KEY_RCONTROL, // 0xA3 VK_RCONTROL +KEY_LALT, // 0xA4 VK_LMENU +KEY_RALT, // 0xA5 VK_RMENU +0, // 0xA6 +0, // 0xA7 +0, // 0xA8 +0, // 0xA9 +0, // 0xAA +0, // 0xAB +0, // 0xAC +0, // 0xAD +0, // 0xAE +0, // 0xAF +0, // 0xB0 +0, // 0xB1 +0, // 0xB2 +0, // 0xB3 +0, // 0xB4 +0, // 0xB5 +0, // 0xB6 +0, // 0xB7 +0, // 0xB8 +0, // 0xB9 +KEY_SEMICOLON, // 0xBA VK_OEM_1 +KEY_EQUALS, // 0xBB VK_OEM_PLUS +KEY_COMMA, // 0xBC VK_OEM_COMMA +KEY_MINUS, // 0xBD VK_OEM_MINUS +KEY_PERIOD, // 0xBE VK_OEM_PERIOD +KEY_SLASH, // 0xBF VK_OEM_2 +KEY_TILDE, // 0xC0 VK_OEM_3 +0, // 0xC1 +0, // 0xC2 +0, // 0xC3 +0, // 0xC4 +0, // 0xC5 +0, // 0xC6 +0, // 0xC7 +0, // 0xC8 +0, // 0xC9 +0, // 0xCA +0, // 0xCB +0, // 0xCC +0, // 0xCD +0, // 0xCE +0, // 0xCF +0, // 0xD0 +0, // 0xD1 +0, // 0xD2 +0, // 0xD3 +0, // 0xD4 +0, // 0xD5 +0, // 0xD6 +0, // 0xD7 +0, // 0xD8 +0, // 0xD9 +0, // 0xDA +KEY_LBRACKET, // 0xDB VK_OEM_4 +KEY_BACKSLASH, // 0xDC VK_OEM_5 +KEY_RBRACKET, // 0xDD VK_OEM_6 +KEY_APOSTROPHE, // 0xDE VK_OEM_7 +0, // 0xDF VK_OEM_8 +0, // 0xE0 +0, // 0xE1 VK_OEM_AX AX key on Japanese AX keyboard +KEY_OEM_102, // 0xE2 VK_OEM_102 +0, // 0xE3 +0, // 0xE4 + +0, // 0xE5 VK_PROCESSKEY + +0, // 0xE6 +0, // 0xE7 +0, // 0xE8 +0, // 0xE9 +0, // 0xEA +0, // 0xEB +0, // 0xEC +0, // 0xED +0, // 0xEE +0, // 0xEF + +0, // 0xF0 +0, // 0xF1 +0, // 0xF2 +0, // 0xF3 +0, // 0xF4 +0, // 0xF5 + +0, // 0xF6 VK_ATTN +0, // 0xF7 VK_CRSEL +0, // 0xF8 VK_EXSEL +0, // 0xF9 VK_EREOF +0, // 0xFA VK_PLAY +0, // 0xFB VK_ZOOM +0, // 0xFC VK_NONAME +0, // 0xFD VK_PA1 +0, // 0xFE VK_OEM_CLEAR +0 // 0xFF +}; + + +//------------------------------------------------------------------------------ +// +// This function translates a virtual key code to our corresponding internal +// key code using the preceding table. +// +//------------------------------------------------------------------------------ +U8 TranslateOSKeyCode(U8 vcode) +{ + return VcodeRemap[vcode]; +} + +U8 TranslateKeyCodeToOS(U8 keycode) +{ + for(S32 i = 0;i < sizeof(VcodeRemap) / sizeof(U8);++i) + { + if(VcodeRemap[i] == keycode) + return i; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Clipboard functions +const char* Platform::getClipboard() +{ + HGLOBAL hGlobal; + LPVOID pGlobal; + + //make sure we can access the clipboard + if (!IsClipboardFormatAvailable(CF_TEXT)) + return ""; + if (!OpenClipboard(NULL)) + return ""; + + hGlobal = GetClipboardData(CF_TEXT); + pGlobal = GlobalLock(hGlobal); + S32 cbLength = strlen((char *)pGlobal); + char *returnBuf = Con::getReturnBuffer(cbLength + 1); + strcpy(returnBuf, (char *)pGlobal); + returnBuf[cbLength] = '\0'; + GlobalUnlock(hGlobal); + CloseClipboard(); + + //note - this function never returns NULL + return returnBuf; +} + +//----------------------------------------------------------------------------- +bool Platform::setClipboard(const char *text) +{ + if (!text) + return false; + + //make sure we can access the clipboard + if (!OpenClipboard(NULL)) + return false; + + S32 cbLength = strlen(text); + + HGLOBAL hGlobal; + LPVOID pGlobal; + + hGlobal = GlobalAlloc(GHND, cbLength + 1); + pGlobal = GlobalLock (hGlobal); + + strcpy((char *)pGlobal, text); + + GlobalUnlock(hGlobal); + + EmptyClipboard(); + SetClipboardData(CF_TEXT, hGlobal); + CloseClipboard(); + + return true; +} + diff --git a/platformWin32/winMath.cpp b/platformWin32/winMath.cpp new file mode 100644 index 0000000..13ddce3 --- /dev/null +++ b/platformWin32/winMath.cpp @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "console/console.h" + +#include "math/mMath.h" + + +extern void mInstallLibrary_C(); +extern void mInstallLibrary_ASM(); +extern void mInstall_AMD_Math(); +extern void mInstall_Library_SSE(); + +//-------------------------------------- +ConsoleFunction( mathInit, void, 1, 10, "( ... )" + "Install the math library with specified extensions.\n\n" + "Possible parameters are:\n" + " - 'DETECT' Autodetect math lib settings.\n" + " - 'C' Enable the C math routines. C routines are always enabled.\n" + " - 'FPU' Enable floating point unit routines.\n" + " - 'MMX' Enable MMX math routines.\n" + " - '3DNOW' Enable 3dNow! math routines.\n" + " - 'SSE' Enable SSE math routines.\n") + + +{ + U32 properties = CPU_PROP_C; // C entensions are always used + + if (argc == 1) + { + Math::init(0); + return; + } + for (argc--, argv++; argc; argc--, argv++) + { + if (dStricmp(*argv, "DETECT") == 0) { + Math::init(0); + return; + } + if (dStricmp(*argv, "C") == 0) { + properties |= CPU_PROP_C; + continue; + } + if (dStricmp(*argv, "FPU") == 0) { + properties |= CPU_PROP_FPU; + continue; + } + if (dStricmp(*argv, "MMX") == 0) { + properties |= CPU_PROP_MMX; + continue; + } + if (dStricmp(*argv, "3DNOW") == 0) { + properties |= CPU_PROP_3DNOW; + continue; + } + if (dStricmp(*argv, "SSE") == 0) { + properties |= CPU_PROP_SSE; + continue; + } + Con::printf("Error: MathInit(): ignoring unknown math extension '%s'", *argv); + } + Math::init(properties); +} + + + +//------------------------------------------------------------------------------ +void Math::init(U32 properties) +{ + if (!properties) + // detect what's available + properties = Platform::SystemInfo.processor.properties; + else + // Make sure we're not asking for anything that's not supported + properties &= Platform::SystemInfo.processor.properties; + + Con::printf("Math Init:"); + Con::printf(" Installing Standard C extensions"); + mInstallLibrary_C(); + + Con::printf(" Installing Assembly extensions"); + mInstallLibrary_ASM(); + + if (properties & CPU_PROP_FPU) + { + Con::printf(" Installing FPU extensions"); + } + + if (properties & CPU_PROP_MMX) + { + Con::printf(" Installing MMX extensions"); + if (properties & CPU_PROP_3DNOW) + { + Con::printf(" Installing 3DNow extensions"); + mInstall_AMD_Math(); + } + } + + if (properties & CPU_PROP_SSE) + { + Con::printf(" Installing SSE extensions"); + mInstall_Library_SSE(); + } + + Con::printf(" "); +} + + diff --git a/platformWin32/winMath_ASM.cpp b/platformWin32/winMath_ASM.cpp new file mode 100644 index 0000000..83a1e8f --- /dev/null +++ b/platformWin32/winMath_ASM.cpp @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMath.h" + +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) +static S32 m_mulDivS32_ASM(S32 a, S32 b, S32 c) +{ // a * b / c + S32 r; + _asm + { + mov eax, a + imul b + idiv c + mov r, eax + } + return r; +} + + +static U32 m_mulDivU32_ASM(S32 a, S32 b, U32 c) +{ // a * b / c + S32 r; + _asm + { + mov eax, a + mov edx, 0 + mul b + div c + mov r, eax + } + return r; +} + +U32 Platform::getMathControlState() +{ + U16 cw; + _asm + { + fstcw cw + } + return cw; +} + +void Platform::setMathControlState(U32 state) +{ + U16 cw = state; + _asm + { + fldcw cw + } +} + +void Platform::setMathControlStateKnown() +{ + U16 cw = 0x27F; + _asm + { + fldcw cw + } +} + + +#endif + +//------------------------------------------------------------------------------ +void mInstallLibrary_ASM() +{ +#if defined(TORQUE_SUPPORTS_VC_INLINE_X86_ASM) + m_mulDivS32 = m_mulDivS32_ASM; + m_mulDivU32 = m_mulDivU32_ASM; +#endif +} + + diff --git a/platformWin32/winMemory.cpp b/platformWin32/winMemory.cpp new file mode 100644 index 0000000..d644d87 --- /dev/null +++ b/platformWin32/winMemory.cpp @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include + +void* dMemcpy(void *dst, const void *src, unsigned size) +{ + return memcpy(dst,src,size); +} + + +//-------------------------------------- +void* dMemmove(void *dst, const void *src, unsigned size) +{ + return memmove(dst,src,size); +} + +//-------------------------------------- +void* dMemset(void *dst, S32 c, unsigned size) +{ + return memset(dst,c,size); +} + +//-------------------------------------- +S32 dMemcmp(const void *ptr1, const void *ptr2, unsigned len) +{ + return memcmp(ptr1, ptr2, len); +} + +#if defined(TORQUE_COMPILER_MINGW) +#include +#endif + +//-------------------------------------- + +void* dRealMalloc(dsize_t s) +{ + return malloc(s); +} + +void dRealFree(void* p) +{ + free(p); +} + +void *dAligned_malloc(dsize_t in_size, dsize_t alignment) +{ + return _mm_malloc(in_size, alignment); +} + +void dAligned_free(void* p) +{ + return _mm_free(p); +} \ No newline at end of file diff --git a/platformWin32/winProcessControl.cpp b/platformWin32/winProcessControl.cpp new file mode 100644 index 0000000..ded4569 --- /dev/null +++ b/platformWin32/winProcessControl.cpp @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "core/strings/stringFunctions.h" + +void Platform::postQuitMessage(const U32 in_quitVal) +{ + if (!Platform::getWebDeployment()) + PostQuitMessage(in_quitVal); +} + +void Platform::debugBreak() +{ + DebugBreak(); +} + +void Platform::forceShutdown(S32 returnValue) +{ + // Don't do an ExitProcess here or you'll wreak havoc in a multithreaded + // environment. + + exit( returnValue ); +} + +void Platform::outputDebugString( const char *string, ... ) +{ + // Expand string. + + char buffer[ 2048 ]; + + va_list args; + va_start( args, string ); + + dVsprintf( buffer, sizeof( buffer ), string, args ); + va_end( args ); + + // Append a newline to buffer. This is better than calling OutputDebugStringA + // twice as in a multi-threaded environment, some other thread may output some + // stuff in between the two calls. + + U32 length = strlen( buffer ); + if( length == ( sizeof( buffer ) - 1 ) ) + length --; + + buffer[ length ] = '\n'; + buffer[ length + 1 ] = '\0'; + + OutputDebugStringA( buffer ); +} + diff --git a/platformWin32/winRedbook.cpp b/platformWin32/winRedbook.cpp new file mode 100644 index 0000000..a6e3caf --- /dev/null +++ b/platformWin32/winRedbook.cpp @@ -0,0 +1,480 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "platform/platformRedBook.h" +#include "core/strings/unicode.h" +#include "core/strings/stringFunctions.h" + +class Win32RedBookDevice : public RedBookDevice +{ + private: + typedef RedBookDevice Parent; + + U32 mDeviceId; + + void setLastError(const char *); + void setLastError(U32); + + MIXERCONTROLDETAILS mMixerVolumeDetails; + MIXERCONTROLDETAILS_UNSIGNED mMixerVolumeValue; + + union { + HMIXEROBJ mVolumeDeviceId; + UINT mAuxVolumeDeviceId; + }; + + U32 mOriginalVolume; + bool mVolumeInitialized; + + bool mUsingMixer; + + void openVolume(); + void closeVolume(); + + public: + Win32RedBookDevice(); + ~Win32RedBookDevice(); + + U32 getDeviceId(); + + bool open(); + bool close(); + bool play(U32); + bool stop(); + bool getTrackCount(U32 *); + bool getVolume(F32 *); + bool setVolume(F32); +}; + +//------------------------------------------------------------------------------ +// Win32 specific +//------------------------------------------------------------------------------ +void installRedBookDevices() +{ + U32 bufSize = ::GetLogicalDriveStrings(0,0); + + char * buf = new char[bufSize]; + + ::GetLogicalDriveStringsA(bufSize, buf); + + char * str = buf; + while(*str) + { + if(::GetDriveTypeA(str) == DRIVE_CDROM) + { + Win32RedBookDevice * device = new Win32RedBookDevice; + device->mDeviceName = new char[dStrlen(str) + 1]; + dStrcpy(device->mDeviceName, str); + + RedBook::installDevice(device); + } + str += dStrlen(str) + 1; + } + + delete [] buf; +} + +void handleRedBookCallback(U32 code, U32 deviceId) +{ + if(code != MCI_NOTIFY_SUCCESSFUL) + return; + + Win32RedBookDevice * device = dynamic_cast(RedBook::getCurrentDevice()); + if(!device) + return; + + if(device->getDeviceId() != deviceId) + return; + + // only installed callback on play (no callback if play is aborted) + RedBook::handleCallback(RedBook::PlayFinished); +} + +//------------------------------------------------------------------------------ +// Class: Win32RedBookDevice +//------------------------------------------------------------------------------ +Win32RedBookDevice::Win32RedBookDevice() +{ + mVolumeInitialized = false; +} + +Win32RedBookDevice::~Win32RedBookDevice() +{ + close(); +} + +U32 Win32RedBookDevice::getDeviceId() +{ + return(mDeviceId); +} + +bool Win32RedBookDevice::open() +{ + if(mAcquired) + { + setLastError("Device is already open."); + return(false); + } + + U32 error; + + // open the device + MCI_OPEN_PARMS openParms; +#ifdef UNICODE + openParms.lpstrDeviceType = (LPCWSTR)MCI_DEVTYPE_CD_AUDIO; + + UTF16 buf[512]; + convertUTF8toUTF16((UTF8 *)mDeviceName, buf, sizeof(buf)); + openParms.lpstrElementName = buf; +#else + openParms.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO; + openParms.lpstrElementName = mDeviceName; +#endif + + error = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD_PTR)(LPMCI_OPEN_PARMS)&openParms); + if(error) + { + // attempt to open as a shared device + error = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID|MCI_OPEN_SHAREABLE, (DWORD_PTR)(LPMCI_OPEN_PARMS)&openParms); + if(error) + { + setLastError(error); + return(false); + } + } + + // set time mode to milliseconds + MCI_SET_PARMS setParms; + setParms.dwTimeFormat = MCI_FORMAT_MILLISECONDS; + + error = mciSendCommand(openParms.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPMCI_SET_PARMS)&setParms); + if(error) + { + setLastError(error); + return(false); + } + + // + mDeviceId = openParms.wDeviceID; + mAcquired = true; + + openVolume(); + setLastError(""); + return(true); +} + +bool Win32RedBookDevice::close() +{ + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + stop(); + + U32 error; + + MCI_GENERIC_PARMS closeParms; + error = mciSendCommand(mDeviceId, MCI_CLOSE, 0, (DWORD_PTR)(LPMCI_GENERIC_PARMS)&closeParms); + if(error) + { + setLastError(error); + return(false); + } + + mAcquired = false; + closeVolume(); + setLastError(""); + return(true); +} + +bool Win32RedBookDevice::play(U32 track) +{ + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + U32 numTracks; + if(!getTrackCount(&numTracks)) + return(false); + + if(track >= numTracks) + { + setLastError("Track index is out of range"); + return(false); + } + + MCI_STATUS_PARMS statusParms; + + // get track start time + statusParms.dwItem = MCI_STATUS_POSITION; + statusParms.dwTrack = track + 1; + + U32 error; + error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK|MCI_WAIT, + (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms); + + if(error) + { + setLastError(error); + return(false); + } + + MCI_PLAY_PARMS playParms; + playParms.dwFrom = statusParms.dwReturn; + + // get track end time + statusParms.dwItem = MCI_STATUS_LENGTH; + error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK|MCI_WAIT, + (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms); + + if(error) + { + setLastError(error); + return(false); + } + + playParms.dwTo = playParms.dwFrom + statusParms.dwReturn; + + // play the track + playParms.dwCallback = MAKELONG(getWin32WindowHandle(), 0); + error = mciSendCommand(mDeviceId, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY, + (DWORD_PTR)(LPMCI_PLAY_PARMS)&playParms); + + if(error) + { + setLastError(error); + return(false); + } + + setLastError(""); + return(true); +} + +bool Win32RedBookDevice::stop() +{ + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + MCI_GENERIC_PARMS genParms; + + U32 error = mciSendCommand(mDeviceId, MCI_STOP, 0, (DWORD_PTR)(LPMCI_GENERIC_PARMS)&genParms); + if(error) + { + setLastError(error); + return(false); + } + + setLastError(""); + return(true); +} + +bool Win32RedBookDevice::getTrackCount(U32 * numTracks) +{ + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + MCI_STATUS_PARMS statusParms; + + statusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; + U32 error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms); + if(error) + { + setLastError(error); + return(false); + } + + *numTracks = statusParms.dwReturn; + return(true); +} + +bool Win32RedBookDevice::getVolume(F32 * volume) +{ + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + if(!mVolumeInitialized) + { + setLastError("Volume failed to initialize"); + return(false); + } + + U32 vol = 0; + if(mUsingMixer) + { + mixerGetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_GETCONTROLDETAILSF_VALUE); + vol = mMixerVolumeValue.dwValue; + } + else + auxGetVolume(mAuxVolumeDeviceId, (unsigned long *)&vol); + + vol &= 0xffff; + *volume = F32(vol) / 65535.f; + + setLastError(""); + return(true); +} + +bool Win32RedBookDevice::setVolume(F32 volume) +{ + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + if(!mVolumeInitialized) + { + setLastError("Volume failed to initialize"); + return(false); + } + + // move into a U32 - left/right U16 volumes + U32 vol = U32(volume * 65536.f); + if(vol > 0xffff) + vol = 0xffff; + + if(mUsingMixer) + { + mMixerVolumeValue.dwValue = vol; + mixerSetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE); + } + else + { + vol |= vol << 16; + auxSetVolume(mAuxVolumeDeviceId, vol); + } + + setLastError(""); + return(true); +} + +//------------------------------------------------------------------------------ + +void Win32RedBookDevice::openVolume() +{ + setLastError(""); + + // first attempt to get the volume control through the mixer API + S32 i; + for(i = mixerGetNumDevs() - 1; i >= 0; i--) + { + // open the mixer + if(mixerOpen((HMIXER*)&mVolumeDeviceId, i, 0, 0, 0) == MMSYSERR_NOERROR) + { + MIXERLINE lineInfo; + memset(&lineInfo, 0, sizeof(lineInfo)); + lineInfo.cbStruct = sizeof(lineInfo); + lineInfo.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC; + + // get the cdaudio line + if(mixerGetLineInfo(mVolumeDeviceId, &lineInfo, MIXER_GETLINEINFOF_COMPONENTTYPE) == MMSYSERR_NOERROR) + { + MIXERLINECONTROLS lineControls; + MIXERCONTROL volumeControl; + + memset(&lineControls, 0, sizeof(lineControls)); + lineControls.cbStruct = sizeof(lineControls); + lineControls.dwLineID = lineInfo.dwLineID; + lineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; + lineControls.cControls = 1; + lineControls.cbmxctrl = sizeof(volumeControl); + lineControls.pamxctrl = &volumeControl; + + memset(&volumeControl, 0, sizeof(volumeControl)); + volumeControl.cbStruct = sizeof(volumeControl); + + // get the volume control + if(mixerGetLineControls(mVolumeDeviceId, &lineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE) == MMSYSERR_NOERROR) + { + memset(&mMixerVolumeDetails, 0, sizeof(mMixerVolumeDetails)); + mMixerVolumeDetails.cbStruct = sizeof(mMixerVolumeDetails); + mMixerVolumeDetails.dwControlID = volumeControl.dwControlID; + mMixerVolumeDetails.cChannels = 1; + mMixerVolumeDetails.cbDetails = sizeof(mMixerVolumeValue); + mMixerVolumeDetails.paDetails = &mMixerVolumeValue; + + memset(&mMixerVolumeValue, 0, sizeof(mMixerVolumeValue)); + + // query the current value + if(mixerGetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_GETCONTROLDETAILSF_VALUE) == MMSYSERR_NOERROR) + { + mUsingMixer = true; + mVolumeInitialized = true; + mOriginalVolume = mMixerVolumeValue.dwValue; + return; + } + } + } + } + + mixerClose((HMIXER)mVolumeDeviceId); + } + + // try aux + for(i = auxGetNumDevs() - 1; i >= 0; i--) + { + AUXCAPS caps; + auxGetDevCaps(i, &caps, sizeof(AUXCAPS)); + if((caps.wTechnology == AUXCAPS_CDAUDIO) && (caps.dwSupport & AUXCAPS_VOLUME)) + { + mAuxVolumeDeviceId = i; + mVolumeInitialized = true; + mUsingMixer = false; + auxGetVolume(i, (unsigned long *)&mOriginalVolume); + return; + } + } + + setLastError("Volume failed to initialize"); +} + +void Win32RedBookDevice::closeVolume() +{ + setLastError(""); + if(!mVolumeInitialized) + return; + + if(mUsingMixer) + { + mMixerVolumeValue.dwValue = mOriginalVolume; + mixerSetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE); + mixerClose((HMIXER)mVolumeDeviceId); + } + else + auxSetVolume(mAuxVolumeDeviceId, mOriginalVolume); + + mVolumeInitialized = false; +} + +//------------------------------------------------------------------------------ + +void Win32RedBookDevice::setLastError(const char * error) +{ + RedBook::setLastError(error); +} + +void Win32RedBookDevice::setLastError(U32 errorId) +{ + char buffer[256]; + if(!mciGetErrorStringA(errorId, buffer, sizeof(buffer) - 1)) + setLastError("Failed to get MCI error string!"); + else + setLastError(buffer); +} + diff --git a/platformWin32/winSemaphore.cpp b/platformWin32/winSemaphore.cpp new file mode 100644 index 0000000..b62e7f3 --- /dev/null +++ b/platformWin32/winSemaphore.cpp @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "platform/threads/semaphore.h" + +class PlatformSemaphore +{ +public: + HANDLE *semaphore; + + PlatformSemaphore(S32 initialCount) + { + semaphore = new HANDLE; + *semaphore = CreateSemaphore(0, initialCount, S32_MAX, 0); + } + + ~PlatformSemaphore() + { + CloseHandle(*(HANDLE*)(semaphore)); + delete semaphore; + semaphore = NULL; + } +}; + +Semaphore::Semaphore(S32 initialCount) +{ + mData = new PlatformSemaphore(initialCount); +} + +Semaphore::~Semaphore() +{ + AssertFatal(mData && mData->semaphore, "Semaphore::destroySemaphore: invalid semaphore"); + delete mData; +} + +bool Semaphore::acquire(bool block) +{ + AssertFatal(mData && mData->semaphore, "Semaphore::acquireSemaphore: invalid semaphore"); + if(block) + { + WaitForSingleObject(*(HANDLE*)(mData->semaphore), INFINITE); + return(true); + } + else + { + DWORD result = WaitForSingleObject(*(HANDLE*)(mData->semaphore), 0); + return(result == WAIT_OBJECT_0); + } +} + +void Semaphore::release() +{ + AssertFatal(mData && mData->semaphore, "Semaphore::releaseSemaphore: invalid semaphore"); + ReleaseSemaphore(*(HANDLE*)(mData->semaphore), 1, 0); +} diff --git a/platformWin32/winTLS.cpp b/platformWin32/winTLS.cpp new file mode 100644 index 0000000..f721465 --- /dev/null +++ b/platformWin32/winTLS.cpp @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformTLS.h" +#include "platformWin32/platformWin32.h" +#include "core/util/safeDelete.h" + +#define TORQUE_ALLOC_STORAGE(member, cls, data) \ + AssertFatal(sizeof(cls) <= sizeof(data), avar("Error, storage for %s must be %d bytes.", #cls, sizeof(cls))); \ + member = (cls *) data; \ + constructInPlace(member) + +//----------------------------------------------------------------------------- + +struct PlatformThreadStorage +{ + DWORD mTlsIndex; +}; + +//----------------------------------------------------------------------------- + +ThreadStorage::ThreadStorage() +{ + TORQUE_ALLOC_STORAGE(mThreadStorage, PlatformThreadStorage, mStorage); + mThreadStorage->mTlsIndex = TlsAlloc(); +} + +ThreadStorage::~ThreadStorage() +{ + TlsFree(mThreadStorage->mTlsIndex); + destructInPlace(mThreadStorage); +} + +void *ThreadStorage::get() +{ + return TlsGetValue(mThreadStorage->mTlsIndex); +} + +void ThreadStorage::set(void *value) +{ + TlsSetValue(mThreadStorage->mTlsIndex, value); +} + +/* POSIX IMPLEMENTATION LOOKS LIKE THIS: + +class PlatformThreadStorage +{ +pthread_key_t mThreadKey; +}; + +ThreadStorage::ThreadStorage() +{ +TORQUE_ALLOC_STORAGE(mThreadStorage, PlatformThreadStorage, mStorage); +pthread_key_create(&mThreadStorage->mThreadKey, NULL); +} + +ThreadStorage::~ThreadStorage() +{ +pthread_key_delete(mThreadStorage->mThreadKey); +} + +void *ThreadStorage::get() +{ +return pthread_getspecific(mThreadStorage->mThreadKey); +} + +void ThreadStorage::set(void *value) +{ +pthread_setspecific(mThreadStorage->mThreadKey, value); +} +*/ \ No newline at end of file diff --git a/platformWin32/winTime.cpp b/platformWin32/winTime.cpp new file mode 100644 index 0000000..db93eba --- /dev/null +++ b/platformWin32/winTime.cpp @@ -0,0 +1,151 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platformWin32/platformWin32.h" + +#include "time.h" + +void Platform::sleep(U32 ms) +{ + Sleep(ms); +} + +//-------------------------------------- +void Platform::getLocalTime(LocalTime <) +{ + struct tm *systime; + time_t long_time; + + time( &long_time ); // Get time as long integer. + systime = localtime( &long_time ); // Convert to local time. + + lt.sec = systime->tm_sec; + lt.min = systime->tm_min; + lt.hour = systime->tm_hour; + lt.month = systime->tm_mon; + lt.monthday = systime->tm_mday; + lt.weekday = systime->tm_wday; + lt.year = systime->tm_year; + lt.yearday = systime->tm_yday; + lt.isdst = systime->tm_isdst; +} + +String Platform::localTimeToString( const LocalTime < ) +{ + // Converting a LocalTime to SYSTEMTIME + // requires a few annoying adjustments. + SYSTEMTIME st; + st.wMilliseconds = 0; + st.wSecond = lt.sec; + st.wMinute = lt.min; + st.wHour = lt.hour; + st.wDay = lt.monthday; + st.wDayOfWeek = lt.weekday; + st.wMonth = lt.month + 1; + st.wYear = lt.year + 1900; + + TCHAR buffer[1024] = {0}; + + int result = 0; + + String outStr; + + // Note: The 'Ex' version of GetDateFormat and GetTimeFormat are preferred + // and have better support for supplemental locales but are not supported + // for version of windows prior to Vista. + // + // Would be nice if Torque was more aware of the OS version and + // take full advantage of it. + + result = GetDateFormat( LOCALE_USER_DEFAULT, + DATE_SHORTDATE, + &st, + NULL, + (LPTSTR)buffer, + 1024 ); + + // Also would be nice to have a standard system for torque to + // retrieve and display windows level errors using GetLastError and + // FormatMessage... + AssertWarn( result != 0, "Platform::getLocalTime" ); + + outStr += buffer; + outStr += "\t"; + + result = GetTimeFormat( LOCALE_USER_DEFAULT, + 0, + &st, + NULL, + (LPTSTR)buffer, + 1024 ); + + AssertWarn( result != 0, "Platform::localTimeToString, error occured!" ); + + outStr += buffer; + + return outStr; +} + +U32 Platform::getTime() +{ + time_t long_time; + time( &long_time ); + return long_time; +} + +void Platform::fileToLocalTime(const FileTime & ft, LocalTime * lt) +{ + if(!lt) + return; + + dMemset(lt, 0, sizeof(LocalTime)); + + FILETIME winFileTime; + winFileTime.dwLowDateTime = ft.v1; + winFileTime.dwHighDateTime = ft.v2; + + SYSTEMTIME winSystemTime; + + // convert the filetime to local time + FILETIME convertedFileTime; + if(::FileTimeToLocalFileTime(&winFileTime, &convertedFileTime)) + { + // get the time into system time struct + if(::FileTimeToSystemTime((const FILETIME *)&convertedFileTime, &winSystemTime)) + { + SYSTEMTIME * time = &winSystemTime; + + // fill it in... + lt->sec = time->wSecond; + lt->min = time->wMinute; + lt->hour = time->wHour; + lt->month = time->wMonth; + lt->monthday = time->wDay; + lt->weekday = time->wDayOfWeek; + lt->year = (time->wYear < 1900) ? 1900 : (time->wYear - 1900); + + // not calculated + lt->yearday = 0; + lt->isdst = false; + } + } +} + +U32 Platform::getRealMilliseconds() +{ + return GetTickCount(); +} + +U32 Platform::getVirtualMilliseconds() +{ + return winState.currentTime; +} + +void Platform::advanceTime(U32 delta) +{ + winState.currentTime += delta; +} + diff --git a/platformWin32/winTimer.cpp b/platformWin32/winTimer.cpp new file mode 100644 index 0000000..eff3966 --- /dev/null +++ b/platformWin32/winTimer.cpp @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// Grab the win32 headers so we can access QPC +#define WIN32_LEAN_AND_MEAN +#include + +#include "platform/platformTimer.h" +#include "math/mMath.h" + +class Win32Timer : public PlatformTimer +{ +private: + U32 mTickCountCurrent; + U32 mTickCountNext; + S64 mPerfCountCurrent; + S64 mPerfCountNext; + S64 mFrequency; + F64 mPerfCountRemainderCurrent; + F64 mPerfCountRemainderNext; + bool mUsingPerfCounter; +public: + + Win32Timer() + { + mPerfCountRemainderCurrent = 0.0f; + + // Attempt to use QPC for high res timing, otherwise fallback to GTC. + mUsingPerfCounter = QueryPerformanceFrequency((LARGE_INTEGER *) &mFrequency); + if(mUsingPerfCounter) + mUsingPerfCounter = QueryPerformanceCounter((LARGE_INTEGER *) &mPerfCountCurrent); + if(!mUsingPerfCounter) + mTickCountCurrent = GetTickCount(); + } + + const S32 getElapsedMs() + { + if(mUsingPerfCounter) + { + // Use QPC, update remainders so we don't leak time, and return the elapsed time. + QueryPerformanceCounter( (LARGE_INTEGER *) &mPerfCountNext); + F64 elapsedF64 = (1000.0 * F64(mPerfCountNext - mPerfCountCurrent) / F64(mFrequency)); + elapsedF64 += mPerfCountRemainderCurrent; + U32 elapsed = (U32)mFloor(elapsedF64); + mPerfCountRemainderNext = elapsedF64 - F64(elapsed); + + return elapsed; + } + else + { + // Do something naive with GTC. + mTickCountNext = GetTickCount(); + return mTickCountNext - mTickCountCurrent; + } + } + + void reset() + { + // Do some simple copying to reset the timer to 0. + mTickCountCurrent = mTickCountNext; + mPerfCountCurrent = mPerfCountNext; + mPerfCountRemainderCurrent = mPerfCountRemainderNext; + } +}; + +PlatformTimer *PlatformTimer::create() +{ + return new Win32Timer(); +} diff --git a/platformWin32/winUser.cpp b/platformWin32/winUser.cpp new file mode 100644 index 0000000..b4715c5 --- /dev/null +++ b/platformWin32/winUser.cpp @@ -0,0 +1,208 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "core/stringTable.h" +#include "core/strings/unicode.h" + +typedef long SHANDLE_PTR; +#include +#include +#include + +#define CSIDL_PROFILE 0x0028 + +const char *Platform::getUserDataDirectory() +{ + TCHAR szBuffer[ MAX_PATH + 1 ]; + + if(! SHGetSpecialFolderPath( NULL, szBuffer, CSIDL_APPDATA, true ) ) + return ""; + + TCHAR *ptr = szBuffer; + while(*ptr) + { + if(*ptr == '\\') + *ptr = '/'; + ++ptr; + } + +#ifdef UNICODE + char path[ MAX_PATH * 3 + 1 ]; + convertUTF16toUTF8( szBuffer, path, sizeof( path ) ); +#else + char* path = szBuffer; +#endif + + + return StringTable->insert( path ); +} + +const char *Platform::getUserHomeDirectory() +{ + TCHAR szBuffer[ MAX_PATH + 1 ]; + if(! SHGetSpecialFolderPath( NULL, szBuffer, CSIDL_PERSONAL, false ) ) + if(! SHGetSpecialFolderPath( NULL, szBuffer, CSIDL_COMMON_DOCUMENTS, false ) ) + return ""; + + TCHAR *ptr = szBuffer; + while(*ptr) + { + if(*ptr == '\\') + *ptr = '/'; + ++ptr; + } + +#ifdef UNICODE + char path[ MAX_PATH * 3 + 1 ]; + convertUTF16toUTF8( szBuffer, path, sizeof( path ) ); +#else + char* path = szBuffer; +#endif + + return StringTable->insert( path ); +} + + +bool Platform::getUserIsAdministrator() +{ + BOOL fReturn = FALSE; + DWORD dwStatus; + DWORD dwAccessMask; + DWORD dwAccessDesired; + DWORD dwACLSize; + DWORD dwStructureSize = sizeof(PRIVILEGE_SET); + PACL pACL = NULL; + PSID psidAdmin = NULL; + + HANDLE hToken = NULL; + HANDLE hImpersonationToken = NULL; + + PRIVILEGE_SET ps; + GENERIC_MAPPING GenericMapping; + + PSECURITY_DESCRIPTOR psdAdmin = NULL; + SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY; + + + /* + Determine if the current thread is running as a user that is a member of + the local admins group. To do this, create a security descriptor that + has a DACL which has an ACE that allows only local aministrators access. + Then, call AccessCheck with the current thread's token and the security + descriptor. It will say whether the user could access an object if it + had that security descriptor. Note: you do not need to actually create + the object. Just checking access against the security descriptor alone + will be sufficient. + */ + const DWORD ACCESS_READ = 1; + const DWORD ACCESS_WRITE = 2; + + + __try + { + + /* + AccessCheck() requires an impersonation token. We first get a primary + token and then create a duplicate impersonation token. The + impersonation token is not actually assigned to the thread, but is + used in the call to AccessCheck. Thus, this function itself never + impersonates, but does use the identity of the thread. If the thread + was impersonating already, this function uses that impersonation context. + */ + if (!OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE|TOKEN_QUERY, TRUE, &hToken)) + { + if (GetLastError() != ERROR_NO_TOKEN) + __leave; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE|TOKEN_QUERY, &hToken)) + __leave; + } + + if (!DuplicateToken (hToken, SecurityImpersonation, &hImpersonationToken)) + __leave; + + + /* + Create the binary representation of the well-known SID that + represents the local administrators group. Then create the security + descriptor and DACL with an ACE that allows only local admins access. + After that, perform the access check. This will determine whether + the current user is a local admin. + */ + if (!AllocateAndInitializeSid(&SystemSidAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdmin)) + __leave; + + psdAdmin = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (psdAdmin == NULL) + __leave; + + if (!InitializeSecurityDescriptor(psdAdmin, SECURITY_DESCRIPTOR_REVISION)) + __leave; + + // Compute size needed for the ACL. + dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psidAdmin) - sizeof(DWORD); + + pACL = (PACL)LocalAlloc(LPTR, dwACLSize); + if (pACL == NULL) + __leave; + + if (!InitializeAcl(pACL, dwACLSize, ACL_REVISION2)) + __leave; + + dwAccessMask= ACCESS_READ | ACCESS_WRITE; + + if (!AddAccessAllowedAce(pACL, ACL_REVISION2, dwAccessMask, psidAdmin)) + __leave; + + if (!SetSecurityDescriptorDacl(psdAdmin, TRUE, pACL, FALSE)) + __leave; + + /* + AccessCheck validates a security descriptor somewhat; set the group + and owner so that enough of the security descriptor is filled out to + make AccessCheck happy. + */ + SetSecurityDescriptorGroup(psdAdmin, psidAdmin, FALSE); + SetSecurityDescriptorOwner(psdAdmin, psidAdmin, FALSE); + + if (!IsValidSecurityDescriptor(psdAdmin)) + __leave; + + dwAccessDesired = ACCESS_READ; + + /* + Initialize GenericMapping structure even though you + do not use generic rights. + */ + GenericMapping.GenericRead = ACCESS_READ; + GenericMapping.GenericWrite = ACCESS_WRITE; + GenericMapping.GenericExecute = 0; + GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE; + + if (!AccessCheck(psdAdmin, hImpersonationToken, dwAccessDesired, + &GenericMapping, &ps, &dwStructureSize, &dwStatus, + &fReturn)) + { + fReturn = FALSE; + __leave; + } + } + __finally + { + + // Clean up. + if (pACL) LocalFree(pACL); + if (psdAdmin) LocalFree(psdAdmin); + if (psidAdmin) FreeSid(psidAdmin); + if (hImpersonationToken) CloseHandle (hImpersonationToken); + if (hToken) CloseHandle (hToken); + } + + return fReturn; + +} \ No newline at end of file diff --git a/platformWin32/winVFS.cpp b/platformWin32/winVFS.cpp new file mode 100644 index 0000000..0107a6f --- /dev/null +++ b/platformWin32/winVFS.cpp @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformWin32/platformWin32.h" +#include "platform/platformVFS.h" +#include "console/console.h" + +#include "core/stream/memstream.h" +#include "core/util/zip/zipArchive.h" + +#include "core/util/safeDelete.h" + +#include "VFSRes.h" + +//----------------------------------------------------------------------------- + +struct Win32VFSState +{ + S32 mRefCount; + + HGLOBAL mResData; + + MemStream *mZipStream; + Zip::ZipArchive *mZip; + + Win32VFSState() : mResData(NULL), mZip(NULL), mRefCount(0), mZipStream(NULL) + { + } +}; + +static Win32VFSState gVFSState; + +//----------------------------------------------------------------------------- + +Zip::ZipArchive *openEmbeddedVFSArchive() +{ + if(gVFSState.mZip) + { + ++gVFSState.mRefCount; + return gVFSState.mZip; + } + + HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(IDR_ZIPFILE), dT("RT_RCDATA")); + + if(hRsrc == NULL) + return NULL; + + if((gVFSState.mResData = LoadResource(NULL, hRsrc)) == NULL) + return NULL; + + void * mem = LockResource(gVFSState.mResData); + if(mem != NULL) + { + U32 size = SizeofResource(NULL, hRsrc); + gVFSState.mZipStream = new MemStream(size, mem, true, false); + gVFSState.mZip = new Zip::ZipArchive; + + if(gVFSState.mZip->openArchive(gVFSState.mZipStream)) + { + ++gVFSState.mRefCount; + return gVFSState.mZip; + } + + SAFE_DELETE(gVFSState.mZip); + SAFE_DELETE(gVFSState.mZipStream); + } + + FreeResource(gVFSState.mResData); + gVFSState.mResData = NULL; + + return NULL; +} + +void closeEmbeddedVFSArchive() +{ + if(gVFSState.mRefCount == 0) + return; + + --gVFSState.mRefCount; + + if(gVFSState.mRefCount < 1) + { + SAFE_DELETE(gVFSState.mZip); + SAFE_DELETE(gVFSState.mZipStream); + + if(gVFSState.mResData) + { + FreeResource(gVFSState.mResData); + gVFSState.mResData = NULL; + } + } +} diff --git a/platformWin32/winVolume.cpp b/platformWin32/winVolume.cpp new file mode 100644 index 0000000..b5b5cd3 --- /dev/null +++ b/platformWin32/winVolume.cpp @@ -0,0 +1,737 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#include + +#include "core/crc.h" +#include "core/frameAllocator.h" +#include "core/util/str.h" +#include "core/strings/stringFunctions.h" +#include "core/strings/unicode.h" + +#include "platform/platformVolume.h" + +#include "platformWin32/winVolume.h" + +#include "console/console.h" + + +#ifndef NGROUPS_UMAX + #define NGROUPS_UMAX 32 +#endif + +namespace Torque +{ +namespace Win32 +{ + + // If the file is a Directory, Offline, System or Temporary then FALSE +#define S_ISREG(Flags) \ + !((Flags) & \ + (FILE_ATTRIBUTE_DIRECTORY | \ + FILE_ATTRIBUTE_OFFLINE | \ + FILE_ATTRIBUTE_SYSTEM | \ + FILE_ATTRIBUTE_TEMPORARY)) + +#define S_ISDIR(Flags) \ + ((Flags) & FILE_ATTRIBUTE_DIRECTORY) + +//----------------------------------------------------------------------------- + + class Win32FileSystemChangeNotifier : public FileSystemChangeNotifier + { + public: + Win32FileSystemChangeNotifier( FileSystem *fs ) + : FileSystemChangeNotifier( fs ) + { + VECTOR_SET_ASSOCIATION( mHandleList ); + VECTOR_SET_ASSOCIATION( mDirs ); + } + + // for use in the thread itself + U32 getNumHandles() const { return mHandleList.size(); } + HANDLE *getHANDLES() { return mHandleList.address(); } + + private: + virtual void internalProcessOnce(); + + virtual bool internalAddNotification( const Path &dir ); + virtual bool internalRemoveNotification( const Path &dir ); + + Vector mDirs; + Vector mHandleList; + }; + +//----------------------------------------------------------------------------- + +static String _BuildFileName(const String& prefix,const Path& path) +{ + // Need to join the path (minus the root) with our + // internal path name. + String file = prefix; + file = Path::Join(file, '/', path.getPath()); + file = Path::Join(file, '/', path.getFileName()); + file = Path::Join(file, '.', path.getExtension()); + return file; +} + +/* +static bool _IsFile(const String& file) +{ + // Get file info + WIN32_FIND_DATA info; + HANDLE handle = ::FindFirstFile(PathToOS(file).utf16(), &info); + ::FindClose(handle); + if (handle == INVALID_HANDLE_VALUE) + return false; + + return S_ISREG(info.dwFileAttributes); +} +*/ + +static bool _IsDirectory(const String& file) +{ + // Get file info + WIN32_FIND_DATAW info; + HANDLE handle = ::FindFirstFileW(PathToOS(file).utf16(), &info); + ::FindClose(handle); + if (handle == INVALID_HANDLE_VALUE) + return false; + + return S_ISDIR(info.dwFileAttributes); +} + + +//----------------------------------------------------------------------------- + +static void _CopyStatAttributes(const WIN32_FIND_DATAW& info, FileNode::Attributes* attr) +{ + // Fill in the return struct. + attr->flags = 0; + if (S_ISDIR(info.dwFileAttributes)) + attr->flags |= FileNode::Directory; + if (S_ISREG(info.dwFileAttributes)) + attr->flags |= FileNode::File; + + if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + attr->flags |= FileNode::ReadOnly; + + attr->size = info.nFileSizeLow; + attr->mtime = Win32FileTimeToTime( + info.ftLastWriteTime.dwLowDateTime, + info.ftLastWriteTime.dwHighDateTime); + + attr->atime = Win32FileTimeToTime( + info.ftLastAccessTime.dwLowDateTime, + info.ftLastAccessTime.dwHighDateTime); +} + + +//----------------------------------------------------------------------------- + +bool Win32FileSystemChangeNotifier::internalAddNotification( const Path &dir ) +{ + for ( U32 i = 0; i < mDirs.size(); ++i ) + { + if ( mDirs[i] == dir ) + return false; + } + + Path fullFSPath = mFS->mapTo( dir ); + String osPath = PathToOS( fullFSPath ); + +// Con::printf( "[Win32FileSystemChangeNotifier::internalAddNotification] : [%s]", osPath.c_str() ); + + HANDLE changeHandle = ::FindFirstChangeNotificationW( + osPath.utf16(), // directory to watch + FALSE, // do not watch subtree + FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES); // watch file write changes + + if (changeHandle == INVALID_HANDLE_VALUE || changeHandle == NULL) + { + Con::errorf("[Win32FileSystemChangeNotifier::internalAddNotification] : failed on [%s] [%d]", osPath.c_str(), GetLastError()); + + return false; + } + + mDirs.push_back( dir ); + mHandleList.push_back( changeHandle ); + + return true; +} + +bool Win32FileSystemChangeNotifier::internalRemoveNotification( const Path &dir ) +{ + for ( U32 i = 0; i < mDirs.size(); ++i ) + { + if ( mDirs[i] == dir ) + { +// Con::printf( "[Win32FileSystemChangeNotifier::internalRemoveNotification] : [%s]", dir.getFullPath().c_str() ); + + mDirs.erase( i ); + mHandleList.erase( i ); + + return true; + } + } + + return false; +} + +void Win32FileSystemChangeNotifier::internalProcessOnce() +{ + U32 numHandles = mHandleList.size(); + if ( numHandles == 0 ) + return; + + DWORD dwWaitStatus = WaitForMultipleObjects(numHandles, mHandleList.address(), FALSE, 0); + if ( dwWaitStatus == WAIT_FAILED || dwWaitStatus == WAIT_TIMEOUT ) + return; + + if ( dwWaitStatus >= WAIT_OBJECT_0 && dwWaitStatus <= (WAIT_OBJECT_0 + numHandles - 1)) + { + U32 index = dwWaitStatus; + + // reset our notification + // NOTE: we do this before letting the volume system check mod times so we don't miss any. + // It is going to loop over the files and check their mod time vs. the saved time. + // This may result in extra calls to internalNotifyDirChanged(), but it will simpley check mod times again. + ::FindNextChangeNotification( mHandleList[index] ); + + internalNotifyDirChanged( mDirs[index] ); + } +} + +//----------------------------------------------------------------------------- + +Win32FileSystem::Win32FileSystem(String volume) +{ + mVolume = volume; + mChangeNotifier = new Win32FileSystemChangeNotifier( this ); +} + +Win32FileSystem::~Win32FileSystem() +{ +} + +FileNodeRef Win32FileSystem::resolve(const Path& path) +{ + String file = _BuildFileName(mVolume,path); + + WIN32_FIND_DATAW info; + HANDLE handle = ::FindFirstFileW(PathToOS(file).utf16(), &info); + ::FindClose(handle); + if (handle != INVALID_HANDLE_VALUE) + { + if (S_ISREG(info.dwFileAttributes)) + return new Win32File(path,file); + if (S_ISDIR(info.dwFileAttributes)) + return new Win32Directory(path,file); + } + return 0; +} + +FileNodeRef Win32FileSystem::create(const Path& path, FileNode::Mode mode) +{ + // The file will be created on disk when it's opened. + if (mode & FileNode::File) + return new Win32File(path,_BuildFileName(mVolume,path)); + + // Create with default permissions. + if (mode & FileNode::Directory) + { + String file = PathToOS(_BuildFileName(mVolume,path)); + + if (::CreateDirectoryW(file.utf16(), 0)) + return new Win32Directory(path, file); + } + return 0; +} + +bool Win32FileSystem::remove(const Path& path) +{ + // Should probably check for outstanding files or directory objects. + String file = PathToOS(_BuildFileName(mVolume,path)); + + WIN32_FIND_DATAW info; + HANDLE handle = ::FindFirstFileW(file.utf16(), &info); + ::FindClose(handle); + if (handle == INVALID_HANDLE_VALUE) + return false; + + if (S_ISDIR(info.dwFileAttributes)) + return ::RemoveDirectoryW(file.utf16()); + + return ::DeleteFileW(file.utf16()); +} + +bool Win32FileSystem::rename(const Path& from,const Path& to) +{ + String fa = PathToOS(_BuildFileName(mVolume,from)); + String fb = PathToOS(_BuildFileName(mVolume,to)); + + return MoveFile(fa.utf16(),fb.utf16()); +} + +Path Win32FileSystem::mapTo(const Path& path) +{ + return _BuildFileName(mVolume,path); +} + +Path Win32FileSystem::mapFrom(const Path& path) +{ + const String::SizeType volumePathLen = mVolume.length(); + + String pathStr = path.getFullPath(); + + if ( mVolume.compare( pathStr, volumePathLen, String::NoCase )) + return Path(); + + return pathStr.substr( volumePathLen, pathStr.length() - volumePathLen ); +} + +//----------------------------------------------------------------------------- + +Win32File::Win32File(const Path& path,String name) +{ + mPath = path; + mName = name; + mStatus = Closed; + mHandle = 0; +} + +Win32File::~Win32File() +{ + if (mHandle) + close(); +} + +Path Win32File::getName() const +{ + return mPath; +} + +FileNode::Status Win32File::getStatus() const +{ + return mStatus; +} + +bool Win32File::getAttributes(Attributes* attr) +{ + WIN32_FIND_DATAW info; + HANDLE handle = ::FindFirstFileW(PathToOS(mName).utf16(), &info); + ::FindClose(handle); + if (handle == INVALID_HANDLE_VALUE) + return false; + + _CopyStatAttributes(info,attr); + attr->name = mPath; + return true; +} + +U32 Win32File::calculateChecksum() +{ + if (!open( Read )) + return 0; + + U64 fileSize = getSize(); + + FrameTemp buf( fileSize ); + + U32 bytesRead = read(buf, fileSize); + + close(); + + if ( bytesRead != fileSize ) + return 0; + + return CRC::calculateCRC(buf, fileSize); +} + +bool Win32File::open(AccessMode mode) +{ + close(); + + if (mName.isEmpty()) + return mStatus; + + struct Mode + { + DWORD mode,share,open; + } Modes[] = + { + { GENERIC_READ,FILE_SHARE_READ,OPEN_EXISTING }, // Read + { GENERIC_WRITE,0,CREATE_ALWAYS }, // Write + { GENERIC_WRITE | GENERIC_READ,0,OPEN_ALWAYS }, // ReadWrite + { GENERIC_WRITE,0,OPEN_ALWAYS } // WriteAppend + }; + + Mode& m = (mode == Read)? Modes[0]: (mode == Write)? Modes[1]: + (mode == ReadWrite)? Modes[2]: Modes[3]; + + mHandle = (void*)::CreateFileW(PathToOS(mName).utf16(), + m.mode, m.share, + NULL, m.open, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + + if ( mHandle == INVALID_HANDLE_VALUE || mHandle == NULL ) + { + _updateStatus(); + return false; + } + + mStatus = Open; + return true; +} + +bool Win32File::close() +{ + if (mHandle) + { + ::CloseHandle((HANDLE)mHandle); + mHandle = 0; + } + + mStatus = Closed; + return true; +} + +U32 Win32File::getPosition() +{ + if (mStatus == Open || mStatus == EndOfFile) + return ::SetFilePointer((HANDLE)mHandle,0,0,FILE_CURRENT); + return 0; +} + +U32 Win32File::setPosition(U32 delta, SeekMode mode) +{ + if (mStatus != Open && mStatus != EndOfFile) + return 0; + + DWORD fmode; + switch (mode) + { + case Begin: fmode = FILE_BEGIN; break; + case Current: fmode = FILE_CURRENT; break; + case End: fmode = FILE_END; break; + default: fmode = 0; break; + } + + DWORD pos = ::SetFilePointer((HANDLE)mHandle,delta,0,fmode); + if (pos == INVALID_SET_FILE_POINTER) + { + mStatus = UnknownError; + return 0; + } + + mStatus = Open; + + return pos; +} + +U32 Win32File::read(void* dst, U32 size) +{ + if (mStatus != Open && mStatus != EndOfFile) + return 0; + + DWORD bytesRead; + if (!::ReadFile((HANDLE)mHandle,dst,size,&bytesRead,0)) + _updateStatus(); + else if (bytesRead != size) + mStatus = EndOfFile; + + return bytesRead; +} + +U32 Win32File::write(const void* src, U32 size) +{ + if ((mStatus != Open && mStatus != EndOfFile) || !size) + return 0; + + DWORD bytesWritten; + if (!::WriteFile((HANDLE)mHandle,src,size,&bytesWritten,0)) + _updateStatus(); + return bytesWritten; +} + +void Win32File::_updateStatus() +{ + switch (::GetLastError()) + { + case ERROR_INVALID_ACCESS: mStatus = AccessDenied; break; + case ERROR_TOO_MANY_OPEN_FILES: mStatus = UnknownError; break; + case ERROR_PATH_NOT_FOUND: mStatus = NoSuchFile; break; + case ERROR_FILE_NOT_FOUND: mStatus = NoSuchFile; break; + case ERROR_SHARING_VIOLATION: mStatus = SharingViolation; break; + case ERROR_HANDLE_DISK_FULL: mStatus = FileSystemFull; break; + case ERROR_ACCESS_DENIED: mStatus = AccessDenied; break; + default: mStatus = UnknownError; break; + } +} + +//----------------------------------------------------------------------------- + +Win32Directory::Win32Directory(const Path& path,String name) +{ + mPath = path; + mName = name; + mStatus = Closed; + mHandle = 0; +} + +Win32Directory::~Win32Directory() +{ + if (mHandle) + close(); +} + +Path Win32Directory::getName() const +{ + return mPath; +} + +bool Win32Directory::open() +{ + if (!_IsDirectory(mName)) + { + mStatus = NoSuchFile; + return false; + } + mStatus = Open; + return true; +} + +bool Win32Directory::close() +{ + if (mHandle) + { + ::FindClose((HANDLE)mHandle); + mHandle = 0; + return true; + } + return false; +} + +bool Win32Directory::read(Attributes* entry) +{ + if (mStatus != Open) + return false; + + WIN32_FIND_DATA info; + if (!mHandle) + { + mHandle = ::FindFirstFileW((PathToOS(mName) + "\\*").utf16(), &info); + + if (mHandle == NULL) + { + _updateStatus(); + return false; + } + } + else + if (!::FindNextFileW((HANDLE)mHandle, &info)) + { + _updateStatus(); + return false; + } + + // Skip "." and ".." entries + if (info.cFileName[0] == '.' && (info.cFileName[1] == '\0' || + (info.cFileName[1] == '.' && info.cFileName[2] == '\0'))) + return read(entry); + + _CopyStatAttributes(info,entry); + entry->name = info.cFileName; + return true; +} + + +U32 Win32Directory::calculateChecksum() +{ + // Return checksum of current entry + return 0; +} + +bool Win32Directory::getAttributes(Attributes* attr) +{ + WIN32_FIND_DATA info; + HANDLE handle = ::FindFirstFileW(PathToOS(mName).utf16(), &info); + ::FindClose(handle); + if (handle == INVALID_HANDLE_VALUE) + { + _updateStatus(); + return false; + } + + _CopyStatAttributes(info,attr); + attr->name = mPath; + return true; +} + +FileNode::Status Win32Directory::getStatus() const +{ + return mStatus; +} + +void Win32Directory::_updateStatus() +{ + switch (::GetLastError()) + { + case ERROR_NO_MORE_FILES: mStatus = EndOfFile; break; + case ERROR_INVALID_ACCESS: mStatus = AccessDenied; break; + case ERROR_PATH_NOT_FOUND: mStatus = NoSuchFile; break; + case ERROR_SHARING_VIOLATION: mStatus = SharingViolation; break; + case ERROR_ACCESS_DENIED: mStatus = AccessDenied; break; + default: mStatus = UnknownError; break; + } +} + +} // Namespace Win32 + +bool FS::VerifyWriteAccess(const Path &path) +{ + // due to UAC's habit of creating "virtual stores" when permission isn't actually available + // actually create, write, read, verify, and delete a file to the folder being tested + + String temp = path.getPath(); + temp += "\\torque_writetest.tmp"; + + // first, (try and) delete the file if it exists + ::DeleteFileW(temp.utf16()); + + // now, create the file + + HANDLE hFile = ::CreateFileW(PathToOS(temp).utf16(), + GENERIC_WRITE, 0, + NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + + if ( hFile == INVALID_HANDLE_VALUE || hFile == NULL ) + return false; + + U32 t = Platform::getTime(); + + DWORD bytesWritten; + if (!::WriteFile(hFile,&t,sizeof(t),&bytesWritten,0)) + { + ::CloseHandle(hFile); + ::DeleteFileW(temp.utf16()); + return false; + } + + // close the file + ::CloseHandle(hFile); + + // open for read + + hFile = ::CreateFileW(PathToOS(temp).utf16(), + GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + + if ( hFile == INVALID_HANDLE_VALUE || hFile == NULL ) + return false; + + U32 t2 = 0; + + DWORD bytesRead; + if (!::ReadFile(hFile,&t2,sizeof(t2),&bytesRead,0)) + { + ::CloseHandle(hFile); + ::DeleteFileW(temp.utf16()); + return false; + } + + ::CloseHandle(hFile); + ::DeleteFileW(temp.utf16()); + + return t == t2; +} + + +} // Namespace Torque + +//----------------------------------------------------------------------------- + +Torque::FS::FileSystemRef Platform::FS::createNativeFS( const String &volume ) +{ + return new Win32::Win32FileSystem( volume ); +} + +String Platform::FS::getAssetDir() +{ + char cen_buf[2048]; +#ifdef TORQUE_UNICODE + if (!Platform::getWebDeployment()) + { + TCHAR buf[ 2048 ]; + ::GetModuleFileNameW( NULL, buf, sizeof( buf ) ); + convertUTF16toUTF8( buf, cen_buf, sizeof( cen_buf ) ); + } + else + { + TCHAR buf[ 2048 ]; + GetCurrentDirectoryW( sizeof( buf ) / sizeof( buf[ 0 ] ), buf ); + convertUTF16toUTF8( buf, cen_buf, sizeof( cen_buf ) ); + return Path::CleanSeparators(cen_buf); + } +#else + ::GetModuleFileNameA( NULL, cen_buf, 2047); +#endif + + char *delimiter = dStrrchr( cen_buf, '\\' ); + + if( delimiter != NULL ) + *delimiter = '\0'; + + return Path::CleanSeparators(cen_buf); +} + +/// Function invoked by the kernel layer to install OS specific +/// file systems. +bool Platform::FS::InstallFileSystems() +{ + WCHAR buffer[1024]; + + // [8/24/2009 tomb] This stops Windows from complaining about drives that have no disks in + SetErrorMode(SEM_FAILCRITICALERRORS); + + // Load all the Win32 logical drives. + DWORD mask = ::GetLogicalDrives(); + char drive[] = "A"; + char volume[] = "A:/"; + while (mask) + { + if (mask & 1) + { + volume[0] = drive[0]; + Platform::FS::Mount(drive, Platform::FS::createNativeFS(volume)); + } + mask >>= 1; + drive[0]++; + } + + // Set the current working dir. Windows normally returns + // upper case driver letters, but the cygwin bash shell + // seems to make it return lower case drives. Force upper + // to be consistent with the mounts. + ::GetCurrentDirectory(sizeof(buffer), buffer); + + if (buffer[1] == ':') + buffer[0] = dToupper(buffer[0]); + + String wd = buffer; + + wd += '/'; + + Platform::FS::SetCwd(wd); + + return true; +} + + diff --git a/platformWin32/winVolume.h b/platformWin32/winVolume.h new file mode 100644 index 0000000..713b6eb --- /dev/null +++ b/platformWin32/winVolume.h @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// GarageGames Library +// Copyright (c) GarageGames, All Rights Reserved +//----------------------------------------------------------------------------- + +#ifndef _WINVOLUME_H_ +#define _WINVOLUME_H_ + +#ifndef _VOLUME_H_ +#include "core/volume.h" +#endif + +namespace Torque +{ +using namespace FS; + +namespace Win32 +{ + +//----------------------------------------------------------------------------- + +class Win32FileSystem: public FileSystem +{ +public: + Win32FileSystem(String volume); + ~Win32FileSystem(); + + String getTypeStr() const { return "Win32"; } + + FileNodeRef resolve(const Path& path); + FileNodeRef create(const Path& path,FileNode::Mode); + bool remove(const Path& path); + bool rename(const Path& from,const Path& to); + Path mapTo(const Path& path); + Path mapFrom(const Path& path); + +private: + String mVolume; +}; + + +//----------------------------------------------------------------------------- +/// Win32 stdio file access. +/// This class makes use the fopen, fread and fwrite for buffered io. +class Win32File: public File +{ +public: + ~Win32File(); + + Path getName() const; + Status getStatus() const; + bool getAttributes(Attributes*); + + U32 getPosition(); + U32 setPosition(U32,SeekMode); + + bool open(AccessMode); + bool close(); + + U32 read(void* dst, U32 size); + U32 write(const void* src, U32 size); + +private: + friend class Win32FileSystem; + + U32 calculateChecksum(); + + Path mPath; + String mName; + void *mHandle; + Status mStatus; + + Win32File(const Path &path, String name); + + bool _updateInfo(); + void _updateStatus(); +}; + + +//----------------------------------------------------------------------------- + +class Win32Directory: public Directory +{ +public: + ~Win32Directory(); + + Path getName() const; + Status getStatus() const; + bool getAttributes(Attributes*); + + bool open(); + bool close(); + bool read(Attributes*); + +private: + friend class Win32FileSystem; + + U32 calculateChecksum(); + + Path mPath; + String mName; + void *mHandle; + Status mStatus; + + Win32Directory(const Path &path,String name); + + void _updateStatus(); +}; + +} // Namespace +} // Namespace +#endif diff --git a/platformWin32/winWindow.cpp b/platformWin32/winWindow.cpp new file mode 100644 index 0000000..10c562c --- /dev/null +++ b/platformWin32/winWindow.cpp @@ -0,0 +1,592 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/event.h" +#include "platformWin32/platformWin32.h" +#include "platformWin32/winConsole.h" +#include "platformWin32/winDirectInput.h" +#include "windowManager/win32/win32Window.h" +#include "console/console.h" +#include "math/mRandom.h" +#include "core/stream/fileStream.h" +#include "T3D/resource.h" +#include +#include "gfx/gfxInit.h" +#include "gfx/gfxDevice.h" +#include "core/strings/unicode.h" +#include "gui/core/guiCanvas.h" + + +extern void createFontInit(); +extern void createFontShutdown(); +extern void installRedBookDevices(); +extern void handleRedBookCallback(U32, U32); + +static MRandomLCG sgPlatRandom; +static bool sgQueueEvents; + +// is keyboard input a standard (non-changing) VK keycode +#define dIsStandardVK(c) (((0x08 <= (c)) && ((c) <= 0x12)) || \ + ((c) == 0x1b) || \ + ((0x20 <= (c)) && ((c) <= 0x2e)) || \ + ((0x30 <= (c)) && ((c) <= 0x39)) || \ + ((0x41 <= (c)) && ((c) <= 0x5a)) || \ + ((0x70 <= (c)) && ((c) <= 0x7B))) + +extern InputObjectInstances DIK_to_Key( U8 dikCode ); + +// static helper variables +static HANDLE gMutexHandle = NULL; +static bool sgDoubleByteEnabled = false; + +// track window states +Win32PlatState winState; + + +//----------------------------------------------------------------------------------------------------------------------------------------------------------- +// +// Microsoft Layer for Unicode +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/mslu/winprog/compiling_your_application_with_the_microsoft_layer_for_unicode.asp +// +//----------------------------------------------------------------------------------------------------------------------------------------------------------- +#ifdef UNICODE + +HMODULE LoadUnicowsProc(void) +{ + return(LoadLibraryA("unicows.dll")); +} + +#ifdef _cplusplus +extern "C" { +#endif +extern FARPROC _PfnLoadUnicows = (FARPROC) &LoadUnicowsProc; +#ifdef _cplusplus +} +#endif + +#endif + +//-------------------------------------- +Win32PlatState::Win32PlatState() +{ + log_fp = NULL; + hinstOpenGL = NULL; + hinstGLU = NULL; + hinstOpenAL = NULL; + appDC = NULL; + appInstance = NULL; + currentTime = 0; + processId = 0; +} + +//-------------------------------------- +bool Platform::excludeOtherInstances(const char *mutexName) +{ +#ifdef UNICODE + UTF16 b[512]; + convertUTF8toUTF16((UTF8 *)mutexName, b, sizeof(b)); + gMutexHandle = CreateMutex(NULL, true, b); +#else + gMutexHandle = CreateMutex(NULL, true, mutexName); +#endif + if(!gMutexHandle) + return false; + + if(GetLastError() == ERROR_ALREADY_EXISTS) + { + CloseHandle(gMutexHandle); + gMutexHandle = NULL; + return false; + } + + return true; +} + +void Platform::restartInstance() +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + TCHAR cen_buf[2048]; + GetModuleFileName( NULL, cen_buf, 2047); + + // Start the child process. + if( CreateProcess( cen_buf, + NULL, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi ) // Pointer to PROCESS_INFORMATION structure + != false ) + { + WaitForInputIdle( pi.hProcess, 5000 ); + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + } +} + +///just check if the app's global mutex exists, and if so, +///return true - otherwise, false. Should be called before ExcludeOther +/// at very start of app execution. +bool Platform::checkOtherInstances(const char *mutexName) +{ +#ifdef TORQUE_MULTITHREAD + + HANDLE pMutex = NULL; +#ifdef UNICODE + UTF16 b[512]; + convertUTF8toUTF16((UTF8 *)mutexName, b, sizeof(b)); + pMutex = CreateMutex(NULL, true, b); +#else + pMutex = CreateMutex(NULL, true, mutexName); +#endif + if(!pMutex) + return false; + + if(GetLastError() == ERROR_ALREADY_EXISTS) + { + //another mutex of the same name exists + //close ours + CloseHandle(pMutex); + pMutex = NULL; + return true; + } +#endif + + CloseHandle(pMutex); + pMutex = NULL; + + //we don;t care, always false + return false; + +} + +//-------------------------------------- +void Platform::AlertOK(const char *windowTitle, const char *message) +{ + ShowCursor(true); +#ifdef UNICODE + UTF16 m[1024], t[512]; + convertUTF8toUTF16((UTF8 *)windowTitle, t, sizeof(t)); + convertUTF8toUTF16((UTF8 *)message, m, sizeof(m)); + MessageBox(NULL, m, t, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OK); +#else + MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OK); +#endif +} + +//-------------------------------------- +bool Platform::AlertOKCancel(const char *windowTitle, const char *message) +{ + ShowCursor(true); +#ifdef UNICODE + UTF16 m[1024], t[512]; + convertUTF8toUTF16((UTF8 *)windowTitle, t, sizeof(t)); + convertUTF8toUTF16((UTF8 *)message, m, sizeof(m)); + return MessageBox(NULL, m, t, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OKCANCEL) == IDOK; +#else + return MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_OKCANCEL) == IDOK; +#endif +} + +//-------------------------------------- +bool Platform::AlertRetry(const char *windowTitle, const char *message) +{ + ShowCursor(true); +#ifdef UNICODE + UTF16 m[1024], t[512]; + convertUTF8toUTF16((UTF8 *)windowTitle, t, sizeof(t)); + convertUTF8toUTF16((UTF8 *)message, m, sizeof(m)); + return (MessageBox(NULL, m, t, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_RETRYCANCEL) == IDRETRY); +#else + return (MessageBox(NULL, message, windowTitle, MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TASKMODAL | MB_RETRYCANCEL) == IDRETRY); +#endif +} + +//-------------------------------------- +HIMC gIMEContext; + +static void InitInput() +{ +#ifndef TORQUE_LIB +#ifdef UNICODE + //gIMEContext = ImmGetContext(getWin32WindowHandle()); + //ImmReleaseContext( getWin32WindowHandle(), gIMEContext ); +#endif +#endif +} + +//-------------------------------------- +void Platform::init() +{ + Con::printf("Initializing platform..."); + + // Set the platform variable for the scripts + Con::setVariable( "$platform", "windows" ); + + WinConsole::create(); + + if ( !WinConsole::isEnabled() ) + Input::init(); + + InitInput(); // in case DirectInput falls through + + installRedBookDevices(); + + sgDoubleByteEnabled = GetSystemMetrics( SM_DBCSENABLED ); + sgQueueEvents = true; + Con::printf("Done"); +} + +//-------------------------------------- +void Platform::shutdown() +{ + sgQueueEvents = false; + + if(gMutexHandle) + CloseHandle(gMutexHandle); + + Input::destroy(); + + GFXDevice::destroy(); + + WinConsole::destroy(); +} + +extern bool LinkConsoleFunctions; + +#ifndef TORQUE_SHARED + +extern S32 TorqueMain(S32 argc, const char **argv); + +//-------------------------------------- +static S32 run(S32 argc, const char **argv) +{ + // Console hack to ensure consolefunctions get linked in + LinkConsoleFunctions=true; + + createFontInit(); + + S32 ret = TorqueMain(argc, argv); + + createFontShutdown(); + + return ret; +} + +//-------------------------------------- +S32 main(S32 argc, const char **argv) +{ + winState.appInstance = GetModuleHandle(NULL); + return run(argc, argv); +} + +//-------------------------------------- + +#include "unit/test.h" +#include "app/mainLoop.h" + +S32 PASCAL WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR lpszCmdLine, S32) +{ +#if 0 + // Run a unit test. + StandardMainLoop::initCore(); + UnitTesting::TestRun tr; + tr.test("Platform", true); +#else + + Vector argv( __FILE__, __LINE__ ); + + char moduleName[256]; +#ifdef TORQUE_UNICODE + { + TCHAR buf[ 256 ]; + GetModuleFileNameW( NULL, buf, sizeof( buf ) ); + convertUTF16toUTF8( buf, moduleName, sizeof( moduleName ) ); + } +#else + GetModuleFileNameA(NULL, moduleName, sizeof(moduleName)); +#endif + argv.push_back(moduleName); + + for (const char* word,*ptr = lpszCmdLine; *ptr; ) + { + // Eat white space + for (; dIsspace(*ptr) && *ptr; ptr++) + ; + + // Pick out the next word + for (word = ptr; !dIsspace(*ptr) && *ptr; ptr++) + ; + + // Add the word to the argument list. + if (*word) + { + int len = ptr - word; + char *arg = (char *) dMalloc(len + 1); + dStrncpy(arg, word, len); + arg[len] = 0; + argv.push_back(arg); + } + } + + winState.appInstance = hInstance; + + S32 retVal = run(argv.size(), (const char **) argv.address()); + + for(U32 j = 1; j < argv.size(); j++) + dFree(argv[j]); + + return retVal; +#endif +} + +#else //TORQUE_SHARED + +extern "C" +{ + bool torque_engineinit(int argc, const char **argv); + int torque_enginetick(); + bool torque_engineshutdown(); +}; + +int TorqueMain(int argc, const char **argv) +{ + if (!torque_engineinit(argc, argv)) + return 1; + + while(torque_enginetick()) + { + + } + + torque_engineshutdown(); + + return 0; + +} + + + +extern "C" { + +S32 torque_winmain( HINSTANCE hInstance, HINSTANCE, LPSTR lpszCmdLine, S32) +{ + Vector argv( __FILE__, __LINE__ ); + + char moduleName[256]; +#ifdef TORQUE_UNICODE + { + TCHAR buf[ 256 ]; + GetModuleFileNameW( NULL, buf, sizeof( buf ) ); + convertUTF16toUTF8( buf, moduleName, sizeof( moduleName ) ); +} +#else + GetModuleFileNameA(NULL, moduleName, sizeof(moduleName)); +#endif + argv.push_back(moduleName); + + for (const char* word,*ptr = lpszCmdLine; *ptr; ) + { + // Eat white space + for (; dIsspace(*ptr) && *ptr; ptr++) + ; + + // Pick out the next word + for (word = ptr; !dIsspace(*ptr) && *ptr; ptr++) + ; + + // Add the word to the argument list. + if (*word) + { + int len = ptr - word; + char *arg = (char *) dMalloc(len + 1); + dStrncpy(arg, word, len); + arg[len] = 0; + argv.push_back(arg); + } + } + + winState.appInstance = hInstance; + + S32 retVal = TorqueMain(argv.size(), (const char **) argv.address()); + + for(U32 j = 1; j < argv.size(); j++) + dFree(argv[j]); + + return retVal; +} + +} // extern "C" + +#endif + + + +//-------------------------------------- + +F32 Platform::getRandom() +{ + return sgPlatRandom.randF(); +} + +////-------------------------------------- +/// Spawn the default Operating System web browser with a URL +/// @param webAddress URL to pass to browser +/// @return true if browser successfully spawned +bool Platform::openWebBrowser( const char* webAddress ) +{ + static bool sHaveKey = false; + static wchar_t sWebKey[512]; + char utf8WebKey[512]; + + { + HKEY regKey; + DWORD size = sizeof( sWebKey ); + + if ( RegOpenKeyEx( HKEY_CLASSES_ROOT, dT("\\http\\shell\\open\\command"), 0, KEY_QUERY_VALUE, ®Key ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Platform::openWebBrowser - Failed to open the HKCR\\http registry key!!!"); + return( false ); + } + + if ( RegQueryValueEx( regKey, dT(""), NULL, NULL, (unsigned char *)sWebKey, &size ) != ERROR_SUCCESS ) + { + Con::errorf( ConsoleLogEntry::General, "Platform::openWebBrowser - Failed to query the open command registry key!!!" ); + return( false ); + } + + RegCloseKey( regKey ); + sHaveKey = true; + + convertUTF16toUTF8(sWebKey,utf8WebKey,512); + +#ifdef UNICODE + char *p = dStrstr((const char *)utf8WebKey, "%1"); +#else + char *p = strstr( (const char *) sWebKey , "%1"); +#endif + if (p) *p = 0; + + } + + STARTUPINFO si; + dMemset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + char buf[1024]; +#ifdef UNICODE + dSprintf( buf, sizeof( buf ), "%s %s", utf8WebKey, webAddress ); + UTF16 b[1024]; + convertUTF8toUTF16((UTF8 *)buf, b, sizeof(b)); +#else + dSprintf( buf, sizeof( buf ), "%s %s", sWebKey, webAddress ); +#endif + + //Con::errorf( ConsoleLogEntry::General, "** Web browser command = %s **", buf ); + + PROCESS_INFORMATION pi; + dMemset( &pi, 0, sizeof( pi ) ); + CreateProcess( NULL, +#ifdef UNICODE + b, +#else + buf, +#endif + NULL, + NULL, + false, + CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP, + NULL, + NULL, + &si, + &pi ); + + return( true ); +} + +//-------------------------------------- +// Login password routines: +//-------------------------------------- +#ifdef UNICODE +static const UTF16* TorqueRegKey = dT("SOFTWARE\\GarageGames\\Torque"); +#else +static const char* TorqueRegKey = "SOFTWARE\\GarageGames\\Torque"; +#endif + +const char* Platform::getLoginPassword() +{ + HKEY regKey; + char* returnString = NULL; + if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, TorqueRegKey, 0, KEY_QUERY_VALUE, ®Key ) == ERROR_SUCCESS ) + { + U8 buf[32]; + DWORD size = sizeof( buf ); + if ( RegQueryValueEx( regKey, dT("LoginPassword"), NULL, NULL, buf, &size ) == ERROR_SUCCESS ) + { + returnString = Con::getReturnBuffer( size + 1 ); + dStrcpy( returnString, (const char*) buf ); + } + + RegCloseKey( regKey ); + } + + if ( returnString ) + return( returnString ); + else + return( "" ); +} + +//-------------------------------------- +bool Platform::setLoginPassword( const char* password ) +{ + HKEY regKey; + if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, TorqueRegKey, 0, KEY_WRITE, ®Key ) == ERROR_SUCCESS ) + { + if ( RegSetValueEx( regKey, dT("LoginPassword"), 0, REG_SZ, (const U8*) password, dStrlen( password ) + 1 ) != ERROR_SUCCESS ) + Con::errorf( ConsoleLogEntry::General, "setLoginPassword - Failed to set the subkey value!" ); + + RegCloseKey( regKey ); + return( true ); + } + else + Con::errorf( ConsoleLogEntry::General, "setLoginPassword - Failed to open the Torque registry key!" ); + + return( false ); +} + +//-------------------------------------- +// Silly Korean registry key checker: +// +// NOTE: "Silly" refers to the nature of this hack, and is not intended +// as commentary on Koreans as a nationality. Thank you for your +// attention. +//-------------------------------------- +ConsoleFunction( isKoreanBuild, bool, 1, 1, "isKoreanBuild()" ) +{ + argc; argv; + HKEY regKey; + bool result = false; + if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, TorqueRegKey, 0, KEY_QUERY_VALUE, ®Key ) == ERROR_SUCCESS ) + { + DWORD val; + DWORD size = sizeof( val ); + if ( RegQueryValueEx( regKey, dT("Korean"), NULL, NULL, (U8*) &val, &size ) == ERROR_SUCCESS ) + result = ( val > 0 ); + + RegCloseKey( regKey ); + } + + return( result ); +} \ No newline at end of file diff --git a/platformWin32/win_common_prefix.h b/platformWin32/win_common_prefix.h new file mode 100644 index 0000000..9a67281 --- /dev/null +++ b/platformWin32/win_common_prefix.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//------------------------------ +//win_common_prefix.h +//------------------------------ + +//------------------------------ +// basic build defines + +// define our platform +//#define TARG_WIN32 1 +//#define WIN32 1 + +// normally, this should be on for a PC build +//#define USEASSEMBLYTERRBLEND 1 + + +//------------------------------ +// setup compiler-specific flags + +// METROWERKS CODEWARRIOR +//#if __MWERKS__ +//#pragma once +// we turn this on to use inlined CW6-safe ASM in CWProject builds. +// ... and thus not require NASM for CWProject building ... +//#define TARG_INLINED_ASM 1 +//#endif + + +// these are general build flags Torque uses. +//#define PNG_NO_READ_tIME 1 +//#define PNG_NO_WRITE_TIME 1 + +//#define NO_MILES_OPENAL 1 diff --git a/platformWin32/win_debug_prefix.h b/platformWin32/win_debug_prefix.h new file mode 100644 index 0000000..654e4ff --- /dev/null +++ b/platformWin32/win_debug_prefix.h @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//win_debug_prefix.h + +#include "win_common_prefix.h" + +// our defines +//#define BUILD_SUFFIX "_DEBUG" +//#define DEBUG 1 +//#define ENABLE_ASSERTS 1 + +// the PC doesn't need this. just the mac... +//#define ITFDUMP_NOASM 1 + + +//#define USEASSEMBLYTERRBLEND 1 + +//#define PNG_NO_READ_tIME 1 +//#define PNG_NO_WRITE_TIME 1 + +//#define NO_MILES_OPENAL 1 diff --git a/platformWin32/win_release_prefix.h b/platformWin32/win_release_prefix.h new file mode 100644 index 0000000..61a11f4 --- /dev/null +++ b/platformWin32/win_release_prefix.h @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +//win_debug_prefix.h + +#include "win_common_prefix.h" + +// our defines +//#define TGE_RELEASE 1 +//#define TGE_NO_ASSERTS 1 +//#define BUILD_SUFFIX "" + +// these should be off in a release build +//#define DEBUG 1 +//#define ENABLE_ASSERTS 1 + +// the PC doesn't need this. just the mac... +//#define ITFDUMP_NOASM 1 + + +//#define USEASSEMBLYTERRBLEND 1 + +//#define PNG_NO_READ_tIME 1 +//#define PNG_NO_WRITE_TIME 1 + +//#define NO_MILES_OPENAL 1 diff --git a/platformX86UNIX/gl_types.h b/platformX86UNIX/gl_types.h new file mode 100644 index 0000000..e216023 --- /dev/null +++ b/platformX86UNIX/gl_types.h @@ -0,0 +1,966 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIX_GL_TYPES_H_ +#define _X86UNIX_GL_TYPES_H_ + +// added by JMQ: +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 + +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#define GL_CLAMP_TO_EDGE_EXT 0x812F + +#define GL_V12MTVFMT_EXT 0x8702 +#define GL_V12MTNVFMT_EXT 0x8703 +#define GL_V12FTVFMT_EXT 0x8704 +#define GL_V12FMTVFMT_EXT 0x8705 + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE3_RGB_EXT 0x8583 +#define GL_SOURCE4_RGB_EXT 0x8584 +#define GL_SOURCE5_RGB_EXT 0x8585 +#define GL_SOURCE6_RGB_EXT 0x8586 +#define GL_SOURCE7_RGB_EXT 0x8587 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_SOURCE3_ALPHA_EXT 0x858B +#define GL_SOURCE4_ALPHA_EXT 0x858C +#define GL_SOURCE5_ALPHA_EXT 0x858D +#define GL_SOURCE6_ALPHA_EXT 0x858E +#define GL_SOURCE7_ALPHA_EXT 0x858F +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND3_RGB_EXT 0x8593 +#define GL_OPERAND4_RGB_EXT 0x8594 +#define GL_OPERAND5_RGB_EXT 0x8595 +#define GL_OPERAND6_RGB_EXT 0x8596 +#define GL_OPERAND7_RGB_EXT 0x8597 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#define GL_OPERAND3_ALPHA_EXT 0x859B +#define GL_OPERAND4_ALPHA_EXT 0x859C +#define GL_OPERAND5_ALPHA_EXT 0x859D +#define GL_OPERAND6_ALPHA_EXT 0x859E +#define GL_OPERAND7_ALPHA_EXT 0x859F +#endif + +/* + * Mesa 3-D graphics library + * Version: 3.4 + * + * Copyright (C) 1999-2000 Brian Paul All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + + +#define GL_VERSION_1_1 1 +#define GL_VERSION_1_2 1 + +/* + * + * Datatypes + * + */ +#ifdef CENTERLINE_CLPP +#define signed +#endif +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; /* 1-byte signed */ +typedef short GLshort; /* 2-byte signed */ +typedef int GLint; /* 4-byte signed */ +typedef unsigned char GLubyte; /* 1-byte unsigned */ +typedef unsigned short GLushort; /* 2-byte unsigned */ +typedef unsigned int GLuint; /* 4-byte unsigned */ +typedef int GLsizei; /* 4-byte signed */ +typedef float GLfloat; /* single precision float */ +typedef float GLclampf; /* single precision float in [0,1] */ +typedef double GLdouble; /* double precision float */ +typedef double GLclampd; /* double precision float in [0,1] */ + + + +/* + * + * Constants + * + */ + +/* Boolean values */ +#define GL_FALSE 0x0 +#define GL_TRUE 0x1 + +/* Data types */ +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 + +/* Primitives */ +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +/* Vertex Arrays */ +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 +#define GL_VERTEX_ARRAY_SIZE 0x807A +#define GL_VERTEX_ARRAY_TYPE 0x807B +#define GL_VERTEX_ARRAY_STRIDE 0x807C +#define GL_NORMAL_ARRAY_TYPE 0x807E +#define GL_NORMAL_ARRAY_STRIDE 0x807F +#define GL_COLOR_ARRAY_SIZE 0x8081 +#define GL_COLOR_ARRAY_TYPE 0x8082 +#define GL_COLOR_ARRAY_STRIDE 0x8083 +#define GL_INDEX_ARRAY_TYPE 0x8085 +#define GL_INDEX_ARRAY_STRIDE 0x8086 +#define GL_TEXTURE_COORD_ARRAY_SIZE 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE 0x808A +#define GL_EDGE_FLAG_ARRAY_STRIDE 0x808C +#define GL_VERTEX_ARRAY_POINTER 0x808E +#define GL_NORMAL_ARRAY_POINTER 0x808F +#define GL_COLOR_ARRAY_POINTER 0x8090 +#define GL_INDEX_ARRAY_POINTER 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER 0x8093 +#define GL_V2F 0x2A20 +#define GL_V3F 0x2A21 +#define GL_C4UB_V2F 0x2A22 +#define GL_C4UB_V3F 0x2A23 +#define GL_C3F_V3F 0x2A24 +#define GL_N3F_V3F 0x2A25 +#define GL_C4F_N3F_V3F 0x2A26 +#define GL_T2F_V3F 0x2A27 +#define GL_T4F_V4F 0x2A28 +#define GL_T2F_C4UB_V3F 0x2A29 +#define GL_T2F_C3F_V3F 0x2A2A +#define GL_T2F_N3F_V3F 0x2A2B +#define GL_T2F_C4F_N3F_V3F 0x2A2C +#define GL_T4F_C4F_N3F_V4F 0x2A2D + +/* Matrix Mode */ +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 + +/* Points */ +#define GL_POINT_SMOOTH 0x0B10 +#define GL_POINT_SIZE 0x0B11 +#define GL_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_POINT_SIZE_RANGE 0x0B12 + +/* Lines */ +#define GL_LINE_SMOOTH 0x0B20 +#define GL_LINE_STIPPLE 0x0B24 +#define GL_LINE_STIPPLE_PATTERN 0x0B25 +#define GL_LINE_STIPPLE_REPEAT 0x0B26 +#define GL_LINE_WIDTH 0x0B21 +#define GL_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_LINE_WIDTH_RANGE 0x0B22 + +/* Polygons */ +#define GL_POINT 0x1B00 +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_POLYGON_MODE 0x0B40 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_EDGE_FLAG 0x0B43 +#define GL_CULL_FACE 0x0B44 +#define GL_CULL_FACE_MODE 0x0B45 +#define GL_FRONT_FACE 0x0B46 +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +/* Display Lists */ +#define GL_COMPILE 0x1300 +#define GL_COMPILE_AND_EXECUTE 0x1301 +#define GL_LIST_BASE 0x0B32 +#define GL_LIST_INDEX 0x0B33 +#define GL_LIST_MODE 0x0B30 + +/* Depth buffer */ +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_BITS 0x0D56 +#define GL_DEPTH_CLEAR_VALUE 0x0B73 +#define GL_DEPTH_FUNC 0x0B74 +#define GL_DEPTH_RANGE 0x0B70 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_DEPTH_COMPONENT 0x1902 + +/* Lighting */ +#define GL_LIGHTING 0x0B50 +#define GL_LIGHT0 0x4000 +#define GL_LIGHT1 0x4001 +#define GL_LIGHT2 0x4002 +#define GL_LIGHT3 0x4003 +#define GL_LIGHT4 0x4004 +#define GL_LIGHT5 0x4005 +#define GL_LIGHT6 0x4006 +#define GL_LIGHT7 0x4007 +#define GL_SPOT_EXPONENT 0x1205 +#define GL_SPOT_CUTOFF 0x1206 +#define GL_CONSTANT_ATTENUATION 0x1207 +#define GL_LINEAR_ATTENUATION 0x1208 +#define GL_QUADRATIC_ATTENUATION 0x1209 +#define GL_AMBIENT 0x1200 +#define GL_DIFFUSE 0x1201 +#define GL_SPECULAR 0x1202 +#define GL_SHININESS 0x1601 +#define GL_EMISSION 0x1600 +#define GL_POSITION 0x1203 +#define GL_SPOT_DIRECTION 0x1204 +#define GL_AMBIENT_AND_DIFFUSE 0x1602 +#define GL_COLOR_INDEXES 0x1603 +#define GL_LIGHT_MODEL_TWO_SIDE 0x0B52 +#define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51 +#define GL_LIGHT_MODEL_AMBIENT 0x0B53 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_SHADE_MODEL 0x0B54 +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 +#define GL_COLOR_MATERIAL 0x0B57 +#define GL_COLOR_MATERIAL_FACE 0x0B55 +#define GL_COLOR_MATERIAL_PARAMETER 0x0B56 +#define GL_NORMALIZE 0x0BA1 + +/* User clipping planes */ +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 + +/* Accumulation buffer */ +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_ACCUM_CLEAR_VALUE 0x0B80 +#define GL_ACCUM 0x0100 +#define GL_ADD 0x0104 +#define GL_LOAD 0x0101 +#define GL_MULT 0x0103 +#define GL_RETURN 0x0102 + +/* Alpha testing */ +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_REF 0x0BC2 +#define GL_ALPHA_TEST_FUNC 0x0BC1 + +/* Blending */ +#define GL_BLEND 0x0BE2 +#define GL_BLEND_SRC 0x0BE1 +#define GL_BLEND_DST 0x0BE0 +#define GL_ZERO 0x0 +#define GL_ONE 0x1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + +/* Render Mode */ +#define GL_FEEDBACK 0x1C01 +#define GL_RENDER 0x1C00 +#define GL_SELECT 0x1C02 + +/* Feedback */ +#define GL_2D 0x0600 +#define GL_3D 0x0601 +#define GL_3D_COLOR 0x0602 +#define GL_3D_COLOR_TEXTURE 0x0603 +#define GL_4D_COLOR_TEXTURE 0x0604 +#define GL_POINT_TOKEN 0x0701 +#define GL_LINE_TOKEN 0x0702 +#define GL_LINE_RESET_TOKEN 0x0707 +#define GL_POLYGON_TOKEN 0x0703 +#define GL_BITMAP_TOKEN 0x0704 +#define GL_DRAW_PIXEL_TOKEN 0x0705 +#define GL_COPY_PIXEL_TOKEN 0x0706 +#define GL_PASS_THROUGH_TOKEN 0x0700 +#define GL_FEEDBACK_BUFFER_POINTER 0x0DF0 +#define GL_FEEDBACK_BUFFER_SIZE 0x0DF1 +#define GL_FEEDBACK_BUFFER_TYPE 0x0DF2 + +/* Selection */ +#define GL_SELECTION_BUFFER_POINTER 0x0DF3 +#define GL_SELECTION_BUFFER_SIZE 0x0DF4 + +/* Fog */ +#define GL_FOG 0x0B60 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_COLOR 0x0B66 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_LINEAR 0x2601 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +/* Logic Ops */ +#define GL_LOGIC_OP 0x0BF1 +#define GL_INDEX_LOGIC_OP 0x0BF1 +#define GL_COLOR_LOGIC_OP 0x0BF2 +#define GL_LOGIC_OP_MODE 0x0BF0 +#define GL_CLEAR 0x1500 +#define GL_SET 0x150F +#define GL_COPY 0x1503 +#define GL_COPY_INVERTED 0x150C +#define GL_NOOP 0x1505 +#define GL_INVERT 0x150A +#define GL_AND 0x1501 +#define GL_NAND 0x150E +#define GL_OR 0x1507 +#define GL_NOR 0x1508 +#define GL_XOR 0x1506 +#define GL_EQUIV 0x1509 +#define GL_AND_REVERSE 0x1502 +#define GL_AND_INVERTED 0x1504 +#define GL_OR_REVERSE 0x150B +#define GL_OR_INVERTED 0x150D + +/* Stencil */ +#define GL_STENCIL_TEST 0x0B90 +#define GL_STENCIL_WRITEMASK 0x0B98 +#define GL_STENCIL_BITS 0x0D57 +#define GL_STENCIL_FUNC 0x0B92 +#define GL_STENCIL_VALUE_MASK 0x0B93 +#define GL_STENCIL_REF 0x0B97 +#define GL_STENCIL_FAIL 0x0B94 +#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96 +#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95 +#define GL_STENCIL_CLEAR_VALUE 0x0B91 +#define GL_STENCIL_INDEX 0x1901 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +/* Buffers, Pixel Drawing/Reading */ +#define GL_NONE 0x0 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +/*GL_FRONT 0x0404 */ +/*GL_BACK 0x0405 */ +/*GL_FRONT_AND_BACK 0x0408 */ +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C +#define GL_COLOR_INDEX 0x1900 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_ALPHA_BITS 0x0D55 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_INDEX_BITS 0x0D51 +#define GL_SUBPIXEL_BITS 0x0D50 +#define GL_AUX_BUFFERS 0x0C00 +#define GL_READ_BUFFER 0x0C02 +#define GL_DRAW_BUFFER 0x0C01 +#define GL_DOUBLEBUFFER 0x0C32 +#define GL_STEREO 0x0C33 +#define GL_BITMAP 0x1A00 +#define GL_COLOR 0x1800 +#define GL_DEPTH 0x1801 +#define GL_STENCIL 0x1802 +#define GL_DITHER 0x0BD0 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 + +/* Implementation limits */ +#define GL_MAX_LIST_NESTING 0x0B31 +#define GL_MAX_ATTRIB_STACK_DEPTH 0x0D35 +#define GL_MAX_MODELVIEW_STACK_DEPTH 0x0D36 +#define GL_MAX_NAME_STACK_DEPTH 0x0D37 +#define GL_MAX_PROJECTION_STACK_DEPTH 0x0D38 +#define GL_MAX_TEXTURE_STACK_DEPTH 0x0D39 +#define GL_MAX_EVAL_ORDER 0x0D30 +#define GL_MAX_LIGHTS 0x0D31 +#define GL_MAX_CLIP_PLANES 0x0D32 +#define GL_MAX_TEXTURE_SIZE 0x0D33 +#define GL_MAX_PIXEL_MAP_TABLE 0x0D34 +#define GL_MAX_VIEWPORT_DIMS 0x0D3A +#define GL_MAX_CLIENT_ATTRIB_STACK_DEPTH 0x0D3B + +/* Gets */ +#define GL_ATTRIB_STACK_DEPTH 0x0BB0 +#define GL_CLIENT_ATTRIB_STACK_DEPTH 0x0BB1 +#define GL_COLOR_CLEAR_VALUE 0x0C22 +#define GL_COLOR_WRITEMASK 0x0C23 +#define GL_CURRENT_INDEX 0x0B01 +#define GL_CURRENT_COLOR 0x0B00 +#define GL_CURRENT_NORMAL 0x0B02 +#define GL_CURRENT_RASTER_COLOR 0x0B04 +#define GL_CURRENT_RASTER_DISTANCE 0x0B09 +#define GL_CURRENT_RASTER_INDEX 0x0B05 +#define GL_CURRENT_RASTER_POSITION 0x0B07 +#define GL_CURRENT_RASTER_TEXTURE_COORDS 0x0B06 +#define GL_CURRENT_RASTER_POSITION_VALID 0x0B08 +#define GL_CURRENT_TEXTURE_COORDS 0x0B03 +#define GL_INDEX_CLEAR_VALUE 0x0C20 +#define GL_INDEX_MODE 0x0C30 +#define GL_INDEX_WRITEMASK 0x0C21 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_MODELVIEW_STACK_DEPTH 0x0BA3 +#define GL_NAME_STACK_DEPTH 0x0D70 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_PROJECTION_STACK_DEPTH 0x0BA4 +#define GL_RENDER_MODE 0x0C40 +#define GL_RGBA_MODE 0x0C31 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_TEXTURE_STACK_DEPTH 0x0BA5 +#define GL_VIEWPORT 0x0BA2 + +/* Evaluators */ +#define GL_AUTO_NORMAL 0x0D80 +#define GL_MAP1_COLOR_4 0x0D90 +#define GL_MAP1_GRID_DOMAIN 0x0DD0 +#define GL_MAP1_GRID_SEGMENTS 0x0DD1 +#define GL_MAP1_INDEX 0x0D91 +#define GL_MAP1_NORMAL 0x0D92 +#define GL_MAP1_TEXTURE_COORD_1 0x0D93 +#define GL_MAP1_TEXTURE_COORD_2 0x0D94 +#define GL_MAP1_TEXTURE_COORD_3 0x0D95 +#define GL_MAP1_TEXTURE_COORD_4 0x0D96 +#define GL_MAP1_VERTEX_3 0x0D97 +#define GL_MAP1_VERTEX_4 0x0D98 +#define GL_MAP2_COLOR_4 0x0DB0 +#define GL_MAP2_GRID_DOMAIN 0x0DD2 +#define GL_MAP2_GRID_SEGMENTS 0x0DD3 +#define GL_MAP2_INDEX 0x0DB1 +#define GL_MAP2_NORMAL 0x0DB2 +#define GL_MAP2_TEXTURE_COORD_1 0x0DB3 +#define GL_MAP2_TEXTURE_COORD_2 0x0DB4 +#define GL_MAP2_TEXTURE_COORD_3 0x0DB5 +#define GL_MAP2_TEXTURE_COORD_4 0x0DB6 +#define GL_MAP2_VERTEX_3 0x0DB7 +#define GL_MAP2_VERTEX_4 0x0DB8 +#define GL_COEFF 0x0A00 +#define GL_DOMAIN 0x0A02 +#define GL_ORDER 0x0A01 + +/* Hints */ +#define GL_FOG_HINT 0x0C54 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_PERSPECTIVE_CORRECTION_HINT 0x0C50 +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +/* Scissor box */ +#define GL_SCISSOR_TEST 0x0C11 +#define GL_SCISSOR_BOX 0x0C10 + +/* Pixel Mode / Transfer */ +#define GL_MAP_COLOR 0x0D10 +#define GL_MAP_STENCIL 0x0D11 +#define GL_INDEX_SHIFT 0x0D12 +#define GL_INDEX_OFFSET 0x0D13 +#define GL_RED_SCALE 0x0D14 +#define GL_RED_BIAS 0x0D15 +#define GL_GREEN_SCALE 0x0D18 +#define GL_GREEN_BIAS 0x0D19 +#define GL_BLUE_SCALE 0x0D1A +#define GL_BLUE_BIAS 0x0D1B +#define GL_ALPHA_SCALE 0x0D1C +#define GL_ALPHA_BIAS 0x0D1D +#define GL_DEPTH_SCALE 0x0D1E +#define GL_DEPTH_BIAS 0x0D1F +#define GL_PIXEL_MAP_S_TO_S_SIZE 0x0CB1 +#define GL_PIXEL_MAP_I_TO_I_SIZE 0x0CB0 +#define GL_PIXEL_MAP_I_TO_R_SIZE 0x0CB2 +#define GL_PIXEL_MAP_I_TO_G_SIZE 0x0CB3 +#define GL_PIXEL_MAP_I_TO_B_SIZE 0x0CB4 +#define GL_PIXEL_MAP_I_TO_A_SIZE 0x0CB5 +#define GL_PIXEL_MAP_R_TO_R_SIZE 0x0CB6 +#define GL_PIXEL_MAP_G_TO_G_SIZE 0x0CB7 +#define GL_PIXEL_MAP_B_TO_B_SIZE 0x0CB8 +#define GL_PIXEL_MAP_A_TO_A_SIZE 0x0CB9 +#define GL_PIXEL_MAP_S_TO_S 0x0C71 +#define GL_PIXEL_MAP_I_TO_I 0x0C70 +#define GL_PIXEL_MAP_I_TO_R 0x0C72 +#define GL_PIXEL_MAP_I_TO_G 0x0C73 +#define GL_PIXEL_MAP_I_TO_B 0x0C74 +#define GL_PIXEL_MAP_I_TO_A 0x0C75 +#define GL_PIXEL_MAP_R_TO_R 0x0C76 +#define GL_PIXEL_MAP_G_TO_G 0x0C77 +#define GL_PIXEL_MAP_B_TO_B 0x0C78 +#define GL_PIXEL_MAP_A_TO_A 0x0C79 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_PACK_LSB_FIRST 0x0D01 +#define GL_PACK_ROW_LENGTH 0x0D02 +#define GL_PACK_SKIP_PIXELS 0x0D04 +#define GL_PACK_SKIP_ROWS 0x0D03 +#define GL_PACK_SWAP_BYTES 0x0D00 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_UNPACK_LSB_FIRST 0x0CF1 +#define GL_UNPACK_ROW_LENGTH 0x0CF2 +#define GL_UNPACK_SKIP_PIXELS 0x0CF4 +#define GL_UNPACK_SKIP_ROWS 0x0CF3 +#define GL_UNPACK_SWAP_BYTES 0x0CF0 +#define GL_ZOOM_X 0x0D16 +#define GL_ZOOM_Y 0x0D17 + +/* Texture mapping */ +#define GL_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_TEXTURE_COMPONENTS 0x1003 +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_LINEAR 0x2400 +#define GL_EYE_PLANE 0x2502 +#define GL_SPHERE_MAP 0x2402 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 +#define GL_NEAREST 0x2600 +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 + +/* GL 1.1 texturing */ +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_TEXTURE_PRIORITY 0x8066 +#define GL_TEXTURE_RESIDENT 0x8067 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 + +/* GL 1.2 texturing */ +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_TEXTURE_BINDING_3D 0x806A + +/* Internal texture formats (GL 1.1) */ +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B + +/* Utility */ +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +/* Errors */ +#define GL_NO_ERROR 0x0 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + + +/* OpenGL 1.2 */ +#define GL_RESCALE_NORMAL 0x803A +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E + + + +/* + * OpenGL 1.2 imaging subset (NOT IMPLEMENTED BY MESA) + */ +/* GL_EXT_color_table */ +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +/* GL_EXT_convolution and GL_HP_convolution_border_modes */ +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +/* GL_SGI_color_matrix */ +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +/* GL_EXT_histogram */ +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +/* GL_EXT_blend_color, GL_EXT_blend_minmax */ +#define GL_BLEND_EQUATION 0x8009 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_FUNC_ADD 0x8006 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_BLEND_COLOR 0x8005 + + +/* glPush/PopAttrib bits */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000FFFFF + + +#define GL_CLIENT_PIXEL_STORE_BIT 0x00000001 +#define GL_CLIENT_VERTEX_ARRAY_BIT 0x00000002 +#define GL_ALL_CLIENT_ATTRIB_BITS 0xFFFFFFFF + +/* + * GL_ARB_multitexture (ARB extension 1 and OpenGL 1.2.1) + */ +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 + +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF + + + + + +#endif // #ifndef _X86UNIX_GL_TYPES_H_ diff --git a/platformX86UNIX/platformAL.h b/platformX86UNIX/platformAL.h new file mode 100644 index 0000000..488fed8 --- /dev/null +++ b/platformX86UNIX/platformAL.h @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMAL_H_ +#define _PLATFORMAL_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#define AL_NO_PROTOTYPES +#include +#include +#include + +// extra enums for win32/miles implementation +enum { + // error values + AL_CONTEXT_ALREADY_INSTANTIATED = 0xbaadf00d, + AL_ENVIRONMENT_ALREADY_INSTANTIATED, + AL_UNSUPPORTED, + AL_INVALID_BUFFER, + AL_ERROR, + + // context extention + ALC_PROVIDER, + ALC_PROVIDER_COUNT, + ALC_PROVIDER_NAME, + ALC_SPEAKER, + ALC_SPEAKER_COUNT, + ALC_SPEAKER_NAME, + ALC_BUFFER_DYNAMIC_MEMORY_SIZE, + ALC_BUFFER_DYNAMIC_MEMORY_USAGE, + ALC_BUFFER_DYNAMIC_COUNT, + ALC_BUFFER_MEMORY_USAGE, + ALC_BUFFER_COUNT, + ALC_BUFFER_LATENCY, + + // misc 3d params + AL_MIN_DISTANCE, + AL_MAX_DISTANCE, + AL_CONE_OUTER_GAIN, + + // relative with pos(0,0,0) won't work for ambient sounds with miles + AL_SOURCE_AMBIENT, + AL_PAN, + + // other extensions + AL_BUFFER_KEEP_RESIDENT, + AL_FORMAT_WAVE_EXT, + + // Environment extensions: + AL_ENV_EFFECT_VOLUME_EXT, + AL_ENV_FLAGS_EXT, + AL_ENV_DAMPING_EXT, + AL_ENV_ENVIRONMENT_SIZE_EXT, + AL_ENV_ROOM_VOLUME_EXT, +}; + +enum { + // sample level environment: + AL_ENV_SAMPLE_REVERB_MIX_EXT = 0, + AL_ENV_SAMPLE_DIRECT_EXT, + AL_ENV_SAMPLE_DIRECT_HF_EXT, + AL_ENV_SAMPLE_ROOM_EXT, + AL_ENV_SAMPLE_ROOM_HF_EXT, + AL_ENV_SAMPLE_OBSTRUCTION_EXT, + AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, + AL_ENV_SAMPLE_OCCLUSION_EXT, + AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, + AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, + AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, + AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, + AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, + AL_ENV_SAMPLE_FLAGS_EXT, + + AL_ENV_SAMPLE_COUNT, +}; + +// room types: same as miles/eax +enum { + AL_ENVIRONMENT_GENERIC = 0, + AL_ENVIRONMENT_PADDEDCELL, + AL_ENVIRONMENT_ROOM, + AL_ENVIRONMENT_BATHROOM, + AL_ENVIRONMENT_LIVINGROOM, + AL_ENVIRONMENT_STONEROOM, + AL_ENVIRONMENT_AUDITORIUM, + AL_ENVIRONMENT_CONCERTHALL, + AL_ENVIRONMENT_CAVE, + AL_ENVIRONMENT_ARENA, + AL_ENVIRONMENT_HANGAR, + AL_ENVIRONMENT_CARPETEDHALLWAY, + AL_ENVIRONMENT_HALLWAY, + AL_ENVIRONMENT_STONECORRIDOR, + AL_ENVIRONMENT_ALLEY, + AL_ENVIRONMENT_FOREST, + AL_ENVIRONMENT_CITY, + AL_ENVIRONMENT_MOUNTAINS, + AL_ENVIRONMENT_QUARRY, + AL_ENVIRONMENT_PLAIN, + AL_ENVIRONMENT_PARKINGLOT, + AL_ENVIRONMENT_SEWERPIPE, + AL_ENVIRONMENT_UNDERWATER, + AL_ENVIRONMENT_DRUGGED, + AL_ENVIRONMENT_DIZZY, + AL_ENVIRONMENT_PSYCHOTIC, + + AL_ENVIRONMENT_COUNT +}; + +// declare OpenAL functions +#define AL_EXTENSION(ext_name) extern bool gDoesSupport_##ext_name; +#define AL_FUNCTION(fn_return,fn_name,fn_args) extern fn_return (FN_CDECL *fn_name)fn_args; +#define AL_EXT_FUNCTION(ext_name,fn_return,fn_name,fn_args) extern fn_return (FN_CDECL *fn_name)fn_args; +#ifndef _OPENALFN_H_ +#include +#endif + +namespace Audio +{ + +bool libraryInit(const char *library); +void libraryInitExtensions(); +void libraryShutdown(); + +inline bool doesSupportIASIG() +{ + return gDoesSupport_AL_EXT_IASIG; +} + +inline bool doesSupportDynamix() +{ + return gDoesSupport_AL_EXT_DYNAMIX; +} + +// helpers +F32 DBToLinear(F32 value); +F32 linearToDB(F32 value); + +} // end namespace Audio + + +#endif // _H_PLATFORMAL_ diff --git a/platformX86UNIX/platformX86UNIX.h b/platformX86UNIX/platformX86UNIX.h new file mode 100644 index 0000000..6ff04b8 --- /dev/null +++ b/platformX86UNIX/platformX86UNIX.h @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMX86UNIX_H_ +#define _PLATFORMX86UNIX_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +#include +#include + +// these will be used to construct the user preference directory where +// created files will be stored (~/PREF_DIR_ROOT/PREF_DIR_GAME_NAME) +#define PREF_DIR_ROOT ".garagegames" +#define PREF_DIR_GAME_NAME "torqueDemo" + +// event codes for custom SDL events +const S32 TORQUE_SETVIDEOMODE = 1; + +extern bool GL_EXT_Init( void ); + +extern void PlatformBlitInit( void ); + +// Process Control functions +void Cleanup(bool minimal=false); +void ImmediateShutdown(S32 exitCode, S32 signalNum = 0); +void ProcessControlInit(); +bool AcquireProcessMutex(const char *mutexName); + +// Utility functions +// Convert a string to lowercase in place +char *strtolwr(char* str); + +void DisplayErrorAlert(const char* errMsg, bool showSDLError = true); + +// Just like strstr, except case insensitive +// (Found this function at http://www.codeguru.com/string/stristr.html) +extern char *stristr(char *szStringToBeSearched, const char *szSubstringToSearchFor); + +extern "C" +{ + // x86UNIX doesn't have a way to automatically get the executable file name + void setExePathName(const char* exePathName); +} +#endif diff --git a/platformX86UNIX/threads/mutex.cpp b/platformX86UNIX/threads/mutex.cpp new file mode 100644 index 0000000..1e79622 --- /dev/null +++ b/platformX86UNIX/threads/mutex.cpp @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/threads/mutex.h" +#include "core/util/safeDelete.h" + +#include +#include +#include +#include +#include + +struct PlatformMutexData +{ + pthread_mutex_t mutex; +}; + +Mutex::Mutex() +{ + mData = new PlatformMutexData; + pthread_mutexattr_t attr; + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&mData->mutex, &attr); +} + +Mutex::~Mutex() +{ + AssertFatal(mData, "Mutex::destroyMutex: invalid mutex"); + pthread_mutex_destroy(&mData->mutex); + SAFE_DELETE(mData); +} + +bool Mutex::lock(bool block) +{ + if(mData == NULL) + return false; + if(block) + { + return pthread_mutex_lock(&mData->mutex) == 0; + } + else + { + return pthread_mutex_trylock(&mData->mutex) == 0; + } +} + +void Mutex::unlock() +{ + if(mData == NULL) + return; + pthread_mutex_unlock(&mData->mutex); +} diff --git a/platformX86UNIX/threads/semaphore.cpp b/platformX86UNIX/threads/semaphore.cpp new file mode 100644 index 0000000..629da59 --- /dev/null +++ b/platformX86UNIX/threads/semaphore.cpp @@ -0,0 +1,61 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/threads/semaphore.h" +// Instead of that mess that was here before, lets use the SDL lib to deal +// with the semaphores. + +#include +#include + +struct PlatformSemaphore +{ + SDL_sem *semaphore; + + PlatformSemaphore(S32 initialCount) + { + semaphore = SDL_CreateSemaphore(initialCount); + AssertFatal(semaphore, "PlatformSemaphore constructor - Failed to create SDL Semaphore."); + } + + ~PlatformSemaphore() + { + SDL_DestroySemaphore(semaphore); + } +}; + +Semaphore::Semaphore(S32 initialCount) +{ + mData = new PlatformSemaphore(initialCount); +} + +Semaphore::~Semaphore() +{ + AssertFatal(mData, "Semaphore destructor - Invalid semaphore."); + delete mData; +} + +bool Semaphore::acquire(bool block) +{ + AssertFatal(mData, "Semaphore::acquire - Invalid semaphore."); + if (block) + { + if (SDL_SemWait(mData->semaphore) < 0) + AssertFatal(false, "Semaphore::acquie - Wait failed."); + return (true); + } + else + { + int res = SDL_SemTryWait(mData->semaphore); + return (res == 0); + } +} + +void Semaphore::release() +{ + AssertFatal(mData, "Semaphore::releaseSemaphore - Invalid semaphore."); + SDL_SemPost(mData->semaphore); +} diff --git a/platformX86UNIX/threads/thread.cpp b/platformX86UNIX/threads/thread.cpp new file mode 100644 index 0000000..ec7c95b --- /dev/null +++ b/platformX86UNIX/threads/thread.cpp @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "platform/threads/thread.h" +#include "platform/threads/semaphore.h" +#include "platform/threads/mutex.h" +#include + +class PlatformThreadData +{ +public: + ThreadRunFunction mRunFunc; + void* mRunArg; + Thread* mThread; + Semaphore mGateway; // default count is 1 + pthread_t mThreadID; + bool mDead; +}; + +ThreadManager::MainThreadId ThreadManager::smMainThreadId; + +//----------------------------------------------------------------------------- +// Function: ThreadRunHandler +// Summary: Calls Thread::run() with the thread's specified run argument. +// Neccesary because Thread::run() is provided as a non-threaded +// way to execute the thread's run function. So we have to keep +// track of the thread's lock here. +static void *ThreadRunHandler(void * arg) +{ + PlatformThreadData *mData = reinterpret_cast(arg); + Thread *thread = mData->mThread; + + // mThreadID is filled in twice, once here and once in pthread_create(). + // We fill in mThreadID here as well as in pthread_create() because addThread() + // can execute before pthread_create() returns and sets mThreadID. + // The value from pthread_create() and pthread_self() are guaranteed to be equivalent (but not identical) + mData->mThreadID = pthread_self(); + + ThreadManager::addThread(thread); + thread->run(mData->mRunArg); + ThreadManager::removeThread(thread); + + bool autoDelete = thread->autoDelete; + + mData->mThreadID = 0; + mData->mDead = true; + mData->mGateway.release(); + + if( autoDelete ) + delete thread; + + // return value for pthread lib's benefit + return NULL; + // the end of this function is where the created pthread will die. +} + +//----------------------------------------------------------------------------- +Thread::Thread(ThreadRunFunction func, void* arg, bool start_thread, bool autodelete) +{ + AssertFatal( !start_thread, "Thread::Thread() - auto-starting threads from ctor has been disallowed since the run() method is virtual" ); + + mData = new PlatformThreadData; + mData->mRunFunc = func; + mData->mRunArg = arg; + mData->mThread = this; + mData->mThreadID = 0; + mData->mDead = false; + autoDelete = autodelete; +} + +Thread::~Thread() +{ + stop(); + if( isAlive() ) + join(); + + delete mData; +} + +void Thread::start( void* arg ) +{ + // cause start to block out other pthreads from using this Thread, + // at least until ThreadRunHandler exits. + mData->mGateway.acquire(); + + // reset the shouldStop flag, so we'll know when someone asks us to stop. + shouldStop = false; + + mData->mDead = false; + + if( !mData->mRunArg ) + mData->mRunArg = arg; + + pthread_create(&mData->mThreadID, NULL, ThreadRunHandler, mData); +} + +bool Thread::join() +{ + // not using pthread_join here because pthread_join cannot deal + // with multiple simultaneous calls. + + mData->mGateway.acquire(); + AssertFatal( !isAlive(), "Thread::join() - thread not dead after join()" ); + mData->mGateway.release(); + + return true; +} + +void Thread::run(void* arg) +{ + if(mData->mRunFunc) + mData->mRunFunc(arg); +} + +bool Thread::isAlive() +{ + return ( !mData->mDead ); +} + +U32 Thread::getId() +{ + return (U32)mData->mThreadID; +} + +void Thread::_setName( const char* ) +{ + // Not supported. Wading through endless lists of Thread-1, Thread-2, Thread-3, ... trying to find + // that one thread you are looking for is just so much fun. +} + +U32 ThreadManager::getCurrentThreadId() +{ + return (U32)pthread_self(); +} + +bool ThreadManager::compare(U32 threadId_1, U32 threadId_2) +{ + return pthread_equal((pthread_t)threadId_1, (pthread_t)threadId_2); +} diff --git a/platformX86UNIX/x86UNIXAsmBlit.cpp b/platformX86UNIX/x86UNIXAsmBlit.cpp new file mode 100644 index 0000000..ce29050 --- /dev/null +++ b/platformX86UNIX/x86UNIXAsmBlit.cpp @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMath.h" +#include "gfx/bitmap/bitmapUtils.h" + +//-------------------------------------------------------------------------- +void bitmapExtrude5551_asm(const void *srcMip, void *mip, U32 height, U32 width) +{ + const U16 *src = (const U16 *) srcMip; + U16 *dst = (U16 *) mip; + U32 stride = width << 1; + + for(U32 y = 0; y < height; y++) + { + for(U32 x = 0; x < width; x++) + { + U32 a = src[0]; + U32 b = src[1]; + U32 c = src[stride]; + U32 d = src[stride+1]; + dst[x] = ((((a >> 11) + (b >> 11) + (c >> 11) + (d >> 11)) >> 2) << 11) | + ((( ((a >> 6) & 0x1f) + ((b >> 6) & 0x1f) + ((c >> 6) & 0x1f) + ((d >> 6) & 0x1F) ) >> 2) << 6) | + ((( ((a >> 1) & 0x1F) + ((b >> 1) & 0x1F) + ((c >> 1) & 0x1f) + ((d >> 1) & 0x1f)) >> 2) << 1); + src += 2; + } + src += stride; + dst += width; + } +} + +//-------------------------------------------------------------------------- +void PlatformBlitInit() +{ + bitmapExtrude5551 = bitmapExtrude5551_asm; + bitmapExtrudeRGB = bitmapExtrudeRGB_c; + + if (Platform::SystemInfo.processor.properties & CPU_PROP_MMX) + { + // JMQ: haven't bothered porting mmx bitmap funcs because they don't + // seem to offer a big performance boost right now. + } +} + diff --git a/platformX86UNIX/x86UNIXCPUInfo.cpp b/platformX86UNIX/x86UNIXCPUInfo.cpp new file mode 100644 index 0000000..287f7a0 --- /dev/null +++ b/platformX86UNIX/x86UNIXCPUInfo.cpp @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "console/console.h" +#include "core/stringTable.h" +#include "core/strings/stringFunctions.h" +#include + +Platform::SystemInfo_struct Platform::SystemInfo; + +extern void PlatformBlitInit(); +extern void SetProcessorInfo(Platform::SystemInfo_struct::Processor& pInfo, + char* vendor, U32 processor, U32 properties); // platform/platformCPU.cc + +// asm cpu detection routine from platform code +extern "C" +{ +void detectX86CPUInfo(char *vendor, U32 *processor, U32 *properties); +} + +/* used in the asm */ +static U32 time[2]; +static char vendor[13] = {0,}; +static U32 properties = 0; +static U32 processor = 0; +U32 clockticks = 0; +U32 timeHi = 0; +U32 timeLo = 0; + +void Processor::init() +{ + // Reference: + // www.cyrix.com + // www.amd.com + // www.intel.com + // http://developer.intel.com/design/PentiumII/manuals/24512701.pdf + Platform::SystemInfo.processor.type = CPU_X86Compatible; + Platform::SystemInfo.processor.name = StringTable->insert("Unknown x86 Compatible"); + Platform::SystemInfo.processor.mhz = 0; + Platform::SystemInfo.processor.properties = CPU_PROP_C; + + clockticks = properties = processor = time[0] = 0; + dStrcpy(vendor, ""); + + detectX86CPUInfo(vendor, &processor, &properties); + SetProcessorInfo(Platform::SystemInfo.processor, + vendor, processor, properties); + + //-------------------------------------- + // if RDTSC support calculate the aproximate Mhz of the CPU + if (Platform::SystemInfo.processor.properties & CPU_PROP_RDTSC && + Platform::SystemInfo.processor.properties & CPU_PROP_FPU) + { + const U32 MS_INTERVAL = 750; + +#if defined(TORQUE_COMPILER_GCC) && ((__GNUC__ >= 3) && (__GNUC_MINOR__ >=4)) || ((__GNUC__ >= 4) && (__GNUC_MINOR__ >=0)) + asm("rdtsc" : "=a" (timeLo), "=d" (timeHi)); +#else + __asm__( + "pushl %eax\n" + "pushl %edx\n" + "rdtsc\n" + "movl %eax, (time)\n" + "movl %edx, (time+4)\n" + "popl %edx\n" + "popl %eax\n" + ); +#endif + U32 ms = Platform::getRealMilliseconds(); + while ( Platform::getRealMilliseconds() < ms+MS_INTERVAL ) + { /* empty */ } + ms = Platform::getRealMilliseconds()-ms; +#if defined(TORQUE_COMPILER_GCC) && ((__GNUC__ >= 3) && (__GNUC_MINOR__ >= 4)) || ((__GNUC__ >= 4) && (__GNUC_MINOR__ >=0)) + asm( + "pushl %eax\n" + "pushl %edx\n" + "rdtsc\n" + "sub (timeHi), %edx\n" + "sbb (timeLo), %eax\n" + "mov %eax, (clockticks)\n" + "popl %edx\n" + "popl %eax\n" + ); +#else + asm( + "pushl %eax\n" + "pushl %edx\n" + "rdtsc\n" + "sub (time+4), %edx\n" + "sbb (time), %eax\n" + "mov %eax, (clockticks)\n" + "popl %edx\n" + "popl %eax\n" + ); +#endif + U32 mhz = static_cast(F32(clockticks) / F32(ms) / 1000.0f); + + // catch-22 the timing method used above to calc Mhz is generally + // wrong by a few percent so we want to round to the nearest clock + // multiple but we also want to be careful to not touch overclocked + // results + + // measure how close the Raw Mhz number is to the center of each clock + // bucket + U32 bucket25 = mhz % 25; + U32 bucket33 = mhz % 33; + U32 bucket50 = mhz % 50; + + if (bucket50 < 8 || bucket50 > 42) + Platform::SystemInfo.processor.mhz = + U32((mhz+(50.0f/2.0f))/50.0f) * 50; + else if (bucket25 < 5 || bucket25 > 20) + Platform::SystemInfo.processor.mhz = + U32((mhz+(25.0f/2.0f))/25.0f) * 25; + else if (bucket33 < 5 || bucket33 > 28) + Platform::SystemInfo.processor.mhz = + U32((mhz+(33.0f/2.0f))/33.0f) * 33; + else + Platform::SystemInfo.processor.mhz = U32(mhz); + } + + Con::printf("Processor Init:"); + Con::printf(" %s, %d Mhz", Platform::SystemInfo.processor.name, Platform::SystemInfo.processor.mhz); + if (Platform::SystemInfo.processor.properties & CPU_PROP_FPU) + Con::printf(" FPU detected"); + if (Platform::SystemInfo.processor.properties & CPU_PROP_MMX) + Con::printf(" MMX detected"); + if (Platform::SystemInfo.processor.properties & CPU_PROP_3DNOW) + Con::printf(" 3DNow detected"); + if (Platform::SystemInfo.processor.properties & CPU_PROP_SSE) + Con::printf(" SSE detected"); + Con::printf(" "); + + PlatformBlitInit(); +} diff --git a/platformX86UNIX/x86UNIXConsole.cpp b/platformX86UNIX/x86UNIXConsole.cpp new file mode 100644 index 0000000..38d17a9 --- /dev/null +++ b/platformX86UNIX/x86UNIXConsole.cpp @@ -0,0 +1,387 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXStdConsole.h" +#include "platformX86UNIX/x86UNIXUtils.h" +#include "platform/event.h" +#include "platform/platform.h" +#include "core/util/rawData.h" +#include "core/strings/stringFunctions.h" +#include "core/util/journal/process.h" + +#include +#include +#include +#include + +StdConsole *stdConsole = NULL; + +ConsoleFunction(enableWinConsole, void, 2, 2, "enableWinConsole(bool);") +{ + argc; + if (stdConsole) + stdConsole->enable(dAtob(argv[1])); +} + +void StdConsole::create() +{ + if (stdConsole == NULL) + stdConsole = new StdConsole(); +} + +void StdConsole::destroy() +{ + if (stdConsole && stdConsole->isEnabled()) + stdConsole->enable(false); + + delete stdConsole; + stdConsole = NULL; +} + +static void signalHandler(int sigtype) +{ + if (sigtype == SIGCONT && stdConsole != NULL) + stdConsole->resetTerminal(); +} + +void StdConsole::resetTerminal() +{ + if (stdConsoleEnabled) + { + /* setup the proper terminal modes */ + struct termios termModes; + tcgetattr(stdIn, &termModes); + termModes.c_lflag &= ~ICANON; // enable non-canonical mode + termModes.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE); + termModes.c_cc[VMIN] = 0; + termModes.c_cc[VTIME] = 5; + tcsetattr(stdIn, TCSAFLUSH, &termModes); + } +} + +void StdConsole::enable(bool enabled) +{ + if (enabled && !stdConsoleEnabled) + { + stdConsoleEnabled = true; + + // install signal handler for sigcont + signal(SIGCONT, &signalHandler); + + // save the terminal state + if (originalTermState == NULL) + originalTermState = new termios; + + tcgetattr(stdIn, originalTermState); + + // put the terminal into our preferred mode + resetTerminal(); + + printf("%s", Con::getVariable("Con::Prompt")); + + } + else if (!enabled && stdConsoleEnabled) + { + stdConsoleEnabled = false; + + // uninstall signal handler + signal(SIGCONT, SIG_DFL); + + // reset the original terminal state + if (originalTermState != NULL) + tcsetattr(stdIn, TCSANOW, originalTermState); + } +} + +bool StdConsole::isEnabled() +{ + if ( stdConsole ) + return stdConsole->stdConsoleEnabled; + + return false; +} + +static void stdConsoleConsumer(ConsoleLogEntry::Level, const char *line) +{ + stdConsole->processConsoleLine(line); +} + +StdConsole::StdConsole() +{ + for (S32 iIndex = 0; iIndex < MAX_CMDS; iIndex ++) + rgCmds[iIndex][0] = '\0'; + + stdOut = dup(1); + stdIn = dup(0); + stdErr = dup(2); + + iCmdIndex = 0; + stdConsoleEnabled = false; + Con::addConsumer(stdConsoleConsumer); + inpos = 0; + lineOutput = false; + inBackground = false; + originalTermState = NULL; + + Process::notify(this, &StdConsole::process, PROCESS_LAST_ORDER); +} + +StdConsole::~StdConsole() +{ + Con::removeConsumer(stdConsoleConsumer); + + if (stdConsoleEnabled) + enable(false); + + if (originalTermState != NULL) + { + delete originalTermState; + originalTermState = NULL; + } +} + +void StdConsole::printf(const char *s, ...) +{ + // Get the line into a buffer. + static const int BufSize = 4096; + static char buffer[BufSize]; + va_list args; + va_start(args, s); + vsnprintf(buffer, BufSize, s, args); + // Replace tabs with carats, like the "real" console does. + char *pos = buffer; + while (*pos) { + if (*pos == '\t') { + *pos = '^'; + } + pos++; + } + // Axe the color characters. + Con::stripColorChars(buffer); + // Print it. + write(stdOut, buffer, strlen(buffer)); + fflush(stdout); +} + +void StdConsole::processConsoleLine(const char *consoleLine) +{ + if(stdConsoleEnabled) + { + inbuf[inpos] = 0; + if(lineOutput) + printf("%s\n", consoleLine); + else + printf("%c%s\n%s%s", '\r', consoleLine, Con::getVariable("Con::Prompt"), inbuf); + } +} + +void StdConsole::process() +{ + if(stdConsoleEnabled) + { + //U16 key; + char typedData[64]; // damn, if you can type this fast... :-D + + if (UUtils->inBackground()) + // we don't have the terminal + inBackground = true; + else + { + // if we were in the background, reset the terminal + if (inBackground) + resetTerminal(); + inBackground = false; + } + + // see if stdIn has any input waiting + // mojo for select call + fd_set rfds; + struct timeval tv; + int retval; + FD_ZERO(&rfds); + FD_SET(stdIn, &rfds); + // don't wait at all in select + tv.tv_sec = 0; + tv.tv_usec = 0; + + int numEvents = select(stdIn+1, &rfds, NULL, NULL, &tv); + if (numEvents <= 0) + // no data available + return; + + numEvents = read(stdIn, typedData, 64); + if (numEvents == -1) + return; + + typedData[numEvents] = '\0'; + if (numEvents > 0) + { + char outbuf[512]; + S32 outpos = 0; + + for (int i = 0; i < numEvents; i++) + { + switch(typedData[i]) + { + case 8: + case 127: + /* backspace */ + if (inpos > 0) + { + outbuf[outpos++] = '\b'; + outbuf[outpos++] = ' '; + outbuf[outpos++] = '\b'; + inpos--; + } + break; + + // XXX Don't know if we can detect shift-TAB. So, only handling + // TAB for now. + + case '\t': + // In the output buffer, we're going to have to erase the current line (in case + // we're cycling through various completions) and write out the whole input + // buffer, so (inpos * 3) + complen <= 512. Should be OK. The input buffer is + // also 512 chars long so that constraint will also be fine for the input buffer. + { + // Erase the current line. + U32 i; + for (i = 0; i < inpos; i++) { + outbuf[outpos++] = '\b'; + outbuf[outpos++] = ' '; + outbuf[outpos++] = '\b'; + } + // Modify the input buffer with the completion. + U32 maxlen = 512 - (inpos * 3); + inpos = Con::tabComplete(inbuf, inpos, maxlen, true); + // Copy the input buffer to the output. + for (i = 0; i < inpos; i++) { + outbuf[outpos++] = inbuf[i]; + } + } + break; + + case '\n': + case '\r': + /* new line */ + outbuf[outpos++] = '\n'; + + inbuf[inpos] = 0; + outbuf[outpos] = 0; + printf("%s", outbuf); + + S32 eventSize; + eventSize = 1; + + { + RawData rd; + rd.size = inpos + 1; + rd.data = (S8*) inbuf; + + Con::smConsoleInput.trigger(rd); + } + + // If we've gone off the end of our array, wrap + // back to the beginning + if (iCmdIndex >= MAX_CMDS) + iCmdIndex %= MAX_CMDS; + + // Put the new command into the array + strcpy(rgCmds[iCmdIndex ++], inbuf); + + printf("%s", Con::getVariable("Con::Prompt")); + inpos = outpos = 0; + break; + case 27: + // JMQTODO: are these magic numbers keyboard map specific? + if (typedData[i+1] == 91 || typedData[i+1] == 79) + { + i += 2; + // an arrow key was pressed. + switch(typedData[i]) + { + case 'A': + /* up arrow */ + // Go to the previous command in the cyclic array + if ((-- iCmdIndex) < 0) + iCmdIndex = MAX_CMDS - 1; + + // If this command isn't empty ... + if (rgCmds[iCmdIndex][0] != '\0') + { + // Obliterate current displayed text + for (S32 i = outpos = 0; i < inpos; i ++) + { + outbuf[outpos++] = '\b'; + outbuf[outpos++] = ' '; + outbuf[outpos++] = '\b'; + } + + // Copy command into command and display buffers + for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos++, outpos++) + { + outbuf[outpos] = rgCmds[iCmdIndex][inpos]; + inbuf [inpos ] = rgCmds[iCmdIndex][inpos]; + } + } + // If previous is empty, stay on current command + else if ((++ iCmdIndex) >= MAX_CMDS) + { + iCmdIndex = 0; + } + break; + case 'B': + /* down arrow */ + // Go to the next command in the command array, if + // it isn't empty + if (rgCmds[iCmdIndex][0] != '\0' && (++ iCmdIndex) >= MAX_CMDS) + iCmdIndex = 0; + + // Obliterate current displayed text + for (S32 i = outpos = 0; i < inpos; i ++) + { + outbuf[outpos++] = '\b'; + outbuf[outpos++] = ' '; + outbuf[outpos++] = '\b'; + } + + // Copy command into command and display buffers + for (inpos = 0; inpos < (S32)strlen(rgCmds[iCmdIndex]); inpos++, outpos++) + { + outbuf[outpos] = rgCmds[iCmdIndex][inpos]; + inbuf [inpos ] = rgCmds[iCmdIndex][inpos]; + } + break; + case 'C': + /* right arrow */ + break; + case 'D': + /* left arrow */ + break; + } + // read again to get rid of a bad char. + //read(stdIn, &key, sizeof(char)); + break; + } else { + inbuf[inpos++] = typedData[i]; + outbuf[outpos++] = typedData[i]; + break; + } + break; + default: + inbuf[inpos++] = typedData[i]; + outbuf[outpos++] = typedData[i]; + break; + } + } + if (outpos) + { + outbuf[outpos] = 0; + printf("%s", outbuf); + } + } + } +} diff --git a/platformX86UNIX/x86UNIXFileio.cpp b/platformX86UNIX/x86UNIXFileio.cpp new file mode 100644 index 0000000..caf10de --- /dev/null +++ b/platformX86UNIX/x86UNIXFileio.cpp @@ -0,0 +1,1247 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + /* JMQ: + + Here's the scoop on unix file IO. The windows platform makes some + assumptions about fileio: 1) the file system is case-insensitive, and + 2) the platform can write to the directory in which + the game is running. Both of these are usually false on linux. So, to + compensate, we "route" created files and directories to the user's home + directory (see GetPrefPath()). When a file is to be accessed, the code + looks in the home directory first. If the file is not found there and the + open mode is read only, the code will look in the game installation + directory. Files are never created or modified in the game directory. + + For case-sensitivity, the MungePath code will test whether a given path + specified by the engine exists. If not, it will use the MungeCase function + which will try to determine if an actual filesystem path matches the + specified path case insensitive. If one is found, the actual path + transparently (we hope) replaces the one requested by the engine. + + The preference directory is global to all torque executables with the same + name. You should make sure you keep it clean if you build from multiple + torque development trees. + */ + + // evil hack to get around insane X windows #define-happy header files + #ifdef Status + #undef Status + #endif + + #include "platformX86UNIX/platformX86UNIX.h" + #include "core/fileio.h" + #include "core/util/tVector.h" + #include "core/stringTable.h" + #include "console/console.h" + #include "core/strings/stringFunctions.h" + #include "util/tempAlloc.h" + #include "cinterface/cinterface.h" + + #if defined(__FreeBSD__) + #include + #endif + #include + + /* these are for reading directors, getting stats, etc. */ + #include + #include + #include + #include + #include + #include + #include + + extern int x86UNIXOpen(const char *path, int oflag); + extern int x86UNIXClose(int fd); + extern ssize_t x86UNIXRead(int fd, void *buf, size_t nbytes); + extern ssize_t x86UNIXWrite(int fd, const void *buf, size_t nbytes); + + const int MaxPath = PATH_MAX; + + namespace + { + const char sTempDir[] = "/tmp/"; + + static char sBinPathName[MaxPath] = ""; + static char *sBinName = sBinPathName; + bool sUseRedirect = true; + } + + StringTableEntry osGetTemporaryDirectory() + { + return StringTable->insert(sTempDir); + } + + // Various handy utility functions: + //------------------------------------------------------------------------------ + // find all \ in a path and convert them in place to / + static void ForwardSlash(char *str) + { + while(*str) + { + if(*str == '\\') + *str = '/'; + str++; + } + } + + //------------------------------------------------------------------------------ + // copy a file from src to dest + static bool CopyFile(const char* src, const char* dest) + { + S32 srcFd = x86UNIXOpen(src, O_RDONLY); + S32 destFd = x86UNIXOpen(dest, O_WRONLY | O_CREAT | O_TRUNC); + bool error = false; + + if (srcFd != -1 && destFd != -1) + { + const int BufSize = 8192; + char buf[BufSize]; + S32 bytesRead = 0; + while ((bytesRead = x86UNIXRead(srcFd, buf, BufSize)) > 0) + { + // write data + if (x86UNIXWrite(destFd, buf, bytesRead) == -1) + { + error = true; + break; + } + } + + if (bytesRead == -1) + error = true; + } + + if (srcFd != -1) + x86UNIXClose(srcFd); + if (destFd != -1) + x86UNIXClose(destFd); + + if (error) + { + Con::errorf("Error copying file: %s, %s", src, dest); + remove(dest); + } + return error; + } + +bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite) +{ + return CopyFile(fromName,toName); +} + + //----------------------------------------------------------------------------- + static char sgPrefDir[MaxPath]; + static bool sgPrefDirInitialized = false; + + // get the "pref dir", which is where game output files are stored. the pref + // dir is ~/PREF_DIR_ROOT/PREF_DIR_GAME_NAME + static const char* GetPrefDir() + { + if (sgPrefDirInitialized) + return sgPrefDir; + + if (sUseRedirect) + { + const char *home = getenv("HOME"); + AssertFatal(home, "HOME environment variable must be set"); + + dSprintf(sgPrefDir, MaxPath, "%s/%s/%s", + home, PREF_DIR_ROOT, PREF_DIR_GAME_NAME); + } + else + { + getcwd(sgPrefDir, MaxPath); + } + + sgPrefDirInitialized = true; + return sgPrefDir; + } + + //------------------------------------------------------------------------------ + // munge the case of the specified pathName. This means try to find the actual + // filename in with case-insensitive matching on the specified pathName, and + // store the actual found name. + static void MungeCase(char* pathName, S32 pathNameSize) + { + char tempBuf[MaxPath]; + dStrncpy(tempBuf, pathName, pathNameSize); + + AssertFatal(pathName[0] == '/', "PATH must be absolute"); + + struct stat filestat; + const int MaxPathEl = 200; + char *currChar = pathName; + char testPath[MaxPath]; + char pathEl[MaxPathEl]; + bool done = false; + + dStrncpy(tempBuf, "/", MaxPath); + currChar++; + + while (!done) + { + char* termChar = dStrchr(currChar, '/'); + if (termChar == NULL) + termChar = dStrchr(currChar, '\0'); + AssertFatal(termChar, "Can't find / or NULL terminator"); + + S32 pathElLen = (termChar - currChar); + dStrncpy(pathEl, currChar, pathElLen); + pathEl[pathElLen] = '\0'; + dStrncpy(testPath, tempBuf, MaxPath); + dStrcat(testPath, pathEl); + if (stat(testPath, &filestat) != -1) + { + dStrncpy(tempBuf, testPath, MaxPath); + } + else + { + DIR *dir = opendir(tempBuf); + struct dirent* ent; + bool foundMatch = false; + while (dir != NULL && (ent = readdir(dir)) != NULL) + { + if (dStricmp(pathEl, ent->d_name) == 0) + { + foundMatch = true; + dStrcat(tempBuf, ent->d_name); + break; + } + } + + if (!foundMatch) + dStrncpy(tempBuf, testPath, MaxPath); + if (dir) + closedir(dir); + } + if (*termChar == '/') + { + dStrcat(tempBuf, "/"); + termChar++; + currChar = termChar; + } + else + done = true; + } + + dStrncpy(pathName, tempBuf, pathNameSize); + } + + //----------------------------------------------------------------------------- + // Returns true if the pathname exists, false otherwise. If isFile is true, + // the pathname is assumed to be a file path, and only the directory part + // will be examined (everything before last /) + bool DirExists(char* pathname, bool isFile) + { + static char testpath[20000]; + dStrncpy(testpath, pathname, sizeof(testpath)); + if (isFile) + { + // find the last / and make it into null + char* lastSlash = dStrrchr(testpath, '/'); + if (lastSlash != NULL) + *lastSlash = 0; + } + return Platform::isDirectory(testpath); + } + + //----------------------------------------------------------------------------- + // Munge the specified path. + static void MungePath(char* dest, S32 destSize, + const char* src, const char* absolutePrefix) + { + char tempBuf[MaxPath]; + dStrncpy(dest, src, MaxPath); + + // translate all \ to / + ForwardSlash(dest); + + // if it is relative, make it absolute with the absolutePrefix + if (dest[0] != '/') + { + AssertFatal(absolutePrefix, "Absolute Prefix must not be NULL"); + + dSprintf(tempBuf, MaxPath, "%s/%s", + absolutePrefix, dest); + + // copy the result back into dest + dStrncpy(dest, tempBuf, destSize); + } + + // if the path exists, we're done + struct stat filestat; + if (stat(dest, &filestat) != -1) + return; + + // otherwise munge the case of the path + MungeCase(dest, destSize); + } + + //----------------------------------------------------------------------------- + enum + { + TOUCH, + DELETE + }; + + //----------------------------------------------------------------------------- + // perform a modification on the specified file. allowed modifications are + // specified in the enum above. + bool ModifyFile(const char * name, S32 modType) + { + if(!name || (dStrlen(name) >= MaxPath) || dStrstr(name, "../") != NULL) + return(false); + + // if its absolute skip it + if (name[0]=='/' || name[0]=='\\') + return(false); + + // only modify files in home directory + char prefPathName[MaxPath]; + MungePath(prefPathName, MaxPath, name, GetPrefDir()); + + if (modType == TOUCH) + return(utime(prefPathName, 0) != -1); + else if (modType == DELETE) + return (remove(prefPathName) != -1); + else + AssertFatal(false, "Unknown File Mod type"); + return false; + } + + //----------------------------------------------------------------------------- + static bool RecurseDumpPath(const char *path, const char* relativePath, const char *pattern, Vector &fileVector) + { + char search[1024]; + + dSprintf(search, sizeof(search), "%s", path, pattern); + + DIR *directory = opendir(search); + + if (directory == NULL) + return false; + + struct dirent *fEntry; + fEntry = readdir(directory); // read the first "file" in the directory + + if (fEntry == NULL) + return false; + + do + { + char filename[BUFSIZ+1]; + struct stat fStat; + + dSprintf(filename, sizeof(filename), "%s/%s", search, fEntry->d_name); // "construct" the file name + stat(filename, &fStat); // get the file stats + + if ( (fStat.st_mode & S_IFMT) == S_IFDIR ) + { + // Directory + // skip . and .. directories + if (dStrcmp(fEntry->d_name, ".") == 0 || dStrcmp(fEntry->d_name, "..") == 0) + continue; + + // skip excluded directories + if( Platform::isExcludedDirectory(fEntry->d_name)) + continue; + + + char child[MaxPath]; + dSprintf(child, sizeof(child), "%s/%s", path, fEntry->d_name); + char* childRelative = NULL; + char childRelativeBuf[MaxPath]; + if (relativePath) + { + dSprintf(childRelativeBuf, sizeof(childRelativeBuf), "%s/%s", + relativePath, fEntry->d_name); + childRelative = childRelativeBuf; + } + RecurseDumpPath(child, childRelative, pattern, fileVector); + } + else + { + // File + + // add it to the list + fileVector.increment(); + Platform::FileInfo& rInfo = fileVector.last(); + + if (relativePath) + rInfo.pFullPath = StringTable->insert(relativePath); + else + rInfo.pFullPath = StringTable->insert(path); + rInfo.pFileName = StringTable->insert(fEntry->d_name); + rInfo.fileSize = fStat.st_size; + //dPrintf("Adding file: %s/%s\n", rInfo.pFullPath, rInfo.pFileName); + } + + } while( (fEntry = readdir(directory)) != NULL ); + + closedir(directory); + return true; + } + + //----------------------------------------------------------------------------- + bool dFileDelete(const char * name) + { + return ModifyFile(name, DELETE); + } + + //----------------------------------------------------------------------------- + bool dFileTouch(const char * name) + { + return ModifyFile(name, TOUCH); + } + + bool dFileRename(const char *oldName, const char *newName) + { + AssertFatal( oldName != NULL && newName != NULL, "dFileRename - NULL file name" ); + + // only modify files in home directory + TempAlloc oldPrefPathName(MaxPath); + TempAlloc newPrefPathName(MaxPath); + MungePath(oldPrefPathName, MaxPath, oldName, GetPrefDir()); + MungePath(newPrefPathName, MaxPath, newName, GetPrefDir()); + + return rename(oldPrefPathName, newPrefPathName) == 0; + } + + //----------------------------------------------------------------------------- + // Constructors & Destructor + //----------------------------------------------------------------------------- + + //----------------------------------------------------------------------------- + // After construction, the currentStatus will be Closed and the capabilities + // will be 0. + //----------------------------------------------------------------------------- + File::File() + : currentStatus(Closed), capability(0) + { + // AssertFatal(sizeof(int) == sizeof(void *), "File::File: cannot cast void* to int"); + + handle = (void *)NULL; + } + + //----------------------------------------------------------------------------- + // insert a copy constructor here... (currently disabled) + //----------------------------------------------------------------------------- + + //----------------------------------------------------------------------------- + // Destructor + //----------------------------------------------------------------------------- + File::~File() + { + close(); + handle = (void *)NULL; + } + + //----------------------------------------------------------------------------- + // Open a file in the mode specified by openMode (Read, Write, or ReadWrite). + // Truncate the file if the mode is either Write or ReadWrite and truncate is + // true. + // + // Sets capability appropriate to the openMode. + // Returns the currentStatus of the file. + //----------------------------------------------------------------------------- + File::Status File::open(const char *filename, const AccessMode openMode) + { + AssertFatal(NULL != filename, "File::open: NULL filename"); + AssertWarn(NULL == handle, "File::open: handle already valid"); + + // Close the file if it was already open... + if (Closed != currentStatus) + close(); + + char prefPathName[MaxPath]; + char gamePathName[MaxPath]; + char cwd[MaxPath]; + getcwd(cwd, MaxPath); + MungePath(prefPathName, MaxPath, filename, GetPrefDir()); + MungePath(gamePathName, MaxPath, filename, cwd); + + int oflag; + struct stat filestat; + handle = (void *)dRealMalloc(sizeof(int)); + + switch (openMode) + { + case Read: + oflag = O_RDONLY; + break; + case Write: + oflag = O_WRONLY | O_CREAT | O_TRUNC; + break; + case ReadWrite: + oflag = O_RDWR | O_CREAT; + // if the file does not exist copy it before reading/writing + if (stat(prefPathName, &filestat) == -1) + bool ret = CopyFile(gamePathName, prefPathName); + break; + case WriteAppend: + oflag = O_WRONLY | O_CREAT | O_APPEND; + // if the file does not exist copy it before appending + if (stat(prefPathName, &filestat) == -1) + bool ret = CopyFile(gamePathName, prefPathName); + break; + default: + AssertFatal(false, "File::open: bad access mode"); // impossible + } + + // if we are writing, make sure output path exists + if (openMode == Write || openMode == ReadWrite || openMode == WriteAppend) + Platform::createPath(prefPathName); + + int fd = -1; + fd = x86UNIXOpen(prefPathName, oflag); + if (fd == -1 && openMode == Read) + // for read only files we can use the gamePathName + fd = x86UNIXOpen(gamePathName, oflag); + + dMemcpy(handle, &fd, sizeof(int)); + + #ifdef DEBUG + // fprintf(stdout,"fd = %d, handle = %d\n", fd, *((int *)handle)); + #endif + + if (*((int *)handle) == -1) + { + // handle not created successfully + Con::errorf("Can't open file: %s", filename); + return setStatus(); + } + else + { + // successfully created file, so set the file capabilities... + switch (openMode) + { + case Read: + capability = U32(FileRead); + break; + case Write: + case WriteAppend: + capability = U32(FileWrite); + break; + case ReadWrite: + capability = U32(FileRead) | + U32(FileWrite); + break; + default: + AssertFatal(false, "File::open: bad access mode"); + } + return currentStatus = Ok; // success! + } + } + + //----------------------------------------------------------------------------- + // Get the current position of the file pointer. + //----------------------------------------------------------------------------- + U32 File::getPosition() const + { + AssertFatal(Closed != currentStatus, "File::getPosition: file closed"); + AssertFatal(NULL != handle, "File::getPosition: invalid file handle"); + + #ifdef DEBUG + // fprintf(stdout, "handle = %d\n",*((int *)handle));fflush(stdout); + #endif + return (U32) lseek(*((int *)handle), 0, SEEK_CUR); + } + + //----------------------------------------------------------------------------- + // Set the position of the file pointer. + // Absolute and relative positioning is supported via the absolutePos + // parameter. + // + // If positioning absolutely, position MUST be positive - an IOError results if + // position is negative. + // Position can be negative if positioning relatively, however positioning + // before the start of the file is an IOError. + // + // Returns the currentStatus of the file. + //----------------------------------------------------------------------------- + File::Status File::setPosition(S32 position, bool absolutePos) + { + AssertFatal(Closed != currentStatus, "File::setPosition: file closed"); + AssertFatal(NULL != handle, "File::setPosition: invalid file handle"); + + if (Ok != currentStatus && EOS != currentStatus) + return currentStatus; + + U32 finalPos = 0; + switch (absolutePos) + { + case true: // absolute position + AssertFatal(0 <= position, "File::setPosition: negative absolute position"); + + // position beyond EOS is OK + finalPos = lseek(*((int *)handle), position, SEEK_SET); + break; + case false: // relative position + AssertFatal((getPosition() >= (U32)abs(position) && 0 > position) || 0 <= position, "File::setPosition: negative relative position"); + + // position beyond EOS is OK + finalPos = lseek(*((int *)handle), position, SEEK_CUR); + break; + } + + if (0xffffffff == finalPos) + return setStatus(); // unsuccessful + else if (finalPos >= getSize()) + return currentStatus = EOS; // success, at end of file + else + return currentStatus = Ok; // success! + } + + //----------------------------------------------------------------------------- + // Get the size of the file in bytes. + // It is an error to query the file size for a Closed file, or for one with an + // error status. + //----------------------------------------------------------------------------- + U32 File::getSize() const + { + AssertWarn(Closed != currentStatus, "File::getSize: file closed"); + AssertFatal(NULL != handle, "File::getSize: invalid file handle"); + + if (Ok == currentStatus || EOS == currentStatus) + { + long currentOffset = getPosition(); // keep track of our current position + long fileSize; + lseek(*((int *)handle), 0, SEEK_END); // seek to the end of the file + fileSize = getPosition(); // get the file size + lseek(*((int *)handle), currentOffset, SEEK_SET); // seek back to our old offset + return fileSize; // success! + } + else + return 0; // unsuccessful + } + + //----------------------------------------------------------------------------- + // Flush the file. + // It is an error to flush a read-only file. + // Returns the currentStatus of the file. + //----------------------------------------------------------------------------- + File::Status File::flush() + { + AssertFatal(Closed != currentStatus, "File::flush: file closed"); + AssertFatal(NULL != handle, "File::flush: invalid file handle"); + AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file"); + + if (fsync(*((int *)handle)) == 0) + return currentStatus = Ok; // success! + else + return setStatus(); // unsuccessful + } + + //----------------------------------------------------------------------------- + // Close the File. + // + // Returns the currentStatus + //----------------------------------------------------------------------------- + File::Status File::close() + { + // if the handle is non-NULL, close it if necessary and free it + if (NULL != handle) + { + // make a local copy of the handle value and + // free the handle + int handleVal = *((int *)handle); + dRealFree(handle); + handle = (void *)NULL; + + // close the handle if it is valid + if (handleVal != -1 && x86UNIXClose(handleVal) != 0) + return setStatus(); // unsuccessful + } + // Set the status to closed + return currentStatus = Closed; + } + + //----------------------------------------------------------------------------- + // Self-explanatory. + //----------------------------------------------------------------------------- + File::Status File::getStatus() const + { + return currentStatus; + } + + //----------------------------------------------------------------------------- + // Sets and returns the currentStatus when an error has been encountered. + //----------------------------------------------------------------------------- + File::Status File::setStatus() + { + Con::printf("File IO error: %s", strerror(errno)); + return currentStatus = IOError; + } + + //----------------------------------------------------------------------------- + // Sets and returns the currentStatus to status. + //----------------------------------------------------------------------------- + File::Status File::setStatus(File::Status status) + { + return currentStatus = status; + } + + //----------------------------------------------------------------------------- + // Read from a file. + // The number of bytes to read is passed in size, the data is returned in src. + // The number of bytes read is available in bytesRead if a non-Null pointer is + // provided. + //----------------------------------------------------------------------------- + File::Status File::read(U32 size, char *dst, U32 *bytesRead) + { + #ifdef DEBUG + // fprintf(stdout,"reading %d bytes\n",size);fflush(stdout); + #endif + AssertFatal(Closed != currentStatus, "File::read: file closed"); + AssertFatal(NULL != handle, "File::read: invalid file handle"); + AssertFatal(NULL != dst, "File::read: NULL destination pointer"); + AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability"); + AssertWarn(0 != size, "File::read: size of zero"); + + /* show stats for this file */ + #ifdef DEBUG + //struct stat st; + //fstat(*((int *)handle), &st); + //fprintf(stdout,"file size = %d\n", st.st_size); + #endif + /****************************/ + + if (Ok != currentStatus || 0 == size) + return currentStatus; + else + { + long lastBytes; + long *bytes = (NULL == bytesRead) ? &lastBytes : (long *)bytesRead; + if ( (*((U32 *)bytes) = x86UNIXRead(*((int *)handle), dst, size)) == -1) + { + #ifdef DEBUG + // fprintf(stdout,"unsuccessful: %d\n", *((U32 *)bytes));fflush(stdout); + #endif + return setStatus(); // unsuccessful + } else { + // dst[*((U32 *)bytes)] = '\0'; + if (*((U32 *)bytes) != size || *((U32 *)bytes) == 0) { + #ifdef DEBUG + // fprintf(stdout,"end of stream: %d\n", *((U32 *)bytes));fflush(stdout); + #endif + return currentStatus = EOS; // end of stream + } + } + } + // dst[*bytesRead] = '\0'; + #ifdef DEBUG + //fprintf(stdout, "We read:\n"); + //fprintf(stdout, "====================================================\n"); + //fprintf(stdout, "%s\n",dst); + //fprintf(stdout, "====================================================\n"); + //fprintf(stdout,"read ok: %d\n", *bytesRead);fflush(stdout); + #endif + return currentStatus = Ok; // successfully read size bytes + } + + //----------------------------------------------------------------------------- + // Write to a file. + // The number of bytes to write is passed in size, the data is passed in src. + // The number of bytes written is available in bytesWritten if a non-Null + // pointer is provided. + //----------------------------------------------------------------------------- + File::Status File::write(U32 size, const char *src, U32 *bytesWritten) + { + // JMQ: despite the U32 parameters, the maximum filesize supported by this + // function is probably the max value of S32, due to the unix syscall + // api. + AssertFatal(Closed != currentStatus, "File::write: file closed"); + AssertFatal(NULL != handle, "File::write: invalid file handle"); + AssertFatal(NULL != src, "File::write: NULL source pointer"); + AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability"); + AssertWarn(0 != size, "File::write: size of zero"); + + if ((Ok != currentStatus && EOS != currentStatus) || 0 == size) + return currentStatus; + else + { + S32 numWritten = x86UNIXWrite(*((int *)handle), src, size); + if (numWritten < 0) + return setStatus(); + + if (bytesWritten) + *bytesWritten = static_cast(numWritten); + return currentStatus = Ok; + } + } + + //----------------------------------------------------------------------------- + // Self-explanatory. JMQ: No explanation needed. Move along. These aren't + // the droids you're looking for. + //----------------------------------------------------------------------------- + bool File::hasCapability(Capability cap) const + { + return (0 != (U32(cap) & capability)); + } + + //----------------------------------------------------------------------------- + S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b) + { + if(a > b) + return 1; + if(a < b) + return -1; + return 0; + } + + //----------------------------------------------------------------------------- + static bool GetFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime) + { + struct stat fStat; + + if (stat(filePath, &fStat) == -1) + return false; + + if(createTime) + { + // no where does SysV/BSD UNIX keep a record of a file's + // creation time. instead of creation time I'll just use + // changed time for now. + *createTime = fStat.st_ctime; + } + if(modifyTime) + { + *modifyTime = fStat.st_mtime; + } + + return true; + } + + //----------------------------------------------------------------------------- + bool Platform::getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime) + { + char pathName[MaxPath]; + + // if it starts with cwd, we need to strip that off so that we can look for + // the file in the pref dir + char cwd[MaxPath]; + getcwd(cwd, MaxPath); + if (dStrstr(filePath, cwd) == filePath) + filePath = filePath + dStrlen(cwd) + 1; + + // if its relative, first look in the pref dir + if (filePath[0] != '/' && filePath[0] != '\\') + { + MungePath(pathName, MaxPath, filePath, GetPrefDir()); + if (GetFileTimes(pathName, createTime, modifyTime)) + return true; + } + + // here if the path is absolute or not in the pref dir + MungePath(pathName, MaxPath, filePath, cwd); + return GetFileTimes(pathName, createTime, modifyTime); + } + + //----------------------------------------------------------------------------- + bool Platform::createPath(const char *file) + { + char pathbuf[MaxPath]; + const char *dir; + pathbuf[0] = 0; + U32 pathLen = 0; + + // all paths should be created in home directory + char prefPathName[MaxPath]; + MungePath(prefPathName, MaxPath, file, GetPrefDir()); + file = prefPathName; + + // does the directory exist already? + if (DirExists(prefPathName, true)) // true means that the path is a filepath + return true; + + while((dir = dStrchr(file, '/')) != NULL) + { + dStrncpy(pathbuf + pathLen, file, dir - file); + pathbuf[pathLen + dir-file] = 0; + bool ret = mkdir(pathbuf, 0700); + pathLen += dir - file; + pathbuf[pathLen++] = '/'; + file = dir + 1; + } + return true; + } + + // JMQ: Platform:cdFileExists in unimplemented + //------------------------------------------------------------------------------ + // bool Platform::cdFileExists(const char *filePath, const char *volumeName, + // S32 serialNum) + // { + // } + + //----------------------------------------------------------------------------- + bool Platform::dumpPath(const char *path, Vector &fileVector, int depth) + { + const char* pattern = "*"; + + // if it is not absolute, dump the pref dir first + if (path[0] != '/' && path[0] != '\\') + { + char prefPathName[MaxPath]; + MungePath(prefPathName, MaxPath, path, GetPrefDir()); + RecurseDumpPath(prefPathName, path, pattern, fileVector); + } + + // munge the requested path and dump it + char mungedPath[MaxPath]; + char cwd[MaxPath]; + getcwd(cwd, MaxPath); + MungePath(mungedPath, MaxPath, path, cwd); + return RecurseDumpPath(mungedPath, path, pattern, fileVector); + } + + //----------------------------------------------------------------------------- + StringTableEntry Platform::getCurrentDirectory() + { + char cwd_buf[2048]; + getcwd(cwd_buf, 2047); + return StringTable->insert(cwd_buf); + } + + //----------------------------------------------------------------------------- + bool Platform::setCurrentDirectory(StringTableEntry newDir) + { + if (Platform::getWebDeployment()) + return true; + + TempAlloc< UTF8 > buf( dStrlen( newDir ) + 2 ); + + dStrcpy( buf, newDir ); + + ForwardSlash( buf ); + return chdir( buf ) == 0; + } + + //----------------------------------------------------------------------------- + const char *Platform::getUserDataDirectory() + { + return StringTable->insert( GetPrefDir() ); + } + + //----------------------------------------------------------------------------- + StringTableEntry Platform::getUserHomeDirectory() + { + char *home = getenv( "HOME" ); + return StringTable->insert( home ); + } + + StringTableEntry Platform::getExecutablePath() +{ + if( !sBinPathName[0] ) + { + const char *cpath; + if( (cpath = torque_getexecutablepath()) ) + { + dStrncpy(sBinPathName, cpath, sizeof(sBinPathName)); + chdir(sBinPathName); + } + else + { + getcwd(sBinPathName, sizeof(sBinPathName)-1); + } + } + + return StringTable->insert(sBinPathName); +} + + //----------------------------------------------------------------------------- + bool Platform::isFile(const char *pFilePath) + { + if (!pFilePath || !*pFilePath) + return false; + // Get file info + struct stat fStat; + if (stat(pFilePath, &fStat) < 0) + return false; + + // if the file is a "regular file" then true + if ( (fStat.st_mode & S_IFMT) == S_IFREG) + return true; + // must be some other file (directory, device, etc.) + return false; + } + + //----------------------------------------------------------------------------- + S32 Platform::getFileSize(const char *pFilePath) + { + if (!pFilePath || !*pFilePath) + return -1; + // Get the file info + struct stat fStat; + if (stat(pFilePath, &fStat) < 0) + return -1; + // if the file is a "regular file" then return the size + if ( (fStat.st_mode & S_IFMT) == S_IFREG) + return fStat.st_size; + // Must be something else or we can't read the file. + return -1; + } + + //----------------------------------------------------------------------------- + bool Platform::isDirectory(const char *pDirPath) + { + if (!pDirPath || !*pDirPath) + return false; + + // Get file info + struct stat fStat; + if (stat(pDirPath, &fStat) < 0) + return false; + + // if the file is a Directory then true + if ( (fStat.st_mode & S_IFMT) == S_IFDIR) + return true; + + return false; + } + + //----------------------------------------------------------------------------- + bool Platform::isSubDirectory(const char *pParent, const char *pDir) + { + if (!pParent || !*pDir) + return false; + + // this is somewhat of a brute force method but we need to be 100% sure + // that the user cannot enter things like ../dir or /dir etc,... + DIR *directory; + + directory = opendir(pParent); + if (directory == NULL) + return false; + + struct dirent *fEntry; + fEntry = readdir(directory); + if ( fEntry == NULL ) + return false; + + do + { + char dirBuf[MaxPath]; + struct stat fStat; + + dSprintf(dirBuf, sizeof(dirBuf), "%s/%s", pParent, fEntry->d_name); + if (stat(dirBuf, &fStat) < 0) + continue; + // if it is a directory... + if ( (fStat.st_mode & S_IFMT) == S_IFDIR) + { + // and the names match + if (dStrcmp(pDir, fEntry->d_name ) == 0) + { + // then we have a real sub directory + closedir(directory); + return true; + } + } + } while( (fEntry = readdir(directory)) != NULL ); + + closedir(directory); + return false; + } + + //----------------------------------------------------------------------------- + + + // This is untested -- BJG + + bool Platform::fileTimeToString(FileTime * time, char * string, U32 strLen) + { + if(!time || !string) + return(false); + + dSprintf(string, strLen, "%ld", *time); + return(true); + } + + bool Platform::stringToFileTime(const char * string, FileTime * time) + { + if(!time || !string) + return(false); + + *time = dAtoi(string); + + return(true); + } + + bool Platform::hasSubDirectory(const char *pPath) + { + if (!pPath) + return false; + + struct dirent *d; + DIR *dip; + dip = opendir(pPath); + if (dip == NULL) + return false; + + while (d = readdir(dip)) + { + bool isDir = false; + if (d->d_type == DT_UNKNOWN) + { + char child [1024]; + if ((pPath[dStrlen(pPath) - 1] == '/')) + dSprintf(child, 1024, "%s%s", pPath, d->d_name); + else + dSprintf(child, 1024, "%s/%s", pPath, d->d_name); + isDir = Platform::isDirectory (child); + } + else if (d->d_type & DT_DIR) + isDir = true; + if( isDir ) + { + // Skip the . and .. directories + if (dStrcmp(d->d_name, ".") == 0 ||dStrcmp(d->d_name, "..") == 0) + continue; + if (Platform::isExcludedDirectory(d->d_name)) + continue; + Platform::clearExcludedDirectories(); + closedir(dip); + return true; + } + } + closedir(dip); + Platform::clearExcludedDirectories(); + return false; + } + + static bool recurseDumpDirectories(const char *basePath, const char *subPath, Vector &directoryVector, S32 currentDepth, S32 recurseDepth, bool noBasePath) + { + char Path[1024]; + DIR *dip; + struct dirent *d; + + if (subPath && (dStrncmp(subPath, "", 1) != 0)) + { + if ((basePath[dStrlen(basePath) - 1]) == '/') + dSprintf(Path, 1024, "%s%s", basePath, subPath); + else + dSprintf(Path, 1024, "%s/%s", basePath, subPath); + } + else + dSprintf(Path, 1024, "%s", basePath); + dip = opendir(Path); + if (dip == NULL) + return false; + ////////////////////////////////////////////////////////////////////////// + // add path to our return list ( provided it is valid ) + ////////////////////////////////////////////////////////////////////////// + if (!Platform::isExcludedDirectory(subPath)) + { + if (noBasePath) + { + // We have a path and it's not an empty string or an excluded directory + if ( (subPath && (dStrncmp (subPath, "", 1) != 0)) ) + directoryVector.push_back(StringTable->insert(subPath)); + } + else + { + if ( (subPath && (dStrncmp(subPath, "", 1) != 0)) ) + { + char szPath[1024]; + dMemset(szPath, 0, 1024); + if ( (basePath[dStrlen(basePath) - 1]) != '/') + dSprintf(szPath, 1024, "%s%s", basePath, subPath); + else + dSprintf(szPath, 1024, "%s%s", basePath, &subPath[1]); + directoryVector.push_back(StringTable->insert(szPath)); + } + else + directoryVector.push_back(StringTable->insert(basePath)); + } + } + ////////////////////////////////////////////////////////////////////////// + // Iterate through and grab valid directories + ////////////////////////////////////////////////////////////////////////// + + while (d = readdir(dip)) + { + bool isDir; + isDir = false; + if (d->d_type == DT_UNKNOWN) + { + char child [1024]; + if ((Path[dStrlen(Path) - 1] == '/')) + dSprintf(child, 1024, "%s%s", Path, d->d_name); + else + dSprintf(child, 1024, "%s/%s", Path, d->d_name); + isDir = Platform::isDirectory (child); + } + else if (d->d_type & DT_DIR) + isDir = true; + + if ( isDir ) + { + if (dStrcmp(d->d_name, ".") == 0 || + dStrcmp(d->d_name, "..") == 0) + continue; + if (Platform::isExcludedDirectory(d->d_name)) + continue; + if ( (subPath && (dStrncmp(subPath, "", 1) != 0)) ) + { + char child[1024]; + if ((subPath[dStrlen(subPath) - 1] == '/')) + dSprintf(child, 1024, "%s%s", subPath, d->d_name); + else + dSprintf(child, 1024, "%s/%s", subPath, d->d_name); + if (currentDepth < recurseDepth || recurseDepth == -1 ) + recurseDumpDirectories(basePath, child, directoryVector, + currentDepth + 1, recurseDepth, + noBasePath); + } + else + { + char child[1024]; + if ( (basePath[dStrlen(basePath) - 1]) == '/') + dStrcpy (child, d->d_name); + else + dSprintf(child, 1024, "/%s", d->d_name); + if (currentDepth < recurseDepth || recurseDepth == -1) + recurseDumpDirectories(basePath, child, directoryVector, + currentDepth + 1, recurseDepth, + noBasePath); + } + } + } + closedir(dip); + return true; + } + + bool Platform::dumpDirectories(const char *path, Vector &directoryVector, S32 depth, bool noBasePath) + { + bool retVal = recurseDumpDirectories(path, "", directoryVector, 0, depth, noBasePath); + clearExcludedDirectories(); + return retVal; + } + +StringTableEntry Platform::getExecutableName() +{ + return StringTable->insert(sBinName); +} + +extern "C" +{ + void setExePathName(const char* exePathName) + { + if (exePathName == NULL) + sBinPathName[0] = '\0'; + else + dStrncpy(sBinPathName, exePathName, sizeof(sBinPathName)); + + // set the base exe name field + char *binName = dStrrchr(sBinPathName, '/'); + if( !binName ) + binName = sBinPathName; + else + *binName++ = '\0'; + sBinName = binName; + } +} diff --git a/platformX86UNIX/x86UNIXFont.client.cpp b/platformX86UNIX/x86UNIXFont.client.cpp new file mode 100644 index 0000000..dfb1050 --- /dev/null +++ b/platformX86UNIX/x86UNIXFont.client.cpp @@ -0,0 +1,331 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gFont.h" +#include "gfx/bitmap/gBitmap.h" +#include "math/mRect.h" +#include "console/console.h" +#include "core/strings/unicode.h" +#include "core/stringTable.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXFont.h" + +// Needed by createFont +#include +#include +#include +#include +#include +#include // For XRenderColor + +// Needed for getenv in createFont +#include +XftFont *loadFont(const char *name, S32 size, Display *display) +{ + XftFont *fontInfo = NULL; + char* fontname = const_cast(name); + if (dStrlen(fontname)==0) + fontname = "arial"; + else if (stristr(const_cast(name), "arial") != NULL) + fontname = "arial"; + else if (stristr(const_cast(name), "lucida console") != NULL) + fontname = "lucida console"; + + char* weight = "medium"; + char* slant = "roman"; // no slant + + if (stristr(const_cast(name), "bold") != NULL) + weight = "bold"; + if (stristr(const_cast(name), "italic") != NULL) + slant = "italic"; + + int mSize = size - 2 - (int)((float)size * 0.1); + char xfontName[512]; + // We specify a lower DPI to get 'correct' looking fonts, if we go with the + // native DPI the fonts are to big and don't fit the widgets. + dSprintf(xfontName, 512, "%s-%d:%s:slant=%s:dpi=76", fontname, mSize, weight, slant); + + // Lets see if Xft can get a font for us. + char xftname[1024]; + fontInfo = XftFontOpenName(display, DefaultScreen(display), xfontName); + // Cant find a suitabke font, default to a known font (6x10) + if ( !fontInfo ) + { + dSprintf(xfontName, 512, "6x10-%d:%s:slant=%s:dpi=76", mSize, weight, slant); + fontInfo = XftFontOpenName(display, DefaultScreen(display), xfontName); + } + XftNameUnparse(fontInfo->pattern, xftname, 1024); + +#ifdef DEBUG + Con::printf("Font '%s %d' mapped to '%s'\n", name, size, xftname); +#endif + + return fontInfo; +} + + +//GOldFont* createFont(const char *name, dsize_t size, U32 charset) +//{ +// Display *display = XOpenDisplay(getenv("DISPLAY")); +// int screen; +// +// if (!display) +// AssertFatal(false, "createFont: cannot connect to X server"); +// screen = DefaultScreen(display); +// +// XftFont *font = loadFont (name, size, display); +// if (!font) // This should almost never trigger anymore. +// AssertFatal(false, "createFont: cannot load font"); +// +// // Create the pixmap to draw on. +// Pixmap pixmap = XCreatePixmap(display, +// DefaultRootWindow(display), +// font->max_advance_width, +// font->height, +// DefaultDepth(display, screen)); +// // And the Xft wrapper around it. +// XftDraw *draw = XftDrawCreate(display, +// pixmap, +// DefaultVisual(display, screen), +// DefaultColormap(display, screen)); +// // Allocate some colors, we don't use XftColorAllocValue here as that +// // Don't appear to function correctly (or I'm using it wrong) As we only do +// // this twice per new un cached font it isn't that big of a penalty. (Each +// // call to XftColorAllocName involves a round trip to the X Server) +// XftColor black, white; +// XftColorAllocName(display, +// DefaultVisual(display, screen), +// DefaultColormap(display, screen), +// "black", +// &black); +// // White +// XftColorAllocName(display, +// DefaultVisual(display, screen), +// DefaultColormap(display, screen), +// "white", +// &white); +// +// // The font. +// GOldFont *retFont = new GOldFont; +// static U8 scratchPad[65536]; +// int x, y; +// // insert bitmaps into the font for each character +// for(U16 i = 32; i < 256; i++) +// { +// XGlyphInfo extent; +// FT_UInt glyph; +// if (!XftCharExists(display, font, i)) +// { +// retFont->insertBitmap(i, scratchPad, 0, 0, 0, 0, 0, font->max_advance_width); +// continue; +// } +// // Get the glyph and its extents. +// glyph = XftCharIndex(display, font, i); +// XftGlyphExtents (display, font, &glyph, 1, &extent); +// // Clear the bounding box and draw the glyph +// XftDrawRect (draw, &black, 0, 0, font->max_advance_width, font->height); +// XftDrawGlyphs (draw, &white, font, 0, font->ascent, &glyph, 1); +// // Grab the rendered image ... +// XImage *ximage = XGetImage(display, pixmap, 0, 0, +// extent.xOff, font->height, +// AllPlanes, XYPixmap); +// if (ximage == NULL) +// AssertFatal(false, "cannot get x image"); +// // And store each pixel in the scratchPad for insertion into the bitmap. +// // We grab the full height of the pixmap. +// for(y = 0; y < font->height; y++) +// { +// // and the width of the glyph and its padding. +// for(x = 0; x < extent.xOff; x++) +// scratchPad[y * extent.xOff + x] = static_cast(XGetPixel(ximage, x, y)); +// } +// // Done with the image. +// XDestroyImage(ximage); +// // Add it to the bitmap. +// retFont->insertBitmap(i, // index +// scratchPad, // src +// extent.xOff, // stride +// extent.xOff, // width +// font->height, // height +// 0, // xOrigin +// font->ascent, // yOrigin +// extent.xOff); // xIncrement +// +// } +// retFont->pack(font->height, font->ascent); +// XftFontClose(display, font); +// +// XftColorFree(display, DefaultVisual(display, screen), +// DefaultColormap(display, screen), &black); +// XftColorFree(display, DefaultVisual(display, screen), +// DefaultColormap(display, screen), &white); +// XftDrawDestroy(draw); +// XFreePixmap(display, pixmap); +// XCloseDisplay(display); +// return retFont; +//} + + +// XA: New class for the unix unicode font +PlatformFont *createPlatformFont(const char *name, U32 size, U32 charset /* = TGE_ANSI_CHARSET */) +{ + PlatformFont *retFont = new x86UNIXFont; + + if(retFont->create(name, size, charset)) + return retFont; + + delete retFont; + return NULL; +} + +x86UNIXFont::x86UNIXFont() +{} + +x86UNIXFont::~x86UNIXFont() +{} + + +bool x86UNIXFont::create(const char *name, U32 size, U32 charset) +{ + Display *display = XOpenDisplay(getenv("DISPLAY")); + if (display == NULL) + AssertFatal(false, "createFont: cannot connect to X server"); + + XftFont *font = loadFont(name, size, display); + + if (!font) + { + Con::errorf("Error: Could not load font -%s-", name); + return false; + } + char xfontname[1024]; + XftNameUnparse(font->pattern, xfontname, 1024); +#ifdef DEBUG + Con::printf("CreateFont: request for %s %d, using %s", name, size, xfontname); +#endif + // store some info about the font + baseline = font->ascent; + height = font->height; + mFontName = StringTable->insert(xfontname); + XftFontClose (display, font); + // DISPLAY + XCloseDisplay(display); + + return true; +} + +bool x86UNIXFont::isValidChar(const UTF16 str) const +{ + // 0x20 == 32 + // 0x100 == 256 + if( str < 0x20 || str > 0x100 ) + return false; + + return true; +} + +bool x86UNIXFont::isValidChar(const UTF8 *str) const +{ + + return isValidChar(oneUTF32toUTF16(oneUTF8toUTF32(str,NULL))); +} + +PlatformFont::CharInfo &x86UNIXFont::getCharInfo(const UTF16 ch) const +{ + Display *display = XOpenDisplay(getenv("DISPLAY")); + if (!display ) + AssertFatal(false, "createFont: cannot connect to X server"); + + static PlatformFont::CharInfo c; + dMemset(&c, 0, sizeof(c)); + c.bitmapIndex = 0; + c.xOffset = 0; + c.yOffset = 0; + + XftFont *fontInfo = XftFontOpenName(display, DefaultScreen(display), mFontName); + if (!fontInfo) + AssertFatal(false, "createFont: cannot load font"); + + int screen = DefaultScreen(display); + // Create the pixmap to draw on. + Drawable pixmap = XCreatePixmap(display, + DefaultRootWindow(display), + fontInfo->max_advance_width, + fontInfo->height, + DefaultDepth(display, screen)); + // And the Xft wrapper around it. + XftDraw *draw = XftDrawCreate(display, + pixmap, + DefaultVisual(display, screen), + DefaultColormap(display, screen)); + // Allocate some colors, we don't use XftColorAllocValue here as that + // Don't appear to function correctly (or I'm using it wrong) As we only do + // this twice per new un cached font it isn't that big of a penalty. (Each + // call to XftColorAllocName involves a round trip to the X Server) + XftColor black, white; + XftColorAllocName(display, + DefaultVisual(display, screen), + DefaultColormap(display, screen), + "black", + &black); + // White + XftColorAllocName(display, + DefaultVisual(display, screen), + DefaultColormap(display, screen), + "white", + &white); + + XGlyphInfo charinfo; + XftTextExtents16(display, fontInfo, &ch, 1, &charinfo); + c.height = fontInfo->height; + c.xOrigin = 0; + c.yOrigin = fontInfo->ascent; + c.xIncrement = charinfo.xOff; + c.width = charinfo.xOff; + // kick out early if the character is undrawable + if( c.width == 0 || c.height == 0) + return c; + + // allocate a greyscale bitmap and clear it. + int bitmapDataSize = c.width * c.height; + c.bitmapData = new U8[bitmapDataSize]; + dMemset(c.bitmapData, 0, bitmapDataSize); + + XftDrawRect (draw, &black, 0, 0, fontInfo->max_advance_width, fontInfo->height); + XftDrawString16 (draw, &white, fontInfo, 0, fontInfo->ascent, &ch, 1); + // grab the pixmap image + + XImage *ximage = XGetImage(display, pixmap, 0, 0, + charinfo.xOff, fontInfo->height, + AllPlanes, XYPixmap); + if (!ximage) + AssertFatal(false, "cannot get x image"); + int x, y; + + // grab each pixel and store it in the scratchPad + for(y = 0; y < fontInfo->height; y++) + { + for(x = 0; x < charinfo.xOff; x++) + c.bitmapData[y * charinfo.xOff + x] = static_cast(XGetPixel(ximage, x, y)); + } + XDestroyImage(ximage); + + XftColorFree(display, DefaultVisual(display, screen), + DefaultColormap(display, screen), &black); + XftColorFree(display, DefaultVisual(display, screen), + DefaultColormap(display, screen), &white); + XftDrawDestroy(draw); + XFreePixmap(display, pixmap); + XCloseDisplay(display); + + return c; +} + + +PlatformFont::CharInfo &x86UNIXFont::getCharInfo(const UTF8 *str) const +{ + return getCharInfo(oneUTF32toUTF16(oneUTF8toUTF32(str,NULL))); +} + diff --git a/platformX86UNIX/x86UNIXFont.h b/platformX86UNIX/x86UNIXFont.h new file mode 100644 index 0000000..96d332f --- /dev/null +++ b/platformX86UNIX/x86UNIXFont.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PLATFORMFONT_H_ +#include "platform/platformFont.h" +#include "platform/platform.h" +#endif + +#ifndef _X86UNIXFONT_H_ +#define _X86UNIXFONT_H_ +// Needed by createFont +#include +#include +#include +#include + +class x86UNIXFont : public PlatformFont +{ + private: + int baseline; + int height; + StringTableEntry mFontName; + public: + x86UNIXFont(); + virtual ~x86UNIXFont(); + + // PlatformFont virtual methods + virtual bool isValidChar(const UTF16 ch) const; + virtual bool isValidChar(const UTF8 *str) const; + + inline U32 getFontHeight() const + { + return height; + } + + inline U32 getFontBaseLine() const + { + return baseline; + } + + virtual PlatformFont::CharInfo &getCharInfo(const UTF16 ch) const; + virtual PlatformFont::CharInfo &getCharInfo(const UTF8 *str) const; + + virtual bool create(const char *name, dsize_t size, U32 charset = TGE_ANSI_CHARSET); +}; + +#endif diff --git a/platformX86UNIX/x86UNIXGL.client.cpp b/platformX86UNIX/x86UNIXGL.client.cpp new file mode 100644 index 0000000..7b9f670 --- /dev/null +++ b/platformX86UNIX/x86UNIXGL.client.cpp @@ -0,0 +1,374 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformGL.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "console/console.h" + +#include +#include + +// declare stub functions +#define GL_FUNCTION(fn_return, fn_name, fn_args, fn_value) fn_return stub_##fn_name fn_args{ fn_value } +#include "platform/GLCoreFunc.h" +#include "platform/GLExtFunc.h" +#undef GL_FUNCTION + +// point gl function pointers at stub functions +#define GL_FUNCTION(fn_return,fn_name,fn_args, fn_value) fn_return (*fn_name)fn_args = stub_##fn_name; +#include "platform/GLCoreFunc.h" +#include "platform/GLExtFunc.h" +#undef GL_FUNCTION + +static void* dlHandle = NULL; + +//------------------------------------------------------------------ +//bind functions for each function prototype +//------------------------------------------------------------------ + +//GL_EXT/ARB +enum { + ARB_multitexture = BIT(0), + ARB_texture_compression = BIT(1), + EXT_compiled_vertex_array = BIT(2), + EXT_fog_coord = BIT(3), + EXT_paletted_texture = BIT(4), + NV_vertex_array_range = BIT(5), + EXT_blend_color = BIT(6), + EXT_blend_minmax = BIT(7) +}; + +//WGL_ARB +enum { + WGL_ARB_extensions_string = BIT(0), + WGL_EXT_swap_control = BIT(1), + WGL_3DFX_gamma_control = BIT(2) +}; + + +static bool isFnOk( const char *name) +{ + bool ok = false; + + // JMQ: these are specific to torque's d3d->gl wrapper. They are not used under linux. + if (dStrcmp(name, "glAvailableVertexBufferEXT")==0) + ok = true; + else if (dStrcmp(name, "glAllocateVertexBufferEXT")==0) + ok = true; + else if (dStrcmp(name, "glLockVertexBufferEXT")==0) + ok = true; + else if (dStrcmp(name, "glUnlockVertexBufferEXT")==0) + ok = true; + else if (dStrcmp(name, "glSetVertexBufferEXT")==0) + ok = true; + else if (dStrcmp(name, "glOffsetVertexBufferEXT")==0) + ok = true; + else if (dStrcmp(name, "glFillVertexBufferEXT")==0) + ok = true; + else if (dStrcmp(name, "glFreeVertexBufferEXT")==0) + ok = true; + + return ok; +} + +//------------------------------------------------------------------ +//bind functions for each function prototype +//------------------------------------------------------------------ +static bool bindGLFunction( void *&fnAddress, const char *name ) +{ + void* addr = (void*)SDL_GL_GetProcAddress(name); + bool ok = (bool)addr; + if( !ok ) + { + if (!isFnOk(name)) + Con::errorf(ConsoleLogEntry::General, " Missing OpenGL function '%s'", name); + else + ok = true; + } + else + fnAddress = addr; + return ok; +} + +static bool bindEXTFunction( void *&fnAddress, const char *name ) +{ + void* addr = (void*)SDL_GL_GetProcAddress(name); + if( !addr ) + Con::errorf(ConsoleLogEntry::General, " Missing OpenGL extension '%s'", name); + else + fnAddress = addr; + return (addr != NULL); +} + +//------------------------------------------------------------------ +//binds for each function group +//------------------------------------------------------------------ +static bool bindGLFunctions() +{ + bool result = true; + #define GL_FUNCTION(fn_return, fn_name, fn_args, fn_value) \ + result &= bindGLFunction( *(void**)&fn_name, #fn_name); + #include "platform/GLCoreFunc.h" + #undef GL_FUNCTION + return result; +} + +static bool bindEXTFunctions(U32 extMask) +{ + bool result = true; + + #define GL_GROUP_BEGIN( flag ) \ + if( extMask & flag ) { + #define GL_GROUP_END() } + + #define GL_FUNCTION(fn_return, fn_name, fn_args, fn_value) \ + result &= bindEXTFunction( *(void**)&fn_name, #fn_name); + #include "platform/GLExtFunc.h" + #undef GL_FUNCTION + + #undef GL_GROUP_BEGIN + #undef GL_GROUP_END + + return result; +} + +static void unbindGLFunctions() +{ + // point gl function pointers at stub functions +#define GL_FUNCTION(fn_return, fn_name, fn_args, fn_value) fn_name = stub_##fn_name; +#include "platform/GLCoreFunc.h" +#include "platform/GLExtFunc.h" +#undef GL_FUNCTION +} + +namespace GLLoader +{ + + bool OpenGLInit() + { + return OpenGLDLLInit(); + } + + void OpenGLShutdown() + { + OpenGLDLLShutdown(); + } + + bool OpenGLDLLInit() + { + OpenGLDLLShutdown(); + + // load libGL.so + if (SDL_GL_LoadLibrary("libGL.so") == -1 && + SDL_GL_LoadLibrary("libGL.so.1") == -1) + { + Con::errorf("Error loading GL library: %s", SDL_GetError()); + return false; + } + + // bind functions + if (!bindGLFunctions()) + { + Con::errorf("Error binding GL functions"); + OpenGLDLLShutdown(); + return false; + } + + return true; + } + + void OpenGLDLLShutdown() + { + // there is no way to tell SDL to unload the library + if (dlHandle != NULL) + { + dlclose(dlHandle); + dlHandle = NULL; + } + + unbindGLFunctions(); + } + +} + +GLState gGLState; + +bool gOpenGLDisablePT = false; +bool gOpenGLDisableCVA = false; +bool gOpenGLDisableTEC = false; +bool gOpenGLDisableARBMT = false; +bool gOpenGLDisableFC = false; +bool gOpenGLDisableTCompress = false; +bool gOpenGLNoEnvColor = false; +float gOpenGLGammaCorrection = 0.5; +bool gOpenGLNoDrawArraysAlpha = false; + +// JMQTODO: really need a platform-shared version of this nastiness +bool GL_EXT_Init( ) +{ + // Load extensions... + // + const char* pExtString = reinterpret_cast(glGetString(GL_EXTENSIONS)); + gGLState.primMode = 0; + U32 extBitMask = 0; + + // GL_EXT_paletted_texture + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_paletted_texture") != NULL) + { + extBitMask |= EXT_paletted_texture; + gGLState.suppPalettedTexture = true; + } + else + gGLState.suppPalettedTexture = false; + + // EXT_compiled_vertex_array + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_compiled_vertex_array") != NULL) + { + extBitMask |= EXT_compiled_vertex_array; + gGLState.suppLockedArrays = true; + } + else + { + gGLState.suppLockedArrays = false; + } + + // ARB_multitexture + if (pExtString && dStrstr(pExtString, (const char*)"GL_ARB_multitexture") != NULL) + { + extBitMask |= ARB_multitexture; + gGLState.suppARBMultitexture = true; + } else { + gGLState.suppARBMultitexture = false; + } + + // EXT_blend_color + if(pExtString && dStrstr(pExtString, (const char*)"GL_EXT_blend_color") != NULL) + { + extBitMask |= EXT_blend_color; + gGLState.suppEXTblendcolor = true; + } else { + gGLState.suppEXTblendcolor = false; + } + + // EXT_blend_minmax + if(pExtString && dStrstr(pExtString, (const char*)"GL_EXT_blend_minmax") != NULL) + { + extBitMask |= EXT_blend_color; + gGLState.suppEXTblendminmax = true; + } else { + gGLState.suppEXTblendminmax = false; + } + + // EXT_fog_coord + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_fog_coord") != NULL) + { + extBitMask |= EXT_fog_coord; + gGLState.suppFogCoord = true; + } else { + gGLState.suppFogCoord = false; + } + + // EXT_texture_compression_s3tc + if (pExtString && dStrstr(pExtString, (const char*)"GL_EXT_texture_compression_s3tc") != NULL) + gGLState.suppS3TC = true; + else + gGLState.suppS3TC = false; + + // ARB_texture_compression + if (pExtString && dStrstr(pExtString, (const char*)"GL_ARB_texture_compression") != NULL) + { + extBitMask |= ARB_texture_compression; + gGLState.suppTextureCompression = true; + } else { + gGLState.suppTextureCompression = false; + } + + // NV_vertex_array_range (not on *nix) + gGLState.suppVertexArrayRange = false; + + // 3DFX_texture_compression_FXT1 + if (pExtString && dStrstr(pExtString, (const char*)"3DFX_texture_compression_FXT1") != NULL) + gGLState.suppFXT1 = true; + else + gGLState.suppFXT1 = false; + + if (!bindEXTFunctions(extBitMask)) + Con::warnf("You are missing some OpenGL Extensions. You may experience rendering problems."); + + // Binary states, i.e., no supporting functions + // EXT_packed_pixels + // EXT_texture_env_combine + // + // dhc note: a number of these can have multiple matching 'versions', private, ext, and arb. + gGLState.suppPackedPixels = pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_packed_pixels") != NULL) : false; + gGLState.suppTextureEnvCombine = pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_texture_env_combine") != NULL) : false; + gGLState.suppEdgeClamp = pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_texture_edge_clamp") != NULL) : false; + gGLState.suppEdgeClamp |= pExtString? (dStrstr(pExtString, (const char*)"GL_SGIS_texture_edge_clamp") != NULL) : false; + gGLState.suppTexEnvAdd = pExtString? (dStrstr(pExtString, (const char*)"GL_ARB_texture_env_add") != NULL) : false; + gGLState.suppTexEnvAdd |= pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_texture_env_add") != NULL) : false; + + // Anisotropic filtering + gGLState.suppTexAnisotropic = pExtString? (dStrstr(pExtString, (const char*)"GL_EXT_texture_filter_anisotropic") != NULL) : false; + if (gGLState.suppTexAnisotropic) + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gGLState.maxAnisotropy); + if (gGLState.suppARBMultitexture) + glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &gGLState.maxTextureUnits); + else + gGLState.maxTextureUnits = 1; + + // JMQ: vsync/swap interval skipped + gGLState.suppSwapInterval = false; + + Con::printf("OpenGL Init: Enabled Extensions"); + if (gGLState.suppARBMultitexture) Con::printf(" ARB_multitexture (Max Texture Units: %d)", gGLState.maxTextureUnits); + if (gGLState.suppEXTblendcolor) Con::printf(" EXT_blend_color"); + if (gGLState.suppEXTblendminmax) Con::printf(" EXT_blend_minmax"); + if (gGLState.suppPalettedTexture) Con::printf(" EXT_paletted_texture"); + if (gGLState.suppLockedArrays) Con::printf(" EXT_compiled_vertex_array"); + if (gGLState.suppVertexArrayRange) Con::printf(" NV_vertex_array_range"); + if (gGLState.suppTextureEnvCombine) Con::printf(" EXT_texture_env_combine"); + if (gGLState.suppPackedPixels) Con::printf(" EXT_packed_pixels"); + if (gGLState.suppFogCoord) Con::printf(" EXT_fog_coord"); + if (gGLState.suppTextureCompression) Con::printf(" ARB_texture_compression"); + if (gGLState.suppS3TC) Con::printf(" EXT_texture_compression_s3tc"); + if (gGLState.suppFXT1) Con::printf(" 3DFX_texture_compression_FXT1"); + if (gGLState.suppTexEnvAdd) Con::printf(" (ARB|EXT)_texture_env_add"); + if (gGLState.suppTexAnisotropic) Con::printf(" EXT_texture_filter_anisotropic (Max anisotropy: %f)", gGLState.maxAnisotropy); + if (gGLState.suppSwapInterval) Con::printf(" WGL_EXT_swap_control"); + + Con::warnf("OpenGL Init: Disabled Extensions"); + if (!gGLState.suppARBMultitexture) Con::warnf(" ARB_multitexture"); + if (!gGLState.suppEXTblendcolor) Con::warnf(" EXT_blend_color"); + if (!gGLState.suppEXTblendminmax) Con::warnf(" EXT_blend_minmax"); + if (!gGLState.suppPalettedTexture) Con::warnf(" EXT_paletted_texture"); + if (!gGLState.suppLockedArrays) Con::warnf(" EXT_compiled_vertex_array"); + if (!gGLState.suppVertexArrayRange) Con::warnf(" NV_vertex_array_range"); + if (!gGLState.suppTextureEnvCombine) Con::warnf(" EXT_texture_env_combine"); + if (!gGLState.suppPackedPixels) Con::warnf(" EXT_packed_pixels"); + if (!gGLState.suppFogCoord) Con::warnf(" EXT_fog_coord"); + if (!gGLState.suppTextureCompression) Con::warnf(" ARB_texture_compression"); + if (!gGLState.suppS3TC) Con::warnf(" EXT_texture_compression_s3tc"); + if (!gGLState.suppFXT1) Con::warnf(" 3DFX_texture_compression_FXT1"); + if (!gGLState.suppTexEnvAdd) Con::warnf(" (ARB|EXT)_texture_env_add"); + if (!gGLState.suppTexAnisotropic) Con::warnf(" EXT_texture_filter_anisotropic"); + if (!gGLState.suppSwapInterval) Con::warnf(" WGL_EXT_swap_control"); + Con::printf(" "); + + // Set some console variables: + Con::setBoolVariable( "$FogCoordSupported", gGLState.suppFogCoord ); + Con::setBoolVariable( "$TextureCompressionSupported", gGLState.suppTextureCompression ); + Con::setBoolVariable( "$AnisotropySupported", gGLState.suppTexAnisotropic ); + Con::setBoolVariable( "$PalettedTextureSupported", gGLState.suppPalettedTexture ); + Con::setBoolVariable( "$SwapIntervalSupported", gGLState.suppSwapInterval ); + + if (!gGLState.suppPalettedTexture && Con::getBoolVariable("$pref::OpenGL::forcePalettedTexture",false)) + { + Con::setBoolVariable("$pref::OpenGL::forcePalettedTexture", false); + Con::setBoolVariable("$pref::OpenGL::force16BitTexture", true); + } + + return true; +} + diff --git a/platformX86UNIX/x86UNIXIO.cpp b/platformX86UNIX/x86UNIXIO.cpp new file mode 100644 index 0000000..97bc43f --- /dev/null +++ b/platformX86UNIX/x86UNIXIO.cpp @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" + +#include +#include +#include +#include +#include +#include +#include + +int x86UNIXOpen(const char *path, int oflag) +{ + return open(path, oflag, 0666); +} + +int x86UNIXClose(int fd) +{ + return close(fd); +} + +ssize_t x86UNIXRead(int fd, void *buf, size_t nbytes) +{ + return read(fd, buf, nbytes); +} + +ssize_t x86UNIXWrite(int fd, const void *buf, size_t nbytes) +{ + return write(fd, buf, nbytes); +} diff --git a/platformX86UNIX/x86UNIXInput.client.cpp b/platformX86UNIX/x86UNIXInput.client.cpp new file mode 100644 index 0000000..724e0bf --- /dev/null +++ b/platformX86UNIX/x86UNIXInput.client.cpp @@ -0,0 +1,587 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platformInput.h" +#include "platform/platformVideo.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "console/console.h" +#include "platformX86UNIX/x86UNIXState.h" +#include "platformX86UNIX/x86UNIXInputManager.h" + +#include +#include +#include + +#include + +#ifdef LOG_INPUT +#include +#include +#include +#include + +extern int x86UNIXOpen(const char *path, int oflag); +extern int x86UNIXClose(int fd); +extern ssize_t x86UNIXWrite(int fd, const void *buf, size_t nbytes); +#endif + +class XClipboard +{ + private: + Atom mClipboardProperty; + Atom mClipboard; + Atom mPrimary; + bool mInitialized; + U8 *mXData; + char *mTData; + S32 mTDataSize; + + void init(); + void freeXData(); + void freeTData(); + void checkTDataSize(S32 requestedSize); + public: + XClipboard(); + ~XClipboard(); + + bool setClipboard(const char *text); + const char* getClipboard(); + void handleSelectionRequest(XSelectionRequestEvent& request); +}; + +// Static class variables: +InputManager* Input::smManager; + +// smActive is not maintained under unix. Use Input::isActive() +// instead +bool Input::smActive = false; + +// unix platform state +extern x86UNIXPlatformState * x86UNIXState; + +extern AsciiData AsciiTable[NUM_KEYS]; + +static XClipboard xclipboard; + +#ifdef LOG_INPUT +S32 gInputLog = -1; +#endif + +//------------------------------------------------------------------------------ +void Input::init() +{ + Con::printf( "Input Init:" ); + + destroy(); + +#ifdef LOG_INPUT + struct tm* newTime; + time_t aclock; + time( &aclock ); + newTime = localtime( &aclock ); + asctime( newTime ); + + gInputLog = x86UNIXOpen("input.log", O_WRONLY | O_CREAT); + log("Input log opened at %s\n", asctime( newTime ) ); + log("Operating System:\n" ); + log(" %s", UUtils->getOSName()); + log("\n"); +#endif + + smActive = false; + smManager = NULL; + + UInputManager *uInputManager = new UInputManager; + if ( !uInputManager->enable() ) + { + Con::errorf( " Failed to enable Input Manager." ); + delete uInputManager; + return; + } + + uInputManager->init(); + + smManager = uInputManager; + + Con::printf(" Input initialized"); + Con::printf(" "); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( isJoystickDetected, bool, 1, 1, "isJoystickDetected()" ) +{ + argc; argv; + UInputManager* manager = dynamic_cast(Input::getManager()); + if (manager) + return manager->joystickDetected(); + else + return false; +} + +//------------------------------------------------------------------------------ +ConsoleFunction( getJoystickAxes, const char*, 2, 2, "getJoystickAxes( instance )" ) +{ + argc; argv; + UInputManager* manager = dynamic_cast(Input::getManager()); + if (manager) + return manager->getJoystickAxesString(dAtoi(argv[1])); + else + return ""; +} + +//------------------------------------------------------------------------------ +U16 Input::getKeyCode( U16 asciiCode ) +{ + U16 keyCode = 0; + U16 i; + + // This is done three times so the lowerkey will always + // be found first. Some foreign keyboards have duplicate + // chars on some keys. + for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) + { + if ( AsciiTable[i].lower.ascii == asciiCode ) + { + keyCode = i; + break; + }; + } + + for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) + { + if ( AsciiTable[i].upper.ascii == asciiCode ) + { + keyCode = i; + break; + }; + } + + for ( i = KEY_FIRST; i < NUM_KEYS && !keyCode; i++ ) + { + if ( AsciiTable[i].goofy.ascii == asciiCode ) + { + keyCode = i; + break; + }; + } + + return( keyCode ); +} + +//----------------------------------------------------------------------------- +// +// This function gets the standard ASCII code corresponding to our key code +// and the existing modifier key state. +// +//----------------------------------------------------------------------------- +U16 Input::getAscii( U16 keyCode, KEY_STATE keyState ) +{ + if ( keyCode >= NUM_KEYS ) + return 0; + + switch ( keyState ) + { + case STATE_LOWER: + return AsciiTable[keyCode].lower.ascii; + case STATE_UPPER: + return AsciiTable[keyCode].upper.ascii; + case STATE_GOOFY: + return AsciiTable[keyCode].goofy.ascii; + default: + return(0); + + } +} + +//------------------------------------------------------------------------------ +void Input::destroy() +{ +#ifdef LOG_INPUT + if ( gInputLog != -1 ) + { + log( "*** CLOSING LOG ***\n" ); + x86UNIXClose(gInputLog); + gInputLog = -1; + } +#endif + + if ( smManager && smManager->isEnabled() ) + { + smManager->disable(); + delete smManager; + smManager = NULL; + } +} + +//------------------------------------------------------------------------------ +bool Input::enable() +{ + if ( smManager && !smManager->isEnabled() ) + return( smManager->enable() ); + + return( false ); +} + +//------------------------------------------------------------------------------ +void Input::disable() +{ + if ( smManager && smManager->isEnabled() ) + smManager->disable(); +} + +//------------------------------------------------------------------------------ +void Input::activate() +{ + if ( smManager && smManager->isEnabled() && !isActive()) + { +#ifdef LOG_INPUT + Input::log( "Activating Input...\n" ); +#endif + UInputManager* uInputManager = dynamic_cast( smManager ); + if ( uInputManager ) + uInputManager->activate(); + } +} + +//------------------------------------------------------------------------------ +void Input::deactivate() +{ + if ( smManager && smManager->isEnabled() && isActive() ) + { +#ifdef LOG_INPUT + Input::log( "Deactivating Input...\n" ); +#endif + UInputManager* uInputManager = dynamic_cast( smManager ); + if ( uInputManager ) + uInputManager->deactivate(); + } +} + +//------------------------------------------------------------------------------ +void Input::reactivate() +{ + Input::deactivate(); + Input::activate(); +} + +//------------------------------------------------------------------------------ +bool Input::isEnabled() +{ + if ( smManager ) + return smManager->isEnabled(); + return false; +} + +//------------------------------------------------------------------------------ +bool Input::isActive() +{ + UInputManager* uInputManager = dynamic_cast( smManager ); + if ( uInputManager ) + return uInputManager->isActive(); + return false; +} + +//------------------------------------------------------------------------------ +void Input::process() +{ + if ( smManager ) + smManager->process(); +} + +//------------------------------------------------------------------------------ +InputManager* Input::getManager() +{ + return smManager; +} + +#ifdef LOG_INPUT +//------------------------------------------------------------------------------ +void Input::log( const char* format, ... ) +{ + if ( gInputLog == -1) + return; + + va_list argptr; + va_start( argptr, format ); + + const int BufSize = 4096; + char buffer[BufSize]; + dVsprintf( buffer, BufSize, format, argptr ); + x86UNIXWrite(gInputLog, buffer, dStrlen( buffer )); + va_end( argptr ); +} + +ConsoleFunction( inputLog, void, 2, 2, "inputLog( string )" ) +{ + argc; + Input::log( "%s\n", argv[1] ); +} +#endif // LOG_INPUT + +//------------------------------------------------------------------------------ +void NotifySelectionEvent(XEvent& event) +{ + // somebody sent us a select event + if (event.type == SelectionRequest) + xclipboard.handleSelectionRequest(event.xselectionrequest); +} + +//------------------------------------------------------------------------------ +const char* Platform::getClipboard() +{ + return xclipboard.getClipboard(); +} + +//------------------------------------------------------------------------------ +bool Platform::setClipboard(const char *text) +{ + return xclipboard.setClipboard(text); +} + +//----------------------------------------------------------------------------- +// XClipboard members +XClipboard::XClipboard() +{ + mInitialized = false; +} + +//------------------------------------------------------------------------------ +XClipboard::~XClipboard() +{ + freeXData(); + freeTData(); +} + +//------------------------------------------------------------------------------ +void XClipboard::init() +{ + DisplayPtrManager xdisplay; + Display* display = xdisplay.getDisplayPointer(); + + mClipboardProperty = XInternAtom(display, + "TORQUE_CLIPBOARD_ATOM", False); + mClipboard = XInternAtom(display, "CLIPBOARD", + False); + mPrimary = XA_PRIMARY; //XInternAtom(display, "PRIMARY", False); + mXData = NULL; + mTData = NULL; + mTDataSize = 0; + + mInitialized = true; +} + +//------------------------------------------------------------------------------ +inline void XClipboard::freeXData() +{ + if (mXData != NULL) + { + XFree(mXData); + mXData = NULL; + } +} + +//------------------------------------------------------------------------------ +inline void XClipboard::freeTData() +{ + if (mTData != NULL) + { + dRealFree(mTData); + mTData = NULL; + mTDataSize = 0; + } +} + +// +// JMQ: As you might expect, X clipboard usage is bizarre. I +// found this document to be useful. +// +// http://www.freedesktop.org/standards/clipboards.txt +// +// JMQ: later note: programming the X clipboard is not just +// bizarre, it SUCKS. No wonder so many apps have +// clipboard problems. +// +//------------------------------------------------------------------------------ +const char* XClipboard::getClipboard() +{ + DisplayPtrManager xdisplay; + Display* display = xdisplay.getDisplayPointer(); + + if (!mInitialized) + init(); + + // find the owner of the clipboard + Atom targetSelection = mClipboard; + Window clipOwner = XGetSelectionOwner(display, + targetSelection); + if (clipOwner == None) + { + // It seems like KDE/QT reads the clipboard but doesn't set it. + // This is a bug, that supposedly will be fixed in QT3. + // I tried working around this by using + // PRIMARY instead of CLIPBOARD, but this has some nonintuitive + // side effects. So, no pasting from KDE apps for now. + //targetSelection = mPrimary; + //clipOwner = XGetSelectionOwner(display, targetSelection); + } + + if (clipOwner == None) + // oh well + return ""; + + // request that the owner convert the selection to a string + XConvertSelection(display, targetSelection, + XA_STRING, mClipboardProperty, x86UNIXState->getWindow(), CurrentTime); + + // flush the output buffer to make sure the selection request event gets + // sent now + XFlush(display); + + XEvent xevent; + + // if our window is the current owner, (e.g. copy from one part of + // torque and paste to another), then we just sent an event to our + // window that won't get processed until we get back to the event + // loop in x86Unixwindow. So look for selection request events in + // the event queue immediately and handle them. + while (XCheckTypedWindowEvent(display, + x86UNIXState->getWindow(), SelectionRequest, &xevent)) + handleSelectionRequest(xevent.xselectionrequest); + + // poll for the SelectionNotify event for 5 seconds. in most cases + // we should get the event very quickly + U32 startTime = Platform::getRealMilliseconds(); + bool timeOut = false; + while (!XCheckTypedWindowEvent(display, + x86UNIXState->getWindow(), SelectionNotify, &xevent) && + !timeOut) + { + // we'll be spinning here, but who cares + if ((Platform::getRealMilliseconds() - startTime) > 5000) + timeOut = true; + } + + if (timeOut) + { + Con::warnf(ConsoleLogEntry::General, + "XClipboard: waited too long for owner to convert selection"); + return ""; + } + + if (xevent.xselection.property == None) + return ""; + + // free the X data from a previous get + freeXData(); + + // grab the string data from the property + Atom actual_type; + int actual_format; + unsigned long bytes_after; + unsigned long nitems; + // query the property length the 250000 is "the length in 32-bit + // multiples of the data to be retrieved". so we support up to a + // million bytes of returned data. + int numToRetrieve = 250000; + int status = XGetWindowProperty(display, + x86UNIXState->getWindow(), + mClipboardProperty, 0, numToRetrieve, True, XA_STRING, + &actual_type, &actual_format, &nitems, &bytes_after, &mXData); + + // we should have returned OK, with string type, 8bit data, + // and > 0 items. + if ((status != Success) || (actual_type != XA_STRING) || + (actual_format != 8) || (nitems == 0)) + return ""; + + // if there is data left in the clipboard, warn about it + if (bytes_after > 0) + Con::warnf(ConsoleLogEntry::General, + "XClipboard: some data was not retrieved"); + + return reinterpret_cast(mXData); +} + +//------------------------------------------------------------------------------ +void XClipboard::checkTDataSize(S32 requestedSize) +{ + if (mTDataSize < requestedSize) + { + freeTData(); + mTData = static_cast(dRealMalloc(sizeof(char) * requestedSize)); + AssertFatal(mTData, "unable to allocate clipboard buffer data!"); + mTDataSize = requestedSize; + } +} + +//------------------------------------------------------------------------------ +bool XClipboard::setClipboard(const char *text) +{ + DisplayPtrManager xdisplay; + Display* display = xdisplay.getDisplayPointer(); + + if (!mInitialized) + init(); + + // get the length of the text + S32 len = dStrlen(text) + 1; + + // reallocate the storage buffer if necessary + checkTDataSize(len); + + // copy the data into the storage buffer + dStrcpy(mTData, text); + + // tell X that we own the clipboard. (we'll get events + // if an app tries to paste) + XSetSelectionOwner(display, mClipboard, + x86UNIXState->getWindow(), CurrentTime); + + return true; +} + +//------------------------------------------------------------------------------ +void XClipboard::handleSelectionRequest(XSelectionRequestEvent& request) +{ + DisplayPtrManager xdisplay; + Display* display = xdisplay.getDisplayPointer(); + + // init our response + XSelectionEvent notify; + + notify.type = SelectionNotify; + notify.display = display; + notify.requestor = request.requestor; + notify.selection = request.selection; + notify.target = XA_STRING; + notify.property = None; + notify.time = CurrentTime; + + // make sure the owner is our window, and that the + // requestor wants the clipboard + if (request.owner == x86UNIXState->getWindow() && + request.selection == mClipboard) + { + notify.property = request.property; + // check to see if they did not set the property + if (notify.property == None) + notify.property = mClipboardProperty; + + // get the length of the data in the clipboard + S32 length = dStrlen(mTData); + // set the property on the requestor window + XChangeProperty(display, request.requestor, + notify.property, XA_STRING, + 8, PropModeReplace, reinterpret_cast(mTData), + length); + } + XSendEvent(display, notify.requestor, False, 0, + reinterpret_cast(¬ify)); + + // flush the output buffer to send the event now + XFlush(display); +} diff --git a/platformX86UNIX/x86UNIXInputManager.client.cpp b/platformX86UNIX/x86UNIXInputManager.client.cpp new file mode 100644 index 0000000..e73df80 --- /dev/null +++ b/platformX86UNIX/x86UNIXInputManager.client.cpp @@ -0,0 +1,1818 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "console/consoleTypes.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "platformX86UNIX/x86UNIXState.h" +#include "platformX86UNIX/x86UNIXInputManager.h" +#include "math/mMathFn.h" + +#include +#include +#include +#include + +#include + +// ascii table +AsciiData AsciiTable[NUM_KEYS]; + +// keymap table +static const U32 SDLtoTKeyMapSize = SDLK_LAST; +static U8 SDLtoTKeyMap[SDLtoTKeyMapSize]; +static bool keyMapsInitialized = false; + +// helper functions +static void MapKey(Uint16 SDLkey, U8 tkey, KeySym xkeysym); +static void InitKeyMaps(); +static inline U8 TranslateSDLKeytoTKey(SDLKey keysym); + +// unix platform state +extern x86UNIXPlatformState * x86UNIXState; + +// constants + +static const U32 MouseMask = SDL_MOUSEEVENTMASK; +static const U32 KeyboardMask = SDL_KEYUPMASK | SDL_KEYDOWNMASK; +static const U32 JoystickMask = SDL_JOYEVENTMASK; + +static const U32 AllInputEvents = MouseMask | KeyboardMask | JoystickMask; + +// defined in SDL +extern "C" Uint16 X11_KeyToUnicode( SDLKey keysym, SDLMod modifiers ); + +//============================================================================== +// Static helper functions +//============================================================================== +static void MapKey(Uint16 SDLkey, U8 tkey, KeySym xkeysym) +{ + DisplayPtrManager xdisplay; + Display* display = xdisplay.getDisplayPointer(); + + SDLtoTKeyMap[SDLkey] = tkey; + + Uint16 key = 0; + SDLKey skey = (SDLKey)SDLkey; + SDLMod mod = KMOD_NONE; + // lower case + key = X11_KeyToUnicode( skey, mod ); + AsciiTable[tkey].lower.ascii = key; + // upper case + mod = KMOD_LSHIFT; + key = X11_KeyToUnicode( skey, mod ); + AsciiTable[tkey].upper.ascii = key; + // goofy (i18n) case + mod = KMOD_MODE; + key = X11_KeyToUnicode( skey, mod ); + AsciiTable[tkey].goofy.ascii = key; + +#if 0 + if (xkeysym == 0) + return; + + XKeyPressedEvent fooKey; + const int keybufSize = 256; + char keybuf[keybufSize]; + + // find the x keycode for the keysym + KeyCode xkeycode = XKeysymToKeycode( + display, xkeysym); + +// Display *dpy = XOpenDisplay(NULL); +// KeyCode xkeycode = XKeysymToKeycode( +// dpy, xkeysym); + + if (!xkeycode) + return; + + // create an event with the keycode + dMemset(&fooKey, 0, sizeof(fooKey)); + fooKey.type = KeyPress; + fooKey.display = display; + fooKey.window = DefaultRootWindow(display); + fooKey.time = CurrentTime; + fooKey.keycode = xkeycode; + + // translate the event with no modifiers (yields lowercase) + KeySym dummyKeySym; + int numChars = XLookupString( + &fooKey, keybuf, keybufSize, &dummyKeySym, NULL); + if (numChars) + { + //Con::printf("assigning lowercase string %c", *keybuf); + // ignore everything but first char + AsciiTable[tkey].lower.ascii = *keybuf; + AsciiTable[tkey].goofy.ascii = *keybuf; + } + + // translate the event with shift modifier (yields uppercase) + fooKey.state |= ShiftMask; + numChars = XLookupString(&fooKey, keybuf, keybufSize, &dummyKeySym, NULL); + if (numChars) + { + //Con::printf("assigning uppercase string %c", *keybuf); + // ignore everything but first char + AsciiTable[tkey].upper.ascii = *keybuf; + } +#endif +} + +//------------------------------------------------------------------------------ +void InitKeyMaps() +{ + dMemset( &AsciiTable, 0, sizeof( AsciiTable ) ); + dMemset(SDLtoTKeyMap, KEY_NULL, SDLtoTKeyMapSize); + + // set up the X to Torque key map + // stuff + MapKey(SDLK_BACKSPACE, KEY_BACKSPACE, XK_BackSpace); + MapKey(SDLK_TAB, KEY_TAB, XK_Tab); + MapKey(SDLK_RETURN, KEY_RETURN, XK_Return); + MapKey(SDLK_PAUSE, KEY_PAUSE, XK_Pause); + MapKey(SDLK_CAPSLOCK, KEY_CAPSLOCK, XK_Caps_Lock); + MapKey(SDLK_ESCAPE, KEY_ESCAPE, XK_Escape); + + // more stuff + MapKey(SDLK_SPACE, KEY_SPACE, XK_space); + MapKey(SDLK_PAGEDOWN, KEY_PAGE_DOWN, XK_Page_Down); + MapKey(SDLK_PAGEUP, KEY_PAGE_UP, XK_Page_Up); + MapKey(SDLK_END, KEY_END, XK_End); + MapKey(SDLK_HOME, KEY_HOME, XK_Home); + MapKey(SDLK_LEFT, KEY_LEFT, XK_Left); + MapKey(SDLK_UP, KEY_UP, XK_Up); + MapKey(SDLK_RIGHT, KEY_RIGHT, XK_Right); + MapKey(SDLK_DOWN, KEY_DOWN, XK_Down); + MapKey(SDLK_PRINT, KEY_PRINT, XK_Print); + MapKey(SDLK_INSERT, KEY_INSERT, XK_Insert); + MapKey(SDLK_DELETE, KEY_DELETE, XK_Delete); + + S32 keysym; + S32 tkeycode; + KeySym xkey; + // main numeric keys + for (keysym = SDLK_0, tkeycode = KEY_0, xkey = XK_0; + keysym <= SDLK_9; + ++keysym, ++tkeycode, ++xkey) + MapKey(static_cast(keysym), tkeycode, xkey); + + // lowercase letters + for (keysym = SDLK_a, tkeycode = KEY_A, xkey = XK_a; + keysym <= SDLK_z; + ++keysym, ++tkeycode, ++xkey) + MapKey(static_cast(keysym), tkeycode, xkey); + + // various punctuation + MapKey('|', KEY_TILDE, XK_grave); + MapKey(SDLK_BACKQUOTE, KEY_TILDE, XK_grave); + MapKey(SDLK_MINUS, KEY_MINUS, XK_minus); + MapKey(SDLK_EQUALS, KEY_EQUALS, XK_equal); + MapKey(SDLK_LEFTBRACKET, KEY_LBRACKET, XK_bracketleft); + MapKey('{', KEY_LBRACKET, XK_bracketleft); + MapKey(SDLK_RIGHTBRACKET, KEY_RBRACKET, XK_bracketright); + MapKey('}', KEY_RBRACKET, XK_bracketright); + MapKey(SDLK_BACKSLASH, KEY_BACKSLASH, XK_backslash); + MapKey(SDLK_SEMICOLON, KEY_SEMICOLON, XK_semicolon); + MapKey(SDLK_QUOTE, KEY_APOSTROPHE, XK_apostrophe); + MapKey(SDLK_COMMA, KEY_COMMA, XK_comma); + MapKey(SDLK_PERIOD, KEY_PERIOD, XK_period); + MapKey(SDLK_SLASH, KEY_SLASH, XK_slash); + + // numpad numbers + for (keysym = SDLK_KP0, tkeycode = KEY_NUMPAD0, xkey = XK_KP_0; + keysym <= SDLK_KP9; + ++keysym, ++tkeycode, ++xkey) + MapKey(static_cast(keysym), tkeycode, xkey); + + // other numpad stuff + MapKey(SDLK_KP_MULTIPLY, KEY_MULTIPLY, XK_KP_Multiply); + MapKey(SDLK_KP_PLUS, KEY_ADD, XK_KP_Add); + MapKey(SDLK_KP_EQUALS, KEY_SEPARATOR, XK_KP_Separator); + MapKey(SDLK_KP_MINUS, KEY_SUBTRACT, XK_KP_Subtract); + MapKey(SDLK_KP_PERIOD, KEY_DECIMAL, XK_KP_Decimal); + MapKey(SDLK_KP_DIVIDE, KEY_DIVIDE, XK_KP_Divide); + MapKey(SDLK_KP_ENTER, KEY_NUMPADENTER, XK_KP_Enter); + + // F keys + for (keysym = SDLK_F1, tkeycode = KEY_F1, xkey = XK_F1; + keysym <= SDLK_F15; + ++keysym, ++tkeycode, ++xkey) + MapKey(static_cast(keysym), tkeycode, xkey); + + // various modifiers + MapKey(SDLK_NUMLOCK, KEY_NUMLOCK, XK_Num_Lock); + MapKey(SDLK_SCROLLOCK, KEY_SCROLLLOCK, XK_Scroll_Lock); + MapKey(SDLK_LCTRL, KEY_LCONTROL, XK_Control_L); + MapKey(SDLK_RCTRL, KEY_RCONTROL, XK_Control_R); + MapKey(SDLK_LALT, KEY_LALT, XK_Alt_L); + MapKey(SDLK_RALT, KEY_RALT, XK_Alt_R); + MapKey(313, KEY_RALT, XK_Alt_R); + MapKey(SDLK_LSHIFT, KEY_LSHIFT, XK_Shift_L); + MapKey(SDLK_RSHIFT, KEY_RSHIFT, XK_Shift_R); + MapKey(SDLK_LSUPER, KEY_WIN_LWINDOW, 0); + MapKey(SDLK_RSUPER, KEY_WIN_RWINDOW, 0); + MapKey(SDLK_MENU, KEY_WIN_APPS, 0); + MapKey(SDLK_MODE, KEY_OEM_102, 0); + + keyMapsInitialized = true; +}; + +//------------------------------------------------------------------------------ +U8 TranslateSDLKeytoTKey(SDLKey keysym) +{ + if (!keyMapsInitialized) + { + Con::printf("WARNING: SDLkeysymMap is not initialized"); + return 0; + } + if (keysym < 0 || + static_cast(keysym) >= SDLtoTKeyMapSize) + { + Con::printf("WARNING: invalid keysym: %d", keysym); + return 0; + } + return SDLtoTKeyMap[keysym]; +} + +//------------------------------------------------------------------------------ +// this shouldn't be used, use TranslateSDLKeytoTKey instead +U8 TranslateOSKeyCode(U8 vcode) +{ + Con::printf("WARNING: TranslateOSKeyCode is not supported in unix"); + return 0; +} + +//============================================================================== +// UInputManager +//============================================================================== +UInputManager::UInputManager() +{ + mActive = false; + mEnabled = false; + mLocking = true; // locking enabled by default + mKeyboardEnabled = mMouseEnabled = mJoystickEnabled = false; + mKeyboardActive = mMouseActive = mJoystickActive = false; +} + +//------------------------------------------------------------------------------ +void UInputManager::init() +{ + Con::addVariable( "pref::Input::KeyboardEnabled", + TypeBool, &mKeyboardEnabled ); + Con::addVariable( "pref::Input::MouseEnabled", + TypeBool, &mMouseEnabled ); + Con::addVariable( "pref::Input::JoystickEnabled", + TypeBool, &mJoystickEnabled ); +} + +//------------------------------------------------------------------------------ +bool UInputManager::enable() +{ + disable(); +#ifdef LOG_INPUT + Input::log( "Enabling Input...\n" ); +#endif + + mModifierKeys = 0; + dMemset( mMouseButtonState, 0, sizeof( mMouseButtonState ) ); + dMemset( mKeyboardState, 0, 256 ); + + InitKeyMaps(); + + mJoystickEnabled = false; + initJoystick(); + + mEnabled = true; + mMouseEnabled = true; + mKeyboardEnabled = true; + + SDL_EnableKeyRepeat( + SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL); + + return true; +} + +//------------------------------------------------------------------------------ +void UInputManager::disable() +{ + deactivate(); + mEnabled = false; + return; +} + +//------------------------------------------------------------------------------ +void UInputManager::initJoystick() +{ + mJoystickList.clear(); + + // initialize SDL joystick system + if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) + { + Con::warnf(" Unable to initialize joystick: %s", SDL_GetError()); + return; + } + + int numJoysticks = SDL_NumJoysticks(); + if (numJoysticks == 0) + Con::printf(" No joysticks found."); + + // disable joystick events (use polling instead) + SDL_JoystickEventState(SDL_IGNORE); + + // install joysticks + for(int i = 0; i < numJoysticks; i++ ) + { + JoystickInputDevice* newDevice = new JoystickInputDevice(i); + addObject(newDevice); + mJoystickList.push_back(newDevice); + Con::printf(" %s: %s", + newDevice->getDeviceName(), newDevice->getName()); +#ifdef LOG_INPUT + Input::log(" %s: %s\n", + newDevice->getDeviceName(), newDevice->getName()); +#endif + } + + mJoystickEnabled = true; +} + +//------------------------------------------------------------------------------ +void UInputManager::activate() +{ + if (mEnabled && !isActive()) + { + mActive = true; + SDL_ShowCursor(SDL_DISABLE); + resetInputState(); + // hack; if the mouse or keyboard has been disabled, re-enable them. + // prevents scripts like default.cs from breaking our input, although + // there is probably a better solution + mMouseEnabled = mKeyboardEnabled = true; + activateMouse(); + activateKeyboard(); + activateJoystick(); + if (x86UNIXState->windowLocked()) + lockInput(); + } +} + +//------------------------------------------------------------------------------ +void UInputManager::deactivate() +{ + if (mEnabled && isActive()) + { + unlockInput(); + deactivateKeyboard(); + deactivateMouse(); + deactivateJoystick(); + resetInputState(); + SDL_ShowCursor(SDL_ENABLE); + mActive = false; + } +} + +//------------------------------------------------------------------------------ +void UInputManager::resetKeyboardState() +{ + // unpress any pressed keys; in the future we may want + // to actually sync with the keyboard state + for (int i = 0; i < 256; ++i) + { + if (mKeyboardState[i]) + { + InputEvent event; + + event.deviceInst = 0; + event.deviceType = KeyboardDeviceType; + event.objType = SI_KEY; + event.objInst = i; + event.action = SI_BREAK; + event.fValue = 0.0; + Game->postEvent(event); + } + } + dMemset(mKeyboardState, 0, 256); + + // clear modifier keys + mModifierKeys = 0; +} + +//------------------------------------------------------------------------------ +void UInputManager::resetMouseState() +{ + // unpress any buttons; in the future we may want + // to actually sync with the mouse state + for (int i = 0; i < 3; ++i) + { + if (mMouseButtonState[i]) + { + // add KEY_BUTTON0 to the index to get the real + // button ID + S32 buttonID = i + KEY_BUTTON0; + InputEvent event; + + event.deviceInst = 0; + event.deviceType = MouseDeviceType; + event.objType = SI_BUTTON; + event.objInst = buttonID; + event.action = SI_BREAK; + event.fValue = 0.0; + Game->postEvent(event); + } + } + + dMemset(mMouseButtonState, 0, 3); +} + +//------------------------------------------------------------------------------ +void UInputManager::resetInputState() +{ + resetKeyboardState(); + resetMouseState(); + + // reset joysticks + for (Vector::iterator iter = mJoystickList.begin(); + iter != mJoystickList.end(); + ++iter) + { + (*iter)->reset(); + } + + // JMQTODO: make event arrays be members + // dispose of any lingering SDL input events + static const int MaxEvents = 255; + static SDL_Event events[MaxEvents]; + SDL_PumpEvents(); + SDL_PeepEvents(events, MaxEvents, SDL_GETEVENT, + AllInputEvents); +} + +//------------------------------------------------------------------------------ +void UInputManager::setLocking(bool enabled) +{ + mLocking = enabled; + if (mLocking) + lockInput(); + else + unlockInput(); +} + +//------------------------------------------------------------------------------ +void UInputManager::lockInput() +{ + if (x86UNIXState->windowActive() && x86UNIXState->windowLocked() && + mLocking && + SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_OFF) + SDL_WM_GrabInput(SDL_GRAB_ON); +} + +//------------------------------------------------------------------------------ +void UInputManager::unlockInput() +{ + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON) + SDL_WM_GrabInput(SDL_GRAB_OFF); +} + +//------------------------------------------------------------------------------ +void UInputManager::onDeleteNotify( SimObject* object ) +{ + Parent::onDeleteNotify( object ); +} + +//------------------------------------------------------------------------------ +bool UInputManager::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + return true; +} + +//------------------------------------------------------------------------------ +void UInputManager::onRemove() +{ + deactivate(); + Parent::onRemove(); +} + +//------------------------------------------------------------------------------ +void UInputManager::mouseMotionEvent(const SDL_Event& event) +{ +// Con::printf("motion event: %d %d %d %d", +// event.motion.xrel, event.motion.yrel, +// event.motion.x, event.motion.y); + if (x86UNIXState->windowLocked()) + { + InputEvent ievent; + ievent.deviceInst = 0; + ievent.deviceType = MouseDeviceType; + ievent.objInst = 0; + ievent.modifier = mModifierKeys; + ievent.ascii = 0; + ievent.action = SI_MOVE; + + // post events if things have changed + if (event.motion.xrel != 0) + { + ievent.objType = SI_XAXIS; + ievent.fValue = event.motion.xrel; + Game->postEvent(ievent); + } + if (event.motion.yrel != 0) + { + ievent.objType = SI_YAXIS; + ievent.fValue = event.motion.yrel; + Game->postEvent(ievent); + } +#ifdef LOG_INPUT +#ifdef LOG_MOUSEMOVE + Input::log( "EVENT (Input): Mouse relative move (%.1f, %.1f).\n", + event.motion.xrel != 0 ? F32(event.motion.xrel) : 0.0, + event.motion.yrel != 0 ? F32(event.motion.yrel) : 0.0); +#endif +#endif + } + else + { + MouseMoveEvent mmevent; + mmevent.xPos = mLastMouseX = event.motion.x; + mmevent.yPos = mLastMouseY = event.motion.y; + mmevent.modifier = mModifierKeys; + Game->postEvent(mmevent); +#ifdef LOG_INPUT +#ifdef LOG_MOUSEMOVE + Input::log( "EVENT (Input): Mouse absolute move (%.1f, %.1f).\n", + F32(event.motion.x), + F32(event.motion.y)); +#endif +#endif + } +} + +//------------------------------------------------------------------------------ +void UInputManager::joyButtonEvent(const SDL_Event& event) +{ + joyButtonEvent(event.jbutton.which, event.jbutton.button, + event.type == SDL_JOYBUTTONDOWN); +} + +//------------------------------------------------------------------------------ +void UInputManager::joyButtonEvent(U8 deviceID, U8 buttonNum, bool pressed) + +{ + S32 action = pressed ? SI_MAKE : SI_BREAK; + S32 objInst = buttonNum + KEY_BUTTON0; + + InputEvent ievent; + + ievent.deviceInst = deviceID; + ievent.deviceType = JoystickDeviceType; + ievent.modifier = mModifierKeys; + ievent.ascii = 0; + ievent.objType = SI_BUTTON; + ievent.objInst = objInst; + ievent.action = action; + ievent.fValue = (action == SI_MAKE) ? 1.0 : 0.0; + + Game->postEvent(ievent); +#ifdef LOG_INPUT + Input::log( "EVENT (Input): joystick%d button%d %s. MODS:%c%c%c \n", + deviceID, + buttonNum, + pressed ? "pressed" : "released", + ( mModifierKeys & SI_SHIFT ? 'S' : '.' ), + ( mModifierKeys & SI_CTRL ? 'C' : '.' ), + ( mModifierKeys & SI_ALT ? 'A' : '.' )); +#endif +} + +//------------------------------------------------------------------------------ +void UInputManager::joyHatEvent(U8 deviceID, U8 hatNum, + U8 prevHatState, U8 currHatState) +{ + if (prevHatState == currHatState) + return; + + InputEvent ievent; + + ievent.deviceInst = deviceID; + ievent.deviceType = JoystickDeviceType; + ievent.modifier = mModifierKeys; + ievent.ascii = 0; + ievent.objType = SI_POV; + + // first break any positions that are no longer valid + ievent.action = SI_BREAK; + ievent.fValue = 0.0; + + if (prevHatState & SDL_HAT_UP && !(currHatState & SDL_HAT_UP)) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Up POV released.\n"); +#endif + ievent.objInst = SI_UPOV; + Game->postEvent(ievent); + } + else if (prevHatState & SDL_HAT_DOWN && !(currHatState & SDL_HAT_DOWN)) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Down POV released.\n"); +#endif + ievent.objInst = SI_DPOV; + Game->postEvent(ievent); + } + if (prevHatState & SDL_HAT_LEFT && !(currHatState & SDL_HAT_LEFT)) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Left POV released.\n"); +#endif + ievent.objInst = SI_LPOV; + Game->postEvent(ievent); + } + else if (prevHatState & SDL_HAT_RIGHT && !(currHatState & SDL_HAT_RIGHT)) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Right POV released.\n"); +#endif + ievent.objInst = SI_RPOV; + Game->postEvent(ievent); + } + + // now do the make events + ievent.action = SI_MAKE; + ievent.fValue = 1.0; + + if (!(prevHatState & SDL_HAT_UP) && currHatState & SDL_HAT_UP) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Up POV pressed.\n"); +#endif + ievent.objInst = SI_UPOV; + Game->postEvent(ievent); + } + else if (!(prevHatState & SDL_HAT_DOWN) && currHatState & SDL_HAT_DOWN) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Down POV pressed.\n"); +#endif + ievent.objInst = SI_DPOV; + Game->postEvent(ievent); + } + if (!(prevHatState & SDL_HAT_LEFT) && currHatState & SDL_HAT_LEFT) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Left POV pressed.\n"); +#endif + ievent.objInst = SI_LPOV; + Game->postEvent(ievent); + } + else if (!(prevHatState & SDL_HAT_RIGHT) && currHatState & SDL_HAT_RIGHT) + { +#ifdef LOG_INPUT + Input::log( "EVENT (Input): Right POV pressed.\n"); +#endif + ievent.objInst = SI_RPOV; + Game->postEvent(ievent); + } +} + +//------------------------------------------------------------------------------ +void UInputManager::joyAxisEvent(const SDL_Event& event) +{ + joyAxisEvent(event.jaxis.which, event.jaxis.axis, event.jaxis.value); +} + +//------------------------------------------------------------------------------ +void UInputManager::joyAxisEvent(U8 deviceID, U8 axisNum, S16 axisValue) +{ + JoystickInputDevice* stick; + + stick = mJoystickList[deviceID]; + AssertFatal(stick, "JoystickInputDevice* is NULL"); + JoystickAxisInfo axisInfo = stick->getAxisInfo(axisNum); + + if (axisInfo.type == -1) + return; + + // scale the value to [-1,1] + F32 scaledValue = 0; + if (axisValue < 0) + scaledValue = -F32(axisValue) / axisInfo.minValue; + else if (axisValue > 0) + scaledValue = F32(axisValue) / axisInfo.maxValue; + +// F32 range = F32(axisInfo.maxValue - axisInfo.minValue); +// F32 scaledValue = F32((2 * axisValue) - axisInfo.maxValue - +// axisInfo.minValue) / range; + + if (scaledValue > 1.f) + scaledValue = 1.f; + else if (scaledValue < -1.f) + scaledValue = -1.f; + + // create and post the event + InputEvent ievent; + + ievent.deviceInst = deviceID; + ievent.deviceType = JoystickDeviceType; + ievent.modifier = mModifierKeys; + ievent.ascii = 0; + ievent.objType = axisInfo.type; + ievent.objInst = 0; + ievent.action = SI_MOVE; + ievent.fValue = scaledValue; + + Game->postEvent(ievent); + +#ifdef LOG_INPUT + Input::log( "EVENT (Input): joystick axis %d moved: %.1f.\n", + axisNum, ievent.fValue); +#endif + +} + +//------------------------------------------------------------------------------ +void UInputManager::mouseButtonEvent(const SDL_Event& event) +{ + S32 action = (event.type == SDL_MOUSEBUTTONDOWN) ? SI_MAKE : SI_BREAK; + S32 objInst = -1; + // JMQTODO: support wheel delta like windows version? + // JMQTODO: make this value configurable? + S32 wheelDelta = 10; + bool wheel = false; + + switch (event.button.button) + { + case SDL_BUTTON_LEFT: + objInst = KEY_BUTTON0; + break; + case SDL_BUTTON_RIGHT: + objInst = KEY_BUTTON1; + break; + case SDL_BUTTON_MIDDLE: + objInst = KEY_BUTTON2; + break; + case Button4: + wheel = true; + break; + case Button5: + wheel = true; + wheelDelta = -wheelDelta; + break; + } + + if (objInst == -1 && !wheel) + // unsupported button + return; + + InputEvent ievent; + + ievent.deviceInst = 0; + ievent.deviceType = MouseDeviceType; + ievent.modifier = mModifierKeys; + ievent.ascii = 0; + + if (wheel) + { + // SDL generates a button press/release for each wheel move, + // so ignore breaks to translate those into a single event + if (action == SI_BREAK) + return; + ievent.objType = SI_ZAXIS; + ievent.objInst = 0; + ievent.action = SI_MOVE; + ievent.fValue = wheelDelta; +#ifdef LOG_INPUT + Input::log( "EVENT (Input): mouse wheel moved %s: %.1f. MODS:%c%c%c\n", + wheelDelta > 0 ? "up" : "down", + ievent.fValue, + ( mModifierKeys & SI_SHIFT ? 'S' : '.' ), + ( mModifierKeys & SI_CTRL ? 'C' : '.' ), + ( mModifierKeys & SI_ALT ? 'A' : '.' )); +#endif + } + else // regular button + { + S32 buttonID = (objInst - KEY_BUTTON0); + if (buttonID < 3) + mMouseButtonState[buttonID] = ( action == SI_MAKE ) ? true : false; + + ievent.objType = SI_BUTTON; + ievent.objInst = objInst; + ievent.action = action; + ievent.fValue = (action == SI_MAKE) ? 1.0 : 0.0; +#ifdef LOG_INPUT + Input::log( "EVENT (Input): mouse button%d %s. MODS:%c%c%c\n", + buttonID, + action == SI_MAKE ? "pressed" : "released", + ( mModifierKeys & SI_SHIFT ? 'S' : '.' ), + ( mModifierKeys & SI_CTRL ? 'C' : '.' ), + ( mModifierKeys & SI_ALT ? 'A' : '.' )); +#endif + } + + Game->postEvent(ievent); +} + +//------------------------------------------------------------------------------ +const char* getKeyName( U16 key ) +{ + switch ( key ) + { + case KEY_BACKSPACE: return "Backspace"; + case KEY_TAB: return "Tab"; + case KEY_RETURN: return "Return"; + case KEY_PAUSE: return "Pause"; + case KEY_CAPSLOCK: return "CapsLock"; + case KEY_ESCAPE: return "Esc"; + + case KEY_SPACE: return "SpaceBar"; + case KEY_PAGE_DOWN: return "PageDown"; + case KEY_PAGE_UP: return "PageUp"; + case KEY_END: return "End"; + case KEY_HOME: return "Home"; + case KEY_LEFT: return "Left"; + case KEY_UP: return "Up"; + case KEY_RIGHT: return "Right"; + case KEY_DOWN: return "Down"; + case KEY_PRINT: return "PrintScreen"; + case KEY_INSERT: return "Insert"; + case KEY_DELETE: return "Delete"; + case KEY_HELP: return "Help"; + + case KEY_NUMPAD0: return "Numpad 0"; + case KEY_NUMPAD1: return "Numpad 1"; + case KEY_NUMPAD2: return "Numpad 2"; + case KEY_NUMPAD3: return "Numpad 3"; + case KEY_NUMPAD4: return "Numpad 4"; + case KEY_NUMPAD5: return "Numpad 5"; + case KEY_NUMPAD6: return "Numpad 6"; + case KEY_NUMPAD7: return "Numpad 7"; + case KEY_NUMPAD8: return "Numpad 8"; + case KEY_NUMPAD9: return "Numpad 9"; + case KEY_MULTIPLY: return "Multiply"; + case KEY_ADD: return "Add"; + case KEY_SEPARATOR: return "Separator"; + case KEY_SUBTRACT: return "Subtract"; + case KEY_DECIMAL: return "Decimal"; + case KEY_DIVIDE: return "Divide"; + case KEY_NUMPADENTER: return "Numpad Enter"; + + case KEY_F1: return "F1"; + case KEY_F2: return "F2"; + case KEY_F3: return "F3"; + case KEY_F4: return "F4"; + case KEY_F5: return "F5"; + case KEY_F6: return "F6"; + case KEY_F7: return "F7"; + case KEY_F8: return "F8"; + case KEY_F9: return "F9"; + case KEY_F10: return "F10"; + case KEY_F11: return "F11"; + case KEY_F12: return "F12"; + case KEY_F13: return "F13"; + case KEY_F14: return "F14"; + case KEY_F15: return "F15"; + case KEY_F16: return "F16"; + case KEY_F17: return "F17"; + case KEY_F18: return "F18"; + case KEY_F19: return "F19"; + case KEY_F20: return "F20"; + case KEY_F21: return "F21"; + case KEY_F22: return "F22"; + case KEY_F23: return "F23"; + case KEY_F24: return "F24"; + + case KEY_NUMLOCK: return "NumLock"; + case KEY_SCROLLLOCK: return "ScrollLock"; + case KEY_LCONTROL: return "LCtrl"; + case KEY_RCONTROL: return "RCtrl"; + case KEY_LALT: return "LAlt"; + case KEY_RALT: return "RAlt"; + case KEY_LSHIFT: return "LShift"; + case KEY_RSHIFT: return "RShift"; + + case KEY_WIN_LWINDOW: return "LWin"; + case KEY_WIN_RWINDOW: return "RWin"; + case KEY_WIN_APPS: return "Apps"; + } + + static char returnString[5]; + dSprintf( returnString, sizeof( returnString ), "%c", Input::getAscii( key, STATE_UPPER ) ); + return returnString; +} + + +//------------------------------------------------------------------------------ +void UInputManager::keyEvent(const SDL_Event& event) +{ + S32 action = (event.type == SDL_KEYDOWN) ? SI_MAKE : SI_BREAK; + InputEvent ievent; + + ievent.deviceInst = 0; + ievent.deviceType = KeyboardDeviceType; + ievent.objType = SI_KEY; + ievent.objInst = TranslateSDLKeytoTKey(event.key.keysym.sym); + // if the action is a make but this key is already pressed, + // count it as a repeat + if (action == SI_MAKE && mKeyboardState[ievent.objInst]) + action = SI_REPEAT; + ievent.action = action; + ievent.fValue = (action == SI_MAKE || action == SI_REPEAT) ? 1.0 : 0.0; + + processKeyEvent(ievent); + Game->postEvent(ievent); + +#if 0 + if (ievent.action == SI_MAKE) + dPrintf("key event: : %s key pressed. MODS:%c%c%c\n", + getKeyName(ievent.objInst), + ( mModifierKeys & SI_SHIFT ? 'S' : '.' ), + ( mModifierKeys & SI_CTRL ? 'C' : '.' ), + ( mModifierKeys & SI_ALT ? 'A' : '.' )); + else if (ievent.action == SI_REPEAT) + dPrintf("key event: : %s key repeated. MODS:%c%c%c\n", + getKeyName(ievent.objInst), + ( mModifierKeys & SI_SHIFT ? 'S' : '.' ), + ( mModifierKeys & SI_CTRL ? 'C' : '.' ), + ( mModifierKeys & SI_ALT ? 'A' : '.' )); + else if (ievent.action == SI_BREAK) + dPrintf("key event: : %s key released. MODS:%c%c%c\n", + getKeyName(ievent.objInst), + ( mModifierKeys & SI_SHIFT ? 'S' : '.' ), + ( mModifierKeys & SI_CTRL ? 'C' : '.' ), + ( mModifierKeys & SI_ALT ? 'A' : '.' )); + else + dPrintf("unknown key event!\n"); +#endif + +#ifdef LOG_INPUT + Input::log( "EVENT (Input): %s key %s. MODS:%c%c%c\n", + getKeyName(ievent.objInst), + action == SI_MAKE ? "pressed" : "released", + ( mModifierKeys & SI_SHIFT ? 'S' : '.' ), + ( mModifierKeys & SI_CTRL ? 'C' : '.' ), + ( mModifierKeys & SI_ALT ? 'A' : '.' )); +#endif +} + +//------------------------------------------------------------------------------ +// This function was ripped from DInputDevice almost entirely intact. +bool UInputManager::processKeyEvent( InputEvent &event ) +{ + if ( event.deviceType != KeyboardDeviceType || event.objType != SI_KEY ) + return false; + + bool modKey = false; + U8 keyCode = event.objInst; + + if ( event.action == SI_MAKE || event.action == SI_REPEAT) + { + // Maintain the key structure: + mKeyboardState[keyCode] = true; + + switch ( event.objInst ) + { + case KEY_LSHIFT: + mModifierKeys |= SI_LSHIFT; + modKey = true; + break; + + case KEY_RSHIFT: + mModifierKeys |= SI_RSHIFT; + modKey = true; + break; + + case KEY_LCONTROL: + mModifierKeys |= SI_LCTRL; + modKey = true; + break; + + case KEY_RCONTROL: + mModifierKeys |= SI_RCTRL; + modKey = true; + break; + + case KEY_LALT: + mModifierKeys |= SI_LALT; + modKey = true; + break; + + case KEY_RALT: + mModifierKeys |= SI_RALT; + modKey = true; + break; + } + } + else + { + // Maintain the keys structure: + mKeyboardState[keyCode] = false; + + switch ( event.objInst ) + { + case KEY_LSHIFT: + mModifierKeys &= ~SI_LSHIFT; + modKey = true; + break; + + case KEY_RSHIFT: + mModifierKeys &= ~SI_RSHIFT; + modKey = true; + break; + + case KEY_LCONTROL: + mModifierKeys &= ~SI_LCTRL; + modKey = true; + break; + + case KEY_RCONTROL: + mModifierKeys &= ~SI_RCTRL; + modKey = true; + break; + + case KEY_LALT: + mModifierKeys &= ~SI_LALT; + modKey = true; + break; + + case KEY_RALT: + mModifierKeys &= ~SI_RALT; + modKey = true; + break; + } + } + + if ( modKey ) + event.modifier = 0; + else + event.modifier = mModifierKeys; + + // TODO: alter this getAscii call + KEY_STATE state = STATE_LOWER; + if (event.modifier & (SI_CTRL|SI_ALT) ) + { + state = STATE_GOOFY; + } + if ( event.modifier & SI_SHIFT ) + { + state = STATE_UPPER; + } + + event.ascii = Input::getAscii( event.objInst, state ); + + return modKey; +} + +//------------------------------------------------------------------------------ +void UInputManager::setWindowLocked(bool locked) +{ + if (locked) + lockInput(); + else + { + unlockInput(); + // SDL keeps track of abs mouse position in fullscreen mode, which means + // that if you switch to unlocked mode while fullscreen, the mouse will + // suddenly warp to someplace unexpected on screen. To fix this, we + // warp the mouse to the last known Torque abs mouse position. + if (mLastMouseX != -1 && mLastMouseY != -1) + SDL_WarpMouse(mLastMouseX, mLastMouseY); + } +} + +//------------------------------------------------------------------------------ +void UInputManager::process() +{ + if (!mEnabled || !isActive()) + return; + + // JMQTODO: make these be class members + static const int MaxEvents = 255; + static SDL_Event events[MaxEvents]; + + U32 mask = 0; + + // process keyboard and mouse events + if (mMouseActive) + mask |= MouseMask; + if (mKeyboardActive) + mask |= KeyboardMask; + + if (mask != 0) + { + SDL_PumpEvents(); + S32 numEvents = SDL_PeepEvents(events, MaxEvents, SDL_GETEVENT, mask); + + for (int i = 0; i < numEvents; ++i) + { + switch (events[i].type) + { + case SDL_MOUSEMOTION: + mouseMotionEvent(events[i]); + break; + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEBUTTONDOWN: + mouseButtonEvent(events[i]); + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + keyEvent(events[i]); + break; + } + } + } + + // poll joysticks + if (!mJoystickActive) + return; + + SDL_JoystickUpdate(); + + for (Vector::iterator iter = mJoystickList.begin(); + iter != mJoystickList.end(); + ++iter) + { + (*iter)->process(); + } +} + +//------------------------------------------------------------------------------ +bool UInputManager::enableKeyboard() +{ + if ( !isEnabled() ) + return( false ); + + if ( isKeyboardEnabled() && isKeyboardActive() ) + return( true ); + + mKeyboardEnabled = true; + if ( isActive() ) + mKeyboardEnabled = activateKeyboard(); + + if ( mKeyboardEnabled ) + { + Con::printf( "Keyboard enabled." ); +#ifdef LOG_INPUT + Input::log( "Keyboard enabled.\n" ); +#endif + } + else + { + Con::warnf( "Keyboard failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Keyboard failed to enable!\n" ); +#endif + } + + return( mKeyboardEnabled ); +} + +//------------------------------------------------------------------------------ +void UInputManager::disableKeyboard() +{ + if ( !isEnabled() || !isKeyboardEnabled()) + return; + + deactivateKeyboard(); + mKeyboardEnabled = false; + Con::printf( "Keyboard disabled." ); +#ifdef LOG_INPUT + Input::log( "Keyboard disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool UInputManager::activateKeyboard() +{ + if ( !isEnabled() || !isActive() || !isKeyboardEnabled() ) + return( false ); + + mKeyboardActive = true; +#ifdef LOG_INPUT + Input::log( mKeyboardActive ? "Keyboard activated.\n" : "Keyboard failed to activate!\n" ); +#endif + return( mKeyboardActive ); +} + +//------------------------------------------------------------------------------ +void UInputManager::deactivateKeyboard() +{ + if ( isEnabled() && isKeyboardActive() ) + { + mKeyboardActive = false; +#ifdef LOG_INPUT + Input::log( "Keyboard deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +bool UInputManager::enableMouse() +{ + if ( !isEnabled() ) + return( false ); + + if ( isMouseEnabled() && isMouseActive() ) + return( true ); + + mMouseEnabled = true; + if ( isActive() ) + mMouseEnabled = activateMouse(); + + if ( mMouseEnabled ) + { + Con::printf( "Mouse enabled." ); +#ifdef LOG_INPUT + Input::log( "Mouse enabled.\n" ); +#endif + } + else + { + Con::warnf( "Mouse failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Mouse failed to enable!\n" ); +#endif + } + + return( mMouseEnabled ); +} + +//------------------------------------------------------------------------------ +void UInputManager::disableMouse() +{ + if ( !isEnabled() || !isMouseEnabled()) + return; + + deactivateMouse(); + mMouseEnabled = false; + Con::printf( "Mouse disabled." ); +#ifdef LOG_INPUT + Input::log( "Mouse disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool UInputManager::activateMouse() +{ + if ( !isEnabled() || !isActive() || !isMouseEnabled() ) + return( false ); + + mMouseActive = true; +#ifdef LOG_INPUT + Input::log( mMouseActive ? + "Mouse activated.\n" : "Mouse failed to activate!\n" ); +#endif + return( mMouseActive ); +} + +//------------------------------------------------------------------------------ +void UInputManager::deactivateMouse() +{ + if ( isEnabled() && isMouseActive() ) + { + mMouseActive = false; +#ifdef LOG_INPUT + Input::log( "Mouse deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +bool UInputManager::enableJoystick() +{ + if ( !isEnabled() ) + return( false ); + + if ( isJoystickEnabled() && isJoystickActive() ) + return( true ); + + mJoystickEnabled = true; + if ( isActive() ) + mJoystickEnabled = activateJoystick(); + + if ( mJoystickEnabled ) + { + Con::printf( "Joystick enabled." ); +#ifdef LOG_INPUT + Input::log( "Joystick enabled.\n" ); +#endif + } + else + { + Con::warnf( "Joystick failed to enable!" ); +#ifdef LOG_INPUT + Input::log( "Joystick failed to enable!\n" ); +#endif + } + + return( mJoystickEnabled ); +} + +//------------------------------------------------------------------------------ +void UInputManager::disableJoystick() +{ + if ( !isEnabled() || !isJoystickEnabled()) + return; + + deactivateJoystick(); + mJoystickEnabled = false; + Con::printf( "Joystick disabled." ); +#ifdef LOG_INPUT + Input::log( "Joystick disabled.\n" ); +#endif +} + +//------------------------------------------------------------------------------ +bool UInputManager::activateJoystick() +{ + if ( !isEnabled() || !isActive() || !isJoystickEnabled() ) + return( false ); + + mJoystickActive = false; + JoystickInputDevice* dptr; + for ( iterator ptr = begin(); ptr != end(); ptr++ ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr && dptr->getDeviceType() == JoystickDeviceType) + if ( dptr->activate() ) + mJoystickActive = true; + } +#ifdef LOG_INPUT + Input::log( mJoystickActive ? + "Joystick activated.\n" : "Joystick failed to activate!\n" ); +#endif + return( mJoystickActive ); +} + +//------------------------------------------------------------------------------ +void UInputManager::deactivateJoystick() +{ + if ( isEnabled() && isJoystickActive() ) + { + mJoystickActive = false; + JoystickInputDevice* dptr; + for ( iterator ptr = begin(); ptr != end(); ptr++ ) + { + dptr = dynamic_cast( *ptr ); + if ( dptr && dptr->getDeviceType() == JoystickDeviceType) + dptr->deactivate(); + } +#ifdef LOG_INPUT + Input::log( "Joystick deactivated.\n" ); +#endif + } +} + +//------------------------------------------------------------------------------ +const char* UInputManager::getJoystickAxesString( U32 deviceID ) +{ + for (Vector::iterator iter = mJoystickList.begin(); + iter != mJoystickList.end(); + ++iter) + { + if ((*iter)->getDeviceID() == deviceID) + return (*iter)->getJoystickAxesString(); + } + return( "" ); +} + +//============================================================================== +// JoystickInputDevice +//============================================================================== +JoystickInputDevice::JoystickInputDevice(U8 deviceID) +{ + mActive = false; + mStick = NULL; + mAxisList.clear(); + mDeviceID = deviceID; + dSprintf(mName, 29, "joystick%d", mDeviceID); + + mButtonState.clear(); + mHatState.clear(); + mNumAxes = mNumButtons = mNumHats = mNumBalls = 0; + + loadJoystickInfo(); + + // initialize state variables + for (int i = 0; i < mNumButtons; ++i) + mButtonState.push_back(false); // all buttons unpressed initially + + for (int i = 0; i < mNumHats; ++i) + mHatState.push_back(SDL_HAT_CENTERED); // hats centered initially +} + +//------------------------------------------------------------------------------ +JoystickInputDevice::~JoystickInputDevice() +{ + if (isActive()) + deactivate(); +} + +//------------------------------------------------------------------------------ +bool JoystickInputDevice::activate() +{ + if (isActive()) + return true; + + // open the stick + mStick = SDL_JoystickOpen(mDeviceID); + if (mStick == NULL) + { + Con::printf("Unable to activate %s: %s", getDeviceName(), SDL_GetError()); + return false; + } + + // reload axis mapping info + loadAxisInfo(); + + mActive = true; + return true; +} + +//------------------------------------------------------------------------------ +bool JoystickInputDevice::deactivate() +{ + if (!isActive()) + return true; + + if (mStick != NULL) + { + SDL_JoystickClose(mStick); + mStick = NULL; + } + + mActive = false; + return true; +} + +//------------------------------------------------------------------------------ +const char* JoystickInputDevice::getName() +{ + return SDL_JoystickName(mDeviceID); +} + +//------------------------------------------------------------------------------ +void JoystickInputDevice::reset() +{ + UInputManager* manager = dynamic_cast(Input::getManager()); + if (!manager) + return; + + // clear joystick state variables + + // buttons + for (int i = 0; i < mButtonState.size(); ++i) + if (mButtonState[i]) + { + manager->joyButtonEvent(mDeviceID, i, false); + mButtonState[i] = false; + } + + // hats + for (int i = 0; i < mHatState.size(); ++i) + if (mHatState[i] != SDL_HAT_CENTERED) + { + manager->joyHatEvent(mDeviceID, i, mHatState[i], SDL_HAT_CENTERED); + mHatState[i] = SDL_HAT_CENTERED; + } + + // axis and ball state is not maintained +} + +//------------------------------------------------------------------------------ +bool JoystickInputDevice::process() +{ + if (!isActive()) + return false; + + UInputManager* manager = dynamic_cast(Input::getManager()); + if (!manager) + return false; + + // axes + for (int i = 0; i < mNumAxes; ++i) + { + // skip the axis if we don't have a mapping for it + if (mAxisList[i].type == -1) + continue; + manager->joyAxisEvent(mDeviceID, i, SDL_JoystickGetAxis(mStick, i)); + } + + // buttons + for (int i = 0; i < mNumButtons; ++i) + { + if (bool(SDL_JoystickGetButton(mStick, i)) == + mButtonState[i]) + continue; + mButtonState[i] = !mButtonState[i]; + manager->joyButtonEvent(mDeviceID, i, mButtonState[i]); + } + + // hats + for (int i = 0; i < mNumHats; ++i) + { + U8 currHatState = SDL_JoystickGetHat(mStick, i); + if (mHatState[i] == currHatState) + continue; + + manager->joyHatEvent(mDeviceID, i, mHatState[i], currHatState); + mHatState[i] = currHatState; + } + + // ballz + // JMQTODO: how to map ball events (xaxis,yaxis?) + return true; +} + +//------------------------------------------------------------------------------ +static S32 GetAxisType(S32 axisNum, const char* namedType) +{ + S32 axisType = -1; + + if (namedType != NULL) + { + if (dStricmp(namedType, "xaxis")==0) + axisType = SI_XAXIS; + else if (dStricmp(namedType, "yaxis")==0) + axisType = SI_YAXIS; + else if (dStricmp(namedType, "zaxis")==0) + axisType = SI_ZAXIS; + else if (dStricmp(namedType, "rxaxis")==0) + axisType = SI_RXAXIS; + else if (dStricmp(namedType, "ryaxis")==0) + axisType = SI_RYAXIS; + else if (dStricmp(namedType, "rzaxis")==0) + axisType = SI_RZAXIS; + else if (dStricmp(namedType, "slider")==0) + axisType = SI_SLIDER; + } + + if (axisType == -1) + { + // use a hardcoded default mapping if possible + switch (axisNum) + { + case 0: + axisType = SI_XAXIS; + break; + case 1: + axisType = SI_YAXIS; + break; + case 2: + axisType = SI_RZAXIS; + break; + case 3: + axisType = SI_SLIDER; + break; + } + } + + return axisType; +} + +//------------------------------------------------------------------------------ +void JoystickInputDevice::loadJoystickInfo() +{ + bool opened = false; + if (mStick == NULL) + { + mStick = SDL_JoystickOpen(mDeviceID); + if (mStick == NULL) + { + Con::printf("Unable to open %s: %s", getDeviceName(), SDL_GetError()); + return; + } + opened = true; + } + + // get the number of thingies on this joystick + mNumAxes = SDL_JoystickNumAxes(mStick); + mNumButtons = SDL_JoystickNumButtons(mStick); + mNumHats = SDL_JoystickNumHats(mStick); + mNumBalls = SDL_JoystickNumBalls(mStick); + + // load axis mapping info + loadAxisInfo(); + + if (opened) + SDL_JoystickClose(mStick); +} + +//------------------------------------------------------------------------------ +// for each axis on a joystick, torque needs to know the type of the axis +// (SI_XAXIS, etc), the minimum value, and the maximum value. However none of +// this information is generally available with the unix/linux api. All you +// get is a device and axis number and a value. Therefore, +// we allow the user to specify these values in preferences. hopefully +// someday we can implement a gui joystick calibrator that takes care of this +// cruft for the user. +void JoystickInputDevice::loadAxisInfo() +{ + mAxisList.clear(); + + AssertFatal(mStick, "mStick is NULL"); + + static int AxisDefaults[] = { SI_XAXIS, SI_YAXIS, SI_ZAXIS, + SI_RXAXIS, SI_RYAXIS, SI_RZAXIS, + SI_SLIDER }; + + int numAxis = SDL_JoystickNumAxes(mStick); + for (int i = 0; i < numAxis; ++i) + { + JoystickAxisInfo axisInfo; + + // defaults + axisInfo.type = -1; + axisInfo.minValue = -32768; + axisInfo.maxValue = 32767; + + // look in console to see if there is mapping information for this axis + const int TempBufSize = 1024; + char tempBuf[TempBufSize]; + dSprintf(tempBuf, TempBufSize, "$Pref::Input::Joystick%d::Axis%d", + mDeviceID, i); + + const char* axisStr = Con::getVariable(tempBuf); + if (axisStr == NULL || dStrlen(axisStr) == 0) + { + if (i < sizeof(AxisDefaults)) + axisInfo.type = AxisDefaults[i]; + } + else + { + // format is "TorqueAxisName MinValue MaxValue"; + dStrncpy(tempBuf, axisStr, TempBufSize); + char* temp = dStrtok( tempBuf, " \0" ); + if (temp) + { + axisInfo.type = GetAxisType(i, temp); + temp = dStrtok( NULL, " \0" ); + if (temp) + { + axisInfo.minValue = dAtoi(temp); + temp = dStrtok( NULL, "\0" ); + if (temp) + { + axisInfo.maxValue = dAtoi(temp); + } + } + } + } + + mAxisList.push_back(axisInfo); + } +} + +//------------------------------------------------------------------------------ +const char* JoystickInputDevice::getJoystickAxesString() +{ + char buf[64]; + dSprintf( buf, sizeof( buf ), "%d", mAxisList.size()); + + for (Vector::iterator iter = mAxisList.begin(); + iter != mAxisList.end(); + ++iter) + { + switch ((*iter).type) + { + case SI_XAXIS: + dStrcat( buf, "\tX" ); + break; + case SI_YAXIS: + dStrcat( buf, "\tY" ); + break; + case SI_ZAXIS: + dStrcat( buf, "\tZ" ); + break; + case SI_RXAXIS: + dStrcat( buf, "\tR" ); + break; + case SI_RYAXIS: + dStrcat( buf, "\tU" ); + break; + case SI_RZAXIS: + dStrcat( buf, "\tV" ); + break; + case SI_SLIDER: + dStrcat( buf, "\tS" ); + break; + } + } + + char* returnString = Con::getReturnBuffer( dStrlen( buf ) + 1 ); + dStrcpy( returnString, buf ); + return( returnString ); +} + + +//============================================================================== +// Console Functions +//============================================================================== +ConsoleFunction( activateKeyboard, bool, 1, 1, "activateKeyboard()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + return( mgr->activateKeyboard() ); + + return( false ); +} + +// JMQ: disabled deactivateKeyboard since the script calls it but there is +// no fallback keyboard input in unix, resulting in a permanently disabled +// keyboard +//------------------------------------------------------------------------------ +ConsoleFunction( deactivateKeyboard, void, 1, 1, "deactivateKeyboard()" ) +{ +#if 0 + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->deactivateKeyboard(); +#endif +} + +//------------------------------------------------------------------------------ +ConsoleFunction( enableMouse, bool, 1, 1, "enableMouse()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + return( mgr->enableMouse() ); + + return ( false ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableMouse, void, 1, 1, "disableMouse()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->disableMouse(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( enableJoystick, bool, 1, 1, "enableJoystick()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + return( mgr->enableJoystick() ); + + return ( false ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableJoystick, void, 1, 1, "disableJoystick()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->disableJoystick(); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( enableLocking, void, 1, 1, "enableLocking()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->setLocking(true); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( disableLocking, void, 1, 1, "disableLocking()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->setLocking(false); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( toggleLocking, void, 1, 1, "toggleLocking()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr ) + mgr->setLocking(!mgr->getLocking()); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( echoInputState, void, 1, 1, "echoInputState()" ) +{ + UInputManager* mgr = dynamic_cast( Input::getManager() ); + if ( mgr && mgr->isEnabled() ) + { + Con::printf( "Input is enabled %s.", + mgr->isActive() ? "and active" : "but inactive" ); + Con::printf( "- Keyboard is %sabled and %sactive.", + mgr->isKeyboardEnabled() ? "en" : "dis", + mgr->isKeyboardActive() ? "" : "in" ); + Con::printf( "- Mouse is %sabled and %sactive.", + mgr->isMouseEnabled() ? "en" : "dis", + mgr->isMouseActive() ? "" : "in" ); + Con::printf( "- Joystick is %sabled and %sactive.", + mgr->isJoystickEnabled() ? "en" : "dis", + mgr->isJoystickActive() ? "" : "in" ); + } + else + Con::printf( "Input is not enabled." ); +} diff --git a/platformX86UNIX/x86UNIXInputManager.h b/platformX86UNIX/x86UNIXInputManager.h new file mode 100644 index 0000000..8f35ef6 --- /dev/null +++ b/platformX86UNIX/x86UNIXInputManager.h @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXINPUTMANAGER_H_ +#define _X86UNIXINPUTMANAGER_H_ + +#include "core/tVector.h" +#include "platform/platformInput.h" +#include "platformX86UNIX/platformX86UNIX.h" + +#include + +#define NUM_KEYS ( KEY_OEM_102 + 1 ) +#define KEY_FIRST KEY_ESCAPE + +struct AsciiData +{ + struct KeyData + { + U16 ascii; + bool isDeadChar; + }; + + KeyData upper; + KeyData lower; + KeyData goofy; +}; + +typedef struct _SDL_Joystick; + +struct JoystickAxisInfo +{ + S32 type; + S32 minValue; + S32 maxValue; +}; + +//------------------------------------------------------------------------------ +class JoystickInputDevice : public InputDevice +{ + public: + JoystickInputDevice(U8 deviceID); + ~JoystickInputDevice(); + + bool activate(); + bool deactivate(); + bool isActive() { return( mActive ); } + + U8 getDeviceType() { return( JoystickDeviceType ); } + U8 getDeviceID() { return( mDeviceID ); } + const char* getName(); + const char* getJoystickAxesString(); + + void loadJoystickInfo(); + void loadAxisInfo(); + JoystickAxisInfo& getAxisInfo(int axisNum) { return mAxisList[axisNum]; } + + bool process(); + void reset(); + + private: + bool mActive; + U8 mDeviceID; + SDL_Joystick* mStick; + Vector mAxisList; + Vector mButtonState; + Vector mHatState; + + S32 mNumAxes; + S32 mNumButtons; + S32 mNumHats; + S32 mNumBalls; +}; + +//------------------------------------------------------------------------------ +class UInputManager : public InputManager +{ + friend bool JoystickInputDevice::process(); // for joystick event funcs + friend void JoystickInputDevice::reset(); + + public: + UInputManager(); + + void init(); + bool enable(); + void disable(); + void activate(); + void deactivate(); + void setWindowLocked(bool locked); + bool isActive() { return( mActive ); } + + void onDeleteNotify( SimObject* object ); + bool onAdd(); + void onRemove(); + + void process(); + + bool enableKeyboard(); + void disableKeyboard(); + bool isKeyboardEnabled() { return( mKeyboardEnabled ); } + bool activateKeyboard(); + void deactivateKeyboard(); + bool isKeyboardActive() { return( mKeyboardActive ); } + + bool enableMouse(); + void disableMouse(); + bool isMouseEnabled() { return( mMouseEnabled ); } + bool activateMouse(); + void deactivateMouse(); + bool isMouseActive() { return( mMouseActive ); } + + bool enableJoystick(); + void disableJoystick(); + bool isJoystickEnabled() { return( mJoystickEnabled ); } + bool activateJoystick(); + void deactivateJoystick(); + bool isJoystickActive() { return( mJoystickActive ); } + + void setLocking(bool enabled); + bool getLocking() { return mLocking; } + + const char* getJoystickAxesString( U32 deviceID ); + bool joystickDetected() { return mJoystickList.size() > 0; } + private: + typedef SimGroup Parent; + // the following vector is just for quick access during event processing. + // it does not manage the cleanup of the JoystickInputDevice objects + Vector mJoystickList; + + bool mKeyboardEnabled; + bool mMouseEnabled; + bool mJoystickEnabled; + + bool mKeyboardActive; + bool mMouseActive; + bool mJoystickActive; + + bool mActive; + + // Device state variables + S32 mModifierKeys; + bool mKeyboardState[256]; + bool mMouseButtonState[3]; + + // last mousex and y are maintained when window is unlocked + S32 mLastMouseX; + S32 mLastMouseY; + + void initJoystick(); + + void resetKeyboardState(); + void resetMouseState(); + void resetInputState(); + + void lockInput(); + void unlockInput(); + bool mLocking; + + void joyHatEvent(U8 deviceID, U8 hatNum, + U8 prevHatState, U8 currHatState); + void joyButtonEvent(U8 deviceID, U8 buttonNum, bool pressed); + void joyButtonEvent(const SDL_Event& event); + void joyAxisEvent(const SDL_Event& event); + void joyAxisEvent(U8 deviceID, U8 axisNum, S16 axisValue); + void mouseButtonEvent(const SDL_Event& event); + void mouseMotionEvent(const SDL_Event& event); + void keyEvent(const SDL_Event& event); + bool processKeyEvent(InputEvent &event); +}; + +#endif // _H_X86UNIXINPUTMANAGER_ diff --git a/platformX86UNIX/x86UNIXMain.cpp b/platformX86UNIX/x86UNIXMain.cpp new file mode 100644 index 0000000..ae083e6 --- /dev/null +++ b/platformX86UNIX/x86UNIXMain.cpp @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/platformInput.h" +#include "console/console.h" + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXStdConsole.h" + +//------------------------------------------------------------------------------ +void Platform::init() +{ + Con::printf("Initializing platform..."); + + // Set the platform variable for the scripts + Con::setVariable( "$platform", "x86UNIX" ); +#if defined(__linux__) + Con::setVariable( "$platformUnixType", "Linux" ); +#elif defined(__OpenBSD__) + Con::setVariable( "$platformUnixType", "OpenBSD" ); +#else + Con::setVariable( "$platformUnixType", "Unknown" ); +#endif + + StdConsole::create(); + + Input::init(); + + //installRedBookDevices(); + +#if 0 +#ifndef DEDICATED + // if we're not dedicated do more initialization + if (!x86UNIXState->isDedicated()) + { + // init SDL + if (!InitSDL()) + { + DisplayErrorAlert("Unable to initialize SDL."); + ImmediateShutdown(1); + } + + Con::printf( "Video Init:" ); + + // load gl library + if (!GLLoader::OpenGLInit()) + { + DisplayErrorAlert("Unable to initialize OpenGL."); + ImmediateShutdown(1); + } + + // initialize video + Video::init(); + if ( Video::installDevice( OpenGLDevice::create() ) ) + Con::printf( " OpenGL display device detected." ); + else + Con::printf( " OpenGL display device not detected." ); + + Con::printf(" "); + } +#endif + // if we are dedicated, do sleep timing and display results + if (x86UNIXState->isDedicated()) + { + const S32 MaxSleepIter = 10; + U32 totalSleepTime = 0; + U32 start; + for (S32 i = 0; i < MaxSleepIter; ++i) + { + start = Platform::getRealMilliseconds(); + Sleep(0, 1000000); + totalSleepTime += Platform::getRealMilliseconds() - start; + } + U32 average = static_cast(totalSleepTime / MaxSleepIter); + + Con::printf("Sleep latency: %ums", average); + // dPrintf as well, since console output won't be visible yet + dPrintf("Sleep latency: %ums\n", average); + if (!x86UNIXState->getDSleep() && average < 10) + { + const char* msg = "Sleep latency ok, enabling dsleep for lower cpu " \ + "utilization"; + Con::printf("%s", msg); + dPrintf("%s\n", msg); + x86UNIXState->setDSleep(true); + } + } +#endif +} + +//------------------------------------------------------------------------------ +void Platform::shutdown() +{ + Cleanup(); +} + +//------------------------------------------------------------------------------ + + +extern "C" +{ + bool torque_engineinit(int argc, const char **argv); + int torque_enginetick(); + bool torque_engineshutdown(); + + int torque_unixmain(int argc, const char **argv) + { + if (!torque_engineinit(argc, argv)) + return 1; + + while(torque_enginetick()) + { + + } + + torque_engineshutdown(); + + return 0; + + } +} diff --git a/platformX86UNIX/x86UNIXMath.cpp b/platformX86UNIX/x86UNIXMath.cpp new file mode 100644 index 0000000..519681a --- /dev/null +++ b/platformX86UNIX/x86UNIXMath.cpp @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/console.h" +#include "math/mMath.h" +#include "core/strings/stringFunctions.h" + + +extern void mInstallLibrary_C(); +extern void mInstallLibrary_ASM(); + + +extern void mInstall_AMD_Math(); +extern void mInstall_Library_SSE(); + + +//-------------------------------------- +ConsoleFunction( MathInit, void, 1, 10, "(detect|C|FPU|MMX|3DNOW|SSE|...)") +{ + U32 properties = CPU_PROP_C; // C entensions are always used + + if (argc == 1) + { + Math::init(0); + return; + } + for (argc--, argv++; argc; argc--, argv++) + { + if (dStricmp(*argv, "DETECT") == 0) { + Math::init(0); + return; + } + if (dStricmp(*argv, "C") == 0) { + properties |= CPU_PROP_C; + continue; + } + if (dStricmp(*argv, "FPU") == 0) { + properties |= CPU_PROP_FPU; + continue; + } + if (dStricmp(*argv, "MMX") == 0) { + properties |= CPU_PROP_MMX; + continue; + } + if (dStricmp(*argv, "3DNOW") == 0) { + properties |= CPU_PROP_3DNOW; + continue; + } + if (dStricmp(*argv, "SSE") == 0) { + properties |= CPU_PROP_SSE; + continue; + } + Con::printf("Error: MathInit(): ignoring unknown math extension '%s'", *argv); + } + Math::init(properties); +} + + + +//------------------------------------------------------------------------------ +void Math::init(U32 properties) +{ + if (!properties) + // detect what's available + properties = Platform::SystemInfo.processor.properties; + else + // Make sure we're not asking for anything that's not supported + properties &= Platform::SystemInfo.processor.properties; + + Con::printf("Math Init:"); + Con::printf(" Installing Standard C extensions"); + mInstallLibrary_C(); + + Con::printf(" Installing Assembly extensions"); + mInstallLibrary_ASM(); + + if (properties & CPU_PROP_FPU) + { + Con::printf(" Installing FPU extensions"); + } + + if (properties & CPU_PROP_MMX) + { + Con::printf(" Installing MMX extensions"); + if (properties & CPU_PROP_3DNOW) + { + Con::printf(" Installing 3DNow extensions"); + mInstall_AMD_Math(); + } + } + +#if !defined(__MWERKS__) || (__MWERKS__ >= 0x2400) + if (properties & CPU_PROP_SSE) + { + Con::printf(" Installing SSE extensions"); + mInstall_Library_SSE(); + } +#endif //mwerks>2.4 + + Con::printf(" "); +} + + +//------------------------------------------------------------------------------ + +static MRandomLCG sgPlatRandom; + +F32 Platform::getRandom() +{ + return sgPlatRandom.randF(); +} + +U32 Platform::getMathControlState() +{ + return 0; +} + +void Platform::setMathControlStateKnown() +{ + +} + +void Platform::setMathControlState(U32 state) +{ + +} diff --git a/platformX86UNIX/x86UNIXMath_ASM.cpp b/platformX86UNIX/x86UNIXMath_ASM.cpp new file mode 100644 index 0000000..7cf1dbb --- /dev/null +++ b/platformX86UNIX/x86UNIXMath_ASM.cpp @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mMath.h" + +static S32 m_mulDivS32_ASM(S32 a, S32 b, S32 c) +{ // a * b / c + S32 r; + + __asm__ __volatile__( + "imul %2\n" + "idiv %3\n" + : "=a" (r) : "a" (a) , "b" (b) , "c" (c) + ); + return r; +} + + +static U32 m_mulDivU32_ASM(S32 a, S32 b, U32 c) +{ // a * b / c + S32 r; + __asm__ __volatile__( + "mov $0, %%edx\n" + "mul %2\n" + "div %3\n" + : "=a" (r) : "a" (a) , "b" (b) , "c" (c) + ); + return r; +} + +//------------------------------------------------------------------------------ +void mInstallLibrary_ASM() +{ + m_mulDivS32 = m_mulDivS32_ASM; + m_mulDivU32 = m_mulDivU32_ASM; +} diff --git a/platformX86UNIX/x86UNIXMemory.cpp b/platformX86UNIX/x86UNIXMemory.cpp new file mode 100644 index 0000000..32b6881 --- /dev/null +++ b/platformX86UNIX/x86UNIXMemory.cpp @@ -0,0 +1,51 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include +#include + +void* dMemcpy(void *dst, const void *src, unsigned size) +{ + return memcpy(dst,src,size); +} + +//-------------------------------------- +void* dMemmove(void *dst, const void *src, unsigned size) +{ + return memmove(dst,src,size); +} + +//-------------------------------------- +void* dMemset(void *dst, S32 c, unsigned size) +{ + return memset(dst,c,size); +} + +//-------------------------------------- +S32 dMemcmp(const void *ptr1, const void *ptr2, unsigned len) +{ + return memcmp(ptr1, ptr2, len); +} + +void* dRealMalloc(dsize_t s) +{ + return malloc(s); +} + +void dRealFree(void* p) +{ + free(p); +} + +void *dAligned_malloc(dsize_t in_size, dsize_t alignment) +{ + return _mm_malloc(in_size, alignment); +} + +void dAligned_free(void* p) +{ + return _mm_free(p); +} diff --git a/platformX86UNIX/x86UNIXMessageBox.client.cpp b/platformX86UNIX/x86UNIXMessageBox.client.cpp new file mode 100644 index 0000000..dce69fc --- /dev/null +++ b/platformX86UNIX/x86UNIXMessageBox.client.cpp @@ -0,0 +1,478 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include +#include +#include + +#include "platformX86UNIX/x86UNIXMessageBox.h" + +#define MessageBox_MaxWinWidth 800 +#define MessageBox_MaxWinHeight 600 +#define MessageBox_MinWinWidth 450 + +#define MessageBox_ButtonBoxWidth 60 +#define MessageBox_ButtonBoxHeight 22 +#define MessageBox_ButtonSpacer 20 +#define MessageBox_ButtonVMargin 10 +#define MessageBox_ButtonHMargin 10 + +#define MessageBox_LineSpacer 2 +#define MessageBox_LineVMargin 10 +#define MessageBox_LineHMargin 10 + +XMessageBoxButton::XMessageBoxButton() +{ + strcpy(mLabel, ""); + mClickVal = -1; + mLabelWidth = mX = mY = mWidth = mHeight = mMouseX = mMouseX = -1; + mMouseDown = false; +} + +XMessageBoxButton::XMessageBoxButton(const char* label, int clickVal) +{ + strncpy(mLabel, label, LabelSize); + mClickVal = clickVal; + mLabelWidth = mX = mY = mWidth = mHeight = mMouseX = mMouseX = -1; + mMouseDown = false; +} + +XMessageBox::XMessageBox(Display* display) +{ + mMessage = ""; + mFS = NULL; + mDisplay = display; +} + +XMessageBox::~XMessageBox() +{ + clearMessageLines(); + if (mDisplay != NULL) + { + mDisplay = NULL; + } +} + +int XMessageBox::alertOK(const char *windowTitle, const char *message) +{ + mMessage = message; + mTitle = windowTitle; + mButtons.clear(); + mButtons.push_back(XMessageBoxButton("OK", OK)); + return show(); +} + +int XMessageBox::alertOKCancel(const char *windowTitle, const char *message) +{ + mMessage = message; + mTitle = windowTitle; + mButtons.clear(); + mButtons.push_back(XMessageBoxButton("OK", OK)); + mButtons.push_back(XMessageBoxButton("Cancel", Cancel)); + return show(); +} + +int XMessageBox::alertRetryCancel(const char *windowTitle, const char *message) +{ + mMessage = message; + mTitle = windowTitle; + mButtons.clear(); + mButtons.push_back(XMessageBoxButton("Retry", Retry)); + mButtons.push_back(XMessageBoxButton("Cancel", Cancel)); + return show(); +} + +void XMessageBox::repaint() +{ + int white = WhitePixel(mDisplay, DefaultScreen(mDisplay)); + int black = BlackPixel(mDisplay, DefaultScreen(mDisplay)); + + int x = 0; + int y = 0; + + // line V margin + y = y + MessageBox_LineVMargin * 2; + + // line H margin + x = MessageBox_LineHMargin; + + XSetForeground(mDisplay, mGC, black); + for (unsigned int i = 0; i < mMessageLines.size(); ++i) + { + XDrawString(mDisplay, mWin, mGC, x, y, mMessageLines[i], + strlen(mMessageLines[i])); + if (i < (mMessageLines.size() - 1)) + y = y + MessageBox_LineSpacer + mFontHeight; + } + XFlush(mDisplay); + + // line V margin + y = y + MessageBox_LineVMargin; + + int maxButWidth = MessageBox_ButtonBoxWidth; + int maxButHeight = MessageBox_ButtonBoxHeight; + + // compute size of text labels on buttons + int fgColor, bgColor; + + int fontDirection, fontAscent, fontDescent; + Vector::iterator iter; + for (iter = mButtons.begin(); iter != mButtons.end(); ++iter) + { + XCharStruct strInfo; + XTextExtents(mFS, iter->getLabel(), strlen(iter->getLabel()), + &fontDirection, &fontAscent, &fontDescent, + &strInfo); +// if (maxButWidth < strInfo.width) +// maxButWidth = strInfo.width; +// if (maxButHeight < (strInfo.ascent + strInfo.descent)) +// maxButHeight = (strInfo.ascent + strInfo.descent); + iter->setLabelWidth(strInfo.width); + } + int buttonBoxWidth = maxButWidth; + int buttonBoxHeight = maxButHeight; + + // draw buttons + // button V margin + y = y + MessageBox_ButtonVMargin; + + // center the buttons + x = MessageBox_ButtonHMargin + (mMBWidth - getButtonLineWidth()) / 2; + + for (iter = mButtons.begin(); iter != mButtons.end(); ++iter) + { + if (iter->drawReverse()) + { + fgColor = white; + bgColor = black; + } + else + { + fgColor = black; + bgColor = white; + } + + XSetForeground(mDisplay, mGC, bgColor); + XFillRectangle(mDisplay, mWin, mGC, x, y, + buttonBoxWidth, buttonBoxHeight); + XSetForeground(mDisplay, mGC, fgColor); + XDrawRectangle(mDisplay, mWin, mGC, x, y, + buttonBoxWidth, buttonBoxHeight); + XDrawString(mDisplay, mWin, mGC, + x + ((buttonBoxWidth - iter->getLabelWidth()) / 2), + y + mFontAscent + ((buttonBoxHeight - mFontAscent) / 2), + iter->getLabel(), + strlen(iter->getLabel())); + iter->setButtonRect(x, y, buttonBoxWidth, buttonBoxHeight); + x = x + buttonBoxWidth + MessageBox_ButtonSpacer; + } +} + +template +static inline Type max(Type v1, Type v2) +{ + if (v1 <= v2) + return v2; + else + return v1; +} + +template +static inline Type min(Type v1, Type v2) +{ + if (v1 > v2) + return v2; + else + return v1; +} + +void XMessageBox::clearMessageLines() +{ + Vector::iterator iter; + for (iter = mMessageLines.begin(); iter != mMessageLines.end(); ++iter) + delete [] *iter; + mMessageLines.clear(); +} + +void XMessageBox::splitMessage() +{ + clearMessageLines(); + if (mMessage == NULL || strlen(mMessage)==0) + // JMQTODO: what to do with empty strings? + return; + + // need to break message up in to lines, and store lines in + // mMessageLines + + int numChars = strlen(mMessage); + const int ScratchBufSize = 2048; + char scratchBuf[ScratchBufSize]; + memset(scratchBuf, 0, ScratchBufSize); + + int fontDirection, fontAscent, fontDescent; + XCharStruct strInfo; + + char *curChar = const_cast(mMessage); + char *endChar; + char *curWrapped = scratchBuf; + int curWidth = 0; + int maxWidth = mMaxWindowWidth - (MessageBox_LineHMargin); + + while ( // while pointers are in range... + (curChar - mMessage) < numChars && + (curWrapped - scratchBuf) < ScratchBufSize) + { + // look for next space in remaining string + endChar = index(curChar, ' '); + if (endChar == NULL) + endChar = index(curChar, '\0'); + + if (endChar != NULL) + // increment one char past the space to include it + endChar++; + else + // otherwise, set the endchar to one char ahead + endChar = curChar + 1; + + // compute length of substr + int len = endChar - curChar; + XTextExtents(mFS, curChar, len, + &fontDirection, &fontAscent, &fontDescent, + &strInfo); + // if its too big, time to add a new line... + if ((curWidth + strInfo.width) > maxWidth) + { + // create a new block for the line and add it + *curWrapped = '\0'; + int len = strlen(scratchBuf); + char* line = new char[len+1]; + strncpy(line, scratchBuf, len+1); // +1 gets the null char + mMessageLines.push_back(line); + + // reset curWrapped to the beginning of the scratch buffer + curWrapped = scratchBuf; + curWidth = 0; + } + // copy the current string into curWrapped if we have enough room + int bytesRemaining = + ScratchBufSize - (curWrapped - scratchBuf); + if (bytesRemaining >= len) + strncpy(curWrapped, curChar, len); + + curWrapped += len; + curWidth += strInfo.width; + curChar = endChar; + } + + // make a final line out of any leftover stuff in the scratch buffer + if (curWrapped != scratchBuf) + { + *curWrapped = '\0'; + int len = strlen(scratchBuf); + char* line = new char[len+1]; + strncpy(line, scratchBuf, len+1); // +1 gets the null char + mMessageLines.push_back(line); + } +} + +int XMessageBox::loadFont() +{ + // load the font + mFS = XLoadQueryFont(mDisplay, + "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*"); + + if (mFS == NULL) + mFS = XLoadQueryFont(mDisplay, "fixed"); + + if (mFS == NULL) + return -1; + + // dummy call to XTextExtents to get the font specs + XCharStruct strInfo; + + XTextExtents(mFS, "foo", 1, + &mFontDirection, &mFontAscent, &mFontDescent, + &strInfo); + + mFontHeight = mFontAscent + mFontDescent; + return 0; +} + +int XMessageBox::getButtonLineWidth() +{ + return mButtons.size() * MessageBox_ButtonBoxWidth + + (mButtons.size() - 1) * MessageBox_ButtonSpacer + + MessageBox_ButtonHMargin * 2; +} + +void XMessageBox::setDimensions() +{ + mMBWidth = MessageBox_MaxWinWidth; + mMBHeight = MessageBox_MaxWinHeight; + + // determine width of button line + int buttonWidth = getButtonLineWidth(); + + // if there is only one line, the desired width is the greater of the + // line width and the buttonWidth, otherwise the lineWidth is the + // max possible width which we already set. + if (mMessageLines.size() == 1) + { + XCharStruct strInfo; + int fontDirection, fontAscent, fontDescent; + + XTextExtents(mFS, mMessageLines[0], strlen(mMessageLines[0]), + &fontDirection, &fontAscent, &fontDescent, + &strInfo); + + mMBWidth = max(MessageBox_LineHMargin * 2 + strInfo.width, + buttonWidth); + mMBWidth = max(mMBWidth, MessageBox_MinWinWidth); + } + + // determine the height of the button line + int buttonHeight = MessageBox_ButtonBoxHeight + + MessageBox_ButtonVMargin * 2; + + int lineHeight = mFontHeight * mMessageLines.size() + + (mMessageLines.size() - 1) * MessageBox_LineSpacer + + MessageBox_LineVMargin * 2; + + mMBHeight = buttonHeight + lineHeight; +} + +int XMessageBox::show() +{ + if (mDisplay == NULL) + return -1; + + int retVal = 0; + retVal = loadFont(); + if (retVal < 0) + return retVal; + + // set the maximum window dimensions + mScreenWidth = DisplayWidth(mDisplay, DefaultScreen(mDisplay)); + mScreenHeight = DisplayHeight(mDisplay, DefaultScreen(mDisplay)); + mMaxWindowWidth = min(mScreenWidth, MessageBox_MaxWinWidth); + mMaxWindowHeight = min(mScreenHeight, MessageBox_MaxWinHeight); + + // split the message into a vector of lines + splitMessage(); + + // set the dialog dimensions + setDimensions(); + + mWin = XCreateSimpleWindow( + mDisplay, + DefaultRootWindow(mDisplay), + (mScreenWidth - mMBWidth) / 2, (mScreenHeight - mMBHeight) / 2, + mMBWidth, mMBHeight, + 1, + BlackPixel(mDisplay, DefaultScreen(mDisplay)), + WhitePixel(mDisplay, DefaultScreen(mDisplay))); + + mGC = XCreateGC(mDisplay, mWin, 0, 0); + + XSetFont(mDisplay, mGC, mFS->fid); + + // set input mask + XSelectInput(mDisplay, mWin, + ExposureMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); + + // set wm protocols in case they hit X + Atom wm_delete_window = + XInternAtom(mDisplay, "WM_DELETE_WINDOW", False); + Atom wm_protocols = + XInternAtom(mDisplay, "WM_PROTOCOLS", False); + XSetWMProtocols (mDisplay, mWin, &wm_delete_window, 1); + // set pop up dialog hint + XSetTransientForHint(mDisplay, mWin, mWin); + + // set title + XTextProperty wtitle; + wtitle.value = (unsigned char *)mTitle; + wtitle.encoding = XA_STRING; + wtitle.format = 8; + wtitle.nitems = strlen(mTitle); + XSetWMName(mDisplay, mWin, &wtitle); + + // show window + XMapWindow(mDisplay, mWin); + // move it in case some bozo window manager repositioned it + XMoveWindow(mDisplay, mWin, + (mScreenWidth - mMBWidth) / 2, (mScreenHeight - mMBHeight) / 2); + // raise it to top + XRaiseWindow(mDisplay, mWin); + + XMessageBoxButton* clickedButton = NULL; + XEvent event; + Vector::iterator iter; + bool done = false; + while (!done) + { + XNextEvent(mDisplay, &event); + switch (event.type) + { + case Expose: + repaint(); + break; + case MotionNotify: + for (iter = mButtons.begin(); iter != mButtons.end(); ++iter) + iter->setMouseCoordinates(event.xmotion.x, event.xmotion.y); + break; + case ButtonPress: + for (iter = mButtons.begin(); iter != mButtons.end(); ++iter) + { + if (iter->pointInRect(event.xbutton.x, event.xbutton.y)) + { + iter->setMouseDown(true); + iter->setMouseCoordinates(event.xbutton.x, event.xbutton.y); + break; + } + } + break; + case ButtonRelease: + for (iter = mButtons.begin(); iter != mButtons.end(); ++iter) + { + if (iter->pointInRect(event.xbutton.x, event.xbutton.y) && + iter->isMouseDown()) + { + // we got a winner! + clickedButton = iter; + done = true; + break; + } + } + if (clickedButton == NULL) + { + // user released outside a button. clear the button states + for (iter = mButtons.begin(); iter != mButtons.end(); ++iter) + iter->setMouseDown(false); + } + break; + case ClientMessage: + if (event.xclient.message_type == wm_protocols && + event.xclient.data.l[0] == static_cast(wm_delete_window)) + done = true; + break; + } + repaint(); + } + + XUnmapWindow(mDisplay, mWin); + XDestroyWindow(mDisplay, mWin); + XFreeGC(mDisplay, mGC); + XFreeFont(mDisplay, mFS); + + if (clickedButton != NULL) + return clickedButton->getClickVal(); + else + return -1; +} diff --git a/platformX86UNIX/x86UNIXMessageBox.h b/platformX86UNIX/x86UNIXMessageBox.h new file mode 100644 index 0000000..6b38580 --- /dev/null +++ b/platformX86UNIX/x86UNIXMessageBox.h @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXMESSAGEBOX_H_ +#define _X86UNIXMESSAGEBOX_H_ + +#include +#include "core/util/tVector.h" + +class XMessageBoxButton +{ + public: + XMessageBoxButton(); + XMessageBoxButton(const char* label, int clickVal); + + const char *getLabel() { return static_cast(mLabel); } + int getClickVal() { return mClickVal; } + + int getLabelWidth() { return mLabelWidth; } + void setLabelWidth(int width) { mLabelWidth = width; } + + void setButtonRect(int x, int y, int width, int height) + { + mX = x; + mY = y; + mWidth = width; + mHeight = height; + } + void setMouseCoordinates(int x, int y) + { + mMouseX = x; + mMouseY = y; + } + + bool drawReverse() + { + return mMouseDown && pointInRect(mMouseX, mMouseY); + } + + bool pointInRect(int x, int y) + { + if (x >= mX && x <= (mX+mWidth) && + y >= mY && y <= (mY+mHeight)) + return true; + return false; + } + + void setMouseDown(bool mouseDown) { mMouseDown = mouseDown; } + bool isMouseDown() { return mMouseDown; } + + private: + static const int LabelSize = 100; + char mLabel[LabelSize]; + int mClickVal; + int mLabelWidth; + int mX, mY, mWidth, mHeight; + int mMouseX, mMouseY; + bool mMouseDown; +}; + +class XMessageBox +{ + public: + static const int OK = 1; + static const int Cancel = 2; + static const int Retry = 3; + + XMessageBox(Display* display); + ~XMessageBox(); + + int alertOK(const char *windowTitle, const char *message); + int alertOKCancel(const char *windowTitle, const char *message); + int alertRetryCancel(const char *windowTitle, const char *message); + private: + int show(); + void repaint(); + void splitMessage(); + void clearMessageLines(); + int loadFont(); + void setDimensions(); + int getButtonLineWidth(); + + const char* mMessage; + const char* mTitle; + Vector mButtons; + Vector mMessageLines; + + Display* mDisplay; + GC mGC; + Window mWin; + XFontStruct* mFS; + int mFontHeight; + int mFontAscent; + int mFontDescent; + int mFontDirection; + + int mScreenWidth, mScreenHeight, mMaxWindowWidth, mMaxWindowHeight; + int mMBWidth, mMBHeight; +}; + +#endif // #define _X86UNIXMESSAGEBOX_H_ diff --git a/platformX86UNIX/x86UNIXNet.cpp b/platformX86UNIX/x86UNIXNet.cpp new file mode 100644 index 0000000..d7156f0 --- /dev/null +++ b/platformX86UNIX/x86UNIXNet.cpp @@ -0,0 +1,905 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platform.h" +#include "platform/event.h" +#include "platform/platformNetAsync.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* for PROTO_IPX */ +#if defined(__linux__) +#include +#include /* ioctl() */ +#include +#elif defined(__OpenBSD__) || defined(__FreeBSD__) +#include /* ioctl() */ +#include +#endif + +#include +#include + +#include "console/console.h" +#include "platform/gameInterface.h" +#include "core/fileStream.h" +#include "core/tVector.h" + +static Net::Error getLastError(); +static S32 defaultPort = 28000; +static S32 netPort = 0; +static int ipxSocket = InvalidSocket; +static int udpSocket = InvalidSocket; + +// local enum for socket states for polled sockets +enum SocketState +{ + InvalidState, + Connected, + ConnectionPending, + Listening, + NameLookupRequired +}; + +// the Socket structure helps us keep track of the +// above states +struct Socket +{ + Socket() + { + fd = InvalidSocket; + state = InvalidState; + remoteAddr[0] = 0; + remotePort = -1; + } + + NetSocket fd; + S32 state; + char remoteAddr[256]; + S32 remotePort; +}; + +// list of polled sockets +static Vector gPolledSockets; + +static Socket* addPolledSocket(NetSocket& fd, S32 state, + char* remoteAddr = NULL, S32 port = -1) +{ + Socket* sock = new Socket(); + sock->fd = fd; + sock->state = state; + if (remoteAddr) + dStrcpy(sock->remoteAddr, remoteAddr); + if (port != -1) + sock->remotePort = port; + gPolledSockets.push_back(sock); + return sock; +} + +enum { + MaxConnections = 1024, +}; + +S32 Poll(NetSocket fd, S32 eventMask, S32 timeoutMs) +{ + pollfd pfd; + S32 retVal; + pfd.fd = fd; + pfd.events = eventMask; + + retVal = poll(&pfd, 1, timeoutMs); + return retVal; + if (retVal <= 0) + return retVal; + else + return pfd.revents; +} + +bool Net::init() +{ + NetAsync::startAsync(); + return(true); +} + +void Net::shutdown() +{ + while (gPolledSockets.size() > 0) + closeConnectTo(gPolledSockets[0]->fd); + + closePort(); + NetAsync::stopAsync(); +} + +static void netToIPSocketAddress(const NetAddress *address, struct sockaddr_in *sockAddr) +{ + dMemset(sockAddr, 0, sizeof(struct sockaddr_in)); + sockAddr->sin_family = AF_INET; + sockAddr->sin_port = htons(address->port); + char tAddr[20]; + dSprintf(tAddr, 20, "%d.%d.%d.%d\n", address->netNum[0], address->netNum[1], address->netNum[2], address->netNum[3]); +//fprintf(stdout,"netToIPSocketAddress(): %s\n",tAddr);fflush(NULL); + sockAddr->sin_addr.s_addr = inet_addr(tAddr); +// sockAddr->sin_addr.s_addr = address->netNum[0]; // hopefully this will work. +} + +static void IPSocketToNetAddress(const struct sockaddr_in *sockAddr, NetAddress *address) +{ + address->type = NetAddress::IPAddress; + address->port = htons(sockAddr->sin_port); + char *tAddr; + tAddr = inet_ntoa(sockAddr->sin_addr); +//fprintf(stdout,"IPSocketToNetAddress(): %s\n",tAddr);fflush(NULL); + U8 nets[4]; + nets[0] = atoi(strtok(tAddr, ".")); + nets[1] = atoi(strtok(NULL, ".")); + nets[2] = atoi(strtok(NULL, ".")); + nets[3] = atoi(strtok(NULL, ".")); +//fprintf(stdout,"0 = %d, 1 = %d, 2 = %d, 3 = %d\n", nets[0], nets[1], nets[2], nets[3]); + address->netNum[0] = nets[0]; + address->netNum[1] = nets[1]; + address->netNum[2] = nets[2]; + address->netNum[3] = nets[3]; +} + +static void netToIPXSocketAddress(const NetAddress *address, sockaddr_ipx *sockAddr) +{ +#if !defined(__FreeBSD__) + dMemset(sockAddr, 0, sizeof(sockaddr_ipx)); + sockAddr->sipx_family = AF_INET; + sockAddr->sipx_port = htons(address->port); + sockAddr->sipx_network = address->netNum[0]; + sockAddr->sipx_node[0] = address->nodeNum[0]; + sockAddr->sipx_node[1] = address->nodeNum[1]; + sockAddr->sipx_node[2] = address->nodeNum[2]; + sockAddr->sipx_node[3] = address->nodeNum[3]; + sockAddr->sipx_node[4] = address->nodeNum[4]; + sockAddr->sipx_node[5] = address->nodeNum[5]; +#endif +} + +static void IPXSocketToNetAddress(const sockaddr_ipx *sockAddr, NetAddress *address) +{ +#if !defined(__FreeBSD__) + address->type = NetAddress::IPXAddress; + address->port = htons(sockAddr->sipx_port); + address->netNum[0] = sockAddr->sipx_network; + address->nodeNum[0] = sockAddr->sipx_node[0]; + address->nodeNum[1] = sockAddr->sipx_node[1]; + address->nodeNum[2] = sockAddr->sipx_node[2]; + address->nodeNum[3] = sockAddr->sipx_node[3]; + address->nodeNum[4] = sockAddr->sipx_node[4]; + address->nodeNum[5] = sockAddr->sipx_node[5]; +#endif +} + +NetSocket Net::openListenPort(U16 port) +{ + if(Game->isJournalReading()) + { + U32 ret; + Game->journalRead(&ret); + return NetSocket(ret); + } + NetSocket sock = openSocket(); + if (sock == InvalidSocket) + { + Con::errorf("Unable to open listen socket: %s", strerror(errno)); + return InvalidSocket; + } + + if (bind(sock, port) != NoError) + { + Con::errorf("Unable to bind port %d: %s", port, strerror(errno)); + ::close(sock); + return InvalidSocket; + } + if (listen(sock, 4) != NoError) + { + Con::errorf("Unable to listen on port %d: %s", port, strerror(errno)); + ::close(sock); + return InvalidSocket; + } + + setBlocking(sock, false); + addPolledSocket(sock, Listening); + if (Game->isJournalWriting()) + Game->journalWrite(U32(sock)); + return sock; +} + +NetSocket Net::openConnectTo(const char *addressString) +{ + if(!dStrnicmp(addressString, "ipx:", 4)) + return InvalidSocket; + if(!dStrnicmp(addressString, "ip:", 3)) + addressString += 3; // eat off the ip: + char remoteAddr[256]; + dStrcpy(remoteAddr, addressString); + + char *portString = dStrchr(remoteAddr, ':'); + + U16 port; + if(portString) + { + *portString++ = 0; + port = htons(dAtoi(portString)); + } + else + port = htons(defaultPort); + + if(!dStricmp(remoteAddr, "broadcast")) + return InvalidSocket; + + if(Game->isJournalReading()) + { + U32 ret; + Game->journalRead(&ret); + return NetSocket(ret); + } + NetSocket sock = openSocket(); + setBlocking(sock, false); + + sockaddr_in ipAddr; + dMemset(&ipAddr, 0, sizeof(ipAddr)); + + if (inet_aton(remoteAddr, &ipAddr.sin_addr) != 0) + { + ipAddr.sin_port = port; + ipAddr.sin_family = AF_INET; + if(::connect(sock, (struct sockaddr *)&ipAddr, sizeof(ipAddr)) == -1 && + errno != EINPROGRESS) + { + Con::errorf("Error connecting %s: %s", + addressString, strerror(errno)); + ::close(sock); + sock = InvalidSocket; + } + if(sock != InvalidSocket) { + // add this socket to our list of polled sockets + addPolledSocket(sock, ConnectionPending); + } + } + else + { + // need to do an asynchronous name lookup. first, add the socket + // to the polled list + addPolledSocket(sock, NameLookupRequired, remoteAddr, port); + // queue the lookup + gNetAsync.queueLookup(remoteAddr, sock); + } + if(Game->isJournalWriting()) + Game->journalWrite(U32(sock)); + return sock; +} + +void Net::closeConnectTo(NetSocket sock) +{ + if(Game->isJournalReading()) + return; + + // if this socket is in the list of polled sockets, remove it + for (int i = 0; i < gPolledSockets.size(); ++i) + if (gPolledSockets[i]->fd == sock) + { + delete gPolledSockets[i]; + gPolledSockets.erase(i); + break; + } + + closeSocket(sock); +} + +Net::Error Net::sendtoSocket(NetSocket socket, const U8 *buffer, int bufferSize) +{ + if(Game->isJournalReading()) + { + U32 e; + Game->journalRead(&e); + + return (Net::Error) e; + } + Net::Error e = send(socket, buffer, bufferSize); + if(Game->isJournalWriting()) + Game->journalWrite(U32(e)); + return e; +} + +bool Net::openPort(S32 port) +{ + if(udpSocket != InvalidSocket) + close(udpSocket); + if(ipxSocket != InvalidSocket) + close(ipxSocket); + + udpSocket = socket(AF_INET, SOCK_DGRAM, 0); + ipxSocket = socket(AF_IPX, SOCK_DGRAM, 0); + + if(udpSocket != InvalidSocket) + { + Net::Error error; + error = bind(udpSocket, port); + if(error == NoError) + error = setBufferSize(udpSocket, 32768); + if(error == NoError) + error = setBroadcast(udpSocket, true); + if(error == NoError) + error = setBlocking(udpSocket, false); + if(error == NoError) + Con::printf("UDP initialized on port %d", port); + else + { + close(udpSocket); + udpSocket = InvalidSocket; + Con::printf("Unable to initialize UDP - error %d", error); + } + } + if(ipxSocket != InvalidSocket) + { + Net::Error error = NoError; + sockaddr_ipx ipxAddress; + memset((char *)&ipxAddress, 0, sizeof(ipxAddress)); + ipxAddress.sipx_family = AF_IPX; + ipxAddress.sipx_port = htons(port); + S32 err = ::bind(ipxSocket, (struct sockaddr *)&ipxAddress, sizeof(ipxAddress)); + if(err) + error = getLastError(); + if(error == NoError) + error = setBufferSize(ipxSocket, 32768); + if(error == NoError) + error = setBroadcast(ipxSocket, true); + if(error == NoError) + error = setBlocking(ipxSocket, false); + if(error == NoError) + Con::printf("IPX initialized on port %d", port); + else + { + close(ipxSocket); + ipxSocket = InvalidSocket; + Con::printf("Unable to initialize IPX - error %d", error); + } + } + netPort = port; + return ipxSocket != InvalidSocket || udpSocket != InvalidSocket; +} + +void Net::closePort() +{ + if(ipxSocket != InvalidSocket) + close(ipxSocket); + if(udpSocket != InvalidSocket) + close(udpSocket); +} + +Net::Error Net::sendto(const NetAddress *address, const U8 *buffer, S32 bufferSize) +{ + if(Game->isJournalReading()) + return NoError; + + if(address->type == NetAddress::IPXAddress) + { + sockaddr_ipx ipxAddr; + netToIPXSocketAddress(address, &ipxAddr); + if(::sendto(ipxSocket, (const char*)buffer, bufferSize, 0, + (sockaddr *) &ipxAddr, sizeof(sockaddr_ipx)) == -1) + return getLastError(); + else + return NoError; + } + else + { + sockaddr_in ipAddr; + netToIPSocketAddress(address, &ipAddr); + if(::sendto(udpSocket, (const char*)buffer, bufferSize, 0, + (sockaddr *) &ipAddr, sizeof(sockaddr_in)) == -1) + return getLastError(); + else + return NoError; + } +} + +void Net::process() +{ + sockaddr sa; + + PacketReceiveEvent receiveEvent; + for(;;) + { + U32 addrLen = sizeof(sa); + S32 bytesRead = -1; + if(udpSocket != InvalidSocket) + bytesRead = recvfrom(udpSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); + if(bytesRead == -1 && ipxSocket != InvalidSocket) + { + addrLen = sizeof(sa); + bytesRead = recvfrom(ipxSocket, (char *) receiveEvent.data, MaxPacketDataSize, 0, &sa, &addrLen); + } + + if(bytesRead == -1) + break; + + if(sa.sa_family == AF_INET) + IPSocketToNetAddress((sockaddr_in *) &sa, &receiveEvent.sourceAddress); + else if(sa.sa_family == AF_IPX) + IPXSocketToNetAddress((sockaddr_ipx *) &sa, &receiveEvent.sourceAddress); + else + continue; + + NetAddress &na = receiveEvent.sourceAddress; + if(na.type == NetAddress::IPAddress && + na.netNum[0] == 127 && + na.netNum[1] == 0 && + na.netNum[2] == 0 && + na.netNum[3] == 1 && + na.port == netPort) + continue; + if(bytesRead <= 0) + continue; + receiveEvent.size = PacketReceiveEventHeaderSize + bytesRead; + Game->postEvent(receiveEvent); + } + + // process the polled sockets. This blob of code performs functions + // similar to WinsockProc in winNet.cc + + if (gPolledSockets.size() == 0) + return; + + static ConnectedNotifyEvent notifyEvent; + static ConnectedAcceptEvent acceptEvent; + static ConnectedReceiveEvent cReceiveEvent; + + S32 optval; + socklen_t optlen = sizeof(S32); + S32 bytesRead; + Net::Error err; + bool removeSock = false; + Socket *currentSock = NULL; + sockaddr_in ipAddr; + NetSocket incoming = InvalidSocket; + char out_h_addr[1024]; + int out_h_length = 0; + + for (S32 i = 0; i < gPolledSockets.size(); + /* no increment, this is done at end of loop body */) + { + removeSock = false; + currentSock = gPolledSockets[i]; + switch (currentSock->state) + { + case InvalidState: + Con::errorf("Error, InvalidState socket in polled sockets list"); + break; + case ConnectionPending: + notifyEvent.tag = currentSock->fd; + // see if it is now connected + if (getsockopt(currentSock->fd, SOL_SOCKET, SO_ERROR, + &optval, &optlen) == -1) + { + Con::errorf("Error getting socket options: %s", strerror(errno)); + notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; + Game->postEvent(notifyEvent); + removeSock = true; + } + else + { + if (optval == EINPROGRESS) + // still connecting... + break; + + if (optval == 0) + { + // connected + notifyEvent.state = ConnectedNotifyEvent::Connected; + Game->postEvent(notifyEvent); + currentSock->state = Connected; + } + else + { + // some kind of error + Con::errorf("Error connecting: %s", strerror(errno)); + notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; + Game->postEvent(notifyEvent); + removeSock = true; + } + } + break; + case Connected: + bytesRead = 0; + // try to get some data + err = Net::recv(currentSock->fd, cReceiveEvent.data, + MaxPacketDataSize, &bytesRead); + if(err == Net::NoError) + { + if (bytesRead > 0) + { + // got some data, post it + cReceiveEvent.tag = currentSock->fd; + cReceiveEvent.size = ConnectedReceiveEventHeaderSize + + bytesRead; + Game->postEvent(cReceiveEvent); + } + else + { + // zero bytes read means EOF + if (bytesRead < 0) + // ack! this shouldn't happen + Con::errorf("Unexpected error on socket: %s", + strerror(errno)); + + notifyEvent.tag = currentSock->fd; + notifyEvent.state = ConnectedNotifyEvent::Disconnected; + Game->postEvent(notifyEvent); + removeSock = true; + } + } + else if (err != Net::NoError && err != Net::WouldBlock) + { + Con::errorf("Error reading from socket: %s", strerror(errno)); + notifyEvent.tag = currentSock->fd; + notifyEvent.state = ConnectedNotifyEvent::Disconnected; + Game->postEvent(notifyEvent); + removeSock = true; + } + break; + case NameLookupRequired: + // is the lookup complete? + if (!gNetAsync.checkLookup( + currentSock->fd, out_h_addr, &out_h_length, + sizeof(out_h_addr))) + break; + + notifyEvent.tag = currentSock->fd; + if (out_h_length == -1) + { + Con::errorf("DNS lookup failed: %s", currentSock->remoteAddr); + notifyEvent.state = ConnectedNotifyEvent::DNSFailed; + removeSock = true; + } + else + { + // try to connect + dMemcpy(&(ipAddr.sin_addr.s_addr), out_h_addr, out_h_length); + ipAddr.sin_port = currentSock->remotePort; + ipAddr.sin_family = AF_INET; + if(::connect(currentSock->fd, (struct sockaddr *)&ipAddr, + sizeof(ipAddr)) == -1) + { + if (errno == EINPROGRESS) + { + notifyEvent.state = ConnectedNotifyEvent::DNSResolved; + currentSock->state = ConnectionPending; + } + else + { + Con::errorf("Error connecting to %s: %s", + currentSock->remoteAddr, strerror(errno)); + notifyEvent.state = ConnectedNotifyEvent::ConnectFailed; + removeSock = true; + } + } + else + { + notifyEvent.state = ConnectedNotifyEvent::Connected; + currentSock->state = Connected; + } + } + Game->postEvent(notifyEvent); + break; + case Listening: + incoming = + Net::accept(currentSock->fd, &acceptEvent.address); + if(incoming != InvalidSocket) + { + acceptEvent.portTag = currentSock->fd; + acceptEvent.connectionTag = incoming; + setBlocking(incoming, false); + addPolledSocket(incoming, Connected); + Game->postEvent(acceptEvent); + } + break; + } + + // only increment index if we're not removing the connection, since + // the removal will shift the indices down by one + if (removeSock) + closeConnectTo(currentSock->fd); + else + i++; + } +} + +NetSocket Net::openSocket() +{ + int retSocket; + retSocket = socket(AF_INET, SOCK_STREAM, 0); + + if(retSocket == InvalidSocket) + return InvalidSocket; + else + return retSocket; +} + +Net::Error Net::closeSocket(NetSocket socket) +{ + if(socket != InvalidSocket) + { + if(!close(socket)) + return NoError; + else + return getLastError(); + } + else + return NotASocket; +} + +Net::Error Net::connect(NetSocket socket, const NetAddress *address) +{ + if(address->type != NetAddress::IPAddress) + return WrongProtocolType; + sockaddr_in socketAddress; + netToIPSocketAddress(address, &socketAddress); + if(!::connect(socket, (sockaddr *) &socketAddress, sizeof(socketAddress))) + return NoError; + return getLastError(); +} + +Net::Error Net::listen(NetSocket socket, S32 backlog) +{ + if(!::listen(socket, backlog)) + return NoError; + return getLastError(); +} + +NetSocket Net::accept(NetSocket acceptSocket, NetAddress *remoteAddress) +{ + sockaddr_in socketAddress; + U32 addrLen = sizeof(socketAddress); + + int retVal = ::accept(acceptSocket, (sockaddr *) &socketAddress, &addrLen); + if(retVal != InvalidSocket) + { + IPSocketToNetAddress(&socketAddress, remoteAddress); + return retVal; + } + return InvalidSocket; +} + +Net::Error Net::bind(NetSocket socket, U16 port) +{ + S32 error; + + sockaddr_in socketAddress; + dMemset((char *)&socketAddress, 0, sizeof(socketAddress)); + socketAddress.sin_family = AF_INET; + // It's entirely possible that there are two NIC cards. + // We let the user specify which one the server runs on. + + // thanks to [TPG]P1aGu3 for the name + const char* serverIP = Con::getVariable( "Pref::Net::BindAddress" ); + // serverIP is guaranteed to be non-0. + AssertFatal( serverIP, "serverIP is NULL!" ); + + if( serverIP[0] != '\0' ) { + // we're not empty + socketAddress.sin_addr.s_addr = inet_addr( serverIP ); + + if( socketAddress.sin_addr.s_addr != INADDR_NONE ) { + Con::printf( "Binding server port to %s", serverIP ); + } else { + Con::warnf( ConsoleLogEntry::General, + "inet_addr() failed for %s while binding!", + serverIP ); + socketAddress.sin_addr.s_addr = INADDR_ANY; + } + + } else { + Con::printf( "Binding server port to default IP" ); + socketAddress.sin_addr.s_addr = INADDR_ANY; + } + + socketAddress.sin_port = htons(port); + error = ::bind(socket, (sockaddr *) &socketAddress, sizeof(socketAddress)); + + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::setBufferSize(NetSocket socket, S32 bufferSize) +{ + S32 error; + error = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *) &bufferSize, sizeof(bufferSize)); + if(!error) + error = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *) &bufferSize, sizeof(bufferSize)); + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::setBroadcast(NetSocket socket, bool broadcast) +{ + S32 bc = broadcast; + S32 error = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&bc, sizeof(bc)); + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::setBlocking(NetSocket socket, bool blockingIO) +{ + int notblock = !blockingIO; + S32 error = ioctl(socket, FIONBIO, ¬block); + if(!error) + return NoError; + return getLastError(); +} + +Net::Error Net::send(NetSocket socket, const U8 *buffer, S32 bufferSize) +{ + // Poll for write status. this blocks. should really + // do this in a separate thread or set it up so that the data can + // get queued and sent later + // JMQTODO + Poll(socket, POLLOUT, 10000); + + S32 error = ::send(socket, (const char*)buffer, bufferSize, 0); + if(error != -1) + return NoError; + + return getLastError(); +} + +Net::Error Net::recv(NetSocket socket, U8 *buffer, S32 bufferSize, S32 *bytesRead) +{ + *bytesRead = ::recv(socket, (char*)buffer, bufferSize, 0); + if(*bytesRead == -1) + return getLastError(); + return NoError; +} + +bool Net::compareAddresses(const NetAddress *a1, const NetAddress *a2) +{ + if((a1->type != a2->type) || + (*((U32 *)a1->netNum) != *((U32 *)a2->netNum)) || + (a1->port != a2->port)) + return false; + + if(a1->type == NetAddress::IPAddress) + return true; + for(S32 i = 0; i < 6; i++) + if(a1->nodeNum[i] != a2->nodeNum[i]) + return false; + return true; +} + +bool Net::stringToAddress(const char *addressString, NetAddress *address) +{ + if(dStrnicmp(addressString, "ipx:", 4)) + { + // assume IP if it doesn't have ipx: at the front. + + if(!dStrnicmp(addressString, "ip:", 3)) + addressString += 3; // eat off the ip: + + sockaddr_in ipAddr; + char remoteAddr[256]; + if(strlen(addressString) > 255) + return false; + + dStrcpy(remoteAddr, addressString); + + char *portString = dStrchr(remoteAddr, ':'); + if(portString) + *portString++ = '\0'; + + struct hostent *hp; + if(!dStricmp(remoteAddr, "broadcast")) + ipAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + else + { + if (inet_aton(remoteAddr,&ipAddr.sin_addr) == 0) // error + { + if((hp = gethostbyname(remoteAddr)) == 0) + return false; + else + memcpy(&ipAddr.sin_addr.s_addr, hp->h_addr, sizeof(in_addr)); + } + } + if(portString) + ipAddr.sin_port = htons(dAtoi(portString)); + else + ipAddr.sin_port = htons(defaultPort); + ipAddr.sin_family = AF_INET; + IPSocketToNetAddress(&ipAddr, address); + return true; + } + else + { + S32 i; + S32 port; + + address->type = NetAddress::IPXAddress; + for(i = 0; i < 6; i++) + address->nodeNum[i] = 0xFF; + + // it's an IPX string + addressString += 4; + if(!dStricmp(addressString, "broadcast")) + { + address->port = defaultPort; + return true; + } + else if(sscanf(addressString, "broadcast:%d", &port) == 1) + { + address->port = port; + return true; + } + else + { + S32 nodeNum[6]; + S32 netNum[4]; + S32 count = dSscanf(addressString, "%2x%2x%2x%2x:%2x%2x%2x%2x%2x%2x:%d", + &netNum[0], &netNum[1], &netNum[2], &netNum[3], + &nodeNum[0], &nodeNum[1], &nodeNum[2], &nodeNum[3], &nodeNum[4], &nodeNum[5], + &port); + + if(count == 10) + { + port = defaultPort; + count++; + } + if(count != 11) + return false; + + for(i = 0; i < 6; i++) + address->nodeNum[i] = nodeNum[i]; + for(i = 0; i < 4; i++) + address->netNum[i] = netNum[i]; + address->port = port; + return true; + } + } +} + +void Net::addressToString(const NetAddress *address, char addressString[256]) +{ + if(address->type == NetAddress::IPAddress) + { + sockaddr_in ipAddr; + netToIPSocketAddress(address, &ipAddr); + + if(ipAddr.sin_addr.s_addr == htonl(INADDR_BROADCAST)) + dSprintf(addressString, 256, "IP:Broadcast:%d", ntohs(ipAddr.sin_port)); + else + dSprintf(addressString, 256, "IP:%s:%d", inet_ntoa(ipAddr.sin_addr), + ntohs(ipAddr.sin_port)); +// dSprintf(addressString, 256, "IP:%d:%d", ipAddr.sin_addr.s_addr, +// ntohs(ipAddr.sin_port)); + } + else + { + return; + dSprintf(addressString, 256, "IPX:%.2X%.2X%.2X%.2X:%.2X%.2X%.2X%.2X%.2X%.2X:%d", + address->netNum[0], address->netNum[1], address->netNum[2], address->netNum[3], + address->nodeNum[0], address->nodeNum[1], address->nodeNum[2], address->nodeNum[3], address->nodeNum[4], address->nodeNum[5], + address->port); + } +} + +Net::Error getLastError() +{ + if (errno == EAGAIN) + return Net::WouldBlock; + return Net::UnknownError; +} + diff --git a/platformX86UNIX/x86UNIXOGLVideo.client.cpp b/platformX86UNIX/x86UNIXOGLVideo.client.cpp new file mode 100644 index 0000000..63f4b28 --- /dev/null +++ b/platformX86UNIX/x86UNIXOGLVideo.client.cpp @@ -0,0 +1,488 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "platform/event.h" +#include "platform/gameInterface.h" + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/platformGL.h" +#include "platformX86UNIX/x86UNIXOGLVideo.h" +#include "platformX86UNIX/x86UNIXState.h" + +#include +#include +#include + +//------------------------------------------------------------------------------ +bool InitOpenGL() +{ + DisplayDevice::init(); + + // Get the video settings from the prefs: + const char* resString = Con::getVariable( "$pref::Video::resolution" ); + char* tempBuf = new char[dStrlen( resString ) + 1]; + dStrcpy( tempBuf, resString ); + char* temp = dStrtok( tempBuf, " x\0" ); + U32 width = ( temp ? dAtoi( temp ) : 800 ); + temp = dStrtok( NULL, " x\0" ); + U32 height = ( temp ? dAtoi( temp ) : 600 ); + temp = dStrtok( NULL, "\0" ); + U32 bpp = ( temp ? dAtoi( temp ) : 16 ); + delete [] tempBuf; + + bool fullScreen = Con::getBoolVariable( "$pref::Video::fullScreen" ); + + // the only supported video device in unix is OpenGL + if ( !Video::setDevice( "OpenGL", width, height, bpp, fullScreen ) ) + { + Con::errorf("Unable to create default OpenGL mode: %d %d %d %d", + width, height, bpp, fullScreen); + + // if we can't create the default, attempt to create a "safe" window + if ( !Video::setDevice( "OpenGL", 640, 480, 16, true ) ) + { + DisplayErrorAlert("Could not find a compatible OpenGL display " \ + "resolution. Please check your driver configuration."); + return false; + } + } + + return true; +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::smCanSwitchBitDepth = false; + +//------------------------------------------------------------------------------ +OpenGLDevice::OpenGLDevice() +{ + initDevice(); +} + +//------------------------------------------------------------------------------ +OpenGLDevice::~OpenGLDevice() +{ +} + +//------------------------------------------------------------------------------ +void OpenGLDevice::addResolution(S32 width, S32 height, bool check) +{ + Point2I desktopSize = x86UNIXState->getDesktopSize(); + U32 desktopBpp = x86UNIXState->getDesktopBpp(); + + // don't allow any resolution under this size + if (width < 640 || height < 480) + return; + + if (check) + { + // don't allow resolutions that exceed the current desktop size + if (width > desktopSize.x || height > desktopSize.y) + return; + } + + if (smCanSwitchBitDepth) + { + // add both 16 and 32 bit resolutions + mResolutionList.push_back(Resolution(width, height, 16)); + mResolutionList.push_back(Resolution(width, height, 32)); + } + else + { + // add just the desktop resolution + mResolutionList.push_back(Resolution(width, height, desktopBpp)); + } +} + +//------------------------------------------------------------------------------ +void OpenGLDevice::initDevice() +{ + mDeviceName = "OpenGL"; + mFullScreenOnly = false; +} + +//------------------------------------------------------------------------------ +void OpenGLDevice::loadResolutions() +{ + mResolutionList.clear(); + + // X cannot switch bit depths on the fly. In case this feature is + // implemented someday, calling this function will let you take + // advantage of it + if (Con::getBoolVariable("$pref::Unix::CanSwitchBitDepth")) + smCanSwitchBitDepth = true; + + // add some default resolutions + addResolution(640, 480); + addResolution(800, 600); + addResolution(1024, 768); + addResolution(1152, 864); + addResolution(1280, 1024); + addResolution(1600, 1200); + + // specifying full screen should give us the resolutions that the + // X server allows + SDL_Rect** modes = SDL_ListModes(NULL, SDL_FULLSCREEN); + if (modes && + (modes != (SDL_Rect **)-1)) + { + for (int i = 0; modes[i] != NULL; ++i) + { + // do we already have this mode? + bool found = false; + for (Vector::iterator iter = mResolutionList.begin(); + iter != mResolutionList.end(); + ++iter) + { + if (iter->w == modes[i]->w && iter->h == modes[i]->h) + { + found = true; + break; + } + } + if (!found) + // don't check these resolutions because they should be OK + // (and checking might drop resolutions that are higher than the + // current desktop bpp) + addResolution(modes[i]->w, modes[i]->h, false); + } + } +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::activate( U32 width, U32 height, U32 bpp, bool fullScreen ) +{ + if (!setScreenMode(width, height, bpp, fullScreen)) + { + Con::printf("Unable to set screen mode."); + return false; + } + + // Output some driver info to the console + const char* vendorString = (const char*) glGetString( GL_VENDOR ); + const char* rendererString = (const char*) glGetString( GL_RENDERER ); + const char* versionString = (const char*) glGetString( GL_VERSION ); + Con::printf( "OpenGL driver information:" ); + if ( vendorString ) + Con::printf( " Vendor: %s", vendorString ); + if ( rendererString ) + Con::printf( " Renderer: %s", rendererString ); + if ( versionString ) + Con::printf( " Version: %s", versionString ); + + GL_EXT_Init(); + + Con::setVariable( "$pref::Video::displayDevice", mDeviceName ); + + // Do this here because we now know about the extensions: + if ( gGLState.suppSwapInterval ) + setVerticalSync( + !Con::getBoolVariable( "$pref::Video::disableVerticalSync" ) ); + Con::setBoolVariable("$pref::OpenGL::allowTexGen", true); + + return true; +} + + +//------------------------------------------------------------------------------ +void OpenGLDevice::shutdown() +{ + // Shutdown is deferred to Platform::shutdown() +} + +//------------------------------------------------------------------------------ +static void PrintGLAttributes() +{ + int doubleBuf; + int bufferSize, depthSize, stencilSize; + int red, green, blue, alpha; + int aRed, aGreen, aBlue, aAlpha; + + SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &doubleBuf); + SDL_GL_GetAttribute(SDL_GL_BUFFER_SIZE, &bufferSize); + SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthSize); + SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencilSize); + SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &red); + SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &green); + SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blue); + SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &alpha); + SDL_GL_GetAttribute(SDL_GL_ACCUM_RED_SIZE, &aRed); + SDL_GL_GetAttribute(SDL_GL_ACCUM_GREEN_SIZE, &aGreen); + SDL_GL_GetAttribute(SDL_GL_ACCUM_BLUE_SIZE, &aBlue); + SDL_GL_GetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, &aAlpha); + + Con::printf("OpenGL Attributes:"); + Con::printf(" DoubleBuffer: %d", doubleBuf); + Con::printf(" BufferSize: %d, DepthSize: %d, StencilSize: %d", + bufferSize, depthSize, stencilSize); + Con::printf(" Red: %d, Green: %d, Blue: %d, Alpha: %d", + red, green, blue, alpha); + Con::printf(" Accum Red: %d, Green: %d, Blue: %d, Alpha: %d", + aRed, aGreen, aBlue, aAlpha); +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setScreenMode( U32 width, U32 height, U32 bpp, + bool fullScreen, bool forceIt, bool repaint ) +{ + // load resolutions, this is done lazily so that we can check the setting + // of smCanSwitchBitDepth, which may be overridden by console + if (mResolutionList.size()==0) + loadResolutions(); + + if (mResolutionList.size()==0) + { + Con::printf("No resolutions available!"); + return false; + } + + if (bpp == 0) + { + // bpp comes in as "0" when it is set to "Default" + bpp = x86UNIXState->getDesktopBpp(); + } + + if (height == 0 || width == 0) + { + // paranoia check. set it to the default to prevent crashing + width = 800; + height = 600; + } + + U32 desktopDepth = x86UNIXState->getDesktopBpp(); + // if we can't switch bit depths and the requested bpp is not equal to + // the desktop bpp, set bpp to the desktop bpp + if (!smCanSwitchBitDepth && + bpp != desktopDepth) + { + bpp = desktopDepth; + } + + bool IsInList = false; + + Resolution NewResolution( width, height, bpp ); + + // See if the desired resolution is in the list + if ( mResolutionList.size() ) + { + for ( int i = 0; i < mResolutionList.size(); i++ ) + { + if ( width == mResolutionList[i].w + && height == mResolutionList[i].h + && bpp == mResolutionList[i].bpp ) + { + IsInList = true; + break; + } + } + + if ( !IsInList ) + { + Con::printf( "Selected resolution not available: %d %d %d", + width, height, bpp); + return false; + } + } + else + { + AssertFatal( false, "No resolution list found!!" ); + } + + // Here if we found a matching resolution in the list + + bool needResurrect = false; + if (x86UNIXState->windowCreated()) + { + Con::printf( "Killing the texture manager..." ); + Game->textureKill(); + needResurrect = true; + } + + // Set the desired GL Attributes + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); +// JMQ: NVIDIA 2802+ doesn't like this setting for stencil size +// SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0); + SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 0); +// SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); +// SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); +// SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); +// SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + + U32 flags = SDL_OPENGL; + if (fullScreen) + flags |= SDL_FULLSCREEN; + + Con::printf( "Setting screen mode to %dx%dx%d (%s)...", width, height, + bpp, ( fullScreen ? "fs" : "w" ) ); + + // set the new video mode + if (SDL_SetVideoMode(width, height, bpp, flags) == NULL) + { + Con::printf("Unable to set SDL Video Mode: %s", SDL_GetError()); + return false; + } + + PrintGLAttributes(); + + // clear screen here to prevent buffer garbage from being displayed when + // video mode is switched + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + if ( needResurrect ) + { + // Reload the textures: + Con::printf( "Resurrecting the texture manager..." ); + Game->textureResurrect(); + } + + if ( gGLState.suppSwapInterval ) + setVerticalSync( !Con::getBoolVariable( "$pref::Video::disableVerticalSync" ) ); + + // reset the window in platform state + SDL_SysWMinfo sysinfo; + SDL_VERSION(&sysinfo.version); + if (SDL_GetWMInfo(&sysinfo) == 0) + { + Con::printf("Unable to set SDL Video Mode: %s", SDL_GetError()); + return false; + } + x86UNIXState->setWindow(sysinfo.info.x11.window); + + // set various other parameters + x86UNIXState->setWindowCreated(true); + smCurrentRes = NewResolution; + Platform::setWindowSize ( width, height ); + smIsFullScreen = fullScreen; + Con::setBoolVariable( "$pref::Video::fullScreen", smIsFullScreen ); + char tempBuf[15]; + dSprintf( tempBuf, sizeof( tempBuf ), "%d %d %d", + smCurrentRes.w, smCurrentRes.h, smCurrentRes.bpp ); + Con::setVariable( "$pref::Video::resolution", tempBuf ); + + // post a TORQUE_SETVIDEOMODE user event + SDL_Event event; + event.type = SDL_USEREVENT; + event.user.code = TORQUE_SETVIDEOMODE; + event.user.data1 = NULL; + event.user.data2 = NULL; + SDL_PushEvent(&event); + + // reset the caption + SDL_WM_SetCaption(x86UNIXState->getWindowName(), NULL); + + // repaint + if ( repaint ) + Con::evaluate( "resetCanvas();" ); + + return true; +} + +//------------------------------------------------------------------------------ +void OpenGLDevice::swapBuffers() +{ + SDL_GL_SwapBuffers(); +} + +//------------------------------------------------------------------------------ +const char* OpenGLDevice::getDriverInfo() +{ + const char* vendorString = (const char*) glGetString( GL_VENDOR ); + const char* rendererString = (const char*) glGetString( GL_RENDERER ); + const char* versionString = (const char*) glGetString( GL_VERSION ); + const char* extensionsString = (const char*) glGetString( GL_EXTENSIONS ); + + U32 bufferLen = ( vendorString ? dStrlen( vendorString ) : 0 ) + + ( rendererString ? dStrlen( rendererString ) : 0 ) + + ( versionString ? dStrlen( versionString ) : 0 ) + + ( extensionsString ? dStrlen( extensionsString ) : 0 ) + + 4; + + char* returnString = Con::getReturnBuffer( bufferLen ); + dSprintf( returnString, bufferLen, "%s\t%s\t%s\t%s", + ( vendorString ? vendorString : "" ), + ( rendererString ? rendererString : "" ), + ( versionString ? versionString : "" ), + ( extensionsString ? extensionsString : "" ) ); + + return( returnString ); +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::getGammaCorrection(F32 &g) +{ + U16 redtable[256]; + U16 greentable[256]; + U16 bluetable[256]; + + if (SDL_GetGammaRamp(redtable, greentable, bluetable) == -1) + { + Con::warnf("getGammaCorrection error: %s", SDL_GetError()); + return false; + } + + F32 csum = 0.0; + U32 ccount = 0; + + for (U16 i = 0; i < 256; ++i) + { + if (i != 0 && redtable[i] != 0 && redtable[i] != 65535) + { + F64 b = (F64) i/256.0; + F64 a = (F64) redtable[i]/65535.0; + F32 c = (F32) (mLog(a)/mLog(b)); + + csum += c; + ++ccount; + } + } + g = csum/ccount; + + return true; +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setGammaCorrection(F32 g) +{ + U16 redtable[256]; + U16 greentable[256]; + U16 bluetable[256]; + + for (U16 i = 0; i < 256; ++i) + redtable[i] = static_cast(mPow((F32) i/256.0f, g) * 65535.0f); + dMemcpy(greentable,redtable,256*sizeof(U16)); + dMemcpy(bluetable,redtable,256*sizeof(U16)); + + S32 ok = SDL_SetGammaRamp(redtable, greentable, bluetable); + if (ok == -1) + Con::warnf("Error setting gamma correction: %s", SDL_GetError()); + + return ok != -1; +} + +//------------------------------------------------------------------------------ +bool OpenGLDevice::setVerticalSync( bool on ) +{ + Con::printf("WARNING: OpenGLDevice::setVerticalSync is unimplemented %s %d\n", __FILE__, __LINE__); + return false; +#if 0 + if ( !gGLState.suppSwapInterval ) + return( false ); + + return( qwglSwapIntervalEXT( on ? 1 : 0 ) ); +#endif +} + +//------------------------------------------------------------------------------ +DisplayDevice* OpenGLDevice::create() +{ + return new OpenGLDevice(); +} diff --git a/platformX86UNIX/x86UNIXOGLVideo.h b/platformX86UNIX/x86UNIXOGLVideo.h new file mode 100644 index 0000000..61a52e3 --- /dev/null +++ b/platformX86UNIX/x86UNIXOGLVideo.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXOGLVIDEO_H_ +#define _X86UNIXOGLVIDEO_H_ + +#ifndef _PLATFORMVIDEO_H_ +#include "platform/platformVideo.h" +#endif + +class OpenGLDevice : public DisplayDevice +{ + static bool smCanSwitchBitDepth; + + bool mRestoreGamma; + U16 mOriginalRamp[256*3]; + + void addResolution(S32 width, S32 height, bool check=true); + + public: + OpenGLDevice(); + virtual ~OpenGLDevice(); + + void initDevice(); + bool activate( U32 width, U32 height, U32 bpp, bool fullScreen ); + void shutdown(); + void destroy(); + bool setScreenMode( U32 width, U32 height, U32 bpp, bool fullScreen, bool forceIt = false, bool repaint = true ); + void swapBuffers(); + const char* getDriverInfo(); + bool getGammaCorrection(F32 &g); + bool setGammaCorrection(F32 g); + bool setVerticalSync( bool on ); + void loadResolutions(); + + static DisplayDevice* create(); +}; + +#endif // _H_X86UNIXOGLVIDEO diff --git a/platformX86UNIX/x86UNIXOpenAL.client.cpp b/platformX86UNIX/x86UNIXOpenAL.client.cpp new file mode 100644 index 0000000..3d4f1c1 --- /dev/null +++ b/platformX86UNIX/x86UNIXOpenAL.client.cpp @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "console/console.h" + +#include + +#include +#include +#define INITGUID +#include + + +// Define the OpenAL and Extension Stub functions +#define AL_FUNCTION(fn_return, fn_name, fn_args, fn_value) fn_return stub_##fn_name fn_args{ fn_value } +#include +#include +#include +#undef AL_FUNCTION + + +// Declare the OpenAL and Extension Function pointers +// And initialize them to the stub functions +#define AL_FUNCTION(fn_return,fn_name,fn_args, fn_value) fn_return (*fn_name)fn_args = stub_##fn_name; +#include +#include +#include +#undef AL_FUNCTION + +// Declarations for the "emulated" functions (al functions that don't +// exist in the loki openal implementation) +ALboolean emu_alGetBoolean(ALenum param); +ALint emu_alGetInteger(ALenum param); +ALfloat emu_alGetFloat(ALenum param); +ALdouble emu_alGetDouble(ALenum param); +void emu_alListeneri( ALenum param, ALint value ); +void emu_alGetListener3f(ALenum pname,ALfloat *v1,ALfloat *v2,ALfloat *v3); +ALCdevice* emu_alcGetContextsDevice(ALCcontext *context); + +static void *dlHandle = NULL; +static char* dlError = "no error"; + +/*! Get an "emulated" function address and bind it to the function pointer +*/ +static bool bindEmulatedFunction(void *&fnAddress, const char *name) +{ + fnAddress = NULL; + + if (dStrcmp(name, "alGetBoolean")==0) + fnAddress = (void*)&emu_alGetBoolean; + else if (dStrcmp(name, "alGetInteger")==0) + fnAddress = (void*)&emu_alGetInteger; + else if (dStrcmp(name, "alGetFloat")==0) + fnAddress = (void*)&emu_alGetFloat; + else if (dStrcmp(name, "alGetDouble")==0) + fnAddress = (void*)&emu_alGetDouble; + else if (dStrcmp(name, "alListeneri")==0) + fnAddress = (void*)&emu_alListeneri; + else if (dStrcmp(name, "alGetListener3f")==0) + fnAddress = (void*)&emu_alGetListener3f; + else if (dStrcmp(name, "alcGetContextsDevice")==0) + fnAddress = (void*)&emu_alcGetContextsDevice; + + return fnAddress != NULL; +} + +/*! Get a function address from the OpenAL DLL and bind it to the +* function pointer +*/ +static bool bindFunction( void *&fnAddress, const char *name ) +{ + fnAddress = dlsym(dlHandle, name); + if( !fnAddress ) + if (bindEmulatedFunction(fnAddress, name)) + Con::warnf(ConsoleLogEntry::General, " Missing OpenAL function '%s', using emulated function", name); + else + Con::errorf(ConsoleLogEntry::General, " Missing OpenAL function '%s'", name); + return (fnAddress != NULL); +} + +/*! Get a function address for an OpenAL extension function and bind it +* to it's function pointer +*/ +static bool bindExtensionFunction( void *&fnAddress, const char *name ) +{ + fnAddress = alGetProcAddress( (ALubyte*)name ); + if( !fnAddress ) + Con::errorf(ConsoleLogEntry::General, " Missing OpenAL Extension function '%s'", name); + return (fnAddress != NULL); +} + +/*! Bind the functions in the OpenAL DLL to the al interface functions +*/ +static bool bindOpenALFunctions() +{ + bool result = true; + #define AL_FUNCTION(fn_return, fn_name, fn_args, fn_value) result &= bindFunction( *(void**)&fn_name, #fn_name); + #include + #include + #undef AL_FUNCTION + return result; +} + +/*! Bind the stub functions to the al interface functions +*/ +static void unbindOpenALFunctions() +{ + #define AL_FUNCTION(fn_return, fn_name, fn_args, fn_value) fn_name = stub_##fn_name; + #include + #include + #include + #undef AL_FUNCTION +} + +/*! Bind the EAX Extension functions to the EAX interface functions +*/ +static bool bindEAXFunctions() +{ + bool result = true; + #define AL_FUNCTION(fn_return, fn_name, fn_args, fn_value) result &= bindExtensionFunction( *(void**)&fn_name, #fn_name); + #include + #undef AL_FUNCTION + return result; +} + +// Definitions for the emulated functions +ALboolean emu_alGetBoolean(ALenum param) +{ + ALboolean alboolean; + alGetBooleanv(param, &alboolean); + return alboolean; +} + +ALint emu_alGetInteger(ALenum param) +{ + ALint alint; + alGetIntegerv(param, &alint); + return alint; +} + +ALfloat emu_alGetFloat(ALenum param) +{ + ALfloat alfloat; + alGetFloatv(param, &alfloat); + return alfloat; +} + +ALdouble emu_alGetDouble(ALenum param) +{ + ALdouble aldouble; + alGetDoublev(param, &aldouble); + return aldouble; +} + +void emu_alGetListener3f(ALenum pname,ALfloat *v0,ALfloat *v1,ALfloat *v2) +{ + ALfloat ptArray[10]; + ptArray[0] = *v0; + ptArray[1] = *v1; + ptArray[2] = *v2; + alGetListenerfv(pname, ptArray); + *v0 = ptArray[0]; + *v1 = ptArray[1]; + *v2 = ptArray[2]; +} + +void emu_alListeneri( ALenum param, ALint value ) +{ + alListenerf(param, static_cast(value)); +} + +ALCdevice* emu_alcGetContextsDevice(ALCcontext *context) +{ + // this function isn't emulated + AssertFatal(false, "alcGetContextsDevice is not available"); + return NULL; +} + +namespace Audio +{ + +/*! Shutdown and Unload the OpenAL DLL +*/ +void OpenALDLLShutdown() +{ + if (dlHandle != NULL) + { + dlclose(dlHandle); + // FreeBSD didn't like that const dlerror() was returning. + if ((dlError = (char *)dlerror()) != NULL) + Con::errorf(ConsoleLogEntry::General, " Error unloading OpenAL Library: %s", dlError); + } + dlHandle = NULL; + + unbindOpenALFunctions(); +} + +/*! Dynamically Loads the OpenAL DLL if present and binds all the functions. +* If there is no DLL or an unexpected error occurs binding functions the +* stub functions are automatically bound. +*/ +bool OpenALDLLInit() +{ + OpenALDLLShutdown(); + + const char* libName = "libopenal.so"; + + // these are relative to the current working directory + const char* searchPath[] = { + "lib", + "tplib", // superceeded by "lib", here for backass compatibility + "", // i.e.: current working directory + NULL // this must be last + }; + + char openalPath[4096]; + for (int i = 0; searchPath[i] != NULL; ++i) + { + dSprintf(openalPath, sizeof(openalPath), "%s/%s/%s", + Platform::getWorkingDirectory(), + searchPath[i], + libName); + + Con::printf(" Searching for OpenAl at location : %s", openalPath); + dlHandle = dlopen(openalPath, RTLD_NOW); + if (dlHandle != NULL) + { + // found it + Con::printf(" Loading OpenAL: %s", openalPath); + break; + } + } + + if (dlHandle == NULL) + { + // couldn't find it in our searchPath, try the system path + dlHandle = dlopen(libName, RTLD_NOW); + if (dlHandle != NULL) + Con::printf(" Loading OpenAL from system (dlopen) path"); + } + + if (dlHandle != NULL) + { + // if the DLL loaded bind the OpenAL function pointers + if(bindOpenALFunctions()) + { + // if EAX is available bind it's function pointers + if (alIsExtensionPresent((ALubyte*)"EAX" )) + bindEAXFunctions(); + return(true); + } + + // an error occured, shutdown + OpenALDLLShutdown(); + } + else + { + Con::errorf(ConsoleLogEntry::General, " Error loading OpenAL Library: %s", dlerror()); + } + return(false); +} + +} // end namespace Audio + diff --git a/platformX86UNIX/x86UNIXProcessControl.cpp b/platformX86UNIX/x86UNIXProcessControl.cpp new file mode 100644 index 0000000..0efdc1c --- /dev/null +++ b/platformX86UNIX/x86UNIXProcessControl.cpp @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXState.h" +#include "platformX86UNIX/x86UNIXStdConsole.h" +#include "platform/platformInput.h" +#include "console/console.h" + +#include +#include +#include + +#ifndef DEDICATED +#include +#endif + +//----------------------------------------------------------------------------- +// This is a mainly a debugging function for intercepting a nonzero exit code +// and generating a core dump for a stack trace. +// Need an S64 here because postQuitMessage uses a U32, and +// forceshutdown uses an S32. So S64 is needed to +// accomodate them both +static void CheckExitCode(S64 exitCode) +{ + if (exitCode != 0) + { + Con::errorf(ConsoleLogEntry::General, + "Nonzero exit code: %d, triggering SIGSEGV for core dump", + exitCode); + kill(getpid(), SIGSEGV); + } +} + +//----------------------------------------------------------------------------- +static void SignalHandler(int sigtype) +{ + if (sigtype == SIGSEGV || sigtype == SIGTRAP) + { + signal(SIGSEGV, SIG_DFL); + signal(SIGTRAP, SIG_DFL); + // restore the signal handling to default so that we don't get into + // a crash loop with ImmediateShutdown + ImmediateShutdown(-sigtype, sigtype); + } + else + { + signal(sigtype, SIG_DFL); + dPrintf("Unknown signal caught by SignalHandler: %d\n", sigtype); + // exit to be safe + ImmediateShutdown(1); + } +} + +//----------------------------------------------------------------------------- +void Cleanup(bool minimal) +{ + if (!minimal) + { + Input::destroy(); + } + + StdConsole::destroy(); + SDL_Quit(); +} + +//----------------------------------------------------------------------------- +void ImmediateShutdown(S32 exitCode, S32 signalNum) +{ + bool segfault = signalNum > 0; + + Cleanup(segfault); + + if (!segfault) + { + dPrintf("Exiting\n"); + // exit (doesn't call destructors) + _exit(exitCode); + } + else + { +// there is a problem in kernel 2.4.17 which causes a hang when a segfault +// occurs. also subsequent runs of "ps" will hang and the machine has to be +// hard reset to clear up the problem +// JMQ: this bug appears to be fixed in 2.4.18 +//#define KERNEL_2_4_WORKAROUND +#ifdef KERNEL_2_4_WORKAROUND + dPrintf("Segmentation Fault (Exiting without core dump due to #define KERNEL_2_4_WORKAROUND)\n"); + dFflushStdout(); + _exit(exitCode); +#else + // kill with signal + kill(getpid(), signalNum); +#endif + } + +} + +//----------------------------------------------------------------------------- +void ProcessControlInit() +{ + // JMQ: ignore IO signals background read/write terminal (so that we don't + // get suspended in daemon mode) + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + + // we're not interested in the exit status of child processes, so this + // prevents zombies from accumulating. +#if defined(__FreeBSD__) + signal(SIGCHLD, SIG_IGN); +#else + signal(SIGCLD, SIG_IGN); +#endif + + // install signal handler for SIGSEGV, so that we can attempt + // clean shutdown + signal(SIGSEGV, &SignalHandler); + signal(SIGTRAP, &SignalHandler); +} + +//----------------------------------------------------------------------------- +void Platform::postQuitMessage(const U32 in_quitVal) +{ + // if we have a window send a quit event, otherwise just force shutdown +#if 0 + if (x86UNIXState->windowCreated()) + { + CheckExitCode(in_quitVal); + SendQuitEvent(); + } + else +#endif + { + forceShutdown(in_quitVal); + } +} + +//----------------------------------------------------------------------------- +void Platform::debugBreak() +{ + // in windows, "Calling DebugBreak causes the program to display + // a dialog box as if it had crashed." So we segfault. + Con::errorf(ConsoleLogEntry::General, + "Platform::debugBreak: triggering SIGSEGV for core dump"); + //kill(getpid(), SIGSEGV); + kill(getpid(), SIGTRAP); +} + +//----------------------------------------------------------------------------- +void Platform::forceShutdown(S32 returnValue) +{ + CheckExitCode(returnValue); + +#if 0 + // if a dedicated server is running, turn it off + if (x86UNIXState->isDedicated() && Game->isRunning()) + Game->setRunning(false); + else +#endif + ImmediateShutdown(returnValue); +} + +//----------------------------------------------------------------------------- +void Platform::outputDebugString(const char *string, ...) +{ + char buffer[2048]; + + va_list args; + va_start( args, string ); + + dVsprintf( buffer, sizeof(buffer), string, args ); + va_end( args ); + + U32 length = dStrlen(buffer); + if( length == (sizeof(buffer) - 1 ) ) + length--; + + buffer[length++] = '\n'; + buffer[length] = '\0'; + + fwrite(buffer, sizeof(char), length, stderr); +} + +//----------------------------------------------------------------------------- +// testing function +ConsoleFunction(debug_debugbreak, void, 1, 1, "debug_debugbreak()") +{ + Platform::debugBreak(); +} diff --git a/platformX86UNIX/x86UNIXRedbook.cpp b/platformX86UNIX/x86UNIXRedbook.cpp new file mode 100644 index 0000000..d10cd98 --- /dev/null +++ b/platformX86UNIX/x86UNIXRedbook.cpp @@ -0,0 +1,438 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "console/console.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platformRedBook.h" +#include "core/strings/stringFunctions.h" + +#if defined(__linux__) +#include +#include +#include +#include +#endif + +#include + +class UnixRedBookDevice : public RedBookDevice +{ +#if !defined(__FreeBSD__) + private: + S32 mDeviceId; + SDL_CD *mCD; + cdrom_volctrl mOriginalVolume; + bool mVolumeInitialized; +#endif + bool mPlaying; + + void openVolume(); + void closeVolume(); + void setLastError(const char *); + + public: + UnixRedBookDevice(); + ~UnixRedBookDevice(); + + bool open(); + bool close(); + bool play(U32); + bool stop(); + bool getTrackCount(U32 *); + bool getVolume(F32 *); + bool setVolume(F32); + + bool isPlaying() { return mPlaying; } + bool updateStatus(); + void setDeviceInfo(S32 deviceId, const char *deviceName); +}; + +//------------------------------------------------------------------------------- +// Class: UnixRedBookDevice +//------------------------------------------------------------------------------- +UnixRedBookDevice::UnixRedBookDevice() +{ +#if !defined(__FreeBSD__) + mVolumeInitialized = false; + mDeviceId = -1; + mDeviceName = NULL; + mCD = NULL; + mPlaying = false; +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +UnixRedBookDevice::~UnixRedBookDevice() +{ +#if !defined(__FreeBSD__) + close(); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::updateStatus() +{ +#if !defined(__FreeBSD__) + AssertFatal(mCD, "mCD is NULL"); + + CDstatus status = SDL_CDStatus(mCD); + if (status == CD_ERROR) + { + setLastError("Error accessing device"); + return(false); + } + else if (status == CD_TRAYEMPTY) + { + setLastError("CD tray empty"); + return false; + } + + mPlaying = (status == CD_PLAYING); + return true; +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +void UnixRedBookDevice::setDeviceInfo(S32 deviceId, const char *deviceName) +{ +#if !defined(__FreeBSD__) + mDeviceId = deviceId; + mDeviceName = new char[dStrlen(deviceName) + 1]; + dStrcpy(mDeviceName, deviceName); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::open() +{ +#if !defined(__FreeBSD__) + if(mAcquired) + { + setLastError("Device is already open."); + return(false); + } + + // open the device + mCD = SDL_CDOpen(mDeviceId); + if (mCD == NULL) + { + setLastError(SDL_GetError()); + return false; + } + + mAcquired = true; + + openVolume(); + setLastError(""); + return(true); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::close() +{ +#if !defined(__FreeBSD__) + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + stop(); + closeVolume(); + + if (mCD != NULL) + { + SDL_CDClose(mCD); + mCD = NULL; + } + + mAcquired = false; + setLastError(""); + return(true); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::play(U32 track) +{ +#if !defined(__FreeBSD__) + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + U32 numTracks; + if(!getTrackCount(&numTracks)) + return(false); + + if(track >= numTracks) + { + setLastError("Track index is out of range"); + return(false); + } + + if (!updateStatus()) + return false; + + AssertFatal(mCD, "mCD is NULL"); + if (SDL_CDPlayTracks(mCD, track, 0, 1, 0) == -1) + { + setLastError(SDL_GetError()); + return false; + } + + mPlaying = true; + + setLastError(""); + return(true); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::stop() +{ +#if !defined(__FreeBSD__) + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + AssertFatal(mCD, "mCD is NULL"); + + if (SDL_CDStop(mCD) == -1) + { + setLastError(SDL_GetError()); + return(false); + } + + mPlaying = false; + + setLastError(""); + return(true); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::getTrackCount(U32 * numTracks) +{ +#if !defined(__FreeBSD__) + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + if (!updateStatus()) + return false; + + AssertFatal(mCD, "mCD is NULL"); + *numTracks = mCD->numtracks; + + return(true); +#endif // !defined(__FreeBSD__) +} + +template +static inline Type max(Type v1, Type v2) +{ +#if !defined(__FreeBSD__) + if (v1 <= v2) + return v2; + else + return v1; +#endif // !defined(__FreeBSD__) +} +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::getVolume(F32 * volume) +{ +#if !defined(__FreeBSD__) + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + if(!mVolumeInitialized) + { + setLastError("Volume failed to initialize"); + return(false); + } + +#if defined(__linux__) + AssertFatal(mCD, "mCD is NULL"); + + setLastError(""); + cdrom_volctrl sysvol; + if (ioctl(mCD->id, CDROMVOLREAD, &sysvol) == -1) + { + setLastError(strerror(errno)); + return(false); + } + U8 maxVol = max(sysvol.channel0, sysvol.channel1); + // JMQTODO: support different left/right channel volumes? + *volume = static_cast(maxVol) / 255.f; + return true; +#else + return(false); +#endif +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +bool UnixRedBookDevice::setVolume(F32 volume) +{ +#if !defined(__FreeBSD__) + if(!mAcquired) + { + setLastError("Device has not been acquired"); + return(false); + } + + if(!mVolumeInitialized) + { + setLastError("Volume failed to initialize"); + return(false); + } + +#if defined(__linux__) + AssertFatal(mCD, "mCD is NULL"); + + setLastError(""); + cdrom_volctrl sysvol; + volume = volume * 255.f; + if (volume > 255) + volume = 255; + if (volume < 0) + volume = 0; + sysvol.channel0 = sysvol.channel1 = static_cast<__u8>(volume); + if (ioctl(mCD->id, CDROMVOLCTRL, &sysvol) == -1) + { + setLastError(strerror(errno)); + return(false); + } + return true; +#else + return(false); +#endif +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +void UnixRedBookDevice::openVolume() +{ +#if !defined(__FreeBSD__) +// Its unforunate that we have to do it this way, but SDL does not currently +// support setting CD audio volume +#if defined(__linux__) + AssertFatal(mCD, "mCD is NULL"); + + setLastError(""); + + if (ioctl(mCD->id, CDROMVOLREAD, &mOriginalVolume) == -1) + { + setLastError(strerror(errno)); + return; + } + + mVolumeInitialized = true; +#else + setLastError("Volume failed to initialize"); +#endif +#endif // !defined(__FreeBSD__) +} + +void UnixRedBookDevice::closeVolume() +{ +#if !defined(__FreeBSD__) + if(!mVolumeInitialized) + return; + +#if defined(__linux__) + AssertFatal(mCD, "mCD is NULL"); + + setLastError(""); + + if (ioctl(mCD->id, CDROMVOLCTRL, &mOriginalVolume) == -1) + { + setLastError(strerror(errno)); + return; + } +#endif + + mVolumeInitialized = false; +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +void UnixRedBookDevice::setLastError(const char * error) +{ +#if !defined(__FreeBSD__) + RedBook::setLastError(error); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +void InstallRedBookDevices() +{ +#if !defined(__FreeBSD__) + Con::printf("CD Audio Init:"); + if (SDL_InitSubSystem(SDL_INIT_CDROM) == -1) + { + Con::printf(" Unable to initialize CD Audio: %s", SDL_GetError()); + return; + } + + S32 numDrives = SDL_CDNumDrives(); + if (numDrives == 0) + { + Con::printf(" No drives found."); + return; + } + + for (int i = 0; i < numDrives; ++i) + { + const char * deviceName = SDL_CDName(i); + Con::printf(" Installing CD Audio device: %s", deviceName); + + UnixRedBookDevice * device = new UnixRedBookDevice; + device->setDeviceInfo(i, deviceName); + RedBook::installDevice(device); + } + + Con::printf(" "); +#endif // !defined(__FreeBSD__) +} + +//------------------------------------------------------------------------------ +void PollRedbookDevices() +{ +#if !defined(__FreeBSD__) + UnixRedBookDevice *device = dynamic_cast(RedBook::getCurrentDevice()); + + if (device == NULL || !device->isPlaying()) + return; + + static const U32 PollDelay = 1000; + + static U32 lastPollTime = 0; + U32 curTime = Platform::getVirtualMilliseconds(); + + if (lastPollTime != 0 && + (curTime - lastPollTime) < PollDelay) + return; + + lastPollTime = curTime; + + if (device->isPlaying()) + { + device->updateStatus(); + if (!device->isPlaying()) + RedBook::handleCallback(RedBook::PlayFinished); + } +#endif // !defined(__FreeBSD__) +} diff --git a/platformX86UNIX/x86UNIXState.h b/platformX86UNIX/x86UNIXState.h new file mode 100644 index 0000000..95066ed --- /dev/null +++ b/platformX86UNIX/x86UNIXState.h @@ -0,0 +1,257 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mPoint2.h" +#include "platformX86UNIX/platformX86UNIX.h" +//#include "platformX86UNIX/platformGL.h" +#include "core/strings/stringFunctions.h" + +#ifndef DEDICATED +#include // for Display, Window and other X mojo +#else +#define Display int +#define Window int +#define Screen int +#endif + +#include // for basename + +typedef void (*LockFunc_t)(void); + +class DisplayPtrManager; + +class x86UNIXPlatformState +{ + friend class DisplayPtrManager; + + private: + Point2I mDesktopSize; + Point2I mWindowSize; + S32 mDesktopBpp; + Display *mDisplay; + Window mCurrentWindow; + Screen *mScreenPointer; + int mScreenNumber; + char mWindowName[40]; + char mExePathName[4096]; + char mExeName[40]; + bool mWindowCreated; + bool mWindowActive; + bool mWindowLocked; + bool mXWindowsRunning; + bool mDedicated; + bool mCDAudioEnabled; + bool mDSleep; + bool mUseRedirect; + + // Access to the display* needs to be controlled because the SDL event + // loop runs in a separate thread. If you need the display pointer, + // use the DisplayPtrManager class. See the clipboard functions in + // x86unixinput.cc for an example. + Display *getDisplayPointer() { return mDisplay; } + + public: + U32 currentTime; + + void setDisplayPointer( Display *displayPointer ) + { mDisplay = displayPointer; } + + void setScreenNumber( int newNumber ) { mScreenNumber = newNumber; } + int getScreenNumber() { return mScreenNumber; } + + void setScreenPointer( Screen *newScreenPointer ) + { mScreenPointer = newScreenPointer; } + Screen * getScreenPointer() { return mScreenPointer; } + + // for compatibility, convert 24 bpp to 32 + void setDesktopBpp( S32 bpp ) + { + if (bpp == 24) + mDesktopBpp = 32; + else + mDesktopBpp = bpp; + } + S32 getDesktopBpp() { return mDesktopBpp; } + + void setDesktopSize( S32 horizontal, S32 vertical ) + { mDesktopSize.set( horizontal, vertical ); } + Point2I getDesktopSize() { return mDesktopSize; } + + void setWindow( Window newWindow ) { mCurrentWindow = newWindow; } + Window getWindow() { return mCurrentWindow; } + + void setWindowSize (S32 horizontal, S32 vertical ) + { mWindowSize.set ( horizontal, vertical ); } + void setWindowSize( Point2I size ) { mWindowSize = size; } + Point2I& getWindowSize() { return ( mWindowSize ); } + + void setWindowName (const char * windowName) + { + if (windowName == NULL) + dStrncpy( mWindowName, "", sizeof( mWindowName )); + else + dStrncpy( mWindowName, windowName, sizeof( mWindowName ) ); + } + const char * getWindowName() { return mWindowName; } + + void setExePathName(const char* exePathName) + { + if (exePathName == NULL) + dStrncpy(mExePathName, "", sizeof(mExePathName)); + else + dStrncpy(mExePathName, exePathName, sizeof(mExePathName)); + + // set the base exe name field + char tempBuf[2048]; + dStrncpy(tempBuf, mExePathName, 2048); + dStrncpy(mExeName, basename(tempBuf), sizeof(mExeName)); + } + const char * getExePathName() { return mExePathName; } + const char * getExeName() { return mExeName; } + + bool windowCreated() { return mWindowCreated; } + bool windowActive() { return mWindowActive; } + bool windowLocked() { return mWindowLocked; } + void setWindowCreated(bool windowCreated) + { mWindowCreated = windowCreated; } + void setWindowActive(bool windowActive) + { mWindowActive = windowActive; } + void setWindowLocked(bool windowLocked) + { mWindowLocked = windowLocked; } + + bool isXWindowsRunning() { return mXWindowsRunning; } + void setXWindowsRunning(bool running) { mXWindowsRunning = running; } + + bool isDedicated() { return mDedicated; } + void setDedicated(bool dedicated) { mDedicated = dedicated; } + + bool getCDAudioEnabled() { return mCDAudioEnabled; } + void setCDAudioEnabled(bool enabled) { mCDAudioEnabled = enabled; } + + bool getDSleep() { return mDSleep; } + void setDSleep(bool enabled) { mDSleep = enabled; } + + bool getUseRedirect() { return mUseRedirect; } + void setUseRedirect(bool enabled) { mUseRedirect = enabled; } + + x86UNIXPlatformState() + { + currentTime = 0; + mDesktopBpp = 16; + mDesktopSize.set( 0, 0 ); + mWindowSize.set( 800, 600 ); + setWindowName("Torque"); + setExePathName(NULL); + mWindowCreated = mWindowActive = mWindowLocked = false; + mXWindowsRunning = false; + mDedicated = false; + mCDAudioEnabled = false; + mDSleep = false; +#ifdef USE_FILE_REDIRECT + mUseRedirect = true; +#else + mUseRedirect = false; +#endif + } +}; + +extern x86UNIXPlatformState * x86UNIXState; + +class DisplayPtrManager +{ + // static interface + private: + static bool sgDisplayLocked; + static LockFunc_t sgLockFunc; + static LockFunc_t sgUnlockFunc; + + static bool lockDisplay() + { + if (!sgDisplayLocked && sgLockFunc) + { + sgLockFunc(); + sgDisplayLocked = true; + return true; + } + else + return false; + } + static void unlockDisplay() + { + if (sgDisplayLocked && sgUnlockFunc) + { + sgUnlockFunc(); + sgDisplayLocked = false; + } + } + + //friend Display* x86UNIXPlatformState::getDisplayPointer(); + + public: + static void setDisplayLockFunction(LockFunc_t lockFunc) + { sgLockFunc = lockFunc; } + static void setDisplayUnlockFunction(LockFunc_t unlockFunc) + { sgUnlockFunc = unlockFunc; } + + // nonstatic interface + private: + bool mAcquiredLock; // true if this instance acquired the display lock + // (multiple instances of DisplayPtrManager can coexist, but only + // the first to access the display pointer will be responsible for + // acquiring and releasing the lock) + bool mOpenedDisplay; // true if this instance created a display pointer + // because the one in platform state was null. + Display* mDisplay; + + private: + Display* openDisplay() + { +#ifndef DEDICATED + mDisplay = XOpenDisplay(NULL); + if (mDisplay != NULL) + mOpenedDisplay = true; +#endif + return mDisplay; + } + + void closeDisplay() + { + if (mOpenedDisplay) + { +#ifndef DEDICATED + XCloseDisplay(mDisplay); + mDisplay = NULL; + mOpenedDisplay = false; +#endif + } + } + public: + DisplayPtrManager() + { + mAcquiredLock = false; + mOpenedDisplay = false; + mDisplay = NULL; + } + + ~DisplayPtrManager() + { + if (mAcquiredLock) + { + DisplayPtrManager::unlockDisplay(); + mAcquiredLock = false; + } + closeDisplay(); + } + + Display* getDisplayPointer() + { + Display* display = x86UNIXState->getDisplayPointer(); + if (display == NULL) + return openDisplay(); + + mAcquiredLock = DisplayPtrManager::lockDisplay(); + return display; + } +}; diff --git a/platformX86UNIX/x86UNIXStdConsole.h b/platformX86UNIX/x86UNIXStdConsole.h new file mode 100644 index 0000000..92833c6 --- /dev/null +++ b/platformX86UNIX/x86UNIXStdConsole.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXSTDCONSOLE_H_ +#define _X86UNIXSTDCONSOLE_H_ + +#define MAX_CMDS 10 +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif + +#include + +class StdConsole +{ + bool stdConsoleEnabled; + // true if we're running in the background + bool inBackground; + + int stdOut; + int stdIn; + int stdErr; + char inbuf[512]; + S32 inpos; + bool lineOutput; + char curTabComplete[512]; + S32 tabCompleteStart; + char rgCmds[MAX_CMDS][512]; + S32 iCmdIndex; + + // this holds the original terminal state + // before we messed with it + struct termios *originalTermState; + + void printf(const char *s, ...); + +public: + StdConsole(); + virtual ~StdConsole(); + void process(); + void enable(bool); + void processConsoleLine(const char *consoleLine); + static void create(); + static void destroy(); + static bool isEnabled(); + void resetTerminal(); +}; + +extern StdConsole *stdConsole; + +#endif diff --git a/platformX86UNIX/x86UNIXStrings.cpp b/platformX86UNIX/x86UNIXStrings.cpp new file mode 100644 index 0000000..7a641e8 --- /dev/null +++ b/platformX86UNIX/x86UNIXStrings.cpp @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "core/strings/stringFunctions.h" +#include +#include +#include + + +char *stristr(char *szStringToBeSearched, const char *szSubstringToSearchFor) +{ + char *pPos = NULL; + char *szCopy1 = NULL; + char *szCopy2 = NULL; + + // verify parameters + if ( szStringToBeSearched == NULL || + szSubstringToSearchFor == NULL ) + { + return szStringToBeSearched; + } + + // empty substring - return input (consistent with strstr) + if (strlen(szSubstringToSearchFor) == 0 ) { + return szStringToBeSearched; + } + + szCopy1 = dStrlwr(strdup(szStringToBeSearched)); + szCopy2 = dStrlwr(strdup(szSubstringToSearchFor)); + + if ( szCopy1 == NULL || szCopy2 == NULL ) { + // another option is to raise an exception here + free((void*)szCopy1); + free((void*)szCopy2); + return NULL; + } + + pPos = strstr(szCopy1, szCopy2); + + if ( pPos != NULL ) { + // map to the original string + pPos = szStringToBeSearched + (pPos - szCopy1); + } + + free((void*)szCopy1); + free((void*)szCopy2); + + return pPos; +} // stristr(...) + diff --git a/platformX86UNIX/x86UNIXStub.dedicated.cpp b/platformX86UNIX/x86UNIXStub.dedicated.cpp new file mode 100644 index 0000000..ac930ff --- /dev/null +++ b/platformX86UNIX/x86UNIXStub.dedicated.cpp @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platformInput.h" +#include "platformX86UNIX/x86UNIXFont.h" +#include "platform/nativeDialogs/fileDialog.h" + +#if 0 +// declare stub functions +#define GL_FUNCTION(fn_return, fn_name, fn_args, fn_value) fn_return stub_##fn_name fn_args{ fn_value } +#include "platform/GLCoreFunc.h" +#include "platform/GLExtFunc.h" +#include "platform/GLUFunc.h" +#undef GL_FUNCTION + +// point gl function pointers at stub functions +#define GL_FUNCTION(fn_return,fn_name,fn_args, fn_value) fn_return (*fn_name)fn_args = stub_##fn_name; +#include "platform/GLCoreFunc.h" +#include "platform/GLExtFunc.h" +#include "platform/GLUFunc.h" +#undef GL_FUNCTION + +GLState gGLState; +bool gOpenGLDisablePT = false; +bool gOpenGLDisableCVA = false; +bool gOpenGLDisableTEC = false; +bool gOpenGLDisableARBMT = false; +bool gOpenGLDisableFC = false; +bool gOpenGLDisableTCompress = false; +bool gOpenGLNoEnvColor = false; +float gOpenGLGammaCorrection = 0.5; +bool gOpenGLNoDrawArraysAlpha = false; + +// AL stubs +//#include +//#include +//#define INITGUID +//#include + +// Define the OpenAL and Extension Stub functions +#define AL_FUNCTION(fn_return, fn_name, fn_args, fn_value) fn_return stub_##fn_name fn_args{ fn_value } +#include +#include +#include +#undef AL_FUNCTION +#include "platform/platformInput.h" + +// Declare the OpenAL and Extension Function pointers +// And initialize them to the stub functions +#define AL_FUNCTION(fn_return,fn_name,fn_args, fn_value) fn_return (*fn_name)fn_args = stub_##fn_name; +#include +#include +#include +#undef AL_FUNCTION + +namespace Audio +{ +bool OpenALDLLInit() { return false; } +void OpenALDLLShutdown() {} +} +#endif + +// Platform Stubs + +// clipboard +const char* Platform::getClipboard() { return ""; } +bool Platform::setClipboard(const char *text) { return false; } + +// fs +void Platform::openFolder(const char *path) { } +void Platform::openFile(const char *path) { } + +// window +bool Platform::displaySplashWindow() { return false; } + +// font +PlatformFont *createPlatformFont(const char *name, U32 size, U32 charset) { return NULL; } +bool x86UNIXFont::create(const char *name, U32 size, U32 charset) { return false; } + +// web +bool Platform::openWebBrowser(const char *) { return false; } + +// messagebox +void Platform::AlertOK(const char *, const char *) {} +bool Platform::AlertOKCancel(const char *, const char *) { return false; } +S32 Platform::messageBox(char const*, char const*, MBButtons, MBIcons) { return 0; } +bool Platform::AlertRetry(char const*, char const*) { return false ; } + +// file dialog +IMPLEMENT_CONOBJECT(FileDialog); +FileDialog::FileDialog() {} +FileDialog::~FileDialog() {} +bool FileDialog::Execute() { return false; } +void FileDialog::initPersistFields() { Parent::initPersistFields(); } + +class FileDialogOpaqueData {}; + +FileDialogData::FileDialogData() {} +FileDialogData::~FileDialogData() {} + +IMPLEMENT_CONOBJECT(OpenFileDialog); +OpenFileDialog::OpenFileDialog() {} +OpenFileDialog::~OpenFileDialog() {} +void OpenFileDialog::initPersistFields() { Parent::initPersistFields(); } + +// Input stubs +void Input::init() {} +void Input::destroy() {} +bool Input::enable() { return false; } +void Input::disable() {} +void Input::activate() {} +void Input::deactivate() {} +U16 Input::getAscii( U16 keyCode, KEY_STATE keyState ) { return 0; } +U16 Input::getKeyCode( U16 asciiCode ) { return 0; } +bool Input::isEnabled() { return false; } +bool Input::isActive() { return false; } +void Input::process() {} +InputManager* Input::getManager() { return NULL; } +InputEvent Input::smInputEvent; + +bool OpenGLInit() { return false; } diff --git a/platformX86UNIX/x86UNIXTime.cpp b/platformX86UNIX/x86UNIXTime.cpp new file mode 100644 index 0000000..7c659dc --- /dev/null +++ b/platformX86UNIX/x86UNIXTime.cpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platform/platformTimer.h" +#include "time.h" +#include +#include +#include +#include +#include + +static U32 sgCurrentTime = 0; + +U32 x86UNIXGetTickCount(); + +//-------------------------------------- +void Platform::getLocalTime(LocalTime <) +{ + struct tm *systime; + time_t long_time; + + time( &long_time ); // Get time as long integer. + systime = localtime( &long_time ); // Convert to local time. + + lt.sec = systime->tm_sec; + lt.min = systime->tm_min; + lt.hour = systime->tm_hour; + lt.month = systime->tm_mon; + lt.monthday = systime->tm_mday; + lt.weekday = systime->tm_wday; + lt.year = systime->tm_year; + lt.yearday = systime->tm_yday; + lt.isdst = systime->tm_isdst; +} + +String Platform::localTimeToString( const LocalTime < ) +{ + tm systime; + + systime.tm_sec = lt.sec; + systime.tm_min = lt.min; + systime.tm_hour = lt.hour; + systime.tm_mon = lt.month; + systime.tm_mday = lt.monthday; + systime.tm_wday = lt.weekday; + systime.tm_year = lt.year; + systime.tm_yday = lt.yearday; + systime.tm_isdst = lt.isdst; + + return asctime( &systime ); +} + +U32 Platform::getTime() +{ + time_t long_time; + time( &long_time ); + return long_time; +} + +U32 Platform::getRealMilliseconds() +{ +// struct rusage usageStats; +// getrusage(RUSAGE_SELF, &usageStats); +// return usageStats.ru_utime.tv_usec; + return x86UNIXGetTickCount(); +} + +U32 Platform::getVirtualMilliseconds() +{ + return sgCurrentTime; +} + +void Platform::advanceTime(U32 delta) +{ + sgCurrentTime += delta; +} + +void Platform::fileToLocalTime(const FileTime & ft, LocalTime * lt) +{ + if(!lt) + return; + + time_t long_time = ft; + + struct tm systime; + + /// Convert to local time, thread safe. + localtime_r( &long_time, &systime ); + + /// Fill the return struct + lt->sec = systime.tm_sec; + lt->min = systime.tm_min; + lt->hour = systime.tm_hour; + lt->month = systime.tm_mon; + lt->monthday = systime.tm_mday; + lt->weekday = systime.tm_wday; + lt->year = systime.tm_year; + lt->yearday = systime.tm_yday; + lt->isdst = systime.tm_isdst; +} + +PlatformTimer *PlatformTimer::create() +{ + return new DefaultPlatformTimer(); +} +//------------------------------------------------------------------------------ +//-------------------------------------- x86UNIX Implementation +// +// +static bool sg_initialized = false; +static U32 sg_secsOffset = 0; +//-------------------------------------- +U32 x86UNIXGetTickCount() +{ + // TODO: What happens when crossing a day boundary? + // + timeval t; + + if (sg_initialized == false) { + sg_initialized = true; + + gettimeofday(&t, NULL); + sg_secsOffset = t.tv_sec; + } + + gettimeofday(&t, NULL); + + U32 secs = t.tv_sec - sg_secsOffset; + U32 uSecs = t.tv_usec; + + // Make granularity 1 ms + return (secs * 1000) + (uSecs / 1000); +} + + +void Platform::sleep(U32 ms) +{ + // note: this will overflow if you want to sleep for more than 49 days. just so ye know. + usleep( ms * 1000 ); +} + diff --git a/platformX86UNIX/x86UNIXUtils.cpp b/platformX86UNIX/x86UNIXUtils.cpp new file mode 100644 index 0000000..d25b065 --- /dev/null +++ b/platformX86UNIX/x86UNIXUtils.cpp @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include +#include +#include + +// for UnixCommandExecutor +#include +#include +#include +#include + +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXUtils.h" + +UnixUtils *UUtils = NULL; +UnixUtils utils; + +UnixUtils::UnixUtils() +{ + UUtils = this; + + mUnameInfo = (struct utsname*)dRealMalloc(sizeof(struct utsname));; + if (uname(mUnameInfo) == -1) + { + // oh well + dRealFree(mUnameInfo); + mUnameInfo = NULL; + } +} + +UnixUtils::~UnixUtils() +{ + if (mUnameInfo != NULL) + { + dRealFree(mUnameInfo); + mUnameInfo = NULL; + } +} + +const char* UnixUtils::getOSName() +{ + if (mUnameInfo == NULL) + return ""; + + return mUnameInfo->sysname; +} + +bool UnixUtils::inBackground() +{ + int terminalGroupId = tcgetpgrp(fileno(stdin)); + int myPid = getpid(); + if (terminalGroupId != myPid) + return true; + else + return false; +} + +//----------------------------------------------------------------------------- +// UnixCommandExecutor +void UnixCommandExecutor::clearFields() +{ + mRet = -1; + mStdoutSave = -1; + mStderrSave = -1; + mPipeFiledes[0] = -1; + mPipeFiledes[1] = -1; + mChildPID = -1; + mBytesRead = 0; + mStdoutClosed = false; + mStderrClosed = false; + mChildExited = false; +} + +UnixCommandExecutor::UnixCommandExecutor() +{ + clearFields(); +} + +UnixCommandExecutor::~UnixCommandExecutor() +{ + cleanup(); +} + +int UnixCommandExecutor::exec(char* args[], + char* stdoutCapture, int stdoutCaptureSize) +{ + // check for shitty parameters + if (args == NULL || stdoutCapture == NULL || + stdoutCaptureSize <= 0) + return -666; + + // we're going to be redirecting stdout, so save it so that we can + // restore it + mRet = dup(1); + if (mRet == -1) + { + cleanup(); + return mRet; + } + mStdoutSave = mRet; + + // save stderr + mRet = dup(2); + if (mRet == -1) + { + cleanup(); + return mRet; + } + mStderrSave = mRet; + + // we'll need some pipe action for communicating with subprocess + mRet = pipe(mPipeFiledes); + if (mRet == -1) + { + cleanup(); + return mRet; + } + + // close stdout + mRet = close(1); + if (mRet == -1) + { + cleanup(); + return mRet; + } + mStdoutClosed = true; + + // stderr just gets closed and the output discarded + mRet = close(2); + if (mRet == -1) + { + cleanup(); + return mRet; + } + mStderrClosed = true; + + // dup the pipe output into stdout + mRet = dup2(mPipeFiledes[1], 1); + if (mRet == -1) + { + cleanup(); + return mRet; + } + + // fork + mRet = fork(); + if (mRet == -1) + { + cleanup(); + return mRet; + } + + if (mRet == 0) + { + // child process + + //close(mPipeFiledes[0]); + mRet = execvp(args[0], args); + // if exec returns, some bad shit went down, so just + // get outta here + exit(mRet); + } + + // parent process + mChildPID = mRet; + + // need to suck in data from pipe while child is running, + // otherwise child will eventually block on write and we'll + // wait forever + memset(stdoutCapture, 0, stdoutCaptureSize); + + // set input to be non blocking so that we don't block on read + mRet = fcntl(mPipeFiledes[0], F_SETFL, O_NONBLOCK); + if (mRet == -1) + { + cleanup(); + return mRet; + } + + // check to see if child has exited + mRet = waitpid(mChildPID, NULL, WNOHANG); + while (mRet == 0) + { + // not exited, read some data + mRet = read(mPipeFiledes[0], stdoutCapture + mBytesRead, + stdoutCaptureSize - mBytesRead); + // any error that isn't EAGAIN means we should exit + if (mRet == -1 && errno != EAGAIN) + { + cleanup(); + return mRet; + } + + // if the read was ok, increment bytes read + if (mRet != -1) + mBytesRead += mRet; + + // check again for child exit + mRet = waitpid(mChildPID, NULL, WNOHANG); + } + + // check for error from waitpid + if (mRet == -1 && errno != ECHILD) + { + cleanup(); + return mRet; + } + + // if we get here, the child exited + mChildExited = true; + + // read final bit of data + mRet = read(mPipeFiledes[0], stdoutCapture + mBytesRead, + stdoutCaptureSize - mBytesRead); + if (mRet == -1 && errno != EAGAIN) + { + cleanup(); + return mRet; + } + + if (mRet != -1) + mBytesRead += mRet; + + // done...cleanup + cleanup(); + + return 0; +} + +void UnixCommandExecutor::cleanup() +{ + // if child spawned and not exited, wait + if (mChildPID > 0 && !mChildExited) + waitpid(mChildPID, NULL, 0); + // close pipe descriptors + if (mPipeFiledes[0] != -1) + close(mPipeFiledes[0]); + if (mPipeFiledes[1] != -1) + close(mPipeFiledes[1]); + // if stdout is redirected, restore it + if (mStdoutClosed && mStdoutSave != -1) + dup2(mStdoutSave, 1); + // close stdout save descriptor + if (mStdoutSave != -1) + close(mStdoutSave); + // if stderr is redirected, restore it + if (mStderrClosed && mStderrSave != -1) + dup2(mStderrSave, 2); + // close stderr save descriptor + if (mStderrSave != -1) + close(mStderrSave); + + clearFields(); +} + +/* Usage: + UnixCommandExecutor exec; + char* args[] = { "ps", "-aux", NULL }; + char data[20000]; + int ret = exec.exec(args, data, sizeof(data)); + printf("%s", data); +*/ diff --git a/platformX86UNIX/x86UNIXUtils.h b/platformX86UNIX/x86UNIXUtils.h new file mode 100644 index 0000000..cd0e233 --- /dev/null +++ b/platformX86UNIX/x86UNIXUtils.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _X86UNIXUTILS_H_ +#define _X86UNIXUTILS_H_ + +struct utsname; + +class UnixUtils +{ +public: + UnixUtils(); + virtual ~UnixUtils(); + + /** + Returns true if we're running in the background, false otherwise. + There's no "standard" way to determine this in unix, but + modern job control unices should support the method described + here: + + http://www.faqs.org/faqs/unix-faq/faq/part3/ + + (question 3.7) + */ + bool inBackground(); + + /** + Returns the name of the OS, as reported by uname. + */ + const char* getOSName(); + +private: + struct utsname* mUnameInfo; +}; + +extern UnixUtils *UUtils; + +// utility class for running a unix command and capturing its output +class UnixCommandExecutor +{ + private: + int mRet; + int mStdoutSave; + int mStderrSave; + int mPipeFiledes[2]; + int mChildPID; + int mBytesRead; + bool mStdoutClosed; + bool mStderrClosed; + bool mChildExited; + + void clearFields(); + void cleanup(); + + public: + UnixCommandExecutor(); + ~UnixCommandExecutor(); + + // Runs the specified command. + // - args is a null terminated list of the command and its arguments, + // e.g: "ps", "-aux", NULL + // - stdoutCapture is the buffer where stdout data will be stored + // - stdoutCaptureSize is the size of the buffer + // None of these parameters may be null. stdoutCaptureSize must be > 0 + // + // returns -2 if the parameters are bad. returns -1 if some other + // error occurs, check errno for the exact error. + int exec(char* args[], char* stdoutCapture, int stdoutCaptureSize); +}; + +#endif diff --git a/platformX86UNIX/x86UNIXWindow.client.cpp b/platformX86UNIX/x86UNIXWindow.client.cpp new file mode 100644 index 0000000..f6683c6 --- /dev/null +++ b/platformX86UNIX/x86UNIXWindow.client.cpp @@ -0,0 +1,842 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + + + +#include "console/console.h" +#include "core/fileStream.h" +#include "game/resource.h" +#include "game/version.h" +#include "math/mRandom.h" +#include "platformX86UNIX/platformX86UNIX.h" +#include "platformX86UNIX/x86UNIXStdConsole.h" +#include "platform/event.h" +#include "platform/gameInterface.h" +#include "platform/platform.h" +#include "platform/platformAL.h" +#include "platform/platformInput.h" +#include "platform/platformVideo.h" +#include "platform/profiler.h" +#include "platformX86UNIX/platformGL.h" +#include "platformX86UNIX/x86UNIXOGLVideo.h" +#include "platformX86UNIX/x86UNIXState.h" + +#ifndef DEDICATED +#include "platformX86UNIX/x86UNIXMessageBox.h" +#include "platformX86UNIX/x86UNIXInputManager.h" +#endif + +#include +#include +#include +#include // fork, execvp, chdir +#include // nanosleep + +#ifndef DEDICATED +#include +#include + +#include +#include +#include +#endif + +x86UNIXPlatformState *x86UNIXState; + +bool DisplayPtrManager::sgDisplayLocked = false; +LockFunc_t DisplayPtrManager::sgLockFunc = NULL; +LockFunc_t DisplayPtrManager::sgUnlockFunc = NULL; + +static U32 lastTimeTick; +static MRandomLCG sgPlatRandom; + +#ifndef DEDICATED +extern void InstallRedBookDevices(); +extern void PollRedbookDevices(); +extern bool InitOpenGL(); +// This is called when some X client sends +// a selection event (e.g. SelectionRequest) +// to the window +extern void NotifySelectionEvent(XEvent& event); +#endif + +//------------------------------------------------------------------------------ +static S32 ParseCommandLine(S32 argc, const char **argv, + Vector& newCommandLine) +{ + x86UNIXState->setExePathName(argv[0]); + bool foundDedicated = false; + + for ( int i=0; i < argc; i++ ) + { + // look for platform specific args + if (dStrcmp(argv[i], "-version") == 0) + { + dPrintf("%s (built on %s)\n", getVersionString(), getCompileTimeString()); + dPrintf("gcc: %s\n", __VERSION__); + return 1; + } + if (dStrcmp(argv[i], "-cdaudio") == 0) + { + x86UNIXState->setCDAudioEnabled(true); + continue; + } + if (dStrcmp(argv[i], "-dedicated") == 0) + { + foundDedicated = true; + // no continue because dedicated is also handled by script + } + if (dStrcmp(argv[i], "-dsleep") == 0) + { + x86UNIXState->setDSleep(true); + continue; + } + if (dStrcmp(argv[i], "-nohomedir") == 0) + { + x86UNIXState->setUseRedirect(false); + continue; + } + if (dStrcmp(argv[i], "-chdir") == 0) + { + if ( ++i >= argc ) + { + dPrintf("Follow -chdir option with the desired working directory.\n"); + return 1; + } + if (chdir(argv[i]) == -1) + { + dPrintf("Unable to chdir to %s: %s\n", argv[i], strerror(errno)); + return 1; + } + continue; + } + + // copy the arg into newCommandLine + int argLen = dStrlen(argv[i]) + 1; + char* argBuf = new char[argLen]; // this memory is deleted in main() + dStrncpy(argBuf, argv[i], argLen); + newCommandLine.push_back(argBuf); + } + x86UNIXState->setDedicated(foundDedicated); +#if defined(DEDICATED) && !defined(TORQUE_ENGINE) + if (!foundDedicated) + { + dPrintf("This is a dedicated server build. You must supply the -dedicated command line parameter.\n"); + return 1; + } +#endif + return 0; +} + +static void DetectWindowingSystem() +{ +#ifndef DEDICATED + Display* dpy = XOpenDisplay(NULL); + if (dpy != NULL) + { + x86UNIXState->setXWindowsRunning(true); + XCloseDisplay(dpy); + } +#endif +} + +//------------------------------------------------------------------------------ +static void InitWindow(const Point2I &initialSize, const char *name) +{ + x86UNIXState->setWindowSize(initialSize); + x86UNIXState->setWindowName(name); +} + +#ifndef DEDICATED +//------------------------------------------------------------------------------ +static bool InitSDL() +{ + if (SDL_Init(SDL_INIT_VIDEO) != 0) + return false; + + atexit(SDL_Quit); + + SDL_SysWMinfo sysinfo; + SDL_VERSION(&sysinfo.version); + if (SDL_GetWMInfo(&sysinfo) == 0) + return false; + + x86UNIXState->setDisplayPointer(sysinfo.info.x11.display); + DisplayPtrManager::setDisplayLockFunction(sysinfo.info.x11.lock_func); + DisplayPtrManager::setDisplayUnlockFunction(sysinfo.info.x11.unlock_func); + + DisplayPtrManager xdisplay; + Display* display = xdisplay.getDisplayPointer(); + + x86UNIXState->setScreenNumber( + DefaultScreen( display ) ); + x86UNIXState->setScreenPointer( + DefaultScreenOfDisplay( display ) ); + + x86UNIXState->setDesktopSize( + (S32) DisplayWidth( + display, + x86UNIXState->getScreenNumber()), + (S32) DisplayHeight( + display, + x86UNIXState->getScreenNumber()) + ); + x86UNIXState->setDesktopBpp( + (S32) DefaultDepth( + display, + x86UNIXState->getScreenNumber())); + + // indicate that we want sys WM messages + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + + return true; +} + +//------------------------------------------------------------------------------ +static void ProcessSYSWMEvent(const SDL_Event& event) +{ + XEvent& xevent = event.syswm.msg->event.xevent; + //Con::printf("xevent : %d", xevent.type); + switch (xevent.type) + { + case SelectionRequest: + // somebody wants our clipboard + NotifySelectionEvent(xevent); + break; + } +} + +//------------------------------------------------------------------------------ +static void SetAppState() +{ + U8 state = SDL_GetAppState(); + + // if we're not active but we have appactive and inputfocus, set window + // active and reactivate input + if ((!x86UNIXState->windowActive() || !Input::isActive()) && + state & SDL_APPACTIVE && + state & SDL_APPINPUTFOCUS) + { + x86UNIXState->setWindowActive(true); + Input::reactivate(); + } + // if we are active, but we don't have appactive or input focus, + // deactivate input (if window not locked) and clear windowActive + else if (x86UNIXState->windowActive() && + !(state & SDL_APPACTIVE && state & SDL_APPINPUTFOCUS)) + { + if (x86UNIXState->windowLocked()) + Input::deactivate(); + x86UNIXState->setWindowActive(false); + } +} + +//------------------------------------------------------------------------------ +static S32 NumEventsPending() +{ + static const int MaxEvents = 255; + static SDL_Event events[MaxEvents]; + + SDL_PumpEvents(); + return SDL_PeepEvents(events, MaxEvents, SDL_PEEKEVENT, SDL_ALLEVENTS); +} + +//------------------------------------------------------------------------------ +static void PrintSDLEventQueue() +{ + static const int MaxEvents = 255; + static SDL_Event events[MaxEvents]; + + SDL_PumpEvents(); + S32 numEvents = SDL_PeepEvents( + events, MaxEvents, SDL_PEEKEVENT, SDL_ALLEVENTS); + if (numEvents <= 0) + { + dPrintf("SDL Event Queue is empty\n"); + return; + } + + dPrintf("SDL Event Queue:\n"); + for (int i = 0; i < numEvents; ++i) + { + const char *eventType; + switch (events[i].type) + { + case SDL_NOEVENT: eventType = "SDL_NOEVENT"; break; + case SDL_ACTIVEEVENT: eventType = "SDL_ACTIVEEVENT"; break; + case SDL_KEYDOWN: eventType = "SDL_KEYDOWN"; break; + case SDL_KEYUP: eventType = "SDL_KEYUP"; break; + case SDL_MOUSEMOTION: eventType = "SDL_MOUSEMOTION"; break; + case SDL_MOUSEBUTTONDOWN: eventType = "SDL_MOUSEBUTTONDOWN"; break; + case SDL_MOUSEBUTTONUP: eventType = "SDL_MOUSEBUTTONUP"; break; + case SDL_JOYAXISMOTION: eventType = "SDL_JOYAXISMOTION"; break; + case SDL_JOYBALLMOTION: eventType = "SDL_JOYBALLMOTION"; break; + case SDL_JOYHATMOTION: eventType = "SDL_JOYHATMOTION"; break; + case SDL_JOYBUTTONDOWN: eventType = "SDL_JOYBUTTONDOWN"; break; + case SDL_JOYBUTTONUP: eventType = "SDL_JOYBUTTONUP"; break; + case SDL_QUIT: eventType = "SDL_QUIT"; break; + case SDL_SYSWMEVENT: eventType = "SDL_SYSWMEVENT"; break; + case SDL_VIDEORESIZE: eventType = "SDL_VIDEORESIZE"; break; + case SDL_VIDEOEXPOSE: eventType = "SDL_VIDEOEXPOSE"; break; + /* Events SDL_USEREVENT through SDL_MAXEVENTS-1 are for your use */ + case SDL_USEREVENT: eventType = "SDL_USEREVENT"; break; + default: eventType = "UNKNOWN!"; break; + } + dPrintf("Event %d: %s\n", i, eventType); + } +} + +//------------------------------------------------------------------------------ +static bool ProcessMessages() +{ + static const int MaxEvents = 255; + static const U32 Mask = + SDL_QUITMASK | SDL_VIDEORESIZEMASK | SDL_VIDEOEXPOSEMASK | + SDL_ACTIVEEVENTMASK | SDL_SYSWMEVENTMASK | + SDL_EVENTMASK(SDL_USEREVENT); + static SDL_Event events[MaxEvents]; + + SDL_PumpEvents(); + S32 numEvents = SDL_PeepEvents(events, MaxEvents, SDL_GETEVENT, Mask); + if (numEvents == 0) + return true; + for (int i = 0; i < numEvents; ++i) + { + SDL_Event& event = events[i]; + switch (event.type) + { + case SDL_QUIT: + return false; + break; + case SDL_VIDEORESIZE: + case SDL_VIDEOEXPOSE: + Game->refreshWindow(); + break; + case SDL_USEREVENT: + if (event.user.code == TORQUE_SETVIDEOMODE) + { + SetAppState(); + // SDL will send a motion event to restore the mouse position + // on the new window. Ignore that if the window is locked. + if (x86UNIXState->windowLocked()) + { + SDL_Event tempEvent; + SDL_PeepEvents(&tempEvent, 1, SDL_GETEVENT, + SDL_MOUSEMOTIONMASK); + } + } + break; + case SDL_ACTIVEEVENT: + SetAppState(); + break; + case SDL_SYSWMEVENT: + ProcessSYSWMEvent(event); + break; + } + } + return true; +} + +//------------------------------------------------------------------------------ +// send a destroy window event to the window. assumes +// window is created. +void SendQuitEvent() +{ + SDL_Event quitevent; + quitevent.type = SDL_QUIT; + SDL_PushEvent(&quitevent); +} +#endif // DEDICATED + +//------------------------------------------------------------------------------ +static inline void Sleep(int secs, int nanoSecs) +{ + timespec sleeptime; + sleeptime.tv_sec = secs; + sleeptime.tv_nsec = nanoSecs; + nanosleep(&sleeptime, NULL); +} + +#ifndef DEDICATED +struct AlertWinState +{ + bool fullScreen; + bool cursorHidden; + bool inputGrabbed; +}; + +//------------------------------------------------------------------------------ +void DisplayErrorAlert(const char* errMsg, bool showSDLError) +{ + char fullErrMsg[2048]; + dStrncpy(fullErrMsg, errMsg, sizeof(fullErrMsg)); + + if (showSDLError) + { + char* sdlerror = SDL_GetError(); + if (sdlerror != NULL && dStrlen(sdlerror) > 0) + { + dStrcat(fullErrMsg, " (Error: "); + dStrcat(fullErrMsg, sdlerror); + dStrcat(fullErrMsg, ")"); + } + } + + Platform::AlertOK("Error", fullErrMsg); +} + + +//------------------------------------------------------------------------------ +static inline void AlertDisableVideo(AlertWinState& state) +{ + + state.fullScreen = Video::isFullScreen(); + state.cursorHidden = (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE); + state.inputGrabbed = (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GRAB_ON); + + if (state.fullScreen) + SDL_WM_ToggleFullScreen(SDL_GetVideoSurface()); + if (state.cursorHidden) + SDL_ShowCursor(SDL_ENABLE); + if (state.inputGrabbed) + SDL_WM_GrabInput(SDL_GRAB_OFF); +} + +//------------------------------------------------------------------------------ +static inline void AlertEnableVideo(AlertWinState& state) +{ + if (state.fullScreen) + SDL_WM_ToggleFullScreen(SDL_GetVideoSurface()); + if (state.cursorHidden) + SDL_ShowCursor(SDL_DISABLE); + if (state.inputGrabbed) + SDL_WM_GrabInput(SDL_GRAB_ON); +} +#endif // DEDICATED + +//------------------------------------------------------------------------------ +void Platform::AlertOK(const char *windowTitle, const char *message) +{ +#ifndef DEDICATED + if (x86UNIXState->isXWindowsRunning()) + { + AlertWinState state; + AlertDisableVideo(state); + + DisplayPtrManager xdisplay; + XMessageBox mBox(xdisplay.getDisplayPointer()); + mBox.alertOK(windowTitle, message); + + AlertEnableVideo(state); + } + else +#endif + { + if (Con::isActive() && StdConsole::isEnabled()) + Con::printf("Alert: %s %s", windowTitle, message); + else + dPrintf("Alert: %s %s\n", windowTitle, message); + } +} + +//------------------------------------------------------------------------------ +bool Platform::AlertOKCancel(const char *windowTitle, const char *message) +{ +#ifndef DEDICATED + if (x86UNIXState->isXWindowsRunning()) + { + AlertWinState state; + AlertDisableVideo(state); + + DisplayPtrManager xdisplay; + XMessageBox mBox(xdisplay.getDisplayPointer()); + bool val = + mBox.alertOKCancel(windowTitle, message) == XMessageBox::OK; + + AlertEnableVideo(state); + return val; + } + else +#endif + { + if (Con::isActive() && StdConsole::isEnabled()) + Con::printf("Alert: %s %s", windowTitle, message); + else + dPrintf("Alert: %s %s\n", windowTitle, message); + return false; + } +} + +//------------------------------------------------------------------------------ +bool Platform::AlertRetry(const char *windowTitle, const char *message) +{ +#ifndef DEDICATED + if (x86UNIXState->isXWindowsRunning()) + { + AlertWinState state; + AlertDisableVideo(state); + + DisplayPtrManager xdisplay; + XMessageBox mBox(xdisplay.getDisplayPointer()); + bool val = + mBox.alertRetryCancel(windowTitle, message) == XMessageBox::Retry; + + AlertEnableVideo(state); + return val; + } + else +#endif + { + if (Con::isActive() && StdConsole::isEnabled()) + Con::printf("Alert: %s %s", windowTitle, message); + else + dPrintf("Alert: %s %s\n", windowTitle, message); + return false; + } +} + +//------------------------------------------------------------------------------ +bool Platform::excludeOtherInstances(const char *mutexName) +{ + return AcquireProcessMutex(mutexName); +} + + +//------------------------------------------------------------------------------ +void Platform::enableKeyboardTranslation(void) +{ +#ifndef DEDICATED + // JMQ: not sure if this is needed for i18n keyboards + //SDL_EnableUNICODE( 1 ); +// SDL_EnableKeyRepeat( +// SDL_DEFAULT_REPEAT_DELAY, +// SDL_DEFAULT_REPEAT_INTERVAL); +#endif +} + +//------------------------------------------------------------------------------ +void Platform::disableKeyboardTranslation(void) +{ +#ifndef DEDICATED + //SDL_EnableUNICODE( 0 ); + // SDL_EnableKeyRepeat(0, 0); +#endif +} + +//------------------------------------------------------------------------------ +void Platform::setWindowLocked(bool locked) +{ +#ifndef DEDICATED + x86UNIXState->setWindowLocked(locked); + + UInputManager* uInputManager = + dynamic_cast( Input::getManager() ); + + if ( uInputManager && uInputManager->isEnabled() && + Input::isActive() ) + uInputManager->setWindowLocked(locked); +#endif +} + +//------------------------------------------------------------------------------ +void Platform::minimizeWindow() +{ +#ifndef DEDICATED + if (x86UNIXState->windowCreated()) + SDL_WM_IconifyWindow(); +#endif +} + +//------------------------------------------------------------------------------ +void Platform::process() +{ + PROFILE_START(XUX_PlatformProcess); + stdConsole->process(); + + if (x86UNIXState->windowCreated()) + { +#ifndef DEDICATED + // process window events + PROFILE_START(XUX_ProcessMessages); + bool quit = !ProcessMessages(); + PROFILE_END(); + if(quit) + { + // generate a quit event + Event quitEvent; + quitEvent.type = QuitEventType; + Game->postEvent(quitEvent); + } + + // process input events + PROFILE_START(XUX_InputProcess); + Input::process(); + PROFILE_END(); + + // poll redbook state + PROFILE_START(XUX_PollRedbookDevices); + PollRedbookDevices(); + PROFILE_END(); + + // if we're not the foreground window, sleep for 1 ms + if (!x86UNIXState->windowActive()) + Sleep(0, getBackgroundSleepTime() * 1000000); +#endif + } + else + { + // no window + // if we're not in journal mode, sleep for 1 ms + // JMQ: since linux's minimum sleep latency seems to be 20ms, this can + // increase player pings by 10-20ms in the dedicated server. So + // you have to use -dsleep to enable it. the server sleeps anyway when + // there are no players connected. + // JMQ: recent kernels (such as RH 8.0 2.4.18) reduce the latency + // to 2-4 ms on average. + if (!Game->isJournalReading() && (x86UNIXState->getDSleep() || + Con::getIntVariable("Server::PlayerCount") - + Con::getIntVariable("Server::BotCount") <= 0)) + { + PROFILE_START(XUX_Sleep); + Sleep(0, getBackgroundSleepTime() * 1000000); + PROFILE_END(); + } + } + +#ifndef DEDICATED +#if 0 +// JMQ: disabled this because it may fire mistakenly in some configurations. +// sdl's default event handling scheme should be enough. + // crude check to make sure that we're not loading up events. the sdl + // event queue should never have more than (say) 25 events in it at this + // point + const int MaxEvents = 25; + if (NumEventsPending() > MaxEvents) + { + PrintSDLEventQueue(); + AssertFatal(false, "The SDL event queue has too many events!"); + } +#endif +#endif + PROFILE_END(); +} + +// extern U32 calculateCRC(void * buffer, S32 len, U32 crcVal ); + +// #if defined(DEBUG) || defined(INTERNAL_RELEASE) +// static U32 stubCRC = 0; +// #else +// static U32 stubCRC = 0xEA63F56C; +// #endif + +//------------------------------------------------------------------------------ +const Point2I &Platform::getWindowSize() +{ + return x86UNIXState->getWindowSize(); +} + + +//------------------------------------------------------------------------------ +void Platform::setWindowSize( U32 newWidth, U32 newHeight ) +{ + x86UNIXState->setWindowSize( (S32) newWidth, (S32) newHeight ); +} + + +//------------------------------------------------------------------------------ +void Platform::initWindow(const Point2I &initialSize, const char *name) +{ +#ifndef DEDICATED + // initialize window + InitWindow(initialSize, name); + if (!InitOpenGL()) + ImmediateShutdown(1); +#endif +} + + +//------------------------------------------------------------------------------ +// Web browser function: +//------------------------------------------------------------------------------ +bool Platform::openWebBrowser( const char* webAddress ) +{ + if (!webAddress || dStrlen(webAddress)==0) + return false; + + // look for a browser preference variable + // JMQTODO: be nice to implement some UI to customize this + const char* webBrowser = Con::getVariable("Pref::Unix::WebBrowser"); + if (dStrlen(webBrowser) == 0) + webBrowser = NULL; + + pid_t pid = fork(); + if (pid == -1) + { + Con::printf("WARNING: Platform::openWebBrowser failed to fork"); + return false; + } + else if (pid != 0) + { + // parent + if (Video::isFullScreen()) + Video::toggleFullScreen(); + + return true; + } + else if (pid == 0) + { + // child + // try to exec konqueror, then netscape + char* argv[3]; + argv[0] = ""; + argv[1] = const_cast(webAddress); + argv[2] = 0; + + int ok = -1; + + // if execvp returns, it means it couldn't execute the program + if (webBrowser != NULL) + ok = execvp(webBrowser, argv); + + ok = execvp("konqueror", argv); + ok = execvp("mozilla", argv); + ok = execvp("netscape", argv); + // use dPrintf instead of Con here since we're now in another process, + dPrintf("WARNING: Platform::openWebBrowser: couldn't launch a web browser\n"); + _exit(-1); + return false; + } + else + { + Con::printf("WARNING: Platform::openWebBrowser: forking problem"); + return false; + } +} + +//------------------------------------------------------------------------------ +// Login password routines: +//------------------------------------------------------------------------------ +const char* Platform::getLoginPassword() +{ + Con::printf("WARNING: Platform::getLoginPassword() is unimplemented"); + return ""; +} + +//------------------------------------------------------------------------------ +bool Platform::setLoginPassword( const char* password ) +{ + Con::printf("WARNING: Platform::setLoginPassword is unimplemented"); + return false; +} + +//------------------------------------------------------------------------------- +void TimeManager::process() +{ + U32 curTime = Platform::getRealMilliseconds(); + TimeEvent event; + event.elapsedTime = curTime - lastTimeTick; + if(event.elapsedTime > sgTimeManagerProcessInterval) + { + lastTimeTick = curTime; + Game->postEvent(event); + } +} + +//------------------------------------------------------------------------------ +ConsoleFunction( getDesktopResolution, const char*, 1, 1, + "getDesktopResolution()" ) +{ + if (!x86UNIXState->windowCreated()) + return "0 0 0"; + + char buffer[256]; + char* returnString = Con::getReturnBuffer( dStrlen( buffer ) + 1 ); + + dSprintf( buffer, sizeof( buffer ), "%d %d %d", + x86UNIXState->getDesktopSize().x, + x86UNIXState->getDesktopSize().y, + x86UNIXState->getDesktopBpp() ); + dStrcpy( returnString, buffer ); + return( returnString ); +} + +//------------------------------------------------------------------------------ +// Silly Korean registry key checker: +//------------------------------------------------------------------------------ +ConsoleFunction( isKoreanBuild, bool, 1, 1, "isKoreanBuild()" ) +{ + Con::printf("WARNING: isKoreanBuild() is unimplemented"); + return false; +} + +//------------------------------------------------------------------------------ +int main(S32 argc, const char **argv) +{ + // init platform state + x86UNIXState = new x86UNIXPlatformState; + + // parse the command line for unix-specific params + Vector newCommandLine; + S32 returnVal = ParseCommandLine(argc, argv, newCommandLine); + if (returnVal != 0) + return returnVal; + + // init lastTimeTick for TimeManager::process() + lastTimeTick = Platform::getRealMilliseconds(); + + // init process control stuff + ProcessControlInit(); + + // check to see if X is running + DetectWindowingSystem(); + + // run the game + returnVal = Game->main(newCommandLine.size(), + const_cast(newCommandLine.address())); + + // dispose of command line + for(U32 i = 0; i < newCommandLine.size(); i++) + delete [] newCommandLine[i]; + + // dispose of state + delete x86UNIXState; + + return returnVal; +} + +void Platform::setWindowTitle( const char* title ) +{ +#ifndef DEDICATED + x86UNIXState->setWindowName(title); + SDL_WM_SetCaption(x86UNIXState->getWindowName(), NULL); +#endif +} + +Resolution Video::getDesktopResolution() +{ + Resolution Result; + Result.h = x86UNIXState->getDesktopSize().x; + Result.w = x86UNIXState->getDesktopSize().y; + Result.bpp = x86UNIXState->getDesktopBpp(); + + return Result; +} + + +//----------------------------------------------------------------------------- +void Platform::restartInstance() +{ + + if (Game->isRunning() ) + { + //Con::errorf( "Error restarting Instance. Game is Still running!"); + return; + } + + char cmd[2048]; + sprintf(cmd, "%s &", x86UNIXState->getExePathName()); + system(cmd); + exit(0); +} diff --git a/postFx/postEffect.cpp b/postFx/postEffect.cpp new file mode 100644 index 0000000..942af1f --- /dev/null +++ b/postFx/postEffect.cpp @@ -0,0 +1,1334 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "postFx/postEffect.h" + +#include "postFx/postEffectManager.h" +#include "console/consoleTypes.h" +#include "math/util/frustum.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/sim/gfxStateBlockData.h" +#include "sceneGraph/sceneState.h" +#include "shaderGen/shaderGenVars.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "lighting/lightInfo.h" +#include "core/strings/stringUnit.h" +#include "materials/materialManager.h" +#include "materials/shaderData.h" +#include "math/mathUtils.h" +#include "postFx/postEffectVis.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/util/screenspace.h" +#include "core/stream/fileStream.h" + +// For named buffers +#include "renderInstance/renderPrePassMgr.h" +#include "lighting/advanced/advancedLightBinManager.h" + +static const EnumTable::Enums sPFXRenderTimeEnums[] = +{ + { PFXBeforeBin, "PFXBeforeBin" }, + { PFXAfterBin, "PFXAfterBin" }, + { PFXAfterDiffuse, "PFXAfterDiffuse" }, + { PFXEndOfFrame, "PFXEndOfFrame" }, + { PFXTexGenOnDemand, "PFXTexGenOnDemand" } +}; + +static const EnumTable sPFXRenderTimeTable( + sizeof( sPFXRenderTimeEnums ) / sizeof( EnumTable::Enums ), + sPFXRenderTimeEnums ); + +static const EnumTable::Enums sPFXTargetClearEnums[] = +{ + { PFXTargetClear_None, "PFXTargetClear_None" }, + { PFXTargetClear_OnCreate, "PFXTargetClear_OnCreate" }, + { PFXTargetClear_OnDraw, "PFXTargetClear_OnDraw" }, +}; + +static const EnumTable sPFXTargetClearTable( + sizeof( sPFXTargetClearEnums ) / sizeof( EnumTable::Enums ), + sPFXTargetClearEnums ); + +static EnumTable::Enums gRequirementEnums[] = +{ + { 0, "None" }, + { PostEffect::RequiresDepth, "PrePassDepth" }, + { PostEffect::RequiresNormals, "PrePassNormal" }, + { PostEffect::RequiresNormals | + PostEffect::RequiresDepth, "PrePassDepthAndNormal" }, + { PostEffect::RequiresLightInfo, "LightInfo" }, +}; +EnumTable gRequirementEnumTable(5, gRequirementEnums); + +GFXImplementVertexFormat( PFXVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float2, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float3, 1 ); +}; + +GFX_ImplementTextureProfile( PostFxTargetProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::RenderTarget | + GFXTextureProfile::Pooled, + GFXTextureProfile::None ); + +IMPLEMENT_CONOBJECT(PostEffect); + + +GFX_ImplementTextureProfile( PostFxTextureProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::Static | GFXTextureProfile::PreserveSize | GFXTextureProfile::NoMipmap, + GFXTextureProfile::None ); + + +void PostEffect::EffectConst::set( const String &newVal ) +{ + if ( mStringVal == newVal ) + return; + + mStringVal = newVal; + mDirty = true; +} + +void PostEffect::EffectConst::setToBuffer( GFXShaderConstBufferRef buff ) +{ + // Nothing to do if the value hasn't changed. + if ( !mDirty ) + return; + mDirty = false; + + // If we don't have a handle... get it now. + if ( !mHandle ) + mHandle = buff->getShader()->getShaderConstHandle( mName ); + + // If the handle isn't valid then we're done. + if ( !mHandle->isValid() ) + return; + + const GFXShaderConstType type = mHandle->getType(); + + // For now, we're only going + // to support float4 arrays. + // Expand to other types as necessary. + U32 arraySize = mHandle->getArraySize(); + + const char *strVal = mStringVal.c_str(); + + if ( type == GFXSCT_Float ) + { + F32 val; + Con::setData( TypeF32, &val, 0, 1, &strVal ); + buff->set( mHandle, val ); + } + else if ( type == GFXSCT_Float2 ) + { + Point2F val; + Con::setData( TypePoint2F, &val, 0, 1, &strVal ); + buff->set( mHandle, val ); + } + else if ( type == GFXSCT_Float3 ) + { + Point3F val; + Con::setData( TypePoint3F, &val, 0, 1, &strVal ); + buff->set( mHandle, val ); + } + else + { + Point4F val; + + if ( arraySize > 1 ) + { + // Do array setup! + //U32 unitCount = StringUnit::getUnitCount( strVal, "\t" ); + //AssertFatal( unitCount == arraySize, "" ); + + String tmpString; + Vector valArray; + + for ( U32 i = 0; i < arraySize; i++ ) + { + tmpString = StringUnit::getUnit( strVal, i, "\t" ); + valArray.increment(); + const char *tmpCStr = tmpString.c_str(); + + Con::setData( TypePoint4F, &valArray.last(), 0, 1, &tmpCStr ); + } + + AlignedArray rectData( valArray.size(), sizeof( Point4F ), (U8*)valArray.address(), false ); + buff->set( mHandle, rectData ); + } + else + { + // Do regular setup. + Con::setData( TypePoint4F, &val, 0, 1, &strVal ); + buff->set( mHandle, val ); + } + } +} + + +//------------------------------------------------------------------------- +// PostEffect +//------------------------------------------------------------------------- + +PostEffect::PostEffect() + : mRenderTime( PFXAfterDiffuse ), + mRenderPriority( 1.0 ), + mEnabled( false ), + mSkip( false ), + mIsNamedTarget( false ), + mUpdateShader( true ), + mStateBlockData( NULL ), + mAllowReflectPass( false ), + mTargetClear( PFXTargetClear_None ), + mTargetScale( Point2F::One ), + mTargetSize( Point2I::Zero ), + mTargetFormat( GFXFormatR8G8B8A8 ), + mTargetClearColor( ColorF::BLACK ), + mOneFrameOnly( false ), + mOnThisFrame( true ), + mShaderReloadKey( 0 ), + mPostEffectRequirements( U32_MAX ), + mRequirementsMet( true ), + mRTSizeSC( NULL ), + mOneOverRTSizeSC( NULL ), + mViewportOffsetSC( NULL ), + mFogDataSC( NULL ), + mFogColorSC( NULL ), + mEyePosSC( NULL ), + mMatWorldToScreenSC( NULL ), + mMatScreenToWorldSC( NULL ), + mMatPrevScreenToWorldSC( NULL ), + mNearFarSC( NULL ), + mInvNearFarSC( NULL ), + mWorldToScreenScaleSC( NULL ), + mWaterColorSC( NULL ), + mWaterFogDataSC( NULL ), + mAmbientColorSC( NULL ), + mWaterFogPlaneSC( NULL ), + mScreenSunPosSC( NULL ), + mLightDirectionSC( NULL ), + mCameraForwardSC( NULL ), + mAccumTimeSC( NULL ), + mDeltaTimeSC( NULL ), + mInvCameraMatSC( NULL ) +{ + dMemset( mActiveTextures, 0, sizeof( GFXTextureObject* ) * NumTextures ); + dMemset( mActiveNamedTarget, 0, sizeof( MatTextureTarget* ) * NumTextures ); + dMemset( mTexSizeSC, 0, sizeof( GFXShaderConstHandle* ) * NumTextures ); + dMemset( mTexSizeSC, 0, sizeof( GFXShaderConstHandle* ) * NumTextures ); +} + +PostEffect::~PostEffect() +{ + EffectConstTable::Iterator iter = mEffectConsts.begin(); + for ( ; iter != mEffectConsts.end(); iter++ ) + delete iter->value; +} + +void PostEffect::initPersistFields() +{ + addField( "shader", TypeRealString, Offset( mShaderName, PostEffect ) ); + + addField( "stateBlock", TypeSimObjectPtr, Offset( mStateBlockData, PostEffect ) ); + + addField( "target", TypeRealString, Offset( mTargetName, PostEffect ) ); + + addField( "targetScale", TypePoint2F, Offset( mTargetScale, PostEffect ), + "If targetSize is zero this is used to set a relative size from the current target." ); + + addField( "targetSize", TypePoint2I, Offset( mTargetSize, PostEffect ), + "If non-zero this is used as the absolute target size." ); + + addField( "targetFormat", TypeEnum, Offset( mTargetFormat, PostEffect ), 1, &gTextureFormatEnumTable ); + addField( "targetClearColor", TypeColorF, Offset( mTargetClearColor, PostEffect ) ); + addField( "targetClear", TypeEnum, Offset( mTargetClear, PostEffect ), 1, &sPFXTargetClearTable ); + + addField( "texture", TypeImageFilename, Offset( mTexFilename, PostEffect ), NumTextures ); + + addField( "renderTime", TypeEnum, Offset( mRenderTime, PostEffect ), 1, &sPFXRenderTimeTable ); + addField( "renderBin", TypeRealString, Offset( mRenderBin, PostEffect ) ); + addField( "renderPriority", TypeF32, Offset( mRenderPriority, PostEffect ), + "PostEffects are processed in DESCENDING order of renderPriority if more than one has the same renderBin/Time." ); + + addField( "allowReflectPass", TypeBool, Offset( mAllowReflectPass, PostEffect ) ); + + addProtectedField( "isEnabled", TypeBool, Offset( mEnabled, PostEffect), + &PostEffect::_setIsEnabled, &defaultProtectedGetFn, + "Toggles the effect on and off." ); + + addField( "onThisFrame", TypeBool, Offset( mOnThisFrame, PostEffect ), "Allows you to turn on a posteffect for only a single frame." ); + addField( "oneFrameOnly", TypeBool, Offset( mOneFrameOnly, PostEffect ), "Allows you to turn on a posteffect for only a single frame." ); + + addField( "skip", TypeBool, Offset( mSkip, PostEffect ), "Skip processing of this PostEffect and its children even if its parent is enabled. Parent and sibling PostEffects in the chain are still processed." ); + + addField( "requirements", TypeEnum, Offset( mPostEffectRequirements, PostEffect ), 1, &gRequirementEnumTable ); + + Parent::initPersistFields(); +} + +bool PostEffect::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + LightManager::smActivateSignal.notify( this, &PostEffect::_onLMActivate ); + mUpdateShader = true; + + // Grab the script path. + Torque::Path scriptPath( Con::getVariable( "$Con::File" ) ); + scriptPath.setFileName( String::EmptyString ); + scriptPath.setExtension( String::EmptyString ); + + // Find additional textures + for( int i = 0; i < NumTextures; i++ ) + { + String texFilename = mTexFilename[i]; + + // Skip empty stages or ones with variable or target names. + if ( texFilename.isEmpty() || + texFilename[0] == '$' || + texFilename[0] == '#' ) + continue; + + // If '/', then path is specified, open normally + if ( texFilename[0] != '/' ) + texFilename = scriptPath.getFullPath() + '/' + texFilename; + + // Try to load the texture. + mTextures[i].set( texFilename, &PostFxTextureProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); + } + + // Is the target a named target? + if ( mTargetName.isNotEmpty() && mTargetName[0] == '#' ) + { + mIsNamedTarget = true; + MatTextureTarget::registerTarget( mTargetName.substr( 1 ), this ); + GFXTextureManager::addEventDelegate( this, &PostEffect::_onTextureEvent ); + } + else + mIsNamedTarget = false; + + // Call onAdd in script + Con::executef(this, "onAdd", Con::getIntArg(getId())); + + // Should we start enabled? + if ( mEnabled ) + { + mEnabled = false; + enable(); + } + + return true; +} + +void PostEffect::onRemove() +{ + Parent::onRemove(); + + PFXMGR->_removeEffect( this ); + + LightManager::smActivateSignal.remove( this, &PostEffect::_onLMActivate ); + + mShader = NULL; + _cleanTargets(); + + if ( mIsNamedTarget ) + { + GFXTextureManager::removeEventDelegate( this, &PostEffect::_onTextureEvent ); + MatTextureTarget::unregisterTarget( mTargetName.substr( 1 ), this ); + } +} + +void PostEffect::_updateScreenGeometry( const Frustum &frustum, + GFXVertexBufferHandle *outVB ) +{ + outVB->set( GFX, 4, GFXBufferTypeVolatile ); + + const Point3F *frustumPoints = frustum.getPoints(); + + PFXVertex *vert = outVB->lock(); + + vert->point.set( -1.0, -1.0, 0.0 ); + vert->texCoord.set( 0.0f, 1.0f ); + vert->wsEyeRay = frustumPoints[Frustum::FarBottomLeft] - frustumPoints[ Frustum::CameraPosition ]; + vert++; + + vert->point.set( -1.0, 1.0, 0.0 ); + vert->texCoord.set( 0.0f, 0.0f ); + vert->wsEyeRay = frustumPoints[Frustum::FarTopLeft] - frustumPoints[ Frustum::CameraPosition ]; + vert++; + + vert->point.set( 1.0, 1.0, 0.0 ); + vert->texCoord.set( 1.0f, 0.0f ); + vert->wsEyeRay = frustumPoints[Frustum::FarTopRight] - frustumPoints[ Frustum::CameraPosition ]; + vert++; + + vert->point.set( 1.0, -1.0, 0.0 ); + vert->texCoord.set( 1.0f, 1.0f ); + vert->wsEyeRay = frustumPoints[Frustum::FarBottomRight] - frustumPoints[ Frustum::CameraPosition ]; + vert++; + + outVB->unlock(); +} + +void PostEffect::_setupStateBlock( const SceneState *state ) +{ + if ( mStateBlock.isNull() ) + { + GFXStateBlockDesc desc; + if ( mStateBlockData ) + desc = mStateBlockData->getState(); + + mStateBlock = GFX->createStateBlock( desc ); + } + + GFX->setStateBlock( mStateBlock ); +} + +void PostEffect::_setupConstants( const SceneState *state ) +{ + // Alloc the const buffer. + if ( mShaderConsts.isNull() ) + { + mShaderConsts = mShader->allocConstBuffer(); + + mRTSizeSC = mShader->getShaderConstHandle( "$targetSize" ); + mOneOverRTSizeSC = mShader->getShaderConstHandle( "$oneOverTargetSize" ); + + mTexSizeSC[0] = mShader->getShaderConstHandle( "$texSize0" ); + mTexSizeSC[1] = mShader->getShaderConstHandle( "$texSize1" ); + mTexSizeSC[2] = mShader->getShaderConstHandle( "$texSize2" ); + mTexSizeSC[3] = mShader->getShaderConstHandle( "$texSize3" ); + mRenderTargetParamsSC[0] = mShader->getShaderConstHandle( "$rtParams0" ); + mRenderTargetParamsSC[1] = mShader->getShaderConstHandle( "$rtParams1" ); + mRenderTargetParamsSC[2] = mShader->getShaderConstHandle( "$rtParams2" ); + mRenderTargetParamsSC[3] = mShader->getShaderConstHandle( "$rtParams3" ); + + //mViewportSC = shader->getShaderConstHandle( "$viewport" ); + + mFogDataSC = mShader->getShaderConstHandle( ShaderGenVars::fogData ); + mFogColorSC = mShader->getShaderConstHandle( ShaderGenVars::fogColor ); + + mEyePosSC = mShader->getShaderConstHandle( ShaderGenVars::eyePosWorld ); + + mNearFarSC = mShader->getShaderConstHandle( "$nearFar" ); + mInvNearFarSC = mShader->getShaderConstHandle( "$invNearFar" ); + mWorldToScreenScaleSC = mShader->getShaderConstHandle( "$worldToScreenScale" ); + + mMatWorldToScreenSC = mShader->getShaderConstHandle( "$matWorldToScreen" ); + mMatScreenToWorldSC = mShader->getShaderConstHandle( "$matScreenToWorld" ); + mMatPrevScreenToWorldSC = mShader->getShaderConstHandle( "$matPrevScreenToWorld" ); + + mWaterColorSC = mShader->getShaderConstHandle( "$waterColor" ); + mAmbientColorSC = mShader->getShaderConstHandle( "$ambientColor" ); + mWaterFogDataSC = mShader->getShaderConstHandle( "$waterFogData" ); + mWaterFogPlaneSC = mShader->getShaderConstHandle( "$waterFogPlane" ); + mScreenSunPosSC = mShader->getShaderConstHandle( "$screenSunPos" ); + mLightDirectionSC = mShader->getShaderConstHandle( "$lightDirection" ); + mCameraForwardSC = mShader->getShaderConstHandle( "$camForward" ); + + mAccumTimeSC = mShader->getShaderConstHandle( "$accumTime" ); + mDeltaTimeSC = mShader->getShaderConstHandle( "$deltaTime" ); + + mInvCameraMatSC = mShader->getShaderConstHandle( "$invCameraMat" ); + } + + // Set up shader constants for source image size + if ( mRTSizeSC->isValid() ) + { + const Point2I &resolution = GFX->getActiveRenderTarget()->getSize(); + Point2F pixelShaderConstantData; + + pixelShaderConstantData.x = resolution.x; + pixelShaderConstantData.y = resolution.y; + + mShaderConsts->set( mRTSizeSC, pixelShaderConstantData ); + } + + if ( mOneOverRTSizeSC->isValid() ) + { + const Point2I &resolution = GFX->getActiveRenderTarget()->getSize(); + Point2F oneOverTargetSize( 1.0f / (F32)resolution.x, 1.0f / (F32)resolution.y ); + + mShaderConsts->set( mOneOverRTSizeSC, oneOverTargetSize ); + } + + // Set up additional textures + Point2F texSizeConst; + for( U32 i = 0; i < NumTextures; i++ ) + { + if( !mActiveTextures[i] ) + continue; + + if ( mTexSizeSC[i]->isValid() ) + { + texSizeConst.x = (F32)mActiveTextures[i]->getWidth(); + texSizeConst.y = (F32)mActiveTextures[i]->getHeight(); + mShaderConsts->set( mTexSizeSC[i], texSizeConst ); + } + } + + for ( U32 i = 0; i < NumTextures; i++ ) + { + if( !mActiveTextures[i] ) + continue; + + if ( mRenderTargetParamsSC[i]->isValid() ) + { + const Point3I &targetSz = mActiveTextures[i]->getSize(); + RectI targetVp = mActiveTextureViewport[i]; + + /* + if ( mActiveNamedTarget[i] ) + targetVp = mActiveNamedTarget[i]->getTargetViewport(); + else + { + targetVp = GFX->getViewport(); + //targetVp.set( 0, 0, targetSz.x, targetSz.y ); + } + */ + + Point4F rtParams; + + ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams); + + mShaderConsts->set( mRenderTargetParamsSC[i], rtParams ); + } + } + + // Pull damage flash information from the game connection + /* + GameConnection *conToServer = GameConnection::getConnectionToServer(); + // Set up the damage/whiteout/blackout in a pixel shader constant if they are + // available + Point4F gameConnEffects( 0.0f, 0.0f, 0.0f, 0.0f ); + if( conToServer != NULL ) + { + gameConnEffects.x = conToServer->getDamageFlash(); + gameConnEffects.y = conToServer->getWhiteOut(); + gameConnEffects.z = conToServer->getBlackOut(); + } + + static const String sGameConEfx( "$gameConEfx" ); + mShaderConsts->set( sGameConEfx, gameConnEffects ); + */ + + // Set the fog data. + if ( mFogDataSC->isValid() ) + { + const FogData &data = gClientSceneGraph->getFogData(); + + Point3F params; + params.x = data.density; + params.y = data.densityOffset; + + if ( !mIsZero( data.atmosphereHeight ) ) + params.z = 1.0f / data.atmosphereHeight; + else + params.z = 0.0f; + + mShaderConsts->set( mFogDataSC, params ); + } + + if ( mFogColorSC->isValid() ) + mShaderConsts->set( mFogColorSC, gClientSceneGraph->getFogData().color ); + + if ( mEyePosSC->isValid() && state ) + mShaderConsts->set( mEyePosSC, /*gClientSceneGraph->mNormCamPos*/ state->getDiffuseCameraPosition() ); + + if ( mNearFarSC->isValid() && state ) + mShaderConsts->set( mNearFarSC, Point2F( state->getNearPlane(), state->getFarPlane() ) ); + + if ( mInvNearFarSC->isValid() && state ) + mShaderConsts->set( mInvNearFarSC, Point2F( 1.0f / state->getNearPlane(), 1.0f / state->getFarPlane() ) ); + + if ( mWorldToScreenScaleSC->isValid() && state ) + mShaderConsts->set( mWorldToScreenScaleSC, state->getWorldToScreenScale() ); + + if ( mMatWorldToScreenSC->isValid() || mMatScreenToWorldSC->isValid() ) + { + const PFXFrameState &thisFrame = PFXMGR->getFrameState(); + + // Screen space->world space + MatrixF tempMat = thisFrame.cameraToScreen; + tempMat.mul( thisFrame.worldToCamera ); + tempMat.fullInverse(); + tempMat.transpose(); + + // Support using these matrices as float3x3 or float4x4... + mShaderConsts->set( mMatWorldToScreenSC, tempMat, mMatWorldToScreenSC->getType() ); + + // World space->screen space + tempMat = thisFrame.cameraToScreen; + tempMat.mul( thisFrame.worldToCamera ); + tempMat.transpose(); + + // Support using these matrices as float3x3 or float4x4... + mShaderConsts->set( mMatScreenToWorldSC, tempMat, mMatScreenToWorldSC->getType() ); + } + + if ( mMatPrevScreenToWorldSC->isValid() ) + { + const PFXFrameState &lastFrame = PFXMGR->getLastFrameState(); + + // Previous frame world space->screen space + MatrixF tempMat = lastFrame.cameraToScreen; + tempMat.mul( lastFrame.worldToCamera ); + tempMat.transpose(); + mShaderConsts->set( mMatPrevScreenToWorldSC, tempMat ); + } + + if ( mWaterColorSC->isValid() ) + { + ColorF color( gClientSceneGraph->getWaterFogData().color ); + mShaderConsts->set( mWaterColorSC, color ); + } + + if ( mWaterFogDataSC->isValid() ) + { + const WaterFogData &data = gClientSceneGraph->getWaterFogData(); + Point4F params( data.density, data.densityOffset, data.wetDepth, data.wetDarkening ); + mShaderConsts->set( mWaterFogDataSC, params ); + } + + if ( mAmbientColorSC->isValid() ) + { + const ColorF &sunlight = gClientSceneGraph->getLightManager()->getSpecialLight(LightManager::slSunLightType)->getAmbient(); + Point3F ambientColor( sunlight.red, sunlight.green, sunlight.blue ); + + mShaderConsts->set( mAmbientColorSC, ambientColor ); + } + + if ( mWaterFogPlaneSC->isValid() ) + { + const PlaneF &plane = gClientSceneGraph->getWaterFogData().plane; + mShaderConsts->set( mWaterFogPlaneSC, plane ); + } + + if ( mScreenSunPosSC->isValid() && state ) + { + // Grab our projection matrix + // from the frustum. + Frustum frust = state->getFrustum(); + MatrixF proj( true ); + frust.getProjectionMatrix( &proj ); + + // Grab the ScatterSky world matrix. + MatrixF camMat = state->getCameraTransform(); + camMat.inverse(); + MatrixF tmp( true ); + tmp = camMat; + tmp.setPosition( Point3F( 0, 0, 0 ) ); + + Point3F sunPos( 0, 0, 0 ); + + // Get the light manager and sun light object. + LightManager *lm = state->getLightManager(); + LightInfo *sunLight = lm->getSpecialLight( LightManager::slSunLightType ); + + // Grab the light direction and scale + // by the ScatterSky radius to get the world + // space sun position. + const VectorF &lightDir = sunLight->getDirection(); + + Point3F lightPos( lightDir.x * (6378.0f * 1000.0f), + lightDir.y * (6378.0f * 1000.0f), + lightDir.z * (6378.0f * 1000.0f) ); + + // Get the screen space sun position. + MathUtils::mProjectWorldToScreen( lightPos, &sunPos, GFX->getViewport(), tmp, proj ); + + // And normalize it to the 0 to 1 range. + sunPos.x /= (F32)GFX->getViewport().extent.x; + sunPos.y /= (F32)GFX->getViewport().extent.y; + + mShaderConsts->set( mScreenSunPosSC, Point2F( sunPos.x, sunPos.y ) ); + } + + if ( mLightDirectionSC->isValid() && state ) + { + LightManager *lm = state->getLightManager(); + LightInfo *sunLight = lm->getSpecialLight( LightManager::slSunLightType ); + + const VectorF &lightDir = sunLight->getDirection(); + mShaderConsts->set( mLightDirectionSC, lightDir ); + } + + if ( mCameraForwardSC->isValid() && state ) + { + const MatrixF &camMat = state->getCameraTransform(); + VectorF camFwd( 0, 0, 0 ); + + camMat.getColumn( 1, &camFwd ); + + mShaderConsts->set( mCameraForwardSC, camFwd ); + } + + if ( mAccumTimeSC->isValid() ) + mShaderConsts->set( mAccumTimeSC, MATMGR->getTotalTime() ); + + if ( mDeltaTimeSC->isValid() ) + mShaderConsts->set( mDeltaTimeSC, MATMGR->getDeltaTime() ); + + if ( mInvCameraMatSC->isValid() && state ) + { + MatrixF mat = state->getCameraTransform(); + mat.inverse(); + mShaderConsts->set( mInvCameraMatSC, mat, mInvCameraMatSC->getType() ); + } + + // Set EffectConsts - specified from script + + // If our shader has reloaded since last frame we must mark all + // EffectConsts dirty so they will be reset. + if ( mShader->getReloadKey() != mShaderReloadKey ) + { + mShaderReloadKey = mShader->getReloadKey(); + + EffectConstTable::Iterator iter = mEffectConsts.begin(); + for ( ; iter != mEffectConsts.end(); iter++ ) + iter->value->mDirty = true; + } + + // Doesn't look like anyone is using this anymore. + // But if we do want to pass this info to script, + // we should do so in the same way as I am doing below. + /* + Point2F texSizeScriptConst( 0, 0 ); + String buffer; + if ( mActiveTextures[0] ) + { + texSizeScriptConst.x = (F32)mActiveTextures[0]->getWidth(); + texSizeScriptConst.y = (F32)mActiveTextures[0]->getHeight(); + + dSscanf( buffer.c_str(), "%g %g", texSizeScriptConst.x, texSizeScriptConst.y ); + } + */ + + if ( isMethod( "setShaderConsts" ) ) + { + PROFILE_SCOPE( PostEffect_SetShaderConsts ); + + // Pass some data about the current render state to script. + // + // TODO: This is pretty messy... it should go away. This info + // should be available from some other script accessible method + // or field which isn't PostEffect specific. + // + if ( state ) + { + Con::setFloatVariable( "$Param::NearDist", state->getNearPlane() ); + Con::setFloatVariable( "$Param::FarDist", state->getFarPlane() ); + } + + Con::executef( this, "setShaderConsts" ); + } + + EffectConstTable::Iterator iter = mEffectConsts.begin(); + for ( ; iter != mEffectConsts.end(); iter++ ) + iter->value->setToBuffer( mShaderConsts ); +} + +void PostEffect::_setupTexture( U32 stage, GFXTexHandle &inputTex, const RectI *inTexViewport ) +{ + const String &texFilename = mTexFilename[ stage ]; + + GFXTexHandle theTex; + MatTextureTarget *namedTarget = NULL; + + RectI viewport = GFX->getViewport(); + + if ( texFilename.compare( "$inTex", 0, String::NoCase ) == 0 ) + { + theTex = inputTex; + + if ( inTexViewport ) + { + viewport = *inTexViewport; + } + else if ( theTex ) + { + viewport.set( 0, 0, theTex->getWidth(), theTex->getHeight() ); + } + } + else if ( texFilename.compare( "$backBuffer", 0, String::NoCase ) == 0 ) + { + theTex = PFXMGR->getBackBufferTex(); + if ( theTex ) + viewport.set( 0, 0, theTex->getWidth(), theTex->getHeight() ); + } + else if ( texFilename.isNotEmpty() && texFilename[0] == '#' ) + { + namedTarget = MatTextureTarget::findTargetByName( texFilename.c_str() + 1 ); + if ( namedTarget ) + { + theTex = namedTarget->getTargetTexture( 0 ); + viewport = namedTarget->getTargetViewport(); + } + } + else + { + theTex = mTextures[ stage ]; + if ( theTex ) + viewport.set( 0, 0, theTex->getWidth(), theTex->getHeight() ); + } + + mActiveTextures[ stage ] = theTex; + mActiveNamedTarget[ stage ] = namedTarget; + mActiveTextureViewport[ stage ] = viewport; + + if ( theTex.isValid() ) + GFX->setTexture( stage, theTex ); +} + +void PostEffect::_setupTransforms() +{ + // Set everything to identity. + GFX->setWorldMatrix( MatrixF::Identity ); + GFX->setProjectionMatrix( MatrixF::Identity ); +} + +void PostEffect::_setupTarget( const SceneState *state, bool *outClearTarget ) +{ + if ( mIsNamedTarget || mTargetName.compare( "$outTex", 0, String::NoCase ) == 0 ) + { + // Size it relative to the texture of the first stage or + // if NULL then use the current target. + + Point2I targetSize; + + // If we have an absolute target size then use that. + if ( !mTargetSize.isZero() ) + targetSize = mTargetSize; + + // Else generate a relative size using the target scale. + else if ( mActiveTextures[ 0 ] ) + { + const Point3I &texSize = mActiveTextures[ 0 ]->getSize(); + + targetSize.set( texSize.x * mTargetScale.x, + texSize.y * mTargetScale.y ); + } + else + { + GFXTarget *oldTarget = GFX->getActiveRenderTarget(); + const Point2I &oldTargetSize = oldTarget->getSize(); + + targetSize.set( oldTargetSize.x * mTargetScale.x, + oldTargetSize.y * mTargetScale.y ); + } + + // Make sure its at least 1x1. + targetSize.setMax( Point2I::One ); + + if ( !mIsNamedTarget || + !mTargetTex || + mTargetTex.getWidthHeight() != targetSize ) + { + mTargetTex.set( targetSize.x, targetSize.y, mTargetFormat, + &PostFxTargetProfile, "PostEffect::_setupTarget" ); + + if ( mTargetClear == PFXTargetClear_OnCreate ) + *outClearTarget = true; + + mTargetRect.set( 0, 0, targetSize.x, targetSize.y ); + } + } + else + mTargetTex = NULL; + + if ( mTargetClear == PFXTargetClear_OnDraw ) + *outClearTarget = true; + + if ( !mTarget && mTargetTex ) + mTarget = GFX->allocRenderToTextureTarget(); +} + +void PostEffect::_cleanTargets( bool recurse ) +{ + mTargetTex = NULL; + mTarget = NULL; + + if ( !recurse ) + return; + + // Clear the children too! + for ( U32 i = 0; i < size(); i++ ) + { + PostEffect *effect = (PostEffect*)(*this)[i]; + effect->_cleanTargets( true ); + } +} + +void PostEffect::process( const SceneState *state, + GFXTexHandle &inOutTex, + const RectI *inTexViewport ) +{ + GFXDEBUGEVENT_SCOPE_EX( PostEffect_Process, ColorI::GREEN, avar("PostEffect: %s", getName()) ); + + if ( mSkip || ( !mRequirementsMet && !mUpdateShader ) ) + return; + + // Skip out if we don't support reflection passes. + if ( state && state->isReflectPass() && !mAllowReflectPass ) + return; + + if ( mOneFrameOnly && !mOnThisFrame ) + return; + + if ( isMethod( "preProcess" ) ) + { + PROFILE_SCOPE( PostEffect_preProcess ); + Con::executef( this, "preProcess" ); + } + + GFXTransformSaver saver; + + // Set the textures. + for ( U32 i = 0; i < NumTextures; i++ ) + _setupTexture( i, inOutTex, inTexViewport ); + + _setupStateBlock( state ) ; + _setupTransforms(); + + bool clearTarget = false; + _setupTarget( state, &clearTarget ); + + if ( mTargetTex ) + { +#ifdef TORQUE_OS_XENON + // You may want to disable this functionality for speed reasons as it does + // add some overhead. The upside is it makes things "just work". If you + // re-work your post-effects properly, this is not needed. + // + // If this post effect doesn't alpha blend to the back-buffer, than preserve + // the active render target contents so they are still around the next time + // that render target activates + if(!mStateBlockData->getState().blendEnable) + GFX->getActiveRenderTarget()->preserve(); +#endif + GFX->pushActiveRenderTarget(); + mTarget->attachTexture( GFXTextureTarget::Color0, mTargetTex ); + GFX->setActiveRenderTarget( mTarget ); + } + + if ( clearTarget ) + GFX->clear( GFXClearTarget, mTargetClearColor, 1.f, 0 ); + + // Do we have a shader that needs updating? + if ( mUpdateShader ) + { + mShader = NULL; + mUpdateShader = false; + + // check requirements + mRequirementsMet = checkRequirements(); + if ( mRequirementsMet ) + { + ShaderData *shaderData; + if ( Sim::findObject( mShaderName, shaderData ) ) + { + // Gather macros specified on this PostEffect. + Vector macros( mShaderMacros ); + + // Gather conditioner macros. + for ( U32 i = 0; i < NumTextures; i++ ) + { + if ( mActiveNamedTarget[i] ) + mActiveNamedTarget[i]->getTargetShaderMacros( ¯os ); + } + + mShader = shaderData->getShader( macros ); + } + } + else + { + // Clear the targets... we won't be rendering + // again until the shader passes requirements. + _cleanTargets( true ); + } + } + + // Setup the shader and constants. + if ( mShader ) + { + _setupConstants( state ); + + GFX->setShader( mShader ); + GFX->setShaderConstBuffer( mShaderConsts ); + } + else + GFX->disableShaders(); + + Frustum frustum; + if ( state ) + frustum = state->getFrustum(); + else + { + // If we don't have a scene state then setup + // a dummy frustum... you better not be depending + // on this being related to the camera in any way. + + frustum.set( false, -0.1f, 0.1f, -0.1f, 0.1f, 0.1f, 100.0f ); + } + + GFXVertexBufferHandle vb; + _updateScreenGeometry( frustum, &vb ); + + // Draw it. + GFX->setVertexBuffer( vb ); + GFX->drawPrimitive( GFXTriangleFan, 0, 2 ); + + // Allow PostEffecVis to hook in. + PFXVIS->onPFXProcessed( this ); + + if ( mTargetTex ) + { + mTarget->resolve(); + GFX->popActiveRenderTarget(); + } + else + { + // We wrote to the active back buffer, so release + // the current texture copy held by the manager. + // + // This ensures a new copy is made. + PFXMGR->releaseBackBufferTex(); + } + + // Return and release our target texture. + inOutTex = mTargetTex; + if ( !mIsNamedTarget ) + mTargetTex = NULL; + + // Restore the transforms before the children + // are processed as it screws up the viewport. + saver.restore(); + + // Now process my children. + iterator i = begin(); + for ( ; i != end(); i++ ) + { + PostEffect *effect = static_cast(*i); + effect->process( state, inOutTex ); + } + + if ( mOneFrameOnly ) + mOnThisFrame = false; +} + +bool PostEffect::_setIsEnabled( void* obj, const char* data ) +{ + bool enabled = dAtob( data ); + if ( enabled ) + static_cast( obj )->enable(); + else + static_cast( obj )->disable(); + + // Always return false from a protected field. + return false; +} + +void PostEffect::enable() +{ + // Don't add TexGen PostEffects to the PostEffectManager! + if ( mRenderTime == PFXTexGenOnDemand ) + return; + + // Ignore it if its already enabled. + if ( mEnabled ) + return; + + mEnabled = true; + + // We cannot really enable the effect + // until its been registed. + if ( !isProperlyAdded() ) + return; + + // If the enable callback returns 'false' then + // leave the effect disabled. + const char* result = Con::executef( this, "onEnabled" ); + if ( result[0] && !dAtob( result ) ) + { + mEnabled = false; + return; + } + + PFXMGR->_addEffect( this ); +} + +void PostEffect::disable() +{ + if ( !mEnabled ) + return; + + mEnabled = false; + _cleanTargets( true ); + + if ( isProperlyAdded() ) + { + PFXMGR->_removeEffect( this ); + Con::executef( this, "onDisabled" ); + } +} + +void PostEffect::reload() +{ + // Reload the shader if we have one or mark it + // for updating when its processed next. + if ( mShader ) + mShader->reload(); + else + mUpdateShader = true; + + // Null stateblock so it is reloaded. + mStateBlock = NULL; + + // Call reload on any children + // this PostEffect may have. + for ( U32 i = 0; i < size(); i++ ) + { + PostEffect *effect = (PostEffect*)(*this)[i]; + effect->reload(); + } +} + +void PostEffect::setShaderConst( const String &name, const String &val ) +{ + PROFILE_SCOPE( PostEffect_SetShaderConst ); + + EffectConstTable::Iterator iter = mEffectConsts.find( name ); + if ( iter == mEffectConsts.end() ) + { + EffectConst *newConst = new EffectConst( name, val ); + iter = mEffectConsts.insertUnique( name, newConst ); + } + + iter->value->set( val ); +} + +F32 PostEffect::getAspectRatio() const +{ + const Point2I &rtSize = GFX->getActiveRenderTarget()->getSize(); + return (F32)rtSize.x / (F32)rtSize.y; +} + +bool PostEffect::checkRequirements() const +{ + // Make sure that the requirements for this post effect can be met before + // enabling it. + U32 checkReqs = mPostEffectRequirements; + PostEffect *parentEffect = dynamic_cast(getGroup()); + while(parentEffect != NULL && checkReqs == U32_MAX) + { + checkReqs = parentEffect->mPostEffectRequirements; + parentEffect = dynamic_cast(parentEffect->getGroup()); + } + + if(checkReqs == U32_MAX) + { + Con::warnf("You should specify 'requirements' field for PostEffect '%s'. (You may need to move 'isEnabled' after the 'requirements' field in your singleton definition)", getName()); + checkReqs = 0; + } + + // Now figure out if this effect can be enabled based on what it requires + bool ret = true; +// TODO... why is it just this part that needs to be +// defined out? I suspect this is actually unnessasary. +#ifndef TORQUE_DEDICATED + if(checkReqs & PostEffect::RequiresDepth) + { + MatTextureTarget *namedTarget = MatTextureTarget::findTargetByName( RenderPrePassMgr::BufferName ); + ret &= (namedTarget != NULL); + } + if(checkReqs & PostEffect::RequiresNormals) + { + // This is kind of a hack. + MatTextureTarget *namedTarget = MatTextureTarget::findTargetByName( AdvancedLightBinManager::smBufferName ); + ret &= (namedTarget != NULL); + } + if(checkReqs & PostEffect::RequiresLightInfo) + { + MatTextureTarget *namedTarget = MatTextureTarget::findTargetByName( AdvancedLightBinManager::smBufferName ); + ret &= (namedTarget != NULL); + } +#endif + return ret; +} + +bool PostEffect::dumpShaderDisassembly( String &outFilename ) const +{ + String data; + + if ( !mShader || !mShader->getDisassembly( data ) ) + return false; + + outFilename = FS::MakeUniquePath( "", "ShaderDisassembly", "txt" ); + + FileStream *fstream = FileStream::createAndOpen( outFilename, Torque::FS::File::Write ); + if ( !fstream ) + return false; + + fstream->write( data ); + fstream->close(); + delete fstream; + + return true; +} + +void PostEffect::setShaderMacro( const String &name, const String &value ) +{ + // Check to see if we already have this macro. + Vector::iterator iter = mShaderMacros.begin(); + for ( ; iter != mShaderMacros.end(); iter++ ) + { + if ( iter->name == name ) + { + if ( iter->value != value ) + { + iter->value = value; + mUpdateShader = true; + } + return; + } + } + + // Add a new macro. + mShaderMacros.increment(); + mShaderMacros.last().name = name; + mShaderMacros.last().value = value; + mUpdateShader = true; +} + +bool PostEffect::removeShaderMacro( const String &name ) +{ + Vector::iterator iter = mShaderMacros.begin(); + for ( ; iter != mShaderMacros.end(); iter++ ) + { + if ( iter->name == name ) + { + mShaderMacros.erase( iter ); + mUpdateShader = true; + return true; + } + } + + return false; +} + +void PostEffect::clearShaderMacros() +{ + if ( mShaderMacros.empty() ) + return; + + mShaderMacros.clear(); + mUpdateShader = true; +} + +GFXTextureObject* PostEffect::getTargetTexture( U32 index ) const +{ + // A TexGen PostEffect will generate its texture now if it + // has not already. + if ( mRenderTime == PFXTexGenOnDemand ) + return const_cast( this )->_texGen(); + + return mTargetTex.getPointer(); +} + +GFXTextureObject* PostEffect::_texGen() +{ + if ( !mTargetTex || mUpdateShader ) + { + GFXTexHandle chainTex; + process( NULL, chainTex ); + + // TODO: We should add a conditional copy + // to a non-RT texture here to reduce the + // amount of non-swappable RTs in use. + } + + return mTargetTex.getPointer(); +} + +ConsoleMethod( PostEffect, reload, void, 2, 2, + "Reloads the effect shader and textures." ) +{ + return object->reload(); +} + +ConsoleMethod( PostEffect, enable, void, 2, 2, + "Enables the effect." ) +{ + object->enable(); +} + +ConsoleMethod( PostEffect, disable, void, 2, 2, + "Disables the effect." ) +{ + object->disable(); +} + +ConsoleMethod( PostEffect, toggle, bool, 2, 2, + "Toggles the effect state returning true if we enable it." ) +{ + if ( object->isEnabled() ) + object->disable(); + else + object->enable(); + + return object->isEnabled(); +} + +ConsoleMethod( PostEffect, isEnabled, bool, 2, 2, + "Returns true if the effect is enabled." ) +{ + return object->isEnabled(); +} + +ConsoleMethod( PostEffect, setShaderConst, void, 4, 4, "( String name, float value )" ) +{ + object->setShaderConst( argv[2], argv[3] ); +} + +ConsoleMethod( PostEffect, getAspectRatio, F32, 2, 2, "Returns width over height aspect ratio of the backbuffer." ) +{ + return object->getAspectRatio(); +} + +ConsoleMethod( PostEffect, dumpShaderDisassembly, const char*, 2, 2, "Dumps this PostEffect shader's disassembly to a temporary text file. Returns the fullpath of that file if successful." ) +{ + String fileName; + if ( !object->dumpShaderDisassembly( fileName ) ) + return NULL; + + char *buf = Con::getReturnBuffer(256); + dStrcpy( buf, fileName.c_str() ); + + return buf; +} + +ConsoleMethod( PostEffect, setShaderMacro, void, 3, 4, "( string key, [string value] ) - add/set a shader macro." ) +{ + if ( argc > 3 ) + object->setShaderMacro( argv[2], argv[3] ); + else + object->setShaderMacro( argv[2] ); +} + +ConsoleMethod( PostEffect, removeShaderMacro, void, 3, 3, "( string key )" ) +{ + object->removeShaderMacro( argv[2] ); +} + +ConsoleMethod( PostEffect, clearShaderMacros, void, 2, 2, "()" ) +{ + object->clearShaderMacros(); +} diff --git a/postFx/postEffect.h b/postFx/postEffect.h new file mode 100644 index 0000000..42fb2d3 --- /dev/null +++ b/postFx/postEffect.h @@ -0,0 +1,318 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POST_EFFECT_H_ +#define _POST_EFFECT_H_ + +#ifndef _SIMSET_H_ +#include "console/simSet.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MPOINT2_H_ +#include "math/mPoint2.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif +#ifndef _POSTEFFECTCOMMON_H_ +#include "postFx/postEffectCommon.h" +#endif + +class GFXStateBlockData; +class Frustum; +class SceneState; +class ConditionerFeature; + +/// +GFX_DeclareTextureProfile( PostFxTargetProfile ); + + + + +/// +class PostEffect : public SimGroup, public MatTextureTarget +{ + typedef SimGroup Parent; + + friend class PostEffectVis; + +protected: + + enum + { + NumTextures = 4, + }; + + FileName mTexFilename[NumTextures]; + + GFXTexHandle mTextures[NumTextures]; + + GFXTextureObject *mActiveTextures[NumTextures]; + + MatTextureTarget *mActiveNamedTarget[NumTextures]; + + RectI mActiveTextureViewport[NumTextures]; + + GFXStateBlockData *mStateBlockData; + + GFXStateBlockRef mStateBlock; + + String mShaderName; + + GFXShaderRef mShader; + + Vector mShaderMacros; + + GFXShaderConstBufferRef mShaderConsts; + + GFXShaderConstHandle *mRTSizeSC; + GFXShaderConstHandle *mOneOverRTSizeSC; + + GFXShaderConstHandle *mTexSizeSC[NumTextures]; + GFXShaderConstHandle *mRenderTargetParamsSC[NumTextures]; + + GFXShaderConstHandle *mViewportOffsetSC; + + GFXShaderConstHandle *mFogDataSC; + GFXShaderConstHandle *mFogColorSC; + GFXShaderConstHandle *mEyePosSC; + GFXShaderConstHandle *mMatWorldToScreenSC; + GFXShaderConstHandle *mMatScreenToWorldSC; + GFXShaderConstHandle *mMatPrevScreenToWorldSC; + GFXShaderConstHandle *mNearFarSC; + GFXShaderConstHandle *mInvNearFarSC; + GFXShaderConstHandle *mWorldToScreenScaleSC; + GFXShaderConstHandle *mWaterColorSC; + GFXShaderConstHandle *mWaterFogDataSC; + GFXShaderConstHandle *mAmbientColorSC; + GFXShaderConstHandle *mWaterFogPlaneSC; + GFXShaderConstHandle *mScreenSunPosSC; + GFXShaderConstHandle *mLightDirectionSC; + GFXShaderConstHandle *mCameraForwardSC; + GFXShaderConstHandle *mAccumTimeSC; + GFXShaderConstHandle *mDeltaTimeSC; + GFXShaderConstHandle *mInvCameraMatSC; + + bool mAllowReflectPass; + + /// If true update the shader. + bool mUpdateShader; + + GFXTextureTargetRef mTarget; + + String mTargetName; + + GFXTexHandle mTargetTex; + + bool mIsNamedTarget; + + /// If mTargetSize is zero then this scale is + /// used to make a relative texture size to the + /// active render target. + Point2F mTargetScale; + + /// If non-zero this is used as the absolute + /// texture target size. + /// @see mTargetScale + Point2I mTargetSize; + + RectI mTargetRect; + + GFXFormat mTargetFormat; + + /// The color to prefill the named target when + /// first created by the effect. + ColorF mTargetClearColor; + + PFXRenderTime mRenderTime; + PFXTargetClear mTargetClear; + + String mRenderBin; + + F32 mRenderPriority; + + U32 mPostEffectRequirements; + bool mRequirementsMet; + + /// True if the effect has been enabled by the manager. + bool mEnabled; + + /// Skip processing of this PostEffect and its children even if its parent is enabled. + /// Parent and sibling PostEffects in the chain are still processed. + /// This is intended for debugging purposes. + bool mSkip; + + bool mOneFrameOnly; + bool mOnThisFrame; + + U32 mShaderReloadKey; + + class EffectConst + { + public: + + EffectConst( const String &name, const String &val ) + : mName( name ), + mHandle( NULL ), + mDirty( true ) + { + set( val ); + } + + void set( const String &newVal ); + + void setToBuffer( GFXShaderConstBufferRef buff ); + + String mName; + + GFXShaderConstHandle *mHandle; + + String mStringVal; + + bool mDirty; + }; + + typedef HashTable EffectConstTable; + + EffectConstTable mEffectConsts; + + /// + virtual void _updateScreenGeometry( const Frustum &frustum, + GFXVertexBufferHandle *outVB ); + + /// + virtual void _setupStateBlock( const SceneState *state ); + + /// + virtual void _setupConstants( const SceneState *state ); + + /// + virtual void _setupTransforms(); + + /// + virtual void _setupTarget( const SceneState *state, bool *outClearTarget ); + + /// + virtual void _setupTexture( U32 slot, GFXTexHandle &inputTex, const RectI *inTexViewport ); + + /// Protected set method for toggling the enabled state. + static bool _setIsEnabled( void* obj, const char* data ); + + /// Called from the light manager activate signal. + /// @see LightManager::addActivateCallback + void _onLMActivate( const char*, bool activate ) + { + if ( activate ) + mUpdateShader = true; + } + + /// We handle texture events to release named rendered targets. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ) + { + if ( code == GFXZombify && mIsNamedTarget ) + _cleanTargets(); + } + + /// + void _updateConditioners(); + + /// + void _cleanTargets( bool recurse = false ); + + GFXTextureObject* _texGen(); + +public: + + /// Constructor. + PostEffect(); + + /// Destructor. + virtual ~PostEffect(); + + virtual void process( const SceneState *state, + GFXTexHandle &inOutTex, + const RectI *inTexViewport = NULL ); + + /// + void reload(); + + /// + void enable(); + + /// + void disable(); + + /// + bool checkRequirements() const; + + /// Dump the shader disassembly to a temporary text file. + /// Returns true and sets outFilename to the file if successful. + bool dumpShaderDisassembly( String &outFilename ) const; + + /// + bool isEnabled() const { return mEnabled; } + + PFXRenderTime getRenderTime() const { return mRenderTime; } + + const String& getRenderBin() const { return mRenderBin; } + + F32 getPriority() const { return mRenderPriority; } + + void setTexture( U32 i, GFXTextureObject *tex ); + + void setShaderMacro( const String &name, const String &value = String::EmptyString ); + bool removeShaderMacro( const String &name ); + void clearShaderMacros(); + + /// + void setShaderConst( const String &name, const String &val ); + + void setOnThisFrame( bool enabled ) { mOnThisFrame = enabled; } + bool isOnThisFrame() { return mOnThisFrame; } + void setOneFrameOnly( bool enabled ) { mOneFrameOnly = enabled; } + bool isOneFrameOnly() { return mOneFrameOnly; } + + // SimObject + virtual bool onAdd(); + virtual void onRemove(); + static void initPersistFields(); + + /// MatTextureTarget + virtual GFXTextureObject* getTargetTexture( U32 index ) const; + virtual const RectI& getTargetViewport() const { return mTargetRect; } + virtual void setupSamplerState( GFXSamplerStateDesc *desc ) const {} + virtual ConditionerFeature* getTargetConditioner() const { return NULL; } + + F32 getAspectRatio() const; + + DECLARE_CONOBJECT(PostEffect); + + enum PostEffectRequirements + { + RequiresDepth = BIT(0), + RequiresNormals = BIT(1), + RequiresLightInfo = BIT(2), + }; +}; + +#endif // _POST_EFFECT_H_ \ No newline at end of file diff --git a/postFx/postEffectCommon.h b/postFx/postEffectCommon.h new file mode 100644 index 0000000..c103f87 --- /dev/null +++ b/postFx/postEffectCommon.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POSTEFFECTCOMMON_H_ +#define _POSTEFFECTCOMMON_H_ + + +/// +enum PFXRenderTime +{ + /// Before a RenderInstManager bin. + PFXBeforeBin, + + /// After a RenderInstManager bin. + PFXAfterBin, + + /// After the diffuse rendering pass. + PFXAfterDiffuse, + + /// When the end of the frame is reached. + PFXEndOfFrame, + + /// This PostEffect is not processed by the manager. + /// It will generate its texture when it is requested. + PFXTexGenOnDemand +}; + +/// PFXTargetClear specifies whether and how +/// often a given PostEffect's target will be cleared. +enum PFXTargetClear +{ + /// Never clear the PostEffect target. + PFXTargetClear_None, + + /// Clear once on create. + PFXTargetClear_OnCreate, + + /// Clear before every draw. + PFXTargetClear_OnDraw, +}; + +/// +struct PFXFrameState +{ + MatrixF worldToCamera; + MatrixF cameraToScreen; + + PFXFrameState() + : worldToCamera( true ), + cameraToScreen( true ) + { + } +}; + +/// +GFX_DeclareTextureProfile( PostFxTextureProfile ); + +/// +GFXDeclareVertexFormat( PFXVertex ) +{ + /// xyz position. + Point3F point; + + /// The screen space texture coord. + Point2F texCoord; + + /// + Point3F wsEyeRay; +}; + +#endif // _POSTEFFECTCOMMON_H_ \ No newline at end of file diff --git a/postFx/postEffectManager.cpp b/postFx/postEffectManager.cpp new file mode 100644 index 0000000..a1c574c --- /dev/null +++ b/postFx/postEffectManager.cpp @@ -0,0 +1,280 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "postFx/postEffectManager.h" + +#include "postFx/postEffect.h" +#include "postFx/postEffectVis.h" +#include "renderInstance/renderBinManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" + + +bool PostEffectManager::smRenderEffects = true; +bool PostEffectManager::smRB3DEffects = false; + +PostEffectManager::PostEffectManager() : + mFrameStateSwitch( false ), + mLastBackBufferTarget( NULL ) +{ + GFXDevice::getDeviceEventSignal().notify( this, &PostEffectManager::_handleDeviceEvent ); + RenderPassManager::getRenderBinSignal().notify( this, &PostEffectManager::_handleBinEvent ); + SceneGraph::getPostRenderSignal().notify( this, &PostEffectManager::_onPostRenderPass ); + + Con::addVariable("pref::enablePostEffects", TypeBool, &smRenderEffects); + Con::addVariable("pref::enableRB3D", TypeBool, &smRB3DEffects); +} + +PostEffectManager::~PostEffectManager() +{ + GFXDevice::getDeviceEventSignal().remove( this, &PostEffectManager::_handleDeviceEvent ); + RenderPassManager::getRenderBinSignal().remove( this, &PostEffectManager::_handleBinEvent ); + SceneGraph::getPostRenderSignal().remove( this, &PostEffectManager::_onPostRenderPass ); +} + +bool PostEffectManager::_handleDeviceEvent( GFXDevice::GFXDeviceEventType evt ) +{ + switch( evt ) + { + case GFXDevice::deStartOfFrame: + PFXVIS->onStartOfFrame(); + + // Fall through + + case GFXDevice::deDestroy: + + // Free the back buffer as the device or + // its content is now invalid. + releaseBackBufferTex(); + + break; + + case GFXDevice::deEndOfFrame: + + renderEffects( NULL, PFXEndOfFrame ); + + // Toggle frame state history switch + mFrameStateSwitch = !mFrameStateSwitch; + + break; + + default: + break; + } + + return true; +} + +void PostEffectManager::_handleBinEvent( RenderBinManager *bin, + const SceneState* sceneState, + bool isBinStart ) +{ + if ( sceneState->isShadowPass() || + sceneState->isOtherPass() ) + return; + + // We require a bin name to process effects... without + // it we can skip the bin entirely. + String binName( bin->getName() ); + if ( binName.isEmpty() ) + return; + + renderEffects( sceneState, isBinStart ? PFXBeforeBin : PFXAfterBin, binName ); +} + +void PostEffectManager::_onPostRenderPass( SceneGraph *sceneGraph, const SceneState *sceneState ) +{ + if ( !sceneState->isDiffusePass() ) + return; + + renderEffects( sceneState, PFXAfterDiffuse ); +} + +GFXTextureObject* PostEffectManager::getBackBufferTex() +{ + GFXTarget *target = GFX->getActiveRenderTarget(); + + if ( mBackBufferCopyTex.isNull() || + target != mLastBackBufferTarget ) + { + const Point2I &targetSize = target->getSize(); + GFXFormat targetFormat = target->getFormat(); + + mBackBufferCopyTex.set( targetSize.x, targetSize.y, + targetFormat, + &PostFxTargetProfile, "mBackBufferCopyTex" ); + + target->resolveTo( mBackBufferCopyTex ); + mLastBackBufferTarget = target; + } + + return mBackBufferCopyTex; +} + +void PostEffectManager::releaseBackBufferTex() +{ + mBackBufferCopyTex = NULL; + mLastBackBufferTarget = NULL; +} + +bool PostEffectManager::_addEffect( PostEffect *effect ) +{ + EffectVector *effects = NULL; + + const String &binName = effect->getRenderBin(); + + switch( effect->getRenderTime() ) + { + case PFXAfterDiffuse: + effects = &mAfterDiffuseList; + break; + + case PFXEndOfFrame: + effects = &mEndOfFrameList; + break; + + case PFXBeforeBin: + effects = &mBeforeBinMap[binName]; + break; + + case PFXAfterBin: + effects = &mAfterBinMap[binName]; + break; + } + + if ( effects == NULL ) + return false; + + effects->push_back( effect ); + + // Resort the effects by priority. + effects->sort( &_effectPrioritySort ); + + return true; +} + +bool PostEffectManager::_removeEffect( PostEffect *effect ) +{ + // Check the end of frame list. + EffectVector::iterator iter = find( mEndOfFrameList.begin(), mEndOfFrameList.end(), effect ); + if ( iter != mEndOfFrameList.end() ) + { + mEndOfFrameList.erase( iter ); + return true; + } + + // Check the diffuse list. + iter = find( mAfterDiffuseList.begin(), mAfterDiffuseList.end(), effect ); + if ( iter != mAfterDiffuseList.end() ) + { + mAfterDiffuseList.erase( iter ); + return true; + } + + // Now check the bin maps. + EffectMap::Iterator mapIter = mAfterBinMap.begin(); + for( ; mapIter != mAfterBinMap.end(); mapIter++ ) + { + EffectVector &effects = mapIter->value; + iter = find( effects.begin(), effects.end(), effect ); + if ( iter != effects.end() ) + { + effects.erase( iter ); + return true; + } + } + + mapIter = mBeforeBinMap.begin(); + for( ; mapIter != mBeforeBinMap.end(); mapIter++ ) + { + EffectVector &effects = mapIter->value; + iter = find( effects.begin(), effects.end(), effect ); + if ( iter != effects.end() ) + { + effects.erase( iter ); + return true; + } + } + + return false; +} + +void PostEffectManager::renderEffects( const SceneState *state, + const PFXRenderTime effectTiming, + const String &binName ) +{ + // MACHAX - The proper fix is to ensure that PostFX do not get rendered if + // their shader failed to load. +#ifdef TORQUE_OS_MAC + return; +#endif + + // Check the global render effect state as + // well as the + if ( !smRenderEffects || + ( state && !state->usePostEffects() )) + return; + + EffectVector *effects = NULL; + + switch( effectTiming ) + { + case PFXBeforeBin: + effects = &mBeforeBinMap[binName]; + break; + + case PFXAfterBin: + effects = &mAfterBinMap[binName]; + break; + + case PFXAfterDiffuse: + effects = &mAfterDiffuseList; + break; + + case PFXEndOfFrame: + effects = &mEndOfFrameList; + break; + } + + AssertFatal( effects != NULL, "Bad effect time" ); + + // Skip out if we don't have any effects. + if ( effects->empty() ) + return; + + // This is used to pass the output texture + // of one effect into the next effect. + GFXTexHandle chainTex; + + // Process the effects. + for ( U32 i = 0; i < effects->size(); i++ ) + { + PostEffect *effect = (*effects)[i]; + AssertFatal( effect != NULL, "Somehow this happened" ); + effect->process( state, chainTex ); + } +} + +void PostEffectManager::setFrameMatrices( const MatrixF &worldToCamera, const MatrixF &cameraToScreen ) +{ + PFXFrameState &thisFrame = mFrameState[mFrameStateSwitch]; + thisFrame.worldToCamera = worldToCamera; + thisFrame.cameraToScreen = cameraToScreen; +} + +S32 PostEffectManager::_effectPrioritySort( PostEffect* const *e1, PostEffect* const *e2 ) +{ + F32 p1 = (*e1)->getPriority(); + F32 p2 = (*e2)->getPriority(); + + if( p1 > p2 ) + return -1; + else if( p1 < p2 ) + return 1; + + return 0; +} \ No newline at end of file diff --git a/postFx/postEffectManager.h b/postFx/postEffectManager.h new file mode 100644 index 0000000..c3068c0 --- /dev/null +++ b/postFx/postEffectManager.h @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POSTEFFECTMANAGER_H_ +#define _POSTEFFECTMANAGER_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TDICTIONARY_H_ +#include "core/util/tDictionary.h" +#endif +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif +#ifndef _POSTEFFECTCOMMON_H_ +#include "postFx/postEffectCommon.h" +#endif + +class PostEffect; +class RenderBinManager; +class SceneState; +class SceneGraph; + + +class PostEffectManager +{ +protected: + + friend class PostEffect; + + typedef Vector EffectVector; + + typedef Map EffectMap; + + /// A global flag for toggling the post effect system. It + /// is tied to the $pref::enablePostEffects preference. + static bool smRenderEffects; + + EffectVector mEndOfFrameList; + EffectVector mAfterDiffuseList; + EffectMap mAfterBinMap; + EffectMap mBeforeBinMap; + + /// A copy of the last requested back buffer. + GFXTexHandle mBackBufferCopyTex; + + //GFXTexHandle mBackBufferFloatCopyTex; + + /// The target at the time the last back buffer + /// was copied. Used to detect the need to recopy. + GFXTarget *mLastBackBufferTarget; + + // State for current frame and last frame + bool mFrameStateSwitch; + + PFXFrameState mFrameState[2]; + + bool _handleDeviceEvent( GFXDevice::GFXDeviceEventType evt ); + + void _handleBinEvent( RenderBinManager *bin, + const SceneState* sceneState, + bool isBinStart ); + + /// + void _onPostRenderPass( SceneGraph *sceneGraph, const SceneState *sceneState ); + + // Helper method + void _updateResources(); + + /// + static S32 _effectPrioritySort( PostEffect* const*e1, PostEffect* const*e2 ); + + bool _addEffect( PostEffect *effect ); + + bool _removeEffect( PostEffect *effect ); + +public: + //global rb3d effect is enable ? + //notice , this effect is not managed by posteffectmanager + //it's controled by guitsctrl onrender + static bool smRB3DEffects; + + PostEffectManager(); + + virtual ~PostEffectManager(); + + void renderEffects( const SceneState *state, + const PFXRenderTime effectTiming, + const String &binName = String::EmptyString ); + + /// Returns the current back buffer texture taking + /// a copy of if the target has changed or the buffer + /// was previously released. + GFXTextureObject* getBackBufferTex(); + + /// Releases the current back buffer so that a + /// new copy is made on the next request. + void releaseBackBufferTex(); + + /* + bool submitEffect( PostEffect *effect, const PFXRenderTime renderTime = PFXDefaultRenderTime, const GFXRenderBinTypes afterBin = GFXBin_DefaultPostProcessBin ) + { + return _addEntry( effect, false, renderTime, afterBin ); + } + */ + + // State interface + const PFXFrameState &getFrameState() const { return mFrameState[mFrameStateSwitch]; } + const PFXFrameState &getLastFrameState() const { return mFrameState[!mFrameStateSwitch]; } + + void setFrameMatrices( const MatrixF &worldToCamera, const MatrixF &cameraToScreen ); +}; + +/// Returns the PostEffectManager singleton. +#define PFXMGR Singleton::instance() + +#endif // _POSTEFFECTMANAGER_H_ \ No newline at end of file diff --git a/postFx/postEffectVis.cpp b/postFx/postEffectVis.cpp new file mode 100644 index 0000000..d69794b --- /dev/null +++ b/postFx/postEffectVis.cpp @@ -0,0 +1,359 @@ + +#include "platform/platform.h" +#include "postFx/postEffectVis.h" +#include "gui/containers/guiWindowCtrl.h" +#include "gui/controls/guiBitmapCtrl.h" +#include "gui/core/guiCanvas.h" +#include "postFx/postEffectManager.h" + + +PostEffectVis::PostEffectVis() +{ +} + +PostEffectVis::~PostEffectVis() +{ +} + +void PostEffectVis::open( PostEffect *pfx ) +{ + GuiControl *content = _getContentControl(); + + // If we already have this PostEffect added + // remove it first so we can recreate its controls. + VisVector::iterator itr = mWindows.begin(); + for ( ; itr != mWindows.end(); itr++ ) + { + if ( itr->pfx == pfx ) + { + for ( U32 i = 0; i < TexCount; i++ ) + { + // Deleting the GuiWindowCtrl will automatically also delete + // any child controls we have allocated. + if ( itr->window[i] ) + itr->window[i]->deleteObject(); + } + + mWindows.erase_fast( itr ); + break; + } + } + + // Allocate VisWindow struct. + mWindows.increment(); + VisWindow &window = mWindows.last(); + window.pfx = pfx; + + for ( U32 i = 0; i < TexCount; i++ ) + { + // Only allocate window/bitmaps for input textures that are actually used. + if ( i > Target ) + { + if ( pfx->mTexFilename[i-1].isEmpty() ) + { + window.window[i] = NULL; + window.bmp[i] = NULL; + continue; + } + } + + // Allocate GuiWindowCtrl + GuiWindowCtrl *winCtrl = new GuiWindowCtrl(); + winCtrl->setPosition( Point2I( 50, 50 ) + Point2I( 15, 15 ) * i ); + winCtrl->setExtent( 347, 209 ); + winCtrl->setMinExtent( Point2I( 150, 100 ) ); + winCtrl->setMobility( true, true, true, true, false, false ); + winCtrl->setCanResize( true, true ); + winCtrl->setDataField( StringTable->insert( "closeCommand" ), NULL, "PfxVis::onWindowClosed( $ThisControl );" ); + winCtrl->registerObject(); + + window.window[i] = winCtrl; + + _setDefaultCaption( window, i ); + + // Allocate background GuiBitmapCtrl + GuiBitmapCtrl *bmpCtrl = new GuiBitmapCtrl(); + bmpCtrl->setPosition( 3, 23 ); + bmpCtrl->setSizing( GuiControl::horizResizeWidth, GuiControl::vertResizeHeight ); + bmpCtrl->setExtent( 341, 181 ); + bmpCtrl->setDataField( StringTable->insert( "wrap" ), NULL, "1" ); + bmpCtrl->setBitmap( "core/art/gui/images/transp_grid" ); + bmpCtrl->registerObject(); + winCtrl->addObject( bmpCtrl ); + + // Allocate GuiBitmapCtrl + bmpCtrl = new GuiBitmapCtrl(); + bmpCtrl->setPosition( 3, 23 ); + bmpCtrl->setSizing( GuiControl::horizResizeWidth, GuiControl::vertResizeHeight ); + bmpCtrl->setExtent( 341, 181 ); + bmpCtrl->registerObject(); + winCtrl->addObject( bmpCtrl ); + + window.bmp[i] = bmpCtrl; + + content->addObject( winCtrl ); + } + + // Make sure we visible. + setVisible( true ); +} + +void PostEffectVis::setVisible( bool visible ) +{ + GuiCanvas *canvas = NULL; + if ( !Sim::findObject( "Canvas", canvas ) ) + { + Con::errorf( "PostEffectVis::setVisible, Canvas was not found." ); + return; + } + + GuiControl *content = _getContentControl(); + + if ( visible && !content->isAwake() ) + canvas->pushDialogControl( content, 100 ); + + if ( !visible && content->isAwake() ) + canvas->popDialogControl( content ); +} + +void PostEffectVis::clear() +{ + GuiControl *content = _getContentControl(); + + content->clear(); + mWindows.clear(); +} + +void PostEffectVis::onStartOfFrame() +{ + if ( mWindows.empty() ) + return; + if ( !_getContentControl()->isAwake() ) + return; + + // Restore vis windows to a default state. + // This ensures to users that open PostEffects that are not + // actively being processed are obvious. + + VisVector::iterator itr = mWindows.begin(); + for ( ; itr != mWindows.end(); itr++ ) + { + for ( U32 i = 0; i < TexCount; i++ ) + { + if ( !itr->bmp[i] || itr->pfx->getRenderTime() == PFXTexGenOnDemand ) + continue; + + itr->bmp[i]->setBitmap( NULL ); + _setDefaultCaption( *itr, i ); + } + } +} + +void PostEffectVis::onPFXProcessed( PostEffect *pfx ) +{ + // If we have no windows we can early out before even testing + // isAwake so we avoid creating the content control unnecessarily. + if ( mWindows.empty() ) + return; + + if ( !_getContentControl()->isAwake() ) + return; + + VisVector::iterator itr = mWindows.begin(); + for ( ; itr != mWindows.end(); itr++ ) + { + if ( itr->pfx == pfx ) + { + GuiBitmapCtrl *pBmpCtrl = NULL; + GuiWindowCtrl *pWinCtrl = NULL; + + if ( itr->bmp[Target] != NULL ) + { + pBmpCtrl = itr->bmp[Target]; + pWinCtrl = itr->window[Target]; + + GFXTextureObject *tex; + + if ( pfx->mTargetTex ) + tex = pfx->mTargetTex; + else + tex = PFXMGR->getBackBufferTex(); + + pBmpCtrl->setBitmapHandle( tex ); + + char caption[256]; + char name[256]; + + if ( pfx->getName() == NULL || dStrlen( pfx->getName() ) == 0 ) + dSprintf( name, 256, "(none)" ); + else + dSprintf( name, 256, "%s", pfx->getName() ); + + + if ( tex ) + dSprintf( caption, 256, "%s[%i] target - %s [ %ix%i ]", name, pfx->getId(), pfx->mTargetName.c_str(), tex->getWidth(), tex->getHeight() ); + else + dSprintf( caption, 256, "%s[%i] target", name, pfx->getId() ); + + + pWinCtrl->setDataField( StringTable->insert("text"), NULL, caption ); + } + + for ( U32 i = Input1; i < TexCount; i++ ) + { + if ( itr->bmp[i] == NULL ) + continue; + + pBmpCtrl = itr->bmp[i]; + pWinCtrl = itr->window[i]; + + GFXTextureObject *tex = pfx->mActiveTextures[i-1]; + + pBmpCtrl->setBitmapHandle( tex ); + + char caption[256]; + char name[256]; + + if ( pfx->getName() == NULL || dStrlen( pfx->getName() ) == 0 ) + dSprintf( name, 256, "(none)" ); + else + dSprintf( name, 256, "%s", pfx->getName() ); + + + if ( tex ) + dSprintf( caption, 256, "%s[%i] input%i - %s [ %ix%i ]", name, pfx->getId(), i-1, pfx->mTexFilename[i-1].c_str(), tex->getWidth(), tex->getHeight() ); + else + dSprintf( caption, 256, "%s[%i] input%i - %s", name, pfx->getId(), i-1, pfx->mTexFilename[i-1].c_str() ); + + pWinCtrl->setDataField( StringTable->insert("text"), NULL, caption ); + } + } + } +} + +void PostEffectVis::onWindowClosed( GuiWindowCtrl *ctrl ) +{ + VisVector::iterator itr = mWindows.begin(); + + for ( ; itr != mWindows.end(); itr++ ) + { + for ( U32 i = 0; i < TexCount; i++ ) + { + if ( itr->window[i] == ctrl ) + { + itr->window[i] = NULL; + itr->bmp[i] = NULL; + ctrl->setVisible( false ); + + // Avoid deleting immediately since this happens in response to a + // script callback. + Con::evaluate( "%i.schedule( 1, \"delete\" );" ); + + return; + } + } + } + + Con::errorf( "PostEffectVis::onWindowClosed, passed window (%s) [%i] was found.", StringTable->insert( ctrl->getName() ), ctrl->getId() ); +} + +GuiControl* PostEffectVis::_getContentControl() +{ + if ( mContent == NULL ) + { + GuiCanvas *canvas = NULL; + if ( !Sim::findObject( "Canvas", canvas ) ) + { + AssertFatal( false, "PostEffectVis::_getContentControl, Canvas not found." ); + return NULL; + } + + mContent = new GuiControl(); + mContent->setPosition( 0, 0 ); + mContent->setExtent( 1024, 768 ); + mContent->setDataField( StringTable->insert( "noCursor" ), NULL, "1" ); + mContent->setDataField( StringTable->insert( "profile" ), NULL, "GuiModelessDialogProfile" ); + mContent->registerObject( "PfxVisContent" ); + + canvas->pushDialogControl( mContent, 100 ); + } + + return mContent; +} + +void PostEffectVis::_setDefaultCaption( VisWindow &vis, U32 texIndex ) +{ + PostEffect *pfx = vis.pfx; + GuiWindowCtrl *winCtrl = vis.window[texIndex]; + + if ( texIndex == Target ) + { + char caption[256]; + char name[256]; + + if ( pfx->getName() == NULL || dStrlen( pfx->getName() ) == 0 ) + dSprintf( name, 256, "(none)" ); + else + dSprintf( name, 256, "%s", pfx->getName() ); + + dSprintf( caption, 256, "%s[%i] target [NOT ENABLED]", name, pfx->getId() ); + + winCtrl->setDataField( StringTable->insert("text"), NULL, caption ); + } + else + { + char caption[256]; + char name[256]; + + if ( pfx->getName() == NULL || dStrlen( pfx->getName() ) == 0 ) + dSprintf( name, 256, "(none)" ); + else + dSprintf( name, 256, "%s", pfx->getName() ); + + dSprintf( caption, 256, "%s[%i] input%i - %s [NOT ENABLED]", name, pfx->getId(), texIndex-1, pfx->mTexFilename[texIndex-1].c_str() ); + + winCtrl->setDataField( StringTable->insert("text"), NULL, caption ); + } +} + +ConsoleStaticMethod( PfxVis, clear, void, 1, 1, "()" ) +{ + PFXVIS->clear(); +} + +ConsoleStaticMethod( PfxVis, open, void, 2, 3, "( PostEffect, [bool clear = false] )" ) +{ + if ( argc == 3 && dAtob( argv[2] ) ) + PFXVIS->clear(); + + PostEffect *pfx; + if ( !Sim::findObject( argv[1], pfx ) ) + { + Con::errorf( "PfxVis::add, argument %s was not a PostEffect", argv[1] ); + return; + } + + PFXVIS->open( pfx ); +} + +ConsoleStaticMethod( PfxVis, hide, void, 1, 1, "()" ) +{ + PFXVIS->setVisible( false ); +} + +ConsoleStaticMethod( PfxVis, show, void, 1, 1, "()" ) +{ + PFXVIS->setVisible( true ); +} + +ConsoleStaticMethod( PfxVis, onWindowClosed, void, 2, 2, "( GuiWindowCtrl )" ) +{ + GuiWindowCtrl *ctrl; + if ( !Sim::findObject( argv[1], ctrl ) ) + { + Con::errorf( "PfxVis::onWindowClosed, argument %s was not a GuiWindowCtrl", argv[1] ); + return; + } + + PFXVIS->onWindowClosed( ctrl ); +} \ No newline at end of file diff --git a/postFx/postEffectVis.h b/postFx/postEffectVis.h new file mode 100644 index 0000000..8e83318 --- /dev/null +++ b/postFx/postEffectVis.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _POSTEFFECTVIS_H_ +#define _POSTEFFECTVIS_H_ + +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif + +#ifndef _POST_EFFECT_H_ +#include "postFx/postEffect.h" +#endif + +class GuiWindowCtrl; +class GuiBitmapCtrl; +class GuiControl; + +class PostEffectVis +{ + // Protected constructor. + // Use PFXVIS define to access singleton. + PostEffectVis(); + friend class Singleton; + +public: + + ~PostEffectVis(); + + /// Open visualization windows for all input and target textures. + void open( PostEffect *pfx ); + + /// Close all visualization windows. + void clear(); + + /// Hide or show all visualization windows. + void setVisible( bool visible ); + + /// Callback from PostEffectManager at the start of a frame. + void onStartOfFrame(); + + /// Callback from PostEffect to update visualization. + void onPFXProcessed( PostEffect *pfx ); + + /// Callback when a visualization window is closed. + void onWindowClosed( GuiWindowCtrl *ctrl ); + +protected: + + /// Get or create the content control, the parent of all visualization windows. + GuiControl* _getContentControl(); + +protected: + + enum TexIndex + { + Target = 0, + Input1, + Input2, + Input3, + Input4, + TexCount + }; + + /// Structure representing a single 'opened' PostEffect + /// including GuiControls for displaying any input/target textures. + struct VisWindow + { + PostEffect *pfx; + GuiWindowCtrl *window[TexCount]; + GuiBitmapCtrl *bmp[TexCount]; + }; + + void _setDefaultCaption( VisWindow &vis, U32 texIndex ); + + typedef Vector< VisWindow > VisVector; + + VisVector mWindows; + + GuiControl *mContent; +}; + +/// Returns the PostEffectVis singleton. +#define PFXVIS Singleton::instance() + +#endif // _POSTEFFECTVIS_H_ \ No newline at end of file diff --git a/renderInstance/forcedMaterialMeshMgr.cpp b/renderInstance/forcedMaterialMeshMgr.cpp new file mode 100644 index 0000000..1a517f4 --- /dev/null +++ b/renderInstance/forcedMaterialMeshMgr.cpp @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderInstance/forcedMaterialMeshMgr.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxDebugEvent.h" +#include "materials/sceneData.h" +#include "materials/materialManager.h" +#include "materials/materialDefinition.h" +#include "console/consoleTypes.h" +#include "math/util/matrixSet.h" + +IMPLEMENT_CONOBJECT(ForcedMaterialMeshMgr); + +ForcedMaterialMeshMgr::ForcedMaterialMeshMgr() +{ + mOverrideInstance = NULL; + mOverrideMaterial = NULL; +} + +ForcedMaterialMeshMgr::ForcedMaterialMeshMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder, BaseMatInstance* overrideMaterial) +: RenderMeshMgr(riType, renderOrder, processAddOrder) +{ + mOverrideInstance = overrideMaterial; + mOverrideMaterial = NULL; +} + +void ForcedMaterialMeshMgr::setOverrideMaterial(BaseMatInstance* overrideMaterial) +{ + SAFE_DELETE(mOverrideInstance); + mOverrideInstance = overrideMaterial; +} + +ForcedMaterialMeshMgr::~ForcedMaterialMeshMgr() +{ + setOverrideMaterial(NULL); +} + +void ForcedMaterialMeshMgr::initPersistFields() +{ + addProtectedField("material", TypeSimObjectPtr, Offset(mOverrideMaterial, ForcedMaterialMeshMgr), + &_setOverrideMat, &defaultProtectedGetFn, "Material used to draw all meshes in the render bin."); + + Parent::initPersistFields(); +} + +void ForcedMaterialMeshMgr::render(SceneState * state) +{ + PROFILE_SCOPE(ForcedMaterialMeshMgr_render); + + if(!mOverrideInstance && mOverrideMaterial.isValid()) + { + mOverrideInstance = mOverrideMaterial->createMatInstance(); + mOverrideInstance->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat() ); + } + + // Early out if nothing to draw. + if(!mElementList.size() || !mOverrideInstance) + return; + + GFXDEBUGEVENT_SCOPE(ForcedMaterialMeshMgr_Render, ColorI::RED); + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + // init loop data + SceneGraphData sgData; + MeshRenderInst *ri = static_cast(mElementList[0].inst); + setupSGData( ri, sgData ); + + while (mOverrideInstance->setupPass(state, sgData)) + { + for( U32 j=0; j(mElementList[j].inst); + if(passRI->primBuff->getPointer()->mPrimitiveArray[passRI->primBuffIndex].numVertices < 1) + continue; + + getParentManager()->getMatrixSet().setWorld(*passRI->objectToWorld); + getParentManager()->getMatrixSet().setView(*passRI->worldToCamera); + getParentManager()->getMatrixSet().setProjection(*passRI->projection); + mOverrideInstance->setTransforms(getParentManager()->getMatrixSet(), state); + + mOverrideInstance->setBuffers(passRI->vertBuff, passRI->primBuff); + GFX->drawPrimitive( passRI->primBuffIndex ); + } + } +} + +bool ForcedMaterialMeshMgr::_setOverrideMat( void *obj, const char *data ) +{ + // Clear out the instance and let the assignment take care of the rest. + ForcedMaterialMeshMgr &mgr = *reinterpret_cast(obj); + mgr.setOverrideMaterial(NULL); + return true; +} \ No newline at end of file diff --git a/renderInstance/forcedMaterialMeshMgr.h b/renderInstance/forcedMaterialMeshMgr.h new file mode 100644 index 0000000..e5d0233 --- /dev/null +++ b/renderInstance/forcedMaterialMeshMgr.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _RENDERFORCEDMATMESHMGR_H_ +#define _RENDERFORCEDMATMESHMGR_H_ + +#ifndef _RENDERMESHMGR_H_ +#include "renderInstance/renderMeshMgr.h" +#endif + +class Material; + +/// Basically the same as RenderMeshMgr, but will override the material of the instance. +class ForcedMaterialMeshMgr : public RenderMeshMgr +{ + typedef RenderMeshMgr Parent; +public: + ForcedMaterialMeshMgr(); + ForcedMaterialMeshMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder, BaseMatInstance* overrideMaterial); + virtual ~ForcedMaterialMeshMgr(); + + void setOverrideMaterial(BaseMatInstance* overrideMaterial); + + // RenderBinManager interface + virtual void render(SceneState * state); + + DECLARE_CONOBJECT(ForcedMaterialMeshMgr); + static void initPersistFields(); +private: + BaseMatInstance* mOverrideInstance; + SimObjectPtr mOverrideMaterial; + static bool _setOverrideMat( void *obj, const char *data ); +}; + +#endif diff --git a/renderInstance/renderBinManager.cpp b/renderInstance/renderBinManager.cpp new file mode 100644 index 0000000..ece4712 --- /dev/null +++ b/renderInstance/renderBinManager.cpp @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderInstance/renderBinManager.h" +#include "console/consoleTypes.h" +#include "materials/matInstance.h" +#include "sceneGraph/sceneGraph.h" +//#include "scene/sceneReflectPass.h" + +IMPLEMENT_CONOBJECT(RenderBinManager); + +//----------------------------------------------------------------------------- +// RenderBinManager +//----------------------------------------------------------------------------- +RenderBinManager::RenderBinManager() +{ + VECTOR_SET_ASSOCIATION( mElementList ); + mElementList.reserve( 2048 ); + mRenderInstType = RenderPassManager::RIT_Custom; + mRenderOrder = 1.0f; + mProcessAddOrder = 1.0f; + mParentManager = NULL; +} + +RenderBinManager::RenderBinManager(const RenderInstType& ritype, F32 renderOrder, F32 processAddOrder) +{ + VECTOR_SET_ASSOCIATION( mElementList ); + mElementList.reserve( 2048 ); + mRenderInstType = ritype; + mRenderOrder = renderOrder; + mProcessAddOrder = processAddOrder; + mParentManager = NULL; +} + +void RenderBinManager::initPersistFields() +{ + addField("binType", TypeRealString, Offset(mRenderInstType.mName, RenderBinManager)); + addField("renderOrder", TypeF32, Offset(mRenderOrder, RenderBinManager)); + addField("processAddOrder", TypeF32, Offset(mProcessAddOrder, RenderBinManager)); + + Parent::initPersistFields(); +} + +void RenderBinManager::onRemove() +{ + // Tell our parent to remove us when + // we're being unregistered. + if ( mParentManager ) + mParentManager->removeManager( this ); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- +// addElement +//----------------------------------------------------------------------------- +RenderBinManager::AddInstResult RenderBinManager::addElement( RenderInst *inst ) +{ + if (inst->type != mRenderInstType) + return arSkipped; + + internalAddElement(inst); + + return arAdded; +} + +void RenderBinManager::internalAddElement(RenderInst* inst) +{ + mElementList.increment(); + MainSortElem &elem = mElementList.last(); + elem.inst = inst; + elem.key = elem.key2 = 0; + + elem.key = inst->defaultKey; + elem.key2 = inst->defaultKey2; +} + +//----------------------------------------------------------------------------- +// clear +//----------------------------------------------------------------------------- +void RenderBinManager::clear() +{ + mElementList.clear(); +} + +//----------------------------------------------------------------------------- +// sort +//----------------------------------------------------------------------------- +void RenderBinManager::sort() +{ + dQsort( mElementList.address(), mElementList.size(), sizeof(MainSortElem), cmpKeyFunc); +} + +//----------------------------------------------------------------------------- +// QSort callback function +//----------------------------------------------------------------------------- +S32 FN_CDECL RenderBinManager::cmpKeyFunc(const void* p1, const void* p2) +{ + const MainSortElem* mse1 = (const MainSortElem*) p1; + const MainSortElem* mse2 = (const MainSortElem*) p2; + + S32 test1 = S32(mse2->key) - S32(mse1->key); + + return ( test1 == 0 ) ? S32(mse1->key2) - S32(mse2->key2) : test1; +} + +void RenderBinManager::setupSGData( MeshRenderInst *ri, SceneGraphData &data ) +{ + data.reset(); + data.setFogParams(mParentManager->getSceneManager()->getFogData()); + + dMemcpy( data.lights, ri->lights, sizeof( data.lights ) ); + + data.objTrans = *ri->objectToWorld; + data.backBuffTex = ri->backBuffTex; + data.cubemap = ri->cubemap; + data.miscTex = ri->miscTex; + data.reflectTex = ri->reflectTex; + + data.wireframe = GFXDevice::getWireframe(); + + data.lightmap = ri->lightmap; + data.visibility = ri->visibility; + + data.materialHint = ri->materialHint; +} + +ConsoleMethod(RenderBinManager, getBinType, const char*, 2, 2, "Returns the type of manager.") +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + + return object->getRenderInstType().getName(); +} diff --git a/renderInstance/renderBinManager.h b/renderInstance/renderBinManager.h new file mode 100644 index 0000000..87a9499 --- /dev/null +++ b/renderInstance/renderBinManager.h @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _RENDERBINMANAGER_H_ +#define _RENDERBINMANAGER_H_ + +#ifndef _CONSOLEOBJECT_H_ +#include "console/consoleObject.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _BASEMATINSTANCE_H_ +#include "materials/baseMatInstance.h" +#endif +#ifndef _UTIL_DELEGATE_H_ +#include "core/util/delegate.h" +#endif + +class SceneState; + + +/// This delegate is used in derived RenderBinManager classes +/// to allow material instances to be overriden. +typedef Delegate MaterialOverrideDelegate; + + +/// The RenderBinManager manages and renders lists of MainSortElem, which +/// is a light wrapper around RenderInst. +class RenderBinManager : public SimObject +{ + typedef SimObject Parent; +public: + struct MainSortElem + { + RenderInst *inst; + U32 key; + U32 key2; + }; + + // Returned by AddInst below + enum AddInstResult + { + arAdded, // We added this instance + arSkipped, // We didn't add this instance + arStop // Stop processing this instance + }; +public: + RenderBinManager(); + RenderBinManager(const RenderInstType& ritype, F32 renderOrder, F32 processAddOrder); + virtual ~RenderBinManager() {} + + // SimObject + void onRemove(); + + virtual AddInstResult addElement( RenderInst *inst ); + virtual void sort(); + virtual void render( SceneState *state ) {} + virtual void clear(); + + // Manager info + F32 getProcessAddOrder() const { return mProcessAddOrder; } + void setProcessAddOrder(F32 processAddOrder) { mProcessAddOrder = processAddOrder; } + F32 getRenderOrder() const { return mRenderOrder; } + void setRenderOrder(F32 renderOrder) { mRenderOrder = renderOrder; } + const RenderInstType& getRenderInstType() { return mRenderInstType; } + RenderPassManager* getParentManager() const { return mParentManager; } + void setParentManager(RenderPassManager* parentManager) { mParentManager = parentManager; } + + /// QSort callback function + static S32 FN_CDECL cmpKeyFunc(const void* p1, const void* p2); + + DECLARE_CONOBJECT(RenderBinManager); + static void initPersistFields(); + + MaterialOverrideDelegate& getMatOverrideDelegate() { return mMatOverrideDelegate; } + +protected: + Vector< MainSortElem > mElementList; // List of our instances + RenderInstType mRenderInstType; // What kind of render bin are we + F32 mProcessAddOrder; // Where in the list do we process RenderInstance additions? + F32 mRenderOrder; // Where in the list do we render? + RenderPassManager* mParentManager; // What render pass manager is our parent? + + MaterialOverrideDelegate mMatOverrideDelegate; + + virtual void setupSGData(MeshRenderInst *ri, SceneGraphData &data ); + virtual bool newPassNeeded(BaseMatInstance* currMatInst, MeshRenderInst* ri); + BaseMatInstance* getMaterial(RenderInst* inst) const; + virtual void internalAddElement(RenderInst* inst); +}; + +// The bin is sorted by (see RenderBinManager::cmpKeyFunc) +// 1. Material +// 2. Manager specific key (vertex buffer address by default) +// This function is called on each item of the bin and basically detects any changes in conditions 1 or 2 +inline bool RenderBinManager::newPassNeeded(BaseMatInstance* currMatInst, MeshRenderInst* ri) +{ + BaseMatInstance *matInst = ri->matInst; + + // If we have a delegate then we must let it + // update the mat instance else the comparision + // will always fail. + if ( mMatOverrideDelegate ) + matInst = mMatOverrideDelegate( matInst ); + + // We need a new pass if: + // 1. There's no Material Instance (old ff object?) + // 2. If the material differ + // 3. If the vertex formats differ (materials with different vert formats can have different shaders). + return matInst == NULL || + matInst->getMaterial() != currMatInst->getMaterial() || + matInst->getVertexFormat() != currMatInst->getVertexFormat(); +} + +/// Utility function, gets the material from the RenderInst if available, otherwise, return NULL +inline BaseMatInstance* RenderBinManager::getMaterial(RenderInst* inst) const +{ + if ( inst->type == RenderPassManager::RIT_Mesh || + inst->type == RenderPassManager::RIT_Interior || + inst->type == RenderPassManager::RIT_Decal || + inst->type == RenderPassManager::RIT_Translucent ) + return static_cast(inst)->matInst; + + return NULL; +} + +#endif // _RENDERBINMANAGER_H_ diff --git a/renderInstance/renderFormatChanger.cpp b/renderInstance/renderFormatChanger.cpp new file mode 100644 index 0000000..6a04461 --- /dev/null +++ b/renderInstance/renderFormatChanger.cpp @@ -0,0 +1,292 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderInstance/renderFormatChanger.h" +#include "console/consoleTypes.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gfxDebugEvent.h" +#include "postFx/postEffect.h" +#include "postFx/postEffectManager.h" + +extern ColorI gCanvasClearColor; + +IMPLEMENT_CONOBJECT(RenderFormatToken); + +RenderFormatToken::RenderFormatToken() : + Parent(), mFCState(FTSDisabled), + mColorFormat(GFXFormat_COUNT), + mDepthFormat(GFXFormat_COUNT), + mTargetUpdatePending(true), + mTargetChainIdx(0), + mViewportRect(Point2I::Zero, Point2I::One), + mTargetSize(Point2I::Zero), + mTargetAALevel(GFXTextureManager::AA_MATCH_BACKBUFFER), + mCopyPostEffect(NULL), + mResolvePostEffect(NULL) +{ + GFXDevice::getDeviceEventSignal().notify(this, &RenderFormatToken::_handleGFXEvent); + GFXTextureManager::addEventDelegate(this, &RenderFormatToken::_onTextureEvent); +} + +RenderFormatToken::~RenderFormatToken() +{ + GFXTextureManager::removeEventDelegate(this, &RenderFormatToken::_onTextureEvent); + GFXDevice::getDeviceEventSignal().remove(this, &RenderFormatToken::_handleGFXEvent); + + _teardownTargets(); +} + +void RenderFormatToken::process(SceneState *state, RenderPassStateBin *callingBin) +{ + switch(mFCState) + { + case FTSWaiting: + { + GFXDEBUGEVENT_SCOPE_EX(RFT_Waiting, ColorI::BLUE, avar("[%s Activate] (%s)", getName(), GFXStringTextureFormat[mColorFormat])); + mFCState = FTSActive; + + mViewportRect = GFX->getViewport(); + + // Update targets + _updateTargets(); + + // If we have a copy PostEffect then get the active backbuffer copy + // now before we swap the render targets. + GFXTexHandle curBackBuffer; + if(mCopyPostEffect.isValid()) + curBackBuffer = PFXMGR->getBackBufferTex(); + + // Push target + GFX->pushActiveRenderTarget(); + GFX->setActiveRenderTarget(mTargetChain[mTargetChainIdx]); + + // Set viewport + GFX->setViewport(mViewportRect); + + // Clear + GFX->clear(GFXClearTarget | GFXClearZBuffer | GFXClearStencil, gCanvasClearColor, 1.0f, 0); + + // Set active z target on render pass + if(mTargetDepthStencilTexture[mTargetChainIdx].isValid()) + { + if(callingBin->getParentManager()->getDepthTargetTexture() != GFXTextureTarget::sDefaultDepthStencil) + mStoredPassZTarget = callingBin->getParentManager()->getDepthTargetTexture(); + else + mStoredPassZTarget = NULL; + + callingBin->getParentManager()->setDepthTargetTexture(mTargetDepthStencilTexture[mTargetChainIdx]); + } + + // Run the PostEffect which copies data into the new target. + if ( mCopyPostEffect.isValid() ) + mCopyPostEffect->process( state, curBackBuffer, &mViewportRect ); + } + break; + + case FTSActive: + { + GFXDEBUGEVENT_SCOPE_EX(RFT_Active, ColorI::BLUE, avar("[%s Deactivate]", getName())); + mFCState = FTSComplete; + + // Pop target + AssertFatal(GFX->getActiveRenderTarget() == mTargetChain[mTargetChainIdx], "Render target stack went wrong somewhere"); + mTargetChain[mTargetChainIdx]->resolve(); + GFX->popActiveRenderTarget(); + + // This is the GFX viewport when we were first processed. + GFX->setViewport(mViewportRect); + + // Restore active z-target + if(mTargetDepthStencilTexture[mTargetChainIdx].isValid()) + { + callingBin->getParentManager()->setDepthTargetTexture(mStoredPassZTarget.getPointer()); + mStoredPassZTarget = NULL; + } + + // Run the PostEffect which copies data to the backbuffer + if(mResolvePostEffect.isValid()) + { + // Need to create a texhandle here, since inOutTex gets assigned during process() + GFXTexHandle inOutTex = mTargetColorTexture[mTargetChainIdx]; + mResolvePostEffect->process( state, inOutTex, &mViewportRect ); + } + } + break; + + case FTSComplete: + AssertFatal(false, "process() called on a RenderFormatToken which was already complete."); + // fall through + case FTSDisabled: + break; + } +} + +void RenderFormatToken::reset() +{ + AssertFatal(mFCState != FTSActive, "RenderFormatToken still active during reset()!"); + if(mFCState != FTSDisabled) + mFCState = FTSWaiting; +} + +void RenderFormatToken::_updateTargets() +{ + if ( GFX->getActiveRenderTarget() == NULL ) + return; + + const Point2I &rtSize = GFX->getActiveRenderTarget()->getSize(); + + if ( rtSize.x <= mTargetSize.x && + rtSize.y <= mTargetSize.y && + !mTargetUpdatePending ) + return; + + mTargetSize = rtSize; + mTargetUpdatePending = false; + mTargetChainIdx = 0; + + for( U32 i = 0; i < TargetChainLength; i++ ) + { + if( !mTargetChain[i] ) + mTargetChain[i] = GFX->allocRenderToTextureTarget(); + + // Update color target + if(mColorFormat != GFXFormat_COUNT) + { + mTargetColorTexture[i].set( rtSize.x, rtSize.y, mColorFormat, + &GFXDefaultRenderTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ), + 1, mTargetAALevel ); + mTargetChain[i]->attachTexture( GFXTextureTarget::Color0, mTargetColorTexture[i] ); + } + + mTargetChain[i]->attachTexture( GFXTextureTarget::Color0, mTargetColorTexture[i] ); + + + // Update depth target + if(mDepthFormat != GFXFormat_COUNT) + { + mTargetDepthStencilTexture[i].set( rtSize.x, rtSize.y, mDepthFormat, + &GFXDefaultZTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ), + 1, mTargetAALevel ); + } + + mTargetChain[i]->attachTexture( GFXTextureTarget::DepthStencil, mTargetDepthStencilTexture[i] ); + } +} + +void RenderFormatToken::_teardownTargets() +{ + for(int i = 0; i < TargetChainLength; i++) + { + mTargetColorTexture[i] = NULL; + mTargetDepthStencilTexture[i] = NULL; + mTargetChain[i] = NULL; + } +} + +bool RenderFormatToken::_setFmt(void* obj, const char* data) +{ + // Flag update pending + reinterpret_cast(obj)->mTargetUpdatePending = true; + + // Allow console system to assign value + return true; +} + +void RenderFormatToken::enable( bool enabled /*= true*/ ) +{ + AssertFatal(mFCState != FTSActive, "RenderFormatToken is active, cannot change state now!"); + + if(enabled) + mFCState = FTSWaiting; + else + mFCState = FTSDisabled; +} + +bool RenderFormatToken::isEnabled() const +{ + return (mFCState != FTSDisabled); +} + +void RenderFormatToken::initPersistFields() +{ + addProtectedField("format", TypeEnum, Offset(mColorFormat, RenderFormatToken), + &_setFmt, &defaultProtectedGetFn, 1, &gTextureFormatEnumTable, + "Sets the color buffer format for this token."); + + addProtectedField("depthFormat", TypeEnum, Offset(mDepthFormat, RenderFormatToken), + &_setFmt, &defaultProtectedGetFn, 1, &gTextureFormatEnumTable, + "Sets the depth/stencil buffer format for this token."); + + addField("copyEffect", TypeSimObjectPtr, Offset(mCopyPostEffect, RenderFormatToken), + "This PostEffect will be run when the render target is changed to the format specified " + "by this token. It is used to copy/format data into the token rendertarget"); + + addField("resolveEffect", TypeSimObjectPtr, Offset(mResolvePostEffect, RenderFormatToken), + "This PostEffect will be run when the render target is changed back to the format " + "active prior to this token. It is used to copy/format data from the token rendertarget to the backbuffer."); + + addField("aaLevel", TypeS32, Offset(mTargetAALevel, RenderFormatToken), + "Anti-ailiasing level for the this token. 0 disables, -1 uses adapter default."); + + Parent::initPersistFields(); +} + + +bool RenderFormatToken::_handleGFXEvent(GFXDevice::GFXDeviceEventType event) +{ +#define _SWAP_CHAIN_HELPER(swapchainidx, swapchainsz) { swapchainidx++; swapchainidx = swapchainidx < swapchainsz ? swapchainidx : 0; } + switch(event) + { + case GFXDevice::deStartOfFrame: + _SWAP_CHAIN_HELPER( mTargetChainIdx, TargetChainLength ); + break; + default: + break; + } +#undef _SWAP_CHAIN_HELPER + + return true; +} + +void RenderFormatToken::_onTextureEvent( GFXTexCallbackCode code ) +{ + if(code == GFXZombify) + { + _teardownTargets(); + mTargetUpdatePending = true; + } +} + +void RenderFormatToken::setupSamplerState( GFXSamplerStateDesc *desc ) const +{ + desc->addressModeU = GFXAddressClamp; + desc->addressModeV = GFXAddressClamp; + desc->minFilter = GFXTextureFilterPoint; + desc->magFilter = GFXTextureFilterPoint; + desc->mipFilter = GFXTextureFilterPoint; +} + +GFXTextureObject* RenderFormatToken::getTargetTexture( U32 mrtIndex ) const +{ + return mTargetColorTexture[mTargetChainIdx].getPointer(); +} + +bool RenderFormatToken::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if(getName()) + MatTextureTarget::registerTarget(getName(), this); + + return true; +} + +void RenderFormatToken::onRemove() +{ + MatTextureTarget::unregisterTarget(getName(), this); + + Parent::onRemove(); +} \ No newline at end of file diff --git a/renderInstance/renderFormatChanger.h b/renderInstance/renderFormatChanger.h new file mode 100644 index 0000000..7a0a321 --- /dev/null +++ b/renderInstance/renderFormatChanger.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderInstance/renderPassStateToken.h" +#include "materials/matTextureTarget.h" + +class PostEffect; + +class RenderFormatToken : public RenderPassStateToken, public MatTextureTarget +{ + typedef RenderPassStateToken Parent; + +public: + enum FormatTokenState + { + FTSDisabled, + FTSWaiting, + FTSActive, + FTSComplete, + }; + + const static U32 TargetChainLength = 1; + +protected: + FormatTokenState mFCState; + GFXFormat mColorFormat; + GFXFormat mDepthFormat; + bool mTargetUpdatePending; + U32 mTargetChainIdx; + RectI mViewportRect; + Point2I mTargetSize; + S32 mTargetAALevel; + SimObjectPtr mCopyPostEffect; + SimObjectPtr mResolvePostEffect; + + GFXTexHandle mTargetColorTexture[TargetChainLength]; + GFXTexHandle mTargetDepthStencilTexture[TargetChainLength]; + GFXTextureTargetRef mTargetChain[TargetChainLength]; + + GFXTexHandle mStoredPassZTarget; + + void _updateTargets(); + void _teardownTargets(); + + void _onTextureEvent( GFXTexCallbackCode code ); + virtual bool _handleGFXEvent(GFXDevice::GFXDeviceEventType event); + + static bool _setFmt(void* obj, const char* data); +public: + DECLARE_CONOBJECT(RenderFormatToken); + static void initPersistFields(); + virtual bool onAdd(); + virtual void onRemove(); + + RenderFormatToken(); + virtual ~RenderFormatToken(); + + virtual void process(SceneState *state, RenderPassStateBin *callingBin); + virtual void reset(); + virtual void enable(bool enabled = true); + virtual bool isEnabled() const; + + virtual GFXTextureObject* getTargetTexture( U32 mrtIndex ) const; + virtual const RectI& getTargetViewport() const { return mViewportRect; } + virtual void setupSamplerState( GFXSamplerStateDesc *desc ) const; + virtual ConditionerFeature* getTargetConditioner() const { return NULL; }; +}; \ No newline at end of file diff --git a/renderInstance/renderGlowMgr.cpp b/renderInstance/renderGlowMgr.cpp new file mode 100644 index 0000000..5b01157 --- /dev/null +++ b/renderInstance/renderGlowMgr.cpp @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderGlowMgr.h" + +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "materials/sceneData.h" +#include "materials/matInstance.h" +#include "materials/materialFeatureTypes.h" +#include "materials/processedMaterial.h" +#include "postFx/postEffect.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "math/util/matrixSet.h" + +IMPLEMENT_CONOBJECT( RenderGlowMgr ); + + +const MatInstanceHookType RenderGlowMgr::GlowMaterialHook::Type( "Glow" ); + + +RenderGlowMgr::GlowMaterialHook::GlowMaterialHook( BaseMatInstance *matInst ) + : mGlowMatInst( NULL ) +{ + mGlowMatInst = (MatInstance*)matInst->getMaterial()->createMatInstance(); + mGlowMatInst->getFeaturesDelegate().bind( &GlowMaterialHook::_overrideFeatures ); + mGlowMatInst->init( matInst->getRequestedFeatures(), + matInst->getVertexFormat() ); +} + +RenderGlowMgr::GlowMaterialHook::~GlowMaterialHook() +{ + SAFE_DELETE( mGlowMatInst ); +} + +void RenderGlowMgr::GlowMaterialHook::_overrideFeatures( ProcessedMaterial *mat, + U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ) +{ + // If this isn't a glow pass... then add the glow mask feature. + if ( mat->getMaterial() && + !mat->getMaterial()->mGlow[stageNum] ) + fd.features.addFeature( MFT_GlowMask ); + + // Don't allow fog or HDR encoding on + // the glow materials. + fd.features.removeFeature( MFT_Fog ); + fd.features.removeFeature( MFT_HDROut ); +} + +RenderGlowMgr::RenderGlowMgr() + : RenderTexTargetBinManager( RenderPassManager::RIT_Mesh, + 1.0f, + 1.0f, + GFXFormatR8G8B8A8, + Point2I( 512, 512 ) ) +{ + mTargetSizeType = WindowSize; + MatTextureTarget::registerTarget( "glowbuffer", this ); +} + +RenderGlowMgr::~RenderGlowMgr() +{ + MatTextureTarget::unregisterTarget( "glowbuffer", this ); +} + +bool RenderGlowMgr::isGlowEnabled() +{ + if ( !mGlowEffect ) + mGlowEffect = dynamic_cast( Sim::findObject( "GlowPostFx" ) ); + + return mGlowEffect && mGlowEffect->isEnabled(); +} + +RenderBinManager::AddInstResult RenderGlowMgr::addElement( RenderInst *inst ) +{ + // Skip out if we don't have the glow post + // effect enabled at this time. + if ( !isGlowEnabled() ) + return RenderBinManager::arSkipped; + + // TODO: We need to get the scene state here in a more reliable + // manner so we can skip glow in a non-diffuse render pass. + //if ( !mParentManager->getSceneManager()->getSceneState()->isDiffusePass() ) + //return RenderBinManager::arSkipped; + + BaseMatInstance* matInst = getMaterial(inst); + bool hasGlow = matInst && matInst->hasGlow(); + if ( !hasGlow ) + return RenderBinManager::arSkipped; + + internalAddElement(inst); + + return RenderBinManager::arAdded; +} + +void RenderGlowMgr::render( SceneState *state ) +{ + PROFILE_SCOPE( RenderGlowMgr_Render ); + + // Don't allow non-diffuse passes. + if ( !state->isDiffusePass() ) + return; + + GFXDEBUGEVENT_SCOPE( RenderGlowMgr_Render, ColorI::GREEN ); + + GFXTransformSaver saver; + + // Tell the superclass we're about to render, preserve contents + const bool isRenderingToTarget = _onPreRender( state, true ); + + // Clear all the buffers to black. + GFX->clear( GFXClearTarget, ColorI::BLACK, 1.0f, 0); + + // Restore transforms + MatrixSet &matrixSet = getParentManager()->getMatrixSet(); + matrixSet.restoreSceneViewProjection(); + + // init loop data + SceneGraphData sgData; + U32 binSize = mElementList.size(); + + for( U32 j=0; j(mElementList[j].inst); + + setupSGData( ri, sgData ); + sgData.binType = SceneGraphData::GlowBin; + + BaseMatInstance *mat = ri->matInst; + GlowMaterialHook *hook = mat->getHook(); + if ( !hook ) + { + hook = new GlowMaterialHook( ri->matInst ); + ri->matInst->addHook( hook ); + } + BaseMatInstance *glowMat = hook->getMatInstance(); + + U32 matListEnd = j; + + while( glowMat && glowMat->setupPass( state, sgData ) ) + { + U32 a; + for( a=j; a(mElementList[a].inst); + + if (newPassNeeded(mat, passRI)) + break; + + matrixSet.setWorld(*passRI->objectToWorld); + matrixSet.setView(*passRI->worldToCamera); + matrixSet.setProjection(*passRI->projection); + glowMat->setTransforms(matrixSet, state); + + glowMat->setSceneInfo(state, sgData); + glowMat->setBuffers(passRI->vertBuff, passRI->primBuff); + + if ( passRI->prim ) + GFX->drawPrimitive( *passRI->prim ); + else + GFX->drawPrimitive( passRI->primBuffIndex ); + } + matListEnd = a; + } + + // force increment if none happened, otherwise go to end of batch + j = ( j == matListEnd ) ? j+1 : matListEnd; + } + + // Finish up. + if ( isRenderingToTarget ) + _onPostRender(); +} diff --git a/renderInstance/renderGlowMgr.h b/renderInstance/renderGlowMgr.h new file mode 100644 index 0000000..025762a --- /dev/null +++ b/renderInstance/renderGlowMgr.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RENDERGLOWMGR_H_ +#define _RENDERGLOWMGR_H_ + +#ifndef _TEXTARGETBIN_MGR_H_ +#include "renderInstance/renderTexTargetBinManager.h" +#endif + + +class PostEffect; + + +/// +class RenderGlowMgr : public RenderTexTargetBinManager +{ + typedef RenderTexTargetBinManager Parent; + +public: + + RenderGlowMgr(); + virtual ~RenderGlowMgr(); + + /// Returns true if the glow post effect is + /// enabled and the glow buffer should be updated. + bool isGlowEnabled(); + + // RenderBinManager + virtual RenderBinManager::AddInstResult addElement( RenderInst *inst ); + virtual void render( SceneState *state ); + + // ConsoleObject + DECLARE_CONOBJECT( RenderGlowMgr ); + +protected: + + class GlowMaterialHook : public MatInstanceHook + { + public: + + GlowMaterialHook( BaseMatInstance *matInst ); + virtual ~GlowMaterialHook(); + + virtual BaseMatInstance *getMatInstance() { return mGlowMatInst; } + + virtual const MatInstanceHookType& getType() const { return Type; } + + /// Our material hook type. + static const MatInstanceHookType Type; + + protected: + + static void _overrideFeatures( ProcessedMaterial *mat, + U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ); + + BaseMatInstance *mGlowMatInst; + }; + + SimObjectPtr mGlowEffect; + +}; + + +#endif // _RENDERGLOWMGR_H_ diff --git a/renderInstance/renderImposterMgr.cpp b/renderInstance/renderImposterMgr.cpp new file mode 100644 index 0000000..1d29fb9 --- /dev/null +++ b/renderInstance/renderImposterMgr.cpp @@ -0,0 +1,400 @@ +//----------------------------------------------------------------------------- +// Torque Forest Kit +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderImposterMgr.h" + +#include "sceneGraph/sceneGraph.h" +#include "T3D/gameConnection.h" +#include "ts/tsLastDetail.h" +#include "materials/shaderData.h" +#include "lighting/lightManager.h" +#include "lighting/lightInfo.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxDebugEvent.h" +#include "renderInstance/renderPrePassMgr.h" +#include "gfx/gfxTransformSaver.h" +#include "console/consoleTypes.h" +#include "gfx/util/screenspace.h" + +GFXImplementVertexFormat( ImposterVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float4 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float3, 0 ); + addElement( GFXSemantic::TEXCOORD, GFXDeclType_Float4, 1 ); +}; + +const RenderInstType RenderImposterMgr::RIT_Imposter("Imposter"); + + +U32 RenderImposterMgr::smRendered = 0.0f; +U32 RenderImposterMgr::smBatches = 0.0f; +U32 RenderImposterMgr::smDrawCalls = 0.0f; +U32 RenderImposterMgr::smPolyCount = 0.0f; +U32 RenderImposterMgr::smRTChanges = 0.0f; + + +IMPLEMENT_CONOBJECT(RenderImposterMgr); + +RenderImposterMgr::RenderImposterMgr() + : RenderBinManager( RenderImposterMgr::RIT_Imposter, 1.0f, 1.0f ), + mImposterBatchSize( 250 ) +{ + RenderPrePassMgr::getRenderSignal().notify( this, &RenderImposterMgr::_renderPrePass ); +} + +RenderImposterMgr::RenderImposterMgr( F32 renderOrder, F32 processAddOrder ) + : RenderBinManager( RenderImposterMgr::RIT_Imposter, renderOrder, processAddOrder ), + mImposterBatchSize( 250 ) +{ + RenderPrePassMgr::getRenderSignal().notify( this, &RenderImposterMgr::_renderPrePass ); +} + +void RenderImposterMgr::initPersistFields() +{ + Con::addVariable( "$ImposterStats::rendered", TypeS32, &smRendered ); + Con::addVariable( "$ImposterStats::batches", TypeS32, &smBatches ); + Con::addVariable( "$ImposterStats::drawCalls", TypeS32, &smDrawCalls ); + Con::addVariable( "$ImposterStats::polyCount", TypeS32, &smPolyCount ); + Con::addVariable( "$ImposterStats::rtChanges", TypeS32, &smRTChanges ); + + Parent::initPersistFields(); +} + +RenderImposterMgr::~RenderImposterMgr() +{ + RenderPrePassMgr::getRenderSignal().remove( this, &RenderImposterMgr::_renderPrePass ); + + mVB = NULL; + mIB = NULL; +} + +RenderImposterMgr::ShaderState::ShaderState() + : mShader( NULL ) +{ + LightManager::smActivateSignal.notify( this, &ShaderState::_onLMActivate ); +} + +RenderImposterMgr::ShaderState::~ShaderState() +{ + LightManager::smActivateSignal.remove( this, &ShaderState::_onLMActivate ); +} + +bool RenderImposterMgr::ShaderState::init( const String &shaderName, + const GFXStateBlockDesc *desc ) +{ + ShaderData *shaderData; + if ( !Sim::findObject( shaderName, shaderData ) ) + { + Con::warnf( "TSImposterRenderMgr - failed to locate shader '%s'!", shaderName.c_str() ); + return false; + } + + // We're adding both the lightinfo uncondition and the + // prepass conditioner to the shader... we usually only + // use one of them, but the extra macros doesn't hurt. + + Vector macros; + mLightTarget = MatTextureTarget::findTargetByName( "lightinfo" ); + if ( mLightTarget ) + mLightTarget->getTargetShaderMacros( ¯os ); + + MatTextureTargetRef prepassTarget = MatTextureTarget::findTargetByName( "prepass" ); + if ( prepassTarget ) + prepassTarget->getTargetShaderMacros( ¯os ); + + // Get the shader. + mShader = shaderData->getShader( macros ); + if ( !mShader ) + return false; + + mConsts = mShader->allocConstBuffer(); + + mWorldViewProjectSC = mShader->getShaderConstHandle( "$modelViewProj" ); + mCamPosSC = mShader->getShaderConstHandle( "$camPos" ); + mCamRightSC = mShader->getShaderConstHandle( "$camRight" ); + mCamUpSC = mShader->getShaderConstHandle( "$camUp" ); + mSunDirSC = mShader->getShaderConstHandle( "$sunDir" ); + mFogDataSC = mShader->getShaderConstHandle( "$fogData" ); + mParamsSC = mShader->getShaderConstHandle( "$params" ); + mUVsSC = mShader->getShaderConstHandle( "$uvs" ); + mLightColorSC = mShader->getShaderConstHandle( "$lightColor" ); + mAmbientSC = mShader->getShaderConstHandle( "$ambient" ); + + mLightTexRT = mShader->getShaderConstHandle( "$lightTexRT" ); + + GFXStateBlockDesc d; + d.cullDefined = true; + d.cullMode = GFXCullNone; + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + d.samplers[1] = GFXSamplerStateDesc::getClampLinear(); + d.samplers[2] = GFXSamplerStateDesc::getClampLinear(); + + // We clip in the shader! + //d.alphaDefined = true; + //d.alphaTestEnable = true; + //d.alphaTestRef = 84; + //d.alphaTestFunc = GFXCmpGreater; + + d.zDefined = true; + d.zEnable = true; + d.zWriteEnable = true; + + if ( desc ) + d.addDesc( *desc ); + + mSB = GFX->createStateBlock(d); + return true; +} + +void RenderImposterMgr::render( SceneState *state ) +{ + PROFILE_SCOPE( RenderImposterMgr_Render ); + + if ( !mElementList.size() || + ( !mDiffuseShaderState.mShader && + !mDiffuseShaderState.init( "TSImposterShaderData", NULL ) ) ) + return; + + GFXDEBUGEVENT_SCOPE( RenderImposterMgr_Render, ColorI::RED ); + + _innerRender( state, mDiffuseShaderState ); +} + +void RenderImposterMgr::sort() +{ + Parent::sort(); + + // Sort is called before rendering, so this is a + // better place to clear stats than clear(). + smRendered = 0.0f; + smBatches = 0.0f; + smDrawCalls = 0.0f; + smPolyCount = 0.0f; + smRTChanges = 0.0f; +} + +void RenderImposterMgr::_renderPrePass( const SceneState *state, RenderPrePassMgr *prePassBin, bool startPrePass ) +{ + PROFILE_SCOPE( RenderImposterMgr_RenderPrePass ); + + if ( !mElementList.size() || !startPrePass || + ( !mPrePassShaderState.mShader && + !mPrePassShaderState.init( "TSImposterPrePassShaderData", + &prePassBin->getOpaqueStenciWriteDesc() ) ) ) + return; + + GFXDEBUGEVENT_SCOPE( RenderImposterMgr_RenderPrePass, ColorI::RED ); + + _innerRender( state, mPrePassShaderState ); +} + +void RenderImposterMgr::_innerRender( const SceneState *state, ShaderState &shaderState ) +{ + PROFILE_SCOPE( RenderImposterMgr_InnerRender ); + + // Capture the GFX stats for this render. + GFXDeviceStatistics stats; + stats.start( GFX->getDeviceStatistics() ); + + GFXTransformSaver saver; + + // Init the shader. + GFX->setShader( shaderState.mShader ); + GFX->setShaderConstBuffer( shaderState.mConsts ); + GFX->setStateBlock( shaderState.mSB ); + + // Set the projection and world transform info. + MatrixF proj = GFX->getProjectionMatrix() * GFX->getWorldMatrix(); + shaderState.mConsts->set( shaderState.mWorldViewProjectSC, proj ); + + if ( shaderState.mSunDirSC || + shaderState.mLightColorSC || + shaderState.mAmbientSC ) + { + // Pass the lighting consts. + const LightInfo *sunlight = state->getLightManager()->getSpecialLight( LightManager::slSunLightType ); + VectorF sunDir( sunlight->getDirection() ); + sunDir.normalize(); + + shaderState.mConsts->set( shaderState.mSunDirSC, sunDir ); + shaderState.mConsts->set( shaderState.mLightColorSC, sunlight->getColor() ); + shaderState.mConsts->set( shaderState.mAmbientSC, sunlight->getAmbient() ); + } + + // Get the data we need from the camera matrix. + const MatrixF &camMat = state->getCameraTransform(); + Point3F camPos; + VectorF camRight, camUp, camDir; + camMat.getColumn( 0, &camRight ); + camMat.getColumn( 1, &camDir ); + camMat.getColumn( 2, &camUp ); + camMat.getColumn( 3, &camPos ); + shaderState.mConsts->set( shaderState.mCamPosSC, camPos ); + shaderState.mConsts->set( shaderState.mCamRightSC, camRight ); + shaderState.mConsts->set( shaderState.mCamUpSC, camUp ); + + if ( shaderState.mLightTexRT && shaderState.mLightTarget ) + { + GFXTextureObject *texObject = shaderState.mLightTarget->getTargetTexture( 0 ); + GFX->setTexture( 2, texObject ); + + // TODO: This normally shouldn't be NULL, but can be on the + // first render... not sure why... we should investigate and + // fix that rather than protect against it. + if ( texObject ) + { + const Point3I &targetSz = texObject->getSize(); + const RectI &targetVp = shaderState.mLightTarget->getTargetViewport(); + Point4F rtParams; + + ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams); + + shaderState.mConsts->set( shaderState.mLightTexRT, rtParams ); + } + } + + // Setup a fairly large dynamic vb to hold a bunch of imposters. + if ( !mVB.isValid() ) + { + // Setup the vb to hold a bunch of imposters at once. + mVB.set( GFX, mImposterBatchSize * 4, GFXBufferTypeDynamic ); + + // Setup a static index buffer for rendering. + mIB.set( GFX, mImposterBatchSize * 6, 0, GFXBufferTypeStatic ); + U16 *idxBuff; + mIB.lock(&idxBuff, NULL, NULL, NULL); + for ( U32 i=0; i < mImposterBatchSize; i++ ) + { + // + // The vertex pattern in the VB for each + // imposter is as follows... + // + // 0----1 + // |\ | + // | \ | + // | \ | + // | \| + // 3----2 + // + // We setup the index order below to ensure + // sequental, cache friendly, access. + // + U32 offset = i * 4; + idxBuff[i*6+0] = 0 + offset; + idxBuff[i*6+1] = 1 + offset; + idxBuff[i*6+2] = 2 + offset; + idxBuff[i*6+3] = 2 + offset; + idxBuff[i*6+4] = 3 + offset; + idxBuff[i*6+5] = 0 + offset; + } + mIB.unlock(); + } + + // Set the buffers here once. + GFX->setPrimitiveBuffer( mIB ); + GFX->setVertexBuffer( mVB ); + + // Batch up the imposters into the buffer. These + // are already sorted by texture, to minimize switches + // so just batch them up and render as they come. + + ImposterVertex* vertPtr = NULL; + U32 vertCount = 0; + F32 halfSize, fade, scale; + Point3F center; + QuatF rotQuat; + + const U32 binSize = mElementList.size(); + for( U32 i=0; i < binSize; ) + { + ImposterRenderInst *ri = static_cast( mElementList[i].inst ); + + TSLastDetail* detail = ri->detail; + + // Setup the textures. + GFX->setTexture( 0, detail->getTextureMap() ); + GFX->setTexture( 1, detail->getNormalMap() ); + + // Setup the constants for this batch. + Point4F params( (detail->getNumPolarSteps() * 2) + 1, detail->getNumEquatorSteps(), detail->getPolarAngle(), detail->getIncludePoles() ); + shaderState.mConsts->set( shaderState.mParamsSC, params ); + + U32 uvCount = getMin( detail->getTextureUVs().size(), 64 ); + AlignedArray rectData( uvCount, sizeof( Point4F ), (U8*)detail->getTextureUVs().address(), false ); + shaderState.mConsts->set( shaderState.mUVsSC, rectData ); + + vertPtr = mVB.lock(); + vertCount = 0; + + for ( ; i < binSize; i++ ) + { + ri = static_cast( mElementList[i].inst ); + + // Stop the loop if the detail changed. + if ( ri->detail != detail ) + break; + + ++smRendered; + + // If we're out of vb space then draw what we got. + if ( vertCount + 4 >= mVB->mNumVerts ) + { + mVB.unlock(); + GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2 ); + vertPtr = mVB.lock(); + vertCount = 0; + } + + center = ri->center; + halfSize = ri->halfSize; + fade = ri->alpha; + scale = ri->scale; + rotQuat = ri->rotQuat; + + // Fill in the points for this instance. + vertPtr->center = center; + vertPtr->center.w = 0; + vertPtr->miscParams.set( halfSize, fade, scale ); + vertPtr->rotQuat.set( rotQuat.x, rotQuat.y, rotQuat.z, rotQuat.w ); + vertPtr++; + + vertPtr->center = center; + vertPtr->center.w = 1; + vertPtr->miscParams.set( halfSize, fade, scale ); + vertPtr->rotQuat.set( rotQuat.x, rotQuat.y, rotQuat.z, rotQuat.w ); + vertPtr++; + + vertPtr->center = center; + vertPtr->center.w = 2; + vertPtr->miscParams.set( halfSize, fade, scale ); + vertPtr->rotQuat.set( rotQuat.x, rotQuat.y, rotQuat.z, rotQuat.w ); + vertPtr++; + + vertPtr->center = center; + vertPtr->center.w = 3; + vertPtr->miscParams.set( halfSize, fade, scale ); + vertPtr->rotQuat.set( rotQuat.x, rotQuat.y, rotQuat.z, rotQuat.w ); + vertPtr++; + + vertCount += 4; + } + + // Any remainder to dump? + if ( vertCount > 0 ) + { + smBatches++; + mVB.unlock(); + GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, vertCount, 0, vertCount / 2 ); + } + } + + // Capture the GFX stats for this render. + stats.end( GFX->getDeviceStatistics() ); + smDrawCalls += stats.mDrawCalls; + smPolyCount += stats.mPolyCount; + smRTChanges += stats.mRenderTargetChanges; +} diff --git a/renderInstance/renderImposterMgr.h b/renderInstance/renderImposterMgr.h new file mode 100644 index 0000000..e843a1f --- /dev/null +++ b/renderInstance/renderImposterMgr.h @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------------- +// Torque Forest Kit +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#ifndef _IMPOSTERRENDERMGR_H_ +#define _IMPOSTERRENDERMGR_H_ + +#ifndef _RENDERBINMANAGER_H_ +#include "renderInstance/renderBinManager.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif + +class TSLastDetail; +class GFXTextureObject; +class RenderPrePassMgr; +struct ImposterRenderInst; + + +/// This is the vertex definition used for TSLastDetail +/// based imposter billboard geometry. +GFXDeclareVertexFormat( ImposterVertex ) +{ + /// .xyz = imposter center + /// .w = billboard corner index + Point4F center; + + /// .x = half size + /// .y = alpha fade out + /// .z = object scale + Point3F miscParams; + + /// .xyzw = object orientation quaternion + Point4F rotQuat; +}; + + +/// This is a special render manager for processing single +/// billboard imposters typically generated by the tsLastDetail +/// class. It tries to render them in large batches with as +/// few state changes as possible. For an example of use see +/// TSLastDetail::render(). +class RenderImposterMgr : public RenderBinManager +{ +protected: + + typedef RenderBinManager Parent; + + const U32 mImposterBatchSize; + + static U32 smRendered; + static U32 smBatches; + static U32 smDrawCalls; + static U32 smPolyCount; + static U32 smRTChanges; + + struct ShaderState + { + ShaderState(); + ~ShaderState(); + + bool init( const String &shaderName, const GFXStateBlockDesc *desc ); + + void _onLMActivate( const char*, bool activate ) + { + if ( activate && mShader ) + mShader = NULL; + } + + GFXShaderRef mShader; + + MatTextureTargetRef mLightTarget; + + GFXStateBlockRef mSB; + + GFXShaderConstBufferRef mConsts; + + GFXShaderConstHandle *mWorldViewProjectSC; + GFXShaderConstHandle *mCamPosSC; + GFXShaderConstHandle *mCamRightSC; + GFXShaderConstHandle *mCamUpSC; + GFXShaderConstHandle *mSunDirSC; + GFXShaderConstHandle *mFogDataSC; + GFXShaderConstHandle *mParamsSC; + GFXShaderConstHandle *mUVsSC; + GFXShaderConstHandle *mLightColorSC; + GFXShaderConstHandle *mAmbientSC; + GFXShaderConstHandle *mLightTexRT; + }; + + ShaderState mPrePassShaderState; + + ShaderState mDiffuseShaderState; + + GFXPrimitiveBufferHandle mIB; + + GFXVertexBufferHandle mVB; + + void _innerRender( const SceneState *state, ShaderState &shaderState ); + + void _renderPrePass( const SceneState *state, RenderPrePassMgr *prePassBin, bool startPrePass ); + +public: + + static const RenderInstType RIT_Imposter; + + RenderImposterMgr(); + RenderImposterMgr( F32 renderOrder, F32 processAddOrder ); + virtual ~RenderImposterMgr(); + + // ConsoleObject + DECLARE_CONOBJECT(RenderImposterMgr); + static void initPersistFields(); + + // RenderBinManager + virtual void render( SceneState *state ); + virtual void sort(); +}; + + +/// This is a render instance for a TSLastDetail based imposter. +/// @see TSLastDetail +/// @see RenderImposterMgr +struct ImposterRenderInst : public RenderInst +{ + /// The detail object for this imposter. + TSLastDetail *detail; + + /// The world space center point of the object which + /// is used as the center of the imposter. + Point3F center; + + /// The orientation of the object being impostered + /// stored in a quaternion. + QuatF rotQuat; + + /// The object scale to apply to the imposter. + F32 scale; + + /// The half size of the imposter billboard. + F32 halfSize; + + /// The alpha fade amount for this imposter. + F32 alpha; + + /// Helper for setting this instance to a default state. + void clear() + { + dMemset( this, 0, sizeof( ImposterRenderInst ) ); + type = RenderImposterMgr::RIT_Imposter; + } +}; + +#endif // _TSIMPOSTERRENDERMGR_H_ diff --git a/renderInstance/renderMeshMgr.cpp b/renderInstance/renderMeshMgr.cpp new file mode 100644 index 0000000..cec9dea --- /dev/null +++ b/renderInstance/renderMeshMgr.cpp @@ -0,0 +1,240 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderMeshMgr.h" + +#include "console/consoleTypes.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "materials/sceneData.h" +#include "materials/processedMaterial.h" +#include "materials/materialManager.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxDebugEvent.h" +#include "math/util/matrixSet.h" + +//************************************************************************** +// RenderMeshMgr +//************************************************************************** +IMPLEMENT_CONOBJECT(RenderMeshMgr); + +RenderMeshMgr::RenderMeshMgr() +: RenderBinManager(RenderPassManager::RIT_Mesh, 1.0f, 1.0f) +{ +} + +RenderMeshMgr::RenderMeshMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder) + : RenderBinManager(riType, renderOrder, processAddOrder) +{ +} + +void RenderMeshMgr::init() +{ + GFXStateBlockDesc d; + + d.cullDefined = true; + d.cullMode = GFXCullCCW; + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getWrapLinear(); + + mNormalSB = GFX->createStateBlock(d); + + d.cullMode = GFXCullCW; + mReflectSB = GFX->createStateBlock(d); +} + +void RenderMeshMgr::initPersistFields() +{ + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// add element +//----------------------------------------------------------------------------- +RenderBinManager::AddInstResult RenderMeshMgr::addElement( RenderInst *inst ) +{ + if (inst->type != mRenderInstType) + return RenderBinManager::arSkipped; + + // If this instance is translucent handle it in RenderTranslucentMgr + if (inst->translucentSort) + return RenderBinManager::arSkipped; + + AssertFatal( inst->defaultKey != 0, "RenderMeshMgr::addElement() - Got null sort key... did you forget to set it?" ); + + internalAddElement(inst); + + return RenderBinManager::arAdded; +} + +//----------------------------------------------------------------------------- +// render +//----------------------------------------------------------------------------- +void RenderMeshMgr::render(SceneState * state) +{ + PROFILE_SCOPE(RenderMeshMgr_render); + + // Early out if nothing to draw. + if(!mElementList.size()) + return; + + + GFXDEBUGEVENT_SCOPE( RenderMeshMgr_Render, ColorI::GREEN ); + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + // Restore transforms + MatrixSet &matrixSet = getParentManager()->getMatrixSet(); + matrixSet.restoreSceneViewProjection(); + + // init loop data + GFXVertexBuffer * lastVB = NULL; + GFXPrimitiveBuffer * lastPB = NULL; + + GFXTextureObject *lastLM = NULL; + GFXCubemap *lastCubemap = NULL; + GFXTextureObject *lastReflectTex = NULL; + GFXTextureObject *lastMiscTex = NULL; + + SceneGraphData sgData; + U32 binSize = mElementList.size(); + + for( U32 j=0; j(mElementList[j].inst); + + setupSGData( ri, sgData ); + BaseMatInstance *mat = ri->matInst; + + // .ifl? + if( !mat ) + { + // Deal with reflect pass. + if ( state->isReflectPass() ) + GFX->setStateBlock(mReflectSB); + else + GFX->setStateBlock(mNormalSB); + + GFX->pushWorldMatrix(); + GFX->setWorldMatrix(*ri->objectToWorld); + + GFX->setTexture( 0, ri->miscTex ); + GFX->setPrimitiveBuffer(*ri->primBuff ); + GFX->setVertexBuffer(*ri->vertBuff ); + GFX->disableShaders(); + GFX->setupGenericShaders( GFXDevice::GSModColorTexture ); + GFX->drawPrimitive( ri->primBuffIndex ); + + GFX->popWorldMatrix(); + + lastVB = NULL; // no longer valid, null it + lastPB = NULL; // no longer valid, null it + + j++; + continue; + } + + // If we have an override delegate then give it a + // chance to swap the material with another. + if ( mMatOverrideDelegate ) + { + mat = mMatOverrideDelegate( mat ); + if ( !mat ) + { + j++; + continue; + } + } + + if( !mat ) + mat = MATMGR->getWarningMatInstance(); + + + U32 matListEnd = j; + lastMiscTex = sgData.miscTex; + + while( mat && mat->setupPass(state, sgData ) ) + { + U32 a; + for( a=j; a(mElementList[a].inst); + + if (newPassNeeded(mat, passRI) || lastMiscTex != passRI->miscTex ) + { + lastLM = NULL; // pointer no longer valid after setupPass() call + break; + } + + matrixSet.setWorld(*passRI->objectToWorld); + matrixSet.setView(*passRI->worldToCamera); + matrixSet.setProjection(*passRI->projection); + mat->setTransforms(matrixSet, state); + + setupSGData( passRI, sgData ); + mat->setSceneInfo(state, sgData); + + mat->setBuffers(passRI->vertBuff, passRI->primBuff); + + // TODO: This could proably be done in a cleaner way. + // + // This section of code is dangerous, it overwrites the + // lightmap values in sgData. This could be a problem when multiple + // render instances use the same multi-pass material. When + // the first pass is done, setupPass() is called again on + // the material, but the lightmap data has been changed in + // sgData to the lightmaps in the last renderInstance rendered. + + // This section sets the lightmap data for the current batch. + // For the first iteration, it sets the same lightmap data, + // however the redundancy will be caught by GFXDevice and not + // actually sent to the card. This is done for simplicity given + // the possible condition mentioned above. Better to set always + // than to get bogged down into special case detection. + //------------------------------------- + bool dirty = false; + + // set the lightmaps if different + if( passRI->lightmap && passRI->lightmap != lastLM ) + { + sgData.lightmap = passRI->lightmap; + lastLM = passRI->lightmap; + dirty = true; + } + + // set the cubemap if different. + if ( passRI->cubemap != lastCubemap ) + { + sgData.cubemap = passRI->cubemap; + lastCubemap = passRI->cubemap; + dirty = true; + } + + if ( passRI->reflectTex != lastReflectTex ) + { + sgData.reflectTex = passRI->reflectTex; + lastReflectTex = passRI->reflectTex; + dirty = true; + } + + if ( dirty ) + mat->setTextureStages( state, sgData ); + + if ( passRI->prim ) + GFX->drawPrimitive( *passRI->prim ); + else + GFX->drawPrimitive( passRI->primBuffIndex ); + } + + matListEnd = a; + } + + // force increment if none happened, otherwise go to end of batch + j = ( j == matListEnd ) ? j+1 : matListEnd; + } +} + diff --git a/renderInstance/renderMeshMgr.h b/renderInstance/renderMeshMgr.h new file mode 100644 index 0000000..2418088 --- /dev/null +++ b/renderInstance/renderMeshMgr.h @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _RENDERMESHMGR_H_ +#define _RENDERMESHMGR_H_ + +#ifndef _RENDERBINMANAGER_H_ +#include "renderInstance/renderBinManager.h" +#endif + +//************************************************************************** +// RenderMeshMgr +//************************************************************************** +class RenderMeshMgr : public RenderBinManager +{ + typedef RenderBinManager Parent; +public: + RenderMeshMgr(); + RenderMeshMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder); + + // RenderBinManager interface + virtual void init(); + virtual void render(SceneState * state); + AddInstResult addElement( RenderInst *inst ); + + // ConsoleObject interface + static void initPersistFields(); + DECLARE_CONOBJECT(RenderMeshMgr); +protected: + GFXStateBlockRef mNormalSB; + GFXStateBlockRef mReflectSB; + + void construct(); +}; + +#endif // _RENDERMESHMGR_H_ diff --git a/renderInstance/renderObjectMgr.cpp b/renderInstance/renderObjectMgr.cpp new file mode 100644 index 0000000..30a4e8b --- /dev/null +++ b/renderInstance/renderObjectMgr.cpp @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderObjectMgr.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneObject.h" + +IMPLEMENT_CONOBJECT(RenderObjectMgr); + +RenderObjectMgr::RenderObjectMgr() +: RenderBinManager(RenderPassManager::RIT_Object, 1.0f, 1.0f) +{ + mOverrideMat = NULL; +} + +RenderObjectMgr::RenderObjectMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder) + : RenderBinManager(riType, renderOrder, processAddOrder) +{ + mOverrideMat = NULL; +} + +void RenderObjectMgr::initPersistFields() +{ + Parent::initPersistFields(); +} + +void RenderObjectMgr::setOverrideMaterial(BaseMatInstance* overrideMat) +{ + mOverrideMat = overrideMat; +} + +//----------------------------------------------------------------------------- +// render objects +//----------------------------------------------------------------------------- +void RenderObjectMgr::render( SceneState *state ) +{ + PROFILE_SCOPE(RenderObjectMgr_render); + + // Early out if nothing to draw. + if(!mElementList.size()) + return; + + for( U32 i=0; i(mElementList[i].inst); + if ( ri->renderDelegate ) + ri->renderDelegate( ri, state, mOverrideMat ); + } +} \ No newline at end of file diff --git a/renderInstance/renderObjectMgr.h b/renderInstance/renderObjectMgr.h new file mode 100644 index 0000000..bd04bf6 --- /dev/null +++ b/renderInstance/renderObjectMgr.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _RENDEROBJECTMGR_H_ +#define _RENDEROBJECTMGR_H_ + +#ifndef _RENDERBINMANAGER_H_ +#include "renderInstance/renderBinManager.h" +#endif + +//************************************************************************** +// RenderObjectMgr +//************************************************************************** +class RenderObjectMgr : public RenderBinManager +{ + typedef RenderBinManager Parent; +public: + RenderObjectMgr(); + RenderObjectMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder); + + virtual void setOverrideMaterial(BaseMatInstance* overrideMat); + + // RenderBinMgr + virtual void render(SceneState * state); + + // ConsoleObject + static void initPersistFields(); + DECLARE_CONOBJECT(RenderObjectMgr); +protected: + BaseMatInstance* mOverrideMat; +}; + +#endif // _RENDEROBJECTMGR_H_ \ No newline at end of file diff --git a/renderInstance/renderOcclusionMgr.cpp b/renderInstance/renderOcclusionMgr.cpp new file mode 100644 index 0000000..7beb2fa --- /dev/null +++ b/renderInstance/renderOcclusionMgr.cpp @@ -0,0 +1,218 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderOcclusionMgr.h" +#include "console/consoleTypes.h" +#include "sceneGraph/sceneObject.h" +#include "gfx/gfxOcclusionQuery.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxTransformSaver.h" +#include "math/util/sphereMesh.h" + +IMPLEMENT_CONOBJECT(RenderOcclusionMgr); + +bool RenderOcclusionMgr::smDebugRender = false; + +RenderOcclusionMgr::RenderOcclusionMgr() +: RenderBinManager(RenderPassManager::RIT_Occluder, 1.0f, 1.0f) +{ + mOverrideMat = NULL; + mSpherePrimCount = 0; +} + +RenderOcclusionMgr::RenderOcclusionMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder) +: RenderBinManager(riType, renderOrder, processAddOrder) +{ + mOverrideMat = NULL; +} + +static const Point3F cubePoints[8] = +{ + Point3F(-0.5, -0.5, -0.5), Point3F(-0.5, -0.5, 0.5), Point3F(-0.5, 0.5, -0.5), Point3F(-0.5, 0.5, 0.5), + Point3F( 0.5, -0.5, -0.5), Point3F( 0.5, -0.5, 0.5), Point3F( 0.5, 0.5, -0.5), Point3F( 0.5, 0.5, 0.5) +}; + +static const U32 cubeFaces[6][4] = +{ + { 0, 4, 6, 2 }, { 0, 2, 3, 1 }, { 0, 1, 5, 4 }, + { 3, 2, 6, 7 }, { 7, 6, 4, 5 }, { 3, 7, 5, 1 } +}; + +void RenderOcclusionMgr::init() +{ + GFXStateBlockDesc d; + + d.setBlend( false ); + d.cullDefined = true; + d.cullMode = GFXCullCCW; + d.setZReadWrite( true, false ); + + mDebugSB = GFX->createStateBlock(d); + + d.setColorWrites( false, false, false, false ); + + mNormalSB = GFX->createStateBlock(d); + + d.setZReadWrite( false, false ); + + mTestSB = GFX->createStateBlock(d); + + mBoxBuff.set( GFX, 36, GFXBufferTypeStatic ); + GFXVertexPC *verts = mBoxBuff.lock(); + + U32 vertexIndex = 0; + U32 idx; + for(int i = 0; i < 6; i++) + { + idx = cubeFaces[i][0]; + verts[vertexIndex].point = cubePoints[idx]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + + idx = cubeFaces[i][1]; + verts[vertexIndex].point = cubePoints[idx]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + + idx = cubeFaces[i][3]; + verts[vertexIndex].point = cubePoints[idx]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + + idx = cubeFaces[i][1]; + verts[vertexIndex].point = cubePoints[idx]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + + idx = cubeFaces[i][3]; + verts[vertexIndex].point = cubePoints[idx]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + + idx = cubeFaces[i][2]; + verts[vertexIndex].point = cubePoints[idx]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + } + + mBoxBuff.unlock(); + + SphereMesh sphere; + const SphereMesh::TriangleMesh *sphereMesh = sphere.getMesh(2); + mSpherePrimCount = sphereMesh->numPoly; + mSphereBuff.set( GFX, mSpherePrimCount * 3, GFXBufferTypeStatic ); + verts = mSphereBuff.lock(); + vertexIndex = 0; + + for ( S32 i = 0; i < mSpherePrimCount; i++ ) + { + verts[vertexIndex].point = sphereMesh->poly[i].pnt[0]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + + verts[vertexIndex].point = sphereMesh->poly[i].pnt[1]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + + verts[vertexIndex].point = sphereMesh->poly[i].pnt[2]; + verts[vertexIndex].color.set( 1,0,1,1 ); + vertexIndex++; + } + mSphereBuff.unlock(); +} + +void RenderOcclusionMgr::consoleInit() +{ + Con::addVariable( "$RenderOcclusionMgr::debugRender", TypeBool, &RenderOcclusionMgr::smDebugRender ); +} + +void RenderOcclusionMgr::initPersistFields() +{ + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- +// render objects +//----------------------------------------------------------------------------- +void RenderOcclusionMgr::render( SceneState *state ) +{ + PROFILE_SCOPE(RenderOcclusionMgr_render); + + // Early out if nothing to draw. + if ( !mElementList.size() ) + return; + + if ( mNormalSB.isNull() ) + init(); + + GFX->disableShaders(); + GFX->setupGenericShaders( GFXDevice::GSColor ); + + + OccluderRenderInst *firstEl = static_cast(mElementList[0].inst); + + if ( firstEl->isSphere ) + GFX->setVertexBuffer( mSphereBuff ); + else + GFX->setVertexBuffer( mBoxBuff ); + + bool wasSphere = firstEl->isSphere; + + + for( U32 i=0; i(mElementList[i].inst); + + AssertFatal( ri->query != NULL, "RenderOcclusionMgr::render, OcclusionRenderInst has NULL GFXOcclusionQuery" ); + + if ( ri->isSphere != wasSphere ) + { + if ( ri->isSphere ) + GFX->setVertexBuffer( mSphereBuff ); + else + GFX->setVertexBuffer( mBoxBuff ); + + wasSphere = ri->isSphere; + } + + GFX->pushWorldMatrix(); + + MatrixF xfm( *ri->orientation ); + xfm.setPosition( ri->position ); + xfm.scale( ri->scale ); + + //GFXTransformSaver saver; + GFX->multWorld( xfm ); + + if ( smDebugRender ) + GFX->setStateBlock( mDebugSB ); + else + GFX->setStateBlock( mNormalSB ); + + ri->query->begin(); + + if ( wasSphere ) + GFX->drawPrimitive( GFXTriangleList, 0, mSpherePrimCount ); + else + GFX->drawPrimitive( GFXTriangleList, 0, 12 ); + + ri->query->end(); + + if ( ri->query2 ) + { + GFX->setStateBlock( mTestSB ); + + ri->query2->begin(); + + if ( wasSphere ) + GFX->drawPrimitive( GFXTriangleList, 0, mSpherePrimCount ); + else + GFX->drawPrimitive( GFXTriangleList, 0, 12 ); + + ri->query2->end(); + } + + GFX->popWorldMatrix(); + } +} \ No newline at end of file diff --git a/renderInstance/renderOcclusionMgr.h b/renderInstance/renderOcclusionMgr.h new file mode 100644 index 0000000..6653e4c --- /dev/null +++ b/renderInstance/renderOcclusionMgr.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _RENDEROCCLUSIONMGR_H_ +#define _RENDEROCCLUSIONMGR_H_ + +#ifndef _RENDERBINMANAGER_H_ +#include "renderInstance/renderBinManager.h" +#endif + +//************************************************************************** +// RenderOcclusionMgr +//************************************************************************** +class RenderOcclusionMgr : public RenderBinManager +{ + typedef RenderBinManager Parent; +public: + RenderOcclusionMgr(); + RenderOcclusionMgr(RenderInstType riType, F32 renderOrder, F32 processAddOrder); + + // RenderOcclusionMgr + virtual void init(); + virtual void render(SceneState * state); + + // ConsoleObject + static void consoleInit(); + static void initPersistFields(); + DECLARE_CONOBJECT(RenderOcclusionMgr); + +protected: + BaseMatInstance* mOverrideMat; + GFXStateBlockRef mNormalSB; + GFXStateBlockRef mTestSB; + + GFXStateBlockRef mDebugSB; + static bool smDebugRender; + + GFXVertexBufferHandle mBoxBuff; + GFXVertexBufferHandle mSphereBuff; + U32 mSpherePrimCount; +}; + +#endif // _RENDEROCCLUSIONMGR_H_ \ No newline at end of file diff --git a/renderInstance/renderParticleMgr.cpp b/renderInstance/renderParticleMgr.cpp new file mode 100644 index 0000000..d01086c --- /dev/null +++ b/renderInstance/renderParticleMgr.cpp @@ -0,0 +1,781 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderParticleMgr.h" +#include "renderInstance/renderPrePassMgr.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneObject.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "materials/shaderData.h" +#include "materials/sceneData.h" +#include "materials/matInstance.h" +#include "gfx/util/screenspace.h" +#include "gfx/gfxDrawUtil.h" +#include "collision/clippedPolyList.h" + +static const Point4F cubePoints[9] = +{ + Point4F(-0.5, -0.5, -0.5, 1.0f), Point4F(-0.5, -0.5, 0.5, 1.0f), Point4F(-0.5, 0.5, -0.5, 1.0f), Point4F(-0.5, 0.5, 0.5, 1.0f), + Point4F( 0.5, -0.5, -0.5, 1.0f), Point4F( 0.5, -0.5, 0.5, 1.0f), Point4F( 0.5, 0.5, -0.5, 1.0f), Point4F( 0.5, 0.5, 0.5, 1.0f) +}; + +GFXImplementVertexFormat( CompositeQuadVert ) +{ + addElement( GFXSemantic::COLOR, GFXDeclType_Color ); +} + +IMPLEMENT_CONOBJECT(RenderParticleMgr); + +const RenderInstType RenderParticleMgr::RIT_Particles("ParticleSystem"); + +// TODO: Replace these once they are supported via options +const bool RenderToParticleTarget = true; +const bool RenderToSingleTarget = true; + +RenderParticleMgr::RenderParticleMgr() +: Parent( RenderParticleMgr::RIT_Particles, 1.0f, 1.0f, GFXFormatR8G8B8A8, + Point2I( Parent::DefaultTargetSize, Parent::DefaultTargetSize), + RenderToParticleTarget ? Parent::DefaultTargetChainLength : 0 ), + mParticleShader( NULL ) +{ + // Render particles at 1/4 resolution + mTargetSizeType = WindowSizeScaled; + mTargetScale.set(0.25f, 0.25f); + + // We use the target chain like a texture pool, not like a swap chain + if(!RenderToSingleTarget) + setTargetChainLength(5); + else + mOffscreenSystems.setSize(1); + + LightManager::smActivateSignal.notify( this, &RenderParticleMgr::_onLMActivate ); +} + +RenderParticleMgr::~RenderParticleMgr() +{ + LightManager::smActivateSignal.remove( this, &RenderParticleMgr::_onLMActivate ); +} + +void RenderParticleMgr::setTargetChainLength( const U32 chainLength ) +{ + Parent::setTargetChainLength(chainLength); + + if(!RenderToSingleTarget) + mOffscreenSystems.setSize(chainLength); +} + +RenderBinManager::AddInstResult RenderParticleMgr::addElement( RenderInst *inst ) +{ + // Particles only + if( inst->type != RenderPassManager::RIT_Particle ) + return RenderBinManager::arSkipped; + + ParticleRenderInst *pri = reinterpret_cast(inst); + + // If this system isn't waiting for an offscreen draw, skip it + if( pri->systemState != ParticleRenderInst::AwaitingOffscreenDraw ) + return RenderBinManager::arSkipped; + + // If offscreen rendering isn't enabled, set to high-res, and skip + if(!mOffscreenRenderEnabled) + { + pri->systemState = ParticleRenderInst::AwaitingHighResDraw; + return RenderBinManager::arSkipped; + } + + // Assign a target index + RectF screenRect; + S32 chainIndex = -1; + if(RenderToSingleTarget) + { + pri->targetIndex = 0; + screenRect.point.set(-1.0f, -1.0f); + screenRect.extent.set(2.0f, 2.0f); + + mElementList.setSize(1); + } + else + { + + // If we can't fit this into the offscreen systems, skip it, it will render + // high resolution + // + // TODO: Improve this once we are grouping systems + if( mTargetChainIdx == OffscreenPoolSize ) + return RenderBinManager::arSkipped; + + // Transform bounding box into screen space + const static PlaneF planes[] = { + PlaneF(Point3F( 1.0f, 0.0f, 0.0f), Point3F(-1.0f, 0.0f, 0.0f)), + PlaneF(Point3F(-1.0f, 0.0f, 0.0f), Point3F( 1.0f, 0.0f, 0.0f)), + + PlaneF(Point3F( 0.0f, 1.0f, 0.0f), Point3F( 0.0f, -1.0f, 0.0f)), + PlaneF(Point3F( 0.0f, -1.0f, 0.0f), Point3F( 0.0f, 1.0f, 0.0f)), + + PlaneF(Point3F( 0.0f, 0.0f, 0.0f), Point3F( 0.0f, 0.0f, 1.0f)), + PlaneF(Point3F( 0.0f, 0.0f, 1.0f), Point3F( 0.0f, 0.0f, -1.0f)), + }; + const static dsize_t numPlanes = sizeof(planes) / sizeof(PlaneF); + + // Set up a clipper + ClippedPolyList screenClipper; + screenClipper.setBaseTransform(MatrixF::Identity); + screenClipper.setTransform(&MatrixF::Identity, Point3F::One); + TORQUE_UNUSED(numPlanes); + + Point4F tempPt(0.0f, 0.0f, 0.0f, 1.0f); + pri->bbModelViewProj->mul(tempPt); + tempPt = tempPt / tempPt.w; + + for(int i = 0; i < 1; i++) + { + screenClipper.mPlaneList.push_back(planes[i]); + screenClipper.mPlaneList.last() += tempPt.asPoint3F(); + } + + Box3F screenSpaceBoundingBox; + screenSpaceBoundingBox.minExtents = Point3F::Zero; + screenSpaceBoundingBox.maxExtents = Point3F::Zero; + + for(int i = 0; i < 8; i++) + { + tempPt = cubePoints[i]; + pri->bbModelViewProj->mul(tempPt); + tempPt = tempPt / tempPt.w; + + screenSpaceBoundingBox.maxExtents.setMax(tempPt.asPoint3F()); + screenSpaceBoundingBox.minExtents.setMin(tempPt.asPoint3F()); + } + + screenClipper.addBox(screenSpaceBoundingBox); + + screenClipper.cullUnusedVerts(); + //screenClipper.triangulate(); + + // Empty vertex list? Skip! + if(screenClipper.mVertexList.empty()) + { + pri->systemState = ParticleRenderInst::AwaitingHighResDraw; + return RenderBinManager::arSkipped; + } + + Point2F minExtents(0.0f, 0.0f), maxExtents(0.0f, 0.0f); + for(ClippedPolyList::VertexList::const_iterator itr = screenClipper.mVertexList.begin(); + itr != screenClipper.mVertexList.end(); itr++) + { + minExtents.x = getMin(minExtents.x, (*itr).point.x); + minExtents.y = getMin(minExtents.y, (*itr).point.y); + maxExtents.x = getMax(maxExtents.x, (*itr).point.x); + maxExtents.y = getMax(maxExtents.y, (*itr).point.y); + } + screenRect.set( minExtents, maxExtents - minExtents ); + + // Check the size of the system on screen. If it is small, it won't + // be eating fillrate anyway, so just draw it high-resolution. + // The value it checks against is one I found from experimentation, + // not anything really meaningful. + if( screenRect.extent.x < 0.35f || screenRect.extent.y < 0.35f ) + { + pri->systemState = ParticleRenderInst::AwaitingHighResDraw; + return RenderBinManager::arSkipped; + } + + pri->targetIndex = mTargetChainIdx; + chainIndex = mTargetChainIdx; + mTargetChainIdx++; + + // TODO: Rewrite this... + mElementList.increment(); + } + + // Set up system entry + OffscreenSystemEntry &systemEntry = mOffscreenSystems[pri->targetIndex]; + + systemEntry.screenRect = screenRect; + systemEntry.targetChainIdx = chainIndex; + systemEntry.pInstances.push_back(pri); + + // TODO: Rewrite this block + // Assign proper values to sort element + MainSortElem& elem = mElementList.last(); + elem.inst = reinterpret_cast(&systemEntry); + elem.key = *((U32*)&inst->sortDistSq); + elem.key2 = inst->defaultKey; + + // TODO: [re]move this block + systemEntry.clipMatrix.identity(); + if(!RenderToSingleTarget) + { + // Construct crop matrix + Point3F scale( getMax(2.0f / systemEntry.screenRect.extent.x, 1.0f), + getMax(2.0f / systemEntry.screenRect.extent.y, 1.0f), + 1.0f); + + Point3F offset((systemEntry.screenRect.point.x + systemEntry.screenRect.extent.x * 0.5f) * scale.x, + (systemEntry.screenRect.point.y + systemEntry.screenRect.extent.y * 0.5f) * scale.y, + 0.0f); + + //systemEntry.clipMatrix.scale(scale); + //systemEntry.clipMatrix.setPosition(-offset); + } + + // The translucent mgr will also pick up particles, and will call this class + // to composiste the particle systems back into the main scene + return RenderBinManager::arAdded; +} + +void RenderParticleMgr::sort() +{ + Parent::sort(); +} + +void RenderParticleMgr::clear() +{ + Parent::clear(); + + // Reset pool index + if(!RenderToSingleTarget) + mTargetChainIdx = 0; + + for(Vector::iterator itr = mOffscreenSystems.begin(); + itr != mOffscreenSystems.end(); itr++) + { + (*itr).drawnThisFrame = false; + (*itr).pInstances.clear(); + } +} + +void RenderParticleMgr::render( SceneState *state ) +{ + PROFILE_SCOPE(RenderParticleMgr_render); + + // Early out if nothing to draw + if( !mElementList.size() || + (!mParticleShader && !_initShader()) ) + return; + + GFXDEBUGEVENT_SCOPE(RenderParticleMgr_Render, ColorI::RED); + + GFXTransformSaver saver; + + // Iterate render instances + for( Vector::const_iterator itr = mElementList.begin(); + itr != mElementList.end(); itr++ ) + { + OffscreenSystemEntry &systemEntry = *reinterpret_cast(itr->inst); + + // Setup target + // NOTE: If you are using this on the Xbox360 with Basic Lighting, + // you are going to have to mess with either the render order, or + // you are going to have to make this a 'preserve' draw + if(!RenderToSingleTarget) + mTargetChainIdx = systemEntry.targetChainIdx; + _onPreRender(state); + + // Clear offscreen target + GFX->clear(GFXClearTarget, ColorI::ZERO, 1.0f, 0); + + // Draw offscreen systems + for( Vector::const_iterator itr2 = systemEntry.pInstances.begin(); + itr2 != systemEntry.pInstances.end(); itr2++ ) + { + ParticleRenderInst *ri = *itr2; + + // Sanity check + if(ri->systemState == ParticleRenderInst::AwaitingOffscreenDraw) + { + // If this is not a diffuse path, flag the system appropriately, and skip + // the offscreen processing. + if( !state->isDiffusePass() ) + { + if(state->isReflectPass()) + ri->systemState = ParticleRenderInst::AwaitingHighResDraw; + else + ri->systemState = ParticleRenderInst::DrawComplete; + continue; + } + + // Draw system offscreen + renderInstance(ri, state); + } + } + + // Cleanup + _onPostRender(); + } +} + +void RenderParticleMgr::_initGFXResources() +{ + // Screen quad + U16 *prims = NULL; + mScreenQuadPrimBuff.set(GFX, 4, 2, GFXBufferTypeStatic); + mScreenQuadPrimBuff.lock(&prims); + (*prims++) = 0; + (*prims++) = 1; + (*prims++) = 2; + (*prims++) = 3; + mScreenQuadPrimBuff.unlock(); + + mScreenQuadVertBuff.set(GFX, 4, GFXBufferTypeStatic); + CompositeQuadVert *verts = mScreenQuadVertBuff.lock(); + (*verts++).uvCoord.set(0, 0, 0, 0); + (*verts++).uvCoord.set(0, 255, 0, 0); + (*verts++).uvCoord.set(255, 0, 0, 0); + (*verts++).uvCoord.set(255, 255, 0, 0); + mScreenQuadVertBuff.unlock(); + + // Stencil setup state block + GFXStateBlockDesc d; + + d.setCullMode(GFXCullNone); + d.setColorWrites(false, false, false, false); + d.setBlend(false); + d.setZReadWrite(false, false); + + d.stencilDefined = true; + d.stencilEnable = true; + d.stencilMask = RenderParticleMgr::ParticleSystemStencilMask; + d.stencilWriteMask = RenderParticleMgr::ParticleSystemStencilMask; + d.stencilFunc = GFXCmpAlways; + d.stencilPassOp = GFXStencilOpZero; + + mStencilClearSB = GFX->createStateBlock(d); +} + +void RenderParticleMgr::renderInstance(ParticleRenderInst *ri, SceneState *state) +{ + // Draw system path, or draw composite path + if(ri->systemState == ParticleRenderInst::DrawComplete) + return; + + if(ri->systemState != ParticleRenderInst::AwaitingCompositeDraw) + { + // Set proper stateblock, and update state + if(ri->systemState == ParticleRenderInst::AwaitingOffscreenDraw) + { + GFX->setStateBlock( _getOffscreenStateBlock(ri) ); + ri->systemState = ParticleRenderInst::AwaitingCompositeDraw; + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mModelViewProjSC, + *ri->modelViewProj * mOffscreenSystems[ri->targetIndex].clipMatrix ); + } + else + { + if(ri->systemState == ParticleRenderInst::AwaitingMixedResDraw) + GFX->setStateBlock( _getMixedResStateBlock( ri ) ); + else + GFX->setStateBlock( _getHighResStateBlock( ri ) ); + ri->systemState = ParticleRenderInst::DrawComplete; + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mModelViewProjSC, *ri->modelViewProj ); + } + + // We want to turn everything into variation on a pre-multiplied alpha blend + F32 alphaFactor = 0.0f, alphaScale = 1.0f; + switch(ri->blendStyle) + { + // SrcAlpha, InvSrcAlpha + case ParticleRenderInst::BlendNormal: + alphaFactor = 1.0f; + break; + + // SrcAlpha, One + case ParticleRenderInst::BlendAdditive: + alphaFactor = 1.0f; + alphaScale = 0.0f; + break; + + // SrcColor, One + case ParticleRenderInst::BlendGreyscale: + alphaFactor = -1.0f; + alphaScale = 0.0f; + break; + } + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mAlphaFactorSC, alphaFactor ); + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mAlphaScaleSC, alphaScale ); + + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mFSModelViewProjSC, *ri->modelViewProj ); + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mOneOverFarSC, 1.0f / state->getFarPlane() ); + + if ( mParticleShaderConsts.mOneOverSoftnessSC ) + { + F32 oneOverSoftness = 1.0f; + if ( ri->softnessDistance >= 0.0f ) + oneOverSoftness = 1.0f / ( ri->softnessDistance / state->getFarPlane() ); + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mOneOverSoftnessSC, oneOverSoftness ); + } + + GFX->setShader( mParticleShader ); + GFX->setShaderConstBuffer( mParticleShaderConsts.mShaderConsts ); + + GFX->setTexture( 0, ri->diffuseTex ); + + // Set up the prepass texture. + GFXTextureObject *texObject = mPrepassTarget ? mPrepassTarget->getTargetTexture(0) : NULL; + if( texObject ) + { + GFX->setTexture( 1, texObject ); + + Point4F rtParams; + ScreenSpace::RenderTargetParameters(texObject->getSize(), mPrepassTarget->getTargetViewport(), rtParams); + mParticleShaderConsts.mShaderConsts->set( mParticleShaderConsts.mPrePassTargetParamsSC, rtParams ); + } + + GFX->setPrimitiveBuffer( *ri->primBuff ); + GFX->setVertexBuffer( *ri->vertBuff ); + + GFX->drawIndexedPrimitive( GFXTriangleList, 0, 0, ri->count * 4, 0, ri->count * 2 ); + } + else if(ri->systemState == ParticleRenderInst::AwaitingCompositeDraw) + { + OffscreenSystemEntry &systemEntry = mOffscreenSystems[ri->targetIndex]; + + // If this system has already been composited this frame, skip it + if(systemEntry.drawnThisFrame) + return; + + // Non-target render, composite the particle system back into the scene + GFX->setVertexBuffer(mScreenQuadVertBuff); + GFX->setPrimitiveBuffer(mScreenQuadPrimBuff); + + + // Set up shader constants + mParticleCompositeShaderConsts.mShaderConsts->set( mParticleCompositeShaderConsts.mScreenRect, *((Point4F *)&systemEntry.screenRect) ); + + // Set offscreen texture + Point4F rtParams; + GFXTextureObject *particleSource = getTargetTexture(0, systemEntry.targetChainIdx); + GFX->setTexture( 0, particleSource ); + if(particleSource) + { + ScreenSpace::RenderTargetParameters(particleSource->getSize(), getTargetViewport(), rtParams); + mParticleCompositeShaderConsts.mShaderConsts->set( mParticleCompositeShaderConsts.mOffscreenTargetParamsSC, rtParams ); + } + + // And edges + GFXTextureObject *texObject = mEdgeTarget ? mEdgeTarget->getTargetTexture(0) : NULL; + GFX->setTexture( 1, texObject ); + if(texObject) + { + ScreenSpace::RenderTargetParameters(texObject->getSize(), mEdgeTarget->getTargetViewport(), rtParams); + mParticleCompositeShaderConsts.mShaderConsts->set( mParticleCompositeShaderConsts.mEdgeTargetParamsSC, rtParams ); + } + + // Set shader and constant buffer + GFX->setShader( mParticleCompositeShader ); + GFX->setShaderConstBuffer( mParticleCompositeShaderConsts.mShaderConsts ); + + // Draw to stencil buffer only to clear the stencil values + GFX->setStateBlock( mStencilClearSB ); + GFX->drawIndexedPrimitive( GFXTriangleStrip, 0, 0, 4, 0, 2 ); + + // composite particle system back into the scene + GFX->setStateBlock( _getCompositeStateBlock(ri) ); + GFX->drawIndexedPrimitive( GFXTriangleStrip, 0, 0, 4, 0, 2 ); + + // Re-draw the particle systems in high-res, but only to the stenciled + // areas which were enabled via the edge buffer + for( Vector::const_iterator itr = systemEntry.pInstances.begin(); + itr != systemEntry.pInstances.end(); itr++ ) + { + ParticleRenderInst *pri = *itr; + if(pri->systemState == ParticleRenderInst::AwaitingCompositeDraw) + { + pri->systemState = ParticleRenderInst::AwaitingMixedResDraw; + renderInstance(pri, state); + } + } + + // Mark this system as having been composited this frame + systemEntry.drawnThisFrame = true; + } +} + +bool RenderParticleMgr::_initShader() +{ + ShaderData *shaderData = NULL; + bool ret = true; + + // Need depth from pre-pass, so get the macros + Vector macros; + if ( mPrepassTarget ) + mPrepassTarget->getTargetShaderMacros( ¯os ); + + // Create particle shader + if ( !Sim::findObject( "ParticlesShaderData", shaderData ) || !shaderData ) + Con::warnf( "RenderParticleMgr::_initShader - failed to locate shader ParticlesShaderData!" ); + if( shaderData ) + mParticleShader = shaderData->getShader( macros ); + ret &= (mParticleShader != NULL); + + if ( mParticleShader && !mParticleShaderConsts.mShaderConsts ) + { + mParticleShaderConsts.mShaderConsts = mParticleShader->allocConstBuffer(); + mParticleShaderConsts.mModelViewProjSC = mParticleShader->getShaderConstHandle( "$modelViewProj" ); + mParticleShaderConsts.mOneOverFarSC = mParticleShader->getShaderConstHandle( "$oneOverFar" ); + mParticleShaderConsts.mOneOverSoftnessSC = mParticleShader->getShaderConstHandle( "$oneOverSoftness" ); + mParticleShaderConsts.mAlphaFactorSC = mParticleShader->getShaderConstHandle( "$alphaFactor" ); + mParticleShaderConsts.mAlphaScaleSC = mParticleShader->getShaderConstHandle( "$alphaScale" ); + mParticleShaderConsts.mFSModelViewProjSC = mParticleShader->getShaderConstHandle( "$fsModelViewProj" ); + mParticleShaderConsts.mPrePassTargetParamsSC = mParticleShader->getShaderConstHandle( "$prePassTargetParams" ); + } + + shaderData = NULL; + + // Create off screen particle composite shader + if ( !Sim::findObject( "OffscreenParticleCompositeShaderData", shaderData ) || !shaderData ) + Con::warnf( "RenderParticleMgr::_initShader - failed to locate shader OffscreenParticleCompositeShaderData!" ); + if( shaderData ) + mParticleCompositeShader = shaderData->getShader( macros ); + ret &= (mParticleCompositeShader != NULL); + + if ( mParticleCompositeShader && !mParticleCompositeShaderConsts.mShaderConsts ) + { + mParticleCompositeShaderConsts.mShaderConsts = mParticleCompositeShader->allocConstBuffer(); + mParticleCompositeShaderConsts.mScreenRect = mParticleCompositeShader->getShaderConstHandle( "$screenRect" ); + mParticleCompositeShaderConsts.mEdgeTargetParamsSC = mParticleCompositeShader->getShaderConstHandle( "$edgeTargetParams" ); + mParticleCompositeShaderConsts.mOffscreenTargetParamsSC = mParticleCompositeShader->getShaderConstHandle( "$offscreenTargetParams" ); + } + + return ret; +} + +void RenderParticleMgr::_onLMActivate( const char*, bool activate ) +{ + RenderPassManager *rpm = getParentManager(); + if ( !rpm ) + return; + + // Hunt for the pre-pass manager/target + RenderPrePassMgr *prePassBin = NULL; + for( U32 i = 0; i < rpm->getManagerCount(); i++ ) + { + RenderBinManager *bin = rpm->getManager(i); + if( bin->getRenderInstType() == RenderPrePassMgr::RIT_PrePass ) + { + prePassBin = (RenderPrePassMgr*)bin; + break; + } + } + + // If we found the prepass bin, set this bin to render very shortly afterwards + // and re-add this render-manager. If there is no pre-pass bin, or it doesn't + // have a depth-texture, we can't render offscreen. + mOffscreenRenderEnabled = prePassBin && (prePassBin->getTargetChainLength() > 0); + if(mOffscreenRenderEnabled) + { + rpm->removeManager(this); + setRenderOrder( prePassBin->getRenderOrder() + 0.011f ); + rpm->addManager(this); + } + + // Find the targets we use + mPrepassTarget = MatTextureTarget::findTargetByName( "prepass" ); + mEdgeTarget = MatTextureTarget::findTargetByName( "edge" ); + + // Setup the shader + if ( activate ) + _initShader(); + + if ( mScreenQuadVertBuff.isNull() ) + _initGFXResources(); +} + +GFXStateBlockRef RenderParticleMgr::_getOffscreenStateBlock(ParticleRenderInst *ri) +{ + const U8 blendStyle = ri->blendStyle; + if ( mOffscreenBlocks[blendStyle].isValid() ) + return mOffscreenBlocks[blendStyle]; + + GFXStateBlockDesc d; + + d.setCullMode(GFXCullNone); + d.setZReadWrite(false, false); // No zreads or writes, all z-testing is done in the pixel shader + + // Draw everything either subtractive, or using a variation on premultiplied + // alpha + if(blendStyle == ParticleRenderInst::BlendSubtractive) + d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); + else + d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); + + // Offscreen target, we need to add alpha. + d.separateAlphaBlendDefined = true; + d.separateAlphaBlendEnable = true; + d.separateAlphaBlendSrc = GFXBlendOne; + d.separateAlphaBlendDest = GFXBlendInvSrcAlpha; + + d.samplersDefined = true; + + // Diffuse texture sampler + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + d.samplers[0].alphaOp = GFXTOPModulate; + d.samplers[0].alphaArg1 = GFXTATexture; + d.samplers[0].alphaArg2 = GFXTADiffuse; + + // Prepass sampler + d.samplers[1] = GFXSamplerStateDesc::getClampPoint(); + + mOffscreenBlocks[blendStyle] = GFX->createStateBlock(d); + return mOffscreenBlocks[blendStyle]; +} + +GFXStateBlockRef RenderParticleMgr::_getHighResStateBlock(ParticleRenderInst *ri) +{ + const U8 blendStyle = ri->blendStyle; + if ( mHighResBlocks[blendStyle].isValid() ) + return mHighResBlocks[blendStyle]; + + GFXStateBlockDesc d; + + d.setZReadWrite(true, false); + d.setCullMode(GFXCullNone); + + // Draw everything either subtractive, or using a variation on premultiplied + // alpha + if(blendStyle == ParticleRenderInst::BlendSubtractive) + d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); + else + d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); + + d.samplersDefined = true; + + // Diffuse texture sampler + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + d.samplers[0].alphaOp = GFXTOPModulate; + d.samplers[0].alphaArg1 = GFXTATexture; + d.samplers[0].alphaArg2 = GFXTADiffuse; + + // Prepass sampler + d.samplers[1] = GFXSamplerStateDesc::getClampPoint(); + + mHighResBlocks[blendStyle] = GFX->createStateBlock(d); + return mHighResBlocks[blendStyle]; +} + +GFXStateBlockRef RenderParticleMgr::_getMixedResStateBlock(ParticleRenderInst *ri) +{ + const U8 blendStyle = ri->blendStyle; + if ( mHighResBlocks[blendStyle].isValid() ) + return mHighResBlocks[blendStyle]; + + GFXStateBlockDesc d; + + d.setZReadWrite(true, false); + d.setCullMode(GFXCullNone); + + /* + // Old blend styles... + switch (blendStyle) + { + case ParticleRenderInst::BlendNormal: + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendInvSrcAlpha; + break; + + case ParticleRenderInst::BlendSubtractive: + d.blendSrc = GFXBlendZero; + d.blendDest = GFXBlendInvSrcColor; + break; + + case ParticleRenderInst::BlendPremultAlpha: + d.blendSrc = GFXBlendOne; + d.blendDest = GFXBlendInvSrcAlpha; + break; + + // Default to additive blend mode + case ParticleRenderInst::BlendAdditive: + case ParticleRenderInst::BlendUndefined: + default: + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendOne; + break; + } + */ + + // Draw everything either subtractive, or using a variation on premultiplied + // alpha + if(blendStyle == ParticleRenderInst::BlendSubtractive) + d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); + else + d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); + + // Draw to anything but the stencil ref value (the edges) + d.stencilDefined = true; + d.stencilEnable = true; + d.stencilRef = RenderParticleMgr::HighResStencilRef; + d.stencilMask = RenderParticleMgr::ParticleSystemStencilMask; + d.stencilPassOp = GFXStencilOpKeep; + d.stencilFailOp = GFXStencilOpKeep; + d.stencilZFailOp = GFXStencilOpKeep; + d.stencilFunc = GFXCmpNotEqual; + + d.samplersDefined = true; + + // Diffuse texture sampler + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + d.samplers[0].alphaOp = GFXTOPModulate; + d.samplers[0].alphaArg1 = GFXTATexture; + d.samplers[0].alphaArg2 = GFXTADiffuse; + + // Prepass sampler + d.samplers[1] = GFXSamplerStateDesc::getClampPoint(); + + mHighResBlocks[blendStyle] = GFX->createStateBlock(d); + return mHighResBlocks[blendStyle]; +} + +GFXStateBlockRef RenderParticleMgr::_getCompositeStateBlock(ParticleRenderInst *ri) +{ + const U8 blendStyle = ri->blendStyle; + if ( mBackbufferBlocks[blendStyle].isValid() ) + return mBackbufferBlocks[blendStyle]; + + GFXStateBlockDesc d; + + // This is a billboard + d.setCullMode(GFXCullNone); + d.setZReadWrite(false, false); + + // When we re-composite the particles, it is always either a pre-mult alpha + // blend, or a subtractive blend! + if(blendStyle == ParticleRenderInst::BlendSubtractive) + d.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); + else + d.setBlend(true, GFXBlendOne, GFXBlendInvSrcAlpha); + + // All areas which are not along the edges of geometry where the particle system + // is being drawn get assigned a stencil ref value as the system is composited + // back into the scene. The high-res stateblock uses this value as a mask, and + // draws only in areas which are NOT this ref value. This causes high resolution + // draws to ONLY the edge areas. + d.stencilDefined = true; + d.stencilEnable = true; + d.stencilRef = RenderParticleMgr::HighResStencilRef; + d.stencilWriteMask = RenderParticleMgr::ParticleSystemStencilMask; + d.stencilMask = RenderParticleMgr::ParticleSystemStencilMask; + d.stencilPassOp = GFXStencilOpReplace; + d.stencilFunc = GFXCmpGreater; + + // Diffuse texture sampler and + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + d.samplers[1] = GFXSamplerStateDesc::getClampLinear(); + + mBackbufferBlocks[blendStyle] = GFX->createStateBlock(d); + return mBackbufferBlocks[blendStyle]; +} + +bool RenderParticleMgr::_handleGFXEvent( GFXDevice::GFXDeviceEventType event ) +{ + if(RenderToSingleTarget) + return Parent::_handleGFXEvent( event ); + + // Do nothing. This render manager uses its target chain as a pool of targets. + return true; +} \ No newline at end of file diff --git a/renderInstance/renderParticleMgr.h b/renderInstance/renderParticleMgr.h new file mode 100644 index 0000000..b7d172d --- /dev/null +++ b/renderInstance/renderParticleMgr.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RENDER_PARTICLE_MGR_H_ +#define _RENDER_PARTICLE_MGR_H_ + +#ifndef _TEXTARGETBIN_MGR_H_ +#include "renderInstance/renderTexTargetBinManager.h" +#endif + +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + +GFXDeclareVertexFormat( CompositeQuadVert ) +{ + GFXVertexColor uvCoord; +}; + +class RenderParticleMgr : public RenderTexTargetBinManager +{ + typedef RenderTexTargetBinManager Parent; + friend class RenderTranslucentMgr; + +public: + // Generic PrePass Render Instance Type + static const RenderInstType RIT_Particles; + + RenderParticleMgr(); + RenderParticleMgr( F32 renderOrder, F32 processAddOrder ); + virtual ~RenderParticleMgr(); + + // RenderBinManager + void render(SceneState * state); + void sort(); + void clear(); + AddInstResult addElement( RenderInst *inst ); + + // ConsoleObject + DECLARE_CONOBJECT(RenderParticleMgr); + + const static U8 HighResStencilRef = 0x80; + const static U8 ParticleSystemStencilMask = 0x80; // We are using the top bit + const static U32 OffscreenPoolSize = 5; + + virtual void setTargetChainLength(const U32 chainLength); + +protected: + + // Override + virtual bool _handleGFXEvent(GFXDevice::GFXDeviceEventType event); + + bool _initShader(); + void _initGFXResources(); + void _onLMActivate( const char*, bool activate ); + + + // Not only a helper method, but a method for the RenderTranslucentMgr to + // request a particle system draw + void renderInstance(ParticleRenderInst *ri, SceneState *state); + + bool mOffscreenRenderEnabled; + + /// The prepass render target used for the + /// soft particle shader effect. + MatTextureTargetRef mPrepassTarget; + + /// The shader used for particle rendering. + GFXShaderRef mParticleShader; + + GFXShaderRef mParticleCompositeShader; + MatTextureTargetRef mEdgeTarget; + + struct OffscreenSystemEntry + { + S32 targetChainIdx; + MatrixF clipMatrix; + RectF screenRect; + bool drawnThisFrame; + Vector pInstances; + }; + Vector mOffscreenSystems; + + struct ShaderConsts + { + GFXShaderConstBufferRef mShaderConsts; + GFXShaderConstHandle *mModelViewProjSC; + GFXShaderConstHandle *mFSModelViewProjSC; + GFXShaderConstHandle *mOneOverFarSC; + GFXShaderConstHandle *mOneOverSoftnessSC; + GFXShaderConstHandle *mPrePassTargetParamsSC; + GFXShaderConstHandle *mAlphaFactorSC; + GFXShaderConstHandle *mAlphaScaleSC; + + } mParticleShaderConsts; + + struct CompositeShaderConsts + { + GFXShaderConstBufferRef mShaderConsts; + GFXShaderConstHandle *mSystemDepth; + GFXShaderConstHandle *mScreenRect; + GFXShaderConstHandle *mEdgeTargetParamsSC; + GFXShaderConstHandle *mOffscreenTargetParamsSC; + } mParticleCompositeShaderConsts; + + GFXVertexBufferHandle mScreenQuadVertBuff; + GFXPrimitiveBufferHandle mScreenQuadPrimBuff; + + GFXStateBlockRef mStencilClearSB; + GFXStateBlockRef mHighResBlocks[ParticleRenderInst::BlendStyle_COUNT]; + GFXStateBlockRef mOffscreenBlocks[ParticleRenderInst::BlendStyle_COUNT]; + GFXStateBlockRef mBackbufferBlocks[ParticleRenderInst::BlendStyle_COUNT]; + GFXStateBlockRef mMixedResBlocks[ParticleRenderInst::BlendStyle_COUNT]; + + GFXStateBlockRef _getHighResStateBlock(ParticleRenderInst *ri); + GFXStateBlockRef _getMixedResStateBlock(ParticleRenderInst *ri); + GFXStateBlockRef _getOffscreenStateBlock(ParticleRenderInst *ri); + GFXStateBlockRef _getCompositeStateBlock(ParticleRenderInst *ri); +}; + + +#endif // _RENDER_TRANSLUCENT_MGR_H_ diff --git a/renderInstance/renderPassManager.cpp b/renderInstance/renderPassManager.cpp new file mode 100644 index 0000000..72b6eec --- /dev/null +++ b/renderInstance/renderPassManager.cpp @@ -0,0 +1,427 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderPassManager.h" + +#include "materials/sceneData.h" +#include "materials/matInstance.h" +#include "materials/customMaterialDefinition.h" +#include "materials/materialManager.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneObject.h" +#include "gfx/primBuilder.h" +#include "platform/profiler.h" +#include "renderInstance/renderBinManager.h" +#include "renderInstance/renderObjectMgr.h" +#include "renderInstance/renderMeshMgr.h" +#include "renderInstance/renderTranslucentMgr.h" +#include "renderInstance/renderGlowMgr.h" +#include "renderInstance/renderTerrainMgr.h" +#include "core/util/safeDelete.h" +#include "math/util/matrixSet.h" + +//const String IRenderable3D::InterfaceName("Render3D"); +const F32 RenderPassManager::PROCESSADD_NONE = -1e30f; +const F32 RenderPassManager::PROCESSADD_NORMAL = 0.5f; + +const RenderInstType RenderPassManager::RIT_Interior("Interior"); +const RenderInstType RenderPassManager::RIT_Mesh("Mesh"); +const RenderInstType RenderPassManager::RIT_Shadow("Shadow"); +const RenderInstType RenderPassManager::RIT_Sky("Sky"); +const RenderInstType RenderPassManager::RIT_Terrain("Terrain"); +const RenderInstType RenderPassManager::RIT_Object("Object"); +const RenderInstType RenderPassManager::RIT_ObjectTranslucent("ObjectTranslucent"); +const RenderInstType RenderPassManager::RIT_Decal("Decal"); +const RenderInstType RenderPassManager::RIT_Water("Water"); +const RenderInstType RenderPassManager::RIT_Foliage("Foliage"); +const RenderInstType RenderPassManager::RIT_Translucent("Translucent"); +const RenderInstType RenderPassManager::RIT_Begin("Begin"); +const RenderInstType RenderPassManager::RIT_Custom("Custom"); +const RenderInstType RenderPassManager::RIT_Particle("Particle"); +const RenderInstType RenderPassManager::RIT_Occluder("Occluder"); + + +//***************************************************************************** +// RenderInstance +//***************************************************************************** + +void RenderInst::clear() +{ + dMemset( this, 0, sizeof(RenderInst) ); +} + +void MeshRenderInst::clear() +{ + dMemset( this, 0, sizeof(MeshRenderInst) ); + visibility = 1.0f; +} + +void ParticleRenderInst::clear() +{ + dMemset( this, 0, sizeof(ParticleRenderInst) ); +} + +void ObjectRenderInst::clear() +{ + userData = NULL; + + dMemset( this, 0, sizeof( ObjectRenderInst ) ); + + // The memset here is kinda wrong... it clears the + // state initialized by the delegate constructor. + // + // This fixes it... but we probably need to have a + // real constructor for RenderInsts. + renderDelegate.clear(); +} + +void OccluderRenderInst::clear() +{ + dMemset( this, 0, sizeof(OccluderRenderInst) ); +} + +//***************************************************************************** +// Render Instance Manager +//***************************************************************************** +IMPLEMENT_CONOBJECT(RenderPassManager); + + +RenderPassManager::RenderBinEventSignal& RenderPassManager::getRenderBinSignal() +{ + static RenderBinEventSignal theSignal; + return theSignal; +} + +void RenderPassManager::initPersistFields() +{ +} + +RenderPassManager::RenderPassManager() +{ + mSceneManager = NULL; + VECTOR_SET_ASSOCIATION( mRenderBins ); + VECTOR_SET_ASSOCIATION( mAddBins ); + +#ifndef TORQUE_SHIPPING + mAddInstCount = 0; + mAddInstPassCount = 0; + VECTOR_SET_ASSOCIATION( mAddBinInstCounts ); +#endif + + mMatrixSet = reinterpret_cast(dAligned_malloc(sizeof(MatrixSet), 16)); + constructInPlace(mMatrixSet); +} + +RenderPassManager::~RenderPassManager() +{ + dAligned_free(mMatrixSet); + + // Any bins left need to be deleted. + for ( U32 i=0; isetParentManager( NULL ); + bin->deleteObject(); + } +} + +void RenderPassManager::_insertSort(Vector& list, RenderBinManager* mgr, bool renderOrder) +{ + U32 i; + for (i = 0; i < list.size(); i++) + { + bool renderCompare = mgr->getRenderOrder() < list[i]->getRenderOrder(); + bool processAddCompare = mgr->getProcessAddOrder() < list[i]->getProcessAddOrder(); + if ((renderOrder && renderCompare) || (!renderOrder && processAddCompare)) + { + list.insert(i); + list[i] = mgr; + return; + } + } + + list.push_back(mgr); +} + +void RenderPassManager::addManager(RenderBinManager* mgr) +{ + if ( !mgr->isProperlyAdded() ) + mgr->registerObject(); + + AssertFatal( mgr->getParentManager() == NULL, "RenderPassManager::addManager() - Bin is still part of another pass manager!" ); + mgr->setParentManager(this); + + _insertSort(mRenderBins, mgr, true); + + if ( mgr->getProcessAddOrder() != PROCESSADD_NONE ) + _insertSort(mAddBins, mgr, false); +} + +void RenderPassManager::removeManager(RenderBinManager* mgr) +{ + AssertFatal( mgr->getParentManager() == this, "RenderPassManager::removeManager() - We do not own this bin!" ); + + mRenderBins.remove( mgr ); + mAddBins.remove( mgr ); + mgr->setParentManager( NULL ); +} + +RenderBinManager* RenderPassManager::getManager(S32 i) const +{ + if (i >= 0 && i < mRenderBins.size()) + return mRenderBins[i]; + else + return NULL; +} + +#ifndef TORQUE_SHIPPING +void RenderPassManager::resetCounters() +{ + mAddInstCount = 0; + mAddInstPassCount = 0; + + mAddBinInstCounts.setSize( mRenderBins.size() ); + dMemset( mAddBinInstCounts.address(), 0, mAddBinInstCounts.size() * sizeof( U32 ) ); + + // TODO: We need to expose the counts again. The difficulty + // is two fold... + // + // First we need to track bins and not RITs... you can have + // the same RIT in multiple bins, so we miss some if we did that. + // + // Second we have multiple RenderPassManagers, so we need to + // either combine the results of all passes, show all the bins + // and passes, or just one pass at a time. +} +#endif + +void RenderPassManager::addInst( RenderInst *inst ) +{ + AssertFatal(inst != NULL, "doh, null instance"); + + PROFILE_SCOPE(SceneRenderPassManager_addInst); + + #ifndef TORQUE_SHIPPING + mAddInstCount++; + #endif + + // See what managers want to look at this instance. + bool bHandled = false; +#ifndef TORQUE_SHIPPING + U32 i = 0; +#endif + for (Vector::iterator itr = mAddBins.begin(); + itr != mAddBins.end(); itr++) + { + RenderBinManager *curBin = *itr; + AssertFatal(curBin, "Empty render bin slot!"); + + // We may want to pass if the inst has been handled already to addElement? + RenderBinManager::AddInstResult result = curBin->addElement(inst); + + #ifndef TORQUE_SHIPPING + // Log some rendering stats + if ( result != RenderBinManager::arSkipped ) + { + mAddInstPassCount++; + if ( i < mAddBinInstCounts.size() ) + mAddBinInstCounts[ i ]++; + } + i++; + #endif + + switch (result) + { + case RenderBinManager::arAdded : + bHandled = true; + break; + case RenderBinManager::arStop : + bHandled = true; + continue; + break; + default : + // handle warning + break; + } + } + //AssertFatal(bHandled, "Instance without a render manager!"); +} + +void RenderPassManager::sort() +{ + PROFILE_SCOPE( RenderPassManager_Sort ); + + for (Vector::iterator itr = mRenderBins.begin(); + itr != mRenderBins.end(); itr++) + { + AssertFatal(*itr, "Render manager invalid!"); + (*itr)->sort(); + } +} + +void RenderPassManager::clear() +{ + PROFILE_SCOPE( RenderPassManager_Clear ); + + mChunker.clear(); + + for (Vector::iterator itr = mRenderBins.begin(); + itr != mRenderBins.end(); itr++) + { + AssertFatal(*itr, "Invalid render manager!"); + (*itr)->clear(); + } +} + +void RenderPassManager::render(SceneState * state) +{ + PROFILE_SCOPE( RenderPassManager_Render ); + + GFX->pushWorldMatrix(); + MatrixF proj = GFX->getProjectionMatrix(); + + + for (Vector::iterator itr = mRenderBins.begin(); + itr != mRenderBins.end(); itr++) + { + RenderBinManager *curBin = *itr; + AssertFatal(curBin, "Invalid render manager!"); + getRenderBinSignal().trigger(curBin, state, true); + curBin->render(state); + getRenderBinSignal().trigger(curBin, state, false); + } + + GFX->popWorldMatrix(); + GFX->setProjectionMatrix( proj ); + + // Restore a clean state for subsequent rendering. + GFX->disableShaders(); + for(S32 i = 0; i < GFX->getNumSamplers(); ++i) + GFX->setTexture(i, NULL); +} + +void RenderPassManager::renderPass(SceneState * state) +{ + PROFILE_SCOPE( RenderPassManager_RenderPass ); + sort(); + render(state); + clear(); +} + +GFXTextureObject *RenderPassManager::getDepthTargetTexture() +{ + // If this is OpenGL, or something else has set the depth buffer, return the pointer + if( mDepthBuff.isValid() ) + { + // If this is OpenGL, make sure the depth target matches up + // with the active render target. Otherwise recreate. + + if( GFX->getAdapterType() == OpenGL ) + { + GFXTarget* activeRT = GFX->getActiveRenderTarget(); + AssertFatal( activeRT, "Must be an active render target to call 'getDepthTargetTexture'" ); + + Point2I activeRTSize = activeRT->getSize(); + if( mDepthBuff.getWidth() == activeRTSize.x && + mDepthBuff.getHeight() == activeRTSize.y ) + return mDepthBuff.getPointer(); + } + else + return mDepthBuff.getPointer(); + } + + if(GFX->getAdapterType() == OpenGL) + { + AssertFatal(GFX->getActiveRenderTarget(), "Must be an active render target to call 'getDepthTargetTexture'"); + + const Point2I rtSize = GFX->getActiveRenderTarget()->getSize(); + mDepthBuff.set(rtSize.x, rtSize.y, GFXFormatD24S8, + &GFXDefaultZTargetProfile, avar("%s() - mDepthBuff (line %d)", __FUNCTION__, __LINE__)); + return mDepthBuff.getPointer(); + } + + // Default return value + return GFXTextureTarget::sDefaultDepthStencil; +} + +void RenderPassManager::setDepthTargetTexture( GFXTextureObject *zTarget ) +{ + mDepthBuff = zTarget; +} + +const MatrixF* RenderPassManager::allocSharedXform( SharedTransformType stt ) +{ + AssertFatal(stt == View || stt == Projection, "Bad shared transform type"); + + // Enable this to simulate non-shared transform performance + //#define SIMULATE_NON_SHARED_TRANSFORMS +#ifdef SIMULATE_NON_SHARED_TRANSFORMS + return allocUniqueXform(stt == View ? mMatrixSet->getWorldToCamera() : mMatrixSet->getCameraToScreen()); +#else + return &(stt == View ? mMatrixSet->getWorldToCamera() : mMatrixSet->getCameraToScreen()); +#endif +} + +void RenderPassManager::assignSharedXform( SharedTransformType stt, const MatrixF &xfm ) +{ + AssertFatal(stt == View || stt == Projection, "Bad shared transform type"); + if(stt == View) + mMatrixSet->setSceneView(xfm); + else + mMatrixSet->setSceneProjection(xfm); +} + +// Script interface + +ConsoleMethod(RenderPassManager, getManagerCount, S32, 2, 2, + "Returns the total number of bin managers." ) +{ + TORQUE_UNUSED(argc); + TORQUE_UNUSED(argv); + + return (S32) object->getManagerCount(); +} + +ConsoleMethod( RenderPassManager, getManager, S32, 3, 3, + "Get the manager at index." ) +{ + TORQUE_UNUSED(argc); + + S32 objectIndex = dAtoi(argv[2]); + if(objectIndex < 0 || objectIndex >= S32(object->getManagerCount())) + { + Con::printf("Set::getObject index out of range."); + return -1; + } + return object->getManager(objectIndex)->getId(); +} + +ConsoleMethod( RenderPassManager, addManager, void, 3, 3, + "Add a manager." ) +{ + TORQUE_UNUSED(argc); + + RenderBinManager* m; + if (Sim::findObject(argv[2], m)) + object->addManager(m); + else + Con::errorf("Object %s does not exist or is not a RenderBinManager", argv[2]); +} + +ConsoleMethod( RenderPassManager, removeManager, void, 3, 3, + "Removes a manager by name." ) +{ + TORQUE_UNUSED(argc); + + RenderBinManager* m; + if (Sim::findObject(argv[2], m)) + object->removeManager(m); + else + Con::errorf("Object %s does not exist or is not a RenderBinManager", argv[2]); +} diff --git a/renderInstance/renderPassManager.h b/renderInstance/renderPassManager.h new file mode 100644 index 0000000..f4f4e00 --- /dev/null +++ b/renderInstance/renderPassManager.h @@ -0,0 +1,431 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _RENDERPASSMANAGER_H_ +#define _RENDERPASSMANAGER_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _SIMOBJECT_H_ +#include "console/simObject.h" +#endif +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif +#ifndef _SCENEGRAPH_H_ +#include "sceneGraph/sceneGraph.h" +#endif + +class SceneState; +class ISceneObject; +class BaseMatInstance; +struct SceneGraphData; +class ShaderData; +class RenderBinManager; +class LightInfo; +struct RenderInst; +class MatrixSet; + +/// A RenderInstType hash value. +typedef U32 RenderInstTypeHash; + +/// A a tiny wrapper around String that exposes a U32 operator so +/// that we can assign the RIT to RenderInst::type field. +class RenderInstType +{ + /// For direct access to mName. + friend class RenderBinManager; + +protected: + + String mName; + +public: + + RenderInstType() { mName = "Custom"; } + RenderInstType( const String &name ) { mName = name; } + RenderInstType( const RenderInstType &rit ) { mName = rit.mName; } + ~RenderInstType() { } + + operator RenderInstTypeHash() const { return (RenderInstTypeHash) mName.getHashCaseInsensitive(); } + + const String& getName() const { return mName; } +}; + + +/// +class RenderPassManager : public SimObject +{ + typedef SimObject Parent; + +public: + + // Default bin types. Not necessarily the only bin types in the system. + // RIT = "R"ender "I"nstance "T"ype + static const RenderInstType RIT_Interior; + static const RenderInstType RIT_Mesh; + static const RenderInstType RIT_Shadow; + static const RenderInstType RIT_Sky; + static const RenderInstType RIT_Terrain; + static const RenderInstType RIT_Object; // objects that do their own rendering + static const RenderInstType RIT_ObjectTranslucent;// self rendering; but sorted with static const RenderInstType RIT_Translucent + static const RenderInstType RIT_Decal; + static const RenderInstType RIT_Water; + static const RenderInstType RIT_Foliage; + static const RenderInstType RIT_Translucent; + static const RenderInstType RIT_Begin; + static const RenderInstType RIT_Custom; + static const RenderInstType RIT_Particle; + static const RenderInstType RIT_Occluder; + + // Constants used by ::addManager with RenderBinManager::getProcessOrder + static const F32 PROCESSADD_NONE; + static const F32 PROCESSADD_NORMAL; + +public: + + RenderPassManager(); + virtual ~RenderPassManager(); + + /// @name Allocation interface + /// @{ + + /// Allocate a render instance, use like so: MyRenderInstType* t = gRenderInstMgr->allocInst(); + /// Valid until ::clear called. + template + T* allocInst() + { + T* inst = mChunker.alloc(); + inst->clear(); + return inst; + } + + /// Allocate a matrix, valid until ::clear called. + MatrixF* allocUniqueXform(const MatrixF& data) + { + MatrixF *r = mChunker.alloc(); + *r = data; + return r; + } + + enum SharedTransformType + { + View, + Projection, + }; + + const MatrixF* allocSharedXform(SharedTransformType stt); + + void assignSharedXform(SharedTransformType stt, const MatrixF &xfm); + + MatrixSet &getMatrixSet() { return *mMatrixSet; } + + /// Allocate a GFXPrimitive object which will remain valid + /// until the pass manager is cleared. + GFXPrimitive* allocPrim() { return mChunker.alloc(); } + /// @} + + /// Add a RenderInstance to the list + virtual void addInst( RenderInst *inst ); + + /// Sorts the list of RenderInst's per bin. (Normally, one should just call renderPass) + void sort(); + + /// Renders the list of RenderInsts (Normally, one should just call renderPass) + void render( SceneState *state ); + + /// Resets our allocated RenderInstances and Matrices. (Normally, one should just call renderPass) + void clear(); + + // Calls sort, render, and clear + void renderPass( SceneState *state ); + + /// Returns the active depth buffer for this pass (NOTE: This value may be GFXTextureTarget::sDefaultDepthStencil) + GFXTextureObject *getDepthTargetTexture(); + + /// Assigns the value for the above method + void setDepthTargetTexture(GFXTextureObject *zTarget); + + /// @name RenderBinManager interface + /// @{ + + /// Add a render bin manager to the list of render bin manager, this SceneRenderPassManager now owns the render bin manager and will free it when needed. + /// @param mgr Render manager to add + /// @param processAddOrder Where to add the manager in the addInst list, set to NO_PROCESSADD to skip processing + /// this is in place for RenderManagers that will bypass the main ::addInst interface and doesn't want to process + /// them. + /// @param renderOrder Where to add the manager in the render list. + void addManager(RenderBinManager* mgr); + + /// Removes a manager from render and process add lists + /// @param mgr Render bin manager to remove, the caller is now responsible for freeing the mgr. + void removeManager(RenderBinManager* mgr); + + /// How many render bin managers do we have? + U32 getManagerCount() const { return mRenderBins.size(); } + + /// Get the render manager at i + RenderBinManager* getManager( S32 i ) const; + + /// @} + + /// Get scene manager which this render pass belongs to. + SceneGraph* getSceneManager() + { + if ( !mSceneManager ) + mSceneManager = gClientSceneGraph; + + return mSceneManager; + } + + /// This signal is triggered when a render bin is about to be rendered. + /// + /// @param bin The render bin we're signaling. + /// @param state The current scene state. + /// @params preRender If true it is before the bin is rendered, else its + /// after being rendered. + /// + typedef Signal RenderBinEventSignal; + + /// @see RenderBinEventSignal + static RenderBinEventSignal& getRenderBinSignal(); + + // ConsoleObject interface + static void initPersistFields(); + DECLARE_CONOBJECT(RenderPassManager); + + #ifndef TORQUE_SHIPPING + + // Performance tracking for render passes. + void resetCounters(); + U32 getAddInstCounter() const { return mAddInstCount; } + U32 getAddInstPassCounter() const { return mAddInstPassCount; } + + #endif + +protected: + + //------------------------------------- + // data + //------------------------------------- + MultiTypedChunker mChunker; + Vector< RenderBinManager* > mRenderBins; + Vector< RenderBinManager* > mAddBins; + SceneGraph * mSceneManager; + GFXTexHandle mDepthBuff; + MatrixSet *mMatrixSet; + + #ifndef TORQUE_SHIPPING + + U32 mAddInstCount; ///< for debugging - incremented every call to addInst() + U32 mAddInstPassCount; ///< for debugging - incremented every call to addElement() in addInst() + Vector mAddBinInstCounts; + + #endif + + /// Do a sorted insert into a vector, renderOrder bool controls which test we run for insertion. + void _insertSort(Vector& list, RenderBinManager* mgr, bool renderOrder); +}; + +//************************************************************************** +// Render Instance +//************************************************************************** +struct RenderInst +{ + /// The type of render instance this is. + RenderInstTypeHash type; + + /// This should be true if the object needs to be sorted + /// back to front with other translucent instances. + /// @see sortDistSq + bool translucentSort; + + /// The reference squared distance from the camera used for + /// back to front sorting of the instances. + /// @see translucentSort + F32 sortDistSq; + + /// The default key used by render managers for + /// internal sorting. + U32 defaultKey; + + /// The secondary key used by render managers for + /// internal sorting. + U32 defaultKey2; + + /// Does a memset to clear the render instance. + void clear(); +}; + +struct ObjectRenderInst : public RenderInst +{ + /// This is a delegate specific index which is usually + /// used to define a mounted object. + S32 objectIndex; + + /// Extra data to be used within the render callback. + /// ObjectRenderInst does not own or cleanup this data. + void *userData; + + /// The delegate callback function to call to render + /// this object instance. + /// + /// @param ri The ObjectRenderInst that called the delegate. + /// + /// @param state The scene state we're rendering. + /// + /// @param overrideMat An alternative material to use during rendering... usually + /// used for special renders like shadows. If the object doesn't + /// support override materials it shouldn't render at all. + Delegate renderDelegate; + + // Clear this instance. + void clear(); +}; + +struct MeshRenderInst : public RenderInst +{ + //// + GFXVertexBufferHandleBase *vertBuff; + + //// + GFXPrimitiveBufferHandle *primBuff; + + /// If not NULL it is used to draw the primitive, else + /// the primBuffIndex is used. + /// @see primBuffIndex + GFXPrimitive *prim; + + /// If prim is NULL then this index is used to draw the + /// indexed primitive from the primitive buffer. + /// @see prim + U32 primBuffIndex; + + /// The material to setup when drawing this instance. + BaseMatInstance *matInst; + + /// The object to world transform (world transform in most API's). + const MatrixF *objectToWorld; + + /// The worldToCamera (view transform in most API's). + const MatrixF* worldToCamera; + + /// The projection matrix. + const MatrixF* projection; + + // misc render states + U8 transFlags; + bool reflective; + F32 visibility; + + /// A generic hint value passed from the game + /// code down to the material for use by shader + /// features. + void *materialHint; + + /// The lights we pass to the material for this + /// mesh in order light importance. + LightInfo* lights[8]; + + // textures + GFXTextureObject *lightmap; + GFXTextureObject *fogTex; + GFXTextureObject *backBuffTex; + GFXTextureObject *reflectTex; + GFXTextureObject *miscTex; + GFXCubemap *cubemap; + + void clear(); +}; + + +/// A special render instance for particles. +struct ParticleRenderInst : public RenderInst +{ + /// The vertex buffer. + GFXVertexBufferHandleBase *vertBuff; + + /// The primitive buffer. + GFXPrimitiveBufferHandle *primBuff; + + /// The total particle count to render. + S32 count; + + /// The combined model, camera, and projection transform. + const MatrixF *modelViewProj; + + /// Blend style for the particle system + enum BlendStyle { + BlendUndefined = 0, + BlendNormal, + BlendAdditive, + BlendSubtractive, + BlendPremultAlpha, + BlendGreyscale, + BlendStyle_COUNT, + }; + U8 blendStyle; + + /// For the offscreen particle manager + U8 targetIndex; + + /// State for the particle system + enum ParticleSystemState + { + AwaitingHighResDraw = 0, // Keep this as first element so that if the offscreen manager rejects a particle system it will get drawn high-res + AwaitingOffscreenDraw, + AwaitingCompositeDraw, + AwaitingMixedResDraw, + DrawComplete, + } systemState; + + /// The soft particle fade distance in meters. + F32 softnessDistance; + + /// Bounding box render transform + const MatrixF *bbModelViewProj; + + /// Particle system bounding sphere in world space + SphereF systemSphere; + + /// Light map + GFXTexHandle particleLightmap; + + /// The particle texture. + GFXTextureObject *diffuseTex; + + void clear(); +}; + +class GFXOcclusionQuery; +class SceneObject; + +/// A special render instance for occlusion tests. +struct OccluderRenderInst : public RenderInst +{ + Point3F scale; + Point3F position; + const MatrixF *orientation; + GFXOcclusionQuery *query; + + // This optional query will have all pixels rendered. + // Its purpose is to return to the user the full pixel count for comparison + // with the other query. + GFXOcclusionQuery *query2; + + /// Render a sphere or a box. + bool isSphere; + + void clear(); +}; + +#endif // _RENDERPASSMANAGER_H_ diff --git a/renderInstance/renderPassStateToken.cpp b/renderInstance/renderPassStateToken.cpp new file mode 100644 index 0000000..de53423 --- /dev/null +++ b/renderInstance/renderPassStateToken.cpp @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderInstance/renderPassStateToken.h" +#include "console/consoleTypes.h" + +IMPLEMENT_CONOBJECT(RenderPassStateToken); + +void RenderPassStateToken::process(SceneState *state, RenderPassStateBin *callingBin) +{ + TORQUE_UNUSED(state); + TORQUE_UNUSED(callingBin); + AssertWarn(false, "RenderPassStateToken is an abstract class, you must re-implement process()"); +} + +void RenderPassStateToken::reset() +{ + AssertWarn(false, "RenderPassStateToken is an abstract class, you must re-implement reset()"); +} + +void RenderPassStateToken::enable( bool enabled /*= true*/ ) +{ + TORQUE_UNUSED(enabled); + AssertWarn(false, "RenderPassStateToken is an abstract class, you must re-implement enable()"); +} + +bool RenderPassStateToken::isEnabled() const +{ + AssertWarn(false, "RenderPassStateToken is an abstract class, you must re-implement isEnabled()"); + return false; +} + +static bool _set_enable(void* obj, const char* data) +{ + reinterpret_cast(obj)->enable(dAtob(data)); + return false; +} + +static const char *_get_enable(void* obj, const char* data) +{ + TORQUE_UNUSED(data); + return reinterpret_cast(obj)->isEnabled() ? "true" : "false"; +} + +void RenderPassStateToken::initPersistFields() +{ + addProtectedField("enabled", TypeBool, NULL, &_set_enable, &_get_enable, "Enables or disables this token."); + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +IMPLEMENT_CONOBJECT(RenderPassStateBin); + +RenderPassStateBin::RenderPassStateBin() : Parent() +{ + // Disable adding instances + setProcessAddOrder(RenderPassManager::PROCESSADD_NONE); +} + +RenderPassStateBin::~RenderPassStateBin() +{ + +} + +RenderBinManager::AddInstResult RenderPassStateBin::addElement( RenderInst *inst ) +{ + return RenderBinManager::arSkipped; +} + +void RenderPassStateBin::render(SceneState *state) +{ + if(mStateToken.isValid()) + mStateToken->process(state, this); +} + +void RenderPassStateBin::clear() +{ + if(mStateToken.isValid()) + mStateToken->reset(); +} + +void RenderPassStateBin::sort() +{ + +} + +void RenderPassStateBin::initPersistFields() +{ + addField("stateToken", TypeSimObjectPtr, Offset(mStateToken, RenderPassStateBin)); + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ + +ConsoleMethod(RenderPassStateToken, enable, void, 2, 2, "") +{ + object->enable(true); +} + +ConsoleMethod(RenderPassStateToken, disable, void, 2, 2, "") +{ + object->enable(false); +} + +ConsoleMethod(RenderPassStateToken, toggle, void, 2, 2, "") +{ + object->enable(!object->isEnabled()); +} \ No newline at end of file diff --git a/renderInstance/renderPassStateToken.h b/renderInstance/renderPassStateToken.h new file mode 100644 index 0000000..e767526 --- /dev/null +++ b/renderInstance/renderPassStateToken.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "renderInstance/renderBinManager.h" + +class RenderPassStateBin; + +class RenderPassStateToken : public SimObject +{ + typedef SimObject Parent; + +public: + DECLARE_CONOBJECT(RenderPassStateToken); + + static void initPersistFields(); + + // These must be re-implemented, and will assert if called on the base class. + // They just can't be pure-virtual, due to SimObject. + virtual void process(SceneState *state, RenderPassStateBin *callingBin); + virtual void reset(); + virtual void enable(bool enabled = true); + virtual bool isEnabled() const; +}; + +//------------------------------------------------------------------------------ + +class RenderPassStateBin : public RenderBinManager +{ + typedef RenderBinManager Parent; + +protected: + SimObjectPtr mStateToken; + +public: + DECLARE_CONOBJECT(RenderPassStateBin); + static void initPersistFields(); + + RenderPassStateBin(); + virtual ~RenderPassStateBin(); + + AddInstResult addElement( RenderInst *inst ); + void render(SceneState *state); + void clear(); + void sort(); +}; diff --git a/renderInstance/renderPrePassMgr.cpp b/renderInstance/renderPrePassMgr.cpp new file mode 100644 index 0000000..b0debb7 --- /dev/null +++ b/renderInstance/renderPrePassMgr.cpp @@ -0,0 +1,765 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderPrePassMgr.h" + +#include "gfx/gfxTransformSaver.h" +#include "materials/sceneData.h" +#include "materials/materialManager.h" +#include "materials/materialFeatureTypes.h" +#include "core/util/safeDelete.h" +#include "shaderGen/featureMgr.h" +#include "shaderGen/HLSL/depthHLSL.h" +#include "shaderGen/GLSL/depthGLSL.h" +#include "shaderGen/conditionerFeature.h" +#include "shaderGen/shaderGenVars.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "gfx/gfxDebugEvent.h" +#include "materials/customMaterialDefinition.h" +#include "lighting/advanced/advancedLightManager.h" +#include "lighting/advanced/advancedLightBinManager.h" +#include "terrain/terrCell.h" +#include "renderInstance/renderTerrainMgr.h" +#include "terrain/terrCellMaterial.h" +#include "math/mathUtils.h" +#include "math/util/matrixSet.h" + +const MatInstanceHookType PrePassMatInstanceHook::Type( "PrePass" ); +const String RenderPrePassMgr::BufferName("prepass"); +const RenderInstType RenderPrePassMgr::RIT_PrePass("PrePass"); + +IMPLEMENT_CONOBJECT(RenderPrePassMgr); + + +RenderPrePassMgr::RenderSignal& RenderPrePassMgr::getRenderSignal() +{ + static RenderSignal theSignal; + return theSignal; +} + + +RenderPrePassMgr::RenderPrePassMgr( bool gatherDepth, + GFXFormat format, + const RenderInstType riType ) + : Parent( riType, + 0.01f, + 0.01f, + format, + Point2I( Parent::DefaultTargetSize, Parent::DefaultTargetSize), + gatherDepth ? Parent::DefaultTargetChainLength : 0 ), + mPrePassMatInstance( NULL ) +{ + // We want a full-resolution buffer + mTargetSizeType = RenderTexTargetBinManager::WindowSize; + + if(getTargetChainLength() > 0) + { + GFXShader::addGlobalMacro( "TORQUE_LINEAR_DEPTH" ); + MatTextureTarget::registerTarget( BufferName, this ); + } + + _registerFeatures(); +} + +RenderPrePassMgr::~RenderPrePassMgr() +{ + GFXShader::removeGlobalMacro( "TORQUE_LINEAR_DEPTH" ); + MatTextureTarget::unregisterTarget( BufferName, this ); + + _unregisterFeatures(); + SAFE_DELETE( mPrePassMatInstance ); +} + +void RenderPrePassMgr::_registerFeatures() +{ +#ifndef TORQUE_DEDICATED + if(GFX->getAdapterType() == OpenGL) + { +#ifdef TORQUE_OS_MAC + FEATUREMGR->registerFeature(MFT_PrePassConditioner, new LinearEyeDepthConditioner(getTargetFormat()) ); +#endif + } + else + { +#ifndef TORQUE_OS_MAC + FEATUREMGR->registerFeature(MFT_PrePassConditioner, new LinearEyeDepthConditioner(getTargetFormat()) ); +#endif + } +#endif +} + +void RenderPrePassMgr::_unregisterFeatures() +{ + FEATUREMGR->unregisterFeature(MFT_PrePassConditioner); +} + +ConditionerFeature *RenderPrePassMgr::getTargetConditioner() const +{ + return dynamic_cast(FEATUREMGR->getByType(MFT_PrePassConditioner)); +} + +bool RenderPrePassMgr::setTargetSize(const Point2I &newTargetSize) +{ + bool ret = Parent::setTargetSize( newTargetSize ); + mTargetViewport = GFX->getViewport(); + return ret; +} + +bool RenderPrePassMgr::_updateTargets() +{ + PROFILE_SCOPE(RenderPrePassMgr_updateTargets); + + bool ret = Parent::_updateTargets(); +#ifndef TORQUE_DEDICATED + // check for an output conditioner, and update it's format + ConditionerFeature *outputConditioner = dynamic_cast(FEATUREMGR->getByType(MFT_PrePassConditioner)); + + if( outputConditioner && outputConditioner->setBufferFormat(mTargetFormat) ) + { + // reload materials, the conditioner needs to alter the generated shaders + } + + // Attach the light info buffer as a second render target, if there is + // lightmapped geometry in the scene. + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + if(lightBin->MRTLightmapsDuringPrePass()) + { + if(lightBin->isProperlyAdded()) + { + // Update the size of the light bin target here. This will call _updateTargets + // on the light bin + ret &= lightBin->setTargetSize(mTargetSize); + if(ret) + { + // Sanity check + AssertFatal(lightBin->getTargetChainLength() == mTargetChainLength, "Target chain length mismatch"); + + // Attach light info buffer to Color1 for each target in the chain + for(U32 i = 0; i < mTargetChainLength; i++) + { + GFXTexHandle lightInfoTex = lightBin->getTargetTexture(0, i); + mTargetChain[i]->attachTexture(GFXTextureTarget::Color1, lightInfoTex); + } + } + } + } + } +#endif + return ret; +} + +void RenderPrePassMgr::_createPrePassMaterial() +{ + SAFE_DELETE(mPrePassMatInstance); + + const GFXVertexFormat *vertexFormat = getGFXVertexFormat(); + + MatInstance* prepassMat = static_cast(MATMGR->createMatInstance("AL_DefaultPrePassMaterial", vertexFormat)); + AssertFatal( prepassMat, "TODO: Handle this better." ); + mPrePassMatInstance = new PrePassMatInstance(prepassMat, this); + mPrePassMatInstance->init( MATMGR->getDefaultFeatures(), vertexFormat); + delete prepassMat; +} + +void RenderPrePassMgr::setPrePassMaterial( PrePassMatInstance *mat ) +{ + SAFE_DELETE(mPrePassMatInstance); + mPrePassMatInstance = mat; +} + +RenderBinManager::AddInstResult RenderPrePassMgr::addElement( RenderInst *inst ) +{ + // Check for a custom refract type + BaseMatInstance* matInst = getMaterial(inst); + CustomMaterial* custMat = NULL; + + if( matInst ) + custMat = dynamic_cast(matInst->getMaterial()); + + if ( ( inst->type == RenderPassManager::RIT_Mesh || + inst->type == RenderPassManager::RIT_Decal || + inst->type == RenderPassManager::RIT_Object || + inst->type == RenderPassManager::RIT_Terrain || + inst->type == RenderPassManager::RIT_Interior ) && + ( custMat == NULL || !custMat->mRefract ) ) + { + internalAddElement( inst ); + + if ( inst->type == RenderPassManager::RIT_Mesh || + inst->type == RenderPassManager::RIT_Decal || + inst->type == RenderPassManager::RIT_Interior ) + { + MeshRenderInst *meshRI = static_cast(inst); + + // Check for a Pre-Pass Mat Hook. If one doesn't exist, create it. + if( meshRI->matInst && !meshRI->matInst->getHook( PrePassMatInstanceHook::Type ) ) + { + MatInstance *m = reinterpret_cast(meshRI->matInst); + if ( m ) + meshRI->matInst->addHook( new PrePassMatInstanceHook( m, this ) ); + } + } + + return arAdded; + } + + return arSkipped; +} + +void RenderPrePassMgr::internalAddElement( RenderInst* inst ) +{ + mElementList.increment(); + MainSortElem &elem = mElementList.last(); + elem.inst = inst; + + U32 originalKey = elem.key; + + // Ignore the default key, and instead sort front-to-back first for a pre-pass + const F32 invSortDistSq = F32_MAX - inst->sortDistSq; + elem.key = *((U32*)&invSortDistSq); + + // Next sort by pre-pass material, if applicable + MeshRenderInst *meshRI = static_cast(inst); + if ( ( inst->type == RenderPassManager::RIT_Mesh || + inst->type == RenderPassManager::RIT_Interior ) && meshRI->matInst ) + elem.key2 = U32(meshRI->matInst->getHook( PrePassMatInstanceHook::Type )); + else + elem.key2 = originalKey; +} + +void RenderPrePassMgr::render( SceneState *state ) +{ + PROFILE_SCOPE(RenderPrePassMgr_render); + + // NOTE: We don't early out here when the element list is + // zero because we need the prepass to be cleared. + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + GFXDEBUGEVENT_SCOPE( RenderPrePassMgr_Render, ColorI::RED ); + + // Tell the superclass we're about to render + const bool isRenderingToTarget = _onPreRender(state); + + + // Clear all the buffers to white so that the + // default depth is to the far plane. + GFX->clear( GFXClearTarget | GFXClearZBuffer | GFXClearStencil, ColorI::WHITE, 1.0f, 0); + + // init loop data + SceneGraphData sgData; + + if ( !mPrePassMatInstance ) + _createPrePassMaterial(); + + // Restore transforms + MatrixSet &matrixSet = getParentManager()->getMatrixSet(); + matrixSet.restoreSceneViewProjection(); + + const MatrixF worldViewXfm = GFX->getWorldMatrix(); + + // Set transforms for the default pre-pass material + if( mPrePassMatInstance ) + { + matrixSet.setWorld(MatrixF::Identity); + mPrePassMatInstance->setTransforms(matrixSet, state); + } + + // Signal start of pre-pass + getRenderSignal().trigger( state, this, true ); + + // Render mesh objects + for( Vector< MainSortElem >::const_iterator itr = mElementList.begin(); + itr != mElementList.end(); itr++) + { + RenderInst *renderInst = itr->inst; + + if ( renderInst->type == RenderPassManager::RIT_Mesh || + renderInst->type == RenderPassManager::RIT_Decal || + renderInst->type == RenderPassManager::RIT_Interior ) + { + MeshRenderInst* meshRI = static_cast(renderInst); + + AssertFatal( meshRI->matInst->getHook( PrePassMatInstanceHook::Type ), "This should not happen." ); + AssertFatal( dynamic_cast( meshRI->matInst->getHook( PrePassMatInstanceHook::Type ) ), "This should also not happen." ); + + PrePassMatInstanceHook *prePassHook = static_cast( meshRI->matInst->getHook( PrePassMatInstanceHook::Type ) ); + BaseMatInstance *mat = prePassHook->getPrePassMatInstance(); + + // Set up SG data proper like, and flag that this is a pre-pass render + setupSGData( meshRI, sgData ); + sgData.binType = SceneGraphData::PrePassBin; + + matrixSet.setWorld(*meshRI->objectToWorld); + matrixSet.setView(*meshRI->worldToCamera); + matrixSet.setProjection(*meshRI->projection); + + while( mat->setupPass(state, sgData) ) + { + mat->setSceneInfo(state, sgData); + mat->setTransforms(matrixSet, state); + + mat->setBuffers(meshRI->vertBuff, meshRI->primBuff); + + if ( meshRI->prim ) + GFX->drawPrimitive( *meshRI->prim ); + else + GFX->drawPrimitive( meshRI->primBuffIndex ); + } + } + else if( renderInst->type == RenderPassManager::RIT_Terrain ) + { + // TODO: Move to RenderTerrainMgr and use the signal. + + TerrainRenderInst *terrainRI = static_cast( renderInst ); + + TerrainCellMaterial *mat = terrainRI->cellMat->getPrePass(); + + GFX->setPrimitiveBuffer( terrainRI->primBuff ); + GFX->setVertexBuffer( terrainRI->vertBuff ); + + mat->setTransformAndEye( *terrainRI->objectToWorldXfm, + worldViewXfm, + GFX->getProjectionMatrix(), + state->getFarPlane() ); + + // The terrain doesn't need any scene graph data + // in the the prepass... so just clear it. + sgData.reset(); + sgData.binType = SceneGraphData::PrePassBin; + sgData.wireframe = GFXDevice::getWireframe(); + + while ( mat->setupPass( state, sgData ) ) + GFX->drawPrimitive( terrainRI->prim ); + } + else if ( renderInst->type == RenderPassManager::RIT_Object && + mPrePassMatInstance != NULL ) + { + ObjectRenderInst *ri = static_cast(renderInst); + if (ri->renderDelegate) + ri->renderDelegate(ri, state, mPrePassMatInstance); + } + } + + // Signal end of pre-pass + getRenderSignal().trigger( state, this, false ); + + if(isRenderingToTarget) + _onPostRender(); +} + +const GFXStateBlockDesc & RenderPrePassMgr::getOpaqueStenciWriteDesc( bool lightmappedGeometry /*= true*/ ) +{ + static bool sbInit = false; + static GFXStateBlockDesc sOpaqueStaticLitStencilWriteDesc; + static GFXStateBlockDesc sOpaqueDynamicLitStencilWriteDesc; + + if(!sbInit) + { + sbInit = true; + + // Build the static opaque stencil write/test state block descriptions + sOpaqueStaticLitStencilWriteDesc.stencilDefined = true; + sOpaqueStaticLitStencilWriteDesc.stencilEnable = true; + sOpaqueStaticLitStencilWriteDesc.stencilWriteMask = 0x03; + sOpaqueStaticLitStencilWriteDesc.stencilMask = 0x03; + sOpaqueStaticLitStencilWriteDesc.stencilRef = RenderPrePassMgr::OpaqueStaticLitMask; + sOpaqueStaticLitStencilWriteDesc.stencilPassOp = GFXStencilOpReplace; + sOpaqueStaticLitStencilWriteDesc.stencilFailOp = GFXStencilOpKeep; + sOpaqueStaticLitStencilWriteDesc.stencilZFailOp = GFXStencilOpKeep; + sOpaqueStaticLitStencilWriteDesc.stencilFunc = GFXCmpAlways; + + // Same only dynamic + sOpaqueDynamicLitStencilWriteDesc = sOpaqueStaticLitStencilWriteDesc; + sOpaqueDynamicLitStencilWriteDesc.stencilRef = RenderPrePassMgr::OpaqueDynamicLitMask; + } + + return (lightmappedGeometry ? sOpaqueStaticLitStencilWriteDesc : sOpaqueDynamicLitStencilWriteDesc); +} + +const GFXStateBlockDesc & RenderPrePassMgr::getOpaqueStencilTestDesc() +{ + static bool sbInit = false; + static GFXStateBlockDesc sOpaqueStencilTestDesc; + + if(!sbInit) + { + // Build opaque test + sbInit = true; + sOpaqueStencilTestDesc.stencilDefined = true; + sOpaqueStencilTestDesc.stencilEnable = true; + sOpaqueStencilTestDesc.stencilWriteMask = 0xFE; + sOpaqueStencilTestDesc.stencilMask = 0x03; + sOpaqueStencilTestDesc.stencilRef = 0; + sOpaqueStencilTestDesc.stencilPassOp = GFXStencilOpKeep; + sOpaqueStencilTestDesc.stencilFailOp = GFXStencilOpKeep; + sOpaqueStencilTestDesc.stencilZFailOp = GFXStencilOpKeep; + sOpaqueStencilTestDesc.stencilFunc = GFXCmpLess; + } + + return sOpaqueStencilTestDesc; +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + +ProcessedPrePassMaterial::ProcessedPrePassMaterial( Material& mat, const RenderPrePassMgr *prePassMgr ) +: Parent(mat), mPrePassMgr(prePassMgr) +{ + +} + +void ProcessedPrePassMaterial::_determineFeatures( U32 stageNum, + MaterialFeatureData &fd, + const FeatureSet &features ) +{ + Parent::_determineFeatures( stageNum, fd, features ); + + // Find this for use down below... + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + bool bEnableMRTLightmap = false; + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + bEnableMRTLightmap = lightBin->MRTLightmapsDuringPrePass(); + } + + // If this material has a lightmap or tonemap (texture or baked vertex color), + // it must be static. Otherwise it is dynamic. + mIsLightmappedGeometry = ( fd.features.hasFeature( MFT_ToneMap ) || + fd.features.hasFeature( MFT_LightMap ) || + fd.features.hasFeature( MFT_VertLit ) || + ( bEnableMRTLightmap && fd.features.hasFeature( MFT_IsTranslucent ) || + fd.features.hasFeature( MFT_IsTranslucentZWrite ) ) ); + + // Integrate proper opaque stencil write state + mUserDefined.addDesc( mPrePassMgr->getOpaqueStenciWriteDesc( mIsLightmappedGeometry ) ); + + FeatureSet newFeatures; + + // These are always on for prepass. + newFeatures.addFeature( MFT_EyeSpaceDepthOut ); + newFeatures.addFeature( MFT_PrePassConditioner ); + +#ifndef TORQUE_DEDICATED + + for ( U32 i=0; i < fd.features.getCount(); i++ ) + { + const FeatureType &type = fd.features.getAt( i ); + + // Turn on the diffuse texture only if we + // have alpha test. + if ( type == MFT_AlphaTest ) + { + newFeatures.addFeature( MFT_AlphaTest ); + newFeatures.addFeature( MFT_DiffuseMap ); + } + + else if ( type == MFT_IsTranslucentZWrite ) + { + newFeatures.addFeature( MFT_IsTranslucentZWrite ); + newFeatures.addFeature( MFT_DiffuseMap ); + } + + // Always allow these. + else if ( type == MFT_IsDXTnm || + type == MFT_TexAnim || + type == MFT_NormalMap || + type == MFT_AlphaTest || + type == MFT_Parallax ) + newFeatures.addFeature( type ); + + // Add any transform features. + else if ( type.getGroup() == MFG_PreTransform || + type.getGroup() == MFG_Transform || + type.getGroup() == MFG_PostTransform ) + newFeatures.addFeature( type ); + } + + // If there is lightmapped geometry support, add the MRT light buffer features + if(bEnableMRTLightmap) + { + // If this material has a lightmap, pass it through, and flag it to + // send it's output to RenderTarget1 + if( fd.features.hasFeature( MFT_ToneMap ) ) + { + newFeatures.addFeature( MFT_ToneMap ); + newFeatures.addFeature( MFT_LightbufferMRT ); + } + else if( fd.features.hasFeature( MFT_LightMap ) ) + { + newFeatures.addFeature( MFT_LightMap ); + newFeatures.addFeature( MFT_LightbufferMRT ); + } + else if( fd.features.hasFeature( MFT_VertLit ) ) + { + // Flag un-tone-map if necesasary + if( fd.features.hasFeature( MFT_DiffuseMap ) ) + newFeatures.addFeature( MFT_VertLitTone ); + + newFeatures.addFeature( MFT_VertLit ); + newFeatures.addFeature( MFT_LightbufferMRT ); + } + else + { + // If this object isn't lightmapped, add a zero-output feature to it + newFeatures.addFeature( MFT_RenderTarget1_Zero ); + } + } + +#endif + + // Set the new features. + fd.features = newFeatures; +} + +U32 ProcessedPrePassMaterial::getNumStages() +{ + // Return 1 stage so this material gets processed for sure + return 1; +} + +void ProcessedPrePassMaterial::addStateBlockDesc(const GFXStateBlockDesc& desc) +{ + GFXStateBlockDesc prePassStateBlock = desc; + + // Adjust color writes if this is a pure z-fill pass + const bool pixelOutEnabled = mPrePassMgr->getTargetChainLength() > 0; + if ( !pixelOutEnabled ) + { + prePassStateBlock.colorWriteDefined = true; + prePassStateBlock.colorWriteRed = pixelOutEnabled; + prePassStateBlock.colorWriteGreen = pixelOutEnabled; + prePassStateBlock.colorWriteBlue = pixelOutEnabled; + prePassStateBlock.colorWriteAlpha = pixelOutEnabled; + } + + // Never allow the alpha test state when rendering + // the prepass as we use the alpha channel for the + // depth information... MFT_AlphaTest will handle it. + prePassStateBlock.alphaDefined = true; + prePassStateBlock.alphaTestEnable = false; + + // If we're translucent then we're doing prepass blending + // which never writes to the depth channels. + const bool isTranslucent = getMaterial()->isTranslucent(); + if ( isTranslucent ) + { + prePassStateBlock.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha ); + prePassStateBlock.setColorWrites( true, true, false, false ); + } + + // Enable z reads, but only enable zwrites if we're not translucent. + prePassStateBlock.setZReadWrite( true, isTranslucent ? false : true ); + + // Pass to parent + Parent::addStateBlockDesc(prePassStateBlock); +} + +PrePassMatInstance::PrePassMatInstance(MatInstance* root, const RenderPrePassMgr *prePassMgr) +: Parent(*root->getMaterial()), mPrePassMgr(prePassMgr) +{ + mFeatureList = root->getRequestedFeatures(); + mVertexFormat = root->getVertexFormat(); +} + +PrePassMatInstance::~PrePassMatInstance() +{ +} + +ProcessedMaterial* PrePassMatInstance::getShaderMaterial() +{ + return new ProcessedPrePassMaterial(*mMaterial, mPrePassMgr); +} + +bool PrePassMatInstance::init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat ) +{ + return Parent::init( features, vertexFormat ); +} + +PrePassMatInstanceHook::PrePassMatInstanceHook( MatInstance *baseMatInst, + const RenderPrePassMgr *prePassMgr ) + : mHookedPrePassMatInst(NULL), mPrePassManager(prePassMgr) +{ + // If the material is a custom material then + // hope that using DefaultPrePassMaterial gives + // them a good prepass. + if ( dynamic_cast(baseMatInst->getMaterial()) ) + { + MatInstance* dummyInst = static_cast( MATMGR->createMatInstance( "AL_DefaultPrePassMaterial", baseMatInst->getVertexFormat() ) ); + + mHookedPrePassMatInst = new PrePassMatInstance( dummyInst, prePassMgr ); + mHookedPrePassMatInst->init( dummyInst->getRequestedFeatures(), baseMatInst->getVertexFormat()); + + delete dummyInst; + return; + } + + mHookedPrePassMatInst = new PrePassMatInstance(baseMatInst, prePassMgr); + mHookedPrePassMatInst->init(baseMatInst->getRequestedFeatures(), baseMatInst->getVertexFormat()); +} + +PrePassMatInstanceHook::~PrePassMatInstanceHook() +{ + SAFE_DELETE(mHookedPrePassMatInst); +} + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +void LinearEyeDepthConditioner::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // find depth + ShaderFeature *depthFeat = FEATUREMGR->getByType( MFT_EyeSpaceDepthOut ); + AssertFatal( depthFeat != NULL, "No eye space depth feature found!" ); + + Var *depth = (Var*) LangElement::find(depthFeat->getOutputVarName()); + AssertFatal( depth, "Something went bad with ShaderGen. The depth should be already generated by the EyeSpaceDepthOut feature." ); + + MultiLine *meta = new MultiLine; + + meta->addStatement( assignOutput( depth ) ); + + output = meta; +} + +Var *LinearEyeDepthConditioner::_conditionOutput( Var *unconditionedOutput, MultiLine *meta ) +{ + Var *retVar = NULL; + + String fracMethodName = (GFX->getAdapterType() == OpenGL) ? "fract" : "frac"; + + switch(getBufferFormat()) + { + case GFXFormatR8G8B8A8: + retVar = new Var; + retVar->setType("float4"); + retVar->setName("_ppDepth"); + meta->addStatement( new GenOp( " // depth conditioner: packing to rgba\r\n" ) ); + meta->addStatement( new GenOp( + avar( " @ = %s(@ * (255.0/256) * float4(1, 255, 255 * 255, 255 * 255 * 255));\r\n", fracMethodName.c_str() ), + new DecOp(retVar), unconditionedOutput ) ); + break; + default: + retVar = unconditionedOutput; + meta->addStatement( new GenOp( " // depth conditioner: no conditioning\r\n" ) ); + break; + } + + AssertFatal( retVar != NULL, avar( "Cannot condition output to buffer format: %s", GFXStringTextureFormat[getBufferFormat()] ) ); + return retVar; +} + +Var *LinearEyeDepthConditioner::_unconditionInput( Var *conditionedInput, MultiLine *meta ) +{ + String float4Typename = (GFX->getAdapterType() == OpenGL) ? "vec4" : "float4"; + + Var *retVar = conditionedInput; + if(getBufferFormat() != GFXFormat_COUNT) + { + retVar = new Var; + retVar->setType(float4Typename.c_str()); + retVar->setName("_ppDepth"); + meta->addStatement( new GenOp( avar( " @ = %s(0, 0, 1, 1);\r\n", float4Typename.c_str() ), new DecOp(retVar) ) ); + + switch(getBufferFormat()) + { + case GFXFormatR32F: + case GFXFormatR16F: + meta->addStatement( new GenOp( " // depth conditioner: float texture\r\n" ) ); + meta->addStatement( new GenOp( " @.w = @.r;\r\n", retVar, conditionedInput ) ); + break; + + case GFXFormatR8G8B8A8: + meta->addStatement( new GenOp( " // depth conditioner: unpacking from rgba\r\n" ) ); + meta->addStatement( new GenOp( + avar( " @.w = dot(@ * (256.0/255), %s(1, 1 / 255, 1 / (255 * 255), 1 / (255 * 255 * 255)));\r\n", float4Typename.c_str() ) + , retVar, conditionedInput ) ); + break; + default: + AssertFatal(false, "LinearEyeDepthConditioner::_unconditionInput - Unrecognized buffer format"); + } + } + + return retVar; +} + +Var* LinearEyeDepthConditioner::printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ) +{ + const bool isCondition = ( methodType == ConditionerFeature::ConditionMethod ); + + Var *retVal = NULL; + + // The uncondition method inputs are changed + if( isCondition ) + retVal = Parent::printMethodHeader( methodType, methodName, stream, meta ); + else + { + Var *methodVar = new Var; + methodVar->setName(methodName); + if (GFX->getAdapterType() == OpenGL) + methodVar->setType("vec4"); + else + methodVar->setType("inline float4"); + DecOp *methodDecl = new DecOp(methodVar); + + Var *prepassSampler = new Var; + prepassSampler->setName("prepassSamplerVar"); + prepassSampler->setType("sampler2D"); + DecOp *prepassSamplerDecl = new DecOp(prepassSampler); + + Var *screenUV = new Var; + screenUV->setName("screenUVVar"); + if (GFX->getAdapterType() == OpenGL) + screenUV->setType("vec2"); + else + screenUV->setType("float2"); + DecOp *screenUVDecl = new DecOp(screenUV); + + Var *bufferSample = new Var; + bufferSample->setName("bufferSample"); + if (GFX->getAdapterType() == OpenGL) + bufferSample->setType("vec4"); + else + bufferSample->setType("float4"); + DecOp *bufferSampleDecl = new DecOp(bufferSample); + + meta->addStatement( new GenOp( "@(@, @)\r\n", methodDecl, prepassSamplerDecl, screenUVDecl ) ); + + meta->addStatement( new GenOp( "{\r\n" ) ); + + meta->addStatement( new GenOp( " // Sampler g-buffer\r\n" ) ); + + // The linear depth target has no mipmaps, so use tex2dlod when + // possible so that the shader compiler can optimize. + meta->addStatement( new GenOp( " #if TORQUE_SM >= 30\r\n" ) ); + if (GFX->getAdapterType() == OpenGL) + meta->addStatement( new GenOp( " @ = texture2DLod(@, @, 0); \r\n", bufferSampleDecl, prepassSampler, screenUV) ); + else + meta->addStatement( new GenOp( " @ = tex2Dlod(@, float4(@,0,0));\r\n", bufferSampleDecl, prepassSampler, screenUV ) ); + meta->addStatement( new GenOp( " #else\r\n" ) ); + if (GFX->getAdapterType() == OpenGL) + meta->addStatement( new GenOp( " @ = texture2D(@, @);\r\n", bufferSampleDecl, prepassSampler, screenUV) ); + else + meta->addStatement( new GenOp( " @ = tex2D(@, @);\r\n", bufferSampleDecl, prepassSampler, screenUV ) ); + meta->addStatement( new GenOp( " #endif\r\n\r\n" ) ); + + // We don't use this way of passing var's around, so this should cause a crash + // if something uses this improperly + retVal = bufferSample; + } + + return retVal; +} diff --git a/renderInstance/renderPrePassMgr.h b/renderInstance/renderPrePassMgr.h new file mode 100644 index 0000000..4fff61b --- /dev/null +++ b/renderInstance/renderPrePassMgr.h @@ -0,0 +1,171 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _PREPASS_MGR_H_ +#define _PREPASS_MGR_H_ + +#include "renderInstance/renderTexTargetBinManager.h" +#include "materials/matInstance.h" +#include "materials/processedShaderMaterial.h" +#include "shaderGen/conditionerFeature.h" +#include "core/util/autoPtr.h" + +// Forward declare +class PrePassMatInstance; + +// This render manager renders opaque objects to the z-buffer as a z-fill pass. +// It can optionally accumulate data from this opaque render pass into a render +// target for later use. +class RenderPrePassMgr : public RenderTexTargetBinManager +{ + typedef RenderTexTargetBinManager Parent; + +public: + + // registered buffer name + static const String BufferName; + + // Generic PrePass Render Instance Type + static const RenderInstType RIT_PrePass; + + RenderPrePassMgr( bool gatherDepth = true, + GFXFormat format = GFXFormatR16G16B16A16, + const RenderInstType riType = RIT_PrePass ); + + virtual ~RenderPrePassMgr(); + + virtual void setPrePassMaterial( PrePassMatInstance *mat ); + + // RenderBinManager interface + virtual void render(SceneState * state); + virtual RenderBinManager::AddInstResult addElement( RenderInst *inst ); + + // ConsoleObject + DECLARE_CONOBJECT(RenderPrePassMgr); + + + typedef Signal RenderSignal; + + static RenderSignal& getRenderSignal(); + + static const U32 OpaqueStaticLitMask = BIT(1); ///< Stencil mask for opaque, lightmapped pixels + static const U32 OpaqueDynamicLitMask = BIT(0); ///< Stencil mask for opaque, dynamic lit pixels + + static const GFXStateBlockDesc &getOpaqueStencilTestDesc(); + static const GFXStateBlockDesc &getOpaqueStenciWriteDesc(bool lightmappedGeometry = true); + + virtual ConditionerFeature *getTargetConditioner() const; + + virtual bool setTargetSize(const Point2I &newTargetSize); + +protected: + + PrePassMatInstance *mPrePassMatInstance; + + MatInstance *mTerrainPrepassMatInstance; + MaterialParameterHandle *mTerrainConstVEye; + + virtual void _registerFeatures(); + virtual void _unregisterFeatures(); + virtual bool _updateTargets(); + virtual void _createPrePassMaterial(); + + bool _lightManagerActivate(bool active); + virtual void internalAddElement(RenderInst* inst); +}; + +//------------------------------------------------------------------------------ + +class ProcessedPrePassMaterial : public ProcessedShaderMaterial +{ + typedef ProcessedShaderMaterial Parent; + +public: + ProcessedPrePassMaterial(Material& mat, const RenderPrePassMgr *prePassMgr); + + virtual U32 getNumStages(); + + virtual void addStateBlockDesc(const GFXStateBlockDesc& desc); + +protected: + virtual void _determineFeatures( U32 stageNum, MaterialFeatureData &fd, const FeatureSet &features ); + + const RenderPrePassMgr *mPrePassMgr; + bool mIsLightmappedGeometry; +}; + +//------------------------------------------------------------------------------ + +class PrePassMatInstance : public MatInstance +{ + typedef MatInstance Parent; + +public: + PrePassMatInstance(MatInstance* root, const RenderPrePassMgr *prePassMgr); + virtual ~PrePassMatInstance(); + + bool init() + { + return init( mFeatureList, mVertexFormat ); + } + + // MatInstance + virtual bool init( const FeatureSet &features, + const GFXVertexFormat *vertexFormat ); + +protected: + virtual ProcessedMaterial* getShaderMaterial(); + + const RenderPrePassMgr *mPrePassMgr; +}; + +//------------------------------------------------------------------------------ + +class PrePassMatInstanceHook : public MatInstanceHook +{ +public: + PrePassMatInstanceHook(MatInstance *baseMatInst, const RenderPrePassMgr *prePassMgr); + virtual ~PrePassMatInstanceHook(); + + virtual PrePassMatInstance *getPrePassMatInstance() { return mHookedPrePassMatInst; } + + virtual const MatInstanceHookType& getType() const { return Type; } + + /// The type for prepass material hooks. + static const MatInstanceHookType Type; + +protected: + PrePassMatInstance *mHookedPrePassMatInst; + const RenderPrePassMgr *mPrePassManager; +}; + +//------------------------------------------------------------------------------ + +// A very simple, default depth conditioner feature +class LinearEyeDepthConditioner : public ConditionerFeature +{ + typedef ConditionerFeature Parent; + +public: + LinearEyeDepthConditioner(const GFXFormat bufferFormat) + : Parent(bufferFormat) + { + + } + + virtual String getName() + { + return "Linear Eye-Space Depth Conditioner"; + } + + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); +protected: + virtual Var *_conditionOutput( Var *unconditionedOutput, MultiLine *meta ); + virtual Var *_unconditionInput( Var *conditionedInput, MultiLine *meta ); + + virtual Var *printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ); +}; + +#endif // _PREPASS_MGR_H_ + diff --git a/renderInstance/renderTerrainMgr.cpp b/renderInstance/renderTerrainMgr.cpp new file mode 100644 index 0000000..1094df8 --- /dev/null +++ b/renderInstance/renderTerrainMgr.cpp @@ -0,0 +1,174 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderTerrainMgr.h" + +#include "platform/profiler.h" +#include "sceneGraph/sceneGraph.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDrawUtil.h" +#include "gfx/gfxDebugEvent.h" +#include "materials/shaderData.h" +#include "materials/matInstance.h" +#include "sceneGraph/sceneState.h" +#include "console/consoleTypes.h" +#include "terrain/terrCell.h" +#include "terrain/terrCellMaterial.h" +#include "math/util/matrixSet.h" + +bool RenderTerrainMgr::smRenderWireframe = false; +bool RenderTerrainMgr::smRenderCellBounds = false; +S32 RenderTerrainMgr::smForcedDetailShader = -1; + +S32 RenderTerrainMgr::smCellsRendered = 0; +S32 RenderTerrainMgr::smOverrideCells = 0; +S32 RenderTerrainMgr::smDrawCalls = 0; + + +IMPLEMENT_CONOBJECT(RenderTerrainMgr); + +RenderTerrainMgr::RenderTerrainMgr() + : RenderBinManager( RenderPassManager::RIT_Terrain, 1.0f, 1.0f ) +{ +} + +RenderTerrainMgr::RenderTerrainMgr( F32 renderOrder, F32 processAddOrder ) + : RenderBinManager( RenderPassManager::RIT_Terrain, renderOrder, processAddOrder ) +{ +} + +RenderTerrainMgr::~RenderTerrainMgr() +{ +} + +void RenderTerrainMgr::initPersistFields() +{ + Con::addVariable( "RenderTerrainMgr::renderWireframe", TypeBool, &smRenderWireframe ); + Con::addVariable( "RenderTerrainMgr::renderCellBounds", TypeBool, &smRenderCellBounds ); + Con::addVariable( "RenderTerrainMgr::forceDetailShader", TypeS32, &smForcedDetailShader ); + + // For stats. + GFXDevice::getDeviceEventSignal().notify( &RenderTerrainMgr::_clearStats ); + Con::addVariable( "$TerrainBlock::cellsRendered", TypeS32, &smCellsRendered ); + Con::addVariable( "$TerrainBlock::overrideCells", TypeS32, &smOverrideCells ); + Con::addVariable( "$TerrainBlock::drawCalls", TypeS32, &smDrawCalls ); + + Parent::initPersistFields(); +} + +bool RenderTerrainMgr::_clearStats( GFXDevice::GFXDeviceEventType type ) +{ + if ( type == GFXDevice::deStartOfFrame ) + { + smCellsRendered = 0; + smOverrideCells = 0; + smDrawCalls = 0; + } + + return true; +} + +void RenderTerrainMgr::internalAddElement( RenderInst *inst_ ) +{ + TerrainRenderInst *inst = static_cast( inst_ ); + + mInstVector.push_back( inst ); +} + +void RenderTerrainMgr::sort() +{ + // TODO: We could probably sort this in some + // manner to improve terrain rendering perf. +} + +void RenderTerrainMgr::clear() +{ + mInstVector.clear(); +} + +void RenderTerrainMgr::render( SceneState *state ) +{ + if ( mInstVector.empty() ) + return; + + PROFILE_SCOPE( RenderTerrainMgr_Render ); + + GFXTransformSaver saver; + + // Prepare the common scene graph data. + SceneGraphData sgData; + sgData.setFogParams( state->getSceneManager()->getFogData() ); + sgData.objTrans.identity(); + sgData.visibility = 1.0f; + sgData.wireframe = smRenderWireframe || GFXDevice::getWireframe(); + + // Restore transforms + MatrixSet &matrixSet = getParentManager()->getMatrixSet(); + matrixSet.restoreSceneViewProjection(); + + GFXDEBUGEVENT_SCOPE( RenderTerrainMgr_Render, ColorI::GREEN ); + + const MatrixF worldViewXfm = matrixSet.getWorldToCamera(); + const MatrixF &projXfm = matrixSet.getCameraToScreen(); + + const U32 binSize = mInstVector.size(); + + // If we have an override delegate then do a special loop! + if ( !mMatOverrideDelegate.empty() ) + { + PROFILE_SCOPE( RenderTerrainMgr_Render_OverrideMat ); + + BaseMatInstance *overideMat = mMatOverrideDelegate( mInstVector[0]->mat ); + while ( overideMat && overideMat->setupPass( state, sgData ) ) + { + for ( U32 i=0; i < binSize; i++ ) + { + smOverrideCells++; + + const TerrainRenderInst *inst = mInstVector[i]; + + GFX->setPrimitiveBuffer( inst->primBuff ); + GFX->setVertexBuffer( inst->vertBuff ); + + matrixSet.setWorld(*inst->objectToWorldXfm); + + overideMat->setSceneInfo( state, sgData ); + overideMat->setTransforms( matrixSet, state ); + + GFX->drawPrimitive( inst->prim ); + } + } + + return; + } + + // Do the detail map passes. + for ( U32 i=0; i < binSize; i++ ) + { + const TerrainRenderInst *inst = mInstVector[i]; + + TerrainCellMaterial *mat = inst->cellMat; + + GFX->setPrimitiveBuffer( inst->primBuff ); + GFX->setVertexBuffer( inst->vertBuff ); + + ++smCellsRendered; + + mat->setTransformAndEye( *inst->objectToWorldXfm, + worldViewXfm, + projXfm, + state->getFarPlane() ); + + sgData.objTrans = *inst->objectToWorldXfm; + dMemcpy( sgData.lights, inst->lights, sizeof( sgData.lights ) ); + + while ( mat->setupPass( state, sgData ) ) + { + ++smDrawCalls; + GFX->drawPrimitive( inst->prim ); + } + } +} + diff --git a/renderInstance/renderTerrainMgr.h b/renderInstance/renderTerrainMgr.h new file mode 100644 index 0000000..e11a609 --- /dev/null +++ b/renderInstance/renderTerrainMgr.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// Copyright (C) Sickhead Games, LLC +//----------------------------------------------------------------------------- + +#ifndef _RENDERTERRAINMGR_H_ +#define _RENDERTERRAINMGR_H_ + +#ifndef _RENDERBINMANAGER_H_ +#include "renderInstance/renderBinManager.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + +class TerrCell; +class GFXTextureObject; +class TerrainCellMaterial; + + +/// The render instance for terrain cells. +struct TerrainRenderInst : public RenderInst +{ + GFXVertexBuffer *vertBuff; + + GFXPrimitiveBuffer *primBuff; + + GFXPrimitive prim; + + BaseMatInstance *mat; + + const MatrixF *objectToWorldXfm; + + TerrainCellMaterial *cellMat; + + /// The lights we pass to the material for + /// this cell in order light importance. + LightInfo *lights[8]; + + void clear() + { + dMemset( this, 0, sizeof( TerrainRenderInst ) ); + type = RenderPassManager::RIT_Terrain; + } +}; + + +/// +class RenderTerrainMgr : public RenderBinManager +{ + typedef RenderBinManager Parent; + +protected: + + Vector mInstVector; + + static bool smRenderWireframe; + static bool smRenderCellBounds; + static S32 smForcedDetailShader; + + static S32 smCellsRendered; + static S32 smOverrideCells; + static S32 smDrawCalls; + + static bool _clearStats( GFXDevice::GFXDeviceEventType type ); + + // RenderBinManager + virtual void internalAddElement( RenderInst *inst ); + +public: + + RenderTerrainMgr(); + RenderTerrainMgr( F32 renderOrder, F32 processAddOrder ); + virtual ~RenderTerrainMgr(); + + // ConsoleObject + static void initPersistFields(); + DECLARE_CONOBJECT(RenderTerrainMgr); + + // RenderBinManager + virtual void sort(); + virtual void render( SceneState *state ); + virtual void clear(); + +}; + +#endif // _RENDERTERRAINMGR_H_ diff --git a/renderInstance/renderTexTargetBinManager.cpp b/renderInstance/renderTexTargetBinManager.cpp new file mode 100644 index 0000000..92cb428 --- /dev/null +++ b/renderInstance/renderTexTargetBinManager.cpp @@ -0,0 +1,353 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderTexTargetBinManager.h" + +#include "shaderGen/conditionerFeature.h" +#include "core/util/safeDelete.h" +#include "gfx/gfxTextureManager.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "lighting/shadowMap/blurTexture.h" // TODO: Move/generalize this file + +IMPLEMENT_CONOBJECT(RenderTexTargetBinManager); + + +RenderTexTargetBinManager::RenderTexTargetBinManager(const GFXFormat targetFormat /* = DefaultTargetFormat */, + const Point2I &targetSize /* = Point2I */, + const U32 targetChainLength /* = DefaultTargetChainLength */) +: Parent(), mTargetFormat(targetFormat), mTargetSize(targetSize), mTargetScale(1.0f, 1.0f), mTargetSizeType(FixedSize), + mTargetChainLength(targetChainLength), mTargetChainIdx(0), mNumRenderTargets(1), + mTargetChain(NULL), mTargetChainTextures(NULL), mBlur(NULL), mScratchTexture(NULL), +#ifndef TORQUE_SHIPPING + m_NeedsOnPostRender(false) +#endif +{ + GFXDevice::getDeviceEventSignal().notify(this, &RenderTexTargetBinManager::_handleGFXEvent); + GFXTextureManager::addEventDelegate( this, &RenderTexTargetBinManager::_onTextureEvent ); + + _setupTargets(); +} + +//------------------------------------------------------------------------------ + +RenderTexTargetBinManager::RenderTexTargetBinManager(const RenderInstType& ritype, F32 renderOrder, F32 processAddOrder, + const GFXFormat targetFormat /* = DefaultTargetFormat */, + const Point2I &targetSize /* = Point2I */, + const U32 targetChainLength /* = DefaultTargetChainLength */) +: Parent(ritype, renderOrder, processAddOrder), mTargetFormat(targetFormat), mTargetSize(targetSize), mTargetScale(1.0f, 1.0f), mTargetSizeType(FixedSize), + mTargetChainLength(targetChainLength), mTargetChainIdx(0), mNumRenderTargets(1), + mTargetChain(NULL), mTargetChainTextures(NULL), mBlur(NULL), mScratchTexture(NULL), +#ifndef TORQUE_SHIPPING + m_NeedsOnPostRender(false) +#endif +{ + GFXDevice::getDeviceEventSignal().notify(this, &RenderTexTargetBinManager::_handleGFXEvent); + GFXTextureManager::addEventDelegate( this, &RenderTexTargetBinManager::_onTextureEvent ); +} + +//------------------------------------------------------------------------------ + +RenderTexTargetBinManager::~RenderTexTargetBinManager() +{ + _teardownTargets(); + + GFXTextureManager::removeEventDelegate( this, &RenderTexTargetBinManager::_onTextureEvent ); + GFXDevice::getDeviceEventSignal().remove(this, &RenderTexTargetBinManager::_handleGFXEvent); + SAFE_DELETE(mBlur); + SAFE_DELETE(mScratchTexture); +} + +//------------------------------------------------------------------------------ + +bool RenderTexTargetBinManager::onAdd() +{ + if(!Parent::onAdd()) + return false; + + _setupTargets(); + + return true; +} + +//------------------------------------------------------------------------------ + +static EnumTable::Enums gSizeTypeEnums[] = +{ + { RenderTexTargetBinManager::WindowSize, "windowsize" }, + { RenderTexTargetBinManager::WindowSizeScaled, "windowsizescaled" }, + { RenderTexTargetBinManager::FixedSize, "fixedsize" }, +}; +EnumTable gSizeTypeEnumTable( 2, gSizeTypeEnums ); + +void RenderTexTargetBinManager::initPersistFields() +{ + // TOM_TODO: + //addField( "targetScale", mTargetScale ); + //addPropertyNOPS( "targetSizeType", mTargetSizeType)->setEnumTable(gSizeTypeEnumTable); + //addPropertyNOPS( "targetFormat")->setEnumTable(gTextureFormatEnumTable)->addGet(this, &RenderTexTargetBinManager::getTargetFormatConsole)->addSet(this, &RenderTexTargetBinManager::setTargetFormatConsole); + //addProperty( "blur" )->addSet(this, &RenderTexTargetBinManager::setBlur)->addGet(this, &RenderTexTargetBinManager::getBlur); + + Parent::initPersistFields(); +} + +//------------------------------------------------------------------------------ + +bool RenderTexTargetBinManager::setTargetSize(const Point2I &newTargetSize) +{ + if ( mTargetSize.x >= newTargetSize.x && + mTargetSize.y >= newTargetSize.y ) + return true; + + mTargetSize = newTargetSize; + mTargetViewport.set( Point2I::Zero, mTargetSize ); + + return _updateTargets(); +} + +//------------------------------------------------------------------------------ + +bool RenderTexTargetBinManager::setTargetFormat(const GFXFormat &newTargetFormat) +{ + if(mTargetFormat == newTargetFormat) + return true; + + mTargetFormat = newTargetFormat; + ConditionerFeature *conditioner = getTargetConditioner(); + if(conditioner) + conditioner->setBufferFormat(mTargetFormat); + + return _updateTargets(); +} + +//------------------------------------------------------------------------------ + +void RenderTexTargetBinManager::setTargetChainLength(const U32 chainLength) +{ + if(mTargetChainLength != chainLength) + { + mTargetChainLength = chainLength; + _setupTargets(); + } +} + +GFXTextureObject* RenderTexTargetBinManager::getTargetTexture( U32 mrtIndex, S32 chainIndex ) const +{ + const U32 chainIdx = ( chainIndex > -1 ) ? chainIndex : mTargetChainIdx; + if(chainIdx < mTargetChainLength) + return mTargetChainTextures[chainIdx][mrtIndex]; + return NULL; +} + +//------------------------------------------------------------------------------ + +bool RenderTexTargetBinManager::_updateTargets() +{ + PROFILE_SCOPE(RenderTexTargetBinManager_updateTargets); + + bool ret = true; + + // Update the target size + for( U32 i = 0; i < mTargetChainLength; i++ ) + { + if( !mTargetChain[i] ) + mTargetChain[i] = GFX->allocRenderToTextureTarget(); + + for( U32 j = 0; j < mNumRenderTargets; j++ ) + { + ret &= mTargetChainTextures[i][j].set( mTargetSize.x, mTargetSize.y, mTargetFormat, + &GFXDefaultRenderTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ), + 1, GFXTextureManager::AA_MATCH_BACKBUFFER ); + + mTargetChain[i]->attachTexture( GFXTextureTarget::RenderSlot(GFXTextureTarget::Color0 + j), mTargetChainTextures[i][j] ); + } + } + + // Update the scratch texture + if(mScratchTexture) + ret &= mScratchTexture->set( mTargetSize.x, mTargetSize.y, mTargetFormat, + &GFXDefaultRenderTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); + + return ret; +} + +//------------------------------------------------------------------------------ + +bool RenderTexTargetBinManager::_handleGFXEvent(GFXDevice::GFXDeviceEventType event) +{ +#define _SWAP_CHAIN_HELPER(swapchainidx, swapchainsz) { swapchainidx++; swapchainidx = swapchainidx < swapchainsz ? swapchainidx : 0; } + switch(event) + { + case GFXDevice::deStartOfFrame: + _SWAP_CHAIN_HELPER( mTargetChainIdx, mTargetChainLength ); + break; + default: + break; + } +#undef _SWAP_CHAIN_HELPER + + return true; +} + +//------------------------------------------------------------------------------ + +void RenderTexTargetBinManager::_onTextureEvent( GFXTexCallbackCode code ) +{ + switch(code) + { + case GFXZombify: + _teardownTargets(); + break; + + case GFXResurrect: + _setupTargets(); + break; + } +} + +//------------------------------------------------------------------------------ + +bool RenderTexTargetBinManager::_setupTargets() +{ + _teardownTargets(); + + mTargetChain = new GFXTextureTargetRef[mTargetChainLength]; + mTargetChainTextures = new GFXTexHandle*[mTargetChainLength]; + + for( U32 i = 0; i < mTargetChainLength; i++ ) + mTargetChainTextures[i] = new GFXTexHandle[mNumRenderTargets]; + + mTargetChainIdx = 0; + + mTargetSize = Point2I::Zero; + //_updateTargets(); + + return true; +} + +//------------------------------------------------------------------------------ + +void RenderTexTargetBinManager::_teardownTargets() +{ + SAFE_DELETE_ARRAY(mTargetChain); + if(mTargetChainTextures != NULL) + { + for( U32 i = 0; i < mTargetChainLength; i++ ) + SAFE_DELETE_ARRAY(mTargetChainTextures[i]); + } + SAFE_DELETE_ARRAY(mTargetChainTextures); +} + +//------------------------------------------------------------------------------ + +GFXTextureTargetRef RenderTexTargetBinManager::_getTextureTarget(const U32 idx /* = 0 */) +{ + return mTargetChain[idx]; +} + +//------------------------------------------------------------------------------ + +bool RenderTexTargetBinManager::_onPreRender(SceneState * state, bool preserve /* = false */) +{ + PROFILE_SCOPE(RenderTexTargetBinManager_onPreRender); + +#ifndef TORQUE_SHIPPING + AssertFatal( m_NeedsOnPostRender == false, "_onPostRender not called on RenderTexTargetBinManager, or sub-class." ); + m_NeedsOnPostRender = false; +#endif + + // Update the render target size + const Point2I &rtSize = GFX->getActiveRenderTarget()->getSize(); + switch(mTargetSizeType) + { + case WindowSize: + setTargetSize(rtSize); + break; + case WindowSizeScaled: + { + Point2I scaledTargetSize(mFloor(rtSize.x * mTargetScale.x), mFloor(rtSize.y * mTargetScale.y)); + setTargetSize(scaledTargetSize); + break; + } + case FixedSize: + // No adjustment necessary + break; + } + + if( mTargetChainLength == 0 ) + return false; + + GFXTextureTargetRef binTarget = _getTextureTarget(mTargetChainIdx); + + if( binTarget.isNull() ) + return false; + + // Attach active depth target texture + binTarget->attachTexture(GFXTextureTarget::DepthStencil, getParentManager()->getDepthTargetTexture()); + + // Preserve contents + if(preserve) + GFX->getActiveRenderTarget()->preserve(); + + GFX->pushActiveRenderTarget(); + GFX->setActiveRenderTarget(binTarget); + GFX->setViewport(mTargetViewport); + +#ifndef TORQUE_SHIPPING + m_NeedsOnPostRender = true; +#endif + + return true; +} + +//------------------------------------------------------------------------------ + +void RenderTexTargetBinManager::_onPostRender() +{ + PROFILE_SCOPE(RenderTexTargetBinManager_onPostRender); + +#ifndef TORQUE_SHIPPING + m_NeedsOnPostRender = false; +#endif + GFXTextureTargetRef binTarget = _getTextureTarget(mTargetChainIdx); + binTarget->resolve(); + + GFX->popActiveRenderTarget(); + + // Apply blur + if(mBlur && mScratchTexture) + mBlur->blur( getTargetTexture( 0 ), *mScratchTexture ); +} + +void RenderTexTargetBinManager::setBlur( const bool enableBlur ) +{ + if(enableBlur == (mBlur != NULL)) + return; + + SAFE_DELETE(mBlur); + SAFE_DELETE(mScratchTexture); +#ifndef TORQUE_DEDICATED + if(enableBlur) + { + mBlur = new BlurOp; + mBlur->init("BlurDepthShader", mTargetSize.x, mTargetSize.y); + mScratchTexture = new GFXTexHandle; + _updateTargets(); + } +#endif +} + +//------------------------------------------------------------------------------ + +void RenderTexTargetBinManager::setupSamplerState( GFXSamplerStateDesc *desc ) const +{ + desc->addressModeU = GFXAddressClamp; + desc->addressModeV = GFXAddressClamp; + desc->minFilter = GFXTextureFilterPoint; + desc->magFilter = GFXTextureFilterPoint; + desc->mipFilter = GFXTextureFilterPoint; +} diff --git a/renderInstance/renderTexTargetBinManager.h b/renderInstance/renderTexTargetBinManager.h new file mode 100644 index 0000000..5291bee --- /dev/null +++ b/renderInstance/renderTexTargetBinManager.h @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _TEXTARGETBIN_MGR_H_ +#define _TEXTARGETBIN_MGR_H_ + +#ifndef _RENDERBINMANAGER_H_ +#include "renderInstance/renderBinManager.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif + +class ConditionerFeature; +class BlurOp; + + +class RenderTexTargetBinManager : public RenderBinManager, public MatTextureTarget +{ + typedef RenderBinManager Parent; + +public: + static const GFXFormat DefaultTargetFormat = GFXFormatR8G8B8A8; + static const U32 DefaultTargetChainLength = 1; + static const U32 DefaultTargetSize = 256; + + enum TargetSizeType + { + WindowSize = 0, + WindowSizeScaled, + FixedSize + }; + +public: + + RenderTexTargetBinManager(const GFXFormat targetFormat = DefaultTargetFormat, + const Point2I &targetSize = Point2I(DefaultTargetSize, DefaultTargetSize), + const U32 targetChainLength = DefaultTargetChainLength ); + RenderTexTargetBinManager(const RenderInstType& ritype, F32 renderOrder, F32 processAddOrder, + const GFXFormat targetFormat = DefaultTargetFormat, + const Point2I &targetSize = Point2I(DefaultTargetSize, DefaultTargetSize), + const U32 targetChainLength = DefaultTargetChainLength); + + virtual ~RenderTexTargetBinManager(); + + // MatTextureTarget + virtual const Point2I &getTargetSize() const { return mTargetSize; } + virtual const RectI& getTargetViewport() const { return mTargetViewport; } + virtual GFXTextureObject* getTargetTexture( U32 mrtIndex ) const { return getTargetTexture(mrtIndex, -1); } + virtual GFXTextureObject* getTargetTexture( U32 mrtIndex, S32 chainIndex ) const; + virtual void setupSamplerState( GFXSamplerStateDesc *desc ) const; + virtual ConditionerFeature* getTargetConditioner() const { return NULL; } + + virtual bool setTargetSize(const Point2I &newTargetSize); + + /// Force a target update + virtual bool updateTargets() { return _updateTargets(); } + + void setTargetFormatConsole(const S32 &fmt) { setTargetFormat(GFXFormat(fmt)); } + virtual bool setTargetFormat(const GFXFormat &newTargetFormat); + + S32 getTargetFormatConsole() { return getTargetFormat(); } + virtual const GFXFormat &getTargetFormat() const { return mTargetFormat; } + + virtual void setTargetChainLength(const U32 chainLength); + virtual U32 getTargetChainLength() const { return mTargetChainLength; } + + virtual void setBlur(bool enableBlur); + virtual bool getBlur() const { return mBlur != NULL; } + + DECLARE_CONOBJECT(RenderTexTargetBinManager); + static void initPersistFields(); + virtual bool onAdd(); + +protected: + + GFXFormat mTargetFormat; + Point2I mTargetSize; + Point2F mTargetScale; + RectI mTargetViewport; + TargetSizeType mTargetSizeType; + + U32 mTargetChainLength; + U32 mTargetChainIdx; + U32 mNumRenderTargets; + GFXTextureTargetRef *mTargetChain; + GFXTexHandle **mTargetChainTextures; + BlurOp *mBlur; + GFXTexHandle *mScratchTexture; + +#ifndef TORQUE_SHIPPING + bool m_NeedsOnPostRender; +#endif + bool mPreserve; + + virtual bool _handleGFXEvent(GFXDevice::GFXDeviceEventType event); + virtual GFXTextureTargetRef _getTextureTarget(const U32 idx = 0); + + /// Pushes the active render target, and sets itself as a render target. The + /// target is then cleared using 'mTargetClearColor', viewport is set properly, + /// and true is returned, and '_onPostRender' must be called after rendering + /// is complete. If the return value is false, than '_onPostRender' + /// should not be called. + /// + /// @param preserve If set to true, the contents of the current render target + // will be the same when _onPostRender is called. Otherwise + // the contents are undefined on console platforms. + virtual bool _onPreRender(SceneState * state, bool preserve = false); + + /// Resolves the active render target, pops the render target from _onPreRender, and sets debug info. + virtual void _onPostRender(); + + virtual bool _updateTargets(); + + virtual bool _setupTargets(); + + virtual void _teardownTargets(); + + /// The callback used to get texture events. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ); +}; + +#endif // _TEXTARGETBIN_MGR_H_ \ No newline at end of file diff --git a/renderInstance/renderTranslucentMgr.cpp b/renderInstance/renderTranslucentMgr.cpp new file mode 100644 index 0000000..4b047b0 --- /dev/null +++ b/renderInstance/renderTranslucentMgr.cpp @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "renderInstance/renderTranslucentMgr.h" + +#include "materials/sceneData.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneObject.h" +#include "sceneGraph/sceneState.h" +#include "materials/matInstance.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "renderInstance/renderParticleMgr.h" +#include "math/util/matrixSet.h" + +#define HIGH_NUM ((U32(-1)/2) - 1) + +IMPLEMENT_CONOBJECT(RenderTranslucentMgr); + + +RenderTranslucentMgr::RenderTranslucentMgr() + : RenderBinManager( RenderPassManager::RIT_Custom, 1.0f, 1.0f ), mParticleRenderMgr(NULL) +{ + +} + +RenderTranslucentMgr::~RenderTranslucentMgr() +{ + +} + +void RenderTranslucentMgr::setupSGData(MeshRenderInst *ri, SceneGraphData &data ) +{ + Parent::setupSGData(ri, data); + + data.backBuffTex = NULL; + data.cubemap = NULL; + data.lightmap = NULL; +} + +RenderBinManager::AddInstResult RenderTranslucentMgr::addElement( RenderInst *inst ) +{ + // See if we support this instance type. + if ( inst->type != RenderPassManager::RIT_ObjectTranslucent && + inst->type != RenderPassManager::RIT_Translucent && + inst->type != RenderPassManager::RIT_Particle) + return RenderBinManager::arSkipped; + + // See if this instance is translucent. + if (!inst->translucentSort) + return RenderBinManager::arSkipped; + + BaseMatInstance* matInst = getMaterial(inst); + bool translucent = (!matInst || matInst->getMaterial()->isTranslucent()); + if (!translucent) + return RenderBinManager::arSkipped; + + // We made it this far, add the instance. + mElementList.increment(); + MainSortElem& elem = mElementList.last(); + elem.inst = inst; + + // Override the instances default key to be the sort distance. All + // the pointer dereferencing is in there to prevent us from losing + // information when converting to a U32. + elem.key = *((U32*)&inst->sortDistSq); + + AssertFatal( inst->defaultKey != 0, "RenderTranslucentMgr::addElement() - Got null sort key... did you forget to set it?" ); + + // Then use the instances primary key as our secondary key + elem.key2 = inst->defaultKey; + + // We are the only thing to handle translucent "things" right now. + return RenderBinManager::arStop; +} + +GFXStateBlockRef RenderTranslucentMgr::_getStateBlock( U8 transFlag ) +{ + if ( mStateBlocks[transFlag].isValid() ) + return mStateBlocks[transFlag]; + + GFXStateBlockDesc d; + + d.cullDefined = true; + d.cullMode = GFXCullNone; + d.blendDefined = true; + d.blendEnable = true; + d.blendSrc = (GFXBlend)((transFlag >> 4) & 0x0f); + d.blendDest = (GFXBlend)(transFlag & 0x0f); + d.alphaDefined = true; + + // See http://www.garagegames.com/mg/forums/result.thread.php?qt=81397 + d.alphaTestEnable = (d.blendSrc == GFXBlendSrcAlpha && (d.blendDest == GFXBlendInvSrcAlpha || d.blendDest == GFXBlendOne)); + d.alphaTestRef = 1; + d.alphaTestFunc = GFXCmpGreaterEqual; + d.zDefined = true; + d.zWriteEnable = false; + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + d.samplers[0].alphaOp = GFXTOPModulate; + d.samplers[0].alphaArg1 = GFXTATexture; + d.samplers[0].alphaArg2 = GFXTADiffuse; + + mStateBlocks[transFlag] = GFX->createStateBlock(d); + return mStateBlocks[transFlag]; +} + +void RenderTranslucentMgr::render( SceneState *state ) +{ + PROFILE_SCOPE(RenderTranslucentMgr_render); + + // Early out if nothing to draw. + if(!mElementList.size()) + return; + + GFXDEBUGEVENT_SCOPE(RenderTranslucentMgr_Render, ColorI::BLUE); + + // Find the particle render manager (if we don't have it) + if(mParticleRenderMgr == NULL) + { + RenderPassManager *rpm = state->getRenderPass(); + for( U32 i = 0; i < rpm->getManagerCount(); i++ ) + { + RenderBinManager *bin = rpm->getManager(i); + if( bin->getRenderInstType() == RenderParticleMgr::RIT_Particles ) + { + mParticleRenderMgr = reinterpret_cast(bin); + break; + } + } + } + + GFXTransformSaver saver; + + SceneGraphData sgData; + + GFXVertexBuffer * lastVB = NULL; + GFXPrimitiveBuffer * lastPB = NULL; + + // Restore transforms + MatrixSet &matrixSet = getParentManager()->getMatrixSet(); + matrixSet.restoreSceneViewProjection(); + + U32 binSize = mElementList.size(); + for( U32 j=0; jtype == RenderPassManager::RIT_ObjectTranslucent ) + { + ObjectRenderInst* objRI = static_cast(baseRI); + objRI->renderDelegate( objRI, state, NULL ); + + lastVB = NULL; + lastPB = NULL; + j++; + continue; + } + else if ( baseRI->type == RenderPassManager::RIT_Particle ) + { + ParticleRenderInst *ri = static_cast(baseRI); + + // Tell Particle RM to draw the system. (This allows the particle render manager + // to manage drawing offscreen particle systems, and allows the systems + // to be composited back into the scene with proper translucent + // sorting order) + mParticleRenderMgr->renderInstance(ri, state); + + lastVB = NULL; // no longer valid, null it + lastPB = NULL; // no longer valid, null it + + j++; + continue; + } + else if ( baseRI->type == RenderPassManager::RIT_Translucent ) + { + MeshRenderInst* ri = static_cast(baseRI); + BaseMatInstance *mat = ri->matInst; + + // .ifl? + if( !mat ) + { + GFX->setStateBlock( _getStateBlock( ri->transFlags ) ); + + GFX->pushWorldMatrix(); + + GFX->setWorldMatrix(*ri->objectToWorld ); + + GFX->setTexture( 0, ri->miscTex ); + GFX->setPrimitiveBuffer( *ri->primBuff ); + GFX->setVertexBuffer( *ri->vertBuff ); + GFX->disableShaders(); + GFX->setupGenericShaders( GFXDevice::GSModColorTexture ); + GFX->drawPrimitive( ri->primBuffIndex ); + + GFX->popWorldMatrix(); + + lastVB = NULL; // no longer valid, null it + lastPB = NULL; // no longer valid, null it + + j++; + continue; + } + + setupSGData( ri, sgData ); + + bool firstmatpass = true; + while( mat->setupPass( state, sgData ) ) + { + U32 a; + for( a=j; atype != RenderPassManager::RIT_Translucent ) + break; + + MeshRenderInst *passRI = static_cast(nextRI); + + // if new matInst is null or different, break + // The visibility check prevents mesh elements with different visibility from being + // batched together. This can happen when visibility is animated in the dts model. + if (newPassNeeded(mat, passRI) || passRI->visibility != ri->visibility) + break; + + // Z sorting and stuff is still not working in this mgr... + setupSGData( passRI, sgData ); + mat->setSceneInfo(state, sgData); + matrixSet.setWorld(*passRI->objectToWorld); + matrixSet.setView(*passRI->worldToCamera); + matrixSet.setProjection(*passRI->projection); + mat->setTransforms(matrixSet, state); + mat->setBuffers(passRI->vertBuff, passRI->primBuff); + + // draw it + if ( passRI->prim ) + GFX->drawPrimitive( *passRI->prim ); + else + GFX->drawPrimitive( passRI->primBuffIndex ); + } + + matListEnd = a; + firstmatpass = false; + } + + // force increment if none happened, otherwise go to end of batch + j = ( j == matListEnd ) ? j+1 : matListEnd; + } + } +} \ No newline at end of file diff --git a/renderInstance/renderTranslucentMgr.h b/renderInstance/renderTranslucentMgr.h new file mode 100644 index 0000000..d431634 --- /dev/null +++ b/renderInstance/renderTranslucentMgr.h @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _RENDER_TRANSLUCENT_MGR_H_ +#define _RENDER_TRANSLUCENT_MGR_H_ + +#ifndef _RENDERBINMANAGER_H_ +#include "renderInstance/renderBinManager.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif + +class RenderParticleMgr; + +class RenderTranslucentMgr : public RenderBinManager +{ + typedef RenderBinManager Parent; + +public: + + RenderTranslucentMgr(); + RenderTranslucentMgr( F32 renderOrder, F32 processAddOrder ); + virtual ~RenderTranslucentMgr(); + + // RenderBinManager + void render(SceneState * state); + AddInstResult addElement( RenderInst *inst ); + void setupSGData( MeshRenderInst *ri, SceneGraphData &data ); + + // ConsoleObject + DECLARE_CONOBJECT(RenderTranslucentMgr); + +protected: + // This is a stateblock per potential blend type, we create + // these as needed. + enum + { + MaxBlend = 256 + }; + GFXStateBlockRef mStateBlocks[MaxBlend]; + + GFXStateBlockRef _getStateBlock( U8 transFlag ); + RenderParticleMgr *mParticleRenderMgr;; +}; + + +#endif // _RENDER_TRANSLUCENT_MGR_H_ diff --git a/sceneGraph/fogStructs.h b/sceneGraph/fogStructs.h new file mode 100644 index 0000000..10b482c --- /dev/null +++ b/sceneGraph/fogStructs.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FOGSTRUCTS_H_ +#define _FOGSTRUCTS_H_ + +/// The aerial fog settings. +struct FogData +{ + F32 density; + F32 densityOffset; + F32 atmosphereHeight; + ColorF color; +}; + + +/// The water fog settings. +struct WaterFogData +{ + F32 density; + + F32 densityOffset; + + F32 wetDepth; + + F32 wetDarkening; + + ColorI color; + + PlaneF plane; +}; + +#endif // _FOGSTRUCTS_H_ \ No newline at end of file diff --git a/sceneGraph/pathManager.cpp b/sceneGraph/pathManager.cpp new file mode 100644 index 0000000..e56ba3f --- /dev/null +++ b/sceneGraph/pathManager.cpp @@ -0,0 +1,404 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "gfx/gfxDevice.h" +#include "sceneGraph/pathManager.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/simPath.h" +#include "interior/interiorInstance.h" +#include "math/mathIO.h" +#include "sceneGraph/sceneState.h" +#include "sceneGraph/sceneGraph.h" +#include "platform/profiler.h" + +extern bool gEditingMission; + + +namespace { + +U32 countNumBits(U32 n) +{ + U32 count = 0; + while (n != 0) { + n >>= 1; + count++; + } + + return count ? count : 1; +} + +} // namespace {} + + + +//-------------------------------------------------------------------------- +//-------------------------------------- PathManagerEvent +// +class PathManagerEvent : public NetEvent +{ + public: + U32 modifiedPath; + bool clearPaths; + PathManager::PathEntry path; + + public: + PathManagerEvent() { } + + void pack(NetConnection*, BitStream*); + void write(NetConnection*, BitStream*); + void unpack(NetConnection*, BitStream*); + void process(NetConnection*); + + DECLARE_CONOBJECT(PathManagerEvent); +}; + +void PathManagerEvent::pack(NetConnection*, BitStream* stream) +{ + // Write out the modified path... + stream->write(modifiedPath); + stream->writeFlag(clearPaths); + stream->write(path.totalTime); + stream->write(path.positions.size()); + + + // This is here for safety. You can remove it if you want to try your luck at bigger sizes. -- BJG + AssertWarn(path.positions.size() < 1500/40, "Warning! Path size is pretty big - may cause packet overrun!"); + + // Each one of these is about 8 floats and 2 ints + // so we'll say it's about 40 bytes in size, which is where the 40 in the above calc comes from. + for (U32 j = 0; j < path.positions.size(); j++) + { + mathWrite(*stream, path.positions[j]); + mathWrite(*stream, path.rotations[j]); + stream->write(path.msToNext[j]); + stream->write(path.smoothingType[j]); + } +} + +void PathManagerEvent::write(NetConnection*nc, BitStream *stream) +{ + pack(nc, stream); +} + +void PathManagerEvent::unpack(NetConnection*, BitStream* stream) +{ + // Read in the modified path... + + stream->read(&modifiedPath); + clearPaths = stream->readFlag(); + stream->read(&path.totalTime); + + U32 numPoints; + stream->read(&numPoints); + path.positions.setSize(numPoints); + path.rotations.setSize(numPoints); + path.msToNext.setSize(numPoints); + path.smoothingType.setSize(numPoints); + for (U32 j = 0; j < path.positions.size(); j++) + { + mathRead(*stream, &path.positions[j]); + mathRead(*stream, &path.rotations[j]); + stream->read(&path.msToNext[j]); + stream->read(&path.smoothingType[j]); + } +} + +void PathManagerEvent::process(NetConnection*) +{ + if (clearPaths) + { + // Clear out all the client's paths... + gClientPathManager->clearPaths(); + } + AssertFatal(modifiedPath <= gClientPathManager->mPaths.size(), "Error out of bounds path!"); + if (modifiedPath == gClientPathManager->mPaths.size()) { + PathManager::PathEntry *pe = new PathManager::PathEntry; + *pe = path; + gClientPathManager->mPaths.push_back(pe); + } + else + *(gClientPathManager->mPaths[modifiedPath]) = path; +} + +IMPLEMENT_CO_NETEVENT_V1(PathManagerEvent); + + +//-------------------------------------------------------------------------- +//-------------------------------------- PathManager Implementation +// +PathManager* gClientPathManager = NULL; +PathManager* gServerPathManager = NULL; + +//-------------------------------------------------------------------------- +PathManager::PathManager(const bool isServer) +{ + VECTOR_SET_ASSOCIATION(mPaths); + + mIsServer = isServer; +} + +PathManager::~PathManager() +{ + clearPaths(); +} + +void PathManager::clearPaths() +{ + for (U32 i = 0; i < mPaths.size(); i++) + delete mPaths[i]; + mPaths.setSize(0); +#ifdef TORQUE_DEBUG + // This gets rid of the memory used by the vector. + // Prevents it from showing up in memory leak logs. + mPaths.compact(); +#endif +} + +ConsoleFunction(clearServerPaths, void, 1, 1, "") +{ + gServerPathManager->clearPaths(); +} + +ConsoleFunction(clearClientPaths, void, 1, 1, "") +{ + gClientPathManager->clearPaths(); +} + +void PathManager::init() +{ + AssertFatal(gClientPathManager == NULL && gServerPathManager == NULL, "Error, already initialized the path manager!"); + + gClientPathManager = new PathManager(false); + gServerPathManager = new PathManager(true); +} + +void PathManager::destroy() +{ + AssertFatal(gClientPathManager != NULL && gServerPathManager != NULL, "Error, path manager not initialized!"); + + delete gClientPathManager; + gClientPathManager = NULL; + delete gServerPathManager; + gServerPathManager = NULL; +} + + +//-------------------------------------------------------------------------- +U32 PathManager::allocatePathId() +{ + mPaths.increment(); + mPaths.last() = new PathEntry; + + return (mPaths.size() - 1); +} + + +void PathManager::updatePath(const U32 id, + const Vector& positions, + const Vector& rotations, + const Vector& times, + const Vector& smoothingTypes) +{ + AssertFatal(mIsServer == true, "PathManager::updatePath: Error, must be called on the server side"); + AssertFatal(id < mPaths.size(), "PathManager::updatePath: error, id out of range"); + AssertFatal(positions.size() == times.size() && positions.size() == smoothingTypes.size(), "Error, times and positions must match!"); + + PathEntry& rEntry = *mPaths[id]; + + rEntry.positions = positions; + rEntry.rotations = rotations; + rEntry.msToNext = times; + rEntry.smoothingType = smoothingTypes; + + rEntry.totalTime = 0; + for (S32 i = 0; i < S32(rEntry.msToNext.size()); i++) + rEntry.totalTime += rEntry.msToNext[i]; + + transmitPath(id); +} + + +//-------------------------------------------------------------------------- +void PathManager::transmitPaths(NetConnection* nc) +{ + AssertFatal(mIsServer, "Error, cannot call transmitPaths on client path manager!"); + + // Send over paths + for(S32 i = 0; i < mPaths.size(); i++) + { + PathManagerEvent* event = new PathManagerEvent; + event->clearPaths = (i == 0); + event->modifiedPath = i; + event->path = *(mPaths[i]); + nc->postNetEvent(event); + } +} + +void PathManager::transmitPath(const U32 id) +{ + AssertFatal(mIsServer, "Error, cannot call transmitNewPath on client path manager!"); + + // Post to all active clients that have already received their paths... + // + SimGroup* pClientGroup = Sim::getClientGroup(); + for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) { + NetConnection* nc = dynamic_cast(*itr); + if (nc && nc->missionPathsSent()) + { + // Transmit the updated path... + PathManagerEvent* event = new PathManagerEvent; + event->modifiedPath = id; + event->clearPaths = false; + event->path = *(mPaths[id]); + nc->postNetEvent(event); + } + } +} + +void PathManager::getPathPosition(const U32 id, + const F64 msPosition, + Point3F& rPosition, + QuatF &rotation) +{ + AssertFatal(isValidPath(id), "Error, this is not a valid path!"); + PROFILE_START(PathManGetPos); + + // Ok, query holds our path information... + F64 ms = msPosition; + if (ms > mPaths[id]->totalTime) + ms = mPaths[id]->totalTime; + + S32 startNode = 0; + while (ms > mPaths[id]->msToNext[startNode]) { + ms -= mPaths[id]->msToNext[startNode]; + startNode++; + } + S32 endNode = (startNode + 1) % mPaths[id]->positions.size(); + + Point3F& rStart = mPaths[id]->positions[startNode]; + Point3F& rEnd = mPaths[id]->positions[endNode]; + + F64 interp = ms / F32(mPaths[id]->msToNext[startNode]); + if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeLinear) + { + rPosition = (rStart * (1.0 - interp)) + (rEnd * interp); + } + else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeAccelerate) + { + interp = mSin(interp * M_PI - (M_PI / 2)) * 0.5 + 0.5; + rPosition = (rStart * (1.0 - interp)) + (rEnd * interp); + } + else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeSpline) + { + S32 preStart = startNode - 1; + S32 postEnd = endNode + 1; + if(postEnd >= mPaths[id]->positions.size()) + postEnd = 0; + if(preStart < 0) + preStart = mPaths[id]->positions.size() - 1; + Point3F p0 = mPaths[id]->positions[preStart]; + Point3F p1 = rStart; + Point3F p2 = rEnd; + Point3F p3 = mPaths[id]->positions[postEnd]; + rPosition.x = mCatmullrom(interp, p0.x, p1.x, p2.x, p3.x); + rPosition.y = mCatmullrom(interp, p0.y, p1.y, p2.y, p3.y); + rPosition.z = mCatmullrom(interp, p0.z, p1.z, p2.z, p3.z); + } + rotation.interpolate( mPaths[id]->rotations[startNode], mPaths[id]->rotations[endNode], interp ); + PROFILE_END(); +} + +U32 PathManager::getPathTotalTime(const U32 id) const +{ + AssertFatal(isValidPath(id), "Error, this is not a valid path!"); + + return mPaths[id]->totalTime; +} + +U32 PathManager::getPathNumWaypoints(const U32 id) const +{ + AssertFatal(isValidPath(id), "Error, this is not a valid path!"); + + return mPaths[id]->positions.size(); +} + +U32 PathManager::getWaypointTime(const U32 id, const U32 wayPoint) const +{ + AssertFatal(isValidPath(id), "Error, this is not a valid path!"); + AssertFatal(wayPoint < getPathNumWaypoints(id), "Invalid waypoint!"); + + U32 time = 0; + for (U32 i = 0; i < wayPoint; i++) + time += mPaths[id]->msToNext[i]; + + return time; +} + +U32 PathManager::getPathTimeBits(const U32 id) +{ + AssertFatal(isValidPath(id), "Error, this is not a valid path!"); + + return countNumBits(mPaths[id]->totalTime); +} + +U32 PathManager::getPathWaypointBits(const U32 id) +{ + AssertFatal(isValidPath(id), "Error, this is not a valid path!"); + + return countNumBits(mPaths[id]->positions.size()); +} + + +bool PathManager::dumpState(BitStream* stream) const +{ + stream->write(mPaths.size()); + + for (U32 i = 0; i < mPaths.size(); i++) { + const PathEntry& rEntry = *mPaths[i]; + stream->write(rEntry.totalTime); + + stream->write(rEntry.positions.size()); + for (U32 j = 0; j < rEntry.positions.size(); j++) { + mathWrite(*stream, rEntry.positions[j]); + stream->write(rEntry.msToNext[j]); + } + } + + return stream->getStatus() == Stream::Ok; +} + +bool PathManager::readState(BitStream* stream) +{ + U32 i; + for (i = 0; i < mPaths.size(); i++) + delete mPaths[i]; + + U32 numPaths; + stream->read(&numPaths); + mPaths.setSize(numPaths); + + for (i = 0; i < mPaths.size(); i++) { + mPaths[i] = new PathEntry; + PathEntry& rEntry = *mPaths[i]; + + stream->read(&rEntry.totalTime); + + U32 numPositions; + stream->read(&numPositions); + rEntry.positions.setSize(numPositions); + rEntry.msToNext.setSize(numPositions); + for (U32 j = 0; j < rEntry.positions.size(); j++) { + mathRead(*stream, &rEntry.positions[j]); + stream->read(&rEntry.msToNext[j]); + } + } + + return stream->getStatus() == Stream::Ok; +} + + + diff --git a/sceneGraph/pathManager.h b/sceneGraph/pathManager.h new file mode 100644 index 0000000..03960ea --- /dev/null +++ b/sceneGraph/pathManager.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// +// Copyright (c) 2001 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _PATHMANAGER_H_ +#define _PATHMANAGER_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _MQUAT_H_ +#include "math/mQuat.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +class NetConnection; +class BitStream; + +class PathManager +{ + friend class PathManagerEvent; + + private: + struct PathEntry { + U32 totalTime; + + Vector positions; + Vector rotations; + Vector smoothingType; + Vector msToNext; + + PathEntry() { + VECTOR_SET_ASSOCIATION(positions); + VECTOR_SET_ASSOCIATION(rotations); + VECTOR_SET_ASSOCIATION(smoothingType); + VECTOR_SET_ASSOCIATION(msToNext); + } + }; + + Vector mPaths; + + public: + enum PathType { + BackAndForth, + Looping + }; + + public: + PathManager(const bool isServer); + ~PathManager(); + + static void init(); + static void destroy(); + void clearPaths(); + + //-------------------------------------- Path querying + public: + bool isValidPath(const U32 id) const; + void getPathPosition(const U32 id, const F64 msPosition, Point3F& rPosition, QuatF &rotation); + U32 getPathTotalTime(const U32 id) const; + U32 getPathNumWaypoints(const U32 id) const; + U32 getWaypointTime(const U32 id, const U32 wayPoint) const; + + U32 getPathTimeBits(const U32 id); + U32 getPathWaypointBits(const U32 id); + + //-------------------------------------- Path Registration/Transmission/Management + public: + // Called after mission load to clear out the paths on the client, and to transmit + // the information for the current mission's paths. + void transmitPaths(NetConnection*); + void transmitPath(U32); + + U32 allocatePathId(); + void updatePath(const U32 id, const Vector&, const Vector&, const Vector &, const Vector&); + + //-------------------------------------- State dumping/reading + public: + bool dumpState(BitStream*) const; + bool readState(BitStream*); + + private: + bool mIsServer; + bool mPathsSent; +}; + +struct PathNode { + Point3F position; + QuatF rotation; + U32 smoothingType; + U32 msToNext; +}; + +extern PathManager* gClientPathManager; +extern PathManager* gServerPathManager; + +//-------------------------------------------------------------------------- +inline bool PathManager::isValidPath(const U32 id) const +{ + return (id < U32(mPaths.size())) && mPaths[id]->positions.size() > 0; +} + + + +#endif // _H_PATHMANAGER diff --git a/sceneGraph/reflectionManager.cpp b/sceneGraph/reflectionManager.cpp new file mode 100644 index 0000000..05b0d34 --- /dev/null +++ b/sceneGraph/reflectionManager.cpp @@ -0,0 +1,241 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sceneGraph/reflectionManager.h" + +#include "platform/profiler.h" +#include "platform/platformTimer.h" +#include "console/consoleTypes.h" +#include "core/tAlgorithm.h" +#include "math/mMathFn.h" +#include "T3D/gameConnection.h" +#include "ts/tsShapeInstance.h" +#include "gui/3d/guiTSControl.h" +#include "sceneGraph/sceneGraph.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxStringEnumTranslate.h" + + +GFX_ImplementTextureProfile( ReflectRenderTargetProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | GFXTextureProfile::NoMipmap | GFXTextureProfile::RenderTarget | GFXTextureProfile::Pooled, + GFXTextureProfile::None ); + +GFX_ImplementTextureProfile( RefractTextureProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::RenderTarget | + GFXTextureProfile::Pooled, + GFXTextureProfile::None ); + +static S32 QSORT_CALLBACK compareReflectors( const void *a, const void *b ) +{ + const ReflectorBase *A = *((ReflectorBase**)a); + const ReflectorBase *B = *((ReflectorBase**)b); + + F32 dif = B->score - A->score; + return (S32)mFloor( dif ); +} + + +U32 ReflectionManager::smFrameReflectionMS = 10; + +ReflectionManager::ReflectionManager() + : mRefractTexScale( 0.5f ), + mUpdateRefract( true ), + mReflectFormat( GFXFormatR8G8B8A8 ) +{ + mTimer = PlatformTimer::create(); + +#if defined(TORQUE_OS_XENON) + // On the Xbox360, it needs to do a resolveTo from the active target, so this + // may as well be the full size of the active target + mRefractTexScale = 1.0f; +#endif + + GFXDevice::getDeviceEventSignal().notify( this, &ReflectionManager::_handleDeviceEvent ); +} + +ReflectionManager::~ReflectionManager() +{ + SAFE_DELETE( mTimer ); + AssertFatal( mReflectors.size() == 0, "ReflectionManager, some reflectors were left nregistered!" ); + + GFXDevice::getDeviceEventSignal().remove( this, &ReflectionManager::_handleDeviceEvent ); +} + +void ReflectionManager::registerReflector( ReflectorBase *reflector ) +{ + mReflectors.push_back_unique( reflector ); +} + +void ReflectionManager::unregisterReflector( ReflectorBase *reflector ) +{ + mReflectors.remove( reflector ); +} + +void ReflectionManager::update( F32 timeSlice, + const Point2I &resolution, + const CameraQuery &query ) +{ + GFXDEBUGEVENT_SCOPE( UpdateReflections, ColorI::WHITE ); + + if ( mReflectors.empty() ) + return; + + PROFILE_SCOPE( ReflectionManager_Update ); + + // Calculate our target time from the slice. + U32 targetMs = timeSlice * smFrameReflectionMS; + + // Setup a culler for testing the + // visibility of reflectors. + Frustum culler; + culler.set( false, + query.fov, + (F32)resolution.x / (F32)resolution.y, + query.nearPlane, + query.farPlane, + query.cameraMatrix ); + + // We use the frame time and not real time + // here as this may be called multiple times + // within a frame. + U32 startOfUpdateMs = Platform::getVirtualMilliseconds(); + + ReflectParams refparams; + refparams.query = &query; + refparams.viewportExtent = resolution; + refparams.culler = culler; + refparams.startOfUpdateMs = startOfUpdateMs; + + // Update the reflection score. + ReflectorList::iterator reflectorIter = mReflectors.begin(); + for ( ; reflectorIter != mReflectors.end(); reflectorIter++ ) + (*reflectorIter)->calcScore( refparams ); + + // Sort them by the score. + dQsort( mReflectors.address(), mReflectors.size(), sizeof(ReflectorBase*), compareReflectors ); + + // Update as many reflections as we can + // within the target time limit. + mTimer->getElapsedMs(); + mTimer->reset(); + U32 numUpdated = 0; + reflectorIter = mReflectors.begin(); + for ( ; reflectorIter != mReflectors.end(); reflectorIter++ ) + { + // We're sorted by score... so once we reach + // a zero score we have nothing more to update. + if ( (*reflectorIter)->score <= 0.0f ) + break; + + (*reflectorIter)->updateReflection( refparams ); + (*reflectorIter)->lastUpdateMs = startOfUpdateMs; + numUpdated++; + + // If we run out of update time then stop. + if ( mTimer->getElapsedMs() > targetMs ) + break; + } + + U32 totalElapsed = mTimer->getElapsedMs(); + + // Set metric/debug related script variables... + + U32 numEnabled = mReflectors.size(); + U32 numVisible = 0; + U32 numOccluded = 0; + + reflectorIter = mReflectors.begin(); + for ( ; reflectorIter != mReflectors.end(); reflectorIter++ ) + { + ReflectorBase *pReflector = (*reflectorIter); + if ( pReflector->isOccluded() ) + numOccluded++; + } + + const GFXTextureProfileStats &stats = ReflectRenderTargetProfile.getStats(); + + F32 mb = ( stats.activeBytes / 1024.0f ) / 1024.0f; + char temp[256]; + + dSprintf( temp, 256, "%s %d %0.2f\n", + ReflectRenderTargetProfile.getName().c_str(), + stats.activeCount, + mb ); + + Con::setVariable( "$Reflect::textureStats", temp ); + Con::setIntVariable( "$Reflect::renderTargetsAllocated", stats.allocatedTextures ); + Con::setIntVariable( "$Reflect::poolSize", stats.activeCount ); + Con::setIntVariable( "$Reflect::numObjects", numEnabled ); + Con::setIntVariable( "$Reflect::numVisible", numVisible ); + Con::setIntVariable( "$Reflect::numOccluded", numOccluded ); + Con::setIntVariable( "$Reflect::numUpdated", numUpdated ); + Con::setIntVariable( "$Reflect::elapsed", totalElapsed ); +} + +GFXTexHandle ReflectionManager::allocRenderTarget( const Point2I &size ) +{ + return GFXTexHandle( size.x, size.y, mReflectFormat, + &ReflectRenderTargetProfile, + avar("%s() - mReflectTex (line %d)", __FUNCTION__, __LINE__) ); +} + +GFXTextureObject* ReflectionManager::getRefractTex() +{ + GFXTarget *target = GFX->getActiveRenderTarget(); + GFXFormat targetFormat = target->getFormat(); + const Point2I &targetSize = target->getSize(); + const U32 desWidth = mFloor( (F32)targetSize.x * mRefractTexScale ); + const U32 desHeight = mFloor( ( F32)targetSize.y * mRefractTexScale ); + + if ( mRefractTex.isNull() || + mRefractTex->getWidth() != desWidth || + mRefractTex->getHeight() != desHeight || + mRefractTex->getFormat() != targetFormat ) + { + mRefractTex.set( desWidth, desHeight, targetFormat, &RefractTextureProfile, "mRefractTex" ); + mUpdateRefract = true; + } + + if ( mUpdateRefract ) + { + target->resolveTo( mRefractTex ); + mUpdateRefract = false; + } + + return mRefractTex; +} + +bool ReflectionManager::_handleDeviceEvent( GFXDevice::GFXDeviceEventType evt ) +{ + switch( evt ) + { + case GFXDevice::deStartOfFrame: + + mUpdateRefract = true; + break; + + case GFXDevice::deDestroy: + + mRefractTex = NULL; + break; + + default: + break; + } + + return true; +} + +ConsoleFunction( setReflectFormat, void, 2, 2, "") +{ + GFXFormat format; + Con::setData( TypeEnum, &format, 0, 1, argv+1, &gTextureFormatEnumTable ); + REFLECTMGR->setReflectFormat( format ); +} + diff --git a/sceneGraph/reflectionManager.h b/sceneGraph/reflectionManager.h new file mode 100644 index 0000000..48cb9dc --- /dev/null +++ b/sceneGraph/reflectionManager.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _REFLECTIONMANAGER_H_ +#define _REFLECTIONMANAGER_H_ + +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _UTIL_DELEGATE_H_ +#include "core/util/delegate.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif +#ifndef _REFLECTOR_H_ +#include "sceneGraph/reflector.h" +#endif + +class PlatformTimer; + +enum ReflectMode +{ + ReflectNever = 0, + ReflectDynamic, + ReflectAlways +}; + +typedef Delegate ReflectDelegate; +class SceneObject; + +struct Reflector +{ + SceneObject *object; + ReflectDelegate updateFn; + F32 priority; + U32 maxRateMs; + F32 maxDist; + U32 lastUpdateMs; + F32 score; + bool updated; + bool tried; + bool hasTexture; +}; + +typedef Vector ReflectorVec; + +GFX_DeclareTextureProfile( ReflectRenderTargetProfile ); +GFX_DeclareTextureProfile( RefractTextureProfile ); + +class ReflectionManager +{ +public: + + ReflectionManager(); + virtual ~ReflectionManager(); + + /// Called to change the reflection texture format. + void setReflectFormat( GFXFormat format ) { mReflectFormat = format; } + + /// Returns the current reflection format. + GFXFormat getReflectFormat() const { return mReflectFormat; } + + /// Doll out callbacks to registered objects based on + /// scoring and elapsed time. This should be called + /// once for each viewport that renders. + void update( F32 timeSlice, + const Point2I &resolution, + const CameraQuery &query ); + + void registerReflector( ReflectorBase *reflector ); + void unregisterReflector( ReflectorBase *reflector ); + + GFXTexHandle allocRenderTarget( const Point2I &size ); + + GFXTextureObject* getRefractTex(); + +protected: + + bool _handleDeviceEvent( GFXDevice::GFXDeviceEventType evt ); + +protected: + + static U32 smFrameReflectionMS; + + /// A timer used for tracking update time. + PlatformTimer *mTimer; + + ReflectorList mReflectors; + + /// Refraction texture copied from the backbuffer once per frame that + /// gets used by all WaterObjects. + GFXTexHandle mRefractTex; + + /// RefractTex has dimensions equal to the active render target scaled in + /// both x and y by this float. + F32 mRefractTexScale; + + /// The texture format to use for reflection and + /// refraction texture sources. + GFXFormat mReflectFormat; + + /// Set when the refaction texture is dirty + /// and requires an update. + bool mUpdateRefract; +}; + + +/// Returns the ReflectionManager singleton. +#define REFLECTMGR Singleton::instance() + +#endif // _REFLECTIONMANAGER_H_ \ No newline at end of file diff --git a/sceneGraph/reflector.cpp b/sceneGraph/reflector.cpp new file mode 100644 index 0000000..e045c8f --- /dev/null +++ b/sceneGraph/reflector.cpp @@ -0,0 +1,606 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sceneGraph/reflector.h" + +#include "console/consoleTypes.h" +#include "gfx/gfxCubemap.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxTransformSaver.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "core/stream/bitStream.h" +#include "sceneGraph/reflectionManager.h" +#include "gui/3d/guiTSControl.h" +#include "ts/tsShapeInstance.h" +#include "gfx/gfxOcclusionQuery.h" +#include "lighting/shadowMap/lightShadowMap.h" + + +extern ColorI gCanvasClearColor; + + +//------------------------------------------------------------------------- +// ReflectorDesc +//------------------------------------------------------------------------- + +IMPLEMENT_CO_DATABLOCK_V1( ReflectorDesc ); + +ReflectorDesc::ReflectorDesc() +{ + texSize = 256; + nearDist = 0.1f; + farDist = 1000.0f; + objectTypeMask = 0xFFFFFFFF; + detailAdjust = 1.0f; + priority = 1.0f; + maxRateMs = 15; + useOcclusionQuery = true; +} + +ReflectorDesc::~ReflectorDesc() +{ +} + +void ReflectorDesc::initPersistFields() +{ + addField( "texSize", TypeS32, Offset( texSize, ReflectorDesc ) ); + addField( "nearDist", TypeF32, Offset( nearDist, ReflectorDesc ) ); + addField( "farDist", TypeF32, Offset( farDist, ReflectorDesc ) ); + addField( "objectTypeMask", TypeS32, Offset( objectTypeMask, ReflectorDesc ) ); + addField( "detailAdjust", TypeF32, Offset( detailAdjust, ReflectorDesc ) ); + addField( "priority", TypeF32, Offset( priority, ReflectorDesc ) ); + addField( "maxRateMs", TypeS32, Offset( maxRateMs, ReflectorDesc ) ); + addField( "useOcclusionQuery", TypeBool, Offset( useOcclusionQuery, ReflectorDesc ) ); + + Parent::initPersistFields(); +} + +void ReflectorDesc::packData( BitStream *stream ) +{ + Parent::packData( stream ); + + stream->write( texSize ); + stream->write( nearDist ); + stream->write( farDist ); + stream->write( objectTypeMask ); + stream->write( detailAdjust ); + stream->write( priority ); + stream->write( maxRateMs ); + stream->writeFlag( useOcclusionQuery ); +} + +void ReflectorDesc::unpackData( BitStream *stream ) +{ + Parent::unpackData( stream ); + + stream->read( &texSize ); + stream->read( &nearDist ); + stream->read( &farDist ); + stream->read( &objectTypeMask ); + stream->read( &detailAdjust ); + stream->read( &priority ); + stream->read( &maxRateMs ); + useOcclusionQuery = stream->readFlag(); +} + +bool ReflectorDesc::preload( bool server, String &errorStr ) +{ + if ( !Parent::preload( server, errorStr ) ) + return false; + + return true; +} + +//------------------------------------------------------------------------- +// ReflectorBase +//------------------------------------------------------------------------- +ReflectorBase::ReflectorBase() +{ + mEnabled = false; + mOccluded = false; + mIsRendering = false; + mDesc = NULL; + mObject = NULL; + mOcclusionQuery = GFX->createOcclusionQuery(); +} + +ReflectorBase::~ReflectorBase() +{ + delete mOcclusionQuery; +} + +void ReflectorBase::unregisterReflector() +{ + if ( mEnabled ) + { + REFLECTMGR->unregisterReflector( this ); + mEnabled = false; + } +} + +F32 ReflectorBase::calcScore( const ReflectParams ¶ms ) +{ + // First check the occlusion query to see if we're hidden. + if ( mDesc->useOcclusionQuery && + mOcclusionQuery && + mOcclusionQuery->getStatus( true ) == GFXOcclusionQuery::Occluded ) + mOccluded = true; + else + mOccluded = false; + + // If we're disabled for any reason then there + // is nothing more left to do. + if ( !mEnabled || + mOccluded || + !params.culler.intersects( mObject->getWorldBox() ) ) + { + score = 0; + return score; + } + + // This mess is calculating a score based on LOD. + + /* + F32 sizeWS = getMax( object->getWorldBox().len_z(), 0.001f ); + Point3F cameraOffset = params.culler.getPosition() - object->getPosition(); + F32 dist = getMax( cameraOffset.len(), 0.01f ); + F32 worldToScreenScaleY = ( params.culler.getNearDist() * params.viewportExtent.y ) / + ( params.culler.getNearTop() - params.culler.getNearBottom() ); + F32 sizeSS = sizeWS / dist * worldToScreenScaleY; + */ + F32 lodFactor = 1.0f; //sizeSS; + + F32 maxRate = getMax( (F32)mDesc->maxRateMs, 1.0f ); + U32 delta = params.startOfUpdateMs - lastUpdateMs; + F32 timeFactor = getMax( (F32)delta / maxRate - 1.0f, 0.0f ); + + score = mDesc->priority * timeFactor * lodFactor; + + return score; +} + + +//------------------------------------------------------------------------- +// CubeReflector +//------------------------------------------------------------------------- + +CubeReflector::CubeReflector() + : mLastTexSize( 0 ) +{ +} + +void CubeReflector::registerReflector( SceneObject *object, + ReflectorDesc *desc ) +{ + if ( mEnabled ) + return; + + mEnabled = true; + mObject = object; + mDesc = desc; + REFLECTMGR->registerReflector( this ); +} + +void CubeReflector::unregisterReflector() +{ + if ( !mEnabled ) + return; + + REFLECTMGR->unregisterReflector( this ); + + mEnabled = false; +} + +void CubeReflector::updateReflection( const ReflectParams ¶ms ) +{ + GFXDEBUGEVENT_SCOPE( CubeReflector_UpdateReflection, ColorI::WHITE ); + + mIsRendering = true; + + // Setup textures and targets... + + if ( mDesc->texSize <= 0 ) + mDesc->texSize = 12; + + bool texResize = ( mDesc->texSize != mLastTexSize ); + + const GFXFormat reflectFormat = REFLECTMGR->getReflectFormat(); + + if ( texResize || + cubemap.isNull() || + cubemap->getFormat() != reflectFormat ) + { + cubemap = GFX->createCubemap(); + cubemap->initDynamic( mDesc->texSize, reflectFormat ); + } + + GFXTexHandle depthBuff = LightShadowMap::_getDepthTarget( mDesc->texSize, mDesc->texSize ); + + if ( renderTarget.isNull() ) + renderTarget = GFX->allocRenderToTextureTarget(); + + GFX->pushActiveRenderTarget(); + renderTarget->attachTexture( GFXTextureTarget::DepthStencil, depthBuff ); + + + F32 oldVisibleDist = gClientSceneGraph->getVisibleDistance(); + gClientSceneGraph->setVisibleDistance( mDesc->farDist ); + + + for ( U32 i = 0; i < 6; i++ ) + updateFace( params, i ); + + + GFX->popActiveRenderTarget(); + + gClientSceneGraph->setVisibleDistance(oldVisibleDist); + + mIsRendering = false; + mLastTexSize = mDesc->texSize; +} + +void CubeReflector::updateFace( const ReflectParams ¶ms, U32 faceidx ) +{ + GFXDEBUGEVENT_SCOPE( CubeReflector_UpdateFace, ColorI::WHITE ); + + // store current matrices + GFXTransformSaver saver; + + // set projection to 90 degrees vertical and horizontal + GFX->setFrustum(90.0f, 1.0f, mDesc->nearDist, mDesc->farDist ); + + // We don't use a special clipping projection, but still need to initialize + // this for objects like SkyBox which will use it during a reflect pass. + gClientSceneGraph->setNonClipProjection( GFX->getProjectionMatrix() ); + + // Standard view that will be overridden below. + VectorF vLookatPt(0.0f, 0.0f, 0.0f), vUpVec(0.0f, 0.0f, 0.0f), vRight(0.0f, 0.0f, 0.0f); + + switch( faceidx ) + { + case 0 : // D3DCUBEMAP_FACE_POSITIVE_X: + vLookatPt = VectorF( 1.0f, 0.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + case 1 : // D3DCUBEMAP_FACE_NEGATIVE_X: + vLookatPt = VectorF( -1.0f, 0.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + case 2 : // D3DCUBEMAP_FACE_POSITIVE_Y: + vLookatPt = VectorF( 0.0f, 1.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 0.0f,-1.0f ); + break; + case 3 : // D3DCUBEMAP_FACE_NEGATIVE_Y: + vLookatPt = VectorF( 0.0f, -1.0f, 0.0f ); + vUpVec = VectorF( 0.0f, 0.0f, 1.0f ); + break; + case 4 : // D3DCUBEMAP_FACE_POSITIVE_Z: + vLookatPt = VectorF( 0.0f, 0.0f, 1.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + case 5: // D3DCUBEMAP_FACE_NEGATIVE_Z: + vLookatPt = VectorF( 0.0f, 0.0f, -1.0f ); + vUpVec = VectorF( 0.0f, 1.0f, 0.0f ); + break; + } + + // create camera matrix + VectorF cross = mCross( vUpVec, vLookatPt ); + cross.normalizeSafe(); + + MatrixF matView(true); + matView.setColumn( 0, cross ); + matView.setColumn( 1, vLookatPt ); + matView.setColumn( 2, vUpVec ); + matView.setPosition( mObject->getPosition() ); + matView.inverse(); + + GFX->setWorldMatrix(matView); + + renderTarget->attachTexture( GFXTextureTarget::Color0, cubemap, faceidx ); + GFX->setActiveRenderTarget( renderTarget ); + GFX->clear( GFXClearStencil | GFXClearTarget | GFXClearZBuffer, gCanvasClearColor, 1.0f, 0 ); + + SceneState *baseState = gClientSceneGraph->createBaseState( SPT_Reflect ); + baseState->setDiffuseCameraTransform( params.query->cameraMatrix ); + + // render scene + gClientSceneGraph->getLightManager()->registerGlobalLights( &baseState->getFrustum(), false ); + gClientSceneGraph->renderScene( baseState, mDesc->objectTypeMask ); + gClientSceneGraph->getLightManager()->unregisterAllLights(); + + // Clean up. + delete baseState; + renderTarget->resolve(); +} + +F32 CubeReflector::calcFaceScore( const ReflectParams ¶ms, U32 faceidx ) +{ + if ( Parent::calcScore( params ) <= 0.0f ) + return score; + + VectorF vLookatPt(0.0f, 0.0f, 0.0f); + + switch( faceidx ) + { + case 0 : // D3DCUBEMAP_FACE_POSITIVE_X: + vLookatPt = VectorF( 1.0f, 0.0f, 0.0f ); + break; + case 1 : // D3DCUBEMAP_FACE_NEGATIVE_X: + vLookatPt = VectorF( -1.0f, 0.0f, 0.0f ); + break; + case 2 : // D3DCUBEMAP_FACE_POSITIVE_Y: + vLookatPt = VectorF( 0.0f, 1.0f, 0.0f ); + break; + case 3 : // D3DCUBEMAP_FACE_NEGATIVE_Y: + vLookatPt = VectorF( 0.0f, -1.0f, 0.0f ); + break; + case 4 : // D3DCUBEMAP_FACE_POSITIVE_Z: + vLookatPt = VectorF( 0.0f, 0.0f, 1.0f ); + break; + case 5: // D3DCUBEMAP_FACE_NEGATIVE_Z: + vLookatPt = VectorF( 0.0f, 0.0f, -1.0f ); + break; + } + + VectorF cameraDir; + params.query->cameraMatrix.getColumn( 1, &cameraDir ); + + F32 dot = mDot( cameraDir, -vLookatPt ); + + dot = getMax( ( dot + 1.0f ) / 2.0f, 0.1f ); + + score *= dot; + + return score; +} + +F32 CubeReflector::CubeFaceReflector::calcScore( const ReflectParams ¶ms ) +{ + score = cube->calcFaceScore( params, faceIdx ); + mOccluded = cube->isOccluded(); + return score; +} + + +//------------------------------------------------------------------------- +// PlaneReflector +//------------------------------------------------------------------------- + +void PlaneReflector::registerReflector( SceneObject *object, + ReflectorDesc *desc ) +{ + mEnabled = true; + mObject = object; + mDesc = desc; + mLastDir = Point3F::One; + mLastPos = Point3F::Max; + + REFLECTMGR->registerReflector( this ); +} + +F32 PlaneReflector::calcScore( const ReflectParams ¶ms ) +{ + if ( Parent::calcScore( params ) <= 0.0f ) + return score; + + // The planar reflection is view dependent to score it + // higher if the view direction and/or position has changed. + + // Get the current camera info. + VectorF camDir = params.query->cameraMatrix.getForwardVector(); + Point3F camPos = params.query->cameraMatrix.getPosition(); + + // Scale up the score based on the view direction change. + F32 dot = mDot( camDir, mLastDir ); + dot = ( 1.0f - dot ) * 1000.0f; + score += dot * mDesc->priority; + + // Also account for the camera movement. + score += ( camPos - mLastPos ).lenSquared() * mDesc->priority; + + return score; +} + +void PlaneReflector::updateReflection( const ReflectParams ¶ms ) +{ + PROFILE_SCOPE(PlaneReflector_updateReflection); + GFXDEBUGEVENT_SCOPE( PlaneReflector_updateReflection, ColorI::WHITE ); + + mIsRendering = true; + + if ( mDesc->texSize <= 0 ) + mDesc->texSize = 12; + + bool texResize = ( mDesc->texSize != mLastTexSize ); + + mLastTexSize = mDesc->texSize; + + const Point2I texSize( mDesc->texSize, mDesc->texSize ); + + if ( texResize || + reflectTex.isNull() || + reflectTex->getFormat() != REFLECTMGR->getReflectFormat() ) + reflectTex = REFLECTMGR->allocRenderTarget( texSize ); + + GFXTexHandle depthBuff = LightShadowMap::_getDepthTarget( texSize.x, texSize.y ); + + // store current matrices + GFXTransformSaver saver; + MatrixF proj = GFX->getProjectionMatrix(); + + GFX->setFrustum( mRadToDeg(params.query->fov), + F32(params.viewportExtent.x) / F32(params.viewportExtent.y ), + params.query->nearPlane, + params.query->farPlane ); + + gClientSceneGraph->mNormCamPos = params.query->cameraMatrix.getPosition(); + + // Store the last view info for scoring. + mLastDir = params.query->cameraMatrix.getForwardVector(); + mLastPos = params.query->cameraMatrix.getPosition(); + + if ( objectSpace ) + { + // set up camera transform relative to object + MatrixF invObjTrans = mObject->getRenderTransform(); + invObjTrans.inverse(); + MatrixF relCamTrans = invObjTrans * params.query->cameraMatrix; + + MatrixF camReflectTrans = getCameraReflection( relCamTrans ); + MatrixF camTrans = mObject->getRenderTransform() * camReflectTrans; + camTrans.inverse(); + + GFX->setWorldMatrix( camTrans ); + + // use relative reflect transform for modelview since clip plane is in object space + camTrans = camReflectTrans; + camTrans.inverse(); + + // set new projection matrix + gClientSceneGraph->setNonClipProjection( (MatrixF&) GFX->getProjectionMatrix() ); + MatrixF clipProj = getFrustumClipProj( camTrans ); + GFX->setProjectionMatrix( clipProj ); + } + else + { + MatrixF camTrans = params.query->cameraMatrix; + + // set world mat from new camera view + MatrixF camReflectTrans = getCameraReflection( camTrans ); + camReflectTrans.inverse(); + GFX->setWorldMatrix( camReflectTrans ); + + // set new projection matrix + gClientSceneGraph->setNonClipProjection( (MatrixF&) GFX->getProjectionMatrix() ); + MatrixF clipProj = getFrustumClipProj( camReflectTrans ); + GFX->setProjectionMatrix( clipProj ); + } + + // Adjust the detail amount + F32 detailAdjustBackup = TSShapeInstance::smDetailAdjust; + TSShapeInstance::smDetailAdjust *= mDesc->detailAdjust; + + + if(reflectTarget.isNull()) + reflectTarget = GFX->allocRenderToTextureTarget(); + reflectTarget->attachTexture( GFXTextureTarget::Color0, reflectTex ); + reflectTarget->attachTexture( GFXTextureTarget::DepthStencil, depthBuff ); + GFX->pushActiveRenderTarget(); + GFX->setActiveRenderTarget( reflectTarget ); + + GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, gCanvasClearColor, 1.0f, 0 ); + + SceneState *baseState = gClientSceneGraph->createBaseState( SPT_Reflect ); + baseState->setDiffuseCameraTransform( params.query->cameraMatrix ); + + U32 objTypeFlag = -1; + gClientSceneGraph->getLightManager()->registerGlobalLights( &baseState->getFrustum(), false ); + gClientSceneGraph->renderScene( baseState, objTypeFlag ); + gClientSceneGraph->getLightManager()->unregisterAllLights(); + + // Clean up. + delete baseState; + reflectTarget->resolve(); + GFX->popActiveRenderTarget(); + + // Restore detail adjust amount. + TSShapeInstance::smDetailAdjust = detailAdjustBackup; + + mIsRendering = false; +} + +MatrixF PlaneReflector::getCameraReflection( MatrixF &camTrans ) +{ + Point3F normal = refplane; + + // Figure out new cam position + Point3F camPos = camTrans.getPosition(); + F32 dist = refplane.distToPlane( camPos ); + Point3F newCamPos = camPos - normal * dist * 2.0; + + // Figure out new look direction + Point3F i, j, k; + camTrans.getColumn( 0, &i ); + camTrans.getColumn( 1, &j ); + camTrans.getColumn( 2, &k ); + + i = MathUtils::reflect( i, normal ); + j = MathUtils::reflect( j, normal ); + k = MathUtils::reflect( k, normal ); + //mCross( i, j, &k ); + + + MatrixF newTrans(true); + newTrans.setColumn( 0, i ); + newTrans.setColumn( 1, j ); + newTrans.setColumn( 2, k ); + + newTrans.setPosition( newCamPos ); + + return newTrans; +} + +inline float sgn(float a) +{ + if (a > 0.0F) return (1.0F); + if (a < 0.0F) return (-1.0F); + return (0.0F); +} + +MatrixF PlaneReflector::getFrustumClipProj( MatrixF &modelview ) +{ + static MatrixF rotMat(EulerF( static_cast(M_PI / 2.f), 0.0, 0.0)); + static MatrixF invRotMat(EulerF( -static_cast(M_PI / 2.f), 0.0, 0.0)); + + + MatrixF revModelview = modelview; + revModelview = rotMat * revModelview; // add rotation to modelview because it needs to be removed from projection + + // rotate clip plane into modelview space + Point4F clipPlane; + Point3F pnt = refplane * -(refplane.d + 0.0 ); + Point3F norm = refplane; + + revModelview.mulP( pnt ); + revModelview.mulV( norm ); + norm.normalize(); + + clipPlane.set( norm.x, norm.y, norm.z, -mDot( pnt, norm ) ); + + + // Manipulate projection matrix + //------------------------------------------------------------------------ + MatrixF proj = GFX->getProjectionMatrix(); + proj.mul( invRotMat ); // reverse rotation imposed by Torque + proj.transpose(); // switch to row-major order + + // Calculate the clip-space corner point opposite the clipping plane + // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and + // transform it into camera space by multiplying it + // by the inverse of the projection matrix + Vector4F q; + q.x = sgn(clipPlane.x) / proj(0,0); + q.y = sgn(clipPlane.y) / proj(1,1); + q.z = -1.0F; + q.w = ( 1.0F - proj(2,2) ) / proj(3,2); + + F32 a = 1.0 / (clipPlane.x * q.x + clipPlane.y * q.y + clipPlane.z * q.z + clipPlane.w * q.w); + + Vector4F c = clipPlane * a; + + // CodeReview [ags 1/23/08] Come up with a better way to deal with this. + if(GFX->getAdapterType() == OpenGL) + c.z += 1.0f; + + // Replace the third column of the projection matrix + proj.setColumn( 2, c ); + proj.transpose(); // convert back to column major order + proj.mul( rotMat ); // restore Torque rotation + + return proj; +} \ No newline at end of file diff --git a/sceneGraph/reflector.h b/sceneGraph/reflector.h new file mode 100644 index 0000000..1761dac --- /dev/null +++ b/sceneGraph/reflector.h @@ -0,0 +1,200 @@ +#ifndef _REFLECTOR_H_ +#define _REFLECTOR_H_ + +#ifndef _GFXCUBEMAP_H_ +#include "gfx/gfxCubemap.h" +#endif +#ifndef _GFXTARGET_H_ +#include "gfx/gfxTarget.h" +#endif +#ifndef _SIMDATABLOCK_H_ +#include "console/simDatablock.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif + +struct CameraQuery; +class Point2I; +class Frustum; +class SceneGraph; +class SceneObject; +class GFXOcclusionQuery; + + +struct ReflectParams +{ + const CameraQuery *query; + Point2I viewportExtent; + Frustum culler; + U32 startOfUpdateMs; +}; + + +class ReflectorDesc : public SimDataBlock +{ + typedef SimDataBlock Parent; + +public: + + ReflectorDesc(); + virtual ~ReflectorDesc(); + + DECLARE_CONOBJECT( ReflectorDesc ); + + static void initPersistFields(); + + virtual void packData( BitStream *stream ); + virtual void unpackData( BitStream* stream ); + virtual bool preload( bool server, String &errorStr ); + + U32 texSize; + F32 nearDist; + F32 farDist; + U32 objectTypeMask; + F32 detailAdjust; + F32 priority; + U32 maxRateMs; + bool useOcclusionQuery; + //U32 lastLodSize; +}; + + +class ReflectorBase +{ +public: + + ReflectorBase(); + virtual ~ReflectorBase(); + + bool isEnabled() const { return mEnabled; } + + virtual void unregisterReflector(); + virtual F32 calcScore( const ReflectParams ¶ms ); + virtual void updateReflection( const ReflectParams ¶ms ) {} + + GFXOcclusionQuery* getOcclusionQuery() const { return mOcclusionQuery; } + + bool isOccluded() const { return mOccluded; } + + /// Returns true if this reflector is in the process of rendering. + bool isRendering() const { return mIsRendering; } + +protected: + + bool mEnabled; + + bool mIsRendering; + + GFXOcclusionQuery *mOcclusionQuery; + + bool mOccluded; + + SceneObject *mObject; + + ReflectorDesc *mDesc; + +public: + + // These are public because some of them + // are exposed as fields. + + F32 score; + U32 lastUpdateMs; + + +}; + +typedef Vector ReflectorList; + + +class CubeReflector : public ReflectorBase +{ + typedef ReflectorBase Parent; + +public: + + CubeReflector(); + virtual ~CubeReflector() {} + + void registerReflector( SceneObject *inObject, + ReflectorDesc *inDesc ); + + virtual void unregisterReflector(); + virtual void updateReflection( const ReflectParams ¶ms ); + + GFXCubemap* getCubemap() const { return cubemap; } + + void updateFace( const ReflectParams ¶ms, U32 faceidx ); + F32 calcFaceScore( const ReflectParams ¶ms, U32 faceidx ); + +protected: + + GFXTexHandle depthBuff; + GFXTextureTargetRef renderTarget; + GFXCubemapHandle cubemap; + U32 mLastTexSize; + + class CubeFaceReflector : public ReflectorBase + { + typedef ReflectorBase Parent; + friend class CubeReflector; + + public: + U32 faceIdx; + CubeReflector *cube; + + virtual void updateReflection( const ReflectParams ¶ms ) { cube->updateFace( params, faceIdx ); } + virtual F32 calcScore( const ReflectParams ¶ms ); + }; + + CubeFaceReflector mFaces[6]; +}; + + +class PlaneReflector : public ReflectorBase +{ + typedef ReflectorBase Parent; + +public: + + PlaneReflector() + { + refplane.set( Point3F(0,0,0), Point3F(0,0,1) ); + objectSpace = false; + mLastTexSize = 0; + } + + virtual ~PlaneReflector() {} + + void registerReflector( SceneObject *inObject, + ReflectorDesc *inDesc ); + + virtual F32 calcScore( const ReflectParams ¶ms ); + virtual void updateReflection( const ReflectParams ¶ms ); + + /// Set up camera matrix for a reflection on the plane + MatrixF getCameraReflection( MatrixF &camTrans ); + + /// Oblique frustum clipping - use near plane of zbuffer as a clip plane + MatrixF getFrustumClipProj( MatrixF &modelview ); + +protected: + + U32 mLastTexSize; + + // The camera position at the last update. + Point3F mLastPos; + + // The camera direction at the last update. + VectorF mLastDir; + +public: + + GFXTextureTargetRef reflectTarget; + GFXTexHandle reflectTex; + PlaneF refplane; + bool objectSpace; +}; + +#endif // _REFLECTOR_H_ \ No newline at end of file diff --git a/sceneGraph/sceneGraph.cpp b/sceneGraph/sceneGraph.cpp new file mode 100644 index 0000000..ba6edcd --- /dev/null +++ b/sceneGraph/sceneGraph.cpp @@ -0,0 +1,865 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sceneGraph/sceneGraph.h" + +#include "sceneGraph/sceneObject.h" +#include "sceneGraph/sceneRoot.h" +#include "sceneGraph/sceneState.h" +#include "lighting/lightManager.h" +#include "core/strings/unicode.h" +#include "sim/netConnection.h" +#include "terrain/terrData.h" +#include "core/stream/fileStream.h" +#include "platform/profiler.h" +#include "renderInstance/renderPassManager.h" +#include "core/util/swizzle.h" +#include "gfx/gfxDevice.h" +#include "gfx/bitmap/gBitmap.h" + + +const U32 SceneGraph::csmMaxTraversalDepth = 4; +U32 SceneGraph::smStateKey = 0; +SceneGraph* gClientSceneGraph = NULL; +SceneGraph* gServerSceneGraph = NULL; +const U32 SceneGraph::csmRefPoolBlockSize = 4096; + + +SceneGraph::SceneGraph( bool isClient ) +{ + VECTOR_SET_ASSOCIATION( mRefPoolBlocks ); + VECTOR_SET_ASSOCIATION( mZoneManagers ); + VECTOR_SET_ASSOCIATION( mZoneLists ); + VECTOR_SET_ASSOCIATION( mRenderPassStack ); + + mLightManager = NULL; + + mSceneState = NULL; + + mCurrZoneEnd = 0; + mNumActiveZones = 0; + + mIsClient = isClient; + + mFogData.density = 0.0f; + mFogData.densityOffset = 0.0f; + mFogData.atmosphereHeight = 0.0f; + mFogData.color.set( 128, 128, 128 ); + + dMemset( &mWaterFogData, 0, sizeof( WaterFogData ) ); + mWaterFogData.color.set( 128, 128, 128 ); + mWaterFogData.plane.set( 0, 0, 1 ); + + mUsePostEffectFog = true; + + mNearClip = 0.1f; + mVisibleDistance = 500.0f; + + mCurrTerrain = NULL; + mFreeRefPool = NULL; + addRefPoolBlock(); + + mDisplayTargetResolution.set(0,0); +} + +SceneGraph::~SceneGraph() +{ + mCurrZoneEnd = 0; + mNumActiveZones = 0; + + for (U32 i = 0; i < mRefPoolBlocks.size(); i++) { + SceneObjectRef* pool = mRefPoolBlocks[i]; + for (U32 j = 0; j < csmRefPoolBlockSize; j++) + AssertFatal(pool[j].object == NULL, "Error, some object isn't properly out of the bins!"); + + delete [] pool; + } + mFreeRefPool = NULL; + + if (mLightManager) + mLightManager->deactivate(); +} + +void SceneGraph::addRefPoolBlock() +{ + mRefPoolBlocks.push_back(new SceneObjectRef[csmRefPoolBlockSize]); + for (U32 i = 0; i < csmRefPoolBlockSize-1; i++) + { + mRefPoolBlocks.last()[i].object = NULL; + mRefPoolBlocks.last()[i].prevInBin = NULL; + mRefPoolBlocks.last()[i].nextInBin = NULL; + mRefPoolBlocks.last()[i].nextInObj = &(mRefPoolBlocks.last()[i+1]); + } + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].object = NULL; + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].prevInBin = NULL; + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInBin = NULL; + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInObj = mFreeRefPool; + + mFreeRefPool = &(mRefPoolBlocks.last()[0]); +} + +SceneState* SceneGraph::createBaseState( ScenePassType passType, bool inverted /* = false */ ) +{ + // Determine the camera position, and store off render state... + const MatrixF modelview(GFX->getWorldMatrix()); + + MatrixF mv(modelview); + mv.inverse(); + const Point3F cp(mv.getPosition()); + + // Set up the base SceneState. + F32 left, right, top, bottom, nearPlane, farPlane; + bool isOrtho; + + GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho ); + const RectI &viewport = GFX->getViewport(); + + Frustum frust( isOrtho, + left, right, top, bottom, + nearPlane, farPlane, mv ); + + SceneState* baseState = new SceneState( + NULL, + this, + passType, + mCurrZoneEnd, + frust, + viewport, + true, + inverted ); + + AssertFatal( !mRenderPassStack.empty(), "SceneGraph::createBaseState() - Render pass stack is empty!" ); + + + // Assign shared matrix data to the render manager + RenderPassManager *renderPass = baseState->getRenderPass(); + renderPass->assignSharedXform(RenderPassManager::View, modelview); + renderPass->assignSharedXform(RenderPassManager::Projection, GFX->getProjectionMatrix()); + + return baseState; +} + +void SceneGraph::renderScene( ScenePassType passType, U32 objectMask ) +{ + PROFILE_SCOPE(SceneGraphRender); + + // If we don't have a render pass then set the diffuse. + if ( mRenderPassStack.empty() ) + _setDiffuseRenderPass(); + + // TODO: We should reset the counters on the canvas + // signal for all render passes right? + //mRenderManager->resetCounters(); + + SceneState *sceneState = createBaseState( passType ); + + // Get the lights for rendering the scene. + PROFILE_START(RegisterLights); + getLightManager()->registerGlobalLights( &sceneState->getFrustum(), false); + PROFILE_END(); + + PROFILE_START(SceneGraphRender_PreRenderSignal); + getPreRenderSignal().trigger( this, sceneState ); + PROFILE_END(); + + renderScene( sceneState, objectMask ); + //DebugDrawer::get()->render(); + + PROFILE_START(SceneGraphRender_PostRenderSignal); + getPostRenderSignal().trigger( this, sceneState ); + PROFILE_END(); + + // Remove the previously registered lights. + PROFILE_START(UnregisterLights); + getLightManager()->unregisterAllLights(); + PROFILE_END(); + + delete sceneState; +} + +void SceneGraph::renderScene( SceneState *sceneState, U32 objectMask ) +{ + // Set the current state. + mSceneState = sceneState; + + // This finds objects in the view frustum and calls + // prepRenderImage on each so that they can submit + // their render instances to the render bins. + // + // Note that internally buildSceneTree deals with zoning + // and finds the corrent start zone for interior support. + PROFILE_START(BuildSceneTree); + _buildSceneTree( mSceneState, objectMask ); + PROFILE_END(); + + // This fires off rendering the active render pass with + // the render instances submitted above. + PROFILE_START(TraverseScene); + _traverseSceneTree( mSceneState ); + PROFILE_END(); + + mSceneState = NULL; +} + +void SceneGraph::_traverseSceneTree(SceneState* pState) +{ + // DMM FIX: only handles trees one deep for now + + if (pState->mSubsidiaries.size() != 0) { + for (U32 i = 0; i < pState->mSubsidiaries.size(); i++) + _traverseSceneTree(pState->mSubsidiaries[i]); + } + + if (pState->mParent != NULL) { + // Comes from a transform portal. Let's see if we need to flip the cull + + // Now, the index gives the TransformPortal index in the Parent... + SceneObject* pPortalOwner = pState->mPortalOwner; + U32 portalIndex = pState->mPortalIndex; + AssertFatal(pPortalOwner != NULL && portalIndex != 0xFFFFFFFF, + "Hm, this should never happen. We should always have an owner and an index here"); + + // Ok, open the portal. Opening and closing the portals is a tricky bit of + // work, since we have to get the z values just right. We're going to toss + // the responsibility onto the shoulders of the object that owns the portal. + pPortalOwner->openPortal(portalIndex, pState, pState->mParent); + + // Render the objects in this subsidiary... + PROFILE_START(RenderCurrentImages); + pState->renderCurrentImages(); + PROFILE_END(); + + // close the portal + pPortalOwner->closePortal(portalIndex, pState, pState->mParent); + } else { + PROFILE_START(RenderCurrentImages); + pState->renderCurrentImages(); + PROFILE_END(); + } + +} + + +//---------------------------------------------------------------------------- +struct ScopingInfo { + Point3F scopePoint; + F32 scopeDist; + F32 scopeDistSquared; + const bool* zoneScopeStates; + NetConnection* connection; +}; + + +inline void scopeCallback(SceneObject* obj, ScopingInfo* pInfo) +{ + NetConnection* ptr = pInfo->connection; + + if (obj->isScopeable()) { + F32 difSq = (obj->getWorldSphere().center - pInfo->scopePoint).lenSquared(); + if (difSq < pInfo->scopeDistSquared) { + // Not even close, it's in... + ptr->objectInScope(obj); + } else { + // Check a little more closely... + F32 realDif = mSqrt(difSq); + if (realDif - obj->getWorldSphere().radius < pInfo->scopeDist) { + ptr->objectInScope(obj); + } + } + } +} + +void SceneGraph::scopeScene(const Point3F& scopePosition, + const F32 scopeDistance, + NetConnection* netConnection) +{ + // Find the start zone... + SceneObject* startObject; + U32 startZone; + findZone(scopePosition, startObject, startZone); + + // Search proceeds from the baseObject, and starts in the baseZone. + // General Outline: + // - Traverse up the tree, stopping at either the root, or the last zone manager + // that prevents traversal outside + // - This will set up the array of zone states, either scoped or unscoped. + // loop through all the objects, placing them in scope if they are in + // a scoped zone. + + // Objects (in particular, those managers that are part of the initial up + // traversal) keep track of whether or not they have done their scope traversal + // by a key which is the same key used for renderState determination + + SceneObject* pTraversalRoot = startObject; + U32 rootZone = startZone; + bool* zoneScopeState = new bool[mCurrZoneEnd]; + dMemset(zoneScopeState, 0, sizeof(bool) * mCurrZoneEnd); + + smStateKey++; + while (true) { + // Anything that we encounter in our up traversal is scoped + if (pTraversalRoot->isScopeable()) + netConnection->objectInScope(pTraversalRoot); + + pTraversalRoot->mLastStateKey = smStateKey; + if (pTraversalRoot->scopeObject(scopePosition, scopeDistance, + zoneScopeState)) { + // Continue upwards + if (pTraversalRoot->getNumCurrZones() != 1) { + Con::errorf(ConsoleLogEntry::General, + "Error, must have one and only one zone to be a traversal root. %s has %d", + pTraversalRoot->getName(), pTraversalRoot->getNumCurrZones()); + } + + rootZone = pTraversalRoot->getCurrZone(0); + pTraversalRoot = getZoneOwner(rootZone); + } else { + // Terminate. This is the traversal root... + break; + } + } + + S32 i; + + // Note that we start at 1 here rather than 0, since if the root was going to be + // scoped, it would have been scoped in the up traversal rather than at this stage. + // Also, it doesn't have a CurrZone(0), so that's bad... :) + for (i = 1; i < mZoneManagers.size(); i++) { + if (mZoneManagers[i].obj->mLastStateKey != smStateKey && + zoneScopeState[mZoneManagers[i].obj->getCurrZone(0)] == true) { + // Scope the zones in this manager... + mZoneManagers[i].obj->scopeObject(scopePosition, scopeDistance, zoneScopeState); + } + } + + + ScopingInfo info; + info.scopePoint = scopePosition; + info.scopeDist = scopeDistance; + info.scopeDistSquared = scopeDistance * scopeDistance; + info.zoneScopeStates = zoneScopeState; + info.connection = netConnection; + + for (i = 0; i < mCurrZoneEnd; i++) { + // Zip through the zone lists... + if (zoneScopeState[i] == true) { + // Scope zone i... + SceneObjectRef* pList = mZoneLists[i]; + SceneObjectRef* pWalk = pList->nextInBin; + while (pWalk != NULL) { + SceneObject* pObject = pWalk->object; + if (pObject->mLastStateKey != smStateKey) { + pObject->mLastStateKey = smStateKey; + scopeCallback(pObject, &info); + } + + pWalk = pWalk->nextInBin; + } + } + } + + delete [] zoneScopeState; + zoneScopeState = NULL; +} + + +//------------------------------------------------------------------------------ +bool SceneGraph::addObjectToScene(SceneObject* obj) +{ + if (obj->getType() & TerrainObjectType) { + // Double check + AssertFatal(dynamic_cast(obj) != NULL, "Not a terrain, but a terrain type?"); + mCurrTerrain = static_cast(obj); + } + + return obj->onSceneAdd(this); +} + + +//------------------------------------------------------------------------------ +void SceneGraph::removeObjectFromScene(SceneObject* obj) +{ + if (obj->mSceneManager != NULL) { + AssertFatal(obj->mSceneManager == this, "Error, removing from the wrong sceneGraph!"); + + if (obj->getType() & TerrainObjectType) { + // Double check + AssertFatal(dynamic_cast(obj) != NULL, "Not a terrain, but a terrain type?"); + if (mCurrTerrain == static_cast(obj)) + mCurrTerrain = NULL; + } + + obj->onSceneRemove(); + } +} + + +//------------------------------------------------------------------------------ +void SceneGraph::registerZones(SceneObject* obj, U32 numZones) +{ + AssertFatal(alreadyManagingZones(obj) == false, "Error, added zones twice!"); + compactZonesCheck(); + + U32 i; + U32 retVal = mCurrZoneEnd; + mCurrZoneEnd += numZones; + mNumActiveZones += numZones; + + mZoneLists.increment(numZones); + for (i = mCurrZoneEnd - numZones; i < mCurrZoneEnd; i++) { + mZoneLists[i] = new SceneObjectRef; + mZoneLists[i]->object = obj; + mZoneLists[i]->nextInBin = NULL; + mZoneLists[i]->prevInBin = NULL; + mZoneLists[i]->nextInObj = NULL; + } + + ZoneManager newEntry; + newEntry.obj = obj; + newEntry.numZones = numZones; + newEntry.zoneRangeStart = retVal; + mZoneManagers.push_back(newEntry); + obj->mZoneRangeStart = retVal; + + // Since we now have new zones in this space, we need to rezone any intersecting + // objects. Query the container database to find all intersecting/contained + // objects, and rezone them + // query + SimpleQueryList list; + getContainer()->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, &list); + + // DMM: Horrendously inefficient. We should do the rejection test against + // obj here + for (i = 0; i < list.mList.size(); i++) { + SceneObject* rezoneObj = list.mList[i]; + + // Make sure this is actually a SceneObject, is not the zone manager, + // and is added to the scene manager. + if (rezoneObj != NULL && rezoneObj != obj && + rezoneObj->mSceneManager != NULL) + rezoneObject(rezoneObj); + } +} + + +//------------------------------------------------------------------------------ +void SceneGraph::unregisterZones(SceneObject* obj) +{ + AssertFatal(alreadyManagingZones(obj) == true, "Error, not managing any zones!"); + + // First, let's nuke the lists associated with this object. We can leave the + // horizontal references in the objects in place, they'll be freed before too + // long. + for (U32 i = 0; i < mZoneManagers.size(); i++) { + if (obj == mZoneManagers[i].obj) { + AssertFatal(mNumActiveZones >= mZoneManagers[i].numZones, "Too many zones removed"); + + for (U32 j = mZoneManagers[i].zoneRangeStart; + j < mZoneManagers[i].zoneRangeStart + mZoneManagers[i].numZones; j++) { + SceneObjectRef* pList = mZoneLists[j]; + SceneObjectRef* pWalk = pList->nextInBin; + + // We have to tree pList a little differently, since it's not a traditional + // link. We can just delete it at the end... + pList->object = NULL; + delete pList; + mZoneLists[j] = NULL; + + while (pWalk) { + AssertFatal(pWalk->object != NULL, "Error, must have an object!"); + SceneObjectRef* pTrash = pWalk; + pWalk = pWalk->nextInBin; + + pTrash->nextInBin = pTrash; + pTrash->prevInBin = pTrash; + + // Ok, now we need to zip through the list in the object to find + // this and remove it since we aren't doubly linked... + SceneObjectRef** ppRef = &pTrash->object->mZoneRefHead; + bool found = false; + while (*ppRef) { + if (*ppRef == pTrash) { + // Remove it + *ppRef = (*ppRef)->nextInObj; + found = true; + + pTrash->object = NULL; + pTrash->nextInBin = NULL; + pTrash->prevInBin = NULL; + pTrash->nextInObj = NULL; + pTrash->zone = 0xFFFFFFFF; + freeObjectRef(pTrash); + break; + } + + ppRef = &(*ppRef)->nextInObj; + } + AssertFatal(found == true, "Error, should have found that reference!"); + } + } + + mNumActiveZones -= mZoneManagers[i].numZones; + mZoneManagers.erase(i); + obj->mZoneRangeStart = 0xFFFFFFFF; + + // query + if ((mIsClient == true && obj != gClientSceneRoot) || + (mIsClient == false && obj != gServerSceneRoot)) + { + SimpleQueryList list; + getContainer()->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, &list); + for (i = 0; i < list.mList.size(); i++) { + SceneObject* rezoneObj = list.mList[i]; + if (rezoneObj != NULL && rezoneObj != obj && rezoneObj->mSceneManager != NULL) + rezoneObject(rezoneObj); + } + } + return; + } + } + compactZonesCheck(); + + // Other assert already ensured we will terminate properly... + AssertFatal(false, "Error, impossible condition reached!"); +} + + +//------------------------------------------------------------------------------ +void SceneGraph::compactZonesCheck() +{ + if (mNumActiveZones > (mCurrZoneEnd / 2)) + return; + + // DMMTODO: Compact zones... + // +} + + +//------------------------------------------------------------------------------ +bool SceneGraph::alreadyManagingZones(SceneObject* obj) const +{ + for (U32 i = 0; i < mZoneManagers.size(); i++) + if (obj == mZoneManagers[i].obj) + return true; + return false; +} + + +//------------------------------------------------------------------------------ +void SceneGraph::findZone(const Point3F& p, SceneObject*& owner, U32& zone) +{ + // Since there is no zone information maintained by the sceneGraph + // any more, this is quite brain-dead. Maybe fix this? DMM + // + U32 currZone = 0; + SceneObject* currOwner = mZoneManagers[0].obj; + + PROFILE_START(SG_FindZone); + while (true) + { + bool cont = false; + + // Loop, but don't consider the root... + for (U32 i = 1; i < mZoneManagers.size(); i++) + { + // RLP/Sickhead NOTE: This warning is currently + // disabled to support the new Zone/Portal functionality + // but needs to be investigated more thoroughly for any side effects. + + //AssertWarn(mZoneManagers[i].obj->getNumCurrZones() == 1 || (i == 0 && mZoneManagers[i].obj->getNumCurrZones() == 0), + // "ZoneManagers are only allowed to belong to one and only one zone!"); + + if (mZoneManagers[i].obj->getCurrZone(0) == currZone) + { + // Test to see if the point is inside + U32 testZone = mZoneManagers[i].obj->getPointZone(p); + if (testZone != 0) + { + // Point is in this manager, reset, and descend + cont = true; + currZone = testZone; + currOwner = mZoneLists[currZone]->object; + break; + } + } + } + + // Have we gone as far as we can? + if (cont == false) + break; + } + + zone = currZone; + owner = currOwner; + PROFILE_END(); +} + + +//------------------------------------------------------------------------------ +void SceneGraph::rezoneObject(SceneObject* obj) +{ + AssertFatal(obj->mSceneManager != NULL && obj->mSceneManager == this, "Error, bad or no scenemanager here!"); + PROFILE_START(SG_Rezone); + + if (obj->mZoneRefHead != NULL) + { + // Remove the object from the zone lists... + SceneObjectRef* walk = obj->mZoneRefHead; + while (walk) + { + SceneObjectRef* remove = walk; + walk = walk->nextInObj; + + remove->prevInBin->nextInBin = remove->nextInBin; + if (remove->nextInBin) + remove->nextInBin->prevInBin = remove->prevInBin; + + remove->nextInObj = NULL; + remove->nextInBin = NULL; + remove->prevInBin = NULL; + remove->object = NULL; + remove->zone = U32(-1); + + freeObjectRef(remove); + } + + obj->mZoneRefHead = NULL; + } + + + U32 numMasterZones = 0; + SceneObject* masterZoneOwners[SceneObject::MaxObjectZones]; + U32 masterZoneBuffer[SceneObject::MaxObjectZones]; + + S32 i; + for (i = S32(mZoneManagers.size()) - 1; i >= 0; i--) + { + // Careful, zone managers are in the list at this point... + if (obj == mZoneManagers[i].obj) + continue; + + if (mZoneManagers[i].obj->getWorldBox().isOverlapped(obj->getWorldBox()) == false) + continue; + + // We have several possible outcomes here + // 1: Object completely contained in zoneManager + // 2: object overlaps manager. (outside zone is included) + // 3: Object completely contains manager (outside zone not included) + // In case 3, we ignore the possibility that the object resides in + // zones managed by the manager, and we can continue + // In case 1 and 2, we need to query the manager for zones. + // In case 1, we break out of the loop, unless the object is not a + // part of the managers interior zones. + // In case 2, we need to continue querying the database until we + // stop due to one of the above conditions (guaranteed to happen + // when we reach the sceneRoot. (Zone 0)) + + // We need to make sure that floating point precision doesn't skip out + // on two objects with global bounds + /* + if (!(mZoneManagers[i].obj->isGlobalBounds() && obj->isGlobalBounds())) + { + if (obj->getWorldBox().isContained(mZoneManagers[i].obj->getWorldBox())) + { + // case 3 + continue; + } + } + */ + + // Query the zones... + U32 numZones = 0; + U32 zoneBuffer[SceneObject::MaxObjectZones]; + + bool outsideIncluded = mZoneManagers[i].obj->getOverlappingZones(obj, zoneBuffer, &numZones); + AssertFatal(numZones != 0 || outsideIncluded == true, "Hm, no zones, but not in the outside zone? Impossible!"); + + // Copy the included zones out + if (numMasterZones + numZones > SceneObject::MaxObjectZones) + Con::errorf(ConsoleLogEntry::General, "Zone Overflow! Object will NOT render correctly. Copying out as many as possible"); + numZones = getMin(numZones, SceneObject::MaxObjectZones - numMasterZones); + + for (U32 j = 0; j < numZones; j++) + { + masterZoneBuffer[numMasterZones] = zoneBuffer[j]; + masterZoneOwners[numMasterZones++] = mZoneManagers[i].obj; + } + + if (outsideIncluded == false) { + // case 3. We can stop the search at this point... + break; + } else { + // Case 2. We need to continue searching... + // ... + } + } + // Copy the found zones into the buffer... + AssertFatal(numMasterZones != 0, "Error, no zones found? Should always find root at least."); + + obj->mNumCurrZones = numMasterZones; + for (i = 0; i < numMasterZones; i++) + { + // Insert into zone masterZoneBuffer[i] + SceneObjectRef* zoneList = mZoneLists[masterZoneBuffer[i]]; + AssertFatal(zoneList != NULL, "Error, no list for this zone!"); + + SceneObjectRef* newRef = allocateObjectRef(); + + // Get it into the list + newRef->zone = masterZoneBuffer[i]; + newRef->object = obj; + newRef->nextInBin = zoneList->nextInBin; + newRef->prevInBin = zoneList; + if (zoneList->nextInBin) + zoneList->nextInBin->prevInBin = newRef; + zoneList->nextInBin = newRef; + + // Now get it into the objects chain... + newRef->nextInObj = obj->mZoneRefHead; + obj->mZoneRefHead = newRef; + } + + // Let the object know its zones have changed. + obj->onRezone(); + + PROFILE_END(); +} + +void SceneGraph::zoneInsert(SceneObject* obj) +{ + PROFILE_START(SG_ZoneInsert); + AssertFatal(obj->mNumCurrZones == 0, "Error, already entered into zone list..."); + + rezoneObject(obj); + + if (obj->isManagingZones()) { + // Query the container database to find all intersecting/contained + // objects, and rezone them + // query + SimpleQueryList list; + getContainer()->findObjects(obj->mWorldBox, 0xFFFFFFFF, SimpleQueryList::insertionCallback, &list); + + // DMM: Horrendously inefficient. We should do the rejection test against + // obj here, but the zoneManagers are so infrequently inserted and removed that + // it really doesn't matter... + for (U32 i = 0; i < list.mList.size(); i++) + { + SceneObject* rezoneObj = list.mList[i]; + + // Make sure this is actually a SceneObject, is not the zone manager, + // and is added to the scene manager. + if (rezoneObj != NULL && rezoneObj != obj && + rezoneObj->mSceneManager == this) + rezoneObject(rezoneObj); + } + } + PROFILE_END(); +} + + +//------------------------------------------------------------------------------ +void SceneGraph::zoneRemove(SceneObject* obj) +{ + PROFILE_START(SG_ZoneRemove); + obj->mNumCurrZones = 0; + + // Remove the object from the zone lists... + SceneObjectRef* walk = obj->mZoneRefHead; + while (walk) { + SceneObjectRef* remove = walk; + walk = walk->nextInObj; + + remove->prevInBin->nextInBin = remove->nextInBin; + if (remove->nextInBin) + remove->nextInBin->prevInBin = remove->prevInBin; + + remove->nextInObj = NULL; + remove->nextInBin = NULL; + remove->prevInBin = NULL; + remove->object = NULL; + remove->zone = U32(-1); + + freeObjectRef(remove); + } + obj->mZoneRefHead = NULL; + PROFILE_END(); +} + +void SceneGraph::setDisplayTargetResolution( const Point2I &size ) +{ + mDisplayTargetResolution = size; +} + +const Point2I & SceneGraph::getDisplayTargetResolution() const +{ + return mDisplayTargetResolution; +} + +bool SceneGraph::setLightManager( const char *lmName ) +{ + LightManager *lm = LightManager::findByName( lmName ); + if ( !lm ) + return false; + + return _setLightManager( lm ); +} + +bool SceneGraph::_setLightManager( LightManager *lm ) +{ + // Avoid unessasary work reinitializing materials. + if ( lm == mLightManager ) + return true; + + // Make sure its valid... else fail! + if ( !lm->isCompatible() ) + return false; + + // We only deactivate it... all light managers are singletons + // and will manager their own lifetime. + if ( mLightManager ) + mLightManager->deactivate(); + + mLightManager = lm; + + if ( mLightManager ) + { + // HACK: We're activating the diffuse render pass + // here so that its there for the light manager + // activation. + _setDiffuseRenderPass(); + + mLightManager->activate( this ); + } + + return true; +} + +LightManager* SceneGraph::getLightManager() +{ + AssertFatal( mIsClient, "SceneGraph::getLightManager() - You should never access the light manager via the server scene graph!" ); + return mLightManager; +} + +void SceneGraph::_setDiffuseRenderPass() +{ + mRenderPassStack.clear(); + + RenderPassManager *rpm; + if ( Sim::findObject( "DiffuseRenderPassManager", rpm ) ) + pushRenderPass( rpm ); +} + +void SceneGraph::pushRenderPass( RenderPassManager *rpm ) +{ + mRenderPassStack.push_back( rpm ); +} + +void SceneGraph::popRenderPass() +{ + AssertFatal( !mRenderPassStack.empty(), "SceneGraph::popRenderPass() - The stack is empty!" ); + mRenderPassStack.pop_back(); +} + diff --git a/sceneGraph/sceneGraph.h b/sceneGraph/sceneGraph.h new file mode 100644 index 0000000..0091bf2 --- /dev/null +++ b/sceneGraph/sceneGraph.h @@ -0,0 +1,344 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENEGRAPH_H_ +#define _SCENEGRAPH_H_ + +//Includes +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _FOGSTRUCTS_H_ +#include "sceneGraph/fogStructs.h" +#endif + +class LightManager; +class SceneGraph; +class SceneState; +class NetConnection; +class RenderPassManager; +class TerrainBlock; + + +/// A signal used to notify of render passes. +/// @see SceneGraph +typedef Signal SceneGraphRenderSignal; + + +/// The type of scene pass. +/// @see SceneGraph +/// @see SceneState +enum ScenePassType +{ + /// The regular diffuse scene pass. + SPT_Diffuse, + + /// The scene pass made for reflection rendering. + SPT_Reflect, + + /// The scene pass made for shadow map rendering. + SPT_Shadow, + + /// A scene pass that isn't one of the other + /// predefined scene pass types. + SPT_Other, +}; + + +/// +class SceneGraph +{ + public: + SceneGraph(bool isClient); + ~SceneGraph(); + + /// @name SceneObject Management + /// @{ + + /// + bool addObjectToScene(SceneObject*); + void removeObjectFromScene(SceneObject*); + void zoneInsert(SceneObject*); + void zoneRemove(SceneObject*); + + Container* getContainer() const { return mIsClient ? &gClientContainer : &gServerContainer; } + /// @} + + + /// @name Zone management + /// @{ + + /// + void registerZones(SceneObject*, U32 numZones); + void unregisterZones(SceneObject*); + SceneObject* getZoneOwner(const U32 zone); + /// @} + + /// @name Rendering and Scope Management + /// @{ + + /// + + SceneState* createBaseState( ScenePassType passType, bool inverted = false ); + + void renderScene( ScenePassType passType, U32 objectMask = -1 ); + + void renderScene( SceneState *state, U32 objectMask = -1 ); + + void scopeScene(const Point3F& scopePosition, + const F32 scopeDistance, + NetConnection* netConnection); + + /// Returns the currently active scene state. + SceneState* getSceneState() const { return mSceneState; } + + /// @} + + /// @name Fog/Visibility Management + /// @{ + + void setPostEffectFog( bool enable ) { mUsePostEffectFog = enable; } + bool usePostEffectFog() const { return mUsePostEffectFog; } + + /// Accessor for the FogData structure. + const FogData& getFogData() const { return mFogData; } + + /// Sets the FogData structure. + void setFogData( const FogData &data ) { mFogData = data; } + + /// Accessor for the WaterFogData structure. + const WaterFogData& getWaterFogData() const { return mWaterFogData; } + + /// Sets the WaterFogData structure. + void setWaterFogData( const WaterFogData &data ) { mWaterFogData = data; } + + /// Used by LevelInfo to set the default visible distance for + /// rendering the scene. + /// + /// Note this should not be used to alter culling which is + /// controlled by the active frustum when a SceneState is created. + /// + /// @see SceneState + /// @see GameProcessCameraQuery + /// @see LevelInfo + void setVisibleDistance( F32 dist ) { mVisibleDistance = dist; } + + /// Returns the default visible distance for the scene. + F32 getVisibleDistance() const { return mVisibleDistance; } + + /// Used by LevelInfo to set the default near clip plane + /// for rendering the scene. + /// + /// @see GameProcessCameraQuery + /// @see LevelInfo + void setNearClip( F32 nearClip ) { mNearClip = nearClip; } + + /// Returns the default near clip distance for the scene. + F32 getNearClip() const { return mNearClip; } + + /// @} + + U32 getStateKey() const { return smStateKey; } + + U32 incStateKey() { return ++smStateKey; } + + //-------------------------------------- Private interface and data + protected: + + static const U32 csmMaxTraversalDepth; + static U32 smStateKey; + + /// The currently active scene state or NULL if we're + /// not in the process of rendering. + SceneState *mSceneState; + + public: + + /// This var is for cases where you need the "normal" camera position if you are + /// in a reflection pass. Used for correct fog calculations in reflections. + Point3F mNormCamPos; + + protected: + + bool mIsClient; + + // NonClipProjection is the projection matrix without oblique frustum clipping + // applied to it (in reflections) + MatrixF mNonClipProj; + + F32 mInvVisibleDistance; + + U32 mCurrZoneEnd; + U32 mNumActiveZones; + + FogData mFogData; + + WaterFogData mWaterFogData; + + bool mUsePostEffectFog; + + F32 mVisibleDistance; + + F32 mNearClip; + + Vector mRenderPassStack; + + LightManager* mLightManager; + + TerrainBlock* mCurrTerrain; + + void addRefPoolBlock(); + SceneObjectRef* allocateObjectRef(); + void freeObjectRef(SceneObjectRef*); + SceneObjectRef* mFreeRefPool; + Vector mRefPoolBlocks; + static const U32 csmRefPoolBlockSize; + + /// @see setDisplayTargetResolution + Point2I mDisplayTargetResolution; + + /// + void _setDiffuseRenderPass(); + + public: + + /// @name dtr Display Target Resolution + /// + /// Some rendering must be targeted at a specific display resolution. + /// This display resolution is distinct from the current RT's size + /// (such as when rendering a reflection to a texture, for instance) + /// so we store the size at which we're going to display the results of + /// the current render. + /// + /// @{ + + /// + void setDisplayTargetResolution(const Point2I &size); + const Point2I &getDisplayTargetResolution() const; + + /// @} + + // Returns the current active light manager. + LightManager* getLightManager(); + + /// Finds the light manager by name and activates it. + bool setLightManager( const char *lmName ); + + /// Returns the current render pass manager on the stack. + RenderPassManager* getRenderPass() const + { + AssertFatal( !mRenderPassStack.empty(), "SceneGraph::getRenderManager() - The stack is empty!" ); + return mRenderPassStack.last(); + } + + void pushRenderPass( RenderPassManager *rpm ); + + void popRenderPass(); + + TerrainBlock* getCurrentTerrain() { return mCurrTerrain; } + + // NonClipProjection is the projection matrix without oblique frustum clipping + // applied to it (in reflections) + void setNonClipProjection( const MatrixF &proj ) { mNonClipProj = proj; } + const MatrixF& getNonClipProjection() const { return mNonClipProj; } + + static SceneGraphRenderSignal& getPreRenderSignal() + { + static SceneGraphRenderSignal theSignal; + return theSignal; + } + + static SceneGraphRenderSignal& getPostRenderSignal() + { + static SceneGraphRenderSignal theSignal; + return theSignal; + } + + // Object database for zone managers + protected: + struct ZoneManager { + SceneObject* obj; + U32 zoneRangeStart; + U32 numZones; + }; + Vector mZoneManagers; + + /// Zone Lists + /// + /// @note The object refs in this are somewhat singular in that the object pointer does not + /// point to a referenced object, but the owner of that zone... + Vector mZoneLists; + +protected: + + /// Deactivates the previous light manager and activates the new one. + bool _setLightManager( LightManager *lm ); + + void _traverseSceneTree( SceneState *state ); + + void _buildSceneTree( SceneState *state, + U32 objectMask = (U32)-1, + SceneObject *baseObject = NULL, + U32 baseZone = 0, + U32 currDepth = 0 ); + + void treeTraverseVisit(SceneObject*, SceneState*, const U32); + + void compactZonesCheck(); + bool alreadyManagingZones(SceneObject*) const; + +public: + void findZone(const Point3F&, SceneObject*&, U32&); +protected: + + void rezoneObject(SceneObject*); +}; + +extern SceneGraph* gClientSceneGraph; +extern SceneGraph* gServerSceneGraph; + + +inline SceneObjectRef* SceneGraph::allocateObjectRef() +{ + if (mFreeRefPool == NULL) { + addRefPoolBlock(); + } + AssertFatal(mFreeRefPool!=NULL, "Error, should always have a free reference here!"); + + SceneObjectRef* ret = mFreeRefPool; + mFreeRefPool = mFreeRefPool->nextInObj; + + ret->nextInObj = NULL; + return ret; +} + +inline void SceneGraph::freeObjectRef(SceneObjectRef* trash) +{ + trash->nextInBin = NULL; + trash->prevInBin = NULL; + trash->nextInObj = mFreeRefPool; + mFreeRefPool = trash; +} + +inline SceneObject* SceneGraph::getZoneOwner(const U32 zone) +{ + AssertFatal(zone < mCurrZoneEnd, "Error, out of bounds zone selected!"); + + return mZoneLists[zone]->object; +} + + +#endif //_SCENEGRAPH_H_ + + diff --git a/sceneGraph/sceneObject.cpp b/sceneGraph/sceneObject.cpp new file mode 100644 index 0000000..c3e7b28 --- /dev/null +++ b/sceneGraph/sceneObject.cpp @@ -0,0 +1,2319 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sceneGraph/sceneObject.h" + +#include "sceneGraph/sceneGraph.h" +#include "console/consoleTypes.h" +#include "collision/extrudedPolyList.h" +#include "collision/earlyOutPolyList.h" +#include "platform/profiler.h" +#include "gfx/bitmap/gBitmap.h" +#include "sim/netConnection.h" +#include "math/util/frustum.h" + + +IMPLEMENT_CONOBJECT(SceneObject); + +const U32 Container::csmNumBins = 16; +const F32 Container::csmBinSize = 64; +const F32 Container::csmTotalBinSize = Container::csmBinSize * Container::csmNumBins; +U32 Container::smCurrSeqKey = 1; +const U32 Container::csmRefPoolBlockSize = 4096; + +Signal SceneObject::smSceneObjectAdd; +Signal SceneObject::smSceneObjectRemove; + +// Statics used by buildPolyList methods +AbstractPolyList* sPolyList; +SphereF sBoundingSphere; +Box3F sBoundingBox; + +// Statics used by collide methods +ExtrudedPolyList sExtrudedPolyList; +Polyhedron sBoxPolyhedron; + +//-------------------------------------------------------------------------- +//-------------------------------------- Console callbacks +// + +ConsoleMethod( SceneObject, getTransform, const char*, 2, 2, "Get transform of object.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const MatrixF& mat = object->getTransform(); + Point3F pos; + mat.getColumn(3,&pos); + AngAxisF aa(mat); + dSprintf(returnBuffer,256,"%g %g %g %g %g %g %g", + pos.x,pos.y,pos.z,aa.axis.x,aa.axis.y,aa.axis.z,aa.angle); + return returnBuffer; +} + +ConsoleMethod( SceneObject, getPosition, const char*, 2, 2, "Get position of object.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const MatrixF& mat = object->getTransform(); + Point3F pos; + mat.getColumn(3,&pos); + dSprintf(returnBuffer,256,"%g %g %g",pos.x,pos.y,pos.z); + return returnBuffer; +} + +ConsoleMethod( SceneObject, getEulerRotation, const char*, 2, 2, "Get Euler rotation of object.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const MatrixF& mat = object->getTransform(); + EulerF rot = mat.toEuler(); + dSprintf(returnBuffer,256,"%g %g %g",mRadToDeg(rot.x),mRadToDeg(rot.y),mRadToDeg(rot.z)); + return returnBuffer; +} + +ConsoleMethod( SceneObject, getForwardVector, const char*, 2, 2, "Returns a vector indicating the direction this object is facing.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const MatrixF& mat = object->getTransform(); + Point3F dir; + mat.getColumn(1,&dir); + dSprintf(returnBuffer,256,"%g %g %g",dir.x,dir.y,dir.z); + return returnBuffer; +} + +ConsoleMethod( SceneObject, setTransform, void, 3, 3, "(Transform T)") +{ + Point3F pos; + const MatrixF& tmat = object->getTransform(); + tmat.getColumn(3,&pos); + AngAxisF aa(tmat); + + dSscanf(argv[2],"%g %g %g %g %g %g %g", + &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); + + MatrixF mat; + aa.setMatrix(&mat); + mat.setColumn(3,pos); + object->setTransform(mat); +} + +ConsoleMethod( SceneObject, getScale, const char*, 2, 2, "Get scaling as a Point3F.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const VectorF & scale = object->getScale(); + dSprintf(returnBuffer, 256, "%g %g %g", + scale.x, scale.y, scale.z); + return(returnBuffer); +} + +ConsoleMethod( SceneObject, setScale, void, 3, 3, "(Point3F scale)") +{ + VectorF scale(0.f,0.f,0.f); + dSscanf(argv[2], "%g %g %g", &scale.x, &scale.y, &scale.z); + object->setScale(scale); +} + +ConsoleMethod( SceneObject, getWorldBox, const char*, 2, 2, "Returns six fields, two Point3Fs, containing the min and max points of the worldbox.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const Box3F& box = object->getWorldBox(); + dSprintf(returnBuffer,256,"%g %g %g %g %g %g", + box.minExtents.x, box.minExtents.y, box.minExtents.z, + box.maxExtents.x, box.maxExtents.y, box.maxExtents.z); + return returnBuffer; +} + +ConsoleMethod( SceneObject, getWorldBoxCenter, const char*, 2, 2, "Returns the center of the world bounding box.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const Box3F& box = object->getWorldBox(); + Point3F center; + box.getCenter(¢er); + + dSprintf(returnBuffer,256,"%g %g %g", center.x, center.y, center.z); + return returnBuffer; +} + +ConsoleMethod( SceneObject, getObjectBox, const char *, 2, 2, "Returns the bounding box relative to the object's origin.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + const Box3F& box = object->getObjBox(); + dSprintf(returnBuffer,256,"%g %g %g %g %g %g", + box.minExtents.x, box.minExtents.y, box.minExtents.z, + box.maxExtents.x, box.maxExtents.y, box.maxExtents.z); + return returnBuffer; +} + +ConsoleMethod( SceneObject, isGlobalBounds, bool, 2, 2, "Returns true if the object has a global bounds.") +{ + return object->isGlobalBounds(); +} + +ConsoleFunctionGroupBegin( Containers, "Functions for ray casting and spatial queries.\n\n" + "@note These only work server-side."); + +ConsoleFunction(containerBoxEmpty, bool, 4, 6, "(bitset mask, Point3F center, float xRadius, float yRadius, float zRadius)" + "See if any objects of given types are present in box of given extent.\n\n" + "@note Extent parameter is last since only one radius is often needed. If one radius is provided, " + "the yRadius and zRadius are assumed to be the same.\n" + "@param mask Indicates the type of objects we are checking against.\n" + "@param center Center of box.\n" + "@param xRadius See above.\n" + "@param yRadius See above.\n" + "@param zRadius See above.") +{ + Point3F center; + Point3F extent; + U32 mask = dAtoi(argv[1]); + dSscanf(argv[2], "%g %g %g", ¢er.x, ¢er.y, ¢er.z); + extent.x = dAtof(argv[3]); + extent.y = argc > 4 ? dAtof(argv[4]) : extent.x; + extent.z = argc > 5 ? dAtof(argv[5]) : extent.x; + + Box3F B(center - extent, center + extent, true); + + EarlyOutPolyList polyList; + polyList.mPlaneList.clear(); + polyList.mNormal.set(0,0,0); + polyList.mPlaneList.setSize(6); + polyList.mPlaneList[0].set(B.minExtents, VectorF(-1,0,0)); + polyList.mPlaneList[1].set(B.maxExtents, VectorF(0,1,0)); + polyList.mPlaneList[2].set(B.maxExtents, VectorF(1,0,0)); + polyList.mPlaneList[3].set(B.minExtents, VectorF(0,-1,0)); + polyList.mPlaneList[4].set(B.minExtents, VectorF(0,0,-1)); + polyList.mPlaneList[5].set(B.maxExtents, VectorF(0,0,1)); + + return ! gServerContainer.buildPolyList(B, mask, &polyList); +} + +ConsoleFunction( initContainerRadiusSearch, void, 4, 4, "(Point3F pos, float radius, bitset mask)" + "Start a search for items within radius of pos, filtering by bitset mask.") +{ + F32 x, y, z; + dSscanf(argv[1], "%g %g %g", &x, &y, &z); + F32 r = dAtof(argv[2]); + U32 mask = dAtoi(argv[3]); + + gServerContainer.initRadiusSearch(Point3F(x, y, z), r, mask); +} + +ConsoleFunction( initContainerTypeSearch, void, 2, 2, "(bitset mask)" + "Start a search for all items of the types specified by the bitset mask.") +{ + U32 mask = dAtoi(argv[1]); + + gServerContainer.initTypeSearch(mask); +} + +ConsoleFunction( containerSearchNext, S32, 1, 1, "Get next item from a search started with initContainerRadiusSearch or initContainerTypeSearch.") +{ + return gServerContainer.containerSearchNext(); +} + +ConsoleFunction( containerSearchCurrDist, F32, 1, 1, "Get distance of the center of the current item from the center of the current initContainerRadiusSearch.") +{ + return gServerContainer.containerSearchCurrDist(); +} + +ConsoleFunction( containerSearchCurrRadiusDist, F32, 1, 1, "Get the distance of the closest point of the current item from the center of the current initContainerRadiusSearch.") +{ + return gServerContainer.containerSearchCurrRadiusDist(); +} + +ConsoleFunction( containerRayCast, const char*, 4, 5, "( Point3F start, Point3F end, bitset mask, SceneObject exempt=NULL )" + "Cast a ray from start to end, checking for collision against items matching mask.\n\n" + "If exempt is specified, then it is temporarily excluded from collision checks (For " + "instance, you might want to exclude the player if said player was firing a weapon.)\n" + "@returns A string containing either null, if nothing was struck, or these fields:\n" + " - The ID of the object that was struck.\n" + " - The x, y, z position that it was struck.\n" + " - The x, y, z of the normal of the face that was struck.") +{ + char *returnBuffer = Con::getReturnBuffer(256); + Point3F start, end; + dSscanf(argv[1], "%g %g %g", &start.x, &start.y, &start.z); + dSscanf(argv[2], "%g %g %g", &end.x, &end.y, &end.z); + U32 mask = dAtoi(argv[3]); + + SceneObject* pExempt = NULL; + if (argc > 4) { + U32 exemptId = dAtoi(argv[4]); + Sim::findObject(exemptId, pExempt); + } + if (pExempt) + pExempt->disableCollision(); + + RayInfo rinfo; + S32 ret = 0; + if (gServerContainer.castRay(start, end, mask, &rinfo) == true) + ret = rinfo.object->getId(); + + if (pExempt) + pExempt->enableCollision(); + + // add the hit position and normal? + if(ret) + { + dSprintf(returnBuffer, 256, "%d %g %g %g %g %g %g", + ret, rinfo.point.x, rinfo.point.y, rinfo.point.z, + rinfo.normal.x, rinfo.normal.y, rinfo.normal.z); + } + else + { + returnBuffer[0] = '0'; + returnBuffer[1] = '\0'; + } + + return(returnBuffer); +} + +ConsoleFunctionGroupEnd( Containers ); + +// Utility method for bin insertion +void getBinRange(const F32 min, + const F32 max, + U32& minBin, + U32& maxBin) +{ + bool b = (max - min) >= 0; + if (!b) + { + int a = 5; + } + AssertFatal(b, "Error, bad range! in getBinRange"); + + if ((max - min) >= (Container::csmTotalBinSize - Container::csmBinSize)) + { + F32 minCoord = mFmod(min, Container::csmTotalBinSize); + if (minCoord < 0.0f) + { + minCoord += Container::csmTotalBinSize; + + // This is truly lame, but it can happen. There must be a better way to + // deal with this. + if (minCoord == Container::csmTotalBinSize) + minCoord = Container::csmTotalBinSize - 0.01; + } + + AssertFatal(minCoord >= 0.0 && minCoord < Container::csmTotalBinSize, "Bad minCoord"); + + minBin = U32(minCoord / Container::csmBinSize); + AssertFatal(minBin < Container::csmNumBins, avar("Error, bad clipping! (%g, %d)", minCoord, minBin)); + + maxBin = minBin + (Container::csmNumBins - 1); + return; + } + else + { + + F32 minCoord = mFmod(min, Container::csmTotalBinSize); + + if (minCoord < 0.0f) + { + minCoord += Container::csmTotalBinSize; + + // This is truly lame, but it can happen. There must be a better way to + // deal with this. + if (minCoord == Container::csmTotalBinSize) + minCoord = Container::csmTotalBinSize - 0.01; + } + AssertFatal(minCoord >= 0.0 && minCoord < Container::csmTotalBinSize, "Bad minCoord"); + + F32 maxCoord = mFmod(max, Container::csmTotalBinSize); + if (maxCoord < 0.0f) { + maxCoord += Container::csmTotalBinSize; + + // This is truly lame, but it can happen. There must be a better way to + // deal with this. + if (maxCoord == Container::csmTotalBinSize) + maxCoord = Container::csmTotalBinSize - 0.01; + } + AssertFatal(maxCoord >= 0.0 && maxCoord < Container::csmTotalBinSize, "Bad maxCoord"); + + minBin = U32(minCoord / Container::csmBinSize); + maxBin = U32(maxCoord / Container::csmBinSize); + AssertFatal(minBin < Container::csmNumBins, avar("Error, bad clipping(min)! (%g, %d)", maxCoord, minBin)); + AssertFatal(minBin < Container::csmNumBins, avar("Error, bad clipping(max)! (%g, %d)", maxCoord, maxBin)); + + // MSVC6 seems to be generating some bad floating point code around + // here when full optimizations are on. The min != max test should + // not be needed, but it clears up the VC issue. + if (min != max && minCoord > maxCoord) + maxBin += Container::csmNumBins; + + AssertFatal(maxBin >= minBin, "Error, min should always be less than max!"); + } +} + + +//-------------------------------------------------------------------------- +//-------------------------------------- SceneObject implementation +// +SceneObject::SceneObject() +{ + mContainer = 0; + mTypeMask = DefaultObjectType; + mCollisionCount = 0; + mGlobalBounds = false; + + mObjScale.set(1,1,1); + mObjToWorld.identity(); + mWorldToObj.identity(); + + mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); + mWorldBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); + mWorldSphere = SphereF(Point3F(0, 0, 0), 0); + + mRenderObjToWorld.identity(); + mRenderWorldToObj.identity(); + mRenderWorldBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); + mRenderWorldSphere = SphereF(Point3F(0, 0, 0), 0); + + mContainerSeqKey = 0; + + mBinRefHead = NULL; + + mSceneManager = NULL; + mZoneRangeStart = 0xFFFFFFFF; + + mNumCurrZones = 0; + mZoneRefHead = NULL; + + mLastState = NULL; + mLastStateKey = 0; + + mBinMinX = 0xFFFFFFFF; + mBinMaxX = 0xFFFFFFFF; + mBinMinY = 0xFFFFFFFF; + mBinMaxY = 0xFFFFFFFF; + mLightPlugin = NULL; + + mMount.object = 0; + mMount.link = 0; + mMount.list = 0; + mSelectionFlags = 0; +} + +SceneObject::~SceneObject() +{ + AssertFatal(mZoneRangeStart == 0xFFFFFFFF && mSceneManager == NULL, + "Error, SceneObject not properly removed from sceneGraph"); + AssertFatal(mZoneRefHead == NULL && mBinRefHead == NULL, + "Error, still linked in reference lists!"); + + unlink(); +} + +//---------------------------------------------------------------------------- +const char* SceneObject::scriptThis() +{ + return Con::getIntArg(getId()); +} + + +//-------------------------------------------------------------------------- +void SceneObject::buildConvex(const Box3F&, Convex*) +{ + return; +} + +bool SceneObject::buildPolyList(AbstractPolyList*, const Box3F&, const SphereF&) +{ + return false; +} + +bool SceneObject::buildRenderedPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere) +{ + // By default, call the standard buildPolyList so simple objects do + // not need to define both methods. + return buildPolyList(polyList, box, sphere); +} + +bool SceneObject::castRay(const Point3F&, const Point3F&, RayInfo*) +{ + return false; +} + +bool SceneObject::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info) +{ + // By default, all ray checking against the rendered mesh will be passed + // on to the collision mesh. This saves having to define both methods + // for simple objects. + return castRay(start, end, info); +} + +bool SceneObject::collideBox(const Point3F &start, const Point3F &end, RayInfo *info) +{ + const F32 * pStart = (const F32*)start; + const F32 * pEnd = (const F32*)end; + const F32 * pMin = (const F32*)mObjBox.minExtents; + const F32 * pMax = (const F32*)mObjBox.maxExtents; + + F32 maxStartTime = -1; + F32 minEndTime = 1; + F32 startTime; + F32 endTime; + + // used for getting normal + U32 hitIndex = 0xFFFFFFFF; + U32 side; + + // walk the axis + for(U32 i = 0; i < 3; i++) + { + // + if(pStart[i] < pEnd[i]) + { + if(pEnd[i] < pMin[i] || pStart[i] > pMax[i]) + return(false); + + F32 dist = pEnd[i] - pStart[i]; + + startTime = (pStart[i] < pMin[i]) ? (pMin[i] - pStart[i]) / dist : -1; + endTime = (pEnd[i] > pMax[i]) ? (pMax[i] - pStart[i]) / dist : 1; + side = 1; + } + else + { + if(pStart[i] < pMin[i] || pEnd[i] > pMax[i]) + return(false); + + F32 dist = pStart[i] - pEnd[i]; + startTime = (pStart[i] > pMax[i]) ? (pStart[i] - pMax[i]) / dist : -1; + endTime = (pEnd[i] < pMin[i]) ? (pStart[i] - pMin[i]) / dist : 1; + side = 0; + } + + // + if(startTime > maxStartTime) + { + maxStartTime = startTime; + hitIndex = i * 2 + side; + } + if(endTime < minEndTime) + minEndTime = endTime; + if(minEndTime < maxStartTime) + return(false); + } + + // fail if inside + if(maxStartTime < 0.f) + return(false); + + // + static Point3F boxNormals[] = { + Point3F( 1, 0, 0), + Point3F(-1, 0, 0), + Point3F( 0, 1, 0), + Point3F( 0,-1, 0), + Point3F( 0, 0, 1), + Point3F( 0, 0,-1), + }; + + // + AssertFatal(hitIndex != 0xFFFFFFFF, "SceneObject::collideBox"); + info->t = maxStartTime; + info->object = this; + mObjToWorld.mulV(boxNormals[hitIndex], &info->normal); + info->material = 0; + return(true); +} + +void SceneObject::disableCollision() +{ + mCollisionCount++; + AssertFatal(mCollisionCount < 50, "Wow, that's too much"); +} + +bool SceneObject::isDisplacable() const +{ + return false; +} + +Point3F SceneObject::getMomentum() const +{ + return Point3F(0, 0, 0); +} + +void SceneObject::setMomentum(const Point3F&) +{ +} + + +F32 SceneObject::getMass() const +{ + return 1.0; +} + + +bool SceneObject::displaceObject(const Point3F&) +{ + return false; +} + + +void SceneObject::enableCollision() +{ + if (mCollisionCount) + --mCollisionCount; +} + +bool SceneObject::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + mWorldToObj = mObjToWorld; + mWorldToObj.affineInverse(); + resetWorldBox(); + + setRenderTransform(mObjToWorld); + + smSceneObjectAdd.trigger(this); + + return true; +} + +void SceneObject::addToScene() +{ + if(isClientObject()) + { + gClientContainer.addObject(this); + gClientSceneGraph->addObjectToScene(this); + } + else + { + gServerContainer.addObject(this); + gServerSceneGraph->addObjectToScene(this); + } +} + +void SceneObject::onRemove() +{ + smSceneObjectRemove.trigger(this); + + Parent::onRemove(); +} + +void SceneObject::inspectPostApply() +{ + if(isServerObject()) { + setTransform(getTransform()); + setScale(getScale()); + } +} + +void SceneObject::removeFromScene() +{ + if (mSceneManager != NULL) + mSceneManager->removeObjectFromScene(this); + if (getContainer()) + getContainer()->removeObject(this); +} + +bool SceneObject::isRenderEnabled() const +{ + AbstractClassRep *classRep = getClassRep(); + return classRep->isRenderEnabled(); +} + +void SceneObject::setTransform(const MatrixF& mat) +{ + PROFILE_START(SceneObjectSetTransform); + mObjToWorld = mWorldToObj = mat; + mWorldToObj.affineInverse(); + + resetWorldBox(); + + if (mSceneManager != NULL && mNumCurrZones != 0) + { + mSceneManager->zoneRemove(this); + mSceneManager->zoneInsert(this); + if (getContainer()) + getContainer()->checkBins(this); + } + + setRenderTransform(mat); + PROFILE_END(); +} + +void SceneObject::setScale( const VectorF &scale ) +{ + AssertFatal( !mIsNaN( scale ), "SceneObject::setScale() - The scale is NaN!" ); + + mObjScale = scale; + setTransform(MatrixF(mObjToWorld)); + + // Make sure that any subclasses of me get a chance to react to the + // scale being changed. + onScaleChanged(); + + setMaskBits( ScaleMask ); +} + +void SceneObject::resetWorldBox() +{ + AssertFatal(mObjBox.isValidBox(), "SceneObject::resetWorldBox - Bad object box!"); + + mWorldBox = mObjBox; + mWorldBox.minExtents.convolve(mObjScale); + mWorldBox.maxExtents.convolve(mObjScale); + mObjToWorld.mul(mWorldBox); + + AssertFatal(mWorldBox.isValidBox(), "SceneObject::resetWorldBox - Bad world box!"); + + // Create mWorldSphere from mWorldBox + mWorldBox.getCenter(&mWorldSphere.center); + mWorldSphere.radius = (mWorldBox.maxExtents - mWorldSphere.center).len(); +} + +void SceneObject::resetObjectBox() +{ + AssertFatal( mWorldBox.isValidBox(), "SceneObject::resetObjectBox - Bad world box!" ); + + mObjBox = mWorldBox; + mWorldToObj.mul( mObjBox ); + + Point3F objScale( mObjScale ); + objScale.setMax( Point3F( (F32)POINT_EPSILON, (F32)POINT_EPSILON, (F32)POINT_EPSILON ) ); + mObjBox.minExtents.convolveInverse( objScale ); + mObjBox.maxExtents.convolveInverse( objScale ); + + AssertFatal( mObjBox.isValidBox(), "SceneObject::resetObjectBox - Bad object box!" ); + + // Update the mWorldSphere from mWorldBox + mWorldBox.getCenter( &mWorldSphere.center ); + mWorldSphere.radius = ( mWorldBox.maxExtents - mWorldSphere.center ).len(); +} + +void SceneObject::setRenderTransform(const MatrixF& mat) +{ + PROFILE_START(SceneObj_setRenderTransform); + mRenderObjToWorld = mRenderWorldToObj = mat; + mRenderWorldToObj.affineInverse(); + + AssertFatal(mObjBox.isValidBox(), "Bad object box!"); + resetRenderWorldBox(); + PROFILE_END(); +} + + +void SceneObject::resetRenderWorldBox() +{ + AssertFatal(mObjBox.isValidBox(), "Bad object box!"); + mRenderWorldBox = mObjBox; + mRenderWorldBox.minExtents.convolve(mObjScale); + mRenderWorldBox.maxExtents.convolve(mObjScale); + mRenderObjToWorld.mul(mRenderWorldBox); +// #if defined(__linux__) || defined(__OpenBSD__) +// if( !mRenderWorldBox.isValidBox() ) { +// // reset +// mRenderWorldBox.minExtents.set( 0.0f, 0.0f, 0.0f ); +// mRenderWorldBox.maxExtents.set( 0.0f, 0.0f, 0.0f ); +// } +// #else + AssertFatal(mRenderWorldBox.isValidBox(), "Bad world box!"); +//#endif + + // Create mRenderWorldSphere from mRenderWorldBox + mRenderWorldBox.getCenter(&mRenderWorldSphere.center); + mRenderWorldSphere.radius = (mRenderWorldBox.maxExtents - mRenderWorldSphere.center).len(); +} + + +void SceneObject::initPersistFields() +{ + addGroup("Transform"); // MM: Added group header. + addField("position", TypeMatrixPosition, Offset(mObjToWorld, SceneObject), "Object world position."); + addField("rotation", TypeMatrixRotation, Offset(mObjToWorld, SceneObject), "Object world orientation."); + addField("scale", TypePoint3F, Offset(mObjScale, SceneObject), "Object world scale."); + endGroup("Transform"); // MM: Added group footer. + + Parent::initPersistFields(); +} + +bool SceneObject::onSceneAdd(SceneGraph* pGraph) +{ + mSceneManager = pGraph; + mSceneManager->zoneInsert(this); + return true; +} + +void SceneObject::onSceneRemove() +{ + mSceneManager->zoneRemove(this); + mSceneManager = NULL; +} + +void SceneObject::onScaleChanged() +{ + // Override this function where you need to specially handle something + // when the size of your object has been changed. +} + +bool SceneObject::prepRenderImage(SceneState*, const U32, const U32, const bool) +{ + return false; +} + +bool SceneObject::scopeObject(const Point3F& /*rootPosition*/, + const F32 /*rootDistance*/, + bool* /*zoneScopeState*/) +{ + AssertFatal(false, "Error, this should never be called on a bare (non-zonemanaging) object. All zone managers must override this function"); + return false; +} + + +//-------------------------------------------------------------------------- + +//-------------------------------------------------------------------------- +// A quick note about these three functions. They should only be called +// on zoneManagers, but since we don't want to force every non-zoneManager +// to implement them, they assert out instead of being pure virtual. +// +bool SceneObject::getOverlappingZones(SceneObject*, U32*, U32* numZones) +{ + AssertISV(false, "Pure virtual (essentially) function called. Should never execute this"); + *numZones = 0; + return false; +} + +U32 SceneObject::getPointZone(const Point3F&) +{ + AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); + return 0; +} + +void SceneObject::transformModelview(const U32, const MatrixF&, MatrixF*) +{ + AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); +} + +void SceneObject::transformPosition(const U32, Point3F&) +{ + AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); +} + +bool SceneObject::computeNewFrustum(const U32, const Frustum&, const F64, const F64, + const RectI&, F64*, RectI&, const bool) +{ + AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); + return false; +} + +void SceneObject::openPortal(const U32 /*portalIndex*/, + SceneState* /*pCurrState*/, + SceneState* /*pParentState*/) +{ + AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); +} + +void SceneObject::closePortal(const U32 /*portalIndex*/, + SceneState* /*pCurrState*/, + SceneState* /*pParentState*/) +{ + AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); +} + +void SceneObject::getWSPortalPlane(const U32 /*portalIndex*/, PlaneF*) +{ + AssertISV(false, "Error, (essentially) pure virtual function called. Any object this is called on should override this function"); +} + + +//---------------------------------------------------------------------------- +//-------------------------------------- Container implementation +// +Container::Link::Link() +{ + next = prev = this; +} + +void Container::Link::unlink() +{ + next->prev = prev; + prev->next = next; + next = prev = this; +} + +void Container::Link::linkAfter(Container::Link* ptr) +{ + next = ptr->next; + next->prev = this; + prev = ptr; + prev->next = this; +} + +//---------------------------------------------------------------------------- + +Container gServerContainer; +Container gClientContainer; + +Container::Container() +{ + mEnd.next = mEnd.prev = &mStart; + mStart.next = mStart.prev = &mEnd; + + if (!sBoxPolyhedron.edgeList.size()) + { + Box3F box; + box.minExtents.set(-1,-1,-1); + box.maxExtents.set(+1,+1,+1); + MatrixF imat(1); + sBoxPolyhedron.buildBox(imat,box); + } + + mBinArray = new SceneObjectRef[csmNumBins * csmNumBins]; + for (U32 i = 0; i < csmNumBins; i++) + { + U32 base = i * csmNumBins; + for (U32 j = 0; j < csmNumBins; j++) + { + mBinArray[base + j].object = NULL; + mBinArray[base + j].nextInBin = NULL; + mBinArray[base + j].prevInBin = NULL; + mBinArray[base + j].nextInObj = NULL; + } + } + mOverflowBin.object = NULL; + mOverflowBin.nextInBin = NULL; + mOverflowBin.prevInBin = NULL; + mOverflowBin.nextInObj = NULL; + + VECTOR_SET_ASSOCIATION(mRefPoolBlocks); + VECTOR_SET_ASSOCIATION(mSearchList); + + mFreeRefPool = NULL; + addRefPoolBlock(); + + cleanupSearchVectors(); +} + +Container::~Container() +{ + delete[] mBinArray; + + for (U32 i = 0; i < mRefPoolBlocks.size(); i++) + { + SceneObjectRef* pool = mRefPoolBlocks[i]; + for (U32 j = 0; j < csmRefPoolBlockSize; j++) + { + // Depressingly, this can give weird results if its pointing at bad memory... + if(pool[j].object != NULL) + Con::warnf("Error, a %s (%x) isn't properly out of the bins!", pool[j].object->getClassName(), pool[j].object); + + // If you're getting this it means that an object created didn't + // remove itself from its container before we destroyed the + // container. Typically you get this behavior from particle + // emitters, as they try to hang around until all their particles + // die. In general it's benign, though if you get it for things + // that aren't particle emitters it can be a bad sign! + } + + delete [] pool; + } + mFreeRefPool = NULL; + + cleanupSearchVectors(); +} + +bool Container::addObject(SceneObject* obj) +{ + AssertFatal(obj->mContainer == NULL, "Adding already added object."); + obj->mContainer = this; + obj->linkAfter(&mStart); + + insertIntoBins(obj); + + // Also insert water and physical zone types into the special vector. + if ( obj->getType() & ( WaterObjectType | PhysicalZoneObjectType ) ) + mWaterAndZones.push_back(obj); + + return true; +} + +bool Container::removeObject(SceneObject* obj) +{ + AssertFatal(obj->mContainer == this, "Trying to remove from wrong container."); + removeFromBins(obj); + + // Remove water and physical zone types from the special vector. + if ( obj->getType() & ( WaterObjectType | PhysicalZoneObjectType ) ) + { + Vector::iterator iter = find( mWaterAndZones.begin(), mWaterAndZones.end(), obj ); + if ( iter != mWaterAndZones.end() ) + mWaterAndZones.erase_fast(iter); + } + + obj->mContainer = 0; + obj->unlink(); + return true; +} + +void Container::addRefPoolBlock() +{ + mRefPoolBlocks.push_back(new SceneObjectRef[csmRefPoolBlockSize]); + for (U32 i = 0; i < csmRefPoolBlockSize-1; i++) + { + mRefPoolBlocks.last()[i].object = NULL; + mRefPoolBlocks.last()[i].prevInBin = NULL; + mRefPoolBlocks.last()[i].nextInBin = NULL; + mRefPoolBlocks.last()[i].nextInObj = &(mRefPoolBlocks.last()[i+1]); + } + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].object = NULL; + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].prevInBin = NULL; + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInBin = NULL; + mRefPoolBlocks.last()[csmRefPoolBlockSize-1].nextInObj = mFreeRefPool; + + mFreeRefPool = &(mRefPoolBlocks.last()[0]); +} + + +void Container::insertIntoBins(SceneObject* obj) +{ + AssertFatal(obj != NULL, "No object?"); + AssertFatal(obj->mBinRefHead == NULL, "Error, already have a bin chain!"); + + // The first thing we do is find which bins are covered in x and y... + const Box3F* pWBox = &obj->getWorldBox(); + + U32 minX, maxX, minY, maxY; + getBinRange(pWBox->minExtents.x, pWBox->maxExtents.x, minX, maxX); + getBinRange(pWBox->minExtents.y, pWBox->maxExtents.y, minY, maxY); + + // Store the current regions for later queries + obj->mBinMinX = minX; + obj->mBinMaxX = maxX; + obj->mBinMinY = minY; + obj->mBinMaxY = maxY; + + // For huge objects, dump them into the overflow bin. Otherwise, everything + // goes into the grid... + if ((maxX - minX + 1) < csmNumBins || (maxY - minY + 1) < csmNumBins && !obj->isGlobalBounds()) + { + SceneObjectRef** pCurrInsert = &obj->mBinRefHead; + + for (U32 i = minY; i <= maxY; i++) + { + U32 insertY = i % csmNumBins; + U32 base = insertY * csmNumBins; + for (U32 j = minX; j <= maxX; j++) + { + U32 insertX = j % csmNumBins; + + SceneObjectRef* ref = allocateObjectRef(); + + ref->object = obj; + ref->nextInBin = mBinArray[base + insertX].nextInBin; + ref->prevInBin = &mBinArray[base + insertX]; + ref->nextInObj = NULL; + + if (mBinArray[base + insertX].nextInBin) + mBinArray[base + insertX].nextInBin->prevInBin = ref; + mBinArray[base + insertX].nextInBin = ref; + + *pCurrInsert = ref; + pCurrInsert = &ref->nextInObj; + } + } + } + else + { + SceneObjectRef* ref = allocateObjectRef(); + + ref->object = obj; + ref->nextInBin = mOverflowBin.nextInBin; + ref->prevInBin = &mOverflowBin; + ref->nextInObj = NULL; + + if (mOverflowBin.nextInBin) + mOverflowBin.nextInBin->prevInBin = ref; + mOverflowBin.nextInBin = ref; + + obj->mBinRefHead = ref; + } +} + +void Container::insertIntoBins(SceneObject* obj, + U32 minX, U32 maxX, + U32 minY, U32 maxY) +{ + PROFILE_START(InsertBins); + AssertFatal(obj != NULL, "No object?"); + + AssertFatal(obj->mBinRefHead == NULL, "Error, already have a bin chain!"); + // Store the current regions for later queries + obj->mBinMinX = minX; + obj->mBinMaxX = maxX; + obj->mBinMinY = minY; + obj->mBinMaxY = maxY; + + // For huge objects, dump them into the overflow bin. Otherwise, everything + // goes into the grid... + // + if ((maxX - minX + 1) < csmNumBins || (maxY - minY + 1) < csmNumBins && !obj->isGlobalBounds()) + { + SceneObjectRef** pCurrInsert = &obj->mBinRefHead; + + for (U32 i = minY; i <= maxY; i++) + { + U32 insertY = i % csmNumBins; + U32 base = insertY * csmNumBins; + for (U32 j = minX; j <= maxX; j++) + { + U32 insertX = j % csmNumBins; + + SceneObjectRef* ref = allocateObjectRef(); + + ref->object = obj; + ref->nextInBin = mBinArray[base + insertX].nextInBin; + ref->prevInBin = &mBinArray[base + insertX]; + ref->nextInObj = NULL; + + if (mBinArray[base + insertX].nextInBin) + mBinArray[base + insertX].nextInBin->prevInBin = ref; + mBinArray[base + insertX].nextInBin = ref; + + *pCurrInsert = ref; + pCurrInsert = &ref->nextInObj; + } + } + } + else + { + SceneObjectRef* ref = allocateObjectRef(); + + ref->object = obj; + ref->nextInBin = mOverflowBin.nextInBin; + ref->prevInBin = &mOverflowBin; + ref->nextInObj = NULL; + + if (mOverflowBin.nextInBin) + mOverflowBin.nextInBin->prevInBin = ref; + mOverflowBin.nextInBin = ref; + obj->mBinRefHead = ref; + } + PROFILE_END(); +} + +void Container::removeFromBins(SceneObject* obj) +{ + PROFILE_START(RemoveFromBins); + AssertFatal(obj != NULL, "No object?"); + + SceneObjectRef* chain = obj->mBinRefHead; + obj->mBinRefHead = NULL; + + while (chain) + { + SceneObjectRef* trash = chain; + chain = chain->nextInObj; + + AssertFatal(trash->prevInBin != NULL, "Error, must have a previous entry in the bin!"); + if (trash->nextInBin) + trash->nextInBin->prevInBin = trash->prevInBin; + trash->prevInBin->nextInBin = trash->nextInBin; + + freeObjectRef(trash); + } + PROFILE_END(); +} + + +void Container::checkBins(SceneObject* obj) +{ + AssertFatal(obj != NULL, "No object?"); + + PROFILE_START(CheckBins); + if (obj->mBinRefHead == NULL) + { + insertIntoBins(obj); + PROFILE_END(); + return; + } + + // Otherwise, the object is already in the bins. Let's see if it has strayed out of + // the bins that it's currently in... + const Box3F* pWBox = &obj->getWorldBox(); + + U32 minX, maxX, minY, maxY; + getBinRange(pWBox->minExtents.x, pWBox->maxExtents.x, minX, maxX); + getBinRange(pWBox->minExtents.y, pWBox->maxExtents.y, minY, maxY); + + if (obj->mBinMinX != minX || obj->mBinMaxX != maxX || + obj->mBinMinY != minY || obj->mBinMaxY != maxY) + { + // We have to rebin the object + removeFromBins(obj); + insertIntoBins(obj, minX, maxX, minY, maxY); + } + PROFILE_END(); +} + + +void Container::findObjects(const Box3F& box, U32 mask, FindCallback callback, void *key) +{ + PROFILE_SCOPE(ContainerFindObjects_Box); + + // If we're searching for just water, just physical zones, or + // just water and physical zones then use the optimized path. + if ( mask == WaterObjectType || + mask == PhysicalZoneObjectType || + mask == (WaterObjectType|PhysicalZoneObjectType) ) + { + _findWaterAndZoneObjects( box, mask, callback, key ); + return; + } + + U32 minX, maxX, minY, maxY; + getBinRange(box.minExtents.x, box.maxExtents.x, minX, maxX); + getBinRange(box.minExtents.y, box.maxExtents.y, minY, maxY); + smCurrSeqKey++; + for (U32 i = minY; i <= maxY; i++) + { + U32 insertY = i % csmNumBins; + U32 base = insertY * csmNumBins; + for (U32 j = minX; j <= maxX; j++) + { + U32 insertX = j % csmNumBins; + + SceneObjectRef* chain = mBinArray[base + insertX].nextInBin; + while (chain) + { + if (chain->object->getContainerSeqKey() != smCurrSeqKey) + { + chain->object->setContainerSeqKey(smCurrSeqKey); + + if ((chain->object->getType() & mask) != 0 && + chain->object->isCollisionEnabled()) + { + if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) + { + (*callback)(chain->object,key); + } + } + } + chain = chain->nextInBin; + } + } + } + SceneObjectRef* chain = mOverflowBin.nextInBin; + while (chain) + { + if (chain->object->getContainerSeqKey() != smCurrSeqKey) + { + chain->object->setContainerSeqKey(smCurrSeqKey); + + if ((chain->object->getType() & mask) != 0 && + chain->object->isCollisionEnabled()) + { + if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) + { + (*callback)(chain->object,key); + } + } + } + chain = chain->nextInBin; + } +} + +void Container::findObjects( const Frustum &frustum, U32 mask, FindCallback callback, void *key ) +{ + PROFILE_SCOPE(ContainerFindObjects_Frustum); + + Box3F searchBox = frustum.getBounds(); + + if ( mask == WaterObjectType || + mask == PhysicalZoneObjectType || + mask == (WaterObjectType|PhysicalZoneObjectType) ) + { + _findWaterAndZoneObjects( searchBox, mask, callback, key ); + return; + } + + U32 minX, maxX, minY, maxY; + getBinRange(searchBox.minExtents.x, searchBox.maxExtents.x, minX, maxX); + getBinRange(searchBox.minExtents.y, searchBox.maxExtents.y, minY, maxY); + smCurrSeqKey++; + + for (U32 i = minY; i <= maxY; i++) + { + U32 insertY = i % csmNumBins; + U32 base = insertY * csmNumBins; + for (U32 j = minX; j <= maxX; j++) + { + U32 insertX = j % csmNumBins; + + SceneObjectRef* chain = mBinArray[base + insertX].nextInBin; + while (chain) + { + SceneObject *object = chain->object; + + if (object->getContainerSeqKey() != smCurrSeqKey) + { + object->setContainerSeqKey(smCurrSeqKey); + + if ((object->getType() & mask) != 0 && + object->isCollisionEnabled()) + { + const Box3F &worldBox = object->getWorldBox(); + if ( object->isGlobalBounds() || worldBox.isOverlapped(searchBox) ) + { + if ( frustum.intersects( worldBox ) ) + (*callback)(chain->object,key); + } + } + } + chain = chain->nextInBin; + } + } + } + + SceneObjectRef* chain = mOverflowBin.nextInBin; + while (chain) + { + SceneObject *object = chain->object; + + if (object->getContainerSeqKey() != smCurrSeqKey) + { + object->setContainerSeqKey(smCurrSeqKey); + + if ((object->getType() & mask) != 0 && + object->isCollisionEnabled()) + { + const Box3F &worldBox = object->getWorldBox(); + + if ( object->isGlobalBounds() || worldBox.isOverlapped(searchBox) ) + { + if ( frustum.intersects( worldBox ) ) + (*callback)(object,key); + } + } + } + chain = chain->nextInBin; + } +} + +void Container::polyhedronFindObjects(const Polyhedron& polyhedron, U32 mask, FindCallback callback, void *key) +{ + PROFILE_SCOPE(ContainerFindObjects_polyhedron); + + U32 i; + Box3F box; + box.minExtents.set(1e9, 1e9, 1e9); + box.maxExtents.set(-1e9, -1e9, -1e9); + for (i = 0; i < polyhedron.pointList.size(); i++) + { + box.minExtents.setMin(polyhedron.pointList[i]); + box.maxExtents.setMax(polyhedron.pointList[i]); + } + + if ( mask == WaterObjectType || + mask == PhysicalZoneObjectType || + mask == (WaterObjectType|PhysicalZoneObjectType) ) + { + _findWaterAndZoneObjects( box, mask, callback, key ); + return; + } + + U32 minX, maxX, minY, maxY; + getBinRange(box.minExtents.x, box.maxExtents.x, minX, maxX); + getBinRange(box.minExtents.y, box.maxExtents.y, minY, maxY); + smCurrSeqKey++; + for (i = minY; i <= maxY; i++) + { + U32 insertY = i % csmNumBins; + U32 base = insertY * csmNumBins; + for (U32 j = minX; j <= maxX; j++) + { + U32 insertX = j % csmNumBins; + + SceneObjectRef* chain = mBinArray[base + insertX].nextInBin; + while (chain) + { + if (chain->object->getContainerSeqKey() != smCurrSeqKey) + { + chain->object->setContainerSeqKey(smCurrSeqKey); + + if ((chain->object->getType() & mask) != 0 && + chain->object->isCollisionEnabled()) + { + if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) + { + (*callback)(chain->object,key); + } + } + } + chain = chain->nextInBin; + } + } + } + SceneObjectRef* chain = mOverflowBin.nextInBin; + while (chain) + { + if (chain->object->getContainerSeqKey() != smCurrSeqKey) + { + chain->object->setContainerSeqKey(smCurrSeqKey); + + if ((chain->object->getType() & mask) != 0 && + chain->object->isCollisionEnabled()) + { + if (chain->object->getWorldBox().isOverlapped(box) || chain->object->isGlobalBounds()) + { + (*callback)(chain->object,key); + } + } + } + chain = chain->nextInBin; + } +} + +void Container::findObjectList( const Box3F& searchBox, U32 mask, Vector *outFound ) +{ + PROFILE_SCOPE( Container_FindObjectList_Box ); + + // TODO: Optimize for water and zones? + + U32 minX, maxX, minY, maxY; + getBinRange(searchBox.minExtents.x, searchBox.maxExtents.x, minX, maxX); + getBinRange(searchBox.minExtents.y, searchBox.maxExtents.y, minY, maxY); + smCurrSeqKey++; + + for (U32 i = minY; i <= maxY; i++) + { + U32 insertY = i % csmNumBins; + U32 base = insertY * csmNumBins; + for (U32 j = minX; j <= maxX; j++) + { + U32 insertX = j % csmNumBins; + + SceneObjectRef* chain = mBinArray[base + insertX].nextInBin; + while (chain) + { + SceneObject *object = chain->object; + + if (object->getContainerSeqKey() != smCurrSeqKey) + { + object->setContainerSeqKey(smCurrSeqKey); + + if ((object->getType() & mask) != 0 && + object->isCollisionEnabled()) + { + const Box3F &worldBox = object->getWorldBox(); + if ( object->isGlobalBounds() || worldBox.isOverlapped( searchBox ) ) + { + outFound->push_back( object ); + } + } + } + chain = chain->nextInBin; + } + } + } + + SceneObjectRef* chain = mOverflowBin.nextInBin; + while (chain) + { + SceneObject *object = chain->object; + + if (object->getContainerSeqKey() != smCurrSeqKey) + { + object->setContainerSeqKey(smCurrSeqKey); + + if ((object->getType() & mask) != 0 && + object->isCollisionEnabled()) + { + const Box3F &worldBox = object->getWorldBox(); + + if ( object->isGlobalBounds() || worldBox.isOverlapped( searchBox ) ) + { + outFound->push_back( object ); + } + } + } + chain = chain->nextInBin; + } +} + +void Container::findObjectList( const Frustum &frustum, U32 mask, Vector *outFound ) +{ + PROFILE_SCOPE( Container_FindObjectList_Frustum ); + + // Do a box find first. + findObjectList( frustum.getBounds(), mask, outFound ); + + // Now do the frustum testing. + for ( U32 i=0; i < outFound->size(); ) + { + const Box3F &worldBox = (*outFound)[i]->getWorldBox(); + if ( !frustum.intersects( worldBox ) ) + outFound->erase_fast( i ); + else + i++; + } +} + +void Container::_findWaterAndZoneObjects( U32 mask, FindCallback callback, void *key ) +{ + PROFILE_SCOPE( Container_FindWaterAndZoneObjects ); + + Vector::iterator iter = mWaterAndZones.begin(); + for ( ; iter != mWaterAndZones.end(); iter++ ) + { + if ( (*iter)->getType() & mask ) + callback( *iter, key ); + } +} + +void Container::_findWaterAndZoneObjects( const Box3F &box, U32 mask, FindCallback callback, void *key ) +{ + PROFILE_SCOPE( Container_FindWaterAndZoneObjects_Box ); + + Vector::iterator iter = mWaterAndZones.begin(); + + for ( ; iter != mWaterAndZones.end(); iter++ ) + { + SceneObject *pObj = *iter; + + if ( pObj->getType() & mask && + ( pObj->isGlobalBounds() || pObj->getWorldBox().isOverlapped(box) ) ) + { + callback( pObj, key ); + } + } +} + +//---------------------------------------------------------------------------- + +bool Container::castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info) +{ + PROFILE_START(ContainerCastRay); + bool result = castRayBase(CollisionGeometry, start, end, mask, info); + PROFILE_END(); + return result; +} + +bool Container::castRayRendered(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info) +{ + PROFILE_START(ContainerCastRayRendered); + bool result = castRayBase(RenderedGeometry, start, end, mask, info); + PROFILE_END(); + return result; +} + +//---------------------------------------------------------------------------- +// DMMNOTE: There are still some optimizations to be done here. In particular: +// - After checking the overflow bin, we can potentially shorten the line +// that we rasterize against the grid if there is a collision with say, +// the terrain. +// - The optimal grid size isn't necessarily what we have set here. possibly +// a resolution of 16 meters would give better results +// - The line rasterizer is pretty lame. Unfortunately we can't use a +// simple bres. here, since we need to check every grid element that the line +// passes through, which bres does _not_ do for us. Possibly there's a +// rasterizer for anti-aliased lines that will serve better than what +// we have below. +// +bool Container::castRayBase(U32 type, const Point3F &start, const Point3F &end, U32 mask, RayInfo * info) +{ + F32 currentT = 2.0; + smCurrSeqKey++; + + SceneObjectRef* chain = mOverflowBin.nextInBin; + while (chain) + { + SceneObject* ptr = chain->object; + if (ptr->getContainerSeqKey() != smCurrSeqKey) + { + ptr->setContainerSeqKey(smCurrSeqKey); + + // In the overflow bin, the world box is always going to intersect the line, + // so we can omit that test... + if ((ptr->getType() & mask) != 0 && + ptr->isCollisionEnabled() == true) + { + Point3F xformedStart, xformedEnd; + ptr->mWorldToObj.mulP(start, &xformedStart); + ptr->mWorldToObj.mulP(end, &xformedEnd); + xformedStart.convolveInverse(ptr->mObjScale); + xformedEnd.convolveInverse(ptr->mObjScale); + + RayInfo ri; + bool result = false; + if (type == CollisionGeometry) + result = ptr->castRay(xformedStart, xformedEnd, &ri); + else if (type == RenderedGeometry) + result = ptr->castRayRendered(xformedStart, xformedEnd, &ri); + if (result) + { + if(ri.t < currentT) + { + *info = ri; + info->point.interpolate(start, end, info->t); + currentT = ri.t; + } + } + } + } + chain = chain->nextInBin; + } + + // These are just for rasterizing the line against the grid. We want the x coord + // of the start to be <= the x coord of the end + Point3F normalStart, normalEnd; + if (start.x <= end.x) + { + normalStart = start; + normalEnd = end; + } + else + { + normalStart = end; + normalEnd = start; + } + + // Ok, let's scan the grids. The simplest way to do this will be to scan across in + // x, finding the y range for each affected bin... + U32 minX, maxX; + U32 minY, maxY; +//if (normalStart.x == normalEnd.x) +// Con::printf("X start = %g, end = %g", normalStart.x, normalEnd.x); + + getBinRange(normalStart.x, normalEnd.x, minX, maxX); + getBinRange(getMin(normalStart.y, normalEnd.y), + getMax(normalStart.y, normalEnd.y), minY, maxY); + +//if (normalStart.x == normalEnd.x && minX != maxX) +// Con::printf("X min = %d, max = %d", minX, maxX); +//if (normalStart.y == normalEnd.y && minY != maxY) +// Con::printf("Y min = %d, max = %d", minY, maxY); + + // We'll optimize the case that the line is contained in one bin row or column, which + // will be quite a few lines. No sense doing more work than we have to... + // + if ((mFabs(normalStart.x - normalEnd.x) < csmTotalBinSize && minX == maxX) || + (mFabs(normalStart.y - normalEnd.y) < csmTotalBinSize && minY == maxY)) + { + U32 count; + U32 incX, incY; + if (minX == maxX) + { + count = maxY - minY + 1; + incX = 0; + incY = 1; + } + else + { + count = maxX - minX + 1; + incX = 1; + incY = 0; + } + + U32 x = minX; + U32 y = minY; + for (U32 i = 0; i < count; i++) + { + U32 checkX = x % csmNumBins; + U32 checkY = y % csmNumBins; + + SceneObjectRef* chain = mBinArray[(checkY * csmNumBins) + checkX].nextInBin; + while (chain) + { + SceneObject* ptr = chain->object; + if (ptr->getContainerSeqKey() != smCurrSeqKey) + { + ptr->setContainerSeqKey(smCurrSeqKey); + + if ((ptr->getType() & mask) != 0 && + ptr->isCollisionEnabled() == true) + { + if (ptr->getWorldBox().collideLine(start, end) || chain->object->isGlobalBounds()) + { + Point3F xformedStart, xformedEnd; + ptr->mWorldToObj.mulP(start, &xformedStart); + ptr->mWorldToObj.mulP(end, &xformedEnd); + xformedStart.convolveInverse(ptr->mObjScale); + xformedEnd.convolveInverse(ptr->mObjScale); + + RayInfo ri; + bool result = false; + if (type == CollisionGeometry) + result = ptr->castRay(xformedStart, xformedEnd, &ri); + else if (type == RenderedGeometry) + result = ptr->castRayRendered(xformedStart, xformedEnd, &ri); + if (result) + { + if(ri.t < currentT) + { + *info = ri; + info->point.interpolate(start, end, info->t); + currentT = ri.t; + info->distance = (start - end).len(); + } + } + } + } + } + chain = chain->nextInBin; + } + + x += incX; + y += incY; + } + } + else + { + // Oh well, let's earn our keep. We know that after the above conditional, we're + // going to cross at least one boundary, so that simplifies our job... + + F32 currStartX = normalStart.x; + + AssertFatal(currStartX != normalEnd.x, "This is going to cause problems in Container::castRay"); + while (currStartX != normalEnd.x) + { + F32 currEndX = getMin(currStartX + csmTotalBinSize, normalEnd.x); + + F32 currStartT = (currStartX - normalStart.x) / (normalEnd.x - normalStart.x); + F32 currEndT = (currEndX - normalStart.x) / (normalEnd.x - normalStart.x); + + F32 y1 = normalStart.y + (normalEnd.y - normalStart.y) * currStartT; + F32 y2 = normalStart.y + (normalEnd.y - normalStart.y) * currEndT; + + U32 subMinX, subMaxX; + getBinRange(currStartX, currEndX, subMinX, subMaxX); + + F32 subStartX = currStartX; + F32 subEndX = currStartX; + + if (currStartX < 0.0f) + subEndX -= mFmod(subEndX, csmBinSize); + else + subEndX += (csmBinSize - mFmod(subEndX, csmBinSize)); + + for (U32 currXBin = subMinX; currXBin <= subMaxX; currXBin++) + { + U32 checkX = currXBin % csmNumBins; + + F32 subStartT = (subStartX - currStartX) / (currEndX - currStartX); + F32 subEndT = getMin(F32((subEndX - currStartX) / (currEndX - currStartX)), 1.f); + + F32 subY1 = y1 + (y2 - y1) * subStartT; + F32 subY2 = y1 + (y2 - y1) * subEndT; + + U32 newMinY, newMaxY; + getBinRange(getMin(subY1, subY2), getMax(subY1, subY2), newMinY, newMaxY); + + for (U32 i = newMinY; i <= newMaxY; i++) + { + U32 checkY = i % csmNumBins; + + SceneObjectRef* chain = mBinArray[(checkY * csmNumBins) + checkX].nextInBin; + while (chain) + { + SceneObject* ptr = chain->object; + if (ptr->getContainerSeqKey() != smCurrSeqKey) + { + ptr->setContainerSeqKey(smCurrSeqKey); + + if ((ptr->getType() & mask) != 0 && + ptr->isCollisionEnabled() == true) + { + if (ptr->getWorldBox().collideLine(start, end)) + { + Point3F xformedStart, xformedEnd; + ptr->mWorldToObj.mulP(start, &xformedStart); + ptr->mWorldToObj.mulP(end, &xformedEnd); + xformedStart.convolveInverse(ptr->mObjScale); + xformedEnd.convolveInverse(ptr->mObjScale); + + RayInfo ri; + bool result = false; + if (type == CollisionGeometry) + result = ptr->castRay(xformedStart, xformedEnd, &ri); + else if (type == RenderedGeometry) + result = ptr->castRayRendered(xformedStart, xformedEnd, &ri); + if (result) + { + if(ri.t < currentT) + { + *info = ri; + info->point.interpolate(start, end, info->t); + currentT = ri.t; + info->distance = (start - end).len(); + } + } + } + } + } + chain = chain->nextInBin; + } + } + + subStartX = subEndX; + subEndX = getMin(subEndX + csmBinSize, currEndX); + } + + currStartX = currEndX; + } + } + + // Bump the normal into worldspace if appropriate. + if(currentT != 2) + { + PlaneF fakePlane; + fakePlane.x = info->normal.x; + fakePlane.y = info->normal.y; + fakePlane.z = info->normal.z; + fakePlane.d = 0; + + PlaneF result; + mTransformPlane(info->object->getTransform(), info->object->getScale(), fakePlane, &result); + info->normal = result; + + return true; + } + else + { + // Do nothing and exit... + return false; + } +} + +// collide with the objects projected object box +bool Container::collideBox(const Point3F &start, const Point3F &end, U32 mask, RayInfo * info) +{ + F32 currentT = 2; + for (Link* itr = mStart.next; itr != &mEnd; itr = itr->next) + { + SceneObject* ptr = static_cast(itr); + if (ptr->getType() & mask && !ptr->mCollisionCount) + { + Point3F xformedStart, xformedEnd; + ptr->mWorldToObj.mulP(start, &xformedStart); + ptr->mWorldToObj.mulP(end, &xformedEnd); + xformedStart.convolveInverse(ptr->mObjScale); + xformedEnd.convolveInverse(ptr->mObjScale); + + RayInfo ri; + if(ptr->collideBox(xformedStart, xformedEnd, &ri)) + { + if(ri.t < currentT) + { + *info = ri; + info->point.interpolate(start, end, info->t); + currentT = ri.t; + } + } + } + } + return currentT != 2; +} + +//---------------------------------------------------------------------------- + +static void buildCallback(SceneObject* object,void *key) +{ + Container::CallbackInfo* info = reinterpret_cast(key); + object->buildPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + +static void buildRenderedCallback(SceneObject* object,void *key) +{ + Container::CallbackInfo* info = reinterpret_cast(key); + object->buildRenderedPolyList(info->polyList,info->boundingBox,info->boundingSphere); +} + + +//---------------------------------------------------------------------------- + +bool Container::buildPolyList(const Box3F& box, U32 mask, AbstractPolyList* polyList) +{ + CallbackInfo info; + info.boundingBox = box; + info.polyList = polyList; + + // Build bounding sphere + info.boundingSphere.center = (info.boundingBox.minExtents + info.boundingBox.maxExtents) * 0.5; + VectorF bv = box.maxExtents - info.boundingSphere.center; + info.boundingSphere.radius = bv.len(); + + sPolyList = polyList; + findObjects(box,mask,buildCallback,&info); + return !polyList->isEmpty(); +} + +//---------------------------------------------------------------------------- + +bool Container::buildRenderedPolyList(const Box3F& box, U32 mask, AbstractPolyList* polyList) +{ + CallbackInfo info; + info.boundingBox = box; + info.polyList = polyList; + + // Build bounding sphere + info.boundingSphere.center = (info.boundingBox.minExtents + info.boundingBox.maxExtents) * 0.5; + VectorF bv = box.maxExtents - info.boundingSphere.center; + info.boundingSphere.radius = bv.len(); + + sPolyList = polyList; + findObjects(box,mask,buildRenderedCallback,&info); + return !polyList->isEmpty(); +} + + +//---------------------------------------------------------------------------- + +bool Container::buildCollisionList(const Box3F& box, + const Point3F& start, + const Point3F& end, + const VectorF& velocity, + U32 mask, + CollisionList* collisionList, + FindCallback callback, + void * key, + const Box3F* queryExpansion) +{ + VectorF vector = end - start; + if (mFabs(vector.x) + mFabs(vector.y) + mFabs(vector.z) == 0) + return false; + CallbackInfo info; + info.key = key; + + // Polylist bounding box & sphere + info.boundingBox.minExtents = info.boundingBox.maxExtents = start; + info.boundingBox.minExtents.setMin(end); + info.boundingBox.maxExtents.setMax(end); + info.boundingBox.minExtents += box.minExtents; + info.boundingBox.maxExtents += box.maxExtents; + info.boundingSphere.center = (info.boundingBox.minExtents + info.boundingBox.maxExtents) * 0.5; + VectorF bv = info.boundingBox.maxExtents - info.boundingSphere.center; + info.boundingSphere.radius = bv.len(); + + // Box polyhedron edges & planes normals are always the same, + // just need to fill in the vertices and plane.d + Point3F* point = &sBoxPolyhedron.pointList[0]; + point[0].x = point[1].x = point[4].x = point[5].x = box.minExtents.x + start.x; + point[0].y = point[3].y = point[4].y = point[7].y = box.minExtents.y + start.y; + point[2].x = point[3].x = point[6].x = point[7].x = box.maxExtents.x + start.x; + point[1].y = point[2].y = point[5].y = point[6].y = box.maxExtents.y + start.y; + point[0].z = point[1].z = point[2].z = point[3].z = box.minExtents.z + start.z; + point[4].z = point[5].z = point[6].z = point[7].z = box.maxExtents.z + start.z; + + PlaneF* plane = &sBoxPolyhedron.planeList[0]; + plane[0].d = point[0].x; + plane[3].d = point[0].y; + plane[4].d = point[0].z; + plane[1].d = -point[6].y; + plane[2].d = -point[6].x; + plane[5].d = -point[6].z; + + // Extruded + sExtrudedPolyList.extrude(sBoxPolyhedron,vector); + sExtrudedPolyList.setVelocity(velocity); + sExtrudedPolyList.setCollisionList(collisionList); + if (velocity.isZero()) + { + sExtrudedPolyList.clearInterestNormal(); + } else + { + Point3F normVec = velocity; + normVec.normalize(); + sExtrudedPolyList.setInterestNormal(normVec); + } + info.polyList = &sExtrudedPolyList; + + // Optional expansion of the query box + Box3F queryBox = info.boundingBox; + if (queryExpansion) + { + queryBox.minExtents += queryExpansion->minExtents; + queryBox.maxExtents += queryExpansion->maxExtents; + } + + // Query main database + findObjects(queryBox,mask,callback? callback: buildCallback,&info); + sExtrudedPolyList.adjustCollisionTime(); + return collisionList->getCount() != 0; +} + + +//---------------------------------------------------------------------------- + +bool Container::buildCollisionList(const Polyhedron& polyhedron, + const Point3F& start, const Point3F& end, + const VectorF& velocity, + U32 mask, CollisionList* collisionList, + FindCallback callback, void *key) +{ + VectorF vector = end - start; + if (mFabs(vector.x) + mFabs(vector.y) + mFabs(vector.z) == 0) + return false; + + CallbackInfo info; + info.key = key; + + // Polylist bounding box & sphere + Point3F minPt(1e10, 1e10, 1e10); + Point3F maxPt(-1e10, -1e10, -1e10); + for (U32 i = 0; i < polyhedron.pointList.size(); i++) + { + minPt.setMin(polyhedron.pointList[i]); + maxPt.setMax(polyhedron.pointList[i]); + } + + info.boundingBox.minExtents = info.boundingBox.maxExtents = Point3F(0, 0, 0); + info.boundingBox.minExtents.setMin(vector); + info.boundingBox.maxExtents.setMax(vector); + info.boundingBox.minExtents += minPt; + info.boundingBox.maxExtents += maxPt; + info.boundingSphere.center = (info.boundingBox.minExtents + info.boundingBox.maxExtents) * 0.5; + VectorF bv = info.boundingBox.maxExtents - info.boundingSphere.center; + info.boundingSphere.radius = bv.len(); + + // Extruded + sExtrudedPolyList.extrude(polyhedron, vector); + sExtrudedPolyList.setVelocity(velocity); + if (velocity.isZero()) + { + sExtrudedPolyList.clearInterestNormal(); + } + else + { + Point3F normVec = velocity; + normVec.normalize(); + sExtrudedPolyList.setInterestNormal(normVec); + } + sExtrudedPolyList.setCollisionList(collisionList); + info.polyList = &sExtrudedPolyList; + + Box3F queryBox = info.boundingBox; + + // Query main database + findObjects(queryBox, mask, callback ? callback : buildCallback, &info); + sExtrudedPolyList.adjustCollisionTime(); + return collisionList->getCount() != 0; +} + + +void Container::cleanupSearchVectors() +{ + for (U32 i = 0; i < mSearchList.size(); i++) + delete mSearchList[i]; + mSearchList.clear(); + mCurrSearchPos = -1; +} + + +static Point3F sgSortReferencePoint; +int QSORT_CALLBACK cmpSearchPointers(const void* inP1, const void* inP2) +{ + SimObjectPtr** p1 = (SimObjectPtr**)inP1; + SimObjectPtr** p2 = (SimObjectPtr**)inP2; + + Point3F temp; + F32 d1, d2; + + if (bool(**p1)) + { + (**p1)->getWorldBox().getCenter(&temp); + d1 = (temp - sgSortReferencePoint).len(); + } + else + { + d1 = 0; + } + if (bool(**p2)) + { + (**p2)->getWorldBox().getCenter(&temp); + d2 = (temp - sgSortReferencePoint).len(); + } + else + { + d2 = 0; + } + + if (d1 > d2) + return 1; + else if (d1 < d2) + return -1; + else + return 0; +} + +void Container::initRadiusSearch(const Point3F& searchPoint, + const F32 searchRadius, + const U32 searchMask) +{ + AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); + cleanupSearchVectors(); + + mSearchReferencePoint = searchPoint; + + Box3F queryBox(searchPoint, searchPoint); + queryBox.minExtents -= Point3F(searchRadius, searchRadius, searchRadius); + queryBox.maxExtents += Point3F(searchRadius, searchRadius, searchRadius); + + SimpleQueryList queryList; + findObjects(queryBox, searchMask, SimpleQueryList::insertionCallback, &queryList); + + F32 radiusSquared = searchRadius * searchRadius; + + const F32* pPoint = &searchPoint.x; + for (U32 i = 0; i < queryList.mList.size(); i++) + { + const F32* bMins; + const F32* bMaxs; + bMins = &queryList.mList[i]->getWorldBox().minExtents.x; + bMaxs = &queryList.mList[i]->getWorldBox().maxExtents.x; + F32 sum = 0; + for (U32 j = 0; j < 3; j++) + { + if (pPoint[j] < bMins[j]) + sum += (pPoint[j] - bMins[j])*(pPoint[j] - bMins[j]); + else if (pPoint[j] > bMaxs[j]) + sum += (pPoint[j] - bMaxs[j])*(pPoint[j] - bMaxs[j]); + } + if (sum < radiusSquared || queryList.mList[i]->isGlobalBounds()) + { + mSearchList.push_back(new SimObjectPtr); + *(mSearchList.last()) = queryList.mList[i]; + } + } + if (mSearchList.size() != 0) + { + sgSortReferencePoint = mSearchReferencePoint; + dQsort(mSearchList.address(), mSearchList.size(), + sizeof(SimObjectPtr*), cmpSearchPointers); + } +} + +void Container::initTypeSearch(const U32 searchMask) +{ + AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); + cleanupSearchVectors(); + + SimpleQueryList queryList; + findObjects(searchMask, SimpleQueryList::insertionCallback, &queryList); + + for (U32 i = 0; i < queryList.mList.size(); i++) + { + mSearchList.push_back(new SimObjectPtr); + *(mSearchList.last()) = queryList.mList[i]; + } + if (mSearchList.size() != 0) + { + sgSortReferencePoint = mSearchReferencePoint; + dQsort(mSearchList.address(), mSearchList.size(), + sizeof(SimObjectPtr*), cmpSearchPointers); + } +} + +U32 Container::containerSearchNext() +{ + AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); + + if (mCurrSearchPos >= mSearchList.size()) + return 0; + + mCurrSearchPos++; + while (mCurrSearchPos < mSearchList.size() && bool(*mSearchList[mCurrSearchPos]) == false) + mCurrSearchPos++; + + if (mCurrSearchPos == mSearchList.size()) + return 0; + + return (*mSearchList[mCurrSearchPos])->getId(); +} + + +F32 Container::containerSearchCurrDist() +{ + AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); + AssertFatal(mCurrSearchPos != -1, "Error, must call containerSearchNext before containerSearchCurrDist"); + + if (mCurrSearchPos == -1 || mCurrSearchPos >= mSearchList.size() || + bool(*mSearchList[mCurrSearchPos]) == false) + return 0.0; + + Point3F pos; + (*mSearchList[mCurrSearchPos])->getWorldBox().getCenter(&pos); + return (pos - mSearchReferencePoint).len(); +} + +F32 Container::containerSearchCurrRadiusDist() +{ + AssertFatal(this == &gServerContainer, "Abort. Searches only allowed on server container"); + AssertFatal(mCurrSearchPos != -1, "Error, must call containerSearchNext before containerSearchCurrDist"); + + if (mCurrSearchPos == -1 || mCurrSearchPos >= mSearchList.size() || + bool(*mSearchList[mCurrSearchPos]) == false) + return 0.0; + + Point3F pos; + (*mSearchList[mCurrSearchPos])->getWorldBox().getCenter(&pos); + + F32 dist = (pos - mSearchReferencePoint).len(); + + F32 min = (*mSearchList[mCurrSearchPos])->getWorldBox().len_x(); + if ((*mSearchList[mCurrSearchPos])->getWorldBox().len_y() < min) + min = (*mSearchList[mCurrSearchPos])->getWorldBox().len_y(); + if ((*mSearchList[mCurrSearchPos])->getWorldBox().len_z() < min) + min = (*mSearchList[mCurrSearchPos])->getWorldBox().len_z(); + + dist -= min; + if (dist < 0) + dist = 0; + + return dist; +} + +//---------------------------------------------------------------------------- +void SimpleQueryList::insertionCallback(SceneObject* obj, void *key) +{ + SimpleQueryList* pList = (SimpleQueryList*)key; + pList->insertObject(obj); +} + +Point3F SceneObject::getPosition() const +{ + Point3F pos; + mObjToWorld.getColumn(3, &pos); + return pos; +} + +Point3F SceneObject::getRenderPosition() const +{ + Point3F pos; + mRenderObjToWorld.getColumn(3, &pos); + return pos; +} + +void SceneObject::setPosition(const Point3F &pos) +{ + AssertFatal( !mIsNaN( pos ), "SceneObject::setPosition() - The position is NaN!" ); + + MatrixF xform = mObjToWorld; + xform.setColumn(3, pos); + setTransform(xform); +} + +//-------------------------------------------------------------------------- + +Point3F SceneObject::getVelocity() const +{ + return Point3F(0, 0, 0); +} + +void SceneObject::setVelocity(const Point3F &) +{ + // derived objects should track velocity if they want... +} + +void SceneObject::applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude ) +{ +} + +F32 SceneObject::distanceTo(const Point3F &pnt) const +{ + return mWorldBox.getDistanceToPoint( pnt ); +} + +S32 SceneObject::getMountedObjectCount() +{ + S32 count = 0; + for (SceneObject* itr = mMount.list; itr; itr = itr->mMount.link) + count++; + return count; +} + +SceneObject* SceneObject::getMountedObject(S32 idx) +{ + if (idx >= 0) { + S32 count = 0; + for (SceneObject* itr = mMount.list; itr; itr = itr->mMount.link) + if (count++ == idx) + return itr; + } + return 0; +} + +S32 SceneObject::getMountedObjectNode(S32 idx) +{ + if (idx >= 0) { + S32 count = 0; + for (SceneObject* itr = mMount.list; itr; itr = itr->mMount.link) + if (count++ == idx) + return itr->mMount.node; + } + return -1; +} + +SceneObject* SceneObject::getMountNodeObject(S32 node) +{ + for (SceneObject* itr = mMount.list; itr; itr = itr->mMount.link) + if (itr->mMount.node == node) + return itr; + return 0; +} + +ConsoleMethod( SceneObject, mountObject, bool, 4, 4, "( SceneObject object, int slot )" + "Mount ourselves on an object in the specified slot.") +{ + SceneObject *target; + if (Sim::findObject(argv[2],target)) { + S32 node = -1; + dSscanf(argv[3],"%d",&node); + object->mountObject(target,node); + return true; + } + return false; +} + +ConsoleMethod( SceneObject, unmountObject, bool, 3, 3, "(SceneObject obj)" + "Unmount an object from ourselves.") +{ + SceneObject *target; + if (Sim::findObject(argv[2],target)) { + object->unmountObject(target); + return true; + } + return false; +} + +ConsoleMethod( SceneObject, unmount, void, 2, 2, "Unmount from the currently mounted object if any.") +{ + object->unmount(); +} + +ConsoleMethod( SceneObject, isMounted, bool, 2, 2, "Are we mounted?") +{ + return object->isMounted(); +} + +ConsoleMethod( SceneObject, getObjectMount, S32, 2, 2, "Returns the SceneObject we're mounted on.") +{ + return object->isMounted()? object->getObjectMount()->getId(): 0; +} + +ConsoleMethod( SceneObject, getMountedObjectCount, S32, 2, 2, "") +{ + return object->getMountedObjectCount(); +} + +ConsoleMethod( SceneObject, getMountedObject, S32, 3, 3, "(int slot)") +{ + SceneObject* mobj = object->getMountedObject(dAtoi(argv[2])); + return mobj? mobj->getId(): 0; +} + +ConsoleMethod( SceneObject, getMountedObjectNode, S32, 3, 3, "(int node)") +{ + return object->getMountedObjectNode(dAtoi(argv[2])); +} + +ConsoleMethod( SceneObject, getMountNodeObject, S32, 3, 3, "(int node)") +{ + SceneObject* mobj = object->getMountNodeObject(dAtoi(argv[2])); + return mobj? mobj->getId(): 0; +} \ No newline at end of file diff --git a/sceneGraph/sceneObject.h b/sceneGraph/sceneObject.h new file mode 100644 index 0000000..54f515a --- /dev/null +++ b/sceneGraph/sceneObject.h @@ -0,0 +1,990 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENEOBJECT_H_ +#define _SCENEOBJECT_H_ + +#ifndef _NETOBJECT_H_ +#include "sim/netObject.h" +#endif +#ifndef _COLLISION_H_ +#include "collision/collision.h" +#endif +#ifndef _POLYHEDRON_H_ +#include "collision/polyhedron.h" +#endif +#ifndef _ABSTRACTPOLYLIST_H_ +#include "collision/abstractPolyList.h" +#endif +#ifndef _OBJECTTYPES_H_ +#include "T3D/objectTypes.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _BASEMATINSTANCE_H_ +#include "materials/baseMatInstance.h" +#endif +#ifndef _LIGHTRECEIVER_H_ +#include "lighting/lightReceiver.h" +#endif + + +//-------------------------------------- Forward declarations... +class SceneObject; +class SceneGraph; +class SceneState; +class Box3F; +class Point3F; +class Convex; +class LightInfo; +class Frustum; +struct ObjectRenderInst; + +//-------------------------------------------------------------------------- + +// CodeReview - old note which was posing as documentation! +// There are two indiscretions here. First is the name, which refers rather +// blatantly to the container bin system. A hygiene issue. Next is the +// user defined U32, which is added solely for the zoning system. This should +// properly be split up into two structures, for the disparate purposes, especially +// since it's not nice to force the container bin to use 20 bytes structures when +// it could get away with a 16 byte version. + +/// Reference to a scene object. +class SceneObjectRef +{ + public: + SceneObject* object; + SceneObjectRef* nextInBin; + SceneObjectRef* prevInBin; + SceneObjectRef* nextInObj; + + U32 zone; +}; + +//---------------------------------------------------------------------------- +class Container +{ + enum CastRayType + { + CollisionGeometry, + RenderedGeometry, + }; + +public: + struct Link + { + Link* next; + Link* prev; + Link(); + void unlink(); + void linkAfter(Link* ptr); + }; + + struct CallbackInfo + { + AbstractPolyList* polyList; + Box3F boundingBox; + SphereF boundingSphere; + void *key; + }; + + static const U32 csmNumBins; + static const F32 csmBinSize; + static const F32 csmTotalBinSize; + static const U32 csmRefPoolBlockSize; + static U32 smCurrSeqKey; + +private: + Link mStart,mEnd; + + SceneObjectRef* mFreeRefPool; + Vector mRefPoolBlocks; + + SceneObjectRef* mBinArray; + SceneObjectRef mOverflowBin; + + /// A vector that contains just the water and physical zone + /// object types which is used to optimize searches. + Vector mWaterAndZones; + +public: + Container(); + ~Container(); + + /// @name Basic database operations + /// @{ + + /// + typedef void (*FindCallback)(SceneObject*,void *key); + void findObjects(U32 mask, FindCallback, void *key = NULL); + void findObjects(const Box3F& box, U32 mask, FindCallback, void *key = NULL); + void findObjects(const Frustum& frustum, U32 mask, FindCallback, void *key = NULL); + void polyhedronFindObjects(const Polyhedron& polyhedron, U32 mask,FindCallback, void *key = NULL); + + void findObjectList( U32 mask, Vector *outFound ); + + void findObjectList( const Box3F& box, U32 mask, Vector *outFound ); + + void findObjectList( const Frustum &frustum, U32 mask, Vector *outFound ); + + /// @} + + /// @name Line intersection + /// @{ + + /// Test against collision geometry -- fast. + bool castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info); + + /// Test against rendered geometry -- slow. + bool castRayRendered(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info); + + /// Base cast ray code + bool castRayBase(U32 type, const Point3F &start, const Point3F &end, U32 mask, RayInfo* info); + + bool collideBox(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info); + /// @} + + /// @name Poly list + /// @{ + + bool buildPolyList(const Box3F& box, U32 mask, AbstractPolyList*); + bool buildCollisionList(const Box3F& box, const Point3F& start, const Point3F& end, const VectorF& velocity, + U32 mask,CollisionList* collisionList,FindCallback = 0,void *key = NULL,const Box3F *queryExpansion = 0); + bool buildCollisionList(const Polyhedron& polyhedron, + const Point3F& start, const Point3F& end, + const VectorF& velocity, + U32 mask, CollisionList* collisionList, + FindCallback callback = 0, void *key = NULL); + + /// Test against rendered geometry -- slow. + bool buildRenderedPolyList(const Box3F& box, U32 mask, AbstractPolyList*); + + /// @} + + /// + bool addObject(SceneObject*); + bool removeObject(SceneObject*); + + void addRefPoolBlock(); + SceneObjectRef* allocateObjectRef(); + void freeObjectRef(SceneObjectRef*); + void insertIntoBins(SceneObject*); + void removeFromBins(SceneObject*); + + /// checkBins makes sure that we're not just sticking the object right back + /// where it came from. The overloaded insertInto is so we don't calculate + /// the ranges twice. + void checkBins(SceneObject*); + void insertIntoBins(SceneObject*, U32, U32, U32, U32); + + +private: + Vector*> mSearchList;///< Object searches to support console querying of the database. ONLY WORKS ON SERVER + S32 mCurrSearchPos; + Point3F mSearchReferencePoint; + void cleanupSearchVectors(); + + void _findWaterAndZoneObjects( U32 mask, FindCallback, void *key = NULL ); + void _findWaterAndZoneObjects( const Box3F &box, U32 mask, FindCallback callback, void *key = NULL ); + +public: + void initRadiusSearch(const Point3F& searchPoint, + const F32 searchRadius, + const U32 searchMask); + void initTypeSearch(const U32 searchMask); + U32 containerSearchNext(); + F32 containerSearchCurrDist(); + F32 containerSearchCurrRadiusDist(); +}; + + +//---------------------------------------------------------------------------- +/// For simple queries. Simply creates a vector of the objects +class SimpleQueryList +{ + public: + Vector mList; + + public: + SimpleQueryList() + { + VECTOR_SET_ASSOCIATION(mList); + } + + void insertObject(SceneObject* obj) { mList.push_back(obj); } + static void insertionCallback(SceneObject* obj, void *key); +}; + +class SceneObjectLightingPlugin +{ +public: + virtual ~SceneObjectLightingPlugin() { } + /// Reset light plugin to clean state. + virtual void reset() {} + + // Called by statics + virtual U32 packUpdate(SceneObject* obj, U32 checkMask, NetConnection *conn, U32 mask, BitStream *stream) = 0; + virtual void unpackUpdate(SceneObject* obj, NetConnection *conn, BitStream *stream) = 0; +}; + +//---------------------------------------------------------------------------- + +/// A 3D object. +/// +/// @section SceneObject_intro Introduction +/// +/// SceneObject exists as a foundation for 3D objects in Torque. It provides the +/// basic functionality for: +/// - A scene graph (in the Zones and Portals sections), allowing efficient +/// and robust rendering of the game scene. +/// - Various helper functions, including functions to get bounding information +/// and momentum/velocity. +/// - Collision detection, as well as ray casting. +/// - Lighting. SceneObjects can register lights both at lightmap generation time, +/// and dynamic lights at runtime (for special effects, such as from flame or +/// a projectile, or from an explosion). +/// - Manipulating scene objects, for instance varying scale. +/// +/// @section SceneObject_example An Example +/// +/// Melv May has written a most marvelous example object deriving from SceneObject. +/// Unfortunately this page is too small to contain it. +/// +/// @see http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=3217 +/// for a copy of Melv's example. +class SceneObject : public NetObject, public LightReceiver, public Container::Link +{ + typedef NetObject Parent; + friend class Container; + friend class SceneGraph; + friend class SceneState; + + //-------------------------------------- Public constants +public: + enum + { + MaxObjectZones = 128 + }; + + enum TraversalState + { + Pending = 0, + Working = 1, + Done = 2 + }; + + enum SceneObjectMasks + { + ScaleMask = BIT(0), + NextFreeMask = BIT(1) + }; + + //-------------------------------------- Public interfaces + // C'tors and D'tors +private: + SceneObject(const SceneObject&); ///< @deprecated disallowed + +public: + SceneObject(); + virtual ~SceneObject(); + + /// Returns a value representing this object which can be passed to script functions. + const char* scriptThis(); + +public: + /// @name Collision and transform related interface + /// + /// The Render Transform is the interpolated transform with respect to the + /// frame rate. The Render Transform will differ from the object transform + /// because the simulation is updated in fixed intervals, which controls the + /// object transform. The framerate is, most likely, higher than this rate, + /// so that is why the render transform is interpolated and will differ slightly + /// from the object transform. + /// + /// @{ + + /// Disables collisions for this object including raycasts + virtual void disableCollision(); + + /// Enables collisions for this object + virtual void enableCollision(); + + /// Returns true if collisions are enabled + bool isCollisionEnabled() const { return mCollisionCount == 0; } + + /// This gets called when an object collides with this object. + /// @param object Object colliding with this object + /// @param vec Vector along which collision occurred + virtual void onCollision( SceneObject *object, const VectorF &vec ) {} + + /// Returns true if this object allows itself to be displaced + /// @see displaceObject + virtual bool isDisplacable() const; + + /// Returns the momentum of this object + virtual Point3F getMomentum() const; + + /// Sets the momentum of this object + /// @param momentum Momentum + virtual void setMomentum(const Point3F &momentum); + + /// Returns the mass of this object + virtual F32 getMass() const; + + /// Displaces this object by a vector + /// @param displaceVector Displacement vector + virtual bool displaceObject(const Point3F& displaceVector); + + /// Returns the transform which can be used to convert object space + /// to world space + virtual const MatrixF& getTransform() const { return mObjToWorld; } + + /// Returns the transform which can be used to convert world space + /// into object space + const MatrixF& getWorldTransform() const { return mWorldToObj; } + + /// Returns the scale of the object + const VectorF& getScale() const { return mObjScale; } + + /// Returns the bounding box for this object in local coordinates + const Box3F& getObjBox() const { return mObjBox; } + + /// Returns the bounding box for this object in world coordinates + const Box3F& getWorldBox() const { return mWorldBox; } + + virtual const Box3F& getZoneBox() const { return mWorldBox; } + + /// Returns the bounding sphere for this object in world coordinates + const SphereF& getWorldSphere() const { return mWorldSphere; } + + /// Returns the center of the bounding box in world coordinates + Point3F getBoxCenter() const { return (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5f; } + + /// Sets the Object -> World transform + /// + /// @param mat New transform matrix + virtual void setTransform(const MatrixF & mat); + + /// Sets the scale for the object + /// @param scale Scaling values + virtual void setScale(const VectorF & scale); + + /// This sets the render transform for this object + /// @param mat New render transform + virtual void setRenderTransform(const MatrixF &mat); + + /// Returns the render transform + const MatrixF& getRenderTransform() const { return mRenderObjToWorld; } + + /// Returns the render transform to convert world to local coordinates + const MatrixF& getRenderWorldTransform() const { return mRenderWorldToObj; } + + /// Returns the render world box + const Box3F& getRenderWorldBox() const { return mRenderWorldBox; } + + /// Builds a convex hull for this object. + /// + /// Think of a convex hull as a low-res mesh which covers, as tightly as + /// possible, the object mesh, and is used as a collision mesh. + /// @param box + /// @param convex Convex mesh generated (out) + virtual void buildConvex(const Box3F& box,Convex* convex); + + /// Builds a list of polygons which intersect a bounding volume. + /// + /// This will use either the sphere or the box, not both, the + /// SceneObject implementation ignores sphere. + /// + /// @see AbstractPolyList + /// @param polyList Poly list build (out) + /// @param box Box bounding volume + /// @param sphere Sphere bounding volume + virtual bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + + /// Same as buildPolyList but operates against the rendered geometry. + /// @see buildPolyList + virtual bool buildRenderedPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + + /// Casts a ray and obtain collision information, returns true if RayInfo is modified. + /// + /// @param start Start point of ray + /// @param end End point of ray + /// @param info Collision information obtained (out) + virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + + /// Casts a ray against rendered geometry, returns true if RayInfo is modified. + /// + /// @param start Start point of ray + /// @param end End point of ray + /// @param info Collision information obtained (out) + virtual bool castRayRendered(const Point3F &start, const Point3F &end, RayInfo* info); + + virtual bool collideBox(const Point3F &start, const Point3F &end, RayInfo* info); + + /// Returns the position of the object. + virtual Point3F getPosition() const; + + /// Returns the render-position of the object. + /// + /// @see getRenderTransform + Point3F getRenderPosition() const; + + /// Sets the position of the object + void setPosition(const Point3F &pos); + + /// Gets the velocity of the object + virtual Point3F getVelocity() const; + + /// Sets the velocity of the object + /// @param v Velocity + virtual void setVelocity(const Point3F &v); + + /// Applies an impulse force to this object + /// @param pos Position where impulse came from in world space + /// @param vec Velocity vector (Impulse force F = m * v) + virtual void applyImpulse(const Point3F& pos,const VectorF& vec) { } + + /// Applies a radial impulse to the object + /// using the impulse origin and force. + /// @param origin Point of origin of the radial impulse. + /// @param radius The radius of the impulse area. + /// @param magnitude The strength of the impulse. + virtual void applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude ); + + /// Returns the distance from this object to a point + /// @param pnt Point + virtual F32 distanceTo( const Point3F &pnt ) const; + + /// @} + + /// @name Mounted objects + /// @{ + + /// Mount an object to a mount point + /// @param obj Object to mount + /// @param node Mount node ID + virtual void mountObject( SceneObject *obj, U32 node ) {} + + /// Remove an object mounting + /// @param obj Object to unmount + virtual void unmountObject( SceneObject *obj ) {} + + /// Unmount this object from it's mount + virtual void unmount() {}; + + /// Callback when this object is mounted. This should be overridden to + /// set maskbits or do other object type specific work. + /// @param obj Object we are mounting to. + /// @param node Node we are unmounting from. + virtual void onMount( SceneObject *obj, S32 node ) {} + + /// Callback when this object is unmounted. This should be overridden to + /// set maskbits or do other object type specific work. + /// @param obj Object we are unmounting from. + /// @param node Node we are unmounting from. + virtual void onUnmount( SceneObject *obj, S32 node ) {} + + // Returns mount point to world space transform at tick time. + virtual void getMountTransform( U32 index, MatrixF *mat ) {} + + // Returns mount point to world space transform at render time. + virtual void getRenderMountTransform( U32 index, MatrixF *mat ) {} + + /// Return the object that this object is mounted to + virtual SceneObject* getObjectMount() { return mMount.object; } + + /// Return object link of next object mounted to this object's mount + virtual SceneObject* getMountLink() { return mMount.link; } + + /// Returns object list of objects mounted to this object. + virtual SceneObject* getMountList() { return mMount.list; } + + /// Returns the mount id that this is mounted to + virtual U32 getMountNode() { return mMount.node; } + + /// Returns true if this object is mounted to anything at all + virtual bool isMounted() { return mMount.object != 0; } + + /// Returns the number of object mounted along with this + virtual S32 getMountedObjectCount(); + + /// Returns the object mounted at a position in the mount list + /// @param idx Position on the mount list + virtual SceneObject* getMountedObject(S32 idx); + + /// Returns the node the object at idx is mounted to + /// @param idx Index + virtual S32 getMountedObjectNode(S32 idx); + + /// Returns the object a object on the mount list is mounted to + /// @param node + virtual SceneObject* getMountNodeObject(S32 node); + + /// @} + +public: + /// @name Zones + /// + /// A zone is a portalized section of an InteriorInstance, and an InteriorInstance can manage more than one zone. + /// There is always at least one zone in the world, zone 0, which represents the whole world. Any + /// other zone will be in an InteriorInstance. Torque keeps track of the zones containing an object + /// as it moves throughout the world. An object can exists in multiple zones at once. + /// @{ + + /// Returns true if this object is managing zones. + /// + /// This is only true in the case of InteriorInstances which have zones in them. + bool isManagingZones() const; + + /// Gets the index of the first zone this object manages in the collection of zones. + U32 getZoneRangeStart() const { return mZoneRangeStart; } + + /// Gets the number of zones containing this object. + U32 getNumCurrZones() const { return mNumCurrZones; } + + /// Returns the nth zone containing this object. + U32 getCurrZone(const U32 index) const; + + /// If an object exists in multiple zones, this method will give you the + /// number and indices of these zones (storing them in the provided variables). + /// + /// @param obj Object in question. + /// @param zones Indices of zones containing the object. (out) + /// @param numZones Number of elements in the returned array. (out) + virtual bool getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones); + + /// Returns the zone containing p. + /// + /// @param p Point to test. + virtual U32 getPointZone(const Point3F& p); + + /// This is called on a zone managing object to scope all the zones managed. + /// + /// @param rootPosition Camera position + /// @param rootDistance Camera visible distance + /// @param zoneScopeState Array of booleans which line up with the collection of zones, marked true if that zone is scoped (out) + virtual bool scopeObject(const Point3F& rootPosition, + const F32 rootDistance, + bool* zoneScopeState); + /// @} + + /// Called when the SceneGraph is ready for the registration of RenderImages. + /// + /// @see SceneState + /// + /// @param state SceneState + /// @param stateKey State key of the current SceneState + /// @param startZone Base zone index + /// @param modifyBaseZoneState If true, the object needs to modify the zone state. + virtual bool prepRenderImage( SceneState *state, + const U32 stateKey, + const U32 startZone, + const bool modifyBaseZoneState = false); + + /// Adds object to the client or server container depending on the object + void addToScene(); + + /// Removes the object from the client/server container + void removeFromScene(); + + /// + bool isRenderEnabled() const; + + //-------------------------------------- Derived class interface + // Overrides +protected: + bool onAdd(); + void onRemove(); + + // Overrideables +protected: + /// Called when this is added to the SceneGraph. + /// + /// @param graph SceneGraph this is getting added to + virtual bool onSceneAdd(SceneGraph *graph); + + /// Called when this is removed from the SceneGraph + virtual void onSceneRemove(); + + /// Called when the size of the object changes + virtual void onScaleChanged(); + + /// Called when the zone list is changed. + virtual void onRezone() {} + + /// @name Portals + /// @{ + + /// This is used by a portal controlling object to transform the base-modelview + /// used by the scenegraph for rendering to the modelview it needs to render correctly. + /// + /// @see MirrorSubObject + /// + /// @param portalIndex Index of portal in the list of portals controlled by the object. + /// @param oldMV Current modelview matrix used by the SceneGraph (in) + /// @param newMV New modelview to be used by the SceneGraph (out) + virtual void transformModelview(const U32 portalIndex, const MatrixF& oldMV, MatrixF* newMV); + + /// Used to transform the position of a point based on a portal. + /// + /// @param portalIndex Index of a portal to transform by. + /// @param point Point to transform. + virtual void transformPosition(const U32 portalIndex, Point3F& point); + + /// Returns a new view frustum for the portal. + /// + /// @param portalIndex Which portal in the list of portals the object controls + /// @param oldFrustum Current frustum. + /// @param nearPlane Near clipping plane. + /// @param farPlane Far clipping plane. + /// @param oldViewport Current viewport. + /// @param newFrustum New view frustum to use. (out) + /// @param newViewport New viewport to use. (out) + /// @param flippedMatrix Should the object should use a flipped matrix to calculate viewport and frustum? + virtual bool computeNewFrustum(const U32 portalIndex, + const Frustum &oldFrustum, + const F64 nearPlane, + const F64 farPlane, + const RectI& oldViewport, + F64 *newFrustum, + RectI& newViewport, + const bool flippedMatrix); + + /// Called before things are to be rendered from the portals point of view, to set up + /// everything the portal needs to render correctly. + /// + /// @param portalIndex Index of portal to use. + /// @param pCurrState Current SceneState + /// @param pParentState SceneState used before this portal was activated + virtual void openPortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState); + + /// Called after rendering of a portal is complete, this resets the states + /// the previous call to openPortal() changed. + /// + /// @param portalIndex Index of portal to use. + /// @param pCurrState Current SceneState + /// @param pParentState SceneState used before this portal was activated + virtual void closePortal(const U32 portalIndex, + SceneState* pCurrState, + SceneState* pParentState); +public: + + /// Returns the plane of the portal in world space. + /// + /// @param portalIndex Index of portal to use. + /// @param plane Plane of the portal in world space (out) + virtual void getWSPortalPlane(const U32 portalIndex, PlaneF *plane); + + /// @} + +protected: + /// Sets the mLastState and mLastStateKey. + /// + /// @param state SceneState to set as the last state + /// @param key Key to set as the last state key + void setLastState(SceneState *state, U32 key); + + /// Returns true if the provided SceneState and key are set as this object's + /// last state and key. + /// + /// @param state SceneState in question + /// @param key State key in question + bool isLastState(SceneState *state, U32 key) const; + + + /// @name Traversal State + /// + /// The SceneGraph traversal is recursive and the traversal state of an object + /// can be one of three things: + /// - Pending - The object has not yet been examined for zone traversal. + /// - Working - The object is currently having its zones traversed. + /// - Done - The object has had all of its zones traversed or doesn't manage zones. + /// + /// @note These states were formerly referred to as TraverseColor, with White, Black, and + /// Gray; this was changed in Torque 1.2 by Pat "KillerBunny" Wilson. This code is + /// only used internal to this class + /// @{ + + // These two replaced by TraversalState because that makes more sense -KB + //void setTraverseColor(TraverseColor); + //TraverseColor getTraverseColor() const; + // ph34r teh muskrat! - Travis Colure + + /// This sets the traversal state of the object. + /// + /// @note This is used internally; you should not normally need to call it. + /// @param s Traversal state to assign + void setTraversalState( TraversalState s ); + + /// Returns the traversal state of this object + TraversalState getTraversalState() const; + + /// @} + + /// @name Lighting + /// @{ + +protected: + + SceneObjectLightingPlugin* mLightPlugin; + +public: + + void setLightingPlugin(SceneObjectLightingPlugin* plugin) { mLightPlugin = plugin; } + SceneObjectLightingPlugin* getLightingPlugin() { return mLightPlugin; } + + /// @} + +public: + + // TODO: figure out why ShapeBase cannot access mMount + // with this in a protected section... + + /// Mounted objects + struct MountInfo { + SceneObject* list; ///< Objects mounted on this object + SceneObject* object; ///< Object this object is mounted on. + SceneObject* link; ///< Link to next object mounted to this object's mount + U32 node; ///< Node point we are mounted to. + } mMount; +protected: + + /// @name Transform and Collision Members + /// @{ + + /// + Container* mContainer; + + MatrixF mObjToWorld; ///< Transform from object space to world space + MatrixF mWorldToObj; ///< Transform from world space to object space (inverse) + Point3F mObjScale; ///< Object scale + + Box3F mObjBox; ///< Bounding box in object space + Box3F mWorldBox; ///< Bounding box in world space + SphereF mWorldSphere; ///< Bounding sphere in world space + + MatrixF mRenderObjToWorld; ///< Render matrix to transform object space to world space + MatrixF mRenderWorldToObj; ///< Render matrix to transform world space to object space + Box3F mRenderWorldBox; ///< Render bounding box in world space + SphereF mRenderWorldSphere; ///< Render bounxing sphere in world space + + /// Regenerates the world-space bounding box and bounding sphere. + void resetWorldBox(); + + /// Regenerates the render-world-space bounding box and sphere. + void resetRenderWorldBox(); + + /// Regenerates the object-space bounding box from the world-space + /// bounding box, the world space to object space transform, and + /// the object scale. + void resetObjectBox(); + + SceneObjectRef* mZoneRefHead; + SceneObjectRef* mBinRefHead; + + U32 mBinMinX; + U32 mBinMaxX; + U32 mBinMinY; + U32 mBinMaxY; + + /// @} + + /// @name Container Interface + /// + /// When objects are searched, we go through all the zones and ask them for + /// all of their objects. Because an object can exist in multiple zones, the + /// container sequence key is set to the id of the current search. Then, while + /// searching, we check to see if an object's sequence key is the same as the + /// current search key. If it is, it will NOT be added to the list of returns + /// since it has already been processed. + /// + /// @{ + + U32 mContainerSeqKey; ///< Container sequence key + + /// Returns the container sequence key + U32 getContainerSeqKey() const { return mContainerSeqKey; } + + /// Sets the container sequence key + void setContainerSeqKey(const U32 key) { mContainerSeqKey = key; } + /// @} + +public: + + /// Returns a pointer to the container that contains this object + Container* getContainer() { return mContainer; } + +protected: + S32 mCollisionCount; + + bool mGlobalBounds; + +public: + const bool isGlobalBounds() const + { + return mGlobalBounds; + } + + /// If global bounds are set to be true, then the object is assumed to + /// have an infinitely large bounding box for collision and rendering + /// purposes. + /// + /// They can't be toggled currently. + void setGlobalBounds() + { + if(mContainer) + mContainer->removeFromBins(this); + + mGlobalBounds = true; + mObjBox.minExtents.set(-1e10, -1e10, -1e10); + mObjBox.maxExtents.set( 1e10, 1e10, 1e10); + + if(mContainer) + mContainer->insertIntoBins(this); + } + + + /// @name Rendering Members + /// @{ + +public: + + SceneGraph* getSceneGraph() const { return mSceneManager; } + +protected: + SceneGraph* mSceneManager; ///< SceneGraph that controls this object + U32 mZoneRangeStart; ///< Start of range of zones this object controls, 0xFFFFFFFF == no zones + + U32 mNumCurrZones; ///< Number of zones this object exists in + + TraversalState mTraversalState; ///< State of this object in the SceneGraph traversal - DON'T MESS WITH THIS + SceneState* mLastState; ///< Last SceneState that was used to render this object. + U32 mLastStateKey; ///< Last state key that was used to render this object. + + /// @} + + /// @name Persist and console + /// @{ +public: + static void initPersistFields(); + void inspectPostApply(); + DECLARE_CONOBJECT(SceneObject); + + /// @} + + /// Triggered when a scene object onAdd is called. Allows other systems to plug into SceneObject + static Signal smSceneObjectAdd; + + /// Triggered when a SceneObject onRemove is called. + static Signal smSceneObjectRemove; + protected: + U8 mSelectionFlags; +public: + enum { + SELECTED = BIT(0), + PRE_SELECTED = BIT(1), + }; + void setSelectionFlags(U8 flags) { mSelectionFlags = flags; } + U8 getSelectionFlags() const { return mSelectionFlags; } + bool needsSelectionHighlighting() const { return (mSelectionFlags != 0); } +}; + + +//-------------------------------------------------------------------------- +extern Container gServerContainer; +extern Container gClientContainer; + +//-------------------------------------------------------------------------- +//-------------------------------------- Inlines +// +inline bool SceneObject::isManagingZones() const +{ + return mZoneRangeStart != 0xFFFFFFFF; +} + +inline void SceneObject::setLastState(SceneState* state, U32 key) +{ + mLastState = state; + mLastStateKey = key; +} + +inline bool SceneObject::isLastState(SceneState* state, U32 key) const +{ + return (mLastState == state && mLastStateKey == key); +} + +inline void SceneObject::setTraversalState( TraversalState s ) { + mTraversalState = s; +} + +inline SceneObject::TraversalState SceneObject::getTraversalState() const { + return mTraversalState; +} + +inline U32 SceneObject::getCurrZone(const U32 index) const +{ + // Not the most efficient way to do this, walking the list, + // but it's an uncommon call... + SceneObjectRef* walk = mZoneRefHead; + for (U32 i = 0; i < index; i++) + { + walk = walk->nextInObj; + AssertFatal(walk!=NULL, "Error, too few object refs!"); + } + AssertFatal(walk!=NULL, "Error, too few object refs!"); + + return walk->zone; +} + +//-------------------------------------------------------------------------- +inline SceneObjectRef* Container::allocateObjectRef() +{ + if (mFreeRefPool == NULL) + { + addRefPoolBlock(); + } + AssertFatal(mFreeRefPool!=NULL, "Error, should always have a free reference here!"); + + SceneObjectRef* ret = mFreeRefPool; + mFreeRefPool = mFreeRefPool->nextInObj; + + ret->nextInObj = NULL; + return ret; +} + +inline void Container::freeObjectRef(SceneObjectRef* trash) +{ + trash->object = NULL; + trash->nextInBin = NULL; + trash->prevInBin = NULL; + trash->nextInObj = mFreeRefPool; + mFreeRefPool = trash; +} + +inline void Container::findObjectList( U32 mask, Vector *outFound ) +{ + for ( Link* itr = mStart.next; itr != &mEnd; itr = itr->next ) + { + SceneObject *ptr = static_cast( itr ); + if ( ( ptr->getType() & mask ) != 0 ) + outFound->push_back( ptr ); + } +} + +inline void Container::findObjects(U32 mask, FindCallback callback, void *key) +{ + for (Link* itr = mStart.next; itr != &mEnd; itr = itr->next) { + SceneObject* ptr = static_cast(itr); + if ((ptr->getType() & mask) != 0 && !ptr->mCollisionCount) + (*callback)(ptr,key); + } +} + +#endif // _SCENEOBJECT_H_ + diff --git a/sceneGraph/sceneRoot.cpp b/sceneGraph/sceneRoot.cpp new file mode 100644 index 0000000..cd068fc --- /dev/null +++ b/sceneGraph/sceneRoot.cpp @@ -0,0 +1,252 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sceneRoot.h" +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sceneState.h" +#include "T3D/portal.h" + +SceneRoot* gClientSceneRoot = NULL; +SceneRoot* gServerSceneRoot = NULL; + +U32 SceneRoot::smPortalKey = 0; + +static Point3F gSortPoint; + +int QSORT_CALLBACK cmpPortalDistance( const void *p1, const void *p2 ) +{ + const Portal** pd1 = (const Portal**)p1; + const Portal** pd2 = (const Portal**)p2; + + F32 dist1 = ( (*pd1)->getPosition() - gSortPoint ).lenSquared(); + F32 dist2 = ( (*pd2)->getPosition() - gSortPoint ).lenSquared(); + + return dist2 - dist1; +} + +SceneRoot::SceneRoot() +{ + setGlobalBounds(); + resetWorldBox(); +} + +SceneRoot::~SceneRoot() +{ + +} + +bool SceneRoot::onSceneAdd(SceneGraph* pGraph) +{ + // _Cannot_ call the parent here. Must handle this ourselves so we can keep out of + // the zone graph... +// if (Parent::onSceneAdd(pGraph) == false) +// return false; + + mSceneManager = pGraph; + mSceneManager->registerZones(this, 1); + AssertFatal(mZoneRangeStart == 0, "error, sceneroot must be first scene object zone manager!"); + + return true; +} + +void SceneRoot::onSceneRemove() +{ + AssertFatal(mZoneRangeStart == 0, "error, sceneroot must be first scene object zone manager!"); + mSceneManager->unregisterZones(this); + mZoneRangeStart = 0xFFFFFFFF; + mSceneManager = NULL; + + // _Cannot_ call the parent here. Must handle this ourselves so we can keep out of + // the zone graph... +// Parent::onSceneRemove(); +} + +bool SceneRoot::getOverlappingZones(SceneObject*, U32* zones, U32* numZones) +{ + // If we are here, we always return the global zone. + zones[0] = 0; + *numZones = 1; + + return false; +} + +bool SceneRoot::prepRenderImage(SceneState* state, const U32 stateKey, + const U32, + const bool modifyBaseZoneState) +{ + AssertFatal(modifyBaseZoneState == true, "error, should never be called unless in the upward traversal!"); + AssertFatal(isLastState(state, stateKey) == false, "Error, should have been colored black in order to prevent double calls!"); + setLastState(state, stateKey); + + // We don't return a render image, or any portals, but we do setup the zone 0 + // rendering parameters. We simply copy them from what is in the states baseZoneState + // structure, and mark the zone as rendered. + state->getZoneStateNC( 0 ).frustum = state->getBaseZoneState().frustum; + + // RLP/Sickhead NOTE: Do the zone traversal after setting + // the base zone default frustum, otherwise + // normal Interior rendering of portaled areas + // will be borked! + _traverseZones( state ); + + state->getZoneStateNC(0).viewport = state->getBaseZoneState().viewport; + state->getZoneStateNC(0).render = true; + + return false; +} + +bool SceneRoot::scopeObject(const Point3F& /*rootPosition*/, + const F32 /*rootDistance*/, + bool* zoneScopeState) +{ + zoneScopeState[0] = true; + return false; +} + +void SceneRoot::_addPortal( Portal *p ) +{ + mPortals.push_back( p ); +} + +void SceneRoot::_removePortal( Portal *p ) +{ + mPortals.remove( p ); +} + +void SceneRoot::_traverseZones( SceneState *state ) +{ + const Frustum &frust = state->getFrustum(); + Frustum currFrustum( frust ); + + // Need to check somewhere if we're inside a zone + // already, looking out into the outside zone + // and if so, use that zone's portal frustum in + // order to check to see if we can see through + // the portals that connect to the outside. + + // Make a new list consisting + // only of portals that we can + // see that connect to the outside zone. + // Only do the new list and sort if + // there's more than one portal! + Vector tmpPortals; + if ( mPortals.size() > 1 ) + { + gSortPoint = state->getCameraPosition(); + for ( U32 i = 0; i < mPortals.size(); i++ ) + { + Portal *portal = mPortals[i]; + if ( !frust.intersectOBB( portal->getOBBPoints() ) ) + continue; + + tmpPortals.push_back( portal ); + } + + // Sort portals by distance from near to far. + dQsort( tmpPortals.address(), tmpPortals.size(), sizeof( Portal* ), cmpPortalDistance ); + } + else + tmpPortals.merge( mPortals ); + + Vector portalStack; + portalStack.merge( tmpPortals ); + + smPortalKey++; + + while ( portalStack.size() ) + { + Portal *portal = portalStack.last(); + if ( !portal ) + { + portalStack.pop_back(); + continue; + } + + portal->setPortalKey( smPortalKey ); + + portalStack.pop_back(); + + // If this portal doesn't intersect + // our frustum, nothing inside the zone + // it connects to needs to be rendered. + if ( !currFrustum.intersectOBB( portal->getOBBPoints() ) ) + continue; + + Frustum newFrustum( frust ); + portal->generatePortalFrustum( state, &newFrustum ); + newFrustum.invert(); + + Zone *z0 = portal->getZone( 0 ); + Zone *z1 = portal->getZone( 1 ); + + if ( z0 && z0->getPortalKey() != smPortalKey ) + { + // If we got the zone that + // this portal we can see connects + // to, then go ahead and set our + // portaled frustum on its ZoneState. + if ( !z0->getPointZone( state->getCameraPosition() ) ) + { + SceneState::ZoneState &zoneState = state->getZoneStateNC( z0->getZoneRangeStart() ); + zoneState.render = true; + zoneState.frustum = newFrustum; + z0->setPortalKey( smPortalKey ); + } + + currFrustum = newFrustum; + + // Go through this zone's portals, + // and see if any of them are visible + // using the new portaled frustum. + const Vector &portals = z0->getPortals(); + for ( U32 i = 0; i < portals.size(); i++ ) + { + Portal *subPortal = portals[i]; + + // If this portal is visible through the + // new portaled frustum, and it's not the current + // upper level portal, stick it onto the stack. + + // Note: Don't need to do the frustum check + // here, because it will check against this portal on + // the next loop through the portals on the stack. + if ( subPortal->getPortalKey() != smPortalKey && currFrustum.intersectOBB( subPortal->getOBBPoints() ) ) + portalStack.push_back( subPortal ); + } + } + + if ( z1 && z1->getPortalKey() != smPortalKey ) + { + // If we got the zone that + // this portal we can see connects + // to, then go ahead and set our + // portaled frustum on its ZoneState. + if ( !z1->getPointZone( state->getCameraPosition() ) ) + { + SceneState::ZoneState &zoneState = state->getZoneStateNC( z1->getZoneRangeStart() ); + zoneState.render = true; + zoneState.frustum = newFrustum; + z1->setPortalKey( smPortalKey ); + } + + currFrustum = newFrustum; + + // Go through this zone's portals, + // and see if any of them are visible + // using the new portaled frustum. + const Vector &portals = z1->getPortals(); + for ( U32 i = 0; i < portals.size(); i++ ) + { + Portal *subPortal = portals[i]; + + // If this portal is visible through the + // new portaled frustum, and it's not the current + // upper level portal, stick it onto the stack. + if ( subPortal->getPortalKey() != smPortalKey ) + portalStack.push_back( subPortal ); + } + } + } +} diff --git a/sceneGraph/sceneRoot.h b/sceneGraph/sceneRoot.h new file mode 100644 index 0000000..e724153 --- /dev/null +++ b/sceneGraph/sceneRoot.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENEROOT_H_ +#define _SCENEROOT_H_ + +//Includes +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif + +class Portal; + +/// Root of scene graph. +class SceneRoot : public SceneObject +{ + typedef SceneObject Parent; + friend class Portal; + + protected: + + static U32 smPortalKey; + + Vector mPortals; + + bool onSceneAdd(SceneGraph *graph); + void onSceneRemove(); + + bool getOverlappingZones(SceneObject *obj, U32 *zones, U32 *numZones); + + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, + const bool modifyBaseZoneState); + + bool scopeObject(const Point3F& rootPosition, + const F32 rootDistance, + bool* zoneScopeState); + + void _addPortal( Portal *p ); + void _removePortal( Portal *p ); + void _traverseZones( SceneState *state ); + + public: + SceneRoot(); + ~SceneRoot(); +}; + +extern SceneRoot* gClientSceneRoot; ///< Client's scene graph root. +extern SceneRoot* gServerSceneRoot; ///< Server's scene graph root. + +#endif //_SCENEROOT_H_ diff --git a/sceneGraph/sceneState.cpp b/sceneGraph/sceneState.cpp new file mode 100644 index 0000000..9517a5a --- /dev/null +++ b/sceneGraph/sceneState.cpp @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sceneGraph/sceneState.h" + +#include "sceneGraph/sgUtil.h" +#include "sceneGraph/sceneObject.h" +#include "platform/profiler.h" +#include "gfx/gfxDevice.h" +#include "gfx/primBuilder.h" +#include "interior/interiorInstance.h" +#include "T3D/gameConnection.h" + + +// MM/JF: Added for mirrorSubObject fix. +void SceneState::setupClipPlanes( ZoneState *rState ) +{ + rState->frustum.set( rState->frustum.isOrtho(), + rState->frustum.getNearLeft(), + rState->frustum.getNearRight(), + rState->frustum.getNearTop(), + rState->frustum.getNearBottom(), + mFrustum.getNearDist(), + mFrustum.getFarDist(), + mFrustum.getTransform() ); + + // clip-planes through mirror portal are inverted + if ( rState->frustum.isInverted() ) + rState->frustum.invert(); + + rState->clipPlanesValid = true; +} + + +SceneState::SceneState( SceneState *parent, + SceneGraph *mgr, + ScenePassType passType, + U32 numZones, + const Frustum &frustum, + const RectI &viewport, + bool usePostEffects, + bool invert ) +{ + VECTOR_SET_ASSOCIATION( mZoneStates ); + VECTOR_SET_ASSOCIATION( mSubsidiaries ); + VECTOR_SET_ASSOCIATION( mTransformPortals ); + VECTOR_SET_ASSOCIATION( mInteriorList ); + + mFrustum.set( frustum ); + + const F32 left = mFrustum.getNearLeft(); + const F32 right = mFrustum.getNearRight(); + const F32 top = mFrustum.getNearTop(); + const F32 bottom = mFrustum.getNearBottom(); + const F32 nearPlane = mFrustum.getNearDist(); + + mSceneManager = mgr; + mLightManager = mgr->getLightManager(); + + mParent = parent; + + mScenePassType = passType; + + if ( passType == SPT_Reflect || invert ) + { + mFlipCull = true; + mFrustum.invert(); + } + else + mFlipCull = false; + + mRenderNonLightmappedMeshes = true; + mRenderLightmappedMeshes = true; + + mBaseZoneState.render = false; + mBaseZoneState.clipPlanesValid = false; + mBaseZoneState.frustum = mFrustum; + mBaseZoneState.viewport = viewport; + + // Setup the default parameters for the screen metrics methods. + mDiffuseCameraTransform = mFrustum.getTransform(); + mViewportExtent = viewport.extent; + + // TODO: What about ortho modes? Is near plane ok + // or do i need to remove it... maybe ortho has a near + // plane of 1 and it just works out? + mWorldToScreenScale.set( ( nearPlane * mViewportExtent.x ) / ( right - left ), + ( nearPlane * mViewportExtent.y ) / ( top - bottom ) ); + + mZoneStates.setSize(numZones); + for (U32 i = 0; i < numZones; i++) + { + mZoneStates[i].render = false; + mZoneStates[i].clipPlanesValid = false; + } + + mPortalOwner = NULL; + mPortalIndex = 0xFFFFFFFF; + + mTerrainOverride = false; + + mUsePostEffects = usePostEffects; + + mAlwaysRender = false; +} + +SceneState::~SceneState() +{ + U32 i; + for (i = 0; i < mSubsidiaries.size(); i++) + delete mSubsidiaries[i]; +} + +void SceneState::setPortal(SceneObject* owner, const U32 index) +{ + mPortalOwner = owner; + mPortalIndex = index; +} + + +void SceneState::insertTransformPortal(SceneObject* owner, U32 portalIndex, + U32 globalZone, const Point3F& traversalStartPoint, + const bool flipCull) +{ + mTransformPortals.increment(); + mTransformPortals.last().owner = owner; + mTransformPortals.last().portalIndex = portalIndex; + mTransformPortals.last().globalZone = globalZone; + mTransformPortals.last().traverseStart = traversalStartPoint; + mTransformPortals.last().flipCull = flipCull; +} + +void SceneState::renderCurrentImages() +{ + GFX->pushWorldMatrix(); + + // need to do this AFTER scene traversal, otherwise zones/portals will not + // work correctly + + PROFILE_START(InteriorPrepBatchRender); + for( U32 i=0; igetResource()->getDetailLevel( elem.detailLevel ); + pInterior->prepBatchRender( elem.obj, this, elem.worldXform ); + } + PROFILE_END(); + + GFX->popWorldMatrix(); + + RenderPassManager *renderPass = mSceneManager->getRenderPass(); + AssertFatal( renderPass, "SceneState::renderCurrentImages() - Got null RenderPassManager!" ); + + renderPass->sort(); + renderPass->render(this); + renderPass->clear(); + + mInteriorList.clear(); + +} + +bool SceneState::isObjectRendered(const SceneObject* obj) +{ + if ( mAlwaysRender ) + return true; + + const SceneObjectRef* pWalk = obj->mZoneRefHead; + + /* + static F32 darkToOGLCoord[16] = { 1, 0, 0, 0, + 0, 0, -1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 }; + static MatrixF darkToOGLMatrix; + static bool matrixInitialized = false; + if (matrixInitialized == false) + { + F32* m = darkToOGLMatrix; + for (U32 i = 0; i < 16; i++) + m[i] = darkToOGLCoord[i]; + darkToOGLMatrix.transpose(); + matrixInitialized = true; + } + */ + + while (pWalk != NULL) + { + if (getZoneState(pWalk->zone).render == true) + { + ZoneState &rState = getZoneStateNC( pWalk->zone ); + + if ( rState.clipPlanesValid == false ) + setupClipPlanes( &rState ); + + // The object's world box intersects or + // overlaps the frustum, so we need to render. + if ( rState.frustum.intersects( obj->getWorldBox() ) ) + return true; + } + + pWalk = pWalk->nextInObj; + } + + // Special-case the control object here. If the game connection + // is in first-person, don't allow it to be culled. + + GameConnection* serverConnection = GameConnection::getConnectionToServer(); + if( serverConnection->isFirstPerson() + && serverConnection->getControlObject() == obj ) + return true; + + return false; +} diff --git a/sceneGraph/sceneState.h b/sceneGraph/sceneState.h new file mode 100644 index 0000000..911deef --- /dev/null +++ b/sceneGraph/sceneState.h @@ -0,0 +1,353 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SCENESTATE_H_ +#define _SCENESTATE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MRECT_H_ +#include "math/mRect.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _MATHUTIL_FRUSTUM_H_ +#include "math/util/frustum.h" +#endif +#ifndef _COLOR_H_ +#include "core/color.h" +#endif +#ifndef _SCENEGRAPH_H_ +#include "sceneGraph/sceneGraph.h" +#endif +#ifndef _LIGHTMANAGER_H_ +#include "lighting/lightManager.h" +#endif + +class SceneObject; +class InteriorInstance; +class RenderPassManager; + +//-------------------------------------------------------------------------- +//-------------------------------------- SceneState +// + + +/// The SceneState describes the state of the scene being rendered. It keeps track +/// of the information that objects need to render properly with regard to the +/// camera position, any fog information, viewing frustum, the global environment +/// map for reflections, viewable distance and portal information. +class SceneState +{ + friend class SceneGraph; + +public: + + struct ZoneState + { + bool render; + Frustum frustum; + RectI viewport; + + bool clipPlanesValid; + }; + + struct InteriorListElem + { + InteriorInstance * obj; + U32 stateKey; + U32 startZone; + U32 detailLevel; + const MatrixF * worldXform; + }; + + /// Sets up the clipping planes using the parameters from a ZoneState. + /// + /// @param zone ZoneState to initalize clipping to + void setupClipPlanes( ZoneState *zone ); + + /// Used to represent a portal which inserts a transformation into the scene. + struct TransformPortal { + SceneObject* owner; + U32 portalIndex; + U32 globalZone; + Point3F traverseStart; + bool flipCull; + }; + + public: + + /// Constructor + SceneState( SceneState *parent, + SceneGraph *mgr, + ScenePassType passType, + U32 numZones, + const Frustum &frustum, + const RectI &viewport, + bool usePostEffects, + bool invert ); + + ~SceneState(); + + /// Sets the active portal. + /// + /// @param owner Object which owns the portal (portalized object) + /// @param idx Index of the portal in the list of portal planes + void setPortal(SceneObject *owner, const U32 idx); + + SceneGraph* getSceneManager() const { return mSceneManager; } + + LightManager* getLightManager() const { return mLightManager; } + + RenderPassManager* getRenderPass() const { return mSceneManager->getRenderPass(); } + + /// Returns the actual camera position. + /// @see getDiffuseCameraPosition + const Point3F& getCameraPosition() const { return mFrustum.getPosition(); } + + /// Returns the camera transform this SceneState is using. + const MatrixF& getCameraTransform() const { return mFrustum.getTransform(); } + + /// Returns the minimum distance something must be from the camera to not be culled. + F32 getNearPlane() const { return mFrustum.getNearDist(); } + + /// Returns the maximum distance something can be from the camera to not be culled. + F32 getFarPlane() const { return mFrustum.getFarDist(); } + + /// Returns the type of scene rendering pass that we're doing. + ScenePassType getScenePassType() const { return mScenePassType; } + + /// Returns true if this is a diffuse scene rendering pass. + bool isDiffusePass() const { return mScenePassType == SPT_Diffuse; } + + /// Returns true if this is a reflection scene rendering pass. + bool isReflectPass() const { return mScenePassType == SPT_Reflect; } + + /// Returns true if this is a shadow scene rendering pass. + bool isShadowPass() const { return mScenePassType == SPT_Shadow; } + + /// Returns true if this is not one of the other rendering passes. + bool isOtherPass() const { return mScenePassType >= SPT_Other; } + + /// If true then bin based post effects are disabled + /// during rendering with this scene state. + bool usePostEffects() const { return mUsePostEffects; } + + /// Returns the pixel size of the radius projected to + /// the screen at a desired distance. + /// + /// Internally this uses the stored world to screen scale + /// and viewport extents. This allows the projection to + /// be overloaded in special cases like when rendering + /// shadows or reflections. + /// + /// @see getWorldToScreenScale + /// @see getViewportExtent + F32 projectRadius( F32 dist, F32 radius ) const; + + /// Returns the possibly overloaded world to screen scale. + /// @see projectRadius + const Point2F& getWorldToScreenScale() const { return mWorldToScreenScale; } + + /// Set a new world to screen scale to overload + /// future screen metrics operations. + void setWorldToScreenScale( const Point2F &scale ) { mWorldToScreenScale = scale; } + + /// Returns the possibly overloaded viewport extents. + const Point2I& getViewportExtent() const { return mViewportExtent; } + + /// Set a new viewport extent to overload future + /// screen metric operations. + void setViewportExtent( const Point2I &extent ) { mViewportExtent = extent; } + + /// Returns the camera position used during the + /// diffuse rendering pass which may be different + /// from the actual camera position. + /// + /// This is useful when doing level of detail + /// calculations that need to be relative to the + /// diffuse pass. + /// + /// @see getCameraPosition + Point3F getDiffuseCameraPosition() const { return mDiffuseCameraTransform.getPosition(); } + const MatrixF& getDiffuseCameraTransform() const { return mDiffuseCameraTransform; } + + /// Set a new diffuse camera transform. + /// @see getDiffuseCameraTransform + void setDiffuseCameraTransform( const MatrixF &mat ) { mDiffuseCameraTransform = mat; } + + /// Returns the frustum. + const Frustum& getFrustum() const { return mFrustum; } + + /// Set a different frustum for culling. + void setFrustum( const Frustum &frustum ) { mFrustum = frustum; } + + /// Returns the base ZoneState. + /// + /// @see ZoneState + const ZoneState& getBaseZoneState() const; + + /// Returns the base ZoneState as a non-const reference. + /// + /// @see getBaseZoneState + ZoneState& getBaseZoneStateNC(); + + /// Returns the ZoneState for a particular zone ID. + /// + /// @param zoneId ZoneId + const ZoneState& getZoneState(const U32 zoneId) const; + + /// Returns the ZoneState for a particular zone ID as a non-const reference. + /// + /// @see getZoneState + /// @param zoneId ZoneId + ZoneState& getZoneStateNC(const U32 zoneId); + + /// Adds a new transform portal to the SceneState. + /// + /// @param owner SceneObject owner of the portal (portalized object). + /// @param portalIndex Index of the portal in the list of portal planes. + /// @param globalZone Index of the zone this portal is in in the list of ZoneStates. + /// @param traversalStartPoint Start point of the zone traversal. + /// @see SceneGraph::buildSceneTree + /// @see SceneGraph::findZone + /// + /// @param flipCull If true, the portal plane will be flipped + void insertTransformPortal(SceneObject* owner, U32 portalIndex, + U32 globalZone, const Point3F& traversalStartPoint, + const bool flipCull); + + /// This enables terrain to be drawn inside interiors. + void enableTerrainOverride(); + + /// Returns true if terrain is allowed to be drawn inside interiors. + bool isTerrainOverridden() const; + + /// Sorts the list of images, builds the translucency BSP tree, + /// sets up the portal, then renders all images in the state. + void renderCurrentImages(); + + /// Returns true if the object in question is going to be rendered + /// as opposed to being culled out. + /// + /// @param obj Object in question. + bool isObjectRendered(const SceneObject *obj); + + /// Returns true if the culling on this state has been flipped + bool isInvertedCull() const { return mFlipCull; } + + /// + void objectAlwaysRender( bool alwaysRender ) { mAlwaysRender = alwaysRender; } + + protected: + + Vector mZoneStates; ///< Collection of ZoneStates in the scene. + + /// Builds the BSP tree of translucent images. + void buildTranslucentBSP(); + + bool mTerrainOverride; ///< If true, terrain is allowed to render inside interiors + + Vector mSubsidiaries; ///< Transform portals which have been processed by the scene traversal process + /// + /// @note Closely related. Transform portals are turned into sorted mSubsidiaries + /// by the traversal process... + + Vector mTransformPortals; ///< Collection of TransformPortals + + ZoneState mBaseZoneState; ///< ZoneState of the base zone of the scene + + /// + MatrixF mDiffuseCameraTransform; + + /// The world to screen space scalar used for LOD calculations. + Point2F mWorldToScreenScale; + + /// The viewport extents used for LOD calculations. + /// @see + Point2I mViewportExtent; + + /// The camera frustum used in culling. + Frustum mFrustum; + + SceneState* mParent; + SceneGraph * mSceneManager; + LightManager* mLightManager; ///< This is used for portals, if this is not NULL, then this SceneState belongs to a portal, and not the main SceneState + + /// The type of scene render pass we're doing. + ScenePassType mScenePassType; + + SceneObject* mPortalOwner; ///< SceneObject which owns the current portal + U32 mPortalIndex; ///< Index the current portal is in the list of portals + + Vector< InteriorListElem > mInteriorList; + + bool mAlwaysRender; + + /// Forces bin based post effects to be disabled + /// during rendering with this scene state. + bool mUsePostEffects; + + bool mFlipCull; ///< If true the portal clipping plane will be reversed + + public: + bool mRenderLightmappedMeshes; ///< If true (default) lightmapped meshes should be rendered + bool mRenderNonLightmappedMeshes; ///< If true (default) non-lightmapped meshes should be rendered + + + void insertInterior( InteriorListElem &elem ) + { + mInteriorList.push_back( elem ); + } +}; + +inline F32 SceneState::projectRadius( F32 dist, F32 radius ) const +{ + // We fixup any negative or zero distance + // so we don't get a divide by zero. + dist = dist > 0.0f ? dist : 0.001f; + return ( radius / dist ) * mWorldToScreenScale.y; +} + +inline const SceneState::ZoneState& SceneState::getBaseZoneState() const +{ + return mBaseZoneState; +} + +inline SceneState::ZoneState& SceneState::getBaseZoneStateNC() +{ + return mBaseZoneState; +} + +inline const SceneState::ZoneState& SceneState::getZoneState(const U32 zoneId) const +{ + AssertFatal(zoneId < (U32)mZoneStates.size(), "Error, out of bounds zone!"); + return mZoneStates[zoneId]; +} + +inline SceneState::ZoneState& SceneState::getZoneStateNC(const U32 zoneId) +{ + AssertFatal(zoneId < (U32)mZoneStates.size(), "Error, out of bounds zone!"); + return mZoneStates[zoneId]; +} + +inline void SceneState::enableTerrainOverride() +{ + mTerrainOverride = true; +} + +inline bool SceneState::isTerrainOverridden() const +{ + return mTerrainOverride; +} + + +#endif // _H_SCENESTATE_ + diff --git a/sceneGraph/sceneTraversal.cpp b/sceneGraph/sceneTraversal.cpp new file mode 100644 index 0000000..418127c --- /dev/null +++ b/sceneGraph/sceneTraversal.cpp @@ -0,0 +1,409 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sceneGraph.h" +#include "sceneGraph/sgUtil.h" +#include "sceneGraph/sceneObject.h" +#include "sceneGraph/sceneState.h" +#include "math/mMatrix.h" +#include "terrain/terrData.h" +#include "gfx/gfxDevice.h" +#include "T3D/gameConnection.h" +#include "interior/interiorInstance.h" + +namespace { + +class PotentialRenderList +{ + +public: + + Box3F mBox; + Frustum mFrustum; + + SceneState *mState; + Vector mList; + + void insertObject(SceneObject* obj); + void setupClipPlanes(SceneState*); +}; + +// MM/JF: Added for mirrorSubObject fix. +void PotentialRenderList::setupClipPlanes(SceneState* state) +{ + mState = state; + + const F32 nearPlane = state->getNearPlane(); + const F32 farPlane = state->getFarPlane(); + + const SceneState::ZoneState &zoneState = state->getBaseZoneState(); + + mFrustum.set( zoneState.frustum.isOrtho(), + zoneState.frustum.getNearLeft(), + zoneState.frustum.getNearRight(), + zoneState.frustum.getNearTop(), + zoneState.frustum.getNearBottom(), + nearPlane, + farPlane, + state->getCameraTransform() ); + + // clip-planes through mirror portal are inverted + if ( mFrustum.isInverted() ) + mFrustum.invert(); + + mBox = mFrustum.getBounds(); +} + +void PotentialRenderList::insertObject(SceneObject* obj) +{ + // Check to see if we need to render always. + if ( obj->isGlobalBounds() || + mFrustum.intersects( obj->getZoneBox() ) ) + mList.push_back(obj); +} + +void prlInsertionCallback(SceneObject* obj, void *key) +{ + if ( !obj->isRenderEnabled() ) + return; + + PotentialRenderList* prList = (PotentialRenderList*)key; + prList->insertObject(obj); +} + +} // namespace {} + +void SceneGraph::_buildSceneTree( SceneState *state, + U32 objectMask, + SceneObject *baseObject, + U32 baseZone, + U32 currDepth ) +{ + AssertFatal(this == gClientSceneGraph, "Error, only the client scenegraph can support this call!"); + + // Find the start zone if we haven't already. + if ( !baseObject ) + { + findZone( state->getCameraPosition(), baseObject, baseZone ); + currDepth = 1; + } + + // Search proceeds from the baseObject, and starts in the baseZone. + // General Outline: + // - Traverse up the tree, stopping at either the root, or the last interior + // that prevents traversal outside + // - Query the container database for all objects intersecting the viewcone, + // which is clipped to the bounding box returned at the last stage of the + // above traversal. + // - Topo sort the returned objects. + // - Traverse through the list, calling setupZones on zone managers, + // and retreiving render images from all applicable objects (including + // ZM's) + // - This process may return "Transform portals", i.e., mirrors, rendered + // teleporters, etc. For each of these, create a new SceneState object + // subsidiary to state, and restart the traversal, with the new parameters, + // and the correct baseObject and baseZone. + + // Objects (in particular, those managers that are part of the initial up + // traversal) keep track of whether or not they have returned a render image + // to the current state by a key, and the state object pointer. + smStateKey++; + + // Save off the base state... + SceneState::ZoneState saveBase = state->getBaseZoneState(); + + SceneObject* pTraversalRoot = baseObject; + U32 rootZone = baseZone; + while (true) + { + if (pTraversalRoot->prepRenderImage(state, smStateKey, rootZone, true)) + { + if (pTraversalRoot->getNumCurrZones() != 1) + Con::errorf(ConsoleLogEntry::General, + "Error, must have one and only one zone to be a traversal root. %s has %d", + pTraversalRoot->getName(), pTraversalRoot->getNumCurrZones()); + + rootZone = pTraversalRoot->getCurrZone(0); + pTraversalRoot = getZoneOwner(rootZone); + } + else + { + break; + } + } + + // Restore the base state... + SceneState::ZoneState& rState = state->getBaseZoneStateNC(); + rState = saveBase; + + // Ok. Now we have renderimages for anything north of the object in the + // tree. Create the query polytope, and clip it to the bounding box of + // the traversalRoot object. + PotentialRenderList prl; + prl.setupClipPlanes(state); + + // We only have to clip the mBox field + AssertFatal(prl.mBox.isOverlapped(pTraversalRoot->getZoneBox()), + "Error, prl box must overlap the traversal root"); + prl.mBox.minExtents.setMax(pTraversalRoot->getZoneBox().minExtents); + prl.mBox.maxExtents.setMin(pTraversalRoot->getZoneBox().maxExtents); + prl.mBox.minExtents -= Point3F(5, 5, 5); + prl.mBox.maxExtents += Point3F(5, 5, 5); + AssertFatal(prl.mBox.isValidBox(), "Error, invalid query box created!"); + + // Query against the container database, storing the objects in the + // potentially rendered list. Note: we can query against the client + // container without testing, since only the client will be calling this + // function. This is assured by the assert at the top... + gClientContainer.findObjects(prl.mBox, objectMask, prlInsertionCallback, &prl); + + // Clear the object colors + U32 i; + for (i = 0; i < prl.mList.size(); i++) + prl.mList[i]->setTraversalState( SceneObject::Pending ); + + // If the connection's control object got culled, but we're in first person, + // add it back in. This happens when the eye node travels outside the object's + // bounding box. + + GameConnection* serverConnection = GameConnection::getConnectionToServer(); + if( serverConnection->isFirstPerson() ) + { + GameBase* controlObject = serverConnection->getControlObject(); + if( controlObject && controlObject->getTraversalState() != SceneObject::Pending ) + { + prl.mList.push_back( controlObject ); + controlObject->setTraversalState( SceneObject::Pending ); + } + } + + for (i = 0; i < prl.mList.size(); i++) + if( prl.mList[i]->getTraversalState() == SceneObject::Pending ) + treeTraverseVisit(prl.mList[i], state, smStateKey); + + if ( currDepth < csmMaxTraversalDepth && + state->mTransformPortals.size() != 0 ) + { + Frustum tempFrustum; + + // Need to handle the transform portals here. + // + for (U32 i = 0; i < state->mTransformPortals.size(); i++) { + const SceneState::TransformPortal& rPortal = state->mTransformPortals[i]; + const SceneState::ZoneState& rPZState = state->getZoneState(rPortal.globalZone); + AssertFatal(rPZState.render == true, "Error, should not have returned a portal if the zone isn't rendering!"); + + Point3F cameraPosition = state->getCameraPosition(); + rPortal.owner->transformPosition(rPortal.portalIndex, cameraPosition); + + // Setup the new modelview matrix... + MatrixF oldMV = GFX->getWorldMatrix(); + MatrixF newMV; + rPortal.owner->transformModelview(rPortal.portalIndex, oldMV, &newMV); + + // Here's the tricky bit. We have to derive a new frustum and viewport + // from the portal, but we have to do it in the NEW coordinate space. + // Seems easiest to dump the responsibility on the object that was rude + // enough to make us go to all this trouble... + F64 newFrustum[4]; + RectI newViewport; + + bool goodPortal = rPortal.owner->computeNewFrustum(rPortal.portalIndex, // which portal? + rPZState.frustum, // old view params + state->getNearPlane(), + state->getFarPlane(), + rPZState.viewport, + newFrustum, // new view params + newViewport, + state->mFlipCull); + + if (goodPortal == false) + { + // Portal isn't visible, or is clipped out by the zone parameters... + continue; + } + + tempFrustum.set( false, newFrustum[0], newFrustum[1], newFrustum[3], newFrustum[2], state->getNearPlane(), state->getFarPlane(), newMV ); + + SceneState* newState = new SceneState( state, + this, + state->getScenePassType(), + mCurrZoneEnd, + tempFrustum, + newViewport, + state->usePostEffects(), + state->mFlipCull ^ rPortal.flipCull ); + + newState->setPortal(rPortal.owner, rPortal.portalIndex); + + GFX->pushWorldMatrix(); + + GFX->setWorldMatrix( newMV ); + + // Find the start zone. Note that in a traversal descent, we start from + // the traversePoint of the transform portal, which is conveniently in + // world space... + SceneObject* startObject; + U32 startZone; + findZone(rPortal.traverseStart, startObject, startZone); + + _buildSceneTree(newState, objectMask, startObject, startZone, currDepth + 1 ); + + // Pop off the new modelview + GFX->popWorldMatrix(); + + // Push the subsidiary... + state->mSubsidiaries.push_back(newState); + } + } + + // Ok, that's it! +} + +bool terrCheck(TerrainBlock* pBlock, + SceneObject* pObj, + const Point3F camPos); + +void SceneGraph::treeTraverseVisit(SceneObject* obj, + SceneState* state, + const U32 stateKey) +{ + if (obj->getNumCurrZones() == 0) + { + obj->setTraversalState( SceneObject::Done ); + return; + } + + PROFILE_START(treeTraverseVisit); + + AssertFatal(obj->getTraversalState() == SceneObject::Pending, + "Wrong state for this stage of the traversal!"); + obj->setTraversalState(SceneObject::Working); // TraversalState Not being updated correctly 'Gonzo' + + SceneObjectRef* pWalk = obj->mZoneRefHead; + AssertFatal(pWalk != NULL, "Error, must belong to something!"); + while (pWalk) + { + // Determine who owns this zone... + SceneObject* pOwner = getZoneOwner(pWalk->zone); + if( pOwner->getTraversalState() == SceneObject::Pending ) + treeTraverseVisit(pOwner, state, stateKey); + + pWalk = pWalk->nextInObj; + } + + obj->setTraversalState( SceneObject::Done ); + + // Cull it, but not if it's too low or there's no terrain to occlude against, or if it's global... + if (getCurrentTerrain() != NULL && obj->getZoneBox().minExtents.x > -1e5 && !obj->isGlobalBounds()) + { + bool doTerrCheck = true; + SceneObjectRef* pRef = obj->mZoneRefHead; + while (pRef != NULL) + { + if (pRef->zone != 0) + { + doTerrCheck = false; + break; + } + pRef = pRef->nextInObj; + } + + if (doTerrCheck == true && terrCheck(getCurrentTerrain(), obj, state->getCameraPosition()) == true) + { + PROFILE_END(); + return; + } + } + + PROFILE_START(treeTraverseVisit_prepRenderImage); + obj->prepRenderImage(state, stateKey, 0xFFFFFFFF); + PROFILE_END(); + + PROFILE_END(); +} + +bool terrCheck(TerrainBlock* pBlock, + SceneObject* pObj, + const Point3F camPos) +{ + PROFILE_START(terrCheck); + + // Don't try to occlude globally bounded objects. + if(pObj->isGlobalBounds()) + { + PROFILE_END(); + return false; + } + + Point3F localCamPos = camPos; + pBlock->getWorldTransform().mulP(localCamPos); + F32 height; + pBlock->getHeight(Point2F(localCamPos.x, localCamPos.y), &height); + bool aboveTerrain = (height <= localCamPos.z); + + // Don't occlude if we're below the terrain. This prevents problems when + // looking out from underground bases... + if (aboveTerrain == false) + { + PROFILE_END(); + return false; + } + + const Box3F& oBox = pObj->getObjBox(); + F32 minSide = getMin(oBox.len_x(), oBox.len_y()); + if (minSide > 85.0f) + { + PROFILE_END(); + return false; + } + + const Box3F& rBox = pObj->getWorldBox(); + Point3F ul(rBox.minExtents.x, rBox.minExtents.y, rBox.maxExtents.z); + Point3F ur(rBox.minExtents.x, rBox.maxExtents.y, rBox.maxExtents.z); + Point3F ll(rBox.maxExtents.x, rBox.minExtents.y, rBox.maxExtents.z); + Point3F lr(rBox.maxExtents.x, rBox.maxExtents.y, rBox.maxExtents.z); + + pBlock->getWorldTransform().mulP(ul); + pBlock->getWorldTransform().mulP(ur); + pBlock->getWorldTransform().mulP(ll); + pBlock->getWorldTransform().mulP(lr); + + Point3F xBaseL0_s = ul - localCamPos; + Point3F xBaseL0_e = lr - localCamPos; + Point3F xBaseL1_s = ur - localCamPos; + Point3F xBaseL1_e = ll - localCamPos; + + static F32 checkPoints[3] = {0.75, 0.5, 0.25}; + RayInfo rinfo; + for (U32 i = 0; i < 3; i++) + { + Point3F start = (xBaseL0_s * checkPoints[i]) + localCamPos; + Point3F end = (xBaseL0_e * checkPoints[i]) + localCamPos; + + if (pBlock->castRay(start, end, &rinfo)) + continue; + + pBlock->getHeight(Point2F(start.x, start.y), &height); + if ((height <= start.z) == aboveTerrain) + continue; + + start = (xBaseL1_s * checkPoints[i]) + localCamPos; + end = (xBaseL1_e * checkPoints[i]) + localCamPos; + + if (pBlock->castRay(start, end, &rinfo)) + continue; + + Point3F test = (start + end) * 0.5; + if (pBlock->castRay(localCamPos, test, &rinfo) == false) + continue; + + PROFILE_END(); + return true; + } + + PROFILE_END(); + return false; +} diff --git a/sceneGraph/sgUtil.cpp b/sceneGraph/sgUtil.cpp new file mode 100644 index 0000000..0dcf374 --- /dev/null +++ b/sceneGraph/sgUtil.cpp @@ -0,0 +1,310 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/sgUtil.h" +#include "math/mRect.h" +#include "math/mMatrix.h" +#include "math/mPlane.h" + +namespace { + +// Static state for sgComputeNewFrustum +// +Point3F sgCamPoint; +MatrixF sgWSToOSMatrix; +MatrixF sgProjMatrix; +PlaneF sgOSPlaneFar; +PlaneF sgOSPlaneXMin; +PlaneF sgOSPlaneXMax; +PlaneF sgOSPlaneYMin; +PlaneF sgOSPlaneYMax; + + +void clipToPlane(Point3F* points, U32& rNumPoints, const PlaneF& rPlane) +{ + S32 start = -1; + for (U32 i = 0; i < rNumPoints; i++) { + if (rPlane.whichSide(points[i]) == PlaneF::Front) { + start = i; + break; + } + } + + // Nothing was in front of the plane... + if (start == -1) { + rNumPoints = 0; + return; + } + + Point3F finalPoints[128]; + U32 numFinalPoints = 0; + + U32 baseStart = start; + U32 end = (start + 1) % rNumPoints; + + while (end != baseStart) { + const Point3F& rStartPoint = points[start]; + const Point3F& rEndPoint = points[end]; + + PlaneF::Side fSide = rPlane.whichSide(rStartPoint); + PlaneF::Side eSide = rPlane.whichSide(rEndPoint); + + S32 code = fSide * 3 + eSide; + switch (code) { + case 4: // f f + case 3: // f o + case 1: // o f + case 0: // o o + // No Clipping required + finalPoints[numFinalPoints++] = points[start]; + start = end; + end = (end + 1) % rNumPoints; + break; + + + case 2: { // f b + // In this case, we emit the front point, Insert the intersection, + // and advancing to point to first point that is in front or on... + // + finalPoints[numFinalPoints++] = points[start]; + + Point3F vector = rEndPoint - rStartPoint; + F32 t = -(rPlane.distToPlane(rStartPoint) / mDot(rPlane, vector)); + + Point3F intersection = rStartPoint + (vector * t); + finalPoints[numFinalPoints++] = intersection; + + U32 endSeek = (end + 1) % rNumPoints; + while (rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + vector = rNewEndPoint - rNewStartPoint; + t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + } + break; + + case -1: {// o b + // In this case, we emit the front point, and advance to point to first + // point that is in front or on... + // + finalPoints[numFinalPoints++] = points[start]; + + U32 endSeek = (end + 1) % rNumPoints; + while (rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + Point3F vector = rNewEndPoint - rNewStartPoint; + F32 t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + Point3F intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + } + break; + + case -2: // b f + case -3: // b o + case -4: // b b + // In the algorithm used here, this should never happen... + AssertISV(false, "SGUtil::clipToPlane: error in polygon clipper"); + break; + + default: + AssertFatal(false, "SGUtil::clipToPlane: bad outcode"); + break; + } + + } + + // Emit the last point. + finalPoints[numFinalPoints++] = points[start]; + AssertFatal(numFinalPoints >= 3, avar("Error, this shouldn't happen! Invalid winding in clipToPlane: %d", numFinalPoints)); + + // Copy the new rWinding, and we're set! + // + dMemcpy(points, finalPoints, numFinalPoints * sizeof(Point3F)); + rNumPoints = numFinalPoints; + AssertISV(rNumPoints <= 128, "MaxWindingPoints exceeded in scenegraph. Fatal error."); +} + + +void fixupViewport(const F64* oldFrustum, + const RectI& oldViewport, + F64* newFrustum, + RectI& newViewport) +{ + F64 widthV = newFrustum[1] - newFrustum[0]; + F64 heightV = newFrustum[3] - newFrustum[2]; + + F64 fx0 = (newFrustum[0] - oldFrustum[0]) / (oldFrustum[1] - oldFrustum[0]); + F64 fx1 = (oldFrustum[1] - newFrustum[1]) / (oldFrustum[1] - oldFrustum[0]); + + F64 dV0 = F64(oldViewport.point.x) + fx0 * F64(oldViewport.extent.x); + F64 dV1 = F64(oldViewport.point.x + + oldViewport.extent.x) - fx1 * F64(oldViewport.extent.x); + + F64 fdV0 = mFloor(dV0); + F64 cdV1 = mCeil(dV1); + + F64 new0 = newFrustum[0] - ((dV0 - fdV0) * (widthV / F64(oldViewport.extent.x))); + F64 new1 = newFrustum[1] + ((cdV1 - dV1) * (widthV / F64(oldViewport.extent.x))); + + newFrustum[0] = new0; + newFrustum[1] = new1; + + newViewport.point.x = S32(fdV0); + newViewport.extent.x = S32(cdV1) - newViewport.point.x; + + F64 fy0 = (oldFrustum[3] - newFrustum[3]) / (oldFrustum[3] - oldFrustum[2]); + F64 fy1 = (newFrustum[2] - oldFrustum[2]) / (oldFrustum[3] - oldFrustum[2]); + + dV0 = F64(oldViewport.point.y) + fy0 * F64(oldViewport.extent.y); + dV1 = F64(oldViewport.point.y + oldViewport.extent.y) - fy1 * F64(oldViewport.extent.y); + fdV0 = mFloor(dV0); + cdV1 = mCeil(dV1); + + new0 = newFrustum[2] - ((cdV1 - dV1) * (heightV / F64(oldViewport.extent.y))); + new1 = newFrustum[3] + ((dV0 - fdV0) * (heightV / F64(oldViewport.extent.y))); + newFrustum[2] = new0; + newFrustum[3] = new1; + + newViewport.point.y = S32(fdV0); + newViewport.extent.y = S32(cdV1) - newViewport.point.y; +} + +bool projectClipAndBoundWinding(const SGWinding& rWinding, F64* pResult) +{ + AssertFatal(rWinding.numPoints >= 3, "Error, that's not a winding!"); + + static Point3F windingPoints[128]; + U32 i; + for (i = 0; i < rWinding.numPoints; i++) + windingPoints[i] = rWinding.points[i]; + U32 numPoints = rWinding.numPoints; + + clipToPlane(windingPoints, numPoints, sgOSPlaneFar); + if (numPoints != 0) + clipToPlane(windingPoints, numPoints, sgOSPlaneXMin); + if (numPoints != 0) + clipToPlane(windingPoints, numPoints, sgOSPlaneXMax); + if (numPoints != 0) + clipToPlane(windingPoints, numPoints, sgOSPlaneYMin); + if (numPoints != 0) + clipToPlane(windingPoints, numPoints, sgOSPlaneYMax); + + if (numPoints == 0) + return false; + + Point4F projPoint; + for (i = 0; i < numPoints; i++) { + projPoint.set(windingPoints[i].x, windingPoints[i].y, windingPoints[i].z, 1.0); + sgProjMatrix.mul(projPoint); + + AssertFatal(projPoint.w != 0.0, "Error, that's bad! (Point projected with non-zero w.)"); + projPoint.x /= projPoint.w; + projPoint.y /= projPoint.w; + + if (projPoint.x < pResult[0]) + pResult[0] = projPoint.x; + if (projPoint.x > pResult[1]) + pResult[1] = projPoint.x; + if (projPoint.y < pResult[2]) + pResult[2] = projPoint.y; + if (projPoint.y > pResult[3]) + pResult[3] = projPoint.y; + } + + if (pResult[0] < -1.0f) pResult[0] = -1.0f; + if (pResult[2] < -1.0f) pResult[2] = -1.0f; + if (pResult[1] > 1.0f) pResult[1] = 1.0f; + if (pResult[3] > 1.0f) pResult[3] = 1.0f; + + return true; +} + +} // namespace { } + + +//-------------------------------------------------------------------------- +bool sgComputeNewFrustum(const Frustum &oldFrustum, + const F64 nearPlane, + const F64 farPlane, + const RectI& oldViewport, + const SGWinding* windings, + const U32 numWindings, + const MatrixF& modelview, + F64 *newFrustum, + RectI& newViewport, + const bool flippedMatrix) +{ + return false; +} + + +void sgComputeOSFrustumPlanes(const F64 frustumParameters[6], + const MatrixF& worldSpaceToObjectSpace, + const Point3F& wsCamPoint, + PlaneF& outFarPlane, + PlaneF& outXMinPlane, + PlaneF& outXMaxPlane, + PlaneF& outYMinPlane, + PlaneF& outYMaxPlane) +{ + // Create the object space clipping planes... + Point3F ul(frustumParameters[0] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[3] * 1000.0); + Point3F ur(frustumParameters[1] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[3] * 1000.0); + Point3F ll(frustumParameters[0] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[2] * 1000.0); + Point3F lr(frustumParameters[1] * 1000.0, frustumParameters[4] * 1000.0, frustumParameters[2] * 1000.0); + Point3F farPlane(0, frustumParameters[5], 0); + + worldSpaceToObjectSpace.mulP(ul); + worldSpaceToObjectSpace.mulP(ur); + worldSpaceToObjectSpace.mulP(ll); + worldSpaceToObjectSpace.mulP(lr); + worldSpaceToObjectSpace.mulP(farPlane); + + outFarPlane.set(farPlane, wsCamPoint - farPlane); + outXMinPlane.set(wsCamPoint, ul, ll); + outXMaxPlane.set(wsCamPoint, lr, ur); + outYMinPlane.set(wsCamPoint, ur, ul); + outYMaxPlane.set(wsCamPoint, ll, lr); +} + +// MM/JF: Added for mirrorSubObject fix. +void sgOrientClipPlanes( + PlaneF * planes, + const Point3F & camPos, + const Point3F & leftUp, + const Point3F & leftDown, + const Point3F & rightUp, + const Point3F & rightDown) +{ + AssertFatal(planes, "orientClipPlanes: NULL planes ptr"); + planes[0].set(camPos, leftUp, leftDown); + planes[1].set(camPos, rightUp, leftUp); + planes[2].set(camPos, rightDown, rightUp); + planes[3].set(camPos, leftDown, rightDown); + planes[4].set(leftUp, rightUp, rightDown); + + // clip-planes through mirror portal are inverted + PlaneF plane(leftUp, rightUp, rightDown); + if(plane.whichSide(camPos) == PlaneF::Back) + for(U32 i = 0; i < 5; i++) + planes[i].invert(); +} diff --git a/sceneGraph/sgUtil.h b/sceneGraph/sgUtil.h new file mode 100644 index 0000000..315b95a --- /dev/null +++ b/sceneGraph/sgUtil.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SGUTIL_H_ +#define _SGUTIL_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif + +class Frustum; +class RectI; +class MatrixF; +class PlaneF; + +struct SGWinding +{ + Point3F points[32]; + U32 numPoints; +}; + +bool sgComputeNewFrustum(const Frustum &oldFrustum, + const F64 nearPlane, + const F64 farPlane, + const RectI& oldViewport, + const SGWinding* windings, + const U32 numWindings, + const MatrixF& modelview, + F64 *newFrustum, + RectI& newViewport, + const bool flippedMatrix); + +/// Compute frustrum planes. +/// +/// Frustum parameters are: +/// - [0] = left +/// - [1] = right +/// - [2] = top +/// - [3] = bottom +/// - [4] = near +/// - [5] = far +void sgComputeOSFrustumPlanes(const F64 frustumParameters[6], + const MatrixF& worldSpaceToObjectSpace, + const Point3F& wsCamPoint, + PlaneF& outFarPlane, + PlaneF& outXMinPlane, + PlaneF& outXMaxPlane, + PlaneF& outYMinPlane, + PlaneF& outYMaxPlane); + +void sgOrientClipPlanes(PlaneF * planes, const Point3F & camPos, const Point3F & leftUp, const Point3F & leftDown, const Point3F & rightUp, const Point3F & rightDown); + +#endif // _H_SGUTIL_ diff --git a/sceneGraph/simPath.cpp b/sceneGraph/simPath.cpp new file mode 100644 index 0000000..6d97ad7 --- /dev/null +++ b/sceneGraph/simPath.cpp @@ -0,0 +1,434 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "gfx/gfxDevice.h" +#include "gfx/gfxVertexBuffer.h" +#include "gfx/gfxPrimitiveBuffer.h" +#include "gfx/gfxTransformSaver.h" +#include "sceneGraph/simPath.h" +#include "console/consoleTypes.h" +#include "sceneGraph/pathManager.h" +#include "sceneGraph/sceneState.h" +#include "math/mathIO.h" +#include "core/stream/bitStream.h" +#include "renderInstance/renderPassManager.h" + +extern bool gEditingMission; + +//-------------------------------------------------------------------------- +//-------------------------------------- Console functions and cmp funcs +// +ConsoleFunction( pathOnMissionLoadDone, void, 1, 1, "Load all path information from interiors.") +{ + // Need to load subobjects for all loaded interiors... + SimGroup* pMissionGroup = dynamic_cast(Sim::findObject("MissionGroup")); + AssertFatal(pMissionGroup != NULL, "Error, mission done loading and no mission group?"); + + U32 currStart = 0; + U32 currEnd = 1; + Vector groups; + groups.push_back(pMissionGroup); + + while (true) { + for (U32 i = currStart; i < currEnd; i++) { + for (SimGroup::iterator itr = groups[i]->begin(); itr != groups[i]->end(); itr++) { + if (dynamic_cast(*itr) != NULL) + groups.push_back(static_cast(*itr)); + } + } + + if (groups.size() == currEnd) { + break; + } else { + currStart = currEnd; + currEnd = groups.size(); + } + } + + for (U32 i = 0; i < groups.size(); i++) { + SimPath::Path* pPath = dynamic_cast(groups[i]); + if (pPath) + pPath->updatePath(); + } +} + +S32 FN_CDECL cmpPathObject(const void* p1, const void* p2) +{ + SimObject* o1 = *((SimObject**)p1); + SimObject* o2 = *((SimObject**)p2); + + Marker* m1 = dynamic_cast(o1); + Marker* m2 = dynamic_cast(o2); + + if (m1 == NULL && m2 == NULL) + return 0; + else if (m1 != NULL && m2 == NULL) + return 1; + else if (m1 == NULL && m2 != NULL) + return -1; + else { + // Both markers... + return S32(m1->mSeqNum) - S32(m2->mSeqNum); + } +} + +namespace SimPath +{ + +//-------------------------------------------------------------------------- +//-------------------------------------- Implementation +// +IMPLEMENT_CONOBJECT(Path); + +Path::Path() +{ + mPathIndex = NoPathIndex; + mIsLooping = true; +} + +Path::~Path() +{ + // +} + +//-------------------------------------------------------------------------- +void Path::initPersistFields() +{ + addField("isLooping", TypeBool, Offset(mIsLooping, Path)); + + Parent::initPersistFields(); + // +} + + + +//-------------------------------------------------------------------------- +bool Path::onAdd() +{ + if(!Parent::onAdd()) + return false; + + return true; +} + + +void Path::onRemove() +{ + // + + Parent::onRemove(); +} + + + +//-------------------------------------------------------------------------- +/// Sort the markers objects into sequence order +void Path::sortMarkers() +{ + dQsort(objectList.address(), objectList.size(), sizeof(SimObject*), cmpPathObject); +} + +void Path::updatePath() +{ + // If we need to, allocate a path index from the manager + if (mPathIndex == NoPathIndex) + mPathIndex = gServerPathManager->allocatePathId(); + + sortMarkers(); + + Vector positions; + Vector rotations; + Vector times; + Vector smoothingTypes; + + for (iterator itr = begin(); itr != end(); itr++) + { + Marker* pMarker = dynamic_cast(*itr); + if (pMarker != NULL) + { + Point3F pos; + pMarker->getTransform().getColumn(3, &pos); + positions.push_back(pos); + + QuatF rot; + rot.set(pMarker->getTransform()); + rotations.push_back(rot); + + times.push_back(pMarker->mMSToNext); + smoothingTypes.push_back(pMarker->mSmoothingType); + } + } + + // DMMTODO: Looping paths. + gServerPathManager->updatePath(mPathIndex, positions, rotations, times, smoothingTypes); +} + +void Path::addObject(SimObject* obj) +{ + Parent::addObject(obj); + + if (mPathIndex != NoPathIndex) { + // If we're already finished, and this object is a marker, then we need to + // update our path information... + if (dynamic_cast(obj) != NULL) + updatePath(); + } +} + +void Path::removeObject(SimObject* obj) +{ + bool recalc = dynamic_cast(obj) != NULL; + + Parent::removeObject(obj); + + if (mPathIndex != NoPathIndex && recalc == true) + updatePath(); +} + +ConsoleMethod(Path, getPathId, S32, 2, 2, "getPathId();") +{ + Path *path = static_cast(object); + return path->getPathIndex(); +} + +} // Namespace + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- + +GFXStateBlockRef Marker::smStateBlock; +GFXVertexBufferHandle Marker::smVertexBuffer; +GFXPrimitiveBufferHandle Marker::smPrimitiveBuffer; + +static Point3F wedgePoints[4] = { + Point3F(-1, -1, 0), + Point3F( 0, 1, 0), + Point3F( 1, -1, 0), + Point3F( 0,-.75, .5), +}; + +void Marker::initGFXResources() +{ + if(smVertexBuffer != NULL) + return; + + GFXStateBlockDesc d; + d.cullDefined = true; + d.cullMode = GFXCullNone; + + smStateBlock = GFX->createStateBlock(d); + + smVertexBuffer.set(GFX, 4, GFXBufferTypeStatic); + GFXVertexPC* verts = smVertexBuffer.lock(); + verts[0].point = wedgePoints[0] * 0.25f; + verts[1].point = wedgePoints[1] * 0.25f; + verts[2].point = wedgePoints[2] * 0.25f; + verts[3].point = wedgePoints[3] * 0.25f; + verts[0].color = verts[1].color = verts[2].color = verts[3].color = GFXVertexColor(ColorI(0, 255, 0, 255)); + smVertexBuffer.unlock(); + + smPrimitiveBuffer.set(GFX, 24, 12, GFXBufferTypeStatic); + U16* prims; + smPrimitiveBuffer.lock(&prims); + prims[0] = 0; + prims[1] = 3; + prims[2] = 3; + prims[3] = 1; + prims[4] = 1; + prims[5] = 0; + + prims[6] = 3; + prims[7] = 1; + prims[8] = 1; + prims[9] = 2; + prims[10] = 2; + prims[11] = 3; + + prims[12] = 0; + prims[13] = 3; + prims[14] = 3; + prims[15] = 2; + prims[16] = 2; + prims[17] = 0; + + prims[18] = 0; + prims[19] = 2; + prims[20] = 2; + prims[21] = 1; + prims[22] = 1; + prims[23] = 0; + smPrimitiveBuffer.unlock(); +} + +IMPLEMENT_CO_NETOBJECT_V1(Marker); +Marker::Marker() +{ + // Not ghostable unless we're editing... + mNetFlags.clear(Ghostable); + + mTypeMask = MarkerObjectType; + + mSeqNum = 0; + mSmoothingType = SmoothingTypeLinear; + mMSToNext = 1000; + mSmoothingType = SmoothingTypeSpline; + mKnotType = KnotTypeNormal; +} + +Marker::~Marker() +{ + // +} + +//-------------------------------------------------------------------------- +static EnumTable::Enums markerEnums[] = +{ + { Marker::SmoothingTypeSpline , "Spline" }, + { Marker::SmoothingTypeLinear , "Linear" }, + //{ Marker::SmoothingTypeAccelerate , "Accelerate" }, +}; +static EnumTable markerSmoothingTable(2, &markerEnums[0]); + +static EnumTable::Enums knotEnums[] = +{ + { Marker::KnotTypeNormal , "Normal" }, + { Marker::KnotTypePositionOnly, "Position Only" }, + { Marker::KnotTypeKink, "Kink" }, +}; +static EnumTable markerKnotTable(3, &knotEnums[0]); + + +void Marker::initPersistFields() +{ + addField("seqNum", TypeS32, Offset(mSeqNum, Marker)); + addField("type", TypeEnum, Offset(mKnotType, Marker), 1, &markerKnotTable); + addField("msToNext", TypeS32, Offset(mMSToNext, Marker)); + addField("smoothingType", TypeEnum, Offset(mSmoothingType, Marker), 1, &markerSmoothingTable); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +//-------------------------------------------------------------------------- +bool Marker::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox = Box3F(Point3F(-.25, -.25, -.25), Point3F(.25, .25, .25)); + resetWorldBox(); + + if(gEditingMission) + onEditorEnable(); + + return true; +} + + +void Marker::onRemove() +{ + if(gEditingMission) + onEditorDisable(); + + Parent::onRemove(); + + smVertexBuffer = NULL; + smPrimitiveBuffer = NULL; +} + +void Marker::onGroupAdd() +{ + mSeqNum = getGroup()->size(); +} + + +/// Enable scoping so we can see this thing on the client. +void Marker::onEditorEnable() +{ + mNetFlags.set(Ghostable); + setScopeAlways(); + addToScene(); +} + +/// Disable scoping so we can see this thing on the client +void Marker::onEditorDisable() +{ + removeFromScene(); + mNetFlags.clear(Ghostable); + clearScopeAlways(); +} + + +/// Tell our parent that this Path has been modified +void Marker::inspectPostApply() +{ + SimPath::Path *path = dynamic_cast(getGroup()); + if (path) + path->updatePath(); +} + + +//-------------------------------------------------------------------------- +bool Marker::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + if (isLastState(state, stateKey)) + return false; + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + if (state->isObjectRendered(this)) { + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &Marker::renderObject ); + ri->type = RenderPassManager::RIT_Object; + state->getRenderPass()->addInst(ri); + } + + return false; +} + + +void Marker::renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat) +{ + initGFXResources(); + + for(U32 i = 0; i < GFX->getNumSamplers(); i++) + GFX->setTexture(i, NULL); + GFXTransformSaver saver; + MatrixF mat = getRenderTransform(); + mat.scale(mObjScale); + GFX->multWorld(mat); + + GFX->setStateBlock(smStateBlock); + GFX->setVertexBuffer(smVertexBuffer); + GFX->setPrimitiveBuffer(smPrimitiveBuffer); + GFX->drawIndexedPrimitive(GFXLineList, 0, 0, 4, 0, 12); +} + + +//-------------------------------------------------------------------------- +U32 Marker::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + // Note that we don't really care about efficiency here, since this is an + // edit-only ghost... + stream->writeAffineTransform(mObjToWorld); + + return retMask; +} + +void Marker::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + // Transform + MatrixF otow; + stream->readAffineTransform(&otow); + + setTransform(otow); +} \ No newline at end of file diff --git a/sceneGraph/simPath.h b/sceneGraph/simPath.h new file mode 100644 index 0000000..7565bf9 --- /dev/null +++ b/sceneGraph/simPath.h @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SIMPATH_H_ +#define _SIMPATH_H_ + +#ifndef _SCENEOBJECT_H_ + #include "sceneGraph/sceneObject.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ + #include "gfx/gfxStateBlock.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ + #include "gfx/gfxVertexBuffer.h" +#endif + + +namespace SimPath +{ + +//-------------------------------------------------------------------------- +/// A path! +class Path : public SimGroup +{ + typedef SimGroup Parent; + + public: + enum { + NoPathIndex = 0xFFFFFFFF + }; + + + private: + U32 mPathIndex; + bool mIsLooping; + + protected: + bool onAdd(); + void onRemove(); + + public: + Path(); + ~Path(); + + void addObject(SimObject*); + void removeObject(SimObject*); + + void sortMarkers(); + void updatePath(); + bool isLooping() { return mIsLooping; } + U32 getPathIndex() const; + + DECLARE_CONOBJECT(Path); + static void initPersistFields(); +}; + +//-------------------------------------------------------------------------- +//-------------------------------------------------------------------------- +inline U32 Path::getPathIndex() const +{ + return mPathIndex; +} + +} // Namespace + + +//-------------------------------------------------------------------------- +class Marker : public SceneObject +{ + typedef SceneObject Parent; + friend class Path; + + public: + enum { + SmoothingTypeLinear, + SmoothingTypeSpline, + SmoothingTypeAccelerate, + }; + + enum { + KnotTypeNormal, + KnotTypePositionOnly, + KnotTypeKink, + }; + + + U32 mSeqNum; + U32 mSmoothingType; + U32 mKnotType; + + U32 mMSToNext; + + // Rendering + protected: + bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState); + void renderObject(ObjectRenderInst *ri, SceneState *state, BaseMatInstance* overrideMat); + + protected: + bool onAdd(); + void onRemove(); + void onGroupAdd(); + + void onEditorEnable(); + void onEditorDisable(); + + static void initGFXResources(); + + static GFXStateBlockRef smStateBlock; + static GFXVertexBufferHandle smVertexBuffer; + static GFXPrimitiveBufferHandle smPrimitiveBuffer; + + public: + Marker(); + ~Marker(); + + DECLARE_CONOBJECT(Marker); + static void initPersistFields(); + void inspectPostApply(); + + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); +}; + +#endif // _H_PATH + diff --git a/sceneGraph/windingClipper.cpp b/sceneGraph/windingClipper.cpp new file mode 100644 index 0000000..b29656e --- /dev/null +++ b/sceneGraph/windingClipper.cpp @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sceneGraph/windingClipper.h" + +void sgUtil_clipToPlane(Point3F* points, U32& rNumPoints, const PlaneF& rPlane) +{ + S32 start = -1; + for (U32 i = 0; i < rNumPoints; i++) { + if (rPlane.whichSide(points[i]) == PlaneF::Front) { + start = i; + break; + } + } + + // Nothing was in front of the plane... + if (start == -1) { + rNumPoints = 0; + return; + } + + Point3F finalPoints[128]; + U32 numFinalPoints = 0; + + U32 baseStart = start; + U32 end = (start + 1) % rNumPoints; + + while (end != baseStart) { + const Point3F& rStartPoint = points[start]; + const Point3F& rEndPoint = points[end]; + + PlaneF::Side fSide = rPlane.whichSide(rStartPoint); + PlaneF::Side eSide = rPlane.whichSide(rEndPoint); + + S32 code = fSide * 3 + eSide; + switch (code) { + case 4: // f f + case 3: // f o + case 1: // o f + case 0: // o o + // No Clipping required + finalPoints[numFinalPoints++] = points[start]; + start = end; + end = (end + 1) % rNumPoints; + break; + + + case 2: { // f b + // In this case, we emit the front point, Insert the intersection, + // and advancing to point to first point that is in front or on... + // + finalPoints[numFinalPoints++] = points[start]; + + Point3F vector = rEndPoint - rStartPoint; + F32 t = -(rPlane.distToPlane(rStartPoint) / mDot(rPlane, vector)); + + Point3F intersection = rStartPoint + (vector * t); + finalPoints[numFinalPoints++] = intersection; + + U32 endSeek = (end + 1) % rNumPoints; + while (rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + vector = rNewEndPoint - rNewStartPoint; + t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + } + break; + + case -1: {// o b + // In this case, we emit the front point, and advance to point to first + // point that is in front or on... + // + finalPoints[numFinalPoints++] = points[start]; + + U32 endSeek = (end + 1) % rNumPoints; + while (rPlane.whichSide(points[endSeek]) == PlaneF::Back) + endSeek = (endSeek + 1) % rNumPoints; + + end = endSeek; + start = (end + (rNumPoints - 1)) % rNumPoints; + + const Point3F& rNewStartPoint = points[start]; + const Point3F& rNewEndPoint = points[end]; + + Point3F vector = rNewEndPoint - rNewStartPoint; + F32 t = -(rPlane.distToPlane(rNewStartPoint) / mDot(rPlane, vector)); + + Point3F intersection = rNewStartPoint + (vector * t); + points[start] = intersection; + } + break; + + case -2: // b f + case -3: // b o + case -4: // b b + // In the algorithm used here, this should never happen... + AssertISV(false, "SGUtil::clipToPlane: error in polygon clipper"); + break; + + default: + AssertFatal(false, "SGUtil::clipToPlane: bad outcode"); + break; + } + + } + + // Emit the last point. + finalPoints[numFinalPoints++] = points[start]; + AssertFatal(numFinalPoints >= 3, avar("Error, this shouldn't happen! Invalid winding in clipToPlane: %d", numFinalPoints)); + + // Copy the new rWinding, and we're set! + // + dMemcpy(points, finalPoints, numFinalPoints * sizeof(Point3F)); + rNumPoints = numFinalPoints; + AssertISV(rNumPoints <= 128, "MaxWindingPoints exceeded in scenegraph. Fatal error."); +} diff --git a/sceneGraph/windingClipper.h b/sceneGraph/windingClipper.h new file mode 100644 index 0000000..f87892d --- /dev/null +++ b/sceneGraph/windingClipper.h @@ -0,0 +1,15 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _WINDINGCLIPPER_H_ +#define _WINDINGCLIPPER_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif + + void sgUtil_clipToPlane(Point3F* points, U32& rNumPoints, const PlaneF& rPlane); + +#endif diff --git a/sfx/dsound/dsFunctions.h b/sfx/dsound/dsFunctions.h new file mode 100644 index 0000000..991af0c --- /dev/null +++ b/sfx/dsound/dsFunctions.h @@ -0,0 +1,10 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// The various functions we need to grab from DSound.dll +DS_FUNCTION( DirectSoundEnumerateA, HRESULT, (LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext) ) +DS_FUNCTION( DirectSoundEnumerateW, HRESULT, (LPDSENUMCALLBACKW pDSEnumCallback, LPVOID pContext) ) +DS_FUNCTION( DirectSoundCreate8, HRESULT, (LPCGUID pcGuidDevice, LPDIRECTSOUND8 *ppDS8, LPUNKNOWN pUnkOuter) ) + diff --git a/sfx/dsound/sfxDSBuffer.cpp b/sfx/dsound/sfxDSBuffer.cpp new file mode 100644 index 0000000..61793b7 --- /dev/null +++ b/sfx/dsound/sfxDSBuffer.cpp @@ -0,0 +1,250 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/dsound/sfxDSBuffer.h" +#include "sfx/sfxStream.h" +#include "sfx/sfxDescription.h" +#include "sfx/sfxInternal.h" +#include "platform/async/asyncUpdate.h" +#include "core/util/safeRelease.h" +#include "core/util/safeCast.h" + + +SFXDSBuffer* SFXDSBuffer::create( IDirectSound8 *dsound, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ) +{ + AssertFatal( dsound, "SFXDSBuffer::create() - Got null dsound!" ); + AssertFatal( stream, "SFXDSBuffer::create() - Got a null stream!" ); + AssertFatal( description, "SFXDSBuffer::create() - Got a null description" ); + + SFXDSBuffer* buffer = new SFXDSBuffer( dsound, + stream, + description, + useHardware ); + + + if( !buffer->_createBuffer( &buffer->mBuffer ) ) + SAFE_DELETE( buffer ); + + return buffer; +} + +SFXDSBuffer::SFXDSBuffer( IDirectSound8* dsound, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ) + : Parent( stream, description ), + mDSound( dsound ), + mIs3d( description->mIs3D ), + mUseHardware( useHardware ), + mBuffer( NULL ), + mDuplicate( false ) +{ + AssertFatal( mDSound, "SFXDSBuffer::SFXDSBuffer() - Got null dsound!" ); + + mDSound->AddRef(); +} + +SFXDSBuffer::~SFXDSBuffer() +{ + SAFE_RELEASE( mBuffer ); + SAFE_RELEASE( mDSound ); +} + +bool SFXDSBuffer::_createBuffer( IDirectSoundBuffer8 **buffer8 ) +{ + AssertFatal( mAsyncState != NULL, + "SFXDSBuffer::_createBuffer() - Can't create buffer when not connected to stream!" ); + + const SFXFormat& format = getFormat(); + + // Set up WAV format structure. + WAVEFORMATEX wfx; + dMemset( &wfx, 0, sizeof( WAVEFORMATEX ) ); + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = format.getChannels(); + wfx.nSamplesPerSec = format.getSamplesPerSecond(); + wfx.wBitsPerSample = format.getBitsPerChannel(); + wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + + // Set up DSBUFFERDESC structure. + DSBUFFERDESC dsbdesc; + dMemset( &dsbdesc, 0, sizeof( DSBUFFERDESC ) ); + dsbdesc.dwSize = sizeof( DSBUFFERDESC ); + dsbdesc.dwFlags = + ( mIs3d ? DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE : DSBCAPS_CTRLPAN ) | + ( isStreaming() ? DSBCAPS_CTRLPOSITIONNOTIFY : 0 ) | + DSBCAPS_CTRLFREQUENCY | + DSBCAPS_CTRLVOLUME | + DSBCAPS_GETCURRENTPOSITION2 | + DSBCAPS_GLOBALFOCUS | + DSBCAPS_STATIC | + ( mUseHardware ? DSBCAPS_LOCHARDWARE : DSBCAPS_LOCSOFTWARE ); + dsbdesc.dwBufferBytes = mBufferSize; + if ( mIs3d ) + dsbdesc.guid3DAlgorithm = DS3DALG_HRTF_FULL; + dsbdesc.lpwfxFormat = &wfx; + + // Create the buffer. + IDirectSoundBuffer *buffer = NULL; + HRESULT hr = mDSound->CreateSoundBuffer( &dsbdesc, &buffer, NULL ); + if ( FAILED( hr ) || !buffer ) + return false; + + // Grab the version 8 interface. + IDirectSoundBuffer8* buffer8Ptr; + hr = buffer->QueryInterface( IID_IDirectSoundBuffer8, ( LPVOID* ) &buffer8Ptr ); + + // Release the original interface. + buffer->Release(); + + // If we failed to get the 8 interface... exit. + if ( FAILED( hr ) || !buffer8Ptr ) + return false; + + // Set up notification positions, if this is a streaming buffer. + + if( isStreaming() ) + { + using namespace SFXInternal; + + const U32 maxQueuedPackets = SFXAsyncQueue::DEFAULT_STREAM_QUEUE_LENGTH; + const U32 packetSize = mAsyncState->mStream->getPacketSize(); + + LPDIRECTSOUNDNOTIFY8 lpDsNotify; + if( FAILED( hr = buffer8Ptr->QueryInterface( IID_IDirectSoundNotify8, ( LPVOID* ) &lpDsNotify ) ) ) + { + SAFE_RELEASE( buffer8Ptr ); + return false; + } + + DSBPOSITIONNOTIFY* dsbNotifyPos = + ( DSBPOSITIONNOTIFY* ) _alloca( maxQueuedPackets * sizeof( DSBPOSITIONNOTIFY ) ); + + // Events seem to be triggered way too early by DS causing the playback queues to + // reject updates, so we nudge the update markers "somewhat" to the right here. + // This value here is based on experimentation. No harm should result if we don't + // hit it other than updates happening in sub-optimal timing. + enum { OFFSET_DELTA = 5000 }; + + U32 offset = ( packetSize + OFFSET_DELTA ) % mBufferSize; + HANDLE updateEvent = ( HANDLE ) UPDATE_THREAD()->getUpdateEvent(); + for( U32 i = 0; i < maxQueuedPackets; ++ i, offset = ( offset + packetSize ) % mBufferSize ) + { + dsbNotifyPos[ i ].dwOffset = offset; + dsbNotifyPos[ i ].hEventNotify = updateEvent; + } + + lpDsNotify->SetNotificationPositions( maxQueuedPackets, dsbNotifyPos ); + SAFE_RELEASE( lpDsNotify ); + + // Don't need to notify on stop as when playback is stopped, + // the packet buffers will just fill up and stop updating + // when saturated. + } + + *buffer8 = buffer8Ptr; + return true; +} + +bool SFXDSBuffer::_copyData( U32 offset, + const U8 *data, + U32 length ) +{ + AssertFatal( mBuffer, "SFXDSBuffer::_copyData() - no buffer" ); + + // Fill the buffer with the resource data. + VOID* lpvWrite; + DWORD dwLength; + VOID* lpvWrite2; + DWORD dwLength2; + HRESULT hr = mBuffer->Lock( + offset, // Offset at which to start lock. + length, // Size of lock. + &lpvWrite, // Gets address of first part of lock. + &dwLength, // Gets size of first part of lock. + &lpvWrite2, // Address of wraparound not needed. + &dwLength2, // Size of wraparound not needed. + 0 ); + if ( FAILED( hr ) ) + return false; + + // Copy the first part. + dMemcpy( lpvWrite, data, dwLength ); + + // Do we have a wrap? + if ( lpvWrite2 ) + dMemcpy( lpvWrite2, data + dwLength, dwLength2 ); + + // And finally, unlock. + hr = mBuffer->Unlock( + lpvWrite, // Address of lock start. + dwLength, // Size of lock. + lpvWrite2, // No wraparound portion. + dwLength2 ); // No wraparound size. + + // Return success code. + return SUCCEEDED(hr); +} + +void SFXDSBuffer::_flush() +{ + AssertFatal( isStreaming(), "SFXDSBuffer::_flush() - not a streaming buffer" ); + AssertFatal( SFXInternal::isSFXThread(), "SFXDSBuffer::_flush() - not on SFX thread" ); + + Parent::_flush(); + mBuffer->SetCurrentPosition( 0 ); +} + +bool SFXDSBuffer::_duplicateBuffer( IDirectSoundBuffer8 **buffer8 ) +{ + AssertFatal( mBuffer, "SFXDSBuffer::_duplicateBuffer() - Duplicate buffer is null!" ); + + // If this is the first duplicate then + // give the caller our original buffer. + if ( !mDuplicate ) + { + mDuplicate = true; + + *buffer8 = mBuffer; + (*buffer8)->AddRef(); + + return true; + } + + IDirectSoundBuffer *buffer1 = NULL; + HRESULT hr = mDSound->DuplicateSoundBuffer( mBuffer, &buffer1 ); + if ( FAILED( hr ) || !buffer1 ) + return false; + + // Grab the version 8 interface. + hr = buffer1->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*)buffer8 ); + + // Release the original interface. + buffer1->Release(); + + return SUCCEEDED( hr ) && (*buffer8); +} + +bool SFXDSBuffer::createVoice( IDirectSoundBuffer8 **buffer8 ) +{ + return ( mBuffer && _duplicateBuffer( buffer8 ) && *buffer8 ); +} + +void SFXDSBuffer::releaseVoice( IDirectSoundBuffer8 **buffer ) +{ + AssertFatal( *buffer, "SFXDSBuffer::releaseVoice() - Got null buffer!" ); + + if ( *buffer == mBuffer ) + { + mDuplicate = false; + (*buffer)->Stop(); + } + + SAFE_RELEASE( (*buffer) ); +} diff --git a/sfx/dsound/sfxDSBuffer.h b/sfx/dsound/sfxDSBuffer.h new file mode 100644 index 0000000..bbe80b5 --- /dev/null +++ b/sfx/dsound/sfxDSBuffer.h @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXDSBUFFER_H_ +#define _SFXDSBUFFER_H_ + +#include + +#ifndef _SFXINTERNAL_H_ +# include "sfx/sfxInternal.h" +#endif + + +/// DirectSound SFXBuffer implementation. +/// +/// Note that the actual sound buffer held by the buffer may +/// get duplicated around for individual voices. This is kind +/// of ugly as the resulting buffers aren't tied to a SFXDSBuffer +/// anymore. +class SFXDSBuffer : public SFXInternal::SFXWrapAroundBuffer +{ + typedef SFXInternal::SFXWrapAroundBuffer Parent; + + friend class SFXDSDevice; + friend class SFXDSVoice; + + protected: + + /// + bool mIs3d; + + /// + bool mUseHardware; + + IDirectSound8 *mDSound; + + /// The buffer used when duplication is allowed. + IDirectSoundBuffer8 *mBuffer; + + /// We set this to true when the original buffer has + /// been handed out and duplicates need to be made. + bool mDuplicate; + + /// + SFXDSBuffer( IDirectSound8 *dsound, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ); + + virtual ~SFXDSBuffer(); + + /// Set up a DirectSound buffer. + /// @note This method will not fill the buffer with data. + /// @note If this is a streaming buffer, the resulting buffer + /// will have its notification positions set up and already + /// be registered with SFXDSStreamThread. + bool _createBuffer( IDirectSoundBuffer8 **buffer8 ); + + /// + bool _duplicateBuffer( IDirectSoundBuffer8 **buffer8 ); + + // SFXWrapAroundBuffer. + virtual bool _copyData( U32 offset, const U8* data, U32 length ); + virtual void _flush(); + +public: + + /// + static SFXDSBuffer* create( IDirectSound8* dsound, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ); + + // + bool createVoice( IDirectSoundBuffer8 **buffer ); + + // + void releaseVoice( IDirectSoundBuffer8 **buffer ); + +}; + +#endif // _SFXDSBUFFER_H_ \ No newline at end of file diff --git a/sfx/dsound/sfxDSDevice.cpp b/sfx/dsound/sfxDSDevice.cpp new file mode 100644 index 0000000..7cd23a3 --- /dev/null +++ b/sfx/dsound/sfxDSDevice.cpp @@ -0,0 +1,214 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/dsound/sfxDSDevice.h" +#include "sfx/dsound/sfxDSBuffer.h" +#include "sfx/dsound/sfxDSVoice.h" +#include "sfx/sfxListener.h" +#include "platformWin32/platformWin32.h" +#include "core/util/safeRelease.h" +#include "platform/async/asyncUpdate.h" +#include "console/console.h" + + +SFXDSDevice::SFXDSDevice( SFXProvider* provider, + DSoundFNTable *dsFnTbl, + GUID* guid, + String name, + bool useHardware, + S32 maxBuffers ) + : SFXDevice( name, provider, useHardware, maxBuffers ), + mDSound( NULL ), + mPrimaryBuffer( NULL ), + mListener( NULL ), + mDSoundTbl( dsFnTbl ), + mGUID( guid ) +{ +} + +bool SFXDSDevice::_init() +{ + HRESULT hr = mDSoundTbl->DirectSoundCreate8( mGUID, &mDSound, NULL ); + if ( FAILED( hr ) || !mDSound ) + { + Con::errorf( "SFXDSDevice::SFXDSDevice() - DirectSoundCreate8 failed" ); + return false; + } + + hr = mDSound->SetCooperativeLevel( getWin32WindowHandle(), DSSCL_PRIORITY ); + if ( FAILED( hr ) ) + { + Con::errorf( "SFXDSDevice::SFXDSDevice() - SetCooperativeLevel failed" ); + return false; + } + + // Get the primary buffer. + DSBUFFERDESC dsbd; + dMemset( &dsbd, 0, sizeof( DSBUFFERDESC ) ); + dsbd.dwSize = sizeof( DSBUFFERDESC ); + dsbd.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER; + hr = mDSound->CreateSoundBuffer( &dsbd, &mPrimaryBuffer, NULL ); + if ( FAILED( hr ) ) + { + Con::errorf( "SFXDSDevice::SFXDSDevice - Creating primary sound buffer failed" ); + return false; + } + + // Set the format and bitrate on the primary buffer. + S32 frequency = Con::getIntVariable( "$pref::SFX::frequency", 44100 ); + S32 bitrate = Con::getIntVariable( "$pref::SFX::bitrate", 32 ); + + WAVEFORMATEX wfx; + dMemset( &wfx, 0, sizeof( WAVEFORMATEX ) ); + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = frequency; + wfx.wBitsPerSample = bitrate; + wfx.nBlockAlign = ( wfx.nChannels * wfx.wBitsPerSample ) / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + hr = mPrimaryBuffer->SetFormat( &wfx ); + if( FAILED( hr ) ) + { + Con::errorf( "SFXDSDevice::SFXDSDevice() - Setting format of primary buffer failed" ); + return false; + } + + // Grab the 3d listener. + hr = mPrimaryBuffer->QueryInterface( IID_IDirectSound3DListener8, (LPVOID*)&mListener ); + if ( FAILED( hr ) ) + { + Con::errorf( "SFXDSDevice::SFXDevice() - Querying the listener interface failed!" ); + mListener = NULL; + } + + mCaps.dwSize = sizeof( DSCAPS ); + mDSound->GetCaps( &mCaps ); + + // If the device reports no hardware buffers then + // we have no choice but to disable hardware. + if ( mCaps.dwMaxHw3DAllBuffers == 0 ) + mUseHardware = false; + + // If mMaxBuffers is negative then use the caps + // to decide on a good maximum value... or set 8. + if ( mMaxBuffers < 0 ) + mMaxBuffers = getMax( mCaps.dwMaxHw3DAllBuffers, 8 ); + + // Start the stream thread. + + if( !Con::getBoolVariable( "$_forceAllMainThread" ) ) + { + SFXInternal::gUpdateThread = + new AsyncUpdateThread( "DirectSound Update Thread", SFXInternal::gBufferUpdateList ); + SFXInternal::gUpdateThread->start(); + } + + return true; +} + +SFXDSDevice::~SFXDSDevice() +{ + // And release our resources. + SAFE_RELEASE( mListener ); + SAFE_RELEASE( mPrimaryBuffer ); + SAFE_RELEASE( mDSound ); +} + +SFXBuffer* SFXDSDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + AssertFatal( stream, "SFXDSDevice::createBuffer() - Got null stream!" ); + AssertFatal( description, "SFXDSDevice::createBuffer() - Got null description!" ); + + SFXDSBuffer* buffer = SFXDSBuffer::create( mDSound, + stream, + description, + mUseHardware ); + + if( buffer ) + _addBuffer( buffer ); + + return buffer; +} + +SFXVoice* SFXDSDevice::createVoice( bool is3D, SFXBuffer *buffer ) +{ + // Don't bother going any further if we've + // exceeded the maximum voices. + if ( mVoices.size() >= mMaxBuffers ) + return NULL; + + AssertFatal( buffer, "SFXDSDevice::createVoice() - Got null buffer!" ); + + SFXDSBuffer* dsBuffer = dynamic_cast( buffer ); + AssertFatal( dsBuffer, "SFXDSDevice::createVoice() - Got bad buffer!" ); + + SFXDSVoice* voice = SFXDSVoice::create( this, dsBuffer ); + if ( !voice ) + return NULL; + + _addVoice( voice ); + return voice; +} + +void SFXDSDevice::_commitDeferred() +{ + if( mListener ) + mListener->CommitDeferredSettings(); +} + +void SFXDSDevice::update( const SFXListener& listener ) +{ + Parent::update( listener ); + + // Get the transform from the listener. + const MatrixF& transform = listener.getTransform(); + Point3F pos, dir, up; + transform.getColumn( 3, &pos ); + transform.getColumn( 1, &dir ); + transform.getColumn( 2, &up ); + + // And the velocity... + const VectorF& velocity = listener.getVelocity(); + + // CodeReview This is a symptom fix, should be undone. BJG - 3/25/07 + if(!mListener) + return; + + // Finally, set it all to DSound! + mListener->SetPosition( pos.x, pos.z, pos.y, DS3D_DEFERRED ); + mListener->SetOrientation( dir.x, dir.z, dir.y, up.x, up.z, up.y, DS3D_DEFERRED ); + mListener->SetVelocity( velocity.x, velocity.z, velocity.y, DS3D_DEFERRED ); + + // Apply the deferred settings that changed between updates. + mListener->CommitDeferredSettings(); +} + +void SFXDSDevice::setDistanceModel( SFXDistanceModel model ) +{ + switch( model ) + { + case SFXDistanceModelLinear: + Con::errorf( "SFXDSDevice::setDistanceModel - 'linear' distance attenuation not supported" ); + break; + + case SFXDistanceModelLogarithmic: + break; // Nothing to do. + + default: + AssertWarn( false, "SFXDSDevice::setDistanceModel() - model not implemented" ); + } +} + +void SFXDSDevice::setDopplerFactor( F32 factor ) +{ + if( mListener ) + mListener->SetDopplerFactor( factor, DS3D_DEFERRED ); // Committed in update. +} + +void SFXDSDevice::setRolloffFactor( F32 factor ) +{ + if( mListener ) + mListener->SetRolloffFactor( factor, DS3D_DEFERRED ); // Committed in update. +} diff --git a/sfx/dsound/sfxDSDevice.h b/sfx/dsound/sfxDSDevice.h new file mode 100644 index 0000000..92277fc --- /dev/null +++ b/sfx/dsound/sfxDSDevice.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXDSDEVICE_H_ +#define _SFXDSDEVICE_H_ + +#ifndef _STRINGFUNCTIONS_H_ +# include "core/strings/stringFunctions.h" +#endif +#ifndef _SFXDEVICE_H_ + #include "sfx/sfxDevice.h" +#endif +#ifndef _SFXDSVOICE_H_ + #include "sfx/dsound/sfxDSVoice.h" +#endif +#ifndef OS_DLIBRARY_H + #include "platform/platformDlibrary.h" +#endif + +#include + +// Typedefs +#define DS_FUNCTION(fn_name, fn_return, fn_args) \ + typedef fn_return (WINAPI *DSFNPTR##fn_name##)##fn_args##; +#include "sfx/dsound/dsFunctions.h" +#undef DS_FUNCTION + +// Function table +struct DSoundFNTable +{ + DSoundFNTable() : isLoaded( false ) {}; + bool isLoaded; + DLibraryRef dllRef; + +#define DS_FUNCTION(fn_name, fn_return, fn_args) \ + DSFNPTR##fn_name fn_name; +#include "sfx/dsound/dsFunctions.h" +#undef DS_FUNCTION +}; + + +/// Helper for asserting on dsound HRESULTS. +inline void DSAssert( HRESULT hr, const char *info ) +{ + #ifdef TORQUE_DEBUG + + if( FAILED( hr ) ) + { + char buf[256]; + dSprintf( buf, 256, "Error code: %x\n%s", hr, info ); + AssertFatal( false, buf ); + } + + #endif +} + + +/// The DirectSound device implementation exposes a couple +/// of settings to script that you should be aware of: +/// +/// $DirectSound::dopplerFactor - This controls the scale of +/// the doppler effect. Valid factors are 0.0 to 10.0 and it +/// defaults to 0.75. +/// +/// $DirectSound::distanceFactor - This sets the unit conversion +/// for +/// +/// $DirectSound::rolloffFactor - ; +/// +/// +class SFXDSDevice : public SFXDevice +{ + typedef SFXDevice Parent; + + friend class SFXDSVoice; + friend class SFXDSProvider; // _init + + public: + + //explicit SFXDSDevice(); + + SFXDSDevice( SFXProvider* provider, + DSoundFNTable *dsFnTbl, + GUID* guid, + String name, + bool useHardware, + S32 maxBuffers ); + + virtual ~SFXDSDevice(); + + protected: + + IDirectSound8 *mDSound; + + IDirectSound3DListener8 *mListener; + + IDirectSoundBuffer *mPrimaryBuffer; + + DSoundFNTable *mDSoundTbl; + + DSCAPS mCaps; + + GUID* mGUID; + + bool _init(); + + /// Called from SFXDSVoice to commit any deferred + /// settings before playback begins. + void _commitDeferred(); + + public: + + // SFXDevice + virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + virtual SFXVoice* createVoice( bool is3D, SFXBuffer *buffer ); + virtual void update( const SFXListener& listener ); + virtual void setDistanceModel( SFXDistanceModel mode ); + virtual void setDopplerFactor( F32 factor ); + virtual void setRolloffFactor( F32 factor ); +}; + +#endif // _SFXDSDEVICE_H_ diff --git a/sfx/dsound/sfxDSProvider.cpp b/sfx/dsound/sfxDSProvider.cpp new file mode 100644 index 0000000..037ed49 --- /dev/null +++ b/sfx/dsound/sfxDSProvider.cpp @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxProvider.h" +#include "sfx/dsound/sfxDSDevice.h" +#include "core/util/safeRelease.h" +#include "console/console.h" +#include "core/strings/unicode.h" +#include "core/util/safeDelete.h" + + +class SFXDSProvider : public SFXProvider +{ +public: + + SFXDSProvider() + : SFXProvider( "DirectSound" ) {} + virtual ~SFXDSProvider(); + + void init(); + +protected: + DSoundFNTable mDSound; + + struct DSDeviceInfo : SFXDeviceInfo + { + GUID* guid; + DSCAPS caps; + }; + + static BOOL CALLBACK dsEnumProc( + LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); + + void addDeviceDesc( GUID* guid, const String& name, const String& desc ); + +public: + + SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ); + +}; + +SFX_INIT_PROVIDER( SFXDSProvider ); + +//------------------------------------------------------------------------------ +// Helper + +bool dsBindFunction( DLibrary *dll, void *&fnAddress, const char *name ) +{ + fnAddress = dll->bind( name ); + + if (!fnAddress) + Con::warnf( "DSound Loader: DLL bind failed for %s", name ); + + return fnAddress != 0; +} + +//------------------------------------------------------------------------------ + +SFXDSProvider::~SFXDSProvider() +{ +} + +void SFXDSProvider::init() +{ + // Grab the functions we'll want from the dsound DLL. + mDSound.dllRef = OsLoadLibrary( "dsound.dll" ); + mDSound.isLoaded = true; +#define DS_FUNCTION(fn_name, fn_return, fn_args) \ + mDSound.isLoaded &= dsBindFunction(mDSound.dllRef, *(void**)&mDSound.fn_name, #fn_name); +#include "sfx/dsound/dsFunctions.h" +#undef DS_FUNCTION + + AssertISV( mDSound.isLoaded, "DirectSound failed to load." ); + + // All we need to do to init is enumerate the + // devices... if this fails then don't register + // the provider as it's broken in some way. + if ( FAILED( mDSound.DirectSoundEnumerate( dsEnumProc, (VOID*)this ) ) ) + { + Con::errorf( "SFXDSProvider - Device enumeration failed!" ); + return; + } + + // Did we get any devices? + if ( mDeviceInfo.empty() ) + { + Con::errorf( "SFXDSProvider - No valid devices found!" ); + return; + } + + // Wow, we made it - register the provider. + regProvider( this ); +} + + +BOOL CALLBACK SFXDSProvider::dsEnumProc( + LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + SFXDSProvider* provider = (SFXDSProvider*)lpContext; + provider->addDeviceDesc( lpGUID, lpszDrvName, lpszDesc ); + return TRUE; +} + +void SFXDSProvider::addDeviceDesc( GUID* guid, const String& name, const String& desc ) +{ + // Create a dummy device to get the caps. + IDirectSound8* dsound; + HRESULT hr = mDSound.DirectSoundCreate8( guid, &dsound, NULL ); + if ( FAILED( hr ) || !dsound ) + return; + + // Init the caps structure and have the device fill it out. + DSCAPS caps; + dMemset( &caps, 0, sizeof( caps ) ); + caps.dwSize = sizeof( caps ); + hr = dsound->GetCaps( &caps ); + + // Clean up and handle errors. + SAFE_RELEASE( dsound ); + + if ( FAILED( hr ) ) + return; + + // Now, record the desc info into our own internal list. + DSDeviceInfo* info = new DSDeviceInfo; + info->name = desc; + info->driver = name; + info->hasHardware = caps.dwMaxHw3DAllBuffers > 0; + info->maxBuffers = caps.dwMaxHw3DAllBuffers; + info->guid = guid; + info->caps = caps; + + mDeviceInfo.push_back( info ); +} + +SFXDevice* SFXDSProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ) +{ + DSDeviceInfo* info = dynamic_cast< DSDeviceInfo* > + ( _findDeviceInfo( deviceName ) ); + + if( !info ) + return NULL; + + SFXDSDevice* device = new SFXDSDevice( this, + &mDSound, + info->guid, + info->name, + useHardware, + maxBuffers ); + + if( !device->_init() ) + SAFE_DELETE( device ); + + return device; +} diff --git a/sfx/dsound/sfxDSVoice.cpp b/sfx/dsound/sfxDSVoice.cpp new file mode 100644 index 0000000..b3b391a --- /dev/null +++ b/sfx/dsound/sfxDSVoice.cpp @@ -0,0 +1,212 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/dsound/sfxDSVoice.h" +#include "sfx/dsound/sfxDSDevice.h" +#include "core/util/safeRelease.h" + + +SFXDSVoice* SFXDSVoice::create( SFXDSDevice *device, SFXDSBuffer *buffer ) +{ + AssertFatal( buffer, "SFXDSVoice::create() - Got null buffer!" ); + + IDirectSoundBuffer8 *dsBuffer8 = NULL; + if ( !buffer->createVoice( &dsBuffer8 ) || !dsBuffer8 ) + return NULL; + + // Now try to grab a 3D interface... if we don't + // get one its probably because its not a 3d sound. + IDirectSound3DBuffer8* dsBuffer3d8 = NULL; + dsBuffer8->QueryInterface( IID_IDirectSound3DBuffer8, (LPVOID*)&dsBuffer3d8 ); + + // Create the voice and return! + SFXDSVoice* voice = new SFXDSVoice( device, + buffer, + dsBuffer8, + dsBuffer3d8 ); + + // Now set the voice to a default state. + // The buffer from which we have duplicated may have been assigned different + // properties and we don't want to inherit these. + + voice->setVolume( 1.0 ); + voice->setPitch( 1.0 ); + + return voice; +} + +SFXDSVoice::SFXDSVoice( SFXDSDevice *device, + SFXDSBuffer *buffer, + IDirectSoundBuffer8 *dsBuffer, + IDirectSound3DBuffer8 *dsBuffer3d ) + : Parent( buffer ), + mDevice( device ), + mDSBuffer( dsBuffer ), + mDSBuffer3D( dsBuffer3d ), + mIsLooping( false ) +{ + AssertFatal( mDevice, "SFXDSVoice::SFXDSVoice() - SFXDSDevice is null!" ); + AssertFatal( mBuffer, "SFXDSVoice::SFXDSVoice() - SFXDSBuffer is null!" ); + AssertFatal( mDSBuffer, "SFXDSVoice::SFXDSVoice() - Dsound buffer is null!" ); +} + +SFXDSVoice::~SFXDSVoice() +{ + SAFE_RELEASE( mDSBuffer3D ); + + SFXDSBuffer* dsBuffer = _getBuffer(); + if( dsBuffer ) + dsBuffer->releaseVoice( &mDSBuffer ); + + mBuffer = NULL; +} + +SFXStatus SFXDSVoice::_status() const +{ + DWORD status = 0; + mDSBuffer->GetStatus( &status ); + + if ( status & DSBSTATUS_PLAYING ) + return SFXStatusPlaying; + else + return SFXStatusStopped; +} + +void SFXDSVoice::_play() +{ + DSAssert( mDSBuffer->Play( 0, 0, mIsLooping ? DSBPLAY_LOOPING : 0 ), + "SFXDSVoice::_play() - Playback failed!" ); +} + +void SFXDSVoice::_stop() +{ + DSAssert( mDSBuffer->Stop(), "SFXDSVoice::pause - stop failed!" ); + mDSBuffer->SetCurrentPosition( 0 ); +} + +void SFXDSVoice::_pause() +{ + DSAssert( mDSBuffer->Stop(), "SFXDSVoice::pause - stop failed!" ); +} + +void SFXDSVoice::_seek( U32 sample ) +{ + U32 pos = mBuffer->getFormat().getBytesPerSample() * sample; + mDSBuffer->SetCurrentPosition( pos ); +} + +U32 SFXDSVoice::_tell() const +{ + DWORD position = 0; + mDSBuffer->GetCurrentPosition( &position, NULL ); + U32 samplePos = _getBuffer()->getSamplePos( position ); + return samplePos; +} + +void SFXDSVoice::setMinMaxDistance( F32 min, F32 max ) +{ + if ( !mDSBuffer3D ) + return; + + mDSBuffer3D->SetMinDistance( min, DS3D_DEFERRED ); + mDSBuffer3D->SetMaxDistance( max, DS3D_DEFERRED ); +} + +void SFXDSVoice::play( bool looping ) +{ + // If this is a 3d sound then we need + // to commit any deferred settings before + // we start playback else we can get some + // glitches. + + if ( mDSBuffer3D ) + mDevice->_commitDeferred(); + + // If this is a streaming buffer, + // force looping. + + const bool isStreaming = mBuffer->isStreaming(); + if( isStreaming ) + looping = true; + mIsLooping = looping; + + Parent::play( looping ); +} + +void SFXDSVoice::setVelocity( const VectorF& velocity ) +{ + if ( !mDSBuffer3D ) + return; + + DSAssert( mDSBuffer3D->SetVelocity( velocity.x, velocity.z, velocity.y, DS3D_DEFERRED ), + "SFXDSVoice::setVelocity - couldn't update buffer!" ); +} + +void SFXDSVoice::setTransform( const MatrixF& transform ) +{ + if ( !mDSBuffer3D ) + return; + + Point3F pos, dir; + transform.getColumn( 3, &pos ); + transform.getColumn( 1, &dir ); + DSAssert( mDSBuffer3D->SetPosition( pos.x, pos.z, pos.y, DS3D_DEFERRED ), + "SFXDSVoice::setTransform - couldn't set position of the buffer." ); + + DSAssert( mDSBuffer3D->SetConeOrientation( dir.x, dir.z, dir.y, DS3D_DEFERRED ), + "SFXDSVoice::setTransform - couldn't set cone orientation of the buffer." ); +} + +/// Helper for converting floating point linear volume +/// to a logrithmic integer volume for dsound. +LONG SFXDSVoice::_linearToLogVolume( F32 linVolume ) +{ + LONG logVolume; + + if ( linVolume <= 0.0f ) + logVolume = DSBVOLUME_MIN; + else + { + logVolume = -2000.0 * mLog( 1.0f / linVolume ); + logVolume = mClamp( logVolume, DSBVOLUME_MIN, DSBVOLUME_MAX ); + } + + return logVolume; +} + +void SFXDSVoice::setVolume( F32 volume ) +{ + LONG logVolume = _linearToLogVolume( volume ); + + HRESULT hr = mDSBuffer->SetVolume( logVolume ); + DSAssert( hr, "SFXDSVoice::setVolume - couldn't set volume!" ); +} + +void SFXDSVoice::setPitch( F32 pitch ) +{ + F32 sampleRate = _getBuffer()->getFormat().getSamplesPerSecond(); + F32 frequency = mFloor( mClampF( sampleRate * pitch, DSBFREQUENCY_MIN, DSBFREQUENCY_MAX ) ); + + DSAssert( mDSBuffer->SetFrequency( ( U32 )frequency ), + "SFXDSVoice::setPitch - couldn't set playback frequency."); +} + +void SFXDSVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ) +{ + if ( !mDSBuffer3D ) + return; + + DSAssert( mDSBuffer3D->SetConeAngles( innerAngle, + outerAngle, + DS3D_DEFERRED ), + "SFXDSVoice::setCone - couldn't set cone angles!" ); + + + LONG logVolume = _linearToLogVolume( outerVolume ); + + DSAssert( mDSBuffer3D->SetConeOutsideVolume( logVolume, + DS3D_DEFERRED ), + "SFXDSVoice::setCone - couldn't set cone outside volume!" ); +} diff --git a/sfx/dsound/sfxDSVoice.h b/sfx/dsound/sfxDSVoice.h new file mode 100644 index 0000000..b13d4a5 --- /dev/null +++ b/sfx/dsound/sfxDSVoice.h @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXDSVOICE_H_ +#define _SFXDSVOICE_H_ + +#ifndef _SFXVOICE_H_ + #include "sfx/sfxVoice.h" +#endif +#ifndef _SFXDSBUFFER_H_ + #include "sfx/dsound/sfxDSBuffer.h" +#endif + +#include + +class SFXDSDevice; + + +class SFXDSVoice : public SFXVoice +{ + typedef SFXVoice Parent; + + protected: + + SFXDSVoice( SFXDSDevice *device, + SFXDSBuffer *buffer, + IDirectSoundBuffer8 *dsBuffer, + IDirectSound3DBuffer8 *dsBuffer3d ); + + /// The device used to commit deferred settings. + SFXDSDevice *mDevice; + + IDirectSoundBuffer8 *mDSBuffer; + + IDirectSound3DBuffer8 *mDSBuffer3D; + + bool mIsLooping; + + SFXDSBuffer* _getBuffer() const { return ( SFXDSBuffer* ) mBuffer.getPointer(); } + + /// Helper for converting floating point linear volume + /// to a logrithmic integer volume for dsound. + static LONG _linearToLogVolume( F32 linVolume ); + + // SFXVoice + virtual SFXStatus _status() const; + virtual void _play(); + virtual void _pause(); + virtual void _stop(); + virtual void _seek( U32 sample ); + virtual U32 _tell() const; + + public: + + /// + static SFXDSVoice* create( SFXDSDevice *device, + SFXDSBuffer *buffer ); + + /// + virtual ~SFXDSVoice(); + + // SFXVoice + void setMinMaxDistance( F32 min, F32 max ); + void play( bool looping ); + void setVelocity( const VectorF& velocity ); + void setTransform( const MatrixF& transform ); + void setVolume( F32 volume ); + void setPitch( F32 pitch ); + void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ); + +}; + +#endif // _SFXDSBUFFER_H_ \ No newline at end of file diff --git a/sfx/fmod/fmodFunctions.h b/sfx/fmod/fmodFunctions.h new file mode 100644 index 0000000..978d383 --- /dev/null +++ b/sfx/fmod/fmodFunctions.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +FMOD_FUNCTION( FMOD_Channel_GetPaused, (FMOD_CHANNEL *channel, FMOD_BOOL *paused)) +FMOD_FUNCTION( FMOD_Channel_SetPaused, (FMOD_CHANNEL *channel, FMOD_BOOL paused)); +FMOD_FUNCTION( FMOD_Channel_IsPlaying, (FMOD_CHANNEL *channel, FMOD_BOOL *isplaying)) +FMOD_FUNCTION( FMOD_Channel_Set3DAttributes, (FMOD_CHANNEL *channel, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel)) +FMOD_FUNCTION( FMOD_Channel_SetFrequency, (FMOD_CHANNEL *channel, float frequency)) +FMOD_FUNCTION( FMOD_Channel_SetLoopCount, (FMOD_CHANNEL *channel, int loopcount)) +FMOD_FUNCTION( FMOD_Channel_SetPosition, (FMOD_CHANNEL *channel, unsigned int position, FMOD_TIMEUNIT postype)) +FMOD_FUNCTION( FMOD_Channel_GetPosition, (FMOD_CHANNEL* channel, unsigned int* position, FMOD_TIMEUNIT postype)) +FMOD_FUNCTION( FMOD_Channel_SetVolume, (FMOD_CHANNEL *channel, float volume)) +FMOD_FUNCTION( FMOD_Channel_GetVolume, (FMOD_CHANNEL *channel, float *volume)); +FMOD_FUNCTION( FMOD_Channel_Stop, (FMOD_CHANNEL *channel)) +FMOD_FUNCTION( FMOD_Channel_SetMode, (FMOD_CHANNEL *channel, FMOD_MODE mode)); +FMOD_FUNCTION( FMOD_Channel_Set3DMinMaxDistance, (FMOD_CHANNEL *channel, float mindistance, float maxdistance)); +FMOD_FUNCTION( FMOD_Channel_Set3DConeSettings, (FMOD_CHANNEL *channel, float insideconeangle, float outsideconeangle, float outsidevolume)) +FMOD_FUNCTION( FMOD_Channel_Set3DConeOrientation, (FMOD_CHANNEL *channel, FMOD_VECTOR *orientation)) +FMOD_FUNCTION( FMOD_Channel_SetReverbProperties, ( FMOD_CHANNEL* channel, const FMOD_REVERB_CHANNELPROPERTIES* prop ) ) + +FMOD_FUNCTION( FMOD_Sound_Lock, (FMOD_SOUND *sound, unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2)) +FMOD_FUNCTION( FMOD_Sound_Unlock, (FMOD_SOUND *sound, void *ptr1, void *ptr2, unsigned int len1, unsigned int len2)) +FMOD_FUNCTION( FMOD_Sound_Release, (FMOD_SOUND *sound)) +FMOD_FUNCTION( FMOD_Sound_Set3DMinMaxDistance, (FMOD_SOUND *sound, float min, float max)) +FMOD_FUNCTION( FMOD_Sound_SetMode, (FMOD_SOUND *sound, FMOD_MODE mode)) +FMOD_FUNCTION( FMOD_Sound_GetMode, (FMOD_SOUND *sound, FMOD_MODE *mode)) +FMOD_FUNCTION( FMOD_Sound_SetLoopCount, (FMOD_SOUND *sound, int loopcount)) +FMOD_FUNCTION( FMOD_Sound_GetFormat, ( FMOD_SOUND* sound, FMOD_SOUND_TYPE* type, FMOD_SOUND_FORMAT* format, int* channels, int* bits ) ) +FMOD_FUNCTION( FMOD_Sound_GetLength, ( FMOD_SOUND* sound, unsigned int* length, FMOD_TIMEUNIT lengthtype ) ) +FMOD_FUNCTION( FMOD_Sound_GetDefaults, ( FMOD_SOUND* sound, float* frequency, float* volume, float* pan, int* priority ) ) +FMOD_FUNCTION( FMOD_Sound_GetMemoryInfo, ( FMOD_SOUND* sound, unsigned int memorybits, unsigned int event_memorybits, unsigned int* memoryused, unsigned int* memoryused_array ) ); + +FMOD_FUNCTION( FMOD_Geometry_AddPolygon, ( FMOD_GEOMETRY* geometry, float directocclusion, float reverbocclusion, FMOD_BOOL doublesided, int numvertices, const FMOD_VECTOR* vertices, int* polygonindex ) ) +FMOD_FUNCTION( FMOD_Geometry_SetPosition, ( FMOD_GEOMETRY* geometry, const FMOD_VECTOR* position ) ) +FMOD_FUNCTION( FMOD_Geometry_SetRotation, ( FMOD_GEOMETRY* geometry, const FMOD_VECTOR* forward, const FMOD_VECTOR* up ) ) +FMOD_FUNCTION( FMOD_Geometry_SetScale, ( FMOD_GEOMETRY* geometry, const FMOD_VECTOR* scale ) ) +FMOD_FUNCTION( FMOD_Geometry_Release, ( FMOD_GEOMETRY* geometry ) ) + +FMOD_FUNCTION( FMOD_System_Close, (FMOD_SYSTEM *system)) +FMOD_FUNCTION( FMOD_System_Create, (FMOD_SYSTEM **system)) +FMOD_FUNCTION( FMOD_System_Release, (FMOD_SYSTEM *system)) +FMOD_FUNCTION( FMOD_System_CreateSound, (FMOD_SYSTEM *system, const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, FMOD_SOUND **sound)) +FMOD_FUNCTION( FMOD_System_CreateGeometry, ( FMOD_SYSTEM* system, int maxpolygons, int maxvertices, FMOD_GEOMETRY** geometry ) ) +FMOD_FUNCTION( FMOD_System_SetDriver, (FMOD_SYSTEM *system, int driver)) +FMOD_FUNCTION( FMOD_System_GetDriverCaps, (FMOD_SYSTEM *system, int id, FMOD_CAPS *caps, int *minfrequency, int *maxfrequency, FMOD_SPEAKERMODE *controlpanelspeakermode)) +FMOD_FUNCTION( FMOD_System_GetDriverInfo, (FMOD_SYSTEM *system, int id, char *name, int namelen, FMOD_GUID *GUID)) +FMOD_FUNCTION( FMOD_System_GetNumDrivers, (FMOD_SYSTEM *system, int *numdrivers)) +FMOD_FUNCTION( FMOD_System_GetVersion, (FMOD_SYSTEM *system, unsigned int *version)) +FMOD_FUNCTION( FMOD_System_Init, (FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata)) +FMOD_FUNCTION( FMOD_System_PlaySound, (FMOD_SYSTEM *system, FMOD_CHANNELINDEX channelid, FMOD_SOUND *sound, FMOD_BOOL paused, FMOD_CHANNEL **channel)) +FMOD_FUNCTION( FMOD_System_Set3DListenerAttributes, (FMOD_SYSTEM *system, int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up)) +FMOD_FUNCTION( FMOD_System_Set3DNumListeners, ( FMOD_SYSTEM* system, int numlisteners ) ) +FMOD_FUNCTION( FMOD_System_SetGeometrySettings, ( FMOD_SYSTEM* system, float maxworldsize ) ) +FMOD_FUNCTION( FMOD_System_Get3DSettings, (FMOD_SYSTEM* system, float* dopplerFactor, float* distanceFactor, float* rolloffFactor)) +FMOD_FUNCTION( FMOD_System_Set3DSettings, (FMOD_SYSTEM *system, float dopplerscale, float distancefactor, float rolloffscale)) +FMOD_FUNCTION( FMOD_System_SetDSPBufferSize, (FMOD_SYSTEM *system, unsigned int bufferlength, int numbuffers)) +FMOD_FUNCTION( FMOD_System_SetSpeakerMode, (FMOD_SYSTEM *system, FMOD_SPEAKERMODE speakermode)) +FMOD_FUNCTION( FMOD_System_SetReverbProperties, ( FMOD_SYSTEM* system, const FMOD_REVERB_PROPERTIES* prop ) ) +FMOD_FUNCTION( FMOD_System_Update, (FMOD_SYSTEM *system)) +FMOD_FUNCTION( FMOD_Memory_GetStats, (int*, int*)) +FMOD_FUNCTION( FMOD_Memory_Initialize,(void *poolmem, int poollen, FMOD_MEMORY_ALLOCCALLBACK useralloc, FMOD_MEMORY_REALLOCCALLBACK userrealloc, FMOD_MEMORY_FREECALLBACK userfree)) diff --git a/sfx/fmod/sfxFMODBuffer.cpp b/sfx/fmod/sfxFMODBuffer.cpp new file mode 100644 index 0000000..5c787df --- /dev/null +++ b/sfx/fmod/sfxFMODBuffer.cpp @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/fmod/sfxFMODBuffer.h" +#include "sfx/fmod/sfxFMODDevice.h" +#include "sfx/sfxDescription.h" +#include "core/util/safeDelete.h" +#include "core/volume.h" + + +//----------------------------------------------------------------------------- + +static const char* sExtensions[] = +{ + "", // First try without extension. + ".aiff", + ".asf", + ".asx", + ".dls", + ".flac", + ".fsb", + ".it", + ".m3u", + ".mid", + ".mod", + ".mp2", + ".mp3", + ".ogg", + ".pls", + ".s3m", + ".vag", + ".wav", + ".wax", + ".wma", + ".xm", + +#ifdef TORQUE_OS_XENON + ".xma", +#endif + + NULL +}; + +//----------------------------------------------------------------------------- + +SFXFMODBuffer* SFXFMODBuffer::create( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + SFXFMODBuffer *buffer = new SFXFMODBuffer( stream, description ); + if( !buffer->mSound ) + SAFE_DELETE( buffer ); + + return buffer; +} + +//----------------------------------------------------------------------------- + +SFXFMODBuffer* SFXFMODBuffer::create( const String& filename, SFXDescription* description ) +{ + if( Con::getBoolVariable( "$pref::SFX::FMOD::noCustomFileLoading", false ) ) + return NULL; + + SFXFMODBuffer *buffer = new SFXFMODBuffer( filename, description ); + if( !buffer->mSound ) + SAFE_DELETE( buffer ); + + return buffer; +} + +//----------------------------------------------------------------------------- + +SFXFMODBuffer::SFXFMODBuffer( const String& filename, SFXDescription* description ) + : Parent( description ), + mSound( NULL ) +{ + FMOD_MODE fMode = FMOD_SOFTWARE | ( description->mIs3D ? FMOD_3D : FMOD_2D ); + if( description->mIsLooping ) + fMode |= FMOD_LOOP_NORMAL; + if( description->mIsStreaming ) + { + fMode |= FMOD_CREATESTREAM; + mIsUnique = true; + } + + for( U32 i = 0; sExtensions[ i ]; ++ i ) + { + Torque::Path fullPath; + if( Torque::FS::GetFSPath( filename + sExtensions[ i ], fullPath ) ) + { + mSound = NULL; + FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_System_CreateSound( + SFXFMODDevice::smSystem, + fullPath.getFullPath().c_str(), + fMode, + ( FMOD_CREATESOUNDEXINFO* ) NULL, + &mSound ); + + if( result == FMOD_OK ) + { + SFXFMODDevice::smFunc->FMOD_Sound_GetMode( mSound, &mMode ); + + // Read out format. + + int numChannels; + int bitsPerSample; + unsigned int length; + float frequency; + + SFXFMODDevice::smFunc->FMOD_Sound_GetFormat( mSound, ( FMOD_SOUND_TYPE* ) NULL, ( FMOD_SOUND_FORMAT* ) NULL, &numChannels, &bitsPerSample ); + SFXFMODDevice::smFunc->FMOD_Sound_GetLength( mSound, &length, FMOD_TIMEUNIT_MS ); + SFXFMODDevice::smFunc->FMOD_Sound_GetDefaults( mSound, &frequency, ( float* ) NULL, ( float* ) NULL, ( int* ) NULL ); + + mDuration = length; + mFormat = SFXFormat( numChannels, numChannels * bitsPerSample, frequency ); + + break; + } + } + } + + if( !mSound ) + Con::errorf( "SFXFMODBuffer::SFXFMODBuffer - failed to load '%s' through FMOD", filename.c_str() ); +} + +//----------------------------------------------------------------------------- + +SFXFMODBuffer::SFXFMODBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) + : Parent( stream, description), + mSound( NULL ) +{ + FMOD_MODE fMode = FMOD_SOFTWARE | ( description->mIs3D ? FMOD_3D : FMOD_2D ); + + FMOD_CREATESOUNDEXINFO* pCreatesoundexinfo = NULL; + FMOD_CREATESOUNDEXINFO createsoundexinfo; + + fMode |= FMOD_OPENUSER; // this tells fmod we are supplying the data directly + if( isStreaming() ) + fMode |= FMOD_LOOP_NORMAL | FMOD_UNIQUE; + + const SFXFormat& format = getFormat(); + U32 channels = format.getChannels(); + U32 frequency = format.getSamplesPerSecond(); + U32 bitsPerChannel = format.getBitsPerSample() / channels; + U32 dataSize = mBufferSize; + + FMOD_SOUND_FORMAT sfxFmt = FMOD_SOUND_FORMAT_NONE; + switch(bitsPerChannel) + { + case 8: + sfxFmt = FMOD_SOUND_FORMAT_PCM8; + break; + case 16: + sfxFmt = FMOD_SOUND_FORMAT_PCM16; + break; + case 24: + sfxFmt = FMOD_SOUND_FORMAT_PCM24; + break; + case 32: + sfxFmt = FMOD_SOUND_FORMAT_PCM32; + break; + default: + AssertISV(false, "SFXFMODBuffer::SFXFMODBuffer() - unsupported bits-per-sample (what format is it in, 15bit PCM?)"); + break; + } + + dMemset(&createsoundexinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO)); + createsoundexinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO); /* required. */ + createsoundexinfo.decodebuffersize = frequency; /* Chunk size of stream update in samples. This will be the amount of data passed to the user callback. */ + createsoundexinfo.length = dataSize; /* Length of PCM data in bytes of whole sound (for Sound::getLength) */ + createsoundexinfo.numchannels = channels; /* Number of channels in the sound. */ + createsoundexinfo.defaultfrequency = frequency; /* Default playback rate of sound. */ + createsoundexinfo.format = sfxFmt; /* Data format of sound. */ + createsoundexinfo.pcmreadcallback = NULL; /* User callback for reading. */ + createsoundexinfo.pcmsetposcallback = NULL; /* User callback for seeking. */ + pCreatesoundexinfo = &createsoundexinfo; + + FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_System_CreateSound( + SFXFMODDevice::smSystem, + ( const char* ) NULL, + fMode, + pCreatesoundexinfo, + &mSound ); + + if( result != FMOD_OK ) + { + mSound = NULL; + Con::errorf( "SFXFMODBuffer::SFXFMODBuffer - failed to create buffer (%i)", result ); + } + else + SFXFMODDevice::smFunc->FMOD_Sound_GetMode( mSound, &mMode ); +} + +//----------------------------------------------------------------------------- + +SFXFMODBuffer::~SFXFMODBuffer() +{ + if( mSound ) + FModAssert( SFXFMODDevice::smFunc->FMOD_Sound_Release( mSound ), + "SFXFMODBuffer::~SFXFMODBuffer - Failed to release a sound!" ); + + mSound = NULL; +} + +//----------------------------------------------------------------------------- + +void SFXFMODBuffer::_flush() +{ + AssertFatal( isStreaming(), "SFXFMODBuffer::_flush() - not a streaming buffer" ); + AssertFatal( SFXInternal::isSFXThread(), "SFXFMODBuffer::_flush() - not on SFX thread" ); + + Parent::_flush(); + SFXFMODDevice::smFunc->FMOD_Channel_SetPosition + ( ( ( SFXFMODVoice* ) mUniqueVoice.getPointer() )->mChannel, 0, FMOD_TIMEUNIT_PCM ); +} + +//----------------------------------------------------------------------------- + +bool SFXFMODBuffer::_copyData( U32 offset, const U8* data, U32 length ) +{ + AssertFatal( data != NULL && length > 0, "Must have data!" ); + + // Fill the buffer with the resource data. + void* lpvWrite; + U32 dwLength; + void* lpvWrite2; + U32 dwLength2; + int res = SFXFMODDevice::smFunc->FMOD_Sound_Lock( + mSound, + offset, // Offset at which to start lock. + length, // Size of lock. + &lpvWrite, // Gets address of first part of lock. + &lpvWrite2, // Address of wraparound not needed. + &dwLength, // Gets size of first part of lock. + &dwLength2 // Size of wraparound not needed. + ); + + if ( res != FMOD_OK ) + { + // You can remove this if it gets spammy. However since we can + // safely fail in this case it doesn't seem right to assert... + // at the same time it can be very annoying not to know why + // an upload fails! + Con::errorf("SFXFMODBuffer::_copyData - failed to lock a sound buffer! (%d)", this); + return false; + } + + // Copy the first part. + dMemcpy( lpvWrite, data, dwLength ); + + // Do we have a wrap? + if ( lpvWrite2 ) + dMemcpy( lpvWrite2, data + dwLength, dwLength2 ); + + // And finally, unlock. + FModAssert( SFXFMODDevice::smFunc->FMOD_Sound_Unlock( + mSound, + lpvWrite, // Address of lock start. + lpvWrite2, // No wraparound portion. + dwLength, // Size of lock. + dwLength2 ), // No wraparound size. + "Failed to unlock sound buffer!" ); + + return true; +} + +//----------------------------------------------------------------------------- + +U32 SFXFMODBuffer::getMemoryUsed() const +{ + unsigned int memoryUsed; + + SFXFMODDevice::smFunc->FMOD_Sound_GetMemoryInfo( + mSound, + FMOD_MEMBITS_ALL, + FMOD_EVENT_MEMBITS_ALL, + &memoryUsed, + ( unsigned int* ) NULL ); + + return memoryUsed; +} diff --git a/sfx/fmod/sfxFMODBuffer.h b/sfx/fmod/sfxFMODBuffer.h new file mode 100644 index 0000000..cc41c43 --- /dev/null +++ b/sfx/fmod/sfxFMODBuffer.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXFMODBUFFER_H_ +#define _SFXFMODBUFFER_H_ + +#include "fmod.h" + +#ifndef _SFXINTERNAL_H_ +# include "sfx/sfxInternal.h" +#endif + + +class SFXFMODBuffer : public SFXInternal::SFXWrapAroundBuffer +{ + typedef SFXInternal::SFXWrapAroundBuffer Parent; + + friend class SFXFMODDevice; + friend class SFXFMODVoice; + + protected: + + FMOD_SOUND *mSound; + FMOD_MODE mMode; + + SFXFMODBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + SFXFMODBuffer( const String& filename, SFXDescription* description ); + + // SFXWrapAroundBuffer. + virtual bool _copyData( U32 offset, const U8* data, U32 length ); + virtual void _flush(); + + virtual ~SFXFMODBuffer(); + + public: + + /// + static SFXFMODBuffer* create( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + static SFXFMODBuffer* create( const String& filename, SFXDescription* description ); + + virtual U32 getMemoryUsed() const; +}; + +#endif // _SFXFMODBUFFER_H_ \ No newline at end of file diff --git a/sfx/fmod/sfxFMODDevice.cpp b/sfx/fmod/sfxFMODDevice.cpp new file mode 100644 index 0000000..c97c15d --- /dev/null +++ b/sfx/fmod/sfxFMODDevice.cpp @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/threads/mutex.h" +#include "sfx/fmod/sfxFMODDevice.h" +#include "sfx/fmod/sfxFMODBuffer.h" +#include "sfx/sfxListener.h" +#include "sfx/sfxSystem.h" +#include "platform/async/asyncUpdate.h" + + +FMOD_SYSTEM *SFXFMODDevice::smSystem; +FModFNTable *SFXFMODDevice::smFunc; +Mutex* FModFNTable::mutex; + + +SFXFMODDevice::SFXFMODDevice( SFXProvider* provider, + FModFNTable *fmodFnTbl, + int deviceIdx, + String name ) + : SFXDevice( name, provider, false, 32 ), + m3drolloffmode( FMOD_3D_LOGROLLOFF ), + mDeviceIndex( deviceIdx ) +{ + // Store off the function pointers for later use. + smFunc = fmodFnTbl; +} + +bool SFXFMODDevice::_init() +{ + #define FMOD_CHECK( message ) \ + if( result != FMOD_OK ) \ + { \ + Con::errorf( "SFXFMODDevice::_init() - %s (%s)", \ + message, \ + FMOD_ErrorString( result ) ); \ + return false; \ + } + + AssertISV(smSystem, + "SFXFMODDevice::_init() - can't init w/o an existing FMOD system handle!"); + + FMOD_RESULT result; + + // Initialize everything from fmod. + FMOD_SPEAKERMODE speakermode; + FMOD_CAPS caps; + result = smFunc->FMOD_System_GetDriverCaps(smSystem, 0, &caps, ( int* ) 0, ( int* ) 0, &speakermode); + FMOD_CHECK( "SFXFMODDevice::SFXFMODDevice - Failed to get driver caps" ); + + result = smFunc->FMOD_System_SetDriver(smSystem, mDeviceIndex); + FMOD_CHECK( "SFXFMODDevice::SFXFMODDevice - Failed to set driver" ); + + result = smFunc->FMOD_System_SetSpeakerMode(smSystem, speakermode); + FMOD_CHECK( "SFXFMODDevice::SFXFMODDevice - Failed to set the user selected speaker mode" ); + + if (caps & FMOD_CAPS_HARDWARE_EMULATED) /* The user has the 'Acceleration' slider set to off! This is really bad for latency!. */ + { /* You might want to warn the user about this. */ + result = smFunc->FMOD_System_SetDSPBufferSize(smSystem, 1024, 10); + FMOD_CHECK( "SFXFMODDevice::SFXFMODDevice - Failed to set DSP buffer size" ); + } + + result = smFunc->FMOD_System_Init(smSystem, 100, FMOD_INIT_NORMAL, ( void* ) 0 ); + if( result == FMOD_ERR_OUTPUT_CREATEBUFFER ) /* Ok, the speaker mode selected isn't supported by this soundcard. Switch it back to stereo... */ + { + result = smFunc->FMOD_System_SetSpeakerMode(smSystem, FMOD_SPEAKERMODE_STEREO); + FMOD_CHECK( "SFXFMODDevice::SFXFMODDevice - failed on fallback speaker mode setup" ); + result = smFunc->FMOD_System_Init(smSystem, 100, FMOD_INIT_NORMAL, ( void* ) 0); + } + FMOD_CHECK( "SFXFMODDevice::SFXFMODDevice - failed to init system" ); + + // we let FMod handle this stuff, instead of having sfx do it + //mCullInaudible = false; + + // Start the update thread. + + if( !Con::getBoolVariable( "$_forceAllMainThread" ) ) + { + SFXInternal::gUpdateThread = new AsyncPeriodicUpdateThread + ( "FMOD Update Thread", SFXInternal::gBufferUpdateList, + Con::getIntVariable( "$pref::SFX::updateInterval", SFXInternal::DEFAULT_UPDATE_INTERVAL ) ); + SFXInternal::gUpdateThread->start(); + } + + return true; +} + +SFXFMODDevice::~SFXFMODDevice() +{ + _releaseAllResources(); + + smFunc->FMOD_System_Close( smSystem ); +} + +SFXBuffer* SFXFMODDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + AssertFatal( stream, "SFXFMODDevice::createBuffer() - Got a null stream!" ); + AssertFatal( description, "SFXFMODDevice::createBuffer() - Got null description!" ); + + SFXFMODBuffer *buffer = SFXFMODBuffer::create( stream, description ); + if ( buffer ) + _addBuffer( buffer ); + + return buffer; +} + +SFXBuffer* SFXFMODDevice::createBuffer( const String& filename, SFXDescription* description ) +{ + AssertFatal( filename.isNotEmpty(), "SFXFMODDevice::createBuffer() - Got an empty filename!" ); + AssertFatal( description, "SFXFMODDevice::createBuffer() - Got null description!" ); + + SFXFMODBuffer* buffer = SFXFMODBuffer::create( filename, description ); + if( buffer ) + _addBuffer( buffer ); + + return buffer; +} + +SFXVoice* SFXFMODDevice::createVoice( bool is3D, SFXBuffer* buffer ) +{ + AssertFatal( buffer, "SFXFMODDevice::createVoice() - Got null buffer!" ); + + SFXFMODBuffer* fmodBuffer = dynamic_cast( buffer ); + AssertFatal( fmodBuffer, "SFXFMODDevice::createVoice() - Got bad buffer!" ); + + SFXFMODVoice* voice = SFXFMODVoice::create( this, fmodBuffer ); + if ( !voice ) + return NULL; + + _addVoice( voice ); + return voice; +} + +void SFXFMODDevice::update( const SFXListener& listener ) +{ + Parent::update( listener ); + + // Set the listener state on fmod! + Point3F position, vel, fwd, up; + vel = listener.getVelocity(); + listener.getTransform().getColumn( 3, &position ); + listener.getTransform().getColumn( 1, &fwd ); + listener.getTransform().getColumn( 2, &up ); + + // We have to convert to FMOD_VECTOR, thus this cacophony. + // NOTE: we correct for handedness here. We model off of the d3d device. + // Basically, XYZ => XZY. + FMOD_VECTOR fposition, fvel, ffwd, fup; +#define COPY_FMOD_VECTOR(a) f##a.x = a.x; f##a.y = a.z; f##a.z = a.y; + COPY_FMOD_VECTOR(position) + COPY_FMOD_VECTOR(vel) + COPY_FMOD_VECTOR(fwd) + COPY_FMOD_VECTOR(up) +#undef COPY_FMOD_VECTOR + + // Do the listener state update, then update! + FModAssert(smFunc->FMOD_System_Set3DListenerAttributes(smSystem, 0, &fposition, &fvel, &ffwd, &fup), "Failed to set 3d listener attribs!"); + FModAssert(smFunc->FMOD_System_Update(smSystem), "Failed to update system!"); +} + +void SFXFMODDevice::setDistanceModel( SFXDistanceModel model ) +{ + switch( model ) + { + case SFXDistanceModelLinear: + m3drolloffmode = FMOD_3D_LINEARROLLOFF; + break; + + case SFXDistanceModelLogarithmic: + m3drolloffmode = FMOD_3D_LOGROLLOFF; + break; + + default: + AssertWarn( false, "SFXFMODDevice::setDistanceModel - model not implemented" ); + } +} + +void SFXFMODDevice::setDopplerFactor( F32 factor ) +{ + F32 dopplerFactor; + F32 distanceFactor; + F32 rolloffFactor; + + smFunc->FMOD_System_Get3DSettings( smSystem, &dopplerFactor, &distanceFactor, &rolloffFactor ); + dopplerFactor = factor; + smFunc->FMOD_System_Set3DSettings( smSystem, dopplerFactor, distanceFactor, rolloffFactor ); +} + +void SFXFMODDevice::setRolloffFactor( F32 factor ) +{ + F32 dopplerFactor; + F32 distanceFactor; + F32 rolloffFactor; + + smFunc->FMOD_System_Get3DSettings( smSystem, &dopplerFactor, &distanceFactor, &rolloffFactor ); + rolloffFactor = factor; + smFunc->FMOD_System_Set3DSettings( smSystem, dopplerFactor, distanceFactor, rolloffFactor ); +} + +ConsoleFunction(fmodDumpMemoryStats, void, 1, 1, "()") +{ + int current = 0; + int max = 0; + + if (SFXFMODDevice::smFunc && SFXFMODDevice::smFunc->FMOD_Memory_GetStats.fn) + SFXFMODDevice::smFunc->FMOD_Memory_GetStats(¤t, &max); + Con::printf("Fmod current: %d, max: %d", current, max); +} \ No newline at end of file diff --git a/sfx/fmod/sfxFMODDevice.h b/sfx/fmod/sfxFMODDevice.h new file mode 100644 index 0000000..9637c96 --- /dev/null +++ b/sfx/fmod/sfxFMODDevice.h @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXFMODDEVICE_H_ +#define _SFXFMODDEVICE_H_ + +#ifndef _SFXDEVICE_H_ + #include "sfx/sfxDevice.h" +#endif +#ifndef _SFXFMODVOICE_H_ + #include "sfx/fmod/sfxFMODVoice.h" +#endif +#ifndef _SFXFMODBUFFER_H_ + #include "sfx/fmod/sfxFMODBuffer.h" +#endif + +#include "core/util/tDictionary.h" + + +// Disable warning for unused static functions. +#ifdef TORQUE_COMPILER_VISUALC +# pragma warning( disable : 4505 ) +#endif + + +#include "fmod.h" +#include "fmod_errors.h" + +#include "platform/platformDlibrary.h" +#include "platform/threads/mutex.h" + + +// This doesn't appear to exist in some contexts, so let's just add it. +#ifdef TORQUE_OS_WIN32 +#ifndef WINAPI +#define WINAPI __stdcall +#endif +#else +#define WINAPI +#endif + + +#define FModAssert(x, msg) \ + { FMOD_RESULT result = ( x ); \ + AssertISV( result == FMOD_OK, String::ToString( "%s: %s", msg, FMOD_ErrorString( result ) ) ); } + +#define FMOD_FN_FILE "sfx/fmod/fmodFunctions.h" + + +// Typedefs +#define FMOD_FUNCTION(fn_name, fn_args) \ + typedef FMOD_RESULT (WINAPI *FMODFNPTR##fn_name)fn_args; +#include FMOD_FN_FILE +#undef FMOD_FUNCTION + + +/// FMOD API function table. +/// +/// FMOD doesn't want to be called concurrently so in order to +/// not force everything to the main thread (where sound updates +/// would just stall during loading), we thunk all the API +/// calls and lock all API entry points to a single mutex. +struct FModFNTable +{ + FModFNTable() + : isLoaded( false ) + { + AssertFatal( mutex == NULL, + "FModFNTable::FModFNTable() - this should be a singleton" ); + mutex = new Mutex; + } + ~FModFNTable() + { + delete mutex; + } + + bool isLoaded; + DLibraryRef dllRef; + static Mutex* mutex; + + template< typename FN > + struct Thunk + { + FN fn; + + template< typename A > + FMOD_RESULT operator()( A a ) + { + mutex->lock(); + FMOD_RESULT result = fn( a ); + mutex->unlock(); + return result; + } + template< typename A, typename B > + FMOD_RESULT operator()( A a, B b ) + { + mutex->lock(); + FMOD_RESULT result = fn( a, b ); + mutex->unlock(); + return result; + } + template< typename A, typename B, typename C > + FMOD_RESULT operator()( A a, B b, C c ) + { + mutex->lock(); + FMOD_RESULT result = fn( a, b, c ); + mutex->unlock(); + return result; + } + template< typename A, typename B, typename C, typename D > + FMOD_RESULT operator()( A a, B b, C c, D d ) + { + mutex->lock(); + FMOD_RESULT result = fn( a, b, c, d ); + mutex->unlock(); + return result; + } + template< typename A, typename B, typename C, typename D, typename E > + FMOD_RESULT operator()( A a, B b, C c, D d, E e ) + { + mutex->lock(); + FMOD_RESULT result = fn( a, b, c, d, e ); + mutex->unlock(); + return result; + } + template< typename A, typename B, typename C, typename D, typename E, typename F > + FMOD_RESULT operator()( A a, B b, C c, D d, E e, F f ) + { + mutex->lock(); + FMOD_RESULT result = fn( a, b, c, d, e, f ); + mutex->unlock(); + return result; + } + template< typename A, typename B, typename C, typename D, typename E, typename F, typename G > + FMOD_RESULT operator()( A a, B b, C c, D d, E e, F f, G g ) + { + mutex->lock(); + FMOD_RESULT result = fn( a, b, c, d, e, f, g ); + mutex->unlock(); + return result; + } + }; + +#define FMOD_FUNCTION(fn_name, fn_args) \ + Thunk< FMODFNPTR##fn_name > fn_name; +#include FMOD_FN_FILE +#undef FMOD_FUNCTION +}; + + + +class SFXProvider; + + + +class SFXFMODDevice : public SFXDevice +{ + public: + + typedef SFXDevice Parent; + friend class SFXFMODProvider; // _init + + explicit SFXFMODDevice(); + + SFXFMODDevice( SFXProvider* provider, FModFNTable *fmodFnTbl, int deviceIdx, String name ); + + virtual ~SFXFMODDevice(); + + protected: + + FMOD_MODE m3drolloffmode; + int mDeviceIndex; + + bool _init(); + + public: + + FMOD_MODE get3dRollOffMode() { return m3drolloffmode; } + + static FMOD_SYSTEM *smSystem; + static FModFNTable *smFunc; + + // SFXDevice. + virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + virtual SFXBuffer* createBuffer( const String& filename, SFXDescription* description ); + virtual SFXVoice* createVoice( bool is3D, SFXBuffer* buffer ); + virtual void update( const SFXListener& listener ); + virtual void setDistanceModel( SFXDistanceModel model ); + virtual void setDopplerFactor( F32 factor ); + virtual void setRolloffFactor( F32 factor ); +}; + +#endif // _SFXFMODDEVICE_H_ diff --git a/sfx/fmod/sfxFMODProvider.cpp b/sfx/fmod/sfxFMODProvider.cpp new file mode 100644 index 0000000..4c62d89 --- /dev/null +++ b/sfx/fmod/sfxFMODProvider.cpp @@ -0,0 +1,194 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxProvider.h" +#include "sfx/fmod/sfxFMODDevice.h" +#include "core/util/safeRelease.h" +#include "console/console.h" +#include "core/util/safeDelete.h" + + +class SFXFMODProvider : public SFXProvider +{ +public: + + SFXFMODProvider() + : SFXProvider( "FMOD" ) {} + virtual ~SFXFMODProvider(); + +protected: + FModFNTable mFMod; + + struct FModDeviceInfo : SFXDeviceInfo + { + }; + + void init(); + +public: + + SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ); + +}; + +SFX_INIT_PROVIDER( SFXFMODProvider ); + +//------------------------------------------------------------------------------ +// Helper + +bool fmodBindFunction( DLibrary *dll, void *&fnAddress, const char* name ) +{ + fnAddress = dll->bind( name ); + + if (!fnAddress) + Con::warnf( "FMod Loader: DLL bind failed for %s", name ); + + return fnAddress != 0; +} + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +void SFXFMODProvider::init() +{ + const char* dllName; +#ifdef TORQUE_OS_WIN32 + dllName = "fmodex.dll"; +#elif defined( TORQUE_OS_MAC ) + dllName = "libfmodex.dylib"; +#elif defined( TORQUE_OS_XENON ) || defined( TORQUE_OS_PS3 ) + dllName = "FMOD static library"; +#else +# warning Need to set FMOD DLL filename for platform. + return; +#endif + +#if !defined(TORQUE_OS_XENON) && !defined(TORQUE_OS_PS3) + // Grab the functions we'll want from the fmod DLL. + mFMod.dllRef = OsLoadLibrary( dllName ); + + if(!mFMod.dllRef) + { + Con::warnf( "SFXFMODProvider - Could not locate %s - FMod not available.", dllName ); + return; + } +#define FMOD_FUNCTION(fn_name, fn_args) \ + mFMod.isLoaded &= fmodBindFunction(mFMod.dllRef, *(void**)&mFMod.fn_name.fn, #fn_name); +#else +#define FMOD_FUNCTION(fn_name, fn_args) \ + (*(void**)&mFMod.fn_name.fn) = &fn_name; +#endif + + mFMod.isLoaded = true; + +#include "sfx/fmod/fmodFunctions.h" +#undef FMOD_FUNCTION + + if(mFMod.isLoaded == false) + { + Con::warnf("SFXFMODProvider - Could not locate %s - FMod not available.", dllName); + return; + } + + // Allocate the FMod system. + FMOD_RESULT res = mFMod.FMOD_System_Create(&SFXFMODDevice::smSystem); + + if(res != FMOD_OK) + { + // Failed - deal with it! + Con::warnf("SFXFMODProvider - Could not create the FMod system - FMod not available."); + return; + } + + // Check that the version is OK. + unsigned int version; + res = mFMod.FMOD_System_GetVersion(SFXFMODDevice::smSystem, &version); + FModAssert(res, "SFXFMODProvider - Failed to get fmod version!"); + + if(version < FMOD_VERSION) + { + Con::warnf("SFXFMODProvider - FMod version in DLL is too old - FMod not available."); + return; + } + + int majorVersion = ( version & 0xffff0000 ) >> 16; + int minorVersion = ( version & 0x0000ff00 ) >> 8; + int revision = ( version & 0x000000ff ); + Con::printf( "SFXFMODProvider - FMOD version: %i.%i.%i", majorVersion, minorVersion, revision ); + + // Now, enumerate our devices. + int numDrivers; + res = mFMod.FMOD_System_GetNumDrivers(SFXFMODDevice::smSystem, &numDrivers); + FModAssert(res, "SFXFMODProvider - Failed to get driver count - FMod not available."); + + char nameBuff[256]; + + for(S32 i=0; iname = String( nameBuff ); +#ifdef TORQUE_OS_WIN32 //RDFIXME: why do we have this? + fmodInfo->hasHardware = false; +#else + fmodInfo->hasHardware = true; +#endif + fmodInfo->maxBuffers = 32; + fmodInfo->driver = String(); + + mDeviceInfo.push_back(fmodInfo); + } + + // Did we get any devices? + if ( mDeviceInfo.empty() ) + { + Con::warnf( "SFXFMODProvider - No valid devices found - FMod not available." ); + return; + } + + // TODO: FMOD_Memory_Initialize +#ifdef TORQUE_OS_XENON + const dsize_t memSz = 5 * 1024 * 1024; + void *memBuffer = XPhysicalAlloc( memSz, MAXULONG_PTR, 0, PAGE_READWRITE ); + mFMod.FMOD_Memory_Initialize( memBuffer, memSz, FMOD_MEMORY_ALLOCCALLBACK(NULL), FMOD_MEMORY_REALLOCCALLBACK(NULL), FMOD_MEMORY_FREECALLBACK(NULL) ); +#endif + + // Wow, we made it - register the provider. + regProvider( this ); +} + +SFXFMODProvider::~SFXFMODProvider() +{ + // Release the fmod system. + if ( mFMod.isLoaded ) + { + mFMod.FMOD_System_Release(SFXFMODDevice::smSystem); + SFXFMODDevice::smSystem = NULL; + } +} + +SFXDevice* SFXFMODProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ) +{ + FModDeviceInfo* info = dynamic_cast< FModDeviceInfo* > + ( _findDeviceInfo( deviceName ) ); + + if( !info ) + return NULL; + + SFXFMODDevice* device = new SFXFMODDevice(this, &mFMod, 0, info->name ); + if( !device->_init() ) + SAFE_DELETE( device ); + + return device; +} diff --git a/sfx/fmod/sfxFMODVoice.cpp b/sfx/fmod/sfxFMODVoice.cpp new file mode 100644 index 0000000..d326a3a --- /dev/null +++ b/sfx/fmod/sfxFMODVoice.cpp @@ -0,0 +1,264 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sfx/fmod/sfxFMODVoice.h" + +#include "sfx/fmod/sfxFMODBuffer.h" +#include "sfx/fmod/sfxFMODDevice.h" +#include "core/tAlgorithm.h" + + +SFXFMODVoice* SFXFMODVoice::create( SFXFMODDevice *device, + SFXFMODBuffer *buffer ) +{ + AssertFatal( device, "SFXFMODVoice::create() - Got null device!" ); + AssertFatal( buffer, "SFXFMODVoice::create() - Got null buffer!" ); + + SFXFMODVoice* voice = new SFXFMODVoice( device, buffer ); + + /* + // A voice should have a channel assigned + // for its entire lifetime. + if ( !voice->_assignChannel() ) + { + delete voice; + return NULL; + } + */ + + return voice; +} + +SFXFMODVoice::SFXFMODVoice( SFXFMODDevice *device, + SFXFMODBuffer *buffer ) + : Parent( buffer ), + mDevice( device ), + mChannel( NULL ) +{ + AssertFatal( device, "SFXFMODVoice::SFXFMODVoice() - No device assigned!" ); + AssertFatal( buffer, "SFXFMODVoice::SFXFMODVoice() - No buffer assigned!" ); + AssertFatal( _getBuffer()->mSound != NULL, "SFXFMODVoice::SFXFMODVoice() - No sound assigned!" ); +} + +SFXFMODVoice::~SFXFMODVoice() +{ + _stop(); +} + +SFXStatus SFXFMODVoice::_status() const +{ + if( mChannel ) + { + FMOD_BOOL isTrue = false; + SFXFMODDevice::smFunc->FMOD_Channel_GetPaused( mChannel, &isTrue ); + if ( isTrue ) + return SFXStatusPaused; + + SFXFMODDevice::smFunc->FMOD_Channel_IsPlaying( mChannel, &isTrue ); + if ( isTrue ) + return SFXStatusPlaying; + } + + return SFXStatusStopped; +} + +void SFXFMODVoice::_play() +{ + if( !mChannel ) + _assignChannel(); + + FModAssert( SFXFMODDevice::smFunc->FMOD_Channel_SetPaused( mChannel, false ), + "SFXFMODBuffer::play - Failed to unpause sound!" ); +} + +void SFXFMODVoice::_pause() +{ + if( mChannel ) + SFXFMODDevice::smFunc->FMOD_Channel_SetPaused( mChannel, true ); +} + +void SFXFMODVoice::_stop() +{ + if( mChannel ) + SFXFMODDevice::smFunc->FMOD_Channel_Stop(mChannel); + + mChannel = NULL; +} + +void SFXFMODVoice::_seek( U32 sample ) +{ + if( !mChannel ) + _assignChannel(); + + SFXFMODDevice::smFunc->FMOD_Channel_SetPosition + ( mChannel, sample, FMOD_TIMEUNIT_PCM ); +} + +bool SFXFMODVoice::_assignChannel() +{ + AssertFatal( _getBuffer()->mSound != NULL, "SFXFMODVoice::_assignChannel() - No sound assigned!" ); + + // we start playing it now in the paused state, so that we can immediately set attributes that + // depend on having a channel (position, volume, etc). According to the FMod docs + // it is ok to do this. + bool success = SFXFMODDevice::smFunc->FMOD_System_PlaySound( + SFXFMODDevice::smSystem, + FMOD_CHANNEL_FREE, + _getBuffer()->mSound, + true, + &mChannel ) == FMOD_OK; + + if( success ) + { + if( mSetFlags.test( SET_Velocity ) ) + SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, ( const FMOD_VECTOR* ) NULL, &mVelocity ); + if( mSetFlags.test( SET_MinMaxDistance ) ) + SFXFMODDevice::smFunc->FMOD_Channel_Set3DMinMaxDistance(mChannel, mMinDistance, mMaxDistance); + if( mSetFlags.test( SET_Transform ) ) + { + SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, &mPosition, ( const FMOD_VECTOR* ) NULL ); + SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeOrientation( mChannel, &mDirection ); + } + if( mSetFlags.test( SET_Volume ) ) + SFXFMODDevice::smFunc->FMOD_Channel_SetVolume(mChannel, mVolume); + if( mSetFlags.test( SET_Pitch ) ) + SFXFMODDevice::smFunc->FMOD_Channel_SetFrequency( mChannel, mFrequency ); + if( mSetFlags.test( SET_Cone ) ) + SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeSettings( + mChannel, + mConeInnerAngle, + mConeOuterAngle, + mConeOuterVolume ); + } + + return success; +} + +U32 SFXFMODVoice::_tell() const +{ + if( !mChannel ) + return 0; + + U32 pos; + SFXFMODDevice::smFunc->FMOD_Channel_GetPosition( mChannel, &pos, ( FMOD_TIMEUNIT ) FMOD_TIMEUNIT_PCMBYTES ); + return _getBuffer()->getSamplePos( pos ); +} + +void SFXFMODVoice::setMinMaxDistance( F32 min, F32 max ) +{ + if ( !( _getBuffer()->mMode & FMOD_3D ) ) + return; + + mMinDistance = min; + mMaxDistance = max; + + mSetFlags.set( SET_MinMaxDistance ); + + if( mChannel ) + SFXFMODDevice::smFunc->FMOD_Channel_Set3DMinMaxDistance(mChannel, mMinDistance, mMaxDistance); +} + +void SFXFMODVoice::play( bool looping ) +{ + if( mBuffer->isStreaming() ) + looping = true; + + S32 loopMode = looping ? -1 : 0; + + if ( !mChannel ) + _assignChannel(); + + FMOD_MODE mode = mDevice->get3dRollOffMode(); + mode |= (looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF); + + FModAssert(SFXFMODDevice::smFunc->FMOD_Channel_SetMode(mChannel, mode), "SFXFMODBuffer::play - failed to set mode on sound."); + FModAssert(SFXFMODDevice::smFunc->FMOD_Channel_SetLoopCount(mChannel, loopMode), "SFXFMODBuffer::play - Failed to set looping!"); + + Parent::play( looping ); +} + +void SFXFMODVoice::setVelocity( const VectorF& velocity ) +{ + if( !( _getBuffer()->mMode & FMOD_3D ) ) + return; + + // Note we have to do a handedness swap; see the + // listener update code in SFXFMODDevice for details. + mVelocity.x = velocity.x; + mVelocity.y = velocity.z; + mVelocity.z = velocity.y; + + mSetFlags.set( SET_Velocity ); + + if( mChannel ) + SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, ( const FMOD_VECTOR* ) NULL, &mVelocity ); +} + +void SFXFMODVoice::setTransform( const MatrixF& transform ) +{ + if ( !( _getBuffer()->mMode & FMOD_3D ) ) + return; + + transform.getColumn( 3, (Point3F*)&mPosition ); + transform.getColumn( 1, (Point3F*)&mDirection ); + + // Note we have to do a handedness swap; see the + // listener update code in SFXFMODDevice for details. + swap( mPosition.y, mPosition.z ); + swap( mDirection.y, mDirection.z ); + + mSetFlags.set( SET_Transform ); + + if( mChannel ) + { + // This can fail safe, so don't assert if it fails. + SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, &mPosition, ( const FMOD_VECTOR* ) NULL ); + SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeOrientation( mChannel, &mDirection ); + } +} + +void SFXFMODVoice::setVolume( F32 volume ) +{ + mVolume = volume; + + mSetFlags.set( SET_Volume ); + + if( mChannel ) + SFXFMODDevice::smFunc->FMOD_Channel_SetVolume(mChannel, volume); +} + +void SFXFMODVoice::setPitch( F32 pitch ) +{ + // if we do not know the frequency, we cannot change the pitch + F32 frequency = _getBuffer()->getFormat().getSamplesPerSecond(); + if ( frequency == 0 ) + return; + + mFrequency = frequency * pitch; + + mSetFlags.set( SET_Pitch ); + + // Scale the original frequency by the pitch factor. + if( mChannel ) + SFXFMODDevice::smFunc->FMOD_Channel_SetFrequency(mChannel, mFrequency); +} + +void SFXFMODVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ) +{ + mConeInnerAngle = innerAngle; + mConeOuterAngle = outerAngle; + mConeOuterVolume = outerVolume; + + mSetFlags.set( SET_Cone ); + + if( mChannel ) + SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeSettings( + mChannel, + mConeInnerAngle, + mConeOuterAngle, + mConeOuterVolume ); +} + diff --git a/sfx/fmod/sfxFMODVoice.h b/sfx/fmod/sfxFMODVoice.h new file mode 100644 index 0000000..0352850 --- /dev/null +++ b/sfx/fmod/sfxFMODVoice.h @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXFMODVOICE_H_ +#define _SFXFMODVOICE_H_ + +#ifndef _SFXDEVICE_H_ + #include "sfx/sfxDevice.h" +#endif +#ifndef _SFXVOICE_H_ + #include "sfx/sfxVoice.h" +#endif +#ifndef _BITSET_H_ + #include "core/bitSet.h" +#endif + +#include "fmod.h" + +class SFXSource; +class SFXFMODBuffer; +class SFXFMODDevice; + + +class SFXFMODVoice : public SFXVoice +{ + typedef SFXVoice Parent; + friend class SFXFMODBuffer; + + protected: + + SFXFMODDevice *mDevice; + + FMOD_CHANNEL *mChannel; + + enum ESettings + { + SET_MinMaxDistance = BIT( 0 ), + SET_Velocity = BIT( 1 ), + SET_Transform = BIT( 2 ), + SET_Volume = BIT( 3 ), + SET_Pitch = BIT( 4 ), + SET_Cone = BIT( 5 ) + }; + + BitSet32 mSetFlags; + + F32 mMinDistance; + F32 mMaxDistance; + F32 mVolume; + F32 mFrequency; + F32 mConeInnerAngle; + F32 mConeOuterAngle; + F32 mConeOuterVolume; + FMOD_VECTOR mVelocity; + FMOD_VECTOR mPosition; + FMOD_VECTOR mDirection; + + /// + SFXFMODVoice( SFXFMODDevice *device, + SFXFMODBuffer *buffer ); + + // prep for playback + bool _assignChannel(); + + SFXFMODBuffer* _getBuffer() const { return ( SFXFMODBuffer* ) mBuffer.getPointer(); } + + // SFXVoice. + virtual SFXStatus _status() const; + virtual void _play(); + virtual void _pause(); + virtual void _stop(); + virtual void _seek( U32 sample ); + virtual U32 _tell() const; + + public: + + /// + static SFXFMODVoice* create( SFXFMODDevice *device, + SFXFMODBuffer *buffer ); + + /// + virtual ~SFXFMODVoice(); + + /// SFXVoice + void setMinMaxDistance( F32 min, F32 max ); + void play( bool looping ); + void setVelocity( const VectorF& velocity ); + void setTransform( const MatrixF& transform ); + void setVolume( F32 volume ); + void setPitch( F32 pitch ); + void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ); +}; + +#endif // _SFXFMODBUFFER_H_ \ No newline at end of file diff --git a/sfx/media/sfxVorbisStream.cpp b/sfx/media/sfxVorbisStream.cpp new file mode 100644 index 0000000..081b70c --- /dev/null +++ b/sfx/media/sfxVorbisStream.cpp @@ -0,0 +1,266 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifdef TORQUE_OGGVORBIS + + +#include "sfx/media/sfxVorbisStream.h" +#include "core/stream/stream.h" +#include "console/console.h" + + +SFXVorbisStream* SFXVorbisStream::create( Stream *stream ) +{ + SFXVorbisStream *sfxStream = new SFXVorbisStream(); + if ( sfxStream->open( stream, true ) ) + return sfxStream; + + delete sfxStream; + return NULL; +} + +SFXVorbisStream::SFXVorbisStream() + : mVF( NULL ), + mBytesRead( 0 ) +{ +} + +SFXVorbisStream::SFXVorbisStream( const SFXVorbisStream& cloneFrom ) + : Parent( cloneFrom ) +{ + if( !mStream->hasCapability( Stream::StreamPosition ) ) + { + Con::errorf( "SFXVorbisStream::SFXVorbisStream() - Source stream does not allow seeking" ); + return; + } + + mStream->setPosition( 0 ); + if( !_openVorbis() ) + { + Con::errorf( "SFXVorbisStream::SFXVorbisStream() - Opening Vorbis stream failed" ); + return; + } + + ov_pcm_seek( mVF, ov_pcm_tell( cloneFrom.mVF ) ); + + mBitstream = cloneFrom.mBitstream; + mBytesRead = cloneFrom.mBytesRead; +} + +SFXVorbisStream::~SFXVorbisStream() +{ + // We must call close from our own destructor + // and not the base class... as it causes a + // pure virtual runtime assertion. + close(); +} + +size_t SFXVorbisStream::_read_func( void *ptr, size_t size, size_t nmemb, void *datasource ) +{ + Stream *stream = reinterpret_cast( datasource ); + + // Stream::read() returns true if any data was + // read, so we must track the read bytes ourselves. + U32 startByte = stream->getPosition(); + stream->read( size * nmemb, ptr ); + U32 endByte = stream->getPosition(); + + // How many did we actually read? + U32 readBytes = ( endByte - startByte ); + U32 readItems = readBytes / size; + + return readItems; +} + +int SFXVorbisStream::_seek_func( void *datasource, ogg_int64_t offset, int whence ) +{ + Stream *stream = reinterpret_cast( datasource ); + + U32 newPos = 0; + if ( whence == SEEK_CUR ) + newPos = stream->getPosition() + (U32)offset; + else if ( whence == SEEK_END ) + newPos = stream->getStreamSize() - (U32)offset; + else + newPos = (U32)offset; + + return stream->setPosition( newPos ) ? 0 : -1; +} + +long SFXVorbisStream::_tell_func( void *datasource ) +{ + Stream *stream = reinterpret_cast( datasource ); + return stream->getPosition(); +} + +bool SFXVorbisStream::_openVorbis() +{ +#if defined(TORQUE_OS_XENON) + // For some reason the datasource pointer passed to the callbacks is not the + // same as it is when passed in to ov_open_callbacks +#pragma message("There is a strange bug in ov_open_callbacks as it compiles on the Xbox360. Use FMOD resource loading.") + AssertFatal(false, "There is a strange bug in ov_open_callbacks as it compiles on the Xbox360. Use FMOD resource loading."); + return false; +#endif + mVF = new OggVorbis_File; + dMemset( mVF, 0, sizeof( OggVorbis_File ) ); + + const bool canSeek = mStream->hasCapability( Stream::StreamPosition ); + + ov_callbacks cb; + cb.read_func = _read_func; + cb.seek_func = canSeek ? _seek_func : NULL; + cb.close_func = NULL; + cb.tell_func = canSeek ? _tell_func : NULL; + + // Open it. + int ovResult = ov_open_callbacks( mStream, mVF, NULL, 0, cb ); + if( ovResult != 0 ) + return false; + + return true; +} + +bool SFXVorbisStream::_readHeader() +{ + if( !_openVorbis() ) + return false; + + // Fill in the format info. + const vorbis_info *vi = getInfo(); + mFormat.set( vi->channels, vi->channels * 16, vi->rate ); + + // Set the sample count. + mSamples = getPcmTotal(); + + // Reset the bitstream. + mBitstream = 0; + + return true; +} + +void SFXVorbisStream::_close() +{ + if ( !mVF ) + return; + + ov_clear( mVF ); + delete mVF; + mVF = NULL; + mBitstream = -1; +} + +const vorbis_info* SFXVorbisStream::getInfo( S32 link ) +{ + AssertFatal( mVF, "SFXVorbisStream::getInfo() - Stream is closed!" ); + return ov_info( mVF, link ); +} + +const vorbis_comment* SFXVorbisStream::getComment( S32 link ) +{ + AssertFatal( mVF, "SFXVorbisStream::getComment() - Stream is closed!" ); + return ov_comment( mVF, link ); +} + +U64 SFXVorbisStream::getPcmTotal( S32 link ) +{ + AssertFatal( mVF, "SFXVorbisStream::getInfo() - Stream is closed!" ); + return ov_pcm_total( mVF, link ); +} + +S32 SFXVorbisStream::read( U8 *buffer, + U32 length, + S32 *bitstream ) +{ + AssertFatal( mVF, "SFXVorbisStream::read() - Stream is closed!" ); + + mBitstream = *bitstream; + + #ifdef TORQUE_BIG_ENDIAN + static const int isBigEndian = 1; + #else + static const int isBigEndian = 0; + #endif + + // Vorbis doesn't seem to like reading + // requests longer than this. + const U32 MAXREAD = 4096; + + U32 bytesRead = 0; + U32 offset = 0; + U32 bytesToRead = 0; + + // Since it only returns the result of one packet + // per call, you generally have to loop to read it all. + while( offset < length ) + { + if ( ( length - offset ) < MAXREAD ) + bytesToRead = length - offset; + else + bytesToRead = MAXREAD; + + bytesRead = ov_read( mVF, (char*)buffer, bytesToRead, isBigEndian, 2, 1, bitstream ); + if( bytesRead == 0 ) // EOF + return offset; + else if( bytesRead < 0 ) + { + // We got an error... return the result. + return bytesRead; + } + + offset += bytesRead; + buffer += bytesRead; + mBytesRead += bytesRead; + } + + // Return the total data read. + return offset; +} + +bool SFXVorbisStream::isEOS() const +{ + return ( Parent::isEOS() || ( mStream && ov_pcm_tell( mVF ) == mSamples ) ); +} + +void SFXVorbisStream::reset() +{ + AssertFatal( mVF, "SFXVorbisStream::reset() - Stream is closed!" ); + + // There's a bug in libvorbis 1.2.0 that will cause the + // ov_pcm_seek* functions to go into an infinite loop on + // some files (apparently if they contain Theora streams). + // Avoiding to seek when not necessary seems to do the trick + // but if it deadlocks here, that's why. + + if( mBytesRead > 0 ) + { + ov_pcm_seek_page( mVF, 0 ); + mBitstream = 0; + mBytesRead = 0; + } +} + +U32 SFXVorbisStream::getPosition() const +{ + AssertFatal( mVF, "SFXVorbisStream::getPosition() - Stream is closed!" ); + return U32( ov_pcm_tell( mVF ) ) * mFormat.getBytesPerSample(); +} + +void SFXVorbisStream::setPosition( U32 offset ) +{ + AssertFatal( mVF, "SFXVorbisStream::setPosition() - Stream is closed!" ); + ov_pcm_seek( mVF, offset / mFormat.getBytesPerSample() ); +} + +U32 SFXVorbisStream::read( U8 *buffer, U32 length ) +{ + S32 result = read( buffer, length, &mBitstream ); + if ( result < 0 ) + return 0; + + return result; +} + +#endif // TORQUE_OGGVORBIS diff --git a/sfx/media/sfxVorbisStream.h b/sfx/media/sfxVorbisStream.h new file mode 100644 index 0000000..05e75e6 --- /dev/null +++ b/sfx/media/sfxVorbisStream.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXVORBISSTREAM_H_ +#define _SFXVORBISSTREAM_H_ + +#ifdef TORQUE_OGGVORBIS + +#ifndef _SFXFILESTREAM_H_ +# include "sfx/sfxFileStream.h" +#endif +#include "core/util/safeDelete.h" +#include "vorbis/vorbisfile.h" + + +/// A simple wrapper around OggVorbis_File which +/// works with the Torque Stream classes. +class SFXVorbisStream : public SFXFileStream, + public IPositionable< U32 > +{ + public: + + typedef SFXFileStream Parent; + + protected: + + /// The vorbis file. + OggVorbis_File *mVF; + + /// The current bitstream index. + S32 mBitstream; + + /// Total number of bytes read from the Vorbis stream so far. + U32 mBytesRead; + + /// + bool _openVorbis(); + + // The ov_callbacks. + static size_t _read_func( void *ptr, size_t size, size_t nmemb, void *datasource ); + static int _seek_func( void *datasource, ogg_int64_t offset, int whence ); + static long _tell_func( void *datasource ); + + // SFXStream + virtual bool _readHeader(); + virtual void _close(); + + public: + + /// + static SFXVorbisStream* create( Stream *stream ); + + /// + SFXVorbisStream(); + + /// + SFXVorbisStream( const SFXVorbisStream& cloneFrom ); + + /// Destructor. + virtual ~SFXVorbisStream(); + + /// + const vorbis_info* getInfo( S32 link = -1 ); + + /// + const vorbis_comment* getComment( S32 link = -1 ); + + /// + // TODO: Deal with error cases... like for unseekable streams! + U64 getPcmTotal( S32 link = -1 ); + + /// + S32 read( U8 *buffer, + U32 length, + S32 *bitstream ); + + // SFXStream + virtual void reset(); + virtual U32 read( U8 *buffer, U32 length ); + virtual bool isEOS() const; + virtual SFXStream* clone() const + { + SFXVorbisStream* stream = new SFXVorbisStream( *this ); + if( !stream->mVF ) + SAFE_DELETE( stream ); + return stream; + } + + // IPositionable + virtual U32 getPosition() const; + virtual void setPosition( U32 offset ); +}; + +#endif // TORQUE_OGGVORBIS +#endif // _SFXVORBISSTREAM_H_ diff --git a/sfx/media/sfxWavStream.cpp b/sfx/media/sfxWavStream.cpp new file mode 100644 index 0000000..0f87815 --- /dev/null +++ b/sfx/media/sfxWavStream.cpp @@ -0,0 +1,282 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/media/sfxWavStream.h" +#include "core/stream/stream.h" +#include "core/strings/stringFunctions.h" + + +/// WAV File-header +struct WAVFileHdr +{ + U8 id[4]; + U32 size; + U8 type[4]; +}; + +//// WAV Fmt-header +struct WAVFmtHdr +{ + U16 format; + U16 channels; + U32 samplesPerSec; + U32 bytesPerSec; + U16 blockAlign; + U16 bitsPerSample; +}; + +/// WAV FmtEx-header +struct WAVFmtExHdr +{ + U16 size; + U16 samplesPerBlock; +}; + +/// WAV Smpl-header +struct WAVSmplHdr +{ + U32 manufacturer; + U32 product; + U32 samplePeriod; + U32 note; + U32 fineTune; + U32 SMPTEFormat; + U32 SMPTEOffest; + U32 loops; + U32 samplerData; + + struct + { + U32 identifier; + U32 type; + U32 start; + U32 end; + U32 fraction; + U32 count; + } loop[1]; +}; + +/// WAV Chunk-header +struct WAVChunkHdr +{ + U8 id[4]; + U32 size; +}; + + +SFXWavStream* SFXWavStream::create( Stream *stream ) +{ + SFXWavStream *sfxStream = new SFXWavStream(); + if ( sfxStream->open( stream, true ) ) + return sfxStream; + + delete sfxStream; + return NULL; +} + +SFXWavStream::SFXWavStream() +{ +} + +SFXWavStream::SFXWavStream( const SFXWavStream& cloneFrom ) + : Parent( cloneFrom ), + mDataStart( cloneFrom.mDataStart ) +{ +} + +SFXWavStream::~SFXWavStream() +{ + // We must call close from our own destructor + // and not the base class... as it causes a + // pure virtual runtime assertion. + close(); +} + +void SFXWavStream::_close() +{ + mDataStart = -1; +} + +bool SFXWavStream::_readHeader() +{ + // We read the wav chunks to gather than header info + // and find the start and end position of the data chunk. + mDataStart = -1; + + WAVFileHdr fileHdr; + mStream->read( 4, &fileHdr.id[0] ); + mStream->read( &fileHdr.size ); + mStream->read( 4, &fileHdr.type[0] ); + + fileHdr.size=((fileHdr.size+1)&~1)-4; + + WAVChunkHdr chunkHdr; + mStream->read( 4, &chunkHdr.id[0] ); + mStream->read( &chunkHdr.size ); + + // Unread chunk data rounded up to nearest WORD. + S32 chunkRemaining = chunkHdr.size + ( chunkHdr.size & 1 ); + + WAVFmtHdr fmtHdr; + WAVFmtExHdr fmtExHdr; + WAVSmplHdr smplHdr; + + dMemset(&fmtHdr, 0, sizeof(fmtHdr)); + + while ((fileHdr.size!=0) && (mStream->getStatus() != Stream::EOS)) + { + // WAV format header chunk. + if ( !dStrncmp( (const char*)chunkHdr.id, "fmt ", 4 ) ) + { + mStream->read(&fmtHdr.format); + mStream->read(&fmtHdr.channels); + mStream->read(&fmtHdr.samplesPerSec); + mStream->read(&fmtHdr.bytesPerSec); + mStream->read(&fmtHdr.blockAlign); + mStream->read(&fmtHdr.bitsPerSample); + + if ( fmtHdr.format == 0x0001 ) + { + mFormat.set( fmtHdr.channels, fmtHdr.bitsPerSample * fmtHdr.channels, fmtHdr.samplesPerSec ); + chunkRemaining -= sizeof( WAVFmtHdr ); + } + else + { + mStream->read(sizeof(WAVFmtExHdr), &fmtExHdr); + chunkRemaining -= sizeof(WAVFmtExHdr); + } + } + + // WAV data chunk + else if (!dStrncmp((const char*)chunkHdr.id,"data",4)) + { + // TODO: Handle these other formats in a more graceful manner! + + if (fmtHdr.format==0x0001) + { + mDataStart = mStream->getPosition(); + mStream->setPosition( mDataStart + chunkHdr.size ); + chunkRemaining -= chunkHdr.size; + mSamples = chunkHdr.size / mFormat.getBytesPerSample(); + } + else if (fmtHdr.format==0x0011) + { + //IMA ADPCM + } + else if (fmtHdr.format==0x0055) + { + //MP3 WAVE + } + } + + // WAV sample header + else if (!dStrncmp((const char*)chunkHdr.id,"smpl",4)) + { + // this struct read is NOT endian safe but it is ok because + // we are only testing the loops field against ZERO + mStream->read(sizeof(WAVSmplHdr), &smplHdr); + + // This has never been hooked up and its usefulness is + // dubious. Do we really want the audio file overriding + // the SFXDescription setting? + //mLooping = ( smplHdr.loops ? true : false ); + + chunkRemaining -= sizeof(WAVSmplHdr); + } + + // either we have unread chunk data or we found an unknown chunk type + // loop and read up to 1K bytes at a time until we have + // read to the end of this chunk + AssertFatal(chunkRemaining >= 0, "AudioBuffer::readWAV: remaining chunk data should never be less than zero."); + if ( chunkRemaining > 0 ) + { + U32 pos = mStream->getPosition(); + mStream->setPosition( pos + chunkRemaining ); + chunkRemaining = 0; + } + + fileHdr.size-=(((chunkHdr.size+1)&~1)+8); + + // read next chunk header... + mStream->read(4, &chunkHdr.id[0]); + mStream->read(&chunkHdr.size); + // unread chunk data rounded up to nearest WORD + chunkRemaining = chunkHdr.size + (chunkHdr.size&1); + } + + return ( mDataStart != -1 ); +} + +void SFXWavStream::reset() +{ + AssertFatal( mStream, "SFXWavStream::reset() - Stream is null!" ); + AssertFatal( mDataStart != -1, "SFXWavStream::seek() - Data start offset is invalid!" ); + mStream->setPosition( mDataStart ); +} + +U32 SFXWavStream::getPosition() const +{ + AssertFatal( mStream, "SFXWavStream::getPosition() - Stream is null!" ); + return ( mStream->getPosition() - mDataStart ); +} + +void SFXWavStream::setPosition( U32 offset ) +{ + AssertFatal( mStream, "SFXWavStream::setPosition() - Stream is null!" ); + + offset -= offset % mFormat.getBytesPerSample(); + const U32 dataLength = mSamples * mFormat.getBytesPerSample(); + if( offset > dataLength ) + offset = dataLength; + + AssertFatal( mDataStart != -1, "SFXWavStream::getPosition() - Data start offset is invalid!" ); + + U32 byte = mDataStart + offset; + + mStream->setPosition( byte ); +} + +U32 SFXWavStream::read( U8 *buffer, U32 bytes ) +{ + AssertFatal( mStream, "SFXWavStream::seek() - Stream is null!" ); + + // Read in even sample chunks. + + bytes -= bytes % mFormat.getBytesPerSample(); + + // Read the data and determine how much we've read. + // FileStreams apparently report positions past + // the actual stream length, so manually cap the + // numbers here. + + const U32 oldPosition = mStream->getPosition(); + mStream->read( bytes, buffer ); + U32 newPosition = mStream->getPosition(); + const U32 maxPosition = getDataLength() + mDataStart; + if( newPosition > maxPosition ) + newPosition = maxPosition; + + const U32 numBytesRead = newPosition - oldPosition; + + // TODO: Is it *just* 16 bit samples that needs to + // be flipped? What about 32 bit samples? + #ifdef TORQUE_BIG_ENDIAN + + // We need to endian-flip 16-bit data. + if ( getFormat().getBytesPerChannel() == 2 ) + { + U16 *ds = (U16*)buffer; + U16 *de = (U16*)(buffer+bytes); + while (ds +{ + public: + + typedef SFXFileStream Parent; + + protected: + + /// The file position of the start of + /// the PCM data for fast reset(). + U32 mDataStart; + + // SFXFileStream + virtual bool _readHeader(); + virtual void _close(); + + public: + + /// + static SFXWavStream* create( Stream *stream ); + + /// + SFXWavStream(); + + /// + SFXWavStream( const SFXWavStream& cloneFrom ); + + /// Destructor. + virtual ~SFXWavStream(); + + // SFXStream + virtual void reset(); + virtual U32 read( U8 *buffer, U32 length ); + virtual SFXStream* clone() const + { + SFXWavStream* stream = new SFXWavStream( *this ); + if( !stream->mStream ) + SAFE_DELETE( stream ); + return stream; + } + + // IPositionable + virtual U32 getPosition() const; + virtual void setPosition( U32 offset ); +}; + +#endif // _SFXWAVSTREAM_H_ diff --git a/sfx/null/sfxNullBuffer.cpp b/sfx/null/sfxNullBuffer.cpp new file mode 100644 index 0000000..729e40e --- /dev/null +++ b/sfx/null/sfxNullBuffer.cpp @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/null/sfxNullBuffer.h" +#include "sfx/sfxInternal.h" + + +SFXNullBuffer::SFXNullBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) + : Parent( stream, description, false ) +{ + mStatus = STATUS_Ready; +} + +SFXNullBuffer::~SFXNullBuffer() +{ +} + +void SFXNullBuffer::write( SFXInternal::SFXStreamPacket* const* packets, U32 num ) +{ + // Should never really be called, but to be safe... + + for( U32 i = 0; i < num; ++ i ) + destructSingle( packets[ i ] ); +} diff --git a/sfx/null/sfxNullBuffer.h b/sfx/null/sfxNullBuffer.h new file mode 100644 index 0000000..06ccf8d --- /dev/null +++ b/sfx/null/sfxNullBuffer.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXNULLBUFFER_H_ +#define _SFXNULLBUFFER_H_ + +#ifndef _SFXBUFFER_H_ + #include "sfx/sfxBuffer.h" +#endif + + +class SFXNullBuffer : public SFXBuffer +{ + friend class SFXNullDevice; + typedef SFXBuffer Parent; + + protected: + + SFXNullBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + + // SFXBuffer. + virtual void write( SFXInternal::SFXStreamPacket* const* packets, U32 num ); + virtual void _flush() {} + + public: + + virtual ~SFXNullBuffer(); + +}; + +#endif // _SFXNULLBUFFER_H_ \ No newline at end of file diff --git a/sfx/null/sfxNullDevice.cpp b/sfx/null/sfxNullDevice.cpp new file mode 100644 index 0000000..3ad388c --- /dev/null +++ b/sfx/null/sfxNullDevice.cpp @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/null/sfxNullDevice.h" +#include "sfx/null/sfxNullBuffer.h" +#include "sfx/sfxListener.h" +#include "sfx/sfxInternal.h" + + +SFXNullDevice::SFXNullDevice( SFXProvider* provider, + String name, + bool useHardware, + S32 maxBuffers ) + + : SFXDevice( name, provider, useHardware, maxBuffers ) +{ + mMaxBuffers = getMax( maxBuffers, 8 ); +} + +SFXNullDevice::~SFXNullDevice() +{ +} + +SFXBuffer* SFXNullDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + SFXNullBuffer* buffer = new SFXNullBuffer( stream, description ); + _addBuffer( buffer ); + + return buffer; +} + +SFXVoice* SFXNullDevice::createVoice( bool is3D, SFXBuffer *buffer ) +{ + // Don't bother going any further if we've + // exceeded the maximum voices. + if ( mVoices.size() >= mMaxBuffers ) + return NULL; + + AssertFatal( buffer, "SFXNullDevice::createVoice() - Got null buffer!" ); + + SFXNullBuffer* nullBuffer = dynamic_cast( buffer ); + AssertFatal( nullBuffer, "SFXNullDevice::createVoice() - Got bad buffer!" ); + + SFXNullVoice* voice = new SFXNullVoice( nullBuffer ); + if ( !voice ) + return NULL; + + _addVoice( voice ); + return voice; +} diff --git a/sfx/null/sfxNullDevice.h b/sfx/null/sfxNullDevice.h new file mode 100644 index 0000000..997f772 --- /dev/null +++ b/sfx/null/sfxNullDevice.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXNULLDEVICE_H_ +#define _SFXNULLDEVICE_H_ + +class SFXProvider; + +#ifndef _SFXDEVICE_H_ + #include "sfx/sfxDevice.h" +#endif +#ifndef _SFXPROVIDER_H_ + #include "sfx/sfxProvider.h" +#endif +#ifndef _SFXNULLBUFFER_H_ + #include "sfx/null/sfxNullBuffer.h" +#endif +#ifndef _SFXNULLVOICE_H_ + #include "sfx/null/sfxNullVoice.h" +#endif + + +class SFXNullDevice : public SFXDevice +{ + typedef SFXDevice Parent; + + public: + + SFXNullDevice( SFXProvider* provider, + String name, + bool useHardware, + S32 maxBuffers ); + + virtual ~SFXNullDevice(); + + public: + + // SFXDevice. + virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + virtual SFXVoice* createVoice( bool is3D, SFXBuffer *buffer ); +}; + +#endif // _SFXNULLDEVICE_H_ \ No newline at end of file diff --git a/sfx/null/sfxNullProvider.cpp b/sfx/null/sfxNullProvider.cpp new file mode 100644 index 0000000..45fba08 --- /dev/null +++ b/sfx/null/sfxNullProvider.cpp @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxProvider.h" +#include "sfx/null/sfxNullDevice.h" +#include "core/strings/stringFunctions.h" + + +class SFXNullProvider : public SFXProvider +{ +public: + + SFXNullProvider() + : SFXProvider( "Null" ) {} + virtual ~SFXNullProvider(); + +protected: + void addDeviceDesc( const String& name, const String& desc ); + void init(); + +public: + + SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ); + +}; + +SFX_INIT_PROVIDER( SFXNullProvider ); + +void SFXNullProvider::init() +{ + regProvider( this ); + addDeviceDesc( "SFX Null Device", "SFX baseline device" ); +} + +SFXNullProvider::~SFXNullProvider() +{ +} + + +void SFXNullProvider::addDeviceDesc( const String& name, const String& desc ) +{ + SFXDeviceInfo* info = new SFXDeviceInfo; + info->name = desc; + info->driver = name; + info->hasHardware = false; + info->maxBuffers = 8; + + mDeviceInfo.push_back( info ); +} + +SFXDevice* SFXNullProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ) +{ + SFXDeviceInfo* info = _findDeviceInfo( deviceName ); + + // Do we find one to create? + if ( info ) + return new SFXNullDevice( this, info->name, useHardware, maxBuffers ); + + return NULL; +} diff --git a/sfx/null/sfxNullVoice.cpp b/sfx/null/sfxNullVoice.cpp new file mode 100644 index 0000000..a443bfd --- /dev/null +++ b/sfx/null/sfxNullVoice.cpp @@ -0,0 +1,104 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/null/sfxNullVoice.h" +#include "sfx/null/sfxNullBuffer.h" +#include "sfx/sfxInternal.h" + + +SFXNullVoice::SFXNullVoice( SFXNullBuffer* buffer ) + : Parent( buffer ), + mIsLooping( false ) +{ +} + +SFXNullVoice::~SFXNullVoice() +{ +} + +SFXStatus SFXNullVoice::_status() const +{ + if( !mIsLooping + && mPlayTimer.isStarted() + && !mPlayTimer.isPaused() + && mPlayTimer.getPosition() >= mBuffer->getDuration() ) + mPlayTimer.stop(); + + if( mPlayTimer.isPaused() ) + return SFXStatusPaused; + else if( mPlayTimer.isStarted() ) + return SFXStatusPlaying; + else + return SFXStatusStopped; +} + +void SFXNullVoice::_play() +{ + mPlayTimer.start(); +} + +void SFXNullVoice::_pause() +{ + mPlayTimer.pause(); +} + +void SFXNullVoice::_stop() +{ + mPlayTimer.stop(); +} + +void SFXNullVoice::_seek( U32 sample ) +{ + const U32 sampleTime = mBuffer->getFormat().getDuration( sample ); + mPlayTimer.setPosition( sampleTime ); +} + +void SFXNullVoice::play( bool looping ) +{ + mIsLooping = looping; + mPlayTimer.start(); +} + +U32 SFXNullVoice::_tell() const +{ + U32 ms = _getPlayTime(); + + const SFXFormat& format = mBuffer->getFormat(); + return ( format.getDataLength( ms ) / format.getBytesPerSample() ); +} + +SFXStatus SFXNullVoice::getStatus() const +{ + return _status(); +} + +void SFXNullVoice::setPosition( U32 sample ) +{ + _seek( sample ); +} + +void SFXNullVoice::setMinMaxDistance( F32 min, F32 max ) +{ +} + +void SFXNullVoice::setVelocity( const VectorF& velocity ) +{ +} + +void SFXNullVoice::setTransform( const MatrixF& transform ) +{ +} + +void SFXNullVoice::setVolume( F32 volume ) +{ +} + +void SFXNullVoice::setPitch( F32 pitch ) +{ +} + +void SFXNullVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ) +{ +} diff --git a/sfx/null/sfxNullVoice.h b/sfx/null/sfxNullVoice.h new file mode 100644 index 0000000..2500095 --- /dev/null +++ b/sfx/null/sfxNullVoice.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXNULLVOICE_H_ +#define _SFXNULLVOICE_H_ + +#ifndef _SFXVOICE_H_ + #include "sfx/sfxVoice.h" +#endif +#ifndef _TIMESOURCE_H_ + #include "core/util/timeSource.h" +#endif + + +class SFXNullBuffer; + + +class SFXNullVoice : public SFXVoice +{ + public: + + typedef SFXVoice Parent; + friend class SFXNullDevice; + + protected: + + typedef GenericTimeSource< VirtualMSTimer > TimeSource; + + SFXNullVoice( SFXNullBuffer* buffer ); + + /// The virtual play timer. + mutable TimeSource mPlayTimer; + + /// + bool mIsLooping; + + // SFXVoice. + virtual SFXStatus _status() const; + virtual void _play(); + virtual void _pause(); + virtual void _stop(); + virtual void _seek( U32 sample ); + virtual U32 _tell() const; + + /// + U32 _getPlayTime() const + { + return mPlayTimer.getPosition(); + } + + public: + + virtual ~SFXNullVoice(); + + /// SFXVoice + SFXStatus getStatus() const; + void setPosition( U32 sample ); + void play( bool looping ); + void setMinMaxDistance( F32 min, F32 max ); + void setVelocity( const VectorF& velocity ); + void setTransform( const MatrixF& transform ); + void setVolume( F32 volume ); + void setPitch( F32 pitch ); + void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ); +}; + +#endif // _SFXNULLVOICE_H_ \ No newline at end of file diff --git a/sfx/openal/LoadOAL.h b/sfx/openal/LoadOAL.h new file mode 100644 index 0000000..4850a38 --- /dev/null +++ b/sfx/openal/LoadOAL.h @@ -0,0 +1,192 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _LOADOAL_H_ +#define _LOADOAL_H_ + +#ifndef _PLATFORM_H_ +# include "platform/platform.h" +#endif + +#if defined(TORQUE_OS_MAC) +# include +# include +#else +# include +# include +#endif + +#ifndef ALAPIENTRY +#define ALAPIENTRY +#endif + +#ifndef ALCAPIENTRY +#define ALCAPIENTRY +#endif + +// Open AL Function table definition + +#ifndef _OPENALFNTABLE +#define _OPENALFNTABLE + +// AL 1.0 did not define the ALchar and ALCchar types, so define them here +// if they don't exist + +#ifndef ALchar +#define ALchar char +#endif + +#ifndef ALCchar +#define ALCchar char +#endif + +// Complete list of functions available in AL 1.0 implementations + +typedef void (ALAPIENTRY *LPALENABLE)( ALenum capability ); +typedef void (ALAPIENTRY *LPALDISABLE)( ALenum capability ); +typedef ALboolean (ALAPIENTRY *LPALISENABLED)( ALenum capability ); +typedef const ALchar* (ALAPIENTRY *LPALGETSTRING)( ALenum param ); +typedef void (ALAPIENTRY *LPALGETBOOLEANV)( ALenum param, ALboolean* data ); +typedef void (ALAPIENTRY *LPALGETINTEGERV)( ALenum param, ALint* data ); +typedef void (ALAPIENTRY *LPALGETFLOATV)( ALenum param, ALfloat* data ); +typedef void (ALAPIENTRY *LPALGETDOUBLEV)( ALenum param, ALdouble* data ); +typedef ALboolean (ALAPIENTRY *LPALGETBOOLEAN)( ALenum param ); +typedef ALint (ALAPIENTRY *LPALGETINTEGER)( ALenum param ); +typedef ALfloat (ALAPIENTRY *LPALGETFLOAT)( ALenum param ); +typedef ALdouble (ALAPIENTRY *LPALGETDOUBLE)( ALenum param ); +typedef ALenum (ALAPIENTRY *LPALGETERROR)( void ); +typedef ALboolean (ALAPIENTRY *LPALISEXTENSIONPRESENT)(const ALchar* extname ); +typedef void* (ALAPIENTRY *LPALGETPROCADDRESS)( const ALchar* fname ); +typedef ALenum (ALAPIENTRY *LPALGETENUMVALUE)( const ALchar* ename ); +typedef void (ALAPIENTRY *LPALLISTENERF)( ALenum param, ALfloat value ); +typedef void (ALAPIENTRY *LPALLISTENER3F)( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALLISTENERFV)( ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALLISTENERI)( ALenum param, ALint value ); +typedef void (ALAPIENTRY *LPALGETLISTENERF)( ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETLISTENER3F)( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); +typedef void (ALAPIENTRY *LPALGETLISTENERFV)( ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETLISTENERI)( ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALGENSOURCES)( ALsizei n, ALuint* sources ); +typedef void (ALAPIENTRY *LPALDELETESOURCES)( ALsizei n, const ALuint* sources ); +typedef ALboolean (ALAPIENTRY *LPALISSOURCE)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEF)( ALuint sid, ALenum param, ALfloat value); +typedef void (ALAPIENTRY *LPALSOURCE3F)( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALSOURCEFV)( ALuint sid, ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALSOURCEI)( ALuint sid, ALenum param, ALint value); +typedef void (ALAPIENTRY *LPALGETSOURCEF)( ALuint sid, ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETSOURCE3F)( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +typedef void (ALAPIENTRY *LPALGETSOURCEFV)( ALuint sid, ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETSOURCEI)( ALuint sid, ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALSOURCEPLAYV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCESTOPV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEREWINDV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEPAUSEV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEPLAY)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCESTOP)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEREWIND)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEPAUSE)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, const ALuint *bids ); +typedef void (ALAPIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, ALuint *bids ); +typedef void (ALAPIENTRY *LPALGENBUFFERS)( ALsizei n, ALuint* buffers ); +typedef void (ALAPIENTRY *LPALDELETEBUFFERS)( ALsizei n, const ALuint* buffers ); +typedef ALboolean (ALAPIENTRY *LPALISBUFFER)( ALuint bid ); +typedef void (ALAPIENTRY *LPALBUFFERDATA)( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); +typedef void (ALAPIENTRY *LPALGETBUFFERF)( ALuint bid, ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETBUFFERI)( ALuint bid, ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALDOPPLERFACTOR)( ALfloat value ); +typedef void (ALAPIENTRY *LPALDOPPLERVELOCITY)( ALfloat value ); +typedef void (ALAPIENTRY *LPALDISTANCEMODEL)( ALenum distanceModel ); + +typedef ALCcontext * (ALCAPIENTRY *LPALCCREATECONTEXT) (ALCdevice *device, const ALCint *attrlist); +typedef ALCboolean (ALCAPIENTRY *LPALCMAKECONTEXTCURRENT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCPROCESSCONTEXT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCSUSPENDCONTEXT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCDESTROYCONTEXT)( ALCcontext *context ); +typedef ALCcontext * (ALCAPIENTRY *LPALCGETCURRENTCONTEXT)( void ); +typedef ALCdevice * (ALCAPIENTRY *LPALCGETCONTEXTSDEVICE)( ALCcontext *context ); +typedef ALCdevice * (ALCAPIENTRY *LPALCOPENDEVICE)( const ALCchar *devicename ); +typedef ALCboolean (ALCAPIENTRY *LPALCCLOSEDEVICE)( ALCdevice *device ); +typedef ALCenum (ALCAPIENTRY *LPALCGETERROR)( ALCdevice *device ); +typedef ALCboolean (ALCAPIENTRY *LPALCISEXTENSIONPRESENT)( ALCdevice *device, const ALCchar *extname ); +typedef void * (ALCAPIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname ); +typedef ALCenum (ALCAPIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname ); +typedef const ALCchar* (ALCAPIENTRY *LPALCGETSTRING)( ALCdevice *device, ALCenum param ); +typedef void (ALCAPIENTRY *LPALCGETINTEGERV)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest ); + +typedef struct +{ + LPALENABLE alEnable; + LPALDISABLE alDisable; + LPALISENABLED alIsEnabled; + LPALGETBOOLEAN alGetBoolean; + LPALGETINTEGER alGetInteger; + LPALGETFLOAT alGetFloat; + LPALGETDOUBLE alGetDouble; + LPALGETBOOLEANV alGetBooleanv; + LPALGETINTEGERV alGetIntegerv; + LPALGETFLOATV alGetFloatv; + LPALGETDOUBLEV alGetDoublev; + LPALGETSTRING alGetString; + LPALGETERROR alGetError; + LPALISEXTENSIONPRESENT alIsExtensionPresent; + LPALGETPROCADDRESS alGetProcAddress; + LPALGETENUMVALUE alGetEnumValue; + LPALLISTENERI alListeneri; + LPALLISTENERF alListenerf; + LPALLISTENER3F alListener3f; + LPALLISTENERFV alListenerfv; + LPALGETLISTENERI alGetListeneri; + LPALGETLISTENERF alGetListenerf; + LPALGETLISTENER3F alGetListener3f; + LPALGETLISTENERFV alGetListenerfv; + LPALGENSOURCES alGenSources; + LPALDELETESOURCES alDeleteSources; + LPALISSOURCE alIsSource; + LPALSOURCEI alSourcei; + LPALSOURCEF alSourcef; + LPALSOURCE3F alSource3f; + LPALSOURCEFV alSourcefv; + LPALGETSOURCEI alGetSourcei; + LPALGETSOURCEF alGetSourcef; + LPALGETSOURCEFV alGetSourcefv; + LPALSOURCEPLAYV alSourcePlayv; + LPALSOURCESTOPV alSourceStopv; + LPALSOURCEPLAY alSourcePlay; + LPALSOURCEPAUSE alSourcePause; + LPALSOURCESTOP alSourceStop; + LPALSOURCEREWIND alSourceRewind; + LPALGENBUFFERS alGenBuffers; + LPALDELETEBUFFERS alDeleteBuffers; + LPALISBUFFER alIsBuffer; + LPALBUFFERDATA alBufferData; + LPALGETBUFFERI alGetBufferi; + LPALGETBUFFERF alGetBufferf; + LPALSOURCEQUEUEBUFFERS alSourceQueueBuffers; + LPALSOURCEUNQUEUEBUFFERS alSourceUnqueueBuffers; + LPALDISTANCEMODEL alDistanceModel; + LPALDOPPLERFACTOR alDopplerFactor; + LPALDOPPLERVELOCITY alDopplerVelocity; + LPALCGETSTRING alcGetString; + LPALCGETINTEGERV alcGetIntegerv; + LPALCOPENDEVICE alcOpenDevice; + LPALCCLOSEDEVICE alcCloseDevice; + LPALCCREATECONTEXT alcCreateContext; + LPALCMAKECONTEXTCURRENT alcMakeContextCurrent; + LPALCPROCESSCONTEXT alcProcessContext; + LPALCGETCURRENTCONTEXT alcGetCurrentContext; + LPALCGETCONTEXTSDEVICE alcGetContextsDevice; + LPALCSUSPENDCONTEXT alcSuspendContext; + LPALCDESTROYCONTEXT alcDestroyContext; + LPALCGETERROR alcGetError; + LPALCISEXTENSIONPRESENT alcIsExtensionPresent; + LPALCGETPROCADDRESS alcGetProcAddress; + LPALCGETENUMVALUE alcGetEnumValue; +} OPENALFNTABLE, *LPOPENALFNTABLE; +#endif + +ALboolean LoadOAL10Library(char *szOALFullPathName, LPOPENALFNTABLE lpOALFnTable); +ALvoid UnloadOAL10Library(); + +#endif // _LOADOAL_H_ diff --git a/sfx/openal/aldlist.cpp b/sfx/openal/aldlist.cpp new file mode 100644 index 0000000..205ce69 --- /dev/null +++ b/sfx/openal/aldlist.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2006, Creative Labs Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions and + * the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the distribution. + * * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "core/strings/stringFunctions.h" + +#include "aldlist.h" +#if defined(TORQUE_OS_MAC) +#include +#else +#include +#endif + +/* + * Init call + */ +ALDeviceList::ALDeviceList( const OPENALFNTABLE &oalft ) +{ + VECTOR_SET_ASSOCIATION( vDeviceInfo ); + + ALDEVICEINFO ALDeviceInfo; + char *devices; + int index; + const char *defaultDeviceName; + const char *actualDeviceName; + + dMemcpy( &ALFunction, &oalft, sizeof( OPENALFNTABLE ) ); + + // DeviceInfo vector stores, for each enumerated device, it's device name, selection status, spec version #, and extension support + vDeviceInfo.clear(); + vDeviceInfo.reserve(10); + + defaultDeviceIndex = 0; + + // grab function pointers for 1.0-API functions, and if successful proceed to enumerate all devices + if (ALFunction.alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT")) { + devices = (char *)ALFunction.alcGetString(NULL, ALC_DEVICE_SPECIFIER); + defaultDeviceName = (char *)ALFunction.alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + index = 0; + // go through device list (each device terminated with a single NULL, list terminated with double NULL) + while (*devices != 0) { + if (dStrcmp(defaultDeviceName, devices) == 0) { + defaultDeviceIndex = index; + } + ALCdevice *device = ALFunction.alcOpenDevice(devices); + if (device) { + ALCcontext *context = ALFunction.alcCreateContext(device, NULL); + if (context) { + ALFunction.alcMakeContextCurrent(context); + // if new actual device name isn't already in the list, then add it... + actualDeviceName = ALFunction.alcGetString(device, ALC_DEVICE_SPECIFIER); + bool bNewName = true; + for (int i = 0; i < GetNumDevices(); i++) { + if (dStrcmp(GetDeviceName(i), actualDeviceName) == 0) { + bNewName = false; + } + } + if ((bNewName) && (actualDeviceName != NULL) && (dStrlen(actualDeviceName) > 0)) { + dMemset(&ALDeviceInfo, 0, sizeof(ALDEVICEINFO)); + ALDeviceInfo.bSelected = true; + dStrncpy(ALDeviceInfo.strDeviceName, actualDeviceName, sizeof(ALDeviceInfo.strDeviceName)); + ALFunction.alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(int), &ALDeviceInfo.iMajorVersion); + ALFunction.alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(int), &ALDeviceInfo.iMinorVersion); + + ALDeviceInfo.iCapsFlags = 0; + + // Check for ALC Extensions + if (ALFunction.alcIsExtensionPresent(device, "ALC_EXT_CAPTURE") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALCapture; + if (ALFunction.alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALEFX; + + // Check for AL Extensions + if (ALFunction.alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALOffset; + + if (ALFunction.alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALLinearDistance; + if (ALFunction.alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALExponentDistance; + + if (ALFunction.alIsExtensionPresent("EAX2.0") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALEAX2; + if (ALFunction.alIsExtensionPresent("EAX3.0") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALEAX3; + if (ALFunction.alIsExtensionPresent("EAX4.0") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALEAX4; + if (ALFunction.alIsExtensionPresent("EAX5.0") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALEAX5; + + if (ALFunction.alIsExtensionPresent("EAX-RAM") == AL_TRUE) + ALDeviceInfo.iCapsFlags |= SFXALEAXRAM; + + // Get Source Count + ALDeviceInfo.uiSourceCount = GetMaxNumSources(); + + vDeviceInfo.push_back(ALDeviceInfo); + } + ALFunction.alcMakeContextCurrent(NULL); + ALFunction.alcDestroyContext(context); + } + ALFunction.alcCloseDevice(device); + } + devices += dStrlen(devices) + 1; + index += 1; + } + } + + ResetFilters(); +} + +/* + * Exit call + */ +ALDeviceList::~ALDeviceList() +{ + vDeviceInfo.empty(); +} + +/* + * Returns the number of devices in the complete device list + */ +int ALDeviceList::GetNumDevices() +{ + return (int)vDeviceInfo.size(); +} + +/* + * Returns the device name at an index in the complete device list + */ +const char *ALDeviceList::GetDeviceName(int index) +{ + if (index < GetNumDevices()) + return vDeviceInfo[index].strDeviceName; + else + return NULL; +} + +/* + * Returns the major and minor version numbers for a device at a specified index in the complete list + */ +void ALDeviceList::GetDeviceVersion(int index, int *major, int *minor) +{ + if (index < GetNumDevices()) { + if (*major) + *major = vDeviceInfo[index].iMajorVersion; + if (*minor) + *minor = vDeviceInfo[index].iMinorVersion; + } + return; +} + +/* + * Returns the maximum number of Sources that can be generate on the given device + */ +unsigned int ALDeviceList::GetMaxNumSources(int index) +{ + if (index < GetNumDevices()) + return vDeviceInfo[index].uiSourceCount; + else + return 0; +} + +/* + * Checks if the extension is supported on the given device + */ +bool ALDeviceList::IsExtensionSupported(int index, SFXALCaps cap) +{ + bool bReturn = false; + + if (index < GetNumDevices()) + bReturn = vDeviceInfo[index].iCapsFlags & cap; + + return bReturn; +} + +/* + * returns the index of the default device in the complete device list + */ +int ALDeviceList::GetDefaultDevice() +{ + return defaultDeviceIndex; +} + +/* + * Deselects devices which don't have the specified minimum version + */ +void ALDeviceList::FilterDevicesMinVer(int major, int minor) +{ + int dMajor, dMinor; + for (unsigned int i = 0; i < vDeviceInfo.size(); i++) { + GetDeviceVersion(i, &dMajor, &dMinor); + if ((dMajor < major) || ((dMajor == major) && (dMinor < minor))) { + vDeviceInfo[i].bSelected = false; + } + } +} + +/* + * Deselects devices which don't have the specified maximum version + */ +void ALDeviceList::FilterDevicesMaxVer(int major, int minor) +{ + int dMajor, dMinor; + for (unsigned int i = 0; i < vDeviceInfo.size(); i++) { + GetDeviceVersion(i, &dMajor, &dMinor); + if ((dMajor > major) || ((dMajor == major) && (dMinor > minor))) { + vDeviceInfo[i].bSelected = false; + } + } +} + +/* + * Deselects device which don't support the given extension name + */ +void ALDeviceList::FilterDevicesExtension(SFXALCaps cap) +{ + for (unsigned int i = 0; i < vDeviceInfo.size(); i++) + vDeviceInfo[i].bSelected = vDeviceInfo[i].iCapsFlags & cap; +} + +/* + * Resets all filtering, such that all devices are in the list + */ +void ALDeviceList::ResetFilters() +{ + for (int i = 0; i < GetNumDevices(); i++) { + vDeviceInfo[i].bSelected = true; + } + filterIndex = 0; +} + +/* + * Gets index of first filtered device + */ +int ALDeviceList::GetFirstFilteredDevice() +{ + int i; + + for (i = 0; i < GetNumDevices(); i++) { + if (vDeviceInfo[i].bSelected == true) { + break; + } + } + filterIndex = i + 1; + return i; +} + +/* + * Gets index of next filtered device + */ +int ALDeviceList::GetNextFilteredDevice() +{ + int i; + + for (i = filterIndex; i < GetNumDevices(); i++) { + if (vDeviceInfo[i].bSelected == true) { + break; + } + } + filterIndex = i + 1; + return i; +} + +/* + * Internal function to detemine max number of Sources that can be generated + */ +unsigned int ALDeviceList::GetMaxNumSources() +{ + ALuint uiSources[256]; + unsigned int iSourceCount = 0; + + // Clear AL Error Code + ALFunction.alGetError(); + + // Generate up to 256 Sources, checking for any errors + for (iSourceCount = 0; iSourceCount < 256; iSourceCount++) + { + ALFunction.alGenSources(1, &uiSources[iSourceCount]); + if (ALFunction.alGetError() != AL_NO_ERROR) + break; + } + + // Release the Sources + ALFunction.alDeleteSources(iSourceCount, uiSources); + if (ALFunction.alGetError() != AL_NO_ERROR) + { + for (unsigned int i = 0; i < 256; i++) + { + ALFunction.alDeleteSources(1, &uiSources[i]); + } + } + + return iSourceCount; +} \ No newline at end of file diff --git a/sfx/openal/aldlist.h b/sfx/openal/aldlist.h new file mode 100644 index 0000000..b2600b2 --- /dev/null +++ b/sfx/openal/aldlist.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef ALDEVICELIST_H +#define ALDEVICELIST_H + +#pragma warning(disable: 4786) //disable warning "identifier was truncated to '255' characters in the browser information" +#include "core/util/tVector.h" +#include "core/stringTable.h" +#include "sfx/openal/sfxALCaps.h" +#include "LoadOAL.h" + +typedef struct +{ + char strDeviceName[256]; + int iMajorVersion; + int iMinorVersion; + unsigned int uiSourceCount; + int iCapsFlags; + bool bSelected; +} ALDEVICEINFO, *LPALDEVICEINFO; + +class ALDeviceList +{ +private: + OPENALFNTABLE ALFunction; + Vector vDeviceInfo; + int defaultDeviceIndex; + int filterIndex; + +public: + ALDeviceList ( const OPENALFNTABLE &oalft ); + ~ALDeviceList (); + int GetNumDevices(); + const char *GetDeviceName(int index); + void GetDeviceVersion(int index, int *major, int *minor); + unsigned int GetMaxNumSources(int index); + bool IsExtensionSupported(int index, SFXALCaps caps); + int GetDefaultDevice(); + void FilterDevicesMinVer(int major, int minor); + void FilterDevicesMaxVer(int major, int minor); + void FilterDevicesExtension(SFXALCaps caps); + void ResetFilters(); + int GetFirstFilteredDevice(); + int GetNextFilteredDevice(); + +private: + unsigned int GetMaxNumSources(); +}; + +#endif // ALDEVICELIST_H diff --git a/sfx/openal/mac/LoadOAL.mac.cpp b/sfx/openal/mac/LoadOAL.mac.cpp new file mode 100644 index 0000000..89b1e98 --- /dev/null +++ b/sfx/openal/mac/LoadOAL.mac.cpp @@ -0,0 +1,428 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// TODO: Implement OpenAL loading code which is currently stubbed out. + +#if defined(__MACOSX__) && !defined(TORQUE_OS_MAC) +#define TORQUE_OS_MAC +#endif + +#include +#include +#include "sfx/openal/LoadOAL.h" + +ALboolean LoadOAL10Library(char *szOALFullPathName, LPOPENALFNTABLE lpOALFnTable) +{ + // TODO: Implement this. + if (!lpOALFnTable) + return AL_FALSE; + + memset(lpOALFnTable, 0, sizeof(OPENALFNTABLE)); + + lpOALFnTable->alEnable = (LPALENABLE)alEnable; + if (lpOALFnTable->alEnable == NULL) + { + warn("Failed to retrieve 'alEnable' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDisable = (LPALDISABLE)alDisable; + if (lpOALFnTable->alDisable == NULL) + { + warn("Failed to retrieve 'alDisable' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsEnabled = (LPALISENABLED)alIsEnabled; + if (lpOALFnTable->alIsEnabled == NULL) + { + warn("Failed to retrieve 'alIsEnabled' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBoolean = (LPALGETBOOLEAN)alGetBoolean; + if (lpOALFnTable->alGetBoolean == NULL) + { + warn("Failed to retrieve 'alGetBoolean' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetInteger = (LPALGETINTEGER)alGetInteger; + if (lpOALFnTable->alGetInteger == NULL) + { + warn("Failed to retrieve 'alGetInteger' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetFloat = (LPALGETFLOAT)alGetFloat; + if (lpOALFnTable->alGetFloat == NULL) + { + warn("Failed to retrieve 'alGetFloat' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetDouble = (LPALGETDOUBLE)alGetDouble; + if (lpOALFnTable->alGetDouble == NULL) + { + warn("Failed to retrieve 'alGetDouble' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBooleanv = (LPALGETBOOLEANV)alGetBooleanv; + if (lpOALFnTable->alGetBooleanv == NULL) + { + warn("Failed to retrieve 'alGetBooleanv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetIntegerv = (LPALGETINTEGERV)alGetIntegerv; + if (lpOALFnTable->alGetIntegerv == NULL) + { + warn("Failed to retrieve 'alGetIntegerv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetFloatv = (LPALGETFLOATV)alGetFloatv; + if (lpOALFnTable->alGetFloatv == NULL) + { + warn("Failed to retrieve 'alGetFloatv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetDoublev = (LPALGETDOUBLEV)alGetDoublev; + if (lpOALFnTable->alGetDoublev == NULL) + { + warn("Failed to retrieve 'alGetDoublev' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetString = (LPALGETSTRING)alGetString; + if (lpOALFnTable->alGetString == NULL) + { + warn("Failed to retrieve 'alGetString' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetError = (LPALGETERROR)alGetError; + if (lpOALFnTable->alGetError == NULL) + { + warn("Failed to retrieve 'alGetError' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsExtensionPresent = (LPALISEXTENSIONPRESENT)alIsExtensionPresent; + if (lpOALFnTable->alIsExtensionPresent == NULL) + { + warn("Failed to retrieve 'alIsExtensionPresent' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetProcAddress = (LPALGETPROCADDRESS)alGetProcAddress; + if (lpOALFnTable->alGetProcAddress == NULL) + { + warn("Failed to retrieve 'alGetProcAddress' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetEnumValue = (LPALGETENUMVALUE)alGetEnumValue; + if (lpOALFnTable->alGetEnumValue == NULL) + { + warn("Failed to retrieve 'alGetEnumValue' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListeneri = (LPALLISTENERI)alListeneri; + if (lpOALFnTable->alListeneri == NULL) + { + warn("Failed to retrieve 'alListeneri' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListenerf = (LPALLISTENERF)alListenerf; + if (lpOALFnTable->alListenerf == NULL) + { + warn("Failed to retrieve 'alListenerf' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListener3f = (LPALLISTENER3F)alListener3f; + if (lpOALFnTable->alListener3f == NULL) + { + warn("Failed to retrieve 'alListener3f' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListenerfv = (LPALLISTENERFV)alListenerfv; + if (lpOALFnTable->alListenerfv == NULL) + { + warn("Failed to retrieve 'alListenerfv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListeneri = (LPALGETLISTENERI)alGetListeneri; + if (lpOALFnTable->alGetListeneri == NULL) + { + warn("Failed to retrieve 'alGetListeneri' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListenerf =(LPALGETLISTENERF)alGetListenerf; + if (lpOALFnTable->alGetListenerf == NULL) + { + warn("Failed to retrieve 'alGetListenerf' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListener3f = (LPALGETLISTENER3F)alGetListener3f; + if (lpOALFnTable->alGetListener3f == NULL) + { + warn("Failed to retrieve 'alGetListener3f' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListenerfv = (LPALGETLISTENERFV)alGetListenerfv; + if (lpOALFnTable->alGetListenerfv == NULL) + { + warn("Failed to retrieve 'alGetListenerfv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGenSources = (LPALGENSOURCES)alGenSources; + if (lpOALFnTable->alGenSources == NULL) + { + warn("Failed to retrieve 'alGenSources' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDeleteSources = (LPALDELETESOURCES)alDeleteSources; + if (lpOALFnTable->alDeleteSources == NULL) + { + warn("Failed to retrieve 'alDeleteSources' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsSource = (LPALISSOURCE)alIsSource; + if (lpOALFnTable->alIsSource == NULL) + { + warn("Failed to retrieve 'alIsSource' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcei = (LPALSOURCEI)alSourcei; + if (lpOALFnTable->alSourcei == NULL) + { + warn("Failed to retrieve 'alSourcei' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcef = (LPALSOURCEF)alSourcef; + if (lpOALFnTable->alSourcef == NULL) + { + warn("Failed to retrieve 'alSourcef' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSource3f = (LPALSOURCE3F)alSource3f; + if (lpOALFnTable->alSource3f == NULL) + { + warn("Failed to retrieve 'alSource3f' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcefv = (LPALSOURCEFV)alSourcefv; + if (lpOALFnTable->alSourcefv == NULL) + { + warn("Failed to retrieve 'alSourcefv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetSourcei = (LPALGETSOURCEI)alGetSourcei; + if (lpOALFnTable->alGetSourcei == NULL) + { + warn("Failed to retrieve 'alGetSourcei' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetSourcef = (LPALGETSOURCEF)alGetSourcef; + if (lpOALFnTable->alGetSourcef == NULL) + { + warn("Failed to retrieve 'alGetSourcef' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetSourcefv = (LPALGETSOURCEFV)alGetSourcefv; + if (lpOALFnTable->alGetSourcefv == NULL) + { + warn("Failed to retrieve 'alGetSourcefv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcePlayv = (LPALSOURCEPLAYV)alSourcePlayv; + if (lpOALFnTable->alSourcePlayv == NULL) + { + warn("Failed to retrieve 'alSourcePlayv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceStopv = (LPALSOURCESTOPV)alSourceStopv; + if (lpOALFnTable->alSourceStopv == NULL) + { + warn("Failed to retrieve 'alSourceStopv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcePlay = (LPALSOURCEPLAY)alSourcePlay; + if (lpOALFnTable->alSourcePlay == NULL) + { + warn("Failed to retrieve 'alSourcePlay' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcePause = (LPALSOURCEPAUSE)alSourcePause; + if (lpOALFnTable->alSourcePause == NULL) + { + warn("Failed to retrieve 'alSourcePause' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceStop = (LPALSOURCESTOP)alSourceStop; + if (lpOALFnTable->alSourceStop == NULL) + { + warn("Failed to retrieve 'alSourceStop' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceRewind = (LPALSOURCEREWIND)alSourceRewind; + if (lpOALFnTable->alSourceRewind == NULL) + { + warn("Failed to retrieve 'alSourceRewind' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGenBuffers = (LPALGENBUFFERS)alGenBuffers; + if (lpOALFnTable->alGenBuffers == NULL) + { + warn("Failed to retrieve 'alGenBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDeleteBuffers = (LPALDELETEBUFFERS)alDeleteBuffers; + if (lpOALFnTable->alDeleteBuffers == NULL) + { + warn("Failed to retrieve 'alDeleteBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsBuffer = (LPALISBUFFER)alIsBuffer; + if (lpOALFnTable->alIsBuffer == NULL) + { + warn("Failed to retrieve 'alIsBuffer' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alBufferData = (LPALBUFFERDATA)alBufferData; + if (lpOALFnTable->alBufferData == NULL) + { + warn("Failed to retrieve 'alBufferData' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBufferi = (LPALGETBUFFERI)alGetBufferi; + if (lpOALFnTable->alGetBufferi == NULL) + { + warn("Failed to retrieve 'alGetBufferi' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBufferf = (LPALGETBUFFERF)alGetBufferf; + if (lpOALFnTable->alGetBufferf == NULL) + { + warn("Failed to retrieve 'alGetBufferf' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceQueueBuffers = (LPALSOURCEQUEUEBUFFERS)alSourceQueueBuffers; + if (lpOALFnTable->alSourceQueueBuffers == NULL) + { + warn("Failed to retrieve 'alSourceQueueBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceUnqueueBuffers = (LPALSOURCEUNQUEUEBUFFERS)alSourceUnqueueBuffers; + if (lpOALFnTable->alSourceUnqueueBuffers == NULL) + { + warn("Failed to retrieve 'alSourceUnqueueBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDistanceModel = (LPALDISTANCEMODEL)alDistanceModel; + if (lpOALFnTable->alDistanceModel == NULL) + { + warn("Failed to retrieve 'alDistanceModel' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDopplerFactor = (LPALDOPPLERFACTOR)alDopplerFactor; + if (lpOALFnTable->alDopplerFactor == NULL) + { + warn("Failed to retrieve 'alDopplerFactor' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDopplerVelocity = (LPALDOPPLERVELOCITY)alDopplerVelocity; + if (lpOALFnTable->alDopplerVelocity == NULL) + { + warn("Failed to retrieve 'alDopplerVelocity' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetString = (LPALCGETSTRING)alcGetString; + if (lpOALFnTable->alcGetString == NULL) + { + warn("Failed to retrieve 'alcGetString' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetIntegerv = (LPALCGETINTEGERV)alcGetIntegerv; + if (lpOALFnTable->alcGetIntegerv == NULL) + { + warn("Failed to retrieve 'alcGetIntegerv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcOpenDevice = (LPALCOPENDEVICE)alcOpenDevice; + if (lpOALFnTable->alcOpenDevice == NULL) + { + warn("Failed to retrieve 'alcOpenDevice' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcCloseDevice = (LPALCCLOSEDEVICE)alcCloseDevice; + if (lpOALFnTable->alcCloseDevice == NULL) + { + warn("Failed to retrieve 'alcCloseDevice' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcCreateContext = (LPALCCREATECONTEXT)alcCreateContext; + if (lpOALFnTable->alcCreateContext == NULL) + { + warn("Failed to retrieve 'alcCreateContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcMakeContextCurrent = (LPALCMAKECONTEXTCURRENT)alcMakeContextCurrent; + if (lpOALFnTable->alcMakeContextCurrent == NULL) + { + warn("Failed to retrieve 'alcMakeContextCurrent' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcProcessContext = (LPALCPROCESSCONTEXT)alcProcessContext; + if (lpOALFnTable->alcProcessContext == NULL) + { + warn("Failed to retrieve 'alcProcessContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetCurrentContext = (LPALCGETCURRENTCONTEXT)alcGetCurrentContext; + if (lpOALFnTable->alcGetCurrentContext == NULL) + { + warn("Failed to retrieve 'alcGetCurrentContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetContextsDevice = (LPALCGETCONTEXTSDEVICE)alcGetContextsDevice; + if (lpOALFnTable->alcGetContextsDevice == NULL) + { + warn("Failed to retrieve 'alcGetContextsDevice' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcSuspendContext = (LPALCSUSPENDCONTEXT)alcSuspendContext; + if (lpOALFnTable->alcSuspendContext == NULL) + { + warn("Failed to retrieve 'alcSuspendContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcDestroyContext = (LPALCDESTROYCONTEXT)alcDestroyContext; + if (lpOALFnTable->alcDestroyContext == NULL) + { + warn("Failed to retrieve 'alcDestroyContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetError = (LPALCGETERROR)alcGetError; + if (lpOALFnTable->alcGetError == NULL) + { + warn("Failed to retrieve 'alcGetError' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcIsExtensionPresent = (LPALCISEXTENSIONPRESENT)alcIsExtensionPresent; + if (lpOALFnTable->alcIsExtensionPresent == NULL) + { + warn("Failed to retrieve 'alcIsExtensionPresent' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetProcAddress = (LPALCGETPROCADDRESS)alcGetProcAddress; + if (lpOALFnTable->alcGetProcAddress == NULL) + { + warn("Failed to retrieve 'alcGetProcAddress' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetEnumValue = (LPALCGETENUMVALUE)alcGetEnumValue; + if (lpOALFnTable->alcGetEnumValue == NULL) + { + warn("Failed to retrieve 'alcGetEnumValue' function address\n"); + return AL_FALSE; + } + + + return AL_TRUE; +} + +ALvoid UnloadOAL10Library() +{ +// TODO: Implement this. +} \ No newline at end of file diff --git a/sfx/openal/sfxALBuffer.cpp b/sfx/openal/sfxALBuffer.cpp new file mode 100644 index 0000000..fc7648e --- /dev/null +++ b/sfx/openal/sfxALBuffer.cpp @@ -0,0 +1,209 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/openal/sfxALBuffer.h" +#include "sfx/openal/sfxALVoice.h" +#include "sfx/openal/sfxALDevice.h" +#include "sfx/sfxDescription.h" +#include "console/console.h" + + +SFXALBuffer* SFXALBuffer::create( const OPENALFNTABLE &oalft, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ) +{ + if( !_sfxFormatToALFormat( stream->getFormat() ) ) + { + Con::errorf( "SFXALBuffer::create() - SFXFormat not supported by OpenAL" ); + return NULL; + } + + SFXALBuffer *buffer = new SFXALBuffer( oalft, + stream, + description, + useHardware ); + + return buffer; +} + +SFXALBuffer::SFXALBuffer( const OPENALFNTABLE &oalft, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ) + : Parent( stream, description ), + mOpenAL( oalft ), + mUseHardware( useHardware ), + mIs3d( description->mIs3D ) +{ + // Set up device buffers. + + if( !isStreaming() ) + mOpenAL.alGenBuffers( 1, &mALBuffer ); +} + +SFXALBuffer::~SFXALBuffer() +{ + if( _getUniqueVoice() ) + _getUniqueVoice()->stop(); + + // Release queue buffers. + + while( mFreeBuffers.size() ) + { + ALuint buffer = mFreeBuffers.last(); + mOpenAL.alDeleteBuffers( 1, &buffer ); + mFreeBuffers.pop_back(); + } +} + +void SFXALBuffer::write( SFXInternal::SFXStreamPacket* const* packets, U32 num ) +{ + using namespace SFXInternal; + + if( !num ) + return; + + // If this is not a streaming buffer, + // just load the data into our single + // static buffer. + + if( !isStreaming() ) + { + SFXStreamPacket* packet = packets[ num - 1 ]; + + ALenum alFormat = _sfxFormatToALFormat( getFormat() ); + AssertFatal( alFormat != 0, "SFXALBuffer::write() - format unsupported" ); + + mOpenAL.alBufferData( mALBuffer, alFormat, + packet->data, packet->mSizeActual, getFormat().getSamplesPerSecond() ); + + destructSingle( packet ); + return; + } + + MutexHandle mutex; + mutex.lock( &_getUniqueVoice()->mMutex, true ); + + // Unqueue processed packets. + + ALuint source = _getUniqueVoice()->mSourceName; + ALint numProcessed; + mOpenAL.alGetSourcei( source, AL_BUFFERS_PROCESSED, &numProcessed ); + + for( U32 i = 0; i < numProcessed; ++ i ) + { + // Unqueue the buffer. + + ALuint buffer; + mOpenAL.alSourceUnqueueBuffers( source, 1, &buffer ); + + // Update the sample offset on the voice. + + ALint size; + mOpenAL.alGetBufferi( buffer, AL_SIZE, &size ); + _getUniqueVoice()->mSampleOffset += size / getFormat().getBytesPerSample(); + + // Push the buffer onto the freelist. + + mFreeBuffers.push_back( buffer ); + } + + // Queue buffers. + + for( U32 i = 0; i < num; ++ i ) + { + SFXStreamPacket* packet = packets[ i ]; + + // Allocate a buffer. + + ALuint buffer; + if( mFreeBuffers.size() ) + { + buffer = mFreeBuffers.last(); + mFreeBuffers.pop_back(); + } + else + mOpenAL.alGenBuffers( 1, &buffer ); + + // Upload the data. + + ALenum alFormat = _sfxFormatToALFormat( getFormat() ); + AssertFatal( alFormat != 0, "SFXALBuffer::write() - format unsupported" ); + AssertFatal( mOpenAL.alIsBuffer( buffer ), "SFXALBuffer::write() - buffer invalid" ); + + mOpenAL.alBufferData( buffer, alFormat, + packet->data, packet->mSizeActual, getFormat().getSamplesPerSecond() ); + + destructSingle( packet ); + + // Queue the buffer. + + mOpenAL.alSourceQueueBuffers( source, 1, &buffer ); + } +} + +void SFXALBuffer::_flush() +{ + AssertFatal( isStreaming(), "SFXALBuffer::_flush() - not a streaming buffer" ); + AssertFatal( SFXInternal::isSFXThread(), "SFXALBuffer::_flush() - not on SFX thread" ); + + MutexHandle mutex; + mutex.lock( &_getUniqueVoice()->mMutex, true ); + + ALuint source = _getUniqueVoice()->mSourceName; + + ALint numQueued; + mOpenAL.alGetSourcei( source, AL_BUFFERS_QUEUED, &numQueued ); + + for( U32 i = 0; i < numQueued; ++ i ) + { + ALuint buffer; + mOpenAL.alSourceUnqueueBuffers( source, 1, &buffer ); + mFreeBuffers.push_back( buffer ); + } + + //RD: disabling hack for now; rewritten queueing should be able to cope + #if 0 //def TORQUE_OS_MAC + + //WORKAROUND: Ugly hack on Mac. Apparently there's a bug in the OpenAL implementation + // that will cause AL_BUFFERS_PROCESSED to not be reset as it should be causing write() + // to fail. Brute-force this and just re-create the source. Let's pray that nobody + // issues any concurrent state changes on the voice resulting in us losing state here. + + ALuint newSource; + mOpenAL.alGenSources( 1, &newSource ); + + #define COPY_F( name ) \ + { \ + F32 val; \ + mOpenAL.alGetSourcef( source, name, &val ); \ + mOpenAL.alSourcef( source, name, val ); \ + } + + #define COPY_FV( name ) \ + { \ + VectorF val; \ + mOpenAL.alGetSourcefv( source, name, val ); \ + mOpenAL.alSourcefv( source, name, val ); \ + } + + COPY_F( AL_REFERENCE_DISTANCE ); + COPY_F( AL_MAX_DISTANCE ); + COPY_F( AL_GAIN ); + COPY_F( AL_PITCH ); + COPY_F( AL_CONE_INNER_ANGLE ); + COPY_F( AL_CONE_OUTER_ANGLE ); + COPY_F( AL_CONE_OUTER_GAIN ); + + COPY_FV( AL_VELOCITY ); + COPY_FV( AL_POSITION ); + COPY_FV( AL_DIRECTION ); + + _getUniqueVoice()->mSourceName = newSource; + mOpenAL.alDeleteSources( 1, &source ); + + #endif +} diff --git a/sfx/openal/sfxALBuffer.h b/sfx/openal/sfxALBuffer.h new file mode 100644 index 0000000..d0ef7b3 --- /dev/null +++ b/sfx/openal/sfxALBuffer.h @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXALBUFFER_H_ +#define _SFXALBUFFER_H_ + +#ifndef _LOADOAL_H + #include "sfx/openal/LoadOAL.h" +#endif +#ifndef _SFXINTERNAL_H_ + #include "sfx/sfxInternal.h" +#endif +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif + + +class SFXALVoice; + + +class SFXALBuffer : public SFXBuffer +{ + public: + + typedef SFXBuffer Parent; + + friend class SFXALDevice; + friend class SFXALVoice; + + protected: + + /// AL buffer in case this is a static, non-streaming buffer. + ALuint mALBuffer; + + /// Free buffers for use in queuing in case this is a streaming buffer. + Vector< ALuint > mFreeBuffers; + + /// + SFXALBuffer( const OPENALFNTABLE &oalft, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ); + + /// + bool mIs3d; + + /// + bool mUseHardware; + + const OPENALFNTABLE &mOpenAL; + + /// + ALenum _getALFormat() const + { + return _sfxFormatToALFormat( getFormat() ); + } + + /// + static ALenum _sfxFormatToALFormat( const SFXFormat& format ) + { + if( format.getChannels() == 2 ) + { + const U32 bps = format.getBitsPerSample(); + if( bps == 16 ) + return AL_FORMAT_STEREO8; + else if( bps == 32 ) + return AL_FORMAT_STEREO16; + } + else if( format.getChannels() == 1 ) + { + const U32 bps = format.getBitsPerSample(); + if( bps == 8 ) + return AL_FORMAT_MONO8; + else if( bps == 16 ) + return AL_FORMAT_MONO16; + } + return 0; + } + + /// + SFXALVoice* _getUniqueVoice() const + { + return ( SFXALVoice* ) mUniqueVoice.getPointer(); + } + + // SFXBuffer. + virtual void write( SFXInternal::SFXStreamPacket* const* packets, U32 num ); + void _flush(); + + public: + + static SFXALBuffer* create( const OPENALFNTABLE &oalft, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description, + bool useHardware ); + + virtual ~SFXALBuffer(); +}; + +#endif // _SFXALBUFFER_H_ \ No newline at end of file diff --git a/sfx/openal/sfxALCaps.h b/sfx/openal/sfxALCaps.h new file mode 100644 index 0000000..5a3524d --- /dev/null +++ b/sfx/openal/sfxALCaps.h @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXALCAPS_H_ +#define _SFXALCAPS_H_ + +enum SFXALCaps +{ + SFXALCapture = 0, + SFXALEFX, + SFXALOffset, + SFXALLinearDistance, + SFXALExponentDistance, + SFXALEAX2, + SFXALEAX3, + SFXALEAX4, + SFXALEAX5, + SFXALEAXRAM +}; + +#endif \ No newline at end of file diff --git a/sfx/openal/sfxALDevice.cpp b/sfx/openal/sfxALDevice.cpp new file mode 100644 index 0000000..f1350b7 --- /dev/null +++ b/sfx/openal/sfxALDevice.cpp @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/openal/sfxALDevice.h" +#include "sfx/openal/sfxALBuffer.h" +#include "sfx/sfxListener.h" +#include "platform/async/asyncUpdate.h" + + +SFXALDevice::SFXALDevice( SFXProvider *provider, + const OPENALFNTABLE &openal, + String name, + bool useHardware, + S32 maxBuffers ) + : Parent( name, provider, useHardware, maxBuffers ), + mOpenAL( openal ), + mDevice( NULL ), + mContext( NULL ) +{ + mMaxBuffers = getMax( maxBuffers, 8 ); + + // TODO: The OpenAL device doesn't set the primary buffer + // $pref::SFX::frequency or $pref::SFX::bitrate! + + mDevice = mOpenAL.alcOpenDevice( name ); + mOpenAL.alcGetError( mDevice ); + if( mDevice ) + { + mContext = mOpenAL.alcCreateContext( mDevice, NULL ); + + if( mContext ) + mOpenAL.alcMakeContextCurrent( mContext ); + + U32 err = mOpenAL.alcGetError( mDevice ); + + if( err != ALC_NO_ERROR ) + Con::errorf( "SFXALDevice - Initialization Error: %s", mOpenAL.alcGetString( mDevice, err ) ); + } + + AssertFatal( mDevice != NULL && mContext != NULL, "Failed to create OpenAL device and/or context!" ); + + // Start the update thread. + + if( !Con::getBoolVariable( "$_forceAllMainThread" ) ) + { + SFXInternal::gUpdateThread = new AsyncPeriodicUpdateThread + ( "OpenAL Update Thread", SFXInternal::gBufferUpdateList, + Con::getIntVariable( "$pref::SFX::updateInterval", SFXInternal::DEFAULT_UPDATE_INTERVAL ) ); + SFXInternal::gUpdateThread->start(); + } +} + + +SFXALDevice::~SFXALDevice() +{ + _releaseAllResources(); + + mOpenAL.alcMakeContextCurrent( NULL ); + mOpenAL.alcDestroyContext( mContext ); + mOpenAL.alcCloseDevice( mDevice ); +} + +SFXBuffer* SFXALDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + AssertFatal( stream, "SFXALDevice::createBuffer() - Got null stream!" ); + AssertFatal( description, "SFXALDevice::createBuffer() - Got null description!" ); + + SFXALBuffer* buffer = SFXALBuffer::create( mOpenAL, + stream, + description, + mUseHardware ); + if ( !buffer ) + return NULL; + + _addBuffer( buffer ); + return buffer; +} + +SFXVoice* SFXALDevice::createVoice( bool is3D, SFXBuffer *buffer ) +{ + // Don't bother going any further if we've + // exceeded the maximum voices. + if ( mVoices.size() >= mMaxBuffers ) + return NULL; + + AssertFatal( buffer, "SFXALDevice::createVoice() - Got null buffer!" ); + + SFXALBuffer* alBuffer = dynamic_cast( buffer ); + AssertFatal( alBuffer, "SFXALDevice::createVoice() - Got bad buffer!" ); + + SFXALVoice* voice = SFXALVoice::create( this, alBuffer ); + if ( !voice ) + return NULL; + + _addVoice( voice ); + return voice; +} + +void SFXALDevice::update( const SFXListener &listener ) +{ + Parent::update( listener ); + + // Torque and OpenAL are both right handed + // systems, so no coordinate flipping is needed. + + const MatrixF &transform = listener.getTransform(); + Point3F pos, tupple[2]; + transform.getColumn( 3, &pos ); + transform.getColumn( 1, &tupple[0] ); + transform.getColumn( 2, &tupple[1] ); + + const VectorF &velocity = listener.getVelocity(); + + mOpenAL.alListenerfv( AL_POSITION, pos ); + mOpenAL.alListenerfv( AL_VELOCITY, velocity ); + mOpenAL.alListenerfv( AL_ORIENTATION, (const F32 *)&tupple[0] ); +} + +void SFXALDevice::setDistanceModel( SFXDistanceModel model ) +{ + switch( model ) + { + case SFXDistanceModelLinear: + mOpenAL.alDistanceModel( AL_LINEAR_DISTANCE_CLAMPED ); + if( mRolloffFactor != 1.0f ) + _setRolloffFactor( 1.0f ); // No rolloff on linear. + break; + + case SFXDistanceModelLogarithmic: + mOpenAL.alDistanceModel( AL_INVERSE_DISTANCE_CLAMPED ); + if( mUserRolloffFactor != mRolloffFactor ) + _setRolloffFactor( mUserRolloffFactor ); + break; + + default: + AssertWarn( false, "SFXALDevice::setDistanceModel - distance model not implemented" ); + } + + mDistanceModel = model; +} + +void SFXALDevice::setDopplerFactor( F32 factor ) +{ + mOpenAL.alDopplerFactor( factor ); +} + +void SFXALDevice::_setRolloffFactor( F32 factor ) +{ + mRolloffFactor = factor; + + for( U32 i = 0, num = mVoices.size(); i < num; ++ i ) + mOpenAL.alSourcef( ( ( SFXALVoice* ) mVoices[ i ] )->mSourceName, AL_ROLLOFF_FACTOR, factor ); +} + +void SFXALDevice::setRolloffFactor( F32 factor ) +{ + if( mDistanceModel == SFXDistanceModelLinear && factor != 1.0f ) + Con::errorf( "SFXALDevice::setRolloffFactor - rolloff factor <> 1.0f ignored in linear distance model" ); + else + _setRolloffFactor( factor ); + + mUserRolloffFactor = factor; +} diff --git a/sfx/openal/sfxALDevice.h b/sfx/openal/sfxALDevice.h new file mode 100644 index 0000000..8c39ee6 --- /dev/null +++ b/sfx/openal/sfxALDevice.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXALDEVICE_H_ +#define _SFXALDEVICE_H_ + +class SFXProvider; + +#ifndef _SFXDEVICE_H_ +# include "sfx/sfxDevice.h" +#endif + +#ifndef _SFXPROVIDER_H_ +# include "sfx/sfxProvider.h" +#endif + +#ifndef _SFXALBUFFER_H_ +# include "sfx/openal/sfxALBuffer.h" +#endif + +#ifndef _SFXALVOICE_H_ +# include "sfx/openal/sfxALVoice.h" +#endif + +#ifndef _OPENALFNTABLE +# include "sfx/openal/LoadOAL.h" +#endif + + +class SFXALDevice : public SFXDevice +{ + public: + + typedef SFXDevice Parent; + friend class SFXALVoice; // mDistanceFactor, mRolloffFactor + + SFXALDevice( SFXProvider *provider, + const OPENALFNTABLE &openal, + String name, + bool useHardware, + S32 maxBuffers ); + + virtual ~SFXALDevice(); + + protected: + + OPENALFNTABLE mOpenAL; + + ALCcontext *mContext; + + ALCdevice *mDevice; + + SFXDistanceModel mDistanceModel; + F32 mDistanceFactor; + F32 mRolloffFactor; + F32 mUserRolloffFactor; + + void _setRolloffFactor( F32 factor ); + + public: + + // SFXDevice. + virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + virtual SFXVoice* createVoice( bool is3D, SFXBuffer *buffer ); + virtual void update( const SFXListener &listener ); + virtual void setDistanceModel( SFXDistanceModel model ); + virtual void setDopplerFactor( F32 factor ); + virtual void setRolloffFactor( F32 factor ); +}; + +#endif // _SFXALDEVICE_H_ \ No newline at end of file diff --git a/sfx/openal/sfxALProvider.cpp b/sfx/openal/sfxALProvider.cpp new file mode 100644 index 0000000..fab8126 --- /dev/null +++ b/sfx/openal/sfxALProvider.cpp @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine Advanced +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "platform/platform.h" + +#include "sfx/sfxProvider.h" +#include "sfx/openal/sfxALDevice.h" +#include "sfx/openal/aldlist.h" + +#include "core/strings/stringFunctions.h" +#include "console/console.h" + +class SFXALProvider : public SFXProvider +{ +public: + + SFXALProvider() + : SFXProvider( "OpenAL" ) {} + virtual ~SFXALProvider(); + +protected: + OPENALFNTABLE mOpenAL; + ALDeviceList *mALDL; + + struct ALDeviceInfo : SFXDeviceInfo + { + + }; + + void init(); + +public: + SFXDevice *createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ); + +}; + +SFX_INIT_PROVIDER( SFXALProvider ); + +void SFXALProvider::init() +{ + if( LoadOAL10Library( NULL, &mOpenAL ) != AL_TRUE ) + { + Con::printf( "SFXALProvider - OpenAL not available." ); + return; + } + mALDL = new ALDeviceList( mOpenAL ); + + // Did we get any devices? + if ( mALDL->GetNumDevices() < 1 ) + { + Con::printf( "SFXALProvider - No valid devices found!" ); + return; + } + + // Cool, loop through them, and caps em + const char *deviceFormat = "OpenAL v%d.%d %s"; + + char temp[256]; + for( int i = 0; i < mALDL->GetNumDevices(); i++ ) + { + ALDeviceInfo* info = new ALDeviceInfo; + + info->name = String( mALDL->GetDeviceName( i ) ); + + int major, minor, eax = 0; + + mALDL->GetDeviceVersion( i, &major, &minor ); + + // Apologies for the blatent enum hack -patw + for( int j = SFXALEAX2; j < SFXALEAXRAM; j++ ) + eax += (int)mALDL->IsExtensionSupported( i, (SFXALCaps)j ); + + if( eax > 0 ) + { + eax += 2; // EAX support starts at 2.0 + dSprintf( temp, sizeof( temp ), "[EAX %d.0] %s", eax, ( mALDL->IsExtensionSupported( i, SFXALEAXRAM ) ? "EAX-RAM" : "" ) ); + } + else + dStrcpy( temp, "" ); + + info->driver = String::ToString( deviceFormat, major, minor, temp ); + info->hasHardware = eax > 0; + info->maxBuffers = mALDL->GetMaxNumSources( i ); + + mDeviceInfo.push_back( info ); + } + + regProvider( this ); +} + +SFXALProvider::~SFXALProvider() +{ + UnloadOAL10Library(); + delete mALDL; +} + +SFXDevice *SFXALProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ) +{ + ALDeviceInfo *info = dynamic_cast< ALDeviceInfo* > + ( _findDeviceInfo( deviceName) ); + + // Do we find one to create? + if ( info ) + return new SFXALDevice( this, mOpenAL, info->name, useHardware, maxBuffers ); + + return NULL; +} \ No newline at end of file diff --git a/sfx/openal/sfxALVoice.cpp b/sfx/openal/sfxALVoice.cpp new file mode 100644 index 0000000..bf71ad8 --- /dev/null +++ b/sfx/openal/sfxALVoice.cpp @@ -0,0 +1,218 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sfx/openal/sfxALVoice.h" +#include "sfx/openal/sfxALBuffer.h" +#include "sfx/openal/sfxALDevice.h" + + +#ifdef TORQUE_DEBUG +# define AL_SANITY_CHECK() \ + AssertFatal( mOpenAL.alIsSource( mSourceName ), "AL Source Sanity Check Failed!" ); +#else +# define AL_SANITY_CHECK() +#endif + + +SFXALVoice* SFXALVoice::create( SFXALDevice* device, SFXALBuffer *buffer ) +{ + AssertFatal( buffer, "SFXALVoice::create() - Got null buffer!" ); + + ALuint sourceName; + device->mOpenAL.alGenSources( 1, &sourceName ); + AssertFatal( device->mOpenAL.alIsSource( sourceName ), "AL Source Sanity Check Failed!" ); + + // Is this 3d? + // Okay, this looks odd, but bear with me for a moment. AL_SOURCE_RELATIVE does NOT indicate + // whether or not the volume of the sound should change depending on the position of the listener. + // OpenAL assumes that the volume will ALWAYS depend on the position of the listener. What AL_SOURCE_RELATIVE + // does do is dictate if the position of THIS SOURCE is relative to the listener. If AL_SOURCE_RELATIVE is AL_TRUE + // and the source's position is 0, 0, 0, then the source is directly on top of the listener at all times, which is what + // we want for non-3d sounds. + device->mOpenAL.alSourcei( sourceName, AL_SOURCE_RELATIVE, ( buffer->mIs3d ? AL_FALSE : AL_TRUE ) ); + + if( buffer->mIs3d ) + device->mOpenAL.alSourcef( sourceName, AL_ROLLOFF_FACTOR, device->mRolloffFactor ); + + SFXALVoice *voice = new SFXALVoice( device->mOpenAL, + buffer, + sourceName ); + + return voice; +} + +SFXALVoice::SFXALVoice( const OPENALFNTABLE &oalft, + SFXALBuffer *buffer, + ALuint sourceName ) + + : Parent( buffer ), + mOpenAL( oalft ), + mResumeAtSampleOffset( -1.0f ), + mSourceName( sourceName ), + mSampleOffset( 0 ) +{ + AL_SANITY_CHECK(); +} + +SFXALVoice::~SFXALVoice() +{ + mOpenAL.alDeleteSources( 1, &mSourceName ); +} + +void SFXALVoice::_lateBindStaticBufferIfNecessary() +{ + if( !mBuffer->isStreaming() ) + { + ALint bufferId; + mOpenAL.alGetSourcei( mSourceName, AL_BUFFER, &bufferId ); + if( !bufferId ) + mOpenAL.alSourcei( mSourceName, AL_BUFFER, _getBuffer()->mALBuffer ); + } +} + + +SFXStatus SFXALVoice::_status() const +{ + AL_SANITY_CHECK(); + + ALint state; + mOpenAL.alGetSourcei( mSourceName, AL_SOURCE_STATE, &state ); + + switch( state ) + { + case AL_PLAYING: return SFXStatusPlaying; + case AL_PAUSED: return SFXStatusPaused; + default: return SFXStatusStopped; + } +} + +void SFXALVoice::_play() +{ + AL_SANITY_CHECK(); + + _lateBindStaticBufferIfNecessary(); + + mOpenAL.alSourcePlay( mSourceName ); + + // Adjust play cursor for buggy OAL when resuming playback. Do this after alSourcePlay + // as it is the play function that will cause the cursor to jump. + + if( mResumeAtSampleOffset != -1.0f ) + { + mOpenAL.alSourcef( mSourceName, AL_SAMPLE_OFFSET, mResumeAtSampleOffset ); + mResumeAtSampleOffset = -1.0f; + } +} + +void SFXALVoice::_pause() +{ + AL_SANITY_CHECK(); + mOpenAL.alSourcePause( mSourceName ); + + //WORKAROUND: Another workaround for the buggy OAL. Resuming playback of a paused source will cause the + // play cursor to jump. Save the cursor so we can manually move it into position in _play(). Sigh. + + mOpenAL.alGetSourcef( mSourceName, AL_SAMPLE_OFFSET, &mResumeAtSampleOffset ); +} + +void SFXALVoice::_stop() +{ + AL_SANITY_CHECK(); + mOpenAL.alSourceStop( mSourceName ); + mSampleOffset = 0; + + mResumeAtSampleOffset = -1.0f; +} + +void SFXALVoice::_seek( U32 sample ) +{ + AL_SANITY_CHECK(); + + _lateBindStaticBufferIfNecessary(); + mOpenAL.alSourcei( mSourceName, AL_SAMPLE_OFFSET, sample ); + + mResumeAtSampleOffset = -1.0f; +} + +U32 SFXALVoice::_tell() const +{ + // Flush processed buffers as AL_SAMPLE_OFFSET will snap back to zero as soon + // as the queue is processed in whole. + + if( mBuffer->isStreaming() ) + mBuffer->write( NULL, 0 ); + + ALint pos; + mOpenAL.alGetSourcei( mSourceName, AL_SAMPLE_OFFSET, &pos ); + return ( pos + mSampleOffset ); +} + +void SFXALVoice::setMinMaxDistance( F32 min, F32 max ) +{ + AL_SANITY_CHECK(); + + mOpenAL.alSourcef( mSourceName, AL_REFERENCE_DISTANCE, min ); + mOpenAL.alSourcef( mSourceName, AL_MAX_DISTANCE, max ); +} + +void SFXALVoice::play( bool looping ) +{ + AL_SANITY_CHECK(); + + mOpenAL.alSourceStop( mSourceName ); + if( !mBuffer->isStreaming() ) + mOpenAL.alSourcei( mSourceName, AL_LOOPING, ( looping ? AL_TRUE : AL_FALSE ) ); + + Parent::play( looping ); +} + +void SFXALVoice::setVelocity( const VectorF& velocity ) +{ + AL_SANITY_CHECK(); + + // Torque and OpenAL are both right handed + // systems, so no coordinate flipping is needed. + + mOpenAL.alSourcefv( mSourceName, AL_VELOCITY, velocity ); +} + +void SFXALVoice::setTransform( const MatrixF& transform ) +{ + AL_SANITY_CHECK(); + + // Torque and OpenAL are both right handed + // systems, so no coordinate flipping is needed. + + Point3F pos, dir; + transform.getColumn( 3, &pos ); + transform.getColumn( 1, &dir ); + + mOpenAL.alSourcefv( mSourceName, AL_POSITION, pos ); + mOpenAL.alSourcefv( mSourceName, AL_DIRECTION, dir ); +} + +void SFXALVoice::setVolume( F32 volume ) +{ + AL_SANITY_CHECK(); + + mOpenAL.alSourcef( mSourceName, AL_GAIN, volume ); +} + +void SFXALVoice::setPitch( F32 pitch ) +{ + AL_SANITY_CHECK(); + + mOpenAL.alSourcef( mSourceName, AL_PITCH, pitch ); +} + +void SFXALVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ) +{ + AL_SANITY_CHECK(); + + mOpenAL.alSourcef( mSourceName, AL_CONE_INNER_ANGLE, innerAngle ); + mOpenAL.alSourcef( mSourceName, AL_CONE_OUTER_ANGLE, outerAngle ); + mOpenAL.alSourcef( mSourceName, AL_CONE_OUTER_GAIN, outerVolume ); +} diff --git a/sfx/openal/sfxALVoice.h b/sfx/openal/sfxALVoice.h new file mode 100644 index 0000000..a5a6f38 --- /dev/null +++ b/sfx/openal/sfxALVoice.h @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXALVOICE_H_ +#define _SFXALVOICE_H_ + +#ifndef _SFXVOICE_H_ + #include "sfx/sfxVoice.h" +#endif +#ifndef _OPENALFNTABLE + #include "sfx/openal/LoadOAL.h" +#endif +#ifndef _PLATFORM_THREADS_MUTEX_H_ + #include "platform/threads/mutex.h" +#endif + + +class SFXALBuffer; +class SFXALDevice; + +class SFXALVoice : public SFXVoice +{ + public: + + typedef SFXVoice Parent; + friend class SFXALDevice; + friend class SFXALBuffer; + + protected: + + SFXALVoice( const OPENALFNTABLE &oalft, + SFXALBuffer *buffer, + ALuint sourceName ); + + ALuint mSourceName; + + /// Buggy OAL jumps around when pausing. Save playback cursor here. + F32 mResumeAtSampleOffset; + + /// Amount by which OAL's reported sample position is offset. + /// + /// OAL's sample position is relative to the current queue state, + /// so we manually need to keep track of how far into the total + /// queue we are. + U32 mSampleOffset; + + Mutex mMutex; + + const OPENALFNTABLE &mOpenAL; + + /// + SFXALBuffer* _getBuffer() const + { + return ( SFXALBuffer* ) mBuffer.getPointer(); + } + + /// For non-streaming buffers, late-bind the audio buffer + /// to the source as OAL will not accept writes to buffers + /// already bound. + void _lateBindStaticBufferIfNecessary(); + + // SFXVoice. + virtual SFXStatus _status() const; + virtual void _play(); + virtual void _pause(); + virtual void _stop(); + virtual void _seek( U32 sample ); + virtual U32 _tell() const; + + public: + + static SFXALVoice* create( SFXALDevice* device, + SFXALBuffer *buffer ); + + virtual ~SFXALVoice(); + + /// SFXVoice + void setMinMaxDistance( F32 min, F32 max ); + void play( bool looping ); + void setVelocity( const VectorF& velocity ); + void setTransform( const MatrixF& transform ); + void setVolume( F32 volume ); + void setPitch( F32 pitch ); + void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ); + +}; + +#endif // _SFXALVOICE_H_ \ No newline at end of file diff --git a/sfx/openal/win32/LoadOAL.cpp b/sfx/openal/win32/LoadOAL.cpp new file mode 100644 index 0000000..50d8cea --- /dev/null +++ b/sfx/openal/win32/LoadOAL.cpp @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2006, Creative Labs Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions and + * the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the distribution. + * * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include "sfx/openal/LoadOAL.h" + +HINSTANCE g_hOpenALDLL = NULL; + +ALboolean LoadOAL10Library(char *szOALFullPathName, LPOPENALFNTABLE lpOALFnTable) +{ + if (!lpOALFnTable) + return AL_FALSE; + + if (szOALFullPathName) + g_hOpenALDLL = LoadLibraryA(szOALFullPathName); + else + g_hOpenALDLL = LoadLibraryA("openal32.dll"); + + if (!g_hOpenALDLL) + return AL_FALSE; + + memset(lpOALFnTable, 0, sizeof(OPENALFNTABLE)); + + // Get function pointers + lpOALFnTable->alEnable = (LPALENABLE)GetProcAddress(g_hOpenALDLL, "alEnable"); + if (lpOALFnTable->alEnable == NULL) + { + OutputDebugStringA("Failed to retrieve 'alEnable' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDisable = (LPALDISABLE)GetProcAddress(g_hOpenALDLL, "alDisable"); + if (lpOALFnTable->alDisable == NULL) + { + OutputDebugStringA("Failed to retrieve 'alDisable' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsEnabled = (LPALISENABLED)GetProcAddress(g_hOpenALDLL, "alIsEnabled"); + if (lpOALFnTable->alIsEnabled == NULL) + { + OutputDebugStringA("Failed to retrieve 'alIsEnabled' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBoolean = (LPALGETBOOLEAN)GetProcAddress(g_hOpenALDLL, "alGetBoolean"); + if (lpOALFnTable->alGetBoolean == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetBoolean' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetInteger = (LPALGETINTEGER)GetProcAddress(g_hOpenALDLL, "alGetInteger"); + if (lpOALFnTable->alGetInteger == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetInteger' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetFloat = (LPALGETFLOAT)GetProcAddress(g_hOpenALDLL, "alGetFloat"); + if (lpOALFnTable->alGetFloat == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetFloat' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetDouble = (LPALGETDOUBLE)GetProcAddress(g_hOpenALDLL, "alGetDouble"); + if (lpOALFnTable->alGetDouble == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetDouble' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBooleanv = (LPALGETBOOLEANV)GetProcAddress(g_hOpenALDLL, "alGetBooleanv"); + if (lpOALFnTable->alGetBooleanv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetBooleanv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetIntegerv = (LPALGETINTEGERV)GetProcAddress(g_hOpenALDLL, "alGetIntegerv"); + if (lpOALFnTable->alGetIntegerv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetIntegerv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetFloatv = (LPALGETFLOATV)GetProcAddress(g_hOpenALDLL, "alGetFloatv"); + if (lpOALFnTable->alGetFloatv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetFloatv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetDoublev = (LPALGETDOUBLEV)GetProcAddress(g_hOpenALDLL, "alGetDoublev"); + if (lpOALFnTable->alGetDoublev == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetDoublev' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetString = (LPALGETSTRING)GetProcAddress(g_hOpenALDLL, "alGetString"); + if (lpOALFnTable->alGetString == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetString' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetError = (LPALGETERROR)GetProcAddress(g_hOpenALDLL, "alGetError"); + if (lpOALFnTable->alGetError == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetError' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsExtensionPresent = (LPALISEXTENSIONPRESENT)GetProcAddress(g_hOpenALDLL, "alIsExtensionPresent"); + if (lpOALFnTable->alIsExtensionPresent == NULL) + { + OutputDebugStringA("Failed to retrieve 'alIsExtensionPresent' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetProcAddress = (LPALGETPROCADDRESS)GetProcAddress(g_hOpenALDLL, "alGetProcAddress"); + if (lpOALFnTable->alGetProcAddress == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetProcAddress' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetEnumValue = (LPALGETENUMVALUE)GetProcAddress(g_hOpenALDLL, "alGetEnumValue"); + if (lpOALFnTable->alGetEnumValue == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetEnumValue' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListeneri = (LPALLISTENERI)GetProcAddress(g_hOpenALDLL, "alListeneri"); + if (lpOALFnTable->alListeneri == NULL) + { + OutputDebugStringA("Failed to retrieve 'alListeneri' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListenerf = (LPALLISTENERF)GetProcAddress(g_hOpenALDLL, "alListenerf"); + if (lpOALFnTable->alListenerf == NULL) + { + OutputDebugStringA("Failed to retrieve 'alListenerf' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListener3f = (LPALLISTENER3F)GetProcAddress(g_hOpenALDLL, "alListener3f"); + if (lpOALFnTable->alListener3f == NULL) + { + OutputDebugStringA("Failed to retrieve 'alListener3f' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alListenerfv = (LPALLISTENERFV)GetProcAddress(g_hOpenALDLL, "alListenerfv"); + if (lpOALFnTable->alListenerfv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alListenerfv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListeneri = (LPALGETLISTENERI)GetProcAddress(g_hOpenALDLL, "alGetListeneri"); + if (lpOALFnTable->alGetListeneri == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetListeneri' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListenerf =(LPALGETLISTENERF)GetProcAddress(g_hOpenALDLL, "alGetListenerf"); + if (lpOALFnTable->alGetListenerf == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetListenerf' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListener3f = (LPALGETLISTENER3F)GetProcAddress(g_hOpenALDLL, "alGetListener3f"); + if (lpOALFnTable->alGetListener3f == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetListener3f' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetListenerfv = (LPALGETLISTENERFV)GetProcAddress(g_hOpenALDLL, "alGetListenerfv"); + if (lpOALFnTable->alGetListenerfv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetListenerfv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGenSources = (LPALGENSOURCES)GetProcAddress(g_hOpenALDLL, "alGenSources"); + if (lpOALFnTable->alGenSources == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGenSources' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDeleteSources = (LPALDELETESOURCES)GetProcAddress(g_hOpenALDLL, "alDeleteSources"); + if (lpOALFnTable->alDeleteSources == NULL) + { + OutputDebugStringA("Failed to retrieve 'alDeleteSources' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsSource = (LPALISSOURCE)GetProcAddress(g_hOpenALDLL, "alIsSource"); + if (lpOALFnTable->alIsSource == NULL) + { + OutputDebugStringA("Failed to retrieve 'alIsSource' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcei = (LPALSOURCEI)GetProcAddress(g_hOpenALDLL, "alSourcei"); + if (lpOALFnTable->alSourcei == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourcei' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcef = (LPALSOURCEF)GetProcAddress(g_hOpenALDLL, "alSourcef"); + if (lpOALFnTable->alSourcef == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourcef' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSource3f = (LPALSOURCE3F)GetProcAddress(g_hOpenALDLL, "alSource3f"); + if (lpOALFnTable->alSource3f == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSource3f' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcefv = (LPALSOURCEFV)GetProcAddress(g_hOpenALDLL, "alSourcefv"); + if (lpOALFnTable->alSourcefv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourcefv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetSourcei = (LPALGETSOURCEI)GetProcAddress(g_hOpenALDLL, "alGetSourcei"); + if (lpOALFnTable->alGetSourcei == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetSourcei' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetSourcef = (LPALGETSOURCEF)GetProcAddress(g_hOpenALDLL, "alGetSourcef"); + if (lpOALFnTable->alGetSourcef == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetSourcef' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetSourcefv = (LPALGETSOURCEFV)GetProcAddress(g_hOpenALDLL, "alGetSourcefv"); + if (lpOALFnTable->alGetSourcefv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetSourcefv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcePlayv = (LPALSOURCEPLAYV)GetProcAddress(g_hOpenALDLL, "alSourcePlayv"); + if (lpOALFnTable->alSourcePlayv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourcePlayv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceStopv = (LPALSOURCESTOPV)GetProcAddress(g_hOpenALDLL, "alSourceStopv"); + if (lpOALFnTable->alSourceStopv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourceStopv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcePlay = (LPALSOURCEPLAY)GetProcAddress(g_hOpenALDLL, "alSourcePlay"); + if (lpOALFnTable->alSourcePlay == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourcePlay' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourcePause = (LPALSOURCEPAUSE)GetProcAddress(g_hOpenALDLL, "alSourcePause"); + if (lpOALFnTable->alSourcePause == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourcePause' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceStop = (LPALSOURCESTOP)GetProcAddress(g_hOpenALDLL, "alSourceStop"); + if (lpOALFnTable->alSourceStop == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourceStop' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceRewind = (LPALSOURCEREWIND)GetProcAddress(g_hOpenALDLL, "alSourceRewind"); + if (lpOALFnTable->alSourceRewind == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourceRewind' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGenBuffers = (LPALGENBUFFERS)GetProcAddress(g_hOpenALDLL, "alGenBuffers"); + if (lpOALFnTable->alGenBuffers == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGenBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDeleteBuffers = (LPALDELETEBUFFERS)GetProcAddress(g_hOpenALDLL, "alDeleteBuffers"); + if (lpOALFnTable->alDeleteBuffers == NULL) + { + OutputDebugStringA("Failed to retrieve 'alDeleteBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alIsBuffer = (LPALISBUFFER)GetProcAddress(g_hOpenALDLL, "alIsBuffer"); + if (lpOALFnTable->alIsBuffer == NULL) + { + OutputDebugStringA("Failed to retrieve 'alIsBuffer' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alBufferData = (LPALBUFFERDATA)GetProcAddress(g_hOpenALDLL, "alBufferData"); + if (lpOALFnTable->alBufferData == NULL) + { + OutputDebugStringA("Failed to retrieve 'alBufferData' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBufferi = (LPALGETBUFFERI)GetProcAddress(g_hOpenALDLL, "alGetBufferi"); + if (lpOALFnTable->alGetBufferi == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetBufferi' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alGetBufferf = (LPALGETBUFFERF)GetProcAddress(g_hOpenALDLL, "alGetBufferf"); + if (lpOALFnTable->alGetBufferf == NULL) + { + OutputDebugStringA("Failed to retrieve 'alGetBufferf' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceQueueBuffers = (LPALSOURCEQUEUEBUFFERS)GetProcAddress(g_hOpenALDLL, "alSourceQueueBuffers"); + if (lpOALFnTable->alSourceQueueBuffers == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourceQueueBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alSourceUnqueueBuffers = (LPALSOURCEUNQUEUEBUFFERS)GetProcAddress(g_hOpenALDLL, "alSourceUnqueueBuffers"); + if (lpOALFnTable->alSourceUnqueueBuffers == NULL) + { + OutputDebugStringA("Failed to retrieve 'alSourceUnqueueBuffers' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDistanceModel = (LPALDISTANCEMODEL)GetProcAddress(g_hOpenALDLL, "alDistanceModel"); + if (lpOALFnTable->alDistanceModel == NULL) + { + OutputDebugStringA("Failed to retrieve 'alDistanceModel' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDopplerFactor = (LPALDOPPLERFACTOR)GetProcAddress(g_hOpenALDLL, "alDopplerFactor"); + if (lpOALFnTable->alDopplerFactor == NULL) + { + OutputDebugStringA("Failed to retrieve 'alDopplerFactor' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alDopplerVelocity = (LPALDOPPLERVELOCITY)GetProcAddress(g_hOpenALDLL, "alDopplerVelocity"); + if (lpOALFnTable->alDopplerVelocity == NULL) + { + OutputDebugStringA("Failed to retrieve 'alDopplerVelocity' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetString = (LPALCGETSTRING)GetProcAddress(g_hOpenALDLL, "alcGetString"); + if (lpOALFnTable->alcGetString == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcGetString' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetIntegerv = (LPALCGETINTEGERV)GetProcAddress(g_hOpenALDLL, "alcGetIntegerv"); + if (lpOALFnTable->alcGetIntegerv == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcGetIntegerv' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcOpenDevice = (LPALCOPENDEVICE)GetProcAddress(g_hOpenALDLL, "alcOpenDevice"); + if (lpOALFnTable->alcOpenDevice == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcOpenDevice' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcCloseDevice = (LPALCCLOSEDEVICE)GetProcAddress(g_hOpenALDLL, "alcCloseDevice"); + if (lpOALFnTable->alcCloseDevice == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcCloseDevice' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcCreateContext = (LPALCCREATECONTEXT)GetProcAddress(g_hOpenALDLL, "alcCreateContext"); + if (lpOALFnTable->alcCreateContext == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcCreateContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcMakeContextCurrent = (LPALCMAKECONTEXTCURRENT)GetProcAddress(g_hOpenALDLL, "alcMakeContextCurrent"); + if (lpOALFnTable->alcMakeContextCurrent == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcMakeContextCurrent' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcProcessContext = (LPALCPROCESSCONTEXT)GetProcAddress(g_hOpenALDLL, "alcProcessContext"); + if (lpOALFnTable->alcProcessContext == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcProcessContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetCurrentContext = (LPALCGETCURRENTCONTEXT)GetProcAddress(g_hOpenALDLL, "alcGetCurrentContext"); + if (lpOALFnTable->alcGetCurrentContext == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcGetCurrentContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetContextsDevice = (LPALCGETCONTEXTSDEVICE)GetProcAddress(g_hOpenALDLL, "alcGetContextsDevice"); + if (lpOALFnTable->alcGetContextsDevice == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcGetContextsDevice' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcSuspendContext = (LPALCSUSPENDCONTEXT)GetProcAddress(g_hOpenALDLL, "alcSuspendContext"); + if (lpOALFnTable->alcSuspendContext == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcSuspendContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcDestroyContext = (LPALCDESTROYCONTEXT)GetProcAddress(g_hOpenALDLL, "alcDestroyContext"); + if (lpOALFnTable->alcDestroyContext == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcDestroyContext' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetError = (LPALCGETERROR)GetProcAddress(g_hOpenALDLL, "alcGetError"); + if (lpOALFnTable->alcGetError == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcGetError' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcIsExtensionPresent = (LPALCISEXTENSIONPRESENT)GetProcAddress(g_hOpenALDLL, "alcIsExtensionPresent"); + if (lpOALFnTable->alcIsExtensionPresent == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcIsExtensionPresent' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetProcAddress = (LPALCGETPROCADDRESS)GetProcAddress(g_hOpenALDLL, "alcGetProcAddress"); + if (lpOALFnTable->alcGetProcAddress == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcGetProcAddress' function address\n"); + return AL_FALSE; + } + lpOALFnTable->alcGetEnumValue = (LPALCGETENUMVALUE)GetProcAddress(g_hOpenALDLL, "alcGetEnumValue"); + if (lpOALFnTable->alcGetEnumValue == NULL) + { + OutputDebugStringA("Failed to retrieve 'alcGetEnumValue' function address\n"); + return AL_FALSE; + } + + return AL_TRUE; +} + +ALvoid UnloadOAL10Library() +{ + // Unload the dll + if (g_hOpenALDLL) + { + FreeLibrary(g_hOpenALDLL); + g_hOpenALDLL = NULL; + } +} \ No newline at end of file diff --git a/sfx/sfxBuffer.cpp b/sfx/sfxBuffer.cpp new file mode 100644 index 0000000..d1a85a4 --- /dev/null +++ b/sfx/sfxBuffer.cpp @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxBuffer.h" +#include "sfx/sfxVoice.h" +#include "sfx/sfxDescription.h" +#include "sfx/sfxInternal.h" + + +Signal< void( SFXBuffer* ) > SFXBuffer::smBufferDestroyedSignal; + + +SFXBuffer::SFXBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description, bool createAsyncState ) + : mStatus( STATUS_Null ), + mIsStreaming( description->mIsStreaming ), + mFormat( stream->getFormat() ), + mDuration( stream->getDuration() ), + mUniqueVoice( NULL ), + mIsDead( false ), + mIsLooping( description->mIsLooping ), + mIsUnique( description->mIsStreaming ) +{ + using namespace SFXInternal; + + if( createAsyncState ) + { + U32 packetLength = description->mStreamPacketSize; + U32 readAhead = description->mStreamReadAhead; + + ThreadSafeRef< SFXStream > streamRef( stream ); + mAsyncState = new AsyncState( + new SFXAsyncStream + ( streamRef, mIsStreaming, packetLength, readAhead, + mIsStreaming ? description->mIsLooping : false ) ); + } +} + +SFXBuffer::SFXBuffer( SFXDescription* description ) + : mStatus( STATUS_Ready ), + mIsStreaming( false ), // Not streaming through our system. + mDuration( 0 ), // Must be set by subclass. + mUniqueVoice( NULL ), + mIsDead( false ), + mIsLooping( description->mIsLooping ), + mIsUnique( false ) // Must be set by subclass. +{ +} + +SFXBuffer::~SFXBuffer() +{ + smBufferDestroyedSignal.trigger( this ); +} + +void SFXBuffer::load() +{ + if( getStatus() == STATUS_Null ) + { + AssertFatal( mAsyncState != NULL, "SFXBuffer::load() - no async state!" ); + + _setStatus( STATUS_Loading ); + SFXInternal::UPDATE_LIST().add( this ); + mAsyncState->mStream->start(); + } +} + +bool SFXBuffer::update() +{ + using namespace SFXInternal; + + if( isDead() ) + { + mAsyncState->mStream->stop(); + mAsyncState = NULL; + gDeadBufferList.pushFront( this ); + return false; + } + else if( isAtEnd() && isStreaming() ) + return true; + + AssertFatal( mAsyncState != NULL, "SFXBuffer::update() - async state has already been released" ); + + bool needFurtherUpdates = true; + if( !isStreaming() ) + { + // Not a streaming buffer. If the async stream has its data + // ready, we load it and finish up on our async stuff. + + SFXStreamPacket* packet; + while( mAsyncState->mStream->read( &packet, 1 ) ) + { + bool isLast = packet->mIsLast; + + write( &packet, 1 ); + packet = NULL; + + _setStatus( STATUS_Ready ); + + if( isLast ) + { + mAsyncState = NULL; + needFurtherUpdates = false; + break; + } + } + } + else + { + // A streaming buffer. + // + // If we don't have a queue, construct one now. Note that when doing + // a stream seek on us, SFXVoice will drop our async stream and queue. + // Work on local copies of the pointers to allow having the async state + // be switched in parallel. + // + // Note that despite us being a streamed buffer, our unique voice may + // not yet have been assigned to us. + + AsyncStatePtr state = mAsyncState; + if( !state->mQueue && !mUniqueVoice.isNull() ) + { + // Make sure we have no data currently submitted to the device. + // This will stop and discard an outdated feed if we've been + // switching streams. + + _setStatus( STATUS_Loading ); + _flush(); + + // Create a new queue. + + state->mQueue = new SFXAsyncQueue( mUniqueVoice, this, mIsLooping ); + } + + // Check the queue. + + if( state->mQueue != NULL ) + { + // Feed the queue, if necessary and possible. + + while( state->mQueue->needPacket() ) + { + SFXStreamPacket* packet; + if( state->mStream->read( &packet, 1 ) ) + { + state->mQueue->submitPacket( packet, packet->getSampleCount() ); + _setStatus( STATUS_Ready ); + } + else + break; + } + + // Detect buffer underrun and end-of-stream. + + if( isReady() && state->mQueue->isEmpty() ) + _setStatus( STATUS_Blocked ); + else if( state->mQueue->isAtEnd() ) + _setStatus( STATUS_AtEnd ); + } + } + + return needFurtherUpdates; +} + +void SFXBuffer::destroySelf() +{ + AssertFatal( !isDead(), "SFXBuffer::destroySelf() - buffer already dead" ); + + mIsDead = true; + if( !mAsyncState ) + { + // Easy way. This buffer has finished all its async + // processing, so we can just kill it. + + delete this; + } + else + { + // Hard way. We will have to make the buffer finish + // all its concurrent stuff, so we mark it dead, make sure + // to see an update, and then wait for the buffer to surface + // on the dead buffer list. + + SFXInternal::TriggerUpdate(); + } +} + +void SFXBuffer::_setStatus( EStatus status ) +{ + if( mStatus != status ) + { + mOnStatusChange.trigger( this, status ); + mStatus = status; + } +} + +SFXBuffer::AsyncState::AsyncState() + : mQueue( NULL ) +{ +} + +SFXBuffer::AsyncState::AsyncState( SFXInternal::SFXAsyncStream* stream ) + : mStream( stream ), mQueue( NULL ) +{ +} + +SFXBuffer::AsyncState::~AsyncState() +{ + if( mQueue ) + SAFE_DELETE( mQueue ); +} diff --git a/sfx/sfxBuffer.h b/sfx/sfxBuffer.h new file mode 100644 index 0000000..5e249b0 --- /dev/null +++ b/sfx/sfxBuffer.h @@ -0,0 +1,206 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXBUFFER_H_ +#define _SFXBUFFER_H_ + +#ifndef _REFBASE_H_ +# include "core/util/refBase.h" +#endif +#ifndef _TSIGNAL_H_ +# include "core/util/tSignal.h" +#endif +#ifndef _TSTREAM_H_ +# include "core/stream/tStream.h" +#endif +#ifndef _SFXCOMMON_H_ +# include "sfx/sfxCommon.h" +#endif +#ifndef _THREADSAFEREFCOUNT_H_ +# include "platform/threads/threadSafeRefCount.h" +#endif + + +class SFXStream; +class SFXDescription; +class SFXVoice; + +namespace SFXInternal { + class SFXStreamPacket; + class SFXAsyncStream; + class SFXAsyncQueue; + void PurgeDeadBuffers(); +} + + +/// The buffer interface hides the details of how the device +/// holds sound data for playback. +/// +/// A sound buffer may either be loaded once completely and then +/// played as needed or it may be progressively streamed from +/// an SFXStream. In the latter case, there can only be a single +/// voice tied to the buffer. +/// +/// @note SFXDevice is the last instance when it comes to ownership +/// of SFXBuffers. If the SFXDevice goes away, it will take all +/// SFXBuffers with it, regardless of whether there are still strong +/// refs to it. Use StrongWeakRefPtrs to keep pointers to +/// SFXBuffers! +/// +/// @see SFXStream +class SFXBuffer : public StrongRefBase, + public IPolled, + public IOutputStream< SFXInternal::SFXStreamPacket* > +{ + friend class SFXVoice; // mUniqueVoice + friend void SFXInternal::PurgeDeadBuffers(); // dtor + + public: + + typedef void Parent; + + /// Status indicators for sound buffers. + enum EStatus + { + STATUS_Null, ///< Initial state. + STATUS_Loading, ///< Buffer has requested data and is waiting for queue to fill up. + STATUS_Ready, ///< Playback queue is fed and ready (non-stream buffers will stop at this state). + STATUS_Blocked, ///< Queue is starved and playback thus held until further data is available (streaming buffers only). + STATUS_AtEnd, ///< Buffer has read all its streaming data (streaming buffers only). + }; + + /// This signal is triggered from SFXBuffer's destructor so the sound system + /// can keep track of buffers being released on the device. + static Signal< void( SFXBuffer* ) > smBufferDestroyedSignal; + + protected: + + typedef ThreadSafeRef< SFXInternal::SFXAsyncStream > SFXAsyncStreamPtr; + typedef SFXInternal::SFXAsyncQueue* SFXAsyncQueuePtr; + + /// Encapsulates the async I/O state of the sound buffer. + struct AsyncState : public ThreadSafeRefCount< AsyncState > + { + /// The sound packet stream. + SFXAsyncStreamPtr mStream; + + /// The packet queue that feeds into the actual device buffer. + /// Only used for streaming buffers; non-streaming buffers directly receive + /// and upload sound packets without queuing. + SFXAsyncQueuePtr mQueue; + + AsyncState(); + AsyncState( SFXInternal::SFXAsyncStream* stream ); + ~AsyncState(); + }; + + typedef ThreadSafeRef< AsyncState > AsyncStatePtr; + + /// Create a new buffer from "stream" using the parameters in "description". + /// If "createAsyncStream" is true, the asynchronous loading state for the + /// buffer will be set up in the constructor. + SFXBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description, bool createAsyncState = true ); + + /// + SFXBuffer( SFXDescription* description ); + + virtual ~SFXBuffer(); + + /// The buffer readiness status. + EStatus mStatus; + + /// The sound sample format used by the buffer. + SFXFormat mFormat; + + /// Total playback time of the associated sound stream in milliseconds. + /// @note For streaming buffers, this will not correspond to the actual + /// playtime of the device buffer. + U32 mDuration; + + /// If true, this is a continuously streaming buffer. + bool mIsStreaming; + + /// For streaming buffers, tells whether the source stream loops. + bool mIsLooping; + + /// If true, this buffer can only have a single SFXVoice attached. + bool mIsUnique; + + /// If true, the buffer is dead and will be deleted. Can't be in status + /// for synchronization reasons. + bool mIsDead; + + /// Pointer to structure keeping the asynchronous I/O state of the buffer. + /// For non-streaming buffers, this is released as soon as all data is loaded. + /// + /// To allow seeking in streaming buffers even after playback has ended, + /// we do not release the async state of these buffers until the buffer is + /// actually released itself. This allows to always access the associated + /// input stream. + AsyncStatePtr mAsyncState; + + /// If this is a unique buffer (i.e. a streaming buffer), then this holds + /// the reference to the unique voice. + StrongWeakRefPtr< SFXVoice > mUniqueVoice; + + /// Set the buffer status and trigger mOnStatusChange if the status changes. + /// @note Called on both the SFX update thread and the main thread. + void _setStatus( EStatus status ); + + /// Flush all queue state for this buffer on the device. + /// @note Called on the SFX update thread. + virtual void _flush() = 0; + + public: + + /// Signal that is triggered when the buffer status changes. + /// @note This signal is triggered on the same thread that the buffer update + /// code runs on. + Signal< void( SFXBuffer* buffer, EStatus newStatus ) > mOnStatusChange; + + /// @return The current buffer loading/queue status. + EStatus getStatus() const { return mStatus; } + + /// @return The sound sample format used by the buffer. + const SFXFormat& getFormat() const { return mFormat; } + + /// @return The total playback time of the buffer in milliseconds. + U32 getDuration() const { return mDuration; } + + /// @return The number of bytes consumed by this sound buffer. + virtual U32 getMemoryUsed() const { return 0; } + + /// @return True if the buffer does continuous sound streaming. + bool isStreaming() const { return mIsStreaming; } + + /// @return True if the buffer is pending deletion. + bool isDead() const { return mIsDead; } + + /// @return True if the buffer's packet queue is loaded and ready for playback. + bool isReady() const { return ( getStatus() == STATUS_Ready ); } + + /// @return True if the buffer's packet queue is still loading. + bool isLoading() const { return ( getStatus() == STATUS_Loading ); } + + /// @return True if the buffer's packet queue has been starved and is waiting for data. + bool isBlocked() const { return ( getStatus() == STATUS_Blocked ); } + + /// @return True if the buffer has exhausted its source stream + bool isAtEnd() const { return ( getStatus() == STATUS_AtEnd ); } + + /// @return True if the buffer can only have a single SFXVoice attached to it. + bool isUnique() const { return mIsUnique; } + + /// Start the async request chain for the buffer. + void load(); + + // IPolled. + virtual bool update(); + + // WeakRefBase. + virtual void destroySelf(); +}; + +#endif // _SFXBUFFER_H_ diff --git a/sfx/sfxCommon.h b/sfx/sfxCommon.h new file mode 100644 index 0000000..733f43b --- /dev/null +++ b/sfx/sfxCommon.h @@ -0,0 +1,208 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXCOMMON_H_ +#define _SFXCOMMON_H_ + +#ifndef _PLATFORM_H_ + #include "platform/platform.h" +#endif + + + +//----------------------------------------------------------------------------- +// SFXStatus. +//----------------------------------------------------------------------------- + + +/// The sound playback state. +enum SFXStatus +{ + SFXStatusNull, ///< Initial state; no operation yet performed on sound. + SFXStatusPlaying, ///< Sound is playing. + SFXStatusStopped, ///< Sound has been stopped. + SFXStatusPaused, ///< Sound is paused. + SFXStatusBlocked, ///< Sound stream is starved and playback blocked. +}; + + +inline const char* SFXStatusToString( SFXStatus status ) +{ + switch ( status ) + { + case SFXStatusPlaying: return "playing"; + case SFXStatusStopped: return "stopped"; + case SFXStatusPaused: return "paused"; + case SFXStatusBlocked: return "blocked"; + + case SFXStatusNull: + default: ; + } + + return "null"; +} + + +//----------------------------------------------------------------------------- +// SFXDistanceModel. +//----------------------------------------------------------------------------- + + +/// Rolloff curve used for distance volume attenuation of 3D sounds. +enum SFXDistanceModel +{ + SFXDistanceModelLinear, ///< Volume decreases linearly from min to max where it reaches zero. + SFXDistanceModelLogarithmic, ///< Volume halves every min distance steps starting from min distance; attenuation stops at max distance. +}; + + +/// Compute the distance attenuation based on the given distance model. +/// +/// @param minDistance Reference distance; attenuation starts here. +/// @param maxDistance +/// @param distance Actual distance of sound from listener. +/// @param volume Unattenuated volume. +/// @param rolloffFactor Rolloff curve scale factor. +/// +/// @return The attenuated volume. +inline F32 SFXDistanceAttenuation( SFXDistanceModel model, F32 minDistance, F32 maxDistance, F32 distance, F32 volume, F32 rolloffFactor ) +{ + F32 gain = 1.0f; + + switch( model ) + { + case SFXDistanceModelLinear: + + distance = getMax( distance, minDistance ); + distance = getMin( distance, maxDistance ); + + gain = ( 1 - ( distance - minDistance ) / ( maxDistance - minDistance ) ); + break; + + case SFXDistanceModelLogarithmic: + + distance = getMax( distance, minDistance ); + distance = getMin( distance, maxDistance ); + + gain = minDistance / ( minDistance + rolloffFactor * ( distance - minDistance ) ); + break; + + } + + return ( volume * gain ); +} + + +//----------------------------------------------------------------------------- +// SFXFormat. +//----------------------------------------------------------------------------- + + +/// This class defines the various types of sound data that may be +/// used in the sound system. +/// +/// Unlike with most sound APIs, we consider each sample point to comprise +/// all channels in a sound stream rather than only one value for a single +/// channel. +class SFXFormat +{ + protected: + + /// The number of sound channels in the data. + U8 mChannels; + + /// The number of bits per sound sample. + U8 mBitsPerSample; + + /// The frequency in samples per second. + U32 mSamplesPerSecond; + + public: + + SFXFormat( U8 channels = 0, + U8 bitsPerSample = 0, + U32 samplesPerSecond = 0 ) + : mChannels( channels ), + mSamplesPerSecond( samplesPerSecond ), + mBitsPerSample( bitsPerSample ) + {} + + /// Copy constructor. + SFXFormat( const SFXFormat &format ) + : mChannels( format.mChannels ), + mBitsPerSample( format.mBitsPerSample ), + mSamplesPerSecond( format.mSamplesPerSecond ) + {} + + public: + + /// Sets the format. + void set( U8 channels, + U8 bitsPerSample, + U32 samplesPerSecond ) + { + mChannels = channels; + mBitsPerSample = bitsPerSample; + mSamplesPerSecond = samplesPerSecond; + } + + /// Comparision between formats. + bool operator == ( const SFXFormat& format ) const + { + return mChannels == format.mChannels && + mBitsPerSample == format.mBitsPerSample && + mSamplesPerSecond == format.mSamplesPerSecond; + } + + /// Returns the number of sound channels. + U8 getChannels() const { return mChannels; } + + /// Returns true if there is a single sound channel. + bool isMono() const { return mChannels == 1; } + + /// Is true if there are two sound channels. + bool isStereo() const { return mChannels == 2; } + + /// Is true if there are more than two sound channels. + bool isMultiChannel() const { return mChannels > 2; } + + /// + U32 getSamplesPerSecond() const { return mSamplesPerSecond; } + + /// The bits of data per channel. + U8 getBitsPerChannel() const { return mBitsPerSample / mChannels; } + + /// The number of bytes of data per channel. + U8 getBytesPerChannel() const { return getBitsPerChannel() / 8; } + + /// The number of bits per sound sample. + U8 getBitsPerSample() const { return mBitsPerSample; } + + /// The number of bytes of data per sample. + /// @note Be aware that this comprises all channels. + U8 getBytesPerSample() const { return mBitsPerSample / 8; } + + /// Returns the duration from the sample count. + U32 getDuration( U32 samples ) const + { + // Use 64bit types to avoid overflow during division. + return ( (U64)samples * (U64)1000 ) / (U64)mSamplesPerSecond; + } + + /// + U32 getSampleCount( U32 ms ) const + { + return U64( mSamplesPerSecond ) * U64( ms ) / U64( 1000 ); + } + + /// Returns the data length in bytes. + U32 getDataLength( U32 ms ) const + { + U32 bytes = ( ( (U64)ms * (U64)mSamplesPerSecond ) * (U64)getBytesPerSample() ) / (U64)1000; + return bytes; + } +}; + +#endif // _SFXCOMMON_H_ diff --git a/sfx/sfxDescription.cpp b/sfx/sfxDescription.cpp new file mode 100644 index 0000000..11fa9a3 --- /dev/null +++ b/sfx/sfxDescription.cpp @@ -0,0 +1,184 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "sfx/sfxDescription.h" +#include "sfx/sfxSystem.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "sfx/sfxInternal.h" + + +IMPLEMENT_CO_DATABLOCK_V1( SFXDescription ); + + +SFXDescription::SFXDescription() + : SimDataBlock(), + mVolume( 1 ), + mPitch( 1 ), + mIsLooping( false ), + mIsStreaming( false ), + mIs3D( false ), + mReferenceDistance( 1 ), + mMaxDistance( 100 ), + mConeInsideAngle( 360 ), + mConeOutsideAngle( 360 ), + mConeOutsideVolume( 1 ), + mChannel( 0 ), + mFadeInTime( 0.0f ), + mFadeOutTime( 0.0f ), + mStreamPacketSize( SFXInternal::SFXAsyncStream::DEFAULT_STREAM_PACKET_LENGTH ), + mStreamReadAhead( SFXInternal::SFXAsyncStream::DEFAULT_STREAM_LOOKAHEAD ) +{ +} + +SFXDescription::SFXDescription( const SFXDescription& desc ) + : SimDataBlock(), + mVolume( desc.mVolume ), + mPitch( desc.mPitch ), + mIsLooping( desc.mIsLooping ), + mIsStreaming( desc.mIsStreaming ), + mIs3D( desc.mIs3D ), + mReferenceDistance( desc.mReferenceDistance ), + mMaxDistance( desc.mMaxDistance ), + mConeInsideAngle( desc.mConeInsideAngle ), + mConeOutsideAngle( desc.mConeOutsideAngle ), + mConeOutsideVolume( desc.mConeOutsideVolume ), + mChannel( desc.mChannel ), + mFadeInTime( desc.mFadeInTime ), + mFadeOutTime( desc.mFadeOutTime ), + mStreamPacketSize( desc.mStreamPacketSize ), + mStreamReadAhead( desc.mStreamReadAhead ) +{ +} + +IMPLEMENT_CONSOLETYPE( SFXDescription ) +IMPLEMENT_GETDATATYPE( SFXDescription ) +IMPLEMENT_SETDATATYPE( SFXDescription ) + + +void SFXDescription::initPersistFields() +{ + addField( "volume", TypeF32, Offset(mVolume, SFXDescription)); + addField( "pitch", TypeF32, Offset(mPitch, SFXDescription)); + addField( "isLooping", TypeBool, Offset(mIsLooping, SFXDescription)); + addField( "isStreaming", TypeBool, Offset(mIsStreaming, SFXDescription)); + addField( "is3D", TypeBool, Offset(mIs3D, SFXDescription)); + addField( "referenceDistance", TypeF32, Offset(mReferenceDistance, SFXDescription)); + addField( "maxDistance", TypeF32, Offset(mMaxDistance, SFXDescription)); + addField( "coneInsideAngle", TypeS32, Offset(mConeInsideAngle, SFXDescription)); + addField( "coneOutsideAngle", TypeS32, Offset(mConeOutsideAngle, SFXDescription)); + addField( "coneOutsideVolume", TypeF32, Offset(mConeOutsideVolume, SFXDescription)); + addField( "channel", TypeS32, Offset(mChannel, SFXDescription)); + addField( "fadeInTime", TypeF32, Offset(mFadeInTime, SFXDescription)); + addField( "fadeOutTime", TypeF32, Offset(mFadeOutTime, SFXDescription)); + addField( "streamPacketSize", TypeS32, Offset(mStreamPacketSize, SFXDescription)); + addField( "streamReadAhead", TypeS32, Offset(mStreamReadAhead, SFXDescription)); + + Parent::initPersistFields(); +} + + +bool SFXDescription::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + // Validate the data we'll be passing to + // the audio layer. + validate(); + + return true; +} + +void SFXDescription::validate() +{ + // Validate the data we'll be passing to the audio layer. + mVolume = mClampF( mVolume, 0, 1 ); + + if( mPitch <= 0.0f ) + mPitch = 1.0f; + if( mFadeInTime < 0.0f ) + mFadeInTime = 0.0f; + if( mFadeOutTime < 0.0f ) + mFadeOutTime = 0.0f; + + mReferenceDistance = mClampF( mReferenceDistance, 0, mReferenceDistance ); + + if ( mMaxDistance <= mReferenceDistance ) + mMaxDistance = mReferenceDistance + 0.01f; + + mConeInsideAngle = mClamp( mConeInsideAngle, 0, 360 ); + mConeOutsideAngle = mClamp( mConeOutsideAngle, mConeInsideAngle, 360 ); + mConeOutsideVolume = mClampF( mConeOutsideVolume, 0, 1 ); + + mChannel = mClamp( mChannel, 0, SFXSystem::NumChannels - 1 ); +} + +void SFXDescription::packData( BitStream *stream ) +{ + Parent::packData( stream ); + + stream->writeFloat( mVolume, 6 ); + stream->writeFloat( mPitch, 6 ); + + stream->writeFlag( mIsLooping ); + + stream->writeFlag( mIsStreaming ); + stream->writeFlag( mIs3D ); + + if ( mIs3D ) + { + stream->write( mReferenceDistance ); + stream->write( mMaxDistance ); + + stream->writeInt( mConeInsideAngle, 9 ); + stream->writeInt( mConeOutsideAngle, 9 ); + + stream->writeFloat( mConeOutsideVolume, 6 ); + } + + stream->writeInt( mChannel, SFXSystem::NumChannelBits ); + stream->writeFloat( mFadeInTime, 6 ); + stream->writeFloat( mFadeOutTime, 6 ); + stream->writeInt( mStreamPacketSize, 8 ); + stream->writeInt( mStreamReadAhead, 8 ); +} + + +void SFXDescription::unpackData( BitStream *stream ) +{ + Parent::unpackData( stream ); + + mVolume = stream->readFloat( 6 ); + mPitch = stream->readFloat( 6 ); + mIsLooping = stream->readFlag(); + + mIsStreaming = stream->readFlag(); + mIs3D = stream->readFlag(); + + if ( mIs3D ) + { + stream->read( &mReferenceDistance ); + stream->read( &mMaxDistance ); + + mConeInsideAngle = stream->readInt( 9 ); + mConeOutsideAngle = stream->readInt( 9 ); + + mConeOutsideVolume = stream->readFloat( 6 ); + } + + mChannel = stream->readInt( SFXSystem::NumChannelBits ); + mFadeInTime = stream->readFloat( 6 ); + mFadeOutTime = stream->readFloat( 6 ); + mStreamPacketSize = stream->readInt( 8 ); + mStreamReadAhead = stream->readInt( 8 ); +} + + + + + diff --git a/sfx/sfxDescription.h b/sfx/sfxDescription.h new file mode 100644 index 0000000..6b5a6e5 --- /dev/null +++ b/sfx/sfxDescription.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXDESCRIPTION_H_ +#define _SFXDESCRIPTION_H_ + + +#ifndef _CONSOLETYPES_H_ + #include "console/consoleTypes.h" +#endif +#ifndef _SIMDATABLOCK_H_ + #include "console/simDataBlock.h" +#endif +#ifndef _MPOINT3_H_ + #include "math/mPoint3.h" +#endif + + +/// The SFXDescription defines how a sound should be played. +/// +/// If mConeInsideAngle and mConeOutsideAngle are not both +/// 360 then the sound will be directional and facing out +/// the Y axis. +/// +/// A few tips: +/// +/// Make sure that server SFXDescription are defined with the +/// datablock keyword, and that client SFXDescription are defined +/// with the 'new' keyword. +/// +class SFXDescription : public SimDataBlock +{ + typedef SimDataBlock Parent; + + public: + + /// The 0 to 1 volume scale. + F32 mVolume; + + /// The pitch scale. + F32 mPitch; + + /// If true the sound will loop. + bool mIsLooping; + + /// If true the sound data will be streamed from + /// disk and not loaded completely into memory. + bool mIsStreaming; + + /// If true the sound will be 3D positional. + bool mIs3D; + + /// The distance from the emitter at which the + /// sound volume is unchanged. Beyond this distance + /// the volume begins to falloff. + /// + /// This is only valid for 3D sounds. + F32 mReferenceDistance; + + /// The distance from the emitter at which the + /// sound volume becomes zero. + /// + /// This is only valid for 3D sounds. + F32 mMaxDistance; + + /// The angle in degrees of the inner part of + /// the cone. It must be within 0 to 360. + /// + /// This is only valid for 3D sounds. + U32 mConeInsideAngle; + + /// The angle in degrees of the outer part of + /// the cone. It must be greater than mConeInsideAngle + /// and less than to 360. + /// + /// This is only valid for 3D sounds. + U32 mConeOutsideAngle; + + /// The volume scalar for on/beyond the outside angle. + /// + /// This is only valid for 3D sounds. + F32 mConeOutsideVolume; + + /// The sound channel for this sound. + /// @see SFXSystem::getChannelVolume, SFXSystem::setChannelVolume + U32 mChannel; + + /// Number of seconds until playback reaches full volume after starting/resuming. + /// Zero to deactivate (default). + F32 mFadeInTime; + + /// Number of seconds to fade out fading before stopping/pausing. + /// Zero to deactivate (default). + F32 mFadeOutTime; + + /// The number of seconds of sound data to read per streaming + /// packet. Only relevant if "isStreaming" is true. + U32 mStreamPacketSize; + + /// The number of streaming packets to read and buffer in advance. + /// Only relevant if "isStreaming" is true. + U32 mStreamReadAhead; + + SFXDescription(); + SFXDescription( const SFXDescription& desc ); + DECLARE_CONOBJECT( SFXDescription ); + static void initPersistFields(); + + // SimObject + virtual bool onAdd(); + virtual void packData( BitStream* stream ); + virtual void unpackData( BitStream* stream ); + + /// Validates the description fixing any + /// parameters that are out of range. + void validate(); +}; + +DECLARE_CONSOLETYPE( SFXDescription ) + + +#endif // _SFXDESCRIPTION_H_ \ No newline at end of file diff --git a/sfx/sfxDevice.cpp b/sfx/sfxDevice.cpp new file mode 100644 index 0000000..6f44219 --- /dev/null +++ b/sfx/sfxDevice.cpp @@ -0,0 +1,192 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxDevice.h" +#include "sfx/sfxBuffer.h" +#include "sfx/sfxVoice.h" +#include "sfx/sfxInternal.h" +#include "core/tAlgorithm.h" +#include "console/console.h" +#include "console/consoleTypes.h" + + +//----------------------------------------------------------------------------- + +SFXDevice::SFXDevice( const String& name, SFXProvider* provider, bool useHardware, S32 maxBuffers ) + : mName( name ), + mProvider( provider ), + mUseHardware( useHardware ), + mMaxBuffers( maxBuffers ), + mStatNumBufferBytes( 0 ), + mStatNumBuffers( 0 ), + mStatNumVoices( 0 ) +{ + AssertFatal( provider, "We must have a provider pointer on device creation!" ); + + VECTOR_SET_ASSOCIATION( mBuffers ); + VECTOR_SET_ASSOCIATION( mVoices ); + + SFXBuffer::smBufferDestroyedSignal.notify( this, &SFXDevice::_removeBuffer ); + SFXVoice::smVoiceDestroyedSignal.notify( this, &SFXDevice::_removeVoice ); + + Con::addVariable( "SFX::Device::numBuffers", TypeS32, &mStatNumBuffers ); + Con::addVariable( "SFX::Device::numVoices", TypeS32, &mStatNumVoices ); + Con::addVariable( "SFX::Device::numBufferBytes", TypeS32, &mStatNumBufferBytes ); +} + +//----------------------------------------------------------------------------- + +SFXDevice::~SFXDevice() +{ + Con::removeVariable( "SFX::Device::numBuffers" ); + Con::removeVariable( "SFX::Device::numVoices" ); + Con::removeVariable( "SFX::Device::numBufferBytes" ); + + _releaseAllResources(); +} + +//----------------------------------------------------------------------------- + +void SFXDevice::_releaseAllResources() +{ + using namespace SFXInternal; + + // Kill the update thread, if there is one. + // Do this first so that further buffer processing + // can be done synchronously by us. + + ThreadSafeRef< SFXUpdateThread > sfxThread = UPDATE_THREAD(); + if( sfxThread != NULL ) + { + gUpdateThread = NULL; // Kill the global reference. + + sfxThread->stop(); + sfxThread->triggerUpdate(); + sfxThread->join(); + + sfxThread = NULL; + } + + // Clean up voices. Do this before cleaning up buffers so that + // resources held by voices that are tied to resources held by buffers + // get released properly. + + SFXVoice::smVoiceDestroyedSignal.remove( this, &SFXDevice::_removeVoice ); + for( VoiceIterator voice = mVoices.begin(); + voice != mVoices.end(); voice++ ) + ( *voice )->destroySelf(); + mVoices.clear(); + + // Clean up buffers. + + SFXBuffer::smBufferDestroyedSignal.remove( this, &SFXDevice::_removeBuffer ); + for( BufferIterator buffer = mBuffers.begin(); + buffer != mBuffers.end(); ++ buffer ) + if( !( *buffer )->isDead() ) + ( *buffer )->destroySelf(); + mBuffers.clear(); + + // Flush all asynchronous requests. + + THREAD_POOL().flushWorkItems(); + + // Clean out the buffer update list and kill + // all buffers that surfaced on the dead list. + // Now the sound buffers are really gone. + + UPDATE_LIST().process(); + PurgeDeadBuffers(); + + // Clean out stats. + + mStatNumBuffers = 0; + mStatNumVoices = 0; + mStatNumBufferBytes = 0; +} + +//----------------------------------------------------------------------------- + +void SFXDevice::update( const SFXListener& listener ) +{ + using namespace SFXInternal; + + // If we don't have an update thread, do the + // updates now on the main thread. + + if( !UPDATE_THREAD() ) + UPDATE_LIST().process( MAIN_THREAD_PROCESS_TIMEOUT ); + + // Clean out buffers that have surfaced on the dead + // buffer list. + + PurgeDeadBuffers(); +} + +//----------------------------------------------------------------------------- + +void SFXDevice::_addBuffer( SFXBuffer* buffer ) +{ + AssertFatal( buffer, "SFXDevice::_addBuffer() - Got a null buffer!" ); + + // Register the buffer. + + mBuffers.push_back( buffer ); + mStatNumBuffers ++; + mStatNumBufferBytes += buffer->getMemoryUsed(); + + // Start loading the buffer. + + buffer->load(); +} + +//----------------------------------------------------------------------------- + +void SFXDevice::_removeBuffer( SFXBuffer* buffer ) +{ + AssertFatal( buffer, "SFXDevice::_removeBuffer() - Got a null buffer!" ); + + BufferIterator iter = find( mBuffers.begin(), mBuffers.end(), buffer ); + if( iter != mBuffers.end() ) + { + SFXBuffer* buffer = *iter; + + mStatNumBufferBytes -= buffer->getMemoryUsed(); + mStatNumBuffers --; + + mBuffers.erase( iter ); + } +} + +//----------------------------------------------------------------------------- + +void SFXDevice::_addVoice( SFXVoice* voice ) +{ + AssertFatal( voice, "SFXDevice::_addVoice() - Got a null voice!" ); + using namespace SFXInternal; + + // Bind the voice to its buffer. This is deferred up to here in order + // to only bind voices that have been successfully constructed. + + voice->_attachToBuffer(); + + // Register the voice. + + mVoices.push_back( voice ); + mStatNumVoices ++; +} + +//----------------------------------------------------------------------------- + +void SFXDevice::_removeVoice( SFXVoice* voice ) +{ + AssertFatal( voice, "SFXDevice::_removeVoice() - Got null voice!" ); + + VoiceIterator iter = find( mVoices.begin(), mVoices.end(), voice ); + if( iter != mVoices.end() ) + { + mStatNumVoices --; + mVoices.erase( iter ); + } +} diff --git a/sfx/sfxDevice.h b/sfx/sfxDevice.h new file mode 100644 index 0000000..0bc8fcf --- /dev/null +++ b/sfx/sfxDevice.h @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXDEVICE_H_ +#define _SFXDEVICE_H_ + +#ifndef _PLATFORM_H_ + #include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif +#ifndef _SFXCOMMON_H_ + #include "sfx/sfxCommon.h" +#endif +#ifndef _THREADSAFEREF_H_ + #include "platform/threads/threadSafeRefCount.h" +#endif + + +class SFXProvider; +class SFXListener; +class SFXBuffer; +class SFXVoice; +class SFXProfile; +class SFXDevice; +class SFXStream; +class SFXDescription; + + + +class SFXDevice +{ + protected: + + typedef Vector< SFXBuffer* > BufferVector; + typedef Vector< SFXVoice* > VoiceVector; + + typedef BufferVector::iterator BufferIterator; + typedef VoiceVector::iterator VoiceIterator; + + SFXDevice( const String& name, SFXProvider* provider, bool useHardware, S32 maxBuffers ); + + /// The name of this device. + String mName; + + /// The provider which created this device. + SFXProvider* mProvider; + + /// Should the device try to use hardware processing. + bool mUseHardware; + + /// The maximum playback buffers this device will use. + S32 mMaxBuffers; + + /// Current set of sound buffers. + BufferVector mBuffers; + + /// Current set of voices. + VoiceVector mVoices; + + /// Current number of buffers. Reflected in $SFX::Device::numBuffers. + U32 mStatNumBuffers; + + /// Current number of voices. Reflected in $SFX::Device::numVoices. + U32 mStatNumVoices; + + /// Current total memory size of sound buffers. Reflected in $SFX::Device::numBufferBytes. + U32 mStatNumBufferBytes; + + /// Register a buffer with the device. + /// This also triggers the buffer's stream packet request chain. + void _addBuffer( SFXBuffer* buffer ); + + /// Unregister the given buffer. + void _removeBuffer( SFXBuffer* buffer ); + + /// Register a voice with the device. + void _addVoice( SFXVoice* voice ); + + /// Unregister the given voice. + virtual void _removeVoice( SFXVoice* buffer ); + + /// Release all resources tied to the device. Can be called repeatedly + /// without harm. It is meant for device destructors that will severe + /// the connection to the sound API and thus need all resources freed + /// before the base destructor is called. + void _releaseAllResources(); + +public: + + virtual ~SFXDevice(); + + /// Returns the provider which created this device. + SFXProvider* getProvider() const { return mProvider; } + + /// Is the device set to use hardware processing. + bool getUseHardware() const { return mUseHardware; } + + /// The maximum number of playback buffers this device will use. + S32 getMaxBuffers() const { return mMaxBuffers; } + + /// Returns the name of this device. + const String& getName() const { return mName; } + + /// Tries to create a new sound buffer. If creation fails + /// freeing another buffer will usually allow a new one to + /// be created. + /// + /// @param stream The sound data stream. + /// @param description The playback configuration. + /// + /// @return Returns a new buffer or NULL if one cannot be created. + /// + virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) = 0; + + /// Create a sound buffer directly for a file. This is for + /// devices that implemented their own custom file loading. + /// + /// @note Only implemented on specific SFXDevices. + /// @return Return a new buffer or NULL. + virtual SFXBuffer* createBuffer( const String& fileName, SFXDescription* description ) { return NULL; } + + /// Tries to create a new voice. + /// + /// @param is3d True if the voice should have 3D sound enabled. + /// @param buffer The sound data to play by the voice. + /// + /// @return Returns a new voice or NULL if one cannot be created. + virtual SFXVoice* createVoice( bool is3D, SFXBuffer* buffer ) = 0; + + /// Set the rolloff curve to be used by distance attenuation of 3D sounds. + virtual void setDistanceModel( SFXDistanceModel model ) {} + + /// Set the scale factor to use for doppler effects on 3D sounds. + virtual void setDopplerFactor( F32 factor ) {} + + /// Set the rolloff scale factor for distance attenuation of 3D sounds. + virtual void setRolloffFactor( F32 factor ) {} + + /// Return the current total number of sound buffers. + U32 getBufferCount() const { return mBuffers.size(); } + + /// Return the current total number of voices. + U32 getVoiceCount() const { return mVoices.size(); } + + /// Called from SFXSystem to do any updates the device may need to make. + virtual void update( const SFXListener& listener ); +}; + + +#endif // _SFXDEVICE_H_ diff --git a/sfx/sfxEffect.cpp b/sfx/sfxEffect.cpp new file mode 100644 index 0000000..388b960 --- /dev/null +++ b/sfx/sfxEffect.cpp @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxEffect.h" + + +//----------------------------------------------------------------------------- +// SFXOneShotEffect. +//----------------------------------------------------------------------------- + +SFXOneShotEffect::SFXOneShotEffect( SFXSource* source, U32 triggerPos, bool removeWhenDone ) + : Parent( source, removeWhenDone ), + mTriggerPos( triggerPos ) +{ +} + +bool SFXOneShotEffect::update() +{ + if( mSource->getPosition() >= mTriggerPos ) + { + _onTrigger(); + return mRemoveWhenDone; + } + else + return true; +} + +//----------------------------------------------------------------------------- +// SFXRangeEffect. +//----------------------------------------------------------------------------- + +SFXRangeEffect::SFXRangeEffect( SFXSource* source, U32 startTime, U32 endTime, bool removeWhenDone ) + : Parent( source, removeWhenDone ), + mStartTime( startTime ), + mEndTime( endTime ), + mIsActive( false ) +{ +} + +bool SFXRangeEffect::update() +{ + if( !isActive() ) + { + SFXStatus status = mSource->getStatus(); + if( ( status == SFXStatusPlaying || status == SFXStatusBlocked ) + && mSource->getPosition() >= mStartTime ) + { + mIsActive = true; + _onStart(); + } + } + + if( isActive() ) + _onUpdate(); + + if( isActive() ) + { + SFXStatus status = mSource->getStatus(); + if( ( status == SFXStatusPlaying || status == SFXStatusBlocked ) + && mSource->getPosition() > mEndTime ) + { + _onEnd(); + mIsActive = false; + + return mRemoveWhenDone; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// SFXFadeEffect. +//----------------------------------------------------------------------------- + +SFXFadeEffect::SFXFadeEffect( SFXSource* source, F32 time, F32 endVolume, U32 startTime, EOnEnd onEndDo, bool removeWhenDone ) + : Parent( source, startTime, startTime + U32( time * 1000.0f ), removeWhenDone ), + mEndVolume( endVolume ), + mOnEnd( onEndDo ) +{ + +} + +SFXFadeEffect::~SFXFadeEffect() +{ + // If the fade is still ongoing, restore the source's volume. + // For fade-in, set to end volume. For fade-out, set to start volume. + + if( isActive() ) + { + if( mStartVolume > mEndVolume ) + mSource->setVolume( mStartVolume ); + else + mSource->setVolume( mEndVolume ); + } +} + +void SFXFadeEffect::_onStart() +{ + mStartVolume = mSource->getVolume(); + mCurrentVolume = mStartVolume; +} + +void SFXFadeEffect::_onUpdate() +{ + F32 multiplier = F32( mSource->getPosition() - mStartTime ) / F32( mEndTime - mStartTime ); + + F32 newVolume; + if( mStartVolume > mEndVolume ) + newVolume = mStartVolume - ( ( mStartVolume - mEndVolume ) * multiplier ); + else + newVolume = mStartVolume + ( ( mEndVolume - mStartVolume ) * multiplier ); + + if( newVolume != mCurrentVolume ) + { + mCurrentVolume = newVolume; + mSource->setVolume( mCurrentVolume ); + } +} + +void SFXFadeEffect::_onEnd() +{ + mSource->setVolume( mEndVolume ); + + switch( mOnEnd ) + { + case ON_END_Pause: + mSource->pause( 0.0f ); // Pause without fade. + break; + + case ON_END_Stop: + mSource->stop( 0.0f ); // Stop without fade. + break; + + case ON_END_Nop: ; + } +} + +//----------------------------------------------------------------------------- +// SFXMarkerEffect. +//----------------------------------------------------------------------------- + +SFXMarkerEffect::SFXMarkerEffect( SFXSource* source, const String& name, U32 pos, bool removeWhenDone ) + : Parent( source, pos, removeWhenDone ), + mMarkerName( name ) +{ +} + +void SFXMarkerEffect::_onTrigger() +{ + Con::executef( mSource, "onMarkerPassed", mMarkerName.c_str() ); +} diff --git a/sfx/sfxEffect.h b/sfx/sfxEffect.h new file mode 100644 index 0000000..2a39f72 --- /dev/null +++ b/sfx/sfxEffect.h @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXEFFECT_H_ +#define _SFXEFFECT_H_ + +#ifndef _SFXSOURCE_H_ + #include "sfx/sfxSource.h" +#endif +#ifndef _TSTREAM_H_ + #include "core/stream/tStream.h" +#endif + + +/// An SFXEffect modifies the playback of an SFXSource while it is running. +class SFXEffect : public IPolled +{ + protected: + + /// The source that this effect works on. + SFXSource* mSource; + + /// If true, the effect is removed from the effects stack + bool mRemoveWhenDone; + + public: + + /// Create an effect that operates on "source". + SFXEffect( SFXSource* source, bool removeWhenDone = false ) + : mSource( source ) {} + + virtual ~SFXEffect() {} +}; + +/// An SFXEffect that is triggered once after passing a certain playback position. +class SFXOneShotEffect : public SFXEffect +{ + public: + + typedef SFXEffect Parent; + + protected: + + /// Playback position that triggers the effect. + U32 mTriggerPos; + + /// + virtual void _onTrigger() = 0; + + public: + + /// Create an effect that triggers when playback of "source" passes "triggerPos". + SFXOneShotEffect( SFXSource* source, U32 triggerPos, bool removeWhenDone = false ); + + // IPolled. + virtual bool update(); +}; + +/// An SFXEffect that is spans a certain range of playback time. +class SFXRangeEffect : public SFXEffect +{ + public: + + typedef SFXEffect Parent; + + protected: + + /// If true, the effect is currently being applied to the source. + bool mIsActive; + + /// Playback position in milliseconds when this effect becomes active. + U32 mStartTime; + + /// Playback position in milliseconds when this effect becomes inactive. + U32 mEndTime; + + /// Called when the play cursor passes mStartTime. + /// @note There may be latency between the cursor actually passing mStartTime + /// and this method being called. + virtual void _onStart() {} + + /// Called on each update() while the play cursor is in range. + virtual void _onUpdate() {} + + /// Called when the play cursor passes mEndTime. + /// @note There may be latency between the cursor actually passing mEndTime + /// and this method being called. + virtual void _onEnd() {} + + public: + + /// Create an effect that operates on "source" between "startTime" milliseconds + /// (inclusive) and "endTime" milliseconds (exclusive). + SFXRangeEffect( SFXSource* source, U32 startTime, U32 endTime, bool removeWhenDone = false ); + + /// + bool isActive() const { return mIsActive; } + + // IPolled. + virtual bool update(); +}; + +/// A volume fade effect (fade-in or fade-out). +class SFXFadeEffect : public SFXRangeEffect +{ + public: + + typedef SFXRangeEffect Parent; + + enum EOnEnd + { + ON_END_Nop, ///< Do nothing with source when fade is complete. + ON_END_Stop, ///< Stop source when fade is complete. + ON_END_Pause, ///< Pause source when fade is complete. + }; + + protected: + + /// Volume when beginning fade. Set when effect is activated. + F32 mStartVolume; + + /// Volume when ending fade. + F32 mEndVolume; + + /// Current volume level. + F32 mCurrentVolume; + + /// Action to perform when the fade has been completed. Defaults to no action. + EOnEnd mOnEnd; + + // SFXEffect. + virtual void _onStart(); + virtual void _onUpdate(); + virtual void _onEnd(); + + public: + + /// Create an effect that fades the volume of "source" to "endVolume" over the + /// period of "time" seconds. The fade will start at "referenceTime" using the + /// source's current volume at the time as a start. + SFXFadeEffect( SFXSource* source, F32 time, F32 endVolume, U32 startTime, EOnEnd onEndDo = ON_END_Nop, bool removeWhenDone = false ); + + virtual ~SFXFadeEffect(); +}; + +/// An effect that calls a method on the SFXSource when a particular playback position +/// is passed. +/// +/// @note At the moment, doing a setPosition() on a source will not cause markers that have +/// been jumped over in the operation to be ignored. Instead they will trigger on the +/// next update. +class SFXMarkerEffect : public SFXOneShotEffect +{ + public: + + typedef SFXOneShotEffect Parent; + + protected: + + /// Symbolic marker name that is passed to the "onMarkerPassed" callback. + String mMarkerName; + + // SFXOneShotEffect + virtual void _onTrigger(); + + public: + + SFXMarkerEffect( SFXSource* source, const String& name, U32 pos, bool removeWhenDone = false ); +}; + +#endif // !_SFXEFFECT_H_ diff --git a/sfx/sfxEnvironment.cpp b/sfx/sfxEnvironment.cpp new file mode 100644 index 0000000..c465827 --- /dev/null +++ b/sfx/sfxEnvironment.cpp @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sfx/sfxEnvironment.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" + + +IMPLEMENT_CO_DATABLOCK_V1( SFXEnvironment ); + + +SFXEnvironment::SFXEnvironment() + + /* + : //mUseRoom( true ), + mRoom( 0 ), + mRoomHF( 0 ), + mReflections( 0 ), + mReverb( 0 ), + mRoomRolloffFactor( 0.1f ), + mDecayTime( 0.1f ), + mDecayHFRatio( 0.1f ), + mReflectionsDelay( 0 ), + mReverbDelay( 0 ), + mRoomVolume( 0 ), + mEffectVolume( 0 ), + mDamping( 0 ), + mEnvironmentSize( 10 ), + mEnvironmentDiffusion( 1 ), + mAirAbsorption( 0 ), + mFlags( 0 ) + */ +{ +} + + +IMPLEMENT_CONSOLETYPE( SFXEnvironment ) +IMPLEMENT_GETDATATYPE( SFXEnvironment ) +IMPLEMENT_SETDATATYPE( SFXEnvironment ) + + +void SFXEnvironment::initPersistFields() +{ + /* + // TODO: Deal with room presets... develop our + // own based on EAX settings? + + //addField( "useRoom", TypeBool, Offset(mUseRoom, SFXEnvironment)); + //addField( "room", TypeEnum, Offset(mRoom, SFXEnvironment), 1, &gAudioEnvironmentRoomTypes); + addField( "roomHF", TypeS32, Offset(mRoomHF, SFXEnvironment)); + addField( "reflections", TypeS32, Offset(mReflections, SFXEnvironment)); + addField( "reverb", TypeS32, Offset(mReverb, SFXEnvironment)); + addField( "roomRolloffFactor", TypeF32, Offset(mRoomRolloffFactor, SFXEnvironment)); + addField( "decayTime", TypeF32, Offset(mDecayTime, SFXEnvironment)); + addField( "decayHFRatio", TypeF32, Offset(mDecayHFRatio, SFXEnvironment)); + addField( "reflectionsDelay", TypeF32, Offset(mReflectionsDelay, SFXEnvironment)); + addField( "reverbDelay", TypeF32, Offset(mReverbDelay, SFXEnvironment)); + addField( "roomVolume", TypeS32, Offset(mRoomVolume, SFXEnvironment)); + addField( "effectVolume", TypeF32, Offset(mEffectVolume, SFXEnvironment)); + addField( "damping", TypeF32, Offset(mDamping, SFXEnvironment)); + addField( "environmentSize", TypeF32, Offset(mEnvironmentSize, SFXEnvironment)); + addField( "environmentDiffusion", TypeF32, Offset(mEnvironmentDiffusion, SFXEnvironment)); + addField( "airAbsorption", TypeF32, Offset(mAirAbsorption, SFXEnvironment)); + addField( "flags", TypeS32, Offset(mFlags, SFXEnvironment)); + */ + + Parent::initPersistFields(); +} + + +void SFXEnvironment::packData( BitStream* stream ) +{ + Parent::packData( stream ); + + /* + if ( stream->writeFlag( mUseRoom ) ) + stream->writeRangedU32(mRoom, EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_COUNT); + else + { + stream->writeRangedS32( mRoomHF, -10000, 0 ); + stream->writeRangedS32( mReflections, -10000, 10000 ); + stream->writeRangedS32( mReverb, -10000, 2000 ); + + stream->writeRangedF32( mRoomRolloffFactor, 0.1f, 10.f, 8 ); + stream->writeRangedF32( mDecayTime, 0.1f, 20.f, 8 ); + stream->writeRangedF32( mDecayHFRatio, 0.1f, 20.f, 8 ); + stream->writeRangedF32( mReflectionsDelay, 0.f, 0.3f, 9 ); + stream->writeRangedF32( mReverbDelay, 0.f, 0.1f, 7 ); + stream->writeRangedS32( mRoomVolume, -10000, 0 ); + stream->writeRangedF32( mEffectVolume, 0.f, 1.f, 8 ); + stream->writeRangedF32( mDamping, 0.f, 2.f, 9 ); + stream->writeRangedF32( mEnvironmentSize, 1.f, 100.f, 10 ); + stream->writeRangedF32( mEnvironmentDiffusion, 0.f, 1.f, 8 ); + stream->writeRangedF32( mAirAbsorption, -100.f, 0.f, 10 ); + + stream->writeInt( mFlags, 6 ); + } + */ +} + + +void SFXEnvironment::unpackData( BitStream* stream ) +{ + Parent::unpackData( stream ); + + /* + mUseRoom = stream->readFlag(); + if(mUseRoom) + mRoom = stream->readRangedU32(EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_COUNT); + else + { + mRoomHF = stream->readRangedS32( -10000, 0 ); + mReflections = stream->readRangedS32( -10000, 10000 ); + mReverb = stream->readRangedS32( -10000, 2000 ); + + mRoomRolloffFactor = stream->readRangedF32( 0.1f, 10.f, 8 ); + mDecayTime = stream->readRangedF32( 0.1f, 20.f, 8 ); + mDecayHFRatio = stream->readRangedF32( 0.1f, 20.f, 8 ); + mReflectionsDelay = stream->readRangedF32( 0.f, 0.3f, 9 ); + mReverbDelay = stream->readRangedF32( 0.f, 0.1f, 7 ); + mRoomVolume = stream->readRangedS32( -10000, 0 ); + mEffectVolume = stream->readRangedF32( 0.f, 1.f, 8 ); + mDamping = stream->readRangedF32( 0.f, 2.f, 9 ); + mEnvironmentSize = stream->readRangedF32( 1.f, 100.f, 10 ); + mEnvironmentDiffusion = stream->readRangedF32( 0.f, 1.f, 8 ); + mAirAbsorption = stream->readRangedF32( -100.f, 0.f, 10 ); + + mFlags = stream->readInt( 6 ); + } + */ +} + + + + + diff --git a/sfx/sfxEnvironment.h b/sfx/sfxEnvironment.h new file mode 100644 index 0000000..1b00a91 --- /dev/null +++ b/sfx/sfxEnvironment.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXENVIRONMENT_H_ +#define _SFXENVIRONMENT_H_ + +#ifndef _CONSOLETYPES_H_ + #include "console/consoleTypes.h" +#endif +#ifndef _SIMDATABLOCK_H_ + #include "console/simDataBlock.h" +#endif + + +/// Warning: This class is non-functional and is only here +/// to allow InteriorInstance to compile. +/// +/// Eventualy this functionaliy will resurface in a +/// way that will play well with multiple providers +/// which may have unique features. +/// +class SFXEnvironment : public SimDataBlock +{ + private: + + typedef SimDataBlock Parent; + + public: + + /* + //bool mUseRoom; + S32 mRoom; + S32 mRoomHF; + S32 mReflections; + S32 mReverb; + F32 mRoomRolloffFactor; + F32 mDecayTime; + F32 mDecayHFRatio; + F32 mReflectionsDelay; + F32 mReverbDelay; + S32 mRoomVolume; + F32 mEffectVolume; + F32 mDamping; + F32 mEnvironmentSize; + F32 mEnvironmentDiffusion; + F32 mAirAbsorption; + S32 mFlags; + */ + + SFXEnvironment(); + DECLARE_CONOBJECT( SFXEnvironment ); + static void initPersistFields(); + + virtual void packData( BitStream* stream ); + virtual void unpackData( BitStream* stream ); + +}; + +DECLARE_CONSOLETYPE( SFXEnvironment ) + + +#endif // _SFXENVIRONMENT_H_ \ No newline at end of file diff --git a/sfx/sfxFileStream.cpp b/sfx/sfxFileStream.cpp new file mode 100644 index 0000000..2b5b67c --- /dev/null +++ b/sfx/sfxFileStream.cpp @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxFileStream.h" +#include "core/stream/fileStream.h" +#include "console/console.h" +#include "core/util/safeDelete.h" + + +SFXFileStream::ExtensionsVector SFXFileStream::smExtensions( __FILE__, __LINE__ ); +SFXFileStream::CreateFnsVector SFXFileStream::smCreateFns( __FILE__, __LINE__ ); + + +void SFXFileStream::registerExtension( String ext, SFXFILESTREAM_CREATE_FN create_fn ) +{ + // Register the stream creation first. + smExtensions.push_back( ext ); + smCreateFns.push_back( create_fn ); +} + +void SFXFileStream::unregisterExtension( String ext ) +{ + for( ExtensionsVector::iterator iter = smExtensions.begin(); + iter != smExtensions.end(); ++ iter ) + if( ( *iter ).equal( ext, String::NoCase ) ) + { + smExtensions.erase( iter ); + return; + } +} + +SFXFileStream* SFXFileStream::create( String filename ) +{ + //RDTODO: if original file has an extension, we should try that first + + // First strip off our current extension (validating + // against a list of known extensions so that we don't + // strip off the last part of a file name with a dot in it. + + String noExtension = Platform::stripExtension( filename, smExtensions ); + + SFXFileStream *sfxStream = NULL; + + for( U32 i = 0; i < smExtensions.size(); i++ ) + { + String testName = noExtension + smExtensions[ i ]; + + Stream *stream = FileStream::createAndOpen( testName, Torque::FS::File::Read ); + if ( !stream ) + continue; + + // Note that the creation function swallows up the + // resource stream and will take care of deleting it. + sfxStream = smCreateFns[i]( stream ); + if ( sfxStream ) + return sfxStream; + } + + return NULL; +} + +bool SFXFileStream::exists( String filename ) +{ + // First strip off our current extension (validating + // against a list of known extensions so that we don't + // strip off the last part of a file name with a dot in it. + + String noExtension = Platform::stripExtension( filename, smExtensions ); + + for( U32 i = 0; i < smExtensions.size(); i++ ) + { + String testName = noExtension + smExtensions[ i ]; + if( Torque::FS::IsFile( testName ) ) + return true; + } + + return false; +} + +SFXFileStream::SFXFileStream() + : mStream( NULL ), + mOwnStream( false ), + mFormat( 0, 0, 0 ), + mSamples( 0 ) +{ +} + +SFXFileStream::SFXFileStream( const SFXFileStream& cloneFrom ) +{ + mStream = cloneFrom.mStream->clone(); + if( !mStream ) + { + Con::errorf( "SFXFileStream::SFXFileStream() - Failed to clone source stream" ); + return; + } + + mOwnStream = true; + mFormat = cloneFrom.mFormat; + mSamples = cloneFrom.mSamples; +} + + +SFXFileStream::~SFXFileStream() +{ + // If the stream is still open, close it now. _close() + // should usually be called by the destructor of derived classes, + // but it their constructor fails, these won't even run. + + if( mStream && mOwnStream ) + SAFE_DELETE( mStream ); +} + +bool SFXFileStream::open( Stream *stream, bool ownStream ) +{ + AssertFatal( stream, "SFXFileStream::open() - Got null stream!" ); + + close(); + + mStream = stream; + mOwnStream = ownStream; + + if( _readHeader() ) + { + reset(); // Make sure we're set to read sample data. + return true; + } + else + return false; +} + +void SFXFileStream::close() +{ + if ( !mStream ) + return; + + // Let the overloaded class cleanup. + _close(); + + // We only close it if we own it. + if ( mOwnStream ) + SAFE_DELETE( mStream ); + + // Reset these to make it easier to detect bugs. + mFormat.set( 0, 0, 0 ); + mSamples = 0; +} + +bool SFXFileStream::isEOS() const +{ + if ( !mStream ) + return true; + + return mStream->getStatus() != Stream::Ok; +} diff --git a/sfx/sfxFileStream.h b/sfx/sfxFileStream.h new file mode 100644 index 0000000..4fe294e --- /dev/null +++ b/sfx/sfxFileStream.h @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXFILESTREAM_H_ +#define _SFXFILESTREAM_H_ + +#ifndef _SFXSTREAM_H_ +# include "sfx/sfxStream.h" +#endif +#ifndef _TVECTOR_H_ +# include "core/util/tVector.h" +#endif +#ifndef _TORQUE_STRING_H_ +# include "core/util/str.h" +#endif + + +class Stream; +class SFXFileStream; + +/// +typedef SFXFileStream* ( *SFXFILESTREAM_CREATE_FN )( Stream *stream ); + +/// An SFXStream that streams from a file. +class SFXFileStream : public SFXStream +{ + protected: + typedef Vector< String > ExtensionsVector; + typedef Vector< SFXFILESTREAM_CREATE_FN > CreateFnsVector; + + static ExtensionsVector smExtensions; + static CreateFnsVector smCreateFns; + + /// The file stream we're reading from. + Stream *mStream; + + /// If true then we're responsible for closing the stream. + bool mOwnStream; + + /// The format of the data in the stream. + SFXFormat mFormat; + + /// The number of samples in the data stream. + U32 mSamples; + + /// Constructs the stream in an uninitilized state. + SFXFileStream(); + + /// + SFXFileStream( const SFXFileStream& cloneFrom ); + + /// Overloaded in the derived classes to read + /// the file header. It should initialize + /// mFormat and mSamples. + virtual bool _readHeader() = 0; + + /// Overloaded for cleanup of file format + /// specific structures. + virtual void _close() = 0; + + public: + + /// + static void registerExtension( String ext, SFXFILESTREAM_CREATE_FN create_fn ); + + /// + static void unregisterExtension( String ext ); + + /// This is a helper function used to create an appropriate SFXStream + /// for the requested sound file. + /// + /// @param filename The sound file path with or without extension. + /// + static SFXFileStream* create( String filename ); + + /// + static bool exists( String filename ); + + /// Destructor. + virtual ~SFXFileStream(); + + /// Opens and optionally takes ownership of the stream. + bool open( Stream *stream, bool ownStream = false ); + + /// Closes the stream. + void close(); + + // SFXStream. + const SFXFormat& getFormat() const { return mFormat; } + U32 getSampleCount() const { return mSamples; } + U32 getDataLength() const { return mSamples * mFormat.getBytesPerSample(); } + U32 getDuration() const { return mFormat.getDuration( mSamples ); } + bool isEOS() const; +}; + +#endif // _SFXFILESTREAM_H_ diff --git a/sfx/sfxInternal.cpp b/sfx/sfxInternal.cpp new file mode 100644 index 0000000..8fefbf2 --- /dev/null +++ b/sfx/sfxInternal.cpp @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxInternal.h" +#include "sfx/sfxDescription.h" +#include "core/util/safeDelete.h" +#include "platform/threads/threadPoolAsyncIO.h" + + +/// @file +/// Implementation of async sound I/O. + + +//#define DEBUG_SPEW + + +namespace SFXInternal { + + +ThreadPool gStreamThreadPool( "SFX", 2 ); +ThreadSafeRef< SFXUpdateThread > gUpdateThread; +ThreadSafeRef< SFXBufferProcessList > gBufferUpdateList = new SFXBufferProcessList; +ThreadSafeDeque< SFXBuffer* > gDeadBufferList; + + +//========================================================================== +// SFXAsyncStream implementation. +//========================================================================== + +//-------------------------------------------------------------------------- + +SFXAsyncStream::SFXAsyncStream( const SFXStreamRef& stream, + bool isIncremental, + U32 streamPacketLength, + U32 numReadAhead, + bool isLooping ) + : Parent( stream, + isIncremental + ? streamPacketLength + * stream->getFormat().getSamplesPerSecond() + * stream->getFormat().getBytesPerSample() // Streamed buffer; read in incremental packets. + : stream->getDataLength(), // Non-streamed buffer; read entire stream in one packet. + stream->getDataLength() // Read all remaining data in stream. + - ( dynamic_cast< IPositionable< U32 >* >( stream.ptr() ) + ? dynamic_cast< IPositionable< U32 >* >( stream.ptr() )->getPosition() + : 0 ), + numReadAhead, + isLooping, + &gStreamThreadPool ), + mReadSilenceAtEnd( false ) +{ +} + +//-------------------------------------------------------------------------- + +void SFXAsyncStream::_onArrival( SFXStreamPacket* const& packet ) +{ + Parent::_onArrival( packet ); + + if( !mIsStopped ) + TriggerUpdate(); +} + +//-------------------------------------------------------------------------- + +void SFXAsyncStream::_requestNext() +{ + if( !mNumRemainingSourceElements && mReadSilenceAtEnd ) + { + // Push an artificial packet of silence. + + SFXStreamPacket* packet = _newPacket( mPacketSize ); + packet->mIndex = mNextPacketIndex; + mNextPacketIndex ++; + mReadSilenceAtEnd = false; + dMemset( packet->data, 0, packet->size ); + packet->mIsLast = true; + + _onArrival( packet ); + } + else + Parent::_requestNext(); +} + +//========================================================================== +// SFXWrapAroundBuffer implementation. +//========================================================================== + +//-------------------------------------------------------------------------- + +SFXWrapAroundBuffer::SFXWrapAroundBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) + : Parent( stream, description ), + mWriteOffset( 0 ) +{ + // Determine the device buffer metrics. + + const U32 maxQueuedPackets = isStreaming() ? SFXAsyncQueue::DEFAULT_STREAM_QUEUE_LENGTH : 1; + const U32 packetSize = mAsyncState->mStream->getPacketSize(); + + mBufferSize = maxQueuedPackets * packetSize; + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXWrapAroundBuffer] size=%i, packets=%i", + mBufferSize, maxQueuedPackets ); + #endif + + // For streaming buffers that are not looping, add a packet of silence to the + // source stream. + + if( isStreaming() && !description->mIsLooping ) + mAsyncState->mStream->setReadSilenceAtEnd( true ); +} + +//-------------------------------------------------------------------------- + +void SFXWrapAroundBuffer::write( SFXStreamPacket* const* packets, U32 num ) +{ + AssertFatal( SFXInternal::isSFXThread(), "SFXWrapAroundBuffer::write() - not on SFX thread" ); + + for( U32 i = 0; i < num; ++ i ) + { + const SFXStreamPacket* packet = packets[ i ]; + + // Determine where in the buffer to copy the data to. In case we are crossing over + // the wrap-around point, we need to copy in two slices. + + U32 offset1 = 0; + U32 offset2 = 0; + U32 numBytes1 = 0; + U32 numBytes2 = 0; + + offset1 = mWriteOffset % mBufferSize; + numBytes1 = packet->size; + + if( offset1 + numBytes1 > mBufferSize ) + { + // Crossing wrap-around point. + + numBytes1 = mBufferSize - offset1; + numBytes2 = packet->size - numBytes1; + } + + offset2 = offset1 + numBytes1; + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXWrapAroundBuffer] writing %i bytes from packet #%i at %i (stream offset: %i)", + numBytes1, packet->mIndex, offset1, mWriteOffset ); + #endif + + // Copy the packet data. + + _copyData( offset1, packet->data, numBytes1 ); + if( numBytes2 > 0 ) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXWrapAroundBuffer] writing %i more bytes at %i", + numBytes2, offset2 ); + #endif + + _copyData( offset2, &packet->data[ numBytes1 ], numBytes2 ); + } + + dFetchAndAdd( mWriteOffset, packet->size ); + + // Free the packet. + + destructSingle( packet ); + } +} + +} // namespace SFXInternal diff --git a/sfx/sfxInternal.h b/sfx/sfxInternal.h new file mode 100644 index 0000000..07109f1 --- /dev/null +++ b/sfx/sfxInternal.h @@ -0,0 +1,407 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXINTERNAL_H_ +#define _SFXINTERNAL_H_ + +#define TORQUE_ENABLE_SAMPLING + +#ifndef _THREADPOOL_H_ + #include "platform/threads/threadPool.h" +#endif +#ifndef _ASYNCUPDATE_H_ + #include "platform/async/asyncUpdate.h" +#endif +#ifndef _ASYNCPACKETSTREAM_H_ + #include "platform/async/asyncPacketStream.h" +#endif +#ifndef _ASYNCPACKETQUEUE_H_ + #include "platform/async/asyncPacketQueue.h" +#endif +#ifndef _SFXSTREAM_H_ + #include "sfx/sfxStream.h" +#endif +#ifndef _SFXBUFFER_H_ + #include "sfx/sfxBuffer.h" +#endif +#ifndef _SFXVOICE_H_ + #include "sfx/sfxVoice.h" +#endif +#ifndef _CONSOLE_H_ + #include "console/console.h" +#endif + +#include "util/sampler.h" + + +/// @file +/// Mostly internal definitions for sound stream handling. +/// The code here is used by SFXBuffer for asynchronously loading +/// sample data from sound files, both for streaming buffers +/// as well as for "normal" buffers. + + +namespace SFXInternal { + +typedef AsyncUpdateThread SFXUpdateThread; +typedef AsyncUpdateList SFXBufferProcessList; + +//-------------------------------------------------------------------------- +// Async sound packets. +//-------------------------------------------------------------------------- + +/// Sound stream packets are raw byte buffers containing PCM sample data. +class SFXStreamPacket : public AsyncPacket< U8 > +{ + public: + + typedef AsyncPacket< U8 > Parent; + + SFXStreamPacket() {} + SFXStreamPacket( U8* data, U32 size, bool ownMemory = false ) + : Parent( data, size, ownMemory ) {} + + /// The format of the sound samples in the packet. + SFXFormat mFormat; + + /// @return the number of samples contained in the packet. + U32 getSampleCount() const { return ( mSizeActual / mFormat.getBytesPerSample() ); } +}; + +//-------------------------------------------------------------------------- +// Async SFXStream I/O. +//-------------------------------------------------------------------------- + +/// Asynchronous sound data stream that delivers sound data +/// in discrete packets. +class SFXAsyncStream : public AsyncPacketBufferedInputStream< SFXStreamRef, SFXStreamPacket > +{ + public: + + typedef AsyncPacketBufferedInputStream< SFXStreamRef, SFXStreamPacket > Parent; + + enum + { + /// The number of seconds of sample data to load per streaming packet by default. + /// Set this reasonably high to ensure the system is able to cope with latencies + /// in the buffer update chain. + DEFAULT_STREAM_PACKET_LENGTH = 8 + }; + + protected: + + /// If true, the stream reads one packet of silence beyond the + /// sound streams actual sound data. This is to avoid wrap-around + /// playback queues running into old data when there is a delay + /// in playback being stopped. + /// + /// @note The silence packet is not counting towards stream + /// playback time. + bool mReadSilenceAtEnd; + + // AsyncPacketStream. + virtual SFXStreamPacket* _newPacket( U32 packetSize ) + { + SFXStreamPacket* packet = Parent::_newPacket( packetSize ); + packet->mFormat = getSourceStream()->getFormat(); + return packet; + } + virtual void _requestNext(); + virtual void _onArrival( SFXStreamPacket* const& packet ); + virtual void _newReadItem( PacketReadItemRef& outRef, SFXStreamPacket* packet, U32 numElements ) + { + if( !this->mNumRemainingSourceElements && mReadSilenceAtEnd ) + packet->mIsLast = false; + Parent::_newReadItem( outRef, packet, numElements ); + } + + public: + + /// Construct a new async sound stream reading data from "stream". + /// + /// @param stream The sound data source stream. + /// @param isIncremental If true, "stream" is read in packets of "streamPacketLength" size + /// each; otherwise the stream is read in a single packet containing the entire stream. + /// @param streamPacketLength Seconds of sample data to read per streaming packet. Only + /// relevant if "isIncremental" is true. + /// @param numReadAhead Number of stream packets to read and buffer in advance. + /// @param isLooping If true, the packet stream infinitely loops over "stream". + SFXAsyncStream( const SFXStreamRef& stream, + bool isIncremental, + U32 streamPacketLength = DEFAULT_STREAM_PACKET_LENGTH, + U32 numReadAhead = DEFAULT_STREAM_LOOKAHEAD, + bool isLooping = false ); + + /// Returns true if the stream will read a packet of silence after the actual sound data. + U32 getReadSilenceAtEnd() const { return mReadSilenceAtEnd; } + + /// Set whether the stream should read one packet of silence past the + /// actual sound data. This is useful for situations where continued + /// playback may run into old data. + void setReadSilenceAtEnd( bool value ) { mReadSilenceAtEnd = value; } + + /// Return the playback time of a single sound packet in milliseconds. + /// For non-incremental streams, this will be the duration of the + /// entire stream. + U32 getPacketDuration() const + { + const SFXFormat& format = getSourceStream()->getFormat(); + return format.getDuration( mPacketSize / format.getBytesPerSample() ); + } +}; + +//-------------------------------------------------------------------------- +// Voice time source wrapper. +//-------------------------------------------------------------------------- + +/// Wrapper around SFXVoice that yields the raw underlying sample position +/// rather than the virtualized position returned by SFXVoice::getPosition(). +class SFXVoiceTimeSource +{ + public: + + typedef void Parent; + + protected: + + /// + SFXVoice* mVoice; + + public: + + SFXVoiceTimeSource( SFXVoice* voice ) + : mVoice( voice ) {} + + U32 getPosition() const + { + return mVoice->_tell(); + } +}; + +//-------------------------------------------------------------------------- +// Async sound packet queue. +//-------------------------------------------------------------------------- + +/// An async stream queue that writes sound packets to SFXBuffers in sync +/// to the playback of an SFXVoice. +/// +/// Sound packet queues use sample counts as tick counts. +class SFXAsyncQueue : public AsyncPacketQueue< SFXStreamPacket*, SFXVoiceTimeSource, SFXBuffer* > +{ + public: + + typedef AsyncPacketQueue< SFXStreamPacket*, SFXVoiceTimeSource, SFXBuffer* > Parent; + + enum + { + /// The number of stream packets that the playback queue for streaming + /// sounds will be sliced into. This should generally be left at + /// three since there is an overhead incurred for each additional + /// segment. Having three segments gives one segment for current + /// immediate playback, one segment as intermediate buffer, and one segment + /// for stream writes. + DEFAULT_STREAM_QUEUE_LENGTH = 3, + }; + + /// Construct a new sound queue that pushes sound packets to "buffer" in sync + /// to the playback of "voice". + /// + /// @param voice The SFXVoice to synchronize to. + /// @param buffer The sound buffer to push sound packets to. + SFXAsyncQueue( SFXVoice* voice, + SFXBuffer* buffer, + bool looping = false ) + : Parent( DEFAULT_STREAM_QUEUE_LENGTH, + voice, + buffer, + ( looping + ? 0 + : ( buffer->getDuration() * ( buffer->getFormat().getSamplesPerSecond() / 1000 ) ) - voice->mOffset ) ) {} +}; + +//-------------------------------------------------------------------------- +// SFXBuffer with a wrap-around buffering scheme. +//-------------------------------------------------------------------------- + +/// Buffer that uses wrap-around packet buffering. +/// +/// This class automatically coordinates retrieval and submitting of +/// sound packets and also protects against play cursors running beyond +/// the last packet by making sure some silence is submitted after the +/// last packet (does not count towards playback time). +class SFXWrapAroundBuffer : public SFXBuffer +{ + public: + + typedef SFXBuffer Parent; + + protected: + + /// Absolute byte offset into the sound stream that the next packet write + /// will occur at. This is not an offset into the device buffer + /// in order to allow us to track how far in the source stream we are. + U32 mWriteOffset; + + /// Size of the device buffer in bytes. + U32 mBufferSize; + + // SFXBuffer. + virtual void _flush() + { + mWriteOffset = 0; + } + + /// Copy "length" bytes from "data" into the device at "offset". + virtual bool _copyData( U32 offset, const U8* data, U32 length ) = 0; + + // SFXBuffer. + virtual void write( SFXStreamPacket* const* packets, U32 num ); + + /// @return the sample position in the sound stream as determined from the + /// given buffer offset. + U32 getSamplePos( U32 bufferOffset ) const + { + if( !mBufferSize ) + return bufferOffset; + + const U32 writeOffset = mWriteOffset; // Concurrent writes on this one. + const U32 writeOffsetRelative = writeOffset % mBufferSize; + + U32 numBufferedBytes; + if( !writeOffset ) + numBufferedBytes = 0; + else if( writeOffsetRelative > bufferOffset ) + numBufferedBytes = writeOffsetRelative - bufferOffset; + else + // Wrap-around. + numBufferedBytes = mBufferSize - bufferOffset + writeOffsetRelative; + + const U32 bytePos = writeOffset - numBufferedBytes; + + return ( bytePos / getFormat().getBytesPerSample() ); + } + + public: + + SFXWrapAroundBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + SFXWrapAroundBuffer( SFXDescription* description ) + : Parent( description ), mBufferSize( 0 ) {} + + virtual U32 getMemoryUsed() const { return mBufferSize; } +}; + +//-------------------------------------------------------------------------- +// Global state. +//-------------------------------------------------------------------------- + +enum +{ + /// Soft limit on milliseconds to spend on updating sound buffers + /// when doing buffer updates on the main thread. + MAIN_THREAD_PROCESS_TIMEOUT = 512, + + /// Default time interval between periodic sound updates in milliseconds. + /// Only relevant for devices that perform periodic updates. + DEFAULT_UPDATE_INTERVAL = 512, +}; + +/// Thread pool for sound I/O. +/// +/// We are using a separate pool for sound packets in order to be +/// able to submit packet items from different threads. This would +/// violate the invariant of the global thread pool that only the +/// main thread is feeding the queues. +/// +/// Note that this also means that only at certain very well-defined +/// points is it possible to safely flush the work item queue on this +/// pool. +/// +/// @note Don't use this directly but rather use THREAD_POOL() instead. +/// This way, the sound code may be easily switched to using a common +/// pool later on. +extern ThreadPool gStreamThreadPool; + +/// Dedicated thread that does sound buffer updates. +/// May be NULL if sound API used does not do asynchronous buffer +/// updates but rather uses per-frame polling. +/// +/// @note SFXDevice automatically polls if this is NULL. +extern ThreadSafeRef< AsyncUpdateThread > gUpdateThread; + +/// List of buffers that need updating. +/// +/// It depends on the actual device whether this list is processed +/// on a stream update thread or on the main thread. +extern ThreadSafeRef< SFXBufferProcessList > gBufferUpdateList; + +/// List of buffers that are pending deletion. +/// +/// This is a messy issue. Buffers with live async states cannot be instantly +/// deleted since they may still be running concurrent updates. However, they +/// also cannot be deleted on the update thread since the StrongRefBase stuff +/// isn't thread-safe (i.e weak references kept by client code would cause trouble). +/// +/// So, what we do is mark buffers for deletion, wait till they surface on the +/// process list and then ping them back to this list to have them deleted by the +/// SFXDevice itself on the main thread. A bit of overhead but only a fraction of +/// the buffers will ever undergo this procedure. +extern ThreadSafeDeque< SFXBuffer* > gDeadBufferList; + +/// Return the thread pool used for SFX work. +inline ThreadPool& THREAD_POOL() +{ + return gStreamThreadPool; +} + +/// Return the dedicated SFX update thread; NULL if updating on the main thread. +inline ThreadSafeRef< SFXUpdateThread > UPDATE_THREAD() +{ + return gUpdateThread; +} + +/// Return the processing list for SFXBuffers that need updating. +inline SFXBufferProcessList& UPDATE_LIST() +{ + return *gBufferUpdateList; +} + +/// Trigger an SFX update. +inline bool TriggerUpdate() +{ + ThreadSafeRef< SFXUpdateThread > sfxThread = UPDATE_THREAD(); + if( sfxThread != NULL ) + { + sfxThread->triggerUpdate(); + return true; + } + else + return false; +} + +/// Delete all buffers currently on the dead buffer list. +inline void PurgeDeadBuffers() +{ + SFXBuffer* buffer; + while( gDeadBufferList.tryPopFront( buffer ) ) + delete buffer; +} + +/// Return true if the current thread is the one responsible for doing SFX updates. +inline bool isSFXThread() +{ + ThreadSafeRef< SFXUpdateThread > sfxThread = UPDATE_THREAD(); + + U32 threadId; + if( sfxThread != NULL ) + threadId = sfxThread->getId(); + else + threadId = ThreadManager::getMainThreadId(); + + return ThreadManager::compare( ThreadManager::getCurrentThreadId(), threadId ); +} + +} // namespace SFXInternal + +#endif // _SFXSTREAMIO_H_ diff --git a/sfx/sfxListener.cpp b/sfx/sfxListener.cpp new file mode 100644 index 0000000..f8f22a3 --- /dev/null +++ b/sfx/sfxListener.cpp @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sfx/sfxListener.h" +#include "sfx/sfxSource.h" +#include "platform/profiler.h" + + +SFXListener::SFXListener() + : mTransform( true ), + mVelocity( 0, 0, 0 ) +{ +} + +SFXListener::~SFXListener() +{ +} + +// TODO: Maybe use the channel as a key for priority? +// Let the user define a priority value for each channel +// in script. We assign it in the system init and use +// it when doleing out hardware handles. + +S32 QSORT_CALLBACK SFXListener::sourceCompare( const void* item1, const void* item2 ) +{ + const SFXSource* source1 = *((SFXSource**)item1); + const SFXSource* source2 = *((SFXSource**)item2); + + // Sounds that are playing are always sorted + // closer than non-playing sounds. + if ( !source1->isPlaying() ) + return 1; + if ( !source2->isPlaying() ) + return -1; + + // The sources with louder attenuated + // volume are higher priority. + const F32 volume1 = source1->getAttenuatedVolume(); + const F32 volume2 = source2->getAttenuatedVolume(); + if ( volume1 < volume2 ) + return 1; + if ( volume1 > volume2 ) + return -1; + + // If we got this far then the source that was + // played last has the higher priority. + if ( source1->mPlayStartTick > source2->mPlayStartTick ) + return -1; + if ( source1->mPlayStartTick < source2->mPlayStartTick ) + return 1; + + // These are sorted the same! + return 0; +} + +void SFXListener::sortSources( Vector< SFXSource* >& sources ) +{ + PROFILE_SCOPE( SFXListener_SortSources ); + + // First have the sources update the attenuated + // volume for each source. + Vector< SFXSource* >::iterator iter = sources.begin(); + for ( ; iter != sources.end(); iter++ ) + (*iter)->_updateVolume( mTransform ); + + // Now sort the source vector by the attenuated + // volume and channel priorities. This leaves us + // with the loudest and highest priority sounds + // at the front of the vector. + dQsort( (void *)sources.address(), sources.size(), sizeof(SFXSource*), sourceCompare ); +} diff --git a/sfx/sfxListener.h b/sfx/sfxListener.h new file mode 100644 index 0000000..29b7e18 --- /dev/null +++ b/sfx/sfxListener.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXLISTENER_H_ +#define _SFXLISTENER_H_ + +#ifndef _MPOINT3_H_ + #include "math/mPoint3.h" +#endif +#ifndef _MMATH_H_ + #include "math/mMatrix.h" +#endif +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif + + +class SFXSource; + + + +/// +class SFXListener +{ + protected: + + /// The current position and rotation. + MatrixF mTransform; + + /// The velocity. + VectorF mVelocity; + + /// Used to sort sources by attenuated volume and channel priority. + static S32 QSORT_CALLBACK sourceCompare( const void* item1, const void* item2 ); + + public: + + /// The constructor. + SFXListener(); + + /// The non-virtual destructor... because you + /// shouldn't need to overload this class. + ~SFXListener(); + + /// + void setTransform( const MatrixF& transform ) { mTransform = transform; } + + /// + const MatrixF& getTransform() const { return mTransform; } + + /// + void setVelocity( const VectorF& velocity ) { mVelocity = velocity; } + + /// + const VectorF& getVelocity() const { return mVelocity; } + + /// + void sortSources( Vector< SFXSource* >& sources ); +}; + +#endif // _SFXLISTENER_H_ diff --git a/sfx/sfxPacketStream.cpp b/sfx/sfxPacketStream.cpp new file mode 100644 index 0000000..4c27f41 --- /dev/null +++ b/sfx/sfxPacketStream.cpp @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxPacketStream.h" +#include "platform/typetraits.h" +#include "console/console.h" + + +SFXPacketStream::SFXPacketStream( const SFXFormat& format, + SourceStreamType* stream, + U32 numSamples ) + : IInputStreamFilter< U8, SourceStreamType* >( stream ), + mFormat( format ), + mNumSamplesTotal( numSamples ), + mNumSamplesLeft( numSamples ), + mCurrentPacket( NULL ), + mCurrentPacketOffset( 0 ) +{ +} + +void SFXPacketStream::reset() +{ + if( dynamic_cast< IResettable* >( getSourceStream() ) ) + { + reinterpret_cast< IResettable* >( getSourceStream() )->reset(); + + if( mCurrentPacket ) + destructSingle( mCurrentPacket ); + + mCurrentPacket = NULL; + mCurrentPacketOffset = 0; + mNumSamplesLeft = mNumSamplesTotal; + } + else + Con::errorf( "SFXPacketStream - cannot reset source stream" ); +} + +U32 SFXPacketStream::read( U8* buffer, U32 length ) +{ + U32 bufferOffset = 0; + + // Determine how much we're supposed to read. + + U32 numBytesToCopy = length; + if( mNumSamplesLeft != U32_MAX ) + numBytesToCopy = getMin( length, mNumSamplesLeft * mFormat.getBytesPerSample() ); + numBytesToCopy -= numBytesToCopy % mFormat.getBytesPerSample(); + + // Copy the data. + + U32 numBytesLeftToCopy = numBytesToCopy; + while( numBytesLeftToCopy ) + { + // If we have a current packet, use its data. + + if( mCurrentPacket ) + { + U32 numBytesLeftInCurrentPacket = mCurrentPacket->size - mCurrentPacketOffset; + + // Copy data. + + if( numBytesLeftInCurrentPacket ) + { + const U32 numBytesToCopy = getMin( numBytesLeftInCurrentPacket, numBytesLeftToCopy ); + dMemcpy( &buffer[ bufferOffset ], &mCurrentPacket->data[ mCurrentPacketOffset ], numBytesToCopy ); + + bufferOffset += numBytesToCopy; + mCurrentPacketOffset += numBytesToCopy; + numBytesLeftInCurrentPacket -= numBytesToCopy; + numBytesLeftToCopy -= numBytesToCopy; + } + + // Discard the packet if there's no data left. + + if( !numBytesLeftInCurrentPacket ) + { + destructSingle( mCurrentPacket ); + mCurrentPacket = NULL; + mCurrentPacketOffset = 0; + } + } + else + { + // Read a new packet. + + if( !getSourceStream()->read( &mCurrentPacket, 1 ) ) + break; + } + } + + // Update count of remaining samples. + + U32 numBytesCopied = numBytesToCopy - numBytesLeftToCopy; + if( mNumSamplesLeft != U32_MAX ) + mNumSamplesLeft -= ( numBytesCopied / mFormat.getBytesPerSample() ); + + return numBytesCopied; +} diff --git a/sfx/sfxPacketStream.h b/sfx/sfxPacketStream.h new file mode 100644 index 0000000..428e00d --- /dev/null +++ b/sfx/sfxPacketStream.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXPACKETSTREAM_H_ +#define _SFXPACKETSTREAM_H_ + +#ifndef _SFXSTREAM_H_ + #include "sfx/sfxStream.h" +#endif +#ifndef _TSTREAM_H_ + #include "core/stream/tStream.h" +#endif +#ifndef _RAWDATA_H_ + #include "core/util/rawData.h" +#endif + + +/// A stream filter that converts sample packets from its source stream +/// to a continuous sample stream. Useful for feeding sound from a source +/// that pushes sample data in discrete packets. +/// +/// @note For the SFXPacketStream to allow a reset(), the source input +/// stream must implement IResettable. +class SFXPacketStream : public SFXStream, + public IInputStreamFilter< U8, IInputStream< RawData* >* > +{ + public: + + typedef SFXStream Parent; + + protected: + + /// + SFXFormat mFormat; + + /// Total number of samples in the stream. If this is U32_MAX, the stream + /// is considered to be of indefinite size. + U32 mNumSamplesTotal; + + /// Number of samples left to be read from stream. Locked to U32_MAX for + /// stream of indefinite size. + U32 mNumSamplesLeft; + + /// The current sample data packet. + RawData* mCurrentPacket; + + /// Read offset in the current sample data packet. + U32 mCurrentPacketOffset; + + public: + + /// + SFXPacketStream( const SFXFormat& format, SourceStreamType* stream, U32 numSamples = U32_MAX ); + + // SFXStream. + const SFXFormat& getFormat() const { return mFormat; } + U32 getSampleCount() const { return mNumSamplesTotal; } + U32 getDataLength() const { return ( mNumSamplesTotal == U32_MAX ? U32_MAX : mFormat.getDataLength( getDuration() ) ); } + U32 getDuration() const { return ( mNumSamplesTotal == U32_MAX ? U32_MAX : mFormat.getDuration( mNumSamplesTotal ) ); } + bool isEOS() const { return ( mNumSamplesLeft != 0 ); } + void reset(); + U32 read( U8 *buffer, U32 length ); +}; + +#endif // !_SFXPACKETSTREAM_H_ diff --git a/sfx/sfxProfile.cpp b/sfx/sfxProfile.cpp new file mode 100644 index 0000000..b003d76 --- /dev/null +++ b/sfx/sfxProfile.cpp @@ -0,0 +1,350 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "sfx/sfxProfile.h" +#include "sfx/sfxDescription.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxStream.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "core/resourceManager.h" + + +IMPLEMENT_CO_DATABLOCK_V1( SFXProfile ); + +IMPLEMENT_CONSOLETYPE( SFXProfile ) +IMPLEMENT_GETDATATYPE( SFXProfile ) +IMPLEMENT_SETDATATYPE( SFXProfile ) + + +//----------------------------------------------------------------------------- + +SFXProfile::SFXProfile() + : mDescription( NULL ), + mPreload( false ), + mDescriptionId( 0 ) +{ +} + +//----------------------------------------------------------------------------- + +SFXProfile::SFXProfile( SFXDescription* desc, const String& filename, bool preload ) + : mFilename( filename ), + mDescription( desc ), + mPreload( preload ), + mDescriptionId( 0 ) +{ +} + +//----------------------------------------------------------------------------- + +SFXProfile::~SFXProfile() +{ +} + +//----------------------------------------------------------------------------- + +void SFXProfile::initPersistFields() +{ + addField( "filename", TypeStringFilename, Offset(mFilename, SFXProfile)); + addField( "description", TypeSFXDescriptionPtr, Offset(mDescription, SFXProfile)); + addField( "preload", TypeBool, Offset(mPreload, SFXProfile)); + + Parent::initPersistFields(); +} + +//----------------------------------------------------------------------------- + +bool SFXProfile::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + // Look up our SFXDescription. + + if( mDescription == NULL && + mDescriptionId != 0 ) + { + if ( !Sim::findObject( mDescriptionId, mDescription ) ) + { + Con::errorf( + "SFXProfile(%s)::onAdd: Invalid packet, bad description id: %d", + getName(), mDescriptionId ); + return false; + } + } + + // If we have no SFXDescription, try to grab a default. + + if( !mDescription ) + { + if( !Sim::findObject( "AudioSim", mDescription ) ) + { + Con::errorf( + "SFXProfile(%s)::onAdd: The profile is missing a description!", + getName() ); + return false; + } + } + + // If we're a streaming profile we don't preload + // or need device events. + if ( SFX && !mDescription->mIsStreaming ) + { + // If preload is enabled we load the resource + // and device buffer now to avoid a delay on + // first playback. + if ( mPreload && !_preloadBuffer() ) + Con::errorf( "SFXProfile(%s)::onAdd: The preload failed!", getName() ); + } + + _registerSignals(); + + return true; +} + +//----------------------------------------------------------------------------- + +void SFXProfile::onRemove() +{ + _unregisterSignals(); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- + +bool SFXProfile::preload( bool server, String &errorStr ) +{ + if ( !Parent::preload( server, errorStr ) ) + return false; + + // TODO: Investigate how NetConnection::filesWereDownloaded() + // effects the system. + + // Validate the datablock... has nothing to do with mPreload. + if ( !server && + NetConnection::filesWereDownloaded() && + ( mFilename.isEmpty() || !SFXResource::exists( mFilename ) ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- + +void SFXProfile::packData(BitStream* stream) +{ + Parent::packData( stream ); + + // audio description: + if ( stream->writeFlag( mDescription ) ) + { + stream->writeRangedU32( mDescription->getId(), + DataBlockObjectIdFirst, + DataBlockObjectIdLast ); + } + + // + char buffer[256]; + if ( mFilename.isEmpty() ) + buffer[0] = 0; + else + dStrncpy( buffer, mFilename.c_str(), 256 ); + stream->writeString( buffer ); + + stream->writeFlag( mPreload ); +} + +//----------------------------------------------------------------------------- + +void SFXProfile::unpackData(BitStream* stream) +{ + Parent::unpackData( stream ); + + // audio datablock: + if ( stream->readFlag() ) + mDescriptionId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); + + char buffer[256]; + stream->readString( buffer ); + mFilename = buffer; + + mPreload = stream->readFlag(); +} + +//----------------------------------------------------------------------------- + +void SFXProfile::_registerSignals() +{ + SFX->getEventSignal().notify( this, &SFXProfile::_onDeviceEvent ); + ResourceManager::get().getChangedSignal().notify( this, &SFXProfile::_onResourceChanged ); +} + +//----------------------------------------------------------------------------- + +void SFXProfile::_unregisterSignals() +{ + ResourceManager::get().getChangedSignal().remove( this, &SFXProfile::_onResourceChanged ); + if( SFX ) + SFX->getEventSignal().remove( this, &SFXProfile::_onDeviceEvent ); +} + +//----------------------------------------------------------------------------- + +void SFXProfile::_onDeviceEvent( SFXSystemEventType evt ) +{ + switch( evt ) + { + case SFXSystemEvent_CreateDevice: + { + if( mPreload && !mDescription->mIsStreaming && !_preloadBuffer() ) + Con::errorf( "SFXProfile::_onDeviceEvent: The preload failed! %s", getName() ); + break; + } + + default: + break; + } +} + +//----------------------------------------------------------------------------- + +void SFXProfile::_onResourceChanged( ResourceBase::Signature sig, const Torque::Path& path ) +{ + if( sig == Resource< SFXResource >::signature() + && path == Path( mFilename ) ) + { + // Let go of the old resource and buffer. + + mResource = NULL; + mBuffer = NULL; + + // Load the new resource. + + getResource(); + + if( mPreload && !mDescription->mIsStreaming ) + { + if( !_preloadBuffer() ) + Con::errorf( "SFXProfile::_onResourceChanged() - failed to preload '%s'", mFilename.c_str() ); + } + + mChangedSignal.trigger( this ); + } +} + +//----------------------------------------------------------------------------- + +bool SFXProfile::_preloadBuffer() +{ + AssertFatal( !mDescription->mIsStreaming, "SFXProfile::_preloadBuffer() - must not be called for streaming profiles" ); + + mBuffer = _createBuffer(); + return ( !mBuffer.isNull() ); +} + +//----------------------------------------------------------------------------- + +Resource& SFXProfile::getResource() +{ + if( !mResource && !mFilename.isEmpty() ) + mResource = SFXResource::load( mFilename ); + + return mResource; +} + +//----------------------------------------------------------------------------- + +SFXBuffer* SFXProfile::getBuffer() +{ + if ( mDescription->mIsStreaming ) + { + // Streaming requires unique buffers per + // source, so this creates a new buffer. + if ( SFX ) + return _createBuffer(); + + return NULL; + } + + if ( mBuffer.isNull() ) + _preloadBuffer(); + + return mBuffer; +} + +//----------------------------------------------------------------------------- + +SFXBuffer* SFXProfile::_createBuffer() +{ + SFXBuffer* buffer = 0; + + // Try to create through SFXDevie. + + if( !mFilename.isEmpty() && SFX ) + { + buffer = SFX->_createBuffer( mFilename, mDescription ); + if( buffer ) + { + #ifdef TORQUE_DEBUG + const SFXFormat& format = buffer->getFormat(); + Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)", + mDescription->mIsStreaming ? "Streaming" : "Loaded", mFilename.c_str(), + format.getChannels(), + format.getSamplesPerSecond() / 1000, + F32( buffer->getDuration() ) / 1000.0f, + format.getDataLength( buffer->getDuration() ) / 1024 ); + #endif + } + } + + // If that failed, load through SFXResource. + + if( !buffer ) + { + Resource< SFXResource >& resource = getResource(); + if( resource != NULL && SFX ) + { + #ifdef TORQUE_DEBUG + const SFXFormat& format = resource->getFormat(); + Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)", + mDescription->mIsStreaming ? "Streaming" : "Loading", resource->getFileName().c_str(), + format.getChannels(), + format.getSamplesPerSecond() / 1000, + F32( resource->getDuration() ) / 1000.0f, + format.getDataLength( resource->getDuration() ) / 1024 ); + #endif + + ThreadSafeRef< SFXStream > sfxStream = resource->openStream(); + buffer = SFX->_createBuffer( sfxStream, mDescription ); + } + } + + return buffer; +} + +//----------------------------------------------------------------------------- + +U32 SFXProfile::getSoundDuration() +{ + Resource< SFXResource >& resource = getResource(); + if( resource != NULL ) + return mResource->getDuration(); + else + return 0; +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(SFXProfile, getSoundDuration, F32, 2, 2, + "()\n" + "@return Returns the length of the sound in seconds." ) +{ + return (F32)object->getSoundDuration() * 0.001f; +} + diff --git a/sfx/sfxProfile.h b/sfx/sfxProfile.h new file mode 100644 index 0000000..21eb882 --- /dev/null +++ b/sfx/sfxProfile.h @@ -0,0 +1,179 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXPROFILE_H_ +#define _SFXPROFILE_H_ + +#ifndef _CONSOLETYPES_H_ + #include "console/consoleTypes.h" +#endif +#ifndef _SIMDATABLOCK_H_ + #include "console/simDataBlock.h" +#endif +#ifndef _SFXDESCRIPTION_H_ + #include "sfx/sfxDescription.h" +#endif +#ifndef _SFXDEVICE_H_ + #include "sfx/sfxDevice.h" +#endif +#ifndef _SFXRESOURCE_H_ + #include "sfx/sfxResource.h" +#endif +#ifndef _SFXBUFFER_H_ + #include "sfx/sfxBuffer.h" +#endif +#ifndef _SFXSYSTEM_H_ + #include "sfx/sfxSystem.h" +#endif +#ifndef _TSIGNAL_H_ + #include "core/util/tSignal.h" +#endif + + +class SFXDescription; + + +/// The SFXProfile is used to define a sound for playback. +/// +/// An SFXProfile will first try to load its file directly through the SFXDevice. +/// Only if this fails (which is the case for most SFXDevices as these do not +/// implement their own custom sound format loading), the file is loaded through +/// SFXResource. +/// +/// A few tips: +/// +/// Make sure each of the defined SFXProfile's fileName doesn't specify +/// an extension. An extension does not need to be specified and by not +/// explicitly saying .ogg or .wav it will allow you to change from one +/// format to the other without having to change the scripts. +/// +/// Make sure that server SFXProfiles are defined with the datablock +/// keyword, and that client SFXProfiles are defined with the 'new' +/// keyword. +/// +/// Make sure SFXDescriptions exist for your SFXProfiles. Also make sure +/// that SFXDescriptions are defined BEFORE SFXProfiles. This is especially +/// important if your SFXProfiles are located in different files than your +/// SFXDescriptions. In this case, make sure the files containing SFXDescriptions +/// are exec'd before the files containing the SFXProfiles. +/// +class SFXProfile : public SimDataBlock +{ + public: + + friend class SFXEmitter; // For access to mFilename + + typedef SimDataBlock Parent; + + typedef Signal< void( SFXProfile* ) > ChangedSignal; + + protected: + + /// Used on the client side during onAdd. + S32 mDescriptionId; + + /// The sound data. + /// @note ATM only valid if loaded through SFX's loading system rather than + /// through the SFXDevice's loading system. + Resource< SFXResource > mResource; + + /// The description which controls playback settings. + SFXDescription *mDescription; + + /// The sound filename. If no extension is specified + /// the system will try .wav first then other formats. + String mFilename; + + /// If true the sound data will be loaded from + /// disk and possibly cached with the active + /// device before the first call for playback. + bool mPreload; + + /// The device specific data buffer. + /// This is only used if for non-streaming sounds. + StrongWeakRefPtr< SFXBuffer > mBuffer; + + /// + ChangedSignal mChangedSignal; + + /// Called when the buffer needs to be preloaded. + bool _preloadBuffer(); + + /// Callback for device events. + void _onDeviceEvent( SFXSystemEventType evt ); + + /// + SFXBuffer* _createBuffer(); + + /// + void _onResourceChanged( ResourceBase::Signature sig, const Torque::Path& path ); + + /// + void _registerSignals(); + + /// + void _unregisterSignals(); + + public: + + /// This is only here to allow DECLARE_CONOBJECT + /// to create us from script. You shouldn't use + /// this constructor from C++. + explicit SFXProfile(); + + /// The constructor. + SFXProfile( SFXDescription* desc, + const String& filename = String(), + bool preload = false ); + + /// The destructor. + virtual ~SFXProfile(); + + DECLARE_CONOBJECT( SFXProfile ); + + static void initPersistFields(); + + // SimObject + bool onAdd(); + void onRemove(); + void packData( BitStream* stream ); + void unpackData( BitStream* stream ); + + /// Returns the sound filename. + const String& getFileName() const { return mFilename; } + + /// @note This has nothing to do with mPreload. + /// @see SimDataBlock::preload + bool preload( bool server, String &errorStr ); + + /// Returns the description object for this sound profile. + SFXDescription* getDescription() const { return mDescription; } + + /// Returns the sound resource loading it from + /// disk if it hasn't been preloaded. + /// + /// @note May be NULL if file is loaded directly through SFXDevice. + Resource& getResource(); + + /// Returns the device specific buffer for this for this + /// sound. If it hasn't been preloaded it will be loaded + /// at this time. + /// + /// If this is a streaming profile then the buffer + /// returned must be deleted by the caller. + SFXBuffer* getBuffer(); + + /// Gets the sound duration in milliseconds or + /// returns 0 if the resource was not found. + U32 getSoundDuration(); + + /// + ChangedSignal& getChangedSignal() { return mChangedSignal; } +}; + +DECLARE_CONSOLETYPE( SFXProfile ); + + +#endif // _SFXPROFILE_H_ \ No newline at end of file diff --git a/sfx/sfxProvider.cpp b/sfx/sfxProvider.cpp new file mode 100644 index 0000000..098f8c3 --- /dev/null +++ b/sfx/sfxProvider.cpp @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "sfx/sfxProvider.h" + +SFXProvider* SFXProvider::smProviders = NULL; +Vector SFXProvider::sAllProviders( __FILE__, __LINE__ ); + +SFXProvider* SFXProvider::findProvider( String providerName ) +{ + if( providerName.isEmpty() ) + return NULL; + + SFXProvider* curr = smProviders; + for ( ; curr != NULL; curr = curr->mNextProvider ) + { + if( curr->getName().equal( providerName, String::NoCase ) ) + return curr; + } + + return NULL; +} + +void SFXProvider::regProvider( SFXProvider* provider ) +{ + AssertFatal( provider, "Got null provider!" ); + AssertFatal( findProvider( provider->getName() ) == NULL, "Can't register provider twice!" ); + AssertFatal( provider->mNextProvider == NULL, "Can't register provider twice!" ); + + SFXProvider* oldHead = smProviders; + smProviders = provider; + provider->mNextProvider = oldHead; +} + +SFXProvider::SFXProvider( const String& name ) + : mName( name ), + mNextProvider( NULL ) +{ + VECTOR_SET_ASSOCIATION( mDeviceInfo ); + + sAllProviders.push_back( this ); +} + +void SFXProvider::initializeAllProviders() +{ + + for (U32 i = 0; i < sAllProviders.size(); i++) + sAllProviders[i]->init(); + +} + +SFXProvider::~SFXProvider() +{ + SFXDeviceInfoVector::iterator iter = mDeviceInfo.begin(); + for ( ; iter != mDeviceInfo.end(); iter++ ) + delete *iter; +} + +SFXDeviceInfo* SFXProvider::_findDeviceInfo( const String& deviceName ) +{ + SFXDeviceInfoVector::iterator iter = mDeviceInfo.begin(); + for ( ; iter != mDeviceInfo.end(); iter++ ) + { + if( deviceName.equal( ( *iter )->name, String::NoCase ) ) + return *iter; + } + + // If not found and deviceName is empty, + // return first (default) device. + + if( deviceName.isEmpty() && mDeviceInfo.size() > 0 ) + return mDeviceInfo[ 0 ]; + + return NULL; +} diff --git a/sfx/sfxProvider.h b/sfx/sfxProvider.h new file mode 100644 index 0000000..aa7fed6 --- /dev/null +++ b/sfx/sfxProvider.h @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXPROVIDER_H_ +#define _SFXPROVIDER_H_ + +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif + + +class SFXDevice; + + +/// This macro is used to init the provider. +#define SFX_INIT_PROVIDER( providerClass ) \ + static providerClass g##providerClass##Instance + + +struct SFXDeviceInfo +{ + String driver; + String name; + bool hasHardware; + S32 maxBuffers; + + virtual ~SFXDeviceInfo() {} +}; + +typedef Vector SFXDeviceInfoVector; + +class SFXProvider +{ + friend class SFXSystem; + + private: + + /// The head of the linked list of avalible providers. + static SFXProvider* smProviders; + + /// The next provider in the linked list of available providers. + SFXProvider* mNextProvider; + + /// The provider name which is passed by the concrete provider + /// class to the SFXProvider constructor. + String mName; + + static Vector sAllProviders; + + protected: + + /// The array of avaIlable devices from this provider. The + /// concrete provider class will fill this on construction. + SFXDeviceInfoVector mDeviceInfo; + + /// This registers the provider to the available provider list. It should be called + /// for providers that are properly initialized and available for device enumeration and creation. + /// the add and registration process is 2 steps to avoid issues when TGEA is used as a shared library (specifically on Windows) + static void regProvider( SFXProvider* provider ); + + virtual void init() = 0; + + SFXProvider( const String& name ); + ~SFXProvider(); + + /// Look up the SFXDeviceInfo for the given device in mDeviceInfo. + /// Return default device (first in list) if no other device matches (or null if device list is empty). + SFXDeviceInfo* _findDeviceInfo( const String& deviceName ); + + /// This is called from SFXSystem to create a new device. Must be implemented + /// by all contrete provider classes. + /// + /// @param deviceName The case sensitive name of the device or NULL to create the + // default device. + /// @param useHardware Toggles the use of hardware processing when available. + /// @param maxBuffers The maximum buffers for this device to use or -1 + /// for the device to pick a reasonable default for that device. + /// + /// @return Returns the created device or NULL for failure. + /// + virtual SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ) = 0; + + public: + + /// Returns a specific provider by searching the provider list + /// for the first provider with the case sensitive name. + static SFXProvider* findProvider( String providerName ); + + /// Returns the first provider in the provider list. Use + /// getNextProvider() to iterate over list. + static SFXProvider* getFirstProvider() { return smProviders; } + + /// Returns the next provider in the provider list or NULL + /// when the end of the list is reached. + SFXProvider* getNextProvider() const { return mNextProvider; } + + /// The case sensitive name of this provider. + const String& getName() const { return mName; } + + /// Returns a read only vector with device information for + /// all creatable devices available from this provider. + const SFXDeviceInfoVector& getDeviceInfo() const { return mDeviceInfo; } + + static void initializeAllProviders(); + +}; + + +#endif // _SFXPROVIDER_H_ \ No newline at end of file diff --git a/sfx/sfxResource.cpp b/sfx/sfxResource.cpp new file mode 100644 index 0000000..1829363 --- /dev/null +++ b/sfx/sfxResource.cpp @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxResource.h" +#include "sfx/sfxFileStream.h" +#include "core/util/fourcc.h" +#include "core/resourceManager.h" + + + +// Ugly workaround to keep the constructor protected. +struct SFXResource::_NewHelper +{ + static SFXResource* New( String fileName, const ThreadSafeRef< SFXStream >& stream ) + { + return new SFXResource( fileName, stream ); + } +}; + + +template<> +void* Resource< SFXResource >::create( const Torque::Path& path ) +{ + String fullPath = path.getFullPath(); + + // Try to open the stream. + ThreadSafeRef< SFXStream > stream = SFXFileStream::create( fullPath ); + if( !stream ) + return NULL; + + // We have a valid stream... create the resource. + SFXResource* res = SFXResource::_NewHelper::New( fullPath, stream ); + + return res; +} + +template<> +ResourceBase::Signature Resource< SFXResource >::signature() +{ + return MakeFourCC( 's', 'f', 'x', 'r' ); +} + +Resource< SFXResource > SFXResource::load( String filename ) +{ + return ResourceManager::get().load( filename ); +} + +SFXResource::SFXResource( String fileName, SFXStream *stream ) + : mFileName( fileName ), + mFormat( stream->getFormat() ), + mDuration( stream->getDuration() ) +{ +} + +bool SFXResource::exists( String filename ) +{ + return SFXFileStream::exists( filename ); +} + +SFXStream* SFXResource::openStream() +{ + return SFXFileStream::create( mFileName ); +} diff --git a/sfx/sfxResource.h b/sfx/sfxResource.h new file mode 100644 index 0000000..9374b85 --- /dev/null +++ b/sfx/sfxResource.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXRESOURCE_H_ +#define _SFXRESOURCE_H_ + +#ifndef _SFXCOMMON_H_ + #include "sfx/sfxCommon.h" +#endif +#ifndef __RESOURCE_H__ + #include "core/resource.h" +#endif + + +class SFXStream; + + +/// This is the base class for all sound file resources including +/// streamed sound files. It acts much like an always in-core +/// header to the actual sound data which is read through an SFXStream. +/// +/// The first step occurs at ResourceManager::load() time at which +/// only the header information, the format, size frequency, and +/// looping flag, are loaded from the sound file. This provides +/// just the nessasary information to simulate sound playback for +/// sounds playing just out of the users hearing range. +/// +/// The second step loads the actual sound data or begins filling +/// the stream buffer. This is triggered by a call to ????. +/// SFXProfile, for example, does this when mPreload is enabled. +/// +class SFXResource +{ + public: + + typedef void Parent; + + protected: + + /// The constructor is protected. + /// @see SFXResource::load() + SFXResource(); + + /// + String mFileName; + + /// The format of the sample data. + SFXFormat mFormat; + + /// The length of the sample in milliseconds. + U32 mDuration; + + /// + SFXResource( String fileName, SFXStream* stream ); + + public: + + /// The destructor. + virtual ~SFXResource() {} + + /// This is a helper function used by SFXProfile for load + /// a sound resource. It takes care of trying different + /// types for extension-less filenames. + /// + /// @param filename The sound file path with or without extension. + /// + static Resource< SFXResource > load( String filename ); + + /// A helper function which returns true if the + /// sound resource exists. + /// + /// @param filename The sound file path with or without extension. + /// + static bool exists( String filename ); + + /// + const String& getFileName() { return mFileName; } + + /// Returns the total playback time milliseconds. + U32 getDuration() const { return mDuration; } + + /// The format of the data in the resource. + const SFXFormat& getFormat() const { return mFormat; } + + /// Open a stream for reading thr resource's sample data. + SFXStream* openStream(); + + + // Internal. + struct _NewHelper; + friend struct _NewHelper; +}; + + +#endif // _SFXRESOURCE_H_ diff --git a/sfx/sfxSource.cpp b/sfx/sfxSource.cpp new file mode 100644 index 0000000..078f2ec --- /dev/null +++ b/sfx/sfxSource.cpp @@ -0,0 +1,834 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxSource.h" +#include "sfx/sfxDevice.h" +#include "sfx/sfxVoice.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxBuffer.h" +#include "sfx/sfxEffect.h" +#include "sfx/sfxStream.h" +#include "core/util/safeDelete.h" + + +//#define DEBUG_SPEW + + +SFXSource::SFXSource() + : mVoice( NULL ) +{ + // NOTE: This should never be used directly + // and is only here to satisfy satisfy the + // construction needs of IMPLEMENT_CONOBJECT. +} + +SFXSource::SFXSource( SFXProfile *profile, SFXDescription* desc ) + : mStatus( SFXStatusStopped ), + mPitch( 1 ), + mVelocity( 0, 0, 0 ), + mTransform( true ), + mAttenuatedVolume( 0 ), + mDistToListener( 0 ), + mModulativeVolume( 1 ), + mVoice( NULL ), + mProfile( profile ), + mStatusCallback( NULL ), + mPlayStartTick( 0 ) +{ + mIs3D = desc->mIs3D; + mIsLooping = desc->mIsLooping; + mIsStreaming = desc->mIsStreaming; + mFadeInTime = desc->mFadeInTime; + mFadeOutTime = desc->mFadeOutTime; + mPitch = desc->mPitch; + + setVolume( desc->mVolume ); + + setMinMaxDistance( desc->mReferenceDistance, desc->mMaxDistance ); + + setCone( F32( desc->mConeInsideAngle ), + F32( desc->mConeOutsideAngle ), + desc->mConeOutsideVolume ); + + mChannel = desc->mChannel; + + // Allow namespace linkage. + mNSLinkMask = LinkSuperClassName | LinkClassName; +} + +SFXSource* SFXSource::_create( SFXDevice *device, SFXProfile *profile ) +{ + AssertFatal( profile, "SFXSource::_create() - Got a null profile!" ); + + SFXDescription* desc = profile->getDescription(); + if ( !desc ) + { + Con::errorf( "SFXSource::_create() - Profile has null description!" ); + return NULL; + } + + // Create the source and init the buffer. + SFXSource* source = new SFXSource( profile, desc ); + SFXBuffer* buffer = profile->getBuffer(); + if( !buffer ) + { + delete source; + Con::errorf( "SFXSource::_create() - Could not create device buffer!" ); + return NULL; + } + source->_setBuffer( buffer ); + + // The source is a console object... register it. + source->registerObject(); + + // All sources are added to + // the global source set. + Sim::getSFXSourceSet()->addObject( source ); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] new source '%i' for profile '%i'", source->getId(), profile->getId() ); + #endif + + // Hook up reloading. + + profile->getChangedSignal().notify( source, &SFXSource::_onProfileChanged ); + + return source; +} + +SFXSource* SFXSource::_create( SFXDevice* device, + const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description ) +{ + AssertFatal( stream.ptr() != NULL, "SFXSource::_create() - Got a null stream!" ); + AssertFatal( description, "SFXSource::_create() - Got a null description!" ); + + // Create the source and init the buffer. + SFXSource* source = new SFXSource( NULL, description ); + SFXBuffer* buffer = SFX->_createBuffer( stream, description ); + if( !buffer ) + { + delete source; + Con::errorf( "SFXSource::_create() - Could not create device buffer!" ); + return NULL; + } + source->_setBuffer( buffer ); + + // We're all good... register this sucker. + source->registerObject(); + + // All sources are added to the global source set. + Sim::getSFXSourceSet()->addObject( source ); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] new source '%i' for stream", source->getId() ); + #endif + + return source; +} + +SFXSource::~SFXSource() +{ + // Delete all remaining effects on this source. + + for( EffectList::Iterator iter = mEffects.begin(); + iter != mEffects.end(); ++ iter ) + delete *iter; +} + +IMPLEMENT_CONOBJECT(SFXSource); + +void SFXSource::initPersistFields() +{ + addField( "statusCallback", TypeString, Offset(mStatusCallback, SFXSource) ); + + // We don't want any fields that manipluate the playback of the + // source as they complicate updates. If we could easily set dirty + // bits on changes to fields then it would be worth it, + // so for now just use the console methods. + + Parent::initPersistFields(); +} + +bool SFXSource::processArguments( S32 argc, const char **argv ) +{ + // We don't want to allow a source to be constructed + // via the script... force use of the SFXSystem. + Con::errorf( ConsoleLogEntry::Script, "Use sfxCreateSource, sfxPlay, or sfxPlayOnce!" ); + return false; +} + +template< class T > +void SFXSource::_clearEffects() +{ + for( EffectList::Iterator iter = mEffects.begin(); + iter != mEffects.end(); ) + { + EffectList::Iterator next = iter; + next ++; + + if( dynamic_cast< T* >( *iter ) ) + { + delete *iter; + mEffects.erase( iter ); + } + + iter = next; + } +} + +void SFXSource::_reloadBuffer() +{ + if( mProfile != NULL && _releaseVoice() ) + { + SFXBuffer* buffer = mProfile->getBuffer(); + if( !buffer ) + { + Con::errorf( "SFXSource::_reloadBuffer() - Could not create device buffer!" ); + return; + } + + _setBuffer( buffer ); + + if( getLastStatus() == SFXStatusPlaying ) + SFX->_assignVoices(); + } +} + +void SFXSource::_setBuffer( SFXBuffer* buffer ) +{ + mBuffer = buffer; + + // There is no telling when the device will be + // destroyed and the buffers deleted. + // + // By caching the duration now we can allow sources + // to continue virtual playback until the device + // is restored. + mDuration = mBuffer->getDuration(); +} + +bool SFXSource::_allocVoice( SFXDevice* device ) +{ + // We shouldn't have any existing voice! + AssertFatal( !mVoice, "SFXSource::_allocVoice() - Already had a voice!" ); + + // Must not assign voice to source that isn't playing. + AssertFatal( getLastStatus() == SFXStatusPlaying, + "SFXSource::_allocVoice() - Source is not playing!" ); + + // The buffer can be lost when the device is reset + // or changed, so initialize it if we have to. If + // that fails then we cannot create the voice. + + if( mBuffer.isNull() ) + { + if( mProfile != NULL ) + _setBuffer( mProfile->getBuffer() ); + + if( mBuffer.isNull() ) + return false; + } + + // Ask the device for a voice based on this buffer. + mVoice = device->createVoice( mIs3D, mBuffer ); + if( !mVoice ) + return false; + + setVolume( mVolume ); + if( mPitch != 1.0f ) + setPitch( mPitch ); + + if( mIs3D ) + { + setTransform( mTransform ); + setVelocity( mVelocity ); + setMinMaxDistance( mMinDistance, mMaxDistance ); + setCone( mConeInsideAngle, mConeOutsideAngle, mConeOutsideVolume ); + } + + // Update the duration... it shouldn't have changed, but + // its probably better that we're accurate if it did. + mDuration = mBuffer->getDuration(); + + // If virtualized playback has been started, we transfer its position to the + // voice and stop virtualization. + + if( mVirtualPlayTimer.isStarted() ) + { + const U32 playTime = mVirtualPlayTimer.getPosition(); + const U32 pos = mBuffer->getFormat().getSampleCount( playTime ); + mVoice->setPosition( pos); + mVirtualPlayTimer.stop(); + } + + mVoice->play( mIsLooping ); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] allocated voice for source '%i'", getId() ); + #endif + + return true; +} + +void SFXSource::onRemove() +{ + stop(); + + // Remove it from the set. + Sim::getSFXSourceSet()->removeObject( this ); + + // Let the system know. + SFX->_onRemoveSource( this ); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] removed source '%i'", getId() ); + #endif + + if( mProfile != NULL ) + mProfile->getChangedSignal().remove( this, &SFXSource::_onProfileChanged ); + + Parent::onRemove(); +} + +bool SFXSource::_releaseVoice() +{ + if( !mVoice ) + return true; + + // Refuse to release a voice for a streaming buffer that + // is not coming from a profile. For streaming buffer, we will + // have to release the buffer, too, and without a profile we don't + // know how to recreate the stream. + + if( isStreaming() && !mProfile ) + return false; + + // If we're currently playing, transfer our playback position + // to the playtimer so we can virtualize playback while not + // having a voice. + + SFXStatus status = getLastStatus(); + if( status == SFXStatusPlaying || status == SFXStatusBlocked ) + { + mVirtualPlayTimer.setPosition( mVoice->getPosition() ); + mVirtualPlayTimer.start(); + + if( status == SFXStatusBlocked ) + status = SFXStatusPlaying; + } + + mVoice = NULL; + + // If this is a streaming source, release our buffer, too. + // Otherwise the voice will stick around as it is uniquely assigned to + // the buffer. When we get reassigned a voice, we will have to do + // a full stream seek anyway, so it's no real loss here. + + if( isStreaming() ) + mBuffer = NULL; + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] release voice for source '%i' (status: %s)", + getId(), SFXStatusToString( status ) ); + #endif + + return true; +} + +void SFXSource::_updateVolume( const MatrixF& listener ) +{ + const F32 volume = mVolume * mModulativeVolume; + + if ( !mIs3D ) + { + mDistToListener = 0.0f; + mAttenuatedVolume = volume; + return; + } + + Point3F pos, lpos; + mTransform.getColumn( 3, &pos ); + listener.getColumn( 3, &lpos ); + + mDistToListener = ( pos - lpos ).len(); + mAttenuatedVolume = SFXDistanceAttenuation( + SFX->getDistanceModel(), + mMinDistance, + mMaxDistance, + mDistToListener, + volume, + SFX->getRolloffFactor() ); +} + +bool SFXSource::_setStatus( SFXStatus status ) +{ + if ( mStatus == status ) + return false; + + mStatus = status; + + // Do the callback if we have it. + + const char* statusString = SFXStatusToString( mStatus ); + if ( mStatusCallback && mStatusCallback[0] ) + Con::executef( mStatusCallback, getIdString(), statusString ); + else if ( getNamespace() ) + Con::executef( this, "onStatusChange", statusString ); + + return true; +} + +void SFXSource::play( F32 fadeInTime ) +{ + // Update our status once. + _updateStatus(); + + if( mStatus == SFXStatusPlaying ) + return; + + if( mStatus != SFXStatusPaused ) + mPlayStartTick = Platform::getVirtualMilliseconds(); + + // Add fade-out, if requested. + + U32 fadeOutStartsAt = getDuration(); + if( mFadeOutTime ) + { + fadeOutStartsAt = getMax( getPosition(), getDuration() - U32( mFadeOutTime * 1000 ) ); + mEffects.pushBack( new SFXFadeEffect( this, + getMin( mFadeOutTime, + F32( getDuration() - getPosition() ) / 1000.f ), + 0.0f, + fadeOutStartsAt, + SFXFadeEffect::ON_END_Stop, + true ) ); + } + + // Add fade-in, if requested. + + if( fadeInTime != 0.0f && ( fadeInTime > 0.0f || mFadeInTime > 0.0f ) ) + { + // Don't fade from full 0.0f to avoid virtualization on this source. + + if( fadeInTime == -1.0f ) + fadeInTime = mFadeInTime; + + fadeInTime = getMin( fadeInTime, F32( fadeOutStartsAt ) * 1000.f ); + + mEffects.pushFront( new SFXFadeEffect( this, + fadeInTime, + mVolume, + getPosition(), + SFXFadeEffect::ON_END_Nop, + true ) ); + setVolume( 0.01f ); + } + + // Start playback. + + _setStatus( SFXStatusPlaying ); + if( mVoice ) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] playing source '%i'", getId() ); + #endif + + mVoice->play( mIsLooping ); + } + else + { + // To ensure the fastest possible reaction + // to this playback let the system reassign + // voices immediately. + SFX->_assignVoices(); + + // If we did not get assigned a voice, start the + // playback timer for virtualized playback. + + if( !mVoice ) + { + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] virtualizing playback of source '%i'", getId() ); + #endif + + mVirtualPlayTimer.start(); + } + } +} + +void SFXSource::stop( F32 fadeOutTime ) +{ + _updateStatus(); + + if( mStatus != SFXStatusPlaying + && mStatus != SFXStatusPaused ) + return; + + if( fadeOutTime != 0.0f && ( fadeOutTime > 0.0f || mFadeOutTime > 0.0f ) ) + { + // Do a fade-out and then stop. + + _clearEffects< SFXFadeEffect >(); + + if( fadeOutTime == -1.0f ) + fadeOutTime = mFadeOutTime; + + mEffects.pushFront( new SFXFadeEffect( this, + getMin( fadeOutTime, + F32( getDuration() - getPosition() ) / 1000.f ), + 0.0f, + getPosition(), + SFXFadeEffect::ON_END_Stop, + true ) ); + } + else + { + // Stop immediately. + + _setStatus( SFXStatusStopped ); + + if ( mVoice ) + mVoice->stop(); + else + mVirtualPlayTimer.stop(); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] stopped playback of source '%i'", getId() ); + #endif + } +} + +void SFXSource::pause( F32 fadeOutTime ) +{ + _updateStatus(); + + if( mStatus != SFXStatusPlaying ) + return; + + if( fadeOutTime != 0.0f && ( fadeOutTime > 0.0f || mFadeOutTime > 0.0f ) ) + { + // Do a fade-out and then pause. + + _clearEffects< SFXFadeEffect >(); + if( fadeOutTime == -1.0f ) + fadeOutTime = mFadeOutTime; + + mEffects.pushFront( new SFXFadeEffect( this, + getMin( fadeOutTime, + F32( getDuration() - getPosition() ) / 1000.f ), + 0.0f, + getPosition(), + SFXFadeEffect::ON_END_Pause, + true ) ); + } + else + { + // Pause immediately. + + _setStatus( SFXStatusPaused ); + + if ( mVoice ) + mVoice->pause(); + else + mVirtualPlayTimer.pause(); + + #ifdef DEBUG_SPEW + Platform::outputDebugString( "[SFXSource] paused playback of source '%i'", getId() ); + #endif + } +} + +void SFXSource::_update() +{ + // Update our effects, if any. + + for( EffectList::Iterator iter = mEffects.begin(); + iter != mEffects.end(); ) + { + EffectList::Iterator next = iter; + next ++; + + if( !( *iter )->update() ) + { + delete *iter; + mEffects.erase( iter ); + } + + iter = next; + } +} + +SFXStatus SFXSource::_updateStatus() +{ + // If we have a voice, use its status. + + if( mVoice ) + { + SFXStatus voiceStatus = mVoice->getStatus(); + + // Filter out SFXStatusBlocked. + + if( voiceStatus == SFXStatusBlocked ) + _setStatus( SFXStatusPlaying ); + else + _setStatus( voiceStatus ); + + return mStatus; + } + + // If we're not in a playing state or we're a looping + // sound then we don't need to calculate the status. + + if( mIsLooping || mStatus != SFXStatusPlaying ) + return mStatus; + + // If we're playing and don't have a voice we + // need to decide if the sound is done playing + // to ensure proper virtualization of the sound. + + if( mVirtualPlayTimer.getPosition() > mDuration ) + _setStatus( SFXStatusStopped ); + + return mStatus; +} + +U32 SFXSource::getPosition() const +{ + if( mVoice ) + return mVoice->getFormat().getDuration( mVoice->getPosition() ); + else + return mVirtualPlayTimer.getPosition(); +} + +void SFXSource::setPosition( U32 ms ) +{ + AssertFatal( ms < getDuration(), "SFXSource::setPosition() - position out of range" ); + if( mVoice ) + mVoice->setPosition( mVoice->getFormat().getSampleCount( ms ) ); + else + mVirtualPlayTimer.setPosition( ms ); +} + +void SFXSource::setVelocity( const VectorF& velocity ) +{ + mVelocity = velocity; + + if ( mVoice && mIs3D ) + mVoice->setVelocity( velocity ); +} + +void SFXSource::setTransform( const MatrixF& transform ) +{ + mTransform = transform; + + if ( mVoice && mIs3D ) + mVoice->setTransform( mTransform ); +} + +void SFXSource::setVolume( F32 volume ) +{ + mVolume = mClampF( volume, 0, 1 ); + + if ( mVoice ) + mVoice->setVolume( mVolume * mModulativeVolume ); +} + +void SFXSource::_setModulativeVolume( F32 volume ) +{ + mModulativeVolume = volume; + setVolume( mVolume ); +} + +void SFXSource::setPitch( F32 pitch ) +{ + AssertFatal( pitch > 0.0f, "Got bad pitch!" ); + mPitch = pitch; + + if ( mVoice ) + mVoice->setPitch( mPitch ); +} + +void SFXSource::setMinMaxDistance( F32 min, F32 max ) +{ + min = getMax( 0.0f, min ); + max = getMax( 0.0f, max ); + + mMinDistance = min; + mMaxDistance = max; + + if ( mVoice && mIs3D ) + mVoice->setMinMaxDistance( mMinDistance, mMaxDistance ); +} + +void SFXSource::setCone( F32 innerAngle, + F32 outerAngle, + F32 outerVolume ) +{ + mConeInsideAngle = mClampF( innerAngle, 0.0f, 360.0 ); + mConeOutsideAngle = mClampF( outerAngle, mConeInsideAngle, 360.0 ); + mConeOutsideVolume = mClampF( outerVolume, 0.0f, 1.0f ); + + if ( mVoice && mIs3D ) + mVoice->setCone( mConeInsideAngle, + mConeOutsideAngle, + mConeOutsideVolume ); +} + +bool SFXSource::isReady() const +{ + return ( mBuffer != NULL && mBuffer->isReady() ); +} + +void SFXSource::addMarker( const String& name, U32 pos ) +{ + mEffects.pushBack( new SFXMarkerEffect( this, name, pos ) ); +} + +SFXProfile* SFXSource::getProfile() const +{ + return mProfile; +} + +//----------------------------------------------------------------------------- + +ConsoleMethod( SFXSource, addMarker, void, 4, 4, "( string name, float pos ) - Add a notification marker called 'name' at 'pos' seconds of playback." ) +{ + String name = argv[ 2 ]; + U32 pos = U32( dAtof( argv[ 3 ] ) * 1000.f ); + object->addMarker( name, pos ); +} + +ConsoleMethod( SFXSource, play, void, 2, 3, "( [float fadeIn] ) - Starts playback of the source." ) +{ + F32 fadeInTime = -1.0f; + if( argc > 2 ) + fadeInTime = dAtof( argv[ 2 ] ); + + object->play( fadeInTime ); +} + +ConsoleMethod( SFXSource, stop, void, 2, 3, "( [float fadeOut] ) - Ends playback of the source." ) +{ + F32 fadeOutTime = -1.0f; + if( argc > 2 ) + fadeOutTime = dAtof( argv[ 2 ] ); + + object->stop( fadeOutTime ); +} + +ConsoleMethod( SFXSource, pause, void, 2, 3, "( [float fadeOut] ) - Pauses playback of the source." ) +{ + F32 fadeOutTime = -1.0f; + if( argc > 2 ) + fadeOutTime = dAtof( argv[ 2 ] ); + + object->pause( fadeOutTime ); +} + +ConsoleMethod( SFXSource, isReady, bool, 2, 2, "() - Returns true if the sound data associated with the source has been loaded." ) +{ + return object->isReady(); +} + +ConsoleMethod( SFXSource, isPlaying, bool, 2, 2, "() - Returns true if the source is playing or false if not." ) +{ + return object->isPlaying(); +} + +ConsoleMethod( SFXSource, isPaused, bool, 2, 2, "() - Returns true if the source is paused or false if not." ) +{ + return object->isPaused(); +} + +ConsoleMethod( SFXSource, isStopped, bool, 2, 2, "() - Returns true if the source is stopped or false if not." ) +{ + return object->isStopped(); +} + +ConsoleMethod( SFXSource, setVolume, void, 3, 3, "( float volume ) - Sets the playback volume of the source." ) +{ + object->setVolume( dAtof( argv[2] ) ); +} + +ConsoleMethod( SFXSource, setPitch, void, 3, 3, "( float pitch ) - Scales the pitch of the source." ) +{ + object->setPitch( dAtof( argv[2] ) ); +} + +ConsoleMethod( SFXSource, getChannel, S32, 2, 2, "() - Returns the volume channel." ) +{ + return object->getChannel(); +} + +ConsoleMethod( SFXSource, setTransform, void, 3, 4, "( vector pos [, vector direction ] ) - Set the position and orientation of a 3D SFXSource." ) +{ + MatrixF mat = object->getTransform(); + + Point3F pos; + dSscanf( argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z ); + mat.setPosition( pos ); + + if( argc > 3 ) + { + Point3F dir; + dSscanf( argv[ 3 ], "%g %g %g", &dir.x, &dir.y, &dir.z ); + mat.setColumn( 1, dir ); + } + + object->setTransform( mat ); +} + +ConsoleMethod( SFXSource, getStatus, const char*, 2, 2, "() - Returns the playback status of the source." ) +{ + return SFXStatusToString( object->getStatus() ); +} + +ConsoleMethod( SFXSource, getPosition, F32, 2, 2, "() - Returns the current playback position in seconds." ) +{ + return F32( object->getPosition() ) * 0.001f; +} + +ConsoleMethod( SFXSource, setPosition, void, 3, 3, "( float ) - Set the current playback position in seconds." ) +{ + S32 pos = dAtoi( argv[ 2 ] ); + if( pos >= 0 && pos <= object->getDuration() ) + object->setPosition( U32( dAtof( argv[ 2 ] ) ) * 1000.0f ); +} + +ConsoleMethod( SFXSource, setCone, void, 5, 5, "( float innerAngle, float outerAngle, float outsideVolume ) - Set the 3D volume cone for the sound." ) +{ + F32 innerAngle = dAtof( argv[ 2 ] ); + F32 outerAngle = dAtof( argv[ 3 ] ); + F32 outsideVolume = dAtof( argv[ 4 ] ); + + bool isValid = true; + + if( innerAngle < 0.0 || innerAngle > 360.0 ) + { + Con::errorf( "SFXSource.setCone() - 'innerAngle' must be between 0 and 360" ); + isValid = false; + } + if( outerAngle < 0.0 || outerAngle > 360.0 ) + { + Con::errorf( "SFXSource.setCone() - 'outerAngle' must be between 0 and 360" ); + isValid = false; + } + if( outsideVolume < 0.0 || outsideVolume > 1.0 ) + { + Con::errorf( "SFXSource.setCone() - 'outsideVolume' must be between 0 and 1" ); + isValid = false; + } + + if( !isValid ) + return; + + object->setCone( innerAngle, outerAngle, outsideVolume ); +} + +ConsoleMethod( SFXSource, getDuration, F32, 2, 2, "() - Get the total playback time in seconds." ) +{ + return F32( object->getDuration() ) * 0.001f; +} diff --git a/sfx/sfxSource.h b/sfx/sfxSource.h new file mode 100644 index 0000000..76061e8 --- /dev/null +++ b/sfx/sfxSource.h @@ -0,0 +1,328 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXSOURCE_H_ +#define _SFXSOURCE_H_ + +#ifndef _SFXDEVICE_H_ + #include "sfx/sfxDevice.h" +#endif +#ifndef _SFXVOICE_H_ + #include "sfx/sfxVoice.h" +#endif +#ifndef _SIMBASE_H_ + #include "console/simBase.h" +#endif +#ifndef _MPOINT3_H_ + #include "math/mPoint3.h" +#endif +#ifndef _MMATRIX_H_ + #include "math/mMatrix.h" +#endif +#ifndef _TSTREAM_H_ + #include "core/stream/tStream.h" +#endif +#ifndef _TIMESOURCE_H_ + #include "core/util/timeSource.h" +#endif +#ifndef _TORQUE_LIST_ + #include "core/util/tList.h" +#endif +#ifndef _SFXPROFILE_H_ + #include "sfx/sfxProfile.h" +#endif + + +class SFXDescription; +class SFXBuffer; +class SFXDevice; +class SFXEffect; + + + +/// A source is a scriptable controller for all +/// aspects of sound playback. +class SFXSource : public SimObject, + public IPositionable< U32 > +{ + friend class SFXSystem; + friend class SFXListener; + + typedef SimObject Parent; + + protected: + + typedef GenericTimeSource< RealMSTimer > TimeSource; + typedef Torque::List< SFXEffect* > EffectList; + + /// Used by SFXSystem to create sources. + static SFXSource* _create( SFXDevice* device, SFXProfile* profile ); + static SFXSource* _create( SFXDevice* device, const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + + /// Internal constructor used for sources. + SFXSource( SFXProfile* profile, SFXDescription* description ); + + /// You cannot delete a source directly. + /// @see SFX_DELETE + virtual ~SFXSource(); + + /// This is called only from the device to allow + /// the source to update it's attenuated volume. + void _updateVolume( const MatrixF& listener ); + + /// Called by the device so that the source can + /// update itself and any attached buffer. + SFXStatus _updateStatus(); + + /// The last updated playback status of the source. + mutable SFXStatus mStatus; + + /// The simulation tick count that playback was started at for this source. + U32 mPlayStartTick; + + /// Time object used to keep track of playback when running virtualized + /// (i.e. without connected SFXVoice). Sync'ed to SFXVoice playback as needed. + TimeSource mVirtualPlayTimer; + + /// The profile used to create this source. + /// NULL if the source has been constructed directly from an SFXStream. + SimObjectPtr mProfile; + + /// The device specific voice which is used during + /// playback. By making it a SafePtr it will NULL + /// automatically when the device is deleted. + StrongWeakRefPtr mVoice; + + /// The reference counted device specific buffer used by + /// the voice for playback. + StrongWeakRefPtr mBuffer; + + /// The duration of the sound cached from the buffer in + /// _initBuffer() used for managing virtual sources. + U32 mDuration; + + /// This is the volume of a source with respect + /// to the last listener position. It is used + /// for culling sounds. + F32 mAttenuatedVolume; + + /// The distance of this source to the last + /// listener position. + F32 mDistToListener; + + /// The desired sound volume. + F32 mVolume; + + /// This + F32 mModulativeVolume; + + /// The sound pitch scalar. + F32 mPitch; + + /// The transform if this is a 3d source. + MatrixF mTransform; + + /// The last set velocity. + VectorF mVelocity; + + bool mIs3D; + + bool mIsLooping; + + bool mIsStreaming; + + F32 mMinDistance; + + F32 mMaxDistance; + + /// In radians. + F32 mConeInsideAngle; + + /// In radians. + F32 mConeOutsideAngle; + + /// + F32 mConeOutsideVolume; + + /// + F32 mFadeInTime; + + /// + F32 mFadeOutTime; + + /// Channel number used for playback of this source. + U32 mChannel; + + /// + StringTableEntry mStatusCallback; + + /// List of effects that are active on this source. + EffectList mEffects; + + /// We overload this to disable creation of + /// a source via script 'new'. + bool processArguments( S32 argc, const char **argv ); + + /// Used internally for setting the sound status. + bool _setStatus( SFXStatus status ); + + /// This is called from SFXSystem for setting + /// the volume scalar generated from the master + /// and channel volumes. + void _setModulativeVolume( F32 volume ); + + /// Create a new voice for this source. + bool _allocVoice( SFXDevice* device ); + + /// Release the voice if the source has one. + bool _releaseVoice(); + + /// + void _update(); + + /// + void _setBuffer( SFXBuffer* buffer ); + + /// Reload the sound buffer. Temporarily goes to virtualized playback when necessary. + void _reloadBuffer(); + + /// Delete all effects of the given type. + template< class T > void _clearEffects(); + + /// + void _onProfileChanged( SFXProfile* profile ) + { + if( profile == mProfile.getObject() ) + _reloadBuffer(); + } + + public: + + DECLARE_CONOBJECT(SFXSource); + + /// The default constructor is *only* here to satisfy the + /// construction needs of IMPLEMENT_CONOBJECT. It does not + /// create a valid source! + explicit SFXSource(); + + /// Also needed by IMPLEMENT_CONOBJECT, but we don't use it. + static void initPersistFields(); + + /// This is normally called from the system to + /// detect if this source has been assigned a + /// voice for playback. + bool hasVoice() const { return mVoice != NULL; } + + /// Starts the sound from the current playback position. + void play( F32 fadeInTime = -1.0f ); + + /// Stops playback and resets the playback position. + void stop( F32 fadeOutTime = -1.0f ); + + /// Pauses the sound playback. + void pause( F32 fadeOutTime = -1.0f ); + + /// Return the current playback position in milliseconds. + /// @note For looping sources, this returns the total playback time so far. + U32 getPosition() const; + + /// Set the current playback position in milliseconds. + void setPosition( U32 ms ); + + /// Sets the position and orientation for a 3d buffer. + void setTransform( const MatrixF& transform ); + + /// Sets the velocity for a 3d buffer. + void setVelocity( const VectorF& velocity ); + + /// Sets the minimum and maximum distances for 3d falloff. + void setMinMaxDistance( F32 min, F32 max ); + + /// Set sound cone of a 3D sound. + /// + /// @param innerAngle Inner cone angle in degrees. + /// @param outerAngle Outer cone angle in degrees. + /// @param outerVolume Outer volume factor. + void setCone( F32 innerAngle, + F32 outerAngle, + F32 outerVolume ); + + /// Sets the source volume which will still be + /// scaled by the master and channel volumes. + void setVolume( F32 volume ); + + /// Sets the source pitch scale. + void setPitch( F32 pitch ); + + /// Returns the last set velocity. + const VectorF& getVelocity() const { return mVelocity; } + + /// Returns the last set transform. + const MatrixF& getTransform() const { return mTransform; } + + /// Returns the source's total playback time in milliseconds. + U32 getDuration() const { return mDuration; } + + /// Returns the source volume. + F32 getVolume() const { return mVolume; } + + /// Returns the volume with respect to the master + /// and channel volumes and the listener. + F32 getAttenuatedVolume() const { return mAttenuatedVolume; } + + /// Returns the source pitch scale. + F32 getPitch() const { return mPitch; } + + /// Returns the last known status without checking + /// the voice or doing the virtual calculation. + SFXStatus getLastStatus() const { return mStatus; } + + /// Returns the sound status. + SFXStatus getStatus() const { return const_cast( this )->_updateStatus(); } + + /// Returns true if the source is playing. + bool isPlaying() const { return getStatus() == SFXStatusPlaying; } + + /// Returns true if the source is stopped. + bool isStopped() const { return getStatus() == SFXStatusStopped; } + + /// Returns true if the source has been paused. + bool isPaused() const { return getStatus() == SFXStatusPaused; } + + /// + bool isBlocked() const { return ( mVoice && mVoice->getStatus() == SFXStatusBlocked ); } + + /// + bool isVirtualized() const { return ( mVoice == NULL && mVirtualPlayTimer.isStarted() ); } + + /// Returns true if this is a 3D source. + bool is3d() const { return mIs3D; } + + /// Returns true if this is a looping source. + bool isLooping() const { return mIsLooping; } + + /// Returns true if this is a continuously streaming source. + bool isStreaming() const { return mIsStreaming; } + + /// Returns true if the source's associated data is ready for playback. + bool isReady() const; + + /// Returns the volume channel this source is assigned to. + U32 getChannel() const { return mChannel; } + + /// Returns the last distance to the listener. + F32 getDistToListener() const { return mDistToListener; } + + /// + void addMarker( const String& name, U32 pos ); + + /// + SFXProfile* getProfile() const; + + // SimObject. + void onRemove(); +}; + +#endif // _SFXSOURCE_H_ \ No newline at end of file diff --git a/sfx/sfxStream.h b/sfx/sfxStream.h new file mode 100644 index 0000000..68e6a3e --- /dev/null +++ b/sfx/sfxStream.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXSTREAM_H_ +#define _SFXSTREAM_H_ + +#ifndef _THREADSAFEREFCOUNT_H_ +# include "platform/threads/threadSafeRefCount.h" +#endif +#ifndef _SFXCOMMON_H_ +# include "sfx/sfxCommon.h" +#endif +#ifndef _TSTREAM_H_ +# include "core/stream/tStream.h" +#endif + + +/// The base sound data streaming interface. +/// +/// @note Streams that support seeking should implement the IPositionable interface. +/// @note Since SFXStreams are byte streams, all offset/size information is in bytes and +/// not in number of samples. +class SFXStream : public ThreadSafeRefCount< SFXStream >, + public IInputStream< U8 >, + public IResettable +{ + public: + + typedef void Parent; + + /// Destructor. + virtual ~SFXStream() {} + + /// Make a copy of this stream with its own private state, so + /// new independent read() operations can be issued on the same + /// data stream. + /// + /// @return Returns a copy of the stream or NULL. + virtual SFXStream* clone() const { return NULL; } + + /// The format of the data in the stream. + virtual const SFXFormat& getFormat() const = 0; + + /// The number of samples in the data stream. + virtual U32 getSampleCount() const = 0; + + /// The data size in bytes of the decompressed PCM data. + virtual U32 getDataLength() const = 0; + + /// The length of the sound in milliseconds. + virtual U32 getDuration() const = 0; + + /// Returns true if we've reached the end of the stream. + virtual bool isEOS() const = 0; + + /// Resets the stream to restart reading data from the begining. + virtual void reset() = 0; + + /// Reads data from the stream and decompresses it into PCM samples. + /// + /// @param length Number of bytes to read. + virtual U32 read( U8 *buffer, U32 length ) = 0; +}; + +typedef ThreadSafeRef< SFXStream > SFXStreamRef; + +#endif // _SFXSTREAM_H_ diff --git a/sfx/sfxSystem.cpp b/sfx/sfxSystem.cpp new file mode 100644 index 0000000..02dd35d --- /dev/null +++ b/sfx/sfxSystem.cpp @@ -0,0 +1,970 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sfx/sfxSystem.h" + +#include "sfx/sfxProvider.h" +#include "sfx/sfxDevice.h" +#include "sfx/sfxInternal.h" +#include "sfx/sfxSource.h" +#include "sfx/sfxProfile.h" + +#include "console/console.h" +#include "console/consoleTypes.h" +#include "sim/processList.h" +#include "platform/profiler.h" + +#include "sfx/media/sfxWavStream.h" +#ifdef TORQUE_OGGVORBIS + #include "sfx/media/sfxVorbisStream.h" +#endif + + +SFXSystem* SFXSystem::smSingleton = NULL; + + +//----------------------------------------------------------------------------- + +void SFXSystem::init() +{ + AssertWarn( smSingleton == NULL, "SFX has already been initialized!" ); + + SFXProvider::initializeAllProviders(); + + // Register the streams and resources. Note that + // the order here does matter! + SFXFileStream::registerExtension( ".wav", ( SFXFILESTREAM_CREATE_FN ) SFXWavStream::create ); + #ifdef TORQUE_OGGVORBIS + SFXFileStream::registerExtension( ".ogg", ( SFXFILESTREAM_CREATE_FN ) SFXVorbisStream::create ); + #endif + + // Note: If you have provider specific file types you should + // register them in the provider initialization. + + // Create the system. + smSingleton = new SFXSystem(); +} + + +void SFXSystem::destroy() +{ + AssertWarn( smSingleton != NULL, "SFX has not been initialized!" ); + + SFXFileStream::unregisterExtension( ".wav" ); + #ifdef TORQUE_OGGVORBIS + SFXFileStream::unregisterExtension( ".ogg" ); + #endif + + delete smSingleton; + smSingleton = NULL; + + SFXInternal::THREAD_POOL().shutdown(); +} + + +SFXSystem::SFXSystem() + : mDevice( NULL ), + mLastTime( 0 ), + mMasterVolume( 1 ), + mStatNumSources( 0 ), + mStatNumPlaying( 0 ), + mStatNumCulled( 0 ), + mStatNumVoices( 0 ), + mDistanceModel( SFXDistanceModelLinear ), + mDopplerFactor( 0.5 ), + mRolloffFactor( 1.0 ) +{ + VECTOR_SET_ASSOCIATION( mSources ); + VECTOR_SET_ASSOCIATION( mPlayOnceSources ); + + // Setup the default channel volumes. + for ( S32 i=0; i < NumChannels; i++ ) + mChannelVolume[i] = 1.0f; + + Con::addVariable( "SFX::numSources", TypeS32, &mStatNumSources ); + Con::addVariable( "SFX::numPlaying", TypeS32, &mStatNumPlaying ); + Con::addVariable( "SFX::numCulled", TypeS32, &mStatNumCulled ); + Con::addVariable( "SFX::numVoices", TypeS32, &mStatNumVoices ); +} + +SFXSystem::~SFXSystem() +{ + Con::removeVariable( "SFX::numSources" ); + Con::removeVariable( "SFX::numPlaying" ); + Con::removeVariable( "SFX::numCulled" ); + Con::removeVariable( "SFX::numBuffers" ); + + // Cleanup any remaining sources! + while ( !mSources.empty() ) + mSources.first()->deleteObject(); + + mSources.clear(); + mPlayOnceSources.clear(); + + // If we still have a device... delete it. + deleteDevice(); +} + +bool SFXSystem::createDevice( const String& providerName, const String& deviceName, bool useHardware, S32 maxBuffers, bool changeDevice ) +{ + // Make sure we don't have a device already. + + if( mDevice && !changeDevice ) + return false; + + // Lookup the provider. + + SFXProvider* provider = SFXProvider::findProvider( providerName ); + if( !provider ) + return false; + + // If we have already created this device and are using it then no need to do anything. + + if( mDevice + && providerName.equal( mDevice->getProvider()->getName(), String::NoCase ) + && deviceName.equal( mDevice->getName(), String::NoCase ) + && useHardware == mDevice->getUseHardware() ) + return true; + + // If we have an existing device remove it. + + if( mDevice ) + deleteDevice(); + + // Create the new device. + + mDevice = provider->createDevice( deviceName, useHardware, maxBuffers ); + if( !mDevice ) + { + Con::errorf( "SFXSystem::createDevice - failed creating %s device '%s'", providerName.c_str(), deviceName.c_str() ); + return false; + } + else + Con::printf( "SFXSystem::createDevice - created %s device '%s'", providerName.c_str(), deviceName.c_str() ); + + // Set defaults. + + mDevice->setDistanceModel( mDistanceModel ); + mDevice->setDopplerFactor( mDopplerFactor ); + mDevice->setRolloffFactor( mRolloffFactor ); + + // Signal system. + + getEventSignal().trigger( SFXSystemEvent_CreateDevice ); + + return true; +} + +String SFXSystem::getDeviceInfoString() +{ + // Make sure we have a valid device. + if( !mDevice ) + return String(); + + return String::ToString( "%s\t%s\t%s\t%d", + mDevice->getProvider()->getName().c_str(), + mDevice->getName().c_str(), + mDevice->getUseHardware() ? "1" : "0", + mDevice->getMaxBuffers() ); +} + +void SFXSystem::deleteDevice() +{ + // Make sure we have a valid device. + if ( !mDevice ) + return; + + // Put all playing sources into virtualized playback mode. Where this fails, + // stop the source. + + for( U32 i = 0, numSources = mSources.size(); i < numSources; ++ i ) + if( !mSources[ i ]->_releaseVoice() ) + mSources[ i ]->stop(); + + // Signal everyone who cares that the + // device is being deleted. + getEventSignal().trigger( SFXSystemEvent_DestroyDevice ); + + // Free the device which should delete all + // the active voices and buffers. + delete mDevice; + mDevice = NULL; +} + +SFXSource* SFXSystem::createSource( SFXProfile* profile, + const MatrixF* transform, + const VectorF* velocity ) +{ + // We sometimes get null profiles... nothing to play without a profile! + if ( !profile ) + return NULL; + + // Create the source. + SFXSource *source = SFXSource::_create( mDevice, profile ); + if ( !source ) + { + Con::errorf( + "SFXSystem::createSource() - Creation failed!\n" + " Profile: %s\n" + " Filename: %s", + profile->getName(), + profile->getFilename() ); + + return NULL; + } + + _addSource( source, transform, velocity ); + return source; +} + +SFXSource* SFXSystem::createSourceFromStream( const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description ) +{ + // We sometimes get null values from script... fail in that case. + if ( !stream || !description ) + return NULL; + + // Create the source. + SFXSource *source = SFXSource::_create( mDevice, stream, description ); + if ( !source ) + return NULL; + + _addSource( source ); + return source; +} + +void SFXSystem::_addSource( SFXSource* source, const MatrixF* transform, const VectorF* velocity ) +{ + mSources.push_back( source ); + + if ( transform ) + source->setTransform( *transform ); + + if ( velocity ) + source->setVelocity( *velocity ); + + const U32 channel = source->getChannel(); + const F32 volume = getChannelVolume( channel ) * mMasterVolume; + source->_setModulativeVolume( volume ); + + // Update the stats. + mStatNumSources = mSources.size(); +} + +void SFXSystem::_onRemoveSource( SFXSource* source ) +{ + SFXSourceVector::iterator iter = find( mSources.begin(), mSources.end(), source ); + AssertFatal( iter != mSources.end(), "Got unknown source!" ); + mSources.erase_fast( iter ); + + // Check if it was a play once source... + iter = find( mPlayOnceSources.begin(), mPlayOnceSources.end(), source ); + if ( iter != mPlayOnceSources.end() ) + mPlayOnceSources.erase_fast( iter ); + + // Update the stats. + mStatNumSources = mSources.size(); +} + +SFXBuffer* SFXSystem::_createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + // The buffers are created by the active + // device... without one we cannot do anything. + if ( !mDevice ) + return NULL; + + // Some sanity checking for streaming. If the stream isn't at least three packets + // in size given the current settings in "description", then turn off streaming. + // The device code *will* mess up if the stream input fails to match certain metrics. + // Just disabling streaming when it doesn't make sense is easier than complicating the + // device logic to account for bad metrics. + + bool streamFlag = description->mIsStreaming; + if( description->mIsStreaming + && stream->getDuration() < description->mStreamPacketSize * 1000 * SFXInternal::SFXAsyncQueue::DEFAULT_STREAM_QUEUE_LENGTH ) + description->mIsStreaming = false; + + SFXBuffer* buffer = mDevice->createBuffer( stream, description ); + + description->mIsStreaming = streamFlag; // restore in case we stomped it + return buffer; +} + +SFXBuffer* SFXSystem::_createBuffer( const String& filename, SFXDescription* description ) +{ + if( !mDevice ) + return NULL; + + return mDevice->createBuffer( filename, description ); +} + +SFXSource* SFXSystem::playOnce( SFXProfile *profile, + const MatrixF *transform, + const VectorF *velocity ) +{ + // We sometimes get null profiles... nothing to play without a profile! + if ( !profile ) + return NULL; + + SFXSource *source = createSource( profile, transform, velocity ); + if ( source ) + { + mPlayOnceSources.push_back( source ); + source->play(); + } + + return source; +} + +void SFXSystem::stopAll( S32 channel ) +{ + AssertFatal( channel < 0 || channel < NumChannels, "Got an invalid channel!" ); + + // Go thru the sources and stop them. + SFXSourceVector::iterator iter = mSources.begin(); + for ( ; iter != mSources.end(); iter++ ) + { + SFXSource* source = *iter; + if ( channel < 0 || source->getChannel() == channel ) + source->stop(); + } +} + +void SFXSystem::setMasterVolume( F32 volume ) +{ + mMasterVolume = mClampF( volume, 0, 1 ); + + // Go thru the sources and update the modulative volume. + SFXSourceVector::iterator iter = mSources.begin(); + for ( ; iter != mSources.end(); iter++ ) + { + SFXSource* source = *iter; + U32 channel = source->getChannel(); + F32 volume = getChannelVolume( channel ) * mMasterVolume; + source->_setModulativeVolume( volume ); + } +} + +F32 SFXSystem::getChannelVolume( U32 channel ) const +{ + AssertFatal( channel < NumChannels, "Got an invalid channel!" ); + return mChannelVolume[ channel ]; +} + +void SFXSystem::setChannelVolume( U32 channel, F32 volume ) +{ + AssertFatal( channel < NumChannels, "Got an invalid channel!" ); + + volume = mClampF( volume, 0, 1 ); + mChannelVolume[ channel ] = volume; + + // Scale it by the master volume. + volume *= mMasterVolume; + + // Go thru the sources and update the modulative volume. + SFXSourceVector::iterator iter = mSources.begin(); + for ( ; iter != mSources.end(); iter++ ) + { + SFXSource* source = *iter; + if ( source->getChannel() == channel ) + source->_setModulativeVolume( volume ); + } +} + +void SFXSystem::_update() +{ + PROFILE_SCOPE( SFXSystem_Update ); + + getEventSignal().trigger( SFXSystemEvent_Update ); + + // Every four system ticks. + const U32 SOURCE_UPDATE_MS = TickMs * 4; + + // The update of the sources can be a bit expensive + // and it does not need to be updated every frame. + const U32 time = Platform::getVirtualMilliseconds(); + const U32 elapsed = time - mLastTime; + if ( elapsed >= SOURCE_UPDATE_MS ) + { + _updateSources(); + mLastTime = time; + } + + // If we have a device then update it. + if ( mDevice ) + mDevice->update( mListener ); + + // Update some stats. + mStatNumSources = mSources.size(); +} + +void SFXSystem::_updateSources() +{ + PROFILE_SCOPE( SFXSystem_UpdateSources ); + + // Check the status of the sources here once. + mStatNumPlaying = 0; + SFXSourceVector::iterator iter = mSources.begin(); + for ( ; iter != mSources.end(); ++iter ) + { + ( *iter )->_update(); + if ( (*iter)->getStatus() == SFXStatusPlaying ) + ++mStatNumPlaying; + } + + // First check to see if any play once sources have + // finished playback... delete them. + iter = mPlayOnceSources.begin(); + for ( ; iter != mPlayOnceSources.end(); ) + { + SFXSource* source = *iter; + + if ( source->getLastStatus() == SFXStatusStopped ) + { + int index = iter - mPlayOnceSources.begin(); + + // Erase it from the vector first, so that onRemoveSource + // doesn't do it during cleanup and screw up our loop here! + mPlayOnceSources.erase_fast( iter ); + source->deleteObject(); + + iter = mPlayOnceSources.begin() + index; + continue; + } + + iter++; + } + + // Reassign buffers to the sources. + _assignVoices(); +} + +void SFXSystem::_assignVoices() +{ + PROFILE_SCOPE( SFXSystem_AssignVoices ); + + mStatNumVoices = 0; + mStatNumCulled = 0; + + // If we have no device then we have + // nothing more to do. + if ( !mDevice ) + return; + + // Now let the listener prioritize the sounds for us + // before we go off and assign buffers. + mListener.sortSources( mSources ); + + // We now make sure that the sources closest to the + // listener, the ones at the top of the source list, + // have a device buffer to play thru. + mStatNumCulled = 0; + SFXSourceVector::iterator iter = mSources.begin(); + for ( ; iter != mSources.end(); ++iter ) + { + SFXSource* source = *iter; + + // Non playing sources (paused or stopped) are at the + // end of the vector, so when i encounter one i know + // that nothing else in the vector needs buffer assignment. + if( !source->isPlaying() ) + break; + + // If the source is outside it's max range we can + // skip it as well, so that we don't waste cycles + // setting up a buffer for something we won't hear. + if( source->getAttenuatedVolume() <= 0.0f ) + { + mStatNumCulled++; + continue; + } + + // If the source has a voice then we can skip it. + if( source->hasVoice() ) + continue; + + // Ok let the device try to assign a new voice for + // this source... this may fail if we're out of voices. + if( source->_allocVoice( mDevice ) ) + continue; + + // The device couldn't assign a new voice, so we look for + // the last source in the list with a voice and free it. + SFXSourceVector::iterator hijack = mSources.end() - 1; + for( ; hijack != iter; hijack-- ) + { + if( ( *hijack )->hasVoice() + && ( *hijack )->_releaseVoice() ) + break; + } + + // Ok try to assign a voice once again! + if( source->_allocVoice( mDevice ) ) + continue; + + // If the source still doesn't have a buffer... well + // tough cookies. It just cannot be heard yet, maybe + // it can in the next update. + mStatNumCulled++; + } + + // Update the buffer count stat. + mStatNumVoices = mDevice->getVoiceCount(); +} + +void SFXSystem::setDistanceModel( SFXDistanceModel model ) +{ + mDistanceModel = model; + if( mDevice ) + mDevice->setDistanceModel( model ); +} + +void SFXSystem::setDopplerFactor( F32 factor ) +{ + mDopplerFactor = factor; + if( mDevice ) + mDevice->setDopplerFactor( factor ); +} + +void SFXSystem::setRolloffFactor( F32 factor ) +{ + mRolloffFactor = factor; + if( mDevice ) + mDevice->setRolloffFactor( factor ); +} + +void SFXSystem::dumpSources() +{ + for( U32 i = 0; i < mSources.size(); ++ i ) + { + SFXSource* source = mSources[ i ]; + + bool isPlayOnce = false; + for( U32 j = 0; j < mPlayOnceSources.size(); ++ j ) + if( mPlayOnceSources[ j ] == source ) + { + isPlayOnce = true; + break; + } + + String fileName; + SFXProfile* profile = source->getProfile(); + if( profile ) + fileName = profile->getFileName(); + + Con::printf( "%5i: status=%s, blocked=%s, virtual=%s, looping=%s, 3d=%s, channel=%i, position=%i, playOnce=%s, streaming=%s, hasVoice=%s, file='%s'", + source->getId(), + source->isPlaying() + ? "playing" + : source->isPaused() + ? "paused" + : source->isStopped() + ? "stopped" + : "unknown", + source->isBlocked() ? "1" : "0", + source->isVirtualized() ? "1" : "0", + source->isLooping() ? "1" : "0", + source->is3d() ? "1" : "0", + source->getChannel(), + source->getPosition(), + isPlayOnce ? "1" : "0", + source->isStreaming() ? "1" : "0", + source->hasVoice() ? "1" : "0", + fileName.c_str() + ); + } +} + +ConsoleFunctionGroupBegin( SFX, + "Functions dealing with the SFX audio layer." ); + +ConsoleFunction( sfxGetAvailableDevices, const char*, 1, 1, + "Returns a list of available devices in the form:\n\n" + "provider1 [tab] device1 [tab] hasHardware1 [tab] maxBuffers1 [nl] provider2 ... etc." ) +{ + char* deviceList = Con::getReturnBuffer( 2048 ); + deviceList[0] = 0; + + SFXProvider* provider = SFXProvider::getFirstProvider(); + while ( provider ) + { + // List the devices in this provider. + const SFXDeviceInfoVector& deviceInfo = provider->getDeviceInfo(); + for ( S32 d=0; d < deviceInfo.size(); d++ ) + { + const SFXDeviceInfo* info = deviceInfo[d]; + dStrcat( deviceList, provider->getName() ); + dStrcat( deviceList, "\t" ); + dStrcat( deviceList, info->name ); + dStrcat( deviceList, "\t" ); + dStrcat( deviceList, info->hasHardware ? "1" : "0" ); + dStrcat( deviceList, "\t" ); + dStrcat( deviceList, Con::getIntArg( info->maxBuffers ) ); + dStrcat( deviceList, "\n" ); + } + + provider = provider->getNextProvider(); + } + + return deviceList; +} + +ConsoleFunction( sfxCreateDevice, bool, 5, 5, + "sfxCreateDevice( string provider, string device, bool useHardware, S32 maxBuffers )\n" + "Initializes the requested device. This must be called successfully before any sounds will be heard.\n" + "@param provider The provider name.\n" + "@param device The device name.\n" + "@param useHardware A boolean which toggles the use of hardware processing when available.\n" + "@param maxBuffers The maximum buffers for this device to use or -1 for the device to pick its own reasonable default.") +{ + return SFX->createDevice( argv[1], argv[2], dAtob( argv[3] ), dAtoi( argv[4] ), true ); +} + +ConsoleFunction( sfxDeleteDevice, void, 1, 1, + "Destroys the currently initialized device. Sounds will still play, but not be heard.") +{ + SFX->deleteDevice(); +} + +ConsoleFunction( sfxGetDeviceInfo, const char*, 1, 1, + "Returns a tab delimited string containing information on the current device." ) +{ + String deviceInfo = SFX->getDeviceInfoString(); + if( deviceInfo.isEmpty() ) + return NULL; + + U32 size = deviceInfo.size(); + char* buffer = Con::getReturnBuffer( size ); + dMemcpy( buffer, deviceInfo.c_str(), size ); + + return buffer; +} + +ConsoleFunction( sfxPlay, S32, 2, 5, "sfxPlay( source )\n" + "sfxPlay( profile, )\n" ) +{ + if ( argc == 2 ) + { + SFXSource* source = dynamic_cast( Sim::findObject( argv[1] ) ); + if ( source ) + { + source->play(); + return source->getId(); + } + } + + SFXProfile *profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if ( !profile ) + { + Con::printf( "Unable to locate sfx profile '%s'", argv[1] ); + return 0; + } + + Point3F pos(0.f, 0.f, 0.f); + if ( argc == 3 ) + dSscanf( argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z ); + else if(argc == 5) + pos.set( dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]) ); + + MatrixF transform; + transform.set( EulerF(0,0,0), pos ); + + SFXSource* source = SFX->playOnce( profile, &transform ); + if ( source ) + return source->getId(); + + return 0; +} + + +ConsoleFunction( sfxCreateSource, S32, 2, 6, + "sfxCreateSource(profile)\n" + "sfxCreateSource(profile, x,y,z)\n" + "sfxCreateSource(description, filename)\n" + "sfxCreateSource(description, filename, x,y,z)\n" + "\n" + "Creates a new paused sound source using a profile or a description " + "and filename. The return value is the source which must be " + "released by delete()." ) +{ + SFXDescription* description = NULL; + SFXProfile* profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if ( !profile ) + { + description = dynamic_cast( Sim::findObject( argv[1] ) ); + if ( !description ) + { + Con::printf( "Unable to locate sound profile/description '%s'", argv[1] ); + return 0; + } + } + + SFXSource* source = NULL; + + if ( profile ) + { + if ( argc == 2 ) + { + source = SFX->createSource( profile ); + } + else + { + MatrixF transform; + transform.set( EulerF(0,0,0), Point3F( dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4])) ); + source = SFX->createSource( profile, &transform ); + } + } + else if ( description ) + { + SFXProfile* tempProfile = new SFXProfile( description, StringTable->insert( argv[2] ), true ); + if( !tempProfile->registerObject() ) + { + Con::errorf( "sfxCreateSource - unable to create profile" ); + delete tempProfile; + } + else + { + if ( argc == 3 ) + { + source = SFX->createSource( tempProfile ); + } + else + { + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) )); + source = SFX->createSource( tempProfile, &transform ); + } + + tempProfile->setAutoDelete( true ); + } + } + + if ( source ) + return source->getId(); + + return 0; +} + +ConsoleFunction( sfxPlayOnce, S32, 2, 6, + "sfxPlayOnce(profile)\n" + "sfxPlayOnce(profile, x,y,z)\n" + "sfxPlayOnce(description, filename)\n" + "sfxPlayOnce(description, filename, x,y,z)\n" + "\n" + "Creates a new sound source using a profile or a description " + "and filename and plays it once. Once playback is finished the " + "source is deleted. The return value is the temporary source id." ) +{ + SFXDescription* description = NULL; + SFXProfile* profile = dynamic_cast( Sim::findObject( argv[1] ) ); + if ( !profile ) + { + description = dynamic_cast( Sim::findObject( argv[1] ) ); + if ( !description ) + { + Con::errorf( "sfxPlayOnce - Unable to locate sound profile/description '%s'", argv[1] ); + return 0; + } + } + + SFXSource* source = NULL; + + if ( profile ) + { + if ( argc == 2 ) + source = SFX->playOnce( profile ); + else + { + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) )); + source = SFX->playOnce( profile, &transform ); + } + } + + else if ( description ) + { + SFXProfile* tempProfile = new SFXProfile( description, StringTable->insert( argv[2] ), true ); + if( !tempProfile->registerObject() ) + { + Con::errorf( "sfxPlayOnce - unable to create profile" ); + delete tempProfile; + } + else + { + if ( argc == 3 ) + source = SFX->playOnce( tempProfile ); + else + { + MatrixF transform; + transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) )); + source = SFX->playOnce( tempProfile, &transform ); + } + + // Set profile to auto-delete when SFXSource releases its reference. + // Also add to root group so the profile will get deleted when the + // Sim system is shut down before the SFXSource has played out. + + tempProfile->setAutoDelete( true ); + Sim::getRootGroup()->addObject( tempProfile ); + } + } + + if ( source ) + return source->getId(); + + return 0; +} + +ConsoleFunction(sfxStop, void, 2, 2, "(S32 id): stop a source, equivalent to id.stop()") +{ + S32 id = dAtoi(argv[1]); + SFXSource * obj; + if (Sim::findObject(id, obj)) + obj->stop(); +} + + +ConsoleFunction(sfxStopAll, void, 1, 2, "(S32 channel = -1)\n\n" + "@param channel The optional channel index of which sources to stop.\n" + "@return The volume of the channel.") +{ + U32 channel = -1; + + if ( argc > 1 ) + { + channel = dAtoi( argv[1] ); + + if ( channel >= SFXSystem::NumChannels ) + { + Con::errorf( ConsoleLogEntry::General, "sfxStopAll: invalid channel '%d'", dAtoi( argv[1] ) ); + return; + } + } + + SFX->stopAll( channel ); +} + +ConsoleFunction(sfxGetChannelVolume, F32, 2, 2, "(S32 channel)\n\n" + "@param channel The channel index to fetch the volume from.\n" + "@return The volume of the channel.") +{ + U32 channel = dAtoi( argv[1] ); + + if ( channel >= SFXSystem::NumChannels ) + { + Con::errorf( ConsoleLogEntry::General, "sfxGetChannelVolume: invalid channel '%d'", dAtoi( argv[1] ) ); + return 0.0f; + } + + return SFX->getChannelVolume( channel ); +} + +ConsoleFunction(sfxSetChannelVolume, bool, 3, 3, "(S32 channel, F32 volume)\n\n" + "@param channel The channel index to set volume on.\n" + "@param volume New 0 to 1 channel volume." + ) +{ + U32 channel = dAtoi( argv[1] ); + + F32 volume = mClampF( dAtof( argv[2] ), 0, 1 ); + + if ( channel >= SFXSystem::NumChannels ) + { + Con::errorf( ConsoleLogEntry::General, "sfxSetChannelVolume: invalid channel '%d'", dAtoi( argv[1] ) ); + return false; + } + + SFX->setChannelVolume( channel, volume ); + return true; +} + +ConsoleFunction(sfxGetMasterVolume, F32, 1, 1, "()\n\n" + "@return The sound system master volume." ) +{ + return SFX->getMasterVolume(); +} + +ConsoleFunction(sfxSetMasterVolume, void, 2, 2, "(F32 volume)\n\n" + "@param volume The new 0 to 1 sound system master volume." ) +{ + F32 volume = mClampF( dAtof( argv[1] ), 0, 1 ); + SFX->setMasterVolume( volume ); +} + +ConsoleFunction( sfxGetDistanceModel, const char*, 1, 1, "()" ) +{ + switch( SFX->getDistanceModel() ) + { + case SFXDistanceModelLinear: + return "linear"; + + case SFXDistanceModelLogarithmic: + return "logarithmic"; + } + + return ""; +} + +ConsoleFunction( sfxSetDistanceModel, void, 2, 2, "( string model ) - Set the curve model used for distance attenuation of 3D sounds." ) +{ + if( !argv[ 1 ] ) + return; + + if( dStricmp( argv[ 1 ], "linear" ) == 0 ) + SFX->setDistanceModel( SFXDistanceModelLinear ); + else if( dStricmp( argv[ 1 ], "logarithmic" ) == 0 ) + SFX->setDistanceModel( SFXDistanceModelLogarithmic ); + else + Con::errorf( "sfxSetDistanceModel - unknown model '%s'", argv[ 1 ] ); +} + +ConsoleFunction( sfxGetDopplerFactor, F32, 1, 1, "()" ) +{ + return SFX->getDopplerFactor(); +} + +ConsoleFunction( sfxSetDopplerFactor, void, 2, 2, "( F32 value )" ) +{ + if( !argv[ 1 ] ) + return; + + F32 val = dAtof( argv[ 1 ] ); + if( val < 0.0f ) + { + Con::errorf( "sfxSetDopplerFactor - factor must be >0" ); + return; + } + + SFX->setDopplerFactor( val ); +} + +ConsoleFunction( sfxGetRolloffFactor, F32, 1, 1, "()" ) +{ + return SFX->getRolloffFactor(); +} + +ConsoleFunction( sfxSetRolloffFactor, void, 2, 2, "( F32 value )" ) +{ + if( !argv[ 1 ] ) + return; + + F32 val = dAtof( argv[ 1 ] ); + if( val < 0.0f ) + { + Con::errorf( "sfxSetRolloffFactor - factor must be >0" ); + return; + } + + SFX->setRolloffFactor( val ); +} + +ConsoleFunction( sfxDumpSources, void, 1, 1, "() - Dump information about all current SFXSource instances" ) +{ + SFX->dumpSources(); +} + +ConsoleFunctionGroupEnd( SFX ); + diff --git a/sfx/sfxSystem.h b/sfx/sfxSystem.h new file mode 100644 index 0000000..054f245 --- /dev/null +++ b/sfx/sfxSystem.h @@ -0,0 +1,395 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXSYSTEM_H_ +#define _SFXSYSTEM_H_ + +#ifndef _SFXCOMMON_H_ + #include "sfx/sfxCommon.h" +#endif +#ifndef _SFXLISTENER_H_ + #include "sfx/sfxListener.h" +#endif +#ifndef _TSIGNAL_H_ + #include "core/util/tSignal.h" +#endif +#ifndef _TVECTOR_H_ + #include "core/util/tVector.h" +#endif +#ifndef _THREADSAFEREFCOUNT_H_ + #include "platform/threads/threadSafeRefCount.h" +#endif + + +class SFXStream; +class SFXSource; +class SFXBuffer; +class SFXDescription; +class SFXDevice; +class SFXProfile; + + +/// SFX system events that can be received notifications on. +enum SFXSystemEventType +{ + /// SFX is being updated. + SFXSystemEvent_Update, + + /// New SFXDevice has been created. + SFXSystemEvent_CreateDevice, + + /// SFXDevice is about to be destroyed. + SFXSystemEvent_DestroyDevice, +}; + + +/// This class provides access to the sound system. +/// +/// There are a few script preferences that are used by +/// the sound providers. +/// +/// $pref::SFX::frequency - This is the playback frequency +/// for the primary sound buffer used for mixing. Although +/// most providers will reformat on the fly, for best quality +/// and performance match your sound files to this setting. +/// +/// $pref::SFX::bitrate - This is the playback bitrate for +/// the primary sound buffer used for mixing. Although most +/// providers will reformat on the fly, for best quality +/// and performance match your sound files to this setting. +/// +class SFXSystem +{ + friend class SFXSource; // _assignVoices/_onRemoveSource. + friend class SFXProfile; // _createBuffer. + + public: + + enum + { + /// The number of volume channels available + /// in the system. + NumChannels = 32, + + /// The number of bits needed to write + /// a channel value to the network stream. + NumChannelBits = 6, + }; + + typedef Signal< void( SFXSystemEventType event ) > EventSignalType; + typedef Vector< SFXSource* > SFXSourceVector; + + protected: + + /// The one and only instance of the SFXSystem. + static SFXSystem* smSingleton; + + /// The protected constructor. + /// + /// @see SFXSystem::init() + /// + SFXSystem(); + + /// The non-virtual destructor. You shouldn't + /// ever need to overload this class. + ~SFXSystem(); + + /// The current output sound device initialized + /// and ready to play back. + SFXDevice* mDevice; + + /// This contains all the sources currently created + /// in the system. This includes all the play once + /// sources below as well. + SFXSourceVector mSources; + + /// This is used to keep track of play once sources + /// that must be released when they stop playing. + SFXSourceVector mPlayOnceSources; + + /// The position and orientation of the listener. + SFXListener mListener; + + /// The last time the system got an update. + U32 mLastTime; + + /// The channel volume which controls the volume of + /// all sources assigned to that channel. + F32 mChannelVolume[ NumChannels ]; + + /// The overall volume for all sounds in the system. + F32 mMasterVolume; + + /// The distance model used for rolloff curve computation on 3D sounds. + SFXDistanceModel mDistanceModel; + + /// The current doppler scale factor. + F32 mDopplerFactor; + + /// The current curve rolloff factor. + F32 mRolloffFactor; + + /// + EventSignalType mEventSignal; + + /// @name Stats + /// + /// Stats reported back to the console for tracking performance. + /// + /// @{ + + S32 mStatNumSources; + S32 mStatNumPlaying; + S32 mStatNumCulled; + S32 mStatNumVoices; + + /// @} + + /// Called to reprioritize and reassign buffers as + /// sources change state, volumes are adjusted, and + /// the listener moves around. + /// + /// @see SFXSource::_update() + /// + void _updateSources(); + + /// This called to reprioritize and reassign + /// voices to sources. + void _assignVoices(); + + /// This is called from the source to + /// properly clean itself up. + /// + /// @see SFXSource::onRemove() + /// + void _onRemoveSource( SFXSource* source ); + + /// Called from SFXProfile to create a device specific + /// sound buffer used in conjunction with a voice in playback. + SFXBuffer* _createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + + /// Load file directly through SFXDevice. Depends on + /// availability with selected SFXDevice. + /// + /// @return Return new buffer or NULL. + SFXBuffer* _createBuffer( const String& filename, SFXDescription* description ); + + /// Helper function to initialize and register a source + /// after creation. + void _addSource( SFXSource* source, + const MatrixF* transform = NULL, + const VectorF* velocity = NULL ); + + public: + + /// Returns the one an only instance of the SFXSystem + /// unless it hasn't been initialized or its been disabled + /// in your build. + /// + /// For convienence you can use the SFX-> macro as well. + /// + /// @see SFXSystem::init() + /// @see SFX + /// + static SFXSystem* getSingleton() { return smSingleton; } + + /// This is called from initialization to prepare the + /// sound system singleton. This also includes registering + /// common resource types and initializing available sound + /// providers. + /// + static void init(); + + /// This is called after Sim::shutdown() in shutdownLibraries() + /// to free the sound system singlton. After this the SFX + /// singleton is null and any call to it will crash. + /// + static void destroy(); + + /// This is only public so that it can be called by + /// the game update loop. It updates the current + /// device and all sources. + /// + void _update(); + + /// This initializes a new device. + /// + /// @param providerName The name of the provider. + /// @param deviceName The name of the provider device. + /// @param useHardware Toggles the use of hardware processing when available. + /// @param maxBuffers The maximum buffers for this device to use or -1 + /// for the device to pick its own reasonable default. + /// @param changeDevice Allows this to change the current device to a new one + /// @return Returns true if the device was created. + /// + bool createDevice( const String& providerName, + const String& deviceName, + bool useHardware, + S32 maxBuffers, + bool changeDevice = false); + + + /// Returns the current device information or NULL if no + /// device is present. The information string is in the + /// following format: + /// + /// Provider Name\tDevice Name\tUse Hardware\tMax Buffers + /// + String getDeviceInfoString(); + + /// This destroys the current device. All sources loose their + /// playback buffers, but otherwise continue to function. + /// + void deleteDevice(); + + /// Returns true if a device is allocated. + /// + bool hasDevice() const { return mDevice != NULL; } + + /// Used to create new sound sources from a sound profile. The + /// returned source is in a stopped state and ready for playback. + /// Use the SFX_DELETE macro to free the source when your done. + /// + /// @param profile The sound profile for the created source. + /// @param transform The optional transform if creating a 3D source. + /// @param velocity The optional doppler velocity if creating a 3D source. + /// + /// @return The sound source or NULL if an error occured. + /// + SFXSource* createSource( SFXProfile* profile, + const MatrixF* transform = NULL, + const VectorF* velocity = NULL ); + + /// Used to create a streaming sound source from a user supplied + /// stream object. + /// + /// It is only intended for memory based streams. For sound file + /// streaming use createSource() with a streaming SFXProfile. + /// + /// Use the SFX_DELETE macro to free the source when your done. + /// + /// @param stream The stream used to create the sound buffer. It + /// must exist for the lifetime of the source and will + /// have its reference count decremented when the source + /// is destroyed. + /// + /// @param description The sound description to apply to the source. + /// + /// @return The sound source or NULL if an error occured. + /// + SFXSource* createSourceFromStream( const ThreadSafeRef< SFXStream >& stream, + SFXDescription* description ); + + /// Creates a source which when it finishes playing will auto delete + /// itself. Be aware that the returned SFXSource pointer should only + /// be used for error checking or immediate setting changes. It may + /// be deleted as soon as the next system tick. + /// + /// @param profile The sound profile for the created source. + /// @param transform The optional transform if creating a 3D source. + /// @param velocity The optional doppler velocity if creating a 3D source. + /// + /// @return The sound source or NULL if an error occured. + /// + SFXSource* playOnce( SFXProfile *profile, + const MatrixF *transform = NULL, + const VectorF *velocity = NULL ); + + /// Returns the one and only listener object. + SFXListener& getListener() { return mListener; } + + /// Stops all the sounds in a particular channel or across + /// all channels if the channel is -1. + /// + /// @param channel The channel index less than NumChannels or -1. + /// + /// @see NumChannels + /// + void stopAll( S32 channel = -1 ); + + /// Returns the volume for the specified sound channel. + /// + /// @param channel The channel index less than NumChannels. + /// + /// @return The channel volume. + /// + /// @see NumChannels + /// + F32 getChannelVolume( U32 channel ) const; + + /// Sets the volume on the specified sound channel, changing + /// the volume on all sources in that channel. + /// + /// @param channel The channel index less than NumChannels. + /// @param volume The channel volume to set. + /// + /// @see NumChannels + /// + void setChannelVolume( U32 channel, F32 volume ); + + /// Returns the system master volume level. + /// + /// @return The channel volume. + /// + F32 getMasterVolume() const { return mMasterVolume; } + + /// Sets the master volume level, changing the + /// volume of all sources. + /// + /// @param volume The channel volume to set. + /// + void setMasterVolume( F32 volume ); + + /// Returns the curve model currently used distance attenuation of positional sounds. + /// + SFXDistanceModel getDistanceModel() const { return mDistanceModel; } + + /// + void setDistanceModel( SFXDistanceModel model ); + + /// + F32 getDopplerFactor() const { return mDopplerFactor; } + + /// + void setDopplerFactor( F32 factor ); + + /// + F32 getRolloffFactor() const { return mRolloffFactor; } + + /// + void setRolloffFactor( F32 factor ); + + /// Dump information about all current SFXSources to the console. + /// + void dumpSources(); + + /// + EventSignalType& getEventSignal() { return mEventSignal; } +}; + + +/// Less verbose macro for accessing the SFX singleton. This +/// should be the prefered method for accessing the system. +/// +/// @see SFXSystem +/// @see SFXSystem::getSingleton() +/// +#define SFX SFXSystem::getSingleton() + + +/// A simple macro to automate the deletion of a source. +/// +/// @see SFXSource +/// +#undef SFX_DELETE +#define SFX_DELETE( source ) \ + if( source ) \ + { \ + source->deleteObject(); \ + source = NULL; \ + } \ + + +#endif // _SFXSYSTEM_H_ diff --git a/sfx/sfxVoice.cpp b/sfx/sfxVoice.cpp new file mode 100644 index 0000000..0b32ea4 --- /dev/null +++ b/sfx/sfxVoice.cpp @@ -0,0 +1,243 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/sfxVoice.h" +#include "sfx/sfxBuffer.h" +#include "sfx/sfxInternal.h" +#include "console/console.h" + + +Signal< void( SFXVoice* voice ) > SFXVoice::smVoiceDestroyedSignal; + + +//----------------------------------------------------------------------------- + +SFXVoice::SFXVoice( SFXBuffer* buffer ) + : mBuffer( buffer ), + mStatus( SFXStatusNull ), + mOffset( 0 ) +{ +} + +//----------------------------------------------------------------------------- + +SFXVoice::~SFXVoice() +{ + if( mBuffer ) + mBuffer->mOnStatusChange.remove( this, &SFXVoice::_onBufferStatusChange ); + + smVoiceDestroyedSignal.trigger( this ); +} + +//----------------------------------------------------------------------------- + +void SFXVoice::_attachToBuffer() +{ + using namespace SFXInternal; + + // If the buffer is unique, attach us as its unique voice. + + if( mBuffer->isUnique() ) + { + AssertFatal( !mBuffer->mUniqueVoice, + "SFXVoice::SFXVoice - streaming buffer already is assigned a voice" ); + + mBuffer->mUniqueVoice = this; + } + + mBuffer->mOnStatusChange.notify( this, &SFXVoice::_onBufferStatusChange ); +} + +//----------------------------------------------------------------------------- + +void SFXVoice::_onBufferStatusChange( SFXBuffer* buffer, SFXBuffer::EStatus newStatus ) +{ + AssertFatal( buffer == mBuffer, "SFXVoice::_onBufferStatusChange() - got an invalid buffer" ); + + switch( newStatus ) + { + case SFXBuffer::STATUS_Loading: + if( mStatus != SFXStatusNull ) + _stop(); + mStatus = SFXStatusBlocked; + break; + + case SFXBuffer::STATUS_AtEnd: + _stop(); + mStatus = SFXStatusStopped; + mOffset = 0; + break; + + case SFXBuffer::STATUS_Blocked: + _pause(); + mStatus = SFXStatusBlocked; + break; + + case SFXBuffer::STATUS_Ready: + if( mStatus == SFXStatusBlocked ) + { + // Get the playback going again. + + _play(); + mStatus = SFXStatusPlaying; + } + break; + + case SFXBuffer::STATUS_Null: + AssertFatal(false, "Buffer changed to invalid NULL status"); + break; + } +} + +//----------------------------------------------------------------------------- + +SFXStatus SFXVoice::getStatus() const +{ + if( mStatus == SFXStatusPlaying + && !mBuffer->isReady() ) + return SFXStatusBlocked; + + // Detect when the device has finished playback. + if( mStatus == SFXStatusPlaying + && !mBuffer->isStreaming() + && _status() == SFXStatusStopped ) + mStatus = SFXStatusStopped; + + return mStatus; +} + +//----------------------------------------------------------------------------- + +void SFXVoice::play( bool looping ) +{ + AssertFatal( mBuffer != NULL, "SFXVoice::play() - no buffer" ); + using namespace SFXInternal; + + // For streaming, check whether we have played previously. + // If so, reset the buffer's stream. + + if( mStatus == SFXStatusStopped + && mBuffer->isStreaming() ) + setPosition( 0 ); + + if( mBuffer->isReady() ) + { + _play(); + mStatus = SFXStatusPlaying; + } + else + mStatus = SFXStatusBlocked; +} + +//----------------------------------------------------------------------------- + +void SFXVoice::pause() +{ + _pause(); + mStatus = SFXStatusPaused; +} + +//----------------------------------------------------------------------------- + +void SFXVoice::stop() +{ + _stop(); + mStatus = SFXStatusStopped; +} + +//----------------------------------------------------------------------------- + +U32 SFXVoice::getPosition() const +{ + // It depends on the device if and when it will return a count of the total samples + // played so far. With streaming buffers, all devices will do that. With non-streaming + // buffers, some may do for looping voices thus returning a number that exceeds the actual + // source stream size. So, clamp things into range here and also take care of any offsetting + // resulting from a setPosition() call + + U32 pos = _tell() + mOffset; + const U32 numStreamSamples = mBuffer->getFormat().getSampleCount( mBuffer->getDuration() ); + + if( mBuffer->mIsLooping ) + pos %= numStreamSamples; + else if( pos > numStreamSamples ) + { + // Ensure we never report out-of-range positions even if the device does. + + pos = numStreamSamples; + } + + return pos; +} + +//----------------------------------------------------------------------------- + +void SFXVoice::setPosition( U32 inSample ) +{ + const U32 sample = getMin( inSample, mBuffer->getFormat().getSampleCount( mBuffer->getDuration() ) - 1 ); + + if( !mBuffer->isStreaming() ) + { + // Non-streaming sound. Just seek in the device buffer. + + _seek( sample ); + } + else + { + ThreadSafeRef< SFXBuffer::AsyncState > oldState = mBuffer->mAsyncState; + AssertFatal( oldState != NULL, + "SFXVoice::setPosition() - streaming buffer must have valid async state" ); + + // Rather than messing up the async code by adding repositioning (which + // further complicates synchronizing the various parts), just construct + // a complete new AsyncState and discard the old one. The only problem + // here is the stateful sound streams. We can't issue a new packet as long + // as we aren't sure there's no request pending, so we just clone the stream + // and leave the old one to the old AsyncState. + + ThreadSafeRef< SFXStream > sfxStream = oldState->mStream->getSourceStream()->clone(); + if( sfxStream != NULL ) + { + IPositionable< U32 >* sfxPositionable = dynamic_cast< IPositionable< U32 >* >( sfxStream.ptr() ); + if( sfxPositionable ) + { + sfxPositionable->setPosition( sample * sfxStream->getFormat().getBytesPerSample() ); + + ThreadSafeRef< SFXInternal::SFXAsyncStream > newStream = + new SFXInternal::SFXAsyncStream + ( sfxStream, + true, + oldState->mStream->getPacketDuration() / 1000, + oldState->mStream->getReadAhead(), + oldState->mStream->isLooping() ); + newStream->setReadSilenceAtEnd( oldState->mStream->getReadSilenceAtEnd() ); + + AssertFatal( newStream->getPacketSize() == oldState->mStream->getPacketSize(), + "SFXVoice::setPosition() - packet size mismatch with new stream" ); + + ThreadSafeRef< SFXBuffer::AsyncState > newState = + new SFXBuffer::AsyncState( newStream ); + newStream->start(); + + // Switch the states. + + mOffset = sample; + mBuffer->mAsyncState = newState; + + // Stop the old state from reading more data. + + oldState->mStream->stop(); + + // Trigger update. + + SFXInternal::TriggerUpdate(); + } + else + Con::errorf( "SFXVoice::setPosition - could not seek in SFXStream" ); + } + else + Con::errorf( "SFXVoice::setPosition - could not clone SFXStream" ); + } +} diff --git a/sfx/sfxVoice.h b/sfx/sfxVoice.h new file mode 100644 index 0000000..1f9546c --- /dev/null +++ b/sfx/sfxVoice.h @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXVOICE_H_ +#define _SFXVOICE_H_ + +#ifndef _REFBASE_H_ +# include "core/util/refBase.h" +#endif +#ifndef _TSTREAM_H_ +# include "core/stream/tStream.h" +#endif +#ifndef _MPOINT3_H_ +# include "math/mPoint3.h" +#endif +#ifndef _MMATRIX_H_ +# include "math/mMatrix.h" +#endif +#ifndef _SFXBUFFER_H_ +# include "sfx/sfxBuffer.h" +#endif + + +namespace SFXInternal { + class SFXVoiceTimeSource; + class SFXAynscQueue; +} + + +/// The voice interface provides for playback of +/// sound buffers and positioning of 3D sounds. +class SFXVoice : public StrongRefBase, + public IPositionable< U32 > +{ + public: + + typedef void Parent; + + friend class SFXDevice; // _attachToBuffer + friend class SFXInternal::SFXVoiceTimeSource; // _tell + friend class SFXInternal::SFXAsyncQueue; // mOffset + + protected: + + /// + mutable SFXStatus mStatus; + + /// + WeakRefPtr< SFXBuffer > mBuffer; + + /// For streaming voices, this keeps track of play start offset + /// after seeking. + U32 mOffset; + + explicit SFXVoice( SFXBuffer* buffer ); + + /// + virtual SFXStatus _status() const = 0; + + /// Stop playback on the device. + /// @note Called from both the SFX update thread and the main thread. + virtual void _stop() = 0; + + /// Start playback on the device. + /// @note Called from both the SFX update thread and the main thread. + virtual void _play() = 0; + + /// Pause playback on the device. + /// @note Called from both the SFX update thread and the main thread. + virtual void _pause() = 0; + + /// Set the playback cursor on the device. + /// Only used for non-streaming voices. + virtual void _seek( U32 sample ) = 0; + + /// Get the playback cursor on the device. + virtual U32 _tell() const = 0; + + /// + virtual void _onBufferStatusChange( SFXBuffer* buffer, SFXBuffer::EStatus newStatus ); + + /// + void _attachToBuffer(); + + public: + + static Signal< void( SFXVoice* ) > smVoiceDestroyedSignal; + + /// The destructor. + virtual ~SFXVoice(); + + /// + const SFXFormat& getFormat() const { return mBuffer->getFormat(); } + + /// Return the current playback position (in number of samples). + /// + /// @note This value this method returns equals the total number of + /// samples played so far. For looping streams, this will exceed + /// the source stream's duration after the first loop. + virtual U32 getPosition() const; + + /// Sets the playback position to the given sample count. + virtual void setPosition( U32 sample ); + + /// @return the current playback status. + virtual SFXStatus getStatus() const; + + /// Starts playback from the current position. + virtual void play( bool looping ); + + /// Stops playback and moves the position to the start. + virtual void stop(); + + /// Pauses playback. + virtual void pause(); + + /// Sets the position and orientation for a 3d voice. + virtual void setTransform( const MatrixF &transform ) = 0; + + /// Sets the velocity for a 3d voice. + virtual void setVelocity( const VectorF &velocity ) = 0; + + /// Sets the minimum and maximum distances for 3d falloff. + virtual void setMinMaxDistance( F32 min, F32 max ) = 0; + + /// Sets the volume. + virtual void setVolume( F32 volume ) = 0; + + /// Sets the pitch scale. + virtual void setPitch( F32 pitch ) = 0; + + /// Set sound cone of a 3D sound. + /// + /// @param innerAngle Inner cone angle in degrees. + /// @param outerAngle Outer cone angle in degrees. + /// @param outerVolume Outer volume factor. + virtual void setCone( F32 innerAngle, + F32 outerAngle, + F32 outerVolume ) = 0; +}; + +#endif // _SFXVOICE_H_ diff --git a/sfx/xaudio/sfxXAudioBuffer.cpp b/sfx/xaudio/sfxXAudioBuffer.cpp new file mode 100644 index 0000000..12a06a1 --- /dev/null +++ b/sfx/xaudio/sfxXAudioBuffer.cpp @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/xaudio/sfxXAudioBuffer.h" +#include "sfx/xaudio/sfxXAudioVoice.h" + + +SFXXAudioBuffer* SFXXAudioBuffer::create( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + SFXXAudioBuffer *buffer = new SFXXAudioBuffer( stream, description ); + return buffer; +} + +SFXXAudioBuffer::SFXXAudioBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) + : Parent( stream, description ), + mBufferQueue( isStreaming() ? QUEUE_LENGTH : 1 ) +{ +} + +SFXXAudioBuffer::~SFXXAudioBuffer() +{ + if( _getUniqueVoice() != NULL ) + _getUniqueVoice()->_stop(); + + while( !mBufferQueue.isEmpty() ) + { + Buffer buffer = mBufferQueue.popFront(); + destructSingle< SFXInternal::SFXStreamPacket* >( buffer.mPacket ); + } +} + +void SFXXAudioBuffer::write( SFXInternal::SFXStreamPacket* const* packets, U32 num ) +{ + AssertFatal( SFXInternal::isSFXThread(), "SFXXAudioBuffer::write() - not on SFX thread" ); + + using namespace SFXInternal; + + // Unqueue processed packets. + + if( isStreaming() ) + { + EnterCriticalSection( &_getUniqueVoice()->mLock ); + + XAUDIO2_VOICE_STATE state; + _getUniqueVoice()->mXAudioVoice->GetState( &state ); + + U32 numProcessed = mBufferQueue.size() - state.BuffersQueued; + for( U32 i = 0; i < numProcessed; ++ i ) + { + Buffer buffer = mBufferQueue.popFront(); + destructSingle< SFXStreamPacket* >( buffer.mPacket ); + } + + LeaveCriticalSection( &_getUniqueVoice()->mLock ); + } + + // Queue new packets. + + for( U32 i = 0; i < num; ++ i ) + { + SFXStreamPacket* packet = packets[ i ]; + Buffer buffer; + + if( packet->mIsLast ) + buffer.mData.Flags = XAUDIO2_END_OF_STREAM; + + buffer.mPacket = packet; + buffer.mData.AudioBytes = packet->mSizeActual; + buffer.mData.pAudioData = packet->data; + + mBufferQueue.pushBack( buffer ); + + if( isStreaming() ) + { + EnterCriticalSection( &_getUniqueVoice()->mLock ); + _getUniqueVoice()->mXAudioVoice->SubmitSourceBuffer( &buffer.mData ); + LeaveCriticalSection( &_getUniqueVoice()->mLock ); + } + } +} + +void SFXXAudioBuffer::_flush() +{ + AssertFatal( isStreaming(), "SFXXAudioBuffer::_flush() - not a streaming buffer" ); + AssertFatal( SFXInternal::isSFXThread(), "SFXXAudioBuffer::_flush() - not on SFX thread" ); + + _getUniqueVoice()->_stop(); + + while( !mBufferQueue.isEmpty() ) + { + Buffer buffer = mBufferQueue.popFront(); + destructSingle( buffer.mPacket ); + } +} diff --git a/sfx/xaudio/sfxXAudioBuffer.h b/sfx/xaudio/sfxXAudioBuffer.h new file mode 100644 index 0000000..216c29b --- /dev/null +++ b/sfx/xaudio/sfxXAudioBuffer.h @@ -0,0 +1,63 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXXAUDIOBUFFER_H_ +#define _SFXXAUDIOBUFFER_H_ + +#include +#ifndef _SFXINTERNAL_H_ +# include "sfx/sfxInternal.h" +#endif +#ifndef _TFIXEDSIZEDEQUE_H_ +# include "core/util/tFixedSizeDeque.h" +#endif + + +class SFXXAudioBuffer : public SFXBuffer +{ + public: + + typedef SFXBuffer Parent; + + friend class SFXXAudioDevice; + friend class SFXXAudioVoice; + + protected: + + enum { QUEUE_LENGTH = SFXInternal::SFXAsyncQueue::DEFAULT_STREAM_QUEUE_LENGTH + 1 }; + + struct Buffer + { + XAUDIO2_BUFFER mData; + SFXInternal::SFXStreamPacket* mPacket; + + Buffer() + : mPacket( 0 ) + { + dMemset( &mData, 0, sizeof( mData ) ); + } + }; + + typedef FixedSizeDeque< Buffer > QueueType; + + QueueType mBufferQueue; + + SFXXAudioVoice* _getUniqueVoice() { return ( SFXXAudioVoice* ) mUniqueVoice.getPointer(); } + + /// + SFXXAudioBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + virtual ~SFXXAudioBuffer(); + + // SFXBuffer. + virtual void write( SFXInternal::SFXStreamPacket* const* packets, U32 num ); + void _flush(); + + public: + + /// + static SFXXAudioBuffer* create( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); +}; + +#endif // _SFXXAUDIOBUFFER_H_ \ No newline at end of file diff --git a/sfx/xaudio/sfxXAudioDevice.cpp b/sfx/xaudio/sfxXAudioDevice.cpp new file mode 100644 index 0000000..48ca5f0 --- /dev/null +++ b/sfx/xaudio/sfxXAudioDevice.cpp @@ -0,0 +1,226 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sfx/xaudio/sfxXAudioDevice.h" +#include "sfx/sfxListener.h" +#include "platform/async/asyncUpdate.h" +#include "core/stringTable.h" +#include "console/console.h" +#include "core/util/safeRelease.h" +#include "core/tAlgorithm.h" +#include "platform/profiler.h" + + +SFXXAudioDevice::SFXXAudioDevice( SFXProvider* provider, + const String& name, + IXAudio2 *xaudio, + U32 deviceIndex, + U32 speakerChannelMask, + U32 maxBuffers ) + : Parent( name, provider, false, maxBuffers ), + mXAudio( xaudio ), + mMasterVoice( NULL ) +{ + dMemset( &mListener, 0, sizeof( mListener ) ); + + // If mMaxBuffers is negative then use some default value. + // to decide on a good maximum value... or set 8. + // + // TODO: We should change the terminology to voices! + if ( mMaxBuffers < 0 ) + mMaxBuffers = 64; + + // Create the mastering voice. + HRESULT hr = mXAudio->CreateMasteringVoice( &mMasterVoice, + XAUDIO2_DEFAULT_CHANNELS, + XAUDIO2_DEFAULT_SAMPLERATE, + 0, + deviceIndex, + NULL ); + if ( FAILED( hr ) || !mMasterVoice ) + { + Con::errorf( "SFXXAudioDevice - Failed creating master voice!" ); + return; + } + + mMasterVoice->GetVoiceDetails( &mMasterVoiceDetails ); + + // Init X3DAudio. + X3DAudioInitialize( speakerChannelMask, + X3DAUDIO_SPEED_OF_SOUND, + mX3DAudio ); + + // Start the update thread. + + if( !Con::getBoolVariable( "$_forceAllMainThread" ) ) + { + SFXInternal::gUpdateThread = new AsyncUpdateThread + ( "XAudio Update Thread", SFXInternal::gBufferUpdateList ); + SFXInternal::gUpdateThread->start(); + } +} + + +SFXXAudioDevice::~SFXXAudioDevice() +{ + _releaseAllResources(); + + if ( mMasterVoice ) + { + mMasterVoice->DestroyVoice(); + mMasterVoice = NULL; + } + + // Kill the engine. + SAFE_RELEASE( mXAudio ); +} + + +SFXBuffer* SFXXAudioDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) +{ + SFXXAudioBuffer* buffer = SFXXAudioBuffer::create( stream, description ); + if ( !buffer ) + return NULL; + + _addBuffer( buffer ); + return buffer; +} + +SFXVoice* SFXXAudioDevice::createVoice( bool is3D, SFXBuffer *buffer ) +{ + // Don't bother going any further if we've + // exceeded the maximum voices. + if ( mVoices.size() >= mMaxBuffers ) + return NULL; + + AssertFatal( buffer, "SFXXAudioDevice::createVoice() - Got null buffer!" ); + + SFXXAudioBuffer* xaBuffer = dynamic_cast( buffer ); + AssertFatal( xaBuffer, "SFXXAudioDevice::createVoice() - Got bad buffer!" ); + + SFXXAudioVoice* voice = SFXXAudioVoice::create( mXAudio, is3D, xaBuffer ); + if ( !voice ) + return NULL; + + voice->mXAudioDevice = this; + + _addVoice( voice ); + return voice; +} + +void SFXXAudioDevice::_setOutputMatrix( SFXXAudioVoice *voice ) +{ + X3DAUDIO_DSP_SETTINGS dspSettings = {0}; + FLOAT32 matrix[12] = { 0 }; + dspSettings.DstChannelCount = mMasterVoiceDetails.InputChannels; + dspSettings.pMatrixCoefficients = matrix; + + const X3DAUDIO_EMITTER &emitter = voice->getEmitter(); + dspSettings.SrcChannelCount = emitter.ChannelCount; + + // Calculate the output volumes and doppler. + X3DAudioCalculate( mX3DAudio, + &mListener, + &emitter, + X3DAUDIO_CALCULATE_MATRIX | + X3DAUDIO_CALCULATE_DOPPLER, + &dspSettings ); + + voice->mXAudioVoice->SetOutputMatrix( mMasterVoice, + dspSettings.SrcChannelCount, + dspSettings.DstChannelCount, + dspSettings.pMatrixCoefficients, + 4321 ); + + voice->mXAudioVoice->SetFrequencyRatio( dspSettings.DopplerFactor * voice->mPitch, + 4321 ); + + // Commit the changes. + mXAudio->CommitChanges( 4321 ); +} + +void SFXXAudioDevice::update( const SFXListener& listener ) +{ + PROFILE_SCOPE( SFXXAudioDevice_Update ); + + Parent::update( listener ); + + // Get the transform from the listener. + const MatrixF& transform = listener.getTransform(); + transform.getColumn( 3, (Point3F*)&mListener.Position ); + transform.getColumn( 1, (Point3F*)&mListener.OrientFront ); + transform.getColumn( 2, (Point3F*)&mListener.OrientTop ); + + // And the velocity... + const VectorF& velocity = listener.getVelocity(); + mListener.Velocity.x = velocity.x; + mListener.Velocity.y = velocity.y; + mListener.Velocity.z = velocity.z; + + // XAudio and Torque use opposite handedness, so + // flip the z coord to account for that. + mListener.Position.z *= -1.0f; + mListener.OrientFront.z *= -1.0f; + mListener.OrientTop.z *= -1.0f; + mListener.Velocity.z *= -1.0f; + + X3DAUDIO_DSP_SETTINGS dspSettings = {0}; + FLOAT32 matrix[12] = { 0 }; + dspSettings.DstChannelCount = mMasterVoiceDetails.InputChannels; + dspSettings.pMatrixCoefficients = matrix; + + dspSettings.DopplerFactor = mDopplerFactor; + + // Now update the volume and frequency of + // all the active 3D voices. + VoiceVector::iterator voice = mVoices.begin(); + for ( ; voice != mVoices.end(); voice++ ) + { + SFXXAudioVoice* xaVoice = ( SFXXAudioVoice* ) *voice; + + // Skip 2D or stopped voices. + if ( !xaVoice->is3D() || + xaVoice->getStatus() != SFXStatusPlaying ) + continue; + + const X3DAUDIO_EMITTER &emitter = xaVoice->getEmitter(); + dspSettings.SrcChannelCount = emitter.ChannelCount; + + // Calculate the output volumes and doppler. + X3DAudioCalculate( mX3DAudio, + &mListener, + &emitter, + X3DAUDIO_CALCULATE_MATRIX | + X3DAUDIO_CALCULATE_DOPPLER, + &dspSettings ); + + xaVoice->mXAudioVoice->SetOutputMatrix( mMasterVoice, + dspSettings.SrcChannelCount, + dspSettings.DstChannelCount, + dspSettings.pMatrixCoefficients, + 4321 ) ; + + xaVoice->mXAudioVoice->SetFrequencyRatio( dspSettings.DopplerFactor * xaVoice->mPitch, + 4321 ); + } + + // Commit the changes. + mXAudio->CommitChanges( 4321 ); +} + +void SFXXAudioDevice::setDistanceModel( SFXDistanceModel model ) +{ + mDistanceModel = model; +} + +void SFXXAudioDevice::setDopplerFactor( F32 factor ) +{ + mDopplerFactor = factor; +} + +void SFXXAudioDevice::setRolloffFactor( F32 factor ) +{ + mRolloffFactor = factor; +} diff --git a/sfx/xaudio/sfxXAudioDevice.h b/sfx/xaudio/sfxXAudioDevice.h new file mode 100644 index 0000000..ecac228 --- /dev/null +++ b/sfx/xaudio/sfxXAudioDevice.h @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXXAUDIODEVICE_H_ +#define _SFXXAUDIODEVICE_H_ + +class SFXProvider; + +#ifndef _SFXDEVICE_H_ +#include "sfx/sfxDevice.h" +#endif + +#ifndef _SFXPROVIDER_H_ +#include "sfx/sfxProvider.h" +#endif + +#ifndef _SFXXAUDIOVOICE_H_ +#include "sfx/xaudio/sfxXAudioVoice.h" +#endif + +#ifndef _SFXXAUDIOBUFFER_H_ +#include "sfx/xaudio/sfxXAudioBuffer.h" +#endif + +#include +#include + + +class SFXXAudioDevice : public SFXDevice +{ + public: + + typedef SFXDevice Parent; + friend class SFXXAudioVoice; // mXAudio + + protected: + + /// The XAudio engine interface passed + /// on creation from the provider. + IXAudio2 *mXAudio; + + /// The X3DAudio instance. + X3DAUDIO_HANDLE mX3DAudio; + + /// The one and only mastering voice. + IXAudio2MasteringVoice* mMasterVoice; + + /// The details of the master voice. + XAUDIO2_VOICE_DETAILS mMasterVoiceDetails; + + /// The one listener. + X3DAUDIO_LISTENER mListener; + + SFXDistanceModel mDistanceModel; + F32 mRolloffFactor; + F32 mDopplerFactor; + + public: + + SFXXAudioDevice( SFXProvider* provider, + const String& name, + IXAudio2 *xaudio, + U32 deviceIndex, + U32 speakerChannelMask, + U32 maxBuffers ); + + virtual ~SFXXAudioDevice(); + + // SFXDevice + virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ); + virtual SFXVoice* createVoice( bool is3D, SFXBuffer *buffer ); + virtual void update( const SFXListener& listener ); + virtual void setDistanceModel( SFXDistanceModel model ); + virtual void setRolloffFactor( F32 factor ); + virtual void setDopplerFactor( F32 factor ); + + /// Called from the voice when its about to start playback. + void _setOutputMatrix( SFXXAudioVoice *voice ); +}; + +#endif // _SFXXAUDIODEVICE_H_ \ No newline at end of file diff --git a/sfx/xaudio/sfxXAudioProvider.cpp b/sfx/xaudio/sfxXAudioProvider.cpp new file mode 100644 index 0000000..b3c838d --- /dev/null +++ b/sfx/xaudio/sfxXAudioProvider.cpp @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +// Note: This must be defined before platform.h so that +// CoInitializeEx is properly included. +#define _WIN32_DCOM +#include + +#include "sfx/xaudio/sfxXAudioDevice.h" +#include "sfx/sfxProvider.h" +#include "core/util/safeRelease.h" +#include "core/strings/unicode.h" +#include "core/strings/stringFunctions.h" +#include "console/console.h" + + +class SFXXAudioProvider : public SFXProvider +{ +public: + + SFXXAudioProvider() + : SFXProvider( "XAudio" ) {} + virtual ~SFXXAudioProvider(); + +protected: + + /// Extended SFXDeviceInfo to also store some + /// extra XAudio specific data. + struct XADeviceInfo : SFXDeviceInfo + { + UINT32 deviceIndex; + + XAUDIO2_DEVICE_ROLE role; + + WAVEFORMATEXTENSIBLE format; + }; + + /// Helper for creating the XAudio engine. + static bool _createXAudio( IXAudio2 **xaudio ); + +public: + + // SFXProvider + void init(); + SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ); + +}; + +SFX_INIT_PROVIDER( SFXXAudioProvider ); + +SFXXAudioProvider::~SFXXAudioProvider() +{ +} + +void SFXXAudioProvider::init() +{ + // Create a temp XAudio object for device enumeration. + IXAudio2 *xAudio = NULL; + if ( !_createXAudio( &xAudio ) ) + { + Con::errorf( "SFXXAudioProvider::init() - XAudio2 failed to load!" ); + return; + } + + // Add the devices to the info list. + UINT32 count = 0; + xAudio->GetDeviceCount( &count ); + for ( UINT32 i = 0; i < count; i++ ) + { + XAUDIO2_DEVICE_DETAILS details; + HRESULT hr = xAudio->GetDeviceDetails( i, &details ); + if ( FAILED( hr ) ) + continue; + + // Add a device to the info list. + XADeviceInfo* info = new XADeviceInfo; + info->deviceIndex = i; + info->driver = String( "XAudio" ); + info->name = String( details.DisplayName ); + info->hasHardware = false; + info->maxBuffers = 64; + info->role = details.Role; + info->format = details.OutputFormat; + mDeviceInfo.push_back( info ); + } + + // We're done with XAudio for now. + SAFE_RELEASE( xAudio ); + + // If we have no devices... we're done. + if ( mDeviceInfo.empty() ) + { + Con::errorf( "SFXXAudioProvider::init() - No valid XAudio2 devices found!" ); + return; + } + + // If we got this far then we should be able to + // safely create a device for XAudio. + regProvider( this ); +} + +bool SFXXAudioProvider::_createXAudio( IXAudio2 **xaudio ) +{ + // In debug builds enable the debug version + // of the XAudio engine. + #ifdef TORQUE_DEBUG + #define XAUDIO_FLAGS XAUDIO2_DEBUG_ENGINE + #else + #define XAUDIO_FLAGS 0 + #endif + +#ifndef TORQUE_OS_XENON + // This must be called first... it doesn't hurt to + // call it more than once. + CoInitialize( NULL ); +#endif + + // Try creating the xaudio engine. + HRESULT hr = XAudio2Create( xaudio, XAUDIO_FLAGS, XAUDIO2_DEFAULT_PROCESSOR ); + + return SUCCEEDED( hr ) && (*xaudio); +} + +SFXDevice* SFXXAudioProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ) +{ + String devName; + + // On the 360, ignore what the prefs say, and create the only audio device +#ifndef TORQUE_OS_XENON + devName = deviceName; +#endif + + XADeviceInfo* info = dynamic_cast< XADeviceInfo* >( _findDeviceInfo( devName ) ); + + // Do we find one to create? + if ( info ) + { + // Create the XAudio object to pass to the device. + IXAudio2 *xAudio = NULL; + if ( !_createXAudio( &xAudio ) ) + { + Con::errorf( "SFXXAudioProvider::createDevice() - XAudio2 failed to load!" ); + return NULL; + } + + return new SFXXAudioDevice( this, + devName, + xAudio, + info->deviceIndex, + info->format.dwChannelMask, + maxBuffers ); + } + + // We didn't find a matching valid device. + return NULL; +} diff --git a/sfx/xaudio/sfxXAudioVoice.cpp b/sfx/xaudio/sfxXAudioVoice.cpp new file mode 100644 index 0000000..573c17a --- /dev/null +++ b/sfx/xaudio/sfxXAudioVoice.cpp @@ -0,0 +1,402 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sfx/xaudio/sfxXAudioVoice.h" +#include "sfx/xaudio/sfxXAudioDevice.h" +#include "sfx/xaudio/sfxXAudioBuffer.h" +#include "core/util/safeDelete.h" +#include "math/mMathFn.h" + + +static void sfxFormatToWAVEFORMATEX( const SFXFormat& format, WAVEFORMATEX *wfx ) +{ + dMemset( wfx, 0, sizeof( WAVEFORMATEX ) ); + wfx->wFormatTag = WAVE_FORMAT_PCM; + wfx->nChannels = format.getChannels(); + wfx->nSamplesPerSec = format.getSamplesPerSecond(); + wfx->wBitsPerSample = format.getBitsPerChannel(); + wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; + wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; +} + + +SFXXAudioVoice* SFXXAudioVoice::create( IXAudio2 *xaudio, + bool is3D, + SFXXAudioBuffer *buffer, + SFXXAudioVoice* inVoice ) +{ + AssertFatal( xaudio, "SFXXAudioVoice::create() - Got null XAudio!" ); + AssertFatal( buffer, "SFXXAudioVoice::create() - Got null buffer!" ); + + // Create the voice object first as it also the callback object. + SFXXAudioVoice* voice = inVoice; + if( !voice ) + voice = new SFXXAudioVoice( buffer ); + + // Get the buffer format. + WAVEFORMATEX wfx; + sfxFormatToWAVEFORMATEX( buffer->getFormat(), &wfx ); + + // We don't support multi-channel 3d sounds! + if ( is3D && wfx.nChannels > 1 ) + return NULL; + + // Create the voice. + IXAudio2SourceVoice *xaVoice; + HRESULT hr = xaudio->CreateSourceVoice( &xaVoice, + (WAVEFORMATEX*)&wfx, + 0, + XAUDIO2_DEFAULT_FREQ_RATIO, + voice, + NULL, + NULL ); + + if( FAILED( hr ) || !voice ) + { + if( !inVoice ) + delete voice; + return NULL; + } + + voice->mIs3D = is3D; + voice->mEmitter.ChannelCount = wfx.nChannels; + voice->mXAudioVoice = xaVoice; + + return voice; +} + +SFXXAudioVoice::SFXXAudioVoice( SFXXAudioBuffer* buffer ) + : Parent( buffer ), + mXAudioDevice( NULL ), + mXAudioVoice( NULL ), + mIs3D( false ), + mPitch( 1.0f ), + mHasStopped( false ), + mHasStarted( false ), + mIsLooping( false ), + mIsPlaying( false ), + mNonStreamBufferOffset( 0 ), + mSamplesPlayedOffset( 0 ) +{ + dMemset( &mEmitter, 0, sizeof( mEmitter ) ); + mEmitter.DopplerScaler = 1.0f; + + InitializeCriticalSection( &mLock ); +} + +SFXXAudioVoice::~SFXXAudioVoice() +{ + if ( mEmitter.pVolumeCurve ) + { + SAFE_DELETE_ARRAY( mEmitter.pVolumeCurve->pPoints ); + SAFE_DELETE( mEmitter.pVolumeCurve ); + } + + SAFE_DELETE( mEmitter.pCone ); + + if ( mXAudioVoice ) + mXAudioVoice->DestroyVoice(); + + DeleteCriticalSection( &mLock ); +} + +SFXStatus SFXXAudioVoice::_status() const +{ + if( mHasStopped ) + return SFXStatusStopped; + else if( mHasStarted ) + { + if( !mIsPlaying ) + return SFXStatusPaused; + else + return SFXStatusPlaying; + } + else + return SFXStatusStopped; +} + +void SFXXAudioVoice::_flush() +{ + AssertFatal( mXAudioVoice != NULL, + "SFXXAudioVoice::_flush() - invalid voice" ); + + EnterCriticalSection( &mLock ); + + mXAudioVoice->Stop( 0 ); + mXAudioVoice->FlushSourceBuffers(); + + // According to the docs, SamplesPlayed reported by the + // voice should be zero now. Alas it ain't. So, save + // the current value here and offset our future play + // cursors. + + XAUDIO2_VOICE_STATE state; + mXAudioVoice->GetState( &state ); + + mSamplesPlayedOffset = state.SamplesPlayed; + + LeaveCriticalSection( &mLock ); +} + +void SFXXAudioVoice::_play() +{ + AssertFatal( mXAudioVoice != NULL, + "SFXXAudioVoice::_play() - invalid voice" ); + + // For non-streaming voices queue the data if we haven't yet. + + XAUDIO2_VOICE_STATE state; + mXAudioVoice->GetState( &state ); + if( !state.BuffersQueued ) + { + AssertFatal( !mBuffer->isStreaming(), "SFXXAudioVoice::_play() - non data queued on streaming voice" ); + _loadNonStreamed(); + } + + mXAudioVoice->Start( 0, 0 ); + + mIsPlaying = true; + mHasStarted = true; + mHasStopped = false; +} + +void SFXXAudioVoice::_pause() +{ + AssertFatal( mXAudioVoice != NULL, + "SFXXAudioVoice::_pause() - invalid voice" ); + + mXAudioVoice->Stop( 0 ); + mIsPlaying = false; + + XAUDIO2_VOICE_STATE state; + mXAudioVoice->GetState( &state ); +} + +void SFXXAudioVoice::_stop() +{ + AssertFatal( mXAudioVoice != NULL, + "SFXXAudioVoice::_stop() - invalid voice" ); + + _flush(); + + mIsPlaying = false; + mHasStarted = false; + mHasStopped = true; +} + +void SFXXAudioVoice::_seek( U32 sample ) +{ + const SFXFormat& format = mBuffer->getFormat(); + U32 pos = sample * format.getBytesPerSample(); + pos = mAlignToMultiple( pos, format.getChannels() * format.getBitsPerSample() / 8 ); + mNonStreamBufferOffset = pos; + + if( mBuffer->isReady() ) + _loadNonStreamed(); +} + +void SFXXAudioVoice::_loadNonStreamed() +{ + AssertFatal( !mBuffer->isStreaming(), "SFXXAudioVoice::_loadNonStreamed() - must not be called on streaming voices" ); + AssertFatal( mXAudioVoice != NULL, "SFXXAudioVoice::_loadNonStreamed() - invalid voice" ); + + if( _status() == SFXStatusPlaying ) + _stop(); // Will also flush buffers. + + EnterCriticalSection( &mLock ); + + const XAUDIO2_BUFFER& orgBuffer = _getBuffer()->mBufferQueue.front().mData; + + mNonStreamBuffer = orgBuffer; + mNonStreamBuffer.pAudioData = orgBuffer.pAudioData + mNonStreamBufferOffset; + mNonStreamBuffer.AudioBytes = orgBuffer.AudioBytes - mNonStreamBufferOffset; + if( mIsLooping ) + mNonStreamBuffer.LoopCount = XAUDIO2_LOOP_INFINITE; + mXAudioVoice->SubmitSourceBuffer( &mNonStreamBuffer ); + mNonStreamBufferOffset = 0; + + if( mStatus == SFXStatusPlaying ) + _play(); + + LeaveCriticalSection( &mLock ); +} + +U32 SFXXAudioVoice::_tell() const +{ + if( !mHasStarted ) + return 0; + + XAUDIO2_VOICE_STATE state; + mXAudioVoice->GetState( &state ); + + // Handle end-of-stream. SamplesPlayed will + // be zero and no buffers queued. + + if( !state.BuffersQueued && !state.SamplesPlayed ) + return mBuffer->getFormat().getSampleCount( mBuffer->getDuration() ); + else + return ( state.SamplesPlayed - mSamplesPlayedOffset ); +} + +void SFXXAudioVoice::setMinMaxDistance( F32 min, F32 max ) +{ + // Set the overall volume curve scale. + mEmitter.CurveDistanceScaler = max; + + // The curve uses normalized distances, so + // figure out the normalized min distance. + F32 normMin = 0.0f; + if ( min > 0.0f ) + normMin = min / max; + + // See what type of curve we are supposed to generate. + const bool linear = ( mXAudioDevice->mDistanceModel == SFXDistanceModelLinear ); + + // Have we setup the curve yet? + if( !mEmitter.pVolumeCurve + || ( linear && mEmitter.pVolumeCurve->PointCount != 2 ) + || ( linear && mEmitter.pVolumeCurve->PointCount != 6 ) ) + { + if( !mEmitter.pVolumeCurve ) + mEmitter.pVolumeCurve = new X3DAUDIO_DISTANCE_CURVE; + else + SAFE_DELETE_ARRAY( mEmitter.pVolumeCurve->pPoints ); + + // We use 6 points for logarithmic volume curves and 2 for linear volume curves. + if( linear ) + { + mEmitter.pVolumeCurve->pPoints = new X3DAUDIO_DISTANCE_CURVE_POINT[ 2 ]; + mEmitter.pVolumeCurve->PointCount = 2; + } + else + { + mEmitter.pVolumeCurve->pPoints = new X3DAUDIO_DISTANCE_CURVE_POINT[ 6 ]; + mEmitter.pVolumeCurve->PointCount = 6; + } + + // The first and last points are known + // and will not change. + mEmitter.pVolumeCurve->pPoints[ 0 ].Distance = 0.0f; + mEmitter.pVolumeCurve->pPoints[ 0 ].DSPSetting = 1.0f; + mEmitter.pVolumeCurve->pPoints[ linear ? 1 : 5 ].Distance = 1.0f; + mEmitter.pVolumeCurve->pPoints[ linear ? 1 : 5 ].DSPSetting = 0.0f; + } + + if( !linear ) + { + // Set the second point of the curve. + mEmitter.pVolumeCurve->pPoints[1].Distance = normMin; + mEmitter.pVolumeCurve->pPoints[1].DSPSetting = 1.0f; + + // The next three points are calculated to + // give the sound a rough logarithmic falloff. + F32 distStep = ( 1.0f - normMin ) / 4.0f; + for ( U32 i=0; i < 3; i++ ) + { + U32 index = 2 + i; + F32 dist = normMin + ( distStep * (F32)( i + 1 ) ); + + mEmitter.pVolumeCurve->pPoints[index].Distance = dist; + mEmitter.pVolumeCurve->pPoints[index].DSPSetting = 1.0f - log10( dist * 10.0f ); + } + } +} + +void SFXXAudioVoice::OnBufferEnd( void* bufferContext ) +{ + if( mBuffer->isStreaming() ) + SFXInternal::TriggerUpdate(); +} + +void SFXXAudioVoice::OnStreamEnd() +{ + // Warning: This is being called within the XAudio + // thread, so be sure your thread safe! + + mHasStopped = true; + + if( mBuffer->isStreaming() ) + SFXInternal::TriggerUpdate(); + else + _stop(); +} + +void SFXXAudioVoice::play( bool looping ) +{ + // Give the device a chance to calculate our positional + // audio settings before we start playback... this is + // important else we get glitches. + if( mIs3D ) + mXAudioDevice->_setOutputMatrix( this ); + + mIsLooping = looping; + Parent::play( looping ); +} + +void SFXXAudioVoice::setVelocity( const VectorF& velocity ) +{ + mEmitter.Velocity.x = velocity.x; + mEmitter.Velocity.y = velocity.y; + + // XAudio and Torque use opposite handedness, so + // flip the z coord to account for that. + mEmitter.Velocity.z = -velocity.z; +} + +void SFXXAudioVoice::setTransform( const MatrixF& transform ) +{ + transform.getColumn( 3, (Point3F*)&mEmitter.Position ); + transform.getColumn( 1, (Point3F*)&mEmitter.OrientFront ); + transform.getColumn( 2, (Point3F*)&mEmitter.OrientTop ); + + // XAudio and Torque use opposite handedness, so + // flip the z coord to account for that. + mEmitter.Position.z *= -1.0f; + mEmitter.OrientFront.z *= -1.0f; + mEmitter.OrientTop.z *= -1.0f; +} + +void SFXXAudioVoice::setVolume( F32 volume ) +{ + mXAudioVoice->SetVolume( volume ); +} + +void SFXXAudioVoice::setPitch( F32 pitch ) +{ + mPitch = mClampF( pitch, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO ); + mXAudioVoice->SetFrequencyRatio( mPitch ); +} + +void SFXXAudioVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ) +{ + // If the cone is set to 360 then the + // cone is null and doesn't need to be + // set on the voice. + if ( mIsEqual( innerAngle, 360 ) ) + { + SAFE_DELETE( mEmitter.pCone ); + return; + } + + if ( !mEmitter.pCone ) + { + mEmitter.pCone = new X3DAUDIO_CONE; + + // The inner volume is always 1... the overall + // volume is what scales it. + mEmitter.pCone->InnerVolume = 1.0f; + + // We don't use these yet. + mEmitter.pCone->InnerLPF = 0.0f; + mEmitter.pCone->OuterLPF = 0.0f; + mEmitter.pCone->InnerReverb = 0.0f; + mEmitter.pCone->OuterReverb = 0.0f; + } + + mEmitter.pCone->InnerAngle = mDegToRad( innerAngle ); + mEmitter.pCone->OuterAngle = mDegToRad( outerAngle ); + mEmitter.pCone->OuterVolume = outerVolume; +} diff --git a/sfx/xaudio/sfxXAudioVoice.h b/sfx/xaudio/sfxXAudioVoice.h new file mode 100644 index 0000000..1a38494 --- /dev/null +++ b/sfx/xaudio/sfxXAudioVoice.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SFXXAUDIOVOICE_H_ +#define _SFXXAUDIOVOICE_H_ + +#include +#include + +#include "sfx/sfxVoice.h" + + +class SFXXAudioBuffer; + + +class SFXXAudioVoice : public SFXVoice, + public IXAudio2VoiceCallback +{ + public: + + typedef SFXVoice Parent; + + friend class SFXXAudioDevice; + friend class SFXXAudioBuffer; + + protected: + + /// This constructor does not create a valid voice. + /// @see SFXXAudioVoice::create + SFXXAudioVoice( SFXXAudioBuffer* buffer ); + + /// The device that created us. + SFXXAudioDevice *mXAudioDevice; + + /// The XAudio voice. + IXAudio2SourceVoice *mXAudioVoice; + + /// + XAUDIO2_BUFFER mNonStreamBuffer; + + /// + U32 mNonStreamBufferOffset; + + /// + CRITICAL_SECTION mLock; + + /// Used to know what sounds need + /// positional updates. + bool mIs3D; + + /// + mutable bool mHasStopped; + + /// + bool mHasStarted; + + /// + bool mIsPlaying; + + /// + bool mIsLooping; + + /// Since 3D sounds are pitch shifted for doppler + /// effect we need to track the users base pitch. + F32 mPitch; + + /// The cached X3DAudio emitter data. + X3DAUDIO_EMITTER mEmitter; + + /// + U32 mSamplesPlayedOffset; + + // IXAudio2VoiceCallback + void STDMETHODCALLTYPE OnStreamEnd(); + void STDMETHODCALLTYPE OnVoiceProcessingPassStart( UINT32 BytesRequired ) {} + void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() {} + void STDMETHODCALLTYPE OnBufferEnd( void *bufferContext ); + void STDMETHODCALLTYPE OnBufferStart( void *bufferContext ) {} + void STDMETHODCALLTYPE OnLoopEnd( void *bufferContext ) {} + void STDMETHODCALLTYPE OnVoiceError( void * bufferContext, HRESULT error ) {} + + /// @deprecated This is only here for compatibility with + /// the March 2008 SDK version of IXAudio2VoiceCallback. + void STDMETHODCALLTYPE OnVoiceProcessingPassStart() {} + + void _flush(); + void _loadNonStreamed(); + + // SFXVoice. + virtual SFXStatus _status() const; + virtual void _play(); + virtual void _pause(); + virtual void _stop(); + virtual void _seek( U32 sample ); + virtual U32 _tell() const; + + SFXXAudioBuffer* _getBuffer() const { return ( SFXXAudioBuffer* ) mBuffer.getPointer(); } + + public: + + /// + static SFXXAudioVoice* create( IXAudio2 *xaudio, + bool is3D, + SFXXAudioBuffer *buffer, + SFXXAudioVoice* inVoice = NULL ); + + /// + virtual ~SFXXAudioVoice(); + + // SFXVoice + void setMinMaxDistance( F32 min, F32 max ); + void play( bool looping ); + void setVelocity( const VectorF& velocity ); + void setTransform( const MatrixF& transform ); + void setVolume( F32 volume ); + void setPitch( F32 pitch ); + void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ); + + /// Is this a 3D positional voice. + bool is3D() const { return mIs3D; } + + /// + const X3DAUDIO_EMITTER& getEmitter() const { return mEmitter; } +}; + +#endif // _SFXXAUDIOVOICE_H_ \ No newline at end of file diff --git a/shaderGen/GLSL/bumpGLSL.cpp b/shaderGen/GLSL/bumpGLSL.cpp new file mode 100644 index 0000000..948c9a8 --- /dev/null +++ b/shaderGen/GLSL/bumpGLSL.cpp @@ -0,0 +1,284 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/GLSL/bumpGLSL.h" + +#include "shaderGen/shaderOp.h" +#include "gfx/gfxDevice.h" +#include "materials/matInstance.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" +#include "shaderGen/shaderGenVars.h" + + +void BumpFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // Output the texture coord. + getOutTexCoord( "texCoord", + "vec2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + + // Also output the worldToTanget transform which + // we use to create the world space normal. + getOutWorldToTangent( componentList, meta ); + + + // TODO: Restore this! + /* + // Check to see if we're rendering world space normals. + if ( fd.materialFeatures[MFT_NormalsOut] ) + { + Var *inNormal = (Var*)LangElement::find( "normal" ); + + Var *outNormal = connectComp->getElement( RT_TEXCOORD ); + outNormal->setName( "normal" ); + outNormal->setStructName( "OUT" ); + outNormal->setType( "float3" ); + outNormal->mapsToSampler = false; + + meta->addStatement( new GenOp( " @ = @; // MFT_NormalsOut\r\n", outNormal, inNormal ) ); + output = meta; + return; + } + */ + + output = meta; +} + +void BumpFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // Get the texture coord. + Var *texCoord = getInTexCoord( "out_texCoord", "vec2", true, componentList ); + + // Sample the bumpmap. + Var *bumpMap = getNormalMapTex(); + LangElement *texOp = new GenOp( "texture2D(@, @)", bumpMap, texCoord ); + Var *bumpNorm = new Var( "bumpNormal", "vec4" ); + meta->addStatement( expandNormalMap( texOp, new DecOp( bumpNorm ), bumpNorm, fd ) ); + + // We transform it into world space by reversing the + // multiplication by the worldToTanget transform. + Var *wsNormal = new Var( "wsNormal", "vec3" ); + Var *worldToTanget = getInWorldToTangent( componentList ); + meta->addStatement( new GenOp( " @ = normalize( vec3( @.xyz * @ ) );\r\n", new DecOp( wsNormal ), bumpNorm, worldToTanget ) ); + + // TODO: Restore this! + /* + // Check to see if we're rendering world space normals. + if ( fd.materialFeatures[MFT_NormalsOut] ) + { + Var *inNormal = getInTexCoord( "normal", "float3", false, componentList ); + + LangElement *normalOut; + Var *outColor = (Var*)LangElement::find( "col" ); + if ( outColor ) + normalOut = new GenOp( "float4( ( -@ + 1 ) * 0.5, @.a )", inNormal, outColor ); + else + normalOut = new GenOp( "float4( ( -@ + 1 ) * 0.5, 1 )", inNormal ); + + meta->addStatement( new GenOp( " @; // MFT_NormalsOut\r\n", + assignColor( normalOut, Material::None ) ) ); + + output = meta; + return; + } + */ + + output = meta; +} + +ShaderFeature::Resources BumpFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // If we have no parallax then we bring on the normal tex. + if ( !fd.features[MFT_Parallax] ) + res.numTex = 1; + + // Only the parallax or diffuse map will add texture + // coords other than us. + if ( !fd.features[MFT_Parallax] && + !fd.features[MFT_DiffuseMap] && + !fd.features[MFT_OverlayMap] && + !fd.features[MFT_DetailMap] ) + res.numTexReg++; + + // We pass the world to tanget space transform. + res.numTexReg += 3; + + return res; +} + +void BumpFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + // If we had a parallax feature then it takes + // care of hooking up the normal map texture. + if ( fd.features[MFT_Parallax] ) + return; + + GFXTextureObject *tex = stageDat.getTex( MFT_NormalMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Bump; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} + +/* +Var* ParallaxFeatHLSL::_getUniformVar( const char *name, const char *type ) +{ + Var *theVar = (Var*)LangElement::find( name ); + if ( !theVar ) + { + theVar = new Var; + theVar->setType( type ); + theVar->setName( name ); + theVar->uniform = true; + theVar->constSortPos = cspPass; + } + + return theVar; +} + +void ParallaxFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::processVert - We don't support SM 1.x!" ); + + MultiLine *meta = new MultiLine; + + // Add the texture coords. + getOutTexCoord( "texCoord", + "float2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + + // Grab the input position. + Var *inPos = (Var*)LangElement::find( "inPosition" ); + if ( !inPos ) + inPos = (Var*)LangElement::find( "position" ); + + // Get the object space eye position and the world + // to tangent transform. + Var *eyePos = _getUniformVar( "eyePos", "float3" ); + Var *objToTangentSpace = getOutObjToTangentSpace( componentList, meta ); + + // send transform to pixel shader + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outViewTS = connectComp->getElement( RT_TEXCOORD, 1 ); + outViewTS->setName( "outViewTS" ); + outViewTS->setStructName( "OUT" ); + outViewTS->setType( "float3" ); + meta->addStatement( new GenOp( " @ = mul( @ - @.xyz, transpose( @ ) );\r\n", + outViewTS, inPos, eyePos, objToTangentSpace ) ); + + output = meta; +} + +void ParallaxFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::processPix - We don't support SM 1.x!" ); + + MultiLine *meta = new MultiLine; + + // Order matters... get this first! + Var *texCoord = getInTexCoord( "texCoord", "float2", true, componentList ); + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // We need the negative tangent space view vector + // as in parallax mapping we step towards the camera. + Var *negViewTS = (Var*)LangElement::find( "negViewTS" ); + if ( !negViewTS ) + { + Var *inViewTS = (Var*)LangElement::find( "outViewTS" ); + if ( !inViewTS ) + { + inViewTS = connectComp->getElement( RT_TEXCOORD, 1 ); + inViewTS->setName( "outViewTS" ); + inViewTS->setStructName( "IN" ); + inViewTS->setType( "float3" ); + } + + negViewTS = new Var( "negViewTS", "float3" ); + meta->addStatement( new GenOp( " @ = -normalize( @ );\r\n", new DecOp( negViewTS ), inViewTS ) ); + } + + // Get the rest of our inputs. + Var *parallaxInfo = _getUniformVar( "parallaxInfo", "float" ); + Var *normalMap = getNormalMapTex(); + + // Do 3 parallax samples to get acceptable + // quality without too much overhead. + Var *pdepth = findOrCreateLocal( "pdepth", "float", meta ); + Var *poffset = findOrCreateLocal( "poffset", "float2", meta ); + meta->addStatement( new GenOp( " @ = tex2D( @, @.xy ).a;\r\n", pdepth, normalMap, texCoord ) ); + meta->addStatement( new GenOp( " @ = @.xy * ( @ * @ );\r\n", poffset, negViewTS, pdepth, parallaxInfo ) ); + + meta->addStatement( new GenOp( " @ = ( @ + tex2D( @, @.xy + @ ).a ) * 0.5;\r\n", pdepth, pdepth, normalMap, texCoord, poffset ) ); + meta->addStatement( new GenOp( " @ = @.xy * ( @ * @ );\r\n", poffset, negViewTS, pdepth, parallaxInfo ) ); + + meta->addStatement( new GenOp( " @ = ( @ + tex2D( @, @.xy + @ ).a ) * 0.5;\r\n", pdepth, pdepth, normalMap, texCoord, poffset ) ); + meta->addStatement( new GenOp( " @ = @.xy * ( @ * @ );\r\n", poffset, negViewTS, pdepth, parallaxInfo ) ); + + meta->addStatement( new GenOp( " @.xy += @;\r\n", texCoord, poffset ) ); + + // TODO: Fix second UV. + + output = meta; +} + +ShaderFeature::Resources ParallaxFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::getResources - We don't support SM 1.x!" ); + + Resources res; + + // We add the outViewTS to the outputstructure. + res.numTexReg = 1; + + // If this isn't a prepass then we will be + // creating the normal map here. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + res.numTex = 1; + + return res; +} + +void ParallaxFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::setTexData - We don't support SM 1.x!" ); + + GFXTextureObject *tex = stageDat.getTex( MFT_NormalMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Bump; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} +*/ \ No newline at end of file diff --git a/shaderGen/GLSL/bumpGLSL.h b/shaderGen/GLSL/bumpGLSL.h new file mode 100644 index 0000000..504f451 --- /dev/null +++ b/shaderGen/GLSL/bumpGLSL.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BUMP_GLSL_H_ +#define _BUMP_GLSL_H_ + +#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#endif + +struct RenderPassData; +class MultiLine; + + +/// The Bumpmap feature will read the normal map and +/// transform it by the inverse of the worldToTanget +/// matrix. This normal is then used by subsequent +/// shader features. +class BumpFeatGLSL : public ShaderFeatureGLSL +{ +public: + + // ShaderFeatureGLSL + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() { return "Bumpmap"; } +}; + +/* +/// This feature either generates the cheap yet effective offset +/// mapping style parallax or the much more expensive occlusion +/// mapping technique based on the enabled feature flags. +class ParallaxFeatGLSL : public ShaderFeatureGLSL +{ +protected: + + static Var* _getUniformVar( const char *name, const char *type ); + +public: + + // ShaderFeatureGLSL + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + virtual String getName() { return "Parallax"; } +}; +*/ + +#endif // _BUMP_GLSL_H_ \ No newline at end of file diff --git a/shaderGen/GLSL/depthGLSL.cpp b/shaderGen/GLSL/depthGLSL.cpp new file mode 100644 index 0000000..c44cb35 --- /dev/null +++ b/shaderGen/GLSL/depthGLSL.cpp @@ -0,0 +1,150 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/GLSL/depthGLSL.h" + +#include "materials/materialFeatureTypes.h" + + +void EyeSpaceDepthOutGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab incoming vert position + Var *inPosition = (Var*) LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*) LangElement::find( "position" ); + + AssertFatal( inPosition, "Something went bad with ShaderGen. The position should be already defined." ); + + // grab output + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outWSEyeVec = connectComp->getElement( RT_TEXCOORD ); + outWSEyeVec->setName( "outWSEyeVec" ); + + // create modelview variable + Var *objToWorld = (Var*) LangElement::find( "objTrans" ); + if( !objToWorld ) + { + objToWorld = new Var; + objToWorld->setType( "mat4x4" ); + objToWorld->setName( "objTrans" ); + objToWorld->uniform = true; + objToWorld->constSortPos = cspPrimitive; + } + + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if( !eyePos ) + { + eyePos = new Var; + eyePos->setType("vec3"); + eyePos->setName("eyePosWorld"); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + LangElement *statement = new GenOp( " @ = vec4(@ * vec4(@.xyz,1)) - vec4(@, 0.0);\r\n", + outWSEyeVec, objToWorld, inPosition, eyePos ); + + output = statement; +} + +void EyeSpaceDepthOutGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // grab connector position + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *wsEyeVec = connectComp->getElement( RT_TEXCOORD ); + wsEyeVec->setName( "outWSEyeVec" ); + wsEyeVec->setType( "vec4" ); + wsEyeVec->mapsToSampler = false; + wsEyeVec->uniform = false; + + // get shader constants + Var *vEye = new Var; + vEye->setType("vec3"); + vEye->setName("vEye"); + vEye->uniform = true; + vEye->constSortPos = cspPass; + + // Expose the depth to the depth format feature + Var *depthOut = new Var; + depthOut->setType("float"); + depthOut->setName(getOutputVarName()); + + LangElement *depthOutDecl = new DecOp( depthOut ); + + meta->addStatement( new GenOp( " @ = dot(@, (@.xyz / @.w));\r\n", depthOutDecl, vEye, wsEyeVec, wsEyeVec ) ); + + // If there isn't an output conditioner for the pre-pass, than just write + // out the depth to rgba and return. + if( !fd.features[MFT_PrePassConditioner] ) + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "vec4(@)", depthOut ), Material::None ) ) ); + + output = meta; +} + +ShaderFeature::Resources EyeSpaceDepthOutGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources temp; + + // Passing from VS->PS: + // - world space position (wsPos) + temp.numTexReg = 1; + + return temp; +} + + +void DepthOutGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Grab the output vert. + Var *outPosition = (Var*)LangElement::find( "gl_Position" ); + + // Grab our output depth. + Var *outDepth = connectComp->getElement( RT_TEXCOORD ); + outDepth->setName( "outDepth" ); + outDepth->setType( "float" ); + + output = new GenOp( " @ = @.z / @.w;\r\n", outDepth, outPosition, outPosition ); +} + +void DepthOutGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // grab connector position + Var *depthVar = connectComp->getElement( RT_TEXCOORD ); + depthVar->setName( "outDepth" ); + depthVar->setType( "float" ); + depthVar->mapsToSampler = false; + depthVar->uniform = false; + + /* + // Expose the depth to the depth format feature + Var *depthOut = new Var; + depthOut->setType("float"); + depthOut->setName(getOutputVarName()); + */ + + LangElement *depthOut = new GenOp( "vec4( @, @ * @, 0, 1 )", depthVar, depthVar, depthVar ); + + output = new GenOp( " @;\r\n", assignColor( depthOut, Material::None ) ); +} + +ShaderFeature::Resources DepthOutGLSL::getResources( const MaterialFeatureData &fd ) +{ + // We pass the depth to the pixel shader. + Resources temp; + temp.numTexReg = 1; + + return temp; +} diff --git a/shaderGen/GLSL/depthGLSL.h b/shaderGen/GLSL/depthGLSL.h new file mode 100644 index 0000000..4c33d95 --- /dev/null +++ b/shaderGen/GLSL/depthGLSL.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _DEPTH_GLSL_H_ +#define _DEPTH_GLSL_H_ + +#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#endif +#ifndef _SHADEROP_H_ +#include "shaderGen/shaderOp.h" +#endif + + +class EyeSpaceDepthOutGLSL : public ShaderFeatureGLSL +{ +public: + + // ShaderFeature + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "Eye Space Depth (Out)"; } + virtual Material::BlendOp getBlendOp() { return Material::None; } + virtual const char* getOutputVarName() const { return "eyeSpaceDepth"; } +}; + + +class DepthOutGLSL : public ShaderFeatureGLSL +{ +public: + + // ShaderFeature + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "Depth (Out)"; } + virtual Material::BlendOp getBlendOp() { return Material::None; } + virtual const char* getOutputVarName() const { return "outDepth"; } +}; + +#endif // _DEPTH_GLSL_H_ \ No newline at end of file diff --git a/shaderGen/GLSL/paraboloidGLSL.cpp b/shaderGen/GLSL/paraboloidGLSL.cpp new file mode 100644 index 0000000..aee1669 --- /dev/null +++ b/shaderGen/GLSL/paraboloidGLSL.cpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/GLSL/paraboloidGLSL.h" + +#include "lighting/lightInfo.h" +#include "materials/sceneData.h" +#include "materials/materialFeatureTypes.h" +#include "gfx/gfxShader.h" + + +void ParaboloidVertTransformGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // First check for an input position from a previous feature + // then look for the default vertex position. + Var *inPosition = (Var*)LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*)LangElement::find( "position" ); + + const bool isSinglePass = fd.features[ MFT_IsSinglePassParaboloid ]; + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Grab connector out position. + Var *outPosition = connectComp->getElement( RT_POSITION ); + outPosition->setName( "gl_Position" ); + + // Get the modelViewOnly matrix + Var *modelview = new Var; + modelview->setType( "mat4x4" ); + modelview->setName( "worldViewOnly" ); + modelview->uniform = true; + modelview->constSortPos = cspPrimitive; + + // Get the atlas scale. + Var *atlasScale = new Var; + atlasScale->setType( "vec2" ); + atlasScale->setName( "atlasScale" ); + atlasScale->uniform = true; + atlasScale->constSortPos = cspPrimitive; + + // So what we're doing here is transforming into camera space, and + // then directly manipulate into shadowmap space. + // + // http://www.gamedev.net/reference/articles/article2308.asp + + // Swizzle z and y post-transform + meta->addStatement( new GenOp( " @ = vec4(@ * vec4(@.xyz,1)).xzyw;\r\n", outPosition, modelview, inPosition ) ); + meta->addStatement( new GenOp( " float L = length(@.xyz);\r\n", outPosition ) ); + + if ( isSinglePass ) + { + // Flip the z in the back case + Var *outIsBack = connectComp->getElement( RT_TEXCOORD ); + outIsBack->setType( "float" ); + outIsBack->setName( "outIsBack" ); + + meta->addStatement( new GenOp( " bool isBack = @.z < 0.0;\r\n", outPosition ) ); + meta->addStatement( new GenOp( " @ = isBack ? -1.0 : 1.0;\r\n", outIsBack ) ); + meta->addStatement( new GenOp( " if ( isBack ) @.z = -@.z;\r\n", outPosition, outPosition ) ); + } + + meta->addStatement( new GenOp( " @ /= L;\r\n", outPosition ) ); + meta->addStatement( new GenOp( " @.z = @.z + 1.0;\r\n", outPosition, outPosition ) ); + meta->addStatement( new GenOp( " @.xy /= @.z;\r\n", outPosition, outPosition ) ); + + // Get the light parameters. + Var *lightParams = new Var; + lightParams->setType( "vec4" ); + lightParams->setName( "lightParams" ); + lightParams->uniform = true; + lightParams->constSortPos = cspPrimitive; + + // TODO: If we change other shadow shaders to write out + // linear depth, than fix this as well! + // + // (L - 1.0)/(lightParams.x - 1.0); + // + meta->addStatement( new GenOp( " @.z = L / @.x;\r\n", outPosition, lightParams ) ); + meta->addStatement( new GenOp( " @.w = 1.0;\r\n", outPosition ) ); + + // Pass unmodified to pixel shader to allow it to clip properly. + Var *outPosXY = connectComp->getElement( RT_TEXCOORD ); + outPosXY->setType( "vec2" ); + outPosXY->setName( "outPosXY" ); + meta->addStatement( new GenOp( " @ = @.xy;\r\n", outPosXY, outPosition ) ); + + // Scale and offset so it shows up in the atlas properly. + meta->addStatement( new GenOp( " @.xy *= @.xy;\r\n", outPosition, atlasScale ) ); + + if ( isSinglePass ) + meta->addStatement( new GenOp( " @.x += isBack ? 0.5 : -0.5;\r\n", outPosition ) ); + else + { + Var *atlasOffset = new Var; + atlasOffset->setType( "vec2" ); + atlasOffset->setName( "atlasXOffset" ); + atlasOffset->uniform = true; + atlasOffset->constSortPos = cspPrimitive; + + meta->addStatement( new GenOp( " @.xy += @;\r\n", outPosition, atlasOffset ) ); + } + + output = meta; +} + +void ParaboloidVertTransformGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + MultiLine *meta = new MultiLine; + + const bool isSinglePass = fd.features[ MFT_IsSinglePassParaboloid ]; + if ( isSinglePass ) + { + // Cull things on the back side of the map. + Var *isBack = connectComp->getElement( RT_TEXCOORD ); + isBack->setName( "outIsBack" ); + isBack->setType( "float" ); + meta->addStatement( new GenOp( " if ( ( abs( @ ) - 0.999 ) < 0 ) discard;\r\n", isBack ) ); + } + + // Cull pixels outside of the valid paraboloid. + Var *posXY = connectComp->getElement( RT_TEXCOORD ); + posXY->setName( "outPosXY" ); + posXY->setType( "vec2" ); + meta->addStatement( new GenOp( " if ( ( 1.0 - length( @ ) ) < 0 ) discard;\r\n", posXY ) ); + + output = meta; +} + +ShaderFeature::Resources ParaboloidVertTransformGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources temp; + temp.numTexReg = 2; + return temp; +} diff --git a/shaderGen/GLSL/paraboloidGLSL.h b/shaderGen/GLSL/paraboloidGLSL.h new file mode 100644 index 0000000..34569e2 --- /dev/null +++ b/shaderGen/GLSL/paraboloidGLSL.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _PARABOLOID_GLSL_H_ +#define _PARABOLOID_GLSL_H_ + +#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#endif +#ifndef _SHADEROP_H_ +#include "shaderGen/shaderOp.h" +#endif + +class GFXShaderConstHandle; + + +class ParaboloidVertTransformGLSL : public ShaderFeatureGLSL +{ +public: + + // ShaderFeature + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "Paraboloid Vert Transform"; } + virtual Material::BlendOp getBlendOp() { return Material::None; } + +}; + +#endif // _PARABOLOID_GLSL_H_ \ No newline at end of file diff --git a/shaderGen/GLSL/pixSpecularGLSL.cpp b/shaderGen/GLSL/pixSpecularGLSL.cpp new file mode 100644 index 0000000..c464fb8 --- /dev/null +++ b/shaderGen/GLSL/pixSpecularGLSL.cpp @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/GLSL/pixSpecularGLSL.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/shaderGenVars.h" +#include "gfx/gfxStructs.h" + + +PixelSpecularGLSL::PixelSpecularGLSL() + : mDep( "shaders/common/gl/lighting.glsl" ) +{ + addDependency( &mDep ); +} + +void PixelSpecularGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + /* + AssertFatal( fd.features[MFT_RTLighting], + "PixelSpecularHLSL requires RTLighting to be enabled!" ); + + MultiLine *meta = new MultiLine; + + // Get the eye world position. + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if( !eyePos ) + { + eyePos = new Var; + eyePos->setType( "float3" ); + eyePos->setName( "eyePosWorld" ); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + // Grab a register for passing the + // world space view vector. + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *wsView = connectComp->getElement( RT_TEXCOORD ); + wsView->setName( "wsView" ); + wsView->setStructName( "OUT" ); + wsView->setType( "float3" ); + + // Get the input position. + Var *position = (Var*)LangElement::find( "inPosition" ); + if ( !position ) + position = (Var*)LangElement::find( "position" ); + + // Get the object to world transform. + Var *objTrans = (Var*) LangElement::find( "objTrans" ); + if ( !objTrans ) + { + objTrans = new Var; + objTrans->setType( "float4x4" ); + objTrans->setName( "objTrans" ); + objTrans->uniform = true; + objTrans->constSortPos = cspPrimitive; + } + + meta->addStatement( new GenOp( " @ = @ - mul( @, float4( @.xyz,1 ) ).xyz;\r\n", + wsView, eyePos, objTrans, position ) ); + + output = meta; + */ +} + +void PixelSpecularGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + /* + AssertFatal( fd.features[MFT_RTLighting], + "PixelSpecularHLSL requires RTLighting to be enabled!" ); + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + MultiLine *meta = new MultiLine; + + // Get the normal and light vectors from which the + // RTLighting feature should have already setup. + Var *wsNormal = (Var*)LangElement::find( "wsNormal" ); + Var *inLightVec = (Var*)LangElement::find( "inLightVec" ); + + // Grab the world space position to eye vector. + Var *wsView = connectComp->getElement( RT_TEXCOORD ); + wsView->setName( "wsView" ); + wsView->setStructName( "IN" ); + wsView->setType( "float3" ); + + // Get the specular power and color. + Var *specPow = new Var( "specularPower", "float" ); + specPow->uniform = true; + specPow->constSortPos = cspPass; + Var *specCol = (Var*)LangElement::find("specularColor"); + if(specCol == NULL) + { + specCol = new Var( "specularColor", "vec4" ); + specCol->uniform = true; + specCol->constSortPos = cspPass; + } + + // Calcuate the specular factor. + Var *specular = new Var( "specular", "float" ); + meta->addStatement( new GenOp( " @ = calcSpecular( -@, normalize( @ ), normalize( @ ), @ );\r\n", + new DecOp( specular ), inLightVec, wsNormal, wsView, specPow ) ); + + LangElement *specMul = new GenOp( "float4(@.rgb,0) * @", specCol, specular ); + LangElement *final = specMul; + + // mask out with lightmap if present + if( fd.features[MFT_LightMap] ) + { + LangElement *lmColor = NULL; + + // find lightmap color + lmColor = LangElement::find( "lmColor" ); + + if ( !lmColor ) + { + LangElement * lightMap = LangElement::find( "lightMap" ); + LangElement * lmCoord = LangElement::find( "texCoord2" ); + + lmColor = new GenOp( "tex2D(@, @)", lightMap, lmCoord ); + } + + final = new GenOp( "@ * float4(@.rgb,0)", specMul, lmColor ); + } + + // We we have a normal map then mask the specular + if ( !fd.features[MFT_SpecularMap] && fd.features[MFT_NormalMap] ) + { + Var *bumpColor = (Var*)LangElement::find( "bumpNormal" ); + final = new GenOp( "@ * @.a", final, bumpColor ); + } + + // Add the specular to the final color. + meta->addStatement( new GenOp( " @;\r\n", assignColor( final, Material::Add ) ) ); + + output = meta; + */ +} + +ShaderFeature::Resources PixelSpecularGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTexReg = 1; + return res; +} + +void SpecularMapGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // Get the texture coord. + Var *texCoord = getInTexCoord( "out_texCoord", "vec2", true, componentList ); + + // create texture var + Var *specularMap = new Var; + specularMap->setType( "sampler2D" ); + specularMap->setName( "specularMap" ); + specularMap->uniform = true; + specularMap->sampler = true; + specularMap->constNum = Var::getTexUnitNum(); + LangElement *texOp = new GenOp( "texture2D(@, @)", specularMap, texCoord ); + + Var *specularColor = new Var( "specularColor", "vec4" ); + + output = new GenOp( " @ = @;\r\n", new DecOp( specularColor ), texOp ); +} + +ShaderFeature::Resources SpecularMapGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + return res; +} + +void SpecularMapGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_SpecularMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Standard; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} \ No newline at end of file diff --git a/shaderGen/GLSL/pixSpecularGLSL.h b/shaderGen/GLSL/pixSpecularGLSL.h new file mode 100644 index 0000000..8430c61 --- /dev/null +++ b/shaderGen/GLSL/pixSpecularGLSL.h @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PIXSPECULAR_GLSL_H_ +#define _PIXSPECULAR_GLSL_H_ + +#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#endif + + +/// A per-pixel specular feature. +class PixelSpecularGLSL : public ShaderFeatureGLSL +{ +protected: + + ShaderIncludeDependency mDep; + +public: + + PixelSpecularGLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Pixel Specular"; + } +}; + + +/// A texture source for the PixSpecular feature +class SpecularMapGLSL : public ShaderFeatureGLSL +{ + +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Specular Map"; + } +}; + + +#endif // _PIXSPECULAR_GLSL_H_ \ No newline at end of file diff --git a/shaderGen/GLSL/shaderCompGLSL.cpp b/shaderGen/GLSL/shaderCompGLSL.cpp new file mode 100644 index 0000000..54f98b5 --- /dev/null +++ b/shaderGen/GLSL/shaderCompGLSL.cpp @@ -0,0 +1,246 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/GLSL/shaderCompGLSL.h" + +#include "shaderGen/shaderComp.h" +#include "shaderGen/langElement.h" +#include "gfx/gfxDevice.h" + + +Var * AppVertConnectorGLSL::getElement( RegisterType type, + U32 numElements, + U32 numRegisters ) +{ + switch( type ) + { + case RT_POSITION: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "gl_Vertex" ); + return newVar; + } + + case RT_NORMAL: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "gl_Normal" ); + return newVar; + } + + + case RT_COLOR: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "gl_Color" ); + return newVar; + } + + case RT_TEXCOORD: + case RT_BINORMAL: + case RT_TANGENT: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + + char out[32]; + dSprintf( (char*)out, sizeof(out), "gl_MultiTexCoord%d", mCurTexElem ); + newVar->setConnectName( out ); + newVar->constNum = mCurTexElem; + newVar->arraySize = numElements; + + if ( numRegisters != -1 ) + mCurTexElem += numRegisters; + else + mCurTexElem += numElements; + + return newVar; + } + + default: + break; + } + + return NULL; +} + +void AppVertConnectorGLSL::sortVars() +{ + // Not required in GLSL +} + +void AppVertConnectorGLSL::setName( char *newName ) +{ + dStrcpy( (char*)mName, newName ); +} + +void AppVertConnectorGLSL::reset() +{ + for( U32 i=0; itype, "float")) + swizzle = "x"; + else if(!dStrcmp((const char*)var->type, "vec2")) + swizzle = "xy"; + else if(!dStrcmp((const char*)var->type, "vec3")) + swizzle = "xyz"; + else + swizzle = "xyzw"; + + // This is ugly. We use #defines to match user defined names with + // built in vars. There is no cleaner way to do this. + dSprintf( (char*)output, sizeof(output), "#define %s %s.%s\r\n", var->name, var->connectName, swizzle ); + + stream.write( dStrlen((char*)output), output ); + } +} + +Var * VertPixelConnectorGLSL::getElement( RegisterType type, + U32 numElements, + U32 numRegisters ) +{ + switch( type ) + { + case RT_POSITION: + case RT_NORMAL: + case RT_COLOR: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + return newVar; + } + + case RT_TEXCOORD: + case RT_BINORMAL: + case RT_TANGENT: + { + Var *newVar = new Var; + newVar->arraySize = numElements; + + if ( numRegisters != -1 ) + mCurTexElem += numRegisters; + else + mCurTexElem += numElements; + + mElementList.push_back( newVar ); + return newVar; + } + + default: + break; + } + + return NULL; +} + +void VertPixelConnectorGLSL::sortVars() +{ + // Not needed in GLSL +} + +void VertPixelConnectorGLSL::setName( char *newName ) +{ + dStrcpy( (char*)mName, newName ); +} + +void VertPixelConnectorGLSL::reset() +{ + for( U32 i=0; iname, "gl_Position")) + continue; + + if(var->arraySize <= 1) + dSprintf((char*)output, sizeof(output), "varying %s %s;\r\n", var->type, var->name); + else + dSprintf((char*)output, sizeof(output), "varying %s %s[%d];\r\n", var->type, var->name, var->arraySize); + + stream.write( dStrlen((char*)output), output ); + } +} + +void VertexParamsDefGLSL::print( Stream &stream ) +{ + // find all the uniform variables and print them out + for( U32 i=0; i(LangElement::elementList[i]); + if( var ) + { + if( var->uniform ) + { + U8 output[256]; + if(var->arraySize <= 1) + dSprintf((char*)output, sizeof(output), "uniform %-8s %-15s;\r\n", var->type, var->name); + else + dSprintf((char*)output, sizeof(output), "uniform %-8s %-15s[%d];\r\n", var->type, var->name, var->arraySize); + + stream.write( dStrlen((char*)output), output ); + } + } + } + + const char *closer = "\r\n\r\nvoid main()\r\n{\r\n"; + stream.write( dStrlen(closer), closer ); +} + +void PixelParamsDefGLSL::print( Stream &stream ) +{ + // find all the uniform variables and print them out + for( U32 i=0; i(LangElement::elementList[i]); + if( var ) + { + if( var->uniform ) + { + U8 output[256]; + if(var->arraySize <= 1) + dSprintf((char*)output, sizeof(output), "uniform %-8s %-15s;\r\n", var->type, var->name); + else + dSprintf((char*)output, sizeof(output), "uniform %-8s %-15s[%d];\r\n", var->type, var->name, var->arraySize); + + stream.write( dStrlen((char*)output), output ); + } + } + } + + const char *closer = "\r\nvoid main()\r\n{\r\n"; + stream.write( dStrlen(closer), closer ); +} diff --git a/shaderGen/GLSL/shaderCompGLSL.h b/shaderGen/GLSL/shaderCompGLSL.h new file mode 100644 index 0000000..c14aff2 --- /dev/null +++ b/shaderGen/GLSL/shaderCompGLSL.h @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADERCOMP_GLSL_H_ +#define _SHADERCOMP_GLSL_H_ + +#ifndef _SHADERCOMP_H_ +#include "shaderGen/shaderComp.h" +#endif + + +class VertPixelConnectorGLSL : public ShaderConnector +{ +public: + + // ShaderConnector + virtual Var* getElement( RegisterType type, + U32 numElements = 1, + U32 numRegisters = -1 ); + virtual void setName( char *newName ); + virtual void reset(); + virtual void sortVars(); + + virtual void print( Stream &stream ); +}; + +class AppVertConnectorGLSL : public ShaderConnector +{ +public: + virtual Var* getElement( RegisterType type, + U32 numElements = 1, + U32 numRegisters = -1 ); + virtual void setName( char *newName ); + virtual void reset(); + virtual void sortVars(); + + virtual void print( Stream &stream ); +}; + + +class VertexParamsDefGLSL : public ParamsDef +{ +public: + virtual void print( Stream &stream ); +}; + + +class PixelParamsDefGLSL : public ParamsDef +{ +public: + virtual void print( Stream &stream ); +}; + +#endif // _SHADERCOMP_GLSL_H_ \ No newline at end of file diff --git a/shaderGen/GLSL/shaderFeatureGLSL.cpp b/shaderGen/GLSL/shaderFeatureGLSL.cpp new file mode 100644 index 0000000..20a296f --- /dev/null +++ b/shaderGen/GLSL/shaderFeatureGLSL.cpp @@ -0,0 +1,1659 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/GLSL/shaderFeatureGLSL.h" + +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/shaderGenVars.h" +#include "gfx/gfxDevice.h" +#include "materials/matInstance.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" +#include "core/util/autoPtr.h" + +#include "lighting/advanced/advancedLightBinManager.h" + +LangElement * ShaderFeatureGLSL::setupTexSpaceMat( Vector &, // componentList + Var **texSpaceMat ) +{ + Var *N = (Var*) LangElement::find( "normal" ); + Var *B = (Var*) LangElement::find( "B" ); + Var *T = (Var*) LangElement::find( "T" ); + + // setup matrix var + *texSpaceMat = new Var; + (*texSpaceMat)->setType( "mat3" ); + (*texSpaceMat)->setName( "objToTangentSpace" ); + + MultiLine * meta = new MultiLine; + + // Recreate the binormal if we don't have one. + if ( !B ) + { + B = new Var; + B->setType( "vec3" ); + B->setName( "B" ); + meta->addStatement( new GenOp( " @ = cross( @, normalize(@) );\r\n", new DecOp( B ), T, N ) ); + } + + meta->addStatement( new GenOp( " @;\r\n", new DecOp( *texSpaceMat ) ) ); + meta->addStatement( new GenOp( " @[0] = vec3(@.x, @.x, normalize(@).x);\r\n", *texSpaceMat, T, B, N ) ); + meta->addStatement( new GenOp( " @[1] = vec3(@.y, @.y, normalize(@).y);\r\n", *texSpaceMat, T, B, N ) ); + meta->addStatement( new GenOp( " @[2] = vec3(@.z, @.z, normalize(@).z);\r\n", *texSpaceMat, T, B, N ) ); + + return meta; +} + +LangElement* ShaderFeatureGLSL::assignColor( LangElement *elem, + Material::BlendOp blend, + LangElement *lerpElem, + ShaderFeature::OutputTarget outputTarget ) +{ + + // search for color var + Var *color = (Var*) LangElement::find( getOutputTargetVarName(outputTarget) ); + + if ( !color ) + { + // create color var + color = new Var; + color->setName( getOutputTargetVarName( outputTarget ) ); + color->setType( "vec4" ); + + return new GenOp( "@ = @", new DecOp(color), elem ); + } + + LangElement *assign; + + switch ( blend ) + { + case Material::Add: + assign = new GenOp( "@ += @", color, elem ); + break; + + case Material::Sub: + assign = new GenOp( "@ -= @", color, elem ); + break; + + case Material::Mul: + assign = new GenOp( "@ *= @", color, elem ); + break; + + case Material::AddAlpha: + assign = new GenOp( "@ += @ * @.a", color, elem, elem ); + break; + + case Material::LerpAlpha: + if ( !lerpElem ) + lerpElem = elem; + assign = new GenOp( "@.rgb = mix( @.rgb, (@).rgb, (@).a )", color, elem, color, lerpElem ); + break; + + case Material::ToneMap: + assign = new GenOp( "@ = 1.0 - exp(-1.0 * @ * @)", color, color, elem ); + break; + + default: + AssertFatal(false, "Unrecognized color blendOp"); + // Fallthru + + case Material::None: + assign = new GenOp( "@ = @", color, elem ); + break; + } + + return assign; +} + + +LangElement *ShaderFeatureGLSL::expandNormalMap( LangElement *sampleNormalOp, + LangElement *normalDecl, + LangElement *normalVar, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + if ( fd.features.hasFeature( MFT_IsDXTnm, getProcessIndex() ) ) + { + // DXT Swizzle trick + meta->addStatement( new GenOp( " @ = vec4( @.ag * 2.0 - 1.0, 0.0, 0.0 ); // DXTnm\r\n", normalDecl, sampleNormalOp ) ); + meta->addStatement( new GenOp( " @.z = sqrt( 1.0 - dot( @.xy, @.xy ) ); // DXTnm\r\n", normalVar, normalVar, normalVar ) ); + } + else + { + meta->addStatement( new GenOp( " @ = @;\r\n", normalDecl, sampleNormalOp ) ); + meta->addStatement( new GenOp( " @.xyz = @.xyz * 2.0 - 1.0;\r\n", normalVar, normalVar ) ); + } + + return meta; +} + +ShaderFeatureGLSL::ShaderFeatureGLSL() +{ + output = NULL; +} + +Var * ShaderFeatureGLSL::getVertTexCoord( const String &name ) +{ + Var *inTex = NULL; + + for( U32 i=0; iname, name.c_str() ) ) + { + inTex = dynamic_cast( LangElement::elementList[i] ); + break; + } + } + + return inTex; +} + +Var* ShaderFeatureGLSL::getOutObjToTangentSpace( Vector &componentList, + MultiLine *meta ) +{ + Var *outObjToTangentSpace = (Var*)LangElement::find( "objToTangentSpace" ); + if ( !outObjToTangentSpace ) + meta->addStatement( setupTexSpaceMat( componentList, &outObjToTangentSpace ) ); + + return outObjToTangentSpace; +} + +Var* ShaderFeatureGLSL::getOutWorldToTangent( Vector &componentList, + MultiLine *meta ) +{ + Var *outWorldToTangent = (Var*)LangElement::find( "worldToTangent" ); + if ( !outWorldToTangent ) + { + Var *texSpaceMat = getOutObjToTangentSpace( componentList, meta ); + + // turn obj->tangent into world->tangent + Var *worldToTangent = new Var; + worldToTangent->setType( "mat3x3" ); + worldToTangent->setName( "worldToTangent" ); + LangElement *worldToTangentDecl = new DecOp( worldToTangent ); + + // Get the world->obj transform + Var *worldToObj = new Var; + worldToObj->setType( "mat4x4" ); + worldToObj->setName( "worldToObj" ); + worldToObj->uniform = true; + worldToObj->constSortPos = cspPrimitive; + + // assign world->tangent transform + meta->addStatement( new GenOp( " @ = @ * mat3x3( @[0].xyz, @[1].xyz, @[2].xyz );\r\n", worldToTangentDecl, texSpaceMat, worldToObj, worldToObj, worldToObj ) ); + + // send transform to pixel shader + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + outWorldToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + outWorldToTangent->setName( "outWorldToTangent" ); + outWorldToTangent->setType( "mat3x3" ); + meta->addStatement( new GenOp( " @ = @;\r\n", outWorldToTangent, worldToTangent ) ); + } + + return outWorldToTangent; +} + +Var* ShaderFeatureGLSL::getOutViewToTangent( Vector &componentList, + MultiLine *meta ) +{ + Var *outViewToTangent = (Var*)LangElement::find( "outViewToTangent" ); + if ( !outViewToTangent ) + { + Var *texSpaceMat = getOutObjToTangentSpace( componentList, meta ); + + // turn obj->tangent into world->tangent + Var *viewToTangent = new Var; + viewToTangent->setType( "mat3" ); + viewToTangent->setName( "viewToTangent" ); + LangElement *viewToTangentDecl = new DecOp( viewToTangent ); + + // Get the view->obj transform + Var *viewToObj = new Var; + viewToObj->setType( "mat4" ); + viewToObj->setName( "viewToObj" ); + viewToObj->uniform = true; + viewToObj->constSortPos = cspPrimitive; + + // assign world->tangent transform + meta->addStatement( new GenOp( " mat3 mat3ViewToObj;\r\n" ) ); + meta->addStatement( new GenOp( " mat3ViewToObj[0] = viewToObj[0].xyz;\r\n" ) ); + meta->addStatement( new GenOp( " mat3ViewToObj[1] = viewToObj[1].xyz;\r\n" ) ); + meta->addStatement( new GenOp( " mat3ViewToObj[2] = viewToObj[2].xyz;\r\n" ) ); + meta->addStatement( new GenOp( " @ = @ * mat3ViewToObj;\r\n", viewToTangentDecl, texSpaceMat ) ); + + // send transform to pixel shader + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + outViewToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + outViewToTangent->setName( "outViewToTangent" ); + outViewToTangent->setType( "mat3" ); + meta->addStatement( new GenOp( " @ = @;\r\n", outViewToTangent, viewToTangent ) ); + } + + return outViewToTangent; +} + + +Var* ShaderFeatureGLSL::getOutTexCoord( const char *name, + const char *type, + bool mapsToSampler, + bool useTexAnim, + MultiLine *meta, + Vector &componentList ) +{ + String outTexName = String::ToString( "out_%s", name ); + Var *texCoord = (Var*)LangElement::find( outTexName ); + if ( !texCoord ) + { + Var *inTex = getVertTexCoord( name ); + AssertFatal( inTex, "ShaderFeatureGLSL::getOutTexCoord - Unknown vertex input coord!" ); + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + texCoord = connectComp->getElement( RT_TEXCOORD ); + texCoord->setName( outTexName ); + texCoord->setType( type ); + texCoord->mapsToSampler = mapsToSampler; + + if( useTexAnim ) + { + inTex->setType( "vec4" ); + + // create texture mat var + Var *texMat = new Var; + texMat->setType( "mat4" ); + texMat->setName( "texMat" ); + texMat->uniform = true; + texMat->constSortPos = cspPass; + + meta->addStatement( new GenOp( " @ = vec2(@ * @);\r\n", texCoord, texMat, inTex ) ); + } + else + meta->addStatement( new GenOp( " @ = @;\r\n", texCoord, inTex ) ); + } + + AssertFatal( dStrcmp( type, (const char*)texCoord->type ) == 0, + "ShaderFeatureGLSL::getOutTexCoord - Type mismatch!" ); + + return texCoord; +} + +Var* ShaderFeatureGLSL::getInTexCoord( const char *name, + const char *type, + bool mapsToSampler, + Vector &componentList ) +{ + Var* texCoord = (Var*)LangElement::find( name ); + if ( !texCoord ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + texCoord = connectComp->getElement( RT_TEXCOORD ); + texCoord->setName( name ); + texCoord->setType( type ); + texCoord->mapsToSampler = mapsToSampler; + } + + AssertFatal( dStrcmp( type, (const char*)texCoord->type ) == 0, + "ShaderFeatureGLSL::getInTexCoord - Type mismatch!" ); + + return texCoord; +} + +Var* ShaderFeatureGLSL::getInWorldToTangent( Vector &componentList ) +{ + Var *worldToTangent = (Var*)LangElement::find( "worldToTangent" ); + if ( !worldToTangent ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + worldToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + worldToTangent->setName( "outWorldToTangent" ); + worldToTangent->setType( "mat3x3" ); + } + + return worldToTangent; +} + +Var* ShaderFeatureGLSL::getInViewToTangent( Vector &componentList ) +{ + Var *viewToTangent = (Var*)LangElement::find( "outViewToTangent" ); + if ( !viewToTangent ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + viewToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + viewToTangent->setName( "outViewToTangent" ); + viewToTangent->setType( "mat3" ); + } + + return viewToTangent; +} + + +Var* ShaderFeatureGLSL::getNormalMapTex() +{ + Var *normalMap = (Var*)LangElement::find( "bumpMap" ); + if ( !normalMap ) + { + normalMap = new Var; + normalMap->setType( "sampler2D" ); + normalMap->setName( "bumpMap" ); + normalMap->uniform = true; + normalMap->sampler = true; + normalMap->constNum = Var::getTexUnitNum(); + } + + return normalMap; +} + +//**************************************************************************** +// Base Texture +//**************************************************************************** + +void DiffuseMapFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + getOutTexCoord( "texCoord", + "vec2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + output = meta; +} + +void DiffuseMapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + Var *inTex = getInTexCoord( "out_texCoord", "vec2", true, componentList ); + + // create texture var + Var *diffuseMap = new Var; + diffuseMap->setType( "sampler2D" ); + diffuseMap->setName( "diffuseMap" ); + diffuseMap->uniform = true; + diffuseMap->sampler = true; + diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + if ( fd.features[MFT_CubeMap] ) + { + MultiLine * meta = new MultiLine; + + // create sample color + Var *diffColor = new Var; + diffColor->setType( "vec4" ); + diffColor->setName( "diffuseColor" ); + LangElement *colorDecl = new DecOp( diffColor ); + + meta->addStatement( new GenOp( " @ = texture2D(@, @);\r\n", + colorDecl, + diffuseMap, + inTex ) ); + + meta->addStatement( new GenOp( " @;\r\n", assignColor( diffColor, Material::Mul ) ) ); + output = meta; + } + else + { + LangElement *statement = new GenOp( "texture2D(@, @)", diffuseMap, inTex ); + output = new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ); + } + +} + +ShaderFeature::Resources DiffuseMapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void DiffuseMapFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_DiffuseMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; +} + + +//**************************************************************************** +// Overlay Texture +//**************************************************************************** + +void OverlayTexFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *inTex = getVertTexCoord( "texCoord2" ); + AssertFatal( inTex, "OverlayTexFeatGLSL::processVert() - The second UV set was not found!" ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "outTexCoord2" ); + outTex->setType( "vec2" ); + outTex->mapsToSampler = true; + + if( fd.features[MFT_TexAnim] ) + { + inTex->setType( "vec4" ); + + // Find or create the texture matrix. + Var *texMat = (Var*)LangElement::find( "texMat" ); + if ( !texMat ) + { + texMat = new Var; + texMat->setType( "mat4x4" ); + texMat->setName( "texMat" ); + texMat->uniform = true; + texMat->constSortPos = cspPass; + } + + output = new GenOp( " @ = @ * @;\r\n", outTex, texMat, inTex ); + return; + } + + // setup language elements to output incoming tex coords to output + output = new GenOp( " @ = @;\r\n", outTex, inTex ); +} + +void OverlayTexFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inTex = connectComp->getElement( RT_TEXCOORD ); + inTex->setName( "outTexCoord2" ); + inTex->setType( "vec2" ); + inTex->mapsToSampler = true; + + // create texture var + Var *diffuseMap = new Var; + diffuseMap->setType( "sampler2D" ); + diffuseMap->setName( "overlayMap" ); + diffuseMap->uniform = true; + diffuseMap->sampler = true; + diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + LangElement *statement = new GenOp( "texture2D(@, @)", diffuseMap, inTex ); + output = new GenOp( " @;\r\n", assignColor( statement, Material::LerpAlpha ) ); +} + +ShaderFeature::Resources OverlayTexFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + return res; +} + +void OverlayTexFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_OverlayMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; +} + + +//**************************************************************************** +// Diffuse color +//**************************************************************************** + +void DiffuseFeatureGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *diffuseMaterialColor = new Var; + diffuseMaterialColor->setType( "vec4" ); + diffuseMaterialColor->setName( "diffuseMaterialColor" ); + diffuseMaterialColor->uniform = true; + diffuseMaterialColor->constSortPos = cspPotentialPrimitive; + + MultiLine * meta = new MultiLine; + meta->addStatement( new GenOp( " @;\r\n", assignColor( diffuseMaterialColor, Material::Add ) ) ); + output = meta; +} + + +//**************************************************************************** +// Lightmap +//**************************************************************************** + +void LightmapFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab tex register from incoming vert + Var *inTex = (Var*) LangElement::find( "texCoord2" ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "outTexCoord2" ); + outTex->setType( "vec2" ); + outTex->mapsToSampler = true; + + // setup language elements to output incoming tex coords to output + output = new GenOp( " @ = @;\r\n", outTex, inTex ); +} + +void LightmapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inTex = connectComp->getElement( RT_TEXCOORD ); + inTex->setName( "outTexCoord2" ); + inTex->setType( "vec2" ); + inTex->mapsToSampler = true; + + // create texture var + Var *lightMap = new Var; + lightMap->setType( "sampler2D" ); + lightMap->setName( "lightMap" ); + lightMap->uniform = true; + lightMap->sampler = true; + lightMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + + // argh, pixel specular should prob use this too + if( fd.features[MFT_NormalMap] ) + { + Var *lmColor = new Var; + lmColor->setName( "lmColor" ); + lmColor->setType( "vec4" ); + LangElement *lmColorDecl = new DecOp( lmColor ); + + output = new GenOp( " @ = texture2D(@, @);\r\n", lmColorDecl, lightMap, inTex ); + return; + } + + // Add realtime lighting, if it is available + LangElement *statement = NULL; + if( fd.features[MFT_RTLighting] ) + { + // Advanced lighting is the only dynamic lighting supported right now + Var *inColor = (Var*) LangElement::find( "d_lightcolor" ); + if(inColor != NULL) + { + // Find out if RTLighting should be added or substituted + bool bPreProcessedLighting = false; + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + bPreProcessedLighting = lightBin->MRTLightmapsDuringPrePass(); + } + + // Lightmap has already been included in the advanced light bin, so + // no need to do any sampling or anything + if(bPreProcessedLighting) + statement = new GenOp( "vec4(@, 1.0)", inColor ); + else + statement = new GenOp( "texture2D(@, @) + vec4(@.rgb, 0.0)", lightMap, inTex, inColor ); + } + } + else + { + statement = new GenOp( "texture2D(@, @)", lightMap, inTex ); + } + + // Assign to proper render target + if( fd.features[MFT_LightbufferMRT] ) + output = new GenOp( " @;\r\n", assignColor( statement, Material::None, NULL, ShaderFeature::RenderTarget1 ) ); + else + output = new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ); +} + +ShaderFeature::Resources LightmapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void LightmapFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_LightMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; + else + passData.mTexType[ texIndex++ ] = Material::Lightmap; +} + +U32 LightmapFeatGLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + +//**************************************************************************** +// Tonemap +//**************************************************************************** + +void TonemapFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Grab the connector + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Set up the second set of texCoords + Var *inTex2 = getVertTexCoord( "texCoord2" ); + + if ( inTex2 ) + { + Var *outTex2 = connectComp->getElement( RT_TEXCOORD ); + outTex2->setName( "toneMapCoord" ); + outTex2->setType( "vec2" ); + outTex2->mapsToSampler = true; + + output = new GenOp( " @ = @;\r\n", outTex2, inTex2 ); + } +} + +void TonemapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Grab connector + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *inTex2 = connectComp->getElement( RT_TEXCOORD ); + inTex2->setName( "toneMapCoord" ); + inTex2->setType( "vec2" ); + inTex2->mapsToSampler = true; + + // create texture var + Var *toneMap = new Var; + toneMap->setType( "sampler2D" ); + toneMap->setName( "toneMap" ); + toneMap->uniform = true; + toneMap->sampler = true; + toneMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + MultiLine * meta = new MultiLine; + + // First get the toneMap color + Var *toneMapColor = new Var; + toneMapColor->setType( "vec4" ); + toneMapColor->setName( "toneMapColor" ); + LangElement *toneMapColorDecl = new DecOp( toneMapColor ); + + meta->addStatement( new GenOp( " @ = texture2D(@, @);\r\n", toneMapColorDecl, toneMap, inTex2 ) ); + + // We do a different calculation if there is a diffuse map or not + Material::BlendOp blendOp = Material::Mul; + if ( fd.features[MFT_DiffuseMap] ) + { + // Reverse the tonemap + meta->addStatement( new GenOp( " @ = -1.0 * log(1.0 - @);\r\n", toneMapColor, toneMapColor ) ); + + // Re-tonemap with the current color factored in + blendOp = Material::ToneMap; + } + + // Find out if RTLighting should be added + bool bPreProcessedLighting = false; + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + bPreProcessedLighting = lightBin->MRTLightmapsDuringPrePass(); + } + + // Add in the realtime lighting contribution + if ( fd.features[MFT_RTLighting] ) + { + // Right now, only Advanced Lighting is supported + Var *inColor = (Var*) LangElement::find( "d_lightcolor" ); + if(inColor != NULL) + { + // Assign value in d_lightcolor to toneMapColor if it exists. This is + // the dynamic light buffer, and it already has the tonemap included + if(bPreProcessedLighting) + meta->addStatement( new GenOp( " @.rgb = @;\r\n", toneMapColor, inColor ) ); + else + meta->addStatement( new GenOp( " @.rgb += @.rgb;\r\n", toneMapColor, inColor ) ); + } + } + + // Assign to proper render target + if( fd.features[MFT_LightbufferMRT] ) + meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); + else + meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, blendOp ) ) ); + + output = meta; +} + +ShaderFeature::Resources TonemapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void TonemapFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_ToneMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::ToneMapTex; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} + +U32 TonemapFeatGLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + +//**************************************************************************** +// pureLIGHT Lighting +//**************************************************************************** + +void VertLitGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If we have a lightMap or toneMap then our lighting will be + // handled by the MFT_LightMap or MFT_ToneNamp feature instead + if ( fd.features[MFT_LightMap] || fd.features[MFT_ToneMap] ) + { + output = NULL; + return; + } + + // Search for vert color + Var *inColor = (Var*) LangElement::find( "diffuse" ); + + // If there isn't a vertex color then we can't do anything + if( !inColor ) + { + output = NULL; + return; + } + + // Grab the connector color + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outColor = connectComp->getElement( RT_COLOR ); + outColor->setName( "vertColor" ); + outColor->setType( "vec4" ); + + output = new GenOp( " @ = @;\r\n", outColor, inColor ); +} + +void VertLitGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If we have a lightMap or toneMap then our lighting will be + // handled by the MFT_LightMap or MFT_ToneNamp feature instead + if ( fd.features[MFT_LightMap] || fd.features[MFT_ToneMap] ) + { + output = NULL; + return; + } + + // Grab the connector color register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *vertColor = connectComp->getElement( RT_COLOR ); + vertColor->setName( "vertColor" ); + vertColor->setType( "vec4" ); + + MultiLine * meta = new MultiLine; + + // Defaults (no diffuse map) + Material::BlendOp blendOp = Material::Mul; + LangElement *outColor = vertColor; + + // We do a different calculation if there is a diffuse map or not + if ( fd.features[MFT_DiffuseMap] || fd.features[MFT_VertLitTone] ) + { + Var * finalVertColor = new Var; + finalVertColor->setName( "finalVertColor" ); + finalVertColor->setType( "vec4" ); + LangElement *finalVertColorDecl = new DecOp( finalVertColor ); + + // Reverse the tonemap + meta->addStatement( new GenOp( " @ = -1.0 * log(1.0 - @);\r\n", finalVertColorDecl, vertColor ) ); + + // Set the blend op to tonemap + blendOp = Material::ToneMap; + outColor = finalVertColor; + + } + + // Add in the realtime lighting contribution, if applicable + if ( fd.features[MFT_RTLighting] ) + { + Var *rtLightingColor = (Var*) LangElement::find( "d_lightcolor" ); + if(rtLightingColor != NULL) + { + // Find out if RTLighting should be added or substituted + bool bPreProcessedLighting = false; + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + bPreProcessedLighting = lightBin->MRTLightmapsDuringPrePass(); + } + + // Assign value in d_lightcolor to toneMapColor if it exists. This is + // the dynamic light buffer, and it already has the baked-vertex-color + // included in it + if(bPreProcessedLighting) + outColor = new GenOp( "vec4(@.rgb, 1.0)", rtLightingColor ); + else + outColor = new GenOp( "vec4(@.rgb, 0.0) + @", rtLightingColor, outColor ); + } + } + + // Output the color + if ( fd.features[MFT_LightbufferMRT] ) + meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); + else + meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, blendOp ) ) ); + + output = meta; +} + +U32 VertLitGLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + + +//**************************************************************************** +// Detail map +//**************************************************************************** + +void DetailFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab incoming texture coords + Var *inTex = getVertTexCoord( "texCoord" ); + + // create detail variable + Var *detScale = new Var; + detScale->setType( "vec2" ); + detScale->setName( "detailScale" ); + detScale->uniform = true; + detScale->constSortPos = cspPass; + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "detCoord" ); + outTex->setType( "vec2" ); + outTex->mapsToSampler = true; + + if( fd.features[MFT_TexAnim] ) + { + inTex->setType( "vec4" ); + + // Find or create the texture matrix. + Var *texMat = (Var*)LangElement::find( "texMat" ); + if ( !texMat ) + { + texMat = new Var; + texMat->setType( "mat4x4" ); + texMat->setName( "texMat" ); + texMat->uniform = true; + texMat->constSortPos = cspPass; + } + + output = new GenOp( " @ = (@ * @) * @;\r\n", outTex, texMat, inTex, detScale ); + return; + } + + // setup output to mul texCoord by detail scale + output = new GenOp( " @ = @ * @;\r\n", outTex, inTex, detScale ); +} + +void DetailFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inTex = connectComp->getElement( RT_TEXCOORD ); + inTex->setName( "detCoord" ); + inTex->setType( "vec2" ); + inTex->mapsToSampler = true; + + // create texture var + Var *detailMap = new Var; + detailMap->setType( "sampler2D" ); + detailMap->setName( "detailMap" ); + detailMap->uniform = true; + detailMap->sampler = true; + detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // We're doing the standard greyscale detail map + // technique which can darken and lighten the + // diffuse texture. + + // TODO: We could add a feature to toggle between this + // and a simple multiplication with the detail map. + + LangElement *statement = new GenOp( "( texture2D(@, @) * 2.0 ) - 1.0", detailMap, inTex ); + output = new GenOp( " @;\r\n", assignColor( statement, Material::Add ) ); +} + +ShaderFeature::Resources DetailFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void DetailFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_DetailMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; +} + + +//**************************************************************************** +// Vertex position +//**************************************************************************** + +void VertPositionGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // First check for an input position from a previous feature + // then look for the default vertex position. + Var *inPosition = (Var*)LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*)LangElement::find( "position" ); + + // grab connector position + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outPosition = connectComp->getElement( RT_POSITION ); + outPosition->setName( "gl_Position" ); + + // create modelview variable + Var *modelview = new Var; + modelview->setType( "mat4" ); + modelview->setName( "modelview" ); + modelview->uniform = true; + modelview->constSortPos = cspPrimitive; + + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( " @ = @ * vec4(@.xyz,1);\r\n", outPosition, modelview, inPosition ) ); + output = meta; +} + + +//**************************************************************************** +// Reflect Cubemap +//**************************************************************************** + +void ReflectCubeFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine * meta = new MultiLine; + + // If a base or bump tex is present in the material, but not in the + // current pass - we need to add one to the current pass to use + // its alpha channel as a gloss map. Here we just need the tex coords. + if( !fd.features[MFT_DiffuseMap] && + !fd.features[MFT_NormalMap] ) + { + if( fd.materialFeatures[MFT_DiffuseMap] || + fd.materialFeatures[MFT_NormalMap] ) + { + // find incoming texture var + Var *inTex = getVertTexCoord( "texCoord" ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "outTexCoord" ); + outTex->setType( "vec2" ); + outTex->mapsToSampler = true; + + // setup language elements to output incoming tex coords to output + meta->addStatement( new GenOp( " @ = @;\r\n", outTex, inTex ) ); + } + } + + // create cubeTrans + Var *cubeTrans = new Var; + cubeTrans->setType( "mat3" ); + cubeTrans->setName( "cubeTrans" ); + cubeTrans->uniform = true; + cubeTrans->constSortPos = cspPrimitive; + + // create cubeEye position + Var *cubeEyePos = new Var; + cubeEyePos->setType( "vec3" ); + cubeEyePos->setName( "cubeEyePos" ); + cubeEyePos->uniform = true; + cubeEyePos->constSortPos = cspPrimitive; + + // search for vert normal + Var *inNormal = (Var*) LangElement::find( "normal" ); + + // cube vert position + Var * cubeVertPos = new Var; + cubeVertPos->setName( "cubeVertPos" ); + cubeVertPos->setType( "vec3" ); + LangElement *cubeVertPosDecl = new DecOp( cubeVertPos ); + + meta->addStatement( new GenOp( " @ = @ * @.xyz;\r\n", + cubeVertPosDecl, cubeTrans, LangElement::find( "position" ) ) ); + + // cube normal + Var * cubeNormal = new Var; + cubeNormal->setName( "cubeNormal" ); + cubeNormal->setType( "vec3" ); + LangElement *cubeNormDecl = new DecOp( cubeNormal ); + + meta->addStatement( new GenOp( " @ = normalize( @ * normalize(@).xyz );\r\n", + cubeNormDecl, cubeTrans, inNormal ) ); + + // eye to vert + Var * eyeToVert = new Var; + eyeToVert->setName( "eyeToVert" ); + eyeToVert->setType( "vec3" ); + LangElement *e2vDecl = new DecOp( eyeToVert ); + + meta->addStatement( new GenOp( " @ = @ - @;\r\n", + e2vDecl, cubeVertPos, cubeEyePos ) ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *reflectVec = connectComp->getElement( RT_TEXCOORD ); + reflectVec->setName( "reflectVec" ); + reflectVec->setType( "vec3" ); + reflectVec->mapsToSampler = true; + + meta->addStatement( new GenOp( " @ = reflect(@, @);\r\n", reflectVec, eyeToVert, cubeNormal ) ); + + output = meta; +} + +void ReflectCubeFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine * meta = new MultiLine; + Var *glossColor = NULL; + + // If a base or bump tex is present in the material, but not in the + // current pass - we need to add one to the current pass to use + // its alpha channel as a gloss map. + if( !fd.features[MFT_DiffuseMap] && + !fd.features[MFT_NormalMap] ) + { + if( fd.materialFeatures[MFT_DiffuseMap] || + fd.materialFeatures[MFT_NormalMap] ) + { + // grab connector texcoord register + Var *inTex = getInTexCoord( "outTexCoord", "vec2", true, componentList ); + + // create texture var + Var *newMap = new Var; + newMap->setType( "sampler2D" ); + newMap->setName( "glossMap" ); + newMap->uniform = true; + newMap->sampler = true; + newMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // create sample color + Var *color = new Var; + color->setType( "vec4" ); + color->setName( "diffuseColor" ); + LangElement *colorDecl = new DecOp( color ); + + glossColor = color; + + meta->addStatement( new GenOp( " @ = texture2D( @, @ );\r\n", colorDecl, newMap, inTex ) ); + } + } + else + { + glossColor = (Var*) LangElement::find( "diffuseColor" ); + if( !glossColor ) + glossColor = (Var*) LangElement::find( "bumpNormal" ); + } + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *reflectVec = connectComp->getElement( RT_TEXCOORD ); + reflectVec->setName( "reflectVec" ); + reflectVec->setType( "vec3" ); + reflectVec->mapsToSampler = true; + + // create cubemap var + Var *cubeMap = new Var; + cubeMap->setType( "samplerCube" ); + cubeMap->setName( "cubeMap" ); + cubeMap->uniform = true; + cubeMap->sampler = true; + cubeMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // TODO: Restore the lighting attenuation here! + Var *attn = NULL; + //if ( fd.materialFeatures[MFT_DynamicLight] ) + //attn = (Var*)LangElement::find("attn"); + //else + if ( fd.materialFeatures[MFT_RTLighting] ) + attn =(Var*)LangElement::find("d_NL_Att"); + + LangElement *texCube = new GenOp( "textureCube( @, @ )", cubeMap, reflectVec ); + LangElement *lerpVal = NULL; + Material::BlendOp blendOp = Material::LerpAlpha; + + // Note that the lerpVal needs to be a float4 so that + // it will work with the LerpAlpha blend. + + if ( glossColor ) + { + if ( attn ) + lerpVal = new GenOp( "@ * saturate( @ )", glossColor, attn ); + else + lerpVal = glossColor; + } + else + { + if ( attn ) + lerpVal = new GenOp( "saturate( @ ).xxxx", attn ); + else + blendOp = Material::None; + } + + meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) ); + output = meta; +} + +ShaderFeature::Resources ReflectCubeFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + if( fd.features[MFT_DiffuseMap] || + fd.features[MFT_NormalMap] ) + { + res.numTex = 1; + res.numTexReg = 1; + } + else + { + res.numTex = 2; + res.numTexReg = 2; + } + + return res; +} + +void ReflectCubeFeatGLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &stageFeatures, + RenderPassData &passData, + U32 &texIndex ) +{ + // set up a gloss map if one is not present in the current pass + // but is present in the current material stage + if( !passData.mFeatureData.features[MFT_DiffuseMap] && + !passData.mFeatureData.features[MFT_NormalMap] ) + { + GFXTextureObject *tex = stageDat.getTex( MFT_DetailMap ); + if ( tex && + stageFeatures.features[MFT_DiffuseMap] ) + passData.mTexSlot[ texIndex++ ].texObject = tex; + else + { + tex = stageDat.getTex( MFT_NormalMap ); + + if ( tex && + stageFeatures.features[ MFT_NormalMap ] ) + passData.mTexSlot[ texIndex++ ].texObject = tex; + } + } + + if( stageDat.getCubemap() ) + { + passData.mCubeMap = stageDat.getCubemap(); + passData.mTexType[texIndex++] = Material::Cube; + } + else + { + if( stageFeatures.features[MFT_CubeMap] ) + { + // assuming here that it is a scenegraph cubemap + passData.mTexType[texIndex++] = Material::SGCube; + } + } + +} + + +//**************************************************************************** +// RTLighting +//**************************************************************************** + +RTLightingFeatGLSL::RTLightingFeatGLSL() + : mDep( "shaders/common/gl/lighting.glsl" ) +{ + addDependency( &mDep ); +} + +void RTLightingFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Find the incoming vertex normal. + Var *inNormal = (Var*)LangElement::find( "normal" ); + + // Skip out on realtime lighting if we don't have a normal + // or we're doing some sort of baked lighting. + if ( !inNormal || + fd.features[MFT_LightMap] || + fd.features[MFT_ToneMap] || + fd.features[MFT_VertLit] ) + return; + + MultiLine *meta = new MultiLine; + + // Get the transform to world space. + Var *objTrans = (Var*) LangElement::find( "objTrans" ); + if ( !objTrans ) + { + objTrans = new Var; + objTrans->setType( "mat4x4" ); + objTrans->setName( "objTrans" ); + objTrans->uniform = true; + objTrans->constSortPos = cspPrimitive; + } + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // If there isn't a normal map then we need to pass + // the world space normal to the pixel shader ourselves. + if ( !fd.features[MFT_NormalMap] ) + { + Var *outNormal = connectComp->getElement( RT_TEXCOORD ); + outNormal->setName( "outWsNormal" ); + outNormal->setType( "vec3" ); + outNormal->mapsToSampler = false; + + // Transform the normal to world space. + meta->addStatement( new GenOp( " @ = mat3x3( @[0].xyz, @[1].xyz, @[2].xyz ) * normalize( @ );\r\n", outNormal, objTrans, objTrans, objTrans, inNormal ) ); + } + + // Get the input position. + Var *inPosition = (Var*)LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*)LangElement::find( "position" ); + + // We also need a world space position. + Var *outPosition = connectComp->getElement( RT_TEXCOORD ); + outPosition->setName( "wsPosition" ); + outPosition->setType( "vec3" ); + outPosition->mapsToSampler = false; + meta->addStatement( new GenOp( " @ = vec3( @ * vec4( @.xyz, 1 ) ).xyz;\r\n", outPosition, objTrans, inPosition ) ); + + output = meta; +} + +void RTLightingFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Skip out on realtime lighting if we don't have a normal + // or we're doing some sort of baked lighting. + // + // TODO: We can totally detect for this in the material + // feature setup... we should move it out of here! + // + if ( fd.features[MFT_LightMap] || fd.features[MFT_ToneMap] || fd.features[MFT_VertLit] ) + return; + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + MultiLine *meta = new MultiLine; + + // Look for a wsNormal or grab it from the connector. + Var *wsNormal = (Var*)LangElement::find( "wsNormal" ); + if ( !wsNormal ) + { + Var *outWsNormal = connectComp->getElement( RT_TEXCOORD ); + outWsNormal->setName( "outWsNormal" ); + outWsNormal->setType( "vec3" ); + + wsNormal = new Var( "wsNormal", "vec3" ); + + // If we loaded the normal its our resposibility + // to normalize it... the interpolators won't. + meta->addStatement( new GenOp( " @ = normalize( @ );\r\n", + new DecOp( wsNormal ), outWsNormal ) ); + } + + // Now the wsPosition. + Var *wsPosition = (Var*)LangElement::find( "wsPosition" ); + if ( !wsPosition ) + { + wsPosition = connectComp->getElement( RT_TEXCOORD ); + wsPosition->setName( "wsPosition" ); + wsPosition->setType( "vec3" ); + } + + // If we have a specular feature then we need to + // get the world space view vector to pass to the + // lighting calculation. + Var *wsView = new Var( "wsView", "vec3" ); + if ( fd.features[MFT_PixSpecular] ) + { + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if ( !eyePos ) + { + eyePos = new Var; + eyePos->setType( "vec3" ); + eyePos->setName( "eyePosWorld" ); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + meta->addStatement( new GenOp( " @ = normalize( @ - @ );\r\n", + new DecOp( wsView ), eyePos, wsPosition ) ); + } + else + meta->addStatement( new GenOp( " @ = vec3( 0 );\r\n", new DecOp( wsView ) ) ); + + // Create temporaries to hold results of lighting. + Var *rtShading = new Var( "rtShading", "vec4" ); + Var *specular = new Var( "specular", "vec4" ); + meta->addStatement( new GenOp( " @; @;\r\n", + new DecOp( rtShading ), new DecOp( specular ) ) ); + + // Calculate the diffuse shading and specular powers. + meta->addStatement( new GenOp( " compute4Lights( @, @, @, @, @ );\r\n", + wsView, wsPosition, wsNormal, rtShading, specular ) ); + + // Look for a light mask generated from a previous + // feature (this is done for BL terrain lightmaps). + Var *lightMask = (Var*)LangElement::find( "lightMask" ); + if ( lightMask ) + meta->addStatement( new GenOp( " @.rgb *= @;\r\n", rtShading, lightMask ) ); + + // Apply the lighting to the diffuse color. + LangElement *lighting = new GenOp( "vec4( @.rgb + ambient.rgb, 1 )", rtShading ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( lighting, Material::Mul ) ) ); + output = meta; +} + +ShaderFeature::Resources RTLightingFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // These features disable realtime lighting. + if ( !fd.features[MFT_LightMap] && + !fd.features[MFT_ToneMap] && + !fd.features[MFT_VertLit] ) + { + // If enabled we pass the position. + res.numTexReg = 1; + + // If there isn't a bump map then we pass the + // world space normal as well. + if ( !fd.features[MFT_NormalMap] ) + res.numTexReg++; + } + + return res; +} + + +//**************************************************************************** +// Fog +//**************************************************************************** + +FogFeatGLSL::FogFeatGLSL() + : mFogDep( "shaders/common/gl/torque.glsl" ) +{ + addDependency( &mFogDep ); +} + +void FogFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Grab a register for passing the world + // position to the shader. + Var *fogPos = connectComp->getElement( RT_TEXCOORD ); + fogPos->setName( "outFogPos" ); + fogPos->setType( "vec3" ); + + // First check for an input position from a previous feature + // then look for the default vertex position. + Var *position = (Var*)LangElement::find( "inPosition" ); + if ( !position ) + position = (Var*)LangElement::find( "position" ); + + Var *objTrans = (Var*) LangElement::find( "objTrans" ); + if(!objTrans) + { + objTrans = new Var; + objTrans->setType( "mat4" ); + objTrans->setName( "objTrans" ); + objTrans->uniform = true; + objTrans->constSortPos = cspPrimitive; + } + + MultiLine * meta = new MultiLine; + meta->addStatement( new GenOp( " @ = vec3( @ * vec4(@.xyz,1) );\r\n", fogPos, objTrans, position ) ); + + output = meta; +} + +void FogFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *fogColor = new Var; + fogColor->setType( "vec4" ); + fogColor->setName( "fogColor" ); + fogColor->uniform = true; + fogColor->constSortPos = cspPass; + + MultiLine *meta = new MultiLine; + + // If no color, then fog is only feature in pass + Var *color = (Var*) LangElement::find( "col" ); + if ( !color ) + meta->addStatement( new GenOp( " @;\r\n", assignColor( fogColor, Material::Mul ) ) ); + + else + { + Var *fogPos = connectComp->getElement( RT_TEXCOORD ); + fogPos->setName( "outFogPos" ); + fogPos->setType( "vec3" ); + + Var *fogData = new Var; + fogData->setType( "vec3" ); + fogData->setName( "fogData" ); + fogData->uniform = true; + fogData->constSortPos = cspPass; + + // grab the eye position + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if( !eyePos ) + { + eyePos = new Var; + eyePos->setType( "vec3" ); + eyePos->setName( "eyePosWorld" ); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + /// Get the fog amount. + Var *fogAmount = new Var( "fogAmount", "float" ); + meta->addStatement( new GenOp( " @ = computeSceneFog( @, @, @.r, @.g, @.b );\r\n", + new DecOp( fogAmount ), eyePos, fogPos, fogData, fogData, fogData ) ); + + // Lerp between the fog color and diffuse color. + LangElement *fogLerp = new GenOp( "mix( @.rgb, @.rgb, saturate( @ ) )", fogColor, color, fogAmount ); + meta->addStatement( new GenOp( " @.rgb = @;\r\n", color, fogLerp ) ); + } + + output = meta; +} + +ShaderFeature::Resources FogFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTexReg = 1; + return res; +} + + +//**************************************************************************** +// Visibility +//**************************************************************************** + +void VisibilityFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // create visibility var + Var *visibility = new Var; + visibility->setType( "float" ); + visibility->setName( "visibility" ); + visibility->uniform = true; + visibility->constSortPos = cspPass; + + // search for color var + Var *color = (Var*) LangElement::find( "col" ); + + // Looks like its going to be a multiline statement + MultiLine * meta = new MultiLine; + + if( !color ) + { + // create color var + color = new Var; + color->setType( "vec4" ); + color->setName( "col" ); + DecOp* colorDecl = new DecOp(color); + + // link it to ConnectData.shading + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inColor = connectComp->getElement( RT_COLOR ); + inColor->setName( "shading" ); + inColor->setType( "vec4" ); + + meta->addStatement( new GenOp( " @ = @;\r\n", colorDecl, inColor ) ); + } + + meta->addStatement( new GenOp( " @.w *= @;\r\n", color, visibility ) ); + + output = meta; +} + + +//**************************************************************************** +// ColorMultiply +//**************************************************************************** + +void ColorMultiplyFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *colorMultiply = new Var; + colorMultiply->setType( "vec4" ); + colorMultiply->setName( "colorMultiply" ); + colorMultiply->uniform = true; + colorMultiply->constSortPos = cspPass; + + // search for color var + Var *color = (Var*) LangElement::find( "col" ); + if (color) + { + MultiLine* meta = new MultiLine; + LangElement* statement = new GenOp("mix(@.rgb, @.rgb, @.a)", color, colorMultiply, colorMultiply); + meta->addStatement(new GenOp(" @.rgb = @;\r\n", color, statement)); + output = meta; + } +} + + +//**************************************************************************** +// AlphaTest +//**************************************************************************** + +void AlphaTestGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If we're below SM3 and don't have a depth output + // feature then don't waste an instruction here. + if ( GFX->getPixelShaderVersion() < 3.0 && + !fd.features[ MFT_EyeSpaceDepthOut ] && + !fd.features[ MFT_DepthOut ] ) + { + output = NULL; + return; + } + + // If we don't have a color var then we cannot do an alpha test. + Var *color = (Var*)LangElement::find( "col" ); + if ( !color ) + { + output = NULL; + return; + } + + // Now grab the alpha test value. + Var *alphaTestVal = new Var; + alphaTestVal->setType( "float" ); + alphaTestVal->setName( "alphaTestValue" ); + alphaTestVal->uniform = true; + alphaTestVal->constSortPos = cspPrimitive; + + // Do the clip. + output = new GenOp( " if ( ( @.a - @ ) < 0 ) discard;\r\n", color, alphaTestVal ); +} + + +//**************************************************************************** +// GlowMask +//**************************************************************************** + +void GlowMaskGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + output = NULL; + + // Get the output color... and make it black to mask out + // glow passes rendered before us. + // + // The shader compiler will optimize out all the other + // code above that doesn't contribute to the alpha mask. + Var *color = (Var*)LangElement::find( "col" ); + if ( color ) + output = new GenOp( " @.rgb = 0;\r\n", color ); +} diff --git a/shaderGen/GLSL/shaderFeatureGLSL.h b/shaderGen/GLSL/shaderFeatureGLSL.h new file mode 100644 index 0000000..d985ccb --- /dev/null +++ b/shaderGen/GLSL/shaderFeatureGLSL.h @@ -0,0 +1,447 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ +#define _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ + +#ifndef _SHADERFEATURE_H_ +#include "shaderGen/shaderFeature.h" +#endif + +struct LangElement; +struct MaterialFeatureData; +struct RenderPassData; + + +class ShaderFeatureGLSL : public ShaderFeature +{ +public: + ShaderFeatureGLSL(); + + /// + Var* getOutTexCoord( const char *name, + const char *type, + bool mapsToSampler, + bool useTexAnim, + MultiLine *meta, + Vector &componentList ); + + /// Returns an input texture coord by name adding it + /// to the input connector if it doesn't exist. + static Var* getInTexCoord( const char *name, + const char *type, + bool mapsToSampler, + Vector &componentList ); + + /// Returns the "objToTangentSpace" transform or creates one if this + /// is the first feature to need it. + Var* getOutObjToTangentSpace( Vector &componentList, + MultiLine *meta ); + + /// Returns the existing output "worldToTangent" transform or + /// creates one if this is the first feature to need it. + Var* getOutWorldToTangent( Vector &componentList, + MultiLine *meta ); + + /// Returns the input "worldToTanget" space transform + /// adding it to the input connector if it doesn't exist. + static Var* getInWorldToTangent( Vector &componentList ); + + /// Returns the existing output "viewToTangent" transform or + /// creates one if this is the first feature to need it. + Var* getOutViewToTangent( Vector &componentList, + MultiLine *meta ); + + /// Returns the input "viewToTangent" space transform + /// adding it to the input connector if it doesn't exist. + static Var* getInViewToTangent( Vector &componentList ); + + /// Returns the input normal map texture. + static Var* getNormalMapTex(); + + // ShaderFeature + Var* getVertTexCoord( const String &name ); + LangElement* setupTexSpaceMat( Vector &componentList, Var **texSpaceMat ); + LangElement* assignColor( LangElement *elem, Material::BlendOp blend, LangElement *lerpElem = NULL, ShaderFeature::OutputTarget outputTarget = ShaderFeature::DefaultTarget ); + LangElement* expandNormalMap( LangElement *sampleNormalOp, LangElement *normalDecl, LangElement *normalVar, const MaterialFeatureData &fd ); +}; + + +class NamedFeatureGLSL : public ShaderFeatureGLSL +{ +protected: + String mName; + +public: + NamedFeatureGLSL( const String &name ) + : mName( name ) + {} + + virtual String getName() { return mName; } +}; + + +/// Vertex position +class VertPositionGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Vert Position"; + } + + virtual void determineFeature( Material *material, + const GFXVertexFormat *vertexFormat, + U32 stageNum, + const FeatureType &type, + const FeatureSet &features, + MaterialFeatureData *outFeatureData ) + { + // This feature is always on! + outFeatureData->features.addFeature( type ); + } + +}; + + +/// Vertex lighting based on the normal and the light +/// direction passed through the vertex color. +class RTLightingFeatGLSL : public ShaderFeatureGLSL +{ +protected: + + ShaderIncludeDependency mDep; + +public: + + RTLightingFeatGLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() + { + return "RT Lighting"; + } +}; + + +/// Base texture +class DiffuseMapFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Base Texture"; + } +}; + + +/// Overlay texture +class OverlayTexFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Overlay Texture"; + } +}; + + +/// Diffuse color +class DiffuseFeatureGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual String getName() + { + return "Diffuse Color"; + } +}; + + +/// Lightmap +class LightmapFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Lightmap"; + } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; +}; + + +/// Tonemap +class TonemapFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Tonemap"; + } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; +}; + + +/// Baked lighting stored on the vertex color +class VertLitGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual String getName() + { + return "Vert Lit"; + } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; +}; + + +/// Detail map +class DetailFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::Mul; } + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Detail"; + } +}; + + +/// Reflect Cubemap +class ReflectCubeFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Reflect Cube"; + } +}; + + +/// Fog +class FogFeatGLSL : public ShaderFeatureGLSL +{ +protected: + + ShaderIncludeDependency mFogDep; + +public: + FogFeatGLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::LerpAlpha; } + + virtual String getName() + { + return "Fog"; + } +}; + + +/// Tex Anim +class TexAnimGLSL : public ShaderFeatureGLSL +{ +public: + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Texture Animation"; + } +}; + + +/// Visibility +class VisibilityFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Visibility"; + } +}; + + +/// ColorMultiply +class ColorMultiplyFeatGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Color Multiply"; + } +}; + + +/// +class AlphaTestGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Alpha Test"; + } +}; + + +/// Special feature used to mask out the RGB color for +/// non-glow passes of glow materials. +/// @see RenderGlowMgr +class GlowMaskGLSL : public ShaderFeatureGLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Glow Mask"; + } +}; + + +#endif // _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ diff --git a/shaderGen/GLSL/shaderGenGLSL.cpp b/shaderGen/GLSL/shaderGenGLSL.cpp new file mode 100644 index 0000000..faf6704 --- /dev/null +++ b/shaderGen/GLSL/shaderGenGLSL.cpp @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/GLSL/shaderGenGLSL.h" + +#include "shaderGen/GLSL/shaderCompGLSL.h" + + +void ShaderGenPrinterGLSL::printShaderHeader( Stream& stream ) +{ + const char *header1 = "//*****************************************************************************\r\n"; + const char *header2 = "// Torque -- GLSL procedural shader\r\n"; + + stream.write( dStrlen(header1), header1 ); + stream.write( dStrlen(header2), header2 ); + stream.write( dStrlen(header1), header1 ); + + // Cheap HLSL compatibility. + const char* header3 = "#include \"shaders/common/gl/hlslCompat.glsl\"\r\n"; + stream.write( dStrlen(header3), header3 ); + + const char* header4 = "\r\n"; + stream.write( dStrlen(header4), header4 ); +} + +void ShaderGenPrinterGLSL::printMainComment( Stream& stream ) +{ + // Print out main function definition + const char * header5 = "// Main \r\n"; + const char * line = "//-----------------------------------------------------------------------------\r\n"; + + stream.write( dStrlen(line), line ); + stream.write( dStrlen(header5), header5 ); + stream.write( dStrlen(line), line ); +} + +void ShaderGenPrinterGLSL::printVertexShaderCloser( Stream& stream ) +{ + const char *closer = "}\r\n"; + stream.write( dStrlen(closer), closer ); +} + +void ShaderGenPrinterGLSL::printPixelShaderOutputStruct( Stream& stream, const MaterialFeatureData &featureData ) +{ + // Nothing here +} + +void ShaderGenPrinterGLSL::printPixelShaderCloser( Stream& stream ) +{ + const char *closer = " gl_FragColor = col;\r\n}\r\n"; + stream.write( dStrlen(closer), closer ); +} + +void ShaderGenPrinterGLSL::printLine(Stream& stream, const String& line) +{ + stream.write(line.length(), line.c_str()); + const char* end = "\r\n"; + stream.write(dStrlen(end), end); +} + +const char* ShaderGenComponentFactoryGLSL::typeToString( GFXDeclType type ) +{ + switch ( type ) + { + default: + case GFXDeclType_Float: + return "float"; + + case GFXDeclType_Float2: + return "vec2"; + + case GFXDeclType_Float3: + return "vec3"; + + case GFXDeclType_Float4: + case GFXDeclType_Color: + return "vec4"; + } +} + +ShaderComponent* ShaderGenComponentFactoryGLSL::createVertexInputConnector( const GFXVertexFormat &vertexFormat ) +{ + AppVertConnectorGLSL *vertComp = new AppVertConnectorGLSL; + + // Loop thru the vertex format elements. + for ( U32 i=0; i < vertexFormat.getElementCount(); i++ ) + { + const GFXVertexElement &element = vertexFormat.getElement( i ); + + Var *var = NULL; + + if ( element.isSemantic( GFXSemantic::POSITION ) ) + { + var = vertComp->getElement( RT_POSITION ); + var->setName( "position" ); + } + else if ( element.isSemantic( GFXSemantic::NORMAL ) ) + { + var = vertComp->getElement( RT_NORMAL ); + var->setName( "normal" ); + } + else if ( element.isSemantic( GFXSemantic::TANGENT ) ) + { + var = vertComp->getElement( RT_TANGENT ); + var->setName( "T" ); + } + else if ( element.isSemantic( GFXSemantic::BINORMAL ) ) + { + var = vertComp->getElement( RT_BINORMAL ); + var->setName( "B" ); + } + else if ( element.isSemantic( GFXSemantic::COLOR ) ) + { + var = vertComp->getElement( RT_COLOR ); + var->setName( "diffuse" ); + } + else if ( element.isSemantic( GFXSemantic::TEXCOORD ) ) + { + var = vertComp->getElement( RT_TEXCOORD ); + if ( element.getSemanticIndex() == 0 ) + var->setName( "texCoord" ); + else + var->setName( String::ToString( "texCoord%d", element.getSemanticIndex() + 1 ) ); + } + else + { + // Everything else is a texcoord! + var = vertComp->getElement( RT_TEXCOORD ); + var->setName( "tc" + element.getSemantic() ); + } + + if ( !var ) + continue; + + var->setStructName( "" ); + var->setType( typeToString( element.getType() ) ); + } + + return vertComp; +} + +ShaderComponent* ShaderGenComponentFactoryGLSL::createVertexPixelConnector() +{ + VertPixelConnectorGLSL* comp = new VertPixelConnectorGLSL; + return comp; +} + +ShaderComponent* ShaderGenComponentFactoryGLSL::createVertexParamsDef() +{ + VertexParamsDefGLSL* comp = new VertexParamsDefGLSL; + return comp; +} + +ShaderComponent* ShaderGenComponentFactoryGLSL::createPixelParamsDef() +{ + PixelParamsDefGLSL* comp = new PixelParamsDefGLSL; + return comp; +} + diff --git a/shaderGen/GLSL/shaderGenGLSL.h b/shaderGen/GLSL/shaderGenGLSL.h new file mode 100644 index 0000000..c5c97d7 --- /dev/null +++ b/shaderGen/GLSL/shaderGenGLSL.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADERGEN_GLSL_H_ +#define _SHADERGEN_GLSL_H_ + +#ifndef _SHADERGEN_H_ +#include "shaderGen/shaderGen.h" +#endif + + +class ShaderGenPrinterGLSL : public ShaderGenPrinter +{ +public: + + // ShaderGenPrinter + virtual void printShaderHeader(Stream& stream); + virtual void printMainComment(Stream& stream); + virtual void printVertexShaderCloser(Stream& stream); + virtual void printPixelShaderOutputStruct(Stream& stream, const MaterialFeatureData &featureData); + virtual void printPixelShaderCloser(Stream& stream); + virtual void printLine(Stream& stream, const String& line); +}; + +class ShaderGenComponentFactoryGLSL : public ShaderGenComponentFactory +{ +public: + + /// Helper function for converting a vertex decl + /// type to an GLSL type string. + static const char* typeToString( GFXDeclType type ); + + // ShaderGenComponentFactory + virtual ShaderComponent* createVertexInputConnector( const GFXVertexFormat &vertexFormat ); + virtual ShaderComponent* createVertexPixelConnector(); + virtual ShaderComponent* createVertexParamsDef(); + virtual ShaderComponent* createPixelParamsDef(); +}; + +#endif \ No newline at end of file diff --git a/shaderGen/GLSL/shaderGenGLSLInit.cpp b/shaderGen/GLSL/shaderGenGLSLInit.cpp new file mode 100644 index 0000000..5fe005c --- /dev/null +++ b/shaderGen/GLSL/shaderGenGLSLInit.cpp @@ -0,0 +1,71 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "shaderGen/shaderGen.h" +#include "shaderGen/GLSL/shaderGenGLSL.h" +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#include "shaderGen/featureMgr.h" +#include "shaderGen/GLSL/bumpGLSL.h" +#include "shaderGen/GLSL/pixSpecularGLSL.h" +#include "shaderGen/GLSL/depthGLSL.h" +#include "shaderGen/GLSL/paraboloidGLSL.h" +#include "materials/materialFeatureTypes.h" + + +class ShaderGenGLSLInit +{ +public: + ShaderGenGLSLInit(); +private: + ShaderGen::ShaderGenInitDelegate mInitDelegate; + + void initShaderGen(ShaderGen* shadergen); +}; + +ShaderGenGLSLInit::ShaderGenGLSLInit() +{ + mInitDelegate.bind(this, &ShaderGenGLSLInit::initShaderGen); + SHADERGEN->registerInitDelegate(OpenGL, mInitDelegate); +} + +void ShaderGenGLSLInit::initShaderGen( ShaderGen *shaderGen ) +{ + shaderGen->setPrinter( new ShaderGenPrinterGLSL ); + shaderGen->setComponentFactory( new ShaderGenComponentFactoryGLSL ); + shaderGen->setFileEnding( "glsl" ); + + FEATUREMGR->registerFeature( MFT_VertTransform, new VertPositionGLSL ); + FEATUREMGR->registerFeature( MFT_RTLighting, new RTLightingFeatGLSL ); + FEATUREMGR->registerFeature( MFT_IsDXTnm, new NamedFeatureGLSL( "DXTnm" ) ); + FEATUREMGR->registerFeature( MFT_TexAnim, new TexAnimGLSL ); + FEATUREMGR->registerFeature( MFT_DiffuseMap, new DiffuseMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_OverlayMap, new OverlayTexFeatGLSL ); + FEATUREMGR->registerFeature( MFT_DiffuseColor, new DiffuseFeatureGLSL ); + FEATUREMGR->registerFeature( MFT_ColorMultiply, new ColorMultiplyFeatGLSL ); + FEATUREMGR->registerFeature( MFT_AlphaTest, new AlphaTestGLSL ); + FEATUREMGR->registerFeature( MFT_GlowMask, new GlowMaskGLSL ); + FEATUREMGR->registerFeature( MFT_LightMap, new LightmapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_ToneMap, new TonemapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_VertLit, new VertLitGLSL ); + FEATUREMGR->registerFeature( MFT_NormalMap, new BumpFeatGLSL ); + FEATUREMGR->registerFeature( MFT_DetailMap, new DetailFeatGLSL ); + FEATUREMGR->registerFeature( MFT_CubeMap, new ReflectCubeFeatGLSL ); + FEATUREMGR->registerFeature( MFT_PixSpecular, new PixelSpecularGLSL ); + FEATUREMGR->registerFeature( MFT_SpecularMap, new SpecularMapGLSL ); + FEATUREMGR->registerFeature( MFT_GlossMap, new NamedFeatureGLSL( "Gloss Map" ) ); + FEATUREMGR->registerFeature( MFT_IsTranslucent, new NamedFeatureGLSL( "Translucent" ) ); + FEATUREMGR->registerFeature( MFT_Visibility, new VisibilityFeatGLSL ); + FEATUREMGR->registerFeature( MFT_Fog, new FogFeatGLSL ); + + FEATUREMGR->registerFeature( MFT_DepthOut, new DepthOutGLSL ); + FEATUREMGR->registerFeature(MFT_EyeSpaceDepthOut, new EyeSpaceDepthOutGLSL()); + + FEATUREMGR->registerFeature( MFT_ParaboloidVertTransform, new ParaboloidVertTransformGLSL ); + FEATUREMGR->registerFeature( MFT_IsSinglePassParaboloid, new NamedFeatureGLSL( "Single Pass Paraboloid" ) ); +} + +static ShaderGenGLSLInit p_GLSLInit; \ No newline at end of file diff --git a/shaderGen/HLSL/bumpHLSL.cpp b/shaderGen/HLSL/bumpHLSL.cpp new file mode 100644 index 0000000..63690fb --- /dev/null +++ b/shaderGen/HLSL/bumpHLSL.cpp @@ -0,0 +1,295 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/HLSL/bumpHLSL.h" + +#include "shaderGen/shaderOp.h" +#include "gfx/gfxDevice.h" +#include "materials/matInstance.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" +#include "shaderGen/shaderGenVars.h" + + +void BumpFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // Output the texture coord. + getOutTexCoord( "texCoord", + "float2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + + // Also output the worldToTanget transform which + // we use to create the world space normal. + getOutWorldToTangent( componentList, meta ); + + + // TODO: Restore this! + /* + // Check to see if we're rendering world space normals. + if ( fd.materialFeatures[MFT_NormalsOut] ) + { + Var *inNormal = (Var*)LangElement::find( "normal" ); + + Var *outNormal = connectComp->getElement( RT_TEXCOORD ); + outNormal->setName( "normal" ); + outNormal->setStructName( "OUT" ); + outNormal->setType( "float3" ); + outNormal->mapsToSampler = false; + + meta->addStatement( new GenOp( " @ = @; // MFT_NormalsOut\r\n", outNormal, inNormal ) ); + output = meta; + return; + } + */ + + output = meta; +} + +void BumpFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // Get the texture coord. + Var *texCoord = getInTexCoord( "texCoord", "float2", true, componentList ); + + // Sample the bumpmap. + Var *bumpMap = getNormalMapTex(); + LangElement *texOp = new GenOp( "tex2D(@, @)", bumpMap, texCoord ); + Var *bumpNorm = new Var( "bumpNormal", "float4" ); + meta->addStatement( expandNormalMap( texOp, new DecOp( bumpNorm ), bumpNorm, fd ) ); + + // We transform it into world space by reversing the + // multiplication by the worldToTanget transform. + Var *wsNormal = new Var( "wsNormal", "float3" ); + Var *worldToTanget = getInWorldToTangent( componentList ); + meta->addStatement( new GenOp( " @ = normalize( mul( @.xyz, @ ) );\r\n", new DecOp( wsNormal ), bumpNorm, worldToTanget ) ); + + // TODO: Restore this! + /* + // Check to see if we're rendering world space normals. + if ( fd.materialFeatures[MFT_NormalsOut] ) + { + Var *inNormal = getInTexCoord( "normal", "float3", false, componentList ); + + LangElement *normalOut; + Var *outColor = (Var*)LangElement::find( "col" ); + if ( outColor ) + normalOut = new GenOp( "float4( ( -@ + 1 ) * 0.5, @.a )", inNormal, outColor ); + else + normalOut = new GenOp( "float4( ( -@ + 1 ) * 0.5, 1 )", inNormal ); + + meta->addStatement( new GenOp( " @; // MFT_NormalsOut\r\n", + assignColor( normalOut, Material::None ) ) ); + + output = meta; + return; + } + */ + + output = meta; +} + +ShaderFeature::Resources BumpFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // If we have no parallax then we bring on the normal tex. + if ( !fd.features[MFT_Parallax] ) + res.numTex = 1; + + // Only the parallax or diffuse map will add texture + // coords other than us. + if ( !fd.features[MFT_Parallax] && + !fd.features[MFT_DiffuseMap] && + !fd.features[MFT_OverlayMap] && + !fd.features[MFT_DetailMap] ) + res.numTexReg++; + + // We pass the world to tanget space transform. + res.numTexReg += 3; + + return res; +} + +void BumpFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + // If we had a parallax feature then it takes + // care of hooking up the normal map texture. + if ( fd.features[MFT_Parallax] ) + return; + + GFXTextureObject *tex = stageDat.getTex( MFT_NormalMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Bump; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} + + +ParallaxFeatHLSL::ParallaxFeatHLSL() + : mIncludeDep( "shaders/common/torque.hlsl" ) +{ + addDependency( &mIncludeDep ); +} + +Var* ParallaxFeatHLSL::_getUniformVar( const char *name, const char *type ) +{ + Var *theVar = (Var*)LangElement::find( name ); + if ( !theVar ) + { + theVar = new Var; + theVar->setType( type ); + theVar->setName( name ); + theVar->uniform = true; + theVar->constSortPos = cspPass; + } + + return theVar; +} + +void ParallaxFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::processVert - We don't support SM 1.x!" ); + + MultiLine *meta = new MultiLine; + + // Add the texture coords. + getOutTexCoord( "texCoord", + "float2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + + // Grab the input position. + Var *inPos = (Var*)LangElement::find( "inPosition" ); + if ( !inPos ) + inPos = (Var*)LangElement::find( "position" ); + + // Get the object space eye position and the + // object to tangent space transform. + Var *eyePos = _getUniformVar( "eyePos", "float3" ); + Var *objToTangentSpace = getOutObjToTangentSpace( componentList, meta ); + + // Now send the negative view vector in tangent space to the pixel shader. + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outNegViewTS = connectComp->getElement( RT_TEXCOORD ); + outNegViewTS->setName( "outNegViewTS" ); + outNegViewTS->setStructName( "OUT" ); + outNegViewTS->setType( "float3" ); + meta->addStatement( new GenOp( " @ = mul( @, float3( @.xyz - @ ) );\r\n", + outNegViewTS, objToTangentSpace, inPos, eyePos ) ); + + // TODO: I'm at a loss at why i need to flip the binormal/y coord + // to get a good view vector for parallax. Lighting works properly + // with the TS matrix as is... but parallax does not. + // + // Someone figure this out! + // + meta->addStatement( new GenOp( " @.y = -@.y;\r\n", outNegViewTS, outNegViewTS ) ); + + // If we have texture anim matrix the tangent + // space view vector may need to be rotated. + Var *texMat = (Var*)LangElement::find( "texMat" ); + if ( texMat ) + { + meta->addStatement( new GenOp( " @ = mul(@, float4(@,0)).xyz;\r\n", + outNegViewTS, texMat, outNegViewTS ) ); + } + + output = meta; +} + +void ParallaxFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::processPix - We don't support SM 1.x!" ); + + MultiLine *meta = new MultiLine; + + // Order matters... get this first! + Var *texCoord = getInTexCoord( "texCoord", "float2", true, componentList ); + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // We need the negative tangent space view vector + // as in parallax mapping we step towards the camera. + Var *negViewTS = (Var*)LangElement::find( "negViewTS" ); + if ( !negViewTS ) + { + Var *inNegViewTS = (Var*)LangElement::find( "outNegViewTS" ); + if ( !inNegViewTS ) + { + inNegViewTS = connectComp->getElement( RT_TEXCOORD ); + inNegViewTS->setName( "outNegViewTS" ); + inNegViewTS->setStructName( "IN" ); + inNegViewTS->setType( "float3" ); + } + + negViewTS = new Var( "negViewTS", "float3" ); + meta->addStatement( new GenOp( " @ = normalize( @ );\r\n", new DecOp( negViewTS ), inNegViewTS ) ); + } + + // Get the rest of our inputs. + Var *parallaxInfo = _getUniformVar( "parallaxInfo", "float" ); + Var *normalMap = getNormalMapTex(); + + // Call the library function to do the rest. + meta->addStatement( new GenOp( " @.xy += parallaxOffset( @, @.xy, @, @ );\r\n", + texCoord, normalMap, texCoord, negViewTS, parallaxInfo ) ); + + // TODO: Fix second UV maybe? + + output = meta; +} + +ShaderFeature::Resources ParallaxFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::getResources - We don't support SM 1.x!" ); + + Resources res; + + // We add the outViewTS to the outputstructure. + res.numTexReg = 1; + + // If this isn't a prepass then we will be + // creating the normal map here. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + res.numTex = 1; + + return res; +} + +void ParallaxFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + AssertFatal( GFX->getPixelShaderVersion() >= 2.0, + "ParallaxFeatHLSL::setTexData - We don't support SM 1.x!" ); + + GFXTextureObject *tex = stageDat.getTex( MFT_NormalMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Bump; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} diff --git a/shaderGen/HLSL/bumpHLSL.h b/shaderGen/HLSL/bumpHLSL.h new file mode 100644 index 0000000..f78180a --- /dev/null +++ b/shaderGen/HLSL/bumpHLSL.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _BUMP_HLSL_H_ +#define _BUMP_HLSL_H_ + +#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#endif + +struct RenderPassData; +class MultiLine; + + +/// The Bumpmap feature will read the normal map and +/// transform it by the inverse of the worldToTanget +/// matrix. This normal is then used by subsequent +/// shader features. +class BumpFeatHLSL : public ShaderFeatureHLSL +{ +public: + + // ShaderFeatureHLSL + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + virtual String getName() { return "Bumpmap"; } +}; + + +/// This feature either generates the cheap yet effective offset +/// mapping style parallax or the much more expensive occlusion +/// mapping technique based on the enabled feature flags. +class ParallaxFeatHLSL : public ShaderFeatureHLSL +{ +protected: + + static Var* _getUniformVar( const char *name, const char *type ); + + ShaderIncludeDependency mIncludeDep; + +public: + + ParallaxFeatHLSL(); + + // ShaderFeatureHLSL + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + virtual String getName() { return "Parallax"; } +}; + + +#endif // _BUMP_HLSL_H_ \ No newline at end of file diff --git a/shaderGen/HLSL/depthHLSL.cpp b/shaderGen/HLSL/depthHLSL.cpp new file mode 100644 index 0000000..1e4d99b --- /dev/null +++ b/shaderGen/HLSL/depthHLSL.cpp @@ -0,0 +1,169 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/hlsl/depthHLSL.h" + +#include "materials/materialFeatureTypes.h" + + +void EyeSpaceDepthOutHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab incoming vert position + Var *inPosition = (Var*) LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*) LangElement::find( "position" ); + + AssertFatal( inPosition, "Something went bad with ShaderGen. The position should be already defined." ); + + // grab output + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outWSEyeVec = connectComp->getElement( RT_TEXCOORD ); + outWSEyeVec->setName( "wsEyeVec" ); + outWSEyeVec->setStructName( "OUT" ); + + // create modelview variable + Var *objToWorld = (Var*) LangElement::find( "objTrans" ); + if( !objToWorld ) + { + objToWorld = new Var; + objToWorld->setType( "float4x4" ); + objToWorld->setName( "objTrans" ); + objToWorld->uniform = true; + objToWorld->constSortPos = cspPrimitive; + } + + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if( !eyePos ) + { + eyePos = new Var; + eyePos->setType("float3"); + eyePos->setName("eyePosWorld"); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + LangElement *statement = new GenOp( " @ = mul(@, float4(@.xyz,1)) - float4(@, 0.0);\r\n", + outWSEyeVec, objToWorld, inPosition, eyePos ); + + output = statement; +} + +void EyeSpaceDepthOutHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // grab connector position + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *wsEyeVec = connectComp->getElement( RT_TEXCOORD ); + wsEyeVec->setName( "wsEyeVec" ); + wsEyeVec->setStructName( "IN" ); + wsEyeVec->setType( "float4" ); + wsEyeVec->mapsToSampler = false; + wsEyeVec->uniform = false; + + // get shader constants + Var *vEye = new Var; + vEye->setType("float3"); + vEye->setName("vEye"); + vEye->uniform = true; + vEye->constSortPos = cspPass; + + // Expose the depth to the depth format feature + Var *depthOut = new Var; + depthOut->setType("float"); + depthOut->setName(getOutputVarName()); + + LangElement *depthOutDecl = new DecOp( depthOut ); + + meta->addStatement( new GenOp( "#ifndef CUBE_SHADOW_MAP\r\n" ) ); + meta->addStatement( new GenOp( " @ = dot(@, (@.xyz / @.w));\r\n", depthOutDecl, vEye, wsEyeVec, wsEyeVec ) ); + meta->addStatement( new GenOp( "#else\r\n" ) ); + + Var *farDist = (Var*)Var::find( "oneOverFarplane" ); + if ( !farDist ) + { + farDist = new Var; + farDist->setType("float4"); + farDist->setName("oneOverFarplane"); + farDist->uniform = true; + farDist->constSortPos = cspPrimitive; + } + + meta->addStatement( new GenOp( " @ = length( @.xyz / @.w ) * @.x;\r\n", depthOutDecl, wsEyeVec, wsEyeVec, farDist ) ); + meta->addStatement( new GenOp( "#endif\r\n" ) ); + + // If there isn't an output conditioner for the pre-pass, than just write + // out the depth to rgba and return. + if( !fd.features[MFT_PrePassConditioner] ) + meta->addStatement( new GenOp( " @;\r\n", assignColor( new GenOp( "float4(@.rrr,1)", depthOut ), Material::None ) ) ); + + output = meta; +} + +ShaderFeature::Resources EyeSpaceDepthOutHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources temp; + + // Passing from VS->PS: + // - world space position (wsPos) + temp.numTexReg = 1; + + return temp; +} + + +void DepthOutHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Grab the output vert. + Var *outPosition = (Var*)LangElement::find( "hpos" ); + + // Grab our output depth. + Var *outDepth = connectComp->getElement( RT_TEXCOORD ); + outDepth->setName( "depth" ); + outDepth->setStructName( "OUT" ); + outDepth->setType( "float" ); + + output = new GenOp( " @ = @.z / @.w;\r\n", outDepth, outPosition, outPosition ); +} + +void DepthOutHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // grab connector position + Var *depthVar = connectComp->getElement( RT_TEXCOORD ); + depthVar->setName( "depth" ); + depthVar->setStructName( "IN" ); + depthVar->setType( "float" ); + depthVar->mapsToSampler = false; + depthVar->uniform = false; + + /* + // Expose the depth to the depth format feature + Var *depthOut = new Var; + depthOut->setType("float"); + depthOut->setName(getOutputVarName()); + */ + + LangElement *depthOut = new GenOp( "float4( @, 0, 0, 1 )", depthVar ); + + output = new GenOp( " @;\r\n", assignColor( depthOut, Material::None ) ); +} + +ShaderFeature::Resources DepthOutHLSL::getResources( const MaterialFeatureData &fd ) +{ + // We pass the depth to the pixel shader. + Resources temp; + temp.numTexReg = 1; + + return temp; +} diff --git a/shaderGen/HLSL/depthHLSL.h b/shaderGen/HLSL/depthHLSL.h new file mode 100644 index 0000000..685c2ca --- /dev/null +++ b/shaderGen/HLSL/depthHLSL.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _DEPTH_HLSL_H_ +#define _DEPTH_HLSL_H_ + +#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#endif +#ifndef _SHADEROP_H_ +#include "shaderGen/shaderOp.h" +#endif + + +class EyeSpaceDepthOutHLSL : public ShaderFeatureHLSL +{ +public: + + // ShaderFeature + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "Eye Space Depth (Out)"; } + virtual Material::BlendOp getBlendOp() { return Material::None; } + virtual const char* getOutputVarName() const { return "eyeSpaceDepth"; } +}; + + +class DepthOutHLSL : public ShaderFeatureHLSL +{ +public: + + // ShaderFeature + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "Depth (Out)"; } + virtual Material::BlendOp getBlendOp() { return Material::None; } + virtual const char* getOutputVarName() const { return "IN.depth"; } +}; + +#endif // _DEPTH_HLSL_H_ \ No newline at end of file diff --git a/shaderGen/HLSL/paraboloidHLSL.cpp b/shaderGen/HLSL/paraboloidHLSL.cpp new file mode 100644 index 0000000..5ccbf83 --- /dev/null +++ b/shaderGen/HLSL/paraboloidHLSL.cpp @@ -0,0 +1,149 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/hlsl/paraboloidHLSL.h" + +#include "lighting/lightInfo.h" +#include "materials/sceneData.h" +#include "materials/materialFeatureTypes.h" +#include "gfx/gfxShader.h" + + +void ParaboloidVertTransformHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + // First check for an input position from a previous feature + // then look for the default vertex position. + Var *inPosition = (Var*)LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*)LangElement::find( "position" ); + + const bool isSinglePass = fd.features[ MFT_IsSinglePassParaboloid ]; + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Grab connector out position. + Var *outPosition = connectComp->getElement( RT_POSITION ); + outPosition->setName( "hpos" ); + outPosition->setStructName( "OUT" ); + + // Get the atlas scale. + Var *atlasScale = new Var; + atlasScale->setType( "float2" ); + atlasScale->setName( "atlasScale" ); + atlasScale->uniform = true; + atlasScale->constSortPos = cspPrimitive; + + // Transform into camera space + Var *worldViewOnly = new Var; + worldViewOnly->setType( "float4x4" ); + worldViewOnly->setName( "worldViewOnly" ); + worldViewOnly->uniform = true; + worldViewOnly->constSortPos = cspPrimitive; + + // So what we're doing here is transforming into camera space, and + // then directly manipulate into shadowmap space. + // + // http://www.gamedev.net/reference/articles/article2308.asp + + // Swizzle z and y post-transform + meta->addStatement( new GenOp( " @ = mul(@, float4(@.xyz,1)).xzyw;\r\n", outPosition, worldViewOnly, inPosition ) ); + meta->addStatement( new GenOp( " float L = length(@.xyz);\r\n", outPosition ) ); + + if ( isSinglePass ) + { + // Flip the z in the back case + Var *outIsBack = connectComp->getElement( RT_TEXCOORD ); + outIsBack->setType( "float" ); + outIsBack->setName( "isBack" ); + outIsBack->setStructName( "OUT" ); + + meta->addStatement( new GenOp( " bool isBack = @.z < 0.0;\r\n", outPosition ) ); + meta->addStatement( new GenOp( " @ = isBack ? -1.0 : 1.0;\r\n", outIsBack ) ); + meta->addStatement( new GenOp( " if ( isBack ) @.z = -@.z;\r\n", outPosition, outPosition ) ); + } + + meta->addStatement( new GenOp( " @ /= L;\r\n", outPosition ) ); + meta->addStatement( new GenOp( " @.z = @.z + 1.0;\r\n", outPosition, outPosition ) ); + meta->addStatement( new GenOp( " @.xy /= @.z;\r\n", outPosition, outPosition ) ); + + // Get the light parameters. + Var *lightParams = new Var; + lightParams->setType( "float4" ); + lightParams->setName( "lightParams" ); + lightParams->uniform = true; + lightParams->constSortPos = cspPrimitive; + + // TODO: If we change other shadow shaders to write out + // linear depth, than fix this as well! + // + // (L - zNear)/(lightParams.x - zNear); + // + meta->addStatement( new GenOp( " @.z = L / @.x;\r\n", outPosition, lightParams ) ); + meta->addStatement( new GenOp( " @.w = 1.0;\r\n", outPosition ) ); + + // Pass unmodified to pixel shader to allow it to clip properly. + Var *outPosXY = connectComp->getElement( RT_TEXCOORD ); + outPosXY->setType( "float2" ); + outPosXY->setName( "posXY" ); + outPosXY->setStructName( "OUT" ); + meta->addStatement( new GenOp( " @ = @.xy;\r\n", outPosXY, outPosition ) ); + + // Scale and offset so it shows up in the atlas properly. + meta->addStatement( new GenOp( " @.xy *= @.xy;\r\n", outPosition, atlasScale ) ); + + if ( isSinglePass ) + meta->addStatement( new GenOp( " @.x += isBack ? 0.5 : -0.5;\r\n", outPosition ) ); + else + { + Var *atlasOffset = new Var; + atlasOffset->setType( "float2" ); + atlasOffset->setName( "atlasXOffset" ); + atlasOffset->uniform = true; + atlasOffset->constSortPos = cspPrimitive; + + meta->addStatement( new GenOp( " @.xy += @;\r\n", outPosition, atlasOffset ) ); + } + + output = meta; +} + +void ParaboloidVertTransformHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + MultiLine *meta = new MultiLine; + + const bool isSinglePass = fd.features[ MFT_IsSinglePassParaboloid ]; + if ( isSinglePass ) + { + // Cull things on the back side of the map. + Var *isBack = connectComp->getElement( RT_TEXCOORD ); + isBack->setName( "isBack" ); + isBack->setStructName( "IN" ); + isBack->setType( "float" ); + meta->addStatement( new GenOp( " clip( abs( @ ) - 0.999 );\r\n", isBack ) ); + } + + // Cull pixels outside of the valid paraboloid. + Var *posXY = connectComp->getElement( RT_TEXCOORD ); + posXY->setName( "posXY" ); + posXY->setStructName( "IN" ); + posXY->setType( "float2" ); + meta->addStatement( new GenOp( " clip( 1.0 - abs(@.x) );\r\n", posXY ) ); + + output = meta; +} + +ShaderFeature::Resources ParaboloidVertTransformHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources temp; + temp.numTexReg = 2; + return temp; +} diff --git a/shaderGen/HLSL/paraboloidHLSL.h b/shaderGen/HLSL/paraboloidHLSL.h new file mode 100644 index 0000000..c02aa17 --- /dev/null +++ b/shaderGen/HLSL/paraboloidHLSL.h @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _PARABOLOID_HLSL_H_ +#define _PARABOLOID_HLSL_H_ + +#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#endif +#ifndef _SHADEROP_H_ +#include "shaderGen/shaderOp.h" +#endif + +class GFXShaderConstHandle; + + +class ParaboloidVertTransformHLSL : public ShaderFeatureHLSL +{ +public: + + // ShaderFeature + virtual void processVert( Vector &componentList, const MaterialFeatureData &fd ); + virtual void processPix( Vector &componentList, const MaterialFeatureData &fd ); + virtual Resources getResources( const MaterialFeatureData &fd ); + virtual String getName() { return "Paraboloid Vert Transform"; } + virtual Material::BlendOp getBlendOp() { return Material::None; } + +}; + +#endif // _PARABOLOID_HLSL_H_ \ No newline at end of file diff --git a/shaderGen/HLSL/pixSpecularHLSL.cpp b/shaderGen/HLSL/pixSpecularHLSL.cpp new file mode 100644 index 0000000..a5133e5 --- /dev/null +++ b/shaderGen/HLSL/pixSpecularHLSL.cpp @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/HLSL/pixSpecularHLSL.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/shaderGenVars.h" +#include "gfx/gfxStructs.h" + + +PixelSpecularHLSL::PixelSpecularHLSL() + : mDep( "shaders/common/lighting.hlsl" ) +{ + addDependency( &mDep ); +} + +void PixelSpecularHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + AssertFatal( fd.features[MFT_RTLighting], + "PixelSpecularHLSL requires RTLighting to be enabled!" ); + + // Nothing to do here... MFT_RTLighting should have + // taken care of passing everything to the pixel shader. +} + +void PixelSpecularHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + AssertFatal( fd.features[MFT_RTLighting], + "PixelSpecularHLSL requires RTLighting to be enabled!" ); + + // RTLighting should have spit out the 4 specular + // powers for the 4 potential lights on this pass. + // + // This can sometimes be NULL if RTLighting skips out + // on us for lightmaps or missing normals. + Var *specular = (Var*)LangElement::find( "specular" ); + if ( !specular ) + return; + + MultiLine *meta = new MultiLine; + + LangElement *specMul = new GenOp( "@", specular ); + LangElement *final = specMul; + + // mask out with lightmap if present + if ( fd.features[MFT_LightMap] ) + { + LangElement *lmColor = NULL; + + // find lightmap color + lmColor = LangElement::find( "lmColor" ); + + if ( !lmColor ) + { + LangElement * lightMap = LangElement::find( "lightMap" ); + LangElement * lmCoord = LangElement::find( "texCoord2" ); + + lmColor = new GenOp( "tex2D(@, @)", lightMap, lmCoord ); + } + + final = new GenOp( "@ * float4(@.rgb,0)", specMul, lmColor ); + } + + // We we have a normal map then mask the specular + if ( fd.features[MFT_NormalMap] ) + { + Var *bumpColor = (Var*)LangElement::find( "bumpNormal" ); + final = new GenOp( "@ * @.a", final, bumpColor ); + } + + // Add the specular to the final color. + meta->addStatement( new GenOp( " @;\r\n", assignColor( final, Material::Add ) ) ); + + output = meta; +} + +ShaderFeature::Resources PixelSpecularHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + return res; +} + + +void SpecularMapHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // Get the texture coord. + Var *texCoord = getInTexCoord( "texCoord", "float2", true, componentList ); + + // create texture var + Var *specularMap = new Var; + specularMap->setType( "sampler2D" ); + specularMap->setName( "specularMap" ); + specularMap->uniform = true; + specularMap->sampler = true; + specularMap->constNum = Var::getTexUnitNum(); + LangElement *texOp = new GenOp( "tex2D(@, @)", specularMap, texCoord ); + + Var *specularColor = new Var( "specularColor", "float4" ); + + output = new GenOp( " @ = @;\r\n", new DecOp( specularColor ), texOp ); +} + +ShaderFeature::Resources SpecularMapHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + return res; +} + +void SpecularMapHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_SpecularMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::Standard; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} \ No newline at end of file diff --git a/shaderGen/HLSL/pixSpecularHLSL.h b/shaderGen/HLSL/pixSpecularHLSL.h new file mode 100644 index 0000000..deb115d --- /dev/null +++ b/shaderGen/HLSL/pixSpecularHLSL.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PIXSPECULAR_HLSL_H_ +#define _PIXSPECULAR_HLSL_H_ + +#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#endif + + +/// A per-pixel specular feature. +class PixelSpecularHLSL : public ShaderFeatureHLSL +{ +protected: + + ShaderIncludeDependency mDep; + +public: + + PixelSpecularHLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Pixel Specular"; + } +}; + +/// A texture source for the PixSpecular feature +class SpecularMapHLSL : public ShaderFeatureHLSL +{ + +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Specular Map"; + } +}; + +#endif // _PIXSPECULAR_HLSL_H_ \ No newline at end of file diff --git a/shaderGen/HLSL/shaderCompHLSL.cpp b/shaderGen/HLSL/shaderCompHLSL.cpp new file mode 100644 index 0000000..52a134c --- /dev/null +++ b/shaderGen/HLSL/shaderCompHLSL.cpp @@ -0,0 +1,319 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/HLSL/shaderCompHLSL.h" + +#include "shaderGen/shaderComp.h" +#include "shaderGen/langElement.h" +#include "gfx/gfxDevice.h" + + +Var * ShaderConnectorHLSL::getElement( RegisterType type, + U32 numElements, + U32 numRegisters ) +{ + Var *ret = getIndexedElement( mCurTexElem, type, numElements, numRegisters ); + + // Adjust texture offset if this is a texcoord type + if( type == RT_TEXCOORD ) + { + if ( numRegisters != -1 ) + mCurTexElem += numRegisters; + else + mCurTexElem += numElements; + } + + return ret; +} + +Var * ShaderConnectorHLSL::getIndexedElement( U32 index, RegisterType type, U32 numElements /*= 1*/, U32 numRegisters /*= -1 */ ) +{ + switch( type ) + { + case RT_POSITION: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "POSITION" ); + return newVar; + } + + case RT_NORMAL: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "NORMAL" ); + return newVar; + } + + case RT_BINORMAL: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "BINORMAL" ); + return newVar; + } + + case RT_TANGENT: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "TANGENT" ); + return newVar; + } + + case RT_COLOR: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + newVar->setConnectName( "COLOR" ); + return newVar; + } + + case RT_TEXCOORD: + { + Var *newVar = new Var; + mElementList.push_back( newVar ); + + char out[32]; + dSprintf( (char*)out, sizeof(out), "TEXCOORD%d", index ); + newVar->setConnectName( out ); + newVar->constNum = index; + newVar->arraySize = numElements; + + return newVar; + } + + default: + break; + } + + return NULL; +} + +void ShaderConnectorHLSL::sortVars() +{ + if ( GFX->getPixelShaderVersion() >= 2.0 ) + return; + + // Sort connector variables - They must be sorted on hardware that is running + // ps 1.4 and below. The reason is that texture coordinate registers MUST + // map exactly to their respective texture stage. Ie. if you have fog + // coordinates being passed into a pixel shader in texture coordinate register + // number 4, the fog texture MUST reside in texture stage 4 for it to work. + // The problem is solved by pushing non-texture coordinate data to the end + // of the structure so that the texture coodinates are all at the "top" of the + // structure in the order that the features are processed. + + // create list of just the texCoords, sorting by 'mapsToSampler' + Vector< Var * > texCoordList; + + // - first pass is just coords mapped to a sampler + for( U32 i=0; imapsToSampler ) + { + texCoordList.push_back( var ); + } + } + + // - next pass is for the others + for( U32 i=0; iconnectName, "TEX" ) && + !var->mapsToSampler ) + { + texCoordList.push_back( var ); + } + } + + // rename the connectNames + for( U32 i=0; isetConnectName( out ); + } + + // write new, sorted list over old one + if( texCoordList.size() ) + { + U32 index = 0; + + for( U32 i=0; iconnectName, "TEX" ) ) + { + mElementList[i] = texCoordList[index]; + index++; + } + } + } +} + +void ShaderConnectorHLSL::setName( char *newName ) +{ + dStrcpy( (char*)mName, newName ); +} + +void ShaderConnectorHLSL::reset() +{ + for( U32 i=0; iarraySize <= 1) + dSprintf( (char*)output, sizeof(output), " %s %-15s : %s;\r\n", var->type, var->name, var->connectName ); + else + dSprintf( (char*)output, sizeof(output), " %s %s[%d] : %s;\r\n", var->type, var->name, var->arraySize, var->connectName ); + + stream.write( dStrlen((char*)output), output ); + } + + stream.write( dStrlen(footer), footer ); +} + +void ParamsDefHLSL::assignConstantNumbers() +{ + + // Here we assign constant number to uniform vars, sorted + // by their update frequency. + + U32 mCurrConst = 0; + for (U32 bin = cspUninit+1; bin < csp_Count; bin++) + { + // Find all the uniform variables that are part of this group and assign constant numbers + for( U32 i=0; i(LangElement::elementList[i]); + if( var ) + { + bool shaderConst = var->uniform && !var->sampler; + AssertFatal((!shaderConst) || var->constSortPos != cspUninit, "Const sort position has not been set, variable will not receive a constant number!!"); + if( shaderConst && var->constSortPos == bin) + { + var->constNum = mCurrConst; + // Increment our constant number based on the variable type + if (dStrcmp((const char*)var->type, "float4x4") == 0) + { + mCurrConst += (4 * var->arraySize); + } else { + if (dStrcmp((const char*)var->type, "float3x3") == 0) + { + mCurrConst += (3 * var->arraySize); + } else { + mCurrConst += var->arraySize; + } + } + } + } + } + } +} + +void VertexParamsDefHLSL::print( Stream &stream ) +{ + assignConstantNumbers(); + + const char *opener = "ConnectData main( VertData IN"; + stream.write( dStrlen(opener), opener ); + + // find all the uniform variables and print them out + for( U32 i=0; i(LangElement::elementList[i]); + if( var ) + { + if( var->uniform ) + { + const char* nextVar = ",\r\n "; + stream.write( dStrlen(nextVar), nextVar ); + + U8 varNum[64]; + dSprintf( (char*)varNum, sizeof(varNum), "register(C%d)", var->constNum ); + + U8 output[256]; + if (var->arraySize <= 1) + dSprintf( (char*)output, sizeof(output), "uniform %-8s %-15s : %s", var->type, var->name, varNum ); + else + dSprintf( (char*)output, sizeof(output), "uniform %-8s %s[%d] : %s", var->type, var->name, var->arraySize, varNum ); + + stream.write( dStrlen((char*)output), output ); + } + } + } + + const char *closer = "\r\n)\r\n{\r\n ConnectData OUT;\r\n\r\n"; + stream.write( dStrlen(closer), closer ); +} + +void PixelParamsDefHLSL::print( Stream &stream ) +{ + assignConstantNumbers(); + + const char * opener = "Fragout main( ConnectData IN"; + stream.write( dStrlen(opener), opener ); + + // find all the sampler & uniform variables and print them out + for( U32 i=0; i(LangElement::elementList[i]); + if( var ) + { + if( var->uniform ) + { + WRITESTR( ",\r\n " ); + + U8 varNum[32]; + + if( var->sampler ) + { + dSprintf( (char*)varNum, sizeof(varNum), "register(S%d)", var->constNum ); + } + else + { + dSprintf( (char*)varNum, sizeof(varNum), "register(C%d)", var->constNum ); + } + + U8 output[256]; + if (var->arraySize <= 1) + dSprintf( (char*)output, sizeof(output), "uniform %-9s %-15s : %s", var->type, var->name, varNum ); + else + dSprintf( (char*)output, sizeof(output), "uniform %-9s %s[%d] : %s", var->type, var->name, var->arraySize, varNum ); + + WRITESTR( (char*) output ); + } + } + } + + const char *closer = "\r\n)\r\n{\r\n Fragout OUT;\r\n\r\n"; + stream.write( dStrlen(closer), closer ); +} diff --git a/shaderGen/HLSL/shaderCompHLSL.h b/shaderGen/HLSL/shaderCompHLSL.h new file mode 100644 index 0000000..10c6e8b --- /dev/null +++ b/shaderGen/HLSL/shaderCompHLSL.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADERCOMP_HLSL_H_ +#define _SHADERCOMP_HLSL_H_ + +#ifndef _SHADERCOMP_H_ +#include "shaderGen/shaderComp.h" +#endif + + +class ShaderConnectorHLSL : public ShaderConnector +{ +public: + + // ShaderConnector + virtual Var* getElement( RegisterType type, + U32 numElements = 1, + U32 numRegisters = -1 ); + virtual Var* getIndexedElement( U32 index, + RegisterType type, + U32 numElements = 1, + U32 numRegisters = -1 ); + + virtual void setName( char *newName ); + virtual void reset(); + virtual void sortVars(); + + virtual void print( Stream &stream ); +}; + + +class ParamsDefHLSL : public ParamsDef +{ +protected: + virtual void assignConstantNumbers(); +}; + + +class VertexParamsDefHLSL : public ParamsDefHLSL +{ +public: + virtual void print( Stream &stream ); +}; + + +class PixelParamsDefHLSL : public ParamsDefHLSL +{ +public: + virtual void print( Stream &stream ); +}; + +#endif // _SHADERCOMP_HLSL_H_ \ No newline at end of file diff --git a/shaderGen/HLSL/shaderFeatureHLSL.cpp b/shaderGen/HLSL/shaderFeatureHLSL.cpp new file mode 100644 index 0000000..ef42d25 --- /dev/null +++ b/shaderGen/HLSL/shaderFeatureHLSL.cpp @@ -0,0 +1,1855 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/HLSL/shaderFeatureHLSL.h" + +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/shaderGenVars.h" +#include "gfx/gfxDevice.h" +#include "materials/matInstance.h" +#include "materials/processedMaterial.h" +#include "materials/materialFeatureTypes.h" +#include "core/util/autoPtr.h" + +#include "lighting/advanced/advancedLightBinManager.h" + +LangElement * ShaderFeatureHLSL::setupTexSpaceMat( Vector &, // componentList + Var **texSpaceMat ) +{ + Var *N = (Var*) LangElement::find( "normal" ); + Var *B = (Var*) LangElement::find( "B" ); + Var *T = (Var*) LangElement::find( "T" ); + + Var *tangentW = (Var*) LangElement::find( "tangentW" ); + + // setup matrix var + *texSpaceMat = new Var; + (*texSpaceMat)->setType( "float3x3" ); + (*texSpaceMat)->setName( "objToTangentSpace" ); + + MultiLine * meta = new MultiLine; + meta->addStatement( new GenOp( " @;\r\n", new DecOp( *texSpaceMat ) ) ); + meta->addStatement( new GenOp( " @[0] = @;\r\n", *texSpaceMat, T ) ); + if ( B ) + meta->addStatement( new GenOp( " @[1] = @;\r\n", *texSpaceMat, B ) ); + else + { + if(dStricmp((char*)T->type, "float4") == 0) + meta->addStatement( new GenOp( " @[1] = cross( @, normalize(@) ) * @.w;\r\n", *texSpaceMat, T, N, T ) ); + else if(tangentW) + meta->addStatement( new GenOp( " @[1] = cross( @, normalize(@) ) * @;\r\n", *texSpaceMat, T, N, tangentW ) ); + else + meta->addStatement( new GenOp( " @[1] = cross( @, normalize(@) );\r\n", *texSpaceMat, T, N ) ); + } + meta->addStatement( new GenOp( " @[2] = normalize(@);\r\n", *texSpaceMat, N ) ); + + return meta; +} + +LangElement* ShaderFeatureHLSL::assignColor( LangElement *elem, + Material::BlendOp blend, + LangElement *lerpElem, + ShaderFeature::OutputTarget outputTarget ) +{ + + // search for color var + Var *color = (Var*) LangElement::find( getOutputTargetVarName(outputTarget) ); + + if ( !color ) + { + // create color var + color = new Var; + color->setType( "fragout" ); + color->setName( getOutputTargetVarName(outputTarget) ); + color->setStructName( "OUT" ); + + return new GenOp( "@ = @", color, elem ); + } + + LangElement *assign; + + switch ( blend ) + { + case Material::Add: + assign = new GenOp( "@ += @", color, elem ); + break; + + case Material::Sub: + assign = new GenOp( "@ -= @", color, elem ); + break; + + case Material::Mul: + assign = new GenOp( "@ *= @", color, elem ); + break; + + case Material::AddAlpha: + assign = new GenOp( "@ += @ * @.a", color, elem, elem ); + break; + + case Material::LerpAlpha: + if ( !lerpElem ) + lerpElem = elem; + assign = new GenOp( "@.rgb = lerp( @.rgb, (@).rgb, (@).a )", color, color, elem, lerpElem ); + break; + + case Material::ToneMap: + assign = new GenOp( "@ = 1.0 - exp(-1.0 * @ * @)", color, color, elem ); + break; + + default: + AssertFatal(false, "Unrecognized color blendOp"); + // Fallthru + + case Material::None: + assign = new GenOp( "@ = @", color, elem ); + break; + } + + return assign; +} + + +LangElement *ShaderFeatureHLSL::expandNormalMap( LangElement *sampleNormalOp, + LangElement *normalDecl, + LangElement *normalVar, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + if ( fd.features.hasFeature( MFT_IsDXTnm, getProcessIndex() ) ) + { + // DXT Swizzle trick + meta->addStatement( new GenOp( " @ = float4( @.ag * 2.0 - 1.0, 0.0, 0.0 ); // DXTnm\r\n", normalDecl, sampleNormalOp ) ); + meta->addStatement( new GenOp( " @.z = sqrt( 1.0 - dot( @.xy, @.xy ) ); // DXTnm\r\n", normalVar, normalVar, normalVar ) ); + } + else + { + meta->addStatement( new GenOp( " @ = @;\r\n", normalDecl, sampleNormalOp ) ); + meta->addStatement( new GenOp( " @.xyz = @.xyz * 2.0 - 1.0;\r\n", normalVar, normalVar ) ); + } + + return meta; +} + +ShaderFeatureHLSL::ShaderFeatureHLSL() +{ + output = NULL; +} + +Var * ShaderFeatureHLSL::getVertTexCoord( const String &name ) +{ + Var *inTex = NULL; + + for( U32 i=0; iname, name.c_str() ) ) + { + inTex = dynamic_cast( LangElement::elementList[i] ); + + if( inTex ) + { + if( !dStrcmp( (char*)inTex->structName, "IN" ) ) + { + break; + } + else + { + inTex = NULL; + } + } + } + } + + return inTex; +} + +Var* ShaderFeatureHLSL::getOutObjToTangentSpace( Vector &componentList, + MultiLine *meta ) +{ + Var *outObjToTangentSpace = (Var*)LangElement::find( "objToTangentSpace" ); + if ( !outObjToTangentSpace ) + meta->addStatement( setupTexSpaceMat( componentList, &outObjToTangentSpace ) ); + + return outObjToTangentSpace; +} + +Var* ShaderFeatureHLSL::getOutWorldToTangent( Vector &componentList, + MultiLine *meta ) +{ + Var *outWorldToTangent = (Var*)LangElement::find( "worldToTangent" ); + if ( !outWorldToTangent ) + { + Var *texSpaceMat = getOutObjToTangentSpace( componentList, meta ); + + // turn obj->tangent into world->tangent + Var *worldToTangent = new Var; + worldToTangent->setType( "float3x3" ); + worldToTangent->setName( "worldToTangent" ); + LangElement *worldToTangentDecl = new DecOp( worldToTangent ); + + // Get the world->obj transform + Var *worldToObj = new Var; + worldToObj->setType( "float4x4" ); + worldToObj->setName( "worldToObj" ); + worldToObj->uniform = true; + worldToObj->constSortPos = cspPrimitive; + + // assign world->tangent transform + meta->addStatement( new GenOp( " @ = mul( @, (float3x3)@ );\r\n", worldToTangentDecl, texSpaceMat, worldToObj ) ); + + // send transform to pixel shader + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + outWorldToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + outWorldToTangent->setName( "worldToTangent" ); + outWorldToTangent->setStructName( "OUT" ); + outWorldToTangent->setType( "float3x3" ); + meta->addStatement( new GenOp( " @ = @;\r\n", outWorldToTangent, worldToTangent ) ); + } + + return outWorldToTangent; +} + +Var* ShaderFeatureHLSL::getOutViewToTangent( Vector &componentList, + MultiLine *meta ) +{ + Var *outViewToTangent = (Var*)LangElement::find( "viewToTangent" ); + if ( !outViewToTangent ) + { + Var *texSpaceMat = getOutObjToTangentSpace( componentList, meta ); + + // turn obj->tangent into world->tangent + Var *viewToTangent = new Var; + viewToTangent->setType( "float3x3" ); + viewToTangent->setName( "viewToTangent" ); + LangElement *viewToTangentDecl = new DecOp( viewToTangent ); + + // Get the view->obj transform + Var *viewToObj = new Var; + viewToObj->setType( "float4x4" ); + viewToObj->setName( "viewToObj" ); + viewToObj->uniform = true; + viewToObj->constSortPos = cspPrimitive; + + // assign world->tangent transform + meta->addStatement( new GenOp( " @ = mul( @, (float3x3)@ );\r\n", viewToTangentDecl, texSpaceMat, viewToObj ) ); + + // send transform to pixel shader + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + outViewToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + outViewToTangent->setName( "viewToTangent" ); + outViewToTangent->setStructName( "OUT" ); + outViewToTangent->setType( "float3x3" ); + meta->addStatement( new GenOp( " @ = @;\r\n", outViewToTangent, viewToTangent ) ); + } + + return outViewToTangent; +} + +Var* ShaderFeatureHLSL::getOutTexCoord( const char *name, + const char *type, + bool mapsToSampler, + bool useTexAnim, + MultiLine *meta, + Vector &componentList ) +{ + String outTexName = String::ToString( "out_%s", name ); + Var *texCoord = (Var*)LangElement::find( outTexName ); + if ( !texCoord ) + { + Var *inTex = getVertTexCoord( name ); + AssertFatal( inTex, "ShaderFeatureHLSL::getOutTexCoord - Unknown vertex input coord!" ); + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + texCoord = connectComp->getElement( RT_TEXCOORD ); + texCoord->setName( outTexName ); + texCoord->setStructName( "OUT" ); + texCoord->setType( type ); + texCoord->mapsToSampler = mapsToSampler; + + if( useTexAnim ) + { + inTex->setType( "float4" ); + + // create texture mat var + Var *texMat = new Var; + texMat->setType( "float4x4" ); + texMat->setName( "texMat" ); + texMat->uniform = true; + texMat->constSortPos = cspPass; + + meta->addStatement( new GenOp( " @ = mul(@, @).xy;\r\n", texCoord, texMat, inTex ) ); + } + else + meta->addStatement( new GenOp( " @ = @;\r\n", texCoord, inTex ) ); + } + + AssertFatal( dStrcmp( type, (const char*)texCoord->type ) == 0, + "ShaderFeatureHLSL::getOutTexCoord - Type mismatch!" ); + + return texCoord; +} + +Var* ShaderFeatureHLSL::getInTexCoord( const char *name, + const char *type, + bool mapsToSampler, + Vector &componentList ) +{ + Var* texCoord = (Var*)LangElement::find( name ); + if ( !texCoord ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + texCoord = connectComp->getElement( RT_TEXCOORD ); + texCoord->setName( name ); + texCoord->setStructName( "IN" ); + texCoord->setType( type ); + texCoord->mapsToSampler = mapsToSampler; + } + + AssertFatal( dStrcmp( type, (const char*)texCoord->type ) == 0, + "ShaderFeatureHLSL::getInTexCoord - Type mismatch!" ); + + return texCoord; +} + +Var* ShaderFeatureHLSL::getInWorldToTangent( Vector &componentList ) +{ + Var *worldToTangent = (Var*)LangElement::find( "worldToTangent" ); + if ( !worldToTangent ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + worldToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + worldToTangent->setName( "worldToTangent" ); + worldToTangent->setStructName( "IN" ); + worldToTangent->setType( "float3x3" ); + } + + return worldToTangent; +} + +Var* ShaderFeatureHLSL::getInViewToTangent( Vector &componentList ) +{ + Var *viewToTangent = (Var*)LangElement::find( "viewToTangent" ); + if ( !viewToTangent ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + viewToTangent = connectComp->getElement( RT_TEXCOORD, 1, 3 ); + viewToTangent->setName( "viewToTangent" ); + viewToTangent->setStructName( "IN" ); + viewToTangent->setType( "float3x3" ); + } + + return viewToTangent; +} + +Var* ShaderFeatureHLSL::getNormalMapTex() +{ + Var *normalMap = (Var*)LangElement::find( "bumpMap" ); + if ( !normalMap ) + { + normalMap = new Var; + normalMap->setType( "sampler2D" ); + normalMap->setName( "bumpMap" ); + normalMap->uniform = true; + normalMap->sampler = true; + normalMap->constNum = Var::getTexUnitNum(); + } + + return normalMap; +} + +void ShaderFeatureHLSL::getWsPosition( MultiLine *meta, LangElement *wsPosition ) +{ + // Get the input position. + Var *inPosition = (Var*)LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*)LangElement::find( "position" ); + + AssertFatal( inPosition, "ShaderFeatureHLSL::getWsPosition - The vertex position was not found!" ); + + // Get the transform to world space. + Var *objTrans = (Var*)LangElement::find( "objTrans" ); + if ( !objTrans ) + { + objTrans = new Var; + objTrans->setType( "float4x4" ); + objTrans->setName( "objTrans" ); + objTrans->uniform = true; + objTrans->constSortPos = cspPrimitive; + } + + meta->addStatement( new GenOp( " @ = mul( @, float4( @.xyz, 1 ) ).xyz;\r\n", + wsPosition, objTrans, inPosition ) ); +} + +Var* ShaderFeatureHLSL::addOutWsPosition( Vector &componentList, MultiLine *meta ) +{ + Var *outWsPosition = (Var*)LangElement::find( "wsPosition" ); + if ( !outWsPosition ) + { + // Setup the connector. + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + outWsPosition = connectComp->getElement( RT_TEXCOORD ); + outWsPosition->setName( "wsPosition" ); + outWsPosition->setStructName( "OUT" ); + outWsPosition->setType( "float3" ); + outWsPosition->mapsToSampler = false; + + getWsPosition( meta, outWsPosition ); + } + + return outWsPosition; +} + +Var* ShaderFeatureHLSL::getInWsPosition( Vector &componentList ) +{ + Var *wsPosition = (Var*)LangElement::find( "wsPosition" ); + if ( !wsPosition ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + wsPosition = connectComp->getElement( RT_TEXCOORD ); + wsPosition->setName( "wsPosition" ); + wsPosition->setStructName( "IN" ); + wsPosition->setType( "float3" ); + } + + return wsPosition; +} + +Var* ShaderFeatureHLSL::getWsView( Var *wsPosition, MultiLine *meta ) +{ + Var *wsView = (Var*)LangElement::find( "wsView" ); + if ( !wsView ) + { + wsView = new Var( "wsView", "float3" ); + + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if ( !eyePos ) + { + eyePos = new Var; + eyePos->setType( "float3" ); + eyePos->setName( "eyePosWorld" ); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + meta->addStatement( new GenOp( " @ = normalize( @ - @ );\r\n", + new DecOp( wsView ), eyePos, wsPosition ) ); + } + + return wsView; +} + + +//**************************************************************************** +// Base Texture +//**************************************************************************** + +void DiffuseMapFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + getOutTexCoord( "texCoord", + "float2", + true, + fd.features[MFT_TexAnim], + meta, + componentList ); + output = meta; +} + +void DiffuseMapFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + Var *inTex = getInTexCoord( "texCoord", "float2", true, componentList ); + + // create texture var + Var *diffuseMap = new Var; + diffuseMap->setType( "sampler2D" ); + diffuseMap->setName( "diffuseMap" ); + diffuseMap->uniform = true; + diffuseMap->sampler = true; + diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + if ( fd.features[MFT_CubeMap] ) + { + MultiLine * meta = new MultiLine; + + // create sample color + Var *diffColor = new Var; + diffColor->setType( "float4" ); + diffColor->setName( "diffuseColor" ); + LangElement *colorDecl = new DecOp( diffColor ); + + meta->addStatement( new GenOp( " @ = tex2D(@, @);\r\n", + colorDecl, + diffuseMap, + inTex ) ); + + meta->addStatement( new GenOp( " @;\r\n", assignColor( diffColor, Material::Mul ) ) ); + output = meta; + } + else + { + LangElement *statement = new GenOp( "tex2D(@, @)", diffuseMap, inTex ); + output = new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ); + } + +} + +ShaderFeature::Resources DiffuseMapFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void DiffuseMapFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_DiffuseMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; +} + + +//**************************************************************************** +// Overlay Texture +//**************************************************************************** + +void OverlayTexFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *inTex = getVertTexCoord( "texCoord2" ); + AssertFatal( inTex, "OverlayTexFeatHLSL::processVert() - The second UV set was not found!" ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "outTexCoord2" ); + outTex->setStructName( "OUT" ); + outTex->setType( "float2" ); + outTex->mapsToSampler = true; + + if( fd.features[MFT_TexAnim] ) + { + inTex->setType( "float4" ); + + // Find or create the texture matrix. + Var *texMat = (Var*)LangElement::find( "texMat" ); + if ( !texMat ) + { + texMat = new Var; + texMat->setType( "float4x4" ); + texMat->setName( "texMat" ); + texMat->uniform = true; + texMat->constSortPos = cspPass; + } + + output = new GenOp( " @ = mul(@, @);\r\n", outTex, texMat, inTex ); + return; + } + + // setup language elements to output incoming tex coords to output + output = new GenOp( " @ = @;\r\n", outTex, inTex ); +} + +void OverlayTexFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inTex = connectComp->getElement( RT_TEXCOORD ); + inTex->setName( "texCoord2" ); + inTex->setStructName( "IN" ); + inTex->setType( "float2" ); + inTex->mapsToSampler = true; + + // create texture var + Var *diffuseMap = new Var; + diffuseMap->setType( "sampler2D" ); + diffuseMap->setName( "overlayMap" ); + diffuseMap->uniform = true; + diffuseMap->sampler = true; + diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + LangElement *statement = new GenOp( "tex2D(@, @)", diffuseMap, inTex ); + output = new GenOp( " @;\r\n", assignColor( statement, Material::LerpAlpha ) ); +} + +ShaderFeature::Resources OverlayTexFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + return res; +} + +void OverlayTexFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_OverlayMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; +} + + +//**************************************************************************** +// Diffuse color +//**************************************************************************** + +void DiffuseFeatureHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *diffuseMaterialColor = new Var; + diffuseMaterialColor->setType( "float4" ); + diffuseMaterialColor->setName( "diffuseMaterialColor" ); + diffuseMaterialColor->uniform = true; + diffuseMaterialColor->constSortPos = cspPotentialPrimitive; + + MultiLine * meta = new MultiLine; + meta->addStatement( new GenOp( " @;\r\n", assignColor( diffuseMaterialColor, Material::Add ) ) ); + output = meta; +} + + +//**************************************************************************** +// Lightmap +//**************************************************************************** + +void LightmapFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab tex register from incoming vert + Var *inTex = getVertTexCoord( "texCoord2" ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "texCoord2" ); + outTex->setStructName( "OUT" ); + outTex->setType( "float2" ); + outTex->mapsToSampler = true; + + // setup language elements to output incoming tex coords to output + output = new GenOp( " @ = @;\r\n", outTex, inTex ); +} + +void LightmapFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inTex = connectComp->getElement( RT_TEXCOORD ); + inTex->setName( "texCoord2" ); + inTex->setStructName( "IN" ); + inTex->setType( "float2" ); + inTex->mapsToSampler = true; + + // create texture var + Var *lightMap = new Var; + lightMap->setType( "sampler2D" ); + lightMap->setName( "lightMap" ); + lightMap->uniform = true; + lightMap->sampler = true; + lightMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + + // argh, pixel specular should prob use this too + if( fd.features[MFT_NormalMap] ) + { + Var *lmColor = new Var; + lmColor->setName( "lmColor" ); + lmColor->setType( "float4" ); + LangElement *lmColorDecl = new DecOp( lmColor ); + + output = new GenOp( " @ = tex2D(@, @);\r\n", lmColorDecl, lightMap, inTex ); + return; + } + + // Add realtime lighting, if it is available + LangElement *statement = NULL; + if( fd.features[MFT_RTLighting] ) + { + // Advanced lighting is the only dynamic lighting supported right now + Var *inColor = (Var*) LangElement::find( "d_lightcolor" ); + if(inColor != NULL) + { + // Find out if RTLighting should be added or substituted + bool bPreProcessedLighting = false; + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + bPreProcessedLighting = lightBin->MRTLightmapsDuringPrePass(); + } + + // Lightmap has already been included in the advanced light bin, so + // no need to do any sampling or anything + if(bPreProcessedLighting) + statement = new GenOp( "float4(@, 1.0)", inColor ); + else + statement = new GenOp( "tex2D(@, @) + float4(@.rgb, 0.0)", lightMap, inTex, inColor ); + } + } + + // If we still don't have it... then just sample the lightmap. + if ( !statement ) + statement = new GenOp( "tex2D(@, @)", lightMap, inTex ); + + // Assign to proper render target + MultiLine *meta = new MultiLine; + if( fd.features[MFT_LightbufferMRT] ) + { + meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + } + else + meta->addStatement( new GenOp( " @;\r\n", assignColor( statement, Material::Mul ) ) ); + + output = meta; +} + +ShaderFeature::Resources LightmapFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void LightmapFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_LightMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; + else + passData.mTexType[ texIndex++ ] = Material::Lightmap; +} + +U32 LightmapFeatHLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + +//**************************************************************************** +// Tonemap +//**************************************************************************** + +void TonemapFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Grab the connector + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Set up the second set of texCoords + Var *inTex2 = getVertTexCoord( "texCoord2" ); + + if ( inTex2 ) + { + Var *outTex2 = connectComp->getElement( RT_TEXCOORD ); + outTex2->setName( "texCoord2" ); + outTex2->setStructName( "OUT" ); + outTex2->setType( "float2" ); + outTex2->mapsToSampler = true; + + output = new GenOp( " @ = @;\r\n", outTex2, inTex2 ); + } +} + +void TonemapFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Grab connector + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *inTex2 = connectComp->getElement( RT_TEXCOORD ); + inTex2->setName( "texCoord2" ); + inTex2->setStructName( "IN" ); + inTex2->setType( "float2" ); + inTex2->mapsToSampler = true; + + // create texture var + Var *toneMap = new Var; + toneMap->setType( "sampler2D" ); + toneMap->setName( "toneMap" ); + toneMap->uniform = true; + toneMap->sampler = true; + toneMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + MultiLine * meta = new MultiLine; + + // First get the toneMap color + Var *toneMapColor = new Var; + toneMapColor->setType( "float4" ); + toneMapColor->setName( "toneMapColor" ); + LangElement *toneMapColorDecl = new DecOp( toneMapColor ); + + meta->addStatement( new GenOp( " @ = tex2D(@, @);\r\n", toneMapColorDecl, toneMap, inTex2 ) ); + + // We do a different calculation if there is a diffuse map or not + Material::BlendOp blendOp = Material::Mul; + if ( fd.features[MFT_DiffuseMap] ) + { + // Reverse the tonemap + meta->addStatement( new GenOp( " @ = -1.0f * log(1.0f - @);\r\n", toneMapColor, toneMapColor ) ); + + // Re-tonemap with the current color factored in + blendOp = Material::ToneMap; + } + + // Find out if RTLighting should be added + bool bPreProcessedLighting = false; + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + bPreProcessedLighting = lightBin->MRTLightmapsDuringPrePass(); + } + + // Add in the realtime lighting contribution + if ( fd.features[MFT_RTLighting] ) + { + // Right now, only Advanced Lighting is supported + Var *inColor = (Var*) LangElement::find( "d_lightcolor" ); + if(inColor != NULL) + { + // Assign value in d_lightcolor to toneMapColor if it exists. This is + // the dynamic light buffer, and it already has the tonemap included + if(bPreProcessedLighting) + meta->addStatement( new GenOp( " @.rgb = @;\r\n", toneMapColor, inColor ) ); + else + meta->addStatement( new GenOp( " @.rgb += @.rgb;\r\n", toneMapColor, inColor ) ); + } + } + + // Assign to proper render target + if( fd.features[MFT_LightbufferMRT] ) + { + meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + } + else + meta->addStatement( new GenOp( " @;\r\n", assignColor( toneMapColor, blendOp ) ) ); + + output = meta; +} + +ShaderFeature::Resources TonemapFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void TonemapFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_ToneMap ); + if ( tex ) + { + passData.mTexType[ texIndex ] = Material::ToneMapTex; + passData.mTexSlot[ texIndex++ ].texObject = tex; + } +} + +U32 TonemapFeatHLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + +//**************************************************************************** +// pureLIGHT Lighting +//**************************************************************************** + +void VertLitHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If we have a lightMap or toneMap then our lighting will be + // handled by the MFT_LightMap or MFT_ToneNamp feature instead + if ( fd.features[MFT_LightMap] || fd.features[MFT_ToneMap] ) + { + output = NULL; + return; + } + + // Search for vert color + Var *inColor = (Var*) LangElement::find( "diffuse" ); + + // If there isn't a vertex color then we can't do anything + if( !inColor ) + { + output = NULL; + return; + } + + // Grab the connector color + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outColor = connectComp->getElement( RT_COLOR ); + outColor->setName( "vertColor" ); + outColor->setStructName( "OUT" ); + outColor->setType( "float4" ); + + output = new GenOp( " @ = @;\r\n", outColor, inColor ); +} + +void VertLitHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If we have a lightMap or toneMap then our lighting will be + // handled by the MFT_LightMap or MFT_ToneNamp feature instead + if ( fd.features[MFT_LightMap] || fd.features[MFT_ToneMap] ) + { + output = NULL; + return; + } + + // Grab the connector color register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *vertColor = connectComp->getElement( RT_COLOR ); + vertColor->setName( "vertColor" ); + vertColor->setStructName( "IN" ); + vertColor->setType( "float4" ); + + MultiLine * meta = new MultiLine; + + // Defaults (no diffuse map) + Material::BlendOp blendOp = Material::Mul; + LangElement *outColor = vertColor; + + // We do a different calculation if there is a diffuse map or not + if ( fd.features[MFT_DiffuseMap] || fd.features[MFT_VertLitTone] ) + { + Var * finalVertColor = new Var; + finalVertColor->setName( "finalVertColor" ); + finalVertColor->setType( "float4" ); + LangElement *finalVertColorDecl = new DecOp( finalVertColor ); + + // Reverse the tonemap + meta->addStatement( new GenOp( " @ = -1.0f * log(1.0f - @);\r\n", finalVertColorDecl, vertColor ) ); + + // Set the blend op to tonemap + blendOp = Material::ToneMap; + outColor = finalVertColor; + } + + // Add in the realtime lighting contribution, if applicable + if ( fd.features[MFT_RTLighting] ) + { + Var *rtLightingColor = (Var*) LangElement::find( "d_lightcolor" ); + if(rtLightingColor != NULL) + { + // Find out if RTLighting should be added or substituted + bool bPreProcessedLighting = false; + MatTextureTarget *texTarget = MatTextureTarget::findTargetByName(AdvancedLightBinManager::smBufferName); + if(texTarget) + { + AssertFatal(dynamic_cast(texTarget), "Bad buffer type!"); + AdvancedLightBinManager *lightBin = static_cast(texTarget); + bPreProcessedLighting = lightBin->MRTLightmapsDuringPrePass(); + } + + // Assign value in d_lightcolor to toneMapColor if it exists. This is + // the dynamic light buffer, and it already has the baked-vertex-color + // included in it + if(bPreProcessedLighting) + outColor = new GenOp( "float4(@.rgb, 1.0)", rtLightingColor ); + else + outColor = new GenOp( "float4(@.rgb + @.rgb, 1.0)", rtLightingColor, outColor ); + } + } + + // Output the color + if ( fd.features[MFT_LightbufferMRT] ) + { + meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, Material::None, NULL, ShaderFeature::RenderTarget1 ) ) ); + meta->addStatement( new GenOp( " @.a = 0.0001;\r\n", LangElement::find( getOutputTargetVarName(ShaderFeature::RenderTarget1) ) ) ); + } + else + meta->addStatement( new GenOp( " @;\r\n", assignColor( outColor, blendOp ) ) ); + + output = meta; +} + +U32 VertLitHLSL::getOutputTargets( const MaterialFeatureData &fd ) const +{ + return fd.features[MFT_LightbufferMRT] ? ShaderFeature::RenderTarget1 : ShaderFeature::DefaultTarget; +} + +//**************************************************************************** +// Detail map +//**************************************************************************** + +void DetailFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab incoming texture coords + Var *inTex = getVertTexCoord( "texCoord" ); + + // create detail variable + Var *detScale = new Var; + detScale->setType( "float2" ); + detScale->setName( "detailScale" ); + detScale->uniform = true; + detScale->constSortPos = cspPass; + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "detCoord" ); + outTex->setStructName( "OUT" ); + outTex->setType( "float2" ); + outTex->mapsToSampler = true; + + if( fd.features[MFT_TexAnim] ) + { + inTex->setType( "float4" ); + + // Find or create the texture matrix. + Var *texMat = (Var*)LangElement::find( "texMat" ); + if ( !texMat ) + { + texMat = new Var; + texMat->setType( "float4x4" ); + texMat->setName( "texMat" ); + texMat->uniform = true; + texMat->constSortPos = cspPass; + } + + output = new GenOp( " @ = mul(@, @) * @;\r\n", outTex, texMat, inTex, detScale ); + return; + } + + // setup output to mul texCoord by detail scale + output = new GenOp( " @ = @ * @;\r\n", outTex, inTex, detScale ); +} + +void DetailFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inTex = connectComp->getElement( RT_TEXCOORD ); + inTex->setName( "detCoord" ); + inTex->setStructName( "IN" ); + inTex->setType( "float2" ); + inTex->mapsToSampler = true; + + // create texture var + Var *detailMap = new Var; + detailMap->setType( "sampler2D" ); + detailMap->setName( "detailMap" ); + detailMap->uniform = true; + detailMap->sampler = true; + detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // We're doing the standard greyscale detail map + // technique which can darken and lighten the + // diffuse texture. + + // TODO: We could add a feature to toggle between this + // and a simple multiplication with the detail map. + + LangElement *statement = new GenOp( "( tex2D(@, @) * 2.0 ) - 1.0", detailMap, inTex ); + output = new GenOp( " @;\r\n", assignColor( statement, Material::Add ) ); +} + +ShaderFeature::Resources DetailFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + res.numTexReg = 1; + + return res; +} + +void DetailFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ) +{ + GFXTextureObject *tex = stageDat.getTex( MFT_DetailMap ); + if ( tex ) + passData.mTexSlot[ texIndex++ ].texObject = tex; +} + + +//**************************************************************************** +// Vertex position +//**************************************************************************** + +void VertPositionHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // First check for an input position from a previous feature + // then look for the default vertex position. + Var *inPosition = (Var*)LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*)LangElement::find( "position" ); + + // grab connector position + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outPosition = connectComp->getElement( RT_POSITION ); + outPosition->setName( "hpos" ); + outPosition->setStructName( "OUT" ); + + // create modelview variable + Var *modelview = new Var; + modelview->setType( "float4x4" ); + modelview->setName( "modelview" ); + modelview->uniform = true; + modelview->constSortPos = cspPrimitive; + + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( " @ = mul(@, float4(@.xyz,1));\r\n", outPosition, modelview, inPosition ) ); + output = meta; +} + + +//**************************************************************************** +// Reflect Cubemap +//**************************************************************************** + +void ReflectCubeFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // search for vert normal + Var *inNormal = (Var*) LangElement::find( "normal" ); + if ( !inNormal ) + return; + + MultiLine * meta = new MultiLine; + + // If a base or bump tex is present in the material, but not in the + // current pass - we need to add one to the current pass to use + // its alpha channel as a gloss map. Here we just need the tex coords. + if( !fd.features[MFT_DiffuseMap] && + !fd.features[MFT_NormalMap] ) + { + if( fd.materialFeatures[MFT_DiffuseMap] || + fd.materialFeatures[MFT_NormalMap] ) + { + // find incoming texture var + Var *inTex = getVertTexCoord( "texCoord" ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "texCoord" ); + outTex->setStructName( "OUT" ); + outTex->setType( "float2" ); + outTex->mapsToSampler = true; + + // setup language elements to output incoming tex coords to output + meta->addStatement( new GenOp( " @ = @;\r\n", outTex, inTex ) ); + } + } + + // create cubeTrans + Var *cubeTrans = new Var; + cubeTrans->setType( "float3x3" ); + cubeTrans->setName( "cubeTrans" ); + cubeTrans->uniform = true; + cubeTrans->constSortPos = cspPrimitive; + + // create cubeEye position + Var *cubeEyePos = new Var; + cubeEyePos->setType( "float3" ); + cubeEyePos->setName( "cubeEyePos" ); + cubeEyePos->uniform = true; + cubeEyePos->constSortPos = cspPrimitive; + + // cube vert position + Var * cubeVertPos = new Var; + cubeVertPos->setName( "cubeVertPos" ); + cubeVertPos->setType( "float3" ); + LangElement *cubeVertPosDecl = new DecOp( cubeVertPos ); + + meta->addStatement( new GenOp( " @ = mul(@, @).xyz;\r\n", + cubeVertPosDecl, cubeTrans, LangElement::find( "position" ) ) ); + + // cube normal + Var * cubeNormal = new Var; + cubeNormal->setName( "cubeNormal" ); + cubeNormal->setType( "float3" ); + LangElement *cubeNormDecl = new DecOp( cubeNormal ); + + meta->addStatement( new GenOp( " @ = normalize( mul(@, normalize(@)).xyz );\r\n", + cubeNormDecl, cubeTrans, inNormal ) ); + + // eye to vert + Var * eyeToVert = new Var; + eyeToVert->setName( "eyeToVert" ); + eyeToVert->setType( "float3" ); + LangElement *e2vDecl = new DecOp( eyeToVert ); + + meta->addStatement( new GenOp( " @ = @ - @;\r\n", + e2vDecl, cubeVertPos, cubeEyePos ) ); + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *reflectVec = connectComp->getElement( RT_TEXCOORD ); + reflectVec->setName( "reflectVec" ); + reflectVec->setStructName( "OUT" ); + reflectVec->setType( "float3" ); + reflectVec->mapsToSampler = true; + + meta->addStatement( new GenOp( " @ = reflect(@, @);\r\n", reflectVec, eyeToVert, cubeNormal ) ); + + output = meta; +} + +void ReflectCubeFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine * meta = new MultiLine; + Var *glossColor = NULL; + + // If a base or bump tex is present in the material, but not in the + // current pass - we need to add one to the current pass to use + // its alpha channel as a gloss map. + if( !fd.features[MFT_DiffuseMap] && + !fd.features[MFT_NormalMap] ) + { + if( fd.materialFeatures[MFT_DiffuseMap] || + fd.materialFeatures[MFT_NormalMap] ) + { + // grab connector texcoord register + Var *inTex = getInTexCoord( "texCoord", "float2", true, componentList ); + + // create texture var + Var *newMap = new Var; + newMap->setType( "sampler2D" ); + newMap->setName( "glossMap" ); + newMap->uniform = true; + newMap->sampler = true; + newMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // create sample color + Var *color = new Var; + color->setType( "float4" ); + color->setName( "diffuseColor" ); + LangElement *colorDecl = new DecOp( color ); + + glossColor = color; + + meta->addStatement( new GenOp( " @ = tex2D( @, @ );\r\n", colorDecl, newMap, inTex ) ); + } + } + else + { + glossColor = (Var*) LangElement::find( "diffuseColor" ); + if( !glossColor ) + glossColor = (Var*) LangElement::find( "bumpNormal" ); + } + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *reflectVec = connectComp->getElement( RT_TEXCOORD ); + reflectVec->setName( "reflectVec" ); + reflectVec->setStructName( "IN" ); + reflectVec->setType( "float3" ); + reflectVec->mapsToSampler = true; + + // create cubemap var + Var *cubeMap = new Var; + cubeMap->setType( "samplerCUBE" ); + cubeMap->setName( "cubeMap" ); + cubeMap->uniform = true; + cubeMap->sampler = true; + cubeMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // TODO: Restore the lighting attenuation here! + Var *attn = NULL; + //if ( fd.materialFeatures[MFT_DynamicLight] ) + //attn = (Var*)LangElement::find("attn"); + //else + if ( fd.materialFeatures[MFT_RTLighting] ) + attn =(Var*)LangElement::find("d_NL_Att"); + + LangElement *texCube = new GenOp( "texCUBE( @, @ )", cubeMap, reflectVec ); + LangElement *lerpVal = NULL; + Material::BlendOp blendOp = Material::LerpAlpha; + + // Note that the lerpVal needs to be a float4 so that + // it will work with the LerpAlpha blend. + + if ( glossColor ) + { + if ( attn ) + lerpVal = new GenOp( "@ * saturate( @ )", glossColor, attn ); + else + lerpVal = glossColor; + } + else + { + if ( attn ) + lerpVal = new GenOp( "saturate( @ ).xxxx", attn ); + else + blendOp = Material::None; + } + + meta->addStatement( new GenOp( " @;\r\n", assignColor( texCube, blendOp, lerpVal ) ) ); + output = meta; +} + +ShaderFeature::Resources ReflectCubeFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + if( fd.features[MFT_DiffuseMap] || + fd.features[MFT_NormalMap] ) + { + res.numTex = 1; + res.numTexReg = 1; + } + else + { + res.numTex = 2; + res.numTexReg = 2; + } + + return res; +} + +void ReflectCubeFeatHLSL::setTexData( Material::StageData &stageDat, + const MaterialFeatureData &stageFeatures, + RenderPassData &passData, + U32 &texIndex ) +{ + // set up a gloss map if one is not present in the current pass + // but is present in the current material stage + if( !passData.mFeatureData.features[MFT_DiffuseMap] && + !passData.mFeatureData.features[MFT_NormalMap] ) + { + GFXTextureObject *tex = stageDat.getTex( MFT_DetailMap ); + if ( tex && + stageFeatures.features[MFT_DiffuseMap] ) + passData.mTexSlot[ texIndex++ ].texObject = tex; + else + { + tex = stageDat.getTex( MFT_NormalMap ); + + if ( tex && + stageFeatures.features[ MFT_NormalMap ] ) + passData.mTexSlot[ texIndex++ ].texObject = tex; + } + } + + if( stageDat.getCubemap() ) + { + passData.mCubeMap = stageDat.getCubemap(); + passData.mTexType[texIndex++] = Material::Cube; + } + else + { + if( stageFeatures.features[MFT_CubeMap] ) + { + // assuming here that it is a scenegraph cubemap + passData.mTexType[texIndex++] = Material::SGCube; + } + } + +} + + +//**************************************************************************** +// RTLighting +//**************************************************************************** + +RTLightingFeatHLSL::RTLightingFeatHLSL() + : mDep( "shaders/common/lighting.hlsl" ) +{ + addDependency( &mDep ); +} + +void RTLightingFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Find the incoming vertex normal. + Var *inNormal = (Var*)LangElement::find( "normal" ); + + // Skip out on realtime lighting if we don't have a normal + // or we're doing some sort of baked lighting. + if ( !inNormal || + fd.features[MFT_LightMap] || + fd.features[MFT_ToneMap] || + fd.features[MFT_VertLit] ) + return; + + MultiLine *meta = new MultiLine; + + // If there isn't a normal map then we need to pass + // the world space normal to the pixel shader ourselves. + if ( !fd.features[MFT_NormalMap] ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *outNormal = connectComp->getElement( RT_TEXCOORD ); + outNormal->setName( "wsNormal" ); + outNormal->setStructName( "OUT" ); + outNormal->setType( "float3" ); + outNormal->mapsToSampler = false; + + // Get the transform to world space. + Var *objTrans = (Var*)LangElement::find( "objTrans" ); + if ( !objTrans ) + { + objTrans = new Var; + objTrans->setType( "float4x4" ); + objTrans->setName( "objTrans" ); + objTrans->uniform = true; + objTrans->constSortPos = cspPrimitive; + } + + // Transform the normal to world space. + meta->addStatement( new GenOp( " @ = mul( @, float4( normalize( @ ), 0.0 ) ).xyz;\r\n", outNormal, objTrans, inNormal ) ); + } + + addOutWsPosition( componentList, meta ); + + output = meta; +} + +void RTLightingFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Skip out on realtime lighting if we don't have a normal + // or we're doing some sort of baked lighting. + // + // TODO: We can totally detect for this in the material + // feature setup... we should move it out of here! + // + if ( fd.features[MFT_LightMap] || fd.features[MFT_ToneMap] || fd.features[MFT_VertLit] ) + return; + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + MultiLine *meta = new MultiLine; + + // Look for a wsNormal or grab it from the connector. + Var *wsNormal = (Var*)LangElement::find( "wsNormal" ); + if ( !wsNormal ) + { + wsNormal = connectComp->getElement( RT_TEXCOORD ); + wsNormal->setName( "wsNormal" ); + wsNormal->setStructName( "IN" ); + wsNormal->setType( "float3" ); + + // If we loaded the normal its our resposibility + // to normalize it... the interpolators won't. + // + // Note we cast to half here to get partial precision + // optimized code which is an acceptable loss of + // precision for normals and performs much better + // on older Geforce cards. + // + meta->addStatement( new GenOp( " @ = normalize( half3( @ ) );\r\n", wsNormal, wsNormal ) ); + } + + // Now the wsPosition and wsView. + Var *wsPosition = getInWsPosition( componentList ); + Var *wsView = getWsView( wsPosition, meta ); + + // Create temporaries to hold results of lighting. + Var *rtShading = new Var( "rtShading", "float4" ); + Var *specular = new Var( "specular", "float4" ); + meta->addStatement( new GenOp( " @; @;\r\n", + new DecOp( rtShading ), new DecOp( specular ) ) ); + + // Look for a light mask generated from a previous + // feature (this is done for BL terrain lightmaps). + LangElement *lightMask = LangElement::find( "lightMask" ); + if ( !lightMask ) + lightMask = new GenOp( "float4( 1, 1, 1, 1 )" ); + + // Calculate the diffuse shading and specular powers. + meta->addStatement( new GenOp( " compute4Lights( @, @, @, @, @, @ );\r\n", + wsView, wsPosition, wsNormal, lightMask, rtShading, specular ) ); + + // Apply the lighting to the diffuse color. + LangElement *lighting = new GenOp( "float4( @.rgb + ambient.rgb, 1 )", rtShading ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( lighting, Material::Mul ) ) ); + output = meta; +} + +ShaderFeature::Resources RTLightingFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // These features disable realtime lighting. + if ( !fd.features[MFT_LightMap] && + !fd.features[MFT_ToneMap] && + !fd.features[MFT_VertLit] ) + { + // If enabled we pass the position. + res.numTexReg = 1; + + // If there isn't a bump map then we pass the + // world space normal as well. + if ( !fd.features[MFT_NormalMap] ) + res.numTexReg++; + } + + return res; +} + + +//**************************************************************************** +// Fog +//**************************************************************************** + +FogFeatHLSL::FogFeatHLSL() + : mFogDep( "shaders/common/torque.hlsl" ) +{ + addDependency( &mFogDep ); +} + +void FogFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + const bool vertexFog = Con::getBoolVariable( "$useVertexFog", false ); + if ( vertexFog || GFX->getPixelShaderVersion() < 3.0 ) + { + // Grab the eye position. + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if ( !eyePos ) + { + eyePos = new Var( "eyePosWorld", "float3" ); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + Var *fogData = new Var( "fogData", "float3" ); + fogData->uniform = true; + fogData->constSortPos = cspPass; + + Var *wsPosition = new Var( "fogPos", "float3" ); + getWsPosition( meta, new DecOp( wsPosition ) ); + + // We pass the fog amount to the pixel shader. + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *fogAmount = connectComp->getElement( RT_TEXCOORD ); + fogAmount->setName( "fogAmount" ); + fogAmount->setStructName( "OUT" ); + fogAmount->setType( "float" ); + fogAmount->mapsToSampler = false; + + meta->addStatement( new GenOp( " @ = saturate( computeSceneFog( @, @, @.r, @.g, @.b ) );\r\n", + fogAmount, eyePos, wsPosition, fogData, fogData, fogData ) ); + } + else + { + // We fog in world space... make sure the world space + // position is passed to the pixel shader. This is + // often already passed for lighting, so it takes up + // no extra output registers. + addOutWsPosition( componentList, meta ); + } + + output = meta; +} + +void FogFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + Var *fogColor = new Var; + fogColor->setType( "float4" ); + fogColor->setName( "fogColor" ); + fogColor->uniform = true; + fogColor->constSortPos = cspPass; + + // Get the out color. + Var *color = (Var*) LangElement::find( "col" ); + if ( !color ) + { + color = new Var; + color->setType( "fragout" ); + color->setName( "col" ); + color->setStructName( "OUT" ); + } + + Var *fogAmount; + + const bool vertexFog = Con::getBoolVariable( "$useVertexFog", false ); + if ( vertexFog || GFX->getPixelShaderVersion() < 3.0 ) + { + // Per-vertex.... just get the fog amount. + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + fogAmount = connectComp->getElement( RT_TEXCOORD ); + fogAmount->setName( "fogAmount" ); + fogAmount->setStructName( "IN" ); + fogAmount->setType( "float" ); + } + else + { + Var *wsPosition = getInWsPosition( componentList ); + + // grab the eye position + Var *eyePos = (Var*)LangElement::find( "eyePosWorld" ); + if ( !eyePos ) + { + eyePos = new Var( "eyePosWorld", "float3" ); + eyePos->uniform = true; + eyePos->constSortPos = cspPass; + } + + Var *fogData = new Var( "fogData", "float3" ); + fogData->uniform = true; + fogData->constSortPos = cspPass; + + /// Get the fog amount. + fogAmount = new Var( "fogAmount", "float" ); + meta->addStatement( new GenOp( " @ = saturate( computeSceneFog( @, @, @.r, @.g, @.b ) );\r\n", + new DecOp( fogAmount ), eyePos, wsPosition, fogData, fogData, fogData ) ); + } + + // Lerp between the fog color and diffuse color. + LangElement *fogLerp = new GenOp( "lerp( @, @, @ )", fogColor, color, fogAmount ); + meta->addStatement( new GenOp( " @ = @;\r\n", color, fogLerp ) ); + + output = meta; +} + +ShaderFeature::Resources FogFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTexReg = 1; + return res; +} + + +//**************************************************************************** +// Visibility +//**************************************************************************** + +void VisibilityFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // create visibility var + Var *visibility = new Var; + visibility->setType( "float" ); + visibility->setName( "visibility" ); + visibility->uniform = true; + visibility->constSortPos = cspPass; + + // search for color var + Var *color = (Var*) LangElement::find( "col" ); + + // Looks like its going to be a multiline statement + MultiLine * meta = new MultiLine; + + if( !color ) + { + // create color var + color = new Var; + color->setType( "fragout" ); + color->setName( "col" ); + color->setStructName( "OUT" ); + + // link it to ConnectData.shading + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *inColor = connectComp->getElement( RT_COLOR ); + inColor->setName( "shading" ); + inColor->setStructName( "IN" ); + inColor->setType( "float4" ); + + meta->addStatement( new GenOp( " @ = @;\r\n", color, inColor ) ); + } + + meta->addStatement( new GenOp( " @.w *= @;\r\n", color, visibility ) ); + + output = meta; +} + + +//**************************************************************************** +// ColorMultiply +//**************************************************************************** + +void ColorMultiplyFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *colorMultiply = new Var; + colorMultiply->setType( "float4" ); + colorMultiply->setName( "colorMultiply" ); + colorMultiply->uniform = true; + colorMultiply->constSortPos = cspPass; + + // search for color var + Var *color = (Var*) LangElement::find( "col" ); + if (color) + { + MultiLine* meta = new MultiLine; + LangElement* statement = new GenOp("lerp(@.rgb, @.rgb, @.a)", color, colorMultiply, colorMultiply); + meta->addStatement(new GenOp(" @.rgb = @;\r\n", color, statement)); + output = meta; + } +} + + +//**************************************************************************** +// AlphaTest +//**************************************************************************** + +void AlphaTestHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // If we're below SM3 and don't have a depth output + // feature then don't waste an instruction here. + if ( GFX->getPixelShaderVersion() < 3.0 && + !fd.features[ MFT_EyeSpaceDepthOut ] && + !fd.features[ MFT_DepthOut ] ) + { + output = NULL; + return; + } + + // If we don't have a color var then we cannot do an alpha test. + Var *color = (Var*)LangElement::find( "col" ); + if ( !color ) + { + output = NULL; + return; + } + + // Now grab the alpha test value. + Var *alphaTestVal = new Var; + alphaTestVal->setType( "float" ); + alphaTestVal->setName( "alphaTestValue" ); + alphaTestVal->uniform = true; + alphaTestVal->constSortPos = cspPrimitive; + + // Do the clip. + output = new GenOp( " clip( @.a - @ );\r\n", color, alphaTestVal ); +} + + +//**************************************************************************** +// GlowMask +//**************************************************************************** + +void GlowMaskHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + output = NULL; + + // Get the output color... and make it black to mask out + // glow passes rendered before us. + // + // The shader compiler will optimize out all the other + // code above that doesn't contribute to the alpha mask. + Var *color = (Var*)LangElement::find( "col" ); + if ( color ) + output = new GenOp( " @.rgb = 0;\r\n", color ); +} + + +//**************************************************************************** +// RenderTargetZero +//**************************************************************************** + +void RenderTargetZeroHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) +{ + // Do not actually assign zero, but instead a number so close to zero it may as well be zero. + // This will prevent a divide by zero causing an FP special on float render targets + output = new GenOp( " @;\r\n", assignColor( new GenOp( "0.00001" ), Material::None, NULL, mOutputTargetMask ) ); +} + + +//**************************************************************************** +// HDR Output +//**************************************************************************** + +HDROutHLSL::HDROutHLSL() + : mTorqueDep( "shaders/common/torque.hlsl" ) +{ + addDependency( &mTorqueDep ); +} + +void HDROutHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Let the helper function do the work. + Var *color = (Var*)LangElement::find( "col" ); + output = new GenOp( " hdrEncode( @ );\r\n", color ); +} + +//**************************************************************************** +// FoliageFeatureHLSL +//**************************************************************************** + +FoliageFeatureHLSL::FoliageFeatureHLSL() +: mDep( "shaders/common/foliage.hlsl" ) +{ + addDependency( &mDep ); +} + +void FoliageFeatureHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Get the input variables we need. + + Var *inPosition = (Var*)LangElement::find( "inPosition" ); + if ( !inPosition ) + inPosition = (Var*)LangElement::find( "position" ); + + Var *inColor = (Var*)LangElement::find( "diffuse" ); + + Var *inParams = (Var*)LangElement::find( "texCoord" ); + + MultiLine *meta = new MultiLine; + + // Declare the normal and tangent variables since they do not exist + // in this vert type, but we do need to set them up for others. + + Var *normal = new Var; + normal->setType( "float3" ); + normal->setName( "normal" ); + LangElement *normalDec = new DecOp( normal ); + meta->addStatement( new GenOp( " @;\n", normalDec ) ); + + Var *tangent = new Var; + tangent->setType( "float3" ); + tangent->setName( "T" ); + LangElement *tangentDec = new DecOp( tangent ); + meta->addStatement( new GenOp( " @;\n", tangentDec ) ); + + // All actual work is offloaded to this method. + meta->addStatement( new GenOp( " foliageProcessVert( @, @, @, @, @ );\r\n", inPosition, inColor, inParams, normal, tangent ) ); + + output = meta; +} + +void FoliageFeatureHLSL::determineFeature( Material *material, const GFXVertexFormat *vertexFormat, U32 stageNum, const FeatureType &type, const FeatureSet &features, MaterialFeatureData *outFeatureData ) +{ + outFeatureData->features.addFeature( type ); +} \ No newline at end of file diff --git a/shaderGen/HLSL/shaderFeatureHLSL.h b/shaderGen/HLSL/shaderFeatureHLSL.h new file mode 100644 index 0000000..5f525f0 --- /dev/null +++ b/shaderGen/HLSL/shaderFeatureHLSL.h @@ -0,0 +1,543 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ +#define _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ + +#ifndef _SHADERFEATURE_H_ +#include "shaderGen/shaderFeature.h" +#endif + +struct LangElement; +struct MaterialFeatureData; +struct RenderPassData; + + +class ShaderFeatureHLSL : public ShaderFeature +{ +public: + ShaderFeatureHLSL(); + + /// + Var* getOutTexCoord( const char *name, + const char *type, + bool mapsToSampler, + bool useTexAnim, + MultiLine *meta, + Vector &componentList ); + + /// Returns an input texture coord by name adding it + /// to the input connector if it doesn't exist. + static Var* getInTexCoord( const char *name, + const char *type, + bool mapsToSampler, + Vector &componentList ); + + /// Returns the "objToTangentSpace" transform or creates one if this + /// is the first feature to need it. + Var* getOutObjToTangentSpace( Vector &componentList, + MultiLine *meta ); + + /// Returns the existing output "worldToTangent" transform or + /// creates one if this is the first feature to need it. + Var* getOutWorldToTangent( Vector &componentList, + MultiLine *meta ); + + /// Returns the input "worldToTanget" space transform + /// adding it to the input connector if it doesn't exist. + static Var* getInWorldToTangent( Vector &componentList ); + + /// Returns the existing output "viewToTangent" transform or + /// creates one if this is the first feature to need it. + Var* getOutViewToTangent( Vector &componentList, + MultiLine *meta ); + + /// Returns the input "viewToTangent" space transform + /// adding it to the input connector if it doesn't exist. + static Var* getInViewToTangent( Vector &componentList ); + + /// Calculates the world space position in the vertex shader and + /// assigns it to the passed language element. It does not pass + /// it across the connector to the pixel shader. + /// @see addOutWsPosition + static void getWsPosition( MultiLine *meta, LangElement *wsPosition ); + + /// Adds the "wsPosition" to the input connector if it doesn't exist. + static Var* addOutWsPosition( Vector &componentList, MultiLine *meta ); + + /// Returns the input world space position from the connector. + static Var* getInWsPosition( Vector &componentList ); + + /// Returns the world space view vector from the wsPosition. + static Var* getWsView( Var *wsPosition, MultiLine *meta ); + + /// Returns the input normal map texture. + static Var* getNormalMapTex(); + + // ShaderFeature + Var* getVertTexCoord( const String &name ); + LangElement* setupTexSpaceMat( Vector &componentList, Var **texSpaceMat ); + LangElement* assignColor( LangElement *elem, Material::BlendOp blend, LangElement *lerpElem = NULL, ShaderFeature::OutputTarget outputTarget = ShaderFeature::DefaultTarget ); + LangElement* expandNormalMap( LangElement *sampleNormalOp, LangElement *normalDecl, LangElement *normalVar, const MaterialFeatureData &fd ); +}; + + +class NamedFeatureHLSL : public ShaderFeatureHLSL +{ +protected: + String mName; + +public: + NamedFeatureHLSL( const String &name ) + : mName( name ) + {} + + virtual String getName() { return mName; } +}; + +class RenderTargetZeroHLSL : public ShaderFeatureHLSL +{ +protected: + ShaderFeature::OutputTarget mOutputTargetMask; + String mFeatureName; + +public: + RenderTargetZeroHLSL( const ShaderFeature::OutputTarget target ) + : mOutputTargetMask( target ) + { + char buffer[256]; + dSprintf(buffer, sizeof(buffer), "Render Target Output = 0.0, output mask %04b", mOutputTargetMask); + mFeatureName = buffer; + } + + virtual String getName() { return mFeatureName; } + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return mOutputTargetMask; } +}; + + +/// Vertex position +class VertPositionHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual String getName() + { + return "Vert Position"; + } + + virtual void determineFeature( Material *material, + const GFXVertexFormat *vertexFormat, + U32 stageNum, + const FeatureType &type, + const FeatureSet &features, + MaterialFeatureData *outFeatureData ) + { + // This feature is always on! + outFeatureData->features.addFeature( type ); + } + +}; + + +/// Vertex lighting based on the normal and the light +/// direction passed through the vertex color. +class RTLightingFeatHLSL : public ShaderFeatureHLSL +{ +protected: + + ShaderIncludeDependency mDep; + +public: + + RTLightingFeatHLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() + { + return "RT Lighting"; + } +}; + + +/// Base texture +class DiffuseMapFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Base Texture"; + } +}; + + +/// Overlay texture +class OverlayTexFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Overlay Texture"; + } +}; + + +/// Diffuse color +class DiffuseFeatureHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual String getName() + { + return "Diffuse Color"; + } +}; + + +/// Lightmap +class LightmapFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Lightmap"; + } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; +}; + + +/// Tonemap +class TonemapFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::LerpAlpha; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Tonemap"; + } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; +}; + + +/// Baked lighting stored on the vertex color +class VertLitHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual String getName() + { + return "Vert Lit"; + } + + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const; +}; + + +/// Detail map +class DetailFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::Mul; } + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Detail"; + } +}; + + +/// Reflect Cubemap +class ReflectCubeFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + // Sets textures and texture flags for current pass + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ); + + virtual String getName() + { + return "Reflect Cube"; + } +}; + + +/// Fog +class FogFeatHLSL : public ShaderFeatureHLSL +{ +protected: + + ShaderIncludeDependency mFogDep; + +public: + FogFeatHLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::LerpAlpha; } + + virtual String getName() + { + return "Fog"; + } +}; + + +/// Tex Anim +class TexAnimHLSL : public ShaderFeatureHLSL +{ +public: + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Texture Animation"; + } +}; + + +/// Visibility +class VisibilityFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Visibility"; + } +}; + + +/// ColorMultiply +class ColorMultiplyFeatHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Color Multiply"; + } +}; + + +/// +class AlphaTestHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Alpha Test"; + } +}; + + +/// Special feature used to mask out the RGB color for +/// non-glow passes of glow materials. +/// @see RenderGlowMgr +class GlowMaskHLSL : public ShaderFeatureHLSL +{ +public: + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() + { + return "Glow Mask"; + } +}; + +/// This should be the final feature on most pixel shaders which +/// encodes the color for the current HDR target format. +/// @see HDRPostFx +/// @see LightManager +/// @see torque.hlsl +class HDROutHLSL : public ShaderFeatureHLSL +{ +protected: + + ShaderIncludeDependency mTorqueDep; + +public: + + HDROutHLSL(); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp() { return Material::None; } + + virtual String getName() { return "HDR Output"; } +}; + +/// +class FoliageFeatureHLSL : public ShaderFeatureHLSL +{ +protected: + + ShaderIncludeDependency mDep; + +public: + + FoliageFeatureHLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + /* + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Material::BlendOp getBlendOp(){ return Material::None; } + + virtual Resources getResources( const MaterialFeatureData &fd ); + */ + + virtual String getName() + { + return "Foliage Feature"; + } + + virtual void determineFeature( Material *material, + const GFXVertexFormat *vertexFormat, + U32 stageNum, + const FeatureType &type, + const FeatureSet &features, + MaterialFeatureData *outFeatureData ); +}; + +#endif // _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ diff --git a/shaderGen/HLSL/shaderGenHLSL.cpp b/shaderGen/HLSL/shaderGenHLSL.cpp new file mode 100644 index 0000000..7449871 --- /dev/null +++ b/shaderGen/HLSL/shaderGenHLSL.cpp @@ -0,0 +1,182 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/HLSL/shaderGenHLSL.h" + +#include "shaderGen/HLSL/shaderCompHLSL.h" +#include "shaderGen/featureMgr.h" + + +void ShaderGenPrinterHLSL::printShaderHeader(Stream& stream) +{ + const char *header1 = "//*****************************************************************************\r\n"; + const char *header2 = "// Torque -- HLSL procedural shader\r\n"; + + stream.write( dStrlen(header1), header1 ); + stream.write( dStrlen(header2), header2 ); + stream.write( dStrlen(header1), header1 ); + + const char* header3 = "\r\n"; + stream.write( dStrlen(header3), header3 ); +} + +void ShaderGenPrinterHLSL::printMainComment(Stream& stream) +{ + const char * header5 = "// Main\r\n"; + const char * line = "//-----------------------------------------------------------------------------\r\n"; + + stream.write( dStrlen(line), line ); + stream.write( dStrlen(header5), header5 ); + stream.write( dStrlen(line), line ); +} + +void ShaderGenPrinterHLSL::printVertexShaderCloser(Stream& stream) +{ + const char *closer = " return OUT;\r\n}\r\n"; + stream.write( dStrlen(closer), closer ); +} + +void ShaderGenPrinterHLSL::printPixelShaderOutputStruct(Stream& stream, const MaterialFeatureData &featureData) +{ + // Determine the number of output targets we need + U32 numMRTs = 0; + for( U32 i = 0; i < FEATUREMGR->getFeatureCount(); i++ ) + { + const FeatureInfo &info = FEATUREMGR->getAt( i ); + if( featureData.features.hasFeature( *info.type ) ) + numMRTs |= info.feature->getOutputTargets( featureData ); + } + + WRITESTR( "struct Fragout\r\n" ); + WRITESTR( "{\r\n" ); + WRITESTR( " float4 col : COLOR0;\r\n" ); + for( U32 i = 1; i < 4; i++ ) + { + if( numMRTs & 1 << i ) + WRITESTR( avar( " float4 col%d : COLOR%d;\r\n", i, i ) ); + } + WRITESTR( "};\r\n" ); + WRITESTR( "\r\n" ); + WRITESTR( "\r\n" ); +} + +void ShaderGenPrinterHLSL::printPixelShaderCloser(Stream& stream) +{ + WRITESTR( "\r\n return OUT;\r\n}\r\n" ); +} + +void ShaderGenPrinterHLSL::printLine(Stream& stream, const String& line) +{ + stream.write(line.length(), line.c_str()); + const char* end = "\r\n"; + stream.write(dStrlen(end), end); +} + +const char* ShaderGenComponentFactoryHLSL::typeToString( GFXDeclType type ) +{ + switch ( type ) + { + default: + case GFXDeclType_Float: + return "float"; + + case GFXDeclType_Float2: + return "float2"; + + case GFXDeclType_Float3: + return "float3"; + + case GFXDeclType_Float4: + case GFXDeclType_Color: + return "float4"; + } +} + +ShaderComponent* ShaderGenComponentFactoryHLSL::createVertexInputConnector( const GFXVertexFormat &vertexFormat ) +{ + ShaderConnectorHLSL *vertComp = new ShaderConnectorHLSL; + vertComp->setName( "VertData" ); + + // Loop thru the vertex format elements. + for ( U32 i=0; i < vertexFormat.getElementCount(); i++ ) + { + const GFXVertexElement &element = vertexFormat.getElement( i ); + + Var *var = NULL; + + if ( element.isSemantic( GFXSemantic::POSITION ) ) + { + var = vertComp->getElement( RT_POSITION ); + var->setName( "position" ); + } + else if ( element.isSemantic( GFXSemantic::NORMAL ) ) + { + var = vertComp->getElement( RT_NORMAL ); + var->setName( "normal" ); + } + else if ( element.isSemantic( GFXSemantic::TANGENT ) ) + { + var = vertComp->getElement( RT_TANGENT ); + var->setName( "T" ); + } + else if ( element.isSemantic( GFXSemantic::TANGENTW ) ) + { + var = vertComp->getIndexedElement( element.getSemanticIndex(), RT_TEXCOORD ); + var->setName( "tangentW" ); + } + else if ( element.isSemantic( GFXSemantic::BINORMAL ) ) + { + var = vertComp->getElement( RT_BINORMAL ); + var->setName( "B" ); + } + else if ( element.isSemantic( GFXSemantic::COLOR ) ) + { + var = vertComp->getElement( RT_COLOR ); + var->setName( "diffuse" ); + } + else if ( element.isSemantic( GFXSemantic::TEXCOORD ) ) + { + var = vertComp->getIndexedElement( element.getSemanticIndex(), RT_TEXCOORD ); + if ( element.getSemanticIndex() == 0 ) + var->setName( "texCoord" ); + else + var->setName( String::ToString( "texCoord%d", element.getSemanticIndex() + 1 ) ); + } + else + { + // Everything else is a texcoord! + var = vertComp->getIndexedElement( element.getSemanticIndex(), RT_TEXCOORD ); + var->setName( "tc" + element.getSemantic() ); + } + + if ( !var ) + continue; + + var->setStructName( "IN" ); + var->setType( typeToString( element.getType() ) ); + } + + return vertComp; +} + +ShaderComponent* ShaderGenComponentFactoryHLSL::createVertexPixelConnector() +{ + ShaderComponent* comp = new ShaderConnectorHLSL; + ((ShaderConnector*)comp)->setName("ConnectData"); + return comp; +} + +ShaderComponent* ShaderGenComponentFactoryHLSL::createVertexParamsDef() +{ + ShaderComponent* comp = new VertexParamsDefHLSL; + return comp; +} + +ShaderComponent* ShaderGenComponentFactoryHLSL::createPixelParamsDef() +{ + ShaderComponent* comp = new PixelParamsDefHLSL; + return comp; +} diff --git a/shaderGen/HLSL/shaderGenHLSL.h b/shaderGen/HLSL/shaderGenHLSL.h new file mode 100644 index 0000000..28fe999 --- /dev/null +++ b/shaderGen/HLSL/shaderGenHLSL.h @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADERGEN_HLSL_H_ +#define _SHADERGEN_HLSL_H_ + +#ifndef _SHADERGEN_H_ +#include "shaderGen/shaderGen.h" +#endif + + +class ShaderGenPrinterHLSL : public ShaderGenPrinter +{ +public: + + // ShaderGenPrinter + virtual void printShaderHeader(Stream& stream); + virtual void printMainComment(Stream& stream); + virtual void printVertexShaderCloser(Stream& stream); + virtual void printPixelShaderOutputStruct(Stream& stream, const MaterialFeatureData &featureData); + virtual void printPixelShaderCloser(Stream& stream); + virtual void printLine(Stream& stream, const String& line); +}; + +class ShaderGenComponentFactoryHLSL : public ShaderGenComponentFactory +{ +public: + + /// Helper function for converting a vertex decl + /// type to an HLSL type string. + static const char* typeToString( GFXDeclType type ); + + // ShaderGenComponentFactory + virtual ShaderComponent* createVertexInputConnector( const GFXVertexFormat &vertexFormat ); + virtual ShaderComponent* createVertexPixelConnector(); + virtual ShaderComponent* createVertexParamsDef(); + virtual ShaderComponent* createPixelParamsDef(); +}; + + +#endif // _SHADERGEN_HLSL_H_ \ No newline at end of file diff --git a/shaderGen/HLSL/shaderGenHLSLInit.cpp b/shaderGen/HLSL/shaderGenHLSLInit.cpp new file mode 100644 index 0000000..e94bcd9 --- /dev/null +++ b/shaderGen/HLSL/shaderGenHLSLInit.cpp @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "shaderGen/shaderGen.h" +#include "shaderGen/HLSL/shaderGenHLSL.h" +#include "shaderGen/HLSL/shaderFeatureHLSL.h" +#include "shaderGen/featureMgr.h" +#include "shaderGen/HLSL/bumpHLSL.h" +#include "shaderGen/HLSL/pixSpecularHLSL.h" +#include "shaderGen/HLSL/depthHLSL.h" +#include "shaderGen/HLSL/paraboloidHLSL.h" +#include "materials/materialFeatureTypes.h" + + +class ShaderGenHLSLInit +{ +public: + ShaderGenHLSLInit(); +private: + ShaderGen::ShaderGenInitDelegate mInitDelegate; + + void initShaderGen(ShaderGen* shadergen); +}; + +ShaderGenHLSLInit::ShaderGenHLSLInit() +{ + mInitDelegate.bind(this, &ShaderGenHLSLInit::initShaderGen); + SHADERGEN->registerInitDelegate(Direct3D9, mInitDelegate); + SHADERGEN->registerInitDelegate(Direct3D9_360, mInitDelegate); +} + +void ShaderGenHLSLInit::initShaderGen( ShaderGen *shaderGen ) +{ + shaderGen->setPrinter( new ShaderGenPrinterHLSL ); + shaderGen->setComponentFactory( new ShaderGenComponentFactoryHLSL ); + shaderGen->setFileEnding( "hlsl" ); + + FEATUREMGR->registerFeature( MFT_VertTransform, new VertPositionHLSL ); + FEATUREMGR->registerFeature( MFT_RTLighting, new RTLightingFeatHLSL ); + FEATUREMGR->registerFeature( MFT_IsDXTnm, new NamedFeatureHLSL( "DXTnm" ) ); + FEATUREMGR->registerFeature( MFT_TexAnim, new TexAnimHLSL ); + FEATUREMGR->registerFeature( MFT_DiffuseMap, new DiffuseMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_OverlayMap, new OverlayTexFeatHLSL ); + FEATUREMGR->registerFeature( MFT_DiffuseColor, new DiffuseFeatureHLSL ); + FEATUREMGR->registerFeature( MFT_ColorMultiply, new ColorMultiplyFeatHLSL ); + FEATUREMGR->registerFeature( MFT_AlphaTest, new AlphaTestHLSL ); + FEATUREMGR->registerFeature( MFT_GlowMask, new GlowMaskHLSL ); + FEATUREMGR->registerFeature( MFT_LightMap, new LightmapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_ToneMap, new TonemapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_VertLit, new VertLitHLSL ); + FEATUREMGR->registerFeature( MFT_Parallax, new ParallaxFeatHLSL ); + FEATUREMGR->registerFeature( MFT_NormalMap, new BumpFeatHLSL ); + FEATUREMGR->registerFeature( MFT_DetailMap, new DetailFeatHLSL ); + FEATUREMGR->registerFeature( MFT_CubeMap, new ReflectCubeFeatHLSL ); + FEATUREMGR->registerFeature( MFT_PixSpecular, new PixelSpecularHLSL ); + FEATUREMGR->registerFeature( MFT_IsTranslucent, new NamedFeatureHLSL( "Translucent" ) ); + FEATUREMGR->registerFeature( MFT_IsTranslucentZWrite, new NamedFeatureHLSL( "Translucent ZWrite" ) ); + FEATUREMGR->registerFeature( MFT_Visibility, new VisibilityFeatHLSL ); + FEATUREMGR->registerFeature( MFT_Fog, new FogFeatHLSL ); + FEATUREMGR->registerFeature( MFT_SpecularMap, new SpecularMapHLSL ); + FEATUREMGR->registerFeature( MFT_GlossMap, new NamedFeatureHLSL( "Gloss Map" ) ); + FEATUREMGR->registerFeature( MFT_LightbufferMRT, new NamedFeatureHLSL( "Lightbuffer MRT" ) ); + FEATUREMGR->registerFeature( MFT_RenderTarget1_Zero, new RenderTargetZeroHLSL( ShaderFeature::RenderTarget1 ) ); + + FEATUREMGR->registerFeature( MFT_DepthOut, new DepthOutHLSL ); + FEATUREMGR->registerFeature( MFT_EyeSpaceDepthOut, new EyeSpaceDepthOutHLSL() ); + + FEATUREMGR->registerFeature( MFT_HDROut, new HDROutHLSL ); + + FEATUREMGR->registerFeature( MFT_ParaboloidVertTransform, new ParaboloidVertTransformHLSL ); + FEATUREMGR->registerFeature( MFT_IsSinglePassParaboloid, new NamedFeatureHLSL( "Single Pass Paraboloid" ) ); + + FEATUREMGR->registerFeature( MFT_Foliage, new FoliageFeatureHLSL ); +} + +static ShaderGenHLSLInit p_HLSLInit; \ No newline at end of file diff --git a/shaderGen/conditionerFeature.cpp b/shaderGen/conditionerFeature.cpp new file mode 100644 index 0000000..2aca9f0 --- /dev/null +++ b/shaderGen/conditionerFeature.cpp @@ -0,0 +1,224 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/conditionerFeature.h" + +#include "shaderGen/shaderOp.h" +#include "shaderGen/featureMgr.h" +#include "gfx/gfxDevice.h" +#include "gfx/gfxStringEnumTranslate.h" +#include "core/stream/fileStream.h" +#include "materials/shaderData.h" +#include "core/util/safeDelete.h" + +const String ConditionerFeature::ConditionerIncludeFileName = "autogenConditioners.h"; + +bool ConditionerFeature::smDirtyConditioners = true; + +Vector ConditionerFeature::smConditioners; + + +ConditionerFeature::ConditionerFeature( const GFXFormat bufferFormat ) + : mBufferFormat(bufferFormat) +{ + dMemset( mMethodDependency, 0, sizeof( mMethodDependency ) ); + + smConditioners.push_back( this ); + smDirtyConditioners = true; +} + +ConditionerFeature::~ConditionerFeature() +{ + for( U32 i = 0; i < NumMethodTypes; i++ ) + SAFE_DELETE( mMethodDependency[i] ); + + smConditioners.remove( this ); + smDirtyConditioners = true; +} + +LangElement *ConditionerFeature::assignOutput( Var *unconditionedOutput, ShaderFeature::OutputTarget outputTarget /* = ShaderFeature::DefaultTarget*/ ) +{ + LangElement *assign; + MultiLine *meta = new MultiLine; + + meta->addStatement( new GenOp( avar( "\r\n\r\n // output buffer format: %s\r\n", GFXStringTextureFormat[getBufferFormat()] ) ) ); + + // condition the output + Var *conditionedOutput = _conditionOutput( unconditionedOutput, meta ); + + // search for color var + Var *color = (Var*) LangElement::find( getOutputTargetVarName(outputTarget) ); + + if ( !color ) + { + // create color var + color = new Var; + + if(GFX->getAdapterType() == OpenGL) + { + color->setName( getOutputTargetVarName(outputTarget) ); + color->setType( "vec4" ); + DecOp* colDecl = new DecOp(color); + + assign = new GenOp( "@ = vec4(@)", colDecl, conditionedOutput ); + } + else + { + color->setType( "fragout" ); + color->setName( getOutputTargetVarName(outputTarget) ); + color->setStructName( "OUT" ); + + assign = new GenOp( "@ = @", color, conditionedOutput ); + } + } + else + { + if (GFX->getAdapterType() == OpenGL) + assign = new GenOp( "@ = vec4(@)", color, conditionedOutput); + else + assign = new GenOp( "@ = @", color, conditionedOutput ); + } + + meta->addStatement( new GenOp( " @;\r\n", assign ) ); + + return meta; +} + +Var *ConditionerFeature::_conditionOutput( Var *unconditionedOutput, MultiLine *meta ) +{ + meta->addStatement( new GenOp( " // generic conditioner: no conditioning performed\r\n" ) ); + return unconditionedOutput; +} + +Var *ConditionerFeature::_unconditionInput( Var *conditionedInput, MultiLine *meta ) +{ + meta->addStatement( new GenOp( " // generic conditioner: no conditioning performed\r\n" ) ); + return conditionedInput; +} + +const String &ConditionerFeature::getShaderMethodName( MethodType methodType ) +{ + if ( mConditionMethodName.isEmpty() ) + { + const U32 hash = getName().getHashCaseInsensitive(); + mUnconditionMethodName = avar("autogen%s_%08x", "Uncondition", hash ); + mConditionMethodName = avar("autogen%s_%08x", "Condition", hash ); + } + + return methodType == UnconditionMethod ? mUnconditionMethodName : mConditionMethodName; +} + +ConditionerMethodDependency* ConditionerFeature::getConditionerMethodDependency( MethodType methodType ) +{ + if ( mMethodDependency[methodType] == NULL ) + mMethodDependency[methodType] = new ConditionerMethodDependency( this, methodType ); + + return mMethodDependency[methodType]; +} + +void ConditionerFeature::_print( Stream *stream ) +{ + _printMethod( ConditionMethod, getShaderMethodName( ConditionMethod ), *stream ); + LangElement::deleteElements(); + + _printMethod( UnconditionMethod, getShaderMethodName( UnconditionMethod ), *stream ); + LangElement::deleteElements(); +} + +void ConditionerFeature::_updateConditioners() +{ + smDirtyConditioners = false; + + String includePath = "shadergen:/" + ConditionerIncludeFileName; + + FileStream stream; + if ( !stream.open( includePath, Torque::FS::File::Write ) ) + return; + + for ( U32 i=0; i < smConditioners.size(); i++ ) + smConditioners[i]->_print( &stream ); +} + +Var *ConditionerFeature::printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ) +{ + Var *methodVar = new Var; + methodVar->setName(methodName); + DecOp *methodDecl = new DecOp(methodVar); + + const bool isCondition = (methodType == ConditionerFeature::ConditionMethod); + + Var *paramVar = new Var; + paramVar->setName(avar("%sconditioned%sput", isCondition ? "un" : "", isCondition ? "Out" : "In")); + DecOp *paramDecl = new DecOp(paramVar); + + if(GFX->getAdapterType() == OpenGL) + { + methodVar->setType("vec4"); + paramVar->setType("vec4"); + } + else + { + methodVar->setType("inline float4"); + paramVar->setType("in float4"); + } + + // Method header and opening bracket + meta->addStatement( new GenOp( "@(@)\r\n", methodDecl, paramDecl ) ); + meta->addStatement( new GenOp( "{\r\n" ) ); + + return paramVar; +} + +void ConditionerFeature::printMethodFooter( MethodType methodType, Var *retVar, Stream &stream, MultiLine *meta ) +{ + // Return and closing bracket + meta->addStatement( new GenOp( "\r\n return @;\r\n", retVar ) ); + meta->addStatement( new GenOp( "}\r\n" ) ); +} + +void ConditionerFeature::_printMethod( MethodType methodType, const String &methodName, Stream &stream ) +{ + MultiLine *meta = new MultiLine; + + printHeaderComment( methodType, methodName, stream, meta ); + Var *paramVar = printMethodHeader( methodType, methodName, stream, meta ); + Var *unconditionedInput = NULL; + if( methodType == UnconditionMethod ) + unconditionedInput = _unconditionInput( paramVar, meta ); + else + unconditionedInput = _conditionOutput( paramVar, meta ); + + printMethodFooter( methodType, unconditionedInput, stream, meta ); + printFooterComment( methodType, methodName, stream, meta ); + + meta->print(stream); +} + +void ConditionerFeature::printHeaderComment( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ) +{ + meta->addStatement( new GenOp( "//------------------------------------------------------------------------------\r\n" ) ); + meta->addStatement( new GenOp( avar( "// Autogenerated '%s' %s Method\r\n", getName().c_str(), + methodType == ConditionMethod ? "Condition" : "Uncondition" ) ) ); + meta->addStatement( new GenOp( "//------------------------------------------------------------------------------\r\n" ) ); +} + +void ConditionerFeature::printFooterComment( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ) +{ + meta->addStatement( new GenOp( "\r\n\r\n" ) ); +} + +void ConditionerMethodDependency::print( Stream &s ) const +{ + mConditioner->_printMethod(mMethodType, mConditioner->getShaderMethodName(mMethodType), s); +} + +void ConditionerMethodDependency::createMethodMacro( const String &methodName, Vector ¯os ) +{ + GFXShaderMacro conditionerMethodMacro; + conditionerMethodMacro.name = methodName; + conditionerMethodMacro.value = mConditioner->getShaderMethodName(mMethodType); + macros.push_back(conditionerMethodMacro); +} diff --git a/shaderGen/conditionerFeature.h b/shaderGen/conditionerFeature.h new file mode 100644 index 0000000..356c757 --- /dev/null +++ b/shaderGen/conditionerFeature.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _CONDITIONER_BASE_H_ +#define _CONDITIONER_BASE_H_ + +#ifndef _SHADERFEATURE_H_ +#include "shaderGen/shaderFeature.h" +#endif +#ifndef _SHADER_DEPENDENCY_H_ +#include "shaderGen/shaderDependency.h" +#endif + +class MultiLine; +class ConditionerMethodDependency; + + +class ConditionerFeature : public ShaderFeature +{ + friend class ConditionerMethodDependency; + + typedef ShaderFeature Parent; + +public: + + enum MethodType + { + ConditionMethod = 0, ///< Method used to take unconditioned data, and turn it into a format that can be written to the conditioned buffer + UnconditionMethod, ///< Method used to take conditioned data from a buffer, and extract what is stored + NumMethodTypes, + }; + + ConditionerFeature( const GFXFormat bufferFormat ); + virtual ~ConditionerFeature(); + + virtual Material::BlendOp getBlendOp() + { + return Material::None; + } + + virtual GFXFormat getBufferFormat() const { return mBufferFormat; } + virtual bool setBufferFormat(const GFXFormat bufferFormat) { bool ret = mBufferFormat == bufferFormat; mBufferFormat = bufferFormat; return ret; } + + // zero-out these methods + virtual Var* getVertTexCoord( const String &name ) { AssertFatal( false, "don't use this." ); return NULL; } + virtual LangElement *setupTexSpaceMat( Vector &componentList, Var **texSpaceMat ) { AssertFatal( false, "don't use this." ); return NULL; } + virtual LangElement *expandNormalMap( LangElement *sampleNormalOp, LangElement *normalDecl, LangElement *normalVar, const MaterialFeatureData &fd ) { AssertFatal( false, "don't use this." ); return NULL; } + virtual LangElement *assignColor( LangElement *elem, Material::BlendOp blend, LangElement *lerpElem = NULL, ShaderFeature::OutputTarget outputTarget = ShaderFeature::DefaultTarget ) { AssertFatal( false, "don't use this." ); return NULL; } + + // conditioned output + virtual LangElement *assignOutput( Var *unconditionedOutput, ShaderFeature::OutputTarget outputTarget = ShaderFeature::DefaultTarget ); + + // Get an HLSL/GLSL method name that will be available for the + // shader to read or write data to a conditioned buffer. + virtual const String &getShaderMethodName( MethodType methodType ); + + // Get the Method Dependency for ShaderGen, for this conditioner + virtual ConditionerMethodDependency *getConditionerMethodDependency( MethodType methodType ); + + static const String ConditionerIncludeFileName; + + static void updateConditioners() { if ( smDirtyConditioners ) _updateConditioners(); } + +protected: + + static void _updateConditioners(); + + static bool smDirtyConditioners; + + ConditionerMethodDependency *mMethodDependency[NumMethodTypes]; + + static Vector smConditioners; + + GFXFormat mBufferFormat; + + String mUnconditionMethodName; + + String mConditionMethodName; + + String mShaderIncludePath; + + void _print( Stream *stream ); + + virtual Var *_conditionOutput( Var *unconditionedOutput, MultiLine *meta ); + virtual Var *_unconditionInput( Var *conditionedInput, MultiLine *meta ); + + // Print method header, return primary parameter + virtual Var *printMethodHeader( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ); + virtual void printMethodFooter( MethodType methodType, Var *retVar, Stream &stream, MultiLine *meta ); + + // Print comments + virtual void printHeaderComment( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ); + virtual void printFooterComment( MethodType methodType, const String &methodName, Stream &stream, MultiLine *meta ); + + // print a HLSL/GLSL method to a stream, which can be used by a custom shader + // to read conditioned data + virtual void _printMethod( MethodType methodType, const String &methodName, Stream &stream ); +}; + +//------------------------------------------------------------------------------ + +// ShaderDependancy that allows shadergen features to add a dependency on a conditioner method +class ConditionerMethodDependency : public ShaderDependency +{ +protected: + ConditionerFeature *mConditioner; + ConditionerFeature::MethodType mMethodType; + +public: + ConditionerMethodDependency( ConditionerFeature *conditioner, const ConditionerFeature::MethodType methodType ) : + mConditioner(conditioner), mMethodType(methodType) {} + + virtual void print( Stream &s ) const; + + // Auto insert information into a macro + virtual void createMethodMacro( const String &methodName, Vector ¯os ); +}; + +#endif // _CONDITIONER_BASE_H_ diff --git a/shaderGen/featureMgr.cpp b/shaderGen/featureMgr.cpp new file mode 100644 index 0000000..99c6b8a --- /dev/null +++ b/shaderGen/featureMgr.cpp @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/featureMgr.h" + +#include "shaderGen/featureType.h" +#include "shaderGen/shaderFeature.h" +#include "core/util/safeDelete.h" + + +FeatureMgr::FeatureMgr() + : mNeedsSort( false ) +{ + VECTOR_SET_ASSOCIATION( mFeatures ); +} + +FeatureMgr::~FeatureMgr() +{ + unregisterAll(); +} + +void FeatureMgr::unregisterAll() +{ + FeatureInfoVector::iterator iter = mFeatures.begin(); + for ( ; iter != mFeatures.end(); iter++ ) + { + if ( iter->feature ) + delete iter->feature; + } + + mFeatures.clear(); + mNeedsSort = false; +} + +const FeatureInfo& FeatureMgr::getAt( U32 index ) +{ + if ( mNeedsSort ) + { + mFeatures.sort( _featureInfoCompare ); + mNeedsSort = false; + } + + AssertFatal( index < mFeatures.size(), "FeatureMgr::getAt() - Index out of range!" ); + + return mFeatures[index]; +} + +ShaderFeature* FeatureMgr::getByType( const FeatureType &type ) +{ + FeatureInfoVector::iterator iter = mFeatures.begin(); + for ( ; iter != mFeatures.end(); iter++ ) + { + if ( *iter->type == type ) + return iter->feature; + } + + return NULL; +} + +void FeatureMgr::registerFeature( const FeatureType &type, + ShaderFeature *feature ) +{ + // Remove any existing feature first. + unregisterFeature( type ); + + // Now add the new feature. + mFeatures.increment(); + mFeatures.last().type = &type; + mFeatures.last().feature = feature; + + // Make sure we resort the features. + mNeedsSort = true; +} + +S32 QSORT_CALLBACK FeatureMgr::_featureInfoCompare( const FeatureInfo* a, const FeatureInfo* b ) +{ + const FeatureType *typeA = a->type; + const FeatureType *typeB = b->type; + + if ( typeA->getGroup() < typeB->getGroup() ) + return -1; + else if ( typeA->getGroup() > typeB->getGroup() ) + return 1; + else if ( typeA->getOrder() < typeB->getOrder() ) + return -1; + else if ( typeA->getOrder() > typeB->getOrder() ) + return 1; + else + return 0; +} + +void FeatureMgr::unregisterFeature( const FeatureType &type ) +{ + FeatureInfoVector::iterator iter = mFeatures.begin(); + for ( ; iter != mFeatures.end(); iter++ ) + { + if ( *iter->type != type ) + continue; + + delete iter->feature; + mFeatures.erase( iter ); + return; + } +} diff --git a/shaderGen/featureMgr.h b/shaderGen/featureMgr.h new file mode 100644 index 0000000..e7bb3fa --- /dev/null +++ b/shaderGen/featureMgr.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _FEATUREMGR_H_ +#define _FEATUREMGR_H_ + +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class FeatureType; +class ShaderFeature; + +/// Used by the feature manager. +struct FeatureInfo +{ + const FeatureType *type; + ShaderFeature *feature; +}; + + +/// +class FeatureMgr +{ +protected: + + bool mNeedsSort; + + typedef Vector FeatureInfoVector; + + FeatureInfoVector mFeatures; + + static S32 QSORT_CALLBACK _featureInfoCompare( const FeatureInfo *a, const FeatureInfo *b ); + +public: + + FeatureMgr(); + ~FeatureMgr(); + + /// Returns the count of registered features. + U32 getFeatureCount() const { return mFeatures.size(); } + + /// Returns the feature info at the index. + const FeatureInfo& getAt( U32 index ); + + /// + ShaderFeature* getByType( const FeatureType &type ); + + // Allows other systems to add features. index is + // the enum in GFXMaterialFeatureData. + void registerFeature( const FeatureType &type, + ShaderFeature *feature ); + + // Unregister a feature. + void unregisterFeature( const FeatureType &type ); + + + /// Removes all features. + void unregisterAll(); +}; + +// Helper for accessing the feature manager singleton. +#define FEATUREMGR Singleton::instance() + +#endif // FEATUREMGR \ No newline at end of file diff --git a/shaderGen/featureSet.cpp b/shaderGen/featureSet.cpp new file mode 100644 index 0000000..a57d9f0 --- /dev/null +++ b/shaderGen/featureSet.cpp @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/featureSet.h" + +#include "shaderGen/featureType.h" +#include "platform/profiler.h" +#include "core/util/hashFunction.h" + + +S32 QSORT_CALLBACK FeatureSet::_typeCmp( const FeatureInfo* a, const FeatureInfo* b ) +{ + if ( a->type->getGroup() < b->type->getGroup() ) + return -1; + else if ( a->type->getGroup() > b->type->getGroup() ) + return 1; + else if ( a->index < b->index ) + return -1; + else if ( a->index > b->index ) + return 1; + else if ( a->type->getOrder() < b->type->getOrder() ) + return -1; + else if ( a->type->getOrder() > b->type->getOrder() ) + return 1; + else + return 0; +} + +void FeatureSet::_rebuildDesc() +{ + PROFILE_SCOPE( FeatureSet_RebuildDesc ); + + // First get the features in the proper order. + mFeatures.sort( _typeCmp ); + + mDescription.clear(); + + for ( U32 i=0; i < mFeatures.size(); i++ ) + mDescription += String::ToString( "%s,%d\n", + mFeatures[i].type->getName().c_str(), + mFeatures[i].index ); + + // Make sure the hash is created here once + // so that it can be used in comparisions later. + mDescription.getHashCaseInsensitive(); +} + +const String& FeatureSet::getDescription() const +{ + if ( mDescription.isEmpty() ) + const_cast(this)->_rebuildDesc(); + + return mDescription; +} + +const FeatureType& FeatureSet::getAt( U32 index, S32 *outIndex ) const +{ + // We want to make sure we access the features in the + // correct order. By updating the description we ensure + // its properly sorted. + if ( mDescription.isEmpty() ) + const_cast(this)->_rebuildDesc(); + + if ( outIndex ) + *outIndex = mFeatures[index].index; + + return *mFeatures[index].type; +} + +void FeatureSet::clear() +{ + mDescription.clear(); + mFeatures.clear(); +} + +FeatureSet& FeatureSet::operator =( const FeatureSet &h ) +{ + clear(); + merge( h ); + return *this; +} + +bool FeatureSet::hasFeature( const FeatureType &type, S32 index ) const +{ + for ( U32 i = 0; i < mFeatures.size(); i++) + { + if ( mFeatures[i].type == &type && + ( index < 0 || mFeatures[i].index == index ) ) + return true; + } + + return false; +} + +void FeatureSet::setFeature( const FeatureType &type, bool set, S32 index ) +{ + for ( U32 i=0; i < mFeatures.size(); i++ ) + { + const FeatureInfo &info = mFeatures[i]; + if ( info.type == &type && info.index == index ) + { + if ( set ) + return; + else + { + mFeatures.erase_fast( i ); + mDescription.clear(); + return; + } + } + } + + if ( !set ) + return; + + FeatureInfo info; + info.type = &type; + info.index = index; + mFeatures.push_back( info ); + + mDescription.clear(); +} + +void FeatureSet::addFeature( const FeatureType &type, S32 index ) +{ + for ( U32 i=0; i < mFeatures.size(); i++ ) + { + const FeatureInfo &info = mFeatures[i]; + if ( info.type == &type && + info.index == index ) + return; + } + + FeatureInfo info; + info.type = &type; + info.index = index; + mFeatures.push_back( info ); + + mDescription.clear(); +} + +void FeatureSet::removeFeature( const FeatureType &type ) +{ + for ( U32 i=0; i < mFeatures.size(); i++ ) + { + const FeatureInfo &info = mFeatures[i]; + if ( info.type == &type ) + { + mFeatures.erase_fast( i ); + mDescription.clear(); + return; + } + } +} + +U32 FeatureSet::getNextFeatureIndex( const FeatureType &type, S32 index ) const +{ + for ( U32 i=0; i < mFeatures.size(); i++ ) + { + const FeatureInfo &info = mFeatures[i]; + if ( info.type == &type && info.index > index ) + return i; + } + + return -1; +} + +void FeatureSet::filter( const FeatureSet &features ) +{ + PROFILE_SCOPE( FeatureSet_Filter ); + + for ( U32 i=0; i < mFeatures.size(); ) + { + if ( !features.hasFeature( *mFeatures[i].type ) ) + mFeatures.erase_fast( i ); + else + i++; + } + + mDescription.clear(); +} + +void FeatureSet::exclude( const FeatureSet &features ) +{ + PROFILE_SCOPE( FeatureSet_Exclude ); + + for ( U32 i=0; i < features.mFeatures.size(); i++ ) + removeFeature( *features.mFeatures[i].type ); + + mDescription.clear(); +} + +void FeatureSet::merge( const FeatureSet &features ) +{ + PROFILE_SCOPE( FeatureSet_Merge ); + + if ( mFeatures.empty() ) + { + mFeatures.merge( features.mFeatures ); + mDescription = features.mDescription; + return; + } + + for ( U32 i=0; i < features.mFeatures.size(); i++ ) + addFeature( *features.mFeatures[i].type, + features.mFeatures[i].index ); +} + diff --git a/shaderGen/featureSet.h b/shaderGen/featureSet.h new file mode 100644 index 0000000..f6712d1 --- /dev/null +++ b/shaderGen/featureSet.h @@ -0,0 +1,101 @@ + +#ifndef _FEATURESET_H_ +#define _FEATURESET_H_ + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +class FeatureType; + + +// +class FeatureSet +{ +protected: + + struct FeatureInfo + { + const FeatureType* type; + S32 index; + }; + + /// The list of featurs. + Vector mFeatures; + + /// A string representation of all the + /// features used for comparisons. + String mDescription; + + /// + static S32 _typeCmp( const FeatureInfo* a, const FeatureInfo *b ); + + /// + void _rebuildDesc(); + + // Protected and unimplemented to force you + // to use the functions and not operators. + bool operator == ( const FeatureSet &h ) const; + bool operator != ( const FeatureSet &h ) const; + +public: + + FeatureSet() + { + } + + FeatureSet( const FeatureSet &h ) + : mFeatures( h.mFeatures ), + mDescription( h.mDescription ) + { + } + + FeatureSet& operator =( const FeatureSet &h ); + + bool operator []( const FeatureType &type ) const { return hasFeature( type ); } + + /// + bool isNotEmpty() const { return !mFeatures.empty(); } + + /// + const String& getDescription() const; + + /// Returns the feature count. + U32 getCount() const { return mFeatures.size(); } + + /// Returns the feature at the index and optionally + /// the feature index when it was added. + const FeatureType& getAt( U32 index, S32 *outIndex = NULL ) const; + + /// Returns true if this handle has this feature. + bool hasFeature( const FeatureType &type, S32 index = -1 ) const; + + /// + void setFeature( const FeatureType &type, bool set, S32 index = -1 ); + + /// + void addFeature( const FeatureType &type, S32 index = -1 ); + + /// + void removeFeature( const FeatureType &type ); + + /// + U32 getNextFeatureIndex( const FeatureType &type, S32 index ) const; + + /// Removes features that are not in the input set. + void filter( const FeatureSet &features ); + + /// Removes features that are in the input set. + void exclude( const FeatureSet &features ); + + /// + void merge( const FeatureSet &features ); + + /// Clears all features. + void clear(); +}; + +#endif // _FEATURESET_H_ diff --git a/shaderGen/featureType.cpp b/shaderGen/featureType.cpp new file mode 100644 index 0000000..827d385 --- /dev/null +++ b/shaderGen/featureType.cpp @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/featureType.h" + +#include "shaderGen/featureSet.h" + +FeatureTypeVector& FeatureType::_getTypes() +{ + // We create it as a method static so that + // its available to other statics regardless + // of initialization order. + static FeatureTypeVector theTypes; + return theTypes; +} + +void FeatureType::addDefaultTypes( FeatureSet *outFeatures ) +{ + const FeatureTypeVector &types = _getTypes(); + for ( U32 i=0; i < types.size(); i++ ) + { + if ( types[i]->isDefault() ) + outFeatures->addFeature( *types[i] ); + } +} + +FeatureType::FeatureType( const char *name, U32 group, F32 order, bool isDefault ) + : mName( name ), + mGroup( group ), + mOrder( order ), + mIsDefault( isDefault ) +{ + FeatureTypeVector &types = _getTypes(); + + #ifdef TORQUE_DEBUG + for ( U32 i=0; i < types.size(); i++ ) + AssertFatal( !mName.equal( types[i]->getName() ), "FeatureType - This feature already exists!" ); + #endif + + mId = types.size(); + types.push_back( this ); +} diff --git a/shaderGen/featureType.h b/shaderGen/featureType.h new file mode 100644 index 0000000..3f9f9fb --- /dev/null +++ b/shaderGen/featureType.h @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _FEATURETYPE_H_ +#define _FEATURETYPE_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +class FeatureType; +class FeatureSet; + + +/// A vector of feature types. +typedef Vector FeatureTypeVector; + + +/// +class FeatureType +{ +protected: + + /// Returns the map of all the types. + static FeatureTypeVector& _getTypes(); + + /// The feature type name. + String mName; + + /// A unique feature id value. + U32 mId; + + /// The group is used to orginize the types. + U32 mGroup; + + /// The sort order of this feature type + /// within its group. + F32 mOrder; + + /// + bool mIsDefault; + + /// + FeatureType( const FeatureType &type ); + +public: + + /// Adds all the default features types to the set. + static void addDefaultTypes( FeatureSet *outFeatures ); + + /// You should not use this constructor directly. + /// @see DeclareFeatureType + /// @see ImplementFeatureType + FeatureType( const char *type, + U32 group = -1, + F32 order = -1.0f, + bool isDefault = true ); + + bool operator !=( const FeatureType &type ) const { return this != &type; } + + bool operator ==( const FeatureType &type ) const { return this == &type; } + + const String& getName() const { return mName; } + + U32 getId() const { return mId; } + + U32 getGroup() const { return mGroup; } + + F32 getOrder() const { return mOrder; } + + bool isDefault() const { return mIsDefault; } + +}; + + +/// +#define DeclareFeatureType( name ) \ + extern const FeatureType name + + +/// +#define ImplementFeatureType( name, group, order, isDefault ) \ + const FeatureType name( #name, group, order, isDefault ) + +/* +#define ImplementFeatureType( name, group, order ) \ + const FeatureType name( #name, group, order ); + +#define ImplementFeatureType( name, group ) \ + const FeatureType name( #name, group ); + +#define ImplementFeatureType( name ) \ + const FeatureType name( #name ); +*/ + +#endif // _FEATURETYPE_H_ \ No newline at end of file diff --git a/shaderGen/langElement.cpp b/shaderGen/langElement.cpp new file mode 100644 index 0000000..7e62747 --- /dev/null +++ b/shaderGen/langElement.cpp @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include "core/util/str.h" + +#include "langElement.h" + +//************************************************************************** +// Language element +//************************************************************************** +Vector LangElement::elementList( __FILE__, __LINE__ ); + +//-------------------------------------------------------------------------- +// Constructor +//-------------------------------------------------------------------------- +LangElement::LangElement() +{ + elementList.push_back( this ); + + static U32 tempNum = 0; + dSprintf( (char*)name, sizeof(name), "tempName%d", tempNum++ ); +} + +//-------------------------------------------------------------------------- +// Find element of specified name +//-------------------------------------------------------------------------- +LangElement * LangElement::find( const char *name ) +{ + for( U32 i=0; iname, name ) ) + { + return elementList[i]; + } + } + + return NULL; +} + +//-------------------------------------------------------------------------- +// Delete existing elements +//-------------------------------------------------------------------------- +void LangElement::deleteElements() +{ + for( U32 i=0; iprint( stream ); + } +} \ No newline at end of file diff --git a/shaderGen/langElement.h b/shaderGen/langElement.h new file mode 100644 index 0000000..45fc48b --- /dev/null +++ b/shaderGen/langElement.h @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _LANG_ELEMENT_H_ +#define _LANG_ELEMENT_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _STREAM_H_ +#include "core/stream/stream.h" +#endif + +#define WRITESTR( a ){ stream.write( dStrlen(a), a ); } + + +//************************************************************************** +/*! + The LangElement class is the base building block for procedurally + generated shader code. LangElement and its subclasses are strung + together using the static Vector 'elementList'. + When a shader needs to be written to disk, the elementList is + traversed and print() is called on each LangElement and the shader + is output. elementList is cleared after each shader is printed out. +*/ +//************************************************************************** + + +//************************************************************************** +// Language element +//************************************************************************** +struct LangElement +{ + static Vector elementList; + static LangElement * find( const char *name ); + static void deleteElements(); + + U8 name[32]; + + LangElement(); + virtual ~LangElement() {}; + virtual void print( Stream &stream ){}; + void setName(const char *newName ); + +}; + +enum ConstantSortPosition +{ + /// Default / unset + cspUninit = 0, + /// Updated before every draw primitive call. + cspPrimitive, + /// Potentially updated every draw primitive call, but not necessarily (lights for example) + cspPotentialPrimitive, + /// Updated one per pass + cspPass, + /// Count var, do not use + csp_Count +}; + +//---------------------------------------------------------------------------- +/*! + Var - Variable - used to specify a variable to be used in a shader. + Var stores information such that when it is printed out, its context + can be identified and the proper information will automatically be printed. + For instance, if a variable is created with 'uniform' set to true, when the + shader function definition is printed, it will automatically add that + variable to the incoming parameters of the shader. There are several + similar cases such as when a new variable is declared within a shader. + + example: + + @code + + Var *modelview = new Var; + modelview->setType( "float4x4" ); + modelview->setName( "modelview" ); + modelview->uniform = true; + modelview->constSortPos = cspPass; + + @endcode + + it prints out in the shader declaration as: + + @code + ConnectData main( VertData IN, + uniform float4x4 modelview : register(C0) ) + @endcode + +*/ +//---------------------------------------------------------------------------- +struct Var : public LangElement +{ + U8 type[32]; + U8 structName[32]; + char connectName[32]; + ConstantSortPosition constSortPos; // used to calculate constant number + U32 constNum; + U32 texCoordNum; + bool uniform; // argument passed in through constant registers + bool vertData; // argument coming from vertex data + bool connector; // variable that will be passed to pixel shader + bool sampler; // texture + bool mapsToSampler; // for ps 1.x shaders - texcoords must be mapped to same sampler stage + U32 arraySize; // 1 = no array, > 1 array of "type" + + static U32 texUnitCount; + static U32 getTexUnitNum(U32 numElements = 1); + static void reset(); + + // Default + Var(); + Var( const char *name, const char *type ); + + void setStructName(const char *newName ); + void setConnectName(const char *newName ); + void setType(const char *newType ); + + virtual void print( Stream &stream ); + + // Construct a uniform / shader const var + void setUniform(const String& constType, const String& constName, ConstantSortPosition sortPos); +}; + +//---------------------------------------------------------------------------- +/*! + MultiLine - Multi Line Statement - This class simply ties multiple + + example: + + @code + + MultiLine *meta = new MultiLine; + meta->addStatement( new GenOp( "foo = true;\r\n" ) ); + meta->addStatement( new GenOp( "bar = false;\r\n ) ); + + @endcode + + it prints out in the shader declaration as: + + @code + foo = true; + bar = false; + @endcode + +*/ +//---------------------------------------------------------------------------- +class MultiLine : public LangElement +{ + Vector mStatementList; + +public: + MultiLine() + { + VECTOR_SET_ASSOCIATION( mStatementList ); + } + + void addStatement( LangElement *elem ); + virtual void print( Stream &stream ); +}; + + + +#endif // _LANG_ELEMENT_H_ diff --git a/shaderGen/shaderComp.cpp b/shaderGen/shaderComp.cpp new file mode 100644 index 0000000..1a7966d --- /dev/null +++ b/shaderGen/shaderComp.cpp @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "shaderComp.h" + +//************************************************************************** +// Connector Struct Component +//************************************************************************** + +//---------------------------------------------------------------------------- +// Constructor +//---------------------------------------------------------------------------- +ShaderConnector::ShaderConnector() +{ + mCurTexElem = 0; +} + +//---------------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------------- +ShaderConnector::~ShaderConnector() +{ + // Elements freed by LangElement::freeElements() +} + diff --git a/shaderGen/shaderComp.h b/shaderGen/shaderComp.h new file mode 100644 index 0000000..2ee8574 --- /dev/null +++ b/shaderGen/shaderComp.h @@ -0,0 +1,82 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERCOMP_H_ +#define _SHADERCOMP_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif + +#ifndef _MISCSHDRDAT_H_ +#include "materials/miscShdrDat.h" +#endif + +class Stream; +struct Var; + +//************************************************************************** +// Shader Component - these objects are the main logical breakdown of a +// high level shader. They represent the various data structures +// and the main() procedure necessary to create a shader. +//************************************************************************** +class ShaderComponent +{ +public: + virtual ~ShaderComponent() {} + + virtual void print( Stream &stream ){}; +}; + + +//************************************************************************** +// Connector Struct Component - used for incoming Vertex struct and also the +// "connection" struct shared by the vertex and pixel shader +//************************************************************************** +class ShaderConnector : public ShaderComponent +{ +protected: + enum Consts + { + NUM_TEX_REGS = 8, + }; + + enum Elements + { + POSITION = 0, + NORMAL, + COLOR, + NUM_BASIC_ELEMS + }; + + Vector mElementList; + + U32 mCurTexElem; + U8 mName[32]; + +public: + + ShaderConnector(); + virtual ~ShaderConnector(); + + /// + virtual Var* getElement( RegisterType type, + U32 numElements = 1, + U32 numRegisters = -1 ) = 0; + + virtual void setName( char *newName ) = 0; + virtual void reset() = 0; + virtual void sortVars() = 0; + + virtual void print( Stream &stream ) = 0; +}; + +/// This is to provide common functionalty needed by vertex and pixel main defs +class ParamsDef : public ShaderComponent +{ +protected: + virtual void assignConstantNumbers() {} +}; + +#endif // _SHADERCOMP_H_ diff --git a/shaderGen/shaderDependency.cpp b/shaderGen/shaderDependency.cpp new file mode 100644 index 0000000..5cc3e7c --- /dev/null +++ b/shaderGen/shaderDependency.cpp @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/shaderDependency.h" + +#include "core/stream/fileStream.h" +#include "core/frameAllocator.h" + + +ShaderIncludeDependency::ShaderIncludeDependency( const Torque::Path &pathToInclude ) + : mIncludePath( pathToInclude ) +{ +} + +bool ShaderIncludeDependency::operator==( const ShaderDependency &cmpTo ) const +{ + return this == &cmpTo || + ( dynamic_cast( &cmpTo ) && + static_cast( &cmpTo )->mIncludePath == mIncludePath ); +} + +void ShaderIncludeDependency::print( Stream &s ) const +{ + // Print the include... all shaders support #includes. + String include = String::ToString( "#include \"%s\"\r\n", mIncludePath.getFullPath().c_str() ); + s.write( include.length(), include.c_str() ); +} diff --git a/shaderGen/shaderDependency.h b/shaderGen/shaderDependency.h new file mode 100644 index 0000000..de141af --- /dev/null +++ b/shaderGen/shaderDependency.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _SHADER_DEPENDENCY_H_ +#define _SHADER_DEPENDENCY_H_ + +#ifndef _PATH_H_ +#include "core/util/path.h" +#endif + + +class Stream; + + +/// The base class for shader dependencies +class ShaderDependency +{ +public: + virtual ~ShaderDependency() {} + + /// Compare this dependency to another one. + virtual bool operator==( const ShaderDependency &cmpTo ) const + { + return this == &cmpTo; + } + + /// Print the dependency into the header of a shader. + virtual void print( Stream &s ) const = 0; +}; + + +/// A shader dependency for adding #include's into shadergen shaders. +class ShaderIncludeDependency : public ShaderDependency +{ +protected: + + Torque::Path mIncludePath; + +public: + + ShaderIncludeDependency( const Torque::Path &pathToInclude ); + + virtual bool operator==( const ShaderDependency &cmpTo ) const; + virtual void print( Stream &s ) const; +}; + +#endif // _SHADER_DEPENDENCY_H_ \ No newline at end of file diff --git a/shaderGen/shaderFeature.cpp b/shaderGen/shaderFeature.cpp new file mode 100644 index 0000000..7f7b1aa --- /dev/null +++ b/shaderGen/shaderFeature.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/shaderFeature.h" + +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" + + +void ShaderFeature::addDependency( const ShaderDependency *dependsOn ) +{ + for ( U32 i = 0; i < mDependencies.size(); i++ ) + { + if ( *mDependencies[i] == *dependsOn ) + return; + } + + mDependencies.push_back( dependsOn ); +} + +ShaderFeature::Resources ShaderFeature::getResources( const MaterialFeatureData &fd ) +{ + Resources temp; + return temp; +} + +const char* ShaderFeature::getOutputTargetVarName( OutputTarget target ) const +{ + const char* targName = "col"; + if ( target != DefaultTarget ) + { + targName = "col1"; + AssertFatal(target == RenderTarget1, "yeah Pat is lame and didn't want to do bit math stuff, TODO"); + } + + return targName; +} + +Var* ShaderFeature::findOrCreateLocal( const char *name, + const char *type, + MultiLine *multi ) +{ + Var *outVar = (Var*)LangElement::find( name ); + if ( !outVar ) + { + outVar = new Var; + outVar->setType( type ); + outVar->setName( name ); + multi->addStatement( new GenOp( " @;\r\n", new DecOp( outVar ) ) ); + } + + return outVar; +} \ No newline at end of file diff --git a/shaderGen/shaderFeature.h b/shaderGen/shaderFeature.h new file mode 100644 index 0000000..ad908a9 --- /dev/null +++ b/shaderGen/shaderFeature.h @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERFEATURE_H_ +#define _SHADERFEATURE_H_ + +#ifndef _MATERIALDEFINITION_H_ +#include "materials/materialDefinition.h" +#endif +#ifndef _SHADERCOMP_H_ +#include "shaderGen/shaderComp.h" +#endif +#ifndef _SHADER_DEPENDENCY_H_ +#include "shaderGen/shaderDependency.h" +#endif + +class MultiLine; +struct LangElement; +struct MaterialFeatureData; +class GFXShaderConstBuffer; +struct RenderPassData; +struct SceneGraphData; +class SceneState; +class GFXShader; + + +/// +class ShaderFeatureConstHandles +{ +public: + + virtual void setConsts( SceneState *state, + const SceneGraphData &sgData, + GFXShaderConstBuffer *buffer ) = 0; +}; + +//************************************************************************** +/*! + The ShaderFeature class is the base class for every procedurally generated + feature. Each feature the engine recognizes is part of the MaterialFeatureType + enum. That structure is used to indicate which features are present in a shader + to be generated. This is useful as many ShaderFeatures will output different + code depending on what other features are going to be in the shader. + + Shaders are generated using the ShaderFeature interface, so all of the + descendants interact pretty much the same way. + +*/ +//************************************************************************** + + +//************************************************************************** +// Shader Feature +//************************************************************************** +class ShaderFeature +{ +public: + + // Bitfield which allows a shader feature to say which render targets it outputs + // data to (could be more than one). + enum OutputTarget + { + DefaultTarget = 1 << 0, + RenderTarget1 = 1 << 1, + RenderTarget2 = 1 << 2, + RenderTarget3 = 1 << 3, + }; + +protected: + + LangElement *output; + + /// The list of unique shader dependencies. + Vector mDependencies; + + /// + S32 mProcessIndex; + +public: + + //************************************************************************** + /*! + The Resources structure is used by ShaderFeature to indicate how many + hardware "resources" it needs. Resources are things such as how + many textures it uses and how many texture registers it needs to pass + information from the vertex to the pixel shader. + + The Resources data can change depending what hardware is available. For + instance, pixel 1.x level hardware may need more texture registers than + pixel 2.0+ hardware because each texture register can only be used with + its respective texture sampler. + + The data in Resources is used to determine how many features can be + squeezed into a singe shader. If a feature requires too many resources + to fit into the current shader, it will be put into another pass. + */ + //************************************************************************** + struct Resources + { + U32 numTex; + U32 numTexReg; + + Resources() + { + dMemset( this, 0, sizeof( Resources ) ); + } + }; + + + //----------------------------------------------------------------------- + // Base functions + //----------------------------------------------------------------------- + + ShaderFeature() + : output( NULL ), + mProcessIndex( 0 ) + { + } + + virtual ~ShaderFeature() {} + + /// returns output from a processed vertex or pixel shader + LangElement* getOutput() const { return output; } + + /// + void setProcessIndex( S32 index ) { mProcessIndex = index; } + + /// + U32 getProcessIndex() const { return mProcessIndex; } + + //----------------------------------------------------------------------- + // Virtual Functions + //----------------------------------------------------------------------- + + /// Get the incoming base texture coords - useful for bumpmap and detail maps + virtual Var* getVertTexCoord( const String &name ) = 0; + + /// Set up a texture space matrix - to pass into pixel shader + virtual LangElement * setupTexSpaceMat( Vector &componentList, + Var **texSpaceMat ) = 0; + + /// Expand and assign a normal map. This takes care of compressed normal maps as well. + virtual LangElement * expandNormalMap( LangElement *sampleNormalOp, + LangElement *normalDecl, LangElement *normalVar, const MaterialFeatureData &fd ) = 0; + + /// Helper function for applying the color to shader output. + /// + /// @param elem The rbg or rgba color to assign. + /// + /// @param blend The type of blending to perform. + /// + /// @param lerpElem The optional lerp parameter when doing a LerpAlpha blend, + /// if not set then the elem is used. + /// + virtual LangElement* assignColor( LangElement *elem, + Material::BlendOp blend, + LangElement *lerpElem = NULL, + ShaderFeature::OutputTarget outputTarget = ShaderFeature::DefaultTarget ) = 0; + + + //----------------------------------------------------------------------- + /*! + Process vertex shader - This function is used by each feature to + generate a list of LangElements that can be traversed and "printed" + to generate the actual shader code. The 'output' member is the head + of that list. + + The componentList is used mostly for access to the "Connector" + structure which is used to pass data from the vertex to the pixel + shader. + + The MaterialFeatureData parameter is used to determine what other + features are present for the shader being generated. + */ + //----------------------------------------------------------------------- + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ) + { output = NULL; } + + //----------------------------------------------------------------------- + /*! + Process pixel shader - This function is used by each feature to + generate a list of LangElements that can be traversed and "printed" + to generate the actual shader code. The 'output' member is the head + of that list. + + The componentList is used mostly for access to the "Connector" + structure which is used to pass data from the vertex to the pixel + shader. + + The MaterialFeatureData parameter is used to determine what other + features are present for the shader being generated. + */ + //----------------------------------------------------------------------- + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ) + { output = NULL; } + + /// Allows the feature to add macros to pixel shader compiles. + virtual void processPixMacros( Vector ¯os, const MaterialFeatureData &fd ) {}; + + /// Allows the feature to add macros to vertex shader compiles. + virtual void processVertMacros( Vector ¯os, const MaterialFeatureData &fd ) {}; + + /// Identifies what type of blending a feature uses. This is used to + /// group features with the same blend operation together in a multipass + /// situation. + virtual Material::BlendOp getBlendOp() { return Material::Add; } + + /// Returns the resource requirements of this feature based on what + /// other features are present. The "resources" are things such as + /// texture units, and texture registers of which there can be + /// very limited numbers. The resources can vary depending on hardware + /// and what other features are present. + virtual Resources getResources( const MaterialFeatureData &fd ); + + /// Fills texture related info in RenderPassData for this feature. It + /// takes into account the current pass (passData) as well as what other + /// data is available to the material stage (stageDat). + /// + /// For instance, ReflectCubeFeatHLSL would like to modulate its output + /// by the alpha channel of another texture. If the current pass does + /// not contain a diffuse or bump texture, but the Material does, then + /// this function allows it to use one of those textures in the current + /// pass. + virtual void setTexData( Material::StageData &stageDat, + const MaterialFeatureData &fd, + RenderPassData &passData, + U32 &texIndex ){}; + + /// Returns the name of this feature. + virtual String getName() = 0; + + /// Adds a dependency to this shader feature. + virtual void addDependency( const ShaderDependency *depends ); + + /// Gets the dependency list for this shader feature. + virtual const Vector &getDependencies() const { return mDependencies; } + + /// Returns the output variable name for this feature if it applies. + virtual const char* getOutputVarName() const { return NULL; } + + /// Gets the render target this shader feature is assigning data to. + virtual U32 getOutputTargets( const MaterialFeatureData &fd ) const { return DefaultTarget; } + + /// Returns the name of output targer var. + const char* getOutputTargetVarName( OutputTarget target = DefaultTarget ) const; + + // Called from ProcessedShaderMaterial::determineFeatures to enable/disable features. + virtual void determineFeature( Material *material, + const GFXVertexFormat *vertexFormat, + U32 stageNum, + const FeatureType &type, + const FeatureSet &features, + MaterialFeatureData *outFeatureData ) { } + + // + virtual ShaderFeatureConstHandles* createConstHandles( GFXShader *shader ) { return NULL; } + + /// Called after processing the vertex and processing the pixel + /// to cleanup any temporary structures stored in the feature. + virtual void reset() { output = NULL; mProcessIndex = 0; } + + /// A simpler helper function which either finds + /// the existing local var or creates one. + static Var* findOrCreateLocal( const char *name, + const char *type, + MultiLine *multi ); +}; + +#endif // _SHADERFEATURE_H_ diff --git a/shaderGen/shaderGen.cpp b/shaderGen/shaderGen.cpp new file mode 100644 index 0000000..eef3bb9 --- /dev/null +++ b/shaderGen/shaderGen.cpp @@ -0,0 +1,472 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/shaderGen.h" + +#include "shaderGen/conditionerFeature.h" +#include "core/stream/fileStream.h" +#include "shaderGen/featureMgr.h" +#include "shaderGen/shaderOp.h" +#include "gfx/gfxDevice.h" +#include "core/memVolume.h" + + +ShaderGen::ShaderGen() +{ + mInit = false; + mRegisteredWithGFX = true; + GFXDevice::getDeviceEventSignal().notify(this, &ShaderGen::_handleGFXEvent); + mOutput = NULL; +} + +ShaderGen::~ShaderGen() +{ + if (mRegisteredWithGFX) + GFXDevice::getDeviceEventSignal().remove(this, &ShaderGen::_handleGFXEvent); + _uninit(); +} + +void ShaderGen::registerInitDelegate(GFXAdapterType adapterType, ShaderGenInitDelegate& initDelegate) +{ + mInitDelegates[(U32)adapterType] = initDelegate; +} + +bool ShaderGen::_handleGFXEvent(GFXDevice::GFXDeviceEventType event) +{ + switch (event) + { + case GFXDevice::deInit : + initShaderGen(); + break; + case GFXDevice::deDestroy : + { + mRegisteredWithGFX = false; + flushProceduralShaders(); + } + break; + default : + break; + } + return true; +} + +void ShaderGen::initShaderGen() +{ + if (mInit) + return; + + const GFXAdapterType adapterType = GFX->getAdapterType(); + if (!mInitDelegates[adapterType]) + return; + + mInitDelegates[adapterType](this); + mFeatureInitSignal.trigger( adapterType ); + mInit = true; + + String shaderPath = Con::getVariable( "$pref::video::shaderGenPath"); +#if defined(TORQUE_SHADERGEN) && ( defined(TORQUE_OS_XENON) || defined(TORQUE_OS_PS3) ) + // If this is a console build, and TORQUE_SHADERGEN is defined + // (signifying that new shaders should be generated) then clear the shader + // path so that the MemFileSystem is used instead. + shaderPath.clear(); +#endif + + if (!shaderPath.equal( "shadergen:" ) && !shaderPath.isEmpty() ) + { + // this is necessary, especially under Windows with UAC enabled + if (!Torque::FS::VerifyWriteAccess(shaderPath)) + { + // we don't have write access so enable the virtualized memory store + Con::warnf("ShaderGen: Write permission unavailable, switching to virtualized memory storage"); + shaderPath.clear(); + } + + } + + if ( shaderPath.equal( "shadergen:" ) || shaderPath.isEmpty() ) + { + // If we didn't get a path then we're gonna cache the shaders to + // a virtualized memory file system. + mMemFS = new Torque::Mem::MemFileSystem( "shadergen:/" ); + Torque::FS::Mount( "shadergen", mMemFS ); + } + else + Torque::FS::Mount( "shadergen", shaderPath + "/" ); + + // Delete the auto-generated conditioner include file. + Torque::FS::Remove( "shadergen:/" + ConditionerFeature::ConditionerIncludeFileName ); +} + +void ShaderGen::generateShader( const MaterialFeatureData &featureData, + char *vertFile, + char *pixFile, + F32 *pixVersion, + const GFXVertexFormat *vertexFormat, + const char* cacheName, + Vector ¯os ) +{ + PROFILE_SCOPE( ShaderGen_GenerateShader ); + + mFeatureData = featureData; + mVertexFormat = vertexFormat; + + _uninit(); + _init(); + + char vertShaderName[256]; + char pixShaderName[256]; + + // Note: We use a postfix of _V/_P here so that it sorts the matching + // vert and pixel shaders together when listed alphabetically. + dSprintf( vertShaderName, sizeof(vertShaderName), "shadergen:/%s_V.%s", cacheName, mFileEnding.c_str() ); + dSprintf( pixShaderName, sizeof(pixShaderName), "shadergen:/%s_P.%s", cacheName, mFileEnding.c_str() ); + + dStrcpy( vertFile, vertShaderName ); + dStrcpy( pixFile, pixShaderName ); + + // this needs to change - need to optimize down to ps v.1.1 + *pixVersion = GFX->getPixelShaderVersion(); + + if ( !Con::getBoolVariable( "ShaderGen::GenNewShaders", true ) ) + { + // If we are not regenerating the shader we will return here. + // But we must fill in the shader macros first! + + _processVertFeatures( macros, true ); + _processPixFeatures( macros, true ); + + return; + } + + // create vertex shader + //------------------------ + FileStream* s = new FileStream(); + if(!s->open(vertShaderName, Torque::FS::File::Write )) + { + AssertFatal(false, "Failed to open Shader Stream" ); + return; + } + + mOutput = new MultiLine; + _processVertFeatures(macros); + _printVertShader( *s ); + delete s; + + ((ShaderConnector*)mComponents[C_CONNECTOR])->reset(); + LangElement::deleteElements(); + + // create pixel shader + //------------------------ + s = new FileStream(); + if(!s->open(pixShaderName, Torque::FS::File::Write )) + { + AssertFatal(false, "Failed to open Shader Stream" ); + return; + } + + mOutput = new MultiLine; + _processPixFeatures(macros); + _printPixShader( *s ); + + delete s; + LangElement::deleteElements(); +} + +void ShaderGen::_init() +{ + _createComponents(); +} + +void ShaderGen::_uninit() +{ + for( U32 i=0; icreateVertexInputConnector( *mVertexFormat ); + mComponents.push_back(vertComp); + + ShaderComponent* vertPixelCon = mComponentFactory->createVertexPixelConnector(); + mComponents.push_back(vertPixelCon); + + ShaderComponent* vertParamDef = mComponentFactory->createVertexParamsDef(); + mComponents.push_back(vertParamDef); + + ShaderComponent* pixParamDef = mComponentFactory->createPixelParamsDef(); + mComponents.push_back(pixParamDef); +} + +//---------------------------------------------------------------------------- +// Process features +//---------------------------------------------------------------------------- +void ShaderGen::_processVertFeatures( Vector ¯os, bool macrosOnly ) +{ + const FeatureSet &features = mFeatureData.features; + + for( U32 i=0; i < features.getCount(); i++ ) + { + S32 index; + const FeatureType &type = features.getAt( i, &index ); + ShaderFeature* feature = FEATUREMGR->getByType( type ); + if ( feature ) + { + feature->setProcessIndex( index ); + + feature->processVertMacros( macros, mFeatureData ); + + if ( macrosOnly ) + continue; + + feature->processVert( mComponents, mFeatureData ); + + String line; + if ( index > -1 ) + line = String::ToString( " // %s %d\r\n", feature->getName().c_str(), index ); + else + line = String::ToString( " // %s\r\n", feature->getName().c_str() ); + mOutput->addStatement( new GenOp( line ) ); + + if ( feature->getOutput() ) + mOutput->addStatement( feature->getOutput() ); + + feature->reset(); + mOutput->addStatement( new GenOp( " \r\n" ) ); + } + } + + ShaderConnector *connect = dynamic_cast( mComponents[C_CONNECTOR] ); + connect->sortVars(); +} + +void ShaderGen::_processPixFeatures( Vector ¯os, bool macrosOnly ) +{ + const FeatureSet &features = mFeatureData.features; + + for( U32 i=0; i < features.getCount(); i++ ) + { + S32 index; + const FeatureType &type = features.getAt( i, &index ); + ShaderFeature* feature = FEATUREMGR->getByType( type ); + if ( feature ) + { + feature->setProcessIndex( index ); + + feature->processPixMacros( macros, mFeatureData ); + + if ( macrosOnly ) + continue; + + feature->processPix( mComponents, mFeatureData ); + + String line; + if ( index > -1 ) + line = String::ToString( " // %s %d\r\n", feature->getName().c_str(), index ); + else + line = String::ToString( " // %s\r\n", feature->getName().c_str() ); + mOutput->addStatement( new GenOp( line ) ); + + if ( feature->getOutput() ) + mOutput->addStatement( feature->getOutput() ); + + feature->reset(); + mOutput->addStatement( new GenOp( " \r\n" ) ); + } + } + + ShaderConnector *connect = dynamic_cast( mComponents[C_CONNECTOR] ); + connect->sortVars(); +} + +void ShaderGen::_printFeatureList(Stream &stream) +{ + mPrinter->printLine(stream, "// Features:"); + + const FeatureSet &features = mFeatureData.features; + + for( U32 i=0; i < features.getCount(); i++ ) + { + S32 index; + const FeatureType &type = features.getAt( i, &index ); + ShaderFeature* feature = FEATUREMGR->getByType( type ); + if ( feature ) + { + String line; + if ( index > -1 ) + line = String::ToString( "// %s %d", feature->getName().c_str(), index ); + else + line = String::ToString( "// %s", feature->getName().c_str() ); + + mPrinter->printLine( stream, line ); + } + } + + mPrinter->printLine(stream, ""); +} + +void ShaderGen::_printDependencies(Stream &stream) +{ + Vector dependencies; + + for( U32 i=0; i < FEATUREMGR->getFeatureCount(); i++ ) + { + const FeatureInfo &info = FEATUREMGR->getAt( i ); + if ( mFeatureData.features.hasFeature( *info.type ) ) + dependencies.merge( info.feature->getDependencies() ); + } + + // Do a quick loop removing any duplicate dependancies. + for( U32 i=0; i < dependencies.size(); ) + { + bool dup = false; + + for( U32 j=0; j < dependencies.size(); j++ ) + { + if ( j != i && + *dependencies[i] == *dependencies[j] ) + { + dup = true; + break; + } + } + + if ( dup ) + dependencies.erase( i ); + else + i++; + } + + // Print dependencies + if( dependencies.size() > 0 ) + { + mPrinter->printLine(stream, "// Dependencies:"); + + for( int i = 0; i < dependencies.size(); i++ ) + dependencies[i]->print( stream ); + + mPrinter->printLine(stream, ""); + } +} + +void ShaderGen::_printFeatures( Stream &stream ) +{ + mOutput->print( stream ); +} + +void ShaderGen::_printVertShader( Stream &stream ) +{ + mPrinter->printShaderHeader(stream); + + _printDependencies(stream); // TODO: Split into vert and pix dependencies? + _printFeatureList(stream); + + // print out structures + mComponents[C_VERT_STRUCT]->print( stream ); + mComponents[C_CONNECTOR]->print( stream ); + + mPrinter->printMainComment(stream); + + mComponents[C_VERT_MAIN]->print( stream ); + + + // print out the function + _printFeatures( stream ); + + mPrinter->printVertexShaderCloser(stream); +} + +void ShaderGen::_printPixShader( Stream &stream ) +{ + mPrinter->printShaderHeader(stream); + + _printDependencies(stream); // TODO: Split into vert and pix dependencies? + _printFeatureList(stream); + + mComponents[C_CONNECTOR]->print( stream ); + + mPrinter->printPixelShaderOutputStruct(stream, mFeatureData); + mPrinter->printMainComment(stream); + + mComponents[C_PIX_MAIN]->print( stream ); + + // print out the function + _printFeatures( stream ); + + mPrinter->printPixelShaderCloser(stream); +} + +GFXShader* ShaderGen::getShader( const MaterialFeatureData &featureData, const GFXVertexFormat *vertexFormat, const Vector *macros ) +{ + PROFILE_SCOPE( ShaderGen_GetShader ); + + const FeatureSet &features = featureData.codify(); + + // Build a description string from the features + // and vertex format combination ( and macros ). + String shaderDescription = vertexFormat->getDescription() + features.getDescription(); + if ( macros && !macros->empty() ) + { + String macroStr; + GFXShaderMacro::stringize( *macros, ¯oStr ); + shaderDescription += macroStr; + } + + // Generate a single 64bit hash from the description string. + // + // Don't get paranoid! This has 1 in 18446744073709551616 + // chance for collision... it won't happen in this lifetime. + // + U64 hash = Torque::hash64( (const U8*)shaderDescription.c_str(), shaderDescription.length(), 0 ); + hash = convertHostToLEndian(hash); + U32 high = (U32)( hash >> 32 ); + U32 low = (U32)( hash & 0x00000000FFFFFFFF ); + String cacheKey = String::ToString( "%x%x", high, low ); + + // return shader if exists + GFXShader *match = mProcShaders[cacheKey]; + if ( match ) + return match; + + // if not, then create it + char vertFile[256]; + char pixFile[256]; + F32 pixVersion; + + Vector shaderMacros; + if ( macros ) + shaderMacros.merge( *macros ); + generateShader( featureData, vertFile, pixFile, &pixVersion, vertexFormat, cacheKey, shaderMacros ); + + GFXShader *shader = GFX->createShader(); + if ( !shader->init( vertFile, pixFile, pixVersion, shaderMacros ) ) + { + delete shader; + return NULL; + } + + String descr = String::ToString( "Files: %s, %s Pix Version: %0.2f", vertFile, pixFile, pixVersion ); + shader->setDescription( descr ); + shader->mVertexFormat = vertexFormat; + mProcShaders[cacheKey] = shader; + + return shader; +} + +void ShaderGen::flushProceduralShaders() +{ + // The shaders are reference counted, so we + // just need to clear the map. + mProcShaders.clear(); +} diff --git a/shaderGen/shaderGen.h b/shaderGen/shaderGen.h new file mode 100644 index 0000000..37eb661 --- /dev/null +++ b/shaderGen/shaderGen.h @@ -0,0 +1,209 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERGEN_H_ +#define _SHADERGEN_H_ + +#ifndef _LANG_ELEMENT_H_ +#include "shaderGen/langElement.h" +#endif +#ifndef _SHADERFEATURE_H_ +#include "shaderGen/shaderFeature.h" +#endif +#ifndef _SHADERCOMP_H_ +#include "shaderGen/shaderComp.h" +#endif +#ifndef _GFXDEVICE_H_ +#include "gfx/gfxDevice.h" +#endif +#ifndef _AUTOPTR_H_ +#include "core/util/autoPtr.h" +#endif +#ifndef _TSINGLETON_H_ +#include "core/util/tSingleton.h" +#endif +#ifndef _VOLUME_H_ +#include "core/volume.h" +#endif + + +/// Base class used by shaderGen to be API agnostic. Subclasses implement the various methods +/// in an API specific way. +class ShaderGenPrinter +{ +public: + virtual ~ShaderGenPrinter() {} + + /// Prints a simple header, including the engine name, language type, and + /// the fact that the shader was procedurally generated + virtual void printShaderHeader(Stream& stream) = 0; + + /// Prints a comment block specifying the beginning of the main() function (or equivalent) + virtual void printMainComment(Stream& stream) = 0; + + /// Prints the final line of the vertex shader, e.g. return OUT; }, }, END + virtual void printVertexShaderCloser(Stream& stream) = 0; + + /// Prints the output struct for the pixel shader. Probably only used in HLSL/Cg. + virtual void printPixelShaderOutputStruct(Stream& stream, const MaterialFeatureData &featureData) = 0; + + /// Prints the final line of the pixel shader. + virtual void printPixelShaderCloser(Stream& stream) = 0; + + // Prints a line into the shader adding the proper terminator. + virtual void printLine(Stream& stream, const String& line) = 0; +}; + +/// Abstract factory for created (and initializating, if necessary) shader components. +class ShaderGenComponentFactory +{ +public: + virtual ~ShaderGenComponentFactory() {} + + /// Creates and initializes a vertex input connector with the specified flags + virtual ShaderComponent* createVertexInputConnector( const GFXVertexFormat &vertexFormat ) = 0; + + /// Creates and names a vertex/pixel connector + virtual ShaderComponent* createVertexPixelConnector() = 0; + + /// Creates an instance of VertexParamsDef + virtual ShaderComponent* createVertexParamsDef() = 0; + + /// Creates an instance of PixelParamsDef + virtual ShaderComponent* createPixelParamsDef() = 0; +}; + +//************************************************************************** +/*! + The ShaderGen class takes shader feature data (usually created by + MatInstance) and creates a vertex/pixel shader pair in text files + to be later compiled by a shader manager. + + It accomplishes this task by creating a group of shader "components" and + "features" that output bits of high level shader code. Shader components + translate to structures in HLSL that indicate incoming vertex data, + data that is output from the vertex shader to the pixel shader, and data + such as constants and textures that are passed directly to the shader + from the app. + + Shader features are separable shader functions that can be turned on or + off. Examples would be bumpmapping and specular highlights. See + MaterialFeatureData for the current list of features supported. + + ShaderGen processes all of the features that are present for a desired + shader, and then prints them out to the respective vertex or pixel + shader file. + + For more information on shader features and components see the + ShaderFeature and ShaderComponent classes. +*/ +//************************************************************************** + + +//************************************************************************** +// Shader generator +//************************************************************************** +class ShaderGen +{ +public: + virtual ~ShaderGen(); + + /// Parameter 1 is the ShaderGen instance to initialize. + typedef Delegate ShaderGenInitDelegate; + + /// Register an initialization delegate for adapterType. This should setPrinter/ComponentFactory/etc, and register + /// shader features. + void registerInitDelegate(GFXAdapterType adapterType, ShaderGenInitDelegate& initDelegate); + + /// Signal used to notify systems to register features. + typedef Signal FeatureInitSignal; + + /// Returns the signal used to notify systems to register features. + FeatureInitSignal& getFeatureInitSignal() { return mFeatureInitSignal; } + + /// vertFile and pixFile are filled in by this function. They point to + /// the vertex and pixel shader files. pixVersion is also filled in by + /// this function. + /// @param assignNum used to assign a specific number as the filename + void generateShader( const MaterialFeatureData &featureData, + char *vertFile, + char *pixFile, + F32 *pixVersion, + const GFXVertexFormat *vertexFormat, + const char* cacheName, + Vector ¯os ); + + // Returns a shader that implements the features listed by dat. + GFXShader* getShader( const MaterialFeatureData &dat, const GFXVertexFormat *vertexFormat, const Vector *macros ); + + // This will delete all of the procedural shaders that we have. Used to regenerate shaders when + // the ShaderFeatures have changed (due to lighting system change, or new plugin) + virtual void flushProceduralShaders(); + + void setPrinter(ShaderGenPrinter* printer) { mPrinter = printer; } + void setComponentFactory(ShaderGenComponentFactory* factory) { mComponentFactory = factory; } + void setFileEnding(String ending) { mFileEnding = ending; } + +protected: + + friend class Singleton; + + // Shader generation + MaterialFeatureData mFeatureData; + const GFXVertexFormat *mVertexFormat; + + Vector< ShaderComponent *> mComponents; + + AutoPtr mPrinter; + AutoPtr mComponentFactory; + + String mFileEnding; + + /// The currently processing output. + MultiLine *mOutput; + + /// Init + bool mInit; + ShaderGenInitDelegate mInitDelegates[GFXAdapterType_Count]; + FeatureInitSignal mFeatureInitSignal; + bool mRegisteredWithGFX; + Torque::FS::FileSystemRef mMemFS; + + /// Map of cache string -> shaders + typedef Map ShaderMap; + ShaderMap mProcShaders; + + ShaderGen(); + + bool _handleGFXEvent(GFXDevice::GFXDeviceEventType event); + + /// Causes the init delegate to be called. + void initShaderGen(); + + void _init(); + void _uninit(); + + /// Creates all the various shader components that will be filled in when + /// the shader features are processed. + void _createComponents(); + + void _printFeatureList(Stream &stream); + + /// print out the processed features to the file stream + void _printFeatures( Stream &stream ); + + void _printDependencies( Stream &stream ); + + void _processPixFeatures( Vector ¯os, bool macrosOnly = false ); + void _printPixShader( Stream &stream ); + + void _processVertFeatures( Vector ¯os, bool macrosOnly = false ); + void _printVertShader( Stream &stream ); +}; + + +/// Returns the ShaderGen singleton. +#define SHADERGEN Singleton::instance() + +#endif // _SHADERGEN_H_ diff --git a/shaderGen/shaderGenVars.cpp b/shaderGen/shaderGenVars.cpp new file mode 100644 index 0000000..b923d45 --- /dev/null +++ b/shaderGen/shaderGenVars.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "shaderGen/shaderGenVars.h" + +const String ShaderGenVars::modelview("$modelview"); +const String ShaderGenVars::worldViewOnly("$worldViewOnly"); +const String ShaderGenVars::worldToCamera("$worldToCamera"); +const String ShaderGenVars::worldToObj("$worldToObj"); +const String ShaderGenVars::viewToObj("$viewToObj"); +const String ShaderGenVars::cubeTrans("$cubeTrans"); +const String ShaderGenVars::objTrans("$objTrans"); +const String ShaderGenVars::cubeEyePos("$cubeEyePos"); +const String ShaderGenVars::eyePos("$eyePos"); +const String ShaderGenVars::eyePosWorld("$eyePosWorld"); +const String ShaderGenVars::vEye("$vEye"); +const String ShaderGenVars::eyeMat("$eyeMat"); +const String ShaderGenVars::oneOverFarplane("$oneOverFarplane"); +const String ShaderGenVars::nearPlaneWorld("$nearPlaneWorld"); +const String ShaderGenVars::fogData("$fogData"); +const String ShaderGenVars::fogColor("$fogColor"); +const String ShaderGenVars::detailScale("$detailScale"); +const String ShaderGenVars::visibility("$visibility"); +const String ShaderGenVars::colorMultiply("$colorMultiply"); +const String ShaderGenVars::alphaTestValue("$alphaTestValue"); +const String ShaderGenVars::texMat("$texMat"); +const String ShaderGenVars::accumTime("$accumTime"); +const String ShaderGenVars::minnaertConstant("$minnaertConstant"); +const String ShaderGenVars::subSurfaceParams("$subSurfaceParams"); + +const String ShaderGenVars::lightPosition("$inLightPos"); +const String ShaderGenVars::lightDiffuse("$inLightColor"); +const String ShaderGenVars::lightAmbient("$ambient"); +const String ShaderGenVars::lightInvRadiusSq("$inLightInvRadiusSq"); +const String ShaderGenVars::lightSpotDir("$inLightSpotDir"); +const String ShaderGenVars::lightSpotAngle("$inLightSpotAngle"); +const String ShaderGenVars::specularColor("$specularColor"); +const String ShaderGenVars::specularPower("$specularPower"); + +// These are ignored by the D3D layers. +const String ShaderGenVars::fogMap("$fogMap"); +const String ShaderGenVars::dlightMap("$dlightMap"); +const String ShaderGenVars::dlightMask("$dlightMask"); +const String ShaderGenVars::dlightMapSec("$dlightMapSec"); +const String ShaderGenVars::blackfogMap("$blackfogMap"); +const String ShaderGenVars::bumpMap("$bumpMap"); +const String ShaderGenVars::lightMap("$lightMap"); +const String ShaderGenVars::lightNormMap("$lightNormMap"); +const String ShaderGenVars::cubeMap("$cubeMap"); +const String ShaderGenVars::dLightMap("$dlightMap"); +const String ShaderGenVars::dLightMapSec("$dlightMapSec"); +const String ShaderGenVars::dLightMask("$dlightMask"); +const String ShaderGenVars::toneMap("$toneMap"); \ No newline at end of file diff --git a/shaderGen/shaderGenVars.h b/shaderGen/shaderGenVars.h new file mode 100644 index 0000000..a7e9408 --- /dev/null +++ b/shaderGen/shaderGenVars.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Torque Game Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADERGENVARS_H_ +#define _SHADERGENVARS_H_ + +#ifndef _TORQUE_STRING_H_ +#include "core/util/str.h" +#endif + +/// +/// ShaderGenVars, predefined string names for variables that shadergen based shaders use, this avoids +/// misspelling and string creation issues +/// +struct ShaderGenVars +{ + const static String modelview; + const static String worldViewOnly; + const static String worldToCamera; + const static String worldToObj; + const static String viewToObj; + const static String cubeTrans; + const static String objTrans; + const static String cubeEyePos; + const static String eyePos; + const static String eyePosWorld; + const static String vEye; + const static String eyeMat; + const static String oneOverFarplane; + const static String nearPlaneWorld; + const static String fogData; + const static String fogColor; + const static String detailScale; + const static String visibility; + const static String colorMultiply; + const static String alphaTestValue; + const static String texMat; + const static String accumTime; + const static String minnaertConstant; + const static String subSurfaceParams; + + // Lighting parameters used by the default + // RTLighting shader feature. + const static String lightPosition; + const static String lightDiffuse; + const static String lightAmbient; + const static String lightInvRadiusSq; + const static String lightSpotDir; + const static String lightSpotAngle; + const static String specularColor; + const static String specularPower; + + // Textures + const static String fogMap; + const static String dlightMap; + const static String dlightMask; + const static String dlightMapSec; + const static String blackfogMap; + const static String bumpMap; + const static String lightMap; + const static String lightNormMap; + const static String cubeMap; + const static String dLightMap; + const static String dLightMapSec; + const static String dLightMask; + const static String toneMap; +}; + +#endif diff --git a/shaderGen/shaderOp.cpp b/shaderGen/shaderOp.cpp new file mode 100644 index 0000000..41957dd --- /dev/null +++ b/shaderGen/shaderOp.cpp @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/strings/stringFunctions.h" +#include + + +#include "shaderOp.h" + +//************************************************************************** +// Shader Operations +//************************************************************************** +ShaderOp::ShaderOp( LangElement *in1, LangElement *in2 ) +{ + mInput[0] = in1; + mInput[1] = in2; +} + +//************************************************************************** +// Declaration Operation - for variables +//************************************************************************** +DecOp::DecOp( Var *in1 ) : Parent( in1, NULL ) +{ + mInput[0] = in1; +} + +//-------------------------------------------------------------------------- +// Print +//-------------------------------------------------------------------------- +void DecOp::print( Stream &stream ) +{ + Var *var = dynamic_cast( mInput[0] ); + + WRITESTR( (char*)var->type ); + WRITESTR( " " ); + + mInput[0]->print( stream ); +} + +//************************************************************************** +// Echo operation - deletes incoming statement! +//************************************************************************** +EchoOp::EchoOp( const char * statement ) : Parent( NULL, NULL ) +{ + mStatement = statement; +} + +//-------------------------------------------------------------------------- +// Destructor +//-------------------------------------------------------------------------- +EchoOp::~EchoOp() +{ + delete [] mStatement; +} + +//-------------------------------------------------------------------------- +// Print +//-------------------------------------------------------------------------- +void EchoOp::print( Stream &stream ) +{ + WRITESTR( mStatement ); +} + + +//************************************************************************** +// General operation +//************************************************************************** +GenOp::GenOp( const char * statement, ... ) : Parent( NULL, NULL ) +{ + VECTOR_SET_ASSOCIATION( mElemList ); + + va_list args; + va_start(args, statement); + + char* lastEntry = (char*)statement; + + while( 1 ) + { + // search 'statement' for @ symbol + char * str = dStrstr( lastEntry, (char *)"@" ); + + if( !str ) + { + // not found, handle end of line + str = (char*)&statement[ dStrlen( (char*)statement ) ]; + + U32 diff = str - lastEntry + 1; + if( diff == 1 ) break; + + char * newStr = new char[diff]; + + dMemcpy( (void*)newStr, lastEntry, diff ); + + mElemList.push_back( new EchoOp( newStr ) ); + + break; + } + + // create and store statement fragment + U32 diff = str - lastEntry + 1; + + if( diff == 1 ) + { + // store langElement + LangElement *elem = va_arg(args, LangElement* ); + AssertFatal( elem, "NULL arguement." ); + mElemList.push_back( elem ); + lastEntry++; + continue; + } + + char * newStr = new char[diff]; + + dMemcpy( (void*)newStr, lastEntry, diff ); + newStr[diff-1] = '\0'; + + lastEntry = str + 1; + + mElemList.push_back( new EchoOp( newStr ) ); + + // store langElement + LangElement *elem = va_arg(args, LangElement* ); + AssertFatal( elem, "NULL argument." ); + mElemList.push_back( elem ); + } + + va_end( args ); +} + +//-------------------------------------------------------------------------- +// Print +//-------------------------------------------------------------------------- +void GenOp::print( Stream &stream ) +{ + for( U32 i=0; iprint( stream ); + } +} diff --git a/shaderGen/shaderOp.h b/shaderGen/shaderOp.h new file mode 100644 index 0000000..f849669 --- /dev/null +++ b/shaderGen/shaderOp.h @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#ifndef _SHADEROP_H_ +#define _SHADEROP_H_ + +#ifndef _LANG_ELEMENT_H_ +#include "shaderGen/langElement.h" +#endif + +//************************************************************************** +/*! + This file contains "shader operation" classes. Originally they were + to represent basic language operations like adding, assignment, etc. + That proved to be far too verbose when implementing shader features, + so they became more generalized helper classes. Along with LangElement + classes, they are the building blocks for the procedurally generated + shaders. + + Each shader is a linked list of LangElements. The list is generated + when the features of the shader are processed. When all the features + are processed, then ShaderGen prints them out by traversing the linked + list of LangElement and calling their print() function. + + The ShaderOp classes are just extensions of LangElement. + +*/ +//************************************************************************** + + + +///************************************************************************** +/// Shader operation base class +///************************************************************************** +class ShaderOp : public LangElement +{ +protected: + LangElement * mInput[2]; + +public: + ShaderOp( LangElement *in1, LangElement *in2 ); +}; + +//---------------------------------------------------------------------------- +/*! + DecOp - Declaration Operation - Used when declaring a variable in a shader + feature. It will automatically print the type of the variable and then + the variable name. If a shader feature set up code like: + + @code + + Var *foo = new Var; + foo->setType( "float" ); + foo->setName( "foo" ); + LangElement *fooDecl = new DecOp( foo ); + + LangElement *statement = new GenOp( " @ = 8.0 * 5.0;", fooDecl ); + + @endcode + + The output in the shader file would be: + + @code + float foo = 8.0 * 5.0; + @endcode +*/ +//---------------------------------------------------------------------------- +class DecOp : public ShaderOp +{ + typedef ShaderOp Parent; + +public: + DecOp( Var *in1 ); + virtual void print( Stream &stream ); +}; + + +//---------------------------------------------------------------------------- +/*! + Like the name suggests, EchoOp merely echoes back whatever string it is + assigned. +*/ +//---------------------------------------------------------------------------- +class EchoOp : public ShaderOp +{ + typedef ShaderOp Parent; + const char * mStatement; + +public: + EchoOp( const char * statement ); + ~EchoOp(); + virtual void print( Stream &stream ); +}; + + +//---------------------------------------------------------------------------- +/*! + GenOp - General Operation - Very useful for combining several variables + into one LangElement statement. It uses an elipses as a parameter, so it can + take as many variables as you can throw at it. It takes a string and parses + it for the '@' symbol which it replaces with passed in parameters. Similar + to the C statement printf(). Here's an example: + + @code + ( assuming three variables var1, var2, var3 exist and their assigned names + are var1Name, var2Name, and var3Name ) + + LangElement *statement = new GenOp( " @ = @ * @.x + @.y;", var1, var1, var2, var3 ); + + @endcode + + The output in the shader file would be: + + @code + + var1Name = var1Name * var2Name.x + var3Name.y; + + @endcode +*/ +//---------------------------------------------------------------------------- +class GenOp : public ShaderOp +{ + typedef ShaderOp Parent; + + Vector mElemList; + +public: + GenOp( const char * statement, ... ); + virtual void print( Stream &stream ); + +}; + +#endif // _SHADEROP_H_ diff --git a/sim/actionMap.cpp b/sim/actionMap.cpp new file mode 100644 index 0000000..9839eff --- /dev/null +++ b/sim/actionMap.cpp @@ -0,0 +1,1942 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "sim/actionMap.h" +#include "platform/event.h" +#include "console/console.h" +#include "platform/platform.h" +#include "platform/platformInput.h" +#include "platform/platformAssert.h" +#include "core/stream/fileStream.h" +#include "math/mMathFn.h" + +#define CONST_E 2.7182818284590452353602874f + +IMPLEMENT_CONOBJECT(ActionMap); + +// This is used for determing keys that have ascii codes for the foreign keyboards. IsAlpha doesn't work on foreign keys. +static inline bool dIsDecentChar(U8 c) +{ + return ((U8(0xa0) <= c) || (( U8(0x21) <= c) && (c <= U8(0x7e))) || ((U8(0x91) <= c) && (c <= U8(0x92)))); +} + +struct CodeMapping +{ + const char* pDescription; + InputEventType type; + InputObjectInstances code; +}; + +struct AsciiMapping +{ + const char* pDescription; + U16 asciiCode; +}; + +extern CodeMapping gVirtualMap[]; +extern AsciiMapping gAsciiMap[]; + +//------------------------------------------------------------------------------ +//-------------------------------------- Action maps +// +Vector ActionMap::smBreakTable(__FILE__, __LINE__); + + +//------------------------------------------------------------------------------ +ActionMap::ActionMap() +{ + VECTOR_SET_ASSOCIATION(mDeviceMaps); +} + +//------------------------------------------------------------------------------ +ActionMap::~ActionMap() +{ + for (U32 i = 0; i < mDeviceMaps.size(); i++) + delete mDeviceMaps[i]; + mDeviceMaps.clear(); +} + +//------------------------------------------------------------------------------ +ActionMap::DeviceMap::~DeviceMap() +{ + for(U32 i = 0; i < nodeMap.size(); i++) + { + dFree(nodeMap[i].makeConsoleCommand); + dFree(nodeMap[i].breakConsoleCommand); + } +} + +//------------------------------------------------------------------------------ +bool ActionMap::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + Sim::getActionMapGroup()->addObject(this); + + return true; +} + +//-------------------------------------------------------------------------- +void ActionMap::dumpActionMap(const char* fileName, const bool append) const +{ + if (fileName != NULL) { + // Dump the deletion, and creation script commands, followed by all the binds + // to a script. + + FileStream *iostrm; + if((iostrm = FileStream::createAndOpen( fileName, append ? Torque::FS::File::WriteAppend : Torque::FS::File::Write )) == NULL) + { + Con::errorf( "Unable to open file '%s' for writing.", fileName ); + return; + } + + char lineBuffer[1024]; + if ( append ) + iostrm->setPosition( iostrm->getStreamSize() ); + else + { + // IMPORTANT -- do NOT change the following line, it identifies the file as an input map file + dStrcpy( lineBuffer, "// Torque Input Map File\n" ); + iostrm->write( dStrlen( lineBuffer ), lineBuffer ); + } + + dSprintf(lineBuffer, 1023, "if (isObject(%s)) %s.delete();\n" + "new ActionMap(%s);\n", getName(), getName(), getName()); + iostrm->write(dStrlen(lineBuffer), lineBuffer); + + // Dump all the binds to the console... + for (S32 i = 0; i < mDeviceMaps.size(); i++) { + const DeviceMap* pDevMap = mDeviceMaps[i]; + + char devbuffer[32]; + getDeviceName(pDevMap->deviceType, pDevMap->deviceInst, devbuffer); + + for (S32 j = 0; j < pDevMap->nodeMap.size(); j++) { + const Node& rNode = pDevMap->nodeMap[j]; + + const char* pModifierString = getModifierString(rNode.modifiers); + + char objectbuffer[64]; + if (getKeyString(rNode.action, objectbuffer) == false) + continue; + + const char* command = (rNode.flags & Node::BindCmd) ? "bindCmd" : "bind"; + + dSprintf(lineBuffer, 1023, "%s.%s(%s, \"%s%s\"", + getName(), + command, + devbuffer, + pModifierString, objectbuffer); + + if (rNode.flags & (Node::HasScale|Node::HasDeadZone|Node::Ranged|Node::Inverted)) { + char buff[10]; + U32 curr = 0; + buff[curr++] = ','; + buff[curr++] = ' '; + if (rNode.flags & Node::HasScale) + buff[curr++] = 'S'; + if (rNode.flags & Node::Ranged) + buff[curr++] = 'R'; + if (rNode.flags & Node::HasDeadZone) + buff[curr++] = 'D'; + if (rNode.flags & Node::Inverted) + buff[curr++] = 'I'; + buff[curr] = '\0'; + + dStrcat(lineBuffer, buff); + } + + if (rNode.flags & Node::HasDeadZone) { + char buff[64]; + dSprintf(buff, 63, ", \"%g %g\"", rNode.deadZoneBegin, rNode.deadZoneEnd); + dStrcat(lineBuffer, buff); + } + + if (rNode.flags & Node::HasScale) { + char buff[64]; + dSprintf(buff, 63, ", %g", rNode.scaleFactor); + dStrcat(lineBuffer, buff); + } + + if (rNode.flags & Node::BindCmd) { + if (rNode.makeConsoleCommand) { + dStrcat(lineBuffer, ", \""); + U32 pos = dStrlen(lineBuffer); + expandEscape(lineBuffer + pos, rNode.makeConsoleCommand); + dStrcat(lineBuffer, "\""); + } else { + dStrcat(lineBuffer, ", \"\""); + } + if (rNode.breakConsoleCommand) { + dStrcat(lineBuffer, ", \""); + U32 pos = dStrlen(lineBuffer); + expandEscape(lineBuffer + pos, rNode.breakConsoleCommand); + dStrcat(lineBuffer, "\""); + } + else + dStrcat(lineBuffer, ", \"\""); + } else { + dStrcat(lineBuffer, ", "); + dStrcat(lineBuffer, rNode.consoleFunction); + } + + dStrcat(lineBuffer, ");\n"); + iostrm->write(dStrlen(lineBuffer), lineBuffer); + } + } + + delete iostrm; + } + else { + // Dump all the binds to the console... + for (S32 i = 0; i < mDeviceMaps.size(); i++) { + const DeviceMap* pDevMap = mDeviceMaps[i]; + + char devbuffer[32]; + getDeviceName(pDevMap->deviceType, pDevMap->deviceInst, devbuffer); + + for (S32 j = 0; j < pDevMap->nodeMap.size(); j++) { + const Node& rNode = pDevMap->nodeMap[j]; + + const char* pModifierString = getModifierString(rNode.modifiers); + + char keybuffer[64]; + if (getKeyString(rNode.action, keybuffer) == false) + continue; + + const char* command = (rNode.flags & Node::BindCmd) ? "bindCmd" : "bind"; + + char finalBuffer[1024]; + dSprintf(finalBuffer, 1023, "%s.%s(%s, \"%s%s\"", + getName(), + command, + devbuffer, + pModifierString, keybuffer); + + if (rNode.flags & (Node::HasScale|Node::HasDeadZone|Node::Ranged|Node::Inverted)) { + char buff[10]; + U32 curr = 0; + buff[curr++] = ','; + buff[curr++] = ' '; + if (rNode.flags & Node::HasScale) + buff[curr++] = 'S'; + if (rNode.flags & Node::Ranged) + buff[curr++] = 'R'; + if (rNode.flags & Node::HasDeadZone) + buff[curr++] = 'D'; + if (rNode.flags & Node::Inverted) + buff[curr++] = 'I'; + buff[curr] = '\0'; + + dStrcat(finalBuffer, buff); + } + + if (rNode.flags & Node::HasDeadZone) { + char buff[64]; + dSprintf(buff, 63, ", \"%g %g\"", rNode.deadZoneBegin, rNode.deadZoneEnd); + dStrcat(finalBuffer, buff); + } + + if (rNode.flags & Node::HasScale) { + char buff[64]; + dSprintf(buff, 63, ", %g", rNode.scaleFactor); + dStrcat(finalBuffer, buff); + } + + if (rNode.flags & Node::BindCmd) { + if (rNode.makeConsoleCommand) { + dStrcat(finalBuffer, ", \""); + dStrcat(finalBuffer, rNode.makeConsoleCommand); + dStrcat(finalBuffer, "\""); + } else { + dStrcat(finalBuffer, ", \"\""); + } + if (rNode.breakConsoleCommand) { + dStrcat(finalBuffer, ", \""); + dStrcat(finalBuffer, rNode.breakConsoleCommand); + dStrcat(finalBuffer, "\""); + } + else + dStrcat(finalBuffer, ", \"\""); + } else { + dStrcat(finalBuffer, ", "); + dStrcat(finalBuffer, rNode.consoleFunction); + } + + dStrcat(finalBuffer, ");"); + Con::printf(finalBuffer); + } + } + } +} + +//-------------------------------------------------------------------------- +bool ActionMap::createEventDescriptor(const char* pEventString, EventDescriptor* pDescriptor) +{ + char copyBuffer[256]; + dStrcpy(copyBuffer, pEventString); + + // Do we have modifiers? + char* pSpace = dStrchr(copyBuffer, ' '); + char* pObjectString; + if (pSpace != NULL) { + // Yes. Parse them out... + // + pDescriptor->flags = 0; + pObjectString = pSpace + 1; + pSpace[0] = '\0'; + + char* pModifier = dStrtok(copyBuffer, "-"); + while (pModifier != NULL) { + if (dStricmp(pModifier, "shift") == 0) { + pDescriptor->flags |= SI_SHIFT; + } else if (dStricmp(pModifier, "ctrl") == 0) { + pDescriptor->flags |= SI_CTRL; + } else if (dStricmp(pModifier, "alt") == 0) { + pDescriptor->flags |= SI_ALT; + } else if (dStricmp(pModifier, "cmd") == 0) { + pDescriptor->flags |= SI_ALT; + } else if (dStricmp(pModifier, "opt") == 0) { + pDescriptor->flags |= SI_MAC_OPT; + } + + pModifier = dStrtok(NULL, "-"); + } + } else { + // No. + pDescriptor->flags = 0; + pObjectString = copyBuffer; + } + + // Now we need to map the key string to the proper KEY code from event.h + // + AssertFatal(dStrlen(pObjectString) != 0, "Error, no key was specified!"); + + if (dStrlen(pObjectString) == 1) + { + if (dIsDecentChar(*pObjectString)) // includes foreign chars + { + U16 asciiCode = (*pObjectString); + // clear out the FF in upper 8bits for foreign keys?? + asciiCode &= 0xFF; + U16 keyCode = Input::getKeyCode(asciiCode); + if ( keyCode >= KEY_0 ) + { + pDescriptor->eventType = SI_KEY; + pDescriptor->eventCode = keyCode; + return true; + } + else if (dIsalpha(*pObjectString) == true) + { + pDescriptor->eventType = SI_KEY; + pDescriptor->eventCode = KEY_A+dTolower(*pObjectString)-'a'; + return true; + } + else if (dIsdigit(*pObjectString) == true) + { + pDescriptor->eventType = SI_KEY; + pDescriptor->eventCode = KEY_0+(*pObjectString)-'0'; + return true; + } + } + return false; + } + else + { + pDescriptor->eventCode = 0; + // Gotta search through the Ascii table... + for (U16 i = 0; gAsciiMap[i].asciiCode != 0xFFFF; i++) + { + if (dStricmp(pObjectString, gAsciiMap[i].pDescription) == 0) + { + U16 asciiCode = gAsciiMap[i].asciiCode; + U16 keyCode = Input::getKeyCode(asciiCode); + if ( keyCode >= KEY_0 ) + { + pDescriptor->eventType = SI_KEY; + pDescriptor->eventCode = keyCode; + return(true); + + } + else + { + break; + } + } + } + // Didn't find an ascii match. Check the virtual map table + for (U32 j = 0; gVirtualMap[j].code != 0xFFFFFFFF; j++) + { + if (dStricmp(pObjectString, gVirtualMap[j].pDescription) == 0) + { + pDescriptor->eventType = gVirtualMap[j].type; + pDescriptor->eventCode = gVirtualMap[j].code; + return true; + } + } + } + return false; +} + +//------------------------------------------------------------------------------ +ActionMap::Node* ActionMap::getNode(const U32 inDeviceType, const U32 inDeviceInst, + const U32 inModifiers, const U32 inAction,SimObject* object /*= NULL*/) +{ + // DMMTODO - Slow INITIAL implementation. Replace with a faster version... + // + DeviceMap* pDeviceMap = NULL; + U32 i; + for (i = 0; i < mDeviceMaps.size(); i++) + { + if (mDeviceMaps[i]->deviceType == inDeviceType && + mDeviceMaps[i]->deviceInst == inDeviceInst) { + pDeviceMap = mDeviceMaps[i]; + break; + } + } + if (pDeviceMap == NULL) + { + mDeviceMaps.increment(); + mDeviceMaps.last() = new DeviceMap; + pDeviceMap = mDeviceMaps.last(); + + pDeviceMap->deviceInst = inDeviceInst; + pDeviceMap->deviceType = inDeviceType; + } + + for (i = 0; i < pDeviceMap->nodeMap.size(); i++) + { + if (pDeviceMap->nodeMap[i].modifiers == inModifiers && + pDeviceMap->nodeMap[i].action == inAction && + ( (object != NULL) ? object == pDeviceMap->nodeMap[i].object : true )) // Check for an object match if the object exists + { + return &pDeviceMap->nodeMap[i]; + } + } + + // If we're here, the node doesn't exist. create it. + pDeviceMap->nodeMap.increment(); + + Node* pRetNode = &pDeviceMap->nodeMap.last(); + pRetNode->modifiers = inModifiers; + pRetNode->action = inAction; + + pRetNode->flags = 0; + pRetNode->deadZoneBegin = 0.0; + pRetNode->deadZoneEnd = 0.0; + pRetNode->scaleFactor = 1.0; + + pRetNode->consoleFunction = NULL; + pRetNode->makeConsoleCommand = NULL; + pRetNode->breakConsoleCommand = NULL; + + //[neob, 5/7/2007 - #2975] + pRetNode->object = 0; + + return pRetNode; +} + +//------------------------------------------------------------------------------ +void ActionMap::removeNode(const U32 inDeviceType, const U32 inDeviceInst, const U32 inModifiers, const U32 inAction, SimObject* object /*= NULL*/) +{ + // DMMTODO - Slow INITIAL implementation. Replace with a faster version... + // + DeviceMap* pDeviceMap = NULL; + U32 i; + for (i = 0; i < mDeviceMaps.size(); i++) { + if (mDeviceMaps[i]->deviceType == inDeviceType && + mDeviceMaps[i]->deviceInst == inDeviceInst) { + pDeviceMap = mDeviceMaps[i]; + break; + } + } + + if (pDeviceMap == NULL) + return; + + U32 realMods = inModifiers; + if (realMods & SI_SHIFT) + realMods |= SI_SHIFT; + if (realMods & SI_CTRL) + realMods |= SI_CTRL; + if (realMods & SI_ALT) + realMods |= SI_ALT; + if (realMods & SI_MAC_OPT) + realMods |= SI_MAC_OPT; + + for (i = 0; i < pDeviceMap->nodeMap.size(); i++) { + if (pDeviceMap->nodeMap[i].modifiers == realMods && + pDeviceMap->nodeMap[i].action == inAction && + ( (object != NULL) ? object == pDeviceMap->nodeMap[i].object : true )) + { + dFree(pDeviceMap->nodeMap[i].makeConsoleCommand); + dFree(pDeviceMap->nodeMap[i].breakConsoleCommand); + pDeviceMap->nodeMap.erase(i); + } + } +} + +//------------------------------------------------------------------------------ +const ActionMap::Node* ActionMap::findNode(const U32 inDeviceType, const U32 inDeviceInst, + const U32 inModifiers, const U32 inAction) +{ + // DMMTODO - Slow INITIAL implementation. Replace with a faster version... + // + DeviceMap* pDeviceMap = NULL; + U32 i; + for (i = 0; i < mDeviceMaps.size(); i++) + { + if (mDeviceMaps[i]->deviceType == inDeviceType && mDeviceMaps[i]->deviceInst == inDeviceInst) + { + pDeviceMap = mDeviceMaps[i]; + break; + } + } + + if (pDeviceMap == NULL) + return NULL; + + U32 realMods = inModifiers; + if (realMods & SI_SHIFT) + realMods |= SI_SHIFT; + if (realMods & SI_CTRL) + realMods |= SI_CTRL; + if (realMods & SI_ALT) + realMods |= SI_ALT; + if (realMods & SI_MAC_OPT) + realMods |= SI_MAC_OPT; + + for (i = 0; i < pDeviceMap->nodeMap.size(); i++) + { + // Special case for an ANYKEY bind... + if (pDeviceMap->nodeMap[i].action == KEY_ANYKEY + && pDeviceMap->nodeMap[i].modifiers == realMods + && dIsDecentChar(inAction) + && inAction <= U8_MAX) + return &pDeviceMap->nodeMap[i]; + + if (pDeviceMap->nodeMap[i].modifiers == realMods + && pDeviceMap->nodeMap[i].action == inAction) + return &pDeviceMap->nodeMap[i]; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +bool ActionMap::findBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex ) +{ + devMapIndex = 0; + nodeIndex = 0; + return nextBoundNode( function, devMapIndex, nodeIndex ); +} + +bool ActionMap::nextBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex ) +{ + // Loop through all of the existing nodes to find the one mapped to the + // given function: + for ( U32 i = devMapIndex; i < mDeviceMaps.size(); i++ ) + { + const DeviceMap* dvcMap = mDeviceMaps[i]; + + for ( U32 j = nodeIndex; j < dvcMap->nodeMap.size(); j++ ) + { + const Node* node = &dvcMap->nodeMap[j]; + if ( !( node->flags & Node::BindCmd ) && ( dStricmp( function, node->consoleFunction ) == 0 ) ) + { + devMapIndex = i; + nodeIndex = j; + return( true ); + } + } + + nodeIndex = 0; + } + + return( false ); +} + +//------------------------------------------------------------------------------ +bool ActionMap::processUnbind(const char *device, const char *action, SimObject* object /*= NULL*/) +{ + U32 deviceType; + U32 deviceInst; + + if(!getDeviceTypeAndInstance(device, deviceType, deviceInst)) + return false; + EventDescriptor eventDescriptor; + if (!createEventDescriptor(action, &eventDescriptor)) + return false; + + removeNode(deviceType, deviceInst, eventDescriptor.flags,eventDescriptor.eventCode, object); + return true; +} + +//------------------------------------------------------------------------------ +// This function is for the use of the control remapper. +// It will only check against the console function (since all remappable commands are +// bound using bind and not bindCmd). +// +const char* ActionMap::getBinding( const char* command ) +{ + char* returnString = Con::getReturnBuffer( 1024 ); + returnString[0] = 0; + + char buffer[256]; + char deviceBuffer[32]; + char keyBuffer[64]; + + U32 devMapIndex = 0, nodeIndex = 0; + while ( nextBoundNode( command, devMapIndex, nodeIndex ) ) + { + const DeviceMap* deviceMap = mDeviceMaps[devMapIndex]; + + if ( getDeviceName( deviceMap->deviceType, deviceMap->deviceInst, deviceBuffer ) ) + { + const Node* node = &deviceMap->nodeMap[nodeIndex]; + const char* modifierString = getModifierString( node->modifiers ); + + if ( getKeyString( node->action, keyBuffer ) ) + { + dSprintf( buffer, sizeof( buffer ), "%s\t%s%s", deviceBuffer, modifierString, keyBuffer ); + if ( returnString[0] ) + dStrcat( returnString, "\t" ); + dStrcat( returnString, buffer ); + } + } + + ++nodeIndex; + } + + return returnString; +} + +//------------------------------------------------------------------------------ +// This function is for the use of the control remapper. +// The intent of this function is to determine if the given event descriptor is already +// bound in this action map. If so, this function returns the command it is bound to. +// If not, it returns NULL. +// +const char* ActionMap::getCommand( const char* device, const char* action ) +{ + U32 deviceType; + U32 deviceInst; + if ( getDeviceTypeAndInstance( device, deviceType, deviceInst ) ) + { + EventDescriptor eventDescriptor; + if ( createEventDescriptor( action, &eventDescriptor ) ) + { + const ActionMap::Node* mapNode = findNode( deviceType, deviceInst, eventDescriptor.flags, eventDescriptor.eventCode ); + if ( mapNode ) + { + if ( mapNode->flags & Node::BindCmd ) + { + S32 bufferLen = dStrlen( mapNode->makeConsoleCommand ) + dStrlen( mapNode->breakConsoleCommand ) + 2; + char* returnString = Con::getReturnBuffer( bufferLen ); + dSprintf( returnString, bufferLen, "%s\t%s", + ( mapNode->makeConsoleCommand ? mapNode->makeConsoleCommand : "" ), + ( mapNode->breakConsoleCommand ? mapNode->breakConsoleCommand : "" ) ); + return( returnString ); + } + else + return( mapNode->consoleFunction ); + } + } + } + + return( "" ); +} + +//------------------------------------------------------------------------------ +// This function returns whether or not the mapping specified is inverted. +// Obviously, this should only be used for axes. +bool ActionMap::isInverted( const char* device, const char* action ) +{ + U32 deviceType; + U32 deviceInst; + if ( getDeviceTypeAndInstance( device, deviceType, deviceInst ) ) + { + EventDescriptor eventDescriptor; + if ( createEventDescriptor( action, &eventDescriptor ) ) + { + const ActionMap::Node* mapNode = findNode( deviceType, deviceInst, eventDescriptor.flags, eventDescriptor.eventCode ); + if ( mapNode ) + return( mapNode->flags & Node::Inverted ); + } + } + + Con::errorf( "The input event specified by %s %s is not in this action map!", device, action ); + return( false ); +} + +//------------------------------------------------------------------------------ +F32 ActionMap::getScale( const char* device, const char* action ) +{ + U32 deviceType; + U32 deviceInst; + if ( getDeviceTypeAndInstance( device, deviceType, deviceInst ) ) + { + EventDescriptor eventDescriptor; + if ( createEventDescriptor( action, &eventDescriptor ) ) + { + const ActionMap::Node* mapNode = findNode( deviceType, deviceInst, eventDescriptor.flags, eventDescriptor.eventCode ); + if ( mapNode ) + { + if ( mapNode->flags & Node::HasScale ) + return( mapNode->scaleFactor ); + else + return( 1.0f ); + } + } + } + + Con::errorf( "The input event specified by %s %s is not in this action map!", device, action ); + return( 1.0f ); +} + +//------------------------------------------------------------------------------ +const char* ActionMap::getDeadZone( const char* device, const char* action ) +{ + U32 deviceType; + U32 deviceInst; + if ( getDeviceTypeAndInstance( device, deviceType, deviceInst ) ) + { + EventDescriptor eventDescriptor; + if ( createEventDescriptor( action, &eventDescriptor ) ) + { + const ActionMap::Node* mapNode = findNode( deviceType, deviceInst, eventDescriptor.flags, eventDescriptor.eventCode ); + if ( mapNode ) + { + if ( mapNode->flags & Node::HasDeadZone ) + { + char buf[64]; + dSprintf( buf, sizeof( buf ), "%g %g", mapNode->deadZoneBegin, mapNode->deadZoneEnd ); + char* returnString = Con::getReturnBuffer( dStrlen( buf ) + 1 ); + dStrcpy( returnString, buf ); + return( returnString ); + } + else + return( "0 0" ); + } + } + } + + Con::errorf( "The input event specified by %s %s is not in this action map!", device, action ); + return( "" ); +} + +//------------------------------------------------------------------------------ +const char* ActionMap::buildActionString( const InputEventInfo* event ) +{ + const char* modifierString = getModifierString( event->modifier ); + + char objectBuffer[64]; + if ( !getKeyString( event->objInst, objectBuffer ) ) + return( "" ); + + U32 returnLen = dStrlen( modifierString ) + dStrlen( objectBuffer ) + 2; + char* returnString = Con::getReturnBuffer( returnLen ); + dSprintf( returnString, returnLen - 1, "%s%s", modifierString, objectBuffer ); + return( returnString ); +} + +//------------------------------------------------------------------------------ +bool ActionMap::getDeviceTypeAndInstance(const char *pDeviceName, U32 &deviceType, U32 &deviceInstance) +{ + U32 offset = 0; + + if (dStrnicmp(pDeviceName, "keyboard", dStrlen("keyboard")) == 0) + { + deviceType = KeyboardDeviceType; + offset = dStrlen("keyboard"); + } + else if (dStrnicmp(pDeviceName, "mouse", dStrlen("mouse")) == 0) + { + deviceType = MouseDeviceType; + offset = dStrlen("mouse"); + } + else if (dStrnicmp(pDeviceName, "joystick", dStrlen("joystick")) == 0) + { + deviceType = JoystickDeviceType; + offset = dStrlen("joystick"); + } + else if (dStrnicmp(pDeviceName, "gamepad", dStrlen("gamepad")) == 0) + { + deviceType = GamepadDeviceType; + offset = dStrlen("gamepad"); + } + else + { + return false; + } + + if (dStrlen(pDeviceName) > offset) + { + const char* pInst = pDeviceName + offset; + S32 instNum = dAtoi(pInst); + + if (instNum < 0) + deviceInstance = 0; + else + deviceInstance = instNum; + } + else + { + deviceInstance = 0; + } + + return true; +} + +//------------------------------------------------------------------------------ +bool ActionMap::getDeviceName(const U32 deviceType, const U32 deviceInstance, char* buffer) +{ + switch (deviceType) { + case KeyboardDeviceType: + dStrcpy(buffer, "keyboard"); + break; + + case MouseDeviceType: + dSprintf(buffer, 16, "mouse%d", deviceInstance); + break; + + case JoystickDeviceType: + dSprintf(buffer, 16, "joystick%d", deviceInstance); + break; + + case GamepadDeviceType: + dSprintf(buffer, 16, "gamepad%d", deviceInstance); + break; + + default: + Con::errorf( "ActionMap::getDeviceName: unknown device type specified, %d (inst: %d)", deviceType, deviceInstance); + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ +const char* ActionMap::getModifierString(const U32 modifiers) +{ + U32 realModifiers = modifiers; + if ( modifiers & SI_LSHIFT || modifiers & SI_RSHIFT ) + realModifiers |= SI_SHIFT; + if ( modifiers & SI_LCTRL || modifiers & SI_RCTRL ) + realModifiers |= SI_CTRL; + if ( modifiers & SI_LALT || modifiers & SI_RALT ) + realModifiers |= SI_ALT; + if ( modifiers & SI_MAC_LOPT || modifiers & SI_MAC_ROPT ) + realModifiers |= SI_MAC_OPT; + + switch (realModifiers & (SI_SHIFT|SI_CTRL|SI_ALT|SI_MAC_OPT)) + { +#if defined(TORQUE_OS_MAC) + // optional code, to output alt as cmd on mac. + // interpreter sees them as the same... + case (SI_SHIFT|SI_CTRL|SI_ALT): + return "cmd-shift-ctrl "; + + case (SI_SHIFT|SI_ALT): + return "cmd-shift "; + + case (SI_CTRL|SI_ALT): + return "cmd-ctrl "; + + case (SI_ALT): + return "cmd "; +#else + case (SI_SHIFT|SI_CTRL|SI_ALT): + return "shift-ctrl-alt "; + + case (SI_SHIFT|SI_ALT): + return "shift-alt "; + + case (SI_CTRL|SI_ALT): + return "ctrl-alt "; + + case (SI_ALT): + return "alt "; +#endif + case (SI_SHIFT|SI_CTRL): + return "shift-ctrl "; + + case (SI_SHIFT): + return "shift "; + + case (SI_CTRL): + return "ctrl "; + +// plus new mac cases: + case (SI_ALT|SI_SHIFT|SI_CTRL|SI_MAC_OPT): + return "cmd-shift-ctrl-opt "; + + case (SI_ALT|SI_SHIFT|SI_MAC_OPT): + return "cmd-shift-opt "; + + case (SI_ALT|SI_CTRL|SI_MAC_OPT): + return "cmd-ctrl-opt "; + + case (SI_ALT|SI_MAC_OPT): + return "cmd-opt "; + + case (SI_SHIFT|SI_CTRL|SI_MAC_OPT): + return "shift-ctrl-opt "; + + case (SI_SHIFT|SI_MAC_OPT): + return "shift-opt "; + + case (SI_CTRL|SI_MAC_OPT): + return "ctrl-opt "; + + case (SI_MAC_OPT): + return "opt "; + + case 0: + return ""; + + default: + AssertFatal(false, "Error, should never reach the default case in getModifierString"); + return ""; + } +} + +//------------------------------------------------------------------------------ +bool ActionMap::getKeyString(const U32 action, char* buffer) +{ + U16 asciiCode = 0; + + // This is a special case.... numpad keys do have ascii values + // but for the purposes of this method we want to return the + // description from the gVirtualMap. + if ( !( KEY_NUMPAD0 <= action && action <= KEY_NUMPAD9 ) ) + asciiCode = Input::getAscii( action, STATE_LOWER ); + +// if (action >= KEY_A && action <= KEY_Z) { +// buffer[0] = char(action - KEY_A + 'a'); +// buffer[1] = '\0'; +// return true; +// } else if (action >= KEY_0 && action <= KEY_9) { +// buffer[0] = char(action - KEY_0 + '0'); +// buffer[1] = '\0'; + if ( (asciiCode != 0) && dIsDecentChar((char)asciiCode)) + { + for (U32 i = 0; gAsciiMap[i].asciiCode != 0xFFFF; i++) { + if (gAsciiMap[i].asciiCode == asciiCode) + { + dStrcpy(buffer, gAsciiMap[i].pDescription); + return true; + } + } + // Must not have found a string for that ascii code just record the char + buffer[0] = char(asciiCode); + buffer[1] = '\0'; + return true; + } + else + { + if (action >= KEY_A && action <= KEY_Z) + { + buffer[0] = char(action - KEY_A + 'a'); + buffer[1] = '\0'; + return true; + } + else if (action >= KEY_0 && action <= KEY_9) { + buffer[0] = char(action - KEY_0 + '0'); + buffer[1] = '\0'; + return true; + } + for (U32 i = 0; gVirtualMap[i].code != 0xFFFFFFFF; i++) { + if (gVirtualMap[i].code == action) { + dStrcpy(buffer, gVirtualMap[i].pDescription); + return true; + } + } + } + + Con::errorf( "ActionMap::getKeyString: no string for action %d", action ); + return false; +} + +//-------------------------------------------------------------------------- +bool ActionMap::processBindCmd(const char *device, const char *action, const char *makeCmd, const char *breakCmd) +{ + U32 deviceType; + U32 deviceInst; + + if(!getDeviceTypeAndInstance(device, deviceType, deviceInst)) + { + Con::printf("processBindCmd: unknown device: %s", device); + return false; + } + + // Ok, we now have the deviceType and instance. Create an event descriptor + // for the bind... + // + EventDescriptor eventDescriptor; + if (createEventDescriptor(action, &eventDescriptor) == false) { + Con::printf("Could not create a description for binding: %s", action); + return false; + } + + // SI_POV == SI_MOVE, and the POV works fine with bindCmd, so we have to add these manually. + if( ( eventDescriptor.eventCode == SI_XAXIS ) || + ( eventDescriptor.eventCode == SI_YAXIS ) || + ( eventDescriptor.eventCode == SI_ZAXIS ) || + ( eventDescriptor.eventCode == SI_RXAXIS ) || + ( eventDescriptor.eventCode == SI_RYAXIS ) || + ( eventDescriptor.eventCode == SI_RZAXIS ) || + ( eventDescriptor.eventCode == SI_SLIDER ) || + ( eventDescriptor.eventCode == SI_XPOV ) || + ( eventDescriptor.eventCode == SI_YPOV ) || + ( eventDescriptor.eventCode == SI_XPOV2 ) || + ( eventDescriptor.eventCode == SI_YPOV2 ) ) + { + Con::warnf( "ActionMap::processBindCmd - Cannot use 'bindCmd' with a move event type. Use 'bind' instead." ); + return false; + } + + // Create the full bind entry, and place it in the map + // + // DMMTODO + Node* pBindNode = getNode(deviceType, deviceInst, + eventDescriptor.flags, + eventDescriptor.eventCode); + + pBindNode->flags = Node::BindCmd; + pBindNode->deadZoneBegin = 0; + pBindNode->deadZoneEnd = 0; + pBindNode->scaleFactor = 1; + + if( pBindNode->makeConsoleCommand ) + dFree( pBindNode->makeConsoleCommand ); + if( pBindNode->breakConsoleCommand ) + dFree( pBindNode->breakConsoleCommand ); + + if(makeCmd[0]) + pBindNode->makeConsoleCommand = dStrdup(makeCmd); + else + pBindNode->makeConsoleCommand = dStrdup(""); + + if(breakCmd[0]) + pBindNode->breakConsoleCommand = dStrdup(breakCmd); + else + pBindNode->breakConsoleCommand = dStrdup(""); + return true; +} + +//------------------------------------------------------------------------------ +bool ActionMap::processBind(const U32 argc, const char** argv, SimObject* object) +{ + // Ok, the bind will come in the following format: + // [device] [key or button] <[param spec] [param] ...> [fnName] + // + const char* pDeviceName = argv[0]; + const char* pEvent = argv[1]; + const char* pFnName = argv[argc - 1]; + + // Determine the device + U32 deviceType; + U32 deviceInst; + + if(!getDeviceTypeAndInstance(argv[0], deviceType, deviceInst)) + { + Con::printf("processBind: unknown device: %s", pDeviceName); + return false; + } + + // Ok, we now have the deviceType and instance. Create an event descriptor + // for the bind... + // + EventDescriptor eventDescriptor; + if (createEventDescriptor(pEvent, &eventDescriptor) == false) + { + Con::printf("Could not create a description for binding: %s", pEvent); + return false; + } + + // Event has now been described, and device determined. we need now to extract + // any modifiers that the action map will apply to incoming events before + // calling the bound function... + // + // DMMTODO + U32 assignedFlags = 0; + F32 deadZoneBegin = 0.0f; + F32 deadZoneEnd = 0.0f; + F32 scaleFactor = 1.0f; + + if (argc != 3) { + // We have the following: "[DSIR]" [deadZone] [scale] + // + const char* pSpec = argv[2]; + + for (U32 i = 0; pSpec[i] != '\0'; i++) { + switch (pSpec[i]) { + case 'r': case 'R': + assignedFlags |= Node::HasScale; + break; + case 's': case 'S': + assignedFlags |= Node::HasScale; + break; + case 'd': case 'D': + assignedFlags |= Node::HasDeadZone; + break; + case 'i': case 'I': + assignedFlags |= Node::Inverted; + break; + case 'n': case 'N': + assignedFlags |= Node::NonLinear; + break; + + default: + AssertFatal(false, avar("Misunderstood specifier in bind (spec string: %s)", + pSpec)); + } + } + + // Ok, we have the flags. Scan the dead zone and scale, if any. + // + U32 curArg = 3; + if (assignedFlags & Node::HasDeadZone) { + dSscanf(argv[curArg], "%g %g", &deadZoneBegin, &deadZoneEnd); + curArg++; + } + if (assignedFlags & Node::HasScale) { + scaleFactor = dAtof(argv[curArg]); + curArg++; + } + + if (curArg != (argc - 1)) { + AssertFatal(curArg == (argc - 1), "error in bind spec somewhere..."); + Con::printf("Improperly specified bind for key: %s", argv[2]); + return false; + } + } + + // Ensure that the console function is properly specified? + // + // DMMTODO + + // Create the full bind entry, and place it in the map + // + // DMMTODO + Node* pBindNode = getNode(deviceType, deviceInst, + eventDescriptor.flags, + eventDescriptor.eventCode, object); + + pBindNode->flags = assignedFlags; + pBindNode->deadZoneBegin = deadZoneBegin; + pBindNode->deadZoneEnd = deadZoneEnd; + pBindNode->scaleFactor = scaleFactor; + pBindNode->object = object; + pBindNode->consoleFunction = StringTable->insert(pFnName); + + return true; +} + +//------------------------------------------------------------------------------ +bool ActionMap::processAction(const InputEventInfo* pEvent) +{ + // Suppress excluded input events, like alt-tab. + if(Platform::checkKeyboardInputExclusion(pEvent)) + return false; + + static const char *argv[2]; + if (pEvent->action == SI_MAKE) { + const Node* pNode = findNode(pEvent->deviceType, pEvent->deviceInst, + pEvent->modifier, pEvent->objInst); + + if (pNode == NULL) { + // Check to see if we clear the modifiers, do we find an action? + if (pEvent->modifier != 0) + pNode = findNode(pEvent->deviceType, pEvent->deviceInst, + 0, pEvent->objInst); + + if (pNode == NULL) + return false; + } + + // Enter the break into the table if this is a make event... + // Do this now rather than after command is processed because + // command might add a binding which can move the vector of nodes. + enterBreakEvent(pEvent, pNode); + + // Whadda ya know, we have this bound. Set up, and call the console + // function associated with it... + // + F32 value = pEvent->fValue; + if (pNode->flags & Node::Ranged) { + value = (value * 2.0f) - 1.0f; + if (pNode->flags & Node::Inverted) + value *= -1.0f; + } else { + if (pNode->flags & Node::Inverted) + value = 1.0f - value; + } + + if (pNode->flags & Node::HasScale) + value *= pNode->scaleFactor; + + if ( pNode->flags & Node::HasDeadZone ) + { + if ( value >= pNode->deadZoneBegin && value <= pNode->deadZoneEnd ) + value = 0.0f; + else + { + if( value > 0 ) + value = ( value - pNode->deadZoneBegin ) * ( 1.f / ( 1.f - pNode->deadZoneBegin ) ); + else + value = ( value + pNode->deadZoneBegin ) * ( 1.f / ( 1.f - pNode->deadZoneBegin ) ); + } + } + + if( pNode->flags & Node::NonLinear ) + value = ( value < 0.f ? -1.f : 1.f ) * mPow( mFabs( value ), CONST_E ); + + // Ok, we're all set up, call the function. + if(pNode->flags & Node::BindCmd) + { + // it's a bind command + if(pNode->makeConsoleCommand) + Con::evaluate(pNode->makeConsoleCommand); + } + else if ( pNode->consoleFunction[0] ) + { + argv[0] = pNode->consoleFunction; + argv[1] = Con::getFloatArg(value); + if (pNode->object) + Con::executef(pNode->object, argv[0], argv[1]); + else + Con::execute(2, argv); + } + return true; + } else if (pEvent->action == SI_MOVE) { + if (pEvent->deviceType == MouseDeviceType) { + const Node* pNode = findNode(pEvent->deviceType, pEvent->deviceInst, + pEvent->modifier, pEvent->objInst); + + if (pNode == NULL) + { + // Check to see if we clear the modifiers, do we find an action? + if (pEvent->modifier != 0) + pNode = findNode(pEvent->deviceType, pEvent->deviceInst, + 0, pEvent->objInst); + + if (pNode == NULL) + return false; + } + + // "Do nothing" bind: + if ( !pNode->consoleFunction[0] ) + return( true ); + + // Whadda ya know, we have this bound. Set up, and call the console + // function associated with it. Mouse events ignore range and dead + // zone params. + // + F32 value = pEvent->fValue; + if (pNode->flags & Node::Inverted) + value *= -1.0f; + if (pNode->flags & Node::HasScale) + value *= pNode->scaleFactor; + + // Ok, we're all set up, call the function. + argv[0] = pNode->consoleFunction; + argv[1] = Con::getFloatArg(value); + if (pNode->object) + Con::executef(pNode->object, argv[0], argv[1]); + else + Con::execute(2, argv); + + return true; + } + else if ( pEvent->deviceType == JoystickDeviceType + || pEvent->deviceType == GamepadDeviceType + ) + { + // Joystick events... + const Node* pNode = findNode( pEvent->deviceType, pEvent->deviceInst, + pEvent->modifier, pEvent->objInst ); + + if ( pNode == NULL ) + { + // Check to see if we clear the modifiers, do we find an action? + if (pEvent->modifier != 0) + pNode = findNode( pEvent->deviceType, pEvent->deviceInst, + 0, pEvent->objInst ); + + if ( pNode == NULL ) + return false; + } + + // "Do nothing" bind: + if ( !pNode->consoleFunction[0] ) + return( true ); + + // Whadda ya know, we have this bound. Set up, and call the console + // function associated with it. Joystick move events are the same as mouse + // move events except that they don't ignore dead zone. + // + F32 value = pEvent->fValue; + if ( pNode->flags & Node::Inverted ) + value *= -1.0f; + + if ( pNode->flags & Node::HasScale ) + value *= pNode->scaleFactor; + + if ( pNode->flags & Node::HasDeadZone ) + { + if ( value >= pNode->deadZoneBegin && + value <= pNode->deadZoneEnd ) + value = 0.0f; + else + { + if( value > 0 ) + value = ( value - pNode->deadZoneBegin ) * ( 1.f / ( 1.f - pNode->deadZoneBegin ) ); + else + value = ( value + pNode->deadZoneBegin ) * ( 1.f / ( 1.f - pNode->deadZoneBegin ) ); + } + } + + if( pNode->flags & Node::NonLinear ) + value = ( value < 0.f ? -1.f : 1.f ) * mPow( mFabs( value ), CONST_E ); + + // Ok, we're all set up, call the function. + argv[0] = pNode->consoleFunction; + argv[1] = Con::getFloatArg( value ); + if (pNode->object) + Con::executef(pNode->object, argv[0], argv[1]); + else + Con::execute(2, argv); + + return true; + } + } + else if (pEvent->action == SI_BREAK) + { + return checkBreakTable(pEvent); + } + + return false; +} + +//------------------------------------------------------------------------------ +void ActionMap::enterBreakEvent(const InputEventInfo* pEvent, const Node* pNode) +{ + // There aren't likely to be many breaks outstanding at any one given time, + // so a simple linear search is probably sufficient. Note that the break table + // is static to the class, all breaks are directed to the action map that received + // the make. + // + S32 entry = -1; + for (U32 i = 0; i < smBreakTable.size(); i++) { + if (smBreakTable[i].deviceType == U32(pEvent->deviceType) && + smBreakTable[i].deviceInst == U32(pEvent->deviceInst) && + smBreakTable[i].objInst == U32(pEvent->objInst)) { + // Match. + entry = i; + break; + } + } + if (entry == -1) { + smBreakTable.increment(); + entry = smBreakTable.size() - 1; + + smBreakTable[entry].deviceType = pEvent->deviceType; + smBreakTable[entry].deviceInst = pEvent->deviceInst; + smBreakTable[entry].objInst = pEvent->objInst; + } + + // Ok, we now have the entry, and know that the device desc. and the objInst match. + // Copy out the node information... + // + smBreakTable[entry].object = pNode->object; + smBreakTable[entry].consoleFunction = pNode->consoleFunction; + if(pNode->breakConsoleCommand) + smBreakTable[entry].breakConsoleCommand = dStrdup(pNode->breakConsoleCommand); + else + smBreakTable[entry].breakConsoleCommand = NULL; + + smBreakTable[entry].flags = pNode->flags; + smBreakTable[entry].deadZoneBegin = pNode->deadZoneBegin; + smBreakTable[entry].deadZoneEnd = pNode->deadZoneEnd; + smBreakTable[entry].scaleFactor = pNode->scaleFactor; +} + +//------------------------------------------------------------------------------ +bool ActionMap::checkBreakTable(const InputEventInfo* pEvent) +{ + for (U32 i = 0; i < smBreakTable.size(); i++) + { + if (smBreakTable[i].deviceType == U32(pEvent->deviceType) && + smBreakTable[i].deviceInst == U32(pEvent->deviceInst) && + smBreakTable[i].objInst == U32(pEvent->objInst)) + { + fireBreakEvent(i, pEvent->fValue); + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------ +bool ActionMap::handleEvent(const InputEventInfo* pEvent) +{ + // Interate through the ActionMapSet until we get a map that + // handles the event or we run out of maps... + // + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + AssertFatal(pActionMapSet && pActionMapSet->size() != 0, + "error, no ActiveMapSet or no global action map..."); + + for (SimSet::iterator itr = pActionMapSet->end() - 1; + itr > pActionMapSet->begin(); itr--) { + ActionMap* pMap = static_cast(*itr); + if (pMap->processAction(pEvent) == true) + return true; + } + + return false; +} + +//------------------------------------------------------------------------------ +bool ActionMap::handleEventGlobal(const InputEventInfo* pEvent) +{ + // Interate through the ActionMapSet until we get a map that + // handles the event or we run out of maps... + // + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + AssertFatal(pActionMapSet && pActionMapSet->size() != 0, + "error, no ActiveMapSet or no global action map..."); + + return ((ActionMap*)pActionMapSet->first())->processAction(pEvent); +} + +bool ActionMap::checkAsciiGlobal( U16 key, U32 modifiers ) +{ + // Does this ascii map to a key? + U16 keyCode = Input::getKeyCode(key); + if(keyCode == 0) + return false; + + // Grab the action map set. + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + AssertFatal(pActionMapSet && pActionMapSet->size() != 0, + "error, no ActiveMapSet or no global action map..."); + + // Grab the device maps for the first ActionMap. + Vector &maps = ((ActionMap*)pActionMapSet->first())->mDeviceMaps; + + // Find the keyboard. + DeviceMap *keyMap = NULL; + for(S32 i=0; ideviceType == KeyboardDeviceType) + { + keyMap = maps[i]; + break; + } + } + + if(!keyMap) + return false; + + // Normalize modifiers. + U32 realMods = modifiers; + if (realMods & SI_SHIFT) + realMods |= SI_SHIFT; + if (realMods & SI_CTRL) + realMods |= SI_CTRL; + if (realMods & SI_ALT) + realMods |= SI_ALT; + if (realMods & SI_MAC_OPT) + realMods |= SI_MAC_OPT; + + // Now find a matching node, if there is one. + for(S32 i=0; inodeMap.size(); i++) + { + Node &n = keyMap->nodeMap[i]; + + if(n.action == keyCode && (n.modifiers == modifiers)) + return true; + } + + return false; +} + +void ActionMap::clearAllBreaks() +{ + while(smBreakTable.size()) + fireBreakEvent(smBreakTable.size()-1); +} + +void ActionMap::fireBreakEvent( U32 i, F32 fValue ) +{ + // Match. Issue the break event... + // + F32 value = fValue; + if (smBreakTable[i].flags & Node::Ranged) { + value = (value * 2.0f) - 1.0f; + if (smBreakTable[i].flags & Node::Inverted) + value *= -1.0f; + } else { + if (smBreakTable[i].flags & Node::Inverted) + value = 1.0f - value; + } + + if (smBreakTable[i].flags & Node::HasScale) + value *= smBreakTable[i].scaleFactor; + + if (smBreakTable[i].flags & Node::HasDeadZone) + { + if (value >= smBreakTable[i].deadZoneBegin && + value <= smBreakTable[i].deadZoneEnd) + value = 0.0f; + else + { + if( value > 0 ) + value = ( value - smBreakTable[i].deadZoneBegin ) * ( 1.f / ( 1.f - smBreakTable[i].deadZoneBegin ) ); + else + value = ( value + smBreakTable[i].deadZoneBegin ) * ( 1.f / ( 1.f - smBreakTable[i].deadZoneBegin ) ); + } + } + + if( smBreakTable[i].flags & Node::NonLinear ) + value = ( value < 0.f ? -1.f : 1.f ) * mPow( mFabs( value ), CONST_E ); + + // Ok, we're all set up, call the function. + if(smBreakTable[i].consoleFunction) + { + if ( smBreakTable[i].consoleFunction[0] ) + { + static const char *argv[2]; + argv[0] = smBreakTable[i].consoleFunction; + argv[1] = Con::getFloatArg(value); + if (smBreakTable[i].object) + Con::executef(smBreakTable[i].object, argv[0], argv[1]); + else + Con::execute(2, argv); + } + } + else if(smBreakTable[i].breakConsoleCommand) + { + Con::evaluate(smBreakTable[i].breakConsoleCommand); + dFree(smBreakTable[i].breakConsoleCommand); + } + smBreakTable.erase(i); +} + +//------------------------------------------------------------------------------ + +ConsoleMethod( ActionMap, bind, void, 5, 10, "actionMap.bind( device, action, [modifier spec, mod...], command )" ) +{ + object->processBind( argc - 2, argv + 2, NULL ); +} + +ConsoleMethod( ActionMap, bindObj, void, 6, 11, "(device, action, [modifier spec, mod...], command, object)") +{ + SimObject* obj = Sim::findObject(argv[argc - 1]); + object->processBind( argc - 3, argv + 2, obj ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, bindCmd, void, 6, 6, "actionMap.bindCmd( device, action, makeCmd, breakCmd )" ) +{ + TORQUE_UNUSED(argc); + object->processBindCmd( argv[2], argv[3], argv[4], argv[5] ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, unbind, void, 4, 4, "actionMap.unbind( device, action )" ) +{ + TORQUE_UNUSED(argc); + object->processUnbind( argv[2], argv[3] ); +} +ConsoleMethod( ActionMap, unbindObj, void, 5, 5, "(device, action, object)") +{ + SimObject* obj = Sim::findObject(argv[4]); + object->processUnbind( argv[2], argv[3], obj ); +} + + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, save, void, 2, 4, "actionMap.save( [fileName], [append] )" ) +{ + const char* fileName = argc > 2 ? argv[2] : NULL; + bool append = argc > 3 ? dAtob(argv[3]) : false; + + char buffer[1024]; + + if(fileName) + { + if(Con::expandScriptFilename(buffer, sizeof(buffer), fileName)) + fileName = buffer; + } + + object->dumpActionMap( fileName, append ); +} + +//------------------------------------------------------------------------------ +ConsoleFunction( getCurrentActionMap, const char *, 1, 1, "getCurrentActionMap()" ) +{ + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + + SimObject *actionMap = pActionMapSet->last(); + + char* retBuf = Con::getReturnBuffer(128); + dSprintf(retBuf, 128, "%d", actionMap->getId() ); + return retBuf; + +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, push, void, 2, 2, "actionMap.push()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + pActionMapSet->pushObject( object ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, pop, void, 2, 2, "actionMap.pop()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + SimSet* pActionMapSet = Sim::getActiveActionMapSet(); + pActionMapSet->removeObject( object ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getBinding, const char*, 3, 3, "actionMap.getBinding( command )" ) +{ + TORQUE_UNUSED(argc); + return( object->getBinding( argv[2] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getCommand, const char*, 4, 4, "actionMap.getCommand( device, action )" ) +{ + TORQUE_UNUSED(argc); + return( object->getCommand( argv[2], argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, isInverted, bool, 4, 4, "actionMap.isInverted( device, action )" ) +{ + TORQUE_UNUSED(argc); + return( object->isInverted( argv[2], argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getScale, F32, 4, 4, "actionMap.getScale( device, action )" ) +{ + TORQUE_UNUSED(argc); + return( object->getScale( argv[2], argv[3] ) ); +} + +//------------------------------------------------------------------------------ +ConsoleMethod( ActionMap, getDeadZone, const char*, 4, 4, "actionMap.getDeadZone( device, action )" ) +{ + TORQUE_UNUSED(argc); + return( object->getDeadZone( argv[2], argv[3] ) ); +} + + +//------------------------------------------------------------------------------ +//-------------------------------------- Key code to string mapping +// TODO: Add most obvious aliases... +// +CodeMapping gVirtualMap[] = +{ + //-------------------------------------- KEYBOARD EVENTS + // + { "backspace", SI_KEY, KEY_BACKSPACE }, + { "tab", SI_KEY, KEY_TAB }, + + { "return", SI_KEY, KEY_RETURN }, + { "enter", SI_KEY, KEY_RETURN }, + + { "shift", SI_KEY, KEY_SHIFT }, + { "ctrl", SI_KEY, KEY_CONTROL }, + { "alt", SI_KEY, KEY_ALT }, + { "pause", SI_KEY, KEY_PAUSE }, + { "capslock", SI_KEY, KEY_CAPSLOCK }, + + { "escape", SI_KEY, KEY_ESCAPE }, + + { "space", SI_KEY, KEY_SPACE }, + { "pagedown", SI_KEY, KEY_PAGE_DOWN }, + { "pageup", SI_KEY, KEY_PAGE_UP }, + { "end", SI_KEY, KEY_END }, + { "home", SI_KEY, KEY_HOME }, + { "left", SI_KEY, KEY_LEFT }, + { "up", SI_KEY, KEY_UP }, + { "right", SI_KEY, KEY_RIGHT }, + { "down", SI_KEY, KEY_DOWN }, + { "print", SI_KEY, KEY_PRINT }, + { "insert", SI_KEY, KEY_INSERT }, + { "delete", SI_KEY, KEY_DELETE }, + { "help", SI_KEY, KEY_HELP }, + + { "win_lwindow", SI_KEY, KEY_WIN_LWINDOW }, + { "win_rwindow", SI_KEY, KEY_WIN_RWINDOW }, + { "win_apps", SI_KEY, KEY_WIN_APPS }, + + { "cmd", SI_KEY, KEY_ALT }, + { "opt", SI_KEY, KEY_MAC_OPT }, + { "lopt", SI_KEY, KEY_MAC_LOPT }, + { "ropt", SI_KEY, KEY_MAC_ROPT }, + + { "numpad0", SI_KEY, KEY_NUMPAD0 }, + { "numpad1", SI_KEY, KEY_NUMPAD1 }, + { "numpad2", SI_KEY, KEY_NUMPAD2 }, + { "numpad3", SI_KEY, KEY_NUMPAD3 }, + { "numpad4", SI_KEY, KEY_NUMPAD4 }, + { "numpad5", SI_KEY, KEY_NUMPAD5 }, + { "numpad6", SI_KEY, KEY_NUMPAD6 }, + { "numpad7", SI_KEY, KEY_NUMPAD7 }, + { "numpad8", SI_KEY, KEY_NUMPAD8 }, + { "numpad9", SI_KEY, KEY_NUMPAD9 }, + { "numpadmult", SI_KEY, KEY_MULTIPLY }, + { "numpadadd", SI_KEY, KEY_ADD }, + { "numpadsep", SI_KEY, KEY_SEPARATOR }, + { "numpadminus", SI_KEY, KEY_SUBTRACT }, + { "numpaddecimal", SI_KEY, KEY_DECIMAL }, + { "numpaddivide", SI_KEY, KEY_DIVIDE }, + { "numpadenter", SI_KEY, KEY_NUMPADENTER }, + + { "f1", SI_KEY, KEY_F1 }, + { "f2", SI_KEY, KEY_F2 }, + { "f3", SI_KEY, KEY_F3 }, + { "f4", SI_KEY, KEY_F4 }, + { "f5", SI_KEY, KEY_F5 }, + { "f6", SI_KEY, KEY_F6 }, + { "f7", SI_KEY, KEY_F7 }, + { "f8", SI_KEY, KEY_F8 }, + { "f9", SI_KEY, KEY_F9 }, + { "f10", SI_KEY, KEY_F10 }, + { "f11", SI_KEY, KEY_F11 }, + { "f12", SI_KEY, KEY_F12 }, + { "f13", SI_KEY, KEY_F13 }, + { "f14", SI_KEY, KEY_F14 }, + { "f15", SI_KEY, KEY_F15 }, + { "f16", SI_KEY, KEY_F16 }, + { "f17", SI_KEY, KEY_F17 }, + { "f18", SI_KEY, KEY_F18 }, + { "f19", SI_KEY, KEY_F19 }, + { "f20", SI_KEY, KEY_F20 }, + { "f21", SI_KEY, KEY_F21 }, + { "f22", SI_KEY, KEY_F22 }, + { "f23", SI_KEY, KEY_F23 }, + { "f24", SI_KEY, KEY_F24 }, + + { "numlock", SI_KEY, KEY_NUMLOCK }, + { "scrolllock", SI_KEY, KEY_SCROLLLOCK }, + + { "lshift", SI_KEY, KEY_LSHIFT }, + { "rshift", SI_KEY, KEY_RSHIFT }, + { "lcontrol", SI_KEY, KEY_LCONTROL }, + { "rcontrol", SI_KEY, KEY_RCONTROL }, + { "lalt", SI_KEY, KEY_LALT }, + { "ralt", SI_KEY, KEY_RALT }, + { "tilde", SI_KEY, KEY_TILDE }, + + { "minus", SI_KEY, KEY_MINUS }, + { "equals", SI_KEY, KEY_EQUALS }, + { "lbracket", SI_KEY, KEY_LBRACKET }, + { "rbracket", SI_KEY, KEY_RBRACKET }, + { "backslash", SI_KEY, KEY_BACKSLASH }, + { "semicolon", SI_KEY, KEY_SEMICOLON }, + { "apostrophe", SI_KEY, KEY_APOSTROPHE }, + { "comma", SI_KEY, KEY_COMMA }, + { "period", SI_KEY, KEY_PERIOD }, + { "slash", SI_KEY, KEY_SLASH }, + { "lessthan", SI_KEY, KEY_OEM_102 }, + + //-------------------------------------- BUTTON EVENTS + // Joystick/Mouse buttons + { "button0", SI_BUTTON, KEY_BUTTON0 }, + { "button1", SI_BUTTON, KEY_BUTTON1 }, + { "button2", SI_BUTTON, KEY_BUTTON2 }, + { "button3", SI_BUTTON, KEY_BUTTON3 }, + { "button4", SI_BUTTON, KEY_BUTTON4 }, + { "button5", SI_BUTTON, KEY_BUTTON5 }, + { "button6", SI_BUTTON, KEY_BUTTON6 }, + { "button7", SI_BUTTON, KEY_BUTTON7 }, + { "button8", SI_BUTTON, KEY_BUTTON8 }, + { "button9", SI_BUTTON, KEY_BUTTON9 }, + { "button10", SI_BUTTON, KEY_BUTTON10 }, + { "button11", SI_BUTTON, KEY_BUTTON11 }, + { "button12", SI_BUTTON, KEY_BUTTON12 }, + { "button13", SI_BUTTON, KEY_BUTTON13 }, + { "button14", SI_BUTTON, KEY_BUTTON14 }, + { "button15", SI_BUTTON, KEY_BUTTON15 }, + { "button16", SI_BUTTON, KEY_BUTTON16 }, + { "button17", SI_BUTTON, KEY_BUTTON17 }, + { "button18", SI_BUTTON, KEY_BUTTON18 }, + { "button19", SI_BUTTON, KEY_BUTTON19 }, + { "button20", SI_BUTTON, KEY_BUTTON20 }, + { "button21", SI_BUTTON, KEY_BUTTON21 }, + { "button22", SI_BUTTON, KEY_BUTTON22 }, + { "button23", SI_BUTTON, KEY_BUTTON23 }, + { "button24", SI_BUTTON, KEY_BUTTON24 }, + { "button25", SI_BUTTON, KEY_BUTTON25 }, + { "button26", SI_BUTTON, KEY_BUTTON26 }, + { "button27", SI_BUTTON, KEY_BUTTON27 }, + { "button28", SI_BUTTON, KEY_BUTTON28 }, + { "button29", SI_BUTTON, KEY_BUTTON29 }, + { "button30", SI_BUTTON, KEY_BUTTON30 }, + { "button31", SI_BUTTON, KEY_BUTTON31 }, + + //-------------------------------------- MOVE EVENTS + // Mouse/Joystick axes: + { "xaxis", SI_AXIS, SI_XAXIS }, + { "yaxis", SI_AXIS, SI_YAXIS }, + { "zaxis", SI_AXIS, SI_ZAXIS }, + { "rxaxis", SI_AXIS, SI_RXAXIS }, + { "ryaxis", SI_AXIS, SI_RYAXIS }, + { "rzaxis", SI_AXIS, SI_RZAXIS }, + { "slider", SI_AXIS, SI_SLIDER }, + + //-------------------------------------- POV EVENTS + // Joystick POV: + { "xpov", SI_POV, SI_XPOV }, + { "ypov", SI_POV, SI_YPOV }, + { "upov", SI_POV, SI_UPOV }, + { "dpov", SI_POV, SI_DPOV }, + { "lpov", SI_POV, SI_LPOV }, + { "rpov", SI_POV, SI_RPOV }, + { "xpov2", SI_POV, SI_XPOV2 }, + { "ypov2", SI_POV, SI_YPOV2 }, + { "upov2", SI_POV, SI_UPOV2 }, + { "dpov2", SI_POV, SI_DPOV2 }, + { "lpov2", SI_POV, SI_LPOV2 }, + { "rpov2", SI_POV, SI_RPOV2 }, + +#if defined( TORQUE_OS_WIN32 ) || defined( TORQUE_OS_XENON ) + //-------------------------------------- XINPUT EVENTS + // Controller connect / disconnect: + { "connect", SI_BUTTON, XI_CONNECT }, + + // L & R Thumbsticks: + { "thumblx", SI_AXIS, XI_THUMBLX }, + { "thumbly", SI_AXIS, XI_THUMBLY }, + { "thumbrx", SI_AXIS, XI_THUMBRX }, + { "thumbry", SI_AXIS, XI_THUMBRY }, + + // L & R Triggers: + { "triggerl", SI_AXIS, XI_LEFT_TRIGGER }, + { "triggerr", SI_AXIS, XI_RIGHT_TRIGGER }, + + // DPAD Buttons: + { "dpadu", SI_BUTTON, SI_UPOV }, + { "dpadd", SI_BUTTON, SI_DPOV }, + { "dpadl", SI_BUTTON, SI_LPOV }, + { "dpadr", SI_BUTTON, SI_RPOV }, + + // START & BACK Buttons: + { "btn_start", SI_BUTTON, XI_START }, + { "btn_back", SI_BUTTON, XI_BACK }, + + // L & R Thumbstick Buttons: + { "btn_lt", SI_BUTTON, XI_LEFT_THUMB }, + { "btn_rt", SI_BUTTON, XI_RIGHT_THUMB }, + + // L & R Shoulder Buttons: + { "btn_l", SI_BUTTON, XI_LEFT_SHOULDER }, + { "btn_r", SI_BUTTON, XI_RIGHT_SHOULDER }, + + // Primary buttons: + { "btn_a", SI_BUTTON, XI_A }, + { "btn_b", SI_BUTTON, XI_B }, + { "btn_x", SI_BUTTON, XI_X }, + { "btn_y", SI_BUTTON, XI_Y }, +#endif + + //-------------------------------------- MISCELLANEOUS EVENTS + // + + { "anykey", SI_KEY, KEY_ANYKEY }, + { "nomatch", SI_UNKNOWN, (InputObjectInstances)0xFFFFFFFF } +}; + +AsciiMapping gAsciiMap[] = +{ + //--- KEYBOARD EVENTS + // + { "space", 0x0020 }, + //{ "exclamation", 0x0021 }, + { "doublequote", 0x0022 }, + //{ "pound", 0x0023 }, + //{ "ampersand", 0x0026 }, + { "apostrophe", 0x0027 }, + //{ "lparen", 0x0028 }, + //{ "rparen", 0x0029 }, + { "comma", 0x002c }, + { "minus", 0x002d }, + { "period", 0x002e }, + //{ "slash", 0x002f }, + //{ "colon", 0x003a }, + //{ "semicolon", 0x003b }, + //{ "lessthan", 0x003c }, + //{ "equals", 0x003d }, + //{ "morethan", 0x003e }, + //{ "lbracket", 0x005b }, + { "backslash", 0x005c }, + //{ "rbracket", 0x005d }, + //{ "circumflex", 0x005e }, + //{ "underscore", 0x005f }, + { "grave", 0x0060 }, + //{ "tilde", 0x007e }, + //{ "vertbar", 0x007c }, + //{ "exclamdown", 0x00a1 }, + //{ "cent", 0x00a2 }, + //{ "sterling", 0x00a3 }, + //{ "currency", 0x00a4 }, + //{ "brokenbar", 0x00a6 }, + //{ "ring", 0x00b0 }, + //{ "plusminus", 0x00b1 }, + { "super2", 0x00b2 }, + { "super3", 0x00b3 }, + { "acute", 0x00b4 }, + //{ "mu", 0x00b5 }, + //{ "ordmasculine", 0x00ba }, + //{ "questiondown", 0x00bf }, + //{ "gemandbls", 0x00df }, + //{ "agrave", 0x00e0 }, + //{ "aacute", 0x00e1 }, + //{ "acircumflex", 0x00e2 }, + //{ "atilde", 0x00e3 }, + //{ "adieresis", 0x00e4 }, + //{ "aring", 0x00e5 }, + //{ "ae", 0x00e6 }, + //{ "ccedille", 0x00e7 }, + //{ "egrave", 0x00e8 }, + //{ "eacute", 0x00e9 }, + //{ "ecircumflex", 0x00ea }, + //{ "edieresis", 0x00eb }, + //{ "igrave", 0x00ec }, + //{ "iacute", 0x00ed }, + //{ "icircumflex", 0x00ee }, + //{ "idieresis", 0x00ef }, + //{ "ntilde", 0x00f1 }, + //{ "ograve", 0x00f2 }, + //{ "oacute", 0x00f3 }, + //{ "ocircumflex", 0x00f4 }, + //{ "otilde", 0x00f5 }, + //{ "odieresis", 0x00f6 }, + //{ "divide", 0x00f7 }, + //{ "oslash", 0x00f8 }, + //{ "ugrave", 0x00f9 }, + //{ "uacute", 0x00fa }, + //{ "ucircumflex", 0x00fb }, + //{ "udieresis", 0x00fc }, + //{ "ygrave", 0x00fd }, + //{ "thorn", 0x00fe }, + //{ "ydieresis", 0x00ff }, + { "nomatch", 0xFFFF } +}; diff --git a/sim/actionMap.h b/sim/actionMap.h new file mode 100644 index 0000000..9443708 --- /dev/null +++ b/sim/actionMap.h @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _ACTIONMAP_H_ +#define _ACTIONMAP_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +struct InputEventInfo; + +struct EventDescriptor +{ + U8 flags; ///< Combination of any modifier flags. + U8 eventType; ///< SI_KEY, etc. + U16 eventCode; ///< From event.h +}; + +/// Map raw inputs to a variety of actions. This is used for all keymapping +/// in the engine. +/// @see ActionMap::Node +class ActionMap : public SimObject +{ + typedef SimObject Parent; + + protected: + bool onAdd(); + + struct Node { + U32 modifiers; + U32 action; + + enum Flags { + Ranged = BIT(0), ///< Ranged input. + HasScale = BIT(1), ///< Scaled input. + HasDeadZone = BIT(2), ///< Dead zone is present. + Inverted = BIT(3), ///< Input is inverted. + NonLinear = BIT(4), ///< Input should be re-fit to a non-linear scale + BindCmd = BIT(5) ///< Bind a console command to this. + }; + + U32 flags; ///< @see Node::Flags + F32 deadZoneBegin; + F32 deadZoneEnd; + F32 scaleFactor; + + SimObject* object; ///< Object to call consoleFunction on. + StringTableEntry consoleFunction; ///< Console function to call with new values. + + char *makeConsoleCommand; ///< Console command to execute when we make this command. + char *breakConsoleCommand; ///< Console command to execute when we break this command. + }; + + /// Used to represent a devices. + struct DeviceMap + { + U32 deviceType; + U32 deviceInst; + + Vector nodeMap; + DeviceMap() { + VECTOR_SET_ASSOCIATION(nodeMap); + } + ~DeviceMap(); + }; + struct BreakEntry + { + U32 deviceType; + U32 deviceInst; + U32 objInst; + SimObject* object; + StringTableEntry consoleFunction; + char *breakConsoleCommand; + + // It's possible that the node could be deleted (unlikely, but possible, + // so we replicate the node flags here... + // + U32 flags; + F32 deadZoneBegin; + F32 deadZoneEnd; + F32 scaleFactor; + }; + + + Vector mDeviceMaps; + static Vector smBreakTable; + + // Find: return NULL if not found in current map, Get: create if not + // found. + const Node* findNode(const U32 inDeviceType, const U32 inDeviceInst, + const U32 inModifiers, const U32 inAction); + bool findBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex ); + bool nextBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex ); + Node* getNode(const U32 inDeviceType, const U32 inDeviceInst, + const U32 inModifiers, const U32 inAction, + SimObject* object = NULL); + + void removeNode(const U32 inDeviceType, const U32 inDeviceInst, + const U32 inModifiers, const U32 inAction, + SimObject* object = NULL); + + void enterBreakEvent(const InputEventInfo* pEvent, const Node* pNode); + + static const char* getModifierString(const U32 modifiers); + + /// Pass index to a break entry, and this function will fire it off. + static void fireBreakEvent(U32 idx, F32 value = 0.f); + + public: + ActionMap(); + ~ActionMap(); + + void dumpActionMap(const char* fileName, const bool append) const; + + static bool createEventDescriptor(const char* pEventString, EventDescriptor* pDescriptor); + + bool processBind(const U32 argc, const char** argv, SimObject* object = NULL); + bool processBindCmd(const char *device, const char *action, const char *makeCmd, const char *breakCmd); + bool processUnbind(const char *device, const char *action, SimObject* object = NULL); + + /// @name Console Interface Functions + /// @{ + const char* getBinding ( const char* command ); ///< Find what the given command is bound to. + const char* getCommand ( const char* device, const char* action ); ///< Find what command is bound to the given event descriptor . + bool isInverted ( const char* device, const char* action ); + F32 getScale ( const char* device, const char* action ); + const char* getDeadZone( const char* device, const char* action ); + /// @} + + + static bool getKeyString(const U32 action, char* buffer); + static bool getDeviceName(const U32 deviceType, const U32 deviceInstance, char* buffer); + static const char* buildActionString( const InputEventInfo* event ); + + bool processAction(const InputEventInfo*); + + static bool checkBreakTable(const InputEventInfo*); + static bool handleEvent(const InputEventInfo*); + static bool handleEventGlobal(const InputEventInfo*); + + /// Called when we lose focus, to make sure we have no dangling inputs. + /// + /// This fires a break event for every currently pending item in the break + /// table. + static void clearAllBreaks(); + + /// Returns true if the specified key + modifiers are bound to something + /// on the global action map. + static bool checkAsciiGlobal(U16 key, U32 modifiers); + + static bool getDeviceTypeAndInstance(const char *device, U32 &deviceType, U32 &deviceInstance); + + DECLARE_CONOBJECT(ActionMap); +}; + +#endif // _ACTIONMAP_H_ diff --git a/sim/connectionStringTable.cpp b/sim/connectionStringTable.cpp new file mode 100644 index 0000000..3ebca01 --- /dev/null +++ b/sim/connectionStringTable.cpp @@ -0,0 +1,172 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" + +class NetStringEvent : public NetEvent +{ + NetStringHandle mString; + U32 mIndex; +public: + NetStringEvent(U32 index = 0, NetStringHandle string = NetStringHandle()) + { + mIndex = index; + mString = string; + } + virtual void pack(NetConnection* /*ps*/, BitStream *bstream) + { + bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize); + bstream->writeString(mString.getString()); + } + virtual void write(NetConnection* /*ps*/, BitStream *bstream) + { + bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize); + bstream->writeString(mString.getString()); + } + virtual void unpack(NetConnection* /*con*/, BitStream *bstream) + { + char buf[256]; + mIndex = bstream->readInt(ConnectionStringTable::EntryBitSize); + bstream->readString(buf); + mString = NetStringHandle(buf); + } + virtual void notifyDelivered(NetConnection *ps, bool madeit) + { + if(madeit) + ps->confirmStringReceived(mString, mIndex); + } + virtual void process(NetConnection *connection) + { + Con::printf("Mapping string: %s to index: %d", mString.getString(), mIndex); + connection->mapString(mIndex, mString); + } +#ifdef TORQUE_DEBUG_NET + const char *getDebugName() + { + static char buffer[512]; + dSprintf(buffer, sizeof(buffer), "%s - \"", getClassName()); + expandEscape(buffer + dStrlen(buffer), mString.getString()); + dStrcat(buffer, "\""); + return buffer; + } +#endif + DECLARE_CONOBJECT(NetStringEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(NetStringEvent); + +//-------------------------------------------------------------------- + + +ConnectionStringTable::ConnectionStringTable(NetConnection *parent) +{ + mParent = parent; + for(U32 i = 0; i < EntryCount; i++) + { + mEntryTable[i].nextHash = NULL; + mEntryTable[i].nextLink = &mEntryTable[i+1]; + mEntryTable[i].prevLink = &mEntryTable[i-1]; + mEntryTable[i].index = i; + + mHashTable[i] = NULL; + } + mLRUHead.nextLink = &mEntryTable[0]; + mEntryTable[0].prevLink = &mLRUHead; + mLRUTail.prevLink = &mEntryTable[EntryCount-1]; + mEntryTable[EntryCount-1].nextLink = &mLRUTail; +} + +U32 ConnectionStringTable::getNetSendId(NetStringHandle &string) +{ + // see if the entry is in the hash table right now + U32 hashIndex = string.getIndex() % EntryCount; + for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash) + if(walk->string == string) + return walk->index; + AssertFatal(0, "Net send id is not in the table. Error!"); + return 0; +} + +U32 ConnectionStringTable::checkString(NetStringHandle &string, bool *isOnOtherSide) +{ + // see if the entry is in the hash table right now + U32 hashIndex = string.getIndex() % EntryCount; + for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash) + { + if(walk->string == string) + { + pushBack(walk); + if(isOnOtherSide) + *isOnOtherSide = walk->receiveConfirmed; + return walk->index; + } + } + + // not in the hash table, means we have to add it + Entry *newEntry; + + // pull the new entry from the LRU list. + newEntry = mLRUHead.nextLink; + pushBack(newEntry); + + // remove the string from the hash table + Entry **hashWalk; + for (hashWalk = &mHashTable[newEntry->string.getIndex() % EntryCount]; *hashWalk; hashWalk = &((*hashWalk)->nextHash)) + { + if(*hashWalk == newEntry) + { + *hashWalk = newEntry->nextHash; + break; + } + } + + newEntry->string = string; + newEntry->receiveConfirmed = false; + newEntry->nextHash = mHashTable[hashIndex]; + mHashTable[hashIndex] = newEntry; + + mParent->postNetEvent(new NetStringEvent(newEntry->index, string)); + if(isOnOtherSide) + *isOnOtherSide = false; + return newEntry->index; +} + +void ConnectionStringTable::mapString(U32 netId, NetStringHandle &string) +{ + // the netId is sent by the other side... throw it in our mapping table: + mRemoteStringTable[netId] = string; +} + +void ConnectionStringTable::readDemoStartBlock(BitStream *stream) +{ + // ok, reading the demo start block + for(U32 i = 0; i < EntryCount; i++) + { + if(stream->readFlag()) + { + char buffer[256]; + stream->readString(buffer); + mRemoteStringTable[i] = NetStringHandle(buffer); + } + } +} + +void ConnectionStringTable::writeDemoStartBlock(ResizeBitStream *stream) +{ + // ok, writing the demo start block + for(U32 i = 0; i < EntryCount; i++) + { + if(stream->writeFlag(mRemoteStringTable[i].isValidString())) + { + stream->writeString(mRemoteStringTable[i].getString()); + stream->validate(); + } + } +} diff --git a/sim/connectionStringTable.h b/sim/connectionStringTable.h new file mode 100644 index 0000000..37357f3 --- /dev/null +++ b/sim/connectionStringTable.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _H_CONNECTIONSTRINGTABLE +#define _H_CONNECTIONSTRINGTABLE + +/// Maintain a table of strings which are shared across the network. +/// +/// This allows us to reference strings in our network streams more efficiently. +class ConnectionStringTable +{ +public: + enum Constants { + EntryCount = 32, + EntryBitSize = 5, + InvalidEntryId = 32, + }; +private: + struct Entry { + NetStringHandle string; ///< Global string table entry of this string + /// will be 0 if this string is unused. + + U32 index; ///< index of this entry + Entry *nextHash; ///< the next hash entry for this id + Entry *nextLink; ///< the next in the LRU list + Entry *prevLink; ///< the prev entry in the LRU list + bool receiveConfirmed; ///< The other side now has this string. + }; + + Entry mEntryTable[EntryCount]; + Entry *mHashTable[EntryCount]; + NetStringHandle mRemoteStringTable[EntryCount]; + Entry mLRUHead, mLRUTail; + + /// Connection over which we are maintaining this string table. + NetConnection *mParent; + + inline void pushBack(Entry *entry) // pushes an entry to the back of the LRU list + { + entry->prevLink->nextLink = entry->nextLink; + entry->nextLink->prevLink = entry->prevLink; + entry->nextLink = &mLRUTail; + entry->prevLink = mLRUTail.prevLink; + entry->nextLink->prevLink = entry; + entry->prevLink->nextLink = entry; + } + +public: + /// Initialize the connection string table. + /// + /// @param parent Connection over which we are maintaining this string table. + ConnectionStringTable(NetConnection *parent); + + /// Has the specified string been received on the other side? + inline void confirmStringReceived(NetStringHandle &string, U32 index) + { + if(mEntryTable[index].string == string) + mEntryTable[index].receiveConfirmed = true; + } + + U32 checkString(NetStringHandle &stringTableId, bool *stringOnOtherSide = NULL); ///< Checks if the global string ID is + /// currently valid for this connection + /// and returns the table ID. + /// Sends a string event to the other side + /// if it is not active. + /// It will fill in stringOnOtherSide. + + U32 getNetSendId(NetStringHandle &stringTableId); ///< Same return value as checkString + /// but will assert if the string is not + /// valid. + + void mapString(U32 netId, NetStringHandle &string); ///< Maps a string that + /// was just sent over the net + /// to the corresponding net ID. + + inline NetStringHandle lookupString(U32 netId) ///< looks up the string ID and returns + { /// the global string table ID for that string. + return mRemoteStringTable[netId]; + } + + /// @name Demo functionality + /// @{ + + void readDemoStartBlock(BitStream *stream); + void writeDemoStartBlock(ResizeBitStream *stream); + /// @} +}; + +#endif + diff --git a/sim/netConnection.cpp b/sim/netConnection.cpp new file mode 100644 index 0000000..79c3964 --- /dev/null +++ b/sim/netConnection.cpp @@ -0,0 +1,1212 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "core/stream/fileStream.h" +#ifndef TORQUE_TGB_ONLY +# include "sceneGraph/pathManager.h" +#endif +#include "console/consoleTypes.h" +#include "sim/netInterface.h" +#include + +S32 gNetBitsSent = 0; +extern S32 gNetBitsReceived; +U32 gGhostUpdates = 0; + +enum NetConnectionConstants { + PingTimeout = 4500, ///< milliseconds + DefaultPingRetryCount = 15, +}; + +SimObjectPtr NetConnection::mServerConnection; +SimObjectPtr NetConnection::mLocalClientConnection; + +//---------------------------------------------------------------------- +/// ConnectionMessageEvent +/// +/// This event is used inside by the connection and subclasses to message +/// itself when sequencing events occur. Right now, the message event +/// only uses 6 bits to transmit the message, so +class ConnectionMessageEvent : public NetEvent +{ + U32 sequence; + U32 message; + U32 ghostCount; +public: + ConnectionMessageEvent(U32 msg=0, U32 seq=0, U32 gc=0) + { message = msg; sequence = seq; ghostCount = gc;} + void pack(NetConnection *, BitStream *bstream) + { + bstream->write(sequence); + bstream->writeInt(message, 3); + bstream->writeInt(ghostCount, NetConnection::GhostIdBitSize + 1); + } + void write(NetConnection *, BitStream *bstream) + { + bstream->write(sequence); + bstream->writeInt(message, 3); + bstream->writeInt(ghostCount, NetConnection::GhostIdBitSize + 1); + } + void unpack(NetConnection *, BitStream *bstream) + { + bstream->read(&sequence); + message = bstream->readInt(3); + ghostCount = bstream->readInt(NetConnection::GhostIdBitSize + 1); + } + void process(NetConnection *ps) + { + ps->handleConnectionMessage(message, sequence, ghostCount); + } + DECLARE_CONOBJECT(ConnectionMessageEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(ConnectionMessageEvent); + +void NetConnection::sendConnectionMessage(U32 message, U32 sequence, U32 ghostCount) +{ + postNetEvent(new ConnectionMessageEvent(message, sequence, ghostCount)); +} + +//-------------------------------------------------------------------- +IMPLEMENT_CONOBJECT(NetConnection); + +NetConnection* NetConnection::mConnectionList = NULL; +NetConnection* NetConnection::mHashTable[NetConnection::HashTableSize] = { NULL, }; + +bool NetConnection::mFilesWereDownloaded = false; + +static inline U32 HashNetAddress(const NetAddress *addr) +{ + return *((U32 *)addr->netNum) % NetConnection::HashTableSize; +} + +NetConnection *NetConnection::lookup(const NetAddress *addr) +{ + U32 hashIndex = HashNetAddress(addr); + for(NetConnection *walk = mHashTable[hashIndex]; walk; walk = walk->mNextTableHash) + if(Net::compareAddresses(addr, walk->getNetAddress())) + return walk; + return NULL; +} + +void NetConnection::netAddressTableInsert() +{ + U32 hashIndex = HashNetAddress(&mNetAddress); + mNextTableHash = mHashTable[hashIndex]; + mHashTable[hashIndex] = this; +} + +void NetConnection::netAddressTableRemove() +{ + U32 hashIndex = HashNetAddress(&mNetAddress); + NetConnection **walk = &mHashTable[hashIndex]; + while(*walk) + { + if(*walk == this) + { + *walk = mNextTableHash; + mNextTableHash = NULL; + return; + } + walk = &((*walk)->mNextTableHash); + } +} + +void NetConnection::setNetAddress(const NetAddress *addr) +{ + mNetAddress = *addr; +} + +const NetAddress *NetConnection::getNetAddress() +{ + return &mNetAddress; +} + +void NetConnection::setSequence(U32 sequence) +{ + mConnectSequence = sequence; +} + +U32 NetConnection::getSequence() +{ + return mConnectSequence; +} + +static U32 gPacketRateToServer = 32; +static U32 gPacketUpdateDelayToServer = 32; +static U32 gPacketRateToClient = 10; +static U32 gPacketSize = 200; + +void NetConnection::consoleInit() +{ + Con::addVariable("pref::Net::PacketRateToServer", TypeS32, &gPacketRateToServer); + Con::addVariable("pref::Net::PacketRateToClient", TypeS32, &gPacketRateToClient); + Con::addVariable("pref::Net::PacketSize", TypeS32, &gPacketSize); + Con::addVariable("Stats::netBitsSent", TypeS32, &gNetBitsSent); + Con::addVariable("Stats::netBitsReceived", TypeS32, &gNetBitsReceived); + Con::addVariable("Stats::netGhostUpdates", TypeS32, &gGhostUpdates); +} + +void NetConnection::checkMaxRate() +{ + // Limit packet rate to server. + //if(gPacketRateToServer > 32) + // gPacketRateToServer = 32; + if(gPacketRateToServer < 8) + gPacketRateToServer = 8; + + // Limit packet rate to client. + //if(gPacketRateToClient > 32) + // gPacketRateToClient = 32; + if(gPacketRateToClient < 1) + gPacketRateToClient = 1; + + // Limit packet size. + //if(gPacketSize > 450) + // gPacketSize = 450; + if(gPacketSize < 100) + gPacketSize = 100; + + U32 packetRateToServer = gPacketRateToServer; + U32 packetRateToClient = gPacketRateToClient; + U32 packetSize = gPacketSize; + + if (isLocalConnection()) + { + packetRateToServer = 128; + packetRateToClient = 128; + packetSize = 1024; + } + + gPacketUpdateDelayToServer = 1024 / packetRateToServer; + U32 toClientUpdateDelay = 1024 / packetRateToClient; + + if(mMaxRate.updateDelay != toClientUpdateDelay || mMaxRate.packetSize != packetSize) + { + mMaxRate.updateDelay = toClientUpdateDelay; + mMaxRate.packetSize = packetSize; + mMaxRate.changed = true; + } +} + +void NetConnection::setSendingEvents(bool sending) +{ + AssertFatal(!mEstablished, "Error, cannot change event behavior after a connection has been established."); + mSendingEvents = sending; +} + +void NetConnection::setTranslatesStrings(bool xl) +{ + AssertFatal(!mEstablished, "Error, cannot change event behavior after a connection has been established."); + mTranslateStrings = xl; + if(mTranslateStrings) + mStringTable = new ConnectionStringTable(this); +} + +void NetConnection::setNetClassGroup(U32 grp) +{ + AssertFatal(!mEstablished, "Error, cannot change net class group after a connection has been established."); + mNetClassGroup = grp; +} + +NetConnection::NetConnection() +{ + mTranslateStrings = false; + mConnectSequence = 0; + + mStringTable = NULL; + mSendingEvents = true; + mNetClassGroup = NetClassGroupGame; + AssertFatal(mNetClassGroup >= NetClassGroupGame && mNetClassGroup < NetClassGroupsCount, + "Invalid net event class type."); + + mSimulatedPing = 0; + mSimulatedPacketLoss = 0; +#ifdef TORQUE_DEBUG_NET + mLogging = false; +#endif + mEstablished = false; + mLastUpdateTime = 0; + mRoundTripTime = 0; + mPacketLoss = 0; + mNextTableHash = NULL; + mSendDelayCredit = 0; + mConnectionState = NotConnected; + + mCurrentDownloadingFile = NULL; + mCurrentFileBuffer = NULL; + + mNextConnection = NULL; + mPrevConnection = NULL; + + mNotifyQueueHead = NULL; + mNotifyQueueTail = NULL; + + mCurRate.updateDelay = 102; + mCurRate.packetSize = 200; + mCurRate.changed = false; + mMaxRate.updateDelay = 102; + mMaxRate.packetSize = 200; + mMaxRate.changed = false; + checkMaxRate(); + + // event management data: + + mNotifyEventList = NULL; + mSendEventQueueHead = NULL; + mSendEventQueueTail = NULL; + mUnorderedSendEventQueueHead = NULL; + mUnorderedSendEventQueueTail = NULL; + mWaitSeqEvents = NULL; + + mNextSendEventSeq = FirstValidSendEventSeq; + mNextRecvEventSeq = FirstValidSendEventSeq; + mLastAckedEventSeq = -1; + + // ghost management data: + + mScopeObject = NULL; + mGhostingSequence = 0; + mGhosting = false; + mScoping = false; + mGhostArray = NULL; + mGhostRefs = NULL; + mGhostLookupTable = NULL; + mLocalGhosts = NULL; + + mGhostsActive = 0; + + mMissionPathsSent = false; + mDemoWriteStream = NULL; + mDemoReadStream = NULL; + + mPingSendCount = 0; + mPingRetryCount = DefaultPingRetryCount; + mLastPingSendTime = Platform::getVirtualMilliseconds(); + + mCurrentDownloadingFile = NULL; + mCurrentFileBuffer = NULL; + mCurrentFileBufferSize = 0; + mCurrentFileBufferOffset = 0; + mNumDownloadedFiles = 0; +} + +NetConnection::~NetConnection() +{ + AssertFatal(mNotifyQueueHead == NULL, "Uncleared notifies remain."); + netAddressTableRemove(); + + dFree(mCurrentFileBuffer); + if(mCurrentDownloadingFile) + delete mCurrentDownloadingFile; + + delete[] mLocalGhosts; + delete[] mGhostLookupTable; + delete[] mGhostRefs; + delete[] mGhostArray; + delete mStringTable; + if(mDemoWriteStream) + delete mDemoWriteStream; + if(mDemoReadStream) + delete mDemoReadStream; +} + +NetConnection::PacketNotify::PacketNotify() +{ + rateChanged = false; + maxRateChanged = false; + sendTime = 0; + eventList = 0; + ghostList = 0; +} + +bool NetConnection::checkTimeout(U32 time) +{ + if(!isNetworkConnection()) + return false; + + if(time > mLastPingSendTime + PingTimeout) + { + if(mPingSendCount >= mPingRetryCount) + return true; + mLastPingSendTime = time; + mPingSendCount++; + sendPingPacket(); + } + return false; +} + +void NetConnection::keepAlive() +{ + mLastPingSendTime = Platform::getVirtualMilliseconds(); + mPingSendCount = 0; +} + +void NetConnection::handleConnectionEstablished() +{ +} + +//-------------------------------------------------------------------------- +#ifndef TORQUE_TGB_ONLY +ConsoleMethod(NetConnection,transmitPaths,void,2,2,"conn.transmitPaths();") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + + gServerPathManager->transmitPaths(object); + object->setMissionPathsSent(true); +} + +ConsoleMethod(NetConnection,clearPaths,void,2,2,"conn.clearPaths();") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->setMissionPathsSent(false); +} +#endif + +ConsoleMethod(NetConnection,getAddress,const char *,2,2,"Returns the address we're connected to.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + if(object->isLocalConnection()) + return "local"; + char *buffer = Con::getReturnBuffer(256); + Net::addressToString(object->getNetAddress(), buffer); + return buffer; +} + +ConsoleMethod(NetConnection,setSimulatedNetParams,void,4, 4,"(float packetLoss, int delay)") +{ + TORQUE_UNUSED(argc); + object->setSimulatedNetParams(dAtof(argv[2]), dAtoi(argv[3])); +} + +ConsoleMethod( NetConnection, getPing, S32, 2, 2, "conn.getPing()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return( S32( object->getRoundTripTime() ) ); +} + +ConsoleMethod( NetConnection, getPacketLoss, S32, 2, 2, "conn.getPacketLoss()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + return( S32( 100 * object->getPacketLoss() ) ); +} + +ConsoleMethod( NetConnection, checkMaxRate, void, 2, 2, "conn.checkMaxRate()") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->checkMaxRate(); +} + +#ifdef TORQUE_DEBUG_NET + +ConsoleMethod( NetConnection, setLogging, void, 3, 3, "conn.setLogging(bool)") +{ + TORQUE_UNUSED(argc); + object->setLogging(dAtob(argv[2])); +} + +#endif + +//-------------------------------------------------------------------- + +void NetConnection::setEstablished() +{ + AssertFatal(!mEstablished, "NetConnection::setEstablished - Error, this NetConnection has already been established."); + + mEstablished = true; + mNextConnection = mConnectionList; + if(mConnectionList) + mConnectionList->mPrevConnection = this; + mConnectionList = this; + + if(isNetworkConnection()) + netAddressTableInsert(); + +} + +void NetConnection::onRemove() +{ + // delete any ghosts that may exist for this connection, but aren't added + while(mGhostAlwaysSaveList.size()) + { + delete mGhostAlwaysSaveList[0].ghost; + mGhostAlwaysSaveList.pop_front(); + } + if(mNextConnection) + mNextConnection->mPrevConnection = mPrevConnection; + if(mPrevConnection) + mPrevConnection->mNextConnection = mNextConnection; + if(mConnectionList == this) + mConnectionList = mNextConnection; + while(mNotifyQueueHead) + handleNotify(false); + + ghostOnRemove(); + eventOnRemove(); + + Parent::onRemove(); +} + +String NetConnection::mErrorBuffer; + +void NetConnection::setLastError(const char *fmt, ...) +{ + va_list argptr; + va_start(argptr, fmt); + mErrorBuffer = String::ToString(fmt, argptr); + va_end(argptr); + +#ifdef TORQUE_DEBUG_NET + // setLastErrors assert in net_debug builds + AssertFatal(false, mErrorBuffer.c_str()); +#endif + +} + +//-------------------------------------------------------------------- + +void NetConnection::handleNotify(bool recvd) +{ +// Con::printf("NET %d: NOTIFY - %d %s", getId(), gPacketId, recvd ? "RECVD" : "DROPPED"); + + PacketNotify *note = mNotifyQueueHead; + AssertFatal(note != NULL, "Error: got a notify with a null notify head."); + mNotifyQueueHead = mNotifyQueueHead->nextPacket; + + if(note->rateChanged && !recvd) + mCurRate.changed = true; + if(note->maxRateChanged && !recvd) + mMaxRate.changed = true; + + if(recvd) + { + // Running average of roundTrip time + U32 curTime = Platform::getVirtualMilliseconds(); + mRoundTripTime = (mRoundTripTime + (curTime - note->sendTime)) * 0.5; + packetReceived(note); + } + else + packetDropped(note); + + delete note; +} + +void NetConnection::processRawPacket(BitStream *bstream) +{ + if(mDemoWriteStream) + recordBlock(BlockTypePacket, bstream->getReadByteSize(), bstream->getBuffer()); + + ConnectionProtocol::processRawPacket(bstream); +} + +void NetConnection::handlePacket(BitStream *bstream) +{ +// Con::printf("NET %d: RECV - %d", getId(), mLastSeqRecvd); + // clear out any errors + + mErrorBuffer = String(); + + if(bstream->readFlag()) + { + mCurRate.updateDelay = bstream->readInt(10); + mCurRate.packetSize = bstream->readInt(10); + } + + if(bstream->readFlag()) + { + U32 omaxDelay = bstream->readInt(10); + S32 omaxSize = bstream->readInt(10); + if(omaxDelay < mMaxRate.updateDelay) + omaxDelay = mMaxRate.updateDelay; + if(omaxSize > mMaxRate.packetSize) + omaxSize = mMaxRate.packetSize; + if(omaxDelay != mCurRate.updateDelay || omaxSize != mCurRate.packetSize) + { + mCurRate.updateDelay = omaxDelay; + mCurRate.packetSize = omaxSize; + mCurRate.changed = true; + } + } + readPacket(bstream); + + if(mErrorBuffer.isNotEmpty()) + connectionError(mErrorBuffer); +} + +void NetConnection::connectionError(const char *errorString) +{ + TORQUE_UNUSED(errorString); +} + +//-------------------------------------------------------------------- + +NetConnection::PacketNotify *NetConnection::allocNotify() +{ + return new PacketNotify; +} + +/// Used when simulating lag. +/// +/// We post this SimEvent when we want to send a packet; it delays for a bit, then +/// sends the actual packet. +class NetDelayEvent : public SimEvent +{ + U8 buffer[Net::MaxPacketDataSize]; + BitStream stream; +public: + NetDelayEvent(BitStream *inStream) : stream(NULL, 0) + { + dMemcpy(buffer, inStream->getBuffer(), inStream->getPosition()); + stream.setBuffer(buffer, inStream->getPosition()); + stream.setPosition(inStream->getPosition()); + } + void process(SimObject *object) + { + ((NetConnection *) object)->sendPacket(&stream); + } +}; + +void NetConnection::checkPacketSend(bool force) +{ + U32 curTime = Platform::getVirtualMilliseconds(); + U32 delay = isConnectionToServer() ? gPacketUpdateDelayToServer : mCurRate.updateDelay; + + if(!force) + { + if(curTime < mLastUpdateTime + delay - mSendDelayCredit) + return; + + mSendDelayCredit = curTime - (mLastUpdateTime + delay - mSendDelayCredit); + if(mSendDelayCredit > 1000) + mSendDelayCredit = 1000; + + if(mDemoWriteStream) + recordBlock(BlockTypeSendPacket, 0, 0); + } + if(windowFull()) + return; + + BitStream *stream = BitStream::getPacketStream(mCurRate.packetSize); + buildSendPacketHeader(stream); + + mLastUpdateTime = curTime; + + PacketNotify *note = allocNotify(); + if(!mNotifyQueueHead) + mNotifyQueueHead = note; + else + mNotifyQueueTail->nextPacket = note; + mNotifyQueueTail = note; + note->nextPacket = NULL; + note->sendTime = curTime; + + note->rateChanged = mCurRate.changed; + note->maxRateChanged = mMaxRate.changed; + + if(stream->writeFlag(mCurRate.changed)) + { + stream->writeInt(mCurRate.updateDelay, 10); + stream->writeInt(mCurRate.packetSize, 10); + mCurRate.changed = false; + } + if(stream->writeFlag(mMaxRate.changed)) + { + stream->writeInt(mMaxRate.updateDelay, 10); + stream->writeInt(mMaxRate.packetSize, 10); + mMaxRate.changed = false; + } +#ifdef TORQUE_DEBUG_NET + U32 start = stream->getCurPos(); +#endif + + DEBUG_LOG(("PKLOG %d START", getId()) ); + writePacket(stream, note); + DEBUG_LOG(("PKLOG %d END - %d", getId(), stream->getCurPos() - start) ); + if(mSimulatedPacketLoss && Platform::getRandom() < mSimulatedPacketLoss) + { + //Con::printf("NET %d: SENDDROP - %d", getId(), mLastSendSeq); + return; + } + if(mSimulatedPing) + { + Sim::postEvent(getId(), new NetDelayEvent(stream), Sim::getCurrentTime() + mSimulatedPing); + return; + } + sendPacket(stream); +} + +Net::Error NetConnection::sendPacket(BitStream *stream) +{ + //Con::printf("NET %d: SEND - %d", getId(), mLastSendSeq); + // do nothing on send if this is a demo replay. + if(mDemoReadStream) + return Net::NoError; + + gNetBitsSent = stream->getStreamSize(); + + if(isLocalConnection()) + { + // short circuit connection to the other side. + // handle the packet, then force a notify. + stream->setBuffer(stream->getBuffer(), stream->getPosition(), stream->getPosition()); + mRemoteConnection->processRawPacket(stream); + + return Net::NoError; + } + else + { + return Net::sendto(getNetAddress(), stream->getBuffer(), stream->getPosition()); + } +} + +//-------------------------------------------------------------------- +//-------------------------------------------------------------------- + +// these are the virtual function defs for Connection - +// if your subclass has additional data to read / write / notify, add it in these functions. + +void NetConnection::readPacket(BitStream *bstream) +{ + eventReadPacket(bstream); + ghostReadPacket(bstream); +} + +void NetConnection::writePacket(BitStream *bstream, PacketNotify *note) +{ + eventWritePacket(bstream, note); + ghostWritePacket(bstream, note); +} + +void NetConnection::packetReceived(PacketNotify *note) +{ + eventPacketReceived(note); + ghostPacketReceived(note); +} + +void NetConnection::packetDropped(PacketNotify *note) +{ + eventPacketDropped(note); + ghostPacketDropped(note); +} + +//-------------------------------------------------------------------- +//-------------------------------------------------------------------- + +void NetConnection::writeDemoStartBlock(ResizeBitStream* stream) +{ + ConnectionProtocol::writeDemoStartBlock(stream); + + stream->write(mRoundTripTime); + stream->write(mPacketLoss); +#ifndef TORQUE_TGB_ONLY + // Write all the current paths to the stream... + gClientPathManager->dumpState(stream); +#endif + stream->validate(); + mStringTable->writeDemoStartBlock(stream); + + U32 start = 0; + PacketNotify *note = mNotifyQueueHead; + while(note) + { + start++; + note = note->nextPacket; + } + stream->write(start); + + eventWriteStartBlock(stream); + ghostWriteStartBlock(stream); +} + +bool NetConnection::readDemoStartBlock(BitStream* stream) +{ + ConnectionProtocol::readDemoStartBlock(stream); + + stream->read(&mRoundTripTime); + stream->read(&mPacketLoss); + +#ifndef TORQUE_TGB_ONLY + // Read + gClientPathManager->readState(stream); +#endif + + mStringTable->readDemoStartBlock(stream); + U32 pos; + stream->read(&pos); // notify count + for(U32 i = 0; i < pos; i++) + { + PacketNotify *note = allocNotify(); + note->nextPacket = NULL; + if(!mNotifyQueueHead) + mNotifyQueueHead = note; + else + mNotifyQueueTail->nextPacket = note; + mNotifyQueueTail = note; + } + eventReadStartBlock(stream); + ghostReadStartBlock(stream); + return true; +} + +bool NetConnection::startDemoRecord(const char *fileName) +{ + FileStream *fs = new FileStream; + + if((fs = FileStream::createAndOpen( fileName, Torque::FS::File::Write )) == NULL) + return false; + + mDemoWriteStream = fs; + mDemoWriteStream->write(mProtocolVersion); + ResizeBitStream bs; + + // then write out the start block + writeDemoStartBlock(&bs); + U32 size = bs.getPosition() + 1; + mDemoWriteStream->write(size); + mDemoWriteStream->write(size, bs.getBuffer()); + return true; +} + +bool NetConnection::replayDemoRecord(const char *fileName) +{ + Stream *fs; + if((fs = FileStream::createAndOpen( fileName, Torque::FS::File::Read )) == NULL) + return false; + + mDemoReadStream = fs; + mDemoReadStream->read(&mProtocolVersion); + U32 size; + mDemoReadStream->read(&size); + U8 *block = new U8[size]; + mDemoReadStream->read(size, block); + BitStream bs(block, size); + + bool res = readDemoStartBlock(&bs); + delete[] block; + if(!res) + return false; + + // prep for first block read + // type/size stored in U16: [type:4][size:12] + U16 typeSize; + mDemoReadStream->read(&typeSize); + + mDemoNextBlockType = typeSize >> 12; + mDemoNextBlockSize = typeSize & 0xFFF; + + if(mDemoReadStream->getStatus() != Stream::Ok) + return false; + return true; +} + +void NetConnection::stopRecording() +{ + if(mDemoWriteStream) + { + delete mDemoWriteStream; + mDemoWriteStream = NULL; + } +} + +void NetConnection::recordBlock(U32 type, U32 size, void *data) +{ + AssertFatal(type < MaxNumBlockTypes, "NetConnection::recordBlock: invalid type"); + AssertFatal(size < MaxBlockSize, "NetConnection::recordBlock: invalid size"); + if((type >= MaxNumBlockTypes) || (size >= MaxBlockSize)) + return; + + if(mDemoWriteStream) + { + // store type/size in U16: [type:4][size:12] + U16 typeSize = (type << 12) | size; + mDemoWriteStream->write(typeSize); + if(size) + mDemoWriteStream->write(size, data); + } +} + +void NetConnection::handleRecordedBlock(U32 type, U32 size, void *data) +{ + switch(type) + { + case BlockTypePacket: { + BitStream bs(data, size); + processRawPacket(&bs); + break; + } + case BlockTypeSendPacket: + checkPacketSend(true); + break; + } +} + +void NetConnection::demoPlaybackComplete() +{ +} + +void NetConnection::stopDemoPlayback() +{ + demoPlaybackComplete(); + deleteObject(); +} + +bool NetConnection::processNextBlock() +{ + U8 buffer[Net::MaxPacketDataSize]; + + // read in and handle + if(mDemoReadStream->read(mDemoNextBlockSize, buffer)) + handleRecordedBlock(mDemoNextBlockType, mDemoNextBlockSize, buffer); + + U16 typeSize; + mDemoReadStream->read(&typeSize); + + mDemoNextBlockType = typeSize >> 12; + mDemoNextBlockSize = typeSize & 0xFFF; + + if(mDemoReadStream->getStatus() != Stream::Ok) + { + stopDemoPlayback(); + return false; + } + return true; +} + +//-------------------------------------------------------------------- +//-------------------------------------------------------------------- + +// some handy string functions for compressing strings over a connection: +enum NetStringConstants +{ + NullString = 0, + CString, + TagString, + Integer +}; + +void NetConnection::validateSendString(const char *str) +{ + if(U8(*str) == StringTagPrefixByte) + { + NetStringHandle strHandle(dAtoi(str + 1)); + checkString(strHandle); + } +} + +void NetConnection::packString(BitStream *stream, const char *str) +{ + char buf[16]; + if(!*str) + { + stream->writeInt(NullString, 2); + return; + } + if(U8(str[0]) == StringTagPrefixByte) + { + stream->writeInt(TagString, 2); + stream->writeInt(dAtoi(str + 1), ConnectionStringTable::EntryBitSize); + return; + } + if(str[0] == '-' || (str[0] >= '0' && str[0] <= '9')) + { + S32 num = dAtoi(str); + dSprintf(buf, sizeof(buf), "%d", num); + if(!dStrcmp(buf, str)) + { + stream->writeInt(Integer, 2); + if(stream->writeFlag(num < 0)) + num = -num; + if(stream->writeFlag(num < 128)) + { + stream->writeInt(num, 7); + return; + } + if(stream->writeFlag(num < 32768)) + { + stream->writeInt(num, 15); + return; + } + else + { + stream->writeInt(num, 31); + return; + } + } + } + stream->writeInt(CString, 2); + stream->writeString(str); +} + +void NetConnection::unpackString(BitStream *stream, char readBuffer[1024]) +{ + U32 code = stream->readInt(2); + switch(code) + { + case NullString: + readBuffer[0] = 0; + return; + case CString: + { + stream->readString(readBuffer); + return; + } + case TagString: + U32 tag; + tag = stream->readInt(ConnectionStringTable::EntryBitSize); + readBuffer[0] = StringTagPrefixByte; + dSprintf(readBuffer+1, 1023, "%d", tag); + return; + case Integer: + bool neg; + neg = stream->readFlag(); + S32 num; + if(stream->readFlag()) + num = stream->readInt(7); + else if(stream->readFlag()) + num = stream->readInt(15); + else + num = stream->readInt(31); + if(neg) + num = -num; + dSprintf(readBuffer, 1024, "%d", num); + } +} + +void NetConnection::packNetStringHandleU(BitStream *stream, NetStringHandle &h) +{ + if(stream->writeFlag(h.isValidString() )) + { + bool isReceived; + U32 netIndex = checkString(h, &isReceived); + if(stream->writeFlag(isReceived)) + stream->writeInt(netIndex, ConnectionStringTable::EntryBitSize); + else + stream->writeString(h.getString()); + } +} + +NetStringHandle NetConnection::unpackNetStringHandleU(BitStream *stream) +{ + NetStringHandle ret; + if(stream->readFlag()) + { + if(stream->readFlag()) + ret = mStringTable->lookupString(stream->readInt(ConnectionStringTable::EntryBitSize)); + else + { + char buf[256]; + stream->readString(buf); + ret = NetStringHandle(buf); + } + } + return ret; +} + +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +void NetConnection::setAddressDigest(U32 digest[4]) +{ + mAddressDigest[0] = digest[0]; + mAddressDigest[1] = digest[1]; + mAddressDigest[2] = digest[2]; + mAddressDigest[3] = digest[3]; +} + +void NetConnection::getAddressDigest(U32 digest[4]) +{ + digest[0] = mAddressDigest[0]; + digest[1] = mAddressDigest[1]; + digest[2] = mAddressDigest[2]; + digest[3] = mAddressDigest[3]; +} + +bool NetConnection::canRemoteCreate() +{ + return false; +} + +void NetConnection::onTimedOut() +{ + +} + +void NetConnection::connect(const NetAddress *address) +{ + mNetAddress = *address; + GNet->startConnection(this); +} + +void NetConnection::onConnectTimedOut() +{ + +} + +void NetConnection::sendDisconnectPacket(const char *reason) +{ + GNet->sendDisconnectPacket(this, reason); +} + +void NetConnection::onDisconnect(const char *reason) +{ + TORQUE_UNUSED(reason); +} + +void NetConnection::onConnectionRejected(const char *reason) +{ +} + +void NetConnection::onConnectionEstablished(bool isInitiator) +{ + +} + +void NetConnection::handleStartupError(const char *errorString) +{ + +} + +void NetConnection::writeConnectRequest(BitStream *stream) +{ + stream->write(mNetClassGroup); + stream->write(U32(AbstractClassRep::getClassCRC(mNetClassGroup))); +} + +bool NetConnection::readConnectRequest(BitStream *stream, const char **errorString) +{ + U32 classGroup, classCRC; + stream->read(&classGroup); + stream->read(&classCRC); + + if(classGroup == mNetClassGroup && classCRC == AbstractClassRep::getClassCRC(mNetClassGroup)) + return true; + + *errorString = "CHR_INVALID"; + return false; +} + +void NetConnection::writeConnectAccept(BitStream *stream) +{ + TORQUE_UNUSED(stream); +} + +bool NetConnection::readConnectAccept(BitStream *stream, const char **errorString) +{ + TORQUE_UNUSED(stream); + TORQUE_UNUSED(errorString); + return true; +} + +ConsoleMethod(NetConnection, resolveGhostID, S32, 3, 3, "( S32 ghostID ) Convert a ghost id from this connection to a real id.") +{ + S32 gID = dAtoi(argv[2]); + + // Safety check + if(gID < 0 || gID > NetConnection::MaxGhostCount) return 0; + + NetObject *foo = object->resolveGhost(gID); + + if(foo) + return foo->getId(); + else + return 0; +} + +ConsoleMethod(NetConnection, resolveObjectFromGhostIndex, S32, 3, 3, "( S32 ghostIdx) Convert a ghost index from this connection to a real id.") +{ + S32 gID = dAtoi(argv[2]); + + // Safety check + if(gID < 0 || gID > NetConnection::MaxGhostCount) return 0; + + NetObject *foo = object->resolveObjectFromGhostIndex(gID); + + if(foo) + return foo->getId(); + else + return 0; +} + +ConsoleMethod(NetConnection, getGhostID, S32, 3, 3, "( S32 realID ) Convert a real id to the ghost id for this connection.") +{ + NetObject * foo; + + if(Sim::findObject(argv[2], foo)) + { + return object->getGhostIndex(foo); + } + else + { + Con::errorf("NetConnection::serverToGhostID - could not find specified object"); + return -1; + } +} + +ConsoleMethod(NetConnection, connect, void, 3, 3, "(string remoteAddress) Connects this NC object to the remote address.") +{ + NetAddress addr; + if(!Net::stringToAddress(argv[2], &addr)) + { + Con::errorf("NetConnection::connect: invalid address - %s", argv[2]); + return; + } + object->connect(&addr); +} + +ConsoleMethod(NetConnection, connectLocal, const char *, 2, 2, "Connects a connection to the server running in the same process.") +{ + ConsoleObject *co = ConsoleObject::create(object->getClassName()); + NetConnection *client = object; + NetConnection *server = dynamic_cast(co); + const char *error = NULL; + BitStream *stream = BitStream::getPacketStream(); + + if(!server || !server->canRemoteCreate()) + goto errorOut; + server->registerObject(); + server->setIsLocalClientConnection(); + + server->setSequence(0); + client->setSequence(0); + client->setRemoteConnectionObject(server); + server->setRemoteConnectionObject(client); + + stream->setPosition(0); + client->writeConnectRequest(stream); + stream->setPosition(0); + if(!server->readConnectRequest(stream, &error)) + goto errorOut; + + stream->setPosition(0); + server->writeConnectAccept(stream); + stream->setPosition(0); + + if(!client->readConnectAccept(stream, &error)) + goto errorOut; + + client->onConnectionEstablished(true); + server->onConnectionEstablished(false); + client->setEstablished(); + server->setEstablished(); + client->setConnectSequence(0); + server->setConnectSequence(0); + NetConnection::setLocalClientConnection(server); + server->assignName("LocalClientConnection"); + return ""; + +errorOut: + server->deleteObject(); + client->deleteObject(); + if(!error) + error = "Unknown Error"; + return error; +} diff --git a/sim/netConnection.h b/sim/netConnection.h new file mode 100644 index 0000000..a30a23b --- /dev/null +++ b/sim/netConnection.h @@ -0,0 +1,1115 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _NETCONNECTION_H_ +#define _NETCONNECTION_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _NETOBJECT_H_ +#include "sim/netObject.h" +#endif +#ifndef _NETSTRINGTABLE_H_ +#include "sim/netStringTable.h" +#endif +#ifndef _EVENT_H_ +#include "platform/event.h" +#endif +#ifndef _DNET_H_ +#include "core/dnet.h" +#endif + +#ifndef _H_CONNECTIONSTRINGTABLE +#include "sim/connectionStringTable.h" +#endif + +class NetConnection; +class NetObject; +class BitStream; +class ResizeBitStream; +class Stream; +class Point3F; + +struct GhostInfo; +struct SubPacketRef; // defined in NetConnection subclass + +//#define DEBUG_NET + +#ifdef TORQUE_DEBUG_NET +#define DEBUG_LOG(x) if(mLogging){Con::printf x;} +#else +#define DEBUG_LOG(x) +#endif + +//---------------------------------------------------------------------------- + +class NetEvent; + +struct NetEventNote +{ + NetEvent *mEvent; + S32 mSeqCount; + NetEventNote *mNextEvent; +}; + +/// An event to be sent over the network. +/// +/// @note Torque implements two methods of network data passing; this is one of them. +/// See NetConnection for details of the other, which is referred to as ghosting. +/// +/// Torque's network layer lets you pass events to/from the server. There are three +/// types of events: +/// - Unguaranteed events are events which are sent once. If they don't +/// make it through the link, they are not resent. This is good for quick, +/// frequent status updates which are of transient interest, like position +/// updates or voice communication. +/// - Guaranteed events are events which are guaranteed to be +/// delivered. If they don't make it through the link, they are sent as +/// needed. This is good for important, one-time information, +/// like which team a user wants to play on, or the current weather. +/// - GuaranteedOrdered events are events which are guaranteed not +/// only to be delivered, but to be delivered in order. This is good for +/// information which is not only important, but also order-critical, like +/// chat messages. +/// +/// There are 6 methods that you need to implement if you want to make a +/// basic NetEvent subclass, and 2 macros you need to call. +/// +/// @code +/// // A simple NetEvent to transmit a string over the network. +/// // This is based on the code in netTest.cc +/// class SimpleMessageEvent : public NetEvent +/// { +/// typedef NetEvent Parent; +/// char *msg; +/// public: +/// SimpleMessageEvent(const char *message = NULL); +/// ~SimpleMessageEvent(); +/// +/// virtual void pack (NetConnection *conn, BitStream *bstream); +/// virtual void write (NetConnection *conn, BitStream *bstream); +/// virtual void unpack (NetConnection *conn, BitStream *bstream); +/// virtual void process(NetConnection *conn); +/// +/// DECLARE_CONOBJECT(SimpleMessageEvent); +/// }; +/// +/// IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent); +/// @endcode +/// +/// Notice the two macros which we call. The first, DECLARE_CONOBJECT() is there +/// because we're a ConsoleObject. The second, IMPLEMENT_CO_NETEVENT_V1(), is there +/// to register this event type with Torque's networking layer, so that it can be +/// properly transmitted over the wire. There are three macros which you might use: +/// - IMPLEMENT_CO_NETEVENT_V1, which indicates an event which may be sent +/// in either direction, from the client to the server, or from the server to the +/// client. +/// - IMPLEMENT_CO_CLIENTEVENT_V1, which indicates an event which may only +/// be sent to the client. +/// - IMPLEMENT_CO_SERVEREVENT_V1, which indicates an event which may only +/// be sent to the server. +/// +/// Choosing the right macro is a good way to make your game more resistant to hacking; for instance, +/// PathManager events are marked as CLIENTEVENTs, because they would cause the server to crash if +/// a client sent them. +/// +/// @note Torque allows you to call NetConnection::setLastError() on the NetConnection passed to +/// your NetEvent. You can cause the connection to abort if invalid data is received, specifying +/// a reason to the user. +/// +/// Now, the 6 methods which we have above; the constructor and destructor need only do +/// whatever book-keeping is needed for your specific implementation. In our case, we +/// just need to allocate/deallocate the space for our string: +/// +/// @code +/// SimpleMessageEvent::SimpleMessageEvent(const char *message = NULL) +/// { +/// // If we wanted to make this not be a GuaranteedOrdered event, we'd +/// // put a line like this in the constructor: +/// // mGuaranteeType = Guaranteed; +/// // (or whatever type you wanted.) +/// if(message) +/// msg = dStrdup(message); +/// else +/// msg = NULL; +/// } +/// +/// SimpleMessageEvent::~SimpleMessageEvent() +/// { +/// dFree(msg); +/// } +/// @endcode +/// +/// Simple as that! Now, onto pack(), write(), unpack(), process(). +/// +/// pack() is responsible for packing the event over the wire: +/// +/// @code +/// void SimpleMessageEvent::pack(NetConnection* conn, BitStream *bstream) +/// { +/// bstream->writeString(msg); +/// } +/// @endcode +/// +/// unpack() is responsible for unpacking the event on the other end: +/// +/// @code +/// // The networking layer takes care of instantiating a new +/// // SimpleMessageEvent, which saves us a bit of effort. +/// void SimpleMessageEvent::unpack(NetConnection *conn, BitStream *bstream) +/// { +/// char buf[256]; +/// bstream->readString(buf); +/// msg = dStrdup(buf); +/// } +/// @endcode +/// +/// process() is called when the network layer is finished with things. +/// A typical case is that a GuaranteedOrdered event is unpacked and stored, but +/// not processed until the events preceding it in the sequence have also been +/// dealt with. +/// +/// @code +/// // This just prints the event in the console. You might +/// // want to do something more clever here -- BJG +/// void SimpleMessageEvent::process(NetConnection *conn) +/// { +/// Con::printf("RMSG %d %s", mSourceId, msg); +/// } +/// @endcode +/// +/// write() is called if a demo recording is started, and the event has not yet been +/// processed, but it has been unpacked. It should be identical in its output to the bitstream +/// compared to pack(), but since it is called after unpack() some lookups may not need to be +/// performed. In normal demo recording, whole network packets are recorded, meaning that most +/// of the time write() will not be called. +/// +/// In our case, it's entirely identical to pack(): +/// +/// @code +/// virtual void write(NetConnection*, BitStream *bstream) +/// { +/// bstream->writeString(msg); +/// } +/// @endcode +/// +/// The NetEvent is sent over the wire in a straightforward way (assuming you have a +/// handle to a NetConnection): +/// +/// @code +/// NetConnection *conn; // We assume you have filled this in. +/// +/// con->postNetEvent(new SimpleMessageEvent("This is a test!")); +/// @endcode +/// +/// @see GhostAlwaysObjectEvent for an example of dissimilar write()/pack() methods. +/// +/// Finally, for more advanced applications, notifySent() is called whenever the event is +/// sent over the wire, in NetConnection::eventWritePacket(). notifyDelivered() is called +/// when the packet is finally received or (in the case of Unguaranteed packets) dropped. +/// +/// @note IMPLEMENT_CO_NETEVENT_V1 and co. have sibling macros which allow you to specify a +/// groupMask; see ConsoleObject for a further discussion of this. +class NetEvent : public ConsoleObject +{ +public: + /// @name Implementation Details + /// + /// These are internal fields which you won't need to manipulate, except for mGuaranteeType. + /// @{ + + /// + S32 mRefCount; + typedef ConsoleObject Parent; + enum { + GuaranteedOrdered = 0, + Guaranteed = 1, + Unguaranteed = 2 + } mGuaranteeType; + NetConnectionId mSourceId; + + void incRef() + { + mRefCount++; + } + void decRef() + { + mRefCount--; + if(!mRefCount) + delete this; + } + +#ifdef TORQUE_DEBUG_NET + virtual const char *getDebugName(); +#endif + /// @} + + /// @name Things To Subclass + /// @{ + + /// + NetEvent() { mGuaranteeType = GuaranteedOrdered; mRefCount = 0; } + virtual ~NetEvent(); + + virtual void write(NetConnection *ps, BitStream *bstream) = 0; + virtual void pack(NetConnection *ps, BitStream *bstream) = 0; + virtual void unpack(NetConnection *ps, BitStream *bstream) = 0; + virtual void process(NetConnection *ps) = 0; + virtual void notifySent(NetConnection *ps); + virtual void notifyDelivered(NetConnection *ps, bool madeit); + /// @} +}; + +#define IMPLEMENT_CO_NETEVENT_V1(className) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className,NetClassGroupGameMask, NetClassTypeEvent, NetEventDirAny, className::getParentStaticClassRep(), &Parent::__description) + +#define IMPLEMENT_CO_CLIENTEVENT_V1(className) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className,NetClassGroupGameMask, NetClassTypeEvent, NetEventDirServerToClient, className::getParentStaticClassRep(), &Parent::__description) + +#define IMPLEMENT_CO_SERVEREVENT_V1(className) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className,NetClassGroupGameMask, NetClassTypeEvent, NetEventDirClientToServer, className::getParentStaticClassRep(), &Parent::__description) + +#define IMPLEMENT_CO_NETEVENT(className,groupMask) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className,groupMask, NetClassTypeEvent, NetEventDirAny, className::getParentStaticClassRep(), &Parent::__description) + +#define IMPLEMENT_CO_CLIENTEVENT(className,groupMask) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className,groupMask, NetClassTypeEvent, NetEventDirServerToClient, className::getParentStaticClassRep(), &Parent::__description) + +#define IMPLEMENT_CO_SERVEREVENT(className,groupMask) \ + AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \ + AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \ + AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \ + ConcreteClassRep className::dynClassRep(#className,groupMask, NetClassTypeEvent, NetEventDirClientToServer, className::getParentStaticClassRep(), &Parent::__description) + + +//---------------------------------------------------------------------------- + +/// Torque network connection. +/// +/// @section NetConnection_intro Introduction +/// +/// NetConnection is the glue that binds a networked Torque game together. It combines +/// the low-level notify protocol implemented in ConnectionProtocol with a SimGroup to +/// provide a powerful basis for implementing a multiplayer game protocol. +/// +/// On top of this basis it implements several distinct subsystems: +/// - Event manager, which is responsible for transmitting NetEvents over the wire. +/// It deals with ensuring that the various types of NetEvents are delivered appropriately, +/// and with notifying the event of its delivery status. +/// - Move manager, which is responsible for transferring a Move to the server 32 +/// times a second (on the client) and applying it to the control object (on the server). +/// - Ghost manager, which is responsible for doing scoping calculations (on the server +/// side) and transmitting most-recent ghost information to the client. +/// - File transfer; it is often the case that clients will lack important files when +/// connecting to a server which is running a mod or new map. This subsystem allows the +/// server to transfer such files to the client. +/// - Networked String Table; string data can easily soak up network bandwidth, so for +/// efficiency, we implement a networked string table. We can then notify the connection +/// of strings we will reference often, such as player names, and transmit only a tag, +/// instead of the whole string. +/// - Demo Recording is also implemented in NetConnection. A demo in Torque is a log +/// of the network traffic between client and server; when a NetConnection records a demo, +/// it simply logs this data to a file. When it plays a demo back, it replays the logged +/// data. +/// - The Connection Database is used to keep track of all the NetConnections; it can +/// be iterated over (for instance, to send an event to all active connections), or queried +/// by address. +/// +/// @section NetConnection_events On Events +/// +/// The Event Manager is exposed to the outside world via postNetEvent(), which accepts NetEvents. +/// +/// @see NetEvent for a more thorough explanation of how to use events. +/// +/// @section NetConnection_ghosting On Ghosting and Scoping +/// +/// Ghosting is the most complex, and most powerful, part of Torque's networking capabilities. It +/// allows the information sent to clients to be very precisely matched to what they need, so that +/// no excess bandwidth is wasted. The control object's onCameraScopeQuery() is called, to determine +/// scoping information for the client; then objects which are in scope are then transmitted to the +/// client, prioritized by the results of their getPriority() method. +/// +/// There is a cap on the maximum number of ghosts; ghost IDs are currently sent via a 10-bit field, +/// ergo, there is a cap of 1024 objects ghosted per client. This can be easily raised; see the +/// GhostConstants enum. +/// +/// Each object ghosted is assigned a ghost ID; the client is _only_ aware of the ghost ID. This acts +/// to enhance game security, as it becomes difficult to map objects from one connection to another, or +/// to reliably identify objects from ID alone. IDs are also reassigned based on need, making it hard +/// to track objects that have fallen out of scope (as any object which the player shouldn't see would). +/// +/// resolveGhost() is used on the client side, and resolveObjectFromGhostIndex() on the server side, to +/// turn ghost IDs into object references. +/// +/// The NetConnection is a SimGroup. On the client side, it contains all the objects which have been +/// ghosted to that client. On the server side, it is empty; it can be used (typically in script) to +/// hold objects related to the connection. For instance, you might place an observation camera in the +/// NetConnnection. In both cases, when the connection is destroyed, so are the contained objects. +/// +/// @see NetObject, which is the superclass for ghostable objects, and ShapeBase, which is the base +/// for player and vehicle classes. +/// +/// @nosubgrouping +class NetConnection : public ConnectionProtocol, public SimGroup +{ + friend class NetInterface; + + typedef SimGroup Parent; + +public: + /// Structure to track ghost references in packets. + /// + /// Every packet we send out with an update from a ghost causes one of these to be + /// allocated. mask is used to track what states were sent; that way if a packet is + /// dropped, we can easily manipulate the stored states and figure out what if any data + /// we need to resend. + /// + struct GhostRef + { + U32 mask; ///< States we transmitted. + U32 ghostInfoFlags; ///< Flags from GhostInfo::Flags + GhostInfo *ghost; ///< Reference to the GhostInfo we're from. + GhostRef *nextRef; ///< Next GhostRef in this packet. + GhostRef *nextUpdateChain; ///< Next update we sent for this ghost. + }; + + enum Constants + { + HashTableSize = 127, + }; + + void sendDisconnectPacket(const char *reason); + + virtual bool canRemoteCreate(); + + virtual void onTimedOut(); + virtual void onConnectTimedOut(); + virtual void onDisconnect(const char *reason); + virtual void onConnectionRejected(const char *reason); + virtual void onConnectionEstablished(bool isInitiator); + virtual void handleStartupError(const char *errorString); + + virtual void writeConnectRequest(BitStream *stream); + virtual bool readConnectRequest(BitStream *stream, const char **errorString); + + virtual void writeConnectAccept(BitStream *stream); + virtual bool readConnectAccept(BitStream *stream, const char **errorString); + + void connect(const NetAddress *address); + + //---------------------------------------------------------------- + /// @name Global Connection List + /// @{ + +private: + /// + NetConnection *mNextConnection; ///< Next item in list. + NetConnection *mPrevConnection; ///< Previous item in list. + static NetConnection *mConnectionList; ///< Head of list. +public: + static NetConnection *getConnectionList() { return mConnectionList; } + NetConnection *getNext() { return mNextConnection; } + /// @} + //---------------------------------------------------------------- + + enum NetConnectionFlags + { + ConnectionToServer = BIT(0), + ConnectionToClient = BIT(1), + LocalClientConnection = BIT(2), + NetworkConnection = BIT(3), + }; + +private: + BitSet32 mTypeFlags; + + U32 mNetClassGroup; ///< The NetClassGroup of this connection. + + /// @name Statistics + /// @{ + + U32 mLastUpdateTime; + F32 mRoundTripTime; + F32 mPacketLoss; + U32 mSimulatedPing; + F32 mSimulatedPacketLoss; + + /// @} + + /// @name State + /// @{ + + U32 mProtocolVersion; + U32 mSendDelayCredit; + U32 mConnectSequence; + U32 mAddressDigest[4]; + + bool mEstablished; + bool mMissionPathsSent; + + struct NetRate + { + U32 updateDelay; + S32 packetSize; + bool changed; + }; + + NetRate mCurRate; + NetRate mMaxRate; + + /// If we're doing a "short circuited" connection, this stores + /// a pointer to the other side. + SimObjectPtr mRemoteConnection; + + NetAddress mNetAddress; + + /// @} + + + /// @name Timeout Management + /// @{ + + U32 mPingSendCount; + U32 mPingRetryCount; + U32 mLastPingSendTime; + /// @} + + /// @name Connection Table + /// + /// We store our connections on a hash table so we can + /// quickly find them. + /// @{ + + NetConnection *mNextTableHash; + static NetConnection *mHashTable[HashTableSize]; + + /// @} + +protected: + static SimObjectPtr mServerConnection; + static SimObjectPtr mLocalClientConnection; + + static bool mFilesWereDownloaded; + + U32 mConnectSendCount; + U32 mConnectLastSendTime; + + SimObjectPtr getRemoteConnection() { return mRemoteConnection; } + +public: + static NetConnection *getConnectionToServer() { return mServerConnection; } + + static NetConnection *getLocalClientConnection() { return mLocalClientConnection; } + static void setLocalClientConnection(NetConnection *conn) { mLocalClientConnection = conn; } + + U32 getNetClassGroup() { return mNetClassGroup; } + static bool filesWereDownloaded() { return mFilesWereDownloaded; } + static String &getErrorBuffer() { return mErrorBuffer; } + +#ifdef TORQUE_DEBUG_NET + bool mLogging; + void setLogging(bool logging) { mLogging = logging; } +#endif + + void setSimulatedNetParams(F32 packetLoss, U32 ping) + { mSimulatedPacketLoss = packetLoss; mSimulatedPing = ping; } + + bool isConnectionToServer() { return mTypeFlags.test(ConnectionToServer); } + bool isLocalConnection() { return !mRemoteConnection.isNull() ; } + bool isNetworkConnection() { return mTypeFlags.test(NetworkConnection); } + + void setIsConnectionToServer() { mTypeFlags.set(ConnectionToServer); } + void setIsLocalClientConnection() { mTypeFlags.set(LocalClientConnection); } + void setNetworkConnection(bool net) { mTypeFlags.set(BitSet32(NetworkConnection), net); } + + virtual void setEstablished(); + + /// Call this if the "connection" is local to this app. This short-circuits the protocol layer. + void setRemoteConnectionObject(NetConnection *connection) { mRemoteConnection = connection; }; + + void setSequence(U32 connectSequence); + + void setAddressDigest(U32 digest[4]); + void getAddressDigest(U32 digest[4]); + + U32 getSequence(); + + void setProtocolVersion(U32 protocolVersion) { mProtocolVersion = protocolVersion; } + U32 getProtocolVersion() { return mProtocolVersion; } + F32 getRoundTripTime() { return mRoundTripTime; } + F32 getPacketLoss() { return( mPacketLoss ); } + + static String mErrorBuffer; + static void setLastError(const char *fmt,...); + + void checkMaxRate(); + void handlePacket(BitStream *stream); + void processRawPacket(BitStream *stream); + void handleNotify(bool recvd); + void handleConnectionEstablished(); + void keepAlive(); + + const NetAddress *getNetAddress(); + void setNetAddress(const NetAddress *address); + Net::Error sendPacket(BitStream *stream); + +private: + void netAddressTableInsert(); + void netAddressTableRemove(); + +public: + /// Find a NetConnection, if any, with the specified address. + static NetConnection *lookup(const NetAddress *remoteAddress); + + bool checkTimeout(U32 time); ///< returns true if the connection timed out + + void checkPacketSend(bool force); + + bool missionPathsSent() const { return mMissionPathsSent; } + void setMissionPathsSent(const bool s) { mMissionPathsSent = s; } + + static void consoleInit(); + + void onRemove(); + + NetConnection(); + ~NetConnection(); + +public: + enum NetConnectionState + { + NotConnected, + AwaitingChallengeResponse, ///< We've sent a challenge request, awaiting the response. + AwaitingConnectRequest, ///< We've received a challenge request and sent a challenge response. + AwaitingConnectResponse, ///< We've received a challenge response and sent a connect request. + Connected, ///< We've accepted a connect request, or we've received a connect response accept. + }; + + U32 mConnectionSendCount; ///< number of connection messages we've sent. + U32 mConnectionState; ///< State of the connection, from NetConnectionState. + + void setConnectionState(U32 state) { mConnectionState = state; } + U32 getConnectionState() { return mConnectionState; } + + + void setGhostFrom(bool ghostFrom); ///< Sets whether ghosts transmit from this side of the connection. + void setGhostTo(bool ghostTo); ///< Sets whether ghosts are allowed from the other side of the connection. + void setSendingEvents(bool sending); ///< Sets whether this side actually sends the events that are posted to it. + void setTranslatesStrings(bool xl); ///< Sets whether this connection is capable of translating strings. + void setNetClassGroup(U32 group); ///< Sets the group of NetClasses this connection traffics in. + bool isEstablished() { return mEstablished; } ///< Is the connection established? + + DECLARE_CONOBJECT(NetConnection); + + /// Structure to track packets and what we sent over them. + /// + /// We need to know what is sent in each packet, so that if a packet is + /// dropped, we know what to resend. This is the structure we use to track + /// this data. + struct PacketNotify + { + bool rateChanged; ///< Did the rate change on this packet? + bool maxRateChanged; ///< Did the max rate change on this packet? + U32 sendTime; ///< Timestampe, when we sent this packet. + + NetEventNote *eventList; ///< Linked list of events sent over this packet. + GhostRef *ghostList; ///< Linked list of ghost updates we sent in this packet. + SubPacketRef *subList; ///< Defined by subclass - used as desired. + + PacketNotify *nextPacket; ///< Next packet sent. + PacketNotify(); + }; + virtual PacketNotify *allocNotify(); + PacketNotify *mNotifyQueueHead; ///< Head of packet notify list. + PacketNotify *mNotifyQueueTail; ///< Tail of packet notify list. + +protected: + virtual void readPacket(BitStream *bstream); + virtual void writePacket(BitStream *bstream, PacketNotify *note); + virtual void packetReceived(PacketNotify *note); + virtual void packetDropped(PacketNotify *note); + virtual void connectionError(const char *errorString); + +//---------------------------------------------------------------- +/// @name Event Manager +/// @{ + +private: + NetEventNote *mSendEventQueueHead; + NetEventNote *mSendEventQueueTail; + NetEventNote *mUnorderedSendEventQueueHead; + NetEventNote *mUnorderedSendEventQueueTail; + NetEventNote *mWaitSeqEvents; + NetEventNote *mNotifyEventList; + + static FreeListChunker mEventNoteChunker; + + bool mSendingEvents; + + S32 mNextSendEventSeq; + S32 mNextRecvEventSeq; + S32 mLastAckedEventSeq; + + enum NetEventConstants { + InvalidSendEventSeq = -1, + FirstValidSendEventSeq = 0 + }; + + void eventOnRemove(); + + void eventPacketDropped(PacketNotify *notify); + void eventPacketReceived(PacketNotify *notify); + + void eventWritePacket(BitStream *bstream, PacketNotify *notify); + void eventReadPacket(BitStream *bstream); + + void eventWriteStartBlock(ResizeBitStream *stream); + void eventReadStartBlock(BitStream *stream); +public: + /// Post an event to this connection. + bool postNetEvent(NetEvent *event); + +/// @} + +//---------------------------------------------------------------- +/// @name Networked string table +/// @{ + +private: + bool mTranslateStrings; + ConnectionStringTable *mStringTable; +public: + void mapString(U32 netId, NetStringHandle &string) + { mStringTable->mapString(netId, string); } + U32 checkString(NetStringHandle &string, bool *isOnOtherSide = NULL) + { if(mStringTable) return mStringTable->checkString(string, isOnOtherSide); else return 0; } + U32 getNetSendId(NetStringHandle &string) + { if(mStringTable) return mStringTable->getNetSendId(string); else return 0;} + void confirmStringReceived(NetStringHandle &string, U32 index) + { if(!isRemoved()) mStringTable->confirmStringReceived(string, index); } + + NetStringHandle translateRemoteStringId(U32 id) { return mStringTable->lookupString(id); } + void validateSendString(const char *str); + + void packString(BitStream *stream, const char *str); + void unpackString(BitStream *stream, char readBuffer[1024]); + + void packNetStringHandleU(BitStream *stream, NetStringHandle &h); + NetStringHandle unpackNetStringHandleU(BitStream *stream); +/// @} + +//---------------------------------------------------------------- +/// @name Ghost manager +/// @{ + +protected: + enum GhostStates + { + GhostAlwaysDone, + ReadyForNormalGhosts, + EndGhosting, + GhostAlwaysStarting, + SendNextDownloadRequest, + FileDownloadSizeMessage, + NumConnectionMessages, + }; + GhostInfo **mGhostArray; ///< Linked list of ghostInfos ghosted by this side of the connection + + U32 mGhostZeroUpdateIndex; ///< Index in mGhostArray of first ghost with 0 update mask. + U32 mGhostFreeIndex; ///< Index in mGhostArray of first free ghost. + + U32 mGhostsActive; ///- Track actve ghosts on client side + + bool mGhosting; ///< Am I currently ghosting objects? + bool mScoping; ///< am I currently scoping objects? + U32 mGhostingSequence; ///< Sequence number describing this ghosting session. + + NetObject **mLocalGhosts; ///< Local ghost for remote object. + /// + /// mLocalGhosts pointer is NULL if mGhostTo is false + + GhostInfo *mGhostRefs; ///< Allocated array of ghostInfos. Null if ghostFrom is false. + GhostInfo **mGhostLookupTable; ///< Table indexed by object id to GhostInfo. Null if ghostFrom is false. + + /// The object around which we are scoping this connection. + /// + /// This is usually the player object, or a related object, like a vehicle + /// that the player is driving. + SimObjectPtr mScopeObject; + + void clearGhostInfo(); + bool validateGhostArray(); + + void ghostPacketDropped(PacketNotify *notify); + void ghostPacketReceived(PacketNotify *notify); + + void ghostWritePacket(BitStream *bstream, PacketNotify *notify); + void ghostReadPacket(BitStream *bstream); + void freeGhostInfo(GhostInfo *); + + void ghostWriteStartBlock(ResizeBitStream *stream); + void ghostReadStartBlock(BitStream *stream); + + virtual void ghostWriteExtra(NetObject *,BitStream *) {} + virtual void ghostReadExtra(NetObject *,BitStream *, bool newGhost) {} + virtual void ghostPreRead(NetObject *, bool newGhost) {} + +public: + /// Some configuration values. + enum GhostConstants + { + GhostIdBitSize = 12, + MaxGhostCount = 1 << GhostIdBitSize, //4096, + GhostLookupTableSize = 1 << GhostIdBitSize, //4096 + GhostIndexBitSize = 4 // number of bits GhostIdBitSize-3 fits into + }; + + U32 getGhostsActive() { return mGhostsActive;}; + + /// Are we ghosting to someone? + bool isGhostingTo() { return mLocalGhosts != NULL; }; + + /// Are we ghosting from someone? + bool isGhostingFrom() { return mGhostArray != NULL; }; + + /// Called by onRemove, to shut down the ghost subsystem. + void ghostOnRemove(); + + /// Called when we're done with normal scoping. + /// + /// This gives subclasses a chance to shove things into scope, such as + /// the results of a sensor network calculation, that would otherwise + /// be awkward to add. + virtual void doneScopingScene() { /* null */ } + + /// Set the object around which we are currently scoping network traffic. + void setScopeObject(NetObject *object); + + /// Get the object around which we are currently scoping network traffic. + NetObject *getScopeObject(); + + /// Add an object to scope. + void objectInScope(NetObject *object); + + /// Add an object to scope, marking that it should always be scoped to this connection. + void objectLocalScopeAlways(NetObject *object); + + /// Mark an object that is being ghosted as not always needing to be scoped. + /// + /// This undoes objectLocalScopeAlways(), but doesn't immediately flush it from scope. + /// + /// Instead, the standard scoping mechanisms will clear it from scope when it is appropos + /// to do so. + void objectLocalClearAlways(NetObject *object); + + /// Get a NetObject* from a ghost ID (on client side). + NetObject *resolveGhost(S32 id); + + /// Get a NetObject* from a ghost index (on the server side). + NetObject *resolveObjectFromGhostIndex(S32 id); + + /// Get the ghost index corresponding to a given NetObject. This is only + /// meaningful on the server side. + S32 getGhostIndex(NetObject *object); + + /// Move a GhostInfo into the nonzero portion of the list (so that we know to update it). + void ghostPushNonZero(GhostInfo *gi); + + /// Move a GhostInfo into the zero portion of the list (so that we know not to update it). + void ghostPushToZero(GhostInfo *gi); + + /// Move a GhostInfo from the zero portion of the list to the free portion. + void ghostPushZeroToFree(GhostInfo *gi); + + /// Move a GhostInfo from the free portion of the list to the zero portion. + inline void ghostPushFreeToZero(GhostInfo *info); + + /// Stop all ghosting activity and inform the other side about this. + /// + /// Turns off ghosting. + void resetGhosting(); + + /// Activate ghosting, once it's enabled. + void activateGhosting(); + + /// Are we ghosting? + bool isGhosting() { return mGhosting; } + + /// Begin to stop ghosting an object. + void detachObject(GhostInfo *info); + + /// Mark an object to be always ghosted. Index is the ghost index of the object. + void setGhostAlwaysObject(NetObject *object, U32 index); + + + /// Send ghost connection handshake message. + /// + /// As part of the ghost connection process, extensive hand-shaking must be performed. + /// + /// This is done by passing ConnectionMessageEvents; this is a helper function + /// to more effectively perform this task. Messages are dealt with by + /// handleConnectionMessage(). + /// + /// @param message One of GhostStates + /// @param sequence A sequence number, if any. + /// @param ghostCount A count of ghosts relating to this message. + void sendConnectionMessage(U32 message, U32 sequence = 0, U32 ghostCount = 0); + + /// Handle message from sendConnectionMessage(). + /// + /// This is called to handle messages sent via sendConnectionMessage. + /// + /// @param message One of GhostStates + /// @param sequence A sequence number, if any. + /// @param ghostCount A count of ghosts relating to this message. + virtual void handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount); + + /// Sends a signal to any object that needs to wait till everything has been ghosted + /// before performing an operation. + static Signal smGhostAlwaysDone; + + /// @} +public: +//---------------------------------------------------------------- +/// @name File transfer +/// @{ + +protected: + /// List of files missing for this connection. + /// + /// The currently downloading file is always first in the list (ie, [0]). + Vector mMissingFileList; + + /// Stream for currently uploading file (if any). + Stream *mCurrentDownloadingFile; + + /// Storage for currently downloading file. + void *mCurrentFileBuffer; + + /// Size of currently downloading file in bytes. + U32 mCurrentFileBufferSize; + + /// Our position in the currently downloading file in bytes. + U32 mCurrentFileBufferOffset; + + /// Number of files we have downloaded. + U32 mNumDownloadedFiles; + + /// Error storage for file transfers. + String mLastFileErrorBuffer; + + /// Structure to track ghost-always objects and their ghost indices. + struct GhostSave { + NetObject *ghost; + U32 index; + }; + + /// List of objects to ghost-always. + Vector mGhostAlwaysSaveList; + +public: + /// Start sending the specified file over the link. + bool startSendingFile(const char *fileName); + + /// Called when we receive a FileChunkEvent. + void chunkReceived(U8 *chunkData, U32 chunkLen); + + /// Get the next file... + void sendNextFileDownloadRequest(); + + /// Post the next FileChunkEvent. + void sendFileChunk(); + + /// Called when we finish downloading file data. + virtual void fileDownloadSegmentComplete(); + + /// This is part of the file transfer logic; basically, we call this + /// every time we finish downloading new files. It attempts to load + /// the GhostAlways objects; if they fail, it marks an error and we + /// have chance to retry. + void loadNextGhostAlwaysObject(bool hadNewFiles); +/// @} + +//---------------------------------------------------------------- +/// @name Demo Recording +/// @{ + +private: + Stream *mDemoWriteStream; + Stream *mDemoReadStream; + U32 mDemoNextBlockType; + U32 mDemoNextBlockSize; + + U32 mDemoWriteStartTime; + U32 mDemoReadStartTime; + U32 mDemoLastWriteTime; + + U32 mDemoRealStartTime; + +public: + enum DemoBlockTypes { + BlockTypePacket, + BlockTypeSendPacket, + NetConnectionBlockTypeCount + }; + + enum DemoConstants { + MaxNumBlockTypes = 16, + MaxBlockSize = 0x1000, + }; + + bool isRecording() + { return mDemoWriteStream != NULL; } + bool isPlayingBack() + { return mDemoReadStream != NULL; } + + U32 getNextBlockType() { return mDemoNextBlockType; } + void recordBlock(U32 type, U32 size, void *data); + virtual void handleRecordedBlock(U32 type, U32 size, void *data); + bool processNextBlock(); + + bool startDemoRecord(const char *fileName); + bool replayDemoRecord(const char *fileName); + void startDemoRead(); + void stopRecording(); + void stopDemoPlayback(); + + virtual void writeDemoStartBlock(ResizeBitStream *stream); + virtual bool readDemoStartBlock(BitStream *stream); + virtual void demoPlaybackComplete(); +/// @} +}; + + +//---------------------------------------------------------------------------- +/// Information about a ghosted object. +/// +/// @note If the size of this structure changes, the +/// NetConnection::getGhostIndex function MUST be changed +/// to reflect the new size. +struct GhostInfo +{ +public: // required for MSVC + NetObject *obj; ///< The object being ghosted. + U32 updateMask; ///< Flags indicating what state data needs to be transferred. + + U32 updateSkipCount; ///< How many updates have we skipped this guy? + U32 flags; ///< Flags from GhostInfo::Flags + F32 priority; ///< A float value indicating the priority of this object for + /// updates. + + /// @name References + /// + /// The GhostInfo structure is used in several linked lists; these members are + /// the implementation for this. + /// @{ + + NetConnection::GhostRef *updateChain; ///< List of references in NetConnections to us. + + GhostInfo *nextObjectRef; ///< Next ghosted object. + GhostInfo *prevObjectRef; ///< Previous ghosted object. + NetConnection *connection; ///< Connection that we're ghosting over. + GhostInfo *nextLookupInfo; ///< GhostInfo references are stored in a hash; this is the bucket + /// implementation. + + /// @} + + U32 index; + U32 arrayIndex; + + /// Flags relating to the state of the object. + enum Flags + { + Valid = BIT(0), + InScope = BIT(1), + ScopeAlways = BIT(2), + NotYetGhosted = BIT(3), + Ghosting = BIT(4), + KillGhost = BIT(5), + KillingGhost = BIT(6), + ScopedEvent = BIT(7), + ScopeLocalAlways = BIT(8), + }; +}; + +inline void NetConnection::ghostPushNonZero(GhostInfo *info) +{ + AssertFatal(info->arrayIndex >= mGhostZeroUpdateIndex && info->arrayIndex < mGhostFreeIndex, "Out of range arrayIndex."); + AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object."); + if(info->arrayIndex != mGhostZeroUpdateIndex) + { + mGhostArray[mGhostZeroUpdateIndex]->arrayIndex = info->arrayIndex; + mGhostArray[info->arrayIndex] = mGhostArray[mGhostZeroUpdateIndex]; + mGhostArray[mGhostZeroUpdateIndex] = info; + info->arrayIndex = mGhostZeroUpdateIndex; + } + mGhostZeroUpdateIndex++; + //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +inline void NetConnection::ghostPushToZero(GhostInfo *info) +{ + AssertFatal(info->arrayIndex < mGhostZeroUpdateIndex, "Out of range arrayIndex."); + AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object."); + mGhostZeroUpdateIndex--; + if(info->arrayIndex != mGhostZeroUpdateIndex) + { + mGhostArray[mGhostZeroUpdateIndex]->arrayIndex = info->arrayIndex; + mGhostArray[info->arrayIndex] = mGhostArray[mGhostZeroUpdateIndex]; + mGhostArray[mGhostZeroUpdateIndex] = info; + info->arrayIndex = mGhostZeroUpdateIndex; + } + //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +inline void NetConnection::ghostPushZeroToFree(GhostInfo *info) +{ + AssertFatal(info->arrayIndex >= mGhostZeroUpdateIndex && info->arrayIndex < mGhostFreeIndex, "Out of range arrayIndex."); + AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object."); + mGhostFreeIndex--; + if(info->arrayIndex != mGhostFreeIndex) + { + mGhostArray[mGhostFreeIndex]->arrayIndex = info->arrayIndex; + mGhostArray[info->arrayIndex] = mGhostArray[mGhostFreeIndex]; + mGhostArray[mGhostFreeIndex] = info; + info->arrayIndex = mGhostFreeIndex; + } + //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +inline void NetConnection::ghostPushFreeToZero(GhostInfo *info) +{ + AssertFatal(info->arrayIndex >= mGhostFreeIndex, "Out of range arrayIndex."); + AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object."); + if(info->arrayIndex != mGhostFreeIndex) + { + mGhostArray[mGhostFreeIndex]->arrayIndex = info->arrayIndex; + mGhostArray[info->arrayIndex] = mGhostArray[mGhostFreeIndex]; + mGhostArray[mGhostFreeIndex] = info; + info->arrayIndex = mGhostFreeIndex; + } + mGhostFreeIndex++; + //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +#endif + + diff --git a/sim/netDownload.cpp b/sim/netDownload.cpp new file mode 100644 index 0000000..c414c83 --- /dev/null +++ b/sim/netDownload.cpp @@ -0,0 +1,244 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "core/stream/fileStream.h" +#include "sim/netObject.h" + +class FileDownloadRequestEvent : public NetEvent +{ +public: + enum + { + MaxFileNames = 31, + }; + + U32 nameCount; + char mFileNames[MaxFileNames][256]; + + FileDownloadRequestEvent(Vector *nameList = NULL) + { + nameCount = 0; + if(nameList) + { + nameCount = nameList->size(); + + if(nameCount > MaxFileNames) + nameCount = MaxFileNames; + + for(U32 i = 0; i < nameCount; i++) + { + dStrcpy(mFileNames[i], (*nameList)[i]); + //Con::printf("Sending request for file %s", mFileNames[i]); + } + } + } + + virtual void pack(NetConnection *, BitStream *bstream) + { + bstream->writeRangedU32(nameCount, 0, MaxFileNames); + for(U32 i = 0; i < nameCount; i++) + bstream->writeString(mFileNames[i]); + } + + virtual void write(NetConnection *, BitStream *bstream) + { + bstream->writeRangedU32(nameCount, 0, MaxFileNames); + for(U32 i = 0; i < nameCount; i++) + bstream->writeString(mFileNames[i]); + } + + virtual void unpack(NetConnection *, BitStream *bstream) + { + nameCount = bstream->readRangedU32(0, MaxFileNames); + for(U32 i = 0; i < nameCount; i++) + bstream->readString(mFileNames[i]); + } + + virtual void process(NetConnection *connection) + { + U32 i; + for(i = 0; i < nameCount; i++) + if(connection->startSendingFile(mFileNames[i])) + break; + if(i == nameCount) + connection->startSendingFile(NULL); // none of the files were sent + } + + DECLARE_CONOBJECT(FileDownloadRequestEvent); + +}; + +IMPLEMENT_CO_NETEVENT_V1(FileDownloadRequestEvent); + +class FileChunkEvent : public NetEvent +{ +public: + enum + { + ChunkSize = 63, + }; + + U8 chunkData[ChunkSize]; + U32 chunkLen; + + FileChunkEvent(U8 *data = NULL, U32 len = 0) + { + if(data) + dMemcpy(chunkData, data, len); + chunkLen = len; + } + + virtual void pack(NetConnection *, BitStream *bstream) + { + bstream->writeRangedU32(chunkLen, 0, ChunkSize); + bstream->write(chunkLen, chunkData); + } + + virtual void write(NetConnection *, BitStream *bstream) + { + bstream->writeRangedU32(chunkLen, 0, ChunkSize); + bstream->write(chunkLen, chunkData); + } + + virtual void unpack(NetConnection *, BitStream *bstream) + { + chunkLen = bstream->readRangedU32(0, ChunkSize); + bstream->read(chunkLen, chunkData); + } + + virtual void process(NetConnection *connection) + { + connection->chunkReceived(chunkData, chunkLen); + } + + virtual void notifyDelivered(NetConnection *nc, bool madeIt) + { + if(!nc->isRemoved()) + nc->sendFileChunk(); + } + + DECLARE_CONOBJECT(FileChunkEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(FileChunkEvent); + +void NetConnection::sendFileChunk() +{ + U8 buffer[FileChunkEvent::ChunkSize]; + U32 len = FileChunkEvent::ChunkSize; + if(len + mCurrentFileBufferOffset > mCurrentFileBufferSize) + len = mCurrentFileBufferSize - mCurrentFileBufferOffset; + + if(!len) + { + delete mCurrentDownloadingFile; + mCurrentDownloadingFile = NULL; + return; + } + + mCurrentFileBufferOffset += len; + mCurrentDownloadingFile->read(len, buffer); + postNetEvent(new FileChunkEvent(buffer, len)); +} + +bool NetConnection::startSendingFile(const char *fileName) +{ + if(!fileName || Con::getBoolVariable("$NetConnection::neverUploadFiles")) + { + sendConnectionMessage(SendNextDownloadRequest); + return false; + } + + mCurrentDownloadingFile = FileStream::createAndOpen( fileName, Torque::FS::File::Read ); + if(!mCurrentDownloadingFile) + { + // the server didn't have the file, so send a 0 byte chunk: + Con::printf("No such file '%s'.", fileName); + postNetEvent(new FileChunkEvent(NULL, 0)); + return false; + } + + Con::printf("Sending file '%s'.", fileName); + mCurrentFileBufferSize = mCurrentDownloadingFile->getStreamSize(); + mCurrentFileBufferOffset = 0; + + // always have 32 file chunks (64 bytes each) in transit + sendConnectionMessage(FileDownloadSizeMessage, mCurrentFileBufferSize); + for(U32 i = 0; i < 32; i++) + sendFileChunk(); + return true; +} + +void NetConnection::sendNextFileDownloadRequest() +{ + // see if we've already downloaded this file... + while(mMissingFileList.size() && (Torque::FS::IsFile(mMissingFileList[0]) || Con::getBoolVariable("$NetConnection::neverDownloadFiles"))) + { + dFree(mMissingFileList[0]); + mMissingFileList.pop_front(); + } + + if(mMissingFileList.size()) + { + postNetEvent(new FileDownloadRequestEvent(&mMissingFileList)); + } + else + { + fileDownloadSegmentComplete(); + } +} + + +void NetConnection::chunkReceived(U8 *chunkData, U32 chunkLen) +{ + if(chunkLen == 0) + { + // the server didn't have the file... apparently it's one we don't need... + dFree(mCurrentFileBuffer); + mCurrentFileBuffer = NULL; + dFree(mMissingFileList[0]); + mMissingFileList.pop_front(); + return; + } + if(chunkLen + mCurrentFileBufferOffset > mCurrentFileBufferSize) + { + setLastError("Invalid file chunk from server."); + return; + } + dMemcpy(((U8 *) mCurrentFileBuffer) + mCurrentFileBufferOffset, chunkData, chunkLen); + mCurrentFileBufferOffset += chunkLen; + if(mCurrentFileBufferOffset == mCurrentFileBufferSize) + { + // this file's done... + // save it to disk: + FileStream *stream; + + Con::printf("Saving file %s.", mMissingFileList[0]); + if((stream = FileStream::createAndOpen( mMissingFileList[0], Torque::FS::File::Write )) == NULL) + { + setLastError("Couldn't open file downloaded by server."); + return; + } + + dFree(mMissingFileList[0]); + mMissingFileList.pop_front(); + stream->write(mCurrentFileBufferSize, mCurrentFileBuffer); + delete stream; + mNumDownloadedFiles++; + dFree(mCurrentFileBuffer); + mCurrentFileBuffer = NULL; + sendNextFileDownloadRequest(); + } + else + { + Con::executef("onFileChunkReceived", mMissingFileList[0], Con::getIntArg(mCurrentFileBufferOffset), Con::getIntArg(mCurrentFileBufferSize)); + } +} + diff --git a/sim/netEvent.cpp b/sim/netEvent.cpp new file mode 100644 index 0000000..2db74e8 --- /dev/null +++ b/sim/netEvent.cpp @@ -0,0 +1,437 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" + +#define DebugChecksum 0xF00DBAAD + +FreeListChunker NetConnection::mEventNoteChunker; + +NetEvent::~NetEvent() +{ + AssertWarn(mRefCount == 0, "NetEvent::~NetEvent - encountered non-zero ref count!"); +} + +void NetEvent::notifyDelivered(NetConnection *, bool) +{ +} + +void NetEvent::notifySent(NetConnection *) +{ +} + +#ifdef TORQUE_DEBUG_NET +const char *NetEvent::getDebugName() +{ + return getClassName(); +} +#endif + +void NetConnection::eventOnRemove() +{ + while(mNotifyEventList) + { + NetEventNote *temp = mNotifyEventList; + mNotifyEventList = temp->mNextEvent; + + temp->mEvent->notifyDelivered(this, true); + temp->mEvent->decRef(); + mEventNoteChunker.free(temp); + } + + while(mUnorderedSendEventQueueHead) + { + NetEventNote *temp = mUnorderedSendEventQueueHead; + mUnorderedSendEventQueueHead = temp->mNextEvent; + + temp->mEvent->notifyDelivered(this, true); + temp->mEvent->decRef(); + mEventNoteChunker.free(temp); + } + + while(mSendEventQueueHead) + { + NetEventNote *temp = mSendEventQueueHead; + mSendEventQueueHead = temp->mNextEvent; + + temp->mEvent->notifyDelivered(this, true); + temp->mEvent->decRef(); + mEventNoteChunker.free(temp); + } +} + +void NetConnection::eventPacketDropped(PacketNotify *notify) +{ + NetEventNote *walk = notify->eventList; + NetEventNote **insertList = &mSendEventQueueHead; + NetEventNote *temp; + + while(walk) + { + switch(walk->mEvent->mGuaranteeType) + { + // It was a guaranteed ordered packet, reinsert it back into + // mSendEventQueueHead in the right place (based on seq numbers) + case NetEvent::GuaranteedOrdered: + + //Con::printf("EVT %d: DROP - %d", getId(), walk->mSeqCount); + + while(*insertList && (*insertList)->mSeqCount < walk->mSeqCount) + insertList = &((*insertList)->mNextEvent); + + temp = walk->mNextEvent; + walk->mNextEvent = *insertList; + if(!walk->mNextEvent) + mSendEventQueueTail = walk; + *insertList = walk; + insertList = &(walk->mNextEvent); + walk = temp; + break; + + // It was a guaranteed packet, put it at the top of + // mUnorderedSendEventQueueHead. + case NetEvent::Guaranteed: + temp = walk->mNextEvent; + walk->mNextEvent = mUnorderedSendEventQueueHead; + mUnorderedSendEventQueueHead = walk; + if(!walk->mNextEvent) + mUnorderedSendEventQueueTail = walk; + walk = temp; + break; + + // Or else it was an unguaranteed packet, notify that + // it was _not_ delivered and blast it. + case NetEvent::Unguaranteed: + walk->mEvent->notifyDelivered(this, false); + walk->mEvent->decRef(); + temp = walk->mNextEvent; + mEventNoteChunker.free(walk); + walk = temp; + } + } +} + +void NetConnection::eventPacketReceived(PacketNotify *notify) +{ + NetEventNote *walk = notify->eventList; + NetEventNote **noteList = &mNotifyEventList; + + while(walk) + { + NetEventNote *next = walk->mNextEvent; + if(walk->mEvent->mGuaranteeType != NetEvent::GuaranteedOrdered) + { + walk->mEvent->notifyDelivered(this, true); + walk->mEvent->decRef(); + mEventNoteChunker.free(walk); + walk = next; + } + else + { + while(*noteList && (*noteList)->mSeqCount < walk->mSeqCount) + noteList = &((*noteList)->mNextEvent); + + walk->mNextEvent = *noteList; + *noteList = walk; + noteList = &walk->mNextEvent; + walk = next; + } + } + while(mNotifyEventList && mNotifyEventList->mSeqCount == mLastAckedEventSeq + 1) + { + mLastAckedEventSeq++; + NetEventNote *next = mNotifyEventList->mNextEvent; + //Con::printf("EVT %d: ACK - %d", getId(), mNotifyEventList->mSeqCount); + mNotifyEventList->mEvent->notifyDelivered(this, true); + mNotifyEventList->mEvent->decRef(); + mEventNoteChunker.free(mNotifyEventList); + mNotifyEventList = next; + } +} + +void NetConnection::eventWritePacket(BitStream *bstream, PacketNotify *notify) +{ +#ifdef TORQUE_DEBUG_NET + bstream->writeInt(DebugChecksum, 32); +#endif + + NetEventNote *packQueueHead = NULL, *packQueueTail = NULL; + + while(mUnorderedSendEventQueueHead) + { + if(bstream->isFull()) + break; + // dequeue the first event + NetEventNote *ev = mUnorderedSendEventQueueHead; + mUnorderedSendEventQueueHead = ev->mNextEvent; +#ifdef TORQUE_DEBUG_NET + U32 start = bstream->getCurPos(); +#endif + + bstream->writeFlag(true); + S32 classId = ev->mEvent->getClassId(getNetClassGroup()); + AssertFatal(classId>=0, "NetConnection::eventWritePacket - event not in group!"); + bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup()); + +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + ev->mEvent->pack(this, bstream); +#ifdef TORQUE_NET_STATS + ev->mEvent->getClassRep()->updateNetStatPack(0, bstream->getBitPosition() - beginSize); +#endif + DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getBitPosition() - start, ev->mEvent->getDebugName()) ); + +#ifdef TORQUE_DEBUG_NET + bstream->writeInt(classId ^ DebugChecksum, 32); +#endif + // add this event onto the packet queue + ev->mNextEvent = NULL; + if(!packQueueHead) + packQueueHead = ev; + else + packQueueTail->mNextEvent = ev; + packQueueTail = ev; + } + + bstream->writeFlag(false); + S32 prevSeq = -2; + + while(mSendEventQueueHead) + { + if(bstream->isFull()) + break; + + // if the event window is full, stop processing + if(mSendEventQueueHead->mSeqCount > mLastAckedEventSeq + 126) + break; + + // dequeue the first event + NetEventNote *ev = mSendEventQueueHead; + mSendEventQueueHead = ev->mNextEvent; + + //Con::printf("EVT %d: SEND - %d", getId(), ev->mSeqCount); + + bstream->writeFlag(true); + + ev->mNextEvent = NULL; + if(!packQueueHead) + packQueueHead = ev; + else + packQueueTail->mNextEvent = ev; + packQueueTail = ev; + if(!bstream->writeFlag(ev->mSeqCount == prevSeq + 1)) + bstream->writeInt(ev->mSeqCount, 7); + + prevSeq = ev->mSeqCount; + +#ifdef TORQUE_DEBUG_NET + U32 start = bstream->getCurPos(); +#endif + + S32 classId = ev->mEvent->getClassId(getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup()); +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + ev->mEvent->pack(this, bstream); +#ifdef TORQUE_NET_STATS + ev->mEvent->getClassRep()->updateNetStatPack(0, bstream->getBitPosition() - beginSize); +#endif + DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getBitPosition() - start, ev->mEvent->getDebugName()) ); +#ifdef TORQUE_DEBUG_NET + bstream->writeInt(classId ^ DebugChecksum, 32); +#endif + } + for(NetEventNote *ev = packQueueHead; ev; ev = ev->mNextEvent) + ev->mEvent->notifySent(this); + + notify->eventList = packQueueHead; + bstream->writeFlag(false); +} + +void NetConnection::eventReadPacket(BitStream *bstream) +{ +#ifdef TORQUE_DEBUG_NET + U32 sum = bstream->readInt(32); + AssertISV(sum == DebugChecksum, "Invalid checksum."); +#endif + + S32 prevSeq = -2; + NetEventNote **waitInsert = &mWaitSeqEvents; + bool unguaranteedPhase = true; + + while(true) + { + bool bit = bstream->readFlag(); + if(unguaranteedPhase && !bit) + { + unguaranteedPhase = false; + bit = bstream->readFlag(); + } + if(!unguaranteedPhase && !bit) + break; + + S32 seq = -1; + + if(!unguaranteedPhase) // get the sequence + { + if(bstream->readFlag()) + seq = (prevSeq + 1) & 0x7f; + else + seq = bstream->readInt(7); + prevSeq = seq; + } + S32 classId = bstream->readClassId(NetClassTypeEvent, getNetClassGroup()); + if(classId == -1) + { + setLastError("Invalid packet. (bad event class id)"); + return; + } + NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classId); + if(!evt) + { + setLastError("Invalid packet. (bad ghost class id)"); + return; + } + AbstractClassRep *rep = evt->getClassRep(); + if((rep->mNetEventDir == NetEventDirServerToClient && !isConnectionToServer()) + || (rep->mNetEventDir == NetEventDirClientToServer && isConnectionToServer()) ) + { + setLastError("Invalid Packet. (invalid direction)"); + return; + } + + + evt->mSourceId = getId(); +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + evt->unpack(this, bstream); +#ifdef TORQUE_NET_STATS + evt->getClassRep()->updateNetStatUnpack(bstream->getBitPosition() - beginSize); +#endif + if(mErrorBuffer.isNotEmpty()) + return; +#ifdef TORQUE_DEBUG_NET + U32 checksum = bstream->readInt(32); + AssertISV( (checksum ^ DebugChecksum) == (U32)classId, + avar("unpack did not match pack for event of class %s.", + evt->getClassName()) ); +#endif + if(unguaranteedPhase) + { + evt->process(this); + evt->decRef(); + if(mErrorBuffer.isNotEmpty()) + return; + continue; + } + seq |= (mNextRecvEventSeq & ~0x7F); + if(seq < mNextRecvEventSeq) + seq += 128; + + NetEventNote *note = mEventNoteChunker.alloc(); + note->mEvent = evt; + note->mEvent->incRef(); + + note->mSeqCount = seq; + //Con::printf("EVT %d: RECV - %d", getId(), evt->mSeqCount); + while(*waitInsert && (*waitInsert)->mSeqCount < seq) + waitInsert = &((*waitInsert)->mNextEvent); + + note->mNextEvent = *waitInsert; + *waitInsert = note; + waitInsert = &(note->mNextEvent); + } + while(mWaitSeqEvents && mWaitSeqEvents->mSeqCount == mNextRecvEventSeq) + { + mNextRecvEventSeq++; + NetEventNote *temp = mWaitSeqEvents; + mWaitSeqEvents = temp->mNextEvent; + + //Con::printf("EVT %d: PROCESS - %d", getId(), temp->mSeqCount); + temp->mEvent->process(this); + temp->mEvent->decRef(); + mEventNoteChunker.free(temp); + if(mErrorBuffer.isNotEmpty()) + return; + } +} + +bool NetConnection::postNetEvent(NetEvent *theEvent) +{ + if(!mSendingEvents) + { + theEvent->decRef(); + return false; + } + NetEventNote *event = mEventNoteChunker.alloc(); + event->mEvent = theEvent; + theEvent->incRef(); + + event->mNextEvent = NULL; + if(theEvent->mGuaranteeType == NetEvent::GuaranteedOrdered) + { + event->mSeqCount = mNextSendEventSeq++; + if(!mSendEventQueueHead) + mSendEventQueueHead = event; + else + mSendEventQueueTail->mNextEvent = event; + mSendEventQueueTail = event; + } + else + { + event->mSeqCount = InvalidSendEventSeq; + if(!mUnorderedSendEventQueueHead) + mUnorderedSendEventQueueHead = event; + else + mUnorderedSendEventQueueTail->mNextEvent = event; + mUnorderedSendEventQueueTail = event; + } + return true; +} + + +void NetConnection::eventWriteStartBlock(ResizeBitStream *stream) +{ + stream->write(mNextRecvEventSeq); + for(NetEventNote *walk = mWaitSeqEvents; walk; walk = walk->mNextEvent) + { + stream->writeFlag(true); + S32 classId = walk->mEvent->getClassId(getNetClassGroup()); + stream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup()); + walk->mEvent->write(this, stream); + stream->validate(); + } + stream->writeFlag(false); +} + +void NetConnection::eventReadStartBlock(BitStream *stream) +{ + stream->read(&mNextRecvEventSeq); + + NetEventNote *lastEvent = NULL; + while(stream->readFlag()) + { + S32 classTag = stream->readClassId(NetClassTypeEvent, getNetClassGroup()); + NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classTag); + evt->unpack(this, stream); + NetEventNote *add = mEventNoteChunker.alloc(); + add->mEvent = evt; + evt->incRef(); + add->mNextEvent = NULL; + + if(!lastEvent) + mWaitSeqEvents = add; + else + lastEvent->mNextEvent = add; + lastEvent = add; + } +} diff --git a/sim/netGhost.cpp b/sim/netGhost.cpp new file mode 100644 index 0000000..3bee656 --- /dev/null +++ b/sim/netGhost.cpp @@ -0,0 +1,1246 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "core/dnet.h" +#include "console/simBase.h" +#include "sim/netConnection.h" +#include "core/stream/bitStream.h" +#include "sim/netObject.h" +//#include "core/resManager.h" +#include "console/console.h" +#include "console/consoleTypes.h" + +#define DebugChecksum 0xF00DBAAD + +Signal NetConnection::smGhostAlwaysDone; + +extern U32 gGhostUpdates; + +class GhostAlwaysObjectEvent : public NetEvent +{ + SimObjectId objectId; + U32 ghostIndex; + NetObject *object; + bool validObject; +public: + GhostAlwaysObjectEvent(NetObject *obj = NULL, U32 index = 0) + { + if(obj) + { + objectId = obj->getId(); + ghostIndex = index; + } + object = NULL; + } + ~GhostAlwaysObjectEvent() + { delete object; } + + void pack(NetConnection *ps, BitStream *bstream) + { + bstream->writeInt(ghostIndex, NetConnection::GhostIdBitSize); + + NetObject *obj = (NetObject *) Sim::findObject(objectId); + if(bstream->writeFlag(obj != NULL)) + { + S32 classId = obj->getClassId(ps->getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeObject, ps->getNetClassGroup()); +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + U32 retMask = obj->packUpdate(ps, 0xFFFFFFFF, bstream); + if ( retMask != 0 ) obj->setMaskBits( retMask ); + +#ifdef TORQUE_NET_STATS + obj->getClassRep()->updateNetStatPack(0xFFFFFFFF, bstream->getBitPosition() - beginSize); +#endif + } + } + void write(NetConnection *ps, BitStream *bstream) + { + bstream->writeInt(ghostIndex, NetConnection::GhostIdBitSize); + if(bstream->writeFlag(validObject)) + { + S32 classId = object->getClassId(ps->getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeObject, ps->getNetClassGroup()); +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + U32 retMask = object->packUpdate(ps, 0xFFFFFFFF, bstream); + if ( retMask != 0 ) object->setMaskBits( retMask ); +#ifdef TORQUE_NET_STATS + object->getClassRep()->updateNetStatPack(0xFFFFFFFF, bstream->getBitPosition() - beginSize); +#endif + } + } + void unpack(NetConnection *ps, BitStream *bstream) + { + ghostIndex = bstream->readInt(NetConnection::GhostIdBitSize); + + if(bstream->readFlag()) + { + S32 classId = bstream->readClassId(NetClassTypeObject, ps->getNetClassGroup()); + if(classId == -1) + { + ps->setLastError("Invalid packet. (invalid ghost class id)"); + return; + } + object = (NetObject *) ConsoleObject::create(ps->getNetClassGroup(), NetClassTypeObject, classId); + if(!object) + { + ps->setLastError("Invalid packet. (failed to created from class id)"); + return; + } + object->mNetFlags = NetObject::IsGhost; + object->mNetIndex = ghostIndex; + +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + object->unpackUpdate(ps, bstream); +#ifdef TORQUE_NET_STATS + object->getClassRep()->updateNetStatUnpack(bstream->getBitPosition() - beginSize); +#endif + validObject = true; + } + else + { + object = new NetObject; + validObject = false; + } + } + void process(NetConnection *ps) + { + Con::executef("onGhostAlwaysObjectReceived"); + + ps->setGhostAlwaysObject(object, ghostIndex); + object = NULL; + } + DECLARE_CONOBJECT(GhostAlwaysObjectEvent); +}; + +IMPLEMENT_CO_NETEVENT_V1(GhostAlwaysObjectEvent); + +ConsoleMethod( NetConnection, getGhostsActive, S32, 2, 2, "()" + "Returns number of ghosts active.") +{ + return object->getGhostsActive(); +} + +void NetConnection::setGhostTo(bool ghostTo) +{ + if(mLocalGhosts) // if ghosting to this is already enabled, silently return + return; + + if(ghostTo) + { + mLocalGhosts = new NetObject *[MaxGhostCount]; + for(S32 i = 0; i < MaxGhostCount; i++) + mLocalGhosts[i] = NULL; + } +} + +void NetConnection::setGhostFrom(bool ghostFrom) +{ + if(mGhostArray) + return; + + if(ghostFrom) + { + mGhostFreeIndex = mGhostZeroUpdateIndex = 0; + mGhostArray = new GhostInfo *[MaxGhostCount]; + mGhostRefs = new GhostInfo[MaxGhostCount]; + S32 i; + for(i = 0; i < MaxGhostCount; i++) + { + mGhostRefs[i].obj = NULL; + mGhostRefs[i].index = i; + mGhostRefs[i].updateMask = 0; + } + mGhostLookupTable = new GhostInfo *[GhostLookupTableSize]; + for(i = 0; i < GhostLookupTableSize; i++) + mGhostLookupTable[i] = 0; + } +} + +void NetConnection::ghostOnRemove() +{ + if(mGhostArray) + clearGhostInfo(); +} + +void NetConnection::ghostPacketDropped(PacketNotify *notify) +{ + GhostRef *packRef = notify->ghostList; + // loop through all the packRefs in the packet + + while(packRef) + { + GhostRef *temp = packRef->nextRef; + + U32 orFlags = 0; + AssertFatal(packRef->nextUpdateChain == NULL, "Out of order notify!!"); + + // clear out the ref for this object, plus or together all + // flags from updates after this + + GhostRef **walk = &(packRef->ghost->updateChain); + while(*walk != packRef) + { + orFlags |= (*walk)->mask; + walk = &((*walk)->nextUpdateChain); + } + *walk = 0; + + // for any flags we haven't updated since this (dropped) packet + // or them into the mask so they'll get updated soon + + orFlags = packRef->mask & ~orFlags; + + if(orFlags) + { + if(!packRef->ghost->updateMask) + { + packRef->ghost->updateMask = orFlags; + ghostPushNonZero(packRef->ghost); + } + else + packRef->ghost->updateMask |= orFlags; + } + + // if this packet was ghosting an object, set it + // to re ghost at it's earliest convenience + + if(packRef->ghostInfoFlags & GhostInfo::Ghosting) + { + packRef->ghost->flags |= GhostInfo::NotYetGhosted; + packRef->ghost->flags &= ~GhostInfo::Ghosting; + } + + // otherwise, if it was being deleted, + // set it to re-delete + + else if(packRef->ghostInfoFlags & GhostInfo::KillingGhost) + { + packRef->ghost->flags |= GhostInfo::KillGhost; + packRef->ghost->flags &= ~GhostInfo::KillingGhost; + } + + delete packRef; + packRef = temp; + } +} + +void NetConnection::ghostPacketReceived(PacketNotify *notify) +{ + GhostRef *packRef = notify->ghostList; + + // loop through all the notifies in this packet + + while(packRef) + { + GhostRef *temp = packRef->nextRef; + + AssertFatal(packRef->nextUpdateChain == NULL, "Out of order notify!!"); + + // clear this notify from the end of the object's notify + // chain + + GhostRef **walk = &(packRef->ghost->updateChain); + while(*walk != packRef) + walk = &((*walk)->nextUpdateChain); + + *walk = 0; + + // if this object was ghosting , it is now ghosted + + if(packRef->ghostInfoFlags & GhostInfo::Ghosting) + packRef->ghost->flags &= ~GhostInfo::Ghosting; + + // otherwise, if it was dieing, free the ghost + + else if(packRef->ghostInfoFlags & GhostInfo::KillingGhost) + freeGhostInfo(packRef->ghost); + + delete packRef; + packRef = temp; + } +} + +struct UpdateQueueEntry +{ + F32 priority; + GhostInfo *obj; + + UpdateQueueEntry(F32 in_priority, GhostInfo *in_obj) + { priority = in_priority; obj = in_obj; } +}; + +static S32 QSORT_CALLBACK UQECompare(const void *a,const void *b) +{ + GhostInfo *ga = *((GhostInfo **) a); + GhostInfo *gb = *((GhostInfo **) b); + + F32 ret = ga->priority - gb->priority; + return (ret < 0) ? -1 : ((ret > 0) ? 1 : 0); +} + +void NetConnection::ghostWritePacket(BitStream *bstream, PacketNotify *notify) +{ +#ifdef TORQUE_DEBUG_NET + bstream->writeInt(DebugChecksum, 32); +#endif + + notify->ghostList = NULL; + + if(!isGhostingFrom()) + return; + + if(!bstream->writeFlag(mGhosting)) + return; + + // fill a packet (or two) with ghosting data + + // first step is to check all our polled ghosts: + + // 1. Scope query - find if any new objects have come into + // scope and if any have gone out. + // 2. call scoped objects' priority functions if the flag set is nonzero + // A removed ghost is assumed to have a high priority + // 3. call updates based on sorted priority until the packet is + // full. set flags to zero for all updated objects + + CameraScopeQuery camInfo; + + camInfo.camera = NULL; + camInfo.pos.set(0,0,0); + camInfo.orientation.set(0,1,0); + camInfo.visibleDistance = 1; + camInfo.fov = (F32)(3.1415f / 4.0f); + camInfo.sinFov = 0.7071f; + camInfo.cosFov = 0.7071f; + + GhostInfo *walk; + + // only need to worry about the ghosts that have update masks set... + S32 maxIndex = 0; + S32 i; + for(i = 0; i < mGhostZeroUpdateIndex; i++) + { + // increment the updateSkip for everyone... it's all good + walk = mGhostArray[i]; + walk->updateSkipCount++; + if(!(walk->flags & (GhostInfo::ScopeAlways | GhostInfo::ScopeLocalAlways))) + walk->flags &= ~GhostInfo::InScope; + } + + if(mScopeObject) + mScopeObject->onCameraScopeQuery(this, &camInfo); + + for(i = mGhostZeroUpdateIndex - 1; i >= 0; i--) + { + if(!(mGhostArray[i]->flags & GhostInfo::InScope)) + detachObject(mGhostArray[i]); + } + + for(i = mGhostZeroUpdateIndex - 1; i >= 0; i--) + { + walk = mGhostArray[i]; + if(walk->index > maxIndex) + maxIndex = walk->index; + + // clear out any kill objects that haven't been ghosted yet + if((walk->flags & GhostInfo::KillGhost) && (walk->flags & GhostInfo::NotYetGhosted)) + { + freeGhostInfo(walk); + continue; + } + // don't do any ghost processing on objects that are being killed + // or in the process of ghosting + else if(!(walk->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting))) + { + if(walk->flags & GhostInfo::KillGhost) + walk->priority = 10000; + else + walk->priority = walk->obj->getUpdatePriority(&camInfo, walk->updateMask, walk->updateSkipCount); + } + else + walk->priority = 0; + } + GhostRef *updateList = NULL; + dQsort(mGhostArray, mGhostZeroUpdateIndex, sizeof(GhostInfo *), UQECompare); + + // reset the array indices... + for(i = mGhostZeroUpdateIndex - 1; i >= 0; i--) + mGhostArray[i]->arrayIndex = i; + + S32 sendSize = 1; + while(maxIndex >>= 1) + sendSize++; + + if(sendSize < 3) + sendSize = 3; + + bstream->writeInt(sendSize - 3, GhostIndexBitSize); + + U32 count = 0; + // + for(i = mGhostZeroUpdateIndex - 1; i >= 0 && !bstream->isFull(); i--) + { + GhostInfo *walk = mGhostArray[i]; + if(walk->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting)) + continue; + + bstream->writeFlag(true); + + bstream->writeInt(walk->index, sendSize); + U32 updateMask = walk->updateMask; + + GhostRef *upd = new GhostRef; + + upd->nextRef = updateList; + updateList = upd; + upd->nextUpdateChain = walk->updateChain; + walk->updateChain = upd; + + upd->ghost = walk; + upd->ghostInfoFlags = 0; + + if(walk->flags & GhostInfo::KillGhost) + { + walk->flags &= ~GhostInfo::KillGhost; + walk->flags |= GhostInfo::KillingGhost; + walk->updateMask = 0; + upd->mask = updateMask; + ghostPushToZero(walk); + upd->ghostInfoFlags = GhostInfo::KillingGhost; + bstream->writeFlag(true); // killing ghost + } + else + { + bstream->writeFlag(false); +#ifdef TORQUE_DEBUG_NET + U32 startPos = bstream->getCurPos(); +#endif + if(walk->flags & GhostInfo::NotYetGhosted) + { + S32 classId = walk->obj->getClassId(getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeObject, getNetClassGroup()); +#ifdef TORQUE_DEBUG_NET + bstream->writeInt(classId ^ DebugChecksum, 32); +#endif + + walk->flags &= ~GhostInfo::NotYetGhosted; + walk->flags |= GhostInfo::Ghosting; + upd->ghostInfoFlags = GhostInfo::Ghosting; + } +#ifdef TORQUE_DEBUG_NET + else { + S32 classId = walk->obj->getClassId(getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeObject, getNetClassGroup()); + bstream->writeInt(classId ^ DebugChecksum, 32); + } +#endif + // update the object +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + U32 retMask = walk->obj->packUpdate(this, updateMask, bstream); +#ifdef TORQUE_NET_STATS + walk->obj->getClassRep()->updateNetStatPack(updateMask, bstream->getBitPosition() - beginSize); +#endif + DEBUG_LOG(("PKLOG %d GHOST %d: %s", getId(), bstream->getBitPosition() - 16 - startPos, walk->obj->getClassName())); + + AssertFatal((retMask & (~updateMask)) == 0, "Cannot set new bits in packUpdate return"); + + ghostWriteExtra(walk->obj,bstream); + + walk->updateMask = retMask; + if(!retMask) + ghostPushToZero(walk); + + upd->mask = updateMask & ~retMask; + + //PacketStream::getStats()->addBits(PacketStats::Send, bstream->getCurPos() - startPos, walk->obj->getPersistTag()); +#ifdef TORQUE_DEBUG_NET + bstream->writeInt(walk->index ^ DebugChecksum, 32); +#endif + } + walk->updateSkipCount = 0; + count++; + } + //Con::printf("Ghosts updated: %d (%d remain)", count, mGhostZeroUpdateIndex); + // no more objects... + bstream->writeFlag(false); + notify->ghostList = updateList; +} + +void NetConnection::ghostReadPacket(BitStream *bstream) +{ +#ifdef TORQUE_DEBUG_NET + U32 sum = bstream->readInt(32); + AssertISV(sum == DebugChecksum, "Invalid checksum."); +#endif + + if(!isGhostingTo()) + return; + if(!bstream->readFlag()) + return; + + S32 idSize; + idSize = bstream->readInt( GhostIndexBitSize); + idSize += 3; + + // while there's an object waiting... + + while(bstream->readFlag()) + { + + gGhostUpdates++; + + U32 index; + //S32 startPos = bstream->getCurPos(); + index = (U32) bstream->readInt(idSize); + if(bstream->readFlag()) // is this ghost being deleted? + { + mGhostsActive--; + AssertFatal(mLocalGhosts[index] != NULL, "Error, NULL ghost encountered."); + mLocalGhosts[index]->deleteObject(); + mLocalGhosts[index] = NULL; + } + else + { + if(!mLocalGhosts[index]) // it's a new ghost... cool + { + mGhostsActive++; + S32 classId = bstream->readClassId(NetClassTypeObject, getNetClassGroup()); + if(classId == -1) + { + setLastError("Invalid packet. (invalid new ghost class id)"); + return; + } + + NetObject *obj = (NetObject *) ConsoleObject::create(getNetClassGroup(), NetClassTypeObject, classId); + if(!obj) + { + setLastError("Invalid packet. (failed to create new ghost)"); + return; + } + // remove all flags associated with netobject + obj->mNetFlags &= ~(BIT(NetObject::MaxNetFlagBit+1)-1); + // we're a ghost... + obj->mNetFlags |= NetObject::IsGhost; + + // object gets initial update before adding to the manager + + obj->mNetIndex = index; + mLocalGhosts[index] = obj; +#ifdef TORQUE_DEBUG_NET + U32 checksum = bstream->readInt(32); + S32 origId = checksum ^ DebugChecksum; + AssertISV(mLocalGhosts[index] != NULL, "Invalid dest ghost."); + AssertISV(origId == mLocalGhosts[index]->getClassId(getNetClassGroup()), + avar("class id mismatch for dest class %s.", + mLocalGhosts[index]->getClassName()) ); +#endif + + // give derived classes a chance to prepare ghost for reading + ghostPreRead(mLocalGhosts[index],true); + +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + mLocalGhosts[index]->unpackUpdate(this, bstream); +#ifdef TORQUE_NET_STATS + mLocalGhosts[index]->getClassRep()->updateNetStatUnpack(bstream->getBitPosition() - beginSize); +#endif + // Setup the remote object pointers before + // we register so that it can be used from onAdd. + if( mRemoteConnection ) + { + obj->mServerObject = mRemoteConnection->resolveObjectFromGhostIndex(index); + if ( obj->mServerObject ) + obj->mServerObject->mClientObject = obj; + } + + if(!obj->registerObject()) + { + if(mErrorBuffer.isEmpty()) + setLastError("Invalid packet. (failed to register ghost)"); + return; + } + + addObject(obj); + ghostReadExtra(mLocalGhosts[index],bstream,true); + } + else + { +#ifdef TORQUE_DEBUG_NET + S32 classId = bstream->readClassId(NetClassTypeObject, getNetClassGroup()); + U32 checksum = bstream->readInt(32); + S32 origId = checksum ^ DebugChecksum; + AssertISV(mLocalGhosts[index] != NULL, "Invalid dest ghost."); + AssertISV(origId == mLocalGhosts[index]->getClassId(getNetClassGroup()), + avar("class id mismatch for dest class %s.", + mLocalGhosts[index]->getClassName()) ); +#endif + // give derived classes a chance to prepare ghost for reading + ghostPreRead(mLocalGhosts[index],false); + +#ifdef TORQUE_NET_STATS + U32 beginSize = bstream->getBitPosition(); +#endif + mLocalGhosts[index]->unpackUpdate(this, bstream); +#ifdef TORQUE_NET_STATS + mLocalGhosts[index]->getClassRep()->updateNetStatUnpack(bstream->getBitPosition() - beginSize); +#endif + ghostReadExtra(mLocalGhosts[index],bstream,false); + } + //PacketStream::getStats()->addBits(PacketStats::Receive, bstream->getCurPos() - startPos, ghostRefs[index].localGhost->getPersistTag()); +#ifdef TORQUE_DEBUG_NET + U32 checksum = bstream->readInt(32); + S32 origIndex = checksum ^ DebugChecksum; + AssertISV(origIndex == index, + avar("unpackUpdate did not match packUpdate for object of class %s.", + mLocalGhosts[index]->getClassName()) ); +#endif + if(mErrorBuffer.isNotEmpty()) + return; + } + } +} + +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- + +void NetConnection::setScopeObject(NetObject *obj) +{ + if(((NetObject *) mScopeObject) == obj) + return; + mScopeObject = obj; +} + +void NetConnection::detachObject(GhostInfo *info) +{ + // mark it for ghost killin' + info->flags |= GhostInfo::KillGhost; + + // if the mask is in the zero range, we've got to move it up... + if(!info->updateMask) + { + info->updateMask = 0xFFFFFFFF; + ghostPushNonZero(info); + } + if(info->obj) + { + if(info->prevObjectRef) + info->prevObjectRef->nextObjectRef = info->nextObjectRef; + else + info->obj->mFirstObjectRef = info->nextObjectRef; + if(info->nextObjectRef) + info->nextObjectRef->prevObjectRef = info->prevObjectRef; + + // remove it from the lookup table + U32 id = info->obj->getId(); + for(GhostInfo **walk = &mGhostLookupTable[id & (GhostLookupTableSize - 1)]; *walk; walk = &((*walk)->nextLookupInfo)) + { + GhostInfo *temp = *walk; + if(temp == info) + { + *walk = temp->nextLookupInfo; + break; + } + } + info->prevObjectRef = info->nextObjectRef = NULL; + info->obj = NULL; + } +} + +void NetConnection::freeGhostInfo(GhostInfo *ghost) +{ + AssertFatal(ghost->arrayIndex < mGhostFreeIndex, "Ghost already freed."); + if(ghost->arrayIndex < mGhostZeroUpdateIndex) + { + AssertFatal(ghost->updateMask != 0, "Invalid ghost mask."); + ghost->updateMask = 0; + ghostPushToZero(ghost); + } + ghostPushZeroToFree(ghost); + AssertFatal(ghost->updateChain == NULL, "Ack!"); +} + +//----------------------------------------------------------------------------- + +void NetConnection::objectLocalScopeAlways(NetObject *obj) +{ + if(!isGhostingFrom()) + return; + objectInScope(obj); + for(GhostInfo *walk = mGhostLookupTable[obj->getId() & (GhostLookupTableSize - 1)]; walk; walk = walk->nextLookupInfo) + { + if(walk->obj != obj) + continue; + walk->flags |= GhostInfo::ScopeLocalAlways; + return; + } +} + +void NetConnection::objectLocalClearAlways(NetObject *obj) +{ + if(!isGhostingFrom()) + return; + for(GhostInfo *walk = mGhostLookupTable[obj->getId() & (GhostLookupTableSize - 1)]; walk; walk = walk->nextLookupInfo) + { + if(walk->obj != obj) + continue; + walk->flags &= ~GhostInfo::ScopeLocalAlways; + return; + } +} + +bool NetConnection::validateGhostArray() +{ + AssertFatal(mGhostZeroUpdateIndex >= 0 && mGhostZeroUpdateIndex <= mGhostFreeIndex, "Invalid update index range."); + AssertFatal(mGhostFreeIndex <= MaxGhostCount, "Invalid free index range."); + U32 i; + for(i = 0; i < mGhostZeroUpdateIndex; i ++) + { + AssertFatal(mGhostArray[i]->arrayIndex == i, "Invalid array index."); + AssertFatal(mGhostArray[i]->updateMask != 0, "Invalid ghost mask."); + } + for(; i < mGhostFreeIndex; i ++) + { + AssertFatal(mGhostArray[i]->arrayIndex == i, "Invalid array index."); + AssertFatal(mGhostArray[i]->updateMask == 0, "Invalid ghost mask."); + } + for(; i < MaxGhostCount; i++) + { + AssertFatal(mGhostArray[i]->arrayIndex == i, "Invalid array index."); + } + return true; +} + +void NetConnection::objectInScope(NetObject *obj) +{ + if (!mScoping || !isGhostingFrom()) + return; + if (obj->isScopeLocal() && !isLocalConnection()) + return; + + S32 index = obj->getId() & (GhostLookupTableSize - 1); + + // check if it's already in scope + // the object may have been cleared out without the lookupTable being cleared + // so validate that the object pointers are the same. + + for(GhostInfo *walk = mGhostLookupTable[index ]; walk; walk = walk->nextLookupInfo) + { + if(walk->obj != obj) + continue; + walk->flags |= GhostInfo::InScope; + + // Make sure scope always if reflected on the ghostinfo too + if (obj->mNetFlags.test(NetObject::ScopeAlways)) + walk->flags |= GhostInfo::ScopeAlways; + + return; + } + + if (mGhostFreeIndex == MaxGhostCount) + { + AssertWarn(0,"NetConnection::objectInScope: too many ghosts"); + return; + } + + GhostInfo *giptr = mGhostArray[mGhostFreeIndex]; + ghostPushFreeToZero(giptr); + giptr->updateMask = 0xFFFFFFFF; + ghostPushNonZero(giptr); + + giptr->flags = GhostInfo::NotYetGhosted | GhostInfo::InScope; + + if(obj->mNetFlags.test(NetObject::ScopeAlways)) + giptr->flags |= GhostInfo::ScopeAlways; + + giptr->obj = obj; + giptr->updateChain = NULL; + giptr->updateSkipCount = 0; + + giptr->connection = this; + + giptr->nextObjectRef = obj->mFirstObjectRef; + if(obj->mFirstObjectRef) + obj->mFirstObjectRef->prevObjectRef = giptr; + giptr->prevObjectRef = NULL; + obj->mFirstObjectRef = giptr; + + giptr->nextLookupInfo = mGhostLookupTable[index]; + mGhostLookupTable[index] = giptr; + //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +//----------------------------------------------------------------------------- + +void NetConnection::handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount) +{ + if(( message == SendNextDownloadRequest + || message == FileDownloadSizeMessage + || message == GhostAlwaysStarting + || message == GhostAlwaysDone + || message == EndGhosting) && !isGhostingTo()) + { + setLastError("Invalid packet. (not ghosting)"); + return; + } + + S32 i; + GhostSave sv; + switch(message) + { + case GhostAlwaysDone: + mGhostingSequence = sequence; + NetConnection::smGhostAlwaysDone.trigger(); + // ok, all the ghost always objects are now on the client... but! + // it's possible that there were some file load errors... + // if so, we need to indicate to the server to restart ghosting, after + // we download all the files... + sv.ghost = NULL; + sv.index = -1; + mGhostAlwaysSaveList.push_back(sv); + if(mGhostAlwaysSaveList.size() == 1) + loadNextGhostAlwaysObject(true); + break; + case ReadyForNormalGhosts: + if(sequence != mGhostingSequence) + return; + Con::executef(this, "onGhostAlwaysObjectsReceived"); + Con::printf("Ghost Always objects received."); + mGhosting = true; + for(i = 0; i < mGhostFreeIndex; i++) + { + if(mGhostArray[i]->flags & GhostInfo::ScopedEvent) + mGhostArray[i]->flags &= ~(GhostInfo::Ghosting | GhostInfo::ScopedEvent); + } + break; + case EndGhosting: + // just delete all the local ghosts, + // and delete all the ghosts in the current save list + for(i = 0; i < MaxGhostCount; i++) + { + if(mLocalGhosts[i]) + { + mLocalGhosts[i]->deleteObject(); + mLocalGhosts[i] = NULL; + } + } + while(mGhostAlwaysSaveList.size()) + { + delete mGhostAlwaysSaveList[0].ghost; + mGhostAlwaysSaveList.pop_front(); + } + break; + case GhostAlwaysStarting: + Con::executef("onGhostAlwaysStarted", Con::getIntArg(ghostCount)); + break; + case SendNextDownloadRequest: + sendNextFileDownloadRequest(); + break; + case FileDownloadSizeMessage: + mCurrentFileBufferSize = sequence; + mCurrentFileBuffer = dRealloc(mCurrentFileBuffer, mCurrentFileBufferSize); + mCurrentFileBufferOffset = 0; + break; + } +} + +void NetConnection::activateGhosting() +{ + if(!isGhostingFrom()) + return; + + mGhostingSequence++; + + // iterate through the ghost always objects and InScope them... + // also post em all to the other side. + + SimSet* ghostAlwaysSet = Sim::getGhostAlwaysSet(); + + SimSet::iterator i; + + AssertFatal((mGhostFreeIndex == 0) && (mGhostZeroUpdateIndex == 0), "Error: ghosts in the ghost list before activate."); + + U32 sz = ghostAlwaysSet->size(); + S32 j; + + for(j = 0; j < sz; j++) + { + U32 idx = MaxGhostCount - sz + j; + mGhostArray[j] = mGhostRefs + idx; + mGhostArray[j]->arrayIndex = j; + } + for(j = sz; j < MaxGhostCount; j++) + { + U32 idx = j - sz; + mGhostArray[j] = mGhostRefs + idx; + mGhostArray[j]->arrayIndex = j; + } + mScoping = true; // so that objectInScope will work + for(i = ghostAlwaysSet->begin(); i != ghostAlwaysSet->end(); i++) + { + AssertFatal(dynamic_cast(*i) != NULL, avar("Non NetObject in GhostAlwaysSet: %s", (*i)->getClassName())); + NetObject *obj = (NetObject *)(*i); + if(obj->mNetFlags.test(NetObject::Ghostable)) + objectInScope(obj); + } + // Send the initial ghosting connection message. + sendConnectionMessage(GhostAlwaysStarting, mGhostingSequence, ghostAlwaysSet->size()); + + // If this is the connection to the local client... + if (getLocalClientConnection() == this) + { + // Get a pointer to the local client. + NetConnection* pClient = NetConnection::getConnectionToServer(); + + // Iterate through the scope always objects... + for (j = mGhostZeroUpdateIndex - 1; j >= 0; j--) + { + AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "NetConnection::activateGhosting: Non-scope always in the scope always list.") + + // Clear the ghost update mask and flags appropriately. + mGhostArray[j]->updateMask = 0; + ghostPushToZero(mGhostArray[j]); + mGhostArray[j]->flags &= ~GhostInfo::NotYetGhosted; + mGhostArray[j]->flags |= GhostInfo::ScopedEvent; + + // Set up a pointer to the new object. + NetObject* pObject = 0; + + // If there's a valid ghost object... + if (mGhostArray[j]->obj) + { + // Set up a buffer for the object send. + U8 iBuffer[4096]; + BitStream mStream(iBuffer, 4096); + + // Pack the server object's update. + U32 retMask = mGhostArray[j]->obj->packUpdate(this, 0xFFFFFFFF, &mStream); + if ( retMask != 0 ) mGhostArray[j]->obj->setMaskBits( retMask ); + + // Set the stream position back to zero. + mStream.setPosition(0); + + // Create a new object instance for the client. + pObject = (NetObject*)ConsoleObject::create(pClient->getNetClassGroup(), NetClassTypeObject, mGhostArray[j]->obj->getClassId(getNetClassGroup())); + + // Set the client object networking flags. + pObject->mNetFlags = NetObject::IsGhost; + pObject->mNetIndex = mGhostArray[j]->index; + + // Unpack the client object's update. + pObject->unpackUpdate(pClient, &mStream); + } + else + { + // Otherwise, create a new dummy netobject. + pObject = new NetObject; + } + + // Execute the appropriate console callback. + Con::executef("onGhostAlwaysObjectReceived"); + + // Set the ghost always object for the client. + pClient->setGhostAlwaysObject(pObject, mGhostArray[j]->index); + } + } + else + { + // Iterate through the scope always objects... + for (j = mGhostZeroUpdateIndex - 1; j >= 0; j--) + { + AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "NetConnection::activateGhosting: Non-scope always in the scope always list.") + + // Clear the ghost update mask and flags appropriately. + mGhostArray[j]->updateMask = 0; + ghostPushToZero(mGhostArray[j]); + mGhostArray[j]->flags &= ~GhostInfo::NotYetGhosted; + mGhostArray[j]->flags |= GhostInfo::ScopedEvent; + + // Post a network event to ghost the scope always object. + postNetEvent(new GhostAlwaysObjectEvent(mGhostArray[j]->obj, mGhostArray[j]->index)); + } + } + + // Send the ghosting always done message. + sendConnectionMessage(GhostAlwaysDone, mGhostingSequence); //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +void NetConnection::clearGhostInfo() +{ + // gotta clear out the ghosts... + for(PacketNotify *walk = mNotifyQueueHead; walk; walk = walk->nextPacket) + { + ghostPacketReceived(walk); + walk->ghostList = NULL; + } + for(S32 i = 0; i < MaxGhostCount; i++) + { + if(mGhostRefs[i].arrayIndex < mGhostFreeIndex) + { + detachObject(&mGhostRefs[i]); + freeGhostInfo(&mGhostRefs[i]); + } + } + AssertFatal((mGhostFreeIndex == 0) && (mGhostZeroUpdateIndex == 0), "Invalid indices."); +} + +void NetConnection::resetGhosting() +{ + if(!isGhostingFrom()) + return; + // stop all ghosting activity + // send a message to the other side notifying of this + + mGhosting = false; + mScoping = false; + sendConnectionMessage(EndGhosting, mGhostingSequence); + mGhostingSequence++; + clearGhostInfo(); + //AssertFatal(validateGhostArray(), "Invalid ghost array!"); +} + +void NetConnection::setGhostAlwaysObject(NetObject *object, U32 index) +{ + if(!isGhostingTo()) + { + object->deleteObject(); + setLastError("Invalid packet. (unexpected ghostalways)"); + return; + } + object->mNetFlags = NetObject::IsGhost; + object->mNetIndex = index; + + // while there's an object waiting... + if ( isLocalConnection() ) + { + object->mServerObject = mRemoteConnection->resolveObjectFromGhostIndex(index); + if ( object->mServerObject ) + object->mServerObject->mClientObject = object; + } + + GhostSave sv; + sv.ghost = object; + sv.index = index; + mGhostAlwaysSaveList.push_back(sv); + + // check if we are already downloading files for a previous object: + if(mGhostAlwaysSaveList.size() == 1) + loadNextGhostAlwaysObject(true); // the initial call always has "new" files + +} + +void NetConnection::fileDownloadSegmentComplete() +{ + // this is called when a the file list has finished processing... + // at this point we can try again to add the object + // subclasses can override this to do, for example, datablock redos. + if(mGhostAlwaysSaveList.size()) + loadNextGhostAlwaysObject(mNumDownloadedFiles != 0); +} + +void NetConnection::loadNextGhostAlwaysObject(bool hadNewFiles) +{ + if(!mGhostAlwaysSaveList.size()) + return; + + while(mGhostAlwaysSaveList.size()) + { + + if (isLocalConnection()) hadNewFiles = false; + + // only check for new files if this is the first load, or if new + // files were downloaded from the server. +// if(hadNewFiles) +// gResourceManager->setMissingFileLogging(true); +// +// gResourceManager->clearMissingFileList(); + NetObject *object = mGhostAlwaysSaveList[0].ghost; + U32 index = mGhostAlwaysSaveList[0].index; + + if(!object) + { + // a null object is used to signify that the last ghost in the list is down + mGhostAlwaysSaveList.pop_front(); + AssertFatal(mGhostAlwaysSaveList.size() == 0, "Error! Ghost save list should be empty!"); + sendConnectionMessage(ReadyForNormalGhosts, mGhostingSequence); +// gResourceManager->setMissingFileLogging(false); + return; + } + mFilesWereDownloaded = hadNewFiles; + + if(!object->registerObject()) + { + mFilesWereDownloaded = false; + // make sure there's an error message if necessary + if(mErrorBuffer.isEmpty()) + setLastError("Invalid packet. (failed to register ghost always)"); + + // if there were no new files, make sure the error message + // is the one from the last time we tried to add this object + if(!hadNewFiles) + { + mErrorBuffer = mLastFileErrorBuffer; +// gResourceManager->setMissingFileLogging(false); + return; + } + + // object failed to load, let's see if it had any missing files +// if(!gResourceManager->getMissingFileList(mMissingFileList)) +// { +// // no missing files, must be an error +// // connection will automagically delete the ghost always list +// // when this error is reported. +// gResourceManager->setMissingFileLogging(false); +// return; +// } + + // ok, copy the error buffer out to a scratch pad for now + mLastFileErrorBuffer = mErrorBuffer; + mErrorBuffer = String(); + + // request the missing files... + mNumDownloadedFiles = 0; + sendNextFileDownloadRequest(); + break; + } + mFilesWereDownloaded = false; +// gResourceManager->setMissingFileLogging(false); + addObject(object); + mGhostAlwaysSaveList.pop_front(); + + AssertFatal(mLocalGhosts[index] == NULL, "Ghost already in table!"); + mLocalGhosts[index] = object; + hadNewFiles = true; + } +} + +//----------------------------------------------------------------------------- + +NetObject *NetConnection::resolveGhost(S32 id) +{ + return mLocalGhosts[id]; +} + +NetObject *NetConnection::resolveObjectFromGhostIndex(S32 id) +{ + return mGhostRefs[id].obj; +} + +S32 NetConnection::getGhostIndex(NetObject *obj) +{ + if(!isGhostingFrom()) + return obj->mNetIndex; + S32 index = obj->getId() & (GhostLookupTableSize - 1); + + for(GhostInfo *gptr = mGhostLookupTable[index]; gptr; gptr = gptr->nextLookupInfo) + { + if(gptr->obj == obj && (gptr->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting | GhostInfo::NotYetGhosted | GhostInfo::KillGhost)) == 0) + return gptr->index; + } + return -1; +} + +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- + +void NetConnection::ghostWriteStartBlock(ResizeBitStream *stream) +{ + // Ok, writing the start block for the ghosts: + // here's how it goes. + // + // First we record out all the indices and class ids for all the objects + // This is so when the objects are read in, all the objects are instantiated + // before they are unpacked. The unpack code may reference other + // existing ghosts, so we want to make sure that all the ghosts are in the + // table with the correct pointers before any of the unpacks are called. + + stream->write(mGhostingSequence); + + // first write out the indices and ids: + for(U32 i = 0; i < MaxGhostCount; i++) + { + if(mLocalGhosts[i]) + { + stream->writeFlag(true); + stream->writeInt(i, GhostIdBitSize); + stream->writeClassId(mLocalGhosts[i]->getClassId(getNetClassGroup()), NetClassTypeObject, getNetClassGroup()); + stream->validate(); + } + } + // mark off the end of the ghost list: + // it would be more space efficient to write out a count of active ghosts followed + // by index run lengths, but hey, what's a few bits here and there? + + stream->writeFlag(false); + + // then, for each ghost written into the start block, write the full pack update + // into the start block. For demos to work properly, packUpdate must + // be callable from client objects. + for(U32 i = 0; i < MaxGhostCount; i++) + { + if(mLocalGhosts[i]) + { + U32 retMask = mLocalGhosts[i]->packUpdate(this, 0xFFFFFFFF, stream); + if ( retMask != 0 ) mLocalGhosts[i]->setMaskBits( retMask ); + stream->validate(); + } + } +} + +void NetConnection::ghostReadStartBlock(BitStream *stream) +{ + stream->read(&mGhostingSequence); + + // read em back in. + // first, read in the index/class id, construct the object, and place it in mLocalGhosts[i] + + while(stream->readFlag()) + { + U32 index = stream->readInt(GhostIdBitSize); + S32 tag = stream->readClassId(NetClassTypeObject, getNetClassGroup()); + NetObject *obj = (NetObject *) ConsoleObject::create(getNetClassGroup(), NetClassTypeObject, tag); + if(!obj) + { + setLastError("Invalid packet. (failed to create ghost from demo block)"); + return; + } + obj->mNetFlags = NetObject::IsGhost; + obj->mNetIndex = index; + mLocalGhosts[index] = obj; + } + + // now, all the ghosts are in the mLocalGhosts, so we loop + // through all non-null mLocalGhosts, unpacking the objects + // as we go: + + for(U32 i = 0; i < MaxGhostCount; i++) + { + if(mLocalGhosts[i]) + { + mLocalGhosts[i]->unpackUpdate(this, stream); + if(!mLocalGhosts[i]->registerObject()) + { + if(mErrorBuffer.isEmpty()) + setLastError("Invalid packet. (failed to register ghost from demo block)"); + return; + } + addObject(mLocalGhosts[i]); + } + } + // MARKF - TODO - looks like we could have memory leaks here + // if there are errors. +} diff --git a/sim/netInterface.cpp b/sim/netInterface.cpp new file mode 100644 index 0000000..c10a303 --- /dev/null +++ b/sim/netInterface.cpp @@ -0,0 +1,634 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "platform/event.h" +#include "sim/netConnection.h" +#include "sim/netInterface.h" +#include "core/stream/bitStream.h" +#include "math/mRandom.h" +#include "core/util/journal/journal.h" + +#ifdef GGC_PLUGIN +#include "GGCNatTunnel.h" +extern void HandleGGCPacket(NetAddress* addr, unsigned char* data, U32 dataSize); +#endif + +NetInterface *GNet = NULL; + +NetInterface::NetInterface() +{ + AssertFatal(GNet == NULL, "ERROR: Multiple net interfaces declared."); + GNet = this; + + mLastTimeoutCheckTime = 0; + mAllowConnections = true; + +} + +void NetInterface::initRandomData() +{ + mRandomDataInitialized = true; + U32 seed = Platform::getRealMilliseconds(); + + if(Journal::IsPlaying()) + Journal::Read(&seed); + else if(Journal::IsRecording()) + Journal::Write(seed); + + MRandomR250 myRandom(seed); + for(U32 i = 0; i < 12; i++) + mRandomHashData[i] = myRandom.randI(); +} + +void NetInterface::addPendingConnection(NetConnection *connection) +{ + Con::printf("Adding a pending connection"); + mPendingConnections.push_back(connection); +} + +void NetInterface::removePendingConnection(NetConnection *connection) +{ + for(U32 i = 0; i < mPendingConnections.size(); i++) + if(mPendingConnections[i] == connection) + mPendingConnections.erase(i); +} + +NetConnection *NetInterface::findPendingConnection(const NetAddress *address, U32 connectSequence) +{ + for(U32 i = 0; i < mPendingConnections.size(); i++) + if(Net::compareAddresses(address, mPendingConnections[i]->getNetAddress()) && + connectSequence == mPendingConnections[i]->getSequence()) + return mPendingConnections[i]; + return NULL; +} + +void NetInterface::processPacketReceiveEvent(NetAddress srcAddress, U32 pPacketData,int bytes) +{ + char * packetData = (char*)pPacketData; + U32 dataSize = bytes; + BitStream pStream(packetData, dataSize); + + // Determine what to do with this packet: + + if(packetData[0] & 0x01) // it's a protocol packet... + { + // if the LSB of the first byte is set, it's a game data packet + // so pass it to the appropriate connection. + + // lookup the connection in the addressTable + NetConnection *conn = NetConnection::lookup(&srcAddress); + if(conn) + conn->processRawPacket(&pStream); + } + else + { + // Otherwise, it's either a game info packet or a + // connection handshake packet. + + U8 packetType; + pStream.read(&packetType); + NetAddress *addr = &srcAddress; + + if(packetType <= GameHeartbeat) + handleInfoPacket(addr, packetType, &pStream); +#ifdef GGC_PLUGIN + else if (packetType == GGCPacket) + { + HandleGGCPacket(addr, (U8*)packetData.data, dataSize); + } +#endif + else + { + // check if there's a connection already: + switch(packetType) + { + case ConnectChallengeRequest: + handleConnectChallengeRequest(addr, &pStream); + break; + case ConnectRequest: + handleConnectRequest(addr, &pStream); + break; + case ConnectChallengeResponse: + handleConnectChallengeResponse(addr, &pStream); + break; + case ConnectAccept: + handleConnectAccept(addr, &pStream); + break; + case Disconnect: + handleDisconnect(addr, &pStream); + break; + case ConnectReject: + handleConnectReject(addr, &pStream); + break; + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// Connection handshaking basic overview: +// The torque engine does a two phase connect handshake to +// prevent a spoofed source address Denial-of-Service (DOS) attack +// +// Basically, the initiator of a connection (client) sends a +// Connect Challenge Request packet to the server to initiate the connection +// The server then hashes the source address of the client request +// with some random magic server data to come up with a 16-byte key that +// the client can then use to gain entry to the server. +// This way there are no partially active connection records on the +// server at all. +// +// The client then sends a Connect Request packet to the server, +// including any game specific data necessary to start a connection (a +// server password, for instance), along with the key the server sent +// on the Connect Challenge Response packet. +// +// The server, on receipt of the Connect Request, compares the +// entry key with a computed key, makes sure it can create the requested +// NetConnection subclass, and then passes all processing on to the connection +// instance. +// +// If the subclass reads and accepts he connect request successfully, the +// server sends a Connect Accept packet - otherwise the connection +// is rejected with the sendConnectReject function +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +void NetInterface::sendConnectChallengeRequest(NetConnection *conn) +{ + Con::printf("Sending Connect challenge Request"); + BitStream *out = BitStream::getPacketStream(); + + out->write(U8(ConnectChallengeRequest)); + out->write(conn->getSequence()); + + conn->mConnectSendCount++; + conn->mConnectLastSendTime = Platform::getVirtualMilliseconds(); + + BitStream::sendPacketStream(conn->getNetAddress()); +} + +void NetInterface::handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream) +{ + char buf[256]; + Net::addressToString(addr, buf); + Con::printf("Got Connect challenge Request from %s", buf); + if(!mAllowConnections) + return; + + U32 connectSequence; + stream->read(&connectSequence); + + if(!mRandomDataInitialized) + initRandomData(); + + U32 addressDigest[4]; + computeNetMD5(addr, connectSequence, addressDigest); + + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectChallengeResponse)); + out->write(connectSequence); + out->write(addressDigest[0]); + out->write(addressDigest[1]); + out->write(addressDigest[2]); + out->write(addressDigest[3]); + + BitStream::sendPacketStream(addr); +} + +//----------------------------------------------------------------------------- + +void NetInterface::handleConnectChallengeResponse(const NetAddress *address, BitStream *stream) +{ + Con::printf("Got Connect challenge Response"); + U32 connectSequence; + stream->read(&connectSequence); + + NetConnection *conn = findPendingConnection(address, connectSequence); + if(!conn || conn->getConnectionState() != NetConnection::AwaitingChallengeResponse) + return; + + U32 addressDigest[4]; + stream->read(&addressDigest[0]); + stream->read(&addressDigest[1]); + stream->read(&addressDigest[2]); + stream->read(&addressDigest[3]); + conn->setAddressDigest(addressDigest); + + conn->setConnectionState(NetConnection::AwaitingConnectResponse); + conn->mConnectSendCount = 0; + Con::printf("Sending Connect Request"); + sendConnectRequest(conn); +} + +//----------------------------------------------------------------------------- + +void NetInterface::sendConnectRequest(NetConnection *conn) +{ + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectRequest)); + out->write(conn->getSequence()); + + U32 addressDigest[4]; + conn->getAddressDigest(addressDigest); + out->write(addressDigest[0]); + out->write(addressDigest[1]); + out->write(addressDigest[2]); + out->write(addressDigest[3]); + + out->writeString(conn->getClassName()); + conn->writeConnectRequest(out); + conn->mConnectSendCount++; + conn->mConnectLastSendTime = Platform::getVirtualMilliseconds(); + + BitStream::sendPacketStream(conn->getNetAddress()); +} + +//----------------------------------------------------------------------------- + +void NetInterface::handleConnectRequest(const NetAddress *address, BitStream *stream) +{ + if(!mAllowConnections) + return; + Con::printf("Got Connect Request"); + U32 connectSequence; + stream->read(&connectSequence); + + // see if the connection is in the main connection table: + + NetConnection *connect = NetConnection::lookup(address); + if(connect && connect->getSequence() == connectSequence) + { + sendConnectAccept(connect); + return; + } + U32 addressDigest[4]; + U32 computedAddressDigest[4]; + + stream->read(&addressDigest[0]); + stream->read(&addressDigest[1]); + stream->read(&addressDigest[2]); + stream->read(&addressDigest[3]); + + computeNetMD5(address, connectSequence, computedAddressDigest); + if(addressDigest[0] != computedAddressDigest[0] || + addressDigest[1] != computedAddressDigest[1] || + addressDigest[2] != computedAddressDigest[2] || + addressDigest[3] != computedAddressDigest[3]) + return; // bogus connection attempt + + if(connect) + { + if(connect->getSequence() > connectSequence) + return; // the existing connection should be kept - the incoming request is stale. + else + connect->deleteObject(); // disconnect this one, and allow the new one to be created. + } + + char connectionClass[255]; + stream->readString(connectionClass); + + ConsoleObject *co = ConsoleObject::create(connectionClass); + NetConnection *conn = dynamic_cast(co); + if(!conn || !conn->canRemoteCreate()) + { + delete co; + return; + } + conn->registerObject(); + conn->setNetAddress(address); + conn->setNetworkConnection(true); + conn->setSequence(connectSequence); + + const char *errorString = NULL; + if(!conn->readConnectRequest(stream, &errorString)) + { + sendConnectReject(conn, errorString); + conn->deleteObject(); + return; + } + conn->setNetworkConnection(true); + conn->onConnectionEstablished(false); + conn->setEstablished(); + conn->setConnectSequence(connectSequence); + sendConnectAccept(conn); +} + +//----------------------------------------------------------------------------- + +void NetInterface::sendConnectAccept(NetConnection *conn) +{ + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectAccept)); + out->write(conn->getSequence()); + conn->writeConnectAccept(out); + BitStream::sendPacketStream(conn->getNetAddress()); +} + +void NetInterface::handleConnectAccept(const NetAddress *address, BitStream *stream) +{ + U32 connectSequence; + stream->read(&connectSequence); + NetConnection *conn = findPendingConnection(address, connectSequence); + if(!conn || conn->getConnectionState() != NetConnection::AwaitingConnectResponse) + return; + const char *errorString = NULL; + if(!conn->readConnectAccept(stream, &errorString)) + { + conn->handleStartupError(errorString); + removePendingConnection(conn); + conn->deleteObject(); + return; + } + + removePendingConnection(conn); // remove from the pending connection list + conn->setNetworkConnection(true); + conn->onConnectionEstablished(true); // notify the connection that it has been established + conn->setEstablished(); // installs the connection in the connection table, and causes pings/timeouts to happen + conn->setConnectSequence(connectSequence); +} + +void NetInterface::sendConnectReject(NetConnection *conn, const char *reason) +{ + if(!reason) + return; // if the stream is NULL, we reject silently + + BitStream *out = BitStream::getPacketStream(); + out->write(U8(ConnectReject)); + out->write(conn->getSequence()); + out->writeString(reason); + BitStream::sendPacketStream(conn->getNetAddress()); +} + +void NetInterface::handleConnectReject(const NetAddress *address, BitStream *stream) +{ + U32 connectSequence; + stream->read(&connectSequence); + NetConnection *conn = findPendingConnection(address, connectSequence); + if(!conn || (conn->getConnectionState() != NetConnection::AwaitingChallengeResponse && + conn->getConnectionState() != NetConnection::AwaitingConnectResponse)) + return; + removePendingConnection(conn); + char reason[256]; + stream->readString(reason); + conn->onConnectionRejected(reason); + conn->deleteObject(); +} + +void NetInterface::handleDisconnect(const NetAddress *address, BitStream *stream) +{ + NetConnection *conn = NetConnection::lookup(address); + if(!conn) + return; + + U32 connectSequence; + char reason[256]; + + stream->read(&connectSequence); + stream->readString(reason); + + if(conn->getSequence() != connectSequence) + return; + + conn->onDisconnect(reason); + conn->deleteObject(); +} + +void NetInterface::handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream) +{ +} + +void NetInterface::processClient() +{ + NetObject::collapseDirtyList(); // collapse all the mask bits... + for(NetConnection *walk = NetConnection::getConnectionList(); + walk; walk = walk->getNext()) + { + if(walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection())) + walk->checkPacketSend(false); + } +} + +void NetInterface::processServer() +{ + NetObject::collapseDirtyList(); // collapse all the mask bits... + for(NetConnection *walk = NetConnection::getConnectionList(); + walk; walk = walk->getNext()) + { + if(!walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection())) + walk->checkPacketSend(false); + } +} + +void NetInterface::startConnection(NetConnection *conn) +{ + addPendingConnection(conn); + conn->mConnectionSendCount = 0; + conn->setConnectSequence(Platform::getVirtualMilliseconds()); + conn->setConnectionState(NetConnection::AwaitingChallengeResponse); + + // This is a the client side of the connection, so set the connection to + // server flag. We need to set this early so that if the connection times + // out, its onRemove() will handle the cleanup properly. + conn->setIsConnectionToServer(); + + // Everything set, so send off the request. + sendConnectChallengeRequest(conn); +} + +void NetInterface::sendDisconnectPacket(NetConnection *conn, const char *reason) +{ + Con::printf("Issuing Disconnect packet."); + + // send a disconnect packet... + U32 connectSequence = conn->getSequence(); + + BitStream *out = BitStream::getPacketStream(); + out->write(U8(Disconnect)); + out->write(connectSequence); + out->writeString(reason); + + BitStream::sendPacketStream(conn->getNetAddress()); +} + +void NetInterface::checkTimeouts() +{ + U32 time = Platform::getVirtualMilliseconds(); + if(time > mLastTimeoutCheckTime + TimeoutCheckInterval) + { + for(U32 i = 0; i < mPendingConnections.size();) + { + NetConnection *pending = mPendingConnections[i]; + + if(pending->getConnectionState() == NetConnection::AwaitingChallengeResponse && + time > pending->mConnectLastSendTime + ChallengeRetryTime) + { + if(pending->mConnectSendCount > ChallengeRetryCount) + { + pending->onConnectTimedOut(); + removePendingConnection(pending); + pending->deleteObject(); + continue; + } + else + sendConnectChallengeRequest(pending); + } + else if(pending->getConnectionState() == NetConnection::AwaitingConnectResponse && + time > pending->mConnectLastSendTime + ConnectRetryTime) + { + if(pending->mConnectSendCount > ConnectRetryCount) + { + pending->onConnectTimedOut(); + removePendingConnection(pending); + pending->deleteObject(); + continue; + } + else + sendConnectRequest(pending); + } + i++; + } + mLastTimeoutCheckTime = time; + NetConnection *walk = NetConnection::getConnectionList(); + + while(walk) + { + NetConnection *next = walk->getNext(); + if(walk->checkTimeout(time)) + { + // this baddie timed out + walk->onTimedOut(); + walk->deleteObject(); + } + walk = next; + } + } +} + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +inline U32 rotlFixed(U32 x, unsigned int y) +{ + return (x >> y) | (x << (32 - y)); +} + +#define MD5STEP(f, w, x, y, z, data, s) w = rotlFixed(w + f(x, y, z) + data, s) + x + +void NetInterface::computeNetMD5(const NetAddress *address, U32 connectSequence, U32 digest[4]) +{ + digest[0] = 0x67452301L; + digest[1] = 0xefcdab89L; + digest[2] = 0x98badcfeL; + digest[3] = 0x10325476L; + + + U32 a, b, c, d; + + a=digest[0]; + b=digest[1]; + c=digest[2]; + d=digest[3]; + + U32 in[16]; + in[0] = address->type; + in[1] = (U32(address->netNum[0]) << 24) | + (U32(address->netNum[1]) << 16) | + (U32(address->netNum[2]) << 8) | + (U32(address->netNum[3])); + in[2] = address->port; + in[3] = connectSequence; + for(U32 i = 0; i < 12; i++) + in[i + 4] = mRandomHashData[i]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + digest[0]+=a; + digest[1]+=b; + digest[2]+=c; + digest[3]+=d; +} + +ConsoleFunctionGroupBegin(NetInterface, "Global control functions for the netInterfaces."); + +ConsoleFunction(allowConnections,void,2,2,"allowConnections(bool);") +{ + TORQUE_UNUSED(argc); + GNet->setAllowsConnections(dAtob(argv[1])); +} + +ConsoleFunctionGroupEnd(NetInterface); + diff --git a/sim/netInterface.h b/sim/netInterface.h new file mode 100644 index 0000000..a76a756 --- /dev/null +++ b/sim/netInterface.h @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _H_NETINTERFACE +#define _H_NETINTERFACE + +/// NetInterface class. Manages all valid and pending notify protocol connections. +/// +/// @see NetConnection, GameConnection, NetObject, NetEvent +class NetInterface +{ +public: + /// PacketType is encoded as the first byte of each packet. If the LSB of + /// the first byte is set (i.e. if the type number is odd), then the packet + /// is a data protocol packet, otherwise it's an OOB packet, suitable for + /// use in strange protocols, like game querying or connection initialization. + enum PacketTypes + { + MasterServerGameTypesRequest = 2, + MasterServerGameTypesResponse = 4, + MasterServerListRequest = 6, + MasterServerListResponse = 8, + GameMasterInfoRequest = 10, + GameMasterInfoResponse = 12, + GamePingRequest = 14, + GamePingResponse = 16, + GameInfoRequest = 18, + GameInfoResponse = 20, + GameHeartbeat = 22, + GGCPacket = 24, + ConnectChallengeRequest = 26, + ConnectChallengeReject = 28, + ConnectChallengeResponse = 30, + ConnectRequest = 32, + ConnectReject = 34, + ConnectAccept = 36, + Disconnect = 38, + }; +protected: + + Vector mPendingConnections; ///< List of connections that are in the startup phase. + U32 mLastTimeoutCheckTime; ///< Last time all the active connections were checked for timeouts. + U32 mRandomHashData[12]; ///< Data that gets hashed with connect challenge requests to prevent connection spoofing. + bool mRandomDataInitialized; ///< Have we initialized our random number generator? + bool mAllowConnections; ///< Is this NetInterface allowing connections at this time? + + enum NetInterfaceConstants + { + MaxPendingConnects = 20, ///< Maximum number of pending connections. If new connection requests come in before + ChallengeRetryCount = 4, ///< Number of times to send connect challenge requests before giving up. + ChallengeRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect challenge. + + ConnectRetryCount = 4, ///< Number of times to send connect requests before giving up. + ConnectRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect request. + TimeoutCheckInterval = 1500, ///< Interval in milliseconds between checking for connection timeouts. + }; + + /// Initialize random data. + void initRandomData(); + + /// @name Connection management + /// Most of these are pretty self-explanatory. + /// @{ + + void addPendingConnection(NetConnection *conn); + NetConnection *findPendingConnection(const NetAddress *address, U32 packetSequence); + void removePendingConnection(NetConnection *conn); + + void sendConnectChallengeRequest(NetConnection *conn); + void handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream); + + void handleConnectChallengeResponse(const NetAddress *address, BitStream *stream); + + void sendConnectRequest(NetConnection *conn); + void handleConnectRequest(const NetAddress *address, BitStream *stream); + + void sendConnectAccept(NetConnection *conn); + void handleConnectAccept(const NetAddress *address, BitStream *stream); + + void sendConnectReject(NetConnection *conn, const char *reason); + void handleConnectReject(const NetAddress *address, BitStream *stream); + + void handleDisconnect(const NetAddress *address, BitStream *stream); + + /// @} + + /// Calculate an MD5 sum representing a connection, and store it into addressDigest. + void computeNetMD5(const NetAddress *address, U32 connectSequence, U32 addressDigest[4]); + +public: + NetInterface(); + + /// Returns whether or not this NetInterface allows connections from remote hosts. + bool doesAllowConnections() { return mAllowConnections; } + + /// Sets whether or not this NetInterface allows connections from remote hosts. + void setAllowsConnections(bool conn) { mAllowConnections = conn; } + + /// Dispatch function for processing all network packets through this NetInterface. + virtual void processPacketReceiveEvent(NetAddress srcAddress, U32 pPacketData, int bytes); + + /// Handles all packets that don't fall into the category of connection handshake or game data. + virtual void handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream); + + /// Checks all connections marked as client to server for packet sends. + void processClient(); + + /// Checks all connections marked as server to client for packet sends. + void processServer(); + + /// Begins the connection handshaking process for a connection. + void startConnection(NetConnection *conn); + + /// Checks for timeouts on all valid and pending connections. + void checkTimeouts(); + + /// Send a disconnect packet on a connection, along with a reason. + void sendDisconnectPacket(NetConnection *conn, const char *reason); +}; + +/// The global net interface instance. +extern NetInterface *GNet; +#endif diff --git a/sim/netObject.cpp b/sim/netObject.cpp new file mode 100644 index 0000000..cf1407e --- /dev/null +++ b/sim/netObject.cpp @@ -0,0 +1,312 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "console/simBase.h" +#include "core/dnet.h" +#include "sim/netConnection.h" +#include "sim/netObject.h" +#include "console/consoleTypes.h" +#include "add/RPGPack/RPGUtils.h" + +IMPLEMENT_CONOBJECT(NetObject); + +extern bool gIsDedicated; +//---------------------------------------------------------------------------- +NetObject *NetObject::mDirtyList = NULL; + +NetObject::NetObject() +{ + // netFlags will clear itself to 0 + mNetIndex = U32(-1); + mFirstObjectRef = NULL; + mPrevDirtyList = NULL; + mNextDirtyList = NULL; + mDirtyMaskBits = 0; +} + +NetObject::~NetObject() +{ + if(mDirtyMaskBits) + { + if(mPrevDirtyList) + mPrevDirtyList->mNextDirtyList = mNextDirtyList; + else + mDirtyList = mNextDirtyList; + if(mNextDirtyList) + mNextDirtyList->mPrevDirtyList = mPrevDirtyList; + } +} + +String NetObject::describeSelf() +{ + String desc = Parent::describeSelf(); + + if( isClientObject() ) + desc += "|net: client"; + else + desc += "|net: server"; + + return desc; +} + +void NetObject::setMaskBits(U32 orMask) +{ + AssertFatal(orMask != 0, "Invalid net mask bits set."); + AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state."); + if(!mDirtyMaskBits) + { + AssertFatal(mNextDirtyList == NULL && mPrevDirtyList == NULL, "Object with zero mask already in list."); + if(mDirtyList) + { + mNextDirtyList = mDirtyList; + mDirtyList->mPrevDirtyList = this; + } + mDirtyList = this; + } + mDirtyMaskBits |= orMask; + AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state."); +} + +void NetObject::clearMaskBits(U32 orMask) +{ + if(isDeleted()) + return; + if(mDirtyMaskBits) + { + mDirtyMaskBits &= ~orMask; + if(!mDirtyMaskBits) + { + if(mPrevDirtyList) + mPrevDirtyList->mNextDirtyList = mNextDirtyList; + else + mDirtyList = mNextDirtyList; + if(mNextDirtyList) + mNextDirtyList->mPrevDirtyList = mPrevDirtyList; + mNextDirtyList = mPrevDirtyList = NULL; + } + } + + for(GhostInfo *walk = mFirstObjectRef; walk; walk = walk->nextObjectRef) + { + if(walk->updateMask && walk->updateMask == orMask) + { + walk->updateMask = 0; + walk->connection->ghostPushToZero(walk); + } + else + walk->updateMask &= ~orMask; + } +} + +void NetObject::collapseDirtyList() +{ +#ifdef TORQUE_DEBUG + Vector tempV; + for(NetObject *t = mDirtyList; t; t = t->mNextDirtyList) + tempV.push_back(t); +#endif + + for(NetObject *obj = mDirtyList; obj; ) + { + NetObject *next = obj->mNextDirtyList; + U32 dirtyMask = obj->mDirtyMaskBits; + + obj->mNextDirtyList = NULL; + obj->mPrevDirtyList = NULL; + obj->mDirtyMaskBits = 0; + + if(!obj->isDeleted() && dirtyMask) + { + for(GhostInfo *walk = obj->mFirstObjectRef; walk; walk = walk->nextObjectRef) + { + U32 orMask = obj->filterMaskBits(dirtyMask,walk->connection); + if(!walk->updateMask && orMask) + { + walk->updateMask = orMask; + walk->connection->ghostPushNonZero(walk); + } + else + walk->updateMask |= orMask; + } + } + obj = next; + } + mDirtyList = NULL; +#ifdef TORQUE_DEBUG + for(U32 i = 0; i < tempV.size(); i++) + { + AssertFatal(tempV[i]->mNextDirtyList == NULL && tempV[i]->mPrevDirtyList == NULL && tempV[i]->mDirtyMaskBits == 0, "Error in collapse"); + } +#endif +} + +//----------------------------------------------------------------------------- + +ConsoleMethod(NetObject,scopeToClient,void,3,3,"(NetConnection %client)" + "Cause the NetObject to be forced as scoped on the specified NetConnection.") +{ + TORQUE_UNUSED(argc); + NetConnection *conn; + if(!Sim::findObject(argv[2], conn)) + { + Con::errorf(ConsoleLogEntry::General, "NetObject::scopeToClient: Couldn't find connection %s", argv[2]); + return; + } + conn->objectLocalScopeAlways(object); +} + +ConsoleMethod(NetObject,clearScopeToClient,void,3,3,"clearScopeToClient(%client)" + "Undo the effects of a scopeToClient() call.") +{ + TORQUE_UNUSED(argc); + NetConnection *conn; + if(!Sim::findObject(argv[2], conn)) + { + Con::errorf(ConsoleLogEntry::General, "NetObject::clearScopeToClient: Couldn't find connection %s", argv[2]); + return; + } + conn->objectLocalClearAlways(object); +} + +ConsoleMethod(NetObject,setScopeAlways,void,2,2,"Always scope this object on all connections.") +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + object->setScopeAlways(); +} + +void NetObject::setScopeAlways() +{ + if(mNetFlags.test(Ghostable) && !mNetFlags.test(IsGhost)) + { + mNetFlags.set(ScopeAlways); + + // if it's a ghost always object, add it to the ghost always set + // for ClientReps created later. + + Sim::getGhostAlwaysSet()->addObject(this); + + // add it to all Connections that already exist. + + SimGroup *clientGroup = Sim::getClientGroup(); + SimGroup::iterator i; + for(i = clientGroup->begin(); i != clientGroup->end(); i++) + { + NetConnection *con = (NetConnection *) (*i); + if(con->isGhosting()) + con->objectInScope(this); + } + } +} + +void NetObject::clearScopeAlways() +{ + if(!mNetFlags.test(IsGhost)) + { + mNetFlags.clear(ScopeAlways); + Sim::getGhostAlwaysSet()->removeObject(this); + + // Un ghost this object from all the connections + while(mFirstObjectRef) + mFirstObjectRef->connection->detachObject(mFirstObjectRef); + } +} + +bool NetObject::onAdd() +{ + if (!Parent::onAdd()) + return false; + if ( ClientOnlyNetObject::isClientOnly(&(typeid(this))) ) + { + if (gIsDedicated) + return false; + else + mNetFlags = IsGhost; + } + + if(mNetFlags.test(ScopeAlways)) + setScopeAlways(); + + return true; +} + +void NetObject::onRemove() +{ + while(mFirstObjectRef) + mFirstObjectRef->connection->detachObject(mFirstObjectRef); + + Parent::onRemove(); +} + +//----------------------------------------------------------------------------- + +F32 NetObject::getUpdatePriority(CameraScopeQuery*, U32, S32 updateSkips) +{ + return F32(updateSkips) * 0.1; +} + +U32 NetObject::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + return 0; +} + +void NetObject::unpackUpdate(NetConnection*, BitStream*) +{ +} + +void NetObject::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery* /*camInfo*/) +{ + // default behavior - + // ghost everything that is ghostable + + for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj) + { + NetObject* nobj = dynamic_cast(*obj); + if (nobj) + { + AssertFatal(!nobj->mNetFlags.test(NetObject::Ghostable) || !nobj->mNetFlags.test(NetObject::IsGhost), + "NetObject::onCameraScopeQuery: object marked both ghostable and as ghost"); + + // Some objects don't ever want to be ghosted + if (!nobj->mNetFlags.test(NetObject::Ghostable)) + continue; + if (!nobj->mNetFlags.test(NetObject::ScopeAlways)) + { + // it's in scope... + cr->objectInScope(nobj); + } + } + } +} + +//----------------------------------------------------------------------------- + +void NetObject::initPersistFields() +{ + Parent::initPersistFields(); +} + +ConsoleMethod( NetObject, getGhostID, S32, 2, 2, "") +{ + return object->getNetIndex(); +} + +ConsoleMethod( NetObject, getClientObject, S32, 2, 2, "Short-Circuit-Netorking: this is only valid for a local-client / singleplayer situation." ) +{ + NetObject *obj = object->getClientObject(); + if ( obj ) + return obj->getId(); + + return NULL; +} + +ConsoleMethod( NetObject, getServerObject, S32, 2, 2, "Short-Circuit-Netorking: this is only valid for a local-client / singleplayer situation." ) +{ + NetObject *obj = object->getServerObject(); + if ( obj ) + return obj->getId(); + + return NULL; +} \ No newline at end of file diff --git a/sim/netObject.h b/sim/netObject.h new file mode 100644 index 0000000..822d53e --- /dev/null +++ b/sim/netObject.h @@ -0,0 +1,431 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _NETOBJECT_H_ +#define _NETOBJECT_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif + + +//----------------------------------------------------------------------------- +class NetConnection; +class NetObject; + +//----------------------------------------------------------------------------- + +struct CameraScopeQuery +{ + NetObject *camera; ///< Pointer to the viewing object. + Point3F pos; ///< Position in world space + Point3F orientation; ///< Viewing vector in world space + F32 fov; ///< Viewing angle/2 + F32 sinFov; ///< sin(fov/2); + F32 cosFov; ///< cos(fov/2); + F32 visibleDistance; ///< Visible distance. +}; + +struct GhostInfo; + + +//----------------------------------------------------------------------------- +/// Superclass for ghostable networked objects. +/// +/// @section NetObject_intro Introduction To NetObject And Ghosting +/// +/// One of the most powerful aspects of Torque's networking code is its support +/// for ghosting and prioritized, most-recent-state network updates. The way +/// this works is a bit complex, but it is immensely efficient. Let's run +/// through the steps that the server goes through for each client in this part +/// of Torque's networking: +/// - First, the server determines what objects are in-scope for the client. +/// This is done by calling onCameraScopeQuery() on the object which is +/// considered the "scope" object. This is usually the player object, but +/// it can be something else. (For instance, the current vehicle, or a +/// object we're remote controlling.) +/// - Second, it ghosts them to the client; this is implemented in netGhost.cc. +/// - Finally, it sends updates as needed, by checking the dirty list and packing +/// updates. +/// +/// There several significant advantages to using this networking system: +/// - Efficient network usage, since we only send data that has changed. In addition, +/// since we only care about most-recent data, if a packet is dropped, we don't waste +/// effort trying to deliver stale data. +/// - Cheating protection; since we don't deliver information about game objects which +/// aren't in scope, we dramatically reduce the ability of clients to hack the game and +/// gain a meaningful advantage. (For instance, they can't find out about things behind +/// them, since objects behind them don't fall in scope.) In addition, since ghost IDs are +/// assigned per-client, it's difficult for any sort of co-ordination between cheaters to +/// occur. +/// +/// NetConnection contains the Ghost Manager implementation, which deals with transferring data to +/// the appropriate clients and keeping state in synch. +/// +/// @section NetObject_Implementation An Example Implementation +/// +/// The basis of the ghost implementation in Torque is NetObject. It tracks the dirty flags for the +/// various states that the object trackers, and does some other book-keeping to allow more efficient +/// operation of the networking layer. +/// +/// Using a NetObject is very simple; let's go through a simple example implementation: +/// +/// @code +/// class SimpleNetObject : public NetObject +/// { +/// public: +/// typedef NetObject Parent; +/// DECLARE_CONOBJECT(SimpleNetObject); +/// @endcode +/// +/// Above is the standard boilerplate code for a Torque class. You can find out more about this in SimObject. +/// +/// @code +/// char message1[256]; +/// char message2[256]; +/// enum States { +/// Message1Mask = BIT(0), +/// Message2Mask = BIT(1), +/// }; +/// @endcode +/// +/// For our example, we're having two "states" that we keep track of, message1 and message2. In a real +/// object, we might map our states to health and position, or some other set of fields. You have 32 +/// bits to work with, so it's possible to be very specific when defining states. In general, you +/// should try to use as few states as possible (you never know when you'll need to expand your object's +/// functionality!), and in fact, most of your fields will end up changing all at once, so it's not worth +/// it to be too fine-grained. (As an example, position and velocity on Player are controlled by the same +/// bit, as one rarely changes without the other changing, too.) +/// +/// @code +/// SimpleNetObject() +/// { +/// // in order for an object to be considered by the network system, +/// // the Ghostable net flag must be set. +/// // the ScopeAlways flag indicates that the object is always scoped +/// // on all active connections. +/// mNetFlags.set(ScopeAlways | Ghostable); +/// dStrcpy(message1, "Hello World 1!"); +/// dStrcpy(message2, "Hello World 2!"); +/// } +/// @endcode +/// +/// Here is the constructor. Here, you see that we initialize our net flags to show that +/// we should always be scoped, and that we're to be taken into consideration for ghosting. We +/// also provide some initial values for the message fields. +/// +/// @code +/// U32 packUpdate(NetConnection *, U32 mask, BitStream *stream) +/// { +/// // check which states need to be updated, and update them +/// if(stream->writeFlag(mask & Message1Mask)) +/// stream->writeString(message1); +/// if(stream->writeFlag(mask & Message2Mask)) +/// stream->writeString(message2); +/// +/// // the return value from packUpdate can set which states still +/// // need to be updated for this object. +/// return 0; +/// } +/// @endcode +/// +/// Here's half of the meat of the networking code, the packUpdate() function. (The other half, unpackUpdate(), +/// we'll get to in a second.) The comments in the code pretty much explain everything, however, notice that the +/// code follows a pattern of if(writeFlag(mask & StateMask)) { ... write data ... }. The packUpdate()/unpackUpdate() +/// functions are responsible for reading and writing the dirty bits to the bitstream by themselves. +/// +/// @code +/// void unpackUpdate(NetConnection *, BitStream *stream) +/// { +/// // the unpackUpdate function must be symmetrical to packUpdate +/// if(stream->readFlag()) +/// { +/// stream->readString(message1); +/// Con::printf("Got message1: %s", message1); +/// } +/// if(stream->readFlag()) +/// { +/// stream->readString(message2); +/// Con::printf("Got message2: %s", message2); +/// } +/// } +/// @endcode +/// +/// The other half of the networking code in any NetObject, unpackUpdate(). In our simple example, all that +/// the code does is print the new messages to the console; however, in a more advanced object, you might +/// trigger animations, update complex object properties, or even spawn new objects, based on what packet +/// data you unpack. +/// +/// @code +/// void setMessage1(const char *msg) +/// { +/// setMaskBits(Message1Mask); +/// dStrcpy(message1, msg); +/// } +/// void setMessage2(const char *msg) +/// { +/// setMaskBits(Message2Mask); +/// dStrcpy(message2, msg); +/// } +/// @endcode +/// +/// Here are the accessors for the two properties. It is good to encapsulate your state +/// variables, so that you don't have to remember to make a call to setMaskBits every time you change +/// anything; the accessors can do it for you. In a more complex object, you might need to set +/// multiple mask bits when you change something; this can be done using the | operator, for instance, +/// setMaskBits( Message1Mask | Message2Mask ); if you changed both messages. +/// +/// @code +/// IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject); +/// +/// ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "(string msg) Set message 1.") +/// { +/// object->setMessage1(argv[2]); +/// } +/// +/// ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "(string msg) Set message 2.") +/// { +/// object->setMessage2(argv[2]); +/// } +/// @endcode +/// +/// Finally, we use the NetObject implementation macro, IMPLEMENT_CO_NETOBJECT_V1(), to implement our +/// NetObject. It is important that we use this, as it makes Torque perform certain initialization tasks +/// that allow us to send the object over the network. IMPLEMENT_CONOBJECT() doesn't perform these tasks, see +/// the documentation on AbstractClassRep for more details. +/// +/// @nosubgrouping +class NetObject: public SimObject +{ + // The Ghost Manager needs read/write access + friend class NetConnection; + friend struct GhostInfo; + friend class ProcessList; + + // Not the best way to do this, but the event needs access to mNetFlags + friend class GhostAlwaysObjectEvent; + +private: + typedef SimObject Parent; + + /// Mask indicating which states are dirty and need to be retransmitted on this + /// object. + U32 mDirtyMaskBits; + + /// @name Dirty List + /// + /// Whenever a NetObject becomes "dirty", we add it to the dirty list. + /// We also remove ourselves on the destructor. + /// + /// This is done so that when we want to send updates (in NetConnection), + /// it's very fast to find the objects that need to be updated. + /// @{ + + /// Static pointer to the head of the dirty NetObject list. + static NetObject *mDirtyList; + + /// Next item in the dirty list... + NetObject *mPrevDirtyList; + + /// Previous item in the dirty list... + NetObject *mNextDirtyList; + + /// @} +protected: + + /// Pointer to the server object on a local connection. + /// @see getServerObject + SimObjectPtr mServerObject; + + /// Pointer to the client object on a local connection. + /// @see getClientObject + SimObjectPtr mClientObject; + + enum NetFlags + { + IsGhost = BIT(1), ///< This is a ghost. + ScopeAlways = BIT(6), ///< Object always ghosts to clients. + ScopeLocal = BIT(7), ///< Ghost only to local client. + Ghostable = BIT(8), ///< Set if this object CAN ghost. + + MaxNetFlagBit = 15 + }; + + BitSet32 mNetFlags; ///< Flag values from NetFlags + U32 mNetIndex; ///< The index of this ghost in the GhostManager on the server. + + GhostInfo *mFirstObjectRef; ///< Head of a linked list storing GhostInfos referencing this NetObject. + +public: + NetObject(); + ~NetObject(); + + virtual String describeSelf(); + + /// @name Miscellaneous + /// @{ + DECLARE_CONOBJECT(NetObject); + static void initPersistFields(); + bool onAdd(); + void onRemove(); + /// @} + + static void collapseDirtyList(); + + /// Used to mark a bit as dirty; ie, that its corresponding set of fields need to be transmitted next update. + /// + /// @param orMask Bit(s) to set + virtual void setMaskBits(U32 orMask); + + /// Clear the specified bits from the dirty mask. + /// + /// @param orMask Bits to clear + virtual void clearMaskBits(U32 orMask); + virtual U32 filterMaskBits(U32 mask, NetConnection * connection) { return mask; } + + /// Scope the object to all connections. + /// + /// The object is marked as ScopeAlways and is immediately ghosted to + /// all active connections. This function has no effect if the object + /// is not marked as Ghostable. + void setScopeAlways(); + + /// Stop scoping the object to all connections. + /// + /// The object's ScopeAlways flag is cleared and the object is removed from + /// all current active connections. + void clearScopeAlways(); + + /// This returns a value which is used to prioritize which objects need to be updated. + /// + /// In NetObject, our returned priority is 0.1 * updateSkips, so that less recently + /// updated objects are more likely to be updated. + /// + /// In subclasses, this can be adjusted. For instance, ShapeBase provides priority + /// based on proximity to the camera. + /// + /// @param focusObject Information from a previous call to onCameraScopeQuery. + /// @param updateMask Current update mask. + /// @param updateSkips Number of ticks we haven't been updated for. + /// @returns A floating point value indicating priority. These are typically < 5.0. + virtual F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + + /// Instructs this object to pack its state for transfer over the network. + /// + /// @param conn Net connection being used + /// @param mask Mask indicating fields to transmit. + /// @param stream Bitstream to pack data to + /// + /// @returns Any bits which were not dealt with. The value is stored by the networking + /// system. Don't set bits you weren't passed. + virtual U32 packUpdate(NetConnection * conn, U32 mask, BitStream *stream); + + /// Instructs this object to read state data previously packed with packUpdate. + /// + /// @param conn Net connection being used + /// @param stream stream to read from + virtual void unpackUpdate(NetConnection * conn, BitStream *stream); + + /// Queries the object about information used to determine scope. + /// + /// Something that is 'in scope' is somehow interesting to the client. + /// + /// If we are a NetConnection's scope object, it calls this method to determine + /// how things should be scoped; basically, we tell it our field of view with camInfo, + /// and have the opportunity to manually mark items as "in scope" as we see fit. + /// + /// By default, we just mark all ghostable objects as in scope. + /// + /// @param cr Net connection requesting scope information. + /// @param camInfo Information about what this object can see. + virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo); + + /// Get the ghost index of this object. + U32 getNetIndex() { return mNetIndex; } + + bool isServerObject() const; ///< Is this a server object? + bool isClientObject() const; ///< Is this a client object? + + bool isGhost() const; ///< Is this is a ghost? + bool isScopeLocal() const; ///< Should this object only be visible to the client which created it? + bool isScopeable() const; ///< Is this object subject to scoping? + bool isGhostable() const; ///< Is this object ghostable? + bool isGhostAlways() const; ///< Should this object always be ghosted? + + void forceToClientObj(); ///< force this instance to client object + + + /// @name Short-Circuited Networking + /// + /// When we are running with client and server on the same system (which can happen be either + /// when we are doing a single player game, or if we're hosting a multiplayer game and having + /// someone playing on the same instance), we can do some short circuited code to enhance + /// performance. + /// + /// These variables are used to make it simpler; if we are running in short-circuited mode, + /// the ghosted client gets the server object while the server gets the client object. + /// + /// @note "Premature optimization is the root of all evil" - Donald Knuth. The current codebase + /// uses this feature in three small places, mostly for non-speed-related purposes. + /// + /// @{ + + /// Returns a pointer to the server object when on a local connection. + NetObject* getServerObject() const { return mServerObject; } + + /// Returns a pointer to the client object when on a local connection. + NetObject* getClientObject() const { return mClientObject; } + + /// @} +}; + +//----------------------------------------------------------------------------- + +inline bool NetObject::isGhost() const +{ + return mNetFlags.test(IsGhost); +} + +inline bool NetObject::isClientObject() const +{ + return mNetFlags.test(IsGhost); +} + +inline bool NetObject::isServerObject() const +{ + return !mNetFlags.test(IsGhost); +} + +inline bool NetObject::isScopeLocal() const +{ + return mNetFlags.test(ScopeLocal); +} + +inline bool NetObject::isScopeable() const +{ + return mNetFlags.test(Ghostable) && !mNetFlags.test(ScopeAlways); +} + +inline bool NetObject::isGhostable() const +{ + return mNetFlags.test(Ghostable); +} + +inline bool NetObject::isGhostAlways() const +{ + AssertFatal(mNetFlags.test(Ghostable) || mNetFlags.test(ScopeAlways) == false, + "That's strange, a ScopeAlways non-ghostable object? Something wrong here"); + return mNetFlags.test(Ghostable) && mNetFlags.test(ScopeAlways); +} + +inline void NetObject::forceToClientObj() +{ + mNetFlags = IsGhost; +} + +#endif diff --git a/sim/netStringTable.cpp b/sim/netStringTable.cpp new file mode 100644 index 0000000..c8bd52c --- /dev/null +++ b/sim/netStringTable.cpp @@ -0,0 +1,262 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#include "core/dnet.h" +#include "core/strings/stringFunctions.h" +#include "core/stringTable.h" + +#include "sim/netStringTable.h" + + +NetStringTable *gNetStringTable = NULL; + +NetStringTable::NetStringTable() +{ + firstFree = 1; + firstValid = 1; + + table = (Entry *) dMalloc(sizeof(Entry) * InitialSize); + size = InitialSize; + for(U32 i = 0; i < InitialSize; i++) + { + table[i].next = i + 1; + table[i].refCount = 0; + table[i].scriptRefCount = 0; + } + table[InitialSize-1].next = InvalidEntry; + for(U32 j = 0; j < HashTableSize; j++) + hashTable[j] = 0; + allocator = new DataChunker(DataChunkerSize); +} + +NetStringTable::~NetStringTable() +{ + delete allocator; + dFree( table ); +} + +void NetStringTable::incStringRef(U32 id) +{ + AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero."); + table[id].refCount++; +} + +void NetStringTable::incStringRefScript(U32 id) +{ + AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero."); + table[id].scriptRefCount++; +} + +U32 NetStringTable::addString(const char *string) +{ + U32 hash = _StringTable::hashString(string); + U32 bucket = hash % HashTableSize; + for(U32 walk = hashTable[bucket];walk; walk = table[walk].next) + { + if(!dStrcmp(table[walk].string, string)) + { + table[walk].refCount++; + return walk; + } + } + U32 e = firstFree; + firstFree = table[e].next; + if(firstFree == InvalidEntry) + { + // in this case, we should expand the table for next time... + U32 newSize = size * 2; + table = (Entry *) dRealloc(table, newSize * sizeof(Entry)); + for(U32 i = size; i < newSize; i++) + { + table[i].next = i + 1; + table[i].refCount = 0; + table[i].scriptRefCount = 0; + } + firstFree = size; + table[newSize - 1].next = InvalidEntry; + size = newSize; + } + table[e].refCount++; + table[e].string = (char *) allocator->alloc(dStrlen(string) + 1); + dStrcpy(table[e].string, string); + table[e].next = hashTable[bucket]; + hashTable[bucket] = e; + table[e].link = firstValid; + table[firstValid].prevLink = e; + firstValid = e; + table[e].prevLink = 0; + return e; +} + +U32 GameAddTaggedString(const char *string) +{ + return gNetStringTable->addString(string); +} + +const char *NetStringTable::lookupString(U32 id) +{ + if(table[id].refCount == 0 && table[id].scriptRefCount == 0) + return NULL; + return table[id].string; +} + +void NetStringTable::removeString(U32 id, bool script) +{ + if(!script) + { + AssertFatal(table[id].refCount != 0, "Error, ref count is already 0!!"); + if(--table[id].refCount) + return; + if(table[id].scriptRefCount) + return; + } + else + { + // If both ref counts are already 0, this id is not valid. Ignore + // the remove + if (table[id].scriptRefCount == 0 && table[id].refCount == 0) + return; + + if(table[id].scriptRefCount == 0 && table[id].refCount) + { + Con::errorf("removeTaggedString failed! Ref count is already 0 for string: %s", table[id].string); + return; + } + if(--table[id].scriptRefCount) + return; + if(table[id].refCount) + return; + } + // unlink first: + U32 prev = table[id].prevLink; + U32 next = table[id].link; + if(next) + table[next].prevLink = prev; + if(prev) + table[prev].link = next; + else + firstValid = next; + // remove it from the hash table + U32 hash = _StringTable::hashString(table[id].string); + U32 bucket = hash % HashTableSize; + for(U32 *walk = &hashTable[bucket];*walk; walk = &table[*walk].next) + { + if(*walk == id) + { + *walk = table[id].next; + break; + } + } + table[id].next = firstFree; + firstFree = id; +} + +void NetStringTable::repack() +{ + DataChunker *newAllocator = new DataChunker(DataChunkerSize); + for(U32 walk = firstValid; walk; walk = table[walk].link) + { + const char *prevStr = table[walk].string; + + + table[walk].string = (char *) newAllocator->alloc(dStrlen(prevStr) + 1); + dStrcpy(table[walk].string, prevStr); + } + delete allocator; + allocator = newAllocator; +} + +void NetStringTable::create() +{ + AssertFatal(gNetStringTable == NULL, "Error, calling NetStringTable::create twice."); + gNetStringTable = new NetStringTable(); +} + +void NetStringTable::destroy() +{ + AssertFatal(gNetStringTable != NULL, "Error, not calling NetStringTable::create."); + delete gNetStringTable; + gNetStringTable = NULL; +} + +void NetStringTable::expandString(NetStringHandle &inString, char *buf, U32 bufSize, U32 argc, const char **argv) +{ + buf[0] = StringTagPrefixByte; + dSprintf(buf + 1, bufSize - 1, "%d ", inString.getIndex()); + + const char *string = inString.getString(); + if (string != NULL) { + U32 index = dStrlen(buf); + while(index < bufSize) + { + char c = *string++; + if(c == '%') + { + c = *string++; + if(c >= '1' && c <= '9') + { + U32 strIndex = c - '1'; + if(strIndex >= argc) + continue; + // start copying out of arg index + const char *copy = argv[strIndex]; + // skip past any tags: + if(*copy == StringTagPrefixByte) + { + while(*copy && *copy != ' ') + copy++; + if(*copy) + copy++; + } + + while(*copy && index < bufSize) + buf[index++] = *copy++; + continue; + } + } + buf[index++] = c; + if(!c) + break; + } + buf[bufSize - 1] = 0; + } else { + dStrcat(buf, ""); + } +} + +#if defined(TORQUE_DEBUG) +void NetStringTable::dumpToConsole() +{ + U32 count = 0; + S32 maxIndex = -1; + for ( U32 i = 0; i < size; i++ ) + { + if ( table[i].refCount > 0 || table[i].scriptRefCount > 0) + { + Con::printf( "%d: \"%c%s%c\" REF: %d", i, 0x10, table[i].string, 0x11, table[i].refCount ); + if ( maxIndex == -1 || table[i].refCount > table[maxIndex].refCount ) + maxIndex = i; + count++; + } + } + Con::printf( ">> STRINGS: %d MAX REF COUNT: %d \"%c%s%c\" <<", + count, + ( maxIndex == -1 ) ? 0 : table[maxIndex].refCount, + 0x10, + ( maxIndex == -1 ) ? "" : table[maxIndex].string, + 0x11 ); +} + +ConsoleFunctionGroupBegin(NetStringTable, "Debug functions for the NetStringTable."); + +ConsoleFunction( dumpNetStringTable, void, 1, 1, "dumpNetStringTable()" ) +{ + TORQUE_UNUSED(argc); TORQUE_UNUSED(argv); + gNetStringTable->dumpToConsole(); +} + +ConsoleFunctionGroupEnd(NetStringTable); + +#endif // DEBUG diff --git a/sim/netStringTable.h b/sim/netStringTable.h new file mode 100644 index 0000000..04efe35 --- /dev/null +++ b/sim/netStringTable.h @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (c) 2002 GarageGames.Com +//----------------------------------------------------------------------------- + +#ifndef _NETSTRINGTABLE_H_ +#define _NETSTRINGTABLE_H_ + +#ifndef _DATACHUNKER_H_ +#include "core/dataChunker.h" +#endif +#ifndef _CONSOLE_H_ +#include "console/console.h" +#endif + +class NetConnection; + +class NetStringHandle; +extern U32 GameAddTaggedString(const char *string); + +class NetStringTable +{ + friend class NetStringHandle; + friend U32 GameAddTaggedString(const char *string); + +#ifdef TORQUE_DEBUG_NET + friend class RemoteCommandEvent; +#endif + + enum Constants { + InitialSize = 16, + InvalidEntry = 0xFFFFFFFF, + HashTableSize = 2128, + DataChunkerSize = 65536 + }; + struct Entry + { + char *string; + U32 refCount; + U32 scriptRefCount; + U32 next; + U32 link; + U32 prevLink; + U32 seq; + }; + U32 size; + U32 firstFree; + U32 firstValid; + U32 sequenceCount; + + Entry *table; + U32 hashTable[HashTableSize]; + DataChunker *allocator; + + NetStringTable(); + ~NetStringTable(); + + U32 addString(const char *string); + +// XA: Moved this ones to public to avoid using the friend_ConsoleMethod hack. +public: + const char *lookupString(U32 id); + void removeString(U32 id, bool script = false); + void incStringRefScript(U32 id); + +private: + void incStringRef(U32 id); + + void repack(); +public: + static void create(); + static void destroy(); + + static void expandString(NetStringHandle &string, char *buf, U32 bufSize, U32 argc, const char **argv); + +#if defined(TORQUE_DEBUG) + void dumpToConsole(); +#endif // DEBUG +}; + +extern NetStringTable *gNetStringTable; + +class NetStringHandle +{ + U32 index; +public: + NetStringHandle() { index = 0; } + NetStringHandle(const NetStringHandle &string) { + index = string.index; + if(index) + gNetStringTable->incStringRef(index); + } + NetStringHandle(const char *string) { + index = gNetStringTable->addString(string); + } + NetStringHandle(U32 initIndex) + { + index = initIndex; + if(index) + gNetStringTable->incStringRef(index); + } + ~NetStringHandle() + { + if(index) + gNetStringTable->removeString(index); + } + + void setFromIndex(U32 newIndex) + { + if(index) + gNetStringTable->removeString(index); + index = newIndex; + } + + bool operator==(const NetStringHandle &s) const { return index == s.index; } + bool operator!=(const NetStringHandle &s) const { return index != s.index; } + + NetStringHandle &operator=(const NetStringHandle &s) + { + if(index) + gNetStringTable->removeString(index); + index = s.index; + if(index) + gNetStringTable->incStringRef(index); + return *this; + } + const char *getString() + { + if(index) + return gNetStringTable->lookupString(index); + else + return NULL; + } + bool isNull() { return index == 0; } + bool isValidString() { return index != 0; } + U32 getIndex() { return index; } +}; + +#endif diff --git a/sim/processList.cpp b/sim/processList.cpp new file mode 100644 index 0000000..df148cc --- /dev/null +++ b/sim/processList.cpp @@ -0,0 +1,242 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "sim/processList.h" + +#include "platform/profiler.h" +#include "console/consoleTypes.h" + +//---------------------------------------------------------------------------- + +void ProcessObject::plUnlink() +{ + mProcessLink.next->mProcessLink.prev = mProcessLink.prev; + mProcessLink.prev->mProcessLink.next = mProcessLink.next; + mProcessLink.next = mProcessLink.prev = this; +} + +void ProcessObject::plLinkAfter(ProcessObject * obj) +{ + AssertFatal(mProcessLink.next == this && mProcessLink.prev == this,"ProcessObject::plLinkAfter: must be unlinked before calling this"); +#ifdef TORQUE_DEBUG + ProcessObject * test1 = obj; + ProcessObject * test2 = obj->mProcessLink.next; + ProcessObject * test3 = obj->mProcessLink.prev; + ProcessObject * test4 = this; +#endif + + // Link this after obj + mProcessLink.next = obj->mProcessLink.next; + mProcessLink.prev = obj; + obj->mProcessLink.next = this; + mProcessLink.next->mProcessLink.prev = this; + +#ifdef TORQUE_DEBUG + AssertFatal(test1->mProcessLink.next->mProcessLink.prev==test1 && test1->mProcessLink.prev->mProcessLink.next==test1,"Doh!"); + AssertFatal(test2->mProcessLink.next->mProcessLink.prev==test2 && test2->mProcessLink.prev->mProcessLink.next==test2,"Doh!"); + AssertFatal(test3->mProcessLink.next->mProcessLink.prev==test3 && test3->mProcessLink.prev->mProcessLink.next==test3,"Doh!"); + AssertFatal(test4->mProcessLink.next->mProcessLink.prev==test4 && test4->mProcessLink.prev->mProcessLink.next==test4,"Doh!"); +#endif +} + +void ProcessObject::plLinkBefore(ProcessObject * obj) +{ + AssertFatal(mProcessLink.next == this && mProcessLink.prev == this,"ProcessObject::plLinkBefore: must be unlinked before calling this"); +#ifdef TORQUE_DEBUG + ProcessObject * test1 = obj; + ProcessObject * test2 = obj->mProcessLink.next; + ProcessObject * test3 = obj->mProcessLink.prev; + ProcessObject * test4 = this; +#endif + + // Link this before obj + mProcessLink.next = obj; + mProcessLink.prev = obj->mProcessLink.prev; + obj->mProcessLink.prev = this; + mProcessLink.prev->mProcessLink.next = this; + +#ifdef TORQUE_DEBUG + AssertFatal(test1->mProcessLink.next->mProcessLink.prev==test1 && test1->mProcessLink.prev->mProcessLink.next==test1,"Doh!"); + AssertFatal(test2->mProcessLink.next->mProcessLink.prev==test2 && test2->mProcessLink.prev->mProcessLink.next==test2,"Doh!"); + AssertFatal(test3->mProcessLink.next->mProcessLink.prev==test3 && test3->mProcessLink.prev->mProcessLink.next==test3,"Doh!"); + AssertFatal(test4->mProcessLink.next->mProcessLink.prev==test4 && test4->mProcessLink.prev->mProcessLink.next==test4,"Doh!"); +#endif +} + +void ProcessObject::plJoin(ProcessObject * head) +{ + ProcessObject * tail1 = head->mProcessLink.prev; + ProcessObject * tail2 = mProcessLink.prev; + tail1->mProcessLink.next = this; + mProcessLink.prev = tail1; + tail2->mProcessLink.next = head; + head->mProcessLink.prev = tail2; +} + +//-------------------------------------------------------------------------- + + +ProcessList::ProcessList() +{ + mCurrentTag = 0; + mDirty = false; + + mTotalTicks = 0; + mLastTick = 0; + mLastTime = 0; + mLastDelta = 0.0f; +} + +void ProcessList::addObject(ProcessObject * obj) +{ + obj->plLinkAfter(&mHead); +} + +//---------------------------------------------------------------------------- + +void ProcessList::orderList() +{ + // ProcessObject tags are initialized to 0, so current tag should never be 0. + if (++mCurrentTag == 0) + mCurrentTag++; + + // Install a temporary head node + ProcessObject list; + list.plLinkBefore(mHead.mProcessLink.next); + mHead.plUnlink(); + + // start out by (bubble) sorting list by GUID + for (ProcessObject * cur = list.mProcessLink.next; cur != &list; cur = cur->mProcessLink.next) + { + if (cur->mOrderGUID == 0) + // special case -- can be no lower, so accept as lowest (this is also + // a common value since it is what non ordered objects have) + continue; + + for (ProcessObject * walk = cur->mProcessLink.next; walk != &list; walk = walk->mProcessLink.next) + { + if (walk->mOrderGUID < cur->mOrderGUID) + { + // swap walk and cur -- need to be careful because walk might be just after cur + // so insert after item before cur and before item after walk + ProcessObject * before = cur->mProcessLink.prev; + ProcessObject * after = walk->mProcessLink.next; + cur->plUnlink(); + walk->plUnlink(); + cur->plLinkBefore(after); + walk->plLinkAfter(before); + ProcessObject * swap = walk; + walk = cur; + cur = swap; + } + } + } + + // Reverse topological sort into the original head node + while (list.mProcessLink.next != &list) + { + ProcessObject * ptr = list.mProcessLink.next; + ProcessObject * afterObject = ptr->getAfterObject(); + ptr->mProcessTag = mCurrentTag; + ptr->plUnlink(); + if (afterObject) + { + // Build chain "stack" of dependent objects and patch + // it to the end of the current list. + while (afterObject && afterObject->mProcessTag != mCurrentTag) + { + afterObject->mProcessTag = mCurrentTag; + afterObject->plUnlink(); + afterObject->plLinkBefore(ptr); + ptr = afterObject; + afterObject = ptr->getAfterObject(); + } + ptr->plJoin(&mHead); + } + else + ptr->plLinkBefore(&mHead); + } + mDirty = false; +} + +void ProcessList::dumpToConsole() +{ + for (ProcessObject * pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) + { + SimObject * obj = dynamic_cast(pobj); + if (obj) + Con::printf("id %i, order guid %i, type %s", obj->getId(), pobj->mOrderGUID, obj->getClassName()); + else + Con::printf("---unknown object type, order guid %i", pobj->mOrderGUID); + } +} + +//---------------------------------------------------------------------------- + +bool ProcessList::advanceTime(SimTime timeDelta) +{ + PROFILE_START(AdvanceTime); + + // some drivers change the FPU control state, which will break our control object simulation + // (leading to packet mismatch errors due to small FP differences). So set it to the known + // state before advancing. + U32 mathState = Platform::getMathControlState(); + Platform::setMathControlStateKnown(); + + if (mDirty) + orderList(); + + SimTime targetTime = mLastTime + timeDelta; + SimTime targetTick = targetTime - (targetTime % TickMs); + SimTime tickDelta = targetTick - mLastTick; + bool tickPass = mLastTick != targetTick; + + if ( tickPass ) + mPreTick.trigger(); + + // Advance all the objects. + for (; mLastTick != targetTick; mLastTick += TickMs) + onAdvanceObjects(); + + mLastTime = targetTime; + mLastDelta = ((TickMs - ((targetTime+1) % TickMs)) % TickMs) / F32(TickMs); + + if ( tickPass ) + mPostTick.trigger( tickDelta ); + + // restore math control state in case others are relying on it being a certain value + Platform::setMathControlState(mathState); + + PROFILE_END(); + return tickPass; +} + +//---------------------------------------------------------------------------- + +void ProcessList::advanceObjects() +{ + PROFILE_START(AdvanceObjects); + + // A little link list shuffling is done here to avoid problems + // with objects being deleted from within the process method. + ProcessObject list; + list.plLinkBefore(mHead.mProcessLink.next); + mHead.plUnlink(); + for (ProcessObject * pobj = list.mProcessLink.next; pobj != &list; pobj = list.mProcessLink.next) + { + pobj->plUnlink(); + pobj->plLinkBefore(&mHead); + + onTickObject(pobj); + } + + mTotalTicks++; + + PROFILE_END(); +} + + + diff --git a/sim/processList.h b/sim/processList.h new file mode 100644 index 0000000..690484d --- /dev/null +++ b/sim/processList.h @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _PROCESSLIST_H_ +#define _PROCESSLIST_H_ + +#ifndef _SIM_H_ +#include "console/sim.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif + +//---------------------------------------------------------------------------- + +#define TickMs 32 +#define TickSec (F32(TickMs) / 1000.0f) + +//---------------------------------------------------------------------------- + +class ProcessObject +{ + friend class ProcessList; + friend class ClientProcessList; + friend class ServerProcessList; + +public: + + ProcessObject() { mProcessTag = 0; mProcessLink.next=mProcessLink.prev=this; mOrderGUID=0; } + virtual ProcessObject * getAfterObject() { return NULL; } + +protected: + + struct Link + { + ProcessObject *next; + ProcessObject *prev; + }; + + // Processing interface + void plUnlink(); + void plLinkAfter(ProcessObject*); + void plLinkBefore(ProcessObject*); + void plJoin(ProcessObject*); + + U32 mProcessTag; // Tag used during sort + U32 mOrderGUID; // UID for keeping order synced (e.g., across network or runs of sim) + Link mProcessLink; // Ordered process queue +}; + +//---------------------------------------------------------------------------- + +typedef Signal PreTickSignal; +typedef Signal PostTickSignal; + +/// List of ProcessObjects. +class ProcessList +{ + +public: + ProcessList(); + + void markDirty() { mDirty = true; } + bool isDirty() { return mDirty; } + + virtual void addObject(ProcessObject * obj); + + SimTime getLastTime() { return mLastTime; } + F32 getLastDelta() { return mLastDelta; } + F32 getLastInterpDelta() { return mLastDelta / F32(TickMs); } + U32 getTotalTicks() { return mTotalTicks; } + void dumpToConsole(); + + PreTickSignal& preTickSignal() { return mPreTick; } + + PostTickSignal& postTickSignal() { return mPostTick; } + + /// @name Advancing Time + /// The advance time functions return true if a tick was processed. + /// @{ + + bool advanceTime(SimTime timeDelta); + +protected: + + ProcessObject mHead; + + U32 mCurrentTag; + bool mDirty; + + U32 mTotalTicks; + SimTime mLastTick; + SimTime mLastTime; + F32 mLastDelta; + + PreTickSignal mPreTick; + PostTickSignal mPostTick; + + void orderList(); + virtual void advanceObjects(); + virtual void onAdvanceObjects() { advanceObjects(); } + virtual void onTickObject(ProcessObject *) {} +}; + +#endif // _PROCESSLIST_H_ \ No newline at end of file diff --git a/terrain/glsl/terrFeatureGLSL.cpp b/terrain/glsl/terrFeatureGLSL.cpp new file mode 100644 index 0000000..192f281 --- /dev/null +++ b/terrain/glsl/terrFeatureGLSL.cpp @@ -0,0 +1,752 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/glsl/terrFeatureGLSL.h" + +#include "terrain/terrFeatureTypes.h" +#include "materials/materialFeatureTypes.h" +#include "gfx/gfxDevice.h" +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/featureMgr.h" + + +OnTorqueStartup +{ + FEATUREMGR->registerFeature( MFT_TerrainEmpty, new TerrainEmptyFeatGLSL ); + FEATUREMGR->registerFeature( MFT_TerrainBaseMap, new TerrainBaseMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_TerrainParallaxMap, new TerrainParallaxMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_TerrainDetailMap, new TerrainDetailMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_TerrainNormalMap, new TerrainNormalMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatGLSL ); + FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureGLSL( "Terrain Side Projection" ) ); + FEATUREMGR->registerFeature( MFT_TerrainAdditive, new TerrainAdditiveFeatGLSL ); +} + +Var* TerrainFeatGLSL::_getUniformVar( const char *name, const char *type ) +{ + Var *theVar = (Var*)LangElement::find( name ); + if ( !theVar ) + { + theVar = new Var; + theVar->setType( type ); + theVar->setName( name ); + theVar->uniform = true; + theVar->constSortPos = cspPass; + } + + return theVar; +} + +Var* TerrainFeatGLSL::_getInDetailCoord( Vector &componentList ) +{ + String name( String::ToString( "outDetCoord%d", getProcessIndex() ) ); + Var *inDet = (Var*)LangElement::find( name ); + + if ( !inDet ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + inDet = connectComp->getElement( RT_TEXCOORD ); + inDet->setName( name ); + inDet->setType( "vec4" ); + inDet->mapsToSampler = true; + } + + return inDet; +} + +Var* TerrainFeatGLSL::_getNormalMapTex() +{ + String name( String::ToString( "normalMap%d", getProcessIndex() ) ); + Var *normalMap = (Var*)LangElement::find( name ); + + if ( !normalMap ) + { + normalMap = new Var; + normalMap->setType( "sampler2D" ); + normalMap->setName( name ); + normalMap->uniform = true; + normalMap->sampler = true; + normalMap->constNum = Var::getTexUnitNum(); + } + + return normalMap; +} + +Var* TerrainFeatGLSL::_getDetailIdStrengthParallax() +{ + String name( String::ToString( "detailIdStrengthParallax%d", getProcessIndex() ) ); + + Var *detailInfo = (Var*)LangElement::find( name ); + if ( !detailInfo ) + { + detailInfo = new Var; + detailInfo->setType( "vec3" ); + detailInfo->setName( name ); + detailInfo->uniform = true; + detailInfo->constSortPos = cspPass; + } + + return detailInfo; +} + +void TerrainBaseMapFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + output = meta; + + // Generate the incoming texture var. + Var *inTex; + { + Var *inPos = (Var*)LangElement::find( "inPosition" ); + if ( !inPos ) + inPos = (Var*)LangElement::find( "position" ); + + inTex = new Var( "texCoord", "vec3" ); + + Var *oneOverTerrainSize = _getUniformVar( "oneOverTerrainSize", "float" ); + + // NOTE: The y coord here should be negative to have + // the texture maps not end up flipped which also caused + // normal and parallax mapping to be incorrect. + // + // This mistake early in development means that the layer + // id bilinear blend depends on it being that way. + // + // So instead i fixed this by flipping the base and detail + // coord y scale to compensate when rendering. + // + meta->addStatement( new GenOp( " @ = @.xyz * vec3( @, @, -@ );\r\n", + new DecOp( inTex ), inPos, oneOverTerrainSize, oneOverTerrainSize, oneOverTerrainSize ) ); + } + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Pass the texture coord to the pixel shader. + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "outTexCoord" ); + outTex->setType( "vec3" ); + outTex->mapsToSampler = true; + meta->addStatement( new GenOp( " @.xy = @.xy;\r\n", outTex, inTex ) ); + + // If this shader has a side projected layer then we + // pass the dot product between the +Y and the normal + // thru outTexCoord.z for use in blending the textures. + if ( fd.features.hasFeature( MFT_TerrainSideProject ) ) + { + Var *inNormal = (Var*)LangElement::find( "normal" ); + meta->addStatement( + new GenOp( " @.z = pow( abs( dot( normalize( vec3( @.x, @.y, 0.0 ) ), vec3( 0, 1, 0 ) ) ), 10.0 );\r\n", + outTex, inNormal, inNormal ) ); + } + else + meta->addStatement( new GenOp( " @.z = 0;\r\n", outTex ) ); + + // HACK: This is sort of lazy... we generate the tanget + // vector here so that we're sure it exists in the parallax + // and normal features which will expect "T" to exist. + // + // If this shader doesn't use it the shader compiler will + // optimize away this code. + // + Var *inTangentZ = getVertTexCoord( "tcTangentZ" ); + Var *inTanget = new Var( "T", "vec3" ); + Var *squareSize = _getUniformVar( "squareSize", "float" ); + meta->addStatement( new GenOp( " @ = normalize( vec3( @, 0.0, @ ) );\r\n", + new DecOp( inTanget ), squareSize, inTangentZ ) ); +} + +void TerrainBaseMapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + Var *texCoord = getInTexCoord( "outTexCoord", "vec3", true, componentList ); + + // We do nothing more if this is a prepass. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) + return; + + // create texture var + Var *diffuseMap = new Var; + diffuseMap->setType( "sampler2D" ); + diffuseMap->setName( "baseTexMap" ); + diffuseMap->uniform = true; + diffuseMap->sampler = true; + diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + MultiLine *meta = new MultiLine; + + Var *baseColor = new Var; + baseColor->setType( "vec4" ); + baseColor->setName( "baseColor" ); + meta->addStatement( new GenOp( " @ = texture2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) ); + + output = meta; +} + +ShaderFeature::Resources TerrainBaseMapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTexReg = 1; + + // We only sample from the base map during a diffuse pass. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + res.numTex = 1; + + return res; +} + +void TerrainEmptyFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *inEmpty = getVertTexCoord( "tcEmpty" ); + + // Pass the empty state down to the pixel shader too. + Var *outEmpty = connectComp->getElement( RT_TEXCOORD ); + outEmpty->setName( "outEmpty" ); + outEmpty->setType( "float" ); + outEmpty->mapsToSampler = false; + output = new GenOp( " @ = @;\r\n", outEmpty, inEmpty ); +} + +void TerrainEmptyFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Now we clip empty terrain squares. + // + // We packed a 1 or -1 into the empty texcoord based + // on if the vertex was part of an empty square or not. + // + // Since the values from the pixel shader are + // interplolated we add a value close to 1 in + // order to limit the clipping to triangles + // where all 3 verts are -1. + // + Var *inEmpty = getInTexCoord( "outEmpty", "float", false, componentList ); + output = new GenOp( " if (@ + 0.999 < 0.0)\r\n discard;\r\n", inEmpty ); +} + +ShaderFeature::Resources TerrainEmptyFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTexReg = 1; + return res; +} + +TerrainDetailMapFeatGLSL::TerrainDetailMapFeatGLSL() + : mTerrainDep( "shaders/common/terrain/terrain.glsl" ) +{ + addDependency( &mTerrainDep ); +} + +void TerrainDetailMapFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + const U32 detailIndex = getProcessIndex(); + + + // If this is a prepass and we don't have a + // matching normal map... we have nothing to do. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) && + !fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) ) + return; + + // Grab incoming texture coords... the base map feature + // made sure this was created. + Var *inTex = (Var*)LangElement::find( "texCoord" ); + AssertFatal( inTex, "The texture coord is missing!" ); + + // Grab the input position. + Var *inPos = (Var*)LangElement::find( "inPosition" ); + if ( !inPos ) + inPos = (Var*)LangElement::find( "position" ); + + // Get the object space eye position. + Var *eyePos = _getUniformVar( "eyePos", "vec3" ); + + MultiLine *meta = new MultiLine; + + // Get the distance from the eye to this vertex. + Var *dist = (Var*)LangElement::find( "dist" ); + if ( !dist ) + { + dist = new Var; + dist->setType( "float" ); + dist->setName( "dist" ); + + meta->addStatement( new GenOp( " @ = distance( @.xyz, @ );\r\n", + new DecOp( dist ), inPos, eyePos ) ); + } + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( String::ToString( "outDetCoord%d", detailIndex ) ); + outTex->setType( "vec4" ); + outTex->mapsToSampler = true; + + // Get the detail scale and fade info. + Var *detScaleAndFade = new Var; + detScaleAndFade->setType( "vec4" ); + detScaleAndFade->setName( String::ToString( "detailScaleAndFade%d", detailIndex ) ); + detScaleAndFade->uniform = true; + detScaleAndFade->constSortPos = cspPass; + + // Setup the detail coord. + // + // NOTE: You see here we scale the texture coord by 'xyx' + // to generate the detail coord. This y is here because + // its scale is flipped to correct for the non negative y + // in texCoord. + // + // See TerrainBaseMapFeatHLSL::processVert(). + // + meta->addStatement( new GenOp( " @.xyz = @ * @.xyx;\r\n", outTex, inTex, detScaleAndFade ) ); + + // And sneak the detail fade thru the w detailCoord. + meta->addStatement( new GenOp( " @.w = clamp( ( @.z - @ ) * @.w, 0.0, 1.0 );\r\n", + outTex, detScaleAndFade, dist, detScaleAndFade ) ); + + output = meta; +} + +void TerrainDetailMapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + const U32 detailIndex = getProcessIndex(); + + // If this is a prepass and we don't have a + // matching normal map... we have nothing to do. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) && + !fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) ) + return; + + Var *inTex = getVertTexCoord( "outTexCoord" ); + + MultiLine *meta = new MultiLine; + + // Get the layer samples. + Var *layerSample = (Var*)LangElement::find( "layerSample" ); + if ( !layerSample ) + { + layerSample = new Var; + layerSample->setType( "vec4" ); + layerSample->setName( "layerSample" ); + + // Get the layer texture var + Var *layerTex = new Var; + layerTex->setType( "sampler2D" ); + layerTex->setName( "layerTex" ); + layerTex->uniform = true; + layerTex->sampler = true; + layerTex->constNum = Var::getTexUnitNum(); + + // Read the layer texture to get the samples. + meta->addStatement( new GenOp( " @ = round( texture2D( @, @.xy ) * 255.0f );\r\n", + new DecOp( layerSample ), layerTex, inTex ) ); + } + + Var *layerSize = (Var*)LangElement::find( "layerSize" ); + if ( !layerSize ) + { + layerSize = new Var; + layerSize->setType( "float" ); + layerSize->setName( "layerSize" ); + layerSize->uniform = true; + layerSize->constSortPos = cspPass; + } + + // Grab the incoming detail coord. + Var *inDet = _getInDetailCoord( componentList ); + + // Get the detail id. + Var *detailInfo = _getDetailIdStrengthParallax(); + + // Create the detail blend var. + Var *detailBlend = new Var; + detailBlend->setType( "float" ); + detailBlend->setName( String::ToString( "detailBlend%d", detailIndex ) ); + + // Calculate the blend for this detail texture. + meta->addStatement( new GenOp( " @ = calcBlend( @.x, @.xy, @, @ );\r\n", + new DecOp( detailBlend ), detailInfo, inTex, layerSize, layerSample ) ); + + // Get a var and accumulate the blend amount. + Var *blendTotal = (Var*)LangElement::find( "blendTotal" ); + if ( !blendTotal ) + { + blendTotal = new Var; + blendTotal->setName( "blendTotal" ); + blendTotal->setType( "float" ); + meta->addStatement( new GenOp( " @ = 0.0;\r\n", new DecOp( blendTotal ) ) ); + } + + // Add to the blend total. + meta->addStatement( new GenOp( " @ += @;\r\n", blendTotal, detailBlend ) ); + //meta->addStatement( new GenOp( " @ += @ * @.y * @.w;\r\n", + //blendTotal, detailBlend, detailInfo, inDet ) ); + + // Nothing more to do for a detail texture in prepass. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) + { + output = meta; + return; + } + + Var *detailColor = (Var*)LangElement::find( "detailColor" ); + if ( !detailColor ) + { + detailColor = new Var; + detailColor->setType( "vec4" ); + detailColor->setName( "detailColor" ); + meta->addStatement( new GenOp( " @;\r\n", new DecOp( detailColor ) ) ); + } + + // Get the detail texture. + Var *detailMap = new Var; + detailMap->setType( "sampler2D" ); + detailMap->setName( String::ToString( "detailMap%d", detailIndex ) ); + detailMap->uniform = true; + detailMap->sampler = true; + detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // If we're using SM 3.0 then take advantage of + // dynamic branching to skip layers per-pixel. + // + // TODO: I disabled branching as it currently causes + // sparkling artifacts... this is probably an issue of + // not using tex2Dlod() or that i'm using ansiotropic + // fliltering... maybe? + // + if ( false && GFX->getPixelShaderVersion() >= 3.0f ) + meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) ); + + meta->addStatement( new GenOp( " {\r\n" ) ); + + // Note that we're doing the standard greyscale detail + // map technique here which can darken and lighten the + // diffuse texture. + // + // We take two color samples and lerp between them for + // side projection layers... else a single sample. + // + if ( fd.features.hasFeature( MFT_TerrainSideProject, detailIndex ) ) + { + meta->addStatement( new GenOp( " @ = ( mix( texture2D( @, @.yz ), texture2D( @, @.xz ), @.z ) * 2.0 ) - 1.0;\r\n", + detailColor, detailMap, inDet, detailMap, inDet, inTex ) ); + } + else + { + meta->addStatement( new GenOp( " @ = ( texture2D( @, @.xy ) * 2.0 ) - 1.0;\r\n", + detailColor, detailMap, inDet ) ); + } + + meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n", + detailColor, detailInfo, inDet ) ); + + Var *baseColor = (Var*)LangElement::find( "baseColor" ); + Var *outColor = (Var*)LangElement::find( "col" ); + + meta->addStatement( new GenOp( " @ = mix( @, @ + @, @ );\r\n", + outColor, outColor, baseColor, detailColor, detailBlend ) ); + + meta->addStatement( new GenOp( " }\r\n" ) ); + + output = meta; +} + +ShaderFeature::Resources TerrainDetailMapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) + { + // If this is a prepass and we don't have a + // matching normal map... we use no resources. + if ( !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() ) ) + return res; + + // If this is the first matching normal map then + // it also samples from the layer tex. + if ( !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() - 1 ) ) + res.numTex += 1; + } + else + { + // If this is the first detail pass then it + // also samples from the layer tex. + if ( !fd.features.hasFeature( MFT_TerrainDetailMap, getProcessIndex() - 1 ) ) + res.numTex += 1; + + res.numTex += 1; + } + + res.numTexReg += 1; + + return res; +} + +void TerrainNormalMapFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // We only need to process normals during the prepass. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + return; + + MultiLine *meta = new MultiLine; + + // Make sure the world to tangent transform + // is created and available for the pixel shader. + getOutViewToTangent( componentList, meta ); + + output = meta; +} + +void TerrainNormalMapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // We only need to process normals during the prepass. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + return; + + MultiLine *meta = new MultiLine; + + Var *viewToTangent = getInViewToTangent( componentList ); + + // This var is read from GBufferConditionerGLSL and + // used in the prepass output. + Var *gbNormal = (Var*)LangElement::find( "gbNormal" ); + if ( !gbNormal ) + { + gbNormal = new Var; + gbNormal->setName( "gbNormal" ); + gbNormal->setType( "vec3" ); + meta->addStatement( new GenOp( " @ = @[2];\r\n", new DecOp( gbNormal ), viewToTangent ) ); + } + + const U32 normalIndex = getProcessIndex(); + + Var *detailBlend = (Var*)LangElement::find( String::ToString( "detailBlend%d", normalIndex ) ); + AssertFatal( detailBlend, "The detail blend is missing!" ); + + // If we're using SM 3.0 then take advantage of + // dynamic branching to skip layers per-pixel. + // + // TODO: I disabled branching as it currently causes + // sparkling artifacts... this is probably an issue of + // not using tex2Dlod() or that i'm using ansiotropic + // fliltering... maybe? + // + if ( false && GFX->getPixelShaderVersion() >= 3.0f ) + meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) ); + + meta->addStatement( new GenOp( " {\r\n" ) ); + + // Get the normal map texture. + Var *normalMap = _getNormalMapTex(); + + /// Get the texture coord. + Var *inDet = _getInDetailCoord( componentList ); + Var *inTex = getVertTexCoord( "outTexCoord" ); + + // Sample the normal map. + // + // We take two normal samples and lerp between them for + // side projection layers... else a single sample. + LangElement *texOp; + if ( fd.features.hasFeature( MFT_TerrainSideProject, normalIndex ) ) + { + texOp = new GenOp( "mix( texture2D( @, @.yz ), texture2D( @, @.xz ), @.z )", + normalMap, inDet, normalMap, inDet, inTex ); + } + else + texOp = new GenOp( "texture2D(@, @.xy)", normalMap, inDet ); + + // create bump normal + Var *bumpNorm = new Var; + bumpNorm->setName( "bumpNormal" ); + bumpNorm->setType( "vec4" ); + + LangElement *bumpNormDecl = new DecOp( bumpNorm ); + meta->addStatement( expandNormalMap( texOp, bumpNormDecl, bumpNorm, fd ) ); + + // Normalize is done later... + // Note: The reverse mul order is intentional. Affine matrix. + meta->addStatement( new GenOp( " @ = mix( @, @.xyz * @, min( @, @.w ) );\r\n", + gbNormal, gbNormal, bumpNorm, viewToTangent, detailBlend, inDet ) ); + + // End the conditional block. + meta->addStatement( new GenOp( " }\r\n" ) ); + + // If this is the last normal map then we + // can test to see the total blend value + // to see if we should clip the result. + //if ( fd.features.getNextFeatureIndex( MFT_TerrainNormalMap, normalIndex ) == -1 ) + //meta->addStatement( new GenOp( " clip( @ - 0.0001f );\r\n", blendTotal ) ); + + output = meta; +} + +ShaderFeature::Resources TerrainNormalMapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // We only need to process normals during the prepass. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) + { + // If this is the first normal map then it + // will generate the worldToTanget transform. + if ( !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() - 1 ) ) + res.numTexReg = 3; + + res.numTex = 1; + } + + return res; +} + +TerrainParallaxMapFeatGLSL::TerrainParallaxMapFeatGLSL() + : mIncludeDep( "shaders/common/gl/torque.glsl" ) +{ + addDependency( &mIncludeDep ); +} + +void TerrainParallaxMapFeatGLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + if ( LangElement::find( "outNegViewTS" ) ) + return; + + MultiLine *meta = new MultiLine; + + // Grab the input position. + Var *inPos = (Var*)LangElement::find( "inPosition" ); + if ( !inPos ) + inPos = (Var*)LangElement::find( "position" ); + + // Get the object space eye position and the + // object to tangent transform. + Var *eyePos = _getUniformVar( "eyePos", "vec3" ); + Var *objToTangentSpace = getOutObjToTangentSpace( componentList, meta ); + + // Now send the negative view vector in tangent space to the pixel shader. + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outNegViewTS = connectComp->getElement( RT_TEXCOORD ); + outNegViewTS->setName( "outNegViewTS" ); + outNegViewTS->setType( "vec3" ); + meta->addStatement( new GenOp( " @ = @ * vec3( @ - @.xyz );\r\n", + outNegViewTS, objToTangentSpace, eyePos, inPos ) ); + + output = meta; +} + +void TerrainParallaxMapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // We need the negative tangent space view vector + // as in parallax mapping we step towards the camera. + Var *negViewTS = (Var*)LangElement::find( "negViewTS" ); + if ( !negViewTS ) + { + Var *inNegViewTS = (Var*)LangElement::find( "outNegViewTS" ); + if ( !inNegViewTS ) + { + inNegViewTS = connectComp->getElement( RT_TEXCOORD ); + inNegViewTS->setName( "outNegViewTS" ); + inNegViewTS->setType( "vec3" ); + } + + negViewTS = new Var( "negViewTS", "vec3" ); + meta->addStatement( new GenOp( " @ = normalize( @ );\r\n", new DecOp( negViewTS ), inNegViewTS ) ); + } + + // Get the rest of our inputs. + Var *detailInfo = _getDetailIdStrengthParallax(); + Var *normalMap = _getNormalMapTex(); + Var *texCoord = _getInDetailCoord( componentList ); + + // Call the library function to do the rest. + meta->addStatement( new GenOp( " @.xy += parallaxOffset( @, @.xy, @, @.z );\r\n", + texCoord, normalMap, texCoord, negViewTS, detailInfo ) ); + + output = meta; +} + +ShaderFeature::Resources TerrainParallaxMapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // If this is the first parallax feature then + // it will generate the tangetEye vector and + // the worldToTanget transform. + if ( getProcessIndex() == 0 || !fd.features.hasFeature( MFT_TerrainParallaxMap, getProcessIndex() - 1 ) ) + res.numTexReg = 4; + + // If this isn't the prepass then we will + // be adding a normal map. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + res.numTex = 1; + + return res; +} + +void TerrainLightMapFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + Var *inTex = (Var*)LangElement::find( "outTexCoord" ); + if ( !inTex ) + return; + + // Get the lightmap texture. + Var *lightMap = new Var; + lightMap->setType( "sampler2D" ); + lightMap->setName( "lightMapTex" ); + lightMap->uniform = true; + lightMap->sampler = true; + lightMap->constNum = Var::getTexUnitNum(); + + // Create a 'lightMask' value which is read by + // RTLighting to mask out the directional lighting. + Var *lightMask = new Var; + lightMask->setType( "vec3" ); + lightMask->setName( "lightMask" ); + + output = new GenOp( " @ = texture2D( @, @.xy ).rgb;\r\n", new DecOp( lightMask ), lightMap, inTex ); +} + +ShaderFeature::Resources TerrainLightMapFeatGLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + return res; +} + + +void TerrainAdditiveFeatGLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *color = (Var*) LangElement::find( "col" ); + Var *blendTotal = (Var*)LangElement::find( "blendTotal" ); + if ( !color || !blendTotal ) + return; + + MultiLine *meta = new MultiLine; + + meta->addStatement( new GenOp( " if ( @ - 0.0001 < 0.0 ) discard;\r\n", blendTotal ) ); + meta->addStatement( new GenOp( " @.a = @;\r\n", color, blendTotal ) ); + + output = meta; +} diff --git a/terrain/glsl/terrFeatureGLSL.h b/terrain/glsl/terrFeatureGLSL.h new file mode 100644 index 0000000..dd7dcba --- /dev/null +++ b/terrain/glsl/terrFeatureGLSL.h @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRFEATUREGLSL_H_ +#define _TERRFEATUREGLSL_H_ + +#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_ +#include "shaderGen/GLSL/shaderFeatureGLSL.h" +#endif + +/// A shared base class for terrain features which +/// includes some helper functions. +class TerrainFeatGLSL : public ShaderFeatureGLSL +{ +protected: + + Var* _getInDetailCoord(Vector &componentList ); + + Var* _getNormalMapTex(); + + static Var* _getUniformVar( const char *name, const char *type ); + + Var* _getDetailIdStrengthParallax(); + +}; + +class TerrainBaseMapFeatGLSL : public TerrainFeatGLSL +{ +public: + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Base Texture"; } +}; + +class TerrainEmptyFeatGLSL : public TerrainFeatGLSL +{ +public: + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Empty"; } +}; + +class TerrainDetailMapFeatGLSL : public TerrainFeatGLSL +{ +protected: + + ShaderIncludeDependency mTerrainDep; + +public: + + TerrainDetailMapFeatGLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Detail Texture"; } +}; + + +class TerrainNormalMapFeatGLSL : public TerrainFeatGLSL +{ +public: + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Normal Texture"; } +}; + +class TerrainParallaxMapFeatGLSL : public TerrainFeatGLSL +{ +protected: + + ShaderIncludeDependency mIncludeDep; + +public: + + TerrainParallaxMapFeatGLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Parallax Texture"; } +}; + +class TerrainLightMapFeatGLSL : public TerrainFeatGLSL +{ +public: + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Lightmap Texture"; } +}; + + +class TerrainAdditiveFeatGLSL : public TerrainFeatGLSL +{ +public: + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Additive"; } +}; + +#endif // _TERRFEATUREGLSL_H_ diff --git a/terrain/hlsl/terrFeatureHLSL.cpp b/terrain/hlsl/terrFeatureHLSL.cpp new file mode 100644 index 0000000..f8a39d8 --- /dev/null +++ b/terrain/hlsl/terrFeatureHLSL.cpp @@ -0,0 +1,739 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/hlsl/terrFeatureHLSL.h" + +#include "terrain/terrFeatureTypes.h" +#include "materials/materialFeatureTypes.h" +#include "gfx/gfxDevice.h" +#include "shaderGen/langElement.h" +#include "shaderGen/shaderOp.h" +#include "shaderGen/featureMgr.h" + + +OnTorqueStartup +{ + FEATUREMGR->registerFeature( MFT_TerrainEmpty, new TerrainEmptyFeatHLSL ); + FEATUREMGR->registerFeature( MFT_TerrainBaseMap, new TerrainBaseMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_TerrainParallaxMap, new NamedFeatureHLSL( "Terrain Parallax Texture" ) ); + FEATUREMGR->registerFeature( MFT_TerrainDetailMap, new TerrainDetailMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_TerrainNormalMap, new TerrainNormalMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatHLSL ); + FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureHLSL( "Terrain Side Projection" ) ); + FEATUREMGR->registerFeature( MFT_TerrainAdditive, new TerrainAdditiveFeatHLSL ); +} + + +Var* TerrainFeatHLSL::_getUniformVar( const char *name, const char *type ) +{ + Var *theVar = (Var*)LangElement::find( name ); + if ( !theVar ) + { + theVar = new Var; + theVar->setType( type ); + theVar->setName( name ); + theVar->uniform = true; + theVar->constSortPos = cspPass; + } + + return theVar; +} + +Var* TerrainFeatHLSL::_getInDetailCoord( Vector &componentList ) +{ + String name( String::ToString( "detCoord%d", getProcessIndex() ) ); + Var *inDet = (Var*)LangElement::find( name ); + + if ( !inDet ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + inDet = connectComp->getElement( RT_TEXCOORD ); + inDet->setName( name ); + inDet->setStructName( "IN" ); + inDet->setType( "float4" ); + inDet->mapsToSampler = true; + } + + return inDet; +} + +Var* TerrainFeatHLSL::_getNormalMapTex() +{ + String name( String::ToString( "normalMap%d", getProcessIndex() ) ); + Var *normalMap = (Var*)LangElement::find( name ); + + if ( !normalMap ) + { + normalMap = new Var; + normalMap->setType( "sampler2D" ); + normalMap->setName( name ); + normalMap->uniform = true; + normalMap->sampler = true; + normalMap->constNum = Var::getTexUnitNum(); + } + + return normalMap; +} + +Var* TerrainFeatHLSL::_getDetailIdStrengthParallax() +{ + String name( String::ToString( "detailIdStrengthParallax%d", getProcessIndex() ) ); + + Var *detailInfo = (Var*)LangElement::find( name ); + if ( !detailInfo ) + { + detailInfo = new Var; + detailInfo->setType( "float3" ); + detailInfo->setName( name ); + detailInfo->uniform = true; + detailInfo->constSortPos = cspPass; + } + + return detailInfo; +} + +void TerrainBaseMapFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + MultiLine *meta = new MultiLine; + output = meta; + + // Generate the incoming texture var. + Var *inTex; + { + Var *inPos = (Var*)LangElement::find( "inPosition" ); + if ( !inPos ) + inPos = (Var*)LangElement::find( "position" ); + + inTex = new Var( "texCoord", "float3" ); + + Var *oneOverTerrainSize = _getUniformVar( "oneOverTerrainSize", "float" ); + + // NOTE: The y coord here should be negative to have + // the texture maps not end up flipped which also caused + // normal and parallax mapping to be incorrect. + // + // This mistake early in development means that the layer + // id bilinear blend depends on it being that way. + // + // So instead i fixed this by flipping the base and detail + // coord y scale to compensate when rendering. + // + meta->addStatement( new GenOp( " @ = @.xyz * float3( @, @, -@ );\r\n", + new DecOp( inTex ), inPos, oneOverTerrainSize, oneOverTerrainSize, oneOverTerrainSize ) ); + } + + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + // Pass the texture coord to the pixel shader. + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( "outTexCoord" ); + outTex->setStructName( "OUT" ); + outTex->setType( "float3" ); + outTex->mapsToSampler = true; + meta->addStatement( new GenOp( " @.xy = @.xy;\r\n", outTex, inTex ) ); + + // If this shader has a side projected layer then we + // pass the dot product between the +Y and the normal + // thru outTexCoord.z for use in blending the textures. + if ( fd.features.hasFeature( MFT_TerrainSideProject ) ) + { + Var *inNormal = (Var*)LangElement::find( "normal" ); + meta->addStatement( + new GenOp( " @.z = pow( abs( dot( normalize( float3( @.x, @.y, 0 ) ), float3( 0, 1, 0 ) ) ), 10.0 );\r\n", + outTex, inNormal, inNormal ) ); + } + else + meta->addStatement( new GenOp( " @.z = 0;\r\n", outTex ) ); + + // HACK: This is sort of lazy... we generate the tanget + // vector here so that we're sure it exists in the parallax + // and normal features which will expect "T" to exist. + // + // If this shader doesn't use it the shader compiler will + // optimize away this code. + // + Var *inTangentZ = getVertTexCoord( "tcTangentZ" ); + Var *inTanget = new Var( "T", "float3" ); + Var *squareSize = _getUniformVar( "squareSize", "float" ); + meta->addStatement( new GenOp( " @ = normalize( float3( @, 0, @ ) );\r\n", + new DecOp( inTanget ), squareSize, inTangentZ ) ); +} + +void TerrainBaseMapFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + Var *texCoord = getInTexCoord( "texCoord", "float3", true, componentList ); + + // We do nothing more if this is a prepass. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) + return; + + // create texture var + Var *diffuseMap = new Var; + diffuseMap->setType( "sampler2D" ); + diffuseMap->setName( "baseTexMap" ); + diffuseMap->uniform = true; + diffuseMap->sampler = true; + diffuseMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + MultiLine *meta = new MultiLine; + + Var *baseColor = new Var; + baseColor->setType( "float4" ); + baseColor->setName( "baseColor" ); + meta->addStatement( new GenOp( " @ = tex2D( @, @.xy );\r\n", new DecOp( baseColor ), diffuseMap, texCoord ) ); + meta->addStatement( new GenOp( " @;\r\n", assignColor( baseColor, Material::Mul ) ) ); + + output = meta; +} + +ShaderFeature::Resources TerrainBaseMapFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTexReg = 1; + + // We only sample from the base map during a diffuse pass. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + res.numTex = 1; + + return res; +} + +void TerrainEmptyFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + + Var *inEmpty = getVertTexCoord( "tcEmpty" ); + + // Pass the empty state down to the pixel shader too. + Var *outEmpty = connectComp->getElement( RT_TEXCOORD ); + outEmpty->setName( "outEmpty" ); + outEmpty->setStructName( "OUT" ); + outEmpty->setType( "float" ); + outEmpty->mapsToSampler = false; + output = new GenOp( " @ = @;\r\n", outEmpty, inEmpty ); +} + +void TerrainEmptyFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // Now we clip empty terrain squares. + // + // We packed a 1 or -1 into the empty texcoord based + // on if the vertex was part of an empty square or not. + // + // Since the values from the pixel shader are + // interplolated we add a value close to 1 in + // order to limit the clipping to triangles + // where all 3 verts are -1. + // + Var *inEmpty = getInTexCoord( "tcEmpty", "float", false, componentList ); + output = new GenOp( " clip( @ + 0.999 );\r\n", inEmpty ); +} + +ShaderFeature::Resources TerrainEmptyFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTexReg = 1; + return res; +} + + +TerrainDetailMapFeatHLSL::TerrainDetailMapFeatHLSL() + : mTorqueDep( "shaders/common/torque.hlsl" ), + mTerrainDep( "shaders/common/terrain/terrain.hlsl" ) + +{ + addDependency( &mTorqueDep ); + addDependency( &mTerrainDep ); +} + +void TerrainDetailMapFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + const U32 detailIndex = getProcessIndex(); + + // Grab incoming texture coords... the base map feature + // made sure this was created. + Var *inTex = (Var*)LangElement::find( "texCoord" ); + AssertFatal( inTex, "The texture coord is missing!" ); + + // Grab the input position. + Var *inPos = (Var*)LangElement::find( "inPosition" ); + if ( !inPos ) + inPos = (Var*)LangElement::find( "position" ); + + // Get the object space eye position. + Var *eyePos = _getUniformVar( "eyePos", "float3" ); + + MultiLine *meta = new MultiLine; + + // If we have parallax mapping then make sure we've sent + // the negative view vector to the pixel shader. + if ( fd.features.hasFeature( MFT_TerrainParallaxMap ) && + !LangElement::find( "outNegViewTS" ) ) + { + // Get the object to tangent transform which + // will consume 3 output registers. + Var *objToTangentSpace = getOutObjToTangentSpace( componentList, meta ); + + // Now use a single output register to send the negative + // view vector in tangent space to the pixel shader. + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outNegViewTS = connectComp->getElement( RT_TEXCOORD ); + outNegViewTS->setName( "outNegViewTS" ); + outNegViewTS->setStructName( "OUT" ); + outNegViewTS->setType( "float3" ); + meta->addStatement( new GenOp( " @ = mul( @, float3( @ - @.xyz ) );\r\n", + outNegViewTS, objToTangentSpace, eyePos, inPos ) ); + } + + // Get the distance from the eye to this vertex. + Var *dist = (Var*)LangElement::find( "dist" ); + if ( !dist ) + { + dist = new Var; + dist->setType( "float" ); + dist->setName( "dist" ); + + meta->addStatement( new GenOp( " @ = distance( @.xyz, @ );\r\n", + new DecOp( dist ), inPos, eyePos ) ); + } + + // grab connector texcoord register + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + Var *outTex = connectComp->getElement( RT_TEXCOORD ); + outTex->setName( String::ToString( "detCoord%d", detailIndex ) ); + outTex->setStructName( "OUT" ); + outTex->setType( "float4" ); + outTex->mapsToSampler = true; + + // Get the detail scale and fade info. + Var *detScaleAndFade = new Var; + detScaleAndFade->setType( "float4" ); + detScaleAndFade->setName( String::ToString( "detailScaleAndFade%d", detailIndex ) ); + detScaleAndFade->uniform = true; + detScaleAndFade->constSortPos = cspPass; + + // Setup the detail coord. + // + // NOTE: You see here we scale the texture coord by 'xyx' + // to generate the detail coord. This y is here because + // its scale is flipped to correct for the non negative y + // in texCoord. + // + // See TerrainBaseMapFeatHLSL::processVert(). + // + meta->addStatement( new GenOp( " @.xyz = @ * @.xyx;\r\n", outTex, inTex, detScaleAndFade ) ); + + // And sneak the detail fade thru the w detailCoord. + meta->addStatement( new GenOp( " @.w = clamp( ( @.z - @ ) * @.w, 0.0, 1.0 );\r\n", + outTex, detScaleAndFade, dist, detScaleAndFade ) ); + + output = meta; +} + +void TerrainDetailMapFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + const U32 detailIndex = getProcessIndex(); + Var *inTex = getVertTexCoord( "texCoord" ); + + MultiLine *meta = new MultiLine; + + // We need the negative tangent space view vector + // as in parallax mapping we step towards the camera. + Var *negViewTS = (Var*)LangElement::find( "negViewTS" ); + if ( !negViewTS && + fd.features.hasFeature( MFT_TerrainParallaxMap ) ) + { + Var *inNegViewTS = (Var*)LangElement::find( "outNegViewTS" ); + if ( !inNegViewTS ) + { + ShaderConnector *connectComp = dynamic_cast( componentList[C_CONNECTOR] ); + inNegViewTS = connectComp->getElement( RT_TEXCOORD ); + inNegViewTS->setName( "outNegViewTS" ); + inNegViewTS->setStructName( "IN" ); + inNegViewTS->setType( "float3" ); + } + + negViewTS = new Var( "negViewTS", "float3" ); + meta->addStatement( new GenOp( " @ = normalize( @ );\r\n", new DecOp( negViewTS ), inNegViewTS ) ); + } + + // Get the layer samples. + Var *layerSample = (Var*)LangElement::find( "layerSample" ); + if ( !layerSample ) + { + layerSample = new Var; + layerSample->setType( "float4" ); + layerSample->setName( "layerSample" ); + + // Get the layer texture var + Var *layerTex = new Var; + layerTex->setType( "sampler2D" ); + layerTex->setName( "layerTex" ); + layerTex->uniform = true; + layerTex->sampler = true; + layerTex->constNum = Var::getTexUnitNum(); + + // Read the layer texture to get the samples. + meta->addStatement( new GenOp( " @ = round( tex2D( @, @.xy ) * 255.0f );\r\n", + new DecOp( layerSample ), layerTex, inTex ) ); + } + + Var *layerSize = (Var*)LangElement::find( "layerSize" ); + if ( !layerSize ) + { + layerSize = new Var; + layerSize->setType( "float" ); + layerSize->setName( "layerSize" ); + layerSize->uniform = true; + layerSize->constSortPos = cspPass; + } + + // Grab the incoming detail coord. + Var *inDet = _getInDetailCoord( componentList ); + + // Get the detail id. + Var *detailInfo = _getDetailIdStrengthParallax(); + + // Create the detail blend var. + Var *detailBlend = new Var; + detailBlend->setType( "float" ); + detailBlend->setName( String::ToString( "detailBlend%d", detailIndex ) ); + + // Calculate the blend for this detail texture. + meta->addStatement( new GenOp( " @ = calcBlend( @.x, @.xy, @, @ );\r\n", + new DecOp( detailBlend ), detailInfo, inTex, layerSize, layerSample ) ); + + // Get a var and accumulate the blend amount. + Var *blendTotal = (Var*)LangElement::find( "blendTotal" ); + if ( !blendTotal ) + { + blendTotal = new Var; + blendTotal->setName( "blendTotal" ); + blendTotal->setType( "float" ); + meta->addStatement( new GenOp( " @ = 0;\r\n", new DecOp( blendTotal ) ) ); + } + + // Add to the blend total. + meta->addStatement( new GenOp( " @ = max( @, @ );\r\n", blendTotal, blendTotal, detailBlend ) ); + + // If we had a parallax feature... then factor in the parallax + // amount so that it fades out with the layer blending. + if ( fd.features.hasFeature( MFT_TerrainParallaxMap, detailIndex ) ) + { + // Get the rest of our inputs. + Var *normalMap = _getNormalMapTex(); + + // Call the library function to do the rest. + meta->addStatement( new GenOp( " @.xy += parallaxOffset( @, @.xy, @, @.z * @ );\r\n", + inDet, normalMap, inDet, negViewTS, detailInfo, detailBlend ) ); + } + + // If this is a prepass then we skip color. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) + { + // Check to see if we have a gbuffer normal. + Var *gbNormal = (Var*)LangElement::find( "gbNormal" ); + + // If we have a gbuffer normal and we don't have a + // normal map feature then we need to lerp in a + // default normal else the normals below this layer + // will show thru. + if ( gbNormal && + !fd.features.hasFeature( MFT_TerrainNormalMap, detailIndex ) ) + { + Var *viewToTangent = getInViewToTangent( componentList ); + + meta->addStatement( new GenOp( " @ = lerp( @, @[2], min( @, @.w ) );\r\n", + gbNormal, gbNormal, viewToTangent, detailBlend, inDet ) ); + } + + output = meta; + return; + } + + Var *detailColor = (Var*)LangElement::find( "detailColor" ); + if ( !detailColor ) + { + detailColor = new Var; + detailColor->setType( "float4" ); + detailColor->setName( "detailColor" ); + meta->addStatement( new GenOp( " @;\r\n", new DecOp( detailColor ) ) ); + } + + // Get the detail texture. + Var *detailMap = new Var; + detailMap->setType( "sampler2D" ); + detailMap->setName( String::ToString( "detailMap%d", detailIndex ) ); + detailMap->uniform = true; + detailMap->sampler = true; + detailMap->constNum = Var::getTexUnitNum(); // used as texture unit num here + + // If we're using SM 3.0 then take advantage of + // dynamic branching to skip layers per-pixel. + // + // TODO: I disabled branching as it currently causes + // sparkling artifacts... this is probably an issue of + // not using tex2Dlod() or that i'm using ansiotropic + // fliltering... maybe? + // + if ( false && GFX->getPixelShaderVersion() >= 3.0f ) + meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) ); + + meta->addStatement( new GenOp( " {\r\n" ) ); + + // Note that we're doing the standard greyscale detail + // map technique here which can darken and lighten the + // diffuse texture. + // + // We take two color samples and lerp between them for + // side projection layers... else a single sample. + // + if ( fd.features.hasFeature( MFT_TerrainSideProject, detailIndex ) ) + { + meta->addStatement( new GenOp( " @ = ( lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z ) * 2.0 ) - 1.0;\r\n", + detailColor, detailMap, inDet, detailMap, inDet, inTex ) ); + } + else + { + meta->addStatement( new GenOp( " @ = ( tex2D( @, @.xy ) * 2.0 ) - 1.0;\r\n", + detailColor, detailMap, inDet ) ); + } + + meta->addStatement( new GenOp( " @ *= @.y * @.w;\r\n", + detailColor, detailInfo, inDet ) ); + + Var *baseColor = (Var*)LangElement::find( "baseColor" ); + Var *outColor = (Var*)LangElement::find( "col" ); + + meta->addStatement( new GenOp( " @ = lerp( @, @ + @, @ );\r\n", + outColor, outColor, baseColor, detailColor, detailBlend ) ); + + meta->addStatement( new GenOp( " }\r\n" ) ); + + output = meta; +} + +ShaderFeature::Resources TerrainDetailMapFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + if ( getProcessIndex() == 0 ) + { + // If this is the first detail pass then we + // samples from the layer tex. + res.numTex += 1; + + // If this material also does parallax then it + // will generate the negative view vector and the + // worldToTanget transform. + if ( fd.features.hasFeature( MFT_TerrainParallaxMap ) ) + res.numTexReg += 4; + } + + // If this isn't the prepass then we sample + // from the detail texture for diffuse coloring. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + res.numTex += 1; + + // If we have parallax for this layer then we'll also + // be sampling the normal map for the parallax heightmap. + if ( fd.features.hasFeature( MFT_TerrainParallaxMap, getProcessIndex() ) ) + res.numTex += 1; + + // Finally we always send the detail texture + // coord to the pixel shader. + res.numTexReg += 1; + + return res; +} + +void TerrainNormalMapFeatHLSL::processVert( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // We only need to process normals during the prepass. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + return; + + MultiLine *meta = new MultiLine; + + // Make sure the world to tangent transform + // is created and available for the pixel shader. + getOutViewToTangent( componentList, meta ); + + output = meta; +} + +void TerrainNormalMapFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // We only need to process normals during the prepass. + if ( !fd.features.hasFeature( MFT_PrePassConditioner ) ) + return; + + MultiLine *meta = new MultiLine; + + Var *viewToTangent = getInViewToTangent( componentList ); + + // This var is read from GBufferConditionerHLSL and + // used in the prepass output. + Var *gbNormal = (Var*)LangElement::find( "gbNormal" ); + if ( !gbNormal ) + { + gbNormal = new Var; + gbNormal->setName( "gbNormal" ); + gbNormal->setType( "float3" ); + meta->addStatement( new GenOp( " @ = @[2];\r\n", new DecOp( gbNormal ), viewToTangent ) ); + } + + const U32 normalIndex = getProcessIndex(); + + Var *detailBlend = (Var*)LangElement::find( String::ToString( "detailBlend%d", normalIndex ) ); + AssertFatal( detailBlend, "The detail blend is missing!" ); + + // If we're using SM 3.0 then take advantage of + // dynamic branching to skip layers per-pixel. + // + // TODO: I disabled branching as it currently causes + // sparkling artifacts... this is probably an issue of + // not using tex2Dlod() or that i'm using ansiotropic + // fliltering... maybe? + // + if ( false && GFX->getPixelShaderVersion() >= 3.0f ) + meta->addStatement( new GenOp( " if ( @ > 0.0f )\r\n", detailBlend ) ); + + meta->addStatement( new GenOp( " {\r\n" ) ); + + // Get the normal map texture. + Var *normalMap = _getNormalMapTex(); + + /// Get the texture coord. + Var *inDet = _getInDetailCoord( componentList ); + Var *inTex = getVertTexCoord( "texCoord" ); + + // Sample the normal map. + // + // We take two normal samples and lerp between them for + // side projection layers... else a single sample. + LangElement *texOp; + if ( fd.features.hasFeature( MFT_TerrainSideProject, normalIndex ) ) + { + texOp = new GenOp( "lerp( tex2D( @, @.yz ), tex2D( @, @.xz ), @.z )", + normalMap, inDet, normalMap, inDet, inTex ); + } + else + texOp = new GenOp( "tex2D(@, @.xy)", normalMap, inDet ); + + // create bump normal + Var *bumpNorm = new Var; + bumpNorm->setName( "bumpNormal" ); + bumpNorm->setType( "float4" ); + + LangElement *bumpNormDecl = new DecOp( bumpNorm ); + meta->addStatement( expandNormalMap( texOp, bumpNormDecl, bumpNorm, fd ) ); + + // Normalize is done later... + // Note: The reverse mul order is intentional. Affine matrix. + meta->addStatement( new GenOp( " @ = lerp( @, mul( @.xyz, @ ), min( @, @.w ) );\r\n", + gbNormal, gbNormal, bumpNorm, viewToTangent, detailBlend, inDet ) ); + + // End the conditional block. + meta->addStatement( new GenOp( " }\r\n" ) ); + + // If this is the last normal map then we + // can test to see the total blend value + // to see if we should clip the result. + //if ( fd.features.getNextFeatureIndex( MFT_TerrainNormalMap, normalIndex ) == -1 ) + //meta->addStatement( new GenOp( " clip( @ - 0.0001f );\r\n", blendTotal ) ); + + output = meta; +} + +ShaderFeature::Resources TerrainNormalMapFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + + // We only need to process normals during the prepass. + if ( fd.features.hasFeature( MFT_PrePassConditioner ) ) + { + // If this is the first normal map and there + // are no parallax features then we will + // generate the worldToTanget transform. + if ( !fd.features.hasFeature( MFT_TerrainParallaxMap ) && + ( getProcessIndex() == 0 || !fd.features.hasFeature( MFT_TerrainNormalMap, getProcessIndex() - 1 ) ) ) + res.numTexReg = 3; + + res.numTex = 1; + } + + return res; +} + +void TerrainLightMapFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + // grab connector texcoord register + Var *inTex = (Var*)LangElement::find( "texCoord" ); + if ( !inTex ) + return; + + // Get the lightmap texture. + Var *lightMap = new Var; + lightMap->setType( "sampler2D" ); + lightMap->setName( "lightMapTex" ); + lightMap->uniform = true; + lightMap->sampler = true; + lightMap->constNum = Var::getTexUnitNum(); + + MultiLine *meta = new MultiLine; + + // Find or create the lightMask value which is read by + // RTLighting to mask out the lights. + // + // The first light is always the sunlight so we apply + // the shadow mask to only the first channel. + // + Var *lightMask = (Var*)LangElement::find( "lightMask" ); + if ( !lightMask ) + { + lightMask = new Var( "lightMask", "float4" ); + meta->addStatement( new GenOp( " @ = 1;\r\n", new DecOp( lightMask ) ) ); + } + + meta->addStatement( new GenOp( " @[0] = tex2D( @, @.xy ).r;\r\n", lightMask, lightMap, inTex ) ); + output = meta; +} + +ShaderFeature::Resources TerrainLightMapFeatHLSL::getResources( const MaterialFeatureData &fd ) +{ + Resources res; + res.numTex = 1; + return res; +} + + +void TerrainAdditiveFeatHLSL::processPix( Vector &componentList, + const MaterialFeatureData &fd ) +{ + Var *color = (Var*) LangElement::find( "col" ); + Var *blendTotal = (Var*)LangElement::find( "blendTotal" ); + if ( !color || !blendTotal ) + return; + + MultiLine *meta = new MultiLine; + + meta->addStatement( new GenOp( " clip( @ - 0.0001 );\r\n", blendTotal ) ); + meta->addStatement( new GenOp( " @.a = @;\r\n", color, blendTotal ) ); + + output = meta; +} diff --git a/terrain/hlsl/terrFeatureHLSL.h b/terrain/hlsl/terrFeatureHLSL.h new file mode 100644 index 0000000..cab44e2 --- /dev/null +++ b/terrain/hlsl/terrFeatureHLSL.h @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRFEATUREHLSL_H_ +#define _TERRFEATUREHLSL_H_ + +#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_ +#include "shaderGen/hlsl/shaderFeatureHLSL.h" +#endif + + +/// A shared base class for terrain features which +/// includes some helper functions. +class TerrainFeatHLSL : public ShaderFeatureHLSL +{ +protected: + + Var* _getInDetailCoord(Vector &componentList ); + + Var* _getNormalMapTex(); + + static Var* _getUniformVar( const char *name, const char *type ); + + Var* _getDetailIdStrengthParallax(); + +}; + + +class TerrainBaseMapFeatHLSL : public TerrainFeatHLSL +{ +public: + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Base Texture"; } +}; + + +class TerrainEmptyFeatHLSL : public TerrainFeatHLSL +{ +public: + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Empty"; } +}; + + +class TerrainDetailMapFeatHLSL : public TerrainFeatHLSL +{ +protected: + + ShaderIncludeDependency mTorqueDep; + ShaderIncludeDependency mTerrainDep; + +public: + + TerrainDetailMapFeatHLSL(); + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Detail Texture"; } +}; + + +class TerrainNormalMapFeatHLSL : public TerrainFeatHLSL +{ +public: + + virtual void processVert( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Normal Texture"; } +}; + +class TerrainLightMapFeatHLSL : public TerrainFeatHLSL +{ +public: + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual Resources getResources( const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Lightmap Texture"; } +}; + + +class TerrainAdditiveFeatHLSL : public TerrainFeatHLSL +{ +public: + + virtual void processPix( Vector &componentList, + const MaterialFeatureData &fd ); + + virtual String getName() { return "Terrain Additive"; } +}; + +#endif // _TERRFEATUREHLSL_H_ diff --git a/terrain/terrCell.cpp b/terrain/terrCell.cpp new file mode 100644 index 0000000..c246636 --- /dev/null +++ b/terrain/terrCell.cpp @@ -0,0 +1,714 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrCell.h" + +#include "math/util/frustum.h" +#include "terrain/terrData.h" +#include "terrain/terrCellMaterial.h" +#include "sceneGraph/sceneState.h" +#include "gfx/gfxDrawUtil.h" + + +GFXImplementVertexFormat( TerrVertex ) +{ + addElement( GFXSemantic::POSITION, GFXDeclType_Float3 ); + addElement( GFXSemantic::NORMAL, GFXDeclType_Float3 ); + addElement( "TangentZ", GFXDeclType_Float, 0 ); + addElement( "Empty", GFXDeclType_Float, 1 ); +}; + +const U32 TerrCell::smMinCellSize = 64; +const U32 TerrCell::smVBStride = TerrCell::smMinCellSize + 1; // 129 +const U32 TerrCell::smVBSize = ( TerrCell::smVBStride * TerrCell::smVBStride ) + + ( TerrCell::smVBStride * 4 ); // 17,157 +const U32 TerrCell::smPBSize = ( TerrCell::smMinCellSize * TerrCell::smMinCellSize * 6 ) + + ( TerrCell::smMinCellSize * 4 * 6 ); // 101,376 +const U32 TerrCell::smTriCount = TerrCell::smPBSize / 3; // 33,792 + + +TerrCell::TerrCell() + : mMaterials( 0 ), + mMaterial( NULL ) +{ + dMemset( mChildren, 0, sizeof( mChildren ) ); +} + +TerrCell::~TerrCell() +{ + SAFE_DELETE( mMaterial ); + + for ( U32 i=0; i < 4; i++ ) + SAFE_DELETE( mChildren[i] ); +} + +void TerrCell::createPrimBuffer( GFXPrimitiveBufferHandle *primBuffer ) +{ + PROFILE_SCOPE( TerrCell_AllocPrimBuffer ); + + primBuffer->set( GFX, smPBSize, 1, GFXBufferTypeStatic, "TerrCell" ); + + // We don't use the primitive for normal clipmap + // rendering, but it is used for the shadow pass. + GFXPrimitive *prim = primBuffer->getPointer()->mPrimitiveArray; + prim->type = GFXTriangleList; + prim->numPrimitives = smTriCount; + prim->numVertices = smVBSize; + + // + // The vertex pattern for the terrain is as + // follows... + // + // 0----1----2.....n + // |\ | /| + // | \ | / | + // | \ | / | + // | \|/ | + // n----n----n + // | /|\ | + // | / | \ | + // | / | \ | + // |/ | \| + // n----n----n + // + + // Lock and fill it up! + U16 *idxBuff; + primBuffer->lock( &idxBuff ); + U32 counter = 0; + U32 maxIndex = 0; + + for ( U32 y = 0; y < smMinCellSize; y++ ) + { + const U32 yTess = y % 2; + + for ( U32 x = 0; x < smMinCellSize; x++ ) + { + U32 index = ( y * smVBStride ) + x; + + const U32 xTess = x % 2; + + if ( ( xTess == 0 && yTess == 0 ) || + ( xTess != 0 && yTess != 0 ) ) + { + idxBuff[0] = index + 0; + idxBuff[1] = index + smVBStride; + idxBuff[2] = index + smVBStride + 1; + + idxBuff[3] = index + 0; + idxBuff[4] = index + smVBStride + 1; + idxBuff[5] = index + 1; + } + else + { + idxBuff[0] = index + 1; + idxBuff[1] = index; + idxBuff[2] = index + smVBStride; + + idxBuff[3] = index + 1; + idxBuff[4] = index + smVBStride; + idxBuff[5] = index + smVBStride + 1; + } + + idxBuff += 6; + maxIndex = index + 1 + smVBStride; + counter += 6; + } + } + + // Now add indices for the 'skirts'. + // These could probably be reduced to a loop. + + // Temporaries that hold triangle indices. + // Top/Bottom - 0,1 + U32 t0, t1, b0, b1; + + // Top edge skirt... + + // Index to the first vert of the top row. + U32 startIndex = 0; + // Index to the first vert of the skirt under the top row. + U32 skirtStartIdx = smVBStride * smVBStride; + // Step to go one vert to the right. + U32 step = 1; + + for ( U32 i = 0; i < smMinCellSize; i++ ) + { + t0 = startIndex + i * step; + t1 = t0 + step; + b0 = skirtStartIdx + i; + b1 = skirtStartIdx + i + 1; + + idxBuff[0] = b0; + idxBuff[1] = t0; + idxBuff[2] = t1; + + idxBuff[3] = b1; + idxBuff[4] = b0; + idxBuff[5] = t1; + + idxBuff += 6; + maxIndex = b1; + counter += 6; + } + + // Bottom edge skirt... + + // Index to the first vert of the bottom row. + startIndex = smVBStride * smVBStride - smVBStride; + // Index to the first vert of the skirt under the bottom row. + skirtStartIdx = startIndex + smVBStride * 2; + // Step to go one vert to the right. + step = 1; + + for ( U32 i = 0; i < smMinCellSize; i++ ) + { + t0 = startIndex + ( i * step ); + t1 = t0 + step; + b0 = skirtStartIdx + i; + b1 = skirtStartIdx + i + 1; + + idxBuff[0] = t1; + idxBuff[1] = t0; + idxBuff[2] = b0; + + idxBuff[3] = t1; + idxBuff[4] = b0; + idxBuff[5] = b1; + + idxBuff += 6; + maxIndex = b1; + counter += 6; + } + + // Left edge skirt... + + // Index to the first vert of the left column. + startIndex = 0; + // Index to the first vert of the skirt under the left column. + skirtStartIdx = smVBStride * smVBStride + smVBStride * 2; + // Step to go one vert down. + step = smVBStride; + + for ( U32 i = 0; i < smMinCellSize; i++ ) + { + t0 = startIndex + ( i * step ); + t1 = t0 + step; + b0 = skirtStartIdx + i; + b1 = skirtStartIdx + i + 1; + + idxBuff[0] = t1; + idxBuff[1] = t0; + idxBuff[2] = b0; + + idxBuff[3] = t1; + idxBuff[4] = b0; + idxBuff[5] = b1; + + idxBuff += 6; + maxIndex = b1; + counter += 6; + } + + // Right edge skirt... + + // Index to the first vert of the right column. + startIndex = smVBStride - 1; + // Index to the first vert of the skirt under the right column. + skirtStartIdx = smVBStride * smVBStride + smVBStride * 3; + // Step to go one vert down. + step = smVBStride; + + for ( U32 i = 0; i < smMinCellSize; i++ ) + { + t0 = startIndex + ( i * step ); + t1 = t0 + step; + b0 = skirtStartIdx + i; + b1 = skirtStartIdx + i + 1; + + idxBuff[0] = b0; + idxBuff[1] = t0; + idxBuff[2] = t1; + + idxBuff[3] = b1; + idxBuff[4] = b0; + idxBuff[5] = t1; + + idxBuff += 6; + maxIndex = b1; + counter += 6; + } + + primBuffer->unlock(); +} + +TerrCell* TerrCell::init( TerrainBlock *terrain ) +{ + // Just create the root cell and call the inner init. + TerrCell *root = new TerrCell; + root->_init( terrain, + Point2I( 0, 0 ), + terrain->getBlockSize(), + 0 ); + + return root; +} + + +void TerrCell::_init( TerrainBlock *terrain, + const Point2I &point, + U32 size, + U32 level ) +{ + PROFILE_SCOPE( TerrCell_Init ); + + mTerrain = terrain; + mPoint = point; + mSize = size; + mLevel = level; + + // Generate a VB for this cell, unless we are the Root cell. + if ( level > 0 ) + _updateVertexBuffer(); + + if ( mSize <= smMinCellSize ) + { + // Update our bounds and materials... the + // parent will use it to update itself. + _updateBounds(); + _updateMaterials(); + return; + } + + // Create our children and update our + // bounds and materials from them. + + const U32 childSize = mSize / 2; + const U32 childLevel = mLevel + 1; + + mChildren[0] = new TerrCell; + mChildren[0]->_init( mTerrain, + Point2I( mPoint.x, mPoint.y ), + childSize, + childLevel ); + mBounds = mChildren[0]->getBounds(); + mMaterials = mChildren[0]->getMaterials(); + + mChildren[1] = new TerrCell; + mChildren[1]->_init( mTerrain, + Point2I( mPoint.x + childSize, mPoint.y ), + childSize, + childLevel ); + mBounds.intersect( mChildren[1]->getBounds() ); + mMaterials |= mChildren[1]->getMaterials(); + + mChildren[2] = new TerrCell; + mChildren[2]->_init( mTerrain, + Point2I( mPoint.x, mPoint.y + childSize ), + childSize, + childLevel ); + mBounds.intersect( mChildren[2]->getBounds() ); + mMaterials |= mChildren[2]->getMaterials(); + + mChildren[3] = new TerrCell; + mChildren[3]->_init( mTerrain, + Point2I( mPoint.x + childSize, mPoint.y + childSize ), + childSize, + childLevel ); + mBounds.intersect( mChildren[3]->getBounds() ); + mMaterials |= mChildren[3]->getMaterials(); + + mRadius = mBounds.len() * 0.5f; +} + +void TerrCell::updateGrid( const RectI &gridRect, bool opacityOnly ) +{ + PROFILE_SCOPE( TerrCell_UpdateGrid ); + + // If we have a VB... then update it. + if ( mVertexBuffer.isValid() && !opacityOnly ) + _updateVertexBuffer(); + + // If we don't have children... then we're + // a leaf at the bottom of the cell quadtree + // and we should just update our bounds. + if ( !mChildren[0] ) + { + if ( !opacityOnly ) + _updateBounds(); + + _updateMaterials(); + return; + } + + // Otherwise, we must call updateGrid on our children + // and then update our bounds/materials AFTER to contain them. + + mMaterials = 0; + + for ( U32 i = 0; i < 4; i++ ) + { + TerrCell *cell = mChildren[i]; + + // The overlap test doesn't hit shared edges + // so grow it a bit when we create it. + const RectI cellRect( cell->mPoint.x - 1, + cell->mPoint.y - 1, + cell->mSize + 2, + cell->mSize + 2 ); + + // We do an overlap and containment test as it + // properly handles zero sized rects. + if ( cellRect.contains( gridRect ) || + cellRect.overlaps( gridRect ) ) + cell->updateGrid( gridRect, opacityOnly ); + + // Update the bounds from our children. + if ( !opacityOnly ) + { + if ( i == 0 ) + mBounds = mChildren[i]->getBounds(); + else + mBounds.intersect( mChildren[i]->getBounds() ); + + mRadius = mBounds.len() * 0.5f; + } + + // Update the material flags. + mMaterials |= mChildren[i]->getMaterials(); + } + + if ( mMaterial ) + mMaterial->init( mTerrain, mMaterials ); +} + +void TerrCell::_updateVertexBuffer() +{ + PROFILE_SCOPE( TerrCell_UpdateVertexBuffer ); + + mVertexBuffer.set( GFX, smVBSize, GFXBufferTypeStatic ); + + const F32 squareSize = mTerrain->getSquareSize(); + const U32 blockSize = mTerrain->getBlockSize(); + const U32 stepSize = mSize / smMinCellSize; + + U32 vbcounter = 0; + + TerrVertex *vert = mVertexBuffer.lock(); + + Point2I gridPt; + Point2F point; + F32 height; + Point3F normal; + + const TerrainFile *file = mTerrain->getFile(); + + for ( U32 y = 0; y < smVBStride; y++ ) + { + for ( U32 x = 0; x < smVBStride; x++ ) + { + // We clamp here to keep the geometry from reading across + // one side of the height map to the other causing walls + // around the edges of the terrain. + gridPt.x = mClamp( mPoint.x + x * stepSize, 0, blockSize - 1 ); + gridPt.y = mClamp( mPoint.y + y * stepSize, 0, blockSize - 1 ); + + // Setup this point. + point.x = (F32)gridPt.x * squareSize; + point.y = (F32)gridPt.y * squareSize; + height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) ); + vert->point.x = point.x; + vert->point.y = point.y; + vert->point.z = height; + + // Get the normal. + mTerrain->getNormal( point, &normal, true, false ); + vert->normal = normal; + + // Get the tangent z. + vert->tangentZ = fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) ) - height; + + // Set the empty state for this vert. + vert->empty = file->isEmptyAt( gridPt.x, gridPt.y ) ? -1.0f : 1.0f; + + vbcounter++; + ++vert; + } + } + + // Add verts for 'skirts' around/beneath the edge verts of this cell. + // This could probably be reduced to a loop... + + const F32 skirtDepth = mSize / smMinCellSize * mTerrain->getSquareSize(); + + // Top edge skirt + for ( U32 i = 0; i < smVBStride; i++ ) + { + gridPt.x = mClamp( mPoint.x + i * stepSize, 0, blockSize - 1 ); + gridPt.y = mClamp( mPoint.y, 0, blockSize - 1 ); + + point.x = (F32)gridPt.x * squareSize; + point.y = (F32)gridPt.y * squareSize; + height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) ); + vert->point.x = point.x; + vert->point.y = point.y; + vert->point.z = height - skirtDepth; + + // Get the normal. + mTerrain->getNormal( point, &normal, true, false ); + vert->normal = normal; + + // Get the tangent. + vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) ); + + // Set the empty state for this vert. + vert->empty = file->isEmptyAt( gridPt.x, gridPt.y ) ? -1.0f : 1.0f; + + vbcounter++; + ++vert; + } + + // Bottom edge skirt + for ( U32 i = 0; i < smVBStride; i++ ) + { + gridPt.x = mClamp( mPoint.x + i * stepSize, 0, blockSize - 1 ); + gridPt.y = mClamp( mPoint.y + smMinCellSize * stepSize, 0, blockSize - 1 ); + + point.x = (F32)gridPt.x * squareSize; + point.y = (F32)gridPt.y * squareSize; + height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) ); + vert->point.x = point.x; + vert->point.y = point.y; + vert->point.z = height - skirtDepth; + + // Get the normal. + mTerrain->getNormal( point, &normal, true, false ); + vert->normal = normal; + + // Get the tangent. + vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) ); + + // Set the empty state for this vert. + vert->empty = file->isEmptyAt( gridPt.x, gridPt.y ) ? -1.0f : 1.0f; + + vbcounter++; + ++vert; + } + + // Left edge skirt + for ( U32 i = 0; i < smVBStride; i++ ) + { + gridPt.x = mClamp( mPoint.x, 0, blockSize - 1 ); + gridPt.y = mClamp( mPoint.y + i * stepSize, 0, blockSize - 1 ); + + point.x = (F32)gridPt.x * squareSize; + point.y = (F32)gridPt.y * squareSize; + height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) ); + vert->point.x = point.x; + vert->point.y = point.y; + vert->point.z = height - skirtDepth; + + // Get the normal. + mTerrain->getNormal( point, &normal, true, false ); + vert->normal = normal; + + // Get the tangent. + vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) ); + + // Set the empty state for this vert. + vert->empty = file->isEmptyAt( gridPt.x, gridPt.y ) ? -1.0f : 1.0f; + + vbcounter++; + ++vert; + } + + // Right edge skirt + for ( U32 i = 0; i < smVBStride; i++ ) + { + gridPt.x = mClamp( mPoint.x + smMinCellSize * stepSize, 0, blockSize - 1 ); + gridPt.y = mClamp( mPoint.y + i * stepSize, 0, blockSize - 1 ); + + point.x = (F32)gridPt.x * squareSize; + point.y = (F32)gridPt.y * squareSize; + height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) ); + vert->point.x = point.x; + vert->point.y = point.y; + vert->point.z = height - skirtDepth; + + // Get the normal. + mTerrain->getNormal( point, &normal, true, false ); + vert->normal = normal; + + // Get the tangent. + vert->tangentZ = height - fixedToFloat( file->getHeight( gridPt.x + 1, gridPt.y ) ); + + // Set the empty state for this vert. + vert->empty = file->isEmptyAt( gridPt.x, gridPt.y ) ? -1.0f : 1.0f; + + vbcounter++; + ++vert; + } + + AssertFatal( vbcounter == smVBSize, "bad" ); + mVertexBuffer.unlock(); +} + +void TerrCell::_updateMaterials() +{ + PROFILE_SCOPE( TerrCell_UpdateMaterials ); + + // This should really only be called for cells of smMinCellSize, + // in which case stepSize is always one. + U32 stepSize = mSize / smMinCellSize; + mMaterials = 0; + U8 index; + U32 x, y; + + const TerrainFile *file = mTerrain->getFile(); + + // Step thru the samples in the map then. + for ( y = 0; y < smVBStride; y++ ) + { + for ( x = 0; x < smVBStride; x++ ) + { + index = file->getLayerIndex( mPoint.x + x * stepSize, + mPoint.y + y * stepSize ); + + // Skip empty layers and anything that doesn't fit + // the 64bit material flags. + if ( index == U8_MAX || index > 63 ) + continue; + + mMaterials |= (U64)(1<init( mTerrain, mMaterials ); +} + +void TerrCell::_updateBounds() +{ + PROFILE_SCOPE( TerrCell_UpdateBounds ); + + const F32 squareSize = mTerrain->getSquareSize(); + + // This should really only be called for cells of smMinCellSize, + // in which case stepSize is always one. + const U32 stepSize = mSize / smMinCellSize; + + // Prepare to expand the bounds. + mBounds.minExtents.set( F32_MAX, F32_MAX, F32_MAX ); + mBounds.maxExtents.set( -F32_MAX, -F32_MAX, -F32_MAX ); + + Point3F vert; + Point2F texCoord; + + const TerrainFile *file = mTerrain->getFile(); + + for ( U32 y = 0; y < smVBStride; y++ ) + { + for ( U32 x = 0; x < smVBStride; x++ ) + { + // Setup this point. + vert.x = (F32)( mPoint.x + x * stepSize ) * squareSize; + vert.y = (F32)( mPoint.y + y * stepSize ) * squareSize; + vert.z = fixedToFloat( file->getHeight( mPoint.x + x, + mPoint.y + y ) ); + + // HACK: Call it twice to deal with the inverted + // inital bounds state... shouldn't be a perf issue. + mBounds.extend( vert ); + mBounds.extend( vert ); + } + } + + mRadius = mBounds.len() * 0.5; +} + +void TerrCell::cullCells( const Frustum *culler, + const SceneState *state, + const Point3F &objLodPos, + Vector *outCells ) +{ + // If we have a VB and no children then just add + // ourselves to the results and return. + if ( mVertexBuffer.isValid() && !mChildren[0] ) + { + outCells->push_back( this ); + return; + } + + const F32 screenError = mTerrain->getScreenError(); + + for ( U32 i = 0; i < 4; i++ ) + { + TerrCell *cell = mChildren[i]; + + // Test if visible. + if ( !culler->intersects( cell->getBounds() ) ) + continue; + + // Lod based on screen error... + // If far enough, just add this child cells vb ( skipping its children ). + F32 dist = cell->getDistanceTo( objLodPos ); + F32 errorMeters = ( cell->mSize / smMinCellSize ) * mTerrain->getSquareSize(); + U32 errorPixels = mCeil( state->projectRadius( dist, errorMeters ) ); + + if ( errorPixels < screenError ) + { + if ( cell->mVertexBuffer.isValid() ) + outCells->push_back( cell ); + } + else + cell->cullCells( culler, state, objLodPos, outCells ); + } +} + +void TerrCell::getRenderPrimitive( GFXPrimitive *prim, + GFXVertexBufferHandleBase *vertBuff ) const +{ + *vertBuff = mVertexBuffer; + + prim->type = GFXTriangleList; + prim->startVertex = 0; + prim->minIndex = 0; + prim->startIndex = 0; + prim->numPrimitives = smTriCount; + prim->numVertices = smVBSize; +} + +void TerrCell::renderBounds() const +{ + ColorI color; + color.interpolate( ColorI::RED, ColorI::GREEN, (F32)mLevel / 3.0f ); + + GFXStateBlockDesc desc; + desc.setZReadWrite( true, false ); + desc.fillMode = GFXFillWireframe; + + GFX->getDrawUtil()->drawCube( desc, mBounds, color ); +} + +TerrainCellMaterial* TerrCell::getMaterial() +{ + if ( !mMaterial ) + { + mMaterial = new TerrainCellMaterial; + mMaterial->init( mTerrain, mMaterials ); + } + + return mMaterial; +} + +void TerrCell::deleteMaterials() +{ + SAFE_DELETE( mMaterial ); + + for ( U32 i = 0; i < 4; i++ ) + if ( mChildren[i] ) + mChildren[i]->deleteMaterials(); +} diff --git a/terrain/terrCell.h b/terrain/terrCell.h new file mode 100644 index 0000000..3ea41e7 --- /dev/null +++ b/terrain/terrCell.h @@ -0,0 +1,162 @@ +//----------------------------------------------------------------------------- +// Torque Shader Engine +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRCELL_H_ +#define _TERRCELL_H_ + +#ifndef _GFXVERTEXBUFFER_H_ + #include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _TDICTIONARY_H_ + #include "core/util/tDictionary.h" +#endif + +class TerrainBlock; +class TerrainCellMaterial; +class Frustum; +class SceneState; +class GFXPrimitiveBufferHandle; + + +/// The TerrainCell vertex format optimized to +/// 32 bytes for optimal vertex cache performance. +GFXDeclareVertexFormat( TerrVertex ) +{ + /// The position. + Point3F point; + + /// The normal. + Point3F normal; + + /// The height for calculating the + /// tangent vector on the GPU. + F32 tangentZ; + + /// The empty flag state which is either + /// -1 or 1 so we can do the special + /// interpolation trick. + F32 empty; +}; + + +/// The TerrCell is a single quadrant of the terrain geometry quadtree. +class TerrCell +{ +protected: + + /// The handle to the static vertex buffer which holds the + /// vertices for this cell. + GFXVertexBufferHandle mVertexBuffer; + + /// + Point2I mPoint; + + /// + U32 mSize; + + /// The level of this cell within the quadtree (of cells) where + /// zero is the root and one is a direct child of the root, etc. + U32 mLevel; + + /// Statics used in VB and PB generation. + static const U32 smVBStride; + static const U32 smMinCellSize; + static const U32 smVBSize; + static const U32 smPBSize; + static const U32 smTriCount; + + /// The terrain this cell is based on. + TerrainBlock *mTerrain; + + /// The material used to render the cell. + TerrainCellMaterial *mMaterial; + + /// The bounding box of this cell in + /// TerrainBlock object space. + Box3F mBounds; + + /// The bounding radius of this cell. + F32 mRadius; + + /// The child cells of this one. + TerrCell *mChildren[4]; + + /// This bit flag tells us which materials effect + /// this cell and is used for optimizing rendering. + /// @see TerrainFile::mMaterialAlphaMap + U64 mMaterials; + + /// + void _updateBounds(); + + // + void _init( TerrainBlock *terrain, + const Point2I &point, + U32 size, + U32 level ); + + // + void _updateVertexBuffer(); + + // + void _updateMaterials(); + +public: + + TerrCell(); + virtual ~TerrCell(); + + static TerrCell* init( TerrainBlock *terrain ); + + void getRenderPrimitive( GFXPrimitive *prim, + GFXVertexBufferHandleBase *vertBuff ) const; + + void updateGrid( const RectI &gridRect, bool opacityOnly = false ); + + void cullCells( const Frustum *culler, + const SceneState *state, + const Point3F &objLodPos, + Vector *outCells ); + + const Box3F& getBounds() const { return mBounds; } + + /// Returns the object space sphere bounds. + SphereF getSphereBounds() const { return SphereF( mBounds.getCenter(), mRadius ); } + + F32 getSqDistanceTo( const Point3F &pt ) const; + + F32 getDistanceTo( const Point3F &pt ) const; + + U64 getMaterials() const { return mMaterials; } + + TerrainCellMaterial* getMaterial(); + + /// Deletes the materials for this cell + /// and all its children. They will be + /// recreate on the next request. + void deleteMaterials(); + + U32 getSize() const { return mSize; } + + Point2I getPoint() const { return mPoint; } + + /// Initializes a primitive buffer for rendering any cell. + static void createPrimBuffer( GFXPrimitiveBufferHandle *primBuffer ); + + /// Debug Rendering + /// @{ + + /// Renders the debug bounds for this cell. + void renderBounds() const; + + /// @} +}; + +inline F32 TerrCell::getDistanceTo( const Point3F &pt ) const +{ + return ( mBounds.getCenter() - pt ).len() - mRadius; +} + +#endif // _TERRCELL_H_ diff --git a/terrain/terrCellMaterial.cpp b/terrain/terrCellMaterial.cpp new file mode 100644 index 0000000..c75c741 --- /dev/null +++ b/terrain/terrCellMaterial.cpp @@ -0,0 +1,578 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrCellMaterial.h" + +#include "terrain/terrData.h" +#include "terrain/terrCell.h" +#include "materials/materialFeatureTypes.h" +#include "materials/materialManager.h" +#include "terrain/terrFeatureTypes.h" +#include "terrain/terrMaterial.h" +#include "renderInstance/renderPrePassMgr.h" +#include "shaderGen/shaderGen.h" +#include "shaderGen/featureMgr.h" +#include "sceneGraph/sceneState.h" +#include "materials/sceneData.h" +#include "gfx/util/screenspace.h" + +TerrainCellMaterial::TerrainCellMaterial() + : mCurrPass( 0 ), + mTerrain( NULL ), + mPrePassMat( NULL ) +{ +} + +TerrainCellMaterial::~TerrainCellMaterial() +{ + SAFE_DELETE( mPrePassMat ); +} + +void TerrainCellMaterial::setTransformAndEye( const MatrixF &modelXfm, + const MatrixF &viewXfm, + const MatrixF &projectXfm, + F32 farPlane ) +{ + PROFILE_SCOPE( TerrainCellMaterial_SetTransformAndEye ); + + MatrixF modelViewProj = projectXfm * viewXfm * modelXfm; + + MatrixF invViewXfm( viewXfm ); + invViewXfm.inverse(); + Point3F eyePos = invViewXfm.getPosition(); + + MatrixF invModelXfm( modelXfm ); + invModelXfm.inverse(); + + Point3F objEyePos = eyePos; + invModelXfm.mulP( objEyePos ); + + VectorF vEye = invViewXfm.getForwardVector(); + vEye.normalize( 1.0f / farPlane ); + + for ( U32 i=0; i < mPasses.size(); i++ ) + { + Pass &pass = mPasses[i]; + + pass.consts->set( pass.modelViewProjConst, modelViewProj ); + + if( pass.viewToObj->isValid() || pass.worldViewOnly->isValid() ) + { + MatrixF worldViewOnly = viewXfm * modelXfm; + + if( pass.worldViewOnly->isValid() ) + pass.consts->set( pass.worldViewOnly, worldViewOnly ); + + if( pass.viewToObj->isValid() ) + { + worldViewOnly.affineInverse(); + pass.consts->set( pass.viewToObj, worldViewOnly); + } + } + + pass.consts->set( pass.eyePosWorldConst, eyePos ); + pass.consts->set( pass.eyePosConst, objEyePos ); + + pass.consts->set( pass.objTransConst, modelXfm ); + pass.consts->set( pass.worldToObjConst, invModelXfm ); + + pass.consts->set( pass.vEyeConst, vEye ); + } +} + +TerrainCellMaterial* TerrainCellMaterial::getPrePass() +{ + if ( !mPrePassMat ) + { + mPrePassMat = new TerrainCellMaterial(); + mPrePassMat->init( mTerrain, mMaterials, true, mMaterials == 0 ); + } + + return mPrePassMat; +} + +void TerrainCellMaterial::init( TerrainBlock *block, + U64 activeMaterials, + bool prePass, + bool baseOnly ) +{ + mTerrain = block; + mMaterials = activeMaterials; + + Vector materials; + + for ( U32 i = 0; i < 64; i++ ) + { + if ( !( mMaterials & ((U64)1 << i ) ) ) + continue; + + TerrainMaterial *mat = block->getMaterial( i ); + + MaterialInfo *info = new MaterialInfo(); + info->layerId = i; + info->mat = mat; + materials.push_back( info ); + } + + mCurrPass = 0; + mPasses.clear(); + + // Ok... loop till we successfully generate all + // the shader passes for the materials. + while ( materials.size() > 0 || baseOnly ) + { + mPasses.increment(); + + if ( !_createPass( &materials, + &mPasses.last(), + mPasses.size() == 1, + prePass, + baseOnly ) ) + { + Con::errorf( "TerrainCellMaterial::init - Failed to create pass!" ); + + // The pass failed to be generated... give up. + mPasses.last().materials.clear(); + mPasses.clear(); + for_each( materials.begin(), materials.end(), delete_pointer() ); + return; + } + + if ( baseOnly ) + break; + } + + // If we have a prepass then update it too. + if ( mPrePassMat ) + mPrePassMat->init( mTerrain, mMaterials, true, baseOnly ); +} + +bool TerrainCellMaterial::_createPass( Vector *materials, + Pass *pass, + bool firstPass, + bool prePass, + bool baseOnly ) +{ + U32 matCount = materials->size(); + + Vector normalMaps; + + // See if we're currently running under the + // basic lighting manager. + // + // TODO: This seems ugly... we should trigger + // features like this differently in the future. + // + bool useBLM = dStrcmp( gClientSceneGraph->getLightManager()->getId(), "BLM" ) == 0; + + // Loop till we create a valid shader! + while( true ) + { + FeatureSet features; + features.addFeature( MFT_VertTransform ); + features.addFeature( MFT_TerrainBaseMap ); + features.addFeature( MFT_TerrainEmpty ); + + if ( prePass ) + { + features.addFeature( MFT_EyeSpaceDepthOut ); + features.addFeature( MFT_PrePassConditioner ); + } + else + features.addFeature( MFT_RTLighting ); + + normalMaps.clear(); + pass->materials.clear(); + + for ( U32 i=0; i < matCount && !baseOnly; i++ ) + { + TerrainMaterial *mat = (*materials)[i]->mat; + + // We only include materials that + // have more than a base texture. + if ( mat->getDetailSize() <= 0 || + mat->getDetailDistance() <= 0 || + mat->getDetailMap().isEmpty() ) + continue; + + features.addFeature( MFT_TerrainDetailMap, i ); + + pass->materials.push_back( (*materials)[i] ); + normalMaps.increment(); + + // Skip normal maps under basic lighting! + if ( !useBLM && mat->getNormalMap().isNotEmpty() ) + { + features.addFeature( MFT_TerrainNormalMap, i ); + + normalMaps.last().set( mat->getNormalMap(), + &GFXDefaultStaticNormalMapProfile, "TerrainCellMaterial::_createPass() - NormalMap" ); + + if ( normalMaps.last().getFormat() == GFXFormatDXT5 ) + features.addFeature( MFT_IsDXTnm, i ); + + // Only allow parallax on 2.0 and above and when + // side projection is disabled. + if ( mat->getParallaxScale() > 0.0f && + GFX->getPixelShaderVersion() >= 2.0f && + !mat->useSideProjection() ) + features.addFeature( MFT_TerrainParallaxMap, i ); + } + + // Is this layer got side projection? + if ( mat->useSideProjection() ) + features.addFeature( MFT_TerrainSideProject, i ); + } + + // Enable lightmaps and fogging if we're in BL. + if ( useBLM ) + { + features.addFeature( MFT_TerrainLightMap ); + features.addFeature( MFT_Fog ); + } + + // The additional passes need to be lerp blended into the + // target to maintain the results of the previous passes. + if ( !firstPass ) + features.addFeature( MFT_TerrainAdditive ); + + MaterialFeatureData featureData; + featureData.features = features; + featureData.materialFeatures = features; + + // Check to see how many vertex shader output + // registers we're gonna need. + U32 numTex = 0; + U32 numTexReg = 0; + for ( U32 i=0; i < features.getCount(); i++ ) + { + S32 index; + const FeatureType &type = features.getAt( i, &index ); + ShaderFeature* sf = FEATUREMGR->getByType( type ); + if ( !sf ) + continue; + + sf->setProcessIndex( index ); + ShaderFeature::Resources res = sf->getResources( featureData ); + + numTex += res.numTex; + numTexReg += res.numTexReg; + } + + // Can we build the shader? + // + // NOTE: The 10 is sort of an abitrary SM 3.0 + // limit. Its really supposed to be 11, but that + // always fails to compile so far. + // + if ( numTex < GFX->getNumSamplers() && + numTexReg <= 10 ) + { + // NOTE: We really shouldn't be getting errors building the shaders, + // but we can generate more instructions than the ps_2_x will allow. + // + // There is no way to deal with this case that i know of other than + // letting the compile fail then recovering by trying to build it + // with fewer materials. + // + // We normally disabe the shader error logging so that the user + // isn't fooled into thinking there is a real bug. That is until + // we get down to a single material. If a single material case + // fails it means it cannot generate any passes at all! + const bool logErrors = matCount == 1; + GFXShader::setLogging( logErrors, true ); + + pass->shader = SHADERGEN->getShader( featureData, getGFXVertexFormat(), NULL ); + } + + // If we got a shader then we can continue. + if ( pass->shader ) + break; + + // If the material count is already 1 then this + // is a real shader error... give up! + if ( matCount <= 1 ) + return false; + + // If we failed we next try half the input materials + // so that we can more quickly arrive at a valid shader. + matCount -= matCount / 2; + } + + // Setup the constant buffer. + pass->consts = pass->shader->allocConstBuffer(); + + // Prepare the basic constants. + pass->modelViewProjConst = pass->shader->getShaderConstHandle( "$modelview" ); + pass->worldViewOnly = pass->shader->getShaderConstHandle( "$worldViewOnly" ); + pass->viewToObj = pass->shader->getShaderConstHandle( "$viewToObj" ); + pass->eyePosWorldConst = pass->shader->getShaderConstHandle( "$eyePosWorld" ); + pass->eyePosConst = pass->shader->getShaderConstHandle( "$eyePos" ); + pass->vEyeConst = pass->shader->getShaderConstHandle( "$vEye" ); + pass->layerSizeConst = pass->shader->getShaderConstHandle( "$layerSize" ); + pass->objTransConst = pass->shader->getShaderConstHandle( "$objTrans" ); + pass->worldToObjConst = pass->shader->getShaderConstHandle( "$worldToObj" ); + pass->lightInfoBufferConst = pass->shader->getShaderConstHandle( "$lightInfoBuffer" ); + pass->baseTexMapConst = pass->shader->getShaderConstHandle( "$baseTexMap" ); + pass->layerTexConst = pass->shader->getShaderConstHandle( "$layerTex" ); + pass->fogDataConst = pass->shader->getShaderConstHandle( "$fogData" ); + pass->fogColorConst = pass->shader->getShaderConstHandle( "$fogColor" ); + pass->lightMapTexConst = pass->shader->getShaderConstHandle( "$lightMapTex" ); + pass->oneOverTerrainSize = pass->shader->getShaderConstHandle( "$oneOverTerrainSize" ); + pass->squareSize = pass->shader->getShaderConstHandle( "$squareSize" ); + + // NOTE: We're assuming rtParams0 here as we know its the only + // render target we currently get in a terrain material and the + // DeferredRTLightingFeatHLSL will always use 0. + // + // This could change in the future and we would need to fix + // the ShaderFeature API to allow us to do this right. + // + pass->lightParamsConst = pass->shader->getShaderConstHandle( "$rtParams0" ); + + // Now prepare the basic stateblock. + GFXStateBlockDesc desc; + if ( !firstPass ) + { + desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha ); + + // If this is the prepass then we don't want to + // write to the last two color channels (where + // depth is usually encoded). + // + // This trick works in combination with the + // MFT_TerrainAdditive feature to lerp the + // output normal with the previous pass. + // + if ( prePass ) + desc.setColorWrites( true, true, false, false ); + } + + // We write to the zbuffer if this is a prepass + // material or if the prepass is disabled. + // We also write the zbuffer if we're using OpenGL, because in OpenGL the prepass + // cannot share the same zbuffer as the backbuffer. + desc.setZReadWrite( true, !MATMGR->getPrePassEnabled() || + GFX->getAdapterType() == OpenGL || + prePass ); + + desc.samplersDefined = true; + if ( pass->baseTexMapConst->isValid() ) + desc.samplers[pass->baseTexMapConst->getSamplerRegister()] = GFXSamplerStateDesc::getWrapLinear(); + + if ( pass->layerTexConst->isValid() ) + desc.samplers[pass->layerTexConst->getSamplerRegister()] = GFXSamplerStateDesc::getClampPoint(); + + if ( pass->lightInfoBufferConst->isValid() ) + desc.samplers[pass->lightInfoBufferConst->getSamplerRegister()] = GFXSamplerStateDesc::getClampPoint(); + + if ( pass->lightMapTexConst->isValid() ) + desc.samplers[pass->lightMapTexConst->getSamplerRegister()] = GFXSamplerStateDesc::getWrapLinear(); + + // Finally setup the material specific shader + // constants and stateblock state. + for ( U32 i=0; i < pass->materials.size(); i++ ) + { + MaterialInfo *matInfo = pass->materials[i]; + + matInfo->detailInfoVConst = pass->shader->getShaderConstHandle( avar( "$detailScaleAndFade%d", i ) ); + matInfo->detailInfoPConst = pass->shader->getShaderConstHandle( avar( "$detailIdStrengthParallax%d", i ) ); + + matInfo->detailTexConst = pass->shader->getShaderConstHandle( avar( "$detailMap%d", i ) ); + if ( matInfo->detailTexConst->isValid() ) + { + const S32 sampler = matInfo->detailTexConst->getSamplerRegister(); + + desc.samplers[sampler] = GFXSamplerStateDesc::getWrapLinear(); + desc.samplers[sampler].magFilter = GFXTextureFilterLinear; + desc.samplers[sampler].mipFilter = GFXTextureFilterLinear; + desc.samplers[sampler].minFilter = GFXTextureFilterAnisotropic; + desc.samplers[sampler].maxAnisotropy = 4; + + matInfo->detailTex.set( matInfo->mat->getDetailMap(), + &GFXDefaultStaticDiffuseProfile, "TerrainCellMaterial::_createPass() - DetailMap" ); + } + + matInfo->normalTexConst = pass->shader->getShaderConstHandle( avar( "$normalMap%d", i ) ); + if ( matInfo->normalTexConst->isValid() ) + { + const S32 sampler = matInfo->normalTexConst->getSamplerRegister(); + + desc.samplers[sampler] = GFXSamplerStateDesc::getWrapLinear(); + desc.samplers[sampler].magFilter = GFXTextureFilterLinear; + desc.samplers[sampler].mipFilter = GFXTextureFilterLinear; + desc.samplers[sampler].minFilter = GFXTextureFilterAnisotropic; + desc.samplers[sampler].maxAnisotropy = 4; + + matInfo->normalTex = normalMaps[i]; + } + } + + // Remove the materials we processed and leave the + // ones that remain for the next pass. + for ( U32 i=0; i < matCount; i++ ) + { + MaterialInfo *matInfo = materials->first(); + if ( baseOnly || pass->materials.find_next( matInfo ) == -1 ) + delete matInfo; + materials->pop_front(); + } + + // If we're doing prepass it requires some + // special stencil settings for it to work. + if ( prePass ) + desc.addDesc( RenderPrePassMgr::getOpaqueStenciWriteDesc( false ) ); + + pass->stateBlock = GFX->createStateBlock( desc ); + + GFXStateBlockDesc wireframe( desc ); + wireframe.fillMode = GFXFillWireframe; + pass->wireframeStateBlock = GFX->createStateBlock( wireframe ); + + desc.setCullMode( GFXCullCW ); + pass->reflectStateBlock = GFX->createStateBlock( desc ); + + return true; +} + +void TerrainCellMaterial::_updateMaterialConsts( Pass *pass ) +{ + PROFILE_SCOPE( TerrainCellMaterial_UpdateMaterialConsts ); + + for ( U32 j=0; j < pass->materials.size(); j++ ) + { + MaterialInfo *matInfo = pass->materials[j]; + + F32 detailSize = matInfo->mat->getDetailSize(); + F32 detailScale = 1.0f; + if ( !mIsZero( detailSize ) ) + detailScale = mTerrain->getWorldBlockSize() / detailSize; + + // Scale the distance by the global scalar. + const F32 distance = mTerrain->smDetailScale * matInfo->mat->getDetailDistance(); + + // NOTE: The negation of the y scale is to make up for + // my mistake early in development and passing the wrong + // y texture coord into the system. + // + // This negation fixes detail, normal, and parallax mapping + // without harming the layer id blending code. + // + // Eventually we should rework this to correct this little + // mistake, but there isn't really a hurry to. + // + Point4F detailScaleAndFade( detailScale, + -detailScale, + distance, + 0 ); + + if ( !mIsZero( distance ) ) + detailScaleAndFade.w = 1.0f / distance; + + Point3F detailIdStrengthParallax( matInfo->layerId, + matInfo->mat->getDetailStrength(), + matInfo->mat->getParallaxScale() ); + + pass->consts->set( matInfo->detailInfoVConst, detailScaleAndFade ); + pass->consts->set( matInfo->detailInfoPConst, detailIdStrengthParallax ); + } +} + +bool TerrainCellMaterial::setupPass( const SceneState *state, + const SceneGraphData &sceneData ) +{ + PROFILE_SCOPE( TerrainCellMaterial_SetupPass ); + + if ( mCurrPass >= mPasses.size() ) + { + mCurrPass = 0; + return false; + } + + Pass &pass = mPasses[mCurrPass]; + + _updateMaterialConsts( &pass ); + + if ( pass.baseTexMapConst->isValid() ) + GFX->setTexture( pass.baseTexMapConst->getSamplerRegister(), mTerrain->mBaseTex.getPointer() ); + + if ( pass.layerTexConst->isValid() ) + GFX->setTexture( pass.layerTexConst->getSamplerRegister(), mTerrain->mLayerTex.getPointer() ); + + if ( pass.lightMapTexConst->isValid() ) + GFX->setTexture( pass.lightMapTexConst->getSamplerRegister(), mTerrain->getLightMapTex() ); + + if ( sceneData.wireframe ) + GFX->setStateBlock( pass.wireframeStateBlock ); + else if ( state->isReflectPass() ) + GFX->setStateBlock( pass.reflectStateBlock ); + else + GFX->setStateBlock( pass.stateBlock ); + + GFX->setShader( pass.shader ); + GFX->setShaderConstBuffer( pass.consts ); + + // Let the light manager prepare any light stuff it needs. + state->getLightManager()->setLightInfo( NULL, + NULL, + sceneData, + state, + mCurrPass, + pass.consts ); + + for ( U32 i=0; i < pass.materials.size(); i++ ) + { + MaterialInfo *matInfo = pass.materials[i]; + + if ( matInfo->detailTexConst->isValid() ) + GFX->setTexture( matInfo->detailTexConst->getSamplerRegister(), matInfo->detailTex ); + if ( matInfo->normalTexConst->isValid() ) + GFX->setTexture( matInfo->normalTexConst->getSamplerRegister(), matInfo->normalTex ); + } + + pass.consts->set( pass.layerSizeConst, (F32)mTerrain->mLayerTex.getWidth() ); + + if ( pass.oneOverTerrainSize->isValid() ) + { + F32 oneOverTerrainSize = 1.0f / mTerrain->getWorldBlockSize(); + pass.consts->set( pass.oneOverTerrainSize, oneOverTerrainSize ); + } + + if ( pass.squareSize->isValid() ) + pass.consts->set( pass.squareSize, mTerrain->getSquareSize() ); + + if ( pass.fogDataConst->isValid() ) + { + Point3F fogData; + fogData.x = sceneData.fogDensity; + fogData.y = sceneData.fogDensityOffset; + fogData.z = sceneData.fogHeightFalloff; + pass.consts->set( pass.fogDataConst, fogData ); + } + + pass.consts->set( pass.fogColorConst, sceneData.fogColor ); + + if ( pass.lightInfoBufferConst->isValid() && + pass.lightParamsConst->isValid() ) + { + if ( !mLightInfoTarget ) + mLightInfoTarget = MatTextureTarget::findTargetByName( "lightinfo" ); + + GFXTextureObject *texObject = mLightInfoTarget->getTargetTexture( 0 ); + GFX->setTexture( pass.lightInfoBufferConst->getSamplerRegister(), texObject ); + + const Point3I &targetSz = texObject->getSize(); + const RectI &targetVp = mLightInfoTarget->getTargetViewport(); + Point4F rtParams; + + ScreenSpace::RenderTargetParameters(targetSz, targetVp, rtParams); + + pass.consts->set( pass.lightParamsConst, rtParams ); + } + + ++mCurrPass; + return true; +} diff --git a/terrain/terrCellMaterial.h b/terrain/terrCellMaterial.h new file mode 100644 index 0000000..267aa7b --- /dev/null +++ b/terrain/terrCellMaterial.h @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRCELLMATERIAL_H_ +#define _TERRCELLMATERIAL_H_ + +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _MATTEXTURETARGET_H_ +#include "materials/matTextureTarget.h" +#endif +#ifndef _GFXTEXTUREHANDLE_H_ +#include "gfx/gfxTextureHandle.h" +#endif +#ifndef _GFXSHADER_H_ +#include "gfx/gfxShader.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif + + +class SceneState; +struct SceneGraphData; +class TerrainMaterial; +class TerrainBlock; + + +/// This is a complex material which holds one or more +/// optimized shaders for rendering a single cell. +class TerrainCellMaterial +{ +protected: + + class MaterialInfo + { + public: + + MaterialInfo() + { + } + + ~MaterialInfo() + { + } + + TerrainMaterial *mat; + U32 layerId; + + GFXShaderConstHandle *detailTexConst; + GFXTexHandle detailTex; + + GFXShaderConstHandle *normalTexConst; + GFXTexHandle normalTex; + + GFXShaderConstHandle *detailInfoVConst; + GFXShaderConstHandle *detailInfoPConst; + }; + + class Pass + { + public: + + Pass() + : shader( NULL ) + { + } + + ~Pass() + { + for ( U32 i=0; i < materials.size(); i++ ) + delete materials[i]; + } + + Vector materials; + + /// + GFXShader *shader; + + GFXShaderConstBufferRef consts; + + GFXStateBlockRef stateBlock; + GFXStateBlockRef reflectStateBlock; + GFXStateBlockRef wireframeStateBlock; + + GFXShaderConstHandle *modelViewProjConst; + GFXShaderConstHandle *worldViewOnly; + GFXShaderConstHandle *viewToObj; + + GFXShaderConstHandle *eyePosWorldConst; + GFXShaderConstHandle *eyePosConst; + + GFXShaderConstHandle *objTransConst; + GFXShaderConstHandle *worldToObjConst; + GFXShaderConstHandle *vEyeConst; + + GFXShaderConstHandle *layerSizeConst; + GFXShaderConstHandle *lightParamsConst; + GFXShaderConstHandle *lightInfoBufferConst; + + GFXShaderConstHandle *baseTexMapConst; + GFXShaderConstHandle *layerTexConst; + + GFXShaderConstHandle *lightMapTexConst; + + GFXShaderConstHandle *squareSize; + GFXShaderConstHandle *oneOverTerrainSize; + + GFXShaderConstHandle *fogDataConst; + GFXShaderConstHandle *fogColorConst; + }; + + TerrainBlock *mTerrain; + + U64 mMaterials; + + Vector mPasses; + + U32 mCurrPass; + + GFXTexHandle mBaseMapTexture; + + GFXTexHandle mLayerMapTexture; + + MatTextureTargetRef mLightInfoTarget; + + /// The prepass material for this material. + TerrainCellMaterial *mPrePassMat; + + + bool _createPass( Vector *materials, + Pass *pass, + bool firstPass, + bool prePass, + bool baseOnly ); + + void _updateMaterialConsts( Pass *pass ); + +public: + + TerrainCellMaterial(); + ~TerrainCellMaterial(); + + void init( TerrainBlock *block, + U64 activeMaterials, + bool prePass = false, + bool baseOnly = false ); + + /// Returns a prepass material from this material. + TerrainCellMaterial* getPrePass(); + + void setTransformAndEye( const MatrixF &modelXfm, + const MatrixF &viewXfm, + const MatrixF &projectXfm, + F32 farPlane ); + + + /// + bool setupPass( const SceneState *state, + const SceneGraphData &sceneData ); + +}; + +#endif // _TERRCELLMATERIAL_H_ + diff --git a/terrain/terrCollision.cpp b/terrain/terrCollision.cpp new file mode 100644 index 0000000..0712cf9 --- /dev/null +++ b/terrain/terrCollision.cpp @@ -0,0 +1,960 @@ + +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrCollision.h" + +#include "terrain/terrData.h" + +const F32 TerrainThickness = 0.5f; +static const U32 MaxExtent = 256; +#define MAX_FLOAT 1e20f + + +//---------------------------------------------------------------------------- + +Convex sTerrainConvexList; + +// Number of vertices followed by point index +S32 sVertexList[5][5] = { + { 3, 1,2,3 }, // 135 B + { 3, 0,1,3 }, // 135 A + { 3, 0,2,3 }, // 45 B + { 3, 0,1,2 }, // 45 A + { 4, 0,1,2,3 } // Convex square +}; + +// Number of edges followed by edge index pairs +S32 sEdgeList45[16][11] = { + { 0 }, // + { 0 }, + { 0 }, + { 1, 0,1 }, // 0-1 + { 0 }, + { 1, 0,1 }, // 0-2 + { 1, 0,1 }, // 1-2 + { 3, 0,1,1,2,2,0 }, // 0-1,1-2,2-0 + { 0 }, + { 0,}, // + { 0 }, + { 1, 0,1 }, // 0-1, + { 0, }, // + { 1, 0,1 }, // 0-2, + { 1, 0,1 }, // 1-2 + { 3, 0,1,1,2,0,2 }, +}; + +S32 sEdgeList135[16][11] = { + { 0 }, + { 0 }, + { 0 }, + { 1, 0,1 }, // 0-1 + { 0 }, + { 0 }, + { 1, 0,1 }, // 1-2 + { 2, 0,1,1,2 }, // 0-1,1-2 + { 0 }, + { 0, }, // + { 1, 0,1 }, // 1-3 + { 2, 0,1,1,2 }, // 0-1,1-3, + { 0 }, // + { 0 }, // + { 2, 0,1,2,0 }, // 1-2,3-1 + { 3, 0,1,1,2,1,3 }, +}; + +// On split squares, the FaceA diagnal is also removed +S32 sEdgeList45A[16][11] = { + { 0 }, // + { 0 }, + { 0 }, + { 1, 0,1 }, // 0-1 + { 0 }, + { 0 }, // + { 1, 0,1 }, // 1-2 + { 2, 0,1,1,2 }, // 0-1,1-2 + { 0 }, + { 0,}, // + { 0 }, + { 1, 0,1 }, // 0-1 + { 0, }, // + { 0, 0,1 }, // + { 1, 0,1 }, // 1-2 + { 3, 0,1,1,2 }, +}; + +S32 sEdgeList135A[16][11] = { + { 0 }, + { 0 }, + { 0 }, + { 1, 0,1 }, // 0-1 + { 0 }, + { 0 }, + { 1, 0,1 }, // 1-2 + { 2, 0,1,1,2 }, // 0-1,1-2 + { 0 }, + { 0 }, // + { 0 }, // + { 1, 0,1 }, // 0-1 + { 0 }, // + { 0 }, // + { 1, 0,1 }, // 1-2 + { 3, 0,1,1,2 }, +}; + + +// Number of faces followed by normal index and vertices +S32 sFaceList45[16][9] = { + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 1, 0,0,1,2 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 1, 1,0,1,2 }, + { 0 }, + { 2, 0,0,1,2, 1,0,2,3 }, +}; + +S32 sFaceList135[16][9] = { + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 1, 0,0,1,2 }, + { 0 }, + { 0 }, + { 1, 1,0,1,2 }, + { 2, 0,0,1,3, 1,1,2,3 }, +}; + + +TerrainConvex::TerrainConvex() +{ + mType = TerrainConvexType; +} + +TerrainConvex::TerrainConvex( const TerrainConvex &cv ) +{ + mType = TerrainConvexType; + + // Only a partial copy... + mObject = cv.mObject; + split45 = cv.split45; + squareId = cv.squareId; + material = cv.material; + point[0] = cv.point[0]; + point[1] = cv.point[1]; + point[2] = cv.point[2]; + point[3] = cv.point[3]; + normal[0] = cv.normal[0]; + normal[1] = cv.normal[1]; + box = cv.box; +} + +Box3F TerrainConvex::getBoundingBox() const +{ + return box; +} + +Box3F TerrainConvex::getBoundingBox(const MatrixF&, const Point3F& ) const +{ + // Function should not be called.... + return box; +} + +Point3F TerrainConvex::support(const VectorF& v) const +{ + S32 *vp; + if (halfA) + vp = square ? sVertexList[(split45 << 1) | 1]: sVertexList[4]; + else + vp = square ? sVertexList[(split45 << 1)] : sVertexList[4]; + + S32 *ve = vp + vp[0] + 1; + const Point3F *bp = &point[vp[1]]; + F32 bd = mDot(*bp,v); + for (vp += 2; vp < ve; vp++) { + const Point3F* cp = &point[*vp]; + F32 dd = mDot(*cp,v); + if (dd > bd) { + bd = dd; + bp = cp; + } + } + return *bp; +} + +inline bool isOnPlane(Point3F& p,PlaneF& plane) +{ + F32 dist = mDot(plane,p) + plane.d; + return dist < 0.1 && dist > -0.1; +} + +void TerrainConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf) +{ + U32 i; + cf->material = 0; + cf->object = mObject; + + // Plane is normal n + support point + PlaneF plane; + plane.set(support(n),n); + S32 vertexCount = cf->mVertexList.size(); + + // Emit vertices on the plane + S32* vertexListPointer; + if (halfA) + vertexListPointer = square ? sVertexList[(split45 << 1) | 1]: sVertexList[4]; + else + vertexListPointer = square ? sVertexList[(split45 << 1)] : sVertexList[4]; + + S32 pm = 0; + S32 numVerts = *vertexListPointer; + vertexListPointer += 1; + for (i = 0; i < numVerts; i++) + { + Point3F& cp = point[vertexListPointer[i]]; + cf->mVertexList.increment(); + mat.mulP(cp,&cf->mVertexList.last()); + pm |= 1 << vertexListPointer[i]; + } + + // Emit Edges + S32* ep = (square && halfA)? + (split45 ? sEdgeList45A[pm]: sEdgeList135A[pm]): + (split45 ? sEdgeList45[pm]: sEdgeList135[pm]); + + S32 numEdges = *ep; + S32 edgeListStart = cf->mEdgeList.size(); + cf->mEdgeList.increment(numEdges); + ep += 1; + for (i = 0; i < numEdges; i++) + { + cf->mEdgeList[edgeListStart + i].vertex[0] = vertexCount + ep[i * 2 + 0]; + cf->mEdgeList[edgeListStart + i].vertex[1] = vertexCount + ep[i * 2 + 1]; + } + + // Emit faces + S32* fp = split45 ? sFaceList45[pm]: sFaceList135[pm]; + S32 numFaces = *fp; + fp += 1; + S32 faceListStart = cf->mFaceList.size(); + cf->mFaceList.increment(numFaces); + for (i = 0; i < numFaces; i++) + { + cf->mFaceList[faceListStart + i].normal = normal[fp[i * 4 + 0]]; + cf->mFaceList[faceListStart + i].vertex[0] = vertexCount + fp[i * 4 + 1]; + cf->mFaceList[faceListStart + i].vertex[1] = vertexCount + fp[i * 4 + 2]; + cf->mFaceList[faceListStart + i].vertex[2] = vertexCount + fp[i * 4 + 3]; + } +} + + +void TerrainConvex::getPolyList(AbstractPolyList* list) +{ + list->setTransform(&mObject->getTransform(), mObject->getScale()); + list->setObject(mObject); + + // Emit vertices + U32 array[4]; + U32 curr = 0; + + S32 numVerts; + S32* vertsStart; + if (halfA) + { + numVerts = square ? sVertexList[(split45 << 1) | 1][0] : sVertexList[4][0]; + vertsStart = square ? &sVertexList[(split45 << 1) | 1][1] : &sVertexList[4][1]; + } + else + { + numVerts = square ? sVertexList[(split45 << 1)][0] : sVertexList[4][0]; + vertsStart = square ? &sVertexList[(split45 << 1)][1] : &sVertexList[4][1]; + } + + S32 pointMask = 0; + for (U32 i = 0; i < numVerts; i++) { + const Point3F& cp = point[vertsStart[i]]; + array[curr++] = list->addPoint(cp); + pointMask |= (1 << vertsStart[i]); + } + + S32 numFaces = split45 ? sFaceList45[pointMask][0] : sFaceList135[pointMask][0]; + S32* faceStart = split45 ? &sFaceList45[pointMask][1] : &sFaceList135[pointMask][1]; + for (U32 j = 0; j < numFaces; j++) { + S32 plane = faceStart[0]; + S32 v0 = faceStart[1]; + S32 v1 = faceStart[2]; + S32 v2 = faceStart[3]; + + list->begin(0, plane); + list->vertex(array[v0]); + list->vertex(array[v1]); + list->vertex(array[v2]); + list->plane(array[v0], array[v1], array[v2]); + list->end(); + + faceStart += 4; + } +} + + +//---------------------------------------------------------------------------- + +void TerrainBlock::buildConvex(const Box3F& box,Convex* convex) +{ + sTerrainConvexList.collectGarbage(); + + // First check to see if the query misses the + // terrain elevation range. + const Point3F &terrainPos = getPosition(); + if ( box.maxExtents.z - terrainPos.z < -TerrainThickness || + box.minExtents.z - terrainPos.z > fixedToFloat( mFile->getMaxHeight() ) ) + return; + + // Transform the bounding sphere into the object's coord space. Note that this + // not really optimal. + Box3F osBox = box; + mWorldToObj.mul(osBox); + AssertWarn(mObjScale == Point3F(1, 1, 1), "Error, handle the scale transform on the terrain"); + + S32 xStart = (S32)mFloor( osBox.minExtents.x / mSquareSize ); + S32 xEnd = (S32)mCeil ( osBox.maxExtents.x / mSquareSize ); + S32 yStart = (S32)mFloor( osBox.minExtents.y / mSquareSize ); + S32 yEnd = (S32)mCeil ( osBox.maxExtents.y / mSquareSize ); + S32 xExt = xEnd - xStart; + if (xExt > MaxExtent) + xExt = MaxExtent; + + U16 heightMax = floatToFixed(osBox.maxExtents.z); + U16 heightMin = (osBox.minExtents.z < 0)? 0: floatToFixed(osBox.minExtents.z); + + const U32 BlockMask = mFile->mSize - 1; + + for ( S32 y = yStart; y < yEnd; y++ ) + { + S32 yi = y & BlockMask; + + // + for ( S32 x = xStart; x < xEnd; x++ ) + { + S32 xi = x & BlockMask; + + const TerrainSquare *sq = mFile->findSquare( 0, xi, yi ); + + // If we disable repeat, then skip non-primary + if ( !mTile && ( x != xi || y != yi ) ) + continue; + + // holes only in the primary terrain block + if ( ( ( sq->flags & TerrainSquare::Empty ) && x == xi && y == yi ) || + sq->minHeight > heightMax || + sq->maxHeight < heightMin ) + continue; + + U32 sid = (x << 16) + (y & ((1 << 16) - 1)); + Convex *cc = 0; + + // See if the square already exists as part of the working set. + CollisionWorkingList& wl = convex->getWorkingList(); + for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) + if (itr->mConvex->getType() == TerrainConvexType && + static_cast(itr->mConvex)->squareId == sid) { + cc = itr->mConvex; + break; + } + + if (cc) + continue; + + // Create a new convex. + TerrainConvex* cp = new TerrainConvex; + sTerrainConvexList.registerObject(cp); + convex->addToWorkingList(cp); + cp->halfA = true; + cp->square = 0; + cp->mObject = this; + cp->squareId = sid; + cp->material = mFile->getLayerIndex( xi, yi ); + cp->box.minExtents.set((F32)(x * mSquareSize), (F32)(y * mSquareSize), fixedToFloat( sq->minHeight )); + cp->box.maxExtents.x = cp->box.minExtents.x + mSquareSize; + cp->box.maxExtents.y = cp->box.minExtents.y + mSquareSize; + cp->box.maxExtents.z = fixedToFloat( sq->maxHeight ); + mObjToWorld.mul(cp->box); + + // Build points + Point3F* pos = cp->point; + for (int i = 0; i < 4 ; i++,pos++) { + S32 dx = i >> 1; + S32 dy = dx ^ (i & 1); + pos->x = (F32)((x + dx) * mSquareSize); + pos->y = (F32)((y + dy) * mSquareSize); + pos->z = fixedToFloat( mFile->getHeight(xi + dx, yi + dy) ); + } + + // Build normals, then split into two Convex objects if the + // square is concave + if ((cp->split45 = sq->flags & TerrainSquare::Split45) == true) { + VectorF *vp = cp->point; + mCross(vp[0] - vp[1],vp[2] - vp[1],&cp->normal[0]); + cp->normal[0].normalize(); + mCross(vp[2] - vp[3],vp[0] - vp[3],&cp->normal[1]); + cp->normal[1].normalize(); + if (mDot(vp[3] - vp[1],cp->normal[0]) > 0) { + TerrainConvex* nc = new TerrainConvex(*cp); + sTerrainConvexList.registerObject(nc); + convex->addToWorkingList(nc); + nc->halfA = false; + nc->square = cp; + cp->square = nc; + } + } + else { + VectorF *vp = cp->point; + mCross(vp[3] - vp[0],vp[1] - vp[0],&cp->normal[0]); + cp->normal[0].normalize(); + mCross(vp[1] - vp[2],vp[3] - vp[2],&cp->normal[1]); + cp->normal[1].normalize(); + if (mDot(vp[2] - vp[0],cp->normal[0]) > 0) { + TerrainConvex* nc = new TerrainConvex(*cp); + sTerrainConvexList.registerObject(nc); + convex->addToWorkingList(nc); + nc->halfA = false; + nc->square = cp; + cp->square = nc; + } + } + } + } +} + +static inline void swap(U32*& a,U32*& b) +{ + U32* t = b; + b = a; + a = t; +} + +static void clrbuf(U32* p, U32 s) +{ + U32* e = p + s; + while (p != e) + *p++ = U32_MAX; +} + +bool TerrainBlock::buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF&) +{ + // First check to see if the query misses the + // terrain elevation range. + const Point3F &terrainPos = getPosition(); + if ( box.maxExtents.z - terrainPos.z < -TerrainThickness || + box.minExtents.z - terrainPos.z > fixedToFloat( mFile->getMaxHeight() ) ) + return false; + + // Transform the bounding sphere into the object's coord + // space. Note that this is really optimal. + Box3F osBox = box; + mWorldToObj.mul(osBox); + AssertWarn(mObjScale == Point3F::One, "Error, handle the scale transform on the terrain"); + + // Setup collision state data + polyList->setTransform(&getTransform(), getScale()); + polyList->setObject(this); + + S32 xStart = (S32)mFloor( osBox.minExtents.x / mSquareSize ); + S32 xEnd = (S32)mCeil ( osBox.maxExtents.x / mSquareSize ); + S32 yStart = (S32)mFloor( osBox.minExtents.y / mSquareSize ); + S32 yEnd = (S32)mCeil ( osBox.maxExtents.y / mSquareSize ); + if (!mTile && xStart<0) + xStart = 0; + S32 xExt = xEnd - xStart; + if (xExt > MaxExtent) + xExt = MaxExtent; + xEnd = xStart + xExt; + + U32 heightMax = floatToFixed(osBox.maxExtents.z); + U32 heightMin = (osBox.minExtents.z < 0.0f)? 0.0f: floatToFixed(osBox.minExtents.z); + + // Index of shared points + U32 bp[(MaxExtent + 1) * 2],*vb[2]; + vb[0] = &bp[0]; + vb[1] = &bp[xExt + 1]; + clrbuf(vb[1],xExt + 1); + + const U32 BlockMask = mFile->mSize - 1; + + bool emitted = false; + for (S32 y = yStart; y < yEnd; y++) + { + S32 yi = y & BlockMask; + + swap(vb[0],vb[1]); + clrbuf(vb[1],xExt + 1); + // + for (S32 x = xStart; x < xEnd; x++) + { + S32 xi = x & BlockMask; + const TerrainSquare *sq = mFile->findSquare( 0, xi, yi ); + + // If we disable repeat, then skip non-primary + if ( !mTile && ( x != xi || y != yi ) ) + continue; + + // holes only in the primary terrain block + if ( ( ( sq->flags & TerrainSquare::Empty ) && x == xi && y == yi ) || + sq->minHeight > heightMax || + sq->maxHeight < heightMin ) + continue; + + emitted = true; + + // Add the missing points + U32 vi[5]; + for (int i = 0; i < 4 ; i++) + { + S32 dx = i >> 1; + S32 dy = dx ^ (i & 1); + U32* vp = &vb[dy][x - xStart + dx]; + if (*vp == U32_MAX) + { + Point3F pos; + pos.x = (F32)((x + dx) * mSquareSize); + pos.y = (F32)((y + dy) * mSquareSize); + pos.z = fixedToFloat( mFile->getHeight(xi + dx, yi + dy) ); + *vp = polyList->addPoint(pos); + } + vi[i] = *vp; + } + + U32* vp = &vi[0]; + if ( !( sq->flags & TerrainSquare::Split45 ) ) + vi[4] = vi[0], vp++; + + BaseMatInstance *material = NULL; //getMaterialInst( xi, yi ); + U32 surfaceKey = ((xi << 16) + yi) << 1; + polyList->begin(material,surfaceKey); + polyList->vertex(vp[0]); + polyList->vertex(vp[1]); + polyList->vertex(vp[2]); + polyList->plane(vp[0],vp[1],vp[2]); + polyList->end(); + polyList->begin(material,surfaceKey + 1); + polyList->vertex(vp[0]); + polyList->vertex(vp[2]); + polyList->vertex(vp[3]); + polyList->plane(vp[0],vp[2],vp[3]); + polyList->end(); + } + } + + return emitted; +} + +//---------------------------------------------------------------------------- + +static F32 calcInterceptV(F32 vStart, F32 invDeltaV, F32 intercept) +{ + return (intercept - vStart) * invDeltaV; +} + +static F32 calcInterceptNone(F32, F32, F32) +{ + return MAX_FLOAT; +} + +static F32 (*calcInterceptX)(F32, F32, F32); +static F32 (*calcInterceptY)(F32, F32, F32); + +static U32 lineCount; +static Point3F lineStart, lineEnd; + +bool TerrainBlock::castRay(const Point3F &start, const Point3F &end, RayInfo *info) +{ + if ( !castRayI(start, end, info, false) ) + return false; + + // Set intersection point. + info->setContactPoint( start, end ); + + // Set material at contact point. + Point2I gridPos = getGridPos( info->point ); + U8 layer = mFile->getLayerIndex( gridPos.x, gridPos.y ); + info->material = mFile->getMaterialMapping( layer ); + + return true; +} + +bool TerrainBlock::castRayI(const Point3F &start, const Point3F &end, RayInfo *info, bool collideEmpty) +{ + lineCount = 0; + lineStart = start; + lineEnd = end; + + info->object = this; + + if(start.x == end.x && start.y == end.y) + { + if (end.z == start.z) + return false; + + F32 height; + if(!getNormalAndHeight(Point2F(start.x, start.y), &info->normal, &height, true)) + return false; + + F32 t = (height - start.z) / (end.z - start.z); + if(t < 0 || t > 1) + return false; + info->t = t; + + return true; + } + + F32 invBlockWorldSize = 1 / getWorldBlockSize(); + + Point3F pStart(start.x * invBlockWorldSize, start.y * invBlockWorldSize, start.z); + Point3F pEnd(end.x * invBlockWorldSize, end.y * invBlockWorldSize, end.z); + + int blockX = (S32)mFloor(pStart.x); + int blockY = (S32)mFloor(pStart.y); + + int dx, dy; + + F32 invDeltaX; + if(pEnd.x == pStart.x) + { + calcInterceptX = calcInterceptNone; + invDeltaX = 0; + dx = 0; + } + else + { + invDeltaX = 1 / (pEnd.x - pStart.x); + calcInterceptX = calcInterceptV; + if(pEnd.x < pStart.x) + dx = -1; + else + dx = 1; + } + + F32 invDeltaY; + if(pEnd.y == pStart.y) + { + calcInterceptY = calcInterceptNone; + invDeltaY = 0; + dy = 0; + } + else + { + invDeltaY = 1 / (pEnd.y - pStart.y); + calcInterceptY = calcInterceptV; + if(pEnd.y < pStart.y) + dy = -1; + else + dy = 1; + } + + const U32 BlockSquareWidth = mFile->mSize; + const U32 GridLevels = mFile->mGridLevels; + + F32 startT = 0; + for(;;) + { + F32 nextXInt = calcInterceptX(pStart.x, invDeltaX, (F32)(blockX + (dx == 1))); + F32 nextYInt = calcInterceptY(pStart.y, invDeltaY, (F32)(blockY + (dy == 1))); + + F32 intersectT = 1; + + if(nextXInt < intersectT) + intersectT = nextXInt; + if(nextYInt < intersectT) + intersectT = nextYInt; + + if ( castRayBlock( pStart, + pEnd, + Point2I( blockX * BlockSquareWidth, + blockY * BlockSquareWidth ), + GridLevels, + invDeltaX, + invDeltaY, + startT, + intersectT, + info, + collideEmpty ) ) + { + info->normal.z *= BlockSquareWidth * mSquareSize; + info->normal.normalize(); + return true; + } + + startT = intersectT; + if(intersectT >= 1) + break; + if(nextXInt < nextYInt) + blockX += dx; + else if(nextYInt < nextXInt) + blockY += dy; + else + { + blockX += dx; + blockY += dy; + } + } + + return false; +} + +struct TerrLOSStackNode +{ + F32 startT; + F32 endT; + Point2I blockPos; + U32 level; +}; + +bool TerrainBlock::castRayBlock( const Point3F &pStart, + const Point3F &pEnd, + const Point2I &aBlockPos, + U32 aLevel, + F32 invDeltaX, + F32 invDeltaY, + F32 aStartT, + F32 aEndT, + RayInfo *info, + bool collideEmpty ) +{ + const U32 BlockSquareWidth = mFile->mSize; + const U32 GridLevels = mFile->mGridLevels; + const U32 BlockMask = mFile->mSize - 1; + + F32 invBlockSize = 1 / F32( BlockSquareWidth ); + + static Vector stack; + stack.setSize( GridLevels * 3 + 1 ); + U32 stackSize = 1; + + stack[0].startT = aStartT; + stack[0].endT = aEndT; + stack[0].blockPos = aBlockPos; + stack[0].level = aLevel; + + if(!mTile && !aBlockPos.isZero()) + return false; + + while(stackSize--) + { + TerrLOSStackNode *sn = stack.address() + stackSize; + U32 level = sn->level; + F32 startT = sn->startT; + F32 endT = sn->endT; + Point2I blockPos = sn->blockPos; + + const TerrainSquare *sq = mFile->findSquare( level, blockPos.x, blockPos.y ); + + F32 startZ = startT * (pEnd.z - pStart.z) + pStart.z; + F32 endZ = endT * (pEnd.z - pStart.z) + pStart.z; + + F32 minHeight = fixedToFloat(sq->minHeight); + if(startZ <= minHeight && endZ <= minHeight) + continue; + + F32 maxHeight = fixedToFloat(sq->maxHeight); + if(startZ >= maxHeight && endZ >= maxHeight) + continue; + + if ( !collideEmpty && ( sq->flags & TerrainSquare::Empty ) && + blockPos.x == ( blockPos.x & BlockMask ) && blockPos.y == ( blockPos.y & BlockMask )) + continue; + + if(level == 0) + { + F32 xs = blockPos.x * invBlockSize; + F32 ys = blockPos.y * invBlockSize; + + F32 zBottomLeft = fixedToFloat( mFile->getHeight(blockPos.x, blockPos.y) ); + F32 zBottomRight= fixedToFloat( mFile->getHeight(blockPos.x + 1, blockPos.y) ); + F32 zTopLeft = fixedToFloat( mFile->getHeight(blockPos.x, blockPos.y + 1) ); + F32 zTopRight = fixedToFloat( mFile->getHeight(blockPos.x + 1, blockPos.y + 1) ); + + PlaneF p1, p2; + PlaneF divider; + Point3F planePoint; + + if(sq->flags & TerrainSquare::Split45) + { + p1.set(zBottomLeft - zBottomRight, zBottomRight - zTopRight, invBlockSize); + p2.set(zTopLeft - zTopRight, zBottomLeft - zTopLeft, invBlockSize); + planePoint.set(xs, ys, zBottomLeft); + divider.x = 1; + divider.y = -1; + divider.z = 0; + } + else + { + p1.set(zTopLeft - zTopRight, zBottomRight - zTopRight, invBlockSize); + p2.set(zBottomLeft - zBottomRight, zBottomLeft - zTopLeft, invBlockSize); + planePoint.set(xs + invBlockSize, ys, zBottomRight); + divider.x = 1; + divider.y = 1; + divider.z = 0; + } + p1.setPoint(planePoint); + p2.setPoint(planePoint); + divider.setPoint(planePoint); + + F32 t1 = p1.intersect(pStart, pEnd); + F32 t2 = p2.intersect(pStart, pEnd); + F32 td = divider.intersect(pStart, pEnd); + + F32 dStart = divider.distToPlane(pStart); + F32 dEnd = divider.distToPlane(pEnd); + + // see if the line crosses the divider + if((dStart >= 0 && dEnd < 0) || (dStart < 0 && dEnd >= 0)) + { + if(dStart < 0) + { + F32 temp = t1; + t1 = t2; + t2 = temp; + } + if(t1 >= startT && t1 && t1 <= td && t1 <= endT) + { + info->t = t1; + info->normal = p1; + return true; + } + if(t2 >= td && t2 >= startT && t2 <= endT) + { + info->t = t2; + info->normal = p2; + return true; + } + } + else + { + F32 t; + if(dStart >= 0) { + t = t1; + info->normal = p1; + } + else { + t = t2; + info->normal = p2; + } + if(t >= startT && t <= endT) + { + info->t = t; + return true; + } + } + continue; + } + int subSqWidth = 1 << (level - 1); + F32 xIntercept = (blockPos.x + subSqWidth) * invBlockSize; + F32 xInt = calcInterceptX(pStart.x, invDeltaX, xIntercept); + F32 yIntercept = (blockPos.y + subSqWidth) * invBlockSize; + F32 yInt = calcInterceptY(pStart.y, invDeltaY, yIntercept); + + F32 startX = startT * (pEnd.x - pStart.x) + pStart.x; + F32 startY = startT * (pEnd.y - pStart.y) + pStart.y; + + if(xInt < startT) + xInt = MAX_FLOAT; + if(yInt < startT) + yInt = MAX_FLOAT; + + U32 x0 = (startX > xIntercept) * subSqWidth; + U32 y0 = (startY > yIntercept) * subSqWidth; + U32 x1 = subSqWidth - x0; + U32 y1 = subSqWidth - y0; + U32 nextLevel = level - 1; + + // push the items on the stack in reverse order of processing + if(xInt > endT && yInt > endT) + { + // only test the square the point started in: + stack[stackSize].blockPos.set(blockPos.x + x0, blockPos.y + y0); + stack[stackSize].level = nextLevel; + stackSize++; + } + else if(xInt < yInt) + { + F32 nextIntersect = endT; + if(yInt <= endT) + { + stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y1); + stack[stackSize].startT = yInt; + stack[stackSize].endT = endT; + stack[stackSize].level = nextLevel; + nextIntersect = yInt; + stackSize++; + } + stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y0); + stack[stackSize].startT = xInt; + stack[stackSize].endT = nextIntersect; + stack[stackSize].level = nextLevel; + + stack[stackSize+1].blockPos.set(blockPos.x + x0, blockPos.y + y0); + stack[stackSize+1].startT = startT; + stack[stackSize+1].endT = xInt; + stack[stackSize+1].level = nextLevel; + stackSize += 2; + } + else if(yInt < xInt) + { + F32 nextIntersect = endT; + if(xInt <= endT) + { + stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y1); + stack[stackSize].startT = xInt; + stack[stackSize].endT = endT; + stack[stackSize].level = nextLevel; + nextIntersect = xInt; + stackSize++; + } + stack[stackSize].blockPos.set(blockPos.x + x0, blockPos.y + y1); + stack[stackSize].startT = yInt; + stack[stackSize].endT = nextIntersect; + stack[stackSize].level = nextLevel; + + stack[stackSize+1].blockPos.set(blockPos.x + x0, blockPos.y + y0); + stack[stackSize+1].startT = startT; + stack[stackSize+1].endT = yInt; + stack[stackSize+1].level = nextLevel; + stackSize += 2; + } + else + { + stack[stackSize].blockPos.set(blockPos.x + x1, blockPos.y + y1); + stack[stackSize].startT = xInt; + stack[stackSize].endT = endT; + stack[stackSize].level = nextLevel; + + stack[stackSize+1].blockPos.set(blockPos.x + x0, blockPos.y + y0); + stack[stackSize+1].startT = startT; + stack[stackSize+1].endT = xInt; + stack[stackSize+1].level = nextLevel; + stackSize += 2; + } + } + + return false; +} diff --git a/terrain/terrCollision.h b/terrain/terrCollision.h new file mode 100644 index 0000000..dd4291f --- /dev/null +++ b/terrain/terrCollision.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRCOLL_H_ +#define _TERRCOLL_H_ + +#ifndef _CONVEX_H_ +#include "collision/convex.h" +#endif + + +class TerrainConvex : public Convex +{ + friend class TerrainBlock; + TerrainConvex *square; ///< Alternate convex if square is concave + bool halfA; ///< Which half of square + bool split45; ///< Square split pattern + U32 squareId; ///< Used to match squares + U32 material; + Point3F point[4]; ///< 3-4 vertices + VectorF normal[2]; + Box3F box; ///< Bounding box + + public: + + TerrainConvex(); + + TerrainConvex( const TerrainConvex& cv ); + + // Convex + Box3F getBoundingBox() const; + Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const; + Point3F support(const VectorF& v) const; + void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf); + void getPolyList(AbstractPolyList* list); +}; + +#endif // _TERRCOLL_H_ diff --git a/terrain/terrData.cpp b/terrain/terrData.cpp new file mode 100644 index 0000000..6eefc1b --- /dev/null +++ b/terrain/terrData.cpp @@ -0,0 +1,1084 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrData.h" + +#include "terrain/terrCollision.h" +#include "terrain/terrCell.h" +#include "terrain/terrRender.h" +#include "terrain/terrMaterial.h" +#include "terrain/terrCellMaterial.h" + +#include "gui/worldEditor/terrainEditor.h" + +#include "math/mathIO.h" +#include "core/stream/fileStream.h" +#include "core/stream/bitStream.h" +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "core/util/safeDelete.h" +#include "T3D/objectTypes.h" +#include "renderInstance/renderPassManager.h" +#include "sceneGraph/sceneState.h" +#include "materials/materialManager.h" +#include "gfx/gfxTextureManager.h" +#include "core/resourceManager.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsStatic.h" + + +using namespace Torque; + +IMPLEMENT_CO_NETOBJECT_V1(TerrainBlock); + + +Signal TerrainBlock::smUpdateSignal; + +F32 TerrainBlock::smLODScale = 1.0f; +F32 TerrainBlock::smDetailScale = 1.0f; + + +//RBP - Global function declared in Terrdata.h +TerrainBlock* getTerrainUnderWorldPoint(const Point3F & wPos) +{ + // Cast a ray straight down from the world position and see which + // Terrain is the closest to our starting point + Point3F startPnt = wPos; + Point3F endPnt = wPos + Point3F(0.0f, 0.0f, -10000.0f); + + S32 blockIndex = -1; + F32 nearT = 1.0f; + + SimpleQueryList queryList; + gServerContainer.findObjects( TerrainObjectType, SimpleQueryList::insertionCallback, &queryList); + + for (U32 i = 0; i < queryList.mList.size(); i++) + { + Point3F tStartPnt, tEndPnt; + TerrainBlock* terrBlock = dynamic_cast(queryList.mList[i]); + terrBlock->getWorldTransform().mulP(startPnt, &tStartPnt); + terrBlock->getWorldTransform().mulP(endPnt, &tEndPnt); + + RayInfo ri; + if (terrBlock->castRayI(tStartPnt, tEndPnt, &ri, true)) + { + if (ri.t < nearT) + { + blockIndex = i; + nearT = ri.t; + } + } + } + + if (blockIndex > -1) + return (TerrainBlock*)(queryList.mList[blockIndex]); + + return NULL; +} + +ConsoleFunction(getTerrainUnderWorldPoint, S32, 2, 4, "(Point3F x/y/z) Gets the terrain block that is located under the given world point.\n" + "@param x/y/z The world coordinates (floating point values) you wish to query at. " + "These can be formatted as either a string (\"x y z\") or separately as (x, y, z)\n" + "@return Returns the ID of the requested terrain block (0 if not found).\n\n") +{ + Point3F pos; + if(argc == 2) + dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z); + else if(argc == 4) + { + pos.x = dAtof(argv[1]); + pos.y = dAtof(argv[2]); + pos.z = dAtof(argv[3]); + } + + else + { + Con::errorf("getTerrainUnderWorldPoint(Point3F): Invalid argument count! Valid arguments are either \"x y z\" or x,y,z\n"); + return 0; + } + + TerrainBlock* terrain = getTerrainUnderWorldPoint(pos); + if(terrain != NULL) + { + return terrain->getId(); + } + + return 0; + +} + + +TerrainBlock::TerrainBlock() + : mSquareSize( 1.0f ), + mScreenError( 16 ), + mDetailsDirty( false ), + mLayerTexDirty( false ), + mLightMap( NULL ), + mLightMapSize( 256 ), + mTile( false ), + mMaxDetailDistance( 0.0f ), + mCell( NULL ), + mCRC( 0 ), + mHasRendered( false ), + mBaseTexSize( 1024 ), + mBaseMaterial( NULL ), + mDefaultMatInst( NULL ), + mBaseTexScaleConst( NULL ), + mBaseTexIdConst( NULL ), + mPhysicsRep( NULL ) +{ + mTypeMask = TerrainObjectType | StaticObjectType | StaticRenderedObjectType | ShadowCasterObjectType; + mNetFlags.set(Ghostable | ScopeAlways); +} + + +extern Convex sTerrainConvexList; + +TerrainBlock::~TerrainBlock() +{ + // Kill collision + sTerrainConvexList.nukeList(); + + // Delete opacity sources. This will also delete the opacity + // maps held by the sources. + + // CLIPMAP_REMOVAL + /* + for( U32 i = 0; i < mOpacitySources.size(); ++ i ) + SAFE_DELETE( mOpacitySources[ i ] ); + mOpacitySources.clear(); + mOpacityMaps.clear(); + */ + + SAFE_DELETE(mLightMap); + mLightMapTex = NULL; + +#ifdef TORQUE_TOOLS + TerrainEditor* editor = dynamic_cast(Sim::findObject("ETerrainEditor")); + if (editor) + editor->detachTerrain(this); +#endif +} + +void TerrainBlock::_onTextureEvent( GFXTexCallbackCode code ) +{ + if ( code == GFXZombify ) + { + if ( mBaseTex.isValid() && + mBaseTex->isRenderTarget() ) + mBaseTex = NULL; + + mLightMapTex = NULL; + } +} + +bool TerrainBlock::_setSquareSize( void *object, const char *data ) +{ + TerrainBlock *terrain = static_cast( object ); + + F32 newSqaureSize = dAtof( data ); + if ( !mIsEqual( terrain->mSquareSize, newSqaureSize ) ) + { + terrain->mSquareSize = newSqaureSize; + + if ( terrain->isServerObject() && terrain->isProperlyAdded() ) + terrain->_updateBounds(); + + terrain->setMaskBits( HeightMapChangeMask | SizeMask ); + } + + return false; +} + +bool TerrainBlock::_setBaseTexSize( void *object, const char *data ) +{ + TerrainBlock *terrain = static_cast( object ); + + // NOTE: We're limiting the base texture size to + // 2048 as anything greater in size becomes too + // large to generate for many cards. + // + // If you want to remove this limit feel free, but + // prepare for problems if you don't ship the baked + // base texture with your installer. + // + + S32 texSize = mClamp( dAtoi( data ), 0, 2048 ); + if ( terrain->mBaseTexSize != texSize ) + { + terrain->mBaseTexSize = texSize; + terrain->setMaskBits( MaterialMask ); + } + + return false; +} + +void TerrainBlock::setFile( const FileName &terrFileName ) +{ + if ( terrFileName == mTerrFileName ) + return; + + Resource file = ResourceManager::get().load( terrFileName ); + setFile( file ); + + setMaskBits( FileMask | HeightMapChangeMask ); +} + +void TerrainBlock::setFile( Resource terr ) +{ + mFile = terr; + mTerrFileName = terr.getPath(); +} + +bool TerrainBlock::save(const char *filename) +{ + return mFile->save(filename); +} + +bool TerrainBlock::_setTerrainFile( void *object, const char *data ) +{ + static_cast( object )->setFile( FileName( data ) ); + return false; +} + +void TerrainBlock::_updateBounds() +{ + if ( !mFile ) + return; // quick fix to stop crashing when deleting terrainblocks + + // Setup our object space bounds. + mBounds.minExtents.set( 0.0f, 0.0f, 0.0f ); + mBounds.maxExtents.set( getWorldBlockSize(), getWorldBlockSize(), 0.0f ); + getMinMaxHeight( &mBounds.minExtents.z, &mBounds.maxExtents.z ); + + // If we aren't tiling go ahead and set our mObjBox to be equal to mBounds + // This will get overridden with global bounds if tiling is on but it is useful to store + if ( !mTile ) + { + if ( mObjBox.maxExtents != mBounds.maxExtents || + mObjBox.minExtents != mBounds.minExtents ) + { + mObjBox = mBounds; + resetWorldBox(); + } + } +} + +void TerrainBlock::setHeight( const Point2I &pos, F32 height ) +{ + U16 ht = floatToFixed( height ); + mFile->setHeight( pos.x, pos.y, ht ); + + // Note: We do not update the grid here as this could + // be called several times in a loop. We depend on the + // caller doing a grid update when he is done. +} + +void TerrainBlock::updateGridMaterials( const Point2I &minPt, const Point2I &maxPt ) +{ + if ( mCell ) + { + // Tell the terrain cell that something changed. + const RectI gridRect( minPt, maxPt - minPt ); + mCell->updateGrid( gridRect, true ); + } + + // We mark us as dirty... it will be updated + // before the next time we render the terrain. + mLayerTexDirty = true; + + // Signal anyone that cares that the opacity was changed. + smUpdateSignal.trigger( LayersUpdate, this, minPt, maxPt ); +} + + +Point2I TerrainBlock::getGridPos( const Point3F &worldPos ) const +{ + Point3F terrainPos = worldPos; + getWorldTransform().mulP( terrainPos ); + + F32 squareSize = ( F32 ) getSquareSize(); + F32 halfSquareSize = squareSize / 2.0; + + F32 x = ( terrainPos.x + halfSquareSize ) / squareSize; + F32 y = ( terrainPos.y + halfSquareSize ) / squareSize; + + Point2I gridPos( ( S32 ) mFloor( x ), ( S32 ) mFloor( y ) ); + return gridPos; +} + +void TerrainBlock::updateGrid( const Point2I &minPt, const Point2I &maxPt, bool updateClient ) +{ + // On the client we just signal everyone that the height + // map has changed... the server does the actual changes. + if ( isClientObject() ) + { + PROFILE_SCOPE( TerrainBlock_updateGrid_Client ); + + // This depends on the client getting this call 'after' the server. + // Which is currently the case. + _updateBounds(); + + smUpdateSignal.trigger( HeightmapUpdate, this, minPt, maxPt ); + + // Tell the terrain cell that the height changed. + const RectI gridRect( minPt, maxPt - minPt ); + mCell->updateGrid( gridRect ); + + // Give the plugin a chance to update the + // collision representation. + if ( mPhysicsRep ) + mPhysicsRep->update(); + + return; + } + + // Now on the server we rebuild the + // affected area of the grid map. + mFile->updateGrid( minPt, maxPt ); + + // Fix up the bounds. + _updateBounds(); + + // Give the plugin a chance to update the + // collision representation. + if ( mPhysicsRep ) + mPhysicsRep->update(); + + // Signal again here for any server side observers. + smUpdateSignal.trigger( HeightmapUpdate, this, minPt, maxPt ); + + // If this is a server object and the client update + // was requested then try to use the local connection + // pointer to do it. + if ( updateClient && getClientObject() ) + ((TerrainBlock*)getClientObject())->updateGrid( minPt, maxPt, false ); +} + +bool TerrainBlock::getHeight( const Point2F &pos, F32 *height ) const +{ + F32 invSquareSize = 1.0f / mSquareSize; + F32 xp = pos.x * invSquareSize; + F32 yp = pos.y * invSquareSize; + S32 x = S32(xp); + S32 y = S32(yp); + xp -= (F32)x; + yp -= (F32)y; + + const U32 blockMask = mFile->mSize - 1; + + // If we disable repeat, then skip non-primary block + if ( !mTile && ( x & ~blockMask || y & ~blockMask ) ) + return false; + + x &= blockMask; + y &= blockMask; + + const TerrainSquare *sq = mFile->findSquare( 0, x, y ); + if ( sq->flags & TerrainSquare::Empty ) + return false; + + F32 zBottomLeft = fixedToFloat( mFile->getHeight( x, y ) ); + F32 zBottomRight = fixedToFloat( mFile->getHeight( x + 1, y ) ); + F32 zTopLeft = fixedToFloat( mFile->getHeight( x, y + 1 ) ); + F32 zTopRight = fixedToFloat( mFile->getHeight( x + 1, y + 1 ) ); + + if ( sq->flags & TerrainSquare::Split45 ) + { + if (xp>yp) + // bottom half + *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight); + else + // top half + *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft); + } + else + { + if (1.0f-xp>yp) + // bottom half + *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft); + else + // top half + *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight); + } + + return true; +} + +bool TerrainBlock::getNormal( const Point2F &pos, Point3F *normal, bool normalize, bool skipEmpty ) const +{ + F32 invSquareSize = 1.0f / mSquareSize; + F32 xp = pos.x * invSquareSize; + F32 yp = pos.y * invSquareSize; + S32 x = S32(xp); + S32 y = S32(yp); + xp -= (F32)x; + yp -= (F32)y; + + const U32 blockMask = mFile->mSize - 1; + + // If we disable repeat, then skip non-primary block + if ( !mTile && ( x & ~blockMask || y & ~blockMask ) ) + return false; + + x &= blockMask; + y &= blockMask; + + const TerrainSquare *sq = mFile->findSquare( 0, x, y ); + if ( skipEmpty && sq->flags & TerrainSquare::Empty ) + return false; + + F32 zBottomLeft = fixedToFloat( mFile->getHeight( x, y ) ); + F32 zBottomRight = fixedToFloat( mFile->getHeight( x + 1, y ) ); + F32 zTopLeft = fixedToFloat( mFile->getHeight( x, y + 1 ) ); + F32 zTopRight = fixedToFloat( mFile->getHeight( x + 1, y + 1 ) ); + + if ( sq->flags & TerrainSquare::Split45 ) + { + if (xp>yp) + // bottom half + normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize); + else + // top half + normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize); + } + else + { + if (1.0f-xp>yp) + // bottom half + normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize); + else + // top half + normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize); + } + + if (normalize) + normal->normalize(); + + return true; +} + +bool TerrainBlock::getNormalAndHeight( const Point2F &pos, Point3F *normal, F32 *height, bool normalize ) const +{ + F32 invSquareSize = 1.0f / mSquareSize; + F32 xp = pos.x * invSquareSize; + F32 yp = pos.y * invSquareSize; + S32 x = S32(xp); + S32 y = S32(yp); + xp -= (F32)x; + yp -= (F32)y; + + const U32 blockMask = mFile->mSize - 1; + + // If we disable repeat, then skip non-primary block + if ( !mTile && ( x & ~blockMask || y & ~blockMask ) ) + return false; + + x &= blockMask; + y &= blockMask; + + const TerrainSquare *sq = mFile->findSquare( 0, x, y ); + if ( sq->flags & TerrainSquare::Empty ) + return false; + + F32 zBottomLeft = fixedToFloat( mFile->getHeight(x, y) ); + F32 zBottomRight = fixedToFloat( mFile->getHeight(x + 1, y) ); + F32 zTopLeft = fixedToFloat( mFile->getHeight(x, y + 1) ); + F32 zTopRight = fixedToFloat( mFile->getHeight(x + 1, y + 1) ); + + if ( sq->flags & TerrainSquare::Split45 ) + { + if (xp>yp) + { + // bottom half + normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize); + *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight); + } + else + { + // top half + normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize); + *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft); + } + } + else + { + if (1.0f-xp>yp) + { + // bottom half + normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize); + *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft); + } + else + { + // top half + normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize); + *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight); + } + } + + if (normalize) + normal->normalize(); + + return true; +} + + +bool TerrainBlock::getNormalHeightMaterial( const Point2F &pos, + Point3F *normal, + F32 *height, + U8 *matIndex ) const +{ + F32 invSquareSize = 1.0f / mSquareSize; + F32 xp = pos.x * invSquareSize; + F32 yp = pos.y * invSquareSize; + S32 x = S32(xp); + S32 y = S32(yp); + xp -= (F32)x; + yp -= (F32)y; + + const U32 blockMask = mFile->mSize - 1; + + // If we disable repeat, then skip non-primary block + if ( !mTile && ( x & ~blockMask || y & ~blockMask ) ) + return false; + + x &= blockMask; + y &= blockMask; + + const TerrainSquare *sq = mFile->findSquare( 0, x, y ); + if ( sq->flags & TerrainSquare::Empty ) + return false; + + F32 zBottomLeft = fixedToFloat( mFile->getHeight(x, y) ); + F32 zBottomRight = fixedToFloat( mFile->getHeight(x + 1, y) ); + F32 zTopLeft = fixedToFloat( mFile->getHeight(x, y + 1) ); + F32 zTopRight = fixedToFloat( mFile->getHeight(x + 1, y + 1) ); + + *matIndex = mFile->getLayerIndex( x, y ); + + if ( sq->flags & TerrainSquare::Split45 ) + { + if (xp>yp) + { + // bottom half + normal->set(zBottomLeft-zBottomRight, zBottomRight-zTopRight, mSquareSize); + *height = zBottomLeft + xp * (zBottomRight-zBottomLeft) + yp * (zTopRight-zBottomRight); + } + else + { + // top half + normal->set(zTopLeft-zTopRight, zBottomLeft-zTopLeft, mSquareSize); + *height = zBottomLeft + xp * (zTopRight-zTopLeft) + yp * (zTopLeft-zBottomLeft); + } + } + else + { + if (1.0f-xp>yp) + { + // bottom half + normal->set(zBottomLeft-zBottomRight, zBottomLeft-zTopLeft, mSquareSize); + *height = zBottomRight + (1.0f-xp) * (zBottomLeft-zBottomRight) + yp * (zTopLeft-zBottomLeft); + } + else + { + // top half + normal->set(zTopLeft-zTopRight, zBottomRight-zTopRight, mSquareSize); + *height = zBottomRight + (1.0f-xp) * (zTopLeft-zTopRight) + yp * (zTopRight-zBottomRight); + } + } + + normal->normalize(); + + return true; +} + +U32 TerrainBlock::getMaterialCount() const +{ + return mFile->mMaterials.size(); +} + +void TerrainBlock::addMaterial( const String &name, U32 insertAt ) +{ + TerrainMaterial *mat = TerrainMaterial::findOrCreate( name ); + + if ( insertAt == -1 ) + { + mFile->mMaterials.push_back( mat ); + mFile->_initMaterialInstMapping(); + } + else + { + + // TODO: Insert and reindex! + + } + + mDetailsDirty = true; + mLayerTexDirty = true; +} + +void TerrainBlock::removeMaterial( U32 index ) +{ + mFile->mMaterials.erase( index ); + mFile->_initMaterialInstMapping(); + + // TODO: Reindex! +} + +void TerrainBlock::updateMaterial( U32 index, const String &name ) +{ + if ( index >= mFile->mMaterials.size() ) + return; + + mFile->mMaterials[ index ] = TerrainMaterial::findOrCreate( name ); + mFile->_initMaterialInstMapping(); + + mDetailsDirty = true; + mLayerTexDirty = true; +} + +TerrainMaterial* TerrainBlock::getMaterial( U32 index ) const +{ + if ( index >= mFile->mMaterials.size() ) + return NULL; + + return mFile->mMaterials[ index ]; +} + +void TerrainBlock::deleteAllMaterials() +{ + mFile->mMaterials.clear(); + mFile->mMaterialInstMapping.clearMatInstList(); +} + +const char* TerrainBlock::getMaterialName( U32 index ) const +{ + if ( index < mFile->mMaterials.size() ) + return mFile->mMaterials[ index ]->getInternalName(); + + return NULL; +} + +void TerrainBlock::setLightMap( GBitmap *newLightMap ) +{ + SAFE_DELETE( mLightMap ); + mLightMap = newLightMap; + mLightMapTex = NULL; +} + +void TerrainBlock::clearLightMap() +{ + if ( !mLightMap ) + mLightMap = new GBitmap( mLightMapSize, mLightMapSize, 0, GFXFormatR8G8B8 ); + + mLightMap->fillWhite(); + mLightMapTex = NULL; +} + +GFXTextureObject* TerrainBlock::getLightMapTex() +{ + if ( mLightMapTex.isNull() && mLightMap ) + { + mLightMapTex.set( mLightMap, + &GFXDefaultStaticDiffuseProfile, + false, + "TerrainBlock::getLightMapTex()" ); + } + + return mLightMapTex; +} + +void TerrainBlock::onEditorEnable() +{ +} + +void TerrainBlock::onEditorDisable() +{ +} + +bool TerrainBlock::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if ( mTerrFileName.isEmpty() ) + { + mTerrFileName = Con::getVariable( "$Client::MissionFile" ); + Vector materials; + materials.push_back( "warning_material" ); + TerrainFile::create( &mTerrFileName, 256, materials ); + } + + Resource terr = ResourceManager::get().load( mTerrFileName ); + if(terr == NULL) + { + if(isClientObject()) + NetConnection::setLastError("You are missing a file needed to play this mission: %s", mTerrFileName.c_str()); + return false; + } + + setFile( terr ); + + if ( terr->mNeedsResaving ) + { + if (Platform::messageBox("Update Terrain File", "You appear to have a Terrain file in an older format. Do you want Torque to update it?", MBOkCancel, MIQuestion) == MROk) + { + terr->save(terr->mFilePath.getFullPath()); + terr->mNeedsResaving = false; + } + } + + if (terr->mFileVersion != TerrainFile::FILE_VERSION || terr->mNeedsResaving) + { + Con::errorf(" *********************************************************"); + Con::errorf(" *********************************************************"); + Con::errorf(" *********************************************************"); + Con::errorf(" PLEASE RESAVE THE TERRAIN FILE FOR THIS MISSION! THANKS!"); + Con::errorf(" *********************************************************"); + Con::errorf(" *********************************************************"); + Con::errorf(" *********************************************************"); + } + + _updateBounds(); + + if (mTile) + setGlobalBounds(); + + resetWorldBox(); + setRenderTransform(mObjToWorld); + + if (isClientObject()) + { + if ( mCRC != terr.getChecksum() ) + { + NetConnection::setLastError("Your terrain file doesn't match the version that is running on the server."); + return false; + } + + clearLightMap(); + + // Init the detail layer rendering helper. + _updateMaterials(); + _updateLayerTexture(); + + // If the cached base texture is older that the terrain file or + // it doesn't exist then generate and cache it. + String baseCachePath = _getBaseTexCacheFileName(); + if ( Platform::compareModifiedTimes( baseCachePath, mTerrFileName ) < 0 ) + _updateBaseTexture( true ); + + // The base texture should have been cached by now... so load it. + mBaseTex.set( baseCachePath, &GFXDefaultStaticDiffuseProfile, "TerrainBlock::mBaseTex" ); + + GFXTextureManager::addEventDelegate( this, &TerrainBlock::_onTextureEvent ); + LightManager::smActivateSignal.notify( this, &TerrainBlock::_onLMActivate ); + + // Build the terrain quadtree. + _rebuildQuadtree(); + } + else + mCRC = terr.getChecksum(); + + addToScene(); + + if ( gPhysicsPlugin ) + mPhysicsRep = gPhysicsPlugin->createStatic( this ); + + return true; +} + +String TerrainBlock::_getBaseTexCacheFileName() const +{ + Torque::Path basePath( mTerrFileName ); + basePath.setFileName( basePath.getFileName() + "_basetex" ); + basePath.setExtension( "dds" ); + return basePath.getFullPath(); +} + +void TerrainBlock::_rebuildQuadtree() +{ + SAFE_DELETE( mCell ); + + // Recursively build the cells. + mCell = TerrCell::init( this ); + + // Build the shared PrimitiveBuffer. + mCell->createPrimBuffer( &mPrimBuffer ); +} + +void TerrainBlock::onRemove() +{ + removeFromScene(); + + SAFE_DELETE( mPhysicsRep ); + + if ( isClientObject() ) + { + mBaseTex = NULL; + mLayerTex = NULL; + SAFE_DELETE( mBaseMaterial ); + SAFE_DELETE( mDefaultMatInst ); + SAFE_DELETE( mCell ); + mPrimBuffer = NULL; + mBaseShader = NULL; + GFXTextureManager::removeEventDelegate( this, &TerrainBlock::_onTextureEvent ); + LightManager::smActivateSignal.remove( this, &TerrainBlock::_onLMActivate ); + } + + Parent::onRemove(); +} + +bool TerrainBlock::prepRenderImage(SceneState* state, const U32 stateKey, + const U32 /*startZone*/, const bool /*modifyBaseState*/) +{ + PROFILE_SCOPE(TerrainBlock_prepRenderImage); + + if (isLastState(state, stateKey)) + return false; + + setLastState(state, stateKey); + + // This should be sufficient for most objects that don't manage zones, and + // don't need to return a specialized RenderImage... + bool render = true; + if (state->isTerrainOverridden() == false) + render = state->isObjectRendered(this); + + // small hack to reduce "stutter" in framerate if terrain is suddenly seen (ie. from an interior) + if( !mHasRendered ) + { + mHasRendered = true; + render = true; + state->enableTerrainOverride(); + } + + if (render == true) + _renderBlock( state ); + + return false; +} + +void TerrainBlock::setTransform(const MatrixF & mat) +{ + Parent::setTransform( mat ); + + if ( mPhysicsRep ) + mPhysicsRep->setTransform( mat ); + + setRenderTransform( mat ); + setMaskBits( TransformMask ); +} + +void TerrainBlock::setScale( const VectorF &scale ) +{ + // We disable scaling... we never scale! + Parent::setScale( VectorF::One ); +} + +void TerrainBlock::initPersistFields() +{ + addGroup( "Media" ); + + addProtectedField( "terrainFile", TypeStringFilename, Offset( mTerrFileName, TerrainBlock ), + &TerrainBlock::_setTerrainFile, &defaultProtectedGetFn, + "The source terrain data file." ); + + endGroup( "Media" ); + + addGroup( "Misc" ); + + addProtectedField( "squareSize", TypeF32, Offset( mSquareSize, TerrainBlock ), + &TerrainBlock::_setSquareSize, &defaultProtectedGetFn, + "Indicates the spacing between points on the XY plane on the terrain." ); + + addField( "tile", TypeBool, Offset( mTile, TerrainBlock ), "Toggles infinite tiling of terrain." ); + + addProtectedField( "baseTexSize", TypeS32, Offset( mBaseTexSize, TerrainBlock ), + &TerrainBlock::_setBaseTexSize, &defaultProtectedGetFn, + "Size of base texture size per meter." ); + + addField( "screenError", TypeS32, Offset( mScreenError, TerrainBlock ), "Not yet implemented." ); + + endGroup( "Misc" ); + + Parent::initPersistFields(); + + Con::addVariable( "$TerrainBlock::debugRender", TypeBool, &smDebugRender ); + Con::addVariable( "$TerrainBlock::lodScale", TypeF32, &smLODScale ); + Con::addVariable( "$TerrainBlock::detailScale", TypeF32, &smDetailScale ); +} + +void TerrainBlock::inspectPostApply() +{ + Parent::inspectPostApply(); + setMaskBits( MiscMask ); +} + +U32 TerrainBlock::packUpdate(NetConnection *, U32 mask, BitStream *stream) +{ + if ( stream->writeFlag( mask & TransformMask ) ) + mathWrite( *stream, getTransform() ); + + if ( stream->writeFlag( mask & FileMask ) ) + { + stream->write( mTerrFileName ); + stream->write( mCRC ); + } + + if ( stream->writeFlag( mask & SizeMask ) ) + { + stream->write( mSquareSize ); + stream->writeFlag( mTile ); + } + + if ( stream->writeFlag( mask & MaterialMask ) ) + stream->write( mBaseTexSize ); + + stream->writeFlag( mask & HeightMapChangeMask ); + + if ( stream->writeFlag( mask & MiscMask ) ) + stream->write( mScreenError ); + + return 0; +} + +void TerrainBlock::unpackUpdate(NetConnection *, BitStream *stream) +{ + if ( stream->readFlag() ) // TransformMask + { + MatrixF mat; + mathRead( *stream, &mat ); + setTransform( mat ); + } + + if ( stream->readFlag() ) // FileMask + { + FileName terrFile; + stream->read( &terrFile ); + stream->read( &mCRC ); + + if ( isProperlyAdded() ) + setFile( terrFile ); + else + mTerrFileName = terrFile; + } + + if ( stream->readFlag() ) // SizeMask + { + stream->read( &mSquareSize ); + mTile = stream->readFlag(); + } + + if ( stream->readFlag() ) // MaterialMask + { + stream->read( &mBaseTexSize ); + + if ( isProperlyAdded() ) + _updateBaseTexture( false ); + } + + if ( stream->readFlag() && isProperlyAdded() ) // HeightMapChangeMask + { + _updateBounds(); + _rebuildQuadtree(); + mDetailsDirty = true; + mLayerTexDirty = true; + } + + if ( stream->readFlag() ) // MiscMask + stream->read( &mScreenError ); +} + +void TerrainBlock::getMinMaxHeight( F32 *minHeight, F32 *maxHeight ) const +{ + // We can get the bound height from the last grid level. + const TerrainSquare *sq = mFile->findSquare( mFile->mGridLevels, 0, 0 ); + *minHeight = fixedToFloat( sq->minHeight ); + *maxHeight = fixedToFloat( sq->maxHeight ); +} + +//----------------------------------------------------------------------------- +// Console Methods +//----------------------------------------------------------------------------- + +ConsoleMethod(TerrainBlock, save, bool, 3, 3, "(string fileName) - saves the terrain block's terrain file to the specified file name.") +{ + char filename[256]; + dStrcpy(filename,argv[2]); + char *ext = dStrrchr(filename, '.'); + if (!ext || dStricmp(ext, ".ter") != 0) + dStrcat(filename, ".ter"); + return static_cast(object)->save(filename); +} + +ConsoleFunction(getTerrainHeight, F32, 2, 3, "(Point2 pos) - gets the terrain height at the specified position." + "@param pos The world space point, minus the z (height) value\n Can be formatted as either (\"x y\") or (x,y)\n" + "@return Returns the terrain height at the given point as an F32 value.\n") +{ + Point2F pos; + F32 height = 0.0f; + + if(argc == 2) + dSscanf(argv[1],"%f %f",&pos.x,&pos.y); + else if(argc == 3) + { + pos.x = dAtof(argv[1]); + pos.y = dAtof(argv[2]); + } + + TerrainBlock * terrain = getTerrainUnderWorldPoint(Point3F(pos.x, pos.y, 5000.0f)); + if(terrain) + if(terrain->isServerObject()) + { + Point3F offset; + terrain->getTransform().getColumn(3, &offset); + pos -= Point2F(offset.x, offset.y); + terrain->getHeight(pos, &height); + } + return height; +} + +ConsoleFunction(getTerrainHeightBelowPosition, F32, 2, 4, "(Point3F pos) - gets the terrain height at the specified position." + "@param pos The world space point. Can be formatted as either (\"x y z\") or (x,y,z)\n" + "@note This function is useful if you simply want to grab the terrain height underneath an object.\n" + "@return Returns the terrain height at the given point as an F32 value.\n") +{ + Point3F pos; + F32 height = 0.0f; + + if(argc == 2) + dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z); + else if(argc == 4) + { + pos.x = dAtof(argv[1]); + pos.y = dAtof(argv[2]); + pos.z = dAtof(argv[3]); + } + + else + { + Con::errorf("getTerrainHeightBelowPosition(Point3F): Invalid argument count! Valid arguments are either \"x y z\" or x,y,z\n"); + return 0; + } + + TerrainBlock * terrain = getTerrainUnderWorldPoint(pos); + + Point2F nohghtPos(pos.x, pos.y); + + if(terrain) + { + if(terrain->isServerObject()) + { + Point3F offset; + terrain->getTransform().getColumn(3, &offset); + nohghtPos -= Point2F(offset.x, offset.y); + terrain->getHeight(nohghtPos, &height); + } + } + + return height; +} diff --git a/terrain/terrData.h b/terrain/terrData.h new file mode 100644 index 0000000..3532013 --- /dev/null +++ b/terrain/terrData.h @@ -0,0 +1,394 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRDATA_H_ +#define _TERRDATA_H_ + +#ifndef _MPOINT3_H_ +#include "math/mPoint3.h" +#endif +#ifndef _SCENEOBJECT_H_ +#include "sceneGraph/sceneObject.h" +#endif +#ifndef __RESOURCE_H__ +#include "core/resource.h" +#endif +#ifndef _RENDERPASSMANAGER_H_ +#include "renderInstance/renderPassManager.h" +#endif +#ifndef _TSIGNAL_H_ +#include "core/util/tSignal.h" +#endif +#ifndef _TERRFILE_H_ +#include "terrain/terrFile.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + + + +class GBitmap; +class TerrainBlock; +class TerrCell; +class PhysicsStatic; +class TerrainCellMaterial; + +class TerrainBlock : public SceneObject +{ + typedef SceneObject Parent; + + friend class TerrainEditor; + friend class TerrainCellMaterial; + +protected: + + enum + { + TransformMask = Parent::NextFreeMask, + FileMask = Parent::NextFreeMask << 1, + SizeMask = Parent::NextFreeMask << 2, + MaterialMask = Parent::NextFreeMask << 3, + HeightMapChangeMask = Parent::NextFreeMask << 4, + MiscMask = Parent::NextFreeMask << 5, + + NextFreeMask = Parent::NextFreeMask << 6, + }; + + Box3F mBounds; + + /// + GBitmap *mLightMap; + + /// The lightmap dimensions in pixels. + U32 mLightMapSize; + + /// The lightmap texture. + GFXTexHandle mLightMapTex; + + /// The terrain data file. + Resource mFile; + + /// The TerrainFile CRC sent from the server. + U32 mCRC; + + /// + FileName mTerrFileName; + + /// This is a old hack to fix a terrain performance + /// issue when first seeing a terrain from inside + /// of an interior. + bool mHasRendered; + + /// The maximum detail distance found in the material list. + F32 mMaxDetailDistance; + + /// + Vector mBaseTextures; + + /// + GFXTexHandle mLayerTex; + + /// The shader used to generate the base texture map. + GFXShaderRef mBaseShader; + + /// + GFXStateBlockRef mBaseShaderSB; + + /// + GFXShaderConstBufferRef mBaseShaderConsts; + + /// + GFXShaderConstHandle *mBaseTexScaleConst; + GFXShaderConstHandle *mBaseTexIdConst; + GFXShaderConstHandle *mBaseLayerSizeConst; + + /// + GFXTextureTargetRef mBaseTarget; + + /// The base texture. + GFXTexHandle mBaseTex; + + /// + bool mDetailsDirty; + + /// + bool mLayerTexDirty; + + /// The desired size for the base texture. + U32 mBaseTexSize; + + /// + TerrCell *mCell; + + /// The shared base material which is used to render + /// cells that are outside the detail map range. + TerrainCellMaterial *mBaseMaterial; + + /// A dummy material only used for shadow + /// material generation. + BaseMatInstance *mDefaultMatInst; + + F32 mSquareSize; + + bool mTile; + + PhysicsStatic *mPhysicsRep; + + U32 mScreenError; + + /// The shared primitive buffer used in rendering. + GFXPrimitiveBufferHandle mPrimBuffer; + + /// The cells used in the last render pass + /// when doing debug rendering. + /// @see _renderDebug + Vector mDebugCells; + + /// Set to enable debug rendering of the terrain. It + /// is exposed to the console via $terrain::debugRender. + static bool smDebugRender; + + /// A global LOD scale used to tweak the default + /// terrain screen error value. + static F32 smLODScale; + + /// A global detail scale used to tweak the + /// material detail distances. + static F32 smDetailScale; + + String _getBaseTexCacheFileName() const; + + void _rebuildQuadtree(); + + void _renderBlock( SceneState *state ); + void _renderDebug( ObjectRenderInst *ri, SceneState *state, BaseMatInstance *overrideMat ); + + /// The callback used to get texture events. + /// @see GFXTextureManager::addEventDelegate + void _onTextureEvent( GFXTexCallbackCode code ); + + /// Used to release terrain materials when + /// the light manager is changed. + /// @see LightManager::smActivateSignal + void _onLMActivate( const char *lm, bool activate ); + + /// + bool _initBaseShader(); + + /// + void _updateMaterials(); + + /// + void _updateBaseTexture( bool writeToCache ); + + void _updateLayerTexture(); + + void _updateBounds(); + + // Protected fields + static bool _setTerrainFile( void *object, const char *data ); + static bool _setSquareSize( void *object, const char *data ); + static bool _setBaseTexSize( void *object, const char *data ); + +public: + + enum + { + LightmapUpdate = BIT(0), + HeightmapUpdate = BIT(1), + LayersUpdate = BIT(2), + EmptyUpdate = BIT(3) + }; + + static Signal smUpdateSignal; + + /// + bool import( const GBitmap &heightMap, + F32 heightScale, + F32 metersPerPixel, + const Vector &layerMap, + const Vector &materials ); + +#ifdef TORQUE_TOOLS + bool exportHeightMap( const UTF8 *filePath, const String &format ) const; + bool exportLayerMaps( const UTF8 *filePrefix, const String &format ) const; +#endif + +public: + + TerrainBlock(); + virtual ~TerrainBlock(); + + U32 getCRC() const { return(mCRC); } + + Resource getFile() const { return mFile; }; + + bool onAdd(); + void onRemove(); + + void onEditorEnable(); + void onEditorDisable(); + + /// Adds a new material as the top layer or + /// inserts it at the specified index. + void addMaterial( const String &name, U32 insertAt = -1 ); + + /// Removes the material at the index. + void removeMaterial( U32 index ); + + /// Updates the material at the index. + void updateMaterial( U32 index, const String &name ); + + /// Deletes all the materials on the terrain. + void deleteAllMaterials(); + + //void setMaterialName( U32 index, const String &name ); + + TerrainMaterial* getMaterial( U32 index ) const; + + const char* getMaterialName( U32 index ) const; + + U32 getMaterialCount() const; + + //BaseMatInstance* getMaterialInst( U32 x, U32 y ); + + void setHeight( const Point2I &pos, F32 height ); + + // Performs an update to the selected range of the terrain + // grid including the collision and rendering structures. + void updateGrid( const Point2I &minPt, + const Point2I &maxPt, + bool updateClient = false ); + + void updateGridMaterials( const Point2I &minPt, const Point2I &maxPt ); + + Point2I getGridPos( const Point3F &worldPos ) const; + + /// This returns true and the terrain z height for + /// a 2d position in the terrains object space. + /// + /// If the terrain at that point is within an empty block + /// or the 2d position is outside of the terrain area then + /// it returns false. + /// + bool getHeight( const Point2F &pos, F32 *height ) const; + + void getMinMaxHeight( F32 *minHeight, F32 *maxHeight ) const; + + /// This returns true and the terrain normal for a + /// 2d position in the terrains object space. + /// + /// If the terrain at that point is within an empty block + /// or the 2d position is outside of the terrain area then + /// it returns false. + /// + bool getNormal( const Point2F &pos, + Point3F *normal, + bool normalize = true, + bool skipEmpty = true ) const; + + /// This returns true and the terrain normal and z height + /// for a 2d position in the terrains object space. + /// + /// If the terrain at that point is within an empty block + /// or the 2d position is outside of the terrain area then + /// it returns false. + /// + bool getNormalAndHeight( const Point2F &pos, + Point3F *normal, + F32 *height, + bool normalize = true ) const; + + /// This returns true and the terrain normal, z height, and + /// material index for a 2d position in the terrains object + /// space. + /// + /// If the terrain at that point is within an empty block + /// or the 2d position is outside of the terrain area then + /// it returns false. + /// + bool getNormalHeightMaterial( const Point2F &pos, + Point3F *normal, + F32 *height, + U8 *matIndex ) const; + + // only the editor currently uses this method - should always be using a ray to collide with + bool collideBox( const Point3F &start, const Point3F &end, RayInfo* info ) + { + return castRay( start, end, info ); + } + + /// + void setLightMap( GBitmap *newLightMap ); + + /// Fills the lightmap with white. + void clearLightMap(); + + /// Retuns the dimensions of the light map. + U32 getLightMapSize() const { return mLightMapSize; } + + const GBitmap* getLightMap() const { return mLightMap; } + + GBitmap* getLightMap() { return mLightMap; } + + /// + GFXTextureObject* getLightMapTex(); + +public: + + void setFile( const FileName& terrFileName ); + + void setFile( Resource file ); + + bool save(const char* filename); + + F32 getSquareSize() const { return mSquareSize; } + + bool isTiling() const { return mTile; } + + /// Returns the dimensions of the terrain in world space. + F32 getWorldBlockSize() const { return mSquareSize * (F32)mFile->mSize; } + + /// Retuns the dimensions of the terrain in samples. + U32 getBlockSize() const { return mFile->mSize; } + + U32 getScreenError() const { return smLODScale * mScreenError; } + + // SceneObject + void setTransform( const MatrixF &mat ); + void setScale( const VectorF &scale ); + + bool prepRenderImage ( SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState=false); + + void buildConvex(const Box3F& box,Convex* convex); + bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere); + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + bool castRayI(const Point3F &start, const Point3F &end, RayInfo* info, bool emptyCollide); + + bool castRayBlock( const Point3F &pStart, + const Point3F &pEnd, + const Point2I &blockPos, + U32 level, + F32 invDeltaX, + F32 invDeltaY, + F32 startT, + F32 endT, + RayInfo *info, + bool collideEmpty ); + + const FileName& getTerrainFile() const { return mTerrFileName; } + + void postLight(Vector &terrBlocks) {}; + + + DECLARE_CONOBJECT(TerrainBlock); + static void initPersistFields(); + U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + void inspectPostApply(); +}; + +#endif // _TERRDATA_H_ diff --git a/terrain/terrExport.cpp b/terrain/terrExport.cpp new file mode 100644 index 0000000..7d1f5f8 --- /dev/null +++ b/terrain/terrExport.cpp @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrData.h" +#include "gfx/bitmap/gBitmap.h" +#include "terrain/terrMaterial.h" +#include "core/stream/fileStream.h" + +#ifdef TORQUE_TOOLS + +bool TerrainBlock::exportHeightMap( const UTF8 *filePath, const String &format ) const +{ + + GBitmap output( mFile->mSize, + mFile->mSize, + false, + GFXFormatR5G6B5 ); + + // First capture the max height... we'll normalize + // everything to this value. + U16 maxHeight = 0; + + Vector::iterator iBits = mFile->mHeightMap.begin(); + for ( S32 y = 0; y < mFile->mSize; y++ ) + { + for ( S32 x = 0; x < mFile->mSize; x++ ) + { + if ( *iBits > maxHeight ) + maxHeight = *iBits; + ++iBits; + } + } + + // Now write out the map. + iBits = mFile->mHeightMap.begin(); + U16 *oBits = (U16*)output.getWritableBits(); + for ( S32 y = 0; y < mFile->mSize; y++ ) + { + for ( S32 x = 0; x < mFile->mSize; x++ ) + { + // PNG expects big endian. + U16 height = (U16)( ( (F32)(*iBits) / (F32)maxHeight ) * (F32)U16_MAX ); + *oBits = convertHostToBEndian( height ); + ++oBits; + ++iBits; + } + } + + FileStream stream; + if ( !stream.open( filePath, Torque::FS::File::Write ) ) + { + Con::errorf( "TerrainBlock::exportHeightMap() - Error opening file for writing: %s !", filePath ); + return false; + } + + if ( !output.writeBitmap( format, stream ) ) + { + Con::errorf( "TerrainBlock::exportHeightMap() - Error writing %s: %s !", format.c_str(), filePath ); + return false; + } + + // Print out the map size in meters, so that the user + // knows what values to use when importing it into + // another terrain tool. + S32 dim = mSquareSize * mFile->mSize; + S32 height = fixedToFloat( maxHeight ); + Con::printf( "Saved heightmap with dimensions %d x %d x %d.", dim, dim, height ); + + return true; +} + +bool TerrainBlock::exportLayerMaps( const UTF8 *filePrefix, const String &format ) const +{ + for(S32 i = 0; i < mFile->mMaterials.size(); i++) + { + Vector::iterator iBits = mFile->mLayerMap.begin(); + + GBitmap output( mFile->mSize, + mFile->mSize, + false, + GFXFormatA8 ); + + // Copy the layer data. + U8 *oBits = (U8*)output.getWritableBits(); + dMemset( oBits, 0, mFile->mSize * mFile->mSize ); + + for ( S32 y = 0; y < mFile->mSize; y++ ) + { + for ( S32 x = 0; x < mFile->mSize; x++ ) + { + if(*iBits == i) + *oBits = 0xFF; + ++iBits; + ++oBits; + } + } + + // Whats the full file name for this layer. + UTF8 filePath[1024]; + dSprintf( filePath, 1024, "%s_%d_%s.%s", filePrefix, i, mFile->mMaterials[i]->getInternalName(), format.c_str() ); + + FileStream stream; + if ( !stream.open( filePath, Torque::FS::File::Write ) ) + { + Con::errorf( "TerrainBlock::exportLayerMaps() - Error opening file for writing: %s !", filePath ); + return false; + } + + if ( !output.writeBitmap( format, stream ) ) + { + Con::errorf( "TerrainBlock::exportLayerMaps() - Error writing %s: %s !", format.c_str(), filePath ); + return false; + } + } + + return true; +} + +ConsoleMethod( TerrainBlock, exportHeightMap, bool, 3, 4, "(string filename, [string format]) - export the terrain block's heightmap to a bitmap file (default: png)" ) +{ + UTF8 fileName[1024]; + String format = "png"; + if( argc > 3 ) + format = argv[ 3 ]; + + Con::expandScriptFilename( fileName, sizeof( fileName ), argv[2] ); + + return object->exportHeightMap( fileName, format ); +} + +ConsoleMethod( TerrainBlock, exportLayerMaps, bool, 3, 4, "(string filePrefix, [string format]) - export the terrain block's layer maps to bitmap files (default: png)" ) +{ + UTF8 filePrefix[1024]; + String format = "png"; + if( argc > 3 ) + format = argv[3]; + + Con::expandScriptFilename( filePrefix, sizeof( filePrefix ), argv[2] ); + + return object->exportLayerMaps( filePrefix, format ); +} +#endif diff --git a/terrain/terrFeatureTypes.cpp b/terrain/terrFeatureTypes.cpp new file mode 100644 index 0000000..61987ec --- /dev/null +++ b/terrain/terrFeatureTypes.cpp @@ -0,0 +1,20 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrFeatureTypes.h" + +#include "materials/materialFeatureTypes.h" + + +ImplementFeatureType( MFT_TerrainEmpty, MFG_PostTransform, 1.0f, true ); +ImplementFeatureType( MFT_TerrainBaseMap, MFG_Texture, 100.0f, true ); +ImplementFeatureType( MFT_TerrainParallaxMap, MFG_Texture, 101.0f, true ); +ImplementFeatureType( MFT_TerrainDetailMap, MFG_Texture, 102.0f, true ); +ImplementFeatureType( MFT_TerrainNormalMap, MFG_Texture, 103.0f, true ); +ImplementFeatureType( MFT_TerrainLightMap, MFG_Texture, 104.0f, true ); +ImplementFeatureType( MFT_TerrainSideProject, MFG_Texture, 105.0f, true ); +ImplementFeatureType( MFT_TerrainAdditive, MFG_PostProcess, 999.0f, true ); + diff --git a/terrain/terrFeatureTypes.h b/terrain/terrFeatureTypes.h new file mode 100644 index 0000000..5986c56 --- /dev/null +++ b/terrain/terrFeatureTypes.h @@ -0,0 +1,23 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRFEATURETYPES_H_ +#define _TERRFEATURETYPES_H_ + +#ifndef _FEATURETYPE_H_ +#include "shaderGen/featureType.h" +#endif + +DeclareFeatureType( MFT_TerrainEmpty ); +DeclareFeatureType( MFT_TerrainBaseMap ); +DeclareFeatureType( MFT_TerrainDetailMap ); +DeclareFeatureType( MFT_TerrainNormalMap ); +DeclareFeatureType( MFT_TerrainParallaxMap ); +DeclareFeatureType( MFT_TerrainLightMap ); +DeclareFeatureType( MFT_TerrainSideProject ); +DeclareFeatureType( MFT_TerrainAdditive ); + +#endif // _TERRFEATURETYPES_H_ + diff --git a/terrain/terrFile.cpp b/terrain/terrFile.cpp new file mode 100644 index 0000000..dbeb577 --- /dev/null +++ b/terrain/terrFile.cpp @@ -0,0 +1,857 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrFile.h" + +#include "core/stream/fileStream.h" +#include "core/resourceManager.h" +#include "terrain/terrMaterial.h" +#include "gfx/gfxTextureHandle.h" +#include "gfx/bitmap/gBitmap.h" +#include "platform/profiler.h" +#include "math/mPlane.h" + + +template<> +void* Resource::create( const Torque::Path &path ) +{ + return TerrainFile::load( path ); +} + +template<> ResourceBase::Signature Resource::signature() +{ + return MakeFourCC('t','e','r','d'); +} + + +TerrainFile::TerrainFile() + : mNeedsResaving( false ), + mFileVersion( FILE_VERSION ), + mSize( 256 ) +{ + mLayerMap.setSize( mSize * mSize ); + dMemset( mLayerMap.address(), 0, mLayerMap.memSize() ); + + mHeightMap.setSize( mSize * mSize ); + dMemset( mHeightMap.address(), 0, mHeightMap.memSize() ); +} + +TerrainFile::~TerrainFile() +{ +} + +static U16 calcDev( const PlaneF &pl, const Point3F &pt ) +{ + F32 z = (pl.d + pl.x * pt.x + pl.y * pt.y) / -pl.z; + F32 diff = z - pt.z; + if(diff < 0.0f) + diff = -diff; + + if(diff > 0xFFFF) + return 0xFFFF; + else + return U16(diff); +} + +static U16 Umax( U16 u1, U16 u2 ) +{ + return u1 > u2 ? u1 : u2; +} + + +inline U32 getMostSignificantBit( U32 v ) +{ + U32 bit = 0; + + while ( v >>= 1 ) + bit++; + + return bit; +} + +void TerrainFile::_buildGridMap() +{ + // The grid level count is the same as the + // most significant bit of the size. While + // we loop we take the time to calculate the + // grid memory pool size. + mGridLevels = 0; + U32 size = mSize; + U32 poolSize = size * size; + while ( size >>= 1 ) + { + poolSize += size * size; + mGridLevels++; + } + + mGridMapPool.setSize( poolSize ); + mGridMapPool.compact(); + mGridMap.setSize( mGridLevels + 1 ); + mGridMap.compact(); + + // Assign memory from the pool to each grid level. + TerrainSquare *sq = mGridMapPool.address(); + for ( S32 i = mGridLevels; i >= 0; i-- ) + { + mGridMap[i] = sq; + sq += 1 << ( 2 * ( mGridLevels - i ) ); + } + + for( S32 i = mGridLevels; i >= 0; i-- ) + { + S32 squareCount = 1 << ( mGridLevels - i ); + S32 squareSize = mSize / squareCount; + + for ( S32 squareX = 0; squareX < squareCount; squareX++ ) + { + for ( S32 squareY = 0; squareY < squareCount; squareY++ ) + { + U16 min = 0xFFFF; + U16 max = 0; + U16 mindev45 = 0; + U16 mindev135 = 0; + + // determine max error for both possible splits. + + const Point3F p1(0, 0, getHeight(squareX * squareSize, squareY * squareSize)); + const Point3F p2(0, (F32)squareSize, getHeight(squareX * squareSize, squareY * squareSize + squareSize)); + const Point3F p3((F32)squareSize, (F32)squareSize, getHeight(squareX * squareSize + squareSize, squareY * squareSize + squareSize)); + const Point3F p4((F32)squareSize, 0, getHeight(squareX * squareSize + squareSize, squareY * squareSize)); + + // pl1, pl2 = split45, pl3, pl4 = split135 + const PlaneF pl1(p1, p2, p3); + const PlaneF pl2(p1, p3, p4); + const PlaneF pl3(p1, p2, p4); + const PlaneF pl4(p2, p3, p4); + + bool parentSplit45 = false; + TerrainSquare *parent = NULL; + if ( i < mGridLevels ) + { + parent = findSquare( i+1, squareX * squareSize, squareY * squareSize ); + parentSplit45 = parent->flags & TerrainSquare::Split45; + } + + bool empty = true; + bool hasEmpty = false; + + for ( S32 sizeX = 0; sizeX <= squareSize; sizeX++ ) + { + for ( S32 sizeY = 0; sizeY <= squareSize; sizeY++ ) + { + S32 x = squareX * squareSize + sizeX; + S32 y = squareY * squareSize + sizeY; + + if(sizeX != squareSize && sizeY != squareSize) + { + if ( !isEmptyAt( x, y ) ) + empty = false; + else + hasEmpty = true; + } + + U16 ht = getHeight( x, y ); + if ( ht < min ) + min = ht; + if( ht > max ) + max = ht; + + Point3F pt( (F32)sizeX, (F32)sizeY, (F32)ht ); + U16 dev; + + if(sizeX < sizeY) + dev = calcDev(pl1, pt); + else if(sizeX > sizeY) + dev = calcDev(pl2, pt); + else + dev = Umax(calcDev(pl1, pt), calcDev(pl2, pt)); + + if(dev > mindev45) + mindev45 = dev; + + if(sizeX + sizeY < squareSize) + dev = calcDev(pl3, pt); + else if(sizeX + sizeY > squareSize) + dev = calcDev(pl4, pt); + else + dev = Umax(calcDev(pl3, pt), calcDev(pl4, pt)); + + if(dev > mindev135) + mindev135 = dev; + } + } + + TerrainSquare *sq = findSquare( i, squareX * squareSize, squareY * squareSize ); + sq->minHeight = min; + sq->maxHeight = max; + + sq->flags = empty ? TerrainSquare::Empty : 0; + if ( hasEmpty ) + sq->flags |= TerrainSquare::HasEmpty; + + bool shouldSplit45 = ((squareX ^ squareY) & 1) == 0; + bool split45; + + //split45 = shouldSplit45; + if ( i == 0 ) + split45 = shouldSplit45; + else if( i < 4 && shouldSplit45 == parentSplit45 ) + split45 = shouldSplit45; + else + split45 = mindev45 < mindev135; + + //split45 = shouldSplit45; + if(split45) + { + sq->flags |= TerrainSquare::Split45; + sq->heightDeviance = mindev45; + } + else + sq->heightDeviance = mindev135; + + if( parent ) + if ( parent->heightDeviance < sq->heightDeviance ) + parent->heightDeviance = sq->heightDeviance; + } + } + } + + /* + for ( S32 y = 0; y < mSize; y += 2 ) + { + for ( S32 x=0; x < mSize; x += 2 ) + { + GridSquare *sq = findSquare(1, Point2I(x, y)); + GridSquare *s1 = findSquare(0, Point2I(x, y)); + GridSquare *s2 = findSquare(0, Point2I(x+1, y)); + GridSquare *s3 = findSquare(0, Point2I(x, y+1)); + GridSquare *s4 = findSquare(0, Point2I(x+1, y+1)); + sq->flags |= (s1->flags | s2->flags | s3->flags | s4->flags) & ~(GridSquare::MaterialStart -1); + } + } + */ +} + +void TerrainFile::_initMaterialInstMapping() +{ + mMaterialInstMapping.clearMatInstList(); + + for( U32 i = 0; i < mMaterials.size(); ++ i ) + { + Torque::Path path( mMaterials[ i ]->getDiffuseMap() ); + mMaterialInstMapping.push_back( path.getFileName() ); + } + + mMaterialInstMapping.mapMaterials(); +} + +bool TerrainFile::save( const char *filename ) +{ + FileStream stream; + stream.open( filename, Torque::FS::File::Write ); + if ( stream.getStatus() != Stream::Ok ) + return false; + + stream.write( (U8)FILE_VERSION ); + + stream.write( mSize ); + + // Write out the height map. + for ( U32 i=0; i < mHeightMap.size(); i++) + stream.write( mHeightMap[i] ); + + // Write out the layer map. + for ( U32 i=0; i < mLayerMap.size(); i++) + stream.write( mLayerMap[i] ); + + // Write out the material names. + stream.write( (U32)mMaterials.size() ); + for ( U32 i=0; i < mMaterials.size(); i++ ) + stream.write( String( mMaterials[i]->getInternalName() ) ); + + return stream.getStatus() == FileStream::Ok; +} + +TerrainFile* TerrainFile::load( const Torque::Path &path ) +{ + FileStream stream; + + stream.open( path.getFullPath(), Torque::FS::File::Read ); + if ( stream.getStatus() != Stream::Ok ) + { + Con::errorf( "Resource::create - could not open '%s'", path.getFullPath().c_str() ); + return NULL; + } + + U8 version; + stream.read(&version); + if (version > TerrainFile::FILE_VERSION) + { + Con::errorf( "Resource::create - file version '%i' is newer than engine version '%i'", version, TerrainFile::FILE_VERSION ); + return NULL; + } + + TerrainFile *ret = new TerrainFile; + ret->mFileVersion = version; + ret->mFilePath = path; + + if ( version >= 7 ) + ret->_load( stream ); + else + ret->_loadLegacy( stream ); + + // Update the collision structures. + ret->_buildGridMap(); + + // Do the material mapping. + ret->_initMaterialInstMapping(); + + return ret; +} + +void TerrainFile::_load( FileStream &stream ) +{ + // NOTE: We read using a loop instad of in one large chunk + // because the stream will do endian conversions for us when + // reading one type at a time. + + stream.read( &mSize ); + + // Load the heightmap. + mHeightMap.setSize( mSize * mSize ); + for ( U32 i=0; i < mHeightMap.size(); i++ ) + stream.read( &mHeightMap[i] ); + + // Load the layer index map. + mLayerMap.setSize( mSize * mSize ); + for ( U32 i=0; i < mLayerMap.size(); i++ ) + stream.read( &mLayerMap[i] ); + + // Get the material name count. + U32 materialCount; + stream.read( &materialCount ); + Vector materials; + materials.setSize( materialCount ); + + // Load the material names. + for ( U32 i=0; i < materialCount; i++ ) + stream.read( &materials[i] ); + + // Resolve the TerrainMaterial objects from the names. + _resolveMaterials( materials ); +} + +void TerrainFile::_loadLegacy( FileStream &stream ) +{ + // Some legacy constants. + enum + { + MaterialGroups = 8, + BlockSquareWidth = 256, + }; + + const U32 sampleCount = BlockSquareWidth * BlockSquareWidth; + mSize = BlockSquareWidth; + + // Load the heightmap. + mHeightMap.setSize( sampleCount ); + for ( U32 i=0; i < mHeightMap.size(); i++ ) + stream.read( &mHeightMap[i] ); + + // Prior to version 7 we stored this weird material struct. + const U32 MATERIAL_GROUP_MASK = 0x7; + struct Material + { + enum Flags + { + Plain = 0, + Rotate = 1, + FlipX = 2, + FlipXRotate = 3, + FlipY = 4, + FlipYRotate = 5, + FlipXY = 6, + FlipXYRotate = 7, + RotateMask = 7, + Empty = 8, + Modified = BIT(7), + + // must not clobber TerrainFile::MATERIAL_GROUP_MASK bits! + PersistMask = BIT(7) + }; + + U8 flags; + U8 index; + }; + + // Temp locals for loading before we convert to the new + // version 7+ format. + U8 baseMaterialMap[sampleCount] = { 0 }; + U8 *materialAlphaMap[MaterialGroups] = { 0 }; + Material materialMap[BlockSquareWidth * BlockSquareWidth]; + + // read the material group map and flags... + dMemset(materialMap, 0, sizeof(materialMap)); + + AssertFatal(!(Material::PersistMask & MATERIAL_GROUP_MASK), + "Doh! We have flag clobberage..."); + + for (S32 j=0; j < sampleCount; j++) + { + U8 val; + stream.read(&val); + + // + baseMaterialMap[j] = val & MATERIAL_GROUP_MASK; + materialMap[j].flags = val & Material::PersistMask; + } + + // Load the material names. + Vector materials; + for ( U32 i=0; i < MaterialGroups; i++ ) + { + String matName; + stream.read( &matName ); + if ( matName.isEmpty() ) + continue; + + if ( mFileVersion > 3 && mFileVersion < 6 ) + { + // Between version 3 and 5 we store the texture file names + // relative to the terrain file. We restore the full path + // here so that we can create a TerrainMaterial from it. + materials.push_back( Torque::Path::CompressPath( mFilePath.getRoot() + mFilePath.getPath() + '/' + matName ) ); + } + else + materials.push_back( matName ); + } + + if ( mFileVersion <= 3 ) + { + GFXTexHandle terrainMat; + Torque::Path matRelPath; + + // Try to automatically fix up our material file names + for (U32 i = 0; i < materials.size(); i++) + { + if ( materials[i].isEmpty() ) + continue; + + terrainMat.set( materials[i], &GFXDefaultPersistentProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); + if ( terrainMat ) + continue; + + matRelPath = materials[i]; + + String path = matRelPath.getPath(); + + String::SizeType n = path.find( '/', 0, String::NoCase ); + if ( n != String::NPos ) + { + matRelPath.setPath( String(Con::getVariable( "$defaultGame" )) + path.substr( n, path.length() - n ) ); + + terrainMat.set( matRelPath, &GFXDefaultPersistentProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); + if ( terrainMat ) + { + materials[i] = matRelPath.getFullPath(); + mNeedsResaving = true; + } + } + + } // for (U32 i = 0; i < TerrainBlock::MaterialGroups; i++) + + } // if ( mFileVersion <= 3 ) + + if ( mFileVersion == 1 ) + { + for( S32 j = 0; j < sampleCount; j++ ) + { + if ( materialAlphaMap[baseMaterialMap[j]] == NULL ) + { + materialAlphaMap[baseMaterialMap[j]] = new U8[sampleCount]; + dMemset(materialAlphaMap[baseMaterialMap[j]], 0, sampleCount); + } + + materialAlphaMap[baseMaterialMap[j]][j] = 255; + } + } + else + { + for( S32 k=0; k < materials.size(); k++ ) + { + AssertFatal(materialAlphaMap[k] == NULL, "Bad assumption. There should be no alpha map at this point..."); + materialAlphaMap[k] = new U8[sampleCount]; + stream.read(sampleCount, materialAlphaMap[k]); + } + } + + // Throw away the old texture and heightfield scripts. + if ( mFileVersion >= 3 ) + { + U32 len; + stream.read(&len); + char *textureScript = (char *)dMalloc(len + 1); + stream.read(len, textureScript); + dFree( textureScript ); + + stream.read(&len); + char *heightfieldScript = (char *)dMalloc(len + 1); + stream.read(len, heightfieldScript); + dFree( heightfieldScript ); + } + + // Load and throw away the old edge terrain paths. + if ( mFileVersion >= 5 ) + { + stream.readSTString(true); + stream.readSTString(true); + } + + U32 layerCount = materials.size() - 1; + + // Ok... time to convert all this mess to the layer index map! + for ( U32 i=0; i < sampleCount; i++ ) + { + // Find the greatest layer. + U32 layer = 0; + U32 lastValue = 0; + for ( U32 k=0; k < MaterialGroups; k++ ) + { + if ( materialAlphaMap[k] && materialAlphaMap[k][i] > lastValue ) + { + layer = k; + lastValue = materialAlphaMap[k][i]; + } + } + + // Set the layer index. + mLayerMap[i] = getMin( layer, layerCount ); + } + + // Cleanup. + for ( U32 i=0; i < MaterialGroups; i++ ) + delete [] materialAlphaMap[i]; + + // Force resaving on these old file versions. + //mNeedsResaving = false; + + // Resolve the TerrainMaterial objects from the names. + _resolveMaterials( materials ); +} + +void TerrainFile::_resolveMaterials( const Vector &materials ) +{ + mMaterials.clear(); + + for ( U32 i=0; i < materials.size(); i++ ) + mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) ); + + // If we didn't get any materials then at least + // add a warning material so we will render. + if ( mMaterials.empty() ) + mMaterials.push_back( TerrainMaterial::getWarningMaterial() ); +} + +void TerrainFile::setSize( U32 newSize, bool clear ) +{ + // Make sure the resolution is a power of two. + newSize = getNextPow2( newSize ); + + // + if ( clear ) + { + mLayerMap.setSize( newSize * newSize ); + mLayerMap.compact(); + dMemset( mLayerMap.address(), 0, mLayerMap.memSize() ); + + // Initialize the elevation to something above + // zero so that we have room to excavate by default. + U16 elev = floatToFixed( 512.0f ); + + mHeightMap.setSize( newSize * newSize ); + mHeightMap.compact(); + for ( U32 i = 0; i < mHeightMap.size(); i++ ) + mHeightMap[i] = elev; + } + else + { + // We're resizing here! + + + + } + + mSize = newSize; + + _buildGridMap(); +} + +void TerrainFile::smooth( F32 factor, U32 steps, bool updateCollision ) +{ + const U32 blockSize = mSize * mSize; + + // Grab some temp buffers for our smoothing results. + Vector h1, h2; + h1.setSize( blockSize ); + h2.setSize( blockSize ); + + // Fill the first buffer with the current heights. + for ( U32 i=0; i < blockSize; i++ ) + h1[i] = (F32)mHeightMap[i]; + + // factor of 0.0 = NO Smoothing + // factor of 1.0 = MAX Smoothing + const F32 matrixM = 1.0f - getMax(0.0f, getMin(1.0f, factor)); + const F32 matrixE = (1.0f-matrixM) * (1.0f/12.0f) * 2.0f; + const F32 matrixC = matrixE * 0.5f; + + // Now loop for our interations. + F32 *src = h1.address(); + F32 *dst = h2.address(); + for ( U32 s=0; s < steps; s++ ) + { + for ( S32 y=0; y < mSize; y++ ) + { + for ( S32 x=0; x < mSize; x++ ) + { + F32 samples[9]; + + S32 c = 0; + for (S32 i = y-1; i < y+2; i++) + for (S32 j = x-1; j < x+2; j++) + { + if ( i < 0 || j < 0 || i >= mSize || j >= mSize ) + samples[c++] = src[ x + ( y * mSize ) ]; + else + samples[c++] = src[ j + ( i * mSize ) ]; + } + + // 0 1 2 + // 3 x,y 5 + // 6 7 8 + + dst[ x + ( y * mSize ) ] = + ((samples[0]+samples[2]+samples[6]+samples[8]) * matrixC) + + ((samples[1]+samples[3]+samples[5]+samples[7]) * matrixE) + + (samples[4] * matrixM); + } + } + + // Swap! + F32 *tmp = dst; + dst = src; + src = tmp; + } + + // Copy the results back to the height map. + for ( U32 i=0; i < blockSize; i++ ) + mHeightMap[i] = (U16)mCeil( (F32)src[i] ); + + if ( updateCollision ) + _buildGridMap(); +} + +void TerrainFile::setHeightMap( const Vector &heightmap, bool updateCollision ) +{ + AssertFatal( mHeightMap.size() == heightmap.size(), "TerrainFile::setHeightMap - Incorrect heightmap size!" ); + dMemcpy( mHeightMap.address(), heightmap.address(), mHeightMap.size() ); + + if ( updateCollision ) + _buildGridMap(); +} + +void TerrainFile::import( const GBitmap &heightMap, + F32 heightScale, + const Vector &layerMap, + const Vector &materials ) +{ + AssertFatal( heightMap.getWidth() == heightMap.getHeight(), "TerrainFile::import - Height map is not square!" ); + AssertFatal( isPow2( heightMap.getWidth() ), "TerrainFile::import - Height map is not power of two!" ); + + const U32 newSize = heightMap.getWidth(); + if ( newSize != mSize ) + { + mHeightMap.setSize( newSize * newSize ); + mHeightMap.compact(); + mSize = newSize; + } + + // Convert the height map to heights. + U16 *oBits = mHeightMap.address(); + if ( heightMap.getFormat() == GFXFormatR5G6B5 ) + { + const F32 toFixedPoint = ( 1.0f / (F32)U16_MAX ) * floatToFixed( heightScale ); + const U16 *iBits = (const U16*)heightMap.getBits(); + for ( U32 i = 0; i < mSize * mSize; i++ ) + { + U16 height = convertBEndianToHost( *iBits ); + *oBits = (U16)mCeil( (F32)height * toFixedPoint ); + ++oBits; + ++iBits; + } + } + else + { + const F32 toFixedPoint = ( 1.0f / (F32)U8_MAX ) * floatToFixed( heightScale ); + const U8 *iBits = heightMap.getBits(); + for ( U32 i = 0; i < mSize * mSize; i++ ) + { + *oBits = (U16)mCeil( ((F32)*iBits) * toFixedPoint ); + ++oBits; + iBits += heightMap.getBytesPerPixel(); + } + } + + // Copy over the layer map. + AssertFatal( layerMap.size() == mHeightMap.size(), "TerrainFile::import - Layer map is the wrong size!" ); + mLayerMap = layerMap; + mLayerMap.compact(); + + // Resolve the materials. + _resolveMaterials( materials ); + + // Rebuild the collision grid map. + _buildGridMap(); +} + + +void TerrainFile::create( String *inOutFilename, + U32 newSize, + const Vector &materials ) +{ + TerrainFile *file = new TerrainFile; + + for ( U32 i=0; i < materials.size(); i++ ) + file->mMaterials.push_back( TerrainMaterial::findOrCreate( materials[i] ) ); + + file->setSize( newSize, true ); + + // We need to construct a default file name + char fileName[1024]; + fileName[0] = 0; + + // See if we know our current mission name + char missionName[1024]; + dSprintf( missionName, sizeof(missionName), "%s\0", inOutFilename->c_str() ); + char * dot = dStrstr((const char*)missionName, ".mis"); + if(dot) + *dot = '\0'; + + // Find the first file that doesn't exist + for (U32 i = 0; i < 32; i++) + { + char testName[1024]; + + if (dStrlen(missionName) == 0 || dStricmp(missionName, "0") == 0) + dSprintf(testName, sizeof(testName), "levels/terrain_%d.ter\0", i ); + else + dSprintf( testName, sizeof(testName), "%s_%d.ter\0", missionName, i ); + + char fullName[1024]; + Platform::makeFullPathName(testName, fullName, sizeof(fullName)); + + if ( !Platform::isFile( fullName ) ) + { + dSprintf(fileName, sizeof(fileName), "%s", fullName); + break; + } + } + + // If we found a valid file name then save it out + if ( dStrlen(fileName) > 0 ) + file->save( fileName ); + + (*inOutFilename) = fileName; + delete file; +} + +inline void getMinMax( U16 &inMin, U16 &inMax, U16 height ) +{ + if ( height < inMin ) + inMin = height; + if ( height > inMax ) + inMax = height; +} + +inline void checkSquare( TerrainSquare *parent, const TerrainSquare *child ) +{ + if(parent->minHeight > child->minHeight) + parent->minHeight = child->minHeight; + if(parent->maxHeight < child->maxHeight) + parent->maxHeight = child->maxHeight; + + if ( child->flags & (TerrainSquare::Empty | TerrainSquare::HasEmpty) ) + parent->flags |= TerrainSquare::HasEmpty; +} + +void TerrainFile::updateGrid( const Point2I &minPt, const Point2I &maxPt ) +{ + // here's how it works: + // for the current terrain renderer we only care about + // the minHeight and maxHeight on the GridSquare + // so we do one pass through, updating minHeight and maxHeight + // on the level 0 squares, then we loop up the grid map from 1 to + // the top, expanding the bounding boxes as necessary. + // this should end up being way, way, way, way faster for the terrain + // editor + + PROFILE_SCOPE( TerrainFile_UpdateGrid ); + + for ( S32 y = minPt.y - 1; y < maxPt.y + 1; y++ ) + { + for ( S32 x = minPt.x - 1; x < maxPt.x + 1; x++ ) + { + S32 px = x; + S32 py = y; + if ( px < 0 ) + px += mSize; + if ( py < 0 ) + py += mSize; + + TerrainSquare *sq = findSquare( 0, px, py ); + sq->minHeight = 0xFFFF; + sq->maxHeight = 0; + + // Update the empty state. + if ( isEmptyAt( x, y ) ) + sq->flags |= TerrainSquare::Empty; + else + sq->flags &= ~TerrainSquare::Empty; + + getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y ) ); + getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y ) ); + getMinMax( sq->minHeight, sq->maxHeight, getHeight( x, y+1 ) ); + getMinMax( sq->minHeight, sq->maxHeight, getHeight( x+1, y+1 ) ); + } + } + + // ok, all the level 0 grid squares are updated: + // now update all the parent grid squares that need to be updated: + for( S32 level = 1; level <= mGridLevels; level++ ) + { + S32 size = 1 << level; + S32 halfSize = size >> 1; + + for( S32 y = (minPt.y - 1) >> level; y < (maxPt.y + size) >> level; y++ ) + { + for ( S32 x = (minPt.x - 1) >> level; x < (maxPt.x + size) >> level; x++ ) + { + S32 px = x << level; + S32 py = y << level; + + TerrainSquare *sq = findSquare(level, px, py); + sq->minHeight = 0xFFFF; + sq->maxHeight = 0; + sq->flags &= ~( TerrainSquare::Empty | TerrainSquare::HasEmpty ); + + checkSquare( sq, findSquare( level - 1, px, py ) ); + checkSquare( sq, findSquare( level - 1, px + halfSize, py ) ); + checkSquare( sq, findSquare( level - 1, px, py + halfSize ) ); + checkSquare( sq, findSquare( level - 1, px + halfSize, py + halfSize ) ); + } + } + } +} diff --git a/terrain/terrFile.h b/terrain/terrFile.h new file mode 100644 index 0000000..c3bc639 --- /dev/null +++ b/terrain/terrFile.h @@ -0,0 +1,239 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRFILE_H_ +#define _TERRFILE_H_ + +#ifndef _TVECTOR_H_ +#include +#endif +#ifndef _PATH_H_ +#include +#endif +#ifndef _MATERIALLIST_H_ +#include "materials/materialList.h" +#endif + +class TerrainMaterial; +class FileStream; +class GBitmap; + + +/// +struct TerrainSquare +{ + U16 minHeight; + + U16 maxHeight; + + U16 heightDeviance; + + U16 flags; + + enum + { + Split45 = BIT(0), + + Empty = BIT(1), + + HasEmpty = BIT(2), + }; +}; + + +/// NOTE: The terrain uses 11.5 fixed point which gives +/// us a height range from 0->2048 in 1/32 increments. +typedef U16 TerrainHeight; + + +/// +class TerrainFile +{ +protected: + + friend class TerrainBlock; + + /// The materials used to render the terrain. + Vector mMaterials; + + /// The dimensions of the layer and height maps. + U32 mSize; + + /// The layer index at each height map sample. + Vector mLayerMap; + + /// The fixed point height map. + /// @see fixedToFloat + Vector mHeightMap; + + /// The memory pool used by the grid map layers. + Vector mGridMapPool; + + /// + U32 mGridLevels; + + /// The grid map layers used to accelerate collision + /// queries for the height map data. + Vector mGridMap; + + /// MaterialList used to map terrain materials to material instances for the + /// sake of collision (physics, etc.). + MaterialList mMaterialInstMapping; + + /// The file version. + U32 mFileVersion; + + /// The dirty flag. + bool mNeedsResaving; + + /// The full path and name of the TerrainFile + Torque::Path mFilePath; + + /// The internal loading function. + void _load( FileStream &stream ); + + /// The legacy file loading code. + void _loadLegacy( FileStream &stream ); + + /// Used to populate the materail vector by finding the + /// TerrainMaterial objects by name. + void _resolveMaterials( const Vector &materials ); + + /// + void _buildGridMap(); + + /// + void _initMaterialInstMapping(); + +public: + + enum Constants + { + FILE_VERSION = 7 + }; + + TerrainFile(); + + virtual ~TerrainFile(); + + /// + static void create( String *inOutFilename, + U32 newSize, + const Vector &materials ); + + /// + static TerrainFile* load( const Torque::Path &path ); + + bool save( const char *filename ); + + /// + void import( const GBitmap &heightMap, + F32 heightScale, + const Vector &layerMap, + const Vector &materials ); + + /// Updates the terrain grid for the specified area. + void updateGrid( const Point2I &minPt, const Point2I &maxPt ); + + /// Performs multiple smoothing steps on the heightmap. + void smooth( F32 factor, U32 steps, bool updateCollision ); + + void setSize( U32 newResolution, bool clear ); + + TerrainSquare* findSquare( U32 level, U32 x, U32 y ) const; + + BaseMatInstance* getMaterialMapping( U32 index ) const; + + void setLayerIndex( U32 x, U32 y, U8 index ); + + U8 getLayerIndex( U32 x, U32 y ) const; + + bool isEmptyAt( U32 x, U32 y ) const { return getLayerIndex( x, y ) == U8_MAX; } + + void setHeight( U32 x, U32 y, U16 height ); + + const U16* getHeightAddress( U32 x, U32 y ) const; + + U16 getHeight( U32 x, U32 y ) const; + + U16 getMaxHeight() const { return mGridMap[mGridLevels]->maxHeight; } + + /// Returns the constant heightmap vector. + const Vector& getHeightMap() const { return mHeightMap; } + + /// Sets a new heightmap state. + void setHeightMap( const Vector &heightmap, bool updateCollision ); + +}; + + +inline TerrainSquare* TerrainFile::findSquare( U32 level, U32 x, U32 y ) const +{ + x %= mSize; + y %= mSize; + x >>= level; + y >>= level; + + return mGridMap[level] + x + ( y << ( mGridLevels - level ) ); +} + +inline void TerrainFile::setHeight( U32 x, U32 y, U16 height ) +{ + x %= mSize; + y %= mSize; + mHeightMap[ x + ( y * mSize ) ] = height; +} + +inline const U16* TerrainFile::getHeightAddress( U32 x, U32 y ) const +{ + x %= mSize; + y %= mSize; + return &mHeightMap[ x + ( y * mSize ) ]; +} + +inline U16 TerrainFile::getHeight( U32 x, U32 y ) const +{ + x %= mSize; + y %= mSize; + return mHeightMap[ x + ( y * mSize ) ]; +} + +inline U8 TerrainFile::getLayerIndex( U32 x, U32 y ) const +{ + x %= mSize; + y %= mSize; + return mLayerMap[ x + ( y * mSize ) ]; +} + +inline void TerrainFile::setLayerIndex( U32 x, U32 y, U8 index ) +{ + x %= mSize; + y %= mSize; + mLayerMap[ x + ( y * mSize ) ] = index; +} + +inline BaseMatInstance* TerrainFile::getMaterialMapping( U32 index ) const +{ + if ( index < mMaterialInstMapping.size() ) + return mMaterialInstMapping.getMaterialInst( index ); + else + return NULL; +} + + + +/// Conversion from 11.5 fixed point to floating point. +inline F32 fixedToFloat( U16 val ) +{ + return F32(val) * 0.03125f; +} + +/// Conversion from floating point to 11.5 fixed point. +inline U16 floatToFixed( F32 val ) +{ + return U16(val * 32.0); +} + +#endif // _TERRFILE_H_ diff --git a/terrain/terrImport.cpp b/terrain/terrImport.cpp new file mode 100644 index 0000000..6757e64 --- /dev/null +++ b/terrain/terrImport.cpp @@ -0,0 +1,295 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +#include "terrain/terrData.h" +#include "gfx/bitmap/gBitmap.h" +#include "sim/netConnection.h" +#include "core/strings/stringUnit.h" +#include "core/resourceManager.h" +#include "gui/worldEditor/terrainEditor.h" +#include "util/noise2d.h" +#include "core/volume.h" + + +ConsoleStaticMethod( TerrainBlock, createNew, S32, 5, 5, + "TerrainBlock.create( String terrainName, U32 resolution, String materialName, bool genNoise )\n" + "" ) +{ + const UTF8 *terrainName = argv[1]; + U32 resolution = dAtoi( argv[2] ); + const UTF8 *materialName = argv[3]; + bool genNoise = dAtob( argv[4] ); + + Vector materials; + materials.push_back( materialName ); + + TerrainBlock *terrain = new TerrainBlock(); + + FileName terrFileName(""); + + // We create terrains based on level name. If the user wants to rename the terrain names; they have to + // rename it themselves in their file browser. The main reason for this is so we can easily increment for ourselves; + // and because its too easy to rename the terrain object and forget to take care of the terrain filename afterwards. + if (terrFileName.isEmpty()) + { + terrFileName = Con::getVariable("$Client::MissionFile"); + terrFileName.replace("levels/", "art/terrains/"); + } + + TerrainFile::create( &terrFileName, resolution, materials ); + + terrain->setFile( terrFileName ); + terrain->setPosition( Point3F( 0, 0, 0 ) ); + + const U32 blockSize = terrain->getBlockSize(); + + if ( genNoise ) + { + TerrainFile *file = terrain->getFile(); + + Vector floatHeights; + floatHeights.setSize( blockSize * blockSize ); + + Noise2D noise; + noise.setSeed( 134208587 ); + + // Set up some defaults. + F32 octaves = 3.0f; + U32 freq = 4; + F32 roughness = 0.0f; + noise.fBm( &floatHeights, blockSize, freq, 1.0f - roughness, octaves ); + + F32 height = 0; + + F32 omax, omin; + noise.getMinMax( &floatHeights, &omin, &omax, blockSize ); + + F32 terrscale = 300.0f / (omax - omin); + for ( S32 y = 0; y < blockSize; y++ ) + { + for ( S32 x = 0; x < blockSize; x++ ) + { + // Very important to subtract the min + // noise value when using the noise functions + // for terrain, otherwise floatToFixed() will + // wrap negative values to U16_MAX, creating + // a very ugly terrain. + height = (floatHeights[ x + (y * blockSize) ] - omin) * terrscale + 30.0f; + file->setHeight( x, y, floatToFixed( height ) ); + } + } + + terrain->updateGrid( Point2I::Zero, Point2I( blockSize, blockSize ) ); + terrain->updateGridMaterials( Point2I::Zero, Point2I( blockSize, blockSize ) ); + } + + terrain->registerObject( terrainName ); + + // Add to mission group! + SimGroup *missionGroup; + if( Sim::findObject( "MissionGroup", missionGroup ) ) + missionGroup->addObject( terrain ); + + return terrain->getId(); +} + +ConsoleStaticMethod( TerrainBlock, import, S32, 7, 7, + "( String terrainName, String heightMap, F32 metersPerPixel, F32 heightScale, String materials, String opacityLayers )\n" + "" ) +{ + // Get the parameters. + const UTF8 *terrainName = argv[1]; + const UTF8 *hmap = argv[2]; + F32 metersPerPixel = dAtof(argv[3]); + F32 heightScale = dAtof(argv[4]); + const UTF8 *opacityFiles = argv[5]; + const UTF8 *materialsStr = argv[6]; + + // First load the height map and validate it. + Resource heightmap = GBitmap::load( hmap ); + if ( !heightmap ) + { + Con::errorf( "Heightmap failed to load!" ); + return 0; + } + + U32 terrSize = heightmap->getWidth(); + U32 hheight = heightmap->getHeight(); + if ( terrSize != hheight || !isPow2( terrSize ) ) + { + Con::errorf( "Height map must be square and power of two in size!" ); + return 0; + } + else if ( terrSize < 128 || terrSize > 4096 ) + { + Con::errorf( "Height map must be between 128 and 4096 in size!" ); + return 0; + } + + U32 fileCount = StringUnit::getUnitCount( opacityFiles, "\n" ); + Vector layerMap; + layerMap.setSize( terrSize * terrSize ); + { + Vector bitmaps; + + for ( U32 i = 0; i < fileCount; i++ ) + { + String fileNameWithChannel = StringUnit::getUnit( opacityFiles, i, "\n" ); + String fileName = StringUnit::getUnit( fileNameWithChannel, 0, "\t" ); + String channel = StringUnit::getUnit( fileNameWithChannel, 1, "\t" ); + + if ( fileName.isEmpty() ) + continue; + + if ( !channel.isEmpty() ) + { + // Load and push back the bitmap here. + Resource opacityMap = ResourceManager::get().load( fileName ); + if ( terrSize != opacityMap->getWidth() || terrSize != opacityMap->getHeight() ) + { + Con::errorf( "The opacity map '%s' doesn't match height map size!", fileName.c_str() ); + return 0; + } + + // Always going to be one channel. + GBitmap *opacityMapChannel = new GBitmap( terrSize, + terrSize, + false, + GFXFormatA8 ); + + if ( opacityMap->getBytesPerPixel() > 1 ) + { + if ( channel.equal( "R", 1 ) ) + opacityMap->copyChannel( 0, opacityMapChannel ); + else if ( channel.equal( "G", 1 ) ) + opacityMap->copyChannel( 1, opacityMapChannel ); + else if ( channel.equal( "B", 1 ) ) + opacityMap->copyChannel( 2, opacityMapChannel ); + else if ( channel.equal( "A", 1 ) ) + opacityMap->copyChannel( 3, opacityMapChannel ); + + bitmaps.push_back( opacityMapChannel ); + } + else + { + opacityMapChannel->copyRect( opacityMap, RectI( 0, 0, terrSize, terrSize ), Point2I( 0, 0 ) ); + bitmaps.push_back( opacityMapChannel ); + } + } + } + + // Ok... time to convert all this opacity layer + // mess to the layer index map! + U32 layerCount = bitmaps.size() - 1; + U32 layer, lastValue; + U8 value; + for ( U32 i = 0; i < terrSize * terrSize; i++ ) + { + // Find the greatest layer. + layer = lastValue = 0; + for ( U32 k=0; k < bitmaps.size(); k++ ) + { + value = bitmaps[k]->getBits()[i]; + if ( value >= lastValue ) + { + layer = k; + lastValue = value; + } + } + + // Set the layer index. + layerMap[i] = getMin( layer, layerCount ); + } + + // Cleanup the bitmaps. + for ( U32 i=0; i < bitmaps.size(); i++ ) + delete bitmaps[i]; + } + + U32 matCount = StringUnit::getUnitCount( materialsStr, "\t\n" ); + if( matCount != fileCount) + { + Con::errorf("Number of Materials and Layer maps must be equal."); + return 0; + } + + Vector materials; + for ( U32 i = 0; i < matCount; i++ ) + { + String matStr = StringUnit::getUnit( materialsStr, i, "\t\n" ); + // even if matStr is empty, insert it as a placeholder (will be replaced with warning material later) + materials.push_back( matStr ); + } + + // Do we have an existing terrain with that name... then update it! + TerrainBlock *terrain = dynamic_cast( Sim::findObject( terrainName ) ); + if ( terrain ) + terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials ); + else + { + terrain = new TerrainBlock(); + terrain->assignName( terrainName ); + terrain->import( (*heightmap), heightScale, metersPerPixel, layerMap, materials ); + terrain->registerObject(); + + // Add to mission group! + SimGroup *missionGroup; + if ( Sim::findObject( "MissionGroup", missionGroup ) ) + missionGroup->addObject( terrain ); + } + + return terrain->getId(); +} + +bool TerrainBlock::import( const GBitmap &heightMap, + F32 heightScale, + F32 metersPerPixel, + const Vector &layerMap, + const Vector &materials ) +{ + AssertFatal( isServerObject(), "TerrainBlock::import - This should only be called on the server terrain!" ); + + AssertFatal( heightMap.getWidth() == heightMap.getHeight(), "TerrainBlock::import - Height map is not square!" ); + AssertFatal( isPow2( heightMap.getWidth() ), "TerrainBlock::import - Height map is not power of two!" ); + + // If we don't have a terrain file then add one. + if ( !mFile ) + { + // Get a unique file name for the terrain. + String fileName( getName() ); + if ( fileName.isEmpty() ) + fileName = "terrain"; + mTerrFileName = FS::MakeUniquePath( "levels", fileName, "ter" ); + + // TODO: We have to save and reload the file to get + // it into the resource system. This creates lots + // of temporary unused files when the terrain is + // discarded because of undo or quit. + TerrainFile *file = new TerrainFile; + file->save( mTerrFileName ); + delete file; + mFile = ResourceManager::get().load( mTerrFileName ); + } + + // The file does a bunch of the work. + mFile->import( heightMap, heightScale, layerMap, materials ); + + // Set the square size. + mSquareSize = metersPerPixel; + + if ( isProperlyAdded() ) + { + // Update the server bounds. + _updateBounds(); + + // Make sure the client gets updated. + setMaskBits( HeightMapChangeMask | SizeMask ); + } + + return true; +} + diff --git a/terrain/terrLighting.cpp b/terrain/terrLighting.cpp new file mode 100644 index 0000000..f0132b9 --- /dev/null +++ b/terrain/terrLighting.cpp @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "terrain/terrRender.h" +#include "lighting/lightInfo.h" +#include "sceneGraph/sceneState.h" + +/* + +U32 TerrainRender::testSquareLights(GridSquare *sq, S32 level, const Point2I &pos, U32 lightMask) +{ + + // Calculate our Box3F for this GridSquare + Point3F boxMin(pos.x * mSquareSize + mBlockPos.x, pos.y * mSquareSize + mBlockPos.y, fixedToFloat(sq->minHeight)); + F32 blockSize = F32(mSquareSize * (1 << level)); + F32 blockHeight = fixedToFloat(sq->maxHeight - sq->minHeight); + Point3F boxMax(boxMin); + boxMax += Point3F(blockSize, blockSize, blockHeight); + Box3F gridBox(boxMin, boxMax); + + U32 retMask = 0; + + for(S32 i = 0; (lightMask >> i) != 0; i++) + { + if(lightMask & (1 << i)) + { + if (mTerrainLights[i].light->mType != LightInfo::Vector) + { + // test the visibility of this light to box + F32 dist = gridBox.getDistanceFromPoint(mTerrainLights[i].pos); + static F32 minDist = 1e14f; + minDist = getMin(minDist, dist); + if(dist < mTerrainLights[i].radius) + retMask |= (1 << i); + } else { + retMask |= (1 << i); + } + } + } + return retMask; +} + +void TerrainRender::buildLightArray(SceneState * state) +{ + PROFILE_SCOPE(TerrainRender_buildLightArray); + + mDynamicLightCount = 0; + if ((mTerrainLighting == NULL) || (!TerrainRender::mEnableTerrainDynLights)) + return; + + static LightInfoList lights; + lights.clear(); + + state->getLightManager()->getBestLights(lights); + // create terrain lights from these... + U32 curIndex = 0; + for(U32 i = 0; i < lights.size(); i++) + { + LightInfo* light = lights[i]; + if((light->mType != LightInfo::Point) && (light->mType != LightInfo::Spot)) + continue; + + // set the 'fo + TerrLightInfo & info = mTerrainLights[curIndex++]; + mCurrentBlock->getWorldTransform().mulP(light->mPos, &info.pos); + info.radius = light->getRadius(); + info.light = light; + } + + mDynamicLightCount = curIndex; +} + +*/ diff --git a/terrain/terrMaterial.cpp b/terrain/terrMaterial.cpp new file mode 100644 index 0000000..5dab68c --- /dev/null +++ b/terrain/terrMaterial.cpp @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrMaterial.h" +#include "console/consoleTypes.h" +#include "gfx/bitmap/gBitmap.h" + + +IMPLEMENT_CONOBJECT( TerrainMaterial ); + +TerrainMaterial::TerrainMaterial() + : mSideProjection( false ), + mDiffuseSize( 500.0f ), + mDetailSize( 5.0f ), + mDetailStrength( 1.0f ), + mDetailDistance( 50.0f ), + mParallaxScale( 0.0f ) +{ +} + +TerrainMaterial::~TerrainMaterial() +{ +} + +void TerrainMaterial::initPersistFields() +{ + addField( "diffuseMap", TypeStringFilename, Offset( mDiffuseMap, TerrainMaterial ) ); + addField( "diffuseSize", TypeF32, Offset( mDiffuseSize, TerrainMaterial ) ); + + addField( "normalMap", TypeStringFilename, Offset( mNormalMap, TerrainMaterial ) ); + + addField( "detailMap", TypeStringFilename, Offset( mDetailMap, TerrainMaterial ) ); + addField( "detailSize", TypeF32, Offset( mDetailSize, TerrainMaterial ) ); + + addField( "detailStrength", TypeF32, Offset( mDetailStrength, TerrainMaterial ) ); + addField( "detailDistance", TypeF32, Offset( mDetailDistance, TerrainMaterial ) ); + addField( "useSideProjection", TypeBool, Offset( mSideProjection, TerrainMaterial ) ); + addField( "parallaxScale", TypeF32, Offset( mParallaxScale, TerrainMaterial ) ); + + Parent::initPersistFields(); + + // Gotta call this at least once or it won't get created! + Sim::getTerrainMaterialSet(); +} + +bool TerrainMaterial::onAdd() +{ + if ( !Parent::onAdd() ) + return false; + + SimSet *set = Sim::getTerrainMaterialSet(); + + // Make sure we have an internal name set. + if ( !mInternalName || !mInternalName[0] ) + Con::warnf( "TerrainMaterial::onAdd() - No internal name set!" ); + else + { + SimObject *object = set->findObjectByInternalName( mInternalName ); + if ( object ) + Con::warnf( "TerrainMaterial::onAdd() - Internal name collision; '%s' already exists!", mInternalName ); + } + + set->addObject( this ); + + return true; +} + +TerrainMaterial* TerrainMaterial::getWarningMaterial() +{ + return findOrCreate( NULL ); +} + +TerrainMaterial* TerrainMaterial::findOrCreate( const char *nameOrPath ) +{ + SimSet *set = Sim::getTerrainMaterialSet(); + + if ( !nameOrPath || !nameOrPath[0] ) + nameOrPath = "warning_material"; + + // See if we can just find it. + TerrainMaterial *mat = dynamic_cast( set->findObjectByInternalName( StringTable->insert( nameOrPath ) ) ); + if ( mat ) + return mat; + + // We didn't find it... so see if its a path to a + // file. If it is lets assume its the texture. + if ( GBitmap::sFindFiles( nameOrPath, NULL ) ) + { + mat = new TerrainMaterial(); + mat->setInternalName( nameOrPath ); + mat->mDiffuseMap = nameOrPath; + mat->registerObject(); + Sim::getRootGroup()->addObject( mat ); + return mat; + } + + // Ok... return a debug material then. + mat = dynamic_cast( set->findObjectByInternalName( StringTable->insert( "warning_material" ) ) ); + if ( !mat ) + { + // This shouldn't happen.... the warning_texture should + // have already been defined in script, but we put this + // fallback here just in case it gets "lost". + mat = new TerrainMaterial(); + mat->setInternalName( "warning_material" ); + mat->mDiffuseMap = "core/art/warnMat.png"; + mat->mDiffuseSize = 500; + mat->mDetailMap = "core/art/warnMat.png"; + mat->mDetailSize = 5; + mat->registerObject(); + } + + return mat; +} diff --git a/terrain/terrMaterial.h b/terrain/terrMaterial.h new file mode 100644 index 0000000..f557e40 --- /dev/null +++ b/terrain/terrMaterial.h @@ -0,0 +1,94 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRMATERIAL_H_ +#define _TERRMATERIAL_H_ + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + + +/// The TerrainMaterial class orginizes the material settings +/// for a single terrain material layer. +class TerrainMaterial : public SimObject +{ + typedef SimObject Parent; + +protected: + + /// + FileName mDiffuseMap; + + /// The size of the diffuse base map in meters + /// used to generate its texture coordinates. + F32 mDiffuseSize; + + /// + FileName mNormalMap; + + /// + FileName mDetailMap; + + /// The size of the detail map in meters used + /// to generate the texture coordinates for the + /// detail and normal maps. + F32 mDetailSize; + + /// + F32 mDetailStrength; + + /// + F32 mDetailDistance; + + /// Normally the detail is projected on to the xy + /// coordinates of the terrain. If this flag is true + /// then this detail is projected along the xz and yz + /// planes. + bool mSideProjection; + + /// + F32 mParallaxScale; + +public: + + TerrainMaterial(); + virtual ~TerrainMaterial(); + + bool onAdd(); + static void initPersistFields(); + + DECLARE_CONOBJECT( TerrainMaterial ); + + /// This method locates the TerrainMaterial if it exists, tries + /// to create a new one if a valid texture path was passed, or + /// returns a debug material if all else fails. + static TerrainMaterial* findOrCreate( const char *nameOrPath ); + + /// Returns the default warning terrain material used when + /// a material is not found or defined. + static TerrainMaterial* getWarningMaterial(); + + const String& getDiffuseMap() const { return mDiffuseMap; } + + F32 getDiffuseSize() const { return mDiffuseSize; } + + const String& getNormalMap() const { return mNormalMap; } + + const String& getDetailMap() const { return mDetailMap; } + + F32 getDetailSize() const { return mDetailSize; } + + F32 getDetailStrength() const { return mDetailStrength; } + + F32 getDetailDistance() const { return mDetailDistance; } + + bool useSideProjection() const { return mSideProjection; } + + F32 getParallaxScale() const { return mParallaxScale; } + +}; + +#endif // _TERRMATERIAL_H_ diff --git a/terrain/terrRender.cpp b/terrain/terrRender.cpp new file mode 100644 index 0000000..388a711 --- /dev/null +++ b/terrain/terrRender.cpp @@ -0,0 +1,462 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "terrain/terrRender.h" + +#include "terrain/terrData.h" +#include "terrain/terrCell.h" +#include "terrain/terrMaterial.h" +#include "terrain/terrCellMaterial.h" +#include "materials/shaderData.h" + +#include "platform/profiler.h" +#include "sceneGraph/sceneState.h" +#include "math/util/frustum.h" +#include "renderInstance/renderPassManager.h" +#include "renderInstance/renderTerrainMgr.h" + +#include "lighting/lightInfo.h" +#include "lighting/lightManager.h" + +#include "materials/matInstance.h" +#include "materials/materialManager.h" +#include "materials/matTextureTarget.h" +#include "shaderGen/conditionerFeature.h" + +#include "gfx/gfxDrawUtil.h" + +#include "gfx/gfxTransformSaver.h" +#include "gfx/bitmap/gBitmap.h" +#include "gfx/bitmap/ddsFile.h" +#include "gfx/bitmap/ddsUtils.h" +#include "terrain/terrMaterial.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxCardProfile.h" +#include "core/stream/fileStream.h" + + +bool TerrainBlock::smDebugRender = false; + + +GFX_ImplementTextureProfile( TerrainLayerTexProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::PreserveSize | + GFXTextureProfile::Static, + GFXTextureProfile::None ); + + +void TerrainBlock::_onLMActivate( const char *lm, bool activate ) +{ + if ( activate ) + { + if ( mCell ) + mCell->deleteMaterials(); + + SAFE_DELETE( mBaseMaterial ); + } +} + +void TerrainBlock::_updateMaterials() +{ + mBaseTextures.setSize( mFile->mMaterials.size() ); + + mMaxDetailDistance = 0.0f; + + for ( U32 i=0; i < mFile->mMaterials.size(); i++ ) + { + TerrainMaterial *mat = mFile->mMaterials[i]; + + mBaseTextures[i].set( mat->getDiffuseMap(), + &GFXDefaultStaticDiffuseProfile, + "TerrainBlock::_updateMaterials() - DiffuseMap" ); + + // Find the maximum detail distance. + if ( mat->getDetailMap().isNotEmpty() && + mat->getDetailDistance() > mMaxDetailDistance ) + mMaxDetailDistance = mat->getDetailDistance(); + } + + if ( mCell ) + mCell->deleteMaterials(); +} + +void TerrainBlock::_updateLayerTexture() +{ + const U32 layerSize = mFile->mSize; + const Vector &layerMap = mFile->mLayerMap; + const U32 pixelCount = layerMap.size(); + + if ( mLayerTex.isNull() || + mLayerTex.getWidth() != layerSize || + mLayerTex.getHeight() != layerSize ) + mLayerTex.set( layerSize, layerSize, GFXFormatR8G8B8A8, &TerrainLayerTexProfile, "" ); + + AssertFatal( mLayerTex.getWidth() == layerSize && + mLayerTex.getHeight() == layerSize, + "TerrainBlock::_updateLayerTexture - The texture size doesn't match the requested size!" ); + + // Update the layer texture. + GFXLockedRect *lock = mLayerTex.lock(); + + for ( U32 i=0; i < pixelCount; i++ ) + { + lock->bits[0] = layerMap[i]; + + if ( i + 1 >= pixelCount ) + lock->bits[1] = lock->bits[0]; + else + lock->bits[1] = layerMap[i+1]; + + if ( i + layerSize >= pixelCount ) + lock->bits[2] = lock->bits[0]; + else + lock->bits[2] = layerMap[i + layerSize]; + + if ( i + layerSize + 1 >= pixelCount ) + lock->bits[3] = lock->bits[0]; + else + lock->bits[3] = layerMap[i + layerSize + 1]; + + lock->bits += 4; + } + + mLayerTex.unlock(); + //mLayerTex->dumpToDisk( "png", "./layerTex.png" ); +} + +bool TerrainBlock::_initBaseShader() +{ + ShaderData *shaderData = NULL; + if ( !Sim::findObject( "TerrainBlendShader", shaderData ) || !shaderData ) + return false; + + mBaseShader = shaderData->getShader(); + + mBaseShaderConsts = mBaseShader->allocConstBuffer(); + mBaseTexScaleConst = mBaseShader->getShaderConstHandle( "$texScale" ); + mBaseTexIdConst = mBaseShader->getShaderConstHandle( "$texId" ); + mBaseLayerSizeConst = mBaseShader->getShaderConstHandle( "$layerSize" ); + + mBaseTarget = GFX->allocRenderToTextureTarget(); + + GFXStateBlockDesc desc; + desc.samplersDefined = true; + desc.samplers[0] = GFXSamplerStateDesc::getClampPoint(); + desc.samplers[1] = GFXSamplerStateDesc::getWrapLinear(); + desc.zDefined = true; + desc.zWriteEnable = false; + desc.zEnable = false; + desc.setBlend( true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha ); + desc.cullDefined = true; + desc.cullMode = GFXCullNone; + mBaseShaderSB = GFX->createStateBlock( desc ); + + return true; +} + +void TerrainBlock::_updateBaseTexture( bool writeToCache ) +{ + if ( !mBaseShader && !_initBaseShader() ) + return; + + // This can sometimes occur outside a begin/end scene. + const bool sceneBegun = GFX->canCurrentlyRender(); + if ( !sceneBegun ) + GFX->beginScene(); + + GFXDEBUGEVENT_SCOPE( TerrainBlock_UpdateBaseTexture, ColorI::GREEN ); + + PROFILE_SCOPE( TerrainBlock_UpdateBaseTexture ); + + GFXTransformSaver saver; + + const U32 maxTextureSize = GFX->getCardProfiler()->queryProfile( "maxTextureSize", 1024 ); + + U32 baseTexSize = getNextPow2( mBaseTexSize ); + baseTexSize = getMin( maxTextureSize, baseTexSize ); + Point2I destSize( baseTexSize, baseTexSize ); + + // Setup geometry + GFXVertexBufferHandle vb; + { + F32 copyOffsetX = 2.0f * GFX->getFillConventionOffset() / (F32)destSize.x; + F32 copyOffsetY = 2.0f * GFX->getFillConventionOffset() / (F32)destSize.y; + + const bool needsYFlip = GFX->getAdapterType() == OpenGL; + + GFXVertexPT points[4]; + points[0].point = Point3F( -1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 ); + points[0].texCoord = Point2F( 0.0, needsYFlip ? 0.0f : 1.0f ); + points[1].point = Point3F( -1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 ); + points[1].texCoord = Point2F( 0.0, needsYFlip ? 1.0f : 0.0f ); + points[2].point = Point3F( 1.0 - copyOffsetX, 1.0 + copyOffsetY, 0.0 ); + points[2].texCoord = Point2F( 1.0, needsYFlip ? 1.0f : 0.0f ); + points[3].point = Point3F( 1.0 - copyOffsetX, -1.0 + copyOffsetY, 0.0 ); + points[3].texCoord = Point2F( 1.0, needsYFlip ? 0.0f : 1.0f ); + + vb.set( GFX, 4, GFXBufferTypeVolatile ); + dMemcpy( vb.lock(), points, sizeof(GFXVertexPT) * 4 ); + vb.unlock(); + } + + GFXTexHandle blendTex; + + // If the base texture is already a valid render target then + // use it to render to else we create one. + if ( mBaseTex.isValid() && + mBaseTex->isRenderTarget() && + mBaseTex->getFormat() == GFXFormatR8G8B8A8 && + mBaseTex->getWidth() == destSize.x && + mBaseTex->getHeight() == destSize.y ) + blendTex = mBaseTex; + else + blendTex.set( destSize.x, destSize.y, GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, "" ); + + GFX->pushActiveRenderTarget(); + + // Set our shader stuff + GFX->setShader( mBaseShader ); + GFX->setShaderConstBuffer( mBaseShaderConsts ); + GFX->setStateBlock( mBaseShaderSB ); + GFX->setVertexBuffer( vb ); + + mBaseTarget->attachTexture( GFXTextureTarget::Color0, blendTex ); + GFX->setActiveRenderTarget( mBaseTarget ); + + GFX->setTexture( 0, mLayerTex ); + mBaseShaderConsts->set( mBaseLayerSizeConst, (F32)mLayerTex->getWidth() ); + + for ( U32 i=0; i < mBaseTextures.size(); i++ ) + { + GFXTextureObject *tex = mBaseTextures[i]; + if ( !tex ) + continue; + + GFX->setTexture( 1, tex ); + + F32 baseSize = mFile->mMaterials[i]->getDiffuseSize(); + F32 scale = 1.0f; + if ( !mIsZero( baseSize ) ) + scale = getWorldBlockSize() / baseSize; + + // A mistake early in development means that texture + // coords are not flipped correctly. To compensate + // we flip the y scale here. + mBaseShaderConsts->set( mBaseTexScaleConst, Point2F( scale, -scale ) ); + mBaseShaderConsts->set( mBaseTexIdConst, (F32)i ); + + GFX->drawPrimitive( GFXTriangleFan, 0, 2 ); + } + + mBaseTarget->resolve(); + + GFX->setShader( NULL ); + //GFX->setStateBlock( NULL ); // WHY NOT? + GFX->setShaderConstBuffer( NULL ); + GFX->setVertexBuffer( NULL ); + + GFX->popActiveRenderTarget(); + + // End it if we begun it... Yeehaw! + if ( !sceneBegun ) + GFX->endScene(); + + /// Do we cache this sucker? + if ( writeToCache ) + { + String cachePath = _getBaseTexCacheFileName(); + + FileStream fs; + if ( fs.open( _getBaseTexCacheFileName(), Torque::FS::File::Write ) ) + { + // Read back the render target, dxt compress it, and write it to disk. + GBitmap blendBmp( destSize.x, destSize.y, false, GFXFormatR8G8B8A8 ); + blendTex.copyToBmp( &blendBmp ); + + /* + // Test code for dumping uncompressed bitmap to disk. + { + FileStream fs; + if ( fs.open( "./basetex.png", Torque::FS::File::Write ) ) + { + blendBmp.writeBitmap( "png", fs ); + fs.close(); + } + } + */ + + blendBmp.extrudeMipLevels(); + + DDSFile *blendDDS = DDSFile::createDDSFileFromGBitmap( &blendBmp ); + DDSUtil::squishDDS( blendDDS, GFXFormatDXT1 ); + + // Write result to file stream + blendDDS->write( fs ); + } + fs.close(); + } + else + { + // We didn't cache the result, so set the base texture + // to the render target we updated. This should be good + // for realtime painting cases. + mBaseTex = blendTex; + } +} + +void TerrainBlock::_renderBlock( SceneState *state ) +{ + PROFILE_SCOPE( TerrainBlock_RenderBlock ); + + MatrixF worldViewXfm = GFX->getWorldMatrix(); + worldViewXfm.mul( getRenderTransform() ); + + MatrixF worldViewProjXfm = GFX->getProjectionMatrix(); + worldViewProjXfm.mul( worldViewXfm ); + + const MatrixF &objectXfm = getRenderWorldTransform(); + + Point3F objCamPos = state->getDiffuseCameraPosition(); + objectXfm.mulP( objCamPos ); + + // Get the shadow material. + if ( !mDefaultMatInst ) + mDefaultMatInst = MATMGR->createMatInstance( "AL_DefaultShadowMaterial", getGFXVertexFormat() ); + + // Make sure we have a base material. + if ( !mBaseMaterial ) + { + mBaseMaterial = new TerrainCellMaterial(); + mBaseMaterial->init( this, 0, false, true ); + } + + // The cells are in object space... we must + // transform frustum so we can cull them. + Frustum frustum( state->getFrustum() ); + frustum.mulL( objectXfm ); + + // If this is a reflection pass we must invert + // the frustum else culling will fail! + if ( state->isReflectPass() ) + frustum.invert(); + + // Did the detail layers change? + if ( mDetailsDirty ) + { + _updateMaterials(); + mDetailsDirty = false; + } + + // Do we need to update the textures? + if ( mLayerTexDirty || mBaseTex.isNull() ) + { + _updateLayerTexture(); + _updateBaseTexture( false ); + mLayerTexDirty = false; + } + + static Vector renderCells; + renderCells.clear(); + + mCell->cullCells( &frustum, + state, + objCamPos, + &renderCells ); + + RenderPassManager *renderPass = state->getRenderPass(); + + MatrixF *riObjectToWorldXfm = renderPass->allocUniqueXform( getRenderTransform() ); + + const bool isColorDrawPass = state->isDiffusePass() || state->isReflectPass(); + + // Only pass and use the light manager if this is not a shadow pass. + LightManager *lm = NULL; + if ( isColorDrawPass ) + lm = state->getLightManager(); + + for ( U32 i=0; i < renderCells.size(); i++ ) + { + TerrCell *cell = renderCells[i]; + + // Ok this cell is fit to render. + TerrainRenderInst *inst = renderPass->allocInst(); + + // Setup lights for this cell. + if ( lm ) + { + SphereF bounds = cell->getSphereBounds(); + getRenderTransform().mulP( bounds.center ); + lm->setupLights( NULL, bounds ); + lm->getBestLights( inst->lights, 4 ); + lm->resetLights(); + } + + GFXVertexBufferHandleBase vertBuff; + + cell->getRenderPrimitive( &inst->prim, &vertBuff ); + + // This is only here so that we can get the + // hook for the shadow material. + inst->mat = mDefaultMatInst; + + inst->vertBuff = vertBuff.getPointer(); + inst->primBuff = mPrimBuffer.getPointer(); + + inst->objectToWorldXfm = riObjectToWorldXfm; + + // If we're not drawing to the shadow map then we need + // to include the normal rendering materials. + if ( isColorDrawPass ) + { + const SphereF &bounds = cell->getSphereBounds(); + + F32 sqDist = ( bounds.center - objCamPos ).lenSquared(); + + F32 radiusSq = ( mMaxDetailDistance + bounds.radius ) * smDetailScale; + radiusSq *= radiusSq; + + // If this cell is near enough to get detail textures then + // use the full detail mapping material. Else we use the + // simple base only material. + if ( !state->isReflectPass() && sqDist < radiusSq ) + inst->cellMat = cell->getMaterial(); + else + inst->cellMat = mBaseMaterial; + } + + inst->defaultKey = (U32)cell->getMaterials(); + + // Submit it for rendering. + renderPass->addInst( inst ); + } + + // Trigger the debug rendering. + if ( state->isDiffusePass() && + !renderCells.empty() && + smDebugRender ) + { + // Store the render cells for later. + mDebugCells = renderCells; + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind( this, &TerrainBlock::_renderDebug ); + ri->type = RenderPassManager::RIT_ObjectTranslucent; + state->getRenderPass()->addInst( ri ); + } +} + +void TerrainBlock::_renderDebug( ObjectRenderInst *ri, + SceneState *state, + BaseMatInstance *overrideMat ) +{ + GFXTransformSaver saver; + GFX->multWorld( getRenderTransform() ); + + for ( U32 i=0; i < mDebugCells.size(); i++ ) + mDebugCells[i]->renderBounds(); + + mDebugCells.clear(); +} \ No newline at end of file diff --git a/terrain/terrRender.h b/terrain/terrRender.h new file mode 100644 index 0000000..b60aab8 --- /dev/null +++ b/terrain/terrRender.h @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TERRRENDER_H_ +#define _TERRRENDER_H_ + +#ifndef _TERRDATA_H_ +#include "terrain/terrData.h" +#endif + +enum TerrConstants +{ + MaxClipPlanes = 8, ///< left, right, top, bottom - don't need far tho... + //MaxTerrainMaterials = 256, + + MaxTerrainLights = 64, + MaxVisibleLights = 31, + ClipPlaneMask = (1 << MaxClipPlanes) - 1, + FarSphereMask = 0x80000000, + FogPlaneBoxMask = 0x40000000, +}; + +class SceneState; + + +// Allows a lighting system to plug into terrain rendering +class TerrainLightingPlugin +{ +public: + virtual ~TerrainLightingPlugin() {} + + virtual void setupLightStage(LightManager * lm, LightInfo* light, SceneGraphData& sgData, BaseMatInstance* basemat, BaseMatInstance** dmat) = 0; + virtual void cleanupLights(LightManager * lm) {} +}; + + +/// A special texture profile used for the terrain layer id map. +GFX_DeclareTextureProfile( TerrainLayerTexProfile ); + +#endif // _TERRRENDER_H_ diff --git a/ts/arch/tsMeshIntrinsics.arch.h b/ts/arch/tsMeshIntrinsics.arch.h new file mode 100644 index 0000000..3f72e62 --- /dev/null +++ b/ts/arch/tsMeshIntrinsics.arch.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSMESHINTRINSICS_ARCH_H_ +#define _TSMESHINTRINSICS_ARCH_H_ + +#if defined(TORQUE_CPU_X86) +# // x86 CPU family implementations +extern void zero_vert_normal_bulk_SSE(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride); +extern void m_matF_x_BatchedVertWeightList_SSE(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride); +#if (_MSC_VER >= 1500) +extern void m_matF_x_BatchedVertWeightList_SSE4(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride); +#endif +# +#elif defined(TORQUE_CPU_PPC) +# // PPC CPU family implementations +# if defined(TORQUE_OS_XENON) +extern void zero_vert_normal_bulk_X360(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride); +extern void m_matF_x_BatchedVertWeightList_X360(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride); +# else +extern void zero_vert_normal_bulk_gccvec(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride); +extern void m_matF_x_BatchedVertWeightList_gccvec(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride); +# endif +# +#else +# // Other CPU types go here... +#endif + +#endif // _TSMESHINTRINSICS_ARCH_H_ + diff --git a/ts/arch/tsMeshIntrinsics.sse.cpp b/ts/arch/tsMeshIntrinsics.sse.cpp new file mode 100644 index 0000000..8dce86f --- /dev/null +++ b/ts/arch/tsMeshIntrinsics.sse.cpp @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "ts/tsMesh.h" + +#if defined(TORQUE_CPU_X86) +#include "ts/tsMeshIntrinsics.h" +#include + +void zero_vert_normal_bulk_SSE(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride) +{ + // A U8 * version of the in/out pointer + register char *outData = reinterpret_cast(outPtr); + + register __m128 vPos; + register __m128 vNrm; + register __m128 vMask; + + const __m128 _point3f_zero_mask = { 0.0f, 0.0f, 0.0f, 1.0f }; + vMask = _mm_load_ps((const F32*)&_point3f_zero_mask); + + // pre-populate cache + for(int i = 0; i < 8; i++) + _mm_prefetch(reinterpret_cast(outData + outStride * i), _MM_HINT_T0); + + for(int i = 0; i < count; i++) + { + TSMesh::__TSMeshVertexBase *curElem = reinterpret_cast(outData); + + // prefetch 8 items ahead (should really detect cache size or something) + _mm_prefetch(reinterpret_cast(outData + outStride * 8), _MM_HINT_T0); + + // load + vPos = _mm_load_ps(curElem->_vert); + vNrm = _mm_load_ps(curElem->_normal); + + // mask + vPos = _mm_and_ps(vPos, _point3f_zero_mask); + vNrm = _mm_and_ps(vNrm, _point3f_zero_mask); + + // store + _mm_store_ps(curElem->_vert, vPos); + _mm_store_ps(curElem->_normal, vNrm); + + // update output pointer + outData += outStride; + } +} + +//------------------------------------------------------------------------------ + +void m_matF_x_BatchedVertWeightList_SSE(const MatrixF &mat, + const dsize_t count, + const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, + U8 * const __restrict outPtr, + const dsize_t outStride) +{ + const char * __restrict iPtr = reinterpret_cast(batch); + const dsize_t inStride = sizeof(TSSkinMesh::BatchData::BatchedVertWeight); + + // SSE intrinsic version + // Based on: http://www.cortstratton.org/articles/HugiCode.html + + // Load matrix, transposed, into registers + MatrixF transMat; + mat.transposeTo(transMat); + register __m128 sseMat[4]; + + sseMat[0] = _mm_loadu_ps(&transMat[0]); + sseMat[1] = _mm_loadu_ps(&transMat[4]); + sseMat[2] = _mm_loadu_ps(&transMat[8]); + sseMat[3] = _mm_loadu_ps(&transMat[12]); + + // mask + const __m128 _w_mask = { 1.0f, 1.0f, 1.0f, 0.0f }; + + // temp registers + register __m128 tempPos; + register __m128 tempNrm; + register __m128 scratch0; + register __m128 scratch1; + register __m128 inPos; + register __m128 inNrm; + + // pre-populate cache + const TSSkinMesh::BatchData::BatchedVertWeight &firstElem = batch[0]; + for(int i = 0; i < 8; i++) + { + _mm_prefetch(reinterpret_cast(iPtr + inStride * i), _MM_HINT_T0); + _mm_prefetch(reinterpret_cast(outPtr + outStride * (i + firstElem.vidx)), _MM_HINT_T0); + } + + for(register int i = 0; i < count; i++) + { + const TSSkinMesh::BatchData::BatchedVertWeight &inElem = batch[i]; + TSMesh::__TSMeshVertexBase *outElem = reinterpret_cast(outPtr + inElem.vidx * outStride); + + // process x (hiding the prefetches in the delays) + inPos = _mm_load_ps(inElem.vert); + inNrm = _mm_load_ps(inElem.normal); + + // prefetch input +#define INPUT_PREFETCH_LOOKAHEAD 64 + const char *prefetchInput = reinterpret_cast(batch) + inStride * (i + INPUT_PREFETCH_LOOKAHEAD); + _mm_prefetch(prefetchInput, _MM_HINT_T0); + + // propagate the .x elements across the vectors + tempPos = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(0, 0, 0, 0)); + tempNrm = _mm_shuffle_ps(inNrm, inNrm, _MM_SHUFFLE(0, 0, 0, 0)); + + // prefetch ouput with half the lookahead distance of the input +#define OUTPUT_PREFETCH_LOOKAHEAD (INPUT_PREFETCH_LOOKAHEAD >> 1) + const char *outPrefetch = reinterpret_cast(outPtr) + outStride * (inElem.vidx + OUTPUT_PREFETCH_LOOKAHEAD); + _mm_prefetch(outPrefetch, _MM_HINT_T0); + + // mul by column 0 + tempPos = _mm_mul_ps(tempPos, sseMat[0]); + tempNrm = _mm_mul_ps(tempNrm, sseMat[0]); + + // process y + scratch0 = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(1, 1, 1, 1)); + scratch1 = _mm_shuffle_ps(inNrm, inNrm, _MM_SHUFFLE(1, 1, 1, 1)); + + scratch0 = _mm_mul_ps(scratch0, sseMat[1]); + scratch1 = _mm_mul_ps(scratch1, sseMat[1]); + + tempPos = _mm_add_ps(tempPos, scratch0); + tempNrm = _mm_add_ps(tempNrm, scratch1); + + + // process z + scratch0 = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(2, 2, 2, 2)); + scratch1 = _mm_shuffle_ps(inNrm, inNrm, _MM_SHUFFLE(2, 2, 2, 2)); + + scratch0 = _mm_mul_ps(scratch0, sseMat[2]); + scratch1 = _mm_mul_ps(scratch1, sseMat[2]); + + tempPos = _mm_add_ps(tempPos, scratch0); + + inNrm = _mm_load_ps(outElem->_normal); //< load normal for accumulation + scratch0 = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(3, 3, 3, 3));//< load bone weight across all elements of scratch0 + + tempNrm = _mm_add_ps(tempNrm, scratch1); + + scratch0 = _mm_mul_ps(scratch0, _w_mask); //< mask off last + + // Translate the position by adding the 4th column of the matrix to it + tempPos = _mm_add_ps(tempPos, sseMat[3]); + + // now multiply by the blend weight, and mask out the W component of both vectors + tempPos = _mm_mul_ps(tempPos, scratch0); + tempNrm = _mm_mul_ps(tempNrm, scratch0); + + inPos = _mm_load_ps(outElem->_vert); //< load position for accumulation + + // accumulate with previous values + tempNrm = _mm_add_ps(tempNrm, inNrm); + tempPos = _mm_add_ps(tempPos, inPos); + + _mm_store_ps(outElem->_vert, tempPos); //< output position + _mm_store_ps(outElem->_normal, tempNrm); //< output normal + } +} + +#endif // TORQUE_CPU_X86 \ No newline at end of file diff --git a/ts/arch/tsMeshIntrinsics.sse4.cpp b/ts/arch/tsMeshIntrinsics.sse4.cpp new file mode 100644 index 0000000..0ef67c0 --- /dev/null +++ b/ts/arch/tsMeshIntrinsics.sse4.cpp @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- +#include "ts/tsMesh.h" + +#if defined(TORQUE_CPU_X86) && (_MSC_VER >= 1500) +#include "ts/tsMeshIntrinsics.h" +#include + +void m_matF_x_BatchedVertWeightList_SSE4(const MatrixF &mat, + const dsize_t count, + const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, + U8 * const __restrict outPtr, + const dsize_t outStride) +{ + const char * __restrict iPtr = reinterpret_cast(batch); + const dsize_t inStride = sizeof(TSSkinMesh::BatchData::BatchedVertWeight); + + __m128 sseMat[4]; + sseMat[0] = _mm_loadu_ps(&mat[0]); + sseMat[1] = _mm_loadu_ps(&mat[4]); + sseMat[2] = _mm_loadu_ps(&mat[8]); + sseMat[3] = _mm_loadu_ps(&mat[12]); + + // temp registers + __m128 inPos, tempPos; + __m128 inNrm, tempNrm; + __m128 temp0, temp1, temp2, temp3; + + // pre-populate cache + const TSSkinMesh::BatchData::BatchedVertWeight &firstElem = batch[0]; + for(int i = 0; i < 8; i++) + { + _mm_prefetch(reinterpret_cast(iPtr + inStride * i), _MM_HINT_T0); + _mm_prefetch(reinterpret_cast(outPtr + outStride * (i + firstElem.vidx)), _MM_HINT_T0); + } + + for(int i = 0; i < count; i++) + { + const TSSkinMesh::BatchData::BatchedVertWeight &inElem = batch[i]; + TSMesh::__TSMeshVertexBase *outElem = reinterpret_cast(outPtr + inElem.vidx * outStride); + + // process x (hiding the prefetches in the delays) + inPos = _mm_load_ps(inElem.vert); + inNrm = _mm_load_ps(inElem.normal); + + // prefetch input +#define INPUT_PREFETCH_LOOKAHEAD 64 + const char *prefetchInput = reinterpret_cast(batch) + inStride * (i + INPUT_PREFETCH_LOOKAHEAD); + _mm_prefetch(prefetchInput, _MM_HINT_T0); + + // prefetch ouput with half the lookahead distance of the input +#define OUTPUT_PREFETCH_LOOKAHEAD (INPUT_PREFETCH_LOOKAHEAD >> 1) + const char *outPrefetch = reinterpret_cast(outPtr) + outStride * (inElem.vidx + OUTPUT_PREFETCH_LOOKAHEAD); + _mm_prefetch(outPrefetch, _MM_HINT_T0); + + // Multiply position + tempPos = _mm_dp_ps(inPos, sseMat[0], 0xF1); + temp0 = _mm_dp_ps(inPos, sseMat[1], 0xF2); + temp1 = _mm_dp_ps(inPos, sseMat[2], 0xF4); + + temp0 = _mm_or_ps(temp0, temp1); + tempPos = _mm_or_ps(tempPos, temp0); + + // Multiply normal + tempNrm = _mm_dp_ps(inNrm, sseMat[0], 0x71); + temp2 = _mm_dp_ps(inNrm, sseMat[1], 0x72); + temp3 = _mm_dp_ps(inNrm, sseMat[2], 0x74); + + temp2 = _mm_or_ps(temp2, temp3); + tempNrm = _mm_or_ps(tempNrm, temp2); + + inPos = _mm_load_ps(outElem->_vert); //< load position for accumulation + inNrm = _mm_load_ps(outElem->_normal); //< load normal for accumulation + + // accumulate with previous values + tempNrm = _mm_add_ps(tempNrm, inNrm); + tempPos = _mm_add_ps(tempPos, inPos); + + _mm_store_ps(outElem->_vert, tempPos); //< output position + _mm_store_ps(outElem->_normal, tempNrm); //< output normal + } +} + +#endif // TORQUE_CPU_X86 \ No newline at end of file diff --git a/ts/collada/colladaAppMaterial.cpp b/ts/collada/colladaAppMaterial.cpp new file mode 100644 index 0000000..b2631e6 --- /dev/null +++ b/ts/collada/colladaAppMaterial.cpp @@ -0,0 +1,219 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "ts/loader/tsShapeLoader.h" +#include "ts/collada/colladaAppMaterial.h" +#include "ts/collada/colladaUtils.h" + +using namespace ColladaUtils; + +String cleanString(const String& str) +{ + String cleanStr(str); + const String badChars(" -,.+=*/"); + for (String::SizeType i = 0; i < badChars.length(); i++) + cleanStr.replace(badChars[i], '_'); + return cleanStr; +} + +//------------------------------------------------------------------------------ + +ColladaAppMaterial::ColladaAppMaterial(const domMaterial *pMat) +: mat(pMat), + diffuseColor(ColorF::ONE), + specularColor(ColorF::ONE), + specularPower(8.0f), + pixelSpecular(false), + emissive(false), + doubleSided(false) +{ + // Get the material name + name = ColladaUtils::getOptions().matNamePrefix + _GetNameOrId(mat); + + // Get the effect element for this material + effect = daeSafeCast(mat->getInstance_effect()->getUrl().getElement()); + effectExt = new ColladaExtension_effect(effect); + + // Get the , and elements + const domProfile_COMMON* commonProfile = ColladaUtils::findEffectCommonProfile(effect); + const domCommon_color_or_texture_type_complexType* domDiffuse = findEffectDiffuse(effect); + const domCommon_color_or_texture_type_complexType* domSpecular = findEffectSpecular(effect); + + // Wrap flags + if (effectExt->wrapU) + flags |= TSMaterialList::S_Wrap; + if (effectExt->wrapV) + flags |= TSMaterialList::T_Wrap; + + // MipMap flags + const domFx_sampler2D_common_complexType* sampler2D = getTextureSampler(effect, domDiffuse); + if (sampler2D) { + if (sampler2D->getMipmap_maxlevel() && + sampler2D->getMipmap_maxlevel()->getValue() == 0) + flags |= TSMaterialList::NoMipMap; + + if (sampler2D->getBorder_color() && + sampler2D->getBorder_color()->getValue()[0] == 0 && + sampler2D->getBorder_color()->getValue()[1] == 0 && + sampler2D->getBorder_color()->getValue()[2] == 0) + flags |= TSMaterialList::MipMap_ZeroBorder; + } + + // Set material attributes + if (commonProfile) { + + F32 transparency = 0.0f; + if (commonProfile->getTechnique()->getConstant()) { + const domProfile_COMMON::domTechnique::domConstant* constant = commonProfile->getTechnique()->getConstant(); + diffuseColor.set(1.0f, 1.0f, 1.0f, 1.0f); + resolveColor(constant->getReflective(), &specularColor); + resolveFloat(constant->getReflectivity(), &specularPower); + resolveTransparency(constant, &transparency); + + flags |= TSMaterialList::SelfIlluminating; // material is SelfIlluminated if it uses the shader + emissive = true; // set emissive flag because constant material is not affected by lighting + } + else if (commonProfile->getTechnique()->getLambert()) { + const domProfile_COMMON::domTechnique::domLambert* lambert = commonProfile->getTechnique()->getLambert(); + resolveColor(lambert->getDiffuse(), &diffuseColor); + resolveColor(lambert->getReflective(), &specularColor); + resolveFloat(lambert->getReflectivity(), &specularPower); + resolveTransparency(lambert, &transparency); + emissive = false; + } + else if (commonProfile->getTechnique()->getPhong()) { + const domProfile_COMMON::domTechnique::domPhong* phong = commonProfile->getTechnique()->getPhong(); + resolveColor(phong->getDiffuse(), &diffuseColor); + resolveColor(phong->getSpecular(), &specularColor); + resolveFloat(phong->getShininess(), &specularPower); + resolveTransparency(phong, &transparency); + emissive = false; + } + else if (commonProfile->getTechnique()->getBlinn()) { + const domProfile_COMMON::domTechnique::domBlinn* blinn = commonProfile->getTechnique()->getBlinn(); + resolveColor(blinn->getDiffuse(), &diffuseColor); + resolveColor(blinn->getSpecular(), &specularColor); + resolveFloat(blinn->getShininess(), &specularPower); + resolveTransparency(blinn, &transparency); + emissive = false; + } + + // Set translucency flags + if (transparency != 0.0f) { + flags |= TSMaterialList::Translucent; + if (transparency > 1.0f) { + flags |= TSMaterialList::Additive; + diffuseColor.alpha = transparency - 1.0f; + } + else if (transparency < 0.0f) { + flags |= TSMaterialList::Subtractive; + diffuseColor.alpha = -transparency; + } + else { + diffuseColor.alpha = transparency; + } + } + else + diffuseColor.alpha = 1.0f; + } + + // Disable environment mapping if material reflectivity is zero + if (specularPower == 0) + flags |= TSMaterialList::NeverEnvMap; + + // Double-sided flag + doubleSided = effectExt->double_sided; + + // Set pixelSpecular flag to ensure geometry is rendered even if the material + // texture cannot be found + // + // MDF: pixelSpecular = true; looks very wrong in most cases so I am disabling it + pixelSpecular = false; + + // Get the paths for the various textures => Collada indirection at its finest! + // ........ + diffuseMap = getSamplerImagePath(effect, getTextureSampler(effect, domDiffuse)); + specularMap = getSamplerImagePath(effect, getTextureSampler(effect, domSpecular)); + normalMap = getSamplerImagePath(effect, effectExt->bumpSampler); + + // If the diffuse texture could not be found, set the 'emissive' flag so + // Torque colors the (blank) material using the diffuse color + // @todo: This isn't right, but Torque seems to ignore the diffuse color + // if emissive is not set + if (diffuseMap.isEmpty()) + emissive = true; +} + +void ColladaAppMaterial::resolveFloat(const domCommon_float_or_param_type* value, F32* dst) +{ + if (value && value->getFloat()) { + *dst = value->getFloat()->getValue(); + } +} + +void ColladaAppMaterial::resolveColor(const domCommon_color_or_texture_type* value, ColorF* dst) +{ + if (value && value->getColor()) { + dst->red = value->getColor()->getValue()[0]; + dst->green = value->getColor()->getValue()[1]; + dst->blue = value->getColor()->getValue()[2]; + dst->alpha = value->getColor()->getValue()[3]; + } +} + +// Generate a script Material object +#define writeLine(str) { stream.write((int)dStrlen(str), str); stream.write(2, "\r\n"); } + +void writeValue(Stream & stream, const char *name, const char *value) +{ + writeLine(avar("\t%s = \"%s\";", name, value)); +} + +void writeValue(Stream & stream, const char *name, bool value) +{ + writeLine(avar("\t%s = %s;", name, value ? "true" : "false")); +} + +void writeValue(Stream & stream, const char *name, F32 value) +{ + writeLine(avar("\t%s = %g;", name, value)); +} + +void writeValue(Stream & stream, const char *name, const ColorF& color) +{ + writeLine(avar("\t%s = \"%g %g %g %g\";", name, color.red, color.green, color.blue, color.alpha)); +} + +void ColladaAppMaterial::write(Stream & stream) +{ + String cleanFile = cleanString(TSShapeLoader::getShapePath().getFileName()); + String cleanName = cleanString(getName()); + + writeLine(avar("singleton Material(%s_%s)", cleanFile.c_str(), cleanName.c_str())); + writeLine("{"); + + writeValue(stream, "mapTo", getName().c_str()); + writeLine(""); + + writeValue(stream, "diffuseMap[0]", diffuseMap.c_str()); + writeValue(stream, "normalMap[0]", normalMap.c_str()); + writeValue(stream, "specularMap[0]", specularMap.c_str()); + writeLine(""); + + writeValue(stream, "diffuseColor[0]", diffuseColor); + writeValue(stream, "specular[0]", specularColor); + writeValue(stream, "specularPower[0]", specularPower); + writeValue(stream, "pixelSpecular[0]", pixelSpecular); + writeValue(stream, "emissive[0]", emissive); + writeLine(""); + + writeValue(stream, "doubleSided", doubleSided); + writeValue(stream, "translucent", (bool)(flags & TSMaterialList::Translucent)); + writeValue(stream, "translucentBlendOp", (flags & TSMaterialList::Additive) ? "Add" : + ((flags & TSMaterialList::Subtractive) ? "Sub" : "None")); + + writeLine("};"); + writeLine(""); +} diff --git a/ts/collada/colladaAppMaterial.h b/ts/collada/colladaAppMaterial.h new file mode 100644 index 0000000..ef45078 --- /dev/null +++ b/ts/collada/colladaAppMaterial.h @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLADA_APP_MATERIAL_H_ +#define _COLLADA_APP_MATERIAL_H_ + +#ifndef _APPMATERIAL_H_ +#include "ts/loader/appMaterial.h" +#endif +#ifndef _COLLADA_EXTENSIONS_H_ +#include "ts/collada/colladaExtensions.h" +#endif + +class ColladaAppMaterial : public AppMaterial +{ +public: + const domMaterial* mat; ///< Collada element + domEffect* effect; ///< Collada element + ColladaExtension_effect* effectExt; ///< effect extension + String name; ///< Name of this material (cleaned) + + // Settings extracted from the Collada file, and optionally saved to materials.cs + String diffuseMap; + String normalMap; + String specularMap; + ColorF diffuseColor; + ColorF specularColor; + F32 specularPower; + bool pixelSpecular; + bool emissive; + bool doubleSided; + + ColladaAppMaterial(const domMaterial* pMat); + ~ColladaAppMaterial() { delete effectExt; } + + String getName() { return name; } + + void resolveFloat(const domCommon_float_or_param_type* value, F32* dst); + void resolveColor(const domCommon_color_or_texture_type* value, ColorF* dst); + + // Determine the material transparency + template void resolveTransparency(const T shader, F32* dst) + { + // Start out by getting the element. If it doesn't exist, 0.0 + // (no transparency => fully opaque) will be used + *dst = 0.0f; + resolveFloat(shader->getTransparency(), dst); + + // Multiply the transparency by the transparent color + if (shader->getTransparent() && shader->getTransparent()->getColor()) { + const domCommon_color_or_texture_type::domColor* color = shader->getTransparent()->getColor(); + if (shader->getTransparent()->getOpaque() == FX_OPAQUE_ENUM_A_ONE) { + // multiply by alpha value and invert (so 1.0 is fully opaque) + *dst = 1.0f - (*dst * color->getValue()[3]); + } + else { + // multiply by average of the RGB values + F32 avg = (color->getValue()[0] + color->getValue()[1] + color->getValue()[2]) / 3; + *dst *= avg; + } + } + } + + void write(Stream & stream); +}; + +#endif // _COLLADA_APP_MATERIAL_H_ diff --git a/ts/collada/colladaAppMesh.cpp b/ts/collada/colladaAppMesh.cpp new file mode 100644 index 0000000..94cf2cc --- /dev/null +++ b/ts/collada/colladaAppMesh.cpp @@ -0,0 +1,960 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" + +// Make GCC happy. Needs to have seen this before processing the +// hash table template. +struct VertTuple; +namespace DictHash +{ + inline U32 hash( const VertTuple& data ); +} + +#include "ts/collada/colladaExtensions.h" +#include "ts/collada/colladaAppMesh.h" +#include "ts/collada/colladaAppNode.h" +#include "ts/collada/colladaAppMaterial.h" + +#include "core/util/tDictionary.h" + +using namespace ColladaUtils; + +bool ColladaAppMesh::fixedSizeEnabled = false; +S32 ColladaAppMesh::fixedSize = 2; + +//----------------------------------------------------------------------------- +// Define a VertTuple dictionary to allow fast tuple lookups +namespace DictHash +{ + inline U32 hash(const VertTuple& data) + { + return (U32)data.vertex; + } +} + +typedef Map VertTupleMap; + +//----------------------------------------------------------------------------- +// Collada scatters the data required for geometry all over the place; this class +// helps to group it all together. +class MeshStreams +{ + /// Classify a single input + template + static void selectInput(T input, T sortedInputs[], S32 start, S32 end=-1) + { + if (end == -1) + end = start; + + // Get the set for this input + const domInputLocalOffset* localOffset = daeSafeCast(input); + domUint newSet = localOffset ? localOffset->getSet() : 0; + + // Add the input to the right place in the list (somewhere between start and end) + for (S32 i = start; i <= end; i++) { + const domInputLocalOffset* localOffset = daeSafeCast(sortedInputs[i]); + domUint set = localOffset ? localOffset->getSet() : 0xFFFFFFFF; + if (newSet < set) { + for (S32 j = i + 1; j <= end; j++) + sortedInputs[j] = sortedInputs[j-1]; + sortedInputs[i] = input; + return; + } + } + } + + /// Attempt to initialise a _SourceReader + template + bool initSourceReader(T input, _SourceReader& reader, const char* params[]) + { + if (!input) + return false; + + // Try to get the source element + const domSource* source = 0; + daeElement *element = input->getSource().getElement(); + if (element->getElementType() == COLLADA_TYPE::SOURCE) + source = daeSafeCast(element); + else if (element->getElementType() == COLLADA_TYPE::VERTICES) { + const domVertices* vertices = daeSafeCast(element); + if (!vertices || !vertices->getInput_array().getCount()) + return false; + source = daeSafeCast(vertices->getInput_array()[0]->getSource().getElement()); + } + if (!source) + return false; + + return reader.initFromSource(source, params); + } + +public: + + // The sources we want to read from the mesh stream. Can be any order, but + // sources of the same type (eg. UVs and UV2s) must be sequential (to allow + // ordering by set index) + enum { + Points, + Normals, + Colors, + UVs, + UV2s, + Joints, + Weights, + InvBindMatrices, + NumStreams + }; + + _SourceReader points; + _SourceReader normals; + _SourceReader colors; + _SourceReader uvs; + _SourceReader uv2s; + + _SourceReader joints; + _SourceReader weights; + _SourceReader invBindMatrices; + + /// Clear the mesh streams + void reset() + { + points.reset(); + normals.reset(); + colors.reset(); + uvs.reset(); + uv2s.reset(); + joints.reset(); + weights.reset(); + invBindMatrices.reset(); + } + + /// Classify a set of inputs by type and set number (needs to be a template + /// because Collada has two forms of input arrays that may be accessed in + /// an identical fashion, but the classes are unrelated. Sigh. + template + static void classifyInputs(const daeTArray& inputs, T sortedInputs[], U32 *maxOffset=0) + { + if (maxOffset) + *maxOffset = 0; + + // Clear output array + for (int i = 0; i < NumStreams; i++) + sortedInputs[i] = 0; + + // Separate inputs by type, and sort by set (ie. lowest TEXCOORD set becomes UV, + // next TEXCOORD set becomes UV2 etc) + for (int iInput = 0; iInput < inputs.getCount(); iInput++) { + + const T& input = inputs[iInput]; + const daeString semantic = input->getSemantic(); + + if (dStrEqual(semantic, "VERTEX")) selectInput(input, sortedInputs, Points); + else if (dStrEqual(semantic, "NORMAL")) selectInput(input, sortedInputs, Normals); + else if (dStrEqual(semantic, "COLOR")) selectInput(input, sortedInputs, Colors); + else if (dStrEqual(semantic, "TEXCOORD")) selectInput(input, sortedInputs, UVs, UV2s); + else if (dStrEqual(semantic, "JOINT")) selectInput(input, sortedInputs, Joints); + else if (dStrEqual(semantic, "WEIGHT")) selectInput(input, sortedInputs, Weights); + else if (dStrEqual(semantic, "INV_BIND_MATRIX")) selectInput(input, sortedInputs, InvBindMatrices); + + if (maxOffset) + { + const domInputLocalOffset* localOffset = daeSafeCast(input); + domUint offset = localOffset ? localOffset->getOffset() : 0; + if (offset > (*maxOffset)) + *maxOffset = offset; + } + } + } + + /// Read a set of inputs into the named sources. There may be multiple 'sets' + /// of COLOR or TEXCOORD (uvs) streams, but we are only interested in the + /// first COLOR set (ie. smallest set value), and the first 2 TEXCOORDS sets. + template + bool readInputs(const daeTArray& inputs) + { + // Sort inputs by type and set to find the ones we are interested in + T sortedInputs[NumStreams]; + classifyInputs(inputs, sortedInputs); + + // Attempt to initialise the SourceReaders + const char* vertex_params[] = { "X", "Y", "Z", "" }; + initSourceReader(sortedInputs[Points], points, vertex_params); + + const char* normal_params[] = { "X", "Y", "Z", "" }; + initSourceReader(sortedInputs[Normals], normals, normal_params); + + const char* color_params[] = { "R", "G", "B", "A", "" }; + initSourceReader(sortedInputs[Colors], colors, color_params); + + const char* uv_params[] = { "S", "T", "" }; + const char* uv_params2[] = { "U", "V", "" }; // some files use the nonstandard U,V param names + if (!initSourceReader(sortedInputs[UVs], uvs, uv_params)) + initSourceReader(sortedInputs[UVs], uvs, uv_params2); + if (!initSourceReader(sortedInputs[UV2s], uv2s, uv_params)) + initSourceReader(sortedInputs[UV2s], uv2s, uv_params2); + + const char* joint_params[] = { "JOINT", "" }; + initSourceReader(sortedInputs[Joints], joints, joint_params); + + const char* weight_params[] = { "WEIGHT", "" }; + initSourceReader(sortedInputs[Weights], weights, weight_params); + + const char* matrix_params[] = { "TRANSFORM", "" }; + initSourceReader(sortedInputs[InvBindMatrices], invBindMatrices, matrix_params); + + return true; + } +}; + +//------------------------------------------------------------------------------ + +ColladaAppMesh::ColladaAppMesh(const domInstance_geometry* instance, ColladaAppNode* node) + : instanceGeom(instance), instanceCtrl(0), appNode(node), geomExt(0) +{ + flags = 0; + numFrames = 0; + numMatFrames = 0; +} + +ColladaAppMesh::ColladaAppMesh(const domInstance_controller* instance, ColladaAppNode* node) + : instanceGeom(0), instanceCtrl(instance), appNode(node), geomExt(0) +{ + flags = 0; + numFrames = 0; + numMatFrames = 0; +} + +const char* ColladaAppMesh::getName(bool allowFixed) +{ + // Some exporters add a 'PIVOT' node between the mesh and the actual + // object node. Detect this and return the object node name instead + // of the pivot node. + const char* nodeName = appNode->getName(); + if (dStrEndsWith(nodeName, "PIVOT")) + nodeName = appNode->getParentName(); + + // If all geometry is being fixed to the same size, append the size + // to the name + return allowFixed && fixedSizeEnabled ? avar("%s %d", nodeName, fixedSize) : nodeName; +} + +MatrixF ColladaAppMesh::getMeshTransform(F32 time) +{ + return appNode->getNodeTransform(time); +} + +bool ColladaAppMesh::animatesVis(const AppSequence* appSeq) +{ + #define IS_VIS_ANIMATED(node) \ + (dynamic_cast(node)->nodeExt->visibility.isAnimated(appSeq->getStart(), appSeq->getEnd())) + + // Check if the node visibility is animated within the sequence interval + return IS_VIS_ANIMATED(appNode) || (appNode->appParent ? IS_VIS_ANIMATED(appNode->appParent) : false); +} + +bool ColladaAppMesh::animatesMatFrame(const AppSequence* appSeq) +{ + // Texture coordinates may be animated in two ways: + // - by animating the MAYA profile texture transform (diffuse texture) + // - by animating the morph weights for morph targets with different UVs + + // Check if the MAYA profile texture transform is animated + for (int iMat = 0; iMat < appMaterials.size(); iMat++) { + ColladaAppMaterial* appMat = static_cast(appMaterials[iMat]); + if (appMat->effectExt->animatesTextureTransform(appSeq->getStart(), appSeq->getEnd())) + return true; + } + + // Check that the morph weights are animated within the sequence interval, + // and that the morph targets have different UVs to the base geometry. + bool animated = false; + bool differentUVs = false; + if (const domMorph* morph = getMorph()) { + for (int iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) { + const domInputLocal* input = morph->getTargets()->getInput_array()[iInput]; + if (dStrEqual(input->getSemantic(), "MORPH_TARGET")) { + // @todo: Check if morph targets have different UVs to base geometry + differentUVs = false; + } + if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) { + const domSource* source = daeSafeCast(input->getSource().getElement()); + AnimatedFloatList weights(source ? source->getFloat_array() : 0); + animated = weights.isAnimated(appSeq->getStart(), appSeq->getEnd()); + } + } + } + + return (animated && differentUVs); +} + +bool ColladaAppMesh::animatesFrame(const AppSequence* appSeq) +{ + // Collada s ALWAYS contain vert positions, so just need to check if + // the morph weights are animated within the sequence interval + bool animated = false; + if (const domMorph* morph = getMorph()) { + for (int iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) { + const domInputLocal* input = morph->getTargets()->getInput_array()[iInput]; + if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) { + const domSource* source = daeSafeCast(input->getSource().getElement()); + AnimatedFloatList weights(source ? source->getFloat_array() : 0); + animated = weights.isAnimated(appSeq->getStart(), appSeq->getEnd()); + break; + } + } + } + return animated; +} + +F32 ColladaAppMesh::getVisValue(F32 t) +{ + #define GET_VIS(node) \ + (dynamic_cast(node)->nodeExt->visibility.getValue(t)) + + // Get the visibility of the mesh's node at time, 't' + return GET_VIS(appNode) * (appNode->appParent ? GET_VIS(appNode->appParent) : 1.0f); +} + +S32 ColladaAppMesh::addMaterial(const char* symbol) +{ + if (!symbol) + return TSDrawPrimitive::NoMaterial; + + // Lookup the symbol in the materials already bound to this geometry/controller + // instance + Map::Iterator itr = boundMaterials.find(symbol); + if (itr != boundMaterials.end()) + return itr->value; + + // Find the Collada material that this symbol maps to + U32 matIndex = TSDrawPrimitive::NoMaterial; + const domBind_material* binds = instanceGeom ? instanceGeom->getBind_material() : + instanceCtrl->getBind_material(); + if (binds) { + const domInstance_material_Array& matArray = binds->getTechnique_common()->getInstance_material_array(); + for (int iBind = 0; iBind < matArray.getCount(); iBind++) { + if (dStrEqual(matArray[iBind]->getSymbol(), symbol)) { + + // Find the index of the bound material in the shape global list + const domMaterial* mat = daeSafeCast(matArray[iBind]->getTarget().getElement()); + for (matIndex = 0; matIndex < appMaterials.size(); matIndex++) { + if (static_cast(appMaterials[matIndex])->mat == mat) + break; + } + + // Check if this material needs to be added to the shape global list + if (matIndex == appMaterials.size()) + appMaterials.push_back(new ColladaAppMaterial(mat)); + + break; + } + } + } + + // Add this symbol to the bound list for the mesh + boundMaterials.insert(StringTable->insert(symbol), matIndex); + return matIndex; +} + +void ColladaAppMesh::getPrimitives(const domGeometry* geometry) +{ + // Only do this once + if (primitives.size()) + return; + + // Read the extension + if (!geomExt) + geomExt = new ColladaExtension_geometry(geometry); + + // Get the supported primitive elements for this geometry, and warn + // about unsupported elements + Vector meshPrims; + const daeElementRefArray& contents = geometry->getMesh()->getContents(); + for (int iElem = 0; iElem < contents.getCount(); iElem++) { + + if (BasePrimitive::isPrimitive(contents[iElem])) { + if (BasePrimitive::isSupportedPrimitive(contents[iElem])) + meshPrims.push_back(BasePrimitive::get(contents[iElem])); + else { + daeErrorHandler::get()->handleWarning(avar("Collada <%s> element " + "in %s is not supported.", contents[iElem]->getElementName(), + _GetNameOrId(geometry))); + } + } + } + + VertTupleMap tupleMap; + + // Create Torque primitives + for (int iPrim = 0; iPrim < meshPrims.size(); iPrim++) { + + // Primitive element must have at least 1 triangle + const domListOfUInts* pTriData = meshPrims[iPrim]->getTriangleData(); + if (!pTriData) + continue; + + U32 numTriangles = pTriData->getCount() / meshPrims[iPrim]->getStride() / 3; + if (!numTriangles) + continue; + + // Create TSMesh primitive + primitives.increment(); + TSDrawPrimitive& primitive = primitives.last(); + primitive.start = indices.size(); + primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | + addMaterial(meshPrims[iPrim]->getMaterial()); + + // Get the AppMaterial associated with this primitive + ColladaAppMaterial* appMat = 0; + if (!(primitive.matIndex & TSDrawPrimitive::NoMaterial)) + appMat = static_cast(appMaterials[primitive.matIndex & TSDrawPrimitive::MaterialMask]); + + // Force the material to be double-sided if this geometry is double-sided. + if (geomExt->double_sided && appMat) + appMat->effectExt->double_sided = true; + + // Pre-allocate triangle indices + primitive.numElements = numTriangles * 3; + indices.setSize(indices.size() + primitive.numElements); + U32* dstIndex = indices.end() - primitive.numElements; + + // Determine the offset for each element type in the stream, and also the + // maximum input offset, which will be the number of indices per vertex we + // need to skip. + domInputLocalOffsetRef sortedInputs[MeshStreams::NumStreams]; + MeshStreams::classifyInputs(meshPrims[iPrim]->getInputs(), sortedInputs); + + S32 offsets[MeshStreams::NumStreams]; + for (S32 i = 0; i < MeshStreams::NumStreams; i++) + offsets[i] = sortedInputs[i] ? sortedInputs[i]->getOffset() : -1; + + // Loop through indices + const domUint* pSrcData = &(pTriData->get(0)); + + for (U32 iTri = 0; iTri < numTriangles; iTri++) { + + // If the next triangle could cause us to index across a 16-bit + // boundary, split this primitive and clear the tuple map to + // ensure primitives only index verts within a 16-bit range. + if (vertTuples.size() && + (((vertTuples.size()-1) ^ (vertTuples.size()+2)) & 0x10000)) + { + // Pad vertTuples up to the next 16-bit boundary + while (vertTuples.size() & 0xFFFF) + vertTuples.push_back(VertTuple(vertTuples.last())); + + // Split the primitive at the current triangle + S32 indicesRemaining = (numTriangles - iTri) * 3; + if (iTri > 0) + { + daeErrorHandler::get()->handleWarning(avar("Splitting primitive " + "in %s: too many verts for 16-bit indices.", _GetNameOrId(geometry))); + + primitives.last().numElements -= indicesRemaining; + primitives.push_back(TSDrawPrimitive(primitives.last())); + } + + primitives.last().numElements = indicesRemaining; + primitives.last().start = indices.size() - indicesRemaining; + + tupleMap.clear(); + } + + for (U32 v = 0; v < 3; v++) { + // Collect vert tuples into a single array so we can easily grab + // vertex data later. + VertTuple tuple; + tuple.prim = iPrim; + tuple.vertex = offsets[MeshStreams::Points] >= 0 ? pSrcData[offsets[MeshStreams::Points]] : -1; + tuple.normal = offsets[MeshStreams::Normals] >= 0 ? pSrcData[offsets[MeshStreams::Normals]] : -1; + tuple.color = offsets[MeshStreams::Colors] >= 0 ? pSrcData[offsets[MeshStreams::Colors]] : -1; + tuple.uv = offsets[MeshStreams::UVs] >= 0 ? pSrcData[offsets[MeshStreams::UVs]] : -1; + tuple.uv2 = offsets[MeshStreams::UV2s] >= 0 ? pSrcData[offsets[MeshStreams::UV2s]] : -1; + + VertTupleMap::Iterator itr = tupleMap.find(tuple); + if (itr == tupleMap.end()) + { + itr = tupleMap.insert(tuple, vertTuples.size()); + vertTuples.push_back(tuple); + } + + // Collada uses CCW for front face and Torque uses the opposite, so + // for normal (non-inverted) meshes, the indices are flipped. + if (appNode->invertMeshes) + dstIndex[v] = itr->value; + else + dstIndex[2 - v] = itr->value; + + pSrcData += meshPrims[iPrim]->getStride(); + } + dstIndex += 3; + } + } + + for (int iPrim = 0; iPrim < meshPrims.size(); iPrim++) + delete meshPrims[iPrim]; +} + +void ColladaAppMesh::getVertexData(const domGeometry* geometry, F32 time, const MatrixF& objectOffset, + Vector& v_points, + Vector& v_norms, + Vector& v_colors, + Vector& v_uvs, + Vector& v_uv2s, + bool appendValues) +{ + if (!primitives.size()) + return; + + MeshStreams streams; + S32 lastPrimitive = -1; + ColladaAppMaterial* appMat = 0; + + // Get the supported primitive elements for this geometry + Vector meshPrims; + const daeElementRefArray& contents = geometry->getMesh()->getContents(); + for (int iElem = 0; iElem < contents.getCount(); iElem++) { + if (BasePrimitive::isSupportedPrimitive(contents[iElem])) + meshPrims.push_back(BasePrimitive::get(contents[iElem])); + } + + // If appending values, pre-allocate the arrays + if (appendValues) { + v_points.setSize(v_points.size() + vertTuples.size()); + v_norms.setSize(v_norms.size() + vertTuples.size()); + v_uvs.setSize(v_uvs.size() + vertTuples.size()); + } + + // Get pointers to arrays + Point3F* points_array = &v_points[v_points.size() - vertTuples.size()]; + Point3F* norms_array = &v_norms[v_norms.size() - vertTuples.size()]; + Point2F* uvs_array = &v_uvs[v_uvs.size() - vertTuples.size()]; + ColorI* colors_array = NULL; + Point2F* uv2s_array = NULL; + + for (int iVert = 0; iVert < vertTuples.size(); iVert++) { + + const VertTuple& tuple = vertTuples[iVert]; + + // Change primitives? + if (tuple.prim != lastPrimitive) { + if (meshPrims.size() <= tuple.prim) { + daeErrorHandler::get()->handleError(avar("Failed to get vertex data " + "for %s. Primitives do not match base geometry.", geometry->getID())); + break; + } + + // Update vertex/normal/UV streams and get the new material index + streams.reset(); + streams.readInputs(meshPrims[tuple.prim]->getInputs()); + S32 matIndex = addMaterial(meshPrims[tuple.prim]->getMaterial()); + if (matIndex != TSDrawPrimitive::NoMaterial) + appMat = static_cast(appMaterials[matIndex]); + else + appMat = 0; + + lastPrimitive = tuple.prim; + } + + // If we are NOT appending values, only set the value if it actually exists + // in the mesh data stream. + + if (appendValues || ((tuple.vertex >= 0) && (tuple.vertex < streams.points.size()))) { + points_array[iVert] = streams.points.getPoint3FValue(tuple.vertex); + + // Flip verts for inverted meshes + if (appNode->invertMeshes) + points_array[iVert].z = -points_array[iVert].z; + + objectOffset.mulP(points_array[iVert]); + } + + if (appendValues || ((tuple.normal >= 0) && (tuple.normal < streams.normals.size()))) { + norms_array[iVert] = streams.normals.getPoint3FValue(tuple.normal); + + // Flip normals for inverted meshes + if (appNode->invertMeshes) + norms_array[iVert].z = -norms_array[iVert].z; + } + + if (appendValues || ((tuple.uv >= 0) && (tuple.uv < streams.uvs.size()))) { + uvs_array[iVert] = streams.uvs.getPoint2FValue(tuple.uv); + if (appMat) + appMat->effectExt->applyTextureTransform(uvs_array[iVert], time); + uvs_array[iVert].y = 1.0f - uvs_array[iVert].y; // Collada texcoords are upside down compared to TGE + } + + // The rest is non-required data... if it doesn't exist then don't append it. + + if ( (tuple.color >= 0) && (tuple.color < streams.colors.size())) + { + if ( !colors_array && iVert == 0 ) + { + v_colors.setSize(v_colors.size() + vertTuples.size()); + colors_array = &v_colors[v_colors.size() - vertTuples.size()]; + } + + if ( colors_array ) + colors_array[iVert] = streams.colors.getColorIValue(tuple.color); + } + + if ( (tuple.uv2 >= 0) && (tuple.uv2 < streams.uv2s.size()) ) + { + if ( !uv2s_array && iVert == 0 ) + { + v_uv2s.setSize(v_uv2s.size() + vertTuples.size()); + uv2s_array = &v_uv2s[v_uv2s.size() - vertTuples.size()]; + } + + if ( uv2s_array ) + { + uv2s_array[iVert] = streams.uv2s.getPoint2FValue(tuple.uv2); + if (appMat) + appMat->effectExt->applyTextureTransform(uv2s_array[iVert], time); + uv2s_array[iVert].y = 1.0f - uv2s_array[iVert].y; // Collada texcoords are upside down compared to TGE + } + } + } + + for (int iPrim = 0; iPrim < meshPrims.size(); iPrim++) + delete meshPrims[iPrim]; +} + +void ColladaAppMesh::getMorphVertexData(const domMorph* morph, F32 time, const MatrixF& objectOffset, + Vector& v_points, + Vector& v_norms, + Vector& v_colors, + Vector& v_uvs, + Vector& v_uv2s) +{ + // @todo: Could the base geometry (or any target geometry) also be a morph? + + // Get the target geometries and weights (could be animated) + Vector targetGeoms; + domListOfFloats targetWeights; + + for (int iInput = 0; iInput < morph->getTargets()->getInput_array().getCount(); iInput++) { + const domInputLocal* input = morph->getTargets()->getInput_array()[iInput]; + const domSource* source = daeSafeCast(input->getSource().getElement()); + + if (dStrEqual(input->getSemantic(), "MORPH_TARGET")) { + // Get the morph targets + _SourceReader srcTargets; + srcTargets.initFromSource(source); + + for (int iTarget = 0; iTarget < srcTargets.size(); iTarget++) { + // Lookup the element and add to the targets list + daeIDRef idref(srcTargets.getStringValue(iTarget)); + idref.setContainer(morph->getDocument()->getDomRoot()); + targetGeoms.push_back(daeSafeCast(idref.getElement())); + } + } + else if (dStrEqual(input->getSemantic(), "MORPH_WEIGHT")) { + // Get the (possibly animated) morph weight + targetWeights = AnimatedFloatList(source->getFloat_array()).getValue(time); + } + } + + // Check that we have a weight for each target + if (targetGeoms.size() != targetWeights.getCount()) + { + domController* ctrl = daeSafeCast(const_cast(morph)->getParent()); + Con::warnf("Mismatched morph targets and weights in %s.", _GetNameOrId(ctrl)); + + // Set unused targets to zero weighting (unused weights are ignored) + while (targetGeoms.size() > targetWeights.getCount()) + targetWeights.append(0.0f); + } + + // Get the base geometry and vertex data + const domGeometry* baseGeometry = daeSafeCast(morph->getSource().getElement()); + if (!baseGeometry) + return; + + getPrimitives(baseGeometry); + getVertexData(baseGeometry, time, objectOffset, v_points, v_norms, v_colors, v_uvs, v_uv2s, true); + + // Get pointers to the arrays of base geometry data + Point3F* points_array = &v_points[v_points.size() - vertTuples.size()]; + Point3F* norms_array = &v_norms[v_norms.size() - vertTuples.size()]; + Point2F* uvs_array = &v_uvs[v_uvs.size() - vertTuples.size()]; + ColorI* colors_array = v_colors.size() ? &v_colors[v_colors.size() - vertTuples.size()] : 0; + Point2F* uv2s_array = v_uv2s.size() ? &v_uv2s[v_uv2s.size() - vertTuples.size()] : 0; + + // Normalize base vertex data? + if (morph->getMethod() == MORPHMETHODTYPE_NORMALIZED) { + + F32 weightSum = 0.0f; + for (int iWeight = 0; iWeight < targetWeights.getCount(); iWeight++) { + weightSum += targetWeights[iWeight]; + } + + // Result = Base*(1.0-w1-w2 ... -wN) + w1*Target1 + w2*Target2 ... + wN*TargetN + weightSum = mClampF(1.0f - weightSum, 0.0f, 1.0f); + + for (int iVert = 0; iVert < vertTuples.size(); iVert++) { + points_array[iVert] *= weightSum; + norms_array[iVert] *= weightSum; + uvs_array[iVert] *= weightSum; + } + + if (uv2s_array) { + for (int iVert = 0; iVert < vertTuples.size(); iVert++) + uv2s_array[iVert] *= weightSum; + } + } + + // Interpolate using the target geometry and weights + for (int iTarget = 0; iTarget < targetGeoms.size(); iTarget++) { + + // Ignore empty weights + if (targetWeights[iTarget] == 0.0f) + continue; + + // Get target geometry data into temporary arrays + Vector targetPoints; + Vector targetNorms; + Vector targetUvs; + Vector targetColors; + Vector targetUv2s; + + // Copy base geometry into target geometry (will be used if target does + // not define normals or uvs) + targetPoints.set(points_array, vertTuples.size()); + targetNorms.set(norms_array, vertTuples.size()); + targetUvs.set(uvs_array, vertTuples.size()); + if (colors_array) + targetColors.set(colors_array, vertTuples.size()); + if (uv2s_array) + targetUv2s.set(uv2s_array, vertTuples.size()); + + getVertexData(targetGeoms[iTarget], time, objectOffset, targetPoints, targetNorms, targetColors, targetUvs, targetUv2s, false); + + // Combine with base geometry + for (int iVert = 0; iVert < vertTuples.size(); iVert++) { + points_array[iVert] += targetPoints[iVert] * targetWeights[iTarget]; + norms_array[iVert] += targetNorms[iVert] * targetWeights[iTarget]; + uvs_array[iVert] += targetUvs[iVert] * targetWeights[iTarget]; + } + + if (uv2s_array) { + for (int iVert = 0; iVert < vertTuples.size(); iVert++) + uv2s_array[iVert] += targetUv2s[iVert] * targetWeights[iTarget]; + } + if (colors_array) { + for (int iVert = 0; iVert < vertTuples.size(); iVert++) + colors_array[iVert] += targetColors[iVert] * (F32)targetWeights[iTarget]; + } + } +} + +void ColladaAppMesh::lockMesh(F32 t, const MatrixF& objectOffset) +{ + // Find the geometry element for this mesh. Could be one of 3 things: + // 1) a simple static mesh (Collada element) + // 2) a simple morph (some combination of static meshes) + // 3) a skin (skin geometry could also be a morph!) + daeElement* geometry = 0; + + if (instanceGeom) { + // Simple, static mesh + geometry = instanceGeom->getUrl().getElement(); + } + else if (instanceCtrl) { + const domController* ctrl = daeSafeCast(instanceCtrl->getUrl().getElement()); + if (!ctrl) { + daeErrorHandler::get()->handleWarning(avar("Failed to find " + "element for %s", getName())); + return; + } + else if (ctrl->getMorph()) { + // Morph controller + geometry = ctrl->getMorph(); + } + else { + // Skinned mesh: source geometry could be static geometry or a morph controller. + geometry = ctrl->getSkin()->getSource().getElement(); + if (geometry->getElementType() == COLLADA_TYPE::CONTROLLER) + geometry = daeSafeCast(geometry)->getMorph(); + } + } + + if (!geometry) { + daeErrorHandler::get()->handleWarning(avar("Failed to find source geometry " + "for %s", getName())); + return; + } + + // Now get the vertex data at the specified time + if (geometry->getElementType() == COLLADA_TYPE::GEOMETRY) { + getPrimitives(daeSafeCast(geometry)); + getVertexData(daeSafeCast(geometry), t, objectOffset, points, normals, colors, uvs, uv2s, true); + } + else if (geometry->getElementType() == COLLADA_TYPE::MORPH) { + getMorphVertexData(daeSafeCast(geometry), t, objectOffset, points, normals, colors, uvs, uv2s); + } + else { + daeErrorHandler::get()->handleWarning(avar("Unsupported geometry type " + "'<%s>' for %s", geometry->getElementName(), getName())); + } +} + +void ColladaAppMesh::lookupSkinData() +{ + // Only lookup skin data once + if (!isSkin() || weight.size()) + return; + + // Get the skin and vertex weight data + const domSkin* skin = daeSafeCast(instanceCtrl->getUrl().getElement())->getSkin(); + const domSkin::domVertex_weights& weightIndices = *(skin->getVertex_weights()); + const domListOfInts& weights_v = weightIndices.getV()->getValue(); + const domListOfUInts& weights_vcount = weightIndices.getVcount()->getValue(); + + MeshStreams streams; + streams.readInputs(skin->getJoints()->getInput_array()); + streams.readInputs(weightIndices.getInput_array()); + + MatrixF invObjOffset(objectOffset); + invObjOffset.inverse(); + + // Get the bind shape matrix + MatrixF bindShapeMatrix(true); + if (skin->getBind_shape_matrix()) + bindShapeMatrix = vecToMatrixF(skin->getBind_shape_matrix()->getValue()); + bindShapeMatrix.mul(invObjOffset); + + // Determine the offset into the vindices array for each vertex (since each + // vertex may have multiple [bone, weight] pairs in the array) + Vector vindicesOffset; + const domInt* vindices = (domInt*)weights_v.getRaw(0); + for (int iWeight = 0; iWeight < weights_vcount.getCount(); iWeight++) { + // Store the offset into the vindices array for this vertex + vindicesOffset.push_back(vindices - (domInt*)weights_v.getRaw(0)); + vindices += (weights_vcount[iWeight]*2); // 2 indices [bone, weight] per vert + } + + // Set vertex weights + bool tooManyWeightsWarning = false; + for (int iVert = 0; iVert < vertsPerFrame; iVert++) { + const domUint* vcount = (domUint*)weights_vcount.getRaw(0); + const domInt* vindices = (domInt*)weights_v.getRaw(0); + vindices += vindicesOffset[vertTuples[iVert].vertex]; + + // Limit the number of weights per bone (keep the N largest influences) + if (vcount[vertTuples[iVert].vertex] > TSSkinMesh::BatchData::maxBonePerVert) + { + if (!tooManyWeightsWarning) + { + tooManyWeightsWarning = true; + daeErrorHandler::get()->handleWarning(avar("At least one vertex has " + "too many bone weights. Limiting to the largest %d influences.", + TSSkinMesh::BatchData::maxBonePerVert)); + } + } + + for (int iWeight = 0; iWeight < vcount[vertTuples[iVert].vertex]; iWeight++) { + if (iWeight >= TSSkinMesh::BatchData::maxBonePerVert) + { + // Too many weights => find and replace the smallest one + S32 minIndex = weight.size() - TSSkinMesh::BatchData::maxBonePerVert; + F32 minWeight = weight[minIndex]; + for (S32 i = minIndex + 1; i < weight.size(); i++) + { + if (weight[i] < minWeight) + { + minWeight = weight[i]; + minIndex = i; + } + } + + boneIndex[minIndex] = vindices[iWeight*2]; + weight[minIndex] = streams.weights.getFloatValue(vindices[iWeight*2 + 1]); + } + else + { + vertexIndex.push_back(iVert); + boneIndex.push_back(vindices[iWeight*2]); + weight.push_back(streams.weights.getFloatValue(vindices[iWeight*2 + 1])); + } + } + } + + // Normalize vertex weights (force weights for each vert to sum to 1) + int iWeight = 0; + while (iWeight < weight.size()) { + // Find the last weight with the same vertex number, and sum all weights for + // that vertex + F32 invTotalWeight = 0; + int iLast; + for (iLast = iWeight; iLast < weight.size(); iLast++) { + if (vertexIndex[iLast] != vertexIndex[iWeight]) + break; + invTotalWeight += weight[iLast]; + } + + // Then normalize the vertex weights + invTotalWeight = 1.0f / invTotalWeight; + for (; iWeight < iLast; iWeight++) + weight[iWeight] *= invTotalWeight; + } + + // Add dummy AppNodes to allow Collada joints to be mapped to 3space nodes + bones.setSize(streams.joints.size()); + initialTransforms.setSize(streams.joints.size()); + for (int iJoint = 0; iJoint < streams.joints.size(); iJoint++) + { + const char* jointName = streams.joints.getStringValue(iJoint); + + // Lookup the joint element + const domNode* joint = 0; + if (instanceCtrl->getSkeleton_array().getCount()) { + // Search for the node using the as the base element + for (int iSkel = 0; iSkel < instanceCtrl->getSkeleton_array().getCount(); iSkel++) { + xsAnyURI skeleton = instanceCtrl->getSkeleton_array()[iSkel]->getValue(); + daeSIDResolver resolver(skeleton.getElement(), jointName); + joint = daeSafeCast(resolver.getElement()); + if (joint) + break; + } + } + else { + // Search for the node from the root level + daeSIDResolver resolver(skin->getDocument()->getDomRoot(), jointName); + joint = daeSafeCast(resolver.getElement()); + } + + if (!joint) { + daeErrorHandler::get()->handleWarning(avar("Failed to find bone '%s', " + "defaulting to instance_controller parent node '%s'", jointName, appNode->getName())); + joint = appNode->getDomNode(); + } + bones[iJoint] = new ColladaAppNode(joint); + + initialTransforms[iJoint] = objectOffset; + + // Bone scaling is generally ignored during import, since 3space only + // stores default node transform and rotation. Compensate for this by + // removing the scaling from the inverse bind transform as well + MatrixF invBind = streams.invBindMatrices.getMatrixFValue(iJoint); + if (!ColladaUtils::getOptions().ignoreNodeScale) + { + Point3F invScale = invBind.getScale(); + invScale.x = invScale.x ? (1.0f / invScale.x) : 0; + invScale.y = invScale.y ? (1.0f / invScale.y) : 0; + invScale.z = invScale.z ? (1.0f / invScale.z) : 0; + initialTransforms[iJoint].scale(invScale); + } + + // Inverted node coordinate spaces (negative scale factor) are corrected + // in ColladaAppNode::getNodeTransform, so need to apply the same operation + // here to match + if (m_matF_determinant(invBind) < 0.0f) + initialTransforms[iJoint].scale(Point3F(1, 1, -1)); + + initialTransforms[iJoint].mul(invBind); + initialTransforms[iJoint].mul(bindShapeMatrix); + } +} diff --git a/ts/collada/colladaAppMesh.h b/ts/collada/colladaAppMesh.h new file mode 100644 index 0000000..29b3927 --- /dev/null +++ b/ts/collada/colladaAppMesh.h @@ -0,0 +1,211 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLADA_APPMESH_H_ +#define _COLLADA_APPMESH_H_ + +#ifndef _TDICTIONARY_H_ +#include "core/tDictionary.h" +#endif +#ifndef _APPMESH_H_ +#include "ts/loader/appMesh.h" +#endif +#ifndef _TSSHAPELOADER_H_ +#include "ts/loader/tsShapeLoader.h" +#endif +#ifndef _COLLADA_APPNODE_H_ +#include "ts/collada/colladaAppNode.h" +#endif +#ifndef _COLLADA_EXTENSIONS_H_ +#include "ts/collada/colladaExtensions.h" +#endif + +//----------------------------------------------------------------------------- +// Torque unifies the vert position, normal and UV values, so that a single index +// uniquely identifies all 3 elements. A triangle then contains just 3 indices, +// and from that we can get the 3 positions, 3 normals and 3 UVs. +// +// for i=1:3 +// index = indices[triangle.start + i] +// points[index], normals[index], uvs[index] +// +// Collada does not use unified vertex streams, (each triangle needs 9 indices), +// so this structure is used to map a single VertTuple index to 3 indices into +// the Collada streams. The Collada (and Torque) primitive index is also stored +// because the Collada document may use different streams for different primitives. +// +// For morph geometry, we can use the same array of VertTuples to access the base +// AND all of the target geometries because they MUST have the same topology. +struct VertTuple +{ + S32 prim, vertex, normal, color, uv, uv2; + VertTuple(): prim(-1), vertex(-1), normal(-1), color(-1), uv(-1), uv2(-1) {} + bool operator==(const VertTuple& p) const + { + return prim == p.prim && + vertex == p.vertex && + color == p.color && + normal == p.normal && + uv == p.uv && + uv2 == p.uv2; + } +}; + +class ColladaAppMesh : public AppMesh +{ + typedef AppMesh Parent; + +protected: + class ColladaAppNode* appNode; ///< Pointer to the node that owns this mesh + const domInstance_geometry* instanceGeom; + const domInstance_controller* instanceCtrl; + ColladaExtension_geometry* geomExt; ///< geometry extension + + Vector vertTuples; ///< + Map boundMaterials; ///< Local map of symbols to materials + + static bool fixedSizeEnabled; ///< Set to true to fix the detail size to a particular value for all geometry + static S32 fixedSize; ///< The fixed detail size value for all geometry + + //----------------------------------------------------------------------- + + /// Get the morph controller for this mesh (if any) + const domMorph* getMorph() + { + if (instanceCtrl) { + const domController* ctrl = daeSafeCast(instanceCtrl->getUrl().getElement()); + if (ctrl->getSkin()) + ctrl = daeSafeCast(ctrl->getSkin()->getSource().getElement()); + return ctrl ? ctrl->getMorph() : NULL; + } + return NULL; + } + + S32 addMaterial(const char* symbol); + + bool checkGeometryType(const daeElement* element); + void getPrimitives(const domGeometry* geometry); + + void getVertexData( const domGeometry* geometry, F32 time, const MatrixF& objectOffset, + Vector& points, Vector& norms, Vector& colors, + Vector& uvs, Vector& uv2s, bool appendValues); + + void getMorphVertexData( const domMorph* morph, F32 time, const MatrixF& objectOffset, + Vector& points, Vector& norms, Vector& colors, + Vector& uvs, Vector& uv2s ); + +public: + + ColladaAppMesh(const domInstance_geometry* instance, ColladaAppNode* node); + ColladaAppMesh(const domInstance_controller* instance, ColladaAppNode* node); + ~ColladaAppMesh() + { + delete geomExt; + } + + static void fixDetailSize(bool fixed, S32 size=2) + { + fixedSizeEnabled = fixed; + fixedSize = size; + } + + /// Get the name of this mesh + /// + /// @return A string containing the name of this mesh + const char *getName(bool allowFixed=true); + + //----------------------------------------------------------------------- + + /// Get a floating point property value + /// + /// @param propName Name of the property to get + /// @param defaultVal Reference to variable to hold return value + /// + /// @return True if a value was set, false if not + bool getFloat(const char *propName, F32 &defaultVal) + { + return appNode->getFloat(propName,defaultVal); + } + + /// Get an integer property value + /// + /// @param propName Name of the property to get + /// @param defaultVal Reference to variable to hold return value + /// + /// @return True if a value was set, false if not + bool getInt(const char *propName, S32 &defaultVal) + { + return appNode->getInt(propName,defaultVal); + } + + /// Get a boolean property value + /// + /// @param propName Name of the property to get + /// @param defaultVal Reference to variable to hold return value + /// + /// @return True if a value was set, false if not + bool getBool(const char *propName, bool &defaultVal) + { + return appNode->getBool(propName,defaultVal); + } + + /// Return true if this mesh is a skin + bool isSkin() + { + if (instanceCtrl) { + const domController* ctrl = daeSafeCast(instanceCtrl->getUrl().getElement()); + if (ctrl && ctrl->getSkin() && + (ctrl->getSkin()->getVertex_weights()->getV()->getValue().getCount() > 0)) + return true; + } + return false; + } + + /// Get the skin data: bones, vertex weights etc + void lookupSkinData(); + + /// Check if the mesh visibility is animated + /// + /// @param appSeq Start/end time to check + /// + /// @return True if the mesh visibility is animated, false if not + bool animatesVis(const AppSequence* appSeq); + + /// Check if the material used by this mesh is animated + /// + /// @param appSeq Start/end time to check + /// + /// @return True if the material is animated, false if not + bool animatesMatFrame(const AppSequence* appSeq); + + /// Check if the mesh is animated + /// + /// @param appSeq Start/end time to check + /// + /// @return True if the mesh is animated, false if not + bool animatesFrame(const AppSequence* appSeq); + + /// Generate the vertex, normal and triangle data for the mesh. + /// + /// @param time Time at which to generate the mesh data + /// @param objectOffset Transform to apply to the generated data (bounds transform) + void lockMesh(F32 time, const MatrixF& objectOffset); + + /// Get the transform of this mesh at a certain time + /// + /// @param time Time at which to get the transform + /// + /// @return The mesh transform at the specified time + MatrixF getMeshTransform(F32 time); + + /// Get the visibility of this mesh at a certain time + /// + /// @param time Time at which to get visibility info + /// + /// @return Visibility from 0 (invisible) to 1 (opaque) + F32 getVisValue(F32 time); +}; + +#endif // _COLLADA_APPMESH_H_ diff --git a/ts/collada/colladaAppNode.cpp b/ts/collada/colladaAppNode.cpp new file mode 100644 index 0000000..4d1fbfd --- /dev/null +++ b/ts/collada/colladaAppNode.cpp @@ -0,0 +1,194 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifdef _MSC_VER +#pragma warning(disable : 4706) // disable warning about assignment within conditional +#endif + +#include "ts/loader/appSequence.h" +#include "ts/collada/colladaExtensions.h" +#include "ts/collada/colladaAppNode.h" +#include "ts/collada/colladaAppMesh.h" + + +ColladaAppNode::ColladaAppNode(const domNode* node, ColladaAppNode* parent) + : p_domNode(node), appParent(parent), nodeExt(new ColladaExtension_node(node)), + lastNodeTransformTime(-1), defaultTransformValid(false), invertMeshes(false) +{ + mName = dStrdup(_GetNameOrId(node)); + mParentName = dStrdup(parent ? parent->getName() : "ROOT"); + + // Extract user properties from the extension as "name=value" pairs + const char *delims = "=\r\n"; + char* properties = dStrdup(nodeExt->user_properties); + char* token = dStrtok(properties, delims); + do { + // Trim leading whitespace from the name string + const char* name = token; + while (name && dIsspace(*name)) + name++; + + // Get the value string + if ( (token = dStrtok(NULL, delims)) ) + mProps.insert(StringTable->insert(name), dAtof(token)); + } while ( (token = dStrtok(NULL, delims)) ); + + dFree( properties ); + + // Create vector of transform elements + for (int iChild = 0; iChild < node->getContents().getCount(); iChild++) { + switch (node->getContents()[iChild]->getElementType()) { + case COLLADA_TYPE::TRANSLATE: + case COLLADA_TYPE::ROTATE: + case COLLADA_TYPE::SCALE: + case COLLADA_TYPE::SKEW: + case COLLADA_TYPE::MATRIX: + case COLLADA_TYPE::LOOKAT: + nodeTransforms.increment(); + nodeTransforms.last().element = node->getContents()[iChild]; + break; + } + } +} + +// Get all child nodes +void ColladaAppNode::buildChildList() +{ + // Process children: collect and elements + for (int iChild = 0; iChild < p_domNode->getContents().getCount(); iChild++) { + + daeElement* child = p_domNode->getContents()[iChild]; + switch (child->getElementType()) { + + case COLLADA_TYPE::NODE: + { + domNode* node = daeSafeCast(child); + mChildNodes.push_back(new ColladaAppNode(node, this)); + break; + } + + case COLLADA_TYPE::INSTANCE_NODE: + { + domInstance_node* instanceNode = daeSafeCast(child); + domNode* node = daeSafeCast(instanceNode->getUrl().getElement()); + mChildNodes.push_back(new ColladaAppNode(node, this)); + break; + } + } + } +} + +// Get all geometry attached to this node +void ColladaAppNode::buildMeshList() +{ + // Process children: collect and elements + for (int iChild = 0; iChild < p_domNode->getContents().getCount(); iChild++) { + + daeElement* child = p_domNode->getContents()[iChild]; + switch (child->getElementType()) { + + case COLLADA_TYPE::INSTANCE_GEOMETRY: + mMeshes.push_back(new ColladaAppMesh(daeSafeCast(child), this)); + break; + + case COLLADA_TYPE::INSTANCE_CONTROLLER: + mMeshes.push_back(new ColladaAppMesh(daeSafeCast(child), this)); + break; + } + } +} + +bool ColladaAppNode::animatesTransform(const AppSequence* appSeq) +{ + // Check if any of this node's transform elements are animated during the + // sequence interval + for (int iTxfm = 0; iTxfm < nodeTransforms.size(); iTxfm++) { + if (nodeTransforms[iTxfm].isAnimated(appSeq->getStart(), appSeq->getEnd())) + return true; + } + return false; +} + +/// Get the world transform of the node at the specified time +MatrixF ColladaAppNode::getNodeTransform(F32 time) +{ + // Avoid re-computing the transform if possible + if (defaultTransformValid && time == TSShapeLoader::DefaultTime) + { + return defaultNodeTransform; + } + else if (time != lastNodeTransformTime) + { + lastNodeTransform = getTransform(time); + + // Check for inverted node coordinate spaces => can happen when modelers + // use the 'mirror' tool in their 3d app. Shows up as negative + // transforms in the collada model. + if (m_matF_determinant(lastNodeTransform) < 0.0f) + { + // Mark this node as inverted so we can mirror mesh geometry, then + // de-invert the transform matrix + invertMeshes = true; + lastNodeTransform.scale(Point3F(1, 1, -1)); + } + + // Cache the transform for subsequent calls + lastNodeTransformTime = time; + if (time == TSShapeLoader::DefaultTime) + { + defaultTransformValid = true; + defaultNodeTransform = lastNodeTransform; + } + } + + return lastNodeTransform; +} + +MatrixF ColladaAppNode::getTransform(F32 time) +{ + MatrixF transform; + + if (appParent) { + // Get parent node's transform + transform = appParent->getTransform(time); + } + else { + // no parent (ie. root level) => scale by global shape + transform.identity(); + transform.scale(ColladaUtils::getOptions().unit); + ColladaUtils::convertTransform(transform); + } + + // Multiply by local node transform elements + for (int iTxfm = 0; iTxfm < nodeTransforms.size(); iTxfm++) { + + MatrixF mat(true); + + // Convert the transform element to a MatrixF + switch (nodeTransforms[iTxfm].element->getElementType()) { + case COLLADA_TYPE::TRANSLATE: mat = vecToMatrixF(nodeTransforms[iTxfm].getValue(time)); break; + case COLLADA_TYPE::SCALE: mat = vecToMatrixF(nodeTransforms[iTxfm].getValue(time)); break; + case COLLADA_TYPE::ROTATE: mat = vecToMatrixF(nodeTransforms[iTxfm].getValue(time)); break; + case COLLADA_TYPE::MATRIX: mat = vecToMatrixF(nodeTransforms[iTxfm].getValue(time)); break; + case COLLADA_TYPE::SKEW: mat = vecToMatrixF(nodeTransforms[iTxfm].getValue(time)); break; + case COLLADA_TYPE::LOOKAT: mat = vecToMatrixF(nodeTransforms[iTxfm].getValue(time)); break; + } + + // Remove node scaling (but keep reflections) if desired + if (ColladaUtils::getOptions().ignoreNodeScale) + { + Point3F invScale = mat.getScale(); + invScale.x = invScale.x ? (1.0f / invScale.x) : 0; + invScale.y = invScale.y ? (1.0f / invScale.y) : 0; + invScale.z = invScale.z ? (1.0f / invScale.z) : 0; + mat.scale(invScale); + } + + // Post multiply the animated transform + transform.mul(mat); + } + + return transform; +} diff --git a/ts/collada/colladaAppNode.h b/ts/collada/colladaAppNode.h new file mode 100644 index 0000000..20471ae --- /dev/null +++ b/ts/collada/colladaAppNode.h @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLADA_APPNODE_H_ +#define _COLLADA_APPNODE_H_ + +#ifndef _TDICTIONARY_H_ +#include "core/tDictionary.h" +#endif +#ifndef _APPNODE_H_ +#include "ts/loader/appNode.h" +#endif +#ifndef _COLLADA_EXTENSIONS_H_ +#include "ts/collada/colladaExtensions.h" +#endif + +class ColladaAppNode : public AppNode +{ + typedef AppNode Parent; + friend class ColladaAppMesh; + + MatrixF getTransform(F32 time); + void buildMeshList(); + void buildChildList(); + +protected: + + const domNode* p_domNode; ///< Pointer to the node in the Collada DOM + ColladaAppNode* appParent; ///< Parent node in Collada-space + ColladaExtension_node* nodeExt; ///< node extension + Vector nodeTransforms; ///< Ordered vector of node transform elements (scale, translate etc) + bool invertMeshes; ///< True if this node's coordinate space is inverted (left handed) + + Map mProps; ///< Hash of float properties (converted to int or bool as needed) + + F32 lastNodeTransformTime; ///< Time of the last transform lookup + MatrixF lastNodeTransform; ///< Last transform lookup + bool defaultTransformValid; ///< Flag indicating whether the defaultNodeTransform is valid + MatrixF defaultNodeTransform; ///< Transform at DefaultTime (t=0) + +public: + + ColladaAppNode(const domNode* node, ColladaAppNode* parent = 0); + virtual ~ColladaAppNode() + { + delete nodeExt; + mProps.clear(); + } + + const domNode* getDomNode() const { return p_domNode; } + + //----------------------------------------------------------------------- + const char *getName() { return mName; } + const char *getParentName() { return mParentName; } + + bool isEqual(AppNode* node) + { + const ColladaAppNode* appNode = dynamic_cast(node); + return (appNode && (appNode->p_domNode == p_domNode)); + } + + // Property look-ups: only float properties are stored, the rest are + // converted from floats as needed + bool getFloat(const char* propName, F32& defaultVal) + { + Map::Iterator itr = mProps.find(propName); + if (itr != mProps.end()) + defaultVal = itr->value; + return false; + } + bool getInt(const char* propName, S32& defaultVal) + { + F32 value = defaultVal; + bool ret = getFloat(propName, value); + defaultVal = (S32)value; + return ret; + } + bool getBool(const char* propName, bool& defaultVal) + { + F32 value = defaultVal; + bool ret = getFloat(propName, value); + defaultVal = (value != 0); + return ret; + } + + MatrixF getNodeTransform(F32 time); + bool animatesTransform(const AppSequence* appSeq); + bool isParentRoot() { return (appParent == NULL); } +}; + +#endif // _COLLADA_APPNODE_H_ diff --git a/ts/collada/colladaAppSequence.cpp b/ts/collada/colladaAppSequence.cpp new file mode 100644 index 0000000..3f7a2a3 --- /dev/null +++ b/ts/collada/colladaAppSequence.cpp @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "ts/collada/colladaExtensions.h" +#include "ts/collada/colladaAppSequence.h" + + +ColladaAppSequence::ColladaAppSequence(const domAnimation_clip* clip) + : pClip(clip), clipExt(new ColladaExtension_animation_clip(clip)) +{ + seqStart = pClip->getStart(); + seqEnd = pClip->getEnd(); +} + +ColladaAppSequence::~ColladaAppSequence() +{ + delete clipExt; +} + +const char* ColladaAppSequence::getName() const +{ + return _GetNameOrId(pClip); +} + +S32 ColladaAppSequence::getNumTriggers() +{ + return clipExt->triggers.size(); +} + +void ColladaAppSequence::getTrigger(S32 index, TSShape::Trigger& trigger) +{ + trigger.pos = clipExt->triggers[index].time; + trigger.state = clipExt->triggers[index].state; +} + +U32 ColladaAppSequence::getFlags() const +{ + U32 flags = 0; + if (clipExt->cyclic) flags |= TSShape::Cyclic; + if (clipExt->blend) flags |= TSShape::Blend; + return flags; +} + +F32 ColladaAppSequence::getPriority() +{ + return clipExt->priority; +} + +F32 ColladaAppSequence::getBlendRefTime() +{ + return clipExt->blendReferenceTime; +} diff --git a/ts/collada/colladaAppSequence.h b/ts/collada/colladaAppSequence.h new file mode 100644 index 0000000..8a0c1a1 --- /dev/null +++ b/ts/collada/colladaAppSequence.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLADA_APPSEQUENCE_H_ +#define _COLLADA_APPSEQUENCE_H_ + +#ifndef _APPSEQUENCE_H_ +#include "ts/loader/appSequence.h" +#endif + +class domAnimation_clip; +class ColladaExtension_animation_clip; + +class ColladaAppSequence : public AppSequence +{ + const domAnimation_clip* pClip; + ColladaExtension_animation_clip* clipExt; + + F32 seqStart; + F32 seqEnd; + +public: + ColladaAppSequence(const domAnimation_clip* clip); + ~ColladaAppSequence(); + + const domAnimation_clip* getClip() const { return pClip; } + + S32 getNumTriggers(); + void getTrigger(S32 index, TSShape::Trigger& trigger); + + const char* getName() const; + + F32 getStart() const { return seqStart; } + F32 getEnd() const { return seqEnd; } + void setEnd(F32 end) { seqEnd = end; } + + U32 getFlags() const; + F32 getPriority(); + F32 getBlendRefTime(); +}; + +#endif // _COLLADA_APPSEQUENCE_H_ diff --git a/ts/collada/colladaExtensions.cpp b/ts/collada/colladaExtensions.cpp new file mode 100644 index 0000000..e51e6a5 --- /dev/null +++ b/ts/collada/colladaExtensions.cpp @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "math/mRandom.h" +#include "ts/collada/colladaExtensions.h" + +/// Check if any of the MAYA texture transform elements are animated within +/// the interval +bool ColladaExtension_effect::animatesTextureTransform(F32 start, F32 end) +{ + return repeatU.isAnimated(start, end) || repeatV.isAnimated(start, end) || + offsetU.isAnimated(start, end) || offsetV.isAnimated(start, end) || + rotateUV.isAnimated(start, end) || noiseU.isAnimated(start, end) || + noiseV.isAnimated(start, end); +} + +/// Apply the MAYA texture transform to the given UV coordinates +void ColladaExtension_effect::applyTextureTransform(Point2F& uv, F32 time) +{ + // This function will be called for every tvert, every frame. So cache the + // texture transform parameters to avoid interpolating them every call (since + // they are constant for all tverts for a given 't') + if (time != lastAnimTime) { + // Update texture transform + textureTransform.set(EulerF(0, 0, rotateUV.getValue(time))); + textureTransform.setPosition(Point3F( + offsetU.getValue(time) + noiseU.getValue(time)*gRandGen.randF(), + offsetV.getValue(time) + noiseV.getValue(time)*gRandGen.randF(), + 0)); + textureTransform.scale(Point3F(repeatU.getValue(time), repeatV.getValue(time), 1.0f)); + + lastAnimTime = time; + } + + // Apply texture transform + Point3F result; + textureTransform.mulP(Point3F(uv.x, uv.y, 0), &result); + + uv.x = result.x; + uv.y = result.y; +} diff --git a/ts/collada/colladaExtensions.h b/ts/collada/colladaExtensions.h new file mode 100644 index 0000000..a00524e --- /dev/null +++ b/ts/collada/colladaExtensions.h @@ -0,0 +1,282 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLADA_EXTENSIONS_H_ +#define _COLLADA_EXTENSIONS_H_ + +#ifndef _TSSHAPE_LOADER_H_ +#include "ts/loader/tsShapeLoader.h" +#endif +#ifndef _COLLADA_UTILS_H_ +#include "ts/collada/colladaUtils.h" +#endif + +//----------------------------------------------------------------------------- +// Collada allows custom data to be included with many elements using the +// tag, followed by one or more named technique profiles. eg. +// +// +// +// value0 +// value1 +// ... +// +// value0 +// value1 +// ... +// +// This class provides an easy way to read the custom parameters into a strongly +// typed subclass. +class ColladaExtension +{ + // Helper macro to simplify getting named parameters + #define GET_EXTRA_PARAM(param, defaultVal) \ + get(#param, param, defaultVal) + +protected: + const domTechnique* pTechnique; + + /// Find the technique with the named profile + template const domTechnique* findExtraTechnique(const T* element, const char* name) const + { + if (element) { + for (int iExt = 0; iExt < element->getExtra_array().getCount(); iExt++) { + for (int iTech = 0; iTech < element->getExtra_array()[iExt]->getTechnique_array().getCount(); iTech++) { + if (dStrEqual(element->getExtra_array()[iExt]->getTechnique_array()[iTech]->getProfile(), name)) + return element->getExtra_array()[iExt]->getTechnique_array()[iTech]; + } + } + } + return NULL; + } + + /// The element does not define an extra_array, so need a specialized + /// version of the template + const domTechnique* findExtraTechnique( + const domCommon_color_or_texture_type_complexType::domTexture* element, const char* name) const + { + if (element && element->getExtra()) { + for (int iTech = 0; iTech < element->getExtra()->getTechnique_array().getCount(); iTech++) { + if (dStrEqual(element->getExtra()->getTechnique_array()[iTech]->getProfile(), name)) + return element->getExtra()->getTechnique_array()[iTech]; + } + } + return NULL; + } + + /// Find the parameter with the given name + const domAny* findParam(const char* name) + { + if (pTechnique) { + // search the technique contents for the desired parameter + for (int iParam = 0; iParam < pTechnique->getContents().getCount(); iParam++) { + const domAny* param = daeSafeCast(pTechnique->getContents()[iParam]); + if (param && !dStrcmp(param->getElementName(), name)) + return param; + } + } + return NULL; + } + + /// Get the value of the named parameter (use defaultVal if parameter not found) + template void get(const char* name, T& value, T defaultVal) + { + value = defaultVal; + if (const domAny* param = findParam(name)) + value = convert(param->getValue()); + } + + /// Get the value of the named animated parameter (use defaultVal if parameter not found) + template void get(const char* name, AnimatedElement& value, T defaultVal) + { + value.defaultVal = defaultVal; + if (const domAny* param = findParam(name)) + value.element = param; + } + +public: + ColladaExtension() : pTechnique(0) { } + virtual ~ColladaExtension() { } +}; + +/// Extensions for the element (and its children) +class ColladaExtension_effect : public ColladaExtension +{ + // Cached texture transform + F32 lastAnimTime; + MatrixF textureTransform; + +public: + //---------------------------------- + // + // MAX3D profile elements + bool double_sided; + + //---------------------------------- + // . + // GOOGLEEARTH profile elements + //bool double_sided; + + //---------------------------------- + // ..... + // MAYA profile elements + bool wrapU, wrapV; + bool mirrorU, mirrorV; + AnimatedFloat coverageU, coverageV; + AnimatedFloat translateFrameU, translateFrameV; + AnimatedFloat rotateFrame; + AnimatedBool stagger; // @todo: not supported yet + AnimatedFloat repeatU, repeatV; + AnimatedFloat offsetU, offsetV; + AnimatedFloat rotateUV; + AnimatedFloat noiseU, noiseV; + + //---------------------------------- + // .. + // FCOLLADA profile elements + domFx_sampler2D_common_complexType* bumpSampler; + +public: + ColladaExtension_effect(const domEffect* effect) + : lastAnimTime(-1), textureTransform(true), bumpSampler(0) + { + //---------------------------------- + // + // MAX3D profile + pTechnique = findExtraTechnique(effect, "MAX3D"); + GET_EXTRA_PARAM(double_sided, false); + + //---------------------------------- + // . + const domProfile_COMMON* profileCommon = ColladaUtils::findEffectCommonProfile(effect); + + // GOOGLEEARTH profile (same double_sided element) + pTechnique = findExtraTechnique(profileCommon, "GOOGLEEARTH"); + GET_EXTRA_PARAM(double_sided, double_sided); + + //---------------------------------- + // ..... + const domCommon_color_or_texture_type_complexType* domDiffuse = ColladaUtils::findEffectDiffuse(effect); + const domFx_sampler2D_common_complexType* sampler2D = ColladaUtils::getTextureSampler(effect, domDiffuse); + + // Use the sampler2D to set default values for wrap/mirror flags + wrapU = wrapV = true; + mirrorU = mirrorV = false; + if (sampler2D) { + domFx_sampler2D_common_complexType::domWrap_s* wrap_s = sampler2D->getWrap_s(); + domFx_sampler2D_common_complexType::domWrap_t* wrap_t = sampler2D->getWrap_t(); + + mirrorU = (wrap_s && wrap_s->getValue() == FX_SAMPLER_WRAP_COMMON_MIRROR); + wrapU = (mirrorU || !wrap_s || (wrap_s->getValue() == FX_SAMPLER_WRAP_COMMON_WRAP)); + mirrorV = (wrap_t && wrap_t->getValue() == FX_SAMPLER_WRAP_COMMON_MIRROR); + wrapV = (mirrorV || !wrap_t || (wrap_t->getValue() == FX_SAMPLER_WRAP_COMMON_WRAP)); + } + + // MAYA profile + pTechnique = findExtraTechnique(domDiffuse ? domDiffuse->getTexture() : 0, "MAYA"); + GET_EXTRA_PARAM(wrapU, wrapU); GET_EXTRA_PARAM(wrapV, wrapV); + GET_EXTRA_PARAM(mirrorU, mirrorU); GET_EXTRA_PARAM(mirrorV, mirrorV); + GET_EXTRA_PARAM(coverageU, 1.0); GET_EXTRA_PARAM(coverageV, 1.0); + GET_EXTRA_PARAM(translateFrameU, 0.0); GET_EXTRA_PARAM(translateFrameV, 0.0); + GET_EXTRA_PARAM(rotateFrame, 0.0); + GET_EXTRA_PARAM(stagger, false); + GET_EXTRA_PARAM(repeatU, 1.0); GET_EXTRA_PARAM(repeatV, 1.0); + GET_EXTRA_PARAM(offsetU, 0.0); GET_EXTRA_PARAM(offsetV, 0.0); + GET_EXTRA_PARAM(rotateUV, 0.0); + GET_EXTRA_PARAM(noiseU, 0.0); GET_EXTRA_PARAM(noiseV, 0.0); + + // FCOLLADA profile + if (profileCommon) { + pTechnique = findExtraTechnique((const domProfile_COMMON::domTechnique*)profileCommon->getTechnique(), "FCOLLADA"); + if (pTechnique) { + domAny* bump = daeSafeCast(const_cast(pTechnique)->getChild("bump")); + if (bump) { + domAny* bumpTexture = daeSafeCast(bump->getChild("texture")); + if (bumpTexture) { + daeSIDResolver resolver(const_cast(effect), bumpTexture->getAttribute("texture").c_str()); + domCommon_newparam_type* param = daeSafeCast(resolver.getElement()); + if (param) + bumpSampler = param->getSampler2D(); + } + } + } + } + } + + /// Check if any of the MAYA texture transform elements are animated within + /// the interval + bool animatesTextureTransform(F32 start, F32 end); + + /// Apply the MAYA texture transform to the given UV coordinates + void applyTextureTransform(Point2F& uv, F32 time); +}; + +/// Extensions for the element +class ColladaExtension_node : public ColladaExtension +{ +public: + // FCOLLADA profile elements + AnimatedFloat visibility; + const char* user_properties; + + ColladaExtension_node(const domNode* node) + { + // FCOLLADA profile + pTechnique = findExtraTechnique(node, "FCOLLADA"); + GET_EXTRA_PARAM(visibility, 1.0); + GET_EXTRA_PARAM(user_properties, ""); + } +}; + +/// Extensions for the element +class ColladaExtension_geometry : public ColladaExtension +{ +public: + // MAYA profile elements + bool double_sided; + + ColladaExtension_geometry(const domGeometry* geometry) + { + // MAYA profile + pTechnique = findExtraTechnique(geometry, "MAYA"); + GET_EXTRA_PARAM(double_sided, false); + } +}; + +// Extensions for the element +class ColladaExtension_animation_clip : public ColladaExtension +{ +public: + struct Trigger { + F32 time; + S32 state; + }; + + // Torque profile elements (none of these are animatable) + S32 num_triggers; + Vector triggers; + bool cyclic; + bool blend; + F32 blendReferenceTime; + F32 priority; + + ColladaExtension_animation_clip(const domAnimation_clip* clip) + { + // Torque profile + pTechnique = findExtraTechnique(clip, "Torque"); + GET_EXTRA_PARAM(num_triggers, 0); + for (int iTrigger = 0; iTrigger < num_triggers; iTrigger++) { + triggers.increment(); + get(avar("trigger_time%d", iTrigger), triggers.last().time, 0.0f); + get(avar("trigger_state%d", iTrigger), triggers.last().state, 0); + } + GET_EXTRA_PARAM(cyclic, false); + GET_EXTRA_PARAM(blend, false); + GET_EXTRA_PARAM(blendReferenceTime, 0.0f); + GET_EXTRA_PARAM(priority, 5.0f); + } +}; + +#endif // _COLLADA_EXTENSIONS_H_ diff --git a/ts/collada/colladaImport.cpp b/ts/collada/colladaImport.cpp new file mode 100644 index 0000000..86d2b8b --- /dev/null +++ b/ts/collada/colladaImport.cpp @@ -0,0 +1,225 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2009 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/volume.h" +#include "ts/collada/colladaUtils.h" +#include "ts/collada/colladaAppNode.h" +#include "ts/collada/colladaShapeLoader.h" + +#include "gui/controls/guiTreeViewCtrl.h" + +// Helper struct for counting nodes, meshes and polygons down through the scene +// hierarchy +struct SceneStats +{ + S32 numNodes; + S32 numMeshes; + S32 numPolygons; + S32 numMaterials; + S32 numLights; + S32 numClips; + + SceneStats() : numNodes(0), numMeshes(0), numPolygons(0), numMaterials(0), numLights(0), numClips(0) { } +}; + +// Recurse through the adding nodes and geometry to the GuiTreeView control +static void processNode(GuiTreeViewCtrl* tree, domNode* node, S32 parentID, SceneStats& stats) +{ + stats.numNodes++; + S32 nodeID = tree->insertItem(parentID, _GetNameOrId(node), "node", "", 0, 0); + + // Update mesh and poly counts + for (int i = 0; i < node->getContents().getCount(); i++) + { + domGeometry* geom = 0; + const char* elemName = ""; + + daeElement* child = node->getContents()[i]; + switch (child->getElementType()) + { + case COLLADA_TYPE::INSTANCE_GEOMETRY: + { + domInstance_geometry* instgeom = daeSafeCast(child); + if (instgeom) + { + geom = daeSafeCast(instgeom->getUrl().getElement()); + elemName = _GetNameOrId(geom); + } + break; + } + + case COLLADA_TYPE::INSTANCE_CONTROLLER: + { + domInstance_controller* instctrl = daeSafeCast(child); + if (instctrl) + { + domController* ctrl = daeSafeCast(instctrl->getUrl().getElement()); + elemName = _GetNameOrId(ctrl); + if (ctrl && ctrl->getSkin()) + geom = daeSafeCast(ctrl->getSkin()->getSource().getElement()); + else if (ctrl && ctrl->getMorph()) + geom = daeSafeCast(ctrl->getMorph()->getSource().getElement()); + } + break; + } + + case COLLADA_TYPE::INSTANCE_LIGHT: + stats.numLights++; + tree->insertItem(nodeID, _GetNameOrId(node), "light", "", 0, 0); + break; + } + + if (geom && geom->getMesh()) + { + stats.numMeshes++; + tree->insertItem(nodeID, _GetNameOrId(node), "mesh", "", 0, 0); + + for (S32 j = 0; j < geom->getMesh()->getTriangles_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getTriangles_array()[j]->getCount(); + for (S32 j = 0; j < geom->getMesh()->getPolygons_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getPolygons_array()[j]->getCount(); + for (S32 j = 0; j < geom->getMesh()->getPolylist_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getPolylist_array()[j]->getCount(); + } + } + + // Recurse into child nodes + for (S32 i = 0; i < node->getNode_array().getCount(); i++) + processNode(tree, node->getNode_array()[i], nodeID, stats); + + for (S32 i = 0; i < node->getInstance_node_array().getCount(); i++) + { + domInstance_node* instnode = node->getInstance_node_array()[i]; + domNode* node = daeSafeCast(instnode->getUrl().getElement()); + if (node) + processNode(tree, node, nodeID, stats); + } +} + +// Collect scene information from a COLLADA file and store it in a GuiTreeView +// control. +ConsoleFunction(enumColladaForImport, bool, 3, 3, "(string shapePath, GuiTreeViewCtrl ctrl)") +{ + GuiTreeViewCtrl* tree; + if (!Sim::findObject(argv[2], tree)) + { + Con::errorf("enumColladaScene::Could not find GuiTreeViewCtrl '%s'", argv[2]); + return false; + } + + // Check if a cached DTS is available => no need to import the collada file + // if we can load the DTS instead + Torque::Path path(argv[1]); + if (ColladaShapeLoader::canLoadCachedDTS(path)) + return false; + + // Check if this is a Sketchup file (.kmz) and if so, mount the zip filesystem + // and get the path to the DAE file. + String mountPoint; + Torque::Path daePath; + bool isSketchup = ColladaShapeLoader::checkAndMountSketchup(path, mountPoint, daePath); + + // Load the Collada file into memory + domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(daePath); + if (!root) + { + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete"); + return false; + } + + if (isSketchup) + { + // Unmount the zip if we mounted it + Torque::FS::Unmount(mountPoint); + } + + // Initialize tree + tree->removeItem(0); + S32 nodesID = tree->insertItem(0, "Shape", "", "", 0, 0); + S32 matsID = tree->insertItem(0, "Materials", "", "", 0, 0); + S32 animsID = tree->insertItem(0, "Animations", "", "", 0, 0); + + SceneStats stats; + + // Query DOM for shape summary details + for (int i = 0; i < root->getLibrary_visual_scenes_array().getCount(); i++) + { + const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[i]; + for (int j = 0; j < libScenes->getVisual_scene_array().getCount(); j++) + { + const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[j]; + for (int k = 0; k < visualScene->getNode_array().getCount(); k++) + processNode(tree, visualScene->getNode_array()[k], nodesID, stats); + } + } + + // Get material count + for (S32 i = 0; i < root->getLibrary_materials_array().getCount(); i++) + { + const domLibrary_materials* libraryMats = root->getLibrary_materials_array()[i]; + stats.numMaterials += libraryMats->getMaterial_array().getCount(); + for (S32 j = 0; j < libraryMats->getMaterial_array().getCount(); j++) + { + domMaterial* mat = libraryMats->getMaterial_array()[j]; + tree->insertItem(matsID, _GetNameOrId(mat), _GetNameOrId(mat), "", 0, 0); + } + } + + // Get animation count + for (S32 i = 0; i < root->getLibrary_animation_clips_array().getCount(); i++) + { + const domLibrary_animation_clips* libraryClips = root->getLibrary_animation_clips_array()[i]; + stats.numClips += libraryClips->getAnimation_clip_array().getCount(); + for (S32 j = 0; j < libraryClips->getAnimation_clip_array().getCount(); j++) + { + domAnimation_clip* clip = libraryClips->getAnimation_clip_array()[j]; + tree->insertItem(animsID, _GetNameOrId(clip), "animation", "", 0, 0); + } + } + if (stats.numClips == 0) + { + // No clips => check if there are any animations (these will be added to a default clip) + for (S32 i = 0; i < root->getLibrary_animations_array().getCount(); i++) + { + const domLibrary_animations* libraryAnims = root->getLibrary_animations_array()[i]; + if (libraryAnims->getAnimation_array().getCount()) + { + stats.numClips = 1; + tree->insertItem(animsID, "ambient", "animation", "", 0, 0); + break; + } + } + } + + // Extract the global scale and up_axis from the top level element, + F32 unit = 1.0f; + domUpAxisType upAxis = UPAXISTYPE_Z_UP; + if (root->getAsset()) { + if (root->getAsset()->getUnit()) + unit = root->getAsset()->getUnit()->getMeter(); + if (root->getAsset()->getUp_axis()) + upAxis = root->getAsset()->getUp_axis()->getValue(); + } + + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete"); + + // Store shape information in the tree control + tree->setDataField(StringTable->insert("_nodeCount"), 0, avar("%d", stats.numNodes)); + tree->setDataField(StringTable->insert("_meshCount"), 0, avar("%d", stats.numMeshes)); + tree->setDataField(StringTable->insert("_polygonCount"), 0, avar("%d", stats.numPolygons)); + tree->setDataField(StringTable->insert("_materialCount"), 0, avar("%d", stats.numMaterials)); + tree->setDataField(StringTable->insert("_lightCount"), 0, avar("%d", stats.numLights)); + tree->setDataField(StringTable->insert("_animCount"), 0, avar("%d", stats.numClips)); + tree->setDataField(StringTable->insert("_unit"), 0, avar("%g", unit)); + + if (upAxis == UPAXISTYPE_X_UP) + tree->setDataField(StringTable->insert("_upAxis"), 0, "X_AXIS"); + else if (upAxis == UPAXISTYPE_Y_UP) + tree->setDataField(StringTable->insert("_upAxis"), 0, "Y_AXIS"); + else + tree->setDataField(StringTable->insert("_upAxis"), 0, "Z_AXIS"); + + return true; +} diff --git a/ts/collada/colladaLights.cpp b/ts/collada/colladaLights.cpp new file mode 100644 index 0000000..f99985f --- /dev/null +++ b/ts/collada/colladaLights.cpp @@ -0,0 +1,196 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2009 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "ts/collada/colladaUtils.h" +#include "ts/collada/colladaAppNode.h" +#include "ts/collada/colladaShapeLoader.h" + +#include "T3D/pointLight.h" +#include "T3D/spotlight.h" + + +//----------------------------------------------------------------------------- +// Collada elements are very similar, but are arranged as separate, unrelated +// classes. These template functions are used to provide a simple way to access the +// common elements. +template static void resolveLightColor(T* light, ColorF& color) +{ + if (light->getColor()) + { + color.red = light->getColor()->getValue()[0]; + color.green = light->getColor()->getValue()[1]; + color.blue = light->getColor()->getValue()[2]; + } +} + +template static void resolveLightAttenuation(T* light, Point3F& attenuationRatio) +{ + if (light->getConstant_attenuation()) + attenuationRatio.x = light->getConstant_attenuation()->getValue(); + if (light->getLinear_attenuation()) + attenuationRatio.y = light->getLinear_attenuation()->getValue(); + if (light->getQuadratic_attenuation()) + attenuationRatio.z = light->getQuadratic_attenuation()->getValue(); +} + +//----------------------------------------------------------------------------- +// Recurse through the collada scene to add s to the Torque scene +static void processNodeLights(AppNode* appNode, const MatrixF& offset, SimGroup* group) +{ + const domNode* node = dynamic_cast(appNode)->getDomNode(); + + for (S32 iLight = 0; iLight < node->getInstance_light_array().getCount(); iLight++) { + + domInstance_light* instLight = node->getInstance_light_array()[iLight]; + domLight* p_domLight = daeSafeCast(instLight->getUrl().getElement()); + if (!p_domLight) { + Con::warnf("Failed to find light for URL \"%s\"", instLight->getUrl().getOriginalURI()); + continue; + } + + String lightName = Sim::getUniqueName(_GetNameOrId(node)); + const char* lightType = ""; + + domLight::domTechnique_common* technique = p_domLight->getTechnique_common(); + if (!technique) { + Con::warnf("No for light \"%s\"", lightName.c_str()); + continue; + } + + LightBase* pLight = 0; + ColorF color(ColorF::WHITE); + Point3F attenuation(0, 1, 1); + + if (technique->getAmbient()) { + domLight::domTechnique_common::domAmbient* ambient = technique->getAmbient(); + // No explicit support for ambient lights, so use a PointLight instead + lightType = "ambient"; + pLight = new PointLight; + resolveLightColor(ambient, color); + } + else if (technique->getDirectional()) { + domLight::domTechnique_common::domDirectional* directional = technique->getDirectional(); + // No explicit support for directional lights, so use a SpotLight instead + lightType = "directional"; + pLight = new SpotLight; + resolveLightColor(directional, color); + } + else if (technique->getPoint()) { + domLight::domTechnique_common::domPoint* point = technique->getPoint(); + lightType = "point"; + pLight = new PointLight; + resolveLightColor(point, color); + resolveLightAttenuation(point, attenuation); + } + else if (technique->getSpot()) { + domLight::domTechnique_common::domSpot* spot = technique->getSpot(); + lightType = "spot"; + pLight = new SpotLight; + resolveLightColor(spot, color); + resolveLightAttenuation(spot, attenuation); + } + else + continue; + + Con::printf("Adding <%s> light \"%s\" as a %s", lightType, lightName.c_str(), pLight->getClassName()); + + MatrixF mat(offset); + mat.mul(appNode->getNodeTransform(TSShapeLoader::DefaultTime)); + + pLight->setDataField(StringTable->insert("color"), 0, + avar("%f %f %f %f", color.red, color.green, color.blue, color.alpha)); + pLight->setDataField(StringTable->insert("attenuationRatio"), 0, + avar("%f %f %f", attenuation.x, attenuation.y, attenuation.z)); + pLight->setTransform(mat); + + if (!pLight->registerObject(lightName)) { + Con::errorf(ConsoleLogEntry::General, "Failed to register light for \"%s\"", lightName.c_str()); + delete pLight; + } + + if (group) + group->addObject(pLight); + } + + // Recurse child nodes + for (S32 iChild = 0; iChild < appNode->getNumChildNodes(); iChild++) + processNodeLights(appNode->getChildNode(iChild), offset, group); +} + +// Load lights from a collada file and add to the scene. +ConsoleFunction(loadColladaLights, bool, 2, 4, "(string filename, [parentGroup], [baseObject])") +{ + Torque::Path path(argv[1]); + + // Optional group to add the lights to. Create if it does not exist, and use + // the MissionGroup if not specified. + SimGroup* missionGroup = dynamic_cast(Sim::findObject("MissionGroup")); + SimGroup* group = 0; + if ((argc > 2) && (argv[2][0])) { + if (!Sim::findObject(argv[2], group)) { + // Create the group if it could not be found + group = new SimGroup; + if (group->registerObject(argv[2])) { + if (missionGroup) + missionGroup->addObject(group); + } + else { + delete group; + group = 0; + } + } + } + if (!group) + group = missionGroup; + + // Optional object to provide the base transform + MatrixF offset(true); + if (argc > 3) { + SceneObject *obj; + if (Sim::findObject(argv[3], obj)) + offset = obj->getTransform(); + } + + // Load the Collada file into memory + domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(path); + if (!root) { + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete"); + return false; + } + + // Extract the global scale and up_axis from the top level element, + F32 unit = 1.0f; + domUpAxisType upAxis = UPAXISTYPE_Z_UP; + if (root->getAsset()) { + if (root->getAsset()->getUnit()) + unit = root->getAsset()->getUnit()->getMeter(); + if (root->getAsset()->getUp_axis()) + upAxis = root->getAsset()->getUp_axis()->getValue(); + } + + ColladaUtils::getOptions().unit = unit; + ColladaUtils::getOptions().upAxis = upAxis; + + // First grab all of the top-level nodes + Vector sceneNodes; + for (int iSceneLib = 0; iSceneLib < root->getLibrary_visual_scenes_array().getCount(); iSceneLib++) { + const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[iSceneLib]; + for (int iScene = 0; iScene < libScenes->getVisual_scene_array().getCount(); iScene++) { + const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[iScene]; + for (int iNode = 0; iNode < visualScene->getNode_array().getCount(); iNode++) + sceneNodes.push_back(new ColladaAppNode(visualScene->getNode_array()[iNode])); + } + } + + // Recurse the scene tree looking for s + for (S32 iNode = 0; iNode < sceneNodes.size(); iNode++) { + processNodeLights(sceneNodes[iNode], offset, group); + delete sceneNodes[iNode]; + } + + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete"); + + return true; +} diff --git a/ts/collada/colladaShapeLoader.cpp b/ts/collada/colladaShapeLoader.cpp new file mode 100644 index 0000000..4d90b06 --- /dev/null +++ b/ts/collada/colladaShapeLoader.cpp @@ -0,0 +1,632 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. + +/* + Resource stream -> Buffer + Buffer -> Collada DOM + Collada DOM -> TSShapeLoader + TSShapeLoader installed into TSShape +*/ + +//----------------------------------------------------------------------------- + +#include "core/util/tVector.h" +#include "core/strings/findMatch.h" +#include "core/stream/fileStream.h" +#include "core/fileObject.h" +#include "ts/tsShape.h" +#include "ts/tsShapeInstance.h" +#include "materials/materialManager.h" +#include "ts/tsShapeConstruct.h" +#include "ts/collada/colladaUtils.h" +#include "ts/collada/colladaShapeLoader.h" +#include "ts/collada/colladaAppNode.h" +#include "ts/collada/colladaAppMesh.h" +#include "ts/collada/colladaAppMaterial.h" +#include "ts/collada/colladaAppSequence.h" +#include "core/util/zip/zipVolume.h" +#include "gfx/bitmap/gBitmap.h" + +// +static DAE sDAE; // Collada model database (holds the last loaded file) +static Torque::Path sLastPath; // Path of the last loaded Collada file +static FileTime sLastModTime; // Modification time of the last loaded Collada file + +//----------------------------------------------------------------------------- +// Custom warning/error message handler +class myErrorHandler : public daeErrorHandler +{ + void handleError( daeString msg ) + { + Con::errorf("Error: %s", msg); + } + + void handleWarning( daeString msg ) + { + Con::errorf("Warning: %s", msg); + } +} sErrorHandler; + +//----------------------------------------------------------------------------- + +ColladaShapeLoader::ColladaShapeLoader(domCOLLADA* _root) + : root(_root) +{ + // Extract the global scale and up_axis from the top level element, + F32 unit = 1.0f; + domUpAxisType upAxis = UPAXISTYPE_Z_UP; + if (root->getAsset()) { + if (root->getAsset()->getUnit()) + unit = root->getAsset()->getUnit()->getMeter(); + if (root->getAsset()->getUp_axis()) + upAxis = root->getAsset()->getUp_axis()->getValue(); + } + + // Set import options (if they are not set to override) + if (ColladaUtils::getOptions().unit <= 0.0f) + ColladaUtils::getOptions().unit = unit; + + if (ColladaUtils::getOptions().upAxis == UPAXISTYPE_COUNT) + ColladaUtils::getOptions().upAxis = upAxis; +} + +ColladaShapeLoader::~ColladaShapeLoader() +{ + // Delete all of the animation channels + for (int iAnim = 0; iAnim < animations.size(); iAnim++) { + for (int iChannel = 0; iChannel < animations[iAnim]->size(); iChannel++) + delete (*animations[iAnim])[iChannel]; + delete animations[iAnim]; + } + animations.clear(); +} + +void ColladaShapeLoader::processAnimation(const domAnimation* anim, F32& maxEndTime) +{ + const char* sRGBANames[] = { ".R", ".G", ".B", ".A", "" }; + const char* sXYZNames[] = { ".X", ".Y", ".Z", "" }; + const char* sXYZANames[] = { ".X", ".Y", ".Z", ".ANGLE" }; + const char* sLOOKATNames[] = { ".POSITIONX", ".POSITIONY", ".POSITIONZ", ".TARGETX", ".TARGETY", ".TARGETZ", ".UPX", ".UPY", ".UPZ", "" }; + const char* sSKEWNames[] = { ".ROTATEX", ".ROTATEY", ".ROTATEZ", ".AROUNDX", ".AROUNDY", ".AROUNDZ", ".ANGLE", "" }; + const char* sNullNames[] = { "" }; + + for (int iChannel = 0; iChannel < anim->getChannel_array().getCount(); iChannel++) { + + // Get the animation elements: , + domChannel* channel = anim->getChannel_array()[iChannel]; + domSampler* sampler = daeSafeCast(channel->getSource().getElement()); + if (!sampler) + continue; + + // Find the animation channel target + daeSIDResolver resolver(channel, channel->getTarget()); + daeElement* target = resolver.getElement(); + if (!target) { + daeErrorHandler::get()->handleWarning(avar("Failed to resolve animation " + "target: %s", channel->getTarget())); + continue; + } +/* + // If the target is a , point it at the array instead + // @todo:Only support targeting float arrays for now... + if (target->getElementType() == COLLADA_TYPE::SOURCE) + { + domSource* source = daeSafeCast(target); + if (source->getFloat_array()) + target = source->getFloat_array(); + } +*/ + // Get the target's animation channels (create them if not already) + if (!AnimData::getAnimChannels(target)) { + animations.push_back(new AnimChannels(target)); + } + AnimChannels* targetChannels = AnimData::getAnimChannels(target); + + // Add a new animation channel to the target + targetChannels->push_back(new AnimData()); + AnimData& data = *targetChannels->last(); + + for (int iInput = 0; iInput < sampler->getInput_array().getCount(); iInput++) { + + const domInputLocal* input = sampler->getInput_array()[iInput]; + const domSource* source = daeSafeCast(input->getSource().getElement()); + + // @todo:don't care about the input param names for now. Could + // validate against the target type.... + if (dStrEqual(input->getSemantic(), "INPUT")) { + data.input.initFromSource(source); + // Adjust the maximum sequence end time + maxEndTime = getMax(maxEndTime, data.input.getFloatValue((S32)data.input.size()-1)); + } + else if (dStrEqual(input->getSemantic(), "OUTPUT")) + data.output.initFromSource(source); + else if (dStrEqual(input->getSemantic(), "IN_TANGENT")) + data.inTangent.initFromSource(source); + else if (dStrEqual(input->getSemantic(), "OUT_TANGENT")) + data.outTangent.initFromSource(source); + else if (dStrEqual(input->getSemantic(), "INTERPOLATION")) + data.interpolation.initFromSource(source); + } + + // Determine the number and offset the elements of the target value + // targeted by this animation + switch (target->getElementType()) { + case COLLADA_TYPE::COLOR: data.parseTargetString(channel->getTarget(), 4, sRGBANames); break; + case COLLADA_TYPE::TRANSLATE: data.parseTargetString(channel->getTarget(), 3, sXYZNames); break; + case COLLADA_TYPE::ROTATE: data.parseTargetString(channel->getTarget(), 4, sXYZANames); break; + case COLLADA_TYPE::SCALE: data.parseTargetString(channel->getTarget(), 3, sXYZNames); break; + case COLLADA_TYPE::LOOKAT: data.parseTargetString(channel->getTarget(), 3, sLOOKATNames); break; + case COLLADA_TYPE::SKEW: data.parseTargetString(channel->getTarget(), 3, sSKEWNames); break; + case COLLADA_TYPE::MATRIX: data.parseTargetString(channel->getTarget(), 16, sNullNames); break; + case COLLADA_TYPE::FLOAT_ARRAY: data.parseTargetString(channel->getTarget(), daeSafeCast(target)->getCount(), sNullNames); break; + default: data.parseTargetString(channel->getTarget(), 1, sNullNames); break; + } + } + + // Process child animations + for (int iAnim = 0; iAnim < anim->getAnimation_array().getCount(); iAnim++) + processAnimation(anim->getAnimation_array()[iAnim], maxEndTime); +} + +void ColladaShapeLoader::enumerateScene() +{ + // Get animation clips + Vector animationClips; + for (int iClipLib = 0; iClipLib < root->getLibrary_animation_clips_array().getCount(); iClipLib++) { + const domLibrary_animation_clips* libraryClips = root->getLibrary_animation_clips_array()[iClipLib]; + for (int iClip = 0; iClip < libraryClips->getAnimation_clip_array().getCount(); iClip++) + appSequences.push_back(new ColladaAppSequence(libraryClips->getAnimation_clip_array()[iClip])); + } + + // Process all animations => this attaches animation channels to the targeted + // Collada elements, and determines the length of the sequence if it is not + // already specified in the Collada element + for (int iSeq = 0; iSeq < appSequences.size(); iSeq++) { + ColladaAppSequence* appSeq = dynamic_cast(appSequences[iSeq]); + for (int iAnim = 0; iAnim < appSeq->getClip()->getInstance_animation_array().getCount(); iAnim++) { + F32 maxEndTime = 0; + domAnimation* anim = daeSafeCast(appSeq->getClip()->getInstance_animation_array()[iAnim]->getUrl().getElement()); + processAnimation(anim, maxEndTime); + if (appSeq->getEnd() == 0) + appSeq->setEnd(maxEndTime); + } + } + + // First grab all of the top-level nodes + Vector sceneNodes; + for (int iSceneLib = 0; iSceneLib < root->getLibrary_visual_scenes_array().getCount(); iSceneLib++) { + const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[iSceneLib]; + for (int iScene = 0; iScene < libScenes->getVisual_scene_array().getCount(); iScene++) { + const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[iScene]; + for (int iNode = 0; iNode < visualScene->getNode_array().getCount(); iNode++) + sceneNodes.push_back(visualScene->getNode_array()[iNode]); + } + } + + // Set LOD option + bool singleDetail = true; + switch (ColladaUtils::getOptions().lodType) + { + case ColladaUtils::ImportOptions::DetectDTS: + // Check for a baseXX->startXX hierarchy at the top-level, if we find + // one, use trailing numbers for LOD, otherwise use a single size + for (int iNode = 0; singleDetail && (iNode < sceneNodes.size()); iNode++) { + domNode* node = sceneNodes[iNode]; + if (dStrStartsWith(_GetNameOrId(node), "base")) { + for (int iChild = 0; iChild < node->getNode_array().getCount(); iChild++) { + domNode* child = node->getNode_array()[iChild]; + if (dStrStartsWith(_GetNameOrId(child), "start")) { + singleDetail = false; + break; + } + } + } + } + break; + + case ColladaUtils::ImportOptions::SingleSize: + singleDetail = true; + break; + + case ColladaUtils::ImportOptions::TrailingNumber: + singleDetail = false; + break; + + default: + break; + } + + ColladaAppMesh::fixDetailSize( singleDetail, ColladaUtils::getOptions().singleDetailSize ); + + // Process the top level nodes + for (S32 iNode = 0; iNode < sceneNodes.size(); iNode++) { + ColladaAppNode* node = new ColladaAppNode(sceneNodes[iNode], 0); + if (!processNode(node)) + delete node; + } + + // Make sure that the scene has a bounds node (for getting the root scene transform) + if (!boundsNode) + { + domVisual_scene* visualScene = root->getLibrary_visual_scenes_array()[0]->getVisual_scene_array()[0]; + domNode* dombounds = daeSafeCast( visualScene->createAndPlace( "node" ) ); + dombounds->setName( "bounds" ); + ColladaAppNode *appBounds = new ColladaAppNode(dombounds, 0); + if (!processNode(appBounds)) + delete appBounds; + } +} + +bool ColladaShapeLoader::ignore(const String& name) +{ + if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImport, name, false)) + return false; + else + return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImport, name, false); +} + +void ColladaShapeLoader::computeShapeOffset() +{ + shapeOffset = Point3F::Zero; + + // Check if the model origin needs adjusting + if (ColladaUtils::getOptions().adjustCenter || + ColladaUtils::getOptions().adjustFloor) + { + Box3F bounds; + computeBounds(bounds); + + // Set the shape offset accordingly + if (bounds.isValidBox()) + { + if (ColladaUtils::getOptions().adjustCenter) + { + bounds.getCenter(&shapeOffset); + shapeOffset = -shapeOffset; + } + if (ColladaUtils::getOptions().adjustFloor) + shapeOffset.z = -bounds.minExtents.z; + } + } +} + +//----------------------------------------------------------------------------- +/// Find the file extension for an extensionless texture +String findTextureExtension(const Torque::Path &texPath) +{ + Torque::Path path(texPath); + for(S32 i = 0;i < GBitmap::sRegistrations.size();++i) + { + GBitmap::Registration ® = GBitmap::sRegistrations[i]; + for(S32 j = 0;j < reg.extensions.size();++j) + { + path.setExtension(reg.extensions[j]); + if (Torque::FS::IsFile(path)) + return path.getExtension(); + } + } + + return String(); +} + +//----------------------------------------------------------------------------- +/// Copy a texture from a KMZ to a cache. Note that the texture filename is modified +void copySketchupTexture(const Torque::Path &path, String &textureFilename) +{ + if (textureFilename.isEmpty()) + return; + + Torque::Path texturePath(textureFilename); + texturePath.setExtension(findTextureExtension(texturePath)); + + String cachedTexFilename = String::ToString("%s_%s.cached", + TSShapeLoader::getShapePath().getFileName().c_str(), texturePath.getFileName().c_str()); + + Torque::Path cachedTexPath; + cachedTexPath.setRoot(path.getRoot()); + cachedTexPath.setPath(path.getPath()); + cachedTexPath.setFileName(cachedTexFilename); + cachedTexPath.setExtension(texturePath.getExtension()); + + FileStream *source; + FileStream *dest; + if ((source = FileStream::createAndOpen(texturePath.getFullPath(), Torque::FS::File::Read)) == NULL) + return; + + if ((dest = FileStream::createAndOpen(cachedTexPath.getFullPath(), Torque::FS::File::Write)) == NULL) + { + delete source; + return; + } + + dest->copyFrom(source); + + delete dest; + delete source; + + // Update the filename in the material + cachedTexPath.setExtension(""); + textureFilename = cachedTexPath.getFullPath(); +} + +//----------------------------------------------------------------------------- +/// Add collada materials to materials.cs +void updateMaterialsScript(const Torque::Path &path, bool copyTextures = false) +{ + // First see what materials we need to add... if one already + // exists then we can ignore it. + Vector materials; + for ( U32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++ ) + { + ColladaAppMaterial *mat = dynamic_cast( AppMesh::appMaterials[iMat] ); + if (ColladaUtils::getOptions().forceUpdateMaterials || + MATMGR->getMapEntry( mat->getName() ).isEmpty() ) + materials.push_back( mat ); + } + + if ( materials.empty() ) + return; + + Torque::Path scriptPath(path); + scriptPath.setFileName("materials"); + scriptPath.setExtension("cs"); + + // Read the current script (if any) into memory + FileObject f; + f.readMemory(scriptPath.getFullPath()); + + FileStream stream; + if (stream.open(scriptPath, Torque::FS::File::Write)) { + + String shapeName = TSShapeLoader::getShapePath().getFullFileName(); + const char *beginMsg = avar("//--- %s MATERIALS BEGIN ---", shapeName.c_str()); + + // Write existing file contents up to start of auto-generated materials + while(!f.isEOF()) { + const char *buffer = (const char *)f.readLine(); + if (dStricmp(buffer, beginMsg) == 0) + break; + stream.writeLine((const U8*)buffer); + } + + // Write new auto-generated materials + stream.writeLine((const U8*)beginMsg); + for (int iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++) + { + ColladaAppMaterial *mat = dynamic_cast(AppMesh::appMaterials[iMat]); + if (mat == NULL) + continue; + + if (copyTextures) + { + // If importing a sketchup file, the paths will point inside the KMZ so we need to cache them. + copySketchupTexture(path, mat->diffuseMap); + copySketchupTexture(path, mat->normalMap); + copySketchupTexture(path, mat->specularMap); + } + mat->write(stream); + } + + const char *endMsg = avar("//--- %s MATERIALS END ---", shapeName.c_str()); + stream.writeLine((const U8*)endMsg); + stream.writeLine((const U8*)""); + + // Write existing file contents after end of auto-generated materials + while (!f.isEOF()) { + const char *buffer = (const char *) f.readLine(); + if (dStricmp(buffer, endMsg) == 0) + break; + } + + // Want at least one blank line after the autogen block, but need to + // be careful not to write it twice, or another blank will be added + // each time the file is written! + if (!f.isEOF()) { + const char *buffer = (const char *) f.readLine(); + if (!dStrEqual(buffer, "")) + stream.writeLine((const U8*)buffer); + } + while (!f.isEOF()) { + const char *buffer = (const char *) f.readLine(); + stream.writeLine((const U8*)buffer); + } + f.close(); + stream.close(); + + // Execute the new script to apply the material settings + if (f.readMemory(scriptPath.getFullPath())) + { + String instantGroup = Con::getVariable("InstantGroup"); + Con::setIntVariable("InstantGroup", RootGroupId); + Con::evaluate((const char*)f.buffer(), false, scriptPath.getFullPath()); + Con::setVariable("InstantGroup", instantGroup.c_str()); + } + } +} + +//----------------------------------------------------------------------------- +/// Check if an up-to-date cached DTS is available for this DAE file +bool ColladaShapeLoader::canLoadCachedDTS(const Torque::Path& path) +{ + // Generate the cached filename + Torque::Path cachedPath(path); + cachedPath.setExtension("cached.dts"); + + // Check if a cached DTS newer than this file is available + FileTime cachedModifyTime; + if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) + { + bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); + + FileTime daeModifyTime; + if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || + (!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) )) + { + // DAE not found, or cached DTS is newer + return true; + } + } + + return false; +} + +bool ColladaShapeLoader::checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath) +{ + bool isSketchup = path.getExtension().equal("kmz", String::NoCase); + if (isSketchup) + { + // Mount the zip so files can be found (it will be unmounted before we return) + mountPoint = String("sketchup_") + path.getFileName(); + String zipPath = path.getFullPath(); + if (!Torque::FS::Mount(mountPoint, new Torque::ZipFileSystem(zipPath))) + return false; + + Vector daeFiles; + Torque::Path findPath; + findPath.setRoot(mountPoint); + S32 results = Torque::FS::FindByPattern(findPath, "*.dae", true, daeFiles); + if (results == 0 || daeFiles.size() == 0) + { + Torque::FS::Unmount(mountPoint); + return false; + } + + daePath = daeFiles[0]; + } + else + { + daePath = path; + } + + return isSketchup; +} + +//----------------------------------------------------------------------------- +/// Get the root collada DOM element for the given DAE file +domCOLLADA* ColladaShapeLoader::getDomCOLLADA(const Torque::Path& path) +{ + daeErrorHandler::setErrorHandler(&sErrorHandler); + + TSShapeLoader::updateProgress(TSShapeLoader::Load_ReadFile, path.getFullFileName().c_str()); + + // Check if we can use the last loaded file + FileTime daeModifyTime; + if (Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime)) + { + if ((path == sLastPath) && (Platform::compareFileTimes(sLastModTime, daeModifyTime) >= 0)) + return sDAE.getRoot(path.getFullPath().c_str()); + } + + // Load the Collada file into memory + FileObject fo; + if (!fo.readMemory(path.getFullPath())) + { + daeErrorHandler::get()->handleError(avar("Could not read %s into memory", path.getFullPath().c_str())); + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed"); + return NULL; + } + + // Read the XML document into the Collada DOM + TSShapeLoader::updateProgress(TSShapeLoader::Load_ParseFile, "Parsing XML..."); + sDAE.clear(); + domCOLLADA* root = sDAE.openFromMemory(path.getFullPath().c_str(), (const char*)fo.buffer()); + if (!root || !root->getLibrary_visual_scenes_array().getCount()) { + daeErrorHandler::get()->handleError(avar("Could not parse %s", path.getFullPath().c_str())); + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed"); + return NULL; + } + + // Fixup issues in the model + ColladaUtils::applyConditioners(root); + + sLastPath = path; + sLastModTime = daeModifyTime; + + return root; +} + +//----------------------------------------------------------------------------- +/// This function is invoked by the resource manager based on file extension. +TSShape* loadColladaShape(const Torque::Path &path) +{ + // Generate the cached filename + Torque::Path cachedPath(path); + cachedPath.setExtension("cached.dts"); + + // Check if an up-to-date cached DTS version of this file exists, and + // if so, use that instead. + if (ColladaShapeLoader::canLoadCachedDTS(path)) + { + FileStream cachedStream; + cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read); + if (cachedStream.getStatus() == Stream::Ok) + { + TSShape *shape = new TSShape; + bool readSuccess = shape->read(&cachedStream); + cachedStream.close(); + + if (readSuccess) + { + #ifdef TORQUE_DEBUG + Con::printf("Loaded cached Collada shape from %s", cachedPath.getFullPath().c_str()); + #endif + return shape; + } + else + delete shape; + } + + Con::warnf("Failed to load cached COLLADA shape from %s", cachedPath.getFullPath().c_str()); + } + + if (!Torque::FS::IsFile(path)) + { + // DAE file does not exist, bail. + return NULL; + } + + // Allow TSShapeConstructor object to override properties + ColladaUtils::getOptions().reset(); + TSShapeConstructor* tscon = TSShapeConstructor::findShapeConstructor(path.getFullPath()); + if (tscon) + ColladaUtils::getOptions() = tscon->mOptions; + ColladaUtils::getOptions().neverImport += "\tdummy"; + + // Check if this is a Sketchup file (.kmz) and if so, mount the zip filesystem + // and get the path to the DAE file. + String mountPoint; + Torque::Path daePath; + bool isSketchup = ColladaShapeLoader::checkAndMountSketchup(path, mountPoint, daePath); + + // Load Collada model and convert to 3space + TSShape* tss = 0; + domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(daePath); + if (root) + { + ColladaShapeLoader loader(root); + tss = loader.generateShape(daePath); + + // Cache the Collada model to a DTS file for faster loading next time. + FileStream dtsStream; + if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) + { + Con::printf("Writing cached COLLADA shape to %s", cachedPath.getFullPath().c_str()); + tss->write(&dtsStream); + } + + // Add collada materials to materials.cs + updateMaterialsScript(path, isSketchup); + } + + // Close progress dialog + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); + + if (isSketchup) + { + // Unmount the zip if we mounted it + Torque::FS::Unmount(mountPoint); + } + + return tss; +} diff --git a/ts/collada/colladaShapeLoader.h b/ts/collada/colladaShapeLoader.h new file mode 100644 index 0000000..0c6f213 --- /dev/null +++ b/ts/collada/colladaShapeLoader.h @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLADA_SHAPELOADER_H_ +#define _COLLADA_SHAPELOADER_H_ + +#ifndef _TSSHAPELOADER_H_ +#include "ts/loader/tsShapeLoader.h" +#endif + +class domCOLLADA; +class domAnimation; + +//----------------------------------------------------------------------------- +class ColladaShapeLoader : public TSShapeLoader +{ + friend TSShape* loadColladaShape(const Torque::Path &path); + + domCOLLADA* root; + Vector animations; ///< Holds all animation channels for deletion after loading + + void processAnimation(const domAnimation* anim, F32& maxEndTime); + + void cleanup(); + +public: + ColladaShapeLoader(domCOLLADA* _root); + ~ColladaShapeLoader(); + + void enumerateScene(); + bool ignore(const String& name); + void computeShapeOffset(); + + static bool canLoadCachedDTS(const Torque::Path& path); + static bool checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath); + static domCOLLADA* getDomCOLLADA(const Torque::Path& path); +}; + +#endif // _COLLADA_SHAPELOADER_H_ diff --git a/ts/collada/colladaUtils.cpp b/ts/collada/colladaUtils.cpp new file mode 100644 index 0000000..02cb993 --- /dev/null +++ b/ts/collada/colladaUtils.cpp @@ -0,0 +1,1459 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include +#include "console/console.h" +#include "gfx/bitmap/gBitmap.h" +#include "ts/collada/colladaUtils.h" + +using namespace ColladaUtils; + +#define MAX_PATH_LENGTH 256 + +// Helper macro to create Collada elements +#define CREATE_ELEMENT(container, name, type) \ + type* name = daeSafeCast(container->createAndPlace(#name)); + +ColladaUtils::ImportOptions& ColladaUtils::getOptions() +{ + static ImportOptions options; + return options; +} + +//------------------------------------------------------------------------------ +// Utility functions + +// Convert a transform from the Collada model coordinate system to the DTS coordinate +// system +void ColladaUtils::convertTransform(MatrixF& mat) +{ + MatrixF rot(true); + + switch (ColladaUtils::getOptions().upAxis) + { + case UPAXISTYPE_X_UP: + // rotate 90 around Y-axis, then 90 around Z-axis + rot(0,0) = 0.0f; rot(1,0) = 1.0f; + rot(1,1) = 0.0f; rot(2,1) = 1.0f; + rot(0,2) = 1.0f; rot(2,2) = 0.0f; + + // pre-multiply the transform by the rotation matrix + mat = rot * mat; + break; + + case UPAXISTYPE_Y_UP: + // rotate 180 around Y-axis, then 90 around X-axis + rot(0,0) = -1.0f; + rot(1,1) = 0.0f; rot(2,1) = 1.0f; + rot(1,2) = 1.0f; rot(2,2) = 0.0f; + + // pre-multiply the transform by the rotation matrix + mat = rot * mat; + break; + + case UPAXISTYPE_Z_UP: + default: + // nothing to do + break; + } +} + +/// Find the COMMON profile element in an effect +const domProfile_COMMON* ColladaUtils::findEffectCommonProfile(const domEffect* effect) +{ + if (effect) { + // Find the COMMON profile + const domFx_profile_abstract_Array& profiles = effect->getFx_profile_abstract_array(); + for (int iProfile = 0; iProfile < profiles.getCount(); iProfile++) { + if (profiles[iProfile]->getElementType() == COLLADA_TYPE::PROFILE_COMMON) + return daeSafeCast(profiles[iProfile]); + } + } + return NULL; +} + +/// Find the element in the COMMON profile of an effect +const domCommon_color_or_texture_type_complexType* ColladaUtils::findEffectDiffuse(const domEffect* effect) +{ + const domProfile_COMMON* profile = findEffectCommonProfile(effect); + if (profile) { + + if (profile->getTechnique()->getLambert()) + return profile->getTechnique()->getLambert()->getDiffuse(); + else if (profile->getTechnique()->getPhong()) + return profile->getTechnique()->getPhong()->getDiffuse(); + else if (profile->getTechnique()->getBlinn()) + return profile->getTechnique()->getBlinn()->getDiffuse(); + } + + return NULL; +} + +/// Find the element in the COMMON profile of an effect +const domCommon_color_or_texture_type_complexType* ColladaUtils::findEffectSpecular(const domEffect* effect) +{ + const domProfile_COMMON* profile = findEffectCommonProfile(effect); + if (profile) { + + if (profile->getTechnique()->getLambert()) + return NULL; // no element for Lambert shader + else if (profile->getTechnique()->getPhong()) + return profile->getTechnique()->getPhong()->getSpecular(); + else if (profile->getTechnique()->getBlinn()) + return profile->getTechnique()->getBlinn()->getSpecular(); + } + + return NULL; +} + +const domFx_sampler2D_common_complexType* ColladaUtils::getTextureSampler(const domEffect* effect, + const domCommon_color_or_texture_type_complexType* texture) +{ + // .. + if (texture) { + const domCommon_color_or_texture_type_complexType::domTexture* domTex = texture->getTexture(); + if (domTex && domTex->getTexture()) { + daeSIDResolver resolver(const_cast(effect), domTex->getTexture()); + const domCommon_newparam_type* param = daeSafeCast(resolver.getElement()); + if (param) + return param->getSampler2D(); + } + } + + return NULL; +} + +String ColladaUtils::getSamplerImagePath(const domEffect* effect, + const domFx_sampler2D_common_complexType* sampler2D) +{ + // ...... + const domProfile_COMMON* profile = findEffectCommonProfile(effect); + if (profile && sampler2D && sampler2D->getSource()) { + + // Resolve the SID to get the param + daeSIDResolver resolver(const_cast(profile), sampler2D->getSource()->getValue()); + domCommon_newparam_type* surfaceParam = daeSafeCast(resolver.getElement()); + + // Get the surface element + if (surfaceParam && surfaceParam->getSurface()) { + + const domFx_surface_init_common* surfaceInit = surfaceParam->getSurface()->getFx_surface_init_common(); + if (surfaceInit && surfaceInit->getInit_from_array().getCount()) { + // Resolve the ID to get the , then read the texture path + xsIDREF& idRef = surfaceInit->getInit_from_array()[0]->getValue(); + const domImage* image = daeSafeCast(idRef.getElement()); + if (image && image->getInit_from()) + return resolveImagePath(image); + } + } + } + + return ""; +} + +// Resolve image path into something we can use. +String ColladaUtils::resolveImagePath(const domImage* image) +{ + // 1. If the URI string contains an absolute path, use it if + // it is inside the Torque folder, otherwise force textures + // to be in the same folder as the shape. + // 2. If the URI string contains a relative path, append it + // to the shape path (since materials.cs cannot handle + // relative paths). + + Torque::Path imagePath; + String imageStr(image->getInit_from()->getValue().originalStr().c_str()); + + // Trim leading "file://" + if (imageStr.compare("file://", 7) == 0) + imageStr.erase(0, 7); + + // Trim leading slash from absolute windows paths. eg. /D:/ + if ((imageStr.compare("/", 1) == 0) && (imageStr.find(':') == 2)) + imageStr.erase(0, 1); + + // Replace %20 with space + imageStr.replace("%20", " "); + + if (Platform::isFullPath(imageStr)) + { + // Absolute path => check for outside the Torque game folder + imagePath = String( Platform::makeRelativePathName(imageStr, Platform::getMainDotCsDir()) ); + if ( !imagePath.getRoot().isEmpty() || // different drive (eg. C:/ vs D:/) + (imagePath.getPath().find("/") == 0) || // different OS (eg. /home vs C:/home) + (imagePath.getPath().find("../") == 0) ) // same drive, outside Torque game folder + { + // Force these to the shape folder + imagePath.setRoot(""); + imagePath.setPath(""); + } + } + else + { + // Relative path => prepend with shape path + Torque::Path tempPath(imageStr); + imagePath = TSShapeLoader::getShapePath(); + imagePath.appendPath(tempPath); + imagePath.setFileName(tempPath.getFileName()); + } + + // No need to specify the path if it is in the same folder as the model + if (imagePath.getPath() == TSShapeLoader::getShapePath().getPath()) + imagePath.setPath(""); + + // Don't care about the extension + imagePath.setExtension(""); + + return imagePath.getFullPath(); +} + +//----------------------------------------------------------------------------- +// Construct the appropriate child class +BasePrimitive* BasePrimitive::get(const daeElement* element) +{ + switch (element->getElementType()) { + case COLLADA_TYPE::TRIANGLES: return new ColladaPrimitive(element); + case COLLADA_TYPE::POLYGONS: return new ColladaPrimitive(element); + case COLLADA_TYPE::POLYLIST: return new ColladaPrimitive(element); + } + return 0; +} + +//------------------------------------------------------------------------------ +// Collada animation curves + +/// Determine which elements are being targeted +void AnimData::parseTargetString(const char* target, int fullCount, const char* elements[]) +{ + // Assume targeting all elements at offset 0 + targetValueCount = fullCount; + targetValueOffset = 0; + + // Check for array syntax: (n) or (n)(m) + if (const char* p = dStrchr(target, '(')) { + int indN, indM; + if (dSscanf(p, "(%d)(%d)", &indN, &indM) == 2) { + targetValueOffset = (indN * 4) + indM; // @todo: 4x4 matrix only + targetValueCount = 1; + } + else if (dSscanf(p, "(%d)", &indN) == 1) { + targetValueOffset = indN; + targetValueCount = 1; + } + } + else if (const char* p = dStrrchr(target, '.')) { + // Check for named elements + for (int iElem = 0; elements[iElem][0] != 0; iElem++) { + if (!dStrcmp(p, elements[iElem])) { + targetValueOffset = iElem; + targetValueCount = 1; + break; + } + } + } +} + +/// Solve the cubic spline B(s) = param for s +F32 AnimData::invertParamCubic(F32 param, F32 x0, F32 x1, F32 x2, F32 x3) const +{ + const double INVERTPARAMCUBIC_TOL = 1.0e-09; + const double INVERTPARAMCUBIC_SMALLERTOL = 1.0e-20; + const double INVERTPARAMCUBIC_MAXIT = 100; + + // check input value for outside range + if ((param - x0) < INVERTPARAMCUBIC_SMALLERTOL) + return 0.0f; + else if ((x3 - param) < INVERTPARAMCUBIC_SMALLERTOL) + return 1.0f; + + U32 iterations = 0; + + // de Casteljau Subdivision. + F32 u = 0.0f; + F32 v = 1.0f; + + while (iterations < INVERTPARAMCUBIC_MAXIT) { + double a = (x0 + x1)*0.5f; + double b = (x1 + x2)*0.5f; + double c = (x2 + x3)*0.5f; + double d = (a + b)*0.5f; + double e = (b + c)*0.5f; + double f = (d + e)*0.5f; + + if (mFabs(f - param) < INVERTPARAMCUBIC_TOL) + break; + + if (f < param) { + x0 = f; + x1 = e; + x2 = c; + u = (u + v)*0.5f; + } + else { + x1 = a; + x2 = d; + x3 = f; + v = (u + v)*0.5f; + } + iterations++; + } + + return mClampF((u+v)*0.5f, 0.0f, 1.0f); +} + +/// Get the interpolated value at time 't' +void AnimData::interpValue(F32 t, U32 offset, double* value) const +{ + // handle degenerate animation data + if (input.size() == 0) + { + *value = 0.0f; + return; + } + else if (input.size() == 1) + { + *value = output.getFloatValue(0); + return; + } + + // find the index of the input keyframe BEFORE 't' + int index; + for (index = 0; index < input.size()-2; index++) { + if (input.getFloatValue(index + 1) > t) + break; + } + + // get the data for the two control points either side of 't' + Point2F v0; + v0.x = input.getFloatValue(index); + v0.y = output.getStringArrayData(index)[offset]; + + Point2F v3; + v3.x = input.getFloatValue(index + 1); + v3.y = output.getStringArrayData(index + 1)[offset]; + + // If spline interpolation is specified but the tangents are not available, + // default to LINEAR. + const char* interp_method = interpolation.getStringValue(index); + if (dStrEqual(interp_method, "BEZIER") || + dStrEqual(interp_method, "HERMITE") || + dStrEqual(interp_method, "CARDINAL")) { + + const double* inArray = inTangent.getStringArrayData(index + 1); + const double* outArray = outTangent.getStringArrayData(index); + if (!inArray || !outArray) + interp_method = "LINEAR"; + } + + if (dStrEqual(interp_method, "STEP")) { + // STEP interpolation + *value = v0.y; + } + else if (dStrEqual(interp_method, "BEZIER") || + dStrEqual(interp_method, "HERMITE") || + dStrEqual(interp_method, "CARDINAL") || + dStrEqual(interp_method, "BSPLINE")) + { + // Cubic spline interpolation. The only difference between the 4 supported + // forms is in the calculation of the other 2 control points: + // BEZIER: control points are specified explicitly + // HERMITE: tangents are specified, need to offset to get the control points + // CARDINAL: (baked) tangents are specified, need to offset to get the control points + // BSPLINE: control points are based on previous and next points + + // Get the 2 extra control points + Point2F v1, v2; + + if (dStrEqual(interp_method, "BSPLINE")) { + // v0 and v3 are the center points => need to + // get the control points before and after them + v1 = v0; + v2 = v3; + + if (index > 0) { + v0.x = input.getFloatValue(index-1); + v0.y = output.getStringArrayData(index-1)[offset]; + } + else { + // mirror P1 through P0 + v0 = v1 + (v1 - v2); + } + + if (index < (input.size()-2)) { + v3.x = input.getFloatValue(index+2); + v3.y = output.getStringArrayData(index+2)[offset]; + } + else { + // mirror P0 through P1 + v3 = v2 + (v2 - v1); + } + } + else { + const double* inArray = inTangent.getStringArrayData(index + 1); + const double* outArray = outTangent.getStringArrayData(index); + + if (output.stride() == inTangent.stride()) { + // This degenerate form (1D control points) does 2 things wrong: + // 1) it does not specify the key (time) value + // 2) the control point is specified as a tangent for both bezier and hermite + // => interpolate to get the key values, and offset the tangent values + v1.set((v0.x*2 + v3.x)/3, v0.y + outArray[offset]); + v2.set((v0.x + v3.x*2)/3, v3.y - inArray[offset]); + } + else { + // the expected form (2D control points) + v1.set(outArray[offset*2], outArray[offset*2+1]); + v2.set(inArray[offset*2], inArray[offset*2+1]); + + // if this is a hermite or cardinal spline, treat the values as tangents + if (dStrEqual(interp_method, "HERMITE") || dStrEqual(interp_method, "CARDINAL")) { + v1.set(v0.x + v1.x, v3.y - v1.y); + v2.set(v0.x + v2.x, v3.x - v2.y); + } + } + } + + // find 's' that gives the desired 't' value + F32 s = invertParamCubic(t, v0.x, v1.x, v2.x, v3.x); + + // Calculate the output value using Bernstein evaluation and the + // computed 's' value + F32 c = 3.0f*(v1.y - v0.y); + F32 e = 3.0f*(v2.y - v1.y); + *value = (((v3.y - v0.y - e)*s + e - c)*s + c)*s + v0.y; + } + else { + // default to LINEAR interpolation + F32 s = mClampF((t - v0.x) / (v3.x - v0.x), 0.0f, 1.0f); + *value = v0.y + (v3.y - v0.y) * s; + } +} + +void AnimData::interpValue(F32 t, U32 offset, const char** value) const +{ + if (input.size() == 0) + *value = ""; + else if (input.size() == 1) + *value = output.getStringValue(0); + else + { + // first find the index of the input keyframe BEFORE 't' + int index; + for (index = 0; index < input.size()-2; index++) { + if (input.getFloatValue(index + 1) > t) + break; + } + + // String values only support STEP interpolation, so just get the + // value at the input keyframe + *value = output.getStringValue(index); + } +} + +//------------------------------------------------------------------------------ +// Collada document conditioners + +static void conditioner_fixupTextureSIDs(domCOLLADA* root) +{ + for (int iLib = 0; iLib < root->getLibrary_effects_array().getCount(); iLib++) { + domLibrary_effects* lib = root->getLibrary_effects_array()[iLib]; + for (int iEffect = 0; iEffect < lib->getEffect_array().getCount(); iEffect++) { + domEffect* effect = lib->getEffect_array()[iEffect]; + const domCommon_color_or_texture_type_complexType* diffuse = findEffectDiffuse(effect); + if (!diffuse || !diffuse->getTexture()) + continue; + + // Resolve the SID => if it is an , add and + // elements to conform to the Collada spec. + const char *image_sid = diffuse->getTexture()->getTexture(); + daeSIDResolver resolver(effect, image_sid); + if (!daeSafeCast(resolver.getElement())) + continue; + + daeErrorHandler::get()->handleWarning(avar("Fixup %s . " + "pointing at instead of ", effect->getID())); + + // Generate SIDs for the new sampler2D and surface elements + std::string sampler_sid(std::string(image_sid) + "-sampler"); + std::string surface_sid(std::string(image_sid) + "-surface"); + + domProfile_COMMON* profile = const_cast(findEffectCommonProfile(effect)); + + // Create .. + { + CREATE_ELEMENT(profile, newparam, domCommon_newparam_type) + CREATE_ELEMENT(newparam, sampler2D, domFx_sampler2D_common) + CREATE_ELEMENT(sampler2D, source, domFx_sampler2D_common_complexType::domSource) + + newparam->setSid(sampler_sid.c_str()); + source->setValue(surface_sid.c_str()); + } + + // Create .. + { + CREATE_ELEMENT(profile, newparam, domCommon_newparam_type) + CREATE_ELEMENT(newparam, surface, domFx_surface_common) + CREATE_ELEMENT(surface, init_from, domFx_surface_init_from_common) + CREATE_ELEMENT(surface, format, domFx_surface_common_complexType::domFormat) + + newparam->setSid(surface_sid.c_str()); + surface->setType(FX_SURFACE_TYPE_ENUM_2D); + format->setValue("A8R8G8B8"); + init_from->setValue(image_sid); + } + + // Store sampler2D sid in the . "texture" attribute + diffuse->getTexture()->setTexture(sampler_sid.c_str()); + } + } +} + +static void conditioner_fixupImageURIs(domCOLLADA* root) +{ + for (int iLib = 0; iLib < root->getLibrary_images_array().getCount(); iLib++) { + domLibrary_images* lib = root->getLibrary_images_array()[iLib]; + for (int iImage = 0; iImage < lib->getImage_array().getCount(); iImage++) { + domImage* image = lib->getImage_array()[iImage]; + if (image->getInit_from()) { + xsAnyURI& uri = image->getInit_from()->getValue(); + + // Replace '\' with '/' + if (uri.originalStr().find("\\") != std::string::npos) { + daeErrorHandler::get()->handleWarning(avar("Fixup invalid URI " + "in %s: \"%s\"", image->getID(), uri.originalStr().c_str())); + + std::string str(uri.originalStr()); + std::replace(str.begin(), str.end(), '\\', '/'); + uri.set(str); + } + + // Detect file://texture.jpg => this is an invalid URI and will + // not be parsed correctly + if (uri.scheme() == "file" && + uri.pathFile().empty() && + !uri.authority().empty()) { + daeErrorHandler::get()->handleWarning(avar("Fixup invalid URI " + "in %s: \"%s\"", image->getID(), uri.originalStr().c_str())); + + uri.set(uri.authority()); + } + } + } + } +} + +static void conditioner_fixupTransparency(domCOLLADA* root) +{ + // Transparency is another example of something simple made complicated by + // Collada. There are two (optional) elements that determine transparency: + // + // : a color + // : a percentage applied to the color values + // + // Additionally, has an optional "opaque" attribute that changes + // the way transparency is determined. If set to A_ONE (the default), only the + // alpha value of the transparent color is used, and a value of "1" means fully + // opaque. If set to RGB_ZERO, only the RGB values of transparent are used, and + // a value of "0" means fully opaque. + // + // To further complicate matters, Google Sketchup (all versions) and FeelingSoftware + // ColladaMax (pre 3.03) export materials with the transparency element inverted + // (1-transparency) + + // Get the string + const char *authoringTool = ""; + if (const domAsset* asset = root->getAsset()) { + for (int iContrib = 0; iContrib < asset->getContributor_array().getCount(); iContrib++) { + const domAsset::domContributor* contrib = asset->getContributor_array()[iContrib]; + if (contrib->getAuthoring_tool()) { + authoringTool = contrib->getAuthoring_tool()->getValue(); + break; + } + } + } + + // Check for a match with the known problem-tools + bool invertTransparency = false; + const char *toolNames[] = { "FBX COLLADA exporter", "Google SketchUp", + "Illusoft Collada Exporter", "FCollada" }; + for (int iName = 0; iName < (sizeof(toolNames)/sizeof(toolNames[0])); iName++) { + if (dStrstr(authoringTool, toolNames[iName])) { + invertTransparency = true; + break; + } + } + + if (!invertTransparency) + return; + + // Invert transparency as required for each effect + for (int iLib = 0; iLib < root->getLibrary_effects_array().getCount(); iLib++) { + domLibrary_effects* lib = root->getLibrary_effects_array()[iLib]; + for (int iEffect = 0; iEffect < lib->getEffect_array().getCount(); iEffect++) { + domEffect* effect = lib->getEffect_array()[iEffect]; + + // Find the common profile + const domProfile_COMMON* commonProfile = findEffectCommonProfile(effect); + if (!commonProfile) + continue; + + domCommon_transparent_type* transparent = 0; + if (commonProfile->getTechnique()->getConstant()) + transparent = commonProfile->getTechnique()->getConstant()->getTransparent(); + else if (commonProfile->getTechnique()->getLambert()) + transparent = commonProfile->getTechnique()->getLambert()->getTransparent(); + else if (commonProfile->getTechnique()->getPhong()) + transparent = commonProfile->getTechnique()->getPhong()->getTransparent(); + else if (commonProfile->getTechnique()->getBlinn()) + transparent = commonProfile->getTechnique()->getBlinn()->getTransparent(); + + if (!transparent) + continue; + + // If the shader "opaque" attribute is not specified, set it to + // RGB_ZERO (the opposite of the Collada default), as this is what + // the bad exporter tools seem to assume. + if (!transparent->isAttributeSet("opaque")) { + + daeErrorHandler::get()->handleWarning(avar("Setting " + "\"opaque\" attribute to RGB_ZERO for %s ", effect->getID())); + + transparent->setOpaque(FX_OPAQUE_ENUM_RGB_ZERO); + } + } + } +} + +static void conditioner_checkBindShapeMatrix(domCOLLADA* root) +{ + for (int iLib = 0; iLib < root->getLibrary_controllers_array().getCount(); iLib++) { + domLibrary_controllers* lib = root->getLibrary_controllers_array().get(iLib); + for (int iCon = 0; iCon < lib->getController_array().getCount(); iCon++) { + domController* con = lib->getController_array().get(iCon); + if (con->getSkin() && con->getSkin()->getBind_shape_matrix()) { + + MatrixF mat = vecToMatrixF(con->getSkin()->getBind_shape_matrix()->getValue()); + if (!mat.fullInverse()) { + daeErrorHandler::get()->handleWarning(avar(" " + "in %s is not invertible (may cause problems with " + "skinning)", con->getID())); + } + } + } + } +} + +static void conditioner_fixupVertexWeightJoints(domCOLLADA* root) +{ + for (int iLib = 0; iLib < root->getLibrary_controllers_array().getCount(); iLib++) { + domLibrary_controllers* lib = root->getLibrary_controllers_array().get(iLib); + for (int iCon = 0; iCon < lib->getController_array().getCount(); iCon++) { + domController* con = lib->getController_array().get(iCon); + if (con->getSkin() && con->getSkin()->getVertex_weights()) + { + domInputLocalOffset_Array& vw_inputs = con->getSkin()->getVertex_weights()->getInput_array(); + for (int vInput = 0; vInput < vw_inputs.getCount(); vInput++) { + + domInputLocalOffset *vw_input = vw_inputs.get(vInput); + if (dStrEqual(vw_input->getSemantic(), "JOINT")) { + + // Check if this input points at a float array (bad) + domSource* vw_source = daeSafeCast(vw_input->getSource().getElement()); + if (vw_source->getFloat_array()) { + + // Copy the value from the JOINTS input instead + domInputLocal_Array& joint_inputs = con->getSkin()->getJoints()->getInput_array(); + for (int jInput = 0; jInput < joint_inputs.getCount(); jInput++) { + + domInputLocal *joint_input = joint_inputs.get(jInput); + if (dStrEqual(joint_input->getSemantic(), "JOINT")) { + vw_input->setSource(joint_input->getSource()); + break; + } + } + } + } + } + } + } + } +} + +static void conditioner_createDefaultClip(domCOLLADA* root) +{ + // Check if the document has any s + for (int iLib = 0; iLib < root->getLibrary_animation_clips_array().getCount(); iLib++) { + if (root->getLibrary_animation_clips_array()[iLib]->getAnimation_clip_array().getCount()) + return; + } + + // Get all top-level s into an array + domAnimation_Array animations; + for (int iAnimLib = 0; iAnimLib < root->getLibrary_animations_array().getCount(); iAnimLib++) { + const domLibrary_animations* libraryAnims = root->getLibrary_animations_array()[iAnimLib]; + for (int iAnim = 0; iAnim < libraryAnims->getAnimation_array().getCount(); iAnim++) + animations.append(libraryAnims->getAnimation_array()[iAnim]); + } + + if (!animations.getCount()) + return; + + daeErrorHandler::get()->handleWarning("Creating cyclic animation clip to " + "hold all animations"); + + // Get animation_clip library (create one if necessary) + if (!root->getLibrary_animation_clips_array().getCount()) { + root->createAndPlace("library_animation_clips"); + } + domLibrary_animation_clips* libraryClips = root->getLibrary_animation_clips_array()[0]; + + // Create new animation_clip for the default sequence + CREATE_ELEMENT(libraryClips, animation_clip, domAnimation_clip) + animation_clip->setName("ambient"); + animation_clip->setId("dummy_ambient_clip"); + animation_clip->setStart(0); + animation_clip->setEnd(0); + + // Add all top_level animations to the clip (sub-animations will be included + // when the clip is procesed) + for (int iAnim = 0; iAnim < animations.getCount(); iAnim++) { + if (!animations[iAnim]->getId()) + animations[iAnim]->setId(avar("dummy-animation-id%d", iAnim)); + CREATE_ELEMENT(animation_clip, instance_animation, domInstanceWithExtra) + std::string url(std::string("#") + animations[iAnim]->getId()); + instance_animation->setUrl(url.c_str()); + } + + // Add the 'Torque' profile to specify the 'Cyclic' flag + CREATE_ELEMENT(animation_clip, extra, domExtra) + CREATE_ELEMENT(extra, technique, domTechnique) + CREATE_ELEMENT(technique, any, domAny) + technique->setProfile("Torque"); + any->setElementName("cyclic"); + any->setValue("1"); +} + +static void conditioner_fixupAnimation(domAnimation* anim) +{ + for (int iChannel = 0; iChannel < anim->getChannel_array().getCount(); iChannel++) { + + // Get the animation elements: , + domChannel* channel = anim->getChannel_array()[iChannel]; + domSampler* sampler = daeSafeCast(channel->getSource().getElement()); + if (!sampler) + continue; +/* + // If using a spline interpolation type but no tangents are specified, + // fall back to LINEAR interpolation. + bool isSpline = false; + bool foundInTangent = false; + bool foundOutTangent = false; + for (int iInput = 0; iInput < sampler->getInput_array().getCount(); iInput++) { + const char *semantic = sampler->getInput_array()[iInput]->getSemantic(); + if (dStrEqual(semantic, "INTERPOLATION")) { + if ( + } + if (dStrEqual(semantic, "IN_TANGENT")) + foundInTangent = true; + if (dStrEqual(semantic, "OUT_TANGENT")) + foundOutTangent = true; + } + + if (isSpline && (!foundInTangent || !foundOutTangent)) { + daeErrorHandler::get()->handleWarning(avar("%s type interpolation " + "specified for %s, but IN/OUT TANGENTS are not provided. Using " + "LINEAR interpolation instead."); + + } +*/ + + // Find the animation channel target + daeSIDResolver resolver(channel, channel->getTarget()); + daeElement* target = resolver.getElement(); + if (!target) { + + // Some exporters generate visibility animations but don't add the + // FCOLLADA extension, so the target doesn't actually exist! Detect + // this situation and add the extension manually so the animation + // still works. + if (dStrEndsWith(channel->getTarget(), "/visibility")) { + + // Get parent SID string + char *parentSID = dStrdup(channel->getTarget()); + parentSID[dStrlen(parentSID) - dStrlen("/visibility")] = '\0'; + + // Find the parent element (should be a ) + daeSIDResolver parentResolver(channel, parentSID); + daeElement* parent = parentResolver.getElement(); + delete [] parentSID; + + if (parent && (parent->getElementType() == COLLADA_TYPE::NODE)) { + + // Create the FCOLLADA extension + daeErrorHandler::get()->handleWarning(avar("Creating missing " + "visibility animation target: %s", channel->getTarget())); + + CREATE_ELEMENT(parent, extra, domExtra) + CREATE_ELEMENT(extra, technique, domTechnique) + CREATE_ELEMENT(technique, any, domAny) + + technique->setProfile("FCOLLADA"); + any->setElementName("visibility"); + any->setAttribute("sid", "visibility"); + any->setValue("1"); + } + } + } + } + + // Process child animations + for (int iAnim = 0; iAnim < anim->getAnimation_array().getCount(); iAnim++) + conditioner_fixupAnimation(anim->getAnimation_array()[iAnim]); +} + +/// Apply the set of model conditioners +void ColladaUtils::applyConditioners(domCOLLADA* root) +{ + //-------------------------------------------------------------------------- + // The built-in MAX FBX exporter specifies an SID in the + // "texture" attribute instead of a SID. Detect and fix this. + conditioner_fixupTextureSIDs(root); + + //-------------------------------------------------------------------------- + // The built-in MAX FBX exporter also generates invalid URI paths in the + // . tag, so fix that up too. + conditioner_fixupImageURIs(root); + + //-------------------------------------------------------------------------- + // Many exporters get transparency backwards. Check if the model was exported + // by one with a known issue and correct it. + conditioner_fixupTransparency(root); + + //-------------------------------------------------------------------------- + // Some exporters (AutoDesk) generate invalid bind_shape matrices. Warn if + // the bind_shape_matrix is not invertible. + conditioner_checkBindShapeMatrix(root); + + //-------------------------------------------------------------------------- + // The PoserPro exporter points the JOINT input to the + // inverse bind matrices instead of the joint names array. Detect and fix it. + conditioner_fixupVertexWeightJoints(root); + + //-------------------------------------------------------------------------- + // If the model contains s but no s, just put all + // top level animations into a single clip. + conditioner_createDefaultClip(root); + + //-------------------------------------------------------------------------- + // Apply some animation fixups: + // 1) Some exporters (eg. Blender) generate "BEZIER" type animation curves, + // but do not specify the IN and OUT tangent data arrays. Detect this and + // fall back to LINEAR interpolation. + // 2) Some exporters generate visibility animations but don't add the FCOLLADA + // extension, so the target doesn't actually exist! Detect this situation + // and add the extension manually so the animation still works. + for (int iLib = 0; iLib < root->getLibrary_animations_array().getCount(); iLib++) { + const domLibrary_animations* lib = root->getLibrary_animations_array()[iLib]; + for (int iAnim = 0; iAnim < lib->getAnimation_array().getCount(); iAnim++) + conditioner_fixupAnimation(lib->getAnimation_array()[iAnim]); + } +} + +Torque::Path ColladaUtils::findTexture(const Torque::Path& diffuseMap) +{ + Vector foundPaths; + + GBitmap::sFindFiles(diffuseMap, &foundPaths); + + if (foundPaths.size() > 0) + return Torque::Path(foundPaths[0]); + + // If unable to load texture in current directory + // look in the parent directory. But never look in the root. + Torque::Path newPath(diffuseMap); + + String filePath = newPath.getPath(); + + String::SizeType slash = filePath.find('/', filePath.length(), String::Right); + + if (slash != String::NPos) + { + slash = filePath.find('/', filePath.length(), String::Right); + + if (slash != String::NPos) + { + String truncPath = filePath.substr(0, slash); + newPath.setPath(truncPath); + + return findTexture(newPath); + } + } + + return String::EmptyString; +} + +void ColladaUtils::exportColladaHeader(TiXmlElement* rootNode) +{ + TiXmlElement* assetNode = new TiXmlElement("asset"); + rootNode->LinkEndChild(assetNode); + + TiXmlElement* contributorNode = new TiXmlElement("contributor"); + assetNode->LinkEndChild(contributorNode); + + TiXmlElement* authorNode = new TiXmlElement("author"); + contributorNode->LinkEndChild(authorNode); + + TiXmlElement* authoringToolNode = new TiXmlElement("authoring_tool"); + contributorNode->LinkEndChild(authoringToolNode); + TiXmlText* authorText = new TiXmlText(avar("%s %s Interior Exporter", getEngineProductString(), getVersionString())); + authoringToolNode->LinkEndChild(authorText); + + TiXmlElement* commentsNode = new TiXmlElement("comments"); + contributorNode->LinkEndChild(commentsNode); + + // Get the current time + Platform::LocalTime lt; + Platform::getLocalTime(lt); + String localTime = Platform::localTimeToString(lt); + + localTime.replace('\t', ' '); + + TiXmlElement* createdNode = new TiXmlElement("created"); + assetNode->LinkEndChild(createdNode); + TiXmlText* createdText = new TiXmlText(avar("%s", localTime.c_str())); + createdNode->LinkEndChild(createdText); + + TiXmlElement* modifiedNode = new TiXmlElement("modified"); + assetNode->LinkEndChild(modifiedNode); + TiXmlText* modifiedText = new TiXmlText(avar("%s", localTime.c_str())); + modifiedNode->LinkEndChild(modifiedText); + + TiXmlElement* revisionNode = new TiXmlElement("revision"); + assetNode->LinkEndChild(revisionNode); + + TiXmlElement* titleNode = new TiXmlElement("title"); + assetNode->LinkEndChild(titleNode); + + TiXmlElement* subjectNode = new TiXmlElement("subject"); + assetNode->LinkEndChild(subjectNode); + + TiXmlElement* keywordsNode = new TiXmlElement("keywords"); + assetNode->LinkEndChild(keywordsNode); + + // Torque uses Z_UP with 1 unit equal to 1 meter by default + TiXmlElement* unitNode = new TiXmlElement("unit"); + assetNode->LinkEndChild(unitNode); + unitNode->SetAttribute("meter", "1.000000"); + + TiXmlElement* axisNode = new TiXmlElement("up_axis"); + assetNode->LinkEndChild(axisNode); + TiXmlText* axisText = new TiXmlText("Z_UP"); + axisNode->LinkEndChild(axisText); +} + +void ColladaUtils::exportColladaMaterials(TiXmlElement* rootNode, const OptimizedPolyList& mesh, Vector& matNames, const Torque::Path& colladaFile) +{ + // First the image library + TiXmlElement* imgLibNode = new TiXmlElement("library_images"); + rootNode->LinkEndChild(imgLibNode); + + for (U32 i = 0; i < mesh.mMaterialList.size(); i++) + { + BaseMatInstance* baseInst = mesh.mMaterialList[i]; + + matNames.push_back(String::ToString("Material%d", i)); + + Material* mat = dynamic_cast(baseInst->getMaterial()); + if (!mat) + continue; + + String diffuseMap; + + if (mat->getName() && mat->getName()[0]) + matNames.last() = String(mat->getName()); + + // Handle an auto-generated "Default Material" specially + if (mat->isAutoGenerated()) + { + Torque::Path diffusePath; + + if (mat->mDiffuseMapFilename[0].isNotEmpty()) + diffusePath = mat->mDiffuseMapFilename[0]; + else if (mat->mBaseTexFilename[0].isNotEmpty()) + diffusePath = mat->mBaseTexFilename[0]; + else + diffusePath = String("warningMat"); + + matNames.last() = diffusePath.getFileName(); + diffuseMap += diffusePath.getFullFileName(); + } + else + { + if (mat->mDiffuseMapFilename[0].isNotEmpty()) + diffuseMap += mat->mDiffuseMapFilename[0]; + else if (mat->mBaseTexFilename[0].isNotEmpty()) + diffuseMap += mat->mBaseTexFilename[0]; + else + diffuseMap += "warningMat"; + } + + Torque::Path diffusePath = findTexture(colladaFile.getPath() + "/" + diffuseMap); + + // If we didn't get a path + if (diffusePath.getFullPath().isNotEmpty()) + diffuseMap = Torque::Path::MakeRelativePath(diffusePath, colladaFile); + + TiXmlElement* imageNode = new TiXmlElement("image"); + imgLibNode->LinkEndChild(imageNode); + imageNode->SetAttribute("id", avar("%s-Diffuse", matNames.last().c_str())); + imageNode->SetAttribute("name", avar("%s-Diffuse", matNames.last().c_str())); + + TiXmlElement* initNode = new TiXmlElement("init_from"); + imageNode->LinkEndChild(initNode); + TiXmlText* initText = new TiXmlText(avar("file://%s", diffuseMap.c_str())); + initNode->LinkEndChild(initText); + } + + // Next the material library + TiXmlElement* matLibNode = new TiXmlElement("library_materials"); + rootNode->LinkEndChild(matLibNode); + + for (U32 i = 0; i < mesh.mMaterialList.size(); i++) + { + BaseMatInstance* baseInst = mesh.mMaterialList[i]; + + Material* mat = dynamic_cast(baseInst->getMaterial()); + if (!mat) + continue; + + TiXmlElement* materialNode = new TiXmlElement("material"); + matLibNode->LinkEndChild(materialNode); + materialNode->SetAttribute("id", matNames[i].c_str()); + materialNode->SetAttribute("name", matNames[i].c_str()); + + TiXmlElement* instEffectNode = new TiXmlElement("instance_effect"); + materialNode->LinkEndChild(instEffectNode); + instEffectNode->SetAttribute("url", avar("#%s-fx", matNames[i].c_str())); + } + + // Finally the effects library + TiXmlElement* effectLibNode = new TiXmlElement("library_effects"); + rootNode->LinkEndChild(effectLibNode); + + for (U32 i = 0; i < mesh.mMaterialList.size(); i++) + { + BaseMatInstance* baseInst = mesh.mMaterialList[i]; + + Material* mat = dynamic_cast(baseInst->getMaterial()); + if (!mat) + continue; + + TiXmlElement* effectNode = new TiXmlElement("effect"); + effectLibNode->LinkEndChild(effectNode); + effectNode->SetAttribute("id", avar("%s-fx", matNames[i].c_str())); + effectNode->SetAttribute("name", avar("%s-fx", matNames[i].c_str())); + + TiXmlElement* profileNode = new TiXmlElement("profile_COMMON"); + effectNode->LinkEndChild(profileNode); + + TiXmlElement* techniqueNode = new TiXmlElement("technique"); + profileNode->LinkEndChild(techniqueNode); + techniqueNode->SetAttribute("sid", "standard"); + + TiXmlElement* phongNode = new TiXmlElement("phong"); + techniqueNode->LinkEndChild(phongNode); + + TiXmlElement* diffuseNode = new TiXmlElement("diffuse"); + phongNode->LinkEndChild(diffuseNode); + + TiXmlElement* textureNode = new TiXmlElement("texture"); + diffuseNode->LinkEndChild(textureNode); + textureNode->SetAttribute("texture", avar("%s-Diffuse", matNames[i].c_str())); + textureNode->SetAttribute("texcoord", "CHANNEL0"); + + // Extra info useful for getting the texture to show up correctly in some apps + TiXmlElement* extraNode = new TiXmlElement("extra"); + textureNode->LinkEndChild(extraNode); + + TiXmlElement* extraTechNode = new TiXmlElement("technique"); + extraNode->LinkEndChild(extraTechNode); + extraTechNode->SetAttribute("profile", "MAYA"); + + TiXmlElement* extraWrapUNode = new TiXmlElement("wrapU"); + extraTechNode->LinkEndChild(extraWrapUNode); + extraWrapUNode->SetAttribute("sid", "wrapU0"); + + TiXmlText* extraWrapUText = new TiXmlText("TRUE"); + extraWrapUNode->LinkEndChild(extraWrapUText); + + TiXmlElement* extraWrapVNode = new TiXmlElement("wrapV"); + extraTechNode->LinkEndChild(extraWrapVNode); + extraWrapVNode->SetAttribute("sid", "wrapV0"); + + TiXmlText* extraWrapVText = new TiXmlText("TRUE"); + extraWrapVNode->LinkEndChild(extraWrapVText); + + TiXmlElement* extraBlendNode = new TiXmlElement("blend_mode"); + extraTechNode->LinkEndChild(extraBlendNode); + + TiXmlText* extraBlendText = new TiXmlText("ADD"); + extraBlendNode->LinkEndChild(extraBlendText); + } +} + +void ColladaUtils::exportColladaTriangles(TiXmlElement* meshNode, const OptimizedPolyList& mesh, const String& meshName, const Vector& matNames) +{ + for (U32 i = 0; i < matNames.size(); i++) + { + // Calculate the number of triangles that uses this Material + U32 triangleCount = 0; + + for (U32 j = 0; j < mesh.mPolyList.size(); j++) + { + const OptimizedPolyList::Poly& poly = mesh.mPolyList[j]; + + if (poly.material != i) + continue; + + if (poly.vertexCount < 3) + continue; + + if (poly.type == OptimizedPolyList::TriangleList || + poly.type == OptimizedPolyList::TriangleFan || + poly.type == OptimizedPolyList::TriangleStrip) + { + triangleCount += poly.vertexCount - 2; + } + else + AssertISV(false, "ColladaUtils::exportColladaTriangles(): Unknown Poly type!"); + } + + // Make sure that we are actually using this Material + if (triangleCount == 0) + continue; + + TiXmlElement* trianglesNode = new TiXmlElement("triangles"); + meshNode->LinkEndChild(trianglesNode); + trianglesNode->SetAttribute("material", matNames[i].c_str()); + trianglesNode->SetAttribute("count", avar("%d", triangleCount)); + + TiXmlElement* trianglesVertInputNode = new TiXmlElement("input"); + trianglesNode->LinkEndChild(trianglesVertInputNode); + trianglesVertInputNode->SetAttribute("semantic", "VERTEX"); + trianglesVertInputNode->SetAttribute("offset", "0"); + trianglesVertInputNode->SetAttribute("source", avar("#%s-Vertex", meshName.c_str())); + + TiXmlElement* trianglesNormalInputNode = new TiXmlElement("input"); + trianglesNode->LinkEndChild(trianglesNormalInputNode); + trianglesNormalInputNode->SetAttribute("semantic", "NORMAL"); + trianglesNormalInputNode->SetAttribute("offset", "1"); + trianglesNormalInputNode->SetAttribute("source", avar("#%s-Normal", meshName.c_str())); + + TiXmlElement* trianglesUV0InputNode = new TiXmlElement("input"); + trianglesNode->LinkEndChild(trianglesUV0InputNode); + trianglesUV0InputNode->SetAttribute("semantic", "TEXCOORD"); + trianglesUV0InputNode->SetAttribute("offset", "2"); + trianglesUV0InputNode->SetAttribute("set", "0"); + trianglesUV0InputNode->SetAttribute("source", avar("#%s-UV0", meshName.c_str())); + + TiXmlElement* polyNode = new TiXmlElement("p"); + trianglesNode->LinkEndChild(polyNode); + + Vector tempIndices; + tempIndices.reserve(4); + + for (U32 j = 0; j < mesh.mPolyList.size(); j++) + { + const OptimizedPolyList::Poly& poly = mesh.mPolyList[j]; + + if (poly.vertexCount < 3) + continue; + + if (poly.material != i) + continue; + + tempIndices.setSize(poly.vertexCount); + dMemset(tempIndices.address(), 0, poly.vertexCount); + + if (poly.type == OptimizedPolyList::TriangleStrip) + { + tempIndices[0] = 0; + U32 idx = 1; + + for (U32 k = 1; k < poly.vertexCount; k += 2) + tempIndices[idx++] = k; + + for (U32 k = ((poly.vertexCount - 1) & (~0x1)); k > 0; k -= 2) + tempIndices[idx++] = k; + } + else if (poly.type == OptimizedPolyList::TriangleList || + poly.type == OptimizedPolyList::TriangleFan) + { + for (U32 k = 0; k < poly.vertexCount; k++) + tempIndices[k] = k; + } + else + AssertISV(false, "ColladaUtils::exportColladaTriangles(): Unknown Poly type!"); + + const U32& firstIdx = mesh.mIndexList[poly.vertexStart]; + const OptimizedPolyList::VertIndex& firstVertIdx = mesh.mVertexList[firstIdx]; + + for (U32 k = 1; k < poly.vertexCount - 1; k++) + { + const U32& secondIdx = mesh.mIndexList[poly.vertexStart + tempIndices[k]]; + const U32& thirdIdx = mesh.mIndexList[poly.vertexStart + tempIndices[k + 1]]; + + const OptimizedPolyList::VertIndex& secondVertIdx = mesh.mVertexList[secondIdx]; + const OptimizedPolyList::VertIndex& thirdVertIdx = mesh.mVertexList[thirdIdx]; + + // Note the reversed winding on the triangles + const char* tri = avar("%d %d %d %d %d %d %d %d %d", + thirdVertIdx.vertIdx, thirdVertIdx.normalIdx, thirdVertIdx.uv0Idx, + secondVertIdx.vertIdx, secondVertIdx.normalIdx, secondVertIdx.uv0Idx, + firstVertIdx.vertIdx, firstVertIdx.normalIdx, firstVertIdx.uv0Idx); + + TiXmlText* triangleText = new TiXmlText(tri); + polyNode->LinkEndChild(triangleText); + } + } + } +} + +void ColladaUtils::exportColladaMesh(TiXmlElement* rootNode, const OptimizedPolyList& mesh, const String& meshName, const Vector& matNames) +{ + TiXmlElement* libGeomsNode = new TiXmlElement("library_geometries"); + rootNode->LinkEndChild(libGeomsNode); + + TiXmlElement* geometryNode = new TiXmlElement("geometry"); + libGeomsNode->LinkEndChild(geometryNode); + geometryNode->SetAttribute("id", avar("%s-lib", meshName.c_str())); + geometryNode->SetAttribute("name", avar("%sMesh", meshName.c_str())); + + TiXmlElement* meshNode = new TiXmlElement("mesh"); + geometryNode->LinkEndChild(meshNode); + + // Save out the vertices + TiXmlElement* vertsSourceNode = new TiXmlElement("source"); + meshNode->LinkEndChild(vertsSourceNode); + vertsSourceNode->SetAttribute("id", avar("%s-Position", meshName.c_str())); + + TiXmlElement* vertsNode = new TiXmlElement("float_array"); + vertsSourceNode->LinkEndChild(vertsNode); + vertsNode->SetAttribute("id", avar("%s-Position-array", meshName.c_str())); + vertsNode->SetAttribute("count", avar("%d", mesh.mPoints.size() * 3)); + + for (U32 i = 0; i < mesh.mPoints.size(); i++) + { + const Point3F& vert = mesh.mPoints[i]; + + TiXmlText* vertText = new TiXmlText(avar("%.4f %.4f %.4f", vert.x, vert.y, vert.z)); + vertsNode->LinkEndChild(vertText); + } + + // Save the vertex accessor + TiXmlElement* vertsTechNode = new TiXmlElement("technique_common"); + vertsSourceNode->LinkEndChild(vertsTechNode); + + TiXmlElement* vertsAccNode = new TiXmlElement("accessor"); + vertsTechNode->LinkEndChild(vertsAccNode); + vertsAccNode->SetAttribute("source", avar("#%s-Position-array", meshName.c_str())); + vertsAccNode->SetAttribute("count", avar("%d", mesh.mPoints.size())); + vertsAccNode->SetAttribute("stride", "3"); + + TiXmlElement* vertsAccXNode = new TiXmlElement("param"); + vertsAccNode->LinkEndChild(vertsAccXNode); + vertsAccXNode->SetAttribute("name", "X"); + vertsAccXNode->SetAttribute("type", "float"); + + TiXmlElement* vertsAccYNode = new TiXmlElement("param"); + vertsAccNode->LinkEndChild(vertsAccYNode); + vertsAccYNode->SetAttribute("name", "Y"); + vertsAccYNode->SetAttribute("type", "float"); + + TiXmlElement* vertsAccZNode = new TiXmlElement("param"); + vertsAccNode->LinkEndChild(vertsAccZNode); + vertsAccZNode->SetAttribute("name", "Z"); + vertsAccZNode->SetAttribute("type", "float"); + + // Save out the normals + TiXmlElement* normalsSourceNode = new TiXmlElement("source"); + meshNode->LinkEndChild(normalsSourceNode); + normalsSourceNode->SetAttribute("id", avar("%s-Normal", meshName.c_str())); + + TiXmlElement* normalsNode = new TiXmlElement("float_array"); + normalsSourceNode->LinkEndChild(normalsNode); + normalsNode->SetAttribute("id", avar("%s-Normal-array", meshName.c_str())); + normalsNode->SetAttribute("count", avar("%d", mesh.mNormals.size() * 3)); + + for (U32 i = 0; i < mesh.mNormals.size(); i++) + { + const Point3F& normal = mesh.mNormals[i]; + + TiXmlText* normalText = new TiXmlText(avar("%.4f %.4f %.4f", normal.x, normal.y, normal.z)); + normalsNode->LinkEndChild(normalText); + } + + // Save the normals accessor + TiXmlElement* normalsTechNode = new TiXmlElement("technique_common"); + normalsSourceNode->LinkEndChild(normalsTechNode); + + TiXmlElement* normalsAccNode = new TiXmlElement("accessor"); + normalsTechNode->LinkEndChild(normalsAccNode); + normalsAccNode->SetAttribute("source", avar("#%s-Normal-array", meshName.c_str())); + normalsAccNode->SetAttribute("count", avar("%d", mesh.mNormals.size())); + normalsAccNode->SetAttribute("stride", "3"); + + TiXmlElement* normalsAccXNode = new TiXmlElement("param"); + normalsAccNode->LinkEndChild(normalsAccXNode); + normalsAccXNode->SetAttribute("name", "X"); + normalsAccXNode->SetAttribute("type", "float"); + + TiXmlElement* normalsAccYNode = new TiXmlElement("param"); + normalsAccNode->LinkEndChild(normalsAccYNode); + normalsAccYNode->SetAttribute("name", "Y"); + normalsAccYNode->SetAttribute("type", "float"); + + TiXmlElement* normalsAccZNode = new TiXmlElement("param"); + normalsAccNode->LinkEndChild(normalsAccZNode); + normalsAccZNode->SetAttribute("name", "Z"); + normalsAccZNode->SetAttribute("type", "float"); + + // Save out the uvs + TiXmlElement* uv0SourceNode = new TiXmlElement("source"); + meshNode->LinkEndChild(uv0SourceNode); + uv0SourceNode->SetAttribute("id", avar("%s-UV0", meshName.c_str())); + + TiXmlElement* uv0Node = new TiXmlElement("float_array"); + uv0SourceNode->LinkEndChild(uv0Node); + uv0Node->SetAttribute("id", avar("%s-UV0-array", meshName.c_str())); + uv0Node->SetAttribute("count", avar("%d", mesh.mUV0s.size() * 2)); + + for (U32 i = 0; i < mesh.mUV0s.size(); i++) + { + const Point2F& uv0 = mesh.mUV0s[i]; + + TiXmlText* uv0Text = new TiXmlText(avar("%.4f %.4f", uv0.x, uv0.y)); + uv0Node->LinkEndChild(uv0Text); + } + + // Save the uv0 accessor + TiXmlElement* uv0TechNode = new TiXmlElement("technique_common"); + uv0SourceNode->LinkEndChild(uv0TechNode); + + TiXmlElement* uv0AccNode = new TiXmlElement("accessor"); + uv0TechNode->LinkEndChild(uv0AccNode); + uv0AccNode->SetAttribute("source", avar("#%s-UV0-array", meshName.c_str())); + uv0AccNode->SetAttribute("count", avar("%d", mesh.mUV0s.size())); + uv0AccNode->SetAttribute("stride", "2"); + + TiXmlElement* uv0AccSNode = new TiXmlElement("param"); + uv0AccNode->LinkEndChild(uv0AccSNode); + uv0AccSNode->SetAttribute("name", "S"); + uv0AccSNode->SetAttribute("type", "float"); + + TiXmlElement* uv0AccTNode = new TiXmlElement("param"); + uv0AccNode->LinkEndChild(uv0AccTNode); + uv0AccTNode->SetAttribute("name", "T"); + uv0AccTNode->SetAttribute("type", "float"); + + // Define the vertices position array + TiXmlElement* verticesNode = new TiXmlElement("vertices"); + meshNode->LinkEndChild(verticesNode); + verticesNode->SetAttribute("id", avar("%s-Vertex", meshName.c_str())); + + TiXmlElement* verticesInputNode = new TiXmlElement("input"); + verticesNode->LinkEndChild(verticesInputNode); + verticesInputNode->SetAttribute("semantic", "POSITION"); + verticesInputNode->SetAttribute("source", avar("#%s-Position", meshName.c_str())); + + exportColladaTriangles(meshNode, mesh, meshName, matNames); +} + +void ColladaUtils::exportColladaScene(TiXmlElement* rootNode, const String& meshName, const Vector& matNames) +{ + TiXmlElement* libSceneNode = new TiXmlElement("library_visual_scenes"); + rootNode->LinkEndChild(libSceneNode); + + TiXmlElement* visSceneNode = new TiXmlElement("visual_scene"); + libSceneNode->LinkEndChild(visSceneNode); + visSceneNode->SetAttribute("id", "RootNode"); + visSceneNode->SetAttribute("name", "RootNode"); + + TiXmlElement* nodeNode = new TiXmlElement("node"); + visSceneNode->LinkEndChild(nodeNode); + nodeNode->SetAttribute("id", avar("%s", meshName.c_str())); + nodeNode->SetAttribute("name", avar("%s", meshName.c_str())); + + TiXmlElement* instanceGeomNode = new TiXmlElement("instance_geometry"); + nodeNode->LinkEndChild(instanceGeomNode); + instanceGeomNode->SetAttribute("url", avar("#%s-lib", meshName.c_str())); + + TiXmlElement* bindMatNode = new TiXmlElement("bind_material"); + instanceGeomNode->LinkEndChild(bindMatNode); + + TiXmlElement* techniqueNode = new TiXmlElement("technique_common"); + bindMatNode->LinkEndChild(techniqueNode); + + // Bind the materials + for (U32 i = 0; i < matNames.size(); i++) + { + TiXmlElement* instMatNode = new TiXmlElement("instance_material"); + techniqueNode->LinkEndChild(instMatNode); + instMatNode->SetAttribute("symbol", avar("%s", matNames[i].c_str())); + instMatNode->SetAttribute("target", avar("#%s", matNames[i].c_str())); + } + + TiXmlElement* sceneNode = new TiXmlElement("scene"); + rootNode->LinkEndChild(sceneNode); + + TiXmlElement* instVisSceneNode = new TiXmlElement("instance_visual_scene"); + sceneNode->LinkEndChild(instVisSceneNode); + instVisSceneNode->SetAttribute("url", "#RootNode"); +} + +void ColladaUtils::exportToCollada(const Torque::Path& colladaFile, const OptimizedPolyList& mesh, const String& meshName) +{ + // Get the mesh name + String outMeshName = meshName; + + if (outMeshName.isEmpty()) + outMeshName = colladaFile.getFileName(); + + // The XML document that will hold all of our data + TiXmlDocument doc; + + // Add a standard XML declaration to the top + TiXmlDeclaration* xmlDecl = new TiXmlDeclaration("1.0", "utf-8", ""); + doc.LinkEndChild(xmlDecl); + + // Create our Collada root node and populate a couple standard attributes + TiXmlElement* rootNode = new TiXmlElement("COLLADA"); + rootNode->SetAttribute("xmlns", "http://www.collada.org/2005/11/COLLADASchema"); + rootNode->SetAttribute("version", "1.4.0"); + + // Add the root node to the document + doc.LinkEndChild(rootNode); + + // Save out our header info + exportColladaHeader(rootNode); + + // Save out the materials + Vector mapNames; + + exportColladaMaterials(rootNode, mesh, mapNames, colladaFile); + + // Save out our geometry + exportColladaMesh(rootNode, mesh, outMeshName, mapNames); + + // Save out our scene nodes + exportColladaScene(rootNode, outMeshName, mapNames); + + // Write out the actual Collada file + char fullPath[MAX_PATH_LENGTH]; + Platform::makeFullPathName(colladaFile.getFullPath(), fullPath, MAX_PATH_LENGTH); + + if (!doc.SaveFile(fullPath)) + Con::errorf("ColladaUtils::exportToCollada(): Unable to export to %s", fullPath); +} \ No newline at end of file diff --git a/ts/collada/colladaUtils.h b/ts/collada/colladaUtils.h new file mode 100644 index 0000000..b77b366 --- /dev/null +++ b/ts/collada/colladaUtils.h @@ -0,0 +1,727 @@ +//----------------------------------------------------------------------------- +// Collada-2-DTS +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _COLLADA_UTILS_H_ +#define _COLLADA_UTILS_H_ + +#ifdef _MSC_VER +#pragma warning(disable : 4786) // disable warning about long debug symbol names +#pragma warning(disable : 4355) // disable "'this' : used in base member initializer list" warnings +#endif + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MMATRIX_H_ +#include "math/mMatrix.h" +#endif +#ifndef _MQUAT_H_ +#include "math/mQuat.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TSSHAPE_LOADER_H_ +#include "ts/loader/tsShapeLoader.h" +#endif +#ifndef _OPTIMIZEDPOLYLIST_H_ +#include "collision/optimizedPolyList.h" +#endif +#ifndef TINYXML_INCLUDED +#include "tinyxml.h" +#endif + +#include "platform/tmm_off.h" + +#include "dae.h" +#include "dae/daeErrorHandler.h" +#include "dae/domAny.h" +#include "dom/domProfile_COMMON.h" +#include "dom/domMaterial.h" +#include "dom/domGeometry.h" +#include "dom/domMorph.h" +#include "dom/domNode.h" +#include "dom/domCOLLADA.h" + +#include "platform/tmm_on.h" + +namespace ColladaUtils +{ + struct ImportOptions + { + enum eLodType + { + DetectDTS = 0, + SingleSize, + TrailingNumber, + NumLodTypes + }; + + domUpAxisType upAxis; // Override for the collada element + F32 unit; // Override for the collada element + eLodType lodType; // LOD type option + S32 singleDetailSize; // Detail size for all meshes in the model + String matNamePrefix; // Prefix to apply to collada material names + String alwaysImport; // List of node names (with wildcards) to import, even if in the neverImport list + String neverImport; // List of node names (with wildcards) to ignore on loading + bool ignoreNodeScale; // Ignore elements in s + bool adjustCenter; // Translate model so origin is at the center + bool adjustFloor; // Translate model so origin is at the bottom + bool forceUpdateMaterials; // Force update of materials.cs + + ImportOptions() + { + reset(); + } + + void reset() + { + upAxis = UPAXISTYPE_COUNT; + unit = -1.0f; + lodType = DetectDTS; + singleDetailSize = 2; + matNamePrefix = ""; + alwaysImport = ""; + neverImport = ""; + ignoreNodeScale = false; + adjustCenter = false; + adjustFloor = false; + forceUpdateMaterials = false; + } + }; + + ImportOptions& getOptions(); + + void convertTransform(MatrixF& m); + + void collapsePath(std::string& path); + + // Apply the set of Collada conditioners (suited for loading Collada models into Torque) + void applyConditioners(domCOLLADA* root); + + const domProfile_COMMON* findEffectCommonProfile(const domEffect* effect); + const domCommon_color_or_texture_type_complexType* findEffectDiffuse(const domEffect* effect); + const domCommon_color_or_texture_type_complexType* findEffectSpecular(const domEffect* effect); + const domFx_sampler2D_common_complexType* getTextureSampler(const domEffect* effect, const domCommon_color_or_texture_type_complexType* texture); + String getSamplerImagePath(const domEffect* effect, const domFx_sampler2D_common_complexType* sampler2D); + String resolveImagePath(const domImage* image); + + // Collada export helper functions + Torque::Path findTexture(const Torque::Path& diffuseMap); + void exportColladaHeader(TiXmlElement* rootNode); + void exportColladaMaterials(TiXmlElement* rootNode, const OptimizedPolyList& mesh, Vector& matNames, const Torque::Path& colladaFile); + void exportColladaTriangles(TiXmlElement* meshNode, const OptimizedPolyList& mesh, const String& meshName, const Vector& matNames); + void exportColladaMesh(TiXmlElement* rootNode, const OptimizedPolyList& mesh, const String& meshName, const Vector& matNames); + void exportColladaScene(TiXmlElement* rootNode, const String& meshName, const Vector& matNames); + + // Export an OptimizedPolyList to a simple Collada file + void exportToCollada(const Torque::Path& colladaFile, const OptimizedPolyList& mesh, const String& meshName = String::EmptyString); +}; + +//----------------------------------------------------------------------------- +// Helper Classes +// +// The Collada DOM uses a different class for each XML element, and there is very +// little class inheritance, even though many elements have the same attributes +// and children. This makes the DOM a bit ugly to work with, and the following +// templates attempt to make this situation a bit nicer by providing a common way +// to access common elements, while retaining the strong typing of the DOM classes. +//----------------------------------------------------------------------------- + +/// Convert from the Collada transform types to a Torque MatrixF +template inline MatrixF vecToMatrixF(const domListOfFloats& vec) { return MatrixF(true); } + +/// Collada : [x_translate, y_translate, z_translate] +template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) +{ + MatrixF mat(true); + mat.setPosition(Point3F(vec[0], vec[1], vec[2])); + return mat; +} + +/// Collada : [x_scale, y_scale, z_scale] +template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) +{ + MatrixF mat(true); + mat.scale(Point3F(vec[0], vec[1], vec[2])); + return mat; +} + +/// Collada : [rotation_axis, angle_in_degrees] +template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) +{ + AngAxisF aaxis(Point3F(vec[0], vec[1], vec[2]), -(vec[3] * M_PI) / 180.0f); + MatrixF mat(true); + aaxis.setMatrix(&mat); + return mat; +} + +/// Collada : same form as TGE (woohoo!) +template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) +{ + MatrixF mat; + for (int i = 0; i < 16; i++) + mat[i] = vec[i]; + return mat; +} + +/// Collada : [angle_in_degrees, rotation_axis, translation_axis] +/// skew transform code adapted from GMANMatrix4 implementation +template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) +{ + F32 angle = -(vec[0] * M_PI) / 180.0f; + Point3F rotAxis(vec[1], vec[2], vec[3]); + Point3F transAxis(vec[4], vec[5], vec[6]); + + transAxis.normalize(); + + Point3F a1 = transAxis * mDot(rotAxis, transAxis); + Point3F a2 = rotAxis - a1; + a2.normalize(); + + F32 an1 = mDot(rotAxis, a2); + F32 an2 = mDot(rotAxis, transAxis); + + F32 rx = an1 * mCos(angle) - an2 * mSin(angle); + F32 ry = an1 * mSin(angle) + an2 * mCos(angle); + + // Check for rotation parallel to translation + F32 alpha = (an1 == 0) ? 0 : (ry/rx - an2/an1); + + MatrixF mat(true); + mat(0,0) = a2.x * transAxis.x * alpha + 1.0; + mat(1,0) = a2.y * transAxis.x * alpha; + mat(2,0) = a2.z * transAxis.x * alpha; + + mat(0,1) = a2.x * transAxis.y * alpha; + mat(1,1) = a2.y * transAxis.y * alpha + 1.0; + mat(2,1) = a2.z * transAxis.y * alpha; + + mat(0,2) = a2.x * transAxis.z * alpha; + mat(1,2) = a2.y * transAxis.z * alpha; + mat(2,2) = a2.z * transAxis.z * alpha + 1.0; + return mat; +} + +/// Collada : [eye, target, up] +template<> inline MatrixF vecToMatrixF(const domListOfFloats& vec) +{ + Point3F eye(vec[1], vec[1], vec[2]); + Point3F target(vec[3], vec[4], vec[5]); + Point3F up(vec[6], vec[7], vec[8]); + + Point3F fwd = target - eye; + fwd.normalizeSafe(); + + Point3F right = mCross(fwd, up); + right.normalizeSafe(); + + up = mCross(right, fwd); + up.normalizeSafe(); + + MatrixF mat(true); + mat.setColumn(0, right); + mat.setColumn(1, fwd); + mat.setColumn(2, up); + mat.setColumn(3, eye); + mat.inverse(); + return mat; +} + +//----------------------------------------------------------------------------- + +/// Try to get a name for the element using the following attributes (in order): +/// name, sid, id, "null" +template inline const char* _GetNameOrId(const T* element) +{ + return element->getName() ? element->getName() : (element->getId() ? element->getId() : "null"); +} + +template<> inline const char* _GetNameOrId(const domInstance_geometry* element) +{ + return element->getName() ? element->getName() : (element->getSid() ? element->getSid() : "null"); +} + +template<> inline const char* _GetNameOrId(const domInstance_controller* element) +{ + return element->getName() ? element->getName() : (element->getSid() ? element->getSid() : "null"); +} + +//----------------------------------------------------------------------------- +// Collada s are extremely flexible, and thus difficult to access in a nice +// way. This class attempts to provide a clean interface to convert Collada source +// data to the appropriate Torque data structure without losing any of the flexibility +// of the underlying Collada DOM. +// +// Some of the conversions we need to handle are: +// - daeString to const char* +// - daeIDRef to const char* +// - double to F32 +// - double to Point2F +// - double to Point3F +// - double to MatrixF +// +// The _SourceReader object is initialized with a list of parameter names that it +// tries to match to elements in the source accessor to figure out how to +// pull values out of the 1D source array. Note that no type checking of any kind +// is done until we actually try to extract values from the source. +class _SourceReader +{ + const domSource* source; // the wrapped Collada source + const domAccessor* accessor; // shortcut to the source accessor + Vector offsets; // offset of each of the desired values to pull from the source array + +public: + _SourceReader() : source(0), accessor(0) {} + + void reset() + { + source = 0; + accessor = 0; + offsets.clear(); + } + + //------------------------------------------------------ + // Initialize the _SourceReader object + bool initFromSource(const domSource* src, const char* paramNames[] = 0) + { + source = src; + accessor = source->getTechnique_common()->getAccessor(); + offsets.clear(); + + // The source array has groups of values in a 1D stream => need to map the + // input param names to source params to determine the offset within the + // group for each desired value + U32 paramCount = 0; + while (paramNames && paramNames[paramCount][0]) { + // lookup the index of the source param that matches the input param + offsets.push_back(0); + for (U32 iParam = 0; iParam < accessor->getParam_array().getCount(); iParam++) { + if (accessor->getParam_array()[iParam]->getName() && + dStrEqual(accessor->getParam_array()[iParam]->getName(), paramNames[paramCount])) { + offsets.last() = iParam; + break; + } + } + paramCount++; + } + + // If no input params were specified, just map the source params directly + if (!offsets.size()) { + for (int iParam = 0; iParam < accessor->getParam_array().getCount(); iParam++) + offsets.push_back(iParam); + } + + return true; + } + + //------------------------------------------------------ + // Shortcut to the size of the array (should be the number of destination objects) + S32 size() const { return accessor ? accessor->getCount() : 0; } + + // Get the number of elements per group in the source + S32 stride() const { return accessor ? accessor->getStride() : 0; } + + //------------------------------------------------------ + // Get a pointer to the start of a group of values (index advances by stride) + //template T getArrayData(int index) const { return 0; } + + const double* getStringArrayData(int index) const + { + if ((index >= 0) && (index < size())) { + if (source->getFloat_array()) + return &source->getFloat_array()->getValue()[index*stride()]; + } + return 0; + } + + //------------------------------------------------------ + // Read a single value from the source array + //template T getValue(int index) const { return T; } + + const char* getStringValue(int index) const + { + if ((index >= 0) && (index < size())) { + // could be plain strings or IDREFs + if (source->getName_array()) + return source->getName_array()->getValue()[index*stride()]; + else if (source->getIDREF_array()) + return source->getIDREF_array()->getValue()[index*stride()].getID(); + } + return ""; + } + + F32 getFloatValue(int index) const + { + F32 value(0); + if (const double* data = getStringArrayData(index)) + return data[offsets[0]]; + return value; + } + + Point2F getPoint2FValue(int index) const + { + Point2F value(0, 0); + if (const double* data = getStringArrayData(index)) + value.set(data[offsets[0]], data[offsets[1]]); + return value; + } + + Point3F getPoint3FValue(int index) const + { + Point3F value(1, 0, 0); + if (const double* data = getStringArrayData(index)) + value.set(data[offsets[0]], data[offsets[1]], data[offsets[2]]); + return value; + } + + ColorI getColorIValue(int index) const + { + ColorI value(255, 255, 255, 255); + if (const double* data = getStringArrayData(index)) + { + value.red = data[offsets[0]] * 255.0; + value.green = data[offsets[1]] * 255.0; + value.blue = data[offsets[2]] * 255.0; + if ( stride() == 4 ) + value.alpha = data[offsets[3]] * 255.0; + } + return value; + } + + MatrixF getMatrixFValue(int index) const + { + MatrixF value(true); + if (const double* data = getStringArrayData(index)) { + for (int i = 0; i < 16; i++) + value[i] = data[i]; + } + return value; + } +}; + +//----------------------------------------------------------------------------- +// Collada geometric primitives: Use the BasePrimitive class to access the +// different primitive types in a nice way. +class BasePrimitive +{ +public: + /// Return true if the element is a geometric primitive type + static bool isPrimitive(const daeElement* element) + { + switch (element->getElementType()) { + case COLLADA_TYPE::TRIANGLES: case COLLADA_TYPE::POLYLIST: + case COLLADA_TYPE::POLYGONS: case COLLADA_TYPE::TRIFANS: + case COLLADA_TYPE::TRISTRIPS: case COLLADA_TYPE::CAPSULE: + case COLLADA_TYPE::CYLINDER: case COLLADA_TYPE::LINES: + case COLLADA_TYPE::LINESTRIPS: case COLLADA_TYPE::PLANE: + case COLLADA_TYPE::SPLINE: case COLLADA_TYPE::SPHERE: + case COLLADA_TYPE::TAPERED_CAPSULE: case COLLADA_TYPE::TAPERED_CYLINDER: + return true; + } + return false; + } + + /// Return true if the element is a supported primitive type + static bool isSupportedPrimitive(const daeElement* element) + { + switch (element->getElementType()) { + case COLLADA_TYPE::TRIANGLES: + case COLLADA_TYPE::POLYLIST: + case COLLADA_TYPE::POLYGONS: + return true; + } + return false; + } + + /// Construct a child class based on the type of Collada element + static BasePrimitive* get(const daeElement* element); + + /// Methods to be implemented for each supported Collada geometric element + virtual const char* getElementName() = 0; + virtual const char* getMaterial() = 0; + virtual const domInputLocalOffset_Array& getInputs() = 0; + + virtual S32 getStride() const = 0; + virtual const domListOfUInts *getTriangleData() = 0; +}; + +/// Template child class for supported Collada primitive elements +template class ColladaPrimitive : public BasePrimitive +{ + T* primitive; + domListOfUInts *pTriangleData; + S32 stride; +public: + ColladaPrimitive(const daeElement* e) : pTriangleData(0) + { + // Cast to geometric primitive element + primitive = daeSafeCast(const_cast(e)); + + // Determine stride + stride = 0; + for (int iInput = 0; iInput < getInputs().getCount(); iInput++) { + if (getInputs()[iInput]->getOffset() >= stride) + stride = getInputs()[iInput]->getOffset() + 1; + } + } + ~ColladaPrimitive() + { + delete pTriangleData; + } + + /// Most primitives can use these common implementations + const char* getElementName() { return primitive->getElementName(); } + const char* getMaterial() { return primitive->getMaterial(); } + const domInputLocalOffset_Array& getInputs() { return primitive->getInput_array(); } + S32 getStride() const { return stride; } + + /// Each supported primitive needs to implement this method (and convert + /// to triangles if required) + const domListOfUInts *getTriangleData() { return NULL; } +}; + +//----------------------------------------------------------------------------- +// +template<> inline const domListOfUInts *ColladaPrimitive::getTriangleData() +{ + // Return the

integer list directly + return (primitive->getP() ? &(primitive->getP()->getValue()) : NULL); +} + +//----------------------------------------------------------------------------- +// +template<> inline const domListOfUInts *ColladaPrimitive::getTriangleData() +{ + if (!pTriangleData) + { + // Convert polygons to triangles + pTriangleData = new domListOfUInts(); + + for (int iPoly = 0; iPoly < primitive->getCount(); iPoly++) { + + domP* P = primitive->getP_array()[iPoly]; + + // Ignore invalid P arrays + if (!P || !P->getValue().getCount()) + continue; + + domUint* pSrcData = &(P->getValue()[0]); + S32 numPoints = P->getValue().getCount() / stride; + + // Use a simple tri-fan (centered at the first point) method of + // converting the polygon to triangles. + domUint* v0 = pSrcData; + pSrcData += stride; + for (int iTri = 0; iTri < numPoints-2; iTri++) { + pTriangleData->appendArray(stride, v0); + pTriangleData->appendArray(stride*2, pSrcData); + pSrcData += stride; + } + } + } + return pTriangleData; +} + +//----------------------------------------------------------------------------- +// +template<> inline const domListOfUInts *ColladaPrimitive::getTriangleData() +{ + if (!pTriangleData) + { + // Convert polygons to triangles + pTriangleData = new domListOfUInts(); + + // Check that the P element has the right number of values (this + // has been seen with certain models exported using COLLADAMax) + const domListOfUInts& vcount = primitive->getVcount()->getValue(); + + U32 expectedCount = 0; + for (int iPoly = 0; iPoly < vcount.getCount(); iPoly++) + expectedCount += vcount[iPoly]; + expectedCount *= stride; + + if (!primitive->getP() || !primitive->getP()->getValue().getCount() || + (primitive->getP()->getValue().getCount() != expectedCount) ) + { + Con::warnf(" element found with invalid

array. This primitive will be ignored."); + return pTriangleData; + } + + domUint* pSrcData = &(primitive->getP()->getValue()[0]); + for (int iPoly = 0; iPoly < vcount.getCount(); iPoly++) { + + // Use a simple tri-fan (centered at the first point) method of + // converting the polygon to triangles. + domUint* v0 = pSrcData; + pSrcData += stride; + for (int iTri = 0; iTri < vcount[iPoly]-2; iTri++) { + pTriangleData->appendArray(stride, v0); + pTriangleData->appendArray(stride*2, pSrcData); + pSrcData += stride; + } + pSrcData += stride; + } + } + return pTriangleData; +} + +//----------------------------------------------------------------------------- + +/// Convert a custom parameter string to a particular type +template inline T convert(const char* value) { return value; } +template<> inline bool convert(const char* value) { return dAtob(value); } +template<> inline S32 convert(const char* value) { return dAtoi(value); } +template<> inline double convert(const char* value) { return dAtof(value); } +template<> inline F32 convert(const char* value) { return convert(value); } + +//----------------------------------------------------------------------------- +/// Collada animation data +struct AnimChannels : public Vector +{ + daeElement *element; + AnimChannels(daeElement* el) : element(el) + { + element->setUserData(this); + } + ~AnimChannels() + { + if (element) + element->setUserData(0); + } +}; + +struct AnimData +{ + _SourceReader input; + _SourceReader output; + + _SourceReader inTangent; + _SourceReader outTangent; + + _SourceReader interpolation; + + U32 targetValueOffset; ///< Offset into the target element (for arrays of values) + U32 targetValueCount; ///< Number of values animated (from OUTPUT source array) + + /// Get the animation channels for the Collada element (if any) + static AnimChannels* getAnimChannels(const daeElement* element) + { + return element ? (AnimChannels*)const_cast(element)->getUserData() : 0; + } + + void parseTargetString(const char* target, int fullCount, const char* elements[]); + + F32 invertParamCubic(F32 param, F32 x0, F32 x1, F32 x2, F32 x3) const; + void interpValue(F32 t, U32 offset, double* value) const; + void interpValue(F32 t, U32 offset, const char** value) const; +}; + +//----------------------------------------------------------------------------- +// Collada allows any element with an SID or ID attribute to be the target of +// an animation channel, which is very flexible, but awkward to work with. Some +// examples of animated values are: +// - single float +// - single int +// - single bool +// - single string +// - list of floats (transform elements or morph weights) +// +// This class provides a generic way to check if an element is animated, and +// to get the value of the element at a given time. +template +struct AnimatedElement +{ + const daeElement* element; ///< The Collada element (can be NULL) + T defaultVal; ///< Default value (used when element is NULL) + + AnimatedElement(const daeElement* e=0) : element(e) { } + + /// Check if the element has any animations channels + bool isAnimated() + { + return (AnimData::getAnimChannels(element) != 0); + } + + /// Check if the element has any animation channels that overlap the interval + bool isAnimated(F32 start, F32 end) + { + const AnimChannels* channels = AnimData::getAnimChannels(element); + if (channels) { + for (int iChannel = 0; iChannel < channels->size(); iChannel++) { + F32 curveStart = (*channels)[iChannel]->input.getFloatValue(0); + if ((curveStart >= start) && (curveStart <= end)) + return true; + } + } + return false; + } + + /// Get the value of the element at the specified time + T getValue(F32 time) + { + // If the element is NULL, just use the default (handy for profiles which + // may or may not be present in the document) + T value(defaultVal); + if (const domAny* param = daeSafeCast(const_cast(element))) { + // If the element is not animated, just use its current value + value = convert(param->getValue()); + // Animate the value + if (const AnimChannels* channels = AnimData::getAnimChannels(element)) { + for (int iChannel = 0; iChannel < channels->size(); iChannel++) { + const AnimData* animData = (*channels)[iChannel]; + F32 curveStart = animData->input.getFloatValue(0); + F32 curveEnd = animData->input.getFloatValue(animData->input.size()-1); + if ((time >= curveStart) && (time <= curveEnd)) { + animData->interpValue(time, 0, &value); + } + } + } + } + return value; + } +}; + +template struct AnimatedElementList : public AnimatedElement +{ + AnimatedElementList(const daeElement* e=0) : AnimatedElement(e) { } + + // @todo: Disable morph animations for now since they are not supported by T3D + bool isAnimated() { return false; } + bool isAnimated(F32 start, F32 end) { return false; } + + // Get the value of the element list at the specified time + T getValue(F32 time) + { + T vec(this->defaultVal); + if (this->element) { + // Get a copy of the vector + vec = *(T*)const_cast(this->element)->getValuePointer(); + + // Animate the vector + if (const AnimChannels* channels = AnimData::getAnimChannels(this->element)) { + for (int iChannel = 0; iChannel < channels->size(); iChannel++) { + const AnimData* animData = (*channels)[iChannel]; + F32 curveStart = animData->input.getFloatValue(0); + F32 curveEnd = animData->input.getFloatValue(animData->input.size()-1); + if ((time >= curveStart) && (time <= curveEnd)) { + for (int iValue = 0; iValue < animData->targetValueCount; iValue++) + animData->interpValue(time, iValue, &vec[animData->targetValueOffset + iValue]); + } + } + } + } + return vec; + } +}; + +// Strongly typed animated values +typedef AnimatedElement AnimatedFloat; +typedef AnimatedElement AnimatedBool; +typedef AnimatedElement AnimatedInt; +typedef AnimatedElement AnimatedString; +typedef AnimatedElementList AnimatedFloatList; + +#endif // _COLLADA_UTILS_H_ diff --git a/ts/loader/appMaterial.h b/ts/loader/appMaterial.h new file mode 100644 index 0000000..7967b4d --- /dev/null +++ b/ts/loader/appMaterial.h @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _APPMATERIAL_H_ +#define _APPMATERIAL_H_ + +#ifndef _MATERIAL_H_ +#include "materials/matInstance.h" +#endif + +struct AppMaterial +{ + U32 flags; + F32 reflectance; + + AppMaterial() : flags(0), reflectance(1.0f) { } + virtual ~AppMaterial() {} + + virtual String getName() { return "unnamed"; } + virtual U32 getFlags() { return flags; } + virtual F32 getReflectance() { return reflectance; } +}; + +#endif // _APPMATERIAL_H_ diff --git a/ts/loader/appMesh.cpp b/ts/loader/appMesh.cpp new file mode 100644 index 0000000..e56cd09 --- /dev/null +++ b/ts/loader/appMesh.cpp @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "ts/loader/appMesh.h" +#include "ts/loader/tsShapeLoader.h" + +Vector AppMesh::appMaterials; + +AppMesh::AppMesh() + : flags(0), numFrames(0), numMatFrames(0), vertsPerFrame(0) +{ +} + +AppMesh::~AppMesh() +{ + for (int iBone = 0; iBone < bones.size(); iBone++) + delete bones[iBone]; + bones.clear(); +} + +TSMesh* AppMesh::constructTSMesh() +{ + TSMesh* tsmesh; + if (isSkin()) + { + TSSkinMesh* tsskin = new TSSkinMesh(); + tsmesh = tsskin; + + // Copy skin elements + tsskin->weight = weight; + tsskin->boneIndex = boneIndex; + tsskin->vertexIndex = vertexIndex; + tsskin->batchData.nodeIndex = nodeIndex; + tsskin->batchData.initialTransforms = initialTransforms; + tsskin->batchData.initialVerts = initialVerts; + tsskin->batchData.initialNorms = initialNorms; + } + else + { + tsmesh = new TSMesh(); + } + + // Copy mesh elements + tsmesh->verts = points; + tsmesh->norms = normals; + tsmesh->tverts = uvs; + tsmesh->primitives = primitives; + tsmesh->indices = indices; + tsmesh->colors = colors; + tsmesh->tverts2 = uv2s; + + // Finish initializing the shape + tsmesh->setFlags(flags); + tsmesh->computeBounds(); + tsmesh->numFrames = numFrames; + tsmesh->numMatFrames = numMatFrames; + tsmesh->vertsPerFrame = vertsPerFrame; + tsmesh->createTangents(tsmesh->verts, tsmesh->norms); + tsmesh->encodedNorms.set(NULL,0); + + return tsmesh; +} + +bool AppMesh::isBillboard() +{ + return !dStrnicmp(getName(),"BB::",4) || !dStrnicmp(getName(),"BB_",3) || isBillboardZAxis(); +} + +bool AppMesh::isBillboardZAxis() +{ + return !dStrnicmp(getName(),"BBZ::",5) || !dStrnicmp(getName(),"BBZ_",4); +} diff --git a/ts/loader/appMesh.h b/ts/loader/appMesh.h new file mode 100644 index 0000000..7fe7733 --- /dev/null +++ b/ts/loader/appMesh.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _APPMESH_H_ +#define _APPMESH_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _APPMATERIAL_H_ +#include "ts/loader/appMaterial.h" +#endif +#ifndef _APPSEQUENCE_H_ +#include "ts/loader/appSequence.h" +#endif + +class AppNode; + +class AppMesh +{ +public: + // Mesh and skin elements + Vector points; + Vector normals; + Vector uvs; + Vector uv2s; + Vector colors; + Vector primitives; + Vector indices; + + // Skin elements + Vector weight; + Vector boneIndex; + Vector vertexIndex; + Vector nodeIndex; + Vector initialTransforms; + Vector initialVerts; + Vector initialNorms; + + U32 flags; + U32 vertsPerFrame; + S32 numFrames; + S32 numMatFrames; + + // Loader elements (can be discarded after loading) + S32 detailSize; + MatrixF objectOffset; + Vector bones; + static Vector appMaterials; + +public: + AppMesh(); + virtual ~AppMesh(); + + // Create a TSMesh object + TSMesh* constructTSMesh(); + + virtual const char * getName(bool allowFixed=true) = 0; + + virtual MatrixF getMeshTransform(F32 time) = 0; + virtual F32 getVisValue(F32 time) = 0; + + virtual bool getFloat(const char* propName, F32& defaultVal) = 0; + virtual bool getInt(const char* propName, S32& defaultVal) = 0; + virtual bool getBool(const char* propName, bool& defaultVal) = 0; + + virtual bool animatesVis(const AppSequence* appSeq) { return false; } + virtual bool animatesMatFrame(const AppSequence* appSeq) { return false; } + virtual bool animatesFrame(const AppSequence* appSeq) { return false; } + + virtual bool isBillboard(); + virtual bool isBillboardZAxis(); + + virtual bool isSkin() { return false; } + virtual void lookupSkinData() = 0; + + virtual void lockMesh(F32 t, const MatrixF& objectOffset) { } +}; + +#endif // _APPMESH_H_ diff --git a/ts/loader/appNode.cpp b/ts/loader/appNode.cpp new file mode 100644 index 0000000..4e12022 --- /dev/null +++ b/ts/loader/appNode.cpp @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "ts/loader/appNode.h" + +AppNode::AppNode() +{ + mName = NULL; + mParentName = NULL; +} + +AppNode::~AppNode() +{ + dFree( mName ); + dFree( mParentName ); + + // delete children and meshes + for (S32 i = 0; i < mChildNodes.size(); i++) + delete mChildNodes[i]; + for (S32 i = 0; i < mMeshes.size(); i++) + delete mMeshes[i]; +} + +S32 AppNode::getNumMesh() +{ + if (mMeshes.size() == 0) + buildMeshList(); + return mMeshes.size(); +} + +AppMesh* AppNode::getMesh(S32 idx) +{ + return (idx < getNumMesh() && idx >= 0) ? mMeshes[idx] : NULL; +} + +S32 AppNode::getNumChildNodes() +{ + if (mChildNodes.size() == 0) + buildChildList(); + return mChildNodes.size(); +} + +AppNode * AppNode::getChildNode(S32 idx) +{ + return (idx < getNumChildNodes() && idx >= 0) ? mChildNodes[idx] : NULL; +} + +bool AppNode::isBillboard() +{ + return !dStrnicmp(getName(),"BB::",4) || !dStrnicmp(getName(),"BB_",3) || isBillboardZAxis(); +} + +bool AppNode::isBillboardZAxis() +{ + return !dStrnicmp(getName(),"BBZ::",5) || !dStrnicmp(getName(),"BBZ_",4); +} + +bool AppNode::isDummy() +{ + // naming convention should work well enough... + // ...but can override this method if one wants more + return !dStrnicmp(getName(), "dummy", 5); +} + +bool AppNode::isBounds() +{ + // naming convention should work well enough... + // ...but can override this method if one wants more + return !dStricmp(getName(), "bounds"); +} + +bool AppNode::isSequence() +{ + // naming convention should work well enough... + // ...but can override this method if one wants more + return !dStrnicmp(getName(), "Sequence", 8); +} + +bool AppNode::isRoot() +{ + // we assume root node isn't added, so this is never true + // but allow for possibility (by overriding this method) + // so that isParentRoot still works. + return false; +} diff --git a/ts/loader/appNode.h b/ts/loader/appNode.h new file mode 100644 index 0000000..0dfa7c7 --- /dev/null +++ b/ts/loader/appNode.h @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _APPNODE_H_ +#define _APPNODE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _APPMESH_H_ +#include "ts/loader/appMesh.h" +#endif + +class AppNode +{ + friend class TSShapeLoader; + + // add attached meshes and child nodes to app node + // the reason these are tracked by AppNode is that + // AppNode is responsible for deleting all it's children + // and attached meshes. + virtual void buildMeshList() = 0; + virtual void buildChildList() = 0; + +protected: + + S32 mParentIndex; + Vector mMeshes; + Vector mChildNodes; + char* mName; + char* mParentName; + +public: + + AppNode(); + virtual ~AppNode(); + + S32 getNumMesh(); + AppMesh* getMesh(S32 idx); + + S32 getNumChildNodes(); + AppNode* getChildNode(S32 idx); + + virtual MatrixF getNodeTransform(F32 time) = 0; + + virtual bool isEqual(AppNode* node) = 0; + + virtual bool animatesTransform(const AppSequence* appSeq) = 0; + + virtual const char* getName() = 0; + virtual const char* getParentName() = 0; + + virtual bool getFloat(const char* propName, F32& defaultVal) = 0; + virtual bool getInt(const char* propName, S32& defaultVal) = 0; + virtual bool getBool(const char* propName, bool& defaultVal) = 0; + + virtual bool isBillboard(); + virtual bool isBillboardZAxis(); + virtual bool isParentRoot() = 0; + virtual bool isDummy(); + virtual bool isBounds(); + virtual bool isSequence(); + virtual bool isRoot(); +}; + +#endif // _APPNODE_H_ diff --git a/ts/loader/appSequence.h b/ts/loader/appSequence.h new file mode 100644 index 0000000..db98c88 --- /dev/null +++ b/ts/loader/appSequence.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _APPSEQUENCE_H_ +#define _APPSEQUENCE_H_ + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif + +class AppSequence +{ +public: + F32 delta; + +public: + AppSequence() { } + virtual ~AppSequence() { } + + virtual S32 getNumTriggers() const { return 0; } + virtual void getTrigger(S32 index, TSShape::Trigger& trigger) const { trigger.state = 0;} + + virtual const char* getName() const { return "ambient"; } + + virtual F32 getStart() const { return 0.0f; } + virtual F32 getEnd() const { return 0.0f; } + + virtual U32 getFlags() const { return 0; } + virtual F32 getPriority() const { return 5; } + virtual F32 getBlendRefTime() const { return 0.0f; } +}; + +#endif // _APPSEQUENCE_H_ diff --git a/ts/loader/tsShapeLoader.cpp b/ts/loader/tsShapeLoader.cpp new file mode 100644 index 0000000..4d1555a --- /dev/null +++ b/ts/loader/tsShapeLoader.cpp @@ -0,0 +1,1228 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "core/volume.h" +#include "materials/materialList.h" +#include "materials/matInstance.h" +#include "materials/materialManager.h" +#include "ts/tsShapeInstance.h" +#include "ts/loader/tsShapeLoader.h" + +const F32 TSShapeLoader::DefaultTime = 0.0f; +const double TSShapeLoader::AppFrameRate = 30.0f; +const double TSShapeLoader::AppGroundFrameRate = 10.0f; +Torque::Path TSShapeLoader::shapePath; + +//------------------------------------------------------------------------------ +// Utility functions + +static bool isEqualQ16(const QuatF& a, const QuatF& b) +{ + U16 MAX_VAL = 0x7fff; + + // convert components to 16 bit, then test for equality + S16 x, y, z, w; + x = ((S16)(a.x * F32(MAX_VAL))) - ((S16)(b.x * F32(MAX_VAL))); + y = ((S16)(a.y * F32(MAX_VAL))) - ((S16)(b.y * F32(MAX_VAL))); + z = ((S16)(a.z * F32(MAX_VAL))) - ((S16)(b.z * F32(MAX_VAL))); + w = ((S16)(a.w * F32(MAX_VAL))) - ((S16)(b.w * F32(MAX_VAL))); + return (x==0) && (y==0) && (z==0) && (w==0); +} + +static void zapScale(MatrixF& mat) +{ + Point3F invScale = mat.getScale(); + invScale.x = invScale.x ? (1.0f / invScale.x) : 0; + invScale.y = invScale.y ? (1.0f / invScale.y) : 0; + invScale.z = invScale.z ? (1.0f / invScale.z) : 0; + mat.scale(invScale); +} + +//------------------------------------------------------------------------------ +// Shape utility functions + +MatrixF TSShapeLoader::getLocalNodeMatrix(AppNode* node, F32 t) +{ + MatrixF m1 = node->getNodeTransform(t); + + // multiply by inverse scale at t=0 + MatrixF m10 = node->getNodeTransform(DefaultTime); + m1.scale(Point3F(1.0f/m10.getScale().x, 1.0f/m10.getScale().y, 1.0f/m10.getScale().z)); + + if (node->mParentIndex >= 0) + { + AppNode *parent = appNodes[node->mParentIndex]; + + MatrixF m2 = parent->getNodeTransform(t); + + // multiply by inverse scale at t=0 + MatrixF m20 = parent->getNodeTransform(DefaultTime); + m2.scale(Point3F(1.0f/m20.getScale().x, 1.0f/m20.getScale().y, 1.0f/m20.getScale().z)); + + // get local transform by pre-multiplying by inverted parent transform + m1 = m2.inverse() * m1; + } + else + { + // offset the shape by translating nodes at the root level + m1.setPosition(m1.getPosition() + shapeOffset); + } + + return m1; +} + +void TSShapeLoader::generateNodeTransform(AppNode* node, F32 t, bool blend, F32 referenceTime, + QuatF& rot, Point3F& trans, QuatF& srot, Point3F& scale) +{ + MatrixF m1 = getLocalNodeMatrix(node, t); + if (blend) + { + MatrixF m0 = getLocalNodeMatrix(node, referenceTime); + m1 = m0.inverse() * m1; + } + + rot.set(m1); + trans = m1.getPosition(); + //@todo: srot not supported yet + scale = m1.getScale(); +} + +void TSShapeLoader::computeBounds(Box3F& bounds) +{ + // Compute the box that encloses the model geometry + bounds = Box3F::Invalid; + + // Use bounds node geometry if present + if (boundsNode && boundsNode->getNumMesh()) + { + for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++) + { + AppMesh* mesh = boundsNode->getMesh(iMesh); + for (S32 iVert = 0; iVert < mesh->points.size(); iVert++) + bounds.extend(mesh->points[iVert]); + } + } + else + { + // Compute bounds based on all geometry in the model + for (S32 iMesh = 0; iMesh < appMeshes.size(); iMesh++) + { + AppMesh* mesh = appMeshes[iMesh]; + + // TODO: Why am i getting NULL meshes here? + if ( !mesh ) + continue; + + MatrixF transform = mesh->getMeshTransform(DefaultTime); + zapScale(transform); + + for (S32 iVert = 0; iVert < appMeshes[iMesh]->points.size(); iVert++) + { + Point3F p; + transform.mulP(mesh->points[iVert], &p); + bounds.extend(p); + } + } + } +} + +//----------------------------------------------------------------------------- + +void TSShapeLoader::updateProgress(int major, const char* msg, int numMinor, int minor) +{ + // Calculate progress value + F32 progress = (F32)major / NumLoadPhases; + const char *progressMsg = msg; + + if (numMinor) + { + progress += (minor * (1.0f / NumLoadPhases) / numMinor); + progressMsg = avar("%s (%d of %d)", msg, minor + 1, numMinor); + } + + Con::executef("updateTSShapeLoadProgress", Con::getFloatArg(progress), progressMsg); +} + +//----------------------------------------------------------------------------- +// Shape creation entry point + +TSShape* TSShapeLoader::generateShape(const Torque::Path& path) +{ + shapePath = path; + shape = new TSShape(); + + shape->mSmallestVisibleSize = 999999; + shape->mSmallestVisibleDL = 0; + + // Get all nodes, objects and sequences in the shape + updateProgress(Load_EnumerateScene, "Enumerating scene..."); + enumerateScene(); + if (!subshapes.size()) + { + delete shape; + Con::errorf("Failed to load shape \"%s\", no subshapes found", path.getFullPath().c_str()); + return NULL; + } + + // Create the TSShape::Node hierarchy + generateSubshapes(); + + // Create objects (meshes and details) + generateObjects(); + + // Generate initial object states and node transforms + generateDefaultStates(); + + // Generate skins + generateSkins(); + + // Generate material list + generateMaterialList(); + generateIflMaterials(); + + // Generate animation sequences + generateSequences(); + + // Sort detail levels and meshes + updateProgress(Load_InitShape, "Initialising shape..."); + sortDetails(); + + // Install the TS memory helper into a TSShape object. + install(); + + return shape; +} + +bool TSShapeLoader::processNode(AppNode* node) +{ + // Detect bounds node + if ( node->isBounds() ) + { + if ( boundsNode ) + { + Con::warnf( "More than one bounds node found" ); + return false; + } + boundsNode = node; + + // Process bounds geometry + for (S32 iMesh = 0; iMesh < boundsNode->getNumMesh(); iMesh++) + { + AppMesh* mesh = boundsNode->getMesh(iMesh); + MatrixF transform = mesh->getMeshTransform(DefaultTime); + mesh->lockMesh(DefaultTime, transform); + } + return true; + } + + // Detect sequence markers + if ( node->isSequence() ) + { + //appSequences.push_back(new AppSequence(node)); + return false; + } + + // Add this node to the subshape (create one if needed) + if ( subshapes.size() == 0 ) + subshapes.push_back( new TSShapeLoader::Subshape ); + + subshapes.last()->branches.push_back( node ); + + return true; +} + +//----------------------------------------------------------------------------- +// Nodes, meshes and skins + +String TSShapeLoader::getUniqueName(const char* name) +{ + String uname(name); + if (shape->findName(uname) != -1) + { + // Try appending A-Z to the name to make it unique + for (char suffix = 'A'; suffix <= 'Z'; suffix++) + { + if (shape->findName(uname + suffix) == -1) + { + uname += suffix; + break; + } + } + } + return uname; +} + +void TSShapeLoader::recurseSubshape(AppNode* appNode, S32 parentIndex, bool recurseChildren) +{ + // Ignore local bounds nodes + if (appNode->isBounds()) + return; + + // Check if we should collapse this node + S32 myIndex; + if (ignore(appNode->getName())) + { + myIndex = parentIndex; + } + else + { + myIndex = shape->nodes.size(); + + // Create the 3space node + shape->nodes.increment(); + shape->nodes.last().nameIndex = shape->addName(getUniqueName(appNode->getName())); + shape->nodes.last().parentIndex = parentIndex; + shape->nodes.last().firstObject = -1; + shape->nodes.last().firstChild = -1; + shape->nodes.last().nextSibling = -1; + + // Add the AppNode to a matching list (so AppNodes can be accessed using 3space + // node indices) + appNodes.push_back(appNode); + appNodes.last()->mParentIndex = parentIndex; + + // Check for AutoBillboard nodes and add a detail level accordingly + if (appNode->isBillboard()) + { + S32 size = 2; + String dname(String::GetTrailingNumber(appNode->getName(), size)); + if (!dStrEqual(dname, appNode->getName())) + { + // AutoBillboard detail + S32 numEquatorSteps = 4; + S32 numPolarSteps = 0; + F32 polarAngle = 0.0f; + S32 dl = 0; + S32 dim = 64; + bool includePoles = true; + + appNode->getInt("BB::EQUATOR_STEPS", numEquatorSteps); + appNode->getInt("BB::POLAR_STEPS", numPolarSteps); + appNode->getFloat("BB::POLAR_ANGLE", polarAngle); + appNode->getInt("BB::DL", dl); + appNode->getInt("BB::DIM", dim); + appNode->getBool("BB::INCLUDE_POLES", includePoles); + + shape->addBillboardDetail( dname, + size, + numEquatorSteps, + numPolarSteps, + dl, + dim, + includePoles, + polarAngle); + } + } + } + + Subshape* subshape = subshapes[shape->subShapeFirstNode.size()-1]; + + // Collect geometry + for (U32 iMesh = 0; iMesh < appNode->getNumMesh(); iMesh++) + { + AppMesh* mesh = appNode->getMesh(iMesh); + if (!ignore(mesh->getName(false))) + { + subshape->objMeshes.push_back(mesh); + subshape->objNodes.push_back(mesh->isSkin() ? -1 : myIndex); + } + } + + // Create children + if (recurseChildren) + { + for (int iChild = 0; iChild < appNode->getNumChildNodes(); iChild++) + recurseSubshape(appNode->getChildNode(iChild), myIndex, true); + } +} + +void TSShapeLoader::generateSubshapes() +{ + for (U32 iSub = 0; iSub < subshapes.size(); iSub++) + { + updateProgress(Load_GenerateSubshapes, "Generating subshapes...", subshapes.size(), iSub); + + Subshape* subshape = subshapes[iSub]; + + // Recurse through the node hierarchy, adding 3space nodes and + // collecting geometry + S32 firstNode = shape->nodes.size(); + shape->subShapeFirstNode.push_back(firstNode); + + for (U32 iBranch = 0; iBranch < subshape->branches.size(); iBranch++) + recurseSubshape(subshape->branches[iBranch], -1, true); + + shape->subShapeNumNodes.push_back(shape->nodes.size() - firstNode); + } +} + +void TSShapeLoader::generateObjects() +{ + for (S32 iSub = 0; iSub < subshapes.size(); iSub++) + { + Subshape* subshape = subshapes[iSub]; + shape->subShapeFirstObject.push_back(shape->objects.size()); + + // Get the names and sizes of the meshes for this subshape + Vector meshNames; + for (int iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++) + { + AppMesh* mesh = subshape->objMeshes[iMesh]; + mesh->detailSize = 2; + meshNames.push_back( String::GetTrailingNumber( mesh->getName(), mesh->detailSize) ); + + // Make sure collision meshes use negative detail sizes + if (dStrStartsWith(meshNames[iMesh], "Collision") || + dStrStartsWith(meshNames[iMesh], "LOSCol")) + { + if (mesh->detailSize > 0) + mesh->detailSize = -mesh->detailSize; + } + } + + // An 'object' is a collection of meshes with the same base name and + // different detail sizes. The object is attached to the node of the + // highest detail mesh. + + // Sort the 3 arrays (objMeshes, objNodes, meshNames) by name and size + for (S32 i = 0; i < subshape->objMeshes.size()-1; i++) + { + for (S32 j = i+1; j < subshape->objMeshes.size(); j++) + { + if ((meshNames[i].compare(meshNames[j]) < 0) || + (subshape->objMeshes[i]->detailSize < subshape->objMeshes[j]->detailSize)) + { + { + AppMesh* tmp = subshape->objMeshes[i]; + subshape->objMeshes[i] = subshape->objMeshes[j]; + subshape->objMeshes[j] = tmp; + } + { + S32 tmp = subshape->objNodes[i]; + subshape->objNodes[i] = subshape->objNodes[j]; + subshape->objNodes[j] = tmp; + } + { + String tmp = meshNames[i]; + meshNames[i] = meshNames[j]; + meshNames[j] = tmp; + } + } + } + } + + // Now create objects + const String* lastName = 0; + for (S32 iMesh = 0; iMesh < subshape->objMeshes.size(); iMesh++) + { + AppMesh* mesh = subshape->objMeshes[iMesh]; + + if (!lastName || (meshNames[iMesh] != *lastName)) + { + shape->objects.increment(); + shape->objects.last().nameIndex = shape->addName(meshNames[iMesh]); + shape->objects.last().nodeIndex = subshape->objNodes[iMesh]; + shape->objects.last().startMeshIndex = appMeshes.size(); + shape->objects.last().numMeshes = 0; + lastName = &meshNames[iMesh]; + } + + // Add this mesh to the object + appMeshes.push_back(mesh); + shape->objects.last().numMeshes++; + + // Set mesh flags + mesh->flags = 0; + if (mesh->isBillboard()) + { + mesh->flags |= TSMesh::Billboard; + if (mesh->isBillboardZAxis()) + mesh->flags |= TSMesh::BillboardZAxis; + } + + // Check that a detail exists for this mesh + const char* detailName = "detail"; + if ( dStrStartsWith(meshNames[iMesh], "Collision") || + dStrStartsWith(meshNames[iMesh], "Col") ) + detailName = "Collision"; + else if (dStrStartsWith(meshNames[iMesh], "LOSCol")) + detailName = "LOS"; + + // Attempt to add the detail (will fail if it already exists) + S32 oldNumDetails = shape->details.size(); + shape->addDetail(detailName, mesh->detailSize, iSub); + if (shape->details.size() > oldNumDetails) + { + Con::warnf("Object mesh \"%s\" has no matching detail (\"%s%d\" has" + " been added automatically)", mesh->getName(false), detailName, mesh->detailSize); + } + } + + // Get object count for this subshape + shape->subShapeNumObjects.push_back(shape->objects.size() - shape->subShapeFirstObject.last()); + } +} + +void TSShapeLoader::generateSkins() +{ + Vector skins; + for (int iObject = 0; iObject < shape->objects.size(); iObject++) + { + for (int iMesh = 0; iMesh < shape->objects[iObject].numMeshes; iMesh++) + { + AppMesh* mesh = appMeshes[shape->objects[iObject].startMeshIndex + iMesh]; + if (mesh->isSkin()) + skins.push_back(mesh); + } + } + + for (int iSkin = 0; iSkin < skins.size(); iSkin++) + { + updateProgress(Load_GenerateSkins, "Generating skins...", skins.size(), iSkin); + + // Get skin data (bones, vertex weights etc) + AppMesh* skin = skins[iSkin]; + skin->lookupSkinData(); + + // Just copy initial verts and norms for now + skin->initialVerts.set(skin->points.address(), skin->vertsPerFrame); + skin->initialNorms.set(skin->normals.address(), skin->vertsPerFrame); + + // Map bones to nodes + skin->nodeIndex.setSize(skin->bones.size()); + for (int iBone = 0; iBone < skin->bones.size(); iBone++) + { + // Find the node that matches this bone + skin->nodeIndex[iBone] = -1; + for (int iNode = 0; iNode < appNodes.size(); iNode++) + { + if (appNodes[iNode]->isEqual(skin->bones[iBone])) + { + skin->nodeIndex[iBone] = iNode; + break; + } + } + + if (skin->nodeIndex[iBone] == -1) + { + Con::warnf("Could not find bone %d. Defaulting to first node", iBone); + skin->nodeIndex[iBone] = 0; + } + } + } +} + +void TSShapeLoader::generateDefaultStates() +{ + // Generate default object states (includes initial geometry) + for (int iObject = 0; iObject < shape->objects.size(); iObject++) + { + updateProgress(Load_GenerateDefaultStates, "Generating initial mesh and node states...", + shape->objects.size(), iObject); + + TSShape::Object& obj = shape->objects[iObject]; + + // Calculate the objectOffset for each mesh at T=0 + for (int iMesh = 0; iMesh < obj.numMeshes; iMesh++) + { + AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh]; + AppNode* appNode = obj.nodeIndex >= 0 ? appNodes[obj.nodeIndex] : boundsNode; + + MatrixF meshMat(appMesh->getMeshTransform(DefaultTime)); + MatrixF nodeMat(appMesh->isSkin() ? meshMat : appNode->getNodeTransform(DefaultTime)); + + zapScale(nodeMat); + + appMesh->objectOffset = nodeMat.inverse() * meshMat; + } + + generateObjectState(shape->objects[iObject], DefaultTime, true, true); + } + + // Allow shape to be offset (need to wait until all geometry has been added) + computeShapeOffset(); + + // Generate default node transforms + for (int iNode = 0; iNode < appNodes.size(); iNode++) + { + // Determine the default translation and rotation for the node + QuatF rot, srot; + Point3F trans, scale; + generateNodeTransform(appNodes[iNode], DefaultTime, false, 0, rot, trans, srot, scale); + + // Add default node translation and rotation + addNodeRotation(rot, true); + addNodeTranslation(trans, true); + } +} + +void TSShapeLoader::generateObjectState(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame) +{ + shape->objectStates.increment(); + TSShape::ObjectState& state = shape->objectStates.last(); + + state.frameIndex = 0; + state.matFrameIndex = 0; + state.vis = mClampF(appMeshes[obj.startMeshIndex]->getVisValue(t), 0.0f, 1.0f); + + if (addFrame || addMatFrame) + { + generateFrame(obj, t, addFrame, addMatFrame); + + // set the frame number for the object state + state.frameIndex = appMeshes[obj.startMeshIndex]->numFrames - 1; + state.matFrameIndex = appMeshes[obj.startMeshIndex]->numMatFrames - 1; + } +} + +void TSShapeLoader::generateFrame(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame) +{ + for (int iMesh = 0; iMesh < obj.numMeshes; iMesh++) + { + AppMesh* appMesh = appMeshes[obj.startMeshIndex + iMesh]; + + U32 oldNumPoints = appMesh->points.size(); + U32 oldNumUvs = appMesh->uvs.size(); + + // Get the mesh geometry at time, 't' + // Geometry verts, normals and tverts can be animated (different set for + // each frame), but the TSDrawPrimitives stay the same, so the way lockMesh + // works is that it will only generate the primitives once, then after that + // will just append verts, normals and tverts each time it is called. + appMesh->lockMesh(t, appMesh->objectOffset); + + // If this is the first call, set the number of points per frame + if (appMesh->numFrames == 0) + { + appMesh->vertsPerFrame = appMesh->points.size(); + } + else + { + // Check frame topology => ie. that the right number of points, normals + // and tverts was added + if ((appMesh->points.size() - oldNumPoints) != appMesh->vertsPerFrame) + { + Con::warnf("Wrong number of points (%d) added at time=%f (expected %d)", + appMesh->points.size() - oldNumPoints, t, appMesh->vertsPerFrame); + addFrame = false; + } + if ((appMesh->normals.size() - oldNumPoints) != appMesh->vertsPerFrame) + { + Con::warnf("Wrong number of normals (%d) added at time=%f (expected %d)", + appMesh->normals.size() - oldNumPoints, t, appMesh->vertsPerFrame); + addFrame = false; + } + if ((appMesh->uvs.size() - oldNumUvs) != appMesh->vertsPerFrame) + { + Con::warnf("Wrong number of tverts (%d) added at time=%f (expected %d)", + appMesh->uvs.size() - oldNumUvs, t, appMesh->vertsPerFrame); + addMatFrame = false; + } + } + + // Because lockMesh adds points, normals AND tverts each call, if we didn't + // actually want another frame or matFrame, we need to remove them afterwards. + // In the common case (we DO want the frame), we can do nothing => the + // points/normals/tverts are already in place! + if (addFrame) + { + appMesh->numFrames++; + } + else + { + appMesh->points.setSize(oldNumPoints); + appMesh->normals.setSize(oldNumPoints); + } + + if (addMatFrame) + { + appMesh->numMatFrames++; + } + else + { + appMesh->uvs.setSize(oldNumPoints); + } + } +} + +//----------------------------------------------------------------------------- +// Materials + +/// Convert all Collada materials into a single TSMaterialList +void TSShapeLoader::generateMaterialList() +{ + // Install the materials into the material list + shape->materialList = new TSMaterialList; + for (int iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++) + { + updateProgress(Load_GenerateMaterials, "Generating materials...", AppMesh::appMaterials.size(), iMat); + + AppMaterial* appMat = AppMesh::appMaterials[iMat]; + shape->materialList->push_back(appMat->getName(), appMat->getFlags(), U32(-1), U32(-1), U32(-1), 1.0f, appMat->getReflectance()); + + // Create corresponding IFL if needed + if (appMat->getFlags() & TSMaterialList::IflMaterial) + { + shape->iflMaterials.increment(); + shape->iflMaterials.last().materialSlot = iMat; + shape->iflMaterials.last().nameIndex = shape->addName(appMat->getName()); + } + } +} + +void TSShapeLoader::generateIflMaterials() +{ + // By default, do nothing => we'll assume the IFL file already exists. If not, + // it's up to the app loader class to generate it. +} + +//----------------------------------------------------------------------------- +// Animation Sequences + +void TSShapeLoader::generateSequences() +{ + for (int iSeq = 0; iSeq < appSequences.size(); iSeq++) + { + updateProgress(Load_GenerateSequences, "Generating sequences...", appSequences.size(), iSeq); + + // Initialize the sequence + shape->sequences.increment(); + TSShape::Sequence& seq = shape->sequences.last(); + + seq.nameIndex = shape->addName(appSequences[iSeq]->getName()); + seq.toolBegin = appSequences[iSeq]->getStart(); + seq.duration = appSequences[iSeq]->getEnd() - appSequences[iSeq]->getStart(); + seq.numKeyframes = seq.duration * AppFrameRate + 0.5f; + seq.priority = appSequences[iSeq]->getPriority(); + seq.flags = appSequences[iSeq]->getFlags(); + + appSequences[iSeq]->delta = seq.duration / seq.numKeyframes; + + // Set membership arrays (ie. which nodes and objects are affected by this sequence) + setNodeMembership(seq, appSequences[iSeq]); + setObjectMembership(seq, appSequences[iSeq]); + setIflMembership(seq, appSequences[iSeq]); + + // Generate keyframes + generateNodeAnimation(seq); + generateObjectAnimation(seq, appSequences[iSeq]); + generateGroundAnimation(seq, appSequences[iSeq]); + generateFrameTriggers(seq, appSequences[iSeq]); + + // Set sequence flags + seq.dirtyFlags = 0; + if (seq.rotationMatters.testAll() || seq.translationMatters.testAll() || seq.scaleMatters.testAll()) + seq.dirtyFlags |= TSShapeInstance::TransformDirty; + if (seq.visMatters.testAll()) + seq.dirtyFlags |= TSShapeInstance::VisDirty; + if (seq.frameMatters.testAll()) + seq.dirtyFlags |= TSShapeInstance::FrameDirty; + if (seq.matFrameMatters.testAll()) + seq.dirtyFlags |= TSShapeInstance::MatFrameDirty; + if (seq.iflMatters.testAll()) + seq.dirtyFlags |= TSShapeInstance::IflDirty; + } +} + +void TSShapeLoader::setNodeMembership(TSShape::Sequence& seq, const AppSequence* appSeq) +{ + seq.rotationMatters.clearAll(); // node rotation (size = nodes.size()) + seq.translationMatters.clearAll(); // node translation (size = nodes.size()) + seq.scaleMatters.clearAll(); // node scale (size = nodes.size()) + + // This shouldn't be allowed, but check anyway... + if (seq.numKeyframes < 2) + return; + + // Note: this fills the cache with current sequence data. Methods that get + // called later (e.g. generateNodeAnimation) use this info (and assume it's set). + fillNodeTransformCache(seq, appSeq); + + // Test to see if the transform changes over the interval in order to decide + // whether to animate the transform in 3space. We don't use app's mechanism + // for doing this because it functions different in different apps and we do + // some special stuff with scale. + setRotationMembership(seq); + setTranslationMembership(seq); + setScaleMembership(seq); +} + +void TSShapeLoader::setRotationMembership(TSShape::Sequence& seq) +{ + for (int iNode = 0; iNode < appNodes.size(); iNode++) + { + // Check if any of the node rotations are different to + // the default rotation + QuatF defaultRot; + shape->defaultRotations[iNode].getQuatF(&defaultRot); + + for (int iFrame = 0; iFrame < seq.numKeyframes; iFrame++) + { + if (!isEqualQ16(nodeRotCache[iNode][iFrame], defaultRot)) + { + seq.rotationMatters.set(iNode); + break; + } + } + } +} + +void TSShapeLoader::setTranslationMembership(TSShape::Sequence& seq) +{ + for (int iNode = 0; iNode < appNodes.size(); iNode++) + { + // Check if any of the node translations are different to + // the default translation + Point3F& defaultTrans = shape->defaultTranslations[iNode]; + + for (int iFrame = 0; iFrame < seq.numKeyframes; iFrame++) + { + if (!nodeTransCache[iNode][iFrame].equal(defaultTrans)) + { + seq.translationMatters.set(iNode); + break; + } + } + } +} + +void TSShapeLoader::setScaleMembership(TSShape::Sequence& seq) +{ + Point3F unitScale(1,1,1); + QuatF unitRot(0,0,0,1); + + U32 arbitraryScaleCount = 0; + U32 alignedScaleCount = 0; + U32 uniformScaleCount = 0; + + for (int iNode = 0; iNode < appNodes.size(); iNode++) + { + // Check if any of the node scales are not the unit scale + for (int iFrame = 0; iFrame < seq.numKeyframes; iFrame++) + { + Point3F& scale = nodeScaleCache[iNode][iFrame]; + if (!unitScale.equal(scale)) + { + // Determine what type of scale this is + if (!isEqualQ16(unitRot, nodeScaleRotCache[iNode][iFrame])) + arbitraryScaleCount++; + else if (scale.x != scale.y || scale.y != scale.z) + alignedScaleCount++; + else + uniformScaleCount++; + + seq.scaleMatters.set(iNode); + break; + } + } + } + + // Only one type of scale is animated + if (arbitraryScaleCount) + seq.flags |= TSShape::ArbitraryScale; + else if (alignedScaleCount) + seq.flags |= TSShape::AlignedScale; + else if (uniformScaleCount) + seq.flags |= TSShape::UniformScale; +} + +void TSShapeLoader::setObjectMembership(TSShape::Sequence& seq, const AppSequence* appSeq) +{ + seq.visMatters.clearAll(); // object visibility (size = objects.size()) + seq.frameMatters.clearAll(); // vert animation (morph) (size = objects.size()) + seq.matFrameMatters.clearAll(); // UV animation (size = objects.size()) + + for (int iObject = 0; iObject < shape->objects.size(); iObject++) + { + if (!appMeshes[shape->objects[iObject].startMeshIndex]) + continue; + + if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesVis(appSeq)) + seq.visMatters.set(iObject); + if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesFrame(appSeq)) + seq.frameMatters.set(iObject); + if (appMeshes[shape->objects[iObject].startMeshIndex]->animatesMatFrame(appSeq)) + seq.matFrameMatters.set(iObject); + } +} + +void TSShapeLoader::setIflMembership(TSShape::Sequence& seq, const AppSequence* appSeq) +{ + seq.iflMatters.clearAll(); // IFL animation (size = iflMaterials.size()) + + for (int iIfl = 0; iIfl < shape->iflMaterials.size(); iIfl++) + { + const TSShape::IflMaterial& iflMat = shape->iflMaterials[iIfl]; + + // does ifl change materials during our range? + const char* firstName = shape->materialList->getMaterialName(iflMat.firstFrame); + for (int iFrame = 1; iFrame < iflMat.numFrames; iFrame++) + { + F32 time = shape->iflFrameOffTimes[iflMat.firstFrameOffTimeIndex + iFrame]; + if (time > appSeq->getEnd()) + break; + + const char* name = shape->materialList->getMaterialName(iflMat.firstFrame + iFrame); + if ((time >= appSeq->getStart()) && dStrcmp(name, firstName)) + { + seq.iflMatters.set(iIfl); + break; + } + } + } +} + +void TSShapeLoader::clearNodeTransformCache() +{ + // clear out the transform caches + for (int i = 0; i < nodeRotCache.size(); i++) + delete [] nodeRotCache[i]; + nodeRotCache.clear(); + for (int i = 0; i < nodeTransCache.size(); i++) + delete [] nodeTransCache[i]; + nodeTransCache.clear(); + for (int i = 0; i < nodeScaleRotCache.size(); i++) + delete [] nodeScaleRotCache[i]; + nodeScaleRotCache.clear(); + for (int i = 0; i < nodeScaleCache.size(); i++) + delete [] nodeScaleCache[i]; + nodeScaleCache.clear(); +} + +void TSShapeLoader::fillNodeTransformCache(TSShape::Sequence& seq, const AppSequence* appSeq) +{ + // clear out the transform caches and set it up for this sequence + clearNodeTransformCache(); + + nodeRotCache.setSize(appNodes.size()); + for (int i = 0; i < nodeRotCache.size(); i++) + nodeRotCache[i] = new QuatF[seq.numKeyframes]; + nodeTransCache.setSize(appNodes.size()); + for (int i = 0; i < nodeTransCache.size(); i++) + nodeTransCache[i] = new Point3F[seq.numKeyframes]; + nodeScaleRotCache.setSize(appNodes.size()); + for (int i = 0; i < nodeScaleRotCache.size(); i++) + nodeScaleRotCache[i] = new QuatF[seq.numKeyframes]; + nodeScaleCache.setSize(appNodes.size()); + for (int i = 0; i < nodeScaleCache.size(); i++) + nodeScaleCache[i] = new Point3F[seq.numKeyframes]; + + // get the node transforms for every frame + F32 time = appSeq->getStart(); + for (int iFrame = 0; iFrame < seq.numKeyframes; iFrame++, time += appSeq->delta) + { + for (int iNode = 0; iNode < appNodes.size(); iNode++) + { + generateNodeTransform(appNodes[iNode], time, seq.isBlend(), appSeq->getBlendRefTime(), + nodeRotCache[iNode][iFrame], nodeTransCache[iNode][iFrame], + nodeScaleRotCache[iNode][iFrame], nodeScaleCache[iNode][iFrame]); + } + } +} + +void TSShapeLoader::addNodeRotation(QuatF& rot, bool defaultVal) +{ + Quat16 rot16; + rot16.set(rot); + + if (!defaultVal) + shape->nodeRotations.push_back(rot16); + else + shape->defaultRotations.push_back(rot16); +} + +void TSShapeLoader::addNodeTranslation(Point3F& trans, bool defaultVal) +{ + if (!defaultVal) + shape->nodeTranslations.push_back(trans); + else + shape->defaultTranslations.push_back(trans); +} + +void TSShapeLoader::addNodeUniformScale(F32 scale) +{ + shape->nodeUniformScales.push_back(scale); +} + +void TSShapeLoader::addNodeAlignedScale(Point3F& scale) +{ + shape->nodeAlignedScales.push_back(scale); +} + +void TSShapeLoader::addNodeArbitraryScale(QuatF& qrot, Point3F& scale) +{ + Quat16 rot16; + rot16.set(qrot); + shape->nodeArbitraryScaleRots.push_back(rot16); + shape->nodeArbitraryScaleFactors.push_back(scale); +} + +void TSShapeLoader::generateNodeAnimation(TSShape::Sequence& seq) +{ + seq.baseRotation = shape->nodeRotations.size(); + seq.baseTranslation = shape->nodeTranslations.size(); + seq.baseScale = (seq.flags & TSShape::ArbitraryScale) ? shape->nodeArbitraryScaleRots.size() : + (seq.flags & TSShape::AlignedScale) ? shape->nodeAlignedScales.size() : + shape->nodeUniformScales.size(); + + for (int iNode = 0; iNode < appNodes.size(); iNode++) + { + for (int iFrame = 0; iFrame < seq.numKeyframes; iFrame++) + { + if (seq.rotationMatters.test(iNode)) + addNodeRotation(nodeRotCache[iNode][iFrame], false); + if (seq.translationMatters.test(iNode)) + addNodeTranslation(nodeTransCache[iNode][iFrame], false); + if (seq.scaleMatters.test(iNode)) + { + QuatF& rot = nodeScaleRotCache[iNode][iFrame]; + Point3F scale = nodeScaleCache[iNode][iFrame]; + + if (seq.flags & TSShape::ArbitraryScale) + addNodeArbitraryScale(rot, scale); + else if (seq.flags & TSShape::AlignedScale) + addNodeAlignedScale(scale); + else if (seq.flags & TSShape::UniformScale) + addNodeUniformScale((scale.x+scale.y+scale.z)/3.0f); + } + } + } +} + +void TSShapeLoader::generateObjectAnimation(TSShape::Sequence& seq, const AppSequence* appSeq) +{ + seq.baseObjectState = shape->objectStates.size(); + + for (int iObject = 0; iObject < shape->objects.size(); iObject++) + { + bool visMatters = seq.visMatters.test(iObject); + bool frameMatters = seq.frameMatters.test(iObject); + bool matFrameMatters = seq.matFrameMatters.test(iObject); + + if (visMatters || frameMatters || matFrameMatters) + { + F32 time = appSeq->getStart(); + for (int iFrame = 0; iFrame < seq.numKeyframes; iFrame++, time += appSeq->delta) + generateObjectState(shape->objects[iObject], time, frameMatters, matFrameMatters); + } + } +} + +void TSShapeLoader::generateGroundAnimation(TSShape::Sequence& seq, const AppSequence* appSeq) +{ + seq.firstGroundFrame = shape->groundTranslations.size(); + seq.numGroundFrames = 0; + + // Check if the bounds node is animated by this sequence + if (!boundsNode || !boundsNode->animatesTransform(appSeq)) + return; + + seq.flags |= TSShape::MakePath; + seq.numGroundFrames = (S32)((seq.duration + 0.25f/AppGroundFrameRate) * AppGroundFrameRate); + + F32 time = appSeq->getStart(); + F32 delta = seq.duration / seq.numGroundFrames; + + // Get ground transform at the start of the sequence + MatrixF invStartMat = boundsNode->getNodeTransform(time); + zapScale(invStartMat); + invStartMat.inverse(); + + for (int iFrame = 0; iFrame < seq.numGroundFrames; iFrame++, time += delta) + { + // Determine delta bounds node transform at 't' + MatrixF mat = boundsNode->getNodeTransform(time); + zapScale(mat); + mat = invStartMat * mat; + + // Add ground transform + Quat16 rotation; + rotation.set(QuatF(mat)); + shape->groundTranslations.push_back(mat.getPosition()); + shape->groundRotations.push_back(rotation); + } +} + +void TSShapeLoader::generateFrameTriggers(TSShape::Sequence& seq, const AppSequence* appSeq) +{ + // Initialize triggers + seq.firstTrigger = shape->triggers.size(); + seq.numTriggers = appSeq->getNumTriggers(); + if (!seq.numTriggers) + return; + + // Add triggers + for (int iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++) + { + shape->triggers.increment(); + appSeq->getTrigger(iTrigger, shape->triggers.last()); + } + + // Track the triggers that get turned off by this shape...normally, triggers + // aren't turned on/off, just on...if we are a trigger that does both then we + // need to mark ourselves as such so that on/off can become off/on when sequence + // is played in reverse... + U32 offTriggers = 0; + for (int iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++) + { + U32 state = shape->triggers[seq.firstTrigger+iTrigger].state; + if ((state & TSShape::Trigger::StateOn) == 0) + offTriggers |= (state & TSShape::Trigger::StateMask); + } + + // We now know which states are turned off, set invert on all those (including when turned on) + for (int iTrigger = 0; iTrigger < seq.numTriggers; iTrigger++) + { + if (shape->triggers[seq.firstTrigger + iTrigger].state & offTriggers) + shape->triggers[seq.firstTrigger + iTrigger].state |= TSShape::Trigger::InvertOnReverse; + } +} + +//----------------------------------------------------------------------------- + +void TSShapeLoader::sortDetails() +{ + // Sort objects by: transparency, material index and node index + + + // Insert NULL meshes where required + for (int iSub = 0; iSub < subshapes.size(); iSub++) + { + Vector validDetails; + shape->getSubShapeDetails(iSub, validDetails); + + for (int iDet = 0; iDet < validDetails.size(); iDet++) + { + TSShape::Detail &detail = shape->details[validDetails[iDet]]; + if (detail.subShapeNum >= 0) + detail.objectDetailNum = iDet; + + for (int iObj = shape->subShapeFirstObject[iSub]; + iObj < (shape->subShapeFirstObject[iSub] + shape->subShapeNumObjects[iSub]); + iObj++) + { + TSShape::Object &object = shape->objects[iObj]; + + // Insert a NULL mesh for this detail level if required (ie. if the + // object does not already have a mesh with an equal or higher detail) + S32 meshIndex = (iDet < object.numMeshes) ? iDet : object.numMeshes-1; + + if (appMeshes[object.startMeshIndex + meshIndex]->detailSize < shape->details[iDet].size) + { + // Add a NULL mesh + appMeshes.insert(object.startMeshIndex + iDet, NULL); + object.numMeshes++; + + // Fixup the start index for the other objects + for (int k = iObj+1; k < shape->objects.size(); k++) + shape->objects[k].startMeshIndex++; + } + } + } + } +} + +// Install into the TSShape, the shape is expected to be empty. +// Data is not copied, the TSShape is modified to point to memory +// managed by this object. This object is also bound to the TSShape +// object and will be deleted when it's deleted. +void TSShapeLoader::install() +{ + // Arrays that are filled in by ts shape init, but need + // to be allocated beforehand. + shape->subShapeFirstTranslucentObject.setSize(shape->subShapeFirstObject.size()); + + // Construct TS sub-meshes + shape->meshes.setSize(appMeshes.size()); + for (U32 m = 0; m < appMeshes.size(); m++) + shape->meshes[m] = appMeshes[m] ? appMeshes[m]->constructTSMesh() : NULL; + + // Remove empty meshes and objects + for (S32 iObj = shape->objects.size()-1; iObj >= 0; iObj--) + { + TSShape::Object& obj = shape->objects[iObj]; + for (S32 iMesh = obj.startMeshIndex + obj.numMeshes-1; iMesh >= obj.startMeshIndex; iMesh--) + { + TSMesh *mesh = shape->meshes[iMesh]; + + if (mesh && !mesh->primitives.size()) + { + destructInPlace(mesh); + shape->removeMeshFromObject(iObj, iMesh - obj.startMeshIndex); + } + } + + if (!obj.numMeshes) + shape->removeObject(shape->getName(obj.nameIndex)); + } + + // Add a dummy object if needed so the shape loads and renders ok + if (!shape->details.size()) + { + shape->addDetail("detail", 2, 0); + shape->subShapeNumObjects.last() = 1; + + shape->meshes.push_back(NULL); + + shape->objects.increment(); + shape->objects.last().nameIndex = shape->addName("dummy"); + shape->objects.last().nodeIndex = 0; + shape->objects.last().startMeshIndex = 0; + shape->objects.last().numMeshes = 1; + + shape->objectStates.increment(); + shape->objectStates.last().frameIndex = 0; + shape->objectStates.last().matFrameIndex = 0; + shape->objectStates.last().vis = 1.0f; + } + + // Update smallest visible detail + shape->mSmallestVisibleDL = -1; + shape->mSmallestVisibleSize = 999999; + for (S32 i = 0; i < shape->details.size(); i++) + { + if ((shape->details[i].size >= 0) && + (shape->details[i].size < shape->mSmallestVisibleSize)) + { + shape->mSmallestVisibleDL = i; + shape->mSmallestVisibleSize = shape->details[i].size; + } + } + + computeBounds(shape->bounds); + if (shape->bounds.isValidBox()) + { + shape->bounds.minExtents += shapeOffset; + shape->bounds.maxExtents += shapeOffset; + } + else + shape->bounds = Box3F(1.0f); + + shape->bounds.getCenter(&shape->center); + shape->radius = (shape->bounds.maxExtents - shape->center).len(); + shape->tubeRadius = shape->radius; + + shape->init(); +} + +TSShapeLoader::~TSShapeLoader() +{ + clearNodeTransformCache(); + + // Clear shared AppMaterial list + for (int iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++) + delete AppMesh::appMaterials[iMat]; + AppMesh::appMaterials.clear(); + + // Delete Subshapes + delete boundsNode; + for (int iSub = 0; iSub < subshapes.size(); iSub++) + delete subshapes[iSub]; + + // Delete AppSequences + for (int iSeq = 0; iSeq < appSequences.size(); iSeq++) + delete appSequences[iSeq]; + appSequences.clear(); +} diff --git a/ts/loader/tsShapeLoader.h b/ts/loader/tsShapeLoader.h new file mode 100644 index 0000000..9460e00 --- /dev/null +++ b/ts/loader/tsShapeLoader.h @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// TS Shape Loader +// Copyright (C) 2008 GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#ifndef _TSSHAPE_LOADER_H_ +#define _TSSHAPE_LOADER_H_ + +#ifndef _MMATH_H_ +#include "math/mMath.h" +#endif +#ifndef _TVECTOR_H_ +#include "core/util/tVector.h" +#endif +#ifndef _TSSHAPE_H_ +#include "ts/tsShape.h" +#endif +#ifndef _APPNODE_H_ +#include "ts/loader/appNode.h" +#endif +#ifndef _APPMESH_H_ +#include "ts/loader/appMesh.h" +#endif +#ifndef _APPSEQUENCE_H_ +#include "ts/loader/appSequence.h" +#endif + +class TSShapeLoader +{ + +public: + enum eLoadPhases + { + Load_ReadFile = 0, + Load_ParseFile, + Load_EnumerateScene, + Load_GenerateSubshapes, + Load_GenerateObjects, + Load_GenerateDefaultStates, + Load_GenerateSkins, + Load_GenerateMaterials, + Load_GenerateSequences, + Load_InitShape, + NumLoadPhases, + Load_Complete = NumLoadPhases + }; + + static void updateProgress(int major, const char* msg, int numMinor=0, int minor=0); + +protected: + struct Subshape + { + Vector branches; ///< Shape branches + Vector objMeshes; ///< Object meshes for this subshape + Vector objNodes; ///< AppNode indices with objects attached + + ~Subshape() + { + // Delete children + for (S32 i = 0; i < branches.size(); i++) + delete branches[i]; + } + }; + +public: + static const F32 DefaultTime; + static const double AppFrameRate; + static const double AppGroundFrameRate; + +protected: + // Variables used during loading that must be held until the shape is deleted + TSShape* shape; + Vector appMeshes; + + // Variables used during loading, but that can be discarded afterwards + static Torque::Path shapePath; + + AppNode* boundsNode; + Vector appNodes; ///< Nodes in the loaded shape + Vector appSequences; + + Vector subshapes; + + Vector nodeRotCache; + Vector nodeTransCache; + Vector nodeScaleRotCache; + Vector nodeScaleCache; + + Point3F shapeOffset; ///< Offset used to translate the shape origin + + //-------------------------------------------------------------------------- + + // Collect the nodes, objects and sequences for the scene + virtual void enumerateScene() = 0; + bool processNode(AppNode* node); + virtual bool ignore(const String& name) { return false; } + + void addSkin(AppMesh* mesh); + void addDetailMesh(AppMesh* mesh); + void addSubshape(AppNode* node); + void addObject(AppMesh* mesh, S32 nodeIndex, S32 subShapeNum); + + String getUniqueName(const char* name); + + // Node transform methods + MatrixF getLocalNodeMatrix(AppNode* node, F32 t); + void generateNodeTransform(AppNode* node, F32 t, bool blend, F32 referenceTime, + QuatF& rot, Point3F& trans, QuatF& srot, Point3F& scale); + + void computeBounds(Box3F& bounds); + virtual void computeShapeOffset() { shapeOffset = Point3F::Zero; } + + // Create objects, materials and sequences + void recurseSubshape(AppNode* appNode, S32 parentIndex, bool recurseChildren); + + void generateSubshapes(); + void generateObjects(); + void generateSkins(); + void generateDefaultStates(); + void generateObjectState(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame); + void generateFrame(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame); + + virtual void generateIflMaterials(); + void generateMaterialList(); + + void generateSequences(); + + // Determine what is actually animated in the sequence + void setNodeMembership(TSShape::Sequence& seq, const AppSequence* appSeq); + void setRotationMembership(TSShape::Sequence& seq); + void setTranslationMembership(TSShape::Sequence& seq); + void setScaleMembership(TSShape::Sequence& seq); + void setObjectMembership(TSShape::Sequence& seq, const AppSequence* appSeq); + void setIflMembership(TSShape::Sequence& seq, const AppSequence* appSeq); + + // Manage a cache of all node transform elements for the sequence + void clearNodeTransformCache(); + void fillNodeTransformCache(TSShape::Sequence& seq, const AppSequence* appSeq); + + // Add node transform elements + void addNodeRotation(QuatF& rot, bool defaultVal); + void addNodeTranslation(Point3F& trans, bool defaultVal); + void addNodeUniformScale(F32 scale); + void addNodeAlignedScale(Point3F& scale); + void addNodeArbitraryScale(QuatF& qrot, Point3F& scale); + + // Generate animation data + void generateNodeAnimation(TSShape::Sequence& seq); + void generateObjectAnimation(TSShape::Sequence& seq, const AppSequence* appSeq); + void generateGroundAnimation(TSShape::Sequence& seq, const AppSequence* appSeq); + void generateFrameTriggers(TSShape::Sequence& seq, const AppSequence* appSeq); + + // Shape construction + void sortDetails(); + void install(); + +public: + TSShapeLoader() : boundsNode(0) { } + virtual ~TSShapeLoader(); + + static const Torque::Path& getShapePath() { return shapePath; } + + TSShape* generateShape(const Torque::Path& path); +}; + +#endif // _TSSHAPE_LOADER_H_ diff --git a/ts/tsAnimate.cpp b/ts/tsAnimate.cpp new file mode 100644 index 0000000..4713519 --- /dev/null +++ b/ts/tsAnimate.cpp @@ -0,0 +1,1083 @@ +//----------------------------------------------------------------------------- +// Torque 3D +// Copyright (C) GarageGames.com, Inc. +//----------------------------------------------------------------------------- + +#include "ts/tsShapeInstance.h" + +//---------------------------------------------------------------------------------- +// some utility functions +//------------------------------------------------------------------------------------- + +S32 QSORT_CALLBACK FN_CDECL compareThreads( const void* e1, const void* e2) +{ + const TSThread * th1 = *(const TSThread**)e1; + const TSThread * th2 = *(const TSThread**)e2; + return (*th1 < *th2); +} + +void sortThreads(Vector & threadList) +{ + dQsort(threadList.address(),threadList.size(),sizeof(TSThread*),compareThreads); +} + +void TSShapeInstance::setDirty(U32 dirty) +{ + AssertFatal((dirty & AllDirtyMask) == dirty,"TSShapeInstance::setDirty: illegal dirty flags"); + for (S32 i=0; isubShapeFirstNode.size(); i++) + mDirtyFlags[i] |= dirty; +} + +void TSShapeInstance::clearDirty(U32 dirty) +{ + AssertFatal((dirty & AllDirtyMask) == dirty,"TSShapeInstance::clearDirty: illegal dirty flags"); + for (S32 i=0; isubShapeFirstNode.size(); i++) + mDirtyFlags[i] &= ~dirty; +} + +//------------------------------------------------------------------------------------- +// Animate nodes +//------------------------------------------------------------------------------------- + +void TSShapeInstance::animateNodes(S32 ss) +{ + if (!mShape->nodes.size()) + return; + + // @todo: When a node is added, we need to make sure to resize the nodeTransforms array as well + if (mShape->nodes.size() > mNodeTransforms.size()) + mNodeTransforms.setSize(mShape->nodes.size()); + + // temporary storage for node transforms + smNodeCurrentRotations.setSize(mShape->nodes.size()); + smNodeCurrentTranslations.setSize(mShape->nodes.size()); + smRotationThreads.setSize(mShape->nodes.size()); + smTranslationThreads.setSize(mShape->nodes.size()); + + TSIntegerSet rotBeenSet; + TSIntegerSet tranBeenSet; + TSIntegerSet scaleBeenSet; + rotBeenSet.setAll(mShape->nodes.size()); + tranBeenSet.setAll(mShape->nodes.size()); + scaleBeenSet.setAll(mShape->nodes.size()); + + S32 i,j,nodeIndex,a,b,start,end,firstBlend = mThreadList.size(); + for (i=0; igetSequence()->isBlend()) + { + // blend sequences need default (if not set by other sequence) + // break rather than continue because the rest will be blends too + firstBlend = i; + break; + } + rotBeenSet.takeAway(th->getSequence()->rotationMatters); + tranBeenSet.takeAway(th->getSequence()->translationMatters); + scaleBeenSet.takeAway(th->getSequence()->scaleMatters); + } + rotBeenSet.takeAway(mCallbackNodes); + rotBeenSet.takeAway(mHandsOffNodes); + rotBeenSet.overlap(mMaskRotationNodes); + + TSIntegerSet maskPosNodes=mMaskPosXNodes; + maskPosNodes.overlap(mMaskPosYNodes); + maskPosNodes.overlap(mMaskPosZNodes); + tranBeenSet.overlap(maskPosNodes); + + tranBeenSet.takeAway(mCallbackNodes); + tranBeenSet.takeAway(mHandsOffNodes); + // can't add masked nodes since x, y, & z masked separately... + // we'll set default regardless of mask status + + // all the nodes marked above need to have the default transform + a = mShape->subShapeFirstNode[ss]; + b = a + mShape->subShapeNumNodes[ss]; + for (i=a; idefaultRotations[i].getQuatF(&smNodeCurrentRotations[i]); + smRotationThreads[i] = NULL; + } + if (tranBeenSet.test(i)) + { + smNodeCurrentTranslations[i] = mShape->defaultTranslations[i]; + smTranslationThreads[i] = NULL; + } + } + + // don't want a transform in these cases... + rotBeenSet.overlap(mHandsOffNodes); + rotBeenSet.overlap(mCallbackNodes); + tranBeenSet.takeAway(maskPosNodes); + tranBeenSet.overlap(mHandsOffNodes); + tranBeenSet.overlap(mCallbackNodes); + + // default scale + if (scaleCurrentlyAnimated()) + handleDefaultScale(a,b,scaleBeenSet); + + // handle non-blend sequences + for (i=0; igetSequence()->rotationMatters.start(); + end = b; + for (nodeIndex=start; nodeIndexgetSequence()->rotationMatters.next(nodeIndex), j++) + { + // skip nodes outside of this detail + if (nodeIndexgetRotation(*th->getSequence(),th->keyNum1,j,&q1); + mShape->getRotation(*th->getSequence(),th->keyNum2,j,&q2); + TSTransform::interpolate(q1,q2,th->keyPos,&smNodeCurrentRotations[nodeIndex]); + rotBeenSet.set(nodeIndex); + smRotationThreads[nodeIndex] = th; + } + } + + j=0; + start = th->getSequence()->translationMatters.start(); + end = b; + for (nodeIndex=start; nodeIndexgetSequence()->translationMatters.next(nodeIndex), j++) + { + if (nodeIndexgetTranslation(*th->getSequence(),th->keyNum1,j); + const Point3F & p2 = mShape->getTranslation(*th->getSequence(),th->keyNum2,j); + TSTransform::interpolate(p1,p2,th->keyPos,&smNodeCurrentTranslations[nodeIndex]); + smTranslationThreads[nodeIndex] = th; + } + tranBeenSet.set(nodeIndex); + } + } + + if (scaleCurrentlyAnimated()) + handleAnimatedScale(th,a,b,scaleBeenSet); + } + + // transitions... + if (inTransition()) + handleTransitionNodes(a,b); + + // @todo: Need to update TSShapeInstances when the number of nodes changes..... + mNodeTransforms.setSize(smNodeCurrentRotations.size()); + + // compute transforms + for (i=a; i=start && nodeIndexsetNodeTransform(this, nodeIndex, mNodeTransforms[nodeIndex]); + } + + // handle blend sequences + for (i=firstBlend; iblendDisabled) + continue; + + handleBlendSequence(th,a,b); + } + + // multiply transforms... + for (i=a; inodes[i].parentIndex; + if (parentIdx>=0) + { + MatrixF localMat = mNodeTransforms[i]; + mNodeTransforms[i].mul(mNodeTransforms[parentIdx],localMat); + } + } +} + +void TSShapeInstance::handleDefaultScale(S32 a, S32 b, TSIntegerSet & scaleBeenSet) +{ + // set default scale values (i.e., identity) and do any initialization + // relating to animated scale (since scale normally not animated) + + smScaleThreads.setSize(mShape->nodes.size()); + scaleBeenSet.takeAway(mCallbackNodes); + scaleBeenSet.takeAway(mHandsOffNodes); + if (animatesUniformScale()) + { + smNodeCurrentUniformScales.setSize(mShape->nodes.size()); + for (S32 i=a; inodes.size()); + for (S32 i=a; inodes.size()); + for (S32 i=a; itransitionData.inTransition ? thread : NULL; + if (!thread) + { + // if not controlled by a sequence in transition then there must be + // some other thread out there that used to control us that is in + // transition now...use that thread to control interpolation + for (S32 i=0; itransitionData.oldRotationNodes.test(nodeIndex) || mTransitionThreads[i]->getSequence()->rotationMatters.test(nodeIndex)) + { + thread = mTransitionThreads[i]; + break; + } + } + AssertFatal(thread!=NULL,"TSShapeInstance::handleRotTransitionNodes (rotation)"); + } + QuatF tmpQ; + TSTransform::interpolate(mNodeReferenceRotations[nodeIndex].getQuatF(&tmpQ),smNodeCurrentRotations[nodeIndex],thread->transitionData.pos,&smNodeCurrentRotations[nodeIndex]); + } + + // then translation + start = mTransitionTranslationNodes.start(); + end = b; + for (nodeIndex=start; nodeIndextransitionData.inTransition ? thread : NULL; + if (!thread) + { + // if not controlled by a sequence in transition then there must be + // some other thread out there that used to control us that is in + // transition now...use that thread to control interpolation + for (S32 i=0; itransitionData.oldTranslationNodes.test(nodeIndex) || mTransitionThreads[i]->getSequence()->translationMatters.test(nodeIndex)) + { + thread = mTransitionThreads[i]; + break; + } + } + AssertFatal(thread!=NULL,"TSShapeInstance::handleTransitionNodes (translation)."); + } + Point3F & p = smNodeCurrentTranslations[nodeIndex]; + Point3F & p1 = mNodeReferenceTranslations[nodeIndex]; + Point3F & p2 = p; + F32 k = thread->transitionData.pos; + p.x = p1.x + k * (p2.x-p1.x); + p.y = p1.y + k * (p2.y-p1.y); + p.z = p1.z + k * (p2.z-p1.z); + } + + // then scale... + if (scaleCurrentlyAnimated()) + { + start = mTransitionScaleNodes.start(); + end = b; + for (nodeIndex=start; nodeIndextransitionData.inTransition ? thread : NULL; + if (!thread) + { + // if not controlled by a sequence in transition then there must be + // some other thread out there that used to control us that is in + // transition now...use that thread to control interpolation + for (S32 i=0; itransitionData.oldScaleNodes.test(nodeIndex) || mTransitionThreads[i]->getSequence()->scaleMatters.test(nodeIndex)) + { + thread = mTransitionThreads[i]; + break; + } + } + AssertFatal(thread!=NULL,"TSShapeInstance::handleTransitionNodes (scale)."); + } + if (animatesUniformScale()) + smNodeCurrentUniformScales[nodeIndex] += thread->transitionData.pos * (mNodeReferenceUniformScales[nodeIndex]-smNodeCurrentUniformScales[nodeIndex]); + else if (animatesAlignedScale()) + TSTransform::interpolate(mNodeReferenceScaleFactors[nodeIndex],smNodeCurrentAlignedScales[nodeIndex],thread->transitionData.pos,&smNodeCurrentAlignedScales[nodeIndex]); + else + { + QuatF q; + TSTransform::interpolate(mNodeReferenceScaleFactors[nodeIndex],smNodeCurrentArbitraryScales[nodeIndex].mScale,thread->transitionData.pos,&smNodeCurrentArbitraryScales[nodeIndex].mScale); + TSTransform::interpolate(mNodeReferenceArbitraryScaleRots[nodeIndex].getQuatF(&q),smNodeCurrentArbitraryScales[nodeIndex].mRotate,thread->transitionData.pos,&smNodeCurrentArbitraryScales[nodeIndex].mRotate); + } + } + } +} + +void TSShapeInstance::handleNodeScale(S32 a, S32 b) +{ + if (animatesUniformScale()) + { + for (S32 i=a; igetSequence()->scaleMatters.start(); + S32 end = b; + + // code the scale conversion (might need to "upgrade" from uniform to arbitrary, e.g.) + // code uniform, aligned, and arbitrary as 0,1, and 2, respectively, + // with sequence coding in first two bits, shape coding in next two bits + S32 code = 0; + if (thread->getSequence()->animatesAlignedScale()) + code += 1; + else if (thread->getSequence()->animatesArbitraryScale()) + code += 2; + if (animatesAlignedScale()) + code +=3; + if (animatesArbitraryScale()) + code += 6; + + F32 uniformScale = 1.0f; + Point3F alignedScale(0.0f, 0.0f, 0.0f); + TSScale arbitraryScale; + for (S32 nodeIndex=start; nodeIndexgetSequence()->scaleMatters.next(nodeIndex), j++) + { + if (nodeIndex uniform + case 1: // uniform -> aligned + case 2: // uniform -> arbitrary + { + F32 s1 = mShape->getUniformScale(*thread->getSequence(),thread->keyNum1,j); + F32 s2 = mShape->getUniformScale(*thread->getSequence(),thread->keyNum2,j); + uniformScale = TSTransform::interpolate(s1,s2,thread->keyPos); + alignedScale.set(uniformScale,uniformScale,uniformScale); + break; + } + case 4: // aligned -> aligned + case 5: // aligned -> arbitrary + { + const Point3F & s1 = mShape->getAlignedScale(*thread->getSequence(),thread->keyNum1,j); + const Point3F & s2 = mShape->getAlignedScale(*thread->getSequence(),thread->keyNum2,j); + TSTransform::interpolate(s1,s2,thread->keyPos,&alignedScale); + break; + } + case 8: // arbitrary -> arbitary + { + TSScale s1,s2; + mShape->getArbitraryScale(*thread->getSequence(),thread->keyNum1,j,&s1); + mShape->getArbitraryScale(*thread->getSequence(),thread->keyNum2,j,&s2); + TSTransform::interpolate(s1,s2,thread->keyPos,&arbitraryScale); + break; + } + default: AssertFatal(0,"TSShapeInstance::handleAnimatedScale"); break; + } + + switch (code) + { + case 0: // uniform -> uniform + { + smNodeCurrentUniformScales[nodeIndex] = uniformScale; + break; + } + case 1: // uniform -> aligned + case 4: // aligned -> aligned + smNodeCurrentAlignedScales[nodeIndex] = alignedScale; + break; + case 2: // uniform -> arbitrary + case 5: // aligned -> arbitrary + { + smNodeCurrentArbitraryScales[nodeIndex].identity(); + smNodeCurrentArbitraryScales[nodeIndex].mScale = alignedScale; + break; + } + case 8: // arbitrary -> arbitary + { + smNodeCurrentArbitraryScales[nodeIndex] = arbitraryScale; + break; + } + default: AssertFatal(0,"TSShapeInstance::handleAnimatedScale"); break; + } + smScaleThreads[nodeIndex] = thread; + scaleBeenSet.set(nodeIndex); + } + } +} + +void TSShapeInstance::handleMaskedPositionNode(TSThread * th, S32 nodeIndex, S32 offset) +{ + const Point3F & p1 = mShape->getTranslation(*th->getSequence(),th->keyNum1,offset); + const Point3F & p2 = mShape->getTranslation(*th->getSequence(),th->keyNum2,offset); + Point3F p; + TSTransform::interpolate(p1,p2,th->keyPos,&p); + + if (!mMaskPosXNodes.test(nodeIndex)) + smNodeCurrentTranslations[nodeIndex].x = p.x; + + if (!mMaskPosYNodes.test(nodeIndex)) + smNodeCurrentTranslations[nodeIndex].y = p.y; + + if (!mMaskPosZNodes.test(nodeIndex)) + smNodeCurrentTranslations[nodeIndex].z = p.z; +} + +void TSShapeInstance::handleBlendSequence(TSThread * thread, S32 a, S32 b) +{ + S32 jrot=0; + S32 jtrans=0; + S32 jscale=0; + TSIntegerSet nodeMatters = thread->getSequence()->translationMatters; + nodeMatters.overlap(thread->getSequence()->rotationMatters); + nodeMatters.overlap(thread->getSequence()->scaleMatters); + S32 start = nodeMatters.start(); + S32 end = b; + for (S32 nodeIndex=start; nodeIndexgetSequence()->rotationMatters.test(nodeIndex)) + jrot++; + if (thread->getSequence()->translationMatters.test(nodeIndex)) + jtrans++; + if (thread->getSequence()->scaleMatters.test(nodeIndex)) + jscale++; + continue; + } + + MatrixF mat(true); + if (thread->getSequence()->rotationMatters.test(nodeIndex)) + { + QuatF q1,q2; + mShape->getRotation(*thread->getSequence(),thread->keyNum1,jrot,&q1); + mShape->getRotation(*thread->getSequence(),thread->keyNum2,jrot,&q2); + QuatF quat; + TSTransform::interpolate(q1,q2,thread->keyPos,&quat); + TSTransform::setMatrix(quat,&mat); + jrot++; + } + + if (thread->getSequence()->translationMatters.test(nodeIndex)) + { + const Point3F & p1 = mShape->getTranslation(*thread->getSequence(),thread->keyNum1,jtrans); + const Point3F & p2 = mShape->getTranslation(*thread->getSequence(),thread->keyNum2,jtrans); + Point3F p; + TSTransform::interpolate(p1,p2,thread->keyPos,&p); + mat.setColumn(3,p); + jtrans++; + } + + if (thread->getSequence()->scaleMatters.test(nodeIndex)) + { + if (thread->getSequence()->animatesUniformScale()) + { + F32 s1 = mShape->getUniformScale(*thread->getSequence(),thread->keyNum1,jscale); + F32 s2 = mShape->getUniformScale(*thread->getSequence(),thread->keyNum2,jscale); + F32 scale = TSTransform::interpolate(s1,s2,thread->keyPos); + TSTransform::applyScale(scale,&mat); + } + else if (animatesAlignedScale()) + { + Point3F s1 = mShape->getAlignedScale(*thread->getSequence(),thread->keyNum1,jscale); + Point3F s2 = mShape->getAlignedScale(*thread->getSequence(),thread->keyNum2,jscale); + Point3F scale; + TSTransform::interpolate(s1,s2,thread->keyPos,&scale); + TSTransform::applyScale(scale,&mat); + } + else + { + TSScale s1,s2; + mShape->getArbitraryScale(*thread->getSequence(),thread->keyNum1,jscale,&s1); + mShape->getArbitraryScale(*thread->getSequence(),thread->keyNum2,jscale,&s2); + TSScale scale; + TSTransform::interpolate(s1,s2,thread->keyPos,&scale); + TSTransform::applyScale(scale,&mat); + } + jscale++; + } + + // apply blend transform + mNodeTransforms[nodeIndex].mul(mat); + } +} + +//------------------------------------------------------------------------------------- +// Other Animation: +//------------------------------------------------------------------------------------- + +void TSShapeInstance::animateIfls() +{ + // for each ifl material decide which thread controls it and set it up + for (S32 i=0; igetSequence()->iflMatters.test(i)) + { + // lookup ifl properties + S32 firstFrameOffTimeIndex = iflMaterialInstance.iflMaterial->firstFrameOffTimeIndex; + S32 numFrames = iflMaterialInstance.iflMaterial->numFrames; + F32 iflDur = numFrames ? mShape->iflFrameOffTimes[firstFrameOffTimeIndex+numFrames-1] : 0.0f; + // where are we in the ifl + F32 time = th->pos * th->getSequence()->duration + th->getSequence()->toolBegin; + if (time>iflDur && iflDur>0.0f) + // handle looping ifl + time -= iflDur * (F32) ((S32) (time/iflDur)); + // look up frame -- consider binary search + S32 k; + for (k=0; k mShape->iflFrameOffTimes[firstFrameOffTimeIndex+k]; k++) + ; + iflMaterialInstance.frame = k; + break; + } + } + } + + // ifl is same for all sub-shapes, so clear them all out now + clearDirty(IflDirty); +} + +void TSShapeInstance::animateVisibility(S32 ss) +{ + S32 i; + if (!mMeshObjects.size()) + return; + + // find out who needs default values set + TSIntegerSet beenSet; + beenSet.setAll(mMeshObjects.size()); + for (i=0; igetSequence()->visMatters); + + // set defaults + S32 a = mShape->subShapeFirstObject[ss]; + S32 b = a + mShape->subShapeNumObjects[ss]; + for (i=a; i